even faster on pimcomp models
Validate Operations / validate-operations (push) Has been cancelled

This commit is contained in:
NiccoloN
2026-07-31 21:15:28 +02:00
parent 9ca1a0ed9f
commit f4a3b012cc
49 changed files with 1923 additions and 583 deletions
@@ -44,6 +44,19 @@ input size and target linear or sublinear time and space. Avoid repeated full-IR
walks, nested scans, and per-operation recomputation when indexing, caching, or walks, nested scans, and per-operation recomputation when indexing, caching, or
a single traversal can express the same behavior. a single traversal can express the same behavior.
Bufferization cost scales with both the number of operations and the number of
MLIR values it receives. Upstream lowering and scheduling must therefore keep
repeated work in compact structured operations, such as statically evaluable
loops and batches, until after bufferization. Do not compensate for avoidable
pre-bufferization expansion by weakening, partitioning, or special-casing the
bufferization analysis; measure both operation and value counts at its input.
Compactness must also be preserved after bufferization so that liveness,
verification, memory planning, and other downstream passes do not repeat work
per logical lane or iteration. Keep structured loops and batches intact until
PIM ISA code generation is forced to scalarize them into concrete per-core
instructions; earlier expansion requires an explicit semantic necessity and
before/after operation and value counts.
When linear-or-better complexity is not possible, use the lowest justified When linear-or-better complexity is not possible, use the lowest justified
complexity and report the actual time and space Big-O, the input variable, and complexity and report the actual time and space Big-O, the input variable, and
why a lower bound is not practical. Include that cost in the final report; do why a lower bound is not practical. Include that cost in the final report; do
+1
View File
@@ -121,6 +121,7 @@ add_pim_library(OMPIMAccel
OMPimCommon OMPimCommon
OMPimBufferization OMPimBufferization
OMPimHostConstantFolding OMPimHostConstantFolding
OMPimInstructionSelection
OMPimVerification OMPimVerification
MLIRTensorInferTypeOpInterfaceImpl MLIRTensorInferTypeOpInterfaceImpl
) )
+1
View File
@@ -31,6 +31,7 @@ add_pim_library(OMPimCompilerUtils
OMPimCommon OMPimCommon
OMPimBufferization OMPimBufferization
OMPimHostConstantFolding OMPimHostConstantFolding
OMPimInstructionSelection
OMPimLocalMemoryPlanning OMPimLocalMemoryPlanning
OMPimVerification OMPimVerification
OMPimPasses OMPimPasses
+62 -89
View File
@@ -108,6 +108,23 @@ static Operation* getDiagnosticAnchor(mlir::Value value) {
return nullptr; return nullptr;
} }
static bool isZeroSplatGlobal(mlir::Value value) {
auto getGlobalOp = value.getDefiningOp<memref::GetGlobalOp>();
auto moduleOp = getGlobalOp ? getGlobalOp->getParentOfType<ModuleOp>() : ModuleOp();
auto globalOp = moduleOp ? lookupGlobalForGetGlobal(moduleOp, getGlobalOp) : memref::GlobalOp();
if (!globalOp || !globalOp.getConstant() || !globalOp.getInitialValue())
return false;
auto denseAttr = dyn_cast<DenseElementsAttr>(*globalOp.getInitialValue());
if (!denseAttr || !denseAttr.isSplat())
return false;
Attribute valueAttr = denseAttr.getSplatValue<Attribute>();
if (auto floatAttr = dyn_cast<FloatAttr>(valueAttr))
return floatAttr.getValue().isZero() && !floatAttr.getValue().isNegative();
if (auto integerAttr = dyn_cast<IntegerAttr>(valueAttr))
return integerAttr.getValue().isZero();
return false;
}
// PIM instruction immediates are serialized as signed int32_t fields today // PIM instruction immediates are serialized as signed int32_t fields today
// (`sldi` goes through checkedI32OrCrash), so local addresses must stay within // (`sldi` goes through checkedI32OrCrash), so local addresses must stay within
// the non-negative int32_t range. // the non-negative int32_t range.
@@ -541,14 +558,23 @@ void PimCodeGen::emitMemCopyOp(pim_binary::Opcode opcode,
size_t rs1Offset, size_t rs1Offset,
size_t size, size_t size,
StringRef sizeFieldName) const { StringRef sizeFieldName) const {
setupRdRs1(rdAddr, rdOffset, rs1Addr, rs1Offset);
pim_binary::InstructionRecord instruction; pim_binary::InstructionRecord instruction;
instruction.opcode = opcode; instruction.opcode = opcode;
instruction.rd = 0; instruction.rd = 0;
instruction.r1 = 1; instruction.r1 = 1;
instruction.generic1 = 0; if (rdOffset == rs1Offset) {
instruction.generic2 = 0; setupRdRs1(rdAddr, 0, rs1Addr, 0);
instruction.generic1 = rdOffset == 0 ? 0 : 3;
instruction.generic2 = pim::checkedI32OrCrash(rdOffset, "shared address offset");
} else if (rdOffset != 0) {
setupRdRs1(rdAddr, 0, rs1Addr, rs1Offset);
instruction.generic1 = 1;
instruction.generic2 = pim::checkedI32OrCrash(rdOffset, "rd address offset");
} else {
setupRdRs1(rdAddr, 0, rs1Addr, 0);
instruction.generic1 = rs1Offset == 0 ? 0 : 2;
instruction.generic2 = pim::checkedI32OrCrash(rs1Offset, "rs1 address offset");
}
instruction.generic3 = pim::checkedI32OrCrash(size, sizeFieldName); instruction.generic3 = pim::checkedI32OrCrash(size, sizeFieldName);
emitInstruction(instruction); emitInstruction(instruction);
} }
@@ -584,6 +610,16 @@ void PimCodeGen::codeGenLoadOp(pim::PimMemCopyHostToDevOp loadOp, const StaticVa
auto hostSourceOffset = indexOf(loadOp.getHostSourceOffset(), knowledge); auto hostSourceOffset = indexOf(loadOp.getHostSourceOffset(), knowledge);
assert(succeeded(deviceTargetOffset) && succeeded(hostSourceOffset) assert(succeeded(deviceTargetOffset) && succeeded(hostSourceOffset)
&& "pim.memcp_hd offsets must be statically resolvable during codegen"); && "pim.memcp_hd offsets must be statically resolvable during codegen");
if (isZeroSplatGlobal(loadOp.getHostSource())) {
setupRd(addressOf(loadOp.getDeviceTarget(), knowledge), *deviceTargetOffset);
pim_binary::InstructionRecord instruction;
instruction.opcode = pim_binary::Opcode::lldi;
instruction.rd = 0;
instruction.r2OrImm = 0;
instruction.generic3 = loadOp.getSize();
emitInstruction(instruction);
return;
}
emitMemCopyOp(pim_binary::Opcode::ld, emitMemCopyOp(pim_binary::Opcode::ld,
addressOf(loadOp.getDeviceTarget(), knowledge), addressOf(loadOp.getDeviceTarget(), knowledge),
*deviceTargetOffset, *deviceTargetOffset,
@@ -619,6 +655,26 @@ void PimCodeGen::codeGenLmvOp(pim::PimMemCopyOp lmvOp, const StaticValueKnowledg
"len"); "len");
} }
void PimCodeGen::codeGenVMVOp(pim::PimVMVOp vmvOp, const StaticValueKnowledge& knowledge) const {
auto targetOffset = indexOf(vmvOp.getTargetOffset(), knowledge);
auto sourceOffset = indexOf(vmvOp.getSourceOffset(), knowledge);
auto sourceStride = indexOf(vmvOp.getSourceStride(), knowledge);
assert(succeeded(targetOffset) && succeeded(sourceOffset) && succeeded(sourceStride)
&& "pim.vmv operands must be statically resolvable during codegen");
auto sourceType = cast<ShapedType>(vmvOp.getSource().getType());
int32_t bitwidth = getVectorElementBitwidthOrCrash(sourceType);
ensureVectorBitwidth(bitwidth, bitwidth);
setupRdRs1Rs2(addressOf(vmvOp.getTarget(), knowledge), *targetOffset,
addressOf(vmvOp.getSource(), knowledge), *sourceOffset, 0, *sourceStride);
pim_binary::InstructionRecord instruction;
instruction.opcode = pim_binary::Opcode::vmv;
instruction.rd = 0;
instruction.r1 = 1;
instruction.r2OrImm = 2;
instruction.generic3 = vmvOp.getLength();
emitInstruction(instruction);
}
void PimCodeGen::codeGenReceiveOp(pim::PimReceiveOp receiveOp, const StaticValueKnowledge& knowledge) const { void PimCodeGen::codeGenReceiveOp(pim::PimReceiveOp receiveOp, const StaticValueKnowledge& knowledge) const {
auto outputOffset = indexOf(receiveOp.getOutputOffset(), knowledge); auto outputOffset = indexOf(receiveOp.getOutputOffset(), knowledge);
auto sourceCoreId = indexOf(receiveOp.getSourceCoreId(), knowledge); auto sourceCoreId = indexOf(receiveOp.getSourceCoreId(), knowledge);
@@ -723,89 +779,6 @@ void PimCodeGen::emitUnaryVectorOp(pim_binary::Opcode opcode,
emitInstruction(instruction); emitInstruction(instruction);
} }
void PimCodeGen::codeGenTransposeOp(const CompiledTransposePlan& plan, const StaticValueKnowledge& knowledge) const {
auto srcAddr = addressOf(plan.source, knowledge);
auto dstAddr = addressOf(plan.destination, knowledge);
size_t maxElementOffset = plan.totalBytes == 0 ? 0 : plan.totalBytes - plan.elementBytes;
int32_t maxSourceAddress = pim::checkedI32OrCrash(
pim::checkedAddOrCrash(srcAddr, maxElementOffset, "transpose source address"), "transpose source address");
int32_t maxDestinationAddress =
pim::checkedI32OrCrash(pim::checkedAddOrCrash(dstAddr, maxElementOffset, "transpose destination address"),
"transpose destination address");
(void) maxSourceAddress;
(void) maxDestinationAddress;
pim_binary::InstructionRecord copyInstruction;
copyInstruction.opcode = pim_binary::Opcode::lmv;
copyInstruction.rd = 0;
copyInstruction.r1 = 1;
size_t maxRunElements = static_cast<size_t>(std::numeric_limits<int32_t>::max()) / plan.elementBytes;
auto emitRun = [&](size_t sourceStart, size_t destinationStart, size_t runLength) {
while (runLength != 0) {
size_t chunkElements = std::min(runLength, maxRunElements);
// totalBytes was checked when the plan was compiled, so these bounded
// products cannot overflow.
size_t sourceOffset = sourceStart * plan.elementBytes;
size_t destinationOffset = destinationStart * plan.elementBytes;
size_t byteSize = chunkElements * plan.elementBytes;
assert(sourceOffset <= plan.totalBytes - byteSize && destinationOffset <= plan.totalBytes - byteSize);
genSetRegisterImmediate(0, static_cast<int32_t>(dstAddr + destinationOffset));
genSetRegisterImmediate(1, static_cast<int32_t>(srcAddr + sourceOffset));
copyInstruction.generic3 = static_cast<int32_t>(byteSize);
emitInstruction(copyInstruction);
sourceStart += chunkElements;
destinationStart += chunkElements;
runLength -= chunkElements;
}
};
if (plan.storagePreserving) {
emitRun(0, 0, plan.totalElements);
return;
}
size_t rank = plan.sourceShape.size();
SmallVector<size_t> sourceIndices(rank, 0);
size_t destinationFlat = 0;
size_t runSourceStart = 0;
size_t runDestinationStart = 0;
size_t runLength = 0;
for (size_t sourceFlat = 0; sourceFlat < plan.totalElements; ++sourceFlat) {
if (runLength != 0 && destinationFlat != runDestinationStart + runLength) {
emitRun(runSourceStart, runDestinationStart, runLength);
runSourceStart = sourceFlat;
runDestinationStart = destinationFlat;
runLength = 0;
}
if (runLength == 0) {
runSourceStart = sourceFlat;
runDestinationStart = destinationFlat;
}
++runLength;
if (runLength == maxRunElements) {
emitRun(runSourceStart, runDestinationStart, runLength);
runLength = 0;
}
if (sourceFlat + 1 == plan.totalElements)
break;
for (size_t sourceDim = rank; sourceDim-- > 0;) {
destinationFlat += plan.destinationStrides[plan.destinationDimensionForSource[sourceDim]];
if (++sourceIndices[sourceDim] < plan.sourceShape[sourceDim])
break;
sourceIndices[sourceDim] = 0;
destinationFlat -= plan.destinationRewinds[sourceDim];
}
}
if (runLength != 0)
emitRun(runSourceStart, runDestinationStart, runLength);
}
static SmallVector<Operation*> collectTopLevelCoreLikeOps(func::FuncOp funcOp) { static SmallVector<Operation*> collectTopLevelCoreLikeOps(func::FuncOp funcOp) {
SmallVector<Operation*> coreLikeOps; SmallVector<Operation*> coreLikeOps;
for (Operation& op : funcOp.getBody().front()) for (Operation& op : funcOp.getBody().front())
@@ -1015,6 +988,7 @@ static LogicalResult executeCompiledCorePlan(
coreCodeGen.codeGenStoreOp(cast<pim::PimMemCopyDevToHostOp>(node.op), knowledge); coreCodeGen.codeGenStoreOp(cast<pim::PimMemCopyDevToHostOp>(node.op), knowledge);
break; break;
case CompiledCoreOpKind::Lmv: coreCodeGen.codeGenLmvOp(cast<pim::PimMemCopyOp>(node.op), knowledge); break; case CompiledCoreOpKind::Lmv: coreCodeGen.codeGenLmvOp(cast<pim::PimMemCopyOp>(node.op), knowledge); break;
case CompiledCoreOpKind::VMV: coreCodeGen.codeGenVMVOp(cast<pim::PimVMVOp>(node.op), knowledge); break;
case CompiledCoreOpKind::Receive: coreCodeGen.codeGenReceiveOp(cast<pim::PimReceiveOp>(node.op), knowledge); break; case CompiledCoreOpKind::Receive: coreCodeGen.codeGenReceiveOp(cast<pim::PimReceiveOp>(node.op), knowledge); break;
case CompiledCoreOpKind::Send: coreCodeGen.codeGenSendOp(cast<pim::PimSendOp>(node.op), knowledge); break; case CompiledCoreOpKind::Send: coreCodeGen.codeGenSendOp(cast<pim::PimSendOp>(node.op), knowledge); break;
case CompiledCoreOpKind::Concat: coreCodeGen.codeGenConcatOp(cast<pim::PimConcatOp>(node.op), knowledge); break; case CompiledCoreOpKind::Concat: coreCodeGen.codeGenConcatOp(cast<pim::PimConcatOp>(node.op), knowledge); break;
@@ -1024,7 +998,6 @@ static LogicalResult executeCompiledCorePlan(
else else
return failure(); return failure();
break; break;
case CompiledCoreOpKind::Transpose: coreCodeGen.codeGenTransposeOp(*node.transposePlan, knowledge); break;
case CompiledCoreOpKind::VVAdd: emitBinary(cast<pim::PimVVAddOp>(node.op), pim_binary::Opcode::vvadd); break; case CompiledCoreOpKind::VVAdd: emitBinary(cast<pim::PimVVAddOp>(node.op), pim_binary::Opcode::vvadd); break;
case CompiledCoreOpKind::VVSub: emitBinary(cast<pim::PimVVSubOp>(node.op), pim_binary::Opcode::vvsub); break; case CompiledCoreOpKind::VVSub: emitBinary(cast<pim::PimVVSubOp>(node.op), pim_binary::Opcode::vvsub); break;
case CompiledCoreOpKind::VVMul: emitBinary(cast<pim::PimVVMulOp>(node.op), pim_binary::Opcode::vvmul); break; case CompiledCoreOpKind::VVMul: emitBinary(cast<pim::PimVVMulOp>(node.op), pim_binary::Opcode::vvmul); break;
@@ -1224,7 +1197,7 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
for (auto [slot, fileName] : llvm::enumerate(weightFiles)) { for (auto [slot, fileName] : llvm::enumerate(weightFiles)) {
xbarsPerGroup.push_back(weights[slot].shape[1] / static_cast<int64_t>(crossbarSize)); xbarsPerGroup.push_back(weights[slot].shape[1] / static_cast<int64_t>(crossbarSize));
std::string sourcePath = outputDirPath + "/weights/" + fileName; std::string sourcePath = "../weights/" + fileName;
std::string targetPath = coreWeightsDirPath + "/crossbar_" + std::to_string(slot) + ".bin"; std::string targetPath = coreWeightsDirPath + "/crossbar_" + std::to_string(slot) + ".bin";
sys::fs::remove(targetPath); sys::fs::remove(targetPath);
if (auto error = sys::fs::create_link(sourcePath, targetPath)) { if (auto error = sys::fs::create_link(sourcePath, targetPath)) {
+1 -2
View File
@@ -25,7 +25,6 @@
namespace onnx_mlir { namespace onnx_mlir {
struct CompiledCoreProgram; struct CompiledCoreProgram;
struct CompiledTransposePlan;
class PimInstructionWriter; class PimInstructionWriter;
struct MemEntry { struct MemEntry {
@@ -214,6 +213,7 @@ public:
void codeGenLoadOp(pim::PimMemCopyHostToDevOp loadOp, const StaticValueKnowledge& knowledge) const; void codeGenLoadOp(pim::PimMemCopyHostToDevOp loadOp, const StaticValueKnowledge& knowledge) const;
void codeGenStoreOp(pim::PimMemCopyDevToHostOp storeOp, const StaticValueKnowledge& knowledge) const; void codeGenStoreOp(pim::PimMemCopyDevToHostOp storeOp, const StaticValueKnowledge& knowledge) const;
void codeGenLmvOp(pim::PimMemCopyOp lmvOp, const StaticValueKnowledge& knowledge) const; void codeGenLmvOp(pim::PimMemCopyOp lmvOp, const StaticValueKnowledge& knowledge) const;
void codeGenVMVOp(pim::PimVMVOp vmvOp, const StaticValueKnowledge& knowledge) const;
void codeGenReceiveOp(pim::PimReceiveOp receiveOp, const StaticValueKnowledge& knowledge) const; void codeGenReceiveOp(pim::PimReceiveOp receiveOp, const StaticValueKnowledge& knowledge) const;
void codeGenSendOp(pim::PimSendOp sendOp, const StaticValueKnowledge& knowledge) const; void codeGenSendOp(pim::PimSendOp sendOp, const StaticValueKnowledge& knowledge) const;
@@ -222,7 +222,6 @@ public:
template <typename MVMTy> template <typename MVMTy>
void codeGenMVMLikeOp(size_t mvmId, MVMTy mvmLikeOp, bool transposeMatrix, const StaticValueKnowledge& knowledge); void codeGenMVMLikeOp(size_t mvmId, MVMTy mvmLikeOp, bool transposeMatrix, const StaticValueKnowledge& knowledge);
void codeGenTransposeOp(const CompiledTransposePlan& plan, const StaticValueKnowledge& knowledge) const;
}; };
OnnxMlirCompilerErrorCodes compileToPimCode(mlir::ModuleOp& moduleOpRef, std::string& outputDirName); OnnxMlirCompilerErrorCodes compileToPimCode(mlir::ModuleOp& moduleOpRef, std::string& outputDirName);
+3
View File
@@ -281,6 +281,7 @@ void addPassesPim(OwningOpRef<ModuleOp>& module,
verifyExplicitPimCoreCount(); verifyExplicitPimCoreCount();
if (pimOnlyCodegen) { if (pimOnlyCodegen) {
pm.addPass(createPimInstructionSelectionPass());
pm.addPass(createPimLocalMemoryPlanningPass()); pm.addPass(createPimLocalMemoryPlanningPass());
pm.addPass(createPimVerificationPass()); pm.addPass(createPimVerificationPass());
pm.addPass(createEmitPimCodePass()); pm.addPass(createEmitPimCodePass());
@@ -315,6 +316,8 @@ void addPassesPim(OwningOpRef<ModuleOp>& module,
pm.addPass(mlir::createLowerAffinePass()); pm.addPass(mlir::createLowerAffinePass());
pm.addPass(createPimHostConstantFoldingPass()); pm.addPass(createPimHostConstantFoldingPass());
pm.addPass(createMessagePass("Pim host constants folded")); pm.addPass(createMessagePass("Pim host constants folded"));
pm.addPass(createPimInstructionSelectionPass());
pm.addPass(createMessagePass("Pim instructions selected"));
pm.addPass(createPimLocalMemoryPlanningPass()); pm.addPass(createPimLocalMemoryPlanningPass());
pm.addPass(createMessagePass("Pim local memory planned")); pm.addPass(createMessagePass("Pim local memory planned"));
pm.addPass(createPimVerificationPass()); pm.addPass(createPimVerificationPass());
+1 -83
View File
@@ -1,12 +1,6 @@
#include "mlir/Dialect/MemRef/IR/MemRef.h" #include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/Dialect/SCF/IR/SCF.h" #include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/IR/BuiltinTypes.h"
#include "llvm/ADT/STLExtras.h"
#include "src/Accelerators/PIM/Common/IR/CoreBlockUtils.hpp" #include "src/Accelerators/PIM/Common/IR/CoreBlockUtils.hpp"
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
#include "src/Accelerators/PIM/Compiler/PimCoreProgram.hpp" #include "src/Accelerators/PIM/Compiler/PimCoreProgram.hpp"
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp" #include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
@@ -20,11 +14,11 @@ static FailureOr<CompiledCoreOpKind> classifyCompiledCoreOpKind(Operation& op) {
if (isa<pim::PimMemCopyHostToDevOp>(op)) return CompiledCoreOpKind::Load; if (isa<pim::PimMemCopyHostToDevOp>(op)) return CompiledCoreOpKind::Load;
if (isa<pim::PimMemCopyDevToHostOp>(op)) return CompiledCoreOpKind::Store; if (isa<pim::PimMemCopyDevToHostOp>(op)) return CompiledCoreOpKind::Store;
if (isa<pim::PimMemCopyOp>(op)) return CompiledCoreOpKind::Lmv; if (isa<pim::PimMemCopyOp>(op)) return CompiledCoreOpKind::Lmv;
if (isa<pim::PimVMVOp>(op)) return CompiledCoreOpKind::VMV;
if (isa<pim::PimReceiveOp>(op)) return CompiledCoreOpKind::Receive; if (isa<pim::PimReceiveOp>(op)) return CompiledCoreOpKind::Receive;
if (isa<pim::PimSendOp>(op)) return CompiledCoreOpKind::Send; if (isa<pim::PimSendOp>(op)) return CompiledCoreOpKind::Send;
if (isa<pim::PimConcatOp>(op)) return CompiledCoreOpKind::Concat; if (isa<pim::PimConcatOp>(op)) return CompiledCoreOpKind::Concat;
if (isa<pim::PimVMMOp>(op)) return CompiledCoreOpKind::Vmm; if (isa<pim::PimVMMOp>(op)) return CompiledCoreOpKind::Vmm;
if (isa<pim::PimTransposeOp>(op)) return CompiledCoreOpKind::Transpose;
if (isa<pim::PimVVAddOp>(op)) return CompiledCoreOpKind::VVAdd; if (isa<pim::PimVVAddOp>(op)) return CompiledCoreOpKind::VVAdd;
if (isa<pim::PimVVSubOp>(op)) return CompiledCoreOpKind::VVSub; if (isa<pim::PimVVSubOp>(op)) return CompiledCoreOpKind::VVSub;
if (isa<pim::PimVVMulOp>(op)) return CompiledCoreOpKind::VVMul; if (isa<pim::PimVVMulOp>(op)) return CompiledCoreOpKind::VVMul;
@@ -38,77 +32,6 @@ static FailureOr<CompiledCoreOpKind> classifyCompiledCoreOpKind(Operation& op) {
return failure(); return failure();
} }
static bool isStoragePreservingTranspose(ArrayRef<size_t> sourceShape, ArrayRef<int64_t> permutation) {
SmallVector<unsigned> sourceNonUnitDims;
SmallVector<unsigned> destinationSourceNonUnitDims;
for (auto [dim, size] : llvm::enumerate(sourceShape))
if (size != 1)
sourceNonUnitDims.push_back(dim);
for (int64_t sourceDim : permutation)
if (sourceShape[sourceDim] != 1)
destinationSourceNonUnitDims.push_back(static_cast<unsigned>(sourceDim));
return sourceNonUnitDims == destinationSourceNonUnitDims;
}
static FailureOr<CompiledTransposePlan> compileTransposePlan(pim::PimTransposeOp transposeOp) {
auto sourceType = cast<ShapedType>(transposeOp.getInput().getType());
ArrayRef<int64_t> sourceShape = sourceType.getShape();
size_t rank = sourceShape.size();
CompiledTransposePlan plan;
plan.source = transposeOp.getInput();
plan.destination = transposeOp.getOutputBuffer();
plan.elementBytes = getElementTypeSizeInBytes(sourceType.getElementType());
auto totalElements = pim::checkedSize(sourceType.getNumElements(), transposeOp, "transpose elements");
if (failed(totalElements)) return failure();
plan.totalElements = *totalElements;
auto totalBytes = pim::checkedMul(plan.totalElements, plan.elementBytes, transposeOp, "transpose byte size");
if (failed(totalBytes)) return failure();
plan.totalBytes = *totalBytes;
SmallVector<int64_t> permutation = map_to_vector(transposeOp.getPermutation().getAsRange<IntegerAttr>(),
[](IntegerAttr attr) { return attr.getInt(); });
if (permutation.size() != rank) {
transposeOp.emitOpError("requires permutation rank to match source rank for PIM codegen");
return failure();
}
SmallVector<size_t> destinationShape(rank);
plan.destinationStrides.assign(rank, 1);
plan.destinationDimensionForSource.assign(rank, 0);
plan.destinationRewinds.assign(rank, 0);
SmallVector<bool> seenSourceDimensions(rank, false);
for (size_t dim = 0; dim < rank; ++dim) {
auto size = pim::checkedSize(sourceShape[dim], transposeOp, "transpose source dimension");
if (failed(size)) return failure();
plan.sourceShape.push_back(*size);
}
for (auto [destinationDim, sourceDim] : llvm::enumerate(permutation)) {
if (sourceDim < 0 || static_cast<size_t>(sourceDim) >= rank || seenSourceDimensions[sourceDim]) {
transposeOp.emitOpError("requires a valid permutation containing each source dimension exactly once");
return failure();
}
seenSourceDimensions[sourceDim] = true;
destinationShape[destinationDim] = plan.sourceShape[sourceDim];
plan.destinationDimensionForSource[sourceDim] = destinationDim;
}
for (size_t dim = rank; dim > 1; --dim) {
auto stride = pim::checkedMul(
plan.destinationStrides[dim - 1], destinationShape[dim - 1], transposeOp, "transpose destination stride");
if (failed(stride)) return failure();
plan.destinationStrides[dim - 2] = *stride;
}
for (size_t sourceDim = 0; sourceDim < rank; ++sourceDim) {
auto rewind = pim::checkedMul(plan.sourceShape[sourceDim],
plan.destinationStrides[plan.destinationDimensionForSource[sourceDim]],
transposeOp,
"transpose destination rewind");
if (failed(rewind)) return failure();
plan.destinationRewinds[sourceDim] = *rewind;
}
plan.storagePreserving = isStoragePreservingTranspose(plan.sourceShape, permutation);
return plan;
}
static LogicalResult compileCoreEmissionPlan(Block& block, SmallVectorImpl<CompiledCoreNode>& plan) { static LogicalResult compileCoreEmissionPlan(Block& block, SmallVectorImpl<CompiledCoreNode>& plan) {
for (Operation& op : block) { for (Operation& op : block) {
if (isa<pim::PimHaltOp, scf::YieldOp, memref::GetGlobalOp>(op) || isCoreStaticAddressOp(&op)) if (isa<pim::PimHaltOp, scf::YieldOp, memref::GetGlobalOp>(op) || isCoreStaticAddressOp(&op))
@@ -188,11 +111,6 @@ static LogicalResult compileCoreEmissionPlan(Block& block, SmallVectorImpl<Compi
CompiledCoreNode node; CompiledCoreNode node;
node.op = &op; node.op = &op;
node.opKind = *opKind; node.opKind = *opKind;
if (*opKind == CompiledCoreOpKind::Transpose) {
auto transposePlan = compileTransposePlan(cast<pim::PimTransposeOp>(op));
if (failed(transposePlan)) return failure();
node.transposePlan = *transposePlan;
}
plan.push_back(std::move(node)); plan.push_back(std::move(node));
} }
return success(); return success();
+1 -17
View File
@@ -6,34 +6,19 @@
#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/SmallVector.h"
#include <memory> #include <memory>
#include <optional>
#include "src/Accelerators/PIM/Common/IR/AddressAnalysis.hpp" #include "src/Accelerators/PIM/Common/IR/AddressAnalysis.hpp"
namespace onnx_mlir { namespace onnx_mlir {
struct CompiledTransposePlan {
mlir::Value source;
mlir::Value destination;
size_t elementBytes = 0;
size_t totalElements = 0;
size_t totalBytes = 0;
llvm::SmallVector<size_t> sourceShape;
llvm::SmallVector<size_t> destinationStrides;
llvm::SmallVector<unsigned> destinationDimensionForSource;
llvm::SmallVector<size_t> destinationRewinds;
bool storagePreserving = false;
};
enum class CompiledCoreOpKind : uint8_t { enum class CompiledCoreOpKind : uint8_t {
Load, Load,
Store, Store,
Lmv, Lmv,
VMV,
Receive, Receive,
Send, Send,
Concat, Concat,
Vmm, Vmm,
Transpose,
VVAdd, VVAdd,
VVSub, VVSub,
VVMul, VVMul,
@@ -62,7 +47,6 @@ struct CompiledCoreNode {
llvm::SmallVector<int64_t> caseValues; llvm::SmallVector<int64_t> caseValues;
llvm::SmallVector<std::unique_ptr<llvm::SmallVector<CompiledCoreNode, 8>>> caseBodies; llvm::SmallVector<std::unique_ptr<llvm::SmallVector<CompiledCoreNode, 8>>> caseBodies;
std::unique_ptr<llvm::SmallVector<CompiledCoreNode, 8>> defaultBody; std::unique_ptr<llvm::SmallVector<CompiledCoreNode, 8>> defaultBody;
std::optional<CompiledTransposePlan> transposePlan;
}; };
struct CompiledCoreProgram { struct CompiledCoreProgram {
@@ -180,7 +180,11 @@ FailureOr<Value> createRowStripAssemblyBlueprint(const RowStripPhysicalValue& va
kRowStripIndexMap, rewriter, loc); kRowStripIndexMap, rewriter, loc);
} }
FailureOr<Value> applyRowStripRelu(const RowStripPhysicalValue& value, PatternRewriter& rewriter, Location loc) { template <typename BuildActivation>
static FailureOr<Value> applyRowStripActivation(const RowStripPhysicalValue& value,
PatternRewriter& rewriter,
Location loc,
BuildActivation buildActivation) {
auto storageType = cast<RankedTensorType>(value.storage.getType()); auto storageType = cast<RankedTensorType>(value.storage.getType());
const int64_t laneCount = storageType.getDimSize(0); const int64_t laneCount = storageType.getDimSize(0);
auto batchOp = createSpatComputeBatch(rewriter, auto batchOp = createSpatComputeBatch(rewriter,
@@ -193,10 +197,9 @@ FailureOr<Value> applyRowStripRelu(const RowStripPhysicalValue& value, PatternRe
FailureOr<Value> fragment = extractGraphBatchPhysicalFragment( FailureOr<Value> fragment = extractGraphBatchPhysicalFragment(
rewriter, loc, args.inputs.front(), args.lane, value.fragmentType); rewriter, loc, args.inputs.front(), args.lane, value.fragmentType);
if (failed(fragment)) return failure(); if (failed(fragment)) return failure();
Value relu = spatial::SpatReluOp::create( Value result = buildActivation(*fragment);
rewriter, loc, value.fragmentType, *fragment).getResult();
publishGraphBatchPhysicalFragment( publishGraphBatchPhysicalFragment(
rewriter, loc, relu, args.outputs.front(), args.lane); rewriter, loc, result, args.outputs.front(), args.lane);
return success(); return success();
}); });
if (failed(batchOp)) if (failed(batchOp))
@@ -204,6 +207,19 @@ FailureOr<Value> applyRowStripRelu(const RowStripPhysicalValue& value, PatternRe
return batchOp->getResult(0); return batchOp->getResult(0);
} }
FailureOr<Value> applyRowStripRelu(const RowStripPhysicalValue& value, PatternRewriter& rewriter, Location loc) {
return applyRowStripActivation(value, rewriter, loc, [&](Value fragment) {
return spatial::SpatReluOp::create(rewriter, loc, value.fragmentType, fragment).getResult();
});
}
FailureOr<Value> applyRowStripSilu(const RowStripPhysicalValue& value, PatternRewriter& rewriter, Location loc) {
return applyRowStripActivation(value, rewriter, loc, [&](Value fragment) {
Value sigmoid = spatial::SpatSigmoidOp::create(rewriter, loc, value.fragmentType, fragment).getResult();
return spatial::SpatVMulOp::create(rewriter, loc, value.fragmentType, fragment, sigmoid).getResult();
});
}
FailureOr<Value> applyRowStripBiasAdd(const RowStripPhysicalValue& value, FailureOr<Value> applyRowStripBiasAdd(const RowStripPhysicalValue& value,
Value bias, Value bias,
PatternRewriter& rewriter, PatternRewriter& rewriter,
@@ -61,6 +61,10 @@ mlir::FailureOr<mlir::Value> applyRowStripRelu(const RowStripPhysicalValue& valu
mlir::PatternRewriter& rewriter, mlir::PatternRewriter& rewriter,
mlir::Location loc); mlir::Location loc);
mlir::FailureOr<mlir::Value> applyRowStripSilu(const RowStripPhysicalValue& value,
mlir::PatternRewriter& rewriter,
mlir::Location loc);
mlir::FailureOr<mlir::Value> applyRowStripBiasAdd(const RowStripPhysicalValue& value, mlir::FailureOr<mlir::Value> applyRowStripBiasAdd(const RowStripPhysicalValue& value,
mlir::Value bias, mlir::Value bias,
mlir::PatternRewriter& rewriter, mlir::PatternRewriter& rewriter,
@@ -58,6 +58,11 @@ lowerRowStripRelu(const RowStripPhysicalValue& input, spatial::SpatReluPlanOp pl
return applyRowStripRelu(input, rewriter, planOp.getLoc()); return applyRowStripRelu(input, rewriter, planOp.getLoc());
} }
static FailureOr<Value>
lowerRowStripSilu(const RowStripPhysicalValue& input, spatial::SpatSiluPlanOp planOp, PatternRewriter& rewriter) {
return applyRowStripSilu(input, rewriter, planOp.getLoc());
}
static FailureOr<Value> lowerRowStripBiasAdd(const RowStripPhysicalValue& input, static FailureOr<Value> lowerRowStripBiasAdd(const RowStripPhysicalValue& input,
spatial::SpatBiasAddPlanOp planOp, spatial::SpatBiasAddPlanOp planOp,
PatternRewriter& rewriter) { PatternRewriter& rewriter) {
@@ -349,6 +354,51 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
rewriter.replaceOp(planOp, computeOp.getResults()); rewriter.replaceOp(planOp, computeOp.getResults());
continue; continue;
} }
if (auto planOp = dyn_cast<spatial::SpatSiluPlanOp>(&op)) {
if (succeeded(getRowStripValue(rowStripValues, planOp.getInput()))) {
auto outputBlueprint = llvm::find_if(planOp.getResult().getUsers(), [](Operation* user) {
auto blueprint = dyn_cast<spatial::SpatBlueprintOp>(user);
return blueprint && blueprint.getPhysicalLayout() == kRowStripLayout;
});
if (outputBlueprint == planOp.getResult().getUsers().end()) {
planOp.emitOpError("row-strip SiLU plan requires a row-strip blueprint result");
signalPassFailure();
return;
}
FailureOr<RowStripPhysicalValue> input = getRowStripValue(rowStripValues, planOp.getInput());
rewriter.setInsertionPoint(planOp);
FailureOr<Value> lowered = lowerRowStripSilu(*input, planOp, rewriter);
if (failed(lowered)) {
planOp.emitOpError("failed to lower selected row-strip Spatial SiLU plan");
signalPassFailure();
return;
}
auto blueprint = cast<spatial::SpatBlueprintOp>(*outputBlueprint);
FailureOr<RowStripPhysicalValue> output = buildRowStripValue(blueprint, *lowered);
if (failed(output)) {
signalPassFailure();
return;
}
rowStripValues[blueprint.getResult()] = *output;
eraseAfterLowering.insert(planOp);
eraseAfterLowering.insert(blueprint);
continue;
}
rewriter.setInsertionPoint(planOp);
auto computeOp = createSpatCompute<1>(
rewriter, planOp.getLoc(), planOp.getOutput().getType(), {}, planOp.getInput(), [&](Value x) {
Value sigmoid = spatial::SpatSigmoidOp::create(
rewriter, planOp.getLoc(), planOp.getOutput().getType(), x).getResult();
Value silu = spatial::SpatVMulOp::create(
rewriter, planOp.getLoc(), planOp.getOutput().getType(), x, sigmoid).getResult();
spatial::SpatYieldOp::create(rewriter, planOp.getLoc(), silu);
});
rewriter.replaceOp(planOp, computeOp.getResults());
continue;
}
if (auto planOp = dyn_cast<spatial::SpatMaxPool2DPlanOp>(&op)) { if (auto planOp = dyn_cast<spatial::SpatMaxPool2DPlanOp>(&op)) {
auto outputBlueprint = llvm::find_if(planOp.getResult().getUsers(), [](Operation* user) { auto outputBlueprint = llvm::find_if(planOp.getResult().getUsers(), [](Operation* user) {
auto blueprint = dyn_cast<spatial::SpatBlueprintOp>(user); auto blueprint = dyn_cast<spatial::SpatBlueprintOp>(user);
@@ -650,6 +700,7 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
spatial::SpatBiasAddPlanOp, spatial::SpatBiasAddPlanOp,
spatial::SpatAddPlanOp, spatial::SpatAddPlanOp,
spatial::SpatReluPlanOp, spatial::SpatReluPlanOp,
spatial::SpatSiluPlanOp,
spatial::SpatMaxPool2DPlanOp, spatial::SpatMaxPool2DPlanOp,
spatial::SpatGlobalAveragePoolPlanOp, spatial::SpatGlobalAveragePoolPlanOp,
spatial::SpatMaterializeLayoutOp>(op) spatial::SpatMaterializeLayoutOp>(op)
@@ -8,6 +8,7 @@
#include "mlir/Pass/Pass.h" #include "mlir/Pass/Pass.h"
#include "mlir/Pass/PassManager.h" #include "mlir/Pass/PassManager.h"
#include "mlir/Transforms/Passes.h" #include "mlir/Transforms/Passes.h"
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/SmallVector.h"
@@ -50,13 +51,14 @@ static void populateEmptyFunction(func::FuncOp funcOp) {
SmallVector<spatial::SpatAddPlanOp> addPlans(funcOp.getOps<spatial::SpatAddPlanOp>()); SmallVector<spatial::SpatAddPlanOp> addPlans(funcOp.getOps<spatial::SpatAddPlanOp>());
SmallVector<spatial::SpatConcatPlanOp> concatPlans(funcOp.getOps<spatial::SpatConcatPlanOp>()); SmallVector<spatial::SpatConcatPlanOp> concatPlans(funcOp.getOps<spatial::SpatConcatPlanOp>());
SmallVector<spatial::SpatReluPlanOp> reluPlans(funcOp.getOps<spatial::SpatReluPlanOp>()); SmallVector<spatial::SpatReluPlanOp> reluPlans(funcOp.getOps<spatial::SpatReluPlanOp>());
SmallVector<spatial::SpatSiluPlanOp> siluPlans(funcOp.getOps<spatial::SpatSiluPlanOp>());
SmallVector<spatial::SpatMaxPool2DPlanOp> maxPoolPlans(funcOp.getOps<spatial::SpatMaxPool2DPlanOp>()); SmallVector<spatial::SpatMaxPool2DPlanOp> maxPoolPlans(funcOp.getOps<spatial::SpatMaxPool2DPlanOp>());
SmallVector<spatial::SpatGlobalAveragePoolPlanOp> globalAveragePoolPlans( SmallVector<spatial::SpatGlobalAveragePoolPlanOp> globalAveragePoolPlans(
funcOp.getOps<spatial::SpatGlobalAveragePoolPlanOp>()); funcOp.getOps<spatial::SpatGlobalAveragePoolPlanOp>());
SmallVector<spatial::SpatBlueprintOp> blueprints(funcOp.getOps<spatial::SpatBlueprintOp>()); SmallVector<spatial::SpatBlueprintOp> blueprints(funcOp.getOps<spatial::SpatBlueprintOp>());
SmallVector<spatial::SpatMaterializeLayoutOp> materializers(funcOp.getOps<spatial::SpatMaterializeLayoutOp>()); SmallVector<spatial::SpatMaterializeLayoutOp> materializers(funcOp.getOps<spatial::SpatMaterializeLayoutOp>());
if (!computes.empty() || !computeBatches.empty() || !convPlans.empty() || !biasAddPlans.empty() || !addPlans.empty() if (!computes.empty() || !computeBatches.empty() || !convPlans.empty() || !biasAddPlans.empty() || !addPlans.empty()
|| !concatPlans.empty() || !reluPlans.empty() || !maxPoolPlans.empty() || !blueprints.empty() || !concatPlans.empty() || !reluPlans.empty() || !siluPlans.empty() || !maxPoolPlans.empty() || !blueprints.empty()
|| !globalAveragePoolPlans.empty() || !materializers.empty()) { || !globalAveragePoolPlans.empty() || !materializers.empty()) {
return; return;
} }
@@ -121,6 +123,14 @@ void ONNXToSpatialPass::runOnOperation() {
return; return;
} }
RewritePatternSet fusionPatterns(ctx);
populateElementwiseFusionPatterns(fusionPatterns, ctx);
if (failed(applyPatternsGreedily(moduleOp, std::move(fusionPatterns)))) {
moduleOp.emitError("failed to fuse layout-aware ONNX elementwise patterns");
signalPassFailure();
return;
}
auto entryFunc = getPimEntryFunc(moduleOp); auto entryFunc = getPimEntryFunc(moduleOp);
if (failed(entryFunc)) { if (failed(entryFunc)) {
moduleOp.emitError("failed to locate the PIM entry function during ONNX-to-Spatial lowering"); moduleOp.emitError("failed to locate the PIM entry function during ONNX-to-Spatial lowering");
@@ -149,6 +149,7 @@ void verifyLogicalTopLevelOps(func::FuncOp funcOp, pim::CappedDiagnosticReporter
spatial::SpatAddPlanOp, spatial::SpatAddPlanOp,
spatial::SpatConcatPlanOp, spatial::SpatConcatPlanOp,
spatial::SpatReluPlanOp, spatial::SpatReluPlanOp,
spatial::SpatSiluPlanOp,
spatial::SpatMaxPool2DPlanOp, spatial::SpatMaxPool2DPlanOp,
spatial::SpatGlobalAveragePoolPlanOp, spatial::SpatGlobalAveragePoolPlanOp,
spatial::SpatBlueprintOp, spatial::SpatBlueprintOp,
@@ -17,6 +17,7 @@ void populateWeightPromotionPatterns(mlir::RewritePatternSet& patterns, mlir::ML
void populateConvPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx); void populateConvPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
void populateElementwisePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx); void populateElementwisePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
void populateElementwiseFusionPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
void populateGemmPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx); void populateGemmPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
void populateMatMulRewritePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx); void populateMatMulRewritePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
void populatePoolPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx); void populatePoolPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
@@ -1256,7 +1256,7 @@ static Value buildPackedWeights(DenseElementsAttr wDenseAttr,
for (int64_t kernelIndex = 0; kernelIndex < tiling.kernelElements; ++kernelIndex) { for (int64_t kernelIndex = 0; kernelIndex < tiling.kernelElements; ++kernelIndex) {
const int64_t kernelH = kernelIndex / wType.getDimSize(3); const int64_t kernelH = kernelIndex / wType.getDimSize(3);
const int64_t kernelW = kernelIndex % wType.getDimSize(3); const int64_t kernelW = kernelIndex % wType.getDimSize(3);
const int64_t targetRow = localChannel * tiling.kernelElements + kernelIndex; const int64_t targetRow = kernelIndex * tiling.channelsPerTile + localChannel;
for (int64_t multiplierIndex = 0; multiplierIndex < tiling.outputMultiplier; ++multiplierIndex) { for (int64_t multiplierIndex = 0; multiplierIndex < tiling.outputMultiplier; ++multiplierIndex) {
const int64_t globalOutChannel = globalChannel * tiling.outputMultiplier + multiplierIndex; const int64_t globalOutChannel = globalChannel * tiling.outputMultiplier + multiplierIndex;
const int64_t sourceFlatIndex = const int64_t sourceFlatIndex =
@@ -1326,16 +1326,49 @@ static Value createInputTile(Value input,
Value channelOffset = tiling.channelsPerTile == 1 Value channelOffset = tiling.channelsPerTile == 1
? channelTileIndex ? channelTileIndex
: affineMulConst(rewriter, loc, channelTileIndex, tiling.channelsPerTile, anchorOp); : affineMulConst(rewriter, loc, channelTileIndex, tiling.channelsPerTile, anchorOp);
Value tile4D = createConvInputPatch(input, Value tile4D;
inputTileType, if (dilationHeight == 1 && dilationWidth == 1) {
batchIndex, SmallVector<OpFoldResult> offsets {batchIndex, inputHeightOffset, inputWidthOffset, channelOffset};
channelOffset, SmallVector<OpFoldResult> sizes {rewriter.getIndexAttr(1),
inputHeightOffset, rewriter.getIndexAttr(inputTileType.getDimSize(1)),
inputWidthOffset, rewriter.getIndexAttr(inputTileType.getDimSize(2)),
dilationHeight, rewriter.getIndexAttr(tiling.channelsPerTile)};
dilationWidth, tile4D = tensor::ExtractSliceOp::create(
rewriter, rewriter, loc, inputTileType, input, offsets, sizes, getUnitStrides(rewriter, 4));
loc); }
else {
auto pixelType = RankedTensorType::get(
{1, 1, 1, tiling.channelsPerTile}, inputTileType.getElementType(), inputTileType.getEncoding());
tile4D = tensor::EmptyOp::create(rewriter, loc, inputTileType.getShape(), inputTileType.getElementType());
for (int64_t kernelH = 0; kernelH < inputTileType.getDimSize(1); ++kernelH)
for (int64_t kernelW = 0; kernelW < inputTileType.getDimSize(2); ++kernelW) {
Value sourceHeight = affineAddConst(rewriter, loc, inputHeightOffset, kernelH * dilationHeight, anchorOp);
Value sourceWidth = affineAddConst(rewriter, loc, inputWidthOffset, kernelW * dilationWidth, anchorOp);
SmallVector<OpFoldResult> sizes {rewriter.getIndexAttr(1),
rewriter.getIndexAttr(1),
rewriter.getIndexAttr(1),
rewriter.getIndexAttr(tiling.channelsPerTile)};
Value pixel = tensor::ExtractSliceOp::create(
rewriter,
loc,
pixelType,
input,
SmallVector<OpFoldResult> {batchIndex, sourceHeight, sourceWidth, channelOffset},
sizes,
getUnitStrides(rewriter, 4));
tile4D = tensor::InsertSliceOp::create(
rewriter,
loc,
pixel,
tile4D,
SmallVector<OpFoldResult> {rewriter.getIndexAttr(0),
rewriter.getIndexAttr(kernelH),
rewriter.getIndexAttr(kernelW),
rewriter.getIndexAttr(0)},
sizes,
getUnitStrides(rewriter, 4));
}
}
auto collapsedType = RankedTensorType::get({1, tiling.tileInputRows}, inputTileType.getElementType()); auto collapsedType = RankedTensorType::get({1, tiling.tileInputRows}, inputTileType.getElementType());
return tensor::CollapseShapeOp::create(rewriter, return tensor::CollapseShapeOp::create(rewriter,
loc, loc,
@@ -1531,10 +1564,18 @@ rewriteConv(Operation* convOp, const ConvLoweringState& state, PatternRewriter&
state.padWidthEnd, state.padWidthEnd,
rewriter, rewriter,
loc); loc);
auto paddedInputType = cast<RankedTensorType>(paddedInput.getType());
auto channelLastInputType = RankedTensorType::get({paddedInputType.getDimSize(0),
paddedInputType.getDimSize(2),
paddedInputType.getDimSize(3),
paddedInputType.getDimSize(1)},
paddedInputType.getElementType());
Value channelLastInput = ONNXTransposeOp::create(
rewriter, loc, channelLastInputType, paddedInput, rewriter.getI64ArrayAttr({0, 2, 3, 1}));
Value packedWeights = buildPackedWeights(wDenseAttr, state.wType, *tiling, rewriter, loc); Value packedWeights = buildPackedWeights(wDenseAttr, state.wType, *tiling, rewriter, loc);
Value expandedBias; Value expandedBias;
SmallVector<Value> batchInputs {paddedInput}; SmallVector<Value> batchInputs {channelLastInput};
if (state.hasBias) { if (state.hasBias) {
expandedBias = expandBiasIfNeeded(state.b, rewriter, loc); expandedBias = expandBiasIfNeeded(state.b, rewriter, loc);
auto biasType = dyn_cast<RankedTensorType>(expandedBias.getType()); auto biasType = dyn_cast<RankedTensorType>(expandedBias.getType());
@@ -1553,9 +1594,8 @@ rewriteConv(Operation* convOp, const ConvLoweringState& state, PatternRewriter&
{1, static_cast<int64_t>(crossbarSize.getValue())}, state.outType.getElementType()); {1, static_cast<int64_t>(crossbarSize.getValue())}, state.outType.getElementType());
auto piecesType = spatial::getGraphBatchPhysicalResultType( auto piecesType = spatial::getGraphBatchPhysicalResultType(
tiling->totalPatches * tiling->numChannelTiles, rowTileType); tiling->totalPatches * tiling->numChannelTiles, rowTileType);
auto paddedInputType = cast<RankedTensorType>(paddedInput.getType());
auto inputTileType = auto inputTileType =
RankedTensorType::get({1, tiling->channelsPerTile, state.wType.getDimSize(2), state.wType.getDimSize(3)}, RankedTensorType::get({1, state.wType.getDimSize(2), state.wType.getDimSize(3), tiling->channelsPerTile},
paddedInputType.getElementType()); paddedInputType.getElementType());
SmallVector<Value> batchWeights; SmallVector<Value> batchWeights;
if (tiling->numChannelTiles == 1) { if (tiling->numChannelTiles == 1) {
@@ -1766,7 +1806,7 @@ static Value unpackRowsFromParallelGemm(Value packedRows,
} }
static Value createWeightMatrix( static Value createWeightMatrix(
Value weights, const ConvGemmPlan& plan, PatternRewriter& rewriter, Location loc) { Value weights, const ConvGemmPlan& plan, bool transpose, PatternRewriter& rewriter, Location loc) {
auto buildWeightMatrix = [&](Value weight) -> Value { auto buildWeightMatrix = [&](Value weight) -> Value {
Value flattened = tensor::CollapseShapeOp::create(rewriter, Value flattened = tensor::CollapseShapeOp::create(rewriter,
loc, loc,
@@ -1776,6 +1816,8 @@ static Value createWeightMatrix(
{0}, {0},
{1, 2, 3} {1, 2, 3}
}); });
if (!transpose)
return flattened;
return ONNXTransposeOp::create(rewriter, loc, plan.wTransType, flattened, rewriter.getI64ArrayAttr({1, 0})) return ONNXTransposeOp::create(rewriter, loc, plan.wTransType, flattened, rewriter.getI64ArrayAttr({1, 0}))
.getResult(); .getResult();
}; };
@@ -1783,8 +1825,9 @@ static Value createWeightMatrix(
if (isCompileTimeComputable(weights)) if (isCompileTimeComputable(weights))
return buildWeightMatrix(weights); return buildWeightMatrix(weights);
RankedTensorType resultType = transpose ? plan.wTransType : plan.wFlatType;
auto computeOp = auto computeOp =
createSpatCompute<1>(rewriter, loc, TypeRange {plan.wTransType}, {}, ValueRange {weights}, [&](Value weight) { createSpatCompute<1>(rewriter, loc, TypeRange {resultType}, {}, ValueRange {weights}, [&](Value weight) {
spatial::SpatYieldOp::create(rewriter, loc, buildWeightMatrix(weight)); spatial::SpatYieldOp::create(rewriter, loc, buildWeightMatrix(weight));
}); });
return computeOp.getResult(0); return computeOp.getResult(0);
@@ -1852,21 +1895,26 @@ static Value createPaddedPixelMajorWeightConstant(DenseElementsAttr sourceAttr,
const ConvLoweringState& state, const ConvLoweringState& state,
int64_t paddedK, int64_t paddedK,
int64_t paddedC, int64_t paddedC,
int64_t packFactor,
PatternRewriter& rewriter) { PatternRewriter& rewriter) {
auto paddedType = RankedTensorType::get({paddedK, paddedC}, state.wType.getElementType()); auto paddedType = RankedTensorType::get({paddedK, paddedC}, state.wType.getElementType());
SmallVector<Attribute> sourceValues(sourceAttr.getValues<Attribute>()); SmallVector<Attribute> sourceValues(sourceAttr.getValues<Attribute>());
SmallVector<Attribute> paddedValues( SmallVector<Attribute> paddedValues(
paddedType.getNumElements(), cast<Attribute>(rewriter.getZeroAttr(paddedType.getElementType()))); paddedType.getNumElements(), cast<Attribute>(rewriter.getZeroAttr(paddedType.getElementType())));
for (int64_t outChannel = 0; outChannel < state.numChannelsOut; ++outChannel) const int64_t patchSize = state.numChannelsIn * state.wHeight * state.wWidth;
for (int64_t kernelH = 0; kernelH < state.wHeight; ++kernelH) for (int64_t copy = 0; copy < packFactor; ++copy)
for (int64_t kernelW = 0; kernelW < state.wWidth; ++kernelW) for (int64_t outChannel = 0; outChannel < state.numChannelsOut; ++outChannel)
for (int64_t inChannel = 0; inChannel < state.numChannelsIn; ++inChannel) { for (int64_t kernelH = 0; kernelH < state.wHeight; ++kernelH)
const int64_t sourceFlatIndex = for (int64_t kernelW = 0; kernelW < state.wWidth; ++kernelW)
(((outChannel * state.numChannelsIn) + inChannel) * state.wHeight + kernelH) * state.wWidth + kernelW; for (int64_t inChannel = 0; inChannel < state.numChannelsIn; ++inChannel) {
const int64_t patchIndex = const int64_t sourceFlatIndex =
((kernelH * state.wWidth) + kernelW) * state.numChannelsIn + inChannel; (((outChannel * state.numChannelsIn) + inChannel) * state.wHeight + kernelH) * state.wWidth + kernelW;
paddedValues[patchIndex * paddedC + outChannel] = sourceValues[sourceFlatIndex]; const int64_t patchIndex =
} ((kernelH * state.wWidth) + kernelW) * state.numChannelsIn + inChannel;
const int64_t packedRow = copy * patchSize + patchIndex;
const int64_t packedColumn = copy * state.numChannelsOut + outChannel;
paddedValues[packedRow * paddedC + packedColumn] = sourceValues[sourceFlatIndex];
}
return getOrCreateConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), return getOrCreateConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(),
DenseElementsAttr::get(paddedType, paddedValues), paddedType); DenseElementsAttr::get(paddedType, paddedValues), paddedType);
} }
@@ -2381,7 +2429,7 @@ static Value createStreamedConvRows(const ConvLoweringState& state,
Value gemmBias = state.hasBias ? state.b : createZeroGemmBias(plan.gemmOutputRowsType, rewriter); Value gemmBias = state.hasBias ? state.b : createZeroGemmBias(plan.gemmOutputRowsType, rewriter);
Value packedBias = buildPackedBias(gemmBias, biasMatrix, biasDenseAttr, state, plan, rewriter, loc); Value packedBias = buildPackedBias(gemmBias, biasMatrix, biasDenseAttr, state, plan, rewriter, loc);
Value gemmRows = ONNXGemmOp::create(rewriter, loc, plan.gemmOutputRowsType, inputRows, Value gemmRows = ONNXGemmOp::create(rewriter, loc, plan.gemmOutputRowsType, inputRows,
packedWeights, packedBias, APFloat(1.0f), APFloat(1.0f), 0, 0).getY(); packedWeights, packedBias, APFloat(1.0f), APFloat(1.0f), 0, !wDenseAttr).getY();
return maybeUnpackChunkRows(gemmRows, plan, rewriter, loc); return maybeUnpackChunkRows(gemmRows, plan, rewriter, loc);
} }
@@ -2401,9 +2449,10 @@ static Value rewritePackedIm2ColConv(const ConvLoweringState& state,
ConvGemmPlan plan = ConvGemmPlan plan =
buildConvGemmPlan(state, static_cast<bool>(wDenseAttr), !state.hasBias || static_cast<bool>(biasDenseAttr), 0, buildConvGemmPlan(state, static_cast<bool>(wDenseAttr), !state.hasBias || static_cast<bool>(biasDenseAttr), 0,
state.batchSize * state.outHeight * state.outWidth); state.batchSize * state.outHeight * state.outWidth);
// Prepare weight matrix W for crossbar storage: // Static weights use the crossbar [patchSize, Cout] layout. Runtime weights
// W: [Cout, Cin, KH, KW] -> [Cout, patchSize] -> [patchSize, Cout] // stay in ONNX's contiguous [Cout, patchSize] layout and Gemm consumes them
Value weightMatrix = createWeightMatrix(state.w, plan, rewriter, loc); // through transB without materializing a transpose.
Value weightMatrix = createWeightMatrix(state.w, plan, static_cast<bool>(wDenseAttr), rewriter, loc);
Value gemmInputRows = createIm2colRows(state, preparedInput, plan, rewriter, loc); Value gemmInputRows = createIm2colRows(state, preparedInput, plan, rewriter, loc);
Value gemmB = buildPackedWeights(wDenseAttr, weightMatrix, state, plan, rewriter, loc); Value gemmB = buildPackedWeights(wDenseAttr, weightMatrix, state, plan, rewriter, loc);
Value gemmBias = createZeroGemmBias(plan.gemmOutputRowsType, rewriter); Value gemmBias = createZeroGemmBias(plan.gemmOutputRowsType, rewriter);
@@ -2420,7 +2469,7 @@ static Value rewritePackedIm2ColConv(const ConvLoweringState& state,
APFloat(1.0f), APFloat(1.0f),
APFloat(1.0f), APFloat(1.0f),
/*transA=*/0, /*transA=*/0,
/*transB=*/0) /*transB=*/!wDenseAttr)
.getY(); .getY();
return createCollectedConvOutput(ValueRange {gemmRows}, return createCollectedConvOutput(ValueRange {gemmRows},
@@ -2452,7 +2501,7 @@ static Value rewriteStreamedConv(const ConvLoweringState& state,
ConvGemmPlan seedPlan = buildConvGemmPlan( ConvGemmPlan seedPlan = buildConvGemmPlan(
state, static_cast<bool>(wDenseAttr), !state.hasBias || static_cast<bool>(biasDenseAttr), 0, 1, forcedPackFactor); state, static_cast<bool>(wDenseAttr), !state.hasBias || static_cast<bool>(biasDenseAttr), 0, 1, forcedPackFactor);
Value weightMatrix = createWeightMatrix(state.w, seedPlan, rewriter, loc); Value weightMatrix = createWeightMatrix(state.w, seedPlan, static_cast<bool>(wDenseAttr), rewriter, loc);
Value collectedRows = createStreamedConvRows(state, Value collectedRows = createStreamedConvRows(state,
preparedInput, preparedInput,
weightMatrix, weightMatrix,
@@ -2516,6 +2565,20 @@ static bool rowStripOutputChannelTileFitsOneCore(const ConvGeometry& geometry) {
<= static_cast<int64_t>(crossbarCountInCore.getValue()); <= static_cast<int64_t>(crossbarCountInCore.getValue());
} }
static int64_t chooseRowStripPixelPackFactor(const ConvLoweringState& state, int64_t xbarDim) {
const int64_t patchSize = state.numChannelsIn * state.wHeight * state.wWidth;
const int64_t baseWeightGroups = ceilIntegerDivide(patchSize, xbarDim)
* ceilIntegerDivide(state.numChannelsOut, xbarDim);
int64_t factor = std::min(state.outWidth, xbarDim / state.numChannelsOut);
while (factor > 1
&& (state.outWidth % factor != 0
|| ceilIntegerDivide(factor * patchSize, xbarDim)
* ceilIntegerDivide(factor * state.numChannelsOut, xbarDim)
> baseWeightGroups))
--factor;
return std::max<int64_t>(factor, 1);
}
static bool canConsumePixelMajorRowStripFragments(const ConvLoweringState& state, StringRef& failureReason) { static bool canConsumePixelMajorRowStripFragments(const ConvLoweringState& state, StringRef& failureReason) {
if (state.batchSize != 1) { if (state.batchSize != 1) {
failureReason = "batch_not_one"; failureReason = "batch_not_one";
@@ -2558,6 +2621,7 @@ static Value createZeroTensorConstant(RankedTensorType type, PatternRewriter& re
} }
static FailureOr<Value> createBiasRowConstant(const ConvLoweringState& state, static FailureOr<Value> createBiasRowConstant(const ConvLoweringState& state,
int64_t packFactor,
PatternRewriter& rewriter) { PatternRewriter& rewriter) {
DenseElementsAttr denseAttr; DenseElementsAttr denseAttr;
if (!isSupportedBiasAddValue(state.b, state.outType, &denseAttr)) if (!isSupportedBiasAddValue(state.b, state.outType, &denseAttr))
@@ -2566,10 +2630,14 @@ static FailureOr<Value> createBiasRowConstant(const ConvLoweringState& state,
if (failed(channelValues)) if (failed(channelValues))
return failure(); return failure();
auto biasType = RankedTensorType::get({1, state.numChannelsOut}, state.outType.getElementType()); SmallVector<Attribute> packedValues;
packedValues.reserve(packFactor * state.numChannelsOut);
for (int64_t copy = 0; copy < packFactor; ++copy)
packedValues.append(channelValues->begin(), channelValues->end());
auto biasType = RankedTensorType::get({1, packFactor * state.numChannelsOut}, state.outType.getElementType());
return getOrCreateConstant(rewriter, return getOrCreateConstant(rewriter,
rewriter.getInsertionBlock()->getParentOp(), rewriter.getInsertionBlock()->getParentOp(),
DenseElementsAttr::get(biasType, *channelValues), DenseElementsAttr::get(biasType, packedValues),
biasType); biasType);
} }
@@ -2825,39 +2893,47 @@ static FailureOr<Value> createConvInputWindow(Value input,
Value initWindow = createZeroTensorConstant(paddedWindowType, rewriter); Value initWindow = createZeroTensorConstant(paddedWindowType, rewriter);
Operation* anchorOp = rewriter.getInsertionBlock()->getParentOp(); Operation* anchorOp = rewriter.getInsertionBlock()->getParentOp();
Value window = initWindow; Value c0 = getOrCreateIndexConstant(rewriter, anchorOp, 0);
for (int64_t kernelRowIndex = 0; kernelRowIndex < state.wHeight; ++kernelRowIndex) { Value c1 = getOrCreateIndexConstant(rewriter, anchorOp, 1);
Value kernelRow = getOrCreateIndexConstant(rewriter, anchorOp, kernelRowIndex); Value cKernelRows = getOrCreateIndexConstant(rewriter, anchorOp, state.wHeight);
auto loop = buildNormalizedScfFor(
rewriter,
loc,
c0,
cKernelRows,
c1,
ValueRange {initWindow},
[&](OpBuilder&, Location rowLoc, Value kernelRow, ValueRange iterArgs,
SmallVectorImpl<Value>& yielded) -> LogicalResult {
FailureOr<Value> sourceRow = FailureOr<Value> sourceRow =
denseInput denseInput
? FailureOr<Value>( ? FailureOr<Value>(
extractDenseConvWindowRow(input, sourceIndexTable, state, outputHeight, kernelRow, rewriter, loc)) extractDenseConvWindowRow(input, sourceIndexTable, state, outputHeight, kernelRow, rewriter, rowLoc))
: extractProjectedRowStripWindowRow(input, sourceIndexTable, state, outputHeight, kernelRow, rewriter, loc); : extractProjectedRowStripWindowRow(input, sourceIndexTable, state, outputHeight, kernelRow, rewriter, rowLoc);
if (failed(sourceRow)) if (failed(sourceRow))
return failure(); return failure();
Value semanticRow = *sourceRow; Value semanticRow = *sourceRow;
if (state.padHeightBegin != 0 || state.padHeightEnd != 0) { if (state.padHeightBegin != 0 || state.padHeightEnd != 0) {
Value mask = extractProjectedRowStripWindowMask(*maskTable, state, outputHeight, kernelRow, rewriter, loc); Value mask = extractProjectedRowStripWindowMask(*maskTable, state, outputHeight, kernelRow, rewriter, rowLoc);
semanticRow = spatial::SpatVMulOp::create(rewriter, loc, fragmentType, semanticRow, mask).getResult(); semanticRow = spatial::SpatVMulOp::create(rewriter, rowLoc, fragmentType, semanticRow, mask).getResult();
} }
Value paddedRow = createHorizontallyPaddedRowStripFragment(semanticRow, state, rewriter, loc); Value paddedRow = createHorizontallyPaddedRowStripFragment(semanticRow, state, rewriter, rowLoc);
window = tensor::InsertSliceOp::create(rewriter, yielded.push_back(tensor::InsertSliceOp::create(
loc, rewriter,
paddedRow, rowLoc,
window, paddedRow,
SmallVector<OpFoldResult> {rewriter.getIndexAttr(0), iterArgs.front(),
rewriter.getIndexAttr(kernelRowIndex), SmallVector<OpFoldResult> {rewriter.getIndexAttr(0), kernelRow,
rewriter.getIndexAttr(0), rewriter.getIndexAttr(0), rewriter.getIndexAttr(0)},
rewriter.getIndexAttr(0)}, SmallVector<OpFoldResult> {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1),
SmallVector<OpFoldResult> {rewriter.getIndexAttr(1), rewriter.getIndexAttr(state.xWidth + state.padWidthBegin
rewriter.getIndexAttr(1), + state.padWidthEnd),
rewriter.getIndexAttr( rewriter.getIndexAttr(state.numChannelsIn)},
state.xWidth + state.padWidthBegin getUnitStrides(rewriter, 4)));
+ state.padWidthEnd), return success();
rewriter.getIndexAttr(state.numChannelsIn)}, });
getUnitStrides(rewriter, 4)); return failed(loop) ? FailureOr<Value>(failure())
} : FailureOr<Value>(loop->results.front());
return window;
} }
static FailureOr<Value> createPixelMajorConvPatchRow(Value paddedWindow, static FailureOr<Value> createPixelMajorConvPatchRow(Value paddedWindow,
@@ -2878,20 +2954,92 @@ static FailureOr<Value> createPixelMajorConvPatchRow(Value paddedWindow,
rewriter.getIndexAttr(state.wHeight), rewriter.getIndexAttr(state.wHeight),
rewriter.getIndexAttr(state.wWidth), rewriter.getIndexAttr(state.wWidth),
rewriter.getIndexAttr(state.numChannelsIn)}; rewriter.getIndexAttr(state.numChannelsIn)};
SmallVector<OpFoldResult> strides {rewriter.getIndexAttr(1), Value patch;
rewriter.getIndexAttr(1), if (state.dilationWidth == 1)
rewriter.getIndexAttr(state.dilationWidth), patch = tensor::ExtractSliceOp::create(
rewriter.getIndexAttr(1)}; rewriter, loc, patchType, paddedWindow, offsets, sizes, getUnitStrides(rewriter, 4));
Value patch = tensor::ExtractSliceOp::create( else {
rewriter, loc, patchType, paddedWindow, offsets, sizes, strides); auto columnType = RankedTensorType::get({1, state.wHeight, 1, state.numChannelsIn},
state.xType.getElementType(), state.xType.getEncoding());
patch = tensor::EmptyOp::create(rewriter, loc, patchType.getShape(), patchType.getElementType());
for (int64_t kernelColumn = 0; kernelColumn < state.wWidth; ++kernelColumn) {
Value sourceWidth =
affineAddConst(rewriter, loc, inputWidthOffset, kernelColumn * state.dilationWidth, anchorOp);
SmallVector<OpFoldResult> columnSizes {rewriter.getIndexAttr(1),
rewriter.getIndexAttr(state.wHeight),
rewriter.getIndexAttr(1),
rewriter.getIndexAttr(state.numChannelsIn)};
Value column = tensor::ExtractSliceOp::create(
rewriter, loc, columnType, paddedWindow,
SmallVector<OpFoldResult> {rewriter.getIndexAttr(0), rewriter.getIndexAttr(0), sourceWidth,
rewriter.getIndexAttr(0)},
columnSizes, getUnitStrides(rewriter, 4));
patch = tensor::InsertSliceOp::create(
rewriter, loc, column, patch,
SmallVector<OpFoldResult> {rewriter.getIndexAttr(0), rewriter.getIndexAttr(0),
rewriter.getIndexAttr(kernelColumn), rewriter.getIndexAttr(0)},
columnSizes, getUnitStrides(rewriter, 4));
}
}
return tensor::CollapseShapeOp::create( return tensor::CollapseShapeOp::create(
rewriter, loc, rowType, patch, SmallVector<ReassociationIndices> {{0}, {1, 2, 3}}) rewriter, loc, rowType, patch, SmallVector<ReassociationIndices> {{0}, {1, 2, 3}})
.getResult(); .getResult();
} }
static FailureOr<Value> createPackedPixelMajorConvPatchRow(Value paddedWindow,
const ConvLoweringState& state,
Value outputGroup,
int64_t packFactor,
PatternRewriter& rewriter,
Location loc) {
if (packFactor == 1)
return createPixelMajorConvPatchRow(paddedWindow, state, outputGroup, rewriter, loc);
Operation* anchorOp = rewriter.getInsertionBlock()->getParentOp();
const int64_t patchSize = state.numChannelsIn * state.wHeight * state.wWidth;
auto packedType = RankedTensorType::get(
{1, packFactor * patchSize}, state.xType.getElementType(), state.xType.getEncoding());
Value packed = tensor::EmptyOp::create(rewriter, loc, packedType.getShape(), packedType.getElementType());
Value outputStart = affineMulConst(rewriter, loc, outputGroup, packFactor, anchorOp);
Value c0 = getOrCreateIndexConstant(rewriter, anchorOp, 0);
Value c1 = getOrCreateIndexConstant(rewriter, anchorOp, 1);
Value cPackFactor = getOrCreateIndexConstant(rewriter, anchorOp, packFactor);
auto loop = buildNormalizedScfFor(
rewriter,
loc,
c0,
cPackFactor,
c1,
ValueRange {packed},
[&](OpBuilder&, Location copyLoc, Value copy, ValueRange iterArgs,
SmallVectorImpl<Value>& yielded) -> LogicalResult {
Value outputWidth = createOrFoldAffineApply(
rewriter, copyLoc, rewriter.getAffineDimExpr(0) + rewriter.getAffineDimExpr(1),
ValueRange {outputStart, copy}, anchorOp);
FailureOr<Value> patch =
createPixelMajorConvPatchRow(paddedWindow, state, outputWidth, rewriter, copyLoc);
if (failed(patch))
return failure();
Value packedOffset = affineMulConst(rewriter, copyLoc, copy, patchSize, anchorOp);
yielded.push_back(tensor::InsertSliceOp::create(
rewriter,
copyLoc,
*patch,
iterArgs.front(),
SmallVector<OpFoldResult> {rewriter.getIndexAttr(0), packedOffset},
SmallVector<OpFoldResult> {rewriter.getIndexAttr(1), rewriter.getIndexAttr(patchSize)},
getUnitStrides(rewriter, 2)));
return success();
});
if (failed(loop))
return failure();
return loop->results.front();
}
static FailureOr<SmallVector<Value>> createConvInputTiles(Value paddedWindow, static FailureOr<SmallVector<Value>> createConvInputTiles(Value paddedWindow,
const ConvLoweringState& state, const ConvLoweringState& state,
Value outputWidth, Value outputWidth,
int64_t packFactor,
Value& partialInputScratch, Value& partialInputScratch,
int64_t patchSize, int64_t patchSize,
int64_t numKSlices, int64_t numKSlices,
@@ -2903,7 +3051,7 @@ static FailureOr<SmallVector<Value>> createConvInputTiles(Value paddedWindow,
SmallVector<Value> inputTiles; SmallVector<Value> inputTiles;
inputTiles.reserve(numKSlices); inputTiles.reserve(numKSlices);
if (state.numChannelsIn % xbarDim == 0) { if (packFactor == 1 && state.numChannelsIn % xbarDim == 0) {
Operation* anchorOp = rewriter.getInsertionBlock()->getParentOp(); Operation* anchorOp = rewriter.getInsertionBlock()->getParentOp();
auto inputTileType = RankedTensorType::get( auto inputTileType = RankedTensorType::get(
{1, 1, 1, xbarDim}, elementType, state.xType.getEncoding()); {1, 1, 1, xbarDim}, elementType, state.xType.getEncoding());
@@ -2938,8 +3086,8 @@ static FailureOr<SmallVector<Value>> createConvInputTiles(Value paddedWindow,
return inputTiles; return inputTiles;
} }
FailureOr<Value> patchRow = FailureOr<Value> patchRow = createPackedPixelMajorConvPatchRow(
createPixelMajorConvPatchRow(paddedWindow, state, outputWidth, rewriter, loc); paddedWindow, state, outputWidth, packFactor, rewriter, loc);
if (failed(patchRow)) if (failed(patchRow))
return failure(); return failure();
@@ -3081,17 +3229,18 @@ static FailureOr<Value> createRowStripConvOutput(const ConvLoweringState& state,
Value input, Value input,
Value paddedWeights, Value paddedWeights,
Value bias, Value bias,
int64_t packFactor,
int64_t paddedK, int64_t paddedK,
int64_t numKSlices, int64_t numKSlices,
int64_t xbarDim, int64_t xbarDim,
PatternRewriter& rewriter, PatternRewriter& rewriter,
Location loc) { Location loc) {
const int64_t laneCount = state.outHeight; const int64_t laneCount = state.outHeight;
const int64_t patchSize = state.numChannelsIn * state.wHeight * state.wWidth; const int64_t patchSize = packFactor * state.numChannelsIn * state.wHeight * state.wWidth;
const bool hasPartialInputTile = patchSize % xbarDim != 0; const bool hasPartialInputTile = patchSize % xbarDim != 0;
auto elementType = state.outType.getElementType(); auto elementType = state.outType.getElementType();
auto partialInputScratchType = RankedTensorType::get({1, xbarDim}, elementType); auto partialInputScratchType = RankedTensorType::get({1, xbarDim}, elementType);
auto outputPixelType = RankedTensorType::get({1, 1, 1, state.numChannelsOut}, elementType); auto outputPixelType = RankedTensorType::get({1, 1, packFactor, state.numChannelsOut}, elementType);
auto fragmentType = getRowStripFragmentType(state.outType); auto fragmentType = getRowStripFragmentType(state.outType);
auto storageType = getRowStripStorageType(state.outType); auto storageType = getRowStripStorageType(state.outType);
@@ -3106,7 +3255,7 @@ static FailureOr<Value> createRowStripConvOutput(const ConvLoweringState& state,
Operation* anchorOp = rewriter.getInsertionBlock()->getParentOp(); Operation* anchorOp = rewriter.getInsertionBlock()->getParentOp();
Value c0 = getOrCreateIndexConstant(rewriter, anchorOp, 0); Value c0 = getOrCreateIndexConstant(rewriter, anchorOp, 0);
Value c1 = getOrCreateIndexConstant(rewriter, anchorOp, 1); Value c1 = getOrCreateIndexConstant(rewriter, anchorOp, 1);
Value cOutWidth = getOrCreateIndexConstant(rewriter, anchorOp, state.outWidth); Value cOutWidth = getOrCreateIndexConstant(rewriter, anchorOp, state.outWidth / packFactor);
FailureOr<Value> inputWindow = FailureOr<Value> inputWindow =
createConvInputWindow(args.inputs.front(), state, args.lane, rewriter, loc); createConvInputWindow(args.inputs.front(), state, args.lane, rewriter, loc);
if (failed(inputWindow)) if (failed(inputWindow))
@@ -3131,6 +3280,7 @@ static FailureOr<Value> createRowStripConvOutput(const ConvLoweringState& state,
FailureOr<SmallVector<Value>> inputTiles = createConvInputTiles(*inputWindow, FailureOr<SmallVector<Value>> inputTiles = createConvInputTiles(*inputWindow,
state, state,
localColumn, localColumn,
packFactor,
partialInputScratch, partialInputScratch,
patchSize, patchSize,
numKSlices, numKSlices,
@@ -3141,7 +3291,7 @@ static FailureOr<Value> createRowStripConvOutput(const ConvLoweringState& state,
return failure(); return failure();
FailureOr<Value> output = createConvOutputRow(*inputTiles, FailureOr<Value> output = createConvOutputRow(*inputTiles,
paddedK, paddedK,
state.numChannelsOut, packFactor * state.numChannelsOut,
args.weights.front(), args.weights.front(),
bias ? args.inputs[1] : Value(), bias ? args.inputs[1] : Value(),
xbarDim, xbarDim,
@@ -3150,7 +3300,7 @@ static FailureOr<Value> createRowStripConvOutput(const ConvLoweringState& state,
if (failed(output)) if (failed(output))
return failure(); return failure();
Value outputPixel = tensor::ExpandShapeOp::create( Value outputPixel = tensor::ExpandShapeOp::create(
rewriter, pixelLoc, outputPixelType, *output, SmallVector<ReassociationIndices> {{0, 1, 2}, {3}}); rewriter, pixelLoc, outputPixelType, *output, SmallVector<ReassociationIndices> {{0, 1}, {2, 3}});
Value next = tensor::InsertSliceOp::create( Value next = tensor::InsertSliceOp::create(
rewriter, rewriter,
pixelLoc, pixelLoc,
@@ -3158,11 +3308,11 @@ static FailureOr<Value> createRowStripConvOutput(const ConvLoweringState& state,
iterArgs.front(), iterArgs.front(),
SmallVector<OpFoldResult> {rewriter.getIndexAttr(0), SmallVector<OpFoldResult> {rewriter.getIndexAttr(0),
rewriter.getIndexAttr(0), rewriter.getIndexAttr(0),
localColumn, affineMulConst(rewriter, pixelLoc, localColumn, packFactor, anchorOp),
rewriter.getIndexAttr(0)}, rewriter.getIndexAttr(0)},
SmallVector<OpFoldResult> {rewriter.getIndexAttr(1), SmallVector<OpFoldResult> {rewriter.getIndexAttr(1),
rewriter.getIndexAttr(1), rewriter.getIndexAttr(1),
rewriter.getIndexAttr(1), rewriter.getIndexAttr(packFactor),
rewriter.getIndexAttr(state.numChannelsOut)}, rewriter.getIndexAttr(state.numChannelsOut)},
getUnitStrides(rewriter, 4)); getUnitStrides(rewriter, 4));
yielded.push_back(next); yielded.push_back(next);
@@ -3255,6 +3405,7 @@ static FailureOr<Value> createOutputChannelTiledRowStripConvOutput(const ConvLow
FailureOr<SmallVector<Value>> inputTiles = createConvInputTiles(*inputWindow, FailureOr<SmallVector<Value>> inputTiles = createConvInputTiles(*inputWindow,
state, state,
widthIndex, widthIndex,
/*packFactor=*/1,
partialInputScratch, partialInputScratch,
patchSize, patchSize,
numKSlices, numKSlices,
@@ -3313,29 +3464,34 @@ static FailureOr<Value>
return failure(); return failure();
const int64_t xbarDim = geometry.xbarSize; const int64_t xbarDim = geometry.xbarSize;
const int64_t patchSize = state.numChannelsIn * state.wHeight * state.wWidth; const int64_t basePatchSize = state.numChannelsIn * state.wHeight * state.wWidth;
const int64_t numKSlices = ceilIntegerDivide(patchSize, xbarDim); const int64_t baseNumKSlices = ceilIntegerDivide(basePatchSize, xbarDim);
const int64_t paddedK = numKSlices * xbarDim; const int64_t basePaddedK = baseNumKSlices * xbarDim;
if (!rowStripOutputTileFitsOneCore(geometry)) { if (!rowStripOutputTileFitsOneCore(geometry)) {
Value tiledWeights = Value tiledWeights =
standard::createPaddedOutputChannelTiledWeightConstant(weightDenseAttr, state, paddedK, xbarDim, rewriter); standard::createPaddedOutputChannelTiledWeightConstant(weightDenseAttr, state, basePaddedK, xbarDim, rewriter);
return createOutputChannelTiledRowStripConvOutput( return createOutputChannelTiledRowStripConvOutput(
state, state.x, tiledWeights, paddedK, numKSlices, xbarDim, rewriter, loc); state, state.x, tiledWeights, basePaddedK, baseNumKSlices, xbarDim, rewriter, loc);
} }
const int64_t paddedOutputChannels = ceilIntegerDivide(state.numChannelsOut, xbarDim) * xbarDim; const int64_t packFactor = chooseRowStripPixelPackFactor(state, xbarDim);
Value paddedWeights = const int64_t packedPatchSize = packFactor * basePatchSize;
standard::createPaddedPixelMajorWeightConstant(weightDenseAttr, state, paddedK, paddedOutputChannels, rewriter); const int64_t numKSlices = ceilIntegerDivide(packedPatchSize, xbarDim);
const int64_t paddedK = numKSlices * xbarDim;
const int64_t packedOutputChannels = packFactor * state.numChannelsOut;
const int64_t paddedOutputChannels = ceilIntegerDivide(packedOutputChannels, xbarDim) * xbarDim;
Value paddedWeights = standard::createPaddedPixelMajorWeightConstant(
weightDenseAttr, state, paddedK, paddedOutputChannels, packFactor, rewriter);
FailureOr<Value> bias = failure(); FailureOr<Value> bias = failure();
if (state.hasBias) if (state.hasBias)
bias = createBiasRowConstant(state, rewriter); bias = createBiasRowConstant(state, packFactor, rewriter);
if (state.hasBias && failed(bias)) if (state.hasBias && failed(bias))
return failure(); return failure();
return createRowStripConvOutput( return createRowStripConvOutput(
state, state.x, paddedWeights, state.hasBias ? *bias : Value(), state, state.x, paddedWeights, state.hasBias ? *bias : Value(),
paddedK, numKSlices, xbarDim, rewriter, loc); packFactor, paddedK, numKSlices, xbarDim, rewriter, loc);
} }
static FailureOr<Value> createConvOutputFromPixelMajorRowStripFragments(Value rowStripStorage, static FailureOr<Value> createConvOutputFromPixelMajorRowStripFragments(Value rowStripStorage,
@@ -3351,32 +3507,36 @@ static FailureOr<Value> createConvOutputFromPixelMajorRowStripFragments(Value ro
ConvGeometry geometry = buildConvGeometry(state); ConvGeometry geometry = buildConvGeometry(state);
const int64_t xbarDim = geometry.xbarSize; const int64_t xbarDim = geometry.xbarSize;
const int64_t patchSize = state.numChannelsIn * state.wHeight * state.wWidth; const int64_t basePatchSize = state.numChannelsIn * state.wHeight * state.wWidth;
const int64_t numKSlices = ceilIntegerDivide(patchSize, xbarDim); const int64_t baseNumKSlices = ceilIntegerDivide(basePatchSize, xbarDim);
const int64_t paddedK = numKSlices * xbarDim; const int64_t basePaddedK = baseNumKSlices * xbarDim;
auto weightDenseAttr = getHostConstDenseElementsAttr(state.w); auto weightDenseAttr = getHostConstDenseElementsAttr(state.w);
if (!weightDenseAttr) if (!weightDenseAttr)
return failure(); return failure();
if (!rowStripOutputTileFitsOneCore(geometry)) { if (!rowStripOutputTileFitsOneCore(geometry)) {
Value tiledWeights = Value tiledWeights =
standard::createPaddedOutputChannelTiledWeightConstant(weightDenseAttr, state, paddedK, xbarDim, rewriter); standard::createPaddedOutputChannelTiledWeightConstant(weightDenseAttr, state, basePaddedK, xbarDim, rewriter);
return createOutputChannelTiledRowStripConvOutput( return createOutputChannelTiledRowStripConvOutput(
state, rowStripStorage, tiledWeights, paddedK, numKSlices, xbarDim, rewriter, loc); state, rowStripStorage, tiledWeights, basePaddedK, baseNumKSlices, xbarDim, rewriter, loc);
} }
const int64_t paddedOutputChannels = const int64_t packFactor = chooseRowStripPixelPackFactor(state, xbarDim);
ceilIntegerDivide(state.numChannelsOut, xbarDim) * xbarDim; const int64_t packedPatchSize = packFactor * basePatchSize;
const int64_t numKSlices = ceilIntegerDivide(packedPatchSize, xbarDim);
const int64_t paddedK = numKSlices * xbarDim;
const int64_t packedOutputChannels = packFactor * state.numChannelsOut;
const int64_t paddedOutputChannels = ceilIntegerDivide(packedOutputChannels, xbarDim) * xbarDim;
Value paddedWeights = standard::createPaddedPixelMajorWeightConstant( Value paddedWeights = standard::createPaddedPixelMajorWeightConstant(
weightDenseAttr, state, paddedK, paddedOutputChannels, rewriter); weightDenseAttr, state, paddedK, paddedOutputChannels, packFactor, rewriter);
FailureOr<Value> bias = failure(); FailureOr<Value> bias = failure();
if (state.hasBias) if (state.hasBias)
bias = createBiasRowConstant(state, rewriter); bias = createBiasRowConstant(state, packFactor, rewriter);
if (state.hasBias && failed(bias)) if (state.hasBias && failed(bias))
return failure(); return failure();
return createRowStripConvOutput( return createRowStripConvOutput(
state, rowStripStorage, paddedWeights, state.hasBias ? *bias : Value(), state, rowStripStorage, paddedWeights, state.hasBias ? *bias : Value(),
paddedK, numKSlices, xbarDim, rewriter, loc); packFactor, paddedK, numKSlices, xbarDim, rewriter, loc);
} }
static FailureOr<Value> createPointwiseOutputFromRowStripFragments(Value rowStripStorage, static FailureOr<Value> createPointwiseOutputFromRowStripFragments(Value rowStripStorage,
@@ -16,6 +16,28 @@ using namespace mlir;
namespace onnx_mlir { namespace onnx_mlir {
namespace { namespace {
struct SiluToSpatialPlan : OpRewritePattern<ONNXMulOp> {
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(ONNXMulOp mulOp, PatternRewriter& rewriter) const override {
ONNXSigmoidOp sigmoidOp = mulOp.getA().getDefiningOp<ONNXSigmoidOp>();
Value input = mulOp.getB();
if (!sigmoidOp) {
sigmoidOp = mulOp.getB().getDefiningOp<ONNXSigmoidOp>();
input = mulOp.getA();
}
if (!sigmoidOp || sigmoidOp.getX() != input || !sigmoidOp->hasOneUse()
|| sigmoidOp.getResult().getType() != input.getType() || mulOp.getResult().getType() != input.getType())
return failure();
auto plan = spatial::SpatSiluPlanOp::create(
rewriter, mulOp.getLoc(), mulOp.getResult().getType(), input, rewriter.getStringAttr("nchw"));
rewriter.replaceOp(mulOp, plan.getResult());
rewriter.eraseOp(sigmoidOp);
return success();
}
};
static DenseElementsAttr getDenseConstantAttr(Value value) { static DenseElementsAttr getDenseConstantAttr(Value value) {
if (auto constantOp = value.getDefiningOp<arith::ConstantOp>()) if (auto constantOp = value.getDefiningOp<arith::ConstantOp>())
return dyn_cast<DenseElementsAttr>(constantOp.getValue()); return dyn_cast<DenseElementsAttr>(constantOp.getValue());
@@ -219,6 +241,10 @@ struct AddToSpatialCompute : OpConversionPattern<ONNXAddOp> {
} // namespace } // namespace
void populateElementwiseFusionPatterns(RewritePatternSet& patterns, MLIRContext* ctx) {
patterns.add<SiluToSpatialPlan>(ctx);
}
void populateElementwisePatterns(RewritePatternSet& patterns, MLIRContext* ctx) { void populateElementwisePatterns(RewritePatternSet& patterns, MLIRContext* ctx) {
patterns.add<AddToSpatialCompute>(ctx); patterns.add<AddToSpatialCompute>(ctx);
patterns.add<BinaryElementwiseToSpatialCompute<ONNXSubOp, spatial::SpatVSubOp>>(ctx); patterns.add<BinaryElementwiseToSpatialCompute<ONNXSubOp, spatial::SpatVSubOp>>(ctx);
@@ -259,17 +259,6 @@ static FailureOr<spatial::SpatComputeBatch> createVmmBatch(Value a,
return *batchOp; return *batchOp;
} }
static Value
createDynamicGemmBatchRow(Value lane, int64_t numOutCols, ConversionPatternRewriter& rewriter, Location loc) {
if (numOutCols == 1)
return lane;
MLIRContext* context = rewriter.getContext();
AffineExpr d0 = getAffineDimExpr(0, context);
return createOrFoldAffineApply(
rewriter, loc, d0.floorDiv(numOutCols), ValueRange {lane}, rewriter.getInsertionBlock()->getParentOp());
}
static Value extractDynamicGemmBColumn( static Value extractDynamicGemmBColumn(
Value matrix, Value column, RankedTensorType vectorType, ConversionPatternRewriter& rewriter, Location loc) { Value matrix, Value column, RankedTensorType vectorType, ConversionPatternRewriter& rewriter, Location loc) {
SmallVector<OpFoldResult> offsets {rewriter.getIndexAttr(0), column}; SmallVector<OpFoldResult> offsets {rewriter.getIndexAttr(0), column};
@@ -373,33 +362,56 @@ static FailureOr<spatial::SpatComputeBatch> createVvdmulBatch(Value a,
Value b, Value b,
RankedTensorType aType, RankedTensorType aType,
RankedTensorType bType, RankedTensorType bType,
RankedTensorType scalarPiecesType, RankedTensorType columnPiecesType,
RankedTensorType outType, RankedTensorType outType,
bool transposeB,
ConversionPatternRewriter& rewriter, ConversionPatternRewriter& rewriter,
Location loc) { Location loc) {
const int64_t numOutRows = outType.getDimSize(0); const int64_t numOutRows = outType.getDimSize(0);
const int64_t numOutCols = outType.getDimSize(1); const int64_t numOutCols = outType.getDimSize(1);
const int64_t reductionSize = aType.getDimSize(1); const int64_t reductionSize = aType.getDimSize(1);
const int64_t laneCount = numOutRows * numOutCols; auto vectorType = RankedTensorType::get({1, reductionSize}, aType.getElementType());
auto scalarType = RankedTensorType::get({1, 1}, outType.getElementType());
auto columnType = RankedTensorType::get({numOutRows, 1}, outType.getElementType());
auto batchOp = createSpatComputeBatch( auto batchOp = createSpatComputeBatch(
rewriter, rewriter,
loc, loc,
TypeRange {scalarPiecesType}, TypeRange {columnPiecesType},
laneCount, numOutCols,
ValueRange {}, ValueRange {},
ValueRange {a, b}, ValueRange {a, b},
[&](detail::SpatComputeBatchBodyArgs args) { [&](detail::SpatComputeBatchBodyArgs args) {
Value row = createDynamicGemmBatchRow(args.lane, numOutCols, rewriter, loc); Value bVector = transposeB
Value column = ? extractDynamicGemmRowVector(args.inputs[1], args.lane, vectorType, rewriter, loc)
onnx_mlir::affineModConst(rewriter, loc, args.lane, numOutCols, rewriter.getInsertionBlock()->getParentOp()); : extractDynamicGemmBColumn(args.inputs[1], args.lane, vectorType, rewriter, loc);
Value columnInit = tensor::EmptyOp::create(rewriter, loc, columnType.getShape(), columnType.getElementType());
auto vectorType = RankedTensorType::get({1, reductionSize}, aType.getElementType()); Value c0 = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 0);
auto scalarType = RankedTensorType::get({1, 1}, outType.getElementType()); Value c1 = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 1);
Value aVector = extractDynamicGemmRowVector(args.inputs[0], row, vectorType, rewriter, loc); Value cNumOutRows =
Value bVector = extractDynamicGemmBColumn(args.inputs[1], column, vectorType, rewriter, loc); getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), numOutRows);
Value scalar = spatial::SpatVVDMulOp::create(rewriter, loc, scalarType, aVector, bVector).getResult(); auto loop = buildNormalizedScfFor(
rewriter,
publishGraphBatchPhysicalFragment(rewriter, loc, scalar, args.outputs.front(), args.lane); loc,
c0,
cNumOutRows,
c1,
ValueRange {columnInit},
[&](OpBuilder&, Location nestedLoc, Value row, ValueRange iterArgs, SmallVectorImpl<Value>& yielded) {
Value aVector = extractDynamicGemmRowVector(args.inputs[0], row, vectorType, rewriter, nestedLoc);
Value scalar = spatial::SpatVVDMulOp::create(rewriter, nestedLoc, scalarType, aVector, bVector).getResult();
Value next = tensor::InsertSliceOp::create(rewriter,
nestedLoc,
scalar,
iterArgs.front(),
SmallVector<OpFoldResult> {row, rewriter.getIndexAttr(0)},
SmallVector<OpFoldResult> {rewriter.getIndexAttr(1),
rewriter.getIndexAttr(1)},
getUnitStrides(rewriter, 2));
yielded.push_back(next);
return success();
});
assert(succeeded(loop) && "dynamic Gemm row loop construction must succeed");
publishGraphBatchPhysicalFragment(rewriter, loc, loop->results.front(), args.outputs.front(), args.lane);
}); });
if (failed(batchOp)) if (failed(batchOp))
return failure(); return failure();
@@ -415,7 +427,7 @@ static FailureOr<spatial::SpatCompute> createDynamicGemmOutputCompute(Value scal
float beta, float beta,
ConversionPatternRewriter& rewriter, ConversionPatternRewriter& rewriter,
Location loc) { Location loc) {
const int64_t laneCount = scalarPiecesType.getDimSize(0); const int64_t numOutRows = outType.getDimSize(0);
const int64_t numOutCols = outType.getDimSize(1); const int64_t numOutCols = outType.getDimSize(1);
SmallVector<Value> inputs {scalarPieces}; SmallVector<Value> inputs {scalarPieces};
if (bias) if (bias)
@@ -428,43 +440,62 @@ static FailureOr<spatial::SpatCompute> createDynamicGemmOutputCompute(Value scal
Value outputInit = tensor::EmptyOp::create(rewriter, loc, outType.getShape(), outType.getElementType()).getResult(); Value outputInit = tensor::EmptyOp::create(rewriter, loc, outType.getShape(), outType.getElementType()).getResult();
Value c0 = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 0); Value c0 = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 0);
Value c1 = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 1); Value c1 = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 1);
Value cLaneCount = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), laneCount); Value cNumOutCols = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), numOutCols);
auto columnType = RankedTensorType::get({numOutRows, 1}, outType.getElementType());
auto loop = buildNormalizedScfFor( auto loop = buildNormalizedScfFor(
rewriter, rewriter,
loc, loc,
c0, c0,
cLaneCount, cNumOutCols,
c1, c1,
ValueRange {outputInit}, ValueRange {outputInit},
[&](OpBuilder&, Location nestedLoc, Value lane, ValueRange iterArgs, SmallVectorImpl<Value>& yielded) { [&](OpBuilder&, Location nestedLoc, Value column, ValueRange iterArgs, SmallVectorImpl<Value>& yielded) {
Value outputAcc = iterArgs.front(); Value outputAcc = iterArgs.front();
Value row = createDynamicGemmBatchRow(lane, numOutCols, rewriter, nestedLoc); FailureOr<Value> columnPiece =
Value column = extractGraphBatchPhysicalFragment(rewriter, nestedLoc, pieces, column, columnType);
onnx_mlir::affineModConst(rewriter, nestedLoc, lane, numOutCols, rewriter.getInsertionBlock()->getParentOp()); if (failed(columnPiece))
SmallVector<OpFoldResult> scalarSizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
SmallVector<OpFoldResult> unitStrides {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
FailureOr<Value> scalar = extractGraphBatchPhysicalFragment(rewriter, nestedLoc, pieces, lane, scalarType);
if (failed(scalar))
return failure(); return failure();
if (alpha != 1.0f) { Value cNumOutRows =
Value alphaTensor = createScalarTensorConstant(scalarType, alpha, rewriter, nestedLoc); getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), numOutRows);
*scalar = spatial::SpatVMulOp::create(rewriter, nestedLoc, scalarType, *scalar, alphaTensor).getResult(); auto rowLoop = buildNormalizedScfFor(
} rewriter,
if (biasArg) { nestedLoc,
Value biasScalar = c0,
createBroadcastedBiasScalar(biasArg, biasType, row, column, scalarType, rewriter, nestedLoc); cNumOutRows,
if (beta != 1.0f) { c1,
Value betaTensor = createScalarTensorConstant(scalarType, beta, rewriter, nestedLoc); ValueRange {outputAcc},
biasScalar = [&](OpBuilder&, Location rowLoc, Value row, ValueRange rowIterArgs, SmallVectorImpl<Value>& rowYielded) {
spatial::SpatVMulOp::create(rewriter, nestedLoc, scalarType, biasScalar, betaTensor).getResult(); SmallVector<OpFoldResult> scalarOffsets {row, rewriter.getIndexAttr(0)};
} SmallVector<OpFoldResult> scalarSizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
*scalar = spatial::SpatVAddOp::create(rewriter, nestedLoc, scalarType, *scalar, biasScalar).getResult(); SmallVector<OpFoldResult> unitStrides {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
} Value scalar = tensor::ExtractSliceOp::create(
SmallVector<OpFoldResult> outputOffsets {row, column}; rewriter, rowLoc, scalarType, *columnPiece, scalarOffsets, scalarSizes, unitStrides);
Value outputNext = if (alpha != 1.0f) {
tensor::InsertSliceOp::create(rewriter, nestedLoc, *scalar, outputAcc, outputOffsets, scalarSizes, unitStrides) Value alphaTensor = createScalarTensorConstant(scalarType, alpha, rewriter, rowLoc);
.getResult(); scalar = spatial::SpatVMulOp::create(rewriter, rowLoc, scalarType, scalar, alphaTensor).getResult();
yielded.push_back(outputNext); }
if (biasArg) {
Value biasScalar = createBroadcastedBiasScalar(biasArg, biasType, row, column, scalarType, rewriter, rowLoc);
if (beta != 1.0f) {
Value betaTensor = createScalarTensorConstant(scalarType, beta, rewriter, rowLoc);
biasScalar =
spatial::SpatVMulOp::create(rewriter, rowLoc, scalarType, biasScalar, betaTensor).getResult();
}
scalar = spatial::SpatVAddOp::create(rewriter, rowLoc, scalarType, scalar, biasScalar).getResult();
}
Value next = tensor::InsertSliceOp::create(rewriter,
rowLoc,
scalar,
rowIterArgs.front(),
SmallVector<OpFoldResult> {row, column},
scalarSizes,
unitStrides);
rowYielded.push_back(next);
return success();
});
if (failed(rowLoop))
return failure();
yielded.push_back(rowLoop->results.front());
return success(); return success();
}); });
if (failed(loop)) if (failed(loop))
@@ -660,16 +691,10 @@ LogicalResult GemmToSpatialComputes::matchAndRewrite(ONNXGemmOp gemmOp,
aType = transposedType; aType = transposedType;
} }
if (gemmOpAdaptor.getTransB()) {
auto bShape = bType.getShape();
auto transposedType = RankedTensorType::get({bShape[1], bShape[0]}, bType.getElementType(), bType.getEncoding());
b = ONNXTransposeOp::create(rewriter, loc, transposedType, b, rewriter.getI64ArrayAttr({1, 0})).getResult();
bType = transposedType;
}
const int64_t numOutRows = outType.getDimSize(0); const int64_t numOutRows = outType.getDimSize(0);
const int64_t numOutCols = outType.getDimSize(1); const int64_t numOutCols = outType.getDimSize(1);
const int64_t reductionSize = aType.getDimSize(1); const int64_t reductionSize = aType.getDimSize(1);
const bool transposeB = gemmOpAdaptor.getTransB();
if (!isCompileTimeComputable(b)) { if (!isCompileTimeComputable(b)) {
bool hasC = hasGemmBias(c); bool hasC = hasGemmBias(c);
@@ -690,8 +715,9 @@ LogicalResult GemmToSpatialComputes::matchAndRewrite(ONNXGemmOp gemmOp,
biasType = *verifiedBiasType; biasType = *verifiedBiasType;
} }
if (aType.getDimSize(0) != numOutRows || bType.getDimSize(0) != reductionSize const int64_t bReductionSize = bType.getDimSize(transposeB ? 1 : 0);
|| bType.getDimSize(1) != numOutCols) { const int64_t bOutputColumns = bType.getDimSize(transposeB ? 0 : 1);
if (aType.getDimSize(0) != numOutRows || bReductionSize != reductionSize || bOutputColumns != numOutCols) {
gemmOp.emitOpError("has inconsistent A, B, and output shapes"); gemmOp.emitOpError("has inconsistent A, B, and output shapes");
return failure(); return failure();
} }
@@ -702,8 +728,9 @@ LogicalResult GemmToSpatialComputes::matchAndRewrite(ONNXGemmOp gemmOp,
return failure(); return failure();
} }
auto scalarPiecesType = spatial::getGraphBatchPhysicalResultType(laneCount64, RankedTensorType::get({1, 1}, outType.getElementType())); auto columnType = RankedTensorType::get({numOutRows, 1}, outType.getElementType());
auto batchOp = createVvdmulBatch(a, b, aType, bType, scalarPiecesType, outType, rewriter, loc); auto scalarPiecesType = spatial::getGraphBatchPhysicalResultType(numOutCols, columnType);
auto batchOp = createVvdmulBatch(a, b, aType, bType, scalarPiecesType, outType, transposeB, rewriter, loc);
if (failed(batchOp)) if (failed(batchOp))
return failure(); return failure();
auto outputCompute = createDynamicGemmOutputCompute( auto outputCompute = createDynamicGemmOutputCompute(
@@ -714,6 +741,13 @@ LogicalResult GemmToSpatialComputes::matchAndRewrite(ONNXGemmOp gemmOp,
return success(); return success();
} }
if (transposeB) {
auto bShape = bType.getShape();
auto transposedType = RankedTensorType::get({bShape[1], bShape[0]}, bType.getElementType(), bType.getEncoding());
b = ONNXTransposeOp::create(rewriter, loc, transposedType, b, rewriter.getI64ArrayAttr({1, 0})).getResult();
bType = transposedType;
}
auto scaledB = materializeScaledConstantTensor(b, gemmOpAdaptor.getAlpha().convertToFloat(), rewriter, loc); auto scaledB = materializeScaledConstantTensor(b, gemmOpAdaptor.getAlpha().convertToFloat(), rewriter, loc);
if (failed(scaledB)) { if (failed(scaledB)) {
gemmOp.emitOpError("requires constant Gemm input B when alpha is not 1.0"); gemmOp.emitOpError("requires constant Gemm input B when alpha is not 1.0");
@@ -389,41 +389,6 @@ static FailureOr<spatial::SpatComputeBatch> createBatchedVmmBatch(Value a,
return *batchOp; return *batchOp;
} }
static Value extractDynamicBatchedBColumn(Value matrix,
ArrayRef<int64_t> sourceBatchShape,
ArrayRef<int64_t> outputBatchShape,
Value outputBatchIndex,
Value column,
RankedTensorType vectorType,
PatternRewriter& rewriter,
Location loc) {
auto columnSliceType = RankedTensorType::get({1, vectorType.getDimSize(1), 1}, vectorType.getElementType());
Value sourceBatchIndex =
mapOutputBatchIndexToSourceBatchIndex(outputBatchIndex, sourceBatchShape, outputBatchShape, rewriter, loc);
SmallVector<OpFoldResult> offsets {OpFoldResult(sourceBatchIndex), rewriter.getIndexAttr(0), column};
SmallVector<OpFoldResult> sizes {
rewriter.getIndexAttr(1), rewriter.getIndexAttr(vectorType.getDimSize(1)), rewriter.getIndexAttr(1)};
SmallVector<OpFoldResult> strides {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
Value columnSlice = tensor::ExtractSliceOp::create(rewriter, loc, columnSliceType, matrix, offsets, sizes, strides);
auto collapsedType = RankedTensorType::get({vectorType.getDimSize(1)}, vectorType.getElementType());
Value collapsed = tensor::CollapseShapeOp::create(rewriter,
loc,
collapsedType,
columnSlice,
SmallVector<ReassociationIndices> {
{0, 1, 2}
})
.getResult();
return tensor::ExpandShapeOp::create(rewriter,
loc,
vectorType,
collapsed,
SmallVector<ReassociationIndices> {
{0, 1}
})
.getResult();
}
static Value extractDynamicBatchedRowVector(Value matrix, static Value extractDynamicBatchedRowVector(Value matrix,
ArrayRef<int64_t> sourceBatchShape, ArrayRef<int64_t> sourceBatchShape,
ArrayRef<int64_t> outputBatchShape, ArrayRef<int64_t> outputBatchShape,
@@ -449,7 +414,7 @@ static FailureOr<spatial::SpatComputeBatch> createBatchedVvdmulBatch(Value a,
ArrayRef<int64_t> outputBatchShape, ArrayRef<int64_t> outputBatchShape,
RankedTensorType aType, RankedTensorType aType,
RankedTensorType bType, RankedTensorType bType,
RankedTensorType scalarPiecesType, RankedTensorType columnPiecesType,
RankedTensorType outType, RankedTensorType outType,
PatternRewriter& rewriter, PatternRewriter& rewriter,
Location loc) { Location loc) {
@@ -457,29 +422,52 @@ static FailureOr<spatial::SpatComputeBatch> createBatchedVvdmulBatch(Value a,
const int64_t numOutRows = outType.getDimSize(1); const int64_t numOutRows = outType.getDimSize(1);
const int64_t numOutCols = outType.getDimSize(2); const int64_t numOutCols = outType.getDimSize(2);
const int64_t reductionSize = aType.getDimSize(2); const int64_t reductionSize = aType.getDimSize(2);
const int64_t laneCount = numBatches * numOutRows * numOutCols; const int64_t laneCount = numBatches * numOutCols;
auto vectorType = RankedTensorType::get({1, reductionSize}, aType.getElementType());
auto scalarType = RankedTensorType::get({1, 1}, outType.getElementType());
auto columnType = RankedTensorType::get({numOutRows, 1}, outType.getElementType());
auto batchOp = createSpatComputeBatch( auto batchOp = createSpatComputeBatch(
rewriter, rewriter,
loc, loc,
TypeRange {scalarPiecesType}, TypeRange {columnPiecesType},
laneCount, laneCount,
ValueRange {}, ValueRange {},
ValueRange {a, b}, ValueRange {a, b},
[&](detail::SpatComputeBatchBodyArgs args) { [&](detail::SpatComputeBatchBodyArgs args) {
Operation* anchorOp = rewriter.getInsertionBlock()->getParentOp(); Operation* anchorOp = rewriter.getInsertionBlock()->getParentOp();
Value batch = affineFloorDivConst(rewriter, loc, args.lane, numOutRows * numOutCols, anchorOp); Value batch = affineFloorDivConst(rewriter, loc, args.lane, numOutCols, anchorOp);
Value batchLane = affineModConst(rewriter, loc, args.lane, numOutRows * numOutCols, anchorOp); Value column = affineModConst(rewriter, loc, args.lane, numOutCols, anchorOp);
Value row = affineFloorDivConst(rewriter, loc, batchLane, numOutCols, anchorOp); Value bVector = extractDynamicBatchedRowVector(
Value column = affineModConst(rewriter, loc, batchLane, numOutCols, anchorOp);
auto vectorType = RankedTensorType::get({1, reductionSize}, aType.getElementType());
auto scalarType = RankedTensorType::get({1, 1}, outType.getElementType());
Value aVector = extractDynamicBatchedRowVector(
args.inputs[0], aBatchShape, outputBatchShape, batch, row, vectorType, rewriter, loc);
Value bVector = extractDynamicBatchedBColumn(
args.inputs[1], bBatchShape, outputBatchShape, batch, column, vectorType, rewriter, loc); args.inputs[1], bBatchShape, outputBatchShape, batch, column, vectorType, rewriter, loc);
Value scalar = spatial::SpatVVDMulOp::create(rewriter, loc, scalarType, aVector, bVector).getResult(); Value columnInit = tensor::EmptyOp::create(rewriter, loc, columnType.getShape(), columnType.getElementType());
publishGraphBatchPhysicalFragment(rewriter, loc, scalar, args.outputs.front(), args.lane); Value c0 = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 0);
Value c1 = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 1);
Value cNumOutRows =
getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), numOutRows);
auto loop = buildNormalizedScfFor(
rewriter,
loc,
c0,
cNumOutRows,
c1,
ValueRange {columnInit},
[&](OpBuilder&, Location nestedLoc, Value row, ValueRange iterArgs, SmallVectorImpl<Value>& yielded) {
Value aVector = extractDynamicBatchedRowVector(
args.inputs[0], aBatchShape, outputBatchShape, batch, row, vectorType, rewriter, nestedLoc);
Value scalar = spatial::SpatVVDMulOp::create(rewriter, nestedLoc, scalarType, aVector, bVector).getResult();
Value next = tensor::InsertSliceOp::create(rewriter,
nestedLoc,
scalar,
iterArgs.front(),
SmallVector<OpFoldResult> {row, rewriter.getIndexAttr(0)},
SmallVector<OpFoldResult> {rewriter.getIndexAttr(1),
rewriter.getIndexAttr(1)},
getUnitStrides(rewriter, 2));
yielded.push_back(next);
return success();
});
assert(succeeded(loop) && "dynamic MatMul row loop construction must succeed");
publishGraphBatchPhysicalFragment(rewriter, loc, loop->results.front(), args.outputs.front(), args.lane);
}); });
if (failed(batchOp)) if (failed(batchOp))
return failure(); return failure();
@@ -492,9 +480,8 @@ static FailureOr<Value> createBatchedDynamicOutputCompute(Value scalarPieces,
PatternRewriter& rewriter, PatternRewriter& rewriter,
Location loc) { Location loc) {
const int64_t laneCount = scalarPiecesType.getDimSize(0); const int64_t laneCount = scalarPiecesType.getDimSize(0);
const int64_t numOutRows = outType.getDimSize(1);
const int64_t numOutCols = outType.getDimSize(2); const int64_t numOutCols = outType.getDimSize(2);
auto scalarType = RankedTensorType::get({1, 1}, outType.getElementType()); auto columnType = RankedTensorType::get({outType.getDimSize(1), 1}, outType.getElementType());
auto computeOp = createSpatCompute<1>( auto computeOp = createSpatCompute<1>(
rewriter, loc, TypeRange {outType}, {}, ValueRange {scalarPieces}, [&](Value pieces) -> LogicalResult { rewriter, loc, TypeRange {outType}, {}, ValueRange {scalarPieces}, [&](Value pieces) -> LogicalResult {
@@ -513,19 +500,18 @@ static FailureOr<Value> createBatchedDynamicOutputCompute(Value scalarPieces,
[&](OpBuilder&, Location nestedLoc, Value lane, ValueRange iterArgs, SmallVectorImpl<Value>& yielded) { [&](OpBuilder&, Location nestedLoc, Value lane, ValueRange iterArgs, SmallVectorImpl<Value>& yielded) {
Value outputAcc = iterArgs.front(); Value outputAcc = iterArgs.front();
Operation* anchorOp = rewriter.getInsertionBlock()->getParentOp(); Operation* anchorOp = rewriter.getInsertionBlock()->getParentOp();
Value batch = affineFloorDivConst(rewriter, nestedLoc, lane, numOutRows * numOutCols, anchorOp); Value batch = affineFloorDivConst(rewriter, nestedLoc, lane, numOutCols, anchorOp);
Value batchLane = affineModConst(rewriter, nestedLoc, lane, numOutRows * numOutCols, anchorOp); Value column = affineModConst(rewriter, nestedLoc, lane, numOutCols, anchorOp);
Value row = affineFloorDivConst(rewriter, nestedLoc, batchLane, numOutCols, anchorOp); FailureOr<Value> columnPiece =
Value column = affineModConst(rewriter, nestedLoc, batchLane, numOutCols, anchorOp); extractGraphBatchPhysicalFragment(rewriter, nestedLoc, pieces, lane, columnType);
FailureOr<Value> scalar = extractGraphBatchPhysicalFragment(rewriter, nestedLoc, pieces, lane, scalarType); if (failed(columnPiece))
if (failed(scalar))
return failure(); return failure();
SmallVector<OpFoldResult> outputOffsets {batch, row, column}; SmallVector<OpFoldResult> outputOffsets {batch, rewriter.getIndexAttr(0), column};
SmallVector<OpFoldResult> outputSizes = { SmallVector<OpFoldResult> outputSizes = {
rewriter.getIndexAttr(1), rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)}; rewriter.getIndexAttr(1), rewriter.getIndexAttr(outType.getDimSize(1)), rewriter.getIndexAttr(1)};
Value next = Value next =
tensor::InsertSliceOp::create( tensor::InsertSliceOp::create(
rewriter, nestedLoc, *scalar, outputAcc, outputOffsets, outputSizes, getUnitStrides(rewriter, 3)) rewriter, nestedLoc, *columnPiece, outputAcc, outputOffsets, outputSizes, getUnitStrides(rewriter, 3))
.getResult(); .getResult();
yielded.push_back(next); yielded.push_back(next);
return success(); return success();
@@ -1011,12 +997,14 @@ struct MatMulBatchedToSpatialComputes : OpRewritePattern<ONNXMatMulOp> {
return success(); return success();
} }
} }
const int64_t laneCount = plan.batch * plan.m * plan.n; const int64_t laneCount = plan.batch * plan.n;
auto columnType = RankedTensorType::get({plan.m, 1}, shapeInfo->outType.getElementType());
auto scalarPiecesType = spatial::getGraphBatchPhysicalResultType( auto scalarPiecesType = spatial::getGraphBatchPhysicalResultType(
laneCount, RankedTensorType::get({1, 1}, shapeInfo->outType.getElementType())); laneCount, columnType);
Value transposedRhs = transposeLastTwoDims(plan.rhs, rewriter, loc);
auto batchOp = createBatchedVvdmulBatch(plan.lhs, auto batchOp = createBatchedVvdmulBatch(plan.lhs,
plan.lhsBatchShape, plan.lhsBatchShape,
plan.rhs, transposedRhs,
plan.rhsBatchShape, plan.rhsBatchShape,
plan.outputBatchShape, plan.outputBatchShape,
plan.lhsType, plan.lhsType,
@@ -34,6 +34,8 @@ static SelectedLayout getSelectedLayout(llvm::DenseMap<Value, SelectedLayout>& l
static bool usesSelectedRowStrip(Operation* user, llvm::DenseMap<Value, SelectedLayout>& layouts) { static bool usesSelectedRowStrip(Operation* user, llvm::DenseMap<Value, SelectedLayout>& layouts) {
if (auto reluPlan = dyn_cast<spatial::SpatReluPlanOp>(user)) if (auto reluPlan = dyn_cast<spatial::SpatReluPlanOp>(user))
return getSelectedLayout(layouts, reluPlan.getResult()) == SelectedLayout::PixelMajorRowStrip; return getSelectedLayout(layouts, reluPlan.getResult()) == SelectedLayout::PixelMajorRowStrip;
if (auto siluPlan = dyn_cast<spatial::SpatSiluPlanOp>(user))
return getSelectedLayout(layouts, siluPlan.getResult()) == SelectedLayout::PixelMajorRowStrip;
if (auto biasAddPlan = dyn_cast<spatial::SpatBiasAddPlanOp>(user)) if (auto biasAddPlan = dyn_cast<spatial::SpatBiasAddPlanOp>(user))
return getSelectedLayout(layouts, biasAddPlan.getResult()) == SelectedLayout::PixelMajorRowStrip; return getSelectedLayout(layouts, biasAddPlan.getResult()) == SelectedLayout::PixelMajorRowStrip;
if (auto addPlan = dyn_cast<spatial::SpatAddPlanOp>(user)) if (auto addPlan = dyn_cast<spatial::SpatAddPlanOp>(user))
@@ -62,7 +64,7 @@ static bool allUsersCanHandleRowStrip(Value value, llvm::DenseMap<Value, Selecte
} }
static bool canConsumeRowStripAsUser(Operation* user) { static bool canConsumeRowStripAsUser(Operation* user) {
if (isa<spatial::SpatReluPlanOp>(user)) if (isa<spatial::SpatReluPlanOp, spatial::SpatSiluPlanOp>(user))
return true; return true;
if (auto biasAddPlan = dyn_cast<spatial::SpatBiasAddPlanOp>(user)) { if (auto biasAddPlan = dyn_cast<spatial::SpatBiasAddPlanOp>(user)) {
auto resultType = dyn_cast<RankedTensorType>(biasAddPlan.getOutput().getType()); auto resultType = dyn_cast<RankedTensorType>(biasAddPlan.getOutput().getType());
@@ -105,13 +107,12 @@ static SelectedLayout chooseConvLayout(spatial::SpatConv2DPlanOp convPlan,
return SelectedLayout::PixelMajorRowStrip; return SelectedLayout::PixelMajorRowStrip;
} }
static SelectedLayout chooseReluLayout(spatial::SpatReluPlanOp reluPlan, static SelectedLayout chooseActivationLayout(Value input,
llvm::DenseMap<Value, SelectedLayout>& layouts) { Value result,
if (getSelectedLayout(layouts, reluPlan.getInput()) != SelectedLayout::PixelMajorRowStrip) llvm::DenseMap<Value, SelectedLayout>& layouts) {
if (getSelectedLayout(layouts, input) != SelectedLayout::PixelMajorRowStrip)
return SelectedLayout::DenseNchw; return SelectedLayout::DenseNchw;
if (!hasRowStripConsumer(reluPlan.getResult())) if (!allUsersCanHandleRowStrip(result, layouts))
return SelectedLayout::DenseNchw;
if (!allUsersCanHandleRowStrip(reluPlan.getResult(), layouts))
return SelectedLayout::DenseNchw; return SelectedLayout::DenseNchw;
return SelectedLayout::PixelMajorRowStrip; return SelectedLayout::PixelMajorRowStrip;
} }
@@ -239,13 +240,21 @@ struct SpatialLayoutPlanningPass final : PassWrapper<SpatialLayoutPlanningPass,
continue; continue;
} }
if (auto reluPlan = dyn_cast<spatial::SpatReluPlanOp>(&op)) { if (auto reluPlan = dyn_cast<spatial::SpatReluPlanOp>(&op)) {
SelectedLayout selected = chooseReluLayout(reluPlan, layouts); SelectedLayout selected = chooseActivationLayout(reluPlan.getInput(), reluPlan.getResult(), layouts);
if (layouts[reluPlan.getResult()] != selected) { if (layouts[reluPlan.getResult()] != selected) {
layouts[reluPlan.getResult()] = selected; layouts[reluPlan.getResult()] = selected;
changed = true; changed = true;
} }
continue; continue;
} }
if (auto siluPlan = dyn_cast<spatial::SpatSiluPlanOp>(&op)) {
SelectedLayout selected = chooseActivationLayout(siluPlan.getInput(), siluPlan.getResult(), layouts);
if (layouts[siluPlan.getResult()] != selected) {
layouts[siluPlan.getResult()] = selected;
changed = true;
}
continue;
}
if (auto biasAddPlan = dyn_cast<spatial::SpatBiasAddPlanOp>(&op)) { if (auto biasAddPlan = dyn_cast<spatial::SpatBiasAddPlanOp>(&op)) {
SelectedLayout selected = chooseBiasAddLayout(biasAddPlan, layouts); SelectedLayout selected = chooseBiasAddLayout(biasAddPlan, layouts);
if (layouts[biasAddPlan.getResult()] != selected) { if (layouts[biasAddPlan.getResult()] != selected) {
@@ -301,6 +310,8 @@ struct SpatialLayoutPlanningPass final : PassWrapper<SpatialLayoutPlanningPass,
producedValue = concatPlan.getResult(); producedValue = concatPlan.getResult();
else if (auto reluPlan = dyn_cast<spatial::SpatReluPlanOp>(&op)) else if (auto reluPlan = dyn_cast<spatial::SpatReluPlanOp>(&op))
producedValue = reluPlan.getResult(); producedValue = reluPlan.getResult();
else if (auto siluPlan = dyn_cast<spatial::SpatSiluPlanOp>(&op))
producedValue = siluPlan.getResult();
else if (auto maxPoolPlan = dyn_cast<spatial::SpatMaxPool2DPlanOp>(&op)) else if (auto maxPoolPlan = dyn_cast<spatial::SpatMaxPool2DPlanOp>(&op))
producedValue = maxPoolPlan.getResult(); producedValue = maxPoolPlan.getResult();
else if (auto averagePoolPlan = dyn_cast<spatial::SpatGlobalAveragePoolPlanOp>(&op)) else if (auto averagePoolPlan = dyn_cast<spatial::SpatGlobalAveragePoolPlanOp>(&op))
@@ -47,11 +47,11 @@ static bool isRuntimeMemoryTouch(Operation* op) {
return isa<PimMemCopyHostToDevOp, return isa<PimMemCopyHostToDevOp,
PimMemCopyDevToHostOp, PimMemCopyDevToHostOp,
PimMemCopyOp, PimMemCopyOp,
PimVMVOp,
PimReceiveOp, PimReceiveOp,
PimSendOp, PimSendOp,
PimConcatOp, PimConcatOp,
PimVMMOp, PimVMMOp,
PimTransposeOp,
PimVVAddOp, PimVVAddOp,
PimVVSubOp, PimVVSubOp,
PimVVMulOp, PimVVMulOp,
+1
View File
@@ -4,6 +4,7 @@ add_onnx_mlir_dialect_doc(pim Pim.td)
add_subdirectory(Analysis) add_subdirectory(Analysis)
add_subdirectory(Transforms/Bufferization) add_subdirectory(Transforms/Bufferization)
add_subdirectory(Transforms/HostConstantFolding) add_subdirectory(Transforms/HostConstantFolding)
add_subdirectory(Transforms/InstructionSelection)
add_subdirectory(Transforms/LocalMemoryPlanning) add_subdirectory(Transforms/LocalMemoryPlanning)
add_subdirectory(Transforms/Verification) add_subdirectory(Transforms/Verification)
+30
View File
@@ -202,6 +202,36 @@ def PimMemCopyOp : PimOp<"memcp", [DestinationStyleOpInterface]> {
}]; }];
} }
def PimVMVOp : PimOp<"vmv", [DestinationStyleOpInterface]> {
let summary = "Gather a strided vector within device memory";
let arguments = (ins
Index:$targetOffset,
Index:$sourceOffset,
Index:$sourceStride,
PimTensor:$target,
PimTensor:$source,
I32Attr:$length
);
let results = (outs
PimTensor:$output
);
let extraClassDeclaration = [{
mlir::MutableOperandRange getDpsInitsMutable() {
return getTargetMutable();
}
}];
let hasVerifier = 1;
let assemblyFormat = [{
`[` $targetOffset `,` $sourceOffset `,` $sourceStride `]`
`(` $target `,` $source `)` attr-dict
`:` type($target) `,` type($source) `->` type($output)
}];
}
def PimConcatOp : PimOp<"concat", [DestinationStyleOpInterface]> { def PimConcatOp : PimOp<"concat", [DestinationStyleOpInterface]> {
let summary = "Concatenate tensors"; let summary = "Concatenate tensors";
+14
View File
@@ -118,6 +118,20 @@ LogicalResult PimCoreBatchOp::verify() {
return verifyOnlyConstantExternalValues(getOperation(), getBody(), "pim.core_batch"); return verifyOnlyConstantExternalValues(getOperation(), getBody(), "pim.core_batch");
} }
LogicalResult PimVMVOp::verify() {
if (getLength() <= 0)
return emitError("length must be positive");
if (failed(verifyCompatibleShapedTypes(
getOperation(), getTarget().getType(), getOutput().getType(), "target and output types must match")))
return failure();
auto targetType = cast<ShapedType>(getTarget().getType());
auto sourceType = dyn_cast<ShapedType>(getSource().getType());
if (!sourceType || !haveSameShapedContainerKind(getTarget().getType(), getSource().getType())
|| targetType.getElementType() != sourceType.getElementType())
return emitError("target and source must use the same shaped container kind and element type");
return success();
}
LogicalResult PimVMMOp::verify() { LogicalResult PimVMMOp::verify() {
if (failed(verifyCompatibleShapedTypes( if (failed(verifyCompatibleShapedTypes(
getOperation(), getOutputBuffer().getType(), getOutput().getType(), "output buffer and output must match"))) getOperation(), getOutputBuffer().getType(), getOutput().getType(), "output buffer and output must match")))
@@ -0,0 +1,12 @@
add_pim_library(OMPimInstructionSelection
InstructionSelectionPass.cpp
EXCLUDE_FROM_OM_LIBS
LINK_LIBS PUBLIC
MLIRArithDialect
MLIRSCFDialect
OMPimCommon
OMPimBufferization
PimOps
)
@@ -0,0 +1,167 @@
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
#include "llvm/ADT/STLExtras.h"
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
#include "src/Accelerators/PIM/Dialect/Pim/Transforms/Bufferization/ContiguityPatterns.hpp"
#include "src/Accelerators/PIM/Pass/PIMPasses.h"
using namespace llvm;
using namespace mlir;
namespace onnx_mlir {
namespace {
static bool isStoragePreservingTranspose(ArrayRef<int64_t> sourceShape, ArrayRef<int64_t> permutation) {
SmallVector<int64_t> sourceNonUnitDims;
SmallVector<int64_t> destinationSourceNonUnitDims;
for (auto [dim, size] : llvm::enumerate(sourceShape))
if (size != 1)
sourceNonUnitDims.push_back(dim);
for (int64_t sourceDim : permutation)
if (sourceShape[sourceDim] != 1)
destinationSourceNonUnitDims.push_back(sourceDim);
return sourceNonUnitDims == destinationSourceNonUnitDims;
}
static Value indexConstant(PatternRewriter& rewriter, Location loc, int64_t value) {
return arith::ConstantIndexOp::create(rewriter, loc, value);
}
struct LowerTransposePattern final : OpRewritePattern<pim::PimTransposeOp> {
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(pim::PimTransposeOp op, PatternRewriter& rewriter) const override {
auto sourceType = dyn_cast<MemRefType>(op.getInput().getType());
auto targetType = dyn_cast<MemRefType>(op.getOutputBuffer().getType());
if (!sourceType || !targetType || !sourceType.hasStaticShape() || !targetType.hasStaticShape())
return op.emitOpError("requires static memref operands before PIM instruction selection");
ArrayRef<int64_t> sourceShape = sourceType.getShape();
size_t rank = sourceShape.size();
SmallVector<int64_t> permutation = map_to_vector(
op.getPermutation().getAsRange<IntegerAttr>(), [](IntegerAttr attr) { return attr.getInt(); });
if (permutation.size() != rank)
return op.emitOpError("requires permutation rank to match source rank");
SmallVector<bool> seen(rank, false);
for (int64_t sourceDim : permutation) {
if (sourceDim < 0 || static_cast<size_t>(sourceDim) >= rank || seen[sourceDim])
return op.emitOpError("requires a permutation containing each source dimension exactly once");
seen[sourceDim] = true;
}
auto totalElements = pim::checkedSize(sourceType.getNumElements(), op, "transpose elements");
size_t elementBytes = getElementTypeSizeInBytes(sourceType.getElementType());
auto totalBytes = failed(totalElements)
? FailureOr<size_t>(failure())
: pim::checkedMul(*totalElements, elementBytes, op, "transpose byte size");
if (failed(totalBytes))
return failure();
auto byteSizeAttr = pim::getCheckedI32Attr(rewriter, op, *totalBytes, "transpose byte size");
if (failed(byteSizeAttr))
return failure();
Location loc = op.getLoc();
Value zero = indexConstant(rewriter, loc, 0);
if (isStoragePreservingTranspose(sourceShape, permutation)) {
auto copy = pim::PimMemCopyOp::create(rewriter, loc, targetType, zero, zero,
op.getOutputBuffer(), op.getInput(), *byteSizeAttr);
rewriter.replaceOp(op, copy.getOutput());
return success();
}
SmallVector<int64_t> sourceStrides(rank, 1);
for (size_t dim = rank; dim > 1; --dim)
sourceStrides[dim - 2] = sourceStrides[dim - 1] * sourceShape[dim - 1];
SmallVector<int64_t> destinationShape;
destinationShape.reserve(rank);
for (int64_t sourceDim : permutation)
destinationShape.push_back(sourceShape[sourceDim]);
size_t runStart = rank;
int64_t runLength = 1;
int64_t sourceStride = 1;
for (size_t destinationDim = rank; destinationDim-- > 0;) {
if (destinationShape[destinationDim] == 1)
continue;
int64_t candidateStride = sourceStrides[permutation[destinationDim]];
if (runStart != rank && candidateStride != runLength * sourceStride)
break;
runStart = destinationDim;
if (runLength == 1)
sourceStride = candidateStride;
runLength *= destinationShape[destinationDim];
}
assert(runStart != rank && "non-storage-preserving transpose must have a non-unit dimension");
int64_t outerCount = sourceType.getNumElements() / runLength;
auto lengthAttr = pim::getCheckedI32Attr(rewriter, op, runLength, "vmv length");
auto runBytesAttr = pim::getCheckedI32Attr(rewriter, op, runLength * elementBytes, "transpose run byte size");
if (failed(lengthAttr) || failed(runBytesAttr))
return failure();
Value upper = indexConstant(rewriter, loc, outerCount);
Value one = indexConstant(rewriter, loc, 1);
auto loop = scf::ForOp::create(rewriter, loc, zero, upper, one, ValueRange{op.getOutputBuffer()});
rewriter.setInsertionPointToStart(loop.getBody());
Value outer = loop.getInductionVar();
Value remaining = outer;
Value sourceElementOffset = zero;
for (size_t destinationDim = runStart; destinationDim-- > 0;) {
int64_t dimension = destinationShape[destinationDim];
if (dimension == 1)
continue;
Value dimensionValue = indexConstant(rewriter, loc, dimension);
Value coordinate = arith::RemUIOp::create(rewriter, loc, remaining, dimensionValue);
remaining = arith::DivUIOp::create(rewriter, loc, remaining, dimensionValue);
Value stride = indexConstant(rewriter, loc, sourceStrides[permutation[destinationDim]]);
Value contribution = arith::MulIOp::create(rewriter, loc, coordinate, stride);
sourceElementOffset = arith::AddIOp::create(rewriter, loc, sourceElementOffset, contribution);
}
Value bytes = indexConstant(rewriter, loc, elementBytes);
Value sourceOffset = arith::MulIOp::create(rewriter, loc, sourceElementOffset, bytes);
Value runByteCount = indexConstant(rewriter, loc, runLength * elementBytes);
Value targetOffset = arith::MulIOp::create(rewriter, loc, outer, runByteCount);
Value target = loop.getRegionIterArg(0);
Value output;
if (sourceStride == 1)
output = pim::PimMemCopyOp::create(
rewriter, loc, targetType, targetOffset, sourceOffset, target, op.getInput(), *runBytesAttr).getOutput();
else
output = pim::PimVMVOp::create(rewriter, loc, targetType, targetOffset, sourceOffset,
indexConstant(rewriter, loc, sourceStride), target, op.getInput(), *lengthAttr)
.getOutput();
scf::YieldOp::create(rewriter, loc, output);
rewriter.replaceOp(op, loop.getResult(0));
return success();
}
};
struct InstructionSelectionPass : PassWrapper<InstructionSelectionPass, OperationPass<ModuleOp>> {
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(InstructionSelectionPass)
StringRef getArgument() const override { return "pim-instruction-selection"; }
StringRef getDescription() const override { return "Select explicit PIM ISA operations"; }
void runOnOperation() override {
RewritePatternSet patterns(&getContext());
patterns.add<LowerTransposePattern>(&getContext());
pim::populatePimContiguityNormalizationPatterns(patterns);
if (failed(applyPatternsGreedily(getOperation(), std::move(patterns))))
signalPassFailure();
}
};
} // namespace
std::unique_ptr<Pass> createPimInstructionSelectionPass() {
return std::make_unique<InstructionSelectionPass>();
}
} // namespace onnx_mlir
@@ -238,11 +238,11 @@ static bool isSupportedCoreInstructionOp(Operation* op) {
return isa<pim::PimMemCopyHostToDevOp, return isa<pim::PimMemCopyHostToDevOp,
pim::PimMemCopyDevToHostOp, pim::PimMemCopyDevToHostOp,
pim::PimMemCopyOp, pim::PimMemCopyOp,
pim::PimVMVOp,
pim::PimReceiveOp, pim::PimReceiveOp,
pim::PimSendOp, pim::PimSendOp,
pim::PimConcatOp, pim::PimConcatOp,
pim::PimVMMOp, pim::PimVMMOp,
pim::PimTransposeOp,
pim::PimVVAddOp, pim::PimVVAddOp,
pim::PimVVSubOp, pim::PimVVSubOp,
pim::PimVVMulOp, pim::PimVVMulOp,
+15
View File
@@ -288,6 +288,21 @@ def SpatReluPlanOp : SpatOp<"relu_plan", []> {
let hasVerifier = 1; let hasVerifier = 1;
} }
def SpatSiluPlanOp : SpatOp<"silu_plan", []> {
let summary = "Layout-aware SiLU planning op";
let arguments = (ins
SpatTensor:$input,
StrAttr:$logicalLayout
);
let results = (outs
SpatTensor:$output
);
let hasVerifier = 1;
}
def SpatMaxPool2DPlanOp : SpatOp<"max_pool2d_plan", []> { def SpatMaxPool2DPlanOp : SpatOp<"max_pool2d_plan", []> {
let summary = "Layout-aware 2D NCHW MaxPool planning op"; let summary = "Layout-aware 2D NCHW MaxPool planning op";
@@ -472,6 +472,14 @@ LogicalResult SpatReluPlanOp::verify() {
return success(); return success();
} }
LogicalResult SpatSiluPlanOp::verify() {
if (failed(verifyPlanTensorTypes(getOperation(), getInput(), getOutput(), "spat.silu_plan")))
return failure();
if (!isKnownLogicalLayout(getLogicalLayout()))
return emitError("requires a known logical layout");
return success();
}
LogicalResult SpatMaxPool2DPlanOp::verify() { LogicalResult SpatMaxPool2DPlanOp::verify() {
if (failed(verifyPlanTensorTypes(getOperation(), getInput(), getOutput(), "spat.max_pool2d_plan"))) if (failed(verifyPlanTensorTypes(getOperation(), getInput(), getOutput(), "spat.max_pool2d_plan")))
return failure(); return failure();
@@ -265,6 +265,46 @@ static FailureOr<Value> emitReceiveValue(ArrayRef<ScheduledTransferSlice> slices
return receive.getOutput(); return receive.getOutput();
} }
static FailureOr<SmallVector<LogicalTransferMetadataView, 0>>
buildRectangularReceiveMetadata(const EmitReceiveAssemblyRun &run,
unsigned laneCount) {
if (run.lanes.size() != laneCount
|| !llvm::all_of(run.entryLanes,
[&](const LaneSet &lanes) { return lanes.size() == laneCount; }))
return failure();
SmallVector<LogicalTransferMetadataView, 0> result;
for (size_t entry = 0; entry < run.positions.size(); ++entry) {
ArrayRef<ScheduledTransferSlice> slices = ArrayRef(run.slices).slice(
run.entryOffsets[entry],
run.entryOffsets[entry + 1] - run.entryOffsets[entry]);
SmallVector<const ScheduledTransferSlice *> ordered;
for (const ScheduledTransferSlice &slice : slices)
ordered.push_back(&slice);
llvm::sort(ordered,
[](const ScheduledTransferSlice *left,
const ScheduledTransferSlice *right) {
LaneInterval leftFamily = left->family->targetLanes.intervals().front();
LaneInterval rightFamily = right->family->targetLanes.intervals().front();
return leftFamily.begin + left->familyOffset
< rightFamily.begin + right->familyOffset;
});
unsigned covered = 0;
LogicalTransferMetadataView metadata;
for (const ScheduledTransferSlice *slice : ordered) {
LaneInterval family = slice->family->targetLanes.intervals().front();
unsigned begin = family.begin + slice->familyOffset;
if (begin != covered)
return failure();
appendMetadata(*slice, metadata);
covered += slice->transferCount;
}
if (covered != laneCount)
return failure();
result.push_back(std::move(metadata));
}
return result;
}
template <typename Insert> template <typename Insert>
static FailureOr<Value> emitReceiveAssembly(const EmitReceiveAssemblyRun &run, Value lane, unsigned laneCount, Value initial, static FailureOr<Value> emitReceiveAssembly(const EmitReceiveAssemblyRun &run, Value lane, unsigned laneCount, Value initial,
DeferredEmissionContext &context, Insert insert) { DeferredEmissionContext &context, Insert insert) {
@@ -275,39 +315,11 @@ static FailureOr<Value> emitReceiveAssembly(const EmitReceiveAssemblyRun &run, V
SmallVector<int64_t> counts(laneCount); SmallVector<int64_t> counts(laneCount);
std::optional<TransferGrids> transferGrids; std::optional<TransferGrids> transferGrids;
std::optional<StaticIntGrid> positions; std::optional<StaticIntGrid> positions;
SmallVector<LogicalTransferMetadataView, 0> metadataByEntry; auto metadataByEntry = buildRectangularReceiveMetadata(run, laneCount);
bool rectangular = run.lanes.size() == laneCount && llvm::all_of(run.entryLanes, [&](const LaneSet &lanes) { return lanes.size() == laneCount; }); if (succeeded(metadataByEntry)) {
for (size_t entry = 0; rectangular && entry < run.positions.size(); ++entry) {
ArrayRef<ScheduledTransferSlice> slices =
ArrayRef(run.slices).slice(run.entryOffsets[entry], run.entryOffsets[entry + 1] - run.entryOffsets[entry]);
SmallVector<const ScheduledTransferSlice *> ordered;
for (const ScheduledTransferSlice &slice : slices)
ordered.push_back(&slice);
llvm::sort(ordered, [](const ScheduledTransferSlice *left, const ScheduledTransferSlice *right) {
LaneInterval leftFamily = left->family->targetLanes.intervals().front();
LaneInterval rightFamily = right->family->targetLanes.intervals().front();
return leftFamily.begin + left->familyOffset < rightFamily.begin + right->familyOffset;
});
unsigned covered = 0;
LogicalTransferMetadataView metadata;
for (const ScheduledTransferSlice *slice : ordered) {
LaneInterval family = slice->family->targetLanes.intervals().front();
unsigned begin = family.begin + slice->familyOffset;
if (begin != covered) {
rectangular = false;
break;
}
appendMetadata(*slice, metadata);
covered += slice->transferCount;
}
rectangular &= covered == laneCount;
if (rectangular)
metadataByEntry.push_back(std::move(metadata));
}
if (rectangular) {
auto buildRows = [&](auto member) { auto buildRows = [&](auto member) {
SmallVector<StaticIntSequence> rows; SmallVector<StaticIntSequence> rows;
for (const LogicalTransferMetadataView &metadata : metadataByEntry) for (const LogicalTransferMetadataView &metadata : *metadataByEntry)
rows.push_back((metadata.*member).canonicalize()); rows.push_back((metadata.*member).canonicalize());
return StaticIntGrid::fromRows(rows); return StaticIntGrid::fromRows(rows);
}; };
@@ -400,6 +412,62 @@ static FailureOr<Value> emitReceiveAssembly(const EmitReceiveAssemblyRun &run, V
return loop->results.front(); return loop->results.front();
} }
static FailureOr<Value> transformProjectionFragment(
Value fragment, Value geometryRow, Value runtimeLane,
const DeferredProjectionLeafTemplate &leaf, const GridGeometry &geometry,
DeferredExchangePlan &exchange, DeferredEmissionContext &context) {
if (leaf.form != DeferredLeafForm::GraphBatchProjection)
return fragment;
Value shaped = extractMixedSliceOrIdentity(
context.rewriter, exchange.deferred.getLoc(), fragment,
getDeferredProjectedFragmentType(leaf),
lookupGeometry(geometry, geometryRow, runtimeLane, exchange.deferred,
context, exchange.deferred.getLoc()));
return shaped ? FailureOr<Value>(shaped)
: FailureOr<Value>(failure());
}
static bool canStreamLoopedReceiveResult(
const EmitReceiveAssemblyRun &run, unsigned laneCount,
const DeferredResultPlan &plan, DeferredEmissionContext &context) {
if (!run.collection
|| run.collection->key.kind != FragmentCollectionKind::Leaf
|| run.collection->key.leafIndex != 0
|| context.fragmentCollections.count(run.collection->key)
|| run.lanes.size() != laneCount)
return false;
if (!canRealizeDeferredLoopResult(plan))
return false;
if (run.collection->positionCount
!= plan.exchange->program.leaves.front().loopIterationCount)
return false;
return llvm::all_of(run.positions, [&](unsigned position) {
return position < run.collection->positionCount;
});
}
static FailureOr<Value> emitLoopedReceiveResult(
const EmitReceiveAssemblyRun &run, Value lane, unsigned laneCount,
const DeferredResultPlan &plan, DeferredEmissionContext &context) {
return realizeDeferredLoopResult(
plan, lane,
[&](Value initial,
DeferredLoopIterationEmitter emitIteration) -> FailureOr<Value> {
return emitReceiveAssembly(
run, lane, laneCount, initial, context,
[&](Value fragment, Value position, Value, Value runtimeLane,
Value current) -> FailureOr<Value> {
auto shaped = transformProjectionFragment(
fragment, position, runtimeLane, plan.exchange->program.leaves.front(),
plan.innerGeometry.front(), *plan.exchange, context);
return failed(shaped)
? FailureOr<Value>(failure())
: emitIteration(*shaped, position, current);
});
},
context);
}
template <typename Emit> template <typename Emit>
static LogicalResult emitCollectionUpdate(const LaneSet &lanes, Value lane, unsigned laneCount, FragmentCollectionKey key, Value current, static LogicalResult emitCollectionUpdate(const LaneSet &lanes, Value lane, unsigned laneCount, FragmentCollectionKey key, Value current,
Operation *anchor, DeferredEmissionContext &context, Emit emit, bool local = false) { Operation *anchor, DeferredEmissionContext &context, Emit emit, bool local = false) {
@@ -424,26 +492,59 @@ static LogicalResult emitCollectionUpdate(const LaneSet &lanes, Value lane, unsi
return success(); return success();
} }
static FailureOr<Value> insertProjectionFragment(Value fragment, Value specialization, Value position, Value geometryRow, Value runtimeLane, static FailureOr<Value> insertProjectionFragment(Value fragment,
Value specialization,
Value position,
unsigned fragmentPositionCount,
Value runtimeLane,
Value assembled, const DeferredProjectionLeafTemplate &leaf, const GridGeometry &geometry, Value assembled, const DeferredProjectionLeafTemplate &leaf, const GridGeometry &geometry,
DeferredExchangePlan &exchange, bool grouped, DeferredEmissionContext &context) { DeferredExchangePlan &exchange, bool grouped, DeferredEmissionContext &context) {
Value shaped = fragment; Value iteration = position;
if (leaf.form == DeferredLeafForm::GraphBatchProjection) { Value fragmentPosition = position;
RankedTensorType projectedType = getDeferredProjectedFragmentType(leaf); if (leaf.enclosingLoop && fragmentPositionCount > 1) {
shaped = extractMixedSliceOrIdentity(context.rewriter, exchange.deferred.getLoc(), shaped, Value divisor = context.constants.getIndex(fragmentPositionCount);
projectedType, iteration = arith::DivUIOp::create(
lookupGeometry(geometry, geometryRow, runtimeLane, exchange.deferred, context, exchange.deferred.getLoc())); context.rewriter, exchange.deferred.getLoc(), position, divisor);
if (!shaped) return failure(); fragmentPosition = arith::RemUIOp::create(
context.rewriter, exchange.deferred.getLoc(), position, divisor);
} }
Value geometryRow = leaf.enclosingLoop
? iteration
: context.constants.getIndex(0);
if (grouped) {
Value base = arith::MulIOp::create(
context.rewriter, exchange.deferred.getLoc(), specialization,
context.constants.getIndex(leaf.loopIterationCount));
geometryRow = arith::AddIOp::create(
context.rewriter, exchange.deferred.getLoc(), base, geometryRow);
}
auto transformed = transformProjectionFragment(
fragment, geometryRow, runtimeLane, leaf, geometry, exchange, context);
if (failed(transformed))
return failure();
Value shaped = *transformed;
auto sourceType = dyn_cast<RankedTensorType>(shaped.getType()); auto sourceType = dyn_cast<RankedTensorType>(shaped.getType());
auto assembledType = dyn_cast<RankedTensorType>(assembled.getType()); auto assembledType = dyn_cast<RankedTensorType>(assembled.getType());
int64_t rankDifference = sourceType && assembledType ? assembledType.getRank() - sourceType.getRank() : 0; int64_t rankDifference = sourceType && assembledType ? assembledType.getRank() - sourceType.getRank() : 0;
if (rankDifference < 0 || rankDifference > 2 || (grouped && rankDifference == 0)) return failure(); bool leading = sourceType && leaf.reconstructedType
if (rankDifference == 0 && sourceType != assembledType) return failure(); && leaf.reconstructedType.getRank() == sourceType.getRank() + 1
&& leaf.reconstructedType.getShape().drop_front()
== sourceType.getShape();
int64_t expectedRankDifference = static_cast<int64_t>(grouped)
+ static_cast<int64_t>(!!leaf.enclosingLoop)
+ static_cast<int64_t>(leading);
if (!sourceType || !assembledType
|| rankDifference != expectedRankDifference)
return failure();
MixedSliceGeometry slice; MixedSliceGeometry slice;
slice.offsets.assign(assembledType.getRank(), context.rewriter.getIndexAttr(0)); slice.offsets.assign(assembledType.getRank(), context.rewriter.getIndexAttr(0));
if (rankDifference) slice.offsets.front() = grouped ? specialization : position; unsigned prefix = 0;
if (rankDifference == 2) slice.offsets[1] = position; if (grouped)
slice.offsets[prefix++] = specialization;
if (leaf.enclosingLoop)
slice.offsets[prefix++] = iteration;
if (leading)
slice.offsets[prefix++] = fragmentPosition;
slice.sizes.assign(rankDifference, context.rewriter.getIndexAttr(1)); slice.sizes.assign(rankDifference, context.rewriter.getIndexAttr(1));
for (int64_t dimension : sourceType.getShape()) slice.sizes.push_back(context.rewriter.getIndexAttr(dimension)); for (int64_t dimension : sourceType.getShape()) slice.sizes.push_back(context.rewriter.getIndexAttr(dimension));
slice.strides.assign(assembledType.getRank(), context.rewriter.getIndexAttr(1)); slice.strides.assign(assembledType.getRank(), context.rewriter.getIndexAttr(1));
@@ -488,7 +589,10 @@ static LogicalResult emitLeafCollectionUpdate(const EmitReceiveAssemblyRun &run,
specialization = arith::DivUIOp::create(context.rewriter, exchange.deferred.getLoc(), position, divisor); specialization = arith::DivUIOp::create(context.rewriter, exchange.deferred.getLoc(), position, divisor);
leafPosition = arith::RemUIOp::create(context.rewriter, exchange.deferred.getLoc(), position, divisor); leafPosition = arith::RemUIOp::create(context.rewriter, exchange.deferred.getLoc(), position, divisor);
} }
return insertProjectionFragment(fragment, specialization, leafPosition, grouped ? specialization : context.constants.getIndex(0), runtimeLane, unsigned fragmentPositionCount =
collection.positionCount / leaf.loopIterationCount;
return insertProjectionFragment(fragment, specialization, leafPosition,
fragmentPositionCount, runtimeLane,
assembled, leaf, geometry, exchange, grouped, context); assembled, leaf, geometry, exchange, grouped, context);
}); });
}; };
@@ -642,10 +746,11 @@ static LogicalResult emitLoopedLocalCollectionUpdate(
unsigned leafIndex = collection.key.leafIndex; unsigned leafIndex = collection.key.leafIndex;
const DeferredProjectionLeafTemplate &leaf = const DeferredProjectionLeafTemplate &leaf =
exchange.program.leaves[leafIndex]; exchange.program.leaves[leafIndex];
unsigned fragmentPositionCount =
collection.positionCount / leaf.loopIterationCount;
auto inserted = insertProjectionFragment( auto inserted = insertProjectionFragment(
*source, specialization, leafPosition, *source, specialization, leafPosition,
grouped ? specialization : context.constants.getIndex(0), fragmentPositionCount, runtimeLane, iterArgs.front(), leaf,
runtimeLane, iterArgs.front(), leaf,
resultPlan.innerGeometry[leafIndex], exchange, grouped, context); resultPlan.innerGeometry[leafIndex], exchange, grouped, context);
if (failed(inserted)) if (failed(inserted))
return failure(); return failure();
@@ -818,8 +923,10 @@ static LogicalResult emitLocalCollectionUpdate(const EmitLocalCollectionRun &upd
auto emit = [&](Value assembled) -> FailureOr<Value> { auto emit = [&](Value assembled) -> FailureOr<Value> {
auto fragment = materialize(); auto fragment = materialize();
if (failed(fragment)) return failure(); if (failed(fragment)) return failure();
unsigned fragmentPositionCount =
collection.positionCount / leaf.loopIterationCount;
return insertProjectionFragment(*fragment, context.constants.getIndex(specialization), return insertProjectionFragment(*fragment, context.constants.getIndex(specialization),
context.constants.getIndex(leafPosition), context.constants.getIndex(specialization), context.constants.getIndex(leafPosition), fragmentPositionCount,
lane ? lane : context.constants.getIndex(0), assembled, leaf, resultPlan.innerGeometry[leafIndex], exchange, grouped, context); lane ? lane : context.constants.getIndex(0), assembled, leaf, resultPlan.innerGeometry[leafIndex], exchange, grouped, context);
}; };
return emitCollectionUpdate(update.lanes, lane, laneCount, collection.key, current, exchange.deferred, context, emit, true); return emitCollectionUpdate(update.lanes, lane, laneCount, collection.key, current, exchange.deferred, context, emit, true);
@@ -828,7 +935,29 @@ static LogicalResult emitLocalCollectionUpdate(const EmitLocalCollectionRun &upd
static FailureOr<SmallVector<Value>> emitInstructions(ArrayRef<BoundaryInstruction> instructions, Value lane, unsigned laneCount, static FailureOr<SmallVector<Value>> emitInstructions(ArrayRef<BoundaryInstruction> instructions, Value lane, unsigned laneCount,
ArrayRef<DeferredResultPlan> results, DeferredEmissionContext &context) { ArrayRef<DeferredResultPlan> results, DeferredEmissionContext &context) {
SmallVector<Value> produced; SmallVector<Value> produced;
for (const BoundaryInstruction &instruction : instructions) { for (size_t instructionIndex = 0;
instructionIndex < instructions.size(); ++instructionIndex) {
const BoundaryInstruction &instruction = instructions[instructionIndex];
if (auto assembly = std::get_if<EmitReceiveAssemblyRun>(&instruction)) {
if (instructionIndex + 1 < instructions.size())
if (auto result = std::get_if<ProduceDeferredResult>(
&instructions[instructionIndex + 1])) {
const DeferredResultPlan *plan = findResultPlan(results, result->exchange);
if (plan && assembly->collection
&& assembly->collection->key.exchange == result->exchange
&& canStreamLoopedReceiveResult(
*assembly, laneCount, *plan, context)) {
auto value = emitLoopedReceiveResult(
*assembly, lane, laneCount, *plan, context);
if (failed(value))
return result->exchange->deferred.emitOpError(
"failed to stream loop-indexed received result"), failure();
produced.push_back(*value);
++instructionIndex;
continue;
}
}
}
if (auto send = std::get_if<EmitSendRun>(&instruction)) { if (auto send = std::get_if<EmitSendRun>(&instruction)) {
if (failed(emitConditionalSendRun(*send, lane, laneCount, context))) if (failed(emitConditionalSendRun(*send, lane, laneCount, context)))
return failure(); return failure();
@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "mlir/Dialect/Tensor/IR/Tensor.h" #include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/IR/Operation.h" #include "mlir/IR/Operation.h"
#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/ArrayRef.h"
@@ -146,6 +147,8 @@ struct DeferredProjectionLeafTemplate {
DeferredSliceTemplate innerGeometry; DeferredSliceTemplate innerGeometry;
mlir::RankedTensorType reconstructedType; mlir::RankedTensorType reconstructedType;
bool leadingRankReduced = false; bool leadingRankReduced = false;
mlir::scf::ForOp enclosingLoop;
unsigned loopIterationCount = 1;
}; };
struct DeferredInsertAssemblyEntryTemplate { struct DeferredInsertAssemblyEntryTemplate {
@@ -171,6 +171,13 @@ static bool isTopLevelDeferredOperation(Operation *op, Block &body,
&& (isDeferredPayloadCandidateOp(op) || isa<scf::ForOp>(op)); && (isDeferredPayloadCandidateOp(op) || isa<scf::ForOp>(op));
} }
static Operation *getTopLevelDeferredOperation(
Operation *op, Block &body, const DeferredInputPlan &plan) {
while (op && op->getBlock() != &body)
op = op->getParentOp();
return op && isTopLevelDeferredOperation(op, body, plan) ? op : nullptr;
}
static bool isEligible(Value value, Block &body, const DeferredInputPlan &plan, static bool isEligible(Value value, Block &body, const DeferredInputPlan &plan,
llvm::SmallPtrSetImpl<Operation *> &seen) { llvm::SmallPtrSetImpl<Operation *> &seen) {
if (value == plan.graphInput || value == plan.graphLane || value == plan.scheduledLane) if (value == plan.graphInput || value == plan.graphLane || value == plan.scheduledLane)
@@ -183,6 +190,27 @@ static bool isEligible(Value value, Block &body, const DeferredInputPlan &plan,
return true; return true;
if (!op || !isTopLevelDeferredOperation(op, body, plan) || !seen.insert(op).second) if (!op || !isTopLevelDeferredOperation(op, body, plan) || !seen.insert(op).second)
return op && seen.contains(op); return op && seen.contains(op);
if (auto loop = dyn_cast<scf::ForOp>(op)) {
bool eligible = true;
loop.getRegion().walk([&](Operation *nested) {
if (isa<scf::ForOp>(nested) && nested != loop)
eligible = false;
for (Value operand : nested->getOperands()) {
Operation *definition = operand.getDefiningOp();
auto argument = dyn_cast<BlockArgument>(operand);
Region *argumentRegion = argument
? argument.getOwner()->getParent() : nullptr;
bool definedInside = definition
? loop->isProperAncestor(definition)
: argumentRegion == &loop.getRegion()
|| (argumentRegion && loop.getRegion().isAncestor(argumentRegion));
if (!definedInside && !isEligible(operand, body, plan, seen))
eligible = false;
}
});
if (!eligible)
return false;
}
return llvm::all_of(op->getOperands(), [&](Value operand) { return isEligible(operand, body, plan, seen); }); return llvm::all_of(op->getOperands(), [&](Value operand) { return isEligible(operand, body, plan, seen); });
} }
@@ -198,8 +226,16 @@ static FailureOr<Value> clonePayloadRoot(Value root, Block &body, const Deferred
argument && argument.getOwner() == &transfer.getBody().front() argument && argument.getOwner() == &transfer.getBody().front()
&& argument.getArgNumber() >= transfer.getSources().size()) && argument.getArgNumber() >= transfer.getSources().size())
return value; return value;
if (isa<BlockArgument>(value)) if (auto argument = dyn_cast<BlockArgument>(value)) {
return transfer.emitOpError("phase 1 payload shaping captures an unsupported block argument"), failure(); Operation *owner = argument.getOwner()->getParentOp();
if (isa_and_nonnull<scf::ForOp>(owner)
&& owner->isProperAncestor(transfer))
return value;
return transfer.emitOpError(
"phase 1 scheduled graph-lane expression captures an unsupported block argument: ")
<< value << " owned by " << owner->getName(),
failure();
}
Operation *op = value.getDefiningOp(); Operation *op = value.getDefiningOp();
if (!op || (!isDeferredPayloadCandidateOp(op) && !op->hasTrait<OpTrait::ConstantLike>())) if (!op || (!isDeferredPayloadCandidateOp(op) && !op->hasTrait<OpTrait::ConstantLike>()))
return transfer.emitOpError("phase 1 cannot clone the scheduled graph-lane expression"), failure(); return transfer.emitOpError("phase 1 cannot clone the scheduled graph-lane expression"), failure();
@@ -216,11 +252,39 @@ static FailureOr<Value> clonePayloadRoot(Value root, Block &body, const Deferred
mapping.map(value, *mappedLane); mapping.map(value, *mappedLane);
return *mappedLane; return *mappedLane;
} }
if (isa<BlockArgument>(value)) if (auto argument = dyn_cast<BlockArgument>(value)) {
return transfer.emitOpError("phase 1 payload shaping captures an unsupported block argument"), failure(); Operation *owner = argument.getOwner()->getParentOp();
if (isa_and_nonnull<scf::ForOp>(owner)
&& owner->isProperAncestor(transfer))
return value;
return transfer.emitOpError(
"phase 1 payload shaping captures an unsupported block argument: ")
<< value << " owned by " << owner->getName(), failure();
}
Operation *op = value.getDefiningOp(); Operation *op = value.getDefiningOp();
if (!op || (!isTopLevelDeferredOperation(op, body, plan) && !op->hasTrait<OpTrait::ConstantLike>())) if (!op || (!isTopLevelDeferredOperation(op, body, plan) && !op->hasTrait<OpTrait::ConstantLike>()))
return transfer.emitOpError("phase 1 payload shaping contains an unsupported operation"), failure(); return transfer.emitOpError("phase 1 payload shaping contains an unsupported operation"), failure();
if (auto loop = dyn_cast<scf::ForOp>(op)) {
SmallVector<Value> captures;
loop.getRegion().walk([&](Operation *nested) {
for (Value operand : nested->getOperands()) {
Operation *definition = operand.getDefiningOp();
auto argument = dyn_cast<BlockArgument>(operand);
Region *argumentRegion = argument
? argument.getOwner()->getParent() : nullptr;
bool definedInside = definition
? loop->isProperAncestor(definition)
: argumentRegion == &loop.getRegion()
|| (argumentRegion
&& loop.getRegion().isAncestor(argumentRegion));
if (!definedInside && !mapping.contains(operand))
captures.push_back(operand);
}
});
for (Value capture : captures)
if (!mapping.contains(capture) && failed(clone(capture)))
return failure();
}
for (Value operand : op->getOperands()) if (failed(clone(operand))) return failure(); for (Value operand : op->getOperands()) if (failed(clone(operand))) return failure();
Operation *copy = builder.clone(*op, mapping); Operation *copy = builder.clone(*op, mapping);
for (auto pair : llvm::zip(op->getResults(), copy->getResults())) mapping.map(std::get<0>(pair), std::get<1>(pair)); for (auto pair : llvm::zip(op->getResults(), copy->getResults())) mapping.map(std::get<0>(pair), std::get<1>(pair));
@@ -352,10 +416,19 @@ LogicalResult materializeDeferredPayloadDemands(OpBuilder &builder, Location loc
Value value = worklist.pop_back_val(); Value value = worklist.pop_back_val();
if (!seen.insert(value).second) continue; if (!seen.insert(value).second) continue;
for (OpOperand &use : value.getUses()) { for (OpOperand &use : value.getUses()) {
Operation *user = use.getOwner(); Operation *user = getTopLevelDeferredOperation(
if (!isTopLevelDeferredOperation(user, body, plan)) { needsIdentity = true; continue; } use.getOwner(), body, plan);
if (!user) {
if (value == plan.graphInput) needsIdentity = true;
else roots.push_back(value);
continue;
}
llvm::SmallPtrSet<Operation *, 16> eligibility; llvm::SmallPtrSet<Operation *, 16> eligibility;
if (!isEligible(user->getResult(0), body, plan, eligibility)) { needsIdentity = true; continue; } if (!isEligible(user->getResult(0), body, plan, eligibility)) {
if (value == plan.graphInput) needsIdentity = true;
else roots.push_back(value);
continue;
}
for (Value result : user->getResults()) { for (Value result : user->getResults()) {
bool hasShapingUse = llvm::any_of(result.getUses(), [&](OpOperand &next) { return isTopLevelDeferredOperation(next.getOwner(), body, plan); }); bool hasShapingUse = llvm::any_of(result.getUses(), [&](OpOperand &next) { return isTopLevelDeferredOperation(next.getOwner(), body, plan); });
bool hasOtherUse = llvm::any_of(result.getUses(), [&](OpOperand &next) { return !isTopLevelDeferredOperation(next.getOwner(), body, plan); }); bool hasOtherUse = llvm::any_of(result.getUses(), [&](OpOperand &next) { return !isTopLevelDeferredOperation(next.getOwner(), body, plan); });
@@ -8,6 +8,8 @@
#include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallSet.h"
#include <limits>
#include "src/Accelerators/PIM/Common/IR/ShapingUtils.hpp" #include "src/Accelerators/PIM/Common/IR/ShapingUtils.hpp"
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp" #include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
@@ -187,6 +189,35 @@ static bool isInsideDeferredLoop(Operation *op,
return false; return false;
} }
static FailureOr<unsigned> getLoopIterationCount(
scf::ForOp loop, Value scheduledLane, Value specialization,
int64_t laneCount, int64_t specializationCount) {
std::optional<unsigned> commonCount;
for (int64_t specializationIndex = 0;
specializationIndex < specializationCount; ++specializationIndex)
for (int64_t lane = 0; lane < laneCount; ++lane) {
StaticIndexEnvironment environment;
if (scheduledLane)
environment.bindings[scheduledLane] = lane;
if (specialization)
environment.bindings[specialization] = specializationIndex;
auto lower = evaluateDeferredIndex(loop.getLowerBound(), environment);
auto upper = evaluateDeferredIndex(loop.getUpperBound(), environment);
auto step = evaluateDeferredIndex(loop.getStep(), environment);
if (failed(lower) || failed(upper) || failed(step) || *step <= 0)
return failure();
int64_t distance = std::max<int64_t>(0, *upper - *lower);
uint64_t count = (static_cast<uint64_t>(distance)
+ static_cast<uint64_t>(*step) - 1)
/ static_cast<uint64_t>(*step);
if (count > std::numeric_limits<unsigned>::max()
|| (commonCount && *commonCount != count))
return failure();
commonCount = count;
}
return commonCount.value_or(0);
}
static FailureOr<SmallVector<unsigned>> static FailureOr<SmallVector<unsigned>>
getPossibleDeferredSourceOperandIndices( getPossibleDeferredSourceOperandIndices(
Value sourceRoot, SpatDeferredCommunicationOp deferred) { Value sourceRoot, SpatDeferredCommunicationOp deferred) {
@@ -220,7 +251,7 @@ static LogicalResult validateDeferredProgram(
op->emitOpError(message); op->emitOpError(message);
return WalkResult::interrupt(); return WalkResult::interrupt();
}; };
if (isa<SpatYieldOp, scf::YieldOp>(op) if (isa<SpatYieldOp, scf::YieldOp, tensor::YieldOp>(op)
|| (isa<linalg::YieldOp>(op) || (isa<linalg::YieldOp>(op)
&& isa<linalg::TransposeOp>(op->getParentOp()))) && isa<linalg::TransposeOp>(op->getParentOp())))
return WalkResult::advance(); return WalkResult::advance();
@@ -250,15 +281,39 @@ static LogicalResult validateDeferredProgram(
return reject("has a non-positive or non-static deferred loop step"); return reject("has a non-positive or non-static deferred loop step");
return WalkResult::advance(); return WalkResult::advance();
} }
if (op->getNumRegions() != 0 && !isa<linalg::TransposeOp>(op)) bool structuredLoopShaping = isInsideDeferredLoop(op, deferred)
&& (isShapingOnlyOp(op)
|| isa<tensor::PadOp>(op));
if (op->getNumRegions() != 0 && !isa<linalg::TransposeOp>(op)
&& !structuredLoopShaping)
return reject("contains an unsupported region operation"); return reject("contains an unsupported region operation");
bool structuredLoopResidual = isInsideDeferredLoop(op, deferred)
&& (op->getNumRegions() == 0
|| structuredLoopShaping);
if (!isShapingOnlyOp(op) && !isCompileTimeOp(op) if (!isShapingOnlyOp(op) && !isCompileTimeOp(op)
&& !isPureIndexComputationOp(op)) && !isPureIndexComputationOp(op) && !structuredLoopResidual)
return reject("contains an unsupported deferred operation"); return reject("contains an unsupported deferred operation");
for (Value operand : op->getOperands()) if (isInsideDeferredLoop(op, deferred)
if (isInsideDeferredLoop(op, deferred) && llvm::any_of(op->getOperands(), [&](Value operand) {
&& originatesFromDeferredSource(operand, deferred)) return originatesFromDeferredSource(operand, deferred);
return reject("projects a deferred source inside a residual loop"); })) {
auto loop = op->getParentOfType<scf::ForOp>();
scf::ForOp outerLoop;
for (Operation *parent = loop ? loop->getParentOp() : nullptr;
parent && parent != deferred; parent = parent->getParentOp())
if (auto candidate = dyn_cast<scf::ForOp>(parent))
outerLoop = candidate;
if (!loop || outerLoop) {
op->emitOpError("projects a deferred source inside an unsupported loop nest")
<< " (inner " << (loop ? loop.getLoc() : op->getLoc())
<< ", outer " << (outerLoop ? outerLoop.getLoc() : op->getLoc())
<< ")";
return WalkResult::interrupt();
}
if (getConstantIntValue(loop.getLowerBound()) != 0
|| getConstantIntValue(loop.getStep()) != 1)
return reject("projects a deferred source inside a non-normalized loop");
}
return WalkResult::advance(); return WalkResult::advance();
}); });
return failure(result.wasInterrupted()); return failure(result.wasInterrupted());
@@ -376,9 +431,10 @@ FailureOr<int64_t> evaluateDeferredIndex(
DeferredLaneValueEvaluator::DeferredLaneValueEvaluator( DeferredLaneValueEvaluator::DeferredLaneValueEvaluator(
const DeferredProgramTemplate &program, unsigned laneCount, const DeferredProgramTemplate &program, unsigned laneCount,
unsigned specializationIndex) unsigned specializationIndex, scf::ForOp loop, unsigned loopIteration)
: program(program), laneCount(laneCount), : program(program), laneCount(laneCount),
specializationIndex(specializationIndex) {} specializationIndex(specializationIndex), loop(loop),
loopIteration(loopIteration) {}
FailureOr<StaticIntSequence> DeferredLaneValueEvaluator::evaluate(Value value) { FailureOr<StaticIntSequence> DeferredLaneValueEvaluator::evaluate(Value value) {
if (auto it = values.find(value); it != values.end()) if (auto it = values.find(value); it != values.end())
@@ -397,6 +453,8 @@ FailureOr<StaticIntSequence> DeferredLaneValueEvaluator::evaluate(Value value) {
if (program.specializationArgument) if (program.specializationArgument)
environment.bindings[program.specializationArgument] = environment.bindings[program.specializationArgument] =
specializationIndex; specializationIndex;
if (loop)
environment.bindings[loop.getInductionVar()] = loopIteration;
auto result = evaluateDeferredIndex(value, environment); auto result = evaluateDeferredIndex(value, environment);
if (failed(result)) if (failed(result))
return failure(); return failure();
@@ -435,6 +493,8 @@ DeferredLaneValueEvaluator::resolveSourceOperandIndices(Value sourceRoot) {
if (program.specializationArgument) if (program.specializationArgument)
environment.bindings[program.specializationArgument] = environment.bindings[program.specializationArgument] =
specializationIndex; specializationIndex;
if (loop)
environment.bindings[loop.getInductionVar()] = loopIteration;
auto index = sourceArgument(sourceRoot, program.deferred, environment); auto index = sourceArgument(sourceRoot, program.deferred, environment);
if (failed(index) || !*index) if (failed(index) || !*index)
return failure(); return failure();
@@ -518,6 +578,7 @@ FailureOr<DeferredProgramTemplate> analyzeDeferredProgramTemplate(
SmallVector<OpFoldResult>( SmallVector<OpFoldResult>(
ArrayRef(slice.getMixedStrides()).drop_front())}; ArrayRef(slice.getMixedStrides()).drop_front())};
leaf.reconstructedType = cast<RankedTensorType>(value.getType()); leaf.reconstructedType = cast<RankedTensorType>(value.getType());
leaf.enclosingLoop = slice->getParentOfType<scf::ForOp>();
if (graphProjection if (graphProjection
&& slice.getSourceType().getRank() && slice.getSourceType().getRank()
== leaf.reconstructedType.getRank() + 1 == leaf.reconstructedType.getRank() + 1
@@ -540,30 +601,59 @@ FailureOr<DeferredProgramTemplate> analyzeDeferredProgramTemplate(
if (!type) if (!type)
return deferred.emitOpError( return deferred.emitOpError(
"deferred source is not a ranked tensor"); "deferred source is not a ranked tensor");
program.leaves.push_back({DeferredLeafForm::DirectSource, value, value, DeferredProjectionLeafTemplate leaf;
{}, {}, type}); leaf.form = DeferredLeafForm::DirectSource;
leaf.sourceRoot = value;
leaf.replacementRoot = value;
leaf.reconstructedType = type;
program.leaves.push_back(std::move(leaf));
return success(); return success();
} }
if (value.getType().isIndex() || isa<IntegerType>(value.getType())) if (value.getType().isIndex() || isa<IntegerType>(value.getType()))
return success(); return success();
if (auto argument = dyn_cast<BlockArgument>(value)) {
auto loop = dyn_cast_or_null<scf::ForOp>(
argument.getOwner()->getParentOp());
if (loop && argument.getArgNumber() > 0)
return visit(loop.getInitArgs()[argument.getArgNumber() - 1]);
}
Operation *op = value.getDefiningOp(); Operation *op = value.getDefiningOp();
if (!op || (op->getBlock() != &body && !isa<scf::ForOp>(op))) if (!op || (op->getBlock() != &body
&& !op->getParentOfType<scf::ForOp>()))
return deferred.emitOpError( return deferred.emitOpError(
"deferred residual escapes its verified body"); "deferred residual escapes its verified body: ") << value;
if (auto loop = dyn_cast<scf::ForOp>(op)) { if (auto loop = dyn_cast<scf::ForOp>(op)) {
for (Value init : loop.getInitArgs()) for (Value init : loop.getInitArgs())
if (failed(visit(init))) if (failed(visit(init)))
return failure(); return failure();
auto loopYield = cast<scf::YieldOp>(loop.getBody()->getTerminator());
for (Value yielded : loopYield.getResults())
if (failed(visit(yielded)))
return failure();
} else { } else {
for (Value operand : op->getOperands()) for (Value operand : op->getOperands())
if (failed(visit(operand))) if (failed(visit(operand)))
return failure(); return failure();
} }
program.residualOps.push_back(op); if (op->getBlock() == &body)
program.residualOps.push_back(op);
return success(); return success();
}; };
if (failed(visit(program.yieldedValue))) if (failed(visit(program.yieldedValue)))
return failure(); return failure();
for (DeferredProjectionLeafTemplate &leaf : program.leaves) {
if (!leaf.enclosingLoop)
continue;
auto count = getLoopIterationCount(
leaf.enclosingLoop, program.scheduledLane,
program.specializationArgument, laneCount,
program.specializationCount);
if (failed(count) || *count == 0)
return deferred.emitOpError(
"deferred source projection loop must have a common non-zero trip count"),
failure();
leaf.loopIterationCount = *count;
}
auto assembly = analyzeInsertAssembly(program); auto assembly = analyzeInsertAssembly(program);
if (failed(assembly)) if (failed(assembly))
return failure(); return failure();
@@ -25,7 +25,9 @@ class DeferredLaneValueEvaluator {
public: public:
DeferredLaneValueEvaluator(const DeferredProgramTemplate &program, DeferredLaneValueEvaluator(const DeferredProgramTemplate &program,
unsigned laneCount, unsigned laneCount,
unsigned specializationIndex = 0); unsigned specializationIndex = 0,
mlir::scf::ForOp loop = {},
unsigned loopIteration = 0);
mlir::FailureOr<StaticIntSequence> evaluate(mlir::Value value); mlir::FailureOr<StaticIntSequence> evaluate(mlir::Value value);
mlir::FailureOr<StaticIntSequence> evaluate(mlir::OpFoldResult value); mlir::FailureOr<StaticIntSequence> evaluate(mlir::OpFoldResult value);
@@ -36,6 +38,8 @@ private:
const DeferredProgramTemplate &program; const DeferredProgramTemplate &program;
unsigned laneCount; unsigned laneCount;
unsigned specializationIndex; unsigned specializationIndex;
mlir::scf::ForOp loop;
unsigned loopIteration;
llvm::DenseMap<mlir::Value, StaticIntSequence> values; llvm::DenseMap<mlir::Value, StaticIntSequence> values;
llvm::DenseMap<mlir::Value, StaticIntSequence> sourceOperands; llvm::DenseMap<mlir::Value, StaticIntSequence> sourceOperands;
}; };
@@ -73,15 +73,19 @@ static LogicalResult buildLeafCollections(DeferredExchangePlan &exchange,
RankedTensorType normalized = fragmentType; RankedTensorType normalized = fragmentType;
if (leaf.form == DeferredLeafForm::GraphBatchProjection) if (leaf.form == DeferredLeafForm::GraphBatchProjection)
normalized = getDeferredProjectedFragmentType(leaf); normalized = getDeferredProjectedFragmentType(leaf);
bool direct = positionCount == 1 && normalized == leaf.reconstructedType; if (positionCount % leaf.loopIterationCount != 0)
return failure();
unsigned fragmentPositionCount = positionCount / leaf.loopIterationCount;
bool direct = fragmentPositionCount == 1
&& normalized == leaf.reconstructedType;
bool leading = leaf.reconstructedType.getRank() == normalized.getRank() + 1 bool leading = leaf.reconstructedType.getRank() == normalized.getRank() + 1
&& leaf.reconstructedType.getDimSize(0) == positionCount && leaf.reconstructedType.getDimSize(0) == fragmentPositionCount
&& leaf.reconstructedType.getShape().drop_front() && leaf.reconstructedType.getShape().drop_front()
== normalized.getShape(); == normalized.getShape();
if (!direct && !leading) if (!direct && !leading)
return exchange.deferred.emitOpError( return exchange.deferred.emitOpError(
"cannot form fragment collection for leaf ") "cannot form fragment collection for leaf ")
<< leafIndex << ": " << positionCount << " fragments of " << leafIndex << ": " << fragmentPositionCount << " fragments of "
<< normalized << " do not reconstruct " << leaf.reconstructedType; << normalized << " do not reconstruct " << leaf.reconstructedType;
FragmentCollectionKind kind = specializationCount == 1 FragmentCollectionKind kind = specializationCount == 1
? FragmentCollectionKind::Leaf ? FragmentCollectionKind::Leaf
@@ -89,6 +93,8 @@ static LogicalResult buildLeafCollections(DeferredExchangePlan &exchange,
SmallVector<int64_t> shape; SmallVector<int64_t> shape;
if (specializationCount > 1) if (specializationCount > 1)
shape.push_back(specializationCount); shape.push_back(specializationCount);
if (leaf.enclosingLoop)
shape.push_back(leaf.loopIterationCount);
llvm::append_range(shape, leaf.reconstructedType.getShape()); llvm::append_range(shape, leaf.reconstructedType.getShape());
FragmentCollectionPlan collection; FragmentCollectionPlan collection;
collection.key = {&exchange, kind, static_cast<unsigned>(leafIndex)}; collection.key = {&exchange, kind, static_cast<unsigned>(leafIndex)};
@@ -196,11 +202,14 @@ static constexpr std::array<TemplateGeometryMember, 3> templateGeometryMembers{
template <typename GetValue> template <typename GetValue>
static FailureOr<StaticIntGrid> buildGrid( static FailureOr<StaticIntGrid> buildGrid(
const DeferredProgramTemplate &program, unsigned laneCount, const DeferredProgramTemplate &program, unsigned laneCount,
unsigned rowCount, bool specializeRows, GetValue getValue) { unsigned rowCount, bool specializeRows, scf::ForOp loop,
unsigned loopIterationCount, GetValue getValue) {
SmallVector<StaticIntSequence> rows; SmallVector<StaticIntSequence> rows;
for (unsigned row = 0; row < rowCount; ++row) { for (unsigned row = 0; row < rowCount; ++row) {
unsigned specialization = specializeRows ? row / loopIterationCount : 0;
unsigned iteration = loop ? row % loopIterationCount : 0;
DeferredLaneValueEvaluator evaluator( DeferredLaneValueEvaluator evaluator(
program, laneCount, specializeRows ? row : 0); program, laneCount, specialization, loop, iteration);
auto sequence = evaluator.evaluate(getValue(row)); auto sequence = evaluator.evaluate(getValue(row));
if (failed(sequence)) return failure(); if (failed(sequence)) return failure();
rows.push_back(std::move(*sequence)); rows.push_back(std::move(*sequence));
@@ -211,7 +220,8 @@ static FailureOr<StaticIntGrid> buildGrid(
template <typename GetGeometry> template <typename GetGeometry>
static FailureOr<DeferredGridSliceGeometry> buildGeometryGrids( static FailureOr<DeferredGridSliceGeometry> buildGeometryGrids(
const DeferredProgramTemplate &program, unsigned laneCount, const DeferredProgramTemplate &program, unsigned laneCount,
unsigned rowCount, bool specializeRows, GetGeometry getGeometry) { unsigned rowCount, bool specializeRows, scf::ForOp loop,
unsigned loopIterationCount, GetGeometry getGeometry) {
DeferredGridSliceGeometry result; DeferredGridSliceGeometry result;
const DeferredSliceTemplate &first = getGeometry(0); const DeferredSliceTemplate &first = getGeometry(0);
for (auto [group, sourceMember] : llvm::enumerate(templateGeometryMembers)) { for (auto [group, sourceMember] : llvm::enumerate(templateGeometryMembers)) {
@@ -219,6 +229,7 @@ static FailureOr<DeferredGridSliceGeometry> buildGeometryGrids(
for (unsigned dimension = 0; for (unsigned dimension = 0;
dimension < (first.*member).size(); ++dimension) { dimension < (first.*member).size(); ++dimension) {
auto grid = buildGrid(program, laneCount, rowCount, specializeRows, auto grid = buildGrid(program, laneCount, rowCount, specializeRows,
loop, loopIterationCount,
[&](unsigned row) { return (getGeometry(row).*member)[dimension]; }); [&](unsigned row) { return (getGeometry(row).*member)[dimension]; });
if (failed(grid)) return failure(); if (failed(grid)) return failure();
result[group].push_back(std::move(*grid)); result[group].push_back(std::move(*grid));
@@ -226,8 +237,92 @@ static FailureOr<DeferredGridSliceGeometry> buildGeometryGrids(
} }
return result; return result;
} }
static Value cloneResidual(DeferredExchangePlan &exchange, IRMapping &mapping, DeferredEmissionContext &context) {
static LogicalResult cloneLoopIteration(
DeferredExchangePlan &exchange, scf::ForOp oldLoop,
const IRMapping &mapping, Value induction, ValueRange iterArgs,
llvm::function_ref<FailureOr<Value>(unsigned)> getLeaf,
DeferredEmissionContext &context, SmallVectorImpl<Value> &yielded) {
IRMapping nested(mapping);
nested.map(oldLoop.getInductionVar(), induction);
for (auto [oldArg, newArg] :
llvm::zip(oldLoop.getRegionIterArgs(), iterArgs))
nested.map(oldArg, newArg);
for (Operation &nestedOp : oldLoop.getBody()->without_terminator()) {
bool replaced = false;
for (auto [leafIndex, leaf] : llvm::enumerate(exchange.program.leaves)) {
if (leaf.enclosingLoop != oldLoop
|| leaf.replacementRoot.getDefiningOp() != &nestedOp)
continue;
auto selected = getLeaf(leafIndex);
if (failed(selected))
return failure();
nested.map(leaf.replacementRoot, *selected);
replaced = true;
}
if (replaced)
continue;
Operation *copy = context.rewriter.clone(nestedOp, nested);
for (auto [oldValue, newValue] :
llvm::zip(nestedOp.getResults(), copy->getResults()))
nested.map(oldValue, newValue);
}
auto oldYield = cast<scf::YieldOp>(oldLoop.getBody()->getTerminator());
for (Value value : oldYield.getResults())
yielded.push_back(nested.lookupOrDefault(value));
return success();
}
static Value cloneResidual(
DeferredExchangePlan &exchange, IRMapping &mapping,
ArrayRef<Value> leafCollections,
DeferredEmissionContext &context) {
for (Operation &op :
exchange.deferred.getBody().front().without_terminator()) {
if (!op.hasTrait<OpTrait::ConstantLike>()
|| llvm::all_of(op.getResults(),
[&](Value result) { return mapping.contains(result); }))
continue;
Operation *copy = context.rewriter.clone(op, mapping);
for (auto [oldValue, newValue] :
llvm::zip(op.getResults(), copy->getResults()))
mapping.map(oldValue, newValue);
}
for (Operation *op : exchange.program.residualOps) { for (Operation *op : exchange.program.residualOps) {
if (op->hasTrait<OpTrait::ConstantLike>())
continue;
if (auto oldLoop = dyn_cast<scf::ForOp>(op)) {
SmallVector<Value> initArgs;
for (Value init : oldLoop.getInitArgs())
initArgs.push_back(mapping.lookupOrDefault(init));
auto loop = buildNormalizedScfFor(
context.rewriter, oldLoop.getLoc(),
mapping.lookupOrDefault(oldLoop.getLowerBound()),
mapping.lookupOrDefault(oldLoop.getUpperBound()),
mapping.lookupOrDefault(oldLoop.getStep()), initArgs,
[&](OpBuilder &, Location loc, Value induction, ValueRange iterArgs,
SmallVectorImpl<Value> &yielded) -> LogicalResult {
return cloneLoopIteration(
exchange, oldLoop, mapping, induction, iterArgs,
[&](unsigned leafIndex) -> FailureOr<Value> {
const auto &leaf = exchange.program.leaves[leafIndex];
Value selected = extractMixedSliceOrIdentity(
context.rewriter, loc, leafCollections[leafIndex],
leaf.reconstructedType,
leadingSlice(context.rewriter, leaf.reconstructedType,
induction));
return selected ? FailureOr<Value>(selected)
: FailureOr<Value>(failure());
},
context, yielded);
});
if (failed(loop))
return {};
for (auto [oldValue, newValue] :
llvm::zip(oldLoop.getResults(), loop->results))
mapping.map(oldValue, newValue);
continue;
}
Operation *copy = context.rewriter.clone(*op, mapping); Operation *copy = context.rewriter.clone(*op, mapping);
for (auto [oldValue, newValue] : llvm::zip(op->getResults(), copy->getResults())) mapping.map(oldValue, newValue); for (auto [oldValue, newValue] : llvm::zip(op->getResults(), copy->getResults())) mapping.map(oldValue, newValue);
} }
@@ -252,16 +347,26 @@ static FailureOr<Value> realizeOne(const DeferredResultPlan &plan, Value lane, D
if (!value.getType().isIndex()) selected = arith::IndexCastOp::create(context.rewriter, exchange.deferred.getLoc(), value.getType(), selected); if (!value.getType().isIndex()) selected = arith::IndexCastOp::create(context.rewriter, exchange.deferred.getLoc(), value.getType(), selected);
mapping.map(value, selected); mapping.map(value, selected);
} }
SmallVector<Value> leafCollections;
for (unsigned index = 0; index < exchange.program.leaves.size(); ++index) { for (unsigned index = 0; index < exchange.program.leaves.size(); ++index) {
Value value = context.fragmentCollections.lookup( Value value = context.fragmentCollections.lookup(
{&exchange, FragmentCollectionKind::Leaf, index}); {&exchange, FragmentCollectionKind::Leaf, index});
if (!value || value.getType() != exchange.program.leaves[index].reconstructedType) { const auto &leaf = exchange.program.leaves[index];
SmallVector<int64_t> expectedShape;
if (leaf.enclosingLoop)
expectedShape.push_back(leaf.loopIterationCount);
llvm::append_range(expectedShape, leaf.reconstructedType.getShape());
Type expectedType = RankedTensorType::get(
expectedShape, leaf.reconstructedType.getElementType());
if (!value || value.getType() != expectedType) {
exchange.deferred.emitOpError("failed to reconstruct deferred result leaf ") << index; exchange.deferred.emitOpError("failed to reconstruct deferred result leaf ") << index;
return failure(); return failure();
} }
mapping.map(exchange.program.leaves[index].replacementRoot, value); leafCollections.push_back(value);
if (!exchange.program.leaves[index].enclosingLoop)
mapping.map(exchange.program.leaves[index].replacementRoot, value);
} }
Value result = cloneResidual(exchange, mapping, context); Value result = cloneResidual(exchange, mapping, leafCollections, context);
Type expected = exchange.program.specializationCount > 1 ? Type(exchange.program.specializationFragmentType) : exchange.deferred.getOutput().getType(); Type expected = exchange.program.specializationCount > 1 ? Type(exchange.program.specializationFragmentType) : exchange.deferred.getOutput().getType();
return result && result.getType() == expected ? FailureOr<Value>(result) : FailureOr<Value>(failure()); return result && result.getType() == expected ? FailureOr<Value>(result) : FailureOr<Value>(failure());
} }
@@ -274,8 +379,10 @@ FailureOr<DeferredResultPlan> buildDeferredResultPlan(DeferredExchangePlan &exch
: buildLeafCollections(exchange, plan))) return failure(); : buildLeafCollections(exchange, plan))) return failure();
unsigned specializations = exchange.program.specializationCount; unsigned specializations = exchange.program.specializationCount;
for (const auto &leaf : exchange.program.leaves) { for (const auto &leaf : exchange.program.leaves) {
unsigned geometryRows = specializations * leaf.loopIterationCount;
auto geometry = buildGeometryGrids(exchange.program, auto geometry = buildGeometryGrids(exchange.program,
exchange.targetLaneCount, specializations, true, exchange.targetLaneCount, geometryRows, true, leaf.enclosingLoop,
leaf.loopIterationCount,
[&](unsigned) -> const DeferredSliceTemplate & { [&](unsigned) -> const DeferredSliceTemplate & {
return leaf.innerGeometry; return leaf.innerGeometry;
}); });
@@ -285,7 +392,7 @@ FailureOr<DeferredResultPlan> buildDeferredResultPlan(DeferredExchangePlan &exch
if (exchange.program.insertAssembly) { if (exchange.program.insertAssembly) {
const auto &entries = exchange.program.insertAssembly->entries; const auto &entries = exchange.program.insertAssembly->entries;
auto geometry = buildGeometryGrids(exchange.program, auto geometry = buildGeometryGrids(exchange.program,
exchange.targetLaneCount, entries.size(), false, exchange.targetLaneCount, entries.size(), false, {}, 1,
[&](unsigned row) -> const DeferredSliceTemplate & { [&](unsigned row) -> const DeferredSliceTemplate & {
return entries[row].targetGeometry; return entries[row].targetGeometry;
}); });
@@ -301,7 +408,7 @@ FailureOr<DeferredResultPlan> buildDeferredResultPlan(DeferredExchangePlan &exch
}); });
for (Value value : residualValues) { for (Value value : residualValues) {
auto grid = buildGrid(exchange.program, exchange.targetLaneCount, auto grid = buildGrid(exchange.program, exchange.targetLaneCount,
specializations, true, specializations, true, {}, 1,
[&](unsigned) { return OpFoldResult(value); }); [&](unsigned) { return OpFoldResult(value); });
if (succeeded(grid)) if (succeeded(grid))
plan.residualValues.try_emplace(value, std::move(*grid)); plan.residualValues.try_emplace(value, std::move(*grid));
@@ -320,6 +427,8 @@ FailureOr<Value> realizeDeferredResult(const DeferredResultPlan &plan, Value lan
FragmentCollectionKey key{&exchange, FragmentCollectionKind::GroupedLeaf, static_cast<unsigned>(index)}; FragmentCollectionKey key{&exchange, FragmentCollectionKind::GroupedLeaf, static_cast<unsigned>(index)};
Value collection = context.fragmentCollections.lookup(key); Value collection = context.fragmentCollections.lookup(key);
SmallVector<int64_t> shape{exchange.program.specializationCount}; SmallVector<int64_t> shape{exchange.program.specializationCount};
if (leaf.enclosingLoop)
shape.push_back(leaf.loopIterationCount);
llvm::append_range(shape, leaf.reconstructedType.getShape()); llvm::append_range(shape, leaf.reconstructedType.getShape());
if (!collection || collection.getType() != RankedTensorType::get(shape, leaf.reconstructedType.getElementType())) return failure(); if (!collection || collection.getType() != RankedTensorType::get(shape, leaf.reconstructedType.getElementType())) return failure();
leafStacks.push_back(collection); leafStacks.push_back(collection);
@@ -337,12 +446,102 @@ FailureOr<Value> realizeDeferredResult(const DeferredResultPlan &plan, Value lan
if (!value.getType().isIndex()) selected = arith::IndexCastOp::create(context.rewriter, loc, value.getType(), selected); if (!value.getType().isIndex()) selected = arith::IndexCastOp::create(context.rewriter, loc, value.getType(), selected);
mapping.map(value, selected); mapping.map(value, selected);
} }
for (auto [index, leaf] : llvm::enumerate(exchange.program.leaves)) { Value selected = extractMixedSliceOrIdentity(context.rewriter, loc, leafStacks[index], leaf.reconstructedType, leadingSlice(context.rewriter, leaf.reconstructedType, specialization)); if (!selected) return failure(); mapping.map(leaf.replacementRoot, selected); } SmallVector<Value> selectedLeaves;
Value fragment = cloneResidual(exchange, mapping, context); for (auto [index, leaf] : llvm::enumerate(exchange.program.leaves)) {
RankedTensorType selectedType =
leaf.reconstructedType;
if (leaf.enclosingLoop) {
SmallVector<int64_t> shape {
static_cast<int64_t>(leaf.loopIterationCount)};
llvm::append_range(shape, leaf.reconstructedType.getShape());
selectedType = RankedTensorType::get(
shape, leaf.reconstructedType.getElementType());
}
Value selected = extractMixedSliceOrIdentity(context.rewriter, loc, leafStacks[index], selectedType, leadingSlice(context.rewriter, selectedType, specialization));
if (!selected) return failure();
selectedLeaves.push_back(selected);
if (!leaf.enclosingLoop)
mapping.map(leaf.replacementRoot, selected);
}
Value fragment = cloneResidual(
exchange, mapping, selectedLeaves,
context);
if (!fragment || fragment.getType() != fragmentType) return failure(); if (!fragment || fragment.getType() != fragmentType) return failure();
yielded.push_back(insertMixedSlice(context.rewriter, loc, fragment, iterArgs.front(), leadingSlice(context.rewriter, fragmentType, specialization))); yielded.push_back(insertMixedSlice(context.rewriter, loc, fragment, iterArgs.front(), leadingSlice(context.rewriter, fragmentType, specialization)));
return success(); return success();
}); });
return failed(loop) || loop->results.size() != 1 ? FailureOr<Value>(failure()) : FailureOr<Value>(loop->results.front()); return failed(loop) || loop->results.size() != 1 ? FailureOr<Value>(failure()) : FailureOr<Value>(loop->results.front());
} }
bool canRealizeDeferredLoopResult(const DeferredResultPlan &plan) {
DeferredExchangePlan &exchange = *plan.exchange;
if (exchange.program.specializationCount != 1
|| exchange.program.leaves.size() != 1)
return false;
const DeferredProjectionLeafTemplate &leaf = exchange.program.leaves.front();
scf::ForOp loop = leaf.enclosingLoop;
return loop
&& llvm::count_if(exchange.program.residualOps, [](Operation *op) {
return !op->hasTrait<OpTrait::ConstantLike>();
}) == 1
&& llvm::is_contained(exchange.program.residualOps, loop.getOperation())
&& loop.getInitArgs().size() == 1 && loop.getNumResults() == 1
&& exchange.program.yieldedValue == loop.getResult(0)
&& (leaf.form != DeferredLeafForm::GraphBatchProjection
|| getDeferredProjectedFragmentType(leaf) == leaf.reconstructedType);
}
FailureOr<Value> realizeDeferredLoopResult(
const DeferredResultPlan &plan, Value lane,
DeferredLoopEmitter emitLoop,
DeferredEmissionContext &context) {
if (!canRealizeDeferredLoopResult(plan))
return failure();
DeferredExchangePlan &exchange = *plan.exchange;
scf::ForOp oldLoop = exchange.program.leaves.front().enclosingLoop;
IRMapping mapping;
if (exchange.program.scheduledLane)
mapping.map(exchange.program.scheduledLane, lane);
for (auto &[value, grid] : plan.residualValues) {
Value selected = grid.emitLookup(
context.constants.getIndex(0),
lane ? lane : context.constants.getIndex(0), exchange.deferred,
context.constants, context.rewriter, exchange.deferred.getLoc());
if (!value.getType().isIndex())
selected = arith::IndexCastOp::create(
context.rewriter, exchange.deferred.getLoc(), value.getType(), selected);
mapping.map(value, selected);
}
for (Operation &op :
exchange.deferred.getBody().front().without_terminator()) {
if (!op.hasTrait<OpTrait::ConstantLike>())
continue;
Operation *copy = context.rewriter.clone(op, mapping);
for (auto [oldValue, newValue] :
llvm::zip(op.getResults(), copy->getResults()))
mapping.map(oldValue, newValue);
}
Value initial = mapping.lookupOrDefault(oldLoop.getInitArgs().front());
auto result = emitLoop(
initial,
[&](Value fragment, Value iteration, Value current) -> FailureOr<Value> {
if (fragment.getType()
!= exchange.program.leaves.front().reconstructedType)
return failure();
SmallVector<Value> yielded;
if (failed(cloneLoopIteration(
exchange, oldLoop, mapping, iteration, ValueRange {current},
[&](unsigned leafIndex) -> FailureOr<Value> {
return leafIndex == 0 ? FailureOr<Value>(fragment)
: FailureOr<Value>(failure());
},
context, yielded))
|| yielded.size() != 1)
return failure();
return yielded.front();
});
return succeeded(result)
&& result->getType() == exchange.deferred.getOutput().getType()
? result : FailureOr<Value>(failure());
}
} // namespace onnx_mlir::spatial } // namespace onnx_mlir::spatial
@@ -2,6 +2,7 @@
#include "DeferredCommunicationModel.hpp" #include "DeferredCommunicationModel.hpp"
#include "src/Accelerators/PIM/Common/IR/StaticIntGrid.hpp" #include "src/Accelerators/PIM/Common/IR/StaticIntGrid.hpp"
#include "llvm/ADT/STLFunctionalExtras.h"
#include <array> #include <array>
namespace onnx_mlir::spatial { namespace onnx_mlir::spatial {
@@ -54,4 +55,16 @@ mlir::FailureOr<mlir::Value> realizeDeferredResult(const DeferredResultPlan& pla
mlir::Value lane, mlir::Value lane,
DeferredEmissionContext& context); DeferredEmissionContext& context);
using DeferredLoopIterationEmitter = llvm::function_ref<
mlir::FailureOr<mlir::Value>(mlir::Value, mlir::Value, mlir::Value)>;
using DeferredLoopEmitter = llvm::function_ref<mlir::FailureOr<mlir::Value>(
mlir::Value, DeferredLoopIterationEmitter)>;
bool canRealizeDeferredLoopResult(const DeferredResultPlan &plan);
mlir::FailureOr<mlir::Value> realizeDeferredLoopResult(
const DeferredResultPlan &plan, mlir::Value lane,
DeferredLoopEmitter emitLoop,
DeferredEmissionContext &context);
} // namespace onnx_mlir::spatial } // namespace onnx_mlir::spatial
@@ -133,11 +133,14 @@ static LogicalResult buildRequirementFamilies(DeferredTransferPlan& plan,
for (unsigned specialization = 0; for (unsigned specialization = 0;
specialization < exchange.program.specializationCount; specialization < exchange.program.specializationCount;
++specialization) { ++specialization) {
DeferredLaneValueEvaluator evaluator(
exchange.program, exchange.targetLaneCount, specialization);
for (auto leafItem : llvm::enumerate(exchange.program.leaves)) { for (auto leafItem : llvm::enumerate(exchange.program.leaves)) {
unsigned leafIndex = leafItem.index(); unsigned leafIndex = leafItem.index();
const DeferredProjectionLeafTemplate &leaf = leafItem.value(); const DeferredProjectionLeafTemplate &leaf = leafItem.value();
for (unsigned iteration = 0;
iteration < leaf.loopIterationCount; ++iteration) {
DeferredLaneValueEvaluator evaluator(
exchange.program, exchange.targetLaneCount, specialization,
leaf.enclosingLoop, iteration);
auto sourceIndices = evaluator.resolveSourceOperandIndices(leaf.sourceRoot); auto sourceIndices = evaluator.resolveSourceOperandIndices(leaf.sourceRoot);
if (failed(sourceIndices)) if (failed(sourceIndices))
return failure(); return failure();
@@ -273,7 +276,9 @@ static LogicalResult buildRequirementFamilies(DeferredTransferPlan& plan,
++end; ++end;
RequirementFamily family; RequirementFamily family;
family.exchange = &exchange; family.exchange = &exchange;
family.coordinate = {specialization, leafIndex, position}; family.coordinate = {
specialization, leafIndex,
iteration * positionCount + position};
family.targetLanes = LaneSet::range(begin, end); family.targetLanes = LaneSet::range(begin, end);
family.producer = producers[begin]; family.producer = producers[begin];
family.publicationFragmentType = fragmentTypes[begin]; family.publicationFragmentType = fragmentTypes[begin];
@@ -297,6 +302,7 @@ static LogicalResult buildRequirementFamilies(DeferredTransferPlan& plan,
begin = end; begin = end;
} }
} }
}
} }
} }
return success(); return success();
+2
View File
@@ -28,6 +28,8 @@ std::unique_ptr<mlir::Pass> createTrivialGraphComputeMergePass(
std::unique_ptr<mlir::Pass> createPimHostConstantFoldingPass(); std::unique_ptr<mlir::Pass> createPimHostConstantFoldingPass();
std::unique_ptr<mlir::Pass> createPimInstructionSelectionPass();
std::unique_ptr<mlir::Pass> createPimLocalMemoryPlanningPass(); std::unique_ptr<mlir::Pass> createPimLocalMemoryPlanningPass();
std::unique_ptr<mlir::Pass> createPimVerificationPass(); std::unique_ptr<mlir::Pass> createPimVerificationPass();
+1
View File
@@ -79,6 +79,7 @@ void PimAccelerator::registerPasses(int optLevel) const {
mlir::registerPass([] { return createTrivialGraphComputeMergePass(); }); mlir::registerPass([] { return createTrivialGraphComputeMergePass(); });
mlir::registerPass([] { return createMergeComputeNodesPass(); }); mlir::registerPass([] { return createMergeComputeNodesPass(); });
registerPass(createPimHostConstantFoldingPass); registerPass(createPimHostConstantFoldingPass);
registerPass(createPimInstructionSelectionPass);
registerPass(createPimLocalMemoryPlanningPass); registerPass(createPimLocalMemoryPlanningPass);
registerPass(createPimVerificationPass); registerPass(createPimVerificationPass);
registerPass(createEmitPimCodePass); registerPass(createEmitPimCodePass);
+2 -1
View File
@@ -58,7 +58,8 @@ Validate a network or network slice:
``` ```
`--operations-dir` may point to any directory tree containing `.onnx` files. `--operations-dir` may point to any directory tree containing `.onnx` files.
The script discovers them recursively. The script discovers them recursively and writes `validation_results.csv` in
that directory while retaining the terminal table.
## Raptor vs PIMCOMP comparison ## Raptor vs PIMCOMP comparison
+80 -13
View File
@@ -1,26 +1,85 @@
# PIMCOMP paper models # PIMCOMP comparison models
This directory contains the four networks evaluated in This directory contains the four networks evaluated in
[PIMCOMP: An End-to-End DNN Compiler for Processing-In-Memory Accelerators](https://arxiv.org/pdf/2411.09159): [PIMCOMP: An End-to-End DNN Compiler for Processing-In-Memory Accelerators](https://arxiv.org/pdf/2411.09159):
VGG-8, ResNet-18, ResNet-34, and GoogLeNet. VGG-8, ResNet-18, ResNet-34, and GoogLeNet. It also contains YOLO11n as an
additional compiler comparison model.
See the runner-generated [results.csv](results.csv) for the current latency See the runner-generated [results.csv](results.csv) for the current latency
and energy results. and energy results.
## Models and provenance ## Models and provenance
| Directory | Model | Input | Provenance | | Directory | Model | Input | Provenance |
|--------------|----------------------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |--------------|----------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `resnet18/` | ResNet-18 v1 | `1x3x224x224` | Symlink to the complete [ONNX Model Zoo `resnet18-v1-7`](https://huggingface.co/onnxmodelzoo/resnet18-v1-7) model already present at `../resnet18/depth_68/resnet18_depth_68.onnx`. | | `resnet18/` | ResNet-18 v1 | `1x3x224x224` | Symlink to the complete [ONNX Model Zoo `resnet18-v1-7`](https://huggingface.co/onnxmodelzoo/resnet18-v1-7) model already present at `../resnet18/depth_68/resnet18_depth_68.onnx`. |
| `resnet34/` | ResNet-34 v1 | `1x3x224x224` | [ONNX Model Zoo `resnet34-v1-7`](https://huggingface.co/onnxmodelzoo/resnet34-v1-7), with its symbolic batch fixed to 1 as PIMCOMP's frontend does. | | `resnet34/` | ResNet-34 v1 | `1x3x224x224` | [ONNX Model Zoo `resnet34-v1-7`](https://huggingface.co/onnxmodelzoo/resnet34-v1-7), with its symbolic batch fixed to 1 as PIMCOMP's frontend does. |
| `googlenet/` | GoogLeNet | `1x3x224x224` | Unmodified [ONNX Model Zoo `googlenet-12`](https://huggingface.co/onnxmodelzoo/googlenet-12). | | `googlenet/` | GoogLeNet | `1x3x224x224` | Unmodified [ONNX Model Zoo `googlenet-12`](https://huggingface.co/onnxmodelzoo/googlenet-12). |
| `vgg8/` | VGG-8 reconstruction | `1x1x28x28` | Reconstruction of the [PIMCOMP VGG-8 benchmark](https://arxiv.org/html/2411.09159#S8.SS1), with six convolution and two fully connected layers. | | `vgg8/` | VGG-8 reconstruction | `1x1x28x28` | Reconstruction of the [PIMCOMP VGG-8 benchmark](https://arxiv.org/html/2411.09159#S8.SS1), with six convolution and two fully connected layers. |
| `yolo11n/` | YOLO11n detection | `1x3x640x640` | Derived from the canonical local model at `../yolo11n/depth_51/yolo11n_depth_51.onnx`, exported from [Ultralytics YOLO11n](https://github.com/ultralytics/ultralytics/blob/main/docs/en/models/yolo11.md). |
`googlenet/googlenet-12-latency.onnx` is the explicit common latency model. `googlenet/googlenet-12-latency.onnx` is the explicit pimsim-nn-ready GoogLeNet model.
It exposes the original model's final FC logits (`loss3/classifier_1`) and It removes the two LRN nodes and terminal Softmax from the original model,
removes its two LRN nodes and terminal Softmax so the comparison covers only so that the comparison covers only operations scheduled by PIMCOMP and supported by pimsim-nn.
operations scheduled by PIMCOMP. Keep `googlenet-12.onnx` as the unmodified
source model. `yolo11n/yolo11n-latency.onnx` is the explicit pimsim-nn-ready YOLO11n model.
It removes the Softmax nodes from the original model,
so that the compiled artifact can be simulated in pimsim-nn.
## Unsupported and ignored operations
PIMCOMP's frontend accepts exactly these ONNX operations:
```text
Add, AveragePool, BatchNormalization, Clip, Concat, Conv, Dropout, Flatten,
Gather, Gemm, GlobalAveragePool, LRN, MatMul, MaxPool, Mul, Pad, Relu, Reshape,
Shape, Sigmoid, Softmax, Squeeze, Sub, Sum, Tanh, Transpose, Unsqueeze
```
`Constant` is consumed as frontend metadata rather than emitted as a PIMCOMP
node. Every other ONNX operation is unsupported: the frontend prints
`operation: <type> not considered` and stops at the first occurrence. Thus the
complete unsupported set is the complement of the allowlist above for the
model's ONNX opset. In particular, YOLO11n contains unsupported `Split` and
`Resize` nodes.
PIMCOMP's low-latency scheduler, hierarchy mapper, and genetic algorithm use
this complete explicit no-consider set:
```text
Input, BatchNormalization, Clip, Dropout, Flatten, LRN, MatMul, Reshape,
Softmax, Squeeze, Transpose
```
Those nodes do not receive scheduled latency instructions when they remain in
the backend graph. Before that point the frontend may fuse BatchNormalization
and activation nodes into Conv/Gemm, convert a
Reshape-Transpose-Reshape channel-shuffle pattern to `OP_SHUFFLE`, remove a
specific Shape-Gather-Unsqueeze-Concat shape chain, and merge Pad into its
consumer. These transformations do not make an otherwise standalone ignored
operation timed.
`pimsim-nn` consumes PIM ISA instructions. It supports every named opcode
in the shared serialized range except `vsoftmax` (opcode 21),
which is rejected explicitly in both JSON and binary input. It
silently ignores no opcode; unknown names and numbers are errors.
These boundaries explain the dedicated artifacts:
- GoogLeNet's two LRN nodes and terminal Softmax perform real computation but
are ignored by PIMCOMP, so the common latency artifact removes them. Its
inference Dropout and shape-only Reshape can remain without adding compute.
- YOLO11n's latency artifact bypasses exactly its two Softmax nodes so it can
run in `pimsim-nn`. Every other node, including MatMul, Transpose, and the
final detection-decoding tail, remains present and timed by Raptor. No
PIMCOMP latency is reported because its frontend stops at `Split` and also
lacks `Resize`; compiling that prefix would not represent YOLO11n.
The authoritative lists are in
[`frontend.py`](../../../third_party/PIMCOMP-NN/frontend/frontend.py),
[`ElementPipelineSchedule.cpp`](../../../third_party/PIMCOMP-NN/backend/ElementPipelineSchedule.cpp),
[`ISA.h`](../../../backend-simulators/pim/pimsim-nn/src/isa/ISA.h), and
[`Instruction.cpp`](../../../backend-simulators/pim/pimsim-nn/src/isa/Instruction.cpp).
The PIMCOMP authors did not publish the ONNX checkpoints used by the paper. The PIMCOMP authors did not publish the ONNX checkpoints used by the paper.
Running PIMCOMP's frontend on the three Model Zoo files above produces JSON Running PIMCOMP's frontend on the three Model Zoo files above produces JSON
@@ -41,6 +100,7 @@ c3231061d081bdd47884137b02134f85142752a39e87263c529cd14ed242b096 resnet34/resne
c99c507058eaf41de8723408fdda7db8325cb57f0a89f2ee07a716d6e963e14e googlenet/googlenet-12.onnx c99c507058eaf41de8723408fdda7db8325cb57f0a89f2ee07a716d6e963e14e googlenet/googlenet-12.onnx
a26f9e33901c573e60c34a3f0abbb4744fff83e4e0f21b18fc66e20395e72982 googlenet/googlenet-12-latency.onnx a26f9e33901c573e60c34a3f0abbb4744fff83e4e0f21b18fc66e20395e72982 googlenet/googlenet-12-latency.onnx
396cdea21e5e7d02c3f26f14d22ef20975171702493f5c5e79b8e0d896e541ef vgg8/vgg8-mnist-reconstructed.onnx 396cdea21e5e7d02c3f26f14d22ef20975171702493f5c5e79b8e0d896e541ef vgg8/vgg8-mnist-reconstructed.onnx
229f3975af8933d39aee8d9031d969bff074b69c33e304a78abb35ff0c5f445f yolo11n/yolo11n-latency.onnx
``` ```
## Paper hardware profiles ## Paper hardware profiles
@@ -190,6 +250,13 @@ RAPTOR_ROOT=$PWD
--pimcomp-pipeline element --pimcomp-pipeline element
``` ```
Use the same command with
`yolo11n/yolo11n-latency.onnx` to probe YOLO11n. Released PIMCOMP-NN cannot
compile it: the frontend stops at `/model.2/Split`, and it also has no mapping
for YOLO11n's two nearest-neighbor `Resize` nodes. Treating the emitted prefix
as YOLO11n would produce a misleading latency, so no PIMCOMP number is
reported for this model.
For Arch-A high throughput, use `--pimsim-mode throughput For Arch-A high throughput, use `--pimsim-mode throughput
--pimcomp-pipeline batch`. For Arch-B, use 138 cores, 128 crossbars, a --pimcomp-pipeline batch`. For Arch-B, use 138 cores, 128 crossbars, a
`6x23` mesh, and `6x23` mesh, and
@@ -1,5 +1,5 @@
model,raptor_latency_ms,pimcomp_latency_ms,raptor_energy_pj,pimcomp_energy_pj,faster_compiler,speedup model,raptor_latency_ms,pimcomp_latency_ms,raptor_energy_pj,pimcomp_energy_pj,faster_compiler,speedup
vgg8,2.463417,7.985074,649524564.120001,1597904071.120000,raptor,3.24 vgg8,1.465778,7.985074,477298145.040001,1597904071.120000,raptor,5.45
resnet18,31.449811,58.853733,9335506856.119984,13983168748.119974,raptor,1.87 resnet18,28.099952,58.853733,8781611766.119984,13983168748.119974,raptor,2.09
resnet34,58.071522,91.607980,17027195415.679953,22722922369.680016,raptor,1.58 resnet34,45.781486,91.607980,14962940227.679951,22722922369.680016,raptor,2.00
googlenet,24.247113,62.923463,8019605936.239990,14547526780.240000,raptor,2.60 googlenet,13.371204,62.923463,6117835798.919991,14547526780.240000,raptor,4.71
1 model raptor_latency_ms pimcomp_latency_ms raptor_energy_pj pimcomp_energy_pj faster_compiler speedup
2 vgg8 2.463417 1.465778 7.985074 649524564.120001 477298145.040001 1597904071.120000 raptor 3.24 5.45
3 resnet18 31.449811 28.099952 58.853733 9335506856.119984 8781611766.119984 13983168748.119974 raptor 1.87 2.09
4 resnet34 58.071522 45.781486 91.607980 17027195415.679953 14962940227.679951 22722922369.680016 raptor 1.58 2.00
5 googlenet 24.247113 13.371204 62.923463 8019605936.239990 6117835798.919991 14547526780.240000 raptor 2.60 4.71
+5 -3
View File
@@ -38,11 +38,12 @@ Run the complete suite with deadlock detection:
Use `--compile-only` for compiler and deadlock checks, then `--run-only` to Use `--compile-only` for compiler and deadlock checks, then `--run-only` to
reuse those artifacts for reference execution, simulation, and comparison. reuse those artifacts for reference execution, simulation, and comparison.
The validator prints the complete operation results table before its summary. The validator prints the complete operation results table before its summary
and writes the same rows to `validation_results.csv`.
## Complete inventory ## Complete inventory
The suite contains 164 models. Tensor shapes, attributes, and constants are The suite contains 165 models. Tensor shapes, attributes, and constants are
defined in `gen_tests.py` and in the checked-in ONNX models. defined in `gen_tests.py` and in the checked-in ONNX models.
### Add (5) ### Add (5)
@@ -63,7 +64,7 @@ defined in `gen_tests.py` and in the checked-in ONNX models.
| `negative_axis` | Concatenates tensors using a negative axis. | | `negative_axis` | Concatenates tensors using a negative axis. |
| `three_inputs_channel_axis` | Concatenates three runtime NCHW tensors along the channel axis. | | `three_inputs_channel_axis` | Concatenates three runtime NCHW tensors along the channel axis. |
### Conv (31) ### Conv (32)
| Case | Description | | Case | Description |
|---|---| |---|---|
@@ -98,6 +99,7 @@ defined in `gen_tests.py` and in the checked-in ONNX models.
| `with_bias_3x3` | Multi-channel 3x3 Conv with bias. | | `with_bias_3x3` | Multi-channel 3x3 Conv with bias. |
| `with_constant` | Hand-authored SAME_UPPER Conv with constant weight and bias. | | `with_constant` | Hand-authored SAME_UPPER Conv with constant weight and bias. |
| `without_kernel_shape_attr` | Conv whose kernel shape is inferred from its weight tensor. | | `without_kernel_shape_attr` | Conv whose kernel shape is inferred from its weight tensor. |
| `yolo11n_stem` | First two YOLO11n `Conv-SiLU` blocks at `640x640`, including the distributed activation boundary. |
### Div (6) ### Div (6)
+25
View File
@@ -218,6 +218,30 @@ def conv_huge_pointwise_1024_dynamic():
save_model(model, "conv/huge_pointwise_1024_dynamic", "conv_huge_pointwise_1024_dynamic.onnx") save_model(model, "conv/huge_pointwise_1024_dynamic", "conv_huge_pointwise_1024_dynamic.onnx")
def conv_yolo11n_stem():
"""First two YOLO11n Conv-SiLU blocks, preserving the distributed intermediate."""
X = helper.make_tensor_value_info("X", TensorProto.FLOAT, [1, 3, 640, 640])
Y = helper.make_tensor_value_info("Y", TensorProto.FLOAT, [1, 32, 160, 160])
rng = np.random.default_rng(109)
W0 = numpy_helper.from_array(rng.uniform(-1, 1, (16, 3, 3, 3)).astype(np.float32), name="W0")
B0 = numpy_helper.from_array(rng.uniform(-1, 1, (16,)).astype(np.float32), name="B0")
W1 = numpy_helper.from_array(rng.uniform(-1, 1, (32, 16, 3, 3)).astype(np.float32), name="W1")
B1 = numpy_helper.from_array(rng.uniform(-1, 1, (32,)).astype(np.float32), name="B1")
nodes = [
helper.make_node("Conv", ["X", "W0", "B0"], ["C0"],
kernel_shape=[3, 3], strides=[2, 2], pads=[1, 1, 1, 1]),
helper.make_node("Sigmoid", ["C0"], ["S0"]),
helper.make_node("Mul", ["C0", "S0"], ["A0"]),
helper.make_node("Conv", ["A0", "W1", "B1"], ["C1"],
kernel_shape=[3, 3], strides=[2, 2], pads=[1, 1, 1, 1]),
helper.make_node("Sigmoid", ["C1"], ["S1"]),
helper.make_node("Mul", ["C1", "S1"], ["Y"]),
]
graph = helper.make_graph(nodes, "conv_yolo11n_stem", [X], [Y], initializer=[W0, B0, W1, B1])
model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 13)])
save_model(model, "conv/yolo11n_stem", "conv_yolo11n_stem.onnx")
def conv_pointwise_tiled_chain(): def conv_pointwise_tiled_chain():
"""Chained pointwise Convs with a tiled intermediate.""" """Chained pointwise Convs with a tiled intermediate."""
X = helper.make_tensor_value_info("X", TensorProto.FLOAT, [1, 1024, 1, 1]) X = helper.make_tensor_value_info("X", TensorProto.FLOAT, [1, 1024, 1, 1])
@@ -2032,6 +2056,7 @@ if __name__ == "__main__":
conv_dynamic() conv_dynamic()
conv_huge_pointwise_1024() conv_huge_pointwise_1024()
conv_huge_pointwise_1024_dynamic() conv_huge_pointwise_1024_dynamic()
conv_yolo11n_stem()
conv_pointwise_tiled_chain() conv_pointwise_tiled_chain()
conv_large_output_channels_1x1() conv_large_output_channels_1x1()
conv_large_input_channels_1x1() conv_large_input_channels_1x1()
@@ -0,0 +1,166 @@
Operation,Result,Compile,Host mem,Cores mem,Cores,Xbars,Latency,Power,Energy
add/after_gemm,PASS,0.141 s,0.01 MiB,0.01 MiB,5,4,0.007784 ms,104.703618 mW,815012.960000 pJ
add/basic,PASS,0.114 s,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
add/broadcast_row,PASS,0.110 s,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
add/channel_broadcast_1024,PASS,0.105 s,0.02 MiB,0.01 MiB,1,0,0.006913 ms,78.118038 mW,540030.000000 pJ
add/leading_dimension_broadcast,PASS,0.137 s,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
concat/channel_axis,PASS,0.127 s,0.00 MiB,0.00 MiB,1,0,0.000457 ms,78.157549 mW,35718.000000 pJ
concat/negative_axis,PASS,0.102 s,0.00 MiB,0.00 MiB,1,0,0.001043 ms,78.092042 mW,81450.000000 pJ
concat/three_inputs_channel_axis,PASS,0.127 s,0.00 MiB,0.00 MiB,1,0,0.000644 ms,78.149068 mW,50328.000000 pJ
conv/batch_2,PASS,0.102 s,0.00 MiB,0.00 MiB,2,2,0.013694 ms,82.623885 mW,1131451.480000 pJ
conv/batch_4_pointwise,PASS,0.110 s,0.00 MiB,0.01 MiB,5,4,0.003932 ms,116.078576 mW,456420.960000 pJ
conv/depthwise_1024_channels,PASS,0.167 s,0.19 MiB,0.38 MiB,129,128,0.220751 ms,178.454307 mW,39393966.720000 pJ
conv/depthwise_grouped,PASS,0.109 s,0.01 MiB,0.00 MiB,5,4,0.006024 ms,108.326521 mW,652558.960000 pJ
conv/dilated_3x3,PASS,0.143 s,0.00 MiB,0.00 MiB,3,3,0.004045 ms,110.234541 mW,445898.720000 pJ
conv/dynamic,PASS,0.107 s,0.00 MiB,0.00 MiB,5,0,0.001835 ms,92.281199 mW,169336.000000 pJ
conv/explicit_padding,PASS,0.139 s,0.00 MiB,0.00 MiB,4,4,0.004327 ms,115.794768 mW,501043.960000 pJ
conv/grouped_many_groups,PASS,0.616 s,0.05 MiB,0.09 MiB,65,64,0.181845 ms,142.210104 mW,25860196.360000 pJ
conv/grouped_two_groups,PASS,0.145 s,0.00 MiB,0.00 MiB,3,2,0.005360 ms,101.459418 mW,543822.480000 pJ
conv/huge_pointwise_1024,PASS,0.749 s,0.01 MiB,0.01 MiB,1,64,0.028261 ms,133.488743 mW,3772525.360000 pJ
conv/huge_pointwise_1024_dynamic,PASS,0.097 s,8.04 MiB,12.61 MiB,168,0,2.627964 ms,169.518697 mW,445489032.000000 pJ
conv/kernel_3x3,PASS,0.155 s,0.00 MiB,0.00 MiB,3,3,0.003091 ms,115.862414 mW,358130.720000 pJ
conv/kernel_equals_input_spatial,PASS,0.156 s,0.00 MiB,0.00 MiB,1,2,0.008443 ms,83.863849 mW,708062.480000 pJ
conv/large_input_channels_1x1,PASS,0.160 s,0.01 MiB,0.01 MiB,1,8,0.017167 ms,89.416900 mW,1535019.920000 pJ
conv/large_output_channels_1x1,PASS,0.141 s,0.00 MiB,0.01 MiB,1,8,0.004964 ms,117.628106 mW,583905.920000 pJ
conv/large_spatial,PASS,0.141 s,0.00 MiB,0.01 MiB,6,6,0.004096 ms,129.015000 mW,528445.440000 pJ
conv/multi_channel,PASS,0.143 s,0.00 MiB,0.00 MiB,3,3,0.005148 ms,106.453520 mW,548022.720000 pJ
conv/non_square_kernel_1x3,PASS,0.127 s,0.00 MiB,0.00 MiB,5,5,0.004029 ms,123.600943 mW,497988.200000 pJ
conv/non_square_kernel_3x1,PASS,0.085 s,0.00 MiB,0.00 MiB,3,3,0.005526 ms,105.464843 mW,582798.720000 pJ
conv/non_uniform_stride,PASS,0.120 s,0.00 MiB,0.00 MiB,4,4,0.005808 ms,110.081433 mW,639352.960000 pJ
conv/pointwise_1x1,PASS,0.139 s,0.00 MiB,0.00 MiB,4,4,0.004539 ms,114.835858 mW,521239.960000 pJ
conv/pointwise_tiled_chain,PASS,0.943 s,0.01 MiB,0.02 MiB,2,80,0.084437 ms,102.289307 mW,8637002.200000 pJ
conv/real_asymmetric_padding,PASS,0.115 s,0.00 MiB,0.00 MiB,4,4,0.005232 ms,111.870214 mW,585304.960000 pJ
conv/relu_conv_store,PASS,0.107 s,0.05 MiB,0.08 MiB,32,32,0.062978 ms,246.649827 mW,15533512.800000 pJ
conv/same_lower_3x3,PASS,0.094 s,0.00 MiB,0.00 MiB,5,5,0.004700 ms,119.232170 mW,560391.200000 pJ
conv/same_padding_3x3,PASS,0.126 s,0.00 MiB,0.00 MiB,5,5,0.004700 ms,119.232170 mW,560391.200000 pJ
conv/simple,PASS,0.125 s,0.00 MiB,0.00 MiB,2,2,0.003148 ms,94.665972 mW,298008.480000 pJ
conv/stride_2,PASS,0.146 s,0.00 MiB,0.00 MiB,2,2,0.002827 ms,96.393873 mW,272505.480000 pJ
conv/with_bias_3x3,PASS,0.121 s,0.00 MiB,0.00 MiB,3,3,0.004898 ms,107.176546 mW,524950.720000 pJ
conv/with_constant,PASS,0.106 s,0.00 MiB,0.00 MiB,3,3,0.004273 ms,109.362677 mW,467306.720000 pJ
conv/without_kernel_shape_attr,PASS,0.105 s,0.00 MiB,0.00 MiB,3,3,0.003091 ms,115.862414 mW,358130.720000 pJ
conv/yolo11n_stem,PASS,3.091 s,29.20 MiB,31.38 MiB,168,488,9.799213 ms,361.075203 mW,3538252825.000010 pJ
div/after_gemm,PASS,0.130 s,0.01 MiB,0.01 MiB,5,4,0.007784 ms,104.703618 mW,815012.960000 pJ
div/basic,PASS,0.116 s,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
div/channel_broadcast_1024,PASS,0.121 s,0.02 MiB,0.01 MiB,1,0,0.006913 ms,78.118038 mW,540030.000000 pJ
div/leading_dimension_broadcast,PASS,0.095 s,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
div/runtime_scalar_rhs,PASS,0.126 s,0.02 MiB,0.01 MiB,1,0,0.006913 ms,78.118038 mW,540030.000000 pJ
div/scalar_constant,PASS,0.069 s,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
gather/3d_input_axis1,PASS,0.121 s,0.00 MiB,0.00 MiB,1,0,0.000589 ms,78.081494 mW,45990.000000 pJ
gather/axis0_matrix_indices,PASS,0.088 s,0.00 MiB,0.00 MiB,1,0,0.000697 ms,78.068867 mW,54414.000000 pJ
gather/axis1,PASS,0.123 s,0.00 MiB,0.00 MiB,1,0,0.000801 ms,78.059925 mW,62526.000000 pJ
gather/negative_axis,PASS,0.087 s,0.00 MiB,0.00 MiB,1,0,0.001437 ms,78.033403 mW,112134.000000 pJ
gather/negative_indices,PASS,0.115 s,0.00 MiB,0.00 MiB,1,0,0.000376 ms,78.127660 mW,29376.000000 pJ
gemm/alpha_beta,PASS,0.121 s,0.01 MiB,0.01 MiB,5,4,0.007456 ms,105.272125 mW,784908.960000 pJ
gemm/bias_rank2_broadcast,PASS,0.127 s,0.00 MiB,0.01 MiB,5,4,0.007072 ms,105.979208 mW,749484.960000 pJ
gemm/dynamic,PASS,0.084 s,0.00 MiB,0.00 MiB,5,0,0.002421 ms,91.480793 mW,221475.000000 pJ
gemm/dynamic_alpha,PASS,0.104 s,0.00 MiB,0.00 MiB,5,0,0.003262 ms,91.415696 mW,298198.000000 pJ
gemm/dynamic_beta,PASS,0.110 s,0.00 MiB,0.00 MiB,5,0,0.004365 ms,91.316151 mW,398595.000000 pJ
gemm/dynamic_bias,PASS,0.092 s,0.00 MiB,0.00 MiB,5,0,0.002665 ms,91.445779 mW,243703.000000 pJ
gemm/dynamic_bias_alpha_beta,PASS,0.089 s,0.00 MiB,0.00 MiB,5,0,0.005629 ms,91.279268 mW,513811.000000 pJ
gemm/dynamic_transB,PASS,0.077 s,0.00 MiB,0.00 MiB,5,0,0.001301 ms,91.378171 mW,118883.000000 pJ
gemm/huge_1024,PASS,0.219 s,0.01 MiB,0.10 MiB,73,64,0.017522 ms,215.037402 mW,3767885.360000 pJ
gemm/large,PASS,0.097 s,0.02 MiB,0.03 MiB,17,16,0.011229 ms,140.152181 mW,1573768.840000 pJ
gemm/large_k_small_n,PASS,0.106 s,0.01 MiB,0.01 MiB,9,8,0.004748 ms,133.481449 mW,633769.920000 pJ
gemm/non_square,PASS,0.102 s,0.00 MiB,0.01 MiB,5,4,0.003527 ms,118.958310 mW,419565.960000 pJ
gemm/scalar_bias,PASS,0.111 s,0.00 MiB,0.01 MiB,5,4,0.007072 ms,105.979208 mW,749484.960000 pJ
gemm/simple,PASS,0.135 s,0.03 MiB,0.08 MiB,42,40,0.021640 ms,151.774196 mW,3284393.600000 pJ
gemm/small,PASS,0.074 s,0.00 MiB,0.00 MiB,2,2,0.004420 ms,90.144000 mW,398436.480000 pJ
gemm/small_k_large_n,PASS,0.129 s,0.01 MiB,0.02 MiB,17,8,0.007962 ms,131.005014 mW,1043061.920000 pJ
gemm/transA,PASS,0.089 s,0.00 MiB,0.01 MiB,5,4,0.005762 ms,109.140743 mW,628868.960000 pJ
gemm/transA_transB,PASS,0.115 s,0.00 MiB,0.01 MiB,5,4,0.005762 ms,109.140743 mW,628868.960000 pJ
gemm/transB,PASS,0.068 s,0.00 MiB,0.01 MiB,5,4,0.003527 ms,118.958310 mW,419565.960000 pJ
gemm/transB_with_bias,PASS,0.069 s,0.01 MiB,0.01 MiB,5,4,0.005046 ms,110.546762 mW,557818.960000 pJ
gemm/with_bias,PASS,0.113 s,0.01 MiB,0.01 MiB,5,4,0.005562 ms,108.767882 mW,604966.960000 pJ
gemv/constant,PASS,0.112 s,0.00 MiB,0.00 MiB,0,0,0.000000 ms,2.000000 mW,0.000000 pJ
gemv/simple,PASS,0.136 s,0.00 MiB,0.01 MiB,6,4,0.005160 ms,111.150380 mW,573535.960000 pJ
gemv/with_heterogeneous_constant,PASS,0.138 s,0.00 MiB,0.01 MiB,6,4,0.005549 ms,109.816536 mW,609371.960000 pJ
gemv/with_homogeneous_constant,PASS,0.140 s,0.00 MiB,0.01 MiB,6,4,0.005549 ms,109.816536 mW,609371.960000 pJ
gemv/with_scalar_constant,PASS,0.124 s,0.00 MiB,0.01 MiB,6,4,0.005549 ms,109.816536 mW,609371.960000 pJ
matmul/basic,PASS,0.081 s,0.00 MiB,0.00 MiB,2,2,0.004420 ms,90.144000 mW,398436.480000 pJ
matmul/batched_3d,PASS,0.133 s,0.00 MiB,0.01 MiB,5,4,0.005958 ms,108.588949 mW,646972.960000 pJ
matmul/batched_3d_dynamic,PASS,0.105 s,0.00 MiB,0.00 MiB,9,0,0.003525 ms,92.330213 mW,325464.000000 pJ
matmul/batched_left_constant,PASS,0.136 s,0.00 MiB,0.02 MiB,9,8,0.008822 ms,114.385164 mW,1009105.920000 pJ
matmul/batched_lhs_broadcast,PASS,0.133 s,0.00 MiB,0.01 MiB,5,4,0.005681 ms,109.389361 mW,621440.960000 pJ
matmul/batched_rhs_broadcast,PASS,0.134 s,0.00 MiB,0.01 MiB,5,4,0.005958 ms,108.588949 mW,646972.960000 pJ
matmul/dynamic,PASS,0.093 s,0.00 MiB,0.00 MiB,5,0,0.001621 ms,91.421962 mW,148195.000000 pJ
matmul/huge_1024,PASS,0.277 s,0.01 MiB,0.10 MiB,73,64,0.017522 ms,215.037402 mW,3767885.360000 pJ
matmul/left_constant,PASS,0.123 s,0.00 MiB,0.01 MiB,5,4,0.005853 ms,108.861944 mW,637168.960000 pJ
matmul/matrix_vector,PASS,0.150 s,0.52 MiB,0.78 MiB,168,173,0.384660 ms,202.131271 mW,77751814.880000 pJ
matmul/vector_matrix,PASS,0.186 s,0.01 MiB,0.01 MiB,9,8,0.007409 ms,118.680243 mW,879301.920000 pJ
mul/after_conv,PASS,0.127 s,0.00 MiB,0.00 MiB,4,3,0.005453 ms,107.639046 mW,586955.720000 pJ
mul/basic,PASS,0.070 s,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
mul/channel_broadcast_1024,PASS,0.065 s,0.02 MiB,0.01 MiB,1,0,0.006913 ms,78.118038 mW,540030.000000 pJ
mul/leading_dimension_broadcast,PASS,0.108 s,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
mul/scalar_constant,PASS,0.082 s,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
pool/avg_basic,PASS,0.070 s,0.00 MiB,0.00 MiB,1,0,0.011939 ms,78.022112 mW,931506.000000 pJ
pool/avg_ceil_mode,PASS,0.107 s,0.00 MiB,0.00 MiB,1,0,0.004359 ms,78.033035 mW,340146.000000 pJ
pool/avg_explicit_padding,PASS,0.111 s,0.00 MiB,0.00 MiB,1,0,0.008822 ms,78.027205 mW,688356.000000 pJ
pool/avg_include_pad,PASS,0.075 s,0.00 MiB,0.00 MiB,1,0,0.008506 ms,78.016929 mW,663612.000000 pJ
pool/avg_large_channels,PASS,0.064 s,0.04 MiB,0.02 MiB,1,0,0.178249 ms,78.280327 mW,13953390.000000 pJ
pool/avg_non_uniform_stride,PASS,0.068 s,0.00 MiB,0.00 MiB,1,0,0.014513 ms,78.016537 mW,1132254.000000 pJ
pool/avg_real_asymmetric_padding,PASS,0.064 s,0.00 MiB,0.00 MiB,1,0,0.025206 ms,78.024756 mW,1966692.000000 pJ
pool/max_after_conv,PASS,0.070 s,0.00 MiB,0.00 MiB,6,4,0.006452 ms,96.374606 mW,621808.960000 pJ
pool/max_basic,PASS,0.063 s,0.00 MiB,0.00 MiB,3,0,0.001634 ms,92.132191 mW,150544.000000 pJ
pool/max_ceil_mode,PASS,0.065 s,0.00 MiB,0.00 MiB,2,0,0.001297 ms,79.111025 mW,102607.000000 pJ
pool/max_global_style_kernel_equals_input,PASS,0.089 s,0.00 MiB,0.00 MiB,1,0,0.004366 ms,78.010994 mW,340596.000000 pJ
pool/max_non_square_kernel,PASS,0.103 s,0.00 MiB,0.00 MiB,4,0,0.003409 ms,93.253447 mW,317901.000000 pJ
pool/max_real_asymmetric_padding,PASS,0.088 s,0.00 MiB,0.00 MiB,4,0,0.003078 ms,93.124756 mW,286638.000000 pJ
pool/max_same_upper,PASS,0.065 s,0.00 MiB,0.00 MiB,3,0,0.003024 ms,92.095238 mW,278496.000000 pJ
pool/max_stride2_multichannel,PASS,0.086 s,0.00 MiB,0.00 MiB,3,0,0.004012 ms,92.269192 mW,370184.000000 pJ
reduce_mean/4d_spatial,PASS,0.061 s,0.00 MiB,0.00 MiB,3,0,0.000321 ms,92.448598 mW,29676.000000 pJ
reduce_mean/4d_spatial_keepdims_0,PASS,0.067 s,0.00 MiB,0.00 MiB,4,0,0.000655 ms,94.352672 mW,61801.000000 pJ
reduce_mean/after_conv,PASS,0.115 s,0.00 MiB,0.00 MiB,5,3,0.005342 ms,106.951089 mW,571332.720000 pJ
reduce_mean/all_axes_keepdims_0,PASS,0.103 s,0.00 MiB,0.00 MiB,2,0,0.000391 ms,79.237852 mW,30982.000000 pJ
reduce_mean/all_axes_keepdims_1,PASS,0.086 s,0.00 MiB,0.00 MiB,1,0,0.000221 ms,78.217195 mW,17286.000000 pJ
reduce_mean/basic,PASS,0.099 s,0.00 MiB,0.00 MiB,4,0,0.000373 ms,93.514745 mW,34881.000000 pJ
reduce_mean/channel_axis_nchw,PASS,0.078 s,0.03 MiB,0.02 MiB,4,0,0.164926 ms,93.596631 mW,15436518.000000 pJ
reduce_mean/keepdims_0,PASS,0.073 s,0.00 MiB,0.00 MiB,5,0,0.000748 ms,91.401070 mW,68368.000000 pJ
reduce_mean/large_dimension_1024,PASS,0.115 s,0.01 MiB,0.00 MiB,1,0,0.002785 ms,78.017235 mW,217278.000000 pJ
reduce_mean/legacy_axes_1_2_keepdims_1,PASS,0.074 s,0.00 MiB,0.00 MiB,2,0,0.000271 ms,79.354244 mW,21505.000000 pJ
reduce_mean/legacy_axis1_keepdims_0,PASS,0.111 s,0.00 MiB,0.00 MiB,9,0,0.001986 ms,92.501511 mW,183708.000000 pJ
reduce_mean/legacy_axis1_keepdims_1,PASS,0.086 s,0.00 MiB,0.00 MiB,8,0,0.001373 ms,94.559359 mW,129830.000000 pJ
reduce_mean/legacy_empty_axes_noop,PASS,0.115 s,0.00 MiB,0.00 MiB,1,0,0.000221 ms,78.217195 mW,17286.000000 pJ
reduce_mean/legacy_nchw_spatial,PASS,0.090 s,0.00 MiB,0.00 MiB,3,0,0.000321 ms,92.448598 mW,29676.000000 pJ
reduce_mean/legacy_negative_axis,PASS,0.110 s,0.00 MiB,0.00 MiB,6,0,0.000553 ms,93.520796 mW,51717.000000 pJ
reduce_mean/legacy_reduce_all_keepdims_1,PASS,0.107 s,0.00 MiB,0.00 MiB,1,0,0.000221 ms,78.217195 mW,17286.000000 pJ
reduce_mean/negative_axis,PASS,0.120 s,0.00 MiB,0.00 MiB,6,0,0.000553 ms,93.520796 mW,51717.000000 pJ
relu/4d,PASS,0.116 s,0.00 MiB,0.00 MiB,1,0,0.000521 ms,78.184261 mW,40734.000000 pJ
relu/after_conv,PASS,0.078 s,0.00 MiB,0.00 MiB,3,3,0.004956 ms,106.998935 mW,530286.720000 pJ
relu/after_gemm,PASS,0.077 s,0.01 MiB,0.01 MiB,5,4,0.007513 ms,105.158653 mW,790056.960000 pJ
relu/basic,PASS,0.066 s,0.00 MiB,0.00 MiB,1,0,0.000221 ms,78.217195 mW,17286.000000 pJ
reshape/4d_to_2d_flatten,PASS,0.098 s,0.00 MiB,0.00 MiB,1,0,0.000258 ms,78.279070 mW,20196.000000 pJ
reshape/infer_dim_minus_one,PASS,0.105 s,0.00 MiB,0.00 MiB,1,0,0.000162 ms,78.296296 mW,12684.000000 pJ
reshape/same_rank,PASS,0.057 s,0.00 MiB,0.00 MiB,1,0,0.000162 ms,78.296296 mW,12684.000000 pJ
reshape/zero_copies_input_dim,PASS,0.090 s,0.00 MiB,0.00 MiB,1,0,0.000162 ms,78.296296 mW,12684.000000 pJ
resize/height_only,PASS,0.104 s,0.00 MiB,0.00 MiB,1,0,0.000795 ms,78.060377 mW,62058.000000 pJ
resize/nearest_2x,PASS,0.104 s,0.00 MiB,0.00 MiB,1,0,0.001422 ms,78.033755 mW,110964.000000 pJ
resize/nearest_downsample,PASS,0.059 s,0.00 MiB,0.00 MiB,1,0,0.000481 ms,78.099792 mW,37566.000000 pJ
resize/non_uniform,PASS,0.063 s,0.00 MiB,0.00 MiB,1,0,0.002108 ms,78.034156 mW,164496.000000 pJ
resize/width_only,PASS,0.068 s,0.00 MiB,0.00 MiB,1,0,0.000792 ms,78.060606 mW,61824.000000 pJ
resize/with_sizes,PASS,0.056 s,0.00 MiB,0.00 MiB,1,0,0.000951 ms,78.050473 mW,74226.000000 pJ
sigmoid/4d,PASS,0.104 s,0.00 MiB,0.00 MiB,1,0,0.000521 ms,78.184261 mW,40734.000000 pJ
sigmoid/after_gemm,PASS,0.118 s,0.01 MiB,0.01 MiB,5,4,0.007513 ms,105.158653 mW,790056.960000 pJ
sigmoid/basic,PASS,0.083 s,0.00 MiB,0.00 MiB,1,0,0.000221 ms,78.217195 mW,17286.000000 pJ
slice/2d_basic,PASS,0.067 s,0.00 MiB,0.00 MiB,1,0,0.000242 ms,78.297521 mW,18948.000000 pJ
slice/after_conv,PASS,0.111 s,0.00 MiB,0.01 MiB,7,6,0.011296 ms,118.190765 mW,1335082.880000 pJ
slice/default_axes,PASS,0.065 s,0.00 MiB,0.00 MiB,1,0,0.000242 ms,78.297521 mW,18948.000000 pJ
slice/large_channel_1024,PASS,0.060 s,0.01 MiB,0.00 MiB,1,0,0.002832 ms,78.144068 mW,221304.000000 pJ
slice/nchw_spatial_crop,PASS,0.067 s,0.00 MiB,0.00 MiB,1,0,0.001302 ms,78.239631 mW,101868.000000 pJ
slice/negative_axis,PASS,0.098 s,0.00 MiB,0.00 MiB,1,0,0.000562 ms,78.298932 mW,44004.000000 pJ
slice/negative_indices,PASS,0.106 s,0.00 MiB,0.00 MiB,1,0,0.000322 ms,78.298137 mW,25212.000000 pJ
slice/step2,PASS,0.068 s,0.00 MiB,0.00 MiB,1,0,0.002042 ms,78.293830 mW,159876.000000 pJ
softmax/3d_last_axis,PASS,0.072 s,0.00 MiB,0.00 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED
softmax/basic,PASS,0.088 s,0.00 MiB,0.00 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED
softmax/channel_axis,PASS,0.110 s,0.00 MiB,0.00 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED
softmax/large_dimension_1024,PASS,0.104 s,0.01 MiB,0.01 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED
softmax/negative_axis,PASS,0.118 s,0.00 MiB,0.00 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED
split/basic,PASS,0.077 s,0.00 MiB,0.00 MiB,1,0,0.000403 ms,78.297767 mW,31554.000000 pJ
split/equal_three_way,PASS,0.107 s,0.00 MiB,0.00 MiB,1,0,0.000564 ms,78.297872 mW,44160.000000 pJ
split/negative_axis,PASS,0.111 s,0.00 MiB,0.00 MiB,1,0,0.001083 ms,78.288089 mW,84786.000000 pJ
split/uneven_channel_axis_4d,PASS,0.061 s,0.00 MiB,0.00 MiB,1,0,0.000242 ms,78.297521 mW,18948.000000 pJ
sub/after_gemm,PASS,0.068 s,0.01 MiB,0.01 MiB,5,4,0.007784 ms,104.703618 mW,815012.960000 pJ
sub/basic,PASS,0.063 s,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
sub/broadcast_row,PASS,0.075 s,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
sub/channel_broadcast_1024,PASS,0.052 s,0.02 MiB,0.01 MiB,1,0,0.006913 ms,78.118038 mW,540030.000000 pJ
sub/constant_lhs_broadcast,PASS,0.057 s,0.00 MiB,0.00 MiB,1,0,0.000322 ms,78.223602 mW,25188.000000 pJ
sub/leading_dimension_broadcast,PASS,0.053 s,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
1 Operation Result Compile Host mem Cores mem Cores Xbars Latency Power Energy
2 add/after_gemm PASS 0.141 s 0.01 MiB 0.01 MiB 5 4 0.007784 ms 104.703618 mW 815012.960000 pJ
3 add/basic PASS 0.114 s 0.00 MiB 0.00 MiB 1 0 0.000323 ms 78.222910 mW 25266.000000 pJ
4 add/broadcast_row PASS 0.110 s 0.00 MiB 0.00 MiB 1 0 0.000323 ms 78.222910 mW 25266.000000 pJ
5 add/channel_broadcast_1024 PASS 0.105 s 0.02 MiB 0.01 MiB 1 0 0.006913 ms 78.118038 mW 540030.000000 pJ
6 add/leading_dimension_broadcast PASS 0.137 s 0.00 MiB 0.00 MiB 1 0 0.000323 ms 78.222910 mW 25266.000000 pJ
7 concat/channel_axis PASS 0.127 s 0.00 MiB 0.00 MiB 1 0 0.000457 ms 78.157549 mW 35718.000000 pJ
8 concat/negative_axis PASS 0.102 s 0.00 MiB 0.00 MiB 1 0 0.001043 ms 78.092042 mW 81450.000000 pJ
9 concat/three_inputs_channel_axis PASS 0.127 s 0.00 MiB 0.00 MiB 1 0 0.000644 ms 78.149068 mW 50328.000000 pJ
10 conv/batch_2 PASS 0.102 s 0.00 MiB 0.00 MiB 2 2 0.013694 ms 82.623885 mW 1131451.480000 pJ
11 conv/batch_4_pointwise PASS 0.110 s 0.00 MiB 0.01 MiB 5 4 0.003932 ms 116.078576 mW 456420.960000 pJ
12 conv/depthwise_1024_channels PASS 0.167 s 0.19 MiB 0.38 MiB 129 128 0.220751 ms 178.454307 mW 39393966.720000 pJ
13 conv/depthwise_grouped PASS 0.109 s 0.01 MiB 0.00 MiB 5 4 0.006024 ms 108.326521 mW 652558.960000 pJ
14 conv/dilated_3x3 PASS 0.143 s 0.00 MiB 0.00 MiB 3 3 0.004045 ms 110.234541 mW 445898.720000 pJ
15 conv/dynamic PASS 0.107 s 0.00 MiB 0.00 MiB 5 0 0.001835 ms 92.281199 mW 169336.000000 pJ
16 conv/explicit_padding PASS 0.139 s 0.00 MiB 0.00 MiB 4 4 0.004327 ms 115.794768 mW 501043.960000 pJ
17 conv/grouped_many_groups PASS 0.616 s 0.05 MiB 0.09 MiB 65 64 0.181845 ms 142.210104 mW 25860196.360000 pJ
18 conv/grouped_two_groups PASS 0.145 s 0.00 MiB 0.00 MiB 3 2 0.005360 ms 101.459418 mW 543822.480000 pJ
19 conv/huge_pointwise_1024 PASS 0.749 s 0.01 MiB 0.01 MiB 1 64 0.028261 ms 133.488743 mW 3772525.360000 pJ
20 conv/huge_pointwise_1024_dynamic PASS 0.097 s 8.04 MiB 12.61 MiB 168 0 2.627964 ms 169.518697 mW 445489032.000000 pJ
21 conv/kernel_3x3 PASS 0.155 s 0.00 MiB 0.00 MiB 3 3 0.003091 ms 115.862414 mW 358130.720000 pJ
22 conv/kernel_equals_input_spatial PASS 0.156 s 0.00 MiB 0.00 MiB 1 2 0.008443 ms 83.863849 mW 708062.480000 pJ
23 conv/large_input_channels_1x1 PASS 0.160 s 0.01 MiB 0.01 MiB 1 8 0.017167 ms 89.416900 mW 1535019.920000 pJ
24 conv/large_output_channels_1x1 PASS 0.141 s 0.00 MiB 0.01 MiB 1 8 0.004964 ms 117.628106 mW 583905.920000 pJ
25 conv/large_spatial PASS 0.141 s 0.00 MiB 0.01 MiB 6 6 0.004096 ms 129.015000 mW 528445.440000 pJ
26 conv/multi_channel PASS 0.143 s 0.00 MiB 0.00 MiB 3 3 0.005148 ms 106.453520 mW 548022.720000 pJ
27 conv/non_square_kernel_1x3 PASS 0.127 s 0.00 MiB 0.00 MiB 5 5 0.004029 ms 123.600943 mW 497988.200000 pJ
28 conv/non_square_kernel_3x1 PASS 0.085 s 0.00 MiB 0.00 MiB 3 3 0.005526 ms 105.464843 mW 582798.720000 pJ
29 conv/non_uniform_stride PASS 0.120 s 0.00 MiB 0.00 MiB 4 4 0.005808 ms 110.081433 mW 639352.960000 pJ
30 conv/pointwise_1x1 PASS 0.139 s 0.00 MiB 0.00 MiB 4 4 0.004539 ms 114.835858 mW 521239.960000 pJ
31 conv/pointwise_tiled_chain PASS 0.943 s 0.01 MiB 0.02 MiB 2 80 0.084437 ms 102.289307 mW 8637002.200000 pJ
32 conv/real_asymmetric_padding PASS 0.115 s 0.00 MiB 0.00 MiB 4 4 0.005232 ms 111.870214 mW 585304.960000 pJ
33 conv/relu_conv_store PASS 0.107 s 0.05 MiB 0.08 MiB 32 32 0.062978 ms 246.649827 mW 15533512.800000 pJ
34 conv/same_lower_3x3 PASS 0.094 s 0.00 MiB 0.00 MiB 5 5 0.004700 ms 119.232170 mW 560391.200000 pJ
35 conv/same_padding_3x3 PASS 0.126 s 0.00 MiB 0.00 MiB 5 5 0.004700 ms 119.232170 mW 560391.200000 pJ
36 conv/simple PASS 0.125 s 0.00 MiB 0.00 MiB 2 2 0.003148 ms 94.665972 mW 298008.480000 pJ
37 conv/stride_2 PASS 0.146 s 0.00 MiB 0.00 MiB 2 2 0.002827 ms 96.393873 mW 272505.480000 pJ
38 conv/with_bias_3x3 PASS 0.121 s 0.00 MiB 0.00 MiB 3 3 0.004898 ms 107.176546 mW 524950.720000 pJ
39 conv/with_constant PASS 0.106 s 0.00 MiB 0.00 MiB 3 3 0.004273 ms 109.362677 mW 467306.720000 pJ
40 conv/without_kernel_shape_attr PASS 0.105 s 0.00 MiB 0.00 MiB 3 3 0.003091 ms 115.862414 mW 358130.720000 pJ
41 conv/yolo11n_stem PASS 3.091 s 29.20 MiB 31.38 MiB 168 488 9.799213 ms 361.075203 mW 3538252825.000010 pJ
42 div/after_gemm PASS 0.130 s 0.01 MiB 0.01 MiB 5 4 0.007784 ms 104.703618 mW 815012.960000 pJ
43 div/basic PASS 0.116 s 0.00 MiB 0.00 MiB 1 0 0.000323 ms 78.222910 mW 25266.000000 pJ
44 div/channel_broadcast_1024 PASS 0.121 s 0.02 MiB 0.01 MiB 1 0 0.006913 ms 78.118038 mW 540030.000000 pJ
45 div/leading_dimension_broadcast PASS 0.095 s 0.00 MiB 0.00 MiB 1 0 0.000323 ms 78.222910 mW 25266.000000 pJ
46 div/runtime_scalar_rhs PASS 0.126 s 0.02 MiB 0.01 MiB 1 0 0.006913 ms 78.118038 mW 540030.000000 pJ
47 div/scalar_constant PASS 0.069 s 0.00 MiB 0.00 MiB 1 0 0.000323 ms 78.222910 mW 25266.000000 pJ
48 gather/3d_input_axis1 PASS 0.121 s 0.00 MiB 0.00 MiB 1 0 0.000589 ms 78.081494 mW 45990.000000 pJ
49 gather/axis0_matrix_indices PASS 0.088 s 0.00 MiB 0.00 MiB 1 0 0.000697 ms 78.068867 mW 54414.000000 pJ
50 gather/axis1 PASS 0.123 s 0.00 MiB 0.00 MiB 1 0 0.000801 ms 78.059925 mW 62526.000000 pJ
51 gather/negative_axis PASS 0.087 s 0.00 MiB 0.00 MiB 1 0 0.001437 ms 78.033403 mW 112134.000000 pJ
52 gather/negative_indices PASS 0.115 s 0.00 MiB 0.00 MiB 1 0 0.000376 ms 78.127660 mW 29376.000000 pJ
53 gemm/alpha_beta PASS 0.121 s 0.01 MiB 0.01 MiB 5 4 0.007456 ms 105.272125 mW 784908.960000 pJ
54 gemm/bias_rank2_broadcast PASS 0.127 s 0.00 MiB 0.01 MiB 5 4 0.007072 ms 105.979208 mW 749484.960000 pJ
55 gemm/dynamic PASS 0.084 s 0.00 MiB 0.00 MiB 5 0 0.002421 ms 91.480793 mW 221475.000000 pJ
56 gemm/dynamic_alpha PASS 0.104 s 0.00 MiB 0.00 MiB 5 0 0.003262 ms 91.415696 mW 298198.000000 pJ
57 gemm/dynamic_beta PASS 0.110 s 0.00 MiB 0.00 MiB 5 0 0.004365 ms 91.316151 mW 398595.000000 pJ
58 gemm/dynamic_bias PASS 0.092 s 0.00 MiB 0.00 MiB 5 0 0.002665 ms 91.445779 mW 243703.000000 pJ
59 gemm/dynamic_bias_alpha_beta PASS 0.089 s 0.00 MiB 0.00 MiB 5 0 0.005629 ms 91.279268 mW 513811.000000 pJ
60 gemm/dynamic_transB PASS 0.077 s 0.00 MiB 0.00 MiB 5 0 0.001301 ms 91.378171 mW 118883.000000 pJ
61 gemm/huge_1024 PASS 0.219 s 0.01 MiB 0.10 MiB 73 64 0.017522 ms 215.037402 mW 3767885.360000 pJ
62 gemm/large PASS 0.097 s 0.02 MiB 0.03 MiB 17 16 0.011229 ms 140.152181 mW 1573768.840000 pJ
63 gemm/large_k_small_n PASS 0.106 s 0.01 MiB 0.01 MiB 9 8 0.004748 ms 133.481449 mW 633769.920000 pJ
64 gemm/non_square PASS 0.102 s 0.00 MiB 0.01 MiB 5 4 0.003527 ms 118.958310 mW 419565.960000 pJ
65 gemm/scalar_bias PASS 0.111 s 0.00 MiB 0.01 MiB 5 4 0.007072 ms 105.979208 mW 749484.960000 pJ
66 gemm/simple PASS 0.135 s 0.03 MiB 0.08 MiB 42 40 0.021640 ms 151.774196 mW 3284393.600000 pJ
67 gemm/small PASS 0.074 s 0.00 MiB 0.00 MiB 2 2 0.004420 ms 90.144000 mW 398436.480000 pJ
68 gemm/small_k_large_n PASS 0.129 s 0.01 MiB 0.02 MiB 17 8 0.007962 ms 131.005014 mW 1043061.920000 pJ
69 gemm/transA PASS 0.089 s 0.00 MiB 0.01 MiB 5 4 0.005762 ms 109.140743 mW 628868.960000 pJ
70 gemm/transA_transB PASS 0.115 s 0.00 MiB 0.01 MiB 5 4 0.005762 ms 109.140743 mW 628868.960000 pJ
71 gemm/transB PASS 0.068 s 0.00 MiB 0.01 MiB 5 4 0.003527 ms 118.958310 mW 419565.960000 pJ
72 gemm/transB_with_bias PASS 0.069 s 0.01 MiB 0.01 MiB 5 4 0.005046 ms 110.546762 mW 557818.960000 pJ
73 gemm/with_bias PASS 0.113 s 0.01 MiB 0.01 MiB 5 4 0.005562 ms 108.767882 mW 604966.960000 pJ
74 gemv/constant PASS 0.112 s 0.00 MiB 0.00 MiB 0 0 0.000000 ms 2.000000 mW 0.000000 pJ
75 gemv/simple PASS 0.136 s 0.00 MiB 0.01 MiB 6 4 0.005160 ms 111.150380 mW 573535.960000 pJ
76 gemv/with_heterogeneous_constant PASS 0.138 s 0.00 MiB 0.01 MiB 6 4 0.005549 ms 109.816536 mW 609371.960000 pJ
77 gemv/with_homogeneous_constant PASS 0.140 s 0.00 MiB 0.01 MiB 6 4 0.005549 ms 109.816536 mW 609371.960000 pJ
78 gemv/with_scalar_constant PASS 0.124 s 0.00 MiB 0.01 MiB 6 4 0.005549 ms 109.816536 mW 609371.960000 pJ
79 matmul/basic PASS 0.081 s 0.00 MiB 0.00 MiB 2 2 0.004420 ms 90.144000 mW 398436.480000 pJ
80 matmul/batched_3d PASS 0.133 s 0.00 MiB 0.01 MiB 5 4 0.005958 ms 108.588949 mW 646972.960000 pJ
81 matmul/batched_3d_dynamic PASS 0.105 s 0.00 MiB 0.00 MiB 9 0 0.003525 ms 92.330213 mW 325464.000000 pJ
82 matmul/batched_left_constant PASS 0.136 s 0.00 MiB 0.02 MiB 9 8 0.008822 ms 114.385164 mW 1009105.920000 pJ
83 matmul/batched_lhs_broadcast PASS 0.133 s 0.00 MiB 0.01 MiB 5 4 0.005681 ms 109.389361 mW 621440.960000 pJ
84 matmul/batched_rhs_broadcast PASS 0.134 s 0.00 MiB 0.01 MiB 5 4 0.005958 ms 108.588949 mW 646972.960000 pJ
85 matmul/dynamic PASS 0.093 s 0.00 MiB 0.00 MiB 5 0 0.001621 ms 91.421962 mW 148195.000000 pJ
86 matmul/huge_1024 PASS 0.277 s 0.01 MiB 0.10 MiB 73 64 0.017522 ms 215.037402 mW 3767885.360000 pJ
87 matmul/left_constant PASS 0.123 s 0.00 MiB 0.01 MiB 5 4 0.005853 ms 108.861944 mW 637168.960000 pJ
88 matmul/matrix_vector PASS 0.150 s 0.52 MiB 0.78 MiB 168 173 0.384660 ms 202.131271 mW 77751814.880000 pJ
89 matmul/vector_matrix PASS 0.186 s 0.01 MiB 0.01 MiB 9 8 0.007409 ms 118.680243 mW 879301.920000 pJ
90 mul/after_conv PASS 0.127 s 0.00 MiB 0.00 MiB 4 3 0.005453 ms 107.639046 mW 586955.720000 pJ
91 mul/basic PASS 0.070 s 0.00 MiB 0.00 MiB 1 0 0.000323 ms 78.222910 mW 25266.000000 pJ
92 mul/channel_broadcast_1024 PASS 0.065 s 0.02 MiB 0.01 MiB 1 0 0.006913 ms 78.118038 mW 540030.000000 pJ
93 mul/leading_dimension_broadcast PASS 0.108 s 0.00 MiB 0.00 MiB 1 0 0.000323 ms 78.222910 mW 25266.000000 pJ
94 mul/scalar_constant PASS 0.082 s 0.00 MiB 0.00 MiB 1 0 0.000323 ms 78.222910 mW 25266.000000 pJ
95 pool/avg_basic PASS 0.070 s 0.00 MiB 0.00 MiB 1 0 0.011939 ms 78.022112 mW 931506.000000 pJ
96 pool/avg_ceil_mode PASS 0.107 s 0.00 MiB 0.00 MiB 1 0 0.004359 ms 78.033035 mW 340146.000000 pJ
97 pool/avg_explicit_padding PASS 0.111 s 0.00 MiB 0.00 MiB 1 0 0.008822 ms 78.027205 mW 688356.000000 pJ
98 pool/avg_include_pad PASS 0.075 s 0.00 MiB 0.00 MiB 1 0 0.008506 ms 78.016929 mW 663612.000000 pJ
99 pool/avg_large_channels PASS 0.064 s 0.04 MiB 0.02 MiB 1 0 0.178249 ms 78.280327 mW 13953390.000000 pJ
100 pool/avg_non_uniform_stride PASS 0.068 s 0.00 MiB 0.00 MiB 1 0 0.014513 ms 78.016537 mW 1132254.000000 pJ
101 pool/avg_real_asymmetric_padding PASS 0.064 s 0.00 MiB 0.00 MiB 1 0 0.025206 ms 78.024756 mW 1966692.000000 pJ
102 pool/max_after_conv PASS 0.070 s 0.00 MiB 0.00 MiB 6 4 0.006452 ms 96.374606 mW 621808.960000 pJ
103 pool/max_basic PASS 0.063 s 0.00 MiB 0.00 MiB 3 0 0.001634 ms 92.132191 mW 150544.000000 pJ
104 pool/max_ceil_mode PASS 0.065 s 0.00 MiB 0.00 MiB 2 0 0.001297 ms 79.111025 mW 102607.000000 pJ
105 pool/max_global_style_kernel_equals_input PASS 0.089 s 0.00 MiB 0.00 MiB 1 0 0.004366 ms 78.010994 mW 340596.000000 pJ
106 pool/max_non_square_kernel PASS 0.103 s 0.00 MiB 0.00 MiB 4 0 0.003409 ms 93.253447 mW 317901.000000 pJ
107 pool/max_real_asymmetric_padding PASS 0.088 s 0.00 MiB 0.00 MiB 4 0 0.003078 ms 93.124756 mW 286638.000000 pJ
108 pool/max_same_upper PASS 0.065 s 0.00 MiB 0.00 MiB 3 0 0.003024 ms 92.095238 mW 278496.000000 pJ
109 pool/max_stride2_multichannel PASS 0.086 s 0.00 MiB 0.00 MiB 3 0 0.004012 ms 92.269192 mW 370184.000000 pJ
110 reduce_mean/4d_spatial PASS 0.061 s 0.00 MiB 0.00 MiB 3 0 0.000321 ms 92.448598 mW 29676.000000 pJ
111 reduce_mean/4d_spatial_keepdims_0 PASS 0.067 s 0.00 MiB 0.00 MiB 4 0 0.000655 ms 94.352672 mW 61801.000000 pJ
112 reduce_mean/after_conv PASS 0.115 s 0.00 MiB 0.00 MiB 5 3 0.005342 ms 106.951089 mW 571332.720000 pJ
113 reduce_mean/all_axes_keepdims_0 PASS 0.103 s 0.00 MiB 0.00 MiB 2 0 0.000391 ms 79.237852 mW 30982.000000 pJ
114 reduce_mean/all_axes_keepdims_1 PASS 0.086 s 0.00 MiB 0.00 MiB 1 0 0.000221 ms 78.217195 mW 17286.000000 pJ
115 reduce_mean/basic PASS 0.099 s 0.00 MiB 0.00 MiB 4 0 0.000373 ms 93.514745 mW 34881.000000 pJ
116 reduce_mean/channel_axis_nchw PASS 0.078 s 0.03 MiB 0.02 MiB 4 0 0.164926 ms 93.596631 mW 15436518.000000 pJ
117 reduce_mean/keepdims_0 PASS 0.073 s 0.00 MiB 0.00 MiB 5 0 0.000748 ms 91.401070 mW 68368.000000 pJ
118 reduce_mean/large_dimension_1024 PASS 0.115 s 0.01 MiB 0.00 MiB 1 0 0.002785 ms 78.017235 mW 217278.000000 pJ
119 reduce_mean/legacy_axes_1_2_keepdims_1 PASS 0.074 s 0.00 MiB 0.00 MiB 2 0 0.000271 ms 79.354244 mW 21505.000000 pJ
120 reduce_mean/legacy_axis1_keepdims_0 PASS 0.111 s 0.00 MiB 0.00 MiB 9 0 0.001986 ms 92.501511 mW 183708.000000 pJ
121 reduce_mean/legacy_axis1_keepdims_1 PASS 0.086 s 0.00 MiB 0.00 MiB 8 0 0.001373 ms 94.559359 mW 129830.000000 pJ
122 reduce_mean/legacy_empty_axes_noop PASS 0.115 s 0.00 MiB 0.00 MiB 1 0 0.000221 ms 78.217195 mW 17286.000000 pJ
123 reduce_mean/legacy_nchw_spatial PASS 0.090 s 0.00 MiB 0.00 MiB 3 0 0.000321 ms 92.448598 mW 29676.000000 pJ
124 reduce_mean/legacy_negative_axis PASS 0.110 s 0.00 MiB 0.00 MiB 6 0 0.000553 ms 93.520796 mW 51717.000000 pJ
125 reduce_mean/legacy_reduce_all_keepdims_1 PASS 0.107 s 0.00 MiB 0.00 MiB 1 0 0.000221 ms 78.217195 mW 17286.000000 pJ
126 reduce_mean/negative_axis PASS 0.120 s 0.00 MiB 0.00 MiB 6 0 0.000553 ms 93.520796 mW 51717.000000 pJ
127 relu/4d PASS 0.116 s 0.00 MiB 0.00 MiB 1 0 0.000521 ms 78.184261 mW 40734.000000 pJ
128 relu/after_conv PASS 0.078 s 0.00 MiB 0.00 MiB 3 3 0.004956 ms 106.998935 mW 530286.720000 pJ
129 relu/after_gemm PASS 0.077 s 0.01 MiB 0.01 MiB 5 4 0.007513 ms 105.158653 mW 790056.960000 pJ
130 relu/basic PASS 0.066 s 0.00 MiB 0.00 MiB 1 0 0.000221 ms 78.217195 mW 17286.000000 pJ
131 reshape/4d_to_2d_flatten PASS 0.098 s 0.00 MiB 0.00 MiB 1 0 0.000258 ms 78.279070 mW 20196.000000 pJ
132 reshape/infer_dim_minus_one PASS 0.105 s 0.00 MiB 0.00 MiB 1 0 0.000162 ms 78.296296 mW 12684.000000 pJ
133 reshape/same_rank PASS 0.057 s 0.00 MiB 0.00 MiB 1 0 0.000162 ms 78.296296 mW 12684.000000 pJ
134 reshape/zero_copies_input_dim PASS 0.090 s 0.00 MiB 0.00 MiB 1 0 0.000162 ms 78.296296 mW 12684.000000 pJ
135 resize/height_only PASS 0.104 s 0.00 MiB 0.00 MiB 1 0 0.000795 ms 78.060377 mW 62058.000000 pJ
136 resize/nearest_2x PASS 0.104 s 0.00 MiB 0.00 MiB 1 0 0.001422 ms 78.033755 mW 110964.000000 pJ
137 resize/nearest_downsample PASS 0.059 s 0.00 MiB 0.00 MiB 1 0 0.000481 ms 78.099792 mW 37566.000000 pJ
138 resize/non_uniform PASS 0.063 s 0.00 MiB 0.00 MiB 1 0 0.002108 ms 78.034156 mW 164496.000000 pJ
139 resize/width_only PASS 0.068 s 0.00 MiB 0.00 MiB 1 0 0.000792 ms 78.060606 mW 61824.000000 pJ
140 resize/with_sizes PASS 0.056 s 0.00 MiB 0.00 MiB 1 0 0.000951 ms 78.050473 mW 74226.000000 pJ
141 sigmoid/4d PASS 0.104 s 0.00 MiB 0.00 MiB 1 0 0.000521 ms 78.184261 mW 40734.000000 pJ
142 sigmoid/after_gemm PASS 0.118 s 0.01 MiB 0.01 MiB 5 4 0.007513 ms 105.158653 mW 790056.960000 pJ
143 sigmoid/basic PASS 0.083 s 0.00 MiB 0.00 MiB 1 0 0.000221 ms 78.217195 mW 17286.000000 pJ
144 slice/2d_basic PASS 0.067 s 0.00 MiB 0.00 MiB 1 0 0.000242 ms 78.297521 mW 18948.000000 pJ
145 slice/after_conv PASS 0.111 s 0.00 MiB 0.01 MiB 7 6 0.011296 ms 118.190765 mW 1335082.880000 pJ
146 slice/default_axes PASS 0.065 s 0.00 MiB 0.00 MiB 1 0 0.000242 ms 78.297521 mW 18948.000000 pJ
147 slice/large_channel_1024 PASS 0.060 s 0.01 MiB 0.00 MiB 1 0 0.002832 ms 78.144068 mW 221304.000000 pJ
148 slice/nchw_spatial_crop PASS 0.067 s 0.00 MiB 0.00 MiB 1 0 0.001302 ms 78.239631 mW 101868.000000 pJ
149 slice/negative_axis PASS 0.098 s 0.00 MiB 0.00 MiB 1 0 0.000562 ms 78.298932 mW 44004.000000 pJ
150 slice/negative_indices PASS 0.106 s 0.00 MiB 0.00 MiB 1 0 0.000322 ms 78.298137 mW 25212.000000 pJ
151 slice/step2 PASS 0.068 s 0.00 MiB 0.00 MiB 1 0 0.002042 ms 78.293830 mW 159876.000000 pJ
152 softmax/3d_last_axis PASS 0.072 s 0.00 MiB 0.00 MiB 1 0 UNSUPPORTED UNSUPPORTED UNSUPPORTED
153 softmax/basic PASS 0.088 s 0.00 MiB 0.00 MiB 1 0 UNSUPPORTED UNSUPPORTED UNSUPPORTED
154 softmax/channel_axis PASS 0.110 s 0.00 MiB 0.00 MiB 1 0 UNSUPPORTED UNSUPPORTED UNSUPPORTED
155 softmax/large_dimension_1024 PASS 0.104 s 0.01 MiB 0.01 MiB 1 0 UNSUPPORTED UNSUPPORTED UNSUPPORTED
156 softmax/negative_axis PASS 0.118 s 0.00 MiB 0.00 MiB 1 0 UNSUPPORTED UNSUPPORTED UNSUPPORTED
157 split/basic PASS 0.077 s 0.00 MiB 0.00 MiB 1 0 0.000403 ms 78.297767 mW 31554.000000 pJ
158 split/equal_three_way PASS 0.107 s 0.00 MiB 0.00 MiB 1 0 0.000564 ms 78.297872 mW 44160.000000 pJ
159 split/negative_axis PASS 0.111 s 0.00 MiB 0.00 MiB 1 0 0.001083 ms 78.288089 mW 84786.000000 pJ
160 split/uneven_channel_axis_4d PASS 0.061 s 0.00 MiB 0.00 MiB 1 0 0.000242 ms 78.297521 mW 18948.000000 pJ
161 sub/after_gemm PASS 0.068 s 0.01 MiB 0.01 MiB 5 4 0.007784 ms 104.703618 mW 815012.960000 pJ
162 sub/basic PASS 0.063 s 0.00 MiB 0.00 MiB 1 0 0.000323 ms 78.222910 mW 25266.000000 pJ
163 sub/broadcast_row PASS 0.075 s 0.00 MiB 0.00 MiB 1 0 0.000323 ms 78.222910 mW 25266.000000 pJ
164 sub/channel_broadcast_1024 PASS 0.052 s 0.02 MiB 0.01 MiB 1 0 0.006913 ms 78.118038 mW 540030.000000 pJ
165 sub/constant_lhs_broadcast PASS 0.057 s 0.00 MiB 0.00 MiB 1 0 0.000322 ms 78.223602 mW 25188.000000 pJ
166 sub/leading_dimension_broadcast PASS 0.053 s 0.00 MiB 0.00 MiB 1 0 0.000323 ms 78.222910 mW 25266.000000 pJ
+60 -2
View File
@@ -1,8 +1,11 @@
import json import json
import re import re
import shutil import shutil
import struct
from pathlib import Path from pathlib import Path
import numpy as np
_METRIC_PATTERNS = { _METRIC_PATTERNS = {
"output_count": r"output count:\s+([0-9]+)\s+samples", "output_count": r"output count:\s+([0-9]+)\s+samples",
@@ -14,6 +17,14 @@ _METRIC_PATTERNS = {
} }
def read_raptor_instruction_count(path):
with open(path, "rb") as f:
magic, version, count = struct.unpack("<4sII", f.read(12))
if magic != b"PIMB" or version != 1:
raise ValueError(f"Invalid Raptor binary instruction file: {path}")
return count
def parse_pimsim_nn_metrics(output): def parse_pimsim_nn_metrics(output):
metrics = {"raw_output": output} metrics = {"raw_output": output}
for name, pattern in _METRIC_PATTERNS.items(): for name, pattern in _METRIC_PATTERNS.items():
@@ -45,6 +56,46 @@ def export_raptor_latency_artifact(pim_dir, output_dir):
json.dump(config, f, separators=(",", ":")) json.dump(config, f, separators=(",", ":"))
f.write("\n") f.write("\n")
binary_sources = sorted(pim_dir.glob("core_*.pim"), key=lambda path: int(path.stem.split("_")[1]))
if binary_sources:
record_dtype = np.dtype([
("opcode", "u1"), ("rd", "u1"), ("r1", "u1"), ("flags", "u1"),
("r2_or_imm", "<i4"), ("generic1", "<i4"),
("generic2", "<i4"), ("generic3", "<i4"),
])
for source in binary_sources:
destination = output_dir / source.name
shutil.copyfile(source, destination)
count = read_raptor_instruction_count(destination)
if destination.stat().st_size != 12 + count * record_dtype.itemsize:
raise ValueError(f"Invalid Raptor binary instruction file: {source}")
records = np.memmap(destination, dtype=record_dtype, mode="r+", offset=12, shape=(count,))
scalar_sldi = np.zeros(count, dtype=bool)
vmv = records["opcode"] == 22
sldi = records["opcode"] == 1
for register in np.unique(records["r2_or_imm"][vmv]):
definitions = np.flatnonzero(sldi & (records["rd"] == register))
consumers = np.flatnonzero(vmv & (records["r2_or_imm"] == register))
reaching = np.searchsorted(definitions, consumers) - 1
scalar_sldi[definitions[reaching[reaching >= 0]]] = True
for start in range(0, count, 1_000_000):
chunk = records[start:start + 1_000_000]
setbw = chunk["opcode"] == 8
chunk["generic1"][setbw] = 8
chunk["generic2"][setbw] = 8
address_sldi = (chunk["opcode"] == 1) & ~scalar_sldi[start:start + len(chunk)]
if np.any(chunk["r2_or_imm"][address_sldi] % 4):
raise ValueError(f"Raptor address is not aligned to its fp32 element width: {source}")
chunk["r2_or_imm"][address_sldi] //= 4
transfer = (chunk["opcode"] >= 25) & (chunk["opcode"] <= 30)
if np.any(chunk["generic2"][transfer] % 4) or np.any(chunk["generic3"][transfer] % 4):
raise ValueError(f"Raptor transfer field is not aligned to its fp32 element width: {source}")
chunk["generic2"][transfer] //= 4
chunk["generic3"][transfer] //= 4
records.flush()
del records
return output_dir
byte_size_fields = { byte_size_fields = {
"ld": "size", "ld": "size",
"st": "size", "st": "size",
@@ -56,12 +107,19 @@ def export_raptor_latency_artifact(pim_dir, output_dir):
for source in sorted(pim_dir.glob("core_*.json"), key=lambda path: int(path.stem.split("_")[1])): for source in sorted(pim_dir.glob("core_*.json"), key=lambda path: int(path.stem.split("_")[1])):
with open(source, encoding="utf-8") as f: with open(source, encoding="utf-8") as f:
instructions = json.load(f) instructions = json.load(f)
for instruction in instructions: last_sldi = {}
scalar_sldi = set()
for index, instruction in enumerate(instructions):
if instruction["op"] == "sldi":
last_sldi[instruction["rd"]] = index
elif instruction["op"] == "vmv" and instruction["rs2"] in last_sldi:
scalar_sldi.add(last_sldi[instruction["rs2"]])
for index, instruction in enumerate(instructions):
op = instruction["op"] op = instruction["op"]
if op == "setbw": if op == "setbw":
instruction["ibiw"] = 8 instruction["ibiw"] = 8
instruction["obiw"] = 8 instruction["obiw"] = 8
elif op == "sldi": elif op == "sldi" and index not in scalar_sldi:
instruction["imm"] = int8_bytes(instruction["imm"], "address") instruction["imm"] = int8_bytes(instruction["imm"], "address")
if field := byte_size_fields.get(op): if field := byte_size_fields.get(op):
instruction[field] = int8_bytes(instruction[field], f"{op} {field}") instruction[field] = int8_bytes(instruction[field], f"{op} {field}")
+55 -14
View File
@@ -1,8 +1,10 @@
import json import json
import os import os
import re
import shutil import shutil
import subprocess import subprocess
import sys import sys
import time
import numpy as np import numpy as np
from dataclasses import dataclass, field from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
@@ -10,7 +12,7 @@ from colorama import Style, Fore
from .gen_network_runner import gen_network_runner from .gen_network_runner import gen_network_runner
from .onnx_utils import gen_random_inputs, save_inputs_to_files, onnx_io, write_inputs_to_memory_bin, _ONNX_TO_NP from .onnx_utils import gen_random_inputs, save_inputs_to_files, onnx_io, write_inputs_to_memory_bin, _ONNX_TO_NP
from .raptor import compile_with_raptor from .raptor import compile_with_raptor
from .pimsim_nn import export_raptor_latency_artifact, parse_pimsim_nn_metrics from .pimsim_nn import export_raptor_latency_artifact, parse_pimsim_nn_metrics, read_raptor_instruction_count
from .subprocess_utils import run_command_with_reporter from .subprocess_utils import run_command_with_reporter
STAGE_TITLES = ( STAGE_TITLES = (
@@ -80,6 +82,35 @@ class ValidationResult:
pimsim_power_mw: float | None = None pimsim_power_mw: float | None = None
pimsim_energy_pj: float | None = None pimsim_energy_pj: float | None = None
pimsim_status: str = PIMSIM_SKIPPED pimsim_status: str = PIMSIM_SKIPPED
compile_time_s: float | None = None
host_memory_bytes: int | None = None
cores_memory_bytes: int | None = None
used_core_count: int | None = None
used_crossbar_count: int | None = None
_MEMORY_UNITS = {"B": 1, "KB": 1 << 10, "MB": 1 << 20, "GB": 1 << 30}
def collect_pim_resource_metrics(pim_dir):
pim_dir = Path(pim_dir)
report_path = pim_dir.parent / "reports" / "memory_report.txt"
report = report_path.read_text(encoding="utf-8") if report_path.exists() else ""
def memory_bytes(label):
match = re.search(rf"^\s*{re.escape(label)}:\s+([0-9.]+)\s+(B|KB|MB|GB)$", report, re.MULTILINE)
return round(float(match.group(1)) * _MEMORY_UNITS[match.group(2)]) if match else None
with open(pim_dir / "config.json", encoding="utf-8") as f:
config = json.load(f)
used_cores = sum(read_raptor_instruction_count(path) > 0 for path in pim_dir.glob("core_*.pim"))
used_crossbars = sum(sum(groups) for groups in config.get("array_group_map", {}).values())
return {
"host_memory_bytes": memory_bytes("Host memory"),
"cores_memory_bytes": memory_bytes("Local memory after reuse"),
"used_core_count": used_cores,
"used_crossbar_count": used_crossbars,
}
class ProgressReporter: class ProgressReporter:
@@ -416,8 +447,6 @@ def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
pimsim_nn_build_dir = Path(pimsim_nn_build_dir).resolve() pimsim_nn_build_dir = Path(pimsim_nn_build_dir).resolve()
pimsim_config_path = Path(pimsim_config_path).resolve() pimsim_config_path = Path(pimsim_config_path).resolve()
compile_extra_args = list(raptor_extra_args or []) compile_extra_args = list(raptor_extra_args or [])
if pimsim_enabled and "--pim-emit-json" not in compile_extra_args:
compile_extra_args.append("--pim-emit-json")
owns_reporter = reporter is None owns_reporter = reporter is None
reporter = reporter or ProgressReporter(model_total, stages_per_model=len(MODE_STAGE_TITLES[mode]), verbose=verbose) reporter = reporter or ProgressReporter(model_total, stages_per_model=len(MODE_STAGE_TITLES[mode]), verbose=verbose)
@@ -435,6 +464,8 @@ def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
f" {Style.BRIGHT}Validating {network_onnx_path.name}{Style.RESET_ALL}") f" {Style.BRIGHT}Validating {network_onnx_path.name}{Style.RESET_ALL}")
failed_with_exception = False failed_with_exception = False
pim_pass_timings = {} pim_pass_timings = {}
compile_time_s = None
resource_metrics = {}
try: try:
stem = network_onnx_path.stem stem = network_onnx_path.stem
@@ -443,6 +474,19 @@ def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
runner_path = runner_build_dir / "runner" runner_path = runner_build_dir / "runner"
pim_output_base = raptor_dir / stem pim_output_base = raptor_dir / stem
def compile_pim():
nonlocal compile_time_s, resource_metrics
started = time.perf_counter()
timings = compile_with_raptor(
network_onnx_path, raptor_path, pim_output_base, crossbar_size,
crossbar_count, core_count=core_count,
raptor_extra_args=compile_extra_args, cwd=raptor_dir,
verbose=verbose, reporter=reporter,
timeout_sec=command_timeout_seconds)
compile_time_s = time.perf_counter() - started
resource_metrics = collect_pim_resource_metrics(raptor_dir / "pim")
return timings
if mode != MODE_RUN_ONLY: if mode != MODE_RUN_ONLY:
print_stage(reporter, model_index, model_total, network_onnx_path.name, "Compile ONNX") print_stage(reporter, model_index, model_total, network_onnx_path.name, "Compile ONNX")
network_so_path, network_mlir_path = compile_onnx_network( network_so_path, network_mlir_path = compile_onnx_network(
@@ -468,16 +512,14 @@ def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
if mode == MODE_COMPILE_ONLY: if mode == MODE_COMPILE_ONLY:
print_stage(reporter, model_index, model_total, network_onnx_path.name, "Compile PIM") print_stage(reporter, model_index, model_total, network_onnx_path.name, "Compile PIM")
pim_pass_timings = compile_with_raptor( pim_pass_timings = compile_pim()
network_onnx_path, raptor_path, pim_output_base, crossbar_size, crossbar_count,
core_count=core_count,
raptor_extra_args=compile_extra_args,
cwd=raptor_dir, verbose=verbose, reporter=reporter, timeout_sec=command_timeout_seconds)
print_info(reporter, f"PIM artifacts saved to {raptor_dir / 'pim'}") print_info(reporter, f"PIM artifacts saved to {raptor_dir / 'pim'}")
reporter.advance() reporter.advance()
reporter.record_result(True) reporter.record_result(True)
reporter.log(Style.BRIGHT + f"Result: {Fore.GREEN}PASS{Style.RESET_ALL}" + Style.RESET_ALL) reporter.log(Style.BRIGHT + f"Result: {Fore.GREEN}PASS{Style.RESET_ALL}" + Style.RESET_ALL)
return ValidationResult(passed=True, pim_pass_timings=pim_pass_timings) return ValidationResult(
passed=True, pim_pass_timings=pim_pass_timings,
compile_time_s=compile_time_s, **resource_metrics)
if mode == MODE_RUN_ONLY: if mode == MODE_RUN_ONLY:
required_paths = [ required_paths = [
@@ -489,6 +531,7 @@ def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
missing = [f"{description} at {path}" for path, description in required_paths if not path.exists()] missing = [f"{description} at {path}" for path, description in required_paths if not path.exists()]
if missing: if missing:
raise FileNotFoundError("run-only mode requires existing artifacts:\n " + "\n ".join(missing)) raise FileNotFoundError("run-only mode requires existing artifacts:\n " + "\n ".join(missing))
resource_metrics = collect_pim_resource_metrics(raptor_dir / "pim")
print_stage(reporter, model_index, model_total, network_onnx_path.name, "Generate Inputs") print_stage(reporter, model_index, model_total, network_onnx_path.name, "Generate Inputs")
inputs_descriptor, outputs_descriptor = onnx_io(network_onnx_path) inputs_descriptor, outputs_descriptor = onnx_io(network_onnx_path)
@@ -508,11 +551,7 @@ def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
if mode != MODE_RUN_ONLY: if mode != MODE_RUN_ONLY:
print_stage(reporter, model_index, model_total, network_onnx_path.name, "Compile PIM") print_stage(reporter, model_index, model_total, network_onnx_path.name, "Compile PIM")
pim_pass_timings = compile_with_raptor( pim_pass_timings = compile_pim()
network_onnx_path, raptor_path, pim_output_base, crossbar_size, crossbar_count,
core_count=core_count,
raptor_extra_args=compile_extra_args,
cwd=raptor_dir, verbose=verbose, reporter=reporter, timeout_sec=command_timeout_seconds)
print_info(reporter, f"PIM artifacts saved to {raptor_dir / 'pim'}") print_info(reporter, f"PIM artifacts saved to {raptor_dir / 'pim'}")
reporter.advance() reporter.advance()
@@ -587,6 +626,8 @@ def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
pimsim_power_mw=pimsim_power_mw, pimsim_power_mw=pimsim_power_mw,
pimsim_energy_pj=pimsim_energy_pj, pimsim_energy_pj=pimsim_energy_pj,
pimsim_status=pimsim_status, pimsim_status=pimsim_status,
compile_time_s=compile_time_s,
**resource_metrics,
) )
except Exception: except Exception:
failed_with_exception = True failed_with_exception = True
+45 -26
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
import csv
import os import os
import signal import signal
import subprocess import subprocess
@@ -140,6 +141,17 @@ def format_pimsim_metric(result, value, unit):
return result.pimsim_status return result.pimsim_status
def format_memory(byte_count):
if byte_count is None:
return "-"
return f"{byte_count / (1 << 20):.2f} MiB"
def operation_label(relative_path):
path = Path(relative_path)
return str(path.parent) if path.parent != Path(".") else path.stem
def main(): def main():
script_dir = Path(__file__).parent.resolve() script_dir = Path(__file__).parent.resolve()
pimcomp_configs_dir = script_dir / "pimsim_configs" / "pimcomp" pimcomp_configs_dir = script_dir / "pimsim_configs" / "pimcomp"
@@ -216,6 +228,7 @@ def main():
removed_count = 0 removed_count = 0
for onnx_path in onnx_files: for onnx_path in onnx_files:
removed_count += len(clean_workspace_artifacts(onnx_path.parent, onnx_path.stem)) removed_count += len(clean_workspace_artifacts(onnx_path.parent, onnx_path.stem))
(operations_dir / "validation_results.csv").unlink(missing_ok=True)
print(Style.BRIGHT + f"Removed {removed_count} generated artifact path(s)." + Style.RESET_ALL) print(Style.BRIGHT + f"Removed {removed_count} generated artifact path(s)." + Style.RESET_ALL)
sys.exit(0) sys.exit(0)
@@ -343,38 +356,44 @@ def main():
# Summary # Summary
n_passed = sum(1 for result in results.values() if result.passed) n_passed = sum(1 for result in results.values() if result.passed)
n_total = len(results) n_total = len(results)
status_width = len("Result") headers = ("Operation", "Result", "Compile", "Host mem", "Cores mem",
path_width = max(len("Operation"), *(len(rel) for rel in results)) "Cores", "Xbars", "Latency", "Power", "Energy")
formatted_metrics = { rows = []
rel: ( for rel, result in results.items():
rows.append((
operation_label(rel), "PASS" if result.passed else "FAIL",
f"{result.compile_time_s:.3f} s" if result.compile_time_s is not None else "-",
format_memory(result.host_memory_bytes),
format_memory(result.cores_memory_bytes),
str(result.used_core_count) if result.used_core_count is not None else "-",
str(result.used_crossbar_count) if result.used_crossbar_count is not None else "-",
format_pimsim_metric(result, result.pimsim_latency_ms, "ms"), format_pimsim_metric(result, result.pimsim_latency_ms, "ms"),
format_pimsim_metric(result, result.pimsim_power_mw, "mW"), format_pimsim_metric(result, result.pimsim_power_mw, "mW"),
format_pimsim_metric(result, result.pimsim_energy_pj, "pJ"), format_pimsim_metric(result, result.pimsim_energy_pj, "pJ"),
) ))
for rel, result in results.items() widths = [max(len(header), *(len(row[index]) for row in rows))
} for index, header in enumerate(headers)]
latency_width = max(len("Latency"), *(len(metrics[0]) for metrics in formatted_metrics.values())) separator = "+-" + "-+-".join("-" * width for width in widths) + "-+"
power_width = max(len("Power"), *(len(metrics[1]) for metrics in formatted_metrics.values()))
energy_width = max(len("Energy"), *(len(metrics[2]) for metrics in formatted_metrics.values())) def table_line(row):
separator = ( return "| " + " | ".join(
f"+-{'-' * path_width}-+-{'-' * status_width}-+-{'-' * latency_width}" value.ljust(widths[index]) if index < 2 else value.rjust(widths[index])
f"-+-{'-' * power_width}-+-{'-' * energy_width}-+") for index, value in enumerate(row)) + " |"
print(separator) print(separator)
print( print(table_line(headers))
f"| {'Operation'.ljust(path_width)} | {'Result'.ljust(status_width)} | "
f"{'Latency'.rjust(latency_width)} | {'Power'.rjust(power_width)} | "
f"{'Energy'.rjust(energy_width)} |"
)
print(separator) print(separator)
for rel, result in results.items(): for row in rows:
plain_status = "PASS" if result.passed else "FAIL" line = table_line(row)
status = Fore.GREEN + plain_status.ljust(status_width) + Style.RESET_ALL if result.passed else \ color = Fore.GREEN if row[1] == "PASS" else Fore.RED
Fore.RED + plain_status.ljust(status_width) + Style.RESET_ALL line = line.replace(row[1].ljust(widths[1]),
latency, power, energy = formatted_metrics[rel] color + row[1].ljust(widths[1]) + Style.RESET_ALL, 1)
print( print(line)
f"| {rel.ljust(path_width)} | {status} | {latency.rjust(latency_width)} | "
f"{power.rjust(power_width)} | {energy.rjust(energy_width)} |")
print(separator) print(separator)
with (operations_dir / "validation_results.csv").open(
"w", encoding="utf-8", newline=""
) as results_file:
csv.writer(results_file).writerows((headers, *rows))
print("\n" + Style.BRIGHT + Fore.CYAN + "Summary" + Style.RESET_ALL) print("\n" + Style.BRIGHT + Fore.CYAN + "Summary" + Style.RESET_ALL)
print(Style.BRIGHT + f"Passed: {n_passed}" + Style.RESET_ALL) print(Style.BRIGHT + f"Passed: {n_passed}" + Style.RESET_ALL)
print(Style.BRIGHT + f"Failed: {n_total - n_passed}" + Style.RESET_ALL) print(Style.BRIGHT + f"Failed: {n_total - n_passed}" + Style.RESET_ALL)