Top-K on a wafer
The Cerebras Kernel Challenge was a three-day window to put an exact Top-K kNN kernel on WSE-2. Start: 9:38 PM EST, April 28. Finish: 8:10 PM EST, May 1.
The job was not approximate search. Every PE owned a shard of D, computed exact squared L2 distances to a query q, and the grid had to agree on the same K winners as a NumPy oracle โ including ties.
The useful result was the cycle count. Most of it came from treating distance as a vector FMAC loop, and from refusing one Top-K method for every K.
Baseline
~160K cycles~25K
K = 256
~851K cycles~318K
The grid
The kernel runs on a P ร P PE rectangle. PE (px, py) owns rows starting at (py * P + px) * rows_per_pe. Padding is a valid_count mask, not a guess.
The host copies D, D_norms, and valid_count to every PE. It copies q only to PE (0,0). The device broadcasts that query across the top row, then down each column.

After compute, each row gathers local top-K west to the row root at px=0, which merges PยทK candidates down to K. Column 0 then gathers those winners north to PE (0,0) for the final top-K.

Distance first
Every case computes exact squared L2. The host folds ||q||ยฒ into the packed row norms, so each PE only has to add the dot-product term.
||Di โ q||ยฒ = ||Di||ยฒ + ||q||ยฒ โ 2 ยท (Di ยท q)
The dominant loop is distance accumulation: one vector @fmacs per feature over local rows. The first FMAC starts from D_norms, so there is no initialization sweep.
When d_dim is divisible by 8, host packing makes the DSD stride 8. I tried 2, 4, 8, and 16. Eight won.
25.1k / (128 ร 32) โ 6.1
One K, several methods
Local selection changes with K because no single method won everywhere.
| K | Local method | Why |
| --- | --- | --- |
| 1 | Track the best row | A linear scan is enough. |
| โค 16 | Sorted insertion | Cheap, and already sorted for the reducer. K = 16 is specialized to cut loop overhead. |
| Full local K | Keep every valid row | For k_large, heap-sort the stream so reducers merge sorted runs. |
| Larger, not full | Max-heap of size K | Avoids shifting up to K entries on every insert. |
Sorted insertion is the right small-K trade. The heap is the right large-K trade. Using one of them for both would have left cycles on the table.
Where the fabric actually hurts
During row gather, each PE sends K distance wavelets and K index wavelets toward px=0. The hot edge is next to the row root: traffic from the other P โ 1 PEs can all cross it.
2 ยท K ยท (P โ 1)
Column gather has the same shape for row winners. Small-K cases stay compute-bound. At K = 256, root-side merge work and candidate movement finally show up.
Ties have to be boring
Each candidate carries a global index, (py ยท P + px) ยท rows_per_pe + local_row. Every stage uses the same comparison: smaller distance wins; if distances are equal, smaller index wins.
What I took away
Three days on WSE-2 was enough to make the same point Twin did, just in cycles instead of milliseconds: the interesting work is rarely a new algorithm. It is making the machine honest about what it is actually paying for.
- Exact is cheaper than it looks if the algebra is honest. Folding
||q||ยฒinto the packed norms removed PE work without making the broadcast heavier. - Layout is a cycle count. Blocking
Dso the FMAC stride is 8 did more for the baseline than any routing trick. - SRAM is the real compiler. Splitting distance and index gathers, and picking a different Top-K structure per K, were both memory decisions wearing an algorithm costume.
- Correctness is a total order. Once every stage ranks by
(distance, global_index), the fabric can be sloppy about when wavelets arrive and still match the oracle.