llm-cachecheck 0.2.1__tar.gz → 0.2.3__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 → llm_cachecheck-0.2.3}/PKG-INFO +33 -11
- {llm_cachecheck-0.2.1 → llm_cachecheck-0.2.3}/README.md +31 -9
- {llm_cachecheck-0.2.1 → llm_cachecheck-0.2.3}/cachecheck.py +37 -5
- {llm_cachecheck-0.2.1 → llm_cachecheck-0.2.3}/pyproject.toml +1 -1
- {llm_cachecheck-0.2.1 → llm_cachecheck-0.2.3}/test_cachecheck.py +10 -0
- {llm_cachecheck-0.2.1 → llm_cachecheck-0.2.3}/.gitignore +0 -0
- {llm_cachecheck-0.2.1 → llm_cachecheck-0.2.3}/LICENSE +0 -0
- {llm_cachecheck-0.2.1 → llm_cachecheck-0.2.3}/examples/qwen2.5-3b-q4km_metal_b12138.json +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: llm-cachecheck
|
|
3
|
-
Version: 0.2.
|
|
4
|
-
Summary: Measure how often the prompt cache changes greedy answers on your LLM server (llama.cpp,
|
|
3
|
+
Version: 0.2.3
|
|
4
|
+
Summary: Measure how often the prompt cache changes greedy answers on your LLM server (llama.cpp, vLLM and SGLang)
|
|
5
5
|
Project-URL: Homepage, https://github.com/aditi-p31/cache-divergence-study/tree/main/cachecheck
|
|
6
6
|
Project-URL: Source, https://github.com/aditi-p31/cache-divergence-study
|
|
7
7
|
Project-URL: Paper, https://doi.org/10.1109/ACCESS.2026.3749922
|
|
@@ -75,23 +75,38 @@ llm-cachecheck --base-url http://127.0.0.1:8080
|
|
|
75
75
|
|
|
76
76
|
**vLLM, SGLang or another OpenAI-compatible server.** The cache is a server
|
|
77
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
|
|
78
|
+
with it off. Both servers must report cached tokens. For vLLM on one 24 GB GPU
|
|
79
|
+
(a 3B model, so two copies fit):
|
|
79
80
|
|
|
80
81
|
```bash
|
|
81
|
-
vllm serve Qwen/Qwen2.5-
|
|
82
|
+
vllm serve Qwen/Qwen2.5-3B-Instruct --port 8000 --gpu-memory-utilization 0.45 \
|
|
82
83
|
--generation-config vllm --enable-prompt-tokens-details
|
|
83
|
-
vllm serve Qwen/Qwen2.5-
|
|
84
|
+
vllm serve Qwen/Qwen2.5-3B-Instruct --port 8100 --gpu-memory-utilization 0.45 \
|
|
84
85
|
--generation-config vllm --enable-prompt-tokens-details --no-enable-prefix-caching
|
|
85
86
|
llm-cachecheck --mode two-servers --base-url http://127.0.0.1:8000 \
|
|
86
|
-
--nocache-url http://127.0.0.1:
|
|
87
|
+
--nocache-url http://127.0.0.1:8100 --model Qwen/Qwen2.5-3B-Instruct
|
|
87
88
|
```
|
|
88
89
|
|
|
89
|
-
For SGLang, add `--enable-cache-report`
|
|
90
|
-
`--disable-radix-cache` to the second one.
|
|
90
|
+
For SGLang, add `--enable-cache-report` and `--sampling-defaults openai` to
|
|
91
|
+
both servers, and `--disable-radix-cache` to the second one.
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
an
|
|
93
|
+
Make sure each server really started on its own port. If another process
|
|
94
|
+
already holds a port, it can answer for it: RunPod's standard images, for
|
|
95
|
+
example, run an nginx proxy on port 8001 that forwards to port 8000, so both
|
|
96
|
+
URLs would reach the same server.
|
|
97
|
+
The tool then reports the recompute path reusing cached tokens and marks every
|
|
98
|
+
probe invalid, so check the server logs if that happens.
|
|
99
|
+
|
|
100
|
+
Tested on real servers on 2026-11-11 and 11-12 (UTC; one RTX 4090; the tool's 20
|
|
101
|
+
conversations; details in the [currency note](https://github.com/aditi-p31/cache-divergence-study/blob/main/CURRENCY_CHECK_2026-11.md)):
|
|
102
|
+
|
|
103
|
+
| Server | Model | Conversations with a different turn-2 answer | Both caches off |
|
|
104
|
+
|---|---|---|---|
|
|
105
|
+
| vLLM 0.33.0, two servers | Qwen2.5-3B-Instruct, BF16 | 14 of 20, in each of 5 runs | 0 of 20 |
|
|
106
|
+
| SGLang 0.5.23, two servers (CUDA graphs disabled) | Qwen2.5-3B-Instruct, BF16 | 12 of 20, in each of 2 runs | 0 of 20 |
|
|
107
|
+
| llama.cpp v0.9.0 | Qwen2.5-7B-Instruct, Q4_K_M | 11 of 20, in each of 2 runs | |
|
|
108
|
+
|
|
109
|
+
If you run it, an [issue](https://github.com/aditi-p31/cache-divergence-study/issues) with what you saw is welcome.
|
|
95
110
|
|
|
96
111
|
Two server processes can also differ for other reasons. To check that, start
|
|
97
112
|
both with prefix caching off and run with `--skip-hit-check`. Every probe
|
|
@@ -152,6 +167,13 @@ your results:
|
|
|
152
167
|
A difference is not a bug in your model. It shows that the answer depends on
|
|
153
168
|
the server's cache state as well as on the request.
|
|
154
169
|
|
|
170
|
+
## Share your result
|
|
171
|
+
|
|
172
|
+
At the end of a run the tool prints a short result block. It leaves out your
|
|
173
|
+
server URL and any key. If you would like to share it, open a
|
|
174
|
+
[result issue](https://github.com/aditi-p31/cache-divergence-study/issues/new?template=cachecheck-result.yml)
|
|
175
|
+
and paste the block. Results where no probe differed are just as useful.
|
|
176
|
+
|
|
155
177
|
## Background and citation
|
|
156
178
|
|
|
157
179
|
`cachecheck` adapts the paired cache-on and cache-off design of the study in
|
|
@@ -51,23 +51,38 @@ llm-cachecheck --base-url http://127.0.0.1:8080
|
|
|
51
51
|
|
|
52
52
|
**vLLM, SGLang or another OpenAI-compatible server.** The cache is a server
|
|
53
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
|
|
54
|
+
with it off. Both servers must report cached tokens. For vLLM on one 24 GB GPU
|
|
55
|
+
(a 3B model, so two copies fit):
|
|
55
56
|
|
|
56
57
|
```bash
|
|
57
|
-
vllm serve Qwen/Qwen2.5-
|
|
58
|
+
vllm serve Qwen/Qwen2.5-3B-Instruct --port 8000 --gpu-memory-utilization 0.45 \
|
|
58
59
|
--generation-config vllm --enable-prompt-tokens-details
|
|
59
|
-
vllm serve Qwen/Qwen2.5-
|
|
60
|
+
vllm serve Qwen/Qwen2.5-3B-Instruct --port 8100 --gpu-memory-utilization 0.45 \
|
|
60
61
|
--generation-config vllm --enable-prompt-tokens-details --no-enable-prefix-caching
|
|
61
62
|
llm-cachecheck --mode two-servers --base-url http://127.0.0.1:8000 \
|
|
62
|
-
--nocache-url http://127.0.0.1:
|
|
63
|
+
--nocache-url http://127.0.0.1:8100 --model Qwen/Qwen2.5-3B-Instruct
|
|
63
64
|
```
|
|
64
65
|
|
|
65
|
-
For SGLang, add `--enable-cache-report`
|
|
66
|
-
`--disable-radix-cache` to the second one.
|
|
66
|
+
For SGLang, add `--enable-cache-report` and `--sampling-defaults openai` to
|
|
67
|
+
both servers, and `--disable-radix-cache` to the second one.
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
an
|
|
69
|
+
Make sure each server really started on its own port. If another process
|
|
70
|
+
already holds a port, it can answer for it: RunPod's standard images, for
|
|
71
|
+
example, run an nginx proxy on port 8001 that forwards to port 8000, so both
|
|
72
|
+
URLs would reach the same server.
|
|
73
|
+
The tool then reports the recompute path reusing cached tokens and marks every
|
|
74
|
+
probe invalid, so check the server logs if that happens.
|
|
75
|
+
|
|
76
|
+
Tested on real servers on 2026-11-11 and 11-12 (UTC; one RTX 4090; the tool's 20
|
|
77
|
+
conversations; details in the [currency note](https://github.com/aditi-p31/cache-divergence-study/blob/main/CURRENCY_CHECK_2026-11.md)):
|
|
78
|
+
|
|
79
|
+
| Server | Model | Conversations with a different turn-2 answer | Both caches off |
|
|
80
|
+
|---|---|---|---|
|
|
81
|
+
| vLLM 0.33.0, two servers | Qwen2.5-3B-Instruct, BF16 | 14 of 20, in each of 5 runs | 0 of 20 |
|
|
82
|
+
| SGLang 0.5.23, two servers (CUDA graphs disabled) | Qwen2.5-3B-Instruct, BF16 | 12 of 20, in each of 2 runs | 0 of 20 |
|
|
83
|
+
| llama.cpp v0.9.0 | Qwen2.5-7B-Instruct, Q4_K_M | 11 of 20, in each of 2 runs | |
|
|
84
|
+
|
|
85
|
+
If you run it, an [issue](https://github.com/aditi-p31/cache-divergence-study/issues) with what you saw is welcome.
|
|
71
86
|
|
|
72
87
|
Two server processes can also differ for other reasons. To check that, start
|
|
73
88
|
both with prefix caching off and run with `--skip-hit-check`. Every probe
|
|
@@ -128,6 +143,13 @@ your results:
|
|
|
128
143
|
A difference is not a bug in your model. It shows that the answer depends on
|
|
129
144
|
the server's cache state as well as on the request.
|
|
130
145
|
|
|
146
|
+
## Share your result
|
|
147
|
+
|
|
148
|
+
At the end of a run the tool prints a short result block. It leaves out your
|
|
149
|
+
server URL and any key. If you would like to share it, open a
|
|
150
|
+
[result issue](https://github.com/aditi-p31/cache-divergence-study/issues/new?template=cachecheck-result.yml)
|
|
151
|
+
and paste the block. Results where no probe differed are just as useful.
|
|
152
|
+
|
|
131
153
|
## Background and citation
|
|
132
154
|
|
|
133
155
|
`cachecheck` adapts the paired cache-on and cache-off design of the study in
|
|
@@ -22,7 +22,8 @@ Two modes:
|
|
|
22
22
|
--mode two-servers vLLM, SGLang or another OpenAI-compatible stack:
|
|
23
23
|
--base-url serves with prefix caching on, and
|
|
24
24
|
--nocache-url serves the same model with it off
|
|
25
|
-
(tested
|
|
25
|
+
(tested on real vLLM 0.33.0 and SGLang 0.5.23
|
|
26
|
+
servers; results in the README)
|
|
26
27
|
|
|
27
28
|
Standard library only. Python 3.8 or newer.
|
|
28
29
|
|
|
@@ -30,7 +31,7 @@ Usage (from PyPI: pip install llm-cachecheck, then run llm-cachecheck
|
|
|
30
31
|
in place of python cachecheck.py):
|
|
31
32
|
python cachecheck.py --base-url http://127.0.0.1:8080
|
|
32
33
|
python cachecheck.py --mode two-servers --base-url http://127.0.0.1:8000 \
|
|
33
|
-
--nocache-url http://127.0.0.1:
|
|
34
|
+
--nocache-url http://127.0.0.1:8100 --model Qwen/Qwen2.5-3B-Instruct
|
|
34
35
|
|
|
35
36
|
Exit status: 0 no difference and at least 80% of probes valid, 1 at
|
|
36
37
|
least one valid probe differs, 2 inconclusive.
|
|
@@ -41,10 +42,11 @@ import binascii
|
|
|
41
42
|
import json
|
|
42
43
|
import os
|
|
43
44
|
import sys
|
|
45
|
+
import time
|
|
44
46
|
import urllib.error
|
|
45
47
|
import urllib.request
|
|
46
48
|
|
|
47
|
-
__version__ = "0.2.
|
|
49
|
+
__version__ = "0.2.3"
|
|
48
50
|
|
|
49
51
|
CITATION = (
|
|
50
52
|
"Patodiya, A. Same Request, Different Answer: Quantization Amplifies "
|
|
@@ -54,6 +56,9 @@ CITATION = (
|
|
|
54
56
|
|
|
55
57
|
MIN_VALID_SHARE = 0.8
|
|
56
58
|
|
|
59
|
+
SHARE_URL = ("https://github.com/aditi-p31/cache-divergence-study/issues/new"
|
|
60
|
+
"?template=cachecheck-result.yml")
|
|
61
|
+
|
|
57
62
|
# Two-turn probes. Turn 1 asks for a worked answer of a few sentences, so
|
|
58
63
|
# the server writes a useful amount of KV state while generating it.
|
|
59
64
|
# Turn 2 builds on that answer, so it is processed on top of that state.
|
|
@@ -305,6 +310,29 @@ def server_props(args):
|
|
|
305
310
|
"model_file": os.path.basename(props.get("model_path") or "")}
|
|
306
311
|
|
|
307
312
|
|
|
313
|
+
def result_block(args, props, summary, when):
|
|
314
|
+
"""A short, copyable summary of the run. It leaves out the server URL
|
|
315
|
+
and any key, so it is safe to paste in public."""
|
|
316
|
+
build = props.get("build_info") or "(fill in engine and version)"
|
|
317
|
+
model = args.model or props.get("model_file") or "(fill in model)"
|
|
318
|
+
lines = [
|
|
319
|
+
"---- llm-cachecheck result ----",
|
|
320
|
+
"tool: llm-cachecheck %s" % __version__,
|
|
321
|
+
"date (UTC): %s" % when,
|
|
322
|
+
"mode: %s" % args.mode,
|
|
323
|
+
"engine build: %s" % build,
|
|
324
|
+
"model: %s" % model,
|
|
325
|
+
"GPU or CPU: (fill in)",
|
|
326
|
+
"probes: %d planned, %d valid, %d differed" % (
|
|
327
|
+
summary["probes_planned"], summary["probes_valid"],
|
|
328
|
+
summary["probes_diverged"]),
|
|
329
|
+
"max tokens: %d, seed: %d, hit check: %s" % (
|
|
330
|
+
args.max_tokens, args.seed, summary["hit_check"]),
|
|
331
|
+
"-------------------------------",
|
|
332
|
+
]
|
|
333
|
+
return "\n".join(lines)
|
|
334
|
+
|
|
335
|
+
|
|
308
336
|
def status_line(r):
|
|
309
337
|
if not r["valid"]:
|
|
310
338
|
return "inconclusive (%s)" % "; ".join(r["problems"])
|
|
@@ -324,8 +352,8 @@ def main(argv=None):
|
|
|
324
352
|
default="llamacpp",
|
|
325
353
|
help="llamacpp (default): one llama.cpp server. "
|
|
326
354
|
"two-servers: vLLM, SGLang or another OpenAI-"
|
|
327
|
-
"compatible stack
|
|
328
|
-
"
|
|
355
|
+
"compatible stack, with a second server that has "
|
|
356
|
+
"prefix caching off")
|
|
329
357
|
ap.add_argument("--nocache-url",
|
|
330
358
|
help="two-servers mode: same model, prefix caching off")
|
|
331
359
|
ap.add_argument("--model", default=None,
|
|
@@ -414,6 +442,10 @@ def main(argv=None):
|
|
|
414
442
|
if args.skip_hit_check:
|
|
415
443
|
print("Cache hits were assumed, not confirmed (--skip-hit-check).")
|
|
416
444
|
print("Report written to %s" % args.out)
|
|
445
|
+
print()
|
|
446
|
+
print(result_block(args, props, summary,
|
|
447
|
+
time.strftime("%Y-%m-%d %H:%M", time.gmtime())))
|
|
448
|
+
print("To share this result (optional), open: " + SHARE_URL)
|
|
417
449
|
print("Method: " + CITATION)
|
|
418
450
|
|
|
419
451
|
if diverged:
|
|
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "llm-cachecheck"
|
|
7
7
|
dynamic = ["version"]
|
|
8
|
-
description = "Measure how often the prompt cache changes greedy answers on your LLM server (llama.cpp,
|
|
8
|
+
description = "Measure how often the prompt cache changes greedy answers on your LLM server (llama.cpp, vLLM and SGLang)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
11
11
|
license = "MIT"
|
|
@@ -162,6 +162,16 @@ class CacheCheckTests(unittest.TestCase):
|
|
|
162
162
|
self.assertIn("DIFFERENT (from character", out)
|
|
163
163
|
self.assertIn("3 of 3 valid probes gave a different", out)
|
|
164
164
|
|
|
165
|
+
def test_result_block_is_printed_and_safe_to_share(self):
|
|
166
|
+
server = self.server(diverge=True)
|
|
167
|
+
code, report, out = self.run_tool(server)
|
|
168
|
+
self.assertIn("---- llm-cachecheck result ----", out)
|
|
169
|
+
self.assertIn("engine build: b-fake", out)
|
|
170
|
+
self.assertIn("probes: 3 planned, 3 valid, 3 differed", out)
|
|
171
|
+
self.assertIn(cachecheck.SHARE_URL, out)
|
|
172
|
+
block = out.split("---- llm-cachecheck result ----")[1]
|
|
173
|
+
self.assertNotIn(server.url, block)
|
|
174
|
+
|
|
165
175
|
def test_divergence_in_reasoning_only(self):
|
|
166
176
|
code, report, out = self.run_tool(self.server(diverge_reasoning=True))
|
|
167
177
|
self.assertEqual(code, 1)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|