This commit is contained in:
+2
-2
@@ -8,10 +8,10 @@ Every developer or coding agent modifying Spatial graph construction, graph
|
|||||||
verification, Blueprint handling, or `MergeComputeNodes` must read this file
|
verification, Blueprint handling, or `MergeComputeNodes` must read this file
|
||||||
after `README.md` and `AGENTS.md`.
|
after `README.md` and `AGENTS.md`.
|
||||||
|
|
||||||
`AGENTS.md` must contain this instruction:
|
`AGENTS.md` must reference this invariant:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
* Always read the full invariants/GRAPH_COMPUTE_BATCH_INVARIANT.md before modifying Spatial graph IR, Blueprint handling, or MergeComputeNodes.
|
* `.agents/invariants/GRAPH_COMPUTE_BATCH_INVARIANT.md`
|
||||||
```
|
```
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# Compiler Performance Optimization Invariant
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This invariant applies to:
|
||||||
|
|
||||||
|
- ONNX-to-Spatial lowering;
|
||||||
|
- graph and trivial merge;
|
||||||
|
- PEFT scheduling;
|
||||||
|
- scheduled materialization;
|
||||||
|
- deferred communication;
|
||||||
|
- Spatial-to-PIM lowering;
|
||||||
|
- bufferization;
|
||||||
|
- memory coalescing;
|
||||||
|
- liveness planning;
|
||||||
|
- PIM code generation.
|
||||||
|
|
||||||
|
## Invariant
|
||||||
|
|
||||||
|
A compiler compile-time or memory optimization must not reduce available
|
||||||
|
hardware parallelism or worsen theoretical execution time. In the absence of a
|
||||||
|
precise runtime model, it is forbidden to serialize independent work, reduce
|
||||||
|
the number of simultaneously schedulable compute instances, replace parallel
|
||||||
|
graph or core batches with sequential loops, introduce recomputation, add
|
||||||
|
runtime copies, increase communication or instruction count, or lengthen the
|
||||||
|
schedule critical path merely to reduce compiler time or memory usage.
|
||||||
|
|
||||||
|
An optimization is acceptable only when its static runtime proxies are equal or
|
||||||
|
better.
|
||||||
|
|
||||||
|
## Forbidden optimization trades
|
||||||
|
|
||||||
|
Do not:
|
||||||
|
|
||||||
|
- use fewer parallel lanes or cores to simplify IR;
|
||||||
|
- scalarize a valid graph or core batch;
|
||||||
|
- replace independent operations with one sequential loop;
|
||||||
|
- recompute values to reduce storage;
|
||||||
|
- add device/device or host/device copies to reduce peak memory;
|
||||||
|
- change hardware parameters to make a benchmark easier;
|
||||||
|
- reduce communication concurrency by imposing a global serial order;
|
||||||
|
- increase emitted instruction count to reduce compiler analysis;
|
||||||
|
- move PIM work to the host.
|
||||||
|
|
||||||
|
## Required proof
|
||||||
|
|
||||||
|
Every performance change must report before and after:
|
||||||
|
|
||||||
|
- compiler wall time;
|
||||||
|
- compiler peak RSS;
|
||||||
|
- IR operation and value counts at the changed stage;
|
||||||
|
- logical compute-instance count;
|
||||||
|
- graph compute batch and scheduled batch lane counts;
|
||||||
|
- PEFT class and core assignment;
|
||||||
|
- maximum scheduled critical-path or step count;
|
||||||
|
- MVM/VMM and vector instruction counts;
|
||||||
|
- send and receive counts and bytes;
|
||||||
|
- total emitted instruction count;
|
||||||
|
- maximum instructions assigned to one core;
|
||||||
|
- number and size of runtime memory copies;
|
||||||
|
- final host, weights, logical-core, physical-core, and maximum-core memory;
|
||||||
|
- numerical validation.
|
||||||
|
|
||||||
|
## Stop rule
|
||||||
|
|
||||||
|
If compile time or memory improves but a runtime proxy worsens, stop and reject
|
||||||
|
the change.
|
||||||
@@ -1,5 +1,11 @@
|
|||||||
* Always read the full README.md before doing anything
|
* Always read the full README.md before doing anything
|
||||||
* Always read the full invariants/GRAPH_COMPUTE_BATCH_INVARIANT.md before modifying Spatial graph IR, Blueprint handling, or MergeComputeNodes.
|
|
||||||
|
# Required project invariants
|
||||||
|
|
||||||
|
Before modifying the relevant subsystem, read:
|
||||||
|
|
||||||
|
* `.agents/invariants/GRAPH_COMPUTE_BATCH_INVARIANT.md`
|
||||||
|
* `.agents/invariants/PERFORMANCE_OPTIMIZATION_INVARIANT.md`
|
||||||
* Build commands:
|
* Build commands:
|
||||||
* `cmake --build ./build_release`
|
* `cmake --build ./build_release`
|
||||||
* `cmake --build ./build_debug`
|
* `cmake --build ./build_debug`
|
||||||
@@ -165,6 +171,10 @@ Do not refactor when:
|
|||||||
* If a change adds a walk, cache, analysis, or structural traversal, justify why it is needed
|
* If a change adds a walk, cache, analysis, or structural traversal, justify why it is needed
|
||||||
* For hot paths, prefer preserving existing asymptotic behavior unless a better structure is part of the requested change
|
* For hot paths, prefer preserving existing asymptotic behavior unless a better structure is part of the requested change
|
||||||
* If performance may change, mention the expected impact and suggest a targeted timing check
|
* If performance may change, mention the expected impact and suggest a targeted timing check
|
||||||
|
* Compile-time and compiler-memory improvements must not trade away hardware
|
||||||
|
parallelism or theoretical runtime. Preserve or improve the static runtime
|
||||||
|
proxies defined in
|
||||||
|
`.agents/invariants/PERFORMANCE_OPTIMIZATION_INVARIANT.md`.
|
||||||
|
|
||||||
# Goal-driven execution
|
# Goal-driven execution
|
||||||
|
|
||||||
|
|||||||
@@ -67,20 +67,22 @@ ONNX-MLIR -> Spatial -> Pim (tensor) -> Pim (bufferized) -> PIM artifacts
|
|||||||
Converts tensor-semantics PIM IR into memref-semantics PIM IR using MLIR's
|
Converts tensor-semantics PIM IR into memref-semantics PIM IR using MLIR's
|
||||||
bufferization interfaces.
|
bufferization interfaces.
|
||||||
|
|
||||||
5. **Static memory coalescing**
|
5. **Safe IR memory coalescing**
|
||||||
(`src/PIM/Dialect/Pim/Transforms/StaticMemoryCoalescing`).
|
(`src/PIM/Dialect/Pim/Transforms/MemoryCoalescing`).
|
||||||
Reuses compatible local memref allocations inside PIM cores before codegen.
|
Normalizes compatible block-local allocations before final planning.
|
||||||
|
6. **PIM local-memory planning**
|
||||||
6. **PIM code generation** (`src/PIM/Pass/PimCodegen` and
|
(`src/PIM/Dialect/Pim/Transforms/LocalMemoryPlanning`).
|
||||||
|
Computes whole-core lifetimes, coalesces non-overlapping allocations into
|
||||||
|
physical slots, assigns addresses, and records the explicit plan in PIM IR.
|
||||||
|
7. **PIM verification and code generation** (`src/PIM/Pass/PimCodegen` and
|
||||||
`src/PIM/Compiler`).
|
`src/PIM/Compiler`).
|
||||||
Folds host constants, materializes remaining host constants, verifies PIM IR,
|
Verifies the memory plan and other PIM invariants, then emits `.pim` core
|
||||||
emits `.pim` core files, writes weights, and writes `memory.bin` /
|
files, weights, and `memory.bin` / `config.json` without rerunning liveness.
|
||||||
`config.json`.
|
|
||||||
|
|
||||||
Supporting pieces:
|
Supporting pieces:
|
||||||
- `src/PIM/Common` - shared IR, filesystem, diagnostics, reports, and utility
|
- `src/PIM/Common` - shared IR, filesystem, diagnostics, reports, and utility
|
||||||
helpers.
|
helpers.
|
||||||
- `src/PIM/Compiler` - PIM compiler options, memory/address planning, binary
|
- `src/PIM/Compiler` - PIM compiler options, planned-address materialization, binary
|
||||||
instruction format, artifact writing, weight emission, and codegen entry
|
instruction format, artifact writing, weight emission, and codegen entry
|
||||||
points.
|
points.
|
||||||
- `src/PIM/Conversion/SpatialToGraphviz` - optional Spatial graphviz conversion
|
- `src/PIM/Conversion/SpatialToGraphviz` - optional Spatial graphviz conversion
|
||||||
@@ -97,8 +99,8 @@ Pass these to `onnx-mlir` when compiling for PIM:
|
|||||||
`--EmitPimCodegen` - stop the PIM pipeline at the requested stage. The PIM
|
`--EmitPimCodegen` - stop the PIM pipeline at the requested stage. The PIM
|
||||||
default is `--EmitPimCodegen`.
|
default is `--EmitPimCodegen`.
|
||||||
- `--core-count=<N>` - required positive core count for PIM compilation.
|
- `--core-count=<N>` - required positive core count for PIM compilation.
|
||||||
- `--crossbar-size=<N>` - crossbar width/height. Default in code is `2`.
|
- `--crossbar-size=<N>` - crossbar width/height. Default in code is `128`.
|
||||||
- `--crossbar-count=<N>` - crossbars per core. Default in code is `256`.
|
- `--crossbar-count=<N>` - crossbars per core. Default in code is `64`.
|
||||||
- `--pim-only-codegen` - assume input is already bufferized PIM IR and only run
|
- `--pim-only-codegen` - assume input is already bufferized PIM IR and only run
|
||||||
the codegen tail.
|
the codegen tail.
|
||||||
- `--pim-emit-json` - also emit `core_*.json` instruction files alongside
|
- `--pim-emit-json` - also emit `core_*.json` instruction files alongside
|
||||||
@@ -109,12 +111,28 @@ Pass these to `onnx-mlir` when compiling for PIM:
|
|||||||
- `--use-experimental-conv-impl` - use the alternate convolution lowering.
|
- `--use-experimental-conv-impl` - use the alternate convolution lowering.
|
||||||
- `--ignore-concat-error` - soft-fail a ConcatOp corner case.
|
- `--ignore-concat-error` - soft-fail a ConcatOp corner case.
|
||||||
|
|
||||||
|
## Standard PIM hardware profile
|
||||||
|
|
||||||
|
Raptor's standard development and YOLO validation profile is:
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
| --- | ---: |
|
||||||
|
| Cores | 144 |
|
||||||
|
| Crossbars per core | 64 |
|
||||||
|
| Crossbar size | 128 × 128 |
|
||||||
|
|
||||||
|
Canonical compiler flags:
|
||||||
|
|
||||||
|
`--crossbar-count=64 --crossbar-size=128 --core-count=144`
|
||||||
|
|
||||||
|
`--core-count` remains mandatory and must be passed explicitly to the compiler.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./build_release/Release/bin/onnx-mlir model.onnx -o /tmp/raptor/model \
|
./build_release/Release/bin/onnx-mlir model.onnx -o /tmp/raptor/model \
|
||||||
--maccel=PIM --EmitPimCodegen \
|
--maccel=PIM --EmitPimCodegen \
|
||||||
--crossbar-size=2048 --crossbar-count=256 --core-count=1000
|
--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/`.
|
||||||
@@ -131,21 +149,33 @@ Python dependencies used by the validation scripts are `numpy`, `onnx`, and
|
|||||||
Per-operation validation from the repository root:
|
Per-operation validation from the repository root:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 validation/validate.py \
|
python validation/validate.py \
|
||||||
--raptor-path build_release/Release/bin/onnx-mlir \
|
--raptor-path build_release/Release/bin/onnx-mlir \
|
||||||
--onnx-include-dir onnx-mlir/include \
|
--onnx-include-dir onnx-mlir/include \
|
||||||
--core-count 1000
|
--operations-dir validation/operations \
|
||||||
|
--crossbar-count 64 \
|
||||||
|
--crossbar-size 128 \
|
||||||
|
--core-count 144 \
|
||||||
|
--verbose \
|
||||||
|
--raptor-extra-arg=--pim-detect-communication-deadlock \
|
||||||
|
--raptor-extra-arg=--pim-export-spatial-dataflow=none
|
||||||
```
|
```
|
||||||
|
|
||||||
Validate one network or a subset by pointing `--operations-dir` at any directory
|
Validate one network or a subset by pointing `--operations-dir` at any directory
|
||||||
containing `.onnx` files:
|
containing `.onnx` files:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 validation/validate.py \
|
python validation/validate.py \
|
||||||
--raptor-path build_release/Release/bin/onnx-mlir \
|
--raptor-path build_release/Release/bin/onnx-mlir \
|
||||||
--onnx-include-dir onnx-mlir/include \
|
--onnx-include-dir onnx-mlir/include \
|
||||||
--operations-dir validation/networks/yolo11n/depth_04 \
|
--operations-dir validation/networks/yolo11n/depth_04 \
|
||||||
--crossbar-size 2048 --crossbar-count 256 --core-count 1000
|
--crossbar-count 64 \
|
||||||
|
--crossbar-size 128 \
|
||||||
|
--core-count 144 \
|
||||||
|
--verbose \
|
||||||
|
--raptor-extra-arg=--pim-memory-report=summary \
|
||||||
|
--raptor-extra-arg=--pim-detect-communication-deadlock \
|
||||||
|
--raptor-extra-arg=--pim-export-spatial-dataflow=none
|
||||||
```
|
```
|
||||||
|
|
||||||
Useful validation options:
|
Useful validation options:
|
||||||
@@ -170,7 +200,7 @@ Each validation run writes artifacts in the model workspace, for example under
|
|||||||
The compiler currently dumps dialect snapshots such as `spatial0.mlir`,
|
The compiler currently dumps dialect snapshots such as `spatial0.mlir`,
|
||||||
`spatial1_graph.mlir`, `spatial2_trivial_merged.mlir`,
|
`spatial1_graph.mlir`, `spatial2_trivial_merged.mlir`,
|
||||||
`spatial3_scheduled_no_comm.mlir`, `spatial4_scheduled.mlir`, `pim0.mlir`, `pim1_buff.mlir`,
|
`spatial3_scheduled_no_comm.mlir`, `spatial4_scheduled.mlir`, `pim0.mlir`, `pim1_buff.mlir`,
|
||||||
`pim2_folded.mlir`, and `pim3_coalesced.mlir` when an output directory is
|
`pim2_folded.mlir`, `pim3_coalesced.mlir`, and `pim4_memory_planned.mlir` when an output directory is
|
||||||
available.
|
available.
|
||||||
|
|
||||||
To rerun the simulator manually with tracing after validation has produced a
|
To rerun the simulator manually with tracing after validation has produced a
|
||||||
|
|||||||
@@ -120,8 +120,8 @@ add_pim_library(OMPIMAccel
|
|||||||
OMSpatialToPim
|
OMSpatialToPim
|
||||||
OMPimCommon
|
OMPimCommon
|
||||||
OMPimBufferization
|
OMPimBufferization
|
||||||
OMPimMemoryCoalescing
|
|
||||||
OMPimHostConstantFolding
|
OMPimHostConstantFolding
|
||||||
|
OMPimMemoryCoalescing
|
||||||
OMPimVerification
|
OMPimVerification
|
||||||
MLIRTensorInferTypeOpInterfaceImpl
|
MLIRTensorInferTypeOpInterfaceImpl
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
||||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/DenseSet.h"
|
||||||
#include "llvm/ADT/STLExtras.h"
|
#include "llvm/ADT/STLExtras.h"
|
||||||
|
|
||||||
#include "src/Accelerators/PIM/Common/IR/CoreBlockUtils.hpp"
|
#include "src/Accelerators/PIM/Common/IR/CoreBlockUtils.hpp"
|
||||||
@@ -12,8 +13,10 @@ namespace onnx_mlir {
|
|||||||
|
|
||||||
bool isCoreStaticAddressOp(mlir::Operation* op) {
|
bool isCoreStaticAddressOp(mlir::Operation* op) {
|
||||||
if (mlir::isa<mlir::affine::AffineApplyOp,
|
if (mlir::isa<mlir::affine::AffineApplyOp,
|
||||||
mlir::arith::ConstantOp, mlir::arith::AddIOp,
|
mlir::arith::ConstantOp,
|
||||||
mlir::arith::SubIOp, mlir::arith::MulIOp,
|
mlir::arith::AddIOp,
|
||||||
|
mlir::arith::SubIOp,
|
||||||
|
mlir::arith::MulIOp,
|
||||||
mlir::arith::DivUIOp,
|
mlir::arith::DivUIOp,
|
||||||
mlir::arith::DivSIOp,
|
mlir::arith::DivSIOp,
|
||||||
mlir::arith::MinUIOp,
|
mlir::arith::MinUIOp,
|
||||||
@@ -33,13 +36,13 @@ bool isCoreStaticAddressOp(mlir::Operation* op) {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
enum class CoreWalkMode { ExecuteAllIterations, StructuralExtremes };
|
enum class CoreWalkMode {
|
||||||
using CoreWalkCallback =
|
ExecuteCommunication,
|
||||||
llvm::function_ref<mlir::LogicalResult(mlir::Operation&, const StaticValueKnowledge&)>;
|
StructuralExtremes
|
||||||
|
};
|
||||||
|
using CoreWalkCallback = llvm::function_ref<mlir::LogicalResult(mlir::Operation&, const StaticValueKnowledge&)>;
|
||||||
|
|
||||||
static void propagateRegionResults(mlir::ValueRange results,
|
static void propagateRegionResults(mlir::ValueRange results, mlir::Region& region, StaticValueKnowledge& knowledge) {
|
||||||
mlir::Region& region,
|
|
||||||
StaticValueKnowledge& knowledge) {
|
|
||||||
if (region.empty())
|
if (region.empty())
|
||||||
return;
|
return;
|
||||||
auto yield = mlir::cast<mlir::scf::YieldOp>(region.front().getTerminator());
|
auto yield = mlir::cast<mlir::scf::YieldOp>(region.front().getTerminator());
|
||||||
@@ -50,23 +53,38 @@ static void propagateRegionResults(mlir::ValueRange results,
|
|||||||
static mlir::LogicalResult walkPimCoreBlockImpl(mlir::Block& block,
|
static mlir::LogicalResult walkPimCoreBlockImpl(mlir::Block& block,
|
||||||
const StaticValueKnowledge& initialKnowledge,
|
const StaticValueKnowledge& initialKnowledge,
|
||||||
CoreWalkMode mode,
|
CoreWalkMode mode,
|
||||||
|
const PimCoreCommunicationPlan* communicationPlan,
|
||||||
CoreWalkCallback callback) {
|
CoreWalkCallback callback) {
|
||||||
bool hasFailure = false;
|
bool hasFailure = false;
|
||||||
StaticValueKnowledge knowledge = initialKnowledge;
|
StaticValueKnowledge knowledge = initialKnowledge;
|
||||||
llvm::StringRef purpose = mode == CoreWalkMode::ExecuteAllIterations ? "codegen" : "verification";
|
llvm::StringRef purpose = mode == CoreWalkMode::ExecuteCommunication ? "communication verification" : "verification";
|
||||||
for (mlir::Operation& op : block) {
|
llvm::SmallVector<mlir::Operation*, 0> structuralOperations;
|
||||||
|
llvm::ArrayRef<mlir::Operation*> operations;
|
||||||
|
if (communicationPlan) {
|
||||||
|
auto it = communicationPlan->find(&block);
|
||||||
|
if (it != communicationPlan->end())
|
||||||
|
operations = it->second;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
structuralOperations.reserve(block.getOperations().size());
|
||||||
|
for (mlir::Operation& op : block)
|
||||||
|
structuralOperations.push_back(&op);
|
||||||
|
operations = structuralOperations;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (mlir::Operation* operation : operations) {
|
||||||
|
mlir::Operation& op = *operation;
|
||||||
if (mlir::isa<pim::PimHaltOp, mlir::scf::YieldOp>(op) || isCoreStaticAddressOp(&op))
|
if (mlir::isa<pim::PimHaltOp, mlir::scf::YieldOp>(op) || isCoreStaticAddressOp(&op))
|
||||||
continue;
|
continue;
|
||||||
if (auto loadOp = mlir::dyn_cast<mlir::memref::LoadOp>(op);
|
if (auto loadOp = mlir::dyn_cast<mlir::memref::LoadOp>(op);
|
||||||
loadOp && succeeded(resolveIndexValue(loadOp.getResult(), knowledge)))
|
loadOp && succeeded(resolveIndexValue(loadOp.getResult(), knowledge)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (auto forOp = mlir::dyn_cast<mlir::scf::ForOp>(op)) {
|
if (auto forOp = mlir::dyn_cast<mlir::scf::ForOp>(op)) {
|
||||||
auto lower = resolveIndexValue(forOp.getLowerBound(), knowledge);
|
auto lower = resolveIndexValue(forOp.getLowerBound(), knowledge);
|
||||||
auto upper = resolveIndexValue(forOp.getUpperBound(), knowledge);
|
auto upper = resolveIndexValue(forOp.getUpperBound(), knowledge);
|
||||||
auto step = resolveIndexValue(forOp.getStep(), knowledge);
|
auto step = resolveIndexValue(forOp.getStep(), knowledge);
|
||||||
if (failed(lower) || failed(upper) || failed(step)
|
if (failed(lower) || failed(upper) || failed(step)
|
||||||
|| (mode == CoreWalkMode::ExecuteAllIterations && *step <= 0)) {
|
|| (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;
|
hasFailure = true;
|
||||||
continue;
|
continue;
|
||||||
@@ -84,13 +102,13 @@ static mlir::LogicalResult walkPimCoreBlockImpl(mlir::Block& block,
|
|||||||
loopKnowledge.indexValues[forOp.getInductionVar()] = induction;
|
loopKnowledge.indexValues[forOp.getInductionVar()] = induction;
|
||||||
for (auto [index, iterArg] : llvm::enumerate(forOp.getRegionIterArgs()))
|
for (auto [index, iterArg] : llvm::enumerate(forOp.getRegionIterArgs()))
|
||||||
loopKnowledge.aliases[iterArg] = carryValues ? iterValues[index] : forOp.getInitArgs()[index];
|
loopKnowledge.aliases[iterArg] = carryValues ? iterValues[index] : forOp.getInitArgs()[index];
|
||||||
hasFailure |= failed(walkPimCoreBlockImpl(body, loopKnowledge, mode, callback));
|
hasFailure |= failed(walkPimCoreBlockImpl(body, loopKnowledge, mode, communicationPlan, callback));
|
||||||
auto yield = mlir::cast<mlir::scf::YieldOp>(body.getTerminator());
|
auto yield = mlir::cast<mlir::scf::YieldOp>(body.getTerminator());
|
||||||
for (auto [index, yielded] : llvm::enumerate(yield.getOperands()))
|
for (auto [index, yielded] : llvm::enumerate(yield.getOperands()))
|
||||||
iterValues[index] = resolveLoopCarriedAlias(yielded, loopKnowledge);
|
iterValues[index] = resolveLoopCarriedAlias(yielded, loopKnowledge);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (mode == CoreWalkMode::ExecuteAllIterations) {
|
if (mode == CoreWalkMode::ExecuteCommunication) {
|
||||||
for (int64_t induction = *lower; induction < *upper; induction += *step)
|
for (int64_t induction = *lower; induction < *upper; induction += *step)
|
||||||
visitIteration(induction, true);
|
visitIteration(induction, true);
|
||||||
}
|
}
|
||||||
@@ -113,12 +131,14 @@ static mlir::LogicalResult walkPimCoreBlockImpl(mlir::Block& block,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
mlir::Region& selected = *condition != 0 ? ifOp.getThenRegion() : ifOp.getElseRegion();
|
mlir::Region& selected = *condition != 0 ? ifOp.getThenRegion() : ifOp.getElseRegion();
|
||||||
if (mode == CoreWalkMode::ExecuteAllIterations) {
|
if (mode == CoreWalkMode::ExecuteCommunication) {
|
||||||
hasFailure |= !selected.empty() && failed(walkPimCoreBlockImpl(selected.front(), knowledge, mode, callback));
|
hasFailure |= !selected.empty()
|
||||||
|
&& failed(walkPimCoreBlockImpl(selected.front(), knowledge, mode, communicationPlan, callback));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for (mlir::Region* region : {&ifOp.getThenRegion(), &ifOp.getElseRegion()})
|
for (mlir::Region* region : {&ifOp.getThenRegion(), &ifOp.getElseRegion()})
|
||||||
hasFailure |= !region->empty() && failed(walkPimCoreBlockImpl(region->front(), knowledge, mode, callback));
|
hasFailure |= !region->empty()
|
||||||
|
&& failed(walkPimCoreBlockImpl(region->front(), knowledge, mode, communicationPlan, callback));
|
||||||
}
|
}
|
||||||
propagateRegionResults(ifOp.getResults(), selected, knowledge);
|
propagateRegionResults(ifOp.getResults(), selected, knowledge);
|
||||||
continue;
|
continue;
|
||||||
@@ -138,33 +158,54 @@ static mlir::LogicalResult walkPimCoreBlockImpl(mlir::Block& block,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
for (mlir::Region& region : switchOp->getRegions()) {
|
for (mlir::Region& region : switchOp->getRegions()) {
|
||||||
if (mode == CoreWalkMode::ExecuteAllIterations && ®ion != selected)
|
if (mode == CoreWalkMode::ExecuteCommunication && ®ion != selected)
|
||||||
continue;
|
continue;
|
||||||
hasFailure |= failed(walkPimCoreBlockImpl(region.front(), knowledge, mode, callback));
|
hasFailure |= failed(walkPimCoreBlockImpl(region.front(), knowledge, mode, communicationPlan, callback));
|
||||||
}
|
}
|
||||||
propagateRegionResults(switchOp.getResults(), *selected, knowledge);
|
propagateRegionResults(switchOp.getResults(), *selected, knowledge);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasFailure |= failed(callback(op, knowledge));
|
if (mode != CoreWalkMode::ExecuteCommunication || mlir::isa<pim::PimSendOp, pim::PimReceiveOp>(op))
|
||||||
|
hasFailure |= failed(callback(op, knowledge));
|
||||||
}
|
}
|
||||||
return mlir::success(!hasFailure);
|
return mlir::success(!hasFailure);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
mlir::LogicalResult
|
PimCoreCommunicationPlan buildPimCoreCommunicationPlan(mlir::Block& block) {
|
||||||
walkPimCoreBlock(mlir::Block& block,
|
llvm::DenseSet<mlir::Operation*> communicationAncestors;
|
||||||
const StaticValueKnowledge& knowledge,
|
block.walk([&](mlir::Operation* op) {
|
||||||
llvm::function_ref<mlir::LogicalResult(mlir::Operation&, const StaticValueKnowledge&)> callback) {
|
if (!mlir::isa<pim::PimSendOp, pim::PimReceiveOp>(op))
|
||||||
return walkPimCoreBlockImpl(block, knowledge, CoreWalkMode::ExecuteAllIterations, callback);
|
return;
|
||||||
|
for (mlir::Operation* parent = op->getParentOp(); parent; parent = parent->getParentOp())
|
||||||
|
communicationAncestors.insert(parent);
|
||||||
|
});
|
||||||
|
|
||||||
|
PimCoreCommunicationPlan plan;
|
||||||
|
block.walk([&](mlir::Operation* op) {
|
||||||
|
bool isCommunication = mlir::isa<pim::PimSendOp, pim::PimReceiveOp>(op);
|
||||||
|
bool isControlFlow = mlir::isa<mlir::scf::ForOp, mlir::scf::IfOp, mlir::scf::IndexSwitchOp>(op);
|
||||||
|
if (isCommunication || (isControlFlow && (op->getNumResults() != 0 || communicationAncestors.contains(op))))
|
||||||
|
plan[op->getBlock()].push_back(op);
|
||||||
|
});
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
|
||||||
|
mlir::LogicalResult walkPimCoreCommunicationBlock(
|
||||||
|
mlir::Block& block,
|
||||||
|
const PimCoreCommunicationPlan& plan,
|
||||||
|
const StaticValueKnowledge& knowledge,
|
||||||
|
llvm::function_ref<mlir::LogicalResult(mlir::Operation&, const StaticValueKnowledge&)> callback) {
|
||||||
|
return walkPimCoreBlockImpl(block, knowledge, CoreWalkMode::ExecuteCommunication, &plan, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
mlir::LogicalResult walkPimCoreBlockStructurally(
|
mlir::LogicalResult walkPimCoreBlockStructurally(
|
||||||
mlir::Block& block,
|
mlir::Block& block,
|
||||||
const StaticValueKnowledge& knowledge,
|
const StaticValueKnowledge& knowledge,
|
||||||
llvm::function_ref<mlir::LogicalResult(mlir::Operation&, const StaticValueKnowledge&)> callback) {
|
llvm::function_ref<mlir::LogicalResult(mlir::Operation&, const StaticValueKnowledge&)> callback) {
|
||||||
return walkPimCoreBlockImpl(block, knowledge, CoreWalkMode::StructuralExtremes, callback);
|
return walkPimCoreBlockImpl(block, knowledge, CoreWalkMode::StructuralExtremes, nullptr, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -3,23 +3,30 @@
|
|||||||
#include "mlir/IR/Block.h"
|
#include "mlir/IR/Block.h"
|
||||||
#include "mlir/Support/LogicalResult.h"
|
#include "mlir/Support/LogicalResult.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/DenseMap.h"
|
||||||
#include "llvm/ADT/STLFunctionalExtras.h"
|
#include "llvm/ADT/STLFunctionalExtras.h"
|
||||||
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
|
||||||
#include "src/Accelerators/PIM/Common/IR/AddressAnalysis.hpp"
|
#include "src/Accelerators/PIM/Common/IR/AddressAnalysis.hpp"
|
||||||
|
|
||||||
namespace onnx_mlir {
|
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
|
/// 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);
|
bool isCoreStaticAddressOp(mlir::Operation* op);
|
||||||
|
|
||||||
/// Walks a `pim.core` body, statically unrolling nested `scf.for` loops when
|
/// Walks a `pim.core` body's communication stream, statically unrolling
|
||||||
/// their bounds are known and invoking `callback` only on instruction-emitting
|
/// control flow that contains send/receive operations and invoking `callback`
|
||||||
/// operations.
|
/// on those operations in execution order.
|
||||||
mlir::LogicalResult
|
PimCoreCommunicationPlan buildPimCoreCommunicationPlan(mlir::Block& block);
|
||||||
walkPimCoreBlock(mlir::Block& block,
|
|
||||||
const StaticValueKnowledge& knowledge,
|
mlir::LogicalResult walkPimCoreCommunicationBlock(
|
||||||
llvm::function_ref<mlir::LogicalResult(mlir::Operation&, const StaticValueKnowledge&)> callback);
|
mlir::Block& block,
|
||||||
|
const PimCoreCommunicationPlan& plan,
|
||||||
|
const StaticValueKnowledge& knowledge,
|
||||||
|
llvm::function_ref<mlir::LogicalResult(mlir::Operation&, const StaticValueKnowledge&)> callback);
|
||||||
|
|
||||||
/// Walks a `pim.core`-like body structurally for verification without
|
/// Walks a `pim.core`-like body structurally for verification without
|
||||||
/// enumerating full loop trip counts. Loop bounds must still be statically
|
/// enumerating full loop trip counts. Loop bounds must still be statically
|
||||||
|
|||||||
@@ -22,9 +22,19 @@
|
|||||||
#include "src/Accelerators/PIM/Common/Support/FileSystemUtils.hpp"
|
#include "src/Accelerators/PIM/Common/Support/FileSystemUtils.hpp"
|
||||||
#include "src/Compiler/CompilerOptions.hpp"
|
#include "src/Compiler/CompilerOptions.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
|
|
||||||
inline constexpr llvm::StringLiteral kCoreIdAttrName = "coreId";
|
inline constexpr llvm::StringLiteral kCoreIdAttrName = "coreId";
|
||||||
inline constexpr llvm::StringLiteral kCoreIdsAttrName = "coreIds";
|
inline constexpr llvm::StringLiteral kCoreIdsAttrName = "coreIds";
|
||||||
|
inline constexpr llvm::StringLiteral kLocalMemoryAddressAttrName = "pim.local_memory_address";
|
||||||
|
inline constexpr llvm::StringLiteral kLocalMemorySlotAttrName = "pim.local_memory_slot";
|
||||||
|
inline constexpr llvm::StringLiteral kLocalMemorySlotSizeAttrName = "pim.local_memory_slot_size";
|
||||||
|
inline constexpr llvm::StringLiteral kLocalMemoryFallbackCountAttrName = "pim.local_memory_fallback_count";
|
||||||
|
inline constexpr llvm::StringLiteral kLocalMemoryNestedSingleUseCountAttrName =
|
||||||
|
"pim.local_memory_nested_single_use_count";
|
||||||
|
inline constexpr size_t kPimLocalMemoryAddressLimit = static_cast<size_t>(std::numeric_limits<int32_t>::max());
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ add_pim_library(OMPimCompilerUtils
|
|||||||
PimArtifactWriter.cpp
|
PimArtifactWriter.cpp
|
||||||
PimCodeGen.cpp
|
PimCodeGen.cpp
|
||||||
PimCoreProgram.cpp
|
PimCoreProgram.cpp
|
||||||
PimMemoryLiveness.cpp
|
|
||||||
PimWeightEmitter.cpp
|
PimWeightEmitter.cpp
|
||||||
|
|
||||||
EXCLUDE_FROM_OM_LIBS
|
EXCLUDE_FROM_OM_LIBS
|
||||||
@@ -31,8 +30,8 @@ add_pim_library(OMPimCompilerUtils
|
|||||||
OMPimCompilerOptions
|
OMPimCompilerOptions
|
||||||
OMPimCommon
|
OMPimCommon
|
||||||
OMPimBufferization
|
OMPimBufferization
|
||||||
OMPimMemoryCoalescing
|
|
||||||
OMPimHostConstantFolding
|
OMPimHostConstantFolding
|
||||||
|
OMPimLocalMemoryPlanning
|
||||||
OMPimVerification
|
OMPimVerification
|
||||||
OMPimPasses
|
OMPimPasses
|
||||||
OMONNXToSpatial
|
OMONNXToSpatial
|
||||||
|
|||||||
+160
-157
@@ -15,6 +15,7 @@
|
|||||||
#include "llvm/ADT/StringExtras.h"
|
#include "llvm/ADT/StringExtras.h"
|
||||||
#include "llvm/Support/Debug.h"
|
#include "llvm/Support/Debug.h"
|
||||||
#include "llvm/Support/FileSystem.h"
|
#include "llvm/Support/FileSystem.h"
|
||||||
|
#include "llvm/Support/FormatVariadic.h"
|
||||||
#include "llvm/Support/JSON.h"
|
#include "llvm/Support/JSON.h"
|
||||||
#include "llvm/Support/raw_ostream.h"
|
#include "llvm/Support/raw_ostream.h"
|
||||||
|
|
||||||
@@ -31,8 +32,8 @@
|
|||||||
|
|
||||||
#include "Common/IR/CompactAsmUtils.hpp"
|
#include "Common/IR/CompactAsmUtils.hpp"
|
||||||
#include "Common/PimCommon.hpp"
|
#include "Common/PimCommon.hpp"
|
||||||
#include "Common/Support/Diagnostics.hpp"
|
|
||||||
#include "Common/Support/CheckedArithmetic.hpp"
|
#include "Common/Support/CheckedArithmetic.hpp"
|
||||||
|
#include "Common/Support/Diagnostics.hpp"
|
||||||
#include "Common/Support/ReportUtils.hpp"
|
#include "Common/Support/ReportUtils.hpp"
|
||||||
#include "Conversion/ONNXToSpatial/Common/Common.hpp"
|
#include "Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.hpp"
|
#include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.hpp"
|
||||||
@@ -43,7 +44,6 @@
|
|||||||
#include "src/Accelerators/PIM/Compiler/PimCodeGen.hpp"
|
#include "src/Accelerators/PIM/Compiler/PimCodeGen.hpp"
|
||||||
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
||||||
#include "src/Accelerators/PIM/Compiler/PimCoreProgram.hpp"
|
#include "src/Accelerators/PIM/Compiler/PimCoreProgram.hpp"
|
||||||
#include "src/Accelerators/PIM/Compiler/PimMemoryLiveness.hpp"
|
|
||||||
#include "src/Accelerators/PIM/Compiler/PimWeightEmitter.hpp"
|
#include "src/Accelerators/PIM/Compiler/PimWeightEmitter.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
||||||
|
|
||||||
@@ -110,8 +110,6 @@ static Operation* getDiagnosticAnchor(mlir::Value value) {
|
|||||||
// PIM instruction immediates are serialized as signed int32_t fields today
|
// PIM instruction immediates are serialized as signed int32_t fields today
|
||||||
// (`sldi` goes through checkedI32OrCrash), so local addresses must stay within
|
// (`sldi` goes through checkedI32OrCrash), so local addresses must stay within
|
||||||
// the non-negative int32_t range.
|
// the non-negative int32_t range.
|
||||||
static constexpr size_t kPimAddressLimit = static_cast<size_t>(std::numeric_limits<int32_t>::max());
|
|
||||||
|
|
||||||
static FailureOr<size_t> checkedAlignTo(size_t value, size_t alignment, Operation* anchor, StringRef fieldName) {
|
static FailureOr<size_t> checkedAlignTo(size_t value, size_t alignment, Operation* anchor, StringRef fieldName) {
|
||||||
if (alignment == 0)
|
if (alignment == 0)
|
||||||
return value;
|
return value;
|
||||||
@@ -129,7 +127,7 @@ static void printMemoryOverflowDiagnostic(const MemoryValueKey& key,
|
|||||||
llvm::errs() << "Requested allocation size: " << requestedSize << " bytes\n";
|
llvm::errs() << "Requested allocation size: " << requestedSize << " bytes\n";
|
||||||
llvm::errs() << "Current firstAvailableAddress: " << currentFirstAvailableAddress << "\n";
|
llvm::errs() << "Current firstAvailableAddress: " << currentFirstAvailableAddress << "\n";
|
||||||
llvm::errs() << "Aligned end address: " << alignedEndAddress << "\n";
|
llvm::errs() << "Aligned end address: " << alignedEndAddress << "\n";
|
||||||
llvm::errs() << "Address limit: " << kPimAddressLimit << " (signed int32_t immediate range)\n";
|
llvm::errs() << "Address limit: " << kPimLocalMemoryAddressLimit << " (signed int32_t immediate range)\n";
|
||||||
if (key.lane)
|
if (key.lane)
|
||||||
llvm::errs() << "Lane: " << *key.lane << "\n";
|
llvm::errs() << "Lane: " << *key.lane << "\n";
|
||||||
llvm::errs() << "Value: ";
|
llvm::errs() << "Value: ";
|
||||||
@@ -152,10 +150,13 @@ size_t PimMemory::allocateAddress(size_t size, const MemoryValueKey& key) {
|
|||||||
FailureOr<size_t> checkedAlignedEnd = failure();
|
FailureOr<size_t> checkedAlignedEnd = failure();
|
||||||
if (succeeded(checkedEnd))
|
if (succeeded(checkedEnd))
|
||||||
checkedAlignedEnd = checkedAlignTo(*checkedEnd, minAlignment, anchor, "local memory alignment");
|
checkedAlignedEnd = checkedAlignTo(*checkedEnd, minAlignment, anchor, "local memory alignment");
|
||||||
if (address > kPimAddressLimit || failed(checkedEnd) || *checkedEnd > kPimAddressLimit
|
if (address > kPimLocalMemoryAddressLimit || failed(checkedEnd) || *checkedEnd > kPimLocalMemoryAddressLimit
|
||||||
|| failed(checkedAlignedEnd) || *checkedAlignedEnd > kPimAddressLimit) {
|
|| failed(checkedAlignedEnd) || *checkedAlignedEnd > kPimLocalMemoryAddressLimit) {
|
||||||
printMemoryOverflowDiagnostic(
|
printMemoryOverflowDiagnostic(
|
||||||
key, size, firstAvailableAddress, succeeded(checkedAlignedEnd) ? *checkedAlignedEnd : kPimAddressLimit);
|
key,
|
||||||
|
size,
|
||||||
|
firstAvailableAddress,
|
||||||
|
succeeded(checkedAlignedEnd) ? *checkedAlignedEnd : kPimLocalMemoryAddressLimit);
|
||||||
llvm_unreachable("PIM local memory allocation overflow");
|
llvm_unreachable("PIM local memory allocation overflow");
|
||||||
}
|
}
|
||||||
firstAvailableAddress = *checkedAlignedEnd;
|
firstAvailableAddress = *checkedAlignedEnd;
|
||||||
@@ -202,15 +203,6 @@ void PimMemory::allocateMemoryForValue(const MemoryValueKey& key, MemEntry& memE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PhysicalSlotInfo PimMemory::allocatePhysicalSlot(size_t slotSize, const MemoryValueKey& key) {
|
|
||||||
PhysicalSlotInfo slot;
|
|
||||||
slot.id = nextPhysicalSlotId++;
|
|
||||||
slot.address = allocateAddress(slotSize, key);
|
|
||||||
slot.size = slotSize;
|
|
||||||
localPhysicalSlots.push_back(slot);
|
|
||||||
return slot;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PimMemory::allocateHost(ModuleOp moduleOp, func::FuncOp funcOp) {
|
void PimMemory::allocateHost(ModuleOp moduleOp, func::FuncOp funcOp) {
|
||||||
SmallDenseMap<memref::GlobalOp, mlir::Value, 8> globalConstants;
|
SmallDenseMap<memref::GlobalOp, mlir::Value, 8> globalConstants;
|
||||||
SmallVector<std::pair<mlir::Value, mlir::Value>, 16> globalAliases;
|
SmallVector<std::pair<mlir::Value, mlir::Value>, 16> globalAliases;
|
||||||
@@ -249,71 +241,17 @@ void PimMemory::allocateHost(ModuleOp moduleOp, func::FuncOp funcOp) {
|
|||||||
globalMemEntriesMap[getMemoryValueKey(alias)] = getMemEntry(getMemoryValueKey(original));
|
globalMemEntriesMap[getMemoryValueKey(alias)] = getMemEntry(getMemoryValueKey(original));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PimMemory::allocateCore(Operation* op, std::optional<unsigned> lane) {
|
void PimMemory::allocateCore(const CompiledCoreMemoryPlan& plan, std::optional<unsigned> lane) {
|
||||||
auto intervals = buildLocalAllocIntervals(op, lane, pimMemoryReport == PimMemoryReportFull);
|
if (localPhysicalSlots.empty()) {
|
||||||
SmallVector<PlannedPhysicalSlot> plannedSlots = planPhysicalSlots(intervals);
|
llvm::append_range(localPhysicalSlots, plan.slots);
|
||||||
|
reportRow.logicalAllocaBytes = plan.logicalBytes;
|
||||||
SmallVector<size_t> slotOrder(plannedSlots.size());
|
reportRow.fallbackIntervals = plan.fallbackIntervals;
|
||||||
std::iota(slotOrder.begin(), slotOrder.end(), 0);
|
reportRow.nestedSingleUseIntervals = plan.nestedSingleUseIntervals;
|
||||||
llvm::stable_sort(slotOrder, [&](size_t lhsIndex, size_t rhsIndex) {
|
|
||||||
const PlannedPhysicalSlot& lhs = plannedSlots[lhsIndex];
|
|
||||||
const PlannedPhysicalSlot& rhs = plannedSlots[rhsIndex];
|
|
||||||
if (lhs.requiredSize != rhs.requiredSize)
|
|
||||||
return lhs.requiredSize > rhs.requiredSize;
|
|
||||||
return lhs.id < rhs.id;
|
|
||||||
});
|
|
||||||
|
|
||||||
SmallVector<bool, 16> usedExistingSlots(localPhysicalSlots.size(), false);
|
|
||||||
for (size_t slotIndex : slotOrder) {
|
|
||||||
PlannedPhysicalSlot& slot = plannedSlots[slotIndex];
|
|
||||||
size_t bestExistingIndex = std::numeric_limits<size_t>::max();
|
|
||||||
auto bestKey = std::tuple<size_t, size_t, size_t>(
|
|
||||||
std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max());
|
|
||||||
|
|
||||||
for (size_t existingIndex = 0; existingIndex < localPhysicalSlots.size(); ++existingIndex) {
|
|
||||||
if (usedExistingSlots[existingIndex])
|
|
||||||
continue;
|
|
||||||
const PhysicalSlotInfo& existingSlot = localPhysicalSlots[existingIndex];
|
|
||||||
if (existingSlot.size < slot.requiredSize)
|
|
||||||
continue;
|
|
||||||
auto candidateKey =
|
|
||||||
std::tuple<size_t, size_t, size_t>(existingSlot.size - slot.requiredSize, existingSlot.size, existingSlot.id);
|
|
||||||
if (candidateKey < bestKey) {
|
|
||||||
bestKey = candidateKey;
|
|
||||||
bestExistingIndex = existingIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bestExistingIndex != std::numeric_limits<size_t>::max()) {
|
|
||||||
const PhysicalSlotInfo& existingSlot = localPhysicalSlots[bestExistingIndex];
|
|
||||||
slot.id = existingSlot.id;
|
|
||||||
slot.address = existingSlot.address;
|
|
||||||
slot.size = existingSlot.size;
|
|
||||||
usedExistingSlots[bestExistingIndex] = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
PhysicalSlotInfo newSlot = allocatePhysicalSlot(slot.requiredSize, intervals[slot.intervalIndices.front()].key);
|
|
||||||
slot.id = newSlot.id;
|
|
||||||
slot.address = newSlot.address;
|
|
||||||
slot.size = newSlot.size;
|
|
||||||
usedExistingSlots.push_back(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t intervalIndex : slot.intervalIndices) {
|
|
||||||
LocalAllocInterval& interval = intervals[intervalIndex];
|
|
||||||
interval.physicalSlotId = slot.id;
|
|
||||||
interval.assignedAddress = slot.address;
|
|
||||||
interval.physicalSlotSize = slot.size;
|
|
||||||
MemEntry memEntry {slot.address, interval.size};
|
|
||||||
ownedMemEntriesMap[interval.key] = memEntry;
|
|
||||||
globalMemEntriesMap[interval.key] = memEntry;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
for (const CompiledLocalMemoryEntry& entry : plan.entries) {
|
||||||
if (pimMemoryReport != PimMemoryReportNone) {
|
MemoryValueKey key = getMemoryValueKey(entry.value, lane);
|
||||||
MemoryPlanArtifacts artifacts =
|
ownedMemEntriesMap[key] = entry.memory;
|
||||||
buildMemoryPlanArtifacts(op, lane, intervals, plannedSlots, kPimAddressLimit, pimMemoryReport);
|
globalMemEntriesMap[key] = entry.memory;
|
||||||
livenessArtifacts += artifacts;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,13 +413,41 @@ void PimAcceleratorMemory::flushReport() {
|
|||||||
uint64_t totalGlobalMemory = hostReportRow.has_value() ? hostReportRow->sizeGlobal : 0;
|
uint64_t totalGlobalMemory = hostReportRow.has_value() ? hostReportRow->sizeGlobal : 0;
|
||||||
uint64_t totalWeightsMemory = totalWeightBytes;
|
uint64_t totalWeightsMemory = totalWeightBytes;
|
||||||
uint64_t totalCoresMemory = 0;
|
uint64_t totalCoresMemory = 0;
|
||||||
for (const MemoryReportEntry& entry : reportEntries)
|
uint64_t totalLogicalCoreMemory = 0;
|
||||||
|
uint64_t totalFallbackIntervals = 0;
|
||||||
|
uint64_t totalNestedSingleUseIntervals = 0;
|
||||||
|
uint64_t maximumCorePeak = 0;
|
||||||
|
std::string worstEntry = "<none>";
|
||||||
|
for (const MemoryReportEntry& entry : reportEntries) {
|
||||||
totalCoresMemory += entry.totalAllocaBytes;
|
totalCoresMemory += entry.totalAllocaBytes;
|
||||||
|
uint64_t factor = entry.kind == MemoryReportEntry::Kind::Batch ? entry.coreIds.size() : 1;
|
||||||
|
totalLogicalCoreMemory += entry.row.logicalAllocaBytes * factor;
|
||||||
|
totalFallbackIntervals += entry.row.fallbackIntervals * factor;
|
||||||
|
totalNestedSingleUseIntervals += entry.row.nestedSingleUseIntervals * factor;
|
||||||
|
if (entry.row.sizeAlloca <= maximumCorePeak)
|
||||||
|
continue;
|
||||||
|
maximumCorePeak = entry.row.sizeAlloca;
|
||||||
|
worstEntry = entry.kind == MemoryReportEntry::Kind::Batch ? "Batch " + std::to_string(entry.id)
|
||||||
|
: "Core " + std::to_string(entry.coreIds.front());
|
||||||
|
}
|
||||||
|
uint64_t savedCoreMemory =
|
||||||
|
totalLogicalCoreMemory >= totalCoresMemory ? totalLogicalCoreMemory - totalCoresMemory : 0;
|
||||||
|
double savedPercent = totalLogicalCoreMemory == 0
|
||||||
|
? 0.0
|
||||||
|
: 100.0 * static_cast<double>(savedCoreMemory) / static_cast<double>(totalLogicalCoreMemory);
|
||||||
|
|
||||||
llvm::SmallVector<ReportField, 3> totalFields = {
|
llvm::SmallVector<ReportField, 10> totalFields = {
|
||||||
{"Global memory", formatReportMemory(totalGlobalMemory) },
|
{"Global memory", formatReportMemory(totalGlobalMemory) },
|
||||||
{"Weights memory", formatReportMemory(totalWeightsMemory)},
|
{"Weights memory", formatReportMemory(totalWeightsMemory) },
|
||||||
{"Cores memory", formatReportMemory(totalCoresMemory) }
|
{"Logical core allocations", formatReportMemory(totalLogicalCoreMemory) },
|
||||||
|
{"Physical core memory", formatReportMemory(totalCoresMemory) },
|
||||||
|
{"Cores memory", formatReportMemory(totalCoresMemory) },
|
||||||
|
{"Saved core memory", formatReportMemory(savedCoreMemory) },
|
||||||
|
{"Saved core memory percent", formatv("{0:F2}%", savedPercent).str() },
|
||||||
|
{"Maximum core peak", formatReportMemory(maximumCorePeak) },
|
||||||
|
{"Worst core/batch", worstEntry },
|
||||||
|
{"Fallback intervals", std::to_string(totalFallbackIntervals) },
|
||||||
|
{"Nested single-use intervals", std::to_string(totalNestedSingleUseIntervals) }
|
||||||
};
|
};
|
||||||
printReportTotalsBlock(os, totalFields);
|
printReportTotalsBlock(os, totalFields);
|
||||||
|
|
||||||
@@ -552,19 +518,14 @@ void PimCodeGen::emitInstruction(const pim_binary::InstructionRecord& instructio
|
|||||||
|
|
||||||
void PimCodeGen::updateScalarRegisterCache(const pim_binary::InstructionRecord& instruction) const {
|
void PimCodeGen::updateScalarRegisterCache(const pim_binary::InstructionRecord& instruction) const {
|
||||||
switch (instruction.opcode) {
|
switch (instruction.opcode) {
|
||||||
case pim_binary::Opcode::sldi:
|
case pim_binary::Opcode::sldi: scalarRegisterValues[instruction.rd] = instruction.r2OrImm; break;
|
||||||
scalarRegisterValues[instruction.rd] = instruction.r2OrImm;
|
|
||||||
break;
|
|
||||||
case pim_binary::Opcode::sld:
|
case pim_binary::Opcode::sld:
|
||||||
case pim_binary::Opcode::sadd:
|
case pim_binary::Opcode::sadd:
|
||||||
case pim_binary::Opcode::ssub:
|
case pim_binary::Opcode::ssub:
|
||||||
case pim_binary::Opcode::smul:
|
case pim_binary::Opcode::smul:
|
||||||
case pim_binary::Opcode::saddi:
|
case pim_binary::Opcode::saddi:
|
||||||
case pim_binary::Opcode::smuli:
|
case pim_binary::Opcode::smuli: scalarRegisterValues[instruction.rd].reset(); break;
|
||||||
scalarRegisterValues[instruction.rd].reset();
|
default: break;
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -580,8 +541,8 @@ void PimCodeGen::genSetRegisterImmediate(uint8_t registerNumber, int32_t immedia
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PimCodeGen::genSetRegisterImmediateUnsigned(size_t registerNumber, size_t immediate) const {
|
void PimCodeGen::genSetRegisterImmediateUnsigned(size_t registerNumber, size_t immediate) const {
|
||||||
genSetRegisterImmediate(
|
genSetRegisterImmediate(pim::checkedU8OrCrash(registerNumber, "register number"),
|
||||||
pim::checkedU8OrCrash(registerNumber, "register number"), pim::checkedI32OrCrash(immediate, "register immediate"));
|
pim::checkedI32OrCrash(immediate, "register immediate"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PimCodeGen::setupRd(size_t rdAddress, size_t rdOffset) const {
|
void PimCodeGen::setupRd(size_t rdAddress, size_t rdOffset) const {
|
||||||
@@ -729,8 +690,7 @@ void PimCodeGen::codeGenConcatOp(pim::PimConcatOp concatOp, const StaticValueKno
|
|||||||
for (size_t outerIndex = 0; outerIndex < outerCount; ++outerIndex) {
|
for (size_t outerIndex = 0; outerIndex < outerCount; ++outerIndex) {
|
||||||
size_t dstOffset = (outerIndex * outputConcatDim + concatOffset) * innerCount * elementSize;
|
size_t dstOffset = (outerIndex * outputConcatDim + concatOffset) * innerCount * elementSize;
|
||||||
size_t srcOffset = outerIndex * inputConcatDim * innerCount * elementSize;
|
size_t srcOffset = outerIndex * inputConcatDim * innerCount * elementSize;
|
||||||
emitMemCopyOp(
|
emitMemCopyOp(pim_binary::Opcode::lmv, outputAddr, dstOffset, inputAddr, srcOffset, blockSizeInBytes, "len");
|
||||||
pim_binary::Opcode::lmv, outputAddr, dstOffset, inputAddr, srcOffset, blockSizeInBytes, "len");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
concatOffset += inputConcatDim;
|
concatOffset += inputConcatDim;
|
||||||
@@ -788,10 +748,11 @@ void PimCodeGen::codeGenTransposeOp(const CompiledTransposePlan& plan, const Sta
|
|||||||
size_t maxElementOffset = plan.totalBytes == 0 ? 0 : plan.totalBytes - plan.elementBytes;
|
size_t maxElementOffset = plan.totalBytes == 0 ? 0 : plan.totalBytes - plan.elementBytes;
|
||||||
int32_t maxSourceAddress = pim::checkedI32OrCrash(
|
int32_t maxSourceAddress = pim::checkedI32OrCrash(
|
||||||
pim::checkedAddOrCrash(srcAddr, maxElementOffset, "transpose source address"), "transpose source address");
|
pim::checkedAddOrCrash(srcAddr, maxElementOffset, "transpose source address"), "transpose source address");
|
||||||
int32_t maxDestinationAddress = pim::checkedI32OrCrash(
|
int32_t maxDestinationAddress =
|
||||||
pim::checkedAddOrCrash(dstAddr, maxElementOffset, "transpose destination address"), "transpose destination address");
|
pim::checkedI32OrCrash(pim::checkedAddOrCrash(dstAddr, maxElementOffset, "transpose destination address"),
|
||||||
(void)maxSourceAddress;
|
"transpose destination address");
|
||||||
(void)maxDestinationAddress;
|
(void) maxSourceAddress;
|
||||||
|
(void) maxDestinationAddress;
|
||||||
|
|
||||||
pim_binary::InstructionRecord copyInstruction;
|
pim_binary::InstructionRecord copyInstruction;
|
||||||
copyInstruction.opcode = pim_binary::Opcode::lmv;
|
copyInstruction.opcode = pim_binary::Opcode::lmv;
|
||||||
@@ -871,6 +832,60 @@ static SmallVector<Operation*> collectTopLevelCoreLikeOps(func::FuncOp funcOp) {
|
|||||||
return coreLikeOps;
|
return coreLikeOps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static FailureOr<CompiledCoreMemoryPlan> compileCoreMemoryPlan(Operation* coreLikeOp) {
|
||||||
|
CompiledCoreMemoryPlan plan;
|
||||||
|
auto fallbackAttr = coreLikeOp->getAttrOfType<IntegerAttr>(kLocalMemoryFallbackCountAttrName);
|
||||||
|
auto nestedSingleUseAttr = coreLikeOp->getAttrOfType<IntegerAttr>(kLocalMemoryNestedSingleUseCountAttrName);
|
||||||
|
if (!fallbackAttr || !nestedSingleUseAttr || fallbackAttr.getInt() < 0 || nestedSingleUseAttr.getInt() < 0) {
|
||||||
|
coreLikeOp->emitError("requires complete PIM local-memory planning summary attributes before codegen");
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
plan.fallbackIntervals = static_cast<uint64_t>(fallbackAttr.getInt());
|
||||||
|
plan.nestedSingleUseIntervals = static_cast<uint64_t>(nestedSingleUseAttr.getInt());
|
||||||
|
SmallDenseMap<size_t, size_t, 32> slotIndices;
|
||||||
|
bool hasFailure = false;
|
||||||
|
coreLikeOp->walk([&](memref::AllocOp allocOp) {
|
||||||
|
if (hasFailure)
|
||||||
|
return;
|
||||||
|
auto addressAttr = allocOp->getAttrOfType<IntegerAttr>(kLocalMemoryAddressAttrName);
|
||||||
|
auto slotAttr = allocOp->getAttrOfType<IntegerAttr>(kLocalMemorySlotAttrName);
|
||||||
|
auto slotSizeAttr = allocOp->getAttrOfType<IntegerAttr>(kLocalMemorySlotSizeAttrName);
|
||||||
|
if (!addressAttr || !slotAttr || !slotSizeAttr || addressAttr.getInt() < 0 || slotAttr.getInt() < 0
|
||||||
|
|| slotSizeAttr.getInt() < 0) {
|
||||||
|
allocOp.emitOpError("requires a complete non-negative PIM local-memory plan before codegen");
|
||||||
|
hasFailure = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto checkedSize = pim::getCheckedShapedTypeSizeInBytes(
|
||||||
|
cast<ShapedType>(allocOp.getType()), allocOp, "planned local allocation byte size");
|
||||||
|
if (failed(checkedSize)) {
|
||||||
|
hasFailure = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
size_t address = static_cast<size_t>(addressAttr.getInt());
|
||||||
|
size_t slotId = static_cast<size_t>(slotAttr.getInt());
|
||||||
|
size_t slotSize = static_cast<size_t>(slotSizeAttr.getInt());
|
||||||
|
plan.entries.push_back({allocOp.getResult(), {address, static_cast<size_t>(*checkedSize)}});
|
||||||
|
plan.logicalBytes += *checkedSize;
|
||||||
|
|
||||||
|
auto [slotIt, inserted] = slotIndices.try_emplace(slotId, plan.slots.size());
|
||||||
|
if (inserted) {
|
||||||
|
plan.slots.push_back({slotId, 0, slotSize});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const PhysicalSlotInfo& existing = plan.slots[slotIt->second];
|
||||||
|
if (existing.size != slotSize) {
|
||||||
|
allocOp.emitOpError("has a PIM local-memory arena inconsistent with another allocation");
|
||||||
|
hasFailure = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (hasFailure)
|
||||||
|
return failure();
|
||||||
|
llvm::sort(plan.slots, [](const PhysicalSlotInfo& lhs, const PhysicalSlotInfo& rhs) { return lhs.id < rhs.id; });
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
|
||||||
struct CoreEmissionResult {
|
struct CoreEmissionResult {
|
||||||
static constexpr size_t kMaxStoredCodegenDiagnostics = 8;
|
static constexpr size_t kMaxStoredCodegenDiagnostics = 8;
|
||||||
|
|
||||||
@@ -882,10 +897,8 @@ struct CoreEmissionResult {
|
|||||||
OnnxMlirCompilerErrorCodes status = CompilerSuccess;
|
OnnxMlirCompilerErrorCodes status = CompilerSuccess;
|
||||||
MemoryReportRow reportRow;
|
MemoryReportRow reportRow;
|
||||||
llvm::SmallVector<ResolvedWeightView, 8> usedWeights;
|
llvm::SmallVector<ResolvedWeightView, 8> usedWeights;
|
||||||
MemoryPlanArtifacts livenessArtifacts;
|
|
||||||
llvm::SmallVector<DiagnosticRecord, kMaxStoredCodegenDiagnostics> diagnostics;
|
llvm::SmallVector<DiagnosticRecord, kMaxStoredCodegenDiagnostics> diagnostics;
|
||||||
size_t diagnosticCount = 0;
|
size_t diagnosticCount = 0;
|
||||||
|
|
||||||
void recordDiagnostic(Operation* op, StringRef message) {
|
void recordDiagnostic(Operation* op, StringRef message) {
|
||||||
++diagnosticCount;
|
++diagnosticCount;
|
||||||
if (diagnostics.size() < kMaxStoredCodegenDiagnostics)
|
if (diagnostics.size() < kMaxStoredCodegenDiagnostics)
|
||||||
@@ -1012,13 +1025,21 @@ static LogicalResult executeCompiledCorePlan(
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto emitBinary = [&](auto op, pim_binary::Opcode opcode) {
|
auto emitBinary = [&](auto op, pim_binary::Opcode opcode) {
|
||||||
coreCodeGen.emitBinaryVectorOp(opcode, op.getOutputBuffer(), op.getLhs(), op.getRhs(),
|
coreCodeGen.emitBinaryVectorOp(opcode,
|
||||||
getVectorByteSizeOrCrash(cast<ShapedType>(op.getLhs().getType())), knowledge);
|
op.getOutputBuffer(),
|
||||||
|
op.getLhs(),
|
||||||
|
op.getRhs(),
|
||||||
|
getVectorByteSizeOrCrash(cast<ShapedType>(op.getLhs().getType())),
|
||||||
|
knowledge);
|
||||||
};
|
};
|
||||||
auto emitUnary = [&](auto op, pim_binary::Opcode opcode, int32_t r2OrImm, int32_t generic1) {
|
auto emitUnary = [&](auto op, pim_binary::Opcode opcode, int32_t r2OrImm, int32_t generic1) {
|
||||||
coreCodeGen.emitUnaryVectorOp(opcode, op.getOutputBuffer(), op.getInput(),
|
coreCodeGen.emitUnaryVectorOp(opcode,
|
||||||
|
op.getOutputBuffer(),
|
||||||
|
op.getInput(),
|
||||||
getVectorByteSizeOrCrash(cast<ShapedType>(op.getInput().getType())),
|
getVectorByteSizeOrCrash(cast<ShapedType>(op.getInput().getType())),
|
||||||
knowledge, r2OrImm, generic1);
|
knowledge,
|
||||||
|
r2OrImm,
|
||||||
|
generic1);
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (node.opKind) {
|
switch (node.opKind) {
|
||||||
@@ -1038,20 +1059,19 @@ static LogicalResult executeCompiledCorePlan(
|
|||||||
else
|
else
|
||||||
return failure();
|
return failure();
|
||||||
break;
|
break;
|
||||||
case CompiledCoreOpKind::Transpose:
|
case CompiledCoreOpKind::Transpose: coreCodeGen.codeGenTransposeOp(*node.transposePlan, knowledge); break;
|
||||||
coreCodeGen.codeGenTransposeOp(*node.transposePlan, knowledge);
|
case CompiledCoreOpKind::VVAdd: emitBinary(cast<pim::PimVVAddOp>(node.op), pim_binary::Opcode::vvadd); break;
|
||||||
break;
|
case CompiledCoreOpKind::VVSub: emitBinary(cast<pim::PimVVSubOp>(node.op), pim_binary::Opcode::vvsub); break;
|
||||||
case CompiledCoreOpKind::VVAdd: emitBinary(cast<pim::PimVVAddOp>(node.op), pim_binary::Opcode::vvadd); break;
|
case CompiledCoreOpKind::VVMul: emitBinary(cast<pim::PimVVMulOp>(node.op), pim_binary::Opcode::vvmul); break;
|
||||||
case CompiledCoreOpKind::VVSub: emitBinary(cast<pim::PimVVSubOp>(node.op), pim_binary::Opcode::vvsub); break;
|
case CompiledCoreOpKind::VVMax: emitBinary(cast<pim::PimVVMaxOp>(node.op), pim_binary::Opcode::vvmax); break;
|
||||||
case CompiledCoreOpKind::VVMul: emitBinary(cast<pim::PimVVMulOp>(node.op), pim_binary::Opcode::vvmul); break;
|
case CompiledCoreOpKind::VVDMul: emitBinary(cast<pim::PimVVDMulOp>(node.op), pim_binary::Opcode::vvdmul); break;
|
||||||
case CompiledCoreOpKind::VVMax: emitBinary(cast<pim::PimVVMaxOp>(node.op), pim_binary::Opcode::vvmax); break;
|
case CompiledCoreOpKind::VAvg: emitUnary(cast<pim::PimVAvgOp>(node.op), pim_binary::Opcode::vavg, 1, 1); break;
|
||||||
case CompiledCoreOpKind::VVDMul: emitBinary(cast<pim::PimVVDMulOp>(node.op), pim_binary::Opcode::vvdmul); break;
|
case CompiledCoreOpKind::VRelu: emitUnary(cast<pim::PimVReluOp>(node.op), pim_binary::Opcode::vrelu, 0, 0); break;
|
||||||
case CompiledCoreOpKind::VAvg: emitUnary(cast<pim::PimVAvgOp>(node.op), pim_binary::Opcode::vavg, 1, 1); break;
|
case CompiledCoreOpKind::VTanh: emitUnary(cast<pim::PimVTanhOp>(node.op), pim_binary::Opcode::vtanh, 0, 0); break;
|
||||||
case CompiledCoreOpKind::VRelu: emitUnary(cast<pim::PimVReluOp>(node.op), pim_binary::Opcode::vrelu, 0, 0); break;
|
case CompiledCoreOpKind::VSigm: emitUnary(cast<pim::PimVSigmOp>(node.op), pim_binary::Opcode::vsigm, 0, 0); break;
|
||||||
case CompiledCoreOpKind::VTanh: emitUnary(cast<pim::PimVTanhOp>(node.op), pim_binary::Opcode::vtanh, 0, 0); break;
|
|
||||||
case CompiledCoreOpKind::VSigm: emitUnary(cast<pim::PimVSigmOp>(node.op), pim_binary::Opcode::vsigm, 0, 0); break;
|
|
||||||
case CompiledCoreOpKind::VSoftmax:
|
case CompiledCoreOpKind::VSoftmax:
|
||||||
emitUnary(cast<pim::PimVSoftmaxOp>(node.op), pim_binary::Opcode::vsoftmax, 0, 0); break;
|
emitUnary(cast<pim::PimVSoftmaxOp>(node.op), pim_binary::Opcode::vsoftmax, 0, 0);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return success();
|
return success();
|
||||||
@@ -1103,8 +1123,7 @@ static void aliasMaterializedHostGlobals(CoreLikeOpTy coreLikeOp,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static OnnxMlirCompilerErrorCodes emitEmptyCoreArtifacts(StringRef outputDirPath, size_t emittedCoreId) {
|
static OnnxMlirCompilerErrorCodes emitEmptyCoreArtifacts(StringRef outputDirPath, size_t emittedCoreId) {
|
||||||
std::string outputCorePath =
|
std::string outputCorePath = (outputDirPath + "/core_" + std::to_string(emittedCoreId) + ".pim").str();
|
||||||
(outputDirPath + "/core_" + std::to_string(emittedCoreId) + ".pim").str();
|
|
||||||
std::error_code errorCode;
|
std::error_code errorCode;
|
||||||
raw_fd_ostream coreBinaryStream(outputCorePath, errorCode, sys::fs::OF_None);
|
raw_fd_ostream coreBinaryStream(outputCorePath, errorCode, sys::fs::OF_None);
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
@@ -1128,8 +1147,7 @@ static OnnxMlirCompilerErrorCodes emitEmptyCoreArtifacts(StringRef outputDirPath
|
|||||||
if (!pimEmitJson.getValue())
|
if (!pimEmitJson.getValue())
|
||||||
return CompilerSuccess;
|
return CompilerSuccess;
|
||||||
|
|
||||||
std::string outputCoreJsonPath =
|
std::string outputCoreJsonPath = (outputDirPath + "/core_" + std::to_string(emittedCoreId) + ".json").str();
|
||||||
(outputDirPath + "/core_" + std::to_string(emittedCoreId) + ".json").str();
|
|
||||||
errorCode = std::error_code();
|
errorCode = std::error_code();
|
||||||
raw_fd_ostream coreJsonStream(outputCoreJsonPath, errorCode);
|
raw_fd_ostream coreJsonStream(outputCoreJsonPath, errorCode);
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
@@ -1169,18 +1187,27 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
|||||||
SmallDenseMap<memref::GlobalOp, MemEntry, 16> materializedHostGlobals =
|
SmallDenseMap<memref::GlobalOp, MemEntry, 16> materializedHostGlobals =
|
||||||
collectMaterializedHostGlobals(moduleOp, funcOp, memory);
|
collectMaterializedHostGlobals(moduleOp, funcOp, memory);
|
||||||
llvm::DenseMap<Operation*, std::unique_ptr<CompiledCoreProgram>> compiledPrograms;
|
llvm::DenseMap<Operation*, std::unique_ptr<CompiledCoreProgram>> compiledPrograms;
|
||||||
|
llvm::DenseMap<Operation*, std::unique_ptr<CompiledCoreMemoryPlan>> memoryPlans;
|
||||||
for (Operation* op : coreLikeOps) {
|
for (Operation* op : coreLikeOps) {
|
||||||
auto program = std::make_unique<CompiledCoreProgram>();
|
auto program = std::make_unique<CompiledCoreProgram>();
|
||||||
if (failed(compileCoreProgram(op, *program)))
|
if (failed(compileCoreProgram(op, *program)))
|
||||||
return CompilerFailure;
|
return CompilerFailure;
|
||||||
compiledPrograms.try_emplace(op, std::move(program));
|
compiledPrograms.try_emplace(op, std::move(program));
|
||||||
|
auto memoryPlan = compileCoreMemoryPlan(op);
|
||||||
|
if (failed(memoryPlan))
|
||||||
|
return CompilerFailure;
|
||||||
|
memoryPlans.try_emplace(op, std::make_unique<CompiledCoreMemoryPlan>(std::move(*memoryPlan)));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto getCompiledProgram = [&](Operation* op) {
|
auto getCompiledProgram = [&](Operation* op) {
|
||||||
auto it = compiledPrograms.find(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();
|
return it->second.get();
|
||||||
};
|
};
|
||||||
|
auto getMemoryPlan = [&](Operation* op) {
|
||||||
|
auto it = memoryPlans.find(op);
|
||||||
|
assert(it != memoryPlans.end() && "missing PIM core memory plan");
|
||||||
|
return it->second.get();
|
||||||
|
};
|
||||||
|
|
||||||
llvm::DenseMap<size_t, size_t> emittedCoreIds;
|
llvm::DenseMap<size_t, size_t> emittedCoreIds;
|
||||||
size_t nextEmittedCoreId = 0;
|
size_t nextEmittedCoreId = 0;
|
||||||
@@ -1210,6 +1237,7 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
|||||||
CoreEmissionJob job;
|
CoreEmissionJob job;
|
||||||
job.coreLikeOp = coreOp;
|
job.coreLikeOp = coreOp;
|
||||||
job.program = getCompiledProgram(op);
|
job.program = getCompiledProgram(op);
|
||||||
|
job.memoryPlan = getMemoryPlan(op);
|
||||||
job.emittedCoreId = emittedCoreIds.lookup(originalCoreId);
|
job.emittedCoreId = emittedCoreIds.lookup(originalCoreId);
|
||||||
jobs.push_back(std::move(job));
|
jobs.push_back(std::move(job));
|
||||||
continue;
|
continue;
|
||||||
@@ -1229,6 +1257,7 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
|||||||
CoreEmissionJob job;
|
CoreEmissionJob job;
|
||||||
job.coreLikeOp = coreBatchOp;
|
job.coreLikeOp = coreBatchOp;
|
||||||
job.program = getCompiledProgram(op);
|
job.program = getCompiledProgram(op);
|
||||||
|
job.memoryPlan = getMemoryPlan(op);
|
||||||
job.emittedCoreId = emittedCoreIds.lookup(originalCoreId);
|
job.emittedCoreId = emittedCoreIds.lookup(originalCoreId);
|
||||||
job.lanes = lanesByCoreId.lookup(originalCoreId);
|
job.lanes = lanesByCoreId.lookup(originalCoreId);
|
||||||
job.batchReportId = nextBatchReportId;
|
job.batchReportId = nextBatchReportId;
|
||||||
@@ -1332,17 +1361,16 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
|||||||
if (auto coreOp = dyn_cast<pim::PimCoreOp>(job.coreLikeOp)) {
|
if (auto coreOp = dyn_cast<pim::PimCoreOp>(job.coreLikeOp)) {
|
||||||
aliasMaterializedHostGlobals(coreOp, moduleOp, materializedHostGlobals, jobMemory);
|
aliasMaterializedHostGlobals(coreOp, moduleOp, materializedHostGlobals, jobMemory);
|
||||||
auto& deviceMemory = jobMemory.getOrCreateDeviceMem(job.emittedCoreId);
|
auto& deviceMemory = jobMemory.getOrCreateDeviceMem(job.emittedCoreId);
|
||||||
deviceMemory.allocateCore(coreOp);
|
deviceMemory.allocateCore(*job.memoryPlan);
|
||||||
|
|
||||||
StaticValueKnowledge knowledge = seedCoreCodegenKnowledge(coreOp);
|
StaticValueKnowledge knowledge = seedCoreCodegenKnowledge(coreOp);
|
||||||
if (failed(executeCompiledCoreProgram(*job.program, coreCodeGen, knowledge, resolveWeightSlot))) {
|
if (failed(executeCompiledCoreProgram(*job.program, coreCodeGen, knowledge, resolveWeightSlot))) {
|
||||||
(void)finalizeInstructions();
|
(void) finalizeInstructions();
|
||||||
result.status = CompilerFailure;
|
result.status = CompilerFailure;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
result.reportRow = deviceMemory.getReportRow();
|
result.reportRow = deviceMemory.getReportRow();
|
||||||
result.usedWeights = std::move(usedWeights);
|
result.usedWeights = std::move(usedWeights);
|
||||||
result.livenessArtifacts = deviceMemory.getLivenessArtifacts();
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
auto coreBatchOp = cast<pim::PimCoreBatchOp>(job.coreLikeOp);
|
auto coreBatchOp = cast<pim::PimCoreBatchOp>(job.coreLikeOp);
|
||||||
@@ -1352,10 +1380,10 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
|||||||
for (unsigned lane : job.lanes) {
|
for (unsigned lane : job.lanes) {
|
||||||
StaticValueKnowledge knowledge = seedCoreBatchCodegenKnowledge(coreBatchOp, lane);
|
StaticValueKnowledge knowledge = seedCoreBatchCodegenKnowledge(coreBatchOp, lane);
|
||||||
|
|
||||||
deviceMemory.allocateCore(coreBatchOp, lane);
|
deviceMemory.allocateCore(*job.memoryPlan, lane);
|
||||||
coreCodeGen.setBatchLane(lane);
|
coreCodeGen.setBatchLane(lane);
|
||||||
if (failed(executeCompiledCoreProgram(*job.program, coreCodeGen, knowledge, resolveWeightSlot))) {
|
if (failed(executeCompiledCoreProgram(*job.program, coreCodeGen, knowledge, resolveWeightSlot))) {
|
||||||
(void)finalizeInstructions();
|
(void) finalizeInstructions();
|
||||||
result.status = CompilerFailure;
|
result.status = CompilerFailure;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -1363,7 +1391,6 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
|||||||
|
|
||||||
result.reportRow = deviceMemory.getReportRow();
|
result.reportRow = deviceMemory.getReportRow();
|
||||||
result.usedWeights = std::move(usedWeights);
|
result.usedWeights = std::move(usedWeights);
|
||||||
result.livenessArtifacts = deviceMemory.getLivenessArtifacts();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!finalizeInstructions()) {
|
if (!finalizeInstructions()) {
|
||||||
@@ -1388,9 +1415,8 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
|||||||
for (const CoreEmissionResult& result : jobResults) {
|
for (const CoreEmissionResult& result : jobResults) {
|
||||||
if (!summaryAnchor && !result.diagnostics.empty())
|
if (!summaryAnchor && !result.diagnostics.empty())
|
||||||
summaryAnchor = result.diagnostics.front().op;
|
summaryAnchor = result.diagnostics.front().op;
|
||||||
for (const CoreEmissionResult::DiagnosticRecord& diagnostic : result.diagnostics) {
|
for (const CoreEmissionResult::DiagnosticRecord& diagnostic : result.diagnostics)
|
||||||
diagnostics.report(diagnostic.op, [&](Operation* op) { op->emitError() << diagnostic.message; });
|
diagnostics.report(diagnostic.op, [&](Operation* op) { op->emitError() << diagnostic.message; });
|
||||||
}
|
|
||||||
size_t unreportedCount = result.diagnosticCount - result.diagnostics.size();
|
size_t unreportedCount = result.diagnosticCount - result.diagnostics.size();
|
||||||
diagnostics.noteFailures(static_cast<int64_t>(unreportedCount));
|
diagnostics.noteFailures(static_cast<int64_t>(unreportedCount));
|
||||||
}
|
}
|
||||||
@@ -1420,19 +1446,6 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
|||||||
auto weightEmission = createAndPopulateWeightFolder(weightRequests, outputDirPath);
|
auto weightEmission = createAndPopulateWeightFolder(weightRequests, outputDirPath);
|
||||||
memory.setTotalWeightBytes(weightEmission.totalWeightBytes);
|
memory.setTotalWeightBytes(weightEmission.totalWeightBytes);
|
||||||
auto& mapCoreWeightToFileName = weightEmission.mapCoreWeightToFileName;
|
auto& mapCoreWeightToFileName = weightEmission.mapCoreWeightToFileName;
|
||||||
if (std::string reportsRoot = getOutputDir(); !reportsRoot.empty()) {
|
|
||||||
std::string reportsDir = reportsRoot + "/reports";
|
|
||||||
sys::fs::remove(reportsDir + "/pim_memory_liveness_report.txt");
|
|
||||||
sys::fs::remove(reportsDir + "/pim_memory_liveness_report.json");
|
|
||||||
sys::fs::remove(reportsDir + "/pim_memory_liveness_timeline.dot");
|
|
||||||
}
|
|
||||||
std::fstream livenessReportFile;
|
|
||||||
std::unique_ptr<llvm::raw_os_ostream> livenessReportOs;
|
|
||||||
if (pimMemoryReport != PimMemoryReportNone) {
|
|
||||||
livenessReportFile = openReportFileWithExtension("pim_memory_liveness_report", "txt");
|
|
||||||
livenessReportOs = std::make_unique<llvm::raw_os_ostream>(livenessReportFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t jobIndex = 0; jobIndex < jobs.size(); ++jobIndex) {
|
for (size_t jobIndex = 0; jobIndex < jobs.size(); ++jobIndex) {
|
||||||
const CoreEmissionJob& job = jobs[jobIndex];
|
const CoreEmissionJob& job = jobs[jobIndex];
|
||||||
const CoreEmissionResult& result = jobResults[jobIndex];
|
const CoreEmissionResult& result = jobResults[jobIndex];
|
||||||
@@ -1443,8 +1456,6 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
|||||||
return err;
|
return err;
|
||||||
xbarsPerArrayGroup["core" + std::to_string(job.emittedCoreId)] = std::move(xbarsPerGroup);
|
xbarsPerArrayGroup["core" + std::to_string(job.emittedCoreId)] = std::move(xbarsPerGroup);
|
||||||
memory.recordCoreReport(job.emittedCoreId, result.reportRow);
|
memory.recordCoreReport(job.emittedCoreId, result.reportRow);
|
||||||
if (livenessReportFile.is_open())
|
|
||||||
*livenessReportOs << "Core " << job.emittedCoreId << ":\n" << result.livenessArtifacts;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1473,18 +1484,10 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
|||||||
batchPerCoreRow.value_or(MemoryReportRow {}),
|
batchPerCoreRow.value_or(MemoryReportRow {}),
|
||||||
batchRow.numAlloca,
|
batchRow.numAlloca,
|
||||||
batchRow.sizeAlloca);
|
batchRow.sizeAlloca);
|
||||||
if (livenessReportFile.is_open())
|
|
||||||
for (size_t jobIndex : group)
|
|
||||||
*livenessReportOs << "Batch " << batchReportId << " core " << jobs[jobIndex].emittedCoreId << ":\n"
|
|
||||||
<< jobResults[jobIndex].livenessArtifacts;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
maxCoreId = nextEmittedCoreId == 0 ? 0 : nextEmittedCoreId - 1;
|
maxCoreId = nextEmittedCoreId == 0 ? 0 : nextEmittedCoreId - 1;
|
||||||
|
|
||||||
if (livenessReportFile.is_open()) {
|
|
||||||
livenessReportOs->flush();
|
|
||||||
livenessReportFile.close();
|
|
||||||
}
|
|
||||||
memory.flushReport();
|
memory.flushReport();
|
||||||
return writeConfigJson(funcOp, memory, maxCoreId, std::move(xbarsPerArrayGroup), outputDirPath);
|
return writeConfigJson(funcOp, memory, maxCoreId, std::move(xbarsPerArrayGroup), outputDirPath);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,8 +39,6 @@ struct PhysicalSlotInfo {
|
|||||||
size_t size = 0;
|
size_t size = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
using MemoryPlanArtifacts = std::string;
|
|
||||||
|
|
||||||
struct MemoryValueKey {
|
struct MemoryValueKey {
|
||||||
mlir::Value value;
|
mlir::Value value;
|
||||||
std::optional<unsigned> lane;
|
std::optional<unsigned> lane;
|
||||||
@@ -48,15 +46,33 @@ struct MemoryValueKey {
|
|||||||
bool operator==(const MemoryValueKey& other) const { return value == other.value && lane == other.lane; }
|
bool operator==(const MemoryValueKey& other) const { return value == other.value && lane == other.lane; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct CompiledLocalMemoryEntry {
|
||||||
|
mlir::Value value;
|
||||||
|
MemEntry memory;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CompiledCoreMemoryPlan {
|
||||||
|
llvm::SmallVector<CompiledLocalMemoryEntry, 32> entries;
|
||||||
|
llvm::SmallVector<PhysicalSlotInfo, 32> slots;
|
||||||
|
uint64_t logicalBytes = 0;
|
||||||
|
uint64_t fallbackIntervals = 0;
|
||||||
|
uint64_t nestedSingleUseIntervals = 0;
|
||||||
|
};
|
||||||
|
|
||||||
struct MemoryReportRow {
|
struct MemoryReportRow {
|
||||||
uint64_t numAlloca = 0;
|
uint64_t numAlloca = 0;
|
||||||
uint64_t sizeAlloca = 0;
|
uint64_t sizeAlloca = 0;
|
||||||
uint64_t numGlobal = 0;
|
uint64_t numGlobal = 0;
|
||||||
uint64_t sizeGlobal = 0;
|
uint64_t sizeGlobal = 0;
|
||||||
|
uint64_t logicalAllocaBytes = 0;
|
||||||
|
uint64_t fallbackIntervals = 0;
|
||||||
|
uint64_t nestedSingleUseIntervals = 0;
|
||||||
|
|
||||||
bool operator==(const MemoryReportRow& other) const {
|
bool operator==(const MemoryReportRow& other) const {
|
||||||
return numAlloca == other.numAlloca && sizeAlloca == other.sizeAlloca && numGlobal == other.numGlobal
|
return numAlloca == other.numAlloca && sizeAlloca == other.sizeAlloca && numGlobal == other.numGlobal
|
||||||
&& sizeGlobal == other.sizeGlobal;
|
&& sizeGlobal == other.sizeGlobal && logicalAllocaBytes == other.logicalAllocaBytes
|
||||||
|
&& fallbackIntervals == other.fallbackIntervals
|
||||||
|
&& nestedSingleUseIntervals == other.nestedSingleUseIntervals;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -93,26 +109,22 @@ class PimMemory {
|
|||||||
llvm::SmallDenseMap<MemoryValueKey, MemEntry, 32>& globalMemEntriesMap;
|
llvm::SmallDenseMap<MemoryValueKey, MemEntry, 32>& globalMemEntriesMap;
|
||||||
llvm::SmallDenseMap<MemoryValueKey, MemEntry, 32> ownedMemEntriesMap;
|
llvm::SmallDenseMap<MemoryValueKey, MemEntry, 32> ownedMemEntriesMap;
|
||||||
MemoryReportRow reportRow;
|
MemoryReportRow reportRow;
|
||||||
MemoryPlanArtifacts livenessArtifacts;
|
|
||||||
|
|
||||||
size_t minAlignment = 4;
|
size_t minAlignment = 4;
|
||||||
size_t firstAvailableAddress = 0;
|
size_t firstAvailableAddress = 0;
|
||||||
size_t nextPhysicalSlotId = 0;
|
|
||||||
|
|
||||||
MemEntry* gatherMemEntry(mlir::Value value, std::optional<unsigned> lane = std::nullopt);
|
MemEntry* gatherMemEntry(mlir::Value value, std::optional<unsigned> lane = std::nullopt);
|
||||||
size_t allocateAddress(size_t size, const MemoryValueKey& key);
|
size_t allocateAddress(size_t size, const MemoryValueKey& key);
|
||||||
void allocateGatheredMemory();
|
void allocateGatheredMemory();
|
||||||
void allocateMemoryForValue(const MemoryValueKey& key, MemEntry& memEntry, MemoryReportKind reportKind);
|
void allocateMemoryForValue(const MemoryValueKey& key, MemEntry& memEntry, MemoryReportKind reportKind);
|
||||||
PhysicalSlotInfo allocatePhysicalSlot(size_t slotSize, const MemoryValueKey& key);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PimMemory(llvm::SmallDenseMap<MemoryValueKey, MemEntry, 32>& globalMemEntriesMap)
|
PimMemory(llvm::SmallDenseMap<MemoryValueKey, MemEntry, 32>& globalMemEntriesMap)
|
||||||
: globalMemEntriesMap(globalMemEntriesMap) {}
|
: globalMemEntriesMap(globalMemEntriesMap) {}
|
||||||
|
|
||||||
void allocateHost(mlir::ModuleOp moduleOp, mlir::func::FuncOp funcOp);
|
void allocateHost(mlir::ModuleOp moduleOp, mlir::func::FuncOp funcOp);
|
||||||
void allocateCore(mlir::Operation* op, std::optional<unsigned> lane = std::nullopt);
|
void allocateCore(const CompiledCoreMemoryPlan& plan, std::optional<unsigned> lane = std::nullopt);
|
||||||
MemoryReportRow getReportRow() const;
|
MemoryReportRow getReportRow() const;
|
||||||
const MemoryPlanArtifacts& getLivenessArtifacts() const { return livenessArtifacts; }
|
|
||||||
|
|
||||||
size_t getFirstAvailableAddress() const { return firstAvailableAddress; }
|
size_t getFirstAvailableAddress() const { return firstAvailableAddress; }
|
||||||
MemEntry getMemEntry(const MemoryValueKey& key) const;
|
MemEntry getMemEntry(const MemoryValueKey& key) const;
|
||||||
@@ -160,6 +172,7 @@ public:
|
|||||||
struct CoreEmissionJob {
|
struct CoreEmissionJob {
|
||||||
mlir::Operation* coreLikeOp = nullptr;
|
mlir::Operation* coreLikeOp = nullptr;
|
||||||
const CompiledCoreProgram* program = nullptr;
|
const CompiledCoreProgram* program = nullptr;
|
||||||
|
const CompiledCoreMemoryPlan* memoryPlan = nullptr;
|
||||||
size_t emittedCoreId = 0;
|
size_t emittedCoreId = 0;
|
||||||
llvm::SmallVector<unsigned, 4> lanes;
|
llvm::SmallVector<unsigned, 4> lanes;
|
||||||
std::optional<uint64_t> batchReportId;
|
std::optional<uint64_t> batchReportId;
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ llvm::cl::opt<bool>
|
|||||||
|
|
||||||
llvm::cl::opt<bool>
|
llvm::cl::opt<bool>
|
||||||
pimDisableMemoryCoalescing("pim-disable-memory-coalescing",
|
pimDisableMemoryCoalescing("pim-disable-memory-coalescing",
|
||||||
llvm::cl::desc("Skip the PIM memory coalescing pass (developer diagnostic option)"),
|
llvm::cl::desc("Skip the early PIM IR memory coalescing pass (developer diagnostic)"),
|
||||||
llvm::cl::init(false),
|
llvm::cl::init(false),
|
||||||
llvm::cl::cat(OnnxMlirOptions));
|
llvm::cl::cat(OnnxMlirOptions));
|
||||||
|
|
||||||
@@ -124,10 +124,10 @@ llvm::cl::opt<bool> pimTraceCommunicationMaterialization(
|
|||||||
llvm::cl::cat(OnnxMlirOptions));
|
llvm::cl::cat(OnnxMlirOptions));
|
||||||
|
|
||||||
llvm::cl::opt<size_t>
|
llvm::cl::opt<size_t>
|
||||||
crossbarSize("crossbar-size", llvm::cl::desc("Width and height of a single crossbar"), llvm::cl::init(2));
|
crossbarSize("crossbar-size", llvm::cl::desc("Width and height of a single crossbar"), llvm::cl::init(128));
|
||||||
|
|
||||||
llvm::cl::opt<size_t>
|
llvm::cl::opt<size_t>
|
||||||
crossbarCountInCore("crossbar-count", llvm::cl::desc("Number of crossbars in each core"), llvm::cl::init(256));
|
crossbarCountInCore("crossbar-count", llvm::cl::desc("Number of crossbars in each core"), llvm::cl::init(64));
|
||||||
|
|
||||||
llvm::cl::opt<long> coresCount("core-count",
|
llvm::cl::opt<long> coresCount("core-count",
|
||||||
llvm::cl::desc("Number of cores in the chip. Required for PIM compilation."),
|
llvm::cl::desc("Number of cores in the chip. Required for PIM compilation."),
|
||||||
|
|||||||
@@ -53,8 +53,8 @@ extern llvm::cl::opt<PimMemoryReportLevel> pimMemoryReport;
|
|||||||
extern llvm::cl::opt<PimConvLoweringType> pimConvLowering;
|
extern llvm::cl::opt<PimConvLoweringType> pimConvLowering;
|
||||||
extern llvm::cl::opt<PimSpatialDataflowExportType> pimExportSpatialDataflow;
|
extern llvm::cl::opt<PimSpatialDataflowExportType> pimExportSpatialDataflow;
|
||||||
|
|
||||||
extern llvm::cl::opt<bool> pimOnlyCodegen;
|
|
||||||
extern llvm::cl::opt<bool> pimDisableMemoryCoalescing;
|
extern llvm::cl::opt<bool> pimDisableMemoryCoalescing;
|
||||||
|
extern llvm::cl::opt<bool> pimOnlyCodegen;
|
||||||
extern llvm::cl::opt<bool> useExperimentalConvImpl;
|
extern llvm::cl::opt<bool> useExperimentalConvImpl;
|
||||||
extern llvm::cl::opt<bool> pimEmitJson;
|
extern llvm::cl::opt<bool> pimEmitJson;
|
||||||
extern llvm::cl::opt<bool> pimReportConvLowering;
|
extern llvm::cl::opt<bool> pimReportConvLowering;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ void addPassesPim(OwningOpRef<ModuleOp>& module,
|
|||||||
verifyExplicitPimCoreCount();
|
verifyExplicitPimCoreCount();
|
||||||
|
|
||||||
if (pimOnlyCodegen) {
|
if (pimOnlyCodegen) {
|
||||||
|
pm.addPass(createPimLocalMemoryPlanningPass());
|
||||||
pm.addPass(createEmitPimCodePass());
|
pm.addPass(createEmitPimCodePass());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -53,6 +54,8 @@ void addPassesPim(OwningOpRef<ModuleOp>& module,
|
|||||||
pm.addPass(createMessagePass("Pim host constants folded"));
|
pm.addPass(createMessagePass("Pim host constants folded"));
|
||||||
if (!pimDisableMemoryCoalescing)
|
if (!pimDisableMemoryCoalescing)
|
||||||
pm.addPass(createPimMemoryCoalescingPass());
|
pm.addPass(createPimMemoryCoalescingPass());
|
||||||
|
pm.addPass(createPimLocalMemoryPlanningPass());
|
||||||
|
pm.addPass(createMessagePass("Pim local memory planned"));
|
||||||
pm.addPass(createPimVerificationPass());
|
pm.addPass(createPimVerificationPass());
|
||||||
pm.addPass(createMessagePass("Pim verified"));
|
pm.addPass(createMessagePass("Pim verified"));
|
||||||
pm.addPass(createEmitPimCodePass());
|
pm.addPass(createEmitPimCodePass());
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
add_pim_library(OMPimLocalMemoryLifetimeAnalysis
|
||||||
|
LocalMemoryLifetimeAnalysis.cpp
|
||||||
|
|
||||||
|
EXCLUDE_FROM_OM_LIBS
|
||||||
|
|
||||||
|
INCLUDE_DIRS PUBLIC
|
||||||
|
${PIM_PUBLIC_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
||||||
|
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||||
|
#include "mlir/Interfaces/DestinationStyleOpInterface.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/SmallPtrSet.h"
|
||||||
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
|
||||||
|
#include "src/Accelerators/PIM/Dialect/Pim/Analysis/LocalMemoryLifetimeAnalysis.hpp"
|
||||||
|
|
||||||
|
using namespace mlir;
|
||||||
|
|
||||||
|
namespace onnx_mlir {
|
||||||
|
namespace pim {
|
||||||
|
|
||||||
|
bool isLocalMemoryAliasOp(Operation* op) {
|
||||||
|
return isa<memref::SubViewOp, memref::CastOp, memref::CollapseShapeOp, memref::ExpandShapeOp>(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
LogicalResult walkLocalMemoryUses(Value root,
|
||||||
|
llvm::function_ref<LogicalResult(Value, Operation*)> visitUser) {
|
||||||
|
llvm::SmallPtrSet<Value, 16> visitedValues;
|
||||||
|
llvm::SmallPtrSet<Operation*, 32> visitedUsers;
|
||||||
|
llvm::SmallVector<Value> pendingValues {root};
|
||||||
|
auto addAlias = [&](Value value) { pendingValues.push_back(value); };
|
||||||
|
|
||||||
|
while (!pendingValues.empty()) {
|
||||||
|
Value value = pendingValues.pop_back_val();
|
||||||
|
if (!visitedValues.insert(value).second)
|
||||||
|
continue;
|
||||||
|
for (Operation* user : value.getUsers()) {
|
||||||
|
if (!visitedUsers.insert(user).second)
|
||||||
|
continue;
|
||||||
|
if (failed(visitUser(value, user)))
|
||||||
|
return failure();
|
||||||
|
|
||||||
|
if (isLocalMemoryAliasOp(user))
|
||||||
|
for (Value result : user->getResults())
|
||||||
|
addAlias(result);
|
||||||
|
|
||||||
|
if (auto dpsOp = dyn_cast<DestinationStyleOpInterface>(user))
|
||||||
|
for (OpResult result : user->getResults())
|
||||||
|
if (OpOperand* tied = dpsOp.getTiedOpOperand(result); tied && tied->get() == value)
|
||||||
|
addAlias(result);
|
||||||
|
|
||||||
|
if (auto forOp = dyn_cast<scf::ForOp>(user))
|
||||||
|
for (auto [index, initArg] : llvm::enumerate(forOp.getInitArgs()))
|
||||||
|
if (initArg == value) {
|
||||||
|
addAlias(forOp.getRegionIterArgs()[index]);
|
||||||
|
addAlias(forOp.getResult(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto yieldOp = dyn_cast<scf::YieldOp>(user);
|
||||||
|
if (!yieldOp)
|
||||||
|
continue;
|
||||||
|
for (auto [index, operand] : llvm::enumerate(yieldOp.getOperands())) {
|
||||||
|
if (operand != value)
|
||||||
|
continue;
|
||||||
|
if (auto forOp = dyn_cast<scf::ForOp>(yieldOp->getParentOp()))
|
||||||
|
addAlias(forOp.getResult(index));
|
||||||
|
else if (auto ifOp = dyn_cast<scf::IfOp>(yieldOp->getParentOp()))
|
||||||
|
addAlias(ifOp.getResult(index));
|
||||||
|
else if (auto switchOp = dyn_cast<scf::IndexSwitchOp>(yieldOp->getParentOp()))
|
||||||
|
addAlias(switchOp.getResult(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pim
|
||||||
|
} // namespace onnx_mlir
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "mlir/IR/Operation.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/STLFunctionalExtras.h"
|
||||||
|
|
||||||
|
namespace onnx_mlir {
|
||||||
|
namespace pim {
|
||||||
|
|
||||||
|
bool isLocalMemoryAliasOp(mlir::Operation* op);
|
||||||
|
|
||||||
|
mlir::LogicalResult walkLocalMemoryUses(
|
||||||
|
mlir::Value root,
|
||||||
|
llvm::function_ref<mlir::LogicalResult(mlir::Value, mlir::Operation*)> visitUser);
|
||||||
|
|
||||||
|
} // namespace pim
|
||||||
|
} // namespace onnx_mlir
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
add_onnx_mlir_dialect(Pim pim)
|
add_onnx_mlir_dialect(Pim pim)
|
||||||
add_onnx_mlir_dialect_doc(pim Pim.td)
|
add_onnx_mlir_dialect_doc(pim Pim.td)
|
||||||
|
|
||||||
|
add_subdirectory(Analysis)
|
||||||
add_subdirectory(Transforms/Bufferization)
|
add_subdirectory(Transforms/Bufferization)
|
||||||
add_subdirectory(Transforms/MemoryCoalescing)
|
|
||||||
add_subdirectory(Transforms/HostConstantFolding)
|
add_subdirectory(Transforms/HostConstantFolding)
|
||||||
|
add_subdirectory(Transforms/MemoryCoalescing)
|
||||||
|
add_subdirectory(Transforms/LocalMemoryPlanning)
|
||||||
add_subdirectory(Transforms/Verification)
|
add_subdirectory(Transforms/Verification)
|
||||||
|
|
||||||
add_pim_library(PimOps
|
add_pim_library(PimOps
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
add_pim_library(OMPimLocalMemoryPlanning
|
||||||
|
LocalMemoryPlanning.cpp
|
||||||
|
|
||||||
|
EXCLUDE_FROM_OM_LIBS
|
||||||
|
|
||||||
|
INCLUDE_DIRS PUBLIC
|
||||||
|
${PIM_PUBLIC_INCLUDE_DIRS}
|
||||||
|
|
||||||
|
LINK_LIBS PUBLIC
|
||||||
|
OMPimCommon
|
||||||
|
OMPimCompilerOptions
|
||||||
|
OMPimLocalMemoryLifetimeAnalysis
|
||||||
|
PimOps
|
||||||
|
)
|
||||||
+264
-340
@@ -1,14 +1,18 @@
|
|||||||
#include "mlir/Dialect/Bufferization/IR/BufferizableOpInterface.h"
|
#include "mlir/Dialect/Bufferization/IR/BufferizableOpInterface.h"
|
||||||
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
||||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||||
|
#include "mlir/Pass/Pass.h"
|
||||||
#include "mlir/IR/Value.h"
|
#include "mlir/IR/Value.h"
|
||||||
#include "mlir/Interfaces/DestinationStyleOpInterface.h"
|
|
||||||
|
|
||||||
#include "llvm/ADT/DenseMap.h"
|
#include "llvm/ADT/DenseMap.h"
|
||||||
#include "llvm/ADT/STLExtras.h"
|
#include "llvm/ADT/STLExtras.h"
|
||||||
#include "llvm/ADT/SmallPtrSet.h"
|
#include "llvm/Support/MathExtras.h"
|
||||||
|
#include "llvm/Support/FileSystem.h"
|
||||||
|
#include "llvm/Support/raw_os_ostream.h"
|
||||||
#include "llvm/Support/raw_ostream.h"
|
#include "llvm/Support/raw_ostream.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <memory>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
@@ -16,9 +20,13 @@
|
|||||||
|
|
||||||
#include "Common/Support/CheckedArithmetic.hpp"
|
#include "Common/Support/CheckedArithmetic.hpp"
|
||||||
#include "Common/Support/ReportUtils.hpp"
|
#include "Common/Support/ReportUtils.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||||
#include "src/Accelerators/PIM/Compiler/PimMemoryLiveness.hpp"
|
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Dialect/Pim/Analysis/LocalMemoryLifetimeAnalysis.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Dialect/Pim/Transforms/LocalMemoryPlanning/LocalMemoryPlanning.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Pass/PIMPasses.h"
|
||||||
|
|
||||||
using namespace llvm;
|
using namespace llvm;
|
||||||
using namespace mlir;
|
using namespace mlir;
|
||||||
@@ -26,19 +34,6 @@ using namespace onnx_mlir;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
static std::optional<unsigned> getLaneForMemoryValue(mlir::Value value, std::optional<unsigned> lane) {
|
|
||||||
if (!lane)
|
|
||||||
return std::nullopt;
|
|
||||||
auto allocOp = value.getDefiningOp<memref::AllocOp>();
|
|
||||||
if (!allocOp || !allocOp->getParentOfType<pim::PimCoreBatchOp>())
|
|
||||||
return std::nullopt;
|
|
||||||
return lane;
|
|
||||||
}
|
|
||||||
|
|
||||||
static MemoryValueKey getMemoryValueKey(mlir::Value value, std::optional<unsigned> lane = std::nullopt) {
|
|
||||||
return {value, getLaneForMemoryValue(value, lane)};
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MemoryTouchInterval {
|
struct MemoryTouchInterval {
|
||||||
uint64_t start = 0;
|
uint64_t start = 0;
|
||||||
uint64_t end = 0;
|
uint64_t end = 0;
|
||||||
@@ -51,7 +46,6 @@ struct MemoryTouchInterval {
|
|||||||
bool endUsedFallback = false;
|
bool endUsedFallback = false;
|
||||||
bool escapesLoop = false;
|
bool escapesLoop = false;
|
||||||
std::string fallbackReason;
|
std::string fallbackReason;
|
||||||
llvm::SmallVector<std::string, 8> aliasesFollowed;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct OperationOrdering {
|
struct OperationOrdering {
|
||||||
@@ -60,50 +54,6 @@ struct OperationOrdering {
|
|||||||
uint64_t nextPosition = 0;
|
uint64_t nextPosition = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::string printValueToString(mlir::Value value) {
|
|
||||||
std::string text;
|
|
||||||
llvm::raw_string_ostream os(text);
|
|
||||||
value.print(os);
|
|
||||||
os.flush();
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::string printOperationToString(Operation* op) {
|
|
||||||
if (!op)
|
|
||||||
return "<none>";
|
|
||||||
std::string text;
|
|
||||||
llvm::raw_string_ostream os(text);
|
|
||||||
op->print(os);
|
|
||||||
os.flush();
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::string printLocationToString(Location loc) {
|
|
||||||
std::string text;
|
|
||||||
llvm::raw_string_ostream os(text);
|
|
||||||
loc.print(os);
|
|
||||||
os.flush();
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::string collapseWhitespace(StringRef text) {
|
|
||||||
std::string out;
|
|
||||||
out.reserve(text.size());
|
|
||||||
bool lastWasSpace = false;
|
|
||||||
for (char c : text) {
|
|
||||||
bool isSpace = c == ' ' || c == '\n' || c == '\t' || c == '\r';
|
|
||||||
if (isSpace) {
|
|
||||||
if (!lastWasSpace && !out.empty())
|
|
||||||
out.push_back(' ');
|
|
||||||
lastWasSpace = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
out.push_back(c);
|
|
||||||
lastWasSpace = false;
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::string abbreviate(StringRef text, size_t maxLen) {
|
static std::string abbreviate(StringRef text, size_t maxLen) {
|
||||||
if (text.size() <= maxLen)
|
if (text.size() <= maxLen)
|
||||||
return text.str();
|
return text.str();
|
||||||
@@ -111,21 +61,23 @@ static std::string abbreviate(StringRef text, size_t maxLen) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static std::string summarizeValue(mlir::Value value, size_t maxLen = 72) {
|
static std::string summarizeValue(mlir::Value value, size_t maxLen = 72) {
|
||||||
return abbreviate(collapseWhitespace(printValueToString(value)), maxLen);
|
std::string text;
|
||||||
|
llvm::raw_string_ostream os(text);
|
||||||
|
if (auto result = dyn_cast<OpResult>(value))
|
||||||
|
os << result.getOwner()->getName() << '#' << result.getResultNumber();
|
||||||
|
else if (auto blockArg = dyn_cast<BlockArgument>(value))
|
||||||
|
os << "block_arg#" << blockArg.getArgNumber();
|
||||||
|
else
|
||||||
|
os << "<unknown value>";
|
||||||
|
os << " : " << value.getType();
|
||||||
|
os.flush();
|
||||||
|
return abbreviate(text, maxLen);
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string summarizeOperation(Operation* op, size_t maxLen = 96) {
|
static std::string summarizeOperation(Operation* op, size_t maxLen = 96) {
|
||||||
if (!op)
|
if (!op)
|
||||||
return "<none>";
|
return "<none>";
|
||||||
std::string prefix = op->getName().getStringRef().str();
|
return abbreviate(op->getName().getStringRef(), maxLen);
|
||||||
std::string full = collapseWhitespace(printOperationToString(op));
|
|
||||||
if (full == prefix)
|
|
||||||
return prefix;
|
|
||||||
return abbreviate(prefix + " :: " + full, maxLen);
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::string summarizeLocation(Location loc, size_t maxLen = 88) {
|
|
||||||
return abbreviate(collapseWhitespace(printLocationToString(loc)), maxLen);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void assignOperationOrdering(Operation* op, OperationOrdering& ordering) {
|
static void assignOperationOrdering(Operation* op, OperationOrdering& ordering) {
|
||||||
@@ -151,13 +103,6 @@ static OperationOrdering buildOperationOrdering(Operation* coreLikeOp) {
|
|||||||
return ordering;
|
return ordering;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isSupportedAliasOp(Operation* op) {
|
|
||||||
return isa<memref::SubViewOp,
|
|
||||||
memref::CastOp,
|
|
||||||
memref::CollapseShapeOp,
|
|
||||||
memref::ExpandShapeOp>(op);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isRuntimeMemoryTouchOp(Operation* op) {
|
static bool isRuntimeMemoryTouchOp(Operation* op) {
|
||||||
return isa<pim::PimMemCopyHostToDevOp,
|
return isa<pim::PimMemCopyHostToDevOp,
|
||||||
pim::PimMemCopyDevToHostOp,
|
pim::PimMemCopyDevToHostOp,
|
||||||
@@ -180,7 +125,8 @@ static bool isRuntimeMemoryTouchOp(Operation* op) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool isIgnoredLivenessUser(Operation* op) {
|
static bool isIgnoredLivenessUser(Operation* op) {
|
||||||
return isSupportedAliasOp(op) || isa<scf::ForOp, scf::YieldOp, memref::DeallocOp>(op) || isCoreStaticAddressOp(op);
|
return pim::isLocalMemoryAliasOp(op) || isa<scf::ForOp, scf::YieldOp, memref::DeallocOp>(op)
|
||||||
|
|| isCoreStaticAddressOp(op);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isWithin(mlir::Value value, Region* region) {
|
static bool isWithin(mlir::Value value, Region* region) {
|
||||||
@@ -207,12 +153,6 @@ static void addFallbackReason(std::string& reason, StringRef newReason) {
|
|||||||
reason += newReason.str();
|
reason += newReason.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void appendAliasDescription(llvm::SmallVectorImpl<std::string>& aliases, mlir::Value value) {
|
|
||||||
std::string text = printValueToString(value);
|
|
||||||
if (!llvm::is_contained(aliases, text))
|
|
||||||
aliases.push_back(std::move(text));
|
|
||||||
}
|
|
||||||
|
|
||||||
struct OrderedTouchRange {
|
struct OrderedTouchRange {
|
||||||
uint64_t start = 0;
|
uint64_t start = 0;
|
||||||
uint64_t end = 0;
|
uint64_t end = 0;
|
||||||
@@ -233,97 +173,28 @@ getEffectiveTouchRange(mlir::Value definingValue, Operation* user, const Operati
|
|||||||
return range;
|
return range;
|
||||||
}
|
}
|
||||||
|
|
||||||
static MemoryTouchInterval
|
static MemoryTouchInterval computeMemoryTouchInterval(memref::AllocOp allocOp,
|
||||||
computeMemoryTouchInterval(memref::AllocOp allocOp,
|
const OperationOrdering& ordering,
|
||||||
const OperationOrdering& ordering,
|
uint64_t fallbackEnd) {
|
||||||
uint64_t fallbackEnd,
|
|
||||||
bool includeAliasDescriptions) {
|
|
||||||
MemoryTouchInterval interval;
|
MemoryTouchInterval interval;
|
||||||
interval.start = ordering.position.lookup(allocOp);
|
interval.start = ordering.position.lookup(allocOp);
|
||||||
interval.end = interval.start;
|
interval.end = interval.start;
|
||||||
auto recordAlias = [&](mlir::Value value) {
|
|
||||||
if (includeAliasDescriptions)
|
|
||||||
appendAliasDescription(interval.aliasesFollowed, value);
|
|
||||||
};
|
|
||||||
|
|
||||||
SmallPtrSet<mlir::Value, 16> visitedValues;
|
|
||||||
SmallPtrSet<Operation*, 32> visitedUsers;
|
|
||||||
SmallVector<mlir::Value> pendingValues;
|
|
||||||
pendingValues.push_back(allocOp.getResult());
|
|
||||||
auto parentLoop = allocOp->getParentOfType<scf::ForOp>();
|
auto parentLoop = allocOp->getParentOfType<scf::ForOp>();
|
||||||
|
(void) pim::walkLocalMemoryUses(
|
||||||
while (!pendingValues.empty()) {
|
allocOp.getResult(),
|
||||||
mlir::Value value = pendingValues.pop_back_val();
|
[&](mlir::Value value, Operation* user) {
|
||||||
if (!visitedValues.insert(value).second)
|
if (auto forOp = dyn_cast<scf::ForOp>(user);
|
||||||
continue;
|
forOp && parentLoop && forOp != parentLoop && llvm::is_contained(forOp.getInitArgs(), value))
|
||||||
|
interval.escapesLoop = true;
|
||||||
for (Operation* user : value.getUsers()) {
|
|
||||||
if (!visitedUsers.insert(user).second)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (isSupportedAliasOp(user)) {
|
|
||||||
for (mlir::Value result : user->getResults()) {
|
|
||||||
pendingValues.push_back(result);
|
|
||||||
recordAlias(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto dpsOp = dyn_cast<DestinationStyleOpInterface>(user)) {
|
|
||||||
for (OpResult result : user->getResults()) {
|
|
||||||
OpOperand* tiedOperand = dpsOp.getTiedOpOperand(result);
|
|
||||||
if (!tiedOperand || tiedOperand->get() != value)
|
|
||||||
continue;
|
|
||||||
pendingValues.push_back(result);
|
|
||||||
recordAlias(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto forOp = dyn_cast<scf::ForOp>(user)) {
|
|
||||||
for (auto [index, initArg] : llvm::enumerate(forOp.getInitArgs())) {
|
|
||||||
if (initArg != value)
|
|
||||||
continue;
|
|
||||||
pendingValues.push_back(forOp.getRegionIterArgs()[index]);
|
|
||||||
pendingValues.push_back(forOp.getResult(index));
|
|
||||||
recordAlias(forOp.getRegionIterArgs()[index]);
|
|
||||||
recordAlias(forOp.getResult(index));
|
|
||||||
if (parentLoop && forOp != parentLoop)
|
|
||||||
interval.escapesLoop = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto yieldOp = dyn_cast<scf::YieldOp>(user)) {
|
if (auto yieldOp = dyn_cast<scf::YieldOp>(user)) {
|
||||||
auto forOp = dyn_cast<scf::ForOp>(yieldOp->getParentOp());
|
auto forOp = dyn_cast<scf::ForOp>(yieldOp->getParentOp());
|
||||||
auto ifOp = dyn_cast<scf::IfOp>(yieldOp->getParentOp());
|
auto ifOp = dyn_cast<scf::IfOp>(yieldOp->getParentOp());
|
||||||
auto indexSwitch = dyn_cast<scf::IndexSwitchOp>(yieldOp->getParentOp());
|
auto indexSwitch = dyn_cast<scf::IndexSwitchOp>(yieldOp->getParentOp());
|
||||||
if (ifOp) {
|
if (!forOp && !ifOp && !indexSwitch)
|
||||||
for (auto [index, operand] : llvm::enumerate(yieldOp.getOperands())) {
|
|
||||||
if (operand != value)
|
|
||||||
continue;
|
|
||||||
pendingValues.push_back(ifOp.getResult(index));
|
|
||||||
recordAlias(ifOp.getResult(index));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (indexSwitch) {
|
|
||||||
for (auto [index, operand] : llvm::enumerate(yieldOp.getOperands())) {
|
|
||||||
if (operand != value)
|
|
||||||
continue;
|
|
||||||
pendingValues.push_back(indexSwitch.getResult(index));
|
|
||||||
recordAlias(indexSwitch.getResult(index));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!forOp) {
|
|
||||||
addFallbackReason(interval.fallbackReason, "yield without scf.for parent");
|
addFallbackReason(interval.fallbackReason, "yield without scf.for parent");
|
||||||
}
|
else if (forOp && parentLoop && forOp == parentLoop && llvm::is_contained(yieldOp.getOperands(), value))
|
||||||
else {
|
interval.escapesLoop = true;
|
||||||
for (auto [index, operand] : llvm::enumerate(yieldOp.getOperands())) {
|
|
||||||
if (operand != value)
|
|
||||||
continue;
|
|
||||||
pendingValues.push_back(forOp.getResult(index));
|
|
||||||
recordAlias(forOp.getResult(index));
|
|
||||||
if (parentLoop && forOp == parentLoop)
|
|
||||||
interval.escapesLoop = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isRuntimeMemoryTouchOp(user)) {
|
if (isRuntimeMemoryTouchOp(user)) {
|
||||||
@@ -345,23 +216,21 @@ computeMemoryTouchInterval(memref::AllocOp allocOp,
|
|||||||
interval.hasRuntimeUse = true;
|
interval.hasRuntimeUse = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (range.start < interval.start) {
|
if (range.start < interval.start)
|
||||||
interval.start = range.start;
|
interval.start = range.start;
|
||||||
}
|
if (range.end > interval.end)
|
||||||
if (range.end > interval.end) {
|
|
||||||
interval.end = range.end;
|
interval.end = range.end;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
continue;
|
return success();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isIgnoredLivenessUser(user))
|
if (isIgnoredLivenessUser(user))
|
||||||
continue;
|
return success();
|
||||||
|
|
||||||
addFallbackReason(interval.fallbackReason, "unhandled user op");
|
addFallbackReason(interval.fallbackReason, "unhandled user op");
|
||||||
interval.endUsedFallback = true;
|
interval.endUsedFallback = true;
|
||||||
}
|
return success();
|
||||||
}
|
});
|
||||||
|
|
||||||
if (!interval.hasRuntimeUse) {
|
if (!interval.hasRuntimeUse) {
|
||||||
interval.startUsedAllocFallback = true;
|
interval.startUsedAllocFallback = true;
|
||||||
@@ -374,9 +243,8 @@ computeMemoryTouchInterval(memref::AllocOp allocOp,
|
|||||||
return interval;
|
return interval;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (interval.endUsedFallback) {
|
if (interval.endUsedFallback)
|
||||||
interval.end = std::max(interval.end, fallbackEnd);
|
interval.end = std::max(interval.end, fallbackEnd);
|
||||||
}
|
|
||||||
|
|
||||||
return interval;
|
return interval;
|
||||||
}
|
}
|
||||||
@@ -402,11 +270,21 @@ static uint64_t getSlotLogicalBytes(const PlannedPhysicalSlot& slot, ArrayRef<Lo
|
|||||||
return slotLogicalBytes;
|
return slotLogicalBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool placementsOverlap(const PlannedPhysicalSlot& lhs, const PlannedPhysicalSlot& rhs) {
|
||||||
|
return lhs.address < rhs.address + rhs.requiredSize && rhs.address < lhs.address + lhs.requiredSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool hasAddressReuse(size_t slotIndex, ArrayRef<PlannedPhysicalSlot> slots) {
|
||||||
|
if (slots[slotIndex].intervalIndices.size() > 1)
|
||||||
|
return true;
|
||||||
|
return llvm::any_of(llvm::enumerate(slots), [&](auto indexedSlot) {
|
||||||
|
return indexedSlot.index() != slotIndex && placementsOverlap(slots[slotIndex], indexedSlot.value());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
SmallVector<LocalAllocInterval, 0> onnx_mlir::buildLocalAllocIntervals(Operation* coreLikeOp,
|
SmallVector<LocalAllocInterval, 0> onnx_mlir::buildLocalAllocIntervals(Operation* coreLikeOp) {
|
||||||
std::optional<unsigned> lane,
|
|
||||||
bool includeAliasDescriptions) {
|
|
||||||
SmallVector<LocalAllocInterval, 0> intervals;
|
SmallVector<LocalAllocInterval, 0> intervals;
|
||||||
OperationOrdering ordering = buildOperationOrdering(coreLikeOp);
|
OperationOrdering ordering = buildOperationOrdering(coreLikeOp);
|
||||||
if (ordering.position.empty())
|
if (ordering.position.empty())
|
||||||
@@ -423,12 +301,10 @@ SmallVector<LocalAllocInterval, 0> onnx_mlir::buildLocalAllocIntervals(Operation
|
|||||||
llvm_unreachable("Failed to compute local allocation size");
|
llvm_unreachable("Failed to compute local allocation size");
|
||||||
}
|
}
|
||||||
|
|
||||||
MemoryTouchInterval touchInterval =
|
MemoryTouchInterval touchInterval = computeMemoryTouchInterval(allocOp, ordering, fallbackEnd);
|
||||||
computeMemoryTouchInterval(allocOp, ordering, fallbackEnd, includeAliasDescriptions);
|
|
||||||
LocalAllocInterval interval;
|
LocalAllocInterval interval;
|
||||||
interval.id = nextIntervalId++;
|
interval.id = nextIntervalId++;
|
||||||
interval.alloc = allocOp;
|
interval.alloc = allocOp;
|
||||||
interval.key = getMemoryValueKey(allocOp.getResult(), lane);
|
|
||||||
interval.start = touchInterval.start;
|
interval.start = touchInterval.start;
|
||||||
interval.end = touchInterval.end;
|
interval.end = touchInterval.end;
|
||||||
interval.size = *checkedSize;
|
interval.size = *checkedSize;
|
||||||
@@ -442,7 +318,9 @@ SmallVector<LocalAllocInterval, 0> onnx_mlir::buildLocalAllocIntervals(Operation
|
|||||||
interval.insideNestedRegion = isNestedAllocation(coreLikeOp, allocOp);
|
interval.insideNestedRegion = isNestedAllocation(coreLikeOp, allocOp);
|
||||||
interval.escapesLoop = touchInterval.escapesLoop;
|
interval.escapesLoop = touchInterval.escapesLoop;
|
||||||
interval.fallbackReason = std::move(touchInterval.fallbackReason);
|
interval.fallbackReason = std::move(touchInterval.fallbackReason);
|
||||||
interval.aliasesFollowed = std::move(touchInterval.aliasesFollowed);
|
interval.valueSummary = summarizeValue(allocOp.getResult(), 88);
|
||||||
|
interval.firstTouchSummary = summarizeOperation(touchInterval.firstTouchOp);
|
||||||
|
interval.lastTouchSummary = summarizeOperation(touchInterval.lastTouchOp);
|
||||||
intervals.push_back(std::move(interval));
|
intervals.push_back(std::move(interval));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -467,60 +345,94 @@ SmallVector<PlannedPhysicalSlot, 0> onnx_mlir::planPhysicalSlots(MutableArrayRef
|
|||||||
|
|
||||||
for (size_t intervalIndex : intervalOrder) {
|
for (size_t intervalIndex : intervalOrder) {
|
||||||
LocalAllocInterval& interval = intervals[intervalIndex];
|
LocalAllocInterval& interval = intervals[intervalIndex];
|
||||||
PlannedPhysicalSlot* bestSlot = nullptr;
|
SmallVector<const PlannedPhysicalSlot*, 16> conflictingSlots;
|
||||||
auto bestKey = std::tuple<size_t, size_t, size_t, size_t>(std::numeric_limits<size_t>::max(),
|
SmallVector<size_t, 16> candidateAddresses = {0};
|
||||||
std::numeric_limits<size_t>::max(),
|
size_t currentPeak = 0;
|
||||||
std::numeric_limits<size_t>::max(),
|
for (const PlannedPhysicalSlot& slot : slots) {
|
||||||
std::numeric_limits<size_t>::max());
|
currentPeak = std::max(currentPeak, slot.address + slot.requiredSize);
|
||||||
|
if (llvm::any_of(slot.intervalIndices, [&](size_t otherIndex) {
|
||||||
for (size_t slotIndex = 0; slotIndex < slots.size(); ++slotIndex) {
|
return intervalsOverlap(interval, intervals[otherIndex]);
|
||||||
PlannedPhysicalSlot& slot = slots[slotIndex];
|
})) {
|
||||||
bool compatible = true;
|
conflictingSlots.push_back(&slot);
|
||||||
for (size_t otherIndex : slot.intervalIndices) {
|
size_t candidate = llvm::alignTo(slot.address + slot.requiredSize, 4);
|
||||||
if (intervalsOverlap(interval, intervals[otherIndex])) {
|
if (!llvm::is_contained(candidateAddresses, candidate))
|
||||||
compatible = false;
|
candidateAddresses.push_back(candidate);
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!compatible)
|
}
|
||||||
continue;
|
|
||||||
|
|
||||||
size_t resultingSize = std::max(slot.requiredSize, interval.size);
|
size_t bestAddress = std::numeric_limits<size_t>::max();
|
||||||
size_t growth = resultingSize - slot.requiredSize;
|
auto bestKey = std::tuple<size_t, size_t>(std::numeric_limits<size_t>::max(),
|
||||||
auto candidateKey =
|
std::numeric_limits<size_t>::max());
|
||||||
std::tuple<size_t, size_t, size_t, size_t>(growth, resultingSize, slot.intervalIndices.size(), slot.id);
|
for (size_t candidate : candidateAddresses) {
|
||||||
|
bool overlaps = llvm::any_of(conflictingSlots, [&](const PlannedPhysicalSlot* slot) {
|
||||||
|
return candidate < slot->address + slot->requiredSize && slot->address < candidate + interval.size;
|
||||||
|
});
|
||||||
|
if (overlaps)
|
||||||
|
continue;
|
||||||
|
auto candidateKey = std::tuple<size_t, size_t>(std::max(currentPeak, candidate + interval.size), candidate);
|
||||||
if (candidateKey < bestKey) {
|
if (candidateKey < bestKey) {
|
||||||
bestKey = candidateKey;
|
bestKey = candidateKey;
|
||||||
bestSlot = &slot;
|
bestAddress = candidate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
assert(bestAddress != std::numeric_limits<size_t>::max() && "address after all conflicts must be available");
|
||||||
|
|
||||||
if (!bestSlot) {
|
auto reusable = llvm::find_if(slots, [&](const PlannedPhysicalSlot& slot) {
|
||||||
slots.push_back({slots.size(), interval.size, interval.size, 0, {intervalIndex}});
|
return slot.address == bestAddress && slot.requiredSize == interval.size;
|
||||||
interval.slotPlanIndex = slots.size() - 1;
|
});
|
||||||
interval.physicalSlotId = slots.back().id;
|
if (reusable != slots.end()) {
|
||||||
interval.physicalSlotSize = slots.back().requiredSize;
|
reusable->intervalIndices.push_back(intervalIndex);
|
||||||
continue;
|
interval.slotPlanIndex = static_cast<size_t>(reusable - slots.begin());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
slots.push_back({slots.size(), interval.size, bestAddress, {intervalIndex}});
|
||||||
|
interval.slotPlanIndex = slots.size() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bestSlot->requiredSize = std::max(bestSlot->requiredSize, interval.size);
|
|
||||||
bestSlot->size = bestSlot->requiredSize;
|
|
||||||
bestSlot->intervalIndices.push_back(intervalIndex);
|
|
||||||
interval.slotPlanIndex = static_cast<size_t>(bestSlot - slots.data());
|
|
||||||
interval.physicalSlotId = bestSlot->id;
|
|
||||||
interval.physicalSlotSize = bestSlot->requiredSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return slots;
|
return slots;
|
||||||
}
|
}
|
||||||
|
|
||||||
MemoryPlanArtifacts onnx_mlir::buildMemoryPlanArtifacts(Operation* coreLikeOp,
|
CoreMemoryPlan onnx_mlir::buildCoreMemoryPlan(Operation* coreLikeOp) {
|
||||||
std::optional<unsigned> lane,
|
CoreMemoryPlan plan;
|
||||||
ArrayRef<LocalAllocInterval> intervals,
|
plan.coreLikeOp = coreLikeOp;
|
||||||
ArrayRef<PlannedPhysicalSlot> slots,
|
plan.intervals = buildLocalAllocIntervals(coreLikeOp);
|
||||||
size_t addressLimit,
|
plan.slots = planPhysicalSlots(plan.intervals);
|
||||||
PimMemoryReportLevel reportLevel) {
|
return plan;
|
||||||
MemoryPlanArtifacts artifacts;
|
}
|
||||||
|
|
||||||
|
LogicalResult onnx_mlir::assignPhysicalSlotAddresses(CoreMemoryPlan& plan, size_t addressLimit) {
|
||||||
|
SmallVector<size_t> slotOrder(plan.slots.size());
|
||||||
|
std::iota(slotOrder.begin(), slotOrder.end(), 0);
|
||||||
|
llvm::stable_sort(slotOrder, [&](size_t lhsIndex, size_t rhsIndex) {
|
||||||
|
const PlannedPhysicalSlot& lhs = plan.slots[lhsIndex];
|
||||||
|
const PlannedPhysicalSlot& rhs = plan.slots[rhsIndex];
|
||||||
|
if (lhs.requiredSize != rhs.requiredSize)
|
||||||
|
return lhs.requiredSize > rhs.requiredSize;
|
||||||
|
return lhs.id < rhs.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
size_t nextSlotId = 0;
|
||||||
|
for (size_t slotIndex : slotOrder) {
|
||||||
|
PlannedPhysicalSlot& slot = plan.slots[slotIndex];
|
||||||
|
if (slot.address > addressLimit || slot.requiredSize > addressLimit - slot.address) {
|
||||||
|
plan.coreLikeOp->emitError() << "PIM local memory plan exceeds the signed int32 address range while assigning "
|
||||||
|
"a "
|
||||||
|
<< slot.requiredSize << " byte physical placement at address " << slot.address;
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
slot.id = nextSlotId++;
|
||||||
|
}
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string onnx_mlir::buildMemoryPlanReport(Operation* coreLikeOp,
|
||||||
|
ArrayRef<LocalAllocInterval> intervals,
|
||||||
|
ArrayRef<PlannedPhysicalSlot> slots,
|
||||||
|
size_t addressLimit,
|
||||||
|
PimMemoryReportLevel reportLevel) {
|
||||||
|
std::string artifacts;
|
||||||
|
|
||||||
uint64_t totalLogicalBytes = 0;
|
uint64_t totalLogicalBytes = 0;
|
||||||
uint64_t totalPhysicalBytes = 0;
|
uint64_t totalPhysicalBytes = 0;
|
||||||
@@ -536,7 +448,6 @@ MemoryPlanArtifacts onnx_mlir::buildMemoryPlanArtifacts(Operation* coreLikeOp,
|
|||||||
for (const LocalAllocInterval& interval : intervals) {
|
for (const LocalAllocInterval& interval : intervals) {
|
||||||
totalLogicalBytes += interval.size;
|
totalLogicalBytes += interval.size;
|
||||||
largestLogicalAllocation = std::max(largestLogicalAllocation, interval.size);
|
largestLogicalAllocation = std::max(largestLogicalAllocation, interval.size);
|
||||||
maximumAssignedAddress = std::max(maximumAssignedAddress, interval.assignedAddress + interval.physicalSlotSize);
|
|
||||||
if (interval.startUsedAllocFallback || interval.endUsedFallback)
|
if (interval.startUsedAllocFallback || interval.endUsedFallback)
|
||||||
++fallbackIntervals;
|
++fallbackIntervals;
|
||||||
if (!interval.hasRuntimeUse)
|
if (!interval.hasRuntimeUse)
|
||||||
@@ -546,12 +457,14 @@ MemoryPlanArtifacts onnx_mlir::buildMemoryPlanArtifacts(Operation* coreLikeOp,
|
|||||||
if (interval.escapesLoop)
|
if (interval.escapesLoop)
|
||||||
++loopEscapingIntervals;
|
++loopEscapingIntervals;
|
||||||
}
|
}
|
||||||
for (const PlannedPhysicalSlot& slot : slots) {
|
for (size_t slotIndex = 0; slotIndex < slots.size(); ++slotIndex) {
|
||||||
totalPhysicalBytes += slot.size;
|
const PlannedPhysicalSlot& slot = slots[slotIndex];
|
||||||
largestPhysicalSlot = std::max(largestPhysicalSlot, slot.size);
|
largestPhysicalSlot = std::max(largestPhysicalSlot, slot.requiredSize);
|
||||||
if (slot.intervalIndices.size() > 1)
|
maximumAssignedAddress = std::max(maximumAssignedAddress, slot.address + slot.requiredSize);
|
||||||
reusedAllocations += slot.intervalIndices.size() - 1;
|
if (hasAddressReuse(slotIndex, slots))
|
||||||
|
reusedAllocations += slot.intervalIndices.size();
|
||||||
}
|
}
|
||||||
|
totalPhysicalBytes = maximumAssignedAddress;
|
||||||
|
|
||||||
uint64_t savedBytes = totalLogicalBytes >= totalPhysicalBytes ? totalLogicalBytes - totalPhysicalBytes : 0;
|
uint64_t savedBytes = totalLogicalBytes >= totalPhysicalBytes ? totalLogicalBytes - totalPhysicalBytes : 0;
|
||||||
double savedPercent =
|
double savedPercent =
|
||||||
@@ -560,8 +473,6 @@ MemoryPlanArtifacts onnx_mlir::buildMemoryPlanArtifacts(Operation* coreLikeOp,
|
|||||||
raw_string_ostream os(artifacts);
|
raw_string_ostream os(artifacts);
|
||||||
os << "=== PIM Memory Liveness Report ===\n";
|
os << "=== PIM Memory Liveness Report ===\n";
|
||||||
os << "Op: " << coreLikeOp->getName() << "\n";
|
os << "Op: " << coreLikeOp->getName() << "\n";
|
||||||
if (lane)
|
|
||||||
os << "Lane: " << *lane << "\n";
|
|
||||||
os << "Summary:\n";
|
os << "Summary:\n";
|
||||||
os << " logical allocation bytes: " << formatReportMemory(totalLogicalBytes) << " (" << totalLogicalBytes << ")\n";
|
os << " logical allocation bytes: " << formatReportMemory(totalLogicalBytes) << " (" << totalLogicalBytes << ")\n";
|
||||||
os << " physical allocation bytes: " << formatReportMemory(totalPhysicalBytes) << " (" << totalPhysicalBytes
|
os << " physical allocation bytes: " << formatReportMemory(totalPhysicalBytes) << " (" << totalPhysicalBytes
|
||||||
@@ -569,32 +480,27 @@ MemoryPlanArtifacts onnx_mlir::buildMemoryPlanArtifacts(Operation* coreLikeOp,
|
|||||||
os << " saved bytes: " << formatReportMemory(savedBytes) << " (" << savedBytes << ")\n";
|
os << " saved bytes: " << formatReportMemory(savedBytes) << " (" << savedBytes << ")\n";
|
||||||
os << " saved percent: " << format("%.2f%%", savedPercent) << "\n";
|
os << " saved percent: " << format("%.2f%%", savedPercent) << "\n";
|
||||||
os << " intervals: " << intervals.size() << "\n";
|
os << " intervals: " << intervals.size() << "\n";
|
||||||
os << " physical slots: " << slots.size() << "\n";
|
os << " address placements: " << slots.size() << "\n";
|
||||||
os << " reused allocations: " << reusedAllocations << "\n";
|
os << " allocations sharing address space: " << reusedAllocations << "\n";
|
||||||
os << " fallback intervals: " << fallbackIntervals << "\n";
|
os << " fallback intervals: " << fallbackIntervals << "\n";
|
||||||
os << " intervals with no runtime memory touch: " << noRuntimeTouchIntervals << "\n";
|
os << " intervals with no runtime memory touch: " << noRuntimeTouchIntervals << "\n";
|
||||||
os << " nested allocations: " << nestedIntervals << "\n";
|
os << " nested allocations: " << nestedIntervals << "\n";
|
||||||
os << " loop-escaping allocations: " << loopEscapingIntervals << "\n";
|
os << " loop-escaping allocations: " << loopEscapingIntervals << "\n";
|
||||||
os << " largest logical allocation: " << largestLogicalAllocation << "\n";
|
os << " largest logical allocation: " << largestLogicalAllocation << "\n";
|
||||||
os << " largest physical slot: " << largestPhysicalSlot << "\n";
|
os << " largest address placement: " << largestPhysicalSlot << "\n";
|
||||||
os << " address limit: " << addressLimit << "\n";
|
os << " address limit: " << addressLimit << "\n";
|
||||||
os << " peak physical memory: " << formatReportMemory(maximumAssignedAddress) << " (" << maximumAssignedAddress
|
os << " peak physical memory: " << formatReportMemory(maximumAssignedAddress) << " (" << maximumAssignedAddress
|
||||||
<< ")\n";
|
<< ")\n";
|
||||||
os << " maximum assigned address: " << maximumAssignedAddress << "\n";
|
os << " maximum assigned address: " << maximumAssignedAddress << "\n";
|
||||||
|
|
||||||
os << "\nHow To Read:\n";
|
|
||||||
os << " `summary` only shows the strongest reuse cases and the worst offenders.\n";
|
|
||||||
os << " Use `--pim-memory-report=full` when you need the complete slot-by-slot and interval-by-interval dump.\n";
|
|
||||||
os << " Large single-use slots, fallback intervals, and nested single-use allocations are the best places\n";
|
|
||||||
os << " to inspect if allocations should be moved, sunk, or made easier to coalesce earlier in the pipeline.\n";
|
|
||||||
|
|
||||||
SmallVector<const PlannedPhysicalSlot*> reusedSlots;
|
SmallVector<const PlannedPhysicalSlot*> reusedSlots;
|
||||||
SmallVector<const PlannedPhysicalSlot*> singleUseSlots;
|
SmallVector<const PlannedPhysicalSlot*> singleUseSlots;
|
||||||
for (const PlannedPhysicalSlot& slot : slots)
|
for (size_t slotIndex = 0; slotIndex < slots.size(); ++slotIndex) {
|
||||||
if (slot.intervalIndices.size() > 1)
|
if (hasAddressReuse(slotIndex, slots))
|
||||||
reusedSlots.push_back(&slot);
|
reusedSlots.push_back(&slots[slotIndex]);
|
||||||
else
|
else
|
||||||
singleUseSlots.push_back(&slot);
|
singleUseSlots.push_back(&slots[slotIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
llvm::stable_sort(reusedSlots, [&](const PlannedPhysicalSlot* lhs, const PlannedPhysicalSlot* rhs) {
|
llvm::stable_sort(reusedSlots, [&](const PlannedPhysicalSlot* lhs, const PlannedPhysicalSlot* rhs) {
|
||||||
uint64_t lhsLogicalBytes = getSlotLogicalBytes(*lhs, intervals);
|
uint64_t lhsLogicalBytes = getSlotLogicalBytes(*lhs, intervals);
|
||||||
@@ -603,140 +509,66 @@ MemoryPlanArtifacts onnx_mlir::buildMemoryPlanArtifacts(Operation* coreLikeOp,
|
|||||||
return lhs->intervalIndices.size() > rhs->intervalIndices.size();
|
return lhs->intervalIndices.size() > rhs->intervalIndices.size();
|
||||||
if (lhsLogicalBytes != rhsLogicalBytes)
|
if (lhsLogicalBytes != rhsLogicalBytes)
|
||||||
return lhsLogicalBytes > rhsLogicalBytes;
|
return lhsLogicalBytes > rhsLogicalBytes;
|
||||||
if (lhs->size != rhs->size)
|
if (lhs->requiredSize != rhs->requiredSize)
|
||||||
return lhs->size > rhs->size;
|
return lhs->requiredSize > rhs->requiredSize;
|
||||||
return lhs->id < rhs->id;
|
return lhs->id < rhs->id;
|
||||||
});
|
});
|
||||||
llvm::stable_sort(singleUseSlots, [&](const PlannedPhysicalSlot* lhs, const PlannedPhysicalSlot* rhs) {
|
llvm::stable_sort(singleUseSlots, [&](const PlannedPhysicalSlot* lhs, const PlannedPhysicalSlot* rhs) {
|
||||||
if (lhs->size != rhs->size)
|
if (lhs->requiredSize != rhs->requiredSize)
|
||||||
return lhs->size > rhs->size;
|
return lhs->requiredSize > rhs->requiredSize;
|
||||||
return lhs->id < rhs->id;
|
return lhs->id < rhs->id;
|
||||||
});
|
});
|
||||||
|
|
||||||
constexpr size_t kSummaryReuseLimit = 6;
|
constexpr size_t kSummaryReuseLimit = 6;
|
||||||
constexpr size_t kSummaryOffenderLimit = 10;
|
constexpr size_t kSummaryOffenderLimit = 10;
|
||||||
|
|
||||||
os << "\nBest Reuse:\n";
|
os << "\nBest Address Reuse:\n";
|
||||||
if (reusedSlots.empty()) {
|
if (reusedSlots.empty()) {
|
||||||
os << " no slots were shared by multiple intervals\n";
|
os << " no address ranges were reused\n";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for (const PlannedPhysicalSlot* slot : ArrayRef(reusedSlots).take_front(kSummaryReuseLimit)) {
|
for (const PlannedPhysicalSlot* slot : ArrayRef(reusedSlots).take_front(kSummaryReuseLimit)) {
|
||||||
uint64_t slotLogicalBytes = getSlotLogicalBytes(*slot, intervals);
|
uint64_t slotLogicalBytes = getSlotLogicalBytes(*slot, intervals);
|
||||||
os << " slot #" << slot->id << " addr=" << slot->address << " size=" << formatReportMemory(slot->size)
|
os << " slot #" << slot->id << " addr=" << slot->address << " size=" << formatReportMemory(slot->requiredSize)
|
||||||
<< " intervals=" << slot->intervalIndices.size() << " logical_sum=" << formatReportMemory(slotLogicalBytes)
|
<< " intervals=" << slot->intervalIndices.size() << " logical_sum=" << formatReportMemory(slotLogicalBytes)
|
||||||
<< "\n";
|
<< "\n";
|
||||||
for (size_t intervalIndex : slot->intervalIndices) {
|
|
||||||
const LocalAllocInterval& interval = intervals[intervalIndex];
|
|
||||||
os << " #" << interval.id << " [" << interval.start << "," << interval.end << "]"
|
|
||||||
<< " logical=" << formatReportMemory(interval.size)
|
|
||||||
<< " first=" << summarizeOperation(interval.firstTouchOp, 40)
|
|
||||||
<< " last=" << summarizeOperation(interval.lastTouchOp, 40) << "\n";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
os << "\nTop Offenders:\n";
|
os << "\nTop Offenders:\n";
|
||||||
bool printedAttention = false;
|
|
||||||
for (const PlannedPhysicalSlot* slot : ArrayRef(singleUseSlots).take_front(kSummaryOffenderLimit)) {
|
for (const PlannedPhysicalSlot* slot : ArrayRef(singleUseSlots).take_front(kSummaryOffenderLimit)) {
|
||||||
const LocalAllocInterval& interval = intervals[slot->intervalIndices.front()];
|
const LocalAllocInterval& interval = intervals[slot->intervalIndices.front()];
|
||||||
printedAttention = true;
|
|
||||||
os << " slot #" << slot->id << " is single-use"
|
os << " slot #" << slot->id << " is single-use"
|
||||||
<< " size=" << formatReportMemory(slot->size) << " interval=#" << interval.id
|
<< " size=" << formatReportMemory(slot->requiredSize) << " interval=#" << interval.id
|
||||||
<< " value=" << summarizeValue(interval.key.value, 56) << "\n";
|
<< " value=" << abbreviate(interval.valueSummary, 56) << "\n";
|
||||||
os << " first=" << summarizeOperation(interval.firstTouchOp, 40)
|
os << " first=" << abbreviate(interval.firstTouchSummary, 40)
|
||||||
<< " last=" << summarizeOperation(interval.lastTouchOp, 40)
|
<< " last=" << abbreviate(interval.lastTouchSummary, 40)
|
||||||
<< " nested=" << (interval.insideNestedRegion ? "yes" : "no")
|
<< " nested=" << (interval.insideNestedRegion ? "yes" : "no")
|
||||||
<< " escapes_loop=" << (interval.escapesLoop ? "yes" : "no") << "\n";
|
<< " escapes_loop=" << (interval.escapesLoop ? "yes" : "no") << "\n";
|
||||||
}
|
}
|
||||||
size_t fallbackPrinted = 0;
|
if (singleUseSlots.empty())
|
||||||
for (const LocalAllocInterval& interval : intervals) {
|
|
||||||
if (!(interval.startUsedAllocFallback || interval.endUsedFallback) || fallbackPrinted >= kSummaryOffenderLimit)
|
|
||||||
continue;
|
|
||||||
printedAttention = true;
|
|
||||||
++fallbackPrinted;
|
|
||||||
os << " fallback interval #" << interval.id << " size=" << formatReportMemory(interval.size)
|
|
||||||
<< " value=" << summarizeValue(interval.key.value, 56) << "\n";
|
|
||||||
os << " reason: " << (interval.fallbackReason.empty() ? "<none>" : interval.fallbackReason) << "\n";
|
|
||||||
}
|
|
||||||
size_t nestedPrinted = 0;
|
|
||||||
for (const LocalAllocInterval& interval : intervals) {
|
|
||||||
if (nestedPrinted >= kSummaryOffenderLimit)
|
|
||||||
break;
|
|
||||||
if (!(interval.insideNestedRegion && slots[interval.slotPlanIndex].intervalIndices.size() == 1))
|
|
||||||
continue;
|
|
||||||
printedAttention = true;
|
|
||||||
++nestedPrinted;
|
|
||||||
os << " nested single-use interval #" << interval.id << " slot #" << interval.physicalSlotId
|
|
||||||
<< " size=" << formatReportMemory(interval.size) << " value=" << summarizeValue(interval.key.value, 56)
|
|
||||||
<< "\n";
|
|
||||||
os << " hint: move or sink this alloc inside the nested region if the IR allows it.\n";
|
|
||||||
}
|
|
||||||
if (!printedAttention)
|
|
||||||
os << " no obvious blockers detected in this core\n";
|
os << " no obvious blockers detected in this core\n";
|
||||||
|
|
||||||
if (reportLevel == PimMemoryReportFull) {
|
if (reportLevel == PimMemoryReportFull) {
|
||||||
os << "\nSlot Reuse:\n";
|
os << "\nSlot Reuse:\n";
|
||||||
for (const PlannedPhysicalSlot& slot : slots) {
|
for (const PlannedPhysicalSlot& slot : slots) {
|
||||||
uint64_t slotLogicalBytes = getSlotLogicalBytes(slot, intervals);
|
uint64_t slotLogicalBytes = getSlotLogicalBytes(slot, intervals);
|
||||||
os << " slot #" << slot.id << " addr=" << slot.address << " size=" << formatReportMemory(slot.size) << " ("
|
os << " slot #" << slot.id << " addr=" << slot.address << " size=" << formatReportMemory(slot.requiredSize)
|
||||||
<< slot.size << ")"
|
<< " (" << slot.requiredSize << ")"
|
||||||
<< " intervals=" << slot.intervalIndices.size() << " logical_sum=" << formatReportMemory(slotLogicalBytes)
|
<< " intervals=" << slot.intervalIndices.size() << " logical_sum=" << formatReportMemory(slotLogicalBytes)
|
||||||
<< "\n";
|
<< "\n";
|
||||||
for (size_t intervalIndex : slot.intervalIndices) {
|
for (size_t intervalIndex : slot.intervalIndices) {
|
||||||
const LocalAllocInterval& interval = intervals[intervalIndex];
|
const LocalAllocInterval& interval = intervals[intervalIndex];
|
||||||
mlir::Value allocValue = interval.key.value;
|
|
||||||
os << " [" << interval.start << "," << interval.end << "]"
|
os << " [" << interval.start << "," << interval.end << "]"
|
||||||
<< " #" << interval.id << " logical=" << formatReportMemory(interval.size)
|
<< " #" << interval.id << " logical=" << formatReportMemory(interval.size) << " (" << interval.size
|
||||||
|
<< ")"
|
||||||
<< " nested=" << (interval.insideNestedRegion ? "yes" : "no")
|
<< " nested=" << (interval.insideNestedRegion ? "yes" : "no")
|
||||||
<< " escapes_loop=" << (interval.escapesLoop ? "yes" : "no")
|
<< " escapes_loop=" << (interval.escapesLoop ? "yes" : "no")
|
||||||
<< " first=" << summarizeOperation(interval.firstTouchOp, 48)
|
<< " first=" << abbreviate(interval.firstTouchSummary, 48)
|
||||||
<< " last=" << summarizeOperation(interval.lastTouchOp, 48) << "\n";
|
<< " last=" << abbreviate(interval.lastTouchSummary, 48) << "\n";
|
||||||
os << " value=" << summarizeValue(allocValue) << "\n";
|
os << " value=" << abbreviate(interval.valueSummary, 72) << "\n";
|
||||||
}
|
if (!interval.fallbackReason.empty())
|
||||||
}
|
os << " fallback_reason=" << interval.fallbackReason << "\n";
|
||||||
}
|
|
||||||
|
|
||||||
if (reportLevel == PimMemoryReportFull) {
|
|
||||||
os << "\nInterval Details:\n";
|
|
||||||
for (const LocalAllocInterval& interval : intervals) {
|
|
||||||
const PlannedPhysicalSlot& slot = slots[interval.slotPlanIndex];
|
|
||||||
mlir::Value allocValue = interval.key.value;
|
|
||||||
Operation* definingOp = allocValue.getDefiningOp();
|
|
||||||
os << " #" << interval.id << " slot=" << slot.id << " live=[" << interval.start << "," << interval.end << "]"
|
|
||||||
<< " logical=" << formatReportMemory(interval.size)
|
|
||||||
<< " slot_size=" << formatReportMemory(interval.physicalSlotSize) << " addr=" << interval.assignedAddress
|
|
||||||
<< "\n";
|
|
||||||
os << " value=" << summarizeValue(allocValue, 88) << "\n";
|
|
||||||
os << " type=" << allocValue.getType() << "\n";
|
|
||||||
os << " loc="
|
|
||||||
<< summarizeLocation(definingOp ? definingOp->getLoc() : UnknownLoc::get(coreLikeOp->getContext())) << "\n";
|
|
||||||
os << " nested=" << (interval.insideNestedRegion ? "yes" : "no")
|
|
||||||
<< " escapes_loop=" << (interval.escapesLoop ? "yes" : "no")
|
|
||||||
<< " start_fallback=" << (interval.startUsedAllocFallback ? "yes" : "no")
|
|
||||||
<< " end_fallback=" << (interval.endUsedFallback ? "yes" : "no") << "\n";
|
|
||||||
os << " first_use=" << summarizeOperation(interval.firstTouchOp) << " @" << interval.firstTouchPosition
|
|
||||||
<< "\n";
|
|
||||||
os << " last_use=" << summarizeOperation(interval.lastTouchOp) << " @" << interval.lastTouchPosition << "\n";
|
|
||||||
os << " slot_peers=";
|
|
||||||
bool first = true;
|
|
||||||
for (size_t otherIndex : slot.intervalIndices) {
|
|
||||||
if (intervals[otherIndex].id == interval.id)
|
|
||||||
continue;
|
|
||||||
if (!first)
|
|
||||||
os << ", ";
|
|
||||||
os << "#" << intervals[otherIndex].id;
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
if (first)
|
|
||||||
os << "<none>";
|
|
||||||
os << "\n";
|
|
||||||
if (!interval.fallbackReason.empty())
|
|
||||||
os << " fallback_reason=" << interval.fallbackReason << "\n";
|
|
||||||
if (!interval.aliasesFollowed.empty()) {
|
|
||||||
os << " aliases_followed=" << interval.aliasesFollowed.size() << "\n";
|
|
||||||
for (const std::string& alias : interval.aliasesFollowed)
|
|
||||||
os << " - " << abbreviate(collapseWhitespace(alias), 108) << "\n";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -744,3 +576,95 @@ MemoryPlanArtifacts onnx_mlir::buildMemoryPlanArtifacts(Operation* coreLikeOp,
|
|||||||
|
|
||||||
return artifacts;
|
return artifacts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace onnx_mlir {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct PimLocalMemoryPlanningPass : PassWrapper<PimLocalMemoryPlanningPass, OperationPass<ModuleOp>> {
|
||||||
|
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(PimLocalMemoryPlanningPass)
|
||||||
|
|
||||||
|
StringRef getArgument() const override { return "pim-local-memory-planning"; }
|
||||||
|
StringRef getDescription() const override {
|
||||||
|
return "Plan liveness-based physical slots and addresses for PIM core-local memory";
|
||||||
|
}
|
||||||
|
|
||||||
|
void runOnOperation() override {
|
||||||
|
std::fstream reportFile;
|
||||||
|
std::unique_ptr<raw_os_ostream> report;
|
||||||
|
if (pimMemoryReport != PimMemoryReportNone) {
|
||||||
|
reportFile = openReportFileWithExtension("pim_memory_liveness_report", "txt");
|
||||||
|
if (reportFile.is_open())
|
||||||
|
report = std::make_unique<raw_os_ostream>(reportFile);
|
||||||
|
}
|
||||||
|
else if (std::string outputRoot = getOutputDir(); !outputRoot.empty()) {
|
||||||
|
sys::fs::remove(outputRoot + "/reports/pim_memory_liveness_report.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder builder(&getContext());
|
||||||
|
uint64_t nextBatchId = 0;
|
||||||
|
bool failedPlanning = false;
|
||||||
|
getOperation().walk([&](Operation* op) {
|
||||||
|
if (failedPlanning || !isa<pim::PimCoreOp, pim::PimCoreBatchOp>(op))
|
||||||
|
return;
|
||||||
|
|
||||||
|
CoreMemoryPlan plan = buildCoreMemoryPlan(op);
|
||||||
|
if (failed(assignPhysicalSlotAddresses(plan, kPimLocalMemoryAddressLimit))) {
|
||||||
|
failedPlanning = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t arenaSize = 0;
|
||||||
|
for (const PlannedPhysicalSlot& placement : plan.slots)
|
||||||
|
arenaSize = std::max(arenaSize, placement.address + placement.requiredSize);
|
||||||
|
for (const LocalAllocInterval& interval : plan.intervals) {
|
||||||
|
const PlannedPhysicalSlot& slot = plan.slots[interval.slotPlanIndex];
|
||||||
|
interval.alloc->setAttr(kLocalMemoryAddressAttrName, builder.getI64IntegerAttr(slot.address));
|
||||||
|
interval.alloc->setAttr(kLocalMemorySlotAttrName, builder.getI64IntegerAttr(0));
|
||||||
|
interval.alloc->setAttr(kLocalMemorySlotSizeAttrName, builder.getI64IntegerAttr(arenaSize));
|
||||||
|
}
|
||||||
|
uint64_t fallbackIntervals = llvm::count_if(plan.intervals, [](const LocalAllocInterval& interval) {
|
||||||
|
return interval.startUsedAllocFallback || interval.endUsedFallback;
|
||||||
|
});
|
||||||
|
uint64_t nestedSingleUseIntervals = llvm::count_if(plan.intervals, [&](const LocalAllocInterval& interval) {
|
||||||
|
return interval.insideNestedRegion && !hasAddressReuse(interval.slotPlanIndex, plan.slots);
|
||||||
|
});
|
||||||
|
op->setAttr(kLocalMemoryFallbackCountAttrName, builder.getI64IntegerAttr(fallbackIntervals));
|
||||||
|
op->setAttr(kLocalMemoryNestedSingleUseCountAttrName, builder.getI64IntegerAttr(nestedSingleUseIntervals));
|
||||||
|
|
||||||
|
if (!report)
|
||||||
|
return;
|
||||||
|
std::string planReport = buildMemoryPlanReport(
|
||||||
|
op, plan.intervals, plan.slots, kPimLocalMemoryAddressLimit, pimMemoryReport);
|
||||||
|
if (auto coreOp = dyn_cast<pim::PimCoreOp>(op)) {
|
||||||
|
*report << "Core " << coreOp.getCoreId() << ":\n";
|
||||||
|
*report << planReport;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SmallVector<int32_t> coreIds = getBatchCoreIds(cast<pim::PimCoreBatchOp>(op));
|
||||||
|
llvm::sort(coreIds);
|
||||||
|
coreIds.erase(std::unique(coreIds.begin(), coreIds.end()), coreIds.end());
|
||||||
|
uint64_t batchId = nextBatchId++;
|
||||||
|
for (int32_t coreId : coreIds)
|
||||||
|
*report << "Batch " << batchId << " core " << coreId << ":\n" << planReport;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (report) {
|
||||||
|
report->flush();
|
||||||
|
reportFile.close();
|
||||||
|
}
|
||||||
|
if (failedPlanning) {
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dumpModule(getOperation(), "pim4_memory_planned");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::unique_ptr<Pass> createPimLocalMemoryPlanningPass() {
|
||||||
|
return std::make_unique<PimLocalMemoryPlanningPass>();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace onnx_mlir
|
||||||
+19
-17
@@ -6,10 +6,8 @@
|
|||||||
#include "llvm/ADT/SmallVector.h"
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "src/Accelerators/PIM/Compiler/PimCodeGen.hpp"
|
|
||||||
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
@@ -17,7 +15,6 @@ namespace onnx_mlir {
|
|||||||
struct LocalAllocInterval {
|
struct LocalAllocInterval {
|
||||||
size_t id = 0;
|
size_t id = 0;
|
||||||
mlir::memref::AllocOp alloc;
|
mlir::memref::AllocOp alloc;
|
||||||
MemoryValueKey key;
|
|
||||||
uint64_t start = 0;
|
uint64_t start = 0;
|
||||||
uint64_t end = 0;
|
uint64_t end = 0;
|
||||||
size_t size = 0;
|
size_t size = 0;
|
||||||
@@ -31,32 +28,37 @@ struct LocalAllocInterval {
|
|||||||
bool insideNestedRegion = false;
|
bool insideNestedRegion = false;
|
||||||
bool escapesLoop = false;
|
bool escapesLoop = false;
|
||||||
std::string fallbackReason;
|
std::string fallbackReason;
|
||||||
llvm::SmallVector<std::string, 8> aliasesFollowed;
|
std::string valueSummary;
|
||||||
|
std::string firstTouchSummary;
|
||||||
|
std::string lastTouchSummary;
|
||||||
size_t slotPlanIndex = std::numeric_limits<size_t>::max();
|
size_t slotPlanIndex = std::numeric_limits<size_t>::max();
|
||||||
size_t physicalSlotId = std::numeric_limits<size_t>::max();
|
|
||||||
size_t assignedAddress = 0;
|
|
||||||
size_t physicalSlotSize = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PlannedPhysicalSlot {
|
struct PlannedPhysicalSlot {
|
||||||
size_t id = std::numeric_limits<size_t>::max();
|
size_t id = std::numeric_limits<size_t>::max();
|
||||||
size_t requiredSize = 0;
|
size_t requiredSize = 0;
|
||||||
size_t size = 0;
|
|
||||||
size_t address = 0;
|
size_t address = 0;
|
||||||
llvm::SmallVector<size_t, 8> intervalIndices;
|
llvm::SmallVector<size_t, 8> intervalIndices;
|
||||||
};
|
};
|
||||||
|
|
||||||
llvm::SmallVector<LocalAllocInterval, 0> buildLocalAllocIntervals(mlir::Operation* coreLikeOp,
|
struct CoreMemoryPlan {
|
||||||
std::optional<unsigned> lane,
|
mlir::Operation* coreLikeOp = nullptr;
|
||||||
bool includeAliasDescriptions = true);
|
llvm::SmallVector<LocalAllocInterval, 0> intervals;
|
||||||
|
llvm::SmallVector<PlannedPhysicalSlot, 0> slots;
|
||||||
|
};
|
||||||
|
|
||||||
|
llvm::SmallVector<LocalAllocInterval, 0> buildLocalAllocIntervals(mlir::Operation* coreLikeOp);
|
||||||
|
|
||||||
llvm::SmallVector<PlannedPhysicalSlot, 0> planPhysicalSlots(llvm::MutableArrayRef<LocalAllocInterval> intervals);
|
llvm::SmallVector<PlannedPhysicalSlot, 0> planPhysicalSlots(llvm::MutableArrayRef<LocalAllocInterval> intervals);
|
||||||
|
|
||||||
MemoryPlanArtifacts buildMemoryPlanArtifacts(mlir::Operation* coreLikeOp,
|
CoreMemoryPlan buildCoreMemoryPlan(mlir::Operation* coreLikeOp);
|
||||||
std::optional<unsigned> lane,
|
|
||||||
llvm::ArrayRef<LocalAllocInterval> intervals,
|
mlir::LogicalResult assignPhysicalSlotAddresses(CoreMemoryPlan& plan, size_t addressLimit);
|
||||||
llvm::ArrayRef<PlannedPhysicalSlot> slots,
|
|
||||||
size_t addressLimit,
|
std::string buildMemoryPlanReport(mlir::Operation* coreLikeOp,
|
||||||
PimMemoryReportLevel reportLevel);
|
llvm::ArrayRef<LocalAllocInterval> intervals,
|
||||||
|
llvm::ArrayRef<PlannedPhysicalSlot> slots,
|
||||||
|
size_t addressLimit,
|
||||||
|
PimMemoryReportLevel reportLevel);
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
@@ -10,5 +10,6 @@ add_pim_library(OMPimMemoryCoalescing
|
|||||||
|
|
||||||
LINK_LIBS PUBLIC
|
LINK_LIBS PUBLIC
|
||||||
OMPimCommon
|
OMPimCommon
|
||||||
|
OMPimLocalMemoryLifetimeAnalysis
|
||||||
PimOps
|
PimOps
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
||||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||||
#include "mlir/Interfaces/DestinationStyleOpInterface.h"
|
|
||||||
|
|
||||||
#include "llvm/ADT/DenseMap.h"
|
#include "llvm/ADT/DenseMap.h"
|
||||||
#include "llvm/ADT/STLExtras.h"
|
#include "llvm/ADT/STLExtras.h"
|
||||||
#include "llvm/ADT/SmallPtrSet.h"
|
|
||||||
#include "llvm/ADT/SmallVector.h"
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Dialect/Pim/Analysis/LocalMemoryLifetimeAnalysis.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Pim/Transforms/MemoryCoalescing/MemoryCoalescing.hpp"
|
#include "src/Accelerators/PIM/Dialect/Pim/Transforms/MemoryCoalescing/MemoryCoalescing.hpp"
|
||||||
|
|
||||||
using namespace mlir;
|
using namespace mlir;
|
||||||
@@ -19,10 +17,6 @@ namespace pim {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
static bool isSupportedAliasOp(Operation* op) {
|
|
||||||
return isa<memref::SubViewOp, memref::CastOp, memref::CollapseShapeOp, memref::ExpandShapeOp>(op);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isCandidateAllocType(MemRefType type) {
|
static bool isCandidateAllocType(MemRefType type) {
|
||||||
return type && type.hasStaticShape() && type.getLayout().isIdentity()
|
return type && type.hasStaticShape() && type.getLayout().isIdentity()
|
||||||
&& hasByteSizedElementType(type.getElementType());
|
&& hasByteSizedElementType(type.getElementType());
|
||||||
@@ -44,59 +38,21 @@ static void analyzeBlock(Block& block, MemoryCoalescingAnalysis& analysis);
|
|||||||
static FailureOr<uint64_t>
|
static FailureOr<uint64_t>
|
||||||
getLastUseInstruction(memref::AllocOp allocOp, Block& scopeBlock, const DenseMap<Operation*, uint64_t>& opOrder) {
|
getLastUseInstruction(memref::AllocOp allocOp, Block& scopeBlock, const DenseMap<Operation*, uint64_t>& opOrder) {
|
||||||
uint64_t endInstruction = opOrder.lookup(allocOp);
|
uint64_t endInstruction = opOrder.lookup(allocOp);
|
||||||
SmallPtrSet<Value, 16> visitedValues;
|
if (failed(walkLocalMemoryUses(allocOp.getResult(), [&](Value, Operation* user) {
|
||||||
SmallPtrSet<Operation*, 16> visitedUsers;
|
|
||||||
SmallVector<Value> pendingValues;
|
|
||||||
pendingValues.push_back(allocOp.getResult());
|
|
||||||
|
|
||||||
while (!pendingValues.empty()) {
|
|
||||||
Value value = pendingValues.pop_back_val();
|
|
||||||
if (!visitedValues.insert(value).second)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for (Operation* user : value.getUsers()) {
|
|
||||||
if (!visitedUsers.insert(user).second)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (isSupportedAliasOp(user))
|
|
||||||
llvm::append_range(pendingValues, user->getResults());
|
|
||||||
|
|
||||||
if (auto dpsOp = dyn_cast<DestinationStyleOpInterface>(user)) {
|
|
||||||
for (OpResult result : user->getResults()) {
|
|
||||||
OpOperand* tiedOperand = dpsOp.getTiedOpOperand(result);
|
|
||||||
if (tiedOperand && tiedOperand->get() == value)
|
|
||||||
pendingValues.push_back(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto forOp = dyn_cast<scf::ForOp>(user)) {
|
|
||||||
for (auto [index, initArg] : llvm::enumerate(forOp.getInitArgs())) {
|
|
||||||
if (initArg != value)
|
|
||||||
continue;
|
|
||||||
pendingValues.push_back(forOp.getRegionIterArgs()[index]);
|
|
||||||
pendingValues.push_back(forOp.getResult(index));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto yieldOp = dyn_cast<scf::YieldOp>(user)) {
|
if (auto yieldOp = dyn_cast<scf::YieldOp>(user)) {
|
||||||
auto forOp = dyn_cast<scf::ForOp>(yieldOp->getParentOp());
|
if (!isa<scf::ForOp>(yieldOp->getParentOp()))
|
||||||
if (!forOp)
|
|
||||||
return failure();
|
return failure();
|
||||||
for (auto [index, operand] : llvm::enumerate(yieldOp.getOperands()))
|
|
||||||
if (operand == value)
|
|
||||||
pendingValues.push_back(forOp.getResult(index));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Operation* orderedUser = getTopLevelAncestorInBlock(user, scopeBlock);
|
Operation* orderedUser = getTopLevelAncestorInBlock(user, scopeBlock);
|
||||||
if (!orderedUser)
|
if (!orderedUser)
|
||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
auto order = opOrder.find(orderedUser);
|
auto order = opOrder.find(orderedUser);
|
||||||
if (order == opOrder.end())
|
if (order == opOrder.end())
|
||||||
return failure();
|
return failure();
|
||||||
endInstruction = std::max(endInstruction, order->second);
|
endInstruction = std::max(endInstruction, order->second);
|
||||||
}
|
return success();
|
||||||
}
|
})))
|
||||||
|
return failure();
|
||||||
|
|
||||||
return endInstruction;
|
return endInstruction;
|
||||||
}
|
}
|
||||||
@@ -133,7 +89,7 @@ static void analyzeBlock(Block& block, MemoryCoalescingAnalysis& analysis) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
blockAnalysis.candidates.push_back(
|
blockAnalysis.candidates.push_back(
|
||||||
AllocationCandidate {allocOp, &block, opOrder.lookup(allocOp), *endInstruction, getTypeSizeBytes(allocType)});
|
AllocationCandidate {allocOp, opOrder.lookup(allocOp), *endInstruction, getTypeSizeBytes(allocType)});
|
||||||
}
|
}
|
||||||
|
|
||||||
analysis.skippedAllocations += blockAnalysis.skippedAllocations;
|
analysis.skippedAllocations += blockAnalysis.skippedAllocations;
|
||||||
@@ -150,6 +106,14 @@ uint64_t MemoryCoalescingAnalysis::getCandidateCount() const {
|
|||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64_t MemoryCoalescingAnalysis::getCandidateBytes() const {
|
||||||
|
uint64_t total = 0;
|
||||||
|
for (const MemoryCoalescingBlockAnalysis& block : blocks)
|
||||||
|
for (const AllocationCandidate& candidate : block.candidates)
|
||||||
|
total += candidate.sizeBytes;
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
MemoryCoalescingAnalysis analyzeMemoryCoalescingCandidates(Operation* coreLikeOp) {
|
MemoryCoalescingAnalysis analyzeMemoryCoalescingCandidates(Operation* coreLikeOp) {
|
||||||
MemoryCoalescingAnalysis analysis;
|
MemoryCoalescingAnalysis analysis;
|
||||||
if (!coreLikeOp || coreLikeOp->getNumRegions() != 1 || coreLikeOp->getRegion(0).empty())
|
if (!coreLikeOp || coreLikeOp->getNumRegions() != 1 || coreLikeOp->getRegion(0).empty())
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ namespace pim {
|
|||||||
|
|
||||||
struct AllocationCandidate {
|
struct AllocationCandidate {
|
||||||
mlir::memref::AllocOp alloc;
|
mlir::memref::AllocOp alloc;
|
||||||
mlir::Block* scopeBlock = nullptr;
|
|
||||||
uint64_t startInstruction = 0;
|
uint64_t startInstruction = 0;
|
||||||
uint64_t endInstruction = 0;
|
uint64_t endInstruction = 0;
|
||||||
uint64_t sizeBytes = 0;
|
uint64_t sizeBytes = 0;
|
||||||
@@ -27,6 +26,7 @@ struct MemoryCoalescingAnalysis {
|
|||||||
uint64_t skippedAllocations = 0;
|
uint64_t skippedAllocations = 0;
|
||||||
|
|
||||||
uint64_t getCandidateCount() const;
|
uint64_t getCandidateCount() const;
|
||||||
|
uint64_t getCandidateBytes() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MemoryCoalescingStats {
|
struct MemoryCoalescingStats {
|
||||||
|
|||||||
@@ -1,207 +1,94 @@
|
|||||||
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
|
||||||
#include "mlir/IR/PatternMatch.h"
|
|
||||||
#include "mlir/Pass/Pass.h"
|
#include "mlir/Pass/Pass.h"
|
||||||
|
|
||||||
#include "llvm/ADT/SmallVector.h"
|
|
||||||
#include "llvm/Support/raw_os_ostream.h"
|
#include "llvm/Support/raw_os_ostream.h"
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
#include "Common/IR/CompactAsmUtils.hpp"
|
|
||||||
#include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.hpp"
|
#include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
|
||||||
#include "src/Accelerators/PIM/Common/Support/DebugDump.hpp"
|
|
||||||
#include "src/Accelerators/PIM/Common/Support/ReportUtils.hpp"
|
#include "src/Accelerators/PIM/Common/Support/ReportUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Pim/Transforms/MemoryCoalescing/MemoryCoalescing.hpp"
|
#include "src/Accelerators/PIM/Dialect/Pim/Transforms/MemoryCoalescing/MemoryCoalescing.hpp"
|
||||||
#include "src/Accelerators/PIM/Pass/PIMPasses.h"
|
#include "src/Accelerators/PIM/Pass/PIMPasses.h"
|
||||||
|
|
||||||
using namespace mlir;
|
using namespace mlir;
|
||||||
using namespace onnx_mlir::compact_asm;
|
|
||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// This pass is an IR cleanup step after bufferization. It only rewrites
|
|
||||||
// obviously compatible local allocations with non-overlapping lifetimes inside
|
|
||||||
// the same block and leaves the final physical memory planning to codegen.
|
|
||||||
|
|
||||||
struct CoalescingReportRow {
|
struct CoalescingReportRow {
|
||||||
uint64_t numCandidates = 0;
|
uint64_t candidates = 0;
|
||||||
uint64_t numSkipped = 0;
|
uint64_t logicalBytes = 0;
|
||||||
uint64_t numRemoved = 0;
|
uint64_t skipped = 0;
|
||||||
|
uint64_t removed = 0;
|
||||||
uint64_t savedBytes = 0;
|
uint64_t savedBytes = 0;
|
||||||
|
|
||||||
bool operator==(const CoalescingReportRow& other) const {
|
|
||||||
return numCandidates == other.numCandidates && numSkipped == other.numSkipped && numRemoved == other.numRemoved
|
|
||||||
&& savedBytes == other.savedBytes;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CoalescingReportEntry {
|
struct CoalescingReportEntry {
|
||||||
enum class Kind {
|
std::string label;
|
||||||
Core,
|
uint64_t coreCount = 1;
|
||||||
Batch
|
|
||||||
};
|
|
||||||
|
|
||||||
Kind kind = Kind::Core;
|
|
||||||
uint64_t id = 0;
|
|
||||||
llvm::SmallVector<int32_t, 8> coreIds;
|
|
||||||
CoalescingReportRow row;
|
CoalescingReportRow row;
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::string formatMemory(uint64_t bytes) { return formatReportMemory(bytes); }
|
|
||||||
|
|
||||||
static void printReportRow(raw_ostream& os, const CoalescingReportRow& row) {
|
|
||||||
llvm::SmallVector<ReportField, 4> fields = {
|
|
||||||
{"Number of candidates", std::to_string(row.numCandidates)},
|
|
||||||
{"Skipped allocations", std::to_string(row.numSkipped) },
|
|
||||||
{"Removed allocations", std::to_string(row.numRemoved) },
|
|
||||||
{"Saved memory", formatMemory(row.savedBytes) }
|
|
||||||
};
|
|
||||||
printReportFlatFields(os, fields);
|
|
||||||
}
|
|
||||||
|
|
||||||
static CoalescingReportRow getTotalRow(const CoalescingReportEntry& entry) {
|
|
||||||
uint64_t factor = std::max<uint64_t>(1, entry.coreIds.size());
|
|
||||||
return {entry.row.numCandidates * factor,
|
|
||||||
entry.row.numSkipped * factor,
|
|
||||||
entry.row.numRemoved * factor,
|
|
||||||
entry.row.savedBytes * factor};
|
|
||||||
}
|
|
||||||
|
|
||||||
static void emitReport(ArrayRef<CoalescingReportEntry> entries) {
|
static void emitReport(ArrayRef<CoalescingReportEntry> entries) {
|
||||||
std::fstream file = openReportFile("memory_coalescing_report");
|
std::fstream file = openReportFile("memory_coalescing_report");
|
||||||
if (!file.is_open())
|
if (!file.is_open())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
llvm::raw_os_ostream os(file);
|
llvm::raw_os_ostream os(file);
|
||||||
CoalescingReportRow totalRow;
|
CoalescingReportRow total;
|
||||||
for (const CoalescingReportEntry& entry : entries) {
|
for (const CoalescingReportEntry& entry : entries) {
|
||||||
CoalescingReportRow entryTotal = getTotalRow(entry);
|
total.candidates += entry.row.candidates * entry.coreCount;
|
||||||
totalRow.numCandidates += entryTotal.numCandidates;
|
total.logicalBytes += entry.row.logicalBytes * entry.coreCount;
|
||||||
totalRow.numSkipped += entryTotal.numSkipped;
|
total.skipped += entry.row.skipped * entry.coreCount;
|
||||||
totalRow.numRemoved += entryTotal.numRemoved;
|
total.removed += entry.row.removed * entry.coreCount;
|
||||||
totalRow.savedBytes += entryTotal.savedBytes;
|
total.savedBytes += entry.row.savedBytes * entry.coreCount;
|
||||||
}
|
}
|
||||||
|
printReportTotalsBlock(os,
|
||||||
llvm::SmallVector<ReportField, 4> totalFields = {
|
{{"Number of candidates", std::to_string(total.candidates)},
|
||||||
{"Number of candidates", std::to_string(totalRow.numCandidates)},
|
{"Logical candidate bytes", formatReportMemory(total.logicalBytes)},
|
||||||
{"Skipped allocations", std::to_string(totalRow.numSkipped) },
|
{"Skipped allocations", std::to_string(total.skipped)},
|
||||||
{"Removed allocations", std::to_string(totalRow.numRemoved) },
|
{"Removed allocations", std::to_string(total.removed)},
|
||||||
{"Saved memory", formatMemory(totalRow.savedBytes) }
|
{"Saved memory", formatReportMemory(total.savedBytes)}});
|
||||||
};
|
for (const CoalescingReportEntry& entry : entries) {
|
||||||
printReportTotalsBlock(os, totalFields);
|
os << "\n" << entry.label << ":\n";
|
||||||
if (!entries.empty())
|
printReportFlatFields(os,
|
||||||
os << "\n";
|
{{"Number of candidates", std::to_string(entry.row.candidates)},
|
||||||
|
{"Logical candidate bytes", formatReportMemory(entry.row.logicalBytes)},
|
||||||
llvm::SmallVector<CoalescingReportEntry, 32> sortedEntries(entries.begin(), entries.end());
|
{"Skipped allocations", std::to_string(entry.row.skipped)},
|
||||||
sortReportEntriesByFirstCore(sortedEntries);
|
{"Removed allocations", std::to_string(entry.row.removed)},
|
||||||
|
{"Saved memory", formatReportMemory(entry.row.savedBytes)}});
|
||||||
for (size_t index = 0; index < sortedEntries.size();) {
|
|
||||||
size_t runEnd = index + 1;
|
|
||||||
while (runEnd < sortedEntries.size() && sortedEntries[runEnd].kind == sortedEntries[index].kind
|
|
||||||
&& sortedEntries[runEnd].row == sortedEntries[index].row) {
|
|
||||||
++runEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sortedEntries[index].kind == CoalescingReportEntry::Kind::Batch) {
|
|
||||||
os << "Batch ";
|
|
||||||
for (size_t batchIndex = index; batchIndex < runEnd; ++batchIndex) {
|
|
||||||
if (batchIndex != index)
|
|
||||||
os << ",\n ";
|
|
||||||
os << sortedEntries[batchIndex].id << " (cores ";
|
|
||||||
printCompressedIntegerEntries(os, ArrayRef<int32_t>(sortedEntries[batchIndex].coreIds));
|
|
||||||
os << ")";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
llvm::SmallVector<int32_t, 8> coreIds;
|
|
||||||
for (size_t coreIndex = index; coreIndex < runEnd; ++coreIndex)
|
|
||||||
coreIds.push_back(sortedEntries[coreIndex].coreIds.front());
|
|
||||||
os << "Core ";
|
|
||||||
printCompressedIntegerEntries(os, ArrayRef<int32_t>(coreIds));
|
|
||||||
}
|
|
||||||
|
|
||||||
os << ":\n";
|
|
||||||
if (sortedEntries[index].kind == CoalescingReportEntry::Kind::Batch) {
|
|
||||||
llvm::SmallVector<ReportField, 4> perCoreFields = {
|
|
||||||
{"Number of candidates", std::to_string(sortedEntries[index].row.numCandidates)},
|
|
||||||
{"Skipped allocations", std::to_string(sortedEntries[index].row.numSkipped) },
|
|
||||||
{"Removed allocations", std::to_string(sortedEntries[index].row.numRemoved) },
|
|
||||||
{"Saved memory", formatMemory(sortedEntries[index].row.savedBytes) }
|
|
||||||
};
|
|
||||||
CoalescingReportRow totalRow = getTotalRow(sortedEntries[index]);
|
|
||||||
llvm::SmallVector<ReportField, 4> totalFields = {
|
|
||||||
{"Number of candidates", std::to_string(totalRow.numCandidates)},
|
|
||||||
{"Skipped allocations", std::to_string(totalRow.numSkipped) },
|
|
||||||
{"Removed allocations", std::to_string(totalRow.numRemoved) },
|
|
||||||
{"Saved memory", formatMemory(totalRow.savedBytes) }
|
|
||||||
};
|
|
||||||
printReportPerCoreAndTotalFields(os, perCoreFields, totalFields);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
printReportRow(os, sortedEntries[index].row);
|
|
||||||
}
|
|
||||||
printReportEntrySeparator(os, runEnd < sortedEntries.size());
|
|
||||||
index = runEnd;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
os.flush();
|
os.flush();
|
||||||
file.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PimMemoryCoalescingPass : PassWrapper<PimMemoryCoalescingPass, OperationPass<ModuleOp>> {
|
struct PimMemoryCoalescingPass : PassWrapper<PimMemoryCoalescingPass, OperationPass<ModuleOp>> {
|
||||||
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(PimMemoryCoalescingPass)
|
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(PimMemoryCoalescingPass)
|
||||||
|
|
||||||
StringRef getArgument() const override { return "pim-memory-coalescing"; }
|
StringRef getArgument() const override { return "pim-memory-coalescing"; }
|
||||||
StringRef getDescription() const override { return "Analyze local PIM memory reuse opportunities"; }
|
StringRef getDescription() const override { return "Safely coalesce compatible block-local PIM allocations"; }
|
||||||
|
|
||||||
PimMemoryCoalescingPass() = default;
|
|
||||||
PimMemoryCoalescingPass(const PimMemoryCoalescingPass& pass) {}
|
|
||||||
|
|
||||||
void runOnOperation() override {
|
void runOnOperation() override {
|
||||||
IRRewriter rewriter(&getContext());
|
IRRewriter rewriter(&getContext());
|
||||||
SmallVector<CoalescingReportEntry, 32> reportEntries;
|
SmallVector<CoalescingReportEntry, 32> reportEntries;
|
||||||
uint64_t nextBatchId = 0;
|
uint64_t nextBatchId = 0;
|
||||||
bool hasFailure = false;
|
|
||||||
|
|
||||||
getOperation().walk([&](Operation* op) {
|
getOperation().walk([&](Operation* op) {
|
||||||
if (hasFailure || !isa<pim::PimCoreOp, pim::PimCoreBatchOp>(op))
|
if (!isa<pim::PimCoreOp, pim::PimCoreBatchOp>(op))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto analysis = pim::analyzeMemoryCoalescingCandidates(op);
|
auto analysis = pim::analyzeMemoryCoalescingCandidates(op);
|
||||||
auto stats = pim::coalesceMemory(op, analysis, rewriter);
|
auto stats = pim::coalesceMemory(op, analysis, rewriter);
|
||||||
CoalescingReportRow row {
|
CoalescingReportRow row {analysis.getCandidateCount(),
|
||||||
analysis.getCandidateCount(), stats.skippedAllocations, stats.removedAllocs, stats.savedBytes};
|
analysis.getCandidateBytes(),
|
||||||
|
stats.skippedAllocations,
|
||||||
|
stats.removedAllocs,
|
||||||
|
stats.savedBytes};
|
||||||
if (auto coreOp = dyn_cast<pim::PimCoreOp>(op)) {
|
if (auto coreOp = dyn_cast<pim::PimCoreOp>(op)) {
|
||||||
auto checkedCoreId =
|
reportEntries.push_back({"Core " + std::to_string(coreOp.getCoreId()), 1, row});
|
||||||
pim::checkedI32(static_cast<uint64_t>(coreOp.getCoreId()), coreOp, "memory coalescing core id");
|
|
||||||
if (failed(checkedCoreId)) {
|
|
||||||
hasFailure = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
reportEntries.push_back(
|
|
||||||
{CoalescingReportEntry::Kind::Core, static_cast<uint64_t>(coreOp.getCoreId()), {*checkedCoreId}, row});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
SmallVector<int32_t> coreIds = getBatchCoreIds(cast<pim::PimCoreBatchOp>(op));
|
||||||
auto coreIds = getBatchCoreIds(cast<pim::PimCoreBatchOp>(op));
|
reportEntries.push_back(
|
||||||
CoalescingReportEntry entry;
|
{"Batch " + std::to_string(nextBatchId++), std::max<uint64_t>(1, coreIds.size()), row});
|
||||||
entry.kind = CoalescingReportEntry::Kind::Batch;
|
|
||||||
entry.id = nextBatchId++;
|
|
||||||
llvm::append_range(entry.coreIds, coreIds);
|
|
||||||
entry.row = row;
|
|
||||||
reportEntries.push_back(std::move(entry));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (hasFailure) {
|
|
||||||
signalPassFailure();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
emitReport(reportEntries);
|
emitReport(reportEntries);
|
||||||
dumpModule(getOperation(), "pim3_coalesced");
|
dumpModule(getOperation(), "pim3_coalesced");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,9 @@
|
|||||||
#include "src/Accelerators/PIM/Common/IR/SubviewUtils.hpp"
|
#include "src/Accelerators/PIM/Common/IR/SubviewUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/IR/WeightUtils.hpp"
|
#include "src/Accelerators/PIM/Common/IR/WeightUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||||
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/Support/Diagnostics.hpp"
|
#include "src/Accelerators/PIM/Common/Support/Diagnostics.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Pim/Transforms/Bufferization/ContiguityPatterns.hpp"
|
#include "src/Accelerators/PIM/Dialect/Pim/Transforms/Bufferization/ContiguityPatterns.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
@@ -108,6 +109,63 @@ static bool isCoreWeightBlockArgument(Value value) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct VerifiedLocalMemorySlot {
|
||||||
|
uint64_t size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
static LogicalResult
|
||||||
|
verifyLocalMemoryPlan(Operation* coreLikeOp, pim::CappedDiagnosticReporter& diagnostics) {
|
||||||
|
DenseMap<uint64_t, VerifiedLocalMemorySlot> slots;
|
||||||
|
bool hasFailure = false;
|
||||||
|
auto fallbackAttr = coreLikeOp->getAttrOfType<IntegerAttr>(kLocalMemoryFallbackCountAttrName);
|
||||||
|
auto nestedSingleUseAttr = coreLikeOp->getAttrOfType<IntegerAttr>(kLocalMemoryNestedSingleUseCountAttrName);
|
||||||
|
if (!fallbackAttr || !nestedSingleUseAttr || fallbackAttr.getInt() < 0 || nestedSingleUseAttr.getInt() < 0) {
|
||||||
|
diagnostics.report(coreLikeOp, [&](Operation* op) {
|
||||||
|
op->emitError("requires complete non-negative PIM local-memory planning summary attributes");
|
||||||
|
});
|
||||||
|
hasFailure = true;
|
||||||
|
}
|
||||||
|
coreLikeOp->walk([&](memref::AllocOp allocOp) {
|
||||||
|
auto addressAttr = allocOp->getAttrOfType<IntegerAttr>(kLocalMemoryAddressAttrName);
|
||||||
|
auto slotAttr = allocOp->getAttrOfType<IntegerAttr>(kLocalMemorySlotAttrName);
|
||||||
|
auto slotSizeAttr = allocOp->getAttrOfType<IntegerAttr>(kLocalMemorySlotSizeAttrName);
|
||||||
|
if (!addressAttr || !slotAttr || !slotSizeAttr || addressAttr.getInt() < 0 || slotAttr.getInt() < 0
|
||||||
|
|| slotSizeAttr.getInt() < 0) {
|
||||||
|
diagnostics.report(allocOp, [&](Operation*) {
|
||||||
|
allocOp.emitOpError("requires a complete non-negative PIM local-memory plan");
|
||||||
|
});
|
||||||
|
hasFailure = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t address = static_cast<uint64_t>(addressAttr.getInt());
|
||||||
|
uint64_t slotId = static_cast<uint64_t>(slotAttr.getInt());
|
||||||
|
uint64_t slotSize = static_cast<uint64_t>(slotSizeAttr.getInt());
|
||||||
|
auto logicalSize = pim::getCheckedShapedTypeSizeInBytes(
|
||||||
|
cast<ShapedType>(allocOp.getType()), allocOp, "planned local allocation byte size");
|
||||||
|
bool invalidRange = failed(logicalSize) || address > slotSize || *logicalSize > slotSize - address
|
||||||
|
|| slotSize > kPimLocalMemoryAddressLimit || address % 4 != 0 || slotId != 0;
|
||||||
|
if (invalidRange) {
|
||||||
|
diagnostics.report(allocOp, [&](Operation*) {
|
||||||
|
allocOp.emitOpError() << "has an invalid PIM local-memory range: address=" << address
|
||||||
|
<< ", logical size=" << (succeeded(logicalSize) ? *logicalSize : 0)
|
||||||
|
<< ", slot size=" << slotSize;
|
||||||
|
});
|
||||||
|
hasFailure = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [slotIt, inserted] = slots.try_emplace(slotId, VerifiedLocalMemorySlot {slotSize});
|
||||||
|
if (!inserted && slotIt->second.size != slotSize) {
|
||||||
|
diagnostics.report(allocOp, [&](Operation*) {
|
||||||
|
allocOp.emitOpError() << "has inconsistent size for PIM local-memory arena " << slotId;
|
||||||
|
});
|
||||||
|
hasFailure = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return success(!hasFailure);
|
||||||
|
}
|
||||||
|
|
||||||
static bool isSupportedCoreInstructionOp(Operation* op) {
|
static bool isSupportedCoreInstructionOp(Operation* op) {
|
||||||
return isa<pim::PimMemCopyHostToDevOp,
|
return isa<pim::PimMemCopyHostToDevOp,
|
||||||
pim::PimMemCopyDevToHostOp,
|
pim::PimMemCopyDevToHostOp,
|
||||||
@@ -148,8 +206,10 @@ static bool isHostAddressableValue(Value value, const StaticValueKnowledge& know
|
|||||||
return isa_and_nonnull<memref::GetGlobalOp>(base.getDefiningOp());
|
return isa_and_nonnull<memref::GetGlobalOp>(base.getDefiningOp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class CommunicationEventKind {
|
||||||
enum class CommunicationEventKind { Send, Receive };
|
Send,
|
||||||
|
Receive
|
||||||
|
};
|
||||||
|
|
||||||
struct CommunicationEvent {
|
struct CommunicationEvent {
|
||||||
CommunicationEventKind kind = CommunicationEventKind::Send;
|
CommunicationEventKind kind = CommunicationEventKind::Send;
|
||||||
@@ -157,19 +217,6 @@ struct CommunicationEvent {
|
|||||||
int64_t peerCoreId = 0;
|
int64_t peerCoreId = 0;
|
||||||
int64_t size = 0;
|
int64_t size = 0;
|
||||||
uint64_t ordinal = 0;
|
uint64_t ordinal = 0;
|
||||||
std::optional<int64_t> minChannelId;
|
|
||||||
std::string materializer;
|
|
||||||
std::optional<int64_t> traceId;
|
|
||||||
std::optional<int64_t> commOrder;
|
|
||||||
std::optional<int64_t> traceClassId;
|
|
||||||
std::optional<int64_t> traceBlockOrdinal;
|
|
||||||
std::string traceKind;
|
|
||||||
std::string tracePhase;
|
|
||||||
std::string traceClassKind;
|
|
||||||
std::string tracePayload;
|
|
||||||
std::string traceMessages;
|
|
||||||
std::string tracePrevOp;
|
|
||||||
std::string traceNextOp;
|
|
||||||
Operation* op = nullptr;
|
Operation* op = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -183,7 +230,6 @@ constexpr StringLiteral kRaptorMinChannelIdAttr = "raptor.min_channel_id";
|
|||||||
constexpr StringLiteral kRaptorMaterializerAttr = "raptor.materializer";
|
constexpr StringLiteral kRaptorMaterializerAttr = "raptor.materializer";
|
||||||
constexpr StringLiteral kRaptorCommOrderAttr = "raptor.comm_order";
|
constexpr StringLiteral kRaptorCommOrderAttr = "raptor.comm_order";
|
||||||
constexpr StringLiteral kRaptorCommTraceIdAttr = "raptor.comm_trace_id";
|
constexpr StringLiteral kRaptorCommTraceIdAttr = "raptor.comm_trace_id";
|
||||||
constexpr StringLiteral kRaptorCommTraceKindAttr = "raptor.comm_trace_kind";
|
|
||||||
constexpr StringLiteral kRaptorCommTracePhaseAttr = "raptor.comm_trace_phase";
|
constexpr StringLiteral kRaptorCommTracePhaseAttr = "raptor.comm_trace_phase";
|
||||||
constexpr StringLiteral kRaptorCommTraceClassIdAttr = "raptor.comm_trace_class_id";
|
constexpr StringLiteral kRaptorCommTraceClassIdAttr = "raptor.comm_trace_class_id";
|
||||||
constexpr StringLiteral kRaptorCommTraceClassKindAttr = "raptor.comm_trace_class_kind";
|
constexpr StringLiteral kRaptorCommTraceClassKindAttr = "raptor.comm_trace_class_kind";
|
||||||
@@ -225,31 +271,44 @@ static std::string formatOperationSummary(Operation* op) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static std::string formatCommunicationEvent(const CommunicationEvent& event) {
|
static std::string formatCommunicationEvent(const CommunicationEvent& event) {
|
||||||
|
std::optional<int64_t> minChannelId = getNearestIntegerAttr(event.op, kRaptorMinChannelIdAttr);
|
||||||
|
std::optional<int64_t> commOrder = getNearestIntegerAttr(event.op, kRaptorCommOrderAttr);
|
||||||
|
std::optional<int64_t> traceId = getNearestIntegerAttr(event.op, kRaptorCommTraceIdAttr);
|
||||||
|
std::optional<int64_t> traceClassId = getNearestIntegerAttr(event.op, kRaptorCommTraceClassIdAttr);
|
||||||
|
std::optional<int64_t> traceBlockOrdinal = getNearestIntegerAttr(event.op, kRaptorCommTraceBlockOrdinalAttr);
|
||||||
|
std::string materializer = getNearestStringAttr(event.op, kRaptorMaterializerAttr);
|
||||||
|
std::string tracePhase = getNearestStringAttr(event.op, kRaptorCommTracePhaseAttr);
|
||||||
|
std::string traceClassKind = getNearestStringAttr(event.op, kRaptorCommTraceClassKindAttr);
|
||||||
|
std::string tracePayload = getNearestStringAttr(event.op, kRaptorCommTracePayloadAttr);
|
||||||
|
std::string traceMessages = getNearestStringAttr(event.op, kRaptorCommTraceMessagesAttr);
|
||||||
|
std::string tracePrevOp = getNearestStringAttr(event.op, kRaptorCommTracePrevOpAttr);
|
||||||
|
std::string traceNextOp = getNearestStringAttr(event.op, kRaptorCommTraceNextOpAttr);
|
||||||
|
|
||||||
std::string text;
|
std::string text;
|
||||||
llvm::raw_string_ostream os(text);
|
llvm::raw_string_ostream os(text);
|
||||||
os << "core " << event.coreId << " " << getCommunicationEventKindName(event.kind) << " "
|
os << "core " << event.coreId << " " << getCommunicationEventKindName(event.kind) << " "
|
||||||
<< (event.kind == CommunicationEventKind::Send ? "to" : "from") << " " << event.peerCoreId
|
<< (event.kind == CommunicationEventKind::Send ? "to" : "from") << " " << event.peerCoreId << " size "
|
||||||
<< " size " << event.size << "B ordinal " << event.ordinal;
|
<< event.size << "B ordinal " << event.ordinal;
|
||||||
if (event.minChannelId)
|
if (minChannelId)
|
||||||
os << " min_channel " << *event.minChannelId;
|
os << " min_channel " << *minChannelId;
|
||||||
if (event.commOrder)
|
if (commOrder)
|
||||||
os << " comm_order " << *event.commOrder;
|
os << " comm_order " << *commOrder;
|
||||||
if (!event.materializer.empty())
|
if (!materializer.empty())
|
||||||
os << " materializer " << event.materializer;
|
os << " materializer " << materializer;
|
||||||
if (event.traceId)
|
if (traceId)
|
||||||
os << " trace#" << *event.traceId;
|
os << " trace#" << *traceId;
|
||||||
if (!event.tracePhase.empty())
|
if (!tracePhase.empty())
|
||||||
os << " phase " << event.tracePhase;
|
os << " phase " << tracePhase;
|
||||||
if (event.traceClassId)
|
if (traceClassId)
|
||||||
os << " class " << event.traceClassKind << "#" << *event.traceClassId;
|
os << " class " << traceClassKind << "#" << *traceClassId;
|
||||||
if (event.traceBlockOrdinal)
|
if (traceBlockOrdinal)
|
||||||
os << " block_ordinal " << *event.traceBlockOrdinal;
|
os << " block_ordinal " << *traceBlockOrdinal;
|
||||||
if (!event.tracePayload.empty())
|
if (!tracePayload.empty())
|
||||||
os << " payload " << event.tracePayload;
|
os << " payload " << tracePayload;
|
||||||
if (!event.traceMessages.empty())
|
if (!traceMessages.empty())
|
||||||
os << " messages {" << event.traceMessages << "}";
|
os << " messages {" << traceMessages << "}";
|
||||||
if (!event.tracePrevOp.empty() || !event.traceNextOp.empty())
|
if (!tracePrevOp.empty() || !traceNextOp.empty())
|
||||||
os << " inserted_between [" << event.tracePrevOp << " | " << event.traceNextOp << "]";
|
os << " inserted_between [" << tracePrevOp << " | " << traceNextOp << "]";
|
||||||
return os.str();
|
return os.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,10 +320,8 @@ static bool areMatchedCommunicationEvents(const CommunicationEvent& lhs, const C
|
|||||||
|| (lhs.kind == CommunicationEventKind::Receive && rhs.kind == CommunicationEventKind::Send);
|
|| (lhs.kind == CommunicationEventKind::Receive && rhs.kind == CommunicationEventKind::Send);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::optional<size_t>
|
||||||
static std::optional<size_t> findMatchingCounterpartIndex(const CommunicationEventVector& events,
|
findMatchingCounterpartIndex(const CommunicationEventVector& events, const CommunicationEvent& event, size_t begin) {
|
||||||
const CommunicationEvent& event,
|
|
||||||
size_t begin) {
|
|
||||||
for (size_t index = begin; index < events.size(); ++index)
|
for (size_t index = begin; index < events.size(); ++index)
|
||||||
if (areMatchedCommunicationEvents(event, events[index]))
|
if (areMatchedCommunicationEvents(event, events[index]))
|
||||||
return index;
|
return index;
|
||||||
@@ -288,8 +345,7 @@ static void printCounterpartProbe(llvm::raw_ostream& os,
|
|||||||
peerPc = peerPcIt->second;
|
peerPc = peerPcIt->second;
|
||||||
|
|
||||||
os << " counterpart probe for " << formatCommunicationEvent(blockedEvent) << "\n";
|
os << " counterpart probe for " << formatCommunicationEvent(blockedEvent) << "\n";
|
||||||
os << " peer core " << blockedEvent.peerCoreId << " current pc " << peerPc << " of " << peerEvents.size()
|
os << " peer core " << blockedEvent.peerCoreId << " current pc " << peerPc << " of " << peerEvents.size() << "\n";
|
||||||
<< "\n";
|
|
||||||
|
|
||||||
std::optional<size_t> nextMatch = findMatchingCounterpartIndex(peerEvents, blockedEvent, peerPc);
|
std::optional<size_t> nextMatch = findMatchingCounterpartIndex(peerEvents, blockedEvent, peerPc);
|
||||||
std::optional<size_t> anyMatch = findMatchingCounterpartIndex(peerEvents, blockedEvent, 0);
|
std::optional<size_t> anyMatch = findMatchingCounterpartIndex(peerEvents, blockedEvent, 0);
|
||||||
@@ -317,7 +373,8 @@ static void printCounterpartProbe(llvm::raw_ostream& os,
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
os << " peer operations blocking before that counterpart:\n";
|
os << " peer operations blocking before that counterpart:\n";
|
||||||
size_t end = std::min(peerEvents.size(), std::min(*nextMatch + static_cast<size_t>(1), peerPc + static_cast<size_t>(12)));
|
size_t end =
|
||||||
|
std::min(peerEvents.size(), std::min(*nextMatch + static_cast<size_t>(1), peerPc + static_cast<size_t>(12)));
|
||||||
for (size_t index = peerPc; index < end; ++index) {
|
for (size_t index = peerPc; index < end; ++index) {
|
||||||
os << (index == peerPc ? " pc => " : " ") << "#" << index << " "
|
os << (index == peerPc ? " pc => " : " ") << "#" << index << " "
|
||||||
<< formatCommunicationEvent(peerEvents[index]) << "\n";
|
<< formatCommunicationEvent(peerEvents[index]) << "\n";
|
||||||
@@ -327,77 +384,58 @@ static void printCounterpartProbe(llvm::raw_ostream& os,
|
|||||||
os << " ... " << (*nextMatch - end + 1) << " more peer communication event(s) before the counterpart\n";
|
os << " ... " << (*nextMatch - end + 1) << " more peer communication event(s) before the counterpart\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
static CommunicationEvent makeCommunicationEvent(CommunicationEventKind kind,
|
static CommunicationEvent makeCommunicationEvent(
|
||||||
int64_t coreId,
|
CommunicationEventKind kind, int64_t coreId, int64_t peerCoreId, int64_t size, uint64_t ordinal, Operation* op) {
|
||||||
int64_t peerCoreId,
|
return CommunicationEvent {kind, coreId, peerCoreId, size, ordinal, op};
|
||||||
int64_t size,
|
|
||||||
uint64_t ordinal,
|
|
||||||
Operation* op) {
|
|
||||||
return CommunicationEvent {kind,
|
|
||||||
coreId,
|
|
||||||
peerCoreId,
|
|
||||||
size,
|
|
||||||
ordinal,
|
|
||||||
getNearestIntegerAttr(op, kRaptorMinChannelIdAttr),
|
|
||||||
getNearestStringAttr(op, kRaptorMaterializerAttr),
|
|
||||||
getNearestIntegerAttr(op, kRaptorCommTraceIdAttr),
|
|
||||||
getNearestIntegerAttr(op, kRaptorCommOrderAttr),
|
|
||||||
getNearestIntegerAttr(op, kRaptorCommTraceClassIdAttr),
|
|
||||||
getNearestIntegerAttr(op, kRaptorCommTraceBlockOrdinalAttr),
|
|
||||||
getNearestStringAttr(op, kRaptorCommTraceKindAttr),
|
|
||||||
getNearestStringAttr(op, kRaptorCommTracePhaseAttr),
|
|
||||||
getNearestStringAttr(op, kRaptorCommTraceClassKindAttr),
|
|
||||||
getNearestStringAttr(op, kRaptorCommTracePayloadAttr),
|
|
||||||
getNearestStringAttr(op, kRaptorCommTraceMessagesAttr),
|
|
||||||
getNearestStringAttr(op, kRaptorCommTracePrevOpAttr),
|
|
||||||
getNearestStringAttr(op, kRaptorCommTraceNextOpAttr),
|
|
||||||
op};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static LogicalResult appendCoreCommunicationEvents(Block& block,
|
static LogicalResult appendCoreCommunicationEvents(Block& block,
|
||||||
|
const PimCoreCommunicationPlan& plan,
|
||||||
int64_t coreId,
|
int64_t coreId,
|
||||||
const StaticValueKnowledge& initialKnowledge,
|
const StaticValueKnowledge& initialKnowledge,
|
||||||
SmallVectorImpl<CommunicationEvent>& events,
|
SmallVectorImpl<CommunicationEvent>& events,
|
||||||
pim::CappedDiagnosticReporter& diagnostics) {
|
pim::CappedDiagnosticReporter& diagnostics) {
|
||||||
return walkPimCoreBlock(block, initialKnowledge, [&](Operation& op, const StaticValueKnowledge& knowledge) {
|
return walkPimCoreCommunicationBlock(
|
||||||
if (auto sendOp = dyn_cast<pim::PimSendOp>(&op)) {
|
block, plan, initialKnowledge, [&](Operation& op, const StaticValueKnowledge& knowledge) {
|
||||||
auto targetCoreId = resolveIndexValue(sendOp.getTargetCoreId(), knowledge);
|
if (auto sendOp = dyn_cast<pim::PimSendOp>(&op)) {
|
||||||
if (failed(targetCoreId)) {
|
auto targetCoreId = resolveIndexValue(sendOp.getTargetCoreId(), knowledge);
|
||||||
diagnostics.report(&op, [](Operation* illegalOp) {
|
if (failed(targetCoreId)) {
|
||||||
illegalOp->emitOpError("cannot statically resolve send target core for PIM communication deadlock check");
|
diagnostics.report(&op, [](Operation* illegalOp) {
|
||||||
});
|
illegalOp->emitOpError("cannot statically resolve send target core for PIM communication deadlock check");
|
||||||
return failure();
|
});
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
events.push_back(makeCommunicationEvent(CommunicationEventKind::Send,
|
||||||
|
coreId,
|
||||||
|
*targetCoreId,
|
||||||
|
sendOp.getSize(),
|
||||||
|
static_cast<uint64_t>(events.size()),
|
||||||
|
&op));
|
||||||
|
return success();
|
||||||
}
|
}
|
||||||
|
|
||||||
events.push_back(makeCommunicationEvent(CommunicationEventKind::Send,
|
if (auto receiveOp = dyn_cast<pim::PimReceiveOp>(&op)) {
|
||||||
coreId,
|
auto sourceCoreId = resolveIndexValue(receiveOp.getSourceCoreId(), knowledge);
|
||||||
*targetCoreId,
|
if (failed(sourceCoreId)) {
|
||||||
sendOp.getSize(),
|
diagnostics.report(&op, [](Operation* illegalOp) {
|
||||||
static_cast<uint64_t>(events.size()),
|
illegalOp->emitOpError(
|
||||||
&op));
|
"cannot statically resolve receive source core for PIM communication deadlock check");
|
||||||
return success();
|
});
|
||||||
}
|
return failure();
|
||||||
|
}
|
||||||
|
|
||||||
if (auto receiveOp = dyn_cast<pim::PimReceiveOp>(&op)) {
|
events.push_back(makeCommunicationEvent(CommunicationEventKind::Receive,
|
||||||
auto sourceCoreId = resolveIndexValue(receiveOp.getSourceCoreId(), knowledge);
|
coreId,
|
||||||
if (failed(sourceCoreId)) {
|
*sourceCoreId,
|
||||||
diagnostics.report(&op, [](Operation* illegalOp) {
|
receiveOp.getSize(),
|
||||||
illegalOp->emitOpError("cannot statically resolve receive source core for PIM communication deadlock check");
|
static_cast<uint64_t>(events.size()),
|
||||||
});
|
&op));
|
||||||
return failure();
|
return success();
|
||||||
}
|
}
|
||||||
|
|
||||||
events.push_back(makeCommunicationEvent(CommunicationEventKind::Receive,
|
|
||||||
coreId,
|
|
||||||
*sourceCoreId,
|
|
||||||
receiveOp.getSize(),
|
|
||||||
static_cast<uint64_t>(events.size()),
|
|
||||||
&op));
|
|
||||||
return success();
|
return success();
|
||||||
}
|
});
|
||||||
|
|
||||||
return success();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void printCommunicationWindow(llvm::raw_ostream& os,
|
static void printCommunicationWindow(llvm::raw_ostream& os,
|
||||||
@@ -466,9 +504,10 @@ static void emitCommunicationDeadlockCycle(ModuleOp moduleOp,
|
|||||||
ArrayRef<int64_t> cycle) {
|
ArrayRef<int64_t> cycle) {
|
||||||
printCommunicationDeadlockReport(coreEvents, programCounters, cycle);
|
printCommunicationDeadlockReport(coreEvents, programCounters, cycle);
|
||||||
|
|
||||||
auto diagnostic = moduleOp.emitError()
|
auto diagnostic =
|
||||||
<< "PIM communication deadlock check found a blocking send/receive cycle while statically simulating the "
|
moduleOp.emitError()
|
||||||
"expanded per-core communication streams; see the PIM static communication deadlock report above";
|
<< "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";
|
||||||
|
|
||||||
for (int64_t coreId : cycle) {
|
for (int64_t coreId : cycle) {
|
||||||
auto eventsIt = coreEvents.find(coreId);
|
auto eventsIt = coreEvents.find(coreId);
|
||||||
@@ -479,14 +518,15 @@ static void emitCommunicationDeadlockCycle(ModuleOp moduleOp,
|
|||||||
const CommunicationEvent& event = eventsIt->second[pcIt->second];
|
const CommunicationEvent& event = eventsIt->second[pcIt->second];
|
||||||
Diagnostic& note = diagnostic.attachNote(event.op->getLoc());
|
Diagnostic& note = diagnostic.attachNote(event.op->getLoc());
|
||||||
note << formatCommunicationEvent(event);
|
note << formatCommunicationEvent(event);
|
||||||
if (!event.materializer.empty())
|
std::string materializer = getNearestStringAttr(event.op, kRaptorMaterializerAttr);
|
||||||
note << " emitted by " << event.materializer;
|
if (!materializer.empty())
|
||||||
|
note << " emitted by " << materializer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static FailureOr<SmallVector<int64_t>> findCommunicationWaitCycle(
|
static FailureOr<SmallVector<int64_t>>
|
||||||
const DenseMap<int64_t, CommunicationEventVector>& coreEvents,
|
findCommunicationWaitCycle(const DenseMap<int64_t, CommunicationEventVector>& coreEvents,
|
||||||
const DenseMap<int64_t, size_t>& programCounters) {
|
const DenseMap<int64_t, size_t>& programCounters) {
|
||||||
for (const auto& [startCoreId, events] : coreEvents) {
|
for (const auto& [startCoreId, events] : coreEvents) {
|
||||||
auto startPcIt = programCounters.find(startCoreId);
|
auto startPcIt = programCounters.find(startCoreId);
|
||||||
if (startPcIt == programCounters.end() || startPcIt->second >= events.size())
|
if (startPcIt == programCounters.end() || startPcIt->second >= events.size())
|
||||||
@@ -519,7 +559,7 @@ static FailureOr<SmallVector<int64_t>> findCommunicationWaitCycle(
|
|||||||
}
|
}
|
||||||
|
|
||||||
static LogicalResult verifyNoStaticCommunicationDeadlock(ModuleOp moduleOp,
|
static LogicalResult verifyNoStaticCommunicationDeadlock(ModuleOp moduleOp,
|
||||||
pim::CappedDiagnosticReporter& diagnostics) {
|
pim::CappedDiagnosticReporter& diagnostics) {
|
||||||
DenseMap<int64_t, CommunicationEventVector> coreEvents;
|
DenseMap<int64_t, CommunicationEventVector> coreEvents;
|
||||||
bool hasFailure = false;
|
bool hasFailure = false;
|
||||||
|
|
||||||
@@ -530,14 +570,20 @@ static LogicalResult verifyNoStaticCommunicationDeadlock(ModuleOp moduleOp,
|
|||||||
for (Operation& op : funcOp.getBody().front().getOperations()) {
|
for (Operation& op : funcOp.getBody().front().getOperations()) {
|
||||||
if (auto coreOp = dyn_cast<pim::PimCoreOp>(&op)) {
|
if (auto coreOp = dyn_cast<pim::PimCoreOp>(&op)) {
|
||||||
int64_t coreId = coreOp.getCoreId();
|
int64_t coreId = coreOp.getCoreId();
|
||||||
if (failed(appendCoreCommunicationEvents(
|
PimCoreCommunicationPlan plan = buildPimCoreCommunicationPlan(coreOp.getBody().front());
|
||||||
coreOp.getBody().front(), coreId, StaticValueKnowledge {}, coreEvents[coreId], diagnostics)))
|
if (failed(appendCoreCommunicationEvents(coreOp.getBody().front(),
|
||||||
|
plan,
|
||||||
|
coreId,
|
||||||
|
StaticValueKnowledge {},
|
||||||
|
coreEvents[coreId],
|
||||||
|
diagnostics)))
|
||||||
hasFailure = true;
|
hasFailure = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto coreBatchOp = dyn_cast<pim::PimCoreBatchOp>(&op)) {
|
if (auto coreBatchOp = dyn_cast<pim::PimCoreBatchOp>(&op)) {
|
||||||
SmallVector<int32_t> coreIds = getBatchCoreIds(coreBatchOp);
|
SmallVector<int32_t> coreIds = getBatchCoreIds(coreBatchOp);
|
||||||
|
PimCoreCommunicationPlan plan = buildPimCoreCommunicationPlan(coreBatchOp.getBody().front());
|
||||||
size_t laneCount = static_cast<size_t>(coreBatchOp.getLaneCount());
|
size_t laneCount = static_cast<size_t>(coreBatchOp.getLaneCount());
|
||||||
for (size_t lane = 0; lane < laneCount; ++lane) {
|
for (size_t lane = 0; lane < laneCount; ++lane) {
|
||||||
StaticValueKnowledge laneKnowledge;
|
StaticValueKnowledge laneKnowledge;
|
||||||
@@ -546,11 +592,14 @@ static LogicalResult verifyNoStaticCommunicationDeadlock(ModuleOp moduleOp,
|
|||||||
laneKnowledge.aliases[coreBatchOp.getInputArgument(inputIndex)] = coreBatchOp.getInputs()[inputIndex];
|
laneKnowledge.aliases[coreBatchOp.getInputArgument(inputIndex)] = coreBatchOp.getInputs()[inputIndex];
|
||||||
|
|
||||||
SmallVector<int32_t> laneCoreIds = getLaneChunkCoreIds(coreIds, laneCount, static_cast<unsigned>(lane));
|
SmallVector<int32_t> laneCoreIds = getLaneChunkCoreIds(coreIds, laneCount, static_cast<unsigned>(lane));
|
||||||
for (int32_t coreId : laneCoreIds) {
|
for (int32_t coreId : laneCoreIds)
|
||||||
if (failed(appendCoreCommunicationEvents(
|
if (failed(appendCoreCommunicationEvents(coreBatchOp.getBody().front(),
|
||||||
coreBatchOp.getBody().front(), coreId, laneKnowledge, coreEvents[coreId], diagnostics)))
|
plan,
|
||||||
|
coreId,
|
||||||
|
laneKnowledge,
|
||||||
|
coreEvents[coreId],
|
||||||
|
diagnostics)))
|
||||||
hasFailure = true;
|
hasFailure = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -607,9 +656,10 @@ static LogicalResult verifyNoStaticCommunicationDeadlock(ModuleOp moduleOp,
|
|||||||
return failure();
|
return failure();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto diagnostic = moduleOp.emitError()
|
auto diagnostic =
|
||||||
<< "PIM communication deadlock check stalled without finding a closed wait cycle; this usually means a "
|
moduleOp.emitError()
|
||||||
"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/receive peer is missing or ordered after a finished core";
|
||||||
for (const auto& [coreId, events] : coreEvents) {
|
for (const auto& [coreId, events] : coreEvents) {
|
||||||
size_t pc = programCounters[coreId];
|
size_t pc = programCounters[coreId];
|
||||||
if (pc >= events.size())
|
if (pc >= events.size())
|
||||||
@@ -652,6 +702,7 @@ struct VerificationPass : PassWrapper<VerificationPass, OperationPass<ModuleOp>>
|
|||||||
for (Operation& op : funcOp.getBody().front().getOperations()) {
|
for (Operation& op : funcOp.getBody().front().getOperations()) {
|
||||||
if (auto coreOp = dyn_cast<pim::PimCoreOp>(&op)) {
|
if (auto coreOp = dyn_cast<pim::PimCoreOp>(&op)) {
|
||||||
(void) verifyCoreWeights(moduleOp, coreOp, diagnostics);
|
(void) verifyCoreWeights(moduleOp, coreOp, diagnostics);
|
||||||
|
(void) verifyLocalMemoryPlan(coreOp, diagnostics);
|
||||||
StaticValueKnowledge knowledge;
|
StaticValueKnowledge knowledge;
|
||||||
(void) verifyCoreLikeOperands(coreOp, knowledge, diagnostics);
|
(void) verifyCoreLikeOperands(coreOp, knowledge, diagnostics);
|
||||||
continue;
|
continue;
|
||||||
@@ -659,6 +710,7 @@ struct VerificationPass : PassWrapper<VerificationPass, OperationPass<ModuleOp>>
|
|||||||
|
|
||||||
if (auto coreBatchOp = dyn_cast<pim::PimCoreBatchOp>(&op)) {
|
if (auto coreBatchOp = dyn_cast<pim::PimCoreBatchOp>(&op)) {
|
||||||
(void) verifyCoreWeights(moduleOp, coreBatchOp, diagnostics);
|
(void) verifyCoreWeights(moduleOp, coreBatchOp, diagnostics);
|
||||||
|
(void) verifyLocalMemoryPlan(coreBatchOp, diagnostics);
|
||||||
llvm::SmallVector<unsigned, 2> lanes;
|
llvm::SmallVector<unsigned, 2> lanes;
|
||||||
lanes.push_back(0);
|
lanes.push_back(0);
|
||||||
if (coreBatchOp.getLaneCount() > 1)
|
if (coreBatchOp.getLaneCount() > 1)
|
||||||
|
|||||||
@@ -133,6 +133,22 @@ void verifyOctTableSize(size_t nodeCount, size_t processorCount) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool hasHighCrossbarPressure(const ComputeGraph& graph, size_t processorCount, size_t crossbarCapacity) {
|
||||||
|
if (crossbarCapacity > std::numeric_limits<size_t>::max() / processorCount)
|
||||||
|
return false;
|
||||||
|
const size_t threshold = processorCount * crossbarCapacity / 2;
|
||||||
|
CrossbarUsage distinctWeights;
|
||||||
|
for (const ComputeGraphNode& node : graph.nodes) {
|
||||||
|
for (const CrossbarWeight& weight : node.crossbarUsage) {
|
||||||
|
if (!containsCrossbarWeight(distinctWeights, weight))
|
||||||
|
distinctWeights.push_back(weight);
|
||||||
|
if (distinctWeights.size() > threshold)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Time getPeftTransferTime(Time transferCost, size_t sourceProcessor, size_t targetProcessor, size_t processorCount) {
|
Time getPeftTransferTime(Time transferCost, size_t sourceProcessor, size_t targetProcessor, size_t processorCount) {
|
||||||
@@ -145,6 +161,7 @@ MergeScheduleResult runPeftScheduler(const ComputeGraph& graph, const PeftSchedu
|
|||||||
if (processorCount == 0)
|
if (processorCount == 0)
|
||||||
llvm::report_fatal_error("PEFT scheduler: processor count must be positive");
|
llvm::report_fatal_error("PEFT scheduler: processor count must be positive");
|
||||||
MeshModel mesh = MeshModel::infer(processorCount);
|
MeshModel mesh = MeshModel::infer(processorCount);
|
||||||
|
const bool preferCrossbarReuse = hasHighCrossbarPressure(graph, processorCount, options.crossbarCapacity);
|
||||||
|
|
||||||
verifyOctTableSize(nodeCount, processorCount);
|
verifyOctTableSize(nodeCount, processorCount);
|
||||||
std::vector<std::vector<size_t>> reverseLevels = buildReverseLevels(graph);
|
std::vector<std::vector<size_t>> reverseLevels = buildReverseLevels(graph);
|
||||||
@@ -282,6 +299,8 @@ MergeScheduleResult runPeftScheduler(const ComputeGraph& graph, const PeftSchedu
|
|||||||
Time eft = addOrMax(est, computeCost);
|
Time eft = addOrMax(est, computeCost);
|
||||||
Time oeft = addOrMax(eft, oct[task * processorCount + processor]);
|
Time oeft = addOrMax(eft, oct[task * processorCount + processor]);
|
||||||
size_t centerDistance = mesh.getCenterDistance(processor);
|
size_t centerDistance = mesh.getCenterDistance(processor);
|
||||||
|
bool betterCrossbarChoice =
|
||||||
|
preferCrossbarReuse ? overlapCount > bestOverlapCount : overlapCount < bestOverlapCount;
|
||||||
|
|
||||||
if (oeft < bestOeft || (oeft == bestOeft && eft < bestEft)
|
if (oeft < bestOeft || (oeft == bestOeft && eft < bestEft)
|
||||||
|| (oeft == bestOeft && eft == bestEft && est < bestEst)) {
|
|| (oeft == bestOeft && eft == bestEft && est < bestEst)) {
|
||||||
@@ -302,7 +321,7 @@ MergeScheduleResult runPeftScheduler(const ComputeGraph& graph, const PeftSchedu
|
|||||||
bestCenterDistance = centerDistance;
|
bestCenterDistance = centerDistance;
|
||||||
}
|
}
|
||||||
else if (oeft == bestOeft && eft == bestEft && est == bestEst
|
else if (oeft == bestOeft && eft == bestEft && est == bestEst
|
||||||
&& centerDistance == bestCenterDistance && overlapCount < bestOverlapCount) {
|
&& centerDistance == bestCenterDistance && betterCrossbarChoice) {
|
||||||
bestProcessor = processor;
|
bestProcessor = processor;
|
||||||
bestEst = est;
|
bestEst = est;
|
||||||
bestEft = eft;
|
bestEft = eft;
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ std::unique_ptr<mlir::Pass> createTrivialGraphComputeMergePass();
|
|||||||
|
|
||||||
std::unique_ptr<mlir::Pass> createPimHostConstantFoldingPass();
|
std::unique_ptr<mlir::Pass> createPimHostConstantFoldingPass();
|
||||||
|
|
||||||
|
std::unique_ptr<mlir::Pass> createPimLocalMemoryPlanningPass();
|
||||||
|
|
||||||
std::unique_ptr<mlir::Pass> createPimVerificationPass();
|
std::unique_ptr<mlir::Pass> createPimVerificationPass();
|
||||||
|
|
||||||
std::unique_ptr<mlir::Pass> createEmitPimCodePass();
|
std::unique_ptr<mlir::Pass> createEmitPimCodePass();
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ void PimAccelerator::registerPasses(int optLevel) const {
|
|||||||
registerPass(createTrivialGraphComputeMergePass);
|
registerPass(createTrivialGraphComputeMergePass);
|
||||||
registerPass(createMergeComputeNodesPass);
|
registerPass(createMergeComputeNodesPass);
|
||||||
registerPass(createPimHostConstantFoldingPass);
|
registerPass(createPimHostConstantFoldingPass);
|
||||||
|
registerPass(createPimLocalMemoryPlanningPass);
|
||||||
registerPass(createPimVerificationPass);
|
registerPass(createPimVerificationPass);
|
||||||
registerPass(createEmitPimCodePass);
|
registerPass(createEmitPimCodePass);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,11 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include "src/Accelerators/PIM/Compiler/PimMemoryLiveness.hpp"
|
#include "src/Accelerators/PIM/Dialect/Pim/Transforms/LocalMemoryPlanning/LocalMemoryPlanning.hpp"
|
||||||
|
|
||||||
using onnx_mlir::LocalAllocInterval;
|
using onnx_mlir::LocalAllocInterval;
|
||||||
|
using onnx_mlir::CoreMemoryPlan;
|
||||||
|
using onnx_mlir::assignPhysicalSlotAddresses;
|
||||||
using onnx_mlir::planPhysicalSlots;
|
using onnx_mlir::planPhysicalSlots;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -18,12 +20,19 @@ LocalAllocInterval makeInterval(size_t id, size_t size, uint64_t start, uint64_t
|
|||||||
return interval;
|
return interval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t getPeak(llvm::ArrayRef<onnx_mlir::PlannedPhysicalSlot> slots) {
|
||||||
|
size_t peak = 0;
|
||||||
|
for (const auto& slot : slots)
|
||||||
|
peak = std::max(peak, slot.address + slot.requiredSize);
|
||||||
|
return peak;
|
||||||
|
}
|
||||||
|
|
||||||
void assertSingleSlotCase(LocalAllocInterval a, LocalAllocInterval b, size_t expectedSlotSize) {
|
void assertSingleSlotCase(LocalAllocInterval a, LocalAllocInterval b, size_t expectedSlotSize) {
|
||||||
llvm::SmallVector<LocalAllocInterval, 4> intervals = {a, b};
|
llvm::SmallVector<LocalAllocInterval, 4> intervals = {a, b};
|
||||||
auto slots = planPhysicalSlots(intervals);
|
auto slots = planPhysicalSlots(intervals);
|
||||||
assert(slots.size() == 1);
|
assert(slots.size() == 1);
|
||||||
assert(slots.front().requiredSize == expectedSlotSize);
|
assert(slots.front().requiredSize == expectedSlotSize);
|
||||||
assert(intervals[0].physicalSlotId == intervals[1].physicalSlotId);
|
assert(intervals[0].slotPlanIndex == intervals[1].slotPlanIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
int testSameSizeNonOverlap() {
|
int testSameSizeNonOverlap() {
|
||||||
@@ -34,13 +43,21 @@ int testSameSizeNonOverlap() {
|
|||||||
|
|
||||||
int testLargerFirst() {
|
int testLargerFirst() {
|
||||||
std::cout << "testLargerFirst:" << std::endl;
|
std::cout << "testLargerFirst:" << std::endl;
|
||||||
assertSingleSlotCase(makeInterval(0, 100, 0, 10), makeInterval(1, 40, 11, 20), 100);
|
llvm::SmallVector<LocalAllocInterval, 4> intervals = {
|
||||||
|
makeInterval(0, 100, 0, 10), makeInterval(1, 40, 11, 20)};
|
||||||
|
auto slots = planPhysicalSlots(intervals);
|
||||||
|
assert(slots.size() == 2);
|
||||||
|
assert(getPeak(slots) == 100);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int testSmallerFirst() {
|
int testSmallerFirst() {
|
||||||
std::cout << "testSmallerFirst:" << std::endl;
|
std::cout << "testSmallerFirst:" << std::endl;
|
||||||
assertSingleSlotCase(makeInterval(0, 40, 0, 10), makeInterval(1, 100, 11, 20), 100);
|
llvm::SmallVector<LocalAllocInterval, 4> intervals = {
|
||||||
|
makeInterval(0, 40, 0, 10), makeInterval(1, 100, 11, 20)};
|
||||||
|
auto slots = planPhysicalSlots(intervals);
|
||||||
|
assert(slots.size() == 2);
|
||||||
|
assert(getPeak(slots) == 100);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +67,7 @@ int testOverlapNeedsTwoSlots() {
|
|||||||
makeInterval(0, 100, 0, 20), makeInterval(1, 40, 10, 30)};
|
makeInterval(0, 100, 0, 20), makeInterval(1, 40, 10, 30)};
|
||||||
auto slots = planPhysicalSlots(intervals);
|
auto slots = planPhysicalSlots(intervals);
|
||||||
assert(slots.size() == 2);
|
assert(slots.size() == 2);
|
||||||
assert(intervals[0].physicalSlotId != intervals[1].physicalSlotId);
|
assert(intervals[0].slotPlanIndex != intervals[1].slotPlanIndex);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,10 +76,31 @@ int testReuseChain() {
|
|||||||
llvm::SmallVector<LocalAllocInterval, 4> intervals = {
|
llvm::SmallVector<LocalAllocInterval, 4> intervals = {
|
||||||
makeInterval(0, 40, 0, 10), makeInterval(1, 100, 11, 20), makeInterval(2, 20, 21, 30)};
|
makeInterval(0, 40, 0, 10), makeInterval(1, 100, 11, 20), makeInterval(2, 20, 21, 30)};
|
||||||
auto slots = planPhysicalSlots(intervals);
|
auto slots = planPhysicalSlots(intervals);
|
||||||
assert(slots.size() == 1);
|
assert(slots.size() == 3);
|
||||||
assert(slots.front().requiredSize == 100);
|
assert(getPeak(slots) == 100);
|
||||||
assert(intervals[0].physicalSlotId == intervals[1].physicalSlotId);
|
return 0;
|
||||||
assert(intervals[1].physicalSlotId == intervals[2].physicalSlotId);
|
}
|
||||||
|
|
||||||
|
int testPartialAddressReuse() {
|
||||||
|
std::cout << "testPartialAddressReuse:" << std::endl;
|
||||||
|
llvm::SmallVector<LocalAllocInterval, 4> intervals = {
|
||||||
|
makeInterval(0, 100, 0, 10), makeInterval(1, 60, 11, 20), makeInterval(2, 40, 11, 20)};
|
||||||
|
auto slots = planPhysicalSlots(intervals);
|
||||||
|
assert(getPeak(slots) == 100);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int testAddressAssignment() {
|
||||||
|
std::cout << "testAddressAssignment:" << std::endl;
|
||||||
|
CoreMemoryPlan plan;
|
||||||
|
plan.intervals = {makeInterval(0, 100, 0, 20), makeInterval(1, 40, 10, 30)};
|
||||||
|
plan.slots = planPhysicalSlots(plan.intervals);
|
||||||
|
assert(mlir::succeeded(assignPhysicalSlotAddresses(plan, 1024)));
|
||||||
|
assert(plan.slots.size() == 2);
|
||||||
|
const auto& firstSlot = plan.slots[plan.intervals[0].slotPlanIndex];
|
||||||
|
const auto& secondSlot = plan.slots[plan.intervals[1].slotPlanIndex];
|
||||||
|
assert(firstSlot.address == 0 && firstSlot.requiredSize == 100);
|
||||||
|
assert(secondSlot.address == 100 && secondSlot.requiredSize == 40);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +116,8 @@ int main(int argc, char *argv[]) {
|
|||||||
failures += testSmallerFirst();
|
failures += testSmallerFirst();
|
||||||
failures += testOverlapNeedsTwoSlots();
|
failures += testOverlapNeedsTwoSlots();
|
||||||
failures += testReuseChain();
|
failures += testReuseChain();
|
||||||
|
failures += testPartialAddressReuse();
|
||||||
|
failures += testAddressAssignment();
|
||||||
if (failures != 0) {
|
if (failures != 0) {
|
||||||
std::cerr << failures << " test failures\n";
|
std::cerr << failures << " test failures\n";
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ PIM_PASS_LABELS = (
|
|||||||
("SpatialToPimPass", "Spatial to PIM"),
|
("SpatialToPimPass", "Spatial to PIM"),
|
||||||
("PimBufferizationPass", "Bufferize PIM"),
|
("PimBufferizationPass", "Bufferize PIM"),
|
||||||
("HostConstantFoldingPass", "Fold Host Constants"),
|
("HostConstantFoldingPass", "Fold Host Constants"),
|
||||||
|
("PimMemoryCoalescingPass", "Coalesce Local Memory"),
|
||||||
|
("PimLocalMemoryPlanningPass", "Plan Local Memory"),
|
||||||
("VerificationPass", "Verify PIM"),
|
("VerificationPass", "Verify PIM"),
|
||||||
("EmitPimCodePass", "Emit PIM Code"),
|
("EmitPimCodePass", "Emit PIM Code"),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -250,9 +250,9 @@ def main():
|
|||||||
parser.add_argument("--raptor-path", type=Path, default=defaults["raptor_path"])
|
parser.add_argument("--raptor-path", type=Path, default=defaults["raptor_path"])
|
||||||
parser.add_argument("--onnx-include-dir", type=Path, default=defaults["onnx_include_dir"])
|
parser.add_argument("--onnx-include-dir", type=Path, default=defaults["onnx_include_dir"])
|
||||||
parser.add_argument("--simulator-dir", type=Path, default=defaults["simulator_dir"])
|
parser.add_argument("--simulator-dir", type=Path, default=defaults["simulator_dir"])
|
||||||
parser.add_argument("--crossbar-size", type=int, default=2048)
|
parser.add_argument("--crossbar-size", type=int, default=128)
|
||||||
parser.add_argument("--crossbar-count", type=int, default=256)
|
parser.add_argument("--crossbar-count", type=int, default=64)
|
||||||
parser.add_argument("--core-count", type=int, default=1000)
|
parser.add_argument("--core-count", type=int, default=144)
|
||||||
parser.add_argument("--top-k", type=int, default=5)
|
parser.add_argument("--top-k", type=int, default=5)
|
||||||
parser.add_argument("--command-timeout-seconds", type=float, default=7200.0)
|
parser.add_argument("--command-timeout-seconds", type=float, default=7200.0)
|
||||||
parser.add_argument("--skip-compile", action="store_true")
|
parser.add_argument("--skip-compile", action="store_true")
|
||||||
|
|||||||
@@ -198,9 +198,9 @@ def main():
|
|||||||
parser.add_argument("--raptor-path", type=Path, default=defaults["raptor_path"])
|
parser.add_argument("--raptor-path", type=Path, default=defaults["raptor_path"])
|
||||||
parser.add_argument("--onnx-include-dir", type=Path, default=defaults["onnx_include_dir"])
|
parser.add_argument("--onnx-include-dir", type=Path, default=defaults["onnx_include_dir"])
|
||||||
parser.add_argument("--simulator-dir", type=Path, default=defaults["simulator_dir"])
|
parser.add_argument("--simulator-dir", type=Path, default=defaults["simulator_dir"])
|
||||||
parser.add_argument("--crossbar-size", type=int, default=2048)
|
parser.add_argument("--crossbar-size", type=int, default=128)
|
||||||
parser.add_argument("--crossbar-count", type=int, default=256)
|
parser.add_argument("--crossbar-count", type=int, default=64)
|
||||||
parser.add_argument("--core-count", type=int, default=1000)
|
parser.add_argument("--core-count", type=int, default=144)
|
||||||
parser.add_argument("--command-timeout-seconds", type=float, default=7200.0)
|
parser.add_argument("--command-timeout-seconds", type=float, default=7200.0)
|
||||||
parser.add_argument("--skip-compile", action="store_true")
|
parser.add_argument("--skip-compile", action="store_true")
|
||||||
parser.add_argument("--verbose", action="store_true")
|
parser.add_argument("--verbose", action="store_true")
|
||||||
|
|||||||
@@ -399,9 +399,9 @@ def main():
|
|||||||
parser.add_argument("--remote-project", default="/home/gmagnani/Project/Raptor")
|
parser.add_argument("--remote-project", default="/home/gmagnani/Project/Raptor")
|
||||||
parser.add_argument("--remote-python", default="/home/gmagnani/venv/bin/python")
|
parser.add_argument("--remote-python", default="/home/gmagnani/venv/bin/python")
|
||||||
parser.add_argument("--network-dir", default="validation/networks/yolo11n/depth_51")
|
parser.add_argument("--network-dir", default="validation/networks/yolo11n/depth_51")
|
||||||
parser.add_argument("--crossbar-size", type=int, default=2048)
|
parser.add_argument("--crossbar-size", type=int, default=128)
|
||||||
parser.add_argument("--crossbar-count", type=int, default=256)
|
parser.add_argument("--crossbar-count", type=int, default=64)
|
||||||
parser.add_argument("--core-count", type=int, default=1000)
|
parser.add_argument("--core-count", type=int, default=144)
|
||||||
parser.add_argument("--command-timeout-seconds", type=int, default=7200)
|
parser.add_argument("--command-timeout-seconds", type=int, default=7200)
|
||||||
parser.add_argument("--skip-compile", action="store_true")
|
parser.add_argument("--skip-compile", action="store_true")
|
||||||
parser.add_argument("--annotated-dir", default="validation/networks/yolo11n/depth_51/real_image_validation/annotated")
|
parser.add_argument("--annotated-dir", default="validation/networks/yolo11n/depth_51/real_image_validation/annotated")
|
||||||
|
|||||||
@@ -72,10 +72,9 @@ def main():
|
|||||||
ap.add_argument("--relative-threshold", type=float, default=1e-5,
|
ap.add_argument("--relative-threshold", type=float, default=1e-5,
|
||||||
help="Relative tolerance for per-element output comparison.")
|
help="Relative tolerance for per-element output comparison.")
|
||||||
ap.add_argument("--seed", type=int, default=0, help="RNG seed for generated validation inputs.")
|
ap.add_argument("--seed", type=int, default=0, help="RNG seed for generated validation inputs.")
|
||||||
ap.add_argument("--crossbar-size", type=int, default=64)
|
ap.add_argument("--crossbar-size", type=int, default=128)
|
||||||
ap.add_argument("--crossbar-count", type=int, default=8)
|
ap.add_argument("--crossbar-count", type=int, default=64)
|
||||||
ap.add_argument("--core-count", type=int, default=None,
|
ap.add_argument("--core-count", type=int, default=144)
|
||||||
help="Core count to pass to Raptor. Required for PIM validation.")
|
|
||||||
ap.add_argument("--pim-memory-report", choices=("none", "summary", "full"), default="none",
|
ap.add_argument("--pim-memory-report", choices=("none", "summary", "full"), default="none",
|
||||||
help="Emit a human-readable PIM memory planning report during codegen.")
|
help="Emit a human-readable PIM memory planning report during codegen.")
|
||||||
ap.add_argument("--raptor-extra-arg", action="append", default=[],
|
ap.add_argument("--raptor-extra-arg", action="append", default=[],
|
||||||
@@ -120,8 +119,6 @@ def main():
|
|||||||
missing_args.append("--raptor-path")
|
missing_args.append("--raptor-path")
|
||||||
if not a.onnx_include_dir:
|
if not a.onnx_include_dir:
|
||||||
missing_args.append("--onnx-include-dir")
|
missing_args.append("--onnx-include-dir")
|
||||||
if a.core_count is None:
|
|
||||||
missing_args.append("--core-count")
|
|
||||||
if missing_args:
|
if missing_args:
|
||||||
ap.error("the following arguments are required unless --clean is used: " + ", ".join(missing_args))
|
ap.error("the following arguments are required unless --clean is used: " + ", ".join(missing_args))
|
||||||
|
|
||||||
|
|||||||
@@ -311,7 +311,7 @@ def validate_outputs(sim_arrays, runner_out_dir, outputs_descriptor, threshold=1
|
|||||||
|
|
||||||
|
|
||||||
def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
|
def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
|
||||||
simulator_dir, crossbar_size=64, crossbar_count=8, core_count=None,
|
simulator_dir, crossbar_size=128, crossbar_count=64, core_count=144,
|
||||||
pim_memory_report="none", raptor_extra_args=None,
|
pim_memory_report="none", raptor_extra_args=None,
|
||||||
threshold=1e-3, rtol=1e-5,
|
threshold=1e-3, rtol=1e-5,
|
||||||
seed=0, reporter=None, model_index=1, model_total=1, verbose=False,
|
seed=0, reporter=None, model_index=1, model_total=1, verbose=False,
|
||||||
|
|||||||
Reference in New Issue
Block a user