# GPUDeck methodology, version 3

Effective 2026-08-21. Version 2 supersedes version 1 the same day; §7 and §7a state what changed and why. Numbered so a figure can be cited against a specific rule, and so a
stranger can reproduce any published number from the published data.

Two independent measurements are published. They come from different sources, cover different
windows, and must not be combined. §1–§4 cover the census; §5–§8 cover the throughput pilot.

---

## 1. What the census counts

A daily snapshot of the GPU models present on the Sogni fast network, taken from that
network's own public worker listing. One row per (date, model): the number of distinct workers
of that model observed on that date.

**A worker count is not capacity, utilisation, or earnings.** It is presence. A model with 40
workers is not doing twice the work of one with 20; it has twice as many machines advertising
themselves.

## 2. Sampling

One observation per model per day. A model appears on a date if at least one worker of that
model was visible in that day's snapshot. Snapshots are taken from a scheduled job; a missed
day is absent from the series rather than interpolated.

Window to date: 2025-12-17 onward, 249 days, 39 distinct models.

## 3. How GPUs are named and ordered

**Figures name GPU models directly.** They did not always: through 2026-08-22 they
identified models as `Class 01`, `Class 02`, … and required a lookup. That was changed
because a reader cannot check a ratio whose operands are hidden, and the anonymity was
protecting nothing — the census already published the model names it was hiding.

The alias file remains, and is still published in `data/class-aliases.csv` and
`data/class-aliases.json`, because it does a second job that the naming change does not
remove: it fixes the permanent order in which the two GPUs of a pair are listed.

**Aliases are permanent and append-only.** A class is never renumbered — not when a model
disappears, not when a new model appears, not when sort order or the roster rule changes. A
new model takes the next unused index. This is what makes `Class 14 was last observed
2026-05-14` still true after the next vintage.

Ordering in a figure may change. Identity may not.

The original reason for aliasing was that this project publishes no universal card ranking
and a figure captioned with vendor names reads as one. That concern is real and was not
dismissed; it is now answered in the figures themselves rather than by withholding names.
Every paired comparison states that it measures a *deployment* — a GPU together with its
host, virtualisation layer and dispatch — and rows whose pairing carries a known host
confound are marked and excluded from the headline range. The alphabetical order is
arbitrary by construction and is stated as such wherever it appears, so it cannot be read
as a ranking.

## 4. Which classes appear in the Lifelines figure

Published rule, fixed:

> A class is plotted if its current count is above zero, **or** its all-window peak is at
> least 5 workers.

No top-N cap and no dynamic threshold. Both would remove a class for reasons of layout rather
than evidence — if a class were dropped because a new class outranked it, the figure would
report a disappearance that did not happen.

Classes below the threshold remain in the census, the CSV/JSON, and the all-class table. The
figure states how many were excluded.

`LAST OBSERVED` marks the final dated appearance of a class in the window. It records that a
class was observed, had a count, reached zero, and stayed absent. It does **not** establish
that hardware was powered off, sold, migrated, or withdrawn for economic rather than
maintenance reasons. Persistent absence is consistent with withdrawal; the census does not
establish why.

---

## 5. What the throughput pilot measures

Completed-render latency on paid production jobs, for one named pair of cards, on workloads
both cards ran on the same day.

This is **not** a synthetic benchmark. Every render counted was a real customer job that a
real customer paid for.

## 6. Goodput

The unit the project exists to publish:

```
goodput = 3600 × completed / occupied_seconds
```

where `occupied_seconds` is wall time the GPU was held **including attempts that were
cancelled before finishing**. Equivalently `goodput = attempt_rate × completion_yield`.

Occupied time is the point. A cancelled attempt consumes the card and earns nothing, so a
median over the renders that survived overstates what the hardware produced. Measured on the
reference fleet, cancellation runs 0–34% by card and is **worse on slower cards**, so counting
it widens the gap between cards rather than flattering it.

An attempt is paired by job id: `inference starting for job <id>` to either completion or
cancellation. A cancelled attempt's terminal line carries no workload; it is recovered from
the dispatch line. Without that recovery, cancelled time is unattributable and the metric
collapses back to completed-only.

