Skip to content

fix(qwen38): mono draft fallback without a RoPE cache; QSA RoPE at ATOM's MRoPE positions - #2993

Open
yifehuan wants to merge 4 commits into
mainfrom
yifehuan/qwen38_flash_mono
Open

yifehuan wants to merge 4 commits into
mainfrom
yifehuan/qwen38_flash_mono

Conversation

@yifehuan

@yifehuan yifehuan commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Text-only Qwen3.8-Flash-Next deployments leave rope_position_cache unset. When MTP leaves the <=4-request draft Mono program, the fused QSA fallback introduced in #2610 asserts that this optional cache exists and terminates the model worker. We reproduced this after a six-request prefill, while the HTTP process continued to respond.

Dispatch to ATOM's existing native QSA implementation before fused preparation when the position cache is absent. That implementation reconstructs text positions arithmetically; the fused fallback remains eligible when the three-axis cache is present. The decision is step_meta.draft_attention_fused, which imports no GPU library, so CI tests it on CPU: q1 / q4 at 1 and 6 requests, with and without the position cache, plus prefill, no QSA metadata and another row count. The Qwen mono tests are now one file, tests/test_qwen38_mono.py (it replaces test_qwen38_mono_{plan,prep,ple}.py). Its CPU half runs in CI; its GPU half skips without a GPU or Triton.

Validation:

  • tests/test_qwen38_mono.py: 35 tests pass on CPU. On MI355X, 141 pass together with test_mono_runtime.py, none skipped.
  • Text-only e2e (--hf-overrides {"_multimodal_config": null}), real MTP, Zhang Ling's A_lo server, head 2460991: 300 GSM8K questions at client concurrency 6 scored 294 / 300, and at 16 scored 298 / 300. Acceptance was 0.724 on both, with 0 tracebacks. Mono served the 1-4 request steps, and the fallback draft served the rest.
  • A full 48-layer TP1/c4 replay with this guard completed 394 formal requests with zero request errors or cancellations. That window was excluded from performance acceptance because separate QSA Triton compilation occurred during timing.
  • Formatting, lint and git diff --check checks recorded before submission.

This follow-up uses the original yifehuan/qwen38_flash_mono branch name, restored from current main after the merged branch was deleted.

Second fix (8106f2c): rotate mono QSA rows at ATOM's MRoPE positions

#2610's mono QSA programs rotated q / k and the index keys at the cache row (seq_lens[b] + s) instead of the position ATOM hands the model. For text the two are equal. For a request with an image, ATOM's decode positions carry the request's MRoPE offset (backends.py: arange(start + delta, stop + delta)), and verify_step only refuses a step that holds image tokens, so the decode steps after an image ran on mono with every new token rotated off by that offset. An eager diagnostic on synthetic 896-1280 px images measured offsets of -736 / -756 / -1160 on the mono-served verify steps (0 for text).

The verify prep (the same graph-capturable launch) now also writes rope_off[b] = positions[b * spec] - (context_lens[b] - spec) (0 for an invalid request). QKNormRope and the indexer rotate at the cache row plus rope_off[b], while pages and blocks are still addressed by cache row. The draft program reads the same argument, left at 0, so it keeps its previous positions; an image request's draft only affects MTP acceptance, and matching the drafter's MRoPE positions is a follow-up.

Validation (MI355X, full 48-layer checkpoint, TP1, MTP3, Zhang Ling's A_lo server):

  • The 8 GDN programs build identical ISA. The 12 QSA / MTP programs keep their VGPR / SGPR / LDS, with no scratch.
  • 145 GPU tests pass, including the draft-dispatch tests above, which CPU CI skips.
  • Full 1319-question GSM8K with real MTP: 1289 / 1319 (acceptance 0.726). Text is unchanged, since rope_off is 0.
  • Fixed load against the parent, measured before and after it, request TPOT: C1 64K 1.904 / 1.900 / 1.898 ms; C2 16K 2.428 / 2.446 / 2.428 ms. C2 is 0.9% slower from the extra offset load.
  • Images at client concurrency 1: OCR 47 / 48 with the fix, Native 46 / 48. The image tasks did not show a measurable difference before the fix either, so this is a semantic correction rather than a measured accuracy gain.

Separately, Native itself mixes images across concurrent requests: at client concurrency 4, Native's OCR drops from 46 / 48 to 13-16 / 48, and wrong answers carry other requests' numbers. This is independent of mono and not addressed here.

@github-actions

github-actions Bot commented Nov 4, 2026

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 2993 --add-label <label>

@valar-cop valar-cop left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some changes needed..

e2e text-only run at 5-16 concurrent requests?

ci green.

Comment thread tests/test_qwen38_mono_plan.py Resolved
@zufayu
zufayu requested a review from yhl-amd November 5, 2026 01:18
The mono target's QSA programs rotated q / k and the index keys at the
cache row (seq_lens[b] + s) instead of the position ATOM hands the model.
For text the two are equal; for a request with an image, ATOM's decode
positions carry the request's MRoPE offset (backends.py: arange(start +
delta, stop + delta)), and verify_step only refuses a step that holds
image tokens, so the decode steps after an image ran on mono with every
new token rotated off by the offset. Measured with an eager diagnostic on
synthetic 896-1280 px images: offsets of -736 / -756 / -1160 on the
mono-served verify steps, 0 for text.

The verify prep (one launch, graph-capturable) now also writes
rope_off[b] = positions[b * spec] - (context_lens[b] - spec), 0 for an
invalid request; QKNormRope and the indexer rotate at the cache row plus
rope_off[b] (the query rows, the span tasks' query vectors and the keys
of the blocks a step completes), while pages and blocks are still
addressed by cache row. rope_pos clamps at 0 as well.

The draft program reads the same argument, left at 0: its rows keep the
drafter's cache-row positions as before (an image request's draft only
affects MTP acceptance, not the output); matching the drafter's MRoPE
positions is a follow-up.

Text is unchanged (rope_off 0). The 8 GDN programs build identical ISA;
the 12 QSA / MTP programs keep their VGPR / SGPR / LDS, no scratch. On
MI355X: 145 GPU tests pass; full 1319 GSM8K with real MTP 1289 / 1319
(acceptance 0.726); fixed load against the parent, before / after, request
TPOT C1 64K 1.904 / 1.900 / 1.898 ms, C2 16K 2.428 / 2.446 / 2.428 ms
(+0.9%: the extra offset load in the QSA programs). Images at client
concurrency 1: OCR 47 / 48 (Native 46 / 48).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@yifehuan yifehuan changed the title fix(qwen): handle optional RoPE cache in Mono draft fallback fix(qwen38): mono draft fallback without a RoPE cache; QSA RoPE at ATOM's MRoPE positions Nov 5, 2026
… path on CPU

- step_meta.draft_attention_fused (CPU-safe) decides whether the fallback
  draft's QSA attention takes its fused path; draft_fallback.attention calls
  it, and the assert the text-only guard had made dead is gone.
- tests/test_qwen38_mono.py replaces test_qwen38_mono_{plan,prep,ple}.py. The
  GPU libraries are imported inside the tests that need them, so the CPU half
  runs in CI, now including the attention path at 1 and 6 requests, q1 and
  q4, with and without the three-axis position cache. The GPU half (verify
  prep, draft metadata, the PLE host table) skips without a GPU or Triton.
  The metadata puts test also covers rope_off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@yifehuan

yifehuan commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

e2e text-only run, as asked in the review: --hf-overrides {"_multimodal_config": null} (no three-axis position cache), real MTP, Zhang Ling's A_lo server on one MI355X, this head (2460991). 300 GSM8K questions at client concurrency 6: 294 / 300. At 16: 298 / 300. Acceptance 0.724 on both, 0 tracebacks. Mono served the 1-4 request steps; past 4 requests the fallback draft ran its attention through ATOM's QSA module, since there is no position cache.

The PR also has a second fix now, 8106f2c (QSA RoPE at ATOM's MRoPE positions for image requests). The description has the evidence.

@yifehuan

yifehuan commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

@valar-cop re-review

Head 2460991; CI is green. Since your review on e6a0a63:

  • 8106f2c: the QSA programs rotate at ATOM's MRoPE positions for image requests (offset of -736 to -1160 measured before the fix; see the description).
  • 2460991: your three points. The dead assert is removed. The dispatch decision is step_meta.draft_attention_fused, tested on CPU in CI. The text-only e2e run gave 294 / 300 at c6 and 298 / 300 at c16. The Qwen mono tests are now one file, tests/test_qwen38_mono.py.

@yifehuan

yifehuan commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

@valar-cop re-review

@valar-cop

Copy link
Copy Markdown

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants