effbelt 0.1.0__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.
effbelt-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sardor Razikov
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.
effbelt-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,168 @@
1
+ Metadata-Version: 2.4
2
+ Name: effbelt
3
+ Version: 0.1.0
4
+ Summary: LLM efficiency toolbelt: fit, tokenwaste, evalsanity, quantcheck, cheapest, modelcard — save money and catch silent bugs, on a laptop.
5
+ Author: Sardor Razikov
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/SRKRZ23/effbelt
8
+ Project-URL: Source, https://github.com/SRKRZ23/effbelt
9
+ Project-URL: Sponsor, https://github.com/sponsors/SRKRZ23
10
+ Keywords: llm,gpu,memory,kv-cache,quantization,cost,efficiency,benchmark
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Provides-Extra: torch
18
+ Requires-Dist: torch>=2.1; extra == "torch"
19
+ Provides-Extra: hf
20
+ Requires-Dist: torch>=2.1; extra == "hf"
21
+ Requires-Dist: transformers>=5; extra == "hf"
22
+ Requires-Dist: kvquant-hf; extra == "hf"
23
+ Dynamic: license-file
24
+
25
+ # effbelt — the LLM efficiency toolbelt
26
+
27
+ Six small, honest tools that **save money and catch silent bugs** — all runnable
28
+ on a laptop with **no GPU and no API key**. Four of them need nothing but Python;
29
+ two can optionally measure real numbers on a tiny local model.
30
+
31
+ ```bash
32
+ pip install effbelt # the four pure tools
33
+ pip install "effbelt[hf]" # + quantcheck tensors and live modelcard
34
+ ```
35
+
36
+ | tool | one line | needs a model? |
37
+ |------|----------|:---:|
38
+ | `fit` | will this model fit on my card? (weights + KV cache + overhead) | no |
39
+ | `tokenwaste` | where your LLM **API bill leaks** (duplicate prompts, retries, oversized model) | no |
40
+ | `evalsanity` | **is your benchmark lying?** (contamination, degenerate data/preds) | no |
41
+ | `quantcheck` | **did your quantization actually do anything?** (silent no-op guard) | tensors only |
42
+ | `cheapest` | the cheapest config that still clears **your** quality bar | your eval |
43
+ | `modelcard` | one-page quant tradeoff card (exact memory + *measured* ppl) | optional |
44
+
45
+ Every number here was produced on an Apple-silicon MacBook, offline. Nothing is invented.
46
+
47
+ ---
48
+
49
+ ## Why trust the memory math? It matches a real cache to the byte.
50
+
51
+ `fit`'s KV-cache formula is cross-checked in the test suite against the **real
52
+ measured bytes** of a live Hugging Face cache (via the sister project
53
+ [kvquant](https://github.com/SRKRZ23/kvquant)):
54
+
55
+ ```
56
+ KV formula check: analytic 180.0KB vs measured 180.0KB at seq=10 (0.00% off)
57
+ ```
58
+
59
+ ## `fit` — stop guessing at OOM
60
+
61
+ ```
62
+ $ effbelt fit llama-3.1-70b --gpu rtx-4090 --seq 8192
63
+ llama-3.1-70b @ seq=8192 batch=1 weights=fp16 kv=fp16
64
+ weights 131.50 GB
65
+ kv cache 2.50 GB (8 kv-heads GQA)
66
+ overhead 1.00 GB
67
+ -------- 135.00 GB total
68
+ DOES NOT FIT on a 24 GB card (headroom -111.00 GB)
69
+ ```
70
+ ```
71
+ $ effbelt fit llama-3.1-8b --gpu rtx-4090 --seq 32768 --kv int8
72
+ ... FITS on a 24 GB card (headroom +6.04 GB)
73
+ ```
74
+ Use `--hf <repo>` to pull authoritative dimensions straight from a model's config.
75
+
76
+ ## `tokenwaste` — audit an API request log (JSONL)
77
+
78
+ ```
79
+ $ effbelt tokenwaste requests.jsonl
80
+ 22 requests, ~$0.0780 total (prices as of 2026-10)
81
+ recoverable up to ~$0.0780 (<= 100% of bill; levers below can overlap):
82
+ - oversized-model ~$0.0718 large model, <=20 output tok -> try a small model
83
+ - over-long-context ~$0.0300 context over 8000 tok
84
+ - no-prefix-cache ~$0.0183 22x identical system prompt (~290 tok)
85
+ - retries ~$0.0013 requests marked retry=true
86
+ ```
87
+ The levers overlap on the same request, so the headline is an honest **upper
88
+ bound** capped at 100% of the bill — never a made-up "save 300%".
89
+
90
+ ## `evalsanity` — before you believe a leaderboard number
91
+
92
+ ```
93
+ $ effbelt evalsanity eval.json
94
+ 41 test items: 2 flag(s)
95
+ [HIGH] 1/41 test items appear VERBATIM in train -> contamination
96
+ [MED ] labels 93% one class ('A') -> imbalanced
97
+ -> at least one HIGH flag: do NOT trust this number yet.
98
+ ```
99
+
100
+ ## `quantcheck` — the silent no-op killer
101
+
102
+ The lesson behind kvquant, generalized to **any** tensor transform you think
103
+ quantizes. A no-op (e.g. you mutated a non-persisting view) passes every smoke
104
+ test and moves no metric — quantcheck proves it *bit*, estimates effective
105
+ bits, and flags the two classic bugs:
106
+
107
+ ```python
108
+ from effbelt import quantcheck
109
+ r = quantcheck.check(my_quant_fn, some_tensor, claimed_bits=8)
110
+ print(r.report())
111
+ # quantcheck: SILENT NO-OP <- if your "quantizer" changed nothing
112
+ # quantcheck: SUSPICIOUS <- not idempotent / coarser than claimed
113
+ # quantcheck: OK
114
+ ```
115
+
116
+ ## `modelcard` — know the tradeoff before you quantize
117
+
118
+ Exact memory arithmetic, and *measured* quality when you point it at a model:
119
+
120
+ ```
121
+ $ effbelt modelcard gemma-3-270m --seq 8192 --live google/gemma-3-270m-it
122
+ kv dtype kv cache vs fp16 quality
123
+ fp16 0.14GB 1.00x +0.00% ppl
124
+ int8 0.07GB 2.00x -0.33% ppl <- free: 2x smaller, lossless
125
+ int4 0.04GB 4.00x +68.41% ppl <- breaks on this small model
126
+ quality measured on google/gemma-3-270m-it (160 tok, MPS/CPU)
127
+ ```
128
+
129
+ ## `cheapest` — smallest model that still passes
130
+
131
+ ```python
132
+ from effbelt import cheapest
133
+ res = cheapest.sweep([
134
+ cheapest.Candidate("7b-int4", cost=1, quality_fn=lambda: my_eval("7b-int4")),
135
+ cheapest.Candidate("7b-fp16", cost=2, quality_fn=lambda: my_eval("7b-fp16")),
136
+ cheapest.Candidate("70b-int4", cost=8, quality_fn=lambda: my_eval("70b-int4")),
137
+ ], bar=0.80)
138
+ print(res.report()) # -> use the cheapest one that clears 0.80
139
+ ```
140
+
141
+ ---
142
+
143
+ ## Honest scope
144
+ - Prices and the small model table are **approximate and editable** (see
145
+ `effbelt/models.py`); for authoritative model dims use `fit --hf`.
146
+ - `quantcheck`'s effective-bit estimate is advisory at small group sizes — the
147
+ *silent-no-op* and *idempotency* checks are the reliable ones.
148
+ - `modelcard` quality numbers are measured only when you pass `--live`; otherwise
149
+ the quality column is left blank. We don't fill it with guesses.
150
+
151
+ ## Tests
152
+ All validation runs on a laptop, offline:
153
+ ```bash
154
+ python -m pytest tests/ -v # 18 tests, incl. the real-cache cross-check
155
+ ```
156
+
157
+ ## Support
158
+ Free and MIT. If it saved you a GPU or a bill, consider sponsoring:
159
+ **https://github.com/sponsors/SRKRZ23**
160
+
161
+ Crypto (receive-only):
162
+ - BTC `bc1q9hm4upksmvkzhl8z3zvxgz94da358qfe3tred0`
163
+ - ETH / Polygon / USDT-ERC20 `0xa9e3b63799f5E5cc6258042D45869a5a1d39F021`
164
+ - USDT-TRC20 `TUyNkMXme1CYAc1wZ8SZ64X9upBc9nwTZ5`
165
+ - SOL `j8zZCsWwM8WmUZwMSDHikNuz6E49Vn8nwo9FMQnHSoW`
166
+
167
+ Sister project: [kvquant](https://github.com/SRKRZ23/kvquant) — the engine behind
168
+ `quantcheck` and `modelcard`.
@@ -0,0 +1,144 @@
1
+ # effbelt — the LLM efficiency toolbelt
2
+
3
+ Six small, honest tools that **save money and catch silent bugs** — all runnable
4
+ on a laptop with **no GPU and no API key**. Four of them need nothing but Python;
5
+ two can optionally measure real numbers on a tiny local model.
6
+
7
+ ```bash
8
+ pip install effbelt # the four pure tools
9
+ pip install "effbelt[hf]" # + quantcheck tensors and live modelcard
10
+ ```
11
+
12
+ | tool | one line | needs a model? |
13
+ |------|----------|:---:|
14
+ | `fit` | will this model fit on my card? (weights + KV cache + overhead) | no |
15
+ | `tokenwaste` | where your LLM **API bill leaks** (duplicate prompts, retries, oversized model) | no |
16
+ | `evalsanity` | **is your benchmark lying?** (contamination, degenerate data/preds) | no |
17
+ | `quantcheck` | **did your quantization actually do anything?** (silent no-op guard) | tensors only |
18
+ | `cheapest` | the cheapest config that still clears **your** quality bar | your eval |
19
+ | `modelcard` | one-page quant tradeoff card (exact memory + *measured* ppl) | optional |
20
+
21
+ Every number here was produced on an Apple-silicon MacBook, offline. Nothing is invented.
22
+
23
+ ---
24
+
25
+ ## Why trust the memory math? It matches a real cache to the byte.
26
+
27
+ `fit`'s KV-cache formula is cross-checked in the test suite against the **real
28
+ measured bytes** of a live Hugging Face cache (via the sister project
29
+ [kvquant](https://github.com/SRKRZ23/kvquant)):
30
+
31
+ ```
32
+ KV formula check: analytic 180.0KB vs measured 180.0KB at seq=10 (0.00% off)
33
+ ```
34
+
35
+ ## `fit` — stop guessing at OOM
36
+
37
+ ```
38
+ $ effbelt fit llama-3.1-70b --gpu rtx-4090 --seq 8192
39
+ llama-3.1-70b @ seq=8192 batch=1 weights=fp16 kv=fp16
40
+ weights 131.50 GB
41
+ kv cache 2.50 GB (8 kv-heads GQA)
42
+ overhead 1.00 GB
43
+ -------- 135.00 GB total
44
+ DOES NOT FIT on a 24 GB card (headroom -111.00 GB)
45
+ ```
46
+ ```
47
+ $ effbelt fit llama-3.1-8b --gpu rtx-4090 --seq 32768 --kv int8
48
+ ... FITS on a 24 GB card (headroom +6.04 GB)
49
+ ```
50
+ Use `--hf <repo>` to pull authoritative dimensions straight from a model's config.
51
+
52
+ ## `tokenwaste` — audit an API request log (JSONL)
53
+
54
+ ```
55
+ $ effbelt tokenwaste requests.jsonl
56
+ 22 requests, ~$0.0780 total (prices as of 2026-10)
57
+ recoverable up to ~$0.0780 (<= 100% of bill; levers below can overlap):
58
+ - oversized-model ~$0.0718 large model, <=20 output tok -> try a small model
59
+ - over-long-context ~$0.0300 context over 8000 tok
60
+ - no-prefix-cache ~$0.0183 22x identical system prompt (~290 tok)
61
+ - retries ~$0.0013 requests marked retry=true
62
+ ```
63
+ The levers overlap on the same request, so the headline is an honest **upper
64
+ bound** capped at 100% of the bill — never a made-up "save 300%".
65
+
66
+ ## `evalsanity` — before you believe a leaderboard number
67
+
68
+ ```
69
+ $ effbelt evalsanity eval.json
70
+ 41 test items: 2 flag(s)
71
+ [HIGH] 1/41 test items appear VERBATIM in train -> contamination
72
+ [MED ] labels 93% one class ('A') -> imbalanced
73
+ -> at least one HIGH flag: do NOT trust this number yet.
74
+ ```
75
+
76
+ ## `quantcheck` — the silent no-op killer
77
+
78
+ The lesson behind kvquant, generalized to **any** tensor transform you think
79
+ quantizes. A no-op (e.g. you mutated a non-persisting view) passes every smoke
80
+ test and moves no metric — quantcheck proves it *bit*, estimates effective
81
+ bits, and flags the two classic bugs:
82
+
83
+ ```python
84
+ from effbelt import quantcheck
85
+ r = quantcheck.check(my_quant_fn, some_tensor, claimed_bits=8)
86
+ print(r.report())
87
+ # quantcheck: SILENT NO-OP <- if your "quantizer" changed nothing
88
+ # quantcheck: SUSPICIOUS <- not idempotent / coarser than claimed
89
+ # quantcheck: OK
90
+ ```
91
+
92
+ ## `modelcard` — know the tradeoff before you quantize
93
+
94
+ Exact memory arithmetic, and *measured* quality when you point it at a model:
95
+
96
+ ```
97
+ $ effbelt modelcard gemma-3-270m --seq 8192 --live google/gemma-3-270m-it
98
+ kv dtype kv cache vs fp16 quality
99
+ fp16 0.14GB 1.00x +0.00% ppl
100
+ int8 0.07GB 2.00x -0.33% ppl <- free: 2x smaller, lossless
101
+ int4 0.04GB 4.00x +68.41% ppl <- breaks on this small model
102
+ quality measured on google/gemma-3-270m-it (160 tok, MPS/CPU)
103
+ ```
104
+
105
+ ## `cheapest` — smallest model that still passes
106
+
107
+ ```python
108
+ from effbelt import cheapest
109
+ res = cheapest.sweep([
110
+ cheapest.Candidate("7b-int4", cost=1, quality_fn=lambda: my_eval("7b-int4")),
111
+ cheapest.Candidate("7b-fp16", cost=2, quality_fn=lambda: my_eval("7b-fp16")),
112
+ cheapest.Candidate("70b-int4", cost=8, quality_fn=lambda: my_eval("70b-int4")),
113
+ ], bar=0.80)
114
+ print(res.report()) # -> use the cheapest one that clears 0.80
115
+ ```
116
+
117
+ ---
118
+
119
+ ## Honest scope
120
+ - Prices and the small model table are **approximate and editable** (see
121
+ `effbelt/models.py`); for authoritative model dims use `fit --hf`.
122
+ - `quantcheck`'s effective-bit estimate is advisory at small group sizes — the
123
+ *silent-no-op* and *idempotency* checks are the reliable ones.
124
+ - `modelcard` quality numbers are measured only when you pass `--live`; otherwise
125
+ the quality column is left blank. We don't fill it with guesses.
126
+
127
+ ## Tests
128
+ All validation runs on a laptop, offline:
129
+ ```bash
130
+ python -m pytest tests/ -v # 18 tests, incl. the real-cache cross-check
131
+ ```
132
+
133
+ ## Support
134
+ Free and MIT. If it saved you a GPU or a bill, consider sponsoring:
135
+ **https://github.com/sponsors/SRKRZ23**
136
+
137
+ Crypto (receive-only):
138
+ - BTC `bc1q9hm4upksmvkzhl8z3zvxgz94da358qfe3tred0`
139
+ - ETH / Polygon / USDT-ERC20 `0xa9e3b63799f5E5cc6258042D45869a5a1d39F021`
140
+ - USDT-TRC20 `TUyNkMXme1CYAc1wZ8SZ64X9upBc9nwTZ5`
141
+ - SOL `j8zZCsWwM8WmUZwMSDHikNuz6E49Vn8nwo9FMQnHSoW`
142
+
143
+ Sister project: [kvquant](https://github.com/SRKRZ23/kvquant) — the engine behind
144
+ `quantcheck` and `modelcard`.
@@ -0,0 +1,21 @@
1
+ """effbelt — the LLM efficiency toolbelt.
2
+
3
+ Six small, honest tools that save money and catch silent bugs, all runnable
4
+ on a laptop with no GPU and no API key:
5
+
6
+ fit will this model fit on my card? (weights + KV cache + overhead)
7
+ tokenwaste where your LLM API bill leaks (duplicate prompts, retries, ...)
8
+ evalsanity is your benchmark lying? (contamination, degenerate data/preds)
9
+ quantcheck did your quantization actually do anything? (silent no-op guard)
10
+ cheapest the cheapest config that still clears your quality bar
11
+ modelcard one-page quant tradeoff card (arithmetic memory + measured ppl)
12
+
13
+ Sister project: kvquant (the engine behind quantcheck/modelcard).
14
+ MIT. If it saved you a GPU, consider sponsoring.
15
+ """
16
+ __version__ = "0.1.0"
17
+
18
+ from . import memory, tokenwaste, evalsanity, quantcheck, cheapest, modelcard
19
+
20
+ __all__ = ["memory", "tokenwaste", "evalsanity", "quantcheck", "cheapest",
21
+ "modelcard", "__version__"]
@@ -0,0 +1,57 @@
1
+ """`cheapest` — the smallest/cheapest config that still passes YOUR bar.
2
+
3
+ You give candidate configs (each with a relative cost and a way to score
4
+ quality on your own task) and a quality bar. cheapest runs the sweep and
5
+ returns the lowest-cost config that clears the bar. The quality function is
6
+ yours (an eval on your data); the logic here is just the honest sweep +
7
+ pick, so the recommendation is only as good as your bar and your eval.
8
+ """
9
+ from __future__ import annotations
10
+ from dataclasses import dataclass
11
+ from typing import Callable
12
+
13
+
14
+ @dataclass
15
+ class Candidate:
16
+ name: str
17
+ cost: float # relative $ or GB or latency -- your unit
18
+ quality_fn: Callable[[], float] # returns a score; higher is better
19
+
20
+
21
+ @dataclass
22
+ class SweepRow:
23
+ name: str
24
+ cost: float
25
+ quality: float
26
+ passes: bool
27
+
28
+
29
+ @dataclass
30
+ class CheapestResult:
31
+ bar: float
32
+ rows: list[SweepRow]
33
+ pick: SweepRow | None
34
+
35
+ def report(self) -> str:
36
+ lines = [f"quality bar = {self.bar:g}"]
37
+ for r in sorted(self.rows, key=lambda x: x.cost):
38
+ mark = "PASS" if r.passes else "fail"
39
+ star = " <= cheapest passing" if self.pick and r.name == self.pick.name else ""
40
+ lines.append(f" cost {r.cost:8.3f} q={r.quality:.4f} [{mark}] "
41
+ f"{r.name}{star}")
42
+ if self.pick:
43
+ lines.append(f"-> use {self.pick.name!r} (cost {self.pick.cost:g}, "
44
+ f"q={self.pick.quality:.4f})")
45
+ else:
46
+ lines.append("-> NOTHING clears the bar; raise budget or lower the bar.")
47
+ return "\n".join(lines)
48
+
49
+
50
+ def sweep(candidates: list[Candidate], bar: float) -> CheapestResult:
51
+ rows = []
52
+ for c in candidates:
53
+ q = float(c.quality_fn())
54
+ rows.append(SweepRow(c.name, c.cost, q, q >= bar))
55
+ passing = [r for r in rows if r.passes]
56
+ pick = min(passing, key=lambda r: r.cost) if passing else None
57
+ return CheapestResult(bar, rows, pick)
@@ -0,0 +1,101 @@
1
+ """effbelt command-line interface: `effbelt <tool> ...`."""
2
+ from __future__ import annotations
3
+ import argparse
4
+ import sys
5
+
6
+
7
+ def _cmd_fit(a):
8
+ from .memory import fit, shape_from_hf
9
+ from .models import GPUS
10
+ gpu = a.gpu
11
+ if isinstance(gpu, str):
12
+ if gpu.lower() not in GPUS:
13
+ print(f"unknown gpu {gpu!r}; known: {sorted(GPUS)}", file=sys.stderr)
14
+ return 2
15
+ gpu = GPUS[gpu.lower()]
16
+ shape = shape_from_hf(a.hf) if a.hf else a.model
17
+ r = fit(shape, gpu_gb=float(gpu), seq_len=a.seq, batch=a.batch,
18
+ weight_dtype=a.wdtype, kv_dtype=a.kv, overhead_gb=a.overhead)
19
+ print(r.report())
20
+ return 0
21
+
22
+
23
+ def _cmd_tokenwaste(a):
24
+ from .tokenwaste import analyze, load_jsonl
25
+ recs = load_jsonl(a.log)
26
+ print(analyze(recs, long_context_tokens=a.long_context).report())
27
+ return 0
28
+
29
+
30
+ def _cmd_evalsanity(a):
31
+ from .evalsanity import check
32
+ import json
33
+ with open(a.data, encoding="utf-8") as f:
34
+ d = json.load(f)
35
+ rep = check(d["test"], d.get("train"), d.get("labels"), d.get("predictions"))
36
+ print(rep.report())
37
+ return 0 if rep.clean else 1
38
+
39
+
40
+ def _cmd_modelcard(a):
41
+ from .modelcard import build, measure_kv_quality
42
+ measured, on = None, None
43
+ if a.live:
44
+ measured = measure_kv_quality(a.live, max_tokens=a.max_tokens)
45
+ on = f"{a.live} ({a.max_tokens} tok, MPS/CPU)"
46
+ print(build(a.model, seq_len=a.seq, batch=a.batch,
47
+ weight_dtype=a.wdtype, measured_ppl=measured,
48
+ measured_on=on).report())
49
+ return 0
50
+
51
+
52
+ def build_parser() -> argparse.ArgumentParser:
53
+ p = argparse.ArgumentParser(prog="effbelt",
54
+ description="LLM efficiency toolbelt")
55
+ sub = p.add_subparsers(dest="cmd", required=True)
56
+
57
+ f = sub.add_parser("fit", help="will this model fit on my card?")
58
+ f.add_argument("model", help="known model name, or use --hf")
59
+ f.add_argument("--hf", help="authoritative dims from a HF repo id")
60
+ f.add_argument("--gpu", default="rtx-4090", help="gpu name or GB number")
61
+ f.add_argument("--seq", type=int, default=4096)
62
+ f.add_argument("--batch", type=int, default=1)
63
+ f.add_argument("--wdtype", default="fp16")
64
+ f.add_argument("--kv", default="fp16")
65
+ f.add_argument("--overhead", type=float, default=1.0)
66
+ f.set_defaults(func=_cmd_fit)
67
+
68
+ t = sub.add_parser("tokenwaste", help="audit an API request log (jsonl)")
69
+ t.add_argument("log")
70
+ t.add_argument("--long-context", type=int, default=8000)
71
+ t.set_defaults(func=_cmd_tokenwaste)
72
+
73
+ e = sub.add_parser("evalsanity", help="is your benchmark lying?")
74
+ e.add_argument("data", help="json with keys: test[, train, labels, predictions]")
75
+ e.set_defaults(func=_cmd_evalsanity)
76
+
77
+ m = sub.add_parser("modelcard", help="quant tradeoff card")
78
+ m.add_argument("model")
79
+ m.add_argument("--seq", type=int, default=4096)
80
+ m.add_argument("--batch", type=int, default=1)
81
+ m.add_argument("--wdtype", default="fp16")
82
+ m.add_argument("--live", help="HF repo id to MEASURE real ppl deltas")
83
+ m.add_argument("--max-tokens", type=int, default=200)
84
+ m.set_defaults(func=_cmd_modelcard)
85
+
86
+ return p
87
+
88
+
89
+ def main(argv=None) -> int:
90
+ args = build_parser().parse_args(argv)
91
+ try:
92
+ gpu = getattr(args, "gpu", None)
93
+ if gpu is not None and gpu.replace(".", "", 1).isdigit():
94
+ args.gpu = float(gpu)
95
+ except Exception:
96
+ pass
97
+ return args.func(args)
98
+
99
+
100
+ if __name__ == "__main__":
101
+ raise SystemExit(main())
@@ -0,0 +1,115 @@
1
+ """`evalsanity` — is your benchmark lying to you?
2
+
3
+ Cheap, deterministic checks that catch the usual ways an eval number is
4
+ inflated or meaningless BEFORE you trust it:
5
+ - train/test contamination (exact + near-dup via shingled Jaccard)
6
+ - degenerate data (duplicate rows, near-constant labels)
7
+ - degenerate predictions (model emits one answer, or copies the input)
8
+ - too-good-to-be-true accuracy on a tiny / imbalanced set
9
+ No model required. Works on lists of strings.
10
+ """
11
+ from __future__ import annotations
12
+ from dataclasses import dataclass, field
13
+ import re
14
+
15
+
16
+ def _shingles(text: str, k: int = 5) -> set[str]:
17
+ toks = re.findall(r"\w+", text.lower())
18
+ if len(toks) < k:
19
+ return {" ".join(toks)} if toks else set()
20
+ return {" ".join(toks[i:i + k]) for i in range(len(toks) - k + 1)}
21
+
22
+
23
+ def _jaccard(a: set[str], b: set[str]) -> float:
24
+ if not a or not b:
25
+ return 0.0
26
+ return len(a & b) / len(a | b)
27
+
28
+
29
+ @dataclass
30
+ class SanityReport:
31
+ n_test: int
32
+ flags: list[tuple[str, str]] = field(default_factory=list) # (severity, msg)
33
+
34
+ @property
35
+ def clean(self) -> bool:
36
+ return not any(s == "HIGH" for s, _ in self.flags)
37
+
38
+ def report(self) -> str:
39
+ if not self.flags:
40
+ return f"{self.n_test} test items: no sanity problems found."
41
+ lines = [f"{self.n_test} test items: {len(self.flags)} flag(s)"]
42
+ for sev, msg in sorted(self.flags, key=lambda x: {"HIGH": 0, "MED": 1,
43
+ "LOW": 2}[x[0]]):
44
+ lines.append(f" [{sev:4s}] {msg}")
45
+ if not self.clean:
46
+ lines.append(" -> at least one HIGH flag: do NOT trust this number yet.")
47
+ return "\n".join(lines)
48
+
49
+
50
+ def check(test_texts: list[str],
51
+ train_texts: list[str] | None = None,
52
+ labels: list | None = None,
53
+ predictions: list | None = None,
54
+ near_dup_threshold: float = 0.8) -> SanityReport:
55
+ flags: list[tuple[str, str]] = []
56
+ n = len(test_texts)
57
+
58
+ # ---- contamination ----
59
+ if train_texts:
60
+ train_set = set(t.strip() for t in train_texts)
61
+ exact = sum(1 for t in test_texts if t.strip() in train_set)
62
+ if exact:
63
+ flags.append(("HIGH", f"{exact}/{n} test items appear VERBATIM in "
64
+ f"train -> contamination"))
65
+ # near-dup (sampled to stay cheap)
66
+ tr_sh = [_shingles(t) for t in train_texts[:2000]]
67
+ near = 0
68
+ for t in test_texts[:2000]:
69
+ sh = _shingles(t)
70
+ if any(_jaccard(sh, trs) >= near_dup_threshold for trs in tr_sh):
71
+ near += 1
72
+ if near > exact:
73
+ flags.append(("HIGH", f"{near}/{min(n,2000)} test items are NEAR "
74
+ f"-duplicates of train (Jaccard>="
75
+ f"{near_dup_threshold})"))
76
+
77
+ # ---- degenerate test data ----
78
+ uniq = len(set(test_texts))
79
+ if n and uniq < n:
80
+ flags.append(("MED", f"{n-uniq}/{n} duplicate test items"))
81
+ if labels is not None:
82
+ from collections import Counter
83
+ c = Counter(labels)
84
+ top, topn = c.most_common(1)[0]
85
+ frac = topn / len(labels)
86
+ if frac >= 0.95:
87
+ flags.append(("HIGH", f"labels {frac*100:.0f}% one class ({top!r}) "
88
+ f"-> accuracy is meaningless; use balanced metric"))
89
+ elif frac >= 0.8:
90
+ flags.append(("MED", f"labels {frac*100:.0f}% one class ({top!r}) "
91
+ f"-> imbalanced"))
92
+
93
+ # ---- degenerate predictions ----
94
+ if predictions is not None:
95
+ from collections import Counter
96
+ pc = Counter(predictions)
97
+ ptop, ptopn = pc.most_common(1)[0]
98
+ if ptopn / len(predictions) >= 0.95 and len(pc) > 1:
99
+ flags.append(("HIGH", f"predictions {100*ptopn/len(predictions):.0f}% "
100
+ f"a single value ({ptop!r}) -> model collapsed"))
101
+ elif len(pc) == 1:
102
+ flags.append(("HIGH", f"predictions are ALL the same value ({ptop!r})"))
103
+ # copying the input?
104
+ if len(predictions) == len(test_texts):
105
+ copied = sum(1 for p, t in zip(predictions, test_texts)
106
+ if isinstance(p, str) and p.strip() == t.strip())
107
+ if copied / len(predictions) >= 0.3:
108
+ flags.append(("MED", f"{copied}/{len(predictions)} predictions "
109
+ f"equal the input text -> possible copying"))
110
+
111
+ # ---- tiny set ----
112
+ if 0 < n < 30:
113
+ flags.append(("LOW", f"only {n} test items -> wide confidence interval; "
114
+ f"a few flips swing the score"))
115
+ return SanityReport(n, flags)