Repository navigation
qwen36: offer the rest of the dense trunk to the VRAM placer (dnout, attnproj, shexp) - #1657
Conversation
…attnproj, shexp) The M10 datapoint (#1652) showed the ceiling on a CPU without AVX2: with every expert VRAM-resident, DeltaNet 169 ms and attention 55 ms of a 384 ms token stayed on the CPU, because qwen36 offered the placer only lm_head and the DeltaNet input projections. Measured on a 16-core CPU where the DeltaNet stack costs 37.5 ms per decoded token: 23.4 are the input projections, 8.3 the out_proj and norm, 3.3 the convolution, 2.4 the recurrence. The matmuls are the cost, not the recurrence, so the trunk is what goes to VRAM. Three more components, offered by name and layer with the bytes of their dense-i8 copies, placed by the existing rule (bytes saved per token per byte of VRAM, displaced experts priced by heat) and served through the generic qt_dense handles the tier already had for Qwen3.8: dnout DeltaNet out_proj, per DeltaNet layer 252 MB on the 35B attnproj q, k, v, o of an attention layer, one item 273 MB shexp the shared expert's gate, up, down, every layer 126 MB Offer order after lmhead and dnproj, each in layer order, so a partial placement is whole layers. On decode (one row) a placed matrix answers from VRAM, one GEMV; a prompt batch keeps the batched CPU matmul. Every matrix falls back on its own: the tier turns a failing handle off and matmul_d takes over, so the CPU path is the one that runs today when nothing is placed or the build has no CUDA (inline stubs). tests/test_qwen36_trunk_dense.c, on the fake CUDA backend: the offers carry the right names, layers and bytes, a component with one matrix missing its dense-i8 copy is not offered, eight matrices are placed and uploaded once each, and each GEMV from VRAM equals matmul_d on the CPU (gap <= 2e-7). CPU path: qwen36 exact on the fixture and byte-identical in chat; decode timings unchanged; make check green. Not measured here: the GPU. The engine side is the same qt_dense path Qwen3.8 runs on one card; the measurement belongs to a GPU owner (#1652).
|
@kreuzzelg if the 3070 is free: this is the single-card case the tier doc's calibration table was built on (auto placed lm_head + dnproj there, +49% warm). On this branch the placer also gets |
…GPU loses The M10 measurement of the first commit came back the wrong way round (#1652): with every expert VRAM-resident, placing the trunk made every component slower, lm_head 68.8 ms against 41.7 on the CPU, the 30 DeltaNet projections 106 against 66, attention 38.8 against 26.1, and decode fell from 3.56 to 2.68 tok/s, byte-identical output. The placer prices a dense component by the bytes it saves on the CPU's memory bus, which presumes the GPU answers a GEMV faster than the CPU does. On sm_50 behind a shared PCIe board it does not; on the Quadro RTX 4000 and the 3070 of the calibration table it did. A prediction that is right on one card and wrong on another has to be measured on the card in front of it. So before any trunk upload the engine times one DeltaNet input projection both ways on the device that would host it: ten GEMVs, best of three rounds, after a warm-up, one `[place] probe:` line with both numbers. If the GPU loses, qt_trunk_withdraw() puts every automatic offer back on the CPU, lm_head included, and returns the bytes to the expert budget before the warmstart, so the VRAM goes to experts instead. Only the automatic placement is questioned: a hand-written COLI_PLACE stands, COLI_TRUNK_PROBE=0 trusts the placer. On the M10 box this should make `auto` equal to `off`. tests/test_qwen36_tier_withdraw.c on the fake backend: five offers placed, budget charged, withdrawal answers CPU for all, lm_head refuses a later upload, budget restored to the byte, nothing uploaded; an explicit list is not automatic and is not withdrawn. make check green.
|
@JustVugg measured on the 3070 alone, dev
Cold (no heat file): The probe times the L3, not the memory bus. 0.095 ms for 16 MiB is 176 GB/s, which no DRAM on this box delivers: 0.355 ms is 47 GB/s, the real number, and Per component, heat file frozen (
Two things in there:
Byte-identity, honestly: without a frozen heat file no two warm runs agree, not even Cosmetic: the |
…FER_SHEXP=1) On the card shexp is 120 synchronous small GEMVs per token that sit between qt_issue and qt_take, where on the CPU the shared expert hides behind the expert group. Measured on the 35B with the DeltaNet layer on the card: shared 3.7-4.0 ms/token on the CPU against 6.3 on the same 3070 and 11-12.7 with layers on a slower second card; the token went 33.3 -> 31.8 ms on one card and 38.6 -> 33.6 as a two-card pipeline once shexp left the card (JustVugg#1657 saw the one-card half of this). A hand-written COLI_PLACE naming shexp is obeyed when the offer is made; the trunk_dense test asks for the offer explicitly. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…FER_SHEXP=1) On the card shexp is 120 synchronous small GEMVs per token that sit between qt_issue and qt_take, where on the CPU the shared expert hides behind the expert group. Measured on the 35B with the DeltaNet layer on the card: shared 3.7-4.0 ms/token on the CPU against 6.3 on the same 3070 and 11-12.7 with layers on a slower second card; the token went 33.3 -> 31.8 ms on one card and 38.6 -> 33.6 as a two-card pipeline once shexp left the card (JustVugg#1657 saw the one-card half of this). A hand-written COLI_PLACE naming shexp is obeyed when the offer is made; the trunk_dense test asks for the offer explicitly. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…FER_SHEXP=1) On the card shexp is 120 synchronous small GEMVs per token that sit between qt_issue and qt_take, where on the CPU the shared expert hides behind the expert group. Measured on the 35B with the DeltaNet layer on the card: shared 3.7-4.0 ms/token on the CPU against 6.3 on the same 3070 and 11-12.7 with layers on a slower second card; the token went 33.3 -> 31.8 ms on one card and 38.6 -> 33.6 as a two-card pipeline once shexp left the card (JustVugg#1657 saw the one-card half of this). A hand-written COLI_PLACE naming shexp is obeyed when the offer is made; the trunk_dense test asks for the offer explicitly. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…FER_SHEXP=1) On the card shexp is 120 synchronous small GEMVs per token that sit between qt_issue and qt_take, where on the CPU the shared expert hides behind the expert group. Measured on the 35B with the DeltaNet layer on the card: shared 3.7-4.0 ms/token on the CPU against 6.3 on the same 3070 and 11-12.7 with layers on a slower second card; the token went 33.3 -> 31.8 ms on one card and 38.6 -> 33.6 as a two-card pipeline once shexp left the card (JustVugg#1657 saw the one-card half of this). A hand-written COLI_PLACE naming shexp is obeyed when the offer is made; the trunk_dense test asks for the offer explicitly. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Follows the M10 datapoint in #1652, which showed the ceiling on a CPU without AVX2: with every expert VRAM-resident, DeltaNet 169 ms and attention 55 ms of a 384 ms token stayed on the CPU, because
qwen36offered the placer onlylm_headand the DeltaNet input projections.Where the time is
Measured on a 16-core CPU (ds, 8 threads), the DeltaNet stack per decoded token: 37.5 ms = input projections 23.4 | out_proj and norm 8.3 | convolution 3.3 | recurrence 2.4. The matmuls are the cost, not the recurrence, so the trunk is what goes to VRAM, and the tier already knew how: the placer prices any component by name, and
qt_dense_init/qt_dense_matmulare what Qwen3.8 runs its whole trunk on.qwen36.csimply never offered these.What is offered now
dnoutattnprojshexpOffer order after
lmheadanddnproj, each in layer order, so a partial placement is whole layers (#1361). A component is offered only when every one of its matrices has a dense-i8 copy. On decode a placed matrix answers from VRAM with one GEMV; a prompt batch keeps the batched CPU matmul. Every matrix falls back on its own: a failing handle is turned off by the tier andmatmul_dtakes over, so the CPU path is exactly the one that runs today when nothing is placed or the build has no CUDA.COLI_PLACEaccepts the three new names like the old ones (dnout=0,attnproj=0,shexp=0, per layer,+splits).Verified without a GPU
tests/test_qwen36_trunk_dense.con the fake CUDA backend (inmake check): the offers carry the right names, layers and bytes; a component with one matrix missing its dense-i8 copy is not offered; eight matrices placed and uploaded once each; every GEMV from VRAM equalsmatmul_don the CPU (relative gap <= 2e-7).make check1,292 tests green.What needs a GPU
The measurement. Same command as today, the placement is automatic:
The
[place]and[dense]lines at startup say what landed; the[timers]lines give the per-phase split;COLI_PLACE=offis the A/B control, and the output must stay byte-identical between the two. Asking the reporter of #1652 (4x M10) and anyone with an 8 GB card to run it before this is merged.