Skip to content

qwen38: the int8 trunk carries one scale per 64 weights (grouped fmt 1 on the GPU), perplexity-neutral - #1552

Open
kreuzzelg wants to merge 1 commit into
JustVugg:devfrom
kreuzzelg:r7c-trunk-gs
Open

kreuzzelg wants to merge 1 commit into
JustVugg:devfrom
kreuzzelg:r7c-trunk-gs

Conversation

@kreuzzelg

Copy link
Copy Markdown
Contributor

Follow-up to #1424 (merged): one commit on dev. Also takes the doc note from the #1424 review (greedy-identical is a measurement, not a guarantee; the two reference knobs).

Why

#1424 quantizes the dense trunk to int8 per row. On wikitext-2 (8 x 512, 2048 scored tokens) that costs +1.9 %
perplexity (1.880 against 1.845 BF16). Probing the rows showed the error is spread over the trunk, not one
matrix class: the expert tier is neutral, lm_head contributes nothing, keeping the routers in BF16 recovers 0.3
points, and the GPU's summation order is 0.2 of the 1.9 (1.877 on the CPU reference path). One scale per 64
weights along the input brings it to 1.852 (+0.4 %), the same eight chunk NLLs on the GPU and on the CPU
reference path, greedy text identical to BF16 over 100 tokens.

What changes

  • Backend: fmt 1 uploaded with a group size takes the grouped int4 branch of quant_matmul (scales
    [O][ceil(I/gs)], group subtotal times scale); the per-element i / gs becomes a shift when gs is a power of
    two (75 -> 69 ms resident-mm per token on the 553 trunk matrices; per-row was 63). The upload records gs for
    fmt 1 as it does for fmt 4. No new symbols, the DLL loader table is unchanged.
  • Tier: qt_dense_init(q, sc, I, O, device, gs); gs > 0 uploads through upload_g and the budget counts the
    scale table; qt_dense_matmul passes the handle's gs. The backend's cached-tensor check refuses a grouped
    tensor asked with gs 0 -- the first hardware run of this branch failed all 553 matrices exactly that way, so
    the fake backend now mirrors the check and the dense test has the case.
  • Engine: Q38_TRUNK_GS (default 64, 0 = per row) drives the quantizer, the CPU reference path
    (Q38_TRUNK_CPU_INT8), the selftest, the offered bytes and the placement sum.
  • Planner: the trunk estimate carries the scale table (+1/16).
  • Docs: knob, trunk paragraph, the perplexity table.

Measured (RTX 3070, cap 224, the 315+100-token prompt of #1424)

per row (#1424) gs 64
trunk in VRAM 3.96 GiB 4.21 GiB
resident experts / VRAM share of routes 352 / 22.3 % 312 / 20.6 %
resident-mm / lm-head, ms per token 63 / 5 69 / 7
decode tok/s 1.91 1.88
greedy text, 100 tokens, against BF16 identical identical
selftest, 553 matrices, GPU against CPU int8 of the same rows and scales 1e-7 1.5e-7
perplexity wikitext-2 1.880 (+1.9 %) 1.852 (+0.4 %)

Tests: the ten tests/test_qwen36_tier_* (dense with the grouped case), test_qwen38_tier_engine (trunk grouped
on the fake tier, cosine 0.99999), Python suites; CUDA builds of qwen36 and qwen38.

Thanks to Claude Code for the help along the way.

https://claude.ai/code/session_01X2HPETYXNwT4RmQkdMMyPS

…1 on the GPU), perplexity-neutral

Per-row int8 for the dense trunk cost +1.9 % perplexity on wikitext-2 (1.880
against 1.845 BF16; 1.877 on the CPU reference path, so 0.2 of it is the
GPU's summation order). Probing the rows showed the error spread over the
trunk, not one matrix class. One scale per 64 weights along the input brings
it to 1.852 (+0.4 %), the same eight chunk NLLs on the GPU and on the CPU
reference path, greedy text identical to BF16 over 100 tokens.

Backend: fmt 1 uploaded with a group size takes the grouped int4 branch of
quant_matmul (scales [O][ceil(I/gs)], group subtotal times scale); the
per-element division becomes a shift when gs is a power of two (75 -> 69 ms
resident-mm per token on the 553 trunk matrices; per-row was 63). The upload
records gs for fmt 1 as it does for fmt 4.
Tier: qt_dense_init(q, sc, I, O, device, gs); gs > 0 uploads through
upload_g and the budget counts the scale table; qt_dense_matmul passes the
handle's gs, because the backend's cached-tensor check refuses a grouped
tensor asked with gs 0 -- the first hardware run failed all 553 matrices
exactly that way, and the fake backend now mirrors the check so a test
catches it.
Engine: Q38_TRUNK_GS (default 64, 0 = per row) drives the quantizer, the
CPU reference path, the selftest, the offered bytes and the placement sum.
Planner: the trunk estimate carries the scale table (+1/16).
Tests: grouped case in test_qwen36_tier_dense (result row by row against
the reference loop, and the gs-0-refusal); the engine test runs the trunk
grouped on the fake tier; planner expectations follow.

Measured on the RTX 3070, cap 224, the 315+100-token prompt: trunk 4.21 GiB
(3.96 per row), 312 resident experts (352), VRAM hits 20.6 % (22.3 %), decode
1.88 tok/s (1.91); selftest 553/553 at 1.5e-7.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X2HPETYXNwT4RmQkdMMyPS
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.

1 participant