Ext to Stem 400

Topology seminar · Wayne State University

Computing Ext to stem 400 on an HPC cluster: a worklog

The Adams $E_2$ page through stem 400: Nassau's algorithm, the structure of the Steenrod algebra, and a cluster of GPUs

Disclaimer

The code in this talk was written with AI coding agents.

They did nothing I couldn't have done in principle. They did it much faster.

  • Production code is whatever makes the run faster.
  • Pull requests are mostly self-contained improvements, backported from it.
  • Everything merged was reviewed by hand: by me, and most of it also by Hood Chatham.

SpectralSequences/sseq · every pull request opened June 1 – October 25, 2026

72 pull requests

GitHub list of all 72 pull requests opened on SpectralSequences/sseq since June 1, 2026, three pages side by side

We want $\operatorname{Ext}_A^{s,t}(\mathbb{F}_2, \mathbb{F}_2)$

The Adams $E_2$ page, from a minimal resolution of $\mathbb{F}_2$ over the Steenrod algebra.

  • One bidegree $(n, s)$ at a time, stem $n = t - s$.
  • Before this: complete data through stem 256 (2023); Lixiong Wu's chart to $t \le 350$.
  • Goal: push as far as possible. Stem 400: all 81,002 bidegrees with $n \le 400$, $1 \le s \le 202$.
s = n / 2 400 0 202 n s 256 Wu: t ≤ 350 (400, 202)

The starting point · 48 CPU cores, 200 GB

Where we started

2.6h
stem 200, $s \le 102$, computing only
61.9h
the same, saving to the parallel filesystem
~780GB
on disk at stem 200, in about 720,000 files
93%
of compute time in Milnor multiplication, at stem 120 (July)

Six months later

2.6h
stem 200 on 48 CPU cores
31.6min
stem 200, every $s$, on one GPU
28h
stems 0–300 on 23 GPUs
100%
of the stem-400 box, finished November 5
April: $S_2$ through $(200, 102)$, 9,356 s, no save. October: stems 0–200, all s, 1,898 s on one H200.

The code, before and after a week at ICERM with Hood Chatham and Francis Baer

45,000 lines of Rust in June, 129,000 in October

Before: one CPU code path, stem 200 (s ≤ 102) in 2.6 hours on 48 cores, 0 GPU jobs. After: GPU row reduction and multiplication, a fleet of 23 GPUs by October, and a scheduler.

Distinct commits per week (rebased duplicates removed). Lines of Rust and CUDA: 45,154 on Jun 23 (master), 129,006 on Oct 11 (production branch).

The cost doubles every 16 stems

Stem 290 costs 72 times stem 200. The census through stem 300 performed 61 trillion Milnor products.

Single-node census. Fit over stems 150–299: +4.5% per stem, doubling every 15.7 stems.

The classical algorithm

  • Bidegree $(n, s)$ waits for $(n-1, s)$ and $(n+1, s-1)$.
  • In internal degree $t = n + s$, row-reduce the matrix of $d_s \colon F_s \to F_{s-1}$ over $\mathbb{F}_2$. Its image should be $\ker d_{s-1}$, known from $(n+1, s-1)$: add a generator of $F_s$ for each vector it misses.
$(n-1, s)$
$(n, s)$
$(n+1, s-1)$
BidegreeMatrixDenseCPU, est.
$(100, 6)$13k × 15k25 MB0.3 s
$(150, 6)$92k × 93k1.1 GB1.4 min
$(200, 9)$417k × 425k22 GB2.2 h
$(300, 10)$4.4M × 4.5M2.5 TB4 months
$(400, 14)$27.6M × 28.1M97 TB74 years

The largest matrix in each stem, at one bit per entry., and one row reduction of it with the fastest CPU code we found.

CPU, est.: single-core M4RI, measured at 30.8 s on a 65,536² matrix, scaled as rows × columns × the smaller of the two.

Christian Nassau's algorithm

