@@ -143,9 +143,9 @@ absl::StatusOr<PoolReshardPlan> BuildPoolReshardPlan(
143143 return absl::InvalidArgumentError (
144144 " uuid must be positive for pool resharding" );
145145 }
146- if (request.dst_units .size () != 1 ) {
146+ if (request.dst_units .empty () ) {
147147 return absl::InvalidArgumentError (
148- " Pool resharding requires exactly one destination unit" );
148+ " Pool resharding requires at least one destination unit" );
149149 }
150150 {
151151 std::set<RaidenId, RequestBlockRegistry::RaidenIdLess> unique_src (
@@ -179,16 +179,18 @@ absl::StatusOr<PoolReshardPlan> BuildPoolReshardPlan(
179179 auto dst_by_unit_or = MetadataByUnit (request.dst_metadata , request.dst_units );
180180 if (!dst_by_unit_or.ok ()) return dst_by_unit_or.status ();
181181 auto & dst_by_unit = *dst_by_unit_or;
182- const RaidenId& dst_unit = request. dst_units [ 0 ];
182+ // All destinations must share one local pool geometry (validated below).
183183 const tpu_sync::rpc::RegisterWorkUnitRequest& dst_meta =
184- *dst_by_unit.at (dst_unit );
184+ *dst_by_unit.at (request. dst_units [ 0 ] );
185185
186186 std::vector<const tpu_sync::rpc::RegisterWorkUnitRequest*> all_metadata;
187- all_metadata.reserve (request.src_units .size () + 1 );
187+ all_metadata.reserve (request.src_units .size () + request. dst_units . size () );
188188 for (const RaidenId& unit : request.src_units ) {
189189 all_metadata.push_back (src_by_unit.at (unit));
190190 }
191- all_metadata.push_back (&dst_meta);
191+ for (const RaidenId& unit : request.dst_units ) {
192+ all_metadata.push_back (dst_by_unit.at (unit));
193+ }
192194 for (const auto * meta : all_metadata) {
193195 RaidenId unit = RaidenIdFromProto (meta->unit ());
194196 if (meta->layout_fingerprint ().empty ()) {
@@ -246,6 +248,34 @@ absl::StatusOr<PoolReshardPlan> BuildPoolReshardPlan(
246248 PythonRepr (src_unit)));
247249 }
248250 }
251+ {
252+ std::vector<std::string> reference_dst_geometry;
253+ for (const auto & pool : dst_meta.pools ()) {
254+ reference_dst_geometry.push_back (GeometrySignature (pool));
255+ }
256+ for (size_t i = 1 ; i < request.dst_units .size (); ++i) {
257+ const RaidenId& unit = request.dst_units [i];
258+ const auto & other_meta = *dst_by_unit.at (unit);
259+ std::vector<std::pair<std::string, std::string>> other_identity;
260+ for (const auto & pool : other_meta.pools ()) {
261+ other_identity.emplace_back (pool.tag (), pool.dtype_tag ());
262+ }
263+ if (other_identity != dst_identity) {
264+ return absl::InvalidArgumentError (absl::StrCat (
265+ " Canonical pool manifest mismatch between destinations at " ,
266+ PythonRepr (unit)));
267+ }
268+ std::vector<std::string> other_geometry;
269+ for (const auto & pool : other_meta.pools ()) {
270+ other_geometry.push_back (GeometrySignature (pool));
271+ }
272+ if (other_geometry != reference_dst_geometry) {
273+ return absl::InvalidArgumentError (
274+ absl::StrCat (" Destination pool geometry differs across units at " ,
275+ PythonRepr (unit)));
276+ }
277+ }
278+ }
249279
250280 const tpu_sync::rpc::RegisterWorkUnitRequest& reference_src =
251281 *src_by_unit.at (request.src_units [0 ]);
@@ -484,7 +514,22 @@ absl::StatusOr<PoolReshardPlan> BuildPoolReshardPlan(
484514 // Per-tag planning: each requested tag selects its own pools, owns its
485515 // own destination block-id space and coverage validation, and emits one
486516 // entry group.
487- const std::string dst_peer = dst_meta.shards (0 );
517+ std::map<RaidenId, std::string, RequestBlockRegistry::RaidenIdLess> dst_peers;
518+ {
519+ std::set<std::string> unique_peers;
520+ for (const RaidenId& unit : request.dst_units ) {
521+ const std::string peer = dst_by_unit.at (unit)->shards (0 );
522+ unique_peers.insert (peer);
523+ dst_peers.emplace (unit, peer);
524+ }
525+ if (unique_peers.size () != request.dst_units .size ()) {
526+ std::vector<std::string> sorted_peers (unique_peers.begin (),
527+ unique_peers.end ());
528+ return absl::InvalidArgumentError (absl::StrCat (
529+ " Destinations must register distinct data-plane endpoints; got " ,
530+ PyStrListRepr (sorted_peers)));
531+ }
532+ }
488533 std::map<RaidenId, std::vector<ScheduleEntry>,
489534 RequestBlockRegistry::RaidenIdLess>
490535 schedules;
@@ -736,8 +781,11 @@ absl::StatusOr<PoolReshardPlan> BuildPoolReshardPlan(
736781 return std::tie (a.span ->dst_block_index , a.span ->dst_offset_bytes ) <
737782 std::tie (b.span ->dst_block_index , b.span ->dst_offset_bytes );
738783 });
739- std::map<RaidenId, std::set<std::tuple<std::string, int64_t , int64_t >>,
740- RequestBlockRegistry::RaidenIdLess>
784+ std::map<
785+ RaidenId,
786+ std::map<RaidenId, std::set<std::tuple<std::string, int64_t , int64_t >>,
787+ RequestBlockRegistry::RaidenIdLess>,
788+ RequestBlockRegistry::RaidenIdLess>
741789 transfer_pairs_per_sender;
742790 for (const OrderedSpan& ordered : ordered_spans) {
743791 const PoolByteSpan& span = *ordered.span ;
@@ -754,37 +802,57 @@ absl::StatusOr<PoolReshardPlan> BuildPoolReshardPlan(
754802 TranslateLiveCopy (precheck.src_segments , precheck.dst_segments ,
755803 src_offset, dst_offset, span.size_bytes );
756804 if (!translated.ok ()) return translated.status ();
757- emitted_chunks += static_cast <int64_t >(translated->size ());
805+ emitted_chunks += static_cast <int64_t >(translated->size ()) *
806+ static_cast <int64_t >(request.dst_units .size ());
758807 if (emitted_chunks > kMaxLiveSegments ) {
759808 return absl::InvalidArgumentError (
760809 " Byte-span plan exceeds the live-region expansion bound" );
761810 }
811+ // Replicated caches: every destination receives the identical
812+ // chunk, so emission is the (chunk x destination) cross product and
813+ // entries differ only in dst_peer. For supporting sharded
814+ // destionations, this needs to be updated.
762815 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));
816+ for (const RaidenId& dst_unit_id : request.dst_units ) {
817+ ScheduleEntry schedule_entry;
818+ schedule_entry.dst_peer = dst_peers.at (dst_unit_id);
819+ schedule_entry.dst_shard_idx = 0 ;
820+ schedule_entry.dst_offset_bytes = chunk.dst_physical ;
821+ schedule_entry.src_offset_bytes = chunk.src_physical ;
822+ schedule_entry.size_bytes = chunk.size ;
823+ schedule_entry.src_block_id = src_block_id;
824+ schedule_entry.dst_block_id = dst_block_id;
825+ schedule_entry.src_stride_bytes = 0 ;
826+ schedule_entry.dst_stride_bytes = 0 ;
827+ schedule_entry.count = 1 ;
828+ schedule_entry.layer_idx = 0 ;
829+ schedule_entry.pool_group = static_cast <int32_t >(group_idx);
830+ schedules[src_unit].push_back (std::move (schedule_entry));
831+ }
777832 }
778833 }
779- transfer_pairs_per_sender[src_unit].insert (
780- std::make_tuple (dst_peer, src_block_id, dst_block_id));
834+ auto & sender_pairs = transfer_pairs_per_sender[src_unit];
835+ for (const RaidenId& dst_unit_id : request.dst_units ) {
836+ sender_pairs[dst_unit_id].insert (std::make_tuple (
837+ dst_peers.at (dst_unit_id), src_block_id, dst_block_id));
838+ }
781839 }
782840
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 ()));
841+ // Computed expected pushes for the receiver.
842+ std::map<RaidenId, int32_t , RequestBlockRegistry::RaidenIdLess>
843+ expected_pushes_by_dst;
844+ for (const RaidenId& dst_unit_id : request.dst_units ) {
845+ int64_t dst_pushes = 0 ;
846+ for (const auto & [unit, by_dst] : transfer_pairs_per_sender) {
847+ auto pairs_it = by_dst.find (dst_unit_id);
848+ if (pairs_it == by_dst.end ()) continue ;
849+ dst_pushes += std::min (requested_parallelism,
850+ static_cast <int64_t >(pairs_it->second .size ()));
851+ }
852+ expected_pushes_by_dst[dst_unit_id] = static_cast <int32_t >(dst_pushes);
787853 }
854+ const int64_t group_expected_pushes =
855+ expected_pushes_by_dst.at (request.dst_units [0 ]);
788856 if (group_expected_pushes <= 0 ) {
789857 return absl::InvalidArgumentError (
790858 absl::StrCat (" Pool reshard plan contains no source pushes for tag " ,
@@ -793,7 +861,7 @@ absl::StatusOr<PoolReshardPlan> BuildPoolReshardPlan(
793861 PlanPoolGroup group;
794862 group.pool_indices = precheck.selected ;
795863 group.dst_device_block_ids = dst_ids_g;
796- group.expected_pushes = static_cast < int32_t >(group_expected_pushes );
864+ group.expected_pushes_by_dst = std::move (expected_pushes_by_dst );
797865 group.dst_expected_extent_bytes = extents;
798866 // FA (the first requested tag by connector convention) uploads first;
799867 // state classes land after it on aliased arena pages.
@@ -830,17 +898,18 @@ absl::StatusOr<PoolReshardPlan> BuildPoolReshardPlan(
830898 plan.src_units .push_back (unit);
831899 }
832900 }
833- plan.dst_unit = dst_unit ;
901+ plan.dst_units = request. dst_units ;
834902 plan.schedules = std::move (schedules);
835903 for (const auto * meta : all_metadata) {
836904 plan.worker_rpc_addresses [RaidenIdFromProto (meta->unit ())] =
837905 meta->control_plane_rpc_address ();
838906 }
839- plan.dst_peer = dst_peer ;
907+ plan.dst_peers = std::move (dst_peers) ;
840908 plan.uuid = uuid;
841909 plan.req_id = req_id;
842910 plan.expected_block_count = static_cast <int64_t >(dst_ids.size ());
843- plan.expected_pushes_per_pool = pool_groups[0 ].expected_pushes ;
911+ plan.expected_pushes_per_pool =
912+ pool_groups[0 ].expected_pushes_by_dst .at (request.dst_units [0 ]);
844913 plan.transfer_pool_indices = union_selected;
845914 for (const auto & pool : dst_meta.pools ()) {
846915 plan.pool_dtype_tags .push_back (pool.dtype_tag ());
0 commit comments