Skip to content

Commit 4a5c430

Browse files
HarutMovcopybara-github
authored andcommitted
A TP>1 decode engine with replicated KV caches needs the same bytes on all of its workers, but a pool-reshard plan allowed exactly one destination.
The changes are for supporting transfer to N destinations. The gist of the changes are: 1. The planner accepts a destination list (all must have identical pool geometry) 2. emits each copy instruction once per destination, computes expected push counts per destination etc. 3. the coordinator arms all receivers concurrently before dispatching each sender, with each arm carrying only that receiver's schedule slice and push count. This covers caches that are fully replicated on every destination rank: each destination receives the identical byte set. Real resharding to sharded destinations (different bytes per rank, e.g. head-split KV caches) is not supported yet and would build on top of this by mapping each declared span to one destination instead of all of them PiperOrigin-RevId: 968553686
1 parent 8fb3d5f commit 4a5c430

4 files changed

Lines changed: 308 additions & 73 deletions

File tree

tpu_sync/kv_cache/reshard/pool_reshard_planner.cc

Lines changed: 103 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -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());

tpu_sync/kv_cache/reshard/pool_reshard_planner.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,20 @@ struct ScheduleEntry {
5252
struct PlanPoolGroup {
5353
std::vector<int32_t> pool_indices;
5454
std::vector<int64_t> dst_device_block_ids;
55-
int32_t expected_pushes = 0;
55+
std::map<RaidenId, int32_t, RequestBlockRegistry::RaidenIdLess>
56+
expected_pushes_by_dst;
5657
std::vector<int64_t> dst_expected_extent_bytes;
5758
int32_t order_rank = 0;
5859
};
5960

6061
// The pool-path subset of TransferPlan that the encoder and coordinator
6162
// consume. Field-for-field mirror of _build_byte_span_plan_claimed's
6263
// return value.
64+
// Note: Multi-destination plans replicate one identical byte set
65+
// to every destination (TP>1 decode engine).
6366
struct PoolReshardPlan {
6467
std::vector<RaidenId> src_units; // active source units, rank order
65-
RaidenId dst_unit;
68+
std::vector<RaidenId> dst_units;
6669
// Per source unit: shard 0's entry list (pool planning enforces one
6770
// endpoint per unit, so the inner Python dict always has the single key
6871
// 0). Keyed in src_units order.
@@ -71,7 +74,8 @@ struct PoolReshardPlan {
7174
schedules;
7275
std::map<RaidenId, std::string, RequestBlockRegistry::RaidenIdLess>
7376
worker_rpc_addresses;
74-
std::string dst_peer; // worker_data_addresses[dst_unit][0]
77+
// worker_data_addresses[unit][0] per destination unit.
78+
std::map<RaidenId, std::string, RequestBlockRegistry::RaidenIdLess> dst_peers;
7579
int64_t uuid = 0;
7680
std::string req_id;
7781
int64_t expected_block_count = 0;

tpu_sync/kv_cache/reshard/reshard_coordinator.cc

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,20 @@ tpu_sync::rpc::StartTransferRequest BuildStartTransferForTarget(
110110
const PoolReshardPlan& plan, const RaidenId& target) {
111111
const bool is_sender = std::find(plan.src_units.begin(), plan.src_units.end(),
112112
target) != plan.src_units.end();
113+
const bool is_receiver =
114+
std::find(plan.dst_units.begin(), plan.dst_units.end(), target) !=
115+
plan.dst_units.end();
113116
tpu_sync::rpc::StartTransferRequest start_req;
114117
for (const RaidenId& unit : plan.src_units) {
115118
*start_req.add_src_units() = RaidenIdToProto(unit);
116119
}
117-
*start_req.add_dst_units() = RaidenIdToProto(plan.dst_unit);
120+
if (is_receiver) {
121+
*start_req.add_dst_units() = RaidenIdToProto(target);
122+
} else {
123+
for (const RaidenId& unit : plan.dst_units) {
124+
*start_req.add_dst_units() = RaidenIdToProto(unit);
125+
}
126+
}
118127
start_req.set_uuid(plan.uuid);
119128
start_req.set_is_sender(is_sender);
120129
start_req.set_dst_mem_type(tpu_sync::rpc::MEMORY_TYPE_HBM);
@@ -141,7 +150,13 @@ tpu_sync::rpc::StartTransferRequest BuildStartTransferForTarget(
141150
for (int64_t block_id : group.dst_device_block_ids) {
142151
group_proto->add_dst_device_block_ids(block_id);
143152
}
144-
group_proto->set_expected_pushes(group.expected_pushes);
153+
const RaidenId& count_key = is_receiver ? target : plan.dst_units[0];
154+
int32_t expected_pushes = 0;
155+
auto by_dst_it = group.expected_pushes_by_dst.find(count_key);
156+
if (by_dst_it != group.expected_pushes_by_dst.end()) {
157+
expected_pushes = by_dst_it->second;
158+
}
159+
group_proto->set_expected_pushes(expected_pushes);
145160
for (int64_t extent : group.dst_expected_extent_bytes) {
146161
group_proto->add_dst_expected_extent_bytes(extent);
147162
}
@@ -171,16 +186,17 @@ tpu_sync::rpc::StartTransferRequest BuildStartTransferForTarget(
171186
}
172187
};
173188

174-
if (target == plan.dst_unit) {
189+
if (is_receiver) {
175190
// Receiver path: every source's schedule, keyed by source ordinal,
176191
// filtered to entries targeting this receiver (single-endpoint pool
177192
// plans always match).
193+
const std::string& target_peer = plan.dst_peers.at(target);
178194
for (const auto& [src_unit, entries] : plan.schedules) {
179195
auto key_it = plan.src_schedule_keys.find(src_unit);
180196
if (key_it == plan.src_schedule_keys.end()) continue;
181197
std::vector<ScheduleEntry> filtered;
182198
for (const ScheduleEntry& entry : entries) {
183-
if (entry.dst_peer == plan.dst_peer) filtered.push_back(entry);
199+
if (entry.dst_peer == target_peer) filtered.push_back(entry);
184200
}
185201
if (filtered.empty()) continue;
186202
tpu_sync::rpc::ShardPushScheduleProto schedule_proto;
@@ -205,7 +221,9 @@ std::string EncodeStartTransfer(const PoolReshardPlan& plan,
205221
tpu_sync::rpc::ControlRequest req;
206222
req.set_command(tpu_sync::rpc::ControlRequest::COMMAND_START_TRANSFER);
207223
// peers: the destination units' data endpoints (one dst, one endpoint).
208-
req.add_peers(plan.dst_peer);
224+
for (const RaidenId& unit : plan.dst_units) {
225+
req.add_peers(plan.dst_peers.at(unit));
226+
}
209227
*req.mutable_start_transfer_request() =
210228
BuildStartTransferForTarget(plan, target);
211229
return req.SerializeAsString();
@@ -354,20 +372,32 @@ absl::Status ReshardCoordinator::ExecutePoolReshard(
354372
const int64_t plan_build_end_ns = MonotonicNs();
355373

356374
// Receivers must be armed before any sender can put bytes on the wire.
375+
// Multi-destination plans arm every receiver concurrently and join
376+
// before dispatching senders.
357377
const int64_t receiver_arm_start_ns = MonotonicNs();
358378
{
359-
auto addr_it = plan.worker_rpc_addresses.find(plan.dst_unit);
360-
if (addr_it == plan.worker_rpc_addresses.end()) {
361-
registry_->AbandonClaim(args.req_id, uuid, claim_owner);
362-
return absl::InternalError(absl::StrCat(
363-
"No control endpoint recorded for ", PythonRepr(plan.dst_unit)));
379+
std::vector<absl::Status> arm_status(plan.dst_units.size());
380+
std::vector<std::thread> armers;
381+
armers.reserve(plan.dst_units.size());
382+
for (size_t i = 0; i < plan.dst_units.size(); ++i) {
383+
const RaidenId& unit = plan.dst_units[i];
384+
armers.emplace_back([this, &plan, &arm_status, i, unit]() {
385+
auto addr_it = plan.worker_rpc_addresses.find(unit);
386+
if (addr_it == plan.worker_rpc_addresses.end()) {
387+
arm_status[i] = absl::InternalError(absl::StrCat(
388+
"No control endpoint recorded for ", PythonRepr(unit)));
389+
return;
390+
}
391+
const std::string arm_payload = EncodeStartTransfer(plan, unit);
392+
arm_status[i] = SendWorkerRpc(transport_, addr_it->second, arm_payload);
393+
});
364394
}
365-
const std::string arm_payload = EncodeStartTransfer(plan, plan.dst_unit);
366-
absl::Status arm_status =
367-
SendWorkerRpc(transport_, addr_it->second, arm_payload);
368-
if (!arm_status.ok()) {
369-
registry_->AbandonClaim(args.req_id, uuid, claim_owner);
370-
return arm_status;
395+
for (std::thread& t : armers) t.join();
396+
for (const absl::Status& status : arm_status) {
397+
if (!status.ok()) {
398+
registry_->AbandonClaim(args.req_id, uuid, claim_owner);
399+
return status;
400+
}
371401
}
372402
}
373403
const int64_t receiver_arm_ack_ns = MonotonicNs();
@@ -464,6 +494,7 @@ absl::Status ReshardCoordinator::ExecutePoolReshard(
464494
", \"controller_total_ms\": ",
465495
JsonFloat((sender_dispatch_end_ns - controller_start_ns) / 1e6),
466496
", \"destination_pages\": ", plan.expected_block_count,
497+
", \"destination_units\": ", plan.dst_units.size(),
467498
", \"event\": \"raiden_pool_reshard_senders_dispatched\"",
468499
", \"expected_pushes_per_pool\": ", plan.expected_pushes_per_pool,
469500
", \"num_tokens\": ", plan.num_tokens, ", \"plan_build_ms\": ",

0 commit comments

Comments
 (0)