Skip to main content
The price indices answer what does compute cost? The Effective Utilization Index answers how much of the compute that exists is actually working? These are different questions, and the second one is not visible in price. A price can sit flat while the market tightens or slackens underneath it — the same $/GPU-hour means something very different at 30% occupancy than at 85%. Utilization is the demand-side series that price alone cannot show.

The headline: price per useful GPU-hour

A rented GPU that sits idle still bills. Dividing the market price by the share of capacity actually working gives the effective price — what a buyer pays for compute that runs:
To read the scale: a 2.80rateat372.80 rate at 37% effective utilization works out to 7.57 per useful GPU-hour — 2.7x the sticker. That ratio, not the sticker, is the number that governs the economics of a training run or an inference fleet. (Illustrative arithmetic; the live values come from the endpoints below.)

Construction

The index composes three layers.
1

Occupancy — is the capacity rented at all?

Capacity-weighted share of tracked GPU-hours under an active rental:
where capacity = rented + available on each hourly reading, drawn from tracked marketplace listings across the providers Ornn observes.The numerator and denominator are summed separately before dividing. This is not the same as averaging the per-reading ratios: a mean of ratios lets a 4-GPU listing count as much as a 4,000-GPU one, so one tiny provider going idle moves “market” occupancy as hard as a hyperscaler. Weighting by the capacity each reading represents is what makes the series a market rate rather than a average of unrelated fractions.
2

Intensity — how hard does it run once rented?

Mean GPU load during rented hours, from fleet heartbeat telemetry.Intensity is live from the fleet’s per-node 1-hour heartbeat rollups: the GPU-hour-weighted mean of hourly GPU load across node-hours that were under an active reservation inside the window, reported as intensitySource: "fleet_rollup_1h". It is one fleet-wide factor per response — the rollups do not carry a GPU class, so gpuType and region filters slice occupancy but not intensity.
If the telemetry feed is unreachable — or the window holds fewer than 24 reserved GPU-hours — the index degrades rather than guessing: the response carries basis: "occupancy_only", intensity: null, and intensitySource: "unavailable", and eur equals occupancy. Check basis before comparing series across time — it is the field that tells you which index you are holding.
3

Effective Utilization Rate

Derived series

revPag stays on occupancy rather than EUR on purpose: an operator is paid for the hour the GPU is rented, whether or not the tenant saturates it.

Why it moved: demand and supply effects

A utilization rate that only goes up and down is nearly useless for a trading decision, because two opposite market events produce the same move. Occupancy rising because buyers took more GPUs is a tightening market. Occupancy rising because a provider withdrew capacity is a shrinking one. The headline rate cannot tell them apart, so the index decomposes every step:
The two sum to the observed change exactly, with no residual:
Worked example — rented flat at 100 GPUs, capacity halved from 400 to 200: occupancy doubles from 25% to 50%, and the decomposition attributes all of it to supply (demandEffect: 0, supplyEffect: 0.25). Nobody bought a single extra GPU. That is the distinction the index exists to publish. On the snapshot endpoint the same decomposition runs across the whole window, so summary.demandEffect and summary.supplyEffect explain where the market ended up versus where it started.

Attribution only happens like-for-like

The effects carry a causal claim, so they are only published when the two days being compared contain the same set of GPU types. If the basket changed — typically a partial scrape outage — the effects are null and decompositionBasis says composition_changed instead of like_for_like. This matters more than it sounds. With six types requested, a day where five stop reporting would otherwise decompose into a −49.8 point “demand collapse” on a day when the one surviving type actually rose a point. The arithmetic balances perfectly; it is attributing a reporting gap to buyer behaviour. Refusing to attribute is the only honest answer. The rate itself still publishes on such a day, alongside reportingTypes and coverage so the gap is visible on the same row. summary.compositionBreaks counts how many steps in the window could not be attributed. decompositionBasis values: like_for_like (attributed), composition_changed (suppressed), no_prior_day (first row of the series).

Reading the market: the other fields

Data quality

Coverage is published next to every rate. Missing scrape hours are missing information, not idle GPUs, and an index that lets a feed outage read as a demand collapse is worse than no index. Coverage is measured against the requested slice set — asking for a GPU type a region does not carry correctly lowers coverage rather than quietly shrinking the denominator. Thin days are dropped, not published. A day with fewer than 24 readings is excluded from the series and skipped as a decomposition baseline, so the next real day compares against the last real day rather than against a gap. Publishing a confident-looking rate computed from three hours of scrape is how a data product loses its reputation. Impossible readings are clamped. capacity = rented + available, so occupancy above 1 means the feed disagrees with itself; it clamps to 1 rather than publishing an impossible rate. Zero-capacity readings carry no information and are excluded entirely. Zero never becomes infinity. effectivePrice is null when EUR is zero, not Infinity. Levels are fleet sizes, not GPU-hours. The underlying readings are hourly, so a naive sum would scale with how often the scraper ran — a slow scrape day would read as a smaller fleet, and the demand/supply decomposition would move even on a market where nothing happened. Each slice is normalised to a mean over its own readings before anything is summed, so capacity, rented, capacityMean and headroomMean are GPU counts and the effects are immune to scrape cadence. effectivePrice divides matched baskets. If part of the fleet has no settled price that day, the price and the occupancy it divides into are both computed over the priced slice only, and priceCoverage reports how much of the fleet that was.

Index level and rebasing

index is 100 x EUR / EUR_base, with baseDate defaulting to 2026-01-01. The base is a fixed constant rather than “the first day of whatever you asked for”, because a level rebased to the window start silently changes meaning between two requests for different windows. If the base date has no usable data the series rebases on its own first day and reports baseAvailable: false — always check that flag before comparing levels across responses.

Endpoints

Snapshot

GET /api/utilization-index — window rate, quality signals, decomposition.

History

GET /api/utilization-index/history — daily series with per-day effects.
Both accept gpuType, region, startDate, endDate and baseDate. The window defaults to the trailing 30 days and is capped at 400 days per request.

Known limitations

Stated plainly, because an index is only as good as its disclosed weaknesses.
  • Intensity is one fleet-wide factor. It comes from Ornn-fleet heartbeat rollups and is not sliced by GPU class or region, and internal/test reservations are not yet excluded — a per-class intensity needs the node inventory join. It also measures the Ornn fleet while occupancy measures tracked marketplace listings; the two populations overlap but are not identical. The basis field is authoritative for what any response holds.
  • Region pairs a regional occupancy with a market-wide price. The occupancy side is region-filtered; gpu_daily_metrics is averaged across the regions it carries for a type, matching /api/daily-index. For region=global these agree; for a narrower region the effective price is approximate.
  • Occupancy is derived from tracked marketplace listings, so it measures the market Ornn observes, not every GPU on earth. Capacity coverage widens as more providers are tracked, which can move capacityMean for reasons that are not market events.
  • Daily granularity only. The price index settles daily, so a finer utilization series would have no price to divide into.