Skip to content

qwen3_5 cuda: serve dense EXL3 checkpoints with the DFlash2 drafter - #1474

Closed
diwakergupta wants to merge 1 commit into
ashhart:mainfrom
diwakergupta:exl3-dense
Closed

diwakergupta wants to merge 1 commit into
ashhart:mainfrom
diwakergupta:exl3-dense

Conversation

@diwakergupta

Copy link
Copy Markdown
Contributor

What this changes

The native CUDA qwen3_5 engine now serves dense EXL3 checkpoints (turboderp's Qwen3.8-27B-exl3) and attaches the DFlash2 drafter to EXL3 targets. This is part (a) of the split of #1407 that @ashhart suggested; (b) prompt tiles, (c) memory and (d) copy policy follow as separate PRs.

  • cuda_geometry.zig: admits non-MoE qwen3_5 EXL3 checkpoints (pocketed ones are still refused).
  • cuda_exl3_weights.zig: loads the dense SwiGLU MLP (mlp.{gate,up,down}_proj) as EXL3 linears, and allocates the grouped-expert scratch only for MoE geometry.
  • cuda_native.zig: lists exl3 in the family's formats, and openDrafter no longer refuses EXL3 targets.
  • cuda_draft_frame.zig: the drafter reads the target's EXL3 head over its two vocabulary spans as column views of the stored matrix. It uses the same packing path as the affine head.
  • exl3.zig: adds Weight.columns(from, n), a view of whole 128-column blocks that keeps the parent's strides, with a host test.
  • Prompt arithmetic is unchanged: prompt rows still take 128-row blocks of the decode linear. So the Qwen3.5-MoE EXL3 checkpoints keep their bits.
  • Docs: the 27B recipe gains a "Native EXL3 checkpoints" section. exl3.md and cuda.md no longer say the native engine serves no EXL3 pack.

Licences: turboderp/Qwen3.8-27B-exl3 is Apache-2.0 (base Qwen/Qwen3.8-27B), and the drafter z-lab/Qwen3.8-27B-DFlash2 is Apache-2.0. No code or tables were taken from other engines.

Receipt

  • Environment. RTX 3090 Ti (24 GB, sm_86, CUDA 13.1 nvcc), zig build native -Dsm=86. Checkpoint turboderp/Qwen3.8-27B-exl3 branch 3.50bpw (rev 8351c54e), drafter z-lab/Qwen3.8-27B-DFlash2 (rev 50307d4c) at --drafter-bits 4. bf16 KV cache (default), --context 32768, one rank. The binary was built from this branch before its rebase onto ee896951; the rebase changed no file in this PR.
  • Exactness, drafted vs "draft": false and concurrent vs solo. tools/bench_concurrent.py --levels 2 --tokens 256 --alone --serial, greedy and sampled:
    • The default code and chat cells: all alone and serial checks equal, none unequal or failed.
    • --prompts with two long prompts (5,080 and 2,278 tokens, so one spans a 4,096-row cut): all equal.
  • Exactness, resumed vs fresh. The engine cuts prompt passes every 4,096 rows and keeps a state at each cut.
    • Turn 1 is a ~5,100-token document. Turn 2 shares its first 4,096 tokens and ends 16, 17, 64 or 300 rows past the cut (sized with /tokenize). Each tail length runs greedy and seeded-sampled, 128 tokens.
    • Every resumed turn 2 reported cached_tokens 4096. The fresh runs went to a restarted server and reported cached_tokens 0.
    • token_sha256 was equal in all 8 cases.
  • Decode speed (2 streams, bench_concurrent, per-stream tok/s): code 155 / 122 (sampled / greedy), chat 86 / 90. There is no baseline: main refuses this checkpoint.
  • Prompt speed: n/a. The prompt arithmetic is unchanged from main's EXL3 path; cold prefill of an 8k prompt is 14.5 s here, and (b) changes it.
  • Tests. zig build test test-kernel-contract test-golden test-server-security test-pull-security fuzz-cortheon test-cuda-host: exit 0, 447/447 build steps succeeded with the same single skip as main at ee896951. python3 tools/zig/lean_new.py against the base: 0 new problems.
  • Not run. Metal (no Mac). GB10 / sm_121. The Qwen3.5-MoE EXL3 checkpoints: no copy here, but their prompt and decode code paths are unchanged.

Checklist

  • Every token still goes through the lane rounds. No serial path, and nothing that needs drafts off.
  • Drafted output equals "draft": false, a resumed prompt equals a fresh one, and concurrent equals solo.
  • No precision traded for speed: the head's column views read the same stored words.
  • No GPU kernel changed.
  • Prompt processing is no slower than the last release (same arithmetic).
  • Platforms: CUDA sm_86 only; Metal and GB10 not run.
  • New tests fail before the change: the Weight.columns host test, and the EXL3 dense admission in qwen35_formats_host.zig.
  • Comments and docstrings are one line.
  • No personal data, machine names, internal hosts or local paths. No AI attribution lines.

…on CUDA

Admit non-MoE qwen3_5 EXL3 checkpoints (pocketed ones stay refused), load
the dense SwiGLU MLP as EXL3 linears, allocate the grouped-expert scratch
only for MoE geometry, and let openDrafter attach DFlash2 to EXL3 targets.

The drafter reads the target's EXL3 head over its two vocabulary spans
through exl3.Weight.columns, a view of whole 128-column blocks with the
parent's strides, sharing the packing path with the affine head.

Docs: the 27B recipe's native EXL3 section, exl3.md and cuda.md no longer
say the native engine serves no EXL3 pack.
@ashhart

ashhart commented Oct 27, 2026

Copy link
Copy Markdown
Owner

Thanks @diwakergupta, and thanks for splitting #1407 up so quickly. Unfortunately this part now overlaps #1465, which makes the same change: the dense admission in cuda_geometry.zig, the dense MLP as three EXL3 linears, the grouped-expert scratch only for MoE, and DFlash2 on dense EXL3 targets. #1465 is already queued for our exactness receipts on a GB10, so we'll take the dense path from there and close this one. Sorry for the duplication, since #1407 proposed it first.

Two things here aren't in #1465, and we'd welcome them as one small PR on top of it:

  1. The drafter reading the EXL3 head over its two vocabulary spans (Weight.columns at zig/src/cuda/exl3.zig:100 with its host test, and the .x3 arm at cuda_draft_frame.zig:162). On qwen3_5 cuda: dense EXL3 checkpoints (Qwen3.8-27B), with DFlash2 drafts #1465 that head still projects all 248,320 columns. Please add drafted == "draft": false and a draft-speed comparison against qwen3_5 cuda: dense EXL3 checkpoints (Qwen3.8-27B), with DFlash2 drafts #1465. The 384-column tail plans 8 K splits where the full head has 1, so its proposals' bits change, which is fine.
  2. exl3 in the family's formats (cuda_native.zig:27) and the exl3.md/cuda.md lines.

Please keep #1465's refusal for MoE EXL3 targets. This branch removes it from both openDrafter and openMtp (cuda_native.zig:161-180), and no MoE EXL3 drafter has been checked. We've asked for #1475 and #1476 to be rebased onto #1465.

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