[LLVMCPU][NFC] Separate the intrinsic MNK shape from its register layout (#24861)
`getIntrinsicMNKShape` states how many M/N/K elements one invocation of
an intrinsic computes, independent of how those elements are laid out in
registers. Selection scores candidates on this shape and the
sibling-pairing validator checks M and N against it, so every intrinsic
declares one, including scalable ones, which declare their base sizes. A
scalable and a non-scalable intrinsic should never compete for the same
matmul, so comparisons stay like-for-like.
The hand-rolled swizzles for intrinsics whose tiles are not row-major
move into `getNonRowMajorIntrinsicSwizzle`.
Assisted-by: Claude Code
Signed-off-by: Ege Beysel <beyselege@gmail.com>
diff --git a/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/IREECPUAttrs.cpp b/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/IREECPUAttrs.cpp
index ed1d263..61ae8a6 100644
--- a/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/IREECPUAttrs.cpp
+++ b/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/IREECPUAttrs.cpp
@@ -432,19 +432,12 @@
//===----------------------------------------------------------------------===//
// Helper function for getIntrinsicSwizzle.
-// Allows succinctly specifying the tiles for the common case of row-major tiles
-// i.e. when the TileSwizzle merely encodes the 2D tile shape and no further
-// expand/transpose.
-// This case is very common for CPU MMA intrinsics due to:
-// 1. CPU matmul instructions having simple tile layouts.
-// 2. The inner_tiled op RHS being transposed, meaning that the row-major RHS
-// tile really is a column-major RHS matmul tile.
-//
-// If you're trying to add support for a new intrinsic that doesn't have a
-// row-major tile layout, don't look at this function, go directly to
-// getIntrinsicSwizzle.
+// Returns how many M/N/K elements one invocation of `intrinsic` computes,
+// independent of how those elements are laid out in registers. Every intrinsic
+// declares its shape here, including scalable ones, which declare their base
+// sizes.
std::optional<std::tuple<int64_t, int64_t, int64_t>>
-getRowMajorTilesMNKShape(MMAIntrinsic intrinsic) {
+getIntrinsicMNKShape(MMAIntrinsic intrinsic) {
using Tuple = std::tuple<int64_t, int64_t, int64_t>;
switch (intrinsic) {
case MMAIntrinsic::None:
@@ -487,6 +480,11 @@
// ACC tile has a non-row-major layout, hand-rolled in `getIntrinsicSwizzle`.
case MMAIntrinsic::MMA_X86_AVX512VNNI_16x16x2_I32_I8_CASTI16:
return Tuple{16, 16, 2};
+ // Base sizes, i.e. the shape at the minimum vector length.
+ case MMAIntrinsic::MMA_ARM_SVE_FMLA_1x4VLx1_F32_F32:
+ return Tuple{1, 4, 1};
+ case MMAIntrinsic::MMA_ARM_SVE_FMLA_4VLx1x1_F32_F32:
+ return Tuple{4, 1, 1};
default:
if (isGenericScalar(intrinsic)) {
return Tuple{1, 1, 1};
@@ -559,16 +557,18 @@
return swizzle;
}
-Codegen::TileSwizzle getIntrinsicSwizzle(IREE::CPU::MMAIntrinsic mma,
- int operandIdx) {
+/// Helper for `getIntrinsicSwizzle`:
+/// Returns the hand-rolled TileSwizzle for the intrinsics whose tiles are not
+/// row-major, or nullopt for every other intrinsic.
+static std::optional<Codegen::TileSwizzle>
+getNonRowMajorIntrinsicSwizzle(IREE::CPU::MMAIntrinsic mma, int operandIdx) {
using TileSwizzle = Codegen::TileSwizzle;
using Dim = TileSwizzle::Dim;
- // SVE has scalable dims that the row-major path below can't express, so we
- // hand-roll the swizzle. The natural orientation is 1×4VL×1: 4VL lives on
- // RHS dim 0 (N) and ACC dim 0 (the convention is `(N, M)` here). The
- // transposed orientation 4VL×1×1 mirrors that: 4VL on LHS dim 0 (M) and
- // ACC dim 0 (now `(M, N)`).
+ // SVE has scalable dims that the row-major path can't express. The natural
+ // orientation is 1×4VL×1: 4VL lives on RHS dim 0 (N) and ACC dim 0 (the
+ // convention is `(N, M)` here). The transposed orientation 4VL×1×1 mirrors
+ // that: 4VL on LHS dim 0 (M) and ACC dim 0 (now `(M, N)`).
if (mma == MMAIntrinsic::MMA_ARM_SVE_FMLA_1x4VLx1_F32_F32 ||
mma == MMAIntrinsic::MMA_ARM_SVE_FMLA_4VLx1x1_F32_F32) {
bool transposed = (mma == MMAIntrinsic::MMA_ARM_SVE_FMLA_4VLx1x1_F32_F32);
@@ -601,11 +601,30 @@
return swizzle;
}
- auto maybeMnkTuple = getRowMajorTilesMNKShape(mma);
+ return std::nullopt;
+}
+
+Codegen::TileSwizzle getIntrinsicSwizzle(IREE::CPU::MMAIntrinsic mma,
+ int operandIdx) {
+ using TileSwizzle = Codegen::TileSwizzle;
+ if (std::optional<TileSwizzle> swizzle =
+ getNonRowMajorIntrinsicSwizzle(mma, operandIdx)) {
+ return *swizzle;
+ }
+
+ // What follows specifies the tiles for the common case of row-major tiles,
+ // i.e. when the TileSwizzle merely encodes the 2D tile shape and no further
+ // expand/transpose. This case is very common for CPU MMA intrinsics due to:
+ // 1. CPU matmul instructions having simple tile layouts.
+ // 2. The inner_tiled op RHS being transposed, meaning that the row-major RHS
+ // tile really is a column-major RHS matmul tile.
+ //
+ // If you're trying to add support for a new intrinsic that doesn't have a
+ // row-major tile layout, its swizzle belongs in
+ // `getNonRowMajorIntrinsicSwizzle`.
+ auto maybeMnkTuple = getIntrinsicMNKShape(mma);
if (!maybeMnkTuple) {
- // Whenever one adds support for a new intrinsic that doesn't have a
- // row-major tile layout, new logic goes here.
- assert(false && "Non-row-major-tile intrinsics not yet implemented.");
+ assert(false && "intrinsic does not declare an MNK shape.");
return TileSwizzle();
}
auto [mSize, nSize, kSize] = *maybeMnkTuple;
diff --git a/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/IREECPUTypes.h b/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/IREECPUTypes.h
index c0a1425..e0a6da3 100644
--- a/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/IREECPUTypes.h
+++ b/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/IREECPUTypes.h
@@ -93,10 +93,10 @@
std::tuple<Type, Type, Type> getABCElementTypes(MLIRContext *ctx,
MMAIntrinsic intrinsic);
-// Returns the (M, N, K) tile shape for a row-major-tile intrinsic, or
-// nullopt otherwise (e.g. SVE).
+// Returns how many M/N/K elements one invocation of `intrinsic` computes.
+// Scalable intrinsics declare their base sizes.
std::optional<std::tuple<int64_t, int64_t, int64_t>>
-getRowMajorTilesMNKShape(MMAIntrinsic intrinsic);
+getIntrinsicMNKShape(MMAIntrinsic intrinsic);
// Idempotently attaches the bitcode for ukernel `name` as
// `hal.executable.objects` on `op`, looking it up first in any
diff --git a/compiler/src/iree/compiler/Codegen/ExternalInterfaces/CPUEncodingExternalModels.cpp b/compiler/src/iree/compiler/Codegen/ExternalInterfaces/CPUEncodingExternalModels.cpp
index 0a5c8abe..e922b33 100644
--- a/compiler/src/iree/compiler/Codegen/ExternalInterfaces/CPUEncodingExternalModels.cpp
+++ b/compiler/src/iree/compiler/Codegen/ExternalInterfaces/CPUEncodingExternalModels.cpp
@@ -454,8 +454,8 @@
// `ui8` LHS visibly swaps with its sibling's `ui8` RHS. Read directly
// from `getABCElementTypes`; `getUndistributedTileTypes` would strip it.
auto getShapeAndTypes = [&](MMAIntrinsic intr) {
- auto mnk = IREE::CPU::getRowMajorTilesMNKShape(intr);
- assert(mnk && "validator only handles row-major-tile intrinsics");
+ auto mnk = IREE::CPU::getIntrinsicMNKShape(intr);
+ assert(mnk && "intrinsic does not declare an MNK shape");
auto [m, n, k] = *mnk;
auto [lhs, rhs, acc] = IREE::CPU::getABCElementTypes(ctx, intr);
return std::make_tuple(m, n, k, lhs, rhs, acc);
@@ -599,7 +599,7 @@
accTy != elementTypes[2]) {
return std::nullopt;
}
- auto mnk = IREE::CPU::getRowMajorTilesMNKShape(intr);
+ auto mnk = IREE::CPU::getIntrinsicMNKShape(intr);
if (!mnk) {
return std::nullopt;
}