Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 80bbf75883 | |||
| a6ebd047b5 | |||
| 62a38bdb76 | |||
| eafd96fd2d | |||
| 4c8b7a3b42 | |||
| 336f0b506e | |||
| 05a04b09a5 | |||
| a9559abec3 | |||
| d634484df2 | |||
| 2d001bafb6 | |||
| 558faaf74e | |||
| 4e7fe721f8 | |||
| 6d08686d32 | |||
| b009e1ff08 | |||
| add20e56eb | |||
| db8d1c1707 | |||
| 4a2487d095 |
@@ -0,0 +1,26 @@
|
||||
# pimsim-nn Oracle Invariant
|
||||
|
||||
`backend-simulators/pim/pimsim-nn` is the performance oracle. Its simulation
|
||||
behavior defines the hardware model used for Raptor/PIMCOMP comparisons.
|
||||
|
||||
## Required invariant
|
||||
|
||||
- Do not add instruction or operator support to pimsim-nn.
|
||||
- Changes to pimsim-nn must preserve simulation behavior exactly. Acceptable
|
||||
changes are limited to behavior-neutral maintenance proven not to alter
|
||||
simulated timing, scheduling, power, energy, or supported input programs.
|
||||
- Unsupported pimsim-nn operations must remain unsupported; do not approximate
|
||||
their timing or map them onto another operation.
|
||||
- Validation must report an explicit unsupported-op diagnostic as
|
||||
`UNSUPPORTED`, not as a simulator failure.
|
||||
- A compiled artifact with zero active PIM cores has no meaningful
|
||||
non-functional simulation and must be reported as `SKIP`.
|
||||
- Adapt compiler inputs to the oracle instead. For YOLO, use
|
||||
`validation/networks/pimcomp_models/yolo11n/yolo11n-pimsim-nn.onnx`, the
|
||||
dedicated pimsim-ready performance artifact with Softmax operations removed.
|
||||
Use `validation/networks/yolo11n/depth_51/yolo11n_depth_51.onnx` for YOLO
|
||||
functional validation; removing Softmax changes the model's numerical
|
||||
behavior, so the pimsim-ready artifact is not a correctness reference.
|
||||
|
||||
Any proposed pimsim-nn behavior change requires explicit user authorization and
|
||||
must not be introduced as part of a compiler optimization.
|
||||
@@ -0,0 +1,105 @@
|
||||
# PIM Synchronization Invariant
|
||||
|
||||
## Scope
|
||||
|
||||
This invariant applies to PIM `SYNC`/`WAIT` planning, event-register
|
||||
allocation, repeating core bodies, host/global-memory transfers, and pipeline
|
||||
restart synchronization.
|
||||
|
||||
## ISA contract
|
||||
|
||||
`SYNC event, target` and `WAIT event, expected` have static operands. Event
|
||||
registers start at zero, persist across instruction-stream restart, and are
|
||||
incremented by `SYNC`. `WAIT` succeeds only when the register equals its
|
||||
static expected count, then resets it to zero. A plan must therefore guarantee
|
||||
exactly the expected number of increments before each wait. It must not use
|
||||
generation-dependent registers, treat `WAIT` as `>=`, permit overshoot, or
|
||||
consume one event register with independent waits in the same iteration.
|
||||
|
||||
Event registers are local to the target core. Allocation must keep READY,
|
||||
FREE/reuse, stage-zero barrier, and downstream restart events disjoint on each
|
||||
physical core.
|
||||
|
||||
## Host/global-memory lifetime
|
||||
|
||||
For every host-routed dependency from writer `W` to reader `R`, the repeating
|
||||
program must establish:
|
||||
|
||||
```text
|
||||
ST_N -> READY SYNC -> READY WAIT -> LD_N
|
||||
LD_N -> reader END_N -> reader start_N+1 -> FREE SYNC
|
||||
-> writer FREE WAIT -> ST_N+1
|
||||
```
|
||||
|
||||
The reader emits one FREE `SYNC` per unique physical `(W, R)` relation at body
|
||||
entry, before any instruction that can block. The writer consumes each FREE
|
||||
event at most once per iteration, with a static count equal to the unique
|
||||
readers in that reuse group. The wait is placed immediately before the first
|
||||
host store that can overwrite a member of the group, after all preceding
|
||||
useful computation. Multiple transfers on one `(W, R)` relation do not create
|
||||
additional FREE signals.
|
||||
|
||||
Relations with the same writer and first dangerous overwrite boundary belong
|
||||
to one FREE group: splitting them cannot unblock an earlier store and only
|
||||
adds waits and register pressure. After preserving the READY registers required
|
||||
by a core, planning assigns independent groups to distinct overwrite boundaries
|
||||
as remaining capacity permits. Capacity-forced merged groups are ordered by
|
||||
first overwrite and waited before their earliest member. Unused READY capacity
|
||||
must not force all readers into one early aggregate wait.
|
||||
|
||||
Host receives lower to READY `WAIT` followed by host load only. They must not
|
||||
emit a post-load acknowledgement. Writers must not wait for host readers at
|
||||
the end of their body. The repeated reader-entry FREE signal is both the
|
||||
initial-slot bootstrap and the release of the previous generation.
|
||||
|
||||
## Pipeline restart independence
|
||||
|
||||
Host lifetime synchronization does not replace pipeline restart
|
||||
synchronization. At the end of every repeating stage-zero body, all unique
|
||||
stage-zero physical cores execute a dissemination barrier with
|
||||
`ceil(log2(stageZeroCoreCount))` rounds. In round `r`, rank `i` sends one
|
||||
signal to rank `(i + 2^r) mod count` and waits for exactly one signal on that
|
||||
round's destination-local event register. Each round has its own register, so
|
||||
an early signal for a later round cannot overshoot or satisfy another wait.
|
||||
|
||||
Every downstream core sends one restart-permission signal to its release-tree
|
||||
parent at body entry, after all host FREE signals. The root signals the
|
||||
stage-zero leader; other cores signal their binary-tree parent. After the barrier, the lowest-ranked
|
||||
stage-zero core waits for the root's permission before sending one restart
|
||||
signal to it. Every downstream parent waits for exactly one restart signal
|
||||
and for one permission from each existing child before forwarding the restart
|
||||
to those children and ending its current body. This reverse permission path
|
||||
prevents a fast parent from sending generation `N+1` before a child consumes
|
||||
generation `N`, which would overshoot an exact-count restart event.
|
||||
|
||||
Thus no downstream core can restart and advance its input generation until
|
||||
every stage-zero core has completed the protected generation, and every
|
||||
restart event receives exactly one signal between waits. Stage-zero and
|
||||
downstream core sets are disjoint; barrier, restart, and restart-permission
|
||||
registers are reserved on their respective target cores. Host READY/FREE
|
||||
allocation must use only the remaining registers and must not move, merge,
|
||||
remove, or weaken this ordering.
|
||||
|
||||
## Ownership and verification
|
||||
|
||||
Deferred boundary planning owns relation deduplication, event allocation,
|
||||
expected counts, and the first dangerous overwrite action. Boundary
|
||||
realization owns body-entry FREE signals and materializes planned waits at
|
||||
their exact boundaries. Channel lowering preserves those explicit operations
|
||||
and lowers host receives as READY wait plus load; it must not reconstruct
|
||||
lifetime policy after scheduling information is lost.
|
||||
|
||||
Verification must reject non-static lane/core mappings, duplicate physical
|
||||
reader representations, register collisions, oversubscribed event capacity,
|
||||
and any plan whose generated FREE signal count differs from its static wait
|
||||
count. Static and simulator deadlock detectors must model blocking WAITs,
|
||||
their remaining matching SYNC producers, missing signals, and exact-count
|
||||
overshoot in addition to SEND/RECV cycles. Both detectors must retain the
|
||||
per-source contributions accumulated since the last successful WAIT and must not
|
||||
add a wait-for edge to a source that already supplied its share of the current
|
||||
event value merely because that source has the same static SYNC in a future
|
||||
iteration. Structural tests must inspect generated instruction streams. Happens-
|
||||
before tests must unroll at least two logical iterations and add only
|
||||
same-core restart edges plus matched communication edges. Functional
|
||||
simulation must enforce exact-count waits and include stalled or randomized
|
||||
legal schedules so correctness never depends on relative core speed.
|
||||
@@ -0,0 +1,56 @@
|
||||
# Pipeline Scheduling Invariant
|
||||
|
||||
## Scope
|
||||
|
||||
This invariant applies to pipeline stage partitioning, physical-core
|
||||
assignment, scheduled materialization, deferred transfers, and pipeline
|
||||
synchronization.
|
||||
|
||||
## Invariant
|
||||
|
||||
A scheduled compute operation and all of its lanes belong to exactly one
|
||||
pipeline stage. An operation may consume results produced in its own stage or
|
||||
the immediately preceding stage only. Therefore every compute-graph edge from
|
||||
stage `S` targets stage `S` or `S + 1`; backward edges and dependencies that
|
||||
skip a stage are invalid.
|
||||
|
||||
Dynamic function inputs are stage-zero sources. Any operation that directly
|
||||
consumes one must belong to stage 0. A later stage may consume that data only
|
||||
through an explicit result forwarded by the preceding stage.
|
||||
|
||||
PEFT assigns physical cores using the injected target topology and their actual
|
||||
resident-weight capacity. Pipeline stage capacities cover every physical core
|
||||
and may have different sizes when the partitioner predicts a lower maximum
|
||||
stage interval. Before stage-local packing, the layout groups physical cores
|
||||
by bidirectional link cost; stage cores need not have consecutive IDs. Packing
|
||||
scores transfers on those physical links, and communication realization must
|
||||
preserve the resulting core identities.
|
||||
Synchronization and deferred transfers consume the explicit stage identity;
|
||||
they must not infer it from a physical core number after placement.
|
||||
|
||||
## Ownership
|
||||
|
||||
Physical PEFT supplies the initial placement. Stage partitioning is the first
|
||||
phase of pipeline scheduling and owns stage adjacency. It must construct a valid
|
||||
operation-level partition before physical-core packing. Operations split for
|
||||
physical capacity retain one shared stage identity. Repacking may move work
|
||||
only within its assigned stage. Deferred-transfer planning and
|
||||
synchronization consume the verified stage assignment; they must not repair
|
||||
or reinterpret it.
|
||||
|
||||
## Verification
|
||||
|
||||
Before scheduled materialization, verify that:
|
||||
|
||||
- every compute instance has one valid physical core and stage;
|
||||
- all lanes of one compute operation have the same stage;
|
||||
- every direct dynamic-function-input consumer belongs to stage 0;
|
||||
- every compute-graph edge stays within a stage or advances exactly one stage;
|
||||
- every stage-local resident-weight set fits its assigned physical core; and
|
||||
- stage capacities cover all physical cores exactly once; and
|
||||
- physical placement is a permutation of all target cores.
|
||||
|
||||
Pipeline scheduling tests must include an uneven physical-core layout and a
|
||||
graph with a long-lived dependency that would cross multiple naive stage
|
||||
cuts. End-to-end validation must preserve functional results and exercise the
|
||||
existing synchronization lowering without simulator changes.
|
||||
@@ -6,6 +6,9 @@ Before modifying the relevant subsystem, read:
|
||||
|
||||
* `.agents/invariants/GRAPH_COMPUTE_BATCH_INVARIANT.md`
|
||||
* `.agents/invariants/PERFORMANCE_OPTIMIZATION_INVARIANT.md`
|
||||
* `.agents/invariants/PIMSIM_NN_ORACLE_INVARIANT.md`
|
||||
* `.agents/invariants/PIM_SYNCHRONIZATION_INVARIANT.md`
|
||||
* `.agents/invariants/PIPELINE_SCHEDULING_INVARIANT.md`
|
||||
* `.agents/invariants/SPATIAL_TARGET_GENERALITY_INVARIANT.md`
|
||||
* Build commands:
|
||||
* `cmake --build ./build_release`
|
||||
|
||||
@@ -5,7 +5,7 @@ targeting in-memory computing / processing-in-memory (PIM) architectures. It
|
||||
extends ONNX-MLIR with a PIM accelerator and progressively lowers ONNX-MLIR
|
||||
through custom MLIR dialects to simulator artifacts.
|
||||
|
||||
The current target is the PIM simulator stack under `backend-simulators/pim`.
|
||||
The current target is the Pim simulator stack under `backend-simulators/pim`.
|
||||
Raptor emits binary per-core `.pim` instruction files by default, plus
|
||||
`memory.bin`, `config.json`, and weight binaries. It can also emit per-core JSON
|
||||
instruction files with `--pim-emit-json`.
|
||||
@@ -29,9 +29,9 @@ lowering, scheduling, memory layout, and code-generation optimizations.
|
||||
- `backend-simulators/pim/pim-simulator` is the in-tree Rust functional
|
||||
simulator used by validation. It reads Raptor's `pim/` artifact directory and
|
||||
compares simulator output against native ONNX-MLIR execution.
|
||||
- `backend-simulators/pim/pimsim-nn` is the non-functional simulator submodule
|
||||
used internally by validation for latency, power, and energy.
|
||||
The helper scripts in `pimcomp_utils/` are for comparison with PIMCOMP-NN and
|
||||
- `backend-simulators/pim/pimsim-nn` contains the non-functional Pimsim
|
||||
simulator used internally by validation for latency, power, and energy.
|
||||
The helper scripts in `pimcomp_utils/` are for comparison with Pimcomp and
|
||||
contain local paths; treat them as local utilities, not portable workflows.
|
||||
|
||||
## Compilation pipeline
|
||||
@@ -43,7 +43,7 @@ them to ONNX-MLIR through generated shim directories under
|
||||
High-level lowering flow:
|
||||
|
||||
```
|
||||
ONNX-MLIR -> Spatial -> Pim (tensor) -> Pim (bufferized) -> PIM artifacts
|
||||
ONNX-MLIR -> Spatial -> Pim (tensor) -> Pim (bufferized) -> Pim artifacts
|
||||
```
|
||||
|
||||
1. **ONNX -> Spatial** (`src/PIM/Conversion/ONNXToSpatial`).
|
||||
@@ -81,20 +81,20 @@ ONNX-MLIR -> Spatial -> Pim (tensor) -> Pim (bufferized) -> PIM artifacts
|
||||
addressable accesses, and `PimBufferizationVerification` checks tensor
|
||||
absence, contiguity, and copy address spaces.
|
||||
|
||||
5. **PIM local-memory planning**
|
||||
5. **Pim local-memory planning**
|
||||
(`src/PIM/Dialect/Pim/Passes/Transforms/LocalMemoryPlanning`).
|
||||
Computes whole-core lifetimes, reuses addresses for non-overlapping
|
||||
allocations, and records the explicit plan in PIM IR. Reusable lifetime
|
||||
allocations, and records the explicit plan in Pim IR. Reusable lifetime
|
||||
analysis lives under `src/PIM/Dialect/Pim/Passes/Analyses`.
|
||||
6. **PIM verification and code generation** (`src/PIM/Passes/PimCodegen` and
|
||||
6. **Pim verification and code generation** (`src/PIM/Passes/PimCodegen` and
|
||||
`src/PIM/Compiler`).
|
||||
Verifies the memory plan and other PIM invariants, then emits `.pim` core
|
||||
Verifies the memory plan and other Pim invariants, then emits `.pim` core
|
||||
files, weights, and `memory.bin` / `config.json` without rerunning liveness.
|
||||
|
||||
Supporting pieces:
|
||||
- `src/PIM/Common` - shared IR, filesystem, diagnostics, reports, and utility
|
||||
helpers.
|
||||
- `src/PIM/Compiler` - PIM compiler options, planned-address materialization, binary
|
||||
- `src/PIM/Compiler` - Pim compiler options, planned-address materialization, binary
|
||||
instruction format, artifact writing, weight emission, and codegen entry
|
||||
points.
|
||||
- `src/PIM/Conversion/SpatialToGraphviz` - optional Spatial graphviz conversion
|
||||
@@ -102,40 +102,97 @@ Supporting pieces:
|
||||
- `src/PIM/Passes` - pass registration and auxiliary passes.
|
||||
- `src/PIM/PimAccelerator.{cpp,hpp}` - ONNX-MLIR accelerator entry point.
|
||||
|
||||
## PIM compiler options
|
||||
## Pim compiler options
|
||||
|
||||
Pass these to `onnx-mlir` when compiling for PIM. These are all Raptor/PIM-specific
|
||||
Pass these to `onnx-mlir` when compiling for Pim. These are all Raptor/Pim-specific
|
||||
options; `onnx-mlir --help` lists the inherited ONNX-MLIR options.
|
||||
|
||||
- `--maccel=PIM` - select the PIM accelerator.
|
||||
- `--maccel=PIM` - select the Pim accelerator. Default: no Pim accelerator.
|
||||
- `--EmitSpatial`, `--EmitPim`, `--EmitPimBufferized`,
|
||||
`--EmitPimCodegen` - stop the PIM pipeline at the requested stage. The PIM
|
||||
default is `--EmitPimCodegen`.
|
||||
- `--core-count=<N>` - required positive core count for PIM compilation.
|
||||
- `--crossbar-size=<N>` - crossbar width/height. Default in code is `128`.
|
||||
- `--crossbar-count=<N>` - crossbars per core. Default in code is `64`.
|
||||
- `--pim-target-config=<PATH>` - optional PIM target configuration used by the
|
||||
`--EmitPimCodegen` - stop the Pim pipeline at the requested stage. Default:
|
||||
`--EmitPimCodegen` for Pim compilation.
|
||||
- `--core-count=<N>` - required positive core count for Pim compilation.
|
||||
Default: none; this option is required.
|
||||
- `--crossbar-size=<N>` - required positive crossbar width/height for Pim
|
||||
compilation. Default: none; this option is required.
|
||||
- `--crossbar-count=<N>` - required positive crossbar count per core for Pim
|
||||
compilation. Default: none; this option is required.
|
||||
- `--pipeline=<N>` - number of throughput pipeline stages; `1` preserves
|
||||
latency scheduling. Default: `1`.
|
||||
- `--pim-target-config=<PATH>` - optional Pim target configuration used by the
|
||||
target adapter to construct the target-neutral Spatial scheduling cost and
|
||||
topology model. Resource values must match the explicit core/crossbar flags.
|
||||
Default: empty; use the built-in target model.
|
||||
- `--pim-memory-report=<summary|none>` - emit the concise combined memory report
|
||||
under `reports/memory_report.txt`, or disable it. Default is `summary`.
|
||||
- `--pim-only-codegen` - assume input is already bufferized PIM IR and only run
|
||||
the codegen tail.
|
||||
under `reports/memory_report.txt`, or disable it. Default: `summary`.
|
||||
- `--pim-only-codegen` - assume input is already bufferized Pim IR and only run
|
||||
the codegen tail. Default: off.
|
||||
- `--pim-disable-synchronization` - omit generated `wait` and `sync`
|
||||
instructions for performance ablation. Default: off.
|
||||
- `--pim-disable-spatial-planning` - select the first, trivial DenseNCHW layout
|
||||
alternative for every Spatial plan operation, disabling cost-based layout
|
||||
planning while leaving ONNX rewrites and graph-compute merging enabled.
|
||||
Default: off.
|
||||
|
||||
### Spatial layout plan variants
|
||||
|
||||
Spatial plan operations advertise alternatives as an exact combination of
|
||||
operand physical layouts and one result physical layout. Every plan operation
|
||||
has the default `DenseNCHW -> DenseNCHW` alternative. The planner can select
|
||||
the following additional variants when the operation, tensor shapes, and
|
||||
target resources make them legal:
|
||||
|
||||
| Physical layout or plan | Meaning and current use |
|
||||
|---|---|
|
||||
| `DenseNCHW` | Ordinary dense NCHW storage. This is the first alternative and the one selected by `--pim-disable-spatial-planning`. |
|
||||
| `NHWCRowStrip` | Row-strip storage for NCHW logical tensors: spatial rows are processed as channel vectors. This enables row-strip lowering through compatible chains. |
|
||||
| `Fragmented` | Fragmented physical input accepted by `Flatten`, which reassembles it to dense NCHW. It is not currently selected as a plan result. |
|
||||
| `NCHWRowStrip` | A Spatial IR layout enum value reserved for NCHW-oriented row strips; current layout-capability implementations do not advertise it as a plan alternative. |
|
||||
|
||||
The operation-specific non-trivial alternatives are:
|
||||
|
||||
| Plan operation | Additional alternatives beyond dense NCHW |
|
||||
|---|---|
|
||||
| `Conv2D` | Dense input to row-strip output, or row-strip input to row-strip output when the target-dependent Conv lowering supports it. |
|
||||
| `Flatten` | Fragmented input to dense output, or row-strip input to dense output when legal. |
|
||||
| `Relu` | Row-strip input to row-strip output. |
|
||||
| `SiLU` | Row-strip input to row-strip output, with a stronger intrinsic cost preference than the generic row-strip variant. |
|
||||
| `ResizeNearest` | Row-strip input to row-strip output when its lowering is legal. |
|
||||
| `MaxPool2D` | Dense input to row-strip output, or row-strip input to row-strip output. |
|
||||
| `GlobalAveragePool` | Dense input to row-strip output, or row-strip input to row-strip output. |
|
||||
| `BiasAdd` | Row-strip data input plus a dense bias input to row-strip output when the bias shape is supported. |
|
||||
| `Add` | All data inputs row-strip to row-strip output. |
|
||||
| `Concat` | All inputs row-strip to row-strip output. |
|
||||
|
||||
Cost-based planning scores intrinsic alternative cost, operand layout
|
||||
mismatches, and downstream incompatibility, then iterates in alternating
|
||||
forward and reverse operation order until the bounded analysis converges.
|
||||
Function results are required to remain `DenseNCHW`; explicit materialization
|
||||
operations reconcile layout mismatches at boundaries. With
|
||||
`--pim-disable-spatial-planning`, the pass still runs and records a valid plan,
|
||||
but chooses the first dense alternative for every plan operation. Later graph
|
||||
compute merging is unchanged, so elementwise operations such as `Relu` remain
|
||||
separate from neighboring parallel operations and can create fan-out/fan-in
|
||||
diamonds.
|
||||
- `--pim-emit-json` - also emit `core_*.json` instruction files alongside
|
||||
`core_*.pim`.
|
||||
`core_*.pim`. Default: off.
|
||||
- `--pim-export-spatial-dataflow=<none|spatial1|spatial2|spatial3|spatial4|all>` -
|
||||
control Spatial dataflow CSV reports for the graph, trivially merged graph,
|
||||
scheduled, and realized snapshots under `reports/`. Default is `none`.
|
||||
scheduled, and realized snapshots under `reports/`. Default: `none`.
|
||||
- `--pim-conv-lowering=<auto|legacy|depthwise|packed-im2col|streamed-patch|streamed-packed|output-channel-tiled|input-k-tiled|tiled-2d>` -
|
||||
select the convolution lowering strategy. Default is `auto`.
|
||||
select the convolution lowering strategy. Default: `auto`.
|
||||
- `--pim-conv-im2col-max-elements=<N>` - maximum globally materialized im2col
|
||||
elements per convolution before streaming. Default is `1048576`.
|
||||
elements per convolution before streaming. Default: `1048576`.
|
||||
- `--pim-conv-stream-chunk-positions=<N>` - maximum output positions per
|
||||
streamed convolution chunk. Default is `1024`.
|
||||
streamed convolution chunk. Default: `1024`.
|
||||
- `--pim-report-conv-lowering=<true|false>` - emit a bounded convolution
|
||||
lowering report. Default: `true`.
|
||||
- `--pim-detect-communication-deadlock` - statically simulate expanded
|
||||
send/receive ordering and reject blocking deadlocks. Default is off.
|
||||
send/receive ordering and reject blocking deadlocks. Default: off.
|
||||
- `--pim-verify-bufferization-copy-freedom` - run the expensive official Pim
|
||||
tensor-copy freedom proof before bufferization. Default: off.
|
||||
|
||||
## Standard PIM hardware profile
|
||||
## Standard Pim hardware profile
|
||||
|
||||
Raptor's standard development and YOLO validation profile is:
|
||||
|
||||
@@ -149,7 +206,8 @@ Canonical compiler flags:
|
||||
|
||||
`--crossbar-count=64 --crossbar-size=128 --core-count=144`
|
||||
|
||||
`--core-count` remains mandatory and must be passed explicitly to the compiler.
|
||||
`--crossbar-size`, `--crossbar-count`, and `--core-count` remain mandatory and
|
||||
must be passed explicitly to the compiler.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -159,11 +217,11 @@ Example:
|
||||
--crossbar-count=64 --crossbar-size=128 --core-count=144
|
||||
```
|
||||
|
||||
This writes PIM artifacts under `/tmp/raptor/pim/`.
|
||||
This writes Pim artifacts under `/tmp/raptor/pim/`.
|
||||
|
||||
## Validation
|
||||
|
||||
Functional validation compiles ONNX models, compares native ONNX-MLIR and PIM
|
||||
Functional validation compiles ONNX models, compares native ONNX-MLIR and Pim
|
||||
simulator outputs, and optionally reports latency, power, and energy. See
|
||||
[`validation/README.md`](validation/README.md) for prerequisites, usage,
|
||||
options, artifacts, and results.
|
||||
@@ -276,7 +334,7 @@ cd backend-simulators/pim/pim-simulator
|
||||
cargo test
|
||||
```
|
||||
|
||||
## Repository Layout
|
||||
## Repository layout
|
||||
|
||||
- `src/PIM/` - PIM accelerator implementation.
|
||||
- `test/PIM/` - PIM C++ unit tests.
|
||||
@@ -284,6 +342,6 @@ cargo test
|
||||
slices, and pimsim config generation.
|
||||
- `backend-simulators/pim/pim-simulator/` - in-tree Rust functional simulator.
|
||||
- `backend-simulators/pim/pimsim-nn/` - non-functional simulator submodule.
|
||||
- `pimcomp_utils/` - local comparison helpers for PIMCOMP-NN.
|
||||
- `pimcomp_utils/` - local comparison helpers for Pimcomp.
|
||||
- `.github/actions/` and `.github/workflows/validate_operations.yml` - CI setup
|
||||
for MLIR/Protobuf caching, building Raptor, and validation.
|
||||
|
||||
@@ -11,6 +11,7 @@ use pimcore::cpu::crossbar::Crossbar;
|
||||
use pimcore::json_to_instruction::json_to_executor;
|
||||
use pimcore::memory_manager::CoreMemory;
|
||||
use pimcore::tracing::TRACER;
|
||||
use pimcore::{DiagnosticSchedulePolicy, DiagnosticScheduleTarget};
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{self, File};
|
||||
@@ -53,13 +54,45 @@ struct Args {
|
||||
#[arg(long)]
|
||||
batch_size: Option<u32>,
|
||||
|
||||
/// Input binary for one iteration; repeat once per batch entry
|
||||
#[arg(long = "input")]
|
||||
inputs: Vec<PathBuf>,
|
||||
/// Directory containing input_*.bin files, one per batch entry
|
||||
#[arg(long = "input-dir")]
|
||||
input_dir: PathBuf,
|
||||
|
||||
/// Optional directory for per-iteration output dumps
|
||||
#[arg(long)]
|
||||
batch_output_dir: Option<PathBuf>,
|
||||
|
||||
/// Optional JSONL shadow provenance trace
|
||||
#[arg(long)]
|
||||
provenance_trace: Option<PathBuf>,
|
||||
|
||||
/// Diagnostic-only barrier between global throughput iterations
|
||||
#[arg(long)]
|
||||
provenance_global_barrier: bool,
|
||||
|
||||
/// Diagnostic-only ready-core delay, formatted as CORE:CYCLES
|
||||
#[arg(long, value_name = "CORE:CYCLES")]
|
||||
provenance_core_stall: Option<String>,
|
||||
|
||||
/// Diagnostic scheduler policy; greedy is the unchanged default
|
||||
#[arg(long, value_enum, default_value_t = DiagnosticSchedulePolicyArg::Greedy)]
|
||||
diagnostic_schedule_policy: DiagnosticSchedulePolicyArg,
|
||||
|
||||
/// Deterministic seed for the randomized diagnostic scheduler
|
||||
#[arg(long, default_value_t = 0)]
|
||||
diagnostic_schedule_seed: u64,
|
||||
|
||||
/// Adversarial target: WCORE:WPC:RCORE:RPC:BEGIN:END[:READER_ITER:WRITER_MIN_ITER]
|
||||
#[arg(long, value_name = "WCORE:WPC:RCORE:RPC:BEGIN:END[:RITER:WMIN]")]
|
||||
diagnostic_schedule_target: Option<String>,
|
||||
|
||||
/// Maximum number of target-consumer deferrals
|
||||
#[arg(long, default_value_t = 10_000)]
|
||||
diagnostic_schedule_deferral_budget: u64,
|
||||
|
||||
/// Diagnostic delay applied when a target reader reaches its PC, formatted as CORE:PC:CYCLES or CORE:PC:ITERATION:CYCLES
|
||||
#[arg(long, value_name = "CORE:PC[:ITERATION]:CYCLES")]
|
||||
diagnostic_target_stall: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, ValueEnum)]
|
||||
@@ -68,6 +101,13 @@ enum ExecutionMode {
|
||||
Throughput,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, ValueEnum)]
|
||||
enum DiagnosticSchedulePolicyArg {
|
||||
Greedy,
|
||||
Randomized,
|
||||
Adversarial,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
@@ -89,6 +129,33 @@ fn main() -> Result<()> {
|
||||
}
|
||||
};
|
||||
set_memory(&mut executor, memory);
|
||||
if let Some(path) = &args.provenance_trace {
|
||||
executor.enable_provenance(path)?;
|
||||
}
|
||||
executor.set_provenance_global_barrier(args.provenance_global_barrier);
|
||||
if let Some(spec) = args.provenance_core_stall.as_deref() {
|
||||
let (core, cycles) = parse_core_stall(spec)?;
|
||||
executor.set_provenance_core_stall(core, cycles);
|
||||
}
|
||||
executor.set_diagnostic_schedule_policy(match args.diagnostic_schedule_policy {
|
||||
DiagnosticSchedulePolicyArg::Greedy => DiagnosticSchedulePolicy::Greedy,
|
||||
DiagnosticSchedulePolicyArg::Randomized => DiagnosticSchedulePolicy::Randomized,
|
||||
DiagnosticSchedulePolicyArg::Adversarial => DiagnosticSchedulePolicy::Adversarial,
|
||||
});
|
||||
executor.set_diagnostic_schedule_seed(args.diagnostic_schedule_seed);
|
||||
executor.set_diagnostic_schedule_deferral_budget(args.diagnostic_schedule_deferral_budget);
|
||||
if let Some(spec) = args.diagnostic_target_stall.as_deref() {
|
||||
let (core, pc, iteration, cycles) = parse_target_stall(spec)?;
|
||||
executor.set_diagnostic_target_stall(core, pc, iteration, cycles);
|
||||
}
|
||||
if let Some(spec) = args.diagnostic_schedule_target.as_deref() {
|
||||
executor.set_diagnostic_schedule_target(parse_schedule_target(spec)?);
|
||||
} else if matches!(
|
||||
args.diagnostic_schedule_policy,
|
||||
DiagnosticSchedulePolicyArg::Adversarial
|
||||
) {
|
||||
bail!("adversarial scheduling requires --diagnostic-schedule-target");
|
||||
}
|
||||
TRACER
|
||||
.lock()
|
||||
.unwrap()
|
||||
@@ -107,6 +174,75 @@ fn main() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_core_stall(spec: &str) -> Result<(usize, u64)> {
|
||||
let (core, cycles) = spec
|
||||
.split_once(':')
|
||||
.context("--provenance-core-stall must be CORE:CYCLES")?;
|
||||
let core = core.parse().context("invalid stalled core")?;
|
||||
let cycles = cycles.parse().context("invalid stall cycle count")?;
|
||||
if cycles == 0 {
|
||||
bail!("--provenance-core-stall cycles must be positive");
|
||||
}
|
||||
Ok((core, cycles))
|
||||
}
|
||||
|
||||
fn parse_schedule_target(spec: &str) -> Result<DiagnosticScheduleTarget> {
|
||||
let values: Vec<usize> = spec
|
||||
.split(':')
|
||||
.map(|value| {
|
||||
value
|
||||
.parse()
|
||||
.with_context(|| format!("invalid schedule target field: {value}"))
|
||||
})
|
||||
.collect::<Result<_>>()?;
|
||||
if values.len() != 6 && values.len() != 8 {
|
||||
bail!(
|
||||
"--diagnostic-schedule-target requires WCORE:WPC:RCORE:RPC:BEGIN:END with optional RITER:WMIN"
|
||||
);
|
||||
}
|
||||
if values[5] <= values[4] {
|
||||
bail!("schedule target address end must be greater than begin");
|
||||
}
|
||||
Ok(DiagnosticScheduleTarget {
|
||||
writer_core: values[0],
|
||||
writer_pc: values[1],
|
||||
reader_core: values[2],
|
||||
reader_pc: values[3],
|
||||
address_begin: values[4],
|
||||
address_end: values[5],
|
||||
reader_iteration: (values.len() == 8).then_some(values[6] as u32),
|
||||
writer_min_iteration: (values.len() == 8).then_some(values[7] as u32),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_target_stall(spec: &str) -> Result<(usize, usize, Option<u32>, u64)> {
|
||||
let values: Vec<&str> = spec.split(':').collect();
|
||||
if values.len() != 3 && values.len() != 4 {
|
||||
bail!("--diagnostic-target-stall must be CORE:PC:CYCLES or CORE:PC:ITERATION:CYCLES");
|
||||
}
|
||||
let core = values[0].parse().context("invalid target-stall core")?;
|
||||
let pc = values[1].parse().context("invalid target-stall PC")?;
|
||||
let (iteration, cycle_field) = if values.len() == 4 {
|
||||
(
|
||||
Some(
|
||||
values[2]
|
||||
.parse()
|
||||
.context("invalid target-stall iteration")?,
|
||||
),
|
||||
values[3],
|
||||
)
|
||||
} else {
|
||||
(None, values[2])
|
||||
};
|
||||
let cycles = cycle_field
|
||||
.parse()
|
||||
.context("invalid target-stall cycle count")?;
|
||||
if cycles == 0 {
|
||||
bail!("--diagnostic-target-stall cycles must be positive");
|
||||
}
|
||||
Ok((core, pc, iteration, cycles))
|
||||
}
|
||||
|
||||
fn batch_size(args: &Args) -> Result<u32> {
|
||||
match (&args.mode, args.batch_size) {
|
||||
(ExecutionMode::Latency, None | Some(1)) => Ok(1),
|
||||
@@ -142,16 +278,9 @@ fn input_regions(config: &Value) -> Result<Vec<(usize, usize)>> {
|
||||
}
|
||||
|
||||
fn retrieve_inputs(args: &Args, batch_size: u32) -> Result<Vec<Vec<u8>>> {
|
||||
if args.inputs.len() != batch_size as usize {
|
||||
bail!(
|
||||
"batch size {batch_size} requires {} inputs, got {}",
|
||||
batch_size,
|
||||
args.inputs.len()
|
||||
);
|
||||
}
|
||||
args.inputs
|
||||
.iter()
|
||||
.map(|path| fs::read(path).with_context(|| format!("Failed to read input file: {path:?}")))
|
||||
(0..batch_size)
|
||||
.map(|index| args.input_dir.join(format!("input_{index}.bin")))
|
||||
.map(|path| fs::read(&path).with_context(|| format!("Failed to read input file: {path:?}")))
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
||||
@@ -80,19 +80,19 @@ fn read_i32_le(bytes: &[u8], offset: usize) -> i32 {
|
||||
|
||||
fn parse_binary_records(bytes: &[u8]) -> Result<Vec<InstructionRecord>> {
|
||||
ensure!(bytes.len() >= HEADER_SIZE, "binary core file too small");
|
||||
ensure!(&bytes[0..4] == MAGIC, "invalid PIM binary magic");
|
||||
ensure!(&bytes[0..4] == MAGIC, "invalid Pim binary magic");
|
||||
|
||||
let version = read_u32_le(bytes, 4);
|
||||
ensure!(
|
||||
version == VERSION,
|
||||
"unsupported PIM binary version {version}"
|
||||
"unsupported Pim binary version {version}"
|
||||
);
|
||||
|
||||
let instruction_count = read_u32_le(bytes, 8) as usize;
|
||||
let expected_len = HEADER_SIZE + instruction_count * RECORD_SIZE;
|
||||
ensure!(
|
||||
bytes.len() == expected_len,
|
||||
"PIM binary size mismatch: expected {expected_len} bytes, got {}",
|
||||
"Pim binary size mismatch: expected {expected_len} bytes, got {}",
|
||||
bytes.len()
|
||||
);
|
||||
|
||||
@@ -335,7 +335,7 @@ fn append_record(
|
||||
.set_offset_select_value(generic1, 0);
|
||||
inst_builder.make_inst(sync, inst_data_builder.build());
|
||||
}
|
||||
_ => bail!("unsupported PIM binary opcode {opcode}"),
|
||||
_ => bail!("unsupported Pim binary opcode {opcode}"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2,11 +2,14 @@ use crate::utility::AddressArg;
|
||||
use anyhow::{Context, Result, ensure};
|
||||
use std::{collections::HashMap, fmt::Debug};
|
||||
|
||||
use super::{DiagnosticSchedulePolicy, DiagnosticScheduleTarget};
|
||||
use crate::{
|
||||
cpu::crossbar::Crossbar,
|
||||
instruction_set::Instructions,
|
||||
memory_manager::{CoreMemory, MemoryStorable, type_traits::TryToUsize},
|
||||
provenance::ProvenanceTracker,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
pub mod crossbar;
|
||||
|
||||
@@ -14,6 +17,7 @@ pub mod crossbar;
|
||||
pub struct CPU<'a> {
|
||||
cores: Box<[Core<'a>]>,
|
||||
batch_outputs: Option<BatchOutputs>,
|
||||
provenance: Option<ProvenanceTracker>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -53,9 +57,265 @@ impl<'a> CPU<'a> {
|
||||
Self {
|
||||
cores: cores.into(),
|
||||
batch_outputs: None,
|
||||
provenance: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn enable_provenance(
|
||||
&mut self,
|
||||
path: impl AsRef<std::path::Path>,
|
||||
) -> std::io::Result<()> {
|
||||
self.provenance = Some(ProvenanceTracker::new(self.cores.len(), path)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn begin_provenance_batch(&mut self, batch_size: usize) {
|
||||
if let Some(provenance) = &mut self.provenance {
|
||||
provenance.begin_batch(batch_size);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_execution_context(
|
||||
&mut self,
|
||||
cycle: u64,
|
||||
core: usize,
|
||||
pc: usize,
|
||||
iteration: u32,
|
||||
) {
|
||||
if let Some(provenance) = &mut self.provenance {
|
||||
provenance.set_context(cycle, core, pc, iteration);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn provenance_input_store(&mut self, address: usize, size: usize, sample: u32) {
|
||||
if let Some(provenance) = &mut self.provenance {
|
||||
provenance.input_store(address, size, sample);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn provenance_global_store_from_local(
|
||||
&mut self,
|
||||
core: usize,
|
||||
global_address: usize,
|
||||
local_address: usize,
|
||||
size: usize,
|
||||
) {
|
||||
if let Some(provenance) = &mut self.provenance {
|
||||
provenance.global_store_from_local(core, global_address, local_address, size);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn provenance_global_load_to_local(
|
||||
&mut self,
|
||||
core: usize,
|
||||
global_address: usize,
|
||||
local_address: usize,
|
||||
size: usize,
|
||||
) {
|
||||
if let Some(provenance) = &mut self.provenance {
|
||||
provenance.global_load_to_local(core, global_address, local_address, size);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn provenance_local_copy(
|
||||
&mut self,
|
||||
core: usize,
|
||||
destination: usize,
|
||||
source: usize,
|
||||
size: usize,
|
||||
operation: &'static str,
|
||||
) {
|
||||
if let Some(provenance) = &mut self.provenance {
|
||||
provenance.local_copy(core, destination, source, size, operation);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn provenance_local_strided_copy(
|
||||
&mut self,
|
||||
core: usize,
|
||||
destination: usize,
|
||||
source: usize,
|
||||
element_size: usize,
|
||||
stride: usize,
|
||||
element_count: usize,
|
||||
operation: &'static str,
|
||||
) {
|
||||
if let Some(provenance) = &mut self.provenance {
|
||||
provenance.local_strided_copy(
|
||||
core,
|
||||
destination,
|
||||
source,
|
||||
element_size,
|
||||
stride,
|
||||
element_count,
|
||||
operation,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn provenance_local_transform(
|
||||
&mut self,
|
||||
core: usize,
|
||||
destination: usize,
|
||||
sources: &[(usize, usize)],
|
||||
output_size: usize,
|
||||
operation: &'static str,
|
||||
) {
|
||||
if let Some(provenance) = &mut self.provenance {
|
||||
provenance.local_transform(core, destination, sources, output_size, operation);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn provenance_local_broadcast_transform(
|
||||
&mut self,
|
||||
core: usize,
|
||||
destination: usize,
|
||||
source: usize,
|
||||
source_size: usize,
|
||||
output_size: usize,
|
||||
operation: &'static str,
|
||||
) {
|
||||
if let Some(provenance) = &mut self.provenance {
|
||||
provenance.local_broadcast_transform(
|
||||
core,
|
||||
destination,
|
||||
source,
|
||||
source_size,
|
||||
output_size,
|
||||
operation,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn provenance_local_mvm_transform(
|
||||
&mut self,
|
||||
core: usize,
|
||||
destination: usize,
|
||||
source: usize,
|
||||
element_size: usize,
|
||||
output_size: usize,
|
||||
used_rows: &[bool],
|
||||
operation: &'static str,
|
||||
) {
|
||||
if let Some(provenance) = &mut self.provenance {
|
||||
provenance.local_mvm_transform(
|
||||
core,
|
||||
destination,
|
||||
source,
|
||||
element_size,
|
||||
output_size,
|
||||
used_rows,
|
||||
operation,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn provenance_send_transfer(
|
||||
&mut self,
|
||||
sender: usize,
|
||||
receiver: usize,
|
||||
source: usize,
|
||||
destination: usize,
|
||||
size: usize,
|
||||
) {
|
||||
if let Some(provenance) = &mut self.provenance {
|
||||
provenance.send_transfer(sender, receiver, source, destination, size);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn finish_provenance(&mut self) {
|
||||
if let Some(provenance) = &mut self.provenance {
|
||||
provenance.flush();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn provenance_schedule_stall(&mut self, core: usize, remaining_cycles: u64) {
|
||||
if let Some(provenance) = &self.provenance {
|
||||
provenance.schedule_stall(core, remaining_cycles);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn provenance_schedule_target_stall(
|
||||
&mut self,
|
||||
core: usize,
|
||||
pc: usize,
|
||||
remaining_cycles: u64,
|
||||
) {
|
||||
if let Some(provenance) = &self.provenance {
|
||||
provenance.schedule_target_stall(core, pc, remaining_cycles);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn provenance_schedule_config(&mut self, config: super::DiagnosticScheduleConfig) {
|
||||
if let Some(provenance) = &self.provenance {
|
||||
provenance.schedule_config(
|
||||
match config.policy {
|
||||
DiagnosticSchedulePolicy::Greedy => "greedy",
|
||||
DiagnosticSchedulePolicy::Randomized => "randomized",
|
||||
DiagnosticSchedulePolicy::Adversarial => "adversarial",
|
||||
},
|
||||
config.seed,
|
||||
config.target.map(|target| {
|
||||
json!({
|
||||
"writer_core": target.writer_core,
|
||||
"writer_pc": target.writer_pc,
|
||||
"reader_core": target.reader_core,
|
||||
"reader_pc": target.reader_pc,
|
||||
"address_begin": target.address_begin,
|
||||
"address_end": target.address_end,
|
||||
"reader_iteration": target.reader_iteration,
|
||||
"writer_min_iteration": target.writer_min_iteration,
|
||||
})
|
||||
}),
|
||||
config.deferral_budget,
|
||||
config.fixed_stall,
|
||||
config.fixed_target_stall,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn provenance_scheduler_event(
|
||||
&mut self,
|
||||
event: &'static str,
|
||||
core: usize,
|
||||
pc: usize,
|
||||
iteration: u32,
|
||||
reason: &'static str,
|
||||
selected_core: Option<usize>,
|
||||
target: Option<DiagnosticScheduleTarget>,
|
||||
deferrals: u64,
|
||||
) {
|
||||
if let Some(provenance) = &self.provenance {
|
||||
let target = target.map(|target| {
|
||||
json!({
|
||||
"writer_core": target.writer_core,
|
||||
"writer_pc": target.writer_pc,
|
||||
"reader_core": target.reader_core,
|
||||
"reader_pc": target.reader_pc,
|
||||
"address_begin": target.address_begin,
|
||||
"address_end": target.address_end,
|
||||
"reader_iteration": target.reader_iteration,
|
||||
"writer_min_iteration": target.writer_min_iteration,
|
||||
})
|
||||
});
|
||||
provenance.scheduler_event(json!({
|
||||
"event": event,
|
||||
"cycle": self.provenance_cycle(),
|
||||
"core": core,
|
||||
"pc": pc,
|
||||
"core_iteration": iteration,
|
||||
"reason": reason,
|
||||
"selected_core": selected_core,
|
||||
"target": target,
|
||||
"deferrals": deferrals,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
fn provenance_cycle(&self) -> u64 {
|
||||
self.provenance.as_ref().map_or(0, ProvenanceTracker::cycle)
|
||||
}
|
||||
|
||||
pub(crate) fn set_current_iteration(&mut self, iteration: u32) {
|
||||
if let Some(batch_outputs) = &mut self.batch_outputs {
|
||||
batch_outputs.iteration = iteration as usize;
|
||||
@@ -93,6 +353,7 @@ impl<'a> CPU<'a> {
|
||||
let Self {
|
||||
cores,
|
||||
batch_outputs,
|
||||
..
|
||||
} = self;
|
||||
let (host, cores) = cores.split_at_mut(1);
|
||||
let bytes = cores[core - 1].load::<u8>(core_address, size)?[0];
|
||||
|
||||
@@ -285,6 +285,10 @@ where
|
||||
let load = loads[0];
|
||||
let vec: Cow<[M]> = load.up();
|
||||
let matrix = crossbar.load::<M>(crossbar_stored_bytes)?[0];
|
||||
let used_rows: Vec<bool> = matrix
|
||||
.chunks_exact(crossbar_elem_width)
|
||||
.map(|row| row.iter().any(|value| *value != M::from_f32(0.0)))
|
||||
.collect();
|
||||
|
||||
// --- FAER IMPLEMENTATION ---
|
||||
|
||||
@@ -323,6 +327,16 @@ where
|
||||
|
||||
let res_up: Cow<[T]> = res.as_slice().up();
|
||||
core.execute_store(rd_val, res_up.as_ref());
|
||||
let _ = core;
|
||||
cores.provenance_local_mvm_transform(
|
||||
core_indx as usize,
|
||||
rd_val as usize,
|
||||
r1_val as usize,
|
||||
size_of::<F>(),
|
||||
res_up.len() * size_of::<T>(),
|
||||
&used_rows,
|
||||
"mvmul",
|
||||
);
|
||||
|
||||
TRACER.lock().unwrap().post_mvm::<F, M, T>(cores, data);
|
||||
Ok(InstructionStatus::Completed)
|
||||
@@ -389,6 +403,14 @@ where
|
||||
);
|
||||
let res_up: Cow<[T]> = res.as_slice().up();
|
||||
core.execute_store(rd_val, res_up.as_ref());
|
||||
let _ = core;
|
||||
cores.provenance_local_transform(
|
||||
core_indx as usize,
|
||||
rd_val,
|
||||
&[(r1_val, byte_len), (r2_val, byte_len)],
|
||||
byte_len,
|
||||
"vvadd",
|
||||
);
|
||||
TRACER.lock().unwrap().post_vvadd::<F, T>(cores, data);
|
||||
Ok(InstructionStatus::Completed)
|
||||
}
|
||||
@@ -474,6 +496,13 @@ where
|
||||
);
|
||||
let res_up: Cow<[T]> = res.as_slice().up();
|
||||
core.execute_store(rd_val, res_up.as_ref());
|
||||
cores.provenance_local_transform(
|
||||
core_indx as usize,
|
||||
rd_val,
|
||||
&[(r1_val, byte_len), (r2_val, byte_len)],
|
||||
byte_len,
|
||||
"vvmul",
|
||||
);
|
||||
Ok(InstructionStatus::Completed)
|
||||
}
|
||||
|
||||
@@ -780,6 +809,15 @@ where
|
||||
);
|
||||
}
|
||||
core.execute_store(destination, &result)?;
|
||||
cores.provenance_local_strided_copy(
|
||||
core_indx as usize,
|
||||
destination as usize,
|
||||
source,
|
||||
size_of::<F>(),
|
||||
stride,
|
||||
element_count,
|
||||
"vmv",
|
||||
);
|
||||
Ok(InstructionStatus::Completed)
|
||||
}
|
||||
|
||||
@@ -799,16 +837,23 @@ pub fn vrsl(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus>
|
||||
#[inline(never)]
|
||||
pub fn ld(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
|
||||
TRACER.lock().unwrap().pre_ld(cores, data);
|
||||
let (core, rd, r1, _, imm_len, offset_select, offset_value) =
|
||||
let (core_index, rd, r1, _, imm_len, offset_select, offset_value) =
|
||||
data.get_core_rd_r1_r2_immlen_offset();
|
||||
ensure!(core != 0, "LD cannot be used to move from host to host");
|
||||
let (host, core) = cores.host_and_cores(core);
|
||||
ensure!(
|
||||
core_index != 0,
|
||||
"LD cannot be used to move from host to host"
|
||||
);
|
||||
let (r1_val, rd_val) = {
|
||||
let (host, core) = cores.host_and_cores(core_index);
|
||||
let r1_val = core.register(r1);
|
||||
let rd_val = core.register(rd);
|
||||
let r1_val = add_offset_r1(r1_val, offset_select, offset_value);
|
||||
let rd_val = add_offset_rd(rd_val, offset_select, offset_value);
|
||||
let global_memory = host.load::<u8>(r1_val, imm_len)?;
|
||||
core.execute_store(rd_val, global_memory[0])?;
|
||||
(r1_val, rd_val)
|
||||
};
|
||||
cores.provenance_global_load_to_local(core_index as usize, r1_val, rd_val, imm_len as usize);
|
||||
TRACER.lock().unwrap().post_ld(cores, data);
|
||||
Ok(InstructionStatus::Completed)
|
||||
}
|
||||
@@ -828,6 +873,7 @@ pub fn st(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
|
||||
(rd_val, r1_val)
|
||||
};
|
||||
cores.store_to_host(core, rd_val, r1_val, imm_len)?;
|
||||
cores.provenance_global_store_from_local(core as usize, rd_val, r1_val, imm_len as usize);
|
||||
TRACER.lock().unwrap().post_st(cores, data);
|
||||
Ok(InstructionStatus::Completed)
|
||||
}
|
||||
@@ -852,9 +898,9 @@ pub fn lldi(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus>
|
||||
#[inline(never)]
|
||||
pub fn lmv(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
|
||||
TRACER.lock().unwrap().pre_lmv(cores, data);
|
||||
let (core, rd, r1, _, imm_len, offset_select, offset_value) =
|
||||
let (core_index, rd, r1, _, imm_len, offset_select, offset_value) =
|
||||
data.get_core_rd_r1_r2_immlen_offset();
|
||||
let core = cores.core(core);
|
||||
let core = cores.core(core_index);
|
||||
let r1_val = core.register(r1);
|
||||
let rd_val = core.register(rd);
|
||||
let r1_val = add_offset_r1(r1_val, offset_select, offset_value);
|
||||
@@ -862,6 +908,8 @@ pub fn lmv(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus>
|
||||
let local_memory = core.load::<u8>(r1_val, imm_len)?;
|
||||
let tmp = local_memory[0].to_vec();
|
||||
core.execute_store(rd_val, tmp.as_slice());
|
||||
let _ = core;
|
||||
cores.provenance_local_copy(core_index as usize, rd_val, r1_val, imm_len as usize, "lmv");
|
||||
TRACER.lock().unwrap().post_lmv(cores, data);
|
||||
Ok(InstructionStatus::Completed)
|
||||
}
|
||||
@@ -886,11 +934,21 @@ pub fn recv(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus>
|
||||
Ok(InstructionStatus::Receiving(data))
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub fn isa_wait(functor: usize) -> bool {
|
||||
(wait as *const () as usize) == functor
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub fn wait(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
|
||||
Ok(InstructionStatus::Waiting(data))
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub fn isa_sync(functor: usize) -> bool {
|
||||
(sync as *const () as usize) == functor
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub fn sync(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
|
||||
Ok(InstructionStatus::Sync(data))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,639 @@
|
||||
use serde_json::{Value, json};
|
||||
use std::{
|
||||
collections::BTreeSet,
|
||||
fs::File,
|
||||
io::{BufWriter, Write},
|
||||
path::Path,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Provenance {
|
||||
#[default]
|
||||
Uninitialized,
|
||||
Unknown,
|
||||
Samples(u64),
|
||||
}
|
||||
|
||||
impl Provenance {
|
||||
pub fn sample(sample: u32) -> Self {
|
||||
if sample < 64 {
|
||||
Self::Samples(1_u64 << sample)
|
||||
} else {
|
||||
Self::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
fn merge(self, other: Self) -> Self {
|
||||
match (self, other) {
|
||||
(Self::Unknown, _) | (_, Self::Unknown) => Self::Unknown,
|
||||
(Self::Uninitialized, value) | (value, Self::Uninitialized) => value,
|
||||
(Self::Samples(left), Self::Samples(right)) => Self::Samples(left | right),
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_all(values: impl IntoIterator<Item = Self>) -> Self {
|
||||
let mut values = values.into_iter();
|
||||
values
|
||||
.next()
|
||||
.map_or(Self::Uninitialized, |first| values.fold(first, Self::merge))
|
||||
}
|
||||
|
||||
fn samples(self) -> Vec<u32> {
|
||||
match self {
|
||||
Self::Samples(mask) => (0..64)
|
||||
.filter(|sample| mask & (1_u64 << sample) != 0)
|
||||
.collect(),
|
||||
Self::Unknown | Self::Uninitialized => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn state(self) -> &'static str {
|
||||
match self {
|
||||
Self::Samples(_) => "known",
|
||||
Self::Unknown => "unknown",
|
||||
Self::Uninitialized => "uninitialized",
|
||||
}
|
||||
}
|
||||
|
||||
fn is_mixed(self) -> bool {
|
||||
matches!(self, Self::Samples(mask) if mask.count_ones() > 1)
|
||||
}
|
||||
|
||||
fn json(self) -> Value {
|
||||
json!({
|
||||
"provenance": self.samples(),
|
||||
"provenance_state": self.state(),
|
||||
"mixed": self.is_mixed(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct ExecutionContext {
|
||||
cycle: u64,
|
||||
core: usize,
|
||||
pc: usize,
|
||||
iteration: u32,
|
||||
}
|
||||
|
||||
impl Default for ExecutionContext {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
cycle: 0,
|
||||
core: 0,
|
||||
pc: 0,
|
||||
iteration: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct Writer {
|
||||
version: u64,
|
||||
cycle: u64,
|
||||
core: usize,
|
||||
pc: usize,
|
||||
iteration: u32,
|
||||
provenance: Provenance,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
struct GlobalCell {
|
||||
provenance: Provenance,
|
||||
writer: Option<Writer>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TraceSink {
|
||||
output: BufWriter<File>,
|
||||
}
|
||||
|
||||
impl TraceSink {
|
||||
fn new(path: impl AsRef<Path>) -> std::io::Result<Self> {
|
||||
let file = File::create(path)?;
|
||||
Ok(Self {
|
||||
output: BufWriter::new(file),
|
||||
})
|
||||
}
|
||||
|
||||
fn event(&mut self, value: Value) {
|
||||
serde_json::to_writer(&mut self.output, &value).expect("write provenance event");
|
||||
self.output
|
||||
.write_all(b"\n")
|
||||
.expect("write provenance newline");
|
||||
}
|
||||
|
||||
fn flush(&mut self) {
|
||||
self.output.flush().expect("flush provenance trace");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProvenanceTracker {
|
||||
global: Vec<GlobalCell>,
|
||||
local: Vec<Vec<Provenance>>,
|
||||
next_version: u64,
|
||||
context: ExecutionContext,
|
||||
sink: Arc<Mutex<TraceSink>>,
|
||||
}
|
||||
|
||||
impl ProvenanceTracker {
|
||||
pub fn new(core_count: usize, path: impl AsRef<Path>) -> std::io::Result<Self> {
|
||||
if let Some(parent) = path.as_ref().parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
let sink = Arc::new(Mutex::new(TraceSink::new(path)?));
|
||||
let tracker = Self {
|
||||
global: Vec::new(),
|
||||
local: vec![Vec::new(); core_count],
|
||||
next_version: 0,
|
||||
context: ExecutionContext::default(),
|
||||
sink,
|
||||
};
|
||||
tracker.write(json!({
|
||||
"event": "provenance_trace_start",
|
||||
"schema": 1,
|
||||
"core_count": core_count,
|
||||
}));
|
||||
Ok(tracker)
|
||||
}
|
||||
|
||||
pub fn begin_batch(&mut self, batch_size: usize) {
|
||||
self.global.fill(GlobalCell::default());
|
||||
for memory in &mut self.local {
|
||||
memory.fill(Provenance::Uninitialized);
|
||||
}
|
||||
self.next_version = 0;
|
||||
self.write(json!({
|
||||
"event": "batch_start",
|
||||
"batch_size": batch_size,
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn set_context(&mut self, cycle: u64, core: usize, pc: usize, iteration: u32) {
|
||||
self.context = ExecutionContext {
|
||||
cycle,
|
||||
core,
|
||||
pc,
|
||||
iteration,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn cycle(&self) -> u64 {
|
||||
self.context.cycle
|
||||
}
|
||||
|
||||
pub fn flush(&mut self) {
|
||||
self.sink.lock().unwrap().flush();
|
||||
}
|
||||
|
||||
pub fn schedule_stall(&self, core: usize, remaining_cycles: u64) {
|
||||
self.write(json!({
|
||||
"event": "diagnostic_core_stall",
|
||||
"cycle": self.context.cycle,
|
||||
"core": core,
|
||||
"pc": self.context.pc,
|
||||
"core_iteration": self.context.iteration,
|
||||
"remaining_cycles": remaining_cycles,
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn schedule_target_stall(&self, core: usize, pc: usize, remaining_cycles: u64) {
|
||||
self.write(json!({
|
||||
"event": "diagnostic_target_stall",
|
||||
"cycle": self.context.cycle,
|
||||
"core": core,
|
||||
"pc": pc,
|
||||
"core_iteration": self.context.iteration,
|
||||
"remaining_cycles": remaining_cycles,
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn schedule_config(
|
||||
&self,
|
||||
policy: &'static str,
|
||||
seed: u64,
|
||||
target: Option<Value>,
|
||||
deferral_budget: u64,
|
||||
fixed_stall: Option<(usize, u64)>,
|
||||
fixed_target_stall: Option<(usize, usize, Option<u32>, u64)>,
|
||||
) {
|
||||
self.write(json!({
|
||||
"event": "scheduler_config",
|
||||
"schedule_policy": policy,
|
||||
"schedule_seed": seed,
|
||||
"target_dependency": target,
|
||||
"deferral_budget": deferral_budget,
|
||||
"fixed_stall": fixed_stall.map(|(core, cycles)| json!({
|
||||
"core": core,
|
||||
"cycles": cycles,
|
||||
})),
|
||||
"fixed_target_stall": fixed_target_stall.map(|(core, pc, iteration, cycles)| json!({
|
||||
"core": core,
|
||||
"pc": pc,
|
||||
"iteration": iteration,
|
||||
"cycles": cycles,
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn scheduler_event(&self, event: Value) {
|
||||
self.write(event);
|
||||
}
|
||||
|
||||
fn write(&self, value: Value) {
|
||||
self.sink.lock().unwrap().event(value);
|
||||
}
|
||||
|
||||
fn ensure_global(&mut self, end: usize) {
|
||||
if self.global.len() < end {
|
||||
self.global.resize(end, GlobalCell::default());
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_local(&mut self, core: usize, end: usize) {
|
||||
if let Some(memory) = self.local.get_mut(core)
|
||||
&& memory.len() < end
|
||||
{
|
||||
memory.resize(end, Provenance::Uninitialized);
|
||||
}
|
||||
}
|
||||
|
||||
fn local_tags(&mut self, core: usize, address: usize, size: usize) -> Vec<Provenance> {
|
||||
let Some(end) = address.checked_add(size) else {
|
||||
return vec![Provenance::Unknown; size];
|
||||
};
|
||||
self.ensure_local(core, end);
|
||||
self.local[core][address..end].to_vec()
|
||||
}
|
||||
|
||||
fn store_local_tags(&mut self, core: usize, address: usize, tags: &[Provenance]) {
|
||||
let Some(end) = address.checked_add(tags.len()) else {
|
||||
return;
|
||||
};
|
||||
self.ensure_local(core, end);
|
||||
self.local[core][address..end].copy_from_slice(tags);
|
||||
}
|
||||
|
||||
fn unique_versions(cells: &[GlobalCell]) -> Vec<u64> {
|
||||
cells
|
||||
.iter()
|
||||
.filter_map(|cell| cell.writer.map(|writer| writer.version))
|
||||
.collect::<BTreeSet<_>>()
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn unique_writers(cells: &[GlobalCell]) -> Vec<Writer> {
|
||||
cells
|
||||
.iter()
|
||||
.filter_map(|cell| cell.writer)
|
||||
.collect::<BTreeSet<_>>()
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn writer_json(writer: Writer) -> Value {
|
||||
json!({
|
||||
"version": writer.version,
|
||||
"cycle": writer.cycle,
|
||||
"core": writer.core,
|
||||
"pc": writer.pc,
|
||||
"core_iteration": writer.iteration,
|
||||
"provenance": writer.provenance.samples(),
|
||||
"provenance_state": writer.provenance.state(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn input_store(&mut self, address: usize, size: usize, sample: u32) {
|
||||
let Some(end) = address.checked_add(size) else {
|
||||
return;
|
||||
};
|
||||
self.ensure_global(end);
|
||||
let provenance = Provenance::sample(sample);
|
||||
self.next_version += 1;
|
||||
let writer = Writer {
|
||||
version: self.next_version,
|
||||
cycle: self.context.cycle,
|
||||
core: 0,
|
||||
pc: 0,
|
||||
iteration: sample,
|
||||
provenance,
|
||||
};
|
||||
let overwritten_versions = Self::unique_versions(&self.global[address..end]);
|
||||
for cell in &mut self.global[address..end] {
|
||||
*cell = GlobalCell {
|
||||
provenance,
|
||||
writer: Some(writer),
|
||||
};
|
||||
}
|
||||
let mut event = json!({
|
||||
"event": "external_input_store",
|
||||
"cycle": self.context.cycle,
|
||||
"core": 0,
|
||||
"pc": 0,
|
||||
"core_iteration": sample,
|
||||
"address": address,
|
||||
"size": size,
|
||||
"sample": sample,
|
||||
"version": writer.version,
|
||||
"overwritten_versions": overwritten_versions,
|
||||
});
|
||||
if let Some(object) = event.as_object_mut() {
|
||||
object.extend(provenance.json().as_object().unwrap().clone());
|
||||
}
|
||||
self.write(event);
|
||||
}
|
||||
|
||||
pub fn global_store_from_local(
|
||||
&mut self,
|
||||
core: usize,
|
||||
global_address: usize,
|
||||
local_address: usize,
|
||||
size: usize,
|
||||
) {
|
||||
let Some(end) = global_address.checked_add(size) else {
|
||||
return;
|
||||
};
|
||||
let tags = self.local_tags(core, local_address, size);
|
||||
self.ensure_global(end);
|
||||
self.next_version += 1;
|
||||
let provenance = Provenance::merge_all(tags.iter().copied());
|
||||
let writer = Writer {
|
||||
version: self.next_version,
|
||||
cycle: self.context.cycle,
|
||||
core,
|
||||
pc: self.context.pc,
|
||||
iteration: self.context.iteration,
|
||||
provenance,
|
||||
};
|
||||
let overwritten_versions = Self::unique_versions(&self.global[global_address..end]);
|
||||
let overwritten_writers = Self::unique_writers(&self.global[global_address..end]);
|
||||
for (cell, tag) in self.global[global_address..end].iter_mut().zip(tags) {
|
||||
*cell = GlobalCell {
|
||||
provenance: tag,
|
||||
writer: Some(writer),
|
||||
};
|
||||
}
|
||||
let mut event = json!({
|
||||
"event": "global_store",
|
||||
"cycle": self.context.cycle,
|
||||
"core": core,
|
||||
"pc": self.context.pc,
|
||||
"core_iteration": self.context.iteration,
|
||||
"address": global_address,
|
||||
"local_address": local_address,
|
||||
"size": size,
|
||||
"version": writer.version,
|
||||
"overwritten_versions": overwritten_versions,
|
||||
"overwritten_writers": overwritten_writers.into_iter().map(Self::writer_json).collect::<Vec<_>>(),
|
||||
});
|
||||
if let Some(object) = event.as_object_mut() {
|
||||
object.extend(provenance.json().as_object().unwrap().clone());
|
||||
}
|
||||
self.write(event);
|
||||
}
|
||||
|
||||
pub fn global_load_to_local(
|
||||
&mut self,
|
||||
core: usize,
|
||||
global_address: usize,
|
||||
local_address: usize,
|
||||
size: usize,
|
||||
) {
|
||||
let Some(end) = global_address.checked_add(size) else {
|
||||
return;
|
||||
};
|
||||
self.ensure_global(end);
|
||||
let cells = self.global[global_address..end].to_vec();
|
||||
let tags: Vec<_> = cells.iter().map(|cell| cell.provenance).collect();
|
||||
let provenance = Provenance::merge_all(tags.iter().copied());
|
||||
let writers = Self::unique_writers(&cells);
|
||||
let versions = Self::unique_versions(&cells);
|
||||
self.store_local_tags(core, local_address, &tags);
|
||||
let mut event = json!({
|
||||
"event": "global_load",
|
||||
"cycle": self.context.cycle,
|
||||
"core": core,
|
||||
"pc": self.context.pc,
|
||||
"core_iteration": self.context.iteration,
|
||||
"address": global_address,
|
||||
"local_address": local_address,
|
||||
"size": size,
|
||||
"versions": versions,
|
||||
"last_writers": writers.into_iter().map(Self::writer_json).collect::<Vec<_>>(),
|
||||
});
|
||||
if let Some(object) = event.as_object_mut() {
|
||||
object.extend(provenance.json().as_object().unwrap().clone());
|
||||
}
|
||||
self.write(event);
|
||||
}
|
||||
|
||||
pub fn local_copy(
|
||||
&mut self,
|
||||
core: usize,
|
||||
destination: usize,
|
||||
source: usize,
|
||||
size: usize,
|
||||
operation: &'static str,
|
||||
) {
|
||||
let tags = self.local_tags(core, source, size);
|
||||
let provenance = Provenance::merge_all(tags.iter().copied());
|
||||
self.store_local_tags(core, destination, &tags);
|
||||
self.local_event(
|
||||
operation,
|
||||
core,
|
||||
destination,
|
||||
size,
|
||||
provenance,
|
||||
&[provenance],
|
||||
);
|
||||
}
|
||||
|
||||
pub fn local_strided_copy(
|
||||
&mut self,
|
||||
core: usize,
|
||||
destination: usize,
|
||||
source: usize,
|
||||
element_size: usize,
|
||||
stride: usize,
|
||||
element_count: usize,
|
||||
operation: &'static str,
|
||||
) {
|
||||
let mut tags = Vec::with_capacity(element_size.saturating_mul(element_count));
|
||||
for index in 0..element_count {
|
||||
let address =
|
||||
source.saturating_add(index.saturating_mul(stride).saturating_mul(element_size));
|
||||
tags.extend(self.local_tags(core, address, element_size));
|
||||
}
|
||||
let provenance = Provenance::merge_all(tags.iter().copied());
|
||||
self.store_local_tags(core, destination, &tags);
|
||||
self.local_event(
|
||||
operation,
|
||||
core,
|
||||
destination,
|
||||
tags.len(),
|
||||
provenance,
|
||||
&[provenance],
|
||||
);
|
||||
}
|
||||
|
||||
pub fn local_transform(
|
||||
&mut self,
|
||||
core: usize,
|
||||
destination: usize,
|
||||
sources: &[(usize, usize)],
|
||||
output_size: usize,
|
||||
operation: &'static str,
|
||||
) {
|
||||
let source_tags: Vec<Vec<_>> = sources
|
||||
.iter()
|
||||
.map(|&(address, size)| self.local_tags(core, address, size))
|
||||
.collect();
|
||||
let mut output = Vec::with_capacity(output_size);
|
||||
for index in 0..output_size {
|
||||
let provenance = Provenance::merge_all(
|
||||
source_tags
|
||||
.iter()
|
||||
.filter_map(|tags| tags.get(index).copied()),
|
||||
);
|
||||
output.push(provenance);
|
||||
}
|
||||
let provenance = Provenance::merge_all(output.iter().copied());
|
||||
self.store_local_tags(core, destination, &output);
|
||||
let operands: Vec<_> = source_tags
|
||||
.iter()
|
||||
.map(|tags| Provenance::merge_all(tags.iter().copied()))
|
||||
.collect();
|
||||
self.local_event(
|
||||
operation,
|
||||
core,
|
||||
destination,
|
||||
output_size,
|
||||
provenance,
|
||||
&operands,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn local_broadcast_transform(
|
||||
&mut self,
|
||||
core: usize,
|
||||
destination: usize,
|
||||
source: usize,
|
||||
source_size: usize,
|
||||
output_size: usize,
|
||||
operation: &'static str,
|
||||
) {
|
||||
let tags = self.local_tags(core, source, source_size);
|
||||
let provenance = Provenance::merge_all(tags.iter().copied());
|
||||
self.store_local_tags(core, destination, &vec![provenance; output_size]);
|
||||
self.local_event(
|
||||
operation,
|
||||
core,
|
||||
destination,
|
||||
output_size,
|
||||
provenance,
|
||||
&[provenance],
|
||||
);
|
||||
}
|
||||
|
||||
pub fn local_mvm_transform(
|
||||
&mut self,
|
||||
core: usize,
|
||||
destination: usize,
|
||||
source: usize,
|
||||
element_size: usize,
|
||||
output_size: usize,
|
||||
used_rows: &[bool],
|
||||
operation: &'static str,
|
||||
) {
|
||||
let mut provenance = Provenance::Uninitialized;
|
||||
for (row, used) in used_rows.iter().copied().enumerate() {
|
||||
if used {
|
||||
provenance = provenance.merge(Provenance::merge_all(self.local_tags(
|
||||
core,
|
||||
source + row * element_size,
|
||||
element_size,
|
||||
)));
|
||||
}
|
||||
}
|
||||
self.store_local_tags(core, destination, &vec![provenance; output_size]);
|
||||
self.local_event(
|
||||
operation,
|
||||
core,
|
||||
destination,
|
||||
output_size,
|
||||
provenance,
|
||||
&[provenance],
|
||||
);
|
||||
}
|
||||
|
||||
pub fn send_transfer(
|
||||
&mut self,
|
||||
sender: usize,
|
||||
receiver: usize,
|
||||
source: usize,
|
||||
destination: usize,
|
||||
size: usize,
|
||||
) {
|
||||
let tags = self.local_tags(sender, source, size);
|
||||
let provenance = Provenance::merge_all(tags.iter().copied());
|
||||
self.store_local_tags(receiver, destination, &tags);
|
||||
let mut event = json!({
|
||||
"event": "send_recv_transfer",
|
||||
"cycle": self.context.cycle,
|
||||
"pc": self.context.pc,
|
||||
"core_iteration": self.context.iteration,
|
||||
"sender_core": sender,
|
||||
"receiver_core": receiver,
|
||||
"source_address": source,
|
||||
"destination_address": destination,
|
||||
"size": size,
|
||||
});
|
||||
if let Some(object) = event.as_object_mut() {
|
||||
object.extend(provenance.json().as_object().unwrap().clone());
|
||||
}
|
||||
self.write(event);
|
||||
}
|
||||
|
||||
fn local_event(
|
||||
&self,
|
||||
operation: &'static str,
|
||||
core: usize,
|
||||
destination: usize,
|
||||
size: usize,
|
||||
provenance: Provenance,
|
||||
operands: &[Provenance],
|
||||
) {
|
||||
let mut event = json!({
|
||||
"event": "local_compute",
|
||||
"operation": operation,
|
||||
"cycle": self.context.cycle,
|
||||
"core": core,
|
||||
"pc": self.context.pc,
|
||||
"core_iteration": self.context.iteration,
|
||||
"destination_address": destination,
|
||||
"size": size,
|
||||
"operand_provenance": operands.iter().map(|tag| tag.json()).collect::<Vec<_>>(),
|
||||
});
|
||||
if let Some(object) = event.as_object_mut() {
|
||||
object.extend(provenance.json().as_object().unwrap().clone());
|
||||
}
|
||||
self.write(event);
|
||||
if provenance.is_mixed() {
|
||||
self.write(json!({
|
||||
"event": "cross_sample_data_mix",
|
||||
"cycle": self.context.cycle,
|
||||
"core": core,
|
||||
"pc": self.context.pc,
|
||||
"core_iteration": self.context.iteration,
|
||||
"operation": operation,
|
||||
"destination_address": destination,
|
||||
"size": size,
|
||||
"operand_provenance": operands.iter().map(|tag| tag.json()).collect::<Vec<_>>(),
|
||||
"provenance": provenance.samples(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,7 @@ where
|
||||
let data = inst.data;
|
||||
TRACER.lock().unwrap().pre_recv(cpu, data);
|
||||
}
|
||||
{
|
||||
let [sender_core, receiver_core] =
|
||||
cpu.get_multiple_cores([sender.internal_core, receiver.internal_core]);
|
||||
let memory = sender_core
|
||||
@@ -85,6 +86,14 @@ where
|
||||
})
|
||||
.unwrap();
|
||||
receiver_core.execute_store(receiver.address, memory[0]);
|
||||
}
|
||||
cpu.provenance_send_transfer(
|
||||
sender.internal_core,
|
||||
receiver.internal_core,
|
||||
sender.address,
|
||||
receiver.address,
|
||||
sender.size,
|
||||
);
|
||||
{
|
||||
let sender = &mut core_instructions[sender.internal_core];
|
||||
let pc = sender.program_counter;
|
||||
|
||||
@@ -297,7 +297,7 @@ fn multiple_send_recv_test() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_wait_tokens_test() {
|
||||
fn sync_wait_exact_count_resets_test() {
|
||||
let cpu = common::empty_cpu(2);
|
||||
let mut cores = CoreInstructionsBuilder::new(2);
|
||||
let mut instructions = InstructionsBuilder::new();
|
||||
@@ -313,14 +313,68 @@ fn sync_wait_tokens_test() {
|
||||
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());
|
||||
}
|
||||
instructions.make_inst(wait, data.set_offset_select_value(0, 2).build());
|
||||
cores.set_core(2, instructions.build());
|
||||
|
||||
Executable::new(cpu, cores.build()).execute().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_wait_rejects_overshoot() {
|
||||
let cpu = common::empty_cpu(3);
|
||||
let mut cores = CoreInstructionsBuilder::new(3);
|
||||
let mut instructions = InstructionsBuilder::new();
|
||||
let mut data = InstructionDataBuilder::new();
|
||||
|
||||
data.set_core_indx(1).fix_core_indx();
|
||||
instructions.make_inst(
|
||||
sync,
|
||||
data.set_imm_core(3).set_offset_select_value(0, 0).build(),
|
||||
);
|
||||
cores.set_core(1, instructions.build());
|
||||
|
||||
data.set_core_indx(2).fix_core_indx();
|
||||
instructions.make_inst(
|
||||
sync,
|
||||
data.set_imm_core(3).set_offset_select_value(0, 0).build(),
|
||||
);
|
||||
cores.set_core(2, instructions.build());
|
||||
|
||||
data.set_core_indx(3).fix_core_indx();
|
||||
instructions.make_inst(wait, data.set_offset_select_value(0, 1).build());
|
||||
cores.set_core(3, instructions.build());
|
||||
|
||||
let error = Executable::new(cpu, cores.build()).execute().unwrap_err();
|
||||
assert!(error.to_string().contains("overshot exact value"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_wait_deadlock_cycle_is_reported() {
|
||||
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();
|
||||
instructions.make_inst(wait, data.set_offset_select_value(0, 1).build());
|
||||
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();
|
||||
instructions.make_inst(wait, data.set_offset_select_value(0, 1).build());
|
||||
instructions.make_inst(
|
||||
sync,
|
||||
data.set_imm_core(1).set_offset_select_value(0, 0).build(),
|
||||
);
|
||||
cores.set_core(2, instructions.build());
|
||||
|
||||
let error = Executable::new(cpu, cores.build()).execute().unwrap_err();
|
||||
assert!(error.to_string().contains("wait event"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocked_transfers_do_not_starve_sync_producer() {
|
||||
let cpu = common::empty_cpu(4);
|
||||
|
||||
Submodule backend-simulators/pim/pimsim-nn updated: f7446dfc92...c7e061c99b
@@ -19,7 +19,7 @@ struct ResolvedContiguousAddress {
|
||||
};
|
||||
|
||||
/// Records compile-time facts used when interpreting address arithmetic and
|
||||
/// loop-carried aliases inside PIM regions.
|
||||
/// loop-carried aliases inside Pim regions.
|
||||
struct StaticValueKnowledge {
|
||||
llvm::DenseMap<mlir::Value, int64_t> indexValues;
|
||||
llvm::DenseMap<mlir::Value, mlir::Value> aliases;
|
||||
|
||||
@@ -85,12 +85,12 @@ static mlir::LogicalResult walkPimCoreBlockImpl(mlir::Block& block,
|
||||
auto step = resolveIndexValue(forOp.getStep(), knowledge);
|
||||
if (failed(lower) || failed(upper) || failed(step)
|
||||
|| (mode == CoreWalkMode::ExecuteCommunication && *step <= 0)) {
|
||||
forOp.emitOpError() << "requires statically evaluable scf.for bounds for PIM " << purpose;
|
||||
forOp.emitOpError() << "requires statically evaluable scf.for bounds for Pim " << purpose;
|
||||
hasFailure = true;
|
||||
continue;
|
||||
}
|
||||
if (*step <= 0) {
|
||||
forOp.emitOpError("requires positive scf.for step for PIM verification");
|
||||
forOp.emitOpError("requires positive scf.for step for Pim verification");
|
||||
hasFailure = true;
|
||||
continue;
|
||||
}
|
||||
@@ -126,7 +126,7 @@ static mlir::LogicalResult walkPimCoreBlockImpl(mlir::Block& block,
|
||||
if (auto ifOp = mlir::dyn_cast<mlir::scf::IfOp>(op)) {
|
||||
auto condition = resolveIndexValue(ifOp.getCondition(), knowledge);
|
||||
if (failed(condition)) {
|
||||
ifOp.emitOpError() << "requires statically evaluable scf.if condition for PIM " << purpose;
|
||||
ifOp.emitOpError() << "requires statically evaluable scf.if condition for Pim " << purpose;
|
||||
hasFailure = true;
|
||||
continue;
|
||||
}
|
||||
@@ -147,7 +147,7 @@ static mlir::LogicalResult walkPimCoreBlockImpl(mlir::Block& block,
|
||||
if (auto switchOp = mlir::dyn_cast<mlir::scf::IndexSwitchOp>(op)) {
|
||||
auto selector = resolveIndexValue(switchOp.getArg(), knowledge);
|
||||
if (failed(selector)) {
|
||||
switchOp.emitOpError() << "requires a statically evaluable scf.index_switch selector for PIM " << purpose;
|
||||
switchOp.emitOpError() << "requires a statically evaluable scf.index_switch selector for Pim " << purpose;
|
||||
hasFailure = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace onnx_mlir {
|
||||
using PimCoreCommunicationPlan = llvm::DenseMap<mlir::Block*, llvm::SmallVector<mlir::Operation*, 8>>;
|
||||
|
||||
/// Returns true for ops in a `pim.core` body that only participate in static
|
||||
/// address or index computation and therefore do not emit PIM instructions.
|
||||
/// address or index computation and therefore do not emit Pim instructions.
|
||||
bool isCoreStaticAddressOp(mlir::Operation* op);
|
||||
|
||||
/// Walks a `pim.core` body's communication stream, statically unrolling
|
||||
|
||||
@@ -9,7 +9,7 @@ llvm::FailureOr<mlir::func::FuncOp> getPimEntryFunc(mlir::ModuleOp moduleOp) {
|
||||
|
||||
llvm::SmallVector<mlir::ONNXEntryPointOp> entryPoints(moduleOp.getOps<mlir::ONNXEntryPointOp>());
|
||||
if (entryPoints.size() > 1) {
|
||||
moduleOp.emitError("PIM pipeline requires a single ONNX entry point, but found ") << entryPoints.size();
|
||||
moduleOp.emitError("Pim pipeline requires a single ONNX entry point, but found ") << entryPoints.size();
|
||||
return mlir::failure();
|
||||
}
|
||||
if (!entryPoints.empty()) {
|
||||
@@ -38,7 +38,7 @@ llvm::FailureOr<mlir::func::FuncOp> getPimEntryFunc(mlir::ModuleOp moduleOp) {
|
||||
if (nonExternalFuncs.size() == 1)
|
||||
return nonExternalFuncs.front();
|
||||
|
||||
moduleOp.emitError("could not resolve a unique PIM entry function");
|
||||
moduleOp.emitError("could not resolve a unique Pim entry function");
|
||||
return mlir::failure();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
/// Resolves the function the PIM pipeline should treat as its entry point.
|
||||
/// Resolves the function the Pim pipeline should treat as its entry point.
|
||||
/// Prefers ONNX entry-point metadata, then `main_graph`, then the only
|
||||
/// non-external function if the module is otherwise unambiguous.
|
||||
llvm::FailureOr<mlir::func::FuncOp> getPimEntryFunc(mlir::ModuleOp moduleOp);
|
||||
|
||||
@@ -32,7 +32,7 @@ struct ResolvedWeightView {
|
||||
bool hasWeightAlways(mlir::Operation* op);
|
||||
|
||||
/// Tags an op as producing a value that should stay materialized as a reusable
|
||||
/// weight across later PIM lowering/codegen stages.
|
||||
/// weight across later Pim lowering/codegen stages.
|
||||
void markWeightAlways(mlir::Operation* op);
|
||||
|
||||
bool isSpatialMvmVmmWeightUse(mlir::OpOperand& use);
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace onnx_mlir::pim {
|
||||
namespace {
|
||||
|
||||
static void emitCrashMessage(llvm::StringRef fieldName, llvm::StringRef message) {
|
||||
llvm::errs() << "PIM " << fieldName << " " << message << "\n";
|
||||
llvm::errs() << "Pim " << fieldName << " " << message << "\n";
|
||||
}
|
||||
|
||||
template <typename To, typename From>
|
||||
@@ -65,7 +65,7 @@ InFlightDiagnostic emitCheckedArithmeticError(Operation* anchor, llvm::StringRef
|
||||
}
|
||||
|
||||
InFlightDiagnostic emitCheckedArithmeticError(Location loc, llvm::StringRef fieldName, llvm::StringRef message) {
|
||||
return emitError(loc) << "PIM " << fieldName << " " << message;
|
||||
return emitError(loc) << "Pim " << fieldName << " " << message;
|
||||
}
|
||||
|
||||
FailureOr<int32_t> checkedI32(int64_t value, Operation* anchor, llvm::StringRef fieldName) {
|
||||
@@ -174,7 +174,7 @@ FailureOr<uint64_t> getCheckedShapedTypeSizeInBytes(ShapedType type, Location lo
|
||||
int32_t checkedI32OrCrash(int64_t value, llvm::StringRef fieldName) {
|
||||
if (value < std::numeric_limits<int32_t>::min() || value > std::numeric_limits<int32_t>::max()) {
|
||||
emitCrashMessage(fieldName, "is outside representable range");
|
||||
llvm_unreachable("PIM checked arithmetic failure");
|
||||
llvm_unreachable("Pim checked arithmetic failure");
|
||||
}
|
||||
return static_cast<int32_t>(value);
|
||||
}
|
||||
@@ -182,7 +182,7 @@ int32_t checkedI32OrCrash(int64_t value, llvm::StringRef fieldName) {
|
||||
int32_t checkedI32OrCrash(uint64_t value, llvm::StringRef fieldName) {
|
||||
if (value > static_cast<uint64_t>(std::numeric_limits<int32_t>::max())) {
|
||||
emitCrashMessage(fieldName, "is outside representable range");
|
||||
llvm_unreachable("PIM checked arithmetic failure");
|
||||
llvm_unreachable("Pim checked arithmetic failure");
|
||||
}
|
||||
return static_cast<int32_t>(value);
|
||||
}
|
||||
@@ -190,7 +190,7 @@ int32_t checkedI32OrCrash(uint64_t value, llvm::StringRef fieldName) {
|
||||
uint8_t checkedU8OrCrash(uint64_t value, llvm::StringRef fieldName) {
|
||||
if (value > static_cast<uint64_t>(std::numeric_limits<uint8_t>::max())) {
|
||||
emitCrashMessage(fieldName, "is outside representable range");
|
||||
llvm_unreachable("PIM checked arithmetic failure");
|
||||
llvm_unreachable("Pim checked arithmetic failure");
|
||||
}
|
||||
return static_cast<uint8_t>(value);
|
||||
}
|
||||
@@ -198,7 +198,7 @@ uint8_t checkedU8OrCrash(uint64_t value, llvm::StringRef fieldName) {
|
||||
size_t checkedSizeOrCrash(int64_t value, llvm::StringRef fieldName) {
|
||||
if (value < 0) {
|
||||
emitCrashMessage(fieldName, "is outside representable range");
|
||||
llvm_unreachable("PIM checked arithmetic failure");
|
||||
llvm_unreachable("Pim checked arithmetic failure");
|
||||
}
|
||||
return static_cast<size_t>(value);
|
||||
}
|
||||
@@ -206,7 +206,7 @@ size_t checkedSizeOrCrash(int64_t value, llvm::StringRef fieldName) {
|
||||
size_t checkedAddOrCrash(size_t lhs, size_t rhs, llvm::StringRef fieldName) {
|
||||
if (rhs > std::numeric_limits<size_t>::max() - lhs) {
|
||||
emitCrashMessage(fieldName, "addition overflow");
|
||||
llvm_unreachable("PIM checked arithmetic failure");
|
||||
llvm_unreachable("Pim checked arithmetic failure");
|
||||
}
|
||||
return lhs + rhs;
|
||||
}
|
||||
@@ -214,7 +214,7 @@ size_t checkedAddOrCrash(size_t lhs, size_t rhs, llvm::StringRef fieldName) {
|
||||
size_t checkedMulOrCrash(size_t lhs, size_t rhs, llvm::StringRef fieldName) {
|
||||
if (lhs != 0 && rhs > std::numeric_limits<size_t>::max() / lhs) {
|
||||
emitCrashMessage(fieldName, "multiplication overflow");
|
||||
llvm_unreachable("PIM checked arithmetic failure");
|
||||
llvm_unreachable("Pim checked arithmetic failure");
|
||||
}
|
||||
return lhs * rhs;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
/// Returns the directory that should hold PIM artifacts/debug dumps for the
|
||||
/// Returns the directory that should hold Pim artifacts/debug dumps for the
|
||||
/// current compiler invocation.
|
||||
std::string getOutputDir();
|
||||
|
||||
|
||||
@@ -171,19 +171,19 @@ inline Opcode opcodeFromString(llvm::StringRef opName) {
|
||||
for (auto [index, name] : llvm::enumerate(kOpcodeNames))
|
||||
if (opName == name)
|
||||
return static_cast<Opcode>(index);
|
||||
llvm_unreachable("Unsupported PIM binary opcode");
|
||||
llvm_unreachable("Unsupported Pim binary opcode");
|
||||
}
|
||||
|
||||
inline llvm::StringRef opcodeToString(Opcode opcode) {
|
||||
size_t index = static_cast<size_t>(opcode);
|
||||
assert(index < kOpcodeNames.size() && "Unsupported PIM binary opcode");
|
||||
assert(index < kOpcodeNames.size() && "Unsupported Pim binary opcode");
|
||||
return kOpcodeNames[index];
|
||||
}
|
||||
|
||||
inline InstructionRecord makeInstructionRecord(const llvm::json::Object& instruction) {
|
||||
InstructionRecord record;
|
||||
std::optional<llvm::StringRef> opName = instruction.getString("op");
|
||||
assert(opName && "Missing op field in PIM instruction");
|
||||
assert(opName && "Missing op field in Pim instruction");
|
||||
record.opcode = opcodeFromString(*opName);
|
||||
const auto& format = kInstructionJsonFormats[static_cast<size_t>(record.opcode)];
|
||||
if (format.rd)
|
||||
|
||||
@@ -125,7 +125,7 @@ static bool isZeroSplatGlobal(mlir::Value value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// PIM instruction immediates are serialized as signed int32_t fields today
|
||||
// Pim instruction immediates are serialized as signed int32_t fields today
|
||||
// (`sldi` goes through checkedI32OrCrash), so local addresses must stay within
|
||||
// the non-negative int32_t range.
|
||||
static FailureOr<size_t> checkedAlignTo(size_t value, size_t alignment, Operation* anchor, StringRef fieldName) {
|
||||
@@ -141,7 +141,7 @@ static void printMemoryOverflowDiagnostic(const MemoryValueKey& key,
|
||||
size_t requestedSize,
|
||||
size_t currentFirstAvailableAddress,
|
||||
size_t alignedEndAddress) {
|
||||
llvm::errs() << "PIM local memory allocation overflow\n";
|
||||
llvm::errs() << "Pim local memory allocation overflow\n";
|
||||
llvm::errs() << "Requested allocation size: " << requestedSize << " bytes\n";
|
||||
llvm::errs() << "Current firstAvailableAddress: " << currentFirstAvailableAddress << "\n";
|
||||
llvm::errs() << "Aligned end address: " << alignedEndAddress << "\n";
|
||||
@@ -187,7 +187,7 @@ size_t PimMemory::allocateAddress(size_t size, const MemoryValueKey& key) {
|
||||
size,
|
||||
firstAvailableAddress,
|
||||
succeeded(checkedAlignedEnd) ? *checkedAlignedEnd : kPimLocalMemoryAddressLimit);
|
||||
llvm_unreachable("PIM local memory allocation overflow");
|
||||
llvm_unreachable("Pim local memory allocation overflow");
|
||||
}
|
||||
firstAvailableAddress = *checkedAlignedEnd;
|
||||
return address;
|
||||
@@ -276,7 +276,7 @@ void PimMemory::allocateCore(const CompiledCoreMemoryPlan& plan, std::optional<u
|
||||
}
|
||||
else if (*localArenaSize != plan.arenaSize || reportRow.logicalLocalAllocationCount != plan.logicalAllocationCount
|
||||
|| reportRow.logicalLocalBytes != plan.logicalBytes)
|
||||
llvm_unreachable("inconsistent PIM local-memory plan across core-batch lanes");
|
||||
llvm_unreachable("inconsistent Pim local-memory plan across core-batch lanes");
|
||||
for (const CompiledLocalMemoryEntry& entry : plan.entries) {
|
||||
MemoryValueKey key = getMemoryValueKey(entry.value, lane);
|
||||
ownedMemEntriesMap[key] = entry.memory;
|
||||
@@ -352,8 +352,8 @@ size_t PimAcceleratorMemory::getValueAddress(mlir::Value value,
|
||||
llvm_unreachable("Missing mem entry");
|
||||
}
|
||||
|
||||
size_t byteOffset = pim::checkedSizeOrCrash(resolvedAddress->byteOffset, "resolved PIM byte offset");
|
||||
return pim::checkedAddOrCrash(iter->second.address, byteOffset, "resolved PIM address");
|
||||
size_t byteOffset = pim::checkedSizeOrCrash(resolvedAddress->byteOffset, "resolved Pim byte offset");
|
||||
return pim::checkedAddOrCrash(iter->second.address, byteOffset, "resolved Pim address");
|
||||
}
|
||||
|
||||
llvm::FailureOr<int64_t> PimAcceleratorMemory::getIndexValue(mlir::Value value,
|
||||
@@ -544,11 +544,23 @@ void PimCodeGen::setupRdRs1(size_t rdAddress, size_t rdOffset, size_t rs1Address
|
||||
genSetRegisterImmediateUnsigned(1, pim::checkedAddOrCrash(rs1Address, rs1Offset, "rs1 address"));
|
||||
}
|
||||
|
||||
void PimCodeGen::setupRdRs1Rs2(
|
||||
std::array<uint8_t, 3> PimCodeGen::setupRdRs1Rs2(
|
||||
size_t rdAddress, size_t rdOffset, size_t rs1Address, size_t rs1Offset, size_t rs2Address, size_t rs2Offset) const {
|
||||
genSetRegisterImmediateUnsigned(0, pim::checkedAddOrCrash(rdAddress, rdOffset, "rd address"));
|
||||
genSetRegisterImmediateUnsigned(1, pim::checkedAddOrCrash(rs1Address, rs1Offset, "rs1 address"));
|
||||
genSetRegisterImmediateUnsigned(2, pim::checkedAddOrCrash(rs2Address, rs2Offset, "rs2 address"));
|
||||
size_t rd = pim::checkedAddOrCrash(rdAddress, rdOffset, "rd address");
|
||||
size_t rs1 = pim::checkedAddOrCrash(rs1Address, rs1Offset, "rs1 address");
|
||||
size_t rs2 = pim::checkedAddOrCrash(rs2Address, rs2Offset, "rs2 address");
|
||||
genSetRegisterImmediateUnsigned(0, rd);
|
||||
uint8_t rs1Register = 0;
|
||||
if (rd != rs1) {
|
||||
genSetRegisterImmediateUnsigned(1, rs1);
|
||||
rs1Register = 1;
|
||||
}
|
||||
if (rd == rs2)
|
||||
return {0, rs1Register, 0};
|
||||
if (rs1 == rs2)
|
||||
return {0, rs1Register, rs1Register};
|
||||
genSetRegisterImmediateUnsigned(2, rs2);
|
||||
return {0, rs1Register, 2};
|
||||
}
|
||||
|
||||
void PimCodeGen::emitMemCopyOp(pim_binary::Opcode opcode,
|
||||
@@ -664,13 +676,13 @@ void PimCodeGen::codeGenVMVOp(pim::PimVMVOp vmvOp, const StaticValueKnowledge& k
|
||||
auto sourceType = cast<ShapedType>(vmvOp.getSource().getType());
|
||||
int32_t bitwidth = getVectorElementBitwidthOrCrash(sourceType);
|
||||
ensureVectorBitwidth(bitwidth, bitwidth);
|
||||
setupRdRs1Rs2(addressOf(vmvOp.getTarget(), knowledge), *targetOffset,
|
||||
auto registers = setupRdRs1Rs2(addressOf(vmvOp.getTarget(), knowledge), *targetOffset,
|
||||
addressOf(vmvOp.getSource(), knowledge), *sourceOffset, 0, *sourceStride);
|
||||
pim_binary::InstructionRecord instruction;
|
||||
instruction.opcode = pim_binary::Opcode::vmv;
|
||||
instruction.rd = 0;
|
||||
instruction.r1 = 1;
|
||||
instruction.r2OrImm = 2;
|
||||
instruction.rd = registers[0];
|
||||
instruction.r1 = registers[1];
|
||||
instruction.r2OrImm = registers[2];
|
||||
instruction.generic3 = vmvOp.getLength();
|
||||
emitInstruction(instruction);
|
||||
}
|
||||
@@ -694,6 +706,8 @@ void PimCodeGen::codeGenSendOp(pim::PimSendOp sendOp, const StaticValueKnowledge
|
||||
|
||||
void PimCodeGen::codeGenWaitOp(
|
||||
pim::PimWaitOp waitOp, const StaticValueKnowledge& knowledge) const {
|
||||
if (pimDisableSynchronization)
|
||||
return;
|
||||
auto eventRegister = indexOf(waitOp.getEventRegister(), knowledge);
|
||||
auto waitValue = indexOf(waitOp.getWaitValue(), knowledge);
|
||||
assert(succeeded(eventRegister) && succeeded(waitValue)
|
||||
@@ -710,6 +724,8 @@ void PimCodeGen::codeGenWaitOp(
|
||||
|
||||
void PimCodeGen::codeGenSyncOp(
|
||||
pim::PimSyncOp syncOp, const StaticValueKnowledge& knowledge) const {
|
||||
if (pimDisableSynchronization)
|
||||
return;
|
||||
auto targetCoreId = indexOf(syncOp.getTargetCoreId(), knowledge);
|
||||
auto eventRegister = indexOf(syncOp.getEventRegister(), knowledge);
|
||||
assert(succeeded(targetCoreId) && succeeded(eventRegister)
|
||||
@@ -780,12 +796,13 @@ void PimCodeGen::emitBinaryVectorOp(pim_binary::Opcode opcode,
|
||||
auto inputType = cast<ShapedType>(lhs.getType());
|
||||
ensureVectorBitwidth(getVectorElementBitwidthOrCrash(inputType),
|
||||
getVectorElementBitwidthOrCrash(cast<ShapedType>(output.getType())));
|
||||
setupRdRs1Rs2(addressOf(output, knowledge), 0, addressOf(lhs, knowledge), 0, addressOf(rhs, knowledge), 0);
|
||||
auto registers = setupRdRs1Rs2(
|
||||
addressOf(output, knowledge), 0, addressOf(lhs, knowledge), 0, addressOf(rhs, knowledge), 0);
|
||||
pim_binary::InstructionRecord instruction;
|
||||
instruction.opcode = opcode;
|
||||
instruction.rd = 0;
|
||||
instruction.r1 = 1;
|
||||
instruction.r2OrImm = 2;
|
||||
instruction.rd = registers[0];
|
||||
instruction.r1 = registers[1];
|
||||
instruction.r2OrImm = registers[2];
|
||||
instruction.generic3 = getVectorElementCountOrCrash(inputType);
|
||||
emitInstruction(instruction);
|
||||
}
|
||||
@@ -945,7 +962,7 @@ static LogicalResult executeCompiledCorePlan(
|
||||
auto step = node.step.evaluate(knowledge);
|
||||
auto forOp = cast<mlir::scf::ForOp>(node.op);
|
||||
if (failed(lowerBound) || failed(upperBound) || failed(step) || *step <= 0) {
|
||||
forOp.emitOpError("requires statically evaluable scf.for bounds for PIM codegen");
|
||||
forOp.emitOpError("requires statically evaluable scf.for bounds for Pim codegen");
|
||||
return failure();
|
||||
}
|
||||
|
||||
@@ -971,7 +988,7 @@ static LogicalResult executeCompiledCorePlan(
|
||||
auto condition = node.condition.evaluate(knowledge);
|
||||
auto ifOp = cast<mlir::scf::IfOp>(node.op);
|
||||
if (failed(condition)) {
|
||||
ifOp.emitOpError("requires statically evaluable scf.if condition for PIM codegen");
|
||||
ifOp.emitOpError("requires statically evaluable scf.if condition for Pim codegen");
|
||||
return failure();
|
||||
}
|
||||
|
||||
@@ -985,7 +1002,7 @@ static LogicalResult executeCompiledCorePlan(
|
||||
auto selector = node.condition.evaluate(knowledge);
|
||||
auto switchOp = cast<mlir::scf::IndexSwitchOp>(node.op);
|
||||
if (failed(selector)) {
|
||||
switchOp.emitOpError("requires a statically evaluable scf.index_switch selector for PIM codegen");
|
||||
switchOp.emitOpError("requires a statically evaluable scf.index_switch selector for Pim codegen");
|
||||
return failure();
|
||||
}
|
||||
const llvm::SmallVectorImpl<CompiledCoreNode>* selectedBody = node.defaultBody.get();
|
||||
@@ -1171,12 +1188,12 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
||||
}
|
||||
auto getCompiledProgram = [&](Operation* op) {
|
||||
auto it = compiledPrograms.find(op);
|
||||
assert(it != compiledPrograms.end() && "missing compiled PIM core program");
|
||||
assert(it != compiledPrograms.end() && "missing compiled Pim core program");
|
||||
return it->second.get();
|
||||
};
|
||||
auto getMemoryPlan = [&](Operation* op) {
|
||||
auto it = memoryPlans.find(op);
|
||||
assert(it != memoryPlans.end() && "missing PIM core memory plan");
|
||||
assert(it != memoryPlans.end() && "missing Pim core memory plan");
|
||||
return it->second.get();
|
||||
};
|
||||
|
||||
@@ -1254,7 +1271,7 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
||||
if (failed(weightView)) {
|
||||
std::string message;
|
||||
llvm::raw_string_ostream os(message);
|
||||
os << "requires a statically resolvable dense global weight view during PIM codegen; weight="
|
||||
os << "requires a statically resolvable dense global weight view during Pim codegen; weight="
|
||||
<< vmmOp.getWeight() << " type=" << vmmOp.getWeight().getType();
|
||||
result.recordDiagnostic(vmmOp, os.str());
|
||||
return failure();
|
||||
@@ -1262,7 +1279,7 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
||||
if (weightView->shape.size() != 2) {
|
||||
std::string message;
|
||||
llvm::raw_string_ostream os(message);
|
||||
os << "requires a rank-2 matrix weight view during PIM codegen; resolved shape=[";
|
||||
os << "requires a rank-2 matrix weight view during Pim codegen; resolved shape=[";
|
||||
llvm::interleaveComma(weightView->shape, os);
|
||||
os << "] weight=" << vmmOp.getWeight() << " type=" << vmmOp.getWeight().getType();
|
||||
result.recordDiagnostic(vmmOp, os.str());
|
||||
@@ -1374,7 +1391,7 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
||||
}
|
||||
if (diagnostics.hasFailure())
|
||||
diagnostics.emitSuppressedSummary(summaryAnchor ? summaryAnchor : moduleOp.getOperation(),
|
||||
"PIM codegen diagnostic(s)");
|
||||
"Pim codegen diagnostic(s)");
|
||||
|
||||
for (size_t jobIndex = 0; jobIndex < jobs.size(); ++jobIndex)
|
||||
if (jobResults[jobIndex].status != CompilerSuccess)
|
||||
@@ -1440,7 +1457,7 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
||||
if (!batchPerCoreRow)
|
||||
batchPerCoreRow = result.reportRow;
|
||||
else if (!(*batchPerCoreRow == result.reportRow))
|
||||
llvm_unreachable("one PIM core batch produced inconsistent per-core memory reports");
|
||||
llvm_unreachable("one Pim core batch produced inconsistent per-core memory reports");
|
||||
}
|
||||
|
||||
uint64_t batchReportId = jobs[group.front()].batchReportId.value_or(0);
|
||||
|
||||
@@ -176,7 +176,7 @@ class PimCodeGen {
|
||||
void genSetRegisterImmediateUnsigned(size_t registerNumber, size_t immediate) const;
|
||||
void setupRd(size_t rdAddress, size_t rdOffset) const;
|
||||
void setupRdRs1(size_t rdAddress, size_t rdOffset, size_t rs1Address, size_t rs1Offset) const;
|
||||
void setupRdRs1Rs2(
|
||||
std::array<uint8_t, 3> setupRdRs1Rs2(
|
||||
size_t rdAddress, size_t rdOffset, size_t rs1Address, size_t rs1Offset, size_t rs2Address, size_t rs2Offset) const;
|
||||
|
||||
void emitMemCopyOp(pim_binary::Opcode opcode,
|
||||
|
||||
@@ -9,25 +9,25 @@
|
||||
namespace onnx_mlir {
|
||||
|
||||
llvm::cl::opt<PimEmissionTargetType> pimEmissionTarget(
|
||||
llvm::cl::desc("[Optional] Choose PIM-related target to emit (once selected it will cancel the other targets):"),
|
||||
llvm::cl::values(clEnumVal(EmitSpatial, "Lower model to spatial IR")),
|
||||
llvm::cl::values(clEnumVal(EmitPim, "Lower model to PIM IR")),
|
||||
llvm::cl::values(clEnumVal(EmitPimBufferized, "Lower model to PIM IR and bufferize it")),
|
||||
llvm::cl::values(clEnumVal(EmitPimCodegen, "Lower model to PIM IR and generate code for PIM")),
|
||||
llvm::cl::desc("[Optional] Choose Pim-related target to emit (once selected it will cancel the other targets):"),
|
||||
llvm::cl::values(clEnumVal(EmitSpatial, "Lower model to Spatial IR")),
|
||||
llvm::cl::values(clEnumVal(EmitPim, "Lower model to Pim IR")),
|
||||
llvm::cl::values(clEnumVal(EmitPimBufferized, "Lower model to Pim IR and bufferize it")),
|
||||
llvm::cl::values(clEnumVal(EmitPimCodegen, "Lower model to Pim IR and generate code for Pim")),
|
||||
llvm::cl::init(EmitPimCodegen),
|
||||
llvm::cl::cat(OnnxMlirOptions));
|
||||
|
||||
llvm::cl::opt<PimMemoryReportLevel> pimMemoryReport(
|
||||
"pim-memory-report",
|
||||
llvm::cl::desc("Emit a human-readable PIM memory planning report"),
|
||||
llvm::cl::values(clEnumValN(PimMemoryReportNone, "none", "Do not emit any PIM memory planning report")),
|
||||
llvm::cl::values(clEnumValN(PimMemoryReportSummary, "summary", "Emit a concise PIM memory summary")),
|
||||
llvm::cl::desc("Emit a human-readable Pim memory planning report"),
|
||||
llvm::cl::values(clEnumValN(PimMemoryReportNone, "none", "Do not emit any Pim memory planning report")),
|
||||
llvm::cl::values(clEnumValN(PimMemoryReportSummary, "summary", "Emit a concise Pim memory summary")),
|
||||
llvm::cl::init(PimMemoryReportSummary),
|
||||
llvm::cl::cat(OnnxMlirOptions));
|
||||
|
||||
llvm::cl::opt<PimConvLoweringType> pimConvLowering(
|
||||
"pim-conv-lowering",
|
||||
llvm::cl::desc("Convolution lowering strategy for PIM"),
|
||||
llvm::cl::desc("Convolution lowering strategy for Pim"),
|
||||
llvm::cl::values(clEnumValN(PimConvLoweringAuto, "auto", "Select the Conv lowering strategy automatically")),
|
||||
llvm::cl::values(clEnumValN(PimConvLoweringLegacy, "legacy", "Use the legacy explicit-im2col Conv lowering")),
|
||||
llvm::cl::values(clEnumValN(PimConvLoweringDepthwise, "depthwise", "Force the depthwise-specialized Conv lowering")),
|
||||
@@ -55,20 +55,20 @@ llvm::cl::opt<PimSpatialDataflowExportType> pimExportSpatialDataflow(
|
||||
llvm::cl::desc("Emit Gephi-importable CSV dataflow reports for Spatial pipeline snapshots"),
|
||||
llvm::cl::values(clEnumValN(SpatialDataflowExportNone, "none", "Do not emit Spatial dataflow CSV reports")),
|
||||
llvm::cl::values(
|
||||
clEnumValN(SpatialDataflowExportSpatial1, "spatial1", "Emit spatial1 graph dataflow CSV reports")),
|
||||
clEnumValN(SpatialDataflowExportSpatial1, "spatial1", "Emit Spatial1 graph dataflow CSV reports")),
|
||||
llvm::cl::values(
|
||||
clEnumValN(SpatialDataflowExportSpatial2, "spatial2", "Emit spatial2 trivially merged graph dataflow CSV reports")),
|
||||
clEnumValN(SpatialDataflowExportSpatial2, "spatial2", "Emit Spatial2 trivially merged graph dataflow CSV reports")),
|
||||
llvm::cl::values(
|
||||
clEnumValN(SpatialDataflowExportSpatial3, "spatial3", "Emit spatial3 scheduled dataflow CSV reports")),
|
||||
clEnumValN(SpatialDataflowExportSpatial3, "spatial3", "Emit Spatial3 scheduled dataflow CSV reports")),
|
||||
llvm::cl::values(
|
||||
clEnumValN(SpatialDataflowExportSpatial4, "spatial4", "Emit spatial4 realized dataflow CSV reports")),
|
||||
clEnumValN(SpatialDataflowExportSpatial4, "spatial4", "Emit Spatial4 realized dataflow CSV reports")),
|
||||
llvm::cl::values(clEnumValN(SpatialDataflowExportAll, "all", "Emit all Spatial dataflow CSV reports")),
|
||||
llvm::cl::init(SpatialDataflowExportNone),
|
||||
llvm::cl::cat(OnnxMlirOptions));
|
||||
|
||||
llvm::cl::opt<bool>
|
||||
pimOnlyCodegen("pim-only-codegen",
|
||||
llvm::cl::desc("Only generate code for PIM (assume input is already in bufferized PIM IR)"),
|
||||
llvm::cl::desc("Only generate code for Pim (assume input is already in bufferized Pim IR)"),
|
||||
llvm::cl::init(false),
|
||||
llvm::cl::cat(OnnxMlirOptions));
|
||||
|
||||
@@ -96,21 +96,37 @@ llvm::cl::opt<bool> pimEmitJson("pim-emit-json",
|
||||
|
||||
llvm::cl::opt<bool> pimDetectCommunicationDeadlock(
|
||||
"pim-detect-communication-deadlock",
|
||||
llvm::cl::desc("Expensively simulate the statically expanded PIM send/receive order at verification time and fail if a blocking communication deadlock is found"),
|
||||
llvm::cl::desc("Expensively simulate statically expanded Pim SEND/RECV and exact-count SYNC/WAIT order at verification time and fail on a blocking deadlock"),
|
||||
llvm::cl::init(false),
|
||||
llvm::cl::cat(OnnxMlirOptions));
|
||||
|
||||
llvm::cl::opt<bool> pimVerifyBufferizationCopyFreedom(
|
||||
"pim-verify-bufferization-copy-freedom",
|
||||
llvm::cl::desc("Run the expensive official PIM tensor-copy freedom proof before bufferization"),
|
||||
llvm::cl::desc("Run the expensive official Pim tensor-copy freedom proof before bufferization"),
|
||||
llvm::cl::init(false),
|
||||
llvm::cl::cat(OnnxMlirOptions));
|
||||
|
||||
llvm::cl::opt<bool> pimDisableSynchronization(
|
||||
"pim-disable-synchronization",
|
||||
llvm::cl::desc("Omit Pim wait/sync instructions from generated code for performance ablation"),
|
||||
llvm::cl::init(false),
|
||||
llvm::cl::cat(OnnxMlirOptions));
|
||||
|
||||
llvm::cl::opt<bool> pimDisableSpatialPlanning(
|
||||
"pim-disable-spatial-planning",
|
||||
llvm::cl::desc("Select the trivial Spatial layout plan for performance ablation"),
|
||||
llvm::cl::init(false),
|
||||
llvm::cl::cat(OnnxMlirOptions));
|
||||
|
||||
llvm::cl::opt<size_t>
|
||||
crossbarSize("crossbar-size", llvm::cl::desc("Width and height of a single crossbar"), llvm::cl::init(128));
|
||||
crossbarSize("crossbar-size",
|
||||
llvm::cl::desc("Width and height of a single crossbar (required for Pim compilation)"),
|
||||
llvm::cl::init(0));
|
||||
|
||||
llvm::cl::opt<size_t>
|
||||
crossbarCountInCore("crossbar-count", llvm::cl::desc("Number of crossbars in each core"), llvm::cl::init(64));
|
||||
crossbarCountInCore("crossbar-count",
|
||||
llvm::cl::desc("Number of crossbars in each core (required for Pim compilation)"),
|
||||
llvm::cl::init(0));
|
||||
|
||||
llvm::cl::opt<size_t> pipelineStages(
|
||||
"pipeline",
|
||||
@@ -119,32 +135,35 @@ llvm::cl::opt<size_t> pipelineStages(
|
||||
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::desc("Number of cores in the chip. Required for Pim compilation."),
|
||||
llvm::cl::init(-1));
|
||||
|
||||
llvm::cl::opt<std::string> pimTargetConfig(
|
||||
"pim-target-config",
|
||||
llvm::cl::desc("PIM target configuration used to construct the Spatial scheduling cost model"),
|
||||
llvm::cl::desc("Pim target configuration used to construct the Spatial scheduling cost model"),
|
||||
llvm::cl::init(""),
|
||||
llvm::cl::cat(OnnxMlirOptions));
|
||||
|
||||
bool hasExplicitPimCoreCount() { return coresCount.getNumOccurrences() != 0; }
|
||||
|
||||
void verifyExplicitPimCoreCount() {
|
||||
if (!hasExplicitPimCoreCount())
|
||||
llvm::report_fatal_error("PIM compilation requires an explicit --core-count=<positive integer>");
|
||||
void verifyPimCompilerOptions() {
|
||||
if (coresCount.getNumOccurrences() == 0)
|
||||
llvm::report_fatal_error("Pim compilation requires an explicit --core-count=<positive integer>");
|
||||
if (coresCount.getValue() <= 0)
|
||||
llvm::report_fatal_error("PIM compilation requires --core-count to be a positive integer");
|
||||
}
|
||||
|
||||
void verifyPimPipelineStages() {
|
||||
llvm::report_fatal_error("Pim compilation requires --core-count to be a positive integer");
|
||||
if (crossbarSize.getNumOccurrences() == 0)
|
||||
llvm::report_fatal_error("Pim compilation requires an explicit --crossbar-size=<positive integer>");
|
||||
if (crossbarSize.getValue() == 0)
|
||||
llvm::report_fatal_error("Pim compilation requires --crossbar-size to be a positive integer");
|
||||
if (crossbarCountInCore.getNumOccurrences() == 0)
|
||||
llvm::report_fatal_error("Pim compilation requires an explicit --crossbar-count=<positive integer>");
|
||||
if (crossbarCountInCore.getValue() == 0)
|
||||
llvm::report_fatal_error("Pim compilation requires --crossbar-count to be a positive integer");
|
||||
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");
|
||||
llvm::report_fatal_error("Pim compilation requires --pipeline to be positive");
|
||||
if (static_cast<size_t>(coresCount.getValue()) < pipelineStages.getValue())
|
||||
llvm::report_fatal_error("Pim compilation requires --pipeline not to exceed --core-count");
|
||||
if (crossbarCountInCore.getValue()
|
||||
> std::numeric_limits<size_t>::max() / pipelineStages.getValue())
|
||||
llvm::report_fatal_error("PIM compilation --crossbar-count * --pipeline overflows");
|
||||
llvm::report_fatal_error("Pim compilation --crossbar-count * --pipeline overflows");
|
||||
}
|
||||
|
||||
} // namespace onnx_mlir
|
||||
|
||||
@@ -59,6 +59,8 @@ extern llvm::cl::opt<bool> pimEmitJson;
|
||||
extern llvm::cl::opt<bool> pimReportConvLowering;
|
||||
extern llvm::cl::opt<bool> pimDetectCommunicationDeadlock;
|
||||
extern llvm::cl::opt<bool> pimVerifyBufferizationCopyFreedom;
|
||||
extern llvm::cl::opt<bool> pimDisableSynchronization;
|
||||
extern llvm::cl::opt<bool> pimDisableSpatialPlanning;
|
||||
|
||||
extern llvm::cl::opt<size_t> crossbarSize;
|
||||
extern llvm::cl::opt<size_t> crossbarCountInCore;
|
||||
@@ -68,8 +70,6 @@ extern llvm::cl::opt<std::string> pimTargetConfig;
|
||||
extern llvm::cl::opt<uint64_t> pimConvIm2colMaxElements;
|
||||
extern llvm::cl::opt<uint64_t> pimConvStreamChunkPositions;
|
||||
|
||||
bool hasExplicitPimCoreCount();
|
||||
void verifyExplicitPimCoreCount();
|
||||
void verifyPimPipelineStages();
|
||||
void verifyPimCompilerOptions();
|
||||
|
||||
} // namespace onnx_mlir
|
||||
|
||||
@@ -97,7 +97,7 @@ spatial::ConvLoweringStrategy getSpatialConvLoweringStrategy(PimConvLoweringType
|
||||
case PimConvLoweringInputKTiled: return spatial::ConvLoweringStrategy::InputKTiled;
|
||||
case PimConvLoweringTiled2D: return spatial::ConvLoweringStrategy::Tiled2D;
|
||||
}
|
||||
llvm_unreachable("unknown PIM Conv lowering strategy");
|
||||
llvm_unreachable("unknown Pim Conv lowering strategy");
|
||||
}
|
||||
|
||||
spatial::SpatialDataflowExportStage getPimSpatialDataflowExportStage(
|
||||
@@ -110,7 +110,7 @@ spatial::SpatialDataflowExportStage getPimSpatialDataflowExportStage(
|
||||
case SpatialDataflowExportSpatial4: return spatial::SpatialDataflowExportStage::Spatial4;
|
||||
case SpatialDataflowExportAll: return spatial::SpatialDataflowExportStage::All;
|
||||
}
|
||||
llvm_unreachable("unknown PIM Spatial dataflow export stage");
|
||||
llvm_unreachable("unknown Pim Spatial dataflow export stage");
|
||||
}
|
||||
|
||||
spatial::SpatialTargetResources getPimSpatialTargetResources(const spatial::SchedulingTarget& target) {
|
||||
@@ -120,7 +120,7 @@ spatial::SpatialTargetResources getPimSpatialTargetResources(const spatial::Sche
|
||||
resources.processorCount = target.processorCount;
|
||||
resources.vectorWidth = target.vectorWidth;
|
||||
if (failed(resources.verify()))
|
||||
llvm::report_fatal_error("PIM target resources are incomplete");
|
||||
llvm::report_fatal_error("Pim target resources are incomplete");
|
||||
return resources;
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ const llvm::json::Object& requireObject(const llvm::json::Object& object,
|
||||
llvm::StringRef path) {
|
||||
const llvm::json::Object* nested = object.getObject(key);
|
||||
if (!nested)
|
||||
llvm::report_fatal_error("PIM target config is missing object '" + path + "." + key + "'");
|
||||
llvm::report_fatal_error("Pim target config is missing object '" + path + "." + key + "'");
|
||||
return *nested;
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ Cost getConfigCost(const llvm::json::Object& object,
|
||||
return fallback;
|
||||
if (!std::isfinite(*number) || *number < 0.0 || (!allowZero && *number == 0.0)
|
||||
|| *number > static_cast<double>(std::numeric_limits<Cost>::max()))
|
||||
llvm::report_fatal_error("PIM target config field '" + key + "' must be a valid positive number");
|
||||
llvm::report_fatal_error("Pim target config field '" + key + "' must be a valid positive number");
|
||||
return static_cast<Cost>(std::ceil(*number));
|
||||
}
|
||||
|
||||
@@ -159,11 +159,11 @@ std::pair<size_t, size_t> getConfigPair(const llvm::json::Object& object,
|
||||
llvm::StringRef key) {
|
||||
const llvm::json::Array* values = object.getArray(key);
|
||||
if (!values || values->size() != 2)
|
||||
llvm::report_fatal_error("PIM target config field '" + key + "' must contain two integers");
|
||||
llvm::report_fatal_error("Pim target config field '" + key + "' must contain two integers");
|
||||
std::optional<int64_t> first = (*values)[0].getAsInteger();
|
||||
std::optional<int64_t> second = (*values)[1].getAsInteger();
|
||||
if (!first || !second || *first <= 0 || *second <= 0)
|
||||
llvm::report_fatal_error("PIM target config field '" + key + "' must contain two positive integers");
|
||||
llvm::report_fatal_error("Pim target config field '" + key + "' must contain two positive integers");
|
||||
return {static_cast<size_t>(*first), static_cast<size_t>(*second)};
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ void loadPimInterProcessorLatencies(
|
||||
network.getString("net_config_file_path");
|
||||
if (!filename)
|
||||
llvm::report_fatal_error(
|
||||
"PIM target config is missing network latency file path");
|
||||
"Pim target config is missing network latency file path");
|
||||
|
||||
llvm::SmallString<256> networkPath(*filename);
|
||||
if (!llvm::sys::path::is_absolute(networkPath)) {
|
||||
@@ -187,19 +187,19 @@ void loadPimInterProcessorLatencies(
|
||||
auto buffer = llvm::MemoryBuffer::getFile(networkPath);
|
||||
if (!buffer)
|
||||
llvm::report_fatal_error(
|
||||
llvm::Twine("failed to read PIM network config '")
|
||||
llvm::Twine("failed to read Pim network config '")
|
||||
+ networkPath + "': " + buffer.getError().message());
|
||||
auto parsed = llvm::json::parse(buffer.get()->getBuffer());
|
||||
if (!parsed)
|
||||
llvm::report_fatal_error(
|
||||
llvm::Twine("failed to parse PIM network config '")
|
||||
llvm::Twine("failed to parse Pim network config '")
|
||||
+ networkPath + "': " + llvm::toString(parsed.takeError()));
|
||||
const llvm::json::Object* root = parsed->getAsObject();
|
||||
const llvm::json::Object* latencies =
|
||||
root ? root->getObject("latency") : nullptr;
|
||||
if (!latencies)
|
||||
llvm::report_fatal_error(
|
||||
"PIM network config is missing its latency matrix");
|
||||
"Pim network config is missing its latency matrix");
|
||||
|
||||
target.interProcessorLatencyNs.assign(
|
||||
target.processorCount * target.processorCount, 0);
|
||||
@@ -210,7 +210,7 @@ void loadPimInterProcessorLatencies(
|
||||
const llvm::json::Object* row = latencies->getObject(sourceKey);
|
||||
if (!row)
|
||||
llvm::report_fatal_error(
|
||||
llvm::Twine("PIM network config is missing latency row ")
|
||||
llvm::Twine("Pim network config is missing latency row ")
|
||||
+ sourceKey);
|
||||
for (size_t destination = 0;
|
||||
destination < target.processorCount; ++destination) {
|
||||
@@ -220,7 +220,7 @@ void loadPimInterProcessorLatencies(
|
||||
std::optional<double> latency = row->getNumber(destinationKey);
|
||||
if (!latency || !std::isfinite(*latency) || *latency <= 0.0)
|
||||
llvm::report_fatal_error(
|
||||
llvm::Twine("PIM network config is missing latency ")
|
||||
llvm::Twine("Pim network config is missing latency ")
|
||||
+ sourceKey + " -> " + destinationKey);
|
||||
Cost roundedLatency = static_cast<Cost>(std::ceil(*latency));
|
||||
target.interProcessorLatencyNs[
|
||||
@@ -244,17 +244,17 @@ spatial::SchedulingTarget getPimSchedulingTarget() {
|
||||
auto buffer = llvm::MemoryBuffer::getFile(pimTargetConfig);
|
||||
if (!buffer)
|
||||
llvm::report_fatal_error(
|
||||
llvm::Twine("failed to read PIM target config '")
|
||||
llvm::Twine("failed to read Pim target config '")
|
||||
+ pimTargetConfig.getValue() + "': " + buffer.getError().message());
|
||||
auto parsed = llvm::json::parse(buffer.get()->getBuffer());
|
||||
if (!parsed)
|
||||
llvm::report_fatal_error(
|
||||
llvm::Twine("failed to parse PIM target config '")
|
||||
llvm::Twine("failed to parse Pim target config '")
|
||||
+ pimTargetConfig.getValue() + "': "
|
||||
+ llvm::toString(parsed.takeError()));
|
||||
const llvm::json::Object* root = parsed->getAsObject();
|
||||
if (!root)
|
||||
llvm::report_fatal_error("PIM target config must contain a JSON object");
|
||||
llvm::report_fatal_error("Pim target config must contain a JSON object");
|
||||
|
||||
const llvm::json::Object& chip = requireObject(*root, "chip_config", "root");
|
||||
const llvm::json::Object& core = requireObject(chip, "core_config", "chip_config");
|
||||
@@ -267,7 +267,7 @@ spatial::SchedulingTarget getPimSchedulingTarget() {
|
||||
|
||||
std::optional<int64_t> coreCount = chip.getInteger("core_cnt");
|
||||
if (!coreCount || *coreCount <= 0)
|
||||
llvm::report_fatal_error("PIM target config field 'core_cnt' must be a positive integer");
|
||||
llvm::report_fatal_error("Pim target config field 'core_cnt' must be a positive integer");
|
||||
target.processorCount = static_cast<size_t>(*coreCount);
|
||||
target.residentWeightCapacity =
|
||||
getConfigCost(matrix, "xbar_array_count", target.residentWeightCapacity);
|
||||
@@ -278,7 +278,7 @@ spatial::SchedulingTarget getPimSchedulingTarget() {
|
||||
|| target.residentWeightCapacity != crossbarCountInCore.getValue()
|
||||
|| target.matrixRows != crossbarSize.getValue()
|
||||
|| target.matrixColumns != crossbarSize.getValue())
|
||||
llvm::report_fatal_error("PIM target config resources do not match --core-count, "
|
||||
llvm::report_fatal_error("Pim target config resources do not match --core-count, "
|
||||
"--crossbar-count, and --crossbar-size");
|
||||
loadPimInterProcessorLatencies(target, network);
|
||||
|
||||
@@ -331,8 +331,7 @@ void addPassesPim(OwningOpRef<ModuleOp>& module,
|
||||
PassManager& pm,
|
||||
EmissionTargetType& emissionTarget,
|
||||
std::string outputNameNoExt) {
|
||||
verifyExplicitPimCoreCount();
|
||||
verifyPimPipelineStages();
|
||||
verifyPimCompilerOptions();
|
||||
spatial::SchedulingTarget schedulingTarget = getPimSchedulingTarget();
|
||||
spatial::SpatialTargetResources targetResources = getPimSpatialTargetResources(schedulingTarget);
|
||||
|
||||
@@ -352,7 +351,8 @@ void addPassesPim(OwningOpRef<ModuleOp>& module,
|
||||
spatial::SpatialDataflowExportStage exportStage =
|
||||
getPimSpatialDataflowExportStage(pimExportSpatialDataflow.getValue());
|
||||
pm.addPass(createONNXToSpatialPass(targetResources, planningOptions));
|
||||
pm.addPass(createSpatialLayoutPlanningPass(targetResources));
|
||||
pm.addPass(createSpatialLayoutPlanningPass(
|
||||
targetResources, pimDisableSpatialPlanning.getValue()));
|
||||
pm.addPass(createLowerSpatialPlansPass(targetResources, planningOptions, exportStage));
|
||||
pm.addPass(createTrivialGraphComputeMergePass(
|
||||
schedulingTarget.residentWeightCapacity, exportStage));
|
||||
|
||||
@@ -46,7 +46,7 @@ static LogicalResult compileCoreEmissionPlan(Block& block, SmallVectorImpl<Compi
|
||||
auto upper = compileIndexExpr(forOp.getUpperBound());
|
||||
auto step = compileIndexExpr(forOp.getStep());
|
||||
if (failed(lower) || failed(upper) || failed(step)) {
|
||||
forOp.emitOpError("requires statically evaluable scf.for bounds for PIM codegen");
|
||||
forOp.emitOpError("requires statically evaluable scf.for bounds for Pim codegen");
|
||||
return failure();
|
||||
}
|
||||
CompiledCoreNode node;
|
||||
@@ -63,7 +63,7 @@ static LogicalResult compileCoreEmissionPlan(Block& block, SmallVectorImpl<Compi
|
||||
if (auto ifOp = dyn_cast<scf::IfOp>(op)) {
|
||||
auto condition = compileIndexExpr(ifOp.getCondition());
|
||||
if (failed(condition)) {
|
||||
ifOp.emitOpError("requires statically evaluable scf.if condition for PIM codegen");
|
||||
ifOp.emitOpError("requires statically evaluable scf.if condition for Pim codegen");
|
||||
return failure();
|
||||
}
|
||||
CompiledCoreNode node;
|
||||
@@ -82,7 +82,7 @@ static LogicalResult compileCoreEmissionPlan(Block& block, SmallVectorImpl<Compi
|
||||
if (auto switchOp = dyn_cast<scf::IndexSwitchOp>(op)) {
|
||||
auto selector = compileIndexExpr(switchOp.getArg());
|
||||
if (failed(selector)) {
|
||||
switchOp.emitOpError("requires a statically evaluable scf.index_switch selector for PIM codegen");
|
||||
switchOp.emitOpError("requires a statically evaluable scf.index_switch selector for Pim codegen");
|
||||
return failure();
|
||||
}
|
||||
CompiledCoreNode node;
|
||||
|
||||
@@ -249,7 +249,7 @@ auto createEmptySpatGraphComputeBatch(RewriterT& rewriter,
|
||||
if (laneCount <= 0 || laneCount > std::numeric_limits<int32_t>::max())
|
||||
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(mlir::failure());
|
||||
|
||||
auto laneCountAttr = pim::getCheckedI32Attr(rewriter, loc, laneCount, "spatial compute_batch lane count");
|
||||
auto laneCountAttr = pim::getCheckedI32Attr(rewriter, loc, laneCount, "Spatial compute_batch lane count");
|
||||
if (mlir::failed(laneCountAttr))
|
||||
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(mlir::failure());
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ llvm::SmallVector<mlir::Value> sliceVector(const mlir::Value& vectorToSlice,
|
||||
mlir::Location loc);
|
||||
|
||||
/// Partitions one logical vector into per-core crossbar-sized slices using the
|
||||
/// current PIM target geometry.
|
||||
/// current Pim target geometry.
|
||||
llvm::DenseMap<CoreId, llvm::SmallVector<mlir::Value>> sliceVectorPerCrossbarPerCore(
|
||||
const mlir::Value& vectorToSlice,
|
||||
mlir::PatternRewriter& rewriter,
|
||||
|
||||
@@ -46,7 +46,7 @@ struct LowerSpatialPlansPass final
|
||||
}
|
||||
auto entryFunc = getPimEntryFunc(moduleOp);
|
||||
if (failed(entryFunc)) {
|
||||
moduleOp.emitError("failed to locate the PIM entry function during LowerSpatialPlans");
|
||||
moduleOp.emitError("failed to locate the Pim entry function during LowerSpatialPlans");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ void ONNXToSpatialPass::runOnOperation() {
|
||||
|
||||
auto entryFunc = getPimEntryFunc(moduleOp);
|
||||
if (failed(entryFunc)) {
|
||||
moduleOp.emitError("failed to locate the PIM entry function during ONNX-to-Spatial lowering");
|
||||
moduleOp.emitError("failed to locate the Pim entry function during ONNX-to-Spatial lowering");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
@@ -245,7 +245,7 @@ void ONNXToSpatialPass::runOnOperation() {
|
||||
RewritePatternSet postPatterns(ctx);
|
||||
populatePostPatterns(postPatterns, ctx);
|
||||
if (failed(applyPartialConversion(*entryFunc, postTarget, std::move(postPatterns)))) {
|
||||
moduleOp.emitError("failed to normalize weight-like Spatial compute operands before Spatial-to-PIM lowering");
|
||||
moduleOp.emitError("failed to normalize weight-like Spatial compute operands before Spatial-to-Pim lowering");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -42,8 +42,9 @@ static SmallVector<spatial::PhysicalLayout> getOperandLayouts(
|
||||
class SpatialLayoutAnalysis {
|
||||
public:
|
||||
SpatialLayoutAnalysis(func::FuncOp funcOp,
|
||||
const spatial::SpatialTargetResources& target)
|
||||
: funcOp(funcOp), target(target) {}
|
||||
const spatial::SpatialTargetResources& target,
|
||||
bool selectTrivialPlan)
|
||||
: funcOp(funcOp), target(target), selectTrivialPlan(selectTrivialPlan) {}
|
||||
|
||||
FailureOr<SpatialLayoutSelection> run() {
|
||||
SpatialLayoutSelection selection;
|
||||
@@ -56,6 +57,9 @@ public:
|
||||
selection.selectedAlternative[&op] = 0;
|
||||
}
|
||||
|
||||
if (selectTrivialPlan)
|
||||
return selection;
|
||||
|
||||
const size_t maxRounds = 2 * planOps.size() + 1;
|
||||
for (size_t round = 0; round < maxRounds; ++round) {
|
||||
bool changed = false;
|
||||
@@ -168,6 +172,7 @@ private:
|
||||
|
||||
func::FuncOp funcOp;
|
||||
const spatial::SpatialTargetResources& target;
|
||||
bool selectTrivialPlan;
|
||||
};
|
||||
|
||||
static LogicalResult materializeMismatchedUses(
|
||||
@@ -251,8 +256,9 @@ struct SpatialLayoutPlanningPass final
|
||||
}
|
||||
|
||||
SpatialLayoutPlanningPass() = default;
|
||||
explicit SpatialLayoutPlanningPass(const spatial::SpatialTargetResources& target)
|
||||
: target(target), hasTarget(true) {}
|
||||
SpatialLayoutPlanningPass(const spatial::SpatialTargetResources& target,
|
||||
bool selectTrivialPlan)
|
||||
: target(target), selectTrivialPlan(selectTrivialPlan), hasTarget(true) {}
|
||||
|
||||
void runOnOperation() override {
|
||||
ModuleOp moduleOp = getOperation();
|
||||
@@ -263,13 +269,13 @@ struct SpatialLayoutPlanningPass final
|
||||
}
|
||||
auto entryFunc = getPimEntryFunc(moduleOp);
|
||||
if (failed(entryFunc)) {
|
||||
moduleOp.emitError("failed to locate the PIM entry function during Spatial layout planning");
|
||||
moduleOp.emitError("failed to locate the Pim entry function during Spatial layout planning");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
func::FuncOp funcOp = *entryFunc;
|
||||
SpatialLayoutAnalysis analysis(funcOp, target);
|
||||
SpatialLayoutAnalysis analysis(funcOp, target, selectTrivialPlan);
|
||||
FailureOr<SpatialLayoutSelection> selection = analysis.run();
|
||||
if (failed(selection)) {
|
||||
signalPassFailure();
|
||||
@@ -301,6 +307,7 @@ struct SpatialLayoutPlanningPass final
|
||||
}
|
||||
|
||||
spatial::SpatialTargetResources target;
|
||||
bool selectTrivialPlan = false;
|
||||
bool hasTarget = false;
|
||||
};
|
||||
|
||||
@@ -311,8 +318,8 @@ std::unique_ptr<Pass> createSpatialLayoutPlanningPass() {
|
||||
}
|
||||
|
||||
std::unique_ptr<Pass> createSpatialLayoutPlanningPass(
|
||||
const spatial::SpatialTargetResources& target) {
|
||||
return std::make_unique<SpatialLayoutPlanningPass>(target);
|
||||
const spatial::SpatialTargetResources& target, bool selectTrivialPlan) {
|
||||
return std::make_unique<SpatialLayoutPlanningPass>(target, selectTrivialPlan);
|
||||
}
|
||||
|
||||
} // namespace onnx_mlir
|
||||
|
||||
@@ -199,7 +199,7 @@ static bool writeConvLoweringReport(const ConvLoweringReportEntry& entry,
|
||||
return false;
|
||||
}
|
||||
|
||||
reportFile << "# PIM Conv Lowering Report (bounded to 512 rows)\n\n";
|
||||
reportFile << "# Pim conv lowering report (bounded to 512 rows)\n\n";
|
||||
reportFile << "## Plan selection\n";
|
||||
writeConvReportTableHeader(reportFile, "Selector");
|
||||
bool realizationSectionStarted = false;
|
||||
|
||||
@@ -370,6 +370,14 @@ struct ReduceMeanToSpatialCompute : OpConversionPattern<ReduceMeanOp> {
|
||||
Location loc = reduceMeanOp.getLoc();
|
||||
RankedTensorType leafType = getAllOnesType(inputType, resultType.getElementType());
|
||||
RankedTensorType keepdimsType = getKeepdimsType(inputType, resultType.getElementType(), reducedAxes);
|
||||
if (semantics->keepdims != 0 && inputType.getRank() == 4
|
||||
&& inputType.getDimSize(0) == 1 && semantics->axes == ArrayRef<int64_t>({2, 3})
|
||||
&& resultType == keepdimsType) {
|
||||
auto plan = spatial::SpatGlobalAveragePoolPlanOp::create(
|
||||
rewriter, loc, resultType, adaptor.getData(), spatial::getNCHWLayout(rewriter.getContext()));
|
||||
rewriter.replaceOp(reduceMeanOp, plan.getResult());
|
||||
return success();
|
||||
}
|
||||
int64_t laneCount = 1;
|
||||
for (auto [dim, isReduced] : llvm::zip_equal(keepdimsType.getShape(), reducedAxes)) {
|
||||
if (isReduced)
|
||||
|
||||
@@ -307,7 +307,7 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatSchedul
|
||||
"resultful compute_batch lowering currently requires a spat.in_parallel terminator");
|
||||
}
|
||||
|
||||
auto coreIds = getRequiredScheduledBatchCoreIds(computeBatchOp, "spatial compute_batch core id");
|
||||
auto coreIds = getRequiredScheduledBatchCoreIds(computeBatchOp, "Spatial compute_batch core id");
|
||||
if (failed(coreIds))
|
||||
return failure();
|
||||
SmallVector<Value> batchWeights(computeBatchOp.getWeights().begin(), computeBatchOp.getWeights().end());
|
||||
@@ -317,7 +317,7 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatSchedul
|
||||
|
||||
rewriter.setInsertionPointAfter(computeBatchOp);
|
||||
auto laneCountAttr = pim::getCheckedI32Attr(
|
||||
rewriter, computeBatchOp, static_cast<uint64_t>(computeBatchOp.getLaneCount()), "pim core_batch lane count");
|
||||
rewriter, computeBatchOp, static_cast<uint64_t>(computeBatchOp.getLaneCount()), "Pim core_batch lane count");
|
||||
if (failed(laneCountAttr))
|
||||
return failure();
|
||||
auto coreBatchOp =
|
||||
|
||||
@@ -373,9 +373,6 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeOp(spatial::SpatScheduledCom
|
||||
rewriter, receiveOp->getLoc(), outputBuffer.getType(), zero,
|
||||
hostWaitLoad.getHostOffset(), outputBuffer, *hostBuffer, *sizeAttr)
|
||||
.getOutput();
|
||||
PimSyncOp::create(
|
||||
rewriter, receiveOp->getLoc(), hostWaitLoad.getSourceCoreId(),
|
||||
hostWaitLoad.getAcknowledgementEventRegister());
|
||||
} else {
|
||||
received = PimReceiveOp::create(
|
||||
rewriter, receiveOp->getLoc(), outputBuffer.getType(), outputBuffer,
|
||||
@@ -410,7 +407,7 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeOp(spatial::SpatScheduledCom
|
||||
continue;
|
||||
}
|
||||
|
||||
return computeOp.emitOpError("has an unsupported remaining result use during Spatial-to-PIM lowering");
|
||||
return computeOp.emitOpError("has an unsupported remaining result use during Spatial-to-Pim lowering");
|
||||
}
|
||||
|
||||
rewriter.setInsertionPoint(yieldOp);
|
||||
@@ -420,7 +417,7 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeOp(spatial::SpatScheduledCom
|
||||
if (!computeOp.getWeights().empty())
|
||||
computeWeights.append(computeOp.getWeights().begin(), computeOp.getWeights().end());
|
||||
rewriter.setInsertionPointAfter(computeOp);
|
||||
auto checkedCoreId = getRequiredScheduledCoreId(computeOp, "spatial compute core id");
|
||||
auto checkedCoreId = getRequiredScheduledCoreId(computeOp, "Spatial compute core id");
|
||||
if (failed(checkedCoreId))
|
||||
return failure();
|
||||
auto coreIdAttr = pim::getCheckedI32Attr(rewriter, computeOp, static_cast<int64_t>(*checkedCoreId), "pim core id");
|
||||
|
||||
@@ -152,10 +152,6 @@ struct HostWaitLoadLowering : OpRewritePattern<spatial::SpatHostWaitLoadOp> {
|
||||
Value output = pim::PimMemCopyHostToDevOp::create(
|
||||
rewriter, op.getLoc(), outputBuffer.getType(), zero,
|
||||
op.getHostOffset(), outputBuffer, *hostBuffer, sizeAttr).getOutput();
|
||||
auto sync = pim::PimSyncOp::create(
|
||||
rewriter, op.getLoc(), op.getSourceCoreId(),
|
||||
op.getAcknowledgementEventRegister());
|
||||
copyRaptorDebugAttrs(op.getOperation(), sync.getOperation());
|
||||
return output;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -734,7 +734,7 @@ raptor::SpatialToPimPass::ReturnPathLoweringResult raptor::SpatialToPimPass::low
|
||||
auto storedType = dyn_cast<RankedTensorType>(storedValue.getType());
|
||||
if (!storedType) {
|
||||
producerOp->emitOpError(
|
||||
"has an unsupported non-ranked concat-return helper yield during Spatial-to-PIM lowering");
|
||||
"has an unsupported non-ranked concat-return helper yield during Spatial-to-Pim lowering");
|
||||
return ReturnPathLoweringResult::Failure;
|
||||
}
|
||||
rewriter.setInsertionPointAfterValue(storedValue);
|
||||
@@ -748,7 +748,7 @@ raptor::SpatialToPimPass::ReturnPathLoweringResult raptor::SpatialToPimPass::low
|
||||
SmallVector<int64_t> destinationIndices;
|
||||
if (failed(mapIndicesThroughHelperChain(
|
||||
sourceIndices, concatReturnUse->concatShape, concatReturnUse->helperChain, destinationIndices))) {
|
||||
producerOp->emitOpError("has an unsupported concat-return helper chain during Spatial-to-PIM lowering");
|
||||
producerOp->emitOpError("has an unsupported concat-return helper chain during Spatial-to-Pim lowering");
|
||||
return ReturnPathLoweringResult::Failure;
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
|
||||
operationsToRemove.clear();
|
||||
ModuleOp moduleOp = getOperation();
|
||||
if (!hasTarget || failed(targetResources.verify())) {
|
||||
moduleOp.emitError("Spatial-to-PIM lowering requires valid injected target resources");
|
||||
moduleOp.emitError("Spatial-to-Pim lowering requires valid injected target resources");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
@@ -96,7 +96,7 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
|
||||
|
||||
auto entryFunc = getPimEntryFunc(moduleOp);
|
||||
if (failed(entryFunc)) {
|
||||
moduleOp.emitError("failed to locate the PIM entry function during Spatial-to-PIM lowering");
|
||||
moduleOp.emitError("failed to locate the Pim entry function during Spatial-to-Pim lowering");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
@@ -135,7 +135,7 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
|
||||
RewritePatternSet initialPatterns(ctx);
|
||||
populateInitialPatterns(initialPatterns);
|
||||
if (failed(applyPartialConversion(moduleOp, target, std::move(initialPatterns)))) {
|
||||
moduleOp.emitError("failed to lower required Spatial ops to the initial PIM form");
|
||||
moduleOp.emitError("failed to lower required Spatial ops to the initial Pim form");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
@@ -153,7 +153,7 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
|
||||
auto returnOp = cast<func::ReturnOp>(funcOp.front().getTerminator());
|
||||
addReturnOutputBuffers(returnOp, rewriter);
|
||||
if (failed(allocateAndInitializeCoreLocalVariables(funcOp, rewriter))) {
|
||||
funcOp.emitOpError("failed to allocate or initialize core-local tensors during Spatial-to-PIM lowering");
|
||||
funcOp.emitOpError("failed to allocate or initialize core-local tensors during Spatial-to-Pim lowering");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
@@ -285,7 +285,7 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
|
||||
RewritePatternSet communicationPatterns(ctx);
|
||||
populateChannelLoweringPatterns(communicationPatterns);
|
||||
if (failed(applyFullConversion(funcOp, communicationTarget, std::move(communicationPatterns)))) {
|
||||
funcOp.emitOpError("failed to lower Spatial communication ops to PIM communication ops");
|
||||
funcOp.emitOpError("failed to lower Spatial communication ops to Pim communication ops");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace raptor {
|
||||
struct SpatialToPimPass : mlir::PassWrapper<SpatialToPimPass, mlir::OperationPass<mlir::ModuleOp>> {
|
||||
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(SpatialToPimPass)
|
||||
llvm::StringRef getArgument() const override { return "convert-spatial-to-pim"; }
|
||||
llvm::StringRef getDescription() const override { return "Lower Spatial ops to PIM-ready format"; }
|
||||
llvm::StringRef getDescription() const override { return "Lower Spatial ops to Pim-ready format"; }
|
||||
|
||||
SpatialToPimPass() = default;
|
||||
explicit SpatialToPimPass(const spatial::SpatialTargetResources& target)
|
||||
|
||||
@@ -402,7 +402,7 @@ static LogicalResult verifyPimCoresNeedNoTensorCopies(
|
||||
|
||||
bufferization::BufferizationState state;
|
||||
if (failed(bufferization::insertTensorCopies(*clone, options, state))) {
|
||||
moduleOp.emitError("official one-shot analysis failed while verifying PIM core copy freedom");
|
||||
moduleOp.emitError("official one-shot analysis failed while verifying Pim core copy freedom");
|
||||
return failure();
|
||||
}
|
||||
|
||||
@@ -415,10 +415,10 @@ static LogicalResult verifyPimCoresNeedNoTensorCopies(
|
||||
Operation* requiredBy = alloc->getUsers().empty()
|
||||
? alloc.getOperation() : *alloc->getUsers().begin();
|
||||
diagnostics.report(requiredBy, [](Operation* op) {
|
||||
op->emitOpError("official one-shot bufferization requires a tensor copy inside a PIM core");
|
||||
op->emitOpError("official one-shot bufferization requires a tensor copy inside a Pim core");
|
||||
});
|
||||
});
|
||||
diagnostics.emitSuppressedSummary(moduleOp, "required PIM core tensor copies");
|
||||
diagnostics.emitSuppressedSummary(moduleOp, "required Pim core tensor copies");
|
||||
return success(!diagnostics.hasFailure());
|
||||
}
|
||||
|
||||
@@ -440,7 +440,7 @@ static LogicalResult runOneShotPimBufferization(
|
||||
bufferization::BufferizationState state;
|
||||
if (failed(bufferization::insertTensorCopies(moduleOp, hostOptions, state))
|
||||
|| failed(bufferization::bufferizeModuleOp(moduleOp, options, state))) {
|
||||
moduleOp.emitError("Failed to bufferize PIM and Spatial ops");
|
||||
moduleOp.emitError("Failed to bufferize Pim and Spatial ops");
|
||||
return failure();
|
||||
}
|
||||
return success();
|
||||
@@ -478,7 +478,7 @@ static LogicalResult verifyContiguousRuntimeOperands(ModuleOp moduleOp) {
|
||||
if (succeeded(resolveContiguousAddress(operand, knowledge)) || succeeded(compileContiguousAddressExpr(operand)))
|
||||
return;
|
||||
op.emitOpError() << "operand #" << operandIndex
|
||||
<< " is not backed by contiguous addressable storage after PIM bufferization";
|
||||
<< " is not backed by contiguous addressable storage after Pim bufferization";
|
||||
hasFailure = true;
|
||||
};
|
||||
|
||||
@@ -552,7 +552,7 @@ static LogicalResult verifyContiguousRuntimeOperands(ModuleOp moduleOp) {
|
||||
});
|
||||
|
||||
if (hasFailure) {
|
||||
moduleOp.emitError("PIM bufferization must fully normalize executable runtime operand contiguity before codegen");
|
||||
moduleOp.emitError("Pim bufferization must fully normalize executable runtime operand contiguity before codegen");
|
||||
return failure();
|
||||
}
|
||||
return success();
|
||||
@@ -589,7 +589,7 @@ static LogicalResult verifyPimCopyAddressSpaces(ModuleOp moduleOp) {
|
||||
});
|
||||
if (failureCount != 0)
|
||||
moduleOp.emitError() << "found " << failureCount
|
||||
<< " PIM copy address-space violation(s); the first is reported above";
|
||||
<< " Pim copy address-space violation(s); the first is reported above";
|
||||
return success(failureCount == 0);
|
||||
}
|
||||
|
||||
@@ -673,7 +673,7 @@ static LogicalResult normalizePimMemory(ModuleOp moduleOp, func::FuncOp funcOp)
|
||||
GreedyRewriteConfig contiguityConfig;
|
||||
contiguityConfig.enableFolding(false);
|
||||
if (failed(applyPatternsGreedily(moduleOp, std::move(contiguityPatterns), contiguityConfig))) {
|
||||
moduleOp.emitError("failed to normalize PIM copy contiguity during bufferization");
|
||||
moduleOp.emitError("failed to normalize Pim copy contiguity during bufferization");
|
||||
return failure();
|
||||
}
|
||||
annotateWeightsMemrefs(moduleOp, funcOp);
|
||||
@@ -684,7 +684,7 @@ static LogicalResult normalizePimMemory(ModuleOp moduleOp, func::FuncOp funcOp)
|
||||
static FailureOr<func::FuncOp> requirePimEntryFunc(ModuleOp moduleOp, StringRef phase) {
|
||||
auto entryFunc = getPimEntryFunc(moduleOp);
|
||||
if (failed(entryFunc)) {
|
||||
moduleOp.emitError("failed to locate the PIM entry function during ") << phase;
|
||||
moduleOp.emitError("failed to locate the Pim entry function during ") << phase;
|
||||
return failure();
|
||||
}
|
||||
return *entryFunc;
|
||||
@@ -701,12 +701,12 @@ struct PimBufferizationPreparationPass
|
||||
|
||||
StringRef getArgument() const override { return "pim-bufferization-preparation"; }
|
||||
StringRef getDescription() const override {
|
||||
return "Prepare writable tensor destinations for PIM one-shot bufferization.";
|
||||
return "Prepare writable tensor destinations for Pim one-shot bufferization.";
|
||||
}
|
||||
|
||||
void runOnOperation() final {
|
||||
ModuleOp moduleOp = getOperation();
|
||||
auto funcOp = requirePimEntryFunc(moduleOp, "PIM bufferization preparation");
|
||||
auto funcOp = requirePimEntryFunc(moduleOp, "Pim bufferization preparation");
|
||||
if (failed(funcOp)) {
|
||||
signalPassFailure();
|
||||
return;
|
||||
@@ -725,7 +725,7 @@ struct PimOneShotBufferizationPass
|
||||
|
||||
StringRef getArgument() const override { return "pim-one-shot-bufferization"; }
|
||||
StringRef getDescription() const override {
|
||||
return "Run one-shot bufferization for PIM and Spatial tensors.";
|
||||
return "Run one-shot bufferization for Pim and Spatial tensors.";
|
||||
}
|
||||
|
||||
void runOnOperation() final {
|
||||
@@ -740,12 +740,12 @@ struct PimMemoryNormalizationPass
|
||||
|
||||
StringRef getArgument() const override { return "pim-memory-normalization"; }
|
||||
StringRef getDescription() const override {
|
||||
return "Normalize PIM memory copies and verify addressable operands.";
|
||||
return "Normalize Pim memory copies and verify addressable operands.";
|
||||
}
|
||||
|
||||
void runOnOperation() final {
|
||||
ModuleOp moduleOp = getOperation();
|
||||
auto funcOp = requirePimEntryFunc(moduleOp, "PIM memory normalization");
|
||||
auto funcOp = requirePimEntryFunc(moduleOp, "Pim memory normalization");
|
||||
if (failed(funcOp)) {
|
||||
signalPassFailure();
|
||||
return;
|
||||
@@ -761,20 +761,20 @@ static LogicalResult verifyNoTensorValues(ModuleOp moduleOp) {
|
||||
if (failureCount >= 8)
|
||||
return;
|
||||
if (op->getDialect()->getNamespace() == "tensor") {
|
||||
op->emitOpError("tensor operation remains after PIM bufferization");
|
||||
op->emitOpError("tensor operation remains after Pim bufferization");
|
||||
++failureCount;
|
||||
return;
|
||||
}
|
||||
for (Value value : op->getOperands()) {
|
||||
if (isa<TensorType>(value.getType())) {
|
||||
op->emitOpError("tensor operand remains after PIM bufferization");
|
||||
op->emitOpError("tensor operand remains after Pim bufferization");
|
||||
++failureCount;
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (Value value : op->getResults()) {
|
||||
if (isa<TensorType>(value.getType())) {
|
||||
op->emitOpError("tensor result remains after PIM bufferization");
|
||||
op->emitOpError("tensor result remains after Pim bufferization");
|
||||
++failureCount;
|
||||
return;
|
||||
}
|
||||
@@ -782,7 +782,7 @@ static LogicalResult verifyNoTensorValues(ModuleOp moduleOp) {
|
||||
});
|
||||
if (failureCount != 0)
|
||||
moduleOp.emitError() << "found " << failureCount
|
||||
<< " tensor value(s) after PIM bufferization"
|
||||
<< " tensor value(s) after Pim bufferization"
|
||||
<< (failureCount == 8 ? " (first 8 reported)" : "");
|
||||
return success(failureCount == 0);
|
||||
}
|
||||
@@ -793,7 +793,7 @@ struct PimBufferizationVerificationPass
|
||||
|
||||
StringRef getArgument() const override { return "pim-bufferization-verification"; }
|
||||
StringRef getDescription() const override {
|
||||
return "Verify tensor elimination, contiguity, and PIM copy address spaces.";
|
||||
return "Verify tensor elimination, contiguity, and Pim copy address spaces.";
|
||||
}
|
||||
|
||||
void runOnOperation() final {
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@ struct HostConstantFoldingPass : PassWrapper<HostConstantFoldingPass, OperationP
|
||||
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(HostConstantFoldingPass)
|
||||
|
||||
StringRef getArgument() const override { return "pim-host-constant-folding-pass"; }
|
||||
StringRef getDescription() const override { return "Fold host-side constant expressions before PIM verification"; }
|
||||
StringRef getDescription() const override { return "Fold host-side constant expressions before Pim verification"; }
|
||||
|
||||
LogicalResult initialize(MLIRContext* context) override {
|
||||
RewritePatternSet owningPatterns(context);
|
||||
@@ -38,7 +38,7 @@ struct HostConstantFoldingPass : PassWrapper<HostConstantFoldingPass, OperationP
|
||||
GreedyRewriteConfig config;
|
||||
config.enableFolding();
|
||||
if (failed(applyPatternsGreedily(moduleOp, *patterns, config))) {
|
||||
moduleOp.emitError("PIM host constant folding failed in the greedy rewrite driver");
|
||||
moduleOp.emitError("Pim host constant folding failed in the greedy rewrite driver");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -472,7 +472,7 @@ struct FoldConstantHostCopyPattern final : OpRewritePattern<memref::CopyOp> {
|
||||
}
|
||||
};
|
||||
|
||||
// Converts PIM copies from dense globals into direct folded globals before codegen.
|
||||
// Converts Pim copies from dense globals into direct folded globals before codegen.
|
||||
struct FoldConstantMemCpPattern final : OpRewritePattern<pim::PimMemCopyOp> {
|
||||
using OpRewritePattern::OpRewritePattern;
|
||||
|
||||
|
||||
+2
-2
@@ -40,7 +40,7 @@ struct LowerTransposePattern final : OpRewritePattern<pim::PimTransposeOp> {
|
||||
auto sourceType = dyn_cast<MemRefType>(op.getInput().getType());
|
||||
auto targetType = dyn_cast<MemRefType>(op.getOutputBuffer().getType());
|
||||
if (!sourceType || !targetType || !sourceType.hasStaticShape() || !targetType.hasStaticShape())
|
||||
return op.emitOpError("requires static memref operands before PIM instruction selection");
|
||||
return op.emitOpError("requires static memref operands before Pim instruction selection");
|
||||
|
||||
ArrayRef<int64_t> sourceShape = sourceType.getShape();
|
||||
size_t rank = sourceShape.size();
|
||||
@@ -147,7 +147,7 @@ struct InstructionSelectionPass : PassWrapper<InstructionSelectionPass, Operatio
|
||||
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(InstructionSelectionPass)
|
||||
|
||||
StringRef getArgument() const override { return "pim-instruction-selection"; }
|
||||
StringRef getDescription() const override { return "Select explicit PIM ISA operations"; }
|
||||
StringRef getDescription() const override { return "Select explicit Pim ISA operations"; }
|
||||
|
||||
void runOnOperation() override {
|
||||
RewritePatternSet patterns(&getContext());
|
||||
|
||||
@@ -36,7 +36,7 @@ struct PimLocalMemoryPlanningPass : PassWrapper<PimLocalMemoryPlanningPass, Oper
|
||||
|
||||
StringRef getArgument() const override { return "pim-local-memory-planning"; }
|
||||
StringRef getDescription() const override {
|
||||
return "Plan liveness-based addresses for PIM core-local memory";
|
||||
return "Plan liveness-based addresses for Pim core-local memory";
|
||||
}
|
||||
|
||||
void runOnOperation() override {
|
||||
@@ -149,14 +149,14 @@ FailureOr<CoreMemoryPlan> buildCoreMemoryPlan(Operation* coreLikeOp) {
|
||||
plan.intervals = std::move(*intervals);
|
||||
auto placements = planLocalMemoryPlacements(plan.intervals, kPimLocalMemoryAddressLimit);
|
||||
if (failed(placements)) {
|
||||
coreLikeOp->emitError("PIM local-memory plan exceeds the signed int32 address range");
|
||||
coreLikeOp->emitError("Pim local-memory plan exceeds the signed int32 address range");
|
||||
return failure();
|
||||
}
|
||||
plan.placements = std::move(*placements);
|
||||
for (const LocalMemoryPlacement& placement : plan.placements) {
|
||||
auto end = alignedEnd(placement.address, placement.size, kPimLocalMemoryAddressLimit);
|
||||
if (failed(end)) {
|
||||
coreLikeOp->emitError("PIM local-memory plan has invalid address arithmetic");
|
||||
coreLikeOp->emitError("Pim local-memory plan has invalid address arithmetic");
|
||||
return failure();
|
||||
}
|
||||
plan.arenaSize = std::max(plan.arenaSize, *end);
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
#include "mlir/Pass/Pass.h"
|
||||
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/ADT/DenseSet.h"
|
||||
#include "llvm/Support/FormatVariadic.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
@@ -117,7 +119,7 @@ verifyLocalMemoryPlan(Operation* coreLikeOp, pim::CappedDiagnosticReporter& diag
|
||||
for (StringRef name : kRemovedLocalMemoryPlanAttrNames)
|
||||
if (coreLikeOp->hasAttr(name)) {
|
||||
diagnostics.report(coreLikeOp, [name](Operation* op) {
|
||||
op->emitError() << "contains removed PIM local-memory planning attribute '" << name << "'";
|
||||
op->emitError() << "contains removed Pim local-memory planning attribute '" << name << "'";
|
||||
});
|
||||
hasFailure = true;
|
||||
}
|
||||
@@ -137,7 +139,7 @@ verifyLocalMemoryPlan(Operation* coreLikeOp, pim::CappedDiagnosticReporter& diag
|
||||
auto analyzed = pim::analyzeLocalMemoryLifetimes(coreLikeOp);
|
||||
if (failed(analyzed)) {
|
||||
diagnostics.report(coreLikeOp, [](Operation* op) {
|
||||
op->emitError("cannot analyze PIM local-memory lifetimes for plan verification");
|
||||
op->emitError("cannot analyze Pim local-memory lifetimes for plan verification");
|
||||
});
|
||||
return failure();
|
||||
}
|
||||
@@ -156,7 +158,7 @@ verifyLocalMemoryPlan(Operation* coreLikeOp, pim::CappedDiagnosticReporter& diag
|
||||
for (StringRef name : kRemovedLocalMemoryPlanAttrNames)
|
||||
if (allocation->hasAttr(name)) {
|
||||
diagnostics.report(allocation, [name](Operation* op) {
|
||||
op->emitOpError() << "contains removed PIM local-memory planning attribute '" << name << "'";
|
||||
op->emitOpError() << "contains removed Pim local-memory planning attribute '" << name << "'";
|
||||
});
|
||||
hasFailure = true;
|
||||
}
|
||||
@@ -171,7 +173,7 @@ verifyLocalMemoryPlan(Operation* coreLikeOp, pim::CappedDiagnosticReporter& diag
|
||||
uint64_t address = static_cast<uint64_t>(addressAttr.getInt());
|
||||
if (address % 4 != 0 || address > arenaSize || interval.size > arenaSize - address) {
|
||||
diagnostics.report(allocation, [&](Operation* op) {
|
||||
op->emitOpError() << "has invalid PIM local-memory range [" << address << ", "
|
||||
op->emitOpError() << "has invalid Pim local-memory range [" << address << ", "
|
||||
<< (address <= arenaSize && interval.size <= arenaSize - address
|
||||
? address + interval.size
|
||||
: arenaSize)
|
||||
@@ -221,7 +223,7 @@ verifyLocalMemoryPlan(Operation* coreLikeOp, pim::CappedDiagnosticReporter& diag
|
||||
memref::AllocOp otherAllocation = other.allocation;
|
||||
diagnostics.report(allocation, [&](Operation*) {
|
||||
auto diagnostic = allocation.emitOpError()
|
||||
<< "PIM local-memory plan assigns simultaneously live allocations to overlapping ranges; first range ["
|
||||
<< "Pim local-memory plan assigns simultaneously live allocations to overlapping ranges; first range ["
|
||||
<< conflicting->first << ", " << conflicting->first + other.size << "), second range [" << address
|
||||
<< ", " << address + interval.size << "), live positions overlap at ["
|
||||
<< std::max(interval.start, other.start) << ", " << std::min(interval.end, other.end) << "]";
|
||||
@@ -278,7 +280,9 @@ static bool isHostAddressableValue(Value value, const StaticValueKnowledge& know
|
||||
|
||||
enum class CommunicationEventKind {
|
||||
Send,
|
||||
Receive
|
||||
Receive,
|
||||
Sync,
|
||||
Wait
|
||||
};
|
||||
|
||||
struct CommunicationEvent {
|
||||
@@ -286,14 +290,25 @@ struct CommunicationEvent {
|
||||
int64_t coreId = 0;
|
||||
int64_t peerCoreId = 0;
|
||||
int64_t size = 0;
|
||||
int64_t eventRegister = 0;
|
||||
int64_t waitValue = 0;
|
||||
uint64_t ordinal = 0;
|
||||
Operation* op = nullptr;
|
||||
};
|
||||
|
||||
using CommunicationEventVector = SmallVector<CommunicationEvent, 0>;
|
||||
using SynchronizationEventKey = std::pair<int64_t, int64_t>;
|
||||
using SynchronizationSourceCounts =
|
||||
DenseMap<SynchronizationEventKey, DenseMap<int64_t, int64_t>>;
|
||||
|
||||
static StringRef getCommunicationEventKindName(CommunicationEventKind kind) {
|
||||
return kind == CommunicationEventKind::Send ? "send" : "receive";
|
||||
switch (kind) {
|
||||
case CommunicationEventKind::Send: return "send";
|
||||
case CommunicationEventKind::Receive: return "receive";
|
||||
case CommunicationEventKind::Sync: return "sync";
|
||||
case CommunicationEventKind::Wait: return "wait";
|
||||
}
|
||||
llvm_unreachable("unknown communication event kind");
|
||||
}
|
||||
|
||||
constexpr StringLiteral kRaptorMinChannelIdAttr = "raptor.min_channel_id";
|
||||
@@ -356,9 +371,15 @@ static std::string formatCommunicationEvent(const CommunicationEvent& event) {
|
||||
|
||||
std::string text;
|
||||
llvm::raw_string_ostream os(text);
|
||||
os << "core " << event.coreId << " " << getCommunicationEventKindName(event.kind) << " "
|
||||
<< (event.kind == CommunicationEventKind::Send ? "to" : "from") << " " << event.peerCoreId << " size "
|
||||
<< event.size << "B ordinal " << event.ordinal;
|
||||
os << "core " << event.coreId << " " << getCommunicationEventKindName(event.kind);
|
||||
if (event.kind == CommunicationEventKind::Send || event.kind == CommunicationEventKind::Receive)
|
||||
os << " " << (event.kind == CommunicationEventKind::Send ? "to" : "from") << " " << event.peerCoreId
|
||||
<< " size " << event.size << "B";
|
||||
else if (event.kind == CommunicationEventKind::Sync)
|
||||
os << " event " << event.eventRegister << " to " << event.peerCoreId;
|
||||
else
|
||||
os << " event " << event.eventRegister << " value " << event.waitValue;
|
||||
os << " ordinal " << event.ordinal;
|
||||
if (minChannelId)
|
||||
os << " min_channel " << *minChannelId;
|
||||
if (commOrder)
|
||||
@@ -383,6 +404,9 @@ static std::string formatCommunicationEvent(const CommunicationEvent& event) {
|
||||
}
|
||||
|
||||
static bool areMatchedCommunicationEvents(const CommunicationEvent& lhs, const CommunicationEvent& rhs) {
|
||||
if ((lhs.kind != CommunicationEventKind::Send && lhs.kind != CommunicationEventKind::Receive)
|
||||
|| (rhs.kind != CommunicationEventKind::Send && rhs.kind != CommunicationEventKind::Receive))
|
||||
return false;
|
||||
if (lhs.coreId != rhs.peerCoreId || lhs.peerCoreId != rhs.coreId || lhs.size != rhs.size)
|
||||
return false;
|
||||
|
||||
@@ -402,6 +426,27 @@ static void printCounterpartProbe(llvm::raw_ostream& os,
|
||||
const DenseMap<int64_t, CommunicationEventVector>& coreEvents,
|
||||
const DenseMap<int64_t, size_t>& programCounters,
|
||||
const CommunicationEvent& blockedEvent) {
|
||||
if (blockedEvent.kind == CommunicationEventKind::Wait) {
|
||||
os << " SYNC probes for " << formatCommunicationEvent(blockedEvent) << "\n";
|
||||
bool found = false;
|
||||
for (const auto& [sourceCore, events] : coreEvents) {
|
||||
size_t begin = programCounters.lookup(sourceCore);
|
||||
for (size_t index = begin; index < events.size(); ++index) {
|
||||
const CommunicationEvent& candidate = events[index];
|
||||
if (candidate.kind != CommunicationEventKind::Sync
|
||||
|| candidate.peerCoreId != blockedEvent.coreId
|
||||
|| candidate.eventRegister != blockedEvent.eventRegister)
|
||||
continue;
|
||||
os << " core " << sourceCore << " next matching SYNC at ordinal "
|
||||
<< index << " (distance +" << index - begin << ")\n";
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
os << " no remaining matching SYNC exists\n";
|
||||
return;
|
||||
}
|
||||
auto peerEventsIt = coreEvents.find(blockedEvent.peerCoreId);
|
||||
if (peerEventsIt == coreEvents.end()) {
|
||||
os << " no local stream was collected for peer core " << blockedEvent.peerCoreId << "\n";
|
||||
@@ -456,7 +501,7 @@ static void printCounterpartProbe(llvm::raw_ostream& os,
|
||||
|
||||
static CommunicationEvent makeCommunicationEvent(
|
||||
CommunicationEventKind kind, int64_t coreId, int64_t peerCoreId, int64_t size, uint64_t ordinal, Operation* op) {
|
||||
return CommunicationEvent {kind, coreId, peerCoreId, size, ordinal, op};
|
||||
return CommunicationEvent {kind, coreId, peerCoreId, size, 0, 0, ordinal, op};
|
||||
}
|
||||
|
||||
static LogicalResult appendCoreCommunicationEvents(Block& block,
|
||||
@@ -471,7 +516,7 @@ static LogicalResult appendCoreCommunicationEvents(Block& block,
|
||||
auto targetCoreId = resolveIndexValue(sendOp.getTargetCoreId(), knowledge);
|
||||
if (failed(targetCoreId)) {
|
||||
diagnostics.report(&op, [](Operation* illegalOp) {
|
||||
illegalOp->emitOpError("cannot statically resolve send target core for PIM communication deadlock check");
|
||||
illegalOp->emitOpError("cannot statically resolve send target core for Pim communication deadlock check");
|
||||
});
|
||||
return failure();
|
||||
}
|
||||
@@ -490,7 +535,7 @@ static LogicalResult appendCoreCommunicationEvents(Block& block,
|
||||
if (failed(sourceCoreId)) {
|
||||
diagnostics.report(&op, [](Operation* illegalOp) {
|
||||
illegalOp->emitOpError(
|
||||
"cannot statically resolve receive source core for PIM communication deadlock check");
|
||||
"cannot statically resolve receive source core for Pim communication deadlock check");
|
||||
});
|
||||
return failure();
|
||||
}
|
||||
@@ -504,6 +549,39 @@ static LogicalResult appendCoreCommunicationEvents(Block& block,
|
||||
return success();
|
||||
}
|
||||
|
||||
if (auto syncOp = dyn_cast<pim::PimSyncOp>(&op)) {
|
||||
auto targetCoreId = resolveIndexValue(syncOp.getTargetCoreId(), knowledge);
|
||||
auto eventRegister = resolveIndexValue(syncOp.getEventRegister(), knowledge);
|
||||
if (failed(targetCoreId) || failed(eventRegister)) {
|
||||
diagnostics.report(&op, [](Operation* illegalOp) {
|
||||
illegalOp->emitOpError(
|
||||
"cannot statically resolve SYNC operands for Pim communication deadlock check");
|
||||
});
|
||||
return failure();
|
||||
}
|
||||
events.push_back(CommunicationEvent {
|
||||
CommunicationEventKind::Sync, coreId, *targetCoreId, 0,
|
||||
*eventRegister, 0, static_cast<uint64_t>(events.size()), &op});
|
||||
return success();
|
||||
}
|
||||
|
||||
if (auto waitOp = dyn_cast<pim::PimWaitOp>(&op)) {
|
||||
auto eventRegister = resolveIndexValue(waitOp.getEventRegister(), knowledge);
|
||||
auto waitValue = resolveIndexValue(waitOp.getWaitValue(), knowledge);
|
||||
if (failed(eventRegister) || failed(waitValue)) {
|
||||
diagnostics.report(&op, [](Operation* illegalOp) {
|
||||
illegalOp->emitOpError(
|
||||
"cannot statically resolve WAIT operands for Pim communication deadlock check");
|
||||
});
|
||||
return failure();
|
||||
}
|
||||
events.push_back(CommunicationEvent {
|
||||
CommunicationEventKind::Wait, coreId, coreId, 0,
|
||||
*eventRegister, *waitValue,
|
||||
static_cast<uint64_t>(events.size()), &op});
|
||||
return success();
|
||||
}
|
||||
|
||||
return success();
|
||||
});
|
||||
}
|
||||
@@ -530,7 +608,7 @@ static void printCommunicationWindow(llvm::raw_ostream& os,
|
||||
static void printCommunicationDeadlockReport(const DenseMap<int64_t, CommunicationEventVector>& coreEvents,
|
||||
const DenseMap<int64_t, size_t>& programCounters,
|
||||
ArrayRef<int64_t> cycle) {
|
||||
llvm::errs() << "\n=== PIM static communication deadlock report ===\n";
|
||||
llvm::errs() << "\n=== Pim static communication/synchronization deadlock report ===\n";
|
||||
llvm::errs() << "wait cycle:";
|
||||
for (int64_t coreId : cycle)
|
||||
llvm::errs() << " " << coreId;
|
||||
@@ -565,7 +643,7 @@ static void printCommunicationDeadlockReport(const DenseMap<int64_t, Communicati
|
||||
continue;
|
||||
printCommunicationWindow(llvm::errs(), coreEvents, coreId, pcIt->second);
|
||||
}
|
||||
llvm::errs() << "=== end PIM static communication deadlock report ===\n\n";
|
||||
llvm::errs() << "=== end Pim static communication/synchronization deadlock report ===\n\n";
|
||||
}
|
||||
|
||||
static void emitCommunicationDeadlockCycle(ModuleOp moduleOp,
|
||||
@@ -576,8 +654,8 @@ static void emitCommunicationDeadlockCycle(ModuleOp moduleOp,
|
||||
|
||||
auto diagnostic =
|
||||
moduleOp.emitError()
|
||||
<< "PIM communication deadlock check found a blocking send/receive cycle while statically simulating the "
|
||||
"expanded per-core communication streams; see the PIM static communication deadlock report above";
|
||||
<< "Pim communication deadlock check found a blocking SEND/RECV/WAIT cycle while statically simulating the "
|
||||
"expanded per-core communication streams; see the static deadlock report above";
|
||||
|
||||
for (int64_t coreId : cycle) {
|
||||
auto eventsIt = coreEvents.find(coreId);
|
||||
@@ -596,46 +674,82 @@ static void emitCommunicationDeadlockCycle(ModuleOp moduleOp,
|
||||
|
||||
static FailureOr<SmallVector<int64_t>>
|
||||
findCommunicationWaitCycle(const DenseMap<int64_t, CommunicationEventVector>& coreEvents,
|
||||
const DenseMap<int64_t, size_t>& programCounters) {
|
||||
for (const auto& [startCoreId, events] : coreEvents) {
|
||||
auto startPcIt = programCounters.find(startCoreId);
|
||||
if (startPcIt == programCounters.end() || startPcIt->second >= events.size())
|
||||
const DenseMap<int64_t, size_t>& programCounters,
|
||||
const DenseSet<int64_t>& repeatingCores,
|
||||
const SynchronizationSourceCounts& sourceCounts) {
|
||||
DenseMap<int64_t, SmallVector<int64_t>> dependencies;
|
||||
for (const auto& [coreId, events] : coreEvents) {
|
||||
size_t pc = programCounters.lookup(coreId);
|
||||
if (pc >= events.size())
|
||||
continue;
|
||||
const CommunicationEvent& event = events[pc];
|
||||
if (event.kind == CommunicationEventKind::Send
|
||||
|| event.kind == CommunicationEventKind::Receive) {
|
||||
dependencies[coreId].push_back(event.peerCoreId);
|
||||
continue;
|
||||
}
|
||||
if (event.kind != CommunicationEventKind::Wait)
|
||||
continue;
|
||||
int64_t blockedCoreId = coreId;
|
||||
SynchronizationEventKey eventKey {coreId, event.eventRegister};
|
||||
auto contributions = sourceCounts.find(eventKey);
|
||||
for (const auto& [sourceCore, sourceEvents] : coreEvents) {
|
||||
size_t sourcePc = programCounters.lookup(sourceCore);
|
||||
auto matches = [&](const CommunicationEvent& candidate) {
|
||||
return candidate.kind == CommunicationEventKind::Sync
|
||||
&& candidate.peerCoreId == blockedCoreId
|
||||
&& candidate.eventRegister == event.eventRegister;
|
||||
};
|
||||
int64_t signalsPerPhase = llvm::count_if(sourceEvents, matches);
|
||||
if (repeatingCores.contains(sourceCore))
|
||||
signalsPerPhase /= 2;
|
||||
int64_t contributed = contributions == sourceCounts.end()
|
||||
? 0 : contributions->second.lookup(sourceCore);
|
||||
if (contributed >= signalsPerPhase)
|
||||
continue;
|
||||
bool canSignal = llvm::any_of(
|
||||
llvm::drop_begin(sourceEvents, sourcePc), matches);
|
||||
if (canSignal)
|
||||
dependencies[coreId].push_back(sourceCore);
|
||||
}
|
||||
}
|
||||
|
||||
DenseSet<int64_t> visited;
|
||||
DenseMap<int64_t, size_t> positionInPath;
|
||||
SmallVector<int64_t, 8> path;
|
||||
int64_t currentCoreId = startCoreId;
|
||||
while (true) {
|
||||
auto eventsIt = coreEvents.find(currentCoreId);
|
||||
auto pcIt = programCounters.find(currentCoreId);
|
||||
if (eventsIt == coreEvents.end() || pcIt == programCounters.end() || pcIt->second >= eventsIt->second.size())
|
||||
break;
|
||||
|
||||
auto positionIt = positionInPath.find(currentCoreId);
|
||||
if (positionIt != positionInPath.end()) {
|
||||
SmallVector<int64_t> cycle;
|
||||
for (size_t index = positionIt->second; index < path.size(); ++index)
|
||||
cycle.push_back(path[index]);
|
||||
std::function<std::optional<SmallVector<int64_t>>(int64_t)> visit =
|
||||
[&](int64_t coreId) -> std::optional<SmallVector<int64_t>> {
|
||||
auto position = positionInPath.find(coreId);
|
||||
if (position != positionInPath.end())
|
||||
return SmallVector<int64_t>(
|
||||
path.begin() + position->second, path.end());
|
||||
if (!visited.insert(coreId).second)
|
||||
return std::nullopt;
|
||||
positionInPath[coreId] = path.size();
|
||||
path.push_back(coreId);
|
||||
for (int64_t target : dependencies.lookup(coreId))
|
||||
if (auto cycle = visit(target))
|
||||
return cycle;
|
||||
}
|
||||
|
||||
positionInPath[currentCoreId] = path.size();
|
||||
path.push_back(currentCoreId);
|
||||
currentCoreId = eventsIt->second[pcIt->second].peerCoreId;
|
||||
}
|
||||
}
|
||||
|
||||
path.pop_back();
|
||||
positionInPath.erase(coreId);
|
||||
return std::nullopt;
|
||||
};
|
||||
for (const auto& [coreId, unused] : dependencies)
|
||||
if (auto cycle = visit(coreId))
|
||||
return *cycle;
|
||||
return failure();
|
||||
}
|
||||
|
||||
static LogicalResult verifyNoStaticCommunicationDeadlock(ModuleOp moduleOp,
|
||||
pim::CappedDiagnosticReporter& diagnostics) {
|
||||
DenseMap<int64_t, CommunicationEventVector> coreEvents;
|
||||
DenseSet<int64_t> repeatingCores;
|
||||
bool hasFailure = false;
|
||||
|
||||
for (func::FuncOp funcOp : moduleOp.getOps<func::FuncOp>()) {
|
||||
if (funcOp.isExternal())
|
||||
continue;
|
||||
bool repeating = funcOp->hasAttr("pim.pipeline_host_buffer_bytes");
|
||||
|
||||
for (Operation& op : funcOp.getBody().front().getOperations()) {
|
||||
if (auto coreOp = dyn_cast<pim::PimCoreOp>(&op)) {
|
||||
@@ -648,6 +762,8 @@ static LogicalResult verifyNoStaticCommunicationDeadlock(ModuleOp moduleOp,
|
||||
coreEvents[coreId],
|
||||
diagnostics)))
|
||||
hasFailure = true;
|
||||
if (repeating)
|
||||
repeatingCores.insert(coreId);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -668,8 +784,11 @@ static LogicalResult verifyNoStaticCommunicationDeadlock(ModuleOp moduleOp,
|
||||
coreId,
|
||||
laneKnowledge,
|
||||
coreEvents[coreId],
|
||||
diagnostics)))
|
||||
diagnostics))) {
|
||||
hasFailure = true;
|
||||
} else if (repeating) {
|
||||
repeatingCores.insert(coreId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -678,10 +797,20 @@ static LogicalResult verifyNoStaticCommunicationDeadlock(ModuleOp moduleOp,
|
||||
if (hasFailure)
|
||||
return failure();
|
||||
|
||||
for (int64_t coreId : repeatingCores) {
|
||||
CommunicationEventVector iteration = coreEvents[coreId];
|
||||
for (CommunicationEvent event : iteration) {
|
||||
event.ordinal = coreEvents[coreId].size();
|
||||
coreEvents[coreId].push_back(event);
|
||||
}
|
||||
}
|
||||
|
||||
DenseMap<int64_t, size_t> programCounters;
|
||||
for (const auto& [coreId, events] : coreEvents)
|
||||
programCounters[coreId] = 0;
|
||||
|
||||
DenseMap<SynchronizationEventKey, int64_t> eventCounts;
|
||||
SynchronizationSourceCounts sourceCounts;
|
||||
while (true) {
|
||||
bool madeProgress = false;
|
||||
for (const auto& [coreId, events] : coreEvents) {
|
||||
@@ -690,6 +819,35 @@ static LogicalResult verifyNoStaticCommunicationDeadlock(ModuleOp moduleOp,
|
||||
continue;
|
||||
|
||||
const CommunicationEvent& event = events[pc];
|
||||
if (event.kind == CommunicationEventKind::Sync) {
|
||||
SynchronizationEventKey eventKey {
|
||||
event.peerCoreId, event.eventRegister};
|
||||
++eventCounts[eventKey];
|
||||
++sourceCounts[eventKey][coreId];
|
||||
++programCounters[coreId];
|
||||
madeProgress = true;
|
||||
continue;
|
||||
}
|
||||
if (event.kind == CommunicationEventKind::Wait) {
|
||||
int64_t observed = eventCounts.lookup(
|
||||
{coreId, event.eventRegister});
|
||||
if (observed == event.waitValue) {
|
||||
SynchronizationEventKey eventKey {coreId, event.eventRegister};
|
||||
eventCounts[eventKey] = 0;
|
||||
sourceCounts.erase(eventKey);
|
||||
++programCounters[coreId];
|
||||
madeProgress = true;
|
||||
} else if (observed > event.waitValue) {
|
||||
auto diagnostic = event.op->emitOpError()
|
||||
<< "Pim synchronization deadlock check found exact-count WAIT overshoot on core "
|
||||
<< coreId << " event " << event.eventRegister << ": expected "
|
||||
<< event.waitValue << ", observed " << observed;
|
||||
diagnostic.attachNote()
|
||||
<< "WAIT requires exact equality and this event cannot decrease without a successful WAIT";
|
||||
return failure();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
auto peerEventsIt = coreEvents.find(event.peerCoreId);
|
||||
if (peerEventsIt == coreEvents.end())
|
||||
continue;
|
||||
@@ -720,7 +878,8 @@ static LogicalResult verifyNoStaticCommunicationDeadlock(ModuleOp moduleOp,
|
||||
if (allDone)
|
||||
return success();
|
||||
|
||||
auto cycle = findCommunicationWaitCycle(coreEvents, programCounters);
|
||||
auto cycle = findCommunicationWaitCycle(
|
||||
coreEvents, programCounters, repeatingCores, sourceCounts);
|
||||
if (succeeded(cycle)) {
|
||||
emitCommunicationDeadlockCycle(moduleOp, coreEvents, programCounters, *cycle);
|
||||
return failure();
|
||||
@@ -728,8 +887,8 @@ static LogicalResult verifyNoStaticCommunicationDeadlock(ModuleOp moduleOp,
|
||||
|
||||
auto diagnostic =
|
||||
moduleOp.emitError()
|
||||
<< "PIM communication deadlock check stalled without finding a closed wait cycle; this usually means a "
|
||||
"send/receive peer is missing or ordered after a finished core";
|
||||
<< "Pim communication deadlock check stalled without finding a closed wait cycle; this usually means a "
|
||||
"SEND/RECV peer or exact-count SYNC signal is missing or ordered after a finished core";
|
||||
for (const auto& [coreId, events] : coreEvents) {
|
||||
size_t pc = programCounters[coreId];
|
||||
if (pc >= events.size())
|
||||
@@ -746,7 +905,7 @@ struct VerificationPass : PassWrapper<VerificationPass, OperationPass<ModuleOp>>
|
||||
|
||||
StringRef getArgument() const override { return "verify-pim-pass"; }
|
||||
StringRef getDescription() const override {
|
||||
return "Verify that bufferized PIM IR contains only explicit host/device transfers";
|
||||
return "Verify that bufferized Pim IR contains only explicit host/device transfers";
|
||||
}
|
||||
|
||||
VerificationPass() {}
|
||||
@@ -763,7 +922,7 @@ struct VerificationPass : PassWrapper<VerificationPass, OperationPass<ModuleOp>>
|
||||
pim::CappedDiagnosticReporter diagnostics;
|
||||
|
||||
if (!hasTarget || failed(targetResources.verify())) {
|
||||
moduleOp.emitError("PIM codegen verification requires valid injected target resources");
|
||||
moduleOp.emitError("Pim codegen verification requires valid injected target resources");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
@@ -792,7 +951,7 @@ struct VerificationPass : PassWrapper<VerificationPass, OperationPass<ModuleOp>>
|
||||
return;
|
||||
|
||||
diagnostics.report(op, [](Operation* illegalOp) {
|
||||
illegalOp->emitError("illegal Spatial operation reached PIM codegen verification");
|
||||
illegalOp->emitError("illegal Spatial operation reached Pim codegen verification");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -833,7 +992,7 @@ struct VerificationPass : PassWrapper<VerificationPass, OperationPass<ModuleOp>>
|
||||
|
||||
if (!isAddressOnlyHostOp(&op)) {
|
||||
diagnostics.report(&op, [](Operation* illegalOp) {
|
||||
illegalOp->emitOpError("illegal host-side runtime op remains after PIM bufferization; "
|
||||
illegalOp->emitOpError("illegal host-side runtime op remains after Pim bufferization; "
|
||||
"fold it to constants or lower it into pim.core");
|
||||
});
|
||||
continue;
|
||||
@@ -849,7 +1008,7 @@ struct VerificationPass : PassWrapper<VerificationPass, OperationPass<ModuleOp>>
|
||||
|
||||
if (diagnostics.hasFailure()) {
|
||||
diagnostics.emitSuppressedSummary(moduleOp, "verification failures");
|
||||
moduleOp.emitError("PIM codegen verification failed; see diagnostics above");
|
||||
moduleOp.emitError("Pim codegen verification failed; see diagnostics above");
|
||||
hasFailure = true;
|
||||
}
|
||||
|
||||
@@ -928,7 +1087,7 @@ private:
|
||||
bool hasFailure = false;
|
||||
if (!isSupportedCoreInstructionOp(&op)) {
|
||||
diagnostics.report(&op, [](Operation* illegalOp) {
|
||||
illegalOp->emitOpError("unsupported executable op reached PIM codegen verification");
|
||||
illegalOp->emitOpError("unsupported executable op reached Pim codegen verification");
|
||||
});
|
||||
hasFailure = true;
|
||||
}
|
||||
@@ -990,7 +1149,7 @@ private:
|
||||
if (failed(resolveIndexValue(storeOp.getHostTargetOffset(), knowledge))
|
||||
|| failed(resolveIndexValue(storeOp.getDeviceSourceOffset(), knowledge))) {
|
||||
diagnostics.report(&op, [](Operation* illegalOp) {
|
||||
illegalOp->emitOpError("offset operands must be statically evaluable for PIM codegen");
|
||||
illegalOp->emitOpError("offset operands must be statically evaluable for Pim codegen");
|
||||
});
|
||||
hasFailure = true;
|
||||
}
|
||||
@@ -1006,7 +1165,7 @@ private:
|
||||
if (failed(resolveIndexValue(loadOp.getDeviceTargetOffset(), knowledge))
|
||||
|| failed(resolveIndexValue(loadOp.getHostSourceOffset(), knowledge))) {
|
||||
diagnostics.report(&op, [](Operation* illegalOp) {
|
||||
illegalOp->emitOpError("offset operands must be statically evaluable for PIM codegen");
|
||||
illegalOp->emitOpError("offset operands must be statically evaluable for Pim codegen");
|
||||
});
|
||||
hasFailure = true;
|
||||
}
|
||||
@@ -1022,7 +1181,7 @@ private:
|
||||
if (failed(resolveIndexValue(copyOp.getTargetOffset(), knowledge))
|
||||
|| failed(resolveIndexValue(copyOp.getSourceOffset(), knowledge))) {
|
||||
diagnostics.report(&op, [](Operation* illegalOp) {
|
||||
illegalOp->emitOpError("offset operands must be statically evaluable for PIM codegen");
|
||||
illegalOp->emitOpError("offset operands must be statically evaluable for Pim codegen");
|
||||
});
|
||||
hasFailure = true;
|
||||
}
|
||||
@@ -1032,7 +1191,7 @@ private:
|
||||
&& failed(resolveIndexValue(receiveOp.getOutputOffset(), knowledge))) {
|
||||
diagnostics.report(&op, [](Operation* illegalOp) {
|
||||
illegalOp->emitOpError(
|
||||
"output offset must be statically evaluable for PIM codegen");
|
||||
"output offset must be statically evaluable for Pim codegen");
|
||||
});
|
||||
hasFailure = true;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ include "mlir/Dialect/Bufferization/IR/BufferViewFlowOpInterface.td"
|
||||
|
||||
def PimDialect : Dialect {
|
||||
let name = "pim";
|
||||
let summary = "A low-level dialect for the PIM coprocessors on ReRAM crossbars";
|
||||
let summary = "A low-level dialect for the Pim coprocessors on ReRAM crossbars";
|
||||
let cppNamespace = "::onnx_mlir::pim";
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ def PimTensor :
|
||||
|
||||
def PimCoreOp : PimOp<"core", [SingleBlock,
|
||||
DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmBlockArgumentNames"]>]> {
|
||||
let summary = "Execute a block on a PIM core";
|
||||
let summary = "Execute a block on a Pim core";
|
||||
|
||||
let regions = (region SizedRegion<1>:$body);
|
||||
|
||||
|
||||
+167
-40
@@ -2,6 +2,8 @@
|
||||
#include "DeferredCommunicationScheduling.hpp"
|
||||
#include "DeferredTransferPlanning.hpp"
|
||||
|
||||
#include "llvm/ADT/DenseSet.h"
|
||||
|
||||
namespace onnx_mlir::spatial {
|
||||
using namespace mlir;
|
||||
namespace {
|
||||
@@ -256,7 +258,7 @@ static unsigned getBarrierRoundCount(size_t coreCount) {
|
||||
|
||||
static LogicalResult assignPipelineSynchronization(
|
||||
DeferredTransferPlan &transfers,
|
||||
ArrayRef<BoundaryProgram> boundaries,
|
||||
MutableArrayRef<BoundaryProgram> boundaries,
|
||||
size_t synchronizationRegisterCount) {
|
||||
bool pipelined = false;
|
||||
for (ScheduledInfo &scheduled : transfers.scheduled) {
|
||||
@@ -284,14 +286,13 @@ static LogicalResult assignPipelineSynchronization(
|
||||
});
|
||||
|
||||
DenseMap<int64_t, SmallVector<HostTransferRef>> incomingByCore;
|
||||
DenseMap<int64_t, llvm::SmallSetVector<int64_t, 4>> readersByWriter;
|
||||
DenseMap<ExternalTransferFamily *, SmallVector<int64_t>> eventRegisters;
|
||||
DenseMap<ExternalTransferFamily *, SmallVector<int64_t>> waitValues;
|
||||
DenseMap<ExternalTransferFamily *, SmallVector<int64_t>> acknowledgementRegisters;
|
||||
auto initialize = [&](ExternalTransferFamily &family) {
|
||||
size_t count = family.targetCores.size();
|
||||
eventRegisters.try_emplace(&family, count, 0);
|
||||
waitValues.try_emplace(&family, count, 0);
|
||||
acknowledgementRegisters.try_emplace(&family, count, 0);
|
||||
};
|
||||
for (const BoundaryProgram &boundary : boundaries)
|
||||
for (const BoundaryInstruction &instruction : boundary.instructions) {
|
||||
@@ -307,32 +308,117 @@ static LogicalResult assignPipelineSynchronization(
|
||||
int64_t source = family.sourceCores.valueAt(index);
|
||||
int64_t target = family.targetCores.valueAt(index);
|
||||
incomingByCore[target].push_back({&family, index});
|
||||
++transfers.hostAcknowledgementCounts[source];
|
||||
readersByWriter[source].insert(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DenseMap<int64_t, SmallVector<SmallVector<int64_t>>>
|
||||
overwriteBoundaryReadersByWriter;
|
||||
DenseSet<std::pair<int64_t, int64_t>> plannedRelations;
|
||||
for (const BoundaryProgram &boundary : boundaries) {
|
||||
DenseMap<int64_t, llvm::SmallSetVector<int64_t, 4>> readersAtBoundary;
|
||||
for (const BoundaryInstruction &instruction : boundary.instructions) {
|
||||
auto *send = std::get_if<EmitSendRun>(&instruction);
|
||||
if (!send || send->slices.empty()
|
||||
|| !send->slices.front().family->hostRouted)
|
||||
continue;
|
||||
for (const ScheduledTransferSlice &slice : send->slices)
|
||||
for (size_t offset = 0; offset < slice.transferCount; ++offset) {
|
||||
size_t index = slice.familyOffset + offset;
|
||||
int64_t writer = slice.family->sourceCores.valueAt(index);
|
||||
int64_t reader = slice.family->targetCores.valueAt(index);
|
||||
if (plannedRelations.insert({writer, reader}).second)
|
||||
readersAtBoundary[writer].insert(reader);
|
||||
}
|
||||
}
|
||||
for (const auto &[writer, readers] : readersAtBoundary)
|
||||
overwriteBoundaryReadersByWriter[writer].emplace_back(
|
||||
readers.begin(), readers.end());
|
||||
}
|
||||
|
||||
unsigned barrierRounds = getBarrierRoundCount(
|
||||
transfers.stageZeroCores.size());
|
||||
bool stageZeroNeedsAcknowledgements = llvm::any_of(
|
||||
transfers.stageZeroCores, [&](int64_t core) {
|
||||
return transfers.hostAcknowledgementCounts.contains(core);
|
||||
});
|
||||
for (auto &[target, incoming] : incomingByCore) {
|
||||
bool needsAcknowledgementRegister =
|
||||
transfers.hostAcknowledgementCounts.contains(target);
|
||||
bool stageZero = llvm::is_contained(transfers.stageZeroCores, target);
|
||||
size_t reserved = stageZero
|
||||
? barrierRounds + (stageZeroNeedsAcknowledgements ? 1 : 0)
|
||||
: 1 + (needsAcknowledgementRegister ? 1 : 0);
|
||||
if (reserved >= synchronizationRegisterCount) {
|
||||
incoming.front().family->requirement->exchange->deferred.emitOpError(
|
||||
"pipeline synchronization leaves no event register for incoming host transfers");
|
||||
auto reservedRegisterCount = [&](int64_t core) -> size_t {
|
||||
if (llvm::is_contained(transfers.stageZeroCores, core)) {
|
||||
bool releasesDownstream = !transfers.downstreamCores.empty()
|
||||
&& core == transfers.stageZeroCores.front();
|
||||
return barrierRounds + releasesDownstream;
|
||||
}
|
||||
auto downstream = llvm::find(transfers.downstreamCores, core);
|
||||
if (downstream == transfers.downstreamCores.end())
|
||||
return 0;
|
||||
size_t rank = downstream - transfers.downstreamCores.begin();
|
||||
bool releasesChildren = 2 * rank + 1 < transfers.downstreamCores.size();
|
||||
return 1 + releasesChildren;
|
||||
};
|
||||
for (int64_t core : transfers.stageZeroCores)
|
||||
if (reservedRegisterCount(core) > synchronizationRegisterCount)
|
||||
return transfers.scheduled.front().op->emitOpError(
|
||||
"pipeline stage-zero synchronization requires more event registers than the target provides");
|
||||
for (int64_t core : transfers.downstreamCores)
|
||||
if (reservedRegisterCount(core) > synchronizationRegisterCount)
|
||||
return transfers.scheduled.front().op->emitOpError(
|
||||
"pipeline downstream restart synchronization requires more event registers than the target provides");
|
||||
SmallVector<int64_t> synchronizationCores;
|
||||
for (const auto &[core, incoming] : incomingByCore)
|
||||
synchronizationCores.push_back(core);
|
||||
for (const auto &[core, readers] : readersByWriter)
|
||||
if (!llvm::is_contained(synchronizationCores, core))
|
||||
synchronizationCores.push_back(core);
|
||||
llvm::sort(synchronizationCores);
|
||||
|
||||
DenseMap<int64_t, SmallVector<unsigned>> freeRegisters;
|
||||
DenseMap<int64_t, SmallVector<unsigned>> freeWaitCounts;
|
||||
DenseMap<int64_t, DenseMap<int64_t, unsigned>> freeGroups;
|
||||
for (int64_t core : synchronizationCores) {
|
||||
auto incomingIt = incomingByCore.find(core);
|
||||
ArrayRef<HostTransferRef> incoming = incomingIt == incomingByCore.end()
|
||||
? ArrayRef<HostTransferRef>() : ArrayRef(incomingIt->second);
|
||||
size_t readerCount = readersByWriter.lookup(core).size();
|
||||
bool needsFreeRegister = readerCount != 0;
|
||||
size_t reserved = reservedRegisterCount(core);
|
||||
if (reserved + needsFreeRegister > synchronizationRegisterCount
|
||||
|| (!incoming.empty()
|
||||
&& reserved + needsFreeRegister == synchronizationRegisterCount)) {
|
||||
transfers.scheduled.front().op->emitOpError(
|
||||
"pipeline synchronization leaves no event register for host communication");
|
||||
return failure();
|
||||
}
|
||||
size_t groupCount = std::min(
|
||||
incoming.size(), synchronizationRegisterCount - reserved);
|
||||
// One wait consumes a complete consecutive group of producer signals.
|
||||
size_t available = synchronizationRegisterCount - reserved;
|
||||
size_t readyRegisterCount = std::min(
|
||||
incoming.size(), available - needsFreeRegister);
|
||||
auto overwriteBoundaries = overwriteBoundaryReadersByWriter.find(core);
|
||||
size_t overwriteBoundaryCount = overwriteBoundaries
|
||||
== overwriteBoundaryReadersByWriter.end()
|
||||
? 0 : overwriteBoundaries->second.size();
|
||||
size_t freeGroupCount = std::min(
|
||||
overwriteBoundaryCount, available - readyRegisterCount);
|
||||
if (needsFreeRegister) {
|
||||
size_t overwriteReaderCount = 0;
|
||||
if (overwriteBoundaries != overwriteBoundaryReadersByWriter.end())
|
||||
for (ArrayRef<int64_t> readers : overwriteBoundaries->second)
|
||||
overwriteReaderCount += readers.size();
|
||||
if (overwriteReaderCount != readerCount)
|
||||
return transfers.scheduled.front().op->emitOpError(
|
||||
"host reuse readers do not match writer overwrite relations");
|
||||
freeRegisters[core].reserve(freeGroupCount);
|
||||
freeWaitCounts[core].assign(freeGroupCount, 0);
|
||||
for (unsigned group = 0; group < freeGroupCount; ++group)
|
||||
freeRegisters[core].push_back(readyRegisterCount + group);
|
||||
for (auto [ordinal, readers] :
|
||||
llvm::enumerate(overwriteBoundaries->second)) {
|
||||
unsigned group = ordinal * freeGroupCount / overwriteBoundaryCount;
|
||||
for (int64_t reader : readers) {
|
||||
freeGroups[core][reader] = group;
|
||||
++freeWaitCounts[core][group];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (incoming.empty())
|
||||
continue;
|
||||
|
||||
size_t groupCount = readyRegisterCount;
|
||||
SmallVector<size_t> groupSizes(groupCount);
|
||||
for (size_t ordinal = 0; ordinal < incoming.size(); ++ordinal)
|
||||
++groupSizes[ordinal * groupCount / incoming.size()];
|
||||
@@ -341,8 +427,6 @@ static LogicalResult assignPipelineSynchronization(
|
||||
size_t group = ordinal * groupCount / incoming.size();
|
||||
HostTransferRef transfer = incoming[ordinal];
|
||||
eventRegisters[transfer.family][transfer.index] = group;
|
||||
acknowledgementRegisters[transfer.family][transfer.index] =
|
||||
synchronizationRegisterCount - 1;
|
||||
if (first[group]) {
|
||||
waitValues[transfer.family][transfer.index] = groupSizes[group];
|
||||
first[group] = false;
|
||||
@@ -352,27 +436,70 @@ static LogicalResult assignPipelineSynchronization(
|
||||
for (auto &[family, values] : eventRegisters) {
|
||||
family->eventRegisters = StaticIntSequence::fromValues(values);
|
||||
family->waitValues = StaticIntSequence::fromValues(waitValues[family]);
|
||||
family->acknowledgementEventRegisters =
|
||||
StaticIntSequence::fromValues(acknowledgementRegisters[family]);
|
||||
}
|
||||
|
||||
if (!transfers.stageZeroCores.empty()) {
|
||||
size_t reserved = barrierRounds
|
||||
+ (stageZeroNeedsAcknowledgements ? 1 : 0);
|
||||
if (reserved > synchronizationRegisterCount)
|
||||
return transfers.scheduled.front().op->emitOpError(
|
||||
"pipeline stage-zero barrier requires more synchronization registers than the target provides");
|
||||
SmallVector<int64_t> writers;
|
||||
for (const auto &[writer, readers] : readersByWriter)
|
||||
writers.push_back(writer);
|
||||
llvm::sort(writers);
|
||||
for (int64_t writer : writers)
|
||||
for (int64_t reader : readersByWriter[writer]) {
|
||||
unsigned group = freeGroups[writer].lookup(reader);
|
||||
transfers.hostReleaseSignals[reader].push_back(
|
||||
{writer, freeRegisters[writer][group]});
|
||||
}
|
||||
if (!transfers.downstreamCores.empty()) {
|
||||
bool needsAcknowledgements = llvm::any_of(
|
||||
transfers.downstreamCores, [&](int64_t core) {
|
||||
return transfers.hostAcknowledgementCounts.contains(core);
|
||||
});
|
||||
if (1 + (needsAcknowledgements ? 1 : 0)
|
||||
> synchronizationRegisterCount)
|
||||
return transfers.scheduled.front().op->emitOpError(
|
||||
"pipeline stage-zero release requires more synchronization registers than the target provides");
|
||||
|
||||
DenseSet<std::pair<int64_t, unsigned>> pendingGroups;
|
||||
for (int64_t writer : writers)
|
||||
for (unsigned group = 0; group < freeRegisters[writer].size(); ++group)
|
||||
pendingGroups.insert({writer, group});
|
||||
for (BoundaryProgram &boundary : boundaries) {
|
||||
SmallVector<BoundaryInstruction, 0> instructions;
|
||||
for (BoundaryInstruction &instruction : boundary.instructions) {
|
||||
auto *send = std::get_if<EmitSendRun>(&instruction);
|
||||
SmallVector<std::pair<unsigned, unsigned>> waits;
|
||||
if (send && !send->slices.empty()
|
||||
&& send->slices.front().family->hostRouted) {
|
||||
for (const ScheduledTransferSlice &slice : send->slices) {
|
||||
ExternalTransferFamily &family = *slice.family;
|
||||
unsigned lane = family.requirement->producer->scheduledLane;
|
||||
if (lane >= boundary.key.first->cores.size())
|
||||
return family.requirement->exchange->deferred.emitOpError(
|
||||
"host reuse wait references an invalid writer lane"), failure();
|
||||
for (size_t offset = 0; offset < slice.transferCount; ++offset) {
|
||||
int64_t writer = family.sourceCores.valueAt(
|
||||
slice.familyOffset + offset);
|
||||
if (boundary.key.first->cores[lane] != writer)
|
||||
return family.requirement->exchange->deferred.emitOpError(
|
||||
"host reuse wait writer does not match its scheduled lane"),
|
||||
failure();
|
||||
int64_t reader = family.targetCores.valueAt(
|
||||
slice.familyOffset + offset);
|
||||
unsigned group = freeGroups[writer].lookup(reader);
|
||||
if (!pendingGroups.erase({writer, group}))
|
||||
continue;
|
||||
waits.push_back({lane, group});
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto [lane, group] : waits) {
|
||||
int64_t writer = boundary.key.first->cores[lane];
|
||||
SmallVector<int64_t> registers(boundary.key.first->cores.size());
|
||||
SmallVector<int64_t> counts(boundary.key.first->cores.size());
|
||||
registers[lane] = freeRegisters[writer][group];
|
||||
counts[lane] = freeWaitCounts[writer][group];
|
||||
instructions.push_back(EmitHostReuseWait {
|
||||
LaneSet::range(lane, lane + 1),
|
||||
StaticIntSequence::fromValues(registers),
|
||||
StaticIntSequence::fromValues(counts)});
|
||||
}
|
||||
instructions.push_back(std::move(instruction));
|
||||
}
|
||||
boundary.instructions = std::move(instructions);
|
||||
}
|
||||
if (!pendingGroups.empty())
|
||||
return transfers.scheduled.front().op->emitOpError(
|
||||
"host reuse permission has no writer overwrite boundary");
|
||||
return success();
|
||||
}
|
||||
|
||||
|
||||
+6
-1
@@ -34,12 +34,17 @@ struct EmitReceiveAssemblyRun {
|
||||
llvm::SmallVector<LaneSet> entryLanes;
|
||||
LaneSet lanes;
|
||||
};
|
||||
struct EmitHostReuseWait {
|
||||
LaneSet lanes;
|
||||
StaticIntSequence eventRegisters = StaticIntSequence::uniform(0, 1);
|
||||
StaticIntSequence waitValues = StaticIntSequence::uniform(0, 1);
|
||||
};
|
||||
struct ProduceDeferredResult {
|
||||
DeferredExchangePlan* exchange = nullptr;
|
||||
};
|
||||
|
||||
using BoundaryInstruction =
|
||||
std::variant<EmitSendRun, EmitLocalCollectionRun,
|
||||
std::variant<EmitSendRun, EmitHostReuseWait, EmitLocalCollectionRun,
|
||||
EmitLocalCollectionLoopRun, EmitReceiveAssemblyRun,
|
||||
ProduceDeferredResult>;
|
||||
struct BoundaryProgram {
|
||||
|
||||
+214
-103
@@ -10,6 +10,7 @@
|
||||
#include "src/Accelerators/PIM/Common/IR/StaticIntSequence.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/TensorSliceUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "llvm/ADT/DenseSet.h"
|
||||
#include <array>
|
||||
namespace onnx_mlir::spatial {
|
||||
using namespace mlir;
|
||||
@@ -23,7 +24,6 @@ struct LogicalTransferMetadataView {
|
||||
StaticIntSequenceChain hostOffsets;
|
||||
StaticIntSequenceChain eventRegisters;
|
||||
StaticIntSequenceChain waitValues;
|
||||
StaticIntSequenceChain acknowledgementEventRegisters;
|
||||
StaticIntSequenceChain targetLanes;
|
||||
StaticIntSequenceChain localOffsets;
|
||||
SmallVector<StaticIntSequenceChain> projectionOffsets;
|
||||
@@ -78,6 +78,29 @@ static FailureOr<Value> emitLaneCondition(const LaneSet &lanes, Value lane, unsi
|
||||
Value selected = active->emitLookup(context.constants.getIndex(0), lane, anchor, context.constants, context.rewriter, loc);
|
||||
return arith::CmpIOp::create(context.rewriter, loc, arith::CmpIPredicate::ne, selected, context.constants.getIndex(0)).getResult();
|
||||
}
|
||||
template <typename Emit>
|
||||
static LogicalResult emitForLanes(
|
||||
const LaneSet &active, Value lane, unsigned laneCount, Operation *anchor,
|
||||
DeferredEmissionContext &context, Location loc, Emit emit) {
|
||||
if (active.empty())
|
||||
return success();
|
||||
if (!lane) {
|
||||
if (active.contains(0))
|
||||
emit();
|
||||
return success();
|
||||
}
|
||||
auto condition = emitLaneCondition(
|
||||
active, lane, laneCount, anchor, context, loc);
|
||||
if (failed(condition))
|
||||
return failure();
|
||||
auto conditional = scf::IfOp::create(
|
||||
context.rewriter, loc, TypeRange {}, *condition, false);
|
||||
OpBuilder::InsertionGuard guard(context.rewriter);
|
||||
context.rewriter.setInsertionPoint(
|
||||
conditional.getThenRegion().front().getTerminator());
|
||||
emit();
|
||||
return success();
|
||||
}
|
||||
static void appendMetadata(const ScheduledTransferSlice &slice, LogicalTransferMetadataView &metadata) {
|
||||
ExternalTransferFamily &family = *slice.family;
|
||||
LaneInterval familyLanes = family.targetLanes.intervals().front();
|
||||
@@ -95,8 +118,6 @@ static void appendMetadata(const ScheduledTransferSlice &slice, LogicalTransferM
|
||||
metadata.eventRegisters.append(
|
||||
family.eventRegisters, familyIndex, count);
|
||||
metadata.waitValues.append(family.waitValues, familyIndex, count);
|
||||
metadata.acknowledgementEventRegisters.append(
|
||||
family.acknowledgementEventRegisters, familyIndex, count);
|
||||
}
|
||||
metadata.targetLanes.append(StaticIntSequence::affine(targetLane, 1, count));
|
||||
if (family.requirement->producerLocalOffsets)
|
||||
@@ -303,20 +324,15 @@ static FailureOr<Value> emitReceiveValue(ArrayRef<ScheduledTransferSlice> slices
|
||||
std::optional<StaticIntGrid> hostOffsets;
|
||||
std::optional<StaticIntGrid> eventRegisters;
|
||||
std::optional<StaticIntGrid> waitValues;
|
||||
std::optional<StaticIntGrid> acknowledgementEventRegisters;
|
||||
if (slices.front().family->hostRouted) {
|
||||
auto offsets = buildGrid(metadata.hostOffsets);
|
||||
auto events = buildGrid(metadata.eventRegisters);
|
||||
auto waits = buildGrid(metadata.waitValues);
|
||||
auto acknowledgements = buildGrid(
|
||||
metadata.acknowledgementEventRegisters);
|
||||
if (failed(offsets) || failed(events) || failed(waits)
|
||||
|| failed(acknowledgements))
|
||||
if (failed(offsets) || failed(events) || failed(waits))
|
||||
return failure();
|
||||
hostOffsets = std::move(*offsets);
|
||||
eventRegisters = std::move(*events);
|
||||
waitValues = std::move(*waits);
|
||||
acknowledgementEventRegisters = std::move(*acknowledgements);
|
||||
}
|
||||
Value position = lane ? lane : context.constants.getIndex(0);
|
||||
Value row = context.constants.getIndex(0);
|
||||
@@ -335,8 +351,6 @@ static FailureOr<Value> emitReceiveValue(ArrayRef<ScheduledTransferSlice> slices
|
||||
eventRegisters->emitLookup(
|
||||
row, position, anchor, context.constants, context.rewriter, anchor->getLoc()),
|
||||
waitValues->emitLookup(
|
||||
row, position, anchor, context.constants, context.rewriter, anchor->getLoc()),
|
||||
acknowledgementEventRegisters->emitLookup(
|
||||
row, position, anchor, context.constants, context.rewriter, anchor->getLoc()));
|
||||
receive = op;
|
||||
output = op.getOutput();
|
||||
@@ -406,7 +420,6 @@ static FailureOr<Value> emitReceiveAssembly(const EmitReceiveAssemblyRun &run, V
|
||||
std::optional<StaticIntGrid> hostOffsets;
|
||||
std::optional<StaticIntGrid> eventRegisters;
|
||||
std::optional<StaticIntGrid> waitValues;
|
||||
std::optional<StaticIntGrid> acknowledgementEventRegisters;
|
||||
bool hostRouted = run.slices.front().family->hostRouted;
|
||||
auto metadataByEntry = buildRectangularReceiveMetadata(run, laneCount);
|
||||
if (succeeded(metadataByEntry)) {
|
||||
@@ -424,15 +437,11 @@ static FailureOr<Value> emitReceiveAssembly(const EmitReceiveAssemblyRun &run, V
|
||||
&LogicalTransferMetadataView::eventRegisters);
|
||||
auto waits = buildRows(
|
||||
&LogicalTransferMetadataView::waitValues);
|
||||
auto acknowledgements = buildRows(
|
||||
&LogicalTransferMetadataView::acknowledgementEventRegisters);
|
||||
if (failed(offsets) || failed(events) || failed(waits)
|
||||
|| failed(acknowledgements))
|
||||
if (failed(offsets) || failed(events) || failed(waits))
|
||||
return failure();
|
||||
hostOffsets = std::move(*offsets);
|
||||
eventRegisters = std::move(*events);
|
||||
waitValues = std::move(*waits);
|
||||
acknowledgementEventRegisters = std::move(*acknowledgements);
|
||||
}
|
||||
SmallVector<StaticIntSequence> positionRows;
|
||||
for (unsigned position : run.positions)
|
||||
@@ -485,15 +494,11 @@ static FailureOr<Value> emitReceiveAssembly(const EmitReceiveAssemblyRun &run, V
|
||||
&LogicalTransferMetadataView::eventRegisters);
|
||||
auto waits = buildGrid(
|
||||
&LogicalTransferMetadataView::waitValues);
|
||||
auto acknowledgements = buildGrid(
|
||||
&LogicalTransferMetadataView::acknowledgementEventRegisters);
|
||||
if (failed(offsets) || failed(events) || failed(waits)
|
||||
|| failed(acknowledgements))
|
||||
if (failed(offsets) || failed(events) || failed(waits))
|
||||
return failure();
|
||||
hostOffsets = std::move(*offsets);
|
||||
eventRegisters = std::move(*events);
|
||||
waitValues = std::move(*waits);
|
||||
acknowledgementEventRegisters = std::move(*acknowledgements);
|
||||
}
|
||||
SmallVector<StaticIntSequence> positionColumns;
|
||||
for (const StaticIntSequenceChain &values : positionsByLane)
|
||||
@@ -527,8 +532,6 @@ static FailureOr<Value> emitReceiveAssembly(const EmitReceiveAssemblyRun &run, V
|
||||
eventRegisters->emitLookup(
|
||||
entry, runtimeLane, anchor, context.constants, context.rewriter, loc),
|
||||
waitValues->emitLookup(
|
||||
entry, runtimeLane, anchor, context.constants, context.rewriter, loc),
|
||||
acknowledgementEventRegisters->emitLookup(
|
||||
entry, runtimeLane, anchor, context.constants, context.rewriter, loc));
|
||||
receive = op;
|
||||
output = op.getOutput();
|
||||
@@ -1083,7 +1086,8 @@ static LogicalResult emitLocalCollectionUpdate(const EmitLocalCollectionRun &upd
|
||||
}
|
||||
|
||||
static FailureOr<SmallVector<Value>> emitInstructions(ArrayRef<BoundaryInstruction> instructions, Value lane, unsigned laneCount,
|
||||
ArrayRef<DeferredResultPlan> results, DeferredEmissionContext &context) {
|
||||
ArrayRef<DeferredResultPlan> results, ScheduledInfo &scheduled,
|
||||
DeferredEmissionContext &context) {
|
||||
SmallVector<Value> produced;
|
||||
for (size_t instructionIndex = 0;
|
||||
instructionIndex < instructions.size(); ++instructionIndex) {
|
||||
@@ -1108,7 +1112,22 @@ static FailureOr<SmallVector<Value>> emitInstructions(ArrayRef<BoundaryInstructi
|
||||
}
|
||||
}
|
||||
}
|
||||
if (auto send = std::get_if<EmitSendRun>(&instruction)) {
|
||||
if (auto reuse = std::get_if<EmitHostReuseWait>(&instruction)) {
|
||||
Value runtimeLane = lane ? lane : context.constants.getIndex(0);
|
||||
Location loc = scheduled.op->getLoc();
|
||||
if (failed(emitForLanes(
|
||||
reuse->lanes, lane, laneCount, scheduled.op, context, loc, [&]() {
|
||||
SpatWaitOp::create(
|
||||
context.rewriter, loc,
|
||||
emitStaticIntLookup(
|
||||
reuse->eventRegisters, runtimeLane, scheduled.op,
|
||||
context.constants, context.rewriter, loc),
|
||||
emitStaticIntLookup(
|
||||
reuse->waitValues, runtimeLane, scheduled.op,
|
||||
context.constants, context.rewriter, loc));
|
||||
})))
|
||||
return failure();
|
||||
} else if (auto send = std::get_if<EmitSendRun>(&instruction)) {
|
||||
if (failed(emitConditionalSendRun(*send, lane, laneCount, context)))
|
||||
return failure();
|
||||
} else if (auto update = std::get_if<EmitLocalCollectionRun>(&instruction)) {
|
||||
@@ -1192,7 +1211,9 @@ static LogicalResult emitBoundary(const BoundaryProgram &boundary, ArrayRef<Defe
|
||||
if (auto batch = dyn_cast<SpatScheduledComputeBatch>(boundary.key.first->op))
|
||||
lane = *batch.getLaneArgument();
|
||||
SmallVector<DeferredExchangePlan *> exchanges = getProducedExchanges(boundary.instructions);
|
||||
auto values = emitInstructions(boundary.instructions, lane, laneCount, results, context);
|
||||
auto values = emitInstructions(
|
||||
boundary.instructions, lane, laneCount, results, *boundary.key.first,
|
||||
context);
|
||||
return failed(values) ? failure() : replaceResults(exchanges, *values, replacements);
|
||||
}
|
||||
|
||||
@@ -1203,30 +1224,129 @@ static unsigned getBarrierRoundCount(size_t coreCount) {
|
||||
return rounds;
|
||||
}
|
||||
|
||||
static LogicalResult emitHostReleaseSynchronization(
|
||||
DeferredTransferPlan &transfers, DeferredEmissionContext &context) {
|
||||
struct Emission {
|
||||
HostReleaseSignal signal;
|
||||
LaneSet lanes;
|
||||
};
|
||||
DenseSet<int64_t> emittedReaders;
|
||||
for (ScheduledInfo &scheduled : transfers.scheduled) {
|
||||
SmallVector<Emission> emissions;
|
||||
for (auto [laneIndex, core] : llvm::enumerate(scheduled.cores)) {
|
||||
auto found = transfers.hostReleaseSignals.find(core);
|
||||
if (found == transfers.hostReleaseSignals.end())
|
||||
continue;
|
||||
if (!emittedReaders.insert(core).second)
|
||||
return scheduled.op->emitOpError(
|
||||
"host release reader is represented by multiple scheduled lanes");
|
||||
for (HostReleaseSignal signal : found->second) {
|
||||
auto emission = llvm::find_if(emissions, [&](const Emission &item) {
|
||||
return item.signal.writerCore == signal.writerCore
|
||||
&& item.signal.eventRegister == signal.eventRegister;
|
||||
});
|
||||
if (emission == emissions.end()) {
|
||||
emissions.push_back({signal, LaneSet::range(
|
||||
laneIndex, laneIndex + 1)});
|
||||
} else {
|
||||
emission->lanes = emission->lanes.unite(
|
||||
LaneSet::range(laneIndex, laneIndex + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (emissions.empty())
|
||||
continue;
|
||||
Block *block = scheduled.blocks.front();
|
||||
context.rewriter.setInsertionPointToStart(block);
|
||||
Location loc = scheduled.op->getLoc();
|
||||
Value lane;
|
||||
if (auto batch = dyn_cast<SpatScheduledComputeBatch>(scheduled.op))
|
||||
lane = *batch.getLaneArgument();
|
||||
for (const Emission &emission : emissions)
|
||||
if (failed(emitForLanes(
|
||||
emission.lanes, lane, scheduled.cores.size(), scheduled.op,
|
||||
context, loc, [&]() {
|
||||
SpatSyncOp::create(
|
||||
context.rewriter, loc,
|
||||
context.constants.getIndex(emission.signal.writerCore),
|
||||
context.constants.getIndex(emission.signal.eventRegister));
|
||||
})))
|
||||
return failure();
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
static LogicalResult emitRestartReleasePermissions(
|
||||
DeferredTransferPlan &transfers, DeferredEmissionContext &context) {
|
||||
if (transfers.downstreamCores.empty())
|
||||
return success();
|
||||
unsigned barrierRounds = getBarrierRoundCount(
|
||||
transfers.stageZeroCores.size());
|
||||
size_t restartRegister = transfers.synchronizationRegisterCount - 1;
|
||||
size_t leaderPermissionRegister = restartRegister - barrierRounds;
|
||||
size_t downstreamPermissionRegister = restartRegister - 1;
|
||||
DenseMap<int64_t, unsigned> downstreamRank;
|
||||
for (auto [rank, core] : llvm::enumerate(transfers.downstreamCores))
|
||||
downstreamRank[core] = rank;
|
||||
|
||||
for (ScheduledInfo &scheduled : transfers.scheduled) {
|
||||
LaneSet lanes;
|
||||
SmallVector<int64_t> targets(scheduled.cores.size());
|
||||
SmallVector<int64_t> registers(scheduled.cores.size());
|
||||
for (auto [lane, core] : llvm::enumerate(scheduled.cores)) {
|
||||
auto rank = downstreamRank.find(core);
|
||||
if (rank == downstreamRank.end())
|
||||
continue;
|
||||
lanes = lanes.unite(LaneSet::range(lane, lane + 1));
|
||||
if (rank->second == 0) {
|
||||
targets[lane] = transfers.stageZeroCores.front();
|
||||
registers[lane] = leaderPermissionRegister;
|
||||
} else {
|
||||
targets[lane] = transfers.downstreamCores[(rank->second - 1) / 2];
|
||||
registers[lane] = downstreamPermissionRegister;
|
||||
}
|
||||
}
|
||||
if (lanes.empty())
|
||||
continue;
|
||||
Block *block = scheduled.blocks.front();
|
||||
context.rewriter.setInsertionPointToStart(block);
|
||||
Location loc = scheduled.op->getLoc();
|
||||
Value lane;
|
||||
if (auto batch = dyn_cast<SpatScheduledComputeBatch>(scheduled.op))
|
||||
lane = *batch.getLaneArgument();
|
||||
Value runtimeLane = lane ? lane : context.constants.getIndex(0);
|
||||
if (failed(emitForLanes(
|
||||
lanes, lane, scheduled.cores.size(), scheduled.op, context, loc,
|
||||
[&]() {
|
||||
Value target = emitStaticIntLookup(
|
||||
StaticIntSequence::fromValues(targets), runtimeLane,
|
||||
scheduled.op, context.constants, context.rewriter, loc);
|
||||
Value eventRegister = emitStaticIntLookup(
|
||||
StaticIntSequence::fromValues(registers), runtimeLane,
|
||||
scheduled.op, context.constants, context.rewriter, loc);
|
||||
SpatSyncOp::create(
|
||||
context.rewriter, loc, target, eventRegister);
|
||||
})))
|
||||
return failure();
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
static LogicalResult emitCompletionSynchronization(
|
||||
DeferredTransferPlan &transfers, DeferredEmissionContext &context) {
|
||||
if (transfers.synchronizationRegisterCount == 0)
|
||||
return success();
|
||||
size_t acknowledgementRegister =
|
||||
size_t restartRegister =
|
||||
transfers.synchronizationRegisterCount - 1;
|
||||
unsigned barrierRounds = getBarrierRoundCount(
|
||||
transfers.stageZeroCores.size());
|
||||
bool stageZeroNeedsAcknowledgements = llvm::any_of(
|
||||
transfers.stageZeroCores, [&](int64_t core) {
|
||||
return transfers.hostAcknowledgementCounts.contains(core);
|
||||
});
|
||||
size_t firstBarrierRegister = acknowledgementRegister
|
||||
- (stageZeroNeedsAcknowledgements ? 1 : 0);
|
||||
size_t firstBarrierRegister = restartRegister;
|
||||
DenseMap<int64_t, unsigned> stageZeroRank;
|
||||
for (auto [rank, core] : llvm::enumerate(transfers.stageZeroCores))
|
||||
stageZeroRank[core] = rank;
|
||||
DenseMap<int64_t, unsigned> downstreamRank;
|
||||
for (auto [rank, core] : llvm::enumerate(transfers.downstreamCores))
|
||||
downstreamRank[core] = rank;
|
||||
auto getReleaseRegister = [&](int64_t core) {
|
||||
return acknowledgementRegister
|
||||
- (transfers.hostAcknowledgementCounts.contains(core) ? 1 : 0);
|
||||
};
|
||||
|
||||
for (ScheduledInfo &scheduled : transfers.scheduled) {
|
||||
Block *block = scheduled.blocks.front();
|
||||
@@ -1236,13 +1356,10 @@ static LogicalResult emitCompletionSynchronization(
|
||||
if (auto batch = dyn_cast<SpatScheduledComputeBatch>(scheduled.op))
|
||||
lane = *batch.getLaneArgument();
|
||||
|
||||
SmallVector<int64_t> acknowledgementCounts, releaseRegisters;
|
||||
SmallVector<int64_t> releaseWaitValues, leftTargets, leftRegisters;
|
||||
SmallVector<int64_t> rightTargets, rightRegisters;
|
||||
LaneSet barrierLanes, leaderLanes, leftLanes, rightLanes;
|
||||
SmallVector<int64_t> leftTargets, rightTargets, childCounts;
|
||||
LaneSet barrierLanes, leaderLanes, downstreamLanes, parentLanes,
|
||||
leftLanes, rightLanes;
|
||||
for (auto [index, core] : llvm::enumerate(scheduled.cores)) {
|
||||
acknowledgementCounts.push_back(
|
||||
transfers.hostAcknowledgementCounts.lookup(core));
|
||||
if (stageZeroRank.contains(core))
|
||||
barrierLanes = barrierLanes.unite(
|
||||
LaneSet::range(index, index + 1));
|
||||
@@ -1252,66 +1369,37 @@ static LogicalResult emitCompletionSynchronization(
|
||||
|
||||
auto rank = downstreamRank.find(core);
|
||||
if (rank == downstreamRank.end()) {
|
||||
releaseRegisters.push_back(0);
|
||||
releaseWaitValues.push_back(0);
|
||||
leftTargets.push_back(core);
|
||||
leftRegisters.push_back(0);
|
||||
rightTargets.push_back(core);
|
||||
rightRegisters.push_back(0);
|
||||
childCounts.push_back(0);
|
||||
continue;
|
||||
}
|
||||
releaseRegisters.push_back(getReleaseRegister(core));
|
||||
releaseWaitValues.push_back(1);
|
||||
downstreamLanes = downstreamLanes.unite(
|
||||
LaneSet::range(index, index + 1));
|
||||
size_t left = 2 * rank->second + 1;
|
||||
size_t right = left + 1;
|
||||
unsigned childCount = 0;
|
||||
if (left < transfers.downstreamCores.size()) {
|
||||
int64_t child = transfers.downstreamCores[left];
|
||||
leftTargets.push_back(child);
|
||||
leftRegisters.push_back(getReleaseRegister(child));
|
||||
leftLanes = leftLanes.unite(LaneSet::range(index, index + 1));
|
||||
++childCount;
|
||||
} else {
|
||||
leftTargets.push_back(core);
|
||||
leftRegisters.push_back(0);
|
||||
}
|
||||
if (right < transfers.downstreamCores.size()) {
|
||||
int64_t child = transfers.downstreamCores[right];
|
||||
rightTargets.push_back(child);
|
||||
rightRegisters.push_back(getReleaseRegister(child));
|
||||
rightLanes = rightLanes.unite(LaneSet::range(index, index + 1));
|
||||
++childCount;
|
||||
} else {
|
||||
rightTargets.push_back(core);
|
||||
rightRegisters.push_back(0);
|
||||
}
|
||||
childCounts.push_back(childCount);
|
||||
if (childCount != 0)
|
||||
parentLanes = parentLanes.unite(LaneSet::range(index, index + 1));
|
||||
}
|
||||
Value runtimeLane = lane ? lane : context.constants.getIndex(0);
|
||||
auto emitForLanes = [&](const LaneSet &active, auto emit) -> LogicalResult {
|
||||
if (active.empty())
|
||||
return success();
|
||||
if (!lane) {
|
||||
if (active.contains(0))
|
||||
emit();
|
||||
return success();
|
||||
}
|
||||
auto condition = emitLaneCondition(
|
||||
active, lane, scheduled.cores.size(), scheduled.op, context, loc);
|
||||
if (failed(condition))
|
||||
return failure();
|
||||
auto conditional = scf::IfOp::create(
|
||||
context.rewriter, loc, TypeRange {}, *condition, false);
|
||||
OpBuilder::InsertionGuard guard(context.rewriter);
|
||||
context.rewriter.setInsertionPoint(
|
||||
conditional.getThenRegion().front().getTerminator());
|
||||
emit();
|
||||
return success();
|
||||
};
|
||||
Value acknowledgementCount = emitStaticIntLookup(
|
||||
StaticIntSequence::fromValues(acknowledgementCounts),
|
||||
runtimeLane, scheduled.op,
|
||||
context.constants, context.rewriter, loc);
|
||||
SpatWaitOp::create(
|
||||
context.rewriter, loc,
|
||||
context.constants.getIndex(acknowledgementRegister),
|
||||
acknowledgementCount);
|
||||
|
||||
// Dissemination barrier: every round doubles the covered stage-zero peers.
|
||||
auto emitBarrier = [&]() {
|
||||
@@ -1341,45 +1429,64 @@ static LogicalResult emitCompletionSynchronization(
|
||||
}
|
||||
};
|
||||
if (barrierRounds > 0
|
||||
&& failed(emitForLanes(barrierLanes, emitBarrier)))
|
||||
&& failed(emitForLanes(
|
||||
barrierLanes, lane, scheduled.cores.size(), scheduled.op, context,
|
||||
loc, emitBarrier)))
|
||||
return failure();
|
||||
|
||||
// Gate downstream restarts so no core advances the simulator input
|
||||
// iteration ahead of stage zero.
|
||||
if (!transfers.downstreamCores.empty()
|
||||
&& failed(emitForLanes(leaderLanes, [&]() {
|
||||
&& failed(emitForLanes(
|
||||
leaderLanes, lane, scheduled.cores.size(), scheduled.op, context,
|
||||
loc, [&]() {
|
||||
SpatWaitOp::create(
|
||||
context.rewriter, loc,
|
||||
context.constants.getIndex(firstBarrierRegister - barrierRounds),
|
||||
context.constants.getIndex(1));
|
||||
int64_t root = transfers.downstreamCores.front();
|
||||
SpatSyncOp::create(
|
||||
context.rewriter, loc, context.constants.getIndex(root),
|
||||
context.constants.getIndex(getReleaseRegister(root)));
|
||||
context.constants.getIndex(restartRegister));
|
||||
})))
|
||||
return failure();
|
||||
|
||||
Value releaseRegister = emitStaticIntLookup(
|
||||
StaticIntSequence::fromValues(releaseRegisters), runtimeLane,
|
||||
scheduled.op, context.constants, context.rewriter, loc);
|
||||
Value releaseWaitValue = emitStaticIntLookup(
|
||||
StaticIntSequence::fromValues(releaseWaitValues), runtimeLane,
|
||||
if (failed(emitForLanes(
|
||||
downstreamLanes, lane, scheduled.cores.size(), scheduled.op,
|
||||
context, loc, [&]() {
|
||||
SpatWaitOp::create(
|
||||
context.rewriter, loc,
|
||||
context.constants.getIndex(restartRegister),
|
||||
context.constants.getIndex(1));
|
||||
})))
|
||||
return failure();
|
||||
|
||||
if (failed(emitForLanes(
|
||||
parentLanes, lane, scheduled.cores.size(), scheduled.op, context,
|
||||
loc, [&]() {
|
||||
Value count = emitStaticIntLookup(
|
||||
StaticIntSequence::fromValues(childCounts), runtimeLane,
|
||||
scheduled.op, context.constants, context.rewriter, loc);
|
||||
SpatWaitOp::create(
|
||||
context.rewriter, loc, releaseRegister, releaseWaitValue);
|
||||
context.rewriter, loc,
|
||||
context.constants.getIndex(restartRegister - 1), count);
|
||||
})))
|
||||
return failure();
|
||||
|
||||
auto emitChild = [&](ArrayRef<int64_t> targets,
|
||||
ArrayRef<int64_t> registers) {
|
||||
auto emitChild = [&](ArrayRef<int64_t> targets) {
|
||||
Value target = emitStaticIntLookup(
|
||||
StaticIntSequence::fromValues(targets), runtimeLane, scheduled.op,
|
||||
context.constants, context.rewriter, loc);
|
||||
Value eventRegister = emitStaticIntLookup(
|
||||
StaticIntSequence::fromValues(registers), runtimeLane, scheduled.op,
|
||||
context.constants, context.rewriter, loc);
|
||||
SpatSyncOp::create(context.rewriter, loc, target, eventRegister);
|
||||
SpatSyncOp::create(
|
||||
context.rewriter, loc, target,
|
||||
context.constants.getIndex(restartRegister));
|
||||
};
|
||||
if (failed(emitForLanes(leftLanes, [&]() {
|
||||
emitChild(leftTargets, leftRegisters);
|
||||
}))
|
||||
|| failed(emitForLanes(rightLanes, [&]() {
|
||||
emitChild(rightTargets, rightRegisters);
|
||||
})))
|
||||
if (failed(emitForLanes(
|
||||
leftLanes, lane, scheduled.cores.size(), scheduled.op, context, loc,
|
||||
[&]() { emitChild(leftTargets); }))
|
||||
|| failed(emitForLanes(
|
||||
rightLanes, lane, scheduled.cores.size(), scheduled.op, context,
|
||||
loc, [&]() { emitChild(rightTargets); })))
|
||||
return failure();
|
||||
}
|
||||
return success();
|
||||
@@ -1399,6 +1506,10 @@ LogicalResult realizeDeferredBoundaries(ArrayRef<BoundaryProgram> boundaries, Ar
|
||||
if (failed(emitBoundary(boundary, results, context, replacements)))
|
||||
return boundary.key.first->op->emitOpError("phase 2 failed to realize a communication boundary");
|
||||
}
|
||||
if (failed(emitRestartReleasePermissions(transfers, context)))
|
||||
return failure();
|
||||
if (failed(emitHostReleaseSynchronization(transfers, context)))
|
||||
return failure();
|
||||
return emitCompletionSynchronization(transfers, context);
|
||||
}
|
||||
|
||||
|
||||
-2
@@ -237,8 +237,6 @@ struct ExternalTransferFamily {
|
||||
StaticIntSequence hostOffsets = StaticIntSequence::uniform(0, 1);
|
||||
StaticIntSequence eventRegisters = StaticIntSequence::uniform(0, 1);
|
||||
StaticIntSequence waitValues = StaticIntSequence::uniform(1, 1);
|
||||
StaticIntSequence acknowledgementEventRegisters =
|
||||
StaticIntSequence::uniform(0, 1);
|
||||
bool hostRouted = false;
|
||||
};
|
||||
|
||||
|
||||
-64
@@ -6,7 +6,6 @@
|
||||
#include "DeferredCommunicationRealization.hpp"
|
||||
#include "DeferredCommunicationScheduling.hpp"
|
||||
#include "DeferredTransferPlanning.hpp"
|
||||
#include "Scheduling/PeftScheduler.hpp"
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||
|
||||
@@ -34,67 +33,6 @@ static LogicalResult verifyNoEscapingRegionValues(Operation* owner, StringRef ph
|
||||
<< escapingUser->getName() << " at " << escapingUser->getLoc();
|
||||
}
|
||||
|
||||
static LogicalResult placeLogicalProcessorsOnPhysicalCores(DeferredTransferPlan& plan, const SchedulingTarget& target) {
|
||||
std::vector<Cost> logicalTrafficFlits(target.processorCount * target.processorCount, 0);
|
||||
for (const std::unique_ptr<DeferredExchangePlan>& exchange : plan.exchanges)
|
||||
for (const ExternalTransferFamily& transfer : exchange->external) {
|
||||
auto fragmentType = dyn_cast<ShapedType>(transfer.requirement->publicationFragmentType);
|
||||
if (!fragmentType || !fragmentType.hasStaticShape())
|
||||
return exchange->deferred.emitOpError("physical core placement requires a static transfer fragment");
|
||||
auto fragmentBytes = pim::getCheckedShapedTypeSizeInBytes(
|
||||
fragmentType, exchange->deferred, "physical core placement transfer fragment");
|
||||
if (failed(fragmentBytes))
|
||||
return failure();
|
||||
Cost flits = static_cast<Cost>(*fragmentBytes) / target.transferWidthBytes
|
||||
+ (*fragmentBytes % target.transferWidthBytes != 0);
|
||||
for (size_t index = 0; index < transfer.sourceCores.size(); ++index) {
|
||||
size_t sourceLogicalProcessor = static_cast<size_t>(transfer.sourceCores.valueAt(index));
|
||||
size_t targetLogicalProcessor = static_cast<size_t>(transfer.targetCores.valueAt(index));
|
||||
Cost& traffic = logicalTrafficFlits[sourceLogicalProcessor * target.processorCount + targetLogicalProcessor];
|
||||
traffic = checkedAdd(traffic, flits);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<size_t> physicalCoreForLogicalProcessor =
|
||||
mapLogicalProcessorsToPhysicalCores(logicalTrafficFlits, target);
|
||||
auto getPhysicalCore = [&](int64_t logicalProcessor) {
|
||||
assert(logicalProcessor >= 0 && static_cast<size_t>(logicalProcessor) < physicalCoreForLogicalProcessor.size()
|
||||
&& "logical processor is outside the scheduling target");
|
||||
return static_cast<int64_t>(physicalCoreForLogicalProcessor[logicalProcessor]);
|
||||
};
|
||||
auto remap = [&](StaticIntSequence& logicalProcessors) {
|
||||
SmallVector<int64_t> physicalCores;
|
||||
physicalCores.reserve(logicalProcessors.size());
|
||||
for (size_t index = 0; index < logicalProcessors.size(); ++index)
|
||||
physicalCores.push_back(getPhysicalCore(logicalProcessors.valueAt(index)));
|
||||
logicalProcessors = StaticIntSequence::fromValues(physicalCores);
|
||||
};
|
||||
|
||||
for (ScheduledInfo& scheduled : plan.scheduled) {
|
||||
for (int64_t& logicalProcessor : scheduled.cores)
|
||||
logicalProcessor = getPhysicalCore(logicalProcessor);
|
||||
if (isa<SpatScheduledCompute>(scheduled.op)) {
|
||||
scheduled.op->setAttr(
|
||||
kCoreIdAttrName, IntegerAttr::get(IntegerType::get(scheduled.op->getContext(), 32), scheduled.cores.front()));
|
||||
}
|
||||
else {
|
||||
SmallVector<int32_t> physicalCores;
|
||||
physicalCores.reserve(scheduled.cores.size());
|
||||
for (int64_t physicalCore : scheduled.cores)
|
||||
physicalCores.push_back(static_cast<int32_t>(physicalCore));
|
||||
scheduled.op->setAttr(kCoreIdsAttrName, DenseI32ArrayAttr::get(scheduled.op->getContext(), physicalCores));
|
||||
}
|
||||
}
|
||||
for (const std::unique_ptr<ProducedValue>& produced : plan.producedStorage)
|
||||
produced->core = getPhysicalCore(produced->core);
|
||||
for (const std::unique_ptr<DeferredExchangePlan>& exchange : plan.exchanges)
|
||||
for (ExternalTransferFamily& transfer : exchange->external) {
|
||||
remap(transfer.sourceCores);
|
||||
remap(transfer.targetCores);
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
static LogicalResult replaceFinalGraphPublications(func::FuncOp funcOp, DeferredTransferPlan& plan) {
|
||||
for (Operation& op : funcOp.getOps()) {
|
||||
if (!isa<SpatGraphCompute, SpatGraphComputeBatch>(op))
|
||||
@@ -218,8 +156,6 @@ LogicalResult realizeDeferredCommunication(func::FuncOp funcOp,
|
||||
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,
|
||||
|
||||
+3
-1
@@ -285,7 +285,7 @@ FailureOr<ScheduledCommunicationPlan> scheduleDeferredCommunication(func::FuncOp
|
||||
enqueue(stream);
|
||||
auto advance = [&] {
|
||||
bool changed = false;
|
||||
while (!advanceable.empty()) {
|
||||
while (!advanceable.empty() && ready.empty()) {
|
||||
unsigned stream = advanceable.front();
|
||||
advanceable.pop();
|
||||
StreamProgress& progress = streams[stream];
|
||||
@@ -305,6 +305,8 @@ FailureOr<ScheduledCommunicationPlan> scheduleDeferredCommunication(func::FuncOp
|
||||
ScheduledCommunicationPlan result;
|
||||
unsigned finishedGroups = 0;
|
||||
while (finishedGroups != groups.size()) {
|
||||
while (!ready.empty() && groups[ready.top()].scheduled)
|
||||
ready.pop();
|
||||
bool progressed = advance();
|
||||
std::optional<unsigned> chosen;
|
||||
unsigned bestExtension = 0;
|
||||
|
||||
+10
-4
@@ -32,9 +32,11 @@ static LogicalResult collectScheduledOperations(
|
||||
DeferredTransferPlan &plan,
|
||||
size_t pipelineStageCount,
|
||||
size_t processorCount) {
|
||||
if (pipelineStageCount == 0 || processorCount % pipelineStageCount != 0)
|
||||
if (pipelineStageCount == 0
|
||||
|| (pipelineStageCount > 1
|
||||
&& materialization.processorStages.size() != processorCount))
|
||||
return failure();
|
||||
size_t stageSize = processorCount / pipelineStageCount;
|
||||
plan.processorStages = materialization.processorStages;
|
||||
unsigned nextStream = 0;
|
||||
for (const ScheduledMaterializationRecord &record :
|
||||
materialization.materializedSchedules) {
|
||||
@@ -56,8 +58,12 @@ static LogicalResult collectScheduledOperations(
|
||||
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);
|
||||
if (pipelineStageCount > 1) {
|
||||
size_t stage = materialization.processorStages[core];
|
||||
if (stage >= pipelineStageCount)
|
||||
return op.emitOpError("phase 2 scheduled core has an invalid pipeline stage");
|
||||
info.pipelineStages.push_back(stage);
|
||||
}
|
||||
}
|
||||
for (size_t lane = 0; lane < info.cores.size(); ++lane)
|
||||
info.streamIds.push_back(nextStream++);
|
||||
|
||||
+8
-1
@@ -7,13 +7,20 @@
|
||||
|
||||
namespace onnx_mlir::spatial {
|
||||
|
||||
struct HostReleaseSignal {
|
||||
int64_t writerCore = -1;
|
||||
unsigned eventRegister = 0;
|
||||
};
|
||||
|
||||
struct DeferredTransferPlan {
|
||||
std::vector<size_t> processorStages;
|
||||
llvm::SmallVector<ScheduledInfo, 0> scheduled;
|
||||
llvm::SmallVector<std::unique_ptr<ProducedValue>> producedStorage;
|
||||
llvm::DenseMap<int64_t, llvm::SmallVector<ProducedValue*>> producedByGraph;
|
||||
llvm::SmallVector<std::unique_ptr<DeferredExchangePlan>> exchanges;
|
||||
llvm::SmallVector<unsigned> stepCounts;
|
||||
llvm::DenseMap<int64_t, unsigned> hostAcknowledgementCounts;
|
||||
llvm::DenseMap<int64_t, llvm::SmallVector<HostReleaseSignal>>
|
||||
hostReleaseSignals;
|
||||
llvm::SmallVector<int64_t> stageZeroCores;
|
||||
llvm::SmallVector<int64_t> downstreamCores;
|
||||
size_t synchronizationRegisterCount = 0;
|
||||
|
||||
+3
-1
@@ -808,7 +808,9 @@ materializeScheduledCompute(func::FuncOp funcOp,
|
||||
}
|
||||
}
|
||||
|
||||
return ScheduledComputeMaterializationResult {std::move(peftClassPlans), std::move(materializedSchedules), std::move(graphComputeToBlockMap)};
|
||||
return ScheduledComputeMaterializationResult {
|
||||
std::move(peftClassPlans), std::move(materializedSchedules),
|
||||
std::move(graphComputeToBlockMap), schedule.processorStages};
|
||||
}
|
||||
|
||||
|
||||
|
||||
+1
@@ -14,6 +14,7 @@ struct ScheduledComputeMaterializationResult {
|
||||
llvm::MapVector<size_t, PeftClassPlan> peftClassPlans;
|
||||
std::vector<ScheduledMaterializationRecord> materializedSchedules;
|
||||
DenseMap<GraphComputeBlockKey, Block *> graphComputeToBlockMap;
|
||||
std::vector<size_t> processorStages;
|
||||
};
|
||||
|
||||
FailureOr<BatchFragmentSpec>
|
||||
|
||||
+10
-2
@@ -111,8 +111,16 @@ void dumpScheduledComputeReport(ModuleOp moduleOp, func::FuncOp funcOp, const Me
|
||||
<< " materialized homogeneous runs: " << materializedRuns << "\n"
|
||||
<< " largest run: " << largestRun << "\n"
|
||||
<< " instances compacted: " << instancesCompacted << "\n"
|
||||
<< " compatible runs rejected: 0\n\n"
|
||||
<< "Materialized scheduled ops\n";
|
||||
<< " compatible runs rejected: 0\n";
|
||||
llvm::MapVector<size_t, SmallVector<size_t>> stageCores;
|
||||
for (auto [core, stage] : llvm::enumerate(schedule.processorStages))
|
||||
stageCores[stage].push_back(core);
|
||||
for (const auto &[stage, cores] : stageCores) {
|
||||
os << " pipeline stage " << stage << " cores=";
|
||||
printIndexedList(os, ArrayRef<size_t>(cores));
|
||||
os << "\n";
|
||||
}
|
||||
os << "\nMaterialized scheduled ops\n";
|
||||
|
||||
for (const ScheduledMaterializationRecord &record : records) {
|
||||
bool batch = isa<SpatScheduledComputeBatch>(record.scheduledOp);
|
||||
|
||||
+22
-54
@@ -10,8 +10,6 @@
|
||||
#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 {
|
||||
@@ -28,43 +26,12 @@ static bool hasValidTarget(const SchedulingTarget& target) {
|
||||
static FailureOr<func::FuncOp> requireEntry(ModuleOp moduleOp) {
|
||||
auto entry = getPimEntryFunc(moduleOp);
|
||||
if (failed(entry)) {
|
||||
moduleOp.emitError("failed to locate the PIM entry function during Spatial scheduling and realization");
|
||||
moduleOp.emitError("failed to locate the Pim entry function during Spatial scheduling and realization");
|
||||
return failure();
|
||||
}
|
||||
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)
|
||||
@@ -88,11 +55,10 @@ struct ScheduleAndRealizeSpatialPass final
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
if (pipelineStages == 0 || target.processorCount % pipelineStages != 0
|
||||
PipelineCoreLayout pipelineLayout(target.processorCount, pipelineStages);
|
||||
if (!pipelineLayout.isValid()
|
||||
|| (pipelineStages > 1
|
||||
&& target.synchronizationRegisterCount == 0)
|
||||
|| target.residentWeightCapacity
|
||||
> std::numeric_limits<size_t>::max() / pipelineStages) {
|
||||
&& target.synchronizationRegisterCount == 0)) {
|
||||
moduleOp.emitError("ScheduleAndRealizeSpatial requires valid pipeline stages and resource counts");
|
||||
signalPassFailure();
|
||||
return;
|
||||
@@ -104,30 +70,32 @@ struct ScheduleAndRealizeSpatialPass final
|
||||
}
|
||||
func::FuncOp entryFunc = *entry;
|
||||
|
||||
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());
|
||||
scheduledGraph = buildComputeGraph(entryFunc, target);
|
||||
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;
|
||||
if (pipelineStages > 1) {
|
||||
FailureOr<PipelineWorkloadPreparation> preparation =
|
||||
preparePipelineWorkload(
|
||||
scheduledGraph, pipelineStages, target, pipelineError);
|
||||
if (failed(preparation)) {
|
||||
moduleOp.emitError() << pipelineError;
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
if (*preparation == PipelineWorkloadPreparation::Changed)
|
||||
continue;
|
||||
}
|
||||
MergeSchedulingAnalysis analysis(
|
||||
scheduledGraph, target, entryFunc.getContext());
|
||||
schedule = std::move(analysis.getResult());
|
||||
if (succeeded(applyPipelineScheduling(
|
||||
scheduledGraph, schedule, pipelineStages, target, pipelineError)))
|
||||
break;
|
||||
moduleOp.emitError() << pipelineError;
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
PatternRewriter rewriter(moduleOp.getContext());
|
||||
FailureOr<ScheduledComputeMaterializationResult> materialization =
|
||||
|
||||
+1
@@ -14,6 +14,7 @@ namespace spatial {
|
||||
|
||||
struct MergeScheduleResult {
|
||||
size_t processorCount = 0;
|
||||
std::vector<size_t> processorStages;
|
||||
std::vector<ComputeInstance> dominanceOrderCompute;
|
||||
llvm::DenseMap<ComputeInstance, size_t> computeToCpuMap;
|
||||
llvm::DenseMap<ComputeInstance, size_t> computeToCpuSlotMap;
|
||||
|
||||
+4
-5
@@ -88,22 +88,21 @@ void verifySchedule(const ComputeGraph& graph,
|
||||
|
||||
} // namespace
|
||||
|
||||
MergeSchedulingAnalysis::MergeSchedulingAnalysis(mlir::Operation* op,
|
||||
MergeSchedulingAnalysis::MergeSchedulingAnalysis(const ComputeGraph& computeGraph,
|
||||
const SchedulingTarget& schedulingTarget,
|
||||
size_t partitionCount)
|
||||
: entryOp(op), target(schedulingTarget), computePartitionCount(partitionCount) {
|
||||
mlir::MLIRContext* context)
|
||||
: context(context), target(schedulingTarget), graph(computeGraph) {
|
||||
result = run();
|
||||
}
|
||||
|
||||
MergeScheduleResult MergeSchedulingAnalysis::run() {
|
||||
graph = buildComputeGraph(entryOp, target, computePartitionCount);
|
||||
if (!verifyAcyclic(graph))
|
||||
llvm::report_fatal_error("merge scheduling: compute graph is cyclic");
|
||||
|
||||
MergeScheduleResult schedule = runPeftScheduler(
|
||||
graph, PeftScheduleOptions {
|
||||
target,
|
||||
entryOp->getContext()});
|
||||
context});
|
||||
verifySchedule(graph, schedule, target);
|
||||
return schedule;
|
||||
}
|
||||
|
||||
+4
-6
@@ -11,17 +11,15 @@ namespace spatial {
|
||||
|
||||
class MergeSchedulingAnalysis {
|
||||
public:
|
||||
MergeSchedulingAnalysis(mlir::Operation* op,
|
||||
MergeSchedulingAnalysis(const ComputeGraph& graph,
|
||||
const SchedulingTarget& target,
|
||||
size_t computePartitionCount = 0);
|
||||
mlir::MLIRContext* context);
|
||||
MergeScheduleResult& getResult() { return result; }
|
||||
const ComputeGraph& getGraph() const { return graph; }
|
||||
|
||||
private:
|
||||
mlir::Operation* entryOp = nullptr;
|
||||
mlir::MLIRContext* context = nullptr;
|
||||
const SchedulingTarget& target;
|
||||
size_t computePartitionCount = 0;
|
||||
ComputeGraph graph;
|
||||
const ComputeGraph& graph;
|
||||
MergeScheduleResult result;
|
||||
|
||||
MergeScheduleResult run();
|
||||
|
||||
+12
-126
@@ -5,7 +5,6 @@
|
||||
#include "llvm/Support/FormatVariadic.h"
|
||||
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <tuple>
|
||||
@@ -42,11 +41,6 @@ struct TopologyModel {
|
||||
}
|
||||
};
|
||||
|
||||
Time getAverageTransferTime(const TransferCost& transferCost, const SchedulingTarget& target) {
|
||||
return checkedAdd(transferCost.fixed,
|
||||
checkedMultiply(transferCost.networkFlits, target.averageInterProcessorLatencyNs));
|
||||
}
|
||||
|
||||
std::vector<std::vector<size_t>> buildReverseLevels(const ComputeGraph& graph) {
|
||||
std::vector<size_t> remainingSuccessors(graph.nodes.size(), 0);
|
||||
std::queue<size_t> readySinks;
|
||||
@@ -83,24 +77,6 @@ std::vector<std::vector<size_t>> buildReverseLevels(const ComputeGraph& graph) {
|
||||
return reverseLevels;
|
||||
}
|
||||
|
||||
void verifyOctTableSize(size_t nodeCount, size_t processorCount) {
|
||||
constexpr size_t kMaxOctTableBytes = 1ull << 35;
|
||||
if (nodeCount == 0 || processorCount == 0)
|
||||
return;
|
||||
if (processorCount > std::numeric_limits<size_t>::max() / sizeof(Time))
|
||||
llvm::report_fatal_error("PEFT scheduler: OCT table size overflow");
|
||||
size_t rowBytes = processorCount * sizeof(Time);
|
||||
if (nodeCount > std::numeric_limits<size_t>::max() / rowBytes)
|
||||
llvm::report_fatal_error("PEFT scheduler: OCT table size overflow");
|
||||
size_t totalBytes = nodeCount * rowBytes;
|
||||
if (totalBytes > kMaxOctTableBytes) {
|
||||
std::string message = llvm::formatv("PEFT scheduler: OCT table would require {0} MiB, exceeding the 1024 MiB guard",
|
||||
totalBytes / (1024 * 1024))
|
||||
.str();
|
||||
llvm::report_fatal_error(llvm::StringRef(message));
|
||||
}
|
||||
}
|
||||
|
||||
bool hasHighResidentWeightPressure(const ComputeGraph& graph, size_t processorCount, size_t residentWeightCapacity) {
|
||||
if (residentWeightCapacity > std::numeric_limits<size_t>::max() / processorCount)
|
||||
return false;
|
||||
@@ -243,73 +219,6 @@ FailureOr<LanePublicationSignatures> buildLanePublicationSignatures(SpatComputeB
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<size_t> mapLogicalProcessorsToPhysicalCores(ArrayRef<Cost> logicalTrafficFlits,
|
||||
const SchedulingTarget& target) {
|
||||
const size_t processorCount = target.processorCount;
|
||||
assert(logicalTrafficFlits.size() == processorCount * processorCount
|
||||
&& "logical traffic matrix must cover every processor pair");
|
||||
|
||||
std::vector<size_t> physicalCoreForLogicalProcessor(processorCount);
|
||||
std::iota(physicalCoreForLogicalProcessor.begin(), physicalCoreForLogicalProcessor.end(), 0);
|
||||
|
||||
auto transferCost = [&](size_t sourceLogicalProcessor,
|
||||
size_t targetLogicalProcessor,
|
||||
size_t sourcePhysicalCore,
|
||||
size_t targetPhysicalCore) {
|
||||
Cost traffic = logicalTrafficFlits[sourceLogicalProcessor * processorCount + targetLogicalProcessor];
|
||||
return checkedMultiply(traffic, target.getInterProcessorLatencyNs(sourcePhysicalCore, targetPhysicalCore));
|
||||
};
|
||||
|
||||
for (size_t logicalProcessor = 0; logicalProcessor < processorCount; ++logicalProcessor) {
|
||||
size_t bestPeerLogicalProcessor = logicalProcessor;
|
||||
Cost bestSaving = 0;
|
||||
for (size_t peerLogicalProcessor = 0; peerLogicalProcessor < processorCount; ++peerLogicalProcessor) {
|
||||
if (peerLogicalProcessor == logicalProcessor)
|
||||
continue;
|
||||
size_t physicalCore = physicalCoreForLogicalProcessor[logicalProcessor];
|
||||
size_t peerPhysicalCore = physicalCoreForLogicalProcessor[peerLogicalProcessor];
|
||||
Cost currentCost = 0;
|
||||
Cost swappedCost = 0;
|
||||
for (size_t otherLogicalProcessor = 0; otherLogicalProcessor < processorCount; ++otherLogicalProcessor) {
|
||||
if (otherLogicalProcessor == logicalProcessor || otherLogicalProcessor == peerLogicalProcessor)
|
||||
continue;
|
||||
size_t otherPhysicalCore = physicalCoreForLogicalProcessor[otherLogicalProcessor];
|
||||
currentCost = checkedAdd(
|
||||
currentCost, transferCost(logicalProcessor, otherLogicalProcessor, physicalCore, otherPhysicalCore));
|
||||
currentCost = checkedAdd(
|
||||
currentCost, transferCost(otherLogicalProcessor, logicalProcessor, otherPhysicalCore, physicalCore));
|
||||
currentCost = checkedAdd(
|
||||
currentCost, transferCost(peerLogicalProcessor, otherLogicalProcessor, peerPhysicalCore, otherPhysicalCore));
|
||||
currentCost = checkedAdd(
|
||||
currentCost, transferCost(otherLogicalProcessor, peerLogicalProcessor, otherPhysicalCore, peerPhysicalCore));
|
||||
swappedCost = checkedAdd(
|
||||
swappedCost, transferCost(logicalProcessor, otherLogicalProcessor, peerPhysicalCore, otherPhysicalCore));
|
||||
swappedCost = checkedAdd(
|
||||
swappedCost, transferCost(otherLogicalProcessor, logicalProcessor, otherPhysicalCore, peerPhysicalCore));
|
||||
swappedCost = checkedAdd(
|
||||
swappedCost, transferCost(peerLogicalProcessor, otherLogicalProcessor, physicalCore, otherPhysicalCore));
|
||||
swappedCost = checkedAdd(
|
||||
swappedCost, transferCost(otherLogicalProcessor, peerLogicalProcessor, otherPhysicalCore, physicalCore));
|
||||
}
|
||||
currentCost =
|
||||
checkedAdd(currentCost, transferCost(logicalProcessor, peerLogicalProcessor, physicalCore, peerPhysicalCore));
|
||||
currentCost =
|
||||
checkedAdd(currentCost, transferCost(peerLogicalProcessor, logicalProcessor, peerPhysicalCore, physicalCore));
|
||||
swappedCost =
|
||||
checkedAdd(swappedCost, transferCost(logicalProcessor, peerLogicalProcessor, peerPhysicalCore, physicalCore));
|
||||
swappedCost =
|
||||
checkedAdd(swappedCost, transferCost(peerLogicalProcessor, logicalProcessor, physicalCore, peerPhysicalCore));
|
||||
if (currentCost > swappedCost && currentCost - swappedCost > bestSaving) {
|
||||
bestSaving = currentCost - swappedCost;
|
||||
bestPeerLogicalProcessor = peerLogicalProcessor;
|
||||
}
|
||||
}
|
||||
std::swap(physicalCoreForLogicalProcessor[logicalProcessor],
|
||||
physicalCoreForLogicalProcessor[bestPeerLogicalProcessor]);
|
||||
}
|
||||
return physicalCoreForLogicalProcessor;
|
||||
}
|
||||
|
||||
MergeScheduleResult runPeftScheduler(const ComputeGraph& graph, const PeftScheduleOptions& options) {
|
||||
const size_t nodeCount = graph.nodes.size();
|
||||
const size_t processorCount = options.target.processorCount;
|
||||
@@ -321,36 +230,17 @@ MergeScheduleResult runPeftScheduler(const ComputeGraph& graph, const PeftSchedu
|
||||
std::vector<ResidentWeightSet> capacityReservations = planResidentWeightReservations(
|
||||
graph, processorCount, options.target.residentWeightCapacity, topology, preferWeightReuse);
|
||||
|
||||
verifyOctTableSize(nodeCount, processorCount);
|
||||
std::vector<std::vector<size_t>> reverseLevels = buildReverseLevels(graph);
|
||||
|
||||
// MOCK: Replace this with your actual heterogeneous cost lookup.
|
||||
// If graph.nodes[task] is modified to hold a vector of costs per processor, access it here.
|
||||
auto getComputeCost = [&](size_t task, size_t processor) -> Time { return graph.nodes[task].cost; };
|
||||
std::vector<Time> oct(nodeCount * processorCount, 0);
|
||||
std::vector<Time> minOctPlusComp(nodeCount, 0);
|
||||
|
||||
// 1. O(P(E+V)) Heterogeneous OCT Calculation
|
||||
// Compute costs are identical on every core. The optimistic successor can
|
||||
// stay on the same core for zero transfer cost, so every physical OCT column
|
||||
// is identical even with nonuniform links. Store that exact lower bound once.
|
||||
std::vector<Time> oct(nodeCount, 0);
|
||||
for (const std::vector<size_t>& levelNodes : reverseLevels) {
|
||||
auto computeNodeOct = [&](size_t levelIndex) {
|
||||
size_t task = levelNodes[levelIndex];
|
||||
std::vector<Time> maxVals(processorCount, 0);
|
||||
|
||||
for (const auto& [succ, comm] : graph.successors[task]) {
|
||||
Time valDifferentCpu = addOrMax(minOctPlusComp[succ], getAverageTransferTime(comm, options.target));
|
||||
for (size_t processor = 0; processor < processorCount; ++processor) {
|
||||
Time valSameCpu = addOrMax(oct[succ * processorCount + processor], getComputeCost(succ, processor));
|
||||
Time bestSucc = std::min(valSameCpu, valDifferentCpu);
|
||||
maxVals[processor] = std::max(maxVals[processor], bestSucc);
|
||||
}
|
||||
}
|
||||
|
||||
Time minForPreds = std::numeric_limits<Time>::max();
|
||||
for (size_t processor = 0; processor < processorCount; ++processor) {
|
||||
oct[task * processorCount + processor] = maxVals[processor];
|
||||
minForPreds = std::min(minForPreds, addOrMax(maxVals[processor], getComputeCost(task, processor)));
|
||||
}
|
||||
minOctPlusComp[task] = minForPreds == std::numeric_limits<Time>::max() ? 0 : minForPreds;
|
||||
for (const auto& [succ, comm] : graph.successors[task])
|
||||
oct[task] = std::max(oct[task], addOrMax(oct[succ], graph.nodes[succ].cost));
|
||||
};
|
||||
|
||||
if (options.context != nullptr)
|
||||
@@ -367,9 +257,7 @@ MergeScheduleResult runPeftScheduler(const ComputeGraph& graph, const PeftSchedu
|
||||
};
|
||||
std::vector<RankEntry> ranks(nodeCount);
|
||||
auto computeRank = [&](size_t node) {
|
||||
long double rank = 0.0L;
|
||||
for (size_t processor = 0; processor < processorCount; ++processor)
|
||||
rank += static_cast<long double>(oct[node * processorCount + processor]);
|
||||
long double rank = static_cast<long double>(oct[node]);
|
||||
ranks[node] = {rank, node, graph.nodes[node].originalOrder};
|
||||
};
|
||||
|
||||
@@ -399,7 +287,6 @@ MergeScheduleResult runPeftScheduler(const ComputeGraph& graph, const PeftSchedu
|
||||
}
|
||||
|
||||
std::vector<char> scheduled(nodeCount, false);
|
||||
std::vector<ResidentWeightSet> reservations = capacityReservations;
|
||||
std::vector<ResidentWeightSet> processorResidentWeights(processorCount);
|
||||
std::vector<ScheduledTask> schedules(nodeCount);
|
||||
std::vector<std::vector<size_t>> tasksByProcessor(processorCount);
|
||||
@@ -429,9 +316,9 @@ MergeScheduleResult runPeftScheduler(const ComputeGraph& graph, const PeftSchedu
|
||||
unsigned int overlapCount =
|
||||
countResidentWeightOverlap(processorResidentWeights[processor], graph.nodes[task].residentWeights);
|
||||
size_t residentWeightUnion =
|
||||
getResidentWeightUnionSize(reservations[processor], graph.nodes[task].residentWeights);
|
||||
getResidentWeightUnionSize(capacityReservations[processor], graph.nodes[task].residentWeights);
|
||||
smallestResidentWeightUnion = std::min(smallestResidentWeightUnion, residentWeightUnion);
|
||||
if (!graph.nodes[task].residentWeights.empty() && residentWeightUnion > options.target.residentWeightCapacity) {
|
||||
if (!graph.nodes[task].residentWeights.empty() && residentWeightUnion > capacityReservations[processor].size()) {
|
||||
residentWeightRejected = true;
|
||||
continue;
|
||||
}
|
||||
@@ -442,7 +329,7 @@ MergeScheduleResult runPeftScheduler(const ComputeGraph& graph, const PeftSchedu
|
||||
dataReady = std::max(dataReady, addOrMax(predSchedule.endTime, commPenalty));
|
||||
}
|
||||
|
||||
Time computeCost = getComputeCost(task, processor);
|
||||
Time computeCost = graph.nodes[task].cost;
|
||||
Time est = dataReady;
|
||||
Time currentEnd = 0;
|
||||
bool foundGap = false;
|
||||
@@ -461,7 +348,7 @@ MergeScheduleResult runPeftScheduler(const ComputeGraph& graph, const PeftSchedu
|
||||
est = std::max(currentEnd, dataReady);
|
||||
|
||||
Time eft = addOrMax(est, computeCost);
|
||||
Time oeft = addOrMax(eft, oct[task * processorCount + processor]);
|
||||
Time oeft = addOrMax(eft, oct[task]);
|
||||
size_t centerDistance = topology.getCenterDistance(processor);
|
||||
size_t taskCount = tasksByProcessor[processor].size();
|
||||
bool betterResidentWeightChoice =
|
||||
@@ -513,7 +400,7 @@ MergeScheduleResult runPeftScheduler(const ComputeGraph& graph, const PeftSchedu
|
||||
const ComputeInstance& instance = graph.nodes[task].instance;
|
||||
std::string message =
|
||||
llvm::formatv("PEFT scheduler: no valid processor for task {0} (lanes {1}..{2}, {3} distinct weights); "
|
||||
"smallest processor union is {4}, exceeding resident-weight capacity {5}",
|
||||
"no physical reservation contains the task weights (smallest union {4}, core capacity {5})",
|
||||
graph.nodes[task].originalOrder,
|
||||
instance.laneStart,
|
||||
instance.laneStart + instance.laneCount,
|
||||
@@ -533,7 +420,6 @@ MergeScheduleResult runPeftScheduler(const ComputeGraph& graph, const PeftSchedu
|
||||
schedules[task] = {bestProcessor, bestEst, bestEft};
|
||||
scheduled[task] = true;
|
||||
++scheduledCount;
|
||||
insertResidentWeights(reservations[bestProcessor], graph.nodes[task].residentWeights);
|
||||
insertResidentWeights(processorResidentWeights[bestProcessor], graph.nodes[task].residentWeights);
|
||||
|
||||
auto& timeline = timelineByProcessor[bestProcessor];
|
||||
|
||||
+2
-6
@@ -21,15 +21,11 @@ inline Time getPeftTransferTime(const TransferCost& transferCost,
|
||||
if (sourceProcessor == targetProcessor)
|
||||
return 0;
|
||||
return checkedAdd(transferCost.fixed,
|
||||
checkedMultiply(transferCost.networkFlits, target.averageInterProcessorLatencyNs));
|
||||
checkedMultiply(transferCost.networkFlits, target.getInterProcessorLatencyNs(sourceProcessor, targetProcessor)));
|
||||
}
|
||||
|
||||
// PEFT assigns logical processors. Physical core IDs are chosen only after
|
||||
// materialization exposes the exact transfer traffic.
|
||||
// PEFT assigns physical cores using the injected target topology.
|
||||
MergeScheduleResult runPeftScheduler(const ComputeGraph& graph, const PeftScheduleOptions& options);
|
||||
|
||||
std::vector<size_t> mapLogicalProcessorsToPhysicalCores(llvm::ArrayRef<Cost> logicalTrafficFlits,
|
||||
const SchedulingTarget& target);
|
||||
|
||||
} // namespace spatial
|
||||
} // namespace onnx_mlir
|
||||
|
||||
+678
-351
File diff suppressed because it is too large
Load Diff
+81
-3
@@ -2,8 +2,15 @@
|
||||
|
||||
#include "mlir/Support/LogicalResult.h"
|
||||
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ComputeGraph.hpp"
|
||||
#include "MergeSchedule.hpp"
|
||||
@@ -11,15 +18,86 @@
|
||||
|
||||
namespace onnx_mlir::spatial {
|
||||
|
||||
struct PipelineStageRange {
|
||||
size_t begin;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
class PipelineCoreLayout {
|
||||
public:
|
||||
PipelineCoreLayout(size_t processorCount, size_t stageCount)
|
||||
: processorCount(processorCount), stageSizes(stageCount) {
|
||||
if (stageCount == 0)
|
||||
return;
|
||||
size_t baseSize = processorCount / stageCount;
|
||||
size_t largerStageCount = processorCount % stageCount;
|
||||
for (size_t stage = 0; stage < stageCount; ++stage)
|
||||
stageSizes[stage] = baseSize + (stage < largerStageCount);
|
||||
}
|
||||
|
||||
explicit PipelineCoreLayout(llvm::ArrayRef<size_t> stageSizes)
|
||||
: processorCount(std::accumulate(
|
||||
stageSizes.begin(), stageSizes.end(), size_t {0})),
|
||||
stageSizes(stageSizes.begin(), stageSizes.end()) {}
|
||||
|
||||
PipelineCoreLayout(llvm::ArrayRef<size_t> stageSizes,
|
||||
const SchedulingTarget &target);
|
||||
|
||||
bool isValid() const {
|
||||
return !stageSizes.empty()
|
||||
&& llvm::none_of(stageSizes, [](size_t size) { return size == 0; });
|
||||
}
|
||||
|
||||
size_t getStageCount() const { return stageSizes.size(); }
|
||||
|
||||
size_t getProcessorCount() const { return processorCount; }
|
||||
|
||||
llvm::ArrayRef<size_t> getStageSizes() const { return stageSizes; }
|
||||
|
||||
size_t getPhysicalCore(size_t position) const {
|
||||
return physicalCores.empty() ? position : physicalCores[position];
|
||||
}
|
||||
|
||||
PipelineStageRange getStageRange(size_t stage) const {
|
||||
return {std::accumulate(
|
||||
stageSizes.begin(), stageSizes.begin() + stage, size_t {0}),
|
||||
stageSizes[stage]};
|
||||
}
|
||||
|
||||
std::optional<size_t> getStageForCore(size_t core) const {
|
||||
if (!isValid() || core >= processorCount)
|
||||
return std::nullopt;
|
||||
if (!physicalCoreStages.empty())
|
||||
return physicalCoreStages[core];
|
||||
size_t end = 0;
|
||||
for (auto [stage, size] : llvm::enumerate(stageSizes)) {
|
||||
end += size;
|
||||
if (core < end)
|
||||
return stage;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t processorCount;
|
||||
std::vector<size_t> stageSizes;
|
||||
std::vector<size_t> physicalCores;
|
||||
std::vector<size_t> physicalCoreStages;
|
||||
};
|
||||
|
||||
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,
|
||||
enum class PipelineWorkloadPreparation {
|
||||
Ready,
|
||||
Changed,
|
||||
};
|
||||
|
||||
mlir::FailureOr<PipelineWorkloadPreparation> preparePipelineWorkload(
|
||||
const ComputeGraph& graph, size_t pipelineStages,
|
||||
const SchedulingTarget& physicalTarget,
|
||||
std::string& error);
|
||||
|
||||
|
||||
+2
-2
@@ -193,7 +193,7 @@ FailureOr<TopLevelOpInfo> buildTopLevelOpInfo(Operation& op, bool isScheduled, s
|
||||
|
||||
if constexpr (std::is_same_v<ComputeOpTy, SpatScheduledCompute>) {
|
||||
if (auto compute = dyn_cast<ComputeOpTy>(&op)) {
|
||||
auto coreId = getOptionalScheduledCoreId(compute, "spatial dataflow export core id");
|
||||
auto coreId = getOptionalScheduledCoreId(compute, "Spatial dataflow export core id");
|
||||
if (failed(coreId))
|
||||
return failure();
|
||||
if (*coreId)
|
||||
@@ -207,7 +207,7 @@ FailureOr<TopLevelOpInfo> buildTopLevelOpInfo(Operation& op, bool isScheduled, s
|
||||
template <typename BatchOpTy>
|
||||
FailureOr<SmallVector<int32_t, 8>> getBatchLaneCoreIds(BatchOpTy batch) {
|
||||
if constexpr (std::is_same_v<BatchOpTy, SpatScheduledComputeBatch>) {
|
||||
auto coreIds = getOptionalScheduledBatchCoreIds(batch, "spatial dataflow export core ids");
|
||||
auto coreIds = getOptionalScheduledBatchCoreIds(batch, "Spatial dataflow export core ids");
|
||||
if (failed(coreIds))
|
||||
return failure();
|
||||
if (!*coreIds)
|
||||
|
||||
@@ -13,7 +13,7 @@ include "mlir/Interfaces/SideEffectInterfaces.td"
|
||||
|
||||
def SpatialDialect : Dialect {
|
||||
let name = "spat";
|
||||
let summary = "Dialect designed for deep learning computation in a spatial architecture";
|
||||
let summary = "Dialect designed for deep learning computation in a Spatial architecture";
|
||||
let cppNamespace = "::onnx_mlir::spatial";
|
||||
let useDefaultAttributePrinterParser = 0;
|
||||
let extraClassDeclaration = [{
|
||||
@@ -592,15 +592,14 @@ def SpatHostStoreSyncOp : SpatOp<"host_store_sync", []> {
|
||||
}
|
||||
|
||||
def SpatHostWaitLoadOp : SpatOp<"host_wait_load", []> {
|
||||
let summary = "Wait for producers, load from host memory, and acknowledge consumption";
|
||||
let summary = "Wait for producers and load from host memory";
|
||||
|
||||
let arguments = (ins
|
||||
Index:$sourceCoreId,
|
||||
Index:$targetCoreId,
|
||||
Index:$hostOffset,
|
||||
Index:$eventRegister,
|
||||
Index:$waitValue,
|
||||
Index:$acknowledgementEventRegister
|
||||
Index:$waitValue
|
||||
);
|
||||
|
||||
let results = (outs
|
||||
@@ -610,7 +609,7 @@ def SpatHostWaitLoadOp : SpatOp<"host_wait_load", []> {
|
||||
let assemblyFormat = [{
|
||||
`from` $sourceCoreId `to` $targetCoreId
|
||||
`host_offset` $hostOffset `event` $eventRegister `count` $waitValue
|
||||
`ack` $acknowledgementEventRegister attr-dict `:` type($output)
|
||||
attr-dict `:` type($output)
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@ std::unique_ptr<mlir::Pass> createONNXToSpatialPass(
|
||||
const spatial::SpatialTargetResources& target,
|
||||
const ONNXToSpatialPlanningOptions& options);
|
||||
std::unique_ptr<mlir::Pass> createSpatialLayoutPlanningPass();
|
||||
std::unique_ptr<mlir::Pass> createSpatialLayoutPlanningPass(const spatial::SpatialTargetResources& target);
|
||||
std::unique_ptr<mlir::Pass> createSpatialLayoutPlanningPass(
|
||||
const spatial::SpatialTargetResources& target, bool selectTrivialPlan = false);
|
||||
std::unique_ptr<mlir::Pass> createLowerSpatialPlansPass();
|
||||
std::unique_ptr<mlir::Pass> createLowerSpatialPlansPass(
|
||||
const spatial::SpatialTargetResources& target,
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace {
|
||||
struct EmitPimCodePass : PassWrapper<EmitPimCodePass, OperationPass<ModuleOp>> {
|
||||
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(EmitPimCodePass);
|
||||
StringRef getArgument() const override { return "emit-pim-code-pass"; }
|
||||
StringRef getDescription() const override { return "Emit PIM simulator code artifacts"; }
|
||||
StringRef getDescription() const override { return "Emit Pim simulator code artifacts"; }
|
||||
|
||||
EmitPimCodePass() {}
|
||||
EmitPimCodePass(const EmitPimCodePass& pass) {}
|
||||
@@ -25,7 +25,7 @@ struct EmitPimCodePass : PassWrapper<EmitPimCodePass, OperationPass<ModuleOp>> {
|
||||
|
||||
int compiler_error_code = compileToPimCode(moduleOp, pimDir);
|
||||
if (compiler_error_code != CompilerSuccess) {
|
||||
moduleOp.emitError() << "failed to emit PIM simulator code artifacts; compiler error code "
|
||||
moduleOp.emitError() << "failed to emit Pim simulator code artifacts; compiler error code "
|
||||
<< compiler_error_code;
|
||||
signalPassFailure();
|
||||
}
|
||||
|
||||
@@ -36,3 +36,13 @@ add_pim_unittest(SpatialSchedulingTargetTest
|
||||
LINK_LIBS PRIVATE
|
||||
OMPimCompilerUtils
|
||||
)
|
||||
|
||||
add_test(
|
||||
NAME PimHostReuseSynchronizationTest
|
||||
COMMAND "${PYTHON_EXECUTABLE}"
|
||||
"${CMAKE_SOURCE_DIR}/validation/tools/pim/pimcomp/compare/test_PIMCOMP_adversarial_memory_sync.py"
|
||||
--compiler "$<TARGET_FILE:onnx-mlir>"
|
||||
--simple-model "${CMAKE_SOURCE_DIR}/validation/operations/relu/after_conv/relu_after_conv.onnx"
|
||||
--grouped-model "${CMAKE_SOURCE_DIR}/validation/operations/conv/relu_conv_store/conv_relu_conv_store.onnx"
|
||||
)
|
||||
set_tests_properties(PimHostReuseSynchronizationTest PROPERTIES LABELS pim-unittest)
|
||||
|
||||
@@ -3,12 +3,183 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "mlir/IR/BuiltinOps.h"
|
||||
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/DeferredCommunicationScheduling.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/DeferredTransferPlanning.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/PeftScheduler.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/PipelineScheduling.hpp"
|
||||
|
||||
using namespace onnx_mlir::spatial;
|
||||
|
||||
static void testPhysicalPeft() {
|
||||
TransferCost transfer {.fixed = 50, .networkFlits = 4};
|
||||
SchedulingTarget line;
|
||||
line.processorCount = 3;
|
||||
line.interProcessorLatencyNs = {
|
||||
0,
|
||||
1,
|
||||
10,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
10,
|
||||
1,
|
||||
0,
|
||||
};
|
||||
line.averageInterProcessorLatencyNs = 4;
|
||||
assert(getPeftTransferTime(transfer, 0, 1, line) == 54);
|
||||
assert(getPeftTransferTime(transfer, 0, 2, line) == 90);
|
||||
SchedulingTarget reversed = line;
|
||||
reversed.interProcessorLatencyNs = {0, 10, 1, 10, 0, 1, 1, 1, 0};
|
||||
assert(getPeftTransferTime(transfer, 0, 1, reversed) == 90);
|
||||
assert(getPeftTransferTime(transfer, 0, 2, reversed) == 54);
|
||||
|
||||
mlir::MLIRContext context;
|
||||
mlir::OwningOpRef<mlir::ModuleOp> owner =
|
||||
mlir::ModuleOp::create(mlir::UnknownLoc::get(&context));
|
||||
ComputeGraph chain;
|
||||
chain.successors.resize(3);
|
||||
chain.predecessors.resize(3);
|
||||
for (uint32_t task = 0; task < 3; ++task) {
|
||||
ComputeInstance instance {owner->getOperation(), task, 1};
|
||||
ResidentWeight weight;
|
||||
weight.opaqueLane = task;
|
||||
chain.nodes.push_back({instance, 10, {weight}, task});
|
||||
if (task != 0) {
|
||||
chain.successors[task - 1].push_back({task, transfer});
|
||||
chain.predecessors[task].push_back({task - 1, transfer});
|
||||
}
|
||||
}
|
||||
line.residentWeightCapacity = reversed.residentWeightCapacity = 3;
|
||||
for (const SchedulingTarget &target : {line, reversed}) {
|
||||
auto scheduled = runPeftScheduler(chain, {target, &context});
|
||||
size_t firstCore = scheduled.computeToCpuMap.lookup(chain.nodes[0].instance);
|
||||
assert(firstCore == (target.interProcessorLatencyNs == line.interProcessorLatencyNs ? 1 : 2));
|
||||
for (size_t task = 1; task < chain.nodes.size(); ++task) {
|
||||
auto previous = chain.nodes[task - 1].instance;
|
||||
auto current = chain.nodes[task].instance;
|
||||
size_t source = scheduled.computeToCpuMap.lookup(previous);
|
||||
size_t destination = scheduled.computeToCpuMap.lookup(current);
|
||||
assert(source != destination);
|
||||
assert(scheduled.computeToAestMap.lookup(current)
|
||||
== scheduled.computeToAestMap.lookup(previous) + 10
|
||||
+ getPeftTransferTime(transfer, source, destination, target));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void testPipelineInputArrival() {
|
||||
ComputeGraph graph;
|
||||
graph.successors.resize(4);
|
||||
graph.predecessors.resize(4);
|
||||
MergeScheduleResult schedule;
|
||||
schedule.processorCount = 4;
|
||||
for (uint32_t task = 0; task < 4; ++task) {
|
||||
ComputeInstance instance {nullptr, task, 1};
|
||||
ResidentWeight weight;
|
||||
weight.opaqueLane = task;
|
||||
graph.nodes.push_back({instance, 1, {weight}, task});
|
||||
schedule.dominanceOrderCompute.push_back(instance);
|
||||
schedule.computeToCpuMap[instance] = task;
|
||||
schedule.computeToCpuSlotMap[instance] = 0;
|
||||
if (task != 0) {
|
||||
TransferCost transfer {.fixed = 0, .networkFlits = 100};
|
||||
graph.predecessors[task].push_back({task - 1, transfer});
|
||||
graph.successors[task - 1].push_back({task, transfer});
|
||||
}
|
||||
}
|
||||
SchedulingTarget target;
|
||||
target.processorCount = 4;
|
||||
target.residentWeightCapacity = 4;
|
||||
target.interProcessorLatencyNs.assign(16, 1);
|
||||
for (size_t core = 0; core < 4; ++core)
|
||||
target.interProcessorLatencyNs[core * 4 + core] = 0;
|
||||
std::string error;
|
||||
assert(mlir::succeeded(applyPipelineScheduling(graph, schedule, 2, target, error)));
|
||||
for (size_t first : {0, 2}) {
|
||||
size_t source = schedule.computeToCpuMap.lookup(graph.nodes[first].instance);
|
||||
size_t destination = schedule.computeToCpuMap.lookup(graph.nodes[first + 1].instance);
|
||||
assert(schedule.processorStages[source] == first / 2);
|
||||
// An idle remote core would finish later than waiting for the local core.
|
||||
assert(source == destination);
|
||||
}
|
||||
}
|
||||
|
||||
static void testReadyCommunication() {
|
||||
mlir::MLIRContext context;
|
||||
mlir::OwningOpRef<mlir::ModuleOp> owner =
|
||||
mlir::ModuleOp::create(mlir::UnknownLoc::get(&context));
|
||||
auto type = mlir::RankedTensorType::get({1}, mlir::Float32Type::get(&context));
|
||||
mlir::Block payloads;
|
||||
auto payload = payloads.addArgument(type, owner->getLoc());
|
||||
DeferredTransferPlan plan;
|
||||
plan.stepCounts = {3, 1, 1};
|
||||
plan.scheduled.resize(3);
|
||||
for (auto [index, info] : llvm::enumerate(plan.scheduled)) {
|
||||
info.op = owner->getOperation();
|
||||
info.streamIds.push_back(index);
|
||||
}
|
||||
ProducedValue producer;
|
||||
producer.scheduled = &plan.scheduled[0];
|
||||
producer.payload = payload;
|
||||
// Two consumers exercise removal of already-scheduled ready-queue entries.
|
||||
for (unsigned target = 1; target <= 2; ++target) {
|
||||
auto exchange = std::make_unique<DeferredExchangePlan>();
|
||||
exchange->target = &plan.scheduled[target];
|
||||
exchange->exchangeId = target;
|
||||
RequirementFamily requirement;
|
||||
requirement.producer = &producer;
|
||||
requirement.publicationFragmentType = type;
|
||||
exchange->requirements.push_back(requirement);
|
||||
ExternalTransferFamily transfer;
|
||||
transfer.requirement = &exchange->requirements.front();
|
||||
transfer.sourceScheduled = producer.scheduled;
|
||||
transfer.targetScheduled = exchange->target;
|
||||
transfer.targetLanes = LaneSet::all(1);
|
||||
transfer.targetStreams = onnx_mlir::StaticIntSequence::uniform(target, 1);
|
||||
exchange->external.push_back(transfer);
|
||||
plan.exchanges.push_back(std::move(exchange));
|
||||
}
|
||||
auto scheduled = scheduleDeferredCommunication({}, plan);
|
||||
assert(mlir::succeeded(scheduled));
|
||||
assert(scheduled->slices.size() == 2);
|
||||
for (const auto &slice : scheduled->slices)
|
||||
assert(slice.sourceInsertionStep == 1);
|
||||
}
|
||||
|
||||
int main() {
|
||||
testPipelineInputArrival();
|
||||
testReadyCommunication();
|
||||
PipelineCoreLayout unevenLayout(138, 4);
|
||||
assert(unevenLayout.isValid());
|
||||
assert(unevenLayout.getStageRange(0).begin == 0);
|
||||
assert(unevenLayout.getStageRange(0).size == 35);
|
||||
assert(unevenLayout.getStageRange(1).begin == 35);
|
||||
assert(unevenLayout.getStageRange(1).size == 35);
|
||||
assert(unevenLayout.getStageRange(2).begin == 70);
|
||||
assert(unevenLayout.getStageRange(2).size == 34);
|
||||
assert(unevenLayout.getStageRange(3).begin == 104);
|
||||
assert(unevenLayout.getStageRange(3).size == 34);
|
||||
assert(unevenLayout.getStageForCore(34) == 0);
|
||||
assert(unevenLayout.getStageForCore(35) == 1);
|
||||
assert(unevenLayout.getStageForCore(69) == 1);
|
||||
assert(unevenLayout.getStageForCore(70) == 2);
|
||||
assert(unevenLayout.getStageForCore(137) == 3);
|
||||
assert(!unevenLayout.getStageForCore(138));
|
||||
|
||||
PipelineCoreLayout dynamicLayout(std::vector<size_t> {2, 4, 1, 3});
|
||||
assert(dynamicLayout.isValid());
|
||||
assert(dynamicLayout.getProcessorCount() == 10);
|
||||
assert(dynamicLayout.getStageRange(0).begin == 0);
|
||||
assert(dynamicLayout.getStageRange(1).begin == 2);
|
||||
assert(dynamicLayout.getStageRange(2).begin == 6);
|
||||
assert(dynamicLayout.getStageRange(3).begin == 7);
|
||||
assert(dynamicLayout.getStageForCore(1) == 0);
|
||||
assert(dynamicLayout.getStageForCore(2) == 1);
|
||||
assert(dynamicLayout.getStageForCore(6) == 2);
|
||||
assert(dynamicLayout.getStageForCore(9) == 3);
|
||||
|
||||
TransferCost transfer {.fixed = 50, .networkFlits = 4};
|
||||
|
||||
SchedulingTarget fast;
|
||||
@@ -26,36 +197,7 @@ int main() {
|
||||
assert(fast.getInterProcessorLatencyNs(0, 1) == 3);
|
||||
assert(slow.getInterProcessorLatencyNs(0, 1) == 10);
|
||||
|
||||
SchedulingTarget line;
|
||||
line.processorCount = 3;
|
||||
line.interProcessorLatencyNs = {
|
||||
0,
|
||||
1,
|
||||
10,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
10,
|
||||
1,
|
||||
0,
|
||||
};
|
||||
std::vector<Cost> logicalTrafficFlits(9, 0);
|
||||
logicalTrafficFlits[2] = 100;
|
||||
assert(mapLogicalProcessorsToPhysicalCores(logicalTrafficFlits, line) == std::vector<size_t>({1, 0, 2}));
|
||||
|
||||
SchedulingTarget alreadyPlaced = line;
|
||||
alreadyPlaced.interProcessorLatencyNs = {
|
||||
0,
|
||||
10,
|
||||
1,
|
||||
10,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
};
|
||||
assert(mapLogicalProcessorsToPhysicalCores(logicalTrafficFlits, alreadyPlaced) == std::vector<size_t>({0, 1, 2}));
|
||||
testPhysicalPeft();
|
||||
|
||||
ComputeGraph graph;
|
||||
graph.successors.resize(6);
|
||||
@@ -71,16 +213,16 @@ int main() {
|
||||
graph.instanceToIndex[instance] = task;
|
||||
}
|
||||
|
||||
MergeScheduleResult pipelineSchedule;
|
||||
pipelineSchedule.processorCount = 2;
|
||||
pipelineSchedule.dominanceOrderCompute.reserve(graph.nodes.size());
|
||||
MergeScheduleResult physicalSchedule;
|
||||
physicalSchedule.processorCount = 4;
|
||||
physicalSchedule.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);
|
||||
physicalSchedule.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;
|
||||
physicalSchedule.computeToCpuMap[instance] = cpu;
|
||||
physicalSchedule.computeToCpuSlotMap[instance] = task < 4 ? task : task - 4;
|
||||
physicalSchedule.computeToAestMap[instance] = task;
|
||||
}
|
||||
|
||||
SchedulingTarget physical = fast;
|
||||
@@ -92,21 +234,90 @@ int main() {
|
||||
3, 3, 0, 3,
|
||||
3, 3, 3, 0,
|
||||
};
|
||||
SchedulingTarget clustered = physical;
|
||||
clustered.interProcessorLatencyNs = {
|
||||
0, 20, 1, 20,
|
||||
20, 0, 20, 1,
|
||||
1, 20, 0, 20,
|
||||
20, 1, 20, 0,
|
||||
};
|
||||
PipelineCoreLayout clusteredLayout(std::vector<size_t> {2, 2}, clustered);
|
||||
assert(clusteredLayout.getPhysicalCore(0) == 0);
|
||||
assert(clusteredLayout.getPhysicalCore(1) == 2);
|
||||
assert(clusteredLayout.getStageForCore(0) == 0);
|
||||
assert(clusteredLayout.getStageForCore(2) == 0);
|
||||
assert(clusteredLayout.getStageForCore(1) == 1);
|
||||
assert(clusteredLayout.getStageForCore(3) == 1);
|
||||
PipelineCoreLayout uniformLayout(std::vector<size_t> {2, 2}, physical);
|
||||
assert(uniformLayout.getPhysicalCore(1) == 1);
|
||||
std::string pipelineError;
|
||||
ComputeGraph preparationGraph = graph;
|
||||
ResidentWeight extraWeight;
|
||||
extraWeight.opaqueLane = graph.nodes.size();
|
||||
preparationGraph.nodes[2].residentWeights.push_back(extraWeight);
|
||||
auto preparation = preparePipelineWorkload(
|
||||
preparationGraph, 2, physical, pipelineError);
|
||||
assert(mlir::succeeded(preparation));
|
||||
assert(*preparation == PipelineWorkloadPreparation::Ready);
|
||||
extraWeight.opaqueLane++;
|
||||
preparationGraph.nodes[2].residentWeights.push_back(extraWeight);
|
||||
assert(mlir::failed(preparePipelineWorkload(
|
||||
preparationGraph, 2, physical, pipelineError)));
|
||||
assert(pipelineError.find("physical core's crossbars") != std::string::npos);
|
||||
pipelineError.clear();
|
||||
ComputeGraph emptyGraph;
|
||||
MergeScheduleResult emptySchedule;
|
||||
emptySchedule.processorCount = 4;
|
||||
assert(mlir::succeeded(applyPipelineScheduling(
|
||||
emptyGraph, emptySchedule, 2, physical, pipelineError)));
|
||||
assert(emptySchedule.processorCount == physical.processorCount);
|
||||
assert(emptySchedule.processorStages == std::vector<size_t>({0, 0, 1, 1}));
|
||||
|
||||
MergeScheduleResult pipelineSchedule = physicalSchedule;
|
||||
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);
|
||||
size_t predecessorCore = pipelineSchedule.computeToCpuMap.lookup(
|
||||
graph.nodes[1].instance);
|
||||
size_t successorCore = pipelineSchedule.computeToCpuMap.lookup(
|
||||
graph.nodes[2].instance);
|
||||
assert(pipelineSchedule.computeToAestMap.lookup(graph.nodes[2].instance)
|
||||
>= pipelineSchedule.computeToAestMap.lookup(graph.nodes[1].instance)
|
||||
+ graph.nodes[1].cost + 4);
|
||||
+ graph.nodes[1].cost
|
||||
+ getPeftTransferTime(
|
||||
TransferCost {.fixed = 1, .networkFlits = 1},
|
||||
predecessorCore, successorCore, physical));
|
||||
assert(pipelineSchedule.processorStages[predecessorCore]
|
||||
<= pipelineSchedule.processorStages[successorCore]);
|
||||
assert(pipelineSchedule.processorStages[successorCore]
|
||||
<= pipelineSchedule.processorStages[predecessorCore] + 1);
|
||||
assert(pipelineSchedule.equivalentClass.empty());
|
||||
|
||||
graph.successors[0].push_back(
|
||||
{5, TransferCost {.fixed = 1, .networkFlits = 1}});
|
||||
graph.predecessors[5].push_back(
|
||||
{0, TransferCost {.fixed = 1, .networkFlits = 1}});
|
||||
SchedulingTarget fourStagePhysical = physical;
|
||||
fourStagePhysical.processorCount = 8;
|
||||
fourStagePhysical.interProcessorLatencyNs.assign(64, 3);
|
||||
for (size_t core = 0; core < 8; ++core)
|
||||
fourStagePhysical.interProcessorLatencyNs[core * 8 + core] = 0;
|
||||
MergeScheduleResult fourStageSchedule = physicalSchedule;
|
||||
fourStageSchedule.processorCount = 8;
|
||||
assert(mlir::succeeded(applyPipelineScheduling(
|
||||
graph, fourStageSchedule, 4, fourStagePhysical, pipelineError)));
|
||||
for (size_t task = 0; task < graph.nodes.size(); ++task)
|
||||
for (const auto &[predecessor, cost] : graph.predecessors[task]) {
|
||||
(void)cost;
|
||||
size_t sourceStage = fourStageSchedule.processorStages[
|
||||
fourStageSchedule.computeToCpuMap.lookup(
|
||||
graph.nodes[predecessor].instance)];
|
||||
size_t targetStage = fourStageSchedule.processorStages[
|
||||
fourStageSchedule.computeToCpuMap.lookup(graph.nodes[task].instance)];
|
||||
assert(sourceStage <= targetStage);
|
||||
assert(targetStage <= sourceStage + 1);
|
||||
}
|
||||
|
||||
ComputeGraph communicationGraph;
|
||||
communicationGraph.successors.resize(5);
|
||||
communicationGraph.predecessors.resize(5);
|
||||
@@ -116,7 +327,7 @@ int main() {
|
||||
{4, TransferCost {.fixed = 0, .networkFlits = 1}});
|
||||
const Cost communicationCosts[] = {6, 4, 6, 4, 1};
|
||||
MergeScheduleResult communicationSchedule;
|
||||
communicationSchedule.processorCount = 2;
|
||||
communicationSchedule.processorCount = 4;
|
||||
for (uint32_t task = 0; task < 5; ++task) {
|
||||
ComputeInstance instance {nullptr, task, 1};
|
||||
ResidentWeight weight;
|
||||
@@ -136,15 +347,22 @@ int main() {
|
||||
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;
|
||||
slowPipeline.interProcessorLatencyNs.assign(16, 10);
|
||||
for (size_t core = 0; core < 4; ++core)
|
||||
slowPipeline.interProcessorLatencyNs[core * 4 + core] = 0;
|
||||
MergeScheduleResult slowCommunicationSchedule = communicationSchedule;
|
||||
assert(mlir::succeeded(applyPipelineScheduling(
|
||||
communicationGraph, slowCommunicationSchedule, 2, slowPipeline, pipelineError)));
|
||||
assert(slowCommunicationSchedule.computeToCpuMap.lookup(
|
||||
communicationGraph.nodes[2].instance) < 2);
|
||||
size_t sourceCore = slowCommunicationSchedule.computeToCpuMap.lookup(
|
||||
communicationGraph.nodes[4].instance);
|
||||
size_t targetCore = slowCommunicationSchedule.computeToCpuMap.lookup(
|
||||
communicationGraph.nodes[3].instance);
|
||||
assert(slowCommunicationSchedule.processorStages[sourceCore]
|
||||
<= slowCommunicationSchedule.processorStages[targetCore]);
|
||||
assert(slowCommunicationSchedule.processorStages[targetCore]
|
||||
<= slowCommunicationSchedule.processorStages[sourceCore] + 1);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ def print_report(path: Path, counts: Counter, groups: dict[tuple[str, str], Chai
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Analyze repeated Spatial/PIM tensor IR cardinality patterns.")
|
||||
parser = argparse.ArgumentParser(description="Analyze repeated Spatial/Pim tensor IR cardinality patterns.")
|
||||
parser.add_argument("paths", nargs="+", help="MLIR files to analyze.")
|
||||
parser.add_argument("--limit", type=int, default=12, help="Maximum number of hot chains to print per file.")
|
||||
args = parser.parse_args()
|
||||
|
||||
+4
-14
@@ -1,22 +1,12 @@
|
||||
operations/**/inputs
|
||||
operations/**/outputs
|
||||
operations/**/raptor
|
||||
operations/**/runner
|
||||
operations/**/simulation
|
||||
operations/**/artifacts
|
||||
operations/**/*.csv
|
||||
!operations/validation_results.csv
|
||||
|
||||
networks/**/inputs
|
||||
networks/**/outputs
|
||||
networks/**/raptor
|
||||
networks/**/pimcomp
|
||||
networks/**/runner
|
||||
networks/**/simulation
|
||||
networks/**/real_image_val
|
||||
networks/**/artifacts
|
||||
networks/**/*.png
|
||||
networks/**/*.jpg
|
||||
networks/**/*.csv
|
||||
!networks/validation_results.csv
|
||||
!networks/full_net/validation_results.csv
|
||||
!networks/pimcomp_models/validation_results.csv
|
||||
!networks/pimcomp_models/results.csv
|
||||
!networks/pimcomp_models/results_comparison.csv
|
||||
!networks/pimcomp_models/results_ablation.csv
|
||||
|
||||
+41
-54
@@ -1,14 +1,14 @@
|
||||
# Raptor Validation
|
||||
# Raptor validation
|
||||
|
||||
`validate.py` validates every ONNX model below a selected directory. For each
|
||||
model it can:
|
||||
|
||||
1. compile an ONNX-MLIR reference library and runner;
|
||||
2. generate deterministic random inputs;
|
||||
3. compile PIM artifacts with Raptor;
|
||||
4. run the reference implementation and functional PIM simulator;
|
||||
3. compile Pim artifacts with Raptor;
|
||||
4. run the reference implementation and functional Pim simulator;
|
||||
5. compare their outputs;
|
||||
6. run `pimsim-nn` to report latency, throughput, power, and energy.
|
||||
6. run Pimsim to report latency, throughput, power, and energy.
|
||||
|
||||
Run the script from the repository root with the repository Python environment.
|
||||
|
||||
@@ -61,29 +61,12 @@ Validate a network or network slice:
|
||||
The script discovers them recursively and writes `validation_results.csv` in
|
||||
that directory while retaining separate latency and throughput terminal tables.
|
||||
|
||||
## Raptor vs PIMCOMP comparison
|
||||
## Pim validation tools
|
||||
|
||||
The PIMCOMP paper-model suite has a one-command multi-architecture comparison:
|
||||
|
||||
```bash
|
||||
.venv/bin/python validation/tools/pim/pimcomp/compare/run_pimcomp_paper_latency.py
|
||||
```
|
||||
|
||||
The runner verifies PIMCOMP's population-200, 1000-iteration GA settings,
|
||||
expects Raptor and the existing `third_party/PIMCOMP-NN/build` tree to already
|
||||
be built, and compares the configured paper models in parallel. Set `--jobs` to
|
||||
control comparison workers;
|
||||
the runner leaves `OMP_NUM_THREADS` at its environment default. Use
|
||||
`--models vgg8` for one model or `--dry-run` to print the commands.
|
||||
|
||||
Generated artifacts are stored beside each model and ignored by Git. The
|
||||
comparison reuses model-level `common/inputs/`, `common/outputs/`,
|
||||
`common/runner/` across architectures, modes, and pipelines. Pimsim configs and
|
||||
network meshes are referenced directly from
|
||||
`validation/pimsim_configs/pimcomp/`. Architecture- and pipeline-specific `raptor/`, `simulation/`, and
|
||||
PIMCOMP artifacts stay under each comparison directory. See
|
||||
[`networks/pimcomp_models/README.md`](networks/pimcomp_models/README.md) for
|
||||
profiles, model provenance, limitations, and remote execution.
|
||||
- [Pimcomp model suite](networks/pimcomp_models/README.md)
|
||||
- [Pimcomp model comparison tools](tools/pim/pimcomp/compare/README.md)
|
||||
- [Pimcomp correctness study](tools/pim/pimcomp/correctness/README.md)
|
||||
- [Raptor compiler ablation study](tools/pim/ablation/README.md)
|
||||
|
||||
## Validation modes
|
||||
|
||||
@@ -92,7 +75,7 @@ uses one input, while throughput uses `--pipeline=4` with four distinct inputs.
|
||||
Both modes reuse the generated input batch, native runner, and reference
|
||||
outputs, and every throughput output is compared with its own reference.
|
||||
|
||||
Use `--compile-only` to build the reference runner and PIM artifacts without
|
||||
Use `--compile-only` to build the reference runner and Pim artifacts without
|
||||
executing either implementation:
|
||||
|
||||
```bash
|
||||
@@ -139,23 +122,23 @@ count with `-j` or `--jobs`:
|
||||
| `--onnx-include-dir PATH` | ONNX-MLIR runtime include directory. Required unless `--clean` is used. |
|
||||
| `--operations-dir PATH` | Directory tree containing models. Defaults to `validation/operations`. |
|
||||
| `--simulator-dir PATH` | Functional `pim-simulator` crate directory. Defaults to the in-tree simulator. |
|
||||
| `--non-functional-simulator-build-dir PATH` | `pimsim-nn` build directory. Defaults to the in-tree build. |
|
||||
| `--non-functional-simulator-build-dir PATH` | Pimsim build directory. Defaults to the in-tree build. |
|
||||
| `--pimcomp-config {arch-a,arch-b,arch-c}` | Non-functional hardware/timing profile. Defaults to `arch-a`. |
|
||||
| `--skip-non-functional-simulation` | Skip `pimsim-nn` latency, throughput, power, and energy measurement. |
|
||||
| `--no-fast` | Disable fast throughput convergence for authoritative full-duration `pimsim-nn` experiments. |
|
||||
| `--skip-non-functional-simulation` | Skip Pimsim latency, throughput, power, and energy measurement. |
|
||||
| `--no-fast` | Disable fast throughput convergence for authoritative full-duration Pimsim experiments. |
|
||||
| `--threshold FLOAT` | Absolute output-comparison tolerance. Defaults to `1e-3`. |
|
||||
| `--relative-threshold FLOAT` | Relative output-comparison tolerance. Defaults to `1e-5`. |
|
||||
| `--seed INT` | Seed for generated inputs. Defaults to `0`. |
|
||||
| `--crossbar-size INT` | Crossbar dimensions passed to Raptor. Defaults to the Arch-A value, `128`. |
|
||||
| `--crossbar-count INT` | Crossbars per core passed to Raptor. Defaults to the Arch-A value, `96`. |
|
||||
| `--core-count INT` | PIM core count passed to Raptor. Defaults to the Arch-A value, `168`. |
|
||||
| `--core-count INT` | Pim core count passed to Raptor. Defaults to the Arch-A value, `168`. |
|
||||
| `--raptor-extra-arg=ARG` | Additional Raptor compiler argument. Repeat for multiple arguments. |
|
||||
| `--command-timeout-seconds FLOAT` | Timeout for each compiler, runner, and simulator subprocess. Defaults to `1000000.0`. |
|
||||
| `-j INT`, `--jobs INT` | Parallel validation workers. Defaults to all available CPUs and must be at least one. |
|
||||
| `--clean` | Remove generated validation artifacts and exit. |
|
||||
| `--compile-only` | Compile reference and PIM artifacts without execution or comparison. |
|
||||
| `--compile-only` | Compile reference and Pim artifacts without execution or comparison. |
|
||||
| `--run-only` | Reuse compiled artifacts and perform execution, simulation, and comparison. |
|
||||
| `--verbose` | Print passing per-stage and subprocess logs, plus average PIM pass timings. |
|
||||
| `--verbose` | Print passing per-stage and subprocess logs, plus average Pim pass timings. |
|
||||
|
||||
Arguments beginning with `--` that are passed through to Raptor should use the
|
||||
equals form:
|
||||
@@ -166,7 +149,7 @@ equals form:
|
||||
|
||||
## Hardware profiles and non-functional simulation
|
||||
|
||||
The selected PIMCOMP profile must match `--core-count`, `--crossbar-count`, and
|
||||
The selected Pimcomp profile must match `--core-count`, `--crossbar-count`, and
|
||||
`--crossbar-size`. A mismatch disables only non-functional simulation and
|
||||
prints the incompatible values; functional validation still runs.
|
||||
|
||||
@@ -189,27 +172,30 @@ comparison. A non-functional simulation failure remains visible as `ERROR` in
|
||||
the corresponding latency, throughput, power, or energy columns but does not
|
||||
change a functional PASS.
|
||||
|
||||
`pimsim-nn` does not currently implement the `vsoftmax` instruction. When its
|
||||
Pimsim does not currently implement the `vsoftmax` instruction. When its
|
||||
explicit unsupported-op diagnostic is encountered, Softmax validations retain
|
||||
their functional PASS and show `UNSUPPORTED` in the non-functional columns.
|
||||
Other `pimsim-nn` failures remain `ERROR`.
|
||||
Other Pimsim failures remain `ERROR`.
|
||||
|
||||
## Generated artifacts
|
||||
|
||||
Artifacts are written beside each model:
|
||||
Generated files are grouped below an `artifacts/` directory beside each model
|
||||
or operation case. This keeps checked-in ONNX files and generated trees
|
||||
separate and lets `--clean` remove the complete workspace, including stale
|
||||
validation lock files:
|
||||
|
||||
| Path | Contents |
|
||||
|---|---|
|
||||
| `inputs.csv` | Generated inputs, one batch entry per line. |
|
||||
| `inputs/`, `outputs/`, `runner/` | Inputs, reference outputs, and the runner shared by latency and throughput validation. |
|
||||
| `raptor/pim/`, `simulation/latency/` | Latency PIM artifacts and functional simulator outputs. |
|
||||
| `raptor/throughput/pim/`, `simulation/throughput/` | Pipeline-4, batch-4 throughput PIM artifacts and functional simulator outputs. |
|
||||
| `common/inputs/` | Shared generated input CSV files. |
|
||||
| `common/outputs/` | Shared ONNX-MLIR reference output CSV files. |
|
||||
| `common/runner/` | Shared reference runner source, build tree, and library. |
|
||||
| `<arch>/<mode>[/pipelineN]/raptor/` | Architecture- and pipeline-specific Raptor MLIR and PIM artifacts. |
|
||||
| `<arch>/<mode>[/pipelineN]/simulation/` | Functional simulator outputs for that comparison. |
|
||||
| `<arch>/<mode>[/pipelineN]/pimcomp/` | PIMCOMP graph, instruction, simulator, and comparison-report artifacts. |
|
||||
| `artifacts/inputs.csv` | Generated inputs, one batch entry per line. |
|
||||
| `artifacts/inputs/`, `artifacts/outputs/`, `artifacts/runner/` | Inputs, reference outputs, and the runner shared by latency and throughput validation. |
|
||||
| `artifacts/raptor/pim/`, `artifacts/simulation/latency/` | Latency Pim artifacts and functional simulator outputs. |
|
||||
| `artifacts/raptor/throughput/pim/`, `artifacts/simulation/throughput/` | Pipeline-4, batch-4 throughput Pim artifacts and functional simulator outputs. |
|
||||
| `artifacts/common/inputs/` | Shared generated input CSV files. |
|
||||
| `artifacts/common/outputs/` | Shared ONNX-MLIR reference output CSV files. |
|
||||
| `artifacts/common/runner/` | Shared reference runner source, build tree, and library. |
|
||||
| `artifacts/<arch>/<mode>[/pipelineN]/raptor/` | Architecture- and pipeline-specific Raptor MLIR and Pim artifacts. |
|
||||
| `artifacts/<arch>/<mode>[/pipelineN]/simulation/` | Functional simulator outputs for that comparison. |
|
||||
| `artifacts/<arch>/<mode>[/pipelineN]/pimcomp/` | Pimcomp graph, instruction, simulator, and comparison-report artifacts. |
|
||||
|
||||
Each comparison's `raptor/` directory may include `spatial0.mlir`,
|
||||
`spatial1_graph.mlir`, `spatial2_trivial_merged.mlir`,
|
||||
@@ -240,22 +226,23 @@ The generated operation inventory is documented in
|
||||
|
||||
## Manual functional simulator tracing
|
||||
|
||||
After validation has produced a `raptor/pim/` directory, rerun the functional
|
||||
After validation has produced an `artifacts/raptor/pim/` directory, rerun the functional
|
||||
simulator with tracing from its crate directory:
|
||||
|
||||
```bash
|
||||
cd backend-simulators/pim/pim-simulator
|
||||
cargo run --no-default-features --features tracing --release \
|
||||
--package pim-simulator --bin pim-simulator -- \
|
||||
-f /path/to/workspace/raptor/pim \
|
||||
-o /path/to/workspace/simulation/out.bin \
|
||||
-f /path/to/workspace/artifacts/raptor/pim \
|
||||
-o /path/to/workspace/artifacts/simulation/out.bin \
|
||||
-d <addr0>,<size0>,<addr1>,<size1>,... \
|
||||
--mode latency \
|
||||
--input /path/to/workspace/simulation/inputs/input_0.bin
|
||||
--batch-size 1 \
|
||||
--input-dir /path/to/workspace/artifacts/simulation/inputs
|
||||
```
|
||||
|
||||
Throughput mode additionally requires `--batch-size N` and exactly `N`
|
||||
`--input` arguments. Each input binary concatenates the model tensors in graph
|
||||
Throughput mode additionally requires `--batch-size N` and at least `N`
|
||||
`input_<index>.bin` files in `--input-dir`. Each input binary concatenates the model tensors in graph
|
||||
input order. The comparison validator also writes one native reference and one
|
||||
`simulation/*_iterations/output_*.bin` dump per batch entry, and checks every
|
||||
entry rather than only the final output.
|
||||
@@ -268,7 +255,7 @@ validator normally derives the `-d` address and byte ranges from
|
||||
|
||||
The final table reports latency and throughput functional pass/fail state plus
|
||||
non-functional latency, throughput, power, and energy. The summary includes pass/fail totals, non-functional simulation
|
||||
counts, total measured latency, and average PIM pass timings when `--verbose`
|
||||
counts, total measured latency, and average Pim pass timings when `--verbose`
|
||||
is enabled.
|
||||
|
||||
- Exit status `0`: all discovered models passed, or cleanup completed.
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
# PIMCOMP comparison models
|
||||
# Pimcomp comparison models
|
||||
|
||||
This directory contains the four networks evaluated in
|
||||
[PIMCOMP: An End-to-End DNN Compiler for Processing-In-Memory Accelerators](https://arxiv.org/pdf/2411.09159):
|
||||
[Pimcomp: An End-to-End DNN Compiler for Processing-In-Memory Accelerators](https://arxiv.org/pdf/2411.09159):
|
||||
VGG-8, ResNet-18, ResNet-34, and GoogLeNet. It also contains YOLO11n as an
|
||||
additional compiler comparison model.
|
||||
|
||||
See the runner-generated [results.csv](results.csv) for the current comparison
|
||||
See the runner-generated [results_comparison.csv](results_comparison.csv) for the current comparison
|
||||
results. Rows are retained separately for each model, architecture, mode, and
|
||||
pipeline. It records separate `PASS`/`FAIL` functional-validation fields for
|
||||
the Raptor and PIMCOMP artifacts; rows without a generated report contain `NA`.
|
||||
Running the runner with `--arch arch-b` or `--arch arch-c` appends those
|
||||
architecture rows without replacing the existing `arch-a` entries.
|
||||
the Raptor and Pimcomp artifacts; rows without a generated report contain `NA`.
|
||||
Use `--archs` to select the architecture rows to generate; existing rows for
|
||||
other architectures remain unchanged.
|
||||
|
||||
## Models and provenance
|
||||
|
||||
| Directory | Model | Input | Provenance |
|
||||
|--------------|----------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `resnet18/` | ResNet-18 v1 | `1x3x224x224` | Symlink to the complete [ONNX Model Zoo `resnet18-v1-7`](https://huggingface.co/onnxmodelzoo/resnet18-v1-7) model already present at `../resnet18/depth_68/resnet18_depth_68.onnx`. |
|
||||
| `resnet34/` | ResNet-34 v1 | `1x3x224x224` | [ONNX Model Zoo `resnet34-v1-7`](https://huggingface.co/onnxmodelzoo/resnet34-v1-7), with its symbolic batch fixed to 1 as PIMCOMP's frontend does. |
|
||||
| `resnet34/` | ResNet-34 v1 | `1x3x224x224` | [ONNX Model Zoo `resnet34-v1-7`](https://huggingface.co/onnxmodelzoo/resnet34-v1-7), with its symbolic batch fixed to 1 as Pimcomp's frontend does. |
|
||||
| `googlenet/` | GoogLeNet | `1x3x224x224` | Unmodified [ONNX Model Zoo `googlenet-12`](https://huggingface.co/onnxmodelzoo/googlenet-12). |
|
||||
| `vgg8/` | VGG-8 reconstruction | `1x1x28x28` | Reconstruction of the [PIMCOMP VGG-8 benchmark](https://arxiv.org/html/2411.09159#S8.SS1), with six convolution and two fully connected layers. |
|
||||
| `vgg8/` | VGG-8 reconstruction | `1x1x28x28` | Reconstruction of the [Pimcomp VGG-8 benchmark](https://arxiv.org/html/2411.09159#S8.SS1), with six convolution and two fully connected layers. |
|
||||
| `yolo11n/` | YOLO11n detection | `1x3x640x640` | Derived from the canonical local model at `../yolo11n/depth_51/yolo11n_depth_51.onnx`, exported from [Ultralytics YOLO11n](https://github.com/ultralytics/ultralytics/blob/main/docs/en/models/yolo11.md). |
|
||||
|
||||
`googlenet/googlenet-12-pimsim-nn.onnx` is the explicit pimsim-nn-ready GoogLeNet model.
|
||||
`googlenet/googlenet-12-pimsim-nn.onnx` is the explicit Pimsim-ready GoogLeNet model.
|
||||
It removes the two LRN nodes and terminal Softmax from the original model,
|
||||
so that the comparison covers only operations scheduled by PIMCOMP and supported by pimsim-nn.
|
||||
so that the comparison covers only operations scheduled by Pimcomp and supported by Pimsim.
|
||||
|
||||
`yolo11n/yolo11n-pimsim-nn.onnx` is the explicit pimsim-nn-ready YOLO11n model.
|
||||
`yolo11n/yolo11n-pimsim-nn.onnx` is the explicit Pimsim-ready YOLO11n model.
|
||||
It removes the Softmax nodes from the original model,
|
||||
so that the compiled artifact can be simulated in pimsim-nn.
|
||||
so that the compiled artifact can be simulated in Pimsim.
|
||||
|
||||
## Unsupported and ignored operations
|
||||
|
||||
PIMCOMP's frontend accepts exactly these ONNX operations:
|
||||
Pimcomp's frontend accepts exactly these ONNX operations:
|
||||
|
||||
```text
|
||||
Add, AveragePool, BatchNormalization, Clip, Concat, Conv, Dropout, Flatten,
|
||||
@@ -40,14 +40,14 @@ Gather, Gemm, GlobalAveragePool, LRN, MatMul, MaxPool, Mul, Pad, Relu, Reshape,
|
||||
Shape, Sigmoid, Softmax, Squeeze, Sub, Sum, Tanh, Transpose, Unsqueeze
|
||||
```
|
||||
|
||||
`Constant` is consumed as frontend metadata rather than emitted as a PIMCOMP
|
||||
`Constant` is consumed as frontend metadata rather than emitted as a Pimcomp
|
||||
node. Every other ONNX operation is unsupported: the frontend prints
|
||||
`operation: <type> not considered` and stops at the first occurrence. Thus the
|
||||
complete unsupported set is the complement of the allowlist above for the
|
||||
model's ONNX opset. In particular, YOLO11n contains unsupported `Split` and
|
||||
`Resize` nodes.
|
||||
|
||||
PIMCOMP's low-latency scheduler, hierarchy mapper, and genetic algorithm use
|
||||
Pimcomp's low-latency scheduler, hierarchy mapper, and genetic algorithm use
|
||||
this complete explicit no-consider set:
|
||||
|
||||
```text
|
||||
@@ -63,7 +63,7 @@ specific Shape-Gather-Unsqueeze-Concat shape chain, and merge Pad into its
|
||||
consumer. These transformations do not make an otherwise standalone ignored
|
||||
operation timed.
|
||||
|
||||
`pimsim-nn` consumes PIM ISA instructions. It supports every named opcode
|
||||
Pimsim consumes Pim ISA instructions. It supports every named opcode
|
||||
in the shared serialized range except `vsoftmax` (opcode 21),
|
||||
which is rejected explicitly in both JSON and binary input. It
|
||||
silently ignores no opcode; unknown names and numbers are errors.
|
||||
@@ -71,12 +71,12 @@ silently ignores no opcode; unknown names and numbers are errors.
|
||||
These boundaries explain the dedicated artifacts:
|
||||
|
||||
- GoogLeNet's two LRN nodes and terminal Softmax perform real computation but
|
||||
are ignored by PIMCOMP, so the common latency artifact removes them. Its
|
||||
are ignored by Pimcomp, so the common latency artifact removes them. Its
|
||||
inference Dropout and shape-only Reshape can remain without adding compute.
|
||||
- YOLO11n's latency artifact bypasses exactly its two Softmax nodes so it can
|
||||
run in `pimsim-nn`. Every other node, including MatMul, Transpose, and the
|
||||
run in Pimsim. Every other node, including MatMul, Transpose, and the
|
||||
final detection-decoding tail, remains present and timed by Raptor. No
|
||||
PIMCOMP latency is reported because its frontend stops at `Split` and also
|
||||
Pimcomp latency is reported because its frontend stops at `Split` and also
|
||||
lacks `Resize`; compiling that prefix would not represent YOLO11n.
|
||||
|
||||
The authoritative lists are in
|
||||
@@ -85,16 +85,16 @@ The authoritative lists are in
|
||||
[`ISA.h`](../../../backend-simulators/pim/pimsim-nn/src/isa/ISA.h), and
|
||||
[`Instruction.cpp`](../../../backend-simulators/pim/pimsim-nn/src/isa/Instruction.cpp).
|
||||
|
||||
The PIMCOMP authors did not publish the ONNX checkpoints used by the paper.
|
||||
Running PIMCOMP's frontend on the three Model Zoo files above produces JSON
|
||||
graphs exactly equal to PIMCOMP-NN's bundled `resnet18.json`, `resnet34.json`,
|
||||
The Pimcomp authors did not publish the ONNX checkpoints used by the paper.
|
||||
Running Pimcomp's frontend on the three Model Zoo files above produces JSON
|
||||
graphs exactly equal to Pimcomp's bundled `resnet18.json`, `resnet34.json`,
|
||||
and `googlenet.json`.
|
||||
|
||||
There is no VGG-8 artifact in the ONNX Model Zoo or any PIMCOMP-NN revision.
|
||||
There is no VGG-8 artifact in the ONNX Model Zoo or any Pimcomp revision.
|
||||
The included VGG-8 therefore has deterministic random weights and is suitable
|
||||
for compiler and simulator comparison, not paper-accuracy reproduction. The
|
||||
paper also says that VGG-8 and ResNet-18 were trained on MNIST, while the
|
||||
published PIMCOMP graphs and ResNet Model Zoo artifacts use ImageNet shapes.
|
||||
published Pimcomp graphs and ResNet Model Zoo artifacts use ImageNet shapes.
|
||||
|
||||
Current SHA-256 checksums:
|
||||
|
||||
@@ -113,10 +113,10 @@ The files in
|
||||
[`../../pimsim_configs/pimcomp/`](../../pimsim_configs/pimcomp/)
|
||||
encode Table V's explicit resource parameters.
|
||||
Each profile subdirectory contains pre-generated latency and throughput
|
||||
`pimsim-nn` configs plus its matching mesh; comparison and validation reference
|
||||
Pimsim configs plus its matching mesh; comparison and validation reference
|
||||
these canonical artifacts directly.
|
||||
|
||||
| Config | Cores | Crossbars/core | Crossbar | Cell | PIMCOMP layout |
|
||||
| Config | Cores | Crossbars/core | Crossbar | Cell | Pimcomp layout |
|
||||
|------------------------------|------------------:|---------------:|------------|------:|-----------------|
|
||||
| `arch-a/latency_config.json` | 168 | 96 | `128x128` | 2-bit | `12x14` |
|
||||
| `arch-b/latency_config.json` | 138 | 128 | `128x128` | 2-bit | `6x23` |
|
||||
@@ -125,10 +125,10 @@ these canonical artifacts directly.
|
||||
`adc_count` is 16, matching the paper's 16-bit fixed-point weight precision.
|
||||
The paper does not give a two-dimensional core topology for Arch-A/B, so the
|
||||
factorizations above preserve core count but cannot reproduce unpublished NoC
|
||||
placement details. Released PIMCOMP-NN has no chip-count field; Arch-C is
|
||||
placement details. Released Pimcomp has no chip-count field; Arch-C is
|
||||
therefore flattened to 64 cores and does not model chip boundaries.
|
||||
|
||||
The remaining latency and power values come from PIMCOMP-NN's released default
|
||||
The remaining latency and power values come from Pimcomp's released default
|
||||
configuration. Consequently, instruction/resource comparisons are
|
||||
reproducible, but absolute paper power and energy numbers are not.
|
||||
|
||||
@@ -147,9 +147,9 @@ cmake --build third_party/PIMCOMP-NN/build --target PIMCOMP-NN
|
||||
|
||||
Do not build either project with `ninja` directly.
|
||||
|
||||
## Compile with PIMCOMP
|
||||
## Compile with Pimcomp
|
||||
|
||||
PIMCOMP-NN reads `third_party/PIMCOMP-NN/config.json` directly. Back it up,
|
||||
Pimcomp reads `third_party/PIMCOMP-NN/config.json` directly. Back it up,
|
||||
select one paper profile, and restore it when the shell exits:
|
||||
|
||||
```bash
|
||||
@@ -162,7 +162,7 @@ trap 'cp "$CONFIG_BACKUP" "$PIMCOMP/config.json"' EXIT
|
||||
cp "$PIMCOMP_CONFIGS/arch-a/latency_config.json" "$PIMCOMP/config.json"
|
||||
```
|
||||
|
||||
The Model Zoo files map exactly to PIMCOMP's bundled model names, so compile
|
||||
The Model Zoo files map exactly to Pimcomp's bundled model names, so compile
|
||||
them directly:
|
||||
|
||||
```bash
|
||||
@@ -179,7 +179,7 @@ cd "$PIMCOMP/build"
|
||||
./PIMCOMP-NN -m=googlenet -r=balance -p=element -o=YES -v=YES -s=YES
|
||||
```
|
||||
|
||||
VGG-8 first needs PIMCOMP's JSON frontend. Use a temporary ONNX copy because
|
||||
VGG-8 first needs Pimcomp's JSON frontend. Use a temporary ONNX copy because
|
||||
the released frontend rewrites the input batch dimension in place:
|
||||
|
||||
```bash
|
||||
@@ -201,62 +201,66 @@ random placement code occasionally segfaults; an unchanged retry succeeded in
|
||||
the observed cases.
|
||||
|
||||
The paper's optimizer uses a genetic algorithm with population 200 and up to
|
||||
1000 iterations. The checked-out PIMCOMP submodule already has both paper
|
||||
1000 iterations. The checked-out Pimcomp submodule already has both paper
|
||||
settings in `backend/GeneticAlgorithm.h`; select them with `-r=GA`. Fitness
|
||||
evaluation uses OpenMP and bounded bandwidth timelines. Set `OMP_NUM_THREADS`
|
||||
to control its parallelism; otherwise OpenMP uses the available CPUs. The GA
|
||||
uses the fixed seed `1`, so repeated serial and parallel runs are reproducible.
|
||||
|
||||
## Compare Raptor and PIMCOMP
|
||||
## Compare Raptor and Pimcomp
|
||||
|
||||
The comparison driver uses one random input and one native ONNX-MLIR reference,
|
||||
compiles both instruction streams, runs both through `pimsim-nn`, runs
|
||||
compiles both instruction streams, runs both through Pimsim, runs
|
||||
functional validation through `pim-simulator`, and writes Markdown and JSON
|
||||
reports.
|
||||
|
||||
To reproduce all configured architectures and both latency/throughput modes,
|
||||
use the model-by-model runner. It verifies the paper GA settings, expects Raptor
|
||||
To reproduce the default `arch-a`/`arch-b` architectures and both
|
||||
latency/throughput modes, use the model-by-model runner. Use `--archs` to
|
||||
specify a different architecture set. It verifies the paper GA settings, expects Raptor
|
||||
and the existing `third_party/PIMCOMP-NN/build` tree to already be built, then
|
||||
runs the comparisons in parallel and regenerates `results.csv` from the JSON
|
||||
runs the comparisons in parallel and regenerates `results_comparison.csv` from the JSON
|
||||
reports:
|
||||
|
||||
```bash
|
||||
.venv/bin/python validation/tools/pim/pimcomp/compare/run_pimcomp_paper_latency.py
|
||||
.venv/bin/python validation/tools/pim/pimcomp/compare/run_pimcomp_models.py
|
||||
```
|
||||
|
||||
Use `--arch arch-a --mode latency` for only the Arch-A latency experiment.
|
||||
Use `--archs arch-a --mode latency` for only the Arch-A latency experiment, or
|
||||
`--archs arch-a arch-b arch-c` to run all three architectures.
|
||||
|
||||
Each model directory has a shared ignored `common/` directory containing
|
||||
Each model directory has a shared ignored `artifacts/common/` directory containing
|
||||
`inputs/`, `outputs/`, and the native `runner/`.
|
||||
Pimsim-nn configs and network meshes remain canonical under
|
||||
Pimsim configs and network meshes remain canonical under
|
||||
`validation/pimsim_configs/pimcomp/` and are referenced in place.
|
||||
Architecture- and pipeline-specific `raptor/`, `simulation/`, and PIMCOMP
|
||||
artifacts remain under each comparison directory; PIMCOMP outputs are prepared
|
||||
Architecture- and pipeline-specific `raptor/`, `simulation/`, and Pimcomp
|
||||
artifacts remain under each `artifacts/<arch>/<mode>[/pipelineN]/` comparison
|
||||
directory; ablation variants use
|
||||
`artifacts/<arch>/<mode>[/pipelineN]/ablation/<variant>/`. Pimcomp outputs are prepared
|
||||
once per model/architecture/mode and linked into the other pipeline directories;
|
||||
`comparison_report.{md,json}`
|
||||
live under its `pimcomp/`. The frontend regenerates
|
||||
one isolated `models/JSON/` graph because PIMCOMP requires that relative
|
||||
one isolated `models/JSON/` graph because Pimcomp requires that relative
|
||||
layout; it is removed after a successful backend run and the shared submodule
|
||||
model directory is never modified. The original ONNX model is passed to the
|
||||
frontend unchanged.
|
||||
PIMCOMP's source tree and build directory remain unchanged at runtime. Use
|
||||
Pimcomp's source tree and build directory remain unchanged at runtime. Use
|
||||
`--models vgg8` to run one model, `--mode throughput` to select one mode,
|
||||
`--pipeline 4` to select one throughput pipeline, `--only raptor` or
|
||||
`--only pimcomp` to reuse the other compiler's existing artifacts, `--dry-run`
|
||||
to inspect every command, or `--out-dir PATH` to keep results outside
|
||||
`validation/`. Use `--clean` to remove generated comparison artifacts and
|
||||
summaries. Selecting a subset replaces only those comparison rows and
|
||||
recomputes the aggregate `results.csv`; missing shared inputs, outputs, or the
|
||||
summaries, including stale reference lock files. Selecting a subset replaces only those comparison rows and
|
||||
recomputes the aggregate `results_comparison.csv`; missing shared inputs, outputs, or the
|
||||
reference runner are generated even for an isolated run. Use `--jobs 4` to cap
|
||||
parallel comparisons. The per-stage timeout is unlimited by default; pass a
|
||||
positive `--timeout-seconds` value to impose one.
|
||||
Throughput comparisons default to `pimsim-nn --fast` with a 1000 ms
|
||||
convergence deadline. Add `--no-fast` for authoritative full-duration runs.
|
||||
PIMCOMP receives the original ONNX model, and its frontend applies native
|
||||
Pimcomp receives the original ONNX model, and its frontend applies native
|
||||
BatchNormalization fusion when the graph matches its supported Conv/Gemm pattern.
|
||||
The runner continues after a failed model so all reports are produced.
|
||||
|
||||
The known PIMCOMP batch-scheduling correctness issue and a reproducible
|
||||
The known Pimcomp batch-scheduling correctness issue and a reproducible
|
||||
reference-intermediate prefill experiment are documented in
|
||||
[`validation/tools/pim/pimcomp/correctness/README.md`](../../tools/pim/pimcomp/correctness/README.md).
|
||||
|
||||
@@ -265,9 +269,9 @@ Arch-A low-latency example:
|
||||
```bash
|
||||
RAPTOR_ROOT=$PWD
|
||||
|
||||
"$RAPTOR_ROOT/.venv/bin/python" "$RAPTOR_ROOT/validation/tools/pim/pimcomp/compare/compare_raptor_pimcomp.py" \
|
||||
"$RAPTOR_ROOT/.venv/bin/python" "$RAPTOR_ROOT/validation/tools/pim/pimcomp/compare/compare_raptor_pimcomp_model.py" \
|
||||
--model "$RAPTOR_ROOT/validation/networks/pimcomp_models/resnet34/resnet34-v1-7.onnx" \
|
||||
--out-dir "$RAPTOR_ROOT/validation/networks/pimcomp_models/resnet34/arch-a/latency" \
|
||||
--out-dir "$RAPTOR_ROOT/validation/networks/pimcomp_models/resnet34/artifacts/arch-a/latency" \
|
||||
--pimcomp-config "$RAPTOR_ROOT/validation/pimsim_configs/pimcomp/arch-a/latency_config.json" \
|
||||
--core-count 168 \
|
||||
--crossbar-count 96 \
|
||||
@@ -279,10 +283,10 @@ RAPTOR_ROOT=$PWD
|
||||
```
|
||||
|
||||
Use the same command with
|
||||
`yolo11n/yolo11n-pimsim-nn.onnx` to probe YOLO11n. Released PIMCOMP-NN cannot
|
||||
`yolo11n/yolo11n-pimsim-nn.onnx` to probe YOLO11n. Released Pimcomp cannot
|
||||
compile it: the frontend stops at `/model.2/Split`, and it also has no mapping
|
||||
for YOLO11n's two nearest-neighbor `Resize` nodes. Treating the emitted prefix
|
||||
as YOLO11n would produce a misleading latency, so no PIMCOMP number is
|
||||
as YOLO11n would produce a misleading latency, so no Pimcomp number is
|
||||
reported for this model.
|
||||
|
||||
For Arch-A high throughput, use `--pimsim-mode throughput
|
||||
@@ -303,18 +307,18 @@ generated report.
|
||||
The functional and non-functional simulators intentionally consume different
|
||||
artifacts:
|
||||
|
||||
- Raptor and PIMCOMP are validated against the native ONNX-MLIR reference as
|
||||
- Raptor and Pimcomp are validated against the native ONNX-MLIR reference as
|
||||
FP32 programs in the Rust simulator. Raptor's emitted program is already
|
||||
FP32. The PIMCOMP-to-Rust export expands its element-addressed storage and
|
||||
FP32. The Pimcomp-to-Rust export expands its element-addressed storage and
|
||||
byte-sized transfers to FP32, emits `setbw 32, 32`, and keeps vector
|
||||
`imm_len` fields as element counts.
|
||||
- PIMCOMP's original `SimulationInfo.gz` is copied unchanged for `pimsim-nn`.
|
||||
PIMCOMP hardcodes `setbw 8, 8` and one byte per element without performing
|
||||
- Pimcomp's original `SimulationInfo.gz` is copied unchanged for Pimsim.
|
||||
Pimcomp hardcodes `setbw 8, 8` and one byte per element without performing
|
||||
numerical quantization; this artifact is used only for latency estimation.
|
||||
- Raptor's original FP32 artifact remains unchanged for functional validation.
|
||||
A separate `raptor/pimsim_nn/` view uses `setbw 8, 8` and scales its
|
||||
byte-addressed storage and transfer sizes from four bytes to one byte per
|
||||
element. Vector `imm_len` fields remain element counts. Like PIMCOMP's
|
||||
element. Vector `imm_len` fields remain element counts. Like Pimcomp's
|
||||
artifact, this view is not numerically valid and is used only for a fair
|
||||
non-functional comparison.
|
||||
|
||||
@@ -326,10 +330,10 @@ Current Raptor status:
|
||||
|
||||
- VGG-8, ResNet-18, fixed-batch ResNet-34, and GoogLeNet compile on Arch-A.
|
||||
- Use `googlenet-12-pimsim-nn.onnx` for the paper-matched latency comparison.
|
||||
It removes the two LRN nodes and terminal softmax that PIMCOMP does not
|
||||
It removes the two LRN nodes and terminal softmax that Pimcomp does not
|
||||
schedule.
|
||||
- Raptor currently accepts one square `--crossbar-size`; Arch-C's rectangular
|
||||
`512x1024` arrays can therefore be compiled by PIMCOMP but not compared
|
||||
`512x1024` arrays can therefore be compiled by Pimcomp but not compared
|
||||
exactly with Raptor.
|
||||
|
||||
Do not change the hardware profile to bypass either limitation; that would no
|
||||
@@ -365,7 +369,7 @@ python3 -m venv .venv
|
||||
.venv/bin/python -m pip install numpy onnx onnxruntime colorama
|
||||
|
||||
# Run every configured comparison in parallel.
|
||||
.venv/bin/python validation/tools/pim/pimcomp/compare/run_pimcomp_paper_latency.py
|
||||
.venv/bin/python validation/tools/pim/pimcomp/compare/run_pimcomp_models.py
|
||||
```
|
||||
|
||||
Copy reports back without transferring large compiler artifacts:
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
model,arch,mode,raptor_pipeline,pimcomp_pipeline,raptor_functional_validation,pimcomp_functional_validation,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,better_compiler,speedup
|
||||
vgg8,arch-a,latency,1,element,PASS,PASS,NA,NA,1.465778,7.985074,325.627854,200.111367,477298145.040001,1597904071.120000,raptor,5.45
|
||||
vgg8,arch-b,latency,1,element,PASS,PASS,NA,NA,1.438869,7.152125,304.673458,173.768633,438385194.040001,1242814988.120001,raptor,4.97
|
||||
vgg8,arch-c,latency,1,element,FAIL,PASS,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA
|
||||
vgg8,arch-a,throughput,2,batch,PASS,PASS,1480.000000,1380.000000,0.674197,0.725548,456.064662,475.694103,307477506.200000,345138738.300000,raptor,1.07
|
||||
vgg8,arch-b,throughput,2,batch,PASS,PASS,1160.000000,1080.000000,0.861527,0.921878,333.045167,408.540967,286927542.600000,376624916.800000,raptor,1.07
|
||||
vgg8,arch-c,throughput,2,batch,FAIL,PASS,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA
|
||||
vgg8,arch-a,throughput,4,batch,PASS,PASS,2160.000000,1380.000000,0.462342,0.725548,446.528150,475.694103,206448818.800000,345138738.300000,raptor,1.57
|
||||
vgg8,arch-c,throughput,4,batch,FAIL,PASS,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA
|
||||
vgg8,arch-a,throughput,8,batch,PASS,PASS,831.000000,1380.000000,1.202894,0.725548,331.341726,475.694103,398569128.700000,345138738.300000,pimcomp,1.66
|
||||
vgg8,arch-c,throughput,8,batch,FAIL,PASS,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA
|
||||
resnet18,arch-a,latency,1,element,PASS,PASS,NA,NA,28.099951,58.855175,312.513413,237.590194,8781611597.119984,13983412446.119972,raptor,2.09
|
||||
resnet18,arch-b,latency,1,element,PASS,PASS,NA,NA,34.938563,65.084209,254.439797,200.986958,8889760875.119959,13081077194.119965,raptor,1.86
|
||||
resnet18,arch-c,latency,1,element,FAIL,PASS,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA
|
||||
resnet18,arch-a,throughput,2,batch,PASS,FAIL,20.000000,76.000000,50.000000,13.149606,263.148477,479.788072,13157423870.000000,6309024249.000000,pimcomp,3.80
|
||||
resnet18,arch-b,throughput,2,batch,PASS,FAIL,26.300000,83.200000,38.016529,12.020906,250.553072,483.029728,9525158116.000000,5806454916.000000,pimcomp,3.16
|
||||
resnet18,arch-c,throughput,2,batch,FAIL,FAIL,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA
|
||||
resnet18,arch-a,throughput,4,batch,PASS,FAIL,31.700000,76.000000,31.578947,13.149606,336.949886,479.788072,10640522720.000000,6309024249.000000,pimcomp,2.40
|
||||
resnet18,arch-c,throughput,4,batch,FAIL,FAIL,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA
|
||||
resnet18,arch-a,throughput,8,batch,PASS,FAIL,46.400000,76.000000,21.566110,13.149606,319.292391,479.788072,6885894954.000000,6309024249.000000,pimcomp,1.64
|
||||
resnet18,arch-c,throughput,8,batch,FAIL,FAIL,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA
|
||||
resnet34,arch-a,latency,1,element,PASS,PASS,NA,NA,45.781484,91.608751,326.833876,248.044564,14962939889.679951,22723052668.680016,raptor,2.00
|
||||
resnet34,arch-b,latency,1,element,PASS,PASS,NA,NA,72.119607,94.519582,239.192092,215.513439,17250439680.679901,20370240175.680019,raptor,1.31
|
||||
resnet34,arch-c,latency,1,element,FAIL,PASS,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA
|
||||
resnet34,arch-a,throughput,2,batch,FAIL,FAIL,NA,40.800000,NA,24.522761,NA,506.131154,NA,12411733160.000000,NA,NA
|
||||
resnet34,arch-b,throughput,2,batch,PASS,FAIL,11.600000,43.300000,86.250000,23.076923,260.049856,489.500435,22429300100.000000,11296163880.000000,pimcomp,3.73
|
||||
resnet34,arch-c,throughput,2,batch,FAIL,FAIL,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA
|
||||
resnet34,arch-a,throughput,4,batch,FAIL,FAIL,NA,40.800000,NA,24.522761,NA,506.131154,NA,12411733160.000000,NA,NA
|
||||
resnet34,arch-c,throughput,4,batch,FAIL,FAIL,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA
|
||||
resnet34,arch-a,throughput,8,batch,PASS,FAIL,24.300000,40.800000,41.176471,24.522761,271.814969,506.131154,11192381060.000000,12411733160.000000,pimcomp,1.68
|
||||
resnet34,arch-c,throughput,8,batch,FAIL,FAIL,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA
|
||||
googlenet,arch-a,latency,1,element,PASS,PASS,NA,NA,13.032305,62.923369,465.072002,231.194088,6060960174.919998,14547510894.240002,raptor,4.83
|
||||
googlenet,arch-b,latency,1,element,PASS,PASS,NA,NA,16.086822,37.935747,378.951815,242.039077,6096130396.919978,9181933205.239973,raptor,2.36
|
||||
googlenet,arch-c,latency,1,element,FAIL,PASS,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA
|
||||
googlenet,arch-a,throughput,2,batch,PASS,FAIL,55.000000,66.200000,18.181818,15.094340,346.696726,434.959232,6303576840.000000,6565422375.000000,pimcomp,1.20
|
||||
googlenet,arch-b,throughput,2,batch,PASS,FAIL,55.000000,72.000000,18.181818,13.888889,314.645370,417.992095,5720824912.000000,5805445765.000000,pimcomp,1.31
|
||||
googlenet,arch-c,throughput,2,batch,FAIL,FAIL,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA
|
||||
googlenet,arch-a,throughput,4,batch,PASS,FAIL,111.000000,66.200000,9.012016,15.094340,373.195227,434.959232,3363241363.000000,6565422375.000000,raptor,1.68
|
||||
googlenet,arch-c,throughput,4,batch,FAIL,FAIL,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA
|
||||
googlenet,arch-a,throughput,8,batch,PASS,FAIL,61.300000,66.200000,16.310680,15.094340,341.592923,434.959232,5571612719.000000,6565422375.000000,pimcomp,1.08
|
||||
googlenet,arch-c,throughput,8,batch,FAIL,FAIL,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA
|
||||
|
@@ -1,6 +0,0 @@
|
||||
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-pimsim-nn,PASS,6.573 s,10.74 MiB,22.41 MiB,168,7176,13.371204 ms,457.538139 mW,6117835798.919991 pJ
|
||||
yolo11n-pimsim-nn,FAIL,58.572 s,82.55 MiB,185.68 MiB,168,6484,885.264931 ms,189.218985 mW,167508931321.001465 pJ
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Operation Validation Suite
|
||||
# Operation validation suite
|
||||
|
||||
This directory contains the ONNX models used by `validation/validate.py` to
|
||||
validate individual operations through compilation, PIM simulation, and
|
||||
validate individual operations through compilation, Pim simulation, and
|
||||
comparison with the ONNX-MLIR reference runtime.
|
||||
|
||||
## Naming
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
Operation,Arch,Result (l),Result (t),Compile (l),Host mem (l),Cores mem (l),Cores (l),Xbars (l),Latency (l),Power (l),Energy (l),Compile (t),Host mem (t),Cores mem (t),Cores (t),Xbars (t),Avg latency (t),Throughput (t),Avg power (t),Avg energy (t)
|
||||
add/after_gemm,arch-a,PASS,PASS,0.058 s,0.01 MiB,0.01 MiB,5,4,0.01 ms,104.70 mW,815012.96 pJ,0.057 s,0.01 MiB,0.01 MiB,6,4,145000.00 samples/s,0.01 ms,31.45 mW,216167.21 pJ/it
|
||||
add/basic,arch-a,PASS,PASS,0.048 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.22 mW,25266.00 pJ,0.050 s,0.00 MiB,0.00 MiB,1,0,3120000.00 samples/s,0.00 ms,2.23 mW,658.67 pJ/it
|
||||
add/broadcast_row,arch-a,PASS,PASS,0.048 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.22 mW,25266.00 pJ,0.051 s,0.00 MiB,0.00 MiB,1,0,3120000.00 samples/s,0.00 ms,2.23 mW,658.67 pJ/it
|
||||
add/channel_broadcast_1024,arch-a,PASS,PASS,0.049 s,0.02 MiB,0.01 MiB,1,0,0.01 ms,78.12 mW,540030.00 pJ,0.051 s,0.02 MiB,0.01 MiB,1,0,145000.00 samples/s,0.01 ms,2.11 mW,13388.67 pJ/it
|
||||
add/leading_dimension_broadcast,arch-a,PASS,PASS,0.051 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.22 mW,25266.00 pJ,0.049 s,0.00 MiB,0.00 MiB,1,0,3120000.00 samples/s,0.00 ms,2.23 mW,658.67 pJ/it
|
||||
concat/channel_axis,arch-a,PASS,PASS,0.048 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.16 mW,35718.00 pJ,0.050 s,0.00 MiB,0.00 MiB,1,0,2200000.00 samples/s,0.00 ms,2.16 mW,934.67 pJ/it
|
||||
concat/negative_axis,arch-a,PASS,PASS,0.050 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.09 mW,81450.00 pJ,0.051 s,0.00 MiB,0.00 MiB,1,0,961000.00 samples/s,0.00 ms,2.09 mW,2108.00 pJ/it
|
||||
concat/three_inputs_channel_axis,arch-a,PASS,PASS,0.048 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.15 mW,50328.00 pJ,0.047 s,0.00 MiB,0.00 MiB,1,0,1560000.00 samples/s,0.00 ms,2.15 mW,1332.67 pJ/it
|
||||
conv/batch_2,arch-a,PASS,PASS,0.059 s,0.00 MiB,0.00 MiB,2,2,0.01 ms,82.62 mW,1131451.48 pJ,0.062 s,0.00 MiB,0.01 MiB,4,2,129000.00 samples/s,0.01 ms,51.25 mW,406238.48 pJ/it
|
||||
conv/batch_4_pointwise,arch-a,PASS,PASS,0.058 s,0.00 MiB,0.01 MiB,5,4,0.00 ms,116.08 mW,456420.96 pJ,0.061 s,0.00 MiB,0.01 MiB,5,4,243000.00 samples/s,0.00 ms,44.13 mW,180813.46 pJ/it
|
||||
conv/depthwise_1024_channels,arch-a,PASS,PASS,0.080 s,0.19 MiB,0.38 MiB,129,128,0.22 ms,178.45 mW,39393966.72 pJ,0.141 s,0.36 MiB,0.48 MiB,87,128,3620.00 samples/s,0.28 ms,131.43 mW,37256350.26 pJ/it
|
||||
conv/depthwise_grouped,arch-a,PASS,PASS,0.056 s,0.01 MiB,0.00 MiB,5,4,0.01 ms,107.78 mW,671878.96 pJ,0.061 s,0.01 MiB,0.00 MiB,7,4,235000.00 samples/s,0.00 ms,53.10 mW,227356.96 pJ/it
|
||||
conv/dilated_3x3,arch-a,PASS,PASS,0.061 s,0.01 MiB,0.01 MiB,10,9,0.01 ms,118.77 mW,1034819.16 pJ,0.071 s,0.01 MiB,0.01 MiB,12,9,119000.00 samples/s,0.01 ms,61.00 mW,511357.16 pJ/it
|
||||
conv/dynamic,arch-a,PASS,PASS,0.056 s,0.00 MiB,0.00 MiB,5,0,0.00 ms,92.28 mW,169336.00 pJ,0.057 s,0.00 MiB,0.00 MiB,6,0,784000.00 samples/s,0.00 ms,18.61 mW,26517.00 pJ/it
|
||||
conv/explicit_padding,arch-a,PASS,PASS,0.060 s,0.01 MiB,0.02 MiB,17,16,0.01 ms,145.34 mW,1454397.84 pJ,0.064 s,0.01 MiB,0.02 MiB,19,16,153000.00 samples/s,0.01 ms,109.61 mW,715669.59 pJ/it
|
||||
conv/grouped_many_groups,arch-a,PASS,PASS,0.498 s,0.05 MiB,0.09 MiB,65,64,0.18 ms,142.21 mW,25867112.36 pJ,0.547 s,0.11 MiB,0.79 MiB,127,64,3750.00 samples/s,0.27 ms,141.11 mW,43353235.67 pJ/it
|
||||
conv/grouped_two_groups,arch-a,PASS,PASS,0.064 s,0.00 MiB,0.00 MiB,3,2,0.01 ms,101.46 mW,543914.48 pJ,0.066 s,0.00 MiB,0.01 MiB,9,2,146000.00 samples/s,0.01 ms,108.34 mW,741101.98 pJ/it
|
||||
conv/huge_pointwise_1024,arch-a,PASS,PASS,0.166 s,0.01 MiB,0.11 MiB,73,64,0.02 ms,249.55 mW,3896647.36 pJ,0.182 s,0.04 MiB,0.11 MiB,74,64,33300.00 samples/s,0.03 ms,133.89 mW,4052259.07 pJ/it
|
||||
conv/huge_pointwise_1024_dynamic,arch-a,PASS,PASS,0.084 s,8.04 MiB,12.61 MiB,168,0,2.63 ms,169.52 mW,445489032.00 pJ,0.263 s,11.49 MiB,10.61 MiB,127,0,213.00 samples/s,4.70 ms,164.24 mW,811591564.70 pJ/it
|
||||
conv/input_224_7x7_stride2,arch-a,PASS,PASS,0.775 s,24.14 MiB,61.87 MiB,168,169,38.41 ms,185.26 mW,7116544212.12 pJ,1.142 s,46.43 MiB,73.41 MiB,126,153,27.30 samples/s,36.66 ms,177.05 mW,6915042527.00 pJ/it
|
||||
conv/kernel_2x2,arch-a,PASS,PASS,0.056 s,0.00 MiB,0.00 MiB,1,1,0.00 ms,83.83 mW,360568.24 pJ,0.055 s,0.00 MiB,0.00 MiB,3,1,334000.00 samples/s,0.00 ms,51.45 mW,171905.91 pJ/it
|
||||
conv/kernel_3x3,arch-a,PASS,PASS,0.060 s,0.01 MiB,0.01 MiB,10,9,0.01 ms,123.80 mW,889640.16 pJ,0.063 s,0.01 MiB,0.01 MiB,12,9,219000.00 samples/s,0.00 ms,83.71 mW,382318.91 pJ/it
|
||||
conv/kernel_equals_input_spatial,arch-a,PASS,PASS,0.054 s,0.00 MiB,0.00 MiB,2,2,0.00 ms,89.61 mW,415689.48 pJ,0.057 s,0.00 MiB,0.00 MiB,4,2,293000.00 samples/s,0.00 ms,59.39 mW,204713.48 pJ/it
|
||||
conv/large_input_channels_1x1,arch-a,PASS,PASS,0.096 s,0.01 MiB,0.02 MiB,9,8,0.01 ms,117.82 mW,901121.92 pJ,0.092 s,0.01 MiB,0.02 MiB,10,8,132000.00 samples/s,0.01 ms,59.24 mW,447909.92 pJ/it
|
||||
conv/large_output_channels_1x1,arch-a,PASS,PASS,0.089 s,0.01 MiB,0.02 MiB,17,8,0.01 ms,128.44 mW,1139415.92 pJ,0.095 s,0.01 MiB,0.02 MiB,18,8,123000.00 samples/s,0.01 ms,43.92 mW,355735.17 pJ/it
|
||||
conv/large_spatial,arch-a,PASS,PASS,0.059 s,0.01 MiB,0.04 MiB,37,36,0.02 ms,172.07 mW,2928344.64 pJ,0.078 s,0.01 MiB,0.04 MiB,39,36,88500.00 samples/s,0.01 ms,169.91 mW,1920027.89 pJ/it
|
||||
conv/multi_channel,arch-a,PASS,PASS,0.057 s,0.00 MiB,0.00 MiB,4,3,0.01 ms,105.68 mW,685040.72 pJ,0.060 s,0.00 MiB,0.00 MiB,4,3,146000.00 samples/s,0.01 ms,30.09 mW,205787.97 pJ/it
|
||||
conv/non_square_kernel_1x3,arch-a,PASS,PASS,0.055 s,0.00 MiB,0.00 MiB,3,2,0.01 ms,99.35 mW,679752.48 pJ,0.059 s,0.00 MiB,0.00 MiB,3,2,141000.00 samples/s,0.01 ms,12.12 mW,85739.48 pJ/it
|
||||
conv/non_square_kernel_3x1,arch-a,PASS,PASS,0.058 s,0.00 MiB,0.00 MiB,3,2,0.01 ms,95.89 mW,1292976.48 pJ,0.061 s,0.00 MiB,0.00 MiB,3,2,72900.00 samples/s,0.01 ms,8.83 mW,121109.48 pJ/it
|
||||
conv/non_uniform_stride,arch-a,PASS,PASS,0.061 s,0.00 MiB,0.00 MiB,4,3,0.01 ms,104.05 mW,790874.72 pJ,0.059 s,0.00 MiB,0.00 MiB,4,3,131000.00 samples/s,0.01 ms,29.05 mW,221084.97 pJ/it
|
||||
conv/output_channel_grouping_minimal,arch-a,PASS,PASS,0.089 s,0.10 MiB,0.34 MiB,131,128,0.26 ms,170.73 mW,44125916.72 pJ,0.181 s,0.18 MiB,0.33 MiB,131,128,3910.00 samples/s,0.26 ms,181.50 mW,48146979.72 pJ/it
|
||||
conv/pointwise_1x1,arch-a,PASS,PASS,0.071 s,0.00 MiB,0.00 MiB,1,1,0.01 ms,80.24 mW,987244.24 pJ,0.084 s,0.00 MiB,0.00 MiB,3,1,131000.00 samples/s,0.01 ms,47.08 mW,380210.74 pJ/it
|
||||
conv/pointwise_tiled_chain,arch-a,PASS,PASS,0.819 s,0.01 MiB,0.04 MiB,20,80,0.04 ms,153.88 mW,6445455.20 pJ,0.777 s,0.05 MiB,0.08 MiB,22,80,12500.00 samples/s,0.08 ms,69.67 mW,5573378.45 pJ/it
|
||||
conv/real_asymmetric_padding,arch-a,PASS,PASS,0.074 s,0.01 MiB,0.03 MiB,29,28,0.01 ms,153.67 mW,2221606.72 pJ,0.087 s,0.00 MiB,0.03 MiB,31,28,104000.00 samples/s,0.01 ms,135.38 mW,1295814.97 pJ/it
|
||||
conv/relu_conv_store,arch-a,PASS,PASS,0.102 s,0.16 MiB,0.67 MiB,168,184,0.56 ms,183.08 mW,103057892.80 pJ,0.291 s,0.32 MiB,0.67 MiB,168,166,1640.00 samples/s,0.61 ms,182.39 mW,113644022.20 pJ/it
|
||||
conv/same_lower_3x3,arch-a,PASS,PASS,0.069 s,0.01 MiB,0.02 MiB,26,25,0.01 ms,166.15 mW,2215009.00 pJ,0.088 s,0.01 MiB,0.03 MiB,28,25,114000.00 samples/s,0.01 ms,134.46 mW,1180460.00 pJ/it
|
||||
conv/same_padding_3x3,arch-a,PASS,PASS,0.062 s,0.01 MiB,0.02 MiB,26,25,0.01 ms,166.15 mW,2215009.00 pJ,0.083 s,0.01 MiB,0.03 MiB,28,25,114000.00 samples/s,0.01 ms,134.46 mW,1180460.00 pJ/it
|
||||
conv/strategy_depthwise_16,arch-a,PASS,PASS,0.093 s,0.06 MiB,0.35 MiB,168,168,0.34 ms,197.94 mW,66331479.08 pJ,0.298 s,0.15 MiB,0.37 MiB,168,168,2890.00 samples/s,0.35 ms,196.87 mW,70672344.81 pJ/it
|
||||
conv/strategy_input_k_tiled,arch-a,PASS,PASS,0.079 s,0.08 MiB,0.27 MiB,109,108,0.35 ms,170.81 mW,60422605.92 pJ,0.120 s,0.16 MiB,0.30 MiB,85,101,3520.00 samples/s,0.28 ms,138.29 mW,40167697.42 pJ/it
|
||||
conv/strategy_output_channel_tiled,arch-a,PASS,PASS,0.079 s,0.03 MiB,0.16 MiB,74,72,0.09 ms,155.74 mW,14244739.28 pJ,0.146 s,0.08 MiB,0.25 MiB,111,72,12000.00 samples/s,0.08 ms,137.73 mW,12695085.91 pJ/it
|
||||
conv/strategy_streamed_packed,arch-a,PASS,PASS,0.168 s,3.34 MiB,7.89 MiB,168,168,9.35 ms,179.86 mW,1682364509.56 pJ,0.453 s,5.38 MiB,7.87 MiB,127,126,119.00 samples/s,8.39 ms,175.52 mW,1616768905.00 pJ/it
|
||||
conv/strategy_streamed_patch,arch-a,PASS,PASS,0.110 s,0.34 MiB,1.32 MiB,168,168,1.90 ms,181.91 mW,346476645.64 pJ,0.416 s,0.84 MiB,1.29 MiB,127,126,525.00 samples/s,1.90 ms,176.18 mW,359355537.30 pJ/it
|
||||
conv/strategy_tiled_2d,arch-a,PASS,PASS,0.170 s,0.11 MiB,0.44 MiB,168,168,0.42 ms,182.13 mW,75690907.84 pJ,0.235 s,0.28 MiB,0.45 MiB,130,168,3010.00 samples/s,0.33 ms,178.45 mW,62153061.01 pJ/it
|
||||
conv/stride_2,arch-a,PASS,PASS,0.060 s,0.01 MiB,0.00 MiB,5,4,0.01 ms,110.78 mW,580154.96 pJ,0.061 s,0.01 MiB,0.00 MiB,7,4,297000.00 samples/s,0.00 ms,48.26 mW,163092.63 pJ/it
|
||||
conv/with_bias_3x3,arch-a,PASS,PASS,0.069 s,0.00 MiB,0.01 MiB,4,3,0.01 ms,104.16 mW,776220.72 pJ,0.066 s,0.00 MiB,0.01 MiB,4,3,128000.00 samples/s,0.01 ms,28.71 mW,224217.97 pJ/it
|
||||
conv/with_constant,arch-a,PASS,PASS,0.058 s,0.00 MiB,0.00 MiB,1,1,0.01 ms,81.74 mW,541270.24 pJ,0.067 s,0.00 MiB,0.00 MiB,4,1,138000.00 samples/s,0.01 ms,90.41 mW,664255.74 pJ/it
|
||||
conv/without_kernel_shape_attr,arch-a,PASS,PASS,0.064 s,0.01 MiB,0.01 MiB,10,9,0.01 ms,123.80 mW,889640.16 pJ,0.066 s,0.01 MiB,0.01 MiB,12,9,219000.00 samples/s,0.00 ms,83.71 mW,382318.91 pJ/it
|
||||
conv/yolo11n_depthwise_head,arch-a,PASS,PASS,2.447 s,8.66 MiB,34.24 MiB,168,255,42.70 ms,200.52 mW,8562449708.00 pJ,3.011 s,22.90 MiB,34.20 MiB,168,216,19.40 samples/s,51.59 ms,195.15 mW,10205156420.00 pJ/it
|
||||
conv/yolo11n_heavy,arch-a,PASS,PASS,0.585 s,4.82 MiB,19.10 MiB,161,800,8.54 ms,350.86 mW,2994764012.00 pJ,1.897 s,10.40 MiB,20.59 MiB,161,800,83.80 samples/s,11.93 ms,299.23 mW,3739084612.00 pJ/it
|
||||
conv/yolo11n_stem,arch-a,PASS,PASS,0.996 s,12.86 MiB,37.59 MiB,168,488,14.24 ms,301.23 mW,4289558753.00 pJ,1.726 s,22.34 MiB,32.79 MiB,168,362,23.80 samples/s,42.04 ms,214.78 mW,9030156087.00 pJ/it
|
||||
div/after_gemm,arch-a,PASS,PASS,0.065 s,0.01 MiB,0.01 MiB,5,4,0.01 ms,104.70 mW,815012.96 pJ,0.071 s,0.01 MiB,0.01 MiB,6,4,145000.00 samples/s,0.01 ms,31.45 mW,216167.21 pJ/it
|
||||
div/basic,arch-a,PASS,PASS,0.057 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.22 mW,25266.00 pJ,0.057 s,0.00 MiB,0.00 MiB,1,0,3120000.00 samples/s,0.00 ms,2.23 mW,658.67 pJ/it
|
||||
div/channel_broadcast_1024,arch-a,PASS,PASS,0.060 s,0.02 MiB,0.01 MiB,1,0,0.01 ms,78.12 mW,540030.00 pJ,0.056 s,0.02 MiB,0.01 MiB,1,0,145000.00 samples/s,0.01 ms,2.11 mW,13388.67 pJ/it
|
||||
div/leading_dimension_broadcast,arch-a,PASS,PASS,0.067 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.22 mW,25266.00 pJ,0.060 s,0.00 MiB,0.00 MiB,1,0,3120000.00 samples/s,0.00 ms,2.23 mW,658.67 pJ/it
|
||||
div/runtime_scalar_rhs,arch-a,PASS,PASS,0.057 s,0.02 MiB,0.01 MiB,1,0,0.01 ms,78.12 mW,540030.00 pJ,0.055 s,0.02 MiB,0.01 MiB,1,0,145000.00 samples/s,0.01 ms,2.11 mW,13388.67 pJ/it
|
||||
div/scalar_constant,arch-a,PASS,PASS,0.054 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.22 mW,25266.00 pJ,0.055 s,0.00 MiB,0.00 MiB,1,0,3120000.00 samples/s,0.00 ms,2.23 mW,658.67 pJ/it
|
||||
gather/3d_input_axis1,arch-a,PASS,PASS,0.061 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.08 mW,45990.00 pJ,0.056 s,0.00 MiB,0.00 MiB,1,0,1700000.00 samples/s,0.00 ms,2.08 mW,1174.67 pJ/it
|
||||
gather/axis0_matrix_indices,arch-a,PASS,PASS,0.083 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.07 mW,54414.00 pJ,0.072 s,0.00 MiB,0.00 MiB,1,0,1440000.00 samples/s,0.00 ms,2.07 mW,1390.67 pJ/it
|
||||
gather/axis1,arch-a,PASS,PASS,0.064 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.06 mW,62526.00 pJ,0.066 s,0.00 MiB,0.00 MiB,1,0,1250000.00 samples/s,0.00 ms,2.06 mW,1598.67 pJ/it
|
||||
gather/negative_axis,arch-a,PASS,PASS,0.078 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.03 mW,112134.00 pJ,0.064 s,0.00 MiB,0.00 MiB,1,0,697000.00 samples/s,0.00 ms,2.03 mW,2870.67 pJ/it
|
||||
gather/negative_indices,arch-a,PASS,PASS,0.062 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.13 mW,29376.00 pJ,0.062 s,0.00 MiB,0.00 MiB,1,0,2670000.00 samples/s,0.00 ms,2.12 mW,748.67 pJ/it
|
||||
gemm/alpha_beta,arch-a,PASS,PASS,0.068 s,0.01 MiB,0.01 MiB,5,4,0.01 ms,105.27 mW,784908.96 pJ,0.070 s,0.01 MiB,0.01 MiB,6,4,153000.00 samples/s,0.01 ms,32.18 mW,210663.21 pJ/it
|
||||
gemm/bias_rank2_broadcast,arch-a,PASS,PASS,0.063 s,0.00 MiB,0.01 MiB,5,4,0.01 ms,105.98 mW,749484.96 pJ,0.063 s,0.01 MiB,0.01 MiB,6,4,168000.00 samples/s,0.01 ms,33.68 mW,200469.21 pJ/it
|
||||
gemm/dynamic,arch-a,PASS,PASS,0.065 s,0.00 MiB,0.00 MiB,5,0,0.00 ms,91.48 mW,221475.00 pJ,0.062 s,0.00 MiB,0.00 MiB,5,0,471000.00 samples/s,0.00 ms,20.30 mW,43105.75 pJ/it
|
||||
gemm/dynamic_alpha,arch-a,PASS,PASS,0.064 s,0.00 MiB,0.00 MiB,5,0,0.00 ms,91.42 mW,298198.00 pJ,0.063 s,0.00 MiB,0.00 MiB,5,0,337000.00 samples/s,0.00 ms,20.28 mW,60117.75 pJ/it
|
||||
gemm/dynamic_beta,arch-a,PASS,PASS,0.070 s,0.00 MiB,0.00 MiB,5,0,0.00 ms,91.32 mW,398595.00 pJ,0.059 s,0.00 MiB,0.00 MiB,5,0,246000.00 samples/s,0.00 ms,20.21 mW,82201.75 pJ/it
|
||||
gemm/dynamic_bias,arch-a,PASS,PASS,0.066 s,0.00 MiB,0.00 MiB,5,0,0.00 ms,91.45 mW,243703.00 pJ,0.063 s,0.00 MiB,0.00 MiB,5,0,422000.00 samples/s,0.00 ms,20.28 mW,48009.75 pJ/it
|
||||
gemm/dynamic_bias_alpha_beta,arch-a,PASS,PASS,0.061 s,0.00 MiB,0.00 MiB,5,0,0.01 ms,91.28 mW,513811.00 pJ,0.077 s,0.00 MiB,0.00 MiB,5,0,188000.00 samples/s,0.01 ms,20.20 mW,107673.75 pJ/it
|
||||
gemm/dynamic_transpose_b,arch-a,PASS,PASS,0.064 s,0.00 MiB,0.00 MiB,5,0,0.00 ms,91.38 mW,118883.00 pJ,0.065 s,0.00 MiB,0.00 MiB,5,0,781000.00 samples/s,0.00 ms,20.51 mW,26151.50 pJ/it
|
||||
gemm/huge_1024,arch-a,PASS,PASS,0.182 s,0.01 MiB,0.10 MiB,73,64,0.02 ms,215.04 mW,3767885.36 pJ,0.220 s,0.03 MiB,0.10 MiB,73,64,36900.00 samples/s,0.03 ms,148.63 mW,4053069.50 pJ/it
|
||||
gemm/large,arch-a,PASS,PASS,0.074 s,0.02 MiB,0.03 MiB,17,16,0.01 ms,140.15 mW,1573768.84 pJ,0.082 s,0.02 MiB,0.03 MiB,17,16,88800.00 samples/s,0.01 ms,84.59 mW,942235.51 pJ/it
|
||||
gemm/large_k_small_n,arch-a,PASS,PASS,0.142 s,0.01 MiB,0.01 MiB,9,8,0.00 ms,133.48 mW,633769.92 pJ,0.119 s,0.01 MiB,0.01 MiB,9,8,194000.00 samples/s,0.01 ms,76.91 mW,390598.09 pJ/it
|
||||
gemm/non_square,arch-a,PASS,PASS,0.069 s,0.00 MiB,0.01 MiB,5,4,0.00 ms,118.96 mW,419565.96 pJ,0.069 s,0.00 MiB,0.01 MiB,5,4,270000.00 samples/s,0.00 ms,46.78 mW,172713.46 pJ/it
|
||||
gemm/scalar_bias,arch-a,PASS,PASS,0.062 s,0.00 MiB,0.01 MiB,5,4,0.01 ms,105.98 mW,749484.96 pJ,0.066 s,0.01 MiB,0.01 MiB,6,4,168000.00 samples/s,0.01 ms,33.68 mW,200469.21 pJ/it
|
||||
gemm/small,arch-a,PASS,PASS,0.058 s,0.00 MiB,0.00 MiB,2,2,0.00 ms,90.14 mW,398436.48 pJ,0.059 s,0.00 MiB,0.00 MiB,4,2,327000.00 samples/s,0.00 ms,61.13 mW,188023.48 pJ/it
|
||||
gemm/small_k_large_n,arch-a,PASS,PASS,0.112 s,0.01 MiB,0.02 MiB,17,8,0.01 ms,131.01 mW,1043061.92 pJ,0.100 s,0.01 MiB,0.02 MiB,18,8,141000.00 samples/s,0.01 ms,47.48 mW,336507.17 pJ/it
|
||||
gemm/square_weights,arch-a,PASS,PASS,0.080 s,0.03 MiB,0.08 MiB,42,40,0.02 ms,151.77 mW,3284393.60 pJ,0.100 s,0.03 MiB,0.09 MiB,44,40,51800.00 samples/s,0.02 ms,115.71 mW,2278356.60 pJ/it
|
||||
gemm/transpose_a,arch-a,PASS,PASS,0.063 s,0.00 MiB,0.01 MiB,5,4,0.01 ms,109.14 mW,628868.96 pJ,0.062 s,0.00 MiB,0.01 MiB,6,4,212000.00 samples/s,0.00 ms,38.03 mW,179501.21 pJ/it
|
||||
gemm/transpose_a_and_b,arch-a,PASS,PASS,0.070 s,0.00 MiB,0.01 MiB,5,4,0.01 ms,109.14 mW,628868.96 pJ,0.080 s,0.00 MiB,0.01 MiB,6,4,212000.00 samples/s,0.00 ms,38.03 mW,179501.21 pJ/it
|
||||
gemm/transpose_b,arch-a,PASS,PASS,0.065 s,0.00 MiB,0.01 MiB,5,4,0.00 ms,118.96 mW,419565.96 pJ,0.069 s,0.00 MiB,0.01 MiB,5,4,270000.00 samples/s,0.00 ms,46.78 mW,172713.46 pJ/it
|
||||
gemm/transpose_b_with_bias,arch-a,PASS,PASS,0.064 s,0.01 MiB,0.01 MiB,5,4,0.01 ms,110.55 mW,557818.96 pJ,0.071 s,0.01 MiB,0.01 MiB,5,4,191000.00 samples/s,0.01 ms,38.98 mW,203117.46 pJ/it
|
||||
gemm/with_bias,arch-a,PASS,PASS,0.064 s,0.01 MiB,0.01 MiB,5,4,0.01 ms,108.77 mW,604966.96 pJ,0.062 s,0.01 MiB,0.01 MiB,5,4,175000.00 samples/s,0.01 ms,37.33 mW,213443.71 pJ/it
|
||||
gemv/all_constant,arch-a,PASS,PASS,0.071 s,0.00 MiB,0.00 MiB,0,0,0.00 ms,2.00 mW,0.00 pJ,0.061 s,0.00 MiB,0.00 MiB,0,0,0.00 samples/s,0.00 ms,2.00 mW,0.00 pJ/it
|
||||
gemv/constant_weight,arch-a,PASS,PASS,0.100 s,0.00 MiB,0.01 MiB,6,4,0.01 ms,111.15 mW,573535.96 pJ,0.079 s,0.00 MiB,0.01 MiB,8,4,235000.00 samples/s,0.00 ms,68.14 mW,293181.96 pJ/it
|
||||
gemv/non_uniform_bias,arch-a,PASS,PASS,0.080 s,0.00 MiB,0.01 MiB,6,4,0.01 ms,109.82 mW,609371.96 pJ,0.081 s,0.00 MiB,0.01 MiB,8,4,215000.00 samples/s,0.00 ms,66.23 mW,310779.96 pJ/it
|
||||
gemv/scalar_bias,arch-a,PASS,PASS,0.092 s,0.00 MiB,0.01 MiB,6,4,0.01 ms,109.82 mW,609371.96 pJ,0.095 s,0.00 MiB,0.01 MiB,8,4,215000.00 samples/s,0.00 ms,66.23 mW,310779.96 pJ/it
|
||||
gemv/uniform_bias,arch-a,PASS,PASS,0.090 s,0.00 MiB,0.01 MiB,6,4,0.01 ms,109.82 mW,609371.96 pJ,0.149 s,0.00 MiB,0.01 MiB,8,4,215000.00 samples/s,0.00 ms,66.23 mW,310779.96 pJ/it
|
||||
matmul/basic,arch-a,PASS,PASS,0.089 s,0.00 MiB,0.00 MiB,2,2,0.00 ms,90.14 mW,398436.48 pJ,0.071 s,0.00 MiB,0.00 MiB,4,2,327000.00 samples/s,0.00 ms,61.13 mW,188023.48 pJ/it
|
||||
matmul/batched_3d,arch-a,PASS,PASS,0.099 s,0.00 MiB,0.01 MiB,5,4,0.01 ms,108.59 mW,646972.96 pJ,0.086 s,0.00 MiB,0.01 MiB,6,4,207000.00 samples/s,0.00 ms,37.52 mW,181507.21 pJ/it
|
||||
matmul/batched_3d_dynamic,arch-a,PASS,PASS,0.065 s,0.00 MiB,0.00 MiB,4,0,0.00 ms,92.19 mW,167975.00 pJ,0.065 s,0.00 MiB,0.00 MiB,5,0,736000.00 samples/s,0.00 ms,17.42 mW,23971.67 pJ/it
|
||||
matmul/batched_left_constant,arch-a,PASS,PASS,0.069 s,0.00 MiB,0.02 MiB,9,8,0.01 ms,114.39 mW,1009105.92 pJ,0.070 s,0.01 MiB,0.02 MiB,11,8,133000.00 samples/s,0.01 ms,58.19 mW,441494.75 pJ/it
|
||||
matmul/batched_lhs_broadcast,arch-a,PASS,PASS,0.065 s,0.00 MiB,0.01 MiB,5,4,0.01 ms,109.39 mW,621440.96 pJ,0.071 s,0.00 MiB,0.01 MiB,6,4,217000.00 samples/s,0.00 ms,38.52 mW,177665.21 pJ/it
|
||||
matmul/batched_rhs_broadcast,arch-a,PASS,PASS,0.065 s,0.00 MiB,0.01 MiB,5,4,0.01 ms,108.59 mW,646972.96 pJ,0.065 s,0.00 MiB,0.01 MiB,6,4,207000.00 samples/s,0.00 ms,37.52 mW,181507.21 pJ/it
|
||||
matmul/dynamic,arch-a,PASS,PASS,0.061 s,0.00 MiB,0.00 MiB,5,0,0.00 ms,91.42 mW,148195.00 pJ,0.080 s,0.00 MiB,0.00 MiB,5,0,628000.00 samples/s,0.00 ms,20.41 mW,32505.75 pJ/it
|
||||
matmul/huge_1024,arch-a,PASS,PASS,0.188 s,0.01 MiB,0.10 MiB,73,64,0.02 ms,215.04 mW,3767885.36 pJ,0.224 s,0.03 MiB,0.10 MiB,73,64,36900.00 samples/s,0.03 ms,148.63 mW,4053069.50 pJ/it
|
||||
matmul/left_constant,arch-a,PASS,PASS,0.076 s,0.00 MiB,0.01 MiB,5,4,0.01 ms,108.86 mW,637168.96 pJ,0.068 s,0.00 MiB,0.01 MiB,6,4,208000.00 samples/s,0.00 ms,37.62 mW,180976.21 pJ/it
|
||||
matmul/matrix_vector,arch-a,PASS,PASS,0.120 s,0.52 MiB,0.78 MiB,168,173,0.38 ms,202.13 mW,77751814.88 pJ,0.384 s,0.97 MiB,0.72 MiB,127,173,2250.00 samples/s,0.44 ms,193.72 mW,92630594.79 pJ/it
|
||||
matmul/vector_matrix,arch-a,PASS,PASS,0.099 s,0.01 MiB,0.01 MiB,9,8,0.01 ms,118.68 mW,879301.92 pJ,0.104 s,0.01 MiB,0.01 MiB,9,8,132000.00 samples/s,0.01 ms,45.10 mW,342617.42 pJ/it
|
||||
matmul/yolo_attention,arch-a,PASS,PASS,0.526 s,1.02 MiB,43.44 MiB,168,0,8.15 ms,170.00 mW,1385775865.00 pJ,0.796 s,13.76 MiB,43.56 MiB,136,0,65.40 samples/s,15.29 ms,166.46 mW,2545338467.00 pJ/it
|
||||
mul/after_conv,arch-a,PASS,PASS,0.072 s,0.00 MiB,0.00 MiB,4,3,0.01 ms,107.64 mW,586955.72 pJ,0.072 s,0.00 MiB,0.00 MiB,4,3,183000.00 samples/s,0.01 ms,32.66 mW,178132.97 pJ/it
|
||||
mul/after_conv_scalar_constant,arch-a,PASS,PASS,0.120 s,0.00 MiB,0.00 MiB,4,3,0.01 ms,107.64 mW,586955.72 pJ,0.160 s,0.00 MiB,0.00 MiB,4,3,183000.00 samples/s,0.01 ms,32.66 mW,178132.97 pJ/it
|
||||
mul/basic,arch-a,PASS,PASS,0.061 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.22 mW,25266.00 pJ,0.086 s,0.00 MiB,0.00 MiB,1,0,3120000.00 samples/s,0.00 ms,2.23 mW,658.67 pJ/it
|
||||
mul/channel_broadcast_1024,arch-a,PASS,PASS,0.063 s,0.02 MiB,0.01 MiB,1,0,0.01 ms,78.12 mW,540030.00 pJ,0.061 s,0.02 MiB,0.01 MiB,1,0,145000.00 samples/s,0.01 ms,2.11 mW,13388.67 pJ/it
|
||||
mul/leading_dimension_broadcast,arch-a,PASS,PASS,0.064 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.22 mW,25266.00 pJ,0.059 s,0.00 MiB,0.00 MiB,1,0,3120000.00 samples/s,0.00 ms,2.23 mW,658.67 pJ/it
|
||||
mul/scalar_constant,arch-a,PASS,PASS,0.065 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.22 mW,25266.00 pJ,0.066 s,0.00 MiB,0.00 MiB,1,0,3120000.00 samples/s,0.00 ms,2.23 mW,658.67 pJ/it
|
||||
pool/avg_basic,arch-a,PASS,PASS,0.062 s,0.00 MiB,0.00 MiB,1,0,0.01 ms,78.02 mW,931506.00 pJ,0.063 s,0.00 MiB,0.00 MiB,1,0,84000.00 samples/s,0.01 ms,2.02 mW,24067.00 pJ/it
|
||||
pool/avg_ceil_mode,arch-a,PASS,PASS,0.062 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.03 mW,340146.00 pJ,0.057 s,0.00 MiB,0.00 MiB,1,0,230000.00 samples/s,0.00 ms,2.03 mW,8810.67 pJ/it
|
||||
pool/avg_explicit_padding,arch-a,PASS,PASS,0.125 s,0.00 MiB,0.00 MiB,1,0,0.01 ms,78.03 mW,688356.00 pJ,0.077 s,0.00 MiB,0.00 MiB,1,0,114000.00 samples/s,0.01 ms,2.03 mW,17809.00 pJ/it
|
||||
pool/avg_include_pad,arch-a,PASS,PASS,0.064 s,0.00 MiB,0.00 MiB,1,0,0.01 ms,78.02 mW,663612.00 pJ,0.059 s,0.00 MiB,0.00 MiB,1,0,118000.00 samples/s,0.01 ms,2.02 mW,17081.00 pJ/it
|
||||
pool/avg_large_channels,arch-a,PASS,PASS,0.069 s,0.04 MiB,0.02 MiB,1,0,0.24 ms,78.00 mW,18399156.00 pJ,0.067 s,0.04 MiB,0.02 MiB,1,0,4250.00 samples/s,0.24 ms,2.00 mW,471428.00 pJ/it
|
||||
pool/avg_non_uniform_stride,arch-a,PASS,PASS,0.060 s,0.00 MiB,0.00 MiB,1,0,0.01 ms,78.02 mW,1132254.00 pJ,0.060 s,0.00 MiB,0.00 MiB,1,0,69100.00 samples/s,0.01 ms,2.02 mW,29191.00 pJ/it
|
||||
pool/avg_real_asymmetric_padding,arch-a,PASS,PASS,0.070 s,0.00 MiB,0.00 MiB,1,0,0.03 ms,78.02 mW,1966692.00 pJ,0.073 s,0.00 MiB,0.00 MiB,1,0,39700.00 samples/s,0.03 ms,2.02 mW,50961.00 pJ/it
|
||||
pool/max_after_conv,arch-a,PASS,PASS,0.069 s,0.00 MiB,0.00 MiB,5,4,0.01 ms,99.12 mW,1210689.96 pJ,0.074 s,0.00 MiB,0.00 MiB,5,4,81600.00 samples/s,0.01 ms,28.10 mW,344619.71 pJ/it
|
||||
pool/max_basic,arch-a,PASS,PASS,0.057 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.06 mW,324744.00 pJ,0.055 s,0.00 MiB,0.00 MiB,1,0,241000.00 samples/s,0.00 ms,2.06 mW,8532.67 pJ/it
|
||||
pool/max_ceil_mode,arch-a,PASS,PASS,0.061 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.07 mW,151464.00 pJ,0.059 s,0.00 MiB,0.00 MiB,1,0,516000.00 samples/s,0.00 ms,2.07 mW,3972.67 pJ/it
|
||||
pool/max_global_style_kernel_equals_input,arch-a,PASS,PASS,0.066 s,0.00 MiB,0.00 MiB,1,0,0.01 ms,78.01 mW,658626.00 pJ,0.063 s,0.00 MiB,0.00 MiB,1,0,119000.00 samples/s,0.01 ms,2.01 mW,16871.00 pJ/it
|
||||
pool/max_non_square_kernel,arch-a,PASS,PASS,0.070 s,0.00 MiB,0.00 MiB,1,0,0.01 ms,78.02 mW,1063068.00 pJ,0.063 s,0.00 MiB,0.00 MiB,1,0,73600.00 samples/s,0.01 ms,2.02 mW,27417.00 pJ/it
|
||||
pool/max_real_asymmetric_padding,arch-a,PASS,PASS,0.060 s,0.00 MiB,0.00 MiB,1,0,0.01 ms,78.03 mW,814992.00 pJ,0.060 s,0.00 MiB,0.00 MiB,1,0,96100.00 samples/s,0.01 ms,2.03 mW,21173.00 pJ/it
|
||||
pool/max_same_upper,arch-a,PASS,PASS,0.066 s,0.00 MiB,0.00 MiB,1,0,0.01 ms,78.04 mW,625068.00 pJ,0.067 s,0.00 MiB,0.00 MiB,1,0,125000.00 samples/s,0.01 ms,2.04 mW,16233.00 pJ/it
|
||||
pool/max_stride2_multichannel,arch-a,PASS,PASS,0.067 s,0.00 MiB,0.00 MiB,1,0,0.02 ms,78.02 mW,1247274.00 pJ,0.074 s,0.00 MiB,0.00 MiB,1,0,62700.00 samples/s,0.02 ms,2.02 mW,32153.00 pJ/it
|
||||
reduce_mean/4d_spatial,arch-a,PASS,PASS,0.068 s,0.00 MiB,0.00 MiB,3,0,0.00 ms,92.45 mW,29676.00 pJ,0.068 s,0.00 MiB,0.00 MiB,3,0,2310000.00 samples/s,0.00 ms,4.54 mW,1959.17 pJ/it
|
||||
reduce_mean/4d_spatial_keepdims_0,arch-a,PASS,PASS,0.072 s,0.00 MiB,0.00 MiB,4,0,0.00 ms,94.35 mW,61801.00 pJ,0.070 s,0.00 MiB,0.00 MiB,4,0,1210000.00 samples/s,0.00 ms,19.43 mW,16020.25 pJ/it
|
||||
reduce_mean/after_conv,arch-a,PASS,PASS,0.071 s,0.00 MiB,0.00 MiB,5,3,0.01 ms,106.95 mW,571332.72 pJ,0.075 s,0.00 MiB,0.00 MiB,5,3,183000.00 samples/s,0.01 ms,19.71 mW,107526.72 pJ/it
|
||||
reduce_mean/all_axes_keepdims_0,arch-a,PASS,PASS,0.059 s,0.00 MiB,0.00 MiB,2,0,0.00 ms,79.24 mW,30982.00 pJ,0.063 s,0.00 MiB,0.00 MiB,2,0,2530000.00 samples/s,0.00 ms,3.31 mW,1260.00 pJ/it
|
||||
reduce_mean/all_axes_keepdims_1,arch-a,PASS,PASS,0.058 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.22 mW,17286.00 pJ,0.067 s,0.00 MiB,0.00 MiB,1,0,4570000.00 samples/s,0.00 ms,2.22 mW,437.33 pJ/it
|
||||
reduce_mean/basic,arch-a,PASS,PASS,0.068 s,0.00 MiB,0.00 MiB,4,0,0.00 ms,93.51 mW,34881.00 pJ,0.061 s,0.00 MiB,0.00 MiB,4,0,2600000.00 samples/s,0.00 ms,5.85 mW,2235.67 pJ/it
|
||||
reduce_mean/channel_axis_nchw,arch-a,PASS,PASS,0.061 s,0.03 MiB,0.02 MiB,4,0,0.16 ms,93.60 mW,15436518.00 pJ,0.062 s,0.03 MiB,0.08 MiB,4,0,12900.00 samples/s,0.08 ms,5.00 mW,388853.50 pJ/it
|
||||
reduce_mean/keepdims_0,arch-a,PASS,PASS,0.067 s,0.00 MiB,0.00 MiB,5,0,0.00 ms,91.40 mW,68368.00 pJ,0.056 s,0.00 MiB,0.00 MiB,5,0,1300000.00 samples/s,0.00 ms,20.71 mW,16115.50 pJ/it
|
||||
reduce_mean/large_dimension_1024,arch-a,PASS,PASS,0.057 s,0.01 MiB,0.00 MiB,1,0,0.00 ms,78.02 mW,217278.00 pJ,0.056 s,0.01 MiB,0.00 MiB,1,0,359000.00 samples/s,0.00 ms,2.02 mW,5274.00 pJ/it
|
||||
reduce_mean/legacy_axes_1_2_keepdims_1,arch-a,PASS,PASS,0.055 s,0.00 MiB,0.00 MiB,2,0,0.00 ms,79.35 mW,21505.00 pJ,0.058 s,0.00 MiB,0.00 MiB,2,0,3620000.00 samples/s,0.00 ms,3.45 mW,898.00 pJ/it
|
||||
reduce_mean/legacy_axis1_keepdims_0,arch-a,PASS,PASS,0.062 s,0.00 MiB,0.00 MiB,9,0,0.00 ms,92.50 mW,183708.00 pJ,0.065 s,0.00 MiB,0.00 MiB,9,0,679000.00 samples/s,0.00 ms,38.84 mW,57998.17 pJ/it
|
||||
reduce_mean/legacy_axis1_keepdims_1,arch-a,PASS,PASS,0.060 s,0.00 MiB,0.00 MiB,8,0,0.00 ms,94.56 mW,129830.00 pJ,0.066 s,0.00 MiB,0.00 MiB,8,0,1340000.00 samples/s,0.00 ms,10.15 mW,7594.50 pJ/it
|
||||
reduce_mean/legacy_empty_axes_noop,arch-a,PASS,PASS,0.051 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.22 mW,17286.00 pJ,0.055 s,0.00 MiB,0.00 MiB,1,0,4570000.00 samples/s,0.00 ms,2.22 mW,437.33 pJ/it
|
||||
reduce_mean/legacy_nchw_spatial,arch-a,PASS,PASS,0.058 s,0.00 MiB,0.00 MiB,3,0,0.00 ms,92.45 mW,29676.00 pJ,0.058 s,0.00 MiB,0.00 MiB,3,0,1720000.00 samples/s,0.00 ms,4.40 mW,2552.75 pJ/it
|
||||
reduce_mean/legacy_negative_axis,arch-a,PASS,PASS,0.057 s,0.00 MiB,0.00 MiB,6,0,0.00 ms,93.52 mW,51717.00 pJ,0.060 s,0.00 MiB,0.00 MiB,6,0,1760000.00 samples/s,0.00 ms,8.07 mW,4588.50 pJ/it
|
||||
reduce_mean/legacy_reduce_all_keepdims_1,arch-a,PASS,PASS,0.055 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.22 mW,17286.00 pJ,0.054 s,0.00 MiB,0.00 MiB,1,0,4570000.00 samples/s,0.00 ms,2.22 mW,437.33 pJ/it
|
||||
reduce_mean/negative_axis,arch-a,PASS,PASS,0.059 s,0.00 MiB,0.00 MiB,6,0,0.00 ms,93.52 mW,51717.00 pJ,0.058 s,0.00 MiB,0.00 MiB,6,0,1760000.00 samples/s,0.00 ms,8.07 mW,4588.50 pJ/it
|
||||
relu/4d,arch-a,PASS,PASS,0.056 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.18 mW,40734.00 pJ,0.053 s,0.00 MiB,0.00 MiB,1,0,1930000.00 samples/s,0.00 ms,2.18 mW,1014.00 pJ/it
|
||||
relu/after_conv,arch-a,PASS,PASS,0.063 s,0.00 MiB,0.00 MiB,4,3,0.01 ms,107.89 mW,577437.72 pJ,0.067 s,0.00 MiB,0.00 MiB,4,3,187000.00 samples/s,0.01 ms,32.91 mW,176189.97 pJ/it
|
||||
relu/after_gemm,arch-a,PASS,PASS,0.069 s,0.01 MiB,0.01 MiB,5,4,0.01 ms,105.16 mW,790056.96 pJ,0.074 s,0.01 MiB,0.01 MiB,6,4,151000.00 samples/s,0.01 ms,32.04 mW,211536.21 pJ/it
|
||||
relu/basic,arch-a,PASS,PASS,0.060 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.22 mW,17286.00 pJ,0.057 s,0.00 MiB,0.00 MiB,1,0,4570000.00 samples/s,0.00 ms,2.22 mW,437.33 pJ/it
|
||||
reshape/4d_to_2d_flatten,arch-a,PASS,PASS,0.061 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.28 mW,20196.00 pJ,0.061 s,0.00 MiB,0.00 MiB,1,0,3910000.00 samples/s,0.00 ms,2.28 mW,488.00 pJ/it
|
||||
reshape/infer_dim_minus_one,arch-a,PASS,PASS,0.055 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.30 mW,12684.00 pJ,0.058 s,0.00 MiB,0.00 MiB,1,0,6250000.00 samples/s,0.00 ms,2.30 mW,308.00 pJ/it
|
||||
reshape/same_rank,arch-a,PASS,PASS,0.060 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.30 mW,12684.00 pJ,0.058 s,0.00 MiB,0.00 MiB,1,0,6250000.00 samples/s,0.00 ms,2.30 mW,308.00 pJ/it
|
||||
reshape/zero_copies_input_dim,arch-a,PASS,PASS,0.059 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.30 mW,12684.00 pJ,0.056 s,0.00 MiB,0.00 MiB,1,0,6250000.00 samples/s,0.00 ms,2.30 mW,308.00 pJ/it
|
||||
resize/height_only,arch-a,PASS,PASS,0.060 s,0.00 MiB,0.00 MiB,4,0,0.00 ms,93.55 mW,64833.00 pJ,0.063 s,0.00 MiB,0.00 MiB,4,0,1880000.00 samples/s,0.00 ms,5.60 mW,2986.00 pJ/it
|
||||
resize/nearest_2x,arch-a,PASS,PASS,0.058 s,0.00 MiB,0.00 MiB,4,0,0.00 ms,93.57 mW,109761.00 pJ,0.059 s,0.00 MiB,0.00 MiB,4,0,1450000.00 samples/s,0.00 ms,5.46 mW,3776.00 pJ/it
|
||||
resize/nearest_downsample,arch-a,PASS,PASS,0.059 s,0.00 MiB,0.00 MiB,2,0,0.00 ms,79.45 mW,33925.00 pJ,0.059 s,0.00 MiB,0.00 MiB,2,0,2330000.00 samples/s,0.00 ms,3.28 mW,1360.50 pJ/it
|
||||
resize/non_uniform_scales,arch-a,PASS,PASS,0.063 s,0.00 MiB,0.00 MiB,6,0,0.00 ms,93.58 mW,164037.00 pJ,0.069 s,0.00 MiB,0.00 MiB,6,0,1250000.00 samples/s,0.00 ms,7.76 mW,6207.25 pJ/it
|
||||
resize/width_only,arch-a,PASS,PASS,0.062 s,0.00 MiB,0.00 MiB,2,0,0.00 ms,79.50 mW,53029.00 pJ,0.059 s,0.00 MiB,0.00 MiB,2,0,1700000.00 samples/s,0.00 ms,3.20 mW,1833.50 pJ/it
|
||||
resize/with_sizes,arch-a,PASS,PASS,0.061 s,0.00 MiB,0.00 MiB,3,0,0.00 ms,92.54 mW,73756.00 pJ,0.068 s,0.00 MiB,0.00 MiB,3,0,1700000.00 samples/s,0.00 ms,4.39 mW,2586.75 pJ/it
|
||||
sigmoid/4d,arch-a,PASS,PASS,0.057 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.18 mW,40734.00 pJ,0.060 s,0.00 MiB,0.00 MiB,1,0,1930000.00 samples/s,0.00 ms,2.18 mW,1014.00 pJ/it
|
||||
sigmoid/after_gemm,arch-a,PASS,PASS,0.063 s,0.01 MiB,0.01 MiB,5,4,0.01 ms,105.16 mW,790056.96 pJ,0.062 s,0.01 MiB,0.01 MiB,6,4,151000.00 samples/s,0.01 ms,32.04 mW,211536.21 pJ/it
|
||||
sigmoid/basic,arch-a,PASS,PASS,0.055 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.22 mW,17286.00 pJ,0.060 s,0.00 MiB,0.00 MiB,1,0,4570000.00 samples/s,0.00 ms,2.22 mW,437.33 pJ/it
|
||||
slice/2d_basic,arch-a,PASS,PASS,0.056 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.30 mW,18948.00 pJ,0.055 s,0.00 MiB,0.00 MiB,1,0,4170000.00 samples/s,0.00 ms,2.30 mW,491.67 pJ/it
|
||||
slice/after_conv,arch-a,PASS,PASS,0.067 s,0.00 MiB,0.01 MiB,7,6,0.01 ms,118.19 mW,1335082.88 pJ,0.074 s,0.00 MiB,0.01 MiB,7,6,87400.00 samples/s,0.01 ms,47.90 mW,547806.13 pJ/it
|
||||
slice/default_axes,arch-a,PASS,PASS,0.055 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.30 mW,18948.00 pJ,0.054 s,0.00 MiB,0.00 MiB,1,0,4170000.00 samples/s,0.00 ms,2.30 mW,491.67 pJ/it
|
||||
slice/large_channel_1024,arch-a,PASS,PASS,0.059 s,0.01 MiB,0.00 MiB,1,0,0.00 ms,78.14 mW,221304.00 pJ,0.052 s,0.01 MiB,0.00 MiB,1,0,353000.00 samples/s,0.00 ms,2.14 mW,5058.00 pJ/it
|
||||
slice/nchw_spatial_crop,arch-a,PASS,PASS,0.062 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.24 mW,101868.00 pJ,0.060 s,0.00 MiB,0.00 MiB,1,0,769000.00 samples/s,0.00 ms,2.24 mW,2851.67 pJ/it
|
||||
slice/negative_axis,arch-a,PASS,PASS,0.054 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.30 mW,44004.00 pJ,0.055 s,0.00 MiB,0.00 MiB,1,0,1790000.00 samples/s,0.00 ms,2.30 mW,1227.67 pJ/it
|
||||
slice/negative_indices,arch-a,PASS,PASS,0.056 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.30 mW,25212.00 pJ,0.054 s,0.00 MiB,0.00 MiB,1,0,3120000.00 samples/s,0.00 ms,2.30 mW,675.67 pJ/it
|
||||
slice/step2,arch-a,PASS,PASS,0.054 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.29 mW,159876.00 pJ,0.053 s,0.00 MiB,0.00 MiB,1,0,490000.00 samples/s,0.00 ms,2.29 mW,4619.67 pJ/it
|
||||
softmax/3d_last_axis,arch-a,PASS,PASS,0.056 s,0.00 MiB,0.00 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED,0.056 s,0.00 MiB,0.00 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED
|
||||
softmax/basic,arch-a,PASS,PASS,0.062 s,0.00 MiB,0.00 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED,0.058 s,0.00 MiB,0.00 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED
|
||||
softmax/channel_axis,arch-a,PASS,PASS,0.060 s,0.00 MiB,0.00 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED,0.067 s,0.00 MiB,0.00 MiB,3,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED
|
||||
softmax/large_dimension_1024,arch-a,PASS,PASS,0.059 s,0.01 MiB,0.01 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED,0.054 s,0.01 MiB,0.01 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED
|
||||
softmax/negative_axis,arch-a,PASS,PASS,0.059 s,0.00 MiB,0.00 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED,0.060 s,0.00 MiB,0.00 MiB,1,0,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED,UNSUPPORTED
|
||||
split/basic,arch-a,PASS,PASS,0.114 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.30 mW,31554.00 pJ,0.067 s,0.00 MiB,0.00 MiB,1,0,2490000.00 samples/s,0.00 ms,2.30 mW,861.67 pJ/it
|
||||
split/equal_three_way,arch-a,PASS,PASS,0.060 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.30 mW,44160.00 pJ,0.073 s,0.00 MiB,0.00 MiB,1,0,1780000.00 samples/s,0.00 ms,2.30 mW,1231.67 pJ/it
|
||||
split/negative_axis,arch-a,PASS,PASS,0.061 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.29 mW,84786.00 pJ,0.059 s,0.00 MiB,0.00 MiB,1,0,925000.00 samples/s,0.00 ms,2.29 mW,2413.67 pJ/it
|
||||
split/uneven_channel_axis_4d,arch-a,PASS,PASS,0.058 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.30 mW,18948.00 pJ,0.060 s,0.00 MiB,0.00 MiB,1,0,4170000.00 samples/s,0.00 ms,2.30 mW,491.67 pJ/it
|
||||
sub/after_gemm,arch-a,PASS,PASS,0.065 s,0.01 MiB,0.01 MiB,5,4,0.01 ms,104.70 mW,815012.96 pJ,0.065 s,0.01 MiB,0.01 MiB,6,4,145000.00 samples/s,0.01 ms,31.45 mW,216167.21 pJ/it
|
||||
sub/basic,arch-a,PASS,PASS,0.062 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.22 mW,25266.00 pJ,0.055 s,0.00 MiB,0.00 MiB,1,0,3120000.00 samples/s,0.00 ms,2.23 mW,658.67 pJ/it
|
||||
sub/broadcast_row,arch-a,PASS,PASS,0.059 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.22 mW,25266.00 pJ,0.077 s,0.00 MiB,0.00 MiB,1,0,3120000.00 samples/s,0.00 ms,2.23 mW,658.67 pJ/it
|
||||
sub/channel_broadcast_1024,arch-a,PASS,PASS,0.064 s,0.02 MiB,0.01 MiB,1,0,0.01 ms,78.12 mW,540030.00 pJ,0.058 s,0.02 MiB,0.01 MiB,1,0,145000.00 samples/s,0.01 ms,2.11 mW,13388.67 pJ/it
|
||||
sub/constant_lhs_broadcast,arch-a,PASS,PASS,0.057 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.22 mW,25188.00 pJ,0.061 s,0.00 MiB,0.00 MiB,1,0,3120000.00 samples/s,0.00 ms,2.23 mW,656.67 pJ/it
|
||||
sub/leading_dimension_broadcast,arch-a,PASS,PASS,0.056 s,0.00 MiB,0.00 MiB,1,0,0.00 ms,78.22 mW,25266.00 pJ,0.057 s,0.00 MiB,0.00 MiB,1,0,3120000.00 samples/s,0.00 ms,2.23 mW,658.67 pJ/it
|
||||
|
@@ -0,0 +1,33 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ARTIFACTS_DIRNAME = "artifacts"
|
||||
|
||||
|
||||
def artifacts_dir(workspace_dir: str | Path) -> Path:
|
||||
return Path(workspace_dir) / ARTIFACTS_DIRNAME
|
||||
|
||||
|
||||
def runner_uses_library(runner_path: str | Path, library_path: str | Path) -> bool:
|
||||
runner_path = Path(runner_path)
|
||||
library_path = Path(library_path)
|
||||
try:
|
||||
return (
|
||||
runner_path.is_file()
|
||||
and library_path.is_file()
|
||||
and str(library_path.resolve()).encode() in runner_path.read_bytes()
|
||||
)
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def remove_lock_files(root: str | Path) -> int:
|
||||
root = Path(root)
|
||||
if not root.exists():
|
||||
return 0
|
||||
removed = 0
|
||||
for path in root.rglob("*.lock"):
|
||||
if path.is_file() or path.is_symlink():
|
||||
path.unlink(missing_ok=True)
|
||||
removed += 1
|
||||
return removed
|
||||
@@ -0,0 +1,39 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
SUITE = REPO / "validation/networks/pimcomp_models"
|
||||
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-pimsim-nn.onnx",
|
||||
"yolo11n": SUITE / "yolo11n/yolo11n-pimsim-nn.onnx",
|
||||
}
|
||||
FUNCTIONAL_MODELS = {
|
||||
**MODELS,
|
||||
"yolo11n": REPO / "validation/networks/yolo11n/depth_51/yolo11n_depth_51.onnx",
|
||||
}
|
||||
MODEL_NAMES = tuple(MODELS)
|
||||
DEFAULT_MODELS = MODEL_NAMES
|
||||
ABLATION_DEFAULT_MODELS = ("vgg8", "resnet18", "resnet34", "googlenet")
|
||||
|
||||
|
||||
def add_models_argument(
|
||||
parser: argparse.ArgumentParser,
|
||||
default: tuple[str, ...] = DEFAULT_MODELS,
|
||||
) -> None:
|
||||
help_text = "Models to run (default: " + ", ".join(default) + ")."
|
||||
if "yolo11n" not in default:
|
||||
help_text += " Select yolo11n explicitly when needed."
|
||||
parser.add_argument(
|
||||
"--models",
|
||||
nargs="+",
|
||||
choices=MODEL_NAMES,
|
||||
default=list(default),
|
||||
metavar="MODEL",
|
||||
help=help_text,
|
||||
)
|
||||
@@ -8,13 +8,13 @@ from .subprocess_utils import run_command_with_reporter
|
||||
|
||||
PIM_PASS_LABELS = (
|
||||
("ONNXToSpatialPass", "ONNX to Spatial"),
|
||||
("MergeComputeNodesPass", "Merge Compute Nodes"),
|
||||
("SpatialToPimPass", "Spatial to PIM"),
|
||||
("PimBufferizationPass", "Bufferize PIM"),
|
||||
("HostConstantFoldingPass", "Fold Host Constants"),
|
||||
("PimLocalMemoryPlanningPass", "Plan Local Memory"),
|
||||
("VerificationPass", "Verify PIM"),
|
||||
("EmitPimCodePass", "Emit PIM Code"),
|
||||
("MergeComputeNodesPass", "Merge compute nodes"),
|
||||
("SpatialToPimPass", "Spatial to Pim"),
|
||||
("PimBufferizationPass", "Bufferize Pim"),
|
||||
("HostConstantFoldingPass", "Fold host constants"),
|
||||
("PimLocalMemoryPlanningPass", "Plan local memory"),
|
||||
("VerificationPass", "Verify Pim"),
|
||||
("EmitPimCodePass", "Emit Pim code"),
|
||||
)
|
||||
PIM_PASS_LABEL_BY_SUFFIX = dict(PIM_PASS_LABELS)
|
||||
TIMING_LINE_RE = re.compile(r"^\s*([0-9]+\.[0-9]+)\s+\(\s*[0-9.]+%\)\s+(.+?)\s*$")
|
||||
|
||||
@@ -9,6 +9,7 @@ import numpy as np
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from colorama import Style, Fore
|
||||
from .artifacts import artifacts_dir, runner_uses_library
|
||||
from .gen_network_runner import gen_network_runner
|
||||
from .onnx_utils import (
|
||||
_ONNX_TO_NP,
|
||||
@@ -26,13 +27,13 @@ from .subprocess_utils import run_command_with_reporter
|
||||
|
||||
STAGE_TITLES = (
|
||||
"Compile ONNX",
|
||||
"Build Runner",
|
||||
"Generate Inputs",
|
||||
"Run Reference",
|
||||
"Compile PIM",
|
||||
"Run Functional Simulation",
|
||||
"Compare Outputs",
|
||||
"Run Non-functional Simulation",
|
||||
"Build runner",
|
||||
"Generate inputs",
|
||||
"Run reference",
|
||||
"Compile Pim",
|
||||
"Run functional simulation",
|
||||
"Compare outputs",
|
||||
"Run non-functional simulation",
|
||||
)
|
||||
STAGE_COLORS = {
|
||||
STAGE_TITLES[0]: Fore.BLUE,
|
||||
@@ -46,8 +47,7 @@ STAGE_COLORS = {
|
||||
}
|
||||
STAGE_COUNT = len(STAGE_TITLES)
|
||||
GENERATED_DIR_NAMES = (
|
||||
"inputs", "outputs", "pimcomp", "raptor", "runner", "simulation",
|
||||
"throughput_validation",
|
||||
"artifacts",
|
||||
)
|
||||
|
||||
MODE_FULL = "full"
|
||||
@@ -58,15 +58,15 @@ MODE_STAGE_TITLES = {
|
||||
MODE_FULL: STAGE_TITLES,
|
||||
MODE_COMPILE_ONLY: (
|
||||
"Compile ONNX",
|
||||
"Build Runner",
|
||||
"Compile PIM",
|
||||
"Build runner",
|
||||
"Compile Pim",
|
||||
),
|
||||
MODE_RUN_ONLY: (
|
||||
"Generate Inputs",
|
||||
"Run Reference",
|
||||
"Run Functional Simulation",
|
||||
"Compare Outputs",
|
||||
"Run Non-functional Simulation",
|
||||
"Generate inputs",
|
||||
"Run reference",
|
||||
"Run functional simulation",
|
||||
"Compare outputs",
|
||||
"Run non-functional simulation",
|
||||
),
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ PIMSIM_FAILED = "ERROR"
|
||||
PIMSIM_UNSUPPORTED = "UNSUPPORTED"
|
||||
PIMSIM_SKIPPED = "SKIP"
|
||||
PIMSIM_NOT_RUN = "-"
|
||||
PIMSIM_UNSUPPORTED_VSOFTMAX = "pimsim-nn does not support opcode vsoftmax"
|
||||
PIMSIM_UNSUPPORTED_VSOFTMAX = "does not support opcode vsoftmax"
|
||||
|
||||
|
||||
class PimSimUnsupportedError(RuntimeError):
|
||||
@@ -338,7 +338,7 @@ def run_pimsim_nn(
|
||||
else ("throughput", "average_latency_ms", "average_power_mw", "average_energy_pj")
|
||||
)
|
||||
if any(name not in metrics for name in required):
|
||||
raise RuntimeError(f"pimsim-nn output did not contain required {execution_mode} metrics")
|
||||
raise RuntimeError(f"Pimsim output did not contain required {execution_mode} metrics")
|
||||
return metrics
|
||||
|
||||
|
||||
@@ -361,6 +361,11 @@ def clean_workspace_artifacts(workspace_dir, model_stem):
|
||||
for suffix in (".onnx.mlir", ".so", ".tmp"):
|
||||
remove_path(workspace_dir / f"{model_stem}{suffix}")
|
||||
|
||||
for path in workspace_dir.rglob("*.lock"):
|
||||
if path.is_file() or path.is_symlink():
|
||||
path.unlink(missing_ok=True)
|
||||
removed_paths.append(path)
|
||||
|
||||
return removed_paths
|
||||
|
||||
|
||||
@@ -407,32 +412,34 @@ def build_dump_ranges(config_path, outputs_descriptor):
|
||||
|
||||
|
||||
def build_pim_simulator_command(
|
||||
pim_dir, output_bin_path, dump_ranges, input_paths, mode="latency",
|
||||
pim_dir, output_bin_path, dump_ranges, input_dir, batch_size, mode="latency",
|
||||
batch_output_dir=None):
|
||||
if mode not in ("latency", "throughput"):
|
||||
raise ValueError(f"unknown simulator mode: {mode}")
|
||||
if not input_paths:
|
||||
if batch_size < 1:
|
||||
raise ValueError("simulator requires at least one input")
|
||||
if input_dir is None:
|
||||
raise ValueError("simulator requires an input directory")
|
||||
command = [
|
||||
"cargo", "run", "--no-default-features", "--release", "--package", "pim-simulator", "--bin", "pim-simulator",
|
||||
"--", "-f", str(pim_dir), "-o", str(output_bin_path), "-d", dump_ranges,
|
||||
"--mode", mode, "--batch-size", str(len(input_paths)),
|
||||
"--mode", mode, "--batch-size", str(batch_size),
|
||||
"--input-dir", str(input_dir),
|
||||
]
|
||||
if batch_output_dir is not None:
|
||||
command += ["--batch-output-dir", str(batch_output_dir)]
|
||||
for path in input_paths:
|
||||
command += ["--input", str(path)]
|
||||
return command
|
||||
|
||||
|
||||
def run_pim_simulator(
|
||||
simulator_dir, pim_dir, output_bin_path, dump_ranges, reporter=None,
|
||||
timeout_sec=None, input_paths=(), mode="latency", batch_output_dir=None):
|
||||
timeout_sec=None, input_dir=None, batch_size=1, mode="latency", batch_output_dir=None):
|
||||
command = build_pim_simulator_command(
|
||||
pim_dir,
|
||||
output_bin_path,
|
||||
dump_ranges,
|
||||
input_paths,
|
||||
input_dir,
|
||||
batch_size,
|
||||
mode=mode,
|
||||
batch_output_dir=batch_output_dir,
|
||||
)
|
||||
@@ -516,7 +523,7 @@ def validate_execution(
|
||||
try:
|
||||
print_stage(
|
||||
reporter, model_index, model_total, model_name,
|
||||
"Run Functional Simulation", name,
|
||||
"Run functional simulation", name,
|
||||
)
|
||||
write_inputs_to_memory_bin(
|
||||
pim_dir / "memory.bin", pim_dir / "config.json", input_batch[0])
|
||||
@@ -526,13 +533,13 @@ def validate_execution(
|
||||
run_pim_simulator(
|
||||
simulator_dir, pim_dir, simulation_dir / "out.bin", dump_ranges,
|
||||
reporter=reporter, timeout_sec=command_timeout_seconds,
|
||||
input_paths=input_paths[:batch_size], mode=name,
|
||||
input_dir=input_paths[0].parent, batch_size=batch_size, mode=name,
|
||||
batch_output_dir=output_dir)
|
||||
reporter.advance()
|
||||
|
||||
print_stage(
|
||||
reporter, model_index, model_total, model_name,
|
||||
"Compare Outputs", name,
|
||||
"Compare outputs", name,
|
||||
)
|
||||
reporter.suspend()
|
||||
try:
|
||||
@@ -553,10 +560,13 @@ def validate_execution(
|
||||
|
||||
print_stage(
|
||||
reporter, model_index, model_total, model_name,
|
||||
"Run Non-functional Simulation", name,
|
||||
"Run non-functional simulation", name,
|
||||
)
|
||||
config_path = execution["pimsim_config"]
|
||||
if state["compiled"] and pimsim_nn_build_dir is not None and config_path is not None:
|
||||
if state["compiled"] and state["resource_metrics"].get("used_core_count") == 0:
|
||||
state["pimsim_status"] = PIMSIM_SKIPPED
|
||||
print_info(reporter, "Pimsim non-functional simulation skipped: no active cores")
|
||||
elif state["compiled"] and pimsim_nn_build_dir is not None and config_path is not None:
|
||||
try:
|
||||
state["metrics"] = run_pimsim_nn(
|
||||
pimsim_nn_build_dir, pim_dir, config_path, name,
|
||||
@@ -580,7 +590,7 @@ def validate_execution(
|
||||
elif not state["compiled"]:
|
||||
state["pimsim_status"] = PIMSIM_NOT_RUN
|
||||
else:
|
||||
print_info(reporter, "pimsim-nn non-functional simulation skipped")
|
||||
print_info(reporter, "Pimsim non-functional simulation skipped")
|
||||
reporter.advance()
|
||||
|
||||
|
||||
@@ -609,12 +619,12 @@ def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
|
||||
owns_reporter = reporter is None
|
||||
reporter = reporter or ProgressReporter(model_total, stages_per_model=len(MODE_STAGE_TITLES[mode]), verbose=verbose)
|
||||
|
||||
workspace_dir = network_onnx_path.parent
|
||||
workspace_dir = artifacts_dir(network_onnx_path.parent)
|
||||
raptor_dir = workspace_dir / "raptor"
|
||||
runner_dir = workspace_dir / "runner"
|
||||
runner_build_dir = runner_dir / "build"
|
||||
if mode != MODE_RUN_ONLY:
|
||||
clean_workspace_artifacts(workspace_dir, network_onnx_path.stem)
|
||||
clean_workspace_artifacts(network_onnx_path.parent, network_onnx_path.stem)
|
||||
Path.mkdir(raptor_dir, parents=True, exist_ok=True)
|
||||
Path.mkdir(runner_build_dir, parents=True, exist_ok=True)
|
||||
|
||||
@@ -669,7 +679,7 @@ def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
|
||||
print_info(reporter, f"Shared library saved to {network_so_path}")
|
||||
reporter.advance()
|
||||
|
||||
print_stage(reporter, model_index, model_total, network_onnx_path.name, "Build Runner")
|
||||
print_stage(reporter, model_index, model_total, network_onnx_path.name, "Build runner")
|
||||
gen_network_runner(
|
||||
network_onnx_path, network_so_path, onnx_include_dir,
|
||||
entry="run_main_graph", out=runner_dir / "runner.c", verbose=False)
|
||||
@@ -683,7 +693,10 @@ def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
|
||||
report_validation_failure(reporter, "reference", "compilation", exc)
|
||||
else:
|
||||
required_paths = (network_so_path, network_mlir_path, runner_path)
|
||||
reference_ready = all(path.exists() for path in required_paths)
|
||||
reference_ready = (
|
||||
all(path.exists() for path in required_paths)
|
||||
and runner_uses_library(runner_path, network_so_path)
|
||||
)
|
||||
if not reference_ready:
|
||||
report_validation_failure(reporter, "reference", "artifact lookup", FileNotFoundError(
|
||||
"run-only mode requires the compiled shared library, ONNX MLIR, and runner"))
|
||||
@@ -696,7 +709,7 @@ def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
|
||||
states[name]["compiled"] = (pim_dir / "config.json").exists()
|
||||
if not states[name]["compiled"]:
|
||||
report_validation_failure(reporter, name, "artifact lookup", FileNotFoundError(
|
||||
f"run-only mode requires compiled PIM artifacts at {pim_dir}"))
|
||||
f"run-only mode requires compiled Pim artifacts at {pim_dir}"))
|
||||
else:
|
||||
states[name]["resource_metrics"] = collect_pim_resource_metrics(pim_dir)
|
||||
if name == "latency":
|
||||
@@ -705,7 +718,7 @@ def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
|
||||
try:
|
||||
print_stage(
|
||||
reporter, model_index, model_total, network_onnx_path.name,
|
||||
"Compile PIM", name,
|
||||
"Compile Pim", name,
|
||||
)
|
||||
root.mkdir(parents=True, exist_ok=True)
|
||||
started = time.perf_counter()
|
||||
@@ -724,7 +737,7 @@ def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
|
||||
states[name]["resource_metrics"] = collect_pim_resource_metrics(pim_dir)
|
||||
if name == "latency":
|
||||
resource_metrics = states[name]["resource_metrics"]
|
||||
print_info(reporter, f"PIM artifacts saved to {pim_dir}")
|
||||
print_info(reporter, f"Pim artifacts saved to {pim_dir}")
|
||||
except Exception as exc:
|
||||
report_validation_failure(reporter, name, "compilation", exc)
|
||||
reporter.advance()
|
||||
@@ -735,7 +748,7 @@ def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
|
||||
else:
|
||||
input_batch = input_paths = reference_dirs = outputs_descriptor = None
|
||||
try:
|
||||
print_stage(reporter, model_index, model_total, network_onnx_path.name, "Generate Inputs")
|
||||
print_stage(reporter, model_index, model_total, network_onnx_path.name, "Generate inputs")
|
||||
inputs_descriptor, outputs_descriptor = onnx_io(network_onnx_path)
|
||||
first_inputs, _ = gen_random_inputs(inputs_descriptor, seed=seed)
|
||||
input_batch = generate_input_batch(
|
||||
@@ -754,7 +767,7 @@ def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
|
||||
|
||||
if not reference_ready:
|
||||
raise FileNotFoundError("reference runner is unavailable")
|
||||
print_stage(reporter, model_index, model_total, network_onnx_path.name, "Run Reference")
|
||||
print_stage(reporter, model_index, model_total, network_onnx_path.name, "Run reference")
|
||||
reference_dirs = []
|
||||
for index, flags in enumerate(input_flags):
|
||||
reference_dir = workspace_dir / "outputs" / f"{index:06d}"
|
||||
|
||||
@@ -16,6 +16,7 @@ if str(VALIDATION_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(VALIDATION_DIR))
|
||||
|
||||
from raptor_validation.onnx_utils import _ONNX_TO_NP, onnx_io, write_inputs_binary, write_inputs_to_memory_bin
|
||||
from raptor_validation.artifacts import artifacts_dir
|
||||
from raptor_validation.validate_one import (
|
||||
MODE_COMPILE_ONLY,
|
||||
build_dump_ranges,
|
||||
@@ -75,10 +76,11 @@ def ensure_local_artifacts(args, model_path: Path):
|
||||
|
||||
|
||||
def ensure_existing_artifacts(model_dir: Path):
|
||||
artifact_root = artifacts_dir(model_dir)
|
||||
required_paths = [
|
||||
model_dir / "runner" / "build" / "runner",
|
||||
model_dir / "raptor" / "pim" / "config.json",
|
||||
model_dir / "raptor" / "pim" / "memory.bin",
|
||||
artifact_root / "runner" / "build" / "runner",
|
||||
artifact_root / "raptor" / "pim" / "config.json",
|
||||
artifact_root / "raptor" / "pim" / "memory.bin",
|
||||
]
|
||||
missing = [str(path) for path in required_paths if not path.exists()]
|
||||
if missing:
|
||||
@@ -185,13 +187,13 @@ def draw_classification_panel(image: Image.Image, results, output_path: Path):
|
||||
|
||||
|
||||
def run_reference_and_simulator(args, model_path: Path, tensor: np.ndarray):
|
||||
model_dir = model_path.parent
|
||||
runner_build_dir = model_dir / "runner" / "build"
|
||||
artifact_root = artifacts_dir(model_path.parent)
|
||||
runner_build_dir = artifact_root / "runner" / "build"
|
||||
runner_path = runner_build_dir / "runner"
|
||||
pim_dir = model_dir / "raptor" / "pim"
|
||||
simulation_dir = model_dir / "classification_demo" / "simulation"
|
||||
reference_dir = model_dir / "classification_demo" / "reference"
|
||||
inputs_dir = model_dir / "classification_demo" / "inputs"
|
||||
pim_dir = artifact_root / "raptor" / "pim"
|
||||
simulation_dir = artifact_root / "classification_demo" / "simulation"
|
||||
reference_dir = artifact_root / "classification_demo" / "reference"
|
||||
inputs_dir = artifact_root / "classification_demo" / "inputs"
|
||||
|
||||
simulation_dir.mkdir(parents=True, exist_ok=True)
|
||||
reference_dir.mkdir(parents=True, exist_ok=True)
|
||||
@@ -222,7 +224,7 @@ def run_reference_and_simulator(args, model_path: Path, tensor: np.ndarray):
|
||||
subprocess.run(runner_cmd, cwd=runner_build_dir, check=True)
|
||||
|
||||
write_inputs_to_memory_bin(pim_dir / "memory.bin", pim_dir / "config.json", [tensor])
|
||||
input_bin_path = simulation_dir / "input.bin"
|
||||
input_bin_path = simulation_dir / "input_0.bin"
|
||||
write_inputs_binary(input_bin_path, [tensor])
|
||||
dump_ranges = build_dump_ranges(pim_dir / "config.json", output_descriptors)
|
||||
output_bin_path = simulation_dir / "out.bin"
|
||||
@@ -232,7 +234,8 @@ def run_reference_and_simulator(args, model_path: Path, tensor: np.ndarray):
|
||||
output_bin_path,
|
||||
dump_ranges,
|
||||
timeout_sec=args.command_timeout_seconds,
|
||||
input_paths=[input_bin_path],
|
||||
input_dir=input_bin_path.parent,
|
||||
batch_size=1,
|
||||
)
|
||||
|
||||
output_index, output_name, output_dtype_code, output_shape = output_descriptors[0]
|
||||
|
||||
@@ -37,55 +37,55 @@ class Tile:
|
||||
|
||||
|
||||
TILES = (
|
||||
Tile("classic-reference", "REFERENCE", "Classic Conv + exact weight unfolding",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
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."),
|
||||
@@ -473,7 +473,7 @@ def operation_scene(d: Drawio, parent: str, scene: str) -> None:
|
||||
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,
|
||||
"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")
|
||||
@@ -483,7 +483,7 @@ def reference_body(d: Drawio, parent: str) -> None:
|
||||
|
||||
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,
|
||||
"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")
|
||||
@@ -500,22 +500,22 @@ def reference_body(d: Drawio, parent: str) -> None:
|
||||
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"
|
||||
elif tile.owner == "Pimcomp":
|
||||
layout = "Pimcomp Spatial-major K order"
|
||||
else:
|
||||
layout = "RAPTOR channel-major K order"
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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)
|
||||
@@ -524,7 +524,7 @@ def algorithm_card(d: Drawio, parent: str, tile: Tile, accent: str) -> None:
|
||||
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]
|
||||
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,
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# Raptor compiler ablation
|
||||
|
||||
`run_ablation.py` performs the complete synchronization/Spatial-planning
|
||||
ablation study on the Pimcomp model suite. By default it runs `vgg8`,
|
||||
`resnet18`, `resnet34`, and `googlenet` across `arch-a` and `arch-b`, latency,
|
||||
and throughput pipeline 4. `arch-c` and `yolo11n` are run only when selected
|
||||
explicitly.
|
||||
Latency is pipeline 1; the wrapper invokes the suite runner separately for
|
||||
latency and throughput pipeline 4.
|
||||
|
||||
```bash
|
||||
.venv/bin/python validation/tools/pim/ablation/run_ablation.py \
|
||||
--jobs 4
|
||||
```
|
||||
|
||||
## Variants
|
||||
|
||||
| Variant | Raptor options |
|
||||
|---|---|
|
||||
| `baseline` | None. |
|
||||
| `no-sync` | `--pim-disable-synchronization` |
|
||||
| `no-plan` | `--pim-disable-spatial-planning` |
|
||||
| `no-sync-no-plan` | Both options. |
|
||||
|
||||
Every variant runs Raptor only. Pimcomp is not compiled, validated, or
|
||||
simulated. Reference inputs and outputs are generated once under the shared
|
||||
common-artifact root and reused by every variant. Ctrl+C terminates the active
|
||||
variant and all of its worker jobs.
|
||||
|
||||
The percentage baseline is `baseline`, so its available values are `+0.00%`.
|
||||
Every other
|
||||
variant reports the signed percentage difference of its Raptor metrics from
|
||||
that reference for the same model, architecture, mode, and pipeline. Positive
|
||||
values mean the metric is higher; negative values mean it is lower.
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Description and default |
|
||||
|---|---|
|
||||
| `-h`, `--help` | Show help and exit. |
|
||||
| `--out-dir PATH` | Suite root. Default: `validation/networks/pimcomp_models`. Model artifacts are stored below `<out-dir>/<model>/artifacts`; disabled variants use `<arch>/<mode>[/pipelineN]/ablation/<variant>`. Variant summaries remain under `<out-dir>/ablation/<variant>/`. |
|
||||
| `--models MODEL [...]` | Models to run. Default: `vgg8 resnet18 resnet34 googlenet`; include `yolo11n` explicitly when needed. |
|
||||
| `--variant NAME` | Select a variant from the table above; repeat for multiple variants. Default: all variants. The feature-full baseline and percentage reference are added automatically when needed. |
|
||||
| `--dry-run` | Print the suite-runner commands that would run without modifying files. Default: off. |
|
||||
|
||||
All options of
|
||||
[`run_pimcomp_models.py`](../pimcomp/compare/README.md), including
|
||||
`--archs`, `--mode`, `--pipeline`, `--no-fast`, and
|
||||
`--raptor-extra-arg=ARG`, are forwarded to each variant. `--out-dir`,
|
||||
`--variant`, and `--dry-run` belong to this wrapper; `--only` is reserved for
|
||||
the suite runner's comparison mode and is rejected by this wrapper, which
|
||||
always invokes `--raptor-only`. If neither `--mode` nor `--pipeline` is
|
||||
forwarded, the wrapper uses its default latency and throughput/pipeline-4
|
||||
case set. Supplying either option overrides that default and is passed through
|
||||
as one suite-runner invocation per variant.
|
||||
|
||||
The feature-full `baseline` variant uses the normal `run_pimcomp_models.py`
|
||||
artifact paths and is rerun with the same selected cases and forwarded options
|
||||
as the disabled variants. The three disabled variants are stored below each model's
|
||||
`artifacts/<arch>/<mode>[/pipelineN]/ablation/` directory. Shared reference
|
||||
artifacts remain under each model's `artifacts/common` directory. Transient
|
||||
per-variant comparison summaries are removed after aggregation. The combined
|
||||
table is written to `<out-dir>/results_ablation.csv`; it contains only the variant, case
|
||||
identifiers, and signed percentage `latency`, `throughput`, `power`, and
|
||||
`energy` columns. These are Raptor metrics;
|
||||
Pimcomp metrics are omitted because the ablation invokes Raptor only.
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the synchronization and Spatial-planning ablation matrix on Pimcomp models."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import math
|
||||
import os
|
||||
import shlex
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
REPO = Path(__file__).resolve().parents[4]
|
||||
RUNNER = REPO / "validation/tools/pim/pimcomp/compare/run_pimcomp_models.py"
|
||||
DEFAULT_OUT_DIR = REPO / "validation/networks/pimcomp_models"
|
||||
sys.path.insert(0, str(REPO / "validation"))
|
||||
|
||||
from raptor_validation.pimcomp_models import (
|
||||
ABLATION_DEFAULT_MODELS,
|
||||
add_models_argument,
|
||||
)
|
||||
from raptor_validation.artifacts import remove_lock_files
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Variant:
|
||||
name: str
|
||||
raptor_args: tuple[str, ...]
|
||||
|
||||
|
||||
VARIANTS = (
|
||||
Variant("baseline", ()),
|
||||
Variant("no-sync", ("--pim-disable-synchronization",)),
|
||||
Variant("no-plan", ("--pim-disable-spatial-planning",)),
|
||||
Variant(
|
||||
"no-sync-no-plan",
|
||||
("--pim-disable-synchronization", "--pim-disable-spatial-planning"),
|
||||
),
|
||||
)
|
||||
VARIANT_BY_NAME = {variant.name: variant for variant in VARIANTS}
|
||||
COMPARISON_RESULTS_FILENAME = "results_comparison.csv"
|
||||
ABLATION_RESULTS_FILENAME = "results_ablation.csv"
|
||||
CASE_FIELDS = ("arch", "model", "mode", "raptor_pipeline")
|
||||
PERCENTAGE_FIELDS = (
|
||||
("raptor_latency_ms", "latency"),
|
||||
("raptor_throughput_samples_s", "throughput"),
|
||||
("raptor_power_mw", "power"),
|
||||
("raptor_energy_pj", "energy"),
|
||||
)
|
||||
RESULT_FIELDS = (*CASE_FIELDS, *(target for _, target in PERCENTAGE_FIELDS))
|
||||
DEFAULT_CASE_ARGUMENTS = (
|
||||
("latency", ("--mode", "latency")),
|
||||
("throughput/pipeline4", ("--mode", "throughput", "--pipeline", "4")),
|
||||
)
|
||||
DEFAULT_CASE_KEYS = frozenset({("latency", "1"), ("throughput", "4")})
|
||||
|
||||
|
||||
def parse_args(argv: list[str] | None = None) -> tuple[argparse.Namespace, list[str]]:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run the complete compiler ablation matrix on the Pimcomp model suite.",
|
||||
allow_abbrev=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--out-dir",
|
||||
type=Path,
|
||||
default=DEFAULT_OUT_DIR,
|
||||
help="Artifact root (default: validation/networks/pimcomp_models).",
|
||||
)
|
||||
add_models_argument(parser, ABLATION_DEFAULT_MODELS)
|
||||
parser.add_argument(
|
||||
"--variant",
|
||||
choices=tuple(VARIANT_BY_NAME),
|
||||
action="append",
|
||||
dest="variants",
|
||||
help="Run only this variant; baseline is added when needed. Repeat as needed.",
|
||||
)
|
||||
parser.add_argument("--dry-run", action="store_true", help="Print runner commands without modifying files.")
|
||||
args, forwarded = parser.parse_known_args(argv)
|
||||
if any(option == "--only" or option.startswith("--only=") for option in forwarded):
|
||||
parser.error("--only is not supported; the ablation wrapper always runs Raptor only")
|
||||
return args, forwarded
|
||||
|
||||
|
||||
def selected_variants(names: list[str] | None) -> list[Variant]:
|
||||
requested = set(names or VARIANT_BY_NAME)
|
||||
requested.add("baseline")
|
||||
return [variant for variant in VARIANTS if variant.name in requested]
|
||||
|
||||
|
||||
def has_option(arguments: list[str], option: str) -> bool:
|
||||
return any(argument == option or argument.startswith(f"{option}=") for argument in arguments)
|
||||
|
||||
|
||||
def runner_argument_sets(forwarded: list[str]) -> tuple[tuple[str, list[str]], ...]:
|
||||
if has_option(forwarded, "--mode") or has_option(forwarded, "--pipeline"):
|
||||
return (("requested", forwarded),)
|
||||
return tuple(
|
||||
(label, [*forwarded, *case_arguments])
|
||||
for label, case_arguments in DEFAULT_CASE_ARGUMENTS
|
||||
)
|
||||
|
||||
|
||||
def variant_output_dir(out_dir: Path, variant: Variant) -> Path:
|
||||
return out_dir if variant.name == "baseline" else out_dir / "ablation" / variant.name
|
||||
|
||||
|
||||
def remove_variant_summaries(out_dir: Path) -> None:
|
||||
summary_root = out_dir / "ablation"
|
||||
if not summary_root.is_dir():
|
||||
return
|
||||
for summary in summary_root.glob("*/results_comparison.csv"):
|
||||
summary.unlink()
|
||||
for variant_dir in summary_root.iterdir():
|
||||
if variant_dir.is_dir() and not any(variant_dir.iterdir()):
|
||||
variant_dir.rmdir()
|
||||
if not any(summary_root.iterdir()):
|
||||
summary_root.rmdir()
|
||||
|
||||
|
||||
def runner_command(
|
||||
out_dir: Path,
|
||||
variant: Variant,
|
||||
models: list[str],
|
||||
forwarded: list[str],
|
||||
common_root: Path,
|
||||
dry_run: bool,
|
||||
) -> list[str]:
|
||||
command = [
|
||||
sys.executable,
|
||||
str(RUNNER),
|
||||
"--out-dir",
|
||||
str(out_dir),
|
||||
"--raptor-only",
|
||||
"--models",
|
||||
*models,
|
||||
]
|
||||
if variant.name != "baseline":
|
||||
command.extend(("--ablation-variant", variant.name))
|
||||
if not has_option(forwarded, "--common-dir"):
|
||||
command.extend(("--common-dir", str(common_root)))
|
||||
command.extend(forwarded)
|
||||
command.extend(f"--raptor-extra-arg={arg}" for arg in variant.raptor_args)
|
||||
if dry_run:
|
||||
command.append("--dry-run")
|
||||
return command
|
||||
|
||||
|
||||
def terminate_process_group(process: subprocess.Popen[bytes]) -> None:
|
||||
if process.poll() is not None:
|
||||
return
|
||||
try:
|
||||
os.killpg(process.pid, signal.SIGTERM)
|
||||
except ProcessLookupError:
|
||||
return
|
||||
try:
|
||||
process.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
try:
|
||||
os.killpg(process.pid, signal.SIGKILL)
|
||||
except ProcessLookupError:
|
||||
return
|
||||
process.wait()
|
||||
|
||||
|
||||
def percentage_difference(value: str | None, reference: str | None) -> str:
|
||||
try:
|
||||
current = float(value) if value is not None else math.nan
|
||||
baseline = float(reference) if reference is not None else math.nan
|
||||
except ValueError:
|
||||
return "NA"
|
||||
if not math.isfinite(current) or not math.isfinite(baseline) or baseline == 0:
|
||||
return "NA"
|
||||
return f"{(current - baseline) / baseline * 100:+.2f}%"
|
||||
|
||||
|
||||
def aggregate_results(
|
||||
out_dir: Path,
|
||||
variants: list[Variant],
|
||||
selected_cases: frozenset[tuple[str, str]] | None = None,
|
||||
selected_models: frozenset[str] | None = None,
|
||||
) -> tuple[Path | None, list[str]]:
|
||||
fields: list[str] | None = None
|
||||
rows_by_variant: dict[str, list[dict[str, str]]] = {}
|
||||
failures = []
|
||||
for variant in variants:
|
||||
results_path = variant_output_dir(out_dir, variant) / COMPARISON_RESULTS_FILENAME
|
||||
if not results_path.is_file():
|
||||
failures.append(f"{variant.name}: missing {results_path}")
|
||||
continue
|
||||
with results_path.open(newline="", encoding="utf-8") as stream:
|
||||
reader = csv.DictReader(stream)
|
||||
if reader.fieldnames is None:
|
||||
failures.append(f"{variant.name}: empty {results_path}")
|
||||
continue
|
||||
if fields is None:
|
||||
fields = reader.fieldnames
|
||||
elif reader.fieldnames != fields:
|
||||
failures.append(f"{variant.name}: inconsistent columns in {results_path}")
|
||||
continue
|
||||
selected_rows = rows_by_variant.setdefault(variant.name, [])
|
||||
for row in reader:
|
||||
if selected_models is not None and row.get("model") not in selected_models:
|
||||
continue
|
||||
if selected_cases is not None and (
|
||||
row.get("mode"), row.get("raptor_pipeline")
|
||||
) not in selected_cases:
|
||||
continue
|
||||
selected_rows.append(row)
|
||||
if fields is None:
|
||||
return None, failures
|
||||
baseline_rows = {
|
||||
tuple(row.get(field, "") for field in CASE_FIELDS): row
|
||||
for row in rows_by_variant.get("baseline", [])
|
||||
}
|
||||
if "baseline" not in rows_by_variant:
|
||||
failures.append("baseline: missing percentage reference results")
|
||||
result_rows = []
|
||||
variant_order = {variant.name: index for index, variant in enumerate(VARIANTS)}
|
||||
for variant in variants:
|
||||
for row in rows_by_variant.get(variant.name, []):
|
||||
reference = baseline_rows.get(tuple(row.get(field, "") for field in CASE_FIELDS))
|
||||
result_rows.append(
|
||||
{
|
||||
"variant": variant.name,
|
||||
**{field: row.get(field, "") for field in CASE_FIELDS},
|
||||
**{
|
||||
target: percentage_difference(
|
||||
row.get(source), reference.get(source) if reference else None
|
||||
)
|
||||
for source, target in PERCENTAGE_FIELDS
|
||||
},
|
||||
}
|
||||
)
|
||||
result_rows.sort(
|
||||
key=lambda row: (
|
||||
row["arch"],
|
||||
row["model"],
|
||||
row["mode"],
|
||||
variant_order[row["variant"]],
|
||||
int(row["raptor_pipeline"]),
|
||||
)
|
||||
)
|
||||
output = out_dir / ABLATION_RESULTS_FILENAME
|
||||
with output.open("w", newline="", encoding="utf-8") as stream:
|
||||
writer = csv.DictWriter(stream, fieldnames=("variant", *RESULT_FIELDS), lineterminator="\n")
|
||||
writer.writeheader()
|
||||
writer.writerows(result_rows)
|
||||
return output, failures
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
args, forwarded = parse_args(argv)
|
||||
if not RUNNER.is_file():
|
||||
print(f"Missing Pimcomp runner: {RUNNER}", file=sys.stderr)
|
||||
return 1
|
||||
variants = selected_variants(args.variants)
|
||||
out_dir = args.out_dir.resolve()
|
||||
case_sets = runner_argument_sets(forwarded)
|
||||
if args.dry_run:
|
||||
for variant in variants:
|
||||
for _, case_forwarded in case_sets:
|
||||
print(
|
||||
shlex.join(
|
||||
runner_command(
|
||||
out_dir,
|
||||
variant,
|
||||
args.models,
|
||||
case_forwarded,
|
||||
out_dir,
|
||||
True,
|
||||
)
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
failed = []
|
||||
current_process = None
|
||||
try:
|
||||
for variant in variants:
|
||||
for case_label, case_forwarded in case_sets:
|
||||
command = runner_command(
|
||||
out_dir, variant, args.models, case_forwarded, out_dir, False
|
||||
)
|
||||
print(f"[{variant.name}/{case_label}] {shlex.join(command)}")
|
||||
current_process = subprocess.Popen(
|
||||
command,
|
||||
cwd=REPO,
|
||||
start_new_session=True,
|
||||
)
|
||||
returncode = current_process.wait()
|
||||
current_process = None
|
||||
if returncode:
|
||||
failed.append(f"{variant.name}/{case_label}: runner exited with {returncode}")
|
||||
except KeyboardInterrupt:
|
||||
if current_process is not None:
|
||||
terminate_process_group(current_process)
|
||||
remove_lock_files(out_dir)
|
||||
remove_variant_summaries(out_dir)
|
||||
print("Interrupted; terminated the active ablation job.", file=sys.stderr)
|
||||
return 130
|
||||
|
||||
remove_lock_files(out_dir)
|
||||
selected_cases = DEFAULT_CASE_KEYS if len(case_sets) > 1 else None
|
||||
output, aggregation_failures = aggregate_results(
|
||||
out_dir,
|
||||
variants,
|
||||
selected_cases,
|
||||
frozenset(args.models),
|
||||
)
|
||||
remove_variant_summaries(out_dir)
|
||||
failed.extend(aggregation_failures)
|
||||
if output is not None:
|
||||
print(f"Ablation results: {output}")
|
||||
if failed:
|
||||
print("Failed: " + "; ".join(failed), file=sys.stderr)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,62 @@
|
||||
# Pimcomp model comparison
|
||||
|
||||
These scripts compare Raptor with Pimcomp on the models in
|
||||
`validation/networks/pimcomp_models`.
|
||||
|
||||
`run_pimcomp_models.py` runs the supported model suite across the configured
|
||||
architectures and simulation modes. `compare_raptor_pimcomp_model.py` is the
|
||||
lower-level one-model comparison used by the suite runner.
|
||||
|
||||
## Suite runner
|
||||
|
||||
Run all five models, Arch-A and Arch-B, latency, and throughput:
|
||||
|
||||
```bash
|
||||
.venv/bin/python validation/tools/pim/pimcomp/compare/run_pimcomp_models.py
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
| Option | Description and default |
|
||||
|---|---|
|
||||
| `-h`, `--help` | Show help and exit. |
|
||||
| `--out-dir PATH` | Suite root; artifacts are placed below `<out-dir>/<model>/artifacts/`. Default: `validation/networks/pimcomp_models`. |
|
||||
| `--common-dir PATH` | Shared root for per-model reference artifacts. Default: `<out-dir>/<model>/artifacts/common`. |
|
||||
| `--models MODEL [...]` | Models to run: `vgg8`, `resnet18`, `resnet34`, `googlenet`, `yolo11n`. Default: all five; pass a subset to select specific models. |
|
||||
| `--archs ARCH [...]` | Pimcomp hardware profiles to run. Default: `arch-a arch-b`. |
|
||||
| `--mode {latency,throughput} [...]` | Simulation modes. Default: both. |
|
||||
| `--only {raptor,pimcomp}` | Re-run only one compiler side and reuse the other side's existing report. Default: run both sides. |
|
||||
| `--raptor-only` | Run Raptor without compiling, validating, or simulating Pimcomp. Default: off. |
|
||||
| `--pipeline {1,2,4,8}` | Select one Raptor pipeline. Default: latency pipeline 1 and throughput pipelines 2, 4, and 8. |
|
||||
| `--pimsim-time-ms INT` | Throughput convergence deadline. Default: `1000`. |
|
||||
| `--batch-size INT` | Functional throughput batch size. Default: `128`. |
|
||||
| `--timeout-seconds FLOAT` | Per-stage timeout; `0` means unlimited. Default: `0`. |
|
||||
| `-j INT`, `--jobs INT` | Parallel comparison workers. Default: `4`. |
|
||||
| `--clean` | Remove generated comparison artifacts and summaries, then exit. Default: off. |
|
||||
| `--ablation-variant NAME` | Put generated artifacts below `<mode>[/pipelineN]/ablation/NAME` and write the summary below `ablation/NAME`. Default: none. |
|
||||
| `--dry-run` | Print commands without modifying files. Default: off. |
|
||||
| `--no-fast` | Disable fast throughput convergence. Default: off. |
|
||||
| `--raptor-extra-arg=ARG` | Extra Raptor compiler argument; repeat for multiple arguments. Default: none. |
|
||||
|
||||
Arguments beginning with `--` must use the equals form when passed through:
|
||||
|
||||
```bash
|
||||
.venv/bin/python validation/tools/pim/pimcomp/compare/run_pimcomp_models.py \
|
||||
--models vgg8 \
|
||||
--raptor-extra-arg=--pim-disable-synchronization
|
||||
```
|
||||
|
||||
The runner writes `results_comparison.csv` under the selected result root. It reuses
|
||||
shared model inputs, outputs, and reference runners, and reuses Pimcomp
|
||||
artifacts between pipelines in the same model/architecture/mode group. With
|
||||
`--raptor-only`, only Raptor results are generated and Pimcomp is not invoked.
|
||||
All generated files are kept below each model's `artifacts/` directory;
|
||||
`--clean` also removes any `.lock` files left by an interrupted reference
|
||||
generation.
|
||||
|
||||
## One-model comparator
|
||||
|
||||
`compare_raptor_pimcomp_model.py` compares one ONNX model. Its required
|
||||
arguments are `--model PATH` and `--out-dir PATH`; use `--help` for the full
|
||||
lower-level interface. The suite runner supplies the model, hardware profile,
|
||||
simulation mode, and reuse paths automatically.
|
||||
+320
-157
File diff suppressed because it is too large
Load Diff
+192
-84
@@ -19,35 +19,37 @@ from colorama import Fore, Style
|
||||
|
||||
|
||||
REPO = Path(__file__).resolve().parents[5]
|
||||
SUITE = REPO / "validation/networks/pimcomp_models"
|
||||
sys.path.insert(0, str(REPO / "validation"))
|
||||
|
||||
from raptor_validation.pimsim_nn import parse_pimsim_nn_metrics # noqa: E402
|
||||
from raptor_validation.validate_one import STAGE_COLORS # noqa: E402
|
||||
from raptor_validation.pimcomp_models import (
|
||||
FUNCTIONAL_MODELS,
|
||||
MODELS,
|
||||
SUITE,
|
||||
add_models_argument,
|
||||
)
|
||||
from raptor_validation.artifacts import artifacts_dir, remove_lock_files
|
||||
from raptor_validation.pimsim_nn import parse_pimsim_nn_metrics
|
||||
from raptor_validation.validate_one import STAGE_COLORS
|
||||
|
||||
PIMCOMP_SOURCE = REPO / "third_party/PIMCOMP-NN"
|
||||
PIMCOMP_CONFIGS = REPO / "validation/pimsim_configs/pimcomp"
|
||||
COMPARE = Path(__file__).resolve().with_name("compare_raptor_pimcomp.py")
|
||||
COMPARE = Path(__file__).resolve().with_name("compare_raptor_pimcomp_model.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-pimsim-nn.onnx",
|
||||
"yolo11n": SUITE / "yolo11n/yolo11n-pimsim-nn.onnx",
|
||||
}
|
||||
DEFAULT_ARCHES = ("arch-a", "arch-b")
|
||||
COMPARISONS = (
|
||||
("latency", 1, "element"),
|
||||
("throughput", 2, "batch"),
|
||||
("throughput", 4, "batch"),
|
||||
("throughput", 8, "batch"),
|
||||
)
|
||||
RESULTS_FILENAME = "results_comparison.csv"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ComparisonSpec:
|
||||
label: str
|
||||
model: Path
|
||||
functional_model: Path
|
||||
output_dir: Path
|
||||
common_dir: Path
|
||||
config: Path
|
||||
@@ -62,34 +64,60 @@ def model_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, arch: str, mode: str, pipeline: int) -> Path:
|
||||
base = model_dir(root, name)
|
||||
def result_dir(
|
||||
root: Path | None,
|
||||
name: str,
|
||||
arch: str,
|
||||
mode: str,
|
||||
pipeline: int,
|
||||
ablation_variant: str | None = None,
|
||||
) -> Path:
|
||||
base = artifacts_dir(model_dir(root, name))
|
||||
suffix = "latency" if mode == "latency" else f"throughput/pipeline{pipeline}"
|
||||
return base / arch / suffix
|
||||
result = base / arch / suffix
|
||||
if ablation_variant is not None:
|
||||
result /= Path("ablation") / ablation_variant
|
||||
return result
|
||||
|
||||
|
||||
def clean_artifacts(root: Path | None, models: list[str], arches: list[str]) -> int:
|
||||
def common_dir(root: Path | None, name: str, common_root: Path | None = None) -> Path:
|
||||
suffix = "common" if FUNCTIONAL_MODELS[name] == MODELS[name] else "common-functional"
|
||||
base = common_root / name if common_root is not None else model_dir(root, name)
|
||||
return artifacts_dir(base) / suffix
|
||||
|
||||
|
||||
def clean_artifacts(
|
||||
root: Path | None,
|
||||
models: list[str],
|
||||
common_root: Path | None = None,
|
||||
) -> int:
|
||||
removed = 0
|
||||
for name in models:
|
||||
base = model_dir(root, name)
|
||||
arch_dirs = {base / arch for arch in arches}
|
||||
arch_dirs.update(path for path in base.glob("arch-*") if path.is_dir() and not path.is_symlink())
|
||||
for path in arch_dirs:
|
||||
for path in (artifacts_dir(base),):
|
||||
if path.is_dir() and not path.is_symlink():
|
||||
shutil.rmtree(path)
|
||||
removed += 1
|
||||
common = base / "common"
|
||||
if common.is_dir() and not common.is_symlink():
|
||||
shutil.rmtree(common)
|
||||
removed += 1
|
||||
for path in (
|
||||
(root or SUITE) / "results.csv",
|
||||
(root or SUITE) / "results_latency.csv",
|
||||
(root or SUITE) / "results_throughput.csv",
|
||||
(root or SUITE) / RESULTS_FILENAME,
|
||||
(root or SUITE) / "results_ablation.csv",
|
||||
):
|
||||
if path.is_file() or path.is_symlink():
|
||||
path.unlink()
|
||||
removed += 1
|
||||
summary_root = root or SUITE
|
||||
if (summary_root / "ablation").is_dir():
|
||||
for path in (summary_root / "ablation").glob("*/results_comparison.csv"):
|
||||
path.unlink(missing_ok=True)
|
||||
removed += 1
|
||||
removed += remove_lock_files(summary_root)
|
||||
if common_root is not None:
|
||||
for name in models:
|
||||
path = artifacts_dir(common_root / name)
|
||||
if path.is_dir() and not path.is_symlink():
|
||||
shutil.rmtree(path)
|
||||
removed += 1
|
||||
removed += remove_lock_files(common_root)
|
||||
return removed
|
||||
|
||||
|
||||
@@ -98,8 +126,11 @@ def write_results_csv(
|
||||
arch: str,
|
||||
models: list[str],
|
||||
comparisons: tuple[tuple[str, int, str], ...] = COMPARISONS,
|
||||
ablation_variant: str | None = None,
|
||||
) -> Path:
|
||||
output = (root or SUITE) / "results.csv"
|
||||
output = (root or SUITE) / RESULTS_FILENAME
|
||||
if ablation_variant is not None:
|
||||
output = output.parent / "ablation" / ablation_variant / RESULTS_FILENAME
|
||||
fields = (
|
||||
"arch",
|
||||
"model",
|
||||
@@ -142,7 +173,9 @@ def write_results_csv(
|
||||
rows.append({field: row.get(field, "NA") for field in fields})
|
||||
for name in models:
|
||||
for comparison_mode, pipeline, pimcomp_pipeline in comparisons:
|
||||
report_path = result_dir(root, name, arch, comparison_mode, pipeline) / "pimcomp/comparison_report.json"
|
||||
report_path = result_dir(
|
||||
root, name, arch, comparison_mode, pipeline, ablation_variant
|
||||
) / "pimcomp/comparison_report.json"
|
||||
row = {
|
||||
"model": name,
|
||||
"arch": arch,
|
||||
@@ -205,6 +238,7 @@ def write_results_csv(
|
||||
int(row["raptor_pipeline"]),
|
||||
)
|
||||
)
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(output, "w", newline="", encoding="utf-8") as f:
|
||||
writer = csv.DictWriter(f, fieldnames=fields, lineterminator="\n")
|
||||
writer.writeheader()
|
||||
@@ -227,23 +261,9 @@ def performance_values(performance: dict) -> dict[str, float | None]:
|
||||
}
|
||||
|
||||
|
||||
def comparison_passed(report: dict, compiler: str | None = None) -> bool:
|
||||
other_compiler = "PIMCOMP" if compiler == "raptor" else "RAPTOR"
|
||||
if any(
|
||||
compiler is None or other_compiler not in failure.get("stage", "").upper()
|
||||
for failure in report.get("failures", [])
|
||||
):
|
||||
return False
|
||||
compilers = (compiler,) if compiler is not None else ("raptor", "pimcomp")
|
||||
for name in compilers:
|
||||
result = report.get(f"{name}_validation") or {}
|
||||
if result.get("status") != "done" or not result.get("passed"):
|
||||
return False
|
||||
for name in compilers:
|
||||
performance = report.get(f"{name}_performance") or {}
|
||||
if performance.get("error") or performance.get("skipped"):
|
||||
return False
|
||||
return True
|
||||
def comparison_passed(report: dict) -> bool:
|
||||
result = report.get("raptor_validation") or {}
|
||||
return result.get("status") == "done" and bool(result.get("passed"))
|
||||
|
||||
|
||||
def functional_validation_status(result: dict | None) -> str:
|
||||
@@ -265,11 +285,11 @@ def format_value(value: float | None) -> str:
|
||||
|
||||
|
||||
def print_stage(title: str, color: str) -> None:
|
||||
print("\n" + Style.BRIGHT + color + f"[{title}]" + Style.RESET_ALL, flush=True)
|
||||
print(Style.BRIGHT + color + f"[{title}]" + Style.RESET_ALL, flush=True)
|
||||
|
||||
|
||||
def print_completed(label: str, output: str = "") -> None:
|
||||
print("\n" + Style.BRIGHT + Fore.CYAN + f"[Completed {label}]" + Style.RESET_ALL)
|
||||
print(Style.BRIGHT + Fore.CYAN + f"[Completed {label}]" + Style.RESET_ALL)
|
||||
if output:
|
||||
print(output, end="" if output.endswith("\n") else "\n")
|
||||
print("=" * 72, flush=True)
|
||||
@@ -311,11 +331,12 @@ def validate_pimcomp_source() -> None:
|
||||
source = header.read_text(encoding="utf-8")
|
||||
for setting in ("int population_num = 200;", "int max_iteration = 1000;"):
|
||||
if setting not in source:
|
||||
raise RuntimeError(f"PIMCOMP paper setting is missing: {setting}")
|
||||
raise RuntimeError(f"Pimcomp paper setting is missing: {setting}")
|
||||
|
||||
|
||||
def comparison_command(
|
||||
model: Path,
|
||||
functional_model: Path,
|
||||
result_dir: Path,
|
||||
common_dir: Path,
|
||||
config: Path,
|
||||
@@ -326,6 +347,8 @@ def comparison_command(
|
||||
batch_size: int,
|
||||
timeout: float,
|
||||
fast: bool,
|
||||
raptor_extra_args: list[str] | tuple[str, ...] = (),
|
||||
raptor_only: bool = False,
|
||||
reuse_raptor_report: Path | None = None,
|
||||
reuse_pimcomp_dir: Path | None = None,
|
||||
reuse_pimcomp_report: Path | None = None,
|
||||
@@ -343,6 +366,8 @@ def comparison_command(
|
||||
str(COMPARE),
|
||||
"--model",
|
||||
str(model),
|
||||
"--functional-model",
|
||||
str(functional_model),
|
||||
"--out-dir",
|
||||
str(result_dir),
|
||||
"--common-dir",
|
||||
@@ -360,16 +385,23 @@ def comparison_command(
|
||||
pimcomp_pipeline,
|
||||
"--pimcomp-replication",
|
||||
"GA",
|
||||
*(["--raptor-only"] if raptor_only else []),
|
||||
f"--raptor-extra-arg=--pipeline={pipeline}",
|
||||
*[f"--raptor-extra-arg={arg}" for arg in raptor_extra_args],
|
||||
"--timeout-seconds",
|
||||
str(timeout),
|
||||
"--fail-on-error",
|
||||
*([] if fast else ["--no-fast"]),
|
||||
*reuse_args,
|
||||
]
|
||||
|
||||
|
||||
def prepare_common_command(model: Path, common_dir: Path, timeout: float) -> list[str]:
|
||||
def prepare_common_command(
|
||||
model: Path,
|
||||
common_dir: Path,
|
||||
timeout: float,
|
||||
batch_size: int,
|
||||
jobs: int,
|
||||
) -> list[str]:
|
||||
return [
|
||||
sys.executable,
|
||||
str(COMPARE),
|
||||
@@ -380,6 +412,10 @@ def prepare_common_command(model: Path, common_dir: Path, timeout: float) -> lis
|
||||
"--common-dir",
|
||||
str(common_dir),
|
||||
"--prepare-common",
|
||||
"--batch-size",
|
||||
str(batch_size),
|
||||
"--jobs",
|
||||
str(jobs),
|
||||
"--timeout-seconds",
|
||||
str(timeout),
|
||||
]
|
||||
@@ -394,6 +430,7 @@ def comparison_command_for(
|
||||
report = spec.output_dir / "pimcomp/comparison_report.json"
|
||||
return comparison_command(
|
||||
spec.model,
|
||||
spec.functional_model,
|
||||
spec.output_dir,
|
||||
spec.common_dir,
|
||||
spec.config,
|
||||
@@ -404,13 +441,19 @@ def comparison_command_for(
|
||||
args.batch_size,
|
||||
args.timeout_seconds,
|
||||
not args.no_fast,
|
||||
args.raptor_extra_args,
|
||||
args.raptor_only,
|
||||
reuse_raptor_report=(
|
||||
report
|
||||
if args.only == "pimcomp"
|
||||
else None
|
||||
),
|
||||
reuse_pimcomp_dir=spec.shared_pimcomp_dir if reuse_shared_pimcomp and args.only != "raptor" else None,
|
||||
reuse_pimcomp_report=report if args.only == "raptor" else None,
|
||||
reuse_pimcomp_dir=(
|
||||
spec.shared_pimcomp_dir
|
||||
if reuse_shared_pimcomp and args.only != "raptor" and not args.raptor_only
|
||||
else None
|
||||
),
|
||||
reuse_pimcomp_report=report if args.only == "raptor" and not args.raptor_only else None,
|
||||
)
|
||||
|
||||
|
||||
@@ -483,20 +526,29 @@ def config_path(arch: str, mode: str, sim_time_ms: int, *, write: bool) -> Path:
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Compare supported PIMCOMP models with Raptor latency and throughput schedules."
|
||||
description="Compare supported Pimcomp models with Raptor latency and throughput schedules."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--out-dir",
|
||||
type=Path,
|
||||
help="Result root (default: artifacts beside each model under validation/).",
|
||||
help="Suite root; generated artifacts go below each model's artifacts/ directory (default: validation/networks/pimcomp_models).",
|
||||
)
|
||||
parser.add_argument("--models", nargs="+", choices=MODELS, default=list(MODELS))
|
||||
parser.add_argument(
|
||||
"--arch",
|
||||
"--common-dir",
|
||||
type=Path,
|
||||
help="Shared root for per-model reference artifacts. Default: inside --out-dir.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ablation-variant",
|
||||
help="Place Raptor artifacts below <mode>/ablation/<variant> and write the comparison summary below ablation/<variant>.",
|
||||
)
|
||||
add_models_argument(parser)
|
||||
parser.add_argument(
|
||||
"--archs",
|
||||
nargs="+",
|
||||
choices=ARCHES,
|
||||
default=list(ARCHES),
|
||||
help="PIM architectures to run (default: all architectures).",
|
||||
default=list(DEFAULT_ARCHES),
|
||||
help=f"Pim architectures to run (default: {', '.join(DEFAULT_ARCHES)}).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--mode",
|
||||
@@ -510,6 +562,11 @@ def main() -> int:
|
||||
choices=("raptor", "pimcomp"),
|
||||
help="Re-run only this compiler's compile, validation, and simulation stages; preserve the other side from its report.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--raptor-only",
|
||||
action="store_true",
|
||||
help="Run only Raptor; do not compile, validate, or simulate Pimcomp. Default: off.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pipeline",
|
||||
type=int,
|
||||
@@ -520,13 +577,13 @@ def main() -> int:
|
||||
"--pimsim-time-ms",
|
||||
type=int,
|
||||
default=1000,
|
||||
help="throughput pimsim-nn convergence deadline in ms (default: 1000).",
|
||||
help="Throughput Pimsim convergence deadline in ms (default: 1000).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--batch-size",
|
||||
type=int,
|
||||
default=128,
|
||||
help="functional throughput batch size (default: 128).",
|
||||
default=64,
|
||||
help="functional throughput and shared reference batch size (default: 64).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--timeout-seconds",
|
||||
@@ -550,15 +607,32 @@ def main() -> int:
|
||||
parser.add_argument(
|
||||
"--no-fast",
|
||||
action="store_true",
|
||||
help="Disable fast pimsim-nn throughput convergence for authoritative experiments.",
|
||||
help="Disable fast Pimsim throughput convergence for authoritative experiments.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--raptor-extra-arg",
|
||||
action="append",
|
||||
default=[],
|
||||
dest="raptor_extra_args",
|
||||
help="Additional argument to pass to Raptor; repeat as needed.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.ablation_variant and Path(args.ablation_variant).name != args.ablation_variant:
|
||||
parser.error("--ablation-variant must be a single directory name")
|
||||
|
||||
if args.only is not None and args.raptor_only:
|
||||
parser.error("--only cannot be combined with --raptor-only")
|
||||
|
||||
out_dir = args.out_dir.resolve() if args.out_dir is not None else None
|
||||
args.arch = list(dict.fromkeys(args.arch))
|
||||
common_root = args.common_dir.resolve() if args.common_dir is not None else None
|
||||
args.archs = list(dict.fromkeys(args.archs))
|
||||
args.mode = list(dict.fromkeys(args.mode))
|
||||
if args.clean:
|
||||
print(f"Removed {clean_artifacts(out_dir, args.models, args.arch)} comparison artifact path(s).")
|
||||
print(
|
||||
f"Removed {clean_artifacts(out_dir, args.models, common_root)} "
|
||||
"comparison artifact path(s)."
|
||||
)
|
||||
return 0
|
||||
|
||||
if args.jobs < 1:
|
||||
@@ -571,7 +645,7 @@ def main() -> int:
|
||||
parser.error("--timeout-seconds must be non-negative")
|
||||
comparisons_by_arch: dict[str, tuple[tuple[str, int, str], ...]] = {}
|
||||
configs_by_arch: dict[str, dict[str, Path]] = {}
|
||||
for arch in args.arch:
|
||||
for arch in args.archs:
|
||||
comparisons = tuple(
|
||||
comparison for comparison in COMPARISONS
|
||||
if comparison[0] in args.mode
|
||||
@@ -585,17 +659,29 @@ def main() -> int:
|
||||
}
|
||||
comparisons_by_arch[arch] = comparisons
|
||||
configs_by_arch[arch] = configs
|
||||
reference_batch_size = max(
|
||||
1 if mode == "latency" else args.batch_size
|
||||
for comparisons in comparisons_by_arch.values()
|
||||
for mode, _, _ in comparisons
|
||||
)
|
||||
|
||||
missing = [str(MODELS[name]) for name in args.models if not MODELS[name].exists()]
|
||||
missing = [
|
||||
str(path)
|
||||
for name in args.models
|
||||
for path in (MODELS[name], FUNCTIONAL_MODELS[name])
|
||||
if not path.exists()
|
||||
]
|
||||
if missing:
|
||||
parser.error(f"missing model(s): {', '.join(missing)}")
|
||||
|
||||
if args.only is not None:
|
||||
missing_reuse = []
|
||||
for arch in args.arch:
|
||||
for arch in args.archs:
|
||||
for name in args.models:
|
||||
for mode, pipeline, _ in comparisons_by_arch[arch]:
|
||||
comparison_dir = result_dir(out_dir, name, arch, mode, pipeline)
|
||||
comparison_dir = result_dir(
|
||||
out_dir, name, arch, mode, pipeline, args.ablation_variant
|
||||
)
|
||||
required = comparison_dir / "pimcomp/comparison_report.json"
|
||||
if not required.exists():
|
||||
missing_reuse.append(str(required))
|
||||
@@ -610,15 +696,17 @@ def main() -> int:
|
||||
+ ", ".join(missing_reuse)
|
||||
)
|
||||
|
||||
if not args.raptor_only:
|
||||
validate_pimcomp_source()
|
||||
if out_dir is not None and not args.dry_run:
|
||||
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"Architectures: {', '.join(args.arch)}")
|
||||
print(Style.BRIGHT + f"Found {len(args.models)} Pimcomp model(s) to compare." + Style.RESET_ALL)
|
||||
print(f"Architectures: {', '.join(args.archs)}")
|
||||
print(f"Modes: {', '.join(args.mode)}")
|
||||
print(f"Throughput pimsim time: {args.pimsim_time_ms} ms")
|
||||
print(f"Throughput Pimsim time: {args.pimsim_time_ms} ms")
|
||||
print(f"Max parallel jobs: {args.jobs}")
|
||||
print(f"Shared reference batch: {reference_batch_size}")
|
||||
print(
|
||||
f"Comparison jobs: "
|
||||
f"{sum(len(args.models) * len(comparisons) for comparisons in comparisons_by_arch.values())}"
|
||||
@@ -626,29 +714,38 @@ def main() -> int:
|
||||
print(f"Results root: {out_dir or SUITE}")
|
||||
print("=" * 72)
|
||||
|
||||
print_stage("Prepare shared artifacts", STAGE_COLORS["Build Runner"])
|
||||
print_stage("Prepare shared artifacts", STAGE_COLORS["Build runner"])
|
||||
try:
|
||||
for name in args.models:
|
||||
try:
|
||||
run(
|
||||
prepare_common_command(
|
||||
MODELS[name],
|
||||
model_dir(out_dir, name) / "common",
|
||||
FUNCTIONAL_MODELS[name],
|
||||
common_dir(out_dir, name, common_root),
|
||||
args.timeout_seconds,
|
||||
reference_batch_size,
|
||||
args.jobs,
|
||||
),
|
||||
dry_run=args.dry_run,
|
||||
)
|
||||
finally:
|
||||
print_completed(f"shared artifacts: {name}")
|
||||
except KeyboardInterrupt:
|
||||
remove_lock_files(out_dir or SUITE)
|
||||
print("Interrupted; cleaned validation lock files.", file=sys.stderr)
|
||||
return 130
|
||||
|
||||
failed = []
|
||||
comparison_specs: list[ComparisonSpec] = []
|
||||
shared_pimcomp_by_group: dict[tuple[str, str, str], Path] = {}
|
||||
for arch in args.arch:
|
||||
for arch in args.archs:
|
||||
comparisons = comparisons_by_arch[arch]
|
||||
configs = configs_by_arch[arch]
|
||||
for name in args.models:
|
||||
for mode, pipeline, pimcomp_pipeline in comparisons:
|
||||
model_result_dir = result_dir(out_dir, name, arch, mode, pipeline)
|
||||
model_result_dir = result_dir(
|
||||
out_dir, name, arch, mode, pipeline, args.ablation_variant
|
||||
)
|
||||
label = f"{arch}/{name}/{mode}/pipeline{pipeline}"
|
||||
group = (arch, name, mode)
|
||||
shared_pimcomp_dir = shared_pimcomp_by_group.get(group)
|
||||
@@ -660,8 +757,9 @@ def main() -> int:
|
||||
ComparisonSpec(
|
||||
label=label,
|
||||
model=MODELS[name],
|
||||
functional_model=FUNCTIONAL_MODELS[name],
|
||||
output_dir=model_result_dir,
|
||||
common_dir=model_dir(out_dir, name) / "common",
|
||||
common_dir=common_dir(out_dir, name, common_root),
|
||||
config=configs[mode],
|
||||
mode=mode,
|
||||
pipeline=pipeline,
|
||||
@@ -681,7 +779,8 @@ def main() -> int:
|
||||
if run(command, dry_run=True, check=False):
|
||||
failed.append(spec.label)
|
||||
elif comparison_specs:
|
||||
anchor_specs = comparison_specs if args.only == "raptor" else [
|
||||
raptor_only_run = args.only == "raptor" or args.raptor_only
|
||||
anchor_specs = comparison_specs if raptor_only_run else [
|
||||
spec for spec in comparison_specs if spec.anchor
|
||||
]
|
||||
anchor_jobs = [
|
||||
@@ -692,10 +791,11 @@ def main() -> int:
|
||||
)
|
||||
for spec in anchor_specs
|
||||
]
|
||||
dependent_specs = [] if args.only == "raptor" else [
|
||||
dependent_specs = [] if raptor_only_run else [
|
||||
spec for spec in comparison_specs if not spec.anchor
|
||||
]
|
||||
print_directly = min(args.jobs, len(comparison_specs)) == 1
|
||||
try:
|
||||
with (nullcontext(None) if print_directly else TemporaryDirectory(prefix="raptor-pimcomp-")) as log_dir:
|
||||
anchor_failed, log_offset = run_comparison_jobs(
|
||||
anchor_jobs,
|
||||
@@ -723,33 +823,41 @@ def main() -> int:
|
||||
log_offset,
|
||||
)
|
||||
failed.extend(dependent_failed)
|
||||
except KeyboardInterrupt:
|
||||
remove_lock_files(out_dir or SUITE)
|
||||
print("Interrupted; cleaned validation lock files.", file=sys.stderr)
|
||||
return 130
|
||||
|
||||
if args.dry_run:
|
||||
return 1 if failed else 0
|
||||
|
||||
remove_lock_files(out_dir or SUITE)
|
||||
|
||||
results_path = None
|
||||
for arch in args.arch:
|
||||
for arch in args.archs:
|
||||
results_path = write_results_csv(
|
||||
out_dir,
|
||||
arch,
|
||||
args.models,
|
||||
comparisons_by_arch[arch],
|
||||
args.ablation_variant,
|
||||
)
|
||||
assert results_path is not None
|
||||
print_stage(results_path.name, STAGE_COLORS["Compare Outputs"])
|
||||
print_stage(results_path.name, STAGE_COLORS["Compare outputs"])
|
||||
print(results_path.read_text(encoding="utf-8"), end="")
|
||||
for arch in args.arch:
|
||||
for arch in args.archs:
|
||||
for name in args.models:
|
||||
for mode, pipeline, _ in comparisons_by_arch[arch]:
|
||||
label = f"{arch}/{name}/{mode}/pipeline{pipeline}"
|
||||
report_path = result_dir(out_dir, name, arch, mode, pipeline) / "pimcomp/comparison_report.json"
|
||||
report_path = result_dir(
|
||||
out_dir, name, arch, mode, pipeline, args.ablation_variant
|
||||
) / "pimcomp/comparison_report.json"
|
||||
if not report_path.exists() or not comparison_passed(
|
||||
json.loads(report_path.read_text(encoding="utf-8")),
|
||||
args.only,
|
||||
):
|
||||
if label not in failed:
|
||||
failed.append(label)
|
||||
print("\n" + Style.BRIGHT + Fore.CYAN + "Summary" + Style.RESET_ALL)
|
||||
print(Style.BRIGHT + Fore.CYAN + "Summary" + Style.RESET_ALL)
|
||||
total_jobs = sum(len(args.models) * len(comparisons) for comparisons in comparisons_by_arch.values())
|
||||
print(Style.BRIGHT + f"Passed: {total_jobs - len(failed)}" + Style.RESET_ALL)
|
||||
print(Style.BRIGHT + f"Failed: {len(failed)}" + Style.RESET_ALL)
|
||||
@@ -1,6 +1,49 @@
|
||||
# PIMCOMP batch correctness reproduction
|
||||
# Pimcomp correctness experiments
|
||||
|
||||
PIMCOMP's batch scheduler currently emits an incomplete standalone program for
|
||||
## Synchronization ordering
|
||||
|
||||
The synchronization experiment checks whether repeated PIMCOMP and Raptor
|
||||
programs preserve cross-core global-memory generations under legal execution
|
||||
schedules. It builds a small two-convolution model for Arch-A, Arch-B, and
|
||||
Arch-C, finds cross-core `ST`/`LD` dependencies, and compares greedy,
|
||||
bounded-stall, randomized, and adversarial runs using the Rust simulator's
|
||||
provenance trace. Byte-identical batch inputs isolate intermediate-memory
|
||||
ordering from the separate host-input lifetime issue.
|
||||
|
||||
The experiment records static dependency evidence, dynamic provenance,
|
||||
functional output comparisons, architecture contract classifications, and
|
||||
per-artifact PIMCOMP/Raptor conclusions. Diagnostic scheduling changes only
|
||||
the functional simulator's execution order; it does not modify PIMCOMP or the
|
||||
`pimsim-nn` performance oracle.
|
||||
|
||||
Prerequisites are the repository virtual environment, the release Raptor
|
||||
compiler, built PIMCOMP frontend/backend, and the existing `pimsim-nn` build.
|
||||
Run from the repository root with a new or empty output directory:
|
||||
|
||||
```bash
|
||||
.venv/bin/python \
|
||||
validation/tools/pim/pimcomp/correctness/synchronization/run_experiment.py \
|
||||
--out-dir /tmp/pimcomp-adversarial-sync \
|
||||
--batch-size 4 --seed 0 --self-check
|
||||
```
|
||||
|
||||
The directory contains:
|
||||
|
||||
- [`run_experiment.py`](synchronization/run_experiment.py): orchestration,
|
||||
classification, reporting, and self-checks.
|
||||
- [`global_memory.py`](synchronization/global_memory.py): artifact compilation,
|
||||
dependency analysis, simulator execution, and provenance helpers.
|
||||
- [`architecture_contract.py`](synchronization/architecture_contract.py) and
|
||||
[`architecture_evidence.json`](synchronization/architecture_evidence.json):
|
||||
conservative architecture-contract evidence and labels.
|
||||
|
||||
The main outputs are `adversarial_memory_sync_report.json` and
|
||||
`adversarial_memory_sync_report.md`, with per-architecture evidence below the
|
||||
same output root.
|
||||
|
||||
## Pimcomp batch prefill reproduction
|
||||
|
||||
Pimcomp's batch scheduler currently emits an incomplete standalone program for
|
||||
models containing post operations. The generated `VerificationInfo.json` uses a
|
||||
negative `source_address` to identify the preceding node, but the batch
|
||||
verifier resolves that address by copying the provider tensor directly from
|
||||
@@ -17,33 +60,69 @@ the missing computation visible in Rust functional validation.
|
||||
For the checked-in GoogLeNet throughput/pipeline2 artifact, 39 provider tensors
|
||||
are referenced by batch loads. Nineteen have generated stores; twenty are
|
||||
never written. Preloading the provider tensors with the same ONNX Runtime
|
||||
intermediates used by PIMCOMP's verifier makes the exported program pass. This
|
||||
reproduces the verifier's input contract; it does not repair PIMCOMP's batch
|
||||
intermediates used by Pimcomp's verifier makes the exported program pass. This
|
||||
reproduces the verifier's input contract; it does not repair Pimcomp's batch
|
||||
schedule.
|
||||
|
||||
Run the default reproduction from the repository root:
|
||||
|
||||
```bash
|
||||
.venv/bin/python validation/tools/pim/pimcomp/correctness/run_prefill_experiment.py
|
||||
.venv/bin/python validation/tools/pim/pimcomp/correctness/prefill/run_experiment.py
|
||||
```
|
||||
|
||||
The launcher accepts an alternate comparison directory, model, and work
|
||||
work directory, and shared reference-artifact directory:
|
||||
The launcher accepts an alternate comparison directory, model, work directory,
|
||||
and shared reference-artifact directory:
|
||||
|
||||
```bash
|
||||
.venv/bin/python validation/tools/pim/pimcomp/correctness/run_prefill_experiment.py \
|
||||
validation/networks/pimcomp_models/googlenet/arch-a/throughput/pipeline2 \
|
||||
.venv/bin/python validation/tools/pim/pimcomp/correctness/prefill/run_experiment.py \
|
||||
validation/networks/pimcomp_models/googlenet/artifacts/arch-a/throughput/pipeline2 \
|
||||
validation/networks/pimcomp_models/googlenet/googlenet-12-pimsim-nn.onnx \
|
||||
/tmp/pimcomp-prefill-googlenet \
|
||||
validation/networks/pimcomp_models/googlenet/common
|
||||
validation/networks/pimcomp_models/googlenet/artifacts/arch-a/throughput/pipeline2/correctness/prefill \
|
||||
validation/networks/pimcomp_models/googlenet/artifacts/common
|
||||
```
|
||||
|
||||
Without the optional work-directory argument, the experiment uses the same
|
||||
`correctness/prefill/` directory below the comparison artifacts.
|
||||
|
||||
It runs the exported artifact once with its original memory image and once
|
||||
with [`prefill_batch_memory.py`](prefill_batch_memory.py), then compares both
|
||||
with [`batch_memory.py`](prefill/batch_memory.py), then compares both
|
||||
outputs with the recorded native reference. The expected GoogLeNet result is a
|
||||
baseline maximum difference near `6.70705` and a prefilled maximum difference
|
||||
near `4.05e-6`.
|
||||
|
||||
The issue is in PIMCOMP batch scheduling/validation semantics, not in the Rust
|
||||
The issue is in Pimcomp batch scheduling/validation semantics, not in the Rust
|
||||
simulator's vector-length interpretation. Vector lengths remain element counts
|
||||
as specified by the reference ISA.
|
||||
|
||||
## ResNet BatchNorm correctness gap
|
||||
|
||||
Pimcomp has a separate correctness limitation in its ResNet element pipeline.
|
||||
`BatchNormalization` is an ONNX operation. The frontend's
|
||||
[`fuse_operators()` pass](../../../../../third_party/PIMCOMP-NN/frontend/frontend.py#L535)
|
||||
marks it as fused and removes the node from the
|
||||
scheduled graph, but the released path does not fold the BatchNorm affine
|
||||
transform into the convolution weights and bias. The corresponding verifier
|
||||
workaround in [`verification.py`](../../../../../third_party/PIMCOMP-NN/verification/verification.py#L99)
|
||||
replaces BatchNorm parameters with identity values (scale and variance equal
|
||||
to one, bias and mean equal to zero) before running ONNX Runtime.
|
||||
|
||||
Therefore Pimcomp's native verifier and exported element program agree with
|
||||
each other, but they do not implement the original ResNet ONNX model. On the
|
||||
current ResNet-18 Arch-A latency artifact, using the same input:
|
||||
|
||||
| Comparison | Maximum absolute difference |
|
||||
|---|---:|
|
||||
| Pimcomp native verifier vs Rust export | `6.7e-8` |
|
||||
| Rust export vs original ONNX reference | `4.8294563` |
|
||||
|
||||
The Pimcomp output ranges from approximately `-0.187` to `0.220`, while the
|
||||
original ONNX output ranges from `-3.572` to `4.834`; 462 of 1000 final
|
||||
elements differ by more than one. This is not a Rust simulator or Python
|
||||
exporter regression. It is a Pimcomp model-semantics mismatch caused by
|
||||
dropping BatchNorm numerics. The same issue affects the ResNet-34 latency
|
||||
artifact. VGG and GoogLeNet do not show this particular mismatch because they
|
||||
do not contain the same ResNet BatchNorm path.
|
||||
|
||||
The latency comparison intentionally uses the original ONNX reference, so
|
||||
these Pimcomp rows must remain `FAIL` until Pimcomp folds BatchNorm correctly
|
||||
or the comparison explicitly uses a BatchNorm-neutralized reference.
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Populate the host-side intermediate buffers expected by PIMCOMP batch mode."""
|
||||
"""Populate the host-side intermediate buffers expected by Pimcomp batch mode."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -23,7 +23,7 @@ def flatten_reference(value: np.ndarray) -> np.ndarray:
|
||||
elif value.ndim == 2:
|
||||
value = value.transpose()
|
||||
else:
|
||||
raise ValueError(f"PIMCOMP batch verification only flattens 2D/4D tensors, got {value.shape}")
|
||||
raise ValueError(f"Pimcomp batch verification only flattens 2D/4D tensors, got {value.shape}")
|
||||
return value.astype(np.float32, copy=False).reshape(-1)
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ def prefill_batch_memory(
|
||||
session = ort.InferenceSession(runtime_model.SerializeToString(), providers=["CPUExecutionProvider"])
|
||||
session_inputs = session.get_inputs()
|
||||
if len(session_inputs) != 1:
|
||||
raise ValueError("PIMCOMP export currently requires exactly one runtime input tensor")
|
||||
raise ValueError("Pimcomp export currently requires exactly one runtime input tensor")
|
||||
input_meta = session_inputs[0]
|
||||
input_tensor = np.loadtxt(input_path, delimiter=",", dtype=np.float32).reshape(input_meta.shape)
|
||||
provider_names = [node_list[index]["name"] for index in providers]
|
||||
+23
-10
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Reproduce the PIMCOMP batch prefill correctness experiment."""
|
||||
"""Reproduce the Pimcomp batch prefill correctness experiment."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -7,19 +7,18 @@ import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
from prefill_batch_memory import prefill_batch_memory
|
||||
from batch_memory import prefill_batch_memory
|
||||
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
REPO_ROOT = SCRIPT_DIR.parents[4]
|
||||
DEFAULT_COMPARISON_DIR = REPO_ROOT / "validation/networks/pimcomp_models/googlenet/arch-a/throughput/pipeline2"
|
||||
REPO_ROOT = SCRIPT_DIR.parents[5]
|
||||
DEFAULT_COMPARISON_DIR = REPO_ROOT / "validation/networks/pimcomp_models/googlenet/artifacts/arch-a/throughput/pipeline2"
|
||||
DEFAULT_MODEL = REPO_ROOT / "validation/networks/pimcomp_models/googlenet/googlenet-12-pimsim-nn.onnx"
|
||||
DEFAULT_COMMON_DIR = REPO_ROOT / "validation/networks/pimcomp_models/googlenet/common"
|
||||
DEFAULT_COMMON_DIR = REPO_ROOT / "validation/networks/pimcomp_models/googlenet/artifacts/common"
|
||||
SIMULATOR_MANIFEST = REPO_ROOT / "backend-simulators/pim/pim-simulator/Cargo.toml"
|
||||
|
||||
|
||||
@@ -28,6 +27,7 @@ def run_simulator(
|
||||
memory: Path,
|
||||
output: Path,
|
||||
dump: str,
|
||||
input_dir: Path,
|
||||
) -> None:
|
||||
subprocess.run(
|
||||
[
|
||||
@@ -44,6 +44,8 @@ def run_simulator(
|
||||
str(comparison_dir / "pimcomp/exported"),
|
||||
"--memory",
|
||||
str(memory),
|
||||
"--input-dir",
|
||||
str(input_dir),
|
||||
"-o",
|
||||
str(output),
|
||||
"-d",
|
||||
@@ -86,14 +88,18 @@ def main() -> int:
|
||||
model = args.model.resolve()
|
||||
common_dir = args.common_dir.resolve()
|
||||
if args.work_dir is None:
|
||||
work_dir = Path(tempfile.mkdtemp(prefix="pimcomp-prefill."))
|
||||
work_dir = comparison_dir / "correctness/prefill"
|
||||
work_dir.mkdir(parents=True, exist_ok=True)
|
||||
else:
|
||||
work_dir = args.work_dir.resolve()
|
||||
work_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
input_path = common_dir / "inputs/in0.csv"
|
||||
if not input_path.is_file():
|
||||
input_path = comparison_dir / "inputs/in0.csv"
|
||||
simulator_input_dir = work_dir / "inputs"
|
||||
simulator_input_dir.mkdir(parents=True, exist_ok=True)
|
||||
np.loadtxt(input_path, delimiter=",", dtype=np.float32).tofile(
|
||||
simulator_input_dir / "input_0.bin"
|
||||
)
|
||||
|
||||
prefilled_memory = work_dir / "prefilled_memory.bin"
|
||||
metadata_path = work_dir / "metadata.json"
|
||||
@@ -117,8 +123,15 @@ def main() -> int:
|
||||
comparison_dir / "pimcomp/exported/memory.bin",
|
||||
baseline_output,
|
||||
dump,
|
||||
simulator_input_dir,
|
||||
)
|
||||
run_simulator(
|
||||
comparison_dir,
|
||||
prefilled_memory,
|
||||
prefilled_output,
|
||||
dump,
|
||||
simulator_input_dir,
|
||||
)
|
||||
run_simulator(comparison_dir, prefilled_memory, prefilled_output, dump)
|
||||
compare_outputs(baseline_output, prefilled_output, reference, work_dir)
|
||||
return 0
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Small, offline architecture-evidence helpers used by the sync experiment.
|
||||
|
||||
The adversarial experiment owns the dynamic investigation. This module keeps
|
||||
the stable repository/configuration facts it needs in one place and exposes a
|
||||
deliberately small API so the experiment can also be imported as a library.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
REPO = Path(__file__).resolve().parents[6]
|
||||
VALIDATION = REPO / "validation"
|
||||
PIMCOMP_ROOT = REPO / "third_party/PIMCOMP-NN"
|
||||
PIMSIM_ROOT = REPO / "backend-simulators/pim/pimsim-nn"
|
||||
RUST_ROOT = REPO / "backend-simulators/pim/pim-simulator"
|
||||
COMPARE_DIR = REPO / "validation/tools/pim/pimcomp/compare"
|
||||
EVIDENCE_PATH = Path(__file__).with_name("architecture_evidence.json")
|
||||
|
||||
|
||||
def _default_evidence() -> dict[str, Any]:
|
||||
return {
|
||||
"schema": 1,
|
||||
"status": "documentary_evidence_plus_repository_checks",
|
||||
"architectures": {
|
||||
"arch-a": {
|
||||
"pimcomp_identity": "ISAAC-like static/deterministic timing model",
|
||||
"primary_classification": "STATIC_TIMING_CONTRACT_MAPPING_UNPROVEN",
|
||||
"config": "validation/pimsim_configs/pimcomp/arch-a/throughput_config_1000ms.json",
|
||||
"hardware_reference": "ISAAC (HPCA 2016), documentary mapping requires review of the cited paper/configuration.",
|
||||
},
|
||||
"arch-b": {
|
||||
"pimcomp_identity": "PUMA-like architecture",
|
||||
"primary_classification": "HARDWARE_SYNC_EXISTS_BUT_NOT_MODELED_BY_PIMSIM_NN",
|
||||
"config": "validation/pimsim_configs/pimcomp/arch-b/throughput_config_1000ms.json",
|
||||
"hardware_reference": "PUMA, documentary valid/count synchronization is not encoded in ordinary PIMCOMP LD/ST.",
|
||||
},
|
||||
"arch-c": {
|
||||
"pimcomp_identity": "ISSCC 2023 ReRAM architecture row",
|
||||
"primary_classification": "MAPPING_NOT_ESTABLISHED",
|
||||
"config": "validation/pimsim_configs/pimcomp/arch-c/throughput_config_1000ms.json",
|
||||
"hardware_reference": "ISSCC 2023 ReRAM reference; cross-system mapping is unresolved.",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def load_evidence() -> dict[str, Any]:
|
||||
if EVIDENCE_PATH.is_file():
|
||||
return json.loads(EVIDENCE_PATH.read_text(encoding="utf-8"))
|
||||
return _default_evidence()
|
||||
|
||||
|
||||
def source_contract() -> dict[str, Any]:
|
||||
"""Return the contract claims used for report labeling.
|
||||
|
||||
These labels are intentionally conservative: they are not a substitute
|
||||
for a paper citation and never turn an unordered relation into a safe one.
|
||||
"""
|
||||
|
||||
return {
|
||||
"ld_st": "ordinary timed global-memory accesses in the Rust model",
|
||||
"send_recv": "explicit point-to-point synchronization modeled by the simulator",
|
||||
"wait_sync": "instruction-level synchronization when emitted",
|
||||
"valid_count": "not encoded by PIMCOMP exported LD/ST instructions",
|
||||
"static_timing": "not proven as a PIMCOMP-to-ISAAC contract",
|
||||
}
|
||||
|
||||
|
||||
def classify_contract(
|
||||
architecture: str, _contract: dict[str, Any], manifest: dict[str, Any]
|
||||
) -> str:
|
||||
return manifest["architectures"][architecture]["primary_classification"]
|
||||
|
||||
|
||||
def architecture_source_evidence(architecture: str, manifest: dict[str, Any]) -> dict[str, Any]:
|
||||
return dict(manifest["architectures"][architecture])
|
||||
|
||||
|
||||
def git_identity(path: Path) -> dict[str, Any]:
|
||||
result: dict[str, Any] = {"path": str(path), "commit": None, "worktree_status": []}
|
||||
if not path.exists():
|
||||
result["error"] = "missing"
|
||||
return result
|
||||
try:
|
||||
result["commit"] = subprocess.run(
|
||||
["git", "-C", str(path), "rev-parse", "HEAD"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
).stdout.strip()
|
||||
status = subprocess.run(
|
||||
["git", "-C", str(path), "status", "--short"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
).stdout.splitlines()
|
||||
result["worktree_status"] = status
|
||||
except (OSError, subprocess.CalledProcessError) as exc:
|
||||
result["error"] = f"{type(exc).__name__}: {exc}"
|
||||
return result
|
||||
|
||||
|
||||
def tree_hash(root: Path, pattern: str) -> dict[str, str]:
|
||||
values: dict[str, str] = {}
|
||||
for path in sorted(root.glob(pattern)):
|
||||
if not path.is_file():
|
||||
continue
|
||||
digest = hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
values[str(path.relative_to(root))] = digest
|
||||
return values
|
||||
|
||||
|
||||
def instruction_graph(artifact: Path) -> tuple[dict[tuple[int, int], list[tuple[int, int]]], dict[str, Any]]:
|
||||
"""Build same-core program-order edges from the exported JSON streams.
|
||||
|
||||
Cross-core synchronization is added only when an artifact explicitly
|
||||
carries matching SEND/RECV metadata. Ordinary global LD/ST creates no
|
||||
edge by construction; that is the relation this experiment is testing.
|
||||
"""
|
||||
|
||||
graph: dict[tuple[int, int], list[tuple[int, int]]] = {}
|
||||
streams: dict[int, list[dict[str, Any]]] = {}
|
||||
for path in sorted(artifact.glob("core_*.json"), key=lambda item: int(item.stem.split("_")[1])):
|
||||
try:
|
||||
instructions = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
continue
|
||||
core = int(path.stem.split("_")[1]) + 1
|
||||
streams[core] = instructions if isinstance(instructions, list) else []
|
||||
for index in range(len(streams[core]) - 1):
|
||||
graph.setdefault((core, index), []).append((core, index + 1))
|
||||
|
||||
sends: dict[tuple[int, int], tuple[int, int]] = {}
|
||||
recvs: dict[tuple[int, int], tuple[int, int]] = {}
|
||||
for core, instructions in streams.items():
|
||||
for index, instruction in enumerate(instructions):
|
||||
op = str(instruction.get("op", instruction.get("operation", ""))).lower()
|
||||
peer = instruction.get("core")
|
||||
if peer is None:
|
||||
continue
|
||||
key = (core, int(peer) + 1)
|
||||
if op == "send":
|
||||
sends.setdefault(key, (core, index))
|
||||
elif op == "recv":
|
||||
recvs.setdefault(key, (core, index))
|
||||
for key, send in sends.items():
|
||||
recv = recvs.get((key[1], key[0]))
|
||||
if recv is not None:
|
||||
graph.setdefault(send, []).append(recv)
|
||||
return graph, {"cores": sorted(streams), "explicit_send_recv_edges": len(sends)}
|
||||
|
||||
|
||||
def make_identical_inputs(model: Path, batch_size: int, out: Path) -> list[Path]:
|
||||
import numpy as np
|
||||
|
||||
import sys
|
||||
|
||||
if str(COMPARE_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(COMPARE_DIR))
|
||||
import compare_raptor_pimcomp_model as compare # noqa: PLC0415
|
||||
|
||||
inputs, _ = compare.onnx_io(model)
|
||||
arrays = []
|
||||
for _index, _name, element_type, shape in inputs:
|
||||
dtype = compare._ONNX_TO_NP[element_type]
|
||||
arrays.append(np.full(shape, 1.0, dtype=dtype))
|
||||
flattened = np.concatenate(
|
||||
[compare.flatten_pimcomp_input(array) for array in arrays]
|
||||
) if arrays else np.empty(0, dtype=np.float32)
|
||||
samples = [[flattened.copy()] for _ in range(batch_size)]
|
||||
return [
|
||||
Path(path)
|
||||
for path in compare.write_input_batch_binaries(
|
||||
samples, out / "inputs/pimcomp_isolated"
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(json.dumps(load_evidence(), indent=2, sort_keys=True))
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"status": "documentary_evidence_plus_repository_checks",
|
||||
"architectures": {
|
||||
"arch-a": {
|
||||
"pimcomp_identity": "ISAAC-like static/deterministic timing model",
|
||||
"primary_classification": "STATIC_TIMING_CONTRACT_MAPPING_UNPROVEN",
|
||||
"config": "validation/pimsim_configs/pimcomp/arch-a/throughput_config_1000ms.json",
|
||||
"hardware_reference": "ISAAC (HPCA 2016), documentary mapping requires review of the cited paper/configuration."
|
||||
},
|
||||
"arch-b": {
|
||||
"pimcomp_identity": "PUMA-like architecture",
|
||||
"primary_classification": "HARDWARE_SYNC_EXISTS_BUT_NOT_MODELED_BY_PIMSIM_NN",
|
||||
"config": "validation/pimsim_configs/pimcomp/arch-b/throughput_config_1000ms.json",
|
||||
"hardware_reference": "PUMA, documentary valid/count synchronization is not encoded in ordinary PIMCOMP LD/ST."
|
||||
},
|
||||
"arch-c": {
|
||||
"pimcomp_identity": "ISSCC 2023 ReRAM architecture row",
|
||||
"primary_classification": "MAPPING_NOT_ESTABLISHED",
|
||||
"config": "validation/pimsim_configs/pimcomp/arch-c/throughput_config_1000ms.json",
|
||||
"hardware_reference": "ISSCC 2023 ReRAM reference; cross-system mapping is unresolved."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,479 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Reusable build, execution, and provenance helpers for PIMCOMP audits."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from bisect import bisect_left
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
REPO = Path(__file__).resolve().parents[6]
|
||||
VALIDATION = REPO / "validation"
|
||||
CONFIG_ROOT = VALIDATION / "pimsim_configs/pimcomp"
|
||||
PIMCOMP_ROOT = REPO / "third_party/PIMCOMP-NN"
|
||||
PIMSIM_NN_ROOT = REPO / "backend-simulators/pim/pimsim-nn"
|
||||
RUST_ROOT = REPO / "backend-simulators/pim/pim-simulator"
|
||||
RUST_BINARY = RUST_ROOT / "target/release/pim-simulator"
|
||||
COMPARE_SCRIPT = REPO / "validation/tools/pim/pimcomp/compare/compare_raptor_pimcomp_model.py"
|
||||
PYTHON = REPO / ".venv/bin/python"
|
||||
|
||||
sys.path.insert(0, str(COMPARE_SCRIPT.parent))
|
||||
import compare_raptor_pimcomp_model as compare # noqa: E402
|
||||
|
||||
|
||||
class ExperimentError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def architecture_configs(architecture: str) -> tuple[Path, Path]:
|
||||
root = CONFIG_ROOT / architecture
|
||||
return root / "throughput_config_1000ms.json", root / "latency_config.json"
|
||||
|
||||
|
||||
def check_prerequisites(throughput: Path, latency: Path) -> None:
|
||||
required = {
|
||||
"PIMCOMP backend": PIMCOMP_ROOT / "build/PIMCOMP-NN",
|
||||
"PIMCOMP frontend": PIMCOMP_ROOT / "frontend/frontend.py",
|
||||
"Raptor compiler": REPO / "build_release/Release/bin/onnx-mlir",
|
||||
"Rust simulator source": RUST_ROOT,
|
||||
"pimsim-nn build": PIMSIM_NN_ROOT / "build",
|
||||
"throughput config": throughput,
|
||||
"latency config": latency,
|
||||
}
|
||||
missing = [f"{label}: {path}" for label, path in required.items() if not path.exists()]
|
||||
if missing:
|
||||
raise ExperimentError("missing prerequisites:\n" + "\n".join(missing))
|
||||
|
||||
|
||||
def _run(cmd: list[str], cwd: Path, log: Path, timeout: float = 0.0) -> subprocess.CompletedProcess[str]:
|
||||
log.parent.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[str(value) for value in cmd],
|
||||
cwd=cwd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
timeout=None if timeout <= 0 else timeout,
|
||||
)
|
||||
except subprocess.TimeoutExpired as exc:
|
||||
log.write_text((exc.stdout or "") + "\nTIMEOUT\n", encoding="utf-8")
|
||||
raise ExperimentError(f"command timed out: {' '.join(map(str, cmd))}") from exc
|
||||
log.write_text(result.stdout, encoding="utf-8")
|
||||
if result.returncode:
|
||||
raise ExperimentError(
|
||||
f"command failed ({result.returncode}): {' '.join(map(str, cmd))}\n"
|
||||
f"see {log}\n{result.stdout[-3000:]}"
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def build_simulator(args: Any, out: Path) -> None:
|
||||
_run(
|
||||
[
|
||||
"cargo", "build", "--release", "--no-default-features",
|
||||
"--package", "pim-simulator", "--bin", "pim-simulator",
|
||||
],
|
||||
RUST_ROOT,
|
||||
out / "cargo_build.log",
|
||||
float(getattr(args, "timeout", 0.0)),
|
||||
)
|
||||
if not RUST_BINARY.is_file():
|
||||
raise ExperimentError(f"Rust simulator binary was not produced: {RUST_BINARY}")
|
||||
|
||||
|
||||
def make_model(path: Path) -> None:
|
||||
import onnx
|
||||
from onnx import TensorProto, helper, numpy_helper
|
||||
|
||||
shape = [1, 64, 8, 8]
|
||||
weights = []
|
||||
for name in ("w0", "w1"):
|
||||
weight = np.zeros((64, 64, 3, 3), dtype=np.float32)
|
||||
for channel in range(64):
|
||||
weight[channel, channel, 1, 1] = 1.0
|
||||
weights.append(numpy_helper.from_array(weight, name=name))
|
||||
input_value = helper.make_tensor_value_info("input", TensorProto.FLOAT, shape)
|
||||
output_value = helper.make_tensor_value_info("output", TensorProto.FLOAT, shape)
|
||||
nodes = [
|
||||
helper.make_node(
|
||||
"Conv", ["input", "w0"], ["hidden"], name="conv0",
|
||||
kernel_shape=[3, 3], strides=[1, 1], pads=[1, 1, 1, 1],
|
||||
dilations=[1, 1], group=1,
|
||||
),
|
||||
helper.make_node(
|
||||
"Conv", ["hidden", "w1"], ["output"], name="conv1",
|
||||
kernel_shape=[3, 3], strides=[1, 1], pads=[1, 1, 1, 1],
|
||||
dilations=[1, 1], group=1,
|
||||
),
|
||||
]
|
||||
graph = helper.make_graph(nodes, "pimcomp_sync_two_conv", [input_value], [output_value], weights)
|
||||
model = helper.make_model(graph, opset_imports=[helper.make_operatorsetid("", 13)])
|
||||
model.ir_version = min(model.ir_version, 8)
|
||||
onnx.checker.check_model(model)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
onnx.save(model, path)
|
||||
|
||||
|
||||
def make_inputs(
|
||||
model: Path, batch_size: int, seed: int, out: Path
|
||||
) -> tuple[list[list[np.ndarray]], list[Path], list[Path], list[np.ndarray]]:
|
||||
del seed # deterministic values are intentional; the seed remains in the report.
|
||||
inputs_desc, _ = compare.onnx_io(model)
|
||||
if len(inputs_desc) != 1:
|
||||
raise ExperimentError("the synchronization model must have exactly one input")
|
||||
_index, _name, element_type, shape = inputs_desc[0]
|
||||
dtype = compare._ONNX_TO_NP[element_type]
|
||||
arrays: list[np.ndarray] = [
|
||||
np.full(shape, float(10 ** index), dtype=dtype) for index in range(max(8, batch_size))
|
||||
]
|
||||
input_batch = [[array] for array in arrays]
|
||||
raptor_paths = [
|
||||
Path(path)
|
||||
for path in compare.write_input_batch_binaries(input_batch, out / "inputs/raptor")
|
||||
]
|
||||
pimcomp_batch = [[compare.flatten_pimcomp_input(array)] for array in arrays]
|
||||
pimcomp_paths = [
|
||||
Path(path)
|
||||
for path in compare.write_input_batch_binaries(pimcomp_batch, out / "inputs/pimcomp")
|
||||
]
|
||||
return input_batch, raptor_paths, pimcomp_paths, arrays
|
||||
|
||||
|
||||
def make_references(
|
||||
model: Path, input_batch: list[list[np.ndarray]], architecture_out: Path, _args: Any
|
||||
) -> list[Path]:
|
||||
import onnxruntime as ort
|
||||
|
||||
input_desc, output_desc = compare.onnx_io(model)
|
||||
session = ort.InferenceSession(str(model), providers=["CPUExecutionProvider"])
|
||||
references: list[Path] = []
|
||||
for index, sample in enumerate(input_batch):
|
||||
values = {input_desc[item][1]: sample[item] for item in range(len(input_desc))}
|
||||
outputs = session.run(None, values)
|
||||
directory = architecture_out / "reference" / f"iteration_{index:06d}"
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
for output, descriptor in zip(outputs, output_desc):
|
||||
output_index, name, _dtype, _shape = descriptor
|
||||
filename = f"output{output_index}_{compare.sanitize_output_name(name)}.csv"
|
||||
np.savetxt(directory / filename, np.asarray(output).reshape(-1), delimiter=",")
|
||||
references.append(directory)
|
||||
return references
|
||||
|
||||
|
||||
def compile_artifact(args: Any, model: Path, architecture_out: Path, throughput_config: Path) -> dict[str, Any]:
|
||||
comparison = architecture_out / "comparison"
|
||||
command = [
|
||||
str(PYTHON), str(COMPARE_SCRIPT),
|
||||
"--model", str(model),
|
||||
"--out-dir", str(comparison),
|
||||
"--common-dir", str(architecture_out / "common"),
|
||||
"--pimcomp-config", str(throughput_config),
|
||||
"--pimsim-mode", "throughput",
|
||||
"--pimsim-time-ms", "1000",
|
||||
"--batch-size", str(args.batch_size),
|
||||
"--pimcomp-pipeline", "batch",
|
||||
"--pimcomp-replication", "GA",
|
||||
"--raptor-extra-arg=--pipeline=4",
|
||||
"--seed", str(args.seed),
|
||||
"--timeout-seconds", str(args.timeout),
|
||||
]
|
||||
if args.no_fast:
|
||||
command.append("--no-fast")
|
||||
log = architecture_out / "comparison_compile.log"
|
||||
try:
|
||||
_run(command, REPO, log, args.timeout)
|
||||
except ExperimentError as exc:
|
||||
report = comparison / "pimcomp/comparison_report.json"
|
||||
if not report.is_file():
|
||||
raise
|
||||
report_data = json.loads(report.read_text(encoding="utf-8"))
|
||||
raptor_error = "; ".join(
|
||||
str(item.get("error", ""))
|
||||
for item in report_data.get("failures", [])
|
||||
if "RAPTOR" in str(item.get("stage", "")).upper()
|
||||
) or str(exc)
|
||||
else:
|
||||
report = comparison / "pimcomp/comparison_report.json"
|
||||
report_data = json.loads(report.read_text(encoding="utf-8"))
|
||||
raptor_error = "; ".join(
|
||||
str(item.get("error", ""))
|
||||
for item in report_data.get("failures", [])
|
||||
if "RAPTOR" in str(item.get("stage", "")).upper()
|
||||
) or None
|
||||
|
||||
paths = report_data.get("paths", {})
|
||||
pimcomp = Path(paths["pimcomp_exported_pim"]) if paths.get("pimcomp_exported_pim") else comparison / "pimcomp/exported"
|
||||
pimsim = Path(paths["pimcomp_pimsim_nn"]) if paths.get("pimcomp_pimsim_nn") else comparison / "pimcomp/pimsim_nn"
|
||||
raptor = Path(paths["raptor_pim"]) if paths.get("raptor_pim") else comparison / "raptor/pim.missing"
|
||||
if not pimcomp.is_dir():
|
||||
raise ExperimentError(f"PIMCOMP Rust artifact missing; see {report}")
|
||||
return {
|
||||
"artifact": pimcomp,
|
||||
"pimsim_artifact": pimsim if pimsim.is_dir() else None,
|
||||
"raptor_artifact": raptor if raptor.is_dir() else Path(),
|
||||
"raptor_error": raptor_error,
|
||||
"comparison_report": report,
|
||||
}
|
||||
|
||||
|
||||
def _instruction_op(instruction: dict[str, Any]) -> str:
|
||||
return str(instruction.get("op", instruction.get("operation", ""))).lower()
|
||||
|
||||
|
||||
def _address(instruction: dict[str, Any], registers: dict[int, int], register: str) -> int | None:
|
||||
try:
|
||||
base = int(registers[int(instruction[register])])
|
||||
except (KeyError, TypeError, ValueError):
|
||||
return None
|
||||
offset = instruction.get("offset") or {}
|
||||
select = int(offset.get("offset_select", 0))
|
||||
value = int(offset.get("offset_value", 0))
|
||||
# LD's global operand is r1 (selector bit 2); ST's global operand is rd
|
||||
# (selector bit 1). The local simulator uses the same asymmetric ISA.
|
||||
selector_bit = 1 if register == "rd" else 2
|
||||
return base + value if select & selector_bit else base
|
||||
|
||||
|
||||
def _static_instruction(
|
||||
core_file_index: int,
|
||||
artifact_format: str,
|
||||
instruction_index: int,
|
||||
instruction: dict[str, Any],
|
||||
address: int,
|
||||
size: int,
|
||||
artifact: Path,
|
||||
) -> dict[str, Any]:
|
||||
core = core_file_index + 1
|
||||
core_file = f"core_{core_file_index}.json"
|
||||
binary_file = f"core_{core_file_index}.pim"
|
||||
return {
|
||||
"core": core,
|
||||
# JSON PIMCOMP streams enter the Rust executor after an initial
|
||||
# synthetic slot; Raptor's emitted binary/JSON streams do not.
|
||||
"pc": instruction_index if artifact_format == "binary+json" else instruction_index - 1,
|
||||
"artifact_pc": instruction_index,
|
||||
"address": address,
|
||||
"size": size,
|
||||
"instruction_file": core_file,
|
||||
"execution_file": binary_file if (artifact / binary_file).is_file() else core_file,
|
||||
"artifact_format": artifact_format,
|
||||
}
|
||||
|
||||
|
||||
def analyze_artifact(artifact: Path) -> dict[str, Any]:
|
||||
core_paths = sorted(artifact.glob("core_*.json"), key=lambda path: int(path.stem.split("_")[1]))
|
||||
if not core_paths:
|
||||
raise ExperimentError(f"artifact has no core_*.json files: {artifact}")
|
||||
artifact_format = "binary+json" if any(artifact.glob("core_*.pim")) else "json"
|
||||
stores: list[dict[str, Any]] = []
|
||||
loads: list[dict[str, Any]] = []
|
||||
counts: dict[str, int] = defaultdict(int)
|
||||
instruction_files: dict[str, str] = {}
|
||||
participating = 0
|
||||
for path in core_paths:
|
||||
core_file_index = int(path.stem.split("_")[1])
|
||||
instructions = json.loads(path.read_text(encoding="utf-8"))
|
||||
if instructions:
|
||||
participating += 1
|
||||
instruction_files[f"core_{core_file_index}"] = str(path)
|
||||
registers: dict[int, int] = {}
|
||||
for index, instruction in enumerate(instructions):
|
||||
op = _instruction_op(instruction)
|
||||
counts[op] += 1
|
||||
if op in {"sldi", "lldi"} and "rd" in instruction and "imm" in instruction:
|
||||
registers[int(instruction["rd"])] = int(instruction["imm"])
|
||||
continue
|
||||
if op not in {"ld", "st"}:
|
||||
continue
|
||||
address = _address(instruction, registers, "rd" if op == "st" else "rs1")
|
||||
if address is None:
|
||||
continue
|
||||
size = int(instruction.get("size", instruction.get("len", 0)))
|
||||
if size <= 0:
|
||||
continue
|
||||
item = _static_instruction(
|
||||
core_file_index, artifact_format, index, instruction, address, size, artifact
|
||||
)
|
||||
(stores if op == "st" else loads).append(item)
|
||||
|
||||
loads_by_address = sorted(loads, key=lambda item: int(item["address"]))
|
||||
starts = [int(item["address"]) for item in loads_by_address]
|
||||
dependencies: list[dict[str, Any]] = []
|
||||
for store in stores:
|
||||
begin = int(store["address"])
|
||||
end = begin + int(store["size"])
|
||||
first = bisect_left(starts, end)
|
||||
for load in loads_by_address[:first]:
|
||||
if load["core"] == store["core"]:
|
||||
continue
|
||||
load_begin = int(load["address"])
|
||||
load_end = load_begin + int(load["size"])
|
||||
if load_end <= begin:
|
||||
continue
|
||||
dependencies.append({
|
||||
"overlap": {
|
||||
"address_begin": max(begin, load_begin),
|
||||
"address_end": min(end, load_end),
|
||||
},
|
||||
"writer": dict(store),
|
||||
"reader": dict(load),
|
||||
"explicit_sync_ordering_evidence": False,
|
||||
})
|
||||
dependencies.sort(key=lambda item: (
|
||||
item["overlap"]["address_begin"], item["writer"]["core"],
|
||||
item["writer"]["pc"], item["reader"]["core"], item["reader"]["pc"],
|
||||
))
|
||||
return {
|
||||
"artifact_format": artifact_format,
|
||||
"instruction_files": instruction_files,
|
||||
"stores": stores,
|
||||
"loads": loads,
|
||||
"cross_core_dependencies": dependencies,
|
||||
"cross_core_dependency_count": len(dependencies),
|
||||
"participating_core_count": participating,
|
||||
"instruction_counts": dict(sorted(counts.items())),
|
||||
"representative_dependency": dependencies[0] if dependencies else None,
|
||||
}
|
||||
|
||||
|
||||
def trace_events(path: Path) -> list[dict[str, Any]]:
|
||||
if not path.is_file():
|
||||
return []
|
||||
events = []
|
||||
for line in path.read_text(encoding="utf-8").splitlines():
|
||||
if line.strip():
|
||||
events.append(json.loads(line))
|
||||
return events
|
||||
|
||||
|
||||
def _prov(event: dict[str, Any]) -> tuple[int, ...]:
|
||||
return tuple(int(value) for value in event.get("provenance", []))
|
||||
|
||||
|
||||
def _overlap(a: dict[str, Any], b: dict[str, Any]) -> bool:
|
||||
return max(int(a["address"]), int(b["address"])) < min(
|
||||
int(a["address"]) + int(a["size"]), int(b["address"]) + int(b["size"])
|
||||
)
|
||||
|
||||
|
||||
def dynamic_analysis(events: list[dict[str, Any]], static: dict[str, Any], config: Path) -> dict[str, Any]:
|
||||
stores = [event for event in events if event.get("event") == "global_store"]
|
||||
loads = [event for event in events if event.get("event") == "global_load"]
|
||||
input_stores = [event for event in events if event.get("event") == "external_input_store"]
|
||||
event_keys = {
|
||||
(int(event.get("core", -1)), int(event.get("pc", -1)), int(event.get("address", -1)), int(event.get("size", -1)))
|
||||
for event in stores + loads
|
||||
}
|
||||
executed = sum(
|
||||
1 for dependency in static["cross_core_dependencies"]
|
||||
if (
|
||||
int(dependency["writer"]["core"]), int(dependency["writer"]["pc"]),
|
||||
int(dependency["writer"]["address"]), int(dependency["writer"]["size"]),
|
||||
) in event_keys
|
||||
and (
|
||||
int(dependency["reader"]["core"]), int(dependency["reader"]["pc"]),
|
||||
int(dependency["reader"]["address"]), int(dependency["reader"]["size"]),
|
||||
) in event_keys
|
||||
)
|
||||
cross_links = []
|
||||
for load in loads:
|
||||
for writer in load.get("last_writers", []):
|
||||
if int(writer.get("core", -1)) == 0 or not writer.get("provenance"):
|
||||
continue
|
||||
cross_links.append((load, writer))
|
||||
host_races = []
|
||||
for load in loads:
|
||||
expected = (int(load.get("core_iteration", -1)),)
|
||||
if any(_overlap(load, source) and _prov(load) and _prov(load) != expected for source in input_stores):
|
||||
host_races.append(load)
|
||||
reused_versions = {
|
||||
int(version)
|
||||
for store in stores
|
||||
for version in store.get("overwritten_versions", [])
|
||||
}
|
||||
mixed = [event for event in events if event.get("event") == "cross_sample_data_mix"]
|
||||
return {
|
||||
"executed_cross_core_dependencies": executed,
|
||||
"sample_dependent_cross_core_links": len(cross_links),
|
||||
"host_input_lifetime_races": len(host_races),
|
||||
"send_recv_generation_races": 0,
|
||||
"mixed_sample_operations": len(mixed),
|
||||
"global_memory_versions_reused": len(reused_versions),
|
||||
"global_memory_generation_races": 0,
|
||||
"host_input_race_contaminates_test": bool(host_races),
|
||||
"config": str(config),
|
||||
}
|
||||
|
||||
|
||||
def run_rust(
|
||||
artifact: Path,
|
||||
inputs: list[Path],
|
||||
references: list[Path],
|
||||
outputs_desc: list[tuple[int, str, int, list[int]]],
|
||||
out: Path,
|
||||
args: Any,
|
||||
*,
|
||||
schedule_policy: str = "greedy",
|
||||
schedule_seed: int = 0,
|
||||
schedule_target: str | None = None,
|
||||
schedule_deferral_budget: int | None = None,
|
||||
target_stall: str | None = None,
|
||||
channel_last: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
if not inputs:
|
||||
raise ExperimentError("Rust run requires at least one input")
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
output = out / "output.bin"
|
||||
batch_outputs = out / "iterations"
|
||||
shutil.rmtree(batch_outputs, ignore_errors=True)
|
||||
dump = compare.build_dump_ranges(artifact / "config.json", outputs_desc)
|
||||
mode = "latency" if len(inputs) == 1 else "throughput"
|
||||
command = [
|
||||
str(RUST_BINARY), "--folder", str(artifact), "--output", str(output),
|
||||
"--dump", dump, "--mode", mode, "--batch-size", str(len(inputs)),
|
||||
"--input-dir", str(inputs[0].parent), "--batch-output-dir", str(batch_outputs),
|
||||
"--provenance-trace", str(out / "provenance.jsonl"),
|
||||
"--diagnostic-schedule-policy", schedule_policy,
|
||||
"--diagnostic-schedule-seed", str(schedule_seed),
|
||||
]
|
||||
if schedule_target:
|
||||
command += ["--diagnostic-schedule-target", schedule_target]
|
||||
if schedule_deferral_budget is not None:
|
||||
command += ["--diagnostic-schedule-deferral-budget", str(schedule_deferral_budget)]
|
||||
if target_stall:
|
||||
command += ["--diagnostic-target-stall", target_stall]
|
||||
log = out / "simulator.log"
|
||||
try:
|
||||
_run(command, RUST_ROOT, log, float(args.timeout))
|
||||
except ExperimentError as exc:
|
||||
return {
|
||||
"passed": False, "max_diffs": {}, "error": str(exc), "completed": False,
|
||||
"command": [str(value) for value in command],
|
||||
}
|
||||
failed: list[int] = []
|
||||
max_diffs: dict[str, float] = {}
|
||||
for index, reference in enumerate(references[: len(inputs)]):
|
||||
result = compare.compare_simulator_outputs(
|
||||
batch_outputs / f"output_{index:06d}.bin", outputs_desc, reference,
|
||||
threshold=args.threshold, rtol=args.rtol, channel_last=channel_last,
|
||||
)
|
||||
if not result.passed:
|
||||
failed.append(index)
|
||||
for name, value in result.max_diffs.items():
|
||||
max_diffs[name] = max(max_diffs.get(name, 0.0), value)
|
||||
return {
|
||||
"passed": not failed, "max_diffs": max_diffs,
|
||||
"failed_iterations": failed, "completed": True,
|
||||
"command": [str(value) for value in command],
|
||||
"trace": str(out / "provenance.jsonl"),
|
||||
}
|
||||
+1401
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user