inkling: shared experts to the GPU, 1.75 to 2.46 tok/s on Apple Silicon - #757
inkling: shared experts to the GPU, 1.75 to 2.46 tok/s on Apple Silicon#757rgbkrk wants to merge 2 commits into
Conversation
Routed and shared experts are independent given the layer input: both read x, both accumulate into out, and the GPU only touches out in the _end scatter-add after the wait. So the last round submits async and the CPU computes the shared experts while the GPU runs the routed ones. Frozen probe: 1.75 -> 1.96 tok/s decode, tokens identical, oracles exact. On a GPU fault _end returns 0 and the CPU redoes the round; shared_done guards double-compute.
Adds fmt=5 (raw bf16 rows, no scales) to moe_gemv — an additive branch, existing formats untouched — and moves inkling's shared experts onto it. The bf16 weights relocate into a page-aligned slab at load (the Wt handles repoint into it, so the CPU fallback reads the same bytes), and each MoE layer submits one fmt=5 block BEFORE the routed rounds: the GPU chews the shared experts while the CPU routes, fills, and packs. Every token uses every shared expert, so group j is just all S rows. Shared experts were the profile's biggest line at 8.0s per 24-token probe; on the GPU they are 0.1s. Frozen probe: 1.96 -> 2.46 tok/s decode, prefill 10.3 -> 6.4s. Audio prefill (113-token prompt) 22.4s. Greedy output is token-identical to the pre-Metal baseline through eos, and the tiny oracle stays exact (its f32 shared weights correctly skip the bf16 path and take the CPU loop).
|
The measurements are excellent and the correctness discipline is exactly right — greedy output token-identical through eos across both commits is the claim that matters, and you made it the headline rather than the throughput. Shared experts from 8.0 s to 0.1 s on the probe, audio prefill 148.8 s → 22.4 s. That is a real change in what the model is usable for on that machine. Commit 1 is a clean piece of reasoning too: routed and shared experts are independent given the layer input, so the CPU can compute one while the GPU is in flight, with One thing I want changed before merging, and it is a number rather than any of the engineering.
|
Follow-up to #750, same validation discipline: two changes that take
Inkling-Small on the same Apple Silicon box from 1.75 to 2.46 tok/s decode
and 10.3 to 6.4 s prefill, with greedy output token-identical through eos
and the tiny multimodal oracle exact at every step.
The profile after #750 put the shared experts at 8.0 s of a 24-token probe,
the single biggest line. Both commits attack that.
Commit 1: overlap shared experts with the last GPU round
Routed and shared experts are independent given the layer input: both read
x, both accumulate intoout, and the GPU only touchesoutin_end's scatter-add after the wait. So the last routed round submits withthe existing
moe_block_begin/_endpair and the CPU computes the sharedexperts while the GPU is in flight. On a GPU fault
_endreturns 0, theCPU redoes the round, and a
shared_doneflag guards double-compute.No new math, no new kernel. 1.75 -> 1.96 tok/s.
Commit 2: shared experts on the GPU as a bf16 block
moe_gemvgains fmt=5: raw bf16 rows, no scales, dequant is a 16-bitshift into the upper half of an f32. The branch is additive and every
existing format's code path is byte-untouched; glm/kimi call sites never
pass fmt=5, so this is dead code for them.
On the inkling side the shared-expert weights move into one page-aligned
slab per sparse layer at load, and the
Wthandles repoint INTO the slab,so the CPU fallback path reads the same bytes it always did. Each MoE layer
then submits one fmt=5 block before the routed rounds: every token uses
every shared expert, so group j is simply all S rows with weight
w[K+j],and the GPU chews it while the CPU routes, fills, and packs the routed
rounds.
INK_METAL_SHARED=0opts out.Shared experts drop from 8.0 s to 0.1 s on the probe. 1.96 -> 2.46 tok/s
decode, prefill 10.3 -> 6.4 s. Audio prefill (113-token spoken prompt):
22.4 s, from 148.8 s on the day-one CPU path.
Validation
(the fixture's f32 shared weights correctly skip the bf16 path and take
the CPU loop).
commits, including the audio transcript.
make checkgreen.make -C c metal-testdoes notcurrently compile on
dev(a pre-existingfp8_nblkredefinition intests/test_backend_metal.mm, unrelated to this change), so the kernelparity suite could not be run. The fmt=5 branch is exercised by the live
runs above; happy to add a fmt=5 case to the parity suite once that
harness builds again.
Benchmark report
devatee0c668warm page cache
--cap 131, pins loaded,USAGE_SAVE=0), greedy,THINK=0Remaining profile for anyone hunting further: attention (7.0 s, CPU) is now
the wall, and the MTP head is still unloaded.