## 7. Cells, floors and why they are not negotiable

Goodput is only published **per (card, workload) cell**. Aggregating across workloads
reintroduces exactly the substitution error the project exists to expose: a card whose
occupied time is dominated by long video jobs looks slow against a card running short image
jobs, and the comparison says nothing about the hardware.

A cell is keyed by **card class**, never by memory capacity.

Version 1 bucketed by memory tier. That was wrong, and not only presentationally: two distinct
32GB classes were summed into one cell whose attempts, completions and occupied seconds
described neither of them. It changed six of eight published ratios, compressing a
2.16x-11.13x spread into 2.13x-4.65x by averaging a slower class into the faster bucket. It is
the same substitution error §7 forbids across workloads, applied to hardware instead.

A cell is published only if it has **at least 8 attempts and at least 300 occupied seconds**.

Both floors are required. Renders can be three seconds long, so an attempt count alone is not
a floor. A 76-second cell once published a ratio of 0.69× — the larger card apparently
losing — where a full day of the same workload showed 1.93× the other way. One model switch
inside a short cell sets the entire number.

## 7e. Replication: attempts are not observations

Every figure here comes from **one operator's fleet**, and the datasets say so in a column
rather than only in prose. `gpu-goodput-ratios` and `gpu-throughput-ratios` both carry:

| column | meaning |
|---|---|
| `operator_count` | always 1. That is the point, not an oversight. |
| `host_count_a` / `_b` | distinct hosts behind each side, without publishing host identity |
| `physical_gpu_count_a` / `_b` | distinct physical cards behind each side of the ratio |
| `card_days_a` / `_b` | distinct (card, day) pairs contributing |
| `attempts`, `completions`, `cancellations` | support counts for the dated cell, never treated as independent replication |
| `measurement_interval_a` / `_b` | ISO-8601 duration of the source measurement window |
| `meets_replication_floor` | every published goodput row is true; rows below two cards/card-days per side are suppressed |

**A card that ran 600 attempts in a day is one observation of that card, not 600.** The
attempts share a card, a host, a driver, a thermal environment and a dispatch queue, so
treating them as independent would overstate confidence by roughly the square root of the
attempt count. Confidence, where stated, is clustered at physical-GPU-by-day.

This matters more than it might sound. The archive contains many (card, workload) cells with
hundreds of attempts from one physical card. Those cells remain in the dated private source
archive, but both public ratio exports now suppress a comparison unless each side has at least
two physical cards/card-days. The generated JSON records how many candidate comparisons were
suppressed, so the missing rows are accounted for rather than silently forgotten.

The market page publishes physical-card counts beside attempts and recomputes on every build
whether any range claim survives the same replication floor. Host-confounded rows, where they
qualify, stay dagger-marked and separated rather than averaged into a clean range.

Some classes cannot clear the floor at all, because only one such card exists in this fleet.
That is a limit of a single-operator dataset and is exactly what §5 of GPUDECK-PLAN's gate B
is waiting on: an independent operator producing an overlapping cell.

## 7a. Which two classes a ratio compares, and why it is fixed in advance

A workload may be observed on more than two classes. The pair a ratio reports is chosen by
**largest memory, then lowest permanent class index** against **smallest memory, then highest
permanent class index** — the same append-only index defined in §3.

Both keys are fixed before any measurement is read. This matters more than it looks: choosing
the fastest and slowest *observed* classes would select each comparison on its own outcome and
inflate every ratio on the page. An unresolved class is an error, not a default — a sentinel
index that still sorts is a sentinel that silently decides the pair.

The same-memory table (§7b) uses the identical rule, ordering the pair by class index alone.

## 7b. Equal memory, unequal output

Published as a separate table: pairs of classes holding the **same** memory, on the same
workload, on the same day. The observed range is published on the page itself and is
recomputed every build; it is deliberately not restated here, because a figure copied
into this document drifts and then contradicts the page it is meant to document.

If memory capacity determined throughput these would all sit at 1.00x. They do not, and the
rows near 1.00x carry as much of the argument as the wide ones.

