Skip to content

qwen36: --down-bits, the mixed expert layout (int4 gate/up, int8 down) -- the knob behind the #1370 numbers - #1559

Open
kreuzzelg wants to merge 3 commits into
JustVugg:devfrom
kreuzzelg:qwen36-down-bits
Open

kreuzzelg wants to merge 3 commits into
JustVugg:devfrom
kreuzzelg:qwen36-down-bits

Conversation

@kreuzzelg

Copy link
Copy Markdown
Contributor

The experiment #1370 asked for: down at more bits with everything else unchanged. This is the knob that
produced the numbers posted there, kept as a measurement tool and a middle step, not as the answer to the gap.

Numbers (Qwen3.6-35B, wikitext-2, 16 x 512 tokens, same tokens and harness as #1370)

experts bits per weight perplexity
Ollama Q4_K_M 5.2 7.147
int4 gs64 (default) 4.5 7.325
down int8 per row, gate/up int4 gs64 (this PR) 5.7 7.281
all experts int8 8.0 7.153

down alone recovers a quarter of the gap to int8 (paired -0.0060 nats vs gs64, SE 0.0037); three quarters sit in
gate/up, and at equal bits Q4_K_M's asymmetric quantizer is ahead. So the format decision is the min-and-scale
quantizer on gate/up (your second step); this PR only makes the measurement repeatable and gives boxes with RAM
to spare a middle step.

What changes

  • Converter: --down-bits N (5..8, with --ebits <= 4) and --down-gs write one uint8 slab per expert,
    [gate int4 packed | up int4 packed | down int8], 2interhidden bytes -- neither the int4 (1.5x) nor the
    int8 (3x) size, so the engine tells the layouts apart from the bytes as it does today. qs stays
    [gate | up | down]; meta gains expert_down_bits / expert_down_gs. The --selftest covers the mixed
    round trip byte for byte.
  • Engine (CPU path): the slab is recognised by size; gate|up unpack into the slot's int8 block as the int4
    container does, down's rows are copied behind them; matmul_qd dispatches down_proj on its own scale layout
    (per row or expert_down_gs), gate/up stay on matmul_qe. The planner prices the expert bytes from the
    tensor sizes as before.
  • Not in this PR: the CUDA VRAM tier takes one format per expert; a mixed container refuses COLI_CUDA=1
    with a line and runs CPU-only. Teaching the tier three formats per expert is a follow-up if the layout is
    wanted beyond measurements.
  • CI: in the Qwen3.6 tiny job, the inter-64 fixture converted with --down-bits 8; the engine must detect
    the layout, give identical ids at cap 1/2/8 (cap 1 recycles the single slot after every expert, where a wrong
    slab offset would show) and refuse the tier with the line. Tiny results: int8 16/16 tokens, int4 gs64 12/16,
    mixed 16/16; cap 1, cap 2, PILOT=1 WIDE=2 and ASan/UBSan clean.
  • Docs: a paragraph under "Which container?" with the numbers and the honest verdict.

For the measurement itself the 35B container was assembled from the existing gs64 (gate/up) and int8 (down)
containers -- both from the same checkpoint with the same deterministic quantizers, so byte-identical to what
--down-bits 8 writes from the source (the selftest is the proof on tiny tensors).

Thanks to Claude Code for the help along the way.

https://claude.ai/code/session_01X2HPETYXNwT4RmQkdMMyPS

kreuzzelg and others added 3 commits September 18, 2026 22:22
…t8 down in one slab)

The JustVugg#1370 experiment: Q4_K_M keeps down/output in Q6_K, our gs64 int4
loses 2.5 % perplexity against it, and all-int8 experts close the gap
(7.153 against 7.147). The first number JustVugg asked for is down alone
at more bits with everything else unchanged.

--down-bits N (5..8, with --ebits <= 4) quantizes down_proj at N bits
(--down-gs groups along its input, 0 = per row) and writes one uint8 slab
per expert: [gate int4 packed | up int4 packed | down int8], 2*inter*hidden
bytes -- neither the int4 (1.5x) nor the int8 (3x) size, so the engine can
tell the layouts apart from the bytes, as it does today. qs stays
[gate | up | down]. meta gains expert_down_bits / expert_down_gs. The
selftest covers the mixed round trip byte for byte.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X2HPETYXNwT4RmQkdMMyPS
… CPU path

The slab is told apart by size (2*inter*hidden bytes); gate|up unpack into
the slot's int8 block as the int4 container does, down's int8 rows are
copied behind them, the scales come as [gate | up | down] with down_proj's
own group size (expert_down_gs, 0 = per row) from qwen36_meta.json.
matmul_qd dispatches down_proj on its own layout; gate/up stay on
matmul_qe. The VRAM tier takes one format per expert, so COLI_CUDA=1 is
refused with a line on a mixed container until the tier learns it.

Tiny fixture (inter 64, gs 64): int8 16/16 tokens, int4 gs64 12/16, mixed
16/16; cap 1, cap 2, PILOT=1 WIDE=2 and ASan/UBSan clean.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X2HPETYXNwT4RmQkdMMyPS
…JustVugg#1370 numbers in "Which container?"

Tiny job: the inter-64 fixture converted with --down-bits 8 must be detected
by size, give identical ids at cap 1/2/8 (cap 1 recycles the one slot after
every expert, where a wrong slab offset shows), and refuse COLI_CUDA=1 with
the line. Docs: the knob, the four perplexity figures, and the verdict --
down alone is a quarter of the gap, the tier does not take the layout yet.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X2HPETYXNwT4RmQkdMMyPS
@JustVugg

Copy link
Copy Markdown
Owner

Retried this in today's merge pass and it no longer applies: dev took #1612 (CACHE_ROUTE in qwen36) and #1606 (K1b multi-row kernels) this morning, and the branch now conflicts with dev on c/qwen36.c. A rebase onto current dev is all that is needed; the mixed layout itself, the size-based detection and the CI step are fine and I will merge once it is clean and green.

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants