When lowering KernelBench level1/10_3D_tensor_matrix_multiplication.py and level1/11_4D_tensor_matrix_multiplication.py I get very different lowering styles.
Both kernels implement a similar einsum:
- 3D:
Bmk,kn -> Bmn
- 4D:
Bbmk,kn -> Bbmn
Reproducer
Clone Lighthouse
- Init uv as in README
- Run:
$ uv run examples/KernelBench/test-kernel-bench.py --kernel level1/10_ --print-original-module
- Run:
$ uv run examples/KernelBench/test-kernel-bench.py --kernel level1/11_ --print-original-module
- Compare the IRs
3D matmul
The 3D lowering materializes a broadcast of the B tensor to Bkn and then applies a batch_matmul:
// Broadcast B into an empty
%bcast = linalg.generic {...} ins(%arg1 : tensor<2048x768xf32>) outs(%empty : tensor<16x2048x768xf32>) {
^bb0(%in: f32, %out: f32):
linalg.yield %in : f32
} -> tensor<16x2048x768xf32>
// Batch matmul
%mm = linalg.batch_matmul ins(%arg0, %bcast : tensor<16x1024x2048xf32>, tensor<16x2048x768xf32>) outs(%5 : tensor<16x1024x768xf32>) -> tensor<16x1024x768xf32>
While this is correct, I now have to elide the broadcast and convert the whole pattern to a contraction with broadcast maps. This could be trivially lowered to linalg.contract with #map2 being a "broadcast-B" map:
linalg.contract { #map1, #map2, #map3 } ins(%arg0, %arg1 : tensor<16x1024x2048xf32>, tensor<2048x768xf32>) outs(%5 : tensor<16x1024x768xf32>) -> tensor<16x1024x768xf32>
4D matmul
The 4D lowering is even weirder. It collapses the 4D A shape to a 2D (as [0,1,2][3]) and then expands the shape of both B and the collapsed A to add a unit dimension, and then call a batch_matmul on that.
// Shape fiddling
%col_a = tensor.collapse_shape %0 [[0, 1, 2], [3]] : tensor<8x256x512x256xf32> into tensor<1048576x256xf32>
%ex_a = tensor.expand_shape %collapsed [[0, 1], [2]] output_shape [1, 1048576, 256] : tensor<1048576x256xf32> into tensor<1x1048576x256xf32>
%ex_b = tensor.expand_shape %1 [[0, 1], [2]] output_shape [1, 256, 768] : tensor<256x768xf32> into tensor<1x256x768xf32>
// Batch matmul
linalg.batch_matmul ins(%ex_a, %ex_b : tensor<1x1048576x256xf32>, tensor<1x256x768xf32>) outs(%empty : tensor<1x1048576x768xf32>) -> tensor<1x1048576x768xf32>
Again, this would be a trivial linalg.contract pattern, with #map2 being a "broadcast-B" map:
linalg.contract { #map1, #map2, map3 } ins(%ex_a, %ex_b : tensor<8x256x512x256xf32>, tensor<256x768xf32>) outs(%empty : tensor<8x256x512x768xf32>) -> tensor<8x256x512x768xf32>
Linalg Contract
I understand this pattern has been created well before linalg.contract existed, so it may not be trivial or immediate to convert this lowering pattern. But at least have a common solution to ND (N>2) lowering to either collapse/expand shapes OR materialize broadcasts, not both.
The more patterns we have for this, the harder is the compiler's job to deal with different situations.
When lowering KernelBench
level1/10_3D_tensor_matrix_multiplication.pyandlevel1/11_4D_tensor_matrix_multiplication.pyI get very different lowering styles.Both kernels implement a similar einsum:
Bmk,kn -> BmnBbmk,kn -> BbmnReproducer
Clone Lighthouse
$ uv run examples/KernelBench/test-kernel-bench.py --kernel level1/10_ --print-original-module$ uv run examples/KernelBench/test-kernel-bench.py --kernel level1/11_ --print-original-module3D matmul
The 3D lowering materializes a
broadcastof the B tensor toBknand then applies abatch_matmul:While this is correct, I now have to elide the broadcast and convert the whole pattern to a contraction with broadcast maps. This could be trivially lowered to
linalg.contractwith#map2being a "broadcast-B" map:4D matmul
The 4D lowering is even weirder. It collapses the 4D
Ashape to a 2D (as[0,1,2][3]) and then expands the shape of bothBand the collapsedAto add a unit dimension, and then call abatch_matmulon that.Again, this would be a trivial
linalg.contractpattern, with#map2being a "broadcast-B" map:Linalg Contract
I understand this pattern has been created well before
linalg.contractexisted, so it may not be trivial or immediate to convert this lowering pattern. But at least have a common solution to ND (N>2) lowering to either collapse/expand shapes OR materialize broadcasts, not both.The more patterns we have for this, the harder is the compiler's job to deal with different situations.