ml-systems-lab 0.1.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.
Files changed (47) hide show
  1. ml_systems_lab-0.1.1/LICENSE +21 -0
  2. ml_systems_lab-0.1.1/PKG-INFO +258 -0
  3. ml_systems_lab-0.1.1/README.md +221 -0
  4. ml_systems_lab-0.1.1/pyproject.toml +56 -0
  5. ml_systems_lab-0.1.1/setup.cfg +4 -0
  6. ml_systems_lab-0.1.1/src/ml_systems_lab.egg-info/PKG-INFO +258 -0
  7. ml_systems_lab-0.1.1/src/ml_systems_lab.egg-info/SOURCES.txt +45 -0
  8. ml_systems_lab-0.1.1/src/ml_systems_lab.egg-info/dependency_links.txt +1 -0
  9. ml_systems_lab-0.1.1/src/ml_systems_lab.egg-info/entry_points.txt +2 -0
  10. ml_systems_lab-0.1.1/src/ml_systems_lab.egg-info/requires.txt +9 -0
  11. ml_systems_lab-0.1.1/src/ml_systems_lab.egg-info/top_level.txt +1 -0
  12. ml_systems_lab-0.1.1/src/mlsyslab/__init__.py +19 -0
  13. ml_systems_lab-0.1.1/src/mlsyslab/__main__.py +4 -0
  14. ml_systems_lab-0.1.1/src/mlsyslab/agent.py +388 -0
  15. ml_systems_lab-0.1.1/src/mlsyslab/analysis/__init__.py +66 -0
  16. ml_systems_lab-0.1.1/src/mlsyslab/analysis/dataset.py +279 -0
  17. ml_systems_lab-0.1.1/src/mlsyslab/analysis/plots.py +392 -0
  18. ml_systems_lab-0.1.1/src/mlsyslab/analysis/tables.py +320 -0
  19. ml_systems_lab-0.1.1/src/mlsyslab/backends/__init__.py +44 -0
  20. ml_systems_lab-0.1.1/src/mlsyslab/backends/base.py +105 -0
  21. ml_systems_lab-0.1.1/src/mlsyslab/backends/llamacpp.py +396 -0
  22. ml_systems_lab-0.1.1/src/mlsyslab/backends/onnxrt.py +208 -0
  23. ml_systems_lab-0.1.1/src/mlsyslab/bench_onnx.py +177 -0
  24. ml_systems_lab-0.1.1/src/mlsyslab/cli.py +396 -0
  25. ml_systems_lab-0.1.1/src/mlsyslab/config.py +259 -0
  26. ml_systems_lab-0.1.1/src/mlsyslab/devices/__init__.py +37 -0
  27. ml_systems_lab-0.1.1/src/mlsyslab/devices/base.py +139 -0
  28. ml_systems_lab-0.1.1/src/mlsyslab/devices/local.py +83 -0
  29. ml_systems_lab-0.1.1/src/mlsyslab/devices/ssh.py +179 -0
  30. ml_systems_lab-0.1.1/src/mlsyslab/membw.py +182 -0
  31. ml_systems_lab-0.1.1/src/mlsyslab/runner.py +252 -0
  32. ml_systems_lab-0.1.1/src/mlsyslab/schema.py +288 -0
  33. ml_systems_lab-0.1.1/src/mlsyslab/sysinfo.py +272 -0
  34. ml_systems_lab-0.1.1/src/mlsyslab/telemetry/__init__.py +183 -0
  35. ml_systems_lab-0.1.1/src/mlsyslab/telemetry/cpu.py +126 -0
  36. ml_systems_lab-0.1.1/src/mlsyslab/telemetry/dvfs.py +91 -0
  37. ml_systems_lab-0.1.1/src/mlsyslab/telemetry/power.py +162 -0
  38. ml_systems_lab-0.1.1/src/mlsyslab/telemetry/thermal.py +98 -0
  39. ml_systems_lab-0.1.1/tests/test_agent_and_devices.py +88 -0
  40. ml_systems_lab-0.1.1/tests/test_analysis.py +113 -0
  41. ml_systems_lab-0.1.1/tests/test_config.py +94 -0
  42. ml_systems_lab-0.1.1/tests/test_llamacpp_backend.py +124 -0
  43. ml_systems_lab-0.1.1/tests/test_membw_and_cli.py +74 -0
  44. ml_systems_lab-0.1.1/tests/test_onnx_backend.py +88 -0
  45. ml_systems_lab-0.1.1/tests/test_runner.py +117 -0
  46. ml_systems_lab-0.1.1/tests/test_schema.py +68 -0
  47. ml_systems_lab-0.1.1/tests/test_telemetry.py +79 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Manu Nicholas Jacob
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,258 @@
1
+ Metadata-Version: 2.4
2
+ Name: ml-systems-lab
3
+ Version: 0.1.1
4
+ Summary: A reproducible benchmarking framework for ML inference across laptops, single-board computers and servers.
5
+ Author: Manu Nicholas Jacob
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/manunicholasjacob/ml-systems-lab
8
+ Project-URL: Issues, https://github.com/manunicholasjacob/ml-systems-lab/issues
9
+ Keywords: benchmark,inference,llama.cpp,onnxruntime,edge-ai,raspberry-pi,energy,latency,ttft,quantization,reproducibility
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Operating System :: MacOS
17
+ Classifier: Operating System :: Microsoft :: Windows
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Scientific/Engineering
25
+ Classifier: Topic :: System :: Benchmark
26
+ Requires-Python: >=3.9
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: numpy>=1.20
30
+ Requires-Dist: matplotlib>=3.4
31
+ Requires-Dist: PyYAML>=5.4
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest>=7; extra == "dev"
34
+ Provides-Extra: onnx
35
+ Requires-Dist: onnxruntime>=1.15; extra == "onnx"
36
+ Dynamic: license-file
37
+
38
+ # ML Systems Lab
39
+
40
+ [![tests](https://github.com/manunicholasjacob/ml-systems-lab/actions/workflows/ci.yml/badge.svg)](https://github.com/manunicholasjacob/ml-systems-lab/actions/workflows/ci.yml)
41
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.21867055.svg)](https://doi.org/10.5281/zenodo.21867055)
42
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
43
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue.svg)](pyproject.toml)
44
+
45
+ A reproducible benchmarking framework for ML inference across heterogeneous hardware:
46
+ laptops, single-board computers, and servers, from one config file and one command.
47
+
48
+ ```
49
+ mlsys run configs/example-smoke.yaml
50
+ mlsys report runs/smoke-test --full
51
+ ```
52
+
53
+ Every run produces a self-describing JSON record carrying the hardware, OS, kernel,
54
+ compiler, backend version, model, quantization, the measured metrics, and the physical
55
+ state of the machine while they were measured (power, temperature, clocks, throttle
56
+ flags, CPU utilization). The analysis layer turns a directory of records into
57
+ publication-quality tables (text, Markdown, LaTeX/booktabs) and figures.
58
+
59
+ Built and used for a real research program: the records under `results/paper12/` are the
60
+ measurements behind an IEEE Transactions on Computers submission, and the framework
61
+ reproduces that paper's published roofline fits exactly (Pi 5: 10.7 GB/s effective,
62
+ R^2 = 0.980; i7-12700H: 35.7 GB/s, R^2 = 0.980). Two campaigns run natively by this
63
+ framework then re-measured the same quantities independently and agreed within 1.6%.
64
+
65
+ ![roofline](results/combined-report/fig_roofline.png)
66
+
67
+ *Every point is a different model or quantization; every line is one device's effective
68
+ memory bandwidth. Two independent Pi 5 campaigns (weeks apart, different harnesses) and
69
+ two laptop campaigns land on top of each other: decode throughput is model bytes divided
70
+ by one number per device.*
71
+
72
+ ## What it measures
73
+
74
+ | Metric | How |
75
+ |---|---|
76
+ | TTFT (time to first token) | streaming request against `llama-server`, first-chunk timing |
77
+ | Prefill / decode throughput | `llama-bench`, parsed from its JSON output |
78
+ | End-to-end request latency | same streaming path, submit to last token |
79
+ | Single-inference latency (mean, p50/p95/p99) | ONNX Runtime, timed in-process on the device |
80
+ | Memory | peak RSS, plus free-memory and swap state around every run |
81
+ | CPU utilization | `/proc/stat` deltas (Linux), `GetSystemTimes` (Windows), per-core where available |
82
+ | Temperature and throttling | sysfs thermal zones, Pi throttle bitmask, in the record not a side file |
83
+ | Power and energy | Raspberry Pi PMIC per-rail (core vs DRAM split), Intel RAPL where present |
84
+ | Derived | decode bandwidth, roofline utilization %, energy per token |
85
+
86
+ Anything a platform cannot measure is reported as absent, never as zero.
87
+
88
+ ## Devices exercised
89
+
90
+ | Device | Route | Notes |
91
+ |---|---|---|
92
+ | Raspberry Pi 5 (2 GB, Cortex-A76) | SSH agent | PMIC per-rail power, throttle bits, DVFS control |
93
+ | i7-12700H laptop (Windows) | local agent | 20-thread sweeps, up to 7B models |
94
+ | RTX 3050 (same laptop) | ONNX Runtime DirectML | modeled as its own device; at batch 1 the GPU loses to the CPU (12.3 ms vs 3.0 ms, dispatch overhead), at batch 64 it wins 29x (9,885 vs 338 inf/s), and both facts come out of the same config file |
95
+
96
+ A new machine is a config block, not code: `host`, an SSH key, and the paths to its
97
+ models. A new accelerator is a device entry pointing at an interpreter whose ONNX
98
+ Runtime carries the right execution provider.
99
+
100
+ ## Design
101
+
102
+ ```
103
+ config.yaml ──> RunSpecs ──> Device ──> agent (on the device) ──> RunRecord ──> analysis
104
+
105
+ ├── LocalDevice (this machine, agent as subprocess)
106
+ └── SSHDevice (agent pushed over SSH, runs remotely)
107
+ ```
108
+
109
+ * **The agent runs on the device under test**, so the benchmark and the telemetry
110
+ sampler are colocated; nothing crosses the network inside a measurement window.
111
+ It is pure standard library and is copied, not installed.
112
+ * **Backends** (`llamacpp`, `onnxruntime`) turn one spec into one task and parse one
113
+ result. The agent returns raw output; parsing happens on the host, so a parser bug is
114
+ fixed by re-parsing stored output rather than re-running a campaign.
115
+ * **Run ids are deterministic** over the spec, so an interrupted campaign resumes by
116
+ skipping what is already on disk (`--no-resume` to override). Failures are records
117
+ too, with the error and the raw output preserved.
118
+ * **Capability model**: each device reports what it can measure (`mlsys probe`), and
119
+ sweeps degrade gracefully rather than failing on a machine without, say, a PMIC.
120
+
121
+ ## Install
122
+
123
+ ```
124
+ pip install -e ".[dev]" # numpy, matplotlib, PyYAML; pytest for the test suite
125
+ pip install -e ".[onnx]" # optional: onnxruntime for the ORT backend on this host
126
+ ```
127
+
128
+ The measurement core (agent, devices, backends, config, schema) is standard library
129
+ only, verified by a dedicated no-dependencies CI job. Devices under test need Python 3.9+
130
+ and their inference backend (a llama.cpp build and/or onnxruntime), nothing else.
131
+
132
+ ## Quick start
133
+
134
+ A ready-to-edit template lives at `configs/example-smoke.yaml`. Copy it, fill in
135
+ your paths, and run:
136
+
137
+ ```
138
+ mlsys doctor --config configs/example-smoke.yaml # check everything is wired up
139
+ mlsys run configs/example-smoke.yaml # run the experiment
140
+ mlsys report runs/smoke-test # see the results
141
+ ```
142
+
143
+ For a multi-device setup, describe your machines and models once:
144
+
145
+ ```yaml
146
+ # configs/lab.yaml
147
+ experiment: my-sweep
148
+ devices:
149
+ laptop:
150
+ kind: local
151
+ dram_peak_GBs: 53.9 # your measured read ceiling; drives utilization %
152
+ llamacpp: { bin_dir: C:/llmpc/bin }
153
+ pi5:
154
+ host: 100.98.217.64 # any SSH-reachable box; Tailscale IPs work fine
155
+ user: manu
156
+ identity_file: ~/.ssh/raspberry_pi_key
157
+ dram_peak_GBs: 13.98
158
+ llamacpp: { bin_dir: ~/llm/llama.cpp/build/bin }
159
+ models:
160
+ qwen0.5b-q4km:
161
+ quantization: Q4_K_M
162
+ paths: { laptop: C:/llmpc/models/qwen0.5b-q4km.gguf, pi5: ~/llm/models/qwen0.5b-q4km.gguf }
163
+ defaults: { backend: llamacpp, repetitions: 3 }
164
+ matrix:
165
+ - devices: [laptop, pi5]
166
+ models: [qwen0.5b-q4km]
167
+ modes: [throughput]
168
+ threads: [1, 2, 4]
169
+ prompt_tokens: [128]
170
+ output_tokens: [64]
171
+ - devices: [laptop, pi5]
172
+ models: [qwen0.5b-q4km]
173
+ modes: [latency] # TTFT via llama-server streaming
174
+ threads: [4]
175
+ prompt_tokens: [128, 512]
176
+ output_tokens: [64]
177
+ ```
178
+
179
+ 2. Check the machines are reachable and see what they can measure:
180
+
181
+ ```
182
+ mlsys probe --config configs/lab.yaml
183
+ ```
184
+
185
+ 3. Preview, then run:
186
+
187
+ ```
188
+ mlsys run configs/lab.yaml --dry-run
189
+ mlsys run configs/lab.yaml
190
+ ```
191
+
192
+ 4. Tables and figures:
193
+
194
+ ```
195
+ mlsys report runs/my-sweep # tables to the terminal
196
+ mlsys report runs/my-sweep --format latex # booktabs, ready to paste
197
+ mlsys report runs/my-sweep --full # REPORT.md + PNG/PDF figures
198
+ ```
199
+
200
+ 5. More tools:
201
+
202
+ ```
203
+ mlsys doctor --config configs/lab.yaml
204
+ # checks Python, dependencies, llama.cpp binaries, model paths,
205
+ # and device reachability; run this first if something is not working
206
+
207
+ mlsys membw --device pi5 --config configs/lab.yaml
208
+ # measures the device's achievable DRAM read ceiling and prints the
209
+ # dram_peak_GBs line to put in the config, with a stability check that
210
+ # flags a machine that was not idle
211
+
212
+ mlsys compare runs/before runs/after --metric decode_tps
213
+ # same workloads in two result sets, side by side with the ratio
214
+ ```
215
+
216
+ ## Measurement methodology
217
+
218
+ The rules encoded in this framework, and why, are documented in
219
+ [docs/METHOD.md](docs/METHOD.md). The short version:
220
+
221
+ * benchmark on an idle machine; the framework flags high run-to-run spread,
222
+ * never sample power in the run you take throughput from (the sampler perturbs decode),
223
+ * temperature and throttle state live inside the record so a hot run cannot be
224
+ silently compared with a cool one,
225
+ * `llama-bench` for throughput, `llama-server` streaming for TTFT, and `llama-cli`
226
+ never (it hangs when scripted),
227
+ * every failure is written to disk with its raw output.
228
+
229
+ ## Repository layout
230
+
231
+ ```
232
+ src/mlsyslab/
233
+ schema.py the RunRecord and its loader
234
+ sysinfo.py automatic hardware/OS description
235
+ config.py YAML/JSON sweep expansion
236
+ runner.py resumable execution, atomic writes
237
+ agent.py the on-device payload (stdlib only)
238
+ bench_onnx.py the on-device ONNX Runtime benchmark
239
+ devices/ local and SSH devices, capability model
240
+ backends/ llamacpp (bench + server) and onnxruntime
241
+ telemetry/ power (PMIC/RAPL), thermal, CPU, DVFS
242
+ analysis/ dataset, tables, figures, REPORT.md
243
+ configs/ experiment definitions
244
+ tools/ result backfill converters
245
+ results/paper12/ real measurements from the IEEE TC submission
246
+ tests/ 70 hardware-free tests (recorded fixtures)
247
+ ```
248
+
249
+ ## Citing
250
+
251
+ Archived on Zenodo; the concept DOI [10.5281/zenodo.21867055](https://doi.org/10.5281/zenodo.21867055)
252
+ always resolves to the latest version. `CITATION.cff` carries the full citation metadata,
253
+ and GitHub's "Cite this repository" button renders it.
254
+
255
+ ## License
256
+
257
+ MIT. llama.cpp and ONNX Runtime are invoked as external tools and are licensed by their
258
+ respective projects.
@@ -0,0 +1,221 @@
1
+ # ML Systems Lab
2
+
3
+ [![tests](https://github.com/manunicholasjacob/ml-systems-lab/actions/workflows/ci.yml/badge.svg)](https://github.com/manunicholasjacob/ml-systems-lab/actions/workflows/ci.yml)
4
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.21867055.svg)](https://doi.org/10.5281/zenodo.21867055)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
6
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue.svg)](pyproject.toml)
7
+
8
+ A reproducible benchmarking framework for ML inference across heterogeneous hardware:
9
+ laptops, single-board computers, and servers, from one config file and one command.
10
+
11
+ ```
12
+ mlsys run configs/example-smoke.yaml
13
+ mlsys report runs/smoke-test --full
14
+ ```
15
+
16
+ Every run produces a self-describing JSON record carrying the hardware, OS, kernel,
17
+ compiler, backend version, model, quantization, the measured metrics, and the physical
18
+ state of the machine while they were measured (power, temperature, clocks, throttle
19
+ flags, CPU utilization). The analysis layer turns a directory of records into
20
+ publication-quality tables (text, Markdown, LaTeX/booktabs) and figures.
21
+
22
+ Built and used for a real research program: the records under `results/paper12/` are the
23
+ measurements behind an IEEE Transactions on Computers submission, and the framework
24
+ reproduces that paper's published roofline fits exactly (Pi 5: 10.7 GB/s effective,
25
+ R^2 = 0.980; i7-12700H: 35.7 GB/s, R^2 = 0.980). Two campaigns run natively by this
26
+ framework then re-measured the same quantities independently and agreed within 1.6%.
27
+
28
+ ![roofline](results/combined-report/fig_roofline.png)
29
+
30
+ *Every point is a different model or quantization; every line is one device's effective
31
+ memory bandwidth. Two independent Pi 5 campaigns (weeks apart, different harnesses) and
32
+ two laptop campaigns land on top of each other: decode throughput is model bytes divided
33
+ by one number per device.*
34
+
35
+ ## What it measures
36
+
37
+ | Metric | How |
38
+ |---|---|
39
+ | TTFT (time to first token) | streaming request against `llama-server`, first-chunk timing |
40
+ | Prefill / decode throughput | `llama-bench`, parsed from its JSON output |
41
+ | End-to-end request latency | same streaming path, submit to last token |
42
+ | Single-inference latency (mean, p50/p95/p99) | ONNX Runtime, timed in-process on the device |
43
+ | Memory | peak RSS, plus free-memory and swap state around every run |
44
+ | CPU utilization | `/proc/stat` deltas (Linux), `GetSystemTimes` (Windows), per-core where available |
45
+ | Temperature and throttling | sysfs thermal zones, Pi throttle bitmask, in the record not a side file |
46
+ | Power and energy | Raspberry Pi PMIC per-rail (core vs DRAM split), Intel RAPL where present |
47
+ | Derived | decode bandwidth, roofline utilization %, energy per token |
48
+
49
+ Anything a platform cannot measure is reported as absent, never as zero.
50
+
51
+ ## Devices exercised
52
+
53
+ | Device | Route | Notes |
54
+ |---|---|---|
55
+ | Raspberry Pi 5 (2 GB, Cortex-A76) | SSH agent | PMIC per-rail power, throttle bits, DVFS control |
56
+ | i7-12700H laptop (Windows) | local agent | 20-thread sweeps, up to 7B models |
57
+ | RTX 3050 (same laptop) | ONNX Runtime DirectML | modeled as its own device; at batch 1 the GPU loses to the CPU (12.3 ms vs 3.0 ms, dispatch overhead), at batch 64 it wins 29x (9,885 vs 338 inf/s), and both facts come out of the same config file |
58
+
59
+ A new machine is a config block, not code: `host`, an SSH key, and the paths to its
60
+ models. A new accelerator is a device entry pointing at an interpreter whose ONNX
61
+ Runtime carries the right execution provider.
62
+
63
+ ## Design
64
+
65
+ ```
66
+ config.yaml ──> RunSpecs ──> Device ──> agent (on the device) ──> RunRecord ──> analysis
67
+
68
+ ├── LocalDevice (this machine, agent as subprocess)
69
+ └── SSHDevice (agent pushed over SSH, runs remotely)
70
+ ```
71
+
72
+ * **The agent runs on the device under test**, so the benchmark and the telemetry
73
+ sampler are colocated; nothing crosses the network inside a measurement window.
74
+ It is pure standard library and is copied, not installed.
75
+ * **Backends** (`llamacpp`, `onnxruntime`) turn one spec into one task and parse one
76
+ result. The agent returns raw output; parsing happens on the host, so a parser bug is
77
+ fixed by re-parsing stored output rather than re-running a campaign.
78
+ * **Run ids are deterministic** over the spec, so an interrupted campaign resumes by
79
+ skipping what is already on disk (`--no-resume` to override). Failures are records
80
+ too, with the error and the raw output preserved.
81
+ * **Capability model**: each device reports what it can measure (`mlsys probe`), and
82
+ sweeps degrade gracefully rather than failing on a machine without, say, a PMIC.
83
+
84
+ ## Install
85
+
86
+ ```
87
+ pip install -e ".[dev]" # numpy, matplotlib, PyYAML; pytest for the test suite
88
+ pip install -e ".[onnx]" # optional: onnxruntime for the ORT backend on this host
89
+ ```
90
+
91
+ The measurement core (agent, devices, backends, config, schema) is standard library
92
+ only, verified by a dedicated no-dependencies CI job. Devices under test need Python 3.9+
93
+ and their inference backend (a llama.cpp build and/or onnxruntime), nothing else.
94
+
95
+ ## Quick start
96
+
97
+ A ready-to-edit template lives at `configs/example-smoke.yaml`. Copy it, fill in
98
+ your paths, and run:
99
+
100
+ ```
101
+ mlsys doctor --config configs/example-smoke.yaml # check everything is wired up
102
+ mlsys run configs/example-smoke.yaml # run the experiment
103
+ mlsys report runs/smoke-test # see the results
104
+ ```
105
+
106
+ For a multi-device setup, describe your machines and models once:
107
+
108
+ ```yaml
109
+ # configs/lab.yaml
110
+ experiment: my-sweep
111
+ devices:
112
+ laptop:
113
+ kind: local
114
+ dram_peak_GBs: 53.9 # your measured read ceiling; drives utilization %
115
+ llamacpp: { bin_dir: C:/llmpc/bin }
116
+ pi5:
117
+ host: 100.98.217.64 # any SSH-reachable box; Tailscale IPs work fine
118
+ user: manu
119
+ identity_file: ~/.ssh/raspberry_pi_key
120
+ dram_peak_GBs: 13.98
121
+ llamacpp: { bin_dir: ~/llm/llama.cpp/build/bin }
122
+ models:
123
+ qwen0.5b-q4km:
124
+ quantization: Q4_K_M
125
+ paths: { laptop: C:/llmpc/models/qwen0.5b-q4km.gguf, pi5: ~/llm/models/qwen0.5b-q4km.gguf }
126
+ defaults: { backend: llamacpp, repetitions: 3 }
127
+ matrix:
128
+ - devices: [laptop, pi5]
129
+ models: [qwen0.5b-q4km]
130
+ modes: [throughput]
131
+ threads: [1, 2, 4]
132
+ prompt_tokens: [128]
133
+ output_tokens: [64]
134
+ - devices: [laptop, pi5]
135
+ models: [qwen0.5b-q4km]
136
+ modes: [latency] # TTFT via llama-server streaming
137
+ threads: [4]
138
+ prompt_tokens: [128, 512]
139
+ output_tokens: [64]
140
+ ```
141
+
142
+ 2. Check the machines are reachable and see what they can measure:
143
+
144
+ ```
145
+ mlsys probe --config configs/lab.yaml
146
+ ```
147
+
148
+ 3. Preview, then run:
149
+
150
+ ```
151
+ mlsys run configs/lab.yaml --dry-run
152
+ mlsys run configs/lab.yaml
153
+ ```
154
+
155
+ 4. Tables and figures:
156
+
157
+ ```
158
+ mlsys report runs/my-sweep # tables to the terminal
159
+ mlsys report runs/my-sweep --format latex # booktabs, ready to paste
160
+ mlsys report runs/my-sweep --full # REPORT.md + PNG/PDF figures
161
+ ```
162
+
163
+ 5. More tools:
164
+
165
+ ```
166
+ mlsys doctor --config configs/lab.yaml
167
+ # checks Python, dependencies, llama.cpp binaries, model paths,
168
+ # and device reachability; run this first if something is not working
169
+
170
+ mlsys membw --device pi5 --config configs/lab.yaml
171
+ # measures the device's achievable DRAM read ceiling and prints the
172
+ # dram_peak_GBs line to put in the config, with a stability check that
173
+ # flags a machine that was not idle
174
+
175
+ mlsys compare runs/before runs/after --metric decode_tps
176
+ # same workloads in two result sets, side by side with the ratio
177
+ ```
178
+
179
+ ## Measurement methodology
180
+
181
+ The rules encoded in this framework, and why, are documented in
182
+ [docs/METHOD.md](docs/METHOD.md). The short version:
183
+
184
+ * benchmark on an idle machine; the framework flags high run-to-run spread,
185
+ * never sample power in the run you take throughput from (the sampler perturbs decode),
186
+ * temperature and throttle state live inside the record so a hot run cannot be
187
+ silently compared with a cool one,
188
+ * `llama-bench` for throughput, `llama-server` streaming for TTFT, and `llama-cli`
189
+ never (it hangs when scripted),
190
+ * every failure is written to disk with its raw output.
191
+
192
+ ## Repository layout
193
+
194
+ ```
195
+ src/mlsyslab/
196
+ schema.py the RunRecord and its loader
197
+ sysinfo.py automatic hardware/OS description
198
+ config.py YAML/JSON sweep expansion
199
+ runner.py resumable execution, atomic writes
200
+ agent.py the on-device payload (stdlib only)
201
+ bench_onnx.py the on-device ONNX Runtime benchmark
202
+ devices/ local and SSH devices, capability model
203
+ backends/ llamacpp (bench + server) and onnxruntime
204
+ telemetry/ power (PMIC/RAPL), thermal, CPU, DVFS
205
+ analysis/ dataset, tables, figures, REPORT.md
206
+ configs/ experiment definitions
207
+ tools/ result backfill converters
208
+ results/paper12/ real measurements from the IEEE TC submission
209
+ tests/ 70 hardware-free tests (recorded fixtures)
210
+ ```
211
+
212
+ ## Citing
213
+
214
+ Archived on Zenodo; the concept DOI [10.5281/zenodo.21867055](https://doi.org/10.5281/zenodo.21867055)
215
+ always resolves to the latest version. `CITATION.cff` carries the full citation metadata,
216
+ and GitHub's "Cite this repository" button renders it.
217
+
218
+ ## License
219
+
220
+ MIT. llama.cpp and ONNX Runtime are invoked as external tools and are licensed by their
221
+ respective projects.
@@ -0,0 +1,56 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ml-systems-lab"
7
+ version = "0.1.1"
8
+ description = "A reproducible benchmarking framework for ML inference across laptops, single-board computers and servers."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Manu Nicholas Jacob" }]
13
+ keywords = [
14
+ "benchmark", "inference", "llama.cpp", "onnxruntime", "edge-ai",
15
+ "raspberry-pi", "energy", "latency", "ttft", "quantization", "reproducibility",
16
+ ]
17
+ classifiers = [
18
+ "Development Status :: 4 - Beta",
19
+ "Environment :: Console",
20
+ "Intended Audience :: Developers",
21
+ "Intended Audience :: Science/Research",
22
+ "License :: OSI Approved :: MIT License",
23
+ "Operating System :: POSIX :: Linux",
24
+ "Operating System :: MacOS",
25
+ "Operating System :: Microsoft :: Windows",
26
+ "Programming Language :: Python :: 3",
27
+ "Programming Language :: Python :: 3.9",
28
+ "Programming Language :: Python :: 3.10",
29
+ "Programming Language :: Python :: 3.11",
30
+ "Programming Language :: Python :: 3.12",
31
+ "Programming Language :: Python :: 3.13",
32
+ "Topic :: Scientific/Engineering",
33
+ "Topic :: System :: Benchmark",
34
+ ]
35
+ # The measurement core is pure standard library on purpose: it has to run unmodified on
36
+ # a 2 GB Raspberry Pi over SSH, where installing a scientific stack is neither cheap nor
37
+ # always possible. numpy and matplotlib are ordinary dependencies so that analysis and
38
+ # plotting work on first install, and a CI job proves the core still imports without them.
39
+ dependencies = ["numpy>=1.20", "matplotlib>=3.4", "PyYAML>=5.4"]
40
+
41
+ [project.optional-dependencies]
42
+ dev = ["pytest>=7"]
43
+ onnx = ["onnxruntime>=1.15"]
44
+
45
+ [project.urls]
46
+ Homepage = "https://github.com/manunicholasjacob/ml-systems-lab"
47
+ Issues = "https://github.com/manunicholasjacob/ml-systems-lab/issues"
48
+
49
+ [project.scripts]
50
+ mlsys = "mlsyslab.cli:main"
51
+
52
+ [tool.setuptools.packages.find]
53
+ where = ["src"]
54
+
55
+ [tool.pytest.ini_options]
56
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+