[5/5][WIP] feat: request-aligned DBO ubatch splitting (flag off by default) - #336
Open
specture724 wants to merge 1 commit into
Open
[5/5][WIP] feat: request-aligned DBO ubatch splitting (flag off by default)#336specture724 wants to merge 1 commit into
specture724 wants to merge 1 commit into
Conversation
specture724
requested review from
hsliuustc0106 and
jiangkuaixue123
as code owners
September 11, 2026 02:12
This was referenced Sep 11, 2026
specture724
force-pushed
the
afd/async-gpu-dbo
branch
from
September 11, 2026 02:17
d442b80 to
388c852
Compare
specture724
added this pull request to stack #337
September 11, 2026 02:17
Collaborator
|
With DBO enabled, the decode execution time appears to nearly double. Could you share a profiling trace to check whether the two ubatches actually overlap? For prefill, could you also check whether it is host-bound, with a significant amount of time spent launching kernels, especially since graph replay is currently not working for DBO prefill? A CPU/GPU timeline would help distinguish a lack of overlap from host-side launch overhead. |
specture724
force-pushed
the
afd/async-gpu-dbo
branch
4 times, most recently
from
September 11, 2026 08:20
3100f73 to
db619df
Compare
Upstream vLLM splits a DBO batch at an even token count, which cuts whichever request straddles that point into both ubatches. AFD's overlap is between whole requests -- one request's dispatch is in flight while the other's attention runs -- so the split has to land on a request boundary, and a batch with no interior boundary (a single prefill) must run whole rather than be divided. The splitter also prefers 16-token-aligned boundaries: a ubatch's per-token tensors are views starting at the split point, and DeepSeek-V4's CuTeDSL compressor kernel rejects anything below 64-byte alignment. It is a preference, not a rule -- uniform decode has boundaries at 1, 2, 3, ... and refusing there would disable DBO for decode entirely. Two bugs that kept DBO from working at all on this connector. The batch- execution override only consulted the rank-local decision when data_parallel_size == 1, but the cross-DP agreement is *also* skipped for connectors with no control plane, where the parent's False is a placeholder rather than a decision -- so `--enable-dbo` was accepted and then silently ignored for the whole run. And the last split point came off cu_num_tokens as a numpy.int32, which Triton refuses to specialize, killing DeepSeek-V4 in _build_c128a_topk_metadata_kernel on the last ubatch. The dispatch stage is now the DBO ubatch id, read from the thread the way vLLM tracks it; forward_context.ubatch_idx does not exist, so both halves looked like stage 0 and claimed the same window slot. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: specture724 <specture724@gmail.com>
specture724
force-pushed
the
afd/async-gpu-dbo
branch
from
September 11, 2026 10:04
db619df to
37ef0cc
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.
Fifth and last of the async GPU connector stack. Stacked on #335 — review that
one first; this PR's diff is only the DBO change.
What this does
Upstream vLLM splits a DBO batch at an even token count, cutting whichever
request straddles that point into both ubatches. AFD's overlap is between
whole requests — one request's dispatch is in flight while the other's
attention runs — so the split has to land on a request boundary, and a batch
with no interior boundary (a single prefill) must run whole.
The splitter also prefers 16-token-aligned boundaries: a ubatch's per-token
tensors are views starting at the split point, and DeepSeek-V4's CuTeDSL
compressor kernel rejects anything below 64-byte alignment. It is a
preference, not a rule — uniform decode has boundaries at 1, 2, 3, … and
refusing there would disable DBO for decode entirely.
Two bugs that kept DBO from working at all on this connector:
data_parallel_size == 1, but the cross-DP agreement is also skipped forconnectors with no control plane, where the parent's
Falseis aplaceholder rather than a decision — so
--enable-dbowas accepted and thensilently ignored for the whole run;
cu_num_tokensas anumpy.int32, whichTriton refuses to specialise, killing DeepSeek-V4 in
_build_c128a_topk_metadata_kernelon the last ubatch.The dispatch stage is now the DBO ubatch id read from the thread, the way vLLM
tracks it;
forward_context.ubatch_idxdoes not exist, so both halves lookedlike stage 0 and claimed the same window slot.
Performance: DBO is a regression on every configuration measured
ENABLE_DBOdefaults to 0 and should stay there. This PR makes the flagwork correctly; it does not make it profitable. Four-arm ablation,
DeepSeek-V2-Lite 1A1F decode, 2x L20X, 64 req x 256 output tokens, conc 16,
3 repeats in one server lifetime:
On DeepSeek-V4 prefill it is −7.1% at 2048-token steps, −3.7% at 4096, and
+0.5% at 8192 with 4096-token prompts — i.e. break-even at best.
Note also that the +22.6% graph win above is V2-Lite-specific. The same
comparison on DeepSeek-V4 decode measured 137.5 s eager against 134.6 s with
graphs — about 2%, and the eager arm's spread (sd 6.1 s) covers it, so it is
not a significant difference. V4 is FFN-compute-bound, so neither lever has
much host time to reclaim there.
Why, and when it could pay
The cost is round-trip latency, not host CPU. Normalising to per-layer
dispatch round trips (26 MoE layers, ~1024 decode steps):
A round trip costs the same whether DBO is on or off; DBO simply performs
twice as many per step while halving the work inside each. Under graphs the
per-round-trip cost is marginally lower with DBO than without — Python is
out of the loop by then — and the 1.90x remains. The overlap that is supposed
to hide one ubatch's round trip behind the other's compute hides essentially
nothing.
That gives a falsifiable criterion: per-ubatch compute must be at least the
~1 ms round-trip latency. On V2-Lite decode a ubatch is 8 sequences x 1
token, orders of magnitude short. Either the round trip drops to ~100 us, or
DBO needs a regime where a ubatch carries >=1 ms of compute.
Merging it keeps the correctness fixes and the splitter available for that
regime, with the flag off by default.
Known gaps
DeepSeek-V4 decode hangs with
--enable-dbo. Three attempts to measure aDSV4 decode ablation (2A2F, 4x L20X, batch 64, 8192-token steps) never produced
a DBO number. The current failure, with everything in this PR applied:
The server starts and serves; it hangs once decode begins. The AFD ubatch
wrapper logs nothing in that run, so the hang precedes any ubatch split rather
than happening inside one. Not root-caused.
Two bugs were found and fixed along the way, both real but neither the cause:
afd_plugin/__init__.pyimported two patch modules that no longer exist, andall the imports shared one
try, so theModuleNotFoundErrorsilentlyskipped
ubatch_positionsandubatch_split— this PR's own patches neverloaded. Each import is now isolated and failures warn instead of debug-log.
RemoteDeepseekV4FFNreadforward_context.ubatch_idx, which does notexist, so both DBO halves resolved to stage 0 and claimed the same window
slot. V4 now uses the per-thread
current_dbo_ubatch_id()like V2.So
ENABLE_DBO=1is not usable on DeepSeek-V4 today. It defaults to 0 and nodefault path is affected; the splitter and the two correctness fixes above are
what this PR is worth merging for.
Under DBO a prefill step never replays a captured graph: the per-ubatch
metadata comes from
split_attn_metadata, which clones per step, so theidentity precondition in
_refresh_metadata_pairrefuses — instrumented atreplays=0. Forcing the shapes to match makes it replay and then segfaultinside
cuGraphLaunchon the first launch. Decode is unaffected (98% replayshare, no crash), so this is specific to the cooperative prefill graph.
Investigation branch:
afd/async-gpu-replay-safe.Test plan
pytest tests/unit— no failures beyond the 13 already present on [4/5] feat: capture the FFN experts at bucketed padded shapes #335pre-commit run --from-ref afd/async-gpu-cudagraph --to-ref HEAD— cleansingle-request refusal, decode fallback) and the DP-coordination gate
🤖 Generated with Claude Code