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.