Skip to content

Instantly share code, notes, and snippets.

@apollo-mg
Last active July 14, 2026 12:08
Show Gist options
  • Select an option

  • Save apollo-mg/9218d50a209d70a85f033bf182657818 to your computer and use it in GitHub Desktop.

Select an option

Save apollo-mg/9218d50a209d70a85f033bf182657818 to your computer and use it in GitHub Desktop.
**The P100 has been doing silently noisy math in llama.cpp for years. Three lines fix it for free.**

The P100 has been doing silently noisy math in llama.cpp for years. Three lines fix it for free.


TL;DR

llama.cpp's CUDA backend has a FAST_FP16_AVAILABLE flag: "this GPU is fast at fp16, so do quality-sensitive math in fp16." The GTX 10-series (sm_61) was exempted from it long ago. The Tesla P100 (sm_60) never was — because GP100 is the one Pascal chip with fast fp16 hardware. Hardware can, therefore software did. Nobody measured what it cost.

Measured against an fp32-arithmetic truth base (Qwen3.6-27B Q6_K, wikitext-2, 2048 ctx, 32 chunks, KLD over full logit distributions):

median KLD vs truth top-token agreement
stock llama.cpp on P100 0.002298 96.53%
3-line patch 0.000001 99.89%

That's ~2300× tighter, and roughly 1 in 29 next-token predictions were changing outright. The speed cost, benchmarked at pp8192/tg32 @ depth 8192 across three model classes (27B hybrid, 4B dense, 36B MoE): zero. Prefill inside noise on all three; decode +1.4% faster patched.

The patch is merged in llama-cpp-turboquant PR #212 and open in buun-llama-cpp PR #80. GGML upstream issue: ggml-org/llama.cpp#25593.

The bug

ggml/src/ggml-cuda/common.cuh has three gates that all say the same thing:

#if defined(FP16_AVAILABLE) && __CUDA_ARCH__ != 610          // device macro
ggml_cuda_highest_compiled_arch(cc) != 610                    // fast_fp16_available()
cc >= GGML_CUDA_CC_PASCAL && cc != 610                        // fast_fp16_hardware_available()

sm_61 (GTX 1070/1080, P40) is carved out of all three — someone measured or knew that chip's fp16 story and made the right call. sm_60 (P100) sailed through all three gates because GP100 has genuine 2:1 fp16 hardware (18.7 TF). The fix extends the existing sm_61 idiom: && __CUDA_ARCH__ != 600 / && cc != 600. Three lines. That's the whole patch.

What those gates control on sm_60 (verified in dispatch source, not headers):

  1. Flash-attention tile and vec kernels — all of attention math ran in half2 vector arithmetic. (Yes, llama.cpp has real working FA on Pascal — a fact the Python world, where FA2 requires sm_80, mostly doesn't know.)
  2. cuBLAS compute type — and this one's subtle: on sm_60, MMQ (the int8 quantized matmul path) is hard-disabled (should_use_mmq returns false below the DP4A gate — GP100 missed the DP4A instruction by one minor arch version). So ALL quantized-weight prefill runs dequantize + cuBLAS GEMM, and this flag chose fp16 GEMM. Your entire weight path, in half precision, silently.
  3. mmvf — f16-weight matrix-vector arithmetic type.

How it was found

Not by reading code — by measuring GPUs against each other. While validating a KV-cache codec (buun's TCQ/VBR work) across a P100 rig and a 3090, the same model, same weights, same test corpus kept producing systematically different KLD floors. Chasing that difference through a controlled decomposition panel (KV storage precision × attention arithmetic × weight-path arithmetic, each isolated) pointed at platform arithmetic, not the codec. The codec was innocent. The platform wasn't.

Key methodology point for anyone reproducing this class of bug: within-build comparisons cannot see it. If your truth base and your test run share the same fp16 arithmetic, the error is common-mode and cancels. Every rung of a KV-quant ladder measured on stock sm_60 looks clean relative to its own f16 baseline while the whole ladder sits ~0.005 median KLD away from the fp32 answer (with a 95.0% same-top floor — the weight-path error is ~2× the attention-path error). You must generate the reference logits with fp32 arithmetic to see it.

Scope discipline

  • sm_60: measured, fixed. sm_61: was already exempt (nothing changes).
  • sm_62 (Jetson TX2): same gate, same silicon family, unmeasured — deliberately NOT patched. Measure before you carve.
  • Everything Volta+: unaffected by this patch. Independently verified by the turboquant maintainer pre-merge: these three gates are the only 600-vs-610 distinction in the CUDA tree, and a Blackwell build produced bit-identical PPL. (Whether other arches have their own distance-to-fp32-truth is a separate, open research question. Different flag, different kernels, different measurement. Don't extrapolate this bug to your 3090.)

Why this matters in 2026

P100s are flooding the used market at ~$80 shipped while the DRAM crisis prices everything else into orbit. That's 16GB of HBM2 at 732 GB/s per card. The market had it half-right: sm_61 P40s (clean math all along) soared to ~$300 while the P100 floundered — the price gap was partly pricing in a software bug. Post-patch, the P100 does fp32-clean inference at fp32 speed that was always there; the "fast" fp16 path it loses was buying nothing (real prefill is bound by cuBLAS GEMM and memory bandwidth, not the fp16 vector path).

Reproduction recipe

  1. Build stock llama.cpp, -DCMAKE_CUDA_ARCHITECTURES=60.
  2. Generate truth base: llama-perplexity --kl-divergence-base out.kld with fp32 KV, FA off, on a patched build (fp32 arithmetic) — this is the step everyone skips.
  3. Score stock vs patched runs against that base with --kl-divergence, 2048 ctx, 32 chunks.
  4. Speed: llama-bench pp8192/tg32 @ d8192, patched vs unpatched, same host, nothing else on the GPUs.

Full panel design, predictions-logged-before-runs, and receipts: [https://github.com/apollo-mg/Project-Apollo/blob/1a41c11439e6661c84c83bec333ccdf964663da6/data/Apollo%20Docs/Pascal_FAST_FP16_Carveout_Results.md] [https://github.com/apollo-mg/Project-Apollo/tree/main/data/carveout_panel].

Credits

  • buun (TCQ/VBR codec) — the cross-GPU codec validation that surfaced the discrepancy, and the first fork PR review.
  • TheTom (llama-cpp-turboquant) — fast merge with independent cross-arch verification.
  • Found and isolated by running Fable 5 through my custom P/ReAct/R agent loop. It wrote the scripts, the hardware provided the receipts.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment