End-to-end example for lowering config driver lowering. (#6779)
Changes here allow the CPU compilation to be controlled using lowering
configuration that is specified on the operation. To plumb this through,
the lowering config can be used to specify a pass-pipeline and
workgroup size as well.
diff --git a/iree/compiler/Codegen/LLVMCPU/KernelDispatch.cpp b/iree/compiler/Codegen/LLVMCPU/KernelDispatch.cpp
index 9133eca..0abb16b 100644
--- a/iree/compiler/Codegen/LLVMCPU/KernelDispatch.cpp
+++ b/iree/compiler/Codegen/LLVMCPU/KernelDispatch.cpp
@@ -183,7 +183,6 @@
/// Sets the lowering configuration for dispatch region with root op being a
/// generic op.
static LogicalResult setDefaultRootConfig(FuncOp entryPointFn, Operation *op) {
- if (getLoweringConfig(op)) return success();
auto partitionedLoops = getPartitionedLoops(op);
if (partitionedLoops.empty()) {
// Return success without doing anything. Eventually default will be used.
@@ -213,15 +212,42 @@
for (auto computeOp : computeOps) {
if (!hasMarker(computeOp, getWorkgroupMarker())) continue;
- auto setRootConfigFn = [&](Operation *op) -> LogicalResult {
- return TypeSwitch<Operation *, LogicalResult>(op)
- .Case<linalg::Mmt4DOp, linalg::ContractionOpInterface>(
- [&](auto op) { return setRootConfig(entryPointFn, op); })
- .Default([&](Operation *op) { return success(); });
- };
-
- if (failed(setRootConfigFn(computeOp))) {
- return failure();
+ /// If the op already has a lowering config, then check for whether it
+ /// specifies a pass-pipeline and workgroup size as well. If so use those.
+ if (auto config = getLoweringConfig(computeOp)) {
+ IREE::HAL::DispatchLoweringPassPipeline passPipeline =
+ IREE::HAL::DispatchLoweringPassPipeline::CPUDefault;
+ if (auto passPipelineAttr = config.passPipeline()) {
+ passPipeline = passPipelineAttr.getValue();
+ }
+ SmallVector<int64_t, 4> workgroupSize;
+ if (auto workgroupSizeAttr = config.workgroupSize()) {
+ workgroupSize = llvm::to_vector<4>(
+ llvm::map_range(workgroupSizeAttr, [](Attribute intAttr) {
+ return intAttr.cast<IntegerAttr>().getInt();
+ }));
+ }
+ if (failed(setOpConfigAndEntryPointFnTranslation(
+ entryPointFn, computeOp, config, passPipeline, workgroupSize))) {
+ return failure();
+ }
+ // Reset the op configuration to drop the pass-pipeline and workgroup size
+ // info. The op does not carry that information anymore.
+ auto resetConfig = IREE::HAL::LoweringConfig::get(
+ config.tileSizes(), config.nativeVectorSize(),
+ /*passPipeline =*/nullptr,
+ /*workgroupSize =*/nullptr, computeOp->getContext());
+ setLoweringConfig(computeOp, resetConfig);
+ } else {
+ auto setRootConfigFn = [&](Operation *op) -> LogicalResult {
+ return TypeSwitch<Operation *, LogicalResult>(op)
+ .Case<linalg::Mmt4DOp, linalg::ContractionOpInterface>(
+ [&](auto op) { return setRootConfig(entryPointFn, op); })
+ .Default([&](Operation *op) { return success(); });
+ };
+ if (failed(setRootConfigFn(computeOp))) {
+ return failure();
+ }
}
if (getLoweringConfig(computeOp)) {
diff --git a/iree/compiler/Codegen/LLVMCPU/LLVMCPULowerExecutableTarget.cpp b/iree/compiler/Codegen/LLVMCPU/LLVMCPULowerExecutableTarget.cpp
index 85f47b8..cdb4720 100644
--- a/iree/compiler/Codegen/LLVMCPU/LLVMCPULowerExecutableTarget.cpp
+++ b/iree/compiler/Codegen/LLVMCPU/LLVMCPULowerExecutableTarget.cpp
@@ -150,6 +150,7 @@
executableLoweringPipeline.nest<ModuleOp>();
switch (passPipeline.getValue()) {
case IREE::HAL::DispatchLoweringPassPipeline::CPUDefault:
+ case IREE::HAL::DispatchLoweringPassPipeline::None:
addCPUDefaultPassPipeline(nestedModulePM);
break;
case IREE::HAL::DispatchLoweringPassPipeline::CPUVectorization:
diff --git a/iree/compiler/Codegen/LLVMCPU/test/materialize_launch_configuration.mlir b/iree/compiler/Codegen/LLVMCPU/test/materialize_launch_configuration.mlir
index 9c8a4c1..653f7b1 100644
--- a/iree/compiler/Codegen/LLVMCPU/test/materialize_launch_configuration.mlir
+++ b/iree/compiler/Codegen/LLVMCPU/test/materialize_launch_configuration.mlir
@@ -67,7 +67,11 @@
// -----
-// CHECK-NOT: #config
+// CHECK: #[[CONFIG:.+]] = {passPipeline = 0 : i32}
+// CHECK-NOT: #config
+// CHECK: hal.executable.entry_point @add_no_config
+// CHECK-SAME: translation.info = #[[CONFIG]]
+// CHECK-NOT: #config
hal.executable @add_no_config attributes {sym_visibility = "private"} {
hal.interface @io {
@@ -352,3 +356,80 @@
// CHECK: hal.return %[[D0]], %[[D1]], %[[ARG2]]
// CHECK: linalg.batch_matmul
// CHECK-SAME: lowering.config = #[[CONFIG]]
+
+// -----
+
+hal.executable @preset_config_matmul_tensors attributes {sym_visibility = "private"} {
+ hal.executable.variant @system_elf_x86_64, target = #hal.executable.target<"llvm", "system-elf-x86_64"> {
+ hal.executable.entry_point @preset_config attributes {interface = @io, ordinal = 0 : index}
+ builtin.module {
+ builtin.func @preset_config() {
+ %c0 = constant 0 : index
+ %c512 = constant 512 : index
+ %c128 = constant 128 : index
+ %cst = constant 0.000000e+00 : f32
+ %0 = hal.interface.binding.subspan @io::@s0b0_ro_external[%c0] : !flow.dispatch.tensor<readonly:128x256xf32>
+ %1 = hal.interface.binding.subspan @io::@s0b1_ro_external[%c0] : !flow.dispatch.tensor<readonly:256x512xf32>
+ %2 = hal.interface.binding.subspan @io::@s0b2_xw_external[%c0] : !flow.dispatch.tensor<writeonly:128x512xf32>
+ %workgroup_size_x = hal.interface.workgroup.size[0] : index
+ %workgroup_size_y = hal.interface.workgroup.size[1] : index
+ %workgroup_id_x = hal.interface.workgroup.id[0] : index
+ %workgroup_count_x = hal.interface.workgroup.count[0] : index
+ %workgroup_id_y = hal.interface.workgroup.id[1] : index
+ %workgroup_count_y = hal.interface.workgroup.count[1] : index
+ %3 = affine.apply affine_map<()[s0, s1] -> (s0 * s1)>()[%workgroup_id_y, %workgroup_size_y]
+ %4 = affine.apply affine_map<()[s0, s1] -> (s0 * s1)>()[%workgroup_count_y, %workgroup_size_y]
+ scf.for %arg0 = %3 to %c128 step %4 {
+ %5 = affine.apply affine_map<()[s0, s1] -> (s0 * s1)>()[%workgroup_id_x, %workgroup_size_x]
+ %6 = affine.apply affine_map<()[s0, s1] -> (s0 * s1)>()[%workgroup_count_x, %workgroup_size_x]
+ scf.for %arg1 = %5 to %c512 step %6 {
+ %7 = affine.min affine_map<(d0)[s0] -> (s0, -d0 + 128)>(%arg0)[%workgroup_size_y]
+ %8 = flow.dispatch.tensor.load %0, offsets = [%arg0, 0], sizes = [%7, 256], strides = [1, 1] : !flow.dispatch.tensor<readonly:128x256xf32> -> tensor<?x256xf32>
+ %9 = affine.min affine_map<(d0)[s0] -> (s0, -d0 + 512)>(%arg1)[%workgroup_size_x]
+ %10 = flow.dispatch.tensor.load %1, offsets = [0, %arg1], sizes = [256, %9], strides = [1, 1] : !flow.dispatch.tensor<readonly:256x512xf32> -> tensor<256x?xf32>
+ %11 = affine.min affine_map<(d0)[s0] -> (s0, -d0 + 128)>(%arg0)[%workgroup_size_y]
+ %12 = affine.min affine_map<(d0)[s0] -> (s0, -d0 + 512)>(%arg1)[%workgroup_size_x]
+ %13 = affine.min affine_map<(d0)[s0] -> (-d0 + 128, s0)>(%arg0)[%workgroup_size_y]
+ %14 = affine.min affine_map<(d0)[s0] -> (-d0 + 512, s0)>(%arg1)[%workgroup_size_x]
+ %15 = linalg.init_tensor [%13, %14] : tensor<?x?xf32>
+ %16 = linalg.fill(%cst, %15) : f32, tensor<?x?xf32> -> tensor<?x?xf32>
+ %17 = linalg.matmul {__internal_linalg_transform__ = "workgroup", lowering.config = {passPipeline = 1 : i32, tileSizes = [[32, 32, 32]]}} ins(%8, %10 : tensor<?x256xf32>, tensor<256x?xf32>) outs(%16 : tensor<?x?xf32>) -> tensor<?x?xf32>
+ flow.dispatch.tensor.store %17, %2, offsets = [%arg0, %arg1], sizes = [%11, %12], strides = [1, 1] : tensor<?x?xf32> -> !flow.dispatch.tensor<writeonly:128x512xf32>
+ }
+ }
+ return
+ }
+ hal.interface @io attributes {sym_visibility = "private"} {
+ hal.interface.binding @s0b0_ro_external, set=0, binding=0, type="StorageBuffer", access="Read"
+ hal.interface.binding @s0b1_ro_external, set=0, binding=1, type="StorageBuffer", access="Read"
+ hal.interface.binding @s0b2_xw_external, set=0, binding=2, type="StorageBuffer", access="Write|Discard"
+ }
+ }
+ }
+}
+// CHECK-DAG: #[[CONFIG:.+]] = {nativeVectorSize = [], tileSizes = {{\[}}[32, 32, 32]{{\]}}}
+// CHECK-DAG: #[[MAP0:.+]] = affine_map<()[s0] -> (s0 ceildiv 32)>
+// CHECK-DAG: #[[MAP1:.+]] = affine_map<()[s0] -> (s0 * 32)>
+// CHECK: hal.executable.entry_point
+// CHECK-SAME: translation.info = {passPipeline = 1 : i32, workloadPerWorkgroup = [32, 32]}
+// CHECK-NEXT: ^bb0(%[[ARG0:[a-zA-Z0-9]+]]: index, %[[ARG1:[a-zA-Z0-9]+]]: index
+// CHECK-DAG: %[[C1:.+]] = constant 1 : index
+// CHECK-DAG: %[[NWG_X:.+]] = affine.apply #[[MAP0]]()[%[[ARG0]]]
+// CHECK-DAG: %[[NWG_Y:.+]] = affine.apply #[[MAP0]]()[%[[ARG1]]]
+// CHECK: return %[[NWG_X]], %[[NWG_Y]], %[[C1]]
+// CHECK: builtin.module
+// CHECK: builtin.func @preset_config
+// CHECK-DAG: %[[WGID_X:.+]] = hal.interface.workgroup.id[0]
+// CHECK-DAG: %[[WGCOUNT_X:.+]] = hal.interface.workgroup.count[0]
+// CHECK-DAG: %[[WGID_Y:.+]] = hal.interface.workgroup.id[1]
+// CHECK-DAG: %[[WGCOUNT_Y:.+]] = hal.interface.workgroup.count[1]
+// CHECK: %[[LB_Y:.+]] = affine.apply #[[MAP1]]()[%[[WGID_Y]]]
+// CHECK: %[[STEP_Y:.+]] = affine.apply #[[MAP1]]()[%[[WGCOUNT_Y]]]
+// CHECK: scf.for %[[IV0:.+]] = %[[LB_Y]] to %{{.+}} step %[[STEP_Y]]
+// CHECK: %[[LB_X:.+]] = affine.apply #[[MAP1]]()[%[[WGID_X]]]
+// CHECK: %[[STEP_X:.+]] = affine.apply #[[MAP1]]()[%[[WGCOUNT_X]]]
+// CHECK: scf.for %[[IV1:.+]] = %[[LB_X]] to %{{.+}} step %[[STEP_X]]
+// CHECK: linalg.matmul
+// CHECK-SAME: lowering.config = #[[CONFIG]]
+// CHECK-SAME: ins(%{{.+}}, %{{.+}} : tensor<32x256xf32>, tensor<256x32xf32>)
+// CHECK-SAME: outs(%{{.+}} : tensor<32x32xf32>)
diff --git a/iree/compiler/Codegen/LLVMGPU/test/gpu_set_num_workgroups.mlir b/iree/compiler/Codegen/LLVMGPU/test/gpu_set_num_workgroups.mlir
index c5d0979..7034e05 100644
--- a/iree/compiler/Codegen/LLVMGPU/test/gpu_set_num_workgroups.mlir
+++ b/iree/compiler/Codegen/LLVMGPU/test/gpu_set_num_workgroups.mlir
@@ -143,14 +143,15 @@
}
}
-// CHECK-DAG: #[[CONFIG:.+]] = {tileSizes = {{\[}}[]{{\]}}}
+// CHECK-DAG: #[[CONFIG0:.+]] = {passPipeline = 2 : i32}
+// CHECK-DAG: #[[CONFIG1:.+]] = {tileSizes = {{\[}}[]{{\]}}}
// CHECK: hal.executable.entry_point @predict_dispatch_153
-// CHECK-SAME: passPipeline = 2 : i32
+// CHECK-SAME: translation.info = #[[CONFIG0]]
// CHECK-SAME: workgroup_size = [1 : index, 1 : index, 1 : index]
// CHECK-NEXT: ^bb0(%[[ARG0:[a-zA-Z0-9]+]]: index,
// CHECK-DAG: %[[C1:.+]] = constant 1 : index
// CHECK: hal.return %[[C1]], %[[C1]], %[[C1]]
// CHECK: linalg.fill
-// CHECK-SAME: lowering.config = #[[CONFIG]]
+// CHECK-SAME: lowering.config = #[[CONFIG1]]
// CHECK: linalg.generic
-// CHECK-SAME: lowering.config = #[[CONFIG]]
+// CHECK-SAME: lowering.config = #[[CONFIG1]]
diff --git a/iree/compiler/Codegen/Utils/Utils.cpp b/iree/compiler/Codegen/Utils/Utils.cpp
index 28a28b2..18df72f 100644
--- a/iree/compiler/Codegen/Utils/Utils.cpp
+++ b/iree/compiler/Codegen/Utils/Utils.cpp
@@ -81,31 +81,27 @@
}
LogicalResult setOpConfigAndEntryPointFnTranslation(
- FuncOp entryPointFn, Operation *op, TileSizesListTypeRef tileSizes,
- ArrayRef<int64_t> nativeVectorSizes,
+ FuncOp entryPointFn, Operation *op, IREE::HAL::LoweringConfig config,
IREE::HAL::DispatchLoweringPassPipeline passPipeline,
ArrayRef<int64_t> workgroupSize) {
- IREE::HAL::LoweringConfig config =
- buildConfigAttr(tileSizes, nativeVectorSizes, op->getContext());
- setLoweringConfig(op, config);
auto partitionedLoops = getPartitionedLoops(op);
SmallVector<int64_t, 3> workloadPerWorkgroup;
- if (!tileSizes.empty() && !tileSizes[0].empty() &&
- !partitionedLoops.empty()) {
+ auto tileSizes = getTileSizes(config, 0);
+ if (!tileSizes.empty() && !partitionedLoops.empty()) {
for (unsigned depth : partitionedLoops) {
- if (depth >= tileSizes[0].size()) {
+ if (depth >= tileSizes.size()) {
return op->emitOpError(
"illegal configuration for lowering op, expect first level "
"tile size to contain at least ")
<< partitionedLoops.back() << " elements";
}
- if (tileSizes[0][depth] == 0) {
+ if (tileSizes[depth] == 0) {
return op->emitOpError("illegal to set tilesize of loop ")
<< depth
<< " to zero since it is set to be partitioned at the flow "
"level";
}
- workloadPerWorkgroup.push_back(tileSizes[0][depth]);
+ workloadPerWorkgroup.push_back(tileSizes[depth]);
}
if (!workloadPerWorkgroup.empty()) {
workloadPerWorkgroup =
diff --git a/iree/compiler/Codegen/Utils/Utils.h b/iree/compiler/Codegen/Utils/Utils.h
index 6d195dd..cc7376a 100644
--- a/iree/compiler/Codegen/Utils/Utils.h
+++ b/iree/compiler/Codegen/Utils/Utils.h
@@ -43,20 +43,22 @@
/// formation to tile and distribute the ops.
SmallVector<unsigned> getPartitionedLoops(Operation *op);
-// /// Usually the tile sizes for the first level of tiling decides the
-// workgroup
-// /// size for the dispatch on the CPU backend. This is a general helper that
-// /// converts tile sizes of the first level into workgroup sizes.
-// SmallVector<int64_t, 3> getWorkloadPerWorkgroup(
-// ArrayRef<int64_t> firstLevelTileSizes, ArrayRef<int64_t>
-// partitionedLoops);
-
/// Sets translation for the entry-point function based on op configuration.
LogicalResult setOpConfigAndEntryPointFnTranslation(
- FuncOp entryPointFn, Operation *op, TileSizesListTypeRef tileSizes,
- ArrayRef<int64_t> nativeVectorSizes,
+ FuncOp entryPointFn, Operation *op, IREE::HAL::LoweringConfig config,
IREE::HAL::DispatchLoweringPassPipeline passPipeline,
ArrayRef<int64_t> workgroupSize = {});
+inline LogicalResult setOpConfigAndEntryPointFnTranslation(
+ FuncOp entryPointFn, Operation *op, TileSizesListTypeRef tileSizes,
+ ArrayRef<int64_t> nativeVectorSize,
+ IREE::HAL::DispatchLoweringPassPipeline passPipeline,
+ ArrayRef<int64_t> workgroupSize = {}) {
+ IREE::HAL::LoweringConfig config =
+ buildConfigAttr(tileSizes, nativeVectorSize, op->getContext());
+ setLoweringConfig(op, config);
+ return setOpConfigAndEntryPointFnTranslation(entryPointFn, op, config,
+ passPipeline, workgroupSize);
+}
/// Returns the number of outer parallel loops of a linalgOp.
/// Note: To be used only if needed. Use the `getPartitionedLoops` method if
diff --git a/iree/compiler/Dialect/HAL/IR/LoweringConfig.cpp b/iree/compiler/Dialect/HAL/IR/LoweringConfig.cpp
index 1c82197..1e1c2b5 100644
--- a/iree/compiler/Dialect/HAL/IR/LoweringConfig.cpp
+++ b/iree/compiler/Dialect/HAL/IR/LoweringConfig.cpp
@@ -109,7 +109,8 @@
nativeVectorSizeAttr = builder.getI64ArrayAttr(nativeVectorSize);
}
return IREE::HAL::LoweringConfig::get(tileSizesAttr, nativeVectorSizeAttr,
- context);
+ /*passPipeline = */ nullptr,
+ /*workgroupSize = */ nullptr, context);
}
TileSizesListType getTileSizes(IREE::HAL::LoweringConfig config) {
diff --git a/iree/compiler/Dialect/HAL/IR/LoweringConfig.td b/iree/compiler/Dialect/HAL/IR/LoweringConfig.td
index 519c754..f067014 100644
--- a/iree/compiler/Dialect/HAL/IR/LoweringConfig.td
+++ b/iree/compiler/Dialect/HAL/IR/LoweringConfig.td
@@ -26,6 +26,8 @@
: I32EnumAttrCase<"SPIRVVectorize", 6>;
def SPIRV_DistributeToGlobalID
: I32EnumAttrCase<"SPIRVDistributeToGlobalID", 7>;
+def None
+ : I32EnumAttrCase<"None", 0x7FFFFFFF>;
// EnumAttrCase for all known lowerings for ops within dispatch region
// to scalar/native-vector code.
@@ -33,8 +35,8 @@
"DispatchLoweringPassPipeline",
"identifier for pass pipeline use to lower dispatch region",
[CPU_Default, CPU_Vectorization, LLVMGPU_SimpleDistribute,
- LLVMGPU_Vectorize, LLVMGPU_MatmulSimt, SPIRV_SimpleDistribute, SPIRV_Vectorize,
- SPIRV_DistributeToGlobalID]> {
+ LLVMGPU_Vectorize, LLVMGPU_MatmulSimt, SPIRV_SimpleDistribute,
+ SPIRV_Vectorize, SPIRV_DistributeToGlobalID, None]> {
let cppNamespace = "::mlir::iree_compiler::IREE::HAL";
}
@@ -58,6 +60,12 @@
DefaultValuedAttr<TileSizesListAttr, "{}">>,
StructFieldAttr<"nativeVectorSize",
DefaultValuedAttr<I64ArrayAttr, "{}">>,
+ StructFieldAttr<"passPipeline",
+ DefaultValuedAttr<
+ DispatchLoweringPassPipelineEnum,
+ "IREE::HAL::DispatchLoweringPassPipeline::None">>,
+ StructFieldAttr<"workgroupSize",
+ DefaultValuedAttr<I64ArrayAttr, "{}">>
]>;
#endif // IREE_COMPILER_DIALECT_HAL_IR_LOWERINGCONFIG
diff --git a/iree/test/e2e/regression/BUILD b/iree/test/e2e/regression/BUILD
index 0946c8e..b95db69 100644
--- a/iree/test/e2e/regression/BUILD
+++ b/iree/test/e2e/regression/BUILD
@@ -50,6 +50,7 @@
"dynamic_linalg_matmul_on_tensors_fuse_1.mlir",
"dynamic_linalg_matmul_on_tensors_fuse_2.mlir",
"linalg_ext_ops.mlir",
+ "lowering_config.mlir",
] + BACKEND_TESTS,
),
data = [
@@ -65,6 +66,7 @@
name = "check_regression_linalg_ops_dylib-llvm-aot",
srcs = [
"linalg_ext_ops.mlir",
+ "lowering_config.mlir",
] + BACKEND_TESTS,
compiler_flags = ["-iree-input-type=mhlo"],
driver = "dylib",
diff --git a/iree/test/e2e/regression/CMakeLists.txt b/iree/test/e2e/regression/CMakeLists.txt
index 3e6bc37..38b2886 100644
--- a/iree/test/e2e/regression/CMakeLists.txt
+++ b/iree/test/e2e/regression/CMakeLists.txt
@@ -41,6 +41,7 @@
SRCS
"linalg_ext_ops.mlir"
"linalg_ops.mlir"
+ "lowering_config.mlir"
TARGET_BACKEND
"dylib-llvm-aot"
DRIVER
diff --git a/iree/test/e2e/regression/lowering_config.mlir b/iree/test/e2e/regression/lowering_config.mlir
new file mode 100644
index 0000000..a70c491
--- /dev/null
+++ b/iree/test/e2e/regression/lowering_config.mlir
@@ -0,0 +1,12 @@
+#config1 = {tileSizes = [[32, 32, 32]], passPipeline = 1 : i32}
+#config2 = {tileSizes = [[64, 64, 64]], passPipeline = 1 : i32}
+func @lowering_config_test() {
+ %a = util.unfoldable_constant dense<1.0> : tensor<128x256xf32>
+ %b = util.unfoldable_constant dense<2.0> : tensor<256x512xf32>
+ %c = util.unfoldable_constant dense<2.0> : tensor<256x1024xf32>
+ %d = "mhlo.dot"(%a, %b) {lowering.config = #config1} : (tensor<128x256xf32>, tensor<256x512xf32>) -> tensor<128x512xf32>
+ %e = "mhlo.dot"(%a, %c) {lowering.config = #config2} : (tensor<128x256xf32>, tensor<256x1024xf32>) -> tensor<128x1024xf32>
+ check.expect_almost_eq_const(%d, dense<512.0> : tensor<128x512xf32>) : tensor<128x512xf32>
+ check.expect_almost_eq_const(%e, dense<512.0> : tensor<128x1024xf32>) : tensor<128x1024xf32>
+ return
+}