Still open
Ternary weights and the Neural Engine.
Two of the models above run on the GPU, and we would rather they did
not. Getting the ternary lineage onto the Neural Engine is the question
this project keeps returning to. So far the hardware has answered no,
and the shape of that no is worth writing down.
A ternary weight stores three values in a little over two bits, which
should make a small model very cheap to move. Decode is bound by
movement, not arithmetic: one token at a time, the accelerator reads the
entire weight set and does almost nothing with each byte, so bytes per
token is the whole cost model. On paper a 2-bit 1.7B is the ideal Neural
Engine model, and a 7.5× smaller weight set should read 7.5×
faster.
It does not, and the reason is where the expansion happens. We
stacked real ternary layer pairs out of the 1.7B and measured them three
ways on an M3:
| Ternary layer pair, 1.7B |
Bits/param |
Placement |
ms per pair |
| Dense fp16 |
16.00 |
Neural Engine |
0.765 |
| 2-bit weight, used directly |
2.50 |
falls back to CPU |
-- |
| 2-bit code and scale decomposition |
2.13 |
Neural Engine |
1.013 |
A 2-bit weight handed straight to a matmul does not place on the
accelerator at all, in either operand layout. Split into codes and
scales it places whole, and the compiled artifact really does hold 2.13
bits per parameter on disk -- and it still runs a third slower than the
dense fp16 version it is supposed to beat. The arithmetic settles the
reading: the time is what moving the fp16-expanded weights would cost,
not what moving the stored bytes would. The engine expands to fp16
before the DMA, so the compression buys disk and nothing else.
Per token, the same 1.7B moves 457 MB as ternary on the GPU, 1.29 GB
palettized to 6 bits on the Neural Engine, and 3.44 GB as 2-bit on the
Neural Engine. That is a property of the weight format rather than of
the model size, so it holds all the way down the lineage: there is no
small ternary model that wins here, and the 1.7B result is not the 27B's
residency problem reappearing at a smaller scale.
Three things we are still turning over. Whether a later generation
streams sub-4-bit natively, in which case the decomposition that already
places is exactly the shape that would benefit, and that work is banked
rather than wasted. Whether palettizing to the 6-bit format the engine
does stream is worth the quality it costs -- 1.29 GB a token is
still nearly three times the GPU's traffic, so the honest guess is no,
but joules per byte are not equal on the two units and we have not
measured that end. And whether one model on one unit is the wrong frame
altogether: prefill is compute-bound and places beautifully, decode is
movement-bound and does not, so a ternary model that reads on the Neural
Engine and talks on the GPU is a split we have not built yet.
Meanwhile the GPU path earns its place for a reason that has nothing to
do with speed. The 1.7B needs no Neural Engine at all, which makes it
the one model that runs on the small and older iPhones where the Core ML
decode graph cannot be lowered -- the devices the rest of this page
leaves out.