Bias fusion
Bias added once after the reduction is bit-identical to the reference; seeding the accumulator with it is legal but changes every partial.
A broadcast is a map, not a copy
The bias is a vector over the output column, so its indexing map drops the row iterator:
#out = affine_map<(m, n) -> (m, n)>
#col = affine_map<(m, n) -> (n)>
#attrs = {indexing_maps = [#out, #col, #out], iterator_types = ["parallel", "parallel"]}
func.func @bias_relu(%acc: memref<32x32xf32>, %bias: memref<32xf32>,
%c: memref<32x32xf32>) {
%zero = arith.constant 0.0 : f32
linalg.generic #attrs ins(%acc, %bias : memref<32x32xf32>, memref<32xf32>)
outs(%c : memref<32x32xf32>) {
^bb0(%a: f32, %b: f32, %o: f32):
%s = arith.addf %a, %b : f32
%r = arith.maximumnumf %s, %zero : f32
linalg.yield %r : f32
}
return
}
The map is what makes the broadcast explicit, and bufferization sees an operand of shape n, not a materialized m by n tensor. In the microkernel the same thing is one vle32 at bias + j with the loop’s vl, followed by vfadd_vv.
Two ways to add a bias
C = A*B + bias as an epilogue add, or C initialised to the broadcast bias so the accumulation starts from it. Both are correct; only the first is bit-identical to an unfused reference. Seeding puts the bias inside every partial sum, so each of the K MACs rounds a different value. If the harness checks bit equality, keep the add after the loop and say so in the test’s name.
What it removes
One read and one write of the output tile: 8 bytes per f32 element, or one full traversal of the output. The bias vector itself is read once per tile column block, so its traffic is smaller by the row-blocking factor and does not show up in the byte count.
Things that break
The bias load uses VLMAX while the store uses vl, so the last strip reads past the bias allocation; the result is usually still right, which is what makes it survive review. The other failure is the map: an operand indexed by m when the output tile was written transposed gives plausible numbers in the wrong places, and only a hand-checked element or a non-square test shape exposes it.
Sources
- MLIR linalg dialect documentationdocs · LLVM 23
- MLIR bufferization documentationdocs · LLVM 23
- TVM: An Automated End-to-End Optimizing Compiler for Deep Learningpaper
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
Which indexing map does a per-column bias use in a matmul epilogue?
Which bias placement is bit-identical to an unfused reference?
The epilogue adds bias and stores the tile. Which `vl` should the bias load use?