What this does **not** say is that capacity predicts nothing. Capacity decides what a card can
run at all: a model whose declared dependency pack exceeds the card does not load, and on this
fleet a 24GB class runs zero of one video family for exactly that reason. The claim is bounded
- equal capacity does not imply equal output - and the earlier wording ("capacity predicts
neither outcome") overstated it into something this evidence cannot support, and that the
fleet's own operating record contradicts.

Marketplaces price against capacity, so a buyer choosing on that alone can pay one rate for a
fraction of the finished work.

This table cannot exist under version 1's bucketing, which averaged exactly these pairs
together before they could be compared.

## 7c. Host environment is inside the measurement

Goodput is measured on production hosts, and the host is part of what is measured. One class
in the reference fleet runs under a Windows virtualisation layer that reserves roughly 2.4GB
of its memory and is not recoverable by configuration; that class anchors the widest cross-tier
ratios reported here.

The number is real — it is what that machine actually produced for paid work — but it is a
property of a card **in that deployment**, not of the silicon. A reader comparing hardware
should treat the widest ratios as an upper bound that includes a hosting penalty.

The same-memory table (§7b) is **not** uniformly free of this. Its 32 GB rows compare two
natively hosted classes and are the cleaner contrast; its 16 GB rows contain the same
paravirtualised class and carry the same hosting penalty. Those rows are marked with a
dagger on the page. An earlier version of this section described §7b as a table "where both
classes sit on comparable hosts", which was true of most of its rows and false of the rest —
the correction is recorded here rather than silently applied, because the claim had already
been published.

The project publishes this rather than quietly dropping the class, because dropping it would
improve the numbers by removing the case that pays for the honesty.

## 7d. Completion yield

Of the attempts a GPU started, the fraction that finished and were paid. An attempt that is
cancelled or fails still occupies the card, so yield is part of the difference between time
bought and work delivered.

**Yield is keyed by GPU model, never by memory capacity** — the same rule as §7, for the same
reason, and it was not always followed here.

Until 2026-08-23 this figure was bucketed into 16/20/32GB classes, and the page concluded from
it that yield "orders the same way completed-render latency does". That ordering does not exist
in the per-GPU data. Measured on 2026-08-22:

| GPU | memory | attempts | yield |
|---|---|---|---|
| RTX 5070 Ti | 16 GB | 557 | 0.679 |
| RTX 3080 | 20 GB | 566 | 0.806 |
| RTX 4080 | 16 GB | 1507 | 0.830 |
| RTX 5090 | 32 GB | 6180 | 0.950 |

The 16GB RTX 4080 converts more of what it starts than the 20GB RTX 3080, so yield is not
monotonic in capacity. Averaging the 4080 together with the 5070 Ti pulled the 16GB bucket to
0.789, below the 20GB figure, and manufactured the monotonic order the page then reported. The
bucketing produced the finding.

This is the identical substitution §7 documents for goodput cells. The rule existed, with
measurements; this figure simply never adopted it, because until this revision it had no
section here at all.

What survives: the lowest yield belongs to the GPU running under the virtualisation layer and
the highest to the fastest card, and yield remains a real cost. It is not a property of how
much memory a card has.

---

## 8. What is deliberately not published

**No prices.** Marketplace prices are those companies' data and their terms restrict
republication. A reader brings their own price and divides. Where a price is needed for
context, GPUDeck uses an openly licensed third-party series (CC BY 4.0) with attribution, and
never a figure it collected itself.

**No universal card ranking.** Every ratio is stated per workload. A single headline number
across workloads would move with the mix of work each card happens to be sent, which is a fact
about dispatch, not hardware.

**No absolute earnings, and no operator identifiers** — no hostnames, wallets, worker or
customer ids, prompts, image ids, or paths, in the page or the data.

**No absolute rates.** The exports publish *ratios*, never per-hour figures:
`data/gpu-goodput-ratios.csv` emits a goodput ratio plus attempts, completions, cancellations
and replication counts on each side, but not `occupied_seconds` or an absolute goodput rate.
The latency export emits `time_ratio` and never `median_s` or
`renders_per_hour`. An absolute renders-per-hour figure describes one operator's throughput
capacity; a ratio does not.

**Attempt and completion counts ARE published**, in the tables and in the exports, because a
ratio cannot be judged without its sample size — a figure resting on 24 attempts and one
resting on 1,507 are not the same claim, and §7 exists precisely because small cells mislead.
An earlier draft of this section claimed no absolute volumes of any kind were published, which
was never true of this page: the completion-yield table has always shown starts and paid
completions, and yield is their quotient, so withholding either while publishing the other
would protect nothing. The rule is about rates, not counts.

---

## 9. Status, revisions and limitations

The census is longitudinal and is the durable series. **The throughput pilot is exploratory
and single-day.** One day cannot establish that the multiplier varies by workload; that
requires consecutive days and an explicit interaction test. The page labels it as such and
should continue to.

The census is not independently replicable: a 19-network survey found no other GPU network
publishing a dated, model-level worker census, so no second source exists to check it against.
The raw daily snapshots are published so the derivation can be checked even though the
underlying observation cannot be reproduced by a third party after the fact.

This document is versioned. When a rule changes, the version increments and the change is
stated here rather than applied silently — a vintage published under version 1 stays
interpretable under version 1.

**Version 3 (2026-08-23)** made four rule changes, all of which affect how published
figures should be read:

1. **Completion yield is keyed by GPU model, not memory capacity** (§7d). This reverses a
   published conclusion: yield does not order by capacity, and the ordering previously
   reported was produced by the bucketing. Yield figures published before this date are
   blended across GPUs within a capacity tier and are not comparable to version 3 figures.

2. **Pairings carrying a known host confound are excluded from headline ranges** (§7c).
   They are still published and still shown, marked, but no aggregate range, threshold or
   conclusion presented as a hardware comparison is computed over them. Headline ranges
   published before this date pooled them: the goodput and break-even ranges both read
   "1.73x to 7.40x" where the unconfounded range is 1.73x to 2.32x.

3. **The equal-memory ratio is stated in a fixed order rather than as larger-over-smaller**
   (§7b). The previous form could not fall below 1.00 and so could not express which way a
   comparison ran. On 2026-08-23 data no published figure changes, because all 28 rows land
   at or above 1.00 either way — but that is now a result rather than something the
   estimator guaranteed.

4. **Goodput ratios are published as data** (`data/gpu-goodput-ratios.csv`, §8). The page's
   headline metric previously had no downloadable form.

**Version 2 (2026-08-21)** changed the goodput cell key from memory tier to card class (§7),
fixed the comparison pair in advance (§7a), added the equal-memory table (§7b), and stated the
host-environment confound (§7c). Ratios published under version 1 are not comparable to
version 2 ratios for the same workload: they were computed over blended cells.

## 10. Reproducing a past vintage

`CITATION.cff` carries a dated `version`, and `/data/` is regenerated daily rather than
archived per date. A vintage is still reproducible, because **both published datasets are
cumulative and dated**:

- `gpu-census.csv` holds one row per (date, model) for every observed day — 249 days at the
  time of writing, from 2025-12-17.
- `gpu-throughput-ratios.csv` carries `observation_date` and accumulates measurement days.

To reproduce the vintage published on a given date, filter both files to
`observation_date <= <that date>`. Nothing is discarded on regeneration, so the rows behind an
earlier citation remain present in the current file.

Class aliases are append-only by the §3 contract, so an identifier quoted from an old vintage
still resolves in the current `class-aliases.csv`. That is the property that makes this work:
if aliases were reassigned, filtering by date would recover the numbers but not their meaning.

The one thing filtering cannot recover is a **methodology change**. That is what §9's version
counter is for: a figure cited under version 1 should be read against the rules in this
document at version 1.

## 11. Contributing a measurement

Every throughput number to date comes from one operator's fleet. `CONTRIBUTING-DATA.md`
describes how to submit your own: one script, one JSON file, one pull request, no account and
no telemetry.

## 12. Licence

All published data is CC BY 4.0. Cite as `data/CITATION.cff`.
