Skip to content

qwen3_5_moe on native CUDA with 3-bit routed experts (a 35B-A3B on a 24 GB card): where it lives, and its gate #803

Closed

Description

@CanadaApollo6

A coordination question before I build it, per CONTRIBUTING's new-family rule. It overlaps @jkuepker's plan on #491 and #785, so I'd like to agree on the split before either of us gets far.

What I want to run

Ornith 1.5 35B-A3B (MIT), a qwen3_5_moe checkpoint with Qwen3.6-35B-A3B's shapes, on one RTX 3090 (sm_86, 24 GB). It is stored as MLX affine in groups of 64: routed experts at 3 bits, everything else at 4, routers and the shared-expert gate at 8, plus its MTP layer as mtp-4bit.safetensors. That is 15 GB of weights, which leaves the 3090 room for a long window.

It serves today on a fork of the Python 0.6 line with added 3-bit expert kernels. On the 3090 it decodes 416-535 tok/s drafted. KL against bf16 is 0.229, where bf16's kernel-noise floor is about 0.04. On 19 Terminal-Bench 2 tasks, 3 attempts each, it solves 10.0 a run against 10.3 for the Q8 GGUF. #286 froze that line, so I'd like it on the native engine.

What exists on native

Proposal and questions

  1. Where the engine lives. One option is to grow Qwen3.8-27B on CUDA: the qwen27 family's engine for NVIDIA GPUs, every stream drafted #608's CUDA engine to the MoE shapes: routed experts in place of the dense MLP, and the MTP layer as the drafter in place of DFlash2. The other is a separate CUDA backend for the qwen3_5 family on feat(qwen3_5): the GPU backends' checkpoint loader, on the host (slice 11) #626's loader, as @jkuepker proposed on Zig ROCM: RDNA2/3/3.5/4 and more integration #491. I'm happy to do either, or to take a slice of @jkuepker's backend: the routed experts, the MTP drafter, or sm_86. Which do you want?

  2. Affine experts at other widths. I'd add 3-bit (and if wanted 2- to 8-bit) affine experts beside Affine4Experts in Grouped. Each would have:

    • its own kernel and the 4-bit form's accumulation order;
    • an FP64 reference per projection class, with worst-column and aggregate error;
    • decode and prompt rows checked separately.

    Is that the gate, or does a new expert width count as an arithmetic change with more to agree?

  3. The exactness reference. 0.6.6 reads only 4-bit group-64 checkpoints for this family. My proposal:

    • 4-bit: the engine is byte-equal to 0.6.6 on every logit row, using TensorFold/Qwen3.6-35B-A3B-MLX-4bit-MTP or Ornith's MLX 4-bit conversion. Drafted equals serial, concurrent equals solo, and resumed equals fresh.
    • 3-bit: the same contracts, plus the FP64 references above. Add a teacher-forced fidelity run against bf16 at 1K, 8K and 32K, recording all top-token differences and margins.

    Does that work, or would you rather the 3-bit form wait until the 4-bit engine has landed?

Hardware I can test on: an RTX 3090 locally, and rented A6000, A100 and H100 cards. I have no DGX Spark or RTX PRO 6000.

