Phase 2, week 9retrospective

Vector register budget

Thirty-one usable RVV registers bound the tile once v0 holds a mask, and the limit is soft: you spill before you fail to compile.


Thirty-two registers, minus one, counted in groups

Registers are allocated in groups aligned to LMUL, so count groups rather than registers. With v0 held for masks you have 31 registers: 31 m1 groups, 15 m2, 7 m4, and 3 m8. That last number is the one people get wrong, because the obvious answer is four.

The count for one tile

For an MR by NR f32 tile at a given LMUL, the accumulator alone needs MR * NR / VLMAX groups, where VLMAX = LMUL * VLEN / SEW. Then add the B vector, any epilogue operand the scheduler hoists into the loop, and one temporary for a broadcast or a mask.

A worked example at vlen=256, e32,m2 (VLMAX=16), with a 4 by 16 tile: the accumulators are 4 * 16 / 16 = 4 groups, or 8 registers. The B vector is one group, 2 registers. A bias vector hoisted out of the K loop is one more group. A broadcast temporary is one more. That is 14 of 32, leaving 18 registers, which is enough to double NR to 32 and still fit. The arithmetic is the whole design review; nothing here needed a compiler.

What the budget is not

It is not a source-level count. The allocator cares about live ranges after scheduling, and the epilogue’s operands become live while the accumulators still are. A tile that fits on paper and spills in practice is usually carrying a value kept alive for the masked path, not an extra accumulator.

Verify with llvm-objdump -d on the kernel object and look for vs1r.v, vl1re32.v and vse32.v against stack offsets inside the K loop. A clean inner loop has loads from A and B only.

Things that break

A blocking parameter that fits only if v0 is counted as free: the first masked tail makes the last group disappear and the spill appears, but only on shapes that are not tile multiples, so the benchmark rows you look at first are clean. The second failure is fusion. A bias vector that lives across the K loop because the epilogue was fused costs a group for the whole loop, and a tile that was comfortable before the fusion now spills.

Sources

A version records the documentation the claim was checked against, not a house pin. Confirm flags with --help on the release you installed.

Check yourself

How many register groups does `m8` leave once `v0` is reserved for masks?

Why count live values inside the K loop rather than at the epilogue?

A tile uses 20 registers on tile-multiple shapes and spills only on ragged ones. What is the likely cause?

0 / 3 correct

← back to the map