Phase 3, week 18retrospective

Vector reduction lowering

Vector reductions such as vfredmax and vfredusum need correct identity values, ordering, and scalable-length handling.


From multi_reduction to vfredmax

%max = vector.multi_reduction maxnumf, %scores, %acc  [1] : vector<4x4xf32> to vector<4xf32>
%sum = vector.multi_reduction add,     %exps,   %acc0 [1] : vector<4x4xf32> to vector<4xf32>

Each lowers to a llvm.vector.reduce.* intrinsic and then to one RVV instruction per row. The accumulator operand is not a courtesy: it becomes the scalar seed in element 0 of vs1, so it has to hold the identity. Negative infinity for the maximum, 0.0 for the sum.

Element zero is the answer

A reduction writes its result into element 0 of the destination and leaves the other elements to the tail policy. So a row wider than VLMAX is several instructions with the running scalar fed back as the next seed. At VLEN=256 an e32,m1 group holds 8 elements, so a 32-element row is four vfred ops and three moves between them.

Read the disassembly for those moves. A loop that reloads the vector each iteration and seeds with zero counts only its last chunk, and it is still right at exactly one VLMAX.

Ordered and unordered are different sums

llvm.vector.reduce.fadd without reassoc must preserve the sequential order, and LLVM keeps a separate opcode for it that selects vfredosum. With reassoc it selects vfredusum, which may be a tree. Same input, different bits, both correct, so any tolerance test has to name which one it compares against.

NaN changes the maximum

vfredmax does not propagate a NaN the way llvm.vector.reduce.fmax does: the intrinsic returns NaN if any lane is NaN, and the instruction does not. Without nnan the backend emits a compare and a vcpop to force the intrinsic’s answer, so the fast kernel is the one where NaNs have been ruled out, not the one that hoped.

Things that break

Seeding a max with 0.0, which turns an all-negative row into zero. Re-seeding every chunk instead of threading the previous result. Assuming a scalable reduction is one instruction at every VLEN, when the type legalizer splits it and the split is where ordering gets decided.

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

What value seeds a `vfredmax` over a row that may be entirely negative?

Where does the result of a vector reduction appear?

Two builds of the same sum differ in the last bits. What explains it?

0 / 3 correct

← back to the map