inferlast 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yuvraj Singh Bhadoria
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.
@@ -0,0 +1,211 @@
1
+ Metadata-Version: 2.4
2
+ Name: inferlast
3
+ Version: 0.1.0
4
+ Summary: CPU-first LLM inference optimizer: prove a real win on your laptop CPU before you pay to rent a GPU. Trustcheck catches any benchmark that is really just noise.
5
+ Author-email: Yuvraj Singh Bhadoria <yuvrajsinghbhado2030@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/YuvrajSinghBhadoria2/inferlast
8
+ Project-URL: Repository, https://github.com/YuvrajSinghBhadoria2/inferlast
9
+ Project-URL: Releases, https://github.com/YuvrajSinghBhadoria2/inferlast/releases
10
+ Keywords: llm,inference,optimization,cpu,quantization,benchmark,trustcheck
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: torch<2.5,>=2.2
22
+ Requires-Dist: transformers<4.47,>=4.40
23
+ Requires-Dist: numpy<2,>=1.26
24
+ Dynamic: license-file
25
+
26
+ <p align="center">
27
+ <br/>
28
+ <img src="https://img.shields.io/badge/status-Phase%201%20(CPU)-informational" alt="Phase 1 CPU"/>
29
+ <img src="https://img.shields.io/badge/tests-55%20passing-brightgreen" alt="tests"/>
30
+ <img src="https://img.shields.io/badge/license-MIT-blue" alt="license"/>
31
+ <img src="https://img.shields.io/badge/python-3.10%2F3.11%2F3.12-blue" alt="python"/>
32
+ <a href="https://github.com/YuvrajSinghBhadoria2/inferlast/releases"><img src="https://img.shields.io/github/v/release/YuvrajSinghBhadoria2/inferlast" alt="release"/></a>
33
+ </p>
34
+
35
+ <h1 align="center">inferlast</h1>
36
+ <p align="center"><b>Fine-tune-free, GPU-free inference:</b> prove a real optimization win on a plain laptop CPU <i>before</i> you ever pay to rent one.<br/>
37
+ Most demos tell you a change is <b>3x faster</b> — inferlast catches when that number is just machine noise.</p>
38
+
39
+ <p align="center">
40
+ <a href="#why">Why</a> · <a href="#30-second-try">30-second try</a> · <a href="#what-it-caught">What it caught on my machine</a> · <a href="#how-it-works">How it works</a> · <a href="#roadmap">Roadmap</a>
41
+ </p>
42
+
43
+ ---
44
+
45
+ ## Why
46
+
47
+ Most LLM-inference guides tell you what to do: *quantize to INT8, batch bigger, grab a GPU.* They don't tell you **whether it helps *your* model on *your* hardware** — and they quietly assume you can rent a GPU to find out.
48
+
49
+ inferlast is the opposite. It is **CPU-first by design**: it profiles and optimizes entirely on the CPU you already have, so **anyone can run it — no GPU, no cloud GPU bill, no CUDA install.** And when you *do* move to a GPU later, inferlast tells you honestly whether it was even worth it.
50
+
51
+ > **Profile → pick → apply → prove.** And if the measurement says an "obvious" optimization doesn't help, inferlast says so — instead of making you guess wrong.
52
+
53
+ This is the core of what inference engineers actually do: not "apply the standard thing," but **find where the time really goes and only ship changes that provably pay off** — cheaply enough that you don't need a GPU to do it.
54
+
55
+ > **Hardware scope — Phase 1 is CPU-only.** Built and measured on a 2019 Intel MacBook Pro 16" (i7-9750H, 6C/12T, 16 GB, no GPU). Findings are CPU-specific and stated as such; on GPU the same model is typically weight-bandwidth-bound, not overhead-bound, so results would differ.
56
+
57
+ The project is organized around one falsifiable claim — **a decision rule for when GPU spend is actually warranted, estimable from CPU-only measurement.** That thesis, its boundaries, and its frozen success test live in [`docs/RESEARCH-SPEC.md`](docs/RESEARCH-SPEC.md).
58
+
59
+ ## The honest insight it encodes
60
+
61
+ For tiny models on CPU, inferlast measures that **~98–99% of decode wall time is framework overhead, not model math**. So **blindly quantizing the weights will not speed up an overhead-bound model** — and inferlast *measures* that rather than pretending otherwise. That refusal-to-guess behavior is the whole point.
62
+
63
+ ## Install
64
+
65
+ ```bash
66
+ pip install inferlast # Python 3.10-3.12; CPU-first, no GPU/CUDA needed
67
+ ```
68
+
69
+ This installs the core (`import trustcheck`, `import gpucheck`, ...) and the
70
+ `inferlast` CLI. Or run straight from the repo:
71
+
72
+ ```bash
73
+ git clone https://github.com/YuvrajSinghBhadoria2/inferlast.git
74
+ pip install -r requirements.txt
75
+ ```
76
+
77
+ ## 30-second try
78
+
79
+ ```bash
80
+ pip install -r requirements.txt # torch CPU, transformers, pytest
81
+ python scripts/run_all.py --model Qwen/Qwen2.5-0.5B-Instruct
82
+ ```
83
+
84
+ One command. One report that tells you:
85
+
86
+ ```
87
+ Bottleneck: mlp 47% · attention 41% · norm 11% (~99% overhead on CPU)
88
+ Decode: ~1220 ms/token (0.8 tok/s)
89
+ Quantization: INT8 = 1.5x but shifts the answer → KEEP fp32 (not a win)
90
+ Batching: B=16 → ~48 tok/s total Pick B for your goal.
91
+ ```
92
+
93
+ And the part that catches the false wins — `trustcheck` on this repo's own
94
+ recorded evidence (the same config that reported 3.0x faster):
95
+
96
+ <p align="center">
97
+ <img src="assets/trustcheck-proof.png" alt="trustcheck catches a false win" width="640"/>
98
+ </p>
99
+
100
+ Run the individual stages to look closer:
101
+
102
+ ```bash
103
+ python scripts/bench.py --model <m> # prefill profile
104
+ python scripts/bench.py --model <m> --decode # decode / per-token latency
105
+ python scripts/bench.py --model <m> --quant # auto-quantization verdict
106
+ python scripts/bench.py --model <m> --batch # latency vs throughput sweep
107
+ python scripts/bench.py --model <m> --trustcheck # is that win real? (see below)
108
+ python scripts/bench.py --model <m> --gpucheck # do you even need a GPU? (see below)
109
+ ```
110
+
111
+ ## What it caught on my machine
112
+
113
+ A quiet, annoying truth that most optimization tutorials skip: **on an overhead-bound CPU model, INT8 quantization is not a free win.** inferlast measured it three ways and told the truth:
114
+
115
+ | | fp32 | INT8 | verdict |
116
+ |---|---|---|---|
117
+ | speed (Qwen 0.5B) | 1.31 s/tok | 0.87 s/tok | 1.5x — *but* |
118
+ | quality (logit-cosine) | — | 0.68 | preserved-ish |
119
+ | quality (top-5 overlap) | — | **0.15** | **token ranking shifted** |
120
+
121
+ INT8 was faster but disturbed what the model would actually say. inferlast's call: **KEEP fp32.** That's not a bug — it's the tool doing its job: *refuse to recommend a change that isn't a real win.*
122
+
123
+ The full measured records are in `benchmarks/` (JSON + markdown), published as measured — the failures included.
124
+
125
+ ## How it works
126
+
127
+ Six small modules, one job each:
128
+
129
+ | Module | Job |
130
+ |---|---|
131
+ | `src/profiler.py` | Per-category (attention / mlp / norm / embed / head) wall-clock profile, and the overhead-vs-weight split. |
132
+ | `src/quantize.py` | Auto-quantization (INT8 dynamic). Measures fp32 vs INT8 averaged over repeats, with a **robust quality metric** (per-token logit cosine + top-5 overlap) — not brittle greedy-token identity. |
133
+ | `src/batcher.py` | Latency-vs-throughput sweep over batch size, with a best-batch picker. |
134
+ | `src/trustcheck.py` | **Is that 'win' worth trusting?** Audits any before/after benchmark for the three ways it lies: single-run noise, a brittle/wrong metric, and a "validated, documented, read by nothing" knob. Returns a REAL / MARGINAL / FALSE verdict. |
135
+ | `src/gpucheck.py` | **Do you even need a GPU?** Estimates, from CPU-only measurements, whether GPU spend would actually beat the best-scheduled CPU config. Returns GPU-warranted / CPU-suffices / insufficient-data — and refuses to guess when it can't tell. |
136
+ | `src/auto_optimizer.py` | Orchestrator: runs all four, emits a combined proof report + JSON. |
137
+ | `scripts/` | `run_all.py` (one command) + `bench.py` (per stage). |
138
+
139
+ A key design decision: `run_all.py` **always persists** a canonical report, so the evidence on disk always matches the latest run — it can't go stale.
140
+
141
+ ## gpucheck — the part that stops you overspending on hardware
142
+
143
+ The whole project is built around one falsifiable claim (in
144
+ [`docs/RESEARCH-SPEC.md`](docs/RESEARCH-SPEC.md)): **for overhead-bound small models on CPU, you
145
+ usually don't need a GPU at all.** `gpucheck` puts that to the test from local CPU measurement:
146
+
147
+ ```bash
148
+ python scripts/bench.py --model <m> \
149
+ --gpucheck --num-params 0.5e9 --overhead-fraction 0.98 --latency-target-ms 2000
150
+ ```
151
+
152
+ It labels the decision — **GPU-warranted / CPU-suffices / insufficient-data** — with the exact inputs
153
+ and reasoning, and it **refuses to guess** (returns `insufficient-data`) whenever it genuinely cannot
154
+ tell from the data you gave it. That refusal is a feature: it never sells you a GPU rental it can't
155
+ defend.
156
+
157
+ ## trustcheck — the part that tells you your benchmark lied
158
+
159
+ Most tools *produce* a number. `trustcheck` tells you whether to believe it. It caught all three lies live on this repo's own evidence:
160
+
161
+ - **Single-run noise.** The same INT8-vs-fp32 config, measured twice, gave `3.0x` faster in one session and `0.65x` **slower** in another. `trustcheck` computes the confidence interval and says: *"CI [-1.8x, 5.5x] straddles 1.0x → not a reliable win."* A naive dashboard would have reported `3.0x`.
162
+ - **Brittle metric.** A quality comparison at greedy-token level, with no logits, is flagged as "can lie" and, when logits are available, re-measured with logit cosine + top-5 overlap.
163
+ - **Read-by-nothing knob.** A flag that is documented/validated but never read by any code is a silent bug (the class of bug Soup and vLLM chased); `trustcheck`'s static pass flags it.
164
+
165
+ ```bash
166
+ python scripts/bench.py --trustcheck \
167
+ --controls 1180 1316 --treatments 390 2031 # audit two real sessions
168
+ python scripts/bench.py --model <m> --trustcheck \
169
+ --collect-repeats 2 --config-key stream_layers # measure the noise band live
170
+ ```
171
+
172
+ ## Tests
173
+
174
+ ```bash
175
+ pip install pytest
176
+ python -m pytest # 55 fast tests, no model downloads
177
+ ```
178
+
179
+ The suite guards the things that would sink a tool like this: profiler categorisation & no-double-counting, the robust INT8 quality metric + the honest decision rule, batcher best-batch selection, the `trustcheck` noise/brittle-metric/read-by-nothing logic, the `gpucheck` GPU-necessity decision rule (including its refusal to guess when data is missing), and a **regression test that the report always emits the new metrics — never a stale one.**
180
+
181
+ ## Roadmap
182
+
183
+ Phase 1 is done and runs on a plain laptop CPU — no GPU needed.
184
+
185
+ **Shipped:**
186
+
187
+ - **`trustcheck` — the false-win catcher.** Tells you the benchmark you were
188
+ about to publish is machine noise, not a win. It caught *this repo's own*
189
+ 3.0x-vs-0.65x as noise.
190
+ - **`gpucheck` — "do you even need a GPU?"** A CPU-only decision rule:
191
+ GPU-warranted / CPU-suffices / insufficient-data. It refuses to guess when it
192
+ can't tell.
193
+ - Bottleneck / decode / quant / batch selection.
194
+
195
+ **Where we want help next** (strongest help first):
196
+
197
+ - **Validate the `gpucheck` boundary on hardware we can't reach.** Your numbers
198
+ ship behind an honest "requires <hardware>" gate — a 4 GB card, an Apple
199
+ Silicon box, a desktop with more RAM. Proof on hardware we lack turns a claim
200
+ into a finding. *This is the most valuable way to contribute right now.*
201
+ - Quantization beyond INT8 (FP4 / INT4), latency percentiles (p50/p99), memory /
202
+ KV-cache footprint, and serving-engine integration (vLLM / llama.cpp) as an
203
+ enrichment layer.
204
+
205
+ PRs welcome. Nothing here is a live claim — it's the plan.
206
+
207
+ ## License
208
+
209
+ MIT — free, stays free, built in the open. If inferlast saved you a guessing session, a star helps others find it.
210
+
211
+ <sub>Not a replacement for vLLM / llama.cpp — a *decision layer* that runs on CPU and tells you which setting is right for your model and hardware, with proof, before you spend on a GPU.</sub>
@@ -0,0 +1,186 @@
1
+ <p align="center">
2
+ <br/>
3
+ <img src="https://img.shields.io/badge/status-Phase%201%20(CPU)-informational" alt="Phase 1 CPU"/>
4
+ <img src="https://img.shields.io/badge/tests-55%20passing-brightgreen" alt="tests"/>
5
+ <img src="https://img.shields.io/badge/license-MIT-blue" alt="license"/>
6
+ <img src="https://img.shields.io/badge/python-3.10%2F3.11%2F3.12-blue" alt="python"/>
7
+ <a href="https://github.com/YuvrajSinghBhadoria2/inferlast/releases"><img src="https://img.shields.io/github/v/release/YuvrajSinghBhadoria2/inferlast" alt="release"/></a>
8
+ </p>
9
+
10
+ <h1 align="center">inferlast</h1>
11
+ <p align="center"><b>Fine-tune-free, GPU-free inference:</b> prove a real optimization win on a plain laptop CPU <i>before</i> you ever pay to rent one.<br/>
12
+ Most demos tell you a change is <b>3x faster</b> — inferlast catches when that number is just machine noise.</p>
13
+
14
+ <p align="center">
15
+ <a href="#why">Why</a> · <a href="#30-second-try">30-second try</a> · <a href="#what-it-caught">What it caught on my machine</a> · <a href="#how-it-works">How it works</a> · <a href="#roadmap">Roadmap</a>
16
+ </p>
17
+
18
+ ---
19
+
20
+ ## Why
21
+
22
+ Most LLM-inference guides tell you what to do: *quantize to INT8, batch bigger, grab a GPU.* They don't tell you **whether it helps *your* model on *your* hardware** — and they quietly assume you can rent a GPU to find out.
23
+
24
+ inferlast is the opposite. It is **CPU-first by design**: it profiles and optimizes entirely on the CPU you already have, so **anyone can run it — no GPU, no cloud GPU bill, no CUDA install.** And when you *do* move to a GPU later, inferlast tells you honestly whether it was even worth it.
25
+
26
+ > **Profile → pick → apply → prove.** And if the measurement says an "obvious" optimization doesn't help, inferlast says so — instead of making you guess wrong.
27
+
28
+ This is the core of what inference engineers actually do: not "apply the standard thing," but **find where the time really goes and only ship changes that provably pay off** — cheaply enough that you don't need a GPU to do it.
29
+
30
+ > **Hardware scope — Phase 1 is CPU-only.** Built and measured on a 2019 Intel MacBook Pro 16" (i7-9750H, 6C/12T, 16 GB, no GPU). Findings are CPU-specific and stated as such; on GPU the same model is typically weight-bandwidth-bound, not overhead-bound, so results would differ.
31
+
32
+ The project is organized around one falsifiable claim — **a decision rule for when GPU spend is actually warranted, estimable from CPU-only measurement.** That thesis, its boundaries, and its frozen success test live in [`docs/RESEARCH-SPEC.md`](docs/RESEARCH-SPEC.md).
33
+
34
+ ## The honest insight it encodes
35
+
36
+ For tiny models on CPU, inferlast measures that **~98–99% of decode wall time is framework overhead, not model math**. So **blindly quantizing the weights will not speed up an overhead-bound model** — and inferlast *measures* that rather than pretending otherwise. That refusal-to-guess behavior is the whole point.
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ pip install inferlast # Python 3.10-3.12; CPU-first, no GPU/CUDA needed
42
+ ```
43
+
44
+ This installs the core (`import trustcheck`, `import gpucheck`, ...) and the
45
+ `inferlast` CLI. Or run straight from the repo:
46
+
47
+ ```bash
48
+ git clone https://github.com/YuvrajSinghBhadoria2/inferlast.git
49
+ pip install -r requirements.txt
50
+ ```
51
+
52
+ ## 30-second try
53
+
54
+ ```bash
55
+ pip install -r requirements.txt # torch CPU, transformers, pytest
56
+ python scripts/run_all.py --model Qwen/Qwen2.5-0.5B-Instruct
57
+ ```
58
+
59
+ One command. One report that tells you:
60
+
61
+ ```
62
+ Bottleneck: mlp 47% · attention 41% · norm 11% (~99% overhead on CPU)
63
+ Decode: ~1220 ms/token (0.8 tok/s)
64
+ Quantization: INT8 = 1.5x but shifts the answer → KEEP fp32 (not a win)
65
+ Batching: B=16 → ~48 tok/s total Pick B for your goal.
66
+ ```
67
+
68
+ And the part that catches the false wins — `trustcheck` on this repo's own
69
+ recorded evidence (the same config that reported 3.0x faster):
70
+
71
+ <p align="center">
72
+ <img src="assets/trustcheck-proof.png" alt="trustcheck catches a false win" width="640"/>
73
+ </p>
74
+
75
+ Run the individual stages to look closer:
76
+
77
+ ```bash
78
+ python scripts/bench.py --model <m> # prefill profile
79
+ python scripts/bench.py --model <m> --decode # decode / per-token latency
80
+ python scripts/bench.py --model <m> --quant # auto-quantization verdict
81
+ python scripts/bench.py --model <m> --batch # latency vs throughput sweep
82
+ python scripts/bench.py --model <m> --trustcheck # is that win real? (see below)
83
+ python scripts/bench.py --model <m> --gpucheck # do you even need a GPU? (see below)
84
+ ```
85
+
86
+ ## What it caught on my machine
87
+
88
+ A quiet, annoying truth that most optimization tutorials skip: **on an overhead-bound CPU model, INT8 quantization is not a free win.** inferlast measured it three ways and told the truth:
89
+
90
+ | | fp32 | INT8 | verdict |
91
+ |---|---|---|---|
92
+ | speed (Qwen 0.5B) | 1.31 s/tok | 0.87 s/tok | 1.5x — *but* |
93
+ | quality (logit-cosine) | — | 0.68 | preserved-ish |
94
+ | quality (top-5 overlap) | — | **0.15** | **token ranking shifted** |
95
+
96
+ INT8 was faster but disturbed what the model would actually say. inferlast's call: **KEEP fp32.** That's not a bug — it's the tool doing its job: *refuse to recommend a change that isn't a real win.*
97
+
98
+ The full measured records are in `benchmarks/` (JSON + markdown), published as measured — the failures included.
99
+
100
+ ## How it works
101
+
102
+ Six small modules, one job each:
103
+
104
+ | Module | Job |
105
+ |---|---|
106
+ | `src/profiler.py` | Per-category (attention / mlp / norm / embed / head) wall-clock profile, and the overhead-vs-weight split. |
107
+ | `src/quantize.py` | Auto-quantization (INT8 dynamic). Measures fp32 vs INT8 averaged over repeats, with a **robust quality metric** (per-token logit cosine + top-5 overlap) — not brittle greedy-token identity. |
108
+ | `src/batcher.py` | Latency-vs-throughput sweep over batch size, with a best-batch picker. |
109
+ | `src/trustcheck.py` | **Is that 'win' worth trusting?** Audits any before/after benchmark for the three ways it lies: single-run noise, a brittle/wrong metric, and a "validated, documented, read by nothing" knob. Returns a REAL / MARGINAL / FALSE verdict. |
110
+ | `src/gpucheck.py` | **Do you even need a GPU?** Estimates, from CPU-only measurements, whether GPU spend would actually beat the best-scheduled CPU config. Returns GPU-warranted / CPU-suffices / insufficient-data — and refuses to guess when it can't tell. |
111
+ | `src/auto_optimizer.py` | Orchestrator: runs all four, emits a combined proof report + JSON. |
112
+ | `scripts/` | `run_all.py` (one command) + `bench.py` (per stage). |
113
+
114
+ A key design decision: `run_all.py` **always persists** a canonical report, so the evidence on disk always matches the latest run — it can't go stale.
115
+
116
+ ## gpucheck — the part that stops you overspending on hardware
117
+
118
+ The whole project is built around one falsifiable claim (in
119
+ [`docs/RESEARCH-SPEC.md`](docs/RESEARCH-SPEC.md)): **for overhead-bound small models on CPU, you
120
+ usually don't need a GPU at all.** `gpucheck` puts that to the test from local CPU measurement:
121
+
122
+ ```bash
123
+ python scripts/bench.py --model <m> \
124
+ --gpucheck --num-params 0.5e9 --overhead-fraction 0.98 --latency-target-ms 2000
125
+ ```
126
+
127
+ It labels the decision — **GPU-warranted / CPU-suffices / insufficient-data** — with the exact inputs
128
+ and reasoning, and it **refuses to guess** (returns `insufficient-data`) whenever it genuinely cannot
129
+ tell from the data you gave it. That refusal is a feature: it never sells you a GPU rental it can't
130
+ defend.
131
+
132
+ ## trustcheck — the part that tells you your benchmark lied
133
+
134
+ Most tools *produce* a number. `trustcheck` tells you whether to believe it. It caught all three lies live on this repo's own evidence:
135
+
136
+ - **Single-run noise.** The same INT8-vs-fp32 config, measured twice, gave `3.0x` faster in one session and `0.65x` **slower** in another. `trustcheck` computes the confidence interval and says: *"CI [-1.8x, 5.5x] straddles 1.0x → not a reliable win."* A naive dashboard would have reported `3.0x`.
137
+ - **Brittle metric.** A quality comparison at greedy-token level, with no logits, is flagged as "can lie" and, when logits are available, re-measured with logit cosine + top-5 overlap.
138
+ - **Read-by-nothing knob.** A flag that is documented/validated but never read by any code is a silent bug (the class of bug Soup and vLLM chased); `trustcheck`'s static pass flags it.
139
+
140
+ ```bash
141
+ python scripts/bench.py --trustcheck \
142
+ --controls 1180 1316 --treatments 390 2031 # audit two real sessions
143
+ python scripts/bench.py --model <m> --trustcheck \
144
+ --collect-repeats 2 --config-key stream_layers # measure the noise band live
145
+ ```
146
+
147
+ ## Tests
148
+
149
+ ```bash
150
+ pip install pytest
151
+ python -m pytest # 55 fast tests, no model downloads
152
+ ```
153
+
154
+ The suite guards the things that would sink a tool like this: profiler categorisation & no-double-counting, the robust INT8 quality metric + the honest decision rule, batcher best-batch selection, the `trustcheck` noise/brittle-metric/read-by-nothing logic, the `gpucheck` GPU-necessity decision rule (including its refusal to guess when data is missing), and a **regression test that the report always emits the new metrics — never a stale one.**
155
+
156
+ ## Roadmap
157
+
158
+ Phase 1 is done and runs on a plain laptop CPU — no GPU needed.
159
+
160
+ **Shipped:**
161
+
162
+ - **`trustcheck` — the false-win catcher.** Tells you the benchmark you were
163
+ about to publish is machine noise, not a win. It caught *this repo's own*
164
+ 3.0x-vs-0.65x as noise.
165
+ - **`gpucheck` — "do you even need a GPU?"** A CPU-only decision rule:
166
+ GPU-warranted / CPU-suffices / insufficient-data. It refuses to guess when it
167
+ can't tell.
168
+ - Bottleneck / decode / quant / batch selection.
169
+
170
+ **Where we want help next** (strongest help first):
171
+
172
+ - **Validate the `gpucheck` boundary on hardware we can't reach.** Your numbers
173
+ ship behind an honest "requires <hardware>" gate — a 4 GB card, an Apple
174
+ Silicon box, a desktop with more RAM. Proof on hardware we lack turns a claim
175
+ into a finding. *This is the most valuable way to contribute right now.*
176
+ - Quantization beyond INT8 (FP4 / INT4), latency percentiles (p50/p99), memory /
177
+ KV-cache footprint, and serving-engine integration (vLLM / llama.cpp) as an
178
+ enrichment layer.
179
+
180
+ PRs welcome. Nothing here is a live claim — it's the plan.
181
+
182
+ ## License
183
+
184
+ MIT — free, stays free, built in the open. If inferlast saved you a guessing session, a star helps others find it.
185
+
186
+ <sub>Not a replacement for vLLM / llama.cpp — a *decision layer* that runs on CPU and tells you which setting is right for your model and hardware, with proof, before you spend on a GPU.</sub>
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "inferlast"
7
+ version = "0.1.0"
8
+ description = "CPU-first LLM inference optimizer: prove a real win on your laptop CPU before you pay to rent a GPU. Trustcheck catches any benchmark that is really just noise."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Yuvraj Singh Bhadoria", email = "yuvrajsinghbhado2030@gmail.com" }]
13
+ keywords = ["llm", "inference", "optimization", "cpu", "quantization", "benchmark", "trustcheck"]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.10",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: OS Independent",
21
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
22
+ ]
23
+ dependencies = [
24
+ "torch>=2.2,<2.5",
25
+ "transformers>=4.40,<4.47",
26
+ "numpy>=1.26,<2",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/YuvrajSinghBhadoria2/inferlast"
31
+ Repository = "https://github.com/YuvrajSinghBhadoria2/inferlast"
32
+ Releases = "https://github.com/YuvrajSinghBhadoria2/inferlast/releases"
33
+
34
+ [project.scripts]
35
+ inferlast = "cli:main"
36
+
37
+ [tool.setuptools]
38
+ # The src/ modules are intentionally top-level importables (import trustcheck,
39
+ # import gpucheck, ...) — identical to how the scripts and tests already use them.
40
+ package-dir = { "" = "src" }
41
+ py-modules = [
42
+ "auto_optimizer",
43
+ "batcher",
44
+ "cli",
45
+ "gpucheck",
46
+ "profiler",
47
+ "quantize",
48
+ "trustcheck",
49
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,159 @@
1
+ """Auto-Optimizer orchestrator: one command -> optimized config + proof report.
2
+
3
+ Runs all four measurements on a given model and emits:
4
+ * profile (prefill per-category bottleneck + overhead-vs-weight fraction)
5
+ * decode (ms/token, tok/s)
6
+ * auto-quantization (fp32 vs int8 BEFORE/AFTER + honest verdict)
7
+ * auto-batching (latency vs throughput sweep + best batch for a goal)
8
+
9
+ Returns one dict with every result plus a human-readable proof report string.
10
+ Everything is measured on the caller's hardware (CPU here). The whole point:
11
+ refuse to recommend a change that doesn't provably help.
12
+ """
13
+
14
+ from __future__ import annotations
15
+ import time
16
+
17
+ import torch
18
+
19
+ from profiler import profile_forward, profile_decode
20
+ from quantize import auto_quantize
21
+ from batcher import bench_batch, best_batch
22
+
23
+
24
+ def _overhead_frac(pr) -> float:
25
+ """fraction of wall time NOT accounted for by leaf matmul spans."""
26
+ leaf = sum(pr.cat_times.values())
27
+ return 1.0 - (leaf / max(pr.total_time, 1e-9))
28
+
29
+
30
+ def run_auto_optimizer(
31
+ model,
32
+ tok,
33
+ prompt: str = "The capital of France is the city of",
34
+ n_new: int = 12,
35
+ batches=(1, 2, 4, 8, 16),
36
+ batch_goal: str = "throughput",
37
+ quant_speedup_floor: float = 1.05,
38
+ quant_quality_floor: float = 0.65,
39
+ quant_top5_floor: float = 0.60,
40
+ ) -> tuple[dict, str]:
41
+ out: dict = {
42
+ "prompt": prompt,
43
+ "hardware": "CPU (2019 MacBook Pro 16in i7-9750H, 16GB)",
44
+ "model_untouched": True,
45
+ }
46
+
47
+ # 1 & 2: bottleneck profile (prefill) + overhead fraction
48
+ t0 = time.time()
49
+ ids = tok([prompt], return_tensors="pt").input_ids
50
+ pr_pre = profile_forward(model, ids, run_repeats=2, warmup=1)
51
+ out["prefill"] = {
52
+ "wall_total_s": pr_pre.total_time,
53
+ "tokens": pr_pre.tokens,
54
+ "tok_per_s": pr_pre.tokens / max(pr_pre.total_time, 1e-9),
55
+ "leaf_time_s": sum(pr_pre.cat_times.values()),
56
+ "pct_of_leaf": {
57
+ k: round(v / max(sum(pr_pre.cat_times.values()), 1e-9) * 100, 1)
58
+ for k, v in pr_pre.cat_times.items()
59
+ },
60
+ "overhead_fraction": round(_overhead_frac(pr_pre), 3),
61
+ }
62
+
63
+ # decode
64
+ pr_dec = profile_decode(model, ids, n_new_tokens=n_new,
65
+ run_repeats=1, warmup=1)
66
+ out["decode"] = {
67
+ "ms_per_token": pr_dec.total_time / n_new * 1000.0,
68
+ "tok_per_s": n_new / max(pr_dec.total_time, 1e-9),
69
+ "overhead_fraction": round(_overhead_frac(pr_dec), 3),
70
+ }
71
+
72
+ # quantization
73
+ qv = auto_quantize(model, tok, prompt=prompt, n_new=n_new,
74
+ speedup_floor=quant_speedup_floor,
75
+ quality_floor=quant_quality_floor,
76
+ top5_floor=quant_top5_floor)
77
+ out["quantization"] = {
78
+ "fp32_ms_per_token": qv.before["ms_per_token"],
79
+ "int8_ms_per_token": qv.after["ms_per_token"],
80
+ "speedup_int8_over_fp32": round(qv.speedup, 3),
81
+ "speedup_worst_repeat": round(qv.speedup_min, 3),
82
+ "logit_cosine": round(qv.logit_cosine, 3),
83
+ "top5_overlap": round(qv.top5_overlap, 3),
84
+ "recommended_int8": qv.recommended,
85
+ "reason": qv.reason,
86
+ }
87
+
88
+ # batching
89
+ rows = bench_batch(model, tok, prompt=prompt, batches=batches,
90
+ n_steps=1, warmup=1)
91
+ best = best_batch(rows, goal=batch_goal)
92
+ out["batching"] = {
93
+ "goal": batch_goal,
94
+ "rows": [
95
+ {"batch": r.batch, "wall_ms": r.wall_ms, "tok_per_s": r.tok_per_s,
96
+ "ms_per_req": r.ms_per_req, "tok_per_s_per_req": r.eff_tok_per_s_per_req}
97
+ for r in rows
98
+ ],
99
+ "best_batch": best.batch,
100
+ "best_tok_per_s": best.tok_per_s,
101
+ }
102
+ out["elapsed_s"] = round(time.time() - t0, 1)
103
+
104
+ report = _report(out)
105
+ return out, report
106
+
107
+
108
+ def _report(o: dict) -> str:
109
+ p = o["prefill"]
110
+ d = o["decode"]
111
+ q = o["quantization"]
112
+ b = o["batching"]
113
+ overhead_note = (
114
+ f"{p['overhead_fraction']*100:.0f}% of prefill wall time and "
115
+ f"{d['overhead_fraction']*100:.0f}% of decode wall time is framework "
116
+ "overhead on CPU. NOTE: this is CPU-specific; on a GPU the same model "
117
+ "(with fused kernels) is typically weight/compute-bound, so the picture "
118
+ "differs. Here it means: on CPU, tiny models are overhead-bound."
119
+ )
120
+ lines = [
121
+ "# Auto-Optimizer proof report",
122
+ f"hardware: {o['hardware']} prompt: {o['prompt']}",
123
+ "",
124
+ "## 1. Bottleneck (from profile)",
125
+ f"prefill throughput ~{p['tok_per_s']:.1f} tok/s; wall {p['wall_total_s']:.2f}s "
126
+ f"for {p['tokens']} tokens.",
127
+ "per-category (% of measured leaf work): " + ", ".join(
128
+ f"{k} {v}%" for k, v in sorted(p['pct_of_leaf'].items(),
129
+ key=lambda x: -x[1])
130
+ ),
131
+ f"overhead-vs-weight: {overhead_note}",
132
+ "",
133
+ "## 2. Decode",
134
+ f"{d['ms_per_token']:.0f} ms/token (~{d['tok_per_s']:.2f} tok/s). "
135
+ "Single-token serial, latency-sensitive.",
136
+ "",
137
+ "## 3. Quantization (fp32 vs int8, measured BEFORE/AFTER, averaged)",
138
+ f"fp32 {q['fp32_ms_per_token']:.0f} ms/tok -> int8 {q['int8_ms_per_token']:.0f} ms/tok "
139
+ f"= {q['speedup_int8_over_fp32']:.2f}x (worst repeat {q['speedup_worst_repeat']:.2f}x), "
140
+ f"quality logit-cos {q['logit_cosine']:.2f}, top-5 {q['top5_overlap']:.2f}.",
141
+ f"suggested: {'USE int8' if q['recommended_int8'] else 'KEEP fp32'} — {q['reason']}",
142
+ "",
143
+ "## 4. Batching (latency vs throughput)",
144
+ "B | wall(ms) | tok/s | ms/req | tok/s/req",
145
+ ]
146
+ for r in b["rows"]:
147
+ lines.append(
148
+ f"{r['batch']} | {r['wall_ms']:.0f} | {r['tok_per_s']:.1f} | "
149
+ f"{r['ms_per_req']:.0f} | {r['tok_per_s_per_req']:.2f}"
150
+ )
151
+ lines += [
152
+ f"best batch for goal='{b['goal']}': B={b['best_batch']} "
153
+ f"-> {b['best_tok_per_s']:.1f} tok/s total.",
154
+ "",
155
+ "## Honest takeaway",
156
+ "The model was never permanently modified; any optimization is advised "
157
+ "ONLY if it provably helps.",
158
+ ]
159
+ return "\n".join(lines)