Skip to content

qwen36 MoE prefill: fix MOE_GPU device-tilemap correctness (#586) + int8 projection default#601

Draft
James-CUDA wants to merge 2 commits into
gittensor-ai-lab:mainfrom
James-CUDA:perf/qwen36-moe-next-opt
Draft

qwen36 MoE prefill: fix MOE_GPU device-tilemap correctness (#586) + int8 projection default#601
James-CUDA wants to merge 2 commits into
gittensor-ai-lab:mainfrom
James-CUDA:perf/qwen36-moe-next-opt

Conversation

@James-CUDA

@James-CUDA James-CUDA commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Two independent Qwen3.6 MoE-prefill changes:

1. fix(qwen36): restore MoE device-tilemap (MOE_GPU) correctness. SPARKINFER_PREFILL_MOE_GPU=1 (device tilemap, skips the per-layer D2H counts sync) is silently broken on main: the down projection's masked Q→int8 dequant runs with cols=moe_ffn=512, which the vectorized fast-mask kernel declines (cols not a multiple of 1024), and launch_gguf_dequant_rows_i8_mask ignored that false return — so the int8 down weights were never written and the down GEMM ran on stale data (the #586 corruption). Fix: fall back to a correct masked slow kernel (single-pass, register-resident, same dequant/amax/round as the host deq_rows_i8_kernel; dead experts exit via the counts mask). The default host-tilemap path is untouched and MOE_GPU stays default OFF (the device path isn't a prefill speedup — it dequants all experts per group vs the host path's live-only coalesced runs — so this fixes the opt-in flag, not perf).

prefill_check … 512 16, MOE_GPU=1 TOP1 KL
before (main) 0.375 0.349
after 0.9375 0.010

2. perf(qwen36): int8 tensor-core projections default-on for the MoE-hybrid prefill. Flips the use_i8 default for the Qwen3.6-256E MoE path (attn/GDN/router-feeding projections), scoped to Qwen3.6 by the existing n_experts==256 guard (Qwen3-30B is 128E, untouched). Scored accuracy is provably unchanged (qwen3_gguf_score teacher-forces via the bf16 decode forward_token path, which SPARKINFER_PREFILL_I8 never reaches — verified byte-identical PPL/ARGMATCH on/off); prefill fidelity holds (TOP1 0.875 @512 / 0.94 @4k, bar 0.80).

⚠️ Honesty note on speed: this was measured +10.6% pp @512 / +18% @4k vs the bf16 baseline, but main has since defaulted MoE projections to fp8 (#595), which captures the same win a different way. Against current main, the int8 flip is ~+0.6% @512 (fp8 and int8 are within noise). Kept in this PR per maintainer request; the standalone value here is the correctness fix above. SPARKINFER_PREFILL_I8=0 restores bf16.

Proof of speedup

  • Tested on RTX 5090 (sm_120) — int8 flip is within noise vs the current fp8 default; no end-to-end speed claim against current main. The primary contribution is the correctness fix.

Prefill pp tok/s — Qwen3.6-35B-A3B-UD-Q4_K_M, RTX 5090 (int8 default vs SPARKINFER_PREFILL_I8=0 fp8, current main):

ctx fp8 (current main) int8 (this PR)
512 5726 5745
4096 16410 16403
# correctness (the real point of this PR), MOE_GPU=1, prefill_check 512 16:
# main:     TOP1 6/16 0.3750   KL 0.34935
# this PR:  TOP1 15/16 0.9375  KL 0.00974

@skyrocket2026 skyrocket2026 added area:runtime subsystem (emission weight 0.26) needs-benchmark Box ticked but decode before/after not filled with a real improvement — not evaluated labels Jul 23, 2026
@skyrocket2026

Copy link
Copy Markdown
Member

⏳ Needs a benchmark to be evaluated

You ticked Tested on RTX 5090 but neither the decode nor the Qwen3.5 prefill before → after table shows a real claimed improvement. The on-device eval won't run until at least one does.

Decode (end-to-end, not an isolated-kernel microbench):

bench/scripts/bench.sh --download            # baseline (before)
bench/scripts/bench.sh --download            # your branch (after)

Prefill pp (Qwythos — report your best of 4k/32k/64k from the prefill pp line in bench output):

bench/scripts/bench.sh --download --ctx 4096   # try 4096 / 32768 / 65536 / 131072
bench/scripts/bench.sh --download --ctx 4096   # your branch

Then the bot greenlights it on the next poll and evaluates it on an RTX 5090.

@James-CUDA
James-CUDA marked this pull request as draft July 23, 2026 23:52
…fill (+10.6% pp @512, +18% @4k)

The Qwen3.6-256E MoE prefill kept attn/GDN/router-feeding projections on bf16
out of caution (the top-k router can turn per-token int8 error into different
expert picks). Measured on Qwen3.6-35B-A3B UD-Q4_K_M this stays inside the
prefill fidelity veto (batched-vs-token-loop TOP1 0.875 @512 / 1.0 @4k, bar
0.80; KL ~0.016-0.020, bar 0.05), and the scored accuracy is untouched:
qwen3_gguf_score teacher-forces via the bf16 decode forward_token path, which
SPARKINFER_PREFILL_I8 never reaches (verified byte-identical PPL/ARGMATCH with
the flag on and off).

Default use_i8 ON for the MoE path (was !moe); scoped to Qwen3.6 by the
n_experts==256 batched-MoE guard (Qwen3-30B is 128E and never enters this
path, so its guard is untouched). Adds a bf16 fallback past bf16_minctx (96k)
for the GDN int8 drift, mirroring the dense path; all eval contexts (<=32k)
stay on the int8 fast path. SPARKINFER_PREFILL_I8=0 restores bf16 (A/B).
@James-CUDA
James-CUDA force-pushed the perf/qwen36-moe-next-opt branch from 7c012c1 to f0fcbf4 Compare July 24, 2026 00:14
…d down dequant no-op

SPARKINFER_PREFILL_MOE_GPU=1 (device tilemap, skips the per-layer D2H counts
sync) is silently broken on main: the down projection's masked Q->int8 dequant
has cols=mffn=512, which the vectorized fast-mask kernel declines (cols not a
multiple of 1024), and launch_gguf_dequant_rows_i8_mask ignored that false
return — so the int8 down weights were never written and the down GEMM ran on
stale/garbage data. Batched-vs-token-loop prefill_check collapses to TOP1 ~0.375
/ KL ~0.35 @512 (this is the gittensor-ai-lab#586 corruption that forced the path default-off).

Fix: launch_gguf_dequant_rows_i8_mask now falls back to a correct masked slow
kernel (single-pass, register-resident, same dequant/amax/round as the host
deq_rows_i8_kernel; dead experts exit via the counts mask) when the fast path
declines. Restores prefill_check to TOP1 0.94 / KL ~0.01 @512 with MOE_GPU=1.

Keeps SPARKINFER_PREFILL_MOE_GPU default OFF: the device path is not currently a
prefill speedup (it must dequant all experts per group rather than the host
path's live-only coalesced runs), so this is a correctness fix for the opt-in
flag, not a perf change. The default host-tilemap path is unaffected.
@James-CUDA James-CUDA changed the title perf(qwen36): int8 tensor-core projections for MoE-hybrid batched prefill (+10.6% pp @512, +18% @4k) qwen36 MoE prefill: fix MOE_GPU device-tilemap correctness (#586) + int8 projection default Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:runtime subsystem (emission weight 0.26) needs-benchmark Box ticked but decode before/after not filled with a real improvement — not evaluated

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants