QLNI's picture
Tidy result notes and comments
822177f verified
|
Raw History Blame Contribute Delete
3.76 kB
# CPU speed, RAM and quantisation loss: arm A (GGUF, llama.cpp) vs arm C (our kernel), laptop, 2026-10-14
Laptop: Intel Xeon W-11955M (8 cores / 16 threads, 24 MB L3, measured DRAM read bandwidth 36-40 GB/s), 8 threads, Windows.
llama.cpp b11515 (official Windows CPU build). Arm C kernel: kernel/cgemma.c v3 (gcc -O3 -mavx2 -mfma -mf16c, NT=8, PIN=1),
ternary GEMV ported from the SHADOW-50M kernel, 1-bit table readout straight off the table bits. Generation = greedy, one
token at a time, 128-token context; "prefill" in our kernel is sequential (no batching), llama.cpp's is batched.
## Speed and RAM (same 8 threads)
| model | format / runtime | file | peak RAM while generating | generate tok/s | prefill tok/s |
|---|---|---|---|---|---|
| A, learned table, float | GGUF f16, llama.cpp | 543 MB | 723 MB | 62 (llama-bench 61.96 +/- 1.38) | 1,105 |
| A | GGUF Q8_0, llama.cpp | 292 MB | 471 MB | 116 (115.9 +/- 0.6) | 1,192 |
| A | GGUF Q4_K_M, llama.cpp | 253 MB | 446 MB | 130 (130.1 +/- 1.3) | 1,313 |
| **C, frozen table, ternary** | **our kernel v3** | **46 MB** | **62-67 MB** | **615-740** (256-token runs: 668 / 684 / 614) | ~1,450 |
| C | our kernel v2 (two-pass 2-bit readout) | 46 MB | 100 MB | 300-327 | ~1,265 |
| C | our kernel v1 (scalar attention, LUT readout) | 46 MB | 61 MB | 117 | - |
Peak RAM for A measured with psutil polling of llama-completion; for C from the process peak working set.
Earlier runs of v2 on this laptop varied by +/-10% with background load; v3 figures above are from 256-token generations.
## Does the GGUF quantisation of A lose quality? (llama-perplexity, export/val_300k.txt, n_ctx 512, 586 chunks)
| arm A format | perplexity | vs f16 |
|---|---|---|
| f16 | 23.857 +/- 0.187 | - |
| Q8_0 | 23.864 +/- 0.187 | +0.03% (no measurable loss) |
| Q4_K_M | 23.966 +/- 0.188 | +0.46% (small, inside the error bar) |
So Q8_0 is a free size/speed win for A (the table stays 16-bit in every GGUF type: ~250 MB of the Q4_K_M file is the
embedding, which is why A never gets below 446 MB of RAM). Comparing at Q8_0 is therefore fair to A: C is still 5.3-6.4x
faster and uses 7x less RAM. Note: this f16 perplexity (23.86) differs slightly from the earlier one-off 23.49 run; the
three numbers above come from one identical setting and are the ones to quote.
## Kernel v3 correctness (golden check vs PyTorch fp32, 64 real validation tokens)
top-1 agreement 100%, top-5 overlap 98.4%, next-token loss 4.4946 vs 4.4826. The 0.012 drift comes from the f16 KV cache
(llama.cpp's default for A as well); with the f32 KV cache of v2 the loss was 4.4858. The readout is exact for the argmax:
the int8 first pass bounds every row, the exact second pass runs only on rows that can still win (0.9 rows per token).
## Where the time goes (v3, generate, ms/token at ~160-token context)
| section | ms | note |
|---|---|---|
| ternary GEMVs (attention + FFN) | 0.52 | 25 MB of weights per token, DRAM-bound (0.39 during prefill when more stays in L3) |
| table readout | 0.50-0.65 | 17 MB per token, DRAM-bound (0.30 with the table hot in L3) |
| attention | 0.12 | positions split across the 8 threads, flash-style merge |
| norms / int8 quantisation / RoPE | 0.12 | serial on the main thread |
| embed + output adapters | 0.03 | |
| barriers | 0.06 | 91 per token at 0.7 us |
Every token streams ~44 MB (body 25 + table 17 + seal/logits 2) through a 24 MB L3, so on this laptop the floor is
44 MB / 38 GB/s = 1.15 ms = ~860 tok/s; v3 is at 1.45-1.6 ms. Steering the cache (non-temporal streaming of the table or
of the body) was measured and is slower. Faster memory or an L3 that holds the 46 MB (server CPUs) lifts the ceiling
directly; the body alone already runs at 1,250 tok/s here when the table pass is skipped.