Skip to content

perf(qwen3_5): SERVE-ASYNC-OPTION-A — decode-graph input H2D staged out of capture (faithful vLLM, gated OFF) - #39

Merged
localai-bot merged 2 commits into
mainfrom
row/SERVE-ASYNC-OPTION-A
Aug 5, 2026
Merged

perf(qwen3_5): SERVE-ASYNC-OPTION-A — decode-graph input H2D staged out of capture (faithful vLLM, gated OFF)#39
localai-bot merged 2 commits into
mainfrom
row/SERVE-ASYNC-OPTION-A

Conversation

@localai-bot

Copy link
Copy Markdown
Collaborator

Option A — stage the decode-graph input H2D OUT of capture (faithful vLLM)

VT_ASYNC_EXECUTOR now ports vLLM's actual async decode-input structure
(gpu_model_runner.py _prepare_input_ids / synchronize_input_prep, states.py:64):
each SizeSlot gets persistent device input buffers the captured graph reads and
pinned host staging; per step the input H2D is enqueued on the main queue before
ReplayGraph (out of capture) and an input-staged event is recorded right after the
tiny staging copy, so the next same-slot Refresh waits only that copy, never the GPU
tail. Pinned host memory makes the H2D a true-async DMA (pageable GB10 H2D is
host-synchronous, which is exactly why #36's poison could never race). Supersedes #36's
in-capture-baked ring (the 2-slot ring is retained for the depth-2 logits double-buffer).
Both drivers (35B MoE + 27B dense). Default OFF, byte-identical.

A capture-time cudaMalloc bug (the persistent s.dev popped-and-held a size-class the
captured forward's scratch then needed) is fixed by routing s.dev through a dedicated
PersistentDecodeInputPool under ActivePoolScope.

Verification (dgx GB10 sm_121a, both locks, single load/arm, drop_caches)

  • CPU -Werror + runner/llm_engine/engine_core_proc/async_llm/input_batch 5/5.
  • RED VT_ASYNC_EXECUTOR_POISON reproduces deterministically (pinned-source
    poison -> coherent-but-wrong tokens). This is the RED row/SERVE-ASYNC-EXECUTOR: decode-graph slot double-buffer, gated OFF (Option-A groundwork) #36 could not construct, and it
    proves the input-staged event is load-bearing.
  • GREEN async test_qwen36_async_serving ON @conc-32 5/5; SACRED
    test_qwen36_paged_engine ON x3; compute-sanitizer memcheck x2 = 0 errors;
    VT_ASYNC_EXECUTOR_NO_DBUF PASS; served ignore_eos UAF bracket 4-128 all ALIVE;
    drain-skip engagement confirmed in the server.
  • Binding A/B (online_gate.py, ONE binary env-toggled, single load/arm, 3 reps):
    throughput WASH (c8 1778.9->1781.4, c16 2294.4->2294.8, c32 2914.5->2916.3 tok/s);
    TTFT WASH.

Decision

Speed-neutral -> default OFF (parity-enablers). Definitive negative: removing exactly
the depth-2 drain and the in-capture baked H2D leaves throughput/TTFT unchanged, so
the c16/c32/c8 deficit to vLLM is not the decode-graph input-H2D structure. Residual
is prefill glue (task #61). Mechanism kept gated (strictly better than #36: faithful
structure + a working RED). Evidence dgx:~/work/mirror-ab/option-a/.

🤖 Generated with Claude Code

https://claude.ai/code/session_014fZAcg1WcU8V629k6HWKys

mudler added 2 commits August 5, 2026 19:12
…UT of capture; faithful vLLM, RED reproduces, A/B WASH, gated OFF

Option A ports vLLM's actual async decode-input structure (states.py:64,
_prepare_input_ids / synchronize_input_prep): under VT_ASYNC_EXECUTOR each decode
step's input H2D is enqueued on the main queue BEFORE ReplayGraph into PERSISTENT
device buffers the captured graph reads, guarded by an input-staged event recorded
right after the tiny staging copy — so the next same-slot Refresh waits only that
copy, never the GPU tail. PINNED host staging makes the H2D a true-async DMA (pageable
GB10 H2D is host-synchronous, which is why #36's poison never raced). Supersedes #36's
in-capture-baked ring (ring retained for the depth-2 logits double-buffer). Both
drivers (35B MoE + 27B dense). Persistent s.dev drawn from a dedicated pool via
ActivePoolScope so it never starves the captured forward's scratch (fixes a
cudaMalloc-during-capture). Default OFF byte-identical.

Verified on dgx GB10 sm_121a (both locks, single load/arm, drop_caches):
- CPU -Werror + runner/llm_engine/engine_core_proc/async_llm/input_batch 5/5.
- RED VT_ASYNC_EXECUTOR_POISON reproduces DETERMINISTICALLY (pinned-source poison ->
  coherent-but-wrong tokens) — the RED #36 could not construct.
- GREEN async 5/5 @conc-32, SACRED x3, memcheck x2 = 0 errors, NO_DBUF PASS, served
  ignore_eos UAF bracket 4-128 all ALIVE; server drain-skip engagement confirmed.
- Binding A/B (online_gate, env-toggle): throughput WASH (c8 +0.14%, c16 +0.02%,
  c32 +0.06%), TTFT WASH. Removing exactly the drain + in-capture H2D does not move
  throughput -> the c16/c32 gap is NOT the async input path. DEFAULT OFF.

FOLLOWING_AGENTS_PROTOCOL
Assisted-by: Claude Code:claude-opus-4-8 [ClaudeCode]
# Conflicts:
#	.agents/NOW.md
#	.agents/benchmark-record.md
#	.agents/state.md
@localai-bot
localai-bot marked this pull request as ready for review August 5, 2026 19:16
@localai-bot
localai-bot merged commit d7f5088 into main Aug 5, 2026
6 of 9 checks passed
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.

2 participants