114 published notes

GPU inference field notes

What worked, what did not, and what the measurements actually said while building ZINC. The newest entries are shown in full; the rest are grouped into a compact archive.

Latest

Q8 KV cache: a quarter of the bytes, seventeen percent slower decode on Metal

In July this blog argued a Q8 KV cache is close to free on an RDNA4 card. On August 17 we turned it off by default for Muse Glimmer on Apple Silicon, because it was costing 15 to 22 percent of decode. Both results are correct. Q8_0 stores a KV element in 1.06 bytes instead of 4, so the quantized path reads 3.8 times fewer bytes and still loses, which means the flash-attention kernel at ordinary context lengths is not bandwidth-bound at all. KV quantization is a capacity decision that a decode kernel charges you for in time.

zincmetalapple-siliconm4-max

The fastest Q5_K lm-head kernel was already in the binary, gated off by one constant

A commit on August 17 made Muse Glimmer decode about six percent faster on an M4 Max without adding a kernel. The faster Q5_K matvec was already compiled, already loaded, and never dispatched, because its threadgroup input cache was declared for K<=4096 and Muse's lm-head is K=6656. The lm-head is 925 MB of the roughly 15 GiB the decoder reads per token, and it was running on a translated shader that reads weights a byte at a time. The interesting part is not the fix, it is why the kernel was marked intentionally unused: a correct measurement about dispatch fusion got generalized into a wrong decision about a kernel that was never about dispatch count.

zincmetalapple-siliconm4-max

Qwen 3.8 27B Shipped Yesterday. Here's What ZINC's Day-One Support Actually Took.

Qwen 3.8 27B landed on Hugging Face on August 14 and ran through ZINC the next day. Two weeks later, the tuned ROCm path beats llama.cpp ROCm across all four published workloads. This is what architecture reuse bought us, what the new fused RMS-norm and Q8 path changed, and why Metal decode still needs work.

zincqwenqwen3-8model-bringup

The KV cache lever the swarm keeps pulling stops being free below 8 bits

Four posts in this series reached for the same lever: quantize the KV cache to q8, halve every byte, double the headroom. That lever is real, and at 8 bits it is close to free. The trouble starts when the swarm wants more and reaches below 8 bits, because the KV cache is not one number to round off. Keys carry large per-channel outliers and values do not, so a single uniform 4-bit or 2-bit knob falls off a quality cliff that the papers only climb back up with per-channel key quantization and explicit outlier isolation. KIVI keeps 2-bit quality only by quantizing keys per-channel and values per-token; KVQuant holds 3-bit to under 0.1 perplexity loss only with pre-RoPE per-channel keys and dense-and-sparse outlier handling. On a single 16 GB RX 9070 XT that means the free win ends at 8 bits, and everything below it is an engineering project with a per-model accuracy budget, not a config flag.

zincamdrdna4rx-9070-xt

Eight agents share one system prompt, so store the prefix KV once

Yesterday's post found the swarm had about 2 GiB of VRAM headroom on a 16 GB RX 9070 XT, and treated that as scarce. It is scarcer than it needs to be, because a good chunk of it is spent storing the same bytes eight times. Every agent in a coding swarm carries an identical prefix, the system prompt plus the tool schemas, and the naive engine caches a separate copy of that prefix's KV for each of the eight decode slots. A 2,000-token shared prefix is 0.30 GiB per copy at fp16, so eight copies cost 2.44 GiB while one shared copy costs 0.30 GiB. Storing it once reclaims 2.14 GiB, which is roughly the entire headroom the swarm was fighting for. This is the prefix-sharing case an earlier post said a single-user engine could mostly skip, and a swarm cannot.

zincamdrdna4rx-9070-xt

Free RDNA4 preemption ends where paused KV caches fill the 16 GB card

Yesterday's post argued that preempting a decode slot on an RX 9070 XT is nearly free because the paused turn's KV cache never leaves VRAM. That is true, and it hides a ceiling. A paused turn keeps every byte of its KV resident, so under sustained oversubscription the paused turns stack up in the same 16 GB the running turns are using. The arithmetic is blunt: eight running turns at a 6k-token average already sit at 13.5 GiB, which leaves room for about two average paused turns before the card is full and you are forced back into the swap or recompute you were avoiding. And the turns you actually preempt are the long ones, so a single paused 16k-token refactor can overflow the budget on its own.

zincamdrdna4rx-9070-xt

A long background turn stalls the foreground agent until RDNA4 preempts the decode slot

On one RX 9070 XT with eight decode slots, the foreground agent usually gets a slot in under a second. The problem is the tail. When it lands behind a long background turn, a slow refactor holding its slot for twenty-plus seconds, it waits the whole thing out: p99 is 6.2 seconds and the worst case is half a minute. That is priority inversion, the same failure that reset Mars Pathfinder, and the fix is not a smarter queue. It is preempting the decode slot at a token boundary, which on RDNA4 costs almost nothing because the paused turn's KV cache never leaves the 16 GB of VRAM.

