Compare commits

6 Commits

Author SHA1 Message Date
ilgeco e2cefd3127 Update Submodule
Validate Operations / validate-operations (push) Has been cancelled
2026-08-06 14:40:10 +02:00
ilgeco 7a3a808ae8 Some tool drawio and sequence diagram 2026-08-06 14:34:22 +02:00
ilgeco 0712c5ba29 New Operations to test 2026-08-06 14:33:42 +02:00
ilgeco aeedf2f566 Test Spatial Scheduling 2026-08-06 14:32:57 +02:00
ilgeco a39fdba366 Raptor sync wait 2026-08-06 14:32:46 +02:00
ilgeco a963009855 Rust wait and sync 2026-08-06 14:32:11 +02:00
76 changed files with 5184 additions and 354 deletions
@@ -326,9 +326,13 @@ fn append_record(
inst_builder.make_inst(recv, inst_data_builder.build()); inst_builder.make_inst(recv, inst_data_builder.build());
} }
31 => { 31 => {
inst_data_builder.set_offset_select_value(generic1, generic2);
inst_builder.make_inst(wait, inst_data_builder.build()); inst_builder.make_inst(wait, inst_data_builder.build());
} }
32 => { 32 => {
inst_data_builder
.set_imm_core(r2_or_imm + 1)
.set_offset_select_value(generic1, 0);
inst_builder.make_inst(sync, inst_data_builder.build()); inst_builder.make_inst(sync, inst_data_builder.build());
} }
_ => bail!("unsupported PIM binary opcode {opcode}"), _ => bail!("unsupported PIM binary opcode {opcode}"),
@@ -601,7 +601,11 @@ fn json_to_wait(
inst_data_builder: &mut InstructionDataBuilder, inst_data_builder: &mut InstructionDataBuilder,
json: &Value, json: &Value,
) -> Result<()> { ) -> Result<()> {
todo!("Not present in the compiler"); inst_data_builder.set_offset_select_value(
json_i64!(json, "event_register") as i32,
json_i64!(json, "wait_value") as i32,
);
inst_builder.make_inst(wait, inst_data_builder.build());
Ok(()) Ok(())
} }
@@ -610,7 +614,10 @@ fn json_to_sync(
inst_data_builder: &mut InstructionDataBuilder, inst_data_builder: &mut InstructionDataBuilder,
json: &Value, json: &Value,
) -> Result<()> { ) -> Result<()> {
todo!("Not present in the compiler"); inst_data_builder
.set_imm_core(json_i64!(json, "core") as i32 + 1)
.set_offset_select_value(json_i64!(json, "event_register") as i32, 0);
inst_builder.make_inst(sync, inst_data_builder.build());
Ok(()) Ok(())
} }
@@ -93,6 +93,8 @@ struct DeadlockInfo {
states: String, states: String,
} }
type SyncEvents = Vec<[i32; 32]>;
fn print_status(core_instructions: &[CoreInstructions]) { fn print_status(core_instructions: &[CoreInstructions]) {
let mut tot_instructions = 0; let mut tot_instructions = 0;
let mut progress = 0; let mut progress = 0;
@@ -135,6 +137,7 @@ impl<'a> Executable<'a> {
} = self; } = self;
let mut cpu_progressed = 0; let mut cpu_progressed = 0;
let max_core = cpu.num_core(); let max_core = cpu.num_core();
let mut sync_events: SyncEvents = vec![[0; 32]; max_core];
let mut cpu_index = 0; let mut cpu_index = 0;
let mut now = SystemTime::now(); let mut now = SystemTime::now();
@@ -169,7 +172,9 @@ impl<'a> Executable<'a> {
now = SystemTime::now(); now = SystemTime::now();
} }
} }
handle_wait_sync(cpu, cores_instructions, core_result); if handle_wait_sync(cores_instructions, &mut sync_events, core_result) {
cpu_progressed = 0;
}
match handle_send_recv(cpu, cores_instructions, send_recv, core_result) { match handle_send_recv(cpu, cores_instructions, send_recv, core_result) {
(true, other_cpu_index) => { (true, other_cpu_index) => {
cpu_progressed = 0; cpu_progressed = 0;
@@ -349,12 +354,31 @@ fn detect_deadlock(cores_instructions: &[CoreInstructions]) -> Option<DeadlockIn
None None
} }
fn handle_wait_sync<'a, 'b, 'c>( fn handle_wait_sync(
cpu: &'b mut CPU<'a>, core_instructions: &mut [CoreInstructions],
core_instructions: &'c mut [CoreInstructions], events: &mut SyncEvents,
core_result: InstructionStatus, core_result: InstructionStatus,
) where ) -> bool {
'a: 'b, match core_result {
'a: 'c, InstructionStatus::Sync(data) => {
{ let (source, target) = data.get_core_immcore();
let register = data.offset_select() as usize;
events[target as usize][register] += 1;
core_instructions[source as usize].program_counter += 1;
true
}
InstructionStatus::Waiting(data) => {
let core = data.core_indx() as usize;
let register = data.offset_select() as usize;
let value = data.offset_value();
if events[core][register] >= value {
events[core][register] -= value;
core_instructions[core].program_counter += 1;
true
} else {
false
}
}
_ => false,
}
} }
@@ -134,7 +134,7 @@ where
send_recv.sending[sender] = None; send_recv.sending[sender] = None;
send_recv.receiving[receiver] = None; send_recv.receiving[receiver] = None;
} }
(transfered, receiver) (transfered, if transfered { receiver } else { 0 })
} }
InstructionStatus::Reciving(instruction_data) => { InstructionStatus::Reciving(instruction_data) => {
let (core_idx, imm_core) = instruction_data.get_core_immcore(); let (core_idx, imm_core) = instruction_data.get_core_immcore();
@@ -163,7 +163,7 @@ where
send_recv.sending[sender] = None; send_recv.sending[sender] = None;
send_recv.receiving[receiver] = None; send_recv.receiving[receiver] = None;
} }
(transfered, sender) (transfered, if transfered { sender } else { 0 })
} }
_ => (false, 0), _ => (false, 0),
} }
@@ -295,3 +295,68 @@ fn multiple_send_recv_test() {
"send_recv failed to store" "send_recv failed to store"
); );
} }
#[test]
fn sync_wait_tokens_test() {
let cpu = common::empty_cpu(2);
let mut cores = CoreInstructionsBuilder::new(2);
let mut instructions = InstructionsBuilder::new();
let mut data = InstructionDataBuilder::new();
data.set_core_indx(1).fix_core_indx();
for _ in 0..2 {
instructions.make_inst(
sync,
data.set_imm_core(2).set_offset_select_value(0, 0).build(),
);
}
cores.set_core(1, instructions.build());
data.set_core_indx(2).fix_core_indx();
for _ in 0..2 {
instructions.make_inst(wait, data.set_offset_select_value(0, 1).build());
}
cores.set_core(2, instructions.build());
Executable::new(cpu, cores.build()).execute().unwrap();
}
#[test]
fn blocked_transfers_do_not_starve_sync_producer() {
let cpu = common::empty_cpu(4);
let mut cores = CoreInstructionsBuilder::new(4);
let mut instructions = InstructionsBuilder::new();
let mut data = InstructionDataBuilder::new();
data.set_core_indx(1).fix_core_indx();
instructions.make_inst(sldi, data.set_rdimm(1, 0).build());
instructions.make_inst(recv, data.set_rd(1).set_imm_core(2).set_imm_len(1).build());
instructions.make_inst(send, data.set_r1(1).set_imm_core(3).set_imm_len(1).build());
cores.set_core(1, instructions.build());
let mut instructions = InstructionsBuilder::new();
let mut data = InstructionDataBuilder::new();
data.set_core_indx(2).fix_core_indx();
instructions.make_inst(sldi, data.set_rdimm(1, 0).build());
instructions.make_inst(wait, data.set_offset_select_value(0, 1).build());
instructions.make_inst(send, data.set_r1(1).set_imm_core(1).set_imm_len(1).build());
cores.set_core(2, instructions.build());
let mut instructions = InstructionsBuilder::new();
let mut data = InstructionDataBuilder::new();
data.set_core_indx(3).fix_core_indx();
instructions.make_inst(sldi, data.set_rdimm(1, 0).build());
instructions.make_inst(recv, data.set_rd(1).set_imm_core(1).set_imm_len(1).build());
cores.set_core(3, instructions.build());
let mut instructions = InstructionsBuilder::new();
let mut data = InstructionDataBuilder::new();
data.set_core_indx(4).fix_core_indx();
instructions.make_inst(
sync,
data.set_imm_core(2).set_offset_select_value(0, 0).build(),
);
cores.set_core(4, instructions.build());
Executable::new(cpu, cores.build()).execute().unwrap();
}
+3
View File
@@ -32,6 +32,9 @@ inline constexpr llvm::StringLiteral kCoreIdAttrName = "coreId";
inline constexpr llvm::StringLiteral kCoreIdsAttrName = "coreIds"; inline constexpr llvm::StringLiteral kCoreIdsAttrName = "coreIds";
inline constexpr llvm::StringLiteral kLocalMemoryAddressAttrName = "pim.local_memory_address"; inline constexpr llvm::StringLiteral kLocalMemoryAddressAttrName = "pim.local_memory_address";
inline constexpr llvm::StringLiteral kLocalMemorySizeAttrName = "pim.local_memory_size"; inline constexpr llvm::StringLiteral kLocalMemorySizeAttrName = "pim.local_memory_size";
inline constexpr llvm::StringLiteral kPipelineHostBufferBytesAttrName = "pim.pipeline_host_buffer_bytes";
inline constexpr llvm::StringLiteral kPipelineHostBufferName = "pim_pipeline_channels";
inline constexpr size_t kPimEventRegisterCount = 32;
inline constexpr std::array<llvm::StringLiteral, 4> kRemovedLocalMemoryPlanAttrNames = { inline constexpr std::array<llvm::StringLiteral, 4> kRemovedLocalMemoryPlanAttrNames = {
"pim.local_memory_slot", "pim.local_memory_slot",
"pim.local_memory_slot_size", "pim.local_memory_slot_size",
+2 -2
View File
@@ -162,8 +162,8 @@ inline constexpr std::array<InstructionJsonFormat, kOpcodeCount> kInstructionJso
{true, true, true, "", "", "", "len" }, // lmv {true, true, true, "", "", "", "len" }, // lmv
{true, false, true, "core", "", "", "size"}, // send {true, false, true, "core", "", "", "size"}, // send
{true, false, true, "core", "", "", "size"}, // recv {true, false, true, "core", "", "", "size"}, // recv
{false, false, false, "", "", "", "" }, // wait {false, false, false, "", "event_register", "wait_value", ""}, // wait
{false, false, false, "", "", "", "" }, // sync {false, false, false, "core", "event_register", "", ""}, // sync
}}; }};
static_assert(kInstructionJsonFormats.size() == kOpcodeCount); static_assert(kInstructionJsonFormats.size() == kOpcodeCount);
+30
View File
@@ -692,6 +692,34 @@ void PimCodeGen::codeGenSendOp(pim::PimSendOp sendOp, const StaticValueKnowledge
pim_binary::Opcode::send, addressOf(sendOp.getInput(), knowledge), *targetCoreId, sendOp.getSize()); pim_binary::Opcode::send, addressOf(sendOp.getInput(), knowledge), *targetCoreId, sendOp.getSize());
} }
void PimCodeGen::codeGenWaitOp(
pim::PimWaitOp waitOp, const StaticValueKnowledge& knowledge) const {
auto eventRegister = indexOf(waitOp.getEventRegister(), knowledge);
assert(succeeded(eventRegister)
&& "pim.wait event register must be statically resolvable during codegen");
pim_binary::InstructionRecord instruction;
instruction.opcode = pim_binary::Opcode::wait;
instruction.generic1 = pim::checkedI32OrCrash(
*eventRegister, "wait event register");
instruction.generic2 = waitOp.getWaitValue();
emitInstruction(instruction);
}
void PimCodeGen::codeGenSyncOp(
pim::PimSyncOp syncOp, const StaticValueKnowledge& knowledge) const {
auto targetCoreId = indexOf(syncOp.getTargetCoreId(), knowledge);
auto eventRegister = indexOf(syncOp.getEventRegister(), knowledge);
assert(succeeded(targetCoreId) && succeeded(eventRegister)
&& "pim.sync operands must be statically resolvable during codegen");
pim_binary::InstructionRecord instruction;
instruction.opcode = pim_binary::Opcode::sync;
instruction.r2OrImm = pim::checkedI32OrCrash(
*targetCoreId, "sync target core id");
instruction.generic1 = pim::checkedI32OrCrash(
*eventRegister, "sync event register");
emitInstruction(instruction);
}
void PimCodeGen::codeGenConcatOp(pim::PimConcatOp concatOp, const StaticValueKnowledge& knowledge) const { void PimCodeGen::codeGenConcatOp(pim::PimConcatOp concatOp, const StaticValueKnowledge& knowledge) const {
auto outputType = cast<ShapedType>(concatOp.getOutputBuffer().getType()); auto outputType = cast<ShapedType>(concatOp.getOutputBuffer().getType());
assert(outputType.hasStaticShape() && "concat codegen requires static output shape"); assert(outputType.hasStaticShape() && "concat codegen requires static output shape");
@@ -991,6 +1019,8 @@ static LogicalResult executeCompiledCorePlan(
case CompiledCoreOpKind::VMV: coreCodeGen.codeGenVMVOp(cast<pim::PimVMVOp>(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::Wait: coreCodeGen.codeGenWaitOp(cast<pim::PimWaitOp>(node.op), knowledge); break;
case CompiledCoreOpKind::Sync: coreCodeGen.codeGenSyncOp(cast<pim::PimSyncOp>(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;
case CompiledCoreOpKind::Vmm: case CompiledCoreOpKind::Vmm:
if (auto weightSlot = resolveWeightSlot(cast<pim::PimVMMOp>(node.op), knowledge); succeeded(weightSlot)) if (auto weightSlot = resolveWeightSlot(cast<pim::PimVMMOp>(node.op), knowledge); succeeded(weightSlot))
+2
View File
@@ -217,6 +217,8 @@ public:
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;
void codeGenWaitOp(pim::PimWaitOp waitOp, const StaticValueKnowledge& knowledge) const;
void codeGenSyncOp(pim::PimSyncOp syncOp, const StaticValueKnowledge& knowledge) const;
void codeGenConcatOp(pim::PimConcatOp concatOp, const StaticValueKnowledge& knowledge) const; void codeGenConcatOp(pim::PimConcatOp concatOp, const StaticValueKnowledge& knowledge) const;
template <typename MVMTy> template <typename MVMTy>
+18
View File
@@ -2,6 +2,8 @@
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp" #include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
#include <limits>
#define DEBUG_TYPE "PimCompilerOptions" #define DEBUG_TYPE "PimCompilerOptions"
namespace onnx_mlir { namespace onnx_mlir {
@@ -110,6 +112,12 @@ llvm::cl::opt<size_t>
llvm::cl::opt<size_t> llvm::cl::opt<size_t>
crossbarCountInCore("crossbar-count", llvm::cl::desc("Number of crossbars in each core"), llvm::cl::init(64)); crossbarCountInCore("crossbar-count", llvm::cl::desc("Number of crossbars in each core"), llvm::cl::init(64));
llvm::cl::opt<size_t> pipelineStages(
"pipeline",
llvm::cl::desc("Number of throughput pipeline stages (1 preserves latency scheduling)"),
llvm::cl::init(1),
llvm::cl::cat(OnnxMlirOptions));
llvm::cl::opt<long> coresCount("core-count", llvm::cl::opt<long> coresCount("core-count",
llvm::cl::desc("Number of cores in the chip. Required for PIM compilation."), llvm::cl::desc("Number of cores in the chip. Required for PIM compilation."),
llvm::cl::init(-1)); llvm::cl::init(-1));
@@ -129,4 +137,14 @@ void verifyExplicitPimCoreCount() {
llvm::report_fatal_error("PIM compilation requires --core-count to be a positive integer"); llvm::report_fatal_error("PIM compilation requires --core-count to be a positive integer");
} }
void verifyPimPipelineStages() {
if (pipelineStages.getValue() == 0)
llvm::report_fatal_error("PIM compilation requires --pipeline to be positive");
if (static_cast<size_t>(coresCount.getValue()) % pipelineStages.getValue() != 0)
llvm::report_fatal_error("PIM compilation requires --core-count to be divisible by --pipeline");
if (crossbarCountInCore.getValue()
> std::numeric_limits<size_t>::max() / pipelineStages.getValue())
llvm::report_fatal_error("PIM compilation --crossbar-count * --pipeline overflows");
}
} // namespace onnx_mlir } // namespace onnx_mlir
+2
View File
@@ -62,6 +62,7 @@ extern llvm::cl::opt<bool> pimVerifyBufferizationCopyFreedom;
extern llvm::cl::opt<size_t> crossbarSize; extern llvm::cl::opt<size_t> crossbarSize;
extern llvm::cl::opt<size_t> crossbarCountInCore; extern llvm::cl::opt<size_t> crossbarCountInCore;
extern llvm::cl::opt<size_t> pipelineStages;
extern llvm::cl::opt<long> coresCount; extern llvm::cl::opt<long> coresCount;
extern llvm::cl::opt<std::string> pimTargetConfig; extern llvm::cl::opt<std::string> pimTargetConfig;
extern llvm::cl::opt<uint64_t> pimConvIm2colMaxElements; extern llvm::cl::opt<uint64_t> pimConvIm2colMaxElements;
@@ -69,5 +70,6 @@ extern llvm::cl::opt<uint64_t> pimConvStreamChunkPositions;
bool hasExplicitPimCoreCount(); bool hasExplicitPimCoreCount();
void verifyExplicitPimCoreCount(); void verifyExplicitPimCoreCount();
void verifyPimPipelineStages();
} // namespace onnx_mlir } // namespace onnx_mlir
+2 -1
View File
@@ -330,6 +330,7 @@ void addPassesPim(OwningOpRef<ModuleOp>& module,
EmissionTargetType& emissionTarget, EmissionTargetType& emissionTarget,
std::string outputNameNoExt) { std::string outputNameNoExt) {
verifyExplicitPimCoreCount(); verifyExplicitPimCoreCount();
verifyPimPipelineStages();
spatial::SchedulingTarget schedulingTarget = getPimSchedulingTarget(); spatial::SchedulingTarget schedulingTarget = getPimSchedulingTarget();
spatial::SpatialTargetResources targetResources = getPimSpatialTargetResources(schedulingTarget); spatial::SpatialTargetResources targetResources = getPimSpatialTargetResources(schedulingTarget);
@@ -354,7 +355,7 @@ void addPassesPim(OwningOpRef<ModuleOp>& module,
pm.addPass(createTrivialGraphComputeMergePass( pm.addPass(createTrivialGraphComputeMergePass(
schedulingTarget.residentWeightCapacity, exportStage)); schedulingTarget.residentWeightCapacity, exportStage));
pm.addPass(spatial::createScheduleAndRealizeSpatialPass( pm.addPass(spatial::createScheduleAndRealizeSpatialPass(
schedulingTarget, exportStage)); schedulingTarget, exportStage, pipelineStages.getValue()));
pm.addPass(createMessagePass("Onnx lowered to Spatial")); pm.addPass(createMessagePass("Onnx lowered to Spatial"));
} }
+2
View File
@@ -17,6 +17,8 @@ static FailureOr<CompiledCoreOpKind> classifyCompiledCoreOpKind(Operation& op) {
if (isa<pim::PimVMVOp>(op)) return CompiledCoreOpKind::VMV; 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::PimWaitOp>(op)) return CompiledCoreOpKind::Wait;
if (isa<pim::PimSyncOp>(op)) return CompiledCoreOpKind::Sync;
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::PimVVAddOp>(op)) return CompiledCoreOpKind::VVAdd; if (isa<pim::PimVVAddOp>(op)) return CompiledCoreOpKind::VVAdd;
+2
View File
@@ -17,6 +17,8 @@ enum class CompiledCoreOpKind : uint8_t {
VMV, VMV,
Receive, Receive,
Send, Send,
Wait,
Sync,
Concat, Concat,
Vmm, Vmm,
VVAdd, VVAdd,
@@ -0,0 +1,39 @@
#include "ContractionMaterialization.hpp"
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
#include "MatrixProductLowering.hpp"
namespace onnx_mlir {
mlir::Value materializePaddedContractionInput(
mlir::Value input,
mlir::RankedTensorType paddedType,
mlir::PatternRewriter& rewriter,
mlir::Location loc) {
return createPaddedInputCompute(input, paddedType, rewriter, loc);
}
mlir::FailureOr<mlir::Value> materializeTransposedContractionConstant(
mlir::Value input,
mlir::RankedTensorType resultType,
llvm::ArrayRef<int64_t> permutation,
mlir::PatternRewriter& rewriter,
mlir::Location loc) {
auto denseAttr = getHostConstDenseElementsAttr(input);
auto inputType = denseAttr ? mlir::dyn_cast<mlir::RankedTensorType>(denseAttr.getType()) : nullptr;
if (!inputType || !inputType.hasStaticShape() || !resultType || !resultType.hasStaticShape()
|| inputType.getRank() != resultType.getRank())
return mlir::failure();
auto transposedAttr = transposeDenseElementsAttr(denseAttr, permutation);
if (mlir::failed(transposedAttr) || transposedAttr->getType() != resultType)
return mlir::failure();
return getOrCreateConstant(rewriter,
rewriter.getInsertionBlock()->getParentOp(),
*transposedAttr,
resultType);
}
} // namespace onnx_mlir
@@ -0,0 +1,23 @@
#pragma once
#include "llvm/ADT/ArrayRef.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/PatternMatch.h"
namespace onnx_mlir {
mlir::Value materializePaddedContractionInput(
mlir::Value input,
mlir::RankedTensorType paddedType,
mlir::PatternRewriter& rewriter,
mlir::Location loc);
mlir::FailureOr<mlir::Value> materializeTransposedContractionConstant(
mlir::Value input,
mlir::RankedTensorType resultType,
llvm::ArrayRef<int64_t> permutation,
mlir::PatternRewriter& rewriter,
mlir::Location loc);
} // namespace onnx_mlir
@@ -0,0 +1,902 @@
#include "mlir/Dialect/Affine/IR/AffineOps.h"
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/Linalg/IR/Linalg.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
#include "mlir/Transforms/DialectConversion.h"
#include "Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp"
#include "mlir/Transforms/Passes.h"
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
#include "src/Accelerators/PIM/Common/Support/DebugDump.hpp"
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/BiasAddUtils.hpp"
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/MatrixProductLowering.hpp"
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/RowStripLayoutUtils.hpp"
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/PlanLowering.hpp"
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
#include "src/Accelerators/PIM/Dialect/Spatial/Transforms/MergeComputeNodes/SpatialDataflowCsvExporter.hpp"
#include "src/Accelerators/PIM/Pass/PIMPasses.h"
using namespace mlir;
namespace onnx_mlir {
namespace {
static FailureOr<RowStripPhysicalValue> getRowStripValue(Value value) {
return getRowStripPhysicalValue(value);
}
static FailureOr<Value> publishRowStripValue(Operation* planOp,
Value storage,
PatternRewriter& rewriter) {
auto logicalType = dyn_cast<RankedTensorType>(planOp->getResult(0).getType());
if (!logicalType)
return planOp->emitOpError("requires ranked logical output type"), failure();
FailureOr<RowStripPhysicalValue> value = describeRowStripPhysicalValue(storage, logicalType);
if (failed(value))
return planOp->emitOpError("lowering produced invalid row-strip physical storage"), failure();
FailureOr<Value> blueprint = createRowStripStorageBlueprint(
storage, logicalType, rewriter, planOp->getLoc());
if (failed(blueprint))
return planOp->emitOpError("failed to create row-strip storage Blueprint"), failure();
rewriter.replaceOp(planOp, *blueprint);
return *blueprint;
}
static bool isRowStripSelected(Operation* op) {
auto selected = spatial::getSelectedPhysicalLayout(op);
return selected && *selected == spatial::PhysicalLayout::NHWCRowStrip;
}
static bool isDenseSelected(Operation* op) {
auto selected = spatial::getSelectedPhysicalLayout(op);
return selected && *selected == spatial::PhysicalLayout::DenseNCHW;
}
static spatial::PhysicalLayout getKnownPhysicalLayout(Value value) {
if (auto materialize = value.getDefiningOp<spatial::SpatMaterializeLayoutOp>())
return materialize.getTargetPhysicalLayout();
if (auto blueprint = value.getDefiningOp<spatial::SpatBlueprintOp>())
return blueprint.getPhysicalLayout();
if (Operation* producer = value.getDefiningOp()) {
if (auto selected = spatial::getSelectedPhysicalLayout(producer))
return *selected;
}
return spatial::PhysicalLayout::DenseNCHW;
}
static LogicalResult verifySelectedLayouts(
func::FuncOp funcOp, const spatial::SpatialTargetInfo& target) {
LogicalResult result = success();
funcOp.walk([&](Operation* op) {
auto capability = dyn_cast<spatial::SpatialLayoutCapabilityInterface>(op);
if (!capability)
return;
auto selected = spatial::getSelectedPhysicalLayout(op);
if (!selected) {
op->emitOpError("requires a selected physical layout from SpatialLayoutPlanning");
result = failure();
return;
}
if (*selected != spatial::PhysicalLayout::DenseNCHW
&& *selected != spatial::PhysicalLayout::NHWCRowStrip) {
op->emitOpError("has an unsupported selected physical layout");
result = failure();
return;
}
SmallVector<spatial::PhysicalLayout> operandLayouts;
operandLayouts.reserve(op->getNumOperands());
for (Value operand : op->getOperands())
operandLayouts.push_back(getKnownPhysicalLayout(operand));
auto alternatives = capability.getLayoutAlternatives(target, operandLayouts);
if (llvm::none_of(alternatives, [&](const spatial::LayoutAlternative& alternative) {
return alternative.resultLayout == *selected
&& alternative.operandLayouts == operandLayouts;
})) {
op->emitOpError("selected physical layout is not lowerable for its explicit operand layouts");
result = failure();
}
});
return result;
}
static FailureOr<Value>
lowerRowStripRelu(const RowStripPhysicalValue& input, spatial::SpatReluPlanOp planOp, PatternRewriter& rewriter) {
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,
spatial::SpatBiasAddPlanOp planOp,
PatternRewriter& rewriter) {
return applyRowStripBiasAdd(input, planOp.getBias(), rewriter, planOp.getLoc());
}
static FailureOr<Value> lowerRowStripAdd(const RowStripPhysicalValue& lhs,
const RowStripPhysicalValue& rhs,
spatial::SpatAddPlanOp planOp,
PatternRewriter& rewriter) {
return applyRowStripAdd(lhs, rhs, rewriter, planOp.getLoc());
}
static FailureOr<Value> lowerRowStripConcat(ArrayRef<RowStripPhysicalValue> inputs,
spatial::SpatConcatPlanOp planOp,
PatternRewriter& rewriter) {
auto outputType = dyn_cast<RankedTensorType>(planOp.getOutput().getType());
if (!outputType)
return failure();
return applyRowStripConcat(inputs, outputType, rewriter, planOp.getLoc());
}
static FailureOr<Value>
materializeRowStripToDense(const RowStripPhysicalValue& rowStripValue, Location loc, PatternRewriter& rewriter) {
if (rowStripValue.logicalType.getRank() != 4 || !rowStripValue.logicalType.hasStaticShape())
return failure();
return createRowStripAssemblyBlueprint(rowStripValue, rewriter, loc);
}
static FailureOr<Value> materializeDenseToRowStrip(
Value input, RankedTensorType logicalType, Location loc, PatternRewriter& rewriter) {
if (!logicalType || !logicalType.hasStaticShape() || logicalType.getRank() != 4
|| logicalType.getDimSize(0) != 1)
return failure();
auto nhwcType = RankedTensorType::get(
{1, logicalType.getDimSize(2), logicalType.getDimSize(3), logicalType.getDimSize(1)},
logicalType.getElementType(), logicalType.getEncoding());
auto rowsType = RankedTensorType::get(
{logicalType.getDimSize(2) * logicalType.getDimSize(3), logicalType.getDimSize(1)},
logicalType.getElementType(), logicalType.getEncoding());
auto rowsCompute = createSpatCompute<1>(
rewriter, loc, rowsType, {}, input, [&](Value denseInput) {
Value nhwc = createLinalgTranspose(
denseInput, nhwcType, {0, 2, 3, 1}, rewriter, loc);
Value rows = tensor::CollapseShapeOp::create(
rewriter, loc, rowsType, nhwc,
SmallVector<ReassociationIndices> {{0, 1, 2}, {3}});
spatial::SpatYieldOp::create(rewriter, loc, rows);
});
Value rows = rowsCompute->getResult(0);
FailureOr<Value> storage = createRowStripStorageFromRows(rows, logicalType, rewriter, loc);
if (failed(storage))
return failure();
return createRowStripStorageBlueprint(*storage, logicalType, rewriter, loc);
}
static FailureOr<Value> lowerDenseBatchBiasAdd(Value input, Value bias, RankedTensorType resultType,
PatternRewriter& rewriter, Location loc) {
auto producer = input.getDefiningOp<spatial::SpatGraphComputeBatch>();
auto inputType = dyn_cast<RankedTensorType>(input.getType());
auto biasType = dyn_cast<RankedTensorType>(bias.getType());
if (!producer || !inputType || !biasType || !inputType.hasStaticShape() || !biasType.hasStaticShape()
|| !resultType.hasStaticShape() || inputType.getDimSize(0) != producer.getLaneCount()
|| biasType.getDimSize(0) != producer.getLaneCount() || resultType.getDimSize(0) != producer.getLaneCount())
return failure();
auto inputFragmentType = spatial::getGraphBatchFragmentType(inputType, producer.getLaneCount());
auto outputFragmentType = spatial::getGraphBatchFragmentType(resultType, producer.getLaneCount());
if (failed(inputFragmentType) || failed(outputFragmentType) || inputFragmentType->getRank() != biasType.getRank()
|| inputFragmentType->getDimSize(0) != 1 || inputFragmentType->getShape().drop_front() != biasType.getShape().drop_front()
|| inputFragmentType->getRank() != outputFragmentType->getRank() + 1)
return failure();
for (auto [inputDim, outputDim] : llvm::zip(inputFragmentType->getShape().drop_front(), outputFragmentType->getShape()))
if (outputDim > inputDim)
return failure();
auto batch = createSpatComputeBatch(rewriter, loc, TypeRange {resultType}, producer.getLaneCount(), {}, ValueRange {input, bias},
[&](detail::SpatComputeBatchBodyArgs args) -> LogicalResult {
FailureOr<Value> fragment = extractGraphBatchPhysicalFragment(rewriter, loc, args.inputs[0], args.lane, *inputFragmentType);
if (failed(fragment))
return failure();
MixedSliceGeometry biasSlice;
for (int64_t dim : inputFragmentType->getShape()) {
biasSlice.offsets.push_back(biasSlice.offsets.empty() ? OpFoldResult(args.lane) : rewriter.getIndexAttr(0));
biasSlice.sizes.push_back(rewriter.getIndexAttr(dim));
biasSlice.strides.push_back(rewriter.getIndexAttr(1));
}
Value biasFragment = extractMixedSliceOrIdentity(rewriter, loc, args.inputs[1], *inputFragmentType, biasSlice);
if (!biasFragment)
return failure();
Value added = spatial::SpatVAddOp::create(rewriter, loc, *inputFragmentType, *fragment, biasFragment);
MixedSliceGeometry outputSlice;
outputSlice.offsets.assign(inputFragmentType->getRank(), rewriter.getIndexAttr(0));
outputSlice.sizes.push_back(rewriter.getIndexAttr(1));
outputSlice.strides.assign(inputFragmentType->getRank(), rewriter.getIndexAttr(1));
for (int64_t dim : outputFragmentType->getShape())
outputSlice.sizes.push_back(rewriter.getIndexAttr(dim));
Value output = extractMixedSliceOrIdentity(rewriter, loc, added, *outputFragmentType, outputSlice);
if (!output)
return failure();
publishGraphBatchPhysicalFragment(rewriter, loc, output, args.outputs.front(), args.lane);
return success();
});
if (failed(batch))
return failure();
return batch->getResult(0);
}
struct LowerDenseReluPlan final : OpRewritePattern<spatial::SpatReluPlanOp> {
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(spatial::SpatReluPlanOp planOp,
PatternRewriter& rewriter) const override {
auto selected = spatial::getSelectedPhysicalLayout(planOp.getOperation());
if (!selected || *selected != spatial::PhysicalLayout::DenseNCHW)
return failure();
auto computeOp = createSpatCompute<1>(
rewriter, planOp.getLoc(), planOp.getOutput().getType(), {}, planOp.getInput(), [&](Value x) {
auto relu = spatial::SpatReluOp::create(rewriter, planOp.getLoc(), planOp.getOutput().getType(), x);
spatial::SpatYieldOp::create(rewriter, planOp.getLoc(), relu.getResult());
});
rewriter.replaceOp(planOp, computeOp.getResults());
return success();
}
};
struct LowerDenseSiluPlan final : OpRewritePattern<spatial::SpatSiluPlanOp> {
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(spatial::SpatSiluPlanOp planOp,
PatternRewriter& rewriter) const override {
auto selected = spatial::getSelectedPhysicalLayout(planOp.getOperation());
if (!selected || *selected != spatial::PhysicalLayout::DenseNCHW)
return failure();
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());
return success();
}
};
struct LowerDenseResizePlan final : OpRewritePattern<spatial::SpatResizeNearestPlanOp> {
explicit LowerDenseResizePlan(MLIRContext* ctx, const spatial::SpatialTargetInfo& target)
: OpRewritePattern<spatial::SpatResizeNearestPlanOp>(ctx), target(target) {}
LogicalResult matchAndRewrite(spatial::SpatResizeNearestPlanOp planOp,
PatternRewriter& rewriter) const override {
if (!isDenseSelected(planOp.getOperation()))
return failure();
FailureOr<Value> lowered = lowerSelectedResizeNearestPlan(planOp, std::nullopt, target, rewriter);
if (failed(lowered))
return planOp.emitOpError("failed to lower selected dense nearest Resize plan");
rewriter.replaceOp(planOp, *lowered);
return success();
}
const spatial::SpatialTargetInfo& target;
};
struct LowerDenseBiasAddPlan final : OpRewritePattern<spatial::SpatBiasAddPlanOp> {
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(spatial::SpatBiasAddPlanOp planOp,
PatternRewriter& rewriter) const override {
if (!isDenseSelected(planOp.getOperation()))
return failure();
auto resultType = dyn_cast<RankedTensorType>(planOp.getOutput().getType());
if (!resultType)
return planOp.emitOpError("requires ranked output type");
FailureOr<Value> denseBias = materializeDenseBiasAddTensor(
planOp.getBias(), resultType, rewriter, planOp.getLoc());
if (failed(denseBias))
return planOp.emitOpError("failed to materialize dense Conv-style bias");
if (planOp.getInput().getDefiningOp<spatial::SpatGraphComputeBatch>()) {
FailureOr<Value> lowered = lowerDenseBatchBiasAdd(
planOp.getInput(), *denseBias, resultType, rewriter, planOp.getLoc());
if (succeeded(lowered)) {
rewriter.replaceOp(planOp, *lowered);
return success();
}
}
auto computeOp = createSpatCompute<2>(
rewriter,
planOp.getLoc(),
planOp.getOutput().getType(),
{},
ValueRange {planOp.getInput(), *denseBias},
[&](Value x, Value y) {
auto added = spatial::SpatVAddOp::create(
rewriter, planOp.getLoc(), planOp.getOutput().getType(), x, y);
spatial::SpatYieldOp::create(rewriter, planOp.getLoc(), added.getResult());
});
rewriter.replaceOp(planOp, computeOp.getResults());
return success();
}
};
struct LowerDenseAddPlan final : OpRewritePattern<spatial::SpatAddPlanOp> {
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(spatial::SpatAddPlanOp planOp,
PatternRewriter& rewriter) const override {
if (!isDenseSelected(planOp.getOperation()))
return failure();
auto compute = createSpatCompute<2>(
rewriter,
planOp.getLoc(),
planOp.getOutput().getType(),
{},
ValueRange {planOp.getLhs(), planOp.getRhs()},
[&](Value lhsValue, Value rhsValue) {
Value added = spatial::SpatVAddOp::create(
rewriter, planOp.getLoc(), planOp.getOutput().getType(), lhsValue, rhsValue);
spatial::SpatYieldOp::create(rewriter, planOp.getLoc(), added);
});
rewriter.replaceOp(planOp, compute.getResults());
return success();
}
};
struct LowerDenseConcatPlan final : OpRewritePattern<spatial::SpatConcatPlanOp> {
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(spatial::SpatConcatPlanOp planOp,
PatternRewriter& rewriter) const override {
if (!isDenseSelected(planOp.getOperation()))
return failure();
auto compute = createSpatCompute(
rewriter,
planOp.getLoc(),
TypeRange {planOp.getOutput().getType()},
{},
planOp.getInputs(),
[&](ValueRange values) {
Value concatenated = spatial::SpatConcatOp::create(
rewriter,
planOp.getLoc(),
planOp.getOutput().getType(),
rewriter.getI64IntegerAttr(planOp.getAxis()),
values);
spatial::SpatYieldOp::create(rewriter, planOp.getLoc(), concatenated);
});
rewriter.replaceOp(planOp, compute.getResults());
return success();
}
};
static LogicalResult lowerAddPlan(spatial::SpatAddPlanOp planOp,
PatternRewriter& rewriter) {
FailureOr<RowStripPhysicalValue> lhs = getRowStripValue(planOp.getLhs());
FailureOr<RowStripPhysicalValue> rhs = getRowStripValue(planOp.getRhs());
if (isRowStripSelected(planOp.getOperation()) && failed(lhs)) {
if (getKnownPhysicalLayout(planOp.getLhs()) == spatial::PhysicalLayout::NHWCRowStrip)
return failure();
return planOp.emitOpError("selected row-strip Add plan requires row-strip inputs");
}
if (isRowStripSelected(planOp.getOperation()) && failed(rhs)) {
if (getKnownPhysicalLayout(planOp.getRhs()) == spatial::PhysicalLayout::NHWCRowStrip)
return failure();
return planOp.emitOpError("selected row-strip Add plan requires row-strip inputs");
}
if (isRowStripSelected(planOp.getOperation())) {
rewriter.setInsertionPoint(planOp);
FailureOr<Value> lowered = lowerRowStripAdd(*lhs, *rhs, planOp, rewriter);
if (failed(lowered))
return planOp.emitOpError("failed to lower selected row-strip Spatial add plan");
if (failed(publishRowStripValue(planOp, *lowered, rewriter)))
return failure();
return success();
}
return planOp.emitOpError("dense Add plan was not lowered by the selected-plan patterns");
}
static LogicalResult lowerConcatPlan(spatial::SpatConcatPlanOp planOp,
PatternRewriter& rewriter) {
SmallVector<RowStripPhysicalValue> inputs;
for (Value input : planOp.getInputs()) {
FailureOr<RowStripPhysicalValue> physical = getRowStripValue(input);
if (failed(physical)) {
inputs.clear();
break;
}
inputs.push_back(*physical);
}
if (isRowStripSelected(planOp.getOperation()) && inputs.size() != planOp.getInputs().size()) {
if (llvm::any_of(planOp.getInputs(), [](Value input) {
return getKnownPhysicalLayout(input) == spatial::PhysicalLayout::NHWCRowStrip;
}))
return failure();
return planOp.emitOpError("selected row-strip Concat plan requires row-strip inputs");
}
if (isRowStripSelected(planOp.getOperation())) {
rewriter.setInsertionPoint(planOp);
FailureOr<Value> lowered = lowerRowStripConcat(inputs, planOp, rewriter);
if (failed(lowered))
return planOp.emitOpError("failed to lower selected row-strip Spatial concat plan");
if (failed(publishRowStripValue(planOp, *lowered, rewriter)))
return failure();
return success();
}
return planOp.emitOpError("dense Concat plan was not lowered by the selected-plan patterns");
}
struct LowerSelectedConvPlan final : OpRewritePattern<spatial::SpatConv2DPlanOp> {
explicit LowerSelectedConvPlan(MLIRContext* ctx, const spatial::SpatialTargetInfo& target)
: OpRewritePattern<spatial::SpatConv2DPlanOp>(ctx), target(target) {}
LogicalResult matchAndRewrite(spatial::SpatConv2DPlanOp planOp,
PatternRewriter& rewriter) const override {
if (isDenseSelected(planOp.getOperation())) {
FailureOr<Value> lowered = lowerSelectedConv2DPlan(
planOp, std::nullopt, /*emitRowStripLayout=*/false, target, rewriter);
if (failed(lowered))
return planOp.emitOpError("failed to lower selected dense Spatial Conv plan");
rewriter.replaceOp(planOp, *lowered);
return success();
}
if (!isRowStripSelected(planOp.getOperation()))
return failure();
FailureOr<RowStripPhysicalValue> rowStripInput = getRowStripValue(planOp.getInput());
if (failed(rowStripInput)
&& getKnownPhysicalLayout(planOp.getInput()) == spatial::PhysicalLayout::NHWCRowStrip)
return failure();
std::optional<Value> physicalInput;
if (succeeded(rowStripInput))
physicalInput = rowStripInput->storage;
FailureOr<Value> lowered = lowerSelectedConv2DPlan(
planOp, physicalInput, /*emitRowStripLayout=*/true, target, rewriter);
if (failed(lowered))
return planOp.emitOpError("failed to lower selected row-strip Spatial Conv plan");
if (failed(publishRowStripValue(planOp, *lowered, rewriter)))
return failure();
return success();
}
const spatial::SpatialTargetInfo& target;
};
struct LowerRowStripReluPlan final : OpRewritePattern<spatial::SpatReluPlanOp> {
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(spatial::SpatReluPlanOp planOp,
PatternRewriter& rewriter) const override {
if (!isRowStripSelected(planOp.getOperation()))
return failure();
FailureOr<RowStripPhysicalValue> input = getRowStripValue(planOp.getInput());
if (failed(input)) {
if (getKnownPhysicalLayout(planOp.getInput()) == spatial::PhysicalLayout::NHWCRowStrip)
return failure();
return planOp.emitOpError("selected row-strip ReLU plan requires a row-strip input");
}
FailureOr<Value> lowered = lowerRowStripRelu(*input, planOp, rewriter);
if (failed(lowered))
return planOp.emitOpError("failed to lower selected row-strip Spatial ReLU plan");
if (failed(publishRowStripValue(planOp, *lowered, rewriter)))
return failure();
return success();
}
};
struct LowerRowStripSiluPlan final : OpRewritePattern<spatial::SpatSiluPlanOp> {
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(spatial::SpatSiluPlanOp planOp,
PatternRewriter& rewriter) const override {
if (!isRowStripSelected(planOp.getOperation()))
return failure();
FailureOr<RowStripPhysicalValue> input = getRowStripValue(planOp.getInput());
if (failed(input)) {
if (getKnownPhysicalLayout(planOp.getInput()) == spatial::PhysicalLayout::NHWCRowStrip)
return failure();
return planOp.emitOpError("selected row-strip SiLU plan requires a row-strip input");
}
FailureOr<Value> lowered = lowerRowStripSilu(*input, planOp, rewriter);
if (failed(lowered))
return planOp.emitOpError("failed to lower selected row-strip Spatial SiLU plan");
if (failed(publishRowStripValue(planOp, *lowered, rewriter)))
return failure();
return success();
}
};
struct LowerRowStripResizePlan final : OpRewritePattern<spatial::SpatResizeNearestPlanOp> {
explicit LowerRowStripResizePlan(MLIRContext* ctx, const spatial::SpatialTargetInfo& target)
: OpRewritePattern<spatial::SpatResizeNearestPlanOp>(ctx), target(target) {}
LogicalResult matchAndRewrite(spatial::SpatResizeNearestPlanOp planOp,
PatternRewriter& rewriter) const override {
if (!isRowStripSelected(planOp.getOperation()))
return failure();
FailureOr<RowStripPhysicalValue> input = getRowStripValue(planOp.getInput());
if (failed(input)) {
if (getKnownPhysicalLayout(planOp.getInput()) == spatial::PhysicalLayout::NHWCRowStrip)
return failure();
return planOp.emitOpError("selected row-strip Resize plan requires a row-strip input");
}
FailureOr<Value> lowered = lowerSelectedResizeNearestPlan(planOp, input->storage, target, rewriter);
if (failed(lowered))
return planOp.emitOpError("failed to lower selected row-strip Resize plan");
if (failed(publishRowStripValue(planOp, *lowered, rewriter)))
return failure();
return success();
}
const spatial::SpatialTargetInfo& target;
};
struct LowerDenseMaxPoolPlan final : OpRewritePattern<spatial::SpatMaxPool2DPlanOp> {
explicit LowerDenseMaxPoolPlan(MLIRContext* ctx, const spatial::SpatialTargetInfo& target)
: OpRewritePattern<spatial::SpatMaxPool2DPlanOp>(ctx), target(target) {}
LogicalResult matchAndRewrite(spatial::SpatMaxPool2DPlanOp planOp,
PatternRewriter& rewriter) const override {
if (!isDenseSelected(planOp.getOperation()))
return failure();
FailureOr<Value> lowered = lowerDenseMaxPool2DPlan(planOp, target, rewriter);
if (failed(lowered))
return planOp.emitOpError("failed to lower selected dense Spatial MaxPool plan");
rewriter.replaceOp(planOp, *lowered);
return success();
}
const spatial::SpatialTargetInfo& target;
};
struct LowerRowStripMaxPoolPlan final : OpRewritePattern<spatial::SpatMaxPool2DPlanOp> {
explicit LowerRowStripMaxPoolPlan(MLIRContext* ctx, const spatial::SpatialTargetInfo& target)
: OpRewritePattern<spatial::SpatMaxPool2DPlanOp>(ctx), target(target) {}
LogicalResult matchAndRewrite(spatial::SpatMaxPool2DPlanOp planOp,
PatternRewriter& rewriter) const override {
if (!isRowStripSelected(planOp.getOperation()))
return failure();
FailureOr<RowStripPhysicalValue> input = getRowStripValue(planOp.getInput());
if (failed(input)
&& getKnownPhysicalLayout(planOp.getInput()) == spatial::PhysicalLayout::NHWCRowStrip)
return failure();
std::optional<Value> physicalInput;
if (succeeded(input))
physicalInput = input->storage;
FailureOr<Value> lowered = lowerSelectedMaxPool2DPlan(planOp, physicalInput, target, rewriter);
if (failed(lowered))
return planOp.emitOpError("failed to lower selected row-strip Spatial MaxPool plan");
if (failed(publishRowStripValue(planOp, *lowered, rewriter)))
return failure();
return success();
}
const spatial::SpatialTargetInfo& target;
};
struct LowerRowStripGlobalAveragePoolPlan
final : OpRewritePattern<spatial::SpatGlobalAveragePoolPlanOp> {
explicit LowerRowStripGlobalAveragePoolPlan(MLIRContext* ctx, const spatial::SpatialTargetInfo& target)
: OpRewritePattern<spatial::SpatGlobalAveragePoolPlanOp>(ctx), target(target) {}
LogicalResult matchAndRewrite(spatial::SpatGlobalAveragePoolPlanOp planOp,
PatternRewriter& rewriter) const override {
if (!isRowStripSelected(planOp.getOperation()))
return failure();
FailureOr<RowStripPhysicalValue> input = getRowStripValue(planOp.getInput());
if (failed(input)
&& getKnownPhysicalLayout(planOp.getInput()) == spatial::PhysicalLayout::NHWCRowStrip)
return failure();
std::optional<Value> physicalInput;
if (succeeded(input))
physicalInput = input->storage;
FailureOr<Value> lowered = lowerSelectedGlobalAveragePoolPlan(planOp, physicalInput, target, rewriter);
if (failed(lowered))
return planOp.emitOpError("failed to lower selected row-strip Spatial global AveragePool plan");
if (failed(publishRowStripValue(planOp, *lowered, rewriter)))
return failure();
return success();
}
const spatial::SpatialTargetInfo& target;
};
struct LowerDenseGlobalAveragePoolPlan
final : OpRewritePattern<spatial::SpatGlobalAveragePoolPlanOp> {
explicit LowerDenseGlobalAveragePoolPlan(MLIRContext* ctx,
const spatial::SpatialTargetInfo& target)
: OpRewritePattern<spatial::SpatGlobalAveragePoolPlanOp>(ctx), target(target) {}
LogicalResult matchAndRewrite(spatial::SpatGlobalAveragePoolPlanOp planOp,
PatternRewriter& rewriter) const override {
if (!isDenseSelected(planOp.getOperation()))
return failure();
FailureOr<Value> lowered = lowerDenseGlobalAveragePoolPlan(planOp, target, rewriter);
if (failed(lowered))
return planOp.emitOpError("failed to lower selected dense Spatial global AveragePool plan");
rewriter.replaceOp(planOp, *lowered);
return success();
}
const spatial::SpatialTargetInfo& target;
};
struct LowerRowStripBiasAddPlan final : OpRewritePattern<spatial::SpatBiasAddPlanOp> {
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(spatial::SpatBiasAddPlanOp planOp,
PatternRewriter& rewriter) const override {
if (!isRowStripSelected(planOp.getOperation()))
return failure();
FailureOr<RowStripPhysicalValue> input = getRowStripValue(planOp.getInput());
if (failed(input)) {
if (getKnownPhysicalLayout(planOp.getInput()) == spatial::PhysicalLayout::NHWCRowStrip)
return failure();
return planOp.emitOpError("selected row-strip bias_add plan requires a row-strip input");
}
FailureOr<Value> lowered = lowerRowStripBiasAdd(*input, planOp, rewriter);
if (failed(lowered))
return planOp.emitOpError("failed to lower selected row-strip Spatial bias_add plan");
if (failed(publishRowStripValue(planOp, *lowered, rewriter)))
return failure();
return success();
}
};
struct LowerRowStripAddPlan final : OpRewritePattern<spatial::SpatAddPlanOp> {
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(spatial::SpatAddPlanOp planOp,
PatternRewriter& rewriter) const override {
if (!isRowStripSelected(planOp.getOperation()))
return failure();
return lowerAddPlan(planOp, rewriter);
}
};
struct LowerRowStripConcatPlan final : OpRewritePattern<spatial::SpatConcatPlanOp> {
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(spatial::SpatConcatPlanOp planOp,
PatternRewriter& rewriter) const override {
if (!isRowStripSelected(planOp.getOperation()))
return failure();
return lowerConcatPlan(planOp, rewriter);
}
};
struct LowerMaterializeLayout final
: OpRewritePattern<spatial::SpatMaterializeLayoutOp> {
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(spatial::SpatMaterializeLayoutOp materializeOp,
PatternRewriter& rewriter) const override {
auto source = materializeOp.getSourcePhysicalLayout();
auto target = materializeOp.getTargetPhysicalLayout();
if (source == spatial::PhysicalLayout::DenseNCHW
&& target == spatial::PhysicalLayout::DenseNCHW) {
rewriter.replaceOp(materializeOp, materializeOp.getInput());
return success();
}
if (source == spatial::PhysicalLayout::DenseNCHW
&& target == spatial::PhysicalLayout::NHWCRowStrip) {
auto logicalType = dyn_cast<RankedTensorType>(materializeOp.getInput().getType());
if (!logicalType)
return materializeOp.emitOpError("requires a ranked dense input"), failure();
FailureOr<Value> rowStrip = materializeDenseToRowStrip(
materializeOp.getInput(), logicalType, materializeOp.getLoc(), rewriter);
if (failed(rowStrip))
return materializeOp.emitOpError(
"failed to materialize dense NCHW storage to row-strip layout"), failure();
rewriter.replaceOp(materializeOp, *rowStrip);
return success();
}
if (source != spatial::PhysicalLayout::NHWCRowStrip
|| target != spatial::PhysicalLayout::DenseNCHW)
return materializeOp.emitOpError(
"unsupported Spatial layout materialization direction"), failure();
auto inputType = dyn_cast<RankedTensorType>(materializeOp.getInput().getType());
if (!inputType)
return materializeOp.emitOpError("requires a ranked row-strip input"), failure();
FailureOr<RowStripPhysicalValue> rowStripValue =
getRowStripValue(materializeOp.getInput());
if (failed(rowStripValue))
return materializeOp.emitOpError(
"requires an explicitly defining row-strip physical value"), failure();
FailureOr<Value> dense = materializeRowStripToDense(
*rowStripValue, materializeOp.getLoc(), rewriter);
if (failed(dense))
return materializeOp.emitOpError(
"failed to materialize row-strip storage to dense NCHW"), failure();
rewriter.replaceOp(materializeOp, *dense);
return success();
}
};
struct LowerRowStripFlatten final
: OpRewritePattern<spatial::SpatGraphCompute> {
explicit LowerRowStripFlatten(MLIRContext* context,
const spatial::SpatialTargetInfo& target)
: OpRewritePattern<spatial::SpatGraphCompute>(context), target(target) {}
LogicalResult matchAndRewrite(spatial::SpatGraphCompute flattenOp,
PatternRewriter& rewriter) const override {
if (flattenOp.getInputs().size() != 1)
return failure();
FailureOr<RowStripPhysicalValue> input =
getRowStripValue(flattenOp.getInputs().front());
if (failed(input) || failed(canLowerFlattenFromRowStrip(flattenOp, target)))
return failure();
if (failed(lowerFlattenFromRowStrip(*input, flattenOp, target, rewriter)))
return flattenOp.emitOpError(
"failed to preserve row-strip layout through Flatten"), failure();
return success();
}
const spatial::SpatialTargetInfo& target;
};
struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, OperationPass<ModuleOp>> {
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(LowerSpatialPlansPass)
StringRef getArgument() const override { return "lower-spatial-plans"; }
StringRef getDescription() const override { return "Lower selected Spatial planning ops to low-level Spatial IR."; }
LowerSpatialPlansPass() = default;
explicit LowerSpatialPlansPass(const spatial::SpatialTargetInfo& target)
: target(target), hasTarget(true) {}
void runOnOperation() override {
ModuleOp moduleOp = getOperation();
if (!hasTarget) {
moduleOp.emitError("Spatial plan lowering requires an injected SpatialTargetInfo");
signalPassFailure();
return;
}
MLIRContext* ctx = moduleOp.getContext();
auto entryFunc = getPimEntryFunc(moduleOp);
if (failed(entryFunc)) {
moduleOp.emitError("failed to locate the PIM entry function during LowerSpatialPlans");
signalPassFailure();
return;
}
func::FuncOp funcOp = *entryFunc;
PatternRewriter rewriter(ctx);
auto verifyLogicalPhase = [&](StringRef stage) -> bool {
if (succeeded(verifyLogicalSpatialGraphInvariants(*entryFunc)))
return true;
moduleOp.emitError() << "logical Spatial graph verification failed " << stage;
signalPassFailure();
return false;
};
if (!verifyLogicalPhase("at the start of LowerSpatialPlans"))
return;
if (failed(verifySelectedLayouts(funcOp, target))) {
moduleOp.emitError("selected Spatial layout verification failed");
signalPassFailure();
return;
}
RewritePatternSet selectedPlanPatterns(ctx);
selectedPlanPatterns.add<LowerDenseReluPlan,
LowerRowStripReluPlan,
LowerDenseSiluPlan,
LowerRowStripSiluPlan,
LowerDenseBiasAddPlan,
LowerRowStripBiasAddPlan,
LowerDenseAddPlan,
LowerRowStripAddPlan,
LowerDenseConcatPlan,
LowerRowStripConcatPlan>(ctx);
selectedPlanPatterns.add<LowerSelectedConvPlan,
LowerDenseResizePlan,
LowerRowStripResizePlan,
LowerDenseMaxPoolPlan,
LowerRowStripMaxPoolPlan,
LowerDenseGlobalAveragePoolPlan,
LowerRowStripGlobalAveragePoolPlan>(ctx, target);
if (failed(applyPatternsGreedily(funcOp, std::move(selectedPlanPatterns)))) {
moduleOp.emitError("failed to lower selected Spatial plans");
signalPassFailure();
return;
}
RewritePatternSet layoutPatterns(ctx);
layoutPatterns.add<LowerMaterializeLayout>(ctx);
layoutPatterns.add<LowerRowStripFlatten>(ctx, target);
ConversionTarget layoutTarget(*ctx);
layoutTarget.addLegalDialect<spatial::SpatialDialect,
tensor::TensorDialect,
linalg::LinalgDialect,
affine::AffineDialect,
arith::ArithDialect,
scf::SCFDialect,
func::FuncDialect>();
layoutTarget.addIllegalDialect<ONNXDialect>();
layoutTarget.addIllegalOp<spatial::SpatMaterializeLayoutOp>();
layoutTarget.addDynamicallyLegalOp<spatial::SpatGraphCompute>(
[&](spatial::SpatGraphCompute computeOp) {
if (computeOp.getInputs().size() != 1)
return true;
FailureOr<RowStripPhysicalValue> input =
getRowStripValue(computeOp.getInputs().front());
return failed(input) || failed(canLowerFlattenFromRowStrip(computeOp, target));
});
FrozenRewritePatternSet frozenLayoutPatterns(std::move(layoutPatterns));
if (failed(applyFullConversion(funcOp, layoutTarget,
frozenLayoutPatterns))) {
moduleOp.emitError("failed to lower explicit Spatial layout materialization");
signalPassFailure();
return;
}
if (!verifyLogicalPhase("after selected-plan conversion"))
return;
SmallVector<spatial::SpatBlueprintOp> deadPhysicalViews;
funcOp.walk([&](spatial::SpatBlueprintOp blueprint) {
if (spatial::isPhysicalView(blueprint.getMode()) && blueprint.use_empty())
deadPhysicalViews.push_back(blueprint);
});
for (spatial::SpatBlueprintOp blueprint : deadPhysicalViews)
rewriter.eraseOp(blueprint);
bool hasIllegalOps = false;
moduleOp.walk([&](Operation* op) {
if (isa<ONNXEntryPointOp>(op))
return;
if (auto blueprint = dyn_cast<spatial::SpatBlueprintOp>(op)) {
if (spatial::isFragmentAssembly(blueprint.getMode()))
return;
op->emitOpError("planning blueprint must not remain after LowerSpatialPlans");
hasIllegalOps = true;
}
else if (isa<spatial::SpatConv2DPlanOp,
spatial::SpatBiasAddPlanOp,
spatial::SpatAddPlanOp,
spatial::SpatReluPlanOp,
spatial::SpatSiluPlanOp,
spatial::SpatResizeNearestPlanOp,
spatial::SpatMaxPool2DPlanOp,
spatial::SpatGlobalAveragePoolPlanOp,
spatial::SpatMaterializeLayoutOp>(op)
|| op->getDialect()->getNamespace() == "onnx") {
op->emitOpError("operation must not remain after LowerSpatialPlans");
hasIllegalOps = true;
}
});
PassManager canonicalizationPM(ctx);
canonicalizationPM.addPass(createCanonicalizerPass());
if (failed(canonicalizationPM.run(moduleOp)))
moduleOp.emitWarning("failed to run LowerSpatialPlansPass canonicalization; continuing");
if (hasIllegalOps) {
signalPassFailure();
} else {
dumpModule(moduleOp, "spatial1_graph");
spatial::SpatialDataflowExportStage exportMode = spatial::getSpatialDataflowExportStage();
if (spatial::shouldExportSpatialDataflowStage(exportMode, spatial::SpatialDataflowExportStage::Spatial1)
&& failed(spatial::exportSpatialDataflowCsvGraph(funcOp, "spatial1_graph"))) {
signalPassFailure();
return;
}
}
if (!verifyLogicalPhase("at the end of LowerSpatialPlans"))
return;
}
spatial::SpatialTargetInfo target;
bool hasTarget = false;
};
} // namespace
std::unique_ptr<Pass> createLowerSpatialPlansPass() { return std::make_unique<LowerSpatialPlansPass>(); }
std::unique_ptr<Pass> createLowerSpatialPlansPass(const spatial::SpatialTargetInfo& target) {
return std::make_unique<LowerSpatialPlansPass>(target);
}
} // namespace onnx_mlir
@@ -108,7 +108,9 @@ void verifyScheduledInputs(ComputeOpTy compute,
for (auto [inputIndex, input] : llvm::enumerate(compute.getInputs())) { for (auto [inputIndex, input] : llvm::enumerate(compute.getInputs())) {
size_t currentInputIndex = inputIndex; size_t currentInputIndex = inputIndex;
Operation* definingOp = input.getDefiningOp(); Operation* definingOp = input.getDefiningOp();
if (allowChannelReceiveInputs && isa_and_nonnull<spatial::SpatChannelReceiveOp>(definingOp)) if (allowChannelReceiveInputs
&& isa_and_nonnull<spatial::SpatChannelReceiveOp,
spatial::SpatHostWaitLoadOp>(definingOp))
continue; continue;
if (isScheduledPhase1Value(input)) if (isScheduledPhase1Value(input))
continue; continue;
@@ -163,7 +165,8 @@ void verifyLogicalTopLevelOps(func::FuncOp funcOp, pim::CappedDiagnosticReporter
}); });
continue; continue;
} }
if (isa<spatial::SpatChannelReceiveOp, spatial::SpatChannelSendOp>(&op)) { if (isa<spatial::SpatChannelReceiveOp, spatial::SpatChannelSendOp,
spatial::SpatHostStoreSyncOp, spatial::SpatHostWaitLoadOp>(&op)) {
diagnostics.report(&op, [&](Operation* illegalOp) { diagnostics.report(&op, [&](Operation* illegalOp) {
illegalOp->emitOpError() << kPhaseMarker illegalOp->emitOpError() << kPhaseMarker
<< " explicit channel communication is not expected before merge materialization"; << " explicit channel communication is not expected before merge materialization";
@@ -182,7 +185,8 @@ void verifyLogicalTopLevelOps(func::FuncOp funcOp, pim::CappedDiagnosticReporter
void verifyScheduledTopLevelOps(func::FuncOp funcOp, pim::CappedDiagnosticReporter& diagnostics) { void verifyScheduledTopLevelOps(func::FuncOp funcOp, pim::CappedDiagnosticReporter& diagnostics) {
for (Operation& op : funcOp.getOps()) { for (Operation& op : funcOp.getOps()) {
if (isa<spatial::SpatChannelSendOp, spatial::SpatChannelReceiveOp>(&op)) { if (isa<spatial::SpatChannelSendOp, spatial::SpatChannelReceiveOp,
spatial::SpatHostStoreSyncOp, spatial::SpatHostWaitLoadOp>(&op)) {
diagnostics.report(&op, [&](Operation* illegalOp) { diagnostics.report(&op, [&](Operation* illegalOp) {
illegalOp->emitOpError() << kPhaseMarker << " real channel communication is not allowed in scheduled phase 1"; illegalOp->emitOpError() << kPhaseMarker << " real channel communication is not allowed in scheduled phase 1";
}); });
@@ -0,0 +1,64 @@
#pragma once
#include <optional>
#include "mlir/IR/PatternMatch.h"
#include "mlir/Support/LogicalResult.h"
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
namespace onnx_mlir {
struct RowStripPhysicalValue;
mlir::FailureOr<mlir::Value>
lowerSelectedConv2DPlan(spatial::SpatConv2DPlanOp planOp,
std::optional<mlir::Value> rowStripInput,
bool emitRowStripLayout,
const spatial::SpatialTargetInfo& target,
mlir::PatternRewriter& rewriter);
mlir::LogicalResult canLowerConvPlanToRowStrip(spatial::SpatConv2DPlanOp planOp,
const spatial::SpatialTargetInfo& target);
mlir::LogicalResult canConsumeAndProduceRowStrip(spatial::SpatConv2DPlanOp planOp,
const spatial::SpatialTargetInfo& target);
mlir::LogicalResult canLowerResizeNearestPlanToRowStrip(
spatial::SpatResizeNearestPlanOp planOp, const spatial::SpatialTargetInfo& target);
mlir::FailureOr<mlir::Value> lowerSelectedResizeNearestPlan(
spatial::SpatResizeNearestPlanOp planOp,
std::optional<mlir::Value> rowStripInput,
const spatial::SpatialTargetInfo& target,
mlir::PatternRewriter& rewriter);
mlir::LogicalResult canLowerMaxPoolPlanToRowStrip(spatial::SpatMaxPool2DPlanOp planOp,
const spatial::SpatialTargetInfo& target);
mlir::FailureOr<mlir::Value>
lowerDenseMaxPool2DPlan(spatial::SpatMaxPool2DPlanOp planOp,
const spatial::SpatialTargetInfo& target,
mlir::PatternRewriter& rewriter);
mlir::FailureOr<mlir::Value>
lowerSelectedMaxPool2DPlan(spatial::SpatMaxPool2DPlanOp planOp,
std::optional<mlir::Value> rowStripInput,
const spatial::SpatialTargetInfo& target,
mlir::PatternRewriter& rewriter);
mlir::LogicalResult
canLowerGlobalAveragePoolPlanToRowStrip(spatial::SpatGlobalAveragePoolPlanOp planOp,
const spatial::SpatialTargetInfo& target);
mlir::FailureOr<mlir::Value>
lowerDenseGlobalAveragePoolPlan(spatial::SpatGlobalAveragePoolPlanOp planOp,
const spatial::SpatialTargetInfo& target,
mlir::PatternRewriter& rewriter);
mlir::FailureOr<mlir::Value>
lowerSelectedGlobalAveragePoolPlan(spatial::SpatGlobalAveragePoolPlanOp planOp,
std::optional<mlir::Value> rowStripInput,
const spatial::SpatialTargetInfo& target,
mlir::PatternRewriter& rewriter);
} // namespace onnx_mlir
@@ -0,0 +1,133 @@
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/BiasAddUtils.hpp"
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/PlanLowering.hpp"
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
using namespace mlir;
namespace onnx_mlir::spatial {
static LayoutAlternative denseAlternative(Operation *op) {
LayoutAlternative alternative;
alternative.operandLayouts.assign(op->getNumOperands(), PhysicalLayout::DenseNCHW);
alternative.resultLayout = PhysicalLayout::DenseNCHW;
return alternative;
}
static LayoutAlternative rowStripAlternative(Operation *op,
ArrayRef<PhysicalLayout> operandLayouts) {
LayoutAlternative alternative;
alternative.operandLayouts.assign(operandLayouts.begin(), operandLayouts.end());
alternative.resultLayout = PhysicalLayout::NHWCRowStrip;
alternative.intrinsicCost = -2;
return alternative;
}
static bool hasRowStripInput(ArrayRef<PhysicalLayout> operandLayouts, unsigned index) {
return index < operandLayouts.size()
&& operandLayouts[index] == PhysicalLayout::NHWCRowStrip;
}
SmallVector<LayoutAlternative> SpatConv2DPlanOp::getLayoutAlternatives(
const SpatialTargetInfo& target, ArrayRef<PhysicalLayout> operandLayouts) {
SmallVector<LayoutAlternative> alternatives {denseAlternative(getOperation())};
if (hasRowStripInput(operandLayouts, 0)) {
if (succeeded(canConsumeAndProduceRowStrip(*this, target)))
alternatives.push_back(rowStripAlternative(getOperation(), operandLayouts));
}
else if (succeeded(canLowerConvPlanToRowStrip(*this, target))) {
LayoutAlternative alternative = denseAlternative(getOperation());
alternative.resultLayout = PhysicalLayout::NHWCRowStrip;
alternative.intrinsicCost = -2;
alternatives.push_back(std::move(alternative));
}
return alternatives;
}
SmallVector<LayoutAlternative> SpatReluPlanOp::getLayoutAlternatives(
const SpatialTargetInfo&, ArrayRef<PhysicalLayout> operandLayouts) {
SmallVector<LayoutAlternative> alternatives {denseAlternative(getOperation())};
if (hasRowStripInput(operandLayouts, 0))
alternatives.push_back(rowStripAlternative(getOperation(), operandLayouts));
return alternatives;
}
SmallVector<LayoutAlternative> SpatSiluPlanOp::getLayoutAlternatives(
const SpatialTargetInfo&, ArrayRef<PhysicalLayout> operandLayouts) {
SmallVector<LayoutAlternative> alternatives {denseAlternative(getOperation())};
if (hasRowStripInput(operandLayouts, 0)) {
LayoutAlternative alternative = rowStripAlternative(getOperation(), operandLayouts);
alternative.intrinsicCost = -3;
alternatives.push_back(std::move(alternative));
}
return alternatives;
}
SmallVector<LayoutAlternative> SpatResizeNearestPlanOp::getLayoutAlternatives(
const SpatialTargetInfo& target, ArrayRef<PhysicalLayout> operandLayouts) {
SmallVector<LayoutAlternative> alternatives {denseAlternative(getOperation())};
if (hasRowStripInput(operandLayouts, 0)
&& succeeded(canLowerResizeNearestPlanToRowStrip(*this, target)))
alternatives.push_back(rowStripAlternative(getOperation(), operandLayouts));
return alternatives;
}
SmallVector<LayoutAlternative> SpatMaxPool2DPlanOp::getLayoutAlternatives(
const SpatialTargetInfo& target, ArrayRef<PhysicalLayout> operandLayouts) {
SmallVector<LayoutAlternative> alternatives {denseAlternative(getOperation())};
if (succeeded(canLowerMaxPoolPlanToRowStrip(*this, target))) {
LayoutAlternative alternative = denseAlternative(getOperation());
if (hasRowStripInput(operandLayouts, 0))
alternative = rowStripAlternative(getOperation(), operandLayouts);
alternative.resultLayout = PhysicalLayout::NHWCRowStrip;
alternative.intrinsicCost = -2;
alternatives.push_back(std::move(alternative));
}
return alternatives;
}
SmallVector<LayoutAlternative> SpatGlobalAveragePoolPlanOp::getLayoutAlternatives(
const SpatialTargetInfo& target, ArrayRef<PhysicalLayout> operandLayouts) {
SmallVector<LayoutAlternative> alternatives {denseAlternative(getOperation())};
if (succeeded(canLowerGlobalAveragePoolPlanToRowStrip(*this, target))) {
LayoutAlternative alternative = denseAlternative(getOperation());
if (hasRowStripInput(operandLayouts, 0))
alternative = rowStripAlternative(getOperation(), operandLayouts);
alternative.resultLayout = PhysicalLayout::NHWCRowStrip;
alternative.intrinsicCost = -2;
alternatives.push_back(std::move(alternative));
}
return alternatives;
}
SmallVector<LayoutAlternative> SpatBiasAddPlanOp::getLayoutAlternatives(
const SpatialTargetInfo&, ArrayRef<PhysicalLayout> operandLayouts) {
SmallVector<LayoutAlternative> alternatives {denseAlternative(getOperation())};
auto resultType = dyn_cast<RankedTensorType>(getOutput().getType());
if (resultType && hasRowStripInput(operandLayouts, 0)
&& isSupportedBiasAddValue(getBias(), resultType))
alternatives.push_back(rowStripAlternative(getOperation(),
{PhysicalLayout::NHWCRowStrip,
PhysicalLayout::DenseNCHW}));
return alternatives;
}
SmallVector<LayoutAlternative> SpatAddPlanOp::getLayoutAlternatives(
const SpatialTargetInfo&, ArrayRef<PhysicalLayout> operandLayouts) {
SmallVector<LayoutAlternative> alternatives {denseAlternative(getOperation())};
if (operandLayouts.size() >= 2 && hasRowStripInput(operandLayouts, 0)
&& hasRowStripInput(operandLayouts, 1))
alternatives.push_back(rowStripAlternative(getOperation(), operandLayouts));
return alternatives;
}
SmallVector<LayoutAlternative> SpatConcatPlanOp::getLayoutAlternatives(
const SpatialTargetInfo&, ArrayRef<PhysicalLayout> operandLayouts) {
SmallVector<LayoutAlternative> alternatives {denseAlternative(getOperation())};
if (!operandLayouts.empty() && llvm::all_of(operandLayouts, [](PhysicalLayout layout) {
return layout == PhysicalLayout::NHWCRowStrip;
}))
alternatives.push_back(rowStripAlternative(getOperation(), operandLayouts));
return alternatives;
}
} // namespace onnx_mlir::spatial
@@ -0,0 +1,265 @@
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/Pass/Pass.h"
#include "llvm/ADT/DenseMap.h"
#include "Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp"
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/RowStripLayoutUtils.hpp"
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
#include "src/Accelerators/PIM/Pass/PIMPasses.h"
#include <algorithm>
using namespace mlir;
namespace onnx_mlir {
namespace {
using LayoutMap = llvm::DenseMap<Value, spatial::PhysicalLayout>;
static spatial::PhysicalLayout getSelectedLayout(const LayoutMap& layouts, Value value) {
if (auto it = layouts.find(value); it != layouts.end())
return it->second;
if (auto materialize = value.getDefiningOp<spatial::SpatMaterializeLayoutOp>())
return materialize.getTargetPhysicalLayout();
if (auto blueprint = value.getDefiningOp<spatial::SpatBlueprintOp>())
return blueprint.getPhysicalLayout();
return spatial::PhysicalLayout::DenseNCHW;
}
static SmallVector<spatial::PhysicalLayout> getOperandLayouts(
Operation* op, const LayoutMap& layouts) {
SmallVector<spatial::PhysicalLayout> operandLayouts;
operandLayouts.reserve(op->getNumOperands());
for (Value operand : op->getOperands())
operandLayouts.push_back(getSelectedLayout(layouts, operand));
return operandLayouts;
}
static FailureOr<SmallVector<spatial::LayoutAlternative>> getAlternatives(
Operation* op, const LayoutMap& layouts, const spatial::SpatialTargetInfo& target) {
auto capability = dyn_cast<spatial::SpatialLayoutCapabilityInterface>(op);
if (!capability)
return failure();
SmallVector<spatial::LayoutAlternative> alternatives =
capability.getLayoutAlternatives(target, getOperandLayouts(op, layouts));
if (alternatives.empty())
return op->emitOpError("does not advertise a legal Spatial layout alternative"), failure();
for (const spatial::LayoutAlternative& alternative : alternatives)
if (alternative.operandLayouts.size() != op->getNumOperands())
return op->emitOpError("advertises a layout alternative with the wrong operand count"), failure();
return alternatives;
}
static unsigned findCurrentAlternative(
Operation* op, ArrayRef<spatial::LayoutAlternative> alternatives,
spatial::PhysicalLayout selectedResult) {
for (auto [index, alternative] : llvm::enumerate(alternatives))
if (alternative.resultLayout == selectedResult)
return index;
return 0;
}
static int64_t alternativeCost(Operation* op,
const spatial::LayoutAlternative& alternative,
const LayoutMap& layouts,
const LayoutMap& selectedResults,
const spatial::SpatialTargetInfo& target) {
int64_t cost = alternative.intrinsicCost;
SmallVector<spatial::PhysicalLayout> operandLayouts = getOperandLayouts(op, layouts);
for (auto [actual, required] : llvm::zip(operandLayouts, alternative.operandLayouts))
cost += actual != required;
Value result = op->getResult(0);
for (OpOperand& use : result.getUses()) {
auto user = dyn_cast<spatial::SpatialLayoutCapabilityInterface>(use.getOwner());
if (!user) {
if (alternative.resultLayout != spatial::PhysicalLayout::DenseNCHW) {
auto flatten = dyn_cast<spatial::SpatGraphCompute>(use.getOwner());
if (!flatten || failed(canLowerFlattenFromRowStrip(flatten, target)))
++cost;
}
continue;
}
auto userAlternatives = getAlternatives(use.getOwner(), selectedResults, target);
if (failed(userAlternatives))
continue;
spatial::PhysicalLayout userResult =
selectedResults.lookup(use.getOwner()->getResult(0));
unsigned userIndex = findCurrentAlternative(use.getOwner(), *userAlternatives, userResult);
if (use.getOperandNumber() < (*userAlternatives)[userIndex].operandLayouts.size()
&& (*userAlternatives)[userIndex].operandLayouts[use.getOperandNumber()]
!= alternative.resultLayout)
++cost;
}
return cost;
}
static LogicalResult materializeMismatchedUses(
IRRewriter& rewriter, Value value, const LayoutMap& layouts,
const spatial::SpatialTargetInfo& target) {
spatial::PhysicalLayout sourceLayout = getSelectedLayout(layouts, value);
SmallVector<std::pair<OpOperand*, spatial::PhysicalLayout>> mismatches;
for (OpOperand& use : value.getUses()) {
Operation* userOp = use.getOwner();
spatial::PhysicalLayout required = spatial::PhysicalLayout::DenseNCHW;
if (auto capability = dyn_cast<spatial::SpatialLayoutCapabilityInterface>(userOp)) {
auto alternatives = getAlternatives(userOp, layouts, target);
if (failed(alternatives))
return failure();
spatial::PhysicalLayout selected =
getSelectedLayout(layouts, userOp->getResult(0));
unsigned selectedIndex = findCurrentAlternative(userOp, *alternatives, selected);
required = (*alternatives)[selectedIndex].operandLayouts[use.getOperandNumber()];
}
else if (auto flatten = dyn_cast<spatial::SpatGraphCompute>(userOp);
flatten && sourceLayout == spatial::PhysicalLayout::NHWCRowStrip
&& succeeded(canLowerFlattenFromRowStrip(flatten, target))) {
continue;
}
if (required != sourceLayout)
mismatches.push_back({&use, required});
}
for (auto [use, required] : mismatches) {
Operation* userOp = use->getOwner();
rewriter.setInsertionPoint(userOp);
auto materialized = spatial::SpatMaterializeLayoutOp::create(
rewriter, userOp->getLoc(), use->get().getType(), use->get(),
spatial::LogicalLayoutAttr::get(
rewriter.getContext(), spatial::LogicalLayout::NCHW),
spatial::PhysicalLayoutAttr::get(rewriter.getContext(), sourceLayout),
spatial::PhysicalLayoutAttr::get(rewriter.getContext(),
required));
use->set(materialized.getResult());
}
return success();
}
static LogicalResult verifySelectedLayouts(
ArrayRef<Operation*> planOps, const LayoutMap& layouts,
const spatial::SpatialTargetInfo& target) {
for (Operation* op : planOps) {
auto selected = spatial::getSelectedPhysicalLayout(op);
if (!selected)
return op->emitOpError("requires a selected physical layout"), failure();
auto alternatives = getAlternatives(op, layouts, target);
if (failed(alternatives))
return failure();
if (llvm::none_of(*alternatives, [&](const spatial::LayoutAlternative& alternative) {
return alternative.resultLayout == *selected;
}))
return op->emitOpError("selected physical layout is not advertised by its layout contract"), failure();
}
return success();
}
struct SpatialLayoutPlanningPass final
: PassWrapper<SpatialLayoutPlanningPass, OperationPass<ModuleOp>> {
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(SpatialLayoutPlanningPass)
StringRef getArgument() const override { return "spatial-layout-planning"; }
StringRef getDescription() const override {
return "Select Spatial layout alternatives and insert explicit reconciliation barriers.";
}
SpatialLayoutPlanningPass() = default;
explicit SpatialLayoutPlanningPass(const spatial::SpatialTargetInfo& target)
: target(target), hasTarget(true) {}
void runOnOperation() override {
ModuleOp moduleOp = getOperation();
if (!hasTarget) {
moduleOp.emitError("Spatial layout planning requires an injected SpatialTargetInfo");
signalPassFailure();
return;
}
auto entryFunc = getPimEntryFunc(moduleOp);
if (failed(entryFunc)) {
moduleOp.emitError("failed to locate the PIM entry function during Spatial layout planning");
signalPassFailure();
return;
}
func::FuncOp funcOp = *entryFunc;
SmallVector<Operation*> planOps;
for (Operation& op : funcOp.getBody().front())
if (isa<spatial::SpatialLayoutCapabilityInterface>(&op))
planOps.push_back(&op);
LayoutMap layouts;
for (Operation* op : planOps)
layouts[op->getResult(0)] = spatial::PhysicalLayout::DenseNCHW;
const size_t maxRounds = 2 * planOps.size() + 1;
bool converged = false;
for (size_t round = 0; round < maxRounds && !converged; ++round) {
converged = true;
SmallVector<Operation*> order(planOps);
if (round % 2)
std::reverse(order.begin(), order.end());
for (Operation* op : order) {
auto alternatives = getAlternatives(op, layouts, target);
if (failed(alternatives)) {
signalPassFailure();
return;
}
spatial::PhysicalLayout current = layouts.lookup(op->getResult(0));
unsigned currentIndex = findCurrentAlternative(op, *alternatives, current);
int64_t bestCost = alternativeCost(
op, (*alternatives)[currentIndex], layouts, layouts, target);
unsigned bestIndex = currentIndex;
for (auto [index, alternative] : llvm::enumerate(*alternatives)) {
int64_t cost = alternativeCost(op, alternative, layouts, layouts, target);
if (cost < bestCost) {
bestCost = cost;
bestIndex = index;
}
}
spatial::PhysicalLayout selected = (*alternatives)[bestIndex].resultLayout;
if (selected != current) {
layouts[op->getResult(0)] = selected;
converged = false;
}
}
}
if (!converged) {
moduleOp.emitError("Spatial layout selection did not converge within its bounded iteration budget");
signalPassFailure();
return;
}
IRRewriter rewriter(&getContext());
for (Operation* op : planOps) {
op->setAttr(spatial::kSelectedLayoutAttrName,
spatial::PhysicalLayoutAttr::get(
rewriter.getContext(), layouts.lookup(op->getResult(0))));
if (failed(materializeMismatchedUses(rewriter, op->getResult(0), layouts, target))) {
signalPassFailure();
return;
}
}
if (failed(verifySelectedLayouts(planOps, layouts, target))
|| failed(verifyLogicalSpatialGraphInvariants(*entryFunc))) {
moduleOp.emitError("Spatial layout planning verification failed");
signalPassFailure();
}
}
spatial::SpatialTargetInfo target;
bool hasTarget = false;
};
} // namespace
std::unique_ptr<Pass> createSpatialLayoutPlanningPass() {
return std::make_unique<SpatialLayoutPlanningPass>();
}
std::unique_ptr<Pass> createSpatialLayoutPlanningPass(
const spatial::SpatialTargetInfo& target) {
return std::make_unique<SpatialLayoutPlanningPass>(target);
}
} // namespace onnx_mlir
@@ -1,7 +1,10 @@
#include "mlir/IR/ValueRange.h" #include "mlir/IR/ValueRange.h"
#include "mlir/Dialect/Arith/IR/Arith.h" #include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/Func/IR/FuncOps.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/BuiltinOps.h"
#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/STLExtras.h"
@@ -28,6 +31,49 @@ FailureOr<IntegerAttr> getTensorSizeInBytesAttr(Builder& builder, Operation* anc
return pim::getCheckedI32Attr(builder, anchor, *byteSize, "tensor byte size"); return pim::getCheckedI32Attr(builder, anchor, *byteSize, "tensor byte size");
} }
LogicalResult materializePipelineHostBuffer(
func::FuncOp funcOp, RewriterBase &rewriter) {
auto bytes = funcOp->getAttrOfType<IntegerAttr>(
kPipelineHostBufferBytesAttrName);
if (!bytes)
return success();
if (bytes.getInt() <= 0)
return funcOp.emitOpError(
"pipeline host transfer buffer must be positive");
ModuleOp moduleOp = funcOp->getParentOfType<ModuleOp>();
if (moduleOp.lookupSymbol<memref::GlobalOp>(kPipelineHostBufferName))
return funcOp.emitOpError(
"pipeline host transfer buffer symbol already exists");
auto type = MemRefType::get(
{bytes.getInt()}, rewriter.getI8Type());
OpBuilder::InsertionGuard guard(rewriter);
rewriter.setInsertionPointToStart(moduleOp.getBody());
memref::GlobalOp::create(
rewriter, funcOp.getLoc(),
rewriter.getStringAttr(kPipelineHostBufferName),
rewriter.getStringAttr("private"), TypeAttr::get(type), Attribute(),
UnitAttr(), IntegerAttr());
return success();
}
FailureOr<mlir::Value> getPipelineHostBuffer(
OpBuilder &builder, Operation *anchor) {
auto funcOp = anchor->getParentOfType<func::FuncOp>();
auto moduleOp = anchor->getParentOfType<ModuleOp>();
auto bytes = funcOp
? funcOp->getAttrOfType<IntegerAttr>(kPipelineHostBufferBytesAttrName)
: IntegerAttr();
auto global = moduleOp
? moduleOp.lookupSymbol<memref::GlobalOp>(kPipelineHostBufferName)
: memref::GlobalOp();
if (!bytes || !global)
return anchor->emitOpError(
"requires the pipeline host transfer buffer"), failure();
auto type = MemRefType::get({bytes.getInt()}, builder.getI8Type());
return memref::GetGlobalOp::create(
builder, anchor->getLoc(), type, kPipelineHostBufferName).getResult();
}
Operation* getEarliestUserWithinBlock(mlir::Value value) { Operation* getEarliestUserWithinBlock(mlir::Value value) {
auto users = value.getUsers(); auto users = value.getUsers();
@@ -10,6 +10,7 @@
#include "mlir/IR/Builders.h" #include "mlir/IR/Builders.h"
#include "mlir/IR/Value.h" #include "mlir/IR/Value.h"
#include "mlir/Dialect/Tensor/IR/Tensor.h" #include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Support/LogicalResult.h" #include "mlir/Support/LogicalResult.h"
#include "src/Accelerators/PIM/Common/PimCommon.hpp" #include "src/Accelerators/PIM/Common/PimCommon.hpp"
@@ -23,6 +24,12 @@ namespace onnx_mlir {
mlir::FailureOr<mlir::IntegerAttr> mlir::FailureOr<mlir::IntegerAttr>
getTensorSizeInBytesAttr(mlir::Builder& builder, mlir::Operation* anchor, mlir::Value value); getTensorSizeInBytesAttr(mlir::Builder& builder, mlir::Operation* anchor, mlir::Value value);
mlir::LogicalResult materializePipelineHostBuffer(
mlir::func::FuncOp funcOp, mlir::RewriterBase &rewriter);
mlir::FailureOr<mlir::Value> getPipelineHostBuffer(
mlir::OpBuilder &builder, mlir::Operation *anchor);
template <class T> template <class T>
size_t rangeLength(const mlir::iterator_range<T> range) { size_t rangeLength(const mlir::iterator_range<T> range) {
return std::distance(range.begin(), range.end()); return std::distance(range.begin(), range.end());
@@ -345,20 +345,39 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeOp(spatial::SpatScheduledCom
auto blockArg = computeOp.getInputArgument(inputIndex); auto blockArg = computeOp.getInputArgument(inputIndex);
if (!blockArg) if (!blockArg)
return computeOp.emitOpError("expected compute input block arguments during lowering"); return computeOp.emitOpError("expected compute input block arguments during lowering");
auto receiveOp = dyn_cast_or_null<spatial::SpatChannelReceiveOp>(input.getDefiningOp()); auto channelReceive = dyn_cast_or_null<spatial::SpatChannelReceiveOp>(
input.getDefiningOp());
auto hostWaitLoad = dyn_cast_or_null<spatial::SpatHostWaitLoadOp>(
input.getDefiningOp());
Operation *receiveOp = channelReceive
? channelReceive.getOperation() : hostWaitLoad.getOperation();
if (receiveOp && !blockArg->use_empty()) { if (receiveOp && !blockArg->use_empty()) {
rewriter.setInsertionPoint(getEarliestUserWithinBlock(*blockArg)); rewriter.setInsertionPoint(getEarliestUserWithinBlock(*blockArg));
auto outputType = cast<ShapedType>(blockArg->getType()); auto outputType = cast<ShapedType>(blockArg->getType());
auto outputBuffer = createEmptyTensorFromShaped(rewriter, receiveOp.getLoc(), outputType); auto outputBuffer = createEmptyTensorFromShaped(
rewriter, receiveOp->getLoc(), outputType);
auto sizeAttr = getTensorSizeInBytesAttr(rewriter, computeOp.getOperation(), *blockArg); auto sizeAttr = getTensorSizeInBytesAttr(rewriter, computeOp.getOperation(), *blockArg);
if (failed(sizeAttr)) if (failed(sizeAttr))
return failure(); return failure();
Value received = Value zero = arith::ConstantIndexOp::create(
PimReceiveOp::create( rewriter, receiveOp->getLoc(), 0);
rewriter, receiveOp.getLoc(), outputBuffer.getType(), outputBuffer, Value received;
arith::ConstantIndexOp::create(rewriter, receiveOp.getLoc(), 0), if (hostWaitLoad) {
*sizeAttr, receiveOp.getSourceCoreId()) auto hostBuffer = getPipelineHostBuffer(rewriter, hostWaitLoad);
if (failed(hostBuffer))
return failure();
PimWaitOp::create(
rewriter, receiveOp->getLoc(), hostWaitLoad.getEventRegister(),
rewriter.getI32IntegerAttr(1));
received = PimMemCopyHostToDevOp::create(
rewriter, receiveOp->getLoc(), outputBuffer.getType(), zero,
hostWaitLoad.getHostOffset(), outputBuffer, *hostBuffer, *sizeAttr)
.getOutput(); .getOutput();
} else {
received = PimReceiveOp::create(
rewriter, receiveOp->getLoc(), outputBuffer.getType(), outputBuffer,
zero, *sizeAttr, channelReceive.getSourceCoreId()).getOutput();
}
blockArg->replaceAllUsesWith(received); blockArg->replaceAllUsesWith(received);
markOpToRemove(receiveOp); markOpToRemove(receiveOp);
continue; continue;
@@ -383,7 +402,8 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeOp(spatial::SpatScheduledCom
if (rangeLength(resultUses) == 1) { if (rangeLength(resultUses) == 1) {
OpOperand& resultUse = *resultUses.begin(); OpOperand& resultUse = *resultUses.begin();
Operation* resultUser = resultUse.getOwner(); Operation* resultUser = resultUse.getOwner();
if (isa<spatial::SpatChannelSendOp>(resultUser)) if (isa<spatial::SpatChannelSendOp,
spatial::SpatHostStoreSyncOp>(resultUser))
continue; continue;
} }
@@ -57,10 +57,29 @@ struct ChannelSendLowering : OpRewritePattern<spatial::SpatChannelSendOp> {
} }
}; };
struct ChannelReceiveLowering : OpRewritePattern<spatial::SpatChannelReceiveOp> { struct HostStoreSyncLowering : OpRewritePattern<spatial::SpatHostStoreSyncOp> {
using OpRewritePattern::OpRewritePattern; using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(spatial::SpatChannelReceiveOp op, PatternRewriter& rewriter) const override { LogicalResult matchAndRewrite(spatial::SpatHostStoreSyncOp op, PatternRewriter& rewriter) const override {
auto sizeAttr = getTensorSizeInBytesAttr(rewriter, op.getOperation(), op.getInput());
auto hostBuffer = getPipelineHostBuffer(rewriter, op);
if (failed(sizeAttr) || failed(hostBuffer))
return failure();
Value zero = arith::ConstantIndexOp::create(rewriter, op.getLoc(), 0);
pim::PimMemCopyDevToHostOp::create(
rewriter, op.getLoc(), hostBuffer->getType(), op.getHostOffset(), zero,
*hostBuffer, op.getInput(), *sizeAttr);
auto sync = pim::PimSyncOp::create(
rewriter, op.getLoc(), op.getTargetCoreId(), op.getEventRegister());
copyRaptorDebugAttrs(op.getOperation(), sync.getOperation());
rewriter.eraseOp(op);
return success();
}
};
template <typename ReceiveOp, typename CreateReceive>
static LogicalResult lowerReceive(
ReceiveOp op, PatternRewriter& rewriter, CreateReceive createReceive) {
if (op->use_empty()) { if (op->use_empty()) {
rewriter.eraseOp(op); rewriter.eraseOp(op);
return success(); return success();
@@ -86,12 +105,11 @@ struct ChannelReceiveLowering : OpRewritePattern<spatial::SpatChannelReceiveOp>
if (failed(sizeAttr)) if (failed(sizeAttr))
return failure(); return failure();
Value zero = arith::ConstantIndexOp::create(rewriter, op.getLoc(), 0); Value zero = arith::ConstantIndexOp::create(rewriter, op.getLoc(), 0);
auto receive = pim::PimReceiveOp::create( auto received = createReceive(outputBuffer, zero, *sizeAttr);
rewriter, op.getLoc(), op.getResult().getType(), outputBuffer, zero, *sizeAttr, op.getSourceCoreId()); if (failed(received))
copyRaptorDebugAttrs(op.getOperation(), receive.getOperation()); return failure();
Value received = receive.getOutput();
if (!destinationInsert) { if (!destinationInsert) {
rewriter.replaceOp(op, received); rewriter.replaceOp(op, *received);
return success(); return success();
} }
@@ -99,10 +117,42 @@ struct ChannelReceiveLowering : OpRewritePattern<spatial::SpatChannelReceiveOp>
Value targetOffset = createDestinationByteOffset(rewriter, destinationInsert); Value targetOffset = createDestinationByteOffset(rewriter, destinationInsert);
auto copy = pim::PimMemCopyOp::create( auto copy = pim::PimMemCopyOp::create(
rewriter, op.getLoc(), destinationInsert.getDestType(), targetOffset, zero, rewriter, op.getLoc(), destinationInsert.getDestType(), targetOffset, zero,
destinationInsert.getDest(), received, *sizeAttr); destinationInsert.getDest(), *received, *sizeAttr);
rewriter.replaceOp(destinationInsert, copy.getOutput()); rewriter.replaceOp(destinationInsert, copy.getOutput());
rewriter.eraseOp(op); rewriter.eraseOp(op);
return success(); return success();
}
struct ChannelReceiveLowering : OpRewritePattern<spatial::SpatChannelReceiveOp> {
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(spatial::SpatChannelReceiveOp op, PatternRewriter& rewriter) const override {
return lowerReceive(op, rewriter, [&](Value outputBuffer, Value zero, IntegerAttr sizeAttr) -> FailureOr<Value> {
auto receive = pim::PimReceiveOp::create(
rewriter, op.getLoc(), op.getResult().getType(), outputBuffer, zero,
sizeAttr, op.getSourceCoreId());
copyRaptorDebugAttrs(op.getOperation(), receive.getOperation());
return receive.getOutput();
});
}
};
struct HostWaitLoadLowering : OpRewritePattern<spatial::SpatHostWaitLoadOp> {
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(spatial::SpatHostWaitLoadOp op, PatternRewriter& rewriter) const override {
return lowerReceive(op, rewriter, [&](Value outputBuffer, Value zero, IntegerAttr sizeAttr) -> FailureOr<Value> {
auto hostBuffer = getPipelineHostBuffer(rewriter, op);
if (failed(hostBuffer))
return failure();
auto wait = pim::PimWaitOp::create(
rewriter, op.getLoc(), op.getEventRegister(),
rewriter.getI32IntegerAttr(1));
copyRaptorDebugAttrs(op.getOperation(), wait.getOperation());
return pim::PimMemCopyHostToDevOp::create(
rewriter, op.getLoc(), outputBuffer.getType(), zero,
op.getHostOffset(), outputBuffer, *hostBuffer, sizeAttr).getOutput();
});
} }
}; };
@@ -148,7 +198,9 @@ struct ConcatLowering : OpRewritePattern<spatial::SpatConcatOp> {
} // namespace } // namespace
void populateChannelLoweringPatterns(RewritePatternSet& patterns) { void populateChannelLoweringPatterns(RewritePatternSet& patterns) {
patterns.add<ChannelSendLowering, ChannelReceiveLowering, ExtractRowsLowering, ConcatLowering>(patterns.getContext()); patterns.add<ChannelSendLowering, ChannelReceiveLowering,
HostStoreSyncLowering, HostWaitLoadLowering,
ExtractRowsLowering, ConcatLowering>(patterns.getContext());
} }
} // namespace onnx_mlir } // namespace onnx_mlir
@@ -859,6 +859,10 @@ void raptor::SpatialToPimPass::replaceReturnWithOutputBuffers(func::ReturnOp ret
markOpToRemove(receiveOp); markOpToRemove(receiveOp);
return; return;
} }
if (auto receiveOp = dyn_cast<spatial::SpatHostWaitLoadOp>(op)) {
markOpToRemove(receiveOp);
return;
}
}; };
SmallVector<Value> originalOperands(returnOp.getOperands().begin(), returnOp.getOperands().end()); SmallVector<Value> originalOperands(returnOp.getOperands().begin(), returnOp.getOperands().end());
@@ -126,6 +126,8 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
spatial::SpatConcatOp, spatial::SpatConcatOp,
spatial::SpatChannelReceiveOp, spatial::SpatChannelReceiveOp,
spatial::SpatChannelSendOp, spatial::SpatChannelSendOp,
spatial::SpatHostStoreSyncOp,
spatial::SpatHostWaitLoadOp,
spatial::SpatExtractRowsOp>(); spatial::SpatExtractRowsOp>();
RewritePatternSet initialPatterns(ctx); RewritePatternSet initialPatterns(ctx);
@@ -140,6 +142,12 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
populateGlobalTensorMaterializationPatterns(globalTensorPatterns); populateGlobalTensorMaterializationPatterns(globalTensorPatterns);
walkAndApplyPatterns(moduleOp, std::move(globalTensorPatterns)); walkAndApplyPatterns(moduleOp, std::move(globalTensorPatterns));
if (funcOp->hasAttr(kPipelineHostBufferBytesAttrName)
&& failed(materializePipelineHostBuffer(funcOp, rewriter))) {
signalPassFailure();
return;
}
auto returnOp = cast<func::ReturnOp>(funcOp.front().getTerminator()); auto returnOp = cast<func::ReturnOp>(funcOp.front().getTerminator());
addReturnOutputBuffers(returnOp, rewriter); addReturnOutputBuffers(returnOp, rewriter);
if (failed(allocateAndInitializeCoreLocalVariables(funcOp, rewriter))) { if (failed(allocateAndInitializeCoreLocalVariables(funcOp, rewriter))) {
@@ -182,6 +190,17 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
continue; continue;
} }
} }
SmallVector<spatial::SpatHostWaitLoadOp> hostWaitLoadOps;
for (auto op : funcOp.getOps<spatial::SpatHostWaitLoadOp>())
hostWaitLoadOps.push_back(op);
for (auto op : hostWaitLoadOps) {
bool onlyPendingRemovalUsers = llvm::all_of(
op->getUsers(), [&](Operation* user) {
return llvm::is_contained(operationsToRemove, user);
});
if (onlyPendingRemovalUsers)
markOpToRemove(op);
}
RewritePatternSet coreBodyPatterns(ctx); RewritePatternSet coreBodyPatterns(ctx);
populateCoreBodyPatterns(coreBodyPatterns); populateCoreBodyPatterns(coreBodyPatterns);
@@ -202,6 +221,8 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
spatial::SpatConcatOp, spatial::SpatConcatOp,
spatial::SpatChannelReceiveOp, spatial::SpatChannelReceiveOp,
spatial::SpatChannelSendOp, spatial::SpatChannelSendOp,
spatial::SpatHostStoreSyncOp,
spatial::SpatHostWaitLoadOp,
spatial::SpatExtractRowsOp>(); spatial::SpatExtractRowsOp>();
SmallVector<pim::PimCoreOp> coreOps; SmallVector<pim::PimCoreOp> coreOps;
@@ -251,6 +272,8 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
communicationTarget.addIllegalOp<spatial::SpatConcatOp, communicationTarget.addIllegalOp<spatial::SpatConcatOp,
spatial::SpatChannelReceiveOp, spatial::SpatChannelReceiveOp,
spatial::SpatChannelSendOp, spatial::SpatChannelSendOp,
spatial::SpatHostStoreSyncOp,
spatial::SpatHostWaitLoadOp,
spatial::SpatExtractRowsOp>(); spatial::SpatExtractRowsOp>();
RewritePatternSet communicationPatterns(ctx); RewritePatternSet communicationPatterns(ctx);
@@ -430,8 +430,7 @@ analyzeCopyRewrite(Value target, Value source, Value targetOffset, Value sourceO
auto targetBytes = getShapedByteSize(targetType); auto targetBytes = getShapedByteSize(targetType);
auto sourceBytes = getShapedByteSize(sourceType); auto sourceBytes = getShapedByteSize(sourceType);
if (targetType.getElementType() == sourceType.getElementType() && succeeded(targetBytes) && succeeded(sourceBytes) if (succeeded(targetBytes) && succeeded(sourceBytes) && size <= *targetBytes && size <= *sourceBytes) {
&& size <= *targetBytes && size <= *sourceBytes) {
auto targetSuffixRank = getContiguousSuffixRank(target, targetType.getShape()); auto targetSuffixRank = getContiguousSuffixRank(target, targetType.getShape());
auto sourceSuffixRank = getContiguousSuffixRank(source, sourceType.getShape()); auto sourceSuffixRank = getContiguousSuffixRank(source, sourceType.getShape());
if (succeeded(targetSuffixRank) && succeeded(sourceSuffixRank) if (succeeded(targetSuffixRank) && succeeded(sourceSuffixRank)
@@ -241,6 +241,8 @@ static bool isSupportedCoreInstructionOp(Operation* op) {
pim::PimVMVOp, pim::PimVMVOp,
pim::PimReceiveOp, pim::PimReceiveOp,
pim::PimSendOp, pim::PimSendOp,
pim::PimSyncOp,
pim::PimWaitOp,
pim::PimConcatOp, pim::PimConcatOp,
pim::PimVMMOp, pim::PimVMMOp,
pim::PimVVAddOp, pim::PimVVAddOp,
+26
View File
@@ -118,6 +118,32 @@ def PimReceiveOp : PimOp<"receive", [DestinationStyleOpInterface]> {
}]; }];
} }
def PimSyncOp : PimOp<"sync", []> {
let summary = "Signal an event register on another core";
let arguments = (ins
Index:$targetCoreId,
Index:$eventRegister
);
let assemblyFormat = [{
$targetCoreId `event` $eventRegister attr-dict
}];
}
def PimWaitOp : PimOp<"wait", []> {
let summary = "Wait for an event register value";
let arguments = (ins
Index:$eventRegister,
I32Attr:$waitValue
);
let assemblyFormat = [{
$eventRegister `value` $waitValue attr-dict
}];
}
def PimMemCopyHostToDevOp : PimOp<"memcp_hd", [DestinationStyleOpInterface]> { def PimMemCopyHostToDevOp : PimOp<"memcp_hd", [DestinationStyleOpInterface]> {
let summary = "Copy a memory region from host memory into device memory"; let summary = "Copy a memory region from host memory into device memory";
+1
View File
@@ -35,6 +35,7 @@ add_pim_library(SpatialOps
Passes/Transforms/MergeComputeNodes/SpatialDataflowCsvExporter.cpp Passes/Transforms/MergeComputeNodes/SpatialDataflowCsvExporter.cpp
Passes/Transforms/MergeComputeNodes/Scheduling/MergeSchedulingAnalysis.cpp Passes/Transforms/MergeComputeNodes/Scheduling/MergeSchedulingAnalysis.cpp
Passes/Transforms/MergeComputeNodes/Scheduling/PeftScheduler.cpp Passes/Transforms/MergeComputeNodes/Scheduling/PeftScheduler.cpp
Passes/Transforms/MergeComputeNodes/Scheduling/PipelineScheduling.cpp
Passes/Transforms/TrivialGraphComputeMergePass.cpp Passes/Transforms/TrivialGraphComputeMergePass.cpp
EXCLUDE_FROM_OM_LIBS EXCLUDE_FROM_OM_LIBS
@@ -219,10 +219,12 @@ static void appendReceive(BoundaryProgram &boundary,
run->entryOffsets[run->entryOffsets.size() - 2]].family->requirement; run->entryOffsets[run->entryOffsets.size() - 2]].family->requirement;
CollectionTarget previousTarget {run->collection, run->positions.back()}; CollectionTarget previousTarget {run->collection, run->positions.back()};
bool sameEntry = previous == requirement; bool sameEntry = previous == requirement;
if (sameEntry bool sameRoute = run->slices.back().family->hostRouted
== slice.family->hostRouted;
if (sameRoute && (sameEntry
|| (sameCollectionEmissionContract(previousTarget, target) || (sameCollectionEmissionContract(previousTarget, target)
&& previous->publicationFragmentType && previous->publicationFragmentType
== requirement->publicationFragmentType)) { == requirement->publicationFragmentType))) {
run->slices.push_back(slice); run->slices.push_back(slice);
if (sameEntry) { if (sameEntry) {
run->entryOffsets.back() = run->slices.size(); run->entryOffsets.back() = run->slices.size();
@@ -8,6 +8,7 @@
#include "src/Accelerators/PIM/Common/IR/StaticIntGrid.hpp" #include "src/Accelerators/PIM/Common/IR/StaticIntGrid.hpp"
#include "src/Accelerators/PIM/Common/IR/StaticIntSequence.hpp" #include "src/Accelerators/PIM/Common/IR/StaticIntSequence.hpp"
#include "src/Accelerators/PIM/Common/IR/TensorSliceUtils.hpp" #include "src/Accelerators/PIM/Common/IR/TensorSliceUtils.hpp"
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
#include <array> #include <array>
namespace onnx_mlir::spatial { namespace onnx_mlir::spatial {
using namespace mlir; using namespace mlir;
@@ -18,6 +19,8 @@ struct LogicalTransferMetadataView {
StaticIntSequenceChain parentCounts; StaticIntSequenceChain parentCounts;
StaticIntSequenceChain sourceCores; StaticIntSequenceChain sourceCores;
StaticIntSequenceChain targetCores; StaticIntSequenceChain targetCores;
StaticIntSequenceChain hostOffsets;
StaticIntSequenceChain eventRegisters;
StaticIntSequenceChain targetLanes; StaticIntSequenceChain targetLanes;
StaticIntSequenceChain localOffsets; StaticIntSequenceChain localOffsets;
SmallVector<StaticIntSequenceChain> projectionOffsets; SmallVector<StaticIntSequenceChain> projectionOffsets;
@@ -28,7 +31,8 @@ struct LogicalTransferMetadataView {
}; };
using MetadataMember = StaticIntSequenceChain LogicalTransferMetadataView::*; using MetadataMember = StaticIntSequenceChain LogicalTransferMetadataView::*;
static constexpr std::array<MetadataMember, 3> transferMetadataMembers{ static constexpr std::array<MetadataMember, 3> transferMetadataMembers{
&LogicalTransferMetadataView::channels, &LogicalTransferMetadataView::sourceCores, &LogicalTransferMetadataView::targetCores}; &LogicalTransferMetadataView::channels, &LogicalTransferMetadataView::sourceCores,
&LogicalTransferMetadataView::targetCores};
struct TransferGrids { struct TransferGrids {
std::array<StaticIntGrid, 3> values; std::array<StaticIntGrid, 3> values;
StaticIntGrid &channels() { return values[0]; } StaticIntGrid &channels() { return values[0]; }
@@ -41,7 +45,8 @@ template <typename Build> static FailureOr<TransferGrids> buildTransferGrids(Bui
auto targetCores = build(transferMetadataMembers[2]); auto targetCores = build(transferMetadataMembers[2]);
if (failed(channels) || failed(sourceCores) || failed(targetCores)) if (failed(channels) || failed(sourceCores) || failed(targetCores))
return failure(); return failure();
return TransferGrids{{std::move(*channels), std::move(*sourceCores), std::move(*targetCores)}}; return TransferGrids{{std::move(*channels), std::move(*sourceCores),
std::move(*targetCores)}};
} }
using GridGeometry = DeferredGridSliceGeometry; using GridGeometry = DeferredGridSliceGeometry;
using StaticGeometryMember = SmallVector<StaticIntSequence> DeferredStaticSliceGeometry::*; using StaticGeometryMember = SmallVector<StaticIntSequence> DeferredStaticSliceGeometry::*;
@@ -82,6 +87,11 @@ static void appendMetadata(const ScheduledTransferSlice &slice, LogicalTransferM
metadata.parentCounts.append(StaticIntSequence::uniform(family.requirement->exchange->externalTransferCount, count)); metadata.parentCounts.append(StaticIntSequence::uniform(family.requirement->exchange->externalTransferCount, count));
metadata.sourceCores.append(family.sourceCores, familyIndex, count); metadata.sourceCores.append(family.sourceCores, familyIndex, count);
metadata.targetCores.append(family.targetCores, familyIndex, count); metadata.targetCores.append(family.targetCores, familyIndex, count);
if (family.hostRouted) {
metadata.hostOffsets.append(family.hostOffsets, familyIndex, count);
metadata.eventRegisters.append(
family.eventRegisters, familyIndex, count);
}
metadata.targetLanes.append(StaticIntSequence::affine(targetLane, 1, count)); metadata.targetLanes.append(StaticIntSequence::affine(targetLane, 1, count));
if (family.requirement->producerLocalOffsets) if (family.requirement->producerLocalOffsets)
metadata.localOffsets.append(*family.requirement->producerLocalOffsets, targetLane - requirementLanes.begin, count); metadata.localOffsets.append(*family.requirement->producerLocalOffsets, targetLane - requirementLanes.begin, count);
@@ -172,6 +182,7 @@ static LogicalResult emitSendRun(const EmitSendRun &run, Value lane, unsigned la
appendMetadata(slice, metadataByLane[sourceLane]); appendMetadata(slice, metadataByLane[sourceLane]);
} }
LogicalTransferMetadataView logical = buildMetadataView(run.slices); LogicalTransferMetadataView logical = buildMetadataView(run.slices);
ExternalTransferFamily &firstFamily = *run.slices.front().family;
size_t actionCount = 0; size_t actionCount = 0;
for (const LogicalTransferMetadataView &laneMetadata : metadataByLane) for (const LogicalTransferMetadataView &laneMetadata : metadataByLane)
actionCount = std::max(actionCount, laneMetadata.size()); actionCount = std::max(actionCount, laneMetadata.size());
@@ -185,6 +196,20 @@ static LogicalResult emitSendRun(const EmitSendRun &run, Value lane, unsigned la
FailureOr<StaticIntGrid> localOffsets = buildGrid(&LogicalTransferMetadataView::localOffsets, logical.localOffsets.valueAt(0)); FailureOr<StaticIntGrid> localOffsets = buildGrid(&LogicalTransferMetadataView::localOffsets, logical.localOffsets.valueAt(0));
if (failed(transferGrids) || failed(localOffsets)) if (failed(transferGrids) || failed(localOffsets))
return failure(); return failure();
std::optional<StaticIntGrid> hostOffsets;
std::optional<StaticIntGrid> eventRegisters;
if (firstFamily.hostRouted) {
auto offsets = buildGrid(
&LogicalTransferMetadataView::hostOffsets,
logical.hostOffsets.valueAt(0));
auto events = buildGrid(
&LogicalTransferMetadataView::eventRegisters,
logical.eventRegisters.valueAt(0));
if (failed(offsets) || failed(events))
return failure();
hostOffsets = std::move(*offsets);
eventRegisters = std::move(*events);
}
GridGeometry projectionGrids; GridGeometry projectionGrids;
for (auto [geometryIndex, sourceMember] : llvm::enumerate(metadataGeometryMembers)) { for (auto [geometryIndex, sourceMember] : llvm::enumerate(metadataGeometryMembers)) {
const auto &logicalValues = logical.*sourceMember; const auto &logicalValues = logical.*sourceMember;
@@ -207,7 +232,6 @@ static LogicalResult emitSendRun(const EmitSendRun &run, Value lane, unsigned la
const LogicalTransferMetadataView &source = metadataByLane[sourceLane]; const LogicalTransferMetadataView &source = metadataByLane[sourceLane];
counts[sourceLane] = source.size(); counts[sourceLane] = source.size();
} }
ExternalTransferFamily &firstFamily = *run.slices.front().family;
RequirementFamily &requirement = *firstFamily.requirement; RequirementFamily &requirement = *firstFamily.requirement;
Operation *anchor = requirement.exchange->deferred; Operation *anchor = requirement.exchange->deferred;
Location loc = requirement.exchange->deferred.getLoc(); Location loc = requirement.exchange->deferred.getLoc();
@@ -217,10 +241,25 @@ static LogicalResult emitSendRun(const EmitSendRun &run, Value lane, unsigned la
auto payload = materializeSendPayload(requirement, localOffset, projectionGrids[0].empty() ? nullptr : &projection, context, loc); auto payload = materializeSendPayload(requirement, localOffset, projectionGrids[0].empty() ? nullptr : &projection, context, loc);
if (failed(payload)) if (failed(payload))
return failure(); return failure();
auto send = SpatChannelSendOp::create( Value sourceCore = transferGrids->sourceCores().emitLookup(
context.rewriter, loc, transferGrids->channels().emitLookup(action, runtimeLane, anchor, context.constants, context.rewriter, loc), action, runtimeLane, anchor, context.constants, context.rewriter, loc);
transferGrids->sourceCores().emitLookup(action, runtimeLane, anchor, context.constants, context.rewriter, loc), Value targetCore = transferGrids->targetCores().emitLookup(
transferGrids->targetCores().emitLookup(action, runtimeLane, anchor, context.constants, context.rewriter, loc), *payload); action, runtimeLane, anchor, context.constants, context.rewriter, loc);
Operation *send;
if (firstFamily.hostRouted)
send = SpatHostStoreSyncOp::create(
context.rewriter, loc, sourceCore, targetCore,
hostOffsets->emitLookup(
action, runtimeLane, anchor, context.constants, context.rewriter, loc),
eventRegisters->emitLookup(
action, runtimeLane, anchor, context.constants, context.rewriter, loc),
*payload);
else
send = SpatChannelSendOp::create(
context.rewriter, loc,
transferGrids->channels().emitLookup(
action, runtimeLane, anchor, context.constants, context.rewriter, loc),
sourceCore, targetCore, *payload);
setLogicalTransferMetadata(send, logical); setLogicalTransferMetadata(send, logical);
return success(); return success();
}; };
@@ -255,14 +294,45 @@ static FailureOr<Value> emitReceiveValue(ArrayRef<ScheduledTransferSlice> slices
}; };
auto grids = buildTransferGrids([&](MetadataMember member) { return buildGrid(metadata.*member); }); auto grids = buildTransferGrids([&](MetadataMember member) { return buildGrid(metadata.*member); });
if (failed(grids)) return failure(); if (failed(grids)) return failure();
std::optional<StaticIntGrid> hostOffsets;
std::optional<StaticIntGrid> eventRegisters;
if (slices.front().family->hostRouted) {
auto offsets = buildGrid(metadata.hostOffsets);
auto events = buildGrid(metadata.eventRegisters);
if (failed(offsets) || failed(events))
return failure();
hostOffsets = std::move(*offsets);
eventRegisters = std::move(*events);
}
Value position = lane ? lane : context.constants.getIndex(0); Value position = lane ? lane : context.constants.getIndex(0);
Value row = context.constants.getIndex(0); Value row = context.constants.getIndex(0);
auto receive = SpatChannelReceiveOp::create(context.rewriter, anchor->getLoc(), requirement.publicationFragmentType, Value sourceCore = grids->sourceCores().emitLookup(
grids->channels().emitLookup(row, position, anchor, context.constants, context.rewriter, anchor->getLoc()), row, position, anchor, context.constants, context.rewriter, anchor->getLoc());
grids->sourceCores().emitLookup(row, position, anchor, context.constants, context.rewriter, anchor->getLoc()), Value targetCore = grids->targetCores().emitLookup(
grids->targetCores().emitLookup(row, position, anchor, context.constants, context.rewriter, anchor->getLoc())); row, position, anchor, context.constants, context.rewriter, anchor->getLoc());
Operation *receive;
Value output;
if (slices.front().family->hostRouted) {
auto op = SpatHostWaitLoadOp::create(
context.rewriter, anchor->getLoc(), requirement.publicationFragmentType,
sourceCore, targetCore,
hostOffsets->emitLookup(
row, position, anchor, context.constants, context.rewriter, anchor->getLoc()),
eventRegisters->emitLookup(
row, position, anchor, context.constants, context.rewriter, anchor->getLoc()));
receive = op;
output = op.getOutput();
} else {
auto op = SpatChannelReceiveOp::create(
context.rewriter, anchor->getLoc(), requirement.publicationFragmentType,
grids->channels().emitLookup(
row, position, anchor, context.constants, context.rewriter, anchor->getLoc()),
sourceCore, targetCore);
receive = op;
output = op.getOutput();
}
setLogicalTransferMetadata(receive, metadata); setLogicalTransferMetadata(receive, metadata);
return receive.getOutput(); return output;
} }
static FailureOr<SmallVector<LogicalTransferMetadataView, 0>> static FailureOr<SmallVector<LogicalTransferMetadataView, 0>>
@@ -315,6 +385,9 @@ 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;
std::optional<StaticIntGrid> hostOffsets;
std::optional<StaticIntGrid> eventRegisters;
bool hostRouted = run.slices.front().family->hostRouted;
auto metadataByEntry = buildRectangularReceiveMetadata(run, laneCount); auto metadataByEntry = buildRectangularReceiveMetadata(run, laneCount);
if (succeeded(metadataByEntry)) { if (succeeded(metadataByEntry)) {
auto buildRows = [&](auto member) { auto buildRows = [&](auto member) {
@@ -324,6 +397,16 @@ static FailureOr<Value> emitReceiveAssembly(const EmitReceiveAssemblyRun &run, V
return StaticIntGrid::fromRows(rows); return StaticIntGrid::fromRows(rows);
}; };
auto grids = buildTransferGrids(buildRows); auto grids = buildTransferGrids(buildRows);
if (hostRouted) {
auto offsets = buildRows(
&LogicalTransferMetadataView::hostOffsets);
auto events = buildRows(
&LogicalTransferMetadataView::eventRegisters);
if (failed(offsets) || failed(events))
return failure();
hostOffsets = std::move(*offsets);
eventRegisters = std::move(*events);
}
SmallVector<StaticIntSequence> positionRows; SmallVector<StaticIntSequence> positionRows;
for (unsigned position : run.positions) for (unsigned position : run.positions)
positionRows.push_back(StaticIntSequence::uniform(position, laneCount)); positionRows.push_back(StaticIntSequence::uniform(position, laneCount));
@@ -368,6 +451,16 @@ static FailureOr<Value> emitReceiveAssembly(const EmitReceiveAssemblyRun &run, V
return StaticIntGrid::fromColumns(actionCount, columns, defaultValue); return StaticIntGrid::fromColumns(actionCount, columns, defaultValue);
}; };
auto grids = buildTransferGrids(buildGrid); auto grids = buildTransferGrids(buildGrid);
if (hostRouted) {
auto offsets = buildGrid(
&LogicalTransferMetadataView::hostOffsets);
auto events = buildGrid(
&LogicalTransferMetadataView::eventRegisters);
if (failed(offsets) || failed(events))
return failure();
hostOffsets = std::move(*offsets);
eventRegisters = std::move(*events);
}
SmallVector<StaticIntSequence> positionColumns; SmallVector<StaticIntSequence> positionColumns;
for (const StaticIntSequenceChain &values : positionsByLane) for (const StaticIntSequenceChain &values : positionsByLane)
positionColumns.push_back( positionColumns.push_back(
@@ -386,15 +479,34 @@ static FailureOr<Value> emitReceiveAssembly(const EmitReceiveAssemblyRun &run, V
Value runtimeLane = lane ? lane : context.constants.getIndex(0); Value runtimeLane = lane ? lane : context.constants.getIndex(0);
auto emitEntry = [&](Value entry, Value current) -> FailureOr<Value> { auto emitEntry = [&](Value entry, Value current) -> FailureOr<Value> {
Type fragmentType = run.slices.front().family->requirement->publicationFragmentType; Type fragmentType = run.slices.front().family->requirement->publicationFragmentType;
auto receive = Value sourceCore = transferGrids->sourceCores().emitLookup(
SpatChannelReceiveOp::create(context.rewriter, loc, fragmentType, entry, runtimeLane, anchor, context.constants, context.rewriter, loc);
transferGrids->channels().emitLookup(entry, runtimeLane, anchor, context.constants, context.rewriter, loc), Value targetCore = transferGrids->targetCores().emitLookup(
transferGrids->sourceCores().emitLookup(entry, runtimeLane, anchor, context.constants, context.rewriter, loc), entry, runtimeLane, anchor, context.constants, context.rewriter, loc);
transferGrids->targetCores().emitLookup(entry, runtimeLane, anchor, context.constants, context.rewriter, loc)); Operation *receive;
Value output;
if (hostRouted) {
auto op = SpatHostWaitLoadOp::create(
context.rewriter, loc, fragmentType, sourceCore, targetCore,
hostOffsets->emitLookup(
entry, runtimeLane, anchor, context.constants, context.rewriter, loc),
eventRegisters->emitLookup(
entry, runtimeLane, anchor, context.constants, context.rewriter, loc));
receive = op;
output = op.getOutput();
} else {
auto op = SpatChannelReceiveOp::create(
context.rewriter, loc, fragmentType,
transferGrids->channels().emitLookup(
entry, runtimeLane, anchor, context.constants, context.rewriter, loc),
sourceCore, targetCore);
receive = op;
output = op.getOutput();
}
setLogicalTransferMetadata(receive, logical); setLogicalTransferMetadata(receive, logical);
Value position = positions->emitLookup( Value position = positions->emitLookup(
entry, runtimeLane, anchor, context.constants, context.rewriter, loc); entry, runtimeLane, anchor, context.constants, context.rewriter, loc);
return insert(receive.getOutput(), position, entry, runtimeLane, current); return insert(output, position, entry, runtimeLane, current);
}; };
if (actionCount == 1 && llvm::all_of(counts, [](int64_t count) { return count == 1; })) if (actionCount == 1 && llvm::all_of(counts, [](int64_t count) { return count == 1; }))
return emitEntry(context.constants.getIndex(0), initial); return emitEntry(context.constants.getIndex(0), initial);
@@ -28,6 +28,11 @@ static std::optional<Event> getPlannedHead(
while (cursor.slice < plan.slices.size()) { while (cursor.slice < plan.slices.size()) {
const ScheduledTransferSlice &slice = plan.slices[cursor.slice]; const ScheduledTransferSlice &slice = plan.slices[cursor.slice];
ExternalTransferFamily &family = *slice.family; ExternalTransferFamily &family = *slice.family;
if (family.hostRouted) {
++cursor.slice;
cursor.offset = 0;
continue;
}
size_t begin = slice.familyOffset + cursor.offset; size_t begin = slice.familyOffset + cursor.offset;
size_t length = slice.transferCount - cursor.offset; size_t length = slice.transferCount - cursor.offset;
auto source = family.sourceStreams.find(stream, begin, length); auto source = family.sourceStreams.find(stream, begin, length);
@@ -243,6 +248,8 @@ LogicalResult verifyPlannedCommunicationDeadlockFree(
DenseMap<ExternalTransferFamily *, unsigned> familyIndex; DenseMap<ExternalTransferFamily *, unsigned> familyIndex;
for (const ScheduledTransferSlice &slice : plan.slices) { for (const ScheduledTransferSlice &slice : plan.slices) {
ExternalTransferFamily *family = slice.family; ExternalTransferFamily *family = slice.family;
if (family->hostRouted)
continue;
if (!familyIndex.try_emplace(family, familyIndex.size()).second) if (!familyIndex.try_emplace(family, familyIndex.size()).second)
continue; continue;
size_t count = family->channelIds.size(); size_t count = family->channelIds.size();
@@ -258,18 +265,6 @@ LogicalResult verifyPlannedCommunicationDeadlockFree(
familyChannels.emplace_back( familyChannels.emplace_back(
first, first + static_cast<int64_t>(count)); first, first + static_cast<int64_t>(count));
} }
llvm::sort(familyChannels);
int64_t nextChannel = 0;
for (auto [firstChannel, endChannel] : familyChannels) {
if (firstChannel != nextChannel)
return anchor->emitError(
"planned communication channels are not exactly contiguous");
nextChannel = endChannel;
}
if (static_cast<uint64_t>(nextChannel) != plan.logicalTransferCount)
return anchor->emitError(
"planned communication channel count is inconsistent");
for (const ScheduledTransferSlice &slice : plan.slices) { for (const ScheduledTransferSlice &slice : plan.slices) {
ExternalTransferFamily &family = *slice.family; ExternalTransferFamily &family = *slice.family;
for (size_t offset = 0; offset < slice.transferCount; ++offset) { for (size_t offset = 0; offset < slice.transferCount; ++offset) {
@@ -296,6 +291,8 @@ LogicalResult verifyRealizedCommunicationDeadlockFree(
DenseMap<ExternalTransferFamily *, unsigned> familyIndex; DenseMap<ExternalTransferFamily *, unsigned> familyIndex;
for (const ScheduledTransferSlice &slice : plan.slices) { for (const ScheduledTransferSlice &slice : plan.slices) {
ExternalTransferFamily *family = slice.family; ExternalTransferFamily *family = slice.family;
if (family->hostRouted)
continue;
if (!familyIndex.try_emplace(family, familyIndex.size()).second) if (!familyIndex.try_emplace(family, familyIndex.size()).second)
continue; continue;
for (size_t index = 0; index < family->channelIds.size(); ++index) for (size_t index = 0; index < family->channelIds.size(); ++index)
@@ -305,6 +302,8 @@ LogicalResult verifyRealizedCommunicationDeadlockFree(
DenseMap<int64_t, StaticIntSequenceChain> expected; DenseMap<int64_t, StaticIntSequenceChain> expected;
for (const ScheduledTransferSlice &slice : plan.slices) { for (const ScheduledTransferSlice &slice : plan.slices) {
ExternalTransferFamily &family = *slice.family; ExternalTransferFamily &family = *slice.family;
if (family.hostRouted)
continue;
appendEventsByCore(expected, family.channelIds, family.sourceCores, appendEventsByCore(expected, family.channelIds, family.sourceCores,
slice.familyOffset, slice.transferCount, true); slice.familyOffset, slice.transferCount, true);
appendEventsByCore(expected, family.channelIds, family.targetCores, appendEventsByCore(expected, family.channelIds, family.targetCores,
@@ -198,6 +198,7 @@ struct ScheduledInfo {
llvm::SmallVector<mlir::Block*> blocks; llvm::SmallVector<mlir::Block*> blocks;
llvm::SmallVector<mlir::Operation*> stepAnchors; llvm::SmallVector<mlir::Operation*> stepAnchors;
llvm::SmallVector<int64_t> cores; llvm::SmallVector<int64_t> cores;
llvm::SmallVector<unsigned> pipelineStages;
unsigned stepCount = 0; unsigned stepCount = 0;
llvm::SmallVector<ProducedValue*> produced; llvm::SmallVector<ProducedValue*> produced;
llvm::SmallVector<unsigned> streamIds; llvm::SmallVector<unsigned> streamIds;
@@ -233,6 +234,9 @@ struct ExternalTransferFamily {
StaticIntSequence sourceCores = StaticIntSequence::uniform(0, 1); StaticIntSequence sourceCores = StaticIntSequence::uniform(0, 1);
StaticIntSequence targetCores = StaticIntSequence::uniform(0, 1); StaticIntSequence targetCores = StaticIntSequence::uniform(0, 1);
StaticIntSequence channelIds = StaticIntSequence::uniform(0, 1); StaticIntSequence channelIds = StaticIntSequence::uniform(0, 1);
StaticIntSequence hostOffsets = StaticIntSequence::uniform(0, 1);
StaticIntSequence eventRegisters = StaticIntSequence::uniform(0, 1);
bool hostRouted = false;
}; };
struct DeferredExchangePlan { struct DeferredExchangePlan {
@@ -209,15 +209,26 @@ static LogicalResult verifyDominance(func::FuncOp funcOp) {
LogicalResult realizeDeferredCommunication(func::FuncOp funcOp, LogicalResult realizeDeferredCommunication(func::FuncOp funcOp,
const ScheduledComputeMaterializationResult& materialization, const ScheduledComputeMaterializationResult& materialization,
const SchedulingTarget& target) { const SchedulingTarget& target,
size_t pipelineStages) {
IRRewriter rewriter(funcOp.getContext()); IRRewriter rewriter(funcOp.getContext());
eraseUnusedIdentityDeferredCommunications(funcOp, rewriter); eraseUnusedIdentityDeferredCommunications(funcOp, rewriter);
auto transfers = buildDeferredTransferPlan(funcOp, materialization); auto transfers = buildDeferredTransferPlan(
funcOp, materialization, pipelineStages, target.processorCount);
if (failed(transfers)) if (failed(transfers))
return funcOp.emitOpError("phase 2 failed to build symbolic transfer families"); return funcOp.emitOpError("phase 2 failed to build symbolic transfer families");
if (failed(placeLogicalProcessorsOnPhysicalCores(*transfers, target))) if (failed(placeLogicalProcessorsOnPhysicalCores(*transfers, target)))
return failure(); return failure();
if (transfers->pipelineHostBufferBytes != 0) {
auto bytes = pim::checkedCast<int64_t>(
transfers->pipelineHostBufferBytes, funcOp,
"pipeline host transfer storage");
if (failed(bytes))
return failure();
funcOp->setAttr(kPipelineHostBufferBytesAttrName,
rewriter.getI64IntegerAttr(*bytes));
}
auto schedule = scheduleDeferredCommunication(funcOp, *transfers); auto schedule = scheduleDeferredCommunication(funcOp, *transfers);
if (failed(schedule) || failed(verifyPlannedCommunicationDeadlockFree(funcOp, transfers->stepCounts, *schedule))) if (failed(schedule) || failed(verifyPlannedCommunicationDeadlockFree(funcOp, transfers->stepCounts, *schedule)))
return funcOp.emitOpError("phase 2 failed to schedule symbolic communication"); return funcOp.emitOpError("phase 2 failed to schedule symbolic communication");
@@ -9,6 +9,7 @@ struct SchedulingTarget;
mlir::LogicalResult realizeDeferredCommunication(mlir::func::FuncOp funcOp, mlir::LogicalResult realizeDeferredCommunication(mlir::func::FuncOp funcOp,
const ScheduledComputeMaterializationResult& materialization, const ScheduledComputeMaterializationResult& materialization,
const SchedulingTarget& target); const SchedulingTarget& target,
size_t pipelineStages = 1);
} // namespace onnx_mlir::spatial } // namespace onnx_mlir::spatial
@@ -11,7 +11,7 @@ using namespace mlir;
namespace { namespace {
using TransferEmissionSignature = using TransferEmissionSignature =
std::tuple<ScheduledInfo*, Value, Type, bool, bool, bool>; std::tuple<ScheduledInfo*, Value, Type, bool, bool, bool, bool>;
static TransferEmissionSignature getTransferEmissionSignature( static TransferEmissionSignature getTransferEmissionSignature(
const ExternalTransferFamily& family) { const ExternalTransferFamily& family) {
@@ -21,7 +21,8 @@ static TransferEmissionSignature getTransferEmissionSignature(
family.requirement->publicationFragmentType, family.requirement->publicationFragmentType,
family.requirement->graphLanes.has_value(), family.requirement->graphLanes.has_value(),
family.requirement->producerProjection.has_value(), family.requirement->producerProjection.has_value(),
producer->scheduled->isBatch()}; producer->scheduled->isBatch(),
family.hostRouted};
} }
struct StreamThreshold { struct StreamThreshold {
@@ -5,6 +5,7 @@
#include "DeferredProjectionAnalysis.hpp" #include "DeferredProjectionAnalysis.hpp"
#include "DeferredTransferPlanning.hpp" #include "DeferredTransferPlanning.hpp"
#include "src/Accelerators/PIM/Common/PimCommon.hpp" #include "src/Accelerators/PIM/Common/PimCommon.hpp"
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
namespace onnx_mlir::spatial { namespace onnx_mlir::spatial {
using namespace mlir; using namespace mlir;
@@ -28,7 +29,12 @@ static FailureOr<unsigned> getStepIndex(
static LogicalResult collectScheduledOperations( static LogicalResult collectScheduledOperations(
const ScheduledComputeMaterializationResult &materialization, const ScheduledComputeMaterializationResult &materialization,
DeferredTransferPlan &plan) { DeferredTransferPlan &plan,
size_t pipelineStageCount,
size_t processorCount) {
if (pipelineStageCount == 0 || processorCount % pipelineStageCount != 0)
return failure();
size_t stageSize = processorCount / pipelineStageCount;
unsigned nextStream = 0; unsigned nextStream = 0;
for (const ScheduledMaterializationRecord &record : for (const ScheduledMaterializationRecord &record :
materialization.materializedSchedules) { materialization.materializedSchedules) {
@@ -46,8 +52,13 @@ static LogicalResult collectScheduledOperations(
if (llvm::any_of(info.stepAnchors, if (llvm::any_of(info.stepAnchors,
[](Operation *anchor) { return !anchor; })) [](Operation *anchor) { return !anchor; }))
return op.emitOpError("phase 2 scheduled step anchor is missing"); return op.emitOpError("phase 2 scheduled step anchor is missing");
for (size_t core : record.cpus) for (size_t core : record.cpus) {
if (core >= processorCount)
return op.emitOpError("phase 2 scheduled core is outside the target");
info.cores.push_back(core); info.cores.push_back(core);
if (pipelineStageCount > 1)
info.pipelineStages.push_back(core / stageSize);
}
for (size_t lane = 0; lane < info.cores.size(); ++lane) for (size_t lane = 0; lane < info.cores.size(); ++lane)
info.streamIds.push_back(nextStream++); info.streamIds.push_back(nextStream++);
plan.scheduled.push_back(std::move(info)); plan.scheduled.push_back(std::move(info));
@@ -308,17 +319,22 @@ static LogicalResult buildRequirementFamilies(DeferredTransferPlan& plan,
return success(); return success();
} }
static void buildAvailabilityFamilies(DeferredExchangePlan& exchange, uint64_t& nextChannel) { static LogicalResult buildAvailabilityFamilies(
DeferredTransferPlan &plan,
DeferredExchangePlan& exchange,
uint64_t& nextChannel,
DenseMap<int64_t, DenseMap<int64_t, unsigned>>& eventRegistersByTarget) {
enum class Availability { Local, Direct, Host };
for (RequirementFamily& requirement : exchange.requirements) { for (RequirementFamily& requirement : exchange.requirements) {
for (LaneInterval interval : requirement.targetLanes.intervals()) { for (LaneInterval interval : requirement.targetLanes.intervals()) {
unsigned runBegin = interval.begin; unsigned runBegin = interval.begin;
bool runLocal = false; Availability runAvailability = Availability::Local;
bool haveRun = false; bool haveRun = false;
auto flush = [&](unsigned end) { auto flush = [&](unsigned end) -> LogicalResult {
if (!haveRun || runBegin == end) if (!haveRun || runBegin == end)
return; return success();
LaneSet lanes = LaneSet::range(runBegin, end); LaneSet lanes = LaneSet::range(runBegin, end);
if (runLocal) { if (runAvailability == Availability::Local) {
exchange.local.push_back({&requirement, lanes}); exchange.local.push_back({&requirement, lanes});
} }
else { else {
@@ -339,25 +355,72 @@ static void buildAvailabilityFamilies(DeferredExchangePlan& exchange, uint64_t&
family.sourceCores = StaticIntSequence::uniform(requirement.producer->core, count); family.sourceCores = StaticIntSequence::uniform(requirement.producer->core, count);
family.targetCores = StaticIntSequence::fromValues(targetCores); family.targetCores = StaticIntSequence::fromValues(targetCores);
family.channelIds = StaticIntSequence::affine(nextChannel, 1, count); family.channelIds = StaticIntSequence::affine(nextChannel, 1, count);
family.hostRouted = runAvailability == Availability::Host;
if (family.hostRouted) {
SmallVector<int64_t> eventRegisters;
for (int64_t targetCore : targetCores) {
auto &registers = eventRegistersByTarget[targetCore];
auto it = registers.try_emplace(
requirement.producer->core, registers.size()).first;
if (it->second >= kPimEventRegisterCount)
return exchange.deferred.emitOpError(
"pipeline host transfer requires more event registers than the target core provides");
eventRegisters.push_back(it->second);
}
family.eventRegisters = StaticIntSequence::fromValues(
eventRegisters);
auto fragmentType = dyn_cast<ShapedType>(
requirement.publicationFragmentType);
auto fragmentBytes = fragmentType
? pim::getCheckedShapedTypeSizeInBytes(
fragmentType, exchange.deferred,
"pipeline host transfer fragment")
: FailureOr<uint64_t>(failure());
if (failed(fragmentBytes))
return failure();
auto bytes = pim::checkedMul<size_t>(
count, static_cast<size_t>(*fragmentBytes), exchange.deferred,
"pipeline host transfer storage");
if (failed(bytes))
return failure();
family.hostOffsets = StaticIntSequence::affine(
plan.pipelineHostBufferBytes, *fragmentBytes, count);
auto endOffset = pim::checkedAdd<size_t>(
plan.pipelineHostBufferBytes, *bytes, exchange.deferred,
"pipeline host transfer storage");
if (failed(endOffset))
return failure();
plan.pipelineHostBufferBytes = *endOffset;
}
nextChannel += count; nextChannel += count;
exchange.externalTransferCount += count; exchange.externalTransferCount += count;
exchange.external.push_back(std::move(family)); exchange.external.push_back(std::move(family));
} }
return success();
}; };
for (unsigned lane = interval.begin; lane < interval.end; ++lane) { for (unsigned lane = interval.begin; lane < interval.end; ++lane) {
unsigned sourceStream = requirement.producer->scheduled->streamIds[requirement.producer->scheduledLane]; unsigned sourceStream = requirement.producer->scheduled->streamIds[requirement.producer->scheduledLane];
bool local = bool local =
sourceStream == exchange.target->streamIds[lane] && requirement.producer->step < exchange.consumerStep; sourceStream == exchange.target->streamIds[lane] && requirement.producer->step < exchange.consumerStep;
if (haveRun && local != runLocal) { bool crossStage = !exchange.target->pipelineStages.empty()
flush(lane); && requirement.producer->scheduled->pipelineStages[
requirement.producer->scheduledLane]
!= exchange.target->pipelineStages[lane];
Availability availability = local ? Availability::Local
: crossStage ? Availability::Host : Availability::Direct;
if (haveRun && availability != runAvailability) {
if (failed(flush(lane)))
return failure();
runBegin = lane; runBegin = lane;
} }
runLocal = local; runAvailability = availability;
haveRun = true; haveRun = true;
} }
flush(interval.end); if (failed(flush(interval.end)))
return failure();
} }
} }
return success();
} }
static LogicalResult buildExchanges(func::FuncOp funcOp, DeferredTransferPlan& plan) { static LogicalResult buildExchanges(func::FuncOp funcOp, DeferredTransferPlan& plan) {
@@ -368,6 +431,7 @@ static LogicalResult buildExchanges(func::FuncOp funcOp, DeferredTransferPlan& p
funcOp.walk([&](SpatDeferredCommunicationOp op) { deferredOps.push_back(op); }); funcOp.walk([&](SpatDeferredCommunicationOp op) { deferredOps.push_back(op); });
GraphBatchPublicationCache publicationCache; GraphBatchPublicationCache publicationCache;
uint64_t nextChannel = 0; uint64_t nextChannel = 0;
DenseMap<int64_t, DenseMap<int64_t, unsigned>> eventRegistersByTarget;
for (SpatDeferredCommunicationOp deferred : deferredOps) { for (SpatDeferredCommunicationOp deferred : deferredOps) {
Operation* targetOp = deferred->getParentOfType<SpatScheduledCompute>(); Operation* targetOp = deferred->getParentOfType<SpatScheduledCompute>();
if (!targetOp) if (!targetOp)
@@ -387,7 +451,9 @@ static LogicalResult buildExchanges(func::FuncOp funcOp, DeferredTransferPlan& p
exchange->program = std::move(*program); exchange->program = std::move(*program);
if (failed(buildRequirementFamilies(plan, *exchange, publicationCache))) if (failed(buildRequirementFamilies(plan, *exchange, publicationCache)))
return failure(); return failure();
buildAvailabilityFamilies(*exchange, nextChannel); if (failed(buildAvailabilityFamilies(
plan, *exchange, nextChannel, eventRegistersByTarget)))
return failure();
plan.exchanges.push_back(std::move(exchange)); plan.exchanges.push_back(std::move(exchange));
} }
return success(); return success();
@@ -464,9 +530,12 @@ retargetBlueprint(DeferredTransferPlan& plan, SpatBlueprintOp blueprint, GraphBa
FailureOr<DeferredTransferPlan> buildDeferredTransferPlan( FailureOr<DeferredTransferPlan> buildDeferredTransferPlan(
func::FuncOp funcOp, func::FuncOp funcOp,
const ScheduledComputeMaterializationResult &materialization) { const ScheduledComputeMaterializationResult &materialization,
size_t pipelineStages,
size_t processorCount) {
DeferredTransferPlan plan; DeferredTransferPlan plan;
if (failed(collectScheduledOperations(materialization, plan)) if (failed(collectScheduledOperations(
materialization, plan, pipelineStages, processorCount))
|| failed(collectProducedValues(materialization, plan)) || failed(collectProducedValues(materialization, plan))
|| failed(buildExchanges(funcOp, plan))) || failed(buildExchanges(funcOp, plan)))
return failure(); return failure();
@@ -13,11 +13,14 @@ struct DeferredTransferPlan {
llvm::DenseMap<int64_t, llvm::SmallVector<ProducedValue*>> producedByGraph; llvm::DenseMap<int64_t, llvm::SmallVector<ProducedValue*>> producedByGraph;
llvm::SmallVector<std::unique_ptr<DeferredExchangePlan>> exchanges; llvm::SmallVector<std::unique_ptr<DeferredExchangePlan>> exchanges;
llvm::SmallVector<unsigned> stepCounts; llvm::SmallVector<unsigned> stepCounts;
size_t pipelineHostBufferBytes = 0;
}; };
mlir::FailureOr<DeferredTransferPlan> mlir::FailureOr<DeferredTransferPlan>
buildDeferredTransferPlan(mlir::func::FuncOp funcOp, buildDeferredTransferPlan(mlir::func::FuncOp funcOp,
const ScheduledComputeMaterializationResult &materialization); const ScheduledComputeMaterializationResult &materialization,
size_t pipelineStages,
size_t processorCount);
mlir::LogicalResult retargetDeferredPublications(mlir::func::FuncOp funcOp, DeferredTransferPlan& plan); mlir::LogicalResult retargetDeferredPublications(mlir::func::FuncOp funcOp, DeferredTransferPlan& plan);
@@ -3,12 +3,15 @@
#include "DeferredCommunicationRealization.hpp" #include "DeferredCommunicationRealization.hpp"
#include "ScheduledComputeReport.hpp" #include "ScheduledComputeReport.hpp"
#include "ScheduledComputeVerification.hpp" #include "ScheduledComputeVerification.hpp"
#include "Scheduling/PipelineScheduling.hpp"
#include "SpatialDataflowCsvExporter.hpp" #include "SpatialDataflowCsvExporter.hpp"
#include "src/Accelerators/PIM/Common/PimCommon.hpp" #include "src/Accelerators/PIM/Common/PimCommon.hpp"
#include "src/Accelerators/PIM/Common/Support/DebugDump.hpp" #include "src/Accelerators/PIM/Common/Support/DebugDump.hpp"
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Passes/Analyses/ONNXToSpatialVerifier.hpp" #include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Passes/Analyses/ONNXToSpatialVerifier.hpp"
#include "src/Accelerators/PIM/Passes/PIMPasses.h" #include "src/Accelerators/PIM/Passes/PIMPasses.h"
#include <limits>
using namespace mlir; using namespace mlir;
namespace onnx_mlir { namespace onnx_mlir {
@@ -31,14 +34,47 @@ static FailureOr<func::FuncOp> requireEntry(ModuleOp moduleOp) {
return *entry; return *entry;
} }
static SchedulingTarget getPipelineSchedulingTarget(
const SchedulingTarget& physicalTarget, size_t pipelineStages) {
if (pipelineStages == 1)
return physicalTarget;
SchedulingTarget schedulingTarget = physicalTarget;
schedulingTarget.processorCount = physicalTarget.processorCount / pipelineStages;
schedulingTarget.residentWeightCapacity = checkedMultiply(
physicalTarget.residentWeightCapacity, pipelineStages);
schedulingTarget.interProcessorLatencyNs.assign(
schedulingTarget.processorCount * schedulingTarget.processorCount, 0);
Cost latencySum = 0;
size_t pairCount = 0;
for (size_t source = 0; source < schedulingTarget.processorCount; ++source)
for (size_t destination = 0;
destination < schedulingTarget.processorCount; ++destination) {
Cost latency = physicalTarget.getInterProcessorLatencyNs(
source, destination);
schedulingTarget.interProcessorLatencyNs[
source * schedulingTarget.processorCount + destination] = latency;
if (source != destination) {
latencySum = checkedAdd(latencySum, latency);
++pairCount;
}
}
schedulingTarget.averageInterProcessorLatencyNs = pairCount == 0
? 0
: (latencySum + pairCount - 1) / pairCount;
return schedulingTarget;
}
struct ScheduleAndRealizeSpatialPass final struct ScheduleAndRealizeSpatialPass final
: PassWrapper<ScheduleAndRealizeSpatialPass, OperationPass<ModuleOp>> { : PassWrapper<ScheduleAndRealizeSpatialPass, OperationPass<ModuleOp>> {
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(ScheduleAndRealizeSpatialPass) MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(ScheduleAndRealizeSpatialPass)
ScheduleAndRealizeSpatialPass() = default; ScheduleAndRealizeSpatialPass() = default;
ScheduleAndRealizeSpatialPass(const SchedulingTarget& target, ScheduleAndRealizeSpatialPass(const SchedulingTarget& target,
SpatialDataflowExportStage exportStage) SpatialDataflowExportStage exportStage,
: target(target), exportStage(exportStage), hasTarget(true) {} size_t pipelineStages)
: target(target), exportStage(exportStage),
pipelineStages(pipelineStages), hasTarget(true) {}
StringRef getArgument() const override { return "schedule-and-realize-spatial"; } StringRef getArgument() const override { return "schedule-and-realize-spatial"; }
StringRef getDescription() const override { StringRef getDescription() const override {
@@ -52,6 +88,13 @@ struct ScheduleAndRealizeSpatialPass final
signalPassFailure(); signalPassFailure();
return; return;
} }
if (pipelineStages == 0 || target.processorCount % pipelineStages != 0
|| target.residentWeightCapacity
> std::numeric_limits<size_t>::max() / pipelineStages) {
moduleOp.emitError("ScheduleAndRealizeSpatial requires valid pipeline stages and resource counts");
signalPassFailure();
return;
}
auto entry = requireEntry(moduleOp); auto entry = requireEntry(moduleOp);
if (failed(entry)) { if (failed(entry)) {
signalPassFailure(); signalPassFailure();
@@ -59,8 +102,31 @@ struct ScheduleAndRealizeSpatialPass final
} }
func::FuncOp entryFunc = *entry; func::FuncOp entryFunc = *entry;
MergeSchedulingAnalysis analysis(entryFunc, target); SchedulingTarget schedulingTarget = getPipelineSchedulingTarget(
MergeScheduleResult schedule = std::move(analysis.getResult()); target, pipelineStages);
ComputeGraph scheduledGraph;
MergeScheduleResult schedule;
for (;;) {
MergeSchedulingAnalysis analysis(
entryFunc, schedulingTarget,
pipelineStages > 1 ? target.processorCount : 0);
scheduledGraph = analysis.getGraph();
schedule = std::move(analysis.getResult());
std::string pipelineError;
if (succeeded(applyPipelineScheduling(
scheduledGraph, schedule, pipelineStages, target, pipelineError)))
break;
std::string splitError;
if (pipelineStages == 1
|| failed(splitPipelineWorkload(
scheduledGraph, schedule, pipelineStages, target, splitError))) {
if (!splitError.empty())
pipelineError = splitError;
moduleOp.emitError() << pipelineError;
signalPassFailure();
return;
}
}
PatternRewriter rewriter(moduleOp.getContext()); PatternRewriter rewriter(moduleOp.getContext());
FailureOr<ScheduledComputeMaterializationResult> materialization = FailureOr<ScheduledComputeMaterializationResult> materialization =
materializeScheduledCompute(entryFunc, schedule, rewriter); materializeScheduledCompute(entryFunc, schedule, rewriter);
@@ -94,7 +160,8 @@ struct ScheduleAndRealizeSpatialPass final
moduleOp, entryFunc, schedule, materializationResult.peftClassPlans, moduleOp, entryFunc, schedule, materializationResult.peftClassPlans,
materializationResult.materializedSchedules); materializationResult.materializedSchedules);
if (failed(realizeDeferredCommunication(entryFunc, materializationResult, target))) { if (failed(realizeDeferredCommunication(
entryFunc, materializationResult, target, pipelineStages))) {
moduleOp.emitError("Spatial communication realization failed"); moduleOp.emitError("Spatial communication realization failed");
signalPassFailure(); signalPassFailure();
return; return;
@@ -126,6 +193,7 @@ struct ScheduleAndRealizeSpatialPass final
private: private:
SchedulingTarget target; SchedulingTarget target;
SpatialDataflowExportStage exportStage = SpatialDataflowExportStage::None; SpatialDataflowExportStage exportStage = SpatialDataflowExportStage::None;
size_t pipelineStages = 1;
bool hasTarget = false; bool hasTarget = false;
}; };
@@ -136,8 +204,11 @@ std::unique_ptr<Pass> createScheduleAndRealizeSpatialPass() {
} }
std::unique_ptr<Pass> createScheduleAndRealizeSpatialPass( std::unique_ptr<Pass> createScheduleAndRealizeSpatialPass(
const SchedulingTarget& target, SpatialDataflowExportStage exportStage) { const SchedulingTarget& target,
return std::make_unique<ScheduleAndRealizeSpatialPass>(target, exportStage); SpatialDataflowExportStage exportStage,
size_t pipelineStages) {
return std::make_unique<ScheduleAndRealizeSpatialPass>(
target, exportStage, pipelineStages);
} }
} // namespace spatial } // namespace spatial
@@ -0,0 +1,16 @@
#pragma once
#include "ScheduledComputeMaterialization.hpp"
#include "Scheduling/MergeSchedulingAnalysis.hpp"
#include <memory>
#include <optional>
namespace onnx_mlir::spatial {
struct ScheduledSpatialState {
std::optional<MergeScheduleResult> logicalSchedule;
std::optional<ScheduledComputeMaterializationResult> materialization;
};
} // namespace onnx_mlir::spatial
@@ -772,6 +772,11 @@ std::vector<ComputeGraphEdge> aggregateEdges(llvm::ArrayRef<ComputeGraphEdge> ed
} // namespace } // namespace
TransferCost getTransferCostFromBytes(Cost bytes,
const SchedulingTarget& target) {
return SchedulerCostModel {target}.getTransferCostFromBytes(bytes);
}
uint64_t countComputeBodyInstructions(Region& body) { uint64_t countComputeBodyInstructions(Region& body) {
uint64_t numOperations = 0; uint64_t numOperations = 0;
body.walk([&](Operation* op) { numOperations = checkedAdd(numOperations, static_cast<uint64_t>(1)); }); body.walk([&](Operation* op) { numOperations = checkedAdd(numOperations, static_cast<uint64_t>(1)); });
@@ -875,9 +880,13 @@ ResidentWeightSet getComputeInstanceResidentWeights(const ComputeInstance& insta
return tiled; return tiled;
} }
ComputeGraph buildComputeGraph(Operation* entryOp, const SchedulingTarget& target) { ComputeGraph buildComputeGraph(Operation* entryOp,
const SchedulingTarget& target,
size_t computePartitionCount) {
ComputeGraph graph; ComputeGraph graph;
SchedulerCostModel costModel {target}; SchedulerCostModel costModel {target};
if (computePartitionCount == 0)
computePartitionCount = target.processorCount;
for (Region& region : entryOp->getRegions()) { for (Region& region : entryOp->getRegions()) {
for (Block& block : region) { for (Block& block : region) {
@@ -898,10 +907,10 @@ ComputeGraph buildComputeGraph(Operation* entryOp, const SchedulingTarget& targe
if (isUsedAsWeightOnly(batch.getOperation())) if (isUsedAsWeightOnly(batch.getOperation()))
continue; continue;
size_t chunkCount = size_t chunkCount =
getBatchChunkTargetCount(batch, target.processorCount); getBatchChunkTargetCount(batch, computePartitionCount);
for (size_t chunkIndex = 0; chunkIndex < chunkCount; ++chunkIndex) { for (size_t chunkIndex = 0; chunkIndex < chunkCount; ++chunkIndex) {
ComputeInstance instance = getBatchChunkForIndex( ComputeInstance instance = getBatchChunkForIndex(
batch, chunkIndex, target.processorCount); batch, chunkIndex, computePartitionCount);
size_t index = graph.nodes.size(); size_t index = graph.nodes.size();
graph.nodes.push_back({instance, graph.nodes.push_back({instance,
getComputeInstanceCost(instance, target), getComputeInstanceCost(instance, target),
@@ -920,7 +929,7 @@ ComputeGraph buildComputeGraph(Operation* entryOp, const SchedulingTarget& targe
for (Value input : inputs) { for (Value input : inputs) {
for (const ProducerValueRef& producerRef : for (const ProducerValueRef& producerRef :
collectProducerValueRefs(input, node.instance, collectProducerValueRefs(input, node.instance,
target.processorCount)) { computePartitionCount)) {
auto producerIt = graph.instanceToIndex.find(producerRef.instance); auto producerIt = graph.instanceToIndex.find(producerRef.instance);
if (producerIt == graph.instanceToIndex.end()) if (producerIt == graph.instanceToIndex.end())
continue; continue;
@@ -61,9 +61,13 @@ struct ComputeGraph {
llvm::DenseMap<ComputeInstance, size_t> instanceToIndex; llvm::DenseMap<ComputeInstance, size_t> instanceToIndex;
}; };
ComputeGraph buildComputeGraph(mlir::Operation* entryOp, const SchedulingTarget& target); ComputeGraph buildComputeGraph(mlir::Operation* entryOp,
const SchedulingTarget& target,
size_t computePartitionCount = 0);
bool verifyAcyclic(const ComputeGraph& graph); bool verifyAcyclic(const ComputeGraph& graph);
TransferCost getTransferCostFromBytes(Cost bytes,
const SchedulingTarget& target);
uint64_t countComputeBodyInstructions(mlir::Region& body); uint64_t countComputeBodyInstructions(mlir::Region& body);
uint64_t countComputeBodyOperationInstances(mlir::Region& body); uint64_t countComputeBodyOperationInstances(mlir::Region& body);
Cost getComputeInstanceCost(const ComputeInstance& instance, const SchedulingTarget& target); Cost getComputeInstanceCost(const ComputeInstance& instance, const SchedulingTarget& target);
@@ -89,13 +89,14 @@ void verifySchedule(const ComputeGraph& graph,
} // namespace } // namespace
MergeSchedulingAnalysis::MergeSchedulingAnalysis(mlir::Operation* op, MergeSchedulingAnalysis::MergeSchedulingAnalysis(mlir::Operation* op,
const SchedulingTarget& schedulingTarget) const SchedulingTarget& schedulingTarget,
: entryOp(op), target(schedulingTarget) { size_t partitionCount)
: entryOp(op), target(schedulingTarget), computePartitionCount(partitionCount) {
result = run(); result = run();
} }
MergeScheduleResult MergeSchedulingAnalysis::run() { MergeScheduleResult MergeSchedulingAnalysis::run() {
ComputeGraph graph = buildComputeGraph(entryOp, target); graph = buildComputeGraph(entryOp, target, computePartitionCount);
if (!verifyAcyclic(graph)) if (!verifyAcyclic(graph))
llvm::report_fatal_error("merge scheduling: compute graph is cyclic"); llvm::report_fatal_error("merge scheduling: compute graph is cyclic");
@@ -3,6 +3,7 @@
#include "mlir/IR/Operation.h" #include "mlir/IR/Operation.h"
#include "MergeSchedule.hpp" #include "MergeSchedule.hpp"
#include "ComputeGraph.hpp"
#include "SchedulingTarget.hpp" #include "SchedulingTarget.hpp"
namespace onnx_mlir { namespace onnx_mlir {
@@ -10,12 +11,17 @@ namespace spatial {
class MergeSchedulingAnalysis { class MergeSchedulingAnalysis {
public: public:
MergeSchedulingAnalysis(mlir::Operation* op, const SchedulingTarget& target); MergeSchedulingAnalysis(mlir::Operation* op,
const SchedulingTarget& target,
size_t computePartitionCount = 0);
MergeScheduleResult& getResult() { return result; } MergeScheduleResult& getResult() { return result; }
const ComputeGraph& getGraph() const { return graph; }
private: private:
mlir::Operation* entryOp = nullptr; mlir::Operation* entryOp = nullptr;
const SchedulingTarget& target; const SchedulingTarget& target;
size_t computePartitionCount = 0;
ComputeGraph graph;
MergeScheduleResult result; MergeScheduleResult result;
MergeScheduleResult run(); MergeScheduleResult run();
@@ -0,0 +1,26 @@
#pragma once
#include "mlir/Support/LogicalResult.h"
#include <cstddef>
#include <string>
#include "ComputeGraph.hpp"
#include "MergeSchedule.hpp"
#include "SchedulingTarget.hpp"
namespace onnx_mlir::spatial {
mlir::LogicalResult applyPipelineScheduling(const ComputeGraph& graph,
MergeScheduleResult& schedule,
size_t pipelineStages,
const SchedulingTarget& physicalTarget,
std::string& error);
mlir::LogicalResult splitPipelineWorkload(const ComputeGraph& graph,
const MergeScheduleResult& schedule,
size_t pipelineStages,
const SchedulingTarget& physicalTarget,
std::string& error);
} // namespace onnx_mlir::spatial
+41 -2
View File
@@ -550,7 +550,8 @@ def SpatChannelSendOp : SpatOp<"channel_send", []> {
); );
let assemblyFormat = [{ let assemblyFormat = [{
$input `channel` $channelId `from` $sourceCoreId `to` $targetCoreId attr-dict `:` type($input) $input `channel` $channelId `from` $sourceCoreId `to` $targetCoreId
attr-dict `:` type($input)
}]; }];
} }
@@ -568,7 +569,45 @@ def SpatChannelReceiveOp : SpatOp<"channel_receive", []> {
); );
let assemblyFormat = [{ let assemblyFormat = [{
`channel` $channelId `from` $sourceCoreId `to` $targetCoreId attr-dict `:` type($output) `channel` $channelId `from` $sourceCoreId `to` $targetCoreId
attr-dict `:` type($output)
}];
}
def SpatHostStoreSyncOp : SpatOp<"host_store_sync", []> {
let summary = "Store a tensor to host memory and signal its consumer";
let arguments = (ins
Index:$sourceCoreId,
Index:$targetCoreId,
Index:$hostOffset,
Index:$eventRegister,
SpatTensor:$input
);
let assemblyFormat = [{
$input `from` $sourceCoreId `to` $targetCoreId
`host_offset` $hostOffset `event` $eventRegister attr-dict `:` type($input)
}];
}
def SpatHostWaitLoadOp : SpatOp<"host_wait_load", []> {
let summary = "Wait for a producer and load its tensor from host memory";
let arguments = (ins
Index:$sourceCoreId,
Index:$targetCoreId,
Index:$hostOffset,
Index:$eventRegister
);
let results = (outs
SpatTensor:$output
);
let assemblyFormat = [{
`from` $sourceCoreId `to` $targetCoreId
`host_offset` $hostOffset `event` $eventRegister attr-dict `:` type($output)
}]; }];
} }
@@ -0,0 +1,24 @@
#ifndef SPATIAL_LAYOUT_INTERFACE_TD
#define SPATIAL_LAYOUT_INTERFACE_TD
include "mlir/IR/OpBase.td"
def SpatialLayoutCapabilityInterface : OpInterface<"SpatialLayoutCapabilityInterface"> {
let description = [{
Contract implemented by logical Spatial planning operations that expose
their legal physical layout alternatives to the Spatial planner.
}];
let methods = [
InterfaceMethod<
"Return legal physical layout alternatives for this operation and its current operand layouts.",
"::llvm::SmallVector<::onnx_mlir::spatial::LayoutAlternative>",
"getLayoutAlternatives",
(ins "const ::onnx_mlir::spatial::SpatialTargetInfo &":$target,
"::llvm::ArrayRef<::onnx_mlir::spatial::PhysicalLayout>":$operandLayouts)>
];
let cppNamespace = "::onnx_mlir::spatial";
}
#endif
@@ -0,0 +1,37 @@
#pragma once
#include <cstddef>
#include <cstdint>
namespace onnx_mlir::spatial {
struct MatrixUnitShape {
size_t rows = 128;
size_t columns = 128;
};
enum class ConvLoweringStrategy : uint8_t {
Auto,
Legacy,
Depthwise,
PackedIm2Col,
StreamedPatch,
StreamedPacked,
OutputChannelTiled,
InputKTiled,
Tiled2D,
};
struct SpatialTargetInfo {
MatrixUnitShape matrixShape;
size_t matrixUnitsPerProcessor = 64;
size_t processorCount = 1;
size_t vectorWidth = 16;
uint64_t convIm2colMaxElements = 1ull << 20;
uint64_t convStreamChunkPositions = 1024;
ConvLoweringStrategy convLoweringStrategy = ConvLoweringStrategy::Auto;
bool useExperimentalConvImplementation = false;
};
} // namespace onnx_mlir::spatial
+63
View File
@@ -0,0 +1,63 @@
#pragma once
#include "mlir/Pass/Pass.h"
#include <cstddef>
#include <memory>
#include <string>
namespace onnx_mlir {
namespace spatial {
struct SchedulingTarget;
struct ScheduledSpatialState;
struct SpatialTargetInfo;
std::unique_ptr<mlir::Pass> createScheduleSpatialGraphPass();
std::unique_ptr<mlir::Pass> createScheduleSpatialGraphPass(const SchedulingTarget& target);
std::unique_ptr<mlir::Pass> createScheduleSpatialGraphPass(
const SchedulingTarget& target,
std::shared_ptr<ScheduledSpatialState> state);
std::unique_ptr<mlir::Pass> createVerifyScheduledSpatialPass();
std::unique_ptr<mlir::Pass> createVerifyScheduledSpatialPass(
std::shared_ptr<ScheduledSpatialState> state);
std::unique_ptr<mlir::Pass> createRealizeSpatialCommunicationPass();
std::unique_ptr<mlir::Pass> createRealizeSpatialCommunicationPass(
const SchedulingTarget& target,
std::shared_ptr<ScheduledSpatialState> state);
std::unique_ptr<mlir::Pass> createVerifyRealizedSpatialPass();
std::unique_ptr<mlir::Pass> createVerifyRealizedSpatialPass(
std::shared_ptr<ScheduledSpatialState> state);
}
std::unique_ptr<mlir::Pass> createONNXToSpatialPass();
std::unique_ptr<mlir::Pass> createONNXToSpatialPass(const spatial::SpatialTargetInfo& target);
std::unique_ptr<mlir::Pass> createSpatialLayoutPlanningPass();
std::unique_ptr<mlir::Pass> createSpatialLayoutPlanningPass(const spatial::SpatialTargetInfo& target);
std::unique_ptr<mlir::Pass> createLowerSpatialPlansPass();
std::unique_ptr<mlir::Pass> createLowerSpatialPlansPass(const spatial::SpatialTargetInfo& target);
std::unique_ptr<mlir::Pass> createSpatialToPimPass();
std::unique_ptr<mlir::Pass> createPimBufferizationPreparationPass();
std::unique_ptr<mlir::Pass> createPimOneShotBufferizationPass();
std::unique_ptr<mlir::Pass> createPimMemoryNormalizationPass();
std::unique_ptr<mlir::Pass> createPimBufferizationVerificationPass();
std::unique_ptr<mlir::Pass> createTrivialGraphComputeMergePass();
std::unique_ptr<mlir::Pass> createTrivialGraphComputeMergePass(
size_t residentWeightCapacity);
std::unique_ptr<mlir::Pass> createPimHostConstantFoldingPass();
std::unique_ptr<mlir::Pass> createPimInstructionSelectionPass();
std::unique_ptr<mlir::Pass> createPimLocalMemoryPlanningPass();
std::unique_ptr<mlir::Pass> createPimVerificationPass();
std::unique_ptr<mlir::Pass> createEmitPimCodePass();
std::unique_ptr<mlir::Pass> createMessagePass(std::string message);
} // namespace onnx_mlir
+2 -1
View File
@@ -16,7 +16,8 @@ enum class SpatialDataflowExportStage;
std::unique_ptr<mlir::Pass> createScheduleAndRealizeSpatialPass(); std::unique_ptr<mlir::Pass> createScheduleAndRealizeSpatialPass();
std::unique_ptr<mlir::Pass> createScheduleAndRealizeSpatialPass( std::unique_ptr<mlir::Pass> createScheduleAndRealizeSpatialPass(
const SchedulingTarget& target, const SchedulingTarget& target,
SpatialDataflowExportStage exportStage); SpatialDataflowExportStage exportStage,
size_t pipelineStages = 1);
} }
std::unique_ptr<mlir::Pass> createONNXToSpatialPass(); std::unique_ptr<mlir::Pass> createONNXToSpatialPass();
+92
View File
@@ -1,8 +1,10 @@
#include <cassert> #include <cassert>
#include <cstdlib> #include <cstdlib>
#include <string>
#include <vector> #include <vector>
#include "src/Accelerators/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/PeftScheduler.hpp" #include "src/Accelerators/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/PeftScheduler.hpp"
#include "src/Accelerators/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/PipelineScheduling.hpp"
using namespace onnx_mlir::spatial; using namespace onnx_mlir::spatial;
@@ -54,5 +56,95 @@ int main() {
0, 0,
}; };
assert(mapLogicalProcessorsToPhysicalCores(logicalTrafficFlits, alreadyPlaced) == std::vector<size_t>({0, 1, 2})); assert(mapLogicalProcessorsToPhysicalCores(logicalTrafficFlits, alreadyPlaced) == std::vector<size_t>({0, 1, 2}));
ComputeGraph graph;
graph.successors.resize(6);
graph.predecessors.resize(6);
graph.successors[1].push_back({2, TransferCost {.fixed = 1, .networkFlits = 1}});
graph.predecessors[2].push_back({1, TransferCost {.fixed = 1, .networkFlits = 1}});
const Cost costs[] = {6, 4, 6, 4, 8, 8};
for (uint32_t task = 0; task < 6; ++task) {
ComputeInstance instance {nullptr, task, 1};
ResidentWeight weight;
weight.opaqueLane = task;
graph.nodes.push_back({instance, costs[task], {weight}, task});
graph.instanceToIndex[instance] = task;
}
MergeScheduleResult pipelineSchedule;
pipelineSchedule.processorCount = 2;
pipelineSchedule.dominanceOrderCompute.reserve(graph.nodes.size());
for (size_t task = 0; task < graph.nodes.size(); ++task) {
const ComputeInstance& instance = graph.nodes[task].instance;
pipelineSchedule.dominanceOrderCompute.push_back(instance);
size_t cpu = task < 4 ? 0 : 1;
pipelineSchedule.computeToCpuMap[instance] = cpu;
pipelineSchedule.computeToCpuSlotMap[instance] = task < 4 ? task : task - 4;
pipelineSchedule.computeToAestMap[instance] = task;
}
SchedulingTarget physical = fast;
physical.processorCount = 4;
physical.residentWeightCapacity = 2;
physical.interProcessorLatencyNs = {
0, 3, 3, 3,
3, 0, 3, 3,
3, 3, 0, 3,
3, 3, 3, 0,
};
std::string pipelineError;
assert(mlir::succeeded(applyPipelineScheduling(
graph, pipelineSchedule, 2, physical, pipelineError)));
assert(pipelineSchedule.processorCount == 4);
assert(pipelineSchedule.computeToCpuMap.lookup(graph.nodes[0].instance) == 0);
assert(pipelineSchedule.computeToCpuMap.lookup(graph.nodes[1].instance) == 0);
assert(pipelineSchedule.computeToCpuMap.lookup(graph.nodes[2].instance) == 2);
assert(pipelineSchedule.computeToCpuMap.lookup(graph.nodes[3].instance) == 2);
assert(pipelineSchedule.computeToCpuMap.lookup(graph.nodes[4].instance) == 1);
assert(pipelineSchedule.computeToCpuMap.lookup(graph.nodes[5].instance) == 3);
assert(pipelineSchedule.computeToAestMap.lookup(graph.nodes[2].instance)
>= pipelineSchedule.computeToAestMap.lookup(graph.nodes[1].instance)
+ graph.nodes[1].cost + 4);
assert(pipelineSchedule.equivalentClass.empty());
ComputeGraph communicationGraph;
communicationGraph.successors.resize(5);
communicationGraph.predecessors.resize(5);
communicationGraph.successors[4].push_back(
{3, TransferCost {.fixed = 0, .networkFlits = 1}});
communicationGraph.predecessors[3].push_back(
{4, TransferCost {.fixed = 0, .networkFlits = 1}});
const Cost communicationCosts[] = {6, 4, 6, 4, 1};
MergeScheduleResult communicationSchedule;
communicationSchedule.processorCount = 2;
for (uint32_t task = 0; task < 5; ++task) {
ComputeInstance instance {nullptr, task, 1};
ResidentWeight weight;
weight.opaqueLane = task;
communicationGraph.nodes.push_back(
{instance, communicationCosts[task], {weight}, task});
communicationGraph.instanceToIndex[instance] = task;
communicationSchedule.dominanceOrderCompute.push_back(instance);
size_t cpu = task < 4 ? 0 : 1;
communicationSchedule.computeToCpuMap[instance] = cpu;
communicationSchedule.computeToCpuSlotMap[instance] = task < 4 ? task : 0;
communicationSchedule.computeToAestMap[instance] = task;
}
SchedulingTarget fastPipeline = physical;
fastPipeline.residentWeightCapacity = 4;
MergeScheduleResult fastCommunicationSchedule = communicationSchedule;
assert(mlir::succeeded(applyPipelineScheduling(
communicationGraph, fastCommunicationSchedule, 2, fastPipeline, pipelineError)));
assert(fastCommunicationSchedule.computeToCpuMap.lookup(
communicationGraph.nodes[2].instance) == 2);
SchedulingTarget slowPipeline = fastPipeline;
slowPipeline.averageInterProcessorLatencyNs = 10;
MergeScheduleResult slowCommunicationSchedule = communicationSchedule;
assert(mlir::succeeded(applyPipelineScheduling(
communicationGraph, slowCommunicationSchedule, 2, slowPipeline, pipelineError)));
assert(slowCommunicationSchedule.computeToCpuMap.lookup(
communicationGraph.nodes[2].instance) < 2);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
@@ -169,11 +169,11 @@ Motifs are not inferred from rendered geometry. For each operation graph the too
## Viewer and API ## Viewer and API
Except for spatial4, the viewer initially fetches an aggregate operation graph unless the browser has a saved view choice. Sigma renders the graph with WebGL. Controls cover report/view/metric selection, text and tensor search, mapping filters, self edges, relayout, fitting, and motif selection. Raw nodes and edges have their own selection details and never call aggregate-only detail or mapping endpoints. Mapping panels remain available for operation aggregate edges. The viewer initially fetches an aggregate operation graph, so a previously selected raw view cannot make a new report exceed the display safety cap before the first render. Browser responses disable caching so HTML and JavaScript from different tool revisions cannot be mixed. Sigma renders the graph with WebGL. Controls cover report/view selection, text and tensor search, mapping filters, self edges, relayout, fitting, and motif selection. Edges use a fixed width. Raw nodes and edges have their own selection details and never call aggregate-only detail or mapping endpoints. Nodes without an SSA name fall back to their source identity. Mapping panels remain available for operation aggregate edges.
Operation expansion is a deterministic projection of the source graph. Expand selected, Expand all operations, Collapse selected operation, and Collapse all rebuild the complete display from the current expanded-operation set. An expanded operation's raw nodes replace its aggregate node, including isolated raw nodes. An aggregate edge is retained only when both endpoint operations are collapsed; otherwise its raw edges replace it with endpoints calculated from the complete expansion set. Expansion order therefore cannot leave stale endpoints or simultaneous aggregate/raw representations. Operation expansion is a deterministic projection of the source graph. Expand selected, Expand all operations, Collapse selected operation, and Collapse all rebuild the complete display from the current expanded-operation set. An expanded operation's raw nodes replace its aggregate node, including isolated raw nodes. An aggregate edge is retained only when both endpoint operations are collapsed; otherwise its raw edges replace it with endpoints calculated from the complete expansion set. Expansion order therefore cannot leave stale endpoints or simultaneous aggregate/raw representations.
Operation ranks are calculated from the complete operation graph, including isolated operations. Collapsed nodes use stable operation anchors. Expanded nodes are sorted by lane and node ID; lane numbers form perpendicular rows, equal lanes align across adjacent operations, and lane-less nodes use distinct deterministic scalar rows. This same model is used by Rerun layout, so unrelated operations do not jump during expansion or collapse and disconnected nodes never pile up at `(0, 0)`. Operation ranks are calculated from the complete operation graph, including isolated operations, and every view flows top-to-bottom. A collapsed operation reserves one displayed row. Expanded nodes use compact rows for distinct lanes in numeric order, followed by deterministic lane-less rows, so sparse lane numbers create no empty geometric space. Raw nodes sharing a lane receive centered deterministic horizontal offsets. Operation and raw views use stable server coordinates, and `Reset layout` restores them; core and node-kind views use ELK, and `Rerun layout` runs it again. Operation labels use the first SSA name; Sigma's collision grid thins normal labels, and a label is skipped when its measured screen rectangle intersects another visible node. Selected or hovered nodes keep the original white bubble with black text. Selecting a motif frames its member nodes on the first click. Aggregate-node sizes remain bounded.
Browser dependencies are pinned in one place, `static/index.html`: Browser dependencies are pinned in one place, `static/index.html`:
@@ -206,8 +206,10 @@ Raw pages default to 100 and cannot exceed 500. Subgraph depth cannot exceed fiv
## Performance behavior ## Performance behavior
- CSV readers stream rows and insert them in bounded batches. - CSV readers stream rows and insert them in bounded batches.
- Temporary SQLite staging and bulk joins resolve endpoints; ingestion performs no per-edge node query. - Temporary SQLite staging tables are function-scoped and dropped immediately; bulk joins resolve endpoints without per-edge node queries.
- Secondary indexes are created after raw insertion. - CSVs without additional columns use a constant empty JSON representation.
- Ingestion closes its write connection before derived indexes, aggregation, motifs, and diagnostics reopen the database.
- Secondary indexes are created after raw insertion and focus on serving and aggregation queries.
- Raw edges are never retained as a Python object graph or loaded into NetworkX. - Raw edges are never retained as a Python object graph or loaded into NetworkX.
- Only aggregate operation nodes/edges enter NetworkX. - Only aggregate operation nodes/edges enter NetworkX.
- Mapping statistics are grouped in SQL; exact stencil comparison streams one operation pair. - Mapping statistics are grouped in SQL; exact stencil comparison streams one operation pair.
@@ -1,6 +1,7 @@
README.md README.md
pyproject.toml pyproject.toml
raptor_graph_explorer/__init__.py raptor_graph_explorer/__init__.py
raptor_graph_explorer/__main__.py
raptor_graph_explorer/aggregate.py raptor_graph_explorer/aggregate.py
raptor_graph_explorer/api.py raptor_graph_explorer/api.py
raptor_graph_explorer/cli.py raptor_graph_explorer/cli.py
+2
View File
@@ -20,3 +20,5 @@ networks/**/*.csv
!networks/full_net/validation_results.csv !networks/full_net/validation_results.csv
!networks/pimcomp_models/validation_results.csv !networks/pimcomp_models/validation_results.csv
!networks/pimcomp_models/results.csv !networks/pimcomp_models/results.csv
!networks/pimcomp_models/validation_results.csv
!operations/validation_results.csv
@@ -1,5 +1,3 @@
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,1.465778,7.985074,477298145.040001,1597904071.120000,raptor,5.45 vgg8,1.521060,7.985074,486309111.040001,1597904071.120000,raptor,5.25
resnet18,28.099952,58.853733,8781611766.119984,13983168748.119974,raptor,2.09 resnet18,33.552733,58.853613,9702508727.119982,13983148468.119974,raptor,1.75
resnet34,45.781486,91.607980,14962940227.679951,22722922369.680016,raptor,2.00
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 1.465778 1.521060 7.985074 477298145.040001 486309111.040001 1597904071.120000 raptor 5.45 5.25
3 resnet18 28.099952 33.552733 58.853733 58.853613 8781611766.119984 9702508727.119982 13983168748.119974 13983148468.119974 raptor 2.09 1.75
resnet34 45.781486 91.607980 14962940227.679951 22722922369.680016 raptor 2.00
googlenet 13.371204 62.923463 6117835798.919991 14547526780.240000 raptor 4.71
@@ -0,0 +1,6 @@
Operation,Result,Compile,Host mem,Cores mem,Cores,Xbars,Latency,Power,Energy
vgg8-mnist-reconstructed,PASS,1.009 s,1.37 MiB,3.14 MiB,141,761,1.465778 ms,325.627854 mW,477298145.040001 pJ
resnet18-v1-7,PASS,11.548 s,9.89 MiB,40.24 MiB,168,7676,28.099952 ms,312.513408 mW,8781611766.119984 pJ
resnet34-v1-7,PASS,28.495 s,9.90 MiB,48.89 MiB,168,15292,45.781486 ms,326.833870 mW,14962940227.679951 pJ
googlenet-12-latency,PASS,6.573 s,10.74 MiB,22.41 MiB,168,7176,13.371204 ms,457.538139 mW,6117835798.919991 pJ
yolo11n-latency,FAIL,58.572 s,82.55 MiB,185.68 MiB,168,6484,885.264931 ms,189.218985 mW,167508931321.001465 pJ
1 Operation Result Compile Host mem Cores mem Cores Xbars Latency Power Energy
2 vgg8-mnist-reconstructed PASS 1.009 s 1.37 MiB 3.14 MiB 141 761 1.465778 ms 325.627854 mW 477298145.040001 pJ
3 resnet18-v1-7 PASS 11.548 s 9.89 MiB 40.24 MiB 168 7676 28.099952 ms 312.513408 mW 8781611766.119984 pJ
4 resnet34-v1-7 PASS 28.495 s 9.90 MiB 48.89 MiB 168 15292 45.781486 ms 326.833870 mW 14962940227.679951 pJ
5 googlenet-12-latency PASS 6.573 s 10.74 MiB 22.41 MiB 168 7176 13.371204 ms 457.538139 mW 6117835798.919991 pJ
6 yolo11n-latency FAIL 58.572 s 82.55 MiB 185.68 MiB 168 6484 885.264931 ms 189.218985 mW 167508931321.001465 pJ
+177 -168
View File
@@ -1,169 +1,178 @@
Operation,Result,Compile,Host mem,Cores mem,Cores,Xbars,Latency,Power,Energy Operation,Result,Compile,Host mem,Cores mem,Cores,Xbars,Latency,Power,Energy
add/after_gemm,PASS,0.063 s,0.01 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP add/after_gemm,PASS,-,0.01 MiB,0.01 MiB,5,4,0.007784 ms,104.703618 mW,815012.960000 pJ
add/basic,PASS,0.057 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP add/basic,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
add/broadcast_row,PASS,0.057 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP add/broadcast_row,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
add/channel_broadcast_1024,PASS,0.061 s,0.02 MiB,0.01 MiB,1,0,SKIP,SKIP,SKIP add/channel_broadcast_1024,PASS,-,0.02 MiB,0.01 MiB,1,0,0.006913 ms,78.118038 mW,540030.000000 pJ
add/leading_dimension_broadcast,PASS,0.057 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP add/leading_dimension_broadcast,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
concat/channel_axis,PASS,0.069 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP concat/channel_axis,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000457 ms,78.157549 mW,35718.000000 pJ
concat/negative_axis,PASS,0.061 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP concat/negative_axis,PASS,-,0.00 MiB,0.00 MiB,1,0,0.001043 ms,78.092042 mW,81450.000000 pJ
concat/three_inputs_channel_axis,PASS,0.058 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP concat/three_inputs_channel_axis,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000644 ms,78.149068 mW,50328.000000 pJ
conv/batch_2,PASS,0.066 s,0.00 MiB,0.00 MiB,2,2,SKIP,SKIP,SKIP conv/batch_2,PASS,-,0.00 MiB,0.00 MiB,2,2,0.013694 ms,82.623885 mW,1131451.480000 pJ
conv/batch_4_pointwise,PASS,0.065 s,0.00 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP conv/batch_4_pointwise,PASS,-,0.00 MiB,0.01 MiB,5,4,0.003932 ms,116.078576 mW,456420.960000 pJ
conv/depthwise_1024_channels,PASS,0.083 s,0.19 MiB,0.38 MiB,129,128,SKIP,SKIP,SKIP conv/conv_1x3x224x224_w64x3x7x7_b64,PASS,-,24.14 MiB,61.87 MiB,168,169,38.414551 ms,185.256473 mW,7116544212.120002 pJ
conv/depthwise_grouped,PASS,0.071 s,0.01 MiB,0.00 MiB,5,4,SKIP,SKIP,SKIP conv/depthwise_1024_channels,PASS,-,0.19 MiB,0.38 MiB,129,128,0.220751 ms,178.454307 mW,39393966.720000 pJ
conv/dilated_3x3,PASS,0.068 s,0.01 MiB,0.01 MiB,10,9,SKIP,SKIP,SKIP conv/depthwise_grouped,PASS,-,0.01 MiB,0.00 MiB,5,4,0.006234 ms,107.776541 mW,671878.960000 pJ
conv/dynamic,PASS,0.065 s,0.00 MiB,0.00 MiB,5,0,SKIP,SKIP,SKIP conv/dilated_3x3,PASS,-,0.01 MiB,0.01 MiB,10,9,0.008713 ms,118.767263 mW,1034819.160000 pJ
conv/explicit_padding,PASS,0.062 s,0.01 MiB,0.02 MiB,17,16,SKIP,SKIP,SKIP conv/dynamic,PASS,-,0.00 MiB,0.00 MiB,5,0,0.001835 ms,92.281199 mW,169336.000000 pJ
conv/grouped_many_groups,PASS,0.442 s,0.05 MiB,0.09 MiB,65,64,SKIP,SKIP,SKIP conv/explicit_padding,PASS,-,0.01 MiB,0.02 MiB,17,16,0.010007 ms,145.338047 mW,1454397.840000 pJ
conv/grouped_two_groups,PASS,0.060 s,0.00 MiB,0.00 MiB,3,2,SKIP,SKIP,SKIP conv/grouped_many_groups,PASS,-,0.05 MiB,0.09 MiB,65,64,0.181897 ms,142.207471 mW,25867112.360000 pJ
conv/huge_pointwise_1024,PASS,0.156 s,0.01 MiB,0.11 MiB,73,64,SKIP,SKIP,SKIP conv/grouped_two_groups,PASS,-,0.00 MiB,0.00 MiB,3,2,0.005361 ms,101.457653 mW,543914.480000 pJ
conv/huge_pointwise_1024_dynamic,PASS,0.079 s,8.04 MiB,12.61 MiB,168,0,SKIP,SKIP,SKIP conv/huge_pointwise_1024,PASS,-,0.01 MiB,0.11 MiB,73,64,0.015615 ms,249.545140 mW,3896647.360000 pJ
conv/kernel_3x3,PASS,0.062 s,0.01 MiB,0.01 MiB,10,9,SKIP,SKIP,SKIP conv/huge_pointwise_1024_dynamic,PASS,-,8.04 MiB,12.61 MiB,168,0,2.627964 ms,169.518697 mW,445489032.000000 pJ
conv/kernel_equals_input_spatial,PASS,0.064 s,0.00 MiB,0.00 MiB,2,2,SKIP,SKIP,SKIP conv/kernel_3x3,PASS,-,0.01 MiB,0.01 MiB,10,9,0.007186 ms,123.801859 mW,889640.160000 pJ
conv/large_input_channels_1x1,PASS,0.089 s,0.01 MiB,0.02 MiB,9,8,SKIP,SKIP,SKIP conv/kernel_equals_input_spatial,PASS,-,0.00 MiB,0.00 MiB,2,2,0.004639 ms,89.607562 mW,415689.480000 pJ
conv/large_output_channels_1x1,PASS,0.098 s,0.01 MiB,0.02 MiB,17,8,SKIP,SKIP,SKIP conv/large_input_channels_1x1,PASS,-,0.01 MiB,0.02 MiB,9,8,0.007648 ms,117.824519 mW,901121.920000 pJ
conv/large_spatial,PASS,0.069 s,0.01 MiB,0.04 MiB,37,36,SKIP,SKIP,SKIP conv/large_output_channels_1x1,PASS,-,0.01 MiB,0.02 MiB,17,8,0.008871 ms,128.442782 mW,1139415.920000 pJ
conv/multi_channel,PASS,0.066 s,0.00 MiB,0.00 MiB,4,3,SKIP,SKIP,SKIP conv/large_spatial,PASS,-,0.01 MiB,0.04 MiB,37,36,0.017018 ms,172.073372 mW,2928344.640000 pJ
conv/non_square_kernel_1x3,PASS,0.061 s,0.00 MiB,0.00 MiB,3,2,SKIP,SKIP,SKIP conv/multi_channel,PASS,-,0.00 MiB,0.00 MiB,4,3,0.006482 ms,105.683542 mW,685040.720000 pJ
conv/non_square_kernel_3x1,PASS,0.064 s,0.00 MiB,0.00 MiB,3,2,SKIP,SKIP,SKIP conv/non_square_kernel_1x3,PASS,-,0.00 MiB,0.00 MiB,3,2,0.006842 ms,99.349968 mW,679752.480000 pJ
conv/non_uniform_stride,PASS,0.062 s,0.00 MiB,0.00 MiB,4,3,SKIP,SKIP,SKIP conv/non_square_kernel_3x1,PASS,-,0.00 MiB,0.00 MiB,3,2,0.013484 ms,95.889683 mW,1292976.480000 pJ
conv/pointwise_1x1,PASS,0.059 s,0.00 MiB,0.00 MiB,1,1,SKIP,SKIP,SKIP conv/non_uniform_stride,PASS,-,0.00 MiB,0.00 MiB,4,3,0.007601 ms,104.048772 mW,790874.720000 pJ
conv/pointwise_tiled_chain,PASS,0.604 s,0.01 MiB,0.04 MiB,20,80,SKIP,SKIP,SKIP conv/output_channel_grouping_minimal,PASS,-,0.10 MiB,0.34 MiB,131,128,0.258453 ms,170.730913 mW,44125916.720000 pJ
conv/real_asymmetric_padding,PASS,0.060 s,0.01 MiB,0.03 MiB,29,28,SKIP,SKIP,SKIP conv/pointwise_1x1,PASS,-,0.00 MiB,0.00 MiB,1,1,0.012303 ms,80.244188 mW,987244.240000 pJ
conv/relu_conv_store,PASS,0.091 s,0.16 MiB,0.67 MiB,168,184,SKIP,SKIP,SKIP conv/pointwise_tiled_chain,PASS,-,0.01 MiB,0.04 MiB,20,80,0.041886 ms,153.880896 mW,6445455.200000 pJ
conv/same_lower_3x3,PASS,0.078 s,0.01 MiB,0.02 MiB,26,25,SKIP,SKIP,SKIP conv/real_asymmetric_padding,PASS,-,0.01 MiB,0.03 MiB,29,28,0.014457 ms,153.669968 mW,2221606.720000 pJ
conv/same_padding_3x3,PASS,0.070 s,0.01 MiB,0.02 MiB,26,25,SKIP,SKIP,SKIP conv/relu_conv_store,PASS,-,0.16 MiB,0.67 MiB,168,184,0.562898 ms,183.084489 mW,103057892.800000 pJ
conv/simple,PASS,0.064 s,0.00 MiB,0.00 MiB,1,1,SKIP,SKIP,SKIP conv/same_lower_3x3,PASS,-,0.01 MiB,0.02 MiB,26,25,0.013331 ms,166.154752 mW,2215009.000000 pJ
conv/stride_2,PASS,0.064 s,0.01 MiB,0.00 MiB,5,4,SKIP,SKIP,SKIP conv/same_padding_3x3,PASS,-,0.01 MiB,0.02 MiB,26,25,0.013331 ms,166.154752 mW,2215009.000000 pJ
conv/with_bias_3x3,PASS,0.067 s,0.00 MiB,0.01 MiB,4,3,SKIP,SKIP,SKIP conv/simple,PASS,-,0.00 MiB,0.00 MiB,1,1,0.004301 ms,83.833583 mW,360568.240000 pJ
conv/with_constant,PASS,0.070 s,0.00 MiB,0.00 MiB,1,1,SKIP,SKIP,SKIP conv/strategy_depthwise_16,PASS,-,0.06 MiB,0.35 MiB,168,168,0.335115 ms,197.936467 mW,66331479.080000 pJ
conv/without_kernel_shape_attr,PASS,0.069 s,0.01 MiB,0.01 MiB,10,9,SKIP,SKIP,SKIP conv/strategy_input_k_tiled,PASS,-,0.08 MiB,0.27 MiB,109,108,0.353736 ms,170.812713 mW,60422605.920000 pJ
conv/yolo11n_depthwise_head,PASS,1.482 s,8.66 MiB,34.24 MiB,168,255,SKIP,SKIP,SKIP conv/strategy_output_channel_tiled,PASS,-,0.03 MiB,0.16 MiB,74,72,0.091463 ms,155.743189 mW,14244739.280000 pJ
conv/yolo11n_heavy,PASS,0.431 s,4.82 MiB,19.10 MiB,161,800,SKIP,SKIP,SKIP conv/strategy_streamed_packed,PASS,-,3.34 MiB,7.89 MiB,168,168,9.353833 ms,179.858301 mW,1682364509.560000 pJ
conv/yolo11n_stem,PASS,0.783 s,12.86 MiB,37.59 MiB,168,488,SKIP,SKIP,SKIP conv/strategy_streamed_patch,PASS,-,0.34 MiB,1.32 MiB,168,168,1.904655 ms,181.910449 mW,346476645.640000 pJ
div/after_gemm,PASS,0.072 s,0.01 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP conv/strategy_tiled_2d,PASS,-,0.11 MiB,0.44 MiB,168,168,0.415594 ms,182.127047 mW,75690907.840000 pJ
div/basic,PASS,0.061 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP conv/stride_2,PASS,-,0.01 MiB,0.00 MiB,5,4,0.005237 ms,110.780019 mW,580154.960000 pJ
div/channel_broadcast_1024,PASS,0.066 s,0.02 MiB,0.01 MiB,1,0,SKIP,SKIP,SKIP conv/with_bias_3x3,PASS,-,0.00 MiB,0.01 MiB,4,3,0.007452 ms,104.162738 mW,776220.720000 pJ
div/leading_dimension_broadcast,PASS,0.059 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP conv/with_constant,PASS,-,0.00 MiB,0.00 MiB,1,1,0.006622 ms,81.738182 mW,541270.240000 pJ
div/runtime_scalar_rhs,PASS,0.056 s,0.02 MiB,0.01 MiB,1,0,SKIP,SKIP,SKIP conv/without_kernel_shape_attr,PASS,-,0.01 MiB,0.01 MiB,10,9,0.007186 ms,123.801859 mW,889640.160000 pJ
div/scalar_constant,PASS,0.073 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP conv/yolo11n_depthwise_head,PASS,-,8.66 MiB,34.24 MiB,168,255,42.701404 ms,200.519161 mW,8562449708.000010 pJ
gather/3d_input_axis1,PASS,0.060 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP conv/yolo11n_heavy,PASS,-,4.82 MiB,19.10 MiB,161,800,8.535404 ms,350.863768 mW,2994764012.000010 pJ
gather/axis0_matrix_indices,PASS,0.056 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP conv/yolo11n_stem,PASS,-,12.86 MiB,37.59 MiB,168,488,14.239985 ms,301.233376 mW,4289558753.000010 pJ
gather/axis1,PASS,0.058 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP div/after_gemm,PASS,-,0.01 MiB,0.01 MiB,5,4,0.007784 ms,104.703618 mW,815012.960000 pJ
gather/negative_axis,PASS,0.058 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP div/basic,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
gather/negative_indices,PASS,0.063 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP div/channel_broadcast_1024,PASS,-,0.02 MiB,0.01 MiB,1,0,0.006913 ms,78.118038 mW,540030.000000 pJ
gemm/alpha_beta,PASS,0.067 s,0.01 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP div/leading_dimension_broadcast,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
gemm/bias_rank2_broadcast,PASS,0.060 s,0.00 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP div/runtime_scalar_rhs,PASS,-,0.02 MiB,0.01 MiB,1,0,0.006913 ms,78.118038 mW,540030.000000 pJ
gemm/dynamic,PASS,0.064 s,0.00 MiB,0.00 MiB,5,0,SKIP,SKIP,SKIP div/scalar_constant,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
gemm/dynamic_alpha,PASS,0.062 s,0.00 MiB,0.00 MiB,5,0,SKIP,SKIP,SKIP gather/3d_input_axis1,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000589 ms,78.081494 mW,45990.000000 pJ
gemm/dynamic_beta,PASS,0.060 s,0.00 MiB,0.00 MiB,5,0,SKIP,SKIP,SKIP gather/axis0_matrix_indices,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000697 ms,78.068867 mW,54414.000000 pJ
gemm/dynamic_bias,PASS,0.058 s,0.00 MiB,0.00 MiB,5,0,SKIP,SKIP,SKIP gather/axis1,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000801 ms,78.059925 mW,62526.000000 pJ
gemm/dynamic_bias_alpha_beta,PASS,0.067 s,0.00 MiB,0.00 MiB,5,0,SKIP,SKIP,SKIP gather/negative_axis,PASS,-,0.00 MiB,0.00 MiB,1,0,0.001437 ms,78.033403 mW,112134.000000 pJ
gemm/dynamic_transB,PASS,0.062 s,0.00 MiB,0.00 MiB,5,0,SKIP,SKIP,SKIP gather/negative_indices,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000376 ms,78.127660 mW,29376.000000 pJ
gemm/huge_1024,PASS,0.147 s,0.01 MiB,0.10 MiB,73,64,SKIP,SKIP,SKIP gemm/alpha_beta,PASS,-,0.01 MiB,0.01 MiB,5,4,0.007456 ms,105.272125 mW,784908.960000 pJ
gemm/large,PASS,0.068 s,0.02 MiB,0.03 MiB,17,16,SKIP,SKIP,SKIP gemm/bias_rank2_broadcast,PASS,-,0.00 MiB,0.01 MiB,5,4,0.007072 ms,105.979208 mW,749484.960000 pJ
gemm/large_k_small_n,PASS,0.095 s,0.01 MiB,0.01 MiB,9,8,SKIP,SKIP,SKIP gemm/dynamic,PASS,-,0.00 MiB,0.00 MiB,5,0,0.002421 ms,91.480793 mW,221475.000000 pJ
gemm/non_square,PASS,0.063 s,0.00 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP gemm/dynamic_alpha,PASS,-,0.00 MiB,0.00 MiB,5,0,0.003262 ms,91.415696 mW,298198.000000 pJ
gemm/scalar_bias,PASS,0.060 s,0.00 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP gemm/dynamic_beta,PASS,-,0.00 MiB,0.00 MiB,5,0,0.004365 ms,91.316151 mW,398595.000000 pJ
gemm/simple,PASS,0.072 s,0.03 MiB,0.08 MiB,42,40,SKIP,SKIP,SKIP gemm/dynamic_bias,PASS,-,0.00 MiB,0.00 MiB,5,0,0.002665 ms,91.445779 mW,243703.000000 pJ
gemm/small,PASS,0.065 s,0.00 MiB,0.00 MiB,2,2,SKIP,SKIP,SKIP gemm/dynamic_bias_alpha_beta,PASS,-,0.00 MiB,0.00 MiB,5,0,0.005629 ms,91.279268 mW,513811.000000 pJ
gemm/small_k_large_n,PASS,0.097 s,0.01 MiB,0.02 MiB,17,8,SKIP,SKIP,SKIP gemm/dynamic_transB,PASS,-,0.00 MiB,0.00 MiB,5,0,0.001301 ms,91.378171 mW,118883.000000 pJ
gemm/transA,PASS,0.064 s,0.00 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP gemm/huge_1024,PASS,-,0.01 MiB,0.10 MiB,73,64,0.017522 ms,215.037402 mW,3767885.360000 pJ
gemm/transA_transB,PASS,0.069 s,0.00 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP gemm/large,PASS,-,0.02 MiB,0.03 MiB,17,16,0.011229 ms,140.152181 mW,1573768.840000 pJ
gemm/transB,PASS,0.062 s,0.00 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP gemm/large_k_small_n,PASS,-,0.01 MiB,0.01 MiB,9,8,0.004748 ms,133.481449 mW,633769.920000 pJ
gemm/transB_with_bias,PASS,0.055 s,0.01 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP gemm/non_square,PASS,-,0.00 MiB,0.01 MiB,5,4,0.003527 ms,118.958310 mW,419565.960000 pJ
gemm/with_bias,PASS,0.067 s,0.01 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP gemm/scalar_bias,PASS,-,0.00 MiB,0.01 MiB,5,4,0.007072 ms,105.979208 mW,749484.960000 pJ
gemv/constant,PASS,0.064 s,0.00 MiB,0.00 MiB,0,0,SKIP,SKIP,SKIP gemm/simple,PASS,-,0.03 MiB,0.08 MiB,42,40,0.021640 ms,151.774196 mW,3284393.600000 pJ
gemv/simple,PASS,0.069 s,0.00 MiB,0.01 MiB,6,4,SKIP,SKIP,SKIP gemm/small,PASS,-,0.00 MiB,0.00 MiB,2,2,0.004420 ms,90.144000 mW,398436.480000 pJ
gemv/with_heterogeneous_constant,PASS,0.066 s,0.00 MiB,0.01 MiB,6,4,SKIP,SKIP,SKIP gemm/small_k_large_n,PASS,-,0.01 MiB,0.02 MiB,17,8,0.007962 ms,131.005014 mW,1043061.920000 pJ
gemv/with_homogeneous_constant,PASS,0.070 s,0.00 MiB,0.01 MiB,6,4,SKIP,SKIP,SKIP gemm/transA,PASS,-,0.00 MiB,0.01 MiB,5,4,0.005762 ms,109.140743 mW,628868.960000 pJ
gemv/with_scalar_constant,PASS,0.070 s,0.00 MiB,0.01 MiB,6,4,SKIP,SKIP,SKIP gemm/transA_transB,PASS,-,0.00 MiB,0.01 MiB,5,4,0.005762 ms,109.140743 mW,628868.960000 pJ
matmul/basic,PASS,0.062 s,0.00 MiB,0.00 MiB,2,2,SKIP,SKIP,SKIP gemm/transB,PASS,-,0.00 MiB,0.01 MiB,5,4,0.003527 ms,118.958310 mW,419565.960000 pJ
matmul/batched_3d,PASS,0.066 s,0.00 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP gemm/transB_with_bias,PASS,-,0.01 MiB,0.01 MiB,5,4,0.005046 ms,110.546762 mW,557818.960000 pJ
matmul/batched_3d_dynamic,PASS,0.057 s,0.00 MiB,0.00 MiB,4,0,SKIP,SKIP,SKIP gemm/with_bias,PASS,-,0.01 MiB,0.01 MiB,5,4,0.005562 ms,108.767882 mW,604966.960000 pJ
matmul/batched_left_constant,PASS,0.070 s,0.00 MiB,0.02 MiB,9,8,SKIP,SKIP,SKIP gemv/constant,PASS,-,0.00 MiB,0.00 MiB,0,0,0.000000 ms,2.000000 mW,0.000000 pJ
matmul/batched_lhs_broadcast,PASS,0.063 s,0.00 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP gemv/simple,PASS,-,0.00 MiB,0.01 MiB,6,4,0.005160 ms,111.150380 mW,573535.960000 pJ
matmul/batched_rhs_broadcast,PASS,0.062 s,0.00 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP gemv/with_heterogeneous_constant,PASS,-,0.00 MiB,0.01 MiB,6,4,0.005549 ms,109.816536 mW,609371.960000 pJ
matmul/dynamic,PASS,0.060 s,0.00 MiB,0.00 MiB,5,0,SKIP,SKIP,SKIP gemv/with_homogeneous_constant,PASS,-,0.00 MiB,0.01 MiB,6,4,0.005549 ms,109.816536 mW,609371.960000 pJ
matmul/huge_1024,PASS,0.145 s,0.01 MiB,0.10 MiB,73,64,SKIP,SKIP,SKIP gemv/with_scalar_constant,PASS,-,0.00 MiB,0.01 MiB,6,4,0.005549 ms,109.816536 mW,609371.960000 pJ
matmul/left_constant,PASS,0.051 s,0.00 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP matmul/basic,PASS,-,0.00 MiB,0.00 MiB,2,2,0.004420 ms,90.144000 mW,398436.480000 pJ
matmul/matrix_vector,PASS,0.095 s,0.52 MiB,0.78 MiB,168,173,SKIP,SKIP,SKIP matmul/batched_3d,PASS,-,0.00 MiB,0.01 MiB,5,4,0.005958 ms,108.588949 mW,646972.960000 pJ
matmul/vector_matrix,PASS,0.087 s,0.01 MiB,0.01 MiB,9,8,SKIP,SKIP,SKIP matmul/batched_3d_dynamic,PASS,-,0.00 MiB,0.00 MiB,4,0,0.001822 ms,92.192645 mW,167975.000000 pJ
matmul/yolo_attention,PASS,0.385 s,1.02 MiB,43.44 MiB,168,0,SKIP,SKIP,SKIP matmul/batched_left_constant,PASS,-,0.00 MiB,0.02 MiB,9,8,0.008822 ms,114.385164 mW,1009105.920000 pJ
mul/after_conv,PASS,0.055 s,0.00 MiB,0.00 MiB,4,3,SKIP,SKIP,SKIP matmul/batched_lhs_broadcast,PASS,-,0.00 MiB,0.01 MiB,5,4,0.005681 ms,109.389361 mW,621440.960000 pJ
mul/basic,PASS,0.055 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP matmul/batched_rhs_broadcast,PASS,-,0.00 MiB,0.01 MiB,5,4,0.005958 ms,108.588949 mW,646972.960000 pJ
mul/channel_broadcast_1024,PASS,0.058 s,0.02 MiB,0.01 MiB,1,0,SKIP,SKIP,SKIP matmul/dynamic,PASS,-,0.00 MiB,0.00 MiB,5,0,0.001621 ms,91.421962 mW,148195.000000 pJ
mul/leading_dimension_broadcast,PASS,0.059 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP matmul/huge_1024,PASS,-,0.01 MiB,0.10 MiB,73,64,0.017522 ms,215.037402 mW,3767885.360000 pJ
mul/scalar_constant,PASS,0.056 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP matmul/left_constant,PASS,-,0.00 MiB,0.01 MiB,5,4,0.005853 ms,108.861944 mW,637168.960000 pJ
pool/avg_basic,PASS,0.063 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP matmul/matrix_vector,PASS,-,0.52 MiB,0.78 MiB,168,173,0.384660 ms,202.131271 mW,77751814.880000 pJ
pool/avg_ceil_mode,PASS,0.063 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP matmul/vector_matrix,PASS,-,0.01 MiB,0.01 MiB,9,8,0.007409 ms,118.680243 mW,879301.920000 pJ
pool/avg_explicit_padding,PASS,0.061 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP matmul/yolo_attention,PASS,-,1.02 MiB,43.44 MiB,168,0,8.151445 ms,170.003707 mW,1385775865.000000 pJ
pool/avg_include_pad,PASS,0.049 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP mul/after_conv,PASS,-,0.00 MiB,0.00 MiB,4,3,0.005453 ms,107.639046 mW,586955.720000 pJ
pool/avg_large_channels,PASS,0.059 s,0.04 MiB,0.02 MiB,1,0,SKIP,SKIP,SKIP mul/after_conv_scalar_constant,PASS,-,0.00 MiB,0.00 MiB,4,3,0.005453 ms,107.639046 mW,586955.720000 pJ
pool/avg_non_uniform_stride,PASS,0.066 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP mul/basic,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
pool/avg_real_asymmetric_padding,PASS,0.055 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP mul/channel_broadcast_1024,PASS,-,0.02 MiB,0.01 MiB,1,0,0.006913 ms,78.118038 mW,540030.000000 pJ
pool/max_after_conv,PASS,0.069 s,0.00 MiB,0.00 MiB,5,4,SKIP,SKIP,SKIP mul/leading_dimension_broadcast,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
pool/max_basic,PASS,0.064 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP mul/scalar_constant,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
pool/max_ceil_mode,PASS,0.058 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP pool/avg_basic,PASS,-,0.00 MiB,0.00 MiB,1,0,0.011939 ms,78.022112 mW,931506.000000 pJ
pool/max_global_style_kernel_equals_input,PASS,0.063 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP pool/avg_ceil_mode,PASS,-,0.00 MiB,0.00 MiB,1,0,0.004359 ms,78.033035 mW,340146.000000 pJ
pool/max_non_square_kernel,PASS,0.067 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP pool/avg_explicit_padding,PASS,-,0.00 MiB,0.00 MiB,1,0,0.008822 ms,78.027205 mW,688356.000000 pJ
pool/max_real_asymmetric_padding,PASS,0.063 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP pool/avg_include_pad,PASS,-,0.00 MiB,0.00 MiB,1,0,0.008506 ms,78.016929 mW,663612.000000 pJ
pool/max_same_upper,PASS,0.061 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP pool/avg_large_channels,PASS,-,0.04 MiB,0.02 MiB,1,0,0.235874 ms,78.004172 mW,18399156.000000 pJ
pool/max_stride2_multichannel,PASS,0.076 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP pool/avg_non_uniform_stride,PASS,-,0.00 MiB,0.00 MiB,1,0,0.014513 ms,78.016537 mW,1132254.000000 pJ
reduce_mean/4d_spatial,PASS,0.058 s,0.00 MiB,0.00 MiB,3,0,SKIP,SKIP,SKIP pool/avg_real_asymmetric_padding,PASS,-,0.00 MiB,0.00 MiB,1,0,0.025206 ms,78.024756 mW,1966692.000000 pJ
reduce_mean/4d_spatial_keepdims_0,PASS,0.068 s,0.00 MiB,0.00 MiB,4,0,SKIP,SKIP,SKIP pool/max_after_conv,PASS,-,0.00 MiB,0.00 MiB,5,4,0.012215 ms,99.115019 mW,1210689.960000 pJ
reduce_mean/after_conv,PASS,0.067 s,0.00 MiB,0.00 MiB,5,3,SKIP,SKIP,SKIP pool/max_basic,PASS,-,0.00 MiB,0.00 MiB,1,0,0.004160 ms,78.063462 mW,324744.000000 pJ
reduce_mean/all_axes_keepdims_0,PASS,0.057 s,0.00 MiB,0.00 MiB,2,0,SKIP,SKIP,SKIP pool/max_ceil_mode,PASS,-,0.00 MiB,0.00 MiB,1,0,0.001940 ms,78.074227 mW,151464.000000 pJ
reduce_mean/all_axes_keepdims_1,PASS,0.053 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP pool/max_global_style_kernel_equals_input,PASS,-,0.00 MiB,0.00 MiB,1,0,0.008443 ms,78.008528 mW,658626.000000 pJ
reduce_mean/basic,PASS,0.058 s,0.00 MiB,0.00 MiB,4,0,SKIP,SKIP,SKIP pool/max_non_square_kernel,PASS,-,0.00 MiB,0.00 MiB,1,0,0.013626 ms,78.017613 mW,1063068.000000 pJ
reduce_mean/channel_axis_nchw,PASS,0.063 s,0.03 MiB,0.02 MiB,4,0,SKIP,SKIP,SKIP pool/max_real_asymmetric_padding,PASS,-,0.00 MiB,0.00 MiB,1,0,0.010444 ms,78.034470 mW,814992.000000 pJ
reduce_mean/keepdims_0,PASS,0.064 s,0.00 MiB,0.00 MiB,5,0,SKIP,SKIP,SKIP pool/max_same_upper,PASS,-,0.00 MiB,0.00 MiB,1,0,0.008010 ms,78.035955 mW,625068.000000 pJ
reduce_mean/large_dimension_1024,PASS,0.066 s,0.01 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP pool/max_stride2_multichannel,PASS,-,0.00 MiB,0.00 MiB,1,0,0.015987 ms,78.018015 mW,1247274.000000 pJ
reduce_mean/legacy_axes_1_2_keepdims_1,PASS,0.069 s,0.00 MiB,0.00 MiB,2,0,SKIP,SKIP,SKIP reduce_mean/4d_spatial,PASS,-,0.00 MiB,0.00 MiB,3,0,0.000321 ms,92.448598 mW,29676.000000 pJ
reduce_mean/legacy_axis1_keepdims_0,PASS,0.067 s,0.00 MiB,0.00 MiB,9,0,SKIP,SKIP,SKIP reduce_mean/4d_spatial_keepdims_0,PASS,-,0.00 MiB,0.00 MiB,4,0,0.000655 ms,94.352672 mW,61801.000000 pJ
reduce_mean/legacy_axis1_keepdims_1,PASS,0.057 s,0.00 MiB,0.00 MiB,8,0,SKIP,SKIP,SKIP reduce_mean/after_conv,PASS,-,0.00 MiB,0.00 MiB,5,3,0.005342 ms,106.951089 mW,571332.720000 pJ
reduce_mean/legacy_empty_axes_noop,PASS,0.058 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP reduce_mean/all_axes_keepdims_0,PASS,-,0.00 MiB,0.00 MiB,2,0,0.000391 ms,79.237852 mW,30982.000000 pJ
reduce_mean/legacy_nchw_spatial,PASS,0.059 s,0.00 MiB,0.00 MiB,3,0,SKIP,SKIP,SKIP reduce_mean/all_axes_keepdims_1,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000221 ms,78.217195 mW,17286.000000 pJ
reduce_mean/legacy_negative_axis,PASS,0.052 s,0.00 MiB,0.00 MiB,6,0,SKIP,SKIP,SKIP reduce_mean/basic,PASS,-,0.00 MiB,0.00 MiB,4,0,0.000373 ms,93.514745 mW,34881.000000 pJ
reduce_mean/legacy_reduce_all_keepdims_1,PASS,0.057 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP reduce_mean/channel_axis_nchw,PASS,-,0.03 MiB,0.02 MiB,4,0,0.164926 ms,93.596631 mW,15436518.000000 pJ
reduce_mean/negative_axis,PASS,0.055 s,0.00 MiB,0.00 MiB,6,0,SKIP,SKIP,SKIP reduce_mean/keepdims_0,PASS,-,0.00 MiB,0.00 MiB,5,0,0.000748 ms,91.401070 mW,68368.000000 pJ
relu/4d,PASS,0.059 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP reduce_mean/large_dimension_1024,PASS,-,0.01 MiB,0.00 MiB,1,0,0.002785 ms,78.017235 mW,217278.000000 pJ
relu/after_conv,PASS,0.062 s,0.00 MiB,0.00 MiB,4,3,SKIP,SKIP,SKIP reduce_mean/legacy_axes_1_2_keepdims_1,PASS,-,0.00 MiB,0.00 MiB,2,0,0.000271 ms,79.354244 mW,21505.000000 pJ
relu/after_gemm,PASS,0.062 s,0.01 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP reduce_mean/legacy_axis1_keepdims_0,PASS,-,0.00 MiB,0.00 MiB,9,0,0.001986 ms,92.501511 mW,183708.000000 pJ
relu/basic,PASS,0.062 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP reduce_mean/legacy_axis1_keepdims_1,PASS,-,0.00 MiB,0.00 MiB,8,0,0.001373 ms,94.559359 mW,129830.000000 pJ
reshape/4d_to_2d_flatten,PASS,0.059 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP reduce_mean/legacy_empty_axes_noop,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000221 ms,78.217195 mW,17286.000000 pJ
reshape/infer_dim_minus_one,PASS,0.059 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP reduce_mean/legacy_nchw_spatial,PASS,-,0.00 MiB,0.00 MiB,3,0,0.000321 ms,92.448598 mW,29676.000000 pJ
reshape/same_rank,PASS,0.056 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP reduce_mean/legacy_negative_axis,PASS,-,0.00 MiB,0.00 MiB,6,0,0.000553 ms,93.520796 mW,51717.000000 pJ
reshape/zero_copies_input_dim,PASS,0.077 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP reduce_mean/legacy_reduce_all_keepdims_1,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000221 ms,78.217195 mW,17286.000000 pJ
resize/height_only,PASS,0.059 s,0.00 MiB,0.00 MiB,4,0,SKIP,SKIP,SKIP reduce_mean/negative_axis,PASS,-,0.00 MiB,0.00 MiB,6,0,0.000553 ms,93.520796 mW,51717.000000 pJ
resize/nearest_2x,PASS,0.066 s,0.00 MiB,0.00 MiB,4,0,SKIP,SKIP,SKIP relu/4d,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000521 ms,78.184261 mW,40734.000000 pJ
resize/nearest_downsample,PASS,0.062 s,0.00 MiB,0.00 MiB,2,0,SKIP,SKIP,SKIP relu/after_conv,PASS,-,0.00 MiB,0.00 MiB,4,3,0.005352 ms,107.891951 mW,577437.720000 pJ
resize/non_uniform,PASS,0.069 s,0.00 MiB,0.00 MiB,6,0,SKIP,SKIP,SKIP relu/after_gemm,PASS,-,0.01 MiB,0.01 MiB,5,4,0.007513 ms,105.158653 mW,790056.960000 pJ
resize/width_only,PASS,0.055 s,0.00 MiB,0.00 MiB,2,0,SKIP,SKIP,SKIP relu/basic,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000221 ms,78.217195 mW,17286.000000 pJ
resize/with_sizes,PASS,0.060 s,0.00 MiB,0.00 MiB,3,0,SKIP,SKIP,SKIP reshape/4d_to_2d_flatten,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000258 ms,78.279070 mW,20196.000000 pJ
sigmoid/4d,PASS,0.060 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP reshape/infer_dim_minus_one,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000162 ms,78.296296 mW,12684.000000 pJ
sigmoid/after_gemm,PASS,0.059 s,0.01 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP reshape/same_rank,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000162 ms,78.296296 mW,12684.000000 pJ
sigmoid/basic,PASS,0.053 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP reshape/zero_copies_input_dim,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000162 ms,78.296296 mW,12684.000000 pJ
slice/2d_basic,PASS,0.058 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP resize/height_only,PASS,-,0.00 MiB,0.00 MiB,4,0,0.000693 ms,93.554113 mW,64833.000000 pJ
slice/after_conv,PASS,0.070 s,0.00 MiB,0.01 MiB,7,6,SKIP,SKIP,SKIP resize/nearest_2x,PASS,-,0.00 MiB,0.00 MiB,4,0,0.001173 ms,93.572890 mW,109761.000000 pJ
slice/default_axes,PASS,0.066 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP resize/nearest_downsample,PASS,-,0.00 MiB,0.00 MiB,2,0,0.000427 ms,79.449649 mW,33925.000000 pJ
slice/large_channel_1024,PASS,0.064 s,0.01 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP resize/non_uniform,PASS,-,0.00 MiB,0.00 MiB,6,0,0.001753 ms,93.575014 mW,164037.000000 pJ
slice/nchw_spatial_crop,PASS,0.055 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP resize/width_only,PASS,-,0.00 MiB,0.00 MiB,2,0,0.000667 ms,79.503748 mW,53029.000000 pJ
slice/negative_axis,PASS,0.060 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP resize/with_sizes,PASS,-,0.00 MiB,0.00 MiB,3,0,0.000797 ms,92.542033 mW,73756.000000 pJ
slice/negative_indices,PASS,0.053 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP sigmoid/4d,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000521 ms,78.184261 mW,40734.000000 pJ
slice/step2,PASS,0.058 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP sigmoid/after_gemm,PASS,-,0.01 MiB,0.01 MiB,5,4,0.007513 ms,105.158653 mW,790056.960000 pJ
softmax/3d_last_axis,PASS,0.050 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP sigmoid/basic,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000221 ms,78.217195 mW,17286.000000 pJ
softmax/basic,PASS,0.061 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP slice/2d_basic,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000242 ms,78.297521 mW,18948.000000 pJ
softmax/channel_axis,PASS,0.058 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP slice/after_conv,PASS,-,0.00 MiB,0.01 MiB,7,6,0.011296 ms,118.190765 mW,1335082.880000 pJ
softmax/large_dimension_1024,PASS,0.061 s,0.01 MiB,0.01 MiB,1,0,SKIP,SKIP,SKIP slice/default_axes,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000242 ms,78.297521 mW,18948.000000 pJ
softmax/negative_axis,PASS,0.058 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP slice/large_channel_1024,PASS,-,0.01 MiB,0.00 MiB,1,0,0.002832 ms,78.144068 mW,221304.000000 pJ
split/basic,PASS,0.059 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP slice/nchw_spatial_crop,PASS,-,0.00 MiB,0.00 MiB,1,0,0.001302 ms,78.239631 mW,101868.000000 pJ
split/equal_three_way,PASS,0.063 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP slice/negative_axis,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000562 ms,78.298932 mW,44004.000000 pJ
split/negative_axis,PASS,0.059 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP slice/negative_indices,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000322 ms,78.298137 mW,25212.000000 pJ
split/uneven_channel_axis_4d,PASS,0.061 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP slice/step2,PASS,-,0.00 MiB,0.00 MiB,1,0,0.002042 ms,78.293830 mW,159876.000000 pJ
sub/after_gemm,PASS,0.064 s,0.01 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP softmax/3d_last_axis,PASS,-,0.00 MiB,0.00 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED
sub/basic,PASS,0.054 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP softmax/basic,PASS,-,0.00 MiB,0.00 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED
sub/broadcast_row,PASS,0.064 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP softmax/channel_axis,PASS,-,0.00 MiB,0.00 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED
sub/channel_broadcast_1024,PASS,0.063 s,0.02 MiB,0.01 MiB,1,0,SKIP,SKIP,SKIP softmax/large_dimension_1024,PASS,-,0.01 MiB,0.01 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED
sub/constant_lhs_broadcast,PASS,0.054 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP softmax/negative_axis,PASS,-,0.00 MiB,0.00 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED
sub/leading_dimension_broadcast,PASS,0.054 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP split/basic,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000403 ms,78.297767 mW,31554.000000 pJ
split/equal_three_way,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000564 ms,78.297872 mW,44160.000000 pJ
split/negative_axis,PASS,-,0.00 MiB,0.00 MiB,1,0,0.001083 ms,78.288089 mW,84786.000000 pJ
split/uneven_channel_axis_4d,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000242 ms,78.297521 mW,18948.000000 pJ
sub/after_gemm,PASS,-,0.01 MiB,0.01 MiB,5,4,0.007784 ms,104.703618 mW,815012.960000 pJ
sub/basic,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
sub/broadcast_row,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
sub/channel_broadcast_1024,PASS,-,0.02 MiB,0.01 MiB,1,0,0.006913 ms,78.118038 mW,540030.000000 pJ
sub/constant_lhs_broadcast,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000322 ms,78.223602 mW,25188.000000 pJ
sub/leading_dimension_broadcast,PASS,-,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.063 s - 0.01 MiB 0.01 MiB 5 4 SKIP 0.007784 ms SKIP 104.703618 mW SKIP 815012.960000 pJ
3 add/basic PASS 0.057 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.000323 ms SKIP 78.222910 mW SKIP 25266.000000 pJ
4 add/broadcast_row PASS 0.057 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.000323 ms SKIP 78.222910 mW SKIP 25266.000000 pJ
5 add/channel_broadcast_1024 PASS 0.061 s - 0.02 MiB 0.01 MiB 1 0 SKIP 0.006913 ms SKIP 78.118038 mW SKIP 540030.000000 pJ
6 add/leading_dimension_broadcast PASS 0.057 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.000323 ms SKIP 78.222910 mW SKIP 25266.000000 pJ
7 concat/channel_axis PASS 0.069 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.000457 ms SKIP 78.157549 mW SKIP 35718.000000 pJ
8 concat/negative_axis PASS 0.061 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.001043 ms SKIP 78.092042 mW SKIP 81450.000000 pJ
9 concat/three_inputs_channel_axis PASS 0.058 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.000644 ms SKIP 78.149068 mW SKIP 50328.000000 pJ
10 conv/batch_2 PASS 0.066 s - 0.00 MiB 0.00 MiB 2 2 SKIP 0.013694 ms SKIP 82.623885 mW SKIP 1131451.480000 pJ
11 conv/batch_4_pointwise PASS 0.065 s - 0.00 MiB 0.01 MiB 5 4 SKIP 0.003932 ms SKIP 116.078576 mW SKIP 456420.960000 pJ
12 conv/depthwise_1024_channels conv/conv_1x3x224x224_w64x3x7x7_b64 PASS 0.083 s - 0.19 MiB 24.14 MiB 0.38 MiB 61.87 MiB 129 168 128 169 SKIP 38.414551 ms SKIP 185.256473 mW SKIP 7116544212.120002 pJ
13 conv/depthwise_grouped conv/depthwise_1024_channels PASS 0.071 s - 0.01 MiB 0.19 MiB 0.00 MiB 0.38 MiB 5 129 4 128 SKIP 0.220751 ms SKIP 178.454307 mW SKIP 39393966.720000 pJ
14 conv/dilated_3x3 conv/depthwise_grouped PASS 0.068 s - 0.01 MiB 0.01 MiB 0.00 MiB 10 5 9 4 SKIP 0.006234 ms SKIP 107.776541 mW SKIP 671878.960000 pJ
15 conv/dynamic conv/dilated_3x3 PASS 0.065 s - 0.00 MiB 0.01 MiB 0.00 MiB 0.01 MiB 5 10 0 9 SKIP 0.008713 ms SKIP 118.767263 mW SKIP 1034819.160000 pJ
16 conv/explicit_padding conv/dynamic PASS 0.062 s - 0.01 MiB 0.00 MiB 0.02 MiB 0.00 MiB 17 5 16 0 SKIP 0.001835 ms SKIP 92.281199 mW SKIP 169336.000000 pJ
17 conv/grouped_many_groups conv/explicit_padding PASS 0.442 s - 0.05 MiB 0.01 MiB 0.09 MiB 0.02 MiB 65 17 64 16 SKIP 0.010007 ms SKIP 145.338047 mW SKIP 1454397.840000 pJ
18 conv/grouped_two_groups conv/grouped_many_groups PASS 0.060 s - 0.00 MiB 0.05 MiB 0.00 MiB 0.09 MiB 3 65 2 64 SKIP 0.181897 ms SKIP 142.207471 mW SKIP 25867112.360000 pJ
19 conv/huge_pointwise_1024 conv/grouped_two_groups PASS 0.156 s - 0.01 MiB 0.00 MiB 0.11 MiB 0.00 MiB 73 3 64 2 SKIP 0.005361 ms SKIP 101.457653 mW SKIP 543914.480000 pJ
20 conv/huge_pointwise_1024_dynamic conv/huge_pointwise_1024 PASS 0.079 s - 8.04 MiB 0.01 MiB 12.61 MiB 0.11 MiB 168 73 0 64 SKIP 0.015615 ms SKIP 249.545140 mW SKIP 3896647.360000 pJ
21 conv/kernel_3x3 conv/huge_pointwise_1024_dynamic PASS 0.062 s - 0.01 MiB 8.04 MiB 0.01 MiB 12.61 MiB 10 168 9 0 SKIP 2.627964 ms SKIP 169.518697 mW SKIP 445489032.000000 pJ
22 conv/kernel_equals_input_spatial conv/kernel_3x3 PASS 0.064 s - 0.00 MiB 0.01 MiB 0.00 MiB 0.01 MiB 2 10 2 9 SKIP 0.007186 ms SKIP 123.801859 mW SKIP 889640.160000 pJ
23 conv/large_input_channels_1x1 conv/kernel_equals_input_spatial PASS 0.089 s - 0.01 MiB 0.00 MiB 0.02 MiB 0.00 MiB 9 2 8 2 SKIP 0.004639 ms SKIP 89.607562 mW SKIP 415689.480000 pJ
24 conv/large_output_channels_1x1 conv/large_input_channels_1x1 PASS 0.098 s - 0.01 MiB 0.02 MiB 17 9 8 SKIP 0.007648 ms SKIP 117.824519 mW SKIP 901121.920000 pJ
25 conv/large_spatial conv/large_output_channels_1x1 PASS 0.069 s - 0.01 MiB 0.04 MiB 0.02 MiB 37 17 36 8 SKIP 0.008871 ms SKIP 128.442782 mW SKIP 1139415.920000 pJ
26 conv/multi_channel conv/large_spatial PASS 0.066 s - 0.00 MiB 0.01 MiB 0.00 MiB 0.04 MiB 4 37 3 36 SKIP 0.017018 ms SKIP 172.073372 mW SKIP 2928344.640000 pJ
27 conv/non_square_kernel_1x3 conv/multi_channel PASS 0.061 s - 0.00 MiB 0.00 MiB 3 4 2 3 SKIP 0.006482 ms SKIP 105.683542 mW SKIP 685040.720000 pJ
28 conv/non_square_kernel_3x1 conv/non_square_kernel_1x3 PASS 0.064 s - 0.00 MiB 0.00 MiB 3 2 SKIP 0.006842 ms SKIP 99.349968 mW SKIP 679752.480000 pJ
29 conv/non_uniform_stride conv/non_square_kernel_3x1 PASS 0.062 s - 0.00 MiB 0.00 MiB 4 3 3 2 SKIP 0.013484 ms SKIP 95.889683 mW SKIP 1292976.480000 pJ
30 conv/pointwise_1x1 conv/non_uniform_stride PASS 0.059 s - 0.00 MiB 0.00 MiB 1 4 1 3 SKIP 0.007601 ms SKIP 104.048772 mW SKIP 790874.720000 pJ
31 conv/pointwise_tiled_chain conv/output_channel_grouping_minimal PASS 0.604 s - 0.01 MiB 0.10 MiB 0.04 MiB 0.34 MiB 20 131 80 128 SKIP 0.258453 ms SKIP 170.730913 mW SKIP 44125916.720000 pJ
32 conv/real_asymmetric_padding conv/pointwise_1x1 PASS 0.060 s - 0.01 MiB 0.00 MiB 0.03 MiB 0.00 MiB 29 1 28 1 SKIP 0.012303 ms SKIP 80.244188 mW SKIP 987244.240000 pJ
33 conv/relu_conv_store conv/pointwise_tiled_chain PASS 0.091 s - 0.16 MiB 0.01 MiB 0.67 MiB 0.04 MiB 168 20 184 80 SKIP 0.041886 ms SKIP 153.880896 mW SKIP 6445455.200000 pJ
34 conv/same_lower_3x3 conv/real_asymmetric_padding PASS 0.078 s - 0.01 MiB 0.02 MiB 0.03 MiB 26 29 25 28 SKIP 0.014457 ms SKIP 153.669968 mW SKIP 2221606.720000 pJ
35 conv/same_padding_3x3 conv/relu_conv_store PASS 0.070 s - 0.01 MiB 0.16 MiB 0.02 MiB 0.67 MiB 26 168 25 184 SKIP 0.562898 ms SKIP 183.084489 mW SKIP 103057892.800000 pJ
36 conv/simple conv/same_lower_3x3 PASS 0.064 s - 0.00 MiB 0.01 MiB 0.00 MiB 0.02 MiB 1 26 1 25 SKIP 0.013331 ms SKIP 166.154752 mW SKIP 2215009.000000 pJ
37 conv/stride_2 conv/same_padding_3x3 PASS 0.064 s - 0.01 MiB 0.00 MiB 0.02 MiB 5 26 4 25 SKIP 0.013331 ms SKIP 166.154752 mW SKIP 2215009.000000 pJ
38 conv/with_bias_3x3 conv/simple PASS 0.067 s - 0.00 MiB 0.01 MiB 0.00 MiB 4 1 3 1 SKIP 0.004301 ms SKIP 83.833583 mW SKIP 360568.240000 pJ
39 conv/with_constant conv/strategy_depthwise_16 PASS 0.070 s - 0.00 MiB 0.06 MiB 0.00 MiB 0.35 MiB 1 168 1 168 SKIP 0.335115 ms SKIP 197.936467 mW SKIP 66331479.080000 pJ
40 conv/without_kernel_shape_attr conv/strategy_input_k_tiled PASS 0.069 s - 0.01 MiB 0.08 MiB 0.01 MiB 0.27 MiB 10 109 9 108 SKIP 0.353736 ms SKIP 170.812713 mW SKIP 60422605.920000 pJ
41 conv/yolo11n_depthwise_head conv/strategy_output_channel_tiled PASS 1.482 s - 8.66 MiB 0.03 MiB 34.24 MiB 0.16 MiB 168 74 255 72 SKIP 0.091463 ms SKIP 155.743189 mW SKIP 14244739.280000 pJ
42 conv/yolo11n_heavy conv/strategy_streamed_packed PASS 0.431 s - 4.82 MiB 3.34 MiB 19.10 MiB 7.89 MiB 161 168 800 168 SKIP 9.353833 ms SKIP 179.858301 mW SKIP 1682364509.560000 pJ
43 conv/yolo11n_stem conv/strategy_streamed_patch PASS 0.783 s - 12.86 MiB 0.34 MiB 37.59 MiB 1.32 MiB 168 488 168 SKIP 1.904655 ms SKIP 181.910449 mW SKIP 346476645.640000 pJ
44 div/after_gemm conv/strategy_tiled_2d PASS 0.072 s - 0.01 MiB 0.11 MiB 0.01 MiB 0.44 MiB 5 168 4 168 SKIP 0.415594 ms SKIP 182.127047 mW SKIP 75690907.840000 pJ
45 div/basic conv/stride_2 PASS 0.061 s - 0.00 MiB 0.01 MiB 0.00 MiB 1 5 0 4 SKIP 0.005237 ms SKIP 110.780019 mW SKIP 580154.960000 pJ
46 div/channel_broadcast_1024 conv/with_bias_3x3 PASS 0.066 s - 0.02 MiB 0.00 MiB 0.01 MiB 1 4 0 3 SKIP 0.007452 ms SKIP 104.162738 mW SKIP 776220.720000 pJ
47 div/leading_dimension_broadcast conv/with_constant PASS 0.059 s - 0.00 MiB 0.00 MiB 1 0 1 SKIP 0.006622 ms SKIP 81.738182 mW SKIP 541270.240000 pJ
48 div/runtime_scalar_rhs conv/without_kernel_shape_attr PASS 0.056 s - 0.02 MiB 0.01 MiB 0.01 MiB 1 10 0 9 SKIP 0.007186 ms SKIP 123.801859 mW SKIP 889640.160000 pJ
49 div/scalar_constant conv/yolo11n_depthwise_head PASS 0.073 s - 0.00 MiB 8.66 MiB 0.00 MiB 34.24 MiB 1 168 0 255 SKIP 42.701404 ms SKIP 200.519161 mW SKIP 8562449708.000010 pJ
50 gather/3d_input_axis1 conv/yolo11n_heavy PASS 0.060 s - 0.00 MiB 4.82 MiB 0.00 MiB 19.10 MiB 1 161 0 800 SKIP 8.535404 ms SKIP 350.863768 mW SKIP 2994764012.000010 pJ
51 gather/axis0_matrix_indices conv/yolo11n_stem PASS 0.056 s - 0.00 MiB 12.86 MiB 0.00 MiB 37.59 MiB 1 168 0 488 SKIP 14.239985 ms SKIP 301.233376 mW SKIP 4289558753.000010 pJ
52 gather/axis1 div/after_gemm PASS 0.058 s - 0.00 MiB 0.01 MiB 0.00 MiB 0.01 MiB 1 5 0 4 SKIP 0.007784 ms SKIP 104.703618 mW SKIP 815012.960000 pJ
53 gather/negative_axis div/basic PASS 0.058 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.000323 ms SKIP 78.222910 mW SKIP 25266.000000 pJ
54 gather/negative_indices div/channel_broadcast_1024 PASS 0.063 s - 0.00 MiB 0.02 MiB 0.00 MiB 0.01 MiB 1 0 SKIP 0.006913 ms SKIP 78.118038 mW SKIP 540030.000000 pJ
55 gemm/alpha_beta div/leading_dimension_broadcast PASS 0.067 s - 0.01 MiB 0.00 MiB 0.01 MiB 0.00 MiB 5 1 4 0 SKIP 0.000323 ms SKIP 78.222910 mW SKIP 25266.000000 pJ
56 gemm/bias_rank2_broadcast div/runtime_scalar_rhs PASS 0.060 s - 0.00 MiB 0.02 MiB 0.01 MiB 5 1 4 0 SKIP 0.006913 ms SKIP 78.118038 mW SKIP 540030.000000 pJ
57 gemm/dynamic div/scalar_constant PASS 0.064 s - 0.00 MiB 0.00 MiB 5 1 0 SKIP 0.000323 ms SKIP 78.222910 mW SKIP 25266.000000 pJ
58 gemm/dynamic_alpha gather/3d_input_axis1 PASS 0.062 s - 0.00 MiB 0.00 MiB 5 1 0 SKIP 0.000589 ms SKIP 78.081494 mW SKIP 45990.000000 pJ
59 gemm/dynamic_beta gather/axis0_matrix_indices PASS 0.060 s - 0.00 MiB 0.00 MiB 5 1 0 SKIP 0.000697 ms SKIP 78.068867 mW SKIP 54414.000000 pJ
60 gemm/dynamic_bias gather/axis1 PASS 0.058 s - 0.00 MiB 0.00 MiB 5 1 0 SKIP 0.000801 ms SKIP 78.059925 mW SKIP 62526.000000 pJ
61 gemm/dynamic_bias_alpha_beta gather/negative_axis PASS 0.067 s - 0.00 MiB 0.00 MiB 5 1 0 SKIP 0.001437 ms SKIP 78.033403 mW SKIP 112134.000000 pJ
62 gemm/dynamic_transB gather/negative_indices PASS 0.062 s - 0.00 MiB 0.00 MiB 5 1 0 SKIP 0.000376 ms SKIP 78.127660 mW SKIP 29376.000000 pJ
63 gemm/huge_1024 gemm/alpha_beta PASS 0.147 s - 0.01 MiB 0.10 MiB 0.01 MiB 73 5 64 4 SKIP 0.007456 ms SKIP 105.272125 mW SKIP 784908.960000 pJ
64 gemm/large gemm/bias_rank2_broadcast PASS 0.068 s - 0.02 MiB 0.00 MiB 0.03 MiB 0.01 MiB 17 5 16 4 SKIP 0.007072 ms SKIP 105.979208 mW SKIP 749484.960000 pJ
65 gemm/large_k_small_n gemm/dynamic PASS 0.095 s - 0.01 MiB 0.00 MiB 0.01 MiB 0.00 MiB 9 5 8 0 SKIP 0.002421 ms SKIP 91.480793 mW SKIP 221475.000000 pJ
66 gemm/non_square gemm/dynamic_alpha PASS 0.063 s - 0.00 MiB 0.01 MiB 0.00 MiB 5 4 0 SKIP 0.003262 ms SKIP 91.415696 mW SKIP 298198.000000 pJ
67 gemm/scalar_bias gemm/dynamic_beta PASS 0.060 s - 0.00 MiB 0.01 MiB 0.00 MiB 5 4 0 SKIP 0.004365 ms SKIP 91.316151 mW SKIP 398595.000000 pJ
68 gemm/simple gemm/dynamic_bias PASS 0.072 s - 0.03 MiB 0.00 MiB 0.08 MiB 0.00 MiB 42 5 40 0 SKIP 0.002665 ms SKIP 91.445779 mW SKIP 243703.000000 pJ
69 gemm/small gemm/dynamic_bias_alpha_beta PASS 0.065 s - 0.00 MiB 0.00 MiB 2 5 2 0 SKIP 0.005629 ms SKIP 91.279268 mW SKIP 513811.000000 pJ
70 gemm/small_k_large_n gemm/dynamic_transB PASS 0.097 s - 0.01 MiB 0.00 MiB 0.02 MiB 0.00 MiB 17 5 8 0 SKIP 0.001301 ms SKIP 91.378171 mW SKIP 118883.000000 pJ
71 gemm/transA gemm/huge_1024 PASS 0.064 s - 0.00 MiB 0.01 MiB 0.01 MiB 0.10 MiB 5 73 4 64 SKIP 0.017522 ms SKIP 215.037402 mW SKIP 3767885.360000 pJ
72 gemm/transA_transB gemm/large PASS 0.069 s - 0.00 MiB 0.02 MiB 0.01 MiB 0.03 MiB 5 17 4 16 SKIP 0.011229 ms SKIP 140.152181 mW SKIP 1573768.840000 pJ
73 gemm/transB gemm/large_k_small_n PASS 0.062 s - 0.00 MiB 0.01 MiB 0.01 MiB 5 9 4 8 SKIP 0.004748 ms SKIP 133.481449 mW SKIP 633769.920000 pJ
74 gemm/transB_with_bias gemm/non_square PASS 0.055 s - 0.01 MiB 0.00 MiB 0.01 MiB 5 4 SKIP 0.003527 ms SKIP 118.958310 mW SKIP 419565.960000 pJ
75 gemm/with_bias gemm/scalar_bias PASS 0.067 s - 0.01 MiB 0.00 MiB 0.01 MiB 5 4 SKIP 0.007072 ms SKIP 105.979208 mW SKIP 749484.960000 pJ
76 gemv/constant gemm/simple PASS 0.064 s - 0.00 MiB 0.03 MiB 0.00 MiB 0.08 MiB 0 42 0 40 SKIP 0.021640 ms SKIP 151.774196 mW SKIP 3284393.600000 pJ
77 gemv/simple gemm/small PASS 0.069 s - 0.00 MiB 0.01 MiB 0.00 MiB 6 2 4 2 SKIP 0.004420 ms SKIP 90.144000 mW SKIP 398436.480000 pJ
78 gemv/with_heterogeneous_constant gemm/small_k_large_n PASS 0.066 s - 0.00 MiB 0.01 MiB 0.01 MiB 0.02 MiB 6 17 4 8 SKIP 0.007962 ms SKIP 131.005014 mW SKIP 1043061.920000 pJ
79 gemv/with_homogeneous_constant gemm/transA PASS 0.070 s - 0.00 MiB 0.01 MiB 6 5 4 SKIP 0.005762 ms SKIP 109.140743 mW SKIP 628868.960000 pJ
80 gemv/with_scalar_constant gemm/transA_transB PASS 0.070 s - 0.00 MiB 0.01 MiB 6 5 4 SKIP 0.005762 ms SKIP 109.140743 mW SKIP 628868.960000 pJ
81 matmul/basic gemm/transB PASS 0.062 s - 0.00 MiB 0.00 MiB 0.01 MiB 2 5 2 4 SKIP 0.003527 ms SKIP 118.958310 mW SKIP 419565.960000 pJ
82 matmul/batched_3d gemm/transB_with_bias PASS 0.066 s - 0.00 MiB 0.01 MiB 0.01 MiB 5 4 SKIP 0.005046 ms SKIP 110.546762 mW SKIP 557818.960000 pJ
83 matmul/batched_3d_dynamic gemm/with_bias PASS 0.057 s - 0.00 MiB 0.01 MiB 0.00 MiB 0.01 MiB 4 5 0 4 SKIP 0.005562 ms SKIP 108.767882 mW SKIP 604966.960000 pJ
84 matmul/batched_left_constant gemv/constant PASS 0.070 s - 0.00 MiB 0.02 MiB 0.00 MiB 9 0 8 0 SKIP 0.000000 ms SKIP 2.000000 mW SKIP 0.000000 pJ
85 matmul/batched_lhs_broadcast gemv/simple PASS 0.063 s - 0.00 MiB 0.01 MiB 5 6 4 SKIP 0.005160 ms SKIP 111.150380 mW SKIP 573535.960000 pJ
86 matmul/batched_rhs_broadcast gemv/with_heterogeneous_constant PASS 0.062 s - 0.00 MiB 0.01 MiB 5 6 4 SKIP 0.005549 ms SKIP 109.816536 mW SKIP 609371.960000 pJ
87 matmul/dynamic gemv/with_homogeneous_constant PASS 0.060 s - 0.00 MiB 0.00 MiB 0.01 MiB 5 6 0 4 SKIP 0.005549 ms SKIP 109.816536 mW SKIP 609371.960000 pJ
88 matmul/huge_1024 gemv/with_scalar_constant PASS 0.145 s - 0.01 MiB 0.00 MiB 0.10 MiB 0.01 MiB 73 6 64 4 SKIP 0.005549 ms SKIP 109.816536 mW SKIP 609371.960000 pJ
89 matmul/left_constant matmul/basic PASS 0.051 s - 0.00 MiB 0.01 MiB 0.00 MiB 5 2 4 2 SKIP 0.004420 ms SKIP 90.144000 mW SKIP 398436.480000 pJ
90 matmul/matrix_vector matmul/batched_3d PASS 0.095 s - 0.52 MiB 0.00 MiB 0.78 MiB 0.01 MiB 168 5 173 4 SKIP 0.005958 ms SKIP 108.588949 mW SKIP 646972.960000 pJ
91 matmul/vector_matrix matmul/batched_3d_dynamic PASS 0.087 s - 0.01 MiB 0.00 MiB 0.01 MiB 0.00 MiB 9 4 8 0 SKIP 0.001822 ms SKIP 92.192645 mW SKIP 167975.000000 pJ
92 matmul/yolo_attention matmul/batched_left_constant PASS 0.385 s - 1.02 MiB 0.00 MiB 43.44 MiB 0.02 MiB 168 9 0 8 SKIP 0.008822 ms SKIP 114.385164 mW SKIP 1009105.920000 pJ
93 mul/after_conv matmul/batched_lhs_broadcast PASS 0.055 s - 0.00 MiB 0.00 MiB 0.01 MiB 4 5 3 4 SKIP 0.005681 ms SKIP 109.389361 mW SKIP 621440.960000 pJ
94 mul/basic matmul/batched_rhs_broadcast PASS 0.055 s - 0.00 MiB 0.00 MiB 0.01 MiB 1 5 0 4 SKIP 0.005958 ms SKIP 108.588949 mW SKIP 646972.960000 pJ
95 mul/channel_broadcast_1024 matmul/dynamic PASS 0.058 s - 0.02 MiB 0.00 MiB 0.01 MiB 0.00 MiB 1 5 0 SKIP 0.001621 ms SKIP 91.421962 mW SKIP 148195.000000 pJ
96 mul/leading_dimension_broadcast matmul/huge_1024 PASS 0.059 s - 0.00 MiB 0.01 MiB 0.00 MiB 0.10 MiB 1 73 0 64 SKIP 0.017522 ms SKIP 215.037402 mW SKIP 3767885.360000 pJ
97 mul/scalar_constant matmul/left_constant PASS 0.056 s - 0.00 MiB 0.00 MiB 0.01 MiB 1 5 0 4 SKIP 0.005853 ms SKIP 108.861944 mW SKIP 637168.960000 pJ
98 pool/avg_basic matmul/matrix_vector PASS 0.063 s - 0.00 MiB 0.52 MiB 0.00 MiB 0.78 MiB 1 168 0 173 SKIP 0.384660 ms SKIP 202.131271 mW SKIP 77751814.880000 pJ
99 pool/avg_ceil_mode matmul/vector_matrix PASS 0.063 s - 0.00 MiB 0.01 MiB 0.00 MiB 0.01 MiB 1 9 0 8 SKIP 0.007409 ms SKIP 118.680243 mW SKIP 879301.920000 pJ
100 pool/avg_explicit_padding matmul/yolo_attention PASS 0.061 s - 0.00 MiB 1.02 MiB 0.00 MiB 43.44 MiB 1 168 0 SKIP 8.151445 ms SKIP 170.003707 mW SKIP 1385775865.000000 pJ
101 pool/avg_include_pad mul/after_conv PASS 0.049 s - 0.00 MiB 0.00 MiB 1 4 0 3 SKIP 0.005453 ms SKIP 107.639046 mW SKIP 586955.720000 pJ
102 pool/avg_large_channels mul/after_conv_scalar_constant PASS 0.059 s - 0.04 MiB 0.00 MiB 0.02 MiB 0.00 MiB 1 4 0 3 SKIP 0.005453 ms SKIP 107.639046 mW SKIP 586955.720000 pJ
103 pool/avg_non_uniform_stride mul/basic PASS 0.066 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.000323 ms SKIP 78.222910 mW SKIP 25266.000000 pJ
104 pool/avg_real_asymmetric_padding mul/channel_broadcast_1024 PASS 0.055 s - 0.00 MiB 0.02 MiB 0.00 MiB 0.01 MiB 1 0 SKIP 0.006913 ms SKIP 78.118038 mW SKIP 540030.000000 pJ
105 pool/max_after_conv mul/leading_dimension_broadcast PASS 0.069 s - 0.00 MiB 0.00 MiB 5 1 4 0 SKIP 0.000323 ms SKIP 78.222910 mW SKIP 25266.000000 pJ
106 pool/max_basic mul/scalar_constant PASS 0.064 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.000323 ms SKIP 78.222910 mW SKIP 25266.000000 pJ
107 pool/max_ceil_mode pool/avg_basic PASS 0.058 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.011939 ms SKIP 78.022112 mW SKIP 931506.000000 pJ
108 pool/max_global_style_kernel_equals_input pool/avg_ceil_mode PASS 0.063 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.004359 ms SKIP 78.033035 mW SKIP 340146.000000 pJ
109 pool/max_non_square_kernel pool/avg_explicit_padding PASS 0.067 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.008822 ms SKIP 78.027205 mW SKIP 688356.000000 pJ
110 pool/max_real_asymmetric_padding pool/avg_include_pad PASS 0.063 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.008506 ms SKIP 78.016929 mW SKIP 663612.000000 pJ
111 pool/max_same_upper pool/avg_large_channels PASS 0.061 s - 0.00 MiB 0.04 MiB 0.00 MiB 0.02 MiB 1 0 SKIP 0.235874 ms SKIP 78.004172 mW SKIP 18399156.000000 pJ
112 pool/max_stride2_multichannel pool/avg_non_uniform_stride PASS 0.076 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.014513 ms SKIP 78.016537 mW SKIP 1132254.000000 pJ
113 reduce_mean/4d_spatial pool/avg_real_asymmetric_padding PASS 0.058 s - 0.00 MiB 0.00 MiB 3 1 0 SKIP 0.025206 ms SKIP 78.024756 mW SKIP 1966692.000000 pJ
114 reduce_mean/4d_spatial_keepdims_0 pool/max_after_conv PASS 0.068 s - 0.00 MiB 0.00 MiB 4 5 0 4 SKIP 0.012215 ms SKIP 99.115019 mW SKIP 1210689.960000 pJ
115 reduce_mean/after_conv pool/max_basic PASS 0.067 s - 0.00 MiB 0.00 MiB 5 1 3 0 SKIP 0.004160 ms SKIP 78.063462 mW SKIP 324744.000000 pJ
116 reduce_mean/all_axes_keepdims_0 pool/max_ceil_mode PASS 0.057 s - 0.00 MiB 0.00 MiB 2 1 0 SKIP 0.001940 ms SKIP 78.074227 mW SKIP 151464.000000 pJ
117 reduce_mean/all_axes_keepdims_1 pool/max_global_style_kernel_equals_input PASS 0.053 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.008443 ms SKIP 78.008528 mW SKIP 658626.000000 pJ
118 reduce_mean/basic pool/max_non_square_kernel PASS 0.058 s - 0.00 MiB 0.00 MiB 4 1 0 SKIP 0.013626 ms SKIP 78.017613 mW SKIP 1063068.000000 pJ
119 reduce_mean/channel_axis_nchw pool/max_real_asymmetric_padding PASS 0.063 s - 0.03 MiB 0.00 MiB 0.02 MiB 0.00 MiB 4 1 0 SKIP 0.010444 ms SKIP 78.034470 mW SKIP 814992.000000 pJ
120 reduce_mean/keepdims_0 pool/max_same_upper PASS 0.064 s - 0.00 MiB 0.00 MiB 5 1 0 SKIP 0.008010 ms SKIP 78.035955 mW SKIP 625068.000000 pJ
121 reduce_mean/large_dimension_1024 pool/max_stride2_multichannel PASS 0.066 s - 0.01 MiB 0.00 MiB 0.00 MiB 1 0 SKIP 0.015987 ms SKIP 78.018015 mW SKIP 1247274.000000 pJ
122 reduce_mean/legacy_axes_1_2_keepdims_1 reduce_mean/4d_spatial PASS 0.069 s - 0.00 MiB 0.00 MiB 2 3 0 SKIP 0.000321 ms SKIP 92.448598 mW SKIP 29676.000000 pJ
123 reduce_mean/legacy_axis1_keepdims_0 reduce_mean/4d_spatial_keepdims_0 PASS 0.067 s - 0.00 MiB 0.00 MiB 9 4 0 SKIP 0.000655 ms SKIP 94.352672 mW SKIP 61801.000000 pJ
124 reduce_mean/legacy_axis1_keepdims_1 reduce_mean/after_conv PASS 0.057 s - 0.00 MiB 0.00 MiB 8 5 0 3 SKIP 0.005342 ms SKIP 106.951089 mW SKIP 571332.720000 pJ
125 reduce_mean/legacy_empty_axes_noop reduce_mean/all_axes_keepdims_0 PASS 0.058 s - 0.00 MiB 0.00 MiB 1 2 0 SKIP 0.000391 ms SKIP 79.237852 mW SKIP 30982.000000 pJ
126 reduce_mean/legacy_nchw_spatial reduce_mean/all_axes_keepdims_1 PASS 0.059 s - 0.00 MiB 0.00 MiB 3 1 0 SKIP 0.000221 ms SKIP 78.217195 mW SKIP 17286.000000 pJ
127 reduce_mean/legacy_negative_axis reduce_mean/basic PASS 0.052 s - 0.00 MiB 0.00 MiB 6 4 0 SKIP 0.000373 ms SKIP 93.514745 mW SKIP 34881.000000 pJ
128 reduce_mean/legacy_reduce_all_keepdims_1 reduce_mean/channel_axis_nchw PASS 0.057 s - 0.00 MiB 0.03 MiB 0.00 MiB 0.02 MiB 1 4 0 SKIP 0.164926 ms SKIP 93.596631 mW SKIP 15436518.000000 pJ
129 reduce_mean/negative_axis reduce_mean/keepdims_0 PASS 0.055 s - 0.00 MiB 0.00 MiB 6 5 0 SKIP 0.000748 ms SKIP 91.401070 mW SKIP 68368.000000 pJ
130 relu/4d reduce_mean/large_dimension_1024 PASS 0.059 s - 0.00 MiB 0.01 MiB 0.00 MiB 1 0 SKIP 0.002785 ms SKIP 78.017235 mW SKIP 217278.000000 pJ
131 relu/after_conv reduce_mean/legacy_axes_1_2_keepdims_1 PASS 0.062 s - 0.00 MiB 0.00 MiB 4 2 3 0 SKIP 0.000271 ms SKIP 79.354244 mW SKIP 21505.000000 pJ
132 relu/after_gemm reduce_mean/legacy_axis1_keepdims_0 PASS 0.062 s - 0.01 MiB 0.00 MiB 0.01 MiB 0.00 MiB 5 9 4 0 SKIP 0.001986 ms SKIP 92.501511 mW SKIP 183708.000000 pJ
133 relu/basic reduce_mean/legacy_axis1_keepdims_1 PASS 0.062 s - 0.00 MiB 0.00 MiB 1 8 0 SKIP 0.001373 ms SKIP 94.559359 mW SKIP 129830.000000 pJ
134 reshape/4d_to_2d_flatten reduce_mean/legacy_empty_axes_noop PASS 0.059 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.000221 ms SKIP 78.217195 mW SKIP 17286.000000 pJ
135 reshape/infer_dim_minus_one reduce_mean/legacy_nchw_spatial PASS 0.059 s - 0.00 MiB 0.00 MiB 1 3 0 SKIP 0.000321 ms SKIP 92.448598 mW SKIP 29676.000000 pJ
136 reshape/same_rank reduce_mean/legacy_negative_axis PASS 0.056 s - 0.00 MiB 0.00 MiB 1 6 0 SKIP 0.000553 ms SKIP 93.520796 mW SKIP 51717.000000 pJ
137 reshape/zero_copies_input_dim reduce_mean/legacy_reduce_all_keepdims_1 PASS 0.077 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.000221 ms SKIP 78.217195 mW SKIP 17286.000000 pJ
138 resize/height_only reduce_mean/negative_axis PASS 0.059 s - 0.00 MiB 0.00 MiB 4 6 0 SKIP 0.000553 ms SKIP 93.520796 mW SKIP 51717.000000 pJ
139 resize/nearest_2x relu/4d PASS 0.066 s - 0.00 MiB 0.00 MiB 4 1 0 SKIP 0.000521 ms SKIP 78.184261 mW SKIP 40734.000000 pJ
140 resize/nearest_downsample relu/after_conv PASS 0.062 s - 0.00 MiB 0.00 MiB 2 4 0 3 SKIP 0.005352 ms SKIP 107.891951 mW SKIP 577437.720000 pJ
141 resize/non_uniform relu/after_gemm PASS 0.069 s - 0.00 MiB 0.01 MiB 0.00 MiB 0.01 MiB 6 5 0 4 SKIP 0.007513 ms SKIP 105.158653 mW SKIP 790056.960000 pJ
142 resize/width_only relu/basic PASS 0.055 s - 0.00 MiB 0.00 MiB 2 1 0 SKIP 0.000221 ms SKIP 78.217195 mW SKIP 17286.000000 pJ
143 resize/with_sizes reshape/4d_to_2d_flatten PASS 0.060 s - 0.00 MiB 0.00 MiB 3 1 0 SKIP 0.000258 ms SKIP 78.279070 mW SKIP 20196.000000 pJ
144 sigmoid/4d reshape/infer_dim_minus_one PASS 0.060 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.000162 ms SKIP 78.296296 mW SKIP 12684.000000 pJ
145 sigmoid/after_gemm reshape/same_rank PASS 0.059 s - 0.01 MiB 0.00 MiB 0.01 MiB 0.00 MiB 5 1 4 0 SKIP 0.000162 ms SKIP 78.296296 mW SKIP 12684.000000 pJ
146 sigmoid/basic reshape/zero_copies_input_dim PASS 0.053 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.000162 ms SKIP 78.296296 mW SKIP 12684.000000 pJ
147 slice/2d_basic resize/height_only PASS 0.058 s - 0.00 MiB 0.00 MiB 1 4 0 SKIP 0.000693 ms SKIP 93.554113 mW SKIP 64833.000000 pJ
148 slice/after_conv resize/nearest_2x PASS 0.070 s - 0.00 MiB 0.01 MiB 0.00 MiB 7 4 6 0 SKIP 0.001173 ms SKIP 93.572890 mW SKIP 109761.000000 pJ
149 slice/default_axes resize/nearest_downsample PASS 0.066 s - 0.00 MiB 0.00 MiB 1 2 0 SKIP 0.000427 ms SKIP 79.449649 mW SKIP 33925.000000 pJ
150 slice/large_channel_1024 resize/non_uniform PASS 0.064 s - 0.01 MiB 0.00 MiB 0.00 MiB 1 6 0 SKIP 0.001753 ms SKIP 93.575014 mW SKIP 164037.000000 pJ
151 slice/nchw_spatial_crop resize/width_only PASS 0.055 s - 0.00 MiB 0.00 MiB 1 2 0 SKIP 0.000667 ms SKIP 79.503748 mW SKIP 53029.000000 pJ
152 slice/negative_axis resize/with_sizes PASS 0.060 s - 0.00 MiB 0.00 MiB 1 3 0 SKIP 0.000797 ms SKIP 92.542033 mW SKIP 73756.000000 pJ
153 slice/negative_indices sigmoid/4d PASS 0.053 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.000521 ms SKIP 78.184261 mW SKIP 40734.000000 pJ
154 slice/step2 sigmoid/after_gemm PASS 0.058 s - 0.00 MiB 0.01 MiB 0.00 MiB 0.01 MiB 1 5 0 4 SKIP 0.007513 ms SKIP 105.158653 mW SKIP 790056.960000 pJ
155 softmax/3d_last_axis sigmoid/basic PASS 0.050 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.000221 ms SKIP 78.217195 mW SKIP 17286.000000 pJ
156 softmax/basic slice/2d_basic PASS 0.061 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.000242 ms SKIP 78.297521 mW SKIP 18948.000000 pJ
157 softmax/channel_axis slice/after_conv PASS 0.058 s - 0.00 MiB 0.00 MiB 0.01 MiB 1 7 0 6 SKIP 0.011296 ms SKIP 118.190765 mW SKIP 1335082.880000 pJ
158 softmax/large_dimension_1024 slice/default_axes PASS 0.061 s - 0.01 MiB 0.00 MiB 0.01 MiB 0.00 MiB 1 0 SKIP 0.000242 ms SKIP 78.297521 mW SKIP 18948.000000 pJ
159 softmax/negative_axis slice/large_channel_1024 PASS 0.058 s - 0.00 MiB 0.01 MiB 0.00 MiB 1 0 SKIP 0.002832 ms SKIP 78.144068 mW SKIP 221304.000000 pJ
160 split/basic slice/nchw_spatial_crop PASS 0.059 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.001302 ms SKIP 78.239631 mW SKIP 101868.000000 pJ
161 split/equal_three_way slice/negative_axis PASS 0.063 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.000562 ms SKIP 78.298932 mW SKIP 44004.000000 pJ
162 split/negative_axis slice/negative_indices PASS 0.059 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.000322 ms SKIP 78.298137 mW SKIP 25212.000000 pJ
163 split/uneven_channel_axis_4d slice/step2 PASS 0.061 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.002042 ms SKIP 78.293830 mW SKIP 159876.000000 pJ
164 sub/after_gemm softmax/3d_last_axis PASS 0.064 s - 0.01 MiB 0.00 MiB 0.01 MiB 0.00 MiB 5 1 4 0 SKIP UNSUPPORTED SKIP UNSUPPORTED SKIP UNSUPPORTED
165 sub/basic softmax/basic PASS 0.054 s - 0.00 MiB 0.00 MiB 1 0 SKIP UNSUPPORTED SKIP UNSUPPORTED SKIP UNSUPPORTED
166 sub/broadcast_row softmax/channel_axis PASS 0.064 s - 0.00 MiB 0.00 MiB 1 0 SKIP UNSUPPORTED SKIP UNSUPPORTED SKIP UNSUPPORTED
167 sub/channel_broadcast_1024 softmax/large_dimension_1024 PASS 0.063 s - 0.02 MiB 0.01 MiB 0.01 MiB 1 0 SKIP UNSUPPORTED SKIP UNSUPPORTED SKIP UNSUPPORTED
168 sub/constant_lhs_broadcast softmax/negative_axis PASS 0.054 s - 0.00 MiB 0.00 MiB 1 0 SKIP UNSUPPORTED SKIP UNSUPPORTED SKIP UNSUPPORTED
169 sub/leading_dimension_broadcast split/basic PASS 0.054 s - 0.00 MiB 0.00 MiB 1 0 SKIP 0.000403 ms SKIP 78.297767 mW SKIP 31554.000000 pJ
170 split/equal_three_way PASS - 0.00 MiB 0.00 MiB 1 0 0.000564 ms 78.297872 mW 44160.000000 pJ
171 split/negative_axis PASS - 0.00 MiB 0.00 MiB 1 0 0.001083 ms 78.288089 mW 84786.000000 pJ
172 split/uneven_channel_axis_4d PASS - 0.00 MiB 0.00 MiB 1 0 0.000242 ms 78.297521 mW 18948.000000 pJ
173 sub/after_gemm PASS - 0.01 MiB 0.01 MiB 5 4 0.007784 ms 104.703618 mW 815012.960000 pJ
174 sub/basic PASS - 0.00 MiB 0.00 MiB 1 0 0.000323 ms 78.222910 mW 25266.000000 pJ
175 sub/broadcast_row PASS - 0.00 MiB 0.00 MiB 1 0 0.000323 ms 78.222910 mW 25266.000000 pJ
176 sub/channel_broadcast_1024 PASS - 0.02 MiB 0.01 MiB 1 0 0.006913 ms 78.118038 mW 540030.000000 pJ
177 sub/constant_lhs_broadcast PASS - 0.00 MiB 0.00 MiB 1 0 0.000322 ms 78.223602 mW 25188.000000 pJ
178 sub/leading_dimension_broadcast PASS - 0.00 MiB 0.00 MiB 1 0 0.000323 ms 78.222910 mW 25266.000000 pJ
@@ -0,0 +1,182 @@
#!/usr/bin/env python3
"""Save exact attention-tap arrays and quantify the MatMul error sources."""
import argparse
import hashlib
import json
import sys
from pathlib import Path
import numpy as np
import onnx
from onnx import numpy_helper
REPO_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(REPO_ROOT / "validation"))
from raptor_validation.onnx_utils import onnx_io # noqa: E402
from raptor_validation.validate_one import ( # noqa: E402
parse_pim_simulator_outputs,
sanitize_output_name,
)
TAP_NAMES = {
"v": "/model.10/m/m.0/attn/Split_output_2",
"raw": "/model.10/m/m.0/attn/MatMul_output_0",
"scaled": "/model.10/m/m.0/attn/Mul_output_0",
"rhs": "/model.10/m/m.0/attn/Transpose_1_output_0",
"c": "/model.10/m/m.0/attn/MatMul_1_output_0",
}
ABSOLUTE_TOLERANCE = 1e-3
RELATIVE_TOLERANCE = 1e-5
def sha256(path):
digest = hashlib.sha256()
with Path(path).open("rb") as stream:
for block in iter(lambda: stream.read(1 << 20), b""):
digest.update(block)
return digest.hexdigest()
def metric(actual, expected):
difference = np.abs(actual.astype(np.float64) - expected.astype(np.float64))
allowed = ABSOLUTE_TOLERANCE + RELATIVE_TOLERANCE * np.abs(expected.astype(np.float64))
return {
"max_abs": float(np.max(difference)),
"mean_abs": float(np.mean(difference)),
"rms": float(np.sqrt(np.mean(np.square(difference)))),
"elements_over_validator_limit": int(np.count_nonzero(difference > allowed)),
}
def f32_matmul(lhs, rhs):
return np.matmul(lhs.astype(np.float32), rhs.astype(np.float32)).astype(np.float32)
def f64_matmul(lhs, rhs):
return np.matmul(lhs.astype(np.float64), rhs.astype(np.float64)).astype(np.float64)
def load_constant(model, output_name):
for initializer in model.graph.initializer:
if initializer.name == output_name:
return float(numpy_helper.to_array(initializer).reshape(-1)[0])
for node in model.graph.node:
if output_name not in node.output:
continue
for attribute in node.attribute:
if attribute.name == "value" and attribute.HasField("t"):
return float(numpy_helper.to_array(attribute.t).reshape(-1)[0])
raise ValueError(f"could not find ONNX Constant producing {output_name}")
def load_arrays(workspace, model_path):
model = onnx.load(model_path)
descriptors = onnx_io(model_path)
output_descriptors = {name: (index, dtype, shape) for index, name, dtype, shape in descriptors[1]}
missing = sorted(set(TAP_NAMES.values()) - set(output_descriptors))
if missing:
raise ValueError("tap model is missing outputs: " + ", ".join(missing))
sim_arrays = parse_pim_simulator_outputs(
workspace / "simulation" / "out.bin", descriptors[1]
)
reference = {}
simulated = {}
input_files = {}
for key, name in TAP_NAMES.items():
index, _dtype, shape = output_descriptors[name]
csv_path = workspace / "outputs" / f"output{index}_{sanitize_output_name(name)}.csv"
reference[key] = np.loadtxt(csv_path, delimiter=",", dtype=np.float32).reshape(shape)
simulated[key] = np.asarray(sim_arrays[index], dtype=np.float32).reshape(shape)
input_files[key] = csv_path
return reference, simulated, input_files
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--workspace", type=Path, required=True,
help="validator workspace containing inputs, outputs, and simulation")
parser.add_argument("--model", type=Path, required=True, help="five-output ONNX tap model")
parser.add_argument("--output-dir", type=Path, required=True,
help="directory for arrays.npz, metadata.json, and decomposition.json")
args = parser.parse_args()
args.output_dir.mkdir(parents=True, exist_ok=True)
model = onnx.load(args.model)
reference, simulated, source_files = load_arrays(args.workspace, args.model)
scale = np.float32(load_constant(model, "/model.10/m/m.0/attn/Constant_1_output_0"))
ref_v, ref_raw, ref_scaled, ref_rhs, ref_c = (reference[key] for key in ("v", "raw", "scaled", "rhs", "c"))
sim_v, sim_raw, sim_scaled, sim_rhs, sim_c = (simulated[key] for key in ("v", "raw", "scaled", "rhs", "c"))
ref_score_transpose = np.swapaxes(ref_scaled, -1, -2)
sim_score_transpose = np.swapaxes(sim_scaled, -1, -2)
ref_ss_f32 = f32_matmul(ref_v, ref_rhs)
sim_ss_f32 = f32_matmul(sim_v, sim_rhs)
ref_ss_f64 = f64_matmul(ref_v, ref_rhs)
sim_ss_f64 = f64_matmul(sim_v, sim_rhs)
ref_split_f32 = (f32_matmul(ref_v, np.swapaxes(ref_raw, -1, -2)) * scale).astype(np.float32)
sim_split_f32 = (f32_matmul(sim_v, np.swapaxes(sim_raw, -1, -2)) * scale).astype(np.float32)
ref_split_f64 = f64_matmul(ref_v, np.swapaxes(ref_raw, -1, -2)) * np.float64(scale)
sim_split_f64 = f64_matmul(sim_v, np.swapaxes(sim_raw, -1, -2)) * np.float64(scale)
arrays = {
**{f"ref_{key}": value for key, value in reference.items()},
**{f"sim_{key}": value for key, value in simulated.items()},
"ref_ss_f32": ref_ss_f32,
"sim_ss_f32": sim_ss_f32,
"ref_ss_f64": ref_ss_f64,
"sim_ss_f64": sim_ss_f64,
"ref_split_f32": ref_split_f32,
"sim_split_f32": sim_split_f32,
"ref_split_f64": ref_split_f64,
"sim_split_f64": sim_split_f64,
}
arrays_path = args.output_dir / "arrays.npz"
np.savez_compressed(arrays_path, **arrays)
metrics = {
"validator_policy": {
"absolute_tolerance": ABSOLUTE_TOLERANCE,
"relative_tolerance": RELATIVE_TOLERANCE,
},
"scale": float(scale),
"shape": list(ref_c.shape),
"tap_differences": {key: metric(simulated[key], reference[key]) for key in TAP_NAMES},
"rhs_transpose_consistency": metric(ref_rhs, ref_score_transpose),
"sim_rhs_transpose_consistency": metric(sim_rhs, sim_score_transpose),
"c_sim_vs_ss_f32": metric(sim_c, ref_ss_f32),
"c_ref_vs_ss_f32": metric(ref_c, ref_ss_f32),
"c_sim_vs_simulated_inputs_ss_f32": metric(sim_c, sim_ss_f32),
"v_drift_only": metric(f32_matmul(sim_v, ref_rhs), ref_ss_f32),
"rhs_drift_only": metric(f32_matmul(ref_v, sim_rhs), ref_ss_f32),
"joint_input_drift": metric(sim_ss_f32, ref_ss_f32),
"scale_reassociation_reference": metric(ref_split_f32, ref_ss_f32),
"scale_reassociation_simulated": metric(sim_split_f32, sim_ss_f32),
"reference_accumulation_f32_vs_f64": metric(ref_ss_f32, ref_ss_f64),
"simulated_accumulation_f32_vs_f64": metric(sim_ss_f32, sim_ss_f64),
"split_accumulation_reference_f32_vs_f64": metric(ref_split_f32, ref_split_f64),
"split_accumulation_simulated_f32_vs_f64": metric(sim_split_f32, sim_split_f64),
}
decomposition_path = args.output_dir / "decomposition.json"
decomposition_path.write_text(json.dumps(metrics, indent=2) + "\n", encoding="utf-8")
metadata = {
"model": str(args.model),
"model_sha256": sha256(args.model),
"workspace": str(args.workspace),
"arrays_sha256": sha256(arrays_path),
"source_sha256": {key: sha256(path) for key, path in source_files.items()},
"simulator_output_sha256": sha256(args.workspace / "simulation" / "out.bin"),
"input_sha256": sha256(args.workspace / "inputs" / "in0.csv"),
"outputs": TAP_NAMES,
"arrays": {key: {"dtype": str(value.dtype), "shape": list(value.shape)} for key, value in arrays.items()},
}
(args.output_dir / "metadata.json").write_text(json.dumps(metadata, indent=2) + "\n", encoding="utf-8")
print(json.dumps(metrics, indent=2))
if __name__ == "__main__":
main()
@@ -0,0 +1,567 @@
#!/usr/bin/env python3
"""Generate an editable diagrams.net library for Conv lowering comparisons."""
from __future__ import annotations
import argparse
import xml.etree.ElementTree as ET
from dataclasses import dataclass
from pathlib import Path
TILE = 760
GAP = 40
COLS = 4
INK = "#172033"
MUTED = "#667085"
GRID = "#d7deea"
PALE = "#f8fafc"
REFERENCE = "#5f6b7a"
PIMCOMP = "#ef8354"
RAPTOR = "#3b82f6"
INPUT = ("#f4a261", "#52b788", "#4895ef")
WEIGHT = ("#c86418", "#237a57", "#2768b2")
OUTPUT = ("#8b5cf6", "#ec4899", "#06b6d4", "#eab308")
SPATIAL = ("00", "01", "02", "10", "11", "12", "20", "21", "22")
@dataclass(frozen=True)
class Tile:
slug: str
owner: str
title: str
subtitle: str
scene: str
formula: str
algorithm: str
TILES = (
Tile("classic-reference", "REFERENCE", "Classic Conv + exact weight unfolding",
"Original OIHW weights; the two implementations choose different K orders.",
"reference", "Y[p,o] = Σc,kh,kw Xpatch[p,c,kh,kw] · W[o,c,kh,kw]",
"At every output position, multiply the patch by one filter and add every product."),
Tile("pimcomp-element", "PIMCOMP", "Element pipeline",
"One patch vector per input cycle; mapped weights stay fixed.",
"pimcomp_element", "patchPIM[1×K] · WflatPIM[K×O] → Yp[1×O]",
"Keep Wflat in the arrays; stream one patch each cycle to produce all O outputs."),
Tile("pimcomp-batch", "PIMCOMP", "Batch / replicated pipeline",
"Complete Wflat copies divide patches or input samples.",
"pimcomp_batch", "for replica r: Yr = patchr[1×K] · WflatPIM[K×O]",
"Copy all weights R times and send different patches to the copies in parallel."),
Tile("raptor-legacy-im2col", "RAPTOR", "Legacy explicit im2col",
"Every patch becomes one row of a global P×K matrix.",
"legacy", "Y[P×O] = im2col(X)[P×K] · WflatR[K×O]",
"Write every image patch as one matrix row, then multiply the two large matrices."),
Tile("raptor-packed-im2col", "RAPTOR", "Packed im2col",
"Pack q patch rows and repeat Wflat on a block diagonal.",
"packed", "packedY[1×qO] = [patch0|…|patchq1] · diag(WflatR,…,WflatR)",
"Join q patches and use diagonal weight copies so one multiply computes q independent outputs."),
Tile("raptor-streamed-patch", "RAPTOR", "Streamed patch",
"Gather one patch into bounded scratch; avoid global im2col.",
"streamed_patch", "Yp[1×O] = scratchPatchp[1×K] · WflatR[K×O]",
"Gather one patch, multiply it, write its output, and reuse scratch for the next patch."),
Tile("raptor-streamed-packed", "RAPTOR", "Streamed packed",
"Gather q patch rows in bounded scratch, then block-diagonal pack them.",
"streamed_packed", "packedY = packedScratch[1×qK] · diag(WflatR×q)[qK×qO]",
"Gather q patches in small scratch, join them, multiply by diagonal weights, then unpack q outputs."),
Tile("raptor-depthwise", "RAPTOR", "Depthwise special case",
"Each channel owns one row-major 3×3 kernel; channels never reduce together.",
"depthwise", "Y[p,c] = Σkh,kw Xpatch[p,c,kh,kw] · W[c,kh,kw]",
"For each channel separately, multiply its nine patch values by its nine weights and add."),
Tile("raptor-output-channel-tiled", "RAPTOR", "Output-channel tiled",
"Every O tile retains all channel-major K rows and selects output columns.",
"c_tiled", "Y[:,Oj] = patch[1×K] · Wflat[:,Oj][K×|Oj|]; concat j",
"Reuse the full patch for each output-filter group, then join the output groups."),
Tile("raptor-input-k-tiled", "RAPTOR", "Input-K tiled",
"Split matching K ranges; add their partial output vectors.",
"k_tiled", "Y[1×O] = Σi patch[Ki] · Wflat[Ki,:]",
"Multiply matching K slices independently, then add their partial output vectors."),
Tile("raptor-tiled-2d", "RAPTOR", "Two-dimensional tiled",
"Partition both K rows and output-filter columns.",
"tiled_2d", "Y[:,Oj] = Σi patch[Ki] · Wflat[Ki,Oj]; concat j",
"Split both directions: add results down K and join results across output groups."),
Tile("raptor-row-strip", "RAPTOR", "Pixel-major row-strip",
"A lane forms patches across one output row and slices K.",
"row_strip", "for x: Y[r,x,:] = Σi patch[r,x,Ki] · Wflat[Ki,:]",
"Move across one output row; at each x form a patch, multiply its K slices, and add."),
Tile("raptor-row-strip-c-tiled", "RAPTOR", "Row-strip + output tiling",
"Each row lane is duplicated across disjoint output-column tiles.",
"row_strip_c", "for x,j: Y[r,x,Oj] = patch[r,x,:] · Wflat[:,Oj]",
"Give each output-filter group a copy of the row lane, then join their output columns."),
)
class Drawio:
def __init__(self) -> None:
rows = (len(TILES) + COLS - 1) // COLS
self.mxfile = ET.Element("mxfile", host="app.diagrams.net", compressed="false")
diagram = ET.SubElement(self.mxfile, "diagram", id="conv-lowering-library",
name="Conv lowering tile library")
model = ET.SubElement(
diagram, "mxGraphModel", dx="1200", dy="900", grid="1", gridSize="10",
guides="1", tooltips="1", connect="1", arrows="1", fold="1",
page="0", pageScale="1", pageWidth=str(COLS * (TILE + GAP)),
pageHeight=str(rows * (TILE + GAP)), math="0", shadow="0",
)
self.root = ET.SubElement(model, "root")
ET.SubElement(self.root, "mxCell", id="0")
ET.SubElement(self.root, "mxCell", id="1", parent="0")
self.counter = 2
def _id(self, prefix: str = "c") -> str:
value = f"{prefix}-{self.counter}"
self.counter += 1
return value
def vertex(self, parent: str, x: float, y: float, w: float, h: float,
value: str = "", style: str = "", *, cell_id: str | None = None) -> str:
cell_id = cell_id or self._id()
cell = ET.SubElement(self.root, "mxCell", id=cell_id, value=value,
style=style, vertex="1", parent=parent)
ET.SubElement(cell, "mxGeometry", x=str(x), y=str(y), width=str(w),
height=str(h), **{"as": "geometry"})
return cell_id
def group(self, x: float, y: float, name: str) -> str:
return self.vertex("1", x, y, TILE, TILE, name,
"group;connectable=0;", cell_id=f"tile-{name}")
def edge(self, parent: str, source: str, target: str, value: str = "") -> str:
style = (
"edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;"
f"html=1;endArrow=block;endFill=1;strokeWidth=2;strokeColor={INK};"
f"fontSize=10;fontColor={INK};labelBackgroundColor=#ffffff;"
)
cell_id = self._id("e")
cell = ET.SubElement(self.root, "mxCell", id=cell_id, value=value,
style=style, edge="1", parent=parent,
source=source, target=target)
ET.SubElement(cell, "mxGeometry", relative="1", **{"as": "geometry"})
return cell_id
def write(self, path: Path) -> None:
ET.indent(self.mxfile, space=" ")
path.parent.mkdir(parents=True, exist_ok=True)
ET.ElementTree(self.mxfile).write(path, encoding="utf-8", xml_declaration=True)
def rect_style(fill: str = "#ffffff", stroke: str = GRID, *, rounded: bool = True,
font_size: int = 11, font_color: str = INK, align: str = "center",
stroke_width: int = 1) -> str:
return (
f"rounded={int(rounded)};whiteSpace=wrap;html=1;fillColor={fill};"
f"strokeColor={stroke};strokeWidth={stroke_width};fontColor={font_color};"
f"fontSize={font_size};fontFamily=Helvetica;align={align};verticalAlign=middle;"
)
def text_style(size: int, *, color: str = INK, align: str = "left", bold: bool = False) -> str:
return (
"text;html=1;strokeColor=none;fillColor=none;whiteSpace=wrap;"
f"fontSize={size};fontColor={color};fontFamily=Helvetica;align={align};"
f"verticalAlign=middle;fontStyle={1 if bold else 0};"
)
def add_text(d: Drawio, parent: str, x: float, y: float, w: float, h: float,
value: str, size: int = 11, *, color: str = INK,
align: str = "left", bold: bool = False) -> str:
return d.vertex(parent, x, y, w, h, value,
text_style(size, color=color, align=align, bold=bold))
def add_box(d: Drawio, parent: str, x: float, y: float, w: float, h: float,
value: str, *, fill: str = "#ffffff", stroke: str = GRID,
size: int = 11, rounded: bool = True, stroke_width: int = 1) -> str:
return d.vertex(parent, x, y, w, h, value,
rect_style(fill, stroke, rounded=rounded, font_size=size,
stroke_width=stroke_width))
def matrix3(d: Drawio, parent: str, x: float, y: float, size: float,
color: str, *, labels: bool = True) -> None:
cell = size / 3
for row in range(3):
for col in range(3):
value = SPATIAL[row * 3 + col] if labels else ""
d.vertex(parent, x + col * cell, y + row * cell, cell, cell, value,
rect_style(color, "#ffffff", rounded=False,
font_size=7, stroke_width=1))
d.vertex(parent, x, y, size, size, "",
"rounded=0;whiteSpace=wrap;html=1;fillColor=none;"
f"strokeColor={INK};strokeWidth=1;")
def source_panel(d: Drawio, parent: str) -> None:
add_text(d, parent, 34, 132, 230, 24, "Input patch X[:,C,3,3]", 12, bold=True)
for channel, color in enumerate(INPUT):
x = 34 + channel * 70
matrix3(d, parent, x, 166, 48, color)
add_text(d, parent, x, 216, 48, 18, f"C{channel}", 9,
color=color, align="center", bold=True)
add_text(d, parent, 340, 132, 380, 24, "Original W[O,C,3,3] (OIHW)",
12, bold=True, align="center")
for output, outline in enumerate(OUTPUT):
x = 340 + output * 96
add_box(d, parent, x, 158, 88, 78, "", fill="#ffffff",
stroke=outline, stroke_width=2)
for channel, color in enumerate(WEIGHT):
gx = x + 7 + channel * 25
matrix3(d, parent, gx, 181, 19, color, labels=False)
add_text(d, parent, gx, 163, 19, 16, f"C{channel}", 7,
color=color, align="center", bold=True)
add_text(d, parent, x, 216, 88, 18, f"filter O{output}", 9,
color=outline, align="center", bold=True)
def order_sequence(order: str) -> list[tuple[int, str]]:
if order == "raptor":
return [(channel, spatial) for channel in range(3) for spatial in SPATIAL]
if order == "pimcomp":
return [(channel, spatial) for spatial in SPATIAL for channel in range(3)]
raise ValueError(order)
def order_vector(d: Drawio, parent: str, y: float, order: str, *,
weight: bool, label: str) -> None:
x, width, height = 125, 595, 28
sequence = order_sequence(order)
cell_w = width / len(sequence)
colors = WEIGHT if weight else INPUT
for index, (channel, spatial) in enumerate(sequence):
value = f"w{spatial}" if weight else (spatial if order == "raptor" else f"C{channel}")
font_color = "#ffffff" if weight else INK
d.vertex(parent, x + index * cell_w, y, cell_w, height, value,
rect_style(colors[channel], "#ffffff", rounded=False,
font_size=7, font_color=font_color))
d.vertex(parent, x, y, width, height, "",
f"rounded=0;fillColor=none;strokeColor={INK};strokeWidth=1;")
add_text(d, parent, 35, y, 82, height, label, 8, color=MUTED,
align="right", bold=True)
if order == "raptor":
for channel in range(3):
add_text(d, parent, x + channel * width / 3, y - 18, width / 3, 16,
f"C{channel}: row-major 00→01→02→10→…→22", 8,
color=colors[channel], align="center", bold=True)
else:
for spatial_index, spatial in enumerate(SPATIAL):
add_text(d, parent, x + spatial_index * width / 9, y - 18,
width / 9, 16, f"({spatial}) C0,C1,C2", 7,
color=MUTED, align="center", bold=True)
def node(d: Drawio, parent: str, x: float, y: float, w: float, h: float,
label: str, *, fill: str = PALE, stroke: str = RAPTOR) -> str:
return add_box(d, parent, x, y, w, h, label, fill=fill, stroke=stroke,
size=10, stroke_width=2)
def layout_strip(d: Drawio, parent: str, x: float, y: float, w: float, h: float,
order: str, colors: tuple[str, ...], *, channel: int | None = None,
repeat: int = 1) -> None:
sequence = ([channel] * 9 if channel is not None
else [item[0] for item in order_sequence(order)])
row_h = h / repeat
for copy in range(repeat):
cell_w = w / len(sequence)
for index, color_index in enumerate(sequence):
d.vertex(parent, x + index * cell_w, y + copy * row_h,
cell_w, row_h, "",
rect_style(colors[color_index], "#ffffff", rounded=False,
font_size=1))
d.vertex(parent, x, y, w, h, "",
f"rounded=0;fillColor=none;strokeColor={INK};strokeWidth=1;")
def layout_node(d: Drawio, parent: str, x: float, y: float, w: float, h: float,
label: str, *, order: str | None = None,
colors: tuple[str, ...] = INPUT, channel: int | None = None,
repeat: int = 1, stroke: str = RAPTOR) -> str:
result = node(d, parent, x, y, w, h, "", fill="#ffffff", stroke=stroke)
label_height = h - (28 if order is not None else 10)
add_text(d, parent, x + 6, y + 5, w - 12, label_height, label, 10,
align="center", bold=True)
if order is not None:
layout_strip(d, parent, x + 8, y + h - 19, w - 16, 12, order, colors,
channel=channel, repeat=repeat)
return result
def scene_linear(d: Drawio, parent: str,
items: tuple[tuple[str, dict | None], ...], *,
y: float = 320) -> None:
margin, gap = 42, 34
width = (TILE - 2 * margin - gap * (len(items) - 1)) / len(items)
ids = []
for index, (label, layout) in enumerate(items):
x = margin + index * (width + gap)
ids.append(layout_node(d, parent, x, y, width, 92, label,
**(layout or {})))
for left, right in zip(ids, ids[1:]):
d.edge(parent, left, right)
def scene_batch(d: Drawio, parent: str) -> None:
for row in range(3):
y = 260 + row * 110
patch = layout_node(d, parent, 42, y, 210, 72,
f"patches p{row}, p{row + 3}, … [1×K]",
order="pimcomp", colors=INPUT, stroke=PIMCOMP)
weights = layout_node(d, parent, 302, y, 220, 72,
f"replica R{row}: Wflat [K×O]",
order="pimcomp", colors=WEIGHT, stroke=PIMCOMP)
result = node(d, parent, 610, y, 106, 72, f"Yp\n[1×O]",
fill=OUTPUT[row], stroke=PIMCOMP)
d.edge(parent, patch, weights, "×")
d.edge(parent, weights, result)
def scene_depthwise(d: Drawio, parent: str) -> None:
for channel in range(3):
y = 260 + channel * 110
patch = layout_node(d, parent, 46, y, 190, 72,
f"patch C{channel} [1×9]", order="raptor",
colors=INPUT, channel=channel)
kernel = layout_node(d, parent, 300, y, 210, 72,
f"W[C{channel},0,:,:] [9×1]", order="raptor",
colors=WEIGHT, channel=channel)
result = node(d, parent, 578, y, 136, 72,
f"Y channel {channel}", fill=OUTPUT[channel])
d.edge(parent, patch, kernel, "×")
d.edge(parent, kernel, result)
def scene_c_tiled(d: Drawio, parent: str) -> None:
for row, outputs in enumerate(((0, 1), (2, 3))):
y = 280 + row * 130
patch = layout_node(d, parent, 42, y, 190, 84,
"same full patch [1×K]", order="raptor",
colors=INPUT)
weights = layout_node(
d, parent, 292, y, 244, 84,
f"Wflat[:,O{outputs[0]}:O{outputs[-1] + 1}]\n[K×2], all K rows",
order="raptor", colors=WEIGHT, stroke=OUTPUT[outputs[0]])
result = node(d, parent, 610, y, 106, 84,
f"Y tile {row}\n[1×2]", fill=OUTPUT[outputs[0]])
d.edge(parent, patch, weights, "×")
d.edge(parent, weights, result)
add_text(d, parent, 250, 552, 260, 22, "concatenate tile 0 | tile 1 along O",
10, color=MUTED, align="center", bold=True)
def scene_k_tiled(d: Drawio, parent: str) -> None:
for row in range(3):
y = 250 + row * 100
patch = layout_node(d, parent, 42, y, 188, 70,
f"patch Ki{row}: C{row} [1×9]", order="raptor",
colors=INPUT, channel=row)
weights = layout_node(d, parent, 298, y, 224, 70,
f"Wflat[Ki{row},:] [9×O]", order="raptor",
colors=WEIGHT, channel=row)
partial = node(d, parent, 596, y, 120, 70,
f"partial {row}\n[1×O]", fill=PALE)
d.edge(parent, patch, weights, "×")
d.edge(parent, weights, partial)
node(d, parent, 300, 570, 160, 46, "VADD Σ → Y [1×O]", fill="#ffffff")
add_text(d, parent, 470, 578, 238, 28,
"partial 0 + partial 1 + partial 2", 9,
color=MUTED, align="center", bold=True)
def scene_2d(d: Drawio, parent: str) -> None:
for row in range(3):
y = 250 + row * 100
patch = layout_node(d, parent, 38, y, 160, 70,
f"patch Ki{row} [1×9]", order="raptor",
colors=INPUT, channel=row)
for col in range(2):
layout_node(d, parent, 270 + col * 230, y, 180, 70,
f"× Wflat[Ki{row},Oj{col}] [9×2]",
order="raptor", colors=WEIGHT, channel=row,
stroke=OUTPUT[col * 2])
add_text(d, parent, 270, 566, 440, 24,
"Σ tile rows along K; concatenate tile columns along O", 10,
color=MUTED, align="center", bold=True)
def scene_row_strip_c(d: Drawio, parent: str) -> None:
patch = layout_node(d, parent, 42, 330, 206, 88,
"row-window patch for x [1×K]", order="raptor",
colors=INPUT)
for col, outputs in enumerate(((0, 1), (2, 3))):
y = 270 + col * 150
weights = layout_node(
d, parent, 310, y, 238, 88,
f"lane r × O{outputs[0]}:O{outputs[-1] + 1}\nWflat[:,Oj] [K×2]",
order="raptor", colors=WEIGHT, stroke=OUTPUT[outputs[0]])
result = node(d, parent, 614, y, 104, 88,
f"Yj\n[1×2]", fill=OUTPUT[outputs[0]])
d.edge(parent, patch, weights, "reuse ×")
d.edge(parent, weights, result)
node(d, parent, 500, 570, 190, 46, "concat O → output row [1×4]")
def operation_scene(d: Drawio, parent: str, scene: str) -> None:
add_text(d, parent, 30, 204, 700, 22, "LOWERED COMPUTE", 11,
color=MUTED, align="center", bold=True)
if scene == "pimcomp_element":
scene_linear(d, parent, (
("input cycle p\npatchPIM [1×K]",
{"order": "pimcomp", "colors": INPUT, "stroke": PIMCOMP}),
("mapped Array Group\nWflatPIM [K×O]",
{"order": "pimcomp", "colors": WEIGHT, "stroke": PIMCOMP}),
("Yp [1×O]", {"stroke": PIMCOMP}),
))
elif scene == "pimcomp_batch":
scene_batch(d, parent)
elif scene == "legacy":
scene_linear(d, parent, (
("global im2col\n[P×K]", {"order": "raptor", "colors": INPUT}),
("WflatR\n[K×O]", {"order": "raptor", "colors": WEIGHT}),
("Y rows\n[P×O]", None),
))
elif scene == "packed":
scene_linear(d, parent, (
("q patch rows\n[q×K]",
{"order": "raptor", "colors": INPUT, "repeat": 2}),
("pack → [1×qK]",
{"order": "raptor", "colors": INPUT, "repeat": 2}),
("diag(WflatR×q)\n[qK×qO]",
{"order": "raptor", "colors": WEIGHT, "repeat": 2}),
("packed Y\n[1×qO]", None),
))
elif scene == "streamed_patch":
scene_linear(d, parent, (
("gather one patch", None),
("bounded scratch\n[1×K]", {"order": "raptor", "colors": INPUT}),
("WflatR\n[K×O]", {"order": "raptor", "colors": WEIGHT}),
("Yp\n[1×O]", None),
))
elif scene == "streamed_packed":
scene_linear(d, parent, (
("q patches\n[q×K]",
{"order": "raptor", "colors": INPUT, "repeat": 2}),
("packedScratch\n[1×qK]",
{"order": "raptor", "colors": INPUT, "repeat": 2}),
("diag(WflatR×q)\n[qK×qO]",
{"order": "raptor", "colors": WEIGHT, "repeat": 2}),
("packed Y\n[1×qO]", None),
))
elif scene == "depthwise":
scene_depthwise(d, parent)
elif scene == "c_tiled":
scene_c_tiled(d, parent)
elif scene == "k_tiled":
scene_k_tiled(d, parent)
elif scene == "tiled_2d":
scene_2d(d, parent)
elif scene == "row_strip":
scene_linear(d, parent, (
("lane r row windows", None),
("for x: patch\n[1×K]", {"order": "raptor", "colors": INPUT}),
("K-sliced Wflat\n[Ki×O]", {"order": "raptor", "colors": WEIGHT}),
("Σ partials →\noutput row", None),
))
elif scene == "row_strip_c":
scene_row_strip_c(d, parent)
else:
raise ValueError(scene)
def reference_body(d: Drawio, parent: str) -> None:
add_box(d, parent, 24, 252, 712, 158, "", fill="#ffffff", stroke=PIMCOMP)
add_text(d, parent, 40, 260, 680, 22,
"PIMCOMP: spatial-major, row-wise positions; C interleaved", 11,
color=PIMCOMP, align="center", bold=True)
order_vector(d, parent, 306, "pimcomp", weight=False, label="Input patch")
order_vector(d, parent, 366, "pimcomp", weight=True, label="Matching W")
add_text(d, parent, 40, 394, 680, 16,
"k=((kh·Kw)+kw)·Cin+c — c changes fastest", 9,
color=PIMCOMP, align="center", bold=True)
add_box(d, parent, 24, 424, 712, 158, "", fill="#ffffff", stroke=RAPTOR)
add_text(d, parent, 40, 432, 680, 22,
"RAPTOR: channel-major; each 3×3 plane is row-major", 11,
color=RAPTOR, align="center", bold=True)
order_vector(d, parent, 478, "raptor", weight=False, label="Input patch")
order_vector(d, parent, 538, "raptor", weight=True, label="Matching W")
add_text(d, parent, 40, 566, 680, 16,
"k=((c·Kh)+kh)·Kw+kw — kw changes fastest", 9,
color=RAPTOR, align="center", bold=True)
add_box(d, parent, 120, 590, 520, 42, "", fill=PALE, stroke=GRID)
add_text(d, parent, 132, 594, 496, 34,
"Shade key: light = activation; dark = matching weight row. "
"Depthwise: independent C0/C1/C2 row-major Kc=9 vectors.",
9, color=MUTED, align="center", bold=True)
def layout_reference(d: Drawio, parent: str, tile: Tile, accent: str) -> None:
if tile.scene == "depthwise":
layout = "independent row-major Kc=9 per channel"
elif tile.owner == "PIMCOMP":
layout = "PIMCOMP spatial-major K order"
else:
layout = "RAPTOR channel-major K order"
add_box(d, parent, 24, 140, 712, 42, "", fill=PALE, stroke=accent)
add_text(d, parent, 38, 146, 684, 30,
f"LAYOUT → see REFERENCE tile: {layout}", 10,
color=accent, align="center", bold=True)
def algorithm_card(d: Drawio, parent: str, tile: Tile, accent: str) -> None:
add_box(d, parent, 24, 638, 712, 102, "", fill="#ffffff", stroke=accent)
add_text(d, parent, 40, 646, 90, 34, "ALGORITHM", 9,
color=accent, bold=True)
add_text(d, parent, 132, 644, 588, 38, tile.algorithm, 10)
add_text(d, parent, 40, 690, 90, 34, "MATH", 9,
color=accent, bold=True)
add_text(d, parent, 132, 686, 588, 42, tile.formula, 10,
color=accent, bold=True)
def render_tile(d: Drawio, tile: Tile, index: int) -> None:
col, row = index % COLS, index // COLS
parent = d.group(col * (TILE + GAP), row * (TILE + GAP), tile.slug)
accent = {"REFERENCE": REFERENCE, "PIMCOMP": PIMCOMP, "RAPTOR": RAPTOR}[tile.owner]
add_box(d, parent, 0, 0, TILE, TILE, "", fill="#fbfcff", stroke=accent,
stroke_width=3)
add_box(d, parent, 24, 20, 106, 28, tile.owner, fill=accent, stroke=accent,
size=10)
add_text(d, parent, 24, 56, 712, 36, tile.title, 22, bold=True)
add_text(d, parent, 24, 92, 712, 30, tile.subtitle, 11, color=MUTED)
if tile.scene == "reference":
source_panel(d, parent)
reference_body(d, parent)
else:
layout_reference(d, parent, tile, accent)
operation_scene(d, parent, tile.scene)
algorithm_card(d, parent, tile, accent)
def validate(root: ET.Element) -> None:
ids = [cell.get("id") for cell in root.findall(".//mxCell")]
assert len(ids) == len(set(ids)), "draw.io cell IDs must be unique"
assert {"0", "1"}.issubset(ids), "draw.io root cells are required"
groups = [cell for cell in root.findall(".//mxCell")
if cell.get("style") == "group;connectable=0;"]
assert len(groups) == len(TILES), "one editable group is required per tile"
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("-o", "--output", type=Path,
default=Path("conv_lowering_tiles.drawio"))
args = parser.parse_args()
diagram = Drawio()
for index, tile in enumerate(TILES):
render_tile(diagram, tile, index)
validate(diagram.mxfile)
diagram.write(args.output)
print(args.output)
return 0
if __name__ == "__main__":
raise SystemExit(main())
+438
View File
@@ -0,0 +1,438 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import re
import struct
import sys
from collections import Counter, namedtuple
from pathlib import Path
Event = namedtuple("Event", "op peer size ordinal instruction")
Program = namedtuple("Program", "events operations starts_inactive ends_with_send")
Transfer = namedtuple(
"Transfer",
"sender receiver size sender_ordinal receiver_ordinal sender_instruction receiver_instruction "
"sender_last_instruction receiver_last_instruction count",
)
CORE_FILE = re.compile(r"core_(\d+)\.(json|pim)$")
HEADER = struct.Struct("<4sII")
RECORD = struct.Struct("<BBBBiiii")
OPCODE_NAMES = (
"nop", "sldi", "sld", "sadd", "ssub", "smul", "saddi", "smuli", "setbw",
"mvmul", "vvadd", "vvsub", "vvmul", "vvdmul", "vvmax", "vvsll", "vvsra",
"vavg", "vrelu", "vtanh", "vsigm", "vsoftmax", "vmv", "vrsu", "vrsl",
"ld", "st", "lldi", "lmv", "send", "recv", "wait", "sync",
)
IGNORED_OPS = {"nop", "sldi", "lldi", "setbw"}
MEMORY_OPS = {"sld", "ld", "st", "vmv", "vrsu", "vrsl", "lmv"}
DISPLAY_ORDER = (
"ld", "lmv", "st", "sld", "vmv", "vrsu", "vrsl",
"vvmul", "mvmul", "vvdmul", "smul", "smuli",
"vvadd", "vvsub", "vvmax", "vavg", "sadd", "saddi", "ssub",
"vvsll", "vvsra", "vrelu", "vtanh", "vsigm", "vsoftmax", "wait", "sync",
)
DISPLAY_RANK = {op: rank for rank, op in enumerate(DISPLAY_ORDER)}
ARROW_HEIGHT = 2.0
def summarize_operations(operations: tuple[str, ...] | list[str]) -> str:
counts = Counter(op for op in operations if op not in IGNORED_OPS and op not in ("send", "recv"))
ordered = sorted(counts, key=lambda op: (DISPLAY_RANK.get(op, len(DISPLAY_RANK)), op))
groups = ([op for op in ordered if op in MEMORY_OPS], [op for op in ordered if op not in MEMORY_OPS])
rows = [
[(counts[op], op) for op in group[index : index + 2]]
for group in groups
for index in range(0, len(group), 2)
]
if not rows:
return ""
count_width = max(len(str(count)) for row in rows for count, _ in row)
op_width = max(len(op) for row in rows for _, op in row)
return '""' + "\\n".join(
" ".join(f"{count:>{count_width}}x {op:<{op_width}}" for count, op in row).rstrip()
for row in rows
) + '""'
def read_json(path: Path) -> Program:
instructions = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(instructions, list):
raise ValueError(f"{path}: expected a JSON instruction array")
operations = tuple(
str(instruction.get("op", "unknown")) if isinstance(instruction, dict) else "unknown"
for instruction in instructions
)
events = []
for index, instruction in enumerate(instructions):
if not isinstance(instruction, dict):
continue
op = instruction.get("op")
if op not in ("send", "recv"):
continue
try:
peer = int(instruction["core"])
size = int(instruction["size"])
except (KeyError, TypeError, ValueError) as error:
raise ValueError(f"{path}: invalid communication instruction {instruction!r}") from error
events.append(Event(op, peer, size, len(events), index))
starts_inactive = bool(
events
and events[0].op == "recv"
and all(
isinstance(instruction, dict) and instruction.get("op") == "sldi"
for instruction in instructions[:events[0].instruction]
)
)
last_op = instructions[-1].get("op") if instructions and isinstance(instructions[-1], dict) else None
return Program(events, operations, starts_inactive, last_op == "send")
def read_binary(path: Path) -> Program:
data = path.read_bytes()
if len(data) < HEADER.size:
raise ValueError(f"{path}: binary core file is too small")
magic, version, count = HEADER.unpack_from(data)
expected_size = HEADER.size + count * RECORD.size
if magic != b"PIMB":
raise ValueError(f"{path}: invalid PIM binary magic")
if version != 1:
raise ValueError(f"{path}: unsupported PIM binary version {version}")
if len(data) != expected_size:
raise ValueError(f"{path}: expected {expected_size} bytes, found {len(data)}")
events = []
operations = []
last_opcode = None
for index in range(count):
opcode, _, _, _, peer, _, _, size = RECORD.unpack_from(data, HEADER.size + index * RECORD.size)
last_opcode = opcode
operations.append(OPCODE_NAMES[opcode] if opcode < len(OPCODE_NAMES) else f"opcode_{opcode}")
if opcode in (29, 30):
events.append(Event("send" if opcode == 29 else "recv", peer, size, len(events), index))
starts_inactive = bool(
events
and events[0].op == "recv"
and all(data[HEADER.size + index * RECORD.size] == 1 for index in range(events[0].instruction))
)
return Program(events, tuple(operations), starts_inactive, last_opcode == 29)
def read_programs(directory: Path, artifact_format: str) -> dict[int, Program]:
candidates: dict[str, dict[int, Path]] = {"json": {}, "pim": {}}
for path in directory.iterdir():
match = CORE_FILE.fullmatch(path.name)
if match:
candidates[match.group(2)][int(match.group(1))] = path
if artifact_format == "auto":
artifact_format = "json" if candidates["json"] else "pim"
files = candidates[artifact_format]
if not files:
raise ValueError(f"{directory}: no core_*.{artifact_format} files found")
reader = read_json if artifact_format == "json" else read_binary
return {core: reader(path) for core, path in files.items()}
def match_transfers(programs: dict[int, Program]) -> list[Transfer]:
positions = {core: 0 for core in programs}
transfers = []
while True:
made_progress = False
for core in sorted(programs):
events = programs[core].events
position = positions[core]
if position == len(events):
continue
event = events[position]
if event.peer not in programs:
sender, receiver = (core, event.peer) if event.op == "send" else (event.peer, core)
transfers.append(
Transfer(
sender,
receiver,
event.size,
event.ordinal if event.op == "send" else None,
event.ordinal if event.op == "recv" else None,
event.instruction if event.op == "send" else None,
event.instruction if event.op == "recv" else None,
event.instruction if event.op == "send" else None,
event.instruction if event.op == "recv" else None,
1,
)
)
positions[core] += 1
made_progress = True
continue
peer_events = programs[event.peer].events
peer_position = positions[event.peer]
if peer_position == len(peer_events):
continue
peer_event = peer_events[peer_position]
if (
event.peer == core
or peer_event.peer != core
or peer_event.op == event.op
or peer_event.size != event.size
):
continue
send = event if event.op == "send" else peer_event
receive = peer_event if event.op == "send" else event
sender, receiver = (core, event.peer) if event.op == "send" else (event.peer, core)
transfers.append(
Transfer(
sender,
receiver,
event.size,
send.ordinal,
receive.ordinal,
send.instruction,
receive.instruction,
send.instruction,
receive.instruction,
1,
)
)
positions[core] += 1
positions[event.peer] += 1
made_progress = True
if made_progress:
continue
remaining = {
core: programs[core].events[position]
for core, position in positions.items()
if position < len(programs[core].events)
}
if not remaining:
return transfers
details = ", ".join(
f"core {core}: {event.op} {event.peer} ({event.size} B)"
for core, event in sorted(remaining.items())
)
raise ValueError(f"communication streams cannot be matched at {details}")
def visible_transfers(
transfers: list[Transfer],
selected: set[int],
programs: dict[int, Program],
) -> list[Transfer]:
visible = [transfer for transfer in transfers if transfer.sender in selected or transfer.receiver in selected]
grouped = []
for transfer in visible:
previous = grouped[-1] if grouped else None
can_group = (
previous is not None
and transfer.sender_ordinal is not None
and previous.sender == transfer.sender
and previous.receiver == transfer.receiver
and previous.sender_ordinal + previous.count == transfer.sender_ordinal
and (
transfer.receiver_ordinal is None
or previous.receiver_ordinal + previous.count == transfer.receiver_ordinal
)
)
if can_group:
sender_gap = programs[transfer.sender].operations[
previous.sender_last_instruction + 1 : transfer.sender_instruction
]
receiver_gap = (
programs[transfer.receiver].operations[
previous.receiver_last_instruction + 1 : transfer.receiver_instruction
]
if transfer.receiver in programs
else ()
)
can_group = not summarize_operations(sender_gap) and not summarize_operations(receiver_gap)
if can_group:
grouped[-1] = previous._replace(
size=previous.size + transfer.size,
sender_last_instruction=transfer.sender_last_instruction,
receiver_last_instruction=transfer.receiver_last_instruction,
count=previous.count + transfer.count,
)
else:
grouped.append(transfer)
return grouped
def collect_operation_notes(
cores: list[int],
transfers: list[Transfer],
programs: dict[int, Program],
) -> dict[int, list[tuple[int, str]]]:
notes: dict[int, list[tuple[int, str]]] = {}
last_instructions = {core: -1 for core in cores}
anchors = {core: -1 for core in cores}
for transfer_index, transfer in enumerate(transfers):
endpoints = {
transfer.sender: (transfer.sender_instruction, transfer.sender_last_instruction),
transfer.receiver: (transfer.receiver_instruction, transfer.receiver_last_instruction),
}
for core, (instruction, last_instruction) in endpoints.items():
if core not in last_instructions or instruction is None:
continue
summary = summarize_operations(
programs[core].operations[last_instructions[core] + 1 : instruction]
)
if summary:
notes.setdefault(anchors[core], []).append((core, summary))
last_instructions[core] = last_instruction
anchors[core] = transfer_index
for core in cores:
summary = summarize_operations(programs[core].operations[last_instructions[core] + 1 :])
if summary:
notes.setdefault(anchors[core], []).append((core, summary))
return notes
def note_height(summary: str) -> float:
return 2.5 + 1.6 * (summary.count("\\n") + 1)
def parallel_note_order(
notes: list[tuple[int, str]],
cores: list[int],
) -> list[tuple[int, str]]:
positions = {core: position for position, core in enumerate(cores)}
packed: list[list[tuple[int, str]]] = []
for note in sorted(notes, key=lambda item: positions[item[0]]):
row = next(
(
row
for row in packed
if positions[note[0]] - positions[row[-1][0]] > 1
),
None,
)
if row is None:
row = []
packed.append(row)
row.append(note)
return [note for row in packed for note in row]
def render_text(cores: list[int], transfers: list[Transfer], programs: dict[int, Program]) -> str:
aliases = {core: f"C{core}" for core in cores}
positions = {core: position for position, core in enumerate(cores)}
lines = [f'participant "Core {core}" as {aliases[core]}' for core in cores]
first_receives = {
core: next((event.ordinal for event in programs[core].events if event.op == "recv"), None)
for core in cores
}
active = {core for core in cores if not programs[core].starts_inactive}
lines.extend(f"activate {aliases[core]}" for core in cores if core in active)
notes = collect_operation_notes(cores, transfers, programs)
availability = [0.0] * (2 * len(cores) + 1)
cursor = 0.0
def emit(line: str, left: int, right: int, height: float) -> None:
nonlocal cursor
start = max(availability[left : right + 1])
gap = start - cursor
if abs(gap) >= 0.05:
value = f"{gap:.1f}".rstrip("0").rstrip(".")
lines.append(f"space {value}")
lines.append(line)
cursor = start + height
availability[left : right + 1] = [cursor] * (right - left + 1)
def emit_notes(anchor: int) -> None:
for core, summary in parallel_note_order(notes.get(anchor, []), cores):
center = 2 * positions[core] + 1
emit(f"note over {aliases[core]}:{summary}", center - 1, center + 1, note_height(summary))
emit_notes(-1)
for transfer_index, transfer in enumerate(transfers):
label = f"{transfer.size} B"
if transfer.count > 1:
label = f"{transfer.count} sends, {label}"
if transfer.sender in aliases and transfer.receiver in aliases:
sender = 2 * positions[transfer.sender] + 1
receiver = 2 * positions[transfer.receiver] + 1
line = f"{aliases[transfer.sender]}->(1){aliases[transfer.receiver]}:{label}"
left, right = sorted((sender, receiver))
elif transfer.receiver in aliases:
line = f"[->(1){aliases[transfer.receiver]}:{label}"
left, right = 0, 2 * positions[transfer.receiver] + 1
else:
line = f"{aliases[transfer.sender]}->(1)]:{label}"
left, right = 2 * positions[transfer.sender] + 1, len(availability) - 1
emit(line, left, right, ARROW_HEIGHT)
receiver = transfer.receiver
if (
receiver in aliases
and receiver not in active
and transfer.receiver_ordinal == first_receives[receiver]
):
lines.append(f"activate {aliases[receiver]}")
active.add(receiver)
sender = transfer.sender
if (
sender in active
and programs[sender].ends_with_send
and transfer.sender_ordinal + transfer.count == len(programs[sender].events)
):
lines.append(f"deactivate {aliases[sender]}")
active.remove(sender)
emit_notes(transfer_index)
lines.extend(f"deactivateafter {aliases[core]}" for core in cores if core in active)
return "\n".join(lines) + "\n"
def main() -> int:
parser = argparse.ArgumentParser(
description="Generate SequenceDiagram.org text for PIM communication and intervening work."
)
parser.add_argument("pim_dir", type=Path, help="Directory containing core_<id>.json or core_<id>.pim files")
selection = parser.add_mutually_exclusive_group(required=True)
selection.add_argument("--cores", nargs="+", type=int, help="Core lifelines to display, in column order")
selection.add_argument("--all-cores", action="store_true", help="Display every used core, ordered by core ID")
parser.add_argument("--format", choices=("auto", "json", "pim"), default="auto")
parser.add_argument("-o", "--output", type=Path, help="Text output path (default: stdout)")
args = parser.parse_args()
try:
if not args.pim_dir.is_dir():
raise ValueError(f"{args.pim_dir}: not a directory")
if args.cores is not None and len(set(args.cores)) != len(args.cores):
raise ValueError("--cores contains duplicates")
programs = read_programs(args.pim_dir, args.format)
cores = (
[core for core in sorted(programs) if programs[core].operations]
if args.all_cores
else args.cores
)
missing = [core for core in cores if core not in programs]
if missing:
raise ValueError(f"missing artifact for selected core(s): {', '.join(map(str, missing))}")
transfers = visible_transfers(match_transfers(programs), set(cores), programs)
diagram = render_text(cores, transfers, programs)
if args.output:
args.output.write_text(diagram, encoding="utf-8")
else:
sys.stdout.write(diagram)
except (OSError, ValueError) as error:
parser.error(str(error))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -285,6 +285,7 @@ def load_effective_hardware(args: argparse.Namespace) -> dict[str, int]:
def select_pimsim_config(args: argparse.Namespace, hardware: dict[str, int]) -> Path: def select_pimsim_config(args: argparse.Namespace, hardware: dict[str, int]) -> Path:
fallback: Path | None = None
for path in sorted(PIMSIM_CONFIG_DIR.glob(f"*/{args.pimsim_mode}_config.json")): for path in sorted(PIMSIM_CONFIG_DIR.glob(f"*/{args.pimsim_mode}_config.json")):
with open(path, encoding="utf-8") as f: with open(path, encoding="utf-8") as f:
config = json.load(f) config = json.load(f)
@@ -297,15 +298,44 @@ def select_pimsim_config(args: argparse.Namespace, hardware: dict[str, int]) ->
and matrix["xbar_size"] == [hardware["crossbar_size"]] * 2 and matrix["xbar_size"] == [hardware["crossbar_size"]] * 2
and network["layout"] == [hardware["mesh_rows"], hardware["mesh_cols"]] and network["layout"] == [hardware["mesh_rows"], hardware["mesh_cols"]]
and config["sim_config"]["sim_mode"] == (1 if args.pimsim_mode == "latency" else 0) and config["sim_config"]["sim_mode"] == (1 if args.pimsim_mode == "latency" else 0)
and config["sim_config"]["sim_time"] == args.pimsim_time_ms
): ):
return path if config["sim_config"]["sim_time"] == args.pimsim_time_ms:
return path
fallback = fallback or path
if fallback is not None:
return fallback
raise ValueError( raise ValueError(
f"No pre-generated {args.pimsim_mode} pimsim-nn config matches " f"No pre-generated {args.pimsim_mode} pimsim-nn config matches {hardware}"
f"{hardware} with sim_time={args.pimsim_time_ms}"
) )
def prepare_pimsim_config(
args: argparse.Namespace,
hardware: dict[str, int],
out_dir: Path,
) -> Path:
source = select_pimsim_config(args, hardware)
with open(source, encoding="utf-8") as f:
config = json.load(f)
if config["sim_config"]["sim_time"] == args.pimsim_time_ms:
return source
config["sim_config"]["sim_time"] = args.pimsim_time_ms
target = out_dir / "pimsim_config.json"
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(json.dumps(config, indent=2) + "\n", encoding="utf-8")
network_path = Path(config["chip_config"]["network_config"]["net_config_file_path"])
if not network_path.is_absolute():
network_path = source.parent / network_path
target_network = target.parent / Path(
config["chip_config"]["network_config"]["net_config_file_path"]
)
target_network.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(network_path, target_network)
return target
def compile_reference( def compile_reference(
args: argparse.Namespace, args: argparse.Namespace,
model_path: Path, model_path: Path,
@@ -1221,6 +1251,8 @@ def main():
help="Return a non-zero status if a stage or semantic validation fails.", help="Return a non-zero status if a stage or semantic validation fails.",
) )
args = parser.parse_args() args = parser.parse_args()
if args.pimsim_time_ms <= 0:
parser.error("--pimsim-time-ms must be positive")
if args.pimcomp_pipeline is None: if args.pimcomp_pipeline is None:
args.pimcomp_pipeline = "element" if args.pimsim_mode == "latency" else "batch" args.pimcomp_pipeline = "element" if args.pimsim_mode == "latency" else "batch"
@@ -1485,10 +1517,11 @@ def main():
if not args.skip_pimsim_nn and hardware["core_count"] > 0: if not args.skip_pimsim_nn and hardware["core_count"] > 0:
written_config = try_stage( written_config = try_stage(
failures, failures,
"Select pimsim-nn config", "Prepare pimsim-nn config",
select_pimsim_config, prepare_pimsim_config,
args, args,
hardware, hardware,
out_dir,
) )
if written_config is not None: if written_config is not None:
pimsim_config = written_config pimsim_config = written_config
@@ -1593,8 +1626,11 @@ def main():
"model": str(model_path), "model": str(model_path),
"hardware": hardware, "hardware": hardware,
"pimsim_mode": args.pimsim_mode, "pimsim_mode": args.pimsim_mode,
"pimsim_time_ms": args.pimsim_time_ms,
"pimcomp_pipeline": args.pimcomp_pipeline, "pimcomp_pipeline": args.pimcomp_pipeline,
"pimcomp_replication": args.pimcomp_replication, "pimcomp_replication": args.pimcomp_replication,
"pimcomp_config": str(args.pimcomp_config),
"raptor_extra_args": args.raptor_extra_arg,
"reused_raptor_report": optional_path(args.reuse_raptor_report.resolve()) if reuse_raptor else None, "reused_raptor_report": optional_path(args.reuse_raptor_report.resolve()) if reuse_raptor else None,
"failures": failures, "failures": failures,
"steps": [asdict(step) for step in steps], "steps": [asdict(step) for step in steps],
@@ -20,57 +20,87 @@ from raptor_validation.pimsim_nn import parse_pimsim_nn_metrics # noqa: E402
from raptor_validation.validate_one import STAGE_COLORS # noqa: E402 from raptor_validation.validate_one import STAGE_COLORS # noqa: E402
PIMCOMP_SOURCE = REPO / "third_party/PIMCOMP-NN" PIMCOMP_SOURCE = REPO / "third_party/PIMCOMP-NN"
PIMCOMP_CONFIG = REPO / "validation/pimsim_configs/pimcomp/arch-a/latency_config.json" PIMCOMP_CONFIGS = REPO / "validation/pimsim_configs/pimcomp"
COMPARE = Path(__file__).resolve().with_name("compare_raptor_pimcomp.py") COMPARE = Path(__file__).resolve().with_name("compare_raptor_pimcomp.py")
ARCHES = tuple(sorted(path.name for path in PIMCOMP_CONFIGS.iterdir() if path.is_dir()))
MODELS = { MODELS = {
"vgg8": SUITE / "vgg8/vgg8-mnist-reconstructed.onnx", "vgg8": SUITE / "vgg8/vgg8-mnist-reconstructed.onnx",
"resnet18": SUITE / "resnet18/resnet18-v1-7.onnx", "resnet18": SUITE / "resnet18/resnet18-v1-7.onnx",
"resnet34": SUITE / "resnet34/resnet34-v1-7.onnx", "resnet34": SUITE / "resnet34/resnet34-v1-7.onnx",
"googlenet": SUITE / "googlenet/googlenet-12-latency.onnx", "googlenet": SUITE / "googlenet/googlenet-12-latency.onnx",
} }
COMPARISONS = (
("latency", 1, "element"),
("throughput", 2, "batch"),
("throughput", 4, "batch"),
("throughput", 8, "batch"),
)
def result_dir(root: Path | None, name: str) -> Path: def result_dir(root: Path | None, name: str, mode: str, pipeline: int) -> Path:
return root / name if root is not None else MODELS[name].parent base = root / name if root is not None else MODELS[name].parent
suffix = "latency" if mode == "latency" else f"throughput/pipeline{pipeline}"
return base / suffix
def write_results_csv(root: Path | None) -> Path: def write_results_csv(root: Path | None, arch: str, models: list[str]) -> Path:
output = (root or SUITE) / "results.csv" output = (root or SUITE) / "results.csv"
fields = ( fields = (
"model", "model",
"arch",
"mode",
"raptor_pipeline",
"pimcomp_pipeline",
"status",
"raptor_throughput_samples_s",
"pimcomp_throughput_samples_s",
"raptor_latency_ms", "raptor_latency_ms",
"pimcomp_latency_ms", "pimcomp_latency_ms",
"raptor_power_mw",
"pimcomp_power_mw",
"raptor_energy_pj", "raptor_energy_pj",
"pimcomp_energy_pj", "pimcomp_energy_pj",
"faster_compiler", "better_compiler",
"speedup", "speedup",
) )
rows = [] rows = []
for name in MODELS: for name in models:
report_path = result_dir(root, name) / "pimcomp/comparison_report.json" for mode, pipeline, pimcomp_pipeline in COMPARISONS:
if not report_path.exists(): report_path = result_dir(root, name, mode, pipeline) / "pimcomp/comparison_report.json"
continue if not report_path.exists():
report = json.loads(report_path.read_text(encoding="utf-8")) continue
raptor = report.get("raptor_performance") or {} report = json.loads(report_path.read_text(encoding="utf-8"))
pimcomp = report.get("pimcomp_performance") or {} raptor = report.get("raptor_performance") or {}
raptor_latency = raptor.get("latency_ms") pimcomp = report.get("pimcomp_performance") or {}
pimcomp_latency = pimcomp.get("latency_ms") raptor_values = performance_values(raptor)
if raptor_latency is None or pimcomp_latency is None: pimcomp_values = performance_values(pimcomp)
continue raptor_metric = raptor_values["throughput"] if mode == "throughput" else raptor_values["latency"]
raptor_energy = (raptor.get("average_energy_pj") pimcomp_metric = pimcomp_values["throughput"] if mode == "throughput" else pimcomp_values["latency"]
or parse_pimsim_nn_metrics(raptor.get("raw_output", "")).get("average_energy_pj")) status = "PASS" if comparison_passed(report) else "FAIL"
pimcomp_energy = (pimcomp.get("average_energy_pj") if raptor_metric is None or pimcomp_metric is None:
or parse_pimsim_nn_metrics(pimcomp.get("raw_output", "")).get("average_energy_pj")) better = ""
faster = "raptor" if raptor_latency < pimcomp_latency else "pimcomp" speedup = ""
rows.append({ else:
"model": name, better = comparison_winner(mode, raptor_metric, pimcomp_metric)
"raptor_latency_ms": f"{raptor_latency:.6f}", speedup = f"{max(raptor_metric, pimcomp_metric) / min(raptor_metric, pimcomp_metric):.2f}"
"pimcomp_latency_ms": f"{pimcomp_latency:.6f}", rows.append({
"raptor_energy_pj": "" if raptor_energy is None else f"{raptor_energy:.6f}", "model": name,
"pimcomp_energy_pj": "" if pimcomp_energy is None else f"{pimcomp_energy:.6f}", "arch": arch,
"faster_compiler": faster, "mode": mode,
"speedup": f"{max(raptor_latency, pimcomp_latency) / min(raptor_latency, pimcomp_latency):.2f}", "raptor_pipeline": pipeline,
}) "pimcomp_pipeline": pimcomp_pipeline,
"status": status,
"raptor_throughput_samples_s": format_value(raptor_values["throughput"]),
"pimcomp_throughput_samples_s": format_value(pimcomp_values["throughput"]),
"raptor_latency_ms": format_value(raptor_values["latency"]),
"pimcomp_latency_ms": format_value(pimcomp_values["latency"]),
"raptor_power_mw": format_value(raptor_values["power"]),
"pimcomp_power_mw": format_value(pimcomp_values["power"]),
"raptor_energy_pj": format_value(raptor_values["energy"]),
"pimcomp_energy_pj": format_value(pimcomp_values["energy"]),
"better_compiler": better,
"speedup": speedup,
})
with open(output, "w", newline="", encoding="utf-8") as f: with open(output, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fields, lineterminator="\n") writer = csv.DictWriter(f, fieldnames=fields, lineterminator="\n")
writer.writeheader() writer.writeheader()
@@ -78,6 +108,47 @@ def write_results_csv(root: Path | None) -> Path:
return output return output
def performance_values(performance: dict) -> dict[str, float | None]:
parsed = parse_pimsim_nn_metrics(performance.get("raw_output", ""))
return {
"throughput": performance.get("throughput") or parsed.get("throughput"),
"latency": (
performance.get("latency_ms")
or performance.get("average_latency_ms")
or parsed.get("latency_ms")
or parsed.get("average_latency_ms")
),
"power": performance.get("average_power_mw") or parsed.get("average_power_mw"),
"energy": performance.get("average_energy_pj") or parsed.get("average_energy_pj"),
}
def comparison_passed(report: dict) -> bool:
if report.get("failures"):
return False
for key in ("raptor_validation", "pimcomp_validation"):
result = report.get(key) or {}
if result.get("status") != "done" or not result.get("passed"):
return False
for key in ("raptor_performance", "pimcomp_performance"):
performance = report.get(key) or {}
if performance.get("error") or performance.get("skipped"):
return False
return True
def comparison_winner(mode: str, raptor: float, pimcomp: float) -> str:
if raptor == pimcomp:
return "tie"
if mode == "throughput":
return "raptor" if raptor > pimcomp else "pimcomp"
return "raptor" if raptor < pimcomp else "pimcomp"
def format_value(value: float | None) -> str:
return "" if value is None else f"{value:.6f}"
def print_stage(title: str, color: str) -> None: def print_stage(title: str, color: str) -> None:
print("\n" + Style.BRIGHT + color + f"[{title}]" + Style.RESET_ALL, flush=True) print("\n" + Style.BRIGHT + color + f"[{title}]" + Style.RESET_ALL, flush=True)
@@ -98,7 +169,17 @@ def validate_pimcomp_source() -> None:
raise RuntimeError(f"PIMCOMP paper setting is missing: {setting}") raise RuntimeError(f"PIMCOMP paper setting is missing: {setting}")
def comparison_command(model: Path, result_dir: Path, timeout: float) -> list[str]: def comparison_command(
model: Path,
result_dir: Path,
config: Path,
mode: str,
pipeline: int,
pimcomp_pipeline: str,
pimsim_time_ms: int,
timeout: float,
) -> list[str]:
time_args = ["--pimsim-time-ms", str(pimsim_time_ms)] if mode == "throughput" else []
return [ return [
sys.executable, sys.executable,
str(COMPARE), str(COMPARE),
@@ -109,32 +190,49 @@ def comparison_command(model: Path, result_dir: Path, timeout: float) -> list[st
"--pimcomp-dir", "--pimcomp-dir",
str(PIMCOMP_SOURCE), str(PIMCOMP_SOURCE),
"--pimcomp-config", "--pimcomp-config",
str(PIMCOMP_CONFIG), str(config),
"--core-count",
"168",
"--crossbar-count",
"96",
"--crossbar-size",
"128",
"--mesh-rows",
"12",
"--mesh-cols",
"14",
"--pimsim-mode", "--pimsim-mode",
"latency", mode,
*time_args,
"--pimcomp-pipeline", "--pimcomp-pipeline",
"element", pimcomp_pipeline,
"--pimcomp-replication", "--pimcomp-replication",
"GA", "GA",
f"--raptor-extra-arg=--pipeline={pipeline}",
"--timeout-seconds", "--timeout-seconds",
str(timeout), str(timeout),
"--fail-on-error", "--fail-on-error",
] ]
def config_path(arch: str, mode: str) -> Path:
path = PIMCOMP_CONFIGS / arch / f"{mode}_config.json"
if not path.exists():
raise ValueError(f"{arch} has no {mode} config: {path}")
return path
def core_count(config: Path) -> int:
with open(config, encoding="utf-8") as f:
return int(json.load(f)["chip_config"]["core_cnt"])
def completed_report(path: Path, mode: str, pipeline: int, config: Path, pimsim_time_ms: int) -> bool:
if not path.exists():
return False
report = json.loads(path.read_text(encoding="utf-8"))
return (
report.get("pimsim_mode") == mode
and report.get("pimcomp_pipeline") == ("element" if mode == "latency" else "batch")
and report.get("pimsim_time_ms") == pimsim_time_ms
and report.get("pimcomp_config") == str(config.resolve())
and f"--pipeline={pipeline}" in report.get("raptor_extra_args", [])
)
def main() -> int: def main() -> int:
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Reproduce the serial Arch-A latency comparison from the PIMCOMP paper." description="Compare supported PIMCOMP models with Raptor latency and throughput schedules."
) )
parser.add_argument( parser.add_argument(
"--out-dir", "--out-dir",
@@ -142,6 +240,15 @@ def main() -> int:
help="Result root (default: artifacts beside each model under validation/).", help="Result root (default: artifacts beside each model under validation/).",
) )
parser.add_argument("--models", nargs="+", choices=MODELS, default=list(MODELS)) parser.add_argument("--models", nargs="+", choices=MODELS, default=list(MODELS))
parser.add_argument(
"--arch", choices=ARCHES, default="arch-a", help="PIM architecture (default: arch-a)."
)
parser.add_argument(
"--pimsim-time-ms",
type=int,
default=100,
help="throughput pimsim-nn horizon in ms (default: 100).",
)
parser.add_argument("--timeout-seconds", type=float, default=3600.0) parser.add_argument("--timeout-seconds", type=float, default=3600.0)
parser.add_argument( parser.add_argument(
"--resume", "--resume",
@@ -151,6 +258,16 @@ def main() -> int:
parser.add_argument("--dry-run", action="store_true", help="Print commands without modifying files.") parser.add_argument("--dry-run", action="store_true", help="Print commands without modifying files.")
args = parser.parse_args() args = parser.parse_args()
if args.pimsim_time_ms <= 0:
parser.error("--pimsim-time-ms must be positive")
configs = {mode: config_path(args.arch, mode) for mode, _, _ in COMPARISONS}
unsupported = [pipeline for mode, pipeline, _ in COMPARISONS if core_count(configs[mode]) % pipeline]
if unsupported:
parser.error(
f"{args.arch} has {core_count(configs['throughput'])} cores; "
f"throughput pipelines must divide that count (invalid: {unsupported})"
)
out_dir = args.out_dir.resolve() if args.out_dir is not None else None out_dir = args.out_dir.resolve() if args.out_dir is not None else None
missing = [str(MODELS[name]) for name in args.models if not MODELS[name].exists()] missing = [str(MODELS[name]) for name in args.models if not MODELS[name].exists()]
@@ -162,6 +279,8 @@ def main() -> int:
out_dir.mkdir(parents=True, exist_ok=True) out_dir.mkdir(parents=True, exist_ok=True)
print(Style.BRIGHT + f"Found {len(args.models)} PIMCOMP model(s) to compare." + Style.RESET_ALL) print(Style.BRIGHT + f"Found {len(args.models)} PIMCOMP model(s) to compare." + Style.RESET_ALL)
print(f"Architecture: {args.arch}")
print(f"Throughput pimsim time: {args.pimsim_time_ms} ms")
print(f"Results root: {out_dir or SUITE}") print(f"Results root: {out_dir or SUITE}")
print("=" * 72) print("=" * 72)
@@ -176,34 +295,51 @@ def main() -> int:
failed = [] failed = []
for index, name in enumerate(args.models, start=1): for index, name in enumerate(args.models, start=1):
model_result_dir = result_dir(out_dir, name) for mode, pipeline, pimcomp_pipeline in COMPARISONS:
print( model_result_dir = result_dir(out_dir, name, mode, pipeline)
"\n" + Fore.CYAN + f"[{index}/{len(args.models)}]" + Style.RESET_ALL
+ f" {Style.BRIGHT}Comparing {name}{Style.RESET_ALL}",
flush=True,
)
if args.resume and (model_result_dir / "pimcomp/comparison_report.json").exists():
print( print(
Fore.YELLOW + " Completed report exists; skipping" + Style.RESET_ALL, "\n" + Fore.CYAN + f"[{index}/{len(args.models)}]" + Style.RESET_ALL
+ f" {Style.BRIGHT}Comparing {name} ({mode}, pipeline={pipeline}){Style.RESET_ALL}",
flush=True, flush=True,
) )
continue if args.resume and completed_report(
returncode = run( model_result_dir / "pimcomp/comparison_report.json",
comparison_command(MODELS[name], model_result_dir, args.timeout_seconds), mode,
dry_run=args.dry_run, pipeline,
check=False, configs[mode],
) args.pimsim_time_ms,
if returncode: ):
failed.append(name) print(
Fore.YELLOW + " Completed report exists; skipping" + Style.RESET_ALL,
flush=True,
)
continue
returncode = run(
comparison_command(
MODELS[name],
model_result_dir,
configs[mode],
mode,
pipeline,
pimcomp_pipeline,
args.pimsim_time_ms,
args.timeout_seconds,
),
dry_run=args.dry_run,
check=False,
)
if returncode:
failed.append(f"{name}/{mode}/pipeline{pipeline}")
if args.dry_run: if args.dry_run:
return 1 if failed else 0 return 1 if failed else 0
results_path = write_results_csv(out_dir) results_path = write_results_csv(out_dir, args.arch, args.models)
print_stage("Results", STAGE_COLORS["Compare Outputs"]) print_stage("Results", STAGE_COLORS["Compare Outputs"])
print(results_path.read_text(encoding="utf-8"), end="") print(results_path.read_text(encoding="utf-8"), end="")
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: {len(args.models) - len(failed)}" + Style.RESET_ALL) total_jobs = len(args.models) * len(COMPARISONS)
print(Style.BRIGHT + f"Passed: {total_jobs - len(failed)}" + Style.RESET_ALL)
print(Style.BRIGHT + f"Failed: {len(failed)}" + Style.RESET_ALL) print(Style.BRIGHT + f"Failed: {len(failed)}" + Style.RESET_ALL)
print(Style.BRIGHT + f"Results: {results_path}" + Style.RESET_ALL) print(Style.BRIGHT + f"Results: {results_path}" + Style.RESET_ALL)
if failed: if failed: