constraintml 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.
Files changed (45) hide show
  1. constraintml-0.1.0/LICENSE +21 -0
  2. constraintml-0.1.0/MANIFEST.in +2 -0
  3. constraintml-0.1.0/PKG-INFO +261 -0
  4. constraintml-0.1.0/README.md +228 -0
  5. constraintml-0.1.0/pyproject.toml +58 -0
  6. constraintml-0.1.0/setup.cfg +4 -0
  7. constraintml-0.1.0/src/constraintml/__init__.py +5 -0
  8. constraintml-0.1.0/src/constraintml/cloud/__init__.py +29 -0
  9. constraintml-0.1.0/src/constraintml/constraints/__init__.py +3 -0
  10. constraintml-0.1.0/src/constraintml/constraints/parsing.py +98 -0
  11. constraintml-0.1.0/src/constraintml/constraints/spec.py +81 -0
  12. constraintml-0.1.0/src/constraintml/constraints/units.py +54 -0
  13. constraintml-0.1.0/src/constraintml/optimization/__init__.py +17 -0
  14. constraintml-0.1.0/src/constraintml/optimization/backends/__init__.py +4 -0
  15. constraintml-0.1.0/src/constraintml/optimization/backends/base.py +29 -0
  16. constraintml-0.1.0/src/constraintml/optimization/backends/pytorch_backend.py +116 -0
  17. constraintml-0.1.0/src/constraintml/optimization/carbon.py +20 -0
  18. constraintml-0.1.0/src/constraintml/optimization/engine.py +76 -0
  19. constraintml-0.1.0/src/constraintml/optimization/planner.py +151 -0
  20. constraintml-0.1.0/src/constraintml/optimization/state.py +49 -0
  21. constraintml-0.1.0/src/constraintml/optimization/strategies.py +34 -0
  22. constraintml-0.1.0/src/constraintml/optimization/telemetry/__init__.py +12 -0
  23. constraintml-0.1.0/src/constraintml/optimization/telemetry/base.py +41 -0
  24. constraintml-0.1.0/src/constraintml/optimization/telemetry/factory.py +34 -0
  25. constraintml-0.1.0/src/constraintml/optimization/telemetry/nvml.py +51 -0
  26. constraintml-0.1.0/src/constraintml/optimization/telemetry/simulated.py +38 -0
  27. constraintml-0.1.0/src/constraintml/py.typed +0 -0
  28. constraintml-0.1.0/src/constraintml/trainers/__init__.py +5 -0
  29. constraintml-0.1.0/src/constraintml/trainers/base.py +211 -0
  30. constraintml-0.1.0/src/constraintml/trainers/green_trainer.py +37 -0
  31. constraintml-0.1.0/src/constraintml/trainers/smart_trainer.py +41 -0
  32. constraintml-0.1.0/src/constraintml.egg-info/PKG-INFO +261 -0
  33. constraintml-0.1.0/src/constraintml.egg-info/SOURCES.txt +43 -0
  34. constraintml-0.1.0/src/constraintml.egg-info/dependency_links.txt +1 -0
  35. constraintml-0.1.0/src/constraintml.egg-info/requires.txt +10 -0
  36. constraintml-0.1.0/src/constraintml.egg-info/top_level.txt +1 -0
  37. constraintml-0.1.0/tests/test_carbon_accounting.py +19 -0
  38. constraintml-0.1.0/tests/test_carbon_savings_demo.py +71 -0
  39. constraintml-0.1.0/tests/test_constraint_parsing.py +109 -0
  40. constraintml-0.1.0/tests/test_engine.py +126 -0
  41. constraintml-0.1.0/tests/test_planner.py +110 -0
  42. constraintml-0.1.0/tests/test_resource_budget_portability_demo.py +61 -0
  43. constraintml-0.1.0/tests/test_telemetry_nvml.py +84 -0
  44. constraintml-0.1.0/tests/test_telemetry_simulated.py +36 -0
  45. constraintml-0.1.0/tests/test_trainers_integration.py +98 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 sofajailgreeting
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,2 @@
1
+ include LICENSE
2
+ include README.md
@@ -0,0 +1,261 @@
1
+ Metadata-Version: 2.4
2
+ Name: constraintml
3
+ Version: 0.1.0
4
+ Summary: A constraint-aware layer that wraps existing ML training frameworks to reduce energy/carbon costs and support budgetary compliance.
5
+ Author-email: sofajailgreeting <ercoleluke@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/sofajailgreeting/ConstraintML
8
+ Project-URL: Repository, https://github.com/sofajailgreeting/ConstraintML
9
+ Project-URL: Issues, https://github.com/sofajailgreeting/ConstraintML/issues
10
+ Keywords: pytorch,machine-learning,sustainability,carbon,energy,green-ai,mlops
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: torch>=2.0
26
+ Provides-Extra: nvidia
27
+ Requires-Dist: pynvml>=11.5.0; extra == "nvidia"
28
+ Provides-Extra: viz
29
+ Requires-Dist: matplotlib>=3.7; extra == "viz"
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=7; extra == "dev"
32
+ Dynamic: license-file
33
+
34
+ # ConstraintML
35
+
36
+ A constraint-aware layer that wraps PyTorch training so you specify **budgets** — energy, carbon, cost, a deadline, an acceptable accuracy loss — instead of manually tuning precision, batch size, gradient accumulation, checkpointing, and early stopping by hand. A deterministic, rule-based runtime observes training as it runs and adjusts execution strategy to satisfy those budgets while preserving model quality. ConstraintML looks to create a shift from today's configuration-driven ML tooling toward constraint-driven ML infrastructure, enabling organizations to easily configure production flows that fit into various budgetary and compliance requirements.
37
+
38
+ ```python
39
+ trainer = GreenTrainer(
40
+ model=model,
41
+ optimizer=optimizer,
42
+ energy_budget=100, # kWh
43
+ carbon_budget=25, # kg CO2e
44
+ deadline="8h",
45
+ )
46
+ trainer.train()
47
+ ```
48
+
49
+ ## Status
50
+
51
+ v1 is implemented and working, scoped to **PyTorch only**. `ConstraintTrainer`, `GreenTrainer`, and `SmartTrainer` (`src/constraintml/`) wrap a real PyTorch training loop, backed by:
52
+
53
+ - a deterministic, rule-based `ConstraintPlanner` — explicitly a heuristic, not a learned/ML optimizer, despite the project's tooling category
54
+ - a `RuntimeOptimizationEngine` that samples telemetry every step and applies planner decisions to the backend
55
+ - hardware telemetry that uses real NVIDIA NVML power readings when available (optional `pynvml` dependency) and falls back to a simulated constant-wattage collector otherwise, so the library imports and trains on CPU-only machines with no GPU at all
56
+
57
+ 62 tests pass (1 additional test is skipped unless it's run on a real NVIDIA GPU with NVML available) covering constraint parsing, the planner's decision ladder, telemetry, carbon accounting, and end-to-end trainer integration. See `examples/` below for runnable demonstrations with real output.
58
+
59
+ ## Installation
60
+
61
+ ```bash
62
+ pip install -e ".[dev]" # editable install + pytest
63
+ pip install -e ".[dev,nvidia]" # + real NVML GPU telemetry via pynvml
64
+ pip install -e ".[dev,viz]" # + matplotlib, for the example charts below
65
+ ```
66
+
67
+ Once published:
68
+ ```bash
69
+ pip install git+https://github.com/sofajailgreeting/ConstraintML
70
+ ```
71
+
72
+ Requires Python 3.9+ and PyTorch 2.0+ (`torch>=2.0` is the only hard runtime dependency).
73
+
74
+ ## Running
75
+
76
+ ```bash
77
+ pytest # full test suite, no GPU required
78
+ pytest tests/test_planner.py::test_ladder_progression_through_all_levers # a single test
79
+
80
+ python examples/green_trainer_quickstart.py # end-to-end sanity check, CPU-only
81
+ python examples/carbon_savings_demo.py # baseline vs. deadline-constrained run
82
+ python examples/resource_budget_portability_demo.py # same deadline across two model sizes
83
+ python examples/execution_strategy_ladder_demo.py # forces the full lever ladder end-to-end
84
+ ```
85
+
86
+ ## Usage
87
+
88
+ ```python
89
+ trainer = GreenTrainer(
90
+ model=model,
91
+ optimizer=optimizer,
92
+ energy_budget=100, # kWh
93
+ carbon_budget=25, # kg CO2e
94
+ deadline="8h",
95
+ )
96
+ trainer.train()
97
+ ```
98
+
99
+ ```python
100
+ trainer = SmartTrainer(
101
+ optimize_for="carbon",
102
+ carbon_budget="8 kgCO2e",
103
+ )
104
+ # model/optimizer/train_loader can be attached later via trainer.attach(...) --
105
+ # useful when not available at construction time.
106
+ trainer.train()
107
+ ```
108
+
109
+ ```python
110
+ trainer = ConstraintTrainer(
111
+ model=model,
112
+ constraints={
113
+ "energy": "<50kWh",
114
+ "carbon": "<10kgCO2e",
115
+ "deadline": "8h",
116
+ "max_accuracy_loss": "0.5%",
117
+ },
118
+ )
119
+ trainer.train()
120
+ ```
121
+
122
+ `GreenTrainer` fixes `optimize_for="accuracy"` and hides that parameter. `SmartTrainer` exposes `optimize_for` as its primary knob (used as a planner tie-breaker among competing budgets, not a different algorithm). `ConstraintTrainer` is the shared base both build on.
123
+
124
+ ## v1 Architecture
125
+
126
+ Layering, thinnest/user-facing at the top:
127
+
128
+ ```
129
+ Application
130
+
131
+ trainers/ <- ConstraintTrainer, GreenTrainer, SmartTrainer (thin, user-facing)
132
+
133
+ optimization/ <- planner, engine, telemetry, carbon accounting, backends
134
+
135
+ PyTorch
136
+ ```
137
+
138
+ - **`constraints/`** — parses user-facing budget strings (`"8h"`, `"<50kWh"`, `"25 kg CO2e"`, `"0.5%"`) into a typed, frozen `ConstraintSpec`. Shared by `trainers/` and `optimization/planner.py`.
139
+ - **`trainers/`** — `ConstraintTrainer` (`base.py`) merges a `constraints={...}` dict with named kwargs into one `ConstraintSpec` (named kwargs win), supports deferred `model`/`optimizer` binding via `.attach()`, and owns the training loop in `.train()`, wiring telemetry + planner + backend together via `RuntimeOptimizationEngine` and returning a `TrainingReport`.
140
+ - **`optimization/planner.py`** — `ConstraintPlanner`: every `evaluate_every_n_steps`, linearly projects cumulative energy/carbon/cost to end-of-run, compares against budgets, and — if the worst metric is trending over the 85% warning threshold — applies the next untried lever in a fixed ladder:
141
+
142
+ **reduce precision (fp32→fp16) → enable activation checkpointing → grow batch size (capped at 4× initial) → increase gradient accumulation → early stop.**
143
+
144
+ An accuracy-loss guard disables further precision reduction once validation accuracy has regressed too far; hysteresis requires two consecutive over-warning evaluations before early-stopping (bypassed immediately if a budget is already exceeded). The ladder is monotonic — v1 never steps back down once a lever is used.
145
+ - **`optimization/engine.py`** — `RuntimeOptimizationEngine`: per-step `begin_step()`/`end_step()` sample telemetry, integrate energy (trapezoidal power integration), update carbon/cost via `CarbonModel`, and apply any resulting `ExecutionStrategy` to the backend on planner-evaluation steps.
146
+ - **`optimization/telemetry/`** — `TelemetryCollector` interface. `NVMLTelemetryCollector` lazily imports `pynvml` for real GPU power/utilization/temperature. `SimulatedTelemetryCollector` (constant assumed wattage) is the guaranteed CPU-safe fallback. `factory.get_telemetry_collector()` auto-selects between them by probing `pynvml.nvmlInit()`.
147
+ - **`optimization/backends/pytorch_backend.py`** — `PyTorchBackend`, the only real `ExecutionBackend`: real `torch.autocast` precision switches (dtype chosen per device — `bfloat16` on CPU, `float16` on CUDA), batch-size growth by rebuilding the `DataLoader` (map-style datasets only), gradient-accumulation micro-stepping, and `checkpoint_sequential`-based activation checkpointing (`nn.Sequential` models only — logs a warning and no-ops for arbitrary models it can't safely wrap).
148
+ - **`optimization/carbon.py`** — `CarbonModel`: a static `energy_kwh * intensity_kg_per_kwh` conversion (default 0.4 kg CO2e/kWh, a rough global-average grid estimate), overridable per-trainer.
149
+ - **`cloud/`** — a docstring-only `CloudAdapter` Protocol stub. No network/auth code; not used (yet) for v1 — exists purely so `CarbonModel.source` and future regional cost/carbon lookups have a defined shape to grow into.
150
+
151
+ ## Examples & Results
152
+
153
+ Four runnable scripts in `examples/`, in increasing order of what they demonstrate. The numbers below are one real run on a CPU-only dev machine (`SimulatedTelemetryCollector`, fixed random seeds) — exact energy/time figures will vary by hardware and machine load, but the qualitative story (relative proportions, which lever fires, stop reasons) is what to look at, and each script prints/plots its own numbers when you run it.
154
+
155
+ ### 1. `green_trainer_quickstart.py` — minimal sanity check
156
+ Trains a tiny model on synthetic data, CPU-only, no GPU or extras required.
157
+ ```
158
+ Steps completed: 65
159
+ Epochs completed: 5
160
+ Energy used: 0.000002 kWh
161
+ Carbon emitted: 0.000001 kg CO2e
162
+ Final strategy: ExecutionStrategy(precision='fp32', batch_size=16, grad_accumulation_steps=1, activation_checkpointing=False)
163
+ ```
164
+
165
+ ### 2. `carbon_savings_demo.py` — deadline-triggered early stop, measured end-to-end
166
+ Baseline (no budget) vs. `GreenTrainer` with a `deadline` set to 40% of the baseline's wall time, on a task designed to plateau then mildly overfit. `energy_budget`/`carbon_budget` are also passed (with generous headroom) so they never bind — `deadline` (a live `elapsed_seconds / deadline_seconds` ratio) is the only thing that actually triggers the stop here. Energy/carbon budgets are projected by extrapolating the current per-step rate to the end of the run instead, so a tight one would fire almost immediately rather than partway through; see the script's docstring for the full explanation.
167
+ ```
168
+ Baseline Constrained
169
+ Epochs completed 25 14
170
+ Energy (kWh) 0.000073 0.000045
171
+ Carbon (kg CO2e) 0.000029 0.000018
172
+ Final val accuracy* -0.0878 -0.0859
173
+
174
+ Energy saved: 38.3%
175
+ Carbon saved: 38.3% (mirrors energy 1:1 -- CarbonModel is a static multiplier, not an independent measurement)
176
+ Accuracy delta: +0.0019 (constrained is actually *better* here -- stopping near the plateau avoided the overfitting tail)
177
+ ```
178
+ *accuracy is `-avg validation loss`, not a task-specific accuracy metric.
179
+
180
+ **What this does and doesn't show:** the energy/carbon savings above are a real, measured side effect of stopping sooner on the clock — not evidence that ConstraintML detected an accuracy plateau or weighed carbon against accuracy to pick this stopping point. The planner has no such lookahead; `deadline` is a plain wall-clock ratio (see `_fractions` in `optimization/planner.py`). The accuracy-neutral (here, slightly better) outcome is a property of this dataset, which is deliberately constructed to plateau then mildly overfit, and of `deadline` having been calibrated after the fact from the baseline's own wall time — a hand-picked epoch count inspecting the same curve would land similarly, for this one model on this one machine. What the example *does* demonstrate honestly: the full telemetry → energy integration → carbon conversion → report pipeline runs end-to-end on real, non-mocked numbers, and a `deadline` constraint gives you a resource-anchored stand-in for a hardcoded epoch count that costs nothing here. Whether that portability actually holds *across* model size and hardware — the harder and more useful claim — is what Example 3 tests.
181
+
182
+ ### 3. `resource_budget_portability_demo.py` — the actual differentiator
183
+ The same hardcoded epoch count (30) vs. the same `deadline`, applied first to a small model, then to a 24x-wider model, on identical data:
184
+ ```
185
+ elapsed x budget epochs
186
+ Small model, naive fixed-epoch 1.328s 1.00x 30
187
+ Small model, ConstraintML deadline 1.288s 0.97x 30
188
+ Large model, naive fixed-epoch 5.707s 4.30x 30
189
+ Large model, ConstraintML deadline 1.722s 1.30x 10
190
+ ```
191
+ Reusing the same epoch count on the larger model blows the same real time budget by 4.3x, because an epoch count carries no information about actual compute cost. The same `deadline` constraint instead self-adjusts to 10 epochs and lands at 1.3x, automatically, with no re-tuning. **This is the real differentiator of ConstraintML, especially in v1** — not smarter plateau detection, but a resource constraint that stays meaningful across model size and hardware.
192
+
193
+ ### 4. `execution_strategy_ladder_demo.py` — the full lever ladder
194
+ A deliberately tiny `energy_budget` (2% of one measured unconstrained epoch) forces `ConstraintPlanner` through every lever, against a real `PyTorchBackend`:
195
+ ```
196
+ step 5: REDUCE_PRECISION -> precision=fp16
197
+ step 10: ENABLE_CHECKPOINTING -> activation_checkpointing=True
198
+ step 15-30: INCREASE_BATCH_SIZE -> batch_size 15 -> 22 -> 33 -> 40 (capped at 4x initial)
199
+ step 35-65: INCREASE_GRAD_ACCUM -> grad_accumulation_steps 2 -> 8 (capped)
200
+ Stopped early: True, after 70 steps spanning 6 real epochs
201
+ Stop reason: "energy projected at 126008% of budget; all optimization levers exhausted."
202
+ ```
203
+ This does **not** claim these levers make training faster on CPU — see the script's docstring and the CPU autocast note below. It demonstrates that the ladder itself executes correctly end-to-end against real training code (real autocast, real `checkpoint_sequential`, a real `DataLoader` rebuild), not just in a unit test against a mocked planner state.
204
+
205
+ ## Current Scope: What This Means for Production Teams
206
+
207
+ v1 targets a narrower use case than the long-term vision below. Concretely:
208
+
209
+ **Good fit today:**
210
+ - Single-process PyTorch training jobs (one GPU, or CPU-only) where you want automatic, soft guardrails against blowing a time, energy, carbon, or cost budget.
211
+ - Teams who currently hardcode an epoch count "that felt about right" and want that number to keep working when the model or hardware changes, without re-tuning — this is exactly what `resource_budget_portability_demo.py` demonstrates.
212
+ - Environments with no GPU at all: the library never requires a GPU or `pynvml`; `SimulatedTelemetryCollector` is a real, tested fallback, not a stub.
213
+
214
+ **Not yet a fit:**
215
+ - **Multi-GPU or distributed training** — v1 has no distributed strategy switching; the planner and backend assume a single device.
216
+ - **Regulatory- or audit-grade carbon accounting** — `CarbonModel` is a static `energy_kwh * 0.4` global-average multiplier, not a regional or real-time grid intensity figure. Treat carbon numbers as directional, not compliance-ready, until a `cloud/` adapter supplies real intensity data.
217
+ - **Cost-sensitive cloud scheduling** — there's no cloud pricing/spot integration; `cost_budget` exists in the API but has no cloud cost signal behind it today.
218
+ - **Guaranteed speedups from precision reduction** — the `REDUCE_PRECISION` lever's real-world payoff is hardware-dependent. Measured on this project's dev CPU, `torch.autocast(dtype=torch.float16)` was ~44x *slower* than fp32 (CPUs lack native fp16 compute kernels and fall back to emulation); v1 uses `bfloat16` on CPU instead to avoid that catastrophic case, but even `bfloat16` wasn't a clear win on hardware without low-precision acceleration. As such, expect real wins mainly on CUDA GPUs with tensor cores.
219
+ - **Precision "recovery"** — once the planner reduces precision or grows batch size, v1 never steps back down, even if usage later drops back under budget. This is a deliberate, monotonic simplification (to avoid oscillation), not an oversight.
220
+
221
+ ## What's Out of Scope for v1 (documented extension points)
222
+
223
+ These are architectural extension points the codebase is designed to grow into — **not committed or scheduled features**:
224
+
225
+ - JAX / TensorFlow execution backends (v1 is PyTorch-only)
226
+ - Real cloud adapters (AWS / Azure / GCP) for regional carbon intensity, spot pricing, and instance recommendations — `cloud/` currently ships only a docstring-only `CloudAdapter` Protocol stub
227
+ - FP8 / INT8 execution (named in the `ExecutionStrategy` precision enum for API completeness, never selected by v1's planner or backend)
228
+ - AMD ROCm SMI / Intel RAPL telemetry (NVIDIA NVML and the CPU-fallback collector only today)
229
+ - Multi-GPU / distributed strategy switching
230
+ - Pause/resume or checkpoint migration across hardware
231
+ - Reporting/visualization dashboards beyond the `[viz]` matplotlib charts in `examples/`
232
+ - Precision "recovery" (stepping back up once back under budget)
233
+
234
+ If your use case depends on any of these, treat it as a gap to plan around today rather than an assumed near-term feature — check current project status directly before committing to a timeline around any of them.
235
+
236
+ ## Long-Term Vision
237
+
238
+ Beyond v1's PyTorch-only, single-device scope, the project's direction is to treat energy, carbon, execution time, and cost as first-class optimization objectives, on equal footing with accuracy, across any ML framework and any hardware:
239
+
240
+ - **Local-first, cloud-independent**: run unmodified on a laptop, an on-prem GPU server, a university HPC cluster, a Kubernetes cluster, or any cloud provider, gathering telemetry directly from hardware (NVML, ROCm SMI, RAPL) rather than depending on cloud-provider APIs.
241
+ - **Genuine multi-objective planning**: evolve `ConstraintPlanner` from today's fixed rule-based ladder toward optimizing across competing goals simultaneously (accuracy vs. energy vs. carbon vs. deadline vs. cost), and toward hardware-specific execution kernel selection and dynamic parallelism control.
242
+ - **Optional cloud adapters**: `cloud/`'s `CloudAdapter` Protocol is meant to eventually supply regional carbon intensity, electricity pricing, spot pricing, and checkpoint-migration signals — extending the planner's visibility without replacing its core logic or requiring cloud auth for the base library to function.
243
+
244
+ This is a direction the architecture is designed to grow into, not a promise of any specific feature landing in any specific release - although most of this should be expected sooner rather than later.
245
+
246
+ ## Publishing (for maintainers)
247
+
248
+ Packaging is `pyproject.toml`-only (PEP 517/518 via `setuptools.build_meta`). To build and upload a release:
249
+
250
+ ```bash
251
+ python -m pip install --upgrade build twine
252
+ python -m build # produces dist/*.whl and dist/*.tar.gz
253
+ python -m twine check dist/* # validates metadata/README rendering before upload
254
+ python -m twine upload dist/* # prompts for PyPI credentials (or use a PyPI API token)
255
+ ```
256
+
257
+ Bump `version` in `pyproject.toml` (and `__version__` in `src/constraintml/__init__.py`, which is not currently read from the package metadata) before each release. `MANIFEST.in` ensures `LICENSE` and `README.md` are included in the sdist.
258
+
259
+ ## License
260
+
261
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,228 @@
1
+ # ConstraintML
2
+
3
+ A constraint-aware layer that wraps PyTorch training so you specify **budgets** — energy, carbon, cost, a deadline, an acceptable accuracy loss — instead of manually tuning precision, batch size, gradient accumulation, checkpointing, and early stopping by hand. A deterministic, rule-based runtime observes training as it runs and adjusts execution strategy to satisfy those budgets while preserving model quality. ConstraintML looks to create a shift from today's configuration-driven ML tooling toward constraint-driven ML infrastructure, enabling organizations to easily configure production flows that fit into various budgetary and compliance requirements.
4
+
5
+ ```python
6
+ trainer = GreenTrainer(
7
+ model=model,
8
+ optimizer=optimizer,
9
+ energy_budget=100, # kWh
10
+ carbon_budget=25, # kg CO2e
11
+ deadline="8h",
12
+ )
13
+ trainer.train()
14
+ ```
15
+
16
+ ## Status
17
+
18
+ v1 is implemented and working, scoped to **PyTorch only**. `ConstraintTrainer`, `GreenTrainer`, and `SmartTrainer` (`src/constraintml/`) wrap a real PyTorch training loop, backed by:
19
+
20
+ - a deterministic, rule-based `ConstraintPlanner` — explicitly a heuristic, not a learned/ML optimizer, despite the project's tooling category
21
+ - a `RuntimeOptimizationEngine` that samples telemetry every step and applies planner decisions to the backend
22
+ - hardware telemetry that uses real NVIDIA NVML power readings when available (optional `pynvml` dependency) and falls back to a simulated constant-wattage collector otherwise, so the library imports and trains on CPU-only machines with no GPU at all
23
+
24
+ 62 tests pass (1 additional test is skipped unless it's run on a real NVIDIA GPU with NVML available) covering constraint parsing, the planner's decision ladder, telemetry, carbon accounting, and end-to-end trainer integration. See `examples/` below for runnable demonstrations with real output.
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ pip install -e ".[dev]" # editable install + pytest
30
+ pip install -e ".[dev,nvidia]" # + real NVML GPU telemetry via pynvml
31
+ pip install -e ".[dev,viz]" # + matplotlib, for the example charts below
32
+ ```
33
+
34
+ Once published:
35
+ ```bash
36
+ pip install git+https://github.com/sofajailgreeting/ConstraintML
37
+ ```
38
+
39
+ Requires Python 3.9+ and PyTorch 2.0+ (`torch>=2.0` is the only hard runtime dependency).
40
+
41
+ ## Running
42
+
43
+ ```bash
44
+ pytest # full test suite, no GPU required
45
+ pytest tests/test_planner.py::test_ladder_progression_through_all_levers # a single test
46
+
47
+ python examples/green_trainer_quickstart.py # end-to-end sanity check, CPU-only
48
+ python examples/carbon_savings_demo.py # baseline vs. deadline-constrained run
49
+ python examples/resource_budget_portability_demo.py # same deadline across two model sizes
50
+ python examples/execution_strategy_ladder_demo.py # forces the full lever ladder end-to-end
51
+ ```
52
+
53
+ ## Usage
54
+
55
+ ```python
56
+ trainer = GreenTrainer(
57
+ model=model,
58
+ optimizer=optimizer,
59
+ energy_budget=100, # kWh
60
+ carbon_budget=25, # kg CO2e
61
+ deadline="8h",
62
+ )
63
+ trainer.train()
64
+ ```
65
+
66
+ ```python
67
+ trainer = SmartTrainer(
68
+ optimize_for="carbon",
69
+ carbon_budget="8 kgCO2e",
70
+ )
71
+ # model/optimizer/train_loader can be attached later via trainer.attach(...) --
72
+ # useful when not available at construction time.
73
+ trainer.train()
74
+ ```
75
+
76
+ ```python
77
+ trainer = ConstraintTrainer(
78
+ model=model,
79
+ constraints={
80
+ "energy": "<50kWh",
81
+ "carbon": "<10kgCO2e",
82
+ "deadline": "8h",
83
+ "max_accuracy_loss": "0.5%",
84
+ },
85
+ )
86
+ trainer.train()
87
+ ```
88
+
89
+ `GreenTrainer` fixes `optimize_for="accuracy"` and hides that parameter. `SmartTrainer` exposes `optimize_for` as its primary knob (used as a planner tie-breaker among competing budgets, not a different algorithm). `ConstraintTrainer` is the shared base both build on.
90
+
91
+ ## v1 Architecture
92
+
93
+ Layering, thinnest/user-facing at the top:
94
+
95
+ ```
96
+ Application
97
+
98
+ trainers/ <- ConstraintTrainer, GreenTrainer, SmartTrainer (thin, user-facing)
99
+
100
+ optimization/ <- planner, engine, telemetry, carbon accounting, backends
101
+
102
+ PyTorch
103
+ ```
104
+
105
+ - **`constraints/`** — parses user-facing budget strings (`"8h"`, `"<50kWh"`, `"25 kg CO2e"`, `"0.5%"`) into a typed, frozen `ConstraintSpec`. Shared by `trainers/` and `optimization/planner.py`.
106
+ - **`trainers/`** — `ConstraintTrainer` (`base.py`) merges a `constraints={...}` dict with named kwargs into one `ConstraintSpec` (named kwargs win), supports deferred `model`/`optimizer` binding via `.attach()`, and owns the training loop in `.train()`, wiring telemetry + planner + backend together via `RuntimeOptimizationEngine` and returning a `TrainingReport`.
107
+ - **`optimization/planner.py`** — `ConstraintPlanner`: every `evaluate_every_n_steps`, linearly projects cumulative energy/carbon/cost to end-of-run, compares against budgets, and — if the worst metric is trending over the 85% warning threshold — applies the next untried lever in a fixed ladder:
108
+
109
+ **reduce precision (fp32→fp16) → enable activation checkpointing → grow batch size (capped at 4× initial) → increase gradient accumulation → early stop.**
110
+
111
+ An accuracy-loss guard disables further precision reduction once validation accuracy has regressed too far; hysteresis requires two consecutive over-warning evaluations before early-stopping (bypassed immediately if a budget is already exceeded). The ladder is monotonic — v1 never steps back down once a lever is used.
112
+ - **`optimization/engine.py`** — `RuntimeOptimizationEngine`: per-step `begin_step()`/`end_step()` sample telemetry, integrate energy (trapezoidal power integration), update carbon/cost via `CarbonModel`, and apply any resulting `ExecutionStrategy` to the backend on planner-evaluation steps.
113
+ - **`optimization/telemetry/`** — `TelemetryCollector` interface. `NVMLTelemetryCollector` lazily imports `pynvml` for real GPU power/utilization/temperature. `SimulatedTelemetryCollector` (constant assumed wattage) is the guaranteed CPU-safe fallback. `factory.get_telemetry_collector()` auto-selects between them by probing `pynvml.nvmlInit()`.
114
+ - **`optimization/backends/pytorch_backend.py`** — `PyTorchBackend`, the only real `ExecutionBackend`: real `torch.autocast` precision switches (dtype chosen per device — `bfloat16` on CPU, `float16` on CUDA), batch-size growth by rebuilding the `DataLoader` (map-style datasets only), gradient-accumulation micro-stepping, and `checkpoint_sequential`-based activation checkpointing (`nn.Sequential` models only — logs a warning and no-ops for arbitrary models it can't safely wrap).
115
+ - **`optimization/carbon.py`** — `CarbonModel`: a static `energy_kwh * intensity_kg_per_kwh` conversion (default 0.4 kg CO2e/kWh, a rough global-average grid estimate), overridable per-trainer.
116
+ - **`cloud/`** — a docstring-only `CloudAdapter` Protocol stub. No network/auth code; not used (yet) for v1 — exists purely so `CarbonModel.source` and future regional cost/carbon lookups have a defined shape to grow into.
117
+
118
+ ## Examples & Results
119
+
120
+ Four runnable scripts in `examples/`, in increasing order of what they demonstrate. The numbers below are one real run on a CPU-only dev machine (`SimulatedTelemetryCollector`, fixed random seeds) — exact energy/time figures will vary by hardware and machine load, but the qualitative story (relative proportions, which lever fires, stop reasons) is what to look at, and each script prints/plots its own numbers when you run it.
121
+
122
+ ### 1. `green_trainer_quickstart.py` — minimal sanity check
123
+ Trains a tiny model on synthetic data, CPU-only, no GPU or extras required.
124
+ ```
125
+ Steps completed: 65
126
+ Epochs completed: 5
127
+ Energy used: 0.000002 kWh
128
+ Carbon emitted: 0.000001 kg CO2e
129
+ Final strategy: ExecutionStrategy(precision='fp32', batch_size=16, grad_accumulation_steps=1, activation_checkpointing=False)
130
+ ```
131
+
132
+ ### 2. `carbon_savings_demo.py` — deadline-triggered early stop, measured end-to-end
133
+ Baseline (no budget) vs. `GreenTrainer` with a `deadline` set to 40% of the baseline's wall time, on a task designed to plateau then mildly overfit. `energy_budget`/`carbon_budget` are also passed (with generous headroom) so they never bind — `deadline` (a live `elapsed_seconds / deadline_seconds` ratio) is the only thing that actually triggers the stop here. Energy/carbon budgets are projected by extrapolating the current per-step rate to the end of the run instead, so a tight one would fire almost immediately rather than partway through; see the script's docstring for the full explanation.
134
+ ```
135
+ Baseline Constrained
136
+ Epochs completed 25 14
137
+ Energy (kWh) 0.000073 0.000045
138
+ Carbon (kg CO2e) 0.000029 0.000018
139
+ Final val accuracy* -0.0878 -0.0859
140
+
141
+ Energy saved: 38.3%
142
+ Carbon saved: 38.3% (mirrors energy 1:1 -- CarbonModel is a static multiplier, not an independent measurement)
143
+ Accuracy delta: +0.0019 (constrained is actually *better* here -- stopping near the plateau avoided the overfitting tail)
144
+ ```
145
+ *accuracy is `-avg validation loss`, not a task-specific accuracy metric.
146
+
147
+ **What this does and doesn't show:** the energy/carbon savings above are a real, measured side effect of stopping sooner on the clock — not evidence that ConstraintML detected an accuracy plateau or weighed carbon against accuracy to pick this stopping point. The planner has no such lookahead; `deadline` is a plain wall-clock ratio (see `_fractions` in `optimization/planner.py`). The accuracy-neutral (here, slightly better) outcome is a property of this dataset, which is deliberately constructed to plateau then mildly overfit, and of `deadline` having been calibrated after the fact from the baseline's own wall time — a hand-picked epoch count inspecting the same curve would land similarly, for this one model on this one machine. What the example *does* demonstrate honestly: the full telemetry → energy integration → carbon conversion → report pipeline runs end-to-end on real, non-mocked numbers, and a `deadline` constraint gives you a resource-anchored stand-in for a hardcoded epoch count that costs nothing here. Whether that portability actually holds *across* model size and hardware — the harder and more useful claim — is what Example 3 tests.
148
+
149
+ ### 3. `resource_budget_portability_demo.py` — the actual differentiator
150
+ The same hardcoded epoch count (30) vs. the same `deadline`, applied first to a small model, then to a 24x-wider model, on identical data:
151
+ ```
152
+ elapsed x budget epochs
153
+ Small model, naive fixed-epoch 1.328s 1.00x 30
154
+ Small model, ConstraintML deadline 1.288s 0.97x 30
155
+ Large model, naive fixed-epoch 5.707s 4.30x 30
156
+ Large model, ConstraintML deadline 1.722s 1.30x 10
157
+ ```
158
+ Reusing the same epoch count on the larger model blows the same real time budget by 4.3x, because an epoch count carries no information about actual compute cost. The same `deadline` constraint instead self-adjusts to 10 epochs and lands at 1.3x, automatically, with no re-tuning. **This is the real differentiator of ConstraintML, especially in v1** — not smarter plateau detection, but a resource constraint that stays meaningful across model size and hardware.
159
+
160
+ ### 4. `execution_strategy_ladder_demo.py` — the full lever ladder
161
+ A deliberately tiny `energy_budget` (2% of one measured unconstrained epoch) forces `ConstraintPlanner` through every lever, against a real `PyTorchBackend`:
162
+ ```
163
+ step 5: REDUCE_PRECISION -> precision=fp16
164
+ step 10: ENABLE_CHECKPOINTING -> activation_checkpointing=True
165
+ step 15-30: INCREASE_BATCH_SIZE -> batch_size 15 -> 22 -> 33 -> 40 (capped at 4x initial)
166
+ step 35-65: INCREASE_GRAD_ACCUM -> grad_accumulation_steps 2 -> 8 (capped)
167
+ Stopped early: True, after 70 steps spanning 6 real epochs
168
+ Stop reason: "energy projected at 126008% of budget; all optimization levers exhausted."
169
+ ```
170
+ This does **not** claim these levers make training faster on CPU — see the script's docstring and the CPU autocast note below. It demonstrates that the ladder itself executes correctly end-to-end against real training code (real autocast, real `checkpoint_sequential`, a real `DataLoader` rebuild), not just in a unit test against a mocked planner state.
171
+
172
+ ## Current Scope: What This Means for Production Teams
173
+
174
+ v1 targets a narrower use case than the long-term vision below. Concretely:
175
+
176
+ **Good fit today:**
177
+ - Single-process PyTorch training jobs (one GPU, or CPU-only) where you want automatic, soft guardrails against blowing a time, energy, carbon, or cost budget.
178
+ - Teams who currently hardcode an epoch count "that felt about right" and want that number to keep working when the model or hardware changes, without re-tuning — this is exactly what `resource_budget_portability_demo.py` demonstrates.
179
+ - Environments with no GPU at all: the library never requires a GPU or `pynvml`; `SimulatedTelemetryCollector` is a real, tested fallback, not a stub.
180
+
181
+ **Not yet a fit:**
182
+ - **Multi-GPU or distributed training** — v1 has no distributed strategy switching; the planner and backend assume a single device.
183
+ - **Regulatory- or audit-grade carbon accounting** — `CarbonModel` is a static `energy_kwh * 0.4` global-average multiplier, not a regional or real-time grid intensity figure. Treat carbon numbers as directional, not compliance-ready, until a `cloud/` adapter supplies real intensity data.
184
+ - **Cost-sensitive cloud scheduling** — there's no cloud pricing/spot integration; `cost_budget` exists in the API but has no cloud cost signal behind it today.
185
+ - **Guaranteed speedups from precision reduction** — the `REDUCE_PRECISION` lever's real-world payoff is hardware-dependent. Measured on this project's dev CPU, `torch.autocast(dtype=torch.float16)` was ~44x *slower* than fp32 (CPUs lack native fp16 compute kernels and fall back to emulation); v1 uses `bfloat16` on CPU instead to avoid that catastrophic case, but even `bfloat16` wasn't a clear win on hardware without low-precision acceleration. As such, expect real wins mainly on CUDA GPUs with tensor cores.
186
+ - **Precision "recovery"** — once the planner reduces precision or grows batch size, v1 never steps back down, even if usage later drops back under budget. This is a deliberate, monotonic simplification (to avoid oscillation), not an oversight.
187
+
188
+ ## What's Out of Scope for v1 (documented extension points)
189
+
190
+ These are architectural extension points the codebase is designed to grow into — **not committed or scheduled features**:
191
+
192
+ - JAX / TensorFlow execution backends (v1 is PyTorch-only)
193
+ - Real cloud adapters (AWS / Azure / GCP) for regional carbon intensity, spot pricing, and instance recommendations — `cloud/` currently ships only a docstring-only `CloudAdapter` Protocol stub
194
+ - FP8 / INT8 execution (named in the `ExecutionStrategy` precision enum for API completeness, never selected by v1's planner or backend)
195
+ - AMD ROCm SMI / Intel RAPL telemetry (NVIDIA NVML and the CPU-fallback collector only today)
196
+ - Multi-GPU / distributed strategy switching
197
+ - Pause/resume or checkpoint migration across hardware
198
+ - Reporting/visualization dashboards beyond the `[viz]` matplotlib charts in `examples/`
199
+ - Precision "recovery" (stepping back up once back under budget)
200
+
201
+ If your use case depends on any of these, treat it as a gap to plan around today rather than an assumed near-term feature — check current project status directly before committing to a timeline around any of them.
202
+
203
+ ## Long-Term Vision
204
+
205
+ Beyond v1's PyTorch-only, single-device scope, the project's direction is to treat energy, carbon, execution time, and cost as first-class optimization objectives, on equal footing with accuracy, across any ML framework and any hardware:
206
+
207
+ - **Local-first, cloud-independent**: run unmodified on a laptop, an on-prem GPU server, a university HPC cluster, a Kubernetes cluster, or any cloud provider, gathering telemetry directly from hardware (NVML, ROCm SMI, RAPL) rather than depending on cloud-provider APIs.
208
+ - **Genuine multi-objective planning**: evolve `ConstraintPlanner` from today's fixed rule-based ladder toward optimizing across competing goals simultaneously (accuracy vs. energy vs. carbon vs. deadline vs. cost), and toward hardware-specific execution kernel selection and dynamic parallelism control.
209
+ - **Optional cloud adapters**: `cloud/`'s `CloudAdapter` Protocol is meant to eventually supply regional carbon intensity, electricity pricing, spot pricing, and checkpoint-migration signals — extending the planner's visibility without replacing its core logic or requiring cloud auth for the base library to function.
210
+
211
+ This is a direction the architecture is designed to grow into, not a promise of any specific feature landing in any specific release - although most of this should be expected sooner rather than later.
212
+
213
+ ## Publishing (for maintainers)
214
+
215
+ Packaging is `pyproject.toml`-only (PEP 517/518 via `setuptools.build_meta`). To build and upload a release:
216
+
217
+ ```bash
218
+ python -m pip install --upgrade build twine
219
+ python -m build # produces dist/*.whl and dist/*.tar.gz
220
+ python -m twine check dist/* # validates metadata/README rendering before upload
221
+ python -m twine upload dist/* # prompts for PyPI credentials (or use a PyPI API token)
222
+ ```
223
+
224
+ Bump `version` in `pyproject.toml` (and `__version__` in `src/constraintml/__init__.py`, which is not currently read from the package metadata) before each release. `MANIFEST.in` ensures `LICENSE` and `README.md` are included in the sdist.
225
+
226
+ ## License
227
+
228
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,58 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "constraintml"
7
+ version = "0.1.0"
8
+ description = "A constraint-aware layer that wraps existing ML training frameworks to reduce energy/carbon costs and support budgetary compliance."
9
+ readme = { file = "README.md", content-type = "text/markdown" }
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ { name = "sofajailgreeting", email = "ercoleluke@gmail.com" },
14
+ ]
15
+ keywords = [
16
+ "pytorch",
17
+ "machine-learning",
18
+ "sustainability",
19
+ "carbon",
20
+ "energy",
21
+ "green-ai",
22
+ "mlops",
23
+ ]
24
+ classifiers = [
25
+ "Development Status :: 4 - Beta",
26
+ "Intended Audience :: Developers",
27
+ "Intended Audience :: Science/Research",
28
+ "Operating System :: OS Independent",
29
+ "Programming Language :: Python :: 3 :: Only",
30
+ "Programming Language :: Python :: 3.9",
31
+ "Programming Language :: Python :: 3.10",
32
+ "Programming Language :: Python :: 3.11",
33
+ "Programming Language :: Python :: 3.12",
34
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
35
+ "Topic :: Software Development :: Libraries :: Python Modules",
36
+ ]
37
+ dependencies = [
38
+ "torch>=2.0",
39
+ ]
40
+
41
+ [project.urls]
42
+ Homepage = "https://github.com/sofajailgreeting/ConstraintML"
43
+ Repository = "https://github.com/sofajailgreeting/ConstraintML"
44
+ Issues = "https://github.com/sofajailgreeting/ConstraintML/issues"
45
+
46
+ [project.optional-dependencies]
47
+ nvidia = ["pynvml>=11.5.0"]
48
+ viz = ["matplotlib>=3.7"]
49
+ dev = ["pytest>=7"]
50
+
51
+ [tool.setuptools.packages.find]
52
+ where = ["src"]
53
+
54
+ [tool.setuptools.package-data]
55
+ constraintml = ["py.typed"]
56
+
57
+ [tool.pytest.ini_options]
58
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ from .trainers import ConstraintTrainer, GreenTrainer, SmartTrainer, TrainingReport
2
+
3
+ __all__ = ["ConstraintTrainer", "GreenTrainer", "SmartTrainer", "TrainingReport"]
4
+
5
+ __version__ = "0.1.0"
@@ -0,0 +1,29 @@
1
+ """Extension point for optional, provider-specific cloud adapters (AWS/Azure/GCP).
2
+
3
+ For v2+ - not implemented in v1 - ConstraintML is local-first and must work fully without
4
+ any cloud adapter. This module only documents the shape a future adapter would take
5
+ so that `CarbonModel.source` and cost accounting have a defined place to plug into.
6
+ No network or authentication code lives here.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Protocol, runtime_checkable
12
+
13
+
14
+ @runtime_checkable
15
+ class CloudAdapter(Protocol):
16
+ """Shape a future cloud adapter would implement. Not used by v1 -- the
17
+ RuntimeOptimizationEngine never depends on this Protocol being satisfied.
18
+ """
19
+
20
+ def get_carbon_intensity(self, region: str) -> float:
21
+ """Return regional grid carbon intensity in kg CO2e/kWh."""
22
+ ...
23
+
24
+ def get_energy_price(self, region: str) -> float:
25
+ """Return regional electricity price in USD/kWh."""
26
+ ...
27
+
28
+
29
+ __all__ = ["CloudAdapter"]
@@ -0,0 +1,3 @@
1
+ from .spec import ConstraintSpec, OPTIMIZE_FOR_CHOICES
2
+
3
+ __all__ = ["ConstraintSpec", "OPTIMIZE_FOR_CHOICES"]