Same Request, Different Answer: Quantization Amplifies Cache-Induced Divergence in LLM Serving
Organizations: Independent Researcher, Milpitas, CA 95035 USA
Abstract
Major open-source serving stacks reuse the key-value tensors of a shared prefix by default. We measure what this prefix reuse costs in reproducibility. Holding model, decoding, seed and request order fixed at batch size one, we ran an eighty-episode multi-turn agentic tool-use workload with prefix reuse on and off across two engines and four weight formats. Without prefix reuse, repeated runs reproduced every token and log-probability in all ten configurations. With it, cache-hit and recompute paths diverged on both engines. On llama.cpp with GGUF weights this path divergence rises with quantization: for Qwen2.5-7B over ten independent server histories, it reached 37.4 percent of episodes at 16-bit and 82.5 percent at four-bit. Near-tie decisions became only about 1.2 times as common, while the cache-path change in top-two margins grew 4.9-fold from 16-bit to three-bit. Serving four-bit weights through 16-bit kernels left divergence not detectably different from 16-bit, placing the amplification mainly in the quantized execution path. At Q4_K_M, run-to-run divergence came mainly from llama.cpp's server-level prompt cache: under the canonical episode order it moved divergence by 37.5 points, and at its default, divergence spanned 20.0 to 28.7 percent under five random orders. With cache state restored, each path reproduced exactly while the two still differed. In the tested stacks, cached serving is irreproducible because the cache state is absent from the request and not reset by default. A single-turn bridge shows path divergence reaching task outcomes with no detectable net change in accuracy. We release the harness, logs, and analysis pipeline.
Figures & tables
| Term | Meaning in this paper |
|---|---|
| Prefix reuse | Reuse of cached key and value tensors for a shared prompt prefix; the manipulated variable |
| Server-level prompt cache | llama.cpp’s host-memory store of conversation states; disabled in the replicated sweep and its other controls, at its default in the production-default runs |
| Path divergence | Same request, cache-hit path against recompute path |
| Run-to-run divergence | Same workload rerun with prefix reuse enabled, differing because of carried-over server state |
| Setting | Value | Reason |
|---|---|---|
| llama.cpp build | release b10434, commit 7e4c0a9, CUDA 12.6, sm_89 | cache behavior is version-specific |
| vLLM build | 0.11.0, PyTorch 2.8.0+cu128, transformers 4.57 | same |
| Slots and batching | one slot, batch size 1, serial requests | removes batch-composition effects |
| Context length | 16,384 tokens | holds every episode but one: in one random order of the production-default reruns, one episode exceeded it at the same request in both passes (Section IV-C ) |
| Key-value cache precision | 16-bit floating point in every arm | keeps cache precision independent of the weight format |
| Server-level prompt cache | default (8192 MiB) in the original grid, the GSM8K bridge and the production-default reruns; both settings in the flag, order and latency experiments; disabled in the replicated sweep and its other controls | the variable isolated in Section IV-C |
| Server-level | passes | cache-off pass |
|---|---|---|
| prompt cache | adjacent (%) | in between (%) |
| default | 38.8 [22.5, 57.5] | 77.5 [67.2, 86.2] |
| disabled | 1.2 [0.0, 6.7] | 1.2 [0.0, 6.7] |
| path reproduces | paths | same in all sessions | ||||
|---|---|---|---|---|---|---|
| Weights | items | recompute | cache hit | differ | recompute | cache hit |
| F16 | 100 | 100/100/100 | 100/100/100 | 2/2/2 | 100/100 | 100/100 |
| Q8_0 | 100 | 100/100/100 | 100/100/100 | 43/43/43 | 100/100 | 100/100 |
| Q4_K_M | 100 | 100/100/100 | 100/100/100 | 43/43/43 | 100/100 | 100/100 |
| Q3_K_M | 100 | 100/100/100 | 100/100/100 | 60/60/60 | 100/100 | 100/100 |
| run-to-run divergence (%) | path divergence (%) | |||||||
|---|---|---|---|---|---|---|---|---|
| Engine | Model | Weights | cache off | cache on | 95% CI | rate | 95% CI | |
| llama.cpp | Qwen2.5-7B | F16 | 80 | 0.0 | 20.0 | [11.2, 30.0] | 36.2 | [26.2, 47.2] |
| llama.cpp | Qwen2.5-7B | Q8_0 | 80 | 0.0 | 55.0 | [44.1, 65.4] | 61.3 | [50.0, 71.2] |
| llama.cpp | Qwen2.5-7B | Q4_K_M | 80 | 0.0 | 77.5 | [67.2, 86.2] | 75.0 | [64.5, 83.8] |
| llama.cpp | Qwen2.5-7B | Q3_K_M | 80 | 0.0 | 67.5 | [56.2, 78.8] | 77.5 | [67.2, 86.2] |
| llama.cpp | Llama-3.1-8B | Q8_0 | 80 | 0.0 | 33.8 | [24.3, 44.6] | 55.0 | [44.1, 65.4] |
| canonical | 10 random histories (%) | difference | histories | ||||
|---|---|---|---|---|---|---|---|
| Weights | history (%) | mean | SD | range | 95% CI | from F16 (points) | above F16 |
| F16 | 40.0 | 37.4 | 1.8 | 35.0–40.0 | [27.4, 47.6] | – | – |
| Q8_0 | 60.0 | 59.5 | 1.6 | 57.5–61.3 | [49.1, 69.8] | +22.1 [+9.9, +34.4] | 10/10 |
| Q4_K_M | 81.2 | 82.5 | 1.9 | 80.0–85.0 | [74.4, 89.9] | +45.1 [+34.9, +55.6] | 10/10 |
| Q3_K_M | 77.5 | 77.1 | 2.5 | 72.5–81.2 | [68.2, 85.4] | +39.8 [+26.9, +52.1] | 10/10 |
| path determinism | path divergence | flips | correct | McNemar | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| Model | Weights | recompute | cache hit | % | 95% CI | (n) | recompute | cache hit | ||
| Qwen2.5-7B | F16 | 200 | 200/200 | 200/200 | 3.5 | [1.7, 7.0] | 0 | 183 | 183 | 1.000 |
| Qwen2.5-7B | Q8_0 | 200 | 200/200 | 200/200 | 44.5 | [37.8, 51.4] | 4 | 184 | 182 | 0.625 |
| Qwen2.5-7B | Q4_K_M | 200 | 200/200 | 200/200 | 41.5 | [34.9, 48.4] | 4 | 180 | 180 | 1.000 |
| Qwen2.5-14B | Q4_K_M | 200 | 200/200 | 200/200 | 48.0 | [41.2, 54.9] | 1 | 185 | 186 | 1.000 |
| Qwen2.5-7B | Q4_K_M | 500 | 500/500 | 500/500 | 45.4 | [41.1, 49.8] | 12 | 453 | 459 | 0.146 |
LLM Inference
Large language model inference studies how models generate outputs efficiently, reliably, and securely across hardware and workloads.
See all 276 papers