Compare commits
6 Commits
10b6ee6c32
...
e2cefd3127
| Author | SHA1 | Date | |
|---|---|---|---|
| e2cefd3127 | |||
| 7a3a808ae8 | |||
| 0712c5ba29 | |||
| aeedf2f566 | |||
| a39fdba366 | |||
| a963009855 |
@@ -326,9 +326,13 @@ fn append_record(
|
||||
inst_builder.make_inst(recv, inst_data_builder.build());
|
||||
}
|
||||
31 => {
|
||||
inst_data_builder.set_offset_select_value(generic1, generic2);
|
||||
inst_builder.make_inst(wait, inst_data_builder.build());
|
||||
}
|
||||
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());
|
||||
}
|
||||
_ => bail!("unsupported PIM binary opcode {opcode}"),
|
||||
|
||||
@@ -601,7 +601,11 @@ fn json_to_wait(
|
||||
inst_data_builder: &mut InstructionDataBuilder,
|
||||
json: &Value,
|
||||
) -> 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(())
|
||||
}
|
||||
|
||||
@@ -610,7 +614,10 @@ fn json_to_sync(
|
||||
inst_data_builder: &mut InstructionDataBuilder,
|
||||
json: &Value,
|
||||
) -> 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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -93,6 +93,8 @@ struct DeadlockInfo {
|
||||
states: String,
|
||||
}
|
||||
|
||||
type SyncEvents = Vec<[i32; 32]>;
|
||||
|
||||
fn print_status(core_instructions: &[CoreInstructions]) {
|
||||
let mut tot_instructions = 0;
|
||||
let mut progress = 0;
|
||||
@@ -135,6 +137,7 @@ impl<'a> Executable<'a> {
|
||||
} = self;
|
||||
let mut cpu_progressed = 0;
|
||||
let max_core = cpu.num_core();
|
||||
let mut sync_events: SyncEvents = vec![[0; 32]; max_core];
|
||||
let mut cpu_index = 0;
|
||||
let mut now = SystemTime::now();
|
||||
|
||||
@@ -169,7 +172,9 @@ impl<'a> Executable<'a> {
|
||||
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) {
|
||||
(true, other_cpu_index) => {
|
||||
cpu_progressed = 0;
|
||||
@@ -349,12 +354,31 @@ fn detect_deadlock(cores_instructions: &[CoreInstructions]) -> Option<DeadlockIn
|
||||
None
|
||||
}
|
||||
|
||||
fn handle_wait_sync<'a, 'b, 'c>(
|
||||
cpu: &'b mut CPU<'a>,
|
||||
core_instructions: &'c mut [CoreInstructions],
|
||||
fn handle_wait_sync(
|
||||
core_instructions: &mut [CoreInstructions],
|
||||
events: &mut SyncEvents,
|
||||
core_result: InstructionStatus,
|
||||
) where
|
||||
'a: 'b,
|
||||
'a: 'c,
|
||||
{
|
||||
) -> bool {
|
||||
match core_result {
|
||||
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.receiving[receiver] = None;
|
||||
}
|
||||
(transfered, receiver)
|
||||
(transfered, if transfered { receiver } else { 0 })
|
||||
}
|
||||
InstructionStatus::Reciving(instruction_data) => {
|
||||
let (core_idx, imm_core) = instruction_data.get_core_immcore();
|
||||
@@ -163,7 +163,7 @@ where
|
||||
send_recv.sending[sender] = None;
|
||||
send_recv.receiving[receiver] = None;
|
||||
}
|
||||
(transfered, sender)
|
||||
(transfered, if transfered { sender } else { 0 })
|
||||
}
|
||||
_ => (false, 0),
|
||||
}
|
||||
|
||||
@@ -295,3 +295,68 @@ fn multiple_send_recv_test() {
|
||||
"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();
|
||||
}
|
||||
|
||||
Submodule backend-simulators/pim/pimsim-nn updated: 0d03316df4...6a3832525b
@@ -32,6 +32,9 @@ inline constexpr llvm::StringLiteral kCoreIdAttrName = "coreId";
|
||||
inline constexpr llvm::StringLiteral kCoreIdsAttrName = "coreIds";
|
||||
inline constexpr llvm::StringLiteral kLocalMemoryAddressAttrName = "pim.local_memory_address";
|
||||
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 = {
|
||||
"pim.local_memory_slot",
|
||||
"pim.local_memory_slot_size",
|
||||
|
||||
@@ -162,8 +162,8 @@ inline constexpr std::array<InstructionJsonFormat, kOpcodeCount> kInstructionJso
|
||||
{true, true, true, "", "", "", "len" }, // lmv
|
||||
{true, false, true, "core", "", "", "size"}, // send
|
||||
{true, false, true, "core", "", "", "size"}, // recv
|
||||
{false, false, false, "", "", "", "" }, // wait
|
||||
{false, false, false, "", "", "", "" }, // sync
|
||||
{false, false, false, "", "event_register", "wait_value", ""}, // wait
|
||||
{false, false, false, "core", "event_register", "", ""}, // sync
|
||||
}};
|
||||
static_assert(kInstructionJsonFormats.size() == kOpcodeCount);
|
||||
|
||||
|
||||
@@ -692,6 +692,34 @@ void PimCodeGen::codeGenSendOp(pim::PimSendOp sendOp, const StaticValueKnowledge
|
||||
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 {
|
||||
auto outputType = cast<ShapedType>(concatOp.getOutputBuffer().getType());
|
||||
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::Receive: coreCodeGen.codeGenReceiveOp(cast<pim::PimReceiveOp>(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::Vmm:
|
||||
if (auto weightSlot = resolveWeightSlot(cast<pim::PimVMMOp>(node.op), knowledge); succeeded(weightSlot))
|
||||
|
||||
@@ -217,6 +217,8 @@ public:
|
||||
|
||||
void codeGenReceiveOp(pim::PimReceiveOp receiveOp, 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;
|
||||
|
||||
template <typename MVMTy>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#define DEBUG_TYPE "PimCompilerOptions"
|
||||
|
||||
namespace onnx_mlir {
|
||||
@@ -110,6 +112,12 @@ 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));
|
||||
|
||||
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::desc("Number of cores in the chip. Required for PIM compilation."),
|
||||
llvm::cl::init(-1));
|
||||
@@ -129,4 +137,14 @@ void verifyExplicitPimCoreCount() {
|
||||
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
|
||||
|
||||
@@ -62,6 +62,7 @@ extern llvm::cl::opt<bool> pimVerifyBufferizationCopyFreedom;
|
||||
|
||||
extern llvm::cl::opt<size_t> crossbarSize;
|
||||
extern llvm::cl::opt<size_t> crossbarCountInCore;
|
||||
extern llvm::cl::opt<size_t> pipelineStages;
|
||||
extern llvm::cl::opt<long> coresCount;
|
||||
extern llvm::cl::opt<std::string> pimTargetConfig;
|
||||
extern llvm::cl::opt<uint64_t> pimConvIm2colMaxElements;
|
||||
@@ -69,5 +70,6 @@ extern llvm::cl::opt<uint64_t> pimConvStreamChunkPositions;
|
||||
|
||||
bool hasExplicitPimCoreCount();
|
||||
void verifyExplicitPimCoreCount();
|
||||
void verifyPimPipelineStages();
|
||||
|
||||
} // namespace onnx_mlir
|
||||
|
||||
@@ -330,6 +330,7 @@ void addPassesPim(OwningOpRef<ModuleOp>& module,
|
||||
EmissionTargetType& emissionTarget,
|
||||
std::string outputNameNoExt) {
|
||||
verifyExplicitPimCoreCount();
|
||||
verifyPimPipelineStages();
|
||||
spatial::SchedulingTarget schedulingTarget = getPimSchedulingTarget();
|
||||
spatial::SpatialTargetResources targetResources = getPimSpatialTargetResources(schedulingTarget);
|
||||
|
||||
@@ -354,7 +355,7 @@ void addPassesPim(OwningOpRef<ModuleOp>& module,
|
||||
pm.addPass(createTrivialGraphComputeMergePass(
|
||||
schedulingTarget.residentWeightCapacity, exportStage));
|
||||
pm.addPass(spatial::createScheduleAndRealizeSpatialPass(
|
||||
schedulingTarget, exportStage));
|
||||
schedulingTarget, exportStage, pipelineStages.getValue()));
|
||||
pm.addPass(createMessagePass("Onnx lowered to Spatial"));
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ static FailureOr<CompiledCoreOpKind> classifyCompiledCoreOpKind(Operation& op) {
|
||||
if (isa<pim::PimVMVOp>(op)) return CompiledCoreOpKind::VMV;
|
||||
if (isa<pim::PimReceiveOp>(op)) return CompiledCoreOpKind::Receive;
|
||||
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::PimVMMOp>(op)) return CompiledCoreOpKind::Vmm;
|
||||
if (isa<pim::PimVVAddOp>(op)) return CompiledCoreOpKind::VVAdd;
|
||||
|
||||
@@ -17,6 +17,8 @@ enum class CompiledCoreOpKind : uint8_t {
|
||||
VMV,
|
||||
Receive,
|
||||
Send,
|
||||
Wait,
|
||||
Sync,
|
||||
Concat,
|
||||
Vmm,
|
||||
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())) {
|
||||
size_t currentInputIndex = inputIndex;
|
||||
Operation* definingOp = input.getDefiningOp();
|
||||
if (allowChannelReceiveInputs && isa_and_nonnull<spatial::SpatChannelReceiveOp>(definingOp))
|
||||
if (allowChannelReceiveInputs
|
||||
&& isa_and_nonnull<spatial::SpatChannelReceiveOp,
|
||||
spatial::SpatHostWaitLoadOp>(definingOp))
|
||||
continue;
|
||||
if (isScheduledPhase1Value(input))
|
||||
continue;
|
||||
@@ -163,7 +165,8 @@ void verifyLogicalTopLevelOps(func::FuncOp funcOp, pim::CappedDiagnosticReporter
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (isa<spatial::SpatChannelReceiveOp, spatial::SpatChannelSendOp>(&op)) {
|
||||
if (isa<spatial::SpatChannelReceiveOp, spatial::SpatChannelSendOp,
|
||||
spatial::SpatHostStoreSyncOp, spatial::SpatHostWaitLoadOp>(&op)) {
|
||||
diagnostics.report(&op, [&](Operation* illegalOp) {
|
||||
illegalOp->emitOpError() << kPhaseMarker
|
||||
<< " 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) {
|
||||
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) {
|
||||
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/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/IR/BuiltinOps.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");
|
||||
}
|
||||
|
||||
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) {
|
||||
auto users = value.getUsers();
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "mlir/IR/Builders.h"
|
||||
#include "mlir/IR/Value.h"
|
||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
||||
#include "mlir/Support/LogicalResult.h"
|
||||
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
@@ -23,6 +24,12 @@ namespace onnx_mlir {
|
||||
mlir::FailureOr<mlir::IntegerAttr>
|
||||
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>
|
||||
size_t rangeLength(const mlir::iterator_range<T> range) {
|
||||
return std::distance(range.begin(), range.end());
|
||||
|
||||
@@ -345,20 +345,39 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeOp(spatial::SpatScheduledCom
|
||||
auto blockArg = computeOp.getInputArgument(inputIndex);
|
||||
if (!blockArg)
|
||||
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()) {
|
||||
rewriter.setInsertionPoint(getEarliestUserWithinBlock(*blockArg));
|
||||
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);
|
||||
if (failed(sizeAttr))
|
||||
return failure();
|
||||
Value received =
|
||||
PimReceiveOp::create(
|
||||
rewriter, receiveOp.getLoc(), outputBuffer.getType(), outputBuffer,
|
||||
arith::ConstantIndexOp::create(rewriter, receiveOp.getLoc(), 0),
|
||||
*sizeAttr, receiveOp.getSourceCoreId())
|
||||
Value zero = arith::ConstantIndexOp::create(
|
||||
rewriter, receiveOp->getLoc(), 0);
|
||||
Value received;
|
||||
if (hostWaitLoad) {
|
||||
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();
|
||||
} else {
|
||||
received = PimReceiveOp::create(
|
||||
rewriter, receiveOp->getLoc(), outputBuffer.getType(), outputBuffer,
|
||||
zero, *sizeAttr, channelReceive.getSourceCoreId()).getOutput();
|
||||
}
|
||||
blockArg->replaceAllUsesWith(received);
|
||||
markOpToRemove(receiveOp);
|
||||
continue;
|
||||
@@ -383,7 +402,8 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeOp(spatial::SpatScheduledCom
|
||||
if (rangeLength(resultUses) == 1) {
|
||||
OpOperand& resultUse = *resultUses.begin();
|
||||
Operation* resultUser = resultUse.getOwner();
|
||||
if (isa<spatial::SpatChannelSendOp>(resultUser))
|
||||
if (isa<spatial::SpatChannelSendOp,
|
||||
spatial::SpatHostStoreSyncOp>(resultUser))
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,10 +57,29 @@ struct ChannelSendLowering : OpRewritePattern<spatial::SpatChannelSendOp> {
|
||||
}
|
||||
};
|
||||
|
||||
struct ChannelReceiveLowering : OpRewritePattern<spatial::SpatChannelReceiveOp> {
|
||||
struct HostStoreSyncLowering : OpRewritePattern<spatial::SpatHostStoreSyncOp> {
|
||||
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()) {
|
||||
rewriter.eraseOp(op);
|
||||
return success();
|
||||
@@ -86,12 +105,11 @@ struct ChannelReceiveLowering : OpRewritePattern<spatial::SpatChannelReceiveOp>
|
||||
if (failed(sizeAttr))
|
||||
return failure();
|
||||
Value zero = arith::ConstantIndexOp::create(rewriter, op.getLoc(), 0);
|
||||
auto receive = pim::PimReceiveOp::create(
|
||||
rewriter, op.getLoc(), op.getResult().getType(), outputBuffer, zero, *sizeAttr, op.getSourceCoreId());
|
||||
copyRaptorDebugAttrs(op.getOperation(), receive.getOperation());
|
||||
Value received = receive.getOutput();
|
||||
auto received = createReceive(outputBuffer, zero, *sizeAttr);
|
||||
if (failed(received))
|
||||
return failure();
|
||||
if (!destinationInsert) {
|
||||
rewriter.replaceOp(op, received);
|
||||
rewriter.replaceOp(op, *received);
|
||||
return success();
|
||||
}
|
||||
|
||||
@@ -99,10 +117,42 @@ struct ChannelReceiveLowering : OpRewritePattern<spatial::SpatChannelReceiveOp>
|
||||
Value targetOffset = createDestinationByteOffset(rewriter, destinationInsert);
|
||||
auto copy = pim::PimMemCopyOp::create(
|
||||
rewriter, op.getLoc(), destinationInsert.getDestType(), targetOffset, zero,
|
||||
destinationInsert.getDest(), received, *sizeAttr);
|
||||
destinationInsert.getDest(), *received, *sizeAttr);
|
||||
rewriter.replaceOp(destinationInsert, copy.getOutput());
|
||||
rewriter.eraseOp(op);
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
@@ -859,6 +859,10 @@ void raptor::SpatialToPimPass::replaceReturnWithOutputBuffers(func::ReturnOp ret
|
||||
markOpToRemove(receiveOp);
|
||||
return;
|
||||
}
|
||||
if (auto receiveOp = dyn_cast<spatial::SpatHostWaitLoadOp>(op)) {
|
||||
markOpToRemove(receiveOp);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
SmallVector<Value> originalOperands(returnOp.getOperands().begin(), returnOp.getOperands().end());
|
||||
|
||||
@@ -126,6 +126,8 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
|
||||
spatial::SpatConcatOp,
|
||||
spatial::SpatChannelReceiveOp,
|
||||
spatial::SpatChannelSendOp,
|
||||
spatial::SpatHostStoreSyncOp,
|
||||
spatial::SpatHostWaitLoadOp,
|
||||
spatial::SpatExtractRowsOp>();
|
||||
|
||||
RewritePatternSet initialPatterns(ctx);
|
||||
@@ -140,6 +142,12 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
|
||||
populateGlobalTensorMaterializationPatterns(globalTensorPatterns);
|
||||
walkAndApplyPatterns(moduleOp, std::move(globalTensorPatterns));
|
||||
|
||||
if (funcOp->hasAttr(kPipelineHostBufferBytesAttrName)
|
||||
&& failed(materializePipelineHostBuffer(funcOp, rewriter))) {
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
auto returnOp = cast<func::ReturnOp>(funcOp.front().getTerminator());
|
||||
addReturnOutputBuffers(returnOp, rewriter);
|
||||
if (failed(allocateAndInitializeCoreLocalVariables(funcOp, rewriter))) {
|
||||
@@ -182,6 +190,17 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
|
||||
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);
|
||||
populateCoreBodyPatterns(coreBodyPatterns);
|
||||
@@ -202,6 +221,8 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
|
||||
spatial::SpatConcatOp,
|
||||
spatial::SpatChannelReceiveOp,
|
||||
spatial::SpatChannelSendOp,
|
||||
spatial::SpatHostStoreSyncOp,
|
||||
spatial::SpatHostWaitLoadOp,
|
||||
spatial::SpatExtractRowsOp>();
|
||||
|
||||
SmallVector<pim::PimCoreOp> coreOps;
|
||||
@@ -251,6 +272,8 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
|
||||
communicationTarget.addIllegalOp<spatial::SpatConcatOp,
|
||||
spatial::SpatChannelReceiveOp,
|
||||
spatial::SpatChannelSendOp,
|
||||
spatial::SpatHostStoreSyncOp,
|
||||
spatial::SpatHostWaitLoadOp,
|
||||
spatial::SpatExtractRowsOp>();
|
||||
|
||||
RewritePatternSet communicationPatterns(ctx);
|
||||
|
||||
@@ -430,8 +430,7 @@ analyzeCopyRewrite(Value target, Value source, Value targetOffset, Value sourceO
|
||||
|
||||
auto targetBytes = getShapedByteSize(targetType);
|
||||
auto sourceBytes = getShapedByteSize(sourceType);
|
||||
if (targetType.getElementType() == sourceType.getElementType() && succeeded(targetBytes) && succeeded(sourceBytes)
|
||||
&& size <= *targetBytes && size <= *sourceBytes) {
|
||||
if (succeeded(targetBytes) && succeeded(sourceBytes) && size <= *targetBytes && size <= *sourceBytes) {
|
||||
auto targetSuffixRank = getContiguousSuffixRank(target, targetType.getShape());
|
||||
auto sourceSuffixRank = getContiguousSuffixRank(source, sourceType.getShape());
|
||||
if (succeeded(targetSuffixRank) && succeeded(sourceSuffixRank)
|
||||
|
||||
@@ -241,6 +241,8 @@ static bool isSupportedCoreInstructionOp(Operation* op) {
|
||||
pim::PimVMVOp,
|
||||
pim::PimReceiveOp,
|
||||
pim::PimSendOp,
|
||||
pim::PimSyncOp,
|
||||
pim::PimWaitOp,
|
||||
pim::PimConcatOp,
|
||||
pim::PimVMMOp,
|
||||
pim::PimVVAddOp,
|
||||
|
||||
@@ -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]> {
|
||||
let summary = "Copy a memory region from host memory into device memory";
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ add_pim_library(SpatialOps
|
||||
Passes/Transforms/MergeComputeNodes/SpatialDataflowCsvExporter.cpp
|
||||
Passes/Transforms/MergeComputeNodes/Scheduling/MergeSchedulingAnalysis.cpp
|
||||
Passes/Transforms/MergeComputeNodes/Scheduling/PeftScheduler.cpp
|
||||
Passes/Transforms/MergeComputeNodes/Scheduling/PipelineScheduling.cpp
|
||||
Passes/Transforms/TrivialGraphComputeMergePass.cpp
|
||||
|
||||
EXCLUDE_FROM_OM_LIBS
|
||||
|
||||
+4
-2
@@ -219,10 +219,12 @@ static void appendReceive(BoundaryProgram &boundary,
|
||||
run->entryOffsets[run->entryOffsets.size() - 2]].family->requirement;
|
||||
CollectionTarget previousTarget {run->collection, run->positions.back()};
|
||||
bool sameEntry = previous == requirement;
|
||||
if (sameEntry
|
||||
bool sameRoute = run->slices.back().family->hostRouted
|
||||
== slice.family->hostRouted;
|
||||
if (sameRoute && (sameEntry
|
||||
|| (sameCollectionEmissionContract(previousTarget, target)
|
||||
&& previous->publicationFragmentType
|
||||
== requirement->publicationFragmentType)) {
|
||||
== requirement->publicationFragmentType))) {
|
||||
run->slices.push_back(slice);
|
||||
if (sameEntry) {
|
||||
run->entryOffsets.back() = run->slices.size();
|
||||
|
||||
+130
-18
@@ -8,6 +8,7 @@
|
||||
#include "src/Accelerators/PIM/Common/IR/StaticIntGrid.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/StaticIntSequence.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/TensorSliceUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include <array>
|
||||
namespace onnx_mlir::spatial {
|
||||
using namespace mlir;
|
||||
@@ -18,6 +19,8 @@ struct LogicalTransferMetadataView {
|
||||
StaticIntSequenceChain parentCounts;
|
||||
StaticIntSequenceChain sourceCores;
|
||||
StaticIntSequenceChain targetCores;
|
||||
StaticIntSequenceChain hostOffsets;
|
||||
StaticIntSequenceChain eventRegisters;
|
||||
StaticIntSequenceChain targetLanes;
|
||||
StaticIntSequenceChain localOffsets;
|
||||
SmallVector<StaticIntSequenceChain> projectionOffsets;
|
||||
@@ -28,7 +31,8 @@ struct LogicalTransferMetadataView {
|
||||
};
|
||||
using MetadataMember = StaticIntSequenceChain LogicalTransferMetadataView::*;
|
||||
static constexpr std::array<MetadataMember, 3> transferMetadataMembers{
|
||||
&LogicalTransferMetadataView::channels, &LogicalTransferMetadataView::sourceCores, &LogicalTransferMetadataView::targetCores};
|
||||
&LogicalTransferMetadataView::channels, &LogicalTransferMetadataView::sourceCores,
|
||||
&LogicalTransferMetadataView::targetCores};
|
||||
struct TransferGrids {
|
||||
std::array<StaticIntGrid, 3> values;
|
||||
StaticIntGrid &channels() { return values[0]; }
|
||||
@@ -41,7 +45,8 @@ template <typename Build> static FailureOr<TransferGrids> buildTransferGrids(Bui
|
||||
auto targetCores = build(transferMetadataMembers[2]);
|
||||
if (failed(channels) || failed(sourceCores) || failed(targetCores))
|
||||
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 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.sourceCores.append(family.sourceCores, 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));
|
||||
if (family.requirement->producerLocalOffsets)
|
||||
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]);
|
||||
}
|
||||
LogicalTransferMetadataView logical = buildMetadataView(run.slices);
|
||||
ExternalTransferFamily &firstFamily = *run.slices.front().family;
|
||||
size_t actionCount = 0;
|
||||
for (const LogicalTransferMetadataView &laneMetadata : metadataByLane)
|
||||
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));
|
||||
if (failed(transferGrids) || failed(localOffsets))
|
||||
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;
|
||||
for (auto [geometryIndex, sourceMember] : llvm::enumerate(metadataGeometryMembers)) {
|
||||
const auto &logicalValues = logical.*sourceMember;
|
||||
@@ -207,7 +232,6 @@ static LogicalResult emitSendRun(const EmitSendRun &run, Value lane, unsigned la
|
||||
const LogicalTransferMetadataView &source = metadataByLane[sourceLane];
|
||||
counts[sourceLane] = source.size();
|
||||
}
|
||||
ExternalTransferFamily &firstFamily = *run.slices.front().family;
|
||||
RequirementFamily &requirement = *firstFamily.requirement;
|
||||
Operation *anchor = requirement.exchange->deferred;
|
||||
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);
|
||||
if (failed(payload))
|
||||
return failure();
|
||||
auto send = SpatChannelSendOp::create(
|
||||
context.rewriter, loc, transferGrids->channels().emitLookup(action, runtimeLane, anchor, context.constants, context.rewriter, loc),
|
||||
transferGrids->sourceCores().emitLookup(action, runtimeLane, anchor, context.constants, context.rewriter, loc),
|
||||
transferGrids->targetCores().emitLookup(action, runtimeLane, anchor, context.constants, context.rewriter, loc), *payload);
|
||||
Value sourceCore = transferGrids->sourceCores().emitLookup(
|
||||
action, runtimeLane, anchor, context.constants, context.rewriter, loc);
|
||||
Value targetCore = transferGrids->targetCores().emitLookup(
|
||||
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);
|
||||
return success();
|
||||
};
|
||||
@@ -255,14 +294,45 @@ static FailureOr<Value> emitReceiveValue(ArrayRef<ScheduledTransferSlice> slices
|
||||
};
|
||||
auto grids = buildTransferGrids([&](MetadataMember member) { return buildGrid(metadata.*member); });
|
||||
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 row = context.constants.getIndex(0);
|
||||
auto receive = SpatChannelReceiveOp::create(context.rewriter, anchor->getLoc(), requirement.publicationFragmentType,
|
||||
grids->channels().emitLookup(row, position, anchor, context.constants, context.rewriter, anchor->getLoc()),
|
||||
grids->sourceCores().emitLookup(row, position, anchor, context.constants, context.rewriter, anchor->getLoc()),
|
||||
grids->targetCores().emitLookup(row, position, anchor, context.constants, context.rewriter, anchor->getLoc()));
|
||||
Value sourceCore = grids->sourceCores().emitLookup(
|
||||
row, position, anchor, context.constants, context.rewriter, anchor->getLoc());
|
||||
Value targetCore = grids->targetCores().emitLookup(
|
||||
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);
|
||||
return receive.getOutput();
|
||||
return output;
|
||||
}
|
||||
|
||||
static FailureOr<SmallVector<LogicalTransferMetadataView, 0>>
|
||||
@@ -315,6 +385,9 @@ static FailureOr<Value> emitReceiveAssembly(const EmitReceiveAssemblyRun &run, V
|
||||
SmallVector<int64_t> counts(laneCount);
|
||||
std::optional<TransferGrids> transferGrids;
|
||||
std::optional<StaticIntGrid> positions;
|
||||
std::optional<StaticIntGrid> hostOffsets;
|
||||
std::optional<StaticIntGrid> eventRegisters;
|
||||
bool hostRouted = run.slices.front().family->hostRouted;
|
||||
auto metadataByEntry = buildRectangularReceiveMetadata(run, laneCount);
|
||||
if (succeeded(metadataByEntry)) {
|
||||
auto buildRows = [&](auto member) {
|
||||
@@ -324,6 +397,16 @@ static FailureOr<Value> emitReceiveAssembly(const EmitReceiveAssemblyRun &run, V
|
||||
return StaticIntGrid::fromRows(rows);
|
||||
};
|
||||
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;
|
||||
for (unsigned position : run.positions)
|
||||
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);
|
||||
};
|
||||
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;
|
||||
for (const StaticIntSequenceChain &values : positionsByLane)
|
||||
positionColumns.push_back(
|
||||
@@ -386,15 +479,34 @@ static FailureOr<Value> emitReceiveAssembly(const EmitReceiveAssemblyRun &run, V
|
||||
Value runtimeLane = lane ? lane : context.constants.getIndex(0);
|
||||
auto emitEntry = [&](Value entry, Value current) -> FailureOr<Value> {
|
||||
Type fragmentType = run.slices.front().family->requirement->publicationFragmentType;
|
||||
auto receive =
|
||||
SpatChannelReceiveOp::create(context.rewriter, loc, fragmentType,
|
||||
transferGrids->channels().emitLookup(entry, runtimeLane, anchor, context.constants, context.rewriter, loc),
|
||||
transferGrids->sourceCores().emitLookup(entry, runtimeLane, anchor, context.constants, context.rewriter, loc),
|
||||
transferGrids->targetCores().emitLookup(entry, runtimeLane, anchor, context.constants, context.rewriter, loc));
|
||||
Value sourceCore = transferGrids->sourceCores().emitLookup(
|
||||
entry, runtimeLane, anchor, context.constants, context.rewriter, loc);
|
||||
Value targetCore = 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);
|
||||
Value position = positions->emitLookup(
|
||||
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; }))
|
||||
return emitEntry(context.constants.getIndex(0), initial);
|
||||
|
||||
+11
-12
@@ -28,6 +28,11 @@ static std::optional<Event> getPlannedHead(
|
||||
while (cursor.slice < plan.slices.size()) {
|
||||
const ScheduledTransferSlice &slice = plan.slices[cursor.slice];
|
||||
ExternalTransferFamily &family = *slice.family;
|
||||
if (family.hostRouted) {
|
||||
++cursor.slice;
|
||||
cursor.offset = 0;
|
||||
continue;
|
||||
}
|
||||
size_t begin = slice.familyOffset + cursor.offset;
|
||||
size_t length = slice.transferCount - cursor.offset;
|
||||
auto source = family.sourceStreams.find(stream, begin, length);
|
||||
@@ -243,6 +248,8 @@ LogicalResult verifyPlannedCommunicationDeadlockFree(
|
||||
DenseMap<ExternalTransferFamily *, unsigned> familyIndex;
|
||||
for (const ScheduledTransferSlice &slice : plan.slices) {
|
||||
ExternalTransferFamily *family = slice.family;
|
||||
if (family->hostRouted)
|
||||
continue;
|
||||
if (!familyIndex.try_emplace(family, familyIndex.size()).second)
|
||||
continue;
|
||||
size_t count = family->channelIds.size();
|
||||
@@ -258,18 +265,6 @@ LogicalResult verifyPlannedCommunicationDeadlockFree(
|
||||
familyChannels.emplace_back(
|
||||
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) {
|
||||
ExternalTransferFamily &family = *slice.family;
|
||||
for (size_t offset = 0; offset < slice.transferCount; ++offset) {
|
||||
@@ -296,6 +291,8 @@ LogicalResult verifyRealizedCommunicationDeadlockFree(
|
||||
DenseMap<ExternalTransferFamily *, unsigned> familyIndex;
|
||||
for (const ScheduledTransferSlice &slice : plan.slices) {
|
||||
ExternalTransferFamily *family = slice.family;
|
||||
if (family->hostRouted)
|
||||
continue;
|
||||
if (!familyIndex.try_emplace(family, familyIndex.size()).second)
|
||||
continue;
|
||||
for (size_t index = 0; index < family->channelIds.size(); ++index)
|
||||
@@ -305,6 +302,8 @@ LogicalResult verifyRealizedCommunicationDeadlockFree(
|
||||
DenseMap<int64_t, StaticIntSequenceChain> expected;
|
||||
for (const ScheduledTransferSlice &slice : plan.slices) {
|
||||
ExternalTransferFamily &family = *slice.family;
|
||||
if (family.hostRouted)
|
||||
continue;
|
||||
appendEventsByCore(expected, family.channelIds, family.sourceCores,
|
||||
slice.familyOffset, slice.transferCount, true);
|
||||
appendEventsByCore(expected, family.channelIds, family.targetCores,
|
||||
|
||||
+4
@@ -198,6 +198,7 @@ struct ScheduledInfo {
|
||||
llvm::SmallVector<mlir::Block*> blocks;
|
||||
llvm::SmallVector<mlir::Operation*> stepAnchors;
|
||||
llvm::SmallVector<int64_t> cores;
|
||||
llvm::SmallVector<unsigned> pipelineStages;
|
||||
unsigned stepCount = 0;
|
||||
llvm::SmallVector<ProducedValue*> produced;
|
||||
llvm::SmallVector<unsigned> streamIds;
|
||||
@@ -233,6 +234,9 @@ struct ExternalTransferFamily {
|
||||
StaticIntSequence sourceCores = StaticIntSequence::uniform(0, 1);
|
||||
StaticIntSequence targetCores = 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 {
|
||||
|
||||
+13
-2
@@ -209,15 +209,26 @@ static LogicalResult verifyDominance(func::FuncOp funcOp) {
|
||||
|
||||
LogicalResult realizeDeferredCommunication(func::FuncOp funcOp,
|
||||
const ScheduledComputeMaterializationResult& materialization,
|
||||
const SchedulingTarget& target) {
|
||||
const SchedulingTarget& target,
|
||||
size_t pipelineStages) {
|
||||
IRRewriter rewriter(funcOp.getContext());
|
||||
eraseUnusedIdentityDeferredCommunications(funcOp, rewriter);
|
||||
|
||||
auto transfers = buildDeferredTransferPlan(funcOp, materialization);
|
||||
auto transfers = buildDeferredTransferPlan(
|
||||
funcOp, materialization, pipelineStages, target.processorCount);
|
||||
if (failed(transfers))
|
||||
return funcOp.emitOpError("phase 2 failed to build symbolic transfer families");
|
||||
if (failed(placeLogicalProcessorsOnPhysicalCores(*transfers, target)))
|
||||
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);
|
||||
if (failed(schedule) || failed(verifyPlannedCommunicationDeadlockFree(funcOp, transfers->stepCounts, *schedule)))
|
||||
return funcOp.emitOpError("phase 2 failed to schedule symbolic communication");
|
||||
|
||||
+2
-1
@@ -9,6 +9,7 @@ struct SchedulingTarget;
|
||||
|
||||
mlir::LogicalResult realizeDeferredCommunication(mlir::func::FuncOp funcOp,
|
||||
const ScheduledComputeMaterializationResult& materialization,
|
||||
const SchedulingTarget& target);
|
||||
const SchedulingTarget& target,
|
||||
size_t pipelineStages = 1);
|
||||
|
||||
} // namespace onnx_mlir::spatial
|
||||
|
||||
+3
-2
@@ -11,7 +11,7 @@ using namespace mlir;
|
||||
namespace {
|
||||
|
||||
using TransferEmissionSignature =
|
||||
std::tuple<ScheduledInfo*, Value, Type, bool, bool, bool>;
|
||||
std::tuple<ScheduledInfo*, Value, Type, bool, bool, bool, bool>;
|
||||
|
||||
static TransferEmissionSignature getTransferEmissionSignature(
|
||||
const ExternalTransferFamily& family) {
|
||||
@@ -21,7 +21,8 @@ static TransferEmissionSignature getTransferEmissionSignature(
|
||||
family.requirement->publicationFragmentType,
|
||||
family.requirement->graphLanes.has_value(),
|
||||
family.requirement->producerProjection.has_value(),
|
||||
producer->scheduled->isBatch()};
|
||||
producer->scheduled->isBatch(),
|
||||
family.hostRouted};
|
||||
}
|
||||
|
||||
struct StreamThreshold {
|
||||
|
||||
+83
-14
@@ -5,6 +5,7 @@
|
||||
#include "DeferredProjectionAnalysis.hpp"
|
||||
#include "DeferredTransferPlanning.hpp"
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||
|
||||
namespace onnx_mlir::spatial {
|
||||
using namespace mlir;
|
||||
@@ -28,7 +29,12 @@ static FailureOr<unsigned> getStepIndex(
|
||||
|
||||
static LogicalResult collectScheduledOperations(
|
||||
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;
|
||||
for (const ScheduledMaterializationRecord &record :
|
||||
materialization.materializedSchedules) {
|
||||
@@ -46,8 +52,13 @@ static LogicalResult collectScheduledOperations(
|
||||
if (llvm::any_of(info.stepAnchors,
|
||||
[](Operation *anchor) { return !anchor; }))
|
||||
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);
|
||||
if (pipelineStageCount > 1)
|
||||
info.pipelineStages.push_back(core / stageSize);
|
||||
}
|
||||
for (size_t lane = 0; lane < info.cores.size(); ++lane)
|
||||
info.streamIds.push_back(nextStream++);
|
||||
plan.scheduled.push_back(std::move(info));
|
||||
@@ -308,17 +319,22 @@ static LogicalResult buildRequirementFamilies(DeferredTransferPlan& plan,
|
||||
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 (LaneInterval interval : requirement.targetLanes.intervals()) {
|
||||
unsigned runBegin = interval.begin;
|
||||
bool runLocal = false;
|
||||
Availability runAvailability = Availability::Local;
|
||||
bool haveRun = false;
|
||||
auto flush = [&](unsigned end) {
|
||||
auto flush = [&](unsigned end) -> LogicalResult {
|
||||
if (!haveRun || runBegin == end)
|
||||
return;
|
||||
return success();
|
||||
LaneSet lanes = LaneSet::range(runBegin, end);
|
||||
if (runLocal) {
|
||||
if (runAvailability == Availability::Local) {
|
||||
exchange.local.push_back({&requirement, lanes});
|
||||
}
|
||||
else {
|
||||
@@ -339,25 +355,72 @@ static void buildAvailabilityFamilies(DeferredExchangePlan& exchange, uint64_t&
|
||||
family.sourceCores = StaticIntSequence::uniform(requirement.producer->core, count);
|
||||
family.targetCores = StaticIntSequence::fromValues(targetCores);
|
||||
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 ®isters = 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;
|
||||
exchange.externalTransferCount += count;
|
||||
exchange.external.push_back(std::move(family));
|
||||
}
|
||||
return success();
|
||||
};
|
||||
for (unsigned lane = interval.begin; lane < interval.end; ++lane) {
|
||||
unsigned sourceStream = requirement.producer->scheduled->streamIds[requirement.producer->scheduledLane];
|
||||
bool local =
|
||||
sourceStream == exchange.target->streamIds[lane] && requirement.producer->step < exchange.consumerStep;
|
||||
if (haveRun && local != runLocal) {
|
||||
flush(lane);
|
||||
bool crossStage = !exchange.target->pipelineStages.empty()
|
||||
&& 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;
|
||||
}
|
||||
runLocal = local;
|
||||
runAvailability = availability;
|
||||
haveRun = true;
|
||||
}
|
||||
flush(interval.end);
|
||||
if (failed(flush(interval.end)))
|
||||
return failure();
|
||||
}
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
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); });
|
||||
GraphBatchPublicationCache publicationCache;
|
||||
uint64_t nextChannel = 0;
|
||||
DenseMap<int64_t, DenseMap<int64_t, unsigned>> eventRegistersByTarget;
|
||||
for (SpatDeferredCommunicationOp deferred : deferredOps) {
|
||||
Operation* targetOp = deferred->getParentOfType<SpatScheduledCompute>();
|
||||
if (!targetOp)
|
||||
@@ -387,7 +451,9 @@ static LogicalResult buildExchanges(func::FuncOp funcOp, DeferredTransferPlan& p
|
||||
exchange->program = std::move(*program);
|
||||
if (failed(buildRequirementFamilies(plan, *exchange, publicationCache)))
|
||||
return failure();
|
||||
buildAvailabilityFamilies(*exchange, nextChannel);
|
||||
if (failed(buildAvailabilityFamilies(
|
||||
plan, *exchange, nextChannel, eventRegistersByTarget)))
|
||||
return failure();
|
||||
plan.exchanges.push_back(std::move(exchange));
|
||||
}
|
||||
return success();
|
||||
@@ -464,9 +530,12 @@ retargetBlueprint(DeferredTransferPlan& plan, SpatBlueprintOp blueprint, GraphBa
|
||||
|
||||
FailureOr<DeferredTransferPlan> buildDeferredTransferPlan(
|
||||
func::FuncOp funcOp,
|
||||
const ScheduledComputeMaterializationResult &materialization) {
|
||||
const ScheduledComputeMaterializationResult &materialization,
|
||||
size_t pipelineStages,
|
||||
size_t processorCount) {
|
||||
DeferredTransferPlan plan;
|
||||
if (failed(collectScheduledOperations(materialization, plan))
|
||||
if (failed(collectScheduledOperations(
|
||||
materialization, plan, pipelineStages, processorCount))
|
||||
|| failed(collectProducedValues(materialization, plan))
|
||||
|| failed(buildExchanges(funcOp, plan)))
|
||||
return failure();
|
||||
|
||||
+4
-1
@@ -13,11 +13,14 @@ struct DeferredTransferPlan {
|
||||
llvm::DenseMap<int64_t, llvm::SmallVector<ProducedValue*>> producedByGraph;
|
||||
llvm::SmallVector<std::unique_ptr<DeferredExchangePlan>> exchanges;
|
||||
llvm::SmallVector<unsigned> stepCounts;
|
||||
size_t pipelineHostBufferBytes = 0;
|
||||
};
|
||||
|
||||
mlir::FailureOr<DeferredTransferPlan>
|
||||
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);
|
||||
|
||||
|
||||
+78
-7
@@ -3,12 +3,15 @@
|
||||
#include "DeferredCommunicationRealization.hpp"
|
||||
#include "ScheduledComputeReport.hpp"
|
||||
#include "ScheduledComputeVerification.hpp"
|
||||
#include "Scheduling/PipelineScheduling.hpp"
|
||||
#include "SpatialDataflowCsvExporter.hpp"
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "src/Accelerators/PIM/Common/Support/DebugDump.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Passes/Analyses/ONNXToSpatialVerifier.hpp"
|
||||
#include "src/Accelerators/PIM/Passes/PIMPasses.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
using namespace mlir;
|
||||
|
||||
namespace onnx_mlir {
|
||||
@@ -31,14 +34,47 @@ static FailureOr<func::FuncOp> requireEntry(ModuleOp moduleOp) {
|
||||
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
|
||||
: PassWrapper<ScheduleAndRealizeSpatialPass, OperationPass<ModuleOp>> {
|
||||
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(ScheduleAndRealizeSpatialPass)
|
||||
|
||||
ScheduleAndRealizeSpatialPass() = default;
|
||||
ScheduleAndRealizeSpatialPass(const SchedulingTarget& target,
|
||||
SpatialDataflowExportStage exportStage)
|
||||
: target(target), exportStage(exportStage), hasTarget(true) {}
|
||||
SpatialDataflowExportStage exportStage,
|
||||
size_t pipelineStages)
|
||||
: target(target), exportStage(exportStage),
|
||||
pipelineStages(pipelineStages), hasTarget(true) {}
|
||||
|
||||
StringRef getArgument() const override { return "schedule-and-realize-spatial"; }
|
||||
StringRef getDescription() const override {
|
||||
@@ -52,6 +88,13 @@ struct ScheduleAndRealizeSpatialPass final
|
||||
signalPassFailure();
|
||||
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);
|
||||
if (failed(entry)) {
|
||||
signalPassFailure();
|
||||
@@ -59,8 +102,31 @@ struct ScheduleAndRealizeSpatialPass final
|
||||
}
|
||||
func::FuncOp entryFunc = *entry;
|
||||
|
||||
MergeSchedulingAnalysis analysis(entryFunc, target);
|
||||
MergeScheduleResult schedule = std::move(analysis.getResult());
|
||||
SchedulingTarget schedulingTarget = getPipelineSchedulingTarget(
|
||||
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());
|
||||
FailureOr<ScheduledComputeMaterializationResult> materialization =
|
||||
materializeScheduledCompute(entryFunc, schedule, rewriter);
|
||||
@@ -94,7 +160,8 @@ struct ScheduleAndRealizeSpatialPass final
|
||||
moduleOp, entryFunc, schedule, materializationResult.peftClassPlans,
|
||||
materializationResult.materializedSchedules);
|
||||
|
||||
if (failed(realizeDeferredCommunication(entryFunc, materializationResult, target))) {
|
||||
if (failed(realizeDeferredCommunication(
|
||||
entryFunc, materializationResult, target, pipelineStages))) {
|
||||
moduleOp.emitError("Spatial communication realization failed");
|
||||
signalPassFailure();
|
||||
return;
|
||||
@@ -126,6 +193,7 @@ struct ScheduleAndRealizeSpatialPass final
|
||||
private:
|
||||
SchedulingTarget target;
|
||||
SpatialDataflowExportStage exportStage = SpatialDataflowExportStage::None;
|
||||
size_t pipelineStages = 1;
|
||||
bool hasTarget = false;
|
||||
};
|
||||
|
||||
@@ -136,8 +204,11 @@ std::unique_ptr<Pass> createScheduleAndRealizeSpatialPass() {
|
||||
}
|
||||
|
||||
std::unique_ptr<Pass> createScheduleAndRealizeSpatialPass(
|
||||
const SchedulingTarget& target, SpatialDataflowExportStage exportStage) {
|
||||
return std::make_unique<ScheduleAndRealizeSpatialPass>(target, exportStage);
|
||||
const SchedulingTarget& target,
|
||||
SpatialDataflowExportStage exportStage,
|
||||
size_t pipelineStages) {
|
||||
return std::make_unique<ScheduleAndRealizeSpatialPass>(
|
||||
target, exportStage, pipelineStages);
|
||||
}
|
||||
|
||||
} // namespace spatial
|
||||
|
||||
+16
@@ -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
|
||||
+13
-4
@@ -772,6 +772,11 @@ std::vector<ComputeGraphEdge> aggregateEdges(llvm::ArrayRef<ComputeGraphEdge> ed
|
||||
|
||||
} // namespace
|
||||
|
||||
TransferCost getTransferCostFromBytes(Cost bytes,
|
||||
const SchedulingTarget& target) {
|
||||
return SchedulerCostModel {target}.getTransferCostFromBytes(bytes);
|
||||
}
|
||||
|
||||
uint64_t countComputeBodyInstructions(Region& body) {
|
||||
uint64_t numOperations = 0;
|
||||
body.walk([&](Operation* op) { numOperations = checkedAdd(numOperations, static_cast<uint64_t>(1)); });
|
||||
@@ -875,9 +880,13 @@ ResidentWeightSet getComputeInstanceResidentWeights(const ComputeInstance& insta
|
||||
return tiled;
|
||||
}
|
||||
|
||||
ComputeGraph buildComputeGraph(Operation* entryOp, const SchedulingTarget& target) {
|
||||
ComputeGraph buildComputeGraph(Operation* entryOp,
|
||||
const SchedulingTarget& target,
|
||||
size_t computePartitionCount) {
|
||||
ComputeGraph graph;
|
||||
SchedulerCostModel costModel {target};
|
||||
if (computePartitionCount == 0)
|
||||
computePartitionCount = target.processorCount;
|
||||
|
||||
for (Region& region : entryOp->getRegions()) {
|
||||
for (Block& block : region) {
|
||||
@@ -898,10 +907,10 @@ ComputeGraph buildComputeGraph(Operation* entryOp, const SchedulingTarget& targe
|
||||
if (isUsedAsWeightOnly(batch.getOperation()))
|
||||
continue;
|
||||
size_t chunkCount =
|
||||
getBatchChunkTargetCount(batch, target.processorCount);
|
||||
getBatchChunkTargetCount(batch, computePartitionCount);
|
||||
for (size_t chunkIndex = 0; chunkIndex < chunkCount; ++chunkIndex) {
|
||||
ComputeInstance instance = getBatchChunkForIndex(
|
||||
batch, chunkIndex, target.processorCount);
|
||||
batch, chunkIndex, computePartitionCount);
|
||||
size_t index = graph.nodes.size();
|
||||
graph.nodes.push_back({instance,
|
||||
getComputeInstanceCost(instance, target),
|
||||
@@ -920,7 +929,7 @@ ComputeGraph buildComputeGraph(Operation* entryOp, const SchedulingTarget& targe
|
||||
for (Value input : inputs) {
|
||||
for (const ProducerValueRef& producerRef :
|
||||
collectProducerValueRefs(input, node.instance,
|
||||
target.processorCount)) {
|
||||
computePartitionCount)) {
|
||||
auto producerIt = graph.instanceToIndex.find(producerRef.instance);
|
||||
if (producerIt == graph.instanceToIndex.end())
|
||||
continue;
|
||||
|
||||
+5
-1
@@ -61,9 +61,13 @@ struct ComputeGraph {
|
||||
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);
|
||||
|
||||
TransferCost getTransferCostFromBytes(Cost bytes,
|
||||
const SchedulingTarget& target);
|
||||
uint64_t countComputeBodyInstructions(mlir::Region& body);
|
||||
uint64_t countComputeBodyOperationInstances(mlir::Region& body);
|
||||
Cost getComputeInstanceCost(const ComputeInstance& instance, const SchedulingTarget& target);
|
||||
|
||||
+4
-3
@@ -89,13 +89,14 @@ void verifySchedule(const ComputeGraph& graph,
|
||||
} // namespace
|
||||
|
||||
MergeSchedulingAnalysis::MergeSchedulingAnalysis(mlir::Operation* op,
|
||||
const SchedulingTarget& schedulingTarget)
|
||||
: entryOp(op), target(schedulingTarget) {
|
||||
const SchedulingTarget& schedulingTarget,
|
||||
size_t partitionCount)
|
||||
: entryOp(op), target(schedulingTarget), computePartitionCount(partitionCount) {
|
||||
result = run();
|
||||
}
|
||||
|
||||
MergeScheduleResult MergeSchedulingAnalysis::run() {
|
||||
ComputeGraph graph = buildComputeGraph(entryOp, target);
|
||||
graph = buildComputeGraph(entryOp, target, computePartitionCount);
|
||||
if (!verifyAcyclic(graph))
|
||||
llvm::report_fatal_error("merge scheduling: compute graph is cyclic");
|
||||
|
||||
|
||||
+7
-1
@@ -3,6 +3,7 @@
|
||||
#include "mlir/IR/Operation.h"
|
||||
|
||||
#include "MergeSchedule.hpp"
|
||||
#include "ComputeGraph.hpp"
|
||||
#include "SchedulingTarget.hpp"
|
||||
|
||||
namespace onnx_mlir {
|
||||
@@ -10,12 +11,17 @@ namespace spatial {
|
||||
|
||||
class MergeSchedulingAnalysis {
|
||||
public:
|
||||
MergeSchedulingAnalysis(mlir::Operation* op, const SchedulingTarget& target);
|
||||
MergeSchedulingAnalysis(mlir::Operation* op,
|
||||
const SchedulingTarget& target,
|
||||
size_t computePartitionCount = 0);
|
||||
MergeScheduleResult& getResult() { return result; }
|
||||
const ComputeGraph& getGraph() const { return graph; }
|
||||
|
||||
private:
|
||||
mlir::Operation* entryOp = nullptr;
|
||||
const SchedulingTarget& target;
|
||||
size_t computePartitionCount = 0;
|
||||
ComputeGraph graph;
|
||||
MergeScheduleResult result;
|
||||
|
||||
MergeScheduleResult run();
|
||||
|
||||
+1092
File diff suppressed because it is too large
Load Diff
+26
@@ -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
|
||||
@@ -550,7 +550,8 @@ def SpatChannelSendOp : SpatOp<"channel_send", []> {
|
||||
);
|
||||
|
||||
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 = [{
|
||||
`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
|
||||
@@ -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
|
||||
@@ -16,7 +16,8 @@ enum class SpatialDataflowExportStage;
|
||||
std::unique_ptr<mlir::Pass> createScheduleAndRealizeSpatialPass();
|
||||
std::unique_ptr<mlir::Pass> createScheduleAndRealizeSpatialPass(
|
||||
const SchedulingTarget& target,
|
||||
SpatialDataflowExportStage exportStage);
|
||||
SpatialDataflowExportStage exportStage,
|
||||
size_t pipelineStages = 1);
|
||||
}
|
||||
|
||||
std::unique_ptr<mlir::Pass> createONNXToSpatialPass();
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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;
|
||||
|
||||
@@ -54,5 +56,95 @@ int main() {
|
||||
0,
|
||||
};
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -169,11 +169,11 @@ Motifs are not inferred from rendered geometry. For each operation graph the too
|
||||
|
||||
## 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 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`:
|
||||
|
||||
@@ -206,8 +206,10 @@ Raw pages default to 100 and cannot exceed 500. Subgraph depth cannot exceed fiv
|
||||
## Performance behavior
|
||||
|
||||
- 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.
|
||||
- Secondary indexes are created after raw insertion.
|
||||
- Temporary SQLite staging tables are function-scoped and dropped immediately; bulk joins resolve endpoints without per-edge node queries.
|
||||
- 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.
|
||||
- Only aggregate operation nodes/edges enter NetworkX.
|
||||
- Mapping statistics are grouped in SQL; exact stencil comparison streams one operation pair.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
README.md
|
||||
pyproject.toml
|
||||
raptor_graph_explorer/__init__.py
|
||||
raptor_graph_explorer/__main__.py
|
||||
raptor_graph_explorer/aggregate.py
|
||||
raptor_graph_explorer/api.py
|
||||
raptor_graph_explorer/cli.py
|
||||
|
||||
@@ -20,3 +20,5 @@ networks/**/*.csv
|
||||
!networks/full_net/validation_results.csv
|
||||
!networks/pimcomp_models/validation_results.csv
|
||||
!networks/pimcomp_models/results.csv
|
||||
!networks/pimcomp_models/validation_results.csv
|
||||
!operations/validation_results.csv
|
||||
|
||||
Binary file not shown.
@@ -1,5 +1,3 @@
|
||||
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
|
||||
resnet18,28.099952,58.853733,8781611766.119984,13983168748.119974,raptor,2.09
|
||||
resnet34,45.781486,91.607980,14962940227.679951,22722922369.680016,raptor,2.00
|
||||
googlenet,13.371204,62.923463,6117835798.919991,14547526780.240000,raptor,4.71
|
||||
vgg8,1.521060,7.985074,486309111.040001,1597904071.120000,raptor,5.25
|
||||
resnet18,33.552733,58.853613,9702508727.119982,13983148468.119974,raptor,1.75
|
||||
|
||||
|
@@ -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
|
||||
|
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -1,169 +1,178 @@
|
||||
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/basic,PASS,0.057 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
add/broadcast_row,PASS,0.057 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
add/channel_broadcast_1024,PASS,0.061 s,0.02 MiB,0.01 MiB,1,0,SKIP,SKIP,SKIP
|
||||
add/leading_dimension_broadcast,PASS,0.057 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
concat/channel_axis,PASS,0.069 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
concat/negative_axis,PASS,0.061 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
concat/three_inputs_channel_axis,PASS,0.058 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
conv/batch_2,PASS,0.066 s,0.00 MiB,0.00 MiB,2,2,SKIP,SKIP,SKIP
|
||||
conv/batch_4_pointwise,PASS,0.065 s,0.00 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP
|
||||
conv/depthwise_1024_channels,PASS,0.083 s,0.19 MiB,0.38 MiB,129,128,SKIP,SKIP,SKIP
|
||||
conv/depthwise_grouped,PASS,0.071 s,0.01 MiB,0.00 MiB,5,4,SKIP,SKIP,SKIP
|
||||
conv/dilated_3x3,PASS,0.068 s,0.01 MiB,0.01 MiB,10,9,SKIP,SKIP,SKIP
|
||||
conv/dynamic,PASS,0.065 s,0.00 MiB,0.00 MiB,5,0,SKIP,SKIP,SKIP
|
||||
conv/explicit_padding,PASS,0.062 s,0.01 MiB,0.02 MiB,17,16,SKIP,SKIP,SKIP
|
||||
conv/grouped_many_groups,PASS,0.442 s,0.05 MiB,0.09 MiB,65,64,SKIP,SKIP,SKIP
|
||||
conv/grouped_two_groups,PASS,0.060 s,0.00 MiB,0.00 MiB,3,2,SKIP,SKIP,SKIP
|
||||
conv/huge_pointwise_1024,PASS,0.156 s,0.01 MiB,0.11 MiB,73,64,SKIP,SKIP,SKIP
|
||||
conv/huge_pointwise_1024_dynamic,PASS,0.079 s,8.04 MiB,12.61 MiB,168,0,SKIP,SKIP,SKIP
|
||||
conv/kernel_3x3,PASS,0.062 s,0.01 MiB,0.01 MiB,10,9,SKIP,SKIP,SKIP
|
||||
conv/kernel_equals_input_spatial,PASS,0.064 s,0.00 MiB,0.00 MiB,2,2,SKIP,SKIP,SKIP
|
||||
conv/large_input_channels_1x1,PASS,0.089 s,0.01 MiB,0.02 MiB,9,8,SKIP,SKIP,SKIP
|
||||
conv/large_output_channels_1x1,PASS,0.098 s,0.01 MiB,0.02 MiB,17,8,SKIP,SKIP,SKIP
|
||||
conv/large_spatial,PASS,0.069 s,0.01 MiB,0.04 MiB,37,36,SKIP,SKIP,SKIP
|
||||
conv/multi_channel,PASS,0.066 s,0.00 MiB,0.00 MiB,4,3,SKIP,SKIP,SKIP
|
||||
conv/non_square_kernel_1x3,PASS,0.061 s,0.00 MiB,0.00 MiB,3,2,SKIP,SKIP,SKIP
|
||||
conv/non_square_kernel_3x1,PASS,0.064 s,0.00 MiB,0.00 MiB,3,2,SKIP,SKIP,SKIP
|
||||
conv/non_uniform_stride,PASS,0.062 s,0.00 MiB,0.00 MiB,4,3,SKIP,SKIP,SKIP
|
||||
conv/pointwise_1x1,PASS,0.059 s,0.00 MiB,0.00 MiB,1,1,SKIP,SKIP,SKIP
|
||||
conv/pointwise_tiled_chain,PASS,0.604 s,0.01 MiB,0.04 MiB,20,80,SKIP,SKIP,SKIP
|
||||
conv/real_asymmetric_padding,PASS,0.060 s,0.01 MiB,0.03 MiB,29,28,SKIP,SKIP,SKIP
|
||||
conv/relu_conv_store,PASS,0.091 s,0.16 MiB,0.67 MiB,168,184,SKIP,SKIP,SKIP
|
||||
conv/same_lower_3x3,PASS,0.078 s,0.01 MiB,0.02 MiB,26,25,SKIP,SKIP,SKIP
|
||||
conv/same_padding_3x3,PASS,0.070 s,0.01 MiB,0.02 MiB,26,25,SKIP,SKIP,SKIP
|
||||
conv/simple,PASS,0.064 s,0.00 MiB,0.00 MiB,1,1,SKIP,SKIP,SKIP
|
||||
conv/stride_2,PASS,0.064 s,0.01 MiB,0.00 MiB,5,4,SKIP,SKIP,SKIP
|
||||
conv/with_bias_3x3,PASS,0.067 s,0.00 MiB,0.01 MiB,4,3,SKIP,SKIP,SKIP
|
||||
conv/with_constant,PASS,0.070 s,0.00 MiB,0.00 MiB,1,1,SKIP,SKIP,SKIP
|
||||
conv/without_kernel_shape_attr,PASS,0.069 s,0.01 MiB,0.01 MiB,10,9,SKIP,SKIP,SKIP
|
||||
conv/yolo11n_depthwise_head,PASS,1.482 s,8.66 MiB,34.24 MiB,168,255,SKIP,SKIP,SKIP
|
||||
conv/yolo11n_heavy,PASS,0.431 s,4.82 MiB,19.10 MiB,161,800,SKIP,SKIP,SKIP
|
||||
conv/yolo11n_stem,PASS,0.783 s,12.86 MiB,37.59 MiB,168,488,SKIP,SKIP,SKIP
|
||||
div/after_gemm,PASS,0.072 s,0.01 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP
|
||||
div/basic,PASS,0.061 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
div/channel_broadcast_1024,PASS,0.066 s,0.02 MiB,0.01 MiB,1,0,SKIP,SKIP,SKIP
|
||||
div/leading_dimension_broadcast,PASS,0.059 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
div/runtime_scalar_rhs,PASS,0.056 s,0.02 MiB,0.01 MiB,1,0,SKIP,SKIP,SKIP
|
||||
div/scalar_constant,PASS,0.073 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
gather/3d_input_axis1,PASS,0.060 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
gather/axis0_matrix_indices,PASS,0.056 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
gather/axis1,PASS,0.058 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
gather/negative_axis,PASS,0.058 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
gather/negative_indices,PASS,0.063 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
gemm/alpha_beta,PASS,0.067 s,0.01 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP
|
||||
gemm/bias_rank2_broadcast,PASS,0.060 s,0.00 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP
|
||||
gemm/dynamic,PASS,0.064 s,0.00 MiB,0.00 MiB,5,0,SKIP,SKIP,SKIP
|
||||
gemm/dynamic_alpha,PASS,0.062 s,0.00 MiB,0.00 MiB,5,0,SKIP,SKIP,SKIP
|
||||
gemm/dynamic_beta,PASS,0.060 s,0.00 MiB,0.00 MiB,5,0,SKIP,SKIP,SKIP
|
||||
gemm/dynamic_bias,PASS,0.058 s,0.00 MiB,0.00 MiB,5,0,SKIP,SKIP,SKIP
|
||||
gemm/dynamic_bias_alpha_beta,PASS,0.067 s,0.00 MiB,0.00 MiB,5,0,SKIP,SKIP,SKIP
|
||||
gemm/dynamic_transB,PASS,0.062 s,0.00 MiB,0.00 MiB,5,0,SKIP,SKIP,SKIP
|
||||
gemm/huge_1024,PASS,0.147 s,0.01 MiB,0.10 MiB,73,64,SKIP,SKIP,SKIP
|
||||
gemm/large,PASS,0.068 s,0.02 MiB,0.03 MiB,17,16,SKIP,SKIP,SKIP
|
||||
gemm/large_k_small_n,PASS,0.095 s,0.01 MiB,0.01 MiB,9,8,SKIP,SKIP,SKIP
|
||||
gemm/non_square,PASS,0.063 s,0.00 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP
|
||||
gemm/scalar_bias,PASS,0.060 s,0.00 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP
|
||||
gemm/simple,PASS,0.072 s,0.03 MiB,0.08 MiB,42,40,SKIP,SKIP,SKIP
|
||||
gemm/small,PASS,0.065 s,0.00 MiB,0.00 MiB,2,2,SKIP,SKIP,SKIP
|
||||
gemm/small_k_large_n,PASS,0.097 s,0.01 MiB,0.02 MiB,17,8,SKIP,SKIP,SKIP
|
||||
gemm/transA,PASS,0.064 s,0.00 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP
|
||||
gemm/transA_transB,PASS,0.069 s,0.00 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP
|
||||
gemm/transB,PASS,0.062 s,0.00 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP
|
||||
gemm/transB_with_bias,PASS,0.055 s,0.01 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP
|
||||
gemm/with_bias,PASS,0.067 s,0.01 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP
|
||||
gemv/constant,PASS,0.064 s,0.00 MiB,0.00 MiB,0,0,SKIP,SKIP,SKIP
|
||||
gemv/simple,PASS,0.069 s,0.00 MiB,0.01 MiB,6,4,SKIP,SKIP,SKIP
|
||||
gemv/with_heterogeneous_constant,PASS,0.066 s,0.00 MiB,0.01 MiB,6,4,SKIP,SKIP,SKIP
|
||||
gemv/with_homogeneous_constant,PASS,0.070 s,0.00 MiB,0.01 MiB,6,4,SKIP,SKIP,SKIP
|
||||
gemv/with_scalar_constant,PASS,0.070 s,0.00 MiB,0.01 MiB,6,4,SKIP,SKIP,SKIP
|
||||
matmul/basic,PASS,0.062 s,0.00 MiB,0.00 MiB,2,2,SKIP,SKIP,SKIP
|
||||
matmul/batched_3d,PASS,0.066 s,0.00 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP
|
||||
matmul/batched_3d_dynamic,PASS,0.057 s,0.00 MiB,0.00 MiB,4,0,SKIP,SKIP,SKIP
|
||||
matmul/batched_left_constant,PASS,0.070 s,0.00 MiB,0.02 MiB,9,8,SKIP,SKIP,SKIP
|
||||
matmul/batched_lhs_broadcast,PASS,0.063 s,0.00 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP
|
||||
matmul/batched_rhs_broadcast,PASS,0.062 s,0.00 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP
|
||||
matmul/dynamic,PASS,0.060 s,0.00 MiB,0.00 MiB,5,0,SKIP,SKIP,SKIP
|
||||
matmul/huge_1024,PASS,0.145 s,0.01 MiB,0.10 MiB,73,64,SKIP,SKIP,SKIP
|
||||
matmul/left_constant,PASS,0.051 s,0.00 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP
|
||||
matmul/matrix_vector,PASS,0.095 s,0.52 MiB,0.78 MiB,168,173,SKIP,SKIP,SKIP
|
||||
matmul/vector_matrix,PASS,0.087 s,0.01 MiB,0.01 MiB,9,8,SKIP,SKIP,SKIP
|
||||
matmul/yolo_attention,PASS,0.385 s,1.02 MiB,43.44 MiB,168,0,SKIP,SKIP,SKIP
|
||||
mul/after_conv,PASS,0.055 s,0.00 MiB,0.00 MiB,4,3,SKIP,SKIP,SKIP
|
||||
mul/basic,PASS,0.055 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
mul/channel_broadcast_1024,PASS,0.058 s,0.02 MiB,0.01 MiB,1,0,SKIP,SKIP,SKIP
|
||||
mul/leading_dimension_broadcast,PASS,0.059 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
mul/scalar_constant,PASS,0.056 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
pool/avg_basic,PASS,0.063 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
pool/avg_ceil_mode,PASS,0.063 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
pool/avg_explicit_padding,PASS,0.061 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
pool/avg_include_pad,PASS,0.049 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
pool/avg_large_channels,PASS,0.059 s,0.04 MiB,0.02 MiB,1,0,SKIP,SKIP,SKIP
|
||||
pool/avg_non_uniform_stride,PASS,0.066 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
pool/avg_real_asymmetric_padding,PASS,0.055 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
pool/max_after_conv,PASS,0.069 s,0.00 MiB,0.00 MiB,5,4,SKIP,SKIP,SKIP
|
||||
pool/max_basic,PASS,0.064 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
pool/max_ceil_mode,PASS,0.058 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
pool/max_global_style_kernel_equals_input,PASS,0.063 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
pool/max_non_square_kernel,PASS,0.067 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
pool/max_real_asymmetric_padding,PASS,0.063 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
pool/max_same_upper,PASS,0.061 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
pool/max_stride2_multichannel,PASS,0.076 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
reduce_mean/4d_spatial,PASS,0.058 s,0.00 MiB,0.00 MiB,3,0,SKIP,SKIP,SKIP
|
||||
reduce_mean/4d_spatial_keepdims_0,PASS,0.068 s,0.00 MiB,0.00 MiB,4,0,SKIP,SKIP,SKIP
|
||||
reduce_mean/after_conv,PASS,0.067 s,0.00 MiB,0.00 MiB,5,3,SKIP,SKIP,SKIP
|
||||
reduce_mean/all_axes_keepdims_0,PASS,0.057 s,0.00 MiB,0.00 MiB,2,0,SKIP,SKIP,SKIP
|
||||
reduce_mean/all_axes_keepdims_1,PASS,0.053 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
reduce_mean/basic,PASS,0.058 s,0.00 MiB,0.00 MiB,4,0,SKIP,SKIP,SKIP
|
||||
reduce_mean/channel_axis_nchw,PASS,0.063 s,0.03 MiB,0.02 MiB,4,0,SKIP,SKIP,SKIP
|
||||
reduce_mean/keepdims_0,PASS,0.064 s,0.00 MiB,0.00 MiB,5,0,SKIP,SKIP,SKIP
|
||||
reduce_mean/large_dimension_1024,PASS,0.066 s,0.01 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
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/legacy_axis1_keepdims_0,PASS,0.067 s,0.00 MiB,0.00 MiB,9,0,SKIP,SKIP,SKIP
|
||||
reduce_mean/legacy_axis1_keepdims_1,PASS,0.057 s,0.00 MiB,0.00 MiB,8,0,SKIP,SKIP,SKIP
|
||||
reduce_mean/legacy_empty_axes_noop,PASS,0.058 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
reduce_mean/legacy_nchw_spatial,PASS,0.059 s,0.00 MiB,0.00 MiB,3,0,SKIP,SKIP,SKIP
|
||||
reduce_mean/legacy_negative_axis,PASS,0.052 s,0.00 MiB,0.00 MiB,6,0,SKIP,SKIP,SKIP
|
||||
reduce_mean/legacy_reduce_all_keepdims_1,PASS,0.057 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
reduce_mean/negative_axis,PASS,0.055 s,0.00 MiB,0.00 MiB,6,0,SKIP,SKIP,SKIP
|
||||
relu/4d,PASS,0.059 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
relu/after_conv,PASS,0.062 s,0.00 MiB,0.00 MiB,4,3,SKIP,SKIP,SKIP
|
||||
relu/after_gemm,PASS,0.062 s,0.01 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP
|
||||
relu/basic,PASS,0.062 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
reshape/4d_to_2d_flatten,PASS,0.059 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
reshape/infer_dim_minus_one,PASS,0.059 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
reshape/same_rank,PASS,0.056 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
reshape/zero_copies_input_dim,PASS,0.077 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
resize/height_only,PASS,0.059 s,0.00 MiB,0.00 MiB,4,0,SKIP,SKIP,SKIP
|
||||
resize/nearest_2x,PASS,0.066 s,0.00 MiB,0.00 MiB,4,0,SKIP,SKIP,SKIP
|
||||
resize/nearest_downsample,PASS,0.062 s,0.00 MiB,0.00 MiB,2,0,SKIP,SKIP,SKIP
|
||||
resize/non_uniform,PASS,0.069 s,0.00 MiB,0.00 MiB,6,0,SKIP,SKIP,SKIP
|
||||
resize/width_only,PASS,0.055 s,0.00 MiB,0.00 MiB,2,0,SKIP,SKIP,SKIP
|
||||
resize/with_sizes,PASS,0.060 s,0.00 MiB,0.00 MiB,3,0,SKIP,SKIP,SKIP
|
||||
sigmoid/4d,PASS,0.060 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
sigmoid/after_gemm,PASS,0.059 s,0.01 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP
|
||||
sigmoid/basic,PASS,0.053 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
slice/2d_basic,PASS,0.058 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
slice/after_conv,PASS,0.070 s,0.00 MiB,0.01 MiB,7,6,SKIP,SKIP,SKIP
|
||||
slice/default_axes,PASS,0.066 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
slice/large_channel_1024,PASS,0.064 s,0.01 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
slice/nchw_spatial_crop,PASS,0.055 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
slice/negative_axis,PASS,0.060 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
slice/negative_indices,PASS,0.053 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
slice/step2,PASS,0.058 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
softmax/3d_last_axis,PASS,0.050 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
softmax/basic,PASS,0.061 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
softmax/channel_axis,PASS,0.058 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
softmax/large_dimension_1024,PASS,0.061 s,0.01 MiB,0.01 MiB,1,0,SKIP,SKIP,SKIP
|
||||
softmax/negative_axis,PASS,0.058 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
split/basic,PASS,0.059 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
split/equal_three_way,PASS,0.063 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
split/negative_axis,PASS,0.059 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
split/uneven_channel_axis_4d,PASS,0.061 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
sub/after_gemm,PASS,0.064 s,0.01 MiB,0.01 MiB,5,4,SKIP,SKIP,SKIP
|
||||
sub/basic,PASS,0.054 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
sub/broadcast_row,PASS,0.064 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
sub/channel_broadcast_1024,PASS,0.063 s,0.02 MiB,0.01 MiB,1,0,SKIP,SKIP,SKIP
|
||||
sub/constant_lhs_broadcast,PASS,0.054 s,0.00 MiB,0.00 MiB,1,0,SKIP,SKIP,SKIP
|
||||
sub/leading_dimension_broadcast,PASS,0.054 s,0.00 MiB,0.00 MiB,1,0,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.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
|
||||
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.02 MiB,0.01 MiB,1,0,0.006913 ms,78.118038 mW,540030.000000 pJ
|
||||
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.00 MiB,0.00 MiB,1,0,0.000457 ms,78.157549 mW,35718.000000 pJ
|
||||
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.00 MiB,0.00 MiB,1,0,0.000644 ms,78.149068 mW,50328.000000 pJ
|
||||
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.00 MiB,0.01 MiB,5,4,0.003932 ms,116.078576 mW,456420.960000 pJ
|
||||
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_1024_channels,PASS,-,0.19 MiB,0.38 MiB,129,128,0.220751 ms,178.454307 mW,39393966.720000 pJ
|
||||
conv/depthwise_grouped,PASS,-,0.01 MiB,0.00 MiB,5,4,0.006234 ms,107.776541 mW,671878.960000 pJ
|
||||
conv/dilated_3x3,PASS,-,0.01 MiB,0.01 MiB,10,9,0.008713 ms,118.767263 mW,1034819.160000 pJ
|
||||
conv/dynamic,PASS,-,0.00 MiB,0.00 MiB,5,0,0.001835 ms,92.281199 mW,169336.000000 pJ
|
||||
conv/explicit_padding,PASS,-,0.01 MiB,0.02 MiB,17,16,0.010007 ms,145.338047 mW,1454397.840000 pJ
|
||||
conv/grouped_many_groups,PASS,-,0.05 MiB,0.09 MiB,65,64,0.181897 ms,142.207471 mW,25867112.360000 pJ
|
||||
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,PASS,-,0.01 MiB,0.11 MiB,73,64,0.015615 ms,249.545140 mW,3896647.360000 pJ
|
||||
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_3x3,PASS,-,0.01 MiB,0.01 MiB,10,9,0.007186 ms,123.801859 mW,889640.160000 pJ
|
||||
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_input_channels_1x1,PASS,-,0.01 MiB,0.02 MiB,9,8,0.007648 ms,117.824519 mW,901121.920000 pJ
|
||||
conv/large_output_channels_1x1,PASS,-,0.01 MiB,0.02 MiB,17,8,0.008871 ms,128.442782 mW,1139415.920000 pJ
|
||||
conv/large_spatial,PASS,-,0.01 MiB,0.04 MiB,37,36,0.017018 ms,172.073372 mW,2928344.640000 pJ
|
||||
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_1x3,PASS,-,0.00 MiB,0.00 MiB,3,2,0.006842 ms,99.349968 mW,679752.480000 pJ
|
||||
conv/non_square_kernel_3x1,PASS,-,0.00 MiB,0.00 MiB,3,2,0.013484 ms,95.889683 mW,1292976.480000 pJ
|
||||
conv/non_uniform_stride,PASS,-,0.00 MiB,0.00 MiB,4,3,0.007601 ms,104.048772 mW,790874.720000 pJ
|
||||
conv/output_channel_grouping_minimal,PASS,-,0.10 MiB,0.34 MiB,131,128,0.258453 ms,170.730913 mW,44125916.720000 pJ
|
||||
conv/pointwise_1x1,PASS,-,0.00 MiB,0.00 MiB,1,1,0.012303 ms,80.244188 mW,987244.240000 pJ
|
||||
conv/pointwise_tiled_chain,PASS,-,0.01 MiB,0.04 MiB,20,80,0.041886 ms,153.880896 mW,6445455.200000 pJ
|
||||
conv/real_asymmetric_padding,PASS,-,0.01 MiB,0.03 MiB,29,28,0.014457 ms,153.669968 mW,2221606.720000 pJ
|
||||
conv/relu_conv_store,PASS,-,0.16 MiB,0.67 MiB,168,184,0.562898 ms,183.084489 mW,103057892.800000 pJ
|
||||
conv/same_lower_3x3,PASS,-,0.01 MiB,0.02 MiB,26,25,0.013331 ms,166.154752 mW,2215009.000000 pJ
|
||||
conv/same_padding_3x3,PASS,-,0.01 MiB,0.02 MiB,26,25,0.013331 ms,166.154752 mW,2215009.000000 pJ
|
||||
conv/simple,PASS,-,0.00 MiB,0.00 MiB,1,1,0.004301 ms,83.833583 mW,360568.240000 pJ
|
||||
conv/strategy_depthwise_16,PASS,-,0.06 MiB,0.35 MiB,168,168,0.335115 ms,197.936467 mW,66331479.080000 pJ
|
||||
conv/strategy_input_k_tiled,PASS,-,0.08 MiB,0.27 MiB,109,108,0.353736 ms,170.812713 mW,60422605.920000 pJ
|
||||
conv/strategy_output_channel_tiled,PASS,-,0.03 MiB,0.16 MiB,74,72,0.091463 ms,155.743189 mW,14244739.280000 pJ
|
||||
conv/strategy_streamed_packed,PASS,-,3.34 MiB,7.89 MiB,168,168,9.353833 ms,179.858301 mW,1682364509.560000 pJ
|
||||
conv/strategy_streamed_patch,PASS,-,0.34 MiB,1.32 MiB,168,168,1.904655 ms,181.910449 mW,346476645.640000 pJ
|
||||
conv/strategy_tiled_2d,PASS,-,0.11 MiB,0.44 MiB,168,168,0.415594 ms,182.127047 mW,75690907.840000 pJ
|
||||
conv/stride_2,PASS,-,0.01 MiB,0.00 MiB,5,4,0.005237 ms,110.780019 mW,580154.960000 pJ
|
||||
conv/with_bias_3x3,PASS,-,0.00 MiB,0.01 MiB,4,3,0.007452 ms,104.162738 mW,776220.720000 pJ
|
||||
conv/with_constant,PASS,-,0.00 MiB,0.00 MiB,1,1,0.006622 ms,81.738182 mW,541270.240000 pJ
|
||||
conv/without_kernel_shape_attr,PASS,-,0.01 MiB,0.01 MiB,10,9,0.007186 ms,123.801859 mW,889640.160000 pJ
|
||||
conv/yolo11n_depthwise_head,PASS,-,8.66 MiB,34.24 MiB,168,255,42.701404 ms,200.519161 mW,8562449708.000010 pJ
|
||||
conv/yolo11n_heavy,PASS,-,4.82 MiB,19.10 MiB,161,800,8.535404 ms,350.863768 mW,2994764012.000010 pJ
|
||||
conv/yolo11n_stem,PASS,-,12.86 MiB,37.59 MiB,168,488,14.239985 ms,301.233376 mW,4289558753.000010 pJ
|
||||
div/after_gemm,PASS,-,0.01 MiB,0.01 MiB,5,4,0.007784 ms,104.703618 mW,815012.960000 pJ
|
||||
div/basic,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
|
||||
div/channel_broadcast_1024,PASS,-,0.02 MiB,0.01 MiB,1,0,0.006913 ms,78.118038 mW,540030.000000 pJ
|
||||
div/leading_dimension_broadcast,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
|
||||
div/runtime_scalar_rhs,PASS,-,0.02 MiB,0.01 MiB,1,0,0.006913 ms,78.118038 mW,540030.000000 pJ
|
||||
div/scalar_constant,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
|
||||
gather/3d_input_axis1,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000589 ms,78.081494 mW,45990.000000 pJ
|
||||
gather/axis0_matrix_indices,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000697 ms,78.068867 mW,54414.000000 pJ
|
||||
gather/axis1,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000801 ms,78.059925 mW,62526.000000 pJ
|
||||
gather/negative_axis,PASS,-,0.00 MiB,0.00 MiB,1,0,0.001437 ms,78.033403 mW,112134.000000 pJ
|
||||
gather/negative_indices,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000376 ms,78.127660 mW,29376.000000 pJ
|
||||
gemm/alpha_beta,PASS,-,0.01 MiB,0.01 MiB,5,4,0.007456 ms,105.272125 mW,784908.960000 pJ
|
||||
gemm/bias_rank2_broadcast,PASS,-,0.00 MiB,0.01 MiB,5,4,0.007072 ms,105.979208 mW,749484.960000 pJ
|
||||
gemm/dynamic,PASS,-,0.00 MiB,0.00 MiB,5,0,0.002421 ms,91.480793 mW,221475.000000 pJ
|
||||
gemm/dynamic_alpha,PASS,-,0.00 MiB,0.00 MiB,5,0,0.003262 ms,91.415696 mW,298198.000000 pJ
|
||||
gemm/dynamic_beta,PASS,-,0.00 MiB,0.00 MiB,5,0,0.004365 ms,91.316151 mW,398595.000000 pJ
|
||||
gemm/dynamic_bias,PASS,-,0.00 MiB,0.00 MiB,5,0,0.002665 ms,91.445779 mW,243703.000000 pJ
|
||||
gemm/dynamic_bias_alpha_beta,PASS,-,0.00 MiB,0.00 MiB,5,0,0.005629 ms,91.279268 mW,513811.000000 pJ
|
||||
gemm/dynamic_transB,PASS,-,0.00 MiB,0.00 MiB,5,0,0.001301 ms,91.378171 mW,118883.000000 pJ
|
||||
gemm/huge_1024,PASS,-,0.01 MiB,0.10 MiB,73,64,0.017522 ms,215.037402 mW,3767885.360000 pJ
|
||||
gemm/large,PASS,-,0.02 MiB,0.03 MiB,17,16,0.011229 ms,140.152181 mW,1573768.840000 pJ
|
||||
gemm/large_k_small_n,PASS,-,0.01 MiB,0.01 MiB,9,8,0.004748 ms,133.481449 mW,633769.920000 pJ
|
||||
gemm/non_square,PASS,-,0.00 MiB,0.01 MiB,5,4,0.003527 ms,118.958310 mW,419565.960000 pJ
|
||||
gemm/scalar_bias,PASS,-,0.00 MiB,0.01 MiB,5,4,0.007072 ms,105.979208 mW,749484.960000 pJ
|
||||
gemm/simple,PASS,-,0.03 MiB,0.08 MiB,42,40,0.021640 ms,151.774196 mW,3284393.600000 pJ
|
||||
gemm/small,PASS,-,0.00 MiB,0.00 MiB,2,2,0.004420 ms,90.144000 mW,398436.480000 pJ
|
||||
gemm/small_k_large_n,PASS,-,0.01 MiB,0.02 MiB,17,8,0.007962 ms,131.005014 mW,1043061.920000 pJ
|
||||
gemm/transA,PASS,-,0.00 MiB,0.01 MiB,5,4,0.005762 ms,109.140743 mW,628868.960000 pJ
|
||||
gemm/transA_transB,PASS,-,0.00 MiB,0.01 MiB,5,4,0.005762 ms,109.140743 mW,628868.960000 pJ
|
||||
gemm/transB,PASS,-,0.00 MiB,0.01 MiB,5,4,0.003527 ms,118.958310 mW,419565.960000 pJ
|
||||
gemm/transB_with_bias,PASS,-,0.01 MiB,0.01 MiB,5,4,0.005046 ms,110.546762 mW,557818.960000 pJ
|
||||
gemm/with_bias,PASS,-,0.01 MiB,0.01 MiB,5,4,0.005562 ms,108.767882 mW,604966.960000 pJ
|
||||
gemv/constant,PASS,-,0.00 MiB,0.00 MiB,0,0,0.000000 ms,2.000000 mW,0.000000 pJ
|
||||
gemv/simple,PASS,-,0.00 MiB,0.01 MiB,6,4,0.005160 ms,111.150380 mW,573535.960000 pJ
|
||||
gemv/with_heterogeneous_constant,PASS,-,0.00 MiB,0.01 MiB,6,4,0.005549 ms,109.816536 mW,609371.960000 pJ
|
||||
gemv/with_homogeneous_constant,PASS,-,0.00 MiB,0.01 MiB,6,4,0.005549 ms,109.816536 mW,609371.960000 pJ
|
||||
gemv/with_scalar_constant,PASS,-,0.00 MiB,0.01 MiB,6,4,0.005549 ms,109.816536 mW,609371.960000 pJ
|
||||
matmul/basic,PASS,-,0.00 MiB,0.00 MiB,2,2,0.004420 ms,90.144000 mW,398436.480000 pJ
|
||||
matmul/batched_3d,PASS,-,0.00 MiB,0.01 MiB,5,4,0.005958 ms,108.588949 mW,646972.960000 pJ
|
||||
matmul/batched_3d_dynamic,PASS,-,0.00 MiB,0.00 MiB,4,0,0.001822 ms,92.192645 mW,167975.000000 pJ
|
||||
matmul/batched_left_constant,PASS,-,0.00 MiB,0.02 MiB,9,8,0.008822 ms,114.385164 mW,1009105.920000 pJ
|
||||
matmul/batched_lhs_broadcast,PASS,-,0.00 MiB,0.01 MiB,5,4,0.005681 ms,109.389361 mW,621440.960000 pJ
|
||||
matmul/batched_rhs_broadcast,PASS,-,0.00 MiB,0.01 MiB,5,4,0.005958 ms,108.588949 mW,646972.960000 pJ
|
||||
matmul/dynamic,PASS,-,0.00 MiB,0.00 MiB,5,0,0.001621 ms,91.421962 mW,148195.000000 pJ
|
||||
matmul/huge_1024,PASS,-,0.01 MiB,0.10 MiB,73,64,0.017522 ms,215.037402 mW,3767885.360000 pJ
|
||||
matmul/left_constant,PASS,-,0.00 MiB,0.01 MiB,5,4,0.005853 ms,108.861944 mW,637168.960000 pJ
|
||||
matmul/matrix_vector,PASS,-,0.52 MiB,0.78 MiB,168,173,0.384660 ms,202.131271 mW,77751814.880000 pJ
|
||||
matmul/vector_matrix,PASS,-,0.01 MiB,0.01 MiB,9,8,0.007409 ms,118.680243 mW,879301.920000 pJ
|
||||
matmul/yolo_attention,PASS,-,1.02 MiB,43.44 MiB,168,0,8.151445 ms,170.003707 mW,1385775865.000000 pJ
|
||||
mul/after_conv,PASS,-,0.00 MiB,0.00 MiB,4,3,0.005453 ms,107.639046 mW,586955.720000 pJ
|
||||
mul/after_conv_scalar_constant,PASS,-,0.00 MiB,0.00 MiB,4,3,0.005453 ms,107.639046 mW,586955.720000 pJ
|
||||
mul/basic,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
|
||||
mul/channel_broadcast_1024,PASS,-,0.02 MiB,0.01 MiB,1,0,0.006913 ms,78.118038 mW,540030.000000 pJ
|
||||
mul/leading_dimension_broadcast,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
|
||||
mul/scalar_constant,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000323 ms,78.222910 mW,25266.000000 pJ
|
||||
pool/avg_basic,PASS,-,0.00 MiB,0.00 MiB,1,0,0.011939 ms,78.022112 mW,931506.000000 pJ
|
||||
pool/avg_ceil_mode,PASS,-,0.00 MiB,0.00 MiB,1,0,0.004359 ms,78.033035 mW,340146.000000 pJ
|
||||
pool/avg_explicit_padding,PASS,-,0.00 MiB,0.00 MiB,1,0,0.008822 ms,78.027205 mW,688356.000000 pJ
|
||||
pool/avg_include_pad,PASS,-,0.00 MiB,0.00 MiB,1,0,0.008506 ms,78.016929 mW,663612.000000 pJ
|
||||
pool/avg_large_channels,PASS,-,0.04 MiB,0.02 MiB,1,0,0.235874 ms,78.004172 mW,18399156.000000 pJ
|
||||
pool/avg_non_uniform_stride,PASS,-,0.00 MiB,0.00 MiB,1,0,0.014513 ms,78.016537 mW,1132254.000000 pJ
|
||||
pool/avg_real_asymmetric_padding,PASS,-,0.00 MiB,0.00 MiB,1,0,0.025206 ms,78.024756 mW,1966692.000000 pJ
|
||||
pool/max_after_conv,PASS,-,0.00 MiB,0.00 MiB,5,4,0.012215 ms,99.115019 mW,1210689.960000 pJ
|
||||
pool/max_basic,PASS,-,0.00 MiB,0.00 MiB,1,0,0.004160 ms,78.063462 mW,324744.000000 pJ
|
||||
pool/max_ceil_mode,PASS,-,0.00 MiB,0.00 MiB,1,0,0.001940 ms,78.074227 mW,151464.000000 pJ
|
||||
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
|
||||
pool/max_non_square_kernel,PASS,-,0.00 MiB,0.00 MiB,1,0,0.013626 ms,78.017613 mW,1063068.000000 pJ
|
||||
pool/max_real_asymmetric_padding,PASS,-,0.00 MiB,0.00 MiB,1,0,0.010444 ms,78.034470 mW,814992.000000 pJ
|
||||
pool/max_same_upper,PASS,-,0.00 MiB,0.00 MiB,1,0,0.008010 ms,78.035955 mW,625068.000000 pJ
|
||||
pool/max_stride2_multichannel,PASS,-,0.00 MiB,0.00 MiB,1,0,0.015987 ms,78.018015 mW,1247274.000000 pJ
|
||||
reduce_mean/4d_spatial,PASS,-,0.00 MiB,0.00 MiB,3,0,0.000321 ms,92.448598 mW,29676.000000 pJ
|
||||
reduce_mean/4d_spatial_keepdims_0,PASS,-,0.00 MiB,0.00 MiB,4,0,0.000655 ms,94.352672 mW,61801.000000 pJ
|
||||
reduce_mean/after_conv,PASS,-,0.00 MiB,0.00 MiB,5,3,0.005342 ms,106.951089 mW,571332.720000 pJ
|
||||
reduce_mean/all_axes_keepdims_0,PASS,-,0.00 MiB,0.00 MiB,2,0,0.000391 ms,79.237852 mW,30982.000000 pJ
|
||||
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/basic,PASS,-,0.00 MiB,0.00 MiB,4,0,0.000373 ms,93.514745 mW,34881.000000 pJ
|
||||
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/keepdims_0,PASS,-,0.00 MiB,0.00 MiB,5,0,0.000748 ms,91.401070 mW,68368.000000 pJ
|
||||
reduce_mean/large_dimension_1024,PASS,-,0.01 MiB,0.00 MiB,1,0,0.002785 ms,78.017235 mW,217278.000000 pJ
|
||||
reduce_mean/legacy_axes_1_2_keepdims_1,PASS,-,0.00 MiB,0.00 MiB,2,0,0.000271 ms,79.354244 mW,21505.000000 pJ
|
||||
reduce_mean/legacy_axis1_keepdims_0,PASS,-,0.00 MiB,0.00 MiB,9,0,0.001986 ms,92.501511 mW,183708.000000 pJ
|
||||
reduce_mean/legacy_axis1_keepdims_1,PASS,-,0.00 MiB,0.00 MiB,8,0,0.001373 ms,94.559359 mW,129830.000000 pJ
|
||||
reduce_mean/legacy_empty_axes_noop,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000221 ms,78.217195 mW,17286.000000 pJ
|
||||
reduce_mean/legacy_nchw_spatial,PASS,-,0.00 MiB,0.00 MiB,3,0,0.000321 ms,92.448598 mW,29676.000000 pJ
|
||||
reduce_mean/legacy_negative_axis,PASS,-,0.00 MiB,0.00 MiB,6,0,0.000553 ms,93.520796 mW,51717.000000 pJ
|
||||
reduce_mean/legacy_reduce_all_keepdims_1,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000221 ms,78.217195 mW,17286.000000 pJ
|
||||
reduce_mean/negative_axis,PASS,-,0.00 MiB,0.00 MiB,6,0,0.000553 ms,93.520796 mW,51717.000000 pJ
|
||||
relu/4d,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000521 ms,78.184261 mW,40734.000000 pJ
|
||||
relu/after_conv,PASS,-,0.00 MiB,0.00 MiB,4,3,0.005352 ms,107.891951 mW,577437.720000 pJ
|
||||
relu/after_gemm,PASS,-,0.01 MiB,0.01 MiB,5,4,0.007513 ms,105.158653 mW,790056.960000 pJ
|
||||
relu/basic,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000221 ms,78.217195 mW,17286.000000 pJ
|
||||
reshape/4d_to_2d_flatten,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000258 ms,78.279070 mW,20196.000000 pJ
|
||||
reshape/infer_dim_minus_one,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000162 ms,78.296296 mW,12684.000000 pJ
|
||||
reshape/same_rank,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000162 ms,78.296296 mW,12684.000000 pJ
|
||||
reshape/zero_copies_input_dim,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000162 ms,78.296296 mW,12684.000000 pJ
|
||||
resize/height_only,PASS,-,0.00 MiB,0.00 MiB,4,0,0.000693 ms,93.554113 mW,64833.000000 pJ
|
||||
resize/nearest_2x,PASS,-,0.00 MiB,0.00 MiB,4,0,0.001173 ms,93.572890 mW,109761.000000 pJ
|
||||
resize/nearest_downsample,PASS,-,0.00 MiB,0.00 MiB,2,0,0.000427 ms,79.449649 mW,33925.000000 pJ
|
||||
resize/non_uniform,PASS,-,0.00 MiB,0.00 MiB,6,0,0.001753 ms,93.575014 mW,164037.000000 pJ
|
||||
resize/width_only,PASS,-,0.00 MiB,0.00 MiB,2,0,0.000667 ms,79.503748 mW,53029.000000 pJ
|
||||
resize/with_sizes,PASS,-,0.00 MiB,0.00 MiB,3,0,0.000797 ms,92.542033 mW,73756.000000 pJ
|
||||
sigmoid/4d,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000521 ms,78.184261 mW,40734.000000 pJ
|
||||
sigmoid/after_gemm,PASS,-,0.01 MiB,0.01 MiB,5,4,0.007513 ms,105.158653 mW,790056.960000 pJ
|
||||
sigmoid/basic,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000221 ms,78.217195 mW,17286.000000 pJ
|
||||
slice/2d_basic,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000242 ms,78.297521 mW,18948.000000 pJ
|
||||
slice/after_conv,PASS,-,0.00 MiB,0.01 MiB,7,6,0.011296 ms,118.190765 mW,1335082.880000 pJ
|
||||
slice/default_axes,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000242 ms,78.297521 mW,18948.000000 pJ
|
||||
slice/large_channel_1024,PASS,-,0.01 MiB,0.00 MiB,1,0,0.002832 ms,78.144068 mW,221304.000000 pJ
|
||||
slice/nchw_spatial_crop,PASS,-,0.00 MiB,0.00 MiB,1,0,0.001302 ms,78.239631 mW,101868.000000 pJ
|
||||
slice/negative_axis,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000562 ms,78.298932 mW,44004.000000 pJ
|
||||
slice/negative_indices,PASS,-,0.00 MiB,0.00 MiB,1,0,0.000322 ms,78.298137 mW,25212.000000 pJ
|
||||
slice/step2,PASS,-,0.00 MiB,0.00 MiB,1,0,0.002042 ms,78.293830 mW,159876.000000 pJ
|
||||
softmax/3d_last_axis,PASS,-,0.00 MiB,0.00 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED
|
||||
softmax/basic,PASS,-,0.00 MiB,0.00 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED
|
||||
softmax/channel_axis,PASS,-,0.00 MiB,0.00 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED
|
||||
softmax/large_dimension_1024,PASS,-,0.01 MiB,0.01 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED
|
||||
softmax/negative_axis,PASS,-,0.00 MiB,0.00 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED
|
||||
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
|
||||
|
||||
|
@@ -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|…|patchq−1] · 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())
|
||||
@@ -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:
|
||||
fallback: Path | None = None
|
||||
for path in sorted(PIMSIM_CONFIG_DIR.glob(f"*/{args.pimsim_mode}_config.json")):
|
||||
with open(path, encoding="utf-8") as 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 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_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(
|
||||
f"No pre-generated {args.pimsim_mode} pimsim-nn config matches "
|
||||
f"{hardware} with sim_time={args.pimsim_time_ms}"
|
||||
f"No pre-generated {args.pimsim_mode} pimsim-nn config matches {hardware}"
|
||||
)
|
||||
|
||||
|
||||
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(
|
||||
args: argparse.Namespace,
|
||||
model_path: Path,
|
||||
@@ -1221,6 +1251,8 @@ def main():
|
||||
help="Return a non-zero status if a stage or semantic validation fails.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
if args.pimsim_time_ms <= 0:
|
||||
parser.error("--pimsim-time-ms must be positive")
|
||||
if args.pimcomp_pipeline is None:
|
||||
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:
|
||||
written_config = try_stage(
|
||||
failures,
|
||||
"Select pimsim-nn config",
|
||||
select_pimsim_config,
|
||||
"Prepare pimsim-nn config",
|
||||
prepare_pimsim_config,
|
||||
args,
|
||||
hardware,
|
||||
out_dir,
|
||||
)
|
||||
if written_config is not None:
|
||||
pimsim_config = written_config
|
||||
@@ -1593,8 +1626,11 @@ def main():
|
||||
"model": str(model_path),
|
||||
"hardware": hardware,
|
||||
"pimsim_mode": args.pimsim_mode,
|
||||
"pimsim_time_ms": args.pimsim_time_ms,
|
||||
"pimcomp_pipeline": args.pimcomp_pipeline,
|
||||
"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,
|
||||
"failures": failures,
|
||||
"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
|
||||
|
||||
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")
|
||||
ARCHES = tuple(sorted(path.name for path in PIMCOMP_CONFIGS.iterdir() if path.is_dir()))
|
||||
MODELS = {
|
||||
"vgg8": SUITE / "vgg8/vgg8-mnist-reconstructed.onnx",
|
||||
"resnet18": SUITE / "resnet18/resnet18-v1-7.onnx",
|
||||
"resnet34": SUITE / "resnet34/resnet34-v1-7.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:
|
||||
return root / name if root is not None else MODELS[name].parent
|
||||
def result_dir(root: Path | None, name: str, mode: str, pipeline: int) -> Path:
|
||||
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"
|
||||
fields = (
|
||||
"model",
|
||||
"arch",
|
||||
"mode",
|
||||
"raptor_pipeline",
|
||||
"pimcomp_pipeline",
|
||||
"status",
|
||||
"raptor_throughput_samples_s",
|
||||
"pimcomp_throughput_samples_s",
|
||||
"raptor_latency_ms",
|
||||
"pimcomp_latency_ms",
|
||||
"raptor_power_mw",
|
||||
"pimcomp_power_mw",
|
||||
"raptor_energy_pj",
|
||||
"pimcomp_energy_pj",
|
||||
"faster_compiler",
|
||||
"better_compiler",
|
||||
"speedup",
|
||||
)
|
||||
rows = []
|
||||
for name in MODELS:
|
||||
report_path = result_dir(root, name) / "pimcomp/comparison_report.json"
|
||||
if not report_path.exists():
|
||||
continue
|
||||
report = json.loads(report_path.read_text(encoding="utf-8"))
|
||||
raptor = report.get("raptor_performance") or {}
|
||||
pimcomp = report.get("pimcomp_performance") or {}
|
||||
raptor_latency = raptor.get("latency_ms")
|
||||
pimcomp_latency = pimcomp.get("latency_ms")
|
||||
if raptor_latency is None or pimcomp_latency is None:
|
||||
continue
|
||||
raptor_energy = (raptor.get("average_energy_pj")
|
||||
or parse_pimsim_nn_metrics(raptor.get("raw_output", "")).get("average_energy_pj"))
|
||||
pimcomp_energy = (pimcomp.get("average_energy_pj")
|
||||
or parse_pimsim_nn_metrics(pimcomp.get("raw_output", "")).get("average_energy_pj"))
|
||||
faster = "raptor" if raptor_latency < pimcomp_latency else "pimcomp"
|
||||
rows.append({
|
||||
"model": name,
|
||||
"raptor_latency_ms": f"{raptor_latency:.6f}",
|
||||
"pimcomp_latency_ms": f"{pimcomp_latency:.6f}",
|
||||
"raptor_energy_pj": "" if raptor_energy is None else f"{raptor_energy:.6f}",
|
||||
"pimcomp_energy_pj": "" if pimcomp_energy is None else f"{pimcomp_energy:.6f}",
|
||||
"faster_compiler": faster,
|
||||
"speedup": f"{max(raptor_latency, pimcomp_latency) / min(raptor_latency, pimcomp_latency):.2f}",
|
||||
})
|
||||
for name in models:
|
||||
for mode, pipeline, pimcomp_pipeline in COMPARISONS:
|
||||
report_path = result_dir(root, name, mode, pipeline) / "pimcomp/comparison_report.json"
|
||||
if not report_path.exists():
|
||||
continue
|
||||
report = json.loads(report_path.read_text(encoding="utf-8"))
|
||||
raptor = report.get("raptor_performance") or {}
|
||||
pimcomp = report.get("pimcomp_performance") or {}
|
||||
raptor_values = performance_values(raptor)
|
||||
pimcomp_values = performance_values(pimcomp)
|
||||
raptor_metric = raptor_values["throughput"] if mode == "throughput" else raptor_values["latency"]
|
||||
pimcomp_metric = pimcomp_values["throughput"] if mode == "throughput" else pimcomp_values["latency"]
|
||||
status = "PASS" if comparison_passed(report) else "FAIL"
|
||||
if raptor_metric is None or pimcomp_metric is None:
|
||||
better = ""
|
||||
speedup = ""
|
||||
else:
|
||||
better = comparison_winner(mode, raptor_metric, pimcomp_metric)
|
||||
speedup = f"{max(raptor_metric, pimcomp_metric) / min(raptor_metric, pimcomp_metric):.2f}"
|
||||
rows.append({
|
||||
"model": name,
|
||||
"arch": arch,
|
||||
"mode": mode,
|
||||
"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:
|
||||
writer = csv.DictWriter(f, fieldnames=fields, lineterminator="\n")
|
||||
writer.writeheader()
|
||||
@@ -78,6 +108,47 @@ def write_results_csv(root: Path | None) -> Path:
|
||||
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:
|
||||
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}")
|
||||
|
||||
|
||||
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 [
|
||||
sys.executable,
|
||||
str(COMPARE),
|
||||
@@ -109,32 +190,49 @@ def comparison_command(model: Path, result_dir: Path, timeout: float) -> list[st
|
||||
"--pimcomp-dir",
|
||||
str(PIMCOMP_SOURCE),
|
||||
"--pimcomp-config",
|
||||
str(PIMCOMP_CONFIG),
|
||||
"--core-count",
|
||||
"168",
|
||||
"--crossbar-count",
|
||||
"96",
|
||||
"--crossbar-size",
|
||||
"128",
|
||||
"--mesh-rows",
|
||||
"12",
|
||||
"--mesh-cols",
|
||||
"14",
|
||||
str(config),
|
||||
"--pimsim-mode",
|
||||
"latency",
|
||||
mode,
|
||||
*time_args,
|
||||
"--pimcomp-pipeline",
|
||||
"element",
|
||||
pimcomp_pipeline,
|
||||
"--pimcomp-replication",
|
||||
"GA",
|
||||
f"--raptor-extra-arg=--pipeline={pipeline}",
|
||||
"--timeout-seconds",
|
||||
str(timeout),
|
||||
"--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:
|
||||
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(
|
||||
"--out-dir",
|
||||
@@ -142,6 +240,15 @@ def main() -> int:
|
||||
help="Result root (default: artifacts beside each model under validation/).",
|
||||
)
|
||||
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(
|
||||
"--resume",
|
||||
@@ -151,6 +258,16 @@ def main() -> int:
|
||||
parser.add_argument("--dry-run", action="store_true", help="Print commands without modifying files.")
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
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("=" * 72)
|
||||
|
||||
@@ -176,34 +295,51 @@ def main() -> int:
|
||||
|
||||
failed = []
|
||||
for index, name in enumerate(args.models, start=1):
|
||||
model_result_dir = result_dir(out_dir, name)
|
||||
print(
|
||||
"\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():
|
||||
for mode, pipeline, pimcomp_pipeline in COMPARISONS:
|
||||
model_result_dir = result_dir(out_dir, name, mode, pipeline)
|
||||
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,
|
||||
)
|
||||
continue
|
||||
returncode = run(
|
||||
comparison_command(MODELS[name], model_result_dir, args.timeout_seconds),
|
||||
dry_run=args.dry_run,
|
||||
check=False,
|
||||
)
|
||||
if returncode:
|
||||
failed.append(name)
|
||||
if args.resume and completed_report(
|
||||
model_result_dir / "pimcomp/comparison_report.json",
|
||||
mode,
|
||||
pipeline,
|
||||
configs[mode],
|
||||
args.pimsim_time_ms,
|
||||
):
|
||||
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:
|
||||
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(results_path.read_text(encoding="utf-8"), end="")
|
||||
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"Results: {results_path}" + Style.RESET_ALL)
|
||||
if failed:
|
||||
|
||||
Reference in New Issue
Block a user