saggio 1.4.2__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.
- saggio-1.4.2/LICENSE +29 -0
- saggio-1.4.2/PKG-INFO +411 -0
- saggio-1.4.2/README.md +371 -0
- saggio-1.4.2/pyproject.toml +158 -0
- saggio-1.4.2/saggio/__init__.py +198 -0
- saggio-1.4.2/saggio/__main__.py +32 -0
- saggio-1.4.2/saggio/analyze/__init__.py +125 -0
- saggio-1.4.2/saggio/analyze/apple.py +542 -0
- saggio-1.4.2/saggio/analyze/capability.py +469 -0
- saggio-1.4.2/saggio/analyze/cpu.py +389 -0
- saggio-1.4.2/saggio/analyze/llm.py +396 -0
- saggio-1.4.2/saggio/analyze/power/__init__.py +133 -0
- saggio-1.4.2/saggio/analyze/power/accelerator.py +223 -0
- saggio-1.4.2/saggio/analyze/power/graphics.py +185 -0
- saggio-1.4.2/saggio/analyze/power/meter.py +391 -0
- saggio-1.4.2/saggio/analyze/power/rapl.py +156 -0
- saggio-1.4.2/saggio/analyze/power/reading.py +97 -0
- saggio-1.4.2/saggio/analyze/power/soc.py +72 -0
- saggio-1.4.2/saggio/analyze/power/sysfs.py +66 -0
- saggio-1.4.2/saggio/analyze/power/tables.py +196 -0
- saggio-1.4.2/saggio/analyze/run.py +1117 -0
- saggio-1.4.2/saggio/analyze/static/__init__.py +123 -0
- saggio-1.4.2/saggio/analyze/static/archetype.py +197 -0
- saggio-1.4.2/saggio/analyze/static/calls.py +223 -0
- saggio-1.4.2/saggio/analyze/static/findings.py +312 -0
- saggio-1.4.2/saggio/analyze/static/languages.py +44 -0
- saggio-1.4.2/saggio/analyze/static/read.py +83 -0
- saggio-1.4.2/saggio/analyze/static/running.py +267 -0
- saggio-1.4.2/saggio/analyze/static/tables.py +358 -0
- saggio-1.4.2/saggio/analyze/static/walking.py +238 -0
- saggio-1.4.2/saggio/analyze/static/worksize.py +213 -0
- saggio-1.4.2/saggio/auditor/__init__.py +74 -0
- saggio-1.4.2/saggio/auditor/assumptions.py +230 -0
- saggio-1.4.2/saggio/auditor/blocks.py +169 -0
- saggio-1.4.2/saggio/auditor/build.py +331 -0
- saggio-1.4.2/saggio/auditor/measuring.py +172 -0
- saggio-1.4.2/saggio/auditor/naming.py +134 -0
- saggio-1.4.2/saggio/auditor/options.py +144 -0
- saggio-1.4.2/saggio/auditor/paths.py +56 -0
- saggio-1.4.2/saggio/auditor/projections.py +287 -0
- saggio-1.4.2/saggio/catalog/__init__.py +71 -0
- saggio-1.4.2/saggio/catalog/pricing.py +410 -0
- saggio-1.4.2/saggio/catalog/refresh/__init__.py +132 -0
- saggio-1.4.2/saggio/catalog/refresh/carbon.py +408 -0
- saggio-1.4.2/saggio/catalog/refresh/embodied.py +296 -0
- saggio-1.4.2/saggio/catalog/refresh/tariffs.py +350 -0
- saggio-1.4.2/saggio/catalog/refresh/timezones.py +233 -0
- saggio-1.4.2/saggio/catalog/registry.py +804 -0
- saggio-1.4.2/saggio/cli/__init__.py +38 -0
- saggio-1.4.2/saggio/cli/app.py +726 -0
- saggio-1.4.2/saggio/cli/commands.py +1151 -0
- saggio-1.4.2/saggio/cli/exit_codes.py +52 -0
- saggio-1.4.2/saggio/data/__init__.py +27 -0
- saggio-1.4.2/saggio/data/grid.yaml +553 -0
- saggio-1.4.2/saggio/data/hardware.yaml +334 -0
- saggio-1.4.2/saggio/data/instances.yaml +119 -0
- saggio-1.4.2/saggio/data/providers.yaml +74 -0
- saggio-1.4.2/saggio/data/report/i18n.yaml +120 -0
- saggio-1.4.2/saggio/data/report/logo.png +0 -0
- saggio-1.4.2/saggio/data/report/report.css +245 -0
- saggio-1.4.2/saggio/data/report/report.html +38 -0
- saggio-1.4.2/saggio/data/report/report.js +168 -0
- saggio-1.4.2/saggio/data/services.yaml +111 -0
- saggio-1.4.2/saggio/data/templates/annotated.yaml +183 -0
- saggio-1.4.2/saggio/data/templates/minimal.yaml +85 -0
- saggio-1.4.2/saggio/diff.py +529 -0
- saggio-1.4.2/saggio/estimate/__init__.py +124 -0
- saggio-1.4.2/saggio/estimate/context.py +618 -0
- saggio-1.4.2/saggio/estimate/embodied.py +329 -0
- saggio-1.4.2/saggio/estimate/energy.py +580 -0
- saggio-1.4.2/saggio/estimate/equivalences.py +263 -0
- saggio-1.4.2/saggio/estimate/extrapolate.py +939 -0
- saggio-1.4.2/saggio/estimate/machine.py +359 -0
- saggio-1.4.2/saggio/estimate/scaling.py +527 -0
- saggio-1.4.2/saggio/fold.py +453 -0
- saggio-1.4.2/saggio/model/__init__.py +135 -0
- saggio-1.4.2/saggio/model/cost_model.py +614 -0
- saggio-1.4.2/saggio/model/derivation.py +487 -0
- saggio-1.4.2/saggio/model/dimensions.py +371 -0
- saggio-1.4.2/saggio/model/quantity.py +482 -0
- saggio-1.4.2/saggio/model/results.py +279 -0
- saggio-1.4.2/saggio/model/schema.py +170 -0
- saggio-1.4.2/saggio/model/taxonomy.py +186 -0
- saggio-1.4.2/saggio/model/validate.py +1001 -0
- saggio-1.4.2/saggio/report/__init__.py +60 -0
- saggio-1.4.2/saggio/report/dashboard.py +273 -0
- saggio-1.4.2/saggio/report/figures.py +835 -0
- saggio-1.4.2/saggio/report/html.py +1168 -0
- saggio-1.4.2/saggio/report/markdown.py +1001 -0
- saggio-1.4.2/saggio/report/office.py +146 -0
- saggio-1.4.2/saggio/templates.py +112 -0
- saggio-1.4.2/saggio.egg-info/PKG-INFO +411 -0
- saggio-1.4.2/saggio.egg-info/SOURCES.txt +96 -0
- saggio-1.4.2/saggio.egg-info/dependency_links.txt +1 -0
- saggio-1.4.2/saggio.egg-info/entry_points.txt +2 -0
- saggio-1.4.2/saggio.egg-info/requires.txt +13 -0
- saggio-1.4.2/saggio.egg-info/top_level.txt +1 -0
- saggio-1.4.2/setup.cfg +4 -0
saggio-1.4.2/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Warith Harchaoui
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
* Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
saggio-1.4.2/PKG-INFO
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: saggio
|
|
3
|
+
Version: 1.4.2
|
|
4
|
+
Summary: What it costs to run your code, per unit of work, with every number saying how far it can be trusted: money, time, energy, carbon, water, and any dimension you register.
|
|
5
|
+
Author-email: Warith HARCHAOUI <warith.harchaoui@sev7n.io>
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/warith-harchaoui/saggio
|
|
8
|
+
Project-URL: Source, https://github.com/warith-harchaoui/saggio
|
|
9
|
+
Project-URL: Issues, https://github.com/warith-harchaoui/saggio/issues
|
|
10
|
+
Keywords: cost of running,green software,carbon footprint,energy measurement,sustainability,FinOps,Green Algorithms,provenance,reproducibility
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
15
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
16
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering
|
|
23
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
24
|
+
Classifier: Topic :: System :: Benchmark
|
|
25
|
+
Requires-Python: <3.14,>=3.10
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: os-helper<3,>=2.0.0
|
|
29
|
+
Requires-Dist: PyYAML>=6.0
|
|
30
|
+
Requires-Dist: platformdirs>=4.0
|
|
31
|
+
Provides-Extra: office
|
|
32
|
+
Requires-Dist: md2star>=1.0; extra == "office"
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest-xdist>=3.6; extra == "dev"
|
|
37
|
+
Requires-Dist: ruff==0.16.10; extra == "dev"
|
|
38
|
+
Requires-Dist: pillow>=10.0; extra == "dev"
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
# saggio
|
|
42
|
+
|
|
43
|
+
[🇫🇷 LISEZMOI.md](https://github.com/warith-harchaoui/saggio/blob/master/LISEZMOI.md) · 🇬🇧 English
|
|
44
|
+
|
|
45
|
+
**What does it cost to run your code?** Money, time, energy, carbon, water, and
|
|
46
|
+
any other dimension you decide to watch, per unit of work, with every number
|
|
47
|
+
saying how far it can be trusted.
|
|
48
|
+
|
|
49
|
+
<p>
|
|
50
|
+
<img src="https://raw.githubusercontent.com/warith-harchaoui/saggio/master/assets/logo.png" alt="saggio logo">
|
|
51
|
+
</p>
|
|
52
|
+
|
|
53
|
+
The answer is a YAML file you commit next to the code, and reports rendered from
|
|
54
|
+
it. The file is reviewable in a pull request, the reports are readable by people
|
|
55
|
+
who will never open a terminal, and neither of them is allowed to state a number
|
|
56
|
+
without saying where it came from.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install saggio
|
|
60
|
+
|
|
61
|
+
saggio audit . --country FR --run -o cost_of_running.yaml
|
|
62
|
+
saggio render cost_of_running.yaml -f html -o cost_of_running.html
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## The one idea
|
|
66
|
+
|
|
67
|
+
Every number carries a **status**:
|
|
68
|
+
|
|
69
|
+
| Status | What it means |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `measured` | A counter on the machine said so. |
|
|
72
|
+
| `estimated` | A sourced formula or a published figure said so. |
|
|
73
|
+
| `placeholder` | The field is being held open. It is not a number. |
|
|
74
|
+
| `TODO` | A human has to supply this before the model can be trusted. |
|
|
75
|
+
|
|
76
|
+
And every derived number names the numbers it came from:
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
costs:
|
|
80
|
+
carbon:
|
|
81
|
+
value: 0.0014
|
|
82
|
+
unit: "gCO2e"
|
|
83
|
+
status: "estimated"
|
|
84
|
+
derived_from: ["scenarios[0].costs.energy", "assumptions.grid_carbon_intensity"]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
From there the **weakest-link rule** follows, and the validator enforces it: a
|
|
88
|
+
derived value may never claim to be better founded than the worst of its inputs.
|
|
89
|
+
Measure the runtime and the energy becomes measured on its own. Leave the country
|
|
90
|
+
unstated and the carbon figure stays open, because nobody knows it yet.
|
|
91
|
+
|
|
92
|
+
And the derivation is **checked, not just declared**. The validator parses every
|
|
93
|
+
unit down to its base dimensions — a watt *is* a joule per second — and recomputes
|
|
94
|
+
the value from its named inputs, multiplying them and, where that cannot reach the
|
|
95
|
+
stated unit, dividing: amortising an embodied footprint over a lifetime is a
|
|
96
|
+
division, and the model names the lifetime among its inputs just the same. A value
|
|
97
|
+
more than a percent from what its inputs give is an error. Where several readings
|
|
98
|
+
reach the unit and the units cannot say which was meant, a value matching none of
|
|
99
|
+
them is still an error, because it is wrong under every reading. A unit no
|
|
100
|
+
arrangement of the inputs can produce is an error needing no arithmetic at all.
|
|
101
|
+
The mistake this closes is the worst-looking one: a number orders of magnitude
|
|
102
|
+
out, carrying a perfectly correct list of the inputs it supposedly came from,
|
|
103
|
+
reads as better founded than anything else on the page. And where any unit
|
|
104
|
+
involved is one the package does not know — a dimension you registered this
|
|
105
|
+
morning — it says nothing rather than inventing a rule.
|
|
106
|
+
|
|
107
|
+
Three properties fall out of writing it this way, and they are the reason for the
|
|
108
|
+
design:
|
|
109
|
+
|
|
110
|
+
- **Nothing can hide.** The validator walks the whole file. Any number outside a
|
|
111
|
+
quantity is an error, wherever it is and whatever block it is in, so an extra
|
|
112
|
+
block cannot smuggle a figure past the rules.
|
|
113
|
+
- **The rule is general.** Because the derivation is data rather than code, a
|
|
114
|
+
dimension you invented this morning is checked exactly as carefully as carbon.
|
|
115
|
+
- **Nothing is invented.** An unresolved country gives a `TODO`, not a zero. A
|
|
116
|
+
provider that publishes no water figure gives a `TODO`, not a plausible one. A
|
|
117
|
+
run that failed projects to nothing at all.
|
|
118
|
+
|
|
119
|
+
## What it does
|
|
120
|
+
|
|
121
|
+
**Reads your repository.** Languages, workload shape, frameworks, how much work a
|
|
122
|
+
full run performs, which paid APIs you call and on which line. All of it
|
|
123
|
+
deterministic, all of it quoting its evidence.
|
|
124
|
+
|
|
125
|
+
**Runs a slice of it, if you let it.** With `--run`, and after you have agreed
|
|
126
|
+
once, it executes a capped slice of your real entry point, times it, reads
|
|
127
|
+
whatever energy counters the machine publishes to an ordinary user, and profiles
|
|
128
|
+
where the time went. On Linux that is the powercap tree, memory zone included,
|
|
129
|
+
and the graphics driver's own sensor; on an Apple Silicon Mac it is the chip's
|
|
130
|
+
own counters, read without a password; on any machine with an NVIDIA board it is
|
|
131
|
+
the driver. `saggio power` says which of them answer here, and prints what it
|
|
132
|
+
would take to open the ones that do not — without ever opening them itself. The
|
|
133
|
+
size the slice covers is read from your own configuration, so projecting to a
|
|
134
|
+
whole run is arithmetic rather than a guess.
|
|
135
|
+
|
|
136
|
+
**Looks things up rather than assuming them.** What a GPU draws, what a kilowatt
|
|
137
|
+
hour emits in Poland, what overhead a datacenter adds, where an API publishes its
|
|
138
|
+
prices: sourced YAML catalogues, each row carrying the URL it came from and the
|
|
139
|
+
date somebody read it, each one going stale on a schedule that matches how fast
|
|
140
|
+
that kind of fact actually moves.
|
|
141
|
+
|
|
142
|
+
**Projects, and says what it assumed — or measures the assumption away.** From a
|
|
143
|
+
measured slice to a whole run.
|
|
144
|
+
From one accelerator to another, and all the way to money and carbon rather than
|
|
145
|
+
stopping at a duration. The first projection rests on the work being uniform, and `--scaling-steps 3`
|
|
146
|
+
replaces that assumption with a number: three slices of different sizes, a fitted
|
|
147
|
+
exponent, and a refusal to project at all when the fit says the slices are not
|
|
148
|
+
measuring one consistent behaviour. That second projection is a bracket, not a number: work is
|
|
149
|
+
limited by arithmetic throughput or by memory bandwidth, the two ratios differ by
|
|
150
|
+
more than a factor of two between an A100 and an H100, and reporting the compute
|
|
151
|
+
ratio alone would understate the bill by a third. It refuses outright when the
|
|
152
|
+
catalogue has no throughput figure for the precision the work runs in.
|
|
153
|
+
|
|
154
|
+
**Counts the hardware, not only the electricity.** Manufacturing one HGX H100
|
|
155
|
+
baseboard emits 1,312 kgCO2e before it computes anything, and a model that
|
|
156
|
+
reports only the energy is claiming that figure is zero. The `embodied_carbon`
|
|
157
|
+
dimension amortises a published product carbon footprint over the share of the
|
|
158
|
+
hardware's life one unit of work reserved — which completes the four terms of
|
|
159
|
+
[ISO/IEC 21031:2024](https://greensoftware.foundation/standards/sci/), the
|
|
160
|
+
Software Carbon Intensity standard, whose shape this package already had.
|
|
161
|
+
|
|
162
|
+
The arithmetic is complete; **the catalogue is not, and will not pretend to
|
|
163
|
+
be.** Seven footprints ship: three accelerators out of 25 rows and four
|
|
164
|
+
processors out of 12. Everything else reports `TODO` with a sentence saying
|
|
165
|
+
nobody has read a footprint for it, which is not the same as it having been free
|
|
166
|
+
to build. The reason the gap is that wide is that most vendors publish a
|
|
167
|
+
footprint for a whole server and not for the part inside it, and the one open
|
|
168
|
+
database that covers processors answers for a chip it does not have by
|
|
169
|
+
substituting the nearest one it does — asked for an Apple M4 Max it returns an
|
|
170
|
+
Apple M1 Max, four generations earlier, with no warning. Those answers are
|
|
171
|
+
refused by name rather than imported.
|
|
172
|
+
[`STANDARDS.md`](https://github.com/warith-harchaoui/saggio/blob/master/STANDARDS.md) has the table, the boundary of every figure, and
|
|
173
|
+
the eight refusals with their reasons.
|
|
174
|
+
|
|
175
|
+
**Writes reports that people read.** Markdown for a pull request. A single
|
|
176
|
+
self-contained HTML page for everyone else: offline, light and dark, English and
|
|
177
|
+
French, with a panel that recomputes the model for a different country in the
|
|
178
|
+
browser and a chart of how the grid would move the carbon elsewhere. Word and PDF
|
|
179
|
+
through `md2star` when a document is what somebody wants. Every known carbon
|
|
180
|
+
figure is also restated in terms a reader can feel — tree-months of
|
|
181
|
+
sequestration, kilometres in an average car, a fraction of a Paris–London
|
|
182
|
+
flight — with the [Green Algorithms](https://doi.org/10.1002/advs.202100707)
|
|
183
|
+
coefficients, and without gaining any confidence in the restating: an open figure
|
|
184
|
+
stays open, and a measured one reads `estimated`, because the tree is an average
|
|
185
|
+
tree.
|
|
186
|
+
|
|
187
|
+
**Shows the team, not just the project.** `saggio dashboard` renders every
|
|
188
|
+
committed cost model on one page. It leads with the one comparison that is honest
|
|
189
|
+
across projects — how much of each model is measured, estimated, or still open —
|
|
190
|
+
and says plainly that the cost rows, each per its own unit of work, do not
|
|
191
|
+
compare with each other.
|
|
192
|
+
|
|
193
|
+
**Fails your build when a cost drifts.** `diff` compares two models and fails on a
|
|
194
|
+
cost that worsened past a threshold, on a status that weakened, and on a quantity
|
|
195
|
+
that quietly disappeared.
|
|
196
|
+
|
|
197
|
+
## Install
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
pip install saggio
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Linux and macOS.** Windows is out of scope, deliberately and for good: it
|
|
204
|
+
publishes no vendor-neutral processor energy counter to an unprivileged process,
|
|
205
|
+
no machine-wide processor-time total this package can read, and no POSIX resource
|
|
206
|
+
accounting for a child. A tool whose whole proposition is that a number says how
|
|
207
|
+
far it can be trusted should not pretend to support a platform where it could
|
|
208
|
+
only ever estimate, so `import saggio` there raises rather than half-working.
|
|
209
|
+
|
|
210
|
+
Three runtime dependencies, on purpose. [`os-helper`](https://pypi.org/project/os-helper/)
|
|
211
|
+
answers every question about the machine and the operating system on both of
|
|
212
|
+
them. PyYAML parses the models and the catalogues.
|
|
213
|
+
`platformdirs` finds the per-user config directory. Everything else this package
|
|
214
|
+
does, it does itself.
|
|
215
|
+
|
|
216
|
+
Word and PDF need one more thing, and only if you want them:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
pip install "saggio[office]"
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
For conda:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
conda env create -f environment.yaml
|
|
226
|
+
conda activate env-for-saggio
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Use it
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
# Start from a worked example, or from a scaffold with everything left open.
|
|
233
|
+
saggio init --template annotated -o cost_of_running.yaml
|
|
234
|
+
|
|
235
|
+
# Check it against the schema and the honesty rules.
|
|
236
|
+
saggio validate cost_of_running.yaml
|
|
237
|
+
|
|
238
|
+
# Read a repository and write a model for it.
|
|
239
|
+
saggio audit . --country FR -o cost_of_running.yaml
|
|
240
|
+
|
|
241
|
+
# Read it, and run a capped slice to measure what it really costs.
|
|
242
|
+
saggio audit . --country FR --run -o cost_of_running.yaml
|
|
243
|
+
|
|
244
|
+
# What would this cost on an H100, from a measurement taken on a 4090?
|
|
245
|
+
saggio audit . --country FR --run \
|
|
246
|
+
--source-accelerator RTX-4090 --target-accelerator H100
|
|
247
|
+
|
|
248
|
+
# Measure one command of your own.
|
|
249
|
+
saggio measure -- python train.py --steps 100
|
|
250
|
+
|
|
251
|
+
# What is this machine, and does the catalogue know its parts?
|
|
252
|
+
saggio machine
|
|
253
|
+
|
|
254
|
+
# Turn the model into something a person reads.
|
|
255
|
+
saggio render cost_of_running.yaml -f html -o report.html
|
|
256
|
+
|
|
257
|
+
# Fail the build when a cost has drifted.
|
|
258
|
+
saggio diff main.yaml branch.yaml --threshold 10
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
The library is the same thing without the printing:
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
import saggio
|
|
265
|
+
|
|
266
|
+
result = saggio.audit(".", options=saggio.AuditOptions(country="FR"))
|
|
267
|
+
print(result.report.summary())
|
|
268
|
+
print(saggio.render_markdown(result.model))
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
[`EXAMPLES.md`](https://github.com/warith-harchaoui/saggio/blob/master/EXAMPLES.md) is the cookbook, [`MEASURING.md`](https://github.com/warith-harchaoui/saggio/blob/master/MEASURING.md) is
|
|
272
|
+
which counters this machine will let you read and what each of them covers,
|
|
273
|
+
[`GALLERY.md`](https://github.com/warith-harchaoui/saggio/blob/master/GALLERY.md) is what it says about nanoGPT, Whisper, DINOv2,
|
|
274
|
+
FastAPI and Airflow with the files committed beside it,
|
|
275
|
+
[`docs/api.md`](https://github.com/warith-harchaoui/saggio/blob/master/docs/api.md) is every name `import saggio` gives you, and
|
|
276
|
+
[`docs/`](https://github.com/warith-harchaoui/saggio/blob/master/docs/README.md) is the map of the rest.
|
|
277
|
+
|
|
278
|
+
[`ANALYSIS.md`](https://github.com/warith-harchaoui/saggio/blob/master/ANALYSIS.md) is the investigation behind the division of labour
|
|
279
|
+
above: what static and dynamic analysis have actually been shown to deliver for
|
|
280
|
+
complexity and for consumption, which of it belongs here, and which of it is
|
|
281
|
+
refused and why.
|
|
282
|
+
|
|
283
|
+
## Running your code, and what that means
|
|
284
|
+
|
|
285
|
+
Measuring what code costs to run means running it. There is no sandbox here and
|
|
286
|
+
none is pretended: your repository under study executes as you, with your
|
|
287
|
+
permissions and your network access.
|
|
288
|
+
|
|
289
|
+
So consent is explicit, asked for once, and recorded where you can find and
|
|
290
|
+
revoke it:
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
saggio consent grant
|
|
294
|
+
saggio consent revoke
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Without it, `--run` does nothing and the audit proceeds on reading alone. A
|
|
298
|
+
session with no terminal is refused rather than defaulted, so a build server can
|
|
299
|
+
never agree on your behalf.
|
|
300
|
+
|
|
301
|
+
## What it never does
|
|
302
|
+
|
|
303
|
+
It never puts a number in a file that nobody chose. The country is stated by a
|
|
304
|
+
person or inferred from the machine's timezone and labelled as an inference; it is
|
|
305
|
+
never read off a locale and written down as fact.
|
|
306
|
+
|
|
307
|
+
It never parses a pricing page, and never asks a model what one says. With
|
|
308
|
+
`--fetch-prices` it reads rates from sources published *as data*, records which
|
|
309
|
+
model the code names and on which line, and stamps every rate with where it was
|
|
310
|
+
read and when. A rate from the vendor's own price API and a rate from somebody
|
|
311
|
+
else's transcription are both `estimated`, so each one also carries a
|
|
312
|
+
`source_kind` saying which it is, and the drift gate fails when that weakens.
|
|
313
|
+
Without the flag nothing reaches the network and the price stays open, pointing
|
|
314
|
+
at the page where the current number lives.
|
|
315
|
+
|
|
316
|
+
It never lets a language model supply a number. A local model, when you have one
|
|
317
|
+
running, is asked what shape of work your repository does and nothing else. Its
|
|
318
|
+
answer is labelled with the model's name and marked low confidence. Every number
|
|
319
|
+
comes from a file you can open or a counter you can read.
|
|
320
|
+
|
|
321
|
+
And it never counts what it cannot count. Making the hardware, the people, the
|
|
322
|
+
office, the idle capacity: all of it is listed in the report as excluded, because
|
|
323
|
+
a footprint that quietly leaves out the largest term is worse than no footprint.
|
|
324
|
+
|
|
325
|
+
## Where the numbers come from
|
|
326
|
+
|
|
327
|
+
The method is [Green Algorithms](https://doi.org/10.1002/advs.202100707)
|
|
328
|
+
(Lannelongue, Grealey, and Inouye, 2021): power multiplied by time is energy,
|
|
329
|
+
energy multiplied by a grid intensity is carbon, energy multiplied by a tariff is
|
|
330
|
+
money, energy multiplied by a water usage effectiveness is water.
|
|
331
|
+
|
|
332
|
+
One distinction is kept that is easy to lose. The machine draws one amount; the
|
|
333
|
+
building draws that amount multiplied by its power usage effectiveness. Carbon and
|
|
334
|
+
money follow the building, because that is what the meter counts. Water follows
|
|
335
|
+
the machine, because water usage effectiveness is defined per kilowatt-hour of IT
|
|
336
|
+
load and using the building figure would count the cooling twice.
|
|
337
|
+
|
|
338
|
+
Hardware wattages, grid intensities, tariffs, and datacenter overheads live in
|
|
339
|
+
[`saggio/data/`](https://github.com/warith-harchaoui/saggio/tree/master/saggio/data), one row each
|
|
340
|
+
with its source and its date. Missing a row is a normal outcome, and the tool
|
|
341
|
+
tells you which one by name:
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
saggio catalog list gpu
|
|
345
|
+
saggio catalog add gpu H300 \
|
|
346
|
+
--source-url https://www.nvidia.com/... --retrieved-date 2026-09-12 \
|
|
347
|
+
--field tdp_w=800 --field peak_bf16_tflops=2400
|
|
348
|
+
saggio catalog freshness # exits 1 when a number has gone stale
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
A row cannot be added without a source and a date. That rule is what keeps the
|
|
352
|
+
catalogues worth trusting.
|
|
353
|
+
|
|
354
|
+
## How it is put together
|
|
355
|
+
|
|
356
|
+
```
|
|
357
|
+
model/ What a cost model means: the taxonomy, the quantity, the dimensions,
|
|
358
|
+
the schema, the validation. No I/O, no network, no subprocess.
|
|
359
|
+
catalog/ Sourced facts about the world, and the rule that a row without
|
|
360
|
+
provenance does not enter.
|
|
361
|
+
estimate/ Facts and measurements into numbers: the machine, the deployment,
|
|
362
|
+
the Green Algorithms chain, the projections.
|
|
363
|
+
analyze/ What a repository is: reading it, running a slice of it, and asking a
|
|
364
|
+
local model about its shape (never about its numbers).
|
|
365
|
+
auditor.py The whole job, in one function.
|
|
366
|
+
diff.py What changed between two models, and whether it fails the gate.
|
|
367
|
+
templates.py The starter models the wheel ships.
|
|
368
|
+
report/ Markdown, HTML, Word, PDF.
|
|
369
|
+
cli/ Argument parsing and printing. Nothing else.
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
The dependency direction only points one way, so the command line can do nothing a
|
|
373
|
+
library caller cannot.
|
|
374
|
+
|
|
375
|
+
The HTML report's own pieces are authored outside the package, in
|
|
376
|
+
[`reporting/`](https://github.com/warith-harchaoui/saggio/tree/master/reporting): the document shell with the tokens the renderer
|
|
377
|
+
fills, the stylesheet, the script, the translations. They are a stylesheet and a
|
|
378
|
+
script there rather than strings quoted inside Python, and `reporting/sync.py`
|
|
379
|
+
copies them into the package that ships them, with a test that fails the build if
|
|
380
|
+
the two ever drift apart. Copy that trio to render reports of your own shape.
|
|
381
|
+
|
|
382
|
+
## Contributing
|
|
383
|
+
|
|
384
|
+
[`CONTRIBUTING.md`](https://github.com/warith-harchaoui/saggio/blob/master/CONTRIBUTING.md) has the details. The short version: add a
|
|
385
|
+
catalogue row with its source and its date, or a test that pins a behaviour you
|
|
386
|
+
care about. [`CODING.md`](https://github.com/warith-harchaoui/saggio/blob/master/CODING.md) is the style this repository is written in.
|
|
387
|
+
|
|
388
|
+
```bash
|
|
389
|
+
pip install -e ".[dev]"
|
|
390
|
+
pytest # 1000+ checks, including every example in every docstring
|
|
391
|
+
ruff check .
|
|
392
|
+
ruff format --check .
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## Related work
|
|
396
|
+
|
|
397
|
+
[`LANDSCAPE.md`](https://github.com/warith-harchaoui/saggio/blob/master/LANDSCAPE.md) places this alongside CodeCarbon, Green Algorithms,
|
|
398
|
+
Scaphandre, PowerAPI, Cloud Carbon Footprint, and the rest of the field, and is
|
|
399
|
+
honest about where each of them is the better tool.
|
|
400
|
+
|
|
401
|
+
## The name
|
|
402
|
+
|
|
403
|
+
*Saggio* is Italian for the assay of a metal: you draw a sample, you determine
|
|
404
|
+
its fineness, and the result is stamped with who determined it and when. It also
|
|
405
|
+
means an essay, and it means judicious. All three are the point. This tool draws
|
|
406
|
+
a capped sample of a real run, reports how well founded each number is, and
|
|
407
|
+
records where every figure came from and on what date somebody read it.
|
|
408
|
+
|
|
409
|
+
## Licence
|
|
410
|
+
|
|
411
|
+
[BSD 3-Clause](https://github.com/warith-harchaoui/saggio/blob/master/LICENSE). Warith Harchaoui, Ph.D.
|