Commit or version
dev at f18a54a (also reproduced on the #1338 branch, which is dev plus the Vulkan tier).
Environment
Observed through the Vulkan tier on a Radeon Pro 580 under MoltenVK (macOS 13 x86_64), but the gate is in the engine before any backend is involved, so the CUDA tier is affected identically.
Reproduction steps
ci.yml's Qwen3.6 tiny fixture, converted at --ebits 8 (int8 experts), engine built with a tier backend, and a 400-token reference so the run outlives the uploader's first copy (a 16-token run ends before anything could upload in either mode, which hides the difference):
python3 tools/make_qwen36_tiny.py --out qwen36_tiny --ref-mode full --emit-ref qwen36_tiny/ref_full.json
python3 tools/convert_qwen36.py --model qwen36_tiny --out qwen36_tiny_c --ebits 8
COLI_DENSE_I8=0 SNAP=qwen36_tiny_c COLI_VULKAN=1 VK_EXPERT_GB=auto COLI_TIMERS=1 QT_NO_WARMSTART=1 ./qwen36 8 8 ref_long.json
Then the same fixture converted at --ebits 4, same command.
Expected behavior
With QT_NO_WARMSTART=1 the tier fills on first use for either container format, as it does for int4, and as the warmstart already does for int8 since #1331/#1334.
Actual behavior and logs
int8 container, 400 tokens, no warmstart: nothing is ever enqueued, the tier reports itself active and sits at 0 % for the life of the process.
[qtier] resident 0/64 experts | uploads 0 | miss(CPU) 6464 | q_skips 0
[qtier] VRAM hit rate: 0.0 % | LFRU swaps 0
int4 container, same command:
[qtier] resident 64/64 experts | uploads 64 | miss(CPU) 901 | q_skips 126
[qtier] VRAM hit rate: 86.1 % | LFRU swaps 0
Cause: both decode-path call sites in c/qwen36.c only offer an expert to the tier when it has a packed int4 copy:
if (e->g4) qt_note(layer, idx[kk], e->g4, e->u4, e->d4, e->gs, e->us, e->ds); // ~line 1969
if (ps->g4) qt_note(lnext, eid, ps->g4, ps->u4, ps->d4, ps->gs, ps->us, ps->ds); // ~line 2374, prefetch
On an int8 container e->g4 is NULL (there is nothing to pack), so qt_note is never called. The warmstart was taught to hand the tier e->g in that case (wg = expert_is_int4 ? e->g4 : e->g); the decode path was not. Same shape as #1331, one call site over.
Fix shape: mirror the warmstart at both call sites, expert_is_int4 ? e->g4 : e->g (the tier accepts int8 pointers since #1334 and refuses grouped-scale int8 at init). A test in the shape of test_qwen36_tier_int8_engine.c, driving the decode path on an in-memory int8 model against the fake backend and asserting fake_uploads > 0, would fail today and pass with the change. Happy to send that if the reading is agreed.
Commit or version
devat f18a54a (also reproduced on the #1338 branch, which isdevplus the Vulkan tier).Environment
Observed through the Vulkan tier on a Radeon Pro 580 under MoltenVK (macOS 13 x86_64), but the gate is in the engine before any backend is involved, so the CUDA tier is affected identically.
Reproduction steps
ci.yml's Qwen3.6 tiny fixture, converted at--ebits 8(int8 experts), engine built with a tier backend, and a 400-token reference so the run outlives the uploader's first copy (a 16-token run ends before anything could upload in either mode, which hides the difference):Then the same fixture converted at
--ebits 4, same command.Expected behavior
With
QT_NO_WARMSTART=1the tier fills on first use for either container format, as it does for int4, and as the warmstart already does for int8 since #1331/#1334.Actual behavior and logs
int8 container, 400 tokens, no warmstart: nothing is ever enqueued, the tier reports itself active and sits at 0 % for the life of the process.
int4 container, same command:
Cause: both decode-path call sites in
c/qwen36.conly offer an expert to the tier when it has a packed int4 copy:On an int8 container
e->g4is NULL (there is nothing to pack), soqt_noteis never called. The warmstart was taught to hand the tiere->gin that case (wg = expert_is_int4 ? e->g4 : e->g); the decode path was not. Same shape as #1331, one call site over.Fix shape: mirror the warmstart at both call sites,
expert_is_int4 ? e->g4 : e->g(the tier accepts int8 pointers since #1334 and refuses grouped-scale int8 at init). A test in the shape oftest_qwen36_tier_int8_engine.c, driving the decode path on an in-memory int8 model against the fake backend and assertingfake_uploads > 0, would fail today and pass with the change. Happy to send that if the reading is agreed.