Skip to content

[SGL ATOM] enable qwen3.8-flash mtp in sgl atom - #2385

Merged
valarLip merged 2 commits into
mainfrom
lingzha/qwenflash-mtp
Sep 28, 2026
Merged

valarLip merged 2 commits into
mainfrom
lingzha/qwenflash-mtp

Conversation

@ZLkanyo009

@ZLkanyo009 ZLkanyo009 commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

Enable Qwen3.8-Flash-Next MTP in SGLang-ATOM.

The target and the same-checkpoint 1-layer QSA draft both run Native ATOM
Qwen4Exp / Qwen4ExpMTP. The SGLang plugin only adapts scheduling, QSA page
tables, PLE state, and hyper-connection hidden storage. MTP is scheduled as
SGLang EAGLE with --speculative-num-steps 2 and --speculative-eagle-topk 1
(3 draft tokens). Target verify uses CUDA graphs. Draft stays eager because
Flash mRoPE positions are [3, N], matching Native ATOM.

Command

MI308X, TP2 + EP2, BF16 KV, page size 64, no prefix / radix cache.
Checkpoint: /models/Qwen3.8-Flash-Next-PTPC-FP8.

SGLang-ATOM with MTP:

export ATOM_PATH=/workspace/qwen/ATOM_main_pr3
export SGLANG_PATH=/workspace/qwen/sglang-v517
export PYTHONPATH="${SGLANG_PATH}/python:${ATOM_PATH}"
export SGLANG_PLUGINS=atom_sglang
export SGLANG_EXTERNAL_MODEL_PACKAGE=atom.plugin.sglang.models
export SGLANG_EXTERNAL_MM_PROCESSOR_PACKAGE=atom.plugin.sglang.models
export SGLANG_USE_AITER=1
export SGLANG_AITER_KV_CACHE_LAYOUT=nhd
export AITER_LOG_LEVEL=WARNING
export AITER_MOE_PADDING_SIZE=128
export CUDA_VISIBLE_DEVICES=0,1
export HIP_VISIBLE_DEVICES=0,1

python3 -m sglang.launch_server \
  --model-path /models/Qwen3.8-Flash-Next-PTPC-FP8 \
  --host 127.0.0.1 --port 8000 --trust-remote-code \
  --tp 2 --ep-size 2 --attention-backend aiter \
  --kv-cache-dtype bf16 --page-size 64 \
  --mem-fraction-static 0.70 \
  --context-length 65536 --max-running-requests 32 \
  --disable-radix-cache \
  --cuda-graph-backend-decode full --cuda-graph-max-bs-decode 32 \
  --chunked-prefill-size 16384 \
  --speculative-algorithm EAGLE \
  --speculative-num-steps 2 --speculative-eagle-topk 1 \
  --reasoning-parser qwen3 \
  --sampling-defaults openai

Omit the three --speculative-* flags to serve without MTP.

Native ATOM with MTP, same shape:

export ATOM_PATH=/workspace/qwen/ATOM_main_pr3
export PYTHONPATH="${ATOM_PATH}"
export AITER_LOG_LEVEL=WARNING
export CUDA_VISIBLE_DEVICES=0,1
export HIP_VISIBLE_DEVICES=0,1

python3 -m atom.entrypoints.openai_server \
  --model /models/Qwen3.8-Flash-Next-PTPC-FP8 \
  --host 127.0.0.1 --server-port 8000 --trust-remote-code \
  -tp 2 --enable-expert-parallel \
  --kv-cache-dtype bf16 --block-size 64 \
  --max-model-len 65536 --max-num-seqs 32 \
  --max-num-batched-tokens 16384 \
  --gpu-memory-utilization 0.70 \
  --cudagraph-mode FULL \
  --cudagraph-capture-sizes "[1,2,4,8,16,32]" \
  --no-enable_prefix_caching \
  --method mtp --num-speculative-tokens 2

Large-output benchmark (ISL 1024, OSL 4096, concurrency 4, 8 prompts, ignore EOS):

