Skip to content

[DRAFT] Stage resharding transfers in plan-allocated host blocks - #736

Draft
yinlin09 wants to merge 1 commit into
yinlin/dynamic-host-stagingfrom
yinlin/reshard-demand-staging
Draft

[DRAFT] Stage resharding transfers in plan-allocated host blocks#736
yinlin09 wants to merge 1 commit into
yinlin/dynamic-host-stagingfrom
yinlin/reshard-demand-staging

Conversation

@yinlin09

@yinlin09 yinlin09 commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

TL;DR

What changes

Resharding resolved every block on the wire by identity into the host mirror:
a sender staged each device block into the host block of the same id, and a
receiver landed each incoming block at its destination id and uploaded from
there. That only works when the host mirror spans the whole device block
space, so both sides of a reshard pinned a host copy of their entire device KV
pool.

piece change
RegisteredPlan gains host_block_of: device block → host block for the blocks the plan names on this side
GetBlockChunks (plan branch) resolves the wire's device block through the map; this was the single identity point for both roles
RegisterActivePlan (receiver) allocates host blocks for the plan's destination blocks, builds the upload map from them, frees them when the receive completes
RegisterActivePlan (sender) allocates host blocks for the plan's source blocks; freed by UnregisterActivePlan
plan_host_blocks(uuid, block_ids) new API: where a sender should copy its device blocks before pushing (identity when the plan has no map)

Pool-addressed plans keep their existing addressing.

Validation

Build: torch wheel via ci/build_wheel.sh; the new API is present in all
three ABI extensions.

End to end: 8→4 reshard — prefill TP8 → decode TP8 with attention TP4 × DP2,
dense Qwen3-1.7B on v7x, 128-token pages, context length 8192. Same image,
wheels and load in every arm; the only differences are the flag and the host
pool size. Host staging per rank is read from the engine start-up line
(max_blocks × num_slots blocks). Every arm below runs on a build that also
carries the multi-sender completion fix for pushed transfers ("Complete a pushed block array only after every declared sender lands", separate PR) — without
it the asymmetric path returns wrong output in every configuration, on or off.

Correctness (greedy probe: 3 prompts × 5 sequential repeats + 8 identical
concurrent requests; 200-question few-shot gsm8k):

arm host staging per rank probe gsm8k acc / invalid failed transfers
symmetric TP8→TP8, no reshard (reference) ordinary sizing 5/5 and 8/8 identical 0.675 / 0.000 0
flag off (host mirror of the whole device pool) 1025 blocks = 1.8 GB prefill rank, 3.5 GB decode rank 5/5 sane, 8/8 identical, equal to the reference 0.690 / 0.005 0
flag on, 512-block pool half the device pool: 0.9 GB prefill rank, 1.8 GB decode rank 5/5 sane, 8/8 identical, equal to the reference 0.685 / 0.000 0

Throughput (bench_serving, 64 × 512-token requests, 64-way, req/s · mean TTFT · mean ITL):

arm run 1 run 2
flag off 12.10 · 1187 ms · 28.2 ms 12.82 · 1225 ms · 27.0 ms
flag on, 512-block pool 13.03 · 1571 ms · 23.3 ms 12.39 · 1208 ms · 26.8 ms

One probe prompt alternates between two sane continuations across its 5 repeats
in both reshard arms (not in the symmetric reference); same with and without
this PR. gsm8k second runs: 0.670 / 0.000 off, 0.665 / 0.000 on.

  • Memory: across the two 8-rank hosts, 42 GB of pinned host mirror off vs
    21 GB on for this load, and the on-side figure is set by the pool size, not
    by the model. On gpt-oss-120b TP8 the mirror is 56.5 GiB per rank (452 GiB
    per host); the same 64 in-flight requests need about 2.3 GiB per host
    (projected from the page arithmetic in [DRAFT] Allocate host staging per transfer instead of fixed slots #722).

Engine lines (one per instance; identical across ranks):

off: RaidenKVManager rank0 engine up | ... max_blocks=1025 num_slots=1
on:  RaidenKVManager rank0 engine up | ... max_blocks=64   num_slots=8

Resharding resolved every block on the wire by identity into the host
mirror: a sender staged each device block into the host block of the same
id, and a receiver landed each incoming block at its destination id and
uploaded from there. That only works when the host mirror spans the whole
device block space, so both sides of a reshard pinned a host copy of their
entire device KV pool.

Under TPU_RAIDEN_DYNAMIC_HOST_STAGING=1 a plan now allocates host blocks
for the device blocks it names -- its source blocks on a sender, its
destination blocks on a receiver -- and records the mapping. Chunk
resolution consults the mapping, the receiver's upload reads from the
mapped blocks, and the blocks return to the pool when the receive completes
or the sender plan is unregistered. The wire is unchanged: it still names
device blocks. Senders obtain their staging blocks through
plan_host_blocks() and copy into those instead of into the mirror.

Off by default. Pool-addressed plans keep their existing addressing.

**Validation**

8->4 reshard (prefill TP8 -> decode attention TP4 x DP2), Qwen3-1.7B on
v7x, 128-token pages, 8k context, 64 x 512-token requests at 64-way
concurrency. Flag off vs on, same build and load, two runs per arm. The
build includes the multi-sender completion fix for pushed transfers.

| | off | on (512-block pool) |
|---|---|---|
| host staging per rank | whole device pool mirrored: 1025 blocks, 1.8 GB prefill / 3.5 GB decode | 512 blocks, 0.9 GB / 1.8 GB |
| pinned host memory, both hosts | 42 GB | 21 GB |
| gsm8k, 200 questions (symmetric serve: 0.675, 0.705) | 0.690, 0.670 | 0.685, 0.665 |
| greedy probe, 5 sequential + 8 concurrent repeats | repeatable, equal to the symmetric serve | same |
| req/s | 12.1, 12.8 | 13.0, 12.4 |
| mean TTFT / ITL, ms | 1187 / 28.2, 1225 / 27.0 | 1571 / 23.3, 1208 / 26.8 |
| failed transfers | 0 | 0 |

- The pool no longer tracks the device pool, so the saving grows with the
  model: on gpt-oss-120b TP8 the mirror is 56.5 GiB per rank (452 GiB per
  host); the same 64 in-flight requests fit in about 2.3 GiB per host.
- One probe prompt alternates between two sane continuations across its
  repeats, in both arms.
@yinlin09
yinlin09 force-pushed the yinlin/reshard-demand-staging branch from cf44cde to f25d9d9 Compare August 21, 2026 14:30
@yinlin09
yinlin09 force-pushed the yinlin/dynamic-host-staging branch from f5a23d7 to f630926 Compare August 21, 2026 18:21
@yinlin09
yinlin09 force-pushed the yinlin/reshard-demand-staging branch from f25d9d9 to 642bb06 Compare August 21, 2026 18:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant