blob: 5d70f1b1c4ffb4d7d1dba498d2bd884749051111 [file]
// 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 <memory>
#include <utility>
#include "iree/compiler/Dialect/HAL/IR/HALDialect.h"
#include "iree/compiler/Dialect/HAL/IR/HALOps.h"
#include "iree/compiler/Dialect/HAL/Transforms/Passes.h"
#include "iree/compiler/Dialect/Util/IR/UtilDialect.h"
#include "iree/compiler/Dialect/Util/IR/UtilOps.h"
#include "llvm/ADT/STLExtras.h"
#include "mlir/IR/Attributes.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/Pass/Pass.h"
namespace mlir::iree_compiler::IREE::HAL {
#define GEN_PASS_DEF_MATERIALIZETARGETDEVICESPASS
#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
namespace {
//===----------------------------------------------------------------------===//
// --iree-hal-materialize-target-devices
//===----------------------------------------------------------------------===//
// Returns the canonical name for a device by ordinal:
// device ordinal `N` -> `@__device_N`
static FlatSymbolRefAttr makeDefaultDeviceOrdinalRef(MLIRContext *context,
int64_t ordinal) {
return FlatSymbolRefAttr::get(
context, (StringRef("__device_") + std::to_string(ordinal)).str());
}
// Returns the canonical name for a device by name:
// device name `NAME` -> `@NAME`
static FlatSymbolRefAttr makeDefaultDeviceNameRef(MLIRContext *context,
StringRef name) {
return FlatSymbolRefAttr::get(context, name);
}
// Returns a symbol ref constructed to reference the specified device.
// Supports:
// integer attrs: device ordinal `N` -> `@__device_N`
// string attrs: device name `NAME` -> `@NAME`
static FailureOr<FlatSymbolRefAttr>
makeDefaultDeviceAttrRef(Attribute defaultDeviceAttr) {
if (auto stringAttr = dyn_cast<StringAttr>(defaultDeviceAttr)) {
return makeDefaultDeviceNameRef(stringAttr.getContext(), stringAttr);
} else if (auto integerAttr = dyn_cast<IntegerAttr>(defaultDeviceAttr)) {
return makeDefaultDeviceOrdinalRef(integerAttr.getContext(),
integerAttr.getInt());
}
return failure();
}
// Creates a named device global with the given attribute.
static FailureOr<FlatSymbolRefAttr>
createDeviceGlobal(Location loc, StringAttr name, Attribute targetAttr,
OpBuilder &moduleBuilder) {
auto deviceType = moduleBuilder.getType<IREE::HAL::DeviceType>();
auto globalOp = moduleBuilder.create<IREE::Util::GlobalOp>(
loc, name, /*isMutable=*/false, deviceType);
globalOp.setPrivate();
TypedAttr attrValue;
if (auto arrayAttr = dyn_cast<ArrayAttr>(targetAttr)) {
if (arrayAttr.size() == 1) {
auto typedAttr = dyn_cast<TypedAttr>(arrayAttr.getValue().front());
if (typedAttr && isa<IREE::HAL::DeviceType>(typedAttr.getType())) {
// Don't care exactly what the attribute is, only that it's a device.
attrValue = typedAttr;
}
} else {
// Expand arrays to selects.
attrValue = moduleBuilder.getAttr<IREE::HAL::DeviceSelectAttr>(deviceType,
arrayAttr);
}
} else if (auto typedAttr = dyn_cast<TypedAttr>(targetAttr)) {
if (isa<IREE::HAL::DeviceType>(typedAttr.getType())) {
// Don't care exactly what the attribute is, only that it's a device.
attrValue = typedAttr;
}
}
if (!attrValue) {
return mlir::emitError(loc)
<< "module has invalid device targets specified; "
"expected hal.device.targets to be an array of !hal.device "
"initialization attributes or a dictionary with named values";
}
globalOp.setInitialValueAttr(attrValue);
return FlatSymbolRefAttr::get(globalOp);
}
// Creates one or more device globals based on the specified targets and returns
// the "default" device (usually just the first one specified).
static FailureOr<FlatSymbolRefAttr> createDeviceGlobals(mlir::ModuleOp moduleOp,
Attribute targetsAttr) {
auto moduleBuilder = OpBuilder::atBlockBegin(moduleOp.getBody());
FlatSymbolRefAttr firstDeviceRef;
if (auto dictAttr = dyn_cast<DictionaryAttr>(targetsAttr)) {
for (auto namedTargetsAttr : dictAttr.getValue()) {
auto deviceRefOr =
createDeviceGlobal(moduleOp.getLoc(), namedTargetsAttr.getName(),
namedTargetsAttr.getValue(), moduleBuilder);
if (failed(deviceRefOr)) {
return failure();
} else if (!firstDeviceRef) {
firstDeviceRef = *deviceRefOr;
}
}
} else if (auto arrayAttr = dyn_cast<ArrayAttr>(targetsAttr)) {
for (auto [i, ordinalTargetsAttr] : llvm::enumerate(arrayAttr.getValue())) {
auto deviceRefOr =
createDeviceGlobal(moduleOp.getLoc(),
moduleBuilder.getStringAttr(
StringRef("__device_") + std::to_string(i)),
ordinalTargetsAttr, moduleBuilder);
if (failed(deviceRefOr)) {
return failure();
} else if (!firstDeviceRef) {
firstDeviceRef = *deviceRefOr;
}
}
} else {
return moduleOp.emitError()
<< "unexpected `hal.device.targets` attribute; must be a dictionary "
"of named devices or an array of devices to use by ordinal";
}
return firstDeviceRef;
}
// Assigns the default device affinity to all top level ops that don't already
// have one set.
static void assignDefaultDeviceAffinity(mlir::ModuleOp moduleOp,
FlatSymbolRefAttr defaultDeviceRef) {
auto affinityAttr = IREE::HAL::DeviceAffinityAttr::get(
moduleOp.getContext(), defaultDeviceRef, /*queue_mask=*/-1ll);
// Default on the module that applies to any ops that don't otherwise have a
// placement. Ideally we never need this but some programs may take/return no
// tensors or have tensors come from unattributed containers (lists/dicts).
moduleOp->setAttr("stream.affinity.default", affinityAttr);
// Set all arg/results to route through the default device unless they've
// already been assigned.
auto affinityName = StringAttr::get(moduleOp.getContext(), "stream.affinity");
for (auto funcOp : moduleOp.getOps<FunctionOpInterface>()) {
if (funcOp.isPublic()) {
for (auto arg : funcOp.getArguments()) {
if (isa<IREE::Stream::AffinityTypeInterface>(arg.getType())) {
if (!funcOp.getArgAttr(arg.getArgNumber(), affinityName)) {
funcOp.setArgAttr(arg.getArgNumber(), affinityName, affinityAttr);
}
}
}
for (auto result : llvm::enumerate(funcOp.getResultTypes())) {
if (isa<IREE::Stream::AffinityTypeInterface>(result.value())) {
if (!funcOp.getResultAttr(result.index(), affinityName)) {
funcOp.setResultAttr(result.index(), affinityName, affinityAttr);
}
}
}
}
}
}
struct MaterializeTargetDevicesPass
: public IREE::HAL::impl::MaterializeTargetDevicesPassBase<
MaterializeTargetDevicesPass> {
using IREE::HAL::impl::MaterializeTargetDevicesPassBase<
MaterializeTargetDevicesPass>::MaterializeTargetDevicesPassBase;
void runOnOperation() override {
auto moduleOp = getOperation();
// Only materialize devices if there's a module-level attribute specified.
FlatSymbolRefAttr defaultDeviceRef;
auto deviceTargetAttrs = moduleOp->getAttr("hal.device.targets");
if (deviceTargetAttrs) {
moduleOp->removeAttr("hal.device.targets");
// Create the globals and get the default device.
auto firstDeviceOr = createDeviceGlobals(moduleOp, deviceTargetAttrs);
if (failed(firstDeviceOr)) {
// Fails if invalid attributes.
return signalPassFailure();
}
defaultDeviceRef = *firstDeviceOr;
}
// Select the default device from what the user specified or from the first
// created.
auto defaultDeviceAttr = moduleOp->getAttr("hal.device.default");
if (defaultDeviceAttr) {
// Always prefer the explicitly specified default device.
moduleOp->removeAttr("hal.device.default");
auto defaultDeviceRefOr = makeDefaultDeviceAttrRef(defaultDeviceAttr);
if (failed(defaultDeviceRefOr)) {
moduleOp.emitError() << "invalid `hal.device.default` value, must be "
"an ordinal or a name";
return signalPassFailure();
}
defaultDeviceRef = *defaultDeviceRefOr;
} else if (!defaultDevice.empty()) {
// Fallback to the option specified, if any provided.
long long defaultDeviceOrdinal = 0;
if (!llvm::getAsSignedInteger(defaultDevice, 10, defaultDeviceOrdinal)) {
defaultDeviceRef =
makeDefaultDeviceOrdinalRef(&getContext(), defaultDeviceOrdinal);
} else {
defaultDeviceRef =
makeDefaultDeviceNameRef(&getContext(), defaultDevice);
}
}
// Assign affinities to all top level ops that don't already have one set.
if (defaultDeviceRef) {
assignDefaultDeviceAffinity(moduleOp, defaultDeviceRef);
}
}
};
} // namespace
} // namespace mlir::iree_compiler::IREE::HAL