Activity

  1. ashhart commented on Oct 14, 2026

    @ashhart
    Owner

    Hello @CanadaApollo6,

    Thank you for asking where it lives before building it.

    The native binary has no qwen3_5_moe family, so pull and info refuse the checkpoint (docs/recipes/qwen3.6-moe.md:9). CUDA on main serves Nemotron only (zig/src/native/cuda.zig:13). The grouped-expert kernel on CUDA is Affine4Experts (zig/src/cuda/experts.zig:91). There is no 3-bit form beside it. The Python line is frozen at v0.6.6, which is why the fork cannot move onto main as it stands.

    Where the engine lives, and whether 3-bit waits until a 4-bit engine matches 0.6.6 byte for byte, are the maintainer's call. The 4-bit contract you wrote is the gate that helps a first PR: drafted equals serial, concurrent equals solo, resumed equals fresh, on TensorFold/Qwen3.6-35B-A3B-MLX-4bit-MTP. The RTX 3090 receipts belong with that PR.

    Thank you again.

  2. BobClawblaw commented on Oct 14, 2026

    @BobClawblaw
    Contributor

    Taking the 4-bit gate as a first PR, so nobody builds it twice: qwen3_5_moe on the Qwen3.5 CUDA engine from #836 (the one that serves the 2B and the 27B), on an RTX 4090 (sm_89, 24 GB). The routing math is the frozen engine's tensorfold/cuda/moe.py (fp32 logits over the experts and the shared gate row, top-k by logit with ties to the lower id, weights exp(l - l_top) over the k, the shared expert as a slot weighted by sigmoid), the experts through the existing grouped affine-4 kernel with its SwiGLU epilogue instantiated, and the MTP layer as the drafter after plain decode matches. Receipts on TensorFold/Qwen3.6-35B-A3B-MLX-4bit-MTP: drafted equals serial, concurrent equals solo, resumed equals fresh, one-row steps equal windows and trees bit for bit. 3-bit experts are not in it.

  3. BobClawblaw commented on Oct 14, 2026

    @BobClawblaw
    Contributor

    Progress on the 4-bit gate, on an RTX 4090 (24 GB, sm_89, Windows, CUDA 13.3), TensorFold/Qwen3.6-35B-A3B-MLX-4bit-MTP at f84b054c. The engine is the Qwen3.5 CUDA engine from #836 with routed experts in place of the dense MLP; it serves model_type: qwen3_5_moe beside the 2B and the 27B, whose bits are unchanged.

    What is in it. The routing is the frozen tensorfold/cuda/moe.py's: fp32 logits over the 256 experts and the shared expert's gate row, top-8 by logit with ties to the lower id, weights exp(l - l_top) over the eight rounded to bf16, the shared expert as a ninth slot weighted by bf16(sigmoid(bf16(gate))), the slots' fma sum rounded once to bf16. The experts run through the existing grouped affine-4 kernel with its SwiGLU epilogue instantiated (gate and up interleaved per group; the shared expert last in the same tables), decode rows to fp32 sums and prompt chunks past 16 rows through the prefill form. The 8-bit router rows are dequantized to bf16 at load. tf-cuda-test moe checks each piece against a host reference: router and combine byte-equal, picks equal (a tie, a flat row and a NaN included) with weights within a bf16 step, the pair experts within 1e-6 of fp64 on random tables.

    Exactness so far.

    • tf-qwen35-cuda over 300 ids: windows of 1 to 16 rows and tree windows equal one-row steps byte for byte (300 of 300 rows each).
    • Served with --parallel 2 --context 8192: tools/bench_concurrent.py --levels 1,2 --tokens 128 --reps 2 --alone --serial, code and chat prompts at T=1 seeded and T=0: every alone and serial check equal, 0 unequal, 0 failed.
    • Against an fp32 PyTorch reference (dequantized weights, the same routing rule), one-row steps over 40 ids: KL mean 0.015, max 0.15, the same argmax in 35 of 40. That is the model's own rounding sensitivity rather than the engine's: two such references that differ only by rounding the normed rows to bf16 before routing differ from each other by KL mean 0.016 (36 of 40), while the dense 2B on the same engine agrees with its reference at 0.0005 and the 27B at 0.0001. Layer by layer the picks and weights match the reference exactly at the early layers; by the last layers the near-tie experts flip.

    Speed, one card. Decode tools/bench_openai.py --tokens 128 --reps 2, median tok/s: 96 / 95 at T=1 (code / chat), 101 / 98 greedy; 2 streams aggregate 195 on code. Plain decode, no drafts yet. Load 8 s from the page cache; 18.6 GiB on the device, 2.8 GiB left for streams at this window.

    Next. The MTP layer as the drafter (the mtp-4bit.safetensors head: fc over [norm(embedding) | norm(hidden)], one attention-plus-experts layer, the target's head), so that drafted equals serial means something; then resumed equals fresh, and the pull request on top of #836 (it needs that engine). The 3-bit form is not in this; a check refuses any width the kernels do not read.

  4. BobClawblaw commented on Oct 14, 2026

    @BobClawblaw
    Contributor

    The 4-bit gate is up as #856: Qwen3.6-35B-A3B on the Qwen3.5 CUDA engine with its MTP layer drafting chains, stacked on #836. Receipt in the pull request: drafted equals serial and lone runs (96 of 96 checks at two and four streams), windows and trees equal one-row steps byte for byte over 300 ids, cold 2,571-token prompt 0.72 s, decode 240 / 206 tok/s greedy (code / chat) at two streams and up to 689 aggregate at four, on an RTX 4090. The 3-bit expert form stays yours; the loader refuses any width the kernels do not read.

  5. ashhart commented on Oct 15, 2026

    @ashhart
    Owner

    Thank you, @CanadaApollo6, for proposing separate prompt and decode checks against an FP64 reference for each expert projection.

    We'll use #856 on #836 for the 4 bit baseline, then add 3 bit experts as a separate width PR. Please build that increment beside the existing grouped affine experts, keeping the loader's accepted widths aligned with the kernels. We'll agree the fidelity gate before enabling the new width. Include your proposed projection references and RTX 3090 receipts for drafted versus plain, resumed versus fresh and concurrent versus solo replies, plus windows and trees against individual row steps. That qualifies the requested width on your target card.

    Posted by Ash's AI Assistant Synthena

  6. CanadaApollo6 commented on Oct 16, 2026

    @CanadaApollo6
    ContributorAuthor

    Thank you, that settles it. I'll build the 3-bit width as its own pull request on #856 (on #836), beside Affine4Experts in Grouped, with the loader accepting 3-bit experts only where a kernel reads them.

    Proposed gate, to agree before the width is turned on:

    • Each projection against FP64. Gate, up and down at 3 bits: the kernel's output against an FP64 product of the dequantized weights on the same bf16 inputs, decode rows (1 to 16) and prompt chunks checked separately, reporting worst-column and aggregate relative error. The bar: on the same inputs, the 3-bit kernel is no further from its FP64 reference than the 4-bit kernel is from its own.
    • The engine against a reference of the same checkpoint. Teacher-forced on a named fixture at 1K, 8K and 32K, against an fp32 reference with the same dequantized weights and Qwen3.6-35B-A3B on CUDA: routed experts and its MTP layer drafting chains (on #836) #856's routing rule. The bar: KL no worse than the 4-bit engine against its reference on the same fixture (Qwen3.6-35B-A3B on CUDA: routed experts and its MTP layer drafting chains (on #836) #856 reports mean 0.015 and max 0.15 over 40 ids). The 3-bit checkpoint's loss against bf16 experts is recorded beside it, with every top-token flip and its margin. That number comes from the quantization, not the engine, so it is information rather than a pass bar.
    • Contracts on an RTX 3090. Drafted equals plain, resumed equals fresh, concurrent equals solo, and windows and trees equal one-row steps byte for byte.

    #836 and #856 haven't run on Ampere yet, so my first step is #856's 4-bit receipts on the 3090, which I'll post on #856.

  7. CanadaApollo6 commented on Oct 16, 2026

    @CanadaApollo6
    ContributorAuthor

    The 3-bit width is up as #972, stacked on #856. It adds Affine3Experts beside Affine4Experts in Grouped, and the loader accepts 3-bit switch_mlp only where these kernels read it.

    On the gate, one change to what I proposed above. For the engine-level check, the reference is the same checkpoint with its 3-bit words rewritten as 4-bit words, run through #856's affine-4 path. Step, window and prompt logits are byte-equal to it over 300 ids. So the engine's distance to any fp32 reference of this checkpoint is the affine-4 engine's. Each projection matches affine-4's fp64 error on the same codes, decode and prompt separately. The RTX 3090 contracts all hold (96 of 96 drafted vs alone/serial, windows and trees equal steps, resumed equals fresh). If you'd still like the teacher-forced fp32 KL at 1K/8K/32K on top, I'll add it to the pull request.

  8. ashhart commented on Oct 17, 2026

    @ashhart
    Owner

    Thank you, @CanadaApollo6, widening the same 3-bit codes into the affine-4 representation removes a quantisation difference from the engine comparison.

    We checked #972's separate affine3 dispatch and your pinned follow-up. The 300-ID byte-equal step/window/prompt control, per-projection FP64 checks and RTX3090 lane receipts are useful, while the original checkpoint's quantisation loss stays a separate measurement.

    We will carry the width after the chosen #836/#856 base and owned-image composition, retaining unsupported-format refusals. Please keep the long-window same-checkpoint quality and image/cache cases with that final candidate. We will confirm the remaining teacher-forced reporting with the shared gate rather than treat the earlier 4-bit KL figure as a new tolerance.

    Posted by Ash's AI Assistant Synthena

  9. ashhart commented on Oct 18, 2026

    @ashhart
    Owner

    Thank you, @CanadaApollo6, for the 300-ID same-code control and the 3-bit routed experts beside a 4-bit shared expert. 1.1.0 carries #972 on #856/#836, with its separate affine3 dispatch, validated per-projection recipes and retained native image/cache paths. The final same-checkpoint width, FP64, long-window, image and draft/cache/concurrency gates must pass before closing this width proposal. Original quantisation loss stays distinct from same-code engine equality; unsupported recipes still refuse by name.

    GB10 MoE serving passed 17/17 and chat 12/12; the specific mixed-width 300-ID/oracle/reference panel is not separately recorded.

    Posted by Ash's AI Assistant Synthena

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions