A new Beginning
Validate Operations / validate-operations (push) Has been cancelled

This commit is contained in:
ilgeco
2026-07-07 18:28:37 +02:00
parent 8338caf3f3
commit 1f4f58de1c
25 changed files with 1187 additions and 13533 deletions
+3 -1
View File
@@ -17,7 +17,7 @@ std::fstream openDialectDumpFileWithExtension(const std::string& name, llvm::Str
return std::fstream(dialectsDir + "/" + name + "." + extension.str(), std::ios::out);
}
void dumpModule(mlir::ModuleOp moduleOp, const std::string& name) {
void dumpModule(mlir::ModuleOp moduleOp, const std::string& name, bool assumeVerified) {
std::fstream file = openDialectDumpFileWithExtension(name, "/dialects", "mlir");
if (!file.is_open())
return;
@@ -25,6 +25,8 @@ void dumpModule(mlir::ModuleOp moduleOp, const std::string& name) {
llvm::raw_os_ostream os(file);
mlir::OpPrintingFlags flags;
flags.elideLargeElementsAttrs().enableDebugInfo(true, false);
if (assumeVerified)
flags.assumeVerified();
moduleOp.print(os, flags);
os.flush();
file.close();
+1 -1
View File
@@ -10,7 +10,7 @@ namespace onnx_mlir {
/// Emits a MLIR snapshot under the current compiler output
/// directory for pass-level debugging.
void dumpModule(mlir::ModuleOp moduleOp, const std::string& name);
void dumpModule(mlir::ModuleOp moduleOp, const std::string& name, bool assumeVerified = false);
/// Opens a file under the same dialect dump directory used by dumpModule.
std::fstream openDialectDumpFileWithExtension(const std::string& name,llvm::StringRef destination = "/dialects", llvm::StringRef extension = "mlir");
@@ -18,7 +18,6 @@
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/RowStripLayoutUtils.hpp"
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/PlanLowering.hpp"
#include "src/Accelerators/PIM/Dialect/Spatial/Transforms/MergeComputeNodes/SpatialDataflowCsvExporter.hpp"
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
#include "src/Accelerators/PIM/Pass/PIMPasses.h"
#include "src/Dialect/ONNX/ONNXOps.hpp"
@@ -380,12 +379,6 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
signalPassFailure();
} else {
dumpModule(moduleOp, "spatial1_graph");
spatial::SpatialDataflowExportStage exportMode = spatial::getSpatialDataflowExportStage();
if (spatial::shouldExportSpatialDataflowStage(exportMode, spatial::SpatialDataflowExportStage::Pre)
&& failed(spatial::exportSpatialDataflowCsvPre(funcOp))) {
signalPassFailure();
return;
}
}
if (!verifyLogicalPhase("at the end of LowerSpatialPlans"))
@@ -56,13 +56,18 @@ bool isLegalExternalCapture(Value value, Region& region) {
return definingOp && definingOp->hasTrait<OpTrait::ConstantLike>();
}
bool isRecordedDeferredCommunicationSource(Operation* op, Value value) {
auto transfer = dyn_cast<spatial::SpatDeferredCommunicationOp>(op);
return transfer && llvm::is_contained(transfer.getSources(), value);
}
template <typename ComputeOpTy>
void verifyComputeBodyCaptures(ComputeOpTy compute, StringRef kind, pim::CappedDiagnosticReporter& diagnostics) {
Region& body = compute.getBody();
body.walk([&](Operation* nestedOp) {
for (OpOperand& operand : nestedOp->getOpOperands()) {
Value value = operand.get();
if (isLegalExternalCapture(value, body))
if (isLegalExternalCapture(value, body) || isRecordedDeferredCommunicationSource(nestedOp, value))
continue;
Operation* definingOp = value.getDefiningOp();
@@ -90,21 +95,29 @@ bool isLegalHostBackedValue(Value value) {
return definingOp->getDialect()->getNamespace() != "spat";
}
bool isScheduledPhase1Value(Value value) {
Operation* definingOp = value.getDefiningOp();
return isa_and_nonnull<spatial::SpatScheduledCompute, spatial::SpatScheduledComputeBatch>(definingOp);
}
template <typename ComputeOpTy>
void verifyScheduledInputs(ComputeOpTy compute,
bool allowChannelReceiveInputs,
StringRef kind,
pim::CappedDiagnosticReporter& diagnostics) {
for (auto [inputIndex, input] : llvm::enumerate(compute.getInputs())) {
size_t currentInputIndex = inputIndex;
Operation* definingOp = input.getDefiningOp();
if (allowChannelReceiveInputs && isa_and_nonnull<spatial::SpatChannelReceiveOp>(definingOp))
continue;
if (isScheduledPhase1Value(input))
continue;
if (isLegalHostBackedValue(input))
continue;
diagnostics.report(compute.getOperation(), [&](Operation* illegalOp) {
InFlightDiagnostic diag = illegalOp->emitOpError()
<< kPhaseMarker << " " << kind << " input #" << inputIndex
<< kPhaseMarker << " " << kind << " input #" << currentInputIndex
<< (allowChannelReceiveInputs ? " must come from the host or explicit spat.channel_receive"
: " must come from the host");
if (definingOp)
@@ -163,9 +176,9 @@ void verifyLogicalTopLevelOps(func::FuncOp funcOp, pim::CappedDiagnosticReporter
void verifyScheduledTopLevelOps(func::FuncOp funcOp, pim::CappedDiagnosticReporter& diagnostics) {
for (Operation& op : funcOp.getOps()) {
if (isa<spatial::SpatGraphCompute, spatial::SpatGraphComputeBatch>(&op)) {
if (isa<spatial::SpatChannelSendOp, spatial::SpatChannelReceiveOp>(&op)) {
diagnostics.report(&op, [&](Operation* illegalOp) {
illegalOp->emitOpError() << kPhaseMarker << " graph Spatial compute op remained after merge materialization";
illegalOp->emitOpError() << kPhaseMarker << " real channel communication is not allowed in scheduled phase 1";
});
}
}
+1 -6
View File
@@ -7,14 +7,9 @@ add_pim_library(SpatialOps
SpatialOpsVerify.cpp
SpatialOpsCanonicalization.cpp
${PIM_SRC_ROOT}/Conversion/ONNXToSpatial/CompileTime.cpp
Transforms/MergeComputeNodes/MergeComputeNodesPass.cpp
Transforms/MergeComputeNodes/CommunicationPlan.cpp
Transforms/MergeComputeNodes/HostOutputFinalization.cpp
Transforms/MergeComputeNodes/MaterializeMergeSchedule.cpp
Transforms/MergeComputeNodes/ProjectedFragments.cpp
Transforms/MergeComputeNodes/SpatialDataflowCsvExporter.cpp
Transforms/MergeComputeNodes/Scheduling/ComputeGraph.cpp
Transforms/MergeComputeNodes/Scheduling/ComputeInstanceUtils.cpp
Transforms/MergeComputeNodes/Scheduling/MergeComputeNodesPass.cpp
Transforms/MergeComputeNodes/Scheduling/MergeSchedulingAnalysis.cpp
Transforms/MergeComputeNodes/Scheduling/PeftScheduler.cpp
+53 -4
View File
@@ -6,6 +6,7 @@ include "mlir/IR/OpAsmInterface.td"
include "mlir/IR/BuiltinTypes.td"
include "mlir/IR/AttrTypeBase.td"
include "mlir/IR/RegionKindInterface.td"
include "mlir/Interfaces/ControlFlowInterfaces.td"
include "mlir/Interfaces/ParallelCombiningOpInterface.td"
include "mlir/Interfaces/SideEffectInterfaces.td"
@@ -27,7 +28,7 @@ def SpatTensor :
//===----------------------------------------------------------------------===//
class SpatComputeLikeBase<string mnemonic> : SpatOp<mnemonic,
[SingleBlock, AttrSizedOperandSegments,
[AttrSizedOperandSegments,
DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmBlockArgumentNames"]>]> {
let summary = "Compute region with attached constant weights";
@@ -40,7 +41,7 @@ class SpatComputeLikeBase<string mnemonic> : SpatOp<mnemonic,
Variadic<SpatTensor>:$outputs
);
let regions = (region SizedRegion<1>:$body);
let regions = (region MinSizedRegion<1>:$body);
let hasVerifier = 1;
let hasFolder = 1;
@@ -76,7 +77,7 @@ def SpatScheduledCompute : SpatComputeLikeBase<"scheduled_compute"> {
}
class SpatComputeBatchLikeBase<string mnemonic> : SpatOp<mnemonic,
[SingleBlock, AttrSizedOperandSegments,
[AttrSizedOperandSegments,
DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmBlockArgumentNames"]>]> {
let summary = "Tensor-native batch of equivalent compute lanes with shared weights and packed inputs";
@@ -90,7 +91,7 @@ class SpatComputeBatchLikeBase<string mnemonic> : SpatOp<mnemonic,
Variadic<SpatTensor>:$outputs
);
let regions = (region SizedRegion<1>:$body);
let regions = (region MinSizedRegion<1>:$body);
let hasVerifier = 1;
let hasCustomAssemblyFormat = 1;
@@ -161,6 +162,54 @@ def SpatYieldOp : SpatOp<"yield", [Terminator]> {
let hasCustomAssemblyFormat = 1;
}
def SpatBlockYieldOp : SpatOp<"block_yield", [
Terminator,
DeclareOpInterfaceMethods<BranchOpInterface, ["getSuccessorForOperands"]>
]> {
let summary = "Terminate a scheduled structural compute block";
let arguments = (ins
Variadic<AnyType>:$outputs
);
let successors = (successor
VariadicSuccessor<AnySuccessor>:$next
);
let hasVerifier = 1;
let hasCustomAssemblyFormat = 1;
}
def SpatDeferredCommunicationOp : SpatOp<"deferred_communication", [SingleBlock]> {
let summary = "Temporary scheduled communication placeholder";
let arguments = (ins
Variadic<SpatTensor>:$sources,
StrAttr:$exchangeId,
StrAttr:$logicalProducer,
StrAttr:$logicalConsumer,
I64Attr:$sourceClass,
I64Attr:$targetClass,
I64Attr:$sourceCore,
I64Attr:$targetCore,
I64Attr:$sourceLane,
I64Attr:$targetLane,
StrAttr:$transferKind,
I64Attr:$resultIndex,
DenseI64ArrayAttr:$projectedTransfer,
I64Attr:$hostOutputOwner
);
let results = (outs
SpatTensor:$output
);
let regions = (region SizedRegion<1>:$body);
let hasVerifier = 1;
let hasCustomAssemblyFormat = 1;
}
def SpatExtractRowsOp : SpatOp<"extract_rows", []> {
let summary = "Extract every row of a rank-2 tensor as separate rank-2 row tensors";
+9
View File
@@ -238,6 +238,15 @@ void SpatScheduledCompute::getAsmBlockArgumentNames(Region& region, OpAsmSetValu
setComputeAsmBlockArgumentNames(*this, region, setNameFn);
}
SuccessorOperands SpatBlockYieldOp::getSuccessorOperands(unsigned index) {
assert(index == 0 && "invalid successor index");
return SuccessorOperands(getOutputsMutable());
}
Block* SpatBlockYieldOp::getSuccessorForOperands(ArrayRef<Attribute>) {
return getOperation()->getNumSuccessors() == 0 ? nullptr : getOperation()->getSuccessor(0);
}
std::optional<BlockArgument> SpatGraphComputeBatch::getLaneArgument() { return getBlockArgument(getBody(), 0); }
std::optional<BlockArgument> SpatGraphComputeBatch::getWeightArgument(unsigned idx) {
return getBlockArgument(getBody(), 1 + idx);
+149 -3
View File
@@ -50,6 +50,64 @@ static void printBlockArgumentList(OpAsmPrinter& printer, ArrayRef<BlockArgument
printer << ")";
}
static void printBlockHeaderWithoutArgLocs(OpAsmPrinter& printer, Block& block) {
printer.printSuccessor(&block);
if (block.getNumArguments() == 0) {
printer << ":";
return;
}
printer << "(";
for (auto [index, argument] : llvm::enumerate(block.getArguments())) {
if (index != 0)
printer << ", ";
printer.printOperand(argument);
printer << ": ";
printer.printType(argument.getType());
}
printer << "):";
}
static void printRegionWithoutBlockArgLocs(OpAsmPrinter& printer,
Region& region,
bool printEntryBlockArgs = true,
bool printBlockTerminators = true,
bool printEmptyBlock = false,
bool printEntryBlockHeaderWhenMultiblock = false) {
printer << " {";
if (region.empty()) {
if (printEmptyBlock)
printer << "\n";
printer << "}";
return;
}
bool hasMultipleBlocks = std::next(region.begin()) != region.end();
for (auto [blockIndex, block] : llvm::enumerate(region)) {
bool printBlockHeader = blockIndex != 0 || printEntryBlockArgs
|| (blockIndex == 0 && printEntryBlockHeaderWhenMultiblock && hasMultipleBlocks)
|| (printEmptyBlock && block.empty());
unsigned indent = printBlockHeader ? 4u : 2u;
if (printBlockHeader) {
printer.getStream() << "\n";
printer.getStream().indent(2);
printBlockHeaderWithoutArgLocs(printer, block);
}
for (Operation& nestedOp : block) {
if (!printBlockTerminators && nestedOp.hasTrait<OpTrait::IsTerminator>())
continue;
printer.getStream() << "\n";
printer.getStream().indent(indent);
printer.printCustomOrGenericOp(&nestedOp);
}
}
printer.getStream() << "\n";
printer << "}";
}
static ParseResult parseBlockArgumentList(OpAsmParser& parser, SmallVectorImpl<OpAsmParser::Argument>& arguments) {
if (parser.parseLParen())
return failure();
@@ -160,7 +218,9 @@ void printComputeLikeOp(ComputeOpTy op, OpAsmPrinter& printer) {
printer << " -> ";
printCompressedTypeSequence(printer, op.getResultTypes());
printer << " ";
printer.printRegion(op.getBody(), /*printEntryBlockArgs=*/false);
printRegionWithoutBlockArgLocs(
printer, op.getBody(), /*printEntryBlockArgs=*/false, /*printBlockTerminators=*/true,
/*printEmptyBlock=*/false, /*printEntryBlockHeaderWhenMultiblock=*/true);
}
template <typename ComputeOpTy>
@@ -290,7 +350,9 @@ void printComputeBatchLikeOp(ComputeBatchOpTy op, OpAsmPrinter& printer) {
printer << " -> ";
printCompressedTypeSequence(printer, op.getResultTypes());
printer << " ";
printer.printRegion(op.getBody(), /*printEntryBlockArgs=*/false);
printRegionWithoutBlockArgLocs(
printer, op.getBody(), /*printEntryBlockArgs=*/false, /*printBlockTerminators=*/true,
/*printEmptyBlock=*/false, /*printEntryBlockHeaderWhenMultiblock=*/true);
}
template <typename ComputeBatchOpTy>
@@ -407,6 +469,89 @@ ParseResult SpatYieldOp::parse(OpAsmParser& parser, OperationState& result) {
return parser.resolveOperands(outputs, outputTypes, parser.getCurrentLocation(), result.operands);
}
void SpatBlockYieldOp::print(OpAsmPrinter& printer) {
printer << " ";
printCompressedValueSequence(printer, getOutputs());
if (getOperation()->getNumSuccessors() != 0) {
printer << " next ";
printer.printSuccessor(getOperation()->getSuccessor(0));
}
printer.printOptionalAttrDict((*this)->getAttrs());
printer << " : ";
printCompressedTypeSequence(printer, getOutputs().getTypes());
}
ParseResult SpatBlockYieldOp::parse(OpAsmParser& parser, OperationState& result) {
SmallVector<OpAsmParser::UnresolvedOperand> outputs;
SmallVector<Type> outputTypes;
Block* successor = nullptr;
OpAsmParser::UnresolvedOperand firstOutput;
OptionalParseResult firstOutputResult = parser.parseOptionalOperand(firstOutput);
if (firstOutputResult.has_value()) {
if (failed(*firstOutputResult))
return failure();
if (parseCompressedOperandEntryWithFirst(parser, firstOutput, outputs))
return failure();
while (succeeded(parser.parseOptionalComma()))
if (parseOneCompressedOperandEntry(parser, outputs))
return failure();
}
if (succeeded(parser.parseOptionalKeyword("next")) && parser.parseSuccessor(successor))
return failure();
if (parser.parseOptionalAttrDict(result.attributes) || parser.parseColon()
|| parseCompressedTypeSequence(parser, outputTypes, /*allowEmpty=*/true))
return failure();
if (outputs.size() != outputTypes.size())
return parser.emitError(parser.getCurrentLocation(), "number of outputs and output types must match");
if (parser.resolveOperands(outputs, outputTypes, parser.getCurrentLocation(), result.operands))
return failure();
if (successor)
result.addSuccessors(successor);
return success();
}
void SpatDeferredCommunicationOp::print(OpAsmPrinter& printer) {
printer << " ";
printCompressedValueSequence(printer, getSources());
printer.printOptionalAttrDict((*this)->getAttrs());
printer << " : ";
printer.printFunctionalType(getSources().getTypes(), getOperation()->getResultTypes());
printer << " ";
printRegionWithoutBlockArgLocs(printer, getBody(), /*printEntryBlockArgs=*/false);
}
ParseResult SpatDeferredCommunicationOp::parse(OpAsmParser& parser, OperationState& result) {
SmallVector<OpAsmParser::UnresolvedOperand> sources;
Type functionTypeStorage;
if (parseCompressedOperandSequence(parser, sources) || parser.parseOptionalAttrDict(result.attributes)
|| parser.parseColon() || parser.parseType(functionTypeStorage))
return failure();
auto functionType = dyn_cast<FunctionType>(functionTypeStorage);
if (!functionType)
return parser.emitError(parser.getCurrentLocation(), "expected deferred communication function type");
if (sources.size() != functionType.getNumInputs())
return parser.emitError(parser.getCurrentLocation(), "number of sources and source types must match");
if (parser.resolveOperands(sources, functionType.getInputs(), parser.getCurrentLocation(), result.operands))
return failure();
result.addTypes(functionType.getResults());
Region* body = result.addRegion();
SmallVector<OpAsmParser::Argument> bodyArgs;
for (Type type : functionType.getInputs()) {
OpAsmParser::Argument argument;
argument.type = type;
bodyArgs.push_back(argument);
}
return parser.parseRegion(*body, bodyArgs);
}
void SpatExtractRowsOp::print(OpAsmPrinter& printer) {
printer << " ";
printer.printOperand(getInput());
@@ -618,7 +763,8 @@ ParseResult SpatScheduledComputeBatch::parse(OpAsmParser& parser, OperationState
void SpatInParallelOp::print(OpAsmPrinter& printer) {
printer << " ";
printer.printRegion(getRegion(), /*printEntryBlockArgs=*/false, /*printBlockTerminators=*/false);
printRegionWithoutBlockArgLocs(
printer, getRegion(), /*printEntryBlockArgs=*/false, /*printBlockTerminators=*/false);
printer.printOptionalAttrDict((*this)->getAttrs());
}
+117 -46
View File
@@ -192,12 +192,18 @@ static bool isConstantExternalValue(Value value) {
return definingOp && definingOp->hasTrait<OpTrait::ConstantLike>();
}
static bool isRecordedDeferredCommunicationSource(Operation* op, Value value) {
auto transfer = dyn_cast<SpatDeferredCommunicationOp>(op);
return transfer && llvm::is_contained(transfer.getSources(), value);
}
static LogicalResult verifyOnlyConstantExternalValues(Operation* ownerOp, Region& region, StringRef kind) {
bool hasFailure = false;
region.walk([&](Operation* op) {
for (OpOperand& operand : op->getOpOperands()) {
Value value = operand.get();
if (isDefinedInsideRegion(value, region) || isConstantExternalValue(value))
if (isDefinedInsideRegion(value, region) || isConstantExternalValue(value)
|| isRecordedDeferredCommunicationSource(op, value))
continue;
InFlightDiagnostic diagnostic =
@@ -219,8 +225,35 @@ static LogicalResult verifyOnlyConstantExternalValues(Operation* ownerOp, Region
return success(!hasFailure);
}
static LogicalResult verifyYieldTypes(Operation* op, Region& region, TypeRange resultTypes, StringRef kind) {
if (region.empty())
return op->emitOpError() << kind << " requires a body block";
Block& block = region.front();
auto yield = dyn_cast_or_null<SpatYieldOp>(block.getTerminator());
if (!yield)
return op->emitOpError() << kind << " body must terminate with spat.yield";
if (yield.getOutputs().size() != resultTypes.size())
return op->emitOpError() << kind << " yield operand count must match result count";
for (auto [yieldType, resultType] : llvm::zip(yield.getOutputs().getTypes(), resultTypes))
if (yieldType != resultType)
return op->emitOpError() << kind << " yield operand types must match result types";
return success();
}
static LogicalResult verifyRegionArguments(Operation* op, Region& region, ValueRange operands, StringRef kind) {
if (region.empty())
return op->emitOpError() << kind << " requires a body block";
Block& block = region.front();
if (block.getNumArguments() != operands.size())
return op->emitOpError() << kind << " body argument count must match operand count";
for (auto [arg, operand] : llvm::zip(block.getArguments(), operands))
if (arg.getType() != operand.getType())
return op->emitOpError() << kind << " body argument types must match operand types";
return success();
}
template <typename ComputeBatchOpTy>
static LogicalResult verifyBatchBody(ComputeBatchOpTy batchOp, Block& block) {
static LogicalResult verifyBatchBody(ComputeBatchOpTy batchOp, Block& block, bool verifyLaneSliceOffsets = true) {
if (batchOp.getNumResults() == 0) {
auto yieldOp = dyn_cast_or_null<SpatYieldOp>(block.getTerminator());
if (!yieldOp)
@@ -235,11 +268,12 @@ static LogicalResult verifyBatchBody(ComputeBatchOpTy batchOp, Block& block) {
auto laneArg = batchOp.getLaneArgument();
if (!laneArg)
return batchOp.emitError("compute_batch body must have a lane block argument");
for (auto& bodyOp : block) {
if (auto extractSlice = dyn_cast<tensor::ExtractSliceOp>(&bodyOp))
if (failed(verifyStaticUnitStrideExtractSliceOp(extractSlice, *laneArg, "tensor.extract_slice")))
return failure();
}
if (verifyLaneSliceOffsets)
for (auto& bodyOp : block) {
if (auto extractSlice = dyn_cast<tensor::ExtractSliceOp>(&bodyOp))
if (failed(verifyStaticUnitStrideExtractSliceOp(extractSlice, *laneArg, "tensor.extract_slice")))
return failure();
}
return success();
}
@@ -679,7 +713,9 @@ LogicalResult verifyComputeResultsUses(Operation* op) {
if (!isAnySpatialComputeLike(op))
return op->emitError("verifyComputeResultUses: op is not a Spatial compute-like operation");
if (!llvm::all_of(op->getResults(), [](Value result) {
return llvm::all_of(result.getUsers(), [](Operation* op) {
return llvm::all_of(result.getUsers(), [result](Operation* op) {
if (isRecordedDeferredCommunicationSource(op, result))
return true;
return !isAnySpatialComputeLike(op->getParentOp());
});
})) {
@@ -690,57 +726,67 @@ LogicalResult verifyComputeResultsUses(Operation* op) {
template <typename ComputeOpTy>
LogicalResult verifyComputeLikeOp(ComputeOpTy compute, StringRef opName) {
auto& block = compute.getBody().front();
unsigned expectedArgCount = compute.getWeights().size() + compute.getInputs().size();
if (block.getNumArguments() != expectedArgCount)
return compute.emitOpError("compute body must have weight and input block arguments");
bool isScheduled = isa<SpatScheduledCompute>(compute.getOperation());
if (compute.getBody().empty())
return compute.emitOpError("compute body must have at least one block");
for (auto [weightIndex, weight] : llvm::enumerate(compute.getWeights())) {
auto blockArg = compute.getWeightArgument(weightIndex);
if (!blockArg || blockArg->getType() != weight.getType())
return compute.emitOpError("compute weight block argument types must match weight operand types exactly");
}
for (auto [inputIndex, input] : llvm::enumerate(compute.getInputs())) {
auto blockArg = compute.getInputArgument(inputIndex);
if (!blockArg || blockArg->getType() != input.getType())
return compute.emitOpError("compute input block argument types must match input operand types exactly");
}
SmallVector<Type> yieldedTypes;
for (Block& block : compute.getBody()) {
if ((!isScheduled && block.getNumArguments() != expectedArgCount)
|| (isScheduled && block.getNumArguments() < expectedArgCount))
return compute.emitOpError("compute body must have weight and input block arguments");
if (block.mightHaveTerminator()) {
auto yieldOp = dyn_cast_or_null<SpatYieldOp>(block.getTerminator());
if (!yieldOp)
for (auto [weightIndex, weight] : llvm::enumerate(compute.getWeights()))
if (block.getArgument(weightIndex).getType() != weight.getType())
return compute.emitOpError("compute weight block argument types must match weight operand types exactly");
for (auto [inputIndex, input] : llvm::enumerate(compute.getInputs()))
if (block.getArgument(compute.getWeights().size() + inputIndex).getType() != input.getType())
return compute.emitOpError("compute input block argument types must match input operand types exactly");
Operation* terminator = block.getTerminator();
if (auto yieldOp = dyn_cast_or_null<SpatYieldOp>(terminator)) {
if (isScheduled)
return compute.emitOpError("scheduled compute blocks must terminate with spat.block_yield");
llvm::append_range(yieldedTypes, yieldOp->getOperandTypes());
continue;
}
auto blockYield = dyn_cast_or_null<SpatBlockYieldOp>(terminator);
if (!blockYield || !isScheduled)
return compute.emitOpError("ComputeOp must have a single yield operation");
if (blockYield->getNumSuccessors() == 0)
llvm::append_range(yieldedTypes, blockYield->getOperandTypes());
}
auto resultTypes = compute.getResultTypes();
auto yieldTypes = yieldOp->getOperandTypes();
if (resultTypes.size() != yieldTypes.size())
return compute.emitOpError("ComputeOp must have same number of results as yieldOp operands");
auto resultTypes = compute.getResultTypes();
if (resultTypes.size() != yieldedTypes.size())
return compute.emitOpError("ComputeOp must have same number of results as yielded operands");
for (auto it : llvm::reverse(llvm::zip(resultTypes, yieldTypes))) {
auto resultType = std::get<0>(it);
auto yieldType = std::get<1>(it);
for (auto it : llvm::reverse(llvm::zip(resultTypes, yieldedTypes))) {
auto resultType = std::get<0>(it);
auto yieldType = std::get<1>(it);
if (resultType != yieldType || failed(verifyCompatibleShape(resultType, yieldType)))
return compute.emitOpError("ComputeOp output must be of the same type as yieldOp operand");
if (resultType != yieldType || failed(verifyCompatibleShape(resultType, yieldType)))
return compute.emitOpError("ComputeOp output must be of the same type as yieldOp operand");
if (auto resultRankedType = dyn_cast<RankedTensorType>(resultType)) {
if (auto yieldRankedType = dyn_cast<RankedTensorType>(yieldType)) {
if (resultRankedType.getEncoding() != yieldRankedType.getEncoding())
return compute.emitOpError("ComputeOp output must have the same encoding as yieldOp operand");
}
else {
if (auto resultRankedType = dyn_cast<RankedTensorType>(resultType)) {
if (auto yieldRankedType = dyn_cast<RankedTensorType>(yieldType)) {
if (resultRankedType.getEncoding() != yieldRankedType.getEncoding())
return compute.emitOpError("ComputeOp output has an encoding while yieldOp operand does not have one");
}
}
else if (dyn_cast<RankedTensorType>(yieldType)) {
return compute.emitOpError("ComputeOp output must not have an encoding if yieldOp operand has one");
else {
return compute.emitOpError("ComputeOp output must have the same encoding as yieldOp operand");
}
}
else if (dyn_cast<RankedTensorType>(yieldType)) {
return compute.emitOpError("ComputeOp output must not have an encoding if yieldOp operand has one");
}
}
for (unsigned inputIndex = 0; inputIndex < compute.getInputs().size(); ++inputIndex)
if (auto inputArg = compute.getInputArgument(inputIndex); !inputArg || inputArg->use_empty())
return compute.emitOpError("ComputeOp block argument is not used");
if (compute.getBody().hasOneBlock())
for (unsigned inputIndex = 0; inputIndex < compute.getInputs().size(); ++inputIndex)
if (auto inputArg = compute.getInputArgument(inputIndex); !inputArg || inputArg->use_empty())
return compute.emitOpError("ComputeOp block argument is not used");
if (failed(verifyStaticWeights(compute, opName)))
return failure();
if (failed(verifyOnlyConstantExternalValues(compute.getOperation(), compute.getBody(), opName)))
@@ -754,6 +800,31 @@ LogicalResult SpatGraphCompute::verify() { return verifyComputeLikeOp(*this, "sp
LogicalResult SpatScheduledCompute::verify() { return verifyComputeLikeOp(*this, "spat.scheduled_compute"); }
LogicalResult SpatBlockYieldOp::verify() {
if (getOperation()->getNumSuccessors() > 1)
return emitOpError("may target at most one next scheduled block");
Operation* parent = getOperation()->getParentOp();
if (!isa_and_nonnull<SpatScheduledCompute>(parent))
return emitOpError("expected spat.scheduled_compute parent");
if (getOperation()->getNumSuccessors() == 1) {
Block* next = getOperation()->getSuccessor(0);
if (getOperation()->getNumOperands() != next->getNumArguments())
return emitOpError("successor operand count must match next block argument count");
for (auto [operand, argument] : llvm::zip(getOperation()->getOperands(), next->getArguments()))
if (operand.getType() != argument.getType())
return emitOpError("successor operand types must match next block argument types");
}
return success();
}
LogicalResult SpatDeferredCommunicationOp::verify() {
if (getSources().empty())
return emitOpError("requires at least one source");
if (failed(verifyRegionArguments(getOperation(), getBody(), getSources(), "spat.deferred_communication")))
return failure();
return verifyYieldTypes(getOperation(), getBody(), getOperation()->getResultTypes(), "spat.deferred_communication");
}
template <typename ComputeBatchOpTy>
LogicalResult verifyComputeBatchLikeOp(ComputeBatchOpTy batch, StringRef opName) {
int32_t count = batch.getLaneCount();
@@ -808,7 +879,7 @@ LogicalResult verifyComputeBatchLikeOp(ComputeBatchOpTy batch, StringRef opName)
return failure();
if (failed(verifyOnlyConstantExternalValues(batch.getOperation(), batch.getBody(), opName)))
return failure();
return verifyBatchBody(batch, block);
return verifyBatchBody(batch, block, !isa<SpatScheduledComputeBatch>(batch.getOperation()));
}
LogicalResult SpatGraphComputeBatch::verify() { return verifyComputeBatchLikeOp(*this, "spat.graph_compute_batch"); }
@@ -1,656 +0,0 @@
#include "CommunicationPlan.hpp"
#include "mlir/IR/Diagnostics.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/STLExtras.h"
#include <algorithm>
using namespace mlir;
namespace onnx_mlir::spatial {
namespace {
StringRef stringifyExchangeKind(CommunicationExchangeKind kind) {
switch (kind) {
case CommunicationExchangeKind::HostForward: return "HostForward";
case CommunicationExchangeKind::OrdinaryValue: return "OrdinaryValue";
case CommunicationExchangeKind::PackedScalarRun: return "PackedScalarRun";
case CommunicationExchangeKind::IndexedBatchRun: return "IndexedBatchRun";
case CommunicationExchangeKind::ProjectedInput: return "ProjectedInput";
}
llvm_unreachable("unknown communication exchange kind");
}
unsigned getExchangeKindOrder(CommunicationExchangeKind kind) {
switch (kind) {
case CommunicationExchangeKind::HostForward: return 0;
case CommunicationExchangeKind::OrdinaryValue: return 1;
case CommunicationExchangeKind::PackedScalarRun: return 2;
case CommunicationExchangeKind::IndexedBatchRun: return 3;
case CommunicationExchangeKind::ProjectedInput: return 4;
}
llvm_unreachable("unknown communication exchange kind");
}
void emitPlanError(Operation* anchor, StringRef message) {
if (anchor)
anchor->emitError(message);
}
struct PeerPair {
int64_t minCore = 0;
int64_t maxCore = 0;
bool operator==(const PeerPair& other) const {
return minCore == other.minCore && maxCore == other.maxCore;
}
};
struct PeerPairInfo {
static PeerPair getEmptyKey() {
return {llvm::DenseMapInfo<int64_t>::getEmptyKey(), llvm::DenseMapInfo<int64_t>::getEmptyKey()};
}
static PeerPair getTombstoneKey() {
return {llvm::DenseMapInfo<int64_t>::getTombstoneKey(), llvm::DenseMapInfo<int64_t>::getTombstoneKey()};
}
static unsigned getHashValue(PeerPair key) { return llvm::hash_combine(key.minCore, key.maxCore); }
static bool isEqual(PeerPair lhs, PeerPair rhs) { return lhs == rhs; }
};
PeerPair getPeerPair(const CommunicationExchange& exchange) {
return {std::min(exchange.sourceCore, exchange.targetCore), std::max(exchange.sourceCore, exchange.targetCore)};
}
bool exchangeOrderLess(const CommunicationExchange& lhs, const CommunicationExchange& rhs) {
if (getExchangeKindOrder(lhs.kind) != getExchangeKindOrder(rhs.kind))
return getExchangeKindOrder(lhs.kind) < getExchangeKindOrder(rhs.kind);
if (lhs.sourceClass != rhs.sourceClass)
return lhs.sourceClass < rhs.sourceClass;
if (lhs.targetClass != rhs.targetClass)
return lhs.targetClass < rhs.targetClass;
if (lhs.producerOrder != rhs.producerOrder)
return lhs.producerOrder < rhs.producerOrder;
if (lhs.producer.resultIndex != rhs.producer.resultIndex)
return lhs.producer.resultIndex < rhs.producer.resultIndex;
if (lhs.producer.instance.laneStart != rhs.producer.instance.laneStart)
return lhs.producer.instance.laneStart < rhs.producer.instance.laneStart;
if (lhs.producer.instance.laneCount != rhs.producer.instance.laneCount)
return lhs.producer.instance.laneCount < rhs.producer.instance.laneCount;
if (lhs.sourceLane != rhs.sourceLane)
return lhs.sourceLane < rhs.sourceLane;
if (lhs.targetLane != rhs.targetLane)
return lhs.targetLane < rhs.targetLane;
if (lhs.ordinal != rhs.ordinal)
return lhs.ordinal < rhs.ordinal;
return lhs.channelId < rhs.channelId;
}
void sortExchangeIds(ArrayRef<CommunicationExchange> exchanges,
SmallVectorImpl<CommunicationExchangeId>& ids) {
llvm::stable_sort(ids, [&](CommunicationExchangeId lhs, CommunicationExchangeId rhs) {
return exchangeOrderLess(exchanges[lhs.value], exchanges[rhs.value]);
});
}
void addEvent(CommunicationPlan& plan,
DenseMap<ClassId, SmallVector<CommunicationEvent, 16>>& eventsByClass,
CommunicationExchangeId id,
CommunicationEventKind kind,
ClassId classId,
int64_t core,
unsigned& order) {
eventsByClass[classId].push_back(CommunicationEvent {id, kind, classId, core, order++});
(void)plan;
}
void buildEventStreams(CommunicationPlan& plan,
SmallVectorImpl<CommunicationExchange>& exchanges,
DenseMap<ClassId, SmallVector<CommunicationEvent, 16>>& eventsByClass) {
DenseMap<PeerPair, SmallVector<CommunicationExchangeId, 8>, PeerPairInfo> exchangesByPair;
for (const CommunicationExchange& exchange : exchanges)
exchangesByPair[getPeerPair(exchange)].push_back(exchange.id);
SmallVector<PeerPair, 8> pairs;
pairs.reserve(exchangesByPair.size());
for (const auto& entry : exchangesByPair)
pairs.push_back(entry.first);
llvm::sort(pairs, [](PeerPair lhs, PeerPair rhs) {
if (lhs.minCore != rhs.minCore)
return lhs.minCore < rhs.minCore;
return lhs.maxCore < rhs.maxCore;
});
unsigned order = 0;
for (PeerPair pair : pairs) {
SmallVector<CommunicationExchangeId, 8> hiToLo;
SmallVector<CommunicationExchangeId, 8> loToHi;
for (CommunicationExchangeId id : exchangesByPair.lookup(pair)) {
const CommunicationExchange& exchange = exchanges[id.value];
if (exchange.sourceCore == pair.maxCore && exchange.targetCore == pair.minCore)
hiToLo.push_back(id);
else
loToHi.push_back(id);
}
sortExchangeIds(exchanges, hiToLo);
sortExchangeIds(exchanges, loToHi);
for (CommunicationExchangeId id : hiToLo) {
const CommunicationExchange& exchange = exchanges[id.value];
addEvent(plan, eventsByClass, id, CommunicationEventKind::Receive, exchange.targetClass, exchange.targetCore, order);
}
for (CommunicationExchangeId id : hiToLo) {
const CommunicationExchange& exchange = exchanges[id.value];
addEvent(plan, eventsByClass, id, CommunicationEventKind::Send, exchange.sourceClass, exchange.sourceCore, order);
}
for (CommunicationExchangeId id : loToHi) {
const CommunicationExchange& exchange = exchanges[id.value];
addEvent(plan, eventsByClass, id, CommunicationEventKind::Send, exchange.sourceClass, exchange.sourceCore, order);
}
for (CommunicationExchangeId id : loToHi) {
const CommunicationExchange& exchange = exchanges[id.value];
addEvent(plan, eventsByClass, id, CommunicationEventKind::Receive, exchange.targetClass, exchange.targetCore, order);
}
}
}
} // namespace
FailureOr<CommunicationPlan>
CommunicationPlan::build(Operation* anchor,
ArrayRef<CommunicationClassInfo> classes,
const DenseMap<CpuId, ClassId>& cpuToClass,
const ProducerSourceClassMap& producerSourceClasses,
const DenseMap<ProducerKey, Type, ProducerKeyInfo>& producerPayloadTypes,
const DenseMap<ProducerKey, SmallVector<ClassId, 4>, ProducerKeyInfo>& producerDestClasses,
const DenseMap<ProducerKey, SmallVector<ClassId, 4>, ProducerKeyInfo>& ordinaryProducerDestClasses,
ProjectedInputPlanMap& projectedPlans,
const DenseMap<Value, ClassId>& hostOutputOwners,
const DenseMap<Value, bool>& liveExternalUseCache,
int64_t& nextChannelId) {
CommunicationPlan plan;
DenseMap<ClassId, CommunicationClassInfo> classById;
for (CommunicationClassInfo klass : classes)
classById[klass.classId] = std::move(klass);
auto appendExchange = [&](CommunicationExchangeKind kind,
ProducerKey producer,
ClassId sourceClass,
ClassId targetClass,
Type payloadType,
int64_t sourceCore,
int64_t targetCore,
unsigned sourceLane,
unsigned targetLane,
unsigned ordinal,
Value originalHostOutput = Value(),
const ProjectedInputTransferFragment* fragment = nullptr) -> FailureOr<CommunicationExchangeId> {
if (sourceCore == targetCore)
return failure();
auto sourceInfoIt = classById.find(sourceClass);
auto targetInfoIt = classById.find(targetClass);
if (sourceInfoIt == classById.end() || targetInfoIt == classById.end())
return failure();
CommunicationExchangeId exchangeId {plan.exchanges.size()};
plan.exchanges.push_back(CommunicationExchange {
exchangeId,
kind,
PlaceholderId {static_cast<unsigned>(plan.demands.size())},
producer,
sourceClass,
targetClass,
sourceInfoIt->second.rank,
targetInfoIt->second.rank,
payloadType,
nextChannelId++,
sourceCore,
targetCore,
sourceLane,
targetLane,
ordinal,
exchangeId.value,
originalHostOutput,
fragment,
});
return exchangeId;
};
auto getPayloadType = [&](ProducerKey producer) -> Type {
auto typeIt = producerPayloadTypes.find(producer);
if (typeIt != producerPayloadTypes.end())
return typeIt->second;
if (!producer.instance.op || producer.resultIndex >= producer.instance.op->getNumResults())
return Type();
return producer.instance.op->getResult(producer.resultIndex).getType();
};
for (const auto& entry : producerDestClasses) {
ProducerKey producer = entry.first;
auto sourceClassIt = producerSourceClasses.find(producer);
if (sourceClassIt == producerSourceClasses.end()) {
emitPlanError(anchor, "communication plan could not find producer source class");
return failure();
}
ClassId sourceClass = sourceClassIt->second;
auto sourceInfoIt = classById.find(sourceClass);
if (sourceInfoIt == classById.end()) {
emitPlanError(anchor, "communication plan references an unknown producer source class");
return failure();
}
const CommunicationClassInfo& sourceInfo = sourceInfoIt->second;
Type payloadType = getPayloadType(producer);
if (!payloadType) {
emitPlanError(anchor, "communication plan could not determine producer payload type");
return failure();
}
for (ClassId targetClass : entry.second) {
if (targetClass == sourceClass)
continue;
auto targetInfoIt = classById.find(targetClass);
if (targetInfoIt == classById.end()) {
emitPlanError(anchor, "communication plan references an unknown target class");
return failure();
}
const CommunicationClassInfo& targetInfo = targetInfoIt->second;
CommunicationExchangeKind kind = CommunicationExchangeKind::OrdinaryValue;
if (sourceInfo.isBatch && !targetInfo.isBatch)
kind = CommunicationExchangeKind::PackedScalarRun;
else if (sourceInfo.isBatch && targetInfo.isBatch)
kind = CommunicationExchangeKind::IndexedBatchRun;
else if (!llvm::is_contained(ordinaryProducerDestClasses.lookup(producer), targetClass))
continue;
SmallVector<CommunicationExchangeId, 8>* bucket = nullptr;
ProducerTargetKey lookupKey {producer, targetClass};
if (kind == CommunicationExchangeKind::OrdinaryValue)
bucket = &plan.ordinaryExchangesByProducerTarget[lookupKey];
else if (kind == CommunicationExchangeKind::PackedScalarRun)
bucket = &plan.packedScalarRunExchangesByProducerTarget[lookupKey];
else if (kind == CommunicationExchangeKind::IndexedBatchRun)
bucket = &plan.indexedBatchRunExchangesByProducerTarget[lookupKey];
if (!bucket)
continue;
if (!sourceInfo.isBatch) {
if (sourceInfo.cpus.empty()) {
emitPlanError(anchor, "communication plan source class has no CPU");
return failure();
}
int64_t sourceCore = static_cast<int64_t>(sourceInfo.cpus.front());
unsigned ordinal = 0;
for (auto [targetLane, targetCpu] : llvm::enumerate(targetInfo.cpus)) {
auto exchangeId = appendExchange(kind,
producer,
sourceClass,
targetClass,
payloadType,
sourceCore,
static_cast<int64_t>(targetCpu),
0,
static_cast<unsigned>(targetLane),
ordinal++);
if (failed(exchangeId)) {
emitPlanError(anchor, "communication plan failed to append scalar source exchange");
return failure();
}
bucket->push_back(*exchangeId);
if (!targetInfo.isBatch)
break;
}
continue;
}
if (sourceInfo.cpus.empty()) {
emitPlanError(anchor, "communication plan batch source class has no CPUs");
return failure();
}
if (targetInfo.isBatch && sourceInfo.cpus.size() != targetInfo.cpus.size()) {
emitPlanError(anchor, "communication plan cannot map batch classes of different sizes");
return failure();
}
for (auto [sourceLane, sourceCpu] : llvm::enumerate(sourceInfo.cpus)) {
CpuId targetCpu = targetInfo.isBatch ? targetInfo.cpus[sourceLane] : targetInfo.cpus.front();
auto exchangeId = appendExchange(kind,
producer,
sourceClass,
targetClass,
payloadType,
static_cast<int64_t>(sourceCpu),
static_cast<int64_t>(targetCpu),
static_cast<unsigned>(sourceLane),
targetInfo.isBatch ? static_cast<unsigned>(sourceLane) : 0,
static_cast<unsigned>(bucket->size()));
if (failed(exchangeId)) {
emitPlanError(anchor, "communication plan failed to append batch source exchange");
return failure();
}
bucket->push_back(*exchangeId);
}
}
}
for (const auto& entry : producerSourceClasses) {
ProducerKey producer = entry.first;
ClassId sourceClass = entry.second;
if (!producer.instance.op || producer.resultIndex >= producer.instance.op->getNumResults())
continue;
Value originalOutput = producer.instance.op->getResult(producer.resultIndex);
auto liveIt = liveExternalUseCache.find(originalOutput);
if (liveIt == liveExternalUseCache.end() || !liveIt->second)
continue;
auto ownerIt = hostOutputOwners.find(originalOutput);
if (ownerIt == hostOutputOwners.end())
continue;
ClassId ownerClass = ownerIt->second;
if (ownerClass == sourceClass)
continue;
auto sourceInfoIt = classById.find(sourceClass);
auto ownerInfoIt = classById.find(ownerClass);
if (sourceInfoIt == classById.end() || ownerInfoIt == classById.end()) {
emitPlanError(anchor, "communication plan references an unknown host-forward class");
return failure();
}
const CommunicationClassInfo& sourceInfo = sourceInfoIt->second;
const CommunicationClassInfo& ownerInfo = ownerInfoIt->second;
if (sourceInfo.isBatch != ownerInfo.isBatch) {
emitPlanError(anchor, "communication plan does not support mixed scalar/batch host forwarding");
return failure();
}
if (sourceInfo.isBatch && sourceInfo.cpus.size() != ownerInfo.cpus.size()) {
emitPlanError(anchor, "communication plan cannot host-forward between batch classes of different sizes");
return failure();
}
Type payloadType = getPayloadType(producer);
if (!payloadType) {
emitPlanError(anchor, "communication plan could not determine host-forward payload type");
return failure();
}
HostForwardKey lookupKey {originalOutput, sourceClass, ownerClass};
SmallVector<CommunicationExchangeId, 8>& bucket = plan.hostForwardExchangesByOutput[lookupKey];
if (!sourceInfo.isBatch) {
if (!bucket.empty())
continue;
auto exchangeId = appendExchange(CommunicationExchangeKind::HostForward,
producer,
sourceClass,
ownerClass,
payloadType,
static_cast<int64_t>(sourceInfo.cpus.front()),
static_cast<int64_t>(ownerInfo.cpus.front()),
0,
0,
0,
originalOutput);
if (failed(exchangeId)) {
emitPlanError(anchor, "communication plan failed to append scalar host-forward exchange");
return failure();
}
bucket.push_back(*exchangeId);
continue;
}
if (bucket.size() >= sourceInfo.cpus.size())
continue;
unsigned ordinal = static_cast<unsigned>(bucket.size());
if (producer.instance.laneStart >= sourceInfo.cpus.size()) {
emitPlanError(anchor, "communication plan host-forward producer lane is out of range");
return failure();
}
unsigned lane = producer.instance.laneStart;
auto exchangeId = appendExchange(CommunicationExchangeKind::HostForward,
producer,
sourceClass,
ownerClass,
payloadType,
static_cast<int64_t>(sourceInfo.cpus[lane]),
static_cast<int64_t>(ownerInfo.cpus[lane]),
lane,
lane,
ordinal,
originalOutput);
if (failed(exchangeId)) {
emitPlanError(anchor, "communication plan failed to append batch host-forward exchange");
return failure();
}
bucket.push_back(*exchangeId);
}
DenseSet<const ProjectedInputTransferFragment*> seenFragments;
for (auto& extractEntry : projectedPlans) {
for (auto& classEntry : extractEntry.second) {
ClassId targetClass = classEntry.first;
ProjectedInputTransferPlan& inputPlan = classEntry.second;
auto targetInfoIt = classById.find(targetClass);
if (targetInfoIt == classById.end()) {
emitPlanError(anchor, "communication plan references an unknown target class");
return failure();
}
const CommunicationClassInfo& targetInfo = targetInfoIt->second;
for (ProjectedInputTransferFragment& fragment : inputPlan.fragments) {
if (!seenFragments.insert(&fragment).second) {
emitPlanError(anchor, "communication plan found the same projected fragment twice");
return failure();
}
auto sourceClassIt = cpuToClass.find(fragment.sourceCoreId);
if (sourceClassIt == cpuToClass.end()) {
emitPlanError(anchor, "communication plan could not map projected source core to a class");
return failure();
}
ClassId sourceClass = sourceClassIt->second;
auto sourceInfoIt = classById.find(sourceClass);
if (sourceInfoIt == classById.end()) {
emitPlanError(anchor, "communication plan references an unknown source class");
return failure();
}
const CommunicationClassInfo& sourceInfo = sourceInfoIt->second;
plan.demands.push_back(InputDemand {{static_cast<unsigned>(plan.demands.size())},
fragment.producer,
targetClass,
inputPlan.layout.fragmentType});
plan.publications.push_back(ProducerPublication {fragment.producer, sourceClass, inputPlan.layout.fragmentType});
if (fragment.sourceCoreId == fragment.targetCoreId)
continue;
if (sourceInfo.rank == targetInfo.rank && sourceClass != targetClass) {
emitPlanError(anchor, "communication plan found a same-rank inter-class projected exchange");
return failure();
}
FailureOr<CommunicationExchangeId> exchangeId = appendExchange(
CommunicationExchangeKind::ProjectedInput,
fragment.producer,
sourceClass,
targetClass,
inputPlan.layout.fragmentType,
fragment.sourceCoreId,
fragment.targetCoreId,
0,
fragment.targetLane,
fragment.ordinal,
Value(),
&fragment);
if (failed(exchangeId)) {
emitPlanError(anchor, "communication plan failed to append projected input exchange");
return failure();
}
fragment.channelId = plan.exchanges[exchangeId->value].channelId;
plan.projectedExchangesByFragment[&fragment].push_back(*exchangeId);
}
}
}
buildEventStreams(plan, plan.exchanges, plan.eventsByClass);
if (failed(plan.verify(anchor)))
return failure();
return plan;
}
LogicalResult CommunicationPlan::verify(Operation* anchor) const {
DenseSet<int64_t> channelIds;
SmallVector<const CommunicationExchange*, 8> malformed;
size_t malformedCount = 0;
auto recordMalformed = [&](const CommunicationExchange& exchange) {
++malformedCount;
if (malformed.size() < 8)
malformed.push_back(&exchange);
};
for (auto [index, exchange] : llvm::enumerate(exchanges)) {
bool bad = false;
if (exchange.id.value != index)
bad = true;
if (exchange.sourceCore == exchange.targetCore)
bad = true;
if (!exchange.payloadType)
bad = true;
if (!channelIds.insert(exchange.channelId).second)
bad = true;
if (bad)
recordMalformed(exchange);
}
DenseMap<size_t, unsigned> sendCounts;
DenseMap<size_t, unsigned> receiveCounts;
for (const auto& classEvents : eventsByClass) {
std::optional<unsigned> previousOrder;
DenseSet<unsigned> classOrders;
for (const CommunicationEvent& event : classEvents.second) {
if (event.exchangeId.value >= exchanges.size()) {
if (anchor)
anchor->emitError("communication plan event references an invalid exchange id");
return failure();
}
const CommunicationExchange& exchange = exchanges[event.exchangeId.value];
if (previousOrder && event.order < *previousOrder) {
if (anchor)
anchor->emitError("communication plan event stream is not sorted");
return failure();
}
previousOrder = event.order;
if (!classOrders.insert(event.order).second) {
if (anchor)
anchor->emitError("communication plan event stream contains duplicate event order");
return failure();
}
if (event.kind == CommunicationEventKind::Send) {
++sendCounts[event.exchangeId.value];
if (event.classId != exchange.sourceClass || event.core != exchange.sourceCore)
recordMalformed(exchange);
}
else {
++receiveCounts[event.exchangeId.value];
if (event.classId != exchange.targetClass || event.core != exchange.targetCore)
recordMalformed(exchange);
}
}
}
for (const CommunicationExchange& exchange : exchanges) {
if (sendCounts[exchange.id.value] != 1 || receiveCounts[exchange.id.value] != 1)
recordMalformed(exchange);
}
auto verifyLookup = [&](ArrayRef<CommunicationExchangeId> ids) -> LogicalResult {
for (CommunicationExchangeId id : ids)
if (id.value >= exchanges.size())
return failure();
return success();
};
for (const auto& entry : projectedExchangesByFragment)
if (failed(verifyLookup(entry.second))) {
if (anchor)
anchor->emitError("communication plan projected lookup references an invalid exchange id");
return failure();
}
if (malformedCount != 0) {
if (anchor) {
InFlightDiagnostic diag = anchor->emitError("communication plan contains malformed exchanges");
for (const CommunicationExchange* exchange : malformed) {
diag << " [exchange=" << exchange->id.value << " kind=" << stringifyExchangeKind(exchange->kind)
<< " sourceClass=" << exchange->sourceClass << " sourceCore=" << exchange->sourceCore
<< " targetClass=" << exchange->targetClass << " targetCore=" << exchange->targetCore
<< " channelId=" << exchange->channelId << " payloadType=" << exchange->payloadType << "]";
}
if (malformedCount > malformed.size())
diag << " additionalMalformed=" << (malformedCount - malformed.size());
}
return failure();
}
return success();
}
ArrayRef<CommunicationEvent> CommunicationPlan::getEventsForClass(ClassId classId) const {
auto it = eventsByClass.find(classId);
if (it == eventsByClass.end())
return {};
return it->second;
}
ArrayRef<CommunicationExchangeId>
CommunicationPlan::getOrdinaryValueExchanges(ProducerKey producer, ClassId targetClass) const {
auto it = ordinaryExchangesByProducerTarget.find({producer, targetClass});
if (it == ordinaryExchangesByProducerTarget.end())
return {};
return it->second;
}
ArrayRef<CommunicationExchangeId>
CommunicationPlan::getProjectedInputExchanges(const ProjectedInputTransferFragment& fragment) const {
auto it = projectedExchangesByFragment.find(&fragment);
if (it == projectedExchangesByFragment.end())
return {};
return it->second;
}
ArrayRef<CommunicationExchangeId>
CommunicationPlan::getPackedScalarRunExchanges(ProducerKey producer, ClassId targetClass) const {
auto it = packedScalarRunExchangesByProducerTarget.find({producer, targetClass});
if (it == packedScalarRunExchangesByProducerTarget.end())
return {};
return it->second;
}
ArrayRef<CommunicationExchangeId>
CommunicationPlan::getIndexedBatchRunExchanges(ProducerKey producer, ClassId targetClass) const {
auto it = indexedBatchRunExchangesByProducerTarget.find({producer, targetClass});
if (it == indexedBatchRunExchangesByProducerTarget.end())
return {};
return it->second;
}
ArrayRef<CommunicationExchangeId>
CommunicationPlan::getHostForwardExchanges(Value originalOutput, ClassId sourceClass, ClassId ownerClass) const {
auto it = hostForwardExchangesByOutput.find({originalOutput, sourceClass, ownerClass});
if (it == hostForwardExchangesByOutput.end())
return {};
return it->second;
}
} // namespace onnx_mlir::spatial
@@ -1,229 +0,0 @@
#pragma once
#include "mlir/IR/Operation.h"
#include "mlir/IR/Types.h"
#include "mlir/Support/LLVM.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/SmallVector.h"
#include <cstddef>
#include <cstdint>
#include <optional>
#include "MergeScheduleKeys.hpp"
#include "ProjectedFragments.hpp"
namespace onnx_mlir::spatial {
enum class TransferKind {
DirectValue,
LocalFragment,
SameClassForward,
RemoteChannel,
WholeTensorBarrier
};
enum class CommunicationExchangeKind {
OrdinaryValue,
ProjectedInput,
PackedScalarRun,
IndexedBatchRun,
HostForward
};
enum class CommunicationEventKind {
Send,
Receive
};
struct CommunicationExchangeId {
size_t value = 0;
bool operator==(const CommunicationExchangeId& other) const { return value == other.value; }
operator size_t() const { return value; }
};
struct CommunicationExchangeIdInfo {
static CommunicationExchangeId getEmptyKey() { return {llvm::DenseMapInfo<size_t>::getEmptyKey()}; }
static CommunicationExchangeId getTombstoneKey() { return {llvm::DenseMapInfo<size_t>::getTombstoneKey()}; }
static unsigned getHashValue(CommunicationExchangeId id) { return llvm::hash_value(id.value); }
static bool isEqual(CommunicationExchangeId lhs, CommunicationExchangeId rhs) { return lhs == rhs; }
};
struct PlaceholderId {
unsigned value = 0;
bool operator==(const PlaceholderId& other) const { return value == other.value; }
};
struct PlaceholderIdInfo {
static PlaceholderId getEmptyKey() { return {static_cast<unsigned>(-1)}; }
static PlaceholderId getTombstoneKey() { return {static_cast<unsigned>(-2)}; }
static unsigned getHashValue(PlaceholderId id) { return llvm::hash_value(id.value); }
static bool isEqual(PlaceholderId lhs, PlaceholderId rhs) { return lhs == rhs; }
};
struct InputDemand {
PlaceholderId placeholder;
ProducerKey producer;
ClassId targetClass = 0;
mlir::Type requiredType;
};
struct ProducerPublication {
ProducerKey producer;
ClassId sourceClass = 0;
mlir::Type payloadType;
};
struct CommunicationClassInfo {
ClassId classId = 0;
size_t rank = 0;
bool isBatch = false;
mlir::Operation* op = nullptr;
llvm::SmallVector<CpuId, 8> cpus;
};
struct CommunicationExchange {
CommunicationExchangeId id;
CommunicationExchangeKind kind = CommunicationExchangeKind::OrdinaryValue;
PlaceholderId placeholder;
ProducerKey producer;
ClassId sourceClass = 0;
ClassId targetClass = 0;
size_t sourceRank = 0;
size_t targetRank = 0;
mlir::Type payloadType;
int64_t channelId = 0;
int64_t sourceCore = 0;
int64_t targetCore = 0;
unsigned sourceLane = 0;
unsigned targetLane = 0;
unsigned ordinal = 0;
uint64_t producerOrder = 0;
mlir::Value originalHostOutput;
const ProjectedInputTransferFragment* fragment = nullptr;
};
using ExchangeDescriptor = CommunicationExchange;
struct CommunicationEvent {
CommunicationExchangeId exchangeId;
CommunicationEventKind kind = CommunicationEventKind::Send;
ClassId classId = 0;
int64_t core = 0;
unsigned order = 0;
};
class CommunicationPlan {
public:
using ProjectedInputPlanMap =
llvm::DenseMap<mlir::Operation*, llvm::DenseMap<ClassId, ProjectedInputTransferPlan>>;
using ProducerSourceClassMap = llvm::DenseMap<ProducerKey, ClassId, ProducerKeyInfo>;
static mlir::FailureOr<CommunicationPlan>
build(mlir::Operation* anchor,
llvm::ArrayRef<CommunicationClassInfo> classes,
const llvm::DenseMap<CpuId, ClassId>& cpuToClass,
const ProducerSourceClassMap& producerSourceClasses,
const llvm::DenseMap<ProducerKey, mlir::Type, ProducerKeyInfo>& producerPayloadTypes,
const llvm::DenseMap<ProducerKey, llvm::SmallVector<ClassId, 4>, ProducerKeyInfo>& producerDestClasses,
const llvm::DenseMap<ProducerKey, llvm::SmallVector<ClassId, 4>, ProducerKeyInfo>& ordinaryProducerDestClasses,
ProjectedInputPlanMap& projectedPlans,
const llvm::DenseMap<mlir::Value, ClassId>& hostOutputOwners,
const llvm::DenseMap<mlir::Value, bool>& liveExternalUseCache,
int64_t& nextChannelId);
mlir::LogicalResult verify(mlir::Operation* anchor) const;
llvm::ArrayRef<CommunicationEvent> getEventsForClass(ClassId classId) const;
llvm::ArrayRef<CommunicationExchange> getExchanges() const { return exchanges; }
const CommunicationExchange& getExchange(CommunicationExchangeId id) const { return exchanges[id.value]; }
const CommunicationExchange& getExchange(size_t id) const { return exchanges[id]; }
llvm::ArrayRef<CommunicationExchangeId>
getOrdinaryValueExchanges(ProducerKey producer, ClassId targetClass) const;
llvm::ArrayRef<CommunicationExchangeId>
getProjectedInputExchanges(const ProjectedInputTransferFragment& fragment) const;
llvm::ArrayRef<CommunicationExchangeId>
getPackedScalarRunExchanges(ProducerKey producer, ClassId targetClass) const;
llvm::ArrayRef<CommunicationExchangeId>
getIndexedBatchRunExchanges(ProducerKey producer, ClassId targetClass) const;
llvm::ArrayRef<CommunicationExchangeId>
getHostForwardExchanges(mlir::Value originalOutput, ClassId sourceClass, ClassId ownerClass) const;
private:
struct ProducerTargetKey {
ProducerKey producer;
ClassId targetClass = 0;
bool operator==(const ProducerTargetKey& other) const {
return producer == other.producer && targetClass == other.targetClass;
}
};
struct ProducerTargetKeyInfo {
static ProducerTargetKey getEmptyKey() {
return {ProducerKeyInfo::getEmptyKey(), llvm::DenseMapInfo<ClassId>::getEmptyKey()};
}
static ProducerTargetKey getTombstoneKey() {
return {ProducerKeyInfo::getTombstoneKey(), llvm::DenseMapInfo<ClassId>::getTombstoneKey()};
}
static unsigned getHashValue(const ProducerTargetKey& key) {
return llvm::hash_combine(ProducerKeyInfo::getHashValue(key.producer), key.targetClass);
}
static bool isEqual(const ProducerTargetKey& lhs, const ProducerTargetKey& rhs) { return lhs == rhs; }
};
struct HostForwardKey {
mlir::Value originalOutput;
ClassId sourceClass = 0;
ClassId ownerClass = 0;
bool operator==(const HostForwardKey& other) const {
return originalOutput == other.originalOutput && sourceClass == other.sourceClass
&& ownerClass == other.ownerClass;
}
};
struct HostForwardKeyInfo {
static HostForwardKey getEmptyKey() {
return {llvm::DenseMapInfo<mlir::Value>::getEmptyKey(),
llvm::DenseMapInfo<ClassId>::getEmptyKey(),
llvm::DenseMapInfo<ClassId>::getEmptyKey()};
}
static HostForwardKey getTombstoneKey() {
return {llvm::DenseMapInfo<mlir::Value>::getTombstoneKey(),
llvm::DenseMapInfo<ClassId>::getTombstoneKey(),
llvm::DenseMapInfo<ClassId>::getTombstoneKey()};
}
static unsigned getHashValue(const HostForwardKey& key) {
return llvm::hash_combine(key.originalOutput, key.sourceClass, key.ownerClass);
}
static bool isEqual(const HostForwardKey& lhs, const HostForwardKey& rhs) { return lhs == rhs; }
};
llvm::SmallVector<InputDemand, 16> demands;
llvm::SmallVector<ProducerPublication, 16> publications;
llvm::SmallVector<CommunicationExchange, 16> exchanges;
llvm::DenseMap<ClassId, llvm::SmallVector<CommunicationEvent, 16>> eventsByClass;
llvm::DenseMap<const ProjectedInputTransferFragment*, llvm::SmallVector<CommunicationExchangeId, 1>>
projectedExchangesByFragment;
llvm::DenseMap<ProducerTargetKey, llvm::SmallVector<CommunicationExchangeId, 8>, ProducerTargetKeyInfo>
ordinaryExchangesByProducerTarget;
llvm::DenseMap<ProducerTargetKey, llvm::SmallVector<CommunicationExchangeId, 8>, ProducerTargetKeyInfo>
packedScalarRunExchangesByProducerTarget;
llvm::DenseMap<ProducerTargetKey, llvm::SmallVector<CommunicationExchangeId, 8>, ProducerTargetKeyInfo>
indexedBatchRunExchangesByProducerTarget;
llvm::DenseMap<HostForwardKey, llvm::SmallVector<CommunicationExchangeId, 8>, HostForwardKeyInfo>
hostForwardExchangesByOutput;
};
} // namespace onnx_mlir::spatial
@@ -1,134 +0,0 @@
#include "HostOutputFinalization.hpp"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/PatternMatch.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/STLExtras.h"
#include "MaterializedClassState.hpp"
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
using namespace mlir;
namespace onnx_mlir::spatial {
LogicalResult finalizeProjectedHostOutputFragments(MaterializerState& state) {
if (state.pendingProjectedHostOutputFragments.empty())
return success();
DenseMap<Value, SmallVector<PendingProjectedHostOutputFragment*, 16>> byOutput;
for (PendingProjectedHostOutputFragment& fragment : state.pendingProjectedHostOutputFragments)
byOutput[fragment.originalOutput].push_back(&fragment);
SmallVector<Value, 8> outputs;
outputs.reserve(byOutput.size());
auto returnOp = dyn_cast<func::ReturnOp>(state.func.getBody().front().getTerminator());
if (!returnOp)
return state.func.emitError("expected func.return terminator while finalizing projected host output fragments");
DenseSet<Value> seenOutputs;
for (Value returned : returnOp.getOperands()) {
if (!byOutput.contains(returned) || !seenOutputs.insert(returned).second)
continue;
outputs.push_back(returned);
}
if (outputs.size() != byOutput.size())
return state.func.emitError("projected host output fragments must be keyed by returned logical host outputs");
for (Value originalOutput : outputs) {
if (isa_and_present<SpatScheduledCompute, SpatScheduledComputeBatch>(originalOutput.getDefiningOp())) {
return state.func.emitError(
"projected host output assembly must be keyed by the original logical host output, not by a materialized scheduled result");
}
auto resultType = dyn_cast<RankedTensorType>(originalOutput.getType());
if (!resultType || !resultType.hasStaticShape())
return state.func.emitError("projected host output must have static ranked tensor type");
SmallVector<PendingProjectedHostOutputFragment*, 16>& fragments = byOutput[originalOutput];
llvm::sort(fragments, [](const PendingProjectedHostOutputFragment* lhs,
const PendingProjectedHostOutputFragment* rhs) {
if (lhs->sourceClass != rhs->sourceClass)
return lhs->sourceClass < rhs->sourceClass;
if (lhs->publicationResultIndex != rhs->publicationResultIndex)
return lhs->publicationResultIndex < rhs->publicationResultIndex;
if (lhs->sourceFragmentOrdinal != rhs->sourceFragmentOrdinal)
return lhs->sourceFragmentOrdinal < rhs->sourceFragmentOrdinal;
return std::lexicographical_compare(lhs->offsets.begin(),
lhs->offsets.end(),
rhs->offsets.begin(),
rhs->offsets.end());
});
state.rewriter.setInsertionPoint(returnOp);
Location loc = fragments.front()->loc;
SmallVector<Value, 16> blueprintOperands;
SmallVector<int64_t, 16> fragmentOperandIndices;
SmallVector<int64_t, 16> fragmentSourceOffsets;
SmallVector<int64_t, 64> flatOffsets;
SmallVector<int64_t, 64> flatSizes;
SmallVector<int64_t, 64> flatStrides;
DenseMap<Value, int64_t> operandIndicesByValue;
for (PendingProjectedHostOutputFragment* fragmentRecord : fragments) {
if (fragmentRecord->sourceClass >= state.classes.size())
return state.func.emitError("projected host output fragment references an invalid source class");
MaterializedClass& sourceClass = state.classes[fragmentRecord->sourceClass];
if (fragmentRecord->publicationResultIndex >= sourceClass.op->getNumResults()) {
return sourceClass.op->emitError("projected host output fragment references an invalid publication result")
<< " sourceClass=" << sourceClass.id
<< " resultIndex=" << fragmentRecord->publicationResultIndex
<< " resultCount=" << sourceClass.op->getNumResults();
}
Value operand = sourceClass.op->getResult(fragmentRecord->publicationResultIndex);
auto [operandIt, inserted] =
operandIndicesByValue.try_emplace(operand, static_cast<int64_t>(blueprintOperands.size()));
if (inserted)
blueprintOperands.push_back(operand);
fragmentOperandIndices.push_back(operandIt->second);
fragmentSourceOffsets.push_back(fragmentRecord->sourceElementOffset);
llvm::append_range(flatOffsets, fragmentRecord->offsets);
llvm::append_range(flatSizes, fragmentRecord->sizes);
llvm::append_range(flatStrides, fragmentRecord->strides);
auto operandType = dyn_cast<RankedTensorType>(operand.getType());
if (!operandType || !operandType.hasStaticShape())
return state.func.emitError("projected host output assembly requires static ranked tensor operands");
}
if (blueprintOperands.empty())
return state.func.emitError("missing projected host output fragments");
Value input = blueprintOperands.front();
ValueRange extraFragments = ValueRange(blueprintOperands).drop_front();
auto blueprint = SpatBlueprintOp::create(
state.rewriter,
loc,
resultType,
input,
extraFragments,
state.rewriter.getStringAttr("nchw"),
state.rewriter.getStringAttr("fragmented"),
state.rewriter.getDenseI64ArrayAttr(flatOffsets),
state.rewriter.getDenseI64ArrayAttr(flatSizes),
state.rewriter.getStringAttr("identity"),
state.rewriter.getStringAttr("fragment_assembly"),
state.rewriter.getDenseI64ArrayAttr(fragmentOperandIndices),
state.rewriter.getDenseI64ArrayAttr(fragmentSourceOffsets),
state.rewriter.getDenseI64ArrayAttr(flatStrides),
state.rewriter.getStringAttr("disjoint"),
state.rewriter.getStringAttr("complete"));
state.hostReplacements[originalOutput] = blueprint.getOutput();
}
return success();
}
} // namespace onnx_mlir::spatial
@@ -1,11 +0,0 @@
#pragma once
#include "mlir/Support/LogicalResult.h"
namespace onnx_mlir::spatial {
struct MaterializerState;
mlir::LogicalResult finalizeProjectedHostOutputFragments(MaterializerState& state);
} // namespace onnx_mlir::spatial
File diff suppressed because it is too large Load Diff
@@ -1,17 +0,0 @@
#pragma once
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Support/LogicalResult.h"
#include "Scheduling/MergeSchedule.hpp"
namespace onnx_mlir {
namespace spatial {
class MergeScheduleMaterializer {
public:
mlir::LogicalResult run(mlir::func::FuncOp func, const MergeScheduleResult& schedule, int64_t& nextChannelId);
};
} // namespace spatial
} // namespace onnx_mlir
@@ -1,296 +0,0 @@
#pragma once
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/Transforms/FoldUtils.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/SmallVector.h"
#include <optional>
#include "MaterializeMergeSchedule.hpp"
#include "CommunicationPlan.hpp"
#include "MergeMessages.hpp"
#include "MergeScheduleKeys.hpp"
#include "ProjectedFragments.hpp"
#include "Scheduling/ComputeInstanceUtils.hpp"
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
namespace onnx_mlir::spatial {
struct MaterializedClass {
ClassId id = 0;
llvm::SmallVector<CpuId, 8> cpus;
mlir::Operation* op = nullptr;
mlir::Block* body = nullptr;
bool isBatch = false;
llvm::DenseMap<CpuId, unsigned> cpuToLane;
llvm::SmallVector<mlir::Value, 8> weights;
llvm::SmallVector<mlir::Value, 8> inputs;
llvm::SmallVector<mlir::Value, 4> hostOutputs;
llvm::DenseMap<mlir::Value, unsigned> publicationOutputToResultIndex;
llvm::DenseMap<mlir::Value, mlir::BlockArgument> weightArgs;
llvm::DenseMap<mlir::Value, mlir::BlockArgument> inputArgs;
llvm::DenseMap<mlir::Value, unsigned> hostOutputToResultIndex;
};
struct PackedScalarRunSlot {
llvm::SmallVector<ProducerKey, 8> keys;
};
enum class PackedScalarRunKind {
Materialized,
DeferredReceive,
DeferredLocalCompute
};
struct PackedScalarRunValue {
ClassId targetClass = 0;
mlir::Operation* sourceOp = nullptr;
size_t resultIndex = 0;
PackedScalarRunKind kind = PackedScalarRunKind::Materialized;
mlir::Value packed;
mlir::RankedTensorType fragmentType;
llvm::SmallVector<PackedScalarRunSlot, 8> slots;
llvm::SmallVector<CommunicationExchangeId, 8> exchangeIds;
};
struct IndexedBatchRunValue {
ClassId targetClass = 0;
mlir::Operation* sourceOp = nullptr;
size_t resultIndex = 0;
mlir::Value packed;
mlir::RankedTensorType fragmentType;
llvm::SmallVector<PackedScalarRunSlot, 8> slots;
llvm::SmallVector<CommunicationExchangeId, 8> exchangeIds;
};
struct LogicalSlotRange {
SlotId start = 0;
SlotId count = 0;
};
struct MaterializationRunSlot {
llvm::SmallVector<ComputeInstance, 8> peers;
};
using MaterializationRun = llvm::SmallVector<MaterializationRunSlot, 8>;
struct OutputDestinationGroup {
llvm::SmallVector<size_t, 4> resultIndices;
llvm::SmallVector<ClassId, 4> destinationClasses;
};
struct BatchRunSendPlan {
size_t resultIndex = 0;
ClassId destinationClass = 0;
llvm::SmallVector<CommunicationExchangeId, 8> exchangeIds;
};
enum class TensorDemandActionKind {
DestinationFanout,
SameClassIndexedFragment,
ProjectedInputPublication,
TerminalBlueprintPublication,
WholeTensorBarrier
};
enum class WholeTensorBarrierReason {
FunctionReturnWithoutBlueprint,
DenseLogicalConsumer
};
struct TensorDemandAction {
TensorDemandActionKind kind = TensorDemandActionKind::DestinationFanout;
std::optional<ClassId> destinationClass;
std::optional<WholeTensorBarrierReason> barrierReason;
};
struct RunOutputDemand {
size_t resultIndex = 0;
mlir::Value originalOutput;
mlir::RankedTensorType fragmentType;
llvm::SmallVector<TensorDemandAction, 4> actions;
};
struct CompactRunPlan {
llvm::SmallVector<RunOutputDemand, 4> outputs;
};
enum class BatchInputDemandKind {
LaneFragment,
ProjectedFragment,
WholeTensorBarrier
};
struct BatchInputDemand {
BatchInputDemandKind kind = BatchInputDemandKind::LaneFragment;
std::optional<ProducerKey> wholeTensorProducer;
};
struct CommunicationExchangeEmissionState {
bool producerBound = false;
bool sendEmitted = false;
bool receiveEmitted = false;
mlir::Value producerPayload;
mlir::Value receiveValue;
mlir::Operation* producerAnchor = nullptr;
mlir::Operation* sendAnchor = nullptr;
mlir::Operation* receiveAnchor = nullptr;
std::optional<mlir::Location> loc;
};
struct CloneIndexingContext {
std::optional<mlir::Value> runSlotIndex;
std::optional<mlir::Value> projectionSlotIndex;
};
struct MaterializerState;
class AvailableValueStore {
public:
struct ExactBatchFragmentRecord {
ProducerKey key;
mlir::Value value;
};
void record(ProducerKey key, ClassId classId, mlir::Value value) {
exactValues[key][classId] = value;
auto batch = mlir::dyn_cast_or_null<SpatComputeBatch>(key.instance.op);
if (!batch || key.instance.laneCount == 0)
return;
WholeBatchAssemblyLookupKey lookupKey {batch.getOperation(), key.resultIndex, classId};
llvm::SmallVector<ExactBatchFragmentRecord, 16>& bucket = exactBatchFragmentsByProducerResultClass[lookupKey];
for (ExactBatchFragmentRecord& record : bucket) {
if (!(record.key == key))
continue;
record.value = value;
return;
}
bucket.push_back({key, value});
}
void recordPackedRun(PackedScalarRunValue run) {
size_t runIndex = packedScalarRuns.size();
packedScalarRuns.push_back(std::move(run));
const PackedScalarRunValue& storedRun = packedScalarRuns[runIndex];
WholeBatchAssemblyLookupKey lookupKey {storedRun.sourceOp, storedRun.resultIndex, storedRun.targetClass};
packedRunsByProducerResultClass[lookupKey].push_back(runIndex);
}
void recordIndexedBatchRun(IndexedBatchRunValue run) { indexedBatchRuns.push_back(std::move(run)); }
void recordDeferredExact(ProducerKey key, ClassId classId, llvm::ArrayRef<CommunicationExchangeId> exchangeIds) {
llvm::SmallVector<CommunicationExchangeId, 4>& bucket = exactExchangeIds[key][classId];
for (CommunicationExchangeId id : exchangeIds)
if (!llvm::is_contained(bucket, id))
bucket.push_back(id);
}
llvm::ArrayRef<CommunicationExchangeId> getDeferredExactExchangeIds(ProducerKey key, ClassId classId) const {
auto producerIt = exactExchangeIds.find(key);
if (producerIt == exactExchangeIds.end())
return {};
auto classIt = producerIt->second.find(classId);
if (classIt == producerIt->second.end())
return {};
return classIt->second;
}
bool hasDeferredExact(ProducerKey key, ClassId classId) const {
return !getDeferredExactExchangeIds(key, classId).empty();
}
std::optional<mlir::Value> lookupExact(ProducerKey key, ClassId classId) const;
std::optional<mlir::Value> lookup(MaterializerState& state, ProducerKey key, ClassId classId);
IndexedBatchRunValue* lookupIndexedBatchRun(ProducerKey key, ClassId classId);
llvm::ArrayRef<size_t> getPackedRunIndicesForWholeBatch(WholeBatchAssemblyLookupKey key) const {
auto it = packedRunsByProducerResultClass.find(key);
if (it == packedRunsByProducerResultClass.end())
return {};
return it->second;
}
llvm::ArrayRef<ExactBatchFragmentRecord> getExactFragmentsForWholeBatch(WholeBatchAssemblyLookupKey key) const {
auto it = exactBatchFragmentsByProducerResultClass.find(key);
if (it == exactBatchFragmentsByProducerResultClass.end())
return {};
return it->second;
}
PackedScalarRunValue& getPackedRun(size_t index) { return packedScalarRuns[index]; }
private:
std::optional<mlir::Value> lookupPackedRun(MaterializerState& state, ProducerKey key, ClassId classId);
llvm::DenseMap<ProducerKey, llvm::DenseMap<ClassId, mlir::Value>, ProducerKeyInfo> exactValues;
llvm::DenseMap<ProducerKey,
llvm::DenseMap<ClassId, llvm::SmallVector<CommunicationExchangeId, 4>>,
ProducerKeyInfo>
exactExchangeIds;
llvm::SmallVector<PackedScalarRunValue, 8> packedScalarRuns;
llvm::SmallVector<IndexedBatchRunValue, 8> indexedBatchRuns;
llvm::DenseMap<WholeBatchAssemblyLookupKey,
llvm::SmallVector<ExactBatchFragmentRecord, 16>,
WholeBatchAssemblyLookupKeyInfo>
exactBatchFragmentsByProducerResultClass;
llvm::DenseMap<WholeBatchAssemblyLookupKey, llvm::SmallVector<size_t, 16>, WholeBatchAssemblyLookupKeyInfo>
packedRunsByProducerResultClass;
};
struct MaterializerState {
mlir::func::FuncOp func;
const MergeScheduleResult& schedule;
mlir::IRRewriter rewriter;
mlir::OperationFolder constantFolder;
int64_t& nextChannelId;
llvm::SmallVector<MaterializedClass, 8> classes;
llvm::DenseMap<CpuId, ClassId> cpuToClass;
llvm::DenseMap<CpuId, llvm::SmallVector<ComputeInstance, 32>> logicalInstancesByCpu;
llvm::DenseMap<ComputeInstance, LogicalSlotRange> scheduledInstanceToLogicalSlots;
llvm::DenseMap<ComputeInstance, ComputeInstance> logicalInstanceToScheduledChunk;
llvm::DenseSet<ClassSlotKey> materializedLogicalSlots;
llvm::DenseMap<ProducerKey, llvm::SmallVector<ClassId, 4>, ProducerKeyInfo> producerDestClasses;
llvm::DenseMap<ProducerKey, llvm::SmallVector<ClassId, 4>, ProducerKeyInfo> ordinaryProducerDestClasses;
llvm::DenseMap<SameClassConsumerLookupKey, llvm::SmallVector<ProducerKey, 4>, SameClassConsumerLookupKeyInfo>
sameClassConsumerIndex;
llvm::DenseMap<ProjectedBatchInputKey, AffineProjectedInputSliceMatch, ProjectedBatchInputKeyInfo>
projectedInputMatches;
llvm::DenseSet<ProjectedBatchInputKey, ProjectedBatchInputKeyInfo> nonProjectedInputs;
llvm::DenseMap<mlir::Value, bool> liveExternalUseCache;
llvm::DenseMap<mlir::Operation*, llvm::SmallVector<mlir::Type, 4>> batchOutputFragmentTypesCache;
llvm::DenseMap<ComputeInstance, llvm::SmallVector<mlir::Value, 4>, llvm::DenseMapInfo<ComputeInstance>>
computeInstanceOutputsCache;
llvm::DenseMap<ProducerKey, llvm::DenseMap<ClassId, ProjectedTransferDescriptor>, ProducerKeyInfo>
projectedTransfers;
llvm::DenseMap<mlir::Operation*, llvm::DenseMap<ClassId, ProjectedExtractReplacement>>
projectedExtractReplacements;
llvm::DenseMap<mlir::Operation*, llvm::DenseMap<ClassId, ProjectedInputTransferPlan>>
projectedInputTransferPlans;
CommunicationPlan communicationPlan;
llvm::SmallVector<CommunicationExchangeEmissionState, 16> communicationExchangeStates;
llvm::SmallVector<ProducerKey, 8> deferredProducerValues;
AvailableValueStore availableValues;
llvm::DenseMap<mlir::Value, mlir::Value> hostReplacements;
llvm::DenseMap<mlir::Value, ClassId> hostOutputOwners;
llvm::SmallVector<PendingProjectedHostOutputFragment, 32> pendingProjectedHostOutputFragments;
llvm::DenseSet<mlir::Operation*> oldComputeOps;
MaterializerState(mlir::func::FuncOp func, const MergeScheduleResult& schedule, int64_t& nextChannelId)
: func(func),
schedule(schedule),
rewriter(func.getContext()),
constantFolder(func.getContext()),
nextChannelId(nextChannelId) {}
};
} // namespace onnx_mlir::spatial
@@ -1,398 +0,0 @@
#include "mlir/Analysis/TopologicalSortUtils.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/IR/IRMapping.h"
#include "mlir/IR/Location.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/IR/Region.h"
#include "mlir/IR/Value.h"
#include "mlir/IR/ValueRange.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Support/LLVM.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/raw_os_ostream.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <fstream>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "MaterializeMergeSchedule.hpp"
#include "SpatialDataflowCsvExporter.hpp"
#include "Scheduling/ComputeGraph.hpp"
#include "Scheduling/ComputeInstanceUtils.hpp"
#include "Scheduling/MergeSchedulingAnalysis.hpp"
#include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.hpp"
#include "src/Accelerators/PIM/Common/IR/CompactAsmUtils.hpp"
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
#include "src/Accelerators/PIM/Common/Support/ReportUtils.hpp"
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp"
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
using namespace mlir;
namespace onnx_mlir {
namespace {
using namespace onnx_mlir::compact_asm;
using SpatCompute = spatial::SpatGraphCompute;
using SpatComputeBatch = spatial::SpatGraphComputeBatch;
bool isTrivialSerialMergeCandidate(SpatCompute compute) {
if (!compute->hasOneUse())
return false;
auto& use = *compute->getUses().begin();
auto user = dyn_cast<SpatCompute>(use.getOwner());
return user && user.getInputs().size() == 1 && use.getOperandNumber() >= user.getWeights().size();
}
SmallVector<size_t> appendMissingWeightsAndBuildIndexMap(SmallVectorImpl<Value>& targetWeights,
ValueRange sourceWeights) {
DenseMap<Value, SmallVector<size_t, 4>> targetWeightIndices;
for (auto [weightIndex, weight] : llvm::enumerate(targetWeights))
targetWeightIndices[weight].push_back(weightIndex);
DenseMap<Value, size_t> usedSourceWeightOccurrences;
SmallVector<size_t> sourceToTargetIndex;
sourceToTargetIndex.reserve(sourceWeights.size());
for (Value weight : sourceWeights) {
size_t occurrence = usedSourceWeightOccurrences[weight]++;
auto& matchingIndices = targetWeightIndices[weight];
if (occurrence >= matchingIndices.size()) {
size_t newIndex = targetWeights.size();
targetWeights.push_back(weight);
matchingIndices.push_back(newIndex);
sourceToTargetIndex.push_back(newIndex);
continue;
}
sourceToTargetIndex.push_back(matchingIndices[occurrence]);
}
return sourceToTargetIndex;
}
void mergeTriviallyConnectedComputes(func::FuncOp funcOp) {
Location loc = funcOp.getLoc();
IRRewriter rewriter(funcOp->getContext());
SmallVector<SpatCompute> trivialComputes;
llvm::SmallSet<SpatCompute, 8> toErase;
for (auto compute : funcOp.getOps<SpatCompute>())
if (isTrivialSerialMergeCandidate(compute))
trivialComputes.push_back(compute);
while (!trivialComputes.empty()) {
auto compute = trivialComputes.front();
if (compute.use_empty()) {
std::swap(trivialComputes.front(), trivialComputes.back());
trivialComputes.pop_back();
continue;
}
auto& computeUse = *compute->getUses().begin();
auto child = cast<SpatCompute>(computeUse.getOwner());
auto usedResult = cast<OpResult>(computeUse.get()).getResultNumber();
auto childInputIndex = computeUse.getOperandNumber() - child.getWeights().size();
rewriter.setInsertionPointAfter(compute.getOperation());
SmallVector<Value> mergedWeights(compute.getWeights().begin(), compute.getWeights().end());
SmallVector<size_t> childWeightToNewIndex = appendMissingWeightsAndBuildIndexMap(mergedWeights, child.getWeights());
SmallVector<Value> mergedInputs(compute.getInputs().begin(), compute.getInputs().end());
auto newCompute = SpatCompute::create(rewriter, loc, child.getResultTypes(), mergedWeights, mergedInputs);
Block* newBody = rewriter.createBlock(&newCompute.getBodyRegion());
for (Value weight : mergedWeights)
newBody->addArgument(weight.getType(), loc);
for (Value input : mergedInputs)
newBody->addArgument(input.getType(), loc);
IRMapping mapper;
for (auto [weightIndex, _] : llvm::enumerate(compute.getWeights())) {
auto oldWeightArg = compute.getWeightArgument(weightIndex);
auto newWeightArg = newCompute.getWeightArgument(weightIndex);
assert(oldWeightArg && newWeightArg && "expected compute weight block arguments");
mapper.map(*oldWeightArg, *newWeightArg);
}
for (auto [inputIndex, _] : llvm::enumerate(compute.getInputs())) {
auto oldInputArg = compute.getInputArgument(inputIndex);
auto newInputArg = newCompute.getInputArgument(inputIndex);
assert(oldInputArg && newInputArg && "expected compute input block arguments");
mapper.map(*oldInputArg, *newInputArg);
}
for (auto [oldIndex, weight] : llvm::enumerate(child.getWeights())) {
auto oldWeightArg = child.getWeightArgument(oldIndex);
auto newWeightArg = newCompute.getWeightArgument(childWeightToNewIndex[oldIndex]);
assert(oldWeightArg && newWeightArg && "expected child compute weight block arguments");
mapper.map(*oldWeightArg, *newWeightArg);
}
rewriter.setInsertionPointToEnd(newBody);
auto computeYield = cast<spatial::SpatYieldOp>(compute.getBody().front().getTerminator());
for (Operation& op : compute.getBody().front().without_terminator())
rewriter.clone(op, mapper);
auto childInputArg = child.getInputArgument(childInputIndex);
assert(childInputArg && "expected child compute input block argument");
mapper.map(*childInputArg, mapper.lookupOrDefault(computeYield.getOperand(usedResult)));
rewriter.setInsertionPointToEnd(newBody);
for (auto& op : child.getBody().front())
rewriter.clone(op, mapper);
child.replaceAllUsesWith(newCompute);
toErase.insert(child);
std::swap(trivialComputes.front(), trivialComputes.back());
trivialComputes.pop_back();
toErase.insert(compute);
if (isTrivialSerialMergeCandidate(newCompute))
trivialComputes.push_back(newCompute);
}
for (auto compute : toErase) {
for (Value result : compute->getResults())
result.dropAllUses();
compute.erase();
}
}
void generateReport(func::FuncOp funcOp, const std::string& name, size_t usedCpuCount = 0) {
std::fstream file = openReportFile(name);
if (!file.is_open())
return;
llvm::raw_os_ostream os(file);
struct ReportRow {
uint64_t id = 0;
uint64_t logicalComputeCount = 0;
uint64_t crossbarCount = 0;
uint64_t instructionCount = 0;
bool isRebatched = false;
SmallVector<int32_t> coreIds;
};
//TODO Used for report refactor
struct CollectorConcatRow {
uint64_t computeId = 0;
int32_t coreId = -1;
uint64_t operandCount = 0;
};
uint64_t totalComputeOps = 0;
uint64_t totalLogicalComputes = 0;
uint64_t totalBatchComputeOps = 0;
uint64_t totalInstructionCount = 0;
uint64_t totalCrossbarCount = 0;
uint64_t nextBatchId = 0;
//TODO Used for report refactor
std::vector<ReportRow> collectedData;
//TODO Used for report refactor
std::vector<CollectorConcatRow> collectorConcatRows;
auto getPerInstanceCrossbarCount = [&](Operation* op) -> uint64_t {
return static_cast<uint64_t>(spatial::collectDistinctCrossbarWeights(op).size());
};
for (Operation& op : funcOp.getBody().front()) {
if (auto spatCompute = dyn_cast<spatial::SpatScheduledCompute>(&op)) {
uint64_t numInst = spatial::countComputeBodyInstructions(spatCompute.getBody());
uint64_t perInstanceCrossbarCount = getPerInstanceCrossbarCount(spatCompute.getOperation());
SmallVector<int32_t> coreIds;
auto coreId = getOptionalScheduledCoreId(spatCompute, "merge compute core id");
if (failed(coreId))
return;
if (*coreId)
coreIds.push_back(**coreId);
uint64_t computeId = totalComputeOps++;
collectedData.push_back({computeId, 1, perInstanceCrossbarCount, numInst, false, coreIds});
uint64_t maxConcatOperands = 0;
spatCompute.getBody().walk([&](spatial::SpatConcatOp concatOp) {
maxConcatOperands = std::max<uint64_t>(maxConcatOperands, concatOp.getInputs().size());
});
//TODO 128 is a magic number
if (maxConcatOperands >= 128 && !coreIds.empty())
collectorConcatRows.push_back({computeId, coreIds.front(), maxConcatOperands});
totalLogicalComputes += 1;
totalInstructionCount += numInst;
totalCrossbarCount += perInstanceCrossbarCount;
continue;
}
if (auto batch = dyn_cast<spatial::SpatScheduledComputeBatch>(&op)) {
uint64_t numInst = spatial::countComputeBodyInstructions(batch.getBody());
uint64_t logicalCount = static_cast<uint64_t>(batch.getLaneCount());
uint64_t perInstanceCrossbarCount = getPerInstanceCrossbarCount(batch.getOperation());
SmallVector<int32_t> coreIds;
auto optionalCoreIds = getOptionalScheduledBatchCoreIds(batch, "merge compute_batch core id");
if (failed(optionalCoreIds))
return;
if (*optionalCoreIds)
coreIds = std::move(**optionalCoreIds);
collectedData.push_back(
{nextBatchId++, logicalCount, perInstanceCrossbarCount * logicalCount, numInst, true, coreIds});
totalComputeOps += 1;
totalLogicalComputes += logicalCount;
totalBatchComputeOps += 1;
totalInstructionCount += numInst * logicalCount;
totalCrossbarCount += perInstanceCrossbarCount * logicalCount;
}
}
llvm::SmallVector<ReportField, 6> totalFields = {
{"Used cores", std::to_string(usedCpuCount) },
{"Number of top-level compute ops", std::to_string(totalComputeOps) },
{"Number of logical computes", std::to_string(totalLogicalComputes) },
{"Number of top-level batch compute ops", std::to_string(totalBatchComputeOps) },
{"Number of instructions", std::to_string(totalInstructionCount)},
{"Number of used crossbars", std::to_string(totalCrossbarCount) }
};
printReportTotalsBlock(os, totalFields);
if (!collectedData.empty() || !collectorConcatRows.empty())
os << "\n";
if (!collectorConcatRows.empty()) {
os << "Collector concat materialization:\n";
for (const CollectorConcatRow& row : collectorConcatRows)
os << "\tmaterialization_kind = single_collector_concat, compute = " << row.computeId
<< ", concat_operand_count = " << row.operandCount << ", collector_core = " << row.coreId << "\n";
os << "\n";
}
sortReportEntriesByFirstCore(collectedData);
for (uint64_t cI = 0; cI < totalComputeOps; ++cI) {
uint64_t lastIndex = cI;
ReportRow current = collectedData[cI];
for (uint64_t nI = cI + 1; nI < totalComputeOps; ++nI) {
ReportRow next = collectedData[nI];
if (current.isRebatched == next.isRebatched && current.crossbarCount == next.crossbarCount
&& current.instructionCount == next.instructionCount
&& current.logicalComputeCount == next.logicalComputeCount)
lastIndex = nI;
else
break;
}
if (current.isRebatched) {
os << "Batch ";
for (uint64_t index = cI; index <= lastIndex; ++index) {
if (index != cI)
os << ",\n ";
os << collectedData[index].id << " (cores ";
if (collectedData[index].coreIds.empty())
os << "unknown";
else
printCompressedIntegerEntries(os, ArrayRef<int32_t>(collectedData[index].coreIds));
os << ")";
}
}
else {
os << "Compute ";
SmallVector<uint64_t> opIds;
opIds.reserve(lastIndex - cI + 1);
for (uint64_t index = cI; index <= lastIndex; ++index)
opIds.push_back(collectedData[index].id);
printCompressedIntegerEntries(os, ArrayRef<uint64_t>(opIds));
}
os << ":\n";
uint64_t perCoreLogicalComputeCount = current.isRebatched ? 1 : current.logicalComputeCount;
uint64_t perCoreInstructionCount = current.instructionCount;
uint64_t perCoreCrossbarCount =
current.logicalComputeCount == 0 ? 0 : current.crossbarCount / current.logicalComputeCount;
uint64_t totalEntryInstructionCount = current.instructionCount * current.logicalComputeCount;
llvm::SmallVector<ReportField, 3> perCoreFields = {
{"Number of logical computes", std::to_string(perCoreLogicalComputeCount)},
{"Number of instructions", std::to_string(perCoreInstructionCount) },
{"Number of used crossbars", std::to_string(perCoreCrossbarCount) }
};
if (current.isRebatched) {
llvm::SmallVector<ReportField, 3> totalEntryFields = {
{"Number of logical computes", std::to_string(current.logicalComputeCount)},
{"Number of instructions", std::to_string(totalEntryInstructionCount) },
{"Number of used crossbars", std::to_string(current.crossbarCount) }
};
printReportPerCoreAndTotalFields(os, perCoreFields, totalEntryFields);
}
else {
printReportFlatFields(os, perCoreFields);
}
printReportEntrySeparator(os, lastIndex + 1 < totalComputeOps);
cI = lastIndex;
}
os.flush();
file.close();
}
struct MergeComputeNodesPass : PassWrapper<MergeComputeNodesPass, OperationPass<func::FuncOp>> {
private:
int64_t nextChannelId = 0;
public:
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(MergeComputeNodesPass)
StringRef getArgument() const override { return "pim-merge-compute-nodes-pass"; }
StringRef getDescription() const override {
return "Merge Spatial-Compute-Nodes in order to reduce the total "
"execution time";
}
LogicalResult initialize(MLIRContext* context) override { return success(); }
void runOnOperation() override {
func::FuncOp func = getOperation();
if (failed(verifyLogicalSpatialGraphInvariants(func))) {
func.emitOpError("logical Spatial graph verification failed at the start of MergeComputeNodes");
signalPassFailure();
return;
}
mergeTriviallyConnectedComputes(func);
if (failed(verifyLogicalSpatialGraphInvariants(func))) {
func.emitOpError("logical Spatial graph verification failed after trivial merge simplification");
signalPassFailure();
return;
}
const spatial::MergeScheduleResult* analysisResult = nullptr;
analysisResult = &getAnalysis<spatial::MergeSchedulingAnalysis>().getResult();
spatial::SpatialDataflowExportStage exportMode = spatial::getSpatialDataflowExportStage();
if (failed(spatial::MergeScheduleMaterializer().run(func, *analysisResult, nextChannelId))) {
signalPassFailure();
return;
}
if (!sortTopologically(&func.getBody().front())) {
func.emitOpError("failed to topologically order merged Spatial IR");
signalPassFailure();
return;
}
if (failed(verifyScheduledSpatialInvariants(func))) {
func.emitOpError("scheduled Spatial verification failed after merge materialization");
signalPassFailure();
return;
}
if (spatial::shouldExportSpatialDataflowStage(exportMode, spatial::SpatialDataflowExportStage::Post)
&& failed(spatial::exportSpatialDataflowCsvPost(func))) {
signalPassFailure();
return;
}
dumpModule(cast<ModuleOp>(func->getParentOp()), "spatial2_merged");
generateReport(func, "spatial_merge_report", analysisResult->cpuToLastComputeMap.size());
}
};
} // namespace
std::unique_ptr<Pass> createMergeComputeNodesPass() { return std::make_unique<MergeComputeNodesPass>(); }
} // namespace onnx_mlir
@@ -1,67 +0,0 @@
#pragma once
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
namespace onnx_mlir::spatial {
using CpuId = size_t;
inline mlir::FailureOr<int32_t> getCheckedCoreId(mlir::Operation* anchor, CpuId cpu, llvm::StringRef fieldName) {
return pim::checkedI32(static_cast<uint64_t>(cpu), anchor, fieldName);
}
inline mlir::FailureOr<llvm::SmallVector<int32_t, 8>>
getCheckedCoreIds(mlir::Operation* anchor, llvm::ArrayRef<CpuId> cpus, llvm::StringRef fieldName) {
llvm::SmallVector<int32_t, 8> coreIds;
coreIds.reserve(cpus.size());
for (CpuId cpu : cpus) {
auto checkedCoreId = getCheckedCoreId(anchor, cpu, fieldName);
if (mlir::failed(checkedCoreId))
return mlir::failure();
coreIds.push_back(*checkedCoreId);
}
return coreIds;
}
struct MessageVector {
llvm::SmallVector<int64_t, 16> channelIds;
llvm::SmallVector<int32_t, 16> sourceCoreIds;
llvm::SmallVector<int32_t, 16> targetCoreIds;
size_t size() const { return channelIds.size(); }
bool empty() const { return channelIds.empty(); }
mlir::LogicalResult verify(mlir::Operation* anchor) const {
if (channelIds.size() != sourceCoreIds.size() || channelIds.size() != targetCoreIds.size())
return anchor->emitError("message metadata is inconsistent");
return mlir::success();
}
void append(int64_t channelId, int32_t sourceCoreId, int32_t targetCoreId) {
channelIds.push_back(channelId);
sourceCoreIds.push_back(sourceCoreId);
targetCoreIds.push_back(targetCoreId);
}
void append(llvm::ArrayRef<int64_t> channels, llvm::ArrayRef<int32_t> sources, llvm::ArrayRef<int32_t> targets) {
assert(channels.size() == sources.size() && "channel/source count mismatch");
assert(channels.size() == targets.size() && "channel/target count mismatch");
llvm::append_range(channelIds, channels);
llvm::append_range(sourceCoreIds, sources);
llvm::append_range(targetCoreIds, targets);
}
MessageVector slice(size_t offset, size_t count) const {
MessageVector result;
result.append(llvm::ArrayRef<int64_t>(channelIds).slice(offset, count),
llvm::ArrayRef<int32_t>(sourceCoreIds).slice(offset, count),
llvm::ArrayRef<int32_t>(targetCoreIds).slice(offset, count));
return result;
}
};
} // namespace onnx_mlir::spatial
@@ -1,134 +0,0 @@
#pragma once
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallVector.h"
#include <cstddef>
#include <cstdint>
#include <limits>
#include <utility>
#include "Scheduling/ComputeInstanceUtils.hpp"
namespace onnx_mlir::spatial {
using ClassId = size_t;
using SlotId = size_t;
struct ProducerKey {
ComputeInstance instance;
size_t resultIndex = 0;
bool operator==(const ProducerKey& other) const {
return instance == other.instance && resultIndex == other.resultIndex;
}
};
struct ProducerKeyInfo {
static ProducerKey getEmptyKey() {
return {llvm::DenseMapInfo<ComputeInstance>::getEmptyKey(), std::numeric_limits<size_t>::max()};
}
static ProducerKey getTombstoneKey() {
return {llvm::DenseMapInfo<ComputeInstance>::getTombstoneKey(), std::numeric_limits<size_t>::max()};
}
static unsigned getHashValue(const ProducerKey& key) {
return llvm::hash_combine(llvm::DenseMapInfo<ComputeInstance>::getHashValue(key.instance), key.resultIndex);
}
static bool isEqual(const ProducerKey& lhs, const ProducerKey& rhs) { return lhs == rhs; }
};
struct SameClassConsumerLookupKey {
mlir::Operation* sourceOp = nullptr;
size_t resultIndex = 0;
ClassId classId = 0;
bool operator==(const SameClassConsumerLookupKey& other) const {
return sourceOp == other.sourceOp && resultIndex == other.resultIndex && classId == other.classId;
}
};
struct SameClassConsumerLookupKeyInfo {
static SameClassConsumerLookupKey getEmptyKey() {
return {llvm::DenseMapInfo<mlir::Operation*>::getEmptyKey(), std::numeric_limits<size_t>::max(),
std::numeric_limits<ClassId>::max()};
}
static SameClassConsumerLookupKey getTombstoneKey() {
return {llvm::DenseMapInfo<mlir::Operation*>::getTombstoneKey(), std::numeric_limits<size_t>::max(),
std::numeric_limits<ClassId>::max()};
}
static unsigned getHashValue(const SameClassConsumerLookupKey& key) {
return llvm::hash_combine(llvm::DenseMapInfo<mlir::Operation*>::getHashValue(key.sourceOp),
key.resultIndex,
key.classId);
}
static bool isEqual(const SameClassConsumerLookupKey& lhs, const SameClassConsumerLookupKey& rhs) {
return lhs == rhs;
}
};
struct WholeBatchAssemblyLookupKey {
mlir::Operation* sourceOp = nullptr;
size_t resultIndex = 0;
ClassId classId = 0;
bool operator==(const WholeBatchAssemblyLookupKey& other) const {
return sourceOp == other.sourceOp && resultIndex == other.resultIndex && classId == other.classId;
}
};
struct WholeBatchAssemblyLookupKeyInfo {
static WholeBatchAssemblyLookupKey getEmptyKey() {
return {llvm::DenseMapInfo<mlir::Operation*>::getEmptyKey(), std::numeric_limits<size_t>::max(),
std::numeric_limits<ClassId>::max()};
}
static WholeBatchAssemblyLookupKey getTombstoneKey() {
return {llvm::DenseMapInfo<mlir::Operation*>::getTombstoneKey(), std::numeric_limits<size_t>::max(),
std::numeric_limits<ClassId>::max()};
}
static unsigned getHashValue(const WholeBatchAssemblyLookupKey& key) {
return llvm::hash_combine(llvm::DenseMapInfo<mlir::Operation*>::getHashValue(key.sourceOp),
key.resultIndex,
key.classId);
}
static bool isEqual(const WholeBatchAssemblyLookupKey& lhs, const WholeBatchAssemblyLookupKey& rhs) {
return lhs == rhs;
}
};
using ClassSlotKey = std::pair<ClassId, SlotId>;
struct ProjectedBatchInputKey {
mlir::Operation* consumerOp = nullptr;
unsigned inputIndex = 0;
bool operator==(const ProjectedBatchInputKey& other) const {
return consumerOp == other.consumerOp && inputIndex == other.inputIndex;
}
};
struct ProjectedBatchInputKeyInfo {
static ProjectedBatchInputKey getEmptyKey() {
return {llvm::DenseMapInfo<mlir::Operation*>::getEmptyKey(), std::numeric_limits<unsigned>::max()};
}
static ProjectedBatchInputKey getTombstoneKey() {
return {llvm::DenseMapInfo<mlir::Operation*>::getTombstoneKey(), std::numeric_limits<unsigned>::max()};
}
static unsigned getHashValue(const ProjectedBatchInputKey& key) {
return llvm::hash_combine(key.consumerOp, key.inputIndex);
}
static bool isEqual(const ProjectedBatchInputKey& lhs, const ProjectedBatchInputKey& rhs) { return lhs == rhs; }
};
} // namespace onnx_mlir::spatial
@@ -1,104 +0,0 @@
#include "ProjectedFragments.hpp"
#include "mlir/IR/BuiltinTypes.h"
namespace onnx_mlir::spatial {
static mlir::FailureOr<mlir::RankedTensorType> getPackedBatchTensorType(mlir::Type laneType, size_t laneCount) {
auto tensorType = mlir::dyn_cast<mlir::RankedTensorType>(laneType);
if (!tensorType || !tensorType.hasStaticShape() || tensorType.getRank() == 0)
return mlir::failure();
llvm::SmallVector<int64_t, 4> shape(tensorType.getShape());
shape[0] *= static_cast<int64_t>(laneCount);
return mlir::RankedTensorType::get(shape, tensorType.getElementType(), tensorType.getEncoding());
}
unsigned getProjectedFragmentsPerLogicalSlot(llvm::ArrayRef<int64_t> loopTripCounts) {
unsigned fragmentsPerLogicalSlot = 1;
for (int64_t tripCount : loopTripCounts) {
assert(tripCount > 0 && "projected loop trip counts must be positive");
fragmentsPerLogicalSlot *= static_cast<unsigned>(tripCount);
}
return fragmentsPerLogicalSlot;
}
mlir::LogicalResult verifyProjectedFragmentLayout(mlir::Operation* anchor, const ProjectedFragmentLayout& layout) {
if (!layout.fragmentType || layout.fragmentShape.empty())
return anchor->emitError("projected fragment layout is missing fragment type metadata");
if (layout.fragmentShape.size() != static_cast<size_t>(layout.fragmentType.getRank()))
return anchor->emitError("projected fragment layout rank does not match fragment type");
if (layout.payloadFragmentCount == 0 || layout.fragmentsPerLogicalSlot == 0)
return anchor->emitError("projected fragment layout has an invalid fragment count");
if (layout.payloadFragmentCount % layout.fragmentsPerLogicalSlot != 0)
return anchor->emitError("projected fragment layout payload fragment count is incompatible with logical slots");
return mlir::success();
}
mlir::FailureOr<mlir::RankedTensorType>
getProjectedPayloadType(mlir::Operation* anchor, mlir::RankedTensorType fragmentType, unsigned payloadFragmentCount) {
auto packedType = getPackedBatchTensorType(fragmentType, payloadFragmentCount);
if (mlir::failed(packedType)) {
anchor->emitError("cannot create projected payload type");
return mlir::failure();
}
return *packedType;
}
llvm::SmallVector<llvm::SmallVector<int64_t, 16>, 4>
buildProjectedFragmentOffsetsByDim(llvm::ArrayRef<llvm::SmallVector<int64_t, 4>> fragmentOffsets, size_t rank) {
llvm::SmallVector<llvm::SmallVector<int64_t, 16>, 4> fragmentOffsetsByDim(rank);
for (llvm::ArrayRef<int64_t> offsets : fragmentOffsets) {
assert(offsets.size() == rank && "projected offset rank mismatch");
for (size_t dim = 0; dim < rank; ++dim)
fragmentOffsetsByDim[dim].push_back(offsets[dim]);
}
return fragmentOffsetsByDim;
}
mlir::LogicalResult verifyProjectedTransferDescriptor(mlir::Operation* anchor,
const ProjectedTransferDescriptor& descriptor) {
if (mlir::failed(verifyProjectedFragmentLayout(anchor, descriptor.layout)))
return mlir::failure();
if (!descriptor.payloadType)
return anchor->emitError("projected transfer descriptor is missing payload type");
if (descriptor.fragmentOffsets.empty())
return anchor->emitError("projected transfer descriptor expected at least one fragment offset");
if (descriptor.fragmentOffsetsByDim.size() != descriptor.layout.fragmentShape.size())
return anchor->emitError("projected transfer descriptor dimension-major offsets are inconsistent");
for (llvm::ArrayRef<int64_t> dimOffsets : descriptor.fragmentOffsetsByDim)
if (dimOffsets.size() != descriptor.fragmentOffsets.size())
return anchor->emitError("projected transfer descriptor dimension-major offsets are inconsistent");
for (llvm::ArrayRef<int64_t> offsets : descriptor.fragmentOffsets)
if (offsets.size() != descriptor.layout.fragmentShape.size())
return anchor->emitError("projected transfer offset rank does not match fragment rank");
return mlir::success();
}
mlir::LogicalResult verifyProjectedSendDescriptor(mlir::Operation* anchor,
const ProjectedTransferDescriptor& descriptor,
const MessageVector& messages) {
if (mlir::failed(verifyProjectedTransferDescriptor(anchor, descriptor)))
return mlir::failure();
if (messages.size() * descriptor.layout.payloadFragmentCount != descriptor.fragmentOffsets.size())
return anchor->emitError("projected send descriptor metadata is inconsistent");
return mlir::success();
}
mlir::LogicalResult finalizeProjectedTransferDescriptor(mlir::Operation* anchor,
ProjectedTransferDescriptor& descriptor) {
descriptor.fragmentOffsetsByDim =
buildProjectedFragmentOffsetsByDim(descriptor.fragmentOffsets, descriptor.layout.fragmentShape.size());
auto payloadType =
getProjectedPayloadType(anchor, descriptor.layout.fragmentType, descriptor.layout.payloadFragmentCount);
if (mlir::failed(payloadType))
return mlir::failure();
if (descriptor.payloadType && descriptor.payloadType != *payloadType)
return anchor->emitError("projected transfer descriptor payload type does not match projected layout");
descriptor.payloadType = *payloadType;
return verifyProjectedTransferDescriptor(anchor, descriptor);
}
} // namespace onnx_mlir::spatial
@@ -1,105 +0,0 @@
#pragma once
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/Value.h"
#include "mlir/IR/ValueRange.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallVector.h"
#include <cstdint>
#include "MergeMessages.hpp"
#include "MergeScheduleKeys.hpp"
namespace onnx_mlir::spatial {
struct ProjectedFragmentLayout {
mlir::RankedTensorType fragmentType;
llvm::SmallVector<int64_t, 4> fragmentShape;
unsigned fragmentsPerLogicalSlot = 1;
unsigned payloadFragmentCount = 1;
llvm::SmallVector<int64_t, 4> loopLowerBounds;
llvm::SmallVector<int64_t, 4> loopSteps;
llvm::SmallVector<int64_t, 4> loopTripCounts;
};
struct StaticProjectedLoopInfo {
mlir::BlockArgument iv;
int64_t lowerBound = 0;
int64_t step = 1;
int64_t tripCount = 1;
};
struct ProjectedTransferDescriptor {
ProjectedBatchInputKey inputKey;
mlir::Operation* extractOp = nullptr;
ProjectedFragmentLayout layout;
mlir::RankedTensorType payloadType;
llvm::SmallVector<llvm::SmallVector<int64_t, 4>, 16> fragmentOffsets;
llvm::SmallVector<llvm::SmallVector<int64_t, 16>, 4> fragmentOffsetsByDim;
};
struct ProjectedExtractReplacement {
mlir::Value payload;
ProjectedFragmentLayout layout;
};
struct ProjectedInputTransferFragment {
ProducerKey producer;
llvm::SmallVector<int64_t, 4> fragmentOffsets;
unsigned targetLane = 0;
unsigned ordinal = 0;
int64_t channelId = 0;
int32_t sourceCoreId = 0;
int32_t targetCoreId = 0;
bool sendEmitted = false;
};
struct ProjectedInputTransferPlan {
ProjectedBatchInputKey inputKey;
mlir::Operation* extractOp = nullptr;
ProjectedFragmentLayout layout;
llvm::SmallVector<ProjectedInputTransferFragment, 8> fragments;
};
struct PendingProjectedHostOutputFragment {
mlir::Value originalOutput;
ClassId sourceClass = 0;
ProducerKey producerKey;
unsigned publicationResultIndex = 0;
int64_t sourceFragmentOrdinal = 0;
int64_t sourceElementOffset = 0;
llvm::SmallVector<int64_t, 4> offsets;
llvm::SmallVector<int64_t, 4> sizes;
llvm::SmallVector<int64_t, 4> strides;
uint32_t sourceLane = 0;
mlir::Location loc;
};
struct AffineProjectedInputSliceMatch {
mlir::tensor::ExtractSliceOp extract;
mlir::RankedTensorType sourceType;
mlir::RankedTensorType fragmentType;
llvm::SmallVector<int64_t, 4> fragmentShape;
llvm::SmallVector<mlir::OpFoldResult, 4> offsets;
llvm::SmallVector<StaticProjectedLoopInfo, 4> loops;
};
unsigned getProjectedFragmentsPerLogicalSlot(llvm::ArrayRef<int64_t> loopTripCounts);
mlir::LogicalResult verifyProjectedFragmentLayout(mlir::Operation* anchor, const ProjectedFragmentLayout& layout);
mlir::FailureOr<mlir::RankedTensorType>
getProjectedPayloadType(mlir::Operation* anchor, mlir::RankedTensorType fragmentType, unsigned payloadFragmentCount);
llvm::SmallVector<llvm::SmallVector<int64_t, 16>, 4>
buildProjectedFragmentOffsetsByDim(llvm::ArrayRef<llvm::SmallVector<int64_t, 4>> fragmentOffsets, size_t rank);
mlir::LogicalResult verifyProjectedTransferDescriptor(mlir::Operation* anchor,
const ProjectedTransferDescriptor& descriptor);
mlir::LogicalResult verifyProjectedSendDescriptor(mlir::Operation* anchor,
const ProjectedTransferDescriptor& descriptor,
const MessageVector& messages);
mlir::LogicalResult finalizeProjectedTransferDescriptor(mlir::Operation* anchor,
ProjectedTransferDescriptor& descriptor);
} // namespace onnx_mlir::spatial
@@ -106,10 +106,11 @@ static std::optional<uint32_t> getConstantExtractLane(tensor::ExtractSliceOp ext
static std::optional<ProducerValueRef> getResultfulBatchProducerValueRef(SpatComputeBatch batch,
const ComputeInstance* consumerInstance) {
if (!consumerInstance)
return std::nullopt;
if (!isa<SpatComputeBatch>(consumerInstance->op))
return std::nullopt;
if (!consumerInstance || !isa<SpatComputeBatch>(consumerInstance->op))
return ProducerValueRef {
{batch.getOperation(), 0, static_cast<uint32_t>(batch.getLaneCount())},
0
};
if (consumerInstance->laneStart + consumerInstance->laneCount > static_cast<uint32_t>(batch.getLaneCount()))
return std::nullopt;
return ProducerValueRef {
@@ -0,0 +1,832 @@
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/IR/AsmState.h"
#include "mlir/IR/IRMapping.h"
#include "mlir/Pass/Pass.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/raw_os_ostream.h"
#include "llvm/Support/raw_ostream.h"
#include <fstream>
#include <optional>
#include <string>
#include <vector>
#include "ComputeInstanceUtils.hpp"
#include "MergeSchedulingAnalysis.hpp"
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
#include "src/Accelerators/PIM/Common/Support/Diagnostics.hpp"
#include "src/Accelerators/PIM/Common/Support/DebugDump.hpp"
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp"
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
#include "src/Accelerators/PIM/Pass/PIMPasses.h"
using namespace mlir;
namespace onnx_mlir {
namespace spatial {
namespace {
struct ProducerValueKey {
ComputeInstance instance;
size_t resultIndex = 0;
bool operator==(const ProducerValueKey& other) const {
return instance == other.instance && resultIndex == other.resultIndex;
}
};
struct GraphComputeBlockKey {
Operation* op = nullptr;
uint32_t laneStart = 0;
uint32_t laneCount = 1;
bool operator==(const GraphComputeBlockKey& other) const {
return op == other.op && laneStart == other.laneStart && laneCount == other.laneCount;
}
};
struct ScheduledMaterializationRecord {
Operation* scheduledOp = nullptr;
SmallVector<size_t> canonicalCpuClasses;
SmallVector<size_t> cpus;
SmallVector<GraphComputeBlockKey> computeKeys;
SmallVector<Block*> blocks;
};
} // namespace
} // namespace spatial
} // namespace onnx_mlir
namespace llvm {
template <>
struct DenseMapInfo<onnx_mlir::spatial::ProducerValueKey> {
static onnx_mlir::spatial::ProducerValueKey getEmptyKey() {
return {DenseMapInfo<onnx_mlir::spatial::ComputeInstance>::getEmptyKey(), std::numeric_limits<size_t>::max()};
}
static onnx_mlir::spatial::ProducerValueKey getTombstoneKey() {
return {DenseMapInfo<onnx_mlir::spatial::ComputeInstance>::getTombstoneKey(), std::numeric_limits<size_t>::max()};
}
static unsigned getHashValue(const onnx_mlir::spatial::ProducerValueKey& key) {
return hash_combine(DenseMapInfo<onnx_mlir::spatial::ComputeInstance>::getHashValue(key.instance), key.resultIndex);
}
static bool isEqual(const onnx_mlir::spatial::ProducerValueKey& lhs,
const onnx_mlir::spatial::ProducerValueKey& rhs) {
return lhs == rhs;
}
};
template <>
struct DenseMapInfo<onnx_mlir::spatial::GraphComputeBlockKey> {
static onnx_mlir::spatial::GraphComputeBlockKey getEmptyKey() {
return {DenseMapInfo<mlir::Operation*>::getEmptyKey(), UINT32_MAX, UINT32_MAX};
}
static onnx_mlir::spatial::GraphComputeBlockKey getTombstoneKey() {
return {DenseMapInfo<mlir::Operation*>::getTombstoneKey(), UINT32_MAX, UINT32_MAX - 1};
}
static unsigned getHashValue(const onnx_mlir::spatial::GraphComputeBlockKey& key) {
return hash_combine(key.op, key.laneStart, key.laneCount);
}
static bool isEqual(const onnx_mlir::spatial::GraphComputeBlockKey& lhs,
const onnx_mlir::spatial::GraphComputeBlockKey& rhs) {
return lhs == rhs;
}
};
} // namespace llvm
namespace onnx_mlir {
namespace spatial {
namespace {
struct ScalarClass {
size_t cpu = 0;
SmallVector<size_t> cpus;
SmallVector<ComputeInstance> instances;
SmallVector<Value> weights;
SmallVector<Value> inputs;
SmallVector<Type> resultTypes;
};
struct CompactCpuIndexMap {
DenseMap<size_t, size_t> rawToCompact;
size_t lookup(size_t rawCpu) const {
auto it = rawToCompact.find(rawCpu);
assert(it != rawToCompact.end() && "missing compact cpu index");
return it->second;
}
};
static ComputeInstance getWholeBatchInstance(SpatComputeBatch batch) {
return {batch.getOperation(), 0, static_cast<uint32_t>(batch.getLaneCount())};
}
static GraphComputeBlockKey getGraphComputeBlockKey(const ComputeInstance& instance) {
return {instance.op, instance.laneStart, instance.laneCount};
}
static CompactCpuIndexMap buildCompactCpuIndexMap(const MergeScheduleResult& schedule) {
SmallVector<size_t> rawCpus;
auto append = [&](size_t rawCpu) {
if (!llvm::is_contained(rawCpus, rawCpu))
rawCpus.push_back(rawCpu);
};
for (const auto& entry : schedule.computeToCpuMap)
append(entry.second);
for (const auto& entry : schedule.equivalentClass) {
append(entry.first);
for (size_t rawCpu : entry.second)
append(rawCpu);
}
for (const auto& entry : schedule.cpuToLastComputeMap)
append(entry.first);
llvm::sort(rawCpus);
CompactCpuIndexMap compactMap;
for (auto [index, rawCpu] : llvm::enumerate(rawCpus))
compactMap.rawToCompact[rawCpu] = index;
return compactMap;
}
static SmallVector<size_t> getEquivalentCpus(size_t cpu, const MergeScheduleResult& schedule) {
llvm::SmallDenseSet<size_t, 8> seen;
SmallVector<size_t> cpus;
auto append = [&](size_t value) {
if (seen.insert(value).second)
cpus.push_back(value);
};
append(cpu);
if (auto it = schedule.equivalentClass.find(cpu); it != schedule.equivalentClass.end())
for (size_t equivalentCpu : it->second)
append(equivalentCpu);
llvm::sort(cpus);
return cpus;
}
static size_t getCanonicalCpuClass(size_t cpu, const MergeScheduleResult& schedule) {
SmallVector<size_t> cpus = getEquivalentCpus(cpu, schedule);
return cpus.empty() ? cpu : cpus.front();
}
static Value getProducerOutput(const ProducerValueRef& ref) {
auto outputs = getComputeInstanceOutputValues(ref.instance);
assert(ref.resultIndex < outputs.size() && "producer result index out of range");
return outputs[ref.resultIndex];
}
static SmallVector<size_t> getCanonicalCpuClassesForBatch(SpatComputeBatch batch, const MergeScheduleResult& schedule) {
llvm::SmallDenseSet<size_t, 4> seen;
SmallVector<size_t> classes;
size_t chunkCount = getBatchChunkTargetCount(batch.getLaneCount());
for (size_t chunkIndex = 0; chunkIndex < chunkCount; ++chunkIndex) {
ComputeInstance chunk = getBatchChunkForIndex(batch, chunkIndex);
auto cpuIt = schedule.computeToCpuMap.find(chunk);
if (cpuIt == schedule.computeToCpuMap.end())
continue;
size_t cpuClass = getCanonicalCpuClass(cpuIt->second, schedule);
if (seen.insert(cpuClass).second)
classes.push_back(cpuClass);
}
llvm::sort(classes);
return classes;
}
static SmallVector<size_t> remapCpuList(ArrayRef<size_t> rawCpus, const CompactCpuIndexMap& compactCpuMap) {
SmallVector<size_t> compactCpus;
compactCpus.reserve(rawCpus.size());
for (size_t rawCpu : rawCpus)
compactCpus.push_back(compactCpuMap.lookup(rawCpu));
llvm::sort(compactCpus);
return compactCpus;
}
static std::string getInstanceName(const ComputeInstance& instance) {
return llvm::formatv("{0}[lanes={1}:{2}]",
instance.op->getName().getStringRef(),
instance.laneStart,
instance.laneStart + instance.laneCount)
.str();
}
static void appendUnique(SmallVectorImpl<Value>& values, Value value) {
if (!llvm::is_contained(values, value))
values.push_back(value);
}
static Value getBlockOperand(Block& block, ValueRange operands, Value value, unsigned firstArgument = 0) {
auto it = llvm::find(operands, value);
assert(it != operands.end() && "missing scheduled operand");
return block.getArgument(firstArgument + std::distance(operands.begin(), it));
}
static void appendComputeBlockArguments(SmallVectorImpl<Type>& argTypes,
SmallVectorImpl<Location>& argLocs,
ValueRange weights,
ValueRange inputs,
ArrayRef<ProducerValueKey> carriedKeys,
Location loc) {
for (Value weight : weights)
argTypes.push_back(weight.getType());
for (Value input : inputs)
argTypes.push_back(input.getType());
for (ProducerValueKey key : carriedKeys) {
auto outputs = getComputeInstanceOutputValues(key.instance);
assert(key.resultIndex < outputs.size() && "missing carried result");
argTypes.push_back(outputs[key.resultIndex].getType());
}
argLocs.append(argTypes.size(), loc);
}
static Block* createScheduledComputeBlock(PatternRewriter& rewriter,
SpatScheduledCompute scheduled,
ArrayRef<ProducerValueKey> carriedKeys,
Location loc) {
SmallVector<Type> argTypes;
SmallVector<Location> argLocs;
appendComputeBlockArguments(argTypes, argLocs, scheduled.getWeights(), scheduled.getInputs(), carriedKeys, loc);
return rewriter.createBlock(&scheduled.getBody(), scheduled.getBody().end(), TypeRange(argTypes), argLocs);
}
static void appendBlockYieldBaseAndCarriedOperands(Block& block,
unsigned baseArgCount,
size_t carriedCount,
SmallVectorImpl<Value>& operands) {
for (unsigned index = 0; index < baseArgCount; ++index)
operands.push_back(block.getArgument(index));
for (size_t index = 0; index < carriedCount; ++index)
operands.push_back(block.getArgument(baseArgCount + index));
}
static void createBlockYield(PatternRewriter& rewriter, Location loc, ValueRange outputs, Block* next = nullptr) {
OperationState state(loc, SpatBlockYieldOp::getOperationName());
state.addOperands(outputs);
if (next)
state.addSuccessors(next);
rewriter.create(state);
}
static std::string formatValueLabel(Value value, AsmState& asmState) {
std::string storage;
llvm::raw_string_ostream os(storage);
value.printAsOperand(os, asmState);
return storage;
}
static std::string formatBlockLabel(Block* block, AsmState& asmState) {
std::string storage;
llvm::raw_string_ostream os(storage);
block->printAsOperand(os, asmState);
return storage;
}
static std::string formatOperationLabel(Operation* op, AsmState& asmState) {
if (op->getNumResults() == 0)
return op->getName().getStringRef().str();
std::string storage;
llvm::raw_string_ostream os(storage);
llvm::interleaveComma(op->getResults(), os, [&](Value result) { os << formatValueLabel(result, asmState); });
return os.str();
}
static std::string formatGraphComputeBlockKey(const GraphComputeBlockKey& key, AsmState& asmState) {
return llvm::formatv("{0} {1} lanes [{2}:{3}]",
formatOperationLabel(key.op, asmState),
key.op->getName().getStringRef(),
key.laneStart,
key.laneStart + key.laneCount)
.str();
}
static size_t countPeftEquivalenceClasses(const MergeScheduleResult& schedule) {
llvm::SmallDenseSet<size_t, 16> classes;
for (const ComputeInstance& instance : schedule.dominanceOrderCompute) {
auto cpuIt = schedule.computeToCpuMap.find(instance);
if (cpuIt == schedule.computeToCpuMap.end())
continue;
classes.insert(getCanonicalCpuClass(cpuIt->second, schedule));
}
return classes.size();
}
static SmallVector<GraphComputeBlockKey> collectExpectedGraphComputeBlockKeys(func::FuncOp funcOp) {
SmallVector<GraphComputeBlockKey> keys;
for (Operation& op : funcOp.getOps()) {
if (auto compute = dyn_cast<SpatGraphCompute>(&op))
keys.push_back(getGraphComputeBlockKey({compute.getOperation(), 0, 1}));
else if (auto batch = dyn_cast<SpatGraphComputeBatch>(&op))
keys.push_back(getGraphComputeBlockKey(getWholeBatchInstance(batch)));
}
return keys;
}
static LogicalResult verifyMaterializedScheduleMapping(
func::FuncOp funcOp,
const MergeScheduleResult& schedule,
const DenseMap<GraphComputeBlockKey, Block*>& graphComputeToBlockMap,
ArrayRef<ScheduledMaterializationRecord> materializedSchedules) {
pim::CappedDiagnosticReporter diagnostics;
size_t expectedClassCount = countPeftEquivalenceClasses(schedule);
if (expectedClassCount != materializedSchedules.size()) {
diagnostics.report(funcOp.getOperation(), [&](Operation* illegalOp) {
illegalOp->emitOpError() << "phase-check expected " << expectedClassCount
<< " PEFT equivalence classes but materialized " << materializedSchedules.size()
<< " scheduled computes";
});
}
for (const ScheduledMaterializationRecord& record : materializedSchedules) {
if (record.canonicalCpuClasses.empty()) {
diagnostics.report(record.scheduledOp, [&](Operation* illegalOp) {
illegalOp->emitOpError("phase-check scheduled compute is missing an owning PEFT equivalence class");
});
continue;
}
if (record.canonicalCpuClasses.size() > 1) {
diagnostics.report(record.scheduledOp, [&](Operation* illegalOp) {
std::string classes;
llvm::raw_string_ostream os(classes);
llvm::interleaveComma(record.canonicalCpuClasses, os, [&](size_t cpuClass) { os << cpuClass; });
illegalOp->emitOpError("phase-check scheduled compute spans multiple PEFT equivalence classes")
<< " (classes " << os.str() << ")";
});
}
}
for (GraphComputeBlockKey key : collectExpectedGraphComputeBlockKeys(funcOp)) {
if (graphComputeToBlockMap.count(key))
continue;
diagnostics.report(key.op, [&](Operation* illegalOp) {
illegalOp->emitOpError() << "phase-check graph compute is missing a scheduled MLIR block mapping for lanes ["
<< key.laneStart << ":" << (key.laneStart + key.laneCount) << "]";
});
}
for (const auto& entry : graphComputeToBlockMap) {
Block* block = entry.second;
if (!block || !isa<SpatScheduledCompute, SpatScheduledComputeBatch>(block->getParentOp())) {
diagnostics.report(entry.first.op, [&](Operation* illegalOp) {
illegalOp->emitOpError("phase-check graph compute block mapping does not target a scheduled compute block");
});
}
}
diagnostics.emitSuppressedSummary(funcOp, "scheduled Spatial materialization verification failed");
return success(!diagnostics.hasFailure());
}
static void dumpPeftMaterializationReport(func::FuncOp funcOp,
const MergeScheduleResult& schedule,
const CompactCpuIndexMap& compactCpuMap,
ArrayRef<ScheduledMaterializationRecord> materializedSchedules) {
std::fstream file = openDialectDumpFileWithExtension("spatial1_schedule_materialization", "/reports", "txt");
if (!file.is_open())
return;
llvm::raw_os_ostream os(file);
AsmState asmState(funcOp);
llvm::MapVector<size_t, SmallVector<size_t>> classToCpus;
DenseMap<size_t, SmallVector<ComputeInstance>> cpuToInstances;
for (const ComputeInstance& instance : schedule.dominanceOrderCompute) {
auto cpuIt = schedule.computeToCpuMap.find(instance);
if (cpuIt == schedule.computeToCpuMap.end())
continue;
size_t cpu = compactCpuMap.lookup(cpuIt->second);
size_t cpuClass = compactCpuMap.lookup(getCanonicalCpuClass(cpuIt->second, schedule));
if (!llvm::is_contained(classToCpus[cpuClass], cpu))
classToCpus[cpuClass].push_back(cpu);
cpuToInstances[cpu].push_back(instance);
}
os << "PEFT Schedule\n";
os << "=============\n";
os << "equivalence classes: " << classToCpus.size() << "\n\n";
for (auto& entry : classToCpus) {
size_t cpuClass = entry.first;
llvm::sort(entry.second);
os << "class " << cpuClass << " cpus [";
llvm::interleaveComma(entry.second, os, [&](size_t cpu) { os << cpu; });
os << "]\n";
for (size_t cpu : entry.second) {
os << " cpu " << cpu << ":\n";
auto& instances = cpuToInstances[cpu];
llvm::sort(instances, [&](const ComputeInstance& lhs, const ComputeInstance& rhs) {
return schedule.computeToCpuSlotMap.lookup(lhs) < schedule.computeToCpuSlotMap.lookup(rhs);
});
for (const ComputeInstance& instance : instances) {
os << " slot " << schedule.computeToCpuSlotMap.lookup(instance) << ": "
<< formatGraphComputeBlockKey(getGraphComputeBlockKey(instance), asmState) << "\n";
}
}
os << "\n";
}
os << "Materialized Scheduled Computes\n";
os << "===============================\n";
os << "scheduled ops: " << materializedSchedules.size() << "\n\n";
for (const ScheduledMaterializationRecord& record : materializedSchedules) {
os << formatOperationLabel(record.scheduledOp, asmState) << " " << record.scheduledOp->getName().getStringRef();
os << " classes [";
llvm::interleaveComma(record.canonicalCpuClasses, os, [&](size_t cpuClass) { os << cpuClass; });
os << "] cpus [";
llvm::interleaveComma(record.cpus, os, [&](size_t cpu) { os << cpu; });
os << "]\n";
for (auto [key, block] : llvm::zip(record.computeKeys, record.blocks))
os << " " << formatGraphComputeBlockKey(key, asmState) << " -> " << formatBlockLabel(block, asmState) << "\n";
os << "\n";
}
}
static LogicalResult createDeferredCommunication(PatternRewriter& rewriter,
Location loc,
Value consumerInput,
Value source,
const ProducerValueRef& producer,
const ComputeInstance& consumer,
const MergeScheduleResult& schedule,
const CompactCpuIndexMap& compactCpuMap,
uint64_t exchangeId,
Value& result) {
auto sourceCpu = compactCpuMap.lookup(schedule.computeToCpuMap.lookup(producer.instance));
auto targetCpu = compactCpuMap.lookup(schedule.computeToCpuMap.lookup(consumer));
auto transfer = SpatDeferredCommunicationOp::create(
rewriter,
loc,
consumerInput.getType(),
ValueRange {source},
rewriter.getStringAttr(("x" + std::to_string(exchangeId)).c_str()),
rewriter.getStringAttr(getInstanceName(producer.instance)),
rewriter.getStringAttr(getInstanceName(consumer)),
rewriter.getI64IntegerAttr(static_cast<int64_t>(sourceCpu)),
rewriter.getI64IntegerAttr(static_cast<int64_t>(targetCpu)),
rewriter.getI64IntegerAttr(static_cast<int64_t>(sourceCpu)),
rewriter.getI64IntegerAttr(static_cast<int64_t>(targetCpu)),
rewriter.getI64IntegerAttr(producer.instance.laneStart),
rewriter.getI64IntegerAttr(consumer.laneStart),
rewriter.getStringAttr(consumerInput.getDefiningOp<tensor::ExtractSliceOp>() ? "projected" : "ordinary"),
rewriter.getI64IntegerAttr(static_cast<int64_t>(producer.resultIndex)),
rewriter.getDenseI64ArrayAttr({}),
rewriter.getI64IntegerAttr(-1));
Block* block = rewriter.createBlock(&transfer.getBody(), transfer.getBody().end(), source.getType(), loc);
rewriter.setInsertionPointToStart(block);
Value yielded = block->getArgument(0);
if (yielded.getType() != consumerInput.getType()) {
auto slice = consumerInput.getDefiningOp<tensor::ExtractSliceOp>();
if (!slice || slice.getSource().getType() != source.getType())
return transfer.emitOpError("cannot derive deferred communication payload type from recorded source");
IRMapping mapper;
mapper.map(slice.getSource(), yielded);
auto cloned = cast<tensor::ExtractSliceOp>(rewriter.clone(*slice, mapper));
yielded = cloned.getResult();
}
SpatYieldOp::create(rewriter, loc, yielded);
result = transfer.getOutput();
rewriter.setInsertionPointAfter(transfer);
return success();
}
static LogicalResult mapComputeInputs(PatternRewriter& rewriter,
Block& block,
SpatCompute compute,
const ComputeInstance& instance,
const ScalarClass& klass,
const MergeScheduleResult& schedule,
const CompactCpuIndexMap& compactCpuMap,
ValueRange scheduledWeights,
ValueRange scheduledInputs,
IRMapping& mapper,
uint64_t& exchangeId) {
for (auto [index, weight] : llvm::enumerate(compute.getWeights()))
mapper.map(*compute.getWeightArgument(index), getBlockOperand(block, scheduledWeights, weight));
for (auto [index, input] : llvm::enumerate(compute.getInputs())) {
Value mapped;
std::optional<ProducerValueRef> producer = getProducerValueRef(input, &instance);
if (producer) {
Value source = getProducerOutput(*producer);
if (failed(createDeferredCommunication(
rewriter, input.getLoc(), input, source, *producer, instance, schedule, compactCpuMap, exchangeId++, mapped)))
return failure();
}
else {
mapped = getBlockOperand(block, scheduledInputs, input, scheduledWeights.size());
}
mapper.map(*compute.getInputArgument(index), mapped);
}
return success();
}
static LogicalResult mapBatchInputs(PatternRewriter& rewriter,
Block& block,
SpatComputeBatch batch,
const MergeScheduleResult& schedule,
const CompactCpuIndexMap& compactCpuMap,
ValueRange scheduledWeights,
ValueRange scheduledInputs,
IRMapping& mapper,
uint64_t& exchangeId) {
mapper.map(*batch.getLaneArgument(), block.getArgument(0));
unsigned firstWeightArg = 1;
unsigned firstInputArg = firstWeightArg + scheduledWeights.size();
for (auto [index, weight] : llvm::enumerate(batch.getWeights()))
mapper.map(*batch.getWeightArgument(index), block.getArgument(firstWeightArg + index));
ComputeInstance consumer {batch.getOperation(), 0, static_cast<uint32_t>(batch.getLaneCount())};
for (auto [index, input] : llvm::enumerate(batch.getInputs())) {
Value mapped;
std::optional<ProducerValueRef> producer = getProducerValueRef(input, &consumer);
if (producer) {
Value source = getProducerOutput(*producer);
if (failed(createDeferredCommunication(
rewriter, input.getLoc(), input, source, *producer, consumer, schedule, compactCpuMap, exchangeId++, mapped)))
return failure();
}
else {
auto it = llvm::find(scheduledInputs, input);
if (it == scheduledInputs.end())
return batch.emitOpError("host batch input is missing from scheduled batch operands");
mapped = block.getArgument(firstInputArg + std::distance(scheduledInputs.begin(), it));
}
mapper.map(*batch.getInputArgument(index), mapped);
}
unsigned firstOutputArg = firstInputArg + scheduledInputs.size();
for (unsigned index = 0; index < batch.getNumResults(); ++index)
mapper.map(*batch.getOutputArgument(index), block.getArgument(firstOutputArg + index));
return success();
}
static void cloneComputeBody(PatternRewriter& rewriter,
Block& source,
IRMapping& mapper,
SmallVectorImpl<Value>& yieldedValues) {
for (Operation& op : source.without_terminator())
rewriter.clone(op, mapper);
auto yield = cast<SpatYieldOp>(source.getTerminator());
for (Value output : yield.getOutputs())
yieldedValues.push_back(mapper.lookup(output));
}
struct MergeComputeNodesPass final : PassWrapper<MergeComputeNodesPass, OperationPass<ModuleOp>> {
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(MergeComputeNodesPass)
StringRef getArgument() const override { return "pim-merge-compute-nodes"; }
StringRef getDescription() const override {
return "Materialize scheduled Spatial compute with deferred communication placeholders.";
}
void runOnOperation() override {
ModuleOp moduleOp = getOperation();
auto entryFunc = getPimEntryFunc(moduleOp);
if (failed(entryFunc)) {
moduleOp.emitError("failed to locate the PIM entry function during MergeComputeNodes");
signalPassFailure();
return;
}
func::FuncOp funcOp = *entryFunc;
MergeScheduleResult schedule = MergeSchedulingAnalysis(funcOp).getResult();
CompactCpuIndexMap compactCpuMap = buildCompactCpuIndexMap(schedule);
llvm::MapVector<size_t, ScalarClass> scalarClasses;
SmallVector<SpatComputeBatch> batchOps;
for (const ComputeInstance& instance : schedule.dominanceOrderCompute) {
if (isa<SpatCompute>(instance.op)) {
size_t cpu = schedule.computeToCpuMap.lookup(instance);
size_t cpuClass = getCanonicalCpuClass(cpu, schedule);
auto& klass = scalarClasses[cpuClass];
klass.cpu = cpuClass;
if (klass.cpus.empty())
klass.cpus = getEquivalentCpus(cpu, schedule);
klass.instances.push_back(instance);
}
else {
auto batch = cast<SpatComputeBatch>(instance.op);
if (!llvm::is_contained(batchOps, batch))
batchOps.push_back(batch);
}
}
for (auto& entry : scalarClasses) {
llvm::sort(entry.second.instances, [&](const ComputeInstance& lhs, const ComputeInstance& rhs) {
return schedule.computeToCpuSlotMap.lookup(lhs) < schedule.computeToCpuSlotMap.lookup(rhs);
});
}
PatternRewriter rewriter(moduleOp.getContext());
Operation* insertionPoint = funcOp.getBody().front().getTerminator();
DenseMap<ScalarClass*, SpatScheduledCompute> scalarOps;
DenseMap<Operation*, SpatScheduledComputeBatch> batchReplacements;
DenseMap<Operation*, size_t> scheduledOpToRecordIndex;
DenseMap<GraphComputeBlockKey, Block*> graphComputeToBlockMap;
std::vector<ScheduledMaterializationRecord> materializedSchedules;
for (auto& entry : scalarClasses) {
ScalarClass& klass = entry.second;
for (const ComputeInstance& instance : klass.instances) {
auto compute = cast<SpatCompute>(instance.op);
llvm::append_range(klass.resultTypes, compute.getResultTypes());
for (Value weight : compute.getWeights())
appendUnique(klass.weights, weight);
for (Value input : compute.getInputs()) {
std::optional<ProducerValueRef> producer = getProducerValueRef(input, &instance);
if (!producer)
appendUnique(klass.inputs, input);
}
}
rewriter.setInsertionPoint(insertionPoint);
auto scheduled = SpatScheduledCompute::create(
rewriter, klass.instances.front().op->getLoc(), TypeRange(klass.resultTypes), klass.weights, klass.inputs);
scheduled->setAttr(
kCoreIdAttrName, rewriter.getI32IntegerAttr(static_cast<int32_t>(compactCpuMap.lookup(klass.cpu))));
SmallVector<int64_t> equivalentCpus;
for (size_t cpu : klass.cpus)
equivalentCpus.push_back(static_cast<int64_t>(compactCpuMap.lookup(cpu)));
scheduled->setAttr("scheduled.equivalent_cpus", rewriter.getDenseI64ArrayAttr(equivalentCpus));
SmallVector<Attribute> sources;
SmallVector<int64_t> lanes;
for (const ComputeInstance& instance : klass.instances) {
sources.push_back(rewriter.getStringAttr(getInstanceName(instance)));
lanes.push_back(instance.laneStart);
lanes.push_back(instance.laneCount);
}
scheduled->setAttr("scheduled.sources", rewriter.getArrayAttr(sources));
scheduled->setAttr("scheduled.lane_ranges", rewriter.getDenseI64ArrayAttr(lanes));
scalarOps[&klass] = scheduled;
ScheduledMaterializationRecord record;
record.scheduledOp = scheduled.getOperation();
record.canonicalCpuClasses.push_back(compactCpuMap.lookup(klass.cpu));
record.cpus = remapCpuList(klass.cpus, compactCpuMap);
scheduledOpToRecordIndex[scheduled.getOperation()] = materializedSchedules.size();
materializedSchedules.push_back(std::move(record));
}
for (SpatComputeBatch batch : batchOps) {
SmallVector<Value> inputs;
ComputeInstance wholeBatch = getWholeBatchInstance(batch);
for (Value input : batch.getInputs())
if (!getProducerValueRef(input, &wholeBatch))
appendUnique(inputs, input);
rewriter.setInsertionPoint(insertionPoint);
auto scheduled = SpatScheduledComputeBatch::create(rewriter,
batch.getLoc(),
batch.getResultTypes(),
rewriter.getI32IntegerAttr(batch.getLaneCount()),
batch.getWeights(),
inputs);
SmallVector<int32_t> coreIds;
uint32_t laneCount = batch.getLaneCount();
coreIds.reserve(laneCount);
for (uint32_t lane = 0; lane < laneCount; ++lane) {
ComputeInstance chunk = getBatchChunkForLane(batch, lane);
coreIds.push_back(static_cast<int32_t>(compactCpuMap.lookup(schedule.computeToCpuMap.lookup(chunk))));
}
scheduled->setAttr(kCoreIdsAttrName, rewriter.getDenseI32ArrayAttr(coreIds));
scheduled->setAttr("scheduled.sources", rewriter.getArrayAttr({rewriter.getStringAttr(getInstanceName(wholeBatch))}));
batchReplacements[batch.getOperation()] = scheduled;
ScheduledMaterializationRecord record;
record.scheduledOp = scheduled.getOperation();
record.canonicalCpuClasses = remapCpuList(getCanonicalCpuClassesForBatch(batch, schedule), compactCpuMap);
for (int32_t coreId : coreIds)
if (!llvm::is_contained(record.cpus, static_cast<size_t>(coreId)))
record.cpus.push_back(static_cast<size_t>(coreId));
scheduledOpToRecordIndex[scheduled.getOperation()] = materializedSchedules.size();
materializedSchedules.push_back(std::move(record));
}
uint64_t exchangeId = 0;
for (auto& entry : scalarClasses) {
ScalarClass& klass = entry.second;
SpatScheduledCompute scheduled = scalarOps.lookup(&klass);
SmallVector<ProducerValueKey> carriedKeys;
Block* block = nullptr;
for (auto [ordinal, instance] : llvm::enumerate(klass.instances)) {
auto compute = cast<SpatCompute>(instance.op);
if (!block)
block = createScheduledComputeBlock(rewriter, scheduled, carriedKeys, compute.getLoc());
GraphComputeBlockKey key = getGraphComputeBlockKey(instance);
graphComputeToBlockMap[key] = block;
auto& record = materializedSchedules[scheduledOpToRecordIndex.lookup(scheduled.getOperation())];
record.computeKeys.push_back(key);
record.blocks.push_back(block);
rewriter.setInsertionPointToStart(block);
IRMapping mapper;
if (failed(mapComputeInputs(rewriter,
*block,
compute,
instance,
klass,
schedule,
compactCpuMap,
scheduled.getWeights(),
scheduled.getInputs(),
mapper,
exchangeId))) {
signalPassFailure();
return;
}
SmallVector<Value> yieldedValues;
cloneComputeBody(rewriter, compute.getBody().front(), mapper, yieldedValues);
SmallVector<ProducerValueKey> currentKeys;
for (auto [index, value] : llvm::enumerate(yieldedValues)) {
ProducerValueKey key {instance, index};
currentKeys.push_back(key);
}
unsigned baseArgCount = scheduled.getWeights().size() + scheduled.getInputs().size();
SmallVector<Value> blockYieldOperands;
bool hasNextBlock = ordinal + 1 < klass.instances.size();
if (hasNextBlock) {
SmallVector<ProducerValueKey> nextCarriedKeys(carriedKeys);
llvm::append_range(nextCarriedKeys, currentKeys);
Block* nextBlock = createScheduledComputeBlock(rewriter, scheduled, nextCarriedKeys, compute.getLoc());
appendBlockYieldBaseAndCarriedOperands(*block, baseArgCount, carriedKeys.size(), blockYieldOperands);
llvm::append_range(blockYieldOperands, yieldedValues);
rewriter.setInsertionPointToEnd(block);
createBlockYield(rewriter, compute.getLoc(), blockYieldOperands, nextBlock);
carriedKeys = std::move(nextCarriedKeys);
block = nextBlock;
}
else {
for (size_t index = 0; index < carriedKeys.size(); ++index)
blockYieldOperands.push_back(block->getArgument(baseArgCount + index));
llvm::append_range(blockYieldOperands, yieldedValues);
rewriter.setInsertionPointToEnd(block);
createBlockYield(rewriter, compute.getLoc(), blockYieldOperands);
}
}
}
for (SpatComputeBatch batch : batchOps) {
SpatScheduledComputeBatch scheduled = batchReplacements.lookup(batch.getOperation());
SmallVector<Type> blockArgTypes {rewriter.getIndexType()};
SmallVector<Location> blockArgLocs {batch.getLoc()};
for (Value weight : scheduled.getWeights()) {
blockArgTypes.push_back(weight.getType());
blockArgLocs.push_back(weight.getLoc());
}
for (Value input : scheduled.getInputs()) {
blockArgTypes.push_back(input.getType());
blockArgLocs.push_back(input.getLoc());
}
for (Type resultType : scheduled.getResultTypes()) {
blockArgTypes.push_back(resultType);
blockArgLocs.push_back(batch.getLoc());
}
Block* block = rewriter.createBlock(
&scheduled.getBody(), scheduled.getBody().end(), TypeRange(blockArgTypes), blockArgLocs);
GraphComputeBlockKey key = getGraphComputeBlockKey(getWholeBatchInstance(batch));
graphComputeToBlockMap[key] = block;
auto& record = materializedSchedules[scheduledOpToRecordIndex.lookup(scheduled.getOperation())];
record.computeKeys.push_back(key);
record.blocks.push_back(block);
rewriter.setInsertionPointToStart(block);
IRMapping mapper;
if (failed(mapBatchInputs(rewriter,
*block,
batch,
schedule,
compactCpuMap,
scheduled.getWeights(),
scheduled.getInputs(),
mapper,
exchangeId))) {
signalPassFailure();
return;
}
for (Operation& op : batch.getBody().front())
rewriter.clone(op, mapper);
}
dumpPeftMaterializationReport(funcOp, schedule, compactCpuMap, materializedSchedules);
if (failed(verifyMaterializedScheduleMapping(funcOp, schedule, graphComputeToBlockMap, materializedSchedules))) {
moduleOp.emitError("scheduled Spatial materialization mapping verification failed");
signalPassFailure();
return;
}
if (failed(verifyScheduledSpatialInvariants(funcOp))) {
moduleOp.emitError("scheduled Spatial phase 1 verification failed");
signalPassFailure();
return;
}
dumpModule(moduleOp, "spatial1_scheduled_no_comm", /*assumeVerified=*/true);
moduleOp.emitError("MergeComputeNodes stopped after spatial1_scheduled_no_comm; "
"Phase 2 communication realization is not implemented");
signalPassFailure();
}
};
} // namespace
} // namespace spatial
std::unique_ptr<Pass> createMergeComputeNodesPass() { return std::make_unique<spatial::MergeComputeNodesPass>(); }
} // namespace onnx_mlir
@@ -1,728 +0,0 @@
#include "SpatialDataflowCsvExporter.hpp"
#include "mlir/Dialect/Affine/IR/AffineOps.h"
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/IR/AsmState.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/Value.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_ostream.h"
#include <cstdint>
#include <fstream>
#include <optional>
#include <string>
#include <utility>
#include "src/Accelerators/PIM/Common/IR/AffineUtils.hpp"
#include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.hpp"
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
#include "src/Accelerators/PIM/Common/Support/DebugDump.hpp"
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
using namespace mlir;
namespace onnx_mlir {
namespace spatial {
namespace {
struct TopLevelOpInfo {
Operation* op = nullptr;
size_t opId = 0;
bool isPost = false;
std::optional<int32_t> scalarCore;
};
struct ExpandedNodeInfo {
std::string id;
std::optional<int32_t> core;
std::optional<uint32_t> lane;
};
struct ChannelSendRecord {
std::string sourceId;
std::optional<uint32_t> sourceLane;
};
enum class LogicalNodeSelector {
Scalar,
Lane,
RangeRepresentative,
};
struct ResolvedProducer {
Operation* op = nullptr;
size_t resultIndex = 0;
LogicalNodeSelector selector = LogicalNodeSelector::Scalar;
uint32_t lane = 0;
uint32_t laneStart = 0;
uint32_t laneCount = 1;
};
struct EdgeSource {
std::string id;
std::optional<uint32_t> sourceLane;
};
std::string csvEscape(StringRef field) {
bool needsQuotes = field.contains(',') || field.contains('"') || field.contains('\n') || field.contains('\r');
if (!needsQuotes)
return field.str();
std::string escaped;
escaped.reserve(field.size() + 2);
escaped.push_back('"');
for (char ch : field) {
if (ch == '"')
escaped += "\"\"";
else
escaped.push_back(ch);
}
escaped.push_back('"');
return escaped;
}
void writeCsvRow(std::fstream& file, ArrayRef<std::string> fields) {
for (size_t i = 0; i < fields.size(); ++i) {
if (i != 0)
file << ",";
file << csvEscape(fields[i]);
}
file << "\n";
}
template <typename NumberT>
std::string maybeNumber(std::optional<NumberT> value) {
if (!value)
return "";
return std::to_string(*value);
}
std::string stringifyType(Type type) {
std::string storage;
llvm::raw_string_ostream os(storage);
type.print(os);
return os.str();
}
std::string stringifyValueAsOperand(Value value, AsmState& asmState) {
std::string storage;
llvm::raw_string_ostream os(storage);
value.printAsOperand(os, asmState);
return os.str();
}
std::string stringifyResultSsaNames(Operation* op, AsmState* asmState) {
if (!asmState || op->getNumResults() == 0)
return "";
std::string storage;
llvm::raw_string_ostream os(storage);
llvm::interleave(
op->getResults(),
[&](Value result) { os << stringifyValueAsOperand(result, *asmState); },
[&]() { os << ";"; });
return os.str();
}
std::optional<uint64_t> getTypeSizeBytes(Type type) {
if (auto shapedType = dyn_cast<ShapedType>(type)) {
if (!shapedType.hasStaticShape() || !hasByteSizedElementType(shapedType.getElementType()))
return std::nullopt;
return static_cast<uint64_t>(getShapedTypeSizeInBytes(shapedType));
}
if (isa<IndexType>(type))
return static_cast<uint64_t>(getElementTypeSizeInBytes(type));
if (auto intType = dyn_cast<IntegerType>(type)) {
if (intType.getWidth() <= 0 || intType.getWidth() % 8 != 0)
return std::nullopt;
return static_cast<uint64_t>(getElementTypeSizeInBytes(type));
}
if (auto floatType = dyn_cast<FloatType>(type)) {
if (floatType.getWidth() <= 0 || floatType.getWidth() % 8 != 0)
return std::nullopt;
return static_cast<uint64_t>(getElementTypeSizeInBytes(type));
}
return std::nullopt;
}
std::string getScalarId(bool isPost, size_t opId) {
return (isPost ? "sc:" : "gc:") + std::to_string(opId);
}
std::string getBatchLaneId(bool isPost, size_t opId, uint32_t lane) {
return (isPost ? "scb:" : "gcb:") + std::to_string(opId) + ":" + std::to_string(lane);
}
template <typename ComputeOpTy, typename BatchOpTy>
bool isTopLevelRelevantCompute(Operation& op) {
return isa<ComputeOpTy, BatchOpTy>(&op);
}
template <typename ComputeOpTy, typename BatchOpTy>
FailureOr<TopLevelOpInfo> buildTopLevelOpInfo(Operation& op, bool isPost, size_t opId) {
TopLevelOpInfo info;
info.op = &op;
info.opId = opId;
info.isPost = isPost;
if constexpr (std::is_same_v<ComputeOpTy, SpatScheduledCompute>) {
if (auto compute = dyn_cast<ComputeOpTy>(&op)) {
auto coreId = getOptionalScheduledCoreId(compute, "spatial dataflow export core id");
if (failed(coreId))
return failure();
if (*coreId)
info.scalarCore = **coreId;
}
}
return info;
}
template <typename BatchOpTy>
FailureOr<SmallVector<int32_t, 8>> getBatchLaneCoreIds(BatchOpTy batch) {
if constexpr (std::is_same_v<BatchOpTy, SpatScheduledComputeBatch>) {
auto coreIds = getOptionalScheduledBatchCoreIds(batch, "spatial dataflow export core ids");
if (failed(coreIds))
return failure();
if (!*coreIds)
return SmallVector<int32_t, 8> {};
return SmallVector<int32_t, 8>((**coreIds).begin(), (**coreIds).end());
}
return SmallVector<int32_t, 8> {};
}
std::string getExpandedNodeId(const DenseMap<std::pair<Operation*, uint32_t>, ExpandedNodeInfo>& expandedNodes,
Operation* op,
uint32_t lane) {
auto it = expandedNodes.find({op, lane});
if (it == expandedNodes.end())
return "";
return it->second.id;
}
void addScalarNodeRow(std::fstream& nodesFile,
DenseMap<std::pair<Operation*, uint32_t>, ExpandedNodeInfo>& expandedNodes,
const TopLevelOpInfo& info,
AsmState* asmState = nullptr) {
std::string id = getScalarId(info.isPost, info.opId);
SmallVector<std::string, 5> row {id, std::to_string(info.opId), "", maybeNumber<int32_t>(info.scalarCore)};
if (asmState)
row.push_back(stringifyResultSsaNames(info.op, asmState));
writeCsvRow(nodesFile, row);
expandedNodes[{info.op, 0}] = {id, info.scalarCore, std::nullopt};
}
template <typename BatchOpTy>
void addBatchNodeRows(std::fstream& nodesFile,
DenseMap<std::pair<Operation*, uint32_t>, ExpandedNodeInfo>& expandedNodes,
const TopLevelOpInfo& info,
BatchOpTy batch,
ArrayRef<std::optional<int32_t>> laneCoreIds,
AsmState* asmState = nullptr) {
for (uint32_t lane = 0; lane < static_cast<uint32_t>(batch.getLaneCount()); ++lane) {
std::string id = getBatchLaneId(info.isPost, info.opId, lane);
SmallVector<std::string, 5> row {id,
std::to_string(info.opId),
std::to_string(lane),
maybeNumber<int32_t>(laneCoreIds[lane])};
if (asmState)
row.push_back(stringifyResultSsaNames(info.op, asmState));
writeCsvRow(nodesFile, row);
expandedNodes[{info.op, lane}] = {id, laneCoreIds[lane], lane};
}
}
std::optional<int64_t> evaluateIndexLike(Value value, Value laneArg, uint32_t lane);
std::optional<int64_t> evaluateIndexLike(Value value, Value laneArg, uint32_t lane) {
if (value == laneArg)
return static_cast<int64_t>(lane);
if (std::optional<int64_t> constant = matchConstantIndexValue(value))
return *constant;
if (auto constant = value.getDefiningOp<arith::ConstantOp>()) {
if (auto intAttr = dyn_cast<IntegerAttr>(constant.getValue()))
return intAttr.getInt();
}
if (auto extract = value.getDefiningOp<tensor::ExtractOp>()) {
auto constant = extract.getTensor().getDefiningOp<arith::ConstantOp>();
auto elements = constant ? dyn_cast<ElementsAttr>(constant.getValue()) : nullptr;
auto shapedType = elements ? dyn_cast<ShapedType>(elements.getType()) : nullptr;
if (!elements || !shapedType || shapedType.getRank() != 1 || extract.getIndices().size() != 1)
return std::nullopt;
std::optional<int64_t> index = evaluateIndexLike(extract.getIndices().front(), laneArg, lane);
if (!index || *index < 0 || *index >= static_cast<int64_t>(elements.getNumElements()))
return std::nullopt;
if (auto denseInts = dyn_cast<DenseIntElementsAttr>(elements))
return (*(denseInts.value_begin<APInt>() + *index)).getSExtValue();
return std::nullopt;
}
if (auto affineApply = value.getDefiningOp<affine::AffineApplyOp>())
if (FailureOr<int64_t> folded = evaluateAffineApply(
affineApply,
[&](Value operand) -> FailureOr<int64_t> {
if (std::optional<int64_t> resolved = evaluateIndexLike(operand, laneArg, lane))
return *resolved;
return failure();
});
succeeded(folded)) {
return *folded;
}
return std::nullopt;
}
SmallVector<int64_t, 8> collectPossibleIntValues(Value value, Value laneArg, uint32_t lane) {
if (std::optional<int64_t> exact = evaluateIndexLike(value, laneArg, lane))
return {*exact};
auto extract = value.getDefiningOp<tensor::ExtractOp>();
auto constant = extract ? extract.getTensor().getDefiningOp<arith::ConstantOp>() : nullptr;
auto elements = constant ? dyn_cast<ElementsAttr>(constant.getValue()) : nullptr;
if (!elements)
return {};
SmallVector<int64_t, 8> values;
if (auto denseInts = dyn_cast<DenseIntElementsAttr>(elements)) {
values.reserve(elements.getNumElements());
for (APInt element : denseInts.getValues<APInt>())
if (!llvm::is_contained(values, element.getSExtValue()))
values.push_back(element.getSExtValue());
}
return values;
}
template <typename BatchOpTy>
std::optional<Value> getBatchLaneInput(BatchOpTy batch, uint32_t lane, unsigned inputIndex) {
if (batch.getNumResults() != 0)
return batch.getInputs()[inputIndex];
size_t laneCount = static_cast<size_t>(batch.getLaneCount());
if (laneCount == 0 || batch.getInputs().size() % laneCount != 0)
return std::nullopt;
size_t inputsPerLane = batch.getInputs().size() / laneCount;
size_t flatIndex = static_cast<size_t>(lane) * inputsPerLane + inputIndex;
if (flatIndex >= batch.getInputs().size())
return std::nullopt;
return batch.getInputs()[flatIndex];
}
template <typename BatchOpTy>
unsigned getBatchLaneInputCount(BatchOpTy batch) {
if (batch.getNumResults() != 0)
return batch.getInputs().size();
size_t laneCount = static_cast<size_t>(batch.getLaneCount());
if (laneCount == 0 || batch.getInputs().size() % laneCount != 0)
return 0;
return static_cast<unsigned>(batch.getInputs().size() / laneCount);
}
template <typename ComputeOpTy, typename BatchOpTy>
std::optional<ResolvedProducer> resolveProducerForValue(Value value, std::optional<uint32_t> consumerLane) {
Operation* op = value.getDefiningOp();
if (!op)
return std::nullopt;
while (auto extract = dyn_cast<tensor::ExtractSliceOp>(op)) {
Value source = extract.getSource();
Operation* sourceOp = source.getDefiningOp();
auto sourceBatch = dyn_cast_or_null<BatchOpTy>(sourceOp);
if (sourceBatch && sourceBatch.getNumResults() != 0) {
auto staticOffsets = extract.getStaticOffsets();
if (!staticOffsets.empty() && staticOffsets.front() != ShapedType::kDynamic) {
uint32_t lane = static_cast<uint32_t>(staticOffsets.front());
return ResolvedProducer {sourceOp, 0, LogicalNodeSelector::Lane, lane, lane, 1};
}
if (consumerLane)
return ResolvedProducer {sourceOp, 0, LogicalNodeSelector::Lane, *consumerLane, *consumerLane, 1};
return ResolvedProducer {
sourceOp, 0, LogicalNodeSelector::RangeRepresentative, 0, 0, static_cast<uint32_t>(sourceBatch.getLaneCount())
};
}
value = source;
op = sourceOp;
if (!op)
return std::nullopt;
}
if (auto compute = dyn_cast<ComputeOpTy>(op))
return ResolvedProducer {
compute.getOperation(), static_cast<size_t>(cast<OpResult>(value).getResultNumber()), LogicalNodeSelector::Scalar, 0, 0, 1
};
if (auto batch = dyn_cast<BatchOpTy>(op)) {
if (batch.getNumResults() != 0) {
if (consumerLane)
return ResolvedProducer {op, 0, LogicalNodeSelector::Lane, *consumerLane, *consumerLane, 1};
return ResolvedProducer {
op, 0, LogicalNodeSelector::RangeRepresentative, 0, 0, static_cast<uint32_t>(batch.getLaneCount())
};
}
uint32_t lane = static_cast<uint32_t>(cast<OpResult>(value).getResultNumber());
return ResolvedProducer {op, static_cast<size_t>(lane), LogicalNodeSelector::Lane, lane, lane, 1};
}
return std::nullopt;
}
SmallVector<EdgeSource, 8>
resolveProducerSourcesForCsv(const ResolvedProducer& producer,
const DenseMap<std::pair<Operation*, uint32_t>, ExpandedNodeInfo>& expandedNodes) {
SmallVector<EdgeSource, 8> sources;
if (producer.selector == LogicalNodeSelector::Scalar) {
std::string id = getExpandedNodeId(expandedNodes, producer.op, 0);
if (!id.empty())
sources.push_back({id, std::nullopt});
return sources;
}
if (producer.selector == LogicalNodeSelector::Lane) {
std::string id = getExpandedNodeId(expandedNodes, producer.op, producer.lane);
if (!id.empty())
sources.push_back({id, producer.lane});
return sources;
}
for (uint32_t lane = producer.laneStart; lane < producer.laneStart + producer.laneCount; ++lane) {
std::string id = getExpandedNodeId(expandedNodes, producer.op, lane);
if (!id.empty())
sources.push_back({id, lane});
}
return sources;
}
void emitEdgeRow(std::fstream& edgesFile,
StringRef sourceId,
StringRef targetId,
std::optional<uint64_t> byteSize,
Type propagatedType,
StringRef stage,
std::optional<uint32_t> sourceLane,
std::optional<uint32_t> targetLane,
std::optional<int64_t> channelId) {
writeCsvRow(edgesFile,
{sourceId.str(),
targetId.str(),
maybeNumber<uint64_t>(byteSize),
stringifyType(propagatedType),
stage.str(),
maybeNumber<uint32_t>(sourceLane),
maybeNumber<uint32_t>(targetLane),
maybeNumber<int64_t>(channelId)});
}
template <typename ComputeOpTy, typename BatchOpTy>
LogicalResult emitDataEdges(std::fstream& edgesFile,
const DenseMap<Operation*, TopLevelOpInfo>& topLevelInfo,
const DenseMap<std::pair<Operation*, uint32_t>, ExpandedNodeInfo>& expandedNodes,
StringRef stage) {
for (const auto& entry : topLevelInfo) {
Operation* op = entry.first;
const TopLevelOpInfo& info = entry.second;
if (auto compute = dyn_cast<ComputeOpTy>(op)) {
for (Value input : compute.getInputs()) {
if (isa_and_nonnull<SpatChannelReceiveOp>(input.getDefiningOp()))
continue;
auto producer = resolveProducerForValue<ComputeOpTy, BatchOpTy>(input, std::nullopt);
if (!producer)
continue;
SmallVector<EdgeSource, 8> sources = resolveProducerSourcesForCsv(*producer, expandedNodes);
std::optional<uint64_t> byteSize = getTypeSizeBytes(input.getType());
std::string targetId = getScalarId(info.isPost, info.opId);
for (const EdgeSource& source : sources)
emitEdgeRow(edgesFile, source.id, targetId, byteSize, input.getType(), stage, source.sourceLane, std::nullopt, std::nullopt);
}
continue;
}
auto batch = dyn_cast<BatchOpTy>(op);
if (!batch)
continue;
unsigned inputCount = getBatchLaneInputCount(batch);
for (uint32_t lane = 0; lane < static_cast<uint32_t>(batch.getLaneCount()); ++lane) {
std::string targetId = getBatchLaneId(info.isPost, info.opId, lane);
for (unsigned inputIndex = 0; inputIndex < inputCount; ++inputIndex) {
std::optional<Value> input = getBatchLaneInput(batch, lane, inputIndex);
if (!input || isa_and_nonnull<SpatChannelReceiveOp>((*input).getDefiningOp()))
continue;
auto producer = resolveProducerForValue<ComputeOpTy, BatchOpTy>(*input, lane);
if (!producer)
continue;
SmallVector<EdgeSource, 8> sources = resolveProducerSourcesForCsv(*producer, expandedNodes);
std::optional<uint64_t> byteSize = getTypeSizeBytes((*input).getType());
for (const EdgeSource& source : sources)
emitEdgeRow(edgesFile, source.id, targetId, byteSize, (*input).getType(), stage, source.sourceLane, lane, std::nullopt);
}
}
}
return success();
}
template <typename BatchOpTy>
void collectChannelSends(DenseMap<int64_t, SmallVector<ChannelSendRecord, 4>>& sendsByChannelId,
const DenseMap<std::pair<Operation*, uint32_t>, ExpandedNodeInfo>& expandedNodes,
BatchOpTy batch) {
std::optional<BlockArgument> laneArg = batch.getLaneArgument();
if (!laneArg)
return;
for (uint32_t lane = 0; lane < static_cast<uint32_t>(batch.getLaneCount()); ++lane) {
std::string sourceId = getExpandedNodeId(expandedNodes, batch.getOperation(), lane);
if (sourceId.empty())
continue;
batch.getBody().walk([&](SpatChannelSendOp send) {
std::optional<int64_t> channelId = evaluateIndexLike(send.getChannelId(), *laneArg, lane);
if (!channelId)
return;
sendsByChannelId[*channelId].push_back({sourceId, lane});
});
}
}
void collectChannelSends(DenseMap<int64_t, SmallVector<ChannelSendRecord, 4>>& sendsByChannelId,
const DenseMap<std::pair<Operation*, uint32_t>, ExpandedNodeInfo>& expandedNodes,
SpatScheduledCompute compute) {
std::string sourceId = getExpandedNodeId(expandedNodes, compute.getOperation(), 0);
if (sourceId.empty())
return;
compute.getBody().walk([&](SpatChannelSendOp send) {
std::optional<int64_t> channelId = evaluateIndexLike(send.getChannelId(), Value(), 0);
if (!channelId)
return;
sendsByChannelId[*channelId].push_back({sourceId, std::nullopt});
});
}
DenseMap<int32_t, SmallVector<ChannelSendRecord, 4>>
buildNodesByCore(const DenseMap<std::pair<Operation*, uint32_t>, ExpandedNodeInfo>& expandedNodes) {
DenseMap<int32_t, SmallVector<ChannelSendRecord, 4>> nodesByCore;
for (const auto& entry : expandedNodes) {
const ExpandedNodeInfo& node = entry.second;
if (!node.core)
continue;
nodesByCore[*node.core].push_back({node.id, node.lane});
}
return nodesByCore;
}
template <typename ComputeOpTy, typename BatchOpTy, typename ResolveChannelSourcesFn>
LogicalResult emitExplicitChannelEdges(std::fstream& edgesFile,
const DenseMap<Operation*, TopLevelOpInfo>& topLevelInfo,
ResolveChannelSourcesFn&& resolveChannelSources,
StringRef stage) {
for (const auto& entry : topLevelInfo) {
Operation* op = entry.first;
const TopLevelOpInfo& info = entry.second;
if (auto compute = dyn_cast<ComputeOpTy>(op)) {
compute.getBody().walk([&](SpatChannelReceiveOp receive) {
SmallVector<ChannelSendRecord, 4> sources = resolveChannelSources(receive, 0);
if (sources.empty())
return;
std::optional<int64_t> channelId = evaluateIndexLike(receive.getChannelId(), Value(), 0);
std::string targetId = getScalarId(info.isPost, info.opId);
std::optional<uint64_t> byteSize = getTypeSizeBytes(receive.getType());
for (const ChannelSendRecord& source : sources)
emitEdgeRow(edgesFile, source.sourceId, targetId, byteSize, receive.getType(), stage, source.sourceLane, std::nullopt, channelId);
});
continue;
}
auto batch = dyn_cast<BatchOpTy>(op);
if (!batch)
continue;
auto laneArg = batch.getLaneArgument();
if (!laneArg)
continue;
for (uint32_t lane = 0; lane < static_cast<uint32_t>(batch.getLaneCount()); ++lane) {
std::string targetId = getBatchLaneId(info.isPost, info.opId, lane);
batch.getBody().walk([&](SpatChannelReceiveOp receive) {
SmallVector<ChannelSendRecord, 4> sources = resolveChannelSources(receive, lane);
if (sources.empty())
return;
std::optional<int64_t> channelId = evaluateIndexLike(receive.getChannelId(), *laneArg, lane);
std::optional<uint64_t> byteSize = getTypeSizeBytes(receive.getType());
for (const ChannelSendRecord& source : sources)
emitEdgeRow(edgesFile, source.sourceId, targetId, byteSize, receive.getType(), stage, source.sourceLane, lane, channelId);
});
}
}
return success();
}
LogicalResult exportStagePre(func::FuncOp func) {
std::fstream nodesFile = openDialectDumpFileWithExtension("spatial1_graph.nodes", "/reports", "csv");
std::fstream edgesFile = openDialectDumpFileWithExtension("spatial1_graph.edges","/reports", "csv");
if (!nodesFile.is_open() || !edgesFile.is_open())
return success();
writeCsvRow(nodesFile, {"Id", "op_id", "lane", "core", "ssa_name"});
writeCsvRow(edgesFile, {"Source", "Target", "Weight", "Type", "stage", "source_lane", "target_lane", "channel_id"});
Operation* asmRoot = func.getOperation();
if (auto moduleOp = func->getParentOfType<ModuleOp>())
asmRoot = moduleOp.getOperation();
OpPrintingFlags flags;
flags.elideLargeElementsAttrs().enableDebugInfo(true, false);
AsmState asmState(asmRoot, flags);
DenseMap<Operation*, TopLevelOpInfo> topLevelInfo;
DenseMap<std::pair<Operation*, uint32_t>, ExpandedNodeInfo> expandedNodes;
size_t opId = 0;
for (Operation& op : func.getBody().front()) {
if (!isTopLevelRelevantCompute<SpatGraphCompute, SpatGraphComputeBatch>(op))
continue;
FailureOr<TopLevelOpInfo> info = buildTopLevelOpInfo<SpatGraphCompute, SpatGraphComputeBatch>(op, false, opId++);
if (failed(info))
return failure();
topLevelInfo[&op] = *info;
if (auto compute = dyn_cast<SpatGraphCompute>(&op)) {
addScalarNodeRow(nodesFile, expandedNodes, *info, &asmState);
continue;
}
auto batch = cast<SpatGraphComputeBatch>(&op);
SmallVector<std::optional<int32_t>, 8> laneCoreIds(batch.getLaneCount());
addBatchNodeRows(nodesFile, expandedNodes, *info, batch, laneCoreIds, &asmState);
}
return emitDataEdges<SpatGraphCompute, SpatGraphComputeBatch>(edgesFile, topLevelInfo, expandedNodes, "pre");
}
LogicalResult exportStagePost(func::FuncOp func) {
std::fstream nodesFile = openDialectDumpFileWithExtension("spatial2_merged.nodes", "/reports", "csv");
std::fstream edgesFile = openDialectDumpFileWithExtension("spatial2_merged.edges", "/reports", "csv");
if (!nodesFile.is_open() || !edgesFile.is_open())
return success();
writeCsvRow(nodesFile, {"Id", "op_id", "lane", "core"});
writeCsvRow(edgesFile, {"Source", "Target", "Weight", "Type", "stage", "source_lane", "target_lane", "channel_id"});
DenseMap<Operation*, TopLevelOpInfo> topLevelInfo;
DenseMap<std::pair<Operation*, uint32_t>, ExpandedNodeInfo> expandedNodes;
size_t opId = 0;
for (Operation& op : func.getBody().front()) {
if (!isTopLevelRelevantCompute<SpatScheduledCompute, SpatScheduledComputeBatch>(op))
continue;
FailureOr<TopLevelOpInfo> info = buildTopLevelOpInfo<SpatScheduledCompute, SpatScheduledComputeBatch>(op, true, opId++);
if (failed(info))
return failure();
topLevelInfo[&op] = *info;
if (isa<SpatScheduledCompute>(&op)) {
addScalarNodeRow(nodesFile, expandedNodes, *info);
continue;
}
auto batch = cast<SpatScheduledComputeBatch>(&op);
auto coreIds = getBatchLaneCoreIds(batch);
if (failed(coreIds))
return failure();
SmallVector<std::optional<int32_t>, 8> laneCoreIds(batch.getLaneCount());
for (uint32_t lane = 0; lane < static_cast<uint32_t>(batch.getLaneCount()); ++lane)
if (lane < coreIds->size())
laneCoreIds[lane] = (*coreIds)[lane];
addBatchNodeRows(nodesFile, expandedNodes, *info, batch, laneCoreIds);
}
if (failed(emitDataEdges<SpatScheduledCompute, SpatScheduledComputeBatch>(edgesFile, topLevelInfo, expandedNodes, "post")))
return failure();
DenseMap<int64_t, SmallVector<ChannelSendRecord, 4>> sendsByChannelId;
for (const auto& entry : topLevelInfo) {
Operation* op = entry.first;
if (auto compute = dyn_cast<SpatScheduledCompute>(op))
collectChannelSends(sendsByChannelId, expandedNodes, compute);
else if (auto batch = dyn_cast<SpatScheduledComputeBatch>(op))
collectChannelSends(sendsByChannelId, expandedNodes, batch);
}
DenseMap<int32_t, SmallVector<ChannelSendRecord, 4>> nodesByCore = buildNodesByCore(expandedNodes);
auto resolveChannelSources = [&](SpatChannelReceiveOp receive, uint32_t lane) {
SmallVector<ChannelSendRecord, 4> sources;
Value laneArg;
if (auto owner = receive->getParentOfType<SpatScheduledComputeBatch>())
if (auto maybeLaneArg = owner.getLaneArgument())
laneArg = *maybeLaneArg;
if (std::optional<int64_t> channelId = evaluateIndexLike(receive.getChannelId(), laneArg, lane)) {
if (auto it = sendsByChannelId.find(*channelId); it != sendsByChannelId.end())
return it->second;
}
for (int64_t sourceCore : collectPossibleIntValues(receive.getSourceCoreId(), laneArg, lane)) {
auto it = nodesByCore.find(static_cast<int32_t>(sourceCore));
if (it == nodesByCore.end())
continue;
llvm::append_range(sources, it->second);
}
return sources;
};
return emitExplicitChannelEdges<SpatScheduledCompute, SpatScheduledComputeBatch>(
edgesFile, topLevelInfo, resolveChannelSources, "post");
}
} // namespace
SpatialDataflowExportStage getSpatialDataflowExportStage() {
switch (pimExportSpatialDataflow.getValue()) {
case SpatialDataflowExportNone: return SpatialDataflowExportStage::None;
case SpatialDataflowExportPre: return SpatialDataflowExportStage::Pre;
case SpatialDataflowExportPost: return SpatialDataflowExportStage::Post;
case SpatialDataflowExportBoth: return SpatialDataflowExportStage::Both;
}
llvm_unreachable("unknown spatial dataflow export mode");
}
bool shouldExportSpatialDataflowStage(SpatialDataflowExportStage mode, SpatialDataflowExportStage stage) {
switch (mode) {
case SpatialDataflowExportStage::None: return false;
case SpatialDataflowExportStage::Pre: return stage == SpatialDataflowExportStage::Pre;
case SpatialDataflowExportStage::Post: return stage == SpatialDataflowExportStage::Post;
case SpatialDataflowExportStage::Both:
return stage == SpatialDataflowExportStage::Pre || stage == SpatialDataflowExportStage::Post;
}
return false;
}
LogicalResult exportSpatialDataflowCsvPre(func::FuncOp func) { return exportStagePre(func); }
LogicalResult exportSpatialDataflowCsvPost(func::FuncOp func) { return exportStagePost(func); }
} // namespace spatial
} // namespace onnx_mlir
@@ -1,25 +0,0 @@
#pragma once
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Support/LogicalResult.h"
namespace onnx_mlir {
namespace spatial {
enum class SpatialDataflowExportStage {
None,
Pre,
Post,
Both,
};
SpatialDataflowExportStage getSpatialDataflowExportStage();
mlir::LogicalResult exportSpatialDataflowCsvPre(mlir::func::FuncOp func);
mlir::LogicalResult exportSpatialDataflowCsvPost(mlir::func::FuncOp func);
bool shouldExportSpatialDataflowStage(SpatialDataflowExportStage mode, SpatialDataflowExportStage stage);
} // namespace spatial
} // namespace onnx_mlir