llm-cachecheck 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- llm_cachecheck-0.2.1/.gitignore +5 -0
- llm_cachecheck-0.2.1/LICENSE +27 -0
- llm_cachecheck-0.2.1/PKG-INFO +180 -0
- llm_cachecheck-0.2.1/README.md +156 -0
- llm_cachecheck-0.2.1/cachecheck.py +427 -0
- llm_cachecheck-0.2.1/examples/qwen2.5-3b-q4km_metal_b12138.json +1545 -0
- llm_cachecheck-0.2.1/pyproject.toml +50 -0
- llm_cachecheck-0.2.1/test_cachecheck.py +355 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aditi Patodiya
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
Scope note: the measurement dataset in this repository (the results/,
|
|
24
|
+
results-repair/ and results-revision/ directories, revision/orders/,
|
|
25
|
+
findings.json, repair_findings.json, and the JSON result files in analysis/) is
|
|
26
|
+
licensed separately under the Creative Commons Attribution 4.0
|
|
27
|
+
International License (CC BY 4.0).
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: llm-cachecheck
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Measure how often the prompt cache changes greedy answers on your LLM server (llama.cpp, plus an experimental two-server mode for vLLM and SGLang)
|
|
5
|
+
Project-URL: Homepage, https://github.com/aditi-p31/cache-divergence-study/tree/main/cachecheck
|
|
6
|
+
Project-URL: Source, https://github.com/aditi-p31/cache-divergence-study
|
|
7
|
+
Project-URL: Paper, https://doi.org/10.1109/ACCESS.2026.3749922
|
|
8
|
+
Project-URL: Preprint, https://arxiv.org/abs/2609.04748
|
|
9
|
+
Project-URL: Issues, https://github.com/aditi-p31/cache-divergence-study/issues
|
|
10
|
+
Author: Aditi Patodiya
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: inference,kv cache,llama.cpp,llm,nondeterminism,prefix caching,prompt cache,reproducibility,sglang,vllm
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Intended Audience :: Science/Research
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Software Development :: Testing
|
|
22
|
+
Requires-Python: >=3.8
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# llm-cachecheck
|
|
26
|
+
|
|
27
|
+
On PyPI this tool is `llm-cachecheck`. The PyPI package named `cachecheck` is
|
|
28
|
+
a different project.
|
|
29
|
+
|
|
30
|
+
How often does the prompt cache change your server's answers?
|
|
31
|
+
|
|
32
|
+
Serving stacks reuse the cached key-value state of a shared prompt prefix. A
|
|
33
|
+
cache hit and a recomputation run different operations, so with greedy
|
|
34
|
+
decoding a small numeric change can flip a token. After that the answer goes
|
|
35
|
+
its own way. This is known behavior: the llama.cpp server README says that
|
|
36
|
+
`cache_prompt` "can cause nondeterministic results". What varies is how often
|
|
37
|
+
it happens on your model, weights and build. `cachecheck` measures that.
|
|
38
|
+
|
|
39
|
+
It sends short two-turn conversations with greedy decoding and compares the
|
|
40
|
+
second answer on two paths:
|
|
41
|
+
|
|
42
|
+
- **cache path:** turn 2 reuses the state the server wrote while it generated
|
|
43
|
+
turn 1
|
|
44
|
+
- **recompute path:** turn 2 is processed from scratch
|
|
45
|
+
|
|
46
|
+
Each path runs twice. A probe counts as valid only when all of these hold:
|
|
47
|
+
|
|
48
|
+
- both paths repeat on their own
|
|
49
|
+
- the server's own counts show that the cache hit covered the turn-1 answer
|
|
50
|
+
- the server's own counts show that the recompute path reused nothing
|
|
51
|
+
|
|
52
|
+
## Run it
|
|
53
|
+
|
|
54
|
+
Python 3.8 or newer, no dependencies. Install it from PyPI:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install llm-cachecheck
|
|
58
|
+
llm-cachecheck --help
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Or download the single file
|
|
62
|
+
[`cachecheck.py`](https://github.com/aditi-p31/cache-divergence-study/blob/main/cachecheck/cachecheck.py)
|
|
63
|
+
and run `python cachecheck.py` in place of `llm-cachecheck`. Both take the
|
|
64
|
+
same options. A full run
|
|
65
|
+
sends 120 requests.
|
|
66
|
+
|
|
67
|
+
**llama.cpp.** The cache is switched per request. Start the server with one
|
|
68
|
+
slot, because the default is several slots that share a cache. Turn the
|
|
69
|
+
host-memory prompt cache off to match the study's controlled runs:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
llama-server -m model.gguf -np 1 --cache-ram 0 --port 8080
|
|
73
|
+
llm-cachecheck --base-url http://127.0.0.1:8080
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**vLLM, SGLang or another OpenAI-compatible server.** The cache is a server
|
|
77
|
+
setting, so serve the same model twice, once with prefix caching on and once
|
|
78
|
+
with it off. Both servers must report cached tokens. For vLLM on one GPU:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
vllm serve Qwen/Qwen2.5-7B-Instruct --port 8000 --gpu-memory-utilization 0.45 \
|
|
82
|
+
--generation-config vllm --enable-prompt-tokens-details
|
|
83
|
+
vllm serve Qwen/Qwen2.5-7B-Instruct --port 8001 --gpu-memory-utilization 0.45 \
|
|
84
|
+
--generation-config vllm --enable-prompt-tokens-details --no-enable-prefix-caching
|
|
85
|
+
llm-cachecheck --mode two-servers --base-url http://127.0.0.1:8000 \
|
|
86
|
+
--nocache-url http://127.0.0.1:8001 --model Qwen/Qwen2.5-7B-Instruct
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
For SGLang, add `--enable-cache-report` to both servers and
|
|
90
|
+
`--disable-radix-cache` to the second one.
|
|
91
|
+
|
|
92
|
+
The two-server mode is covered by the unit tests against a simulated server.
|
|
93
|
+
It has not yet been run against a real vLLM or SGLang server. If you run it,
|
|
94
|
+
an [issue](https://github.com/aditi-p31/cache-divergence-study/issues) with what you saw is welcome.
|
|
95
|
+
|
|
96
|
+
Two server processes can also differ for other reasons. To check that, start
|
|
97
|
+
both with prefix caching off and run with `--skip-hit-check`. Every probe
|
|
98
|
+
should come out the same.
|
|
99
|
+
|
|
100
|
+
Options: `--n` sets the number of probes (default 20), `--max-tokens` the
|
|
101
|
+
answer length (default 256), `--out` the JSON report path.
|
|
102
|
+
|
|
103
|
+
Models that think before answering need thinking turned off for this check.
|
|
104
|
+
Their templates often drop past thinking from the history, which shortens the
|
|
105
|
+
cache hit, and the tool marks those probes as inconclusive.
|
|
106
|
+
|
|
107
|
+
## Reading the result
|
|
108
|
+
|
|
109
|
+
A real run on Apple Silicon (Metal) with Qwen2.5-3B-Instruct at Q4_K_M,
|
|
110
|
+
llama.cpp b12138, `-np 1 --cache-ram 0`, default options:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
probe 1/20: same
|
|
114
|
+
...
|
|
115
|
+
probe 10/20: DIFFERENT (from character 578)
|
|
116
|
+
...
|
|
117
|
+
probe 13/20: DIFFERENT (from character 235)
|
|
118
|
+
...
|
|
119
|
+
probe 15/20: DIFFERENT (from character 376)
|
|
120
|
+
...
|
|
121
|
+
3 of 20 valid probes gave a different turn-2 answer on the cache path.
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
All 20 probes were valid. On every probe the server reported that the cache
|
|
125
|
+
hit covered all of turn 1 and its answer, and that the recompute path reused
|
|
126
|
+
nothing. The full report (made with a pre-release build of 0.2.0) is in
|
|
127
|
+
[`examples/qwen2.5-3b-q4km_metal_b12138.json`](https://github.com/aditi-p31/cache-divergence-study/blob/main/cachecheck/examples/qwen2.5-3b-q4km_metal_b12138.json).
|
|
128
|
+
It took about 15 minutes on a laptop.
|
|
129
|
+
|
|
130
|
+
| Exit status | Meaning |
|
|
131
|
+
|---|---|
|
|
132
|
+
| 0 | No valid probe differed, and at least 80% of probes were valid |
|
|
133
|
+
| 1 | At least one valid probe differed |
|
|
134
|
+
| 2 | Inconclusive: too few valid probes, or a request failed |
|
|
135
|
+
|
|
136
|
+
The JSON report keeps every answer, the finish reasons, token counts, cached
|
|
137
|
+
token counts and the server build.
|
|
138
|
+
|
|
139
|
+
The rate is specific to these short probes. It is not comparable to the
|
|
140
|
+
episode rates below, where one agent episode chains about ten requests.
|
|
141
|
+
|
|
142
|
+
## What to do about a difference
|
|
143
|
+
|
|
144
|
+
For runs that must repeat, turn prefix reuse off and record that setting with
|
|
145
|
+
your results:
|
|
146
|
+
|
|
147
|
+
- llama.cpp: `"cache_prompt": false` per request, or `--no-cache-prompt` for
|
|
148
|
+
the server
|
|
149
|
+
- vLLM: `--no-enable-prefix-caching`
|
|
150
|
+
- SGLang: `--disable-radix-cache`
|
|
151
|
+
|
|
152
|
+
A difference is not a bug in your model. It shows that the answer depends on
|
|
153
|
+
the server's cache state as well as on the request.
|
|
154
|
+
|
|
155
|
+
## Background and citation
|
|
156
|
+
|
|
157
|
+
`cachecheck` adapts the paired cache-on and cache-off design of the study in
|
|
158
|
+
[cache-divergence-study repository](https://github.com/aditi-p31/cache-divergence-study).
|
|
159
|
+
The study used Qwen2.5-7B-Instruct on llama.cpp with greedy
|
|
160
|
+
decoding, 80 BFCL multi-turn agent episodes and the host-memory prompt cache
|
|
161
|
+
off. The share of episodes whose trajectory changed between a cache hit and a
|
|
162
|
+
recomputation was 37.4% with F16 weights and 82.5% with Q4_K_M weights, as a
|
|
163
|
+
mean over ten server histories.
|
|
164
|
+
|
|
165
|
+
If `cachecheck` helps your work, please cite the paper:
|
|
166
|
+
|
|
167
|
+
```bibtex
|
|
168
|
+
@article{patodiya2026samerequest,
|
|
169
|
+
title = {Same Request, Different Answer: Quantization Amplifies
|
|
170
|
+
Cache-Induced Divergence in LLM Serving},
|
|
171
|
+
author = {Patodiya, Aditi},
|
|
172
|
+
journal = {IEEE Access},
|
|
173
|
+
year = {2026},
|
|
174
|
+
doi = {10.1109/ACCESS.2026.3749922},
|
|
175
|
+
note = {Early Access},
|
|
176
|
+
url = {https://doi.org/10.1109/ACCESS.2026.3749922}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
MIT License. See `LICENSE`.
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# llm-cachecheck
|
|
2
|
+
|
|
3
|
+
On PyPI this tool is `llm-cachecheck`. The PyPI package named `cachecheck` is
|
|
4
|
+
a different project.
|
|
5
|
+
|
|
6
|
+
How often does the prompt cache change your server's answers?
|
|
7
|
+
|
|
8
|
+
Serving stacks reuse the cached key-value state of a shared prompt prefix. A
|
|
9
|
+
cache hit and a recomputation run different operations, so with greedy
|
|
10
|
+
decoding a small numeric change can flip a token. After that the answer goes
|
|
11
|
+
its own way. This is known behavior: the llama.cpp server README says that
|
|
12
|
+
`cache_prompt` "can cause nondeterministic results". What varies is how often
|
|
13
|
+
it happens on your model, weights and build. `cachecheck` measures that.
|
|
14
|
+
|
|
15
|
+
It sends short two-turn conversations with greedy decoding and compares the
|
|
16
|
+
second answer on two paths:
|
|
17
|
+
|
|
18
|
+
- **cache path:** turn 2 reuses the state the server wrote while it generated
|
|
19
|
+
turn 1
|
|
20
|
+
- **recompute path:** turn 2 is processed from scratch
|
|
21
|
+
|
|
22
|
+
Each path runs twice. A probe counts as valid only when all of these hold:
|
|
23
|
+
|
|
24
|
+
- both paths repeat on their own
|
|
25
|
+
- the server's own counts show that the cache hit covered the turn-1 answer
|
|
26
|
+
- the server's own counts show that the recompute path reused nothing
|
|
27
|
+
|
|
28
|
+
## Run it
|
|
29
|
+
|
|
30
|
+
Python 3.8 or newer, no dependencies. Install it from PyPI:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install llm-cachecheck
|
|
34
|
+
llm-cachecheck --help
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Or download the single file
|
|
38
|
+
[`cachecheck.py`](https://github.com/aditi-p31/cache-divergence-study/blob/main/cachecheck/cachecheck.py)
|
|
39
|
+
and run `python cachecheck.py` in place of `llm-cachecheck`. Both take the
|
|
40
|
+
same options. A full run
|
|
41
|
+
sends 120 requests.
|
|
42
|
+
|
|
43
|
+
**llama.cpp.** The cache is switched per request. Start the server with one
|
|
44
|
+
slot, because the default is several slots that share a cache. Turn the
|
|
45
|
+
host-memory prompt cache off to match the study's controlled runs:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
llama-server -m model.gguf -np 1 --cache-ram 0 --port 8080
|
|
49
|
+
llm-cachecheck --base-url http://127.0.0.1:8080
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**vLLM, SGLang or another OpenAI-compatible server.** The cache is a server
|
|
53
|
+
setting, so serve the same model twice, once with prefix caching on and once
|
|
54
|
+
with it off. Both servers must report cached tokens. For vLLM on one GPU:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
vllm serve Qwen/Qwen2.5-7B-Instruct --port 8000 --gpu-memory-utilization 0.45 \
|
|
58
|
+
--generation-config vllm --enable-prompt-tokens-details
|
|
59
|
+
vllm serve Qwen/Qwen2.5-7B-Instruct --port 8001 --gpu-memory-utilization 0.45 \
|
|
60
|
+
--generation-config vllm --enable-prompt-tokens-details --no-enable-prefix-caching
|
|
61
|
+
llm-cachecheck --mode two-servers --base-url http://127.0.0.1:8000 \
|
|
62
|
+
--nocache-url http://127.0.0.1:8001 --model Qwen/Qwen2.5-7B-Instruct
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
For SGLang, add `--enable-cache-report` to both servers and
|
|
66
|
+
`--disable-radix-cache` to the second one.
|
|
67
|
+
|
|
68
|
+
The two-server mode is covered by the unit tests against a simulated server.
|
|
69
|
+
It has not yet been run against a real vLLM or SGLang server. If you run it,
|
|
70
|
+
an [issue](https://github.com/aditi-p31/cache-divergence-study/issues) with what you saw is welcome.
|
|
71
|
+
|
|
72
|
+
Two server processes can also differ for other reasons. To check that, start
|
|
73
|
+
both with prefix caching off and run with `--skip-hit-check`. Every probe
|
|
74
|
+
should come out the same.
|
|
75
|
+
|
|
76
|
+
Options: `--n` sets the number of probes (default 20), `--max-tokens` the
|
|
77
|
+
answer length (default 256), `--out` the JSON report path.
|
|
78
|
+
|
|
79
|
+
Models that think before answering need thinking turned off for this check.
|
|
80
|
+
Their templates often drop past thinking from the history, which shortens the
|
|
81
|
+
cache hit, and the tool marks those probes as inconclusive.
|
|
82
|
+
|
|
83
|
+
## Reading the result
|
|
84
|
+
|
|
85
|
+
A real run on Apple Silicon (Metal) with Qwen2.5-3B-Instruct at Q4_K_M,
|
|
86
|
+
llama.cpp b12138, `-np 1 --cache-ram 0`, default options:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
probe 1/20: same
|
|
90
|
+
...
|
|
91
|
+
probe 10/20: DIFFERENT (from character 578)
|
|
92
|
+
...
|
|
93
|
+
probe 13/20: DIFFERENT (from character 235)
|
|
94
|
+
...
|
|
95
|
+
probe 15/20: DIFFERENT (from character 376)
|
|
96
|
+
...
|
|
97
|
+
3 of 20 valid probes gave a different turn-2 answer on the cache path.
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
All 20 probes were valid. On every probe the server reported that the cache
|
|
101
|
+
hit covered all of turn 1 and its answer, and that the recompute path reused
|
|
102
|
+
nothing. The full report (made with a pre-release build of 0.2.0) is in
|
|
103
|
+
[`examples/qwen2.5-3b-q4km_metal_b12138.json`](https://github.com/aditi-p31/cache-divergence-study/blob/main/cachecheck/examples/qwen2.5-3b-q4km_metal_b12138.json).
|
|
104
|
+
It took about 15 minutes on a laptop.
|
|
105
|
+
|
|
106
|
+
| Exit status | Meaning |
|
|
107
|
+
|---|---|
|
|
108
|
+
| 0 | No valid probe differed, and at least 80% of probes were valid |
|
|
109
|
+
| 1 | At least one valid probe differed |
|
|
110
|
+
| 2 | Inconclusive: too few valid probes, or a request failed |
|
|
111
|
+
|
|
112
|
+
The JSON report keeps every answer, the finish reasons, token counts, cached
|
|
113
|
+
token counts and the server build.
|
|
114
|
+
|
|
115
|
+
The rate is specific to these short probes. It is not comparable to the
|
|
116
|
+
episode rates below, where one agent episode chains about ten requests.
|
|
117
|
+
|
|
118
|
+
## What to do about a difference
|
|
119
|
+
|
|
120
|
+
For runs that must repeat, turn prefix reuse off and record that setting with
|
|
121
|
+
your results:
|
|
122
|
+
|
|
123
|
+
- llama.cpp: `"cache_prompt": false` per request, or `--no-cache-prompt` for
|
|
124
|
+
the server
|
|
125
|
+
- vLLM: `--no-enable-prefix-caching`
|
|
126
|
+
- SGLang: `--disable-radix-cache`
|
|
127
|
+
|
|
128
|
+
A difference is not a bug in your model. It shows that the answer depends on
|
|
129
|
+
the server's cache state as well as on the request.
|
|
130
|
+
|
|
131
|
+
## Background and citation
|
|
132
|
+
|
|
133
|
+
`cachecheck` adapts the paired cache-on and cache-off design of the study in
|
|
134
|
+
[cache-divergence-study repository](https://github.com/aditi-p31/cache-divergence-study).
|
|
135
|
+
The study used Qwen2.5-7B-Instruct on llama.cpp with greedy
|
|
136
|
+
decoding, 80 BFCL multi-turn agent episodes and the host-memory prompt cache
|
|
137
|
+
off. The share of episodes whose trajectory changed between a cache hit and a
|
|
138
|
+
recomputation was 37.4% with F16 weights and 82.5% with Q4_K_M weights, as a
|
|
139
|
+
mean over ten server histories.
|
|
140
|
+
|
|
141
|
+
If `cachecheck` helps your work, please cite the paper:
|
|
142
|
+
|
|
143
|
+
```bibtex
|
|
144
|
+
@article{patodiya2026samerequest,
|
|
145
|
+
title = {Same Request, Different Answer: Quantization Amplifies
|
|
146
|
+
Cache-Induced Divergence in LLM Serving},
|
|
147
|
+
author = {Patodiya, Aditi},
|
|
148
|
+
journal = {IEEE Access},
|
|
149
|
+
year = {2026},
|
|
150
|
+
doi = {10.1109/ACCESS.2026.3749922},
|
|
151
|
+
note = {Early Access},
|
|
152
|
+
url = {https://doi.org/10.1109/ACCESS.2026.3749922}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
MIT License. See `LICENSE`.
|