Skip to content

[WIP][AMD] Implement MTP support for qwen3.5 MXFP4 - #720

Open
raikonenfnu wants to merge 2 commits into
mainfrom
raikonen/mtp_qwen35
Open

[WIP][AMD] Implement MTP support for qwen3.5 MXFP4#720
raikonenfnu wants to merge 2 commits into
mainfrom
raikonen/mtp_qwen35

Conversation

@raikonenfnu

Copy link
Copy Markdown
Contributor

Summary

Enables Multi-Token Prediction (MTP / speculative decoding) for Qwen3.5-397B-A17B MXFP4 on AMD. The MXFP4 checkpoint keeps its MTP/NextN draft branch in bf16 even though the target model is MXFP4, so the draft path needs an unquantized MoE apply kernel that AMD did not previously have. This PR adds that path, wires the draft branch through the existing kernel-registry boundary, and stabilizes MoE routing so greedy MTP stays target-equivalent.

Key changes and why

1. AMD Triton unquant MoE draft path (tokenspeed-kernel/.../ops/moe/triton/unquant.py)

  • Adds a registry-selected unquant MoE apply kernel for precomputed top-k routing on AMD.
  • Reuses the existing MXFP4 ragged-matmul routing flow and transposes dense expert weights into the [expert, K, N] layout consumed by triton_kernels.matmul.
  • Registers TP and EP precomputed-topk variants.
  • Why: the bf16 draft branch had no registry path on AMD; this lets the draft model load/execute through the same boundary as the rest of MoE instead of a runtime special case.

2. Treat the mxfp4 draft branch as unquantized (qwen3_5_nextn.py)

  • The MTP branch is dense bf16 in both nvfp4 and mxfp4 checkpoints; treating it as quantized wrongly selected MXFP4 expert weights.
  • Why: fixes draft-model construction for MXFP4 targets.

3. Deterministic MoE routing under MTP (runtime/layers/moe/topk.py)

  • Native top-k helper now resolves exact routing ties with a stable descending sort for both normal and bias-corrected routing.
  • Why: MTP changes target-verify batch shape from batch_size to batch_size * draft_width rows, which can perturb expert selection on exact ties. The Triton MXFP4 precomputed MoE path consumes these ids directly, so stable ids are required for MTP/non-MTP target equivalence.

4. Disable SMG health checks under ts serve by default (cli/serve_smg.py)

  • Why: the gateway health probe was being scheduled as real HEALTH_CHECK_* generation work during long eval batches, changing batch composition while measuring deterministic greedy evals.

5. CI eval exercises MTP (test/ci/eval/qwen3.5-397b-a17b-mxfp4-evalscope-aime25.yaml)

  • Adds --speculative-algorithm MTP --speculative-num-steps 3 --speculative-num-draft-tokens 4 --speculative-draft-model-quantization unquant and disables thinking traces in the chat template to measure the faster speculative path.

6. Tests

  • Kernel-selection coverage + a torch-reference test for the bf16 unquant MoE layout.
  • Top-k tie contract tests (exact-tie and bias-corrected) in test/runtime/layers/test_moe_topk.py.
  • Gateway-args test for the health-check default.

Local validation (TP=4, Qwen3.5-397B-A17B MXFP4)

  • AIME25 limit-8: score 1.0 with enable_thinking=false (~3.5 min) and 1.0 with thinking on (~6 min).
  • MTP accept length ~3.3–3.7 of 4 draft tokens; accept rate ~0.85.
  • Avg TPOT ~13 ms (thinking off) / ~15 ms (thinking on).

Known limitations (why WIP)

  • The stable-sort fix only guarantees exact-tie determinism. Near-tie batch-shape invariance is still open (identical concurrent requests can diverge in trajectory while remaining correct) — tracked as production work.
  • The stable full sort is O(E log E); a deterministic O(E log K) top-k is the preferred production form.
  • Only the torch-native routing path is covered; other routing paths (grouped/CUDA/flashinfer) are not yet audited to the same tie contract.
  • Disabling SMG health checks is a mitigation; the health probe should instead be excluded from scheduler batch composition.

Test plan

  • TP=4 Qwen3.5 397B MXFP4 AIME25 limit-8 MTP eval meets threshold in CI
  • Unit tests pass (MoE top-k tie contract, unquant MoE torch reference, kernel selection, gateway args)
  • No HEALTH_CHECK_* generation requests during ts serve eval after readiness

@raikonenfnu
raikonenfnu requested review from a team as code owners July 18, 2026 06:47
Qwen3.5 MXFP4 checkpoints keep the MTP/NextN branch in bf16 even when the target model is loaded as MXFP4. Treating that draft branch as quantized made construction select MXFP4 expert weights for tensors that are actually dense bf16; treating it as unquantized exposed the next missing piece: AMD had no registry-selected unquant MoE apply kernel for precomputed top-k routing.

Add an AMD Triton unquant MoE path that reuses the existing ragged matmul routing flow, transposes dense expert weights into the [expert, K, N] layout consumed by triton_kernels.matmul, and registers TP/EP precomputed-topk variants. The draft model can now load and execute through the same kernel registry boundary as the rest of MoE instead of adding a runtime special case.

Update the Qwen3.5 397B MXFP4 AIME eval to exercise MTP with the unquant draft branch, running with thinking enabled (max_tokens 32768). The eval scores 1.0 with MTP while measuring the faster speculative path. Add selection coverage and a small torch-reference test for the bf16 unquant MoE layout.

Signed-off-by: Stanley Winata <stanley.winata@amd.com>
Greedy MTP should be target-equivalent, but local debugging showed Qwen3.5 output drift was not isolated to speculative decode. A faster TP=1 35B reproducer showed that four identical non-MTP requests batched together could take different text trajectories, while direct MHA, GDN, and precomputed MoE primitive checks were row-invariant. That points at native MoE routing ties and eval-time request perturbation rather than an MTP cache corruption.

Make the native MoE top-k helper resolve exact routing ties with stable descending expert order for both normal and bias-corrected routing. The Triton MXFP4 precomputed MoE path consumes these ids directly, so stable ids are required before comparing MTP and non-MTP target behavior.

Also disable SMG health checks under ts serve by default. The gateway health probe was being scheduled as real HEALTH_CHECK generation work during long eval batches, changing batch composition while measuring deterministic greedy evals. Keep the existing circuit-breaker/retry disable defaults and add focused tests for both the top-k tie contract and gateway args.

Signed-off-by: Stanley Winata <stanley.winata@amd.com>
@raikonenfnu
raikonenfnu force-pushed the raikonen/mtp_qwen35 branch from ba80ab7 to 7faa322 Compare July 18, 2026 06:49

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ba80ab7796

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

scores: torch.Tensor,
topk: int,
) -> tuple[torch.Tensor, torch.Tensor]:
values, indices = torch.sort(scores, dim=-1, descending=True, stable=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid sorting every expert for top-k routing

When torch_native_fused_topk is used on the normal non-grouped MoE routing path, topk is only a small subset of the experts, but this helper now performs a full stable sort over every expert score for every token/layer. That makes the hot path materialize and order the entire expert dimension even when there are no exact ties, which can add significant routing latency and memory traffic on large MoE models; consider keeping the torch.topk path for non-tied rows or applying the stable tie handling only around actual tie cases.

Useful? React with 👍 / 👎.

_DEFAULT_SMG_DISABLE_FLAGS = (
"--disable-circuit-breaker",
"--disable-retries",
"--disable-health-check",

@borontion borontion Jul 18, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@lightseek-bot @LorrinWWW is that ok we disable smg health check by default?

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