PYTHONPATH=/workspace/qwen/ATOM_main_pr3 python3 -m atom.benchmarks.benchmark_serving \
  --backend sglang --host 127.0.0.1 --port 8000 \
  --model /models/Qwen3.8-Flash-Next-PTPC-FP8 \
  --dataset-name random \
  --random-input-len 1024 --random-output-len 4096 --random-range-ratio 1.0 \
  --num-prompts 8 --max-concurrency 4 \
  --request-rate inf --ignore-eos --seed 0 --num-warmups 0

GSM8K, 5-shot, 1319 items, max_gen_toks=4096, stop on <|im_end|>:

lm_eval --model local-chat-completions \
  --model_args model=/models/Qwen3.8-Flash-Next-PTPC-FP8,base_url=http://127.0.0.1:8000/v1/chat/completions,num_concurrent=8,tokenized_requests=False,trust_remote_code=True \
  --tasks gsm8k --num_fewshot 5 \
  --gen_kwargs max_gen_toks=4096,until='<|im_end|>' \
  --apply_chat_template --confirm_run_unsafe_code \
  --output_path ./gsm8k

RealWorldQA, 765 items, rule score on the answer text (the Qwen3.5-27B judge was not run):

python realworldqa_eval.py infer \
  --model /models/Qwen3.8-Flash-Next-PTPC-FP8 \
  --base-url http://127.0.0.1:8000/v1 \
  --data /workspace/qwen/sglang-v517/eval/data/RealWorldQA.tsv \
  --concurrency 32 \
  --output predictions.jsonl

Performance

Same server shape as above. Random ISL 1024 / OSL 4096, concurrency 4, 8 prompts, ignore EOS. MTP off uses the same benchmark with the speculative flags omitted. Output-token throughput and TPOT are the decode comparison.

Output tok/s Mean TPOT Median TPOT Mean ITL
Native ATOM, MTP off 249.48 15.84 ms 15.84 ms 15.84 ms
Native ATOM, MTP on 376.79 8.88 ms 7.76 ms 20.05 ms
SGLang-ATOM, MTP off 287.18 13.74 ms 13.74 ms 13.74 ms
SGLang-ATOM, MTP on 328.12 10.70 ms 10.11 ms 25.03 ms

MTP helps on this long-output shape. With MTP off, one decode step emits one token, so ITL equals TPOT. With MTP on, a step emits more than one accepted token, so ITL goes up while time per output token goes down.

  • Native ATOM: TPOT 15.84 ms to 8.88 ms, output throughput 249.48 to 376.79 tok/s (about 1.5x).
  • SGLang-ATOM: TPOT 13.74 ms to 10.70 ms, output throughput 287.18 to 328.12 tok/s (about 1.14x).

SGLang-ATOM with MTP is still behind Native ATOM with MTP (10.70 ms vs 8.88 ms, about 13% less output throughput). Verify GPU time matches (about 18.2 ms). The remaining gap is the eager 1-token draft step: its kernels match Native in length, but they are launched on an idle GPU, so host launch latency shows up between kernels. The 3-token draft extend is already packed. Draft CUDA graphs stay off.

ACC

GSM8K flexible-extract / strict-match, 5-shot, 1319 items, max_gen_toks=4096, stop <|im_end|>, temperature 0:

flexible-extract strict-match
Native ATOM, MTP off 0.9788 0.9757
Native ATOM, MTP on 0.9780 0.9750
SGLang-ATOM, MTP off 0.9780 0.9735
SGLang-ATOM, MTP on 0.9757 0.9727

RealWorldQA rule score, 765 items. This counts extracted answers only. The Qwen3.5-27B judge pass was not run.

correct accuracy
Native ATOM, MTP off 591 / 765 77.25%
Native ATOM, MTP on 594 / 765 77.65%
SGLang-ATOM, MTP off 599 / 765 78.30%
SGLang-ATOM, MTP on 599 / 765 78.30%

MTP acceptance

The MTP-on row above (328.12 tok/s, TPOT 10.70 ms) is before the draft QSA seq_lens fix. That build kept GSM8K answer accuracy, but GSM8K draft acceptance dropped from about 79% to 68%.

QSA requires logical_pos < seq_lens[req]. Target decode sits at seq_lens - 1, so no lift is needed. The second draft step is also 1 token per request (ForwardMode.DECODE), but its position is seq_lens. The packed draft path in _fill_qsa_like_native only lifted when tokens_per_req > 1, so that step left the current token outside the indexer context. The first draft extend (width 3) was lifted. The eager path used by the 79% tree already lifts on any draft forward. ReplaySSM stayed enabled; it was not the change that recovered acceptance.

Same GSM8K protocol as the table above, plus temperature=0, top_k=1, top_p=1. Acceptance is accepted draft tokens / drafted tokens (the bonus token is not in the denominator). accept len includes the bonus.

flexible-extract strict-match accept rate accept len
SGLang-ATOM before the lift fix, 1319 items 0.9773 0.9757 0.680 2.36
SGLang-ATOM without the packed-draft path, 1319 items 0.9773 0.9719 0.792 2.58
SGLang-ATOM after the lift fix, first 32 items 0.9688 0.9688 0.812 2.63

The 32-item run is only an acceptance check. It is not a replacement for the 1319-item score.

Remeasured the long-output benchmark after the fix, same shape as the MTP-on row (ISL 1024 / OSL 4096, concurrency 4, 8 prompts, ignore EOS, context 65536, decode graph batch 32):

Output tok/s Mean TPOT Median TPOT Mean ITL accept rate accept len
SGLang-ATOM, MTP on, before 328.12 10.70 ms 10.11 ms 25.03 ms — —
SGLang-ATOM, MTP on, after 399.69 9.31 ms 9.08 ms 24.89 ms 0.858 2.71

Mean ITL is unchanged, so the extra lift kernel did not lengthen the step. TPOT improved because each step accepted more draft tokens.

@github-actions

Copy link
Copy Markdown
Contributor

🏷️ CI Guide

Runs automatically on every eligible PR before approval:

  • ✅ Pre Checkin: Black, Ruff, catalog schema validation, non-GPU unit tests

Heavy model tests:

  • ✅ Run after the PR is approved and Pre Checkin passes
  • ✅ Run immediately when an approval review is submitted
  • ✅ Can be requested before approval with labels
Label Tests
ci:full Run all heavy PR model tests: native ATOM, vLLM, and SGLang
ci:atom Run native ATOM model accuracy tests
ci:vllm Run ATOM vLLM OOT model accuracy tests
ci:sglang Run ATOM SGLang model accuracy tests

Heavy jobs are skipped when the PR is not approved and no matching ci:* label is present.
Add labels via the sidebar or gh pr edit 2385 --add-label <label>

@ZLkanyo009
ZLkanyo009 marked this pull request as ready for review September 24, 2026 07:51
@ZLkanyo009
ZLkanyo009 force-pushed the lingzha/qwenflash-mtp branch from 48a17cf to 37db59d Compare September 24, 2026 09:00
@valarLip
valarLip merged commit ba51495 into main Sep 28, 2026
23 of 24 checks passed
@valarLip
valarLip deleted the lingzha/qwenflash-mtp branch September 28, 2026 02:36
zhangxinyuanliuhengyu pushed a commit that referenced this pull request Sep 28, 2026
Drop the 0.5.17 recognition shims now that main has #2385, but keep the
draft-arch rewrite and HC hidden-width install. GDN pad stays on the
split helper; ReplaySSM imports stay on the GDN backend.

Co-authored-by: Cursor <cursoragent@cursor.com>
zhangxinyuanliuhengyu pushed a commit that referenced this pull request Sep 28, 2026
The Flash decode-graph helper still constructs CpuGpuBuffer, but merging

Co-authored-by: Cursor <cursoragent@cursor.com>
#2385 over that patch left the import behind.
zhangxinyuanliuhengyu pushed a commit that referenced this pull request Sep 28, 2026
The hook imported qwen3_8_flash_next_bridge.refresh_flash_decode_graph_ple,
which is not in the tree, so every replay logged a warning and skipped.
#2385 already aliases PLE to GDN's static mamba_cache_indices and does not
rebuild PLE on replay. Keep the breakable-logits and replay-sync patches.

Co-authored-by: Cursor <cursoragent@cursor.com>
zhuyuhua-v pushed a commit that referenced this pull request Sep 28, 2026
* [sgl+atom] Upgrade SGLang to v0.5.19

Bump SGLang docker pins to v0.5.19 and retarget Kimi/MiniMax pool bridges
from the removed ModelRunnerKVCacheMixin onto KVCacheConfigurator.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [sgl+atom] fix(sglang): keep K3/MiniMax bridges slot-safe on 0.5.19

Store K3 mem-fraction restore off the slots dataclass and attach MiniMax
scale ABI on MiniMaxSparseKVPool without wrapping the upstream pool.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [sgl+atom] fix(sglang): publish V4 empty_kv_indptr on plugin metadata

SGLang V4 decode reads empty_kv_indptr during CUDA graph capture, but the
bridge builds base AttentionMetaData and never attached that buffer.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [sgl+atom] fix(sglang): return DSV4 draft backends as (stamp, backend)

SGLang 0.5.19 unpacks draft attention factories, so Pro MTP3 died in
scheduler init. Also publish empty_kv_indptr on verify metadata, share
MTP embed/head after load, and retry K3 mem-fraction restore until the
schedule is published.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [sgl+atom] fix(sglang): keep MiniMax index cache on ATOM FP8 dtype

SGLang 0.5.19 MiniMaxSparseKVPool still builds bf16 index-K unless
m3_fp8_attn_gemm is on. Align the builder model_dtype for that path and
keep the scale-buffer ABI.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [sgl+atom] fix(sglang): rebind kimi_linear_config on already-imported modules

prepare_model patches hybrid_arch after SGLang has already bound
kimi_linear_config into kv_cache_configurator and friends. Rebind those
local names so the K3 hybrid pool hook actually runs.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [sgl+atom] Upgrade SGLang to v0.5.20

Bump docker pins from v0.5.19 to v0.5.20. Keep the 0.5.19 plugin
bridges; Qwen3.8-Flash is already on main and does not need a
transformers bump.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [sgl+atom] drop Qwen4 recognition shims on SGLang 0.5.20

Upstream 0.5.20 ships qwen4_exp configs, M-RoPE, and processor mapping.
Keep Native GDN pad sentinels in their own file until Native GDN reads
Hybrid's already-padded buffers.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [sgl+atom] Adapt 0.5.20 CUDA-graph CLI and ServerArgs lookup.

SGLang 0.5.20 treats --cuda-graph-bs as ambiguous and retires
get_global_server_args(), which aborted R1 MTP3 and GLM plugin init
before any accuracy work ran.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [sgl+atom] Drop mesh/recipe CLI renames from 0.5.20 fix.

Keep only plugin ServerArgs lookup and SGLang CI extra_args that
block R1/GLM; leave mesh launch scripts and recipes for a follow-up.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [sgl+atom] fix(sglang): publish DSV4 proxy _unified_kv ABI for 0.5.20

SGLang reads pool._unified_kv after the ATOM proxy replaces
DeepSeekV4TokenToKVPool; keep the flag False and expose get_unified_kv
views so Flash/Pro MTP3 no longer AttributeError on CI.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [sgl+atom] Align K3 mem-fraction and MiniMax index budget with 0.5.20.

Mirror upstream aiter reserve predicates (resolved backend + HONOR env),
dedupe restore by override source name, wrap MiniMax configure() so
cell_size and pool index_dtype share the FP8 model_dtype swap, and
split get_sglang_server_args ImportError from live lookup failures.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [sgl+atom] Fix K3 mem-fraction overrides_log() and HONOR envs lookup.

overrides_log is a method on 0.5.20 — iterating the bound method was
TypeError'd into the publish-skip path. HONOR must read envs from
sglang.srt.environ.envs, not the environ module. Add CPU unit coverage.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [sgl+atom] Fix K3 mem-fraction unit test expected restore value.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [sgl+atom] Tighten K3 mem-fraction tests for HONOR and resolved backend.

HONOR skip now uses schedule 0.85 so a missed env lookup would override
to 1.0. Add coverage that raw aiter with unresolved ""/"auto" does not restore.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [sgl+atom] Fix Flash decode CUDA graph on SGLang 0.5.20

