Methodology · Estimates, not measurements

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.

measured
means facility-level input — a live grid signal from a configured provider (electricityMaps / wattTime / the synthetic fallback), or a per-site seed coefficient (waterIntensity in L/kWh).
modeled
means a mathematical slice— the per-inference carbon or water figure derived from that facility input plus the model's draw rate, or the baseline / savings fields that compare the dispatch to a notional hyperscaler. The chip on every published number — including the response payload, the savings dashboard, the network browser, and every card on this page — is set in code by 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

modeled

energyKwh × device.carbonIntensity

gCO₂/kWh × kWh = grams. carbonIntensity on the right side of the formula is a

measured
facility input (per-region grid signal); the product on the left is
modeled
because it mathematically attributes a slice of the facility total to this single dispatch. The chip on carbonGrams travels with that distinction.

waterMilliliters

modeled

energyKwh × device.waterIntensity × 1000

The seed records waterIntensity in litres per kWh — a

measured
per-site engineering coefficient. Multiply that by the same per-inference energyKwh (
modeled
) and the product becomes a modeled per-inference water slice; that's what the dispatched waterMl field carries.

baselineCarbonGrams

modeled

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

modeled
because the baseline is a comparator, not a measurement.

baselineWaterMl

modeled

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

modeled
.

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.

totalKwh = (tokens / 1000) × MODEL_KWH_PER_1K_TOKENS[model] + 0.000045
modeled

The full energyKwh on the wire is a

modeled
product — the per-token draw rate (a published per-model constant lookup) times the caller's tokens, plus the flat idle overhead. 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 tokens
  • cl-compact-v1 — 0.20 kWh / 1k tokens
  • claude-sonnet — 0.27 kWh / 1k tokens
  • claude-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.

Source ranges

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:

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 25 gCO₂/kWh floor

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.

Why 0.85 on renewables

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.

Where the live number comes from — and how to audit it.

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).

waterMilliliters = totalKwh × device.waterIntensity × 1000
modeled

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.

Public anchors

The hyperscaler water-use literature broadly agrees on the order of magnitude but varies by an order of two depending on methodology:

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: true

Lowest 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

~30 × 30 mile hydrological grid
resolution disclosure so the underlying bound is visible at the same eye-line as the figure.

Launch-safety provenance guard
CarbonLayer does not publish facility-level water-scarcity or basin-stress scores. Every water number on this surface is either a per-inference attribution (modeled) or a per-site cooling-water-use coefficient (measured). Facility-level scarcity scoring ships only when a licensed dataset is wired through.

Facility-scarcity scoring resolution

~30 × 30 mile hydrological grid
· neighbors share scores.

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

~30 × 30 mile hydrological grid

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

On the chip — measured vs modeled, ground truth.

When we say

measured
, we mean facility-level input — a live grid signal from a configured provider (electricityMaps / wattTime), the synthetic fallback for a region that age-d out of cache, or a per-site seed coefficient (waterIntensity in L/kWh). Each of these is an independent reading from the physical system, even though Electricity Maps and WattTime themselves publish a published methodology that wraps a measured mean for the region. The one-bit chip here is honest about that boundary: a published grid mix is the measured input our meters don't yet override.

When we say

modeled
, we mean a per-inference slice or a baseline comparator — the multiplier 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
modeled
chip on the response.

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

Grid signals

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

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