[DRAFT] Allocate host staging per transfer instead of fixed slots - #722
Draft
yinlin09 wants to merge 2 commits into
Draft
[DRAFT] Allocate host staging per transfer instead of fixed slots#722yinlin09 wants to merge 2 commits into
yinlin09 wants to merge 2 commits into
Conversation
yinlin09
force-pushed
the
yinlin/dynamic-host-staging
branch
6 times, most recently
from
August 21, 2026 05:54
cd04936 to
e53b335
Compare
yinlin09
added a commit
that referenced
this pull request
Aug 21, 2026
A producer that could not seat a pull-serve marked the send done without transferring, so the consumer learned of the failure only by waiting out its transfer timeout; a consumer that could not seat a read failed it silently. The producer now waits for staging to free, bounded by the transfer deadline, and reports a transfer failure when a request still cannot be seated or can never fit. Both sides log the request, the pages asked, and the pages free, so an exhausted pool is attributed where it happens. Measured on the setup of the previous commit: at 4 fixed slots the load exhausted staging 1112 times across 139 requests, each visible only as a consumer-side failure. With staging on demand none occurred. Memory and performance results for on-demand staging itself are in the commit that introduces it, "Stage disaggregation transfers on demand instead of in fixed slots" (github.com//pull/722).
yinlin09
force-pushed
the
yinlin/dynamic-host-staging
branch
from
August 21, 2026 05:56
e53b335 to
d1fcf2c
Compare
yinlin09
added a commit
that referenced
this pull request
Aug 21, 2026
A producer that could not seat a pull-serve marked the send done without transferring, so the consumer learned of the failure only by waiting out its transfer timeout; a consumer that could not seat a read failed it silently. The producer now waits for staging to free, bounded by the transfer deadline, and reports a transfer failure when a request still cannot be seated or can never fit. Both sides log the request, the pages asked, and the pages free, so an exhausted pool is attributed where it happens. Measured on the setup of the previous commit: at 4 fixed slots the load exhausted staging 1112 times across 139 requests, each visible only as a consumer-side failure. With staging on demand none occurred. Memory and performance results for on-demand staging itself are in the commit that introduces it, "Stage disaggregation transfers on demand instead of in fixed slots" (github.com//pull/722).
yinlin09
force-pushed
the
yinlin/dynamic-host-staging
branch
from
August 21, 2026 06:03
d1fcf2c to
f5a23d7
Compare
yinlin09
added a commit
that referenced
this pull request
Aug 21, 2026
A producer that could not seat a pull-serve marked the send done without transferring, so the consumer learned of the failure only by waiting out its transfer timeout; a consumer that could not seat a read failed it silently. The producer now waits for staging to free, bounded by the transfer deadline, and reports a transfer failure when a request still cannot be seated or can never fit. Both sides log the request, the pages asked, and the pages free, so an exhausted pool is attributed where it happens. Measured on the setup of the previous commit: at 4 fixed slots the load exhausted staging 1112 times across 139 requests, each visible only as a consumer-side failure. With staging on demand none occurred. Memory and performance results for on-demand staging itself are in the commit that introduces it, "Stage disaggregation transfers on demand instead of in fixed slots" (github.com//pull/722).
yinlin09
force-pushed
the
yinlin/dynamic-host-staging
branch
from
August 21, 2026 18:21
f5a23d7 to
f630926
Compare
Disaggregation staging today either pre-carves fixed host slots sized for the maximum context length, over-provisioned when most in-flight requests are small, or mirrors the device KV pool on the host, wasteful when the blocks actually transferred are a small fraction of that pool. This change addresses the slot path on both ends. Under TPU_RAIDEN_DYNAMIC_HOST_STAGING=1 an incoming read and an outgoing pull-serve each allocate exactly the pages they stage and return them on completion, so a pool seats transfers by their size rather than by a fixed count. The copy plans already address host blocks explicitly, so nothing downstream changes. Off by default; the pool-plan receive path and the behaviour on an exhausted pool are unchanged. **Validation** gpt-oss-120b, prefill TP8 -> decode TP8 on v7x, 64-token pages, 8k context, 512-4096-token requests at 64-way concurrency, same build and load in every arm. Flag off carves the pool into fixed 128-page slots; flag on allocates pages per transfer from the same pool. | | off, 8192-page pool (64 slots) | on, 8192-page pool | on, 1024-page pool | |---|---|---|---| | pinned host staging per rank / per 8-rank host | 4.5 GiB / 36 GiB | 4.5 GiB / 36 GiB | 0.56 GiB / 4.5 GiB | | failed transfers | 0 | 0 | 0 | | gsm8k, 200 questions, before / after the load | 0.870 / 0.845 | 0.850 / 0.880 | 0.845 / 0.850 | | req/s: 96 x 128-tok burst / 64 x 512-tok / 32 x 4096-tok | 21.2 / 9.7 / 4.9 | 22.0 / 8.6 / 4.6 | 20.6 / 9.1 / 4.8 | | req/s: 600 x 2048-tok offered at 4 req/s, three runs | 3.86-3.87 | 3.86-3.87 | - | - Two flag-off runs differ by up to 12% req/s on the unpaced phases; the differences in the table are within that spread. - As fixed 128-page slots, 1024 pages seat 8 transfers at a time; the load runs 64-way. - At a 1M-token context a fixed slot is 16K pages while an 8k-token request uses 128: 64 slots would pin 4.6 TiB per host, 288 GiB would seat 4 transfers, and per-transfer pages serve 64 short requests plus one 1M request from about 100 GiB.
yinlin09
pushed a commit
that referenced
this pull request
Aug 23, 2026
A producer that could not seat a pull-serve marked the send done without transferring, so the consumer learned of the failure only by waiting out its transfer timeout; a consumer that could not seat a read failed it silently. The producer now waits for staging to free, bounded by the transfer deadline, and reports a transfer failure when a request still cannot be seated. A request larger than the staging can ever seat -- a fixed slot, or the whole per-transfer pool -- fails at once. The misses while waiting are not logged; the one failure message on each side carries the request, the pages asked, and the pages free, so an exhausted pool is attributed where it happens. Measured on the setup of the previous commit: at 4 fixed slots the load exhausted staging 1112 times across 139 requests, each visible only as a consumer-side failure. With staging on demand none occurred. Memory and performance results for on-demand staging itself are in the commit that introduces it, "Stage disaggregation transfers on demand instead of in fixed slots" (github.com//pull/722).
yinlin09
force-pushed
the
yinlin/dynamic-host-staging
branch
from
August 23, 2026 17:37
f630926 to
6217af1
Compare
yinlin09
pushed a commit
that referenced
this pull request
Aug 23, 2026
A producer that could not seat a pull-serve marked the send done without transferring, so the consumer learned of the failure only by waiting out its transfer timeout; a consumer that could not seat a read failed it silently. The producer now waits for staging to free, bounded by the transfer deadline, and reports a transfer failure when a request still cannot be seated. A request larger than the staging can ever seat -- a fixed slot, or the whole per-transfer pool -- fails at once. The misses while waiting are not logged; the one failure message on each side carries the request, the pages asked, and the pages free, so an exhausted pool is attributed where it happens. Measured on the setup of the previous commit: at 4 fixed slots the load exhausted staging 1112 times across 139 requests, each visible only as a consumer-side failure. With staging on demand none occurred. Memory and performance results for on-demand staging itself are in the commit that introduces it, "Stage disaggregation transfers on demand instead of in fixed slots" (github.com//pull/722). During shutdown a producer waiting for staging stops instead of sleeping out its deadline, and the manager's destructor waits for every pull-serve worker before tearing down the state they read.
yinlin09
force-pushed
the
yinlin/dynamic-host-staging
branch
from
August 23, 2026 20:38
6217af1 to
db7a7aa
Compare
yinlin09
pushed a commit
that referenced
this pull request
Aug 23, 2026
A producer that could not seat a pull-serve marked the send done without transferring, so the consumer learned of the failure only by waiting out its transfer timeout; a consumer that could not seat a read failed it silently. The producer now waits for staging to free, bounded by the transfer deadline, and reports a transfer failure when a request still cannot be seated. A request larger than the staging can ever seat -- a fixed slot, or the whole per-transfer pool -- fails at once. The misses while waiting are not logged; the one failure message on each side carries the request, the pages asked, and the pages free, so an exhausted pool is attributed where it happens. Measured on the setup of the previous commit: at 4 fixed slots the load exhausted staging 1112 times across 139 requests, each visible only as a consumer-side failure. With staging on demand none occurred. Memory and performance results for on-demand staging itself are in the commit that introduces it, "Stage disaggregation transfers on demand instead of in fixed slots" (github.com//pull/722). During shutdown a producer waiting for staging stops instead of sleeping out its deadline, and the manager's destructor waits for every pull-serve worker before tearing down the state they read. A producer entry that reaches its deadline without being pulled is reported as failed, never as sent, and the wait for staging shares that same deadline instead of starting a later one. Shutdown wakes workers parked waiting for a request that will never arrive, so tearing the manager down cannot hang on them.
yinlin09
force-pushed
the
yinlin/dynamic-host-staging
branch
from
August 23, 2026 20:56
db7a7aa to
7074f0d
Compare
yinlin09
pushed a commit
that referenced
this pull request
Aug 23, 2026
A producer that could not seat a pull-serve marked the send done without transferring, so the consumer learned of the failure only by waiting out its transfer timeout; a consumer that could not seat a read failed it silently. The producer now waits for staging to free, bounded by the transfer deadline, and reports a transfer failure when a request still cannot be seated. A request larger than the staging can ever seat -- a fixed slot, or the whole per-transfer pool -- fails at once. The misses while waiting are not logged; the one failure message on each side carries the request, the pages asked, and the pages free, so an exhausted pool is attributed where it happens. Measured on the setup of the previous commit: at 4 fixed slots the load exhausted staging 1112 times across 139 requests, each visible only as a consumer-side failure. With staging on demand none occurred. Memory and performance results for on-demand staging itself are in the commit that introduces it, "Stage disaggregation transfers on demand instead of in fixed slots" (github.com//pull/722). During shutdown a producer waiting for staging stops instead of sleeping out its deadline, and the manager's destructor waits for every pull-serve worker before tearing down the state they read. A producer entry that reaches its deadline without being pulled is reported as failed, never as sent, and the wait for staging shares that same deadline instead of starting a later one. Shutdown wakes workers parked waiting for a request that will never arrive, so tearing the manager down cannot hang on them. A copy or network failure while serving a pull is the transfer's failure: it is reported as such instead of as a completed send, and no longer escapes the worker as an exception.
yinlin09
force-pushed
the
yinlin/dynamic-host-staging
branch
3 times, most recently
from
August 24, 2026 07:42
8d88d6b to
89ef10d
Compare
Previously, handling staging buffer exhaustion had two flaws: 1. Producer false success: When serving a pull request without free staging buffers, the producer marked the send as completed without transferring any data. The consumer received nothing and hung until its transfer deadline timed out. 2. Consumer silent failure: When initiating a read without free staging buffers, the consumer failed without logging an error. This change improves reliability, observability, and shutdown safety: - Producer retry and failure propagation: The producer now waits for staging buffers to free up, bounded by the request deadline. If the request exceeds total pool capacity or times out, it explicitly records a transfer failure. - Diagnostic logging: Both endpoints now log the request ID, requested page count, and available capacity on allocation failure, pinpointing which node ran out of memory. - Clean shutdown: Tracks active pull workers so the destructor waits for in-flight transfers to finish before tearing down resources. Validation: - With 4 fixed slots, the workload encountered 1,112 buffer exhaustion events across 139 requests (all previously surfacing only as consumer timeouts). - With on-demand dynamic staging enabled, 0 exhaustion events occurred.
yinlin09
force-pushed
the
yinlin/dynamic-host-staging
branch
from
August 24, 2026 07:51
89ef10d to
b47cf1c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TL;DR
(
TPU_RAIDEN_DYNAMIC_HOST_STAGING=1) instead of in fixed worst-case slots.zero failed transfers, throughput unchanged. At long context lengths fixed
slots stop fitting in the machine at all (table below).
transfer that never happened.
the whole device KV pool on the host — 452 GiB → ~2.3 GiB per host expected
for the same load (validated end to end on a small model in [DRAFT] Stage resharding transfers in plan-allocated host blocks #736).
Why it matters — host memory per 8-rank host, gpt-oss-120b TP8
Fixed slots are sized for the longest request the server accepts × the
concurrency wanted; on-demand staging is sized for the bytes actually in
flight. Resharding today goes further and mirrors the entire device KV pool on
the host so that block ids can be resolved by identity; the same allocator plus
a per-plan id map removes that requirement — that is #736, stacked on this PR.
What changes
StartRead,StartPushInternal) allocate exactly the pages they stage from the existing host block manager, locked for the transfer's lifetime, and return them on completion/timeout/error. Copy plans already address host blocks explicitly: nothing changes downstream or on the wire. Off by default; reshard/pool-plan paths untouched.Validation
StartRead/ pull-servefailed_recvingper consumer rank ("failed reads"); the serving layer retries, so client success rates hide failuresci/build_wheel.sh; each commit builds standalone; the new path is present in all 3 torch ABI extensionsResults
Throughput, same 8192-page pool, flag off vs on (req/s · mean/p99 TTFT ms)
Run-to-run variation, measured with two flag-off runs: the three unpaced
phases (the 32-way burst and the 64 × 512-token and 32 × 4096-token runs,
each a few seconds long, requests sent as fast as the server accepts them)
differ from each other by up to 12% in req/s; the paced phase (600 requests
offered at a fixed 4 req/s over about 2.5 minutes) is identical across runs,
as expected while the server keeps up with the offered rate.
Exhaustion behaviour: offered load above pool capacity → reads wait
(bounded by the transfer deadline); a read that can never fit fails with a
logged reason (request, blocks asked, blocks free). Before: producer reported
success without transferring, consumer timed out.
Found during validation, fixed here: the first build leaked staging on
every successful read (event-driven completion paths still released by slot
index). The added error line made it diagnosable in one run.
Follow-ups (not in this PR)
staging on both sides (452 GiB → ~2.3 GiB per host projected for this load)
— [DRAFT] Stage resharding transfers in plan-allocated host blocks #736.
consumer fails fast while the producer waits).
(the paced phase ran 1800 transfers without drift).