How CarbonLayer models carbon and water for AI inference.
Every /api/inference response comes with grams of CO₂e (carbonGrams) and millilitres of cooling + process water (waterMilliliters). This page documents exactly how those numbers are calculated — the per-token energy model, the per-region grid-intensity source, the water conversion factors, the hyperscaler reference baseline, and the dispatcher policy that picks a device per request. None of these are direct measurements; they are modeled estimates anchored to published sources. Where the model makes an assumption that future measurements might revise, we say so.
Measured vs modeled — the one boundary
Every number on this page carries one of two chips.
MetricSource and round-trips through the contract.At a glance
The four values behind every counterfactual.
The two numbers you see on the response — carbonGrams and waterMilliliters — are the carbon and water of whatever device the dispatcher picked for your request. The two you see under baseline are the same dispatch cost against a notional hyperscaler running the same workload. Each pair is computed identically — a per-kWh intensity times the dispatch energy — only the intensity differs.
carbonGrams
energyKwh × device.carbonIntensity
gCO₂/kWh × kWh = grams. carbonIntensity on the right side of the formula is a
carbonGrams travels with that distinction.waterMilliliters
energyKwh × device.waterIntensity × 1000
The seed records waterIntensity in litres per kWh — a
energyKwh (waterMl field carries.baselineCarbonGrams
energyKwh × 600 gCO₂/kWh
The reference baseline — the notional global-grid-average hyperscaler the savings field compares against. The 600 g figure is a deliberately conservative top-of-range average; we round up on purpose so savedCarbonGrams never overstates the advantage. The chip is
baselineWaterMl
energyKwh × 1800 mL/kWh
Same comparator as above, for cooling + process water. Public hyperscaler water-use disclosures span roughly 0.8–1.8 L/kWh site-level (variable with cooling design); the 1.8 L/kWh figure is at the high end of the published range and, like the carbon baseline, is rounded up to keep savedWaterMl conservative. The chip is
Energy model
Per-token kWh from the model id + a thin fixed overhead.
The dispatcher uses energyForModel() to compute dispatch energy. The model — not the device — drives the per-token draw.
The full energyKwh on the wire is a
MODEL_KWH_PER_1K_TOKENS is a constant lookup keyed by the model id the caller picked (the dispatch mirrors it back as model on the response). The constants today:claude-haiku— 0.18 kWh / 1k tokenscl-compact-v1— 0.20 kWh / 1k tokensclaude-sonnet— 0.27 kWh / 1k tokensclaude-opus— 0.36 kWh / 1k tokens
The flat 0.000045 kWh idle overhead represents PUE-adjacent per-inference fixed costs — request-routing, average ambient cooling contribution, housekeeping at the host. It is intentionally small relative to the per-token draw: at the smallest model id, 800 tokens of work is roughly 0.0001 × 8 + 0.000045 ≈ 0.0009 kWh; the overhead is one to five percent of the dispatch total across the model range.
A secondary constant — MODEL_KWH_PER_1K_TOKENS was substituted, in the older energy path, by a per-device latency-driven curve that interpolates between 0.18 kWh/1k tokens (low-latency sites) and roughly 0.34 kWh/1k tokens (slow / larger sites). The model-aware path above is the live one — draw is treated as a property of the model, not of the host.
The 0.18 → 0.36 kWh-per-1k-tokens span sits in the broad range reported by inference- specific per-token measurement work and by the family of open tools for ML carbon accounting:
- Strubell, Ganesh & McCallum (2019) — Energy and Policy Considerations for Deep Learning in NLP
- CodeCarbon (mlco2) — GPU/CPU energy tracker
- Eco2AI — training/inference emissions tracker
These tools collectively span a published per-token range that scales with model size and hardware class. We chose to bias toward the conservative (higher) end of the band so the saved-energy number never overstates the worked-example advantage.
Per-model lookup
Not derived from host latency
The model id drives the draw rate so the same model behaves consistently across the dispatch fleet — a Sonnet on Oslo is the same kWh as a Sonnet on Karnataka, modulo hardware rounding.
Fixed overhead
0.000045 kWh / dispatch
PUE-adjacent per-call overhead: routing, ambient cooling, housekeeping. Small relative to per-token draw so the model id remains the dominant lever.
Water co-product
totalKwh × waterIntensity × 1000
mL = kWh × L/kWh × 1000. The same totalKwh flows into carbon and water — the model is a single source of truth, not two coupled but separate estimates.
Regional carbon intensity
Seven dispatchable slugs, four diurnal anchors, one carbon floor.
The simplest version of the model is carbonGrams = energyKwh × device.carbonIntensity. Where that device.carbonIntensity comes from — and what it can do — is the subject of this section.
The seven dispatch slugs
CarbonLayer can route to seven city anchors, each aliased to an AWS region code so the dispatcher works against existing AWS-private interconnect points. The region slug is the public input (case-insensitive in the URL); the AWS region code is what the seed and the live grid read use internally.
oslo · eu-north-1
baseCarbon 65 g · anchor hydro
Oslo Harbour Hydro Cluster — Norway.
quebec · us-east-1
baseCarbon 95 g · anchor hydro
Quebec Hydro Edge — Canada.
oregon · us-west-2
baseCarbon 180 g · anchor wind
Columbia Gorge Wind Pod — United States.
andalucia · eu-west-2
baseCarbon 220 g · anchor solar
Andalucía Solar Lab — Spain.
karnataka · ap-south-1
baseCarbon 410 g · anchor mixed
Karnataka Renewable Bridge — India.
sao-paulo · sa-east-1
baseCarbon 110 g · anchor hydro
São Paulo Hydro Roof — Brazil.
tasmania · oc-east-1
baseCarbon 140 g · anchor wind
Tasmania Wind + Hydro — Australia.
Diurnal-curve synthetic fallback
Each site has a renewable-generation anchor— the dominant clean-energy shape that drives its diurnal variation. The site's carbon intensity rises and falls with local hour-of-day so the model stays order-of-magnitude correct without paying for a live grid read every request. The four anchors:
solar
0.6 + 0.4·cos((h−13)/24·2π)
Steep rise at sunrise, fall after sunset. Andalucía.
hydro
0.92 + 0.08·cos((h−14)/24·2π)
Stored behind dams — light diurnal curve. Oslo, Quebec, São Paulo.
wind
0.86 + 0.14·cos((h−3)/24·2π)
Ramps overnight. Oregon, Tasmania.
mixed
0.82 + 0.18·cos((h−11)/24·2π)
Combined solar + wind + small hydro. Karnataka.
The dispatched carbon intensity is clamped between 25 and 700 gCO₂/kWh. The floor models the embedded carbon of manufacturing + grid maintenance that the diurnal-curve model would otherwise zero out on a fully renewable hour.
carbonIntensity = clamp(baseCarbon − renewableMix·baseCarbon·0.85, 25, 700)
On a 100% hydro hour the diurnal curve drops renewable share to 1.0 and the formula drives carbon intensity toward 15% of baseCarbon; the floor prevents that from going unrealistically low. The 25 g choice tracks published attributable grid-maintenance emissions — the part of system load that isn't from any single generation source.
The 0.85 multiplier models the fact that renewables displace the marginal fossil source on a grid — not 100% of generation. The marginal source is fossil-fuelled until renewables saturate the grid; beyond saturation, additional renewables only displace other renewables. A displacement coefficient of 1.0 would overstate the drop; the published marginal- displacement literature clusters around 0.7–0.9, and the model picks the lower (more conservative) end.
Combined with the renewable share — itself a 0–1 number returned by the live grid read — the displacement is renewableMix × baseCarbon × 0.85. Carbon intensity on a hydro-heavy hour is therefore baseCarbon minus that displacement, then floored at 25.
The grid-live adapter registers four providers via the GRID_INTENSITY_PROVIDER env var:
stub (default)
Deterministic dev adapter — runs the same diurnal recompute tagged source: live.
synthetic
Same curve, tagged source: synthetic. Useful for forced-degraded tests.
electricityMaps
Production adapter placeholder — wired through Electricity Maps; throws if a key is missing, falls through to synthetic.
wattTime
Production adapter placeholder — wired through WattTime; same throw / fallback behaviour.
A 5-minute in-memory cache (keyed by AWS region, not by slug) covers the cold-path repeatability — the first read goes to the configured provider; subsequent reads within the TTL return the same record. If the provider throws AND the cache is cold, the route falls through to the synthetic path and returns a record with source: 'synthetic'. You can audit which is which on every response via GET /api/grid-intensity or by reading the source field on the dispatch payload itself.
Both Electricity Maps and WattTime publish public methodologies for their carbon signals — see their respective sites above, plus IEA Electricity Information Review for the cross-region comparison work that anchors the per-region baseCarbon values the seed ships.
Water conversion
Per-site cooling + process water, scaled to millilitres.
Cooling and process water for AI inference is heterogeneous — it varies with the cooling design (air vs adiabatic vs liquid), the local ambient temperature and humidity, and the rack density of the host. The seed records a single waterIntensity per site in L/kWh — the per-kWh water-use coefficient we apply to every dispatch from that site. Today's site water intensities range from 0.32 L/kWh (Oslo, sub-arctic ambient, chilled-water cooling) to 1.10 L/kWh (Karnataka, hot ambient, adiabatic supplemental cooling).
The on-the-wire field is in millilitres because that is what an API consumer can compare against a single token or a single message. Internally we keep L/kWh so the per-site coefficients stay editable in process-engineering units; the ×1000 is a unit conversion on dispatch, not a stack of assumptions.
For the comparator (the baseline field), we use 1.8 L/kWh — the upper bound of the published hyperscaler range, deliberately rounded up so the savings field doesn't overstate what we have achieved. The wider literature on hyperscaler water-use disclosures puts a "typical" site anywhere from roughly 0.8 L/kWh (cold-ambient, well-tuned adiabatic) to 1.8 L/kWh (hot-ambient with supplementary cooling); 1.8 is a conservative ceiling.
The hyperscaler water-use literature broadly agrees on the order of magnitude but varies by an order of two depending on methodology:
- Li, P. et al. (2023) — "Making AI Less Thirsty: Uncovering and Addressing the Secret Water Footprint of AI Models" — widely-cited quantification of inference-time cooling water.
- Public hyperscaler sustainability reports (Microsoft Scope 3 water withdrawal, Google operational water data) — these report ranges; specific numbers are not cross-fleet comparable and should be read individually.
Per-site water coefficients in our seed are anchored to local climate-adjusted process-engineering estimates, not to a single global number — a site in Oslo genuinely uses less water than the equivalent site in Karnataka, and the model captures that.
Reference baseline
What "saved vs hyperscaler" actually compares against.
The two comparator fields on the response are baselineCarbonGrams and baselineWaterMl. Both are the same dispatch energy against a deliberately conservative notional hyperscaler baseline. They are not measured averages; they are reference values a reader can hold constant when comparing two dispatches.
Carbon baseline
600 gCO₂/kWh
Modelled global grid average across published hyperscaler Published-data ranges span ~460–500 gCO₂/kWh for global averages; the 600 figure is a deliberate conservative round-up one standard deviation above the typical published band, chosen so the savedCarbonGrams field cannot overstate the worked-example advantage.
Water baseline
1.8 L water/kWh
Hyperscaler cooling + process. The published range sits between 0.8 and 1.8 L/kWh for site-level operational water, depending on cooling design and ambient conditions. 1.8 is the published upper bound — the same conservative direction as the carbon baseline.
Both baselines live in BASELINE = { carbonGramsPerKwh: 600, waterMlPerKwh: 1800 } inside src/lib/business/carbon-router.ts. This page documents them; it does not override them.
Public anchors: IEA Electricity Information Review for global grid intensity averages, and IPCC AR6 Working Group III for the data-centre electricity demand and PUE-adjacent assumptions used to size the embedded footprint.
Dispatcher policy
How a device gets picked for a request.
The dispatcher ( src/lib/business/carbon-router.ts ) takes the per-region live read plus your preferLowLatency flag and picks one of the seven sites. Two modes.
preferLowLatency: false (default)Lowest carbon among devices ≤ 250 ms. Default mode. The dispatcher iterates the seven-region live read, filters by latency, and returns the site with the lowest current carbon intensity.
On a clean hour this typically returns the lowest-carbon hydro or wind site in the fleet (Oslo, Quebec, São Paulo are typical); on a dirty hour the same iteration may return a higher-anchor site whose mix is currently cleaner.
preferLowLatency: trueLowest latency among devices ≤ 1.15 × global min carbon. The dispatcher first finds the global minimum carbon across the fleet, then returns the lowest-latency site whose carbon is within 15% of that minimum.
This means a single caller can trade up to 15% on carbon for the best latency. The trade-off is documented on every response in the reasons array — so an auditing reader can see why a particular device was chosen without re-deriving the dispatcher logic.
Known limitations
What this model gets right, and where it can be wrong.
Credibility comes from naming the limits, not from asserting none exist. The list below is the bias direction the model deliberately accepts — in some cases we are intentionally on the side of over-counting, in some on the side of under-counting, in some we just can't measure yet.
Per-token energy is a model
Not a wattmeter on the GPU.
The 0.18–0.36 kWh/1k tokens range tracks a band reported by inference-specific per-token measurement work and open-source ML carbon accounting tools. It is not a per-request measurement. Future versions that ship on wattmetered hardware can update the lookup directly.
Grid intensity carries error bars
The provider publishes its own confidence band.
Both Electricity Maps and WattTime publish per-region confidence / uncertainty bands on the carbon signal. CarbonLayer does not currently fold those into the floor/ceiling clamp; the response now always also returns renewablePct beside carbonIntensity so an auditing reader can cross-check the assumption.
Water is a modelling assumption
Site-level water meters aren't built in.
Per-region waterIntensity is a model coefficient (L/kWh), not a per-call measurement. The figure draws on the published hyperscaler water-use literature, adjust for local climate and cooling design, but is not yet a physically metered value. We are explicit about the gap so a reader can audit it; live metering is in our future scope, not this page. Facility-level water-scarcity scoring remains out of launch scope until a licensed dataset is in place.
Synthetic fallback path
When the live provider unreachable. Detectable from source: 'synthetic'.
If the configured live provider throws AND the 5-minute cache holds nothing, the route falls through to the diurnal-curve synthetic and tags the record with source: 'synthetic'. The synthetic path does not write to the cache, so a successful live refresh in the next window will overwrite it.
Embed / embodied carbon
Not modeled at the inference level.
The 25 g/kWh floor approximates the embedded manufacturing + grid-maintenance footprint the diurnal curve would otherwise drop to zero on a 100% renewable hour. It is a coarse attribution — not a per-device amortisation. Readers comparing two inference workloads exactly should treat the figure as a session-wide amortisation shared across all dispatches.
Regional coverage
Seven dispatch sites today.
The seven-region footprint spans the regions CarbonLayer dispatches inference to. Each site carries its own grid intensity and on-site cooling-water-use profile, so the per-region numbers you see above trace back to one concrete location. Facility scarcity scoring is NOT part of today's licensed coverage. When it lands, every score ships stamped with the
Launch-safety provenance guard
Facility-scarcity scoring resolution
Facility-level water-scarcity rows from the licensed Water Positive dataset are stamped at ~30 × 30 mile hydrological grid resolution. Two neighboring facilities inside the same grid cell will typically return identical values from the upstream dataset — that is a property of the dataset's native granularity, not a measurement artefact. The licensing vocabulary and the /api/scores/facility envelope both commit to the same bound so a future scorer integration ships without UI churn.
Grid resolution disclosure
Facility-level water scarcity scoring is attributed at ~30 × 30 mile hydrological grid resolution. Two neighboring facilities inside the same grid cell will typically return identical values from the upstream dataset — a property of the dataset’s native resolution, not a measurement artefact. Every published score carries this disclosure in-place so the bound is visible at the same eye-line as the figure.
Water Positive licensed dataset
When we say
When we say
energyKwh × device.carbonIntensity, the watermark baseline (600 gCO₂/kWh and 1.8 L water/kWh), the per-call savedCarbonGrams delta against it, and the per-token draw rate that backs the energy model. Each is mathematically attributed — never read off a wattmeter — and so each carries the The boundary is set in code by the MetricSource enum in src/lib/contracts/inference.ts, stamped by every /api route that returns a number, and rendered by the MeasurementSourceChip island everywhere a number reaches a user. The chip is the contract — if it ever disagrees with the formula above, the formula wins and the chip is the bug.
Sources & further reading
The citations this page leans on.
Found a citation or attribution error? Use our contact form and include the section, number, and source.
Energy / per-token literature
- Strubell, Ganesh & McCallum (2019) — Energy and Policy Considerations for Deep Learning in NLP
- CodeCarbon — mlco2/codecarbon on GitHub
- Eco2AI — eco2ai on GitHub
Grid signals
- IEA — Electricity Information Review (cross-region grid intensity averages)
- Electricity Maps — electricitymaps.com (live per-region carbon signal)
- WattTime — watttime.org (alternate live per-region signal)
Water
- Li, P. et al. (2023) — Making AI Less Thirsty (inference-time cooling water quantification)
- Published hyperscaler operational water data (Microsoft, Google) — site-level WUE figures, used to anchor the per-site coefficients and the comparator range
Data-centre + PUE assumptions
- IPCC — AR6 Working Group III (data-centre electricity demand + PUE-adjacent assumptions)
- AWS — sustainability.aboutamazon.com (AWS-region sustainability disclosures — the regions the seed maps to)
The same field, on every response.
Every /api/inference response carries carbonGrams, waterMilliliters, renewablePct, source, and the device name. Query a single request to see the model in action or read the live site numbers on the pricing page.
CarbonLayer · methodology · last reviewed 2026-08-07
Questions or pushback on a citation? carbonlayer@polsia.app