ecocost 0.1.0__py3-none-any.whl

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.
ecocost/__init__.py ADDED
@@ -0,0 +1,108 @@
1
+ """ecocost: estimated carbon, energy and water per LLM run.
2
+
3
+ Pure Python; the only dependency is PyYAML. See METHODOLOGY.md for how the
4
+ estimate is built and CONTRIBUTING.md for how to update the data.
5
+
6
+ >>> from ecocost import estimate
7
+ >>> estimate("accounts/fireworks/models/gpt-oss-120b", provider="fireworks",
8
+ ... input_tokens=800, output_tokens=300)["carbon"]
9
+ {'unit': 'gCO2e', 'value': ..., 'min': ..., 'max': ...}
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from datetime import datetime
15
+ from importlib.metadata import PackageNotFoundError, version
16
+ from numbers import Integral
17
+
18
+ from . import calc
19
+ from .errors import UnknownModelError, UnknownProviderError
20
+ from .loader import get_kb
21
+ from .result import EstimateResult
22
+
23
+ __all__ = ["estimate", "EstimateResult", "UnknownModelError", "UnknownProviderError"]
24
+
25
+ # far above any single request; beyond it the float maths would overflow
26
+ MAX_TOKENS = 10**12
27
+
28
+ try:
29
+ __version__ = version("ecocost")
30
+ except PackageNotFoundError: # running from a source checkout
31
+ __version__ = "0+unknown"
32
+
33
+
34
+ def estimate(
35
+ model: str,
36
+ *,
37
+ provider: str | None = None,
38
+ base_url: str | None = None,
39
+ input_tokens: int = 0,
40
+ output_tokens: int = 0,
41
+ cached_input_tokens: int = 0,
42
+ timestamp: datetime | None = None,
43
+ ) -> EstimateResult:
44
+ """Estimate one request.
45
+
46
+ ``model`` is a model id; provider-specific ids resolve through aliases.
47
+ Who serves the request is taken, in order, from ``provider`` (an id from
48
+ providers.yaml, listed in the README), else ``base_url`` (an API endpoint
49
+ whose host a provider publishes), else, for a closed model, its vendor's
50
+ own API; otherwise wide US defaults. ``reasons`` says when it was inferred
51
+ or defaulted.
52
+
53
+ Ids are matched ignoring case. ``UnknownModelError`` and
54
+ ``UnknownProviderError`` suggest close matches and where to add the
55
+ record. Bad arguments raise ``TypeError`` or ``ValueError``.
56
+ """
57
+ _check_args(
58
+ model, provider, base_url, input_tokens, output_tokens, cached_input_tokens
59
+ )
60
+ kb = get_kb()
61
+ m = kb.resolve_model(model)
62
+ p, provider_reason = kb.resolve_provider(provider, base_url=base_url, model=m)
63
+ est = calc.estimate(
64
+ m,
65
+ p,
66
+ kb.regions[p.region],
67
+ kb.hardware[p.hardware],
68
+ kb.hardware[calc.REFERENCE_HARDWARE_ID],
69
+ input_tokens=input_tokens,
70
+ output_tokens=output_tokens,
71
+ cached_input_tokens=cached_input_tokens,
72
+ candidate_regions=[(kb.regions[rid], w) for rid, w in p.region_candidates],
73
+ candidate_hardware=[(kb.hardware[hid], w) for hid, w in p.hardware_candidates],
74
+ )
75
+ # timestamp is reserved for hourly grid intensity and not used yet.
76
+ if provider_reason:
77
+ est.reasons.insert(0, provider_reason)
78
+ out = est.to_dict()
79
+ out["requested"] = {"model": model, "provider": provider, "base_url": base_url}
80
+ return out
81
+
82
+
83
+ def _check_args(
84
+ model, provider, base_url, input_tokens, output_tokens, cached_input_tokens
85
+ ):
86
+ if not isinstance(model, str):
87
+ raise TypeError(f"model must be a string, got {type(model).__name__}")
88
+ if not model:
89
+ raise ValueError("model must not be empty")
90
+ for name, value in (("provider", provider), ("base_url", base_url)):
91
+ if value is not None and not isinstance(value, str):
92
+ raise TypeError(
93
+ f"{name} must be a string or None, got {type(value).__name__}"
94
+ )
95
+ for name, n in (
96
+ ("input_tokens", input_tokens),
97
+ ("output_tokens", output_tokens),
98
+ ("cached_input_tokens", cached_input_tokens),
99
+ ):
100
+ if isinstance(n, bool) or not isinstance(n, Integral):
101
+ raise TypeError(f"{name} must be an int, got {type(n).__name__}")
102
+ if not 0 <= n <= MAX_TOKENS:
103
+ raise ValueError(f"{name} must be between 0 and {MAX_TOKENS:,}, got {n}")
104
+ if cached_input_tokens > input_tokens:
105
+ raise ValueError(
106
+ f"cached_input_tokens ({cached_input_tokens}) exceeds input_tokens "
107
+ f"({input_tokens}); input_tokens counts cached tokens too"
108
+ )
ecocost/calc.py ADDED
@@ -0,0 +1,308 @@
1
+ """Eco-cost estimation pipeline.
2
+
3
+ tokens ──(active params, utilization)──▶ H100-seconds (work, chip-independent)
4
+ ──(chip energy ratio, TDP, PUE)─▶ Wh at the meter
5
+ ──(grid intensity)────────────▶ gCO2e (usage)
6
+ ──(embodied per H100-second)──▶ gCO2e (embodied)
7
+ ──(WUE)───────────────────────▶ mL water, on-site cooling
8
+ ──(generation water)──────────▶ mL water, upstream at the plant
9
+ ──(primary energy factor)─────▶ MJ primary energy
10
+
11
+ Every step is arithmetic over ``Range``. min/max is the likely range:
12
+ independent multiplicative inputs combine in quadrature in log space.
13
+ ``worst_case`` keeps the plain interval bounds with every input at its
14
+ extreme at once. Confidence is bucketed from the max/min ratio of the likely
15
+ carbon range and explained by listing every tier-3 input that went into it.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from collections.abc import Sequence
21
+ from dataclasses import dataclass
22
+
23
+ from .schema import (
24
+ DEFAULT_HALF_WIDTH,
25
+ H100_TDP_W,
26
+ Hardware,
27
+ Model,
28
+ Provider,
29
+ Range,
30
+ Region,
31
+ Tier,
32
+ sig_round,
33
+ )
34
+
35
+ METHOD_VERSION = "0.2.0"
36
+
37
+ # H100 SXM is the unit of work: an H100-second is "the work one H100 does in
38
+ # one second", whatever chip actually ran it.
39
+ REFERENCE_HARDWARE_ID = "h100-sxm"
40
+
41
+ # Cached prefix tokens cost about a tenth of a fresh prefill (assumed).
42
+ CACHED_INPUT_COST = 0.1
43
+
44
+ # Confidence label from the max/min ratio of the likely carbon range. "high"
45
+ # is within about +/-40% of the point value, "medium" within about a factor
46
+ # of 2.2 either way. For scale, Epoch's plausible range for a ChatGPT query
47
+ # is 0.1 to 4 Wh, a 40x span.
48
+ HIGH_CONFIDENCE_RATIO = 2.0
49
+ MEDIUM_CONFIDENCE_RATIO = 5.0
50
+
51
+
52
+ def estimate(
53
+ model: Model,
54
+ provider: Provider,
55
+ region: Region,
56
+ hardware: Hardware,
57
+ reference_hardware: Hardware,
58
+ *,
59
+ input_tokens: int = 0,
60
+ output_tokens: int = 0,
61
+ cached_input_tokens: int = 0,
62
+ candidate_regions: Sequence[tuple[Region, float]] = (),
63
+ candidate_hardware: Sequence[tuple[Hardware, float]] = (),
64
+ ) -> Estimate:
65
+ """``region`` and ``hardware`` are the provider's primary site and chip.
66
+ When the provider discloses several, the candidates set the ranges."""
67
+ chips = list(candidate_hardware) or [(hardware, 1.0)]
68
+
69
+ def regional(factor: str, floor: float = 0.0) -> Range:
70
+ return _regional(factor, region, provider.region_tier, candidate_regions, floor)
71
+
72
+ # 1. Work, in H100-seconds on the reference chip so the unit does not
73
+ # depend on the fleet; the fleet enters in step 2.
74
+ fresh_input = max(input_tokens - cached_input_tokens, 0)
75
+ work = _h100_seconds(
76
+ model,
77
+ reference_hardware,
78
+ fresh_input + cached_input_tokens * CACHED_INPUT_COST,
79
+ provider.prefill_utilization.as_range(),
80
+ )
81
+ measured = model.measured_wh_per_1k_output_tokens_h100
82
+ if measured is not None:
83
+ # Measured path: Wh on an H100 -> H100-seconds at its TDP.
84
+ wh = measured.as_range() * (output_tokens / 1000)
85
+ work += wh * 3600 / reference_hardware.tdp_w.as_range()
86
+ else:
87
+ work += _h100_seconds(
88
+ model,
89
+ reference_hardware,
90
+ output_tokens,
91
+ provider.decode_utilization.as_range(),
92
+ )
93
+
94
+ # 2. Energy at the meter. The chip ratio is how much energy this fleet
95
+ # spends per H100-second of work (B200 ~0.75, H200 ~0.85). Serving
96
+ # overhead covers host CPU/RAM/network and idle capacity: Google reports
97
+ # 0.24 Wh/prompt all-in vs 0.10 Wh for the accelerators alone.
98
+ chip_ratio = _envelope([(hw.energy_vs_h100.as_range(), w) for hw, w in chips])
99
+ energy_wh = (
100
+ work
101
+ * H100_TDP_W
102
+ / 3600
103
+ * chip_ratio
104
+ * provider.serving_overhead.as_range()
105
+ * provider.pue.as_range()
106
+ )
107
+ kwh = energy_wh / 1000
108
+
109
+ # 3. Carbon. An uncertain site widens the grid range too: "somewhere in
110
+ # the US" spans ~100-700 gCO2e/kWh.
111
+ grid = regional("carbon_intensity_gco2e_per_kwh", floor=10.0)
112
+ embodied_rate = _envelope(
113
+ [(hw.embodied_gco2e_per_h100_second, w) for hw, w in chips]
114
+ )
115
+
116
+ # 4. Water, in two parts because they follow different conventions.
117
+ # On-site is what the operator's WUE measures (and what Google's
118
+ # 0.26 mL per prompt counts). Generation water (WRI 2020) is the LCA
119
+ # convention; it is usually larger and, on hydro grids, contested.
120
+ return Estimate(
121
+ model=model,
122
+ provider=provider,
123
+ region=region,
124
+ hardware=hardware,
125
+ chips=tuple(hw.id for hw, _ in chips),
126
+ tokens=(input_tokens, output_tokens, cached_input_tokens),
127
+ h100_seconds=work,
128
+ chip_energy_ratio=chip_ratio,
129
+ energy_wh=energy_wh,
130
+ primary_energy_mj=kwh * 3.6 * regional("primary_energy_factor"),
131
+ grid_intensity=grid,
132
+ carbon_usage_g=kwh * grid,
133
+ carbon_embodied_g=work * embodied_rate,
134
+ water_onsite_ml=kwh * provider.wue_l_per_kwh.as_range() * 1000,
135
+ water_generation_ml=kwh * regional("water_l_per_kwh_generation") * 1000,
136
+ reasons=_assumed_inputs(model, provider, region, [hw for hw, _ in chips]),
137
+ )
138
+
139
+
140
+ @dataclass(frozen=True)
141
+ class Estimate:
142
+ model: Model
143
+ provider: Provider
144
+ region: Region
145
+ hardware: Hardware
146
+ chips: tuple[str, ...]
147
+ tokens: tuple[int, int, int] # input, output, cached input
148
+ h100_seconds: Range
149
+ chip_energy_ratio: Range # vs H100, envelope over the fleet
150
+ energy_wh: Range
151
+ primary_energy_mj: Range
152
+ grid_intensity: Range
153
+ carbon_usage_g: Range
154
+ carbon_embodied_g: Range
155
+ water_onsite_ml: Range
156
+ water_generation_ml: Range
157
+ reasons: list[str]
158
+
159
+ @property
160
+ def carbon_g(self) -> Range:
161
+ return self.carbon_usage_g + self.carbon_embodied_g
162
+
163
+ @property
164
+ def water_ml(self) -> Range:
165
+ return self.water_onsite_ml + self.water_generation_ml
166
+
167
+ @property
168
+ def confidence_level(self) -> str:
169
+ ratio = self.carbon_g.ratio
170
+ if ratio < HIGH_CONFIDENCE_RATIO:
171
+ return "high"
172
+ if ratio < MEDIUM_CONFIDENCE_RATIO:
173
+ return "medium"
174
+ return "low"
175
+
176
+ def to_dict(self) -> dict:
177
+ input_tokens, output_tokens, cached_input_tokens = self.tokens
178
+ grid = self.grid_intensity
179
+ return {
180
+ "model": self.model.id,
181
+ "provider": self.provider.id,
182
+ "tokens": {
183
+ "input": input_tokens,
184
+ "output": output_tokens,
185
+ "cached_input": cached_input_tokens,
186
+ },
187
+ "carbon": self.carbon_g.to_dict("gCO2e"),
188
+ "energy": {
189
+ **self.energy_wh.to_dict("Wh"),
190
+ "primary_energy_mj": sig_round(self.primary_energy_mj.mid),
191
+ },
192
+ "water": {
193
+ **self.water_ml.to_dict("mL"),
194
+ "on_site": self.water_onsite_ml.to_dict("mL"),
195
+ "generation": self.water_generation_ml.to_dict("mL"),
196
+ },
197
+ "confidence": {
198
+ "level": self.confidence_level,
199
+ "ratio": round(self.carbon_g.ratio, 2),
200
+ "reasons": self.reasons,
201
+ },
202
+ "electricity": {
203
+ "region": self.region.id,
204
+ "country": self.region.country,
205
+ "gco2e_per_kwh": sig_round(grid.mid),
206
+ "gco2e_per_kwh_range": [sig_round(grid.lo), sig_round(grid.hi)],
207
+ "primary_source": self.region.primary_source.value,
208
+ "mix": {k.value: v for k, v in self.region.mix.items()},
209
+ "dataset_year": self.region.dataset_year,
210
+ },
211
+ "breakdown": {
212
+ "h100_seconds": self.h100_seconds.to_dict("s"),
213
+ "usage": {"gco2e": sig_round(self.carbon_usage_g.mid)},
214
+ "embodied": {"gco2e": sig_round(self.carbon_embodied_g.mid)},
215
+ },
216
+ "assumptions": {
217
+ "active_params_b": {
218
+ "min": self.model.active_params_b.min,
219
+ "max": self.model.active_params_b.max,
220
+ },
221
+ "hardware": list(self.chips),
222
+ "chip_energy_vs_h100": sig_round(self.chip_energy_ratio.mid),
223
+ "pue": self.provider.pue.value,
224
+ "decode_utilization": self.provider.decode_utilization.value,
225
+ "serving_overhead": self.provider.serving_overhead.value,
226
+ "method_version": METHOD_VERSION,
227
+ },
228
+ "provenance": {
229
+ name: {
230
+ "trust": record.provenance.trust.value,
231
+ "status": record.provenance.status.value,
232
+ "generated_by": record.provenance.generated_by,
233
+ "stale": record.provenance.is_stale(),
234
+ }
235
+ for name, record in (
236
+ ("model", self.model),
237
+ ("provider", self.provider),
238
+ ("region", self.region),
239
+ ("hardware", self.hardware),
240
+ )
241
+ },
242
+ }
243
+
244
+
245
+ def _h100_seconds(
246
+ model: Model, reference: Hardware, tokens: float, utilization: Range
247
+ ) -> Range:
248
+ """A forward pass is ~2 FLOPs per active parameter per token."""
249
+ if tokens <= 0:
250
+ return Range(0.0, 0.0, 0.0)
251
+ flops = model.active_params_b.as_range() * 2e9 * tokens
252
+ return flops / (reference.peak_dense_flops.as_range() * utilization)
253
+
254
+
255
+ def _envelope(ranges: Sequence[tuple[Range, float]]) -> Range:
256
+ """Weighted mean as the point, min/max of the members as the range."""
257
+ total = sum(w for _, w in ranges)
258
+ return Range(
259
+ min(r.lo for r, _ in ranges),
260
+ sum(r.mid * w for r, w in ranges) / total,
261
+ max(r.hi for r, _ in ranges),
262
+ min(r.wlo for r, _ in ranges),
263
+ max(r.whi for r, _ in ranges),
264
+ )
265
+
266
+
267
+ def _regional(
268
+ factor: str,
269
+ region: Region,
270
+ region_tier: Tier,
271
+ candidates: Sequence[tuple[Region, float]],
272
+ floor: float,
273
+ ) -> Range:
274
+ """A per-kWh regional factor for a provider whose site may be uncertain.
275
+
276
+ Disclosed candidate sites give an envelope, which is evidence. Without
277
+ them, the national figure is widened by the region tier's half-width.
278
+ """
279
+ if candidates:
280
+ return _envelope([(getattr(c, factor).as_range(), w) for c, w in candidates])
281
+ r = getattr(region, factor).as_range()
282
+ if region_tier == Tier.primary:
283
+ return r
284
+ half_width = DEFAULT_HALF_WIDTH[region_tier]
285
+ return Range(max(r.lo / half_width, floor), r.mid, r.hi * half_width)
286
+
287
+
288
+ def _assumed_inputs(
289
+ model: Model, provider: Provider, region: Region, chips: Sequence[Hardware]
290
+ ) -> list[str]:
291
+ """Reason codes: every tier-3 input, most important first."""
292
+ measured = model.measured_wh_per_1k_output_tokens_h100 is not None
293
+ reasons = []
294
+ if not measured and not model.open_weights:
295
+ reasons.append("active_params_undisclosed")
296
+ tiers = {
297
+ # measured energy replaces the parameter count on the decode path
298
+ "active_params_estimated": None if measured else model.active_params_b.tier,
299
+ "region_inferred": provider.region_tier,
300
+ "hardware_assumed": provider.hardware_tier,
301
+ "pue_assumed": provider.pue.tier,
302
+ "wue_assumed": provider.wue_l_per_kwh.tier,
303
+ "utilization_assumed": provider.decode_utilization.tier,
304
+ "serving_overhead_assumed": provider.serving_overhead.tier,
305
+ "grid_intensity_national_average": region.carbon_intensity_gco2e_per_kwh.tier,
306
+ "embodied_carbon_default": max(hw.embodied_tier for hw in chips),
307
+ }
308
+ return reasons + [code for code, tier in tiers.items() if tier == Tier.assumed]
@@ -0,0 +1,145 @@
1
+ # Reference hardware. H100 SXM is the unit everything is normalised to.
2
+ # Leaf shape: {value, unit, tier, [min, max], source, note}
3
+ hardware:
4
+ h100-sxm:
5
+ label: NVIDIA H100 SXM5 80GB
6
+ tdp_w:
7
+ value: 700
8
+ unit: W
9
+ tier: 1
10
+ source: https://www.nvidia.com/en-us/data-center/h100/
11
+ note: Board TDP. Real draw under inference load is lower; captured via utilization.
12
+ peak_dense_flops:
13
+ value: 9.89e14
14
+ unit: FLOP/s
15
+ tier: 1
16
+ source: https://www.nvidia.com/en-us/data-center/h100/
17
+ note: BF16 dense, no sparsity.
18
+ # Embodied carbon per H100-second = kg / (years * active fraction). Kept as
19
+ # three inputs so the range is propagated instead of stacking every extreme
20
+ # (276 kg over 6 busy years vs 985 kg over 3 half-idle years is a 14x span
21
+ # that no single deployment sits at both ends of).
22
+ embodied_kgco2e_per_gpu:
23
+ value: 500
24
+ min: 276
25
+ max: 985
26
+ unit: kgCO2e per GPU incl. server share
27
+ tier: 2
28
+ source: https://images.nvidia.com/aem-dam/Solutions/documents/HGX-H100-PCF-Summary.pdf
29
+ note: >-
30
+ Cradle-to-gate GPU 164 kgCO2e (NVIDIA HGX H100 PCF, 1,312 kg / 8) to
31
+ 273 kgCO2e (ADEME, Lees-Perasso et al. 2026), plus server share per GPU
32
+ 112 kg (BoaviztAPI default server / 8) to 712 kg (EcoLogits p5.48xlarge
33
+ 5,700 kg / 8). Min and max are the sums of the low and high ends.
34
+ lifetime_years:
35
+ value: 4.5
36
+ min: 3
37
+ max: 6
38
+ unit: years
39
+ tier: 3
40
+ source: assumption
41
+ note: Policy choice, not a measurement. 3 years is the pessimistic economic life of an AI accelerator, 6 the longest server depreciation schedule in use.
42
+ active_fraction:
43
+ value: 0.7
44
+ min: 0.5
45
+ max: 1.0
46
+ unit: fraction of lifetime spent serving
47
+ tier: 3
48
+ source: assumption
49
+ note: Assumed. Embodied carbon is charged only to seconds that do work, so idle time raises the per-second rate.
50
+ energy_vs_h100:
51
+ value: 1.0
52
+ min: 1.0
53
+ max: 1.0
54
+ unit: ratio
55
+ tier: 1
56
+ source: definition
57
+ note: Reference chip by definition.
58
+ provenance:
59
+ generated: {by: agent:claude-fable-5-1, at: 2026-09-18}
60
+ verified: []
61
+ status: draft
62
+ stale_after: 2027-03-31
63
+ sources:
64
+ - {resource: https://www.nvidia.com/en-us/data-center/h100/}
65
+ - {resource: https://images.nvidia.com/aem-dam/Solutions/documents/HGX-H100-PCF-Summary.pdf}
66
+ - {resource: https://www.actu-environnement.com/media/pdf/news-47749-acv-gpu-ademe.pdf}
67
+ - {resource: https://ecologits.ai/latest/methodology/llm_inference/}
68
+ - {resource: https://doc.api.boavizta.org/getting_started/single_server/}
69
+
70
+ # ---- Newer chips. Work stays in H100-seconds; each chip carries the energy it
71
+ # spends per H100-second of work relative to an H100. The honest finding from
72
+ # ML.ENERGY's paired H100/B200 runs (55 matched batch sizes, 10 models, Feb
73
+ # 2026): a newer chip is only greener when it is kept busy. gpt-oss-120b on
74
+ # B200 uses 0.66 to 0.80x the energy per token; a 1 kW B200 serving an 8B
75
+ # dense model at batch 8 uses 1.9x. Median across every pair is 0.98.
76
+ # Embodied figures for non-H100 chips are scaled from the H100 record by die
77
+ # area and HBM and are tier 3 until a PCF exists.
78
+
79
+ h200-sxm:
80
+ label: NVIDIA H200 SXM 141GB
81
+ tdp_w: {value: 700, unit: W, tier: 1, source: https://www.nvidia.com/en-us/data-center/h200/, note: "Up to 700W (configurable)"}
82
+ peak_dense_flops: {value: 9.89e14, unit: FLOP/s, tier: 1, source: https://www.nvidia.com/en-us/data-center/h200/, note: "Same GH100 die; 1,979 TFLOPS BF16 with sparsity = 989 dense."}
83
+ embodied_kgco2e_per_gpu: {value: 550, min: 300, max: 1080, unit: kgCO2e per GPU incl. server share, tier: 3, source: derived, note: "H100 record x1.1 for 141 GB HBM3e vs 80 GB; no PCF published."}
84
+ lifetime_years: {value: 4.5, min: 3, max: 6, unit: years, tier: 3, source: assumption, note: Same policy as H100.}
85
+ active_fraction: {value: 0.7, min: 0.5, max: 1.0, unit: fraction, tier: 3, source: assumption, note: Same policy as H100.}
86
+ energy_vs_h100:
87
+ value: 0.85
88
+ min: 0.7
89
+ max: 1.0
90
+ unit: ratio
91
+ tier: 2
92
+ source: https://www.nvidia.com/en-us/data-center/h200/
93
+ note: "Spec-derived: identical compute and TDP, 4.8 vs 3.35 TB/s HBM. Decode is memory-bound so the ceiling is 0.7x; prefill is unchanged at 1.0x. No ML.ENERGY pair yet."
94
+ provenance: {generated: {by: agent:claude-fable-5-1, at: 2026-09-22}, verified: [], status: draft, stale_after: 2027-03-31, sources: [{resource: https://www.nvidia.com/en-us/data-center/h200/}]}
95
+
96
+ b200-sxm:
97
+ label: NVIDIA B200 SXM 180GB
98
+ tdp_w: {value: 1000, unit: W, tier: 1, source: https://www.nvidia.com/en-us/data-center/dgx-b200/, note: "DGX B200 ~14.3 kW for 8 GPUs; per-GPU TDP up to 1,000 W."}
99
+ peak_dense_flops: {value: 2.25e15, unit: FLOP/s, tier: 1, source: https://www.nvidia.com/en-us/data-center/dgx-b200/, note: "FP8 72 PFLOPS sparse per 8-GPU system = 4.5 PFLOPS dense FP8 per GPU = 2.25 PFLOPS dense BF16."}
100
+ embodied_kgco2e_per_gpu: {value: 750, min: 414, max: 1478, unit: kgCO2e per GPU incl. server share, tier: 3, source: derived, note: "H100 record x1.5: dual-die 208 B transistors, 180 to 192 GB HBM3e, heavier board. No PCF published."}
101
+ lifetime_years: {value: 4.5, min: 3, max: 6, unit: years, tier: 3, source: assumption, note: Same policy as H100.}
102
+ active_fraction: {value: 0.7, min: 0.5, max: 1.0, unit: fraction, tier: 3, source: assumption, note: Same policy as H100.}
103
+ energy_vs_h100:
104
+ value: 0.8
105
+ min: 0.65
106
+ max: 1.6
107
+ unit: ratio
108
+ tier: 1
109
+ source: https://raw.githubusercontent.com/ml-energy/leaderboard/master/public/data/tasks/gpqa.json
110
+ note: "Measured. ML.ENERGY v3.0 paired runs: gpt-oss-120b 0.66 to 0.80 across batch 8 to 2048, gpt-oss-20b 0.65 to 0.88, Qwen3-235B FP8 0.71 to 0.81; but BF16 dense 8 to 32B models at batch 8 to 16 run 1.2 to 2.0x because the 1 kW chip idles. Point 0.8 for the MoE, high-batch serving our providers do; max covers the small-dense-low-batch case."
111
+ provenance: {generated: {by: agent:claude-fable-5-1, at: 2026-09-22}, verified: [], status: draft, stale_after: 2027-03-31, sources: [{resource: https://www.nvidia.com/en-us/data-center/dgx-b200/}, {resource: https://raw.githubusercontent.com/ml-energy/leaderboard/master/public/data/tasks/gpqa.json}]}
112
+
113
+ mi300x:
114
+ label: AMD Instinct MI300X 192GB
115
+ tdp_w: {value: 750, unit: W, tier: 1, source: https://www.amd.com/content/dam/amd/en/documents/instinct-tech-docs/data-sheets/amd-instinct-mi300x-data-sheet.pdf, note: "Datasheet: Maximum TBP 750W (read from the PDF 2026-09-22)."}
116
+ peak_dense_flops: {value: 1.3074e15, unit: FLOP/s, tier: 1, source: https://www.amd.com/content/dam/amd/en/documents/instinct-tech-docs/data-sheets/amd-instinct-mi300x-data-sheet.pdf, note: "Datasheet: BFLOAT16 1307.4 TFLOPs dense, 2614.9 with sparsity; 192 GB HBM3 at 5.3 TB/s."}
117
+ embodied_kgco2e_per_gpu: {value: 650, min: 360, max: 1280, unit: kgCO2e per GPU incl. server share, tier: 3, source: derived, note: "H100 record x1.3: 8 XCD + 4 IOD chiplets, 192 GB HBM3. No PCF published."}
118
+ lifetime_years: {value: 4.5, min: 3, max: 6, unit: years, tier: 3, source: assumption, note: Same policy as H100.}
119
+ active_fraction: {value: 0.7, min: 0.5, max: 1.0, unit: fraction, tier: 3, source: assumption, note: Same policy as H100.}
120
+ energy_vs_h100:
121
+ value: 0.9
122
+ min: 0.7
123
+ max: 1.3
124
+ unit: ratio
125
+ tier: 3
126
+ note: "Spec says 0.7 to 0.8 (1.07x TBP, 1.32x FLOPS, 1.58x bandwidth vs H100) but ROCm inference kernels reach lower MFU than CUDA in every public benchmark, so the point sits near parity. No ML.ENERGY measurement."
127
+ source: https://www.amd.com/content/dam/amd/en/documents/instinct-tech-docs/data-sheets/amd-instinct-mi300x-data-sheet.pdf
128
+ provenance: {generated: {by: agent:claude-fable-5-1, at: 2026-09-22}, verified: [{by: agent:claude-fable-5-1, at: 2026-09-22}], status: draft, stale_after: 2027-03-31, sources: [{resource: https://www.amd.com/content/dam/amd/en/documents/instinct-tech-docs/data-sheets/amd-instinct-mi300x-data-sheet.pdf}]}
129
+
130
+ mi325x:
131
+ label: AMD Instinct MI325X 256GB
132
+ tdp_w: {value: 1000, unit: W, tier: 1, source: https://www.amd.com/content/dam/amd/en/documents/instinct-tech-docs/product-briefs/instinct-mi325x-datasheet.pdf, note: "Datasheet: Maximum TBP 1000W (read from the PDF 2026-09-22)."}
133
+ peak_dense_flops: {value: 1.3074e15, unit: FLOP/s, tier: 1, source: https://www.amd.com/content/dam/amd/en/documents/instinct-tech-docs/product-briefs/instinct-mi325x-datasheet.pdf, note: "Datasheet: BFLOAT16 1307.4 TFLOPs dense, 2614.9 with sparsity; 256 GB HBM3E at 6 TB/s. Same CDNA 3 compute as MI300X."}
134
+ embodied_kgco2e_per_gpu: {value: 700, min: 390, max: 1380, unit: kgCO2e per GPU incl. server share, tier: 3, source: derived, note: "H100 record x1.4: MI300X package plus 256 GB HBM3e. No PCF published."}
135
+ lifetime_years: {value: 4.5, min: 3, max: 6, unit: years, tier: 3, source: assumption, note: Same policy as H100.}
136
+ active_fraction: {value: 0.7, min: 0.5, max: 1.0, unit: fraction, tier: 3, source: assumption, note: Same policy as H100.}
137
+ energy_vs_h100:
138
+ value: 1.0
139
+ min: 0.75
140
+ max: 1.4
141
+ unit: ratio
142
+ tier: 3
143
+ note: "Same compute as MI300X at 1.33x the power; the extra bandwidth helps decode but not enough to pay for the watts unless batches are large. Spec-derived, no measurement."
144
+ source: https://www.amd.com/content/dam/amd/en/documents/instinct-tech-docs/product-briefs/instinct-mi325x-datasheet.pdf
145
+ provenance: {generated: {by: agent:claude-fable-5-1, at: 2026-09-22}, verified: [{by: agent:claude-fable-5-1, at: 2026-09-22}], status: draft, stale_after: 2027-03-31, sources: [{resource: https://www.amd.com/content/dam/amd/en/documents/instinct-tech-docs/product-briefs/instinct-mi325x-datasheet.pdf}]}