modeldiet 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.
- modeldiet-0.1.0/LICENSE +21 -0
- modeldiet-0.1.0/PKG-INFO +228 -0
- modeldiet-0.1.0/README.md +193 -0
- modeldiet-0.1.0/pyproject.toml +72 -0
- modeldiet-0.1.0/setup.cfg +4 -0
- modeldiet-0.1.0/src/modeldiet/__init__.py +6 -0
- modeldiet-0.1.0/src/modeldiet/api.py +44 -0
- modeldiet-0.1.0/src/modeldiet/benchmark/__init__.py +5 -0
- modeldiet-0.1.0/src/modeldiet/benchmark/model.py +143 -0
- modeldiet-0.1.0/src/modeldiet/benchmark/models/__init__.py +1 -0
- modeldiet-0.1.0/src/modeldiet/benchmark/models/lr.py +22 -0
- modeldiet-0.1.0/src/modeldiet/benchmark/models/rf.py +22 -0
- modeldiet-0.1.0/src/modeldiet/benchmark/models/xgb.py +30 -0
- modeldiet-0.1.0/src/modeldiet/benchmark/report.py +60 -0
- modeldiet-0.1.0/src/modeldiet/benchmark/runner.py +75 -0
- modeldiet-0.1.0/src/modeldiet/benchmark/statistics.py +63 -0
- modeldiet-0.1.0/src/modeldiet/benchmark/worker.py +211 -0
- modeldiet-0.1.0/src/modeldiet/driver.py +33 -0
- modeldiet-0.1.0/src/modeldiet/model.py +5 -0
- modeldiet-0.1.0/src/modeldiet/models/__init__.py +1 -0
- modeldiet-0.1.0/src/modeldiet/models/lr.py +5 -0
- modeldiet-0.1.0/src/modeldiet/models/rf.py +5 -0
- modeldiet-0.1.0/src/modeldiet/models/xgb.py +5 -0
- modeldiet-0.1.0/src/modeldiet/profiling/__init__.py +17 -0
- modeldiet-0.1.0/src/modeldiet/profiling/benchmark.py +7 -0
- modeldiet-0.1.0/src/modeldiet/profiling/environment.py +51 -0
- modeldiet-0.1.0/src/modeldiet/profiling/isolated_runner.py +9 -0
- modeldiet-0.1.0/src/modeldiet/profiling/measurements.py +181 -0
- modeldiet-0.1.0/src/modeldiet/profiling/orchestrator.py +146 -0
- modeldiet-0.1.0/src/modeldiet/profiling/statistics.py +5 -0
- modeldiet-0.1.0/src/modeldiet/report.py +5 -0
- modeldiet-0.1.0/src/modeldiet/result.py +114 -0
- modeldiet-0.1.0/src/modeldiet.egg-info/PKG-INFO +228 -0
- modeldiet-0.1.0/src/modeldiet.egg-info/SOURCES.txt +42 -0
- modeldiet-0.1.0/src/modeldiet.egg-info/dependency_links.txt +1 -0
- modeldiet-0.1.0/src/modeldiet.egg-info/requires.txt +31 -0
- modeldiet-0.1.0/src/modeldiet.egg-info/top_level.txt +1 -0
- modeldiet-0.1.0/tests/test_benchmark.py +38 -0
- modeldiet-0.1.0/tests/test_isolated_runner.py +87 -0
- modeldiet-0.1.0/tests/test_legacy_deprecations.py +37 -0
- modeldiet-0.1.0/tests/test_measurements.py +123 -0
- modeldiet-0.1.0/tests/test_optional_benchmark_dependencies.py +158 -0
- modeldiet-0.1.0/tests/test_public_api.py +104 -0
- modeldiet-0.1.0/tests/test_public_api_contract.py +134 -0
modeldiet-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Model Diet contributors
|
|
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.
|
modeldiet-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: modeldiet
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Profile inference performance and resource usage of trained models
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: numpy>=1.24
|
|
9
|
+
Requires-Dist: psutil>=5.9
|
|
10
|
+
Provides-Extra: demo
|
|
11
|
+
Requires-Dist: pandas>=2; extra == "demo"
|
|
12
|
+
Requires-Dist: scikit-learn>=1.3; extra == "demo"
|
|
13
|
+
Requires-Dist: xgboost>=2.0; extra == "demo"
|
|
14
|
+
Provides-Extra: benchmark
|
|
15
|
+
Requires-Dist: pandas>=2; extra == "benchmark"
|
|
16
|
+
Requires-Dist: scikit-learn>=1.3; extra == "benchmark"
|
|
17
|
+
Provides-Extra: benchmark-xgboost
|
|
18
|
+
Requires-Dist: pandas>=2; extra == "benchmark-xgboost"
|
|
19
|
+
Requires-Dist: scikit-learn>=1.3; extra == "benchmark-xgboost"
|
|
20
|
+
Requires-Dist: xgboost>=2.0; extra == "benchmark-xgboost"
|
|
21
|
+
Provides-Extra: test
|
|
22
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
23
|
+
Requires-Dist: pytest-cov>=7; extra == "test"
|
|
24
|
+
Requires-Dist: pandas>=2; extra == "test"
|
|
25
|
+
Requires-Dist: build>=1; extra == "test"
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: ruff<0.18,>=0.17.3; extra == "dev"
|
|
28
|
+
Requires-Dist: mypy<3,>=2.4.1; extra == "dev"
|
|
29
|
+
Requires-Dist: pip-audit<3,>=2.10.1; extra == "dev"
|
|
30
|
+
Requires-Dist: pre-commit<5,>=4.7.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pandas-stubs>=2; extra == "dev"
|
|
32
|
+
Requires-Dist: types-psutil>=5.9; extra == "dev"
|
|
33
|
+
Requires-Dist: twine<8,>=7; extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# Model Diet
|
|
37
|
+
|
|
38
|
+
**Profile the inference cost of a fitted Python model before deployment.**
|
|
39
|
+
|
|
40
|
+
[](https://github.com/modeldiet/modeldiet/actions/workflows/ci.yml)
|
|
41
|
+
[](https://github.com/modeldiet/modeldiet/actions/workflows/docs.yml)
|
|
42
|
+
[](https://github.com/modeldiet/modeldiet/blob/master/pyproject.toml)
|
|
43
|
+
[](LICENSE)
|
|
44
|
+
|
|
45
|
+
Model Diet helps ML engineers understand how an already-fitted model behaves at inference time. Give it representative tabular inputs; it returns structured measurements you can inspect or export as JSON and Markdown.
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
**Model Diet is not currently published on production PyPI.** PyPI's [package metadata endpoint](https://pypi.org/pypi/modeldiet/json) returned 404 when checked on November 7, 2026, so `pip install modeldiet` is not a verified installation method. Install from the source repository instead:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
git clone https://github.com/modeldiet/modeldiet.git
|
|
53
|
+
cd modeldiet
|
|
54
|
+
python -m pip install .
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The core package requires Python 3.11+ and depends on NumPy and psutil. It does not require scikit-learn or XGBoost; you can profile a compatible model of your own without those libraries.
|
|
58
|
+
|
|
59
|
+
The copy-and-run quickstart below uses scikit-learn. Install the demo extra from the repository checkout to run it:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
python -m pip install ".[demo]"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The optional benchmark tools have separate extras:
|
|
66
|
+
|
|
67
|
+
| Install from the repository checkout | Includes | Use |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| `python -m pip install ".[benchmark]"` | pandas and scikit-learn | Logistic Regression and Random Forest benchmark runs |
|
|
70
|
+
| `python -m pip install ".[benchmark-xgboost]"` | pandas, scikit-learn, and XGBoost | XGBoost benchmark runs |
|
|
71
|
+
| `python -m pip install ".[demo]"` | pandas, scikit-learn, and XGBoost | All documented demo examples and benchmark types |
|
|
72
|
+
|
|
73
|
+
The test release `0.1.0` is available from [TestPyPI](https://test.pypi.org/project/modeldiet/), but it is not a production release. To try it, let pip use TestPyPI for Model Diet and production PyPI for its dependencies:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
python -m pip install \
|
|
77
|
+
--index-url https://test.pypi.org/simple/ \
|
|
78
|
+
--extra-index-url https://pypi.org/simple/ \
|
|
79
|
+
"modeldiet[demo]==0.1.0"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Use the source installation above for development from the repository.
|
|
83
|
+
|
|
84
|
+
## Quickstart
|
|
85
|
+
|
|
86
|
+
This trains a small Random Forest, profiles it with the default latency measurement, and writes both report formats. It requires the `demo` extra.
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from pathlib import Path
|
|
90
|
+
|
|
91
|
+
import numpy as np
|
|
92
|
+
from sklearn.ensemble import RandomForestClassifier
|
|
93
|
+
|
|
94
|
+
from modeldiet import profile
|
|
95
|
+
|
|
96
|
+
rng = np.random.default_rng(42)
|
|
97
|
+
X = rng.random((100, 5))
|
|
98
|
+
y = rng.integers(0, 2, size=100)
|
|
99
|
+
|
|
100
|
+
model = RandomForestClassifier(n_estimators=20, random_state=42)
|
|
101
|
+
model.fit(X, y)
|
|
102
|
+
|
|
103
|
+
result = profile(model=model, X=X, include_environment=True)
|
|
104
|
+
mean_ms = result.measurements["latency"].metrics["mean_ms"].value
|
|
105
|
+
print(f"Mean single-row prediction latency: {mean_ms:.3f} ms")
|
|
106
|
+
|
|
107
|
+
output_dir = Path("profile-output")
|
|
108
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
109
|
+
result.to_json(output_dir / "profile.json")
|
|
110
|
+
result.to_markdown(output_dir / "profile.md")
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Use representative inputs with the feature layout and preprocessing your fitted model expects. This example generates data only to make the API easy to try.
|
|
114
|
+
|
|
115
|
+
## Example result
|
|
116
|
+
|
|
117
|
+
Below is an abbreviated view of one run of the quickstart above in a local development environment. The latency values are machine- and workload-specific examples, not performance claims or expected results. Your values will differ.
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
model_class: RandomForestClassifier
|
|
121
|
+
input_type: ndarray
|
|
122
|
+
sample_count: 100
|
|
123
|
+
feature_count: 5
|
|
124
|
+
|
|
125
|
+
latency.mean 0.633 ms
|
|
126
|
+
latency.p50 0.609 ms
|
|
127
|
+
latency.p90 0.659 ms
|
|
128
|
+
latency.p95 0.676 ms
|
|
129
|
+
latency.p99 0.754 ms
|
|
130
|
+
latency.sample_count 100 count
|
|
131
|
+
|
|
132
|
+
environment: optional metadata included
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Each metric also has a definition with its identifier, unit, description, and semantics. The environment field can include Python and platform details, CPU and memory information, selected library versions, and thread environment variables.
|
|
136
|
+
|
|
137
|
+
## Supported models and inputs
|
|
138
|
+
|
|
139
|
+
`profile()` accepts an already-fitted Python object with a callable `predict(X)` method. It does not require a particular estimator base class. Inputs must be non-empty two-dimensional tabular data supplied as a NumPy array, pandas DataFrame, or list or tuple of rows. Model Diet passes each row to `predict()` separately, preserving a one-row batch dimension.
|
|
140
|
+
|
|
141
|
+
## Measurements
|
|
142
|
+
|
|
143
|
+
By default, `profile()` runs only `LatencyMeasurement`: it makes one warmup prediction, then times one prediction per input row using `time.perf_counter()` without `tracemalloc` enabled.
|
|
144
|
+
|
|
145
|
+
| Measurement | Metric identifiers | Units | What the values describe |
|
|
146
|
+
| --- | --- | --- | --- |
|
|
147
|
+
| `LatencyMeasurement` | `latency.mean`, `latency.p50`, `latency.p90`, `latency.p95`, `latency.p99` | ms | Mean and percentiles of single-row prediction durations. `latency.sample_count` is a count. |
|
|
148
|
+
| `TracedAllocationMeasurement` | `python_traced.peak_allocation` | MiB | Peak Python-traced allocations during a separate inference pass; native allocations not tracked by `tracemalloc` are excluded. |
|
|
149
|
+
| `ProcessRSSMeasurement` | `process_rss.before`, `process_rss.after`, `process_rss.delta` | MiB | Whole-process RSS endpoint snapshots and their difference; not model-only memory or peak RSS. |
|
|
150
|
+
| `SerializationMeasurement` | `pickle.size` | MiB | Size of the model's Python pickle representation; not a guarantee that pickle is safe or portable. |
|
|
151
|
+
|
|
152
|
+
You can select other strategies from `modeldiet.profiling`. Supplying `measurements=[...]` replaces the default list, so include `LatencyMeasurement()` explicitly if you want latency alongside other measurements. Each strategy makes its own pass over the input.
|
|
153
|
+
|
|
154
|
+
## Understanding the results
|
|
155
|
+
|
|
156
|
+
`profile()` returns a `ProfileResult`. Its `measurements` mapping contains `MeasurementResult` objects. Each metric provides a value and a definition:
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
metric = result.measurements["latency"].metrics["mean_ms"]
|
|
160
|
+
print(metric.value)
|
|
161
|
+
print(metric.definition.identifier, metric.definition.unit)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The result also records the model class and module, input type, sample count, feature count, and schema version. Environment metadata is opt-in through `include_environment=True`. The detailed [application guide](https://modeldiet.github.io/modeldiet/application/) covers error handling, exports, and custom measurement strategies.
|
|
165
|
+
|
|
166
|
+
## Additional benchmark workflow
|
|
167
|
+
|
|
168
|
+
`modeldiet.profile()` measures a model you have already fitted using your own representative data. The separate `modeldiet.benchmark` tools run demonstration workflows: they load a bundled dataset, construct and train a selected estimator, then write raw run and summary JSON files. This benchmark workflow is optional and is not required to use `profile()`.
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
from modeldiet.benchmark import run_repeated_benchmark
|
|
172
|
+
|
|
173
|
+
run_repeated_benchmark("lr", repetitions=1, output_dir="benchmark-output")
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Use the `[benchmark]` extra for Logistic Regression or Random Forest, and `[benchmark-xgboost]` for XGBoost. Unlike core `profile()`, the demonstration benchmark reports accuracy because it has labels for its bundled dataset. See the [benchmark guide](https://modeldiet.github.io/modeldiet/application/#optional-benchmark-runner) for details.
|
|
177
|
+
|
|
178
|
+
## Limitations
|
|
179
|
+
|
|
180
|
+
Model Diet reports inference measurements, not deployment-wide guarantees. In particular, it does **not** measure:
|
|
181
|
+
|
|
182
|
+
- Accuracy or prediction quality in the core profiling API.
|
|
183
|
+
- GPU memory.
|
|
184
|
+
- Batch throughput: predictions are timed one row at a time.
|
|
185
|
+
- Concurrent-serving capacity.
|
|
186
|
+
- End-to-end API or network latency.
|
|
187
|
+
- Peak process RSS: RSS values are before-and-after snapshots.
|
|
188
|
+
|
|
189
|
+
Results depend on the model, input data, software stack, machine, and background load. Compare runs under similar conditions and treat these measurements as local observations, not service-level guarantees.
|
|
190
|
+
|
|
191
|
+
## Documentation
|
|
192
|
+
|
|
193
|
+
- [Quickstart](https://modeldiet.github.io/modeldiet/quickstart/)
|
|
194
|
+
- [Application guide](https://modeldiet.github.io/modeldiet/application/)
|
|
195
|
+
- [Measurement definitions](https://modeldiet.github.io/modeldiet/measurements/)
|
|
196
|
+
- [Public API and compatibility policy](https://modeldiet.github.io/modeldiet/api/)
|
|
197
|
+
- [Documentation hosting and local build](https://modeldiet.github.io/modeldiet/hosting/)
|
|
198
|
+
|
|
199
|
+
## Architecture overview
|
|
200
|
+
|
|
201
|
+
```mermaid
|
|
202
|
+
flowchart LR
|
|
203
|
+
A[Your fitted model] --> C[profile]
|
|
204
|
+
B[Representative tabular rows] --> C
|
|
205
|
+
C --> D[ProfileResult]
|
|
206
|
+
D --> E[Metric values and definitions]
|
|
207
|
+
D --> F[JSON export]
|
|
208
|
+
D --> G[Markdown export]
|
|
209
|
+
H[Optional demo benchmark] --> I[Bundled dataset and estimator]
|
|
210
|
+
I --> J[Raw and summary benchmark JSON]
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Core profiling accepts your model and input data. The demonstration benchmark is a separate optional workflow with its own bundled dataset and estimators.
|
|
214
|
+
|
|
215
|
+
## Development and contributions
|
|
216
|
+
|
|
217
|
+
To work on the project, install its test and demo dependencies from a checkout and run the tests:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
python -m pip install -e ".[test,demo,dev]"
|
|
221
|
+
python -m pytest -v
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for contributor setup and quality checks. Contributions and issue reports are welcome.
|
|
225
|
+
|
|
226
|
+
## License
|
|
227
|
+
|
|
228
|
+
Model Diet is distributed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Model Diet
|
|
2
|
+
|
|
3
|
+
**Profile the inference cost of a fitted Python model before deployment.**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/modeldiet/modeldiet/actions/workflows/ci.yml)
|
|
6
|
+
[](https://github.com/modeldiet/modeldiet/actions/workflows/docs.yml)
|
|
7
|
+
[](https://github.com/modeldiet/modeldiet/blob/master/pyproject.toml)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
|
|
10
|
+
Model Diet helps ML engineers understand how an already-fitted model behaves at inference time. Give it representative tabular inputs; it returns structured measurements you can inspect or export as JSON and Markdown.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
**Model Diet is not currently published on production PyPI.** PyPI's [package metadata endpoint](https://pypi.org/pypi/modeldiet/json) returned 404 when checked on November 7, 2026, so `pip install modeldiet` is not a verified installation method. Install from the source repository instead:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
git clone https://github.com/modeldiet/modeldiet.git
|
|
18
|
+
cd modeldiet
|
|
19
|
+
python -m pip install .
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The core package requires Python 3.11+ and depends on NumPy and psutil. It does not require scikit-learn or XGBoost; you can profile a compatible model of your own without those libraries.
|
|
23
|
+
|
|
24
|
+
The copy-and-run quickstart below uses scikit-learn. Install the demo extra from the repository checkout to run it:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
python -m pip install ".[demo]"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The optional benchmark tools have separate extras:
|
|
31
|
+
|
|
32
|
+
| Install from the repository checkout | Includes | Use |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| `python -m pip install ".[benchmark]"` | pandas and scikit-learn | Logistic Regression and Random Forest benchmark runs |
|
|
35
|
+
| `python -m pip install ".[benchmark-xgboost]"` | pandas, scikit-learn, and XGBoost | XGBoost benchmark runs |
|
|
36
|
+
| `python -m pip install ".[demo]"` | pandas, scikit-learn, and XGBoost | All documented demo examples and benchmark types |
|
|
37
|
+
|
|
38
|
+
The test release `0.1.0` is available from [TestPyPI](https://test.pypi.org/project/modeldiet/), but it is not a production release. To try it, let pip use TestPyPI for Model Diet and production PyPI for its dependencies:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
python -m pip install \
|
|
42
|
+
--index-url https://test.pypi.org/simple/ \
|
|
43
|
+
--extra-index-url https://pypi.org/simple/ \
|
|
44
|
+
"modeldiet[demo]==0.1.0"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Use the source installation above for development from the repository.
|
|
48
|
+
|
|
49
|
+
## Quickstart
|
|
50
|
+
|
|
51
|
+
This trains a small Random Forest, profiles it with the default latency measurement, and writes both report formats. It requires the `demo` extra.
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from pathlib import Path
|
|
55
|
+
|
|
56
|
+
import numpy as np
|
|
57
|
+
from sklearn.ensemble import RandomForestClassifier
|
|
58
|
+
|
|
59
|
+
from modeldiet import profile
|
|
60
|
+
|
|
61
|
+
rng = np.random.default_rng(42)
|
|
62
|
+
X = rng.random((100, 5))
|
|
63
|
+
y = rng.integers(0, 2, size=100)
|
|
64
|
+
|
|
65
|
+
model = RandomForestClassifier(n_estimators=20, random_state=42)
|
|
66
|
+
model.fit(X, y)
|
|
67
|
+
|
|
68
|
+
result = profile(model=model, X=X, include_environment=True)
|
|
69
|
+
mean_ms = result.measurements["latency"].metrics["mean_ms"].value
|
|
70
|
+
print(f"Mean single-row prediction latency: {mean_ms:.3f} ms")
|
|
71
|
+
|
|
72
|
+
output_dir = Path("profile-output")
|
|
73
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
74
|
+
result.to_json(output_dir / "profile.json")
|
|
75
|
+
result.to_markdown(output_dir / "profile.md")
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Use representative inputs with the feature layout and preprocessing your fitted model expects. This example generates data only to make the API easy to try.
|
|
79
|
+
|
|
80
|
+
## Example result
|
|
81
|
+
|
|
82
|
+
Below is an abbreviated view of one run of the quickstart above in a local development environment. The latency values are machine- and workload-specific examples, not performance claims or expected results. Your values will differ.
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
model_class: RandomForestClassifier
|
|
86
|
+
input_type: ndarray
|
|
87
|
+
sample_count: 100
|
|
88
|
+
feature_count: 5
|
|
89
|
+
|
|
90
|
+
latency.mean 0.633 ms
|
|
91
|
+
latency.p50 0.609 ms
|
|
92
|
+
latency.p90 0.659 ms
|
|
93
|
+
latency.p95 0.676 ms
|
|
94
|
+
latency.p99 0.754 ms
|
|
95
|
+
latency.sample_count 100 count
|
|
96
|
+
|
|
97
|
+
environment: optional metadata included
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Each metric also has a definition with its identifier, unit, description, and semantics. The environment field can include Python and platform details, CPU and memory information, selected library versions, and thread environment variables.
|
|
101
|
+
|
|
102
|
+
## Supported models and inputs
|
|
103
|
+
|
|
104
|
+
`profile()` accepts an already-fitted Python object with a callable `predict(X)` method. It does not require a particular estimator base class. Inputs must be non-empty two-dimensional tabular data supplied as a NumPy array, pandas DataFrame, or list or tuple of rows. Model Diet passes each row to `predict()` separately, preserving a one-row batch dimension.
|
|
105
|
+
|
|
106
|
+
## Measurements
|
|
107
|
+
|
|
108
|
+
By default, `profile()` runs only `LatencyMeasurement`: it makes one warmup prediction, then times one prediction per input row using `time.perf_counter()` without `tracemalloc` enabled.
|
|
109
|
+
|
|
110
|
+
| Measurement | Metric identifiers | Units | What the values describe |
|
|
111
|
+
| --- | --- | --- | --- |
|
|
112
|
+
| `LatencyMeasurement` | `latency.mean`, `latency.p50`, `latency.p90`, `latency.p95`, `latency.p99` | ms | Mean and percentiles of single-row prediction durations. `latency.sample_count` is a count. |
|
|
113
|
+
| `TracedAllocationMeasurement` | `python_traced.peak_allocation` | MiB | Peak Python-traced allocations during a separate inference pass; native allocations not tracked by `tracemalloc` are excluded. |
|
|
114
|
+
| `ProcessRSSMeasurement` | `process_rss.before`, `process_rss.after`, `process_rss.delta` | MiB | Whole-process RSS endpoint snapshots and their difference; not model-only memory or peak RSS. |
|
|
115
|
+
| `SerializationMeasurement` | `pickle.size` | MiB | Size of the model's Python pickle representation; not a guarantee that pickle is safe or portable. |
|
|
116
|
+
|
|
117
|
+
You can select other strategies from `modeldiet.profiling`. Supplying `measurements=[...]` replaces the default list, so include `LatencyMeasurement()` explicitly if you want latency alongside other measurements. Each strategy makes its own pass over the input.
|
|
118
|
+
|
|
119
|
+
## Understanding the results
|
|
120
|
+
|
|
121
|
+
`profile()` returns a `ProfileResult`. Its `measurements` mapping contains `MeasurementResult` objects. Each metric provides a value and a definition:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
metric = result.measurements["latency"].metrics["mean_ms"]
|
|
125
|
+
print(metric.value)
|
|
126
|
+
print(metric.definition.identifier, metric.definition.unit)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The result also records the model class and module, input type, sample count, feature count, and schema version. Environment metadata is opt-in through `include_environment=True`. The detailed [application guide](https://modeldiet.github.io/modeldiet/application/) covers error handling, exports, and custom measurement strategies.
|
|
130
|
+
|
|
131
|
+
## Additional benchmark workflow
|
|
132
|
+
|
|
133
|
+
`modeldiet.profile()` measures a model you have already fitted using your own representative data. The separate `modeldiet.benchmark` tools run demonstration workflows: they load a bundled dataset, construct and train a selected estimator, then write raw run and summary JSON files. This benchmark workflow is optional and is not required to use `profile()`.
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from modeldiet.benchmark import run_repeated_benchmark
|
|
137
|
+
|
|
138
|
+
run_repeated_benchmark("lr", repetitions=1, output_dir="benchmark-output")
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Use the `[benchmark]` extra for Logistic Regression or Random Forest, and `[benchmark-xgboost]` for XGBoost. Unlike core `profile()`, the demonstration benchmark reports accuracy because it has labels for its bundled dataset. See the [benchmark guide](https://modeldiet.github.io/modeldiet/application/#optional-benchmark-runner) for details.
|
|
142
|
+
|
|
143
|
+
## Limitations
|
|
144
|
+
|
|
145
|
+
Model Diet reports inference measurements, not deployment-wide guarantees. In particular, it does **not** measure:
|
|
146
|
+
|
|
147
|
+
- Accuracy or prediction quality in the core profiling API.
|
|
148
|
+
- GPU memory.
|
|
149
|
+
- Batch throughput: predictions are timed one row at a time.
|
|
150
|
+
- Concurrent-serving capacity.
|
|
151
|
+
- End-to-end API or network latency.
|
|
152
|
+
- Peak process RSS: RSS values are before-and-after snapshots.
|
|
153
|
+
|
|
154
|
+
Results depend on the model, input data, software stack, machine, and background load. Compare runs under similar conditions and treat these measurements as local observations, not service-level guarantees.
|
|
155
|
+
|
|
156
|
+
## Documentation
|
|
157
|
+
|
|
158
|
+
- [Quickstart](https://modeldiet.github.io/modeldiet/quickstart/)
|
|
159
|
+
- [Application guide](https://modeldiet.github.io/modeldiet/application/)
|
|
160
|
+
- [Measurement definitions](https://modeldiet.github.io/modeldiet/measurements/)
|
|
161
|
+
- [Public API and compatibility policy](https://modeldiet.github.io/modeldiet/api/)
|
|
162
|
+
- [Documentation hosting and local build](https://modeldiet.github.io/modeldiet/hosting/)
|
|
163
|
+
|
|
164
|
+
## Architecture overview
|
|
165
|
+
|
|
166
|
+
```mermaid
|
|
167
|
+
flowchart LR
|
|
168
|
+
A[Your fitted model] --> C[profile]
|
|
169
|
+
B[Representative tabular rows] --> C
|
|
170
|
+
C --> D[ProfileResult]
|
|
171
|
+
D --> E[Metric values and definitions]
|
|
172
|
+
D --> F[JSON export]
|
|
173
|
+
D --> G[Markdown export]
|
|
174
|
+
H[Optional demo benchmark] --> I[Bundled dataset and estimator]
|
|
175
|
+
I --> J[Raw and summary benchmark JSON]
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Core profiling accepts your model and input data. The demonstration benchmark is a separate optional workflow with its own bundled dataset and estimators.
|
|
179
|
+
|
|
180
|
+
## Development and contributions
|
|
181
|
+
|
|
182
|
+
To work on the project, install its test and demo dependencies from a checkout and run the tests:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
python -m pip install -e ".[test,demo,dev]"
|
|
186
|
+
python -m pytest -v
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for contributor setup and quality checks. Contributions and issue reports are welcome.
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
Model Diet is distributed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "modeldiet"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Profile inference performance and resource usage of trained models"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"numpy>=1.24",
|
|
13
|
+
"psutil>=5.9",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
demo = ["pandas>=2", "scikit-learn>=1.3", "xgboost>=2.0"]
|
|
18
|
+
benchmark = ["pandas>=2", "scikit-learn>=1.3"]
|
|
19
|
+
benchmark-xgboost = ["pandas>=2", "scikit-learn>=1.3", "xgboost>=2.0"]
|
|
20
|
+
test = ["pytest>=8", "pytest-cov>=7", "pandas>=2", "build>=1"]
|
|
21
|
+
dev = [
|
|
22
|
+
"ruff>=0.17.3,<0.18",
|
|
23
|
+
"mypy>=2.4.1,<3",
|
|
24
|
+
"pip-audit>=2.10.1,<3",
|
|
25
|
+
"pre-commit>=4.7.0,<5",
|
|
26
|
+
"pandas-stubs>=2",
|
|
27
|
+
"types-psutil>=5.9",
|
|
28
|
+
"twine>=7,<8",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[tool.setuptools]
|
|
32
|
+
package-dir = {"" = "src"}
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
where = ["src"]
|
|
36
|
+
|
|
37
|
+
[tool.mypy]
|
|
38
|
+
python_version = "3.11"
|
|
39
|
+
files = ["src/modeldiet"]
|
|
40
|
+
check_untyped_defs = true
|
|
41
|
+
disallow_untyped_defs = true
|
|
42
|
+
no_implicit_optional = true
|
|
43
|
+
warn_redundant_casts = true
|
|
44
|
+
warn_unused_ignores = true
|
|
45
|
+
show_error_codes = true
|
|
46
|
+
|
|
47
|
+
[[tool.mypy.overrides]]
|
|
48
|
+
module = ["sklearn.*", "xgboost.*"]
|
|
49
|
+
ignore_missing_imports = true
|
|
50
|
+
|
|
51
|
+
[tool.ruff]
|
|
52
|
+
target-version = "py311"
|
|
53
|
+
line-length = 88
|
|
54
|
+
src = ["src", "tests"]
|
|
55
|
+
|
|
56
|
+
[tool.ruff.lint]
|
|
57
|
+
select = ["E4", "E7", "E9", "F", "I", "UP"]
|
|
58
|
+
|
|
59
|
+
[tool.ruff.format]
|
|
60
|
+
quote-style = "double"
|
|
61
|
+
indent-style = "space"
|
|
62
|
+
|
|
63
|
+
[tool.coverage.run]
|
|
64
|
+
branch = true
|
|
65
|
+
source = ["modeldiet"]
|
|
66
|
+
relative_files = true
|
|
67
|
+
patch = ["subprocess"]
|
|
68
|
+
|
|
69
|
+
[tool.coverage.report]
|
|
70
|
+
show_missing = true
|
|
71
|
+
skip_covered = true
|
|
72
|
+
fail_under = 80
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Public Model Diet API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from typing import Any, Literal
|
|
7
|
+
|
|
8
|
+
from modeldiet.profiling.measurements import BaseMeasurement, LatencyMeasurement
|
|
9
|
+
from modeldiet.profiling.orchestrator import run_measurements
|
|
10
|
+
from modeldiet.result import ProfileResult
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def profile(
|
|
14
|
+
model: Any,
|
|
15
|
+
X: Any,
|
|
16
|
+
*,
|
|
17
|
+
measurements: Sequence[BaseMeasurement] | None = None,
|
|
18
|
+
on_error: Literal["raise", "record"] = "raise",
|
|
19
|
+
include_environment: bool = False,
|
|
20
|
+
) -> ProfileResult:
|
|
21
|
+
"""Profile a fitted estimator using the selected measurement strategies."""
|
|
22
|
+
if on_error not in ("raise", "record"):
|
|
23
|
+
raise ValueError("on_error must be 'raise' or 'record'")
|
|
24
|
+
if not callable(getattr(model, "predict", None)):
|
|
25
|
+
raise TypeError("model must provide a callable predict(X) method")
|
|
26
|
+
if X is None:
|
|
27
|
+
raise ValueError("X must contain representative tabular data")
|
|
28
|
+
selected: list[BaseMeasurement]
|
|
29
|
+
if measurements is None:
|
|
30
|
+
selected = [LatencyMeasurement()]
|
|
31
|
+
else:
|
|
32
|
+
selected = list(measurements)
|
|
33
|
+
if not selected:
|
|
34
|
+
raise ValueError("measurements must contain at least one measurement")
|
|
35
|
+
if any(not isinstance(m, BaseMeasurement) for m in selected):
|
|
36
|
+
raise TypeError("each measurement must be a BaseMeasurement instance")
|
|
37
|
+
result = run_measurements(model, X, selected, on_error=on_error)
|
|
38
|
+
if include_environment:
|
|
39
|
+
from dataclasses import replace
|
|
40
|
+
|
|
41
|
+
from modeldiet.profiling.environment import collect_environment
|
|
42
|
+
|
|
43
|
+
return replace(result, environment=collect_environment())
|
|
44
|
+
return result
|