zincamdrdna4rx-9070-xt

A deeper queue cannot fix an oversubscribed RDNA4 agent swarm

Once a single RX 9070 XT is past its twelve-agent knee, the card is already delivering every token it can, about 0.88 turns per second. Every agent you add after that buys zero throughput and only lengthens the wait before a turn starts. A discrete-event simulation shows the unbounded queue's p99 wait climbing from 9 to 27 seconds between twelve and twenty-four agents while goodput never moves, which means the fix is not a bigger buffer. It is admission control, and the only real question is whether you shed by count or by delay.

zincamdrdna4rx-9070-xt

Archive

106 more posts
August 2026 7 posts
  1. Turn-length variance, not load, sets an RDNA4 agent swarm's tail latency
  2. Little's Law caps a responsive RDNA4 agent swarm near twelve
  3. The foreground agent decodes seven times faster than you read
  4. First-come scheduling starves an RDNA4 swarm; fair queuing shares the card
  5. The datacenter disaggregates prefill and decode; a single RDNA4 card fuses them
  6. Chunked prefill keeps an RDNA4 swarm decoding while one agent's prompt loads
  7. A reserve-for-max KV cache fits four of eight agents on a 16 GB RDNA4 card
July 2026 25 posts
  1. A Q8 KV cache pushes an RDNA4 swarm's crossover from 5k to 10k tokens
  2. Static batching drains an RDNA4 swarm to a third of its throughput
  3. A decode weight stream has no reuse, so RDNA4's Infinity Cache runs cold
  4. Batching an agent swarm shares the weights but not the KV cache on RDNA4
  5. Batching eight agents saves more decode time than energy on RDNA4
  6. Batching eight agents leaves each one two speculative tokens on RDNA4
  7. One RDNA4 verify pass has slack for about two dozen speculative tokens
  8. A coding agent's speculative draft is the context it already read back
  9. A local coding agent reads back eighteen tokens for every one it writes
  10. Constrained decoding makes a local agent scan the vocabulary twice per token
  11. The system prompt a local agent swarm caches eight times over
  12. Swapping an idle agent's KV cache beats recomputing it by 177x
  13. Admitting a ninth agent stalls the other eight for 8.3 seconds
  14. Eight concurrent agents pull 5.6 times more tokens from one RX 9070 XT
  15. Decode kernel fusion on RDNA4 deletes the barrier, not the launch
  16. Command buffer reuse is RDNA4's version of the CUDA graph decode win
  17. Weight streaming is under half of a ZINC decode token on the RX 9070 XT
  18. A Qwen3.5-9B chat turn spends 96 percent of its wall clock in decode
  19. Attention's two matmuls want different number formats on RDNA4
  20. int8 WMMA doubles RDNA4's matrix rate, and Q4_K's block scales take half of it back
  21. Softmax steals RDNA4's WMMA issue slots, and the exp is not the thief
  22. ZINC's down projection reads back a SwiGLU tensor 2.7x wider than the hidden state
  23. VGPR pressure caps the fused RDNA4 prefill GEMM at nine waves
  24. The dequant scratch round-trip is ZINC's last RDNA4 prefill tax
  25. On AMD RDNA, ZINC is now faster than llama.cpp across the measured local model suite
June 2026 13 posts
  1. How to Write GLM 5.2 Loops: Agentic, Self-Improving Coding Loops
  2. The RX 9070 XT prefill cliff: a 25× Mesa regression hiding in plain sight
  3. ZINC_RT is how local inference becomes shared infrastructure
  4. A day later: batched prefill, CUDA graphs, and the catalog re-measured on RTX 5090
  5. Four bottlenecks, one CUDA backend: MoE, Gemma, and tensor cores on RTX 5090 and 4090
  6. How we made CUDA LLM decode up to 5× faster: batched MoE experts and kernel fusion in ZINC
  7. How ZINC got a CUDA backend and beat llama.cpp decode on NVIDIA
  8. Bringing ZINC to NVIDIA: a CUDA backend, because WSL2 only speaks CUDA
  9. Why Qwen 35B cannot use ZINC's 208 tok/s batched prefill path yet
  10. How ZINC's RDNA4 batched prefill went from 42 to 208 tok/s
  11. The first ZINC_RT model slices that actually count
  12. Gemma is the model family that keeps ZINC honest
  13. ZINC performance: where it is fast, where it is not, and how to read the numbers
May 2026 33 posts
  1. Timeline semaphores let zinc queue decode steps without a fence wait per token
  2. The imatrix pass that decides which Qwen3 weights survive IQ3_M
  3. How llama.cpp keeps Qwen3's 151k CPU sampler chain off the decode budget
  4. Byte-level BPE splits a character mid-token, and streaming has to hold the bytes
  5. Paged KV cache is the serving fix a single-user local engine can mostly skip
  6. Speculative decoding on Qwen3-A3B loses even at 100% draft acceptance
  7. Static YaRN buys Qwen3 128k context by taxing every short prompt
  8. The outlier channels that keep Qwen3 activations off 4-bit
  9. Qwen3-30B-A3B decodes like a 3B and fills the card like a 30B
  10. Tensor parallelism on two R9700s pays for the Infinity Fabric link RDNA4 left out
  11. The KV split that fills RDNA4's idle compute units on long-context decode
  12. FP8 KV cache is the next decode bandwidth cut RDNA4 already has the wmma for
  13. ROCm vs Vulkan vs ZINC_RT: inside the decision to write our own GPU runtime for local LLM inference on AMD RDNA4
  14. Intel Arc Pro B70 deep dive: 32 GB Xe2 inference at workstation MSRP, and ZINC's T-Intel plan
  15. Qwen3's thinking budget is a cloud feature local engines still have to fake
  16. EXL3's 1.6 bpw trellis quantization has no path to RDNA4 yet
  17. What Qwen3's 151k LMHead costs on RDNA4 decode
  18. Temperature zero is not deterministic for local Qwen3 on RDNA4 yet
  19. How chunk size decides first-token latency on long local Qwen3 prompts
  20. The runtime below Vulkan that local LLMs needed
  21. The wave32 commit that closes RDNA4's long-context flash attention gap
  22. PCIe ASPM is the local Qwen3 decode tax RDNA4 pays on every reboot
  23. How AMD Qwen decode passed llama.cpp in six weeks on the Radeon AI PRO R9700
  24. The FP4 wave breaks at RDNA4 and FP8 WMMA already does what local Qwen3 needs
  25. Why MTP heads are the speculative decode draft Qwen3 A3B always needed on RDNA4
  26. Why Qwen3-Next's gated DeltaNet breaks the prefix cache local engines just built
  27. Why prefix KV reuse is the cheapest 5x left on local Qwen3 chat
  28. Why DRY earns the slot before min-p on Qwen3 long-context decode
  29. Why min-p has aged into the right default sampler for local Qwen3 decode
  30. Why GQA is not the last KV-cache shape for local 32 GB long-context decode
  31. Why JSON-constrained decode no longer scans a 151k-token bitmask
  32. Attention sinks: the four KV tokens local long-context inference cannot evict
  33. Why RDNA4 long-prefill plateaus on attention, not GEMM
April 2026 22 posts
  1. Why RDNA4's matrix cores sit out the local LLM decode loop
  2. Why speculative decoding does not net out on Qwen 35B-A3B local inference
  3. The 16k crossover where KV reads outweigh active weights on RDNA4 decode
  4. The gate that keeps Qwen 35B prefill at half of llama.cpp on RDNA4
  5. Why FP16 KV cache is the wrong default for 128k context on a 32 GB RDNA4 card
  6. Why one vkQueueSubmit per prompt is the next quiet RDNA4 prefill unlock
  7. The single push constant blocking Gemma 4 prefill on RDNA4
  8. Vulkan specialization constants are how RDNA4 prefill ships five DMMV pipelines without a decode regression
  9. Why RDNA4 prefill wants a 32-column DMMV before it wants a GEMM
  10. MTLBinaryArchive is the missing half of the Metal LLM cold-start fix
  11. 38x faster Metal prefill in ZINC and how we discovered two valid answers
  12. Why cold-start Metal LLMs pay a pipeline-state tax nobody budgets for
  13. Why cold-CLI benchmarks lie about Apple Silicon LLM prefill
  14. Why Q8_1 activations are the next RDNA4 prefill unlock
  15. Why RDNA4 prefill for Qwen3.5-35B is stuck at 25 tok/s
  16. Qwen3.6-35B-A3B GGUF on AMD and Metal
  17. How we enabled OpenAI GPT-OSS 20B in ZINC on Metal
  18. Qwen 3.6 architecture and local inference in ZINC
  19. How Mixture of Experts models work in ZINC
  20. Every design decision behind ZINC, and why we'd make them again
  21. Why Zig is the best language for GPU programming across Metal and Vulkan
  22. Bringing ZINC to Apple Silicon: from Vulkan to Metal in one engine
March 2026 6 posts
  1. How we made AMD LLM inference 4x faster on a single GPU
  2. The broken Vulkan shaders keeping our AMD RDNA4 inference engine stuck at 4 tok/s
  3. The Karpathy loop, autoresearch, and the self-improving AI loop behind ZINC
  4. What broke first in local LLM inference on AMD RDNA4
  5. Building a local AI rig: from trading workstation to home AI server
  6. Why we're building ZINC for local LLM inference on AMD GPUs