Use a finite subalgebra $B \subset A$

  • Take $B$ with profile $(p_1, p_2, \dots)$, such as $A(k)$. The signature of $\mathrm{Sq}(r_1, r_2, \dots)$ is $(r_1 \bmod 2^{p_1}, r_2 \bmod 2^{p_2}, \dots)$. There are $\dim B$ of them.
  • Zero step. Where $\operatorname{Ext}_B$ vanishes, one kernel over the signature-zero part, smaller by a factor of $\dim B$, finds the new generators.
  • Signature step. $\dim B - 1$ small corrections fix the differential.
220×
smaller matrices in the zero step than in the classical algorithm, by area, through stem 120
93%
of the time now goes to Milnor products, at stem 120

The $(n, s)$ plane, stems 0–400 · click a cell

Which subalgebra, where

$A(k)$ switches on along a line of slope $2^{k+1} - 2$ in $(n, s)$: $2, 6, 14, 30$ for $k = 1, \dots, 4$. Remember this picture.

Saving the resolution, with its quasi-inverses

Stem 400 would need over 12 petabytes of disk

Almost all of it is quasi-inverses: our copy of stem 256 held 39,000 bytes of them for every byte of differentials.

Two measurements: stem 200 (0.78 TB, an older format) and stem 256 (22.9 TB, from the README of the 2023 stem-256 dataset on Zenodo). The dashed curve's shape is computed, not fitted: the dense size of every classical matrix, from $\dim A_t$ and the generator counts. One constant matches stem 256; it then predicts 0.98 TB at stem 200.

Stem 200 on 48 CPU cores

Saving cost more than computing

96% of the wall time was writing to disk.

CPU jobs of April: 9,356 s, 42,214 s and 222,666 s.

Recompute, don't store

A quasi-inverse can be rebuilt from the tiny differentials, for about what it cost the first time.

Lift all the maps together, one bidegree at a time: each one is rebuilt once, and shared.

On disk: 23 TB → 0. And 3.8× faster than lifting map by map, on a 516-map test.

Storage: solved ✓ (pull request #275, still open)

GitHub pull request #275 on SpectralSequences/sseq: nassau: recompute quasi-inverses on demand, with bidegree-major batched lifting
PR #275, sseq

Two tracks at once

The usual suspects

  • Row reduction. The classical bottleneck. Nassau's algorithm shrinks the matrices, but they are still the biggest objects we build.
  • Milnor multiplication. The bottleneck since sseq adopted Nassau's algorithm in 2022: 93% of the time at stem 120.

In 2022 I asked Christian Nassau about GPUs, and guessed the gains were “potentially several orders of magnitude”. This year I worked on both at once.

Row reduction is mostly matrix multiplication

  • Row reduction is hard to parallelize. Each pivot waits for the one before, a poor fit for a GPU.
  • Blocked elimination. Reduce a narrow panel, then update the rest with one matrix product. The products are the $O(n^3)$ part.
  • Matrix multiplication is embarrassingly parallel. Every entry of $AB$ is independent.
-- forward pass over F₂, b columns at a time
for each panel of b columns, left to right:
  for each column j of the panel:   -- serial
    pick a row below the pivots with a 1 in j
    none? j is a free column: skip it
    swap it up; for each row r below it with a 1 in j:
      row r ← row r + pivot row;  L[r, j] ← 1
  -- k ≤ b pivots
        ┌ P  X ┐   P: the k pivot rows
        └ 0  Y ┘   Y: everything else

  Y ← Y + L · X                    -- one product
then back-substitute, right to left

Bunch and Hopcroft (1974); Ibarra, Moran and Hui (1982) for any rank: row reduction costs what matrix multiplication costs, up to a constant.

And NVIDIA had its own reason to build a 1-bit matrix multiplier.

Low-precision neural networks

Why NVIDIA built a 1-bit multiplier

Neural networks quantize their weights and inputs to make matrix products cheap, all the way down to one bit.

One bit asMultiplicationInstruction
$\pm 1$XOR, since $(-1)^a (-1)^b = (-1)^{a \oplus b}$xor.popc, from 2018
$0/1$AND: multiplication in $\mathbb{F}_2$and.popc, from 2020; Hopper's wgmma offers only this one

Quantizing to 0/1 is reducing mod 2: our use case exactly.

The instruction, in our kernel's PTX · nvcc -arch=sm_90a

  cvt.u64.u32  %rd32, %r2663;
  or.b64   %rd13, %rd32, 4611686293305360384;
  mad.lo.s32  %r2664, %r3606, 24576, %r2657;
  and.b32   %r2665, %r2664, 262016;
  shr.u32  %r2666, %r2665, 4;
  cvt.u64.u32  %rd33, %r2666;
  or.b64   %rd8, %rd33, 4611686293305360384;
  wgmma.mma_async.sync.aligned.m64n128k256.row.col.s32.b1.b1.and.popc {%r3395,%r3394,%r3393,%r3392,%r3391,%r3390,%r3389,%r3388,%r3387,%r3386,%r3385,%r3384,%r3383,%r3382,%r3381,%r3380,%r3379,%r3378,%r3377,%r3376,%r3375,%r3374,%r3373,%r3372,%r3371,%r3370,%r3369,%r3368,%r3367,%r3366,%r3365,%r3364,%r3363,%r3362,%r3361,%r3360,%r3359,%r3358,%r3357,%r3356,%r3355,%r3354,%r3353,%r3352,%r3351,%r3350,%r3349,%r3348,%r3347,%r3346,%r3345,%r3344,%r3343,%r3342,%r3341,%r3340,%r3339,%r3338,%r3337,%r3336,%r3335,%r3334,%r3333,%r3332},%rd8,%rd13, 1;
  add.s32  %r2667, %r2664, 8192;
  and.b32   %r2668, %r2667, 262016;
  shr.u32  %r2669, %r2668, 4;
  cvt.u64.u32  %rd34, %r2669;
  or.b64   %rd10, %rd34, 4611686293305360384;
wgmma
warpgroup MMA: 128 threads issue it together
mma_async
runs while the threads do other work
sync.aligned
every thread arrives at the same instruction
m64n128k256
a $64 \times 128$ output tile, $256$ bits deep
row.col
operand layouts
s32
32-bit counts
b1.b1
one-bit inputs
and.popc
AND, then count the ones
{%r3395, …}
64 registers per thread: 128 threads hold the $64 \times 128$ tile

One instruction: $2 \cdot 64 \cdot 128 \cdot 256 \approx 4.2$ million bit operations.

fp-cuda on one H200

10,000 trillion bit operations a second

10,031
binary TOPS: $\mathbb{F}_2$ matrix multiply, hand-written for Hopper
kernel time, $65536^3$, mean of 20 launches
14.5×
the best other GPU code, same card, same size
331×
the best CPU code, same size

One bit operation every 100 attoseconds, on average. In that time light travels about 30 nanometres: a hundred-odd silicon atoms.

Built the way two worklogs taught: Simon Boehm, “How to Optimize a CUDA Matmul Kernel for cuBLAS-like Performance” (2022), and Pranjal Shankhdhar, “Outperforming cuBLAS on H100” (2024).

fp-cuda, phase by phase · $65536^3$ on one H200

From 1,600 to 10,000 trillion bit operations a second

Every version rebuilt from its own commit and run on one H200 on Nov 11–12: same harness, same matrices, kernel time from Nsight Compute; all give the same product. Phases 3–7 hit an L2 cliff at large sizes (Phase 7 reaches 5,918 at $16384^3$); Phase 8's tile order removed it. Last row: mean of 20 launches.

July 16–17 · the standalone row-reduction kernel on one H200

One $131{,}072^2$ reduction: 74.9 seconds to 5.2

Half-rank random matrices; log scale. CPU: M4RI's measured 30.8 s at 65,536², scaled as $n^3$. At this size the panels, not the products, take most of the time. The production code path, with its memory checks, takes 7.9 s at this size (next slide).

Row reduction of an $n \times n$ matrix over $\mathbb{F}_2$ · 12 codes, 23 matrices, identical answers wherever each ran

Slower when small, 16 times faster at 65,536

At $2^{20}$, a 137 GB matrix: 12 minutes, against about 35 hours for M4RI if it kept growing like $n^3$: about 180 times faster. Ours grows like $n^{1.9}$ until the card is full.

Half-rank matrices. M4RI best of its three methods; linac, the only other published GPU row reduction over finite fields, runs to 4,096. Ours: the production code path on one H200, Oct 25; $2^{20}$ with its memory admission check bypassed.

Blocked elimination, as the kernel does it

Row reduction, mostly multiplication?

31% panel pivots
29% back-substitution
15% promote
20% multiply
Profile at $2^{16}$, July 17, before a faster back-substitution cut that pass 2.9×.

PR #274 · dense random matrices over $\mathbb{F}_2$, half rank

Row reduction: solved

MatrixSizeOne H200Best CPU code, est.Speedup
$262{,}144 \times 262{,}144$8.6 GB26 s33 min76×
$524{,}288 \times 524{,}288$34 GB2 min4.4 h130×
$1{,}048{,}576 \times 1{,}048{,}576$137 GB12 min35 h177×

In September's production run, row reduction was under 0.5% of the CPU time. ✓

GitHub pull request #274 on SpectralSequences/sseq: fp-cuda: device-resident F2 row reduction (RREF) on Hopper

Where does each term $\mathrm{Sq}(T)$ go in the Milnor basis? · PR #270

Nassau's tables, in sseq at last

  • Nassau. Compute each term's position from a few small tables of dimensions of $\mathbb{F}_2[\xi_1, \dots, \xi_k]$.
  • sseq. A hash map: $O(1)$, and faster than the tables at small degrees.
  • The GPU port. The kernel needed a lookup it computes, not a hash map, so the port used Nassau's tables anyway. PR #270 brought them to the CPU.
GitHub pull request #270 on SpectralSequences/sseq: Add hash-free seqno index tables to the Milnor algebra

At degree 400 the tables look up 2.7× faster, and the index on a stem-400 worker shrinks from 12.4 GB of hash maps to 2.1 GB of tables. Below degree ~300 the hash map wins: it falls out of cache only as the basis grows.

Same algorithm, faster chips

HardwareWorkGPU over CPU
Laptop: RTX 3050 Tistem 1001.2×
One H200, against 65–69 cores of the same nodestems 130–1501.3–1.5×
Two H200s, against 96 cores of the same node (row reduction on the GPU in both)bidegrees done in 4 h, from stem 2919.5×

Call for call, one H200 does the work of 79 server cores. Multiplication: good enough, for now ✓

Rows 1–2: CPU time over GPU time for the same work, on the same machine. Row 3: bidegrees finished in the same 4 hours. 79 cores: one core's time for a recorded multiplication call, over the GPU's (126.2 s against 1.60 s).

September 9–18 · one node: 4 H200, 96 cores and 700 GB allocated

The census: stems 0–300, every bidegree timed

60,802
bidegrees, each logged with its sizes, signatures, generators and run time
77,402
generators of the minimal resolution
61trillion
Milnor products $\mathrm{Sq}(R) \cdot \mathrm{Sq}(S)$
205.5h
wall time, from scratch, on one node
0
disagreements with Wu's chart, over 49,174 bidegrees
226GB
peak memory

The reference for everything after it.

More hardware stopped helping

Bidegrees wait for each other: the dependencies form a directed acyclic graph, a DAG. However many workers we have, a run can't finish faster than its longest chain of dependencies. Measured on one GPU node, stems 0–250:

$T_P \;\ge\; \max\Big(\dfrac{W}{P},\; D\Big)$

time on $P$ workers, for total work $W$ and longest chain $D$. Past $P = W/D \approx 20$, more workers can't help much, whether we have 20 or 20,000.

Two reasons the chain is long: one bidegree per row at a time, and one signature at a time inside a bidegree.

A corner of the chart, schematic · click to replay

Shortening the chain between bidegrees

The picture behind the zero step · $B = A(1)$

$A$, seen from $B$

$A$ is free over $B$: copies of $B$, one for each element of $A/\!/B$. Signature zero gives a basis of $A/\!/B$.

01234567891011121314degreeno jumps in degrees 1–3
$B$
$\mathrm{Sq}(4)\,B$
$\mathrm{Sq}(0,2)\,B$
$\mathrm{Sq}(0,0,1)\,B$
$\mathrm{Sq}(8)\,B$

Moving inside a copy is bookkeeping: the signature step. New generators come from jumping between copies, and the zero step sees only those jumps.

The first jump is $\mathrm{Sq}(4)$: in general $\mathrm{Sq}(2^{k+1})$ for $A(k)$.

PR #295

So a row only waits $\sigma$ back

Generators at $(n-j, s)$ reach $(n, s)$ only through $A/\!/A(k)$ in degree $j$, and that is zero for $0 < j < \sigma = 2^{k+1}$.

So computing $(n, s)$ needs only $(n - \sigma, s)$. Registering stays in order along the row, and it is cheap.

$\sigma = 2, 4, 8, 16, 32$ for $A(0)$ to $A(4)$. Same output.

2.17×
mean concurrency, 12.4 → 26.9
115 → 53h
simulated makespan of the 0–300 census, with unlimited workers
degree012345678910111213141516
$A$1112223445667891112
$A/\!/A(1)$10001011101121222

Inside one bidegree

Why the signatures went one at a time

  • Order the signatures colexicographically, $\sigma_1 < \sigma_2 < \cdots < \sigma_N$. Step $i$ reads the $\sigma_i$-coordinates of one shared vector, the differential $dx$ being fixed, solves for a correction $c_i$, and writes it back: $dx \leftarrow dx + d(c_i)$.
  • $d(c_i)$ has coordinates only in signatures $\sigma_j \ge \sigma_i$. So step $j$ reads what steps $i < j$ wrote: a forward substitution, not $N$ independent solves.
  • Checked at stem 120: 33% of the entries a step reads had been changed by earlier steps (up to 44%), in 86% of bidegrees.

The signatures are a filtration, not a grading: corrections only go up. So which signatures feed which?

Inside one bidegree ·

One signature at a time

Depth $= \sum_i \binom{p_i + 1}{2}$, which is $\binom{n+3}{3}$ for $A(n)$

ProfileSignaturesDepth
$[1,\,1]$4$2^{2}$2
$[2,\,1]$ $= A(1)$8$2^{3}$4
$[2,\,1,\,1]$16$2^{4}$5
$[2,\,2,\,1]$32$2^{5}$7
$[3,\,2,\,1]$ $= A(2)$64$2^{6}$10
$[3,\,2,\,1,\,1]$128$2^{7}$11
$[3,\,2,\,2,\,1]$256$2^{8}$13
$[3,\,3,\,2,\,1]$512$2^{9}$16
$[4,\,3,\,2,\,1]$ $= A(3)$1,024$2^{10}$20
$[5,\,4,\,3,\,2,\,1]$ $= A(4)$32,768$2^{15}$35
$[6,\,5,\,4,\,3,\,2,\,1]$ $= A(5)$2,097,152$2^{21}$56
An observed law: it holds on all 28 profiles with up to 4,096 signatures, and on $A(4)$. For $A(5)$, 56 is its prediction.

Far more work available at once

ChangeCountingBeforeAfter
Same-row relaxationbidegrees in flight: the census, simulated (mean)12.426.9
Signature DAGsignatures runnable at once in one bidegree, weighted by run time at the frontier (Sep 22)1~102

And it grows: $A(n)$ has $2^{(n+1)(n+2)/2}$ signatures, but its DAG is only $\binom{n+3}{3}$ deep. The width is exponential, the depth is a cubic: the further out we go, the more concurrency there is.

The stem-400 run, before running it

Pricing the work in advance

We can't measure a run we haven't done, so we price it: a cost model estimates each signature task's hours on an H200, from the sizes of its matrices. Beyond what we have computed, those sizes follow Burklund and Senger's growth law.

$\dfrac{W}{D} \approx 190$

On average, about 190 tasks could run at once, against 20 at stem 250 before the two DAGs. Workload scales ✓

The same model will decide what runs first in Act IV.

Our biggest node

On one node, stem 400 would take a year

4 × H200
141 GB of device memory each
128
CPU cores (AMD EPYC)
755GB
of memory
~1year
to compute through stem 400 on this node (estimated)
4 vs ~40
GPUs in the node, against the GPUs the work could keep busy: each runs about 5 tasks at once

Need to scale horizontally.

One machine at a time

H200 NVL Hopper · 141 GB HBM3e · 4.8 TB/s · 132 SMs @ 1.79 GHz · PCIe 5 L40S Ada · 48 GB GDDR6 · 864 GB/s · 142 SMs @ 2.52 GHz · PCIe 4 V100 Volta · 16 GB HBM2 · 900 GB/s · 80 SMs @ 1.53 GHz · PCIe 3 A30 Ampere · 24 GB HBM2 · 933 GB/s · 56 SMs @ 1.44 GHz · PCIe 4 Network: private Ethernet, TCP only (L40S nodes on 1 GbE until Oct 13, then 25)  ● measured  ○ nominal  rref server  card running other jobs

From one machine to many

Other machines don't share our memory

our node another node network a task → ← its answer

Another node sees only what we send it.

So we need self-contained, well-defined tasks: pieces of work we can hand over whole.

We have them: the signature steps of Act III, each answered by a correction we add back.

Is the network fast enough? Who hands out the tasks? And in what order?

Who hands out the tasks

One coordinator, many workers, over TCP

  • The coordinator holds the resolution and the bidegree and signature DAGs, sends tasks, and merges the answers by XOR. It never computes.
  • A task is one signature step: a bidegree, a signature, and each generator's $d(x)$ there: a few hundred KB. Its correction comes back: a few tens of MB.
  • A worker keeps its own copy of the resolution: the coordinator sends every new generator to every worker.
  • An rref server is an H200 that only row-reduces. Workers send it their large matrices; cards without Hopper send all of them.

On the same four GPUs this is no faster. It is what lets any GPU on the cluster join.

The fleet, one machine at a time

H200 NVL Hopper · 141 GB HBM3e · 4.8 TB/s · 132 SMs @ 1.79 GHz · PCIe 5 L40S Ada · 48 GB GDDR6 · 864 GB/s · 142 SMs @ 2.52 GHz · PCIe 4 V100 Volta · 16 GB HBM2 · 900 GB/s · 80 SMs @ 1.53 GHz · PCIe 3 A30 Ampere · 24 GB HBM2 · 933 GB/s · 56 SMs @ 1.44 GHz · PCIe 4 Network: private Ethernet, TCP only (L40S nodes on 1 GbE until Oct 13, then 25)  ● measured  ○ nominal  rref server  card running other jobs

In what order: dozens of GPUs, 190 tasks at a time

With dependencies, the order matters

Tasks, 2 units each A B C D They arrive A, B, C, D. D starts after C ends. 8 units on 2 workers: at best 4. 0246 First come, first served worker 1worker 2 A B C D idle: waiting on C 6 Longest chain first worker 1worker 2 C A D B 4

Graham et al.'s notation: machines | constraints | objective

Scheduling a DAG on the fleet

  • The problem. $Q \mid \mathit{prec} \mid C_{\max}$: machines of different speeds, tasks in a DAG, finish as early as possible.
  • Plus memory. A card runs several tasks at once. Card $i$ has memory $M_i$, task $j$ needs $m_j$, and at every moment $\sum_{j \text{ on } i} m_j \le M_i$.
  • The easy case. Identical machines, no memory (Graham, 1966): any schedule that never idles finishes by $W/P + D$, at most twice the lower bound $\max(W/P, D)$.
  • Ours. Speeds differ by card and by kind of task ($R$, not $Q$), and every time is an estimate. A greedy scheduler can hand the critical task to the slowest card.

The scheduler's next version: the run itself used its predecessor

whelm: neither overwhelm nor underwhelm the machines

  • Prices every task's time and memory before it runs: sizes from the mathematics (extrapolated beyond what is computed), constants fitted to the census.
  • Learns each card's speed from the tasks it finishes.
  • Runs the longest remaining chain first, and packs each card up to its memory.
  • One message-driven core: every attempt idempotent, every decision in a replayable log.
1.56×
from scheduling policy alone: the census region, stems 0–300, 1.35 million tasks, simulated
5%
above the lower bound, same simulation

During the run · forecasts from October 3–12

The longest remaining chain

The drops mix real speedups with honest re-pricing; the rise on Oct 9 is honesty.

Knowing when to stop

The costs kept moving under the scheduler, so we froze it: good enough beats optimal against a moving target.

Bob Bruner, a few years ago, paraphrased from memory

If you go by diagonals, you can tell the multinomial is zero as soon as you fill in a value, and the whole rest of the computation just disappears.
I remembered it on October 10, tracing $\mathrm{Sq}(4,2) \cdot \mathrm{Sq}(4,1)$ by hand.

$\mathrm{Sq}(4,2) \cdot \mathrm{Sq}(4,1)$, three ways

The matrix kernel

Build every matrix whose rows fit $R$, in odometer order. Check $S$ and the anti-diagonals once it is complete.

Two-sided

Fill from the bottom row up, within both budgets, skipping digits already used on each anti-diagonal.

Anti-diagonal

Fill the last anti-diagonal first; each finished one fixes two slacks, which are checked at once.

Two multiplication calls recorded from the live run on Oct 4, replayed on one H200

At scale

Changing what is enumerated beat tuning the kernel. Each took one or two days from idea to all 23 GPUs.

Why it suits the GPU

No dead ends, and nothing to store

Matrix kernelTwo-sidedAnti-diagonal
Matrices built that contribute, in degree 4360.006%27%all of them
Device memory68–91 GBunder 2 GBunder 2 GB
State per thread–360 bytes148 bytes
Busy threads per warp of 32–1220

A GPU runs 32 threads in lockstep: a thread that hits a dead end idles until its warp is done. Building only matrices that contribute keeps the warps busy on useful work, and needs no table of matrices in memory.

Frontier products · CPU and GPU

The mathematics helped both chips

EnumerationCPUGPU
Every admissible matrix of $R$, tested against $S$1×1×
Two-sided, Lixiong Wu9–35×8.7–10.1×
Anti-diagonal, PR #3071.6–1.8× more3.7–3.9× more

The GPU kept its lead: one H200 went from ≈ 79 server cores to ≈ 250–300 (estimates). Milnor ✓

CPU two-sided: against our old CPU code at $t \approx 307$. CPU anti-diagonal: 48 threads, against two-sided. GPU: the batches of the previous slide.

The census, again · stems 0–300, $s \le 202$

Same 77,402 generators, from a different machine

One node, Sep 9–18The fleet, Oct 6–7
GPUs4 H2008 H200 + 15 L40S
SoftwareSeptember build+ both DAGs, two-sided
Wall time205.5 h28.3 h
GPU-hours822~650, workers
Tasks–1,369,926, no failures

The same bidegrees as Lixiong Wu's CPU code

From 4× slower to 2.8× faster

October 1: before either new multiplication kernel. Ours on about 21 GPUs; the best of three bidegrees at $t = 348$.

The fleet, again

H200 NVL Hopper · 141 GB HBM3e · 4.8 TB/s · 132 SMs · PCIe 5 L40S Ada · 48 GB GDDR6 · 864 GB/s · 142 SMs · PCIe 4 V100 Volta · 16 GB HBM2 · 900 GB/s · 80 SMs · PCIe 3 A30 Ampere · 24 GB HBM2 · 933 GB/s · 56 SMs · PCIe 4 Network: private Ethernet, TCP only (25 GbE; 10 GbE on the V100 and A30 nodes)  rref server  card running other jobs  ↻ reduces on its own card

L40S (Ada) · A30 (Ampere) · V100 (Volta)

Cards without Hopper's instructions

  • Kernels are compiled at run time, for whatever card the worker lands on.
  • Only Hopper has the warpgroup 1-bit instruction our kernels use: the others send their large row reductions to an H200 over the network.
  • Under 2 GB of device memory is what lets a 16 GB V100 or a 24 GB A30 join at all.
  • Each new kind of card first redoes stems 0–200 and must match bit for bit: 0 mismatches.

November 5, 2026, 19:42 EST

The run is complete

81,002
bidegrees: every $(n, s)$ with $n \le 400$, $1 \le s \le 202$
253,204
generators of the minimal resolution
173,007
of them beyond Wu's $t \le 350$ chart, in 30,603 bidegrees
49
rank at $(376, 24)$, the most in any bidegree
37days
as a distributed run, Sep 29 – Nov 5
~28
GPUs on average, of four kinds, 45 at the peak: ~25,000 GPU-hours
~18quadrillion
Milnor products $\mathrm{Sq}(R) \cdot \mathrm{Sq}(S)$, all runs (estimated)
~12.6MWh
of electricity at the servers since July (estimated): a little over a US household's year

How we know it's right

0
disagreements with Wu's chart, over 50,399 bidegrees
77,402
generators through stem 300, identical in three separate runs
⚑
rank outliers flagged: a bidegree with far more generators than its neighbours is caught at once

The CPU code stays the reference for every new kernel.

One mathematician, a team of agents

~1,380
commits, May 25 – Nov 5
57
upstream pull requests, Jun 22 – Nov 5
257
negative results catalogued, to Oct 12
35,167
GPU-hours, Jul 14 – Nov 5, development included

Written with Claude Code agents. The ideas that changed the outcome came from conversation: the filtration, the same-row relaxation, Bob's diagonals.

The agents built, measured, and retracted, around the clock. A result that looked faster was a suspect until it was measured again.

Every completed bidegree, one dot per bidegree with new generators · faint blue: where $A(k)$ changes

Ext through stem 400

What's next

The next few months

Products, then $d_2$

NextWhere it stands
Products, Lin's method$h_i$ products through $(300, 200)$ in 6.8 s, checked against Wu. General products on the way
$d_2$ through stem 300not known past about stem 200; runs on the census save

Generalizing the algorithm to Margolis's P-algebras

One resolver, four algebras

$p = 2$odd $p$
No exterior part$A$Nassau: on master$P$reduced powers: #293, #305
Nassau: not yet
Exterior part$A^{\mathbb{C}}/\tau$algebra: #293, #305
Nassau: #303
$A$Nassau: #303
  • #293, #305: one Milnor algebra, split by the shape of its basis, not by the prime.
  • #303: Nassau's algorithm over each of them with a vanishing line.
  • $P$ still needs a vanishing line for its subalgebras.
  • Which P-algebras and modules would be most worth computing?

PR #279 · Nassau's untwisted tensor resolutions

Every module, from the sphere's resolution?

  • Resolve $\mathbb{F}_2$ once. Tensor with $M$, and untwist with the antipode.
  • The differential is in closed form, so the tensor complex is never built. Works for infinite modules: the first example is $\mathbb{R}P^\infty$.
  • Stem 400 for every $M$, almost for free?
GitHub pull request #279 on SpectralSequences/sseq, under its earlier title: Ext of infinite modules via Nassau's untwisted tensor resolution

New stems

Stem 512

Stem 400Stem 512
Bidegrees, $1 \le s \le N/2 + 2$81,002132,354
$\dim A_t$ at the far corner2.6 million10.0 million
Largest classical matrix97 TB≈ 3.2 PB, est.
Ext classes253,204≈ 800,000, extrapolated
Largest subalgebra, $s \ge 2$$A(4)$ to $s = 5$$A(4)$ to $s = 9$

$A(5)$ only becomes usable at stem 585, so the signature DAGs stay at most 36 deep. At 512 the obstacle is the volume of work, not the dependencies.

Ext to stem 400

Thank you. Questions?

The Wayne State Gridthe H200, L40S, V100 and A30 nodes, and patience with our jobs
Bob Brunerpioneering high-performance Ext computations, and a remark about diagonals
Hood Chathamstarting sseq and running it for years, and reviewing a lot of pull requests
Dan Isaksenmentorship during my PhD and ever since, and organizing ICERM's Machine Computation in Homotopy Theory Workshop, where this started
Christian Nassauthe algorithm, and advice from 2022 that turned out right
Lixiong Wuthe $t \le 350$ chart we checked against, and the two-sided product
Simon Boehm, Pranjal Shankhdhartheir matrix-multiplication worklogs, which the GPU work followed