2828#include < utility>
2929#include < vector>
3030
31+ #include " absl/container/btree_map.h"
32+ #include " absl/container/btree_set.h"
3133#include " absl/status/status.h"
3234#include " absl/status/statusor.h"
3335#include " absl/strings/str_cat.h"
@@ -143,9 +145,9 @@ absl::StatusOr<PoolReshardPlan> BuildPoolReshardPlan(
143145 return absl::InvalidArgumentError (
144146 " uuid must be positive for pool resharding" );
145147 }
146- if (request.dst_units .size () != 1 ) {
148+ if (request.dst_units .empty () ) {
147149 return absl::InvalidArgumentError (
148- " Pool resharding requires exactly one destination unit" );
150+ " Pool resharding requires at least one destination unit" );
149151 }
150152 {
151153 std::set<RaidenId, RequestBlockRegistry::RaidenIdLess> unique_src (
@@ -179,16 +181,18 @@ absl::StatusOr<PoolReshardPlan> BuildPoolReshardPlan(
179181 auto dst_by_unit_or = MetadataByUnit (request.dst_metadata , request.dst_units );
180182 if (!dst_by_unit_or.ok ()) return dst_by_unit_or.status ();
181183 auto & dst_by_unit = *dst_by_unit_or;
182- const RaidenId& dst_unit = request. dst_units [ 0 ];
184+ // All destinations must share one local pool geometry (validated below).
183185 const tpu_sync::rpc::RegisterWorkUnitRequest& dst_meta =
184- *dst_by_unit.at (dst_unit );
186+ *dst_by_unit.at (request. dst_units [ 0 ] );
185187
186188 std::vector<const tpu_sync::rpc::RegisterWorkUnitRequest*> all_metadata;
187- all_metadata.reserve (request.src_units .size () + 1 );
189+ all_metadata.reserve (request.src_units .size () + request. dst_units . size () );
188190 for (const RaidenId& unit : request.src_units ) {
189191 all_metadata.push_back (src_by_unit.at (unit));
190192 }
191- all_metadata.push_back (&dst_meta);
193+ for (const RaidenId& unit : request.dst_units ) {
194+ all_metadata.push_back (dst_by_unit.at (unit));
195+ }
192196 for (const auto * meta : all_metadata) {
193197 RaidenId unit = RaidenIdFromProto (meta->unit ());
194198 if (meta->layout_fingerprint ().empty ()) {
@@ -227,6 +231,7 @@ absl::StatusOr<PoolReshardPlan> BuildPoolReshardPlan(
227231 }
228232
229233 std::vector<std::pair<std::string, std::string>> dst_identity;
234+ dst_identity.reserve (dst_meta.pools ().size ());
230235 for (const auto & pool : dst_meta.pools ()) {
231236 dst_identity.emplace_back (pool.tag (), pool.dtype_tag ());
232237 }
@@ -235,8 +240,10 @@ absl::StatusOr<PoolReshardPlan> BuildPoolReshardPlan(
235240 " Destination pool manifest must not be empty" );
236241 }
237242 for (const RaidenId& src_unit : request.src_units ) {
243+ const auto & src_pools = src_by_unit.at (src_unit)->pools ();
238244 std::vector<std::pair<std::string, std::string>> src_identity;
239- for (const auto & pool : src_by_unit.at (src_unit)->pools ()) {
245+ src_identity.reserve (src_pools.size ());
246+ for (const auto & pool : src_pools) {
240247 src_identity.emplace_back (pool.tag (), pool.dtype_tag ());
241248 }
242249 if (src_identity != dst_identity) {
@@ -246,6 +253,37 @@ absl::StatusOr<PoolReshardPlan> BuildPoolReshardPlan(
246253 PythonRepr (src_unit)));
247254 }
248255 }
256+ {
257+ std::vector<std::string> reference_dst_geometry;
258+ reference_dst_geometry.reserve (dst_meta.pools ().size ());
259+ for (const auto & pool : dst_meta.pools ()) {
260+ reference_dst_geometry.push_back (GeometrySignature (pool));
261+ }
262+ for (size_t i = 1 ; i < request.dst_units .size (); ++i) {
263+ const RaidenId& unit = request.dst_units [i];
264+ const auto & other_meta = *dst_by_unit.at (unit);
265+ std::vector<std::pair<std::string, std::string>> other_identity;
266+ other_identity.reserve (other_meta.pools ().size ());
267+ for (const auto & pool : other_meta.pools ()) {
268+ other_identity.emplace_back (pool.tag (), pool.dtype_tag ());
269+ }
270+ if (other_identity != dst_identity) {
271+ return absl::InvalidArgumentError (absl::StrCat (
272+ " Canonical pool manifest mismatch between destinations at " ,
273+ PythonRepr (unit)));
274+ }
275+ std::vector<std::string> other_geometry;
276+ other_geometry.reserve (other_meta.pools ().size ());
277+ for (const auto & pool : other_meta.pools ()) {
278+ other_geometry.push_back (GeometrySignature (pool));
279+ }
280+ if (other_geometry != reference_dst_geometry) {
281+ return absl::InvalidArgumentError (
282+ absl::StrCat (" Destination pool geometry differs across units at " ,
283+ PythonRepr (unit)));
284+ }
285+ }
286+ }
249287
250288 const tpu_sync::rpc::RegisterWorkUnitRequest& reference_src =
251289 *src_by_unit.at (request.src_units [0 ]);
@@ -484,7 +522,23 @@ absl::StatusOr<PoolReshardPlan> BuildPoolReshardPlan(
484522 // Per-tag planning: each requested tag selects its own pools, owns its
485523 // own destination block-id space and coverage validation, and emits one
486524 // entry group.
487- const std::string dst_peer = dst_meta.shards (0 );
525+ absl::btree_map<RaidenId, std::string, RequestBlockRegistry::RaidenIdLess>
526+ dst_peers;
527+ {
528+ absl::btree_set<std::string> unique_peers;
529+ for (const RaidenId& unit : request.dst_units ) {
530+ const std::string peer = dst_by_unit.at (unit)->shards (0 );
531+ unique_peers.insert (peer);
532+ dst_peers.emplace (unit, peer);
533+ }
534+ if (unique_peers.size () != request.dst_units .size ()) {
535+ std::vector<std::string> sorted_peers (unique_peers.begin (),
536+ unique_peers.end ());
537+ return absl::InvalidArgumentError (absl::StrCat (
538+ " Destinations must register distinct data-plane endpoints; got " ,
539+ PyStrListRepr (sorted_peers)));
540+ }
541+ }
488542 std::map<RaidenId, std::vector<ScheduleEntry>,
489543 RequestBlockRegistry::RaidenIdLess>
490544 schedules;
@@ -736,8 +790,11 @@ absl::StatusOr<PoolReshardPlan> BuildPoolReshardPlan(
736790 return std::tie (a.span ->dst_block_index , a.span ->dst_offset_bytes ) <
737791 std::tie (b.span ->dst_block_index , b.span ->dst_offset_bytes );
738792 });
739- std::map<RaidenId, std::set<std::tuple<std::string, int64_t , int64_t >>,
740- RequestBlockRegistry::RaidenIdLess>
793+ std::map<
794+ RaidenId,
795+ std::map<RaidenId, std::set<std::tuple<std::string, int64_t , int64_t >>,
796+ RequestBlockRegistry::RaidenIdLess>,
797+ RequestBlockRegistry::RaidenIdLess>
741798 transfer_pairs_per_sender;
742799 for (const OrderedSpan& ordered : ordered_spans) {
743800 const PoolByteSpan& span = *ordered.span ;
@@ -754,37 +811,57 @@ absl::StatusOr<PoolReshardPlan> BuildPoolReshardPlan(
754811 TranslateLiveCopy (precheck.src_segments , precheck.dst_segments ,
755812 src_offset, dst_offset, span.size_bytes );
756813 if (!translated.ok ()) return translated.status ();
757- emitted_chunks += static_cast <int64_t >(translated->size ());
814+ emitted_chunks += static_cast <int64_t >(translated->size ()) *
815+ static_cast <int64_t >(request.dst_units .size ());
758816 if (emitted_chunks > kMaxLiveSegments ) {
759817 return absl::InvalidArgumentError (
760818 " Byte-span plan exceeds the live-region expansion bound" );
761819 }
820+ // Replicated caches: every destination receives the identical
821+ // chunk, so emission is the (chunk x destination) cross product and
822+ // entries differ only in dst_peer. For supporting sharded
823+ // destionations, this needs to be updated.
762824 for (const LiveCopyChunk& chunk : *translated) {
763- ScheduleEntry schedule_entry;
764- schedule_entry.dst_peer = dst_peer;
765- schedule_entry.dst_shard_idx = 0 ;
766- schedule_entry.dst_offset_bytes = chunk.dst_physical ;
767- schedule_entry.src_offset_bytes = chunk.src_physical ;
768- schedule_entry.size_bytes = chunk.size ;
769- schedule_entry.src_block_id = src_block_id;
770- schedule_entry.dst_block_id = dst_block_id;
771- schedule_entry.src_stride_bytes = 0 ;
772- schedule_entry.dst_stride_bytes = 0 ;
773- schedule_entry.count = 1 ;
774- schedule_entry.layer_idx = 0 ;
775- schedule_entry.pool_group = static_cast <int32_t >(group_idx);
776- schedules[src_unit].push_back (std::move (schedule_entry));
825+ for (const RaidenId& dst_unit_id : request.dst_units ) {
826+ ScheduleEntry schedule_entry;
827+ schedule_entry.dst_peer = dst_peers.at (dst_unit_id);
828+ schedule_entry.dst_shard_idx = 0 ;
829+ schedule_entry.dst_offset_bytes = chunk.dst_physical ;
830+ schedule_entry.src_offset_bytes = chunk.src_physical ;
831+ schedule_entry.size_bytes = chunk.size ;
832+ schedule_entry.src_block_id = src_block_id;
833+ schedule_entry.dst_block_id = dst_block_id;
834+ schedule_entry.src_stride_bytes = 0 ;
835+ schedule_entry.dst_stride_bytes = 0 ;
836+ schedule_entry.count = 1 ;
837+ schedule_entry.layer_idx = 0 ;
838+ schedule_entry.pool_group = static_cast <int32_t >(group_idx);
839+ schedules[src_unit].push_back (std::move (schedule_entry));
840+ }
777841 }
778842 }
779- transfer_pairs_per_sender[src_unit].insert (
780- std::make_tuple (dst_peer, src_block_id, dst_block_id));
843+ auto & sender_pairs = transfer_pairs_per_sender[src_unit];
844+ for (const RaidenId& dst_unit_id : request.dst_units ) {
845+ sender_pairs[dst_unit_id].insert (std::make_tuple (
846+ dst_peers.at (dst_unit_id), src_block_id, dst_block_id));
847+ }
781848 }
782849
783- int64_t group_expected_pushes = 0 ;
784- for (const auto & [unit, pairs] : transfer_pairs_per_sender) {
785- group_expected_pushes +=
786- std::min (requested_parallelism, static_cast <int64_t >(pairs.size ()));
850+ // Computed expected pushes for the receiver.
851+ std::map<RaidenId, int32_t , RequestBlockRegistry::RaidenIdLess>
852+ expected_pushes_by_dst;
853+ for (const RaidenId& dst_unit_id : request.dst_units ) {
854+ int64_t dst_pushes = 0 ;
855+ for (const auto & [unit, by_dst] : transfer_pairs_per_sender) {
856+ auto pairs_it = by_dst.find (dst_unit_id);
857+ if (pairs_it == by_dst.end ()) continue ;
858+ dst_pushes += std::min (requested_parallelism,
859+ static_cast <int64_t >(pairs_it->second .size ()));
860+ }
861+ expected_pushes_by_dst[dst_unit_id] = static_cast <int32_t >(dst_pushes);
787862 }
863+ const int64_t group_expected_pushes =
864+ expected_pushes_by_dst.at (request.dst_units [0 ]);
788865 if (group_expected_pushes <= 0 ) {
789866 return absl::InvalidArgumentError (
790867 absl::StrCat (" Pool reshard plan contains no source pushes for tag " ,
@@ -793,7 +870,7 @@ absl::StatusOr<PoolReshardPlan> BuildPoolReshardPlan(
793870 PlanPoolGroup group;
794871 group.pool_indices = precheck.selected ;
795872 group.dst_device_block_ids = dst_ids_g;
796- group.expected_pushes = static_cast < int32_t >(group_expected_pushes );
873+ group.expected_pushes_by_dst = std::move (expected_pushes_by_dst );
797874 group.dst_expected_extent_bytes = extents;
798875 // FA (the first requested tag by connector convention) uploads first;
799876 // state classes land after it on aliased arena pages.
@@ -830,17 +907,18 @@ absl::StatusOr<PoolReshardPlan> BuildPoolReshardPlan(
830907 plan.src_units .push_back (unit);
831908 }
832909 }
833- plan.dst_unit = dst_unit ;
910+ plan.dst_units = request. dst_units ;
834911 plan.schedules = std::move (schedules);
835912 for (const auto * meta : all_metadata) {
836913 plan.worker_rpc_addresses [RaidenIdFromProto (meta->unit ())] =
837914 meta->control_plane_rpc_address ();
838915 }
839- plan.dst_peer = dst_peer ;
916+ plan.dst_peers = std::move (dst_peers) ;
840917 plan.uuid = uuid;
841918 plan.req_id = req_id;
842919 plan.expected_block_count = static_cast <int64_t >(dst_ids.size ());
843- plan.expected_pushes_per_pool = pool_groups[0 ].expected_pushes ;
920+ plan.expected_pushes_per_pool =
921+ pool_groups[0 ].expected_pushes_by_dst .at (request.dst_units [0 ]);
844922 plan.transfer_pool_indices = union_selected;
845923 for (const auto & pool : dst_meta.pools ()) {
846924 plan.pool_dtype_tags .push_back (pool.dtype_tag ());
0 commit comments