Hyperscaler
aws, azure, gcp
Catalog list prices. Published as their own series.
TCI‑CRI‑H100 is the headline
series: one NVIDIA H100 SXM GPU-hour, on-demand, per GPU,
ex-VAT, from an EU/EEA data centre. Companion series cover
market segments, additional GPU generations, and a chain-linked composite that follows
the market across hardware cycles. Everything below is read from
config/factors.yaml at build time, so no table on this page
can drift from the calculation. Series identifiers quoted as code below are the
stored keys, which the published files still use.
| GPU model | H100 SXM |
| Term | On-demand, no commitment |
| Location | EU/EEA data centre |
| Node size | ≥2 GPUs; sub-node offers excluded |
| Unit | Per-GPU-hour, ex-VAT, excluding storage and metered egress |
| Currency | USD primary, EUR companion at the ECB reference rate (T−1) |
A class prices its reference variant only. No assumed cross-variant normalisation factor enters the calculation path — a variant re-enters a class only once a factor is measured from same-venue, same-day, same-SKU pairs.
| Class | Reference variant |
| H100 | H100 SXM |
| H200 | H200 SXM |
| B200 | B200 SXM |
| B300 | B300 SXM |
| A100 | A100 SXM |
| H100P | H100 PCIE |
The headline draws from marketplace, neocloud only. The constituent distribution is bimodal — the hyperscaler catalog sits 5.4 standard deviations away — so it is published as its own series rather than averaged in.
aws, azure, gcp
Catalog list prices. Published as their own series.
runpod, vast.ai
Executable quotes with a demonstrated node size.
datacrunch, genesis_cloud, hetzner, leaseweb, nebius, ovhcloud, scaleway, seeweb
Published list prices from specialist operators.
Every offer is weighted by GPU count rather than counted once per provider: a median over six providers is a step function, a median over many weighted offers is locally smooth. Below the provider floor the value is published as a gap, never fabricated.
Hash-locked. A change to any row requires a version bump, a CHANGELOG entry, and one publication's notice.
One H100 SXM GPU-hour, on-demand, delivered from the EU/EEA, per GPU, ex-VAT, excluding storage and metered egress. A class prices its reference variant only.
Offers below 2 GPUs are excluded rather than normalised: the per-GPU discount saturates at 2 GPUs, while a 1-GPU offer carries a small-order premium. Sanity band $0.25–$25.00. Excluded tiers: interruptible, spot, community.
The constituent distribution is bimodal, so series never average across the neocloud/hyperscaler gap. The headline draws from marketplace, neocloud.
Manually verified static entries warn at 45 days and are excluded at 90.
A provider with any executable offer is weighted 2× a list-only provider. Capacity does not enter at provider level: it is unobservable for every list source.
No provider may exceed this share of total weight; the excess is redistributed pro-rata to a fixed point. Mathematically inert at n=4; binds from n=5. Every print publishes whether it bound.
Each provider's share is spread across its own offers in proportion to observed capacity, capped at 64 GPUs, defaulting to 8 where capacity is unobservable.
Count-based: clamp the k highest and k lowest offer prices to the k-th order statistic. Percentile winsorising is inert at this panel size — at n=6 both p5/p95 and p10/p90 resolve to (min, max) and clamp nothing.
Weighted median over offers: the first price at which cumulative weight reaches 50%. It always lands on a price someone actually quoted.
Fewer than 5 qualifying providers or fewer than 5 qualifying offers publishes no value, flagged insufficient_sources / insufficient_offers. There is no fallback waterfall.
USD value divided by the most recent ECB reference rate dated on or before the print date, refused beyond 7 days old. The ECB publishes after the cut-off, so the EUR leg is T−1 by construction.
The headline's 7-day mean, requiring at least 4 non-null days. Published beside the headline, never as it.
Chain-linked over class sub-indices from a base of 100, class shares floored at 5% and capped at 75%. A class that gaps drops out of that day's link.
A constituent moving more than 30% day-over-day is flagged for review and is never silently excluded.
Observations and prints are append-only, enforced by database triggers. An error is corrected as a new revision flagged correction; prior revisions stay queryable forever.
How a third party checks this print without asking us
# the full constituent set behind any print
python -m tci.run constituents --date 2026-09-07 --series EU-CRI-H100
# the weight review in effect on a date, recomputed from stored observations
python -m tci.run weights --date 2026-09-07
# regenerate this document and the lock from config
python -m tci.run docs
A sha256 over factors.yaml, sovereign.yaml, index.py, normalise.py and weights.py. CI fails whenever the working tree stops matching it.sha256:5c61ce6b3b8b577e91deb2d48844b4e6c1a218290df5959b1f28d5de358a6c82
The full published history, latest revision per date and series, plus today's snapshot with its constituent set.
Any print may be challenged. Acknowledged within 7 days; the outcome is published with the next print, whether it is a correction or a rationale for no change.
TCI (The Compute Indices) is a daily reference price for renting AI compute delivered from data centres physically located in the EU/EEA. Headline series: TCI-CRI-H100, in USD per GPU-hour with a EUR companion at the ECB reference rate.
TCI is a price-transparency benchmark, not a settlement benchmark. Every print is reproducible by any third party from public sources using the published code. It is not transaction-based and must not be referenced in a financial contract. The conditions that would have to be met before settlement use is credible are published in §9.
One TCI-CRI-H100 unit = one NVIDIA H100_SXM 80GB GPU-hour, on_demand (no term commitment), datacenter-hosted, delivered from the EU/EEA, per-GPU, ex-VAT, excluding storage and metered egress.
A class prices its reference variant only. Cross-variant normalisation by assumed factors was removed in v0.3.0: H100 SXM and H100 PCIe are different products, and H100→H200 normalisers disagree in sign depending on whether you divide by BF16 PFLOPs (+31%) or HBM bandwidth (−9%). A variant may enter a class only under a factor measured from same-venue, same-day, same-SKU pairs.
| Class | Reference variant | Variants (factor) |
|---|---|---|
H100 | H100_SXM | H100_SXM (x1) |
H200 | H200_SXM | H200_SXM (x1) |
B200 | B200_SXM | B200_SXM (x1) |
B300 | B300_SXM | B300_SXM (x1) |
A100 | A100_SXM | A100_SXM (x1) |
H100P | H100_PCIE | H100_PCIE (x1) |
Excluded outright: variants not listed above, community/consumer hosts, interruptible/spot tiers (interruptible, spot, community), term-committed prices, offers below 2 GPUs, prices outside the sanity band [$0.25, $25.00] per GPU-hour.
Why the node floor is 2, not 8. Measured within one venue on one day, the per-GPU discount saturates at 2 GPUs (1×=1.000, 2×=0.951, 4×=0.916, 8×=0.916), so 2/4/8-GPU offers are mutually comparable within ~4% and need no adjustment, while the 1-GPU offer carries a ~9% small-order premium and is excluded rather than normalised away. The former 8-GPU floor admitted marketplace inventory on 1 collection day in 10 and discarded essentially all of the index's price discovery.
Executable marketplace asks are preferred over list prices and carry a 2× weight multiplier. Overlay data (power prices) never enters the calculation. Competitor indices are never ingested.
The constituent distribution is bimodal — measured separation of 5.4 standard deviations between the neocloud/marketplace cluster and the hyperscaler catalog cluster. Averaging across that gap yields a number no one quotes, so series are segregated by market segment and never drawn across it:
| Series | Population |
|---|---|
EU-CRI-H100 (headline) | marketplace, neocloud |
EU-CRI-H100-MKT | marketplace |
EU-CRI-H100-HS | hyperscaler |
EU-CRI-H100-SOV | marketplace, neocloud (further filtered by config/sovereign.yaml) |
Per UTC day and series. The unit of aggregation is the offer, not the provider.
insufficient_sources / insufficient_offers. There is no fallback waterfall. A gap is credible; a fabricated print is fatal.Constituent prices moving more than 30% day-over-day are flagged (jump) for manual review but are not excluded. A print with no executable input is flagged no_executable_input — a list-price-only print says so on its face.
The headline is the raw daily cross-section, not a chain-linked level. A Laspeyres link with a same-day divisor reset contributes exactly zero return on panel entry and exit, which makes in-panel up-moves permanent while down-moves taken via exit and re-entry are laundered out — a ratchet worth roughly +7.8% per cycle for a constituent at the 25% cap. A chained level is published beside the headline as a labelled companion, and the divergence between them is a published health metric with a 5% review trigger.
The composite aggregates the class series into one level (base 100 at its first print), chain-linked so that reweighting never jumps the published level:
no_linkable_series).| Series | Constituents |
|---|---|
EU-CRI-H100 | all qualifying H100-class providers (headline) |
EU-CRI-H100-7D | 7-day mean of the headline |
EU-CRI-H100-SOV | EU/EEA-headquartered operators: datacrunch, genesis_cloud, hetzner, leaseweb, nebius, ovhcloud, scaleway, seeweb |
EU-CRI-H100-MKT | marketplace segment only |
EU-CRI-H100-NC | neocloud segment only |
EU-CRI-H100-HS | hyperscaler catalog segment only |
EU-CRI-H100-PCIE | H100 PCIe, priced as its own class (no assumed SXM factor) |
class series (EU-CRI-A100, EU-CRI-H200, EU-CRI-B300, …) | one per observed class in §1; published once ≥5 providers exist (gapped, with audit trail, before that) |
EU-CRI-COMPUTE | chain-linked composite of class series (§3.2); a level, not a $/hr price |
EU-CRI-H100-CLOUD was retired in v0.3.0 and is not published. Its historical values remain in index_history.csv under the methodology version that produced them.
Segment size and the publication gate. A sub-population series can only print when its segment holds at least min_providers (5) qualifying providers. EU-CRI-H100-MKT and EU-CRI-H100-HS draw on segments smaller than that and therefore gap by construction rather than by circumstance. This is stated here because a permanent gap and a temporary one look identical on the dashboard, and the difference matters to a reader.
Raw observations and published prints are append-only (enforced by database triggers). Errors are corrected in the next print as a new revision flagged correction; prior revisions remain queryable forever. The full constituent set for any print is available via python -m tci.run constituents --date YYYY-MM-DD.
Any change to the parameters below or the calculation code requires a version bump, a CHANGELOG entry, and one publication's notice before taking effect — enforced mechanically via METHODOLOGY.lock in CI. See GOVERNANCE.md.
# EU-CRI methodology parameters. Every number that affects a published print lives here.
# Changing anything in this file (or sovereign.yaml, index.py, normalise.py, weights.py)
# requires a methodology_version bump + CHANGELOG entry - enforced by CI (see GOVERNANCE.md).
methodology_version: "0.3.0-dev" # '-dev' relaxes the lock guard pre-launch; drop it at launch
reference_unit:
gpu_model: H100_SXM # headline reference variant (class H100 below)
term: on_demand # no term commitment
location: EU_EEA # datacenter physically in the EU/EEA
# v0.3.0 removed `node_gpus: 8`. The node-size floor now lives in filters.min_gpu_count
# and is 2, not 8 - see the measurement recorded there.
# Per-GPU-hour, ex-VAT, excluding metered egress. USD primary, EUR companion at the
# ECB reference rate (T-1: the ECB publishes ~14:00 UTC, after our 11:00 cut-off, so
# the EUR leg structurally cannot use same-day FX - see METHODOLOGY.md).
# Compute classes the index observes. A class prices its reference variant ONLY.
# v0.3.0: cross-variant normalisation factors are removed. H100 SXM and H100 PCIe are
# different products, and H100->H200 normalisers disagree in SIGN depending on whether
# you divide by BF16 PFLOPs (+31%) or HBM bandwidth (-9%). A variant may re-enter a class
# only with a factor MEASURED from same-venue, same-day, same-SKU pairs (see
# `measured_factors` below). No assumed factors are permitted in the calculation path.
model_classes:
H100: {reference_variant: H100_SXM, variants: {H100_SXM: 1.0}}
H200: {reference_variant: H200_SXM, variants: {H200_SXM: 1.0}}
B200: {reference_variant: B200_SXM, variants: {B200_SXM: 1.0}}
B300: {reference_variant: B300_SXM, variants: {B300_SXM: 1.0}}
A100: {reference_variant: A100_SXM, variants: {A100_SXM: 1.0}}
H100P: {reference_variant: H100_PCIE, variants: {H100_PCIE: 1.0}} # PCIe priced separately
# Market segment per provider. This is the tier-segregation control and it is
# methodology-affecting: the constituent population is bimodal (measured 2026-08-15,
# cluster separation 5.4 sd between the neocloud/marketplace cluster [2.16..3.85] and the
# hyperscaler cluster [5.44..7.36]). Averaging across that gap produces a number nobody
# quotes. Providers not listed default to 'neocloud'.
segments:
marketplace: [vast.ai, runpod]
hyperscaler: [aws, gcp, azure]
neocloud: [nebius, datacrunch, seeweb, scaleway, ovhcloud, hetzner, genesis_cloud,
leaseweb]
# Which segments each published series draws from. The headline deliberately excludes the
# hyperscaler catalog tier - those are the two highest and least-moving prices in the panel
# and they belong in the series where they are the correct object of measurement.
series_populations:
headline: [marketplace, neocloud]
marketplace: [marketplace]
hyperscaler: [hyperscaler]
sovereign: [marketplace, neocloud] # further filtered by config/sovereign.yaml
filters:
min_gpu_count: 2 # v0.3.0: was 8. Measured within-venue same-day (vast.ai
# 2026-07-18) the per-GPU discount SATURATES at 2 GPUs:
# 1x=1.000, 2x=0.951, 4x=0.916, 8x=0.916. So 2/4/8-GPU offers
# are mutually comparable within ~4% and need no factor, while
# the 1-GPU offer carries a ~9% small-order premium and is
# excluded rather than normalised. The old 8-GPU floor passed
# 1 of 10 vast.ai days and threw away ALL marketplace price
# discovery (9 distinct EU H100 prices, +12.3% range).
price_floor_usd: 0.25 # junk-listing guard: asks below this are excluded
price_ceiling_usd: 25.0 # sanity ceiling
datacenter_verified_only: true
exclude_tiers: [interruptible, spot, community]
aggregation:
unit: offer # v0.3.0: aggregate over OFFERS, not one price per provider.
# A median over ~6 providers is a step function with delta 1.0
# to one name and 0.0 to the rest. A median over many
# GPU-count-weighted offers is the SOFR construction and is
# locally smooth. Density is what makes the median sound.
estimator: weighted_median # over offers, weighted by GPU count (capacity)
trim_k: # count-based trim: clamp the k highest and k lowest.
- {min_n: 0, k: 0} # Percentile winsorising is INERT at this panel size: at n=6
- {min_n: 5, k: 1} # nearest-rank p5/p95 AND p10/p90 both resolve to (min, max)
- {min_n: 10, k: 2} # and clamp nothing. The shipped [5,95] was a no-op for the
- {min_n: 20, k: 3} # entire history of the index. Count-based trimming binds at
# every n.
min_providers: 5 # below this: value=null, flag='insufficient_sources'.
# A gap stays a gap - no fallback waterfall, no fabrication.
min_offers: 5 # and at least this many surviving offers
smoothing_days: 7 # headline companion series: 7-day mean (>=4 non-null required)
weights:
capacity_cap: 64 # daily capacity term = min(observed qualifying GPUs, cap)
default_capacity: 8 # ... where capacity is unobservable (all list-price rows)
executable_multiplier: 2.0 # executable-quote sources weighted double list-price sources
max_weight_share_pct: 25 # per-PROVIDER aggregate cap, applied after offers are summed
# within a provider. NOTE: this cap is mathematically inert at
# n=4 (max(25, 100/n) forces exactly equal shares - verified
# 0.0 deviation over 50k random weight vectors). It binds again
# only once the panel is wider; `cap_bound` is published per
# print so a reader can see whether it did anything.
review: # scheduled, data-driven weight reviews (fixed rule, no discretion)
anchor_weekday: 1 # ISO weekday a review takes effect (1 = Monday, i.e. weekly)
window_days: 28 # trailing observation window ending the day before the review
min_history_days: 5 # fewer collection days than this in the window -> bootstrap
composite: # EU-CRI-COMPUTE: chain-linked basket of class sub-indices
base_value: 100.0 # level on the composite's first print
min_class_share_pct: 5 # floor for an eligible class at review (applies with >=2 classes)
max_class_share_pct: 75 # cap for a class at review (applies with >=2 classes)
# Continuity series. v0.3.0 DEMOTES chain-linking from the headline to a labelled companion.
# A Laspeyres link with a same-day divisor reset contributes exactly zero return on panel
# entry/exit, which makes in-panel up-moves permanent while down-moves taken via exit and
# re-entry are laundered out - a ratchet worth ~+7.8% per cycle for a 25% constituent.
# The headline is therefore the raw cross-section (reproducible, no drift, no ratchet); the
# chained level is published beside it and the divergence between them is a health metric.
continuity:
publish_chained: true
base_value: 100.0
divergence_review_pct: 5 # chained vs raw divergence above this triggers a documented review
fx:
max_age_days: 7 # refuse an FX rate older than this (flag the print instead)
# HARD RULE enforced in code: fx_date <= print date. Two 2026-07 prints were computed
# with an FX rate published on 2026-07-21, i.e. AFTER the print date - a look-ahead
# introduced by backfill. Covered by a regression test.
staleness: # static (manually verified) provider entries
warn_days: 45 # loader warns
exclude_days: 90 # excluded from the index with exclusion_reason='stale'
quality:
jump_flag_pct: 30 # day-over-day provider move that flags (never excludes)
# Factors measured from same-venue, same-day, same-SKU observations. Published for
# transparency; NONE of these currently enter the calculation path. A factor may only be
# promoted into `model_classes` after it is supported by >=3 independent venue-days.
measured_factors:
node_size_per_gpu: # vast.ai, EU H100_SXM, same-day ratios vs the 1-GPU offer
"2": 0.9514 # n=3 venue-days
"4": 0.9159 # n=1 - provisional
"8": 0.9158 # n=1 - provisional
fabric_infiniband_premium: 1.107 # Azure westeurope ND96isr vs ND96is_noIB, n=1 pair.
# PROVISIONAL - measures Azure's internal price ladder,
# not the market's. Not in the calculation path.
eu_eea_countries: [AT, BE, BG, HR, CY, CZ, DK, EE, FI, FR, DE, GR, HU, IE, IT,
LV, LT, LU, MT, NL, PL, PT, RO, SK, SI, ES, SE, IS, LI, NO]
# NOTE: CH (Switzerland) is deliberately absent - Switzerland is in EFTA but NOT the EEA.
| IOSCO principle | Implementation |
|---|---|
| Benchmark design & data sufficiency (P6–P7) | Observable, executable marketplace quotes anchored; ≥5-source rule; weights set by observed data on a fixed schedule; 25% concentration cap; no expert judgement in the calculation path |
| Hierarchy of data inputs (P8) | Executable quotes weighted 2.0x over list prices inside the weight review; overlay data never enters the calculation |
| Transparency of methodology (P9, P11) | This document is auto-generated from config, semver-versioned; every parameter is a visible config value; every weight review is stored and queryable |
| Changes to methodology (P12) | CHANGELOG + one-publication notice before any parameter change takes effect; hash-locked in CI. Scheduled weight reviews follow the fixed published formula and are data updates, not methodology changes |
| Quality of the administrator (P4–P5) | Named author; stated conflicts (the author may trade venues the index observes — disclosed); corrections never silent |
| Complaints & audit trail (P13, P16) | Raw observations and weight reviews immutable in SQLite; any reader can request the constituent set for a given day |
TCI is a research publication. It is not investment advice and is not administered as a benchmark under EU Regulation 2016/1011; it may not be used as a reference price in financial instruments.
Author and administrator: Mark Rusch (rusch.mh@gmail.com). Conflicts: the author may hold positions on venues whose prices the index observes; see GOVERNANCE.md.