Skip pin_memory for QSA CpuGpuBuffer during HIP stream capture and register
Flash breakable/PLE/replay patches so decode graph capture works on MI308.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [sgl+atom] fix(sglang): satisfy Ruff on Flash decode graph patches

Narrow optional-import handlers and log failures so Pre Checkin reviewdog
passes on PR #2280.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [sgl+atom] style: black-format Flash PLE decode graph patch

Co-authored-by: Cursor <cursoragent@cursor.com>

* [sgl+atom] Restore CpuGpuBuffer import dropped by the bridge merge.

The Flash decode-graph helper still constructs CpuGpuBuffer, but merging

Co-authored-by: Cursor <cursoragent@cursor.com>
#2385 over that patch left the import behind.

* [sgl+atom] Drop the Flash decode-graph PLE refresh that never resolves.

The hook imported qwen3_8_flash_next_bridge.refresh_flash_decode_graph_ple,
which is not in the tree, so every replay logged a warning and skipped.
#2385 already aliases PLE to GDN's static mamba_cache_indices and does not
rebuild PLE on replay. Keep the breakable-logits and replay-sync patches.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [sgl+atom] Drop Flash graph debug patches and non-gating tests.

Replay-sync and breakable-logits are off the Flash full-graph path.
The K3 CPU mock and the extra DSV4 unified_kv asserts do not gate
Pre Checkin; keep the production adapters they were written around.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: xinyuazh <xinyuazh@hjbog-srdc-15.amd.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: root <root@hjbog-srdc-15.amd.com>
zhangxinyuanliuhengyu pushed a commit that referenced this pull request Sep 29, 2026
Move the EP decode asm MoE redirect and the HIP topk=1 Triton tree/verify
shims out of atom/model_ops and eagle3_llama_bridge. Ling's #2385 already
owns the Flash MTP adapter under plugin/; these 0.5.20 ROCm fixes stay
there too.

Co-authored-by: Cursor <cursoragent@cursor.com>
zhangxinyuanliuhengyu added a commit that referenced this pull request Oct 7, 2026
…#2426)

* [sgl+atom] Keep ATOM full-attn for Native Qwen4Exp on SGLang 0.5.20.

SGLang 0.5.20 swaps hybrid full attention onto QwenSparseAttnBackend, so Native Flash never allocates QSA CUDA-graph buffers and decode capture dies. Leave that backend for non-ATOM Qwen QSA and keep ATOMAttnBackendForSgl for Qwen4Exp.

* [sgl+atom] Keep Qwen3.8 Flash EP and MTP working on SGLang 0.5.20.

0.5.20 leaves the speculative draft width unresolved on the raw server args, renames the MTP entry class before ATOM's hook, and the ROCm sgl_kernel tree and greedy-verify kernels fault on the topk=1 chain. Size QSA graph buffers from the resolved spec, load Ling's NextN draft from the text config, keep EP decode on the asm MoE path, and use SGLang's Triton tree kernels on HIP.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [sgl+atom] Keep Flash EP/MTP ROCm workarounds in the plugin.

Move the EP decode asm MoE redirect and the HIP topk=1 Triton tree/verify
shims out of atom/model_ops and eagle3_llama_bridge. Ling's #2385 already
owns the Flash MTP adapter under plugin/; these 0.5.20 ROCm fixes stay
there too.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [sgl+atom] Keep the HIP Triton tree divert on Qwen4Exp only.

v0.5.20 sends every HIP speculative verify through verify_tree_greedy, and R1 MTP already completes on sgl_kernel. Divert tree build and greedy verify only after this process recognizes Qwen4Exp.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [sgl+atom] Refuse MiniMax mono in the SGLang plugin before the cudagraph check.

Co-authored-by: Cursor <cursoragent@cursor.com>

#2419 evaluates every mono refusal clause up front, and SGLang leaves cudagraph_mode unset, so MiniMax startup crashes before the plugin-mode clause can refuse. Return that refusal from the plugin without reading cudagraph_mode.

* [sgl+atom] Arm the Qwen4Exp HIP divert when the model is prepared.

Co-authored-by: Cursor <cursoragent@cursor.com>

#2427 wraps is_qwen_qsa later and returns before the inner hook, so the Triton tree/verify flag is set from prepare_model.

---------

Co-authored-by: xinyuazh <xinyuazh@hjbog-srdc-15.amd.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
zovonoir added a commit to zovonoir/ATOM that referenced this pull request Oct 19, 2026
…totype)

Qwen3.8-Flash-Next MTP on SGLang-ATOM runs every draft decode step eagerly
(ROCm#2385): the plugin skips draft graph capture for Qwen4ExpForCausalLMNextN,
citing the [3, N] mRoPE positions that native ATOM's DraftGraph cannot stage.
SGLang's EAGLEDraftCudaGraphRunner already carries mrope_positions buffers,
so that reason does not hold here. What blocked the graph was the plugin's
QSA metadata: the out-of-graph hook filled the target's _DECODE_GRAPH
buffers, which TARGET_VERIFY replays from.

Behind ATOM_SGLANG_QWEN4_DRAFT_CG=1 (default off, behaviour unchanged):
- register.py: stop skipping draft decode graph capture for the Flash
  drafter. Draft extend stays eager.
- qwen4_exp_bridge.py: the draft runner's out-of-graph hook no longer fills
  the target's buffers. Inside the captured draft forward, QSA metadata is
  rebuilt into the private _DRAFT_QSA buffers, so each draft step's
  positions / out_cache_loc are recomputed on replay.

Measured on rocm/atom-dev:sglang-v0.5.20-nightly_20261012 (ATOM a0527c9),
2x MI308X, release-note MTP launch (TP2/EP2, ctx 8192, max-running 8,
decode graph bs 8), ROCm#2418 harness ISL 1024 / OSL 2048, concurrency 1:

| draft steps | eager TPOT / accept | draft graph TPOT / accept |
|---|---|---|
| 2 | 12.37 ms / 2.39 | 9.16 ms / 2.35 |
| 3 | 13.57 ms / 2.61 | 8.19 ms / 2.72 |
| 4 | - | 8.69 ms / 2.74 |

Steps=2 output throughput, eager -> draft graph: 1024x1 91.6 -> 115.7,
4096x1 84.0 -> 116.9, 1024x8 499.4 -> 600.1, 4096x8 460.6 -> 571.0 tok/s.
GSM8K 400 (run_eval chat) 97.75% both ways. A graphed draft step costs about
0.7 ms against about 8 ms eager.

Not covered: multimodal requests, long context, decode graph bs > 8,
native ATOM. MTP still trails no-MTP TP2/EP1 (6.4 ms TPOT): draft extend is
still eager, and EP2 costs the target about 2.3 ms per step.
zovonoir added a commit to zovonoir/ATOM that referenced this pull request Oct 21, 2026
…totype)

Qwen3.8-Flash-Next MTP on SGLang-ATOM runs every draft decode step eagerly
(ROCm#2385): the plugin skips draft graph capture for Qwen4ExpForCausalLMNextN,
citing the [3, N] mRoPE positions that native ATOM's DraftGraph cannot stage.
SGLang's EAGLEDraftCudaGraphRunner already carries mrope_positions buffers,
so that reason does not hold here. What blocked the graph was the plugin's
QSA metadata: the out-of-graph hook filled the target's _DECODE_GRAPH
buffers, which TARGET_VERIFY replays from.

Behind ATOM_SGLANG_QWEN4_DRAFT_CG=1 (default off, behaviour unchanged):
- register.py: stop skipping draft decode graph capture for the Flash
  drafter. Draft extend stays eager.
- qwen4_exp_bridge.py: the draft runner's out-of-graph hook no longer fills
  the target's buffers. Inside the captured draft forward, QSA metadata is
  rebuilt into the private _DRAFT_QSA buffers, so each draft step's
  positions / out_cache_loc are recomputed on replay.

Measured on rocm/atom-dev:sglang-v0.5.20-nightly_20261012 (ATOM a0527c9),
2x MI308X, release-note MTP launch (TP2/EP2, ctx 8192, max-running 8,
decode graph bs 8), ROCm#2418 harness ISL 1024 / OSL 2048, concurrency 1:

| draft steps | eager TPOT / accept | draft graph TPOT / accept |
|---|---|---|
| 2 | 12.37 ms / 2.39 | 9.16 ms / 2.35 |
| 3 | 13.57 ms / 2.61 | 8.19 ms / 2.72 |
| 4 | - | 8.69 ms / 2.74 |

Steps=2 output throughput, eager -> draft graph: 1024x1 91.6 -> 115.7,
4096x1 84.0 -> 116.9, 1024x8 499.4 -> 600.1, 4096x8 460.6 -> 571.0 tok/s.
GSM8K 400 (run_eval chat) 97.75% both ways. A graphed draft step costs about
0.7 ms against about 8 ms eager.

Not covered: multimodal requests, long context, decode graph bs > 8,
native ATOM. MTP still trails no-MTP TP2/EP1 (6.4 ms TPOT): draft extend is
still eager, and EP2 costs the target about 2.3 ms per step.
valarLip pushed a commit that referenced this pull request Oct 29, 2026
…decode + extend) (#2645)

* perf(sglang): capture Qwen3.8-Flash MTP draft decode CUDA graphs (prototype)

Qwen3.8-Flash-Next MTP on SGLang-ATOM runs every draft decode step eagerly
(#2385): the plugin skips draft graph capture for Qwen4ExpForCausalLMNextN,
citing the [3, N] mRoPE positions that native ATOM's DraftGraph cannot stage.
SGLang's EAGLEDraftCudaGraphRunner already carries mrope_positions buffers,
so that reason does not hold here. What blocked the graph was the plugin's
QSA metadata: the out-of-graph hook filled the target's _DECODE_GRAPH
buffers, which TARGET_VERIFY replays from.

Behind ATOM_SGLANG_QWEN4_DRAFT_CG=1 (default off, behaviour unchanged):
- register.py: stop skipping draft decode graph capture for the Flash
  drafter. Draft extend stays eager.
- qwen4_exp_bridge.py: the draft runner's out-of-graph hook no longer fills
  the target's buffers. Inside the captured draft forward, QSA metadata is
  rebuilt into the private _DRAFT_QSA buffers, so each draft step's
  positions / out_cache_loc are recomputed on replay.

Measured on rocm/atom-dev:sglang-v0.5.20-nightly_20261012 (ATOM a0527c9),
2x MI308X, release-note MTP launch (TP2/EP2, ctx 8192, max-running 8,
decode graph bs 8), #2418 harness ISL 1024 / OSL 2048, concurrency 1:

| draft steps | eager TPOT / accept | draft graph TPOT / accept |
|---|---|---|
| 2 | 12.37 ms / 2.39 | 9.16 ms / 2.35 |
| 3 | 13.57 ms / 2.61 | 8.19 ms / 2.72 |
| 4 | - | 8.69 ms / 2.74 |

Steps=2 output throughput, eager -> draft graph: 1024x1 91.6 -> 115.7,
4096x1 84.0 -> 116.9, 1024x8 499.4 -> 600.1, 4096x8 460.6 -> 571.0 tok/s.
GSM8K 400 (run_eval chat) 97.75% both ways. A graphed draft step costs about
0.7 ms against about 8 ms eager.

Not covered: multimodal requests, long context, decode graph bs > 8,
native ATOM. MTP still trails no-MTP TP2/EP1 (6.4 ms TPOT): draft extend is
still eager, and EP2 costs the target about 2.3 ms per step.

* perf(sglang): capture the Qwen3.8-Flash MTP draft-extend CUDA graph (prototype)

On top of the draft decode graph (ATOM_SGLANG_QWEN4_DRAFT_CG=1),
ATOM_SGLANG_QWEN4_DRAFT_EXTEND_CG=1 also graphs the draft extend that runs
after every verify.

- SGLang only captures draft extend on HIP for aiter multi-step / DSV4 / DSA
  backends, so the Flash drafter takes the plugin-built
  EAGLEDraftExtendCudaGraphRunner path that DSV4 / GLM-5.2 use. The QSA
  metadata is rebuilt into _DRAFT_QSA inside the graph, as for draft decode.
  The DSV4 / GLM-5.2 long seq_len fill for pad rows is not applied to Flash.
- The plugin's EAGLEDraftExtendCudaGraphRunner replay wrapper took only
  (self, forward_batch); SGLang 0.5.20 calls execute(forward_batch,
  select_index). Forward extra arguments in every branch.

Measured on rocm/atom-dev:sglang-v0.5.20-nightly_20261012 (ATOM a0527c9),
2x MI308X, TP2/EP2.

Release-note launch (ctx 8192, max-running 8), ISL 1024 / OSL 2048, conc 1:

| steps | decode graph only | decode + extend graph | GSM8K 400 |
|---|---|---|---|
| 2 | 9.16 ms | 5.90 ms | 97.00% |
| 3 | 8.19 ms | 6.01 ms | 97.00% (accept 3.44, same as eager extend) |
| 4 | 8.69 ms | 5.17 ms | 97.25% |
| 5 | - | 5.02 ms | 97.75% |
| 6 | - | 5.88 ms | 96.75% |

No MTP, TP2/EP1 on the same image: 6.4 ms.

Long-context launch (ctx 132096, max-running 64, decode graph bs 64,
mem 0.88), #2418 29-case sweep (ISL 1024-129024, conc 1-32, OSL 2048),
668/668 requests each. Output throughput geomean:

| | vs MTP off, TP2/EP1 | vs eager MTP, steps 2 | cases ahead of MTP off |
|---|---|---|---|
| steps 2 | +14.5% | +43.9% | 25/29 |
| steps 4 | +15.4% | +45.0% | 25/29 |

Cases behind MTP off: 63488 x 16/32 and 129024 x 8/16, -0.3% to -9.2%.
Multimodal (1080p image + ~3.8K-token text, conc 1-16): all answers
correct on both launches.

* fix(sglang): stage and advance the Flash MTP draft mRoPE; register the draft graph flags

SGLang's EAGLE draft decode / draft-extend graph runners capture
`mrope_positions` but refresh only the 1-D `positions` on replay, and the
buffer is shared by name and shape with other runners. The Flash drafter
reads the 3-row positions, so the decode graph ran every draft step at RoPE
position 1 and the extend graph read whatever target verify left behind.
Stage SGLang's live rows (multimodal offsets included; 1-D positions on all
three rows when SGLang computed none) before both replays.

EAGLE `draft_forward` also advances only the 1-D positions between draft
decode steps, and the runners hand the model a copy of the batch, so the
3-row positions stayed at step 0 (eager and graph). Advance the caller's
mRoPE after every Flash draft decode forward; in a graph capture the add is
recorded on the graph's buffer.

ATOM_SGLANG_QWEN4_DRAFT_CG / _DRAFT_EXTEND_CG are now registered in
atom.utils.envs and read there by both the capture gate and the QSA metadata
hook, which parsed them differently (`yes` / `on` enabled capture but not the
hook's target-buffer isolation).

* chore(sglang): drop PROTOTYPE markers from the Flash draft graph comments

* perf(sglang): always capture the Flash MTP draft graphs; drop ATOM_SGLANG_QWEN4_DRAFT_CG / _DRAFT_EXTEND_CG

With the draft mRoPE staged and advanced, the draft decode and draft-extend
graphs match eager drafting in accuracy and acceptance, and eager drafting
is host-launch bound (slower than no MTP). Capture both graphs whenever the
Flash drafter runs; --disable-cuda-graph still turns them off.

* fix(sglang): keep the Flash draft QSA hook Qwen-only; size draft QSA for the graph bucket; order the legacy argmax patch

- prepare_qwen4_exp_decode_graph_metadata validates the Qwen4 config before
  the draft-decode shortcut, so other EAGLE drafters on the ATOM backend keep
  their normal attention tables.
- _ensure_draft_qsa covers the decode graph bucket when
  --max-running-requests is omitted (32-row default vs. 64-row capture).
- On legacy SGLang, install the argmax draft_forward replacement before the
  mRoPE wrapper so the wrapper composes with it.
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