Skip to content

Commit d9f106c

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 370e7ab commit d9f106c

5 files changed

Lines changed: 321 additions & 74 deletions

File tree

tpu_sync/kv_cache/reshard/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ cc_library(
100100
"//tpu_sync/kv_cache:pool_layout",
101101
"//tpu_sync/kv_cache:raiden_id",
102102
"//tpu_sync/rpc:raiden_service_cc_proto",
103+
"@com_google_absl//absl/container:btree",
103104
"@com_google_absl//absl/status",
104105
"@com_google_absl//absl/status:statusor",
105106
"@com_google_absl//absl/strings",

tpu_sync/kv_cache/reshard/pool_reshard_planner.cc

Lines changed: 113 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
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());

tpu_sync/kv_cache/reshard/pool_reshard_planner.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <string>
2222
#include <vector>
2323

24+
#include "absl/container/btree_map.h"
2425
#include "absl/status/statusor.h"
2526
#include "tpu_sync/kv_cache/raiden_id.h"
2627
#include "tpu_sync/kv_cache/reshard/request_block_registry.h"
@@ -52,17 +53,20 @@ struct ScheduleEntry {
5253
struct PlanPoolGroup {
5354
std::vector<int32_t> pool_indices;
5455
std::vector<int64_t> dst_device_block_ids;
55-
int32_t expected_pushes = 0;
56+
std::map<RaidenId, int32_t, RequestBlockRegistry::RaidenIdLess>
57+
expected_pushes_by_dst;
5658
std::vector<int64_t> dst_expected_extent_bytes;
5759
int32_t order_rank = 0;
5860
};
5961

6062
// The pool-path subset of TransferPlan that the encoder and coordinator
6163
// consume. Field-for-field mirror of _build_byte_span_plan_claimed's
6264
// return value.
65+
// Note: Multi-destination plans replicate one identical byte set
66+
// to every destination (TP>1 decode engine).
6367
struct PoolReshardPlan {
6468
std::vector<RaidenId> src_units; // active source units, rank order
65-
RaidenId dst_unit;
69+
std::vector<RaidenId> dst_units;
6670
// Per source unit: shard 0's entry list (pool planning enforces one
6771
// endpoint per unit, so the inner Python dict always has the single key
6872
// 0). Keyed in src_units order.
@@ -71,7 +75,9 @@ struct PoolReshardPlan {
7175
schedules;
7276
std::map<RaidenId, std::string, RequestBlockRegistry::RaidenIdLess>
7377
worker_rpc_addresses;
74-
std::string dst_peer; // worker_data_addresses[dst_unit][0]
78+
// worker_data_addresses[unit][0] per destination unit.
79+
absl::btree_map<RaidenId, std::string, RequestBlockRegistry::RaidenIdLess>
80+
dst_peers;
7581
int64_t uuid = 0;
7682
std::string req_id;
7783
int64_t expected_block_count = 0;

0 commit comments

Comments
 (0)