[Codegen] Lower data-tiled conv to conv_nchwc ukernel (#24730)
When the convolution ukernel is enabled, routes data-tiled convolution
dispatches through `Mmt4dTilingExpert` pipeline, where the 9D NCHWc
generic is recognized and lowered to an `iree_uk_conv_nchwc` microkernel
call.
Assisted-by: Claude code
---------
Signed-off-by: Pooja Hemashekar <hemashekar@roofline.ai>
diff --git a/compiler/src/iree/compiler/Codegen/Common/CPU/BUILD.bazel b/compiler/src/iree/compiler/Codegen/Common/CPU/BUILD.bazel
index 7f40cd7..1ef369d 100644
--- a/compiler/src/iree/compiler/Codegen/Common/CPU/BUILD.bazel
+++ b/compiler/src/iree/compiler/Codegen/Common/CPU/BUILD.bazel
@@ -84,6 +84,7 @@
"@llvm-project//mlir:FunctionInterfaces",
"@llvm-project//mlir:IR",
"@llvm-project//mlir:LinalgDialect",
+ "@llvm-project//mlir:LinalgInterfaces",
"@llvm-project//mlir:LinalgTransforms",
"@llvm-project//mlir:LinalgUtils",
"@llvm-project//mlir:MemRefDialect",
diff --git a/compiler/src/iree/compiler/Codegen/Common/CPU/CMakeLists.txt b/compiler/src/iree/compiler/Codegen/Common/CPU/CMakeLists.txt
index 0a52a37..c588002 100644
--- a/compiler/src/iree/compiler/Codegen/Common/CPU/CMakeLists.txt
+++ b/compiler/src/iree/compiler/Codegen/Common/CPU/CMakeLists.txt
@@ -59,6 +59,7 @@
MLIRFunctionInterfaces
MLIRIR
MLIRLinalgDialect
+ MLIRLinalgInterfacesIncGenLib
MLIRLinalgTransforms
MLIRLinalgUtils
MLIRMemRefDialect
diff --git a/compiler/src/iree/compiler/Codegen/Common/CPU/CPULowerToUKernels.cpp b/compiler/src/iree/compiler/Codegen/Common/CPU/CPULowerToUKernels.cpp
index bfaa28e..fdecb81 100644
--- a/compiler/src/iree/compiler/Codegen/Common/CPU/CPULowerToUKernels.cpp
+++ b/compiler/src/iree/compiler/Codegen/Common/CPU/CPULowerToUKernels.cpp
@@ -11,6 +11,7 @@
#include "iree/compiler/Codegen/Dialect/Codegen/IR/IREECodegenDialect.h"
#include "iree/compiler/Codegen/Dialect/Codegen/IR/IREECodegenOps.h"
#include "iree/compiler/Codegen/Dialect/Codegen/IR/UKernelOps.h"
+#include "iree/compiler/Codegen/Dialect/Codegen/Utils/Utils.h"
#include "iree/compiler/Codegen/Utils/Utils.h"
#include "iree/compiler/Dialect/Encoding/IR/EncodingOps.h"
#include "iree/compiler/Dialect/Encoding/IR/EncodingTypes.h"
@@ -18,6 +19,7 @@
#include "llvm/ADT/Repeated.h"
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/Linalg/IR/Linalg.h"
+#include "mlir/Dialect/Linalg/IR/LinalgInterfaces.h"
#include "mlir/Dialect/Linalg/Utils/Utils.h"
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/IR/Attributes.h"
@@ -267,6 +269,121 @@
genericMicroKernelOp.getOperation());
}
+/// Matches a 9D data-tiled conv generic op and rewrites it to a call to
+/// iree_uk_conv_nchwc, that is later lowered into a call to the
+/// microkernel.
+static FailureOr<IREE::Codegen::UKernelOpInterface>
+matchDAGForUKernel(RewriterBase &rewriter, linalg::GenericOp op,
+ bool /*skipIntermediateRoundings*/) {
+ auto targetAttr = IREE::HAL::ExecutableTargetAttr::lookup(op);
+ const char ukernelName[] = "conv_nchwc";
+ if (!targetAttr || !hasUkernel(targetAttr.getConfiguration(), ukernelName)) {
+ return failure();
+ }
+ if (!IREE::Codegen::isDataTiledConvGeneric(op)) {
+ return rewriter.notifyMatchFailure(op, "not a data-tiled conv generic");
+ }
+ Value input = op.getDpsInputOperand(0)->get();
+ Value filter = op.getDpsInputOperand(1)->get();
+ Value output = op.getDpsInitOperand(0)->get();
+ auto inputType = cast<RankedTensorType>(input.getType());
+ auto filterType = cast<RankedTensorType>(filter.getType());
+ auto outputType = cast<RankedTensorType>(output.getType());
+
+ Type inElem = inputType.getElementType();
+ Type filterElem = filterType.getElementType();
+ Type outElem = outputType.getElementType();
+ uint32_t flags = 0;
+ if (inElem.isF32() && filterElem.isF32() && outElem.isF32()) {
+ flags = IREE_UK_FLAG_CONV_NCHWC_TYPE_F32F32F32;
+ } else {
+ return rewriter.notifyMatchFailure(op,
+ "unsupported conv_nchwc element types");
+ }
+
+ auto cDims = linalg::inferConvolutionDims(op);
+ if (failed(cDims) || cDims->strides.size() != 2 ||
+ cDims->dilations.size() != 2) {
+ return rewriter.notifyMatchFailure(op, "failed to infer conv dims");
+ }
+ // TODO(#24760): plumb dilation_h/w as ukernel operands and honor them
+ // in the ukernels (window expression currently assumes unit dilation).
+ if (cDims->dilations[0] != 1 || cDims->dilations[1] != 1) {
+ return rewriter.notifyMatchFailure(op, "only dilation=1 supported for now");
+ }
+ int64_t strideH = cDims->strides[0];
+ int64_t strideW = cDims->strides[1];
+
+ if (isInitializedToZero(output)) {
+ // Not setting flags |= IREE_UK_FLAG_CONV_NCHWC_ACCUMULATE, so the conv op
+ // won't read the existing accumulator, so its defining op can be discarded.
+ if (auto fillOp = output.getDefiningOp<linalg::FillOp>()) {
+ output = fillOp.getDpsInitOperand(0)->get();
+ }
+ } else {
+ // Tell the conv op to read the existing accumulator.
+ flags |= IREE_UK_FLAG_CONV_NCHWC_ACCUMULATE;
+ }
+
+ flags |= IREE_UK_FLAG_CONV_NCHWC_ALLOW_GENERIC_FALLBACK_TILE_FUNCTION;
+
+ Location loc = op.getLoc();
+
+ auto dimAsIndex = [&](Value v, int64_t d) -> Value {
+ return tensor::DimOp::create(rewriter, loc, v, d);
+ };
+
+ // isDataTiledConvGeneric() guarantees the canonical operand layouts.
+ Value n = dimAsIndex(input, 0);
+ Value icOuter = dimAsIndex(input, 1);
+ Value ocOuter = dimAsIndex(filter, 0);
+ Value fh = dimAsIndex(filter, 2);
+ Value fw = dimAsIndex(filter, 3);
+ Value k0 = arith::IndexCastOp::create(rewriter, loc, rewriter.getI32Type(),
+ dimAsIndex(filter, 5));
+ Value c0 = arith::IndexCastOp::create(rewriter, loc, rewriter.getI32Type(),
+ dimAsIndex(filter, 4));
+ Value oh = dimAsIndex(output, 2);
+ Value ow = dimAsIndex(output, 3);
+
+ Value strideHVal = arith::ConstantOp::create(
+ rewriter, loc, rewriter.getI32IntegerAttr(strideH));
+ Value strideWVal = arith::ConstantOp::create(
+ rewriter, loc, rewriter.getI32IntegerAttr(strideW));
+ Value flagsVal = arith::ConstantOp::create(rewriter, loc,
+ rewriter.getI32IntegerAttr(flags));
+
+ auto fn = getFnNameAndDefAttrs(ukernelName, rewriter, targetAttr);
+ SmallVector<Type> returnTypes =
+ getUKernelGenericReturnTypes(targetAttr, outputType);
+
+ // Ukernel signature order: (N, OC_outer, OH, OW, IC_outer, FH, FW, k0, c0,
+ // stride_h, stride_w, flags).
+ SmallVector<Value> otherOperands{n, ocOuter, oh, ow,
+ icOuter, fh, fw, k0,
+ c0, strideHVal, strideWVal, flagsVal};
+ // Per operand, the strides of the outer dims are passed to the ukernel. These
+ // operands are views of large packed tensors (via tensor.extract_slice), so
+ // each outer dim's stride comes from the parent layout and can't be assumed
+ // from the slice shape. We only block input/output channels, so the inner
+ // block dims (c0, k0) are contiguous and walked with compile-time strides and
+ // they don't require runtime strides; the remaining iterated dims need one.
+ // input [N, IC/c0, H, W, c0]: strides of dims 0, 1, 2 ->
+ // (input_stride_n, input_stride_ic_outer, input_stride_h)
+ // filter [OC/k0, IC/c0, FH, FW, c0, k0]: strides of dims 0, 1, 2 ->
+ // (filter_stride_oc_outer, filter_stride_ic_outer, filter_stride_fh)
+ // output [N, OC/k0, OH, OW, k0]: strides of dims 0, 1, 2 ->
+ // (output_stride_n, output_stride_oc_outer, output_stride_oh)
+ SmallVector<SmallVector<int64_t>> stridedDims = {
+ {0, 1, 2}, {0, 1, 2}, {0, 1, 2}};
+ auto genericMicroKernelOp = IREE::Codegen::UKernelGenericOp::create(
+ rewriter, loc, returnTypes, fn.name, ValueRange{input, filter}, output,
+ otherOperands,
+ /*fn_def_attrs=*/rewriter.getDictionaryAttr(fn.defAttrs), stridedDims);
+ return cast<IREE::Codegen::UKernelOpInterface>(
+ genericMicroKernelOp.getOperation());
+}
+
static FailureOr<IREE::Codegen::UKernelOpInterface>
matchDAGForUKernel(RewriterBase &rewriter, linalg::PackOp op,
bool /*skipIntermediateRoundings*/) {
@@ -653,7 +770,8 @@
auto allTargets = [](auto target) { return true; };
patterns.insert<LowerToUKernelPattern<linalg::Mmt4DOp>,
LowerToUKernelPattern<linalg::PackOp>,
- LowerToUKernelPattern<linalg::UnPackOp>>(
+ LowerToUKernelPattern<linalg::UnPackOp>,
+ LowerToUKernelPattern<linalg::GenericOp>>(
context, allTargets, skipIntermediateRoundings);
// These patterns are inherently specific to the VMVX backend.
patterns.insert<LowerToUKernelPattern<IREE::Codegen::QueryTileSizesOp>>(
diff --git a/compiler/src/iree/compiler/Codegen/Common/CPU/test/lower_to_ukernel_ops.mlir b/compiler/src/iree/compiler/Codegen/Common/CPU/test/lower_to_ukernel_ops.mlir
index a823864..ff1f9bc 100644
--- a/compiler/src/iree/compiler/Codegen/Common/CPU/test/lower_to_ukernel_ops.mlir
+++ b/compiler/src/iree/compiler/Codegen/Common/CPU/test/lower_to_ukernel_ops.mlir
@@ -597,3 +597,146 @@
// CHECK-SAME: ins(%[[ARG0]], %[[ARG1]] :
// CHECK-SAME: outs(%[[ARG2]] :
// CHECK: return %[[MICRO_KERNEL]]#0
+
+// -----
+
+#map = affine_map<(d0, d1, d2, d3, d4, d5, d6, d7, d8) -> (d0, d4, d2 + d5, d3 + d6, d8)>
+#map1 = affine_map<(d0, d1, d2, d3, d4, d5, d6, d7, d8) -> (d1, d4, d5, d6, d8, d7)>
+#map2 = affine_map<(d0, d1, d2, d3, d4, d5, d6, d7, d8) -> (d0, d1, d2, d3, d7)>
+func.func @conv_nchwc_f32f32f32(%input: tensor<1x1x16x16x16xf32>, %filter: tensor<1x1x3x3x16x16xf32>, %output: tensor<1x1x14x14x16xf32>) -> tensor<1x1x14x14x16xf32> attributes {
+ hal.executable.target = #hal.executable.target<"llvm-cpu", "xyz", {ukernels = "all", target_triple = "x86_64-xyz-xyz", cpu_features = "+avx512f"}>
+} {
+ %0 = linalg.generic {indexing_maps = [#map, #map1, #map2], iterator_types = ["parallel", "parallel", "parallel", "parallel", "reduction", "reduction", "reduction", "parallel", "reduction"]} ins(%input, %filter : tensor<1x1x16x16x16xf32>, tensor<1x1x3x3x16x16xf32>) outs(%output : tensor<1x1x14x14x16xf32>) {
+ ^bb0(%in: f32, %in_0: f32, %out: f32):
+ %1 = arith.mulf %in, %in_0 : f32
+ %2 = arith.addf %1, %out : f32
+ linalg.yield %2 : f32
+ } -> tensor<1x1x14x14x16xf32>
+ return %0 : tensor<1x1x14x14x16xf32>
+}
+// CHECK-LABEL: func @conv_nchwc_f32f32f32(
+// CHECK-SAME: %[[INPUT:[a-zA-Z0-9]+]]: tensor<1x1x16x16x16xf32>
+// CHECK-SAME: %[[FILTER:[a-zA-Z0-9]+]]: tensor<1x1x3x3x16x16xf32>
+// CHECK-SAME: %[[OUTPUT:[a-zA-Z0-9]+]]: tensor<1x1x14x14x16xf32>
+// CHECK-DAG: %[[C1:.+]] = arith.constant 1 : index
+// CHECK-DAG: %[[C3:.+]] = arith.constant 3 : index
+// CHECK-DAG: %[[C14:.+]] = arith.constant 14 : index
+// CHECK-DAG: %[[C16_I32:.+]] = arith.constant 16 : i32
+// CHECK-DAG: %[[C1_I32:.+]] = arith.constant 1 : i32
+// Flag constant: 769 = 0x301 = ACCUMULATE (0x100) | ALLOW_GENERIC_FALLBACK_TILE_FUNCTION (0x200) | TYPE_F32F32F32 (0x1)
+// CHECK-DAG: %[[FLAGS:.+]] = arith.constant 769 : i32
+// CHECK: %[[MICRO_KERNEL:.+]]:2 = iree_codegen.ukernel.generic "iree_uk_conv_nchwc"
+// CHECK-SAME: ins(%[[INPUT]], %[[FILTER]] :
+// CHECK-SAME: outs(%[[OUTPUT]] :
+// CHECK-SAME: (%[[C1]], %[[C1]], %[[C14]], %[[C14]], %[[C1]], %[[C3]], %[[C3]], %[[C16_I32]], %[[C16_I32]], %[[C1_I32]], %[[C1_I32]], %[[FLAGS]] :
+// CHECK-SAME: strided_dims({{\[}}[0, 1, 2], [0, 1, 2], [0, 1, 2]])
+// CHECK: return %[[MICRO_KERNEL]]#0
+
+// -----
+
+// Zero-initialized accumulator: the linalg.fill is folded away and the ukernel
+// writes a fresh buffer.
+
+#map = affine_map<(d0, d1, d2, d3, d4, d5, d6, d7, d8) -> (d0, d4, d2 + d5, d3 + d6, d8)>
+#map1 = affine_map<(d0, d1, d2, d3, d4, d5, d6, d7, d8) -> (d1, d4, d5, d6, d8, d7)>
+#map2 = affine_map<(d0, d1, d2, d3, d4, d5, d6, d7, d8) -> (d0, d1, d2, d3, d7)>
+func.func @conv_nchwc_zero_fill(%input: tensor<1x1x16x16x16xf32>, %filter: tensor<1x1x3x3x16x16xf32>) -> tensor<1x1x14x14x16xf32> attributes {
+ hal.executable.target = #hal.executable.target<"llvm-cpu", "xyz", {ukernels = "all", target_triple = "x86_64-xyz-xyz", cpu_features = "+avx512f"}>
+} {
+ %cst = arith.constant 0.000000e+00 : f32
+ %empty = tensor.empty() : tensor<1x1x14x14x16xf32>
+ %fill = linalg.fill ins(%cst : f32) outs(%empty : tensor<1x1x14x14x16xf32>) -> tensor<1x1x14x14x16xf32>
+ %0 = linalg.generic {indexing_maps = [#map, #map1, #map2], iterator_types = ["parallel", "parallel", "parallel", "parallel", "reduction", "reduction", "reduction", "parallel", "reduction"]} ins(%input, %filter : tensor<1x1x16x16x16xf32>, tensor<1x1x3x3x16x16xf32>) outs(%fill : tensor<1x1x14x14x16xf32>) {
+ ^bb0(%in: f32, %in_0: f32, %out: f32):
+ %1 = arith.mulf %in, %in_0 : f32
+ %2 = arith.addf %1, %out : f32
+ linalg.yield %2 : f32
+ } -> tensor<1x1x14x14x16xf32>
+ return %0 : tensor<1x1x14x14x16xf32>
+}
+// CHECK-LABEL: func @conv_nchwc_zero_fill(
+// Flag constant: 513 = 0x201 = ALLOW_GENERIC_FALLBACK_TILE_FUNCTION (0x200) | TYPE_F32F32F32 (0x1); no ACCUMULATE since the fill zeroes the accumulator
+// CHECK-DAG: %[[FLAGS:.+]] = arith.constant 513 : i32
+// CHECK-DAG: %[[EMPTY:.+]] = tensor.empty() : tensor<1x1x14x14x16xf32>
+// CHECK: %[[MICRO_KERNEL:.+]]:2 = iree_codegen.ukernel.generic "iree_uk_conv_nchwc"
+// CHECK-SAME: outs(%[[EMPTY]] :
+// CHECK-SAME: %[[FLAGS]] :
+// CHECK: return %[[MICRO_KERNEL]]#0
+
+// -----
+
+// Dynamic inner tiles (e.g. scalable c0/k0) flow to the ukernel as runtime
+// tensor.dim/index_cast k0/c0 operands instead of baked constants.
+
+#map = affine_map<(d0, d1, d2, d3, d4, d5, d6, d7, d8) -> (d0, d4, d2 + d5, d3 + d6, d8)>
+#map1 = affine_map<(d0, d1, d2, d3, d4, d5, d6, d7, d8) -> (d1, d4, d5, d6, d8, d7)>
+#map2 = affine_map<(d0, d1, d2, d3, d4, d5, d6, d7, d8) -> (d0, d1, d2, d3, d7)>
+func.func @conv_nchwc_dynamic_tiles(%input: tensor<1x1x16x16x?xf32>, %filter: tensor<1x1x3x3x?x?xf32>, %output: tensor<1x1x14x14x?xf32>) -> tensor<1x1x14x14x?xf32> attributes {
+ hal.executable.target = #hal.executable.target<"llvm-cpu", "xyz", {ukernels = "all", target_triple = "x86_64-xyz-xyz", cpu_features = "+avx512f"}>
+} {
+ %0 = linalg.generic {indexing_maps = [#map, #map1, #map2], iterator_types = ["parallel", "parallel", "parallel", "parallel", "reduction", "reduction", "reduction", "parallel", "reduction"]} ins(%input, %filter : tensor<1x1x16x16x?xf32>, tensor<1x1x3x3x?x?xf32>) outs(%output : tensor<1x1x14x14x?xf32>) {
+ ^bb0(%in: f32, %in_0: f32, %out: f32):
+ %1 = arith.mulf %in, %in_0 : f32
+ %2 = arith.addf %1, %out : f32
+ linalg.yield %2 : f32
+ } -> tensor<1x1x14x14x?xf32>
+ return %0 : tensor<1x1x14x14x?xf32>
+}
+// CHECK-LABEL: func @conv_nchwc_dynamic_tiles(
+// CHECK-SAME: %[[INPUT:[a-zA-Z0-9]+]]: tensor<1x1x16x16x?xf32>
+// CHECK-SAME: %[[FILTER:[a-zA-Z0-9]+]]: tensor<1x1x3x3x?x?xf32>
+// CHECK-SAME: %[[OUTPUT:[a-zA-Z0-9]+]]: tensor<1x1x14x14x?xf32>
+// CHECK-DAG: %[[C4:.+]] = arith.constant 4 : index
+// CHECK-DAG: %[[C5:.+]] = arith.constant 5 : index
+// CHECK: %[[K0_DIM:.+]] = tensor.dim %[[FILTER]], %[[C5]]
+// CHECK: %[[K0:.+]] = arith.index_cast %[[K0_DIM]] : index to i32
+// CHECK: %[[C0_DIM:.+]] = tensor.dim %[[FILTER]], %[[C4]]
+// CHECK: %[[C0:.+]] = arith.index_cast %[[C0_DIM]] : index to i32
+// CHECK: iree_codegen.ukernel.generic "iree_uk_conv_nchwc"
+// CHECK-SAME: ins(%[[INPUT]], %[[FILTER]] :
+// CHECK-SAME: outs(%[[OUTPUT]] :
+// CHECK-SAME: %[[K0]], %[[C0]],
+
+// -----
+
+// Without a ukernel target attribute the conv generic is left untouched.
+
+#map = affine_map<(d0, d1, d2, d3, d4, d5, d6, d7, d8) -> (d0, d4, d2 + d5, d3 + d6, d8)>
+#map1 = affine_map<(d0, d1, d2, d3, d4, d5, d6, d7, d8) -> (d1, d4, d5, d6, d8, d7)>
+#map2 = affine_map<(d0, d1, d2, d3, d4, d5, d6, d7, d8) -> (d0, d1, d2, d3, d7)>
+func.func @negative_conv_ukernel(%input: tensor<1x1x16x16x16xf32>, %filter: tensor<1x1x3x3x16x16xf32>, %output: tensor<1x1x14x14x16xf32>) -> tensor<1x1x14x14x16xf32> attributes {
+ hal.executable.target = #hal.executable.target<"llvm-cpu", "xyz", {target_triple = "x86_64-xyz-xyz", cpu_features = "+avx512f"}>
+} {
+ %0 = linalg.generic {indexing_maps = [#map, #map1, #map2], iterator_types = ["parallel", "parallel", "parallel", "parallel", "reduction", "reduction", "reduction", "parallel", "reduction"]} ins(%input, %filter : tensor<1x1x16x16x16xf32>, tensor<1x1x3x3x16x16xf32>) outs(%output : tensor<1x1x14x14x16xf32>) {
+ ^bb0(%in: f32, %in_0: f32, %out: f32):
+ %1 = arith.mulf %in, %in_0 : f32
+ %2 = arith.addf %1, %out : f32
+ linalg.yield %2 : f32
+ } -> tensor<1x1x14x14x16xf32>
+ return %0 : tensor<1x1x14x14x16xf32>
+}
+// CHECK-LABEL: func @negative_conv_ukernel(
+// CHECK-NOT: iree_uk_conv_nchwc
+// CHECK: linalg.generic
+
+// -----
+
+// linalg.generic with non-MAC body is not recognized as convolution and left as is.
+
+#map = affine_map<(d0, d1, d2, d3, d4, d5, d6, d7, d8) -> (d0, d4, d2 + d5, d3 + d6, d8)>
+#map1 = affine_map<(d0, d1, d2, d3, d4, d5, d6, d7, d8) -> (d1, d4, d5, d6, d8, d7)>
+#map2 = affine_map<(d0, d1, d2, d3, d4, d5, d6, d7, d8) -> (d0, d1, d2, d3, d7)>
+func.func @negative_conv_ukernel_non_conv_body(%input: tensor<1x1x16x16x16xf32>, %filter: tensor<1x1x3x3x16x16xf32>, %output: tensor<1x1x14x14x16xf32>) -> tensor<1x1x14x14x16xf32> attributes {
+ hal.executable.target = #hal.executable.target<"llvm-cpu", "xyz", {ukernels = "all", target_triple = "x86_64-xyz-xyz", cpu_features = "+avx512f"}>
+} {
+ %0 = linalg.generic {indexing_maps = [#map, #map1, #map2], iterator_types = ["parallel", "parallel", "parallel", "parallel", "reduction", "reduction", "reduction", "parallel", "reduction"]} ins(%input, %filter : tensor<1x1x16x16x16xf32>, tensor<1x1x3x3x16x16xf32>) outs(%output : tensor<1x1x14x14x16xf32>) {
+ ^bb0(%in: f32, %in_0: f32, %out: f32):
+ %1 = arith.subf %in, %in_0 : f32
+ %2 = arith.addf %1, %out : f32
+ linalg.yield %2 : f32
+ } -> tensor<1x1x14x14x16xf32>
+ return %0 : tensor<1x1x14x14x16xf32>
+}
+// CHECK-LABEL: func @negative_conv_ukernel_non_conv_body(
+// CHECK-NOT: iree_uk_conv_nchwc
+// CHECK: linalg.generic
diff --git a/compiler/src/iree/compiler/Codegen/Dialect/Codegen/IR/UKernelOps.cpp b/compiler/src/iree/compiler/Codegen/Dialect/Codegen/IR/UKernelOps.cpp
index aec4927..d94a724 100644
--- a/compiler/src/iree/compiler/Codegen/Dialect/Codegen/IR/UKernelOps.cpp
+++ b/compiler/src/iree/compiler/Codegen/Dialect/Codegen/IR/UKernelOps.cpp
@@ -224,7 +224,7 @@
});
auto stridedDimsArrayAttr =
ArrayAttr::get(builder.getContext(), stridedDimsAttrs);
- build(builder, result, outputs.getTypes(), uKernelFnName, inputs, outputs,
+ build(builder, result, resultTypes, uKernelFnName, inputs, outputs,
otherOperands, fnDefAttrs, stridedDimsArrayAttr);
}
diff --git a/compiler/src/iree/compiler/Codegen/LLVMCPU/KernelDispatch.cpp b/compiler/src/iree/compiler/Codegen/LLVMCPU/KernelDispatch.cpp
index 59e5407..ad3f892 100644
--- a/compiler/src/iree/compiler/Codegen/LLVMCPU/KernelDispatch.cpp
+++ b/compiler/src/iree/compiler/Codegen/LLVMCPU/KernelDispatch.cpp
@@ -3117,11 +3117,15 @@
getCPUTranslationInfo(op.getContext(), CPUPipeline::Mmt4dTilingExpert));
}
-/// Assigns CPUDoubleTilingExpert pipeline config to the data-tiled
-/// convolution generic.
+/// Assigns lowering config to the data-tiled convolution generic.
static LogicalResult
setConvDataTiledGenericRootConfig(mlir::FunctionOpInterface entryPointFn,
linalg::GenericOp convOp) {
+ IREE::HAL::ExecutableTargetAttr targetAttr =
+ IREE::HAL::ExecutableTargetAttr::lookup(convOp);
+ bool useUkernel =
+ targetAttr && hasUkernel(targetAttr.getConfiguration(), "conv_nchwc");
+
// Loop ranges:
// (d0, d1, d2, d3, d4, d5, d6, d7, d8)
// (n, OC/k0, OH, OW, IC/c0, FH, FW, k0, c0)
@@ -3166,8 +3170,23 @@
IREE::CPU::LoweringConfigAttr loweringConfig =
generator.generateCPULoweringConfig();
- // Enable loop peeling so the OW vectorization tile produces static-sized
- // main loop iterations (vectorizable) with a scalar peeled tail.
+ // When the conv ukernel is enabled we route through `Mmt4dTilingExpert`
+ // rather than a dedicated conv pipeline, because it already runs the modern
+ // lowerings a data-tiled conv dispatch needs: it invokes
+ // `CPULowerToUKernelsPass`, which matches the data-tiled conv
+ // `linalg.generic` and rewrites it to `iree_codegen.ukernel.generic` op,
+ // alongside tile-and-fuse and the generic vectorization passes.
+ // TODO: factor out a data-tiled pipeline that can be shared
+ // between mmt4d/inner_tiled/conv.
+ if (useUkernel) {
+ return setOpConfigAndEntryPointFnTranslation(
+ entryPointFn, convOp, loweringConfig,
+ getCPUTranslationInfo(convOp.getContext(),
+ CPUPipeline::Mmt4dTilingExpert));
+ }
+
+ // Enable loop peeling so the OW tile yields a static-shaped main loop that
+ // vectorizes cleanly, leaving the remainder to a scalar epilogue.
DictionaryAttr pipelineConfig =
getPipelineConfWithPeelingAttr(convOp.getContext());
return setOpConfigAndEntryPointFnTranslation(
diff --git a/compiler/src/iree/compiler/Codegen/LLVMCPU/test/select_x86_64_lowering_strategy.mlir b/compiler/src/iree/compiler/Codegen/LLVMCPU/test/select_x86_64_lowering_strategy.mlir
index 8fc1c54..919d832 100644
--- a/compiler/src/iree/compiler/Codegen/LLVMCPU/test/select_x86_64_lowering_strategy.mlir
+++ b/compiler/src/iree/compiler/Codegen/LLVMCPU/test/select_x86_64_lowering_strategy.mlir
@@ -479,6 +479,28 @@
// -----
+#executable_target_embedded_elf_x86_64_ = #hal.executable.target<"llvm-cpu", "embedded-elf-x86_64", {cpu_features = "+avx512f", data_layout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128", native_vector_size = 16 : index, target_triple = "x86_64-unknown-linux-gnu", ukernels = "conv_nchwc"}>
+#map = affine_map<(d0, d1, d2, d3, d4, d5, d6, d7, d8) -> (d0, d4, d2 + d5, d3 + d6, d8)>
+#map1 = affine_map<(d0, d1, d2, d3, d4, d5, d6, d7, d8) -> (d1, d4, d5, d6, d8, d7)>
+#map2 = affine_map<(d0, d1, d2, d3, d4, d5, d6, d7, d8) -> (d0, d1, d2, d3, d7)>
+func.func @conv_2d_nchwc_data_tiled_ukernel_enable(%arg0: tensor<1x1x16x16x16xf32>, %arg1: tensor<1x1x3x3x16x16xf32>, %arg2: tensor<1x1x14x14x16xf32>) -> tensor<1x1x14x14x16xf32> attributes {hal.executable.target = #executable_target_embedded_elf_x86_64_} {
+ %0 = linalg.generic {indexing_maps = [#map, #map1, #map2], iterator_types = ["parallel", "parallel", "parallel", "parallel", "reduction", "reduction", "reduction", "parallel", "reduction"]} ins(%arg0, %arg1 : tensor<1x1x16x16x16xf32>, tensor<1x1x3x3x16x16xf32>) outs(%arg2 : tensor<1x1x14x14x16xf32>) {
+ ^bb0(%in: f32, %in_0: f32, %out: f32):
+ %1 = arith.mulf %in, %in_0 : f32
+ %2 = arith.addf %1, %out : f32
+ linalg.yield %2 : f32
+ } -> tensor<1x1x14x14x16xf32>
+ return %0 : tensor<1x1x14x14x16xf32>
+}
+// CHECK-DAG: #[[CONFIG:.+]] = #iree_cpu.lowering_config<distribution = [0, 0, 1, 0, 0, 0, 0, 0, 0], vector_common_parallel = [1, 1, 1, 16, 0, 0, 0, 16, 0], vector_reduction = [0, 0, 0, 0, 1, 1, 1, 0, 16]>
+// CHECK-DAG: #[[TRANSLATION:.+]] = #iree_codegen.translation_info<pipeline = #iree_cpu.pipeline<Mmt4dTilingExpert>>
+// CHECK: func.func @conv_2d_nchwc_data_tiled_ukernel_enable(
+// CHECK-SAME: translation_info = #[[TRANSLATION]]
+// CHECK: linalg.generic
+// CHECK-SAME: lowering_config = #[[CONFIG]]
+
+// -----
+
#executable_target_embedded_elf_x86_64_ = #hal.executable.target<"llvm-cpu", "embedded-elf-x86_64", {cpu = "cascadelake", cpu_features = "+mmx,+popcnt,+sse,+sse2,+sse3,+ssse3,+sse4.1,+sse4.2,+avx,+avx2,+fma,+avx512f,+bmi,+bmi2,+aes,+pclmul,+avx512vl,+avx512bw,+avx512dq,+avx512cd,+avx512vnni,+adx,+clflushopt,+clwb,+cx16,+cx8,+crc32,+f16c,+fsgsbase,+fxsr,+invpcid,+lzcnt,+movbe,+pku,+prfchw,+rdrnd,+rdseed,+sahf,+x87,+xsave,+xsavec,+xsaveopt,+xsaves", data_layout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128", native_vector_size = 32 : index, target_triple = "x86_64-none-elf", ukernels = false}>
func.func @pooling_nchw_max(%2: tensor<1x64x114x114xf32>) -> tensor<1x64x56x56xf32> attributes {hal.executable.target = #executable_target_embedded_elf_x86_64_} {
%cst = arith.constant -3.40282347E+38 : f32