Skip to content

feat(model): complete the device table through Blackwell - #62

Merged
vyncint merged 1 commit into
mainfrom
feat/device-table-through-blackwell
Sep 9, 2026
Merged

feat(model): complete the device table through Blackwell#62
vyncint merged 1 commit into
mainfrom
feat/device-table-through-blackwell

Conversation

@vyncint

@vyncint vyncint commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Milestone 2.2.0, issue #52.

DEVICES held two rows — A10G (8.6) and T4 (7.5) — so --cc 9.0 on a Hopper part was a model error, which is the architecture a reader arriving from cuda-oxide is most likely to be holding. Added 8.0 (A100), 8.9 (L4/L40), 9.0 (H100) and 10.0 (B200).

Provenance

Every field but sm_count is a compute-capability fact from the CUDA C++ Programming Guide's Technical Specifications per Compute Capability table. sm_count is a product fact — two parts at one capability differ — so each row names the part it came from.

The four new rows' shared-memory figures were cross-checked against reconverge 0.6.0's independent cc.rs table and agree exactly, allowing for the 1 KiB the driver reserves per SM on Ampere and later:

cc reconverge max_per_block here smem_per_sm
7.5 64 KiB 64 KiB (no reservation pre-Ampere)
8.0 163 KiB 164 KiB
8.6 99 KiB 100 KiB
8.9 99 KiB 100 KiB
9.0 227 KiB 228 KiB
10.0 227 KiB 228 KiB

sm_count barely affects ranking: it enters only through waves = grid / (blocks_per_sm * sm_count), a constant divisor scaling every candidate alike, and changes an ordering only where the .max(1.0) clamp bites. Documented on the field so nobody reads more into it.

A near-miss worth flagging

I first widened ModelError::UnknownCc(String) to carry the known list as a second field — a breaking change to a public enum variant on a crate published at 2.1.0, which is precisely the mistake I criticised in #53 an hour earlier. It keeps its single field; the list is computed in the #[error] template instead.

No #[non_exhaustive] appears anywhere in this workspace, so every enum here is in that position. Worth raising when #56 lands the semver gate.

The shared-crate question, answered

#52 asked whether one simt-device-table crate should own both tables. No, for 2.2.0 — and the reasoning is recorded on DEVICES, where someone tempted to unify them will read it:

  1. They answer different questions. reconverge needs the per-block opt-in ceiling for RC004's "could this ever load"; this needs per-SM occupancy capacity. Only shared memory overlaps, and the shapes differ even there.
  2. It would be a third pin in the lockstep set — in the release whose headline issue was that the pin set went 133 commits stale.
  3. The drift is checkable without it, and was just checked: eleven numbers, all equal.

Revisit on a third consumer, or on the first actual disagreement.

Tests

  • device("9.0") and device("10.0") return the documented parameters.
  • Every row: ascending by parsed (major, minor)"10.0" precedes "8.6" as a string, which is the trap — warps × 32 == threads, threads in 1024..=2048, blocks in 8..=32, static per-block cap flat at 48 KiB, smem/SM between one block's worth and 228 KiB, sm_count non-zero.
  • Every row reachable by its own name; no duplicate capability (device takes the first match, so a duplicate would shadow silently).
  • The unknown-cc message names all six, ascending.
  • device_table_is_closed kept its rule and moved its example — it asserted device("9.0").is_err(), which is the thing this PR fixes.

Also

docs/LIMITATIONS.md gains a section saying the model's table is narrower than the gate's: reconverge covers 7.0–12.0, so prune --cc 12.0 can succeed where tune --backend model --cc 12.0 refuses. Pascal and the embedded parts (7.2, 8.7) are absent from both halves here because nothing in this project has run on one — I chose not to guess at Jetson SKU SM counts. Only 8.6 and 7.5 have ever had a kernel measured on them; the other four rows are documented capacity, not experience.

just ci green, corpus prunes unchanged (93 clean, 8 refused), all four new capabilities tune end to end.

Closes #52

Signed-off-by: Vyncint Ng 115854244+vyncint@users.noreply.github.com

`DEVICES` held two rows, A10G (8.6) and T4 (7.5), so `--cc 9.0` on a Hopper
part -- the architecture a reader arriving from cuda-oxide is most likely to
be holding -- was a model error. Added 8.0 (A100), 8.9 (L4/L40), 9.0 (H100)
and 10.0 (B200), ascending, each row naming the part its `sm_count` came
from.

Every field but `sm_count` is a compute-capability fact from the CUDA C++
Programming Guide's "Technical Specifications per Compute Capability" table.
`sm_count` is a product fact -- two parts at one capability differ (L4 58 /
L40 142, H100 SXM 132 / PCIe 114) -- and it barely affects ranking anyway:
it enters only through `waves = grid / (blocks_per_sm * sm_count)`, a
constant divisor scaling every candidate alike, and changes an ordering only
where the `.max(1.0)` clamp bites. Documented on the field.

The four new rows' shared-memory figures were cross-checked against
reconverge 0.6.0's independent `cc.rs` table and agree exactly, allowing for
the 1 KiB the driver reserves per SM on Ampere and later (its 163/99/227 KiB
per-block ceilings against 164/100/228 KiB per SM here).

An unknown capability now lists the known ones. Nothing is guessed: a
fabricated capacity would still produce an occupancy number, and an
occupancy number is the sort of thing a reader believes. The list is sorted
numerically, because "10.0" precedes "8.6" as a string and that is the trap.

`ModelError::UnknownCc` keeps its single field -- the known list is computed
in the `#[error]` template. Adding a field to a public enum variant is a
break, and 2.2.0 is a minor bump; I nearly shipped exactly the mistake #53
was about.

## The shared-crate question, answered

#52 asked whether one `simt-device-table` crate should own both this and
reconverge's. No, for 2.2.0, and the reasoning is recorded on `DEVICES`
where someone tempted to unify them will read it: the two answer different
questions (opt-in ceiling per block vs occupancy capacity per SM, sharing
only shared memory and disagreeing in shape even there); a shared crate
joins the lockstep pin set, in the release whose headline issue was a stale
pin set; and the overlap is eleven numbers that were just verified equal.
Revisit on a third consumer, or on the first actual disagreement.

## Tests

- `device("9.0")` and `device("10.0")` return the documented parameters.
- Every row: ascending by parsed `(major, minor)`, warps x 32 == threads,
  threads in 1024..=2048, blocks in 8..=32, static per-block cap flat at
  48 KiB, smem/SM between one block's worth and the largest documented
  228 KiB, `sm_count` non-zero.
- Every row reachable by its own name, no duplicate capability.
- The unknown-cc message names all six, ascending.
- `device_table_is_closed` kept its rule and moved its example: it asserted
  `device("9.0").is_err()`, which is the thing this commit fixes. Pascal
  (6.1), 99.9 and the bare "8" stand in.

Corpus prunes unchanged: 93 clean, 8 refused. All four new capabilities
tune end to end.

Closes #52

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
@vyncint
vyncint merged commit b44fd77 into main Sep 9, 2026
8 checks passed
@vyncint
vyncint deleted the feat/device-table-through-blackwell branch September 9, 2026 10:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The device model knows two GPUs (CC 8.6, 7.5); Hopper and Blackwell are model errors

1 participant