Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Codegen] Add pass to materialize tuning specs #19337

Merged
merged 1 commit into from
Dec 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions compiler/src/iree/compiler/Codegen/Common/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ iree_compiler_cc_library(
"LowerUKernelsToCalls.cpp",
"MaterializeEncodingIntoNop.cpp",
"MaterializeEncodingIntoPackUnPack.cpp",
"MaterializeTuningSpecsPass.cpp",
"MemrefCopyToLinalg.cpp",
"NormalizeLoopBounds.cpp",
"OptimizeTensorInsertExtractSlices.cpp",
Expand Down Expand Up @@ -201,6 +202,7 @@ iree_compiler_cc_library(
"@llvm-project//mlir:BufferizationDialect",
"@llvm-project//mlir:BufferizationInterfaces",
"@llvm-project//mlir:BufferizationTransforms",
"@llvm-project//mlir:BytecodeWriter",
"@llvm-project//mlir:DestinationStyleOpInterface",
"@llvm-project//mlir:DialectUtils",
"@llvm-project//mlir:FuncDialect",
Expand All @@ -219,6 +221,7 @@ iree_compiler_cc_library(
"@llvm-project//mlir:MemRefDialect",
"@llvm-project//mlir:MemRefTransforms",
"@llvm-project//mlir:MemRefUtils",
"@llvm-project//mlir:Parser",
"@llvm-project//mlir:Pass",
"@llvm-project//mlir:SCFDialect",
"@llvm-project//mlir:SCFToControlFlow",
Expand Down Expand Up @@ -284,6 +287,7 @@ iree_compiler_cc_library(
"@llvm-project//mlir:GPUDialect",
"@llvm-project//mlir:LinalgDialect",
"@llvm-project//mlir:LLVMDialect",
"@llvm-project//mlir:Parser",
"@llvm-project//mlir:PDLDialect",
"@llvm-project//mlir:PDLInterpDialect",
"@llvm-project//mlir:SCFDialect",
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/iree/compiler/Codegen/Common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ iree_cc_library(
"LowerUKernelsToCalls.cpp"
"MaterializeEncodingIntoNop.cpp"
"MaterializeEncodingIntoPackUnPack.cpp"
"MaterializeTuningSpecsPass.cpp"
"MemrefCopyToLinalg.cpp"
"NormalizeLoopBounds.cpp"
"OptimizeTensorInsertExtractSlices.cpp"
Expand Down Expand Up @@ -163,6 +164,7 @@ iree_cc_library(
MLIRArithUtils
MLIRBufferizationDialect
MLIRBufferizationTransforms
MLIRBytecodeWriter
MLIRDestinationStyleOpInterface
MLIRFuncDialect
MLIRFuncTransforms
Expand All @@ -180,6 +182,7 @@ iree_cc_library(
MLIRMemRefDialect
MLIRMemRefTransforms
MLIRMemRefUtils
MLIRParser
MLIRPass
MLIRSCFDialect
MLIRSCFToControlFlow
Expand Down Expand Up @@ -257,6 +260,7 @@ iree_cc_library(
MLIRMemRefTransformOps
MLIRPDLDialect
MLIRPDLInterpDialect
MLIRParser
MLIRPass
MLIRRewrite
MLIRSCFDialect
Expand Down
47 changes: 27 additions & 20 deletions compiler/src/iree/compiler/Codegen/Common/LinkTuningSpecsPass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ findNestedModulesWithNamedSequences(ModuleOp module) {
static SmallVector<NamedSequenceOp> findTuningSpecs(ModuleOp module) {
Block *body = module.getBody();
return llvm::filter_to_vector(
body->getOps<NamedSequenceOp>(),
[](NamedSequenceOp op) { return op->hasAttr(kTuningSpecAttrName); });
body->getOps<NamedSequenceOp>(), [](NamedSequenceOp op) {
return op->hasAttr(kTuningSpecEntrypointAttrName);
});
}

static LogicalResult validateTuningSpec(NamedSequenceOp op) {
Expand Down Expand Up @@ -85,7 +86,7 @@ emitLinkedTuningSpec(ModuleOp module, ArrayRef<NamedSequenceOp> specsToLink) {
/*res_attrs*/ ArrayAttr{});
newSpec.setArgAttr(0, transform::TransformDialect::kArgReadOnlyAttrName,
builder.getUnitAttr());
newSpec->setAttr(kTuningSpecAttrName, builder.getUnitAttr());
newSpec->setAttr(kTuningSpecEntrypointAttrName, builder.getUnitAttr());

Region &region = newSpec.getRegion();
Block *body = builder.createBlock(&region, region.begin(),
Expand Down Expand Up @@ -122,28 +123,34 @@ struct LinkTuningSpecsPass final
}

void runOnOperation() override {
ModuleOp module = getOperation();
SmallVector<NamedSequenceOp> tuningSpecs;

for (ModuleOp nested : findNestedModulesWithNamedSequences(module)) {
llvm::append_range(tuningSpecs, findTuningSpecs(nested));
if (failed(linkTuningSpecs(getOperation()))) {
signalPassFailure();
}
}
};

for (NamedSequenceOp spec : tuningSpecs) {
LDBG("Found tuning spec: " << spec.getSymName());
if (failed(validateTuningSpec(spec))) {
return signalPassFailure();
}
}
} // namespace

FailureOr<NamedSequenceOp> linkTuningSpecs(ModuleOp module) {
SmallVector<NamedSequenceOp> tuningSpecs;

if (tuningSpecs.empty()) {
LDBG("No tuning specs found, exiting without linking");
return;
for (ModuleOp nested : findNestedModulesWithNamedSequences(module)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should document more carefully what happens when there are conflicts, i.e some ordering of the spec. From the looks of this the default gets higher precedence than the user defined. I would make it the other way round.

Copy link
Member Author

@kuhar kuhar Dec 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I commented in the pass description that the order in the module is preserved, IE, top down:

def LinkTuningSpecsPass : Pass<"iree-codegen-link-tuning-specs", "ModuleOp"> {
let summary =
"Link nested transform dialect tuning specs named sequences into a single entry point";
let description = [{
Given a module with multiple nested tuning specs, introduce a new named sequence
that includes all the other tuning spec entry points. The order of inclusion is the same
as the in which these nested tuning specs appear in the IR.
A tuning spec entry point is a `transform.named_sequence` op annotated with the
`iree_codegen.tuning_spec` unit attribute. We require it to perform in-place op
modification and not consume the handle.
}];

From the looks of this the default gets higher precedence than the user defined. I would make it the other way round.

Currently we don't have any default specs, so there's nothing to test. But the intention is to have default specs apply at the very end. Once we have that, I'll make sure to add a test that checks the order.

llvm::append_range(tuningSpecs, findTuningSpecs(nested));
}

for (NamedSequenceOp spec : tuningSpecs) {
LDBG("Found tuning spec: " << spec.getSymName());
if (failed(validateTuningSpec(spec))) {
return failure();
}
}

emitLinkedTuningSpec(module, tuningSpecs);
if (tuningSpecs.empty()) {
LDBG("No tuning specs found, exiting without linking");
return NamedSequenceOp{};
}
};

} // namespace
return emitLinkedTuningSpec(module, tuningSpecs);
}

} // namespace mlir::iree_compiler
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// Copyright 2024 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <cassert>
#include "iree/compiler/Codegen/Common/Passes.h"
#include "iree/compiler/Codegen/Dialect/Codegen/IR/IREECodegenAttrs.h"
#include "iree/compiler/Codegen/Dialect/Codegen/IR/IREECodegenDialect.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/ToolOutputFile.h"
#include "llvm/Support/raw_ostream.h"
#include "mlir/Bytecode/BytecodeWriter.h"
#include "mlir/Dialect/Transform/IR/TransformDialect.h"
#include "mlir/Dialect/Transform/IR/TransformOps.h"
#include "mlir/Dialect/Transform/IR/TransformTypes.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/BuiltinTypeInterfaces.h"
#include "mlir/IR/Location.h"
#include "mlir/IR/OwningOpRef.h"
#include "mlir/Support/FileUtilities.h"

#define DEBUG_TYPE "iree-codegen-materialize-tuning-specs"
#define DBGS() (llvm::dbgs() << "[" DEBUG_TYPE "]: ")
#define LDBG(X) LLVM_DEBUG(DBGS() << X << "\n")

namespace mlir::iree_compiler {

#define GEN_PASS_DEF_MATERIALIZETUNINGSPECSPASS
#include "iree/compiler/Codegen/Common/Passes.h.inc"

namespace {

llvm::cl::opt<std::string> clCodegenTuningSpecPath(
"iree-codegen-tuning-spec-path",
llvm::cl::desc("File path to a module containing a tuning spec (transform "
"dialect library)."),
llvm::cl::init(""));
Groverkss marked this conversation as resolved.
Show resolved Hide resolved

llvm::cl::opt<std::string> clCodegenTuningSpecDumpDir(
"iree-codegen-dump-tuning-specs-to",
llvm::cl::desc(
"Dump the final tuning spec modules to the specified directory. When "
"set to '-', prints the tuning spec to stdout."),
llvm::cl::init(""));

using mlir::transform::NamedSequenceOp;

static LogicalResult dumpFinalTuningSpecToDir(ModuleOp tuningSpec,
StringRef dir) {
if (dir == "-") {
tuningSpec->print(llvm::outs());
return success();
}

llvm::sys::fs::create_directories(dir);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should check what happens on Windows. Last time I used llvm::sys::fs it caused issues on Windows.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied this code from the --iree-hal-dump-executable-files-to logic, so I'd think it should work in both places, although I haven't checked on windows. If we learn it doesn't work, I can fix it.

ag fs::create_directories                  
src/iree/compiler/Codegen/Common/MaterializeTuningSpecsPass.cpp
60:  llvm::sys::fs::create_directories(dir);

src/iree/compiler/Dialect/Util/Transforms/DumpModule.cpp
29:    llvm::sys::fs::create_directories(llvm::sys::path::parent_path(path));

src/iree/compiler/Dialect/HAL/Transforms/SerializeExecutables.cpp
72:      llvm::sys::fs::create_directories(dumpIntermediatesPath);
75:      llvm::sys::fs::create_directories(dumpBinariesPath);

src/iree/compiler/Dialect/HAL/Transforms/DumpExecutableSources.cpp
50:      llvm::sys::fs::create_directories(path);

src/iree/compiler/Dialect/HAL/Transforms/DumpExecutableBenchmarks.cpp
514:      llvm::sys::fs::create_directories(path);

llvm::SmallString<64> dumpPath;
auto dumpFileEC = llvm::sys::fs::createUniqueFile(
Twine(dir) + "/iree_tuning_spec_%%.mlir", dumpPath);
if (dumpFileEC) {
return tuningSpec->emitError()
<< "Failed to create a unique file in " << dir << "\n";
}
LDBG("Linked tuning spec file path: " << dumpPath);

std::string error;
auto file = mlir::openOutputFile(dumpPath, &error);
if (!file) {
return tuningSpec->emitError()
<< "Failed to open a tuning spec dump file " << dumpPath << "\n";
}

tuningSpec->print(file->os());
file->keep();
return success();
}

static FailureOr<DenseElementsAttr>
serializeTuningSpecToAttr(ModuleOp tuningSpec) {
std::string buffer;
llvm::raw_string_ostream os(buffer);
if (failed(writeBytecodeToFile(tuningSpec, os))) {
return failure();
}

auto bufferSize = static_cast<int64_t>(buffer.size());
auto bufferShape = VectorType::get(
bufferSize, IntegerType::get(tuningSpec->getContext(), 8));
return DenseElementsAttr::getFromRawBuffer(
bufferShape, ArrayRef(buffer.data(), buffer.data() + bufferSize));
}

struct MaterializeTuningSpecsPass final
: impl::MaterializeTuningSpecsPassBase<MaterializeTuningSpecsPass> {
void getDependentDialects(DialectRegistry &registry) const override {
registerTransformDialectTranslationDependentDialects(registry);
}

void runOnOperation() override {
if (clCodegenTuningSpecPath.empty()) {
return;
}

ModuleOp module = getOperation();
MLIRContext *ctx = &getContext();
auto dialect = ctx->getOrLoadDialect<IREE::Codegen::IREECodegenDialect>();
auto maybeTransformLibrary =
dialect->getOrLoadTransformLibraryModule(clCodegenTuningSpecPath);
if (failed(maybeTransformLibrary)) {
module->emitError()
<< "Failed to load tuning spec transform dialect library from "
<< clCodegenTuningSpecPath;
return signalPassFailure();
}

ModuleOp userTuningSpec = *maybeTransformLibrary;
if (!userTuningSpec.getSymName()) {
// Set a module name so that we can refer to its nested symbols.
userTuningSpec.setSymName("iree_user_tuning_spec");
kuhar marked this conversation as resolved.
Show resolved Hide resolved
}

Location loc = userTuningSpec.getLoc();

// This module will always be released at the end of the pass.
OwningOpRef<ModuleOp> linkedTuningSpec(
ModuleOp::create(loc, "iree_linked_tuning_spec"));
kuhar marked this conversation as resolved.
Show resolved Hide resolved
linkedTuningSpec.get()->setAttr(
transform::TransformDialect::kWithNamedSequenceAttrName,
UnitAttr::get(ctx));
linkedTuningSpec->insert(linkedTuningSpec->begin(), userTuningSpec.clone());

// TODO(https://github.com/iree-org/iree/issues/19214): Add linked tuning
// spec memoization to IREECodegenDialect. We should be able to provide a
// list of input libraries that may have already been linked and ask the
// dialect to return it to us, or invoke a callback that will insert it if
// not found.
FailureOr<transform::NamedSequenceOp> newEntrypoint =
linkTuningSpecs(linkedTuningSpec.get());
if (failed(newEntrypoint)) {
module->emitError("Failed to link tuning specs");
return signalPassFailure();
}

if (!clCodegenTuningSpecDumpDir.empty()) {
if (failed(dumpFinalTuningSpecToDir(linkedTuningSpec.get(),
clCodegenTuningSpecDumpDir))) {
return signalPassFailure();
}
}

FailureOr<DenseElementsAttr> serializedSpec =
serializeTuningSpecToAttr(linkedTuningSpec.get());
if (failed(serializedSpec)) {
module->emitError("Failed to serialize linked tuning specs");
return signalPassFailure();
}
module->setAttr(kSerializedTuningSpecAttrName, *serializedSpec);
}
};

} // namespace
} // namespace mlir::iree_compiler
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <cassert>
#include "iree/compiler/Codegen/Common/Passes.h"
#include "iree/compiler/Codegen/Common/UserConfig.h"
#include "iree/compiler/Codegen/Dialect/Codegen/IR/IREECodegenAttrs.h"
#include "iree/compiler/Codegen/Dialect/Codegen/IR/IREECodegenDialect.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/LogicalResult.h"
#include "mlir/Dialect/Transform/Transforms/TransformInterpreterUtils.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/MLIRContext.h"
#include "mlir/IR/OwningOpRef.h"
#include "mlir/Parser/Parser.h"

#define DEBUG_TYPE "iree-codegen-materialize-user-configs"
#define DBGS() (llvm::dbgs() << "[" DEBUG_TYPE "]: ")
Expand Down Expand Up @@ -110,6 +112,40 @@ getTransformLibraryFromPath(ModuleOp compiledModule, StringRef path) {
entrySequenceName.str()};
}

/// Look up the tuning spec in the given module or any of its parents.
static LogicalResult getModuleTuningSpec(ModuleOp compiledModule,
OwningOpRef<ModuleOp> &tuningSpec) {
IREE::Util::SerializableAttrInterface serializedTuningSpec;
Operation *op = compiledModule;
while (!serializedTuningSpec && op) {
serializedTuningSpec =
op->getAttrOfType<IREE::Util::SerializableAttrInterface>(
kSerializedTuningSpecAttrName);
op = op->getParentOp();
}

if (!serializedTuningSpec) {
return failure();
}

SmallVector<char, 0> bytecode;
if (failed(serializedTuningSpec.serializeToVector(
compiledModule->getLoc(), llvm::endianness::native, bytecode))) {
return compiledModule.emitError()
<< "Failed to read attribute " << kSerializedTuningSpecAttrName;
}

ParserConfig config(compiledModule.getContext());
tuningSpec = parseSourceString<ModuleOp>(
StringRef(bytecode.data(), bytecode.size()), config);
if (!tuningSpec) {
return compiledModule.emitError() << "Failed to parse tuning spec in "
<< kSerializedTuningSpecAttrName;
}
LDBG("--loaded tuning spec");
return success();
}

struct MaterializeUserConfigsPass final
: impl::MaterializeUserConfigsPassBase<MaterializeUserConfigsPass> {
void getDependentDialects(DialectRegistry &registry) const override {
Expand All @@ -119,9 +155,28 @@ struct MaterializeUserConfigsPass final
void runOnOperation() override {
ModuleOp moduleOp = getOperation();

// Try to load the transform library from the user flag first. If none is
// specified, fall back to using the module tuning spec.
FailureOr<TransformLibraryWithEntrypoint> userTransformLibrary =
getTransformLibraryFromPath(moduleOp,
clCodegenTransformDialectLibraryFileName);
OwningOpRef<ModuleOp> tuningSpec;
if (failed(userTransformLibrary)) {
if (succeeded(getModuleTuningSpec(moduleOp, tuningSpec))) {
assert(tuningSpec);
userTransformLibrary = TransformLibraryWithEntrypoint{
tuningSpec.get(), kKernelConfigSpecName.str()};
}
}

// Remove the tuning spec, if any, from the current module. If the tuning
// spec is attached to some other parent op, we conservatively keep it
// as-is, as we are not sure who the producer is and if they want it
// removed.
if (moduleOp->hasAttr(kSerializedTuningSpecAttrName)) {
moduleOp->removeAttr(kSerializedTuningSpecAttrName);
LDBG("--dropped the serialized tuning spec from the module");
}

for (auto funcOp : moduleOp.getOps<FunctionOpInterface>()) {

Expand Down
Loading
Loading