Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e468b58c8 | |||
| a893d23a74 | |||
| 961fd613bd | |||
| 2d17b50ca8 | |||
| dbb66be93e | |||
| 5c94e00f43 | |||
| 6bad9a8008 | |||
| ab54243fda | |||
| 5f42da36ae | |||
| ae67d720c6 | |||
| d788178749 | |||
| c744f388dc | |||
| 51fdb830e5 | |||
| d1a29ace3c | |||
| 61e3ea9996 | |||
| fed6d343e5 | |||
| 871fcfa832 | |||
| 1f4f58de1c | |||
| 8338caf3f3 | |||
| 47f6715296 | |||
| 2bfc033af9 | |||
| 83a54e28e4 | |||
| cc9b025a35 |
@@ -0,0 +1,362 @@
|
||||
# Graph Compute Batch Physical-Fragment Invariant
|
||||
|
||||
## Status
|
||||
|
||||
This document is **normative** for Raptor's Spatial graph IR.
|
||||
|
||||
Every developer or coding agent modifying Spatial graph construction, graph
|
||||
verification, Blueprint handling, or `MergeComputeNodes` must read this file
|
||||
after `README.md` and `AGENTS.md`.
|
||||
|
||||
`AGENTS.md` must reference this invariant:
|
||||
|
||||
```text
|
||||
* `.agents/invariants/GRAPH_COMPUTE_BATCH_INVARIANT.md`
|
||||
```
|
||||
|
||||
## Scope
|
||||
|
||||
This invariant applies to:
|
||||
|
||||
- `spat.graph_compute_batch`;
|
||||
- graph-level values produced by it;
|
||||
- `tensor.parallel_insert_slice` operations that publish its lane results;
|
||||
- `spat.blueprint` operations that describe logical reconstruction;
|
||||
- graph analyses and transformations that consume those values;
|
||||
- the graph-to-scheduled transition in `MergeComputeNodes`.
|
||||
|
||||
It does **not** impose the same representation on:
|
||||
|
||||
- `spat.scheduled_compute`;
|
||||
- `spat.scheduled_compute_batch`;
|
||||
- `pim.core` or `pim.core_batch`;
|
||||
- values whose cross-core movement is already represented by explicit
|
||||
`spat.channel_send` and `spat.channel_receive` operations.
|
||||
|
||||
Scheduled IR represents execution on assigned cores. Communication and value
|
||||
availability there are defined by local SSA forwarding and explicit
|
||||
send/receive operations, not by the graph physical-fragment invariant.
|
||||
|
||||
## Core invariant
|
||||
|
||||
For every result of a `spat.graph_compute_batch` with `N` graph lanes:
|
||||
|
||||
1. Every graph lane produces exactly one fragment for that result.
|
||||
2. All lanes produce fragments with the same exact ranked tensor type `F`.
|
||||
3. The graph result is a physical collection of those fragments with type:
|
||||
|
||||
```text
|
||||
tensor<N x shape(F) x element-type(F)>
|
||||
```
|
||||
|
||||
Conceptually, the result is `N × F`: one leading physical fragment-slot
|
||||
dimension followed by the complete per-lane fragment shape.
|
||||
4. Physical slot `i` identifies a fragment publication. It does not, by itself,
|
||||
identify a row, column, channel, tile, or any other logical tensor position.
|
||||
5. The result type carries no logical reconstruction order.
|
||||
|
||||
The leading dimension is therefore a **physical fragment-slot dimension**, not
|
||||
a logical tensor dimension.
|
||||
|
||||
## Per-lane computation is unrestricted
|
||||
|
||||
The invariant constrains the published result representation, not what a lane
|
||||
may compute.
|
||||
|
||||
A graph lane may:
|
||||
|
||||
- read several input slices;
|
||||
- perform reductions;
|
||||
- add or combine multiple columns;
|
||||
- execute matrix/vector operations;
|
||||
- produce a fragment that corresponds to any logical region;
|
||||
- participate in a multi-stage or logarithmic reduction tree implemented by
|
||||
following `spat.graph_compute` or `spat.graph_compute_batch` operations.
|
||||
|
||||
Arithmetic combination is graph computation. `spat.blueprint` is not an
|
||||
arithmetic reduction operation.
|
||||
|
||||
### Example: `16×4 -> 16×2`
|
||||
|
||||
Two graph lanes may compute:
|
||||
|
||||
```text
|
||||
lane 0: input[:, 0] + input[:, 1] -> tensor<16x1>
|
||||
lane 1: input[:, 2] + input[:, 3] -> tensor<16x1>
|
||||
```
|
||||
|
||||
The physical graph result is:
|
||||
|
||||
```text
|
||||
tensor<2x16x1>
|
||||
```
|
||||
|
||||
A Blueprint then maps:
|
||||
|
||||
```text
|
||||
physical slot 0 -> logical output[:, 0:1]
|
||||
physical slot 1 -> logical output[:, 1:2]
|
||||
```
|
||||
|
||||
and describes the logical result `tensor<16x2>`.
|
||||
|
||||
For a larger reduction, following graph compute batches may reduce fragments in
|
||||
`ceil(log2(N))` stages. Every intermediate batch still publishes a physical
|
||||
`batch × fragment` collection.
|
||||
|
||||
## Physical publication inside `spat.graph_compute_batch`
|
||||
|
||||
The batch body must publish each lane's fragment into the physical result.
|
||||
|
||||
For one result with fragment type `F`, the corresponding
|
||||
`tensor.parallel_insert_slice` must insert the fragment into one slot of the
|
||||
physical `N × F` destination:
|
||||
|
||||
```text
|
||||
physical offsets = [slot, 0, 0, ...]
|
||||
physical sizes = [1, shape(F)...]
|
||||
physical strides = [1, 1, 1, ...]
|
||||
```
|
||||
|
||||
The slot may be the graph lane directly or a statically analyzable permutation
|
||||
of it. The insertion describes physical slot placement only. It must not use a
|
||||
logical output dimension as the physical batch dimension.
|
||||
|
||||
For each graph result, the body must contain exactly one physical publication
|
||||
per graph lane. Since the body executes once per lane, this normally means one
|
||||
`tensor.parallel_insert_slice` operation targeting that result.
|
||||
|
||||
## Logical reconstruction
|
||||
|
||||
Logical reconstruction is separate from physical publication.
|
||||
|
||||
The reconstruction descriptor defines, for every physical fragment slot:
|
||||
|
||||
- which physical batch operand owns the fragment;
|
||||
- which physical slot contains it;
|
||||
- its destination offsets in the logical tensor;
|
||||
- its destination sizes;
|
||||
- its destination strides;
|
||||
- coverage and conflict policy where relevant.
|
||||
|
||||
The persistent owner of this information is `spat.blueprint` or an equivalent
|
||||
explicit graph-level reconstruction operation.
|
||||
|
||||
A logical consumer must not infer reconstruction from the physical tensor type
|
||||
or assume that physical slot order equals logical order.
|
||||
|
||||
The logical mapping may be arbitrary. For example:
|
||||
|
||||
```text
|
||||
physical slot 0 -> logical row 13
|
||||
physical slot 1 -> logical row 4
|
||||
physical slot 2 -> logical row 10
|
||||
```
|
||||
|
||||
The physical result remains a regular `batch × fragment` tensor.
|
||||
|
||||
## Relationship between `parallel_insert_slice` and Blueprint
|
||||
|
||||
During graph construction, an algorithm may naturally describe logical
|
||||
placement with `tensor.parallel_insert_slice` geometry. Before the graph is in
|
||||
its canonical form:
|
||||
|
||||
1. that geometry must be separated from physical fragment publication;
|
||||
2. the graph batch result must be normalized to `N × F`;
|
||||
3. the logical insertion geometry must be transferred to a persistent
|
||||
`spat.blueprint` reconstruction descriptor.
|
||||
|
||||
After normalization:
|
||||
|
||||
- `parallel_insert_slice` inside `spat.graph_compute_batch` publishes into
|
||||
physical fragment slots;
|
||||
- `spat.blueprint` describes reconstruction into the logical tensor.
|
||||
|
||||
The original graph operation may be erased only after all reconstruction
|
||||
information needed by later stages has a persistent owner.
|
||||
|
||||
## Blueprint semantics
|
||||
|
||||
Blueprint is placement/reconstruction metadata. It may:
|
||||
|
||||
- concatenate fragments;
|
||||
- reorder fragments;
|
||||
- insert fragments into arbitrary disjoint logical regions;
|
||||
- describe complete or partial logical coverage;
|
||||
- expose a logical tensor view when materialization is required.
|
||||
|
||||
Blueprint must not silently perform arithmetic such as addition, multiplication,
|
||||
maximum, or reduction. Such transformations must be represented by following
|
||||
`spat.graph_compute` or `spat.graph_compute_batch` operations.
|
||||
|
||||
A Blueprint consuming a physical fragment batch must explicitly identify the
|
||||
physical source slot for every logical fragment. It must not derive that slot
|
||||
from operand order unless that convention is explicitly represented and
|
||||
verified.
|
||||
|
||||
## Multiple results
|
||||
|
||||
A `spat.graph_compute_batch` may have several results.
|
||||
|
||||
For each result `r` independently:
|
||||
|
||||
- every lane produces one fragment of type `F_r`;
|
||||
- the graph result type is `N × F_r`;
|
||||
- its physical publication and logical reconstruction descriptor are verified
|
||||
independently.
|
||||
|
||||
Different results may use different fragment shapes.
|
||||
|
||||
## Graph consumers
|
||||
|
||||
A graph consumer of a batch result may:
|
||||
|
||||
1. consume fragments directly as physical fragments;
|
||||
2. select one or more physical slots in a `spat.deferred_communication` body;
|
||||
3. use a Blueprint to obtain or describe a logical reconstruction;
|
||||
4. feed fragments to following graph computes or graph compute batches.
|
||||
|
||||
A consumer must not treat the leading physical slot dimension as a logical
|
||||
model dimension unless an explicit graph operation intentionally performs such
|
||||
an interpretation.
|
||||
|
||||
All constant selection, slicing, reshaping, concatenation, and other
|
||||
compile-time shaping needed for a scheduled consumer must be encoded inside the
|
||||
corresponding `spat.deferred_communication` body. Phase 2 must not recover
|
||||
missing graph semantics by inspecting consumers after the deferred operation.
|
||||
|
||||
## Graph lane, scheduled lane, and physical core are different identities
|
||||
|
||||
These concepts must never be conflated:
|
||||
|
||||
- **graph lane**: the lane of the original `spat.graph_compute_batch`;
|
||||
- **physical fragment slot**: the slot in the graph batch result;
|
||||
- **scheduled lane**: one lane of a `spat.scheduled_compute_batch` equivalence
|
||||
class;
|
||||
- **physical core**: the core selected by PEFT.
|
||||
|
||||
The graph batch body or its Blueprint defines graph-lane-to-fragment-slot and
|
||||
fragment-slot-to-logical-region mappings.
|
||||
|
||||
PEFT defines graph-instance-to-core placement.
|
||||
|
||||
Scheduled communication defines how values move between cores.
|
||||
|
||||
## Scheduled IR exclusion
|
||||
|
||||
Do not add a verifier requiring `spat.scheduled_compute_batch` results to have
|
||||
`laneCount` as their first dimension.
|
||||
|
||||
Do not rewrite scheduled values merely to resemble graph physical fragment
|
||||
collections.
|
||||
|
||||
When lowering graph IR into scheduled IR:
|
||||
|
||||
- resolve graph fragments and reconstruction metadata before erasing their
|
||||
graph owners;
|
||||
- create local forwarding or `spat.channel_send`/`spat.channel_receive` for
|
||||
cross-core dependencies;
|
||||
- allow scheduled result representation to follow the scheduled IR contract;
|
||||
- preserve numerical and deadlock correctness.
|
||||
|
||||
The graph invariant is an input contract for scheduling, not a scheduled-value
|
||||
layout contract.
|
||||
|
||||
## Required verifier properties
|
||||
|
||||
`spat.graph_compute_batch` verification must establish, for every result:
|
||||
|
||||
1. the result is a static or otherwise supported ranked tensor;
|
||||
2. result rank is exactly `fragment rank + 1`;
|
||||
3. result dimension 0 equals `laneCount`;
|
||||
4. every lane publication source has the same exact fragment type;
|
||||
5. the physical insertion targets the corresponding result block argument;
|
||||
6. physical insertion offsets have the fragment slot in dimension 0;
|
||||
7. all remaining physical offsets are zero;
|
||||
8. physical sizes are `[1] + fragment shape`;
|
||||
9. physical strides are unit;
|
||||
10. exactly one publication is defined for each graph result in the per-lane
|
||||
body.
|
||||
|
||||
These checks apply only to `spat.graph_compute_batch`, not to
|
||||
`spat.scheduled_compute_batch`.
|
||||
|
||||
Blueprint verification must establish that every logical reconstruction entry:
|
||||
|
||||
- references an existing physical batch operand;
|
||||
- references a valid physical fragment slot;
|
||||
- maps a fragment compatible with the declared logical slice;
|
||||
- stays within logical bounds;
|
||||
- follows the declared conflict and coverage policies.
|
||||
|
||||
## Invalid representations
|
||||
|
||||
The following are invariant violations.
|
||||
|
||||
### Logical aggregate returned directly by graph batch
|
||||
|
||||
```text
|
||||
laneCount = 16
|
||||
result = tensor<1x4x16x16>
|
||||
```
|
||||
|
||||
with each lane inserting into logical dimension 2.
|
||||
|
||||
This is a logical assembly masquerading as a graph batch result. The graph
|
||||
result must instead be `16 × per-row-fragment`, and a Blueprint must describe
|
||||
placement into `tensor<1x4x16x16>`.
|
||||
|
||||
### Physical storage derived from logical destination shape
|
||||
|
||||
Code equivalent to:
|
||||
|
||||
```cpp
|
||||
shape = logicalDestinationType.getShape();
|
||||
shape[logicalInsertionDimension] = laneCount;
|
||||
```
|
||||
|
||||
is invalid.
|
||||
|
||||
Physical graph storage must be derived from the per-lane fragment type:
|
||||
|
||||
```cpp
|
||||
physicalShape = [laneCount] + fragmentType.getShape();
|
||||
```
|
||||
|
||||
### Reconstruction inferred from result type
|
||||
|
||||
It is invalid to assume that physical slot `i` belongs at logical offset `i`.
|
||||
The Blueprint or another explicit reconstruction descriptor must state the
|
||||
mapping.
|
||||
|
||||
### Blueprint used for arithmetic
|
||||
|
||||
It is invalid to encode `fragment0 + fragment1` as Blueprint reconstruction.
|
||||
Create a following graph compute or graph compute batch for the addition.
|
||||
|
||||
## Ownership
|
||||
|
||||
- ONNX-to-Spatial lowering owns creation of valid graph fragment batches.
|
||||
- Graph canonicalization owns normalization of temporary logical-assembly forms
|
||||
into physical graph batches plus Blueprints.
|
||||
- `spat.graph_compute_batch` verifier rejects invalid physical publications.
|
||||
- `spat.blueprint` owns persistent logical reconstruction metadata.
|
||||
- Deferred communication Phase 1 owns complete consumer-side constant shaping.
|
||||
- Merge scheduling consumes this graph contract and introduces explicit
|
||||
communication.
|
||||
- Scheduled IR verifiers validate scheduled execution and communication, not
|
||||
the graph fragment representation.
|
||||
|
||||
## No repair downstream
|
||||
|
||||
If graph IR violates this invariant, fix the graph producer or graph
|
||||
canonicalization.
|
||||
|
||||
Do not repair an invalid graph batch by:
|
||||
|
||||
- guessing a lane dimension in `MergeComputeNodes`;
|
||||
- deriving physical storage from a logical destination tensor;
|
||||
- inspecting deferred-result users;
|
||||
- reconstructing omitted Blueprint data after graph erasure;
|
||||
- weakening graph verifiers;
|
||||
- imposing the graph representation on scheduled operations.
|
||||
@@ -0,0 +1,20 @@
|
||||
# Parallelism and Runtime Invariant
|
||||
|
||||
Compile-time optimization must not trade away execution parallelism or worsen
|
||||
theoretical runtime behavior. The absence of a precise runtime measurement does
|
||||
not make such a trade acceptable.
|
||||
|
||||
In particular, a compile-time optimization must not:
|
||||
|
||||
- serialize work that can execute independently;
|
||||
- coarsen lane, core, batch, or scheduling granularity in a way that reduces
|
||||
available execution parallelism;
|
||||
- increase the theoretical runtime critical path, instruction count, memory
|
||||
traffic, synchronization, or required copies merely to reduce compiler work;
|
||||
- merge independently scheduled work when doing so may reduce concurrency.
|
||||
|
||||
Compiler representations may share analysis, planning, or code-generation
|
||||
work only when the emitted execution retains the same independent work,
|
||||
schedule semantics, and available parallelism. If runtime impact cannot be
|
||||
measured precisely, it must be established from the transformation's semantics;
|
||||
uncertainty is not permission to accept a possible runtime regression.
|
||||
@@ -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.
|
||||
+3
-1
@@ -11,4 +11,6 @@ build_*
|
||||
compile.sh
|
||||
pimcomp_utils/*
|
||||
|
||||
**/__*
|
||||
*.zip
|
||||
**/*.zip
|
||||
**/__pycache__/
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
* Always read the full README.md before doing anything
|
||||
|
||||
# Required project invariants
|
||||
|
||||
Before modifying the relevant subsystem, read:
|
||||
|
||||
* `.agents/invariants/GRAPH_COMPUTE_BATCH_INVARIANT.md`
|
||||
* `.agents/invariants/PERFORMANCE_OPTIMIZATION_INVARIANT.md`
|
||||
* Build commands:
|
||||
* `cmake --build ./build_release`
|
||||
* `cmake --build ./build_debug`
|
||||
@@ -164,6 +171,10 @@ Do not refactor when:
|
||||
* 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
|
||||
* 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
|
||||
|
||||
|
||||
@@ -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
|
||||
bufferization interfaces.
|
||||
|
||||
5. **Static memory coalescing**
|
||||
(`src/PIM/Dialect/Pim/Transforms/StaticMemoryCoalescing`).
|
||||
Reuses compatible local memref allocations inside PIM cores before codegen.
|
||||
|
||||
6. **PIM code generation** (`src/PIM/Pass/PimCodegen` and
|
||||
5. **Safe IR memory coalescing**
|
||||
(`src/PIM/Dialect/Pim/Transforms/MemoryCoalescing`).
|
||||
Normalizes compatible block-local allocations before final planning.
|
||||
6. **PIM local-memory planning**
|
||||
(`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`).
|
||||
Folds host constants, materializes remaining host constants, verifies PIM IR,
|
||||
emits `.pim` core files, writes weights, and writes `memory.bin` /
|
||||
`config.json`.
|
||||
Verifies the memory plan and other PIM invariants, then emits `.pim` core
|
||||
files, weights, and `memory.bin` / `config.json` without rerunning liveness.
|
||||
|
||||
Supporting pieces:
|
||||
- `src/PIM/Common` - shared IR, filesystem, diagnostics, reports, and utility
|
||||
helpers.
|
||||
- `src/PIM/Compiler` - PIM compiler options, memory/address planning, binary
|
||||
- `src/PIM/Compiler` - PIM compiler options, planned-address materialization, binary
|
||||
instruction format, artifact writing, weight emission, and codegen entry
|
||||
points.
|
||||
- `src/PIM/Conversion/SpatialToGraphviz` - optional Spatial graphviz conversion
|
||||
@@ -97,23 +99,40 @@ Pass these to `onnx-mlir` when compiling for PIM:
|
||||
`--EmitPimCodegen` - stop the PIM pipeline at the requested stage. The PIM
|
||||
default is `--EmitPimCodegen`.
|
||||
- `--core-count=<N>` - required positive core count for PIM compilation.
|
||||
- `--crossbar-size=<N>` - crossbar width/height. Default in code is `2`.
|
||||
- `--crossbar-count=<N>` - crossbars per core. Default in code is `256`.
|
||||
- `--pim-merge-scheduler=peft` - merge scheduler. `peft` is the only accepted
|
||||
value in the current code.
|
||||
- `--crossbar-size=<N>` - crossbar width/height. Default in code is `128`.
|
||||
- `--crossbar-count=<N>` - crossbars per core. Default in code is `64`.
|
||||
- `--pim-only-codegen` - assume input is already bufferized PIM IR and only run
|
||||
the codegen tail.
|
||||
- `--pim-emit-json` - also emit `core_*.json` instruction files alongside
|
||||
`core_*.pim`.
|
||||
- `--pim-export-spatial-dataflow=<none|spatial1|spatial2|spatial3|spatial4|all>` - control Spatial
|
||||
dataflow CSV reports for the graph, trivially merged graph, scheduled, and
|
||||
realized snapshots under `reports/`.
|
||||
- `--use-experimental-conv-impl` - use the alternate convolution lowering.
|
||||
- `--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:
|
||||
|
||||
```bash
|
||||
./build_release/Release/bin/onnx-mlir model.onnx -o /tmp/raptor/model \
|
||||
--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/`.
|
||||
@@ -130,21 +149,33 @@ Python dependencies used by the validation scripts are `numpy`, `onnx`, and
|
||||
Per-operation validation from the repository root:
|
||||
|
||||
```bash
|
||||
python3 validation/validate.py \
|
||||
python validation/validate.py \
|
||||
--raptor-path build_release/Release/bin/onnx-mlir \
|
||||
--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
|
||||
containing `.onnx` files:
|
||||
|
||||
```bash
|
||||
python3 validation/validate.py \
|
||||
python validation/validate.py \
|
||||
--raptor-path build_release/Release/bin/onnx-mlir \
|
||||
--onnx-include-dir onnx-mlir/include \
|
||||
--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:
|
||||
@@ -167,8 +198,9 @@ Each validation run writes artifacts in the model workspace, for example under
|
||||
- `simulation/out.bin` - raw simulator output used for comparison.
|
||||
|
||||
The compiler currently dumps dialect snapshots such as `spatial0.mlir`,
|
||||
`spatial1_dcp_merged.mlir`, `pim0.mlir`, `pim1_buff.mlir`,
|
||||
`pim2_coalesced.mlir`, and `pim3_folded.mlir` when an output directory is
|
||||
`spatial1_graph.mlir`, `spatial2_trivial_merged.mlir`,
|
||||
`spatial3_scheduled_no_comm.mlir`, `spatial4_scheduled.mlir`, `pim0.mlir`, `pim1_buff.mlir`,
|
||||
`pim2_folded.mlir`, `pim3_coalesced.mlir`, and `pim4_memory_planned.mlir` when an output directory is
|
||||
available.
|
||||
|
||||
To rerun the simulator manually with tracing after validation has produced a
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
colorama>=0.4.6,<1
|
||||
numpy>=1.26.4,<3
|
||||
onnx>=1.17,<2
|
||||
-e ./tools/raptor_graph_explorer[dev]
|
||||
@@ -117,12 +117,11 @@ add_pim_library(OMPIMAccel
|
||||
SpatialOps
|
||||
PimOps
|
||||
OMONNXToSpatial
|
||||
OMSpatialToGraphviz
|
||||
OMSpatialToPim
|
||||
OMPimCommon
|
||||
OMPimBufferization
|
||||
OMPimMemoryCoalescing
|
||||
OMPimHostConstantFolding
|
||||
OMPimMemoryCoalescing
|
||||
OMPimVerification
|
||||
MLIRTensorInferTypeOpInterfaceImpl
|
||||
)
|
||||
|
||||
@@ -8,6 +8,9 @@ add_pim_library(OMPimCommon
|
||||
IR/IndexingUtils.cpp
|
||||
IR/LoopUtils.cpp
|
||||
IR/ShapeUtils.cpp
|
||||
IR/ShapingUtils.cpp
|
||||
IR/StaticIntSequence.cpp
|
||||
IR/StaticIntGrid.cpp
|
||||
IR/SubviewUtils.cpp
|
||||
IR/TensorSliceUtils.cpp
|
||||
IR/WeightUtils.cpp
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "mlir/Dialect/Affine/IR/AffineOps.h"
|
||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||
@@ -7,6 +8,7 @@
|
||||
#include <limits>
|
||||
|
||||
#include "src/Accelerators/PIM/Common/IR/AddressAnalysis.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/AffineUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
||||
|
||||
@@ -391,6 +393,11 @@ llvm::FailureOr<int64_t> resolveIndexValueImpl(mlir::Value value, const StaticVa
|
||||
if (!definingOp)
|
||||
return mlir::failure();
|
||||
|
||||
if (auto affineApplyOp = mlir::dyn_cast<mlir::affine::AffineApplyOp>(definingOp))
|
||||
return evaluateAffineApply(affineApplyOp, [&](mlir::Value operand) {
|
||||
return resolveIndexValueImpl(operand, knowledge);
|
||||
});
|
||||
|
||||
if (auto indexCastOp = mlir::dyn_cast<mlir::arith::IndexCastOp>(definingOp))
|
||||
return resolveIndexValueImpl(indexCastOp.getIn(), knowledge);
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ static FailureOr<int64_t> ceilDivSigned(int64_t lhs, int64_t rhs) {
|
||||
}
|
||||
|
||||
Value createOrFoldAffineApply(
|
||||
RewriterBase& rewriter, Location loc, AffineMap map, ValueRange operands, Operation* constantAnchor) {
|
||||
OpBuilder& builder, Location loc, AffineMap map, ValueRange operands, Operation* constantAnchor) {
|
||||
assert(constantAnchor && "expected a valid constant anchor");
|
||||
assert(map.getNumResults() == 1 && "affine.apply expects a single-result affine map");
|
||||
|
||||
@@ -40,91 +40,91 @@ Value createOrFoldAffineApply(
|
||||
for (Value operand : operands) {
|
||||
std::optional<int64_t> constantValue = matchConstantIndexValue(operand);
|
||||
if (!constantValue)
|
||||
return affine::AffineApplyOp::create(rewriter, loc, map, operands).getResult();
|
||||
operandConstants.push_back(rewriter.getIndexAttr(*constantValue));
|
||||
return affine::AffineApplyOp::create(builder, loc, map, operands).getResult();
|
||||
operandConstants.push_back(builder.getIndexAttr(*constantValue));
|
||||
}
|
||||
|
||||
SmallVector<Attribute> foldedResults;
|
||||
if (succeeded(map.constantFold(operandConstants, foldedResults)) && foldedResults.size() == 1)
|
||||
if (auto constantResult = dyn_cast<IntegerAttr>(foldedResults.front()))
|
||||
return getOrCreateIndexConstant(rewriter, constantAnchor, constantResult.getInt());
|
||||
return getOrCreateIndexConstant(builder, constantAnchor, constantResult.getInt());
|
||||
|
||||
return affine::AffineApplyOp::create(rewriter, loc, map, operands).getResult();
|
||||
return affine::AffineApplyOp::create(builder, loc, map, operands).getResult();
|
||||
}
|
||||
|
||||
Value createOrFoldAffineApply(
|
||||
RewriterBase& rewriter, Location loc, AffineExpr expr, ValueRange dims, Operation* constantAnchor) {
|
||||
OpBuilder& builder, Location loc, AffineExpr expr, ValueRange dims, Operation* constantAnchor) {
|
||||
AffineMap map = AffineMap::get(/*dimCount=*/dims.size(), /*symbolCount=*/0, expr);
|
||||
return createOrFoldAffineApply(rewriter, loc, map, dims, constantAnchor);
|
||||
return createOrFoldAffineApply(builder, loc, map, dims, constantAnchor);
|
||||
}
|
||||
|
||||
Value affineMulConst(RewriterBase& rewriter, Location loc, Value value, int64_t multiplier, Operation* constantAnchor) {
|
||||
Value affineMulConst(OpBuilder& builder, Location loc, Value value, int64_t multiplier, Operation* constantAnchor) {
|
||||
assert(constantAnchor && "expected a valid constant anchor");
|
||||
if (multiplier == 0)
|
||||
return getOrCreateIndexConstant(rewriter, constantAnchor, 0);
|
||||
return getOrCreateIndexConstant(builder, constantAnchor, 0);
|
||||
if (multiplier == 1)
|
||||
return value;
|
||||
|
||||
AffineExpr d0 = getAffineDimExpr(0, rewriter.getContext());
|
||||
return createOrFoldAffineApply(rewriter, loc, d0 * multiplier, ValueRange {value}, constantAnchor);
|
||||
AffineExpr d0 = getAffineDimExpr(0, builder.getContext());
|
||||
return createOrFoldAffineApply(builder, loc, d0 * multiplier, ValueRange {value}, constantAnchor);
|
||||
}
|
||||
|
||||
Value affineAddConst(RewriterBase& rewriter, Location loc, Value value, int64_t offset, Operation* constantAnchor) {
|
||||
Value affineAddConst(OpBuilder& builder, Location loc, Value value, int64_t offset, Operation* constantAnchor) {
|
||||
assert(constantAnchor && "expected a valid constant anchor");
|
||||
if (offset == 0)
|
||||
return value;
|
||||
|
||||
AffineExpr d0 = getAffineDimExpr(0, rewriter.getContext());
|
||||
return createOrFoldAffineApply(rewriter, loc, d0 + offset, ValueRange {value}, constantAnchor);
|
||||
AffineExpr d0 = getAffineDimExpr(0, builder.getContext());
|
||||
return createOrFoldAffineApply(builder, loc, d0 + offset, ValueRange {value}, constantAnchor);
|
||||
}
|
||||
|
||||
Value affineModConst(RewriterBase& rewriter, Location loc, Value value, int64_t divisor, Operation* constantAnchor) {
|
||||
Value affineModConst(OpBuilder& builder, Location loc, Value value, int64_t divisor, Operation* constantAnchor) {
|
||||
assert(constantAnchor && "expected a valid constant anchor");
|
||||
assert(divisor > 0 && "expected a positive affine.mod divisor");
|
||||
if (divisor == 1)
|
||||
return getOrCreateIndexConstant(rewriter, constantAnchor, 0);
|
||||
return getOrCreateIndexConstant(builder, constantAnchor, 0);
|
||||
|
||||
AffineExpr d0 = getAffineDimExpr(0, rewriter.getContext());
|
||||
return createOrFoldAffineApply(rewriter, loc, d0 % divisor, ValueRange {value}, constantAnchor);
|
||||
AffineExpr d0 = getAffineDimExpr(0, builder.getContext());
|
||||
return createOrFoldAffineApply(builder, loc, d0 % divisor, ValueRange {value}, constantAnchor);
|
||||
}
|
||||
|
||||
Value affineFloorDivConst(
|
||||
RewriterBase& rewriter, Location loc, Value value, int64_t divisor, Operation* constantAnchor) {
|
||||
OpBuilder& builder, Location loc, Value value, int64_t divisor, Operation* constantAnchor) {
|
||||
assert(constantAnchor && "expected a valid constant anchor");
|
||||
assert(divisor > 0 && "expected a positive affine.floor_div divisor");
|
||||
if (divisor == 1)
|
||||
return value;
|
||||
|
||||
AffineExpr d0 = getAffineDimExpr(0, rewriter.getContext());
|
||||
return createOrFoldAffineApply(rewriter, loc, d0.floorDiv(divisor), ValueRange {value}, constantAnchor);
|
||||
AffineExpr d0 = getAffineDimExpr(0, builder.getContext());
|
||||
return createOrFoldAffineApply(builder, loc, d0.floorDiv(divisor), ValueRange {value}, constantAnchor);
|
||||
}
|
||||
|
||||
Value affineAddModConst(
|
||||
RewriterBase& rewriter, Location loc, Value value, int64_t offset, int64_t divisor, Operation* constantAnchor) {
|
||||
OpBuilder& builder, Location loc, Value value, int64_t offset, int64_t divisor, Operation* constantAnchor) {
|
||||
assert(constantAnchor && "expected a valid constant anchor");
|
||||
assert(divisor > 0 && "expected a positive affine.mod divisor");
|
||||
if (divisor == 1)
|
||||
return getOrCreateIndexConstant(rewriter, constantAnchor, 0);
|
||||
return getOrCreateIndexConstant(builder, constantAnchor, 0);
|
||||
|
||||
AffineExpr d0 = getAffineDimExpr(0, rewriter.getContext());
|
||||
AffineExpr d0 = getAffineDimExpr(0, builder.getContext());
|
||||
AffineExpr expr = d0;
|
||||
if (offset != 0)
|
||||
expr = expr + offset;
|
||||
return createOrFoldAffineApply(rewriter, loc, expr % divisor, ValueRange {value}, constantAnchor);
|
||||
return createOrFoldAffineApply(builder, loc, expr % divisor, ValueRange {value}, constantAnchor);
|
||||
}
|
||||
|
||||
Value affineAddFloorDivConst(
|
||||
RewriterBase& rewriter, Location loc, Value value, int64_t offset, int64_t divisor, Operation* constantAnchor) {
|
||||
OpBuilder& builder, Location loc, Value value, int64_t offset, int64_t divisor, Operation* constantAnchor) {
|
||||
assert(constantAnchor && "expected a valid constant anchor");
|
||||
assert(divisor > 0 && "expected a positive affine.floor_div divisor");
|
||||
if (divisor == 1)
|
||||
return offset == 0 ? value : affineAddConst(rewriter, loc, value, offset, constantAnchor);
|
||||
return offset == 0 ? value : affineAddConst(builder, loc, value, offset, constantAnchor);
|
||||
|
||||
AffineExpr d0 = getAffineDimExpr(0, rewriter.getContext());
|
||||
AffineExpr d0 = getAffineDimExpr(0, builder.getContext());
|
||||
AffineExpr expr = d0;
|
||||
if (offset != 0)
|
||||
expr = expr + offset;
|
||||
return createOrFoldAffineApply(rewriter, loc, expr.floorDiv(divisor), ValueRange {value}, constantAnchor);
|
||||
return createOrFoldAffineApply(builder, loc, expr.floorDiv(divisor), ValueRange {value}, constantAnchor);
|
||||
}
|
||||
|
||||
FailureOr<int64_t> evaluateAffineExpr(AffineExpr expr, ArrayRef<int64_t> dims, ArrayRef<int64_t> symbols) {
|
||||
|
||||
@@ -11,50 +11,50 @@ namespace onnx_mlir {
|
||||
|
||||
using IndexValueResolver = llvm::function_ref<llvm::FailureOr<int64_t>(mlir::Value)>;
|
||||
|
||||
mlir::Value createOrFoldAffineApply(mlir::RewriterBase& rewriter,
|
||||
mlir::Value createOrFoldAffineApply(mlir::OpBuilder& builder,
|
||||
mlir::Location loc,
|
||||
mlir::AffineMap map,
|
||||
mlir::ValueRange operands,
|
||||
mlir::Operation* constantAnchor);
|
||||
|
||||
mlir::Value createOrFoldAffineApply(mlir::RewriterBase& rewriter,
|
||||
mlir::Value createOrFoldAffineApply(mlir::OpBuilder& builder,
|
||||
mlir::Location loc,
|
||||
mlir::AffineExpr expr,
|
||||
mlir::ValueRange dims,
|
||||
mlir::Operation* constantAnchor);
|
||||
|
||||
mlir::Value affineMulConst(mlir::RewriterBase& rewriter,
|
||||
mlir::Value affineMulConst(mlir::OpBuilder& builder,
|
||||
mlir::Location loc,
|
||||
mlir::Value value,
|
||||
int64_t multiplier,
|
||||
mlir::Operation* constantAnchor);
|
||||
|
||||
mlir::Value affineAddConst(mlir::RewriterBase& rewriter,
|
||||
mlir::Value affineAddConst(mlir::OpBuilder& builder,
|
||||
mlir::Location loc,
|
||||
mlir::Value value,
|
||||
int64_t offset,
|
||||
mlir::Operation* constantAnchor);
|
||||
|
||||
mlir::Value affineModConst(mlir::RewriterBase& rewriter,
|
||||
mlir::Value affineModConst(mlir::OpBuilder& builder,
|
||||
mlir::Location loc,
|
||||
mlir::Value value,
|
||||
int64_t divisor,
|
||||
mlir::Operation* constantAnchor);
|
||||
|
||||
mlir::Value affineFloorDivConst(mlir::RewriterBase& rewriter,
|
||||
mlir::Value affineFloorDivConst(mlir::OpBuilder& builder,
|
||||
mlir::Location loc,
|
||||
mlir::Value value,
|
||||
int64_t divisor,
|
||||
mlir::Operation* constantAnchor);
|
||||
|
||||
mlir::Value affineAddModConst(mlir::RewriterBase& rewriter,
|
||||
mlir::Value affineAddModConst(mlir::OpBuilder& builder,
|
||||
mlir::Location loc,
|
||||
mlir::Value value,
|
||||
int64_t offset,
|
||||
int64_t divisor,
|
||||
mlir::Operation* constantAnchor);
|
||||
|
||||
mlir::Value affineAddFloorDivConst(mlir::RewriterBase& rewriter,
|
||||
mlir::Value affineAddFloorDivConst(mlir::OpBuilder& builder,
|
||||
mlir::Location loc,
|
||||
mlir::Value value,
|
||||
int64_t offset,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef ONNX_MLIR_PIM_COMPACT_ASM_UTILS_HPP
|
||||
#define ONNX_MLIR_PIM_COMPACT_ASM_UTILS_HPP
|
||||
|
||||
#include "mlir/IR/Builders.h"
|
||||
#include "mlir/IR/OpImplementation.h"
|
||||
#include "mlir/IR/Value.h"
|
||||
#include "mlir/Support/LLVM.h"
|
||||
@@ -9,8 +10,11 @@
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/ADT/Twine.h"
|
||||
#include "llvm/Support/LogicalResult.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace onnx_mlir {
|
||||
namespace compact_asm {
|
||||
|
||||
@@ -21,6 +25,59 @@ enum class ListDelimiter {
|
||||
Paren
|
||||
};
|
||||
|
||||
struct NumericSuffixRange {
|
||||
StringRef prefix;
|
||||
unsigned first = 0;
|
||||
unsigned last = 0;
|
||||
};
|
||||
|
||||
inline std::optional<NumericSuffixRange> getNumericSuffixRange(StringRef firstName, StringRef lastName) {
|
||||
auto split = [](StringRef name) -> std::optional<std::pair<StringRef, unsigned>> {
|
||||
size_t suffixStart = name.size();
|
||||
while (suffixStart > 0 && name[suffixStart - 1] >= '0' && name[suffixStart - 1] <= '9')
|
||||
--suffixStart;
|
||||
if (suffixStart == name.size())
|
||||
return std::nullopt;
|
||||
|
||||
unsigned number = 0;
|
||||
if (name.drop_front(suffixStart).getAsInteger(10, number))
|
||||
return std::nullopt;
|
||||
return std::pair(name.take_front(suffixStart), number);
|
||||
};
|
||||
|
||||
auto first = split(firstName);
|
||||
auto last = split(lastName);
|
||||
if (!first || !last || first->first != last->first || first->second > last->second)
|
||||
return std::nullopt;
|
||||
return NumericSuffixRange {first->first, first->second, last->second};
|
||||
}
|
||||
|
||||
inline StringRef getInternedNumberedName(OpAsmParser& parser, StringRef prefix, unsigned number) {
|
||||
return parser.getBuilder().getStringAttr((Twine(prefix) + Twine(number)).str()).getValue();
|
||||
}
|
||||
|
||||
inline ParseResult parseOptionalRepeatCount(OpAsmParser& parser, int64_t& repeatCount) {
|
||||
repeatCount = 1;
|
||||
if (succeeded(parser.parseOptionalKeyword("x"))) {
|
||||
if (parser.parseInteger(repeatCount) || repeatCount <= 0)
|
||||
return parser.emitError(parser.getCurrentLocation(), "repeat count after 'x' must be positive");
|
||||
return success();
|
||||
}
|
||||
|
||||
const char* tokenStart = parser.getCurrentLocation().getPointer();
|
||||
if (!tokenStart || tokenStart[0] != 'x' || tokenStart[1] < '0' || tokenStart[1] > '9')
|
||||
return success();
|
||||
|
||||
StringRef fusedRepeat;
|
||||
if (failed(parser.parseOptionalKeyword(&fusedRepeat)))
|
||||
return failure();
|
||||
if (!fusedRepeat.consume_front("x") || fusedRepeat.empty()
|
||||
|| fusedRepeat.getAsInteger(10, repeatCount) || repeatCount <= 0) {
|
||||
return parser.emitError(parser.getCurrentLocation(), "repeat count after 'x' must be positive");
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
inline ParseResult parseOpenDelimiter(OpAsmParser& parser, ListDelimiter delimiter) {
|
||||
if (delimiter == ListDelimiter::Square)
|
||||
return parser.parseLSquare();
|
||||
@@ -59,10 +116,8 @@ inline ParseResult parseCompressedRepeatedList(OpAsmParser& parser,
|
||||
return failure();
|
||||
|
||||
int64_t repeatCount = 1;
|
||||
if (succeeded(parser.parseOptionalKeyword("x"))) {
|
||||
if (parser.parseInteger(repeatCount) || repeatCount <= 0)
|
||||
return parser.emitError(parser.getCurrentLocation(), "repeat count after 'x' must be positive");
|
||||
}
|
||||
if (parseOptionalRepeatCount(parser, repeatCount))
|
||||
return failure();
|
||||
for (int64_t index = 0; index < repeatCount; ++index)
|
||||
entries.push_back(entry);
|
||||
|
||||
@@ -86,10 +141,8 @@ parseCompressedIntegerEntries(OpAsmParser& parser, ListDelimiter delimiter, Smal
|
||||
return failure();
|
||||
|
||||
int64_t repeatCount = 1;
|
||||
if (succeeded(parser.parseOptionalKeyword("x"))) {
|
||||
if (parser.parseInteger(repeatCount) || repeatCount <= 0)
|
||||
return parser.emitError(parser.getCurrentLocation(), "repeat count after 'x' must be positive");
|
||||
}
|
||||
if (parseOptionalRepeatCount(parser, repeatCount))
|
||||
return failure();
|
||||
for (int64_t repeat = 0; repeat < repeatCount; ++repeat)
|
||||
llvm::append_range(values, subgroup);
|
||||
}
|
||||
@@ -109,10 +162,8 @@ parseCompressedIntegerEntries(OpAsmParser& parser, ListDelimiter delimiter, Smal
|
||||
return parser.emitError(parser.getCurrentLocation(), "step after 'by' must be positive");
|
||||
}
|
||||
int64_t repeatCount = 1;
|
||||
if (succeeded(parser.parseOptionalKeyword("x"))) {
|
||||
if (parser.parseInteger(repeatCount) || repeatCount <= 0)
|
||||
return parser.emitError(parser.getCurrentLocation(), "repeat count after 'x' must be positive");
|
||||
}
|
||||
if (parseOptionalRepeatCount(parser, repeatCount))
|
||||
return failure();
|
||||
if ((last - first) % step != 0) {
|
||||
return parser.emitError(parser.getCurrentLocation(),
|
||||
"range end must be reachable from start using the given step");
|
||||
@@ -124,10 +175,8 @@ parseCompressedIntegerEntries(OpAsmParser& parser, ListDelimiter delimiter, Smal
|
||||
}
|
||||
else {
|
||||
int64_t repeatCount = 1;
|
||||
if (succeeded(parser.parseOptionalKeyword("x"))) {
|
||||
if (parser.parseInteger(repeatCount) || repeatCount <= 0)
|
||||
return parser.emitError(parser.getCurrentLocation(), "repeat count after 'x' must be positive");
|
||||
}
|
||||
if (parseOptionalRepeatCount(parser, repeatCount))
|
||||
return failure();
|
||||
for (int64_t index = 0; index < repeatCount; ++index)
|
||||
values.push_back(static_cast<IntT>(first));
|
||||
}
|
||||
@@ -406,10 +455,8 @@ inline ParseResult parseCompressedTypeSequence(OpAsmParser& parser, SmallVectorI
|
||||
|
||||
auto appendType = [&](Type type) -> ParseResult {
|
||||
int64_t repeatCount = 1;
|
||||
if (succeeded(parser.parseOptionalKeyword("x"))) {
|
||||
if (parser.parseInteger(repeatCount) || repeatCount <= 0)
|
||||
return parser.emitError(parser.getCurrentLocation(), "repeat count after 'x' must be positive");
|
||||
}
|
||||
if (parseOptionalRepeatCount(parser, repeatCount))
|
||||
return failure();
|
||||
for (int64_t index = 0; index < repeatCount; ++index)
|
||||
types.push_back(type);
|
||||
return success();
|
||||
@@ -432,18 +479,23 @@ inline ParseResult parseCompressedOperandEntryWithFirst(OpAsmParser& parser,
|
||||
OpAsmParser::UnresolvedOperand lastOperand;
|
||||
if (parser.parseOperand(lastOperand))
|
||||
return failure();
|
||||
if (firstOperand.name != lastOperand.name || firstOperand.number > lastOperand.number)
|
||||
return parser.emitError(parser.getCurrentLocation(), "invalid operand range");
|
||||
if (firstOperand.name == lastOperand.name && firstOperand.number <= lastOperand.number) {
|
||||
for (unsigned number = firstOperand.number; number <= lastOperand.number; ++number)
|
||||
operands.push_back({firstOperand.location, firstOperand.name, number});
|
||||
return success();
|
||||
}
|
||||
|
||||
int64_t repeatCount = 1;
|
||||
if (succeeded(parser.parseOptionalKeyword("x"))) {
|
||||
if (parser.parseInteger(repeatCount) || repeatCount <= 0)
|
||||
return parser.emitError(parser.getCurrentLocation(), "repeat count after 'x' must be positive");
|
||||
auto range = getNumericSuffixRange(firstOperand.name, lastOperand.name);
|
||||
if (!range || firstOperand.number != 0 || lastOperand.number != 0)
|
||||
return parser.emitError(parser.getCurrentLocation(), "invalid operand range");
|
||||
for (unsigned number = range->first; number <= range->last; ++number)
|
||||
operands.push_back({firstOperand.location, getInternedNumberedName(parser, range->prefix, number), 0});
|
||||
return success();
|
||||
}
|
||||
|
||||
int64_t repeatCount = 1;
|
||||
if (parseOptionalRepeatCount(parser, repeatCount))
|
||||
return failure();
|
||||
for (int64_t index = 0; index < repeatCount; ++index)
|
||||
operands.push_back(firstOperand);
|
||||
return success();
|
||||
@@ -560,10 +612,8 @@ inline ParseResult parseCompressedOrTupleOperandList(OpAsmParser& parser,
|
||||
return failure();
|
||||
|
||||
int64_t repeatCount = 1;
|
||||
if (succeeded(parser.parseOptionalKeyword("x"))) {
|
||||
if (parser.parseInteger(repeatCount) || repeatCount <= 0)
|
||||
return parser.emitError(parser.getCurrentLocation(), "repeat count after 'x' must be positive");
|
||||
}
|
||||
if (parseOptionalRepeatCount(parser, repeatCount))
|
||||
return failure();
|
||||
for (int64_t repeat = 0; repeat < repeatCount; ++repeat)
|
||||
llvm::append_range(operands, tupleOperands);
|
||||
|
||||
@@ -574,11 +624,8 @@ inline ParseResult parseCompressedOrTupleOperandList(OpAsmParser& parser,
|
||||
if (parseCompressedOperandSequence(parser, tupleOperands) || parser.parseRParen())
|
||||
return failure();
|
||||
|
||||
repeatCount = 1;
|
||||
if (succeeded(parser.parseOptionalKeyword("x"))) {
|
||||
if (parser.parseInteger(repeatCount) || repeatCount <= 0)
|
||||
return parser.emitError(parser.getCurrentLocation(), "repeat count after 'x' must be positive");
|
||||
}
|
||||
if (parseOptionalRepeatCount(parser, repeatCount))
|
||||
return failure();
|
||||
for (int64_t repeat = 0; repeat < repeatCount; ++repeat)
|
||||
llvm::append_range(operands, tupleOperands);
|
||||
}
|
||||
@@ -608,10 +655,8 @@ parseCompressedOrTupleTypeList(OpAsmParser& parser, ListDelimiter delimiter, Sma
|
||||
return failure();
|
||||
|
||||
int64_t repeatCount = 1;
|
||||
if (succeeded(parser.parseOptionalKeyword("x"))) {
|
||||
if (parser.parseInteger(repeatCount) || repeatCount <= 0)
|
||||
return parser.emitError(parser.getCurrentLocation(), "repeat count after 'x' must be positive");
|
||||
}
|
||||
if (parseOptionalRepeatCount(parser, repeatCount))
|
||||
return failure();
|
||||
for (int64_t repeat = 0; repeat < repeatCount; ++repeat)
|
||||
llvm::append_range(types, tupleTypes);
|
||||
|
||||
@@ -622,11 +667,8 @@ parseCompressedOrTupleTypeList(OpAsmParser& parser, ListDelimiter delimiter, Sma
|
||||
if (parseCompressedTypeSequence(parser, tupleTypes, /*allowEmpty=*/false) || parser.parseRParen())
|
||||
return failure();
|
||||
|
||||
repeatCount = 1;
|
||||
if (succeeded(parser.parseOptionalKeyword("x"))) {
|
||||
if (parser.parseInteger(repeatCount) || repeatCount <= 0)
|
||||
return parser.emitError(parser.getCurrentLocation(), "repeat count after 'x' must be positive");
|
||||
}
|
||||
if (parseOptionalRepeatCount(parser, repeatCount))
|
||||
return failure();
|
||||
for (int64_t repeat = 0; repeat < repeatCount; ++repeat)
|
||||
llvm::append_range(types, tupleTypes);
|
||||
}
|
||||
@@ -639,10 +681,8 @@ parseCompressedOrTupleTypeList(OpAsmParser& parser, ListDelimiter delimiter, Sma
|
||||
return failure();
|
||||
|
||||
int64_t repeatCount = 1;
|
||||
if (succeeded(parser.parseOptionalKeyword("x"))) {
|
||||
if (parser.parseInteger(repeatCount) || repeatCount <= 0)
|
||||
return parser.emitError(parser.getCurrentLocation(), "repeat count after 'x' must be positive");
|
||||
}
|
||||
if (parseOptionalRepeatCount(parser, repeatCount))
|
||||
return failure();
|
||||
for (int64_t repeat = 0; repeat < repeatCount; ++repeat)
|
||||
types.push_back(type);
|
||||
|
||||
@@ -678,10 +718,8 @@ inline ParseResult parseCompressedArgumentEntryWithFirst(OpAsmParser& parser,
|
||||
OpAsmParser::Argument lastArgument;
|
||||
if (parser.parseArgument(lastArgument))
|
||||
return failure();
|
||||
if (firstArgument.ssaName.name != lastArgument.ssaName.name
|
||||
|| firstArgument.ssaName.number > lastArgument.ssaName.number) {
|
||||
return parser.emitError(parser.getCurrentLocation(), "invalid argument range");
|
||||
}
|
||||
if (firstArgument.ssaName.name == lastArgument.ssaName.name
|
||||
&& firstArgument.ssaName.number <= lastArgument.ssaName.number) {
|
||||
for (unsigned number = firstArgument.ssaName.number; number <= lastArgument.ssaName.number; ++number) {
|
||||
OpAsmParser::Argument argument;
|
||||
argument.ssaName = {firstArgument.ssaName.location, firstArgument.ssaName.name, number};
|
||||
@@ -690,6 +728,17 @@ inline ParseResult parseCompressedArgumentEntryWithFirst(OpAsmParser& parser,
|
||||
return success();
|
||||
}
|
||||
|
||||
auto range = getNumericSuffixRange(firstArgument.ssaName.name, lastArgument.ssaName.name);
|
||||
if (!range || firstArgument.ssaName.number != 0 || lastArgument.ssaName.number != 0)
|
||||
return parser.emitError(parser.getCurrentLocation(), "invalid argument range");
|
||||
for (unsigned number = range->first; number <= range->last; ++number) {
|
||||
OpAsmParser::Argument argument;
|
||||
argument.ssaName = {firstArgument.ssaName.location, getInternedNumberedName(parser, range->prefix, number), 0};
|
||||
arguments.push_back(argument);
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
arguments.push_back(firstArgument);
|
||||
return success();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,32 @@ using namespace mlir;
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
ConstantPool::ConstantPool(Operation *constantAnchor, OpBuilder &builder)
|
||||
: anchor(constantAnchor), block(getConstantInsertionBlock(constantAnchor)),
|
||||
builder(builder) {
|
||||
for (Operation &op : *block)
|
||||
if (auto constant = dyn_cast<arith::ConstantOp>(&op))
|
||||
cache.try_emplace(
|
||||
std::make_pair(constant.getType(), constant.getValue()),
|
||||
constant.getResult());
|
||||
}
|
||||
|
||||
Value ConstantPool::getIndex(int64_t value) {
|
||||
return get(builder.getIndexType(), builder.getIndexAttr(value));
|
||||
}
|
||||
|
||||
Value ConstantPool::get(Type type, Attribute value) {
|
||||
auto key = std::make_pair(type, value);
|
||||
if (Value existing = cache.lookup(key))
|
||||
return existing;
|
||||
OpBuilder::InsertionGuard guard(builder);
|
||||
builder.setInsertionPointToStart(block);
|
||||
Value constant = arith::ConstantOp::create(
|
||||
builder, anchor->getLoc(), type, cast<TypedAttr>(value)).getResult();
|
||||
cache.try_emplace(key, constant);
|
||||
return constant;
|
||||
}
|
||||
|
||||
static std::optional<int64_t> getIndexConstantValue(arith::ConstantOp constantOp) {
|
||||
if (!constantOp.getType().isIndex())
|
||||
return std::nullopt;
|
||||
@@ -49,7 +75,7 @@ Value getOrCreateConstant(OperationFolder& folder, Operation* anchorOp, Attribut
|
||||
return folder.getOrCreateConstant(hostBlock, arithDialect, value, type);
|
||||
}
|
||||
|
||||
Value getOrCreateConstant(RewriterBase& rewriter, Operation* anchorOp, Attribute value, Type type) {
|
||||
Value getOrCreateConstant(OpBuilder& builder, Operation* anchorOp, Attribute value, Type type) {
|
||||
assert(anchorOp && "expected a valid anchor operation");
|
||||
Block* hostBlock = getConstantInsertionBlock(anchorOp);
|
||||
for (Operation& op : *hostBlock) {
|
||||
@@ -59,9 +85,16 @@ Value getOrCreateConstant(RewriterBase& rewriter, Operation* anchorOp, Attribute
|
||||
return constantOp.getResult();
|
||||
}
|
||||
|
||||
OpBuilder::InsertionGuard guard(rewriter);
|
||||
rewriter.setInsertionPointToStart(hostBlock);
|
||||
return arith::ConstantOp::create(rewriter, anchorOp->getLoc(), type, cast<TypedAttr>(value)).getResult();
|
||||
OpBuilder::InsertionGuard guard(builder);
|
||||
builder.setInsertionPointToStart(hostBlock);
|
||||
return arith::ConstantOp::create(builder, anchorOp->getLoc(), type, cast<TypedAttr>(value)).getResult();
|
||||
}
|
||||
|
||||
Value createConstantAtHostBlockStart(OpBuilder& builder, Operation* anchorOp, TypedAttr value) {
|
||||
assert(anchorOp && "expected a valid anchor operation");
|
||||
OpBuilder::InsertionGuard guard(builder);
|
||||
builder.setInsertionPointToStart(getConstantInsertionBlock(anchorOp));
|
||||
return arith::ConstantOp::create(builder, anchorOp->getLoc(), value).getResult();
|
||||
}
|
||||
|
||||
Value getOrCreateConstantLike(OperationFolder& folder, arith::ConstantOp constantOp) {
|
||||
@@ -73,9 +106,8 @@ Value getOrCreateIndexConstant(OperationFolder& folder, Operation* anchorOp, int
|
||||
return getOrCreateConstant(folder, anchorOp, builder.getIndexAttr(value), builder.getIndexType());
|
||||
}
|
||||
|
||||
Value getOrCreateIndexConstant(RewriterBase& rewriter, Operation* anchorOp, int64_t value) {
|
||||
Builder builder(anchorOp->getContext());
|
||||
return getOrCreateConstant(rewriter, anchorOp, builder.getIndexAttr(value), builder.getIndexType());
|
||||
Value getOrCreateIndexConstant(OpBuilder& builder, Operation* anchorOp, int64_t value) {
|
||||
return getOrCreateConstant(builder, anchorOp, builder.getIndexAttr(value), builder.getIndexType());
|
||||
}
|
||||
|
||||
void hoistAndUniquifyIndexConstants(func::FuncOp funcOp, RewriterBase& rewriter) {
|
||||
|
||||
@@ -6,23 +6,42 @@
|
||||
#include "mlir/IR/Value.h"
|
||||
#include "mlir/Transforms/FoldUtils.h"
|
||||
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
class ConstantPool {
|
||||
public:
|
||||
ConstantPool(mlir::Operation *constantAnchor, mlir::OpBuilder &builder);
|
||||
|
||||
mlir::Value getIndex(int64_t value);
|
||||
mlir::Value get(mlir::Type type, mlir::Attribute value);
|
||||
|
||||
private:
|
||||
mlir::Operation *anchor;
|
||||
mlir::Block *block;
|
||||
mlir::OpBuilder &builder;
|
||||
llvm::DenseMap<std::pair<mlir::Type, mlir::Attribute>, mlir::Value> cache;
|
||||
};
|
||||
|
||||
mlir::Block* getConstantInsertionBlock(mlir::Operation* anchorOp);
|
||||
|
||||
mlir::Value
|
||||
getOrCreateConstant(mlir::OperationFolder& folder, mlir::Operation* anchorOp, mlir::Attribute value, mlir::Type type);
|
||||
|
||||
mlir::Value
|
||||
getOrCreateConstant(mlir::RewriterBase& rewriter, mlir::Operation* anchorOp, mlir::Attribute value, mlir::Type type);
|
||||
getOrCreateConstant(mlir::OpBuilder& builder, mlir::Operation* anchorOp, mlir::Attribute value, mlir::Type type);
|
||||
|
||||
mlir::Value
|
||||
createConstantAtHostBlockStart(mlir::OpBuilder& builder, mlir::Operation* anchorOp, mlir::TypedAttr value);
|
||||
|
||||
mlir::Value getOrCreateConstantLike(mlir::OperationFolder& folder, mlir::arith::ConstantOp constantOp);
|
||||
|
||||
mlir::Value getOrCreateIndexConstant(mlir::OperationFolder& folder, mlir::Operation* anchorOp, int64_t value);
|
||||
|
||||
mlir::Value getOrCreateIndexConstant(mlir::RewriterBase& rewriter, mlir::Operation* anchorOp, int64_t value);
|
||||
mlir::Value getOrCreateIndexConstant(mlir::OpBuilder& builder, mlir::Operation* anchorOp, int64_t value);
|
||||
|
||||
void hoistAndUniquifyIndexConstants(mlir::func::FuncOp funcOp, mlir::RewriterBase& rewriter);
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "mlir/Dialect/Affine/IR/AffineOps.h"
|
||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/DenseSet.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
|
||||
#include "src/Accelerators/PIM/Common/IR/CoreBlockUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
||||
@@ -10,7 +12,8 @@
|
||||
namespace onnx_mlir {
|
||||
|
||||
bool isCoreStaticAddressOp(mlir::Operation* op) {
|
||||
if (mlir::isa<mlir::arith::ConstantOp,
|
||||
if (mlir::isa<mlir::affine::AffineApplyOp,
|
||||
mlir::arith::ConstantOp,
|
||||
mlir::arith::AddIOp,
|
||||
mlir::arith::SubIOp,
|
||||
mlir::arith::MulIOp,
|
||||
@@ -27,78 +30,62 @@ bool isCoreStaticAddressOp(mlir::Operation* op) {
|
||||
mlir::memref::CollapseShapeOp,
|
||||
mlir::memref::ExpandShapeOp>(op))
|
||||
return true;
|
||||
|
||||
if (auto selectOp = mlir::dyn_cast<mlir::arith::SelectOp>(op))
|
||||
return selectOp.getType().isIntOrIndex();
|
||||
|
||||
return false;
|
||||
auto selectOp = mlir::dyn_cast<mlir::arith::SelectOp>(op);
|
||||
return selectOp && selectOp.getType().isIntOrIndex();
|
||||
}
|
||||
|
||||
mlir::LogicalResult
|
||||
walkPimCoreBlock(mlir::Block& block,
|
||||
const StaticValueKnowledge& knowledge,
|
||||
llvm::function_ref<mlir::LogicalResult(mlir::Operation&, const StaticValueKnowledge&)> callback) {
|
||||
namespace {
|
||||
|
||||
enum class CoreWalkMode {
|
||||
ExecuteCommunication,
|
||||
StructuralExtremes
|
||||
};
|
||||
using CoreWalkCallback = llvm::function_ref<mlir::LogicalResult(mlir::Operation&, const StaticValueKnowledge&)>;
|
||||
|
||||
static void propagateRegionResults(mlir::ValueRange results, mlir::Region& region, StaticValueKnowledge& knowledge) {
|
||||
if (region.empty())
|
||||
return;
|
||||
auto yield = mlir::cast<mlir::scf::YieldOp>(region.front().getTerminator());
|
||||
for (auto [result, yielded] : llvm::zip(results, yield.getOperands()))
|
||||
knowledge.aliases[result] = resolveLoopCarriedAlias(yielded, knowledge);
|
||||
}
|
||||
|
||||
static mlir::LogicalResult walkPimCoreBlockImpl(mlir::Block& block,
|
||||
const StaticValueKnowledge& initialKnowledge,
|
||||
CoreWalkMode mode,
|
||||
const PimCoreCommunicationPlan* communicationPlan,
|
||||
CoreWalkCallback callback) {
|
||||
bool hasFailure = false;
|
||||
for (mlir::Operation& op : block) {
|
||||
StaticValueKnowledge knowledge = initialKnowledge;
|
||||
llvm::StringRef purpose = mode == CoreWalkMode::ExecuteCommunication ? "communication verification" : "verification";
|
||||
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))
|
||||
continue;
|
||||
if (auto loadOp = mlir::dyn_cast<mlir::memref::LoadOp>(op);
|
||||
loadOp && succeeded(resolveIndexValue(loadOp.getResult(), knowledge)))
|
||||
continue;
|
||||
|
||||
if (auto forOp = mlir::dyn_cast<mlir::scf::ForOp>(op)) {
|
||||
mlir::Block& loopBody = forOp.getRegion().front();
|
||||
auto lowerBound = resolveIndexValue(forOp.getLowerBound(), knowledge);
|
||||
auto upperBound = resolveIndexValue(forOp.getUpperBound(), knowledge);
|
||||
auto lower = resolveIndexValue(forOp.getLowerBound(), knowledge);
|
||||
auto upper = resolveIndexValue(forOp.getUpperBound(), knowledge);
|
||||
auto step = resolveIndexValue(forOp.getStep(), knowledge);
|
||||
if (failed(lowerBound) || failed(upperBound) || failed(step) || *step <= 0) {
|
||||
forOp.emitOpError("requires statically evaluable scf.for bounds for PIM codegen");
|
||||
hasFailure = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
llvm::SmallVector<mlir::Value> iterValues(forOp.getInitArgs().begin(), forOp.getInitArgs().end());
|
||||
for (int64_t inductionValue = *lowerBound; inductionValue < *upperBound; inductionValue += *step) {
|
||||
StaticValueKnowledge loopKnowledge = knowledge;
|
||||
loopKnowledge.indexValues[forOp.getInductionVar()] = inductionValue;
|
||||
for (auto [iterArg, iterValue] : llvm::zip_equal(forOp.getRegionIterArgs(), iterValues))
|
||||
loopKnowledge.aliases[iterArg] = iterValue;
|
||||
|
||||
if (failed(walkPimCoreBlock(loopBody, loopKnowledge, callback)))
|
||||
hasFailure = true;
|
||||
|
||||
auto yieldOp = mlir::cast<mlir::scf::YieldOp>(loopBody.getTerminator());
|
||||
for (auto [index, yieldedValue] : llvm::enumerate(yieldOp.getOperands()))
|
||||
iterValues[index] = resolveLoopCarriedAlias(yieldedValue, loopKnowledge);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (failed(callback(op, knowledge)))
|
||||
hasFailure = true;
|
||||
}
|
||||
return mlir::success(!hasFailure);
|
||||
}
|
||||
|
||||
mlir::LogicalResult walkPimCoreBlockStructurally(
|
||||
mlir::Block& block,
|
||||
const StaticValueKnowledge& knowledge,
|
||||
llvm::function_ref<mlir::LogicalResult(mlir::Operation&, const StaticValueKnowledge&)> callback) {
|
||||
bool hasFailure = false;
|
||||
for (mlir::Operation& op : block) {
|
||||
if (mlir::isa<pim::PimHaltOp, mlir::scf::YieldOp>(op) || isCoreStaticAddressOp(&op))
|
||||
continue;
|
||||
if (auto loadOp = mlir::dyn_cast<mlir::memref::LoadOp>(op);
|
||||
loadOp && succeeded(resolveIndexValue(loadOp.getResult(), knowledge)))
|
||||
continue;
|
||||
|
||||
if (auto forOp = mlir::dyn_cast<mlir::scf::ForOp>(op)) {
|
||||
mlir::Block& loopBody = forOp.getRegion().front();
|
||||
auto lowerBound = resolveIndexValue(forOp.getLowerBound(), knowledge);
|
||||
auto upperBound = resolveIndexValue(forOp.getUpperBound(), knowledge);
|
||||
auto step = resolveIndexValue(forOp.getStep(), knowledge);
|
||||
if (failed(lowerBound) || failed(upperBound) || failed(step)) {
|
||||
forOp.emitOpError("requires statically evaluable scf.for bounds for PIM verification");
|
||||
if (failed(lower) || failed(upper) || failed(step)
|
||||
|| (mode == CoreWalkMode::ExecuteCommunication && *step <= 0)) {
|
||||
forOp.emitOpError() << "requires statically evaluable scf.for bounds for PIM " << purpose;
|
||||
hasFailure = true;
|
||||
continue;
|
||||
}
|
||||
@@ -108,30 +95,117 @@ mlir::LogicalResult walkPimCoreBlockStructurally(
|
||||
continue;
|
||||
}
|
||||
|
||||
llvm::SmallVector<int64_t, 2> samples;
|
||||
if (*lowerBound < *upperBound) {
|
||||
samples.push_back(*lowerBound);
|
||||
int64_t last = *lowerBound + ((*upperBound - 1 - *lowerBound) / *step) * *step;
|
||||
if (last != *lowerBound)
|
||||
samples.push_back(last);
|
||||
}
|
||||
|
||||
for (int64_t inductionValue : samples) {
|
||||
mlir::Block& body = forOp.getRegion().front();
|
||||
llvm::SmallVector<mlir::Value> iterValues(forOp.getInitArgs().begin(), forOp.getInitArgs().end());
|
||||
auto visitIteration = [&](int64_t induction, bool carryValues) {
|
||||
StaticValueKnowledge loopKnowledge = knowledge;
|
||||
loopKnowledge.indexValues[forOp.getInductionVar()] = inductionValue;
|
||||
for (auto [iterArg, iterValue] : llvm::zip_equal(forOp.getRegionIterArgs(), forOp.getInitArgs()))
|
||||
loopKnowledge.aliases[iterArg] = iterValue;
|
||||
loopKnowledge.indexValues[forOp.getInductionVar()] = induction;
|
||||
for (auto [index, iterArg] : llvm::enumerate(forOp.getRegionIterArgs()))
|
||||
loopKnowledge.aliases[iterArg] = carryValues ? iterValues[index] : forOp.getInitArgs()[index];
|
||||
hasFailure |= failed(walkPimCoreBlockImpl(body, loopKnowledge, mode, communicationPlan, callback));
|
||||
auto yield = mlir::cast<mlir::scf::YieldOp>(body.getTerminator());
|
||||
for (auto [index, yielded] : llvm::enumerate(yield.getOperands()))
|
||||
iterValues[index] = resolveLoopCarriedAlias(yielded, loopKnowledge);
|
||||
};
|
||||
|
||||
if (failed(walkPimCoreBlockStructurally(loopBody, loopKnowledge, callback)))
|
||||
hasFailure = true;
|
||||
if (mode == CoreWalkMode::ExecuteCommunication) {
|
||||
for (int64_t induction = *lower; induction < *upper; induction += *step)
|
||||
visitIteration(induction, true);
|
||||
}
|
||||
else if (*lower < *upper) {
|
||||
visitIteration(*lower, false);
|
||||
int64_t last = *lower + ((*upper - 1 - *lower) / *step) * *step;
|
||||
if (last != *lower)
|
||||
visitIteration(last, false);
|
||||
}
|
||||
for (auto [result, value] : llvm::zip(forOp.getResults(), iterValues))
|
||||
knowledge.aliases[result] = value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (failed(callback(op, knowledge)))
|
||||
if (auto ifOp = mlir::dyn_cast<mlir::scf::IfOp>(op)) {
|
||||
auto condition = resolveIndexValue(ifOp.getCondition(), knowledge);
|
||||
if (failed(condition)) {
|
||||
ifOp.emitOpError() << "requires statically evaluable scf.if condition for PIM " << purpose;
|
||||
hasFailure = true;
|
||||
continue;
|
||||
}
|
||||
mlir::Region& selected = *condition != 0 ? ifOp.getThenRegion() : ifOp.getElseRegion();
|
||||
if (mode == CoreWalkMode::ExecuteCommunication) {
|
||||
hasFailure |= !selected.empty()
|
||||
&& failed(walkPimCoreBlockImpl(selected.front(), knowledge, mode, communicationPlan, callback));
|
||||
}
|
||||
else {
|
||||
for (mlir::Region* region : {&ifOp.getThenRegion(), &ifOp.getElseRegion()})
|
||||
hasFailure |= !region->empty()
|
||||
&& failed(walkPimCoreBlockImpl(region->front(), knowledge, mode, communicationPlan, callback));
|
||||
}
|
||||
propagateRegionResults(ifOp.getResults(), selected, knowledge);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (auto switchOp = mlir::dyn_cast<mlir::scf::IndexSwitchOp>(op)) {
|
||||
auto selector = resolveIndexValue(switchOp.getArg(), knowledge);
|
||||
if (failed(selector)) {
|
||||
switchOp.emitOpError() << "requires a statically evaluable scf.index_switch selector for PIM " << purpose;
|
||||
hasFailure = true;
|
||||
continue;
|
||||
}
|
||||
mlir::Region* selected = &switchOp.getDefaultRegion();
|
||||
for (auto [caseValue, caseRegion] : llvm::zip(switchOp.getCases(), switchOp.getCaseRegions()))
|
||||
if (caseValue == *selector) {
|
||||
selected = &caseRegion;
|
||||
break;
|
||||
}
|
||||
for (mlir::Region& region : switchOp->getRegions()) {
|
||||
if (mode == CoreWalkMode::ExecuteCommunication && ®ion != selected)
|
||||
continue;
|
||||
hasFailure |= failed(walkPimCoreBlockImpl(region.front(), knowledge, mode, communicationPlan, callback));
|
||||
}
|
||||
propagateRegionResults(switchOp.getResults(), *selected, knowledge);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mode != CoreWalkMode::ExecuteCommunication || mlir::isa<pim::PimSendOp, pim::PimReceiveOp>(op))
|
||||
hasFailure |= failed(callback(op, knowledge));
|
||||
}
|
||||
return mlir::success(!hasFailure);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PimCoreCommunicationPlan buildPimCoreCommunicationPlan(mlir::Block& block) {
|
||||
llvm::DenseSet<mlir::Operation*> communicationAncestors;
|
||||
block.walk([&](mlir::Operation* op) {
|
||||
if (!mlir::isa<pim::PimSendOp, pim::PimReceiveOp>(op))
|
||||
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::Block& block,
|
||||
const StaticValueKnowledge& knowledge,
|
||||
llvm::function_ref<mlir::LogicalResult(mlir::Operation&, const StaticValueKnowledge&)> callback) {
|
||||
return walkPimCoreBlockImpl(block, knowledge, CoreWalkMode::StructuralExtremes, nullptr, callback);
|
||||
}
|
||||
|
||||
} // namespace onnx_mlir
|
||||
|
||||
@@ -3,21 +3,28 @@
|
||||
#include "mlir/IR/Block.h"
|
||||
#include "mlir/Support/LogicalResult.h"
|
||||
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/STLFunctionalExtras.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
#include "src/Accelerators/PIM/Common/IR/AddressAnalysis.hpp"
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
using PimCoreCommunicationPlan = llvm::DenseMap<mlir::Block*, llvm::SmallVector<mlir::Operation*, 8>>;
|
||||
|
||||
/// Returns true for ops in a `pim.core` body that only participate in static
|
||||
/// address or index computation and therefore do not emit PIM instructions.
|
||||
bool isCoreStaticAddressOp(mlir::Operation* op);
|
||||
|
||||
/// Walks a `pim.core` body, statically unrolling nested `scf.for` loops when
|
||||
/// their bounds are known and invoking `callback` only on instruction-emitting
|
||||
/// operations.
|
||||
mlir::LogicalResult
|
||||
walkPimCoreBlock(mlir::Block& block,
|
||||
/// Walks a `pim.core` body's communication stream, statically unrolling
|
||||
/// control flow that contains send/receive operations and invoking `callback`
|
||||
/// on those operations in execution order.
|
||||
PimCoreCommunicationPlan buildPimCoreCommunicationPlan(mlir::Block& block);
|
||||
|
||||
mlir::LogicalResult walkPimCoreCommunicationBlock(
|
||||
mlir::Block& block,
|
||||
const PimCoreCommunicationPlan& plan,
|
||||
const StaticValueKnowledge& knowledge,
|
||||
llvm::function_ref<mlir::LogicalResult(mlir::Operation&, const StaticValueKnowledge&)> callback);
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#include "mlir/Dialect/Linalg/IR/Linalg.h"
|
||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||
#include "mlir/Interfaces/SideEffectInterfaces.h"
|
||||
|
||||
#include "ShapingUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||
|
||||
using namespace mlir;
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
bool isShapingOnlyOp(Operation *op) {
|
||||
return isa<tensor::CastOp,
|
||||
tensor::CollapseShapeOp,
|
||||
tensor::ExpandShapeOp,
|
||||
tensor::ExtractSliceOp,
|
||||
tensor::InsertSliceOp,
|
||||
tensor::ConcatOp,
|
||||
tensor::EmptyOp,
|
||||
tensor::ExtractOp,
|
||||
tensor::InsertOp,
|
||||
tensor::SplatOp,
|
||||
linalg::TransposeOp,
|
||||
ONNXTransposeOp,
|
||||
spatial::SpatConcatOp,
|
||||
spatial::SpatExtractRowsOp>(op);
|
||||
}
|
||||
|
||||
bool isPureIndexComputationOp(Operation *op) {
|
||||
if (op->getNumRegions() != 0 || op->getNumResults() == 0 || op->hasTrait<OpTrait::IsTerminator>()
|
||||
|| !isMemoryEffectFree(op))
|
||||
return false;
|
||||
auto isIndexOrInteger = [](Type type) { return type.isIndex() || isa<IntegerType>(type); };
|
||||
return llvm::all_of(op->getOperandTypes(), isIndexOrInteger)
|
||||
&& llvm::all_of(op->getResultTypes(), isIndexOrInteger);
|
||||
}
|
||||
|
||||
} // namespace onnx_mlir
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
namespace mlir {
|
||||
class Operation;
|
||||
}
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
bool isShapingOnlyOp(mlir::Operation *op);
|
||||
|
||||
bool isPureIndexComputationOp(mlir::Operation *op);
|
||||
|
||||
} // namespace onnx_mlir
|
||||
@@ -0,0 +1,223 @@
|
||||
#include "StaticIntGrid.hpp"
|
||||
|
||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||
|
||||
#include "AffineUtils.hpp"
|
||||
#include "ConstantUtils.hpp"
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
using namespace mlir;
|
||||
|
||||
namespace onnx_mlir {
|
||||
namespace {
|
||||
|
||||
static std::optional<size_t> cellCount(size_t rows, size_t columns) {
|
||||
if (!rows || !columns ||
|
||||
rows > static_cast<size_t>(std::numeric_limits<int64_t>::max()) / columns)
|
||||
return std::nullopt;
|
||||
return rows * columns;
|
||||
}
|
||||
|
||||
static bool checkedFlatIndex(
|
||||
size_t row, size_t columns, size_t column, size_t &flat) {
|
||||
bool overflow;
|
||||
flat = llvm::SaturatingMultiplyAdd(row, columns, column, &overflow);
|
||||
return !overflow &&
|
||||
flat <= static_cast<size_t>(std::numeric_limits<int64_t>::max());
|
||||
}
|
||||
|
||||
static bool affineValue(int64_t base, int64_t rowStep, int64_t columnStep,
|
||||
size_t row, size_t column, int64_t &result) {
|
||||
if (row > static_cast<size_t>(std::numeric_limits<int64_t>::max()) ||
|
||||
column > static_cast<size_t>(std::numeric_limits<int64_t>::max()))
|
||||
return false;
|
||||
int64_t rowValue, columnValue;
|
||||
return !llvm::MulOverflow(rowStep, static_cast<int64_t>(row), rowValue)
|
||||
&& !llvm::MulOverflow(columnStep, static_cast<int64_t>(column),
|
||||
columnValue)
|
||||
&& !llvm::AddOverflow(base, rowValue, result)
|
||||
&& !llvm::AddOverflow(result, columnValue, result);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
FailureOr<StaticIntGrid> StaticIntGrid::fromSequences(
|
||||
ArrayRef<StaticIntSequence> input, bool columnsInput,
|
||||
int64_t sparseBase) {
|
||||
if (input.empty() || !input.front().size())
|
||||
return failure();
|
||||
size_t rowCount = columnsInput ? input.front().size() : input.size();
|
||||
size_t columnCount = columnsInput ? input.size() : input.front().size();
|
||||
auto cells = cellCount(rowCount, columnCount);
|
||||
if (!cells ||
|
||||
llvm::any_of(input, [&](const StaticIntSequence &sequence) {
|
||||
return sequence.size() != input.front().size();
|
||||
}))
|
||||
return failure();
|
||||
StaticIntGrid result(rowCount, columnCount, input.front().valueAt(0));
|
||||
if (llvm::all_equal(input)) {
|
||||
if (input.front().getKind() == StaticIntSequenceKind::Uniform)
|
||||
return result;
|
||||
result.kind = columnsInput ? Kind::ActionOnly : Kind::LaneOnly;
|
||||
result.values = input.front();
|
||||
return result;
|
||||
}
|
||||
SmallVector<int64_t> outerBases;
|
||||
for (const StaticIntSequence &sequence : input)
|
||||
outerBases.push_back(sequence.valueAt(0));
|
||||
if (llvm::all_of(input, [](const StaticIntSequence &sequence) {
|
||||
return sequence.getKind() == StaticIntSequenceKind::Uniform;
|
||||
})) {
|
||||
result.kind = columnsInput ? Kind::LaneOnly : Kind::ActionOnly;
|
||||
result.values = StaticIntSequence::fromValues(outerBases);
|
||||
return result;
|
||||
}
|
||||
auto innerStep = input.front().getAffineStep();
|
||||
StaticIntSequence bases = StaticIntSequence::fromValues(outerBases);
|
||||
auto outerStep = bases.getAffineStep();
|
||||
if (innerStep && outerStep &&
|
||||
llvm::all_of(input, [&](const StaticIntSequence &sequence) {
|
||||
return sequence.getAffineStep() == innerStep;
|
||||
}))
|
||||
return affine2D(result.base,
|
||||
columnsInput ? *innerStep : *outerStep,
|
||||
columnsInput ? *outerStep : *innerStep, rowCount, columnCount);
|
||||
SmallVector<int64_t> values;
|
||||
values.reserve(*cells);
|
||||
for (size_t row = 0; row < rowCount; ++row)
|
||||
for (size_t column = 0; column < columnCount; ++column)
|
||||
values.push_back(columnsInput ? input[column].valueAt(row)
|
||||
: input[row].valueAt(column));
|
||||
result.values = StaticIntSequence::fromValues(values);
|
||||
for (size_t index = 0; index < *cells; ++index)
|
||||
if (values[index] != sparseBase)
|
||||
result.overrideKeys.push_back(static_cast<int64_t>(index));
|
||||
if (result.overrideKeys.size() <= *cells / 4) {
|
||||
result.kind = Kind::SparseLaneOverrides;
|
||||
result.base = sparseBase;
|
||||
} else {
|
||||
result.kind = Kind::Dense;
|
||||
result.overrideKeys.clear();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
FailureOr<StaticIntGrid> StaticIntGrid::fromRows(
|
||||
ArrayRef<StaticIntSequence> rows) {
|
||||
if (rows.empty() || !rows.front().size())
|
||||
return failure();
|
||||
return fromSequences(rows, false, rows.front().valueAt(0));
|
||||
}
|
||||
|
||||
FailureOr<StaticIntGrid> StaticIntGrid::fromColumns(
|
||||
size_t rowCount, ArrayRef<StaticIntSequence> columnSequences,
|
||||
int64_t defaultValue) {
|
||||
if (!cellCount(rowCount, columnSequences.size()))
|
||||
return failure();
|
||||
SmallVector<StaticIntSequence> padded;
|
||||
padded.reserve(columnSequences.size());
|
||||
for (const StaticIntSequence &sequence : columnSequences) {
|
||||
if (sequence.size() > rowCount)
|
||||
return failure();
|
||||
if (sequence.size() == rowCount) {
|
||||
padded.push_back(sequence);
|
||||
continue;
|
||||
}
|
||||
SmallVector<int64_t> values(rowCount, defaultValue);
|
||||
for (size_t row = 0; row < sequence.size(); ++row)
|
||||
values[row] = sequence.valueAt(row);
|
||||
padded.push_back(StaticIntSequence::fromValues(values));
|
||||
}
|
||||
return fromSequences(padded, true, defaultValue);
|
||||
}
|
||||
|
||||
FailureOr<StaticIntGrid> StaticIntGrid::affine2D(
|
||||
int64_t base, int64_t rowStep, int64_t columnStep,
|
||||
size_t rows, size_t columns) {
|
||||
int64_t last;
|
||||
if (!cellCount(rows, columns) ||
|
||||
!affineValue(base, rowStep, columnStep, rows - 1, columns - 1, last))
|
||||
return failure();
|
||||
StaticIntGrid result(rows, columns, base);
|
||||
result.kind = rowStep || columnStep ? Kind::Affine : Kind::Uniform;
|
||||
result.rowStep = rowStep;
|
||||
result.columnStep = columnStep;
|
||||
return result;
|
||||
}
|
||||
|
||||
FailureOr<StaticIntGrid> StaticIntGrid::laneIntervals(
|
||||
size_t columns, ArrayRef<std::pair<size_t, size_t>> intervals,
|
||||
int64_t insideValue, int64_t outsideValue) {
|
||||
if (!columns)
|
||||
return failure();
|
||||
SmallVector<int64_t> values(columns, outsideValue);
|
||||
for (auto [begin, end] : intervals) {
|
||||
if (begin > end || end > columns)
|
||||
return failure();
|
||||
std::fill(values.begin() + begin, values.begin() + end, insideValue);
|
||||
}
|
||||
StaticIntSequence row = StaticIntSequence::fromValues(values);
|
||||
return fromRows(ArrayRef<StaticIntSequence>(row));
|
||||
}
|
||||
|
||||
int64_t StaticIntGrid::valueAt(size_t row, size_t column) const {
|
||||
assert(row < rows && column < columns);
|
||||
if (kind == Kind::Uniform)
|
||||
return base;
|
||||
if (kind == Kind::ActionOnly)
|
||||
return values->valueAt(row);
|
||||
if (kind == Kind::LaneOnly)
|
||||
return values->valueAt(column);
|
||||
if (kind == Kind::Affine) {
|
||||
int64_t result;
|
||||
bool valid = affineValue(base, rowStep, columnStep, row, column, result);
|
||||
assert(valid);
|
||||
return result;
|
||||
}
|
||||
size_t flat;
|
||||
bool valid = checkedFlatIndex(row, columns, column, flat);
|
||||
assert(valid);
|
||||
if (kind == Kind::SparseLaneOverrides) {
|
||||
auto found = llvm::lower_bound(overrideKeys, static_cast<int64_t>(flat));
|
||||
if (found == overrideKeys.end() || *found != static_cast<int64_t>(flat))
|
||||
return base;
|
||||
}
|
||||
assert(kind == Kind::Dense || kind == Kind::SparseLaneOverrides);
|
||||
return values->valueAt(flat);
|
||||
}
|
||||
|
||||
Value StaticIntGrid::emitLookup(Value row, Value column,
|
||||
Operation *constantAnchor,
|
||||
ConstantPool &constants, OpBuilder &builder,
|
||||
Location loc) const {
|
||||
if (kind == Kind::Uniform)
|
||||
return constants.getIndex(base);
|
||||
if (kind == Kind::ActionOnly || kind == Kind::LaneOnly)
|
||||
return emitStaticIntLookup(
|
||||
*values, kind == Kind::ActionOnly ? row : column,
|
||||
constantAnchor, constants, builder, loc);
|
||||
Value flat = affineMulConst(builder, loc, row, columns, constantAnchor);
|
||||
flat = arith::AddIOp::create(builder, loc, flat, column);
|
||||
if (kind == Kind::Affine) {
|
||||
Value rowValue = affineMulConst(
|
||||
builder, loc, row, rowStep, constantAnchor);
|
||||
Value columnValue = affineMulConst(
|
||||
builder, loc, column, columnStep, constantAnchor);
|
||||
Value result = arith::AddIOp::create(builder, loc, rowValue, columnValue);
|
||||
return affineAddConst(builder, loc, result, base, constantAnchor);
|
||||
}
|
||||
return emitStaticIntLookup(
|
||||
*values, flat, constantAnchor, constants, builder, loc);
|
||||
}
|
||||
|
||||
OpFoldResult StaticIntGrid::emitFoldedLookup(
|
||||
Value row, Value column, Operation *constantAnchor,
|
||||
ConstantPool &constants, OpBuilder &builder, Location loc) const {
|
||||
return kind == Kind::Uniform
|
||||
? OpFoldResult(builder.getIndexAttr(base))
|
||||
: OpFoldResult(emitLookup(
|
||||
row, column, constantAnchor, constants, builder, loc));
|
||||
}
|
||||
|
||||
} // namespace onnx_mlir
|
||||
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include "StaticIntSequence.hpp"
|
||||
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
class ConstantPool;
|
||||
|
||||
class StaticIntGrid {
|
||||
public:
|
||||
static mlir::FailureOr<StaticIntGrid> fromColumns(
|
||||
size_t rows, llvm::ArrayRef<StaticIntSequence> columns,
|
||||
int64_t defaultValue);
|
||||
static mlir::FailureOr<StaticIntGrid> fromRows(
|
||||
llvm::ArrayRef<StaticIntSequence> rows);
|
||||
static mlir::FailureOr<StaticIntGrid> affine2D(
|
||||
int64_t base, int64_t rowStep, int64_t columnStep,
|
||||
size_t rows, size_t columns);
|
||||
static mlir::FailureOr<StaticIntGrid> laneIntervals(
|
||||
size_t columns,
|
||||
llvm::ArrayRef<std::pair<size_t, size_t>> intervals,
|
||||
int64_t insideValue, int64_t outsideValue);
|
||||
|
||||
int64_t valueAt(size_t row, size_t column) const;
|
||||
|
||||
mlir::Value emitLookup(mlir::Value row, mlir::Value column,
|
||||
mlir::Operation *constantAnchor,
|
||||
ConstantPool &constants, mlir::OpBuilder &builder,
|
||||
mlir::Location loc) const;
|
||||
mlir::OpFoldResult emitFoldedLookup(
|
||||
mlir::Value row, mlir::Value column, mlir::Operation *constantAnchor,
|
||||
ConstantPool &constants, mlir::OpBuilder &builder,
|
||||
mlir::Location loc) const;
|
||||
|
||||
private:
|
||||
enum class Kind { Uniform, ActionOnly, LaneOnly, Affine,
|
||||
SparseLaneOverrides, Dense };
|
||||
|
||||
StaticIntGrid(size_t rows, size_t columns, int64_t base)
|
||||
: rows(rows), columns(columns), base(base) {}
|
||||
|
||||
static mlir::FailureOr<StaticIntGrid> fromSequences(
|
||||
llvm::ArrayRef<StaticIntSequence> sequences, bool columns,
|
||||
int64_t sparseBase);
|
||||
|
||||
Kind kind = Kind::Uniform;
|
||||
size_t rows = 0;
|
||||
size_t columns = 0;
|
||||
int64_t base = 0;
|
||||
int64_t rowStep = 0;
|
||||
int64_t columnStep = 0;
|
||||
llvm::SmallVector<int64_t> overrideKeys;
|
||||
std::optional<StaticIntSequence> values;
|
||||
};
|
||||
|
||||
} // namespace onnx_mlir
|
||||
@@ -0,0 +1,539 @@
|
||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
#include "llvm/Support/MathExtras.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "AffineUtils.hpp"
|
||||
#include "ConstantUtils.hpp"
|
||||
#include "StaticIntSequence.hpp"
|
||||
|
||||
using namespace mlir;
|
||||
|
||||
namespace onnx_mlir {
|
||||
namespace {
|
||||
|
||||
static bool getAffineValue(int64_t base, int64_t step, size_t index,
|
||||
int64_t &value) {
|
||||
if (index > static_cast<size_t>(std::numeric_limits<int64_t>::max()))
|
||||
return false;
|
||||
int64_t scaled;
|
||||
return !llvm::MulOverflow(static_cast<int64_t>(index), step, scaled)
|
||||
&& !llvm::AddOverflow(base, scaled, value);
|
||||
}
|
||||
|
||||
static FailureOr<SmallVector<int64_t>> getI64Values(Operation *op,
|
||||
StringRef name) {
|
||||
Attribute attr = op->getAttr(name);
|
||||
if (!attr)
|
||||
return op->emitOpError() << "is missing " << name << " metadata",
|
||||
failure();
|
||||
if (auto scalar = dyn_cast<IntegerAttr>(attr))
|
||||
return SmallVector<int64_t> {scalar.getInt()};
|
||||
if (auto array = dyn_cast<DenseI64ArrayAttr>(attr))
|
||||
return SmallVector<int64_t>(array.asArrayRef());
|
||||
auto elements = dyn_cast<DenseIntElementsAttr>(attr);
|
||||
auto type = elements ? dyn_cast<RankedTensorType>(elements.getType())
|
||||
: RankedTensorType();
|
||||
if (!elements || !type || type.getRank() != 1
|
||||
|| !type.getElementType().isInteger(64))
|
||||
return op->emitOpError() << "has invalid " << name << " metadata",
|
||||
failure();
|
||||
SmallVector<int64_t> values;
|
||||
values.reserve(elements.getNumElements());
|
||||
for (APInt value : elements.getValues<APInt>())
|
||||
values.push_back(value.getSExtValue());
|
||||
return values;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
StaticIntSequence StaticIntSequence::uniform(int64_t value, size_t count) {
|
||||
assert(count != 0 && "empty static integer sequence");
|
||||
StaticIntSequence result;
|
||||
result.kind = StaticIntSequenceKind::Uniform;
|
||||
result.count = count;
|
||||
result.base = value;
|
||||
return result;
|
||||
}
|
||||
|
||||
StaticIntSequence StaticIntSequence::affine(int64_t base, int64_t step,
|
||||
size_t count) {
|
||||
assert(count != 0 && "empty static integer sequence");
|
||||
int64_t last;
|
||||
assert(getAffineValue(base, step, count - 1, last)
|
||||
&& "overflowing static affine sequence");
|
||||
if (count == 1 || step == 0)
|
||||
return uniform(base, count);
|
||||
StaticIntSequence result;
|
||||
result.kind = StaticIntSequenceKind::Affine;
|
||||
result.count = count;
|
||||
result.base = base;
|
||||
result.step = step;
|
||||
return result;
|
||||
}
|
||||
|
||||
StaticIntSequence StaticIntSequence::runLengthEncoded(
|
||||
ArrayRef<int64_t> runs, size_t count) {
|
||||
assert(count != 0 && runs.size() % 2 == 0
|
||||
&& "invalid run-length encoded sequence");
|
||||
StaticIntSequence result;
|
||||
result.kind = StaticIntSequenceKind::RunLengthEncoded;
|
||||
result.count = count;
|
||||
result.data.assign(runs);
|
||||
return result;
|
||||
}
|
||||
|
||||
StaticIntSequence StaticIntSequence::fromValues(ArrayRef<int64_t> values) {
|
||||
assert(!values.empty() && "empty static integer sequence");
|
||||
if (llvm::all_equal(values))
|
||||
return uniform(values.front(), values.size());
|
||||
int64_t step;
|
||||
bool isAffine = !llvm::SubOverflow(values[1], values[0], step);
|
||||
for (size_t index = 1; isAffine && index < values.size(); ++index) {
|
||||
int64_t difference;
|
||||
isAffine = !llvm::SubOverflow(values[index], values[index - 1],
|
||||
difference)
|
||||
&& difference == step;
|
||||
}
|
||||
if (isAffine)
|
||||
return affine(values.front(), step, values.size());
|
||||
|
||||
SmallVector<int64_t> runs;
|
||||
for (int64_t value : values) {
|
||||
if (!runs.empty() && runs[runs.size() - 2] == value) {
|
||||
++runs.back();
|
||||
continue;
|
||||
}
|
||||
runs.push_back(value);
|
||||
runs.push_back(1);
|
||||
}
|
||||
if (runs.size() < values.size())
|
||||
return runLengthEncoded(runs, values.size());
|
||||
StaticIntSequence result;
|
||||
result.kind = StaticIntSequenceKind::Dense;
|
||||
result.count = values.size();
|
||||
result.data.assign(values);
|
||||
return result;
|
||||
}
|
||||
|
||||
int64_t StaticIntSequence::valueAt(size_t index) const {
|
||||
assert(index < count && "static integer sequence index out of range");
|
||||
if (kind == StaticIntSequenceKind::Uniform)
|
||||
return base;
|
||||
if (kind == StaticIntSequenceKind::Affine) {
|
||||
int64_t value;
|
||||
bool valid = getAffineValue(base, step, index, value);
|
||||
assert(valid && "overflowing static affine sequence");
|
||||
return value;
|
||||
}
|
||||
if (kind == StaticIntSequenceKind::Dense)
|
||||
return data[index];
|
||||
for (size_t run = 0; run < data.size(); run += 2) {
|
||||
size_t length = static_cast<size_t>(data[run + 1]);
|
||||
if (index < length)
|
||||
return data[run];
|
||||
index -= length;
|
||||
}
|
||||
llvm_unreachable("malformed run-length encoded sequence");
|
||||
}
|
||||
|
||||
std::optional<size_t> StaticIntSequence::find(int64_t value, size_t begin,
|
||||
size_t length) const {
|
||||
assert(begin <= count && length <= count - begin
|
||||
&& "invalid static integer sequence search");
|
||||
if (length == 0)
|
||||
return std::nullopt;
|
||||
size_t end = begin + length;
|
||||
if (kind == StaticIntSequenceKind::Uniform)
|
||||
return value == base ? std::optional<size_t>(begin) : std::nullopt;
|
||||
if (kind == StaticIntSequenceKind::Affine) {
|
||||
int64_t delta;
|
||||
if (llvm::SubOverflow(value, base, delta) || delta % step != 0)
|
||||
return std::nullopt;
|
||||
int64_t index = delta / step;
|
||||
return index >= static_cast<int64_t>(begin)
|
||||
&& index < static_cast<int64_t>(end)
|
||||
? std::optional<size_t>(index)
|
||||
: std::nullopt;
|
||||
}
|
||||
if (kind == StaticIntSequenceKind::Dense) {
|
||||
ArrayRef<int64_t> selected = ArrayRef(data).slice(begin, length);
|
||||
auto found = llvm::find(selected, value);
|
||||
return found == selected.end()
|
||||
? std::nullopt
|
||||
: std::optional<size_t>(begin + (found - selected.begin()));
|
||||
}
|
||||
size_t runBegin = 0;
|
||||
for (size_t run = 0; run < data.size(); run += 2) {
|
||||
size_t runEnd = runBegin + static_cast<size_t>(data[run + 1]);
|
||||
if (runEnd > begin && runBegin < end && data[run] == value)
|
||||
return std::max(begin, runBegin);
|
||||
if (runBegin >= end)
|
||||
break;
|
||||
runBegin = runEnd;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
StaticIntSequence StaticIntSequence::slice(size_t begin, size_t length) const {
|
||||
assert(length != 0 && begin <= count - length && "invalid sequence slice");
|
||||
if (kind == StaticIntSequenceKind::Uniform)
|
||||
return uniform(base, length);
|
||||
if (kind == StaticIntSequenceKind::Affine)
|
||||
return affine(valueAt(begin), step, length);
|
||||
if (kind == StaticIntSequenceKind::Dense)
|
||||
return fromValues(ArrayRef(data).slice(begin, length));
|
||||
SmallVector<int64_t> runs;
|
||||
size_t end = begin + length;
|
||||
forEachEqualRun([&](int64_t value, size_t runBegin, size_t runCount) {
|
||||
size_t selectedBegin = std::max(begin, runBegin);
|
||||
size_t selectedEnd = std::min(end, runBegin + runCount);
|
||||
if (selectedBegin >= selectedEnd)
|
||||
return;
|
||||
if (!runs.empty() && runs[runs.size() - 2] == value)
|
||||
runs.back() += selectedEnd - selectedBegin;
|
||||
else {
|
||||
runs.push_back(value);
|
||||
runs.push_back(selectedEnd - selectedBegin);
|
||||
}
|
||||
});
|
||||
if (runs.size() == 2)
|
||||
return uniform(runs.front(), length);
|
||||
if (runs.size() < length)
|
||||
return runLengthEncoded(runs, length);
|
||||
SmallVector<int64_t> values;
|
||||
for (size_t run = 0; run < runs.size(); run += 2)
|
||||
values.append(runs[run + 1], runs[run]);
|
||||
return fromValues(values);
|
||||
}
|
||||
|
||||
StaticIntSequence StaticIntSequence::remap(ArrayRef<unsigned> indices) const {
|
||||
assert(!indices.empty() && "empty static integer sequence remap");
|
||||
SmallVector<int64_t> values;
|
||||
values.reserve(indices.size());
|
||||
for (unsigned index : indices)
|
||||
values.push_back(valueAt(index));
|
||||
return fromValues(values);
|
||||
}
|
||||
|
||||
bool StaticIntSequence::operator==(const StaticIntSequence& other) const {
|
||||
return kind == other.kind && count == other.count && base == other.base
|
||||
&& step == other.step && data == other.data;
|
||||
}
|
||||
|
||||
llvm::hash_code StaticIntSequence::hash() const {
|
||||
return llvm::hash_combine(kind, count, base, step,
|
||||
llvm::hash_combine_range(data.begin(), data.end()));
|
||||
}
|
||||
|
||||
void StaticIntSequence::forEachEqualRun(
|
||||
llvm::function_ref<void(int64_t, size_t, size_t)> callback) const {
|
||||
if (kind == StaticIntSequenceKind::Uniform) {
|
||||
callback(base, 0, count);
|
||||
return;
|
||||
}
|
||||
if (kind == StaticIntSequenceKind::RunLengthEncoded) {
|
||||
size_t begin = 0;
|
||||
for (size_t run = 0; run < data.size(); run += 2) {
|
||||
size_t runCount = static_cast<size_t>(data[run + 1]);
|
||||
callback(data[run], begin, runCount);
|
||||
begin += runCount;
|
||||
}
|
||||
return;
|
||||
}
|
||||
size_t begin = 0;
|
||||
while (begin < count) {
|
||||
int64_t value = valueAt(begin);
|
||||
size_t end = begin + 1;
|
||||
while (end < count && valueAt(end) == value)
|
||||
++end;
|
||||
callback(value, begin, end - begin);
|
||||
begin = end;
|
||||
}
|
||||
}
|
||||
|
||||
void StaticIntSequenceChain::append(const StaticIntSequence &sequence,
|
||||
size_t begin, size_t length) {
|
||||
assert(length != 0 && begin <= sequence.size() - length
|
||||
&& "invalid static integer sequence chain slice");
|
||||
if (!slices.empty()) {
|
||||
StaticIntSequenceSlice &last = slices.back();
|
||||
if (last.sequence == &sequence && last.begin + last.count == begin) {
|
||||
last.count += length;
|
||||
count += length;
|
||||
return;
|
||||
}
|
||||
auto affinePart = [](const StaticIntSequenceSlice &slice,
|
||||
int64_t &base, int64_t &step) {
|
||||
base = slice.sequence->valueAt(slice.begin);
|
||||
if (slice.count == 1) {
|
||||
step = 0;
|
||||
return true;
|
||||
}
|
||||
return !llvm::SubOverflow(slice.sequence->valueAt(slice.begin + 1),
|
||||
base, step)
|
||||
&& (slice.sequence->kind == StaticIntSequenceKind::Uniform
|
||||
|| slice.sequence->kind == StaticIntSequenceKind::Affine);
|
||||
};
|
||||
StaticIntSequenceSlice next {&sequence, begin, length};
|
||||
int64_t leftBase, leftStep, rightBase, rightStep, expected;
|
||||
if (affinePart(last, leftBase, leftStep)
|
||||
&& affinePart(next, rightBase, rightStep)
|
||||
&& (last.count == 1 || length == 1 || leftStep == rightStep)) {
|
||||
int64_t step = last.count == 1 ? rightStep : leftStep;
|
||||
if (getAffineValue(leftBase, step, last.count, expected)
|
||||
&& expected == rightBase) {
|
||||
owned.push_back(std::make_unique<StaticIntSequence>(
|
||||
StaticIntSequence::affine(leftBase, step, last.count + length)));
|
||||
last = {owned.back().get(), 0, last.count + length};
|
||||
count += length;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
slices.push_back({&sequence, begin, length});
|
||||
count += length;
|
||||
}
|
||||
|
||||
void StaticIntSequenceChain::append(StaticIntSequence sequence) {
|
||||
size_t length = sequence.size();
|
||||
owned.push_back(std::make_unique<StaticIntSequence>(std::move(sequence)));
|
||||
append(*owned.back(), 0, length);
|
||||
}
|
||||
|
||||
int64_t StaticIntSequenceChain::valueAt(size_t index) const {
|
||||
assert(index < count && "static integer sequence chain index out of range");
|
||||
for (const StaticIntSequenceSlice &slice : slices) {
|
||||
if (index < slice.count)
|
||||
return slice.sequence->valueAt(slice.begin + index);
|
||||
index -= slice.count;
|
||||
}
|
||||
llvm_unreachable("malformed static integer sequence chain");
|
||||
}
|
||||
|
||||
void StaticIntSequenceChain::forEachSegment(llvm::function_ref<void(
|
||||
const StaticIntSequence &, size_t, size_t)> callback) const {
|
||||
for (const StaticIntSequenceSlice &slice : slices)
|
||||
callback(*slice.sequence, slice.begin, slice.count);
|
||||
}
|
||||
|
||||
void StaticIntSequenceChain::forEachEqualRun(
|
||||
llvm::function_ref<void(int64_t, size_t, size_t)> callback) const {
|
||||
std::optional<int64_t> pendingValue;
|
||||
size_t pendingBegin = 0, pendingCount = 0, chainBegin = 0;
|
||||
auto flush = [&] {
|
||||
if (pendingValue)
|
||||
callback(*pendingValue, pendingBegin, pendingCount);
|
||||
};
|
||||
for (const StaticIntSequenceSlice &slice : slices) {
|
||||
size_t sliceEnd = slice.begin + slice.count;
|
||||
slice.sequence->forEachEqualRun(
|
||||
[&](int64_t value, size_t runBegin, size_t runCount) {
|
||||
size_t begin = std::max(slice.begin, runBegin);
|
||||
size_t end = std::min(sliceEnd, runBegin + runCount);
|
||||
if (begin >= end)
|
||||
return;
|
||||
size_t selectedCount = end - begin;
|
||||
size_t globalBegin = chainBegin + begin - slice.begin;
|
||||
if (pendingValue && *pendingValue == value
|
||||
&& pendingBegin + pendingCount == globalBegin) {
|
||||
pendingCount += selectedCount;
|
||||
return;
|
||||
}
|
||||
flush();
|
||||
pendingValue = value;
|
||||
pendingBegin = globalBegin;
|
||||
pendingCount = selectedCount;
|
||||
});
|
||||
chainBegin += slice.count;
|
||||
}
|
||||
flush();
|
||||
}
|
||||
|
||||
StaticIntSequence StaticIntSequenceChain::canonicalize() const {
|
||||
assert(count != 0 && "empty static integer sequence chain");
|
||||
int64_t first = valueAt(0);
|
||||
bool uniform = true;
|
||||
forEachEqualRun([&](int64_t value, size_t, size_t) {
|
||||
uniform &= value == first;
|
||||
});
|
||||
if (uniform)
|
||||
return StaticIntSequence::uniform(first, count);
|
||||
|
||||
int64_t step = 0, previous = first;
|
||||
bool affine = true, haveStep = false;
|
||||
size_t position = 0;
|
||||
forEachSegment([&](const StaticIntSequence &sequence, size_t begin,
|
||||
size_t length) {
|
||||
if (!affine)
|
||||
return;
|
||||
for (size_t index = 0; index < length; ++index) {
|
||||
int64_t value = sequence.valueAt(begin + index);
|
||||
if (position++ == 0) {
|
||||
previous = value;
|
||||
continue;
|
||||
}
|
||||
if (!haveStep) {
|
||||
affine = !llvm::SubOverflow(value, previous, step);
|
||||
haveStep = true;
|
||||
} else if (haveStep) {
|
||||
int64_t difference;
|
||||
affine = !llvm::SubOverflow(value, previous, difference)
|
||||
&& difference == step;
|
||||
}
|
||||
previous = value;
|
||||
if (!affine)
|
||||
break;
|
||||
}
|
||||
});
|
||||
if (affine && haveStep)
|
||||
return StaticIntSequence::affine(first, step, count);
|
||||
|
||||
SmallVector<int64_t> runs;
|
||||
forEachEqualRun([&](int64_t value, size_t, size_t runCount) {
|
||||
runs.push_back(value);
|
||||
runs.push_back(runCount);
|
||||
});
|
||||
if (runs.size() < count)
|
||||
return StaticIntSequence::runLengthEncoded(runs, count);
|
||||
SmallVector<int64_t> values;
|
||||
values.reserve(count);
|
||||
for (size_t run = 0; run < runs.size(); run += 2)
|
||||
values.append(runs[run + 1], runs[run]);
|
||||
return StaticIntSequence::fromValues(values);
|
||||
}
|
||||
|
||||
int64_t StaticIntSequenceChainCursor::value() const {
|
||||
assert(!done() && "static integer sequence chain cursor is done");
|
||||
const StaticIntSequenceSlice ¤t = chain.slices[slice];
|
||||
return current.sequence->valueAt(current.begin + offset);
|
||||
}
|
||||
|
||||
void StaticIntSequenceChainCursor::advance() {
|
||||
assert(!done() && "static integer sequence chain cursor is done");
|
||||
if (++offset != chain.slices[slice].count)
|
||||
return;
|
||||
offset = 0;
|
||||
++slice;
|
||||
}
|
||||
|
||||
void setStaticIntSequenceAttr(Operation *op, StringRef name,
|
||||
const StaticIntSequence &sequence,
|
||||
size_t logicalCount) {
|
||||
assert(sequence.size() == logicalCount && logicalCount != 0
|
||||
&& "invalid static integer metadata count");
|
||||
SmallVector<int64_t> values;
|
||||
StringRef encoding;
|
||||
switch (sequence.kind) {
|
||||
case StaticIntSequenceKind::Uniform:
|
||||
encoding = "uniform";
|
||||
values.push_back(sequence.base);
|
||||
break;
|
||||
case StaticIntSequenceKind::Affine:
|
||||
encoding = "affine";
|
||||
values = {sequence.base, sequence.step};
|
||||
break;
|
||||
case StaticIntSequenceKind::RunLengthEncoded:
|
||||
encoding = "rle";
|
||||
values = sequence.data;
|
||||
break;
|
||||
case StaticIntSequenceKind::Dense:
|
||||
encoding = "dense";
|
||||
values = sequence.data;
|
||||
break;
|
||||
}
|
||||
OpBuilder builder(op);
|
||||
auto type = RankedTensorType::get(
|
||||
{static_cast<int64_t>(values.size())}, builder.getI64Type());
|
||||
op->setAttr(name, DenseIntElementsAttr::get(type, values));
|
||||
if (sequence.kind != StaticIntSequenceKind::Dense)
|
||||
op->setAttr((name + "_encoding").str(), builder.getStringAttr(encoding));
|
||||
}
|
||||
|
||||
FailureOr<StaticIntSequence> getStaticIntSequenceAttr(
|
||||
Operation *op, StringRef name, size_t logicalCount) {
|
||||
if (logicalCount == 0)
|
||||
return op->emitOpError() << "has zero logical count for " << name,
|
||||
failure();
|
||||
auto values = getI64Values(op, name);
|
||||
if (failed(values))
|
||||
return failure();
|
||||
auto encoding = op->getAttrOfType<StringAttr>((name + "_encoding").str());
|
||||
if (!encoding) {
|
||||
if (values->size() != logicalCount)
|
||||
return op->emitOpError() << "has invalid dense " << name << " count",
|
||||
failure();
|
||||
return StaticIntSequence::fromValues(*values);
|
||||
}
|
||||
if (encoding.getValue() == "uniform") {
|
||||
if (values->size() != 1)
|
||||
return op->emitOpError() << "has invalid uniform " << name,
|
||||
failure();
|
||||
return StaticIntSequence::uniform(values->front(), logicalCount);
|
||||
}
|
||||
if (encoding.getValue() == "affine") {
|
||||
int64_t last;
|
||||
if (values->size() != 2
|
||||
|| !getAffineValue((*values)[0], (*values)[1], logicalCount - 1, last))
|
||||
return op->emitOpError() << "has invalid affine " << name,
|
||||
failure();
|
||||
return StaticIntSequence::affine((*values)[0], (*values)[1], logicalCount);
|
||||
}
|
||||
if (encoding.getValue() == "rle") {
|
||||
size_t count = 0;
|
||||
if (values->empty() || values->size() % 2 != 0)
|
||||
return op->emitOpError() << "has invalid RLE " << name, failure();
|
||||
for (size_t index = 1; index < values->size(); index += 2) {
|
||||
if ((*values)[index] <= 0
|
||||
|| static_cast<uint64_t>((*values)[index]) > logicalCount - count)
|
||||
return op->emitOpError() << "has invalid RLE " << name, failure();
|
||||
count += (*values)[index];
|
||||
}
|
||||
if (count != logicalCount)
|
||||
return op->emitOpError() << "has mismatched RLE " << name << " count",
|
||||
failure();
|
||||
return StaticIntSequence::runLengthEncoded(*values, count);
|
||||
}
|
||||
if (encoding.getValue() == "dense") {
|
||||
if (values->size() != logicalCount)
|
||||
return op->emitOpError() << "has invalid dense " << name << " count",
|
||||
failure();
|
||||
return StaticIntSequence::fromValues(*values);
|
||||
}
|
||||
return op->emitOpError() << "has unknown " << name << " encoding",
|
||||
failure();
|
||||
}
|
||||
|
||||
Value emitStaticIntLookup(const StaticIntSequence& sequence, Value position,
|
||||
Operation* constantAnchor,
|
||||
ConstantPool& constants, OpBuilder& builder,
|
||||
Location loc) {
|
||||
if (sequence.getKind() == StaticIntSequenceKind::Uniform)
|
||||
return constants.getIndex(sequence.valueAt(0));
|
||||
if (sequence.getKind() == StaticIntSequenceKind::Affine) {
|
||||
Value scaled = affineMulConst(builder, loc, position,
|
||||
sequence.valueAt(1) - sequence.valueAt(0),
|
||||
constantAnchor);
|
||||
return affineAddConst(builder, loc, scaled, sequence.valueAt(0),
|
||||
constantAnchor);
|
||||
}
|
||||
SmallVector<int64_t> values;
|
||||
values.reserve(sequence.size());
|
||||
sequence.forEachEqualRun([&](int64_t value, size_t, size_t count) {
|
||||
values.append(count, value);
|
||||
});
|
||||
auto type = RankedTensorType::get(
|
||||
{static_cast<int64_t>(values.size())}, builder.getI64Type());
|
||||
Value table = constants.get(type,
|
||||
DenseElementsAttr::get(type, ArrayRef<int64_t>(values)));
|
||||
Value selected = tensor::ExtractOp::create(
|
||||
builder, loc, table, ValueRange {position});
|
||||
return arith::IndexCastOp::create(
|
||||
builder, loc, builder.getIndexType(), selected);
|
||||
}
|
||||
|
||||
} // namespace onnx_mlir
|
||||
@@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
|
||||
#include "mlir/IR/Builders.h"
|
||||
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/FunctionExtras.h"
|
||||
#include "llvm/ADT/Hashing.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
class ConstantPool;
|
||||
|
||||
enum class StaticIntSequenceKind {
|
||||
Uniform,
|
||||
Affine,
|
||||
RunLengthEncoded,
|
||||
Dense
|
||||
};
|
||||
|
||||
class StaticIntSequence {
|
||||
public:
|
||||
static StaticIntSequence fromValues(llvm::ArrayRef<int64_t> values);
|
||||
static StaticIntSequence uniform(int64_t value, size_t count);
|
||||
static StaticIntSequence affine(int64_t base, int64_t step, size_t count);
|
||||
|
||||
size_t size() const { return count; }
|
||||
int64_t valueAt(size_t index) const;
|
||||
std::optional<size_t> find(int64_t value, size_t begin, size_t length) const;
|
||||
StaticIntSequence slice(size_t begin, size_t count) const;
|
||||
StaticIntSequence remap(llvm::ArrayRef<unsigned> indices) const;
|
||||
StaticIntSequenceKind getKind() const { return kind; }
|
||||
std::optional<int64_t> getAffineStep() const {
|
||||
if (kind == StaticIntSequenceKind::Uniform)
|
||||
return 0;
|
||||
return kind == StaticIntSequenceKind::Affine
|
||||
? std::optional<int64_t>(step) : std::nullopt;
|
||||
}
|
||||
|
||||
bool operator==(const StaticIntSequence& other) const;
|
||||
llvm::hash_code hash() const;
|
||||
|
||||
void forEachEqualRun(
|
||||
llvm::function_ref<void(int64_t, size_t, size_t)> callback) const;
|
||||
|
||||
private:
|
||||
friend class StaticIntSequenceChain;
|
||||
friend void setStaticIntSequenceAttr(mlir::Operation *, llvm::StringRef,
|
||||
const StaticIntSequence &, size_t);
|
||||
friend mlir::FailureOr<StaticIntSequence>
|
||||
getStaticIntSequenceAttr(mlir::Operation *, llvm::StringRef, size_t);
|
||||
|
||||
static StaticIntSequence runLengthEncoded(
|
||||
llvm::ArrayRef<int64_t> runs, size_t count);
|
||||
StaticIntSequenceKind kind = StaticIntSequenceKind::Dense;
|
||||
size_t count = 0;
|
||||
int64_t base = 0;
|
||||
int64_t step = 0;
|
||||
llvm::SmallVector<int64_t> data;
|
||||
};
|
||||
|
||||
struct StaticIntSequenceSlice {
|
||||
const StaticIntSequence *sequence = nullptr;
|
||||
size_t begin = 0;
|
||||
size_t count = 0;
|
||||
};
|
||||
|
||||
class StaticIntSequenceChain {
|
||||
public:
|
||||
void append(const StaticIntSequence &sequence, size_t begin, size_t count);
|
||||
void append(StaticIntSequence sequence);
|
||||
size_t size() const { return count; }
|
||||
int64_t valueAt(size_t index) const;
|
||||
void forEachSegment(llvm::function_ref<void(
|
||||
const StaticIntSequence &, size_t, size_t)> callback) const;
|
||||
void forEachEqualRun(
|
||||
llvm::function_ref<void(int64_t, size_t, size_t)> callback) const;
|
||||
StaticIntSequence canonicalize() const;
|
||||
|
||||
private:
|
||||
friend class StaticIntSequenceChainCursor;
|
||||
llvm::SmallVector<StaticIntSequenceSlice> slices;
|
||||
llvm::SmallVector<std::unique_ptr<StaticIntSequence>> owned;
|
||||
size_t count = 0;
|
||||
};
|
||||
|
||||
class StaticIntSequenceChainCursor {
|
||||
public:
|
||||
explicit StaticIntSequenceChainCursor(const StaticIntSequenceChain &chain)
|
||||
: chain(chain) {}
|
||||
|
||||
bool done() const { return slice == chain.slices.size(); }
|
||||
int64_t value() const;
|
||||
void advance();
|
||||
|
||||
private:
|
||||
const StaticIntSequenceChain &chain;
|
||||
size_t slice = 0;
|
||||
size_t offset = 0;
|
||||
};
|
||||
|
||||
void setStaticIntSequenceAttr(mlir::Operation *op, llvm::StringRef name,
|
||||
const StaticIntSequence &sequence,
|
||||
size_t logicalCount);
|
||||
|
||||
mlir::FailureOr<StaticIntSequence>
|
||||
getStaticIntSequenceAttr(mlir::Operation *op, llvm::StringRef name,
|
||||
size_t logicalCount);
|
||||
|
||||
mlir::Value emitStaticIntLookup(const StaticIntSequence& sequence,
|
||||
mlir::Value position,
|
||||
mlir::Operation* constantAnchor,
|
||||
ConstantPool& constants,
|
||||
mlir::OpBuilder& builder,
|
||||
mlir::Location loc);
|
||||
|
||||
} // namespace onnx_mlir
|
||||
@@ -22,7 +22,7 @@ Value extractAxisSlice(
|
||||
.getResult();
|
||||
}
|
||||
|
||||
Value extractStaticSliceOrIdentity(RewriterBase& rewriter,
|
||||
Value extractStaticSliceOrIdentity(OpBuilder& rewriter,
|
||||
Location loc,
|
||||
Value source,
|
||||
RankedTensorType resultType,
|
||||
@@ -52,7 +52,8 @@ Value extractStaticSliceOrIdentity(RewriterBase& rewriter,
|
||||
if (isIdentitySlice)
|
||||
return source;
|
||||
|
||||
return tensor::ExtractSliceOp::create(rewriter, loc, resultType, source, offsets, sizes, strides).getResult();
|
||||
return rewriter.createOrFold<tensor::ExtractSliceOp>(
|
||||
loc, resultType, source, offsets, sizes, strides);
|
||||
}
|
||||
|
||||
Value insertStaticSlice(
|
||||
@@ -68,4 +69,65 @@ Value insertStaticSlice(
|
||||
.getResult();
|
||||
}
|
||||
|
||||
Value extractMixedSliceOrIdentity(OpBuilder &rewriter,
|
||||
Location loc,
|
||||
Value source,
|
||||
RankedTensorType resultType,
|
||||
const MixedSliceGeometry &geometry) {
|
||||
return extractStaticSliceOrIdentity(rewriter, loc, source, resultType,
|
||||
geometry.offsets, geometry.sizes,
|
||||
geometry.strides);
|
||||
}
|
||||
|
||||
Value insertMixedSlice(OpBuilder &builder, Location loc, Value source,
|
||||
Value dest, const MixedSliceGeometry &geometry) {
|
||||
SmallVector<OpFoldResult> sizes(geometry.sizes);
|
||||
auto sourceType = dyn_cast<RankedTensorType>(source.getType());
|
||||
auto destType = dyn_cast<RankedTensorType>(dest.getType());
|
||||
if (sourceType && destType && sourceType.hasStaticShape()
|
||||
&& sourceType.getRank() == destType.getRank()) {
|
||||
sizes.clear();
|
||||
for (int64_t dimension : sourceType.getShape())
|
||||
sizes.push_back(builder.getIndexAttr(dimension));
|
||||
}
|
||||
return tensor::InsertSliceOp::create(builder, loc, source, dest,
|
||||
geometry.offsets, sizes,
|
||||
geometry.strides);
|
||||
}
|
||||
|
||||
FailureOr<Value> addLeadingUnitTensorDimension(OpBuilder& builder, Location loc, Value value) {
|
||||
auto type = dyn_cast<RankedTensorType>(value.getType());
|
||||
if (!type || !type.hasStaticShape())
|
||||
return failure();
|
||||
SmallVector<int64_t> shape {1};
|
||||
llvm::append_range(shape, type.getShape());
|
||||
auto resultType = RankedTensorType::get(shape, type.getElementType(), type.getEncoding());
|
||||
SmallVector<ReassociationIndices> reassociation;
|
||||
if (type.getRank() != 0) {
|
||||
reassociation.push_back({0, 1});
|
||||
for (int64_t dim = 1; dim < type.getRank(); ++dim)
|
||||
reassociation.push_back({dim + 1});
|
||||
}
|
||||
return tensor::ExpandShapeOp::create(builder, loc, resultType, value, reassociation).getResult();
|
||||
}
|
||||
|
||||
FailureOr<Value> removeLeadingUnitTensorDimension(
|
||||
OpBuilder& builder, Location loc, Value value, RankedTensorType resultType) {
|
||||
if (value.getType() == resultType)
|
||||
return value;
|
||||
auto type = dyn_cast<RankedTensorType>(value.getType());
|
||||
if (!type || !resultType || !type.hasStaticShape() || !resultType.hasStaticShape()
|
||||
|| type.getRank() != resultType.getRank() + 1 || type.getDimSize(0) != 1
|
||||
|| type.getElementType() != resultType.getElementType()
|
||||
|| !llvm::equal(type.getShape().drop_front(), resultType.getShape()))
|
||||
return failure();
|
||||
SmallVector<ReassociationIndices> reassociation;
|
||||
if (resultType.getRank() != 0) {
|
||||
reassociation.push_back({0, 1});
|
||||
for (int64_t dim = 1; dim < resultType.getRank(); ++dim)
|
||||
reassociation.push_back({dim + 1});
|
||||
}
|
||||
return tensor::CollapseShapeOp::create(builder, loc, resultType, value, reassociation).getResult();
|
||||
}
|
||||
|
||||
} // namespace onnx_mlir
|
||||
|
||||
@@ -8,10 +8,16 @@
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
struct MixedSliceGeometry {
|
||||
llvm::SmallVector<mlir::OpFoldResult> offsets;
|
||||
llvm::SmallVector<mlir::OpFoldResult> sizes;
|
||||
llvm::SmallVector<mlir::OpFoldResult> strides;
|
||||
};
|
||||
|
||||
mlir::Value extractAxisSlice(
|
||||
mlir::PatternRewriter& rewriter, mlir::Location loc, mlir::Value source, int64_t axis, int64_t offset, int64_t size);
|
||||
|
||||
mlir::Value extractStaticSliceOrIdentity(mlir::RewriterBase& rewriter,
|
||||
mlir::Value extractStaticSliceOrIdentity(mlir::OpBuilder& rewriter,
|
||||
mlir::Location loc,
|
||||
mlir::Value source,
|
||||
mlir::RankedTensorType resultType,
|
||||
@@ -25,4 +31,22 @@ mlir::Value insertStaticSlice(mlir::PatternRewriter& rewriter,
|
||||
mlir::Value dest,
|
||||
llvm::ArrayRef<mlir::OpFoldResult> offsets);
|
||||
|
||||
mlir::Value extractMixedSliceOrIdentity(mlir::OpBuilder &rewriter,
|
||||
mlir::Location loc,
|
||||
mlir::Value source,
|
||||
mlir::RankedTensorType resultType,
|
||||
const MixedSliceGeometry &geometry);
|
||||
|
||||
mlir::Value insertMixedSlice(mlir::OpBuilder &builder,
|
||||
mlir::Location loc,
|
||||
mlir::Value source,
|
||||
mlir::Value dest,
|
||||
const MixedSliceGeometry &geometry);
|
||||
|
||||
mlir::FailureOr<mlir::Value>
|
||||
addLeadingUnitTensorDimension(mlir::OpBuilder& builder, mlir::Location loc, mlir::Value value);
|
||||
|
||||
mlir::FailureOr<mlir::Value> removeLeadingUnitTensorDimension(
|
||||
mlir::OpBuilder& builder, mlir::Location loc, mlir::Value value, mlir::RankedTensorType resultType);
|
||||
|
||||
} // namespace onnx_mlir
|
||||
|
||||
@@ -146,17 +146,17 @@ void walkPimMvmVmmWeightUses(mlir::Operation* root, llvm::function_ref<void(mlir
|
||||
});
|
||||
}
|
||||
|
||||
std::optional<unsigned> resolveWeightIndex(mlir::Operation* weightOwner, mlir::Value weight) {
|
||||
std::optional<unsigned> resolveWeightIndex(mlir::Operation* coreLikeOp, mlir::Value weight) {
|
||||
weight = stripMemRefAddressingOps(weight);
|
||||
|
||||
if (auto coreOp = mlir::dyn_cast_or_null<pim::PimCoreOp>(weightOwner)) {
|
||||
if (auto coreOp = mlir::dyn_cast_or_null<pim::PimCoreOp>(coreLikeOp)) {
|
||||
for (unsigned weightIndex = 0; weightIndex < coreOp.getWeights().size(); ++weightIndex)
|
||||
if (coreOp.getWeightArgument(weightIndex) == weight)
|
||||
return weightIndex;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (auto coreBatchOp = mlir::dyn_cast_or_null<pim::PimCoreBatchOp>(weightOwner)) {
|
||||
if (auto coreBatchOp = mlir::dyn_cast_or_null<pim::PimCoreBatchOp>(coreLikeOp)) {
|
||||
for (unsigned weightIndex = 0; weightIndex < coreBatchOp.getWeights().size(); ++weightIndex)
|
||||
if (coreBatchOp.getWeightArgument(weightIndex) == weight)
|
||||
return weightIndex;
|
||||
@@ -167,7 +167,7 @@ std::optional<unsigned> resolveWeightIndex(mlir::Operation* weightOwner, mlir::V
|
||||
}
|
||||
|
||||
llvm::FailureOr<ResolvedWeightView>
|
||||
resolveWeightView(mlir::Operation* weightOwner, mlir::Value weight, const StaticValueKnowledge& knowledge) {
|
||||
resolveWeightView(mlir::Operation* coreLikeOp, mlir::Value weight, const StaticValueKnowledge& knowledge) {
|
||||
llvm::SmallVector<mlir::Operation*> viewOps;
|
||||
mlir::Value current = weight;
|
||||
|
||||
@@ -179,7 +179,7 @@ resolveWeightView(mlir::Operation* weightOwner, mlir::Value weight, const Static
|
||||
|
||||
if (auto defOp = current.getDefiningOp()) {
|
||||
if (auto getGlobalOp = mlir::dyn_cast<mlir::memref::GetGlobalOp>(defOp)) {
|
||||
auto moduleOp = weightOwner ? weightOwner->getParentOfType<mlir::ModuleOp>() : mlir::ModuleOp {};
|
||||
auto moduleOp = coreLikeOp ? coreLikeOp->getParentOfType<mlir::ModuleOp>() : mlir::ModuleOp {};
|
||||
auto globalOp = lookupGlobalForGetGlobal(moduleOp, getGlobalOp);
|
||||
if (!globalOp || !globalOp.getInitialValue())
|
||||
return mlir::failure();
|
||||
@@ -297,15 +297,15 @@ resolveWeightView(mlir::Operation* weightOwner, mlir::Value weight, const Static
|
||||
continue;
|
||||
}
|
||||
|
||||
auto weightIndex = resolveWeightIndex(weightOwner, current);
|
||||
auto weightIndex = resolveWeightIndex(coreLikeOp, current);
|
||||
if (!weightIndex)
|
||||
return mlir::failure();
|
||||
|
||||
if (auto coreOp = mlir::dyn_cast_or_null<pim::PimCoreOp>(weightOwner)) {
|
||||
if (auto coreOp = mlir::dyn_cast_or_null<pim::PimCoreOp>(coreLikeOp)) {
|
||||
current = coreOp.getWeights()[*weightIndex];
|
||||
continue;
|
||||
}
|
||||
if (auto coreBatchOp = mlir::dyn_cast_or_null<pim::PimCoreBatchOp>(weightOwner)) {
|
||||
if (auto coreBatchOp = mlir::dyn_cast_or_null<pim::PimCoreBatchOp>(coreLikeOp)) {
|
||||
current = coreBatchOp.getWeights()[*weightIndex];
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -45,9 +45,9 @@ bool hasOnlySpatialMvmVmmWeightUses(mlir::Value value);
|
||||
/// passes can identify globals that must remain weight-backed.
|
||||
void walkPimMvmVmmWeightUses(mlir::Operation* root, llvm::function_ref<void(mlir::OpOperand&)> callback);
|
||||
|
||||
std::optional<unsigned> resolveWeightIndex(mlir::Operation* weightOwner, mlir::Value weight);
|
||||
std::optional<unsigned> resolveWeightIndex(mlir::Operation* coreLikeOp, mlir::Value weight);
|
||||
llvm::FailureOr<ResolvedWeightView>
|
||||
resolveWeightView(mlir::Operation* weightOwner, mlir::Value weight, const StaticValueKnowledge& knowledge = {});
|
||||
resolveWeightView(mlir::Operation* coreLikeOp, mlir::Value weight, const StaticValueKnowledge& knowledge = {});
|
||||
|
||||
template <typename CoreLikeOpTy>
|
||||
llvm::SmallVector<unsigned, 8> getUsedWeightIndices(CoreLikeOpTy coreLikeOp) {
|
||||
|
||||
@@ -22,9 +22,19 @@
|
||||
#include "src/Accelerators/PIM/Common/Support/FileSystemUtils.hpp"
|
||||
#include "src/Compiler/CompilerOptions.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
inline constexpr llvm::StringLiteral kCoreIdAttrName = "coreId";
|
||||
inline constexpr llvm::StringLiteral kCoreIdsAttrName = "coreIds";
|
||||
inline constexpr llvm::StringLiteral kLocalMemoryAddressAttrName = "pim.local_memory_address";
|
||||
inline constexpr llvm::StringLiteral 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
|
||||
|
||||
@@ -17,14 +17,16 @@ std::fstream openDialectDumpFileWithExtension(const std::string& name, llvm::Str
|
||||
return std::fstream(dialectsDir + "/" + name + "." + extension.str(), std::ios::out);
|
||||
}
|
||||
|
||||
void dumpModule(mlir::ModuleOp moduleOp, const std::string& name) {
|
||||
void dumpModule(mlir::ModuleOp moduleOp, const std::string& name, bool assumeVerified) {
|
||||
std::fstream file = openDialectDumpFileWithExtension(name, "/dialects", "mlir");
|
||||
if (!file.is_open())
|
||||
return;
|
||||
|
||||
llvm::raw_os_ostream os(file);
|
||||
mlir::OpPrintingFlags flags;
|
||||
flags.elideLargeElementsAttrs().enableDebugInfo(true, false);
|
||||
flags.elideLargeElementsAttrs().enableDebugInfo(false, false);
|
||||
if (assumeVerified)
|
||||
flags.assumeVerified();
|
||||
moduleOp.print(os, flags);
|
||||
os.flush();
|
||||
file.close();
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace onnx_mlir {
|
||||
|
||||
/// Emits a MLIR snapshot under the current compiler output
|
||||
/// directory for pass-level debugging.
|
||||
void dumpModule(mlir::ModuleOp moduleOp, const std::string& name);
|
||||
void dumpModule(mlir::ModuleOp moduleOp, const std::string& name, bool assumeVerified = false);
|
||||
|
||||
/// Opens a file under the same dialect dump directory used by dumpModule.
|
||||
std::fstream openDialectDumpFileWithExtension(const std::string& name,llvm::StringRef destination = "/dialects", llvm::StringRef extension = "mlir");
|
||||
|
||||
@@ -17,7 +17,7 @@ add_pim_library(OMPimCompilerUtils
|
||||
PimCompilerUtils.cpp
|
||||
PimArtifactWriter.cpp
|
||||
PimCodeGen.cpp
|
||||
PimMemoryLiveness.cpp
|
||||
PimCoreProgram.cpp
|
||||
PimWeightEmitter.cpp
|
||||
|
||||
EXCLUDE_FROM_OM_LIBS
|
||||
@@ -26,11 +26,12 @@ add_pim_library(OMPimCompilerUtils
|
||||
${PIM_COMPILER_INCLUDE_DIRS}
|
||||
|
||||
LINK_LIBS PUBLIC
|
||||
MLIRAffineToStandard
|
||||
OMPimCompilerOptions
|
||||
OMPimCommon
|
||||
OMPimBufferization
|
||||
OMPimMemoryCoalescing
|
||||
OMPimHostConstantFolding
|
||||
OMPimLocalMemoryPlanning
|
||||
OMPimVerification
|
||||
OMPimPasses
|
||||
OMONNXToSpatial
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <limits>
|
||||
|
||||
#include "src/Accelerators/PIM/Common/IR/WeightUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Compiler/PimArtifactWriter.hpp"
|
||||
@@ -20,6 +20,65 @@ using namespace mlir;
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
PimInstructionWriter::PimInstructionWriter(raw_pwrite_stream& stream)
|
||||
: stream(stream), buffer(kBufferSize) {
|
||||
pim_binary::writeHeader(stream);
|
||||
}
|
||||
|
||||
PimInstructionWriter::PimInstructionWriter(raw_fd_ostream& stream)
|
||||
: PimInstructionWriter(static_cast<raw_pwrite_stream&>(stream)) {
|
||||
fileStream = &stream;
|
||||
}
|
||||
|
||||
bool PimInstructionWriter::consumeStreamError() {
|
||||
if (!fileStream || !fileStream->has_error())
|
||||
return false;
|
||||
fileStream->clear_error();
|
||||
hasFailure = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
LogicalResult PimInstructionWriter::flushBuffer() {
|
||||
if (hasFailure)
|
||||
return failure();
|
||||
if (bufferedBytes != 0) {
|
||||
stream.write(buffer.data(), bufferedBytes);
|
||||
bufferedBytes = 0;
|
||||
}
|
||||
return consumeStreamError() ? failure() : success();
|
||||
}
|
||||
|
||||
LogicalResult PimInstructionWriter::append(const pim_binary::InstructionRecord& record) {
|
||||
if (hasFailure || finalized || count == std::numeric_limits<uint32_t>::max()) {
|
||||
hasFailure = true;
|
||||
return failure();
|
||||
}
|
||||
|
||||
if (bufferedBytes + pim_binary::kRecordSize > buffer.size() && failed(flushBuffer()))
|
||||
return failure();
|
||||
|
||||
pim_binary::EncodedInstruction encoded = pim_binary::encodeInstructionRecord(record);
|
||||
std::memcpy(buffer.data() + bufferedBytes, encoded.data(), encoded.size());
|
||||
bufferedBytes += encoded.size();
|
||||
++count;
|
||||
return success();
|
||||
}
|
||||
|
||||
LogicalResult PimInstructionWriter::finalize() {
|
||||
if (finalized)
|
||||
return failure();
|
||||
finalized = true;
|
||||
if (hasFailure || failed(flushBuffer()))
|
||||
return failure();
|
||||
|
||||
stream.flush();
|
||||
if (consumeStreamError())
|
||||
return failure();
|
||||
pim_binary::patchInstructionCount(stream, count);
|
||||
stream.flush();
|
||||
return consumeStreamError() ? failure() : success();
|
||||
}
|
||||
|
||||
OnnxMlirCompilerErrorCodes
|
||||
writeMemoryBinary(ModuleOp moduleOp, func::FuncOp funcOp, PimAcceleratorMemory& memory, StringRef outputDirPath) {
|
||||
auto memoryFilePath = (outputDirPath + "/memory.bin").str();
|
||||
|
||||
@@ -2,16 +2,45 @@
|
||||
|
||||
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
||||
#include "mlir/IR/BuiltinOps.h"
|
||||
#include "mlir/Support/LogicalResult.h"
|
||||
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/Support/JSON.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "onnx-mlir/Compiler/OMCompilerTypes.h"
|
||||
#include "src/Accelerators/PIM/Compiler/PimBinaryFormat.hpp"
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
class PimAcceleratorMemory;
|
||||
|
||||
class PimInstructionWriter {
|
||||
public:
|
||||
explicit PimInstructionWriter(llvm::raw_pwrite_stream& stream);
|
||||
explicit PimInstructionWriter(llvm::raw_fd_ostream& stream);
|
||||
|
||||
mlir::LogicalResult append(const pim_binary::InstructionRecord& record);
|
||||
mlir::LogicalResult finalize();
|
||||
|
||||
private:
|
||||
static constexpr size_t kBufferSize = 256 * 1024;
|
||||
mlir::LogicalResult flushBuffer();
|
||||
bool consumeStreamError();
|
||||
|
||||
llvm::raw_pwrite_stream& stream;
|
||||
llvm::raw_fd_ostream* fileStream = nullptr;
|
||||
std::vector<char> buffer;
|
||||
size_t bufferedBytes = 0;
|
||||
uint32_t count = 0;
|
||||
bool finalized = false;
|
||||
bool hasFailure = false;
|
||||
};
|
||||
|
||||
OnnxMlirCompilerErrorCodes writeMemoryBinary(mlir::ModuleOp moduleOp,
|
||||
mlir::func::FuncOp funcOp,
|
||||
PimAcceleratorMemory& memory,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/Support/Endian.h"
|
||||
#include "llvm/Support/JSON.h"
|
||||
@@ -53,6 +54,14 @@ enum class Opcode : uint32_t {
|
||||
sync = 32,
|
||||
};
|
||||
|
||||
inline constexpr size_t kOpcodeCount = static_cast<size_t>(Opcode::sync) + 1;
|
||||
inline constexpr std::array<llvm::StringLiteral, kOpcodeCount> kOpcodeNames = {
|
||||
"nop", "sldi", "sld", "sadd", "ssub", "smul", "saddi", "smuli", "setbw", "mvmul", "vvadd",
|
||||
"vvsub", "vvmul", "vvdmul", "vvmax", "vvsll", "vvsra", "vavg", "vrelu", "vtanh", "vsigm", "vsoftmax",
|
||||
"vmv", "vrsu", "vrsl", "ld", "st", "lldi", "lmv", "send", "recv", "wait", "sync",
|
||||
};
|
||||
static_assert(kOpcodeNames.size() == kOpcodeCount);
|
||||
|
||||
struct InstructionRecord {
|
||||
Opcode opcode = Opcode::nop;
|
||||
uint8_t rd = 0;
|
||||
@@ -64,14 +73,29 @@ struct InstructionRecord {
|
||||
uint8_t flags = 0;
|
||||
};
|
||||
|
||||
using EncodedInstruction = std::array<char, kRecordSize>;
|
||||
static_assert(kRecordSize == 20);
|
||||
static_assert(sizeof(EncodedInstruction) == kRecordSize);
|
||||
|
||||
inline EncodedInstruction encodeInstructionRecord(const InstructionRecord& record) {
|
||||
EncodedInstruction encoded = {};
|
||||
encoded[0] = static_cast<char>(static_cast<uint8_t>(record.opcode));
|
||||
encoded[1] = static_cast<char>(record.rd);
|
||||
encoded[2] = static_cast<char>(record.r1);
|
||||
encoded[3] = static_cast<char>(record.flags);
|
||||
llvm::support::endian::write32le(encoded.data() + 4, static_cast<uint32_t>(record.r2OrImm));
|
||||
llvm::support::endian::write32le(encoded.data() + 8, static_cast<uint32_t>(record.generic1));
|
||||
llvm::support::endian::write32le(encoded.data() + 12, static_cast<uint32_t>(record.generic2));
|
||||
llvm::support::endian::write32le(encoded.data() + 16, static_cast<uint32_t>(record.generic3));
|
||||
return encoded;
|
||||
}
|
||||
|
||||
inline void writeUint32LE(llvm::raw_ostream& os, uint32_t value) {
|
||||
std::array<char, sizeof(uint32_t)> bytes;
|
||||
llvm::support::endian::write32le(bytes.data(), value);
|
||||
os.write(bytes.data(), bytes.size());
|
||||
}
|
||||
|
||||
inline void writeInt32LE(llvm::raw_ostream& os, int32_t value) { writeUint32LE(os, static_cast<uint32_t>(value)); }
|
||||
|
||||
inline void writeHeader(llvm::raw_ostream& os) {
|
||||
os.write(kMagic, sizeof(kMagic));
|
||||
writeUint32LE(os, kVersion);
|
||||
@@ -84,17 +108,6 @@ inline void patchInstructionCount(llvm::raw_pwrite_stream& os, uint32_t instruct
|
||||
os.pwrite(bytes.data(), bytes.size(), kCountOffset);
|
||||
}
|
||||
|
||||
inline void writeInstructionRecord(llvm::raw_ostream& os, const InstructionRecord& record) {
|
||||
os << static_cast<char>(static_cast<uint8_t>(record.opcode));
|
||||
os << static_cast<char>(record.rd);
|
||||
os << static_cast<char>(record.r1);
|
||||
os << static_cast<char>(record.flags);
|
||||
writeInt32LE(os, record.r2OrImm);
|
||||
writeInt32LE(os, record.generic1);
|
||||
writeInt32LE(os, record.generic2);
|
||||
writeInt32LE(os, record.generic3);
|
||||
}
|
||||
|
||||
inline int32_t toI32(int64_t value) { return onnx_mlir::pim::checkedI32OrCrash(value, "binary field"); }
|
||||
|
||||
inline uint8_t toU8(int64_t value) {
|
||||
@@ -107,113 +120,64 @@ inline int32_t getOptionalInt(const llvm::json::Object& object, llvm::StringRef
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
struct InstructionJsonFormat {
|
||||
bool rd;
|
||||
bool r1;
|
||||
bool offset;
|
||||
llvm::StringLiteral r2;
|
||||
llvm::StringLiteral generic1;
|
||||
llvm::StringLiteral generic2;
|
||||
llvm::StringLiteral generic3;
|
||||
};
|
||||
|
||||
inline constexpr std::array<InstructionJsonFormat, kOpcodeCount> kInstructionJsonFormats = {{
|
||||
{false, false, false, "", "", "", "" }, // nop
|
||||
{true, false, false, "imm", "", "", "" }, // sldi
|
||||
{true, true, true, "", "", "", "" }, // sld
|
||||
{true, true, false, "rs2", "", "", "" }, // sadd
|
||||
{true, true, false, "rs2", "", "", "" }, // ssub
|
||||
{true, true, false, "rs2", "", "", "" }, // smul
|
||||
{true, true, false, "imm", "", "", "" }, // saddi
|
||||
{true, true, false, "imm", "", "", "" }, // smuli
|
||||
{false, false, false, "", "ibiw", "obiw", "" }, // setbw
|
||||
{true, true, false, "mbiw", "relu", "group", "" }, // mvmul
|
||||
{true, true, true, "rs2", "", "", "len" }, // vvadd
|
||||
{true, true, true, "rs2", "", "", "len" }, // vvsub
|
||||
{true, true, true, "rs2", "", "", "len" }, // vvmul
|
||||
{true, true, true, "rs2", "", "", "len" }, // vvdmul
|
||||
{true, true, true, "rs2", "", "", "len" }, // vvmax
|
||||
{true, true, true, "rs2", "", "", "len" }, // vvsll
|
||||
{true, true, true, "rs2", "", "", "len" }, // vvsra
|
||||
{true, true, true, "rs2", "", "", "len" }, // vavg
|
||||
{true, true, true, "", "", "", "len" }, // vrelu
|
||||
{true, true, true, "", "", "", "len" }, // vtanh
|
||||
{true, true, true, "", "", "", "len" }, // vsigm
|
||||
{true, true, true, "", "", "", "len" }, // vsoftmax
|
||||
{true, true, true, "rs2", "", "", "len" }, // vmv
|
||||
{true, true, true, "rs2", "", "", "len" }, // vrsu
|
||||
{true, true, true, "rs2", "", "", "len" }, // vrsl
|
||||
{true, true, true, "", "", "", "size"}, // ld
|
||||
{true, true, true, "", "", "", "size"}, // st
|
||||
{true, false, true, "imm", "", "", "len" }, // lldi
|
||||
{true, true, true, "", "", "", "len" }, // lmv
|
||||
{true, false, true, "core", "", "", "size"}, // send
|
||||
{true, false, true, "core", "", "", "size"}, // recv
|
||||
{false, false, false, "", "", "", "" }, // wait
|
||||
{false, false, false, "", "", "", "" }, // sync
|
||||
}};
|
||||
static_assert(kInstructionJsonFormats.size() == kOpcodeCount);
|
||||
|
||||
inline Opcode opcodeFromString(llvm::StringRef opName) {
|
||||
if (opName == "nop")
|
||||
return Opcode::nop;
|
||||
if (opName == "sldi")
|
||||
return Opcode::sldi;
|
||||
if (opName == "sld")
|
||||
return Opcode::sld;
|
||||
if (opName == "sadd")
|
||||
return Opcode::sadd;
|
||||
if (opName == "ssub")
|
||||
return Opcode::ssub;
|
||||
if (opName == "smul")
|
||||
return Opcode::smul;
|
||||
if (opName == "saddi")
|
||||
return Opcode::saddi;
|
||||
if (opName == "smuli")
|
||||
return Opcode::smuli;
|
||||
if (opName == "setbw")
|
||||
return Opcode::setbw;
|
||||
if (opName == "mvmul")
|
||||
return Opcode::mvmul;
|
||||
if (opName == "vvadd")
|
||||
return Opcode::vvadd;
|
||||
if (opName == "vvsub")
|
||||
return Opcode::vvsub;
|
||||
if (opName == "vvmul")
|
||||
return Opcode::vvmul;
|
||||
if (opName == "vvdmul")
|
||||
return Opcode::vvdmul;
|
||||
if (opName == "vvmax")
|
||||
return Opcode::vvmax;
|
||||
if (opName == "vvsll")
|
||||
return Opcode::vvsll;
|
||||
if (opName == "vvsra")
|
||||
return Opcode::vvsra;
|
||||
if (opName == "vavg")
|
||||
return Opcode::vavg;
|
||||
if (opName == "vrelu")
|
||||
return Opcode::vrelu;
|
||||
if (opName == "vtanh")
|
||||
return Opcode::vtanh;
|
||||
if (opName == "vsigm")
|
||||
return Opcode::vsigm;
|
||||
if (opName == "vsoftmax")
|
||||
return Opcode::vsoftmax;
|
||||
if (opName == "vmv")
|
||||
return Opcode::vmv;
|
||||
if (opName == "vrsu")
|
||||
return Opcode::vrsu;
|
||||
if (opName == "vrsl")
|
||||
return Opcode::vrsl;
|
||||
if (opName == "ld")
|
||||
return Opcode::ld;
|
||||
if (opName == "st")
|
||||
return Opcode::st;
|
||||
if (opName == "lldi")
|
||||
return Opcode::lldi;
|
||||
if (opName == "lmv")
|
||||
return Opcode::lmv;
|
||||
if (opName == "send")
|
||||
return Opcode::send;
|
||||
if (opName == "recv")
|
||||
return Opcode::recv;
|
||||
if (opName == "wait")
|
||||
return Opcode::wait;
|
||||
if (opName == "sync")
|
||||
return Opcode::sync;
|
||||
for (auto [index, name] : llvm::enumerate(kOpcodeNames))
|
||||
if (opName == name)
|
||||
return static_cast<Opcode>(index);
|
||||
llvm_unreachable("Unsupported PIM binary opcode");
|
||||
}
|
||||
|
||||
inline llvm::StringRef opcodeToString(Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Opcode::nop: return "nop";
|
||||
case Opcode::sldi: return "sldi";
|
||||
case Opcode::sld: return "sld";
|
||||
case Opcode::sadd: return "sadd";
|
||||
case Opcode::ssub: return "ssub";
|
||||
case Opcode::smul: return "smul";
|
||||
case Opcode::saddi: return "saddi";
|
||||
case Opcode::smuli: return "smuli";
|
||||
case Opcode::setbw: return "setbw";
|
||||
case Opcode::mvmul: return "mvmul";
|
||||
case Opcode::vvadd: return "vvadd";
|
||||
case Opcode::vvsub: return "vvsub";
|
||||
case Opcode::vvmul: return "vvmul";
|
||||
case Opcode::vvdmul: return "vvdmul";
|
||||
case Opcode::vvmax: return "vvmax";
|
||||
case Opcode::vvsll: return "vvsll";
|
||||
case Opcode::vvsra: return "vvsra";
|
||||
case Opcode::vavg: return "vavg";
|
||||
case Opcode::vrelu: return "vrelu";
|
||||
case Opcode::vtanh: return "vtanh";
|
||||
case Opcode::vsigm: return "vsigm";
|
||||
case Opcode::vsoftmax: return "vsoftmax";
|
||||
case Opcode::vmv: return "vmv";
|
||||
case Opcode::vrsu: return "vrsu";
|
||||
case Opcode::vrsl: return "vrsl";
|
||||
case Opcode::ld: return "ld";
|
||||
case Opcode::st: return "st";
|
||||
case Opcode::lldi: return "lldi";
|
||||
case Opcode::lmv: return "lmv";
|
||||
case Opcode::send: return "send";
|
||||
case Opcode::recv: return "recv";
|
||||
case Opcode::wait: return "wait";
|
||||
case Opcode::sync: return "sync";
|
||||
}
|
||||
llvm_unreachable("Unsupported PIM binary opcode");
|
||||
size_t index = static_cast<size_t>(opcode);
|
||||
assert(index < kOpcodeNames.size() && "Unsupported PIM binary opcode");
|
||||
return kOpcodeNames[index];
|
||||
}
|
||||
|
||||
inline InstructionRecord makeInstructionRecord(const llvm::json::Object& instruction) {
|
||||
@@ -221,43 +185,25 @@ inline InstructionRecord makeInstructionRecord(const llvm::json::Object& instruc
|
||||
std::optional<llvm::StringRef> opName = instruction.getString("op");
|
||||
assert(opName && "Missing op field in PIM instruction");
|
||||
record.opcode = opcodeFromString(*opName);
|
||||
const auto& format = kInstructionJsonFormats[static_cast<size_t>(record.opcode)];
|
||||
if (format.rd)
|
||||
record.rd = toU8(getOptionalInt(instruction, "rd"));
|
||||
if (format.r1)
|
||||
record.r1 = toU8(getOptionalInt(instruction, "rs1"));
|
||||
|
||||
switch (record.opcode) {
|
||||
case Opcode::sldi:
|
||||
case Opcode::saddi:
|
||||
case Opcode::smuli:
|
||||
case Opcode::lldi: record.r2OrImm = getOptionalInt(instruction, "imm"); break;
|
||||
case Opcode::mvmul:
|
||||
record.r2OrImm = getOptionalInt(instruction, "mbiw");
|
||||
record.generic1 = getOptionalInt(instruction, "relu");
|
||||
record.generic2 = getOptionalInt(instruction, "group");
|
||||
break;
|
||||
case Opcode::setbw:
|
||||
record.generic1 = getOptionalInt(instruction, "ibiw");
|
||||
record.generic2 = getOptionalInt(instruction, "obiw");
|
||||
break;
|
||||
case Opcode::send:
|
||||
case Opcode::recv:
|
||||
record.r2OrImm = getOptionalInt(instruction, "core");
|
||||
record.generic3 = getOptionalInt(instruction, "size");
|
||||
break;
|
||||
default: record.r2OrImm = getOptionalInt(instruction, "rs2"); break;
|
||||
}
|
||||
|
||||
if (record.opcode != Opcode::mvmul && record.opcode != Opcode::setbw) {
|
||||
if (!format.r2.empty())
|
||||
record.r2OrImm = getOptionalInt(instruction, format.r2);
|
||||
if (!format.generic1.empty())
|
||||
record.generic1 = getOptionalInt(instruction, format.generic1);
|
||||
if (!format.generic2.empty())
|
||||
record.generic2 = getOptionalInt(instruction, format.generic2);
|
||||
if (format.offset) {
|
||||
if (auto* offsetValue = instruction.getObject("offset")) {
|
||||
record.generic1 = getOptionalInt(*offsetValue, "offset_select");
|
||||
record.generic2 = getOptionalInt(*offsetValue, "offset_value");
|
||||
}
|
||||
}
|
||||
|
||||
if (instruction.get("len"))
|
||||
record.generic3 = getOptionalInt(instruction, "len");
|
||||
else if (instruction.get("size") && record.opcode != Opcode::send && record.opcode != Opcode::recv)
|
||||
record.generic3 = getOptionalInt(instruction, "size");
|
||||
|
||||
if (!format.generic3.empty())
|
||||
record.generic3 = getOptionalInt(instruction, format.generic3);
|
||||
return record;
|
||||
}
|
||||
|
||||
@@ -271,98 +217,21 @@ inline llvm::json::Object makeInstructionJson(const InstructionRecord& record) {
|
||||
offset["offset_value"] = offsetValue;
|
||||
instruction["offset"] = std::move(offset);
|
||||
};
|
||||
|
||||
switch (record.opcode) {
|
||||
case Opcode::sldi:
|
||||
instruction["rd"] = static_cast<int64_t>(record.rd);
|
||||
instruction["imm"] = record.r2OrImm;
|
||||
break;
|
||||
case Opcode::sld:
|
||||
const auto& format = kInstructionJsonFormats[static_cast<size_t>(record.opcode)];
|
||||
if (format.rd)
|
||||
instruction["rd"] = static_cast<int64_t>(record.rd);
|
||||
if (format.r1)
|
||||
instruction["rs1"] = static_cast<int64_t>(record.r1);
|
||||
if (!format.r2.empty())
|
||||
instruction[format.r2] = record.r2OrImm;
|
||||
if (!format.generic1.empty())
|
||||
instruction[format.generic1] = record.generic1;
|
||||
if (!format.generic2.empty())
|
||||
instruction[format.generic2] = record.generic2;
|
||||
if (format.offset)
|
||||
addOffset(record.generic1, record.generic2);
|
||||
break;
|
||||
case Opcode::sadd:
|
||||
case Opcode::ssub:
|
||||
case Opcode::smul:
|
||||
instruction["rd"] = static_cast<int64_t>(record.rd);
|
||||
instruction["rs1"] = static_cast<int64_t>(record.r1);
|
||||
instruction["rs2"] = record.r2OrImm;
|
||||
break;
|
||||
case Opcode::saddi:
|
||||
case Opcode::smuli:
|
||||
instruction["rd"] = static_cast<int64_t>(record.rd);
|
||||
instruction["rs1"] = static_cast<int64_t>(record.r1);
|
||||
instruction["imm"] = record.r2OrImm;
|
||||
break;
|
||||
case Opcode::setbw:
|
||||
instruction["ibiw"] = record.generic1;
|
||||
instruction["obiw"] = record.generic2;
|
||||
break;
|
||||
case Opcode::mvmul:
|
||||
instruction["rd"] = static_cast<int64_t>(record.rd);
|
||||
instruction["rs1"] = static_cast<int64_t>(record.r1);
|
||||
instruction["mbiw"] = record.r2OrImm;
|
||||
instruction["relu"] = record.generic1;
|
||||
instruction["group"] = record.generic2;
|
||||
break;
|
||||
case Opcode::vvadd:
|
||||
case Opcode::vvsub:
|
||||
case Opcode::vvmul:
|
||||
case Opcode::vvdmul:
|
||||
case Opcode::vvmax:
|
||||
case Opcode::vvsll:
|
||||
case Opcode::vvsra:
|
||||
case Opcode::vavg:
|
||||
case Opcode::vmv:
|
||||
case Opcode::vrsu:
|
||||
case Opcode::vrsl:
|
||||
instruction["rd"] = static_cast<int64_t>(record.rd);
|
||||
instruction["rs1"] = static_cast<int64_t>(record.r1);
|
||||
instruction["rs2"] = record.r2OrImm;
|
||||
addOffset(record.generic1, record.generic2);
|
||||
instruction["len"] = record.generic3;
|
||||
break;
|
||||
case Opcode::vrelu:
|
||||
case Opcode::vtanh:
|
||||
case Opcode::vsigm:
|
||||
case Opcode::vsoftmax:
|
||||
instruction["rd"] = static_cast<int64_t>(record.rd);
|
||||
instruction["rs1"] = static_cast<int64_t>(record.r1);
|
||||
addOffset(record.generic1, record.generic2);
|
||||
instruction["len"] = record.generic3;
|
||||
break;
|
||||
case Opcode::ld:
|
||||
case Opcode::st:
|
||||
instruction["rd"] = static_cast<int64_t>(record.rd);
|
||||
instruction["rs1"] = static_cast<int64_t>(record.r1);
|
||||
addOffset(record.generic1, record.generic2);
|
||||
instruction["size"] = record.generic3;
|
||||
break;
|
||||
case Opcode::lldi:
|
||||
instruction["rd"] = static_cast<int64_t>(record.rd);
|
||||
instruction["imm"] = record.r2OrImm;
|
||||
addOffset(record.generic1, record.generic2);
|
||||
instruction["len"] = record.generic3;
|
||||
break;
|
||||
case Opcode::lmv:
|
||||
instruction["rd"] = static_cast<int64_t>(record.rd);
|
||||
instruction["rs1"] = static_cast<int64_t>(record.r1);
|
||||
addOffset(record.generic1, record.generic2);
|
||||
instruction["len"] = record.generic3;
|
||||
break;
|
||||
case Opcode::send:
|
||||
case Opcode::recv:
|
||||
instruction["rd"] = static_cast<int64_t>(record.rd);
|
||||
instruction["core"] = record.r2OrImm;
|
||||
addOffset(record.generic1, record.generic2);
|
||||
instruction["size"] = record.generic3;
|
||||
break;
|
||||
case Opcode::wait:
|
||||
case Opcode::sync:
|
||||
case Opcode::nop: break;
|
||||
}
|
||||
|
||||
if (!format.generic3.empty())
|
||||
instruction[format.generic3] = record.generic3;
|
||||
return instruction;
|
||||
}
|
||||
|
||||
|
||||
+414
-609
File diff suppressed because it is too large
Load Diff
@@ -24,6 +24,10 @@
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
struct CompiledCoreProgram;
|
||||
struct CompiledTransposePlan;
|
||||
class PimInstructionWriter;
|
||||
|
||||
struct MemEntry {
|
||||
size_t address;
|
||||
size_t size;
|
||||
@@ -35,10 +39,6 @@ struct PhysicalSlotInfo {
|
||||
size_t size = 0;
|
||||
};
|
||||
|
||||
struct MemoryPlanArtifacts {
|
||||
std::string textReport;
|
||||
};
|
||||
|
||||
struct MemoryValueKey {
|
||||
mlir::Value value;
|
||||
std::optional<unsigned> lane;
|
||||
@@ -46,15 +46,33 @@ struct MemoryValueKey {
|
||||
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 {
|
||||
uint64_t numAlloca = 0;
|
||||
uint64_t sizeAlloca = 0;
|
||||
uint64_t numGlobal = 0;
|
||||
uint64_t sizeGlobal = 0;
|
||||
uint64_t logicalAllocaBytes = 0;
|
||||
uint64_t fallbackIntervals = 0;
|
||||
uint64_t nestedSingleUseIntervals = 0;
|
||||
|
||||
bool operator==(const MemoryReportRow& other) const {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -91,26 +109,22 @@ class PimMemory {
|
||||
llvm::SmallDenseMap<MemoryValueKey, MemEntry, 32>& globalMemEntriesMap;
|
||||
llvm::SmallDenseMap<MemoryValueKey, MemEntry, 32> ownedMemEntriesMap;
|
||||
MemoryReportRow reportRow;
|
||||
MemoryPlanArtifacts livenessArtifacts;
|
||||
|
||||
size_t minAlignment = 4;
|
||||
size_t firstAvailableAddress = 0;
|
||||
size_t nextPhysicalSlotId = 0;
|
||||
|
||||
MemEntry* gatherMemEntry(mlir::Value value, std::optional<unsigned> lane = std::nullopt);
|
||||
size_t allocateAddress(size_t size, const MemoryValueKey& key);
|
||||
void allocateGatheredMemory();
|
||||
void allocateMemoryForValue(const MemoryValueKey& key, MemEntry& memEntry, MemoryReportKind reportKind);
|
||||
PhysicalSlotInfo allocatePhysicalSlot(size_t slotSize, const MemoryValueKey& key);
|
||||
|
||||
public:
|
||||
PimMemory(llvm::SmallDenseMap<MemoryValueKey, MemEntry, 32>& globalMemEntriesMap)
|
||||
: globalMemEntriesMap(globalMemEntriesMap) {}
|
||||
|
||||
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;
|
||||
const MemoryPlanArtifacts& getLivenessArtifacts() const { return livenessArtifacts; }
|
||||
void remove(mlir::Value val);
|
||||
|
||||
size_t getFirstAvailableAddress() const { return firstAvailableAddress; }
|
||||
MemEntry getMemEntry(const MemoryValueKey& key) const;
|
||||
@@ -153,12 +167,12 @@ public:
|
||||
uint64_t totalAllocaBytes);
|
||||
void setTotalWeightBytes(uint64_t bytes) { totalWeightBytes = bytes; }
|
||||
void flushReport();
|
||||
void clean(mlir::Operation* op);
|
||||
};
|
||||
|
||||
struct CoreEmissionJob {
|
||||
mlir::Operation* coreLikeOp = nullptr;
|
||||
size_t originalCoreId = 0;
|
||||
const CompiledCoreProgram* program = nullptr;
|
||||
const CompiledCoreMemoryPlan* memoryPlan = nullptr;
|
||||
size_t emittedCoreId = 0;
|
||||
llvm::SmallVector<unsigned, 4> lanes;
|
||||
std::optional<uint64_t> batchReportId;
|
||||
@@ -166,11 +180,10 @@ struct CoreEmissionJob {
|
||||
|
||||
class PimCodeGen {
|
||||
PimAcceleratorMemory& memory;
|
||||
llvm::raw_fd_ostream& coreBinaryStream;
|
||||
PimInstructionWriter& instructionWriter;
|
||||
llvm::raw_fd_ostream* coreJsonStream;
|
||||
const llvm::DenseMap<size_t, size_t>& emittedCoreIds;
|
||||
std::optional<unsigned> batchLane;
|
||||
mutable uint32_t emittedInstructionCount = 0;
|
||||
mutable std::array<std::optional<int32_t>, 256> scalarRegisterValues = {};
|
||||
|
||||
size_t addressOf(mlir::Value value, const StaticValueKnowledge& knowledge) const {
|
||||
@@ -181,30 +194,44 @@ class PimCodeGen {
|
||||
void emitInstruction(const pim_binary::InstructionRecord& instruction) const;
|
||||
void updateScalarRegisterCache(const pim_binary::InstructionRecord& instruction) const;
|
||||
|
||||
void genSetRegisterImmediate(uint8_t registerNumber, int32_t immediate) const;
|
||||
void genSetRegisterImmediateUnsigned(size_t registerNumber, size_t immediate) const;
|
||||
void setupRd(size_t rdAddress, size_t rdOffset) const;
|
||||
void setupRdRs1(size_t rdAddress, size_t rdOffset, size_t rs1Address, size_t rs1Offset) const;
|
||||
void setupRdRs1Rs2(
|
||||
size_t rdAddress, size_t rdOffset, size_t rs1Address, size_t rs1Offset, size_t rs2Address, size_t rs2Offset) const;
|
||||
|
||||
void emitMemCopyOp(mlir::StringRef opName,
|
||||
void emitMemCopyOp(pim_binary::Opcode opcode,
|
||||
size_t rdAddr,
|
||||
size_t rdOffset,
|
||||
size_t rs1Addr,
|
||||
size_t rs1Offset,
|
||||
size_t size,
|
||||
mlir::StringRef sizeFieldName = "size") const;
|
||||
void emitCommunicationOp(mlir::StringRef opName, size_t bufferAddr, size_t coreId, size_t size) const;
|
||||
void emitCommunicationOp(pim_binary::Opcode opcode, size_t bufferAddr, size_t coreId, size_t size) const;
|
||||
void emitMvmOp(size_t groupId, size_t rdAddr, size_t rdOffset, size_t rs1Addr, size_t rs1Offset) const;
|
||||
|
||||
public:
|
||||
void emitBinaryVectorOp(pim_binary::Opcode opcode,
|
||||
mlir::Value output,
|
||||
mlir::Value lhs,
|
||||
mlir::Value rhs,
|
||||
size_t byteSize,
|
||||
const StaticValueKnowledge& knowledge) const;
|
||||
void emitUnaryVectorOp(pim_binary::Opcode opcode,
|
||||
mlir::Value output,
|
||||
mlir::Value input,
|
||||
size_t byteSize,
|
||||
const StaticValueKnowledge& knowledge,
|
||||
int32_t r2OrImm = 0,
|
||||
int32_t generic1 = 0) const;
|
||||
|
||||
PimCodeGen(PimAcceleratorMemory& memory,
|
||||
llvm::raw_fd_ostream& coreBinary,
|
||||
PimInstructionWriter& instructionWriter,
|
||||
llvm::raw_fd_ostream* coreJson,
|
||||
const llvm::DenseMap<size_t, size_t>& emittedCoreIds)
|
||||
: memory(memory), coreBinaryStream(coreBinary), coreJsonStream(coreJson), emittedCoreIds(emittedCoreIds) {}
|
||||
: memory(memory), instructionWriter(instructionWriter), coreJsonStream(coreJson), emittedCoreIds(emittedCoreIds) {}
|
||||
|
||||
uint32_t getEmittedInstructionCount() const { return emittedInstructionCount; }
|
||||
void setBatchLane(std::optional<unsigned> lane) { batchLane = lane; }
|
||||
llvm::FailureOr<int64_t> indexOf(mlir::Value value, const StaticValueKnowledge& knowledge) const {
|
||||
return memory.getIndexValue(value, knowledge);
|
||||
@@ -221,18 +248,7 @@ public:
|
||||
template <typename MVMTy>
|
||||
void codeGenMVMLikeOp(size_t mvmId, MVMTy mvmLikeOp, bool transposeMatrix, const StaticValueKnowledge& knowledge);
|
||||
|
||||
void codeGenVVAddOp(pim::PimVVAddOp vvaddOp, const StaticValueKnowledge& knowledge) const;
|
||||
void codeGenVVSubOp(pim::PimVVSubOp vvsubOp, const StaticValueKnowledge& knowledge) const;
|
||||
void codeGenVVMulOp(pim::PimVVMulOp vvmulOp, const StaticValueKnowledge& knowledge) const;
|
||||
void codeGenVVMaxOp(pim::PimVVMaxOp vvmaxOp, const StaticValueKnowledge& knowledge) const;
|
||||
void codeGenVVDMulOp(pim::PimVVDMulOp vvdmulOp, const StaticValueKnowledge& knowledge) const;
|
||||
void codeGenVAvgOp(pim::PimVAvgOp vavgOp, const StaticValueKnowledge& knowledge) const;
|
||||
void codeGenVReluOp(pim::PimVReluOp vreluOp, const StaticValueKnowledge& knowledge) const;
|
||||
void codeGenVTanhOp(pim::PimVTanhOp vtanhOp, const StaticValueKnowledge& knowledge) const;
|
||||
void codeGenVSigmOp(pim::PimVSigmOp vsigmOp, const StaticValueKnowledge& knowledge) const;
|
||||
void codeGenVSoftmaxOp(pim::PimVSoftmaxOp vsoftmaxOp, const StaticValueKnowledge& knowledge) const;
|
||||
void codeGetGlobalOp(mlir::memref::GetGlobalOp getGlobalOp, const StaticValueKnowledge& knowledge) const;
|
||||
void codeGenTransposeOp(pim::PimTransposeOp transposeOp, const StaticValueKnowledge& knowledge) const;
|
||||
void codeGenTransposeOp(const CompiledTransposePlan& plan, const StaticValueKnowledge& knowledge) const;
|
||||
};
|
||||
|
||||
OnnxMlirCompilerErrorCodes compileToPimCode(mlir::ModuleOp& moduleOpRef, std::string& outputDirName);
|
||||
|
||||
@@ -15,13 +15,6 @@ llvm::cl::opt<PimEmissionTargetType> pimEmissionTarget(
|
||||
llvm::cl::init(EmitPimCodegen),
|
||||
llvm::cl::cat(OnnxMlirOptions));
|
||||
|
||||
llvm::cl::opt<PimMergeSchedulerType>
|
||||
pimMergeScheduler("pim-merge-scheduler",
|
||||
llvm::cl::desc("Scheduler used by the Spatial merge-compute-nodes pass"),
|
||||
llvm::cl::values(clEnumValN(MergeSchedulerPeft, "peft", "Use PEFT scheduling")),
|
||||
llvm::cl::init(MergeSchedulerPeft),
|
||||
llvm::cl::cat(OnnxMlirOptions));
|
||||
|
||||
llvm::cl::opt<PimMemoryReportLevel> pimMemoryReport(
|
||||
"pim-memory-report",
|
||||
llvm::cl::desc("Emit a human-readable PIM memory planning report"),
|
||||
@@ -59,13 +52,17 @@ llvm::cl::opt<PimConvLoweringType> pimConvLowering(
|
||||
|
||||
llvm::cl::opt<PimSpatialDataflowExportType> pimExportSpatialDataflow(
|
||||
"pim-export-spatial-dataflow",
|
||||
llvm::cl::desc("Emit Gephi-importable CSV dataflow reports around MergeComputeNodes materialization"),
|
||||
llvm::cl::desc("Emit Gephi-importable CSV dataflow reports for Spatial pipeline snapshots"),
|
||||
llvm::cl::values(clEnumValN(SpatialDataflowExportNone, "none", "Do not emit Spatial dataflow CSV reports")),
|
||||
llvm::cl::values(clEnumValN(SpatialDataflowExportPre, "pre", "Emit pre-materialization Spatial dataflow CSV reports")),
|
||||
llvm::cl::values(
|
||||
clEnumValN(SpatialDataflowExportPost, "post", "Emit post-materialization Spatial dataflow CSV reports")),
|
||||
clEnumValN(SpatialDataflowExportSpatial1, "spatial1", "Emit spatial1 graph dataflow CSV reports")),
|
||||
llvm::cl::values(
|
||||
clEnumValN(SpatialDataflowExportBoth, "both", "Emit both pre- and post-materialization Spatial dataflow CSV reports")),
|
||||
clEnumValN(SpatialDataflowExportSpatial2, "spatial2", "Emit spatial2 trivially merged graph dataflow CSV reports")),
|
||||
llvm::cl::values(
|
||||
clEnumValN(SpatialDataflowExportSpatial3, "spatial3", "Emit spatial3 scheduled dataflow CSV reports")),
|
||||
llvm::cl::values(
|
||||
clEnumValN(SpatialDataflowExportSpatial4, "spatial4", "Emit spatial4 realized dataflow CSV reports")),
|
||||
llvm::cl::values(clEnumValN(SpatialDataflowExportAll, "all", "Emit all Spatial dataflow CSV reports")),
|
||||
llvm::cl::init(SpatialDataflowExportNone),
|
||||
llvm::cl::cat(OnnxMlirOptions));
|
||||
|
||||
@@ -77,7 +74,7 @@ llvm::cl::opt<bool>
|
||||
|
||||
llvm::cl::opt<bool>
|
||||
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::cat(OnnxMlirOptions));
|
||||
|
||||
@@ -127,10 +124,10 @@ llvm::cl::opt<bool> pimTraceCommunicationMaterialization(
|
||||
llvm::cl::cat(OnnxMlirOptions));
|
||||
|
||||
llvm::cl::opt<size_t>
|
||||
crossbarSize("crossbar-size", llvm::cl::desc("Width and height of a single crossbar"), llvm::cl::init(2));
|
||||
crossbarSize("crossbar-size", llvm::cl::desc("Width and height of a single crossbar"), llvm::cl::init(128));
|
||||
|
||||
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::desc("Number of cores in the chip. Required for PIM compilation."),
|
||||
|
||||
@@ -20,10 +20,6 @@ typedef enum {
|
||||
EmitPimCodegen = 3
|
||||
} PimEmissionTargetType;
|
||||
|
||||
typedef enum {
|
||||
MergeSchedulerPeft = 0,
|
||||
} PimMergeSchedulerType;
|
||||
|
||||
typedef enum {
|
||||
PimMemoryReportNone = 0,
|
||||
PimMemoryReportSummary = 1,
|
||||
@@ -44,20 +40,21 @@ typedef enum {
|
||||
|
||||
typedef enum {
|
||||
SpatialDataflowExportNone = 0,
|
||||
SpatialDataflowExportPre = 1,
|
||||
SpatialDataflowExportPost = 2,
|
||||
SpatialDataflowExportBoth = 3,
|
||||
SpatialDataflowExportSpatial1 = 1,
|
||||
SpatialDataflowExportSpatial2 = 2,
|
||||
SpatialDataflowExportSpatial3 = 3,
|
||||
SpatialDataflowExportSpatial4 = 4,
|
||||
SpatialDataflowExportAll = 5,
|
||||
} PimSpatialDataflowExportType;
|
||||
|
||||
extern llvm::cl::OptionCategory OnnxMlirOptions;
|
||||
extern llvm::cl::opt<PimEmissionTargetType> pimEmissionTarget;
|
||||
extern llvm::cl::opt<PimMergeSchedulerType> pimMergeScheduler;
|
||||
extern llvm::cl::opt<PimMemoryReportLevel> pimMemoryReport;
|
||||
extern llvm::cl::opt<PimConvLoweringType> pimConvLowering;
|
||||
extern llvm::cl::opt<PimSpatialDataflowExportType> pimExportSpatialDataflow;
|
||||
|
||||
extern llvm::cl::opt<bool> pimOnlyCodegen;
|
||||
extern llvm::cl::opt<bool> pimDisableMemoryCoalescing;
|
||||
extern llvm::cl::opt<bool> pimOnlyCodegen;
|
||||
extern llvm::cl::opt<bool> useExperimentalConvImpl;
|
||||
extern llvm::cl::opt<bool> pimEmitJson;
|
||||
extern llvm::cl::opt<bool> pimReportConvLowering;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "mlir/Conversion/AffineToStandard/AffineToStandard.h"
|
||||
#include "mlir/Transforms/Passes.h"
|
||||
|
||||
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
||||
@@ -20,7 +21,8 @@ void addPassesPim(OwningOpRef<ModuleOp>& module,
|
||||
verifyExplicitPimCoreCount();
|
||||
|
||||
if (pimOnlyCodegen) {
|
||||
// Skip all the lowering passes and directly generate code for PIM.
|
||||
pm.addPass(createPimLocalMemoryPlanningPass());
|
||||
pm.addPass(createEmitPimCodePass());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -31,6 +33,7 @@ void addPassesPim(OwningOpRef<ModuleOp>& module,
|
||||
pm.addPass(createONNXToSpatialPass());
|
||||
pm.addPass(createSpatialLayoutPlanningPass());
|
||||
pm.addPass(createLowerSpatialPlansPass());
|
||||
pm.addPass(createTrivialGraphComputeMergePass());
|
||||
pm.addPass(createMergeComputeNodesPass());
|
||||
pm.addPass(createMessagePass("Onnx lowered to Spatial"));
|
||||
}
|
||||
@@ -46,10 +49,13 @@ void addPassesPim(OwningOpRef<ModuleOp>& module,
|
||||
}
|
||||
|
||||
if (pimEmissionTarget >= EmitPimCodegen) {
|
||||
pm.addPass(mlir::createLowerAffinePass());
|
||||
pm.addPass(createPimHostConstantFoldingPass());
|
||||
pm.addPass(createMessagePass("Pim host constants folded"));
|
||||
if (!pimDisableMemoryCoalescing)
|
||||
pm.addPass(createPimMemoryCoalescingPass());
|
||||
pm.addPass(createPimLocalMemoryPlanningPass());
|
||||
pm.addPass(createMessagePass("Pim local memory planned"));
|
||||
pm.addPass(createPimVerificationPass());
|
||||
pm.addPass(createMessagePass("Pim verified"));
|
||||
pm.addPass(createEmitPimCodePass());
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||
#include "mlir/IR/BuiltinTypes.h"
|
||||
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
|
||||
#include "src/Accelerators/PIM/Common/IR/CoreBlockUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||
#include "src/Accelerators/PIM/Compiler/PimCoreProgram.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
||||
|
||||
using namespace llvm;
|
||||
using namespace mlir;
|
||||
|
||||
namespace onnx_mlir {
|
||||
namespace {
|
||||
|
||||
static FailureOr<CompiledCoreOpKind> classifyCompiledCoreOpKind(Operation& op) {
|
||||
if (isa<pim::PimMemCopyHostToDevOp>(op)) return CompiledCoreOpKind::Load;
|
||||
if (isa<pim::PimMemCopyDevToHostOp>(op)) return CompiledCoreOpKind::Store;
|
||||
if (isa<pim::PimMemCopyOp>(op)) return CompiledCoreOpKind::Lmv;
|
||||
if (isa<pim::PimReceiveOp>(op)) return CompiledCoreOpKind::Receive;
|
||||
if (isa<pim::PimSendOp>(op)) return CompiledCoreOpKind::Send;
|
||||
if (isa<pim::PimConcatOp>(op)) return CompiledCoreOpKind::Concat;
|
||||
if (isa<pim::PimVMMOp>(op)) return CompiledCoreOpKind::Vmm;
|
||||
if (isa<pim::PimTransposeOp>(op)) return CompiledCoreOpKind::Transpose;
|
||||
if (isa<pim::PimVVAddOp>(op)) return CompiledCoreOpKind::VVAdd;
|
||||
if (isa<pim::PimVVSubOp>(op)) return CompiledCoreOpKind::VVSub;
|
||||
if (isa<pim::PimVVMulOp>(op)) return CompiledCoreOpKind::VVMul;
|
||||
if (isa<pim::PimVVMaxOp>(op)) return CompiledCoreOpKind::VVMax;
|
||||
if (isa<pim::PimVVDMulOp>(op)) return CompiledCoreOpKind::VVDMul;
|
||||
if (isa<pim::PimVAvgOp>(op)) return CompiledCoreOpKind::VAvg;
|
||||
if (isa<pim::PimVReluOp>(op)) return CompiledCoreOpKind::VRelu;
|
||||
if (isa<pim::PimVTanhOp>(op)) return CompiledCoreOpKind::VTanh;
|
||||
if (isa<pim::PimVSigmOp>(op)) return CompiledCoreOpKind::VSigm;
|
||||
if (isa<pim::PimVSoftmaxOp>(op)) return CompiledCoreOpKind::VSoftmax;
|
||||
return failure();
|
||||
}
|
||||
|
||||
static bool isStoragePreservingTranspose(ArrayRef<size_t> sourceShape, ArrayRef<int64_t> permutation) {
|
||||
SmallVector<unsigned> sourceNonUnitDims;
|
||||
SmallVector<unsigned> destinationSourceNonUnitDims;
|
||||
for (auto [dim, size] : llvm::enumerate(sourceShape))
|
||||
if (size != 1)
|
||||
sourceNonUnitDims.push_back(dim);
|
||||
for (int64_t sourceDim : permutation)
|
||||
if (sourceShape[sourceDim] != 1)
|
||||
destinationSourceNonUnitDims.push_back(static_cast<unsigned>(sourceDim));
|
||||
return sourceNonUnitDims == destinationSourceNonUnitDims;
|
||||
}
|
||||
|
||||
static FailureOr<CompiledTransposePlan> compileTransposePlan(pim::PimTransposeOp transposeOp) {
|
||||
auto sourceType = cast<ShapedType>(transposeOp.getInput().getType());
|
||||
ArrayRef<int64_t> sourceShape = sourceType.getShape();
|
||||
size_t rank = sourceShape.size();
|
||||
CompiledTransposePlan plan;
|
||||
plan.source = transposeOp.getInput();
|
||||
plan.destination = transposeOp.getOutputBuffer();
|
||||
plan.elementBytes = getElementTypeSizeInBytes(sourceType.getElementType());
|
||||
auto totalElements = pim::checkedSize(sourceType.getNumElements(), transposeOp, "transpose elements");
|
||||
if (failed(totalElements)) return failure();
|
||||
plan.totalElements = *totalElements;
|
||||
auto totalBytes = pim::checkedMul(plan.totalElements, plan.elementBytes, transposeOp, "transpose byte size");
|
||||
if (failed(totalBytes)) return failure();
|
||||
plan.totalBytes = *totalBytes;
|
||||
|
||||
SmallVector<int64_t> permutation = map_to_vector(transposeOp.getPermutation().getAsRange<IntegerAttr>(),
|
||||
[](IntegerAttr attr) { return attr.getInt(); });
|
||||
if (permutation.size() != rank) {
|
||||
transposeOp.emitOpError("requires permutation rank to match source rank for PIM codegen");
|
||||
return failure();
|
||||
}
|
||||
|
||||
SmallVector<size_t> destinationShape(rank);
|
||||
plan.destinationStrides.assign(rank, 1);
|
||||
plan.destinationDimensionForSource.assign(rank, 0);
|
||||
plan.destinationRewinds.assign(rank, 0);
|
||||
SmallVector<bool> seenSourceDimensions(rank, false);
|
||||
for (size_t dim = 0; dim < rank; ++dim) {
|
||||
auto size = pim::checkedSize(sourceShape[dim], transposeOp, "transpose source dimension");
|
||||
if (failed(size)) return failure();
|
||||
plan.sourceShape.push_back(*size);
|
||||
}
|
||||
for (auto [destinationDim, sourceDim] : llvm::enumerate(permutation)) {
|
||||
if (sourceDim < 0 || static_cast<size_t>(sourceDim) >= rank || seenSourceDimensions[sourceDim]) {
|
||||
transposeOp.emitOpError("requires a valid permutation containing each source dimension exactly once");
|
||||
return failure();
|
||||
}
|
||||
seenSourceDimensions[sourceDim] = true;
|
||||
destinationShape[destinationDim] = plan.sourceShape[sourceDim];
|
||||
plan.destinationDimensionForSource[sourceDim] = destinationDim;
|
||||
}
|
||||
for (size_t dim = rank; dim > 1; --dim) {
|
||||
auto stride = pim::checkedMul(
|
||||
plan.destinationStrides[dim - 1], destinationShape[dim - 1], transposeOp, "transpose destination stride");
|
||||
if (failed(stride)) return failure();
|
||||
plan.destinationStrides[dim - 2] = *stride;
|
||||
}
|
||||
for (size_t sourceDim = 0; sourceDim < rank; ++sourceDim) {
|
||||
auto rewind = pim::checkedMul(plan.sourceShape[sourceDim],
|
||||
plan.destinationStrides[plan.destinationDimensionForSource[sourceDim]],
|
||||
transposeOp,
|
||||
"transpose destination rewind");
|
||||
if (failed(rewind)) return failure();
|
||||
plan.destinationRewinds[sourceDim] = *rewind;
|
||||
}
|
||||
plan.storagePreserving = isStoragePreservingTranspose(plan.sourceShape, permutation);
|
||||
return plan;
|
||||
}
|
||||
|
||||
static LogicalResult compileCoreEmissionPlan(Block& block, SmallVectorImpl<CompiledCoreNode>& plan) {
|
||||
for (Operation& op : block) {
|
||||
if (isa<pim::PimHaltOp, scf::YieldOp, memref::GetGlobalOp>(op) || isCoreStaticAddressOp(&op))
|
||||
continue;
|
||||
if (auto loadOp = dyn_cast<memref::LoadOp>(op); loadOp && succeeded(compileIndexExpr(loadOp.getResult())))
|
||||
continue;
|
||||
|
||||
if (auto forOp = dyn_cast<scf::ForOp>(op)) {
|
||||
auto lower = compileIndexExpr(forOp.getLowerBound());
|
||||
auto upper = compileIndexExpr(forOp.getUpperBound());
|
||||
auto step = compileIndexExpr(forOp.getStep());
|
||||
if (failed(lower) || failed(upper) || failed(step)) {
|
||||
forOp.emitOpError("requires statically evaluable scf.for bounds for PIM codegen");
|
||||
return failure();
|
||||
}
|
||||
CompiledCoreNode node;
|
||||
node.kind = CompiledCoreNode::Kind::Loop;
|
||||
node.op = forOp;
|
||||
node.lowerBound = *lower;
|
||||
node.upperBound = *upper;
|
||||
node.step = *step;
|
||||
node.loopBody = std::make_unique<SmallVector<CompiledCoreNode, 8>>();
|
||||
if (failed(compileCoreEmissionPlan(forOp.getRegion().front(), *node.loopBody))) return failure();
|
||||
plan.push_back(std::move(node));
|
||||
continue;
|
||||
}
|
||||
if (auto ifOp = dyn_cast<scf::IfOp>(op)) {
|
||||
auto condition = compileIndexExpr(ifOp.getCondition());
|
||||
if (failed(condition)) {
|
||||
ifOp.emitOpError("requires statically evaluable scf.if condition for PIM codegen");
|
||||
return failure();
|
||||
}
|
||||
CompiledCoreNode node;
|
||||
node.kind = CompiledCoreNode::Kind::If;
|
||||
node.op = ifOp;
|
||||
node.condition = *condition;
|
||||
node.thenBody = std::make_unique<SmallVector<CompiledCoreNode, 8>>();
|
||||
node.elseBody = std::make_unique<SmallVector<CompiledCoreNode, 8>>();
|
||||
if (failed(compileCoreEmissionPlan(ifOp.getThenRegion().front(), *node.thenBody))) return failure();
|
||||
if (!ifOp.getElseRegion().empty()
|
||||
&& failed(compileCoreEmissionPlan(ifOp.getElseRegion().front(), *node.elseBody)))
|
||||
return failure();
|
||||
plan.push_back(std::move(node));
|
||||
continue;
|
||||
}
|
||||
if (auto switchOp = dyn_cast<scf::IndexSwitchOp>(op)) {
|
||||
auto selector = compileIndexExpr(switchOp.getArg());
|
||||
if (failed(selector)) {
|
||||
switchOp.emitOpError("requires a statically evaluable scf.index_switch selector for PIM codegen");
|
||||
return failure();
|
||||
}
|
||||
CompiledCoreNode node;
|
||||
node.kind = CompiledCoreNode::Kind::IndexSwitch;
|
||||
node.op = switchOp;
|
||||
node.condition = *selector;
|
||||
llvm::append_range(node.caseValues, switchOp.getCases());
|
||||
for (Region& region : switchOp.getCaseRegions()) {
|
||||
auto body = std::make_unique<SmallVector<CompiledCoreNode, 8>>();
|
||||
if (failed(compileCoreEmissionPlan(region.front(), *body))) return failure();
|
||||
node.caseBodies.push_back(std::move(body));
|
||||
}
|
||||
node.defaultBody = std::make_unique<SmallVector<CompiledCoreNode, 8>>();
|
||||
if (failed(compileCoreEmissionPlan(switchOp.getDefaultRegion().front(), *node.defaultBody))) return failure();
|
||||
plan.push_back(std::move(node));
|
||||
continue;
|
||||
}
|
||||
|
||||
auto opKind = classifyCompiledCoreOpKind(op);
|
||||
if (failed(opKind)) {
|
||||
InFlightDiagnostic diagnostic = op.emitError() << "unsupported codegen for op '" << op.getName() << "'";
|
||||
if (auto coreOp = op.getParentOfType<pim::PimCoreOp>())
|
||||
diagnostic << " inside pim.core " << coreOp.getCoreId();
|
||||
else if (auto batchOp = op.getParentOfType<pim::PimCoreBatchOp>())
|
||||
diagnostic << " inside pim.core_batch with laneCount " << batchOp.getLaneCount();
|
||||
return failure();
|
||||
}
|
||||
CompiledCoreNode node;
|
||||
node.op = &op;
|
||||
node.opKind = *opKind;
|
||||
if (*opKind == CompiledCoreOpKind::Transpose) {
|
||||
auto transposePlan = compileTransposePlan(cast<pim::PimTransposeOp>(op));
|
||||
if (failed(transposePlan)) return failure();
|
||||
node.transposePlan = *transposePlan;
|
||||
}
|
||||
plan.push_back(std::move(node));
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
LogicalResult compileCoreProgram(Operation* coreLikeOp, CompiledCoreProgram& program) {
|
||||
Block& block = isa<pim::PimCoreOp>(coreLikeOp) ? cast<pim::PimCoreOp>(coreLikeOp).getBody().front()
|
||||
: cast<pim::PimCoreBatchOp>(coreLikeOp).getBody().front();
|
||||
return compileCoreEmissionPlan(block, program.nodes);
|
||||
}
|
||||
|
||||
} // namespace onnx_mlir
|
||||
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include "mlir/IR/Operation.h"
|
||||
#include "mlir/Support/LogicalResult.h"
|
||||
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include "src/Accelerators/PIM/Common/IR/AddressAnalysis.hpp"
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
struct CompiledTransposePlan {
|
||||
mlir::Value source;
|
||||
mlir::Value destination;
|
||||
size_t elementBytes = 0;
|
||||
size_t totalElements = 0;
|
||||
size_t totalBytes = 0;
|
||||
llvm::SmallVector<size_t> sourceShape;
|
||||
llvm::SmallVector<size_t> destinationStrides;
|
||||
llvm::SmallVector<unsigned> destinationDimensionForSource;
|
||||
llvm::SmallVector<size_t> destinationRewinds;
|
||||
bool storagePreserving = false;
|
||||
};
|
||||
|
||||
enum class CompiledCoreOpKind : uint8_t {
|
||||
Load,
|
||||
Store,
|
||||
Lmv,
|
||||
Receive,
|
||||
Send,
|
||||
Concat,
|
||||
Vmm,
|
||||
Transpose,
|
||||
VVAdd,
|
||||
VVSub,
|
||||
VVMul,
|
||||
VVMax,
|
||||
VVDMul,
|
||||
VAvg,
|
||||
VRelu,
|
||||
VTanh,
|
||||
VSigm,
|
||||
VSoftmax
|
||||
};
|
||||
|
||||
struct CompiledCoreNode {
|
||||
enum class Kind : uint8_t { Op, Loop, If, IndexSwitch };
|
||||
|
||||
Kind kind = Kind::Op;
|
||||
mlir::Operation* op = nullptr;
|
||||
CompiledCoreOpKind opKind = CompiledCoreOpKind::Load;
|
||||
CompiledIndexExpr lowerBound;
|
||||
CompiledIndexExpr upperBound;
|
||||
CompiledIndexExpr step;
|
||||
CompiledIndexExpr condition;
|
||||
std::unique_ptr<llvm::SmallVector<CompiledCoreNode, 8>> loopBody;
|
||||
std::unique_ptr<llvm::SmallVector<CompiledCoreNode, 8>> thenBody;
|
||||
std::unique_ptr<llvm::SmallVector<CompiledCoreNode, 8>> elseBody;
|
||||
llvm::SmallVector<int64_t> caseValues;
|
||||
llvm::SmallVector<std::unique_ptr<llvm::SmallVector<CompiledCoreNode, 8>>> caseBodies;
|
||||
std::unique_ptr<llvm::SmallVector<CompiledCoreNode, 8>> defaultBody;
|
||||
std::optional<CompiledTransposePlan> transposePlan;
|
||||
};
|
||||
|
||||
struct CompiledCoreProgram {
|
||||
llvm::SmallVector<CompiledCoreNode, 32> nodes;
|
||||
};
|
||||
|
||||
mlir::LogicalResult compileCoreProgram(mlir::Operation* coreLikeOp, CompiledCoreProgram& program);
|
||||
|
||||
} // namespace onnx_mlir
|
||||
@@ -1,3 +1,2 @@
|
||||
add_subdirectory(ONNXToSpatial)
|
||||
add_subdirectory(SpatialToGraphviz)
|
||||
add_subdirectory(SpatialToPim)
|
||||
@@ -31,8 +31,10 @@ add_pim_library(OMONNXToSpatial
|
||||
SpatialLayoutPlanningPass.cpp
|
||||
LowerSpatialPlansPass.cpp
|
||||
Common/AttributeUtils.cpp
|
||||
Common/BiasAddUtils.cpp
|
||||
Common/ComputeRegionBuilder.cpp
|
||||
Common/MatrixProductLowering.cpp
|
||||
Common/RowStripLayoutUtils.cpp
|
||||
Common/ShapeTilingUtils.cpp
|
||||
Common/WeightMaterialization.cpp
|
||||
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
#include "mlir/IR/BuiltinAttributes.h"
|
||||
#include "mlir/IR/BuiltinTypes.h"
|
||||
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/BiasAddUtils.hpp"
|
||||
|
||||
using namespace mlir;
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
LogicalResult isSupportedBiasAddShape(RankedTensorType biasType, RankedTensorType resultType) {
|
||||
if (!biasType || !resultType || !biasType.hasStaticShape() || !resultType.hasStaticShape())
|
||||
return failure();
|
||||
if (resultType.getRank() != 4)
|
||||
return failure();
|
||||
if (biasType.getElementType() != resultType.getElementType())
|
||||
return failure();
|
||||
|
||||
const int64_t channels = resultType.getDimSize(1);
|
||||
ArrayRef<int64_t> shape = biasType.getShape();
|
||||
if (shape.empty())
|
||||
return success();
|
||||
if (shape.size() == 1)
|
||||
return success(shape[0] == channels);
|
||||
if (shape.size() == 2)
|
||||
return success(shape[0] == 1 && shape[1] == channels);
|
||||
if (shape.size() == 4)
|
||||
return success(shape[0] == 1 && shape[1] == channels && shape[2] == 1 && shape[3] == 1);
|
||||
return failure();
|
||||
}
|
||||
|
||||
FailureOr<SmallVector<Attribute>> getBiasChannelValues(DenseElementsAttr denseAttr, RankedTensorType resultType) {
|
||||
auto biasType = dyn_cast<RankedTensorType>(denseAttr.getType());
|
||||
if (!biasType || failed(isSupportedBiasAddShape(biasType, resultType)))
|
||||
return failure();
|
||||
|
||||
const int64_t channels = resultType.getDimSize(1);
|
||||
if (denseAttr.isSplat()) {
|
||||
return SmallVector<Attribute>(channels, denseAttr.getSplatValue<Attribute>());
|
||||
}
|
||||
|
||||
SmallVector<Attribute> flattened(denseAttr.getValues<Attribute>());
|
||||
if (biasType.getRank() == 1)
|
||||
return flattened;
|
||||
if (biasType.getRank() == 2)
|
||||
return flattened;
|
||||
|
||||
SmallVector<Attribute> channelValues;
|
||||
channelValues.reserve(channels);
|
||||
const int64_t channelStride = biasType.getDimSize(2) * biasType.getDimSize(3);
|
||||
for (int64_t channel = 0; channel < channels; ++channel)
|
||||
channelValues.push_back(flattened[channel * channelStride]);
|
||||
return channelValues;
|
||||
}
|
||||
|
||||
bool isSupportedBiasAddValue(Value bias, RankedTensorType resultType, DenseElementsAttr* denseAttr) {
|
||||
auto attr = getHostConstDenseElementsAttr(bias);
|
||||
if (!attr)
|
||||
return false;
|
||||
auto biasType = dyn_cast<RankedTensorType>(attr.getType());
|
||||
if (!biasType || failed(isSupportedBiasAddShape(biasType, resultType)))
|
||||
return false;
|
||||
if (failed(getBiasChannelValues(attr, resultType)))
|
||||
return false;
|
||||
if (denseAttr)
|
||||
*denseAttr = attr;
|
||||
return true;
|
||||
}
|
||||
|
||||
FailureOr<BiasAddPlanCandidate> classifyBiasAddPlanCandidate(Value lhs, Value rhs, RankedTensorType resultType) {
|
||||
auto lhsType = dyn_cast<RankedTensorType>(lhs.getType());
|
||||
auto rhsType = dyn_cast<RankedTensorType>(rhs.getType());
|
||||
if (!lhsType || !rhsType)
|
||||
return failure();
|
||||
if (lhsType == resultType && isSupportedBiasAddValue(rhs, resultType))
|
||||
return BiasAddPlanCandidate {lhs, rhs};
|
||||
if (rhsType == resultType && isSupportedBiasAddValue(lhs, resultType))
|
||||
return BiasAddPlanCandidate {rhs, lhs};
|
||||
return failure();
|
||||
}
|
||||
|
||||
FailureOr<Value>
|
||||
materializeDenseBiasAddTensor(Value bias, RankedTensorType resultType, RewriterBase& rewriter, Location loc) {
|
||||
DenseElementsAttr denseAttr;
|
||||
if (!isSupportedBiasAddValue(bias, resultType, &denseAttr))
|
||||
return failure();
|
||||
|
||||
FailureOr<SmallVector<Attribute>> channelValues = getBiasChannelValues(denseAttr, resultType);
|
||||
if (failed(channelValues))
|
||||
return failure();
|
||||
|
||||
SmallVector<Attribute> resultValues;
|
||||
resultValues.reserve(resultType.getNumElements());
|
||||
const int64_t batches = resultType.getDimSize(0);
|
||||
const int64_t channels = resultType.getDimSize(1);
|
||||
const int64_t height = resultType.getDimSize(2);
|
||||
const int64_t width = resultType.getDimSize(3);
|
||||
for (int64_t n = 0; n < batches; ++n)
|
||||
for (int64_t c = 0; c < channels; ++c)
|
||||
for (int64_t h = 0; h < height; ++h)
|
||||
for (int64_t w = 0; w < width; ++w)
|
||||
resultValues.push_back((*channelValues)[c]);
|
||||
|
||||
auto resultAttr = DenseElementsAttr::get(resultType, resultValues);
|
||||
return getOrCreateConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), resultAttr, resultType);
|
||||
}
|
||||
|
||||
} // namespace onnx_mlir
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "mlir/IR/BuiltinAttributes.h"
|
||||
#include "mlir/IR/BuiltinTypes.h"
|
||||
#include "mlir/IR/PatternMatch.h"
|
||||
#include "mlir/IR/Value.h"
|
||||
#include "mlir/Support/LogicalResult.h"
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
struct BiasAddPlanCandidate {
|
||||
mlir::Value data;
|
||||
mlir::Value bias;
|
||||
};
|
||||
|
||||
mlir::LogicalResult isSupportedBiasAddShape(mlir::RankedTensorType biasType, mlir::RankedTensorType resultType);
|
||||
bool isSupportedBiasAddValue(mlir::Value bias,
|
||||
mlir::RankedTensorType resultType,
|
||||
mlir::DenseElementsAttr* denseAttr = nullptr);
|
||||
mlir::FailureOr<llvm::SmallVector<mlir::Attribute>>
|
||||
getBiasChannelValues(mlir::DenseElementsAttr denseAttr, mlir::RankedTensorType resultType);
|
||||
mlir::FailureOr<BiasAddPlanCandidate> classifyBiasAddPlanCandidate(mlir::Value lhs,
|
||||
mlir::Value rhs,
|
||||
mlir::RankedTensorType resultType);
|
||||
mlir::FailureOr<mlir::Value> materializeDenseBiasAddTensor(mlir::Value bias,
|
||||
mlir::RankedTensorType resultType,
|
||||
mlir::RewriterBase& rewriter,
|
||||
mlir::Location loc);
|
||||
|
||||
} // namespace onnx_mlir
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/TensorSliceUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
|
||||
@@ -60,6 +61,56 @@ struct SpatComputeBatchBodyArgs {
|
||||
mlir::ValueRange outputs;
|
||||
};
|
||||
|
||||
inline mlir::SmallVector<mlir::Type> getGraphComputeBlockArgTypes(mlir::ValueRange weights, mlir::ValueRange inputs) {
|
||||
mlir::SmallVector<mlir::Type> blockArgTypes;
|
||||
blockArgTypes.reserve(weights.size() + inputs.size());
|
||||
for (mlir::Value weight : weights)
|
||||
blockArgTypes.push_back(weight.getType());
|
||||
for (mlir::Value input : inputs)
|
||||
blockArgTypes.push_back(input.getType());
|
||||
return blockArgTypes;
|
||||
}
|
||||
|
||||
inline mlir::SmallVector<mlir::Location> getGraphComputeBlockArgLocs(mlir::Location defaultLoc,
|
||||
mlir::ValueRange weights,
|
||||
mlir::ValueRange inputs) {
|
||||
mlir::SmallVector<mlir::Location> blockArgLocs;
|
||||
blockArgLocs.reserve(weights.size() + inputs.size());
|
||||
for (mlir::Value weight : weights)
|
||||
blockArgLocs.push_back(weight.getLoc());
|
||||
for (mlir::Value input : inputs)
|
||||
blockArgLocs.push_back(input.getLoc());
|
||||
return blockArgLocs;
|
||||
}
|
||||
|
||||
inline mlir::SmallVector<mlir::Type> getGraphComputeBatchBlockArgTypes(mlir::OpBuilder& builder,
|
||||
mlir::TypeRange resultTypes,
|
||||
mlir::ValueRange weights,
|
||||
mlir::ValueRange inputs) {
|
||||
mlir::SmallVector<mlir::Type> blockArgTypes {builder.getIndexType()};
|
||||
blockArgTypes.reserve(1 + weights.size() + inputs.size() + resultTypes.size());
|
||||
for (mlir::Value weight : weights)
|
||||
blockArgTypes.push_back(weight.getType());
|
||||
for (mlir::Value input : inputs)
|
||||
blockArgTypes.push_back(input.getType());
|
||||
llvm::append_range(blockArgTypes, resultTypes);
|
||||
return blockArgTypes;
|
||||
}
|
||||
|
||||
inline mlir::SmallVector<mlir::Location> getGraphComputeBatchBlockArgLocs(mlir::Location defaultLoc,
|
||||
mlir::TypeRange resultTypes,
|
||||
mlir::ValueRange weights,
|
||||
mlir::ValueRange inputs) {
|
||||
mlir::SmallVector<mlir::Location> blockArgLocs {defaultLoc};
|
||||
blockArgLocs.reserve(1 + weights.size() + inputs.size() + resultTypes.size());
|
||||
for (mlir::Value weight : weights)
|
||||
blockArgLocs.push_back(weight.getLoc());
|
||||
for (mlir::Value input : inputs)
|
||||
blockArgLocs.push_back(input.getLoc());
|
||||
blockArgLocs.append(resultTypes.size(), defaultLoc);
|
||||
return blockArgLocs;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename RewriterT>
|
||||
@@ -87,6 +138,31 @@ inline mlir::Value createSpatConcat(RewriterT& rewriter, mlir::Location loc, int
|
||||
return spatial::SpatConcatOp::create(rewriter, loc, outputType, rewriter.getI64IntegerAttr(axis), inputs).getOutput();
|
||||
}
|
||||
|
||||
template <typename RewriterT>
|
||||
spatial::SpatGraphCompute createEmptySpatGraphCompute(RewriterT& rewriter,
|
||||
mlir::Location loc,
|
||||
mlir::TypeRange resultTypes,
|
||||
mlir::ValueRange weights,
|
||||
mlir::ValueRange inputs,
|
||||
mlir::TypeRange blockArgTypes,
|
||||
llvm::ArrayRef<mlir::Location> blockArgLocs) {
|
||||
auto computeOp = spatial::SpatGraphCompute::create(rewriter, loc, resultTypes, weights, inputs);
|
||||
rewriter.createBlock(&computeOp.getBody(), computeOp.getBody().end(), blockArgTypes, blockArgLocs);
|
||||
rewriter.setInsertionPointToStart(&computeOp.getBody().front());
|
||||
return computeOp;
|
||||
}
|
||||
|
||||
template <typename RewriterT>
|
||||
spatial::SpatGraphCompute createEmptySpatGraphCompute(RewriterT& rewriter,
|
||||
mlir::Location loc,
|
||||
mlir::TypeRange resultTypes,
|
||||
mlir::ValueRange weights,
|
||||
mlir::ValueRange inputs) {
|
||||
auto blockArgTypes = detail::getGraphComputeBlockArgTypes(weights, inputs);
|
||||
auto blockArgLocs = detail::getGraphComputeBlockArgLocs(loc, weights, inputs);
|
||||
return createEmptySpatGraphCompute(rewriter, loc, resultTypes, weights, inputs, blockArgTypes, blockArgLocs);
|
||||
}
|
||||
|
||||
/// Builds a `spat.graph_compute` with a fixed number of SSA inputs and erases it if
|
||||
/// the body callback reports failure.
|
||||
template <size_t NumInputs, typename RewriterT, typename BodyFn>
|
||||
@@ -97,16 +173,8 @@ auto createSpatGraphCompute(RewriterT& rewriter,
|
||||
mlir::ValueRange inputs,
|
||||
BodyFn&& body) {
|
||||
assert(inputs.size() == NumInputs && "NumInputs must match the number of input values");
|
||||
auto computeOp = spatial::SpatGraphCompute::create(rewriter, loc, resultTypes, weights, inputs);
|
||||
|
||||
auto* block = new mlir::Block();
|
||||
for (mlir::Value weight : weights)
|
||||
block->addArgument(weight.getType(), loc);
|
||||
for (mlir::Value input : inputs)
|
||||
block->addArgument(input.getType(), loc);
|
||||
|
||||
computeOp.getBody().push_back(block);
|
||||
rewriter.setInsertionPointToStart(block);
|
||||
auto computeOp = createEmptySpatGraphCompute(rewriter, loc, resultTypes, weights, inputs);
|
||||
auto* block = &computeOp.getBody().front();
|
||||
|
||||
using BodyResult = detail::InvokeWithBlockArgsResultT<std::decay_t<BodyFn>, std::make_index_sequence<NumInputs>>;
|
||||
if constexpr (std::is_same_v<BodyResult, void>) {
|
||||
@@ -140,16 +208,8 @@ auto createSpatGraphCompute(RewriterT& rewriter,
|
||||
mlir::ValueRange weights,
|
||||
mlir::ValueRange inputs,
|
||||
BodyFn&& body) {
|
||||
auto computeOp = spatial::SpatGraphCompute::create(rewriter, loc, resultTypes, weights, inputs);
|
||||
|
||||
auto* block = new mlir::Block();
|
||||
for (mlir::Value weight : weights)
|
||||
block->addArgument(weight.getType(), loc);
|
||||
for (mlir::Value input : inputs)
|
||||
block->addArgument(input.getType(), loc);
|
||||
|
||||
computeOp.getBody().push_back(block);
|
||||
rewriter.setInsertionPointToStart(block);
|
||||
auto computeOp = createEmptySpatGraphCompute(rewriter, loc, resultTypes, weights, inputs);
|
||||
auto* block = &computeOp.getBody().front();
|
||||
|
||||
using BodyResult = detail::InvokeWithValueRangeResultT<std::decay_t<BodyFn>>;
|
||||
if constexpr (std::is_same_v<BodyResult, void>) {
|
||||
@@ -170,14 +230,15 @@ auto createSpatGraphCompute(RewriterT& rewriter,
|
||||
}
|
||||
}
|
||||
|
||||
template <typename RewriterT, typename BodyFn>
|
||||
auto createSpatGraphComputeBatch(RewriterT& rewriter,
|
||||
template <typename RewriterT>
|
||||
auto createEmptySpatGraphComputeBatch(RewriterT& rewriter,
|
||||
mlir::Location loc,
|
||||
mlir::TypeRange resultTypes,
|
||||
int64_t laneCount,
|
||||
mlir::ValueRange weights,
|
||||
mlir::ValueRange inputs,
|
||||
BodyFn&& body) {
|
||||
mlir::TypeRange blockArgTypes,
|
||||
llvm::ArrayRef<mlir::Location> blockArgLocs) {
|
||||
if (laneCount <= 0 || laneCount > std::numeric_limits<int32_t>::max())
|
||||
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(mlir::failure());
|
||||
|
||||
@@ -186,27 +247,36 @@ auto createSpatGraphComputeBatch(RewriterT& rewriter,
|
||||
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(mlir::failure());
|
||||
|
||||
auto batchOp = spatial::SpatGraphComputeBatch::create(rewriter, loc, resultTypes, *laneCountAttr, weights, inputs);
|
||||
|
||||
mlir::SmallVector<mlir::Type> blockArgTypes {rewriter.getIndexType()};
|
||||
mlir::SmallVector<mlir::Location> blockArgLocs {loc};
|
||||
blockArgTypes.reserve(1 + weights.size() + inputs.size() + resultTypes.size());
|
||||
blockArgLocs.reserve(1 + weights.size() + inputs.size() + resultTypes.size());
|
||||
for (mlir::Value weight : weights) {
|
||||
blockArgTypes.push_back(weight.getType());
|
||||
blockArgLocs.push_back(weight.getLoc());
|
||||
}
|
||||
for (mlir::Value input : inputs) {
|
||||
blockArgTypes.push_back(input.getType());
|
||||
blockArgLocs.push_back(input.getLoc());
|
||||
}
|
||||
for (mlir::Type resultType : resultTypes) {
|
||||
blockArgTypes.push_back(resultType);
|
||||
blockArgLocs.push_back(loc);
|
||||
rewriter.createBlock(&batchOp.getBody(), batchOp.getBody().end(), blockArgTypes, blockArgLocs);
|
||||
rewriter.setInsertionPointToStart(&batchOp.getBody().front());
|
||||
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(batchOp);
|
||||
}
|
||||
|
||||
auto* block =
|
||||
rewriter.createBlock(&batchOp.getBody(), batchOp.getBody().end(), mlir::TypeRange(blockArgTypes), blockArgLocs);
|
||||
rewriter.setInsertionPointToStart(block);
|
||||
template <typename RewriterT>
|
||||
auto createEmptySpatGraphComputeBatch(RewriterT& rewriter,
|
||||
mlir::Location loc,
|
||||
mlir::TypeRange resultTypes,
|
||||
int64_t laneCount,
|
||||
mlir::ValueRange weights,
|
||||
mlir::ValueRange inputs) {
|
||||
auto blockArgTypes = detail::getGraphComputeBatchBlockArgTypes(rewriter, resultTypes, weights, inputs);
|
||||
auto blockArgLocs = detail::getGraphComputeBatchBlockArgLocs(loc, resultTypes, weights, inputs);
|
||||
return createEmptySpatGraphComputeBatch(
|
||||
rewriter, loc, resultTypes, laneCount, weights, inputs, blockArgTypes, blockArgLocs);
|
||||
}
|
||||
|
||||
template <typename RewriterT, typename BodyFn>
|
||||
auto createSpatGraphComputeBatch(RewriterT& rewriter,
|
||||
mlir::Location loc,
|
||||
mlir::TypeRange resultTypes,
|
||||
int64_t laneCount,
|
||||
mlir::ValueRange weights,
|
||||
mlir::ValueRange inputs,
|
||||
BodyFn&& body) {
|
||||
auto batchOp = createEmptySpatGraphComputeBatch(rewriter, loc, resultTypes, laneCount, weights, inputs);
|
||||
if (failed(batchOp))
|
||||
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(mlir::failure());
|
||||
auto* block = &(*batchOp).getBody().front();
|
||||
|
||||
detail::SpatComputeBatchBodyArgs args {
|
||||
block->getArgument(0),
|
||||
@@ -217,18 +287,18 @@ auto createSpatGraphComputeBatch(RewriterT& rewriter,
|
||||
using BodyResult = std::invoke_result_t<BodyFn, detail::SpatComputeBatchBodyArgs>;
|
||||
if constexpr (std::is_same_v<BodyResult, void>) {
|
||||
std::forward<BodyFn>(body)(args);
|
||||
rewriter.setInsertionPointAfter(batchOp);
|
||||
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(batchOp);
|
||||
rewriter.setInsertionPointAfter(*batchOp);
|
||||
return batchOp;
|
||||
}
|
||||
else {
|
||||
auto bodyResult = std::forward<BodyFn>(body)(args);
|
||||
if (mlir::failed(bodyResult)) {
|
||||
rewriter.setInsertionPointAfter(batchOp);
|
||||
rewriter.eraseOp(batchOp);
|
||||
rewriter.setInsertionPointAfter(*batchOp);
|
||||
rewriter.eraseOp(*batchOp);
|
||||
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(mlir::failure());
|
||||
}
|
||||
rewriter.setInsertionPointAfter(batchOp);
|
||||
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(batchOp);
|
||||
rewriter.setInsertionPointAfter(*batchOp);
|
||||
return batchOp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,6 +347,46 @@ inline void createParallelInsertSliceIntoBatchOutput(mlir::PatternRewriter& rewr
|
||||
mlir::tensor::ParallelInsertSliceOp::create(rewriter, loc, source, dest, offsets, sizes, strides);
|
||||
}
|
||||
|
||||
inline void publishGraphBatchPhysicalFragment(mlir::PatternRewriter& rewriter,
|
||||
mlir::Location loc,
|
||||
mlir::Value fragment,
|
||||
mlir::Value output,
|
||||
mlir::Value physicalSlot) {
|
||||
auto fragmentType = mlir::cast<mlir::RankedTensorType>(fragment.getType());
|
||||
mlir::SmallVector<mlir::OpFoldResult> offsets {physicalSlot};
|
||||
mlir::SmallVector<mlir::OpFoldResult> sizes {rewriter.getIndexAttr(1)};
|
||||
mlir::SmallVector<mlir::OpFoldResult> strides {rewriter.getIndexAttr(1)};
|
||||
for (int64_t dim : fragmentType.getShape()) {
|
||||
offsets.push_back(rewriter.getIndexAttr(0));
|
||||
sizes.push_back(rewriter.getIndexAttr(dim));
|
||||
strides.push_back(rewriter.getIndexAttr(1));
|
||||
}
|
||||
createParallelInsertSliceIntoBatchOutput(rewriter, loc, fragment, output, offsets, sizes, strides);
|
||||
}
|
||||
|
||||
inline mlir::FailureOr<mlir::Value>
|
||||
extractGraphBatchPhysicalFragment(mlir::PatternRewriter& rewriter,
|
||||
mlir::Location loc,
|
||||
mlir::Value physicalBatch,
|
||||
mlir::OpFoldResult slot,
|
||||
mlir::RankedTensorType fragmentType) {
|
||||
if (fragmentType.getRank() == 0)
|
||||
return mlir::failure();
|
||||
auto physicalType = mlir::dyn_cast<mlir::RankedTensorType>(physicalBatch.getType());
|
||||
if (!physicalType || physicalType.getRank() != fragmentType.getRank() + 1)
|
||||
return mlir::failure();
|
||||
mlir::SmallVector<mlir::OpFoldResult> offsets {slot};
|
||||
mlir::SmallVector<mlir::OpFoldResult> sizes {rewriter.getIndexAttr(1)};
|
||||
mlir::SmallVector<mlir::OpFoldResult> strides {rewriter.getIndexAttr(1)};
|
||||
for (int64_t dim : fragmentType.getShape()) {
|
||||
offsets.push_back(rewriter.getIndexAttr(0));
|
||||
sizes.push_back(rewriter.getIndexAttr(dim));
|
||||
strides.push_back(rewriter.getIndexAttr(1));
|
||||
}
|
||||
return extractMixedSliceOrIdentity(
|
||||
rewriter, loc, physicalBatch, fragmentType, {offsets, sizes, strides});
|
||||
}
|
||||
|
||||
template <typename BodyFn>
|
||||
mlir::Value materializeOrComputeUnary(mlir::Value input,
|
||||
mlir::RankedTensorType resultType,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/ComputeRegionBuilder.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
|
||||
using namespace mlir;
|
||||
@@ -38,6 +39,30 @@ Value createPaddedInputCompute(Value input,
|
||||
if (inputType == paddedInputType)
|
||||
return input;
|
||||
|
||||
auto producer = inputType.getRank() == 2 && paddedInputType.getRank() == 2
|
||||
? input.getDefiningOp<spatial::SpatGraphComputeBatch>()
|
||||
: spatial::SpatGraphComputeBatch();
|
||||
auto inputFragmentType = producer
|
||||
? spatial::getGraphBatchFragmentType(inputType, producer.getLaneCount())
|
||||
: FailureOr<RankedTensorType>(failure());
|
||||
auto paddedFragmentType = producer
|
||||
? spatial::getGraphBatchFragmentType(paddedInputType, producer.getLaneCount())
|
||||
: FailureOr<RankedTensorType>(failure());
|
||||
if (producer && succeeded(inputFragmentType) && succeeded(paddedFragmentType)) {
|
||||
auto batch = createSpatComputeBatch(rewriter, loc, TypeRange {paddedInputType}, producer.getLaneCount(), {}, input,
|
||||
[&](detail::SpatComputeBatchBodyArgs args) -> LogicalResult {
|
||||
auto fragment = extractGraphBatchPhysicalFragment(
|
||||
rewriter, loc, args.inputs.front(), args.lane, *inputFragmentType);
|
||||
if (failed(fragment))
|
||||
return failure();
|
||||
Value padded = createZeroPaddedTensor(*fragment, *paddedFragmentType, rewriter, loc);
|
||||
publishGraphBatchPhysicalFragment(rewriter, loc, padded, args.outputs.front(), args.lane);
|
||||
return success();
|
||||
});
|
||||
if (succeeded(batch))
|
||||
return batch->getResult(0);
|
||||
}
|
||||
|
||||
auto computeOp = createSpatCompute<1>(rewriter, loc, TypeRange {paddedInputType}, {}, input, [&](Value computeInput) {
|
||||
Value paddedInput = createZeroPaddedTensor(computeInput, paddedInputType, rewriter, loc);
|
||||
spatial::SpatYieldOp::create(rewriter, loc, paddedInput);
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
#include "src/Accelerators/PIM/Common/IR/AffineUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/BiasAddUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/RowStripLayoutUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||
|
||||
using namespace mlir;
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
RankedTensorType getRowStripFragmentType(RankedTensorType logicalType) {
|
||||
return RankedTensorType::get({logicalType.getDimSize(0), logicalType.getDimSize(1), 1, logicalType.getDimSize(3)},
|
||||
logicalType.getElementType(),
|
||||
logicalType.getEncoding());
|
||||
}
|
||||
|
||||
RankedTensorType getRowStripStorageType(RankedTensorType logicalType) {
|
||||
return spatial::getGraphBatchPhysicalResultType(logicalType.getDimSize(2), getRowStripFragmentType(logicalType));
|
||||
}
|
||||
|
||||
std::pair<SmallVector<int64_t>, SmallVector<int64_t>> buildRowStripMetadata(RankedTensorType type) {
|
||||
SmallVector<int64_t> offsets;
|
||||
SmallVector<int64_t> sizes;
|
||||
const int64_t channels = type.getDimSize(1);
|
||||
const int64_t height = type.getDimSize(2);
|
||||
const int64_t width = type.getDimSize(3);
|
||||
offsets.reserve(height * 4);
|
||||
sizes.reserve(height * 4);
|
||||
for (int64_t row = 0; row < height; ++row) {
|
||||
offsets.append({0, 0, row, 0});
|
||||
sizes.append({1, channels, 1, width});
|
||||
}
|
||||
return {offsets, sizes};
|
||||
}
|
||||
|
||||
Value extractRowStripFragment(Value storage,
|
||||
RankedTensorType logicalType,
|
||||
OpFoldResult row,
|
||||
PatternRewriter& rewriter,
|
||||
Location loc) {
|
||||
return *extractGraphBatchPhysicalFragment(rewriter, loc, storage, row, getRowStripFragmentType(logicalType));
|
||||
}
|
||||
|
||||
void insertRowStripFragment(Value fragment,
|
||||
Value output,
|
||||
RankedTensorType logicalType,
|
||||
OpFoldResult row,
|
||||
PatternRewriter& rewriter,
|
||||
Location loc) {
|
||||
assert(fragment.getType() == getRowStripFragmentType(logicalType));
|
||||
assert(output.getType() == getRowStripStorageType(logicalType));
|
||||
auto slot = dyn_cast<Value>(row);
|
||||
assert(slot && "row-strip graph publication requires a dynamic physical slot");
|
||||
publishGraphBatchPhysicalFragment(rewriter, loc, fragment, output, slot);
|
||||
}
|
||||
|
||||
FailureOr<Value> createPerChannelConstantFragment(DenseElementsAttr denseAttr,
|
||||
RankedTensorType fragmentType,
|
||||
PatternRewriter& rewriter) {
|
||||
FailureOr<SmallVector<Attribute>> channelValues = getBiasChannelValues(denseAttr, fragmentType);
|
||||
if (failed(channelValues))
|
||||
return failure();
|
||||
|
||||
SmallVector<Attribute> values;
|
||||
values.reserve(fragmentType.getNumElements());
|
||||
for (int64_t n = 0; n < fragmentType.getDimSize(0); ++n)
|
||||
for (int64_t channel = 0; channel < fragmentType.getDimSize(1); ++channel)
|
||||
for (int64_t h = 0; h < fragmentType.getDimSize(2); ++h)
|
||||
for (int64_t w = 0; w < fragmentType.getDimSize(3); ++w)
|
||||
values.push_back((*channelValues)[channel]);
|
||||
|
||||
auto attr = DenseElementsAttr::get(fragmentType, values);
|
||||
return getOrCreateConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), attr, fragmentType);
|
||||
}
|
||||
|
||||
FailureOr<Value> createRowStripStorageFromRows(Value rows,
|
||||
RankedTensorType logicalType,
|
||||
PatternRewriter& rewriter,
|
||||
Location loc) {
|
||||
auto rowsType = dyn_cast<RankedTensorType>(rows.getType());
|
||||
if (!rowsType || !rowsType.hasStaticShape() || rowsType.getRank() != 2)
|
||||
return failure();
|
||||
if (!logicalType || !logicalType.hasStaticShape() || logicalType.getRank() != 4)
|
||||
return failure();
|
||||
if (logicalType.getDimSize(0) != 1)
|
||||
return failure();
|
||||
if (rowsType.getElementType() != logicalType.getElementType())
|
||||
return failure();
|
||||
|
||||
const int64_t channels = logicalType.getDimSize(1);
|
||||
const int64_t height = logicalType.getDimSize(2);
|
||||
const int64_t width = logicalType.getDimSize(3);
|
||||
if (rowsType.getDimSize(0) != height * width)
|
||||
return failure();
|
||||
if (rowsType.getDimSize(1) != channels)
|
||||
return failure();
|
||||
|
||||
auto rowSliceType = RankedTensorType::get({width, channels}, logicalType.getElementType(), rowsType.getEncoding());
|
||||
auto channelWidthType = RankedTensorType::get({channels, width}, logicalType.getElementType(), rowsType.getEncoding());
|
||||
auto fragmentType = getRowStripFragmentType(logicalType);
|
||||
auto storageType = getRowStripStorageType(logicalType);
|
||||
auto batchOp = createSpatComputeBatch(
|
||||
rewriter, loc, TypeRange {storageType}, height, {}, ValueRange {rows}, [&](detail::SpatComputeBatchBodyArgs args) {
|
||||
Operation* anchorOp = rewriter.getInsertionBlock()->getParentOp();
|
||||
Value rowStart = affineMulConst(rewriter, loc, args.lane, width, anchorOp);
|
||||
SmallVector<OpFoldResult> rowOffsets {rowStart, rewriter.getIndexAttr(0)};
|
||||
SmallVector<OpFoldResult> rowSizes {rewriter.getIndexAttr(width), rewriter.getIndexAttr(channels)};
|
||||
Value rowSlice = tensor::ExtractSliceOp::create(
|
||||
rewriter, loc, rowSliceType, args.inputs.front(), rowOffsets, rowSizes, getUnitStrides(rewriter, 2));
|
||||
Value channelWidth = ONNXTransposeOp::create(
|
||||
rewriter, loc, channelWidthType, rowSlice, rewriter.getI64ArrayAttr({1, 0})).getResult();
|
||||
Value fragment = tensor::ExpandShapeOp::create(
|
||||
rewriter, loc, fragmentType, channelWidth, SmallVector<ReassociationIndices> {{0, 1}, {2, 3}});
|
||||
insertRowStripFragment(fragment, args.outputs.front(), logicalType, args.lane, rewriter, loc);
|
||||
return success();
|
||||
});
|
||||
if (failed(batchOp))
|
||||
return failure();
|
||||
return batchOp->getResult(0);
|
||||
}
|
||||
|
||||
FailureOr<Value>
|
||||
createRowStripAssemblyBlueprint(Value storage, RankedTensorType logicalType, PatternRewriter& rewriter, Location loc) {
|
||||
auto storageType = dyn_cast<RankedTensorType>(storage.getType());
|
||||
if (!storageType || storageType != getRowStripStorageType(logicalType))
|
||||
return failure();
|
||||
|
||||
auto [offsets, sizes] = buildRowStripMetadata(logicalType);
|
||||
int64_t height = logicalType.getDimSize(2);
|
||||
SmallVector<int64_t> operandIndices(height, 0), sourceSlots, sourceOffsets(height, 0), strides(height * 4, 1);
|
||||
for (int64_t row = 0; row < height; ++row)
|
||||
sourceSlots.push_back(row);
|
||||
return spatial::SpatBlueprintOp::create(rewriter, loc, logicalType, storage, ValueRange {},
|
||||
rewriter.getStringAttr("nchw"), rewriter.getStringAttr("nchw_row_strip"),
|
||||
rewriter.getDenseI64ArrayAttr(offsets), rewriter.getDenseI64ArrayAttr(sizes),
|
||||
rewriter.getStringAttr("nchw_row_strip_fragments"), rewriter.getStringAttr("fragment_assembly"),
|
||||
rewriter.getDenseI64ArrayAttr(operandIndices), rewriter.getDenseI64ArrayAttr(sourceSlots),
|
||||
rewriter.getDenseI64ArrayAttr(sourceOffsets), rewriter.getDenseI64ArrayAttr(strides),
|
||||
rewriter.getStringAttr("disjoint"), rewriter.getStringAttr("complete")).getOutput();
|
||||
}
|
||||
|
||||
FailureOr<Value>
|
||||
applyRowStripRelu(Value storage, RankedTensorType logicalType, PatternRewriter& rewriter, Location loc) {
|
||||
auto fragmentType = getRowStripFragmentType(logicalType);
|
||||
auto storageType = getRowStripStorageType(logicalType);
|
||||
auto batchOp = createSpatComputeBatch(rewriter,
|
||||
loc,
|
||||
TypeRange {storageType},
|
||||
logicalType.getDimSize(2),
|
||||
{},
|
||||
ValueRange {storage},
|
||||
[&](detail::SpatComputeBatchBodyArgs args) {
|
||||
Value fragment =
|
||||
extractRowStripFragment(args.inputs.front(), logicalType, args.lane, rewriter, loc);
|
||||
fragment = spatial::SpatReluOp::create(rewriter, loc, fragmentType, fragment).getResult();
|
||||
insertRowStripFragment(
|
||||
fragment, args.outputs.front(), logicalType, args.lane, rewriter, loc);
|
||||
return success();
|
||||
});
|
||||
if (failed(batchOp))
|
||||
return failure();
|
||||
return batchOp->getResult(0);
|
||||
}
|
||||
|
||||
FailureOr<Value>
|
||||
applyRowStripBiasAdd(Value storage, RankedTensorType logicalType, Value bias, PatternRewriter& rewriter, Location loc) {
|
||||
DenseElementsAttr denseAttr;
|
||||
if (!isSupportedBiasAddValue(bias, logicalType, &denseAttr))
|
||||
return failure();
|
||||
auto fragmentType = getRowStripFragmentType(logicalType);
|
||||
auto storageType = getRowStripStorageType(logicalType);
|
||||
auto batchOp = createSpatComputeBatch(rewriter,
|
||||
loc,
|
||||
TypeRange {storageType},
|
||||
logicalType.getDimSize(2),
|
||||
{},
|
||||
ValueRange {storage},
|
||||
[&](detail::SpatComputeBatchBodyArgs args) {
|
||||
Value fragment =
|
||||
extractRowStripFragment(args.inputs.front(), logicalType, args.lane, rewriter, loc);
|
||||
Value constant;
|
||||
if (denseAttr.isSplat()) {
|
||||
constant = getOrCreateConstant(
|
||||
rewriter,
|
||||
rewriter.getInsertionBlock()->getParentOp(),
|
||||
DenseElementsAttr::get(fragmentType, denseAttr.getSplatValue<Attribute>()),
|
||||
fragmentType);
|
||||
}
|
||||
else {
|
||||
FailureOr<Value> perChannel =
|
||||
createPerChannelConstantFragment(denseAttr, fragmentType, rewriter);
|
||||
if (failed(perChannel))
|
||||
return failure();
|
||||
constant = *perChannel;
|
||||
}
|
||||
fragment =
|
||||
spatial::SpatVAddOp::create(rewriter, loc, fragmentType, fragment, constant).getResult();
|
||||
insertRowStripFragment(
|
||||
fragment, args.outputs.front(), logicalType, args.lane, rewriter, loc);
|
||||
return success();
|
||||
});
|
||||
if (failed(batchOp))
|
||||
return failure();
|
||||
return batchOp->getResult(0);
|
||||
}
|
||||
|
||||
} // namespace onnx_mlir
|
||||
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include "mlir/IR/BuiltinAttributes.h"
|
||||
#include "mlir/IR/BuiltinTypes.h"
|
||||
#include "mlir/IR/PatternMatch.h"
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
inline constexpr llvm::StringLiteral kRowStripIndexMap = "nchw_row_strip_fragments";
|
||||
|
||||
struct RowStripPhysicalValue {
|
||||
mlir::Value storage;
|
||||
mlir::RankedTensorType logicalType;
|
||||
llvm::SmallVector<int64_t, 16> fragmentOffsets;
|
||||
llvm::SmallVector<int64_t, 16> fragmentSizes;
|
||||
};
|
||||
|
||||
std::pair<llvm::SmallVector<int64_t>, llvm::SmallVector<int64_t>>
|
||||
buildRowStripMetadata(mlir::RankedTensorType type);
|
||||
|
||||
mlir::RankedTensorType getRowStripFragmentType(mlir::RankedTensorType logicalType);
|
||||
|
||||
mlir::RankedTensorType getRowStripStorageType(mlir::RankedTensorType logicalType);
|
||||
|
||||
llvm::SmallVector<mlir::OpFoldResult> buildRowStripFragmentOffsets(mlir::PatternRewriter& rewriter,
|
||||
mlir::OpFoldResult row);
|
||||
|
||||
llvm::SmallVector<mlir::OpFoldResult> buildRowStripFragmentSizes(mlir::PatternRewriter& rewriter,
|
||||
mlir::RankedTensorType logicalType);
|
||||
|
||||
mlir::Value extractRowStripFragment(mlir::Value storage,
|
||||
mlir::RankedTensorType logicalType,
|
||||
mlir::OpFoldResult row,
|
||||
mlir::PatternRewriter& rewriter,
|
||||
mlir::Location loc);
|
||||
|
||||
void insertRowStripFragment(mlir::Value fragment,
|
||||
mlir::Value output,
|
||||
mlir::RankedTensorType logicalType,
|
||||
mlir::OpFoldResult row,
|
||||
mlir::PatternRewriter& rewriter,
|
||||
mlir::Location loc);
|
||||
|
||||
mlir::FailureOr<mlir::Value> createPerChannelConstantFragment(mlir::DenseElementsAttr denseAttr,
|
||||
mlir::RankedTensorType fragmentType,
|
||||
mlir::PatternRewriter& rewriter);
|
||||
|
||||
mlir::FailureOr<mlir::Value> createRowStripStorageFromRows(mlir::Value rows,
|
||||
mlir::RankedTensorType logicalType,
|
||||
mlir::PatternRewriter& rewriter,
|
||||
mlir::Location loc);
|
||||
|
||||
mlir::FailureOr<mlir::Value> createRowStripAssemblyBlueprint(mlir::Value storage,
|
||||
mlir::RankedTensorType logicalType,
|
||||
mlir::PatternRewriter& rewriter,
|
||||
mlir::Location loc);
|
||||
|
||||
mlir::FailureOr<mlir::Value> applyRowStripRelu(mlir::Value storage,
|
||||
mlir::RankedTensorType logicalType,
|
||||
mlir::PatternRewriter& rewriter,
|
||||
mlir::Location loc);
|
||||
|
||||
mlir::FailureOr<mlir::Value> applyRowStripBiasAdd(mlir::Value storage,
|
||||
mlir::RankedTensorType logicalType,
|
||||
mlir::Value bias,
|
||||
mlir::PatternRewriter& rewriter,
|
||||
mlir::Location loc);
|
||||
|
||||
} // namespace onnx_mlir
|
||||
@@ -9,10 +9,12 @@
|
||||
#include "llvm/ADT/SmallPtrSet.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ShapingUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||
@@ -21,24 +23,7 @@ using namespace mlir;
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
namespace {
|
||||
|
||||
static bool hasStaticUnitStrides(tensor::ExtractSliceOp extractSliceOp) {
|
||||
return llvm::all_of(extractSliceOp.getStaticStrides(), [](int64_t stride) { return stride == 1; });
|
||||
}
|
||||
|
||||
static bool hasConstantIndices(tensor::ExtractOp extractOp) {
|
||||
return llvm::all_of(extractOp.getIndices(), [](Value index) { return matchConstantIndexValue(index).has_value(); });
|
||||
}
|
||||
|
||||
static bool isStaticTensorResult(Operation* op) {
|
||||
return llvm::all_of(op->getResultTypes(), [](Type type) {
|
||||
auto shapedType = dyn_cast<ShapedType>(type);
|
||||
return shapedType && shapedType.hasStaticShape();
|
||||
});
|
||||
}
|
||||
|
||||
static FailureOr<DenseElementsAttr> transposeDenseElements(DenseElementsAttr denseAttr, ArrayRef<int64_t> perms) {
|
||||
FailureOr<DenseElementsAttr> transposeDenseElementsAttr(DenseElementsAttr denseAttr, ArrayRef<int64_t> perms) {
|
||||
auto tensorType = dyn_cast<RankedTensorType>(denseAttr.getType());
|
||||
if (!tensorType)
|
||||
return failure();
|
||||
@@ -59,7 +44,45 @@ static FailureOr<DenseElementsAttr> transposeDenseElements(DenseElementsAttr den
|
||||
|
||||
auto transposedType = RankedTensorType::get(transposedShape, tensorType.getElementType(), tensorType.getEncoding());
|
||||
if (denseAttr.isSplat())
|
||||
return DenseElementsAttr::get(transposedType, denseAttr.getSplatValue<Attribute>());
|
||||
return DenseElementsAttr::getFromRawBuffer(transposedType, denseAttr.getRawData());
|
||||
|
||||
const unsigned elementBitWidth = tensorType.getElementTypeBitWidth();
|
||||
const ArrayRef<char> inputData = denseAttr.getRawData();
|
||||
if (elementBitWidth % 8 == 0) {
|
||||
const size_t elementBytes = elementBitWidth / 8;
|
||||
const size_t expectedBytes = denseAttr.getNumElements() * elementBytes;
|
||||
if (inputData.size() == expectedBytes) {
|
||||
SmallVector<char> transposedData(expectedBytes);
|
||||
if (rank == 2 && perms[0] == 1 && perms[1] == 0) {
|
||||
const int64_t rows = tensorType.getDimSize(0);
|
||||
const int64_t columns = tensorType.getDimSize(1);
|
||||
for (int64_t row = 0; row < rows; ++row)
|
||||
for (int64_t column = 0; column < columns; ++column)
|
||||
std::memcpy(transposedData.data() + (column * rows + row) * elementBytes,
|
||||
inputData.data() + (row * columns + column) * elementBytes,
|
||||
elementBytes);
|
||||
return DenseElementsAttr::getFromRawBuffer(transposedType, transposedData);
|
||||
}
|
||||
|
||||
SmallVector<int64_t> originalStrides = computeRowMajorStrides(tensorType.getShape());
|
||||
SmallVector<int64_t> transposedStrides = computeRowMajorStrides(transposedShape);
|
||||
SmallVector<int64_t> originalIndices(rank);
|
||||
for (int64_t linearIndex = 0; linearIndex < tensorType.getNumElements(); ++linearIndex) {
|
||||
int64_t remaining = linearIndex;
|
||||
for (int64_t dim = 0; dim < rank; ++dim) {
|
||||
originalIndices[dim] = originalStrides.empty() ? 0 : remaining / originalStrides[dim];
|
||||
remaining = originalStrides.empty() ? 0 : remaining % originalStrides[dim];
|
||||
}
|
||||
int64_t transposedLinearIndex = 0;
|
||||
for (int64_t dim = 0; dim < rank; ++dim)
|
||||
transposedLinearIndex += originalIndices[perms[dim]] * transposedStrides[dim];
|
||||
std::memcpy(transposedData.data() + transposedLinearIndex * elementBytes,
|
||||
inputData.data() + linearIndex * elementBytes,
|
||||
elementBytes);
|
||||
}
|
||||
return DenseElementsAttr::getFromRawBuffer(transposedType, transposedData);
|
||||
}
|
||||
}
|
||||
|
||||
SmallVector<Attribute> originalValues(denseAttr.getValues<Attribute>());
|
||||
SmallVector<Attribute> transposedValues(originalValues.size());
|
||||
@@ -84,16 +107,30 @@ static FailureOr<DenseElementsAttr> transposeDenseElements(DenseElementsAttr den
|
||||
return DenseElementsAttr::get(transposedType, transposedValues);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
static bool hasStaticUnitStrides(tensor::ExtractSliceOp extractSliceOp) {
|
||||
return llvm::all_of(extractSliceOp.getStaticStrides(), [](int64_t stride) { return stride == 1; });
|
||||
}
|
||||
|
||||
static bool hasConstantIndices(tensor::ExtractOp extractOp) {
|
||||
return llvm::all_of(extractOp.getIndices(), [](Value index) { return matchConstantIndexValue(index).has_value(); });
|
||||
}
|
||||
|
||||
static bool isStaticTensorResult(Operation* op) {
|
||||
return llvm::all_of(op->getResultTypes(), [](Type type) {
|
||||
auto shapedType = dyn_cast<ShapedType>(type);
|
||||
return shapedType && shapedType.hasStaticShape();
|
||||
});
|
||||
}
|
||||
|
||||
static FailureOr<DenseElementsAttr> reshapeDenseElements(DenseElementsAttr denseAttr, RankedTensorType resultType) {
|
||||
auto sourceType = dyn_cast<RankedTensorType>(denseAttr.getType());
|
||||
if (!sourceType || !resultType || sourceType.getNumElements() != resultType.getNumElements())
|
||||
if (!sourceType || !resultType || sourceType.getNumElements() != resultType.getNumElements()
|
||||
|| sourceType.getElementType() != resultType.getElementType())
|
||||
return failure();
|
||||
|
||||
if (denseAttr.isSplat())
|
||||
return DenseElementsAttr::get(resultType, denseAttr.getSplatValue<Attribute>());
|
||||
|
||||
SmallVector<Attribute> values(denseAttr.getValues<Attribute>());
|
||||
return DenseElementsAttr::get(resultType, values);
|
||||
return DenseElementsAttr::getFromRawBuffer(resultType, denseAttr.getRawData());
|
||||
}
|
||||
|
||||
static FailureOr<DenseElementsAttr> extractSliceDenseElements(DenseElementsAttr denseAttr,
|
||||
@@ -161,7 +198,7 @@ static DenseElementsAttr getHostConstantDenseElementsAttrImpl(Value value, llvm:
|
||||
perm.reserve(transposeOp.getPermAttr().size());
|
||||
for (IntegerAttr attr : transposeOp.getPermAttr().getAsRange<IntegerAttr>())
|
||||
perm.push_back(attr.getInt());
|
||||
auto transposedAttr = transposeDenseElements(inputAttr, perm);
|
||||
auto transposedAttr = transposeDenseElementsAttr(inputAttr, perm);
|
||||
return succeeded(transposedAttr) ? *transposedAttr : nullptr;
|
||||
}
|
||||
|
||||
@@ -171,7 +208,7 @@ static DenseElementsAttr getHostConstantDenseElementsAttrImpl(Value value, llvm:
|
||||
return nullptr;
|
||||
|
||||
SmallVector<int64_t> perm(transposeOp.getPermutation().begin(), transposeOp.getPermutation().end());
|
||||
auto transposedAttr = transposeDenseElements(inputAttr, perm);
|
||||
auto transposedAttr = transposeDenseElementsAttr(inputAttr, perm);
|
||||
return succeeded(transposedAttr) ? *transposedAttr : nullptr;
|
||||
}
|
||||
|
||||
@@ -219,6 +256,9 @@ getCompileTimeSourceImpl(Operation* op, llvm::SmallPtrSetImpl<Operation*>& visit
|
||||
|
||||
chainLength += 1;
|
||||
|
||||
if (!isShapingOnlyOp(op))
|
||||
return std::nullopt;
|
||||
|
||||
if (auto extractOp = dyn_cast<tensor::ExtractOp>(op))
|
||||
return hasConstantIndices(extractOp)
|
||||
? getCompileTimeSourceImpl(extractOp.getTensor().getDefiningOp(), visited, chainLength)
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "mlir/IR/Operation.h"
|
||||
#include "mlir/IR/Value.h"
|
||||
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
struct CompileTimeSource {
|
||||
@@ -19,4 +21,7 @@ bool isCompileTimeOp(mlir::Operation* op);
|
||||
|
||||
mlir::DenseElementsAttr getHostConstDenseElementsAttr(mlir::Value value);
|
||||
|
||||
mlir::FailureOr<mlir::DenseElementsAttr> transposeDenseElementsAttr(
|
||||
mlir::DenseElementsAttr denseAttr, llvm::ArrayRef<int64_t> permutation);
|
||||
|
||||
} // namespace onnx_mlir
|
||||
|
||||
@@ -11,13 +11,16 @@
|
||||
#include "llvm/ADT/SmallPtrSet.h"
|
||||
|
||||
#include "Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp"
|
||||
#include "mlir/Transforms/Passes.h"
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "src/Accelerators/PIM/Common/Support/DebugDump.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/BiasAddUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/RowStripLayoutUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/PlanLowering.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/Transforms/MergeComputeNodes/SpatialDataflowCsvExporter.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/Transforms/MergeComputeNodes/SpatialDataflowCsvExporter.hpp"
|
||||
#include "src/Accelerators/PIM/Pass/PIMPasses.h"
|
||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||
|
||||
@@ -29,14 +32,6 @@ namespace {
|
||||
static constexpr StringLiteral kDenseLayout = "dense_nchw";
|
||||
static constexpr StringLiteral kRowStripLayout = "nchw_row_strip";
|
||||
|
||||
struct RowStripPhysicalValue {
|
||||
Value physicalValue;
|
||||
RankedTensorType logicalType;
|
||||
SmallVector<int64_t, 16> fragmentOffsets;
|
||||
SmallVector<int64_t, 16> fragmentSizes;
|
||||
std::string indexMap;
|
||||
};
|
||||
|
||||
static FailureOr<RowStripPhysicalValue> getRowStripValue(llvm::DenseMap<Value, RowStripPhysicalValue>& rowStripValues,
|
||||
Value value) {
|
||||
auto it = rowStripValues.find(value);
|
||||
@@ -46,112 +41,93 @@ static FailureOr<RowStripPhysicalValue> getRowStripValue(llvm::DenseMap<Value, R
|
||||
}
|
||||
|
||||
static FailureOr<RowStripPhysicalValue> buildRowStripValue(spatial::SpatBlueprintOp blueprint,
|
||||
Value physicalValue) {
|
||||
Value storage) {
|
||||
auto logicalType = dyn_cast<RankedTensorType>(blueprint.getOutput().getType());
|
||||
if (!logicalType)
|
||||
return blueprint.emitOpError("requires ranked logical output type"), failure();
|
||||
RowStripPhysicalValue value;
|
||||
value.physicalValue = physicalValue;
|
||||
value.storage = storage;
|
||||
value.logicalType = logicalType;
|
||||
value.fragmentOffsets.append(blueprint.getFragmentOffsets().begin(), blueprint.getFragmentOffsets().end());
|
||||
value.fragmentSizes.append(blueprint.getFragmentSizes().begin(), blueprint.getFragmentSizes().end());
|
||||
value.indexMap = blueprint.getIndexMap().str();
|
||||
if (blueprint.getIndexMap() != kRowStripIndexMap)
|
||||
return blueprint.emitOpError("requires the canonical row-strip index map"), failure();
|
||||
auto storageType = dyn_cast<RankedTensorType>(storage.getType());
|
||||
if (!storageType || storageType != getRowStripStorageType(logicalType))
|
||||
return blueprint.emitOpError("requires physical row-strip fragment storage"), failure();
|
||||
return value;
|
||||
}
|
||||
|
||||
static FailureOr<Value>
|
||||
lowerRowStripRelu(const RowStripPhysicalValue& input, spatial::SpatReluPlanOp planOp, PatternRewriter& rewriter) {
|
||||
auto packedType = cast<RankedTensorType>(input.physicalValue.getType());
|
||||
auto computeOp =
|
||||
createSpatCompute<1>(rewriter, planOp.getLoc(), TypeRange {packedType}, {}, input.physicalValue, [&](Value x) {
|
||||
auto relu = spatial::SpatReluOp::create(rewriter, planOp.getLoc(), packedType, x);
|
||||
spatial::SpatYieldOp::create(rewriter, planOp.getLoc(), relu.getResult());
|
||||
});
|
||||
return computeOp.getResult(0);
|
||||
return applyRowStripRelu(input.storage, input.logicalType, rewriter, planOp.getLoc());
|
||||
}
|
||||
|
||||
static FailureOr<Value> lowerRowStripBiasAdd(const RowStripPhysicalValue& input,
|
||||
spatial::SpatBiasAddPlanOp planOp,
|
||||
PatternRewriter& rewriter) {
|
||||
return applyRowStripBiasAdd(input.storage, input.logicalType, planOp.getBias(), rewriter, planOp.getLoc());
|
||||
}
|
||||
|
||||
static FailureOr<Value>
|
||||
materializeRowStripToDense(const RowStripPhysicalValue& rowStripValue, Location loc, PatternRewriter& rewriter) {
|
||||
auto packedType = dyn_cast<RankedTensorType>(rowStripValue.physicalValue.getType());
|
||||
if (!packedType || packedType.getRank() != 3 || !packedType.hasStaticShape())
|
||||
return failure();
|
||||
if (rowStripValue.logicalType.getRank() != 4 || !rowStripValue.logicalType.hasStaticShape())
|
||||
return failure();
|
||||
if (rowStripValue.indexMap != "packed_hwc_rows_to_nchw")
|
||||
return failure();
|
||||
|
||||
const int64_t rank = rowStripValue.logicalType.getRank();
|
||||
const int64_t fragmentCount = rowStripValue.fragmentOffsets.size() / rank;
|
||||
const int64_t packedWidth = packedType.getDimSize(1);
|
||||
const int64_t packedChannels = packedType.getDimSize(2);
|
||||
if (fragmentCount != packedType.getDimSize(0))
|
||||
return failure();
|
||||
for (int64_t fragmentIndex = 0; fragmentIndex < fragmentCount; ++fragmentIndex) {
|
||||
if (rowStripValue.fragmentOffsets[fragmentIndex * rank + 0] != 0
|
||||
|| rowStripValue.fragmentOffsets[fragmentIndex * rank + 1] != 0
|
||||
|| rowStripValue.fragmentOffsets[fragmentIndex * rank + 2] != fragmentIndex
|
||||
|| rowStripValue.fragmentOffsets[fragmentIndex * rank + 3] != 0)
|
||||
return failure();
|
||||
if (rowStripValue.fragmentSizes[fragmentIndex * rank + 0] != 1
|
||||
|| rowStripValue.fragmentSizes[fragmentIndex * rank + 1] != packedChannels
|
||||
|| rowStripValue.fragmentSizes[fragmentIndex * rank + 2] != 1
|
||||
|| rowStripValue.fragmentSizes[fragmentIndex * rank + 3] != packedWidth)
|
||||
auto [expectedOffsets, expectedSizes] = buildRowStripMetadata(rowStripValue.logicalType);
|
||||
if (!llvm::equal(rowStripValue.fragmentOffsets, expectedOffsets) || !llvm::equal(rowStripValue.fragmentSizes, expectedSizes))
|
||||
return failure();
|
||||
return createRowStripAssemblyBlueprint(rowStripValue.storage, rowStripValue.logicalType, rewriter, loc);
|
||||
}
|
||||
|
||||
auto packedSliceType =
|
||||
RankedTensorType::get({1, packedWidth, packedChannels}, packedType.getElementType(), packedType.getEncoding());
|
||||
auto expandedType =
|
||||
RankedTensorType::get({1, 1, packedWidth, packedChannels}, packedType.getElementType(), packedType.getEncoding());
|
||||
auto logicalFragmentType =
|
||||
RankedTensorType::get({1, packedChannels, 1, packedWidth}, packedType.getElementType(), packedType.getEncoding());
|
||||
auto batchOp = createSpatComputeBatch(
|
||||
rewriter,
|
||||
loc,
|
||||
TypeRange {rowStripValue.logicalType},
|
||||
fragmentCount,
|
||||
{},
|
||||
ValueRange {rowStripValue.physicalValue},
|
||||
[&](detail::SpatComputeBatchBodyArgs args) {
|
||||
SmallVector<OpFoldResult> packedOffsets {args.lane, rewriter.getIndexAttr(0), rewriter.getIndexAttr(0)};
|
||||
SmallVector<OpFoldResult> packedSizes {
|
||||
rewriter.getIndexAttr(1), rewriter.getIndexAttr(packedWidth), rewriter.getIndexAttr(packedChannels)};
|
||||
Value packedSlice = tensor::ExtractSliceOp::create(
|
||||
rewriter, loc, packedSliceType, args.inputs.front(), packedOffsets, packedSizes, getUnitStrides(rewriter, 3));
|
||||
static FailureOr<Value> lowerDenseBatchBiasAdd(Value input, Value bias, RankedTensorType resultType,
|
||||
PatternRewriter& rewriter, Location loc) {
|
||||
auto producer = input.getDefiningOp<spatial::SpatGraphComputeBatch>();
|
||||
auto inputType = dyn_cast<RankedTensorType>(input.getType());
|
||||
auto biasType = dyn_cast<RankedTensorType>(bias.getType());
|
||||
if (!producer || !inputType || !biasType || !inputType.hasStaticShape() || !biasType.hasStaticShape()
|
||||
|| !resultType.hasStaticShape() || inputType.getDimSize(0) != producer.getLaneCount()
|
||||
|| biasType.getDimSize(0) != producer.getLaneCount() || resultType.getDimSize(0) != producer.getLaneCount())
|
||||
return failure();
|
||||
auto inputFragmentType = spatial::getGraphBatchFragmentType(inputType, producer.getLaneCount());
|
||||
auto outputFragmentType = spatial::getGraphBatchFragmentType(resultType, producer.getLaneCount());
|
||||
if (failed(inputFragmentType) || failed(outputFragmentType) || inputFragmentType->getRank() != biasType.getRank()
|
||||
|| inputFragmentType->getDimSize(0) != 1 || inputFragmentType->getShape().drop_front() != biasType.getShape().drop_front()
|
||||
|| inputFragmentType->getRank() != outputFragmentType->getRank() + 1)
|
||||
return failure();
|
||||
for (auto [inputDim, outputDim] : llvm::zip(inputFragmentType->getShape().drop_front(), outputFragmentType->getShape()))
|
||||
if (outputDim > inputDim)
|
||||
return failure();
|
||||
|
||||
Value expanded = tensor::ExpandShapeOp::create(rewriter,
|
||||
loc,
|
||||
expandedType,
|
||||
packedSlice,
|
||||
SmallVector<ReassociationIndices> {
|
||||
{0, 1},
|
||||
{2},
|
||||
{3}
|
||||
});
|
||||
Value transposeInit =
|
||||
tensor::EmptyOp::create(rewriter, loc, logicalFragmentType.getShape(), logicalFragmentType.getElementType());
|
||||
Value logicalFragment =
|
||||
linalg::TransposeOp::create(rewriter, loc, expanded, transposeInit, SmallVector<int64_t> {0, 3, 1, 2})
|
||||
.getResult()[0];
|
||||
|
||||
SmallVector<OpFoldResult> logicalOffsets {
|
||||
rewriter.getIndexAttr(0), rewriter.getIndexAttr(0), args.lane, rewriter.getIndexAttr(0)};
|
||||
SmallVector<OpFoldResult> logicalSizes {rewriter.getIndexAttr(1),
|
||||
rewriter.getIndexAttr(packedChannels),
|
||||
rewriter.getIndexAttr(1),
|
||||
rewriter.getIndexAttr(packedWidth)};
|
||||
createParallelInsertSliceIntoBatchOutput(rewriter,
|
||||
loc,
|
||||
logicalFragment,
|
||||
args.outputs.front(),
|
||||
logicalOffsets,
|
||||
logicalSizes,
|
||||
getUnitStrides(rewriter, 4));
|
||||
auto batch = createSpatComputeBatch(rewriter, loc, TypeRange {resultType}, producer.getLaneCount(), {}, ValueRange {input, bias},
|
||||
[&](detail::SpatComputeBatchBodyArgs args) -> LogicalResult {
|
||||
FailureOr<Value> fragment = extractGraphBatchPhysicalFragment(rewriter, loc, args.inputs[0], args.lane, *inputFragmentType);
|
||||
if (failed(fragment))
|
||||
return failure();
|
||||
MixedSliceGeometry biasSlice;
|
||||
for (int64_t dim : inputFragmentType->getShape()) {
|
||||
biasSlice.offsets.push_back(biasSlice.offsets.empty() ? OpFoldResult(args.lane) : rewriter.getIndexAttr(0));
|
||||
biasSlice.sizes.push_back(rewriter.getIndexAttr(dim));
|
||||
biasSlice.strides.push_back(rewriter.getIndexAttr(1));
|
||||
}
|
||||
Value biasFragment = extractMixedSliceOrIdentity(rewriter, loc, args.inputs[1], *inputFragmentType, biasSlice);
|
||||
if (!biasFragment)
|
||||
return failure();
|
||||
Value added = spatial::SpatVAddOp::create(rewriter, loc, *inputFragmentType, *fragment, biasFragment);
|
||||
MixedSliceGeometry outputSlice;
|
||||
outputSlice.offsets.assign(inputFragmentType->getRank(), rewriter.getIndexAttr(0));
|
||||
outputSlice.sizes.push_back(rewriter.getIndexAttr(1));
|
||||
outputSlice.strides.assign(inputFragmentType->getRank(), rewriter.getIndexAttr(1));
|
||||
for (int64_t dim : outputFragmentType->getShape())
|
||||
outputSlice.sizes.push_back(rewriter.getIndexAttr(dim));
|
||||
Value output = extractMixedSliceOrIdentity(rewriter, loc, added, *outputFragmentType, outputSlice);
|
||||
if (!output)
|
||||
return failure();
|
||||
publishGraphBatchPhysicalFragment(rewriter, loc, output, args.outputs.front(), args.lane);
|
||||
return success();
|
||||
});
|
||||
if (failed(batchOp))
|
||||
if (failed(batch))
|
||||
return failure();
|
||||
return batchOp->getResult(0);
|
||||
return batch->getResult(0);
|
||||
}
|
||||
|
||||
struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, OperationPass<ModuleOp>> {
|
||||
@@ -194,7 +170,7 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
|
||||
rewriter.setInsertionPoint(planOp);
|
||||
FailureOr<Value> lowered = lowerSelectedConv2DPlan(
|
||||
planOp,
|
||||
succeeded(rowStripInput) ? std::optional<Value> {rowStripInput->physicalValue} : std::nullopt,
|
||||
succeeded(rowStripInput) ? std::optional<Value> {rowStripInput->storage} : std::nullopt,
|
||||
/*emitRowStripLayout=*/true,
|
||||
rewriter);
|
||||
if (failed(lowered)) {
|
||||
@@ -266,6 +242,102 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
|
||||
rewriter.replaceOp(planOp, computeOp.getResults());
|
||||
continue;
|
||||
}
|
||||
if (auto planOp = dyn_cast<spatial::SpatMaxPool2DPlanOp>(&op)) {
|
||||
auto outputBlueprint = llvm::find_if(planOp.getResult().getUsers(), [](Operation* user) {
|
||||
auto blueprint = dyn_cast<spatial::SpatBlueprintOp>(user);
|
||||
return blueprint && blueprint.getPhysicalLayout() == kRowStripLayout;
|
||||
});
|
||||
if (outputBlueprint == planOp.getResult().getUsers().end()) {
|
||||
planOp.emitOpError("selected MaxPool plan requires a row-strip blueprint result");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
FailureOr<RowStripPhysicalValue> input = getRowStripValue(rowStripValues, planOp.getInput());
|
||||
rewriter.setInsertionPoint(planOp);
|
||||
FailureOr<Value> lowered = lowerSelectedMaxPool2DPlan(
|
||||
planOp, succeeded(input) ? std::optional<Value> {input->storage} : std::nullopt, rewriter);
|
||||
if (failed(lowered)) {
|
||||
planOp.emitOpError("failed to lower selected row-strip Spatial MaxPool plan");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
auto blueprint = cast<spatial::SpatBlueprintOp>(*outputBlueprint);
|
||||
FailureOr<RowStripPhysicalValue> output = buildRowStripValue(blueprint, *lowered);
|
||||
if (failed(output)) {
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
rowStripValues[blueprint.getResult()] = *output;
|
||||
eraseAfterLowering.insert(planOp);
|
||||
eraseAfterLowering.insert(blueprint);
|
||||
continue;
|
||||
}
|
||||
if (auto planOp = dyn_cast<spatial::SpatBiasAddPlanOp>(&op)) {
|
||||
if (succeeded(getRowStripValue(rowStripValues, planOp.getInput()))) {
|
||||
auto outputBlueprint = llvm::find_if(planOp.getResult().getUsers(), [](Operation* user) {
|
||||
auto blueprint = dyn_cast<spatial::SpatBlueprintOp>(user);
|
||||
return blueprint && blueprint.getPhysicalLayout() == kRowStripLayout;
|
||||
});
|
||||
if (outputBlueprint == planOp.getResult().getUsers().end()) {
|
||||
planOp.emitOpError("row-strip bias_add plan requires a row-strip blueprint result");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
FailureOr<RowStripPhysicalValue> input = getRowStripValue(rowStripValues, planOp.getInput());
|
||||
rewriter.setInsertionPoint(planOp);
|
||||
FailureOr<Value> lowered = lowerRowStripBiasAdd(*input, planOp, rewriter);
|
||||
if (failed(lowered)) {
|
||||
planOp.emitOpError("failed to lower selected row-strip Spatial bias_add plan");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
auto blueprint = cast<spatial::SpatBlueprintOp>(*outputBlueprint);
|
||||
FailureOr<RowStripPhysicalValue> output = buildRowStripValue(blueprint, *lowered);
|
||||
if (failed(output)) {
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
rowStripValues[blueprint.getResult()] = *output;
|
||||
eraseAfterLowering.insert(planOp);
|
||||
eraseAfterLowering.insert(blueprint);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto resultType = dyn_cast<RankedTensorType>(planOp.getOutput().getType());
|
||||
if (!resultType) {
|
||||
planOp.emitOpError("requires ranked output type");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
rewriter.setInsertionPoint(planOp);
|
||||
FailureOr<Value> denseBias = materializeDenseBiasAddTensor(planOp.getBias(), resultType, rewriter, planOp.getLoc());
|
||||
if (failed(denseBias)) {
|
||||
planOp.emitOpError("failed to materialize dense Conv-style bias");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
if (planOp.getInput().getDefiningOp<spatial::SpatGraphComputeBatch>()) {
|
||||
FailureOr<Value> lowered = lowerDenseBatchBiasAdd(planOp.getInput(), *denseBias, resultType, rewriter, planOp.getLoc());
|
||||
if (succeeded(lowered)) {
|
||||
rewriter.replaceOp(planOp, *lowered);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
auto computeOp = createSpatCompute<2>(rewriter,
|
||||
planOp.getLoc(),
|
||||
planOp.getOutput().getType(),
|
||||
{},
|
||||
ValueRange {planOp.getInput(), *denseBias},
|
||||
[&](Value x, Value y) {
|
||||
auto added = spatial::SpatVAddOp::create(
|
||||
rewriter, planOp.getLoc(), planOp.getOutput().getType(), x, y);
|
||||
spatial::SpatYieldOp::create(rewriter, planOp.getLoc(), added.getResult());
|
||||
});
|
||||
rewriter.replaceOp(planOp, computeOp.getResults());
|
||||
continue;
|
||||
}
|
||||
if (auto materializeOp = dyn_cast<spatial::SpatMaterializeLayoutOp>(&op)) {
|
||||
if (materializeOp.getSourcePhysicalLayout() == kDenseLayout
|
||||
&& materializeOp.getTargetPhysicalLayout() == kDenseLayout) {
|
||||
@@ -295,6 +367,8 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
|
||||
continue;
|
||||
}
|
||||
if (auto blueprintOp = dyn_cast<spatial::SpatBlueprintOp>(&op)) {
|
||||
if (std::optional<StringRef> mode = blueprintOp.getMode(); mode && *mode == "fragment_assembly")
|
||||
continue;
|
||||
if (blueprintOp.getPhysicalLayout() == kDenseLayout) {
|
||||
rewriter.replaceOp(blueprintOp, blueprintOp.getInput());
|
||||
continue;
|
||||
@@ -346,17 +420,25 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
|
||||
RewritePatternSet helperPatterns(ctx);
|
||||
populateGemmPatterns(helperPatterns, ctx);
|
||||
populateTransposePatterns(helperPatterns, ctx);
|
||||
if (failed(applyPartialConversion(moduleOp, helperTarget, std::move(helperPatterns)))) {
|
||||
FrozenRewritePatternSet frozenHelperPatterns(
|
||||
std::move(helperPatterns));
|
||||
SmallVector<Operation*> topLevelHelperOps;
|
||||
funcOp.walk([&](Operation* op) {
|
||||
if (isa<spatial::SpatGraphCompute,
|
||||
spatial::SpatGraphComputeBatch>(op))
|
||||
return WalkResult::skip();
|
||||
if (isa<ONNXGemmOp, ONNXTransposeOp>(op))
|
||||
topLevelHelperOps.push_back(op);
|
||||
return WalkResult::advance();
|
||||
});
|
||||
for (Operation *helper : topLevelHelperOps) {
|
||||
if (failed(applyPartialConversion(
|
||||
helper, helperTarget, frozenHelperPatterns))) {
|
||||
moduleOp.emitError("failed to lower helper ONNX ops emitted by selected Spatial plan lowering");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
FrozenRewritePatternSet nestedHelperPatterns([&] {
|
||||
RewritePatternSet patterns(ctx);
|
||||
populateGemmPatterns(patterns, ctx);
|
||||
populateTransposePatterns(patterns, ctx);
|
||||
return patterns;
|
||||
}());
|
||||
}
|
||||
ConversionTarget nestedHelperTarget(*ctx);
|
||||
nestedHelperTarget.addLegalDialect<spatial::SpatialDialect,
|
||||
tensor::TensorDialect,
|
||||
@@ -372,7 +454,8 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
|
||||
computeLikeOps.push_back(op);
|
||||
});
|
||||
for (Operation* op : computeLikeOps) {
|
||||
if (failed(applyFullConversion(op, nestedHelperTarget, nestedHelperPatterns))) {
|
||||
if (failed(applyFullConversion(
|
||||
op, nestedHelperTarget, frozenHelperPatterns))) {
|
||||
op->emitOpError("failed to lower nested helper ONNX ops emitted by selected Spatial plan lowering");
|
||||
signalPassFailure();
|
||||
return;
|
||||
@@ -384,22 +467,34 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
|
||||
moduleOp.walk([&](Operation* op) {
|
||||
if (isa<ONNXEntryPointOp>(op))
|
||||
return;
|
||||
if (isa<spatial::SpatConv2DPlanOp,
|
||||
if (auto blueprint = dyn_cast<spatial::SpatBlueprintOp>(op)) {
|
||||
if (std::optional<StringRef> mode = blueprint.getMode(); mode && *mode == "fragment_assembly")
|
||||
return;
|
||||
op->emitOpError("planning blueprint must not remain after LowerSpatialPlans");
|
||||
hasIllegalOps = true;
|
||||
} else if (isa<spatial::SpatConv2DPlanOp,
|
||||
spatial::SpatBiasAddPlanOp,
|
||||
spatial::SpatReluPlanOp,
|
||||
spatial::SpatBlueprintOp,
|
||||
spatial::SpatMaxPool2DPlanOp,
|
||||
spatial::SpatMaterializeLayoutOp>(op)
|
||||
|| op->getDialect()->getNamespace() == "onnx") {
|
||||
op->emitOpError("operation must not remain after LowerSpatialPlans");
|
||||
hasIllegalOps = true;
|
||||
}
|
||||
});
|
||||
|
||||
PassManager canonicalizationPM(ctx);
|
||||
canonicalizationPM.addPass(createCanonicalizerPass());
|
||||
if (failed(canonicalizationPM.run(moduleOp)))
|
||||
moduleOp.emitWarning("failed to run LowerSpatialPlansPass canonicalization; continuing");
|
||||
|
||||
if (hasIllegalOps) {
|
||||
signalPassFailure();
|
||||
} else {
|
||||
dumpModule(moduleOp, "spatial1_graph");
|
||||
spatial::SpatialDataflowExportStage exportMode = spatial::getSpatialDataflowExportStage();
|
||||
if (spatial::shouldExportSpatialDataflowStage(exportMode, spatial::SpatialDataflowExportStage::Pre)
|
||||
&& failed(spatial::exportSpatialDataflowCsvPre(funcOp))) {
|
||||
if (spatial::shouldExportSpatialDataflowStage(exportMode, spatial::SpatialDataflowExportStage::Spatial1)
|
||||
&& failed(spatial::exportSpatialDataflowCsvGraph(funcOp, "spatial1_graph"))) {
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "Common/Common.hpp"
|
||||
#include "Common/PimCommon.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/ComputeRegionBuilder.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||
@@ -45,11 +46,13 @@ static void populateEmptyFunction(func::FuncOp funcOp) {
|
||||
SmallVector<spatial::SpatGraphCompute> computes(funcOp.getOps<spatial::SpatGraphCompute>());
|
||||
SmallVector<spatial::SpatGraphComputeBatch> computeBatches(funcOp.getOps<spatial::SpatGraphComputeBatch>());
|
||||
SmallVector<spatial::SpatConv2DPlanOp> convPlans(funcOp.getOps<spatial::SpatConv2DPlanOp>());
|
||||
SmallVector<spatial::SpatBiasAddPlanOp> biasAddPlans(funcOp.getOps<spatial::SpatBiasAddPlanOp>());
|
||||
SmallVector<spatial::SpatReluPlanOp> reluPlans(funcOp.getOps<spatial::SpatReluPlanOp>());
|
||||
SmallVector<spatial::SpatMaxPool2DPlanOp> maxPoolPlans(funcOp.getOps<spatial::SpatMaxPool2DPlanOp>());
|
||||
SmallVector<spatial::SpatBlueprintOp> blueprints(funcOp.getOps<spatial::SpatBlueprintOp>());
|
||||
SmallVector<spatial::SpatMaterializeLayoutOp> materializers(funcOp.getOps<spatial::SpatMaterializeLayoutOp>());
|
||||
if (!computes.empty() || !computeBatches.empty() || !convPlans.empty() || !reluPlans.empty() || !blueprints.empty()
|
||||
|| !materializers.empty()) {
|
||||
if (!computes.empty() || !computeBatches.empty() || !convPlans.empty() || !biasAddPlans.empty() || !reluPlans.empty()
|
||||
|| !maxPoolPlans.empty() || !blueprints.empty() || !materializers.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -65,9 +68,9 @@ static void populateEmptyFunction(func::FuncOp funcOp) {
|
||||
sourceLocs.push_back(source.getLoc());
|
||||
}
|
||||
|
||||
auto newCompute = spatial::SpatGraphCompute::create(
|
||||
rewriter, returnOp.getLoc(), returnOp.getOperandTypes(), funcOp.getArguments(), {}, {});
|
||||
auto* newBlock = rewriter.createBlock(&newCompute.getBody(), newCompute.getBody().end(), sourceTypes, sourceLocs);
|
||||
auto newCompute = createEmptySpatGraphCompute(
|
||||
rewriter, returnOp.getLoc(), returnOp.getOperandTypes(), {}, funcOp.getArguments(), sourceTypes, sourceLocs);
|
||||
auto* newBlock = &newCompute.getBody().front();
|
||||
for (auto [blockArg, computeArg] : llvm::zip(newBlock->getArguments(), newCompute.getOperands()))
|
||||
mapper.map(computeArg, blockArg);
|
||||
newCompute.getProperties().setOperandSegmentSizes({0, static_cast<int>(sourceTypes.size())});
|
||||
@@ -174,11 +177,6 @@ void ONNXToSpatialPass::runOnOperation() {
|
||||
arith::ArithDialect,
|
||||
scf::SCFDialect>();
|
||||
|
||||
PassManager cleanupPM(ctx);
|
||||
cleanupPM.addPass(createCanonicalizerPass());
|
||||
if (failed(cleanupPM.run(moduleOp)))
|
||||
moduleOp.emitWarning("failed to run ONNX-to-Spatial canonicalization cleanup; continuing");
|
||||
|
||||
annotateWeightsConstants(*entryFunc);
|
||||
|
||||
if (failed(verifyLogicalSpatialGraphInvariants(*entryFunc))) {
|
||||
@@ -214,13 +212,18 @@ void ONNXToSpatialPass::runOnOperation() {
|
||||
|
||||
populateEmptyFunction(*entryFunc);
|
||||
|
||||
PassManager canonicalizationPM(ctx);
|
||||
canonicalizationPM.addPass(createCanonicalizerPass());
|
||||
if (failed(canonicalizationPM.run(moduleOp)))
|
||||
moduleOp.emitWarning("failed to run ONNXToSpatial canonicalization; continuing");
|
||||
|
||||
dumpModule(moduleOp, "spatial0");
|
||||
|
||||
if (failed(verifyLogicalSpatialGraphInvariants(*entryFunc))) {
|
||||
moduleOp.emitError("logical Spatial graph verification failed after ONNX-to-Spatial");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
dumpModule(moduleOp, "spatial0");
|
||||
|
||||
if (failed(verifyONNXToSpatial(*entryFunc))) {
|
||||
moduleOp.emitError("ONNX-to-Spatial host legality verification failed");
|
||||
signalPassFailure();
|
||||
|
||||
@@ -56,13 +56,18 @@ bool isLegalExternalCapture(Value value, Region& region) {
|
||||
return definingOp && definingOp->hasTrait<OpTrait::ConstantLike>();
|
||||
}
|
||||
|
||||
bool isRecordedDeferredCommunicationSource(Operation* op, Value value) {
|
||||
auto transfer = dyn_cast<spatial::SpatDeferredCommunicationOp>(op);
|
||||
return transfer && llvm::is_contained(transfer.getSources(), value);
|
||||
}
|
||||
|
||||
template <typename ComputeOpTy>
|
||||
void verifyComputeBodyCaptures(ComputeOpTy compute, StringRef kind, pim::CappedDiagnosticReporter& diagnostics) {
|
||||
Region& body = compute.getBody();
|
||||
body.walk([&](Operation* nestedOp) {
|
||||
for (OpOperand& operand : nestedOp->getOpOperands()) {
|
||||
Value value = operand.get();
|
||||
if (isLegalExternalCapture(value, body))
|
||||
if (isLegalExternalCapture(value, body) || isRecordedDeferredCommunicationSource(nestedOp, value))
|
||||
continue;
|
||||
|
||||
Operation* definingOp = value.getDefiningOp();
|
||||
@@ -90,21 +95,29 @@ bool isLegalHostBackedValue(Value value) {
|
||||
return definingOp->getDialect()->getNamespace() != "spat";
|
||||
}
|
||||
|
||||
bool isScheduledPhase1Value(Value value) {
|
||||
Operation* definingOp = value.getDefiningOp();
|
||||
return isa_and_nonnull<spatial::SpatScheduledCompute, spatial::SpatScheduledComputeBatch>(definingOp);
|
||||
}
|
||||
|
||||
template <typename ComputeOpTy>
|
||||
void verifyScheduledInputs(ComputeOpTy compute,
|
||||
bool allowChannelReceiveInputs,
|
||||
StringRef kind,
|
||||
pim::CappedDiagnosticReporter& diagnostics) {
|
||||
for (auto [inputIndex, input] : llvm::enumerate(compute.getInputs())) {
|
||||
size_t currentInputIndex = inputIndex;
|
||||
Operation* definingOp = input.getDefiningOp();
|
||||
if (allowChannelReceiveInputs && isa_and_nonnull<spatial::SpatChannelReceiveOp>(definingOp))
|
||||
continue;
|
||||
if (isScheduledPhase1Value(input))
|
||||
continue;
|
||||
if (isLegalHostBackedValue(input))
|
||||
continue;
|
||||
|
||||
diagnostics.report(compute.getOperation(), [&](Operation* illegalOp) {
|
||||
InFlightDiagnostic diag = illegalOp->emitOpError()
|
||||
<< kPhaseMarker << " " << kind << " input #" << inputIndex
|
||||
<< kPhaseMarker << " " << kind << " input #" << currentInputIndex
|
||||
<< (allowChannelReceiveInputs ? " must come from the host or explicit spat.channel_receive"
|
||||
: " must come from the host");
|
||||
if (definingOp)
|
||||
@@ -132,7 +145,9 @@ void verifyLogicalTopLevelOps(func::FuncOp funcOp, pim::CappedDiagnosticReporter
|
||||
spatial::SpatGraphCompute,
|
||||
spatial::SpatGraphComputeBatch,
|
||||
spatial::SpatConv2DPlanOp,
|
||||
spatial::SpatBiasAddPlanOp,
|
||||
spatial::SpatReluPlanOp,
|
||||
spatial::SpatMaxPool2DPlanOp,
|
||||
spatial::SpatBlueprintOp,
|
||||
spatial::SpatMaterializeLayoutOp>(&op)) {
|
||||
continue;
|
||||
@@ -162,9 +177,9 @@ void verifyLogicalTopLevelOps(func::FuncOp funcOp, pim::CappedDiagnosticReporter
|
||||
|
||||
void verifyScheduledTopLevelOps(func::FuncOp funcOp, pim::CappedDiagnosticReporter& diagnostics) {
|
||||
for (Operation& op : funcOp.getOps()) {
|
||||
if (isa<spatial::SpatGraphCompute, spatial::SpatGraphComputeBatch>(&op)) {
|
||||
if (isa<spatial::SpatChannelSendOp, spatial::SpatChannelReceiveOp>(&op)) {
|
||||
diagnostics.report(&op, [&](Operation* illegalOp) {
|
||||
illegalOp->emitOpError() << kPhaseMarker << " graph Spatial compute op remained after merge materialization";
|
||||
illegalOp->emitOpError() << kPhaseMarker << " real channel communication is not allowed in scheduled phase 1";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
||||
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/BiasAddUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
@@ -47,38 +47,28 @@ static FailureOr<Value> materializeBroadcastedConstantTensor(Value value,
|
||||
return failure();
|
||||
|
||||
const int64_t rankOffset = static_cast<int64_t>(resultShape.size() - sourceShape.size());
|
||||
for (int64_t i = 0; i < static_cast<int64_t>(resultShape.size()); ++i) {
|
||||
const int64_t sourceIndex = i - rankOffset;
|
||||
const int64_t sourceDim = sourceIndex < 0 ? 1 : sourceShape[sourceIndex];
|
||||
const int64_t resultDim = resultShape[i];
|
||||
if (sourceDim != 1 && sourceDim != resultDim)
|
||||
return failure();
|
||||
}
|
||||
|
||||
SmallVector<Attribute> sourceValues(denseAttr.getValues<Attribute>());
|
||||
SmallVector<int64_t> sourceStrides = computeRowMajorStrides(sourceShape);
|
||||
SmallVector<int64_t> resultStrides = computeRowMajorStrides(resultShape);
|
||||
|
||||
SmallVector<Attribute> sourceValues(denseAttr.getValues<Attribute>());
|
||||
SmallVector<Attribute> resultValues;
|
||||
resultValues.reserve(resultType.getNumElements());
|
||||
|
||||
for (int64_t flatIndex = 0; flatIndex < resultType.getNumElements(); ++flatIndex) {
|
||||
int64_t remaining = flatIndex;
|
||||
int64_t sourceFlatIndex = 0;
|
||||
|
||||
for (int64_t i = 0; i < static_cast<int64_t>(resultShape.size()); ++i) {
|
||||
const int64_t resultIndex = resultStrides.empty() ? 0 : remaining / resultStrides[i];
|
||||
remaining = resultStrides.empty() ? 0 : remaining % resultStrides[i];
|
||||
|
||||
const int64_t sourceIndex = i - rankOffset;
|
||||
if (sourceIndex < 0)
|
||||
continue;
|
||||
|
||||
const int64_t sourceDim = sourceShape[sourceIndex];
|
||||
const int64_t resultDim = resultShape[i];
|
||||
if (sourceDim != 1 && sourceDim != resultDim)
|
||||
return failure();
|
||||
const int64_t mappedIndex = sourceDim == 1 ? 0 : resultIndex;
|
||||
sourceFlatIndex += mappedIndex * sourceStrides[sourceIndex];
|
||||
}
|
||||
|
||||
resultValues.push_back(sourceValues[sourceFlatIndex]);
|
||||
}
|
||||
|
||||
@@ -106,7 +96,7 @@ static FailureOr<Value> materializeReciprocalTensor(Value value,
|
||||
if (failed(broadcastedValue))
|
||||
return failure();
|
||||
|
||||
auto denseAttr = dyn_cast<DenseFPElementsAttr>(getDenseConstantAttr(*broadcastedValue));
|
||||
auto denseAttr = dyn_cast<DenseFPElementsAttr>(getHostConstDenseElementsAttr(*broadcastedValue));
|
||||
if (!denseAttr)
|
||||
return failure();
|
||||
|
||||
@@ -185,10 +175,45 @@ struct DivToSpatialCompute : OpConversionPattern<ONNXDivOp> {
|
||||
}
|
||||
};
|
||||
|
||||
struct AddToSpatialCompute : OpConversionPattern<ONNXAddOp> {
|
||||
using OpConversionPattern::OpConversionPattern;
|
||||
|
||||
LogicalResult
|
||||
matchAndRewrite(ONNXAddOp op, ONNXAddOpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override {
|
||||
auto resultType = dyn_cast<RankedTensorType>(op.getResult().getType());
|
||||
if (!resultType || !resultType.hasStaticShape())
|
||||
return failure();
|
||||
|
||||
FailureOr<BiasAddPlanCandidate> candidate =
|
||||
classifyBiasAddPlanCandidate(adaptor.getA(), adaptor.getB(), resultType);
|
||||
if (succeeded(candidate)) {
|
||||
auto plan = spatial::SpatBiasAddPlanOp::create(
|
||||
rewriter, op.getLoc(), resultType, candidate->data, candidate->bias, rewriter.getStringAttr("nchw"));
|
||||
rewriter.replaceOp(op, plan.getResult());
|
||||
return success();
|
||||
}
|
||||
|
||||
auto lhs = prepareElementwiseOperand(adaptor.getA(), resultType, rewriter, op.getLoc());
|
||||
if (failed(lhs))
|
||||
return failure();
|
||||
auto rhs = prepareElementwiseOperand(adaptor.getB(), resultType, rewriter, op.getLoc());
|
||||
if (failed(rhs))
|
||||
return failure();
|
||||
|
||||
auto computeOp =
|
||||
createSpatCompute<2>(rewriter, op.getLoc(), resultType, {}, ValueRange {*lhs, *rhs}, [&](Value x, Value y) {
|
||||
auto loweredOp = spatial::SpatVAddOp::create(rewriter, op.getLoc(), resultType, x, y);
|
||||
spatial::SpatYieldOp::create(rewriter, op.getLoc(), loweredOp.getResult());
|
||||
});
|
||||
rewriter.replaceOp(op, computeOp);
|
||||
return success();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void populateElementwisePatterns(RewritePatternSet& patterns, MLIRContext* ctx) {
|
||||
patterns.add<BinaryElementwiseToSpatialCompute<ONNXAddOp, spatial::SpatVAddOp>>(ctx);
|
||||
patterns.add<AddToSpatialCompute>(ctx);
|
||||
patterns.add<BinaryElementwiseToSpatialCompute<ONNXSubOp, spatial::SpatVSubOp>>(ctx);
|
||||
patterns.add<BinaryElementwiseToSpatialCompute<ONNXMulOp, spatial::SpatVMulOp>>(ctx);
|
||||
patterns.add<DivToSpatialCompute>(ctx);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "src/Accelerators/PIM/Common/IR/AffineUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/TensorSliceUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "src/Accelerators/PIM/Common/Support/Diagnostics.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||
@@ -251,10 +252,7 @@ static FailureOr<spatial::SpatComputeBatch> createVmmBatch(Value a,
|
||||
rewriter, loc, args.weights.front(), bTileType, bOffsets, bSizes, unitStrides);
|
||||
Value piece = spatial::SpatVMMOp::create(rewriter, loc, pieceType, bTile, aTile).getResult();
|
||||
|
||||
SmallVector<OpFoldResult> pieceOffsets {args.lane, rewriter.getIndexAttr(0)};
|
||||
SmallVector<OpFoldResult> pieceSizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(crossbarSize.getValue())};
|
||||
createParallelInsertSliceIntoBatchOutput(
|
||||
rewriter, loc, piece, args.outputs.front(), pieceOffsets, pieceSizes, unitStrides);
|
||||
publishGraphBatchPhysicalFragment(rewriter, loc, piece, args.outputs.front(), args.lane);
|
||||
});
|
||||
if (failed(batchOp))
|
||||
return failure();
|
||||
@@ -401,11 +399,7 @@ static FailureOr<spatial::SpatComputeBatch> createVvdmulBatch(Value a,
|
||||
Value bVector = extractDynamicGemmBColumn(args.inputs[1], column, vectorType, rewriter, loc);
|
||||
Value scalar = spatial::SpatVVDMulOp::create(rewriter, loc, scalarType, aVector, bVector).getResult();
|
||||
|
||||
SmallVector<OpFoldResult> outputOffsets {args.lane, rewriter.getIndexAttr(0)};
|
||||
SmallVector<OpFoldResult> scalarSizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
||||
SmallVector<OpFoldResult> unitStrides = getUnitStrides(rewriter, 2);
|
||||
createParallelInsertSliceIntoBatchOutput(
|
||||
rewriter, loc, scalar, args.outputs.front(), outputOffsets, scalarSizes, unitStrides);
|
||||
publishGraphBatchPhysicalFragment(rewriter, loc, scalar, args.outputs.front(), args.lane);
|
||||
});
|
||||
if (failed(batchOp))
|
||||
return failure();
|
||||
@@ -447,15 +441,14 @@ static FailureOr<spatial::SpatCompute> createDynamicGemmOutputCompute(Value scal
|
||||
Value row = createDynamicGemmBatchRow(lane, numOutCols, rewriter, nestedLoc);
|
||||
Value column =
|
||||
onnx_mlir::affineModConst(rewriter, nestedLoc, lane, numOutCols, rewriter.getInsertionBlock()->getParentOp());
|
||||
SmallVector<OpFoldResult> scalarOffsets {lane, rewriter.getIndexAttr(0)};
|
||||
SmallVector<OpFoldResult> scalarSizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
||||
SmallVector<OpFoldResult> unitStrides {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
||||
Value scalar = tensor::ExtractSliceOp::create(
|
||||
rewriter, nestedLoc, scalarType, pieces, scalarOffsets, scalarSizes, unitStrides)
|
||||
.getResult();
|
||||
FailureOr<Value> scalar = extractGraphBatchPhysicalFragment(rewriter, nestedLoc, pieces, lane, scalarType);
|
||||
if (failed(scalar))
|
||||
return failure();
|
||||
if (alpha != 1.0f) {
|
||||
Value alphaTensor = createScalarTensorConstant(scalarType, alpha, rewriter, nestedLoc);
|
||||
scalar = spatial::SpatVMulOp::create(rewriter, nestedLoc, scalarType, scalar, alphaTensor).getResult();
|
||||
*scalar = spatial::SpatVMulOp::create(rewriter, nestedLoc, scalarType, *scalar, alphaTensor).getResult();
|
||||
}
|
||||
if (biasArg) {
|
||||
Value biasScalar =
|
||||
@@ -465,11 +458,11 @@ static FailureOr<spatial::SpatCompute> createDynamicGemmOutputCompute(Value scal
|
||||
biasScalar =
|
||||
spatial::SpatVMulOp::create(rewriter, nestedLoc, scalarType, biasScalar, betaTensor).getResult();
|
||||
}
|
||||
scalar = spatial::SpatVAddOp::create(rewriter, nestedLoc, scalarType, scalar, biasScalar).getResult();
|
||||
*scalar = spatial::SpatVAddOp::create(rewriter, nestedLoc, scalarType, *scalar, biasScalar).getResult();
|
||||
}
|
||||
SmallVector<OpFoldResult> outputOffsets {row, column};
|
||||
Value outputNext =
|
||||
tensor::InsertSliceOp::create(rewriter, nestedLoc, scalar, outputAcc, outputOffsets, scalarSizes, unitStrides)
|
||||
tensor::InsertSliceOp::create(rewriter, nestedLoc, *scalar, outputAcc, outputOffsets, scalarSizes, unitStrides)
|
||||
.getResult();
|
||||
yielded.push_back(outputNext);
|
||||
return success();
|
||||
@@ -505,14 +498,13 @@ static Value extractReductionPiece(Value partialPiecesArg,
|
||||
int64_t numOutRows,
|
||||
ConversionPatternRewriter& rewriter,
|
||||
Location loc) {
|
||||
SmallVector<OpFoldResult> unitStrides {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
||||
SmallVector<OpFoldResult> pieceSizes {rewriter.getIndexAttr(numOutRows),
|
||||
rewriter.getIndexAttr(crossbarSize.getValue())};
|
||||
SmallVector<OpFoldResult> unitStrides {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
||||
SmallVector<OpFoldResult> pieceSizes {rewriter.getIndexAttr(numOutRows), rewriter.getIndexAttr(1), rewriter.getIndexAttr(crossbarSize.getValue())};
|
||||
SmallVector<OpFoldResult> pieceOffsets {
|
||||
createPartialGroupOffset(hSlice, kSlice, numKSlices, numOutRows, rewriter, loc), rewriter.getIndexAttr(0)};
|
||||
return tensor::ExtractSliceOp::create(
|
||||
rewriter, loc, pieceType, partialPiecesArg, pieceOffsets, pieceSizes, unitStrides)
|
||||
.getResult();
|
||||
createPartialGroupOffset(hSlice, kSlice, numKSlices, numOutRows, rewriter, loc), rewriter.getIndexAttr(0), rewriter.getIndexAttr(0)};
|
||||
return extractMixedSliceOrIdentity(
|
||||
rewriter, loc, partialPiecesArg, pieceType,
|
||||
{pieceOffsets, pieceSizes, unitStrides});
|
||||
}
|
||||
|
||||
static Value reducePartialPiecesForHSlice(Value partialPiecesArg,
|
||||
@@ -730,7 +722,7 @@ LogicalResult GemmToSpatialComputes::matchAndRewrite(ONNXGemmOp gemmOp,
|
||||
return failure();
|
||||
}
|
||||
|
||||
auto scalarPiecesType = RankedTensorType::get({laneCount64, 1}, outType.getElementType());
|
||||
auto scalarPiecesType = spatial::getGraphBatchPhysicalResultType(laneCount64, RankedTensorType::get({1, 1}, outType.getElementType()));
|
||||
auto batchOp = createVvdmulBatch(a, b, aType, bType, scalarPiecesType, outType, rewriter, loc);
|
||||
if (failed(batchOp))
|
||||
return failure();
|
||||
@@ -802,8 +794,8 @@ LogicalResult GemmToSpatialComputes::matchAndRewrite(ONNXGemmOp gemmOp,
|
||||
return failure();
|
||||
}
|
||||
|
||||
auto partialPiecesType =
|
||||
RankedTensorType::get({laneCount64, static_cast<int64_t>(crossbarSize.getValue())}, outType.getElementType());
|
||||
auto partialPiecesType = spatial::getGraphBatchPhysicalResultType(
|
||||
laneCount64, RankedTensorType::get({1, static_cast<int64_t>(crossbarSize.getValue())}, outType.getElementType()));
|
||||
auto batchOp =
|
||||
createVmmBatch(a, b, aType, paddedBType, partialPiecesType, numOutRows, numKSlices, numOutHSlices, rewriter, loc);
|
||||
if (failed(batchOp))
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "src/Accelerators/PIM/Common/IR/AffineUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/TensorSliceUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||
@@ -299,22 +300,14 @@ static Value extractBatchedATile(Value a,
|
||||
RankedTensorType aTileType,
|
||||
PatternRewriter& rewriter,
|
||||
Location loc) {
|
||||
auto aSliceType = RankedTensorType::get({1, 1, aTileType.getDimSize(1)}, aTileType.getElementType());
|
||||
Value sourceBatchIndex =
|
||||
mapOutputBatchIndexToSourceBatchIndex(outputBatchIndex, sourceBatchShape, outputBatchShape, rewriter, loc);
|
||||
SmallVector<OpFoldResult> offsets {OpFoldResult(sourceBatchIndex), row, kOffset};
|
||||
SmallVector<OpFoldResult> sizes {
|
||||
rewriter.getIndexAttr(1), rewriter.getIndexAttr(1), rewriter.getIndexAttr(aTileType.getDimSize(1))};
|
||||
auto slice =
|
||||
tensor::ExtractSliceOp::create(rewriter, loc, aSliceType, a, offsets, sizes, getUnitStrides(rewriter, 3));
|
||||
return tensor::CollapseShapeOp::create(rewriter,
|
||||
loc,
|
||||
aTileType,
|
||||
slice,
|
||||
SmallVector<ReassociationIndices> {
|
||||
{0, 1},
|
||||
{2}
|
||||
});
|
||||
return extractMixedSliceOrIdentity(
|
||||
rewriter, loc, a, aTileType,
|
||||
{offsets, sizes, getUnitStrides(rewriter, 3)});
|
||||
}
|
||||
|
||||
static Value extractBatchedBTile(Value b,
|
||||
@@ -326,24 +319,15 @@ static Value extractBatchedBTile(Value b,
|
||||
RankedTensorType bTileType,
|
||||
PatternRewriter& rewriter,
|
||||
Location loc) {
|
||||
auto bSliceType =
|
||||
RankedTensorType::get({1, bTileType.getDimSize(0), bTileType.getDimSize(1)}, bTileType.getElementType());
|
||||
Value sourceBatchIndex =
|
||||
mapOutputBatchIndexToSourceBatchIndex(outputBatchIndex, sourceBatchShape, outputBatchShape, rewriter, loc);
|
||||
SmallVector<OpFoldResult> offsets {OpFoldResult(sourceBatchIndex), kOffset, hOffset};
|
||||
SmallVector<OpFoldResult> sizes {rewriter.getIndexAttr(1),
|
||||
rewriter.getIndexAttr(bTileType.getDimSize(0)),
|
||||
rewriter.getIndexAttr(bTileType.getDimSize(1))};
|
||||
auto slice =
|
||||
tensor::ExtractSliceOp::create(rewriter, loc, bSliceType, b, offsets, sizes, getUnitStrides(rewriter, 3));
|
||||
return tensor::CollapseShapeOp::create(rewriter,
|
||||
loc,
|
||||
bTileType,
|
||||
slice,
|
||||
SmallVector<ReassociationIndices> {
|
||||
{0, 1},
|
||||
{2}
|
||||
});
|
||||
return extractMixedSliceOrIdentity(
|
||||
rewriter, loc, b, bTileType,
|
||||
{offsets, sizes, getUnitStrides(rewriter, 3)});
|
||||
}
|
||||
|
||||
static Value getBatchLaneIndex(
|
||||
@@ -398,10 +382,7 @@ static FailureOr<spatial::SpatComputeBatch> createBatchedVmmBatch(Value a,
|
||||
args.weights.front(), bBatchShape, outputBatchShape, batch, kOffset, hOffset, bTileType, rewriter, loc);
|
||||
Value piece = spatial::SpatVMMOp::create(rewriter, loc, pieceType, bTile, aTile).getResult();
|
||||
|
||||
SmallVector<OpFoldResult> pieceOffsets {args.lane, rewriter.getIndexAttr(0)};
|
||||
SmallVector<OpFoldResult> pieceSizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(crossbarSize.getValue())};
|
||||
createParallelInsertSliceIntoBatchOutput(
|
||||
rewriter, loc, piece, args.outputs.front(), pieceOffsets, pieceSizes, getUnitStrides(rewriter, 2));
|
||||
publishGraphBatchPhysicalFragment(rewriter, loc, piece, args.outputs.front(), args.lane);
|
||||
});
|
||||
if (failed(batchOp))
|
||||
return failure();
|
||||
@@ -451,22 +432,14 @@ static Value extractDynamicBatchedRowVector(Value matrix,
|
||||
RankedTensorType vectorType,
|
||||
PatternRewriter& rewriter,
|
||||
Location loc) {
|
||||
auto rowSliceType = RankedTensorType::get({1, 1, vectorType.getDimSize(1)}, vectorType.getElementType());
|
||||
Value sourceBatchIndex =
|
||||
mapOutputBatchIndexToSourceBatchIndex(outputBatchIndex, sourceBatchShape, outputBatchShape, rewriter, loc);
|
||||
SmallVector<OpFoldResult> offsets {OpFoldResult(sourceBatchIndex), row, rewriter.getIndexAttr(0)};
|
||||
SmallVector<OpFoldResult> sizes {
|
||||
rewriter.getIndexAttr(1), rewriter.getIndexAttr(1), rewriter.getIndexAttr(vectorType.getDimSize(1))};
|
||||
auto rowSlice =
|
||||
tensor::ExtractSliceOp::create(rewriter, loc, rowSliceType, matrix, offsets, sizes, getUnitStrides(rewriter, 3));
|
||||
return tensor::CollapseShapeOp::create(rewriter,
|
||||
loc,
|
||||
vectorType,
|
||||
rowSlice,
|
||||
SmallVector<ReassociationIndices> {
|
||||
{0, 1},
|
||||
{2}
|
||||
});
|
||||
return extractMixedSliceOrIdentity(
|
||||
rewriter, loc, matrix, vectorType,
|
||||
{offsets, sizes, getUnitStrides(rewriter, 3)});
|
||||
}
|
||||
|
||||
static FailureOr<spatial::SpatComputeBatch> createBatchedVvdmulBatch(Value a,
|
||||
@@ -506,10 +479,7 @@ static FailureOr<spatial::SpatComputeBatch> createBatchedVvdmulBatch(Value a,
|
||||
Value bVector = extractDynamicBatchedBColumn(
|
||||
args.inputs[1], bBatchShape, outputBatchShape, batch, column, vectorType, rewriter, loc);
|
||||
Value scalar = spatial::SpatVVDMulOp::create(rewriter, loc, scalarType, aVector, bVector).getResult();
|
||||
SmallVector<OpFoldResult> outputOffsets {args.lane, rewriter.getIndexAttr(0)};
|
||||
SmallVector<OpFoldResult> scalarSizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
||||
createParallelInsertSliceIntoBatchOutput(
|
||||
rewriter, loc, scalar, args.outputs.front(), outputOffsets, scalarSizes, getUnitStrides(rewriter, 2));
|
||||
publishGraphBatchPhysicalFragment(rewriter, loc, scalar, args.outputs.front(), args.lane);
|
||||
});
|
||||
if (failed(batchOp))
|
||||
return failure();
|
||||
@@ -525,7 +495,6 @@ static FailureOr<Value> createBatchedDynamicOutputCompute(Value scalarPieces,
|
||||
const int64_t numOutRows = outType.getDimSize(1);
|
||||
const int64_t numOutCols = outType.getDimSize(2);
|
||||
auto scalarType = RankedTensorType::get({1, 1}, outType.getElementType());
|
||||
auto outputScalarType = RankedTensorType::get({1, 1, 1}, outType.getElementType());
|
||||
|
||||
auto computeOp = createSpatCompute<1>(
|
||||
rewriter, loc, TypeRange {outType}, {}, ValueRange {scalarPieces}, [&](Value pieces) -> LogicalResult {
|
||||
@@ -548,24 +517,15 @@ static FailureOr<Value> createBatchedDynamicOutputCompute(Value scalarPieces,
|
||||
Value batchLane = affineModConst(rewriter, nestedLoc, lane, numOutRows * numOutCols, anchorOp);
|
||||
Value row = affineFloorDivConst(rewriter, nestedLoc, batchLane, numOutCols, anchorOp);
|
||||
Value column = affineModConst(rewriter, nestedLoc, batchLane, numOutCols, anchorOp);
|
||||
SmallVector<OpFoldResult> scalarOffsets {lane, rewriter.getIndexAttr(0)};
|
||||
SmallVector<OpFoldResult> scalarSizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
||||
Value scalar = tensor::ExtractSliceOp::create(
|
||||
rewriter, nestedLoc, scalarType, pieces, scalarOffsets, scalarSizes, getUnitStrides(rewriter, 2));
|
||||
Value expanded = tensor::ExpandShapeOp::create(rewriter,
|
||||
nestedLoc,
|
||||
outputScalarType,
|
||||
scalar,
|
||||
SmallVector<ReassociationIndices> {
|
||||
{0},
|
||||
{1, 2}
|
||||
});
|
||||
FailureOr<Value> scalar = extractGraphBatchPhysicalFragment(rewriter, nestedLoc, pieces, lane, scalarType);
|
||||
if (failed(scalar))
|
||||
return failure();
|
||||
SmallVector<OpFoldResult> outputOffsets {batch, row, column};
|
||||
SmallVector<OpFoldResult> outputSizes = {
|
||||
rewriter.getIndexAttr(1), rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
||||
Value next =
|
||||
tensor::InsertSliceOp::create(
|
||||
rewriter, nestedLoc, expanded, outputAcc, outputOffsets, outputSizes, getUnitStrides(rewriter, 3))
|
||||
rewriter, nestedLoc, *scalar, outputAcc, outputOffsets, outputSizes, getUnitStrides(rewriter, 3))
|
||||
.getResult();
|
||||
yielded.push_back(next);
|
||||
return success();
|
||||
@@ -596,10 +556,11 @@ static Value extractBatchedReductionPiece(Value partialPiecesArg,
|
||||
Value kOffset = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), kSlice * numOutRows);
|
||||
Value batchAndHSlice = arith::AddIOp::create(rewriter, loc, batchOffset, hOffset);
|
||||
Value pieceOffset = arith::AddIOp::create(rewriter, loc, batchAndHSlice, kOffset);
|
||||
SmallVector<OpFoldResult> offsets {pieceOffset, rewriter.getIndexAttr(0)};
|
||||
SmallVector<OpFoldResult> sizes {rewriter.getIndexAttr(numOutRows), rewriter.getIndexAttr(crossbarSize.getValue())};
|
||||
return tensor::ExtractSliceOp::create(
|
||||
rewriter, loc, pieceType, partialPiecesArg, offsets, sizes, getUnitStrides(rewriter, 2));
|
||||
SmallVector<OpFoldResult> offsets {pieceOffset, rewriter.getIndexAttr(0), rewriter.getIndexAttr(0)};
|
||||
SmallVector<OpFoldResult> sizes {rewriter.getIndexAttr(numOutRows), rewriter.getIndexAttr(1), rewriter.getIndexAttr(crossbarSize.getValue())};
|
||||
return extractMixedSliceOrIdentity(
|
||||
rewriter, loc, partialPiecesArg, pieceType,
|
||||
{offsets, sizes, getUnitStrides(rewriter, 3)});
|
||||
}
|
||||
|
||||
static Value reduceBatchedPartialPiecesForHSlice(Value partialPiecesArg,
|
||||
@@ -646,8 +607,6 @@ static FailureOr<Value> createBatchedReductionCompute(Value partialPieces,
|
||||
const int64_t numOutHSlices = ceilIntegerDivide(outType.getDimSize(2), crossbarSize.getValue());
|
||||
auto pieceType = RankedTensorType::get({numOutRows, static_cast<int64_t>(crossbarSize.getValue())},
|
||||
partialPiecesType.getElementType());
|
||||
auto outputSliceType = RankedTensorType::get({1, numOutRows, static_cast<int64_t>(crossbarSize.getValue())},
|
||||
partialPiecesType.getElementType());
|
||||
|
||||
Value outputInit =
|
||||
tensor::EmptyOp::create(rewriter, loc, paddedOutType.getShape(), paddedOutType.getElementType()).getResult();
|
||||
@@ -677,14 +636,6 @@ static FailureOr<Value> createBatchedReductionCompute(Value partialPieces,
|
||||
Value outputAcc = hIterArgs.front();
|
||||
Value reduced = reduceBatchedPartialPiecesForHSlice(
|
||||
partialPiecesArg, batch, hSlice, pieceType, numKSlices, numOutHSlices, numOutRows, rewriter, hLoc);
|
||||
Value expandedReduced = tensor::ExpandShapeOp::create(rewriter,
|
||||
hLoc,
|
||||
outputSliceType,
|
||||
reduced,
|
||||
SmallVector<ReassociationIndices> {
|
||||
{0, 1},
|
||||
{2}
|
||||
});
|
||||
Value hOffset = affineMulConst(
|
||||
rewriter, hLoc, hSlice, crossbarSize.getValue(), rewriter.getInsertionBlock()->getParentOp());
|
||||
SmallVector<OpFoldResult> outputOffsets {batch, rewriter.getIndexAttr(0), hOffset};
|
||||
@@ -693,7 +644,7 @@ static FailureOr<Value> createBatchedReductionCompute(Value partialPieces,
|
||||
rewriter.getIndexAttr(crossbarSize.getValue())};
|
||||
Value next =
|
||||
tensor::InsertSliceOp::create(
|
||||
rewriter, hLoc, expandedReduced, outputAcc, outputOffsets, outputSizes, getUnitStrides(rewriter, 3))
|
||||
rewriter, hLoc, reduced, outputAcc, outputOffsets, outputSizes, getUnitStrides(rewriter, 3))
|
||||
.getResult();
|
||||
hYielded.push_back(next);
|
||||
return success();
|
||||
@@ -917,9 +868,7 @@ struct MatMulToGemm : OpRewritePattern<ONNXMatMulOp> {
|
||||
if (failed(shapeInfo) || shapeInfo->lhsWasVector || shapeInfo->rhsWasVector)
|
||||
return failure();
|
||||
|
||||
const bool hasNonSingletonOutputBatch =
|
||||
!shapeInfo->outputBatchShape.empty() && getStaticShapeElementCount(shapeInfo->outputBatchShape) != 1;
|
||||
if (hasNonSingletonOutputBatch)
|
||||
if (!shapeInfo->outputBatchShape.empty())
|
||||
return failure();
|
||||
|
||||
Location loc = matmulOp.getLoc();
|
||||
@@ -1021,8 +970,8 @@ struct MatMulBatchedToSpatialComputes : OpRewritePattern<ONNXMatMulOp> {
|
||||
if (succeeded(paddedRhs)) {
|
||||
Value paddedLhs = createPaddedInputCompute(plan.lhs, paddedLhsType, rewriter, loc);
|
||||
const int64_t laneCount = plan.batch * plan.m * numKSlices * numOutHSlices;
|
||||
auto partialPiecesType = RankedTensorType::get({laneCount, static_cast<int64_t>(crossbarSize.getValue())},
|
||||
shapeInfo->outType.getElementType());
|
||||
auto partialPiecesType = spatial::getGraphBatchPhysicalResultType(
|
||||
laneCount, RankedTensorType::get({1, static_cast<int64_t>(crossbarSize.getValue())}, shapeInfo->outType.getElementType()));
|
||||
auto batchOp = createBatchedVmmBatch(paddedLhs,
|
||||
*paddedRhs,
|
||||
paddedLhsType,
|
||||
@@ -1063,7 +1012,8 @@ struct MatMulBatchedToSpatialComputes : OpRewritePattern<ONNXMatMulOp> {
|
||||
}
|
||||
}
|
||||
const int64_t laneCount = plan.batch * plan.m * plan.n;
|
||||
auto scalarPiecesType = RankedTensorType::get({laneCount, 1}, shapeInfo->outType.getElementType());
|
||||
auto scalarPiecesType = spatial::getGraphBatchPhysicalResultType(
|
||||
laneCount, RankedTensorType::get({1, 1}, shapeInfo->outType.getElementType()));
|
||||
auto batchOp = createBatchedVvdmulBatch(plan.lhs,
|
||||
plan.lhsBatchShape,
|
||||
plan.rhs,
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
|
||||
@@ -122,14 +121,6 @@ static RankedTensorType getKeepdimsType(RankedTensorType inputType, Type element
|
||||
return RankedTensorType::get(shape, elementType, inputType.getEncoding());
|
||||
}
|
||||
|
||||
static RankedTensorType getCompactKeptType(RankedTensorType inputType, Type elementType, ArrayRef<bool> reducedAxes) {
|
||||
SmallVector<int64_t> shape;
|
||||
for (auto [dim, isReduced] : llvm::zip_equal(inputType.getShape(), reducedAxes))
|
||||
if (!isReduced)
|
||||
shape.push_back(dim);
|
||||
return RankedTensorType::get(shape, elementType, inputType.getEncoding());
|
||||
}
|
||||
|
||||
static RankedTensorType getReducedSliceType(RankedTensorType inputType, ArrayRef<bool> reducedAxes) {
|
||||
SmallVector<int64_t> shape;
|
||||
shape.reserve(inputType.getRank());
|
||||
@@ -139,9 +130,7 @@ static RankedTensorType getReducedSliceType(RankedTensorType inputType, ArrayRef
|
||||
}
|
||||
|
||||
static RankedTensorType getLanePackedKeepdimsType(int64_t laneCount, RankedTensorType leafType) {
|
||||
SmallVector<int64_t> shape(leafType.getShape().begin(), leafType.getShape().end());
|
||||
shape.front() = laneCount;
|
||||
return RankedTensorType::get(shape, leafType.getElementType(), leafType.getEncoding());
|
||||
return spatial::getGraphBatchPhysicalResultType(laneCount, leafType);
|
||||
}
|
||||
|
||||
static SmallVector<int64_t> getKeptAxes(ArrayRef<bool> reducedAxes) {
|
||||
@@ -191,12 +180,9 @@ static FailureOr<Value> buildReduceMeanKeepdimsBatch(Value input,
|
||||
|
||||
SmallVector<OpFoldResult> sliceOffsets;
|
||||
SmallVector<OpFoldResult> sliceSizes;
|
||||
SmallVector<OpFoldResult> insertOffsets;
|
||||
SmallVector<OpFoldResult> insertSizes(inputType.getRank(), rewriter.getIndexAttr(1));
|
||||
SmallVector<OpFoldResult> unitStrides = getUnitStrides(rewriter, inputType.getRank());
|
||||
sliceOffsets.reserve(inputType.getRank());
|
||||
sliceSizes.reserve(inputType.getRank());
|
||||
insertOffsets.reserve(inputType.getRank());
|
||||
|
||||
auto batchOp =
|
||||
createSpatComputeBatch(rewriter,
|
||||
@@ -209,7 +195,6 @@ static FailureOr<Value> buildReduceMeanKeepdimsBatch(Value input,
|
||||
size_t keptAxisIndex = 0;
|
||||
sliceOffsets.clear();
|
||||
sliceSizes.clear();
|
||||
insertOffsets.clear();
|
||||
for (auto [axis, isReduced] : llvm::enumerate(reducedAxes)) {
|
||||
if (isReduced) {
|
||||
sliceOffsets.push_back(rewriter.getIndexAttr(0));
|
||||
@@ -224,72 +209,90 @@ static FailureOr<Value> buildReduceMeanKeepdimsBatch(Value input,
|
||||
sliceSizes.push_back(rewriter.getIndexAttr(1));
|
||||
}
|
||||
|
||||
insertOffsets.push_back(args.lane);
|
||||
insertOffsets.append(inputType.getRank() - 1, rewriter.getIndexAttr(0));
|
||||
|
||||
Value slice = tensor::ExtractSliceOp::create(
|
||||
rewriter, loc, sliceType, args.inputs.front(), sliceOffsets, sliceSizes, unitStrides);
|
||||
Value reduced = spatial::SpatVAvgOp::create(rewriter, loc, leafType, slice).getResult();
|
||||
createParallelInsertSliceIntoBatchOutput(
|
||||
rewriter, loc, reduced, args.outputs.front(), insertOffsets, insertSizes, unitStrides);
|
||||
publishGraphBatchPhysicalFragment(rewriter, loc, reduced, args.outputs.front(), args.lane);
|
||||
});
|
||||
if (failed(batchOp))
|
||||
return failure();
|
||||
return (*batchOp).getResult(0);
|
||||
}
|
||||
|
||||
static Value buildKeepdimsFromLanePackedBatch(Value batchValue,
|
||||
RankedTensorType keepdimsType,
|
||||
RankedTensorType compactKeptType,
|
||||
ArrayRef<bool> reducedAxes,
|
||||
ConversionPatternRewriter& rewriter,
|
||||
static FailureOr<Value> buildReduceMeanKeepdimsBlueprint(
|
||||
Value batchValue, RankedTensorType keepdimsType,
|
||||
ArrayRef<bool> reducedAxes, ConversionPatternRewriter& rewriter,
|
||||
Location loc) {
|
||||
auto batchType = cast<RankedTensorType>(batchValue.getType());
|
||||
if (batchType == keepdimsType)
|
||||
return batchValue;
|
||||
auto batchType = dyn_cast<RankedTensorType>(batchValue.getType());
|
||||
int64_t rank = keepdimsType.getRank();
|
||||
if (!batchType || !batchType.hasStaticShape()
|
||||
|| !keepdimsType.hasStaticShape()
|
||||
|| static_cast<int64_t>(reducedAxes.size()) != rank
|
||||
|| batchType.getRank() != rank + 1
|
||||
|| batchType.getElementType() != keepdimsType.getElementType())
|
||||
return failure();
|
||||
|
||||
SmallVector<ReassociationIndices> collapseToFlat {{}};
|
||||
for (int64_t axis = 0; axis < batchType.getRank(); ++axis)
|
||||
collapseToFlat.front().push_back(axis);
|
||||
int64_t laneCount = 1;
|
||||
SmallVector<int64_t> keptAxes;
|
||||
SmallVector<int64_t> keptAxisStrides;
|
||||
for (auto [axis, isReduced] : llvm::enumerate(reducedAxes)) {
|
||||
int64_t dim = keepdimsType.getDimSize(axis);
|
||||
if (dim <= 0 || (isReduced && dim != 1))
|
||||
return failure();
|
||||
if (!isReduced)
|
||||
keptAxes.push_back(axis);
|
||||
}
|
||||
keptAxisStrides.resize(keptAxes.size(), 1);
|
||||
for (int64_t index = static_cast<int64_t>(keptAxes.size()) - 1;
|
||||
index >= 0; --index) {
|
||||
keptAxisStrides[index] = laneCount;
|
||||
int64_t dim = keepdimsType.getDimSize(keptAxes[index]);
|
||||
if (laneCount > std::numeric_limits<int64_t>::max() / dim)
|
||||
return failure();
|
||||
laneCount *= dim;
|
||||
}
|
||||
if (batchType.getDimSize(0) != laneCount
|
||||
|| llvm::any_of(batchType.getShape().drop_front(),
|
||||
[](int64_t dim) { return dim != 1; }))
|
||||
return failure();
|
||||
|
||||
SmallVector<ReassociationIndices> expandFlatToCompact(1);
|
||||
for (int64_t axis = 0; axis < compactKeptType.getRank(); ++axis)
|
||||
expandFlatToCompact.front().push_back(axis);
|
||||
|
||||
SmallVector<ReassociationIndices> expandCompactToKeepdims;
|
||||
ReassociationIndices pendingLeadingReducedAxes;
|
||||
SmallVector<int64_t> operandIndices(laneCount, 0);
|
||||
SmallVector<int64_t> sourceSlots;
|
||||
SmallVector<int64_t> sourceOffsets(laneCount, 0);
|
||||
SmallVector<int64_t> fragmentOffsets;
|
||||
sourceSlots.reserve(laneCount);
|
||||
fragmentOffsets.reserve(laneCount * rank);
|
||||
for (int64_t lane = 0; lane < laneCount; ++lane) {
|
||||
sourceSlots.push_back(lane);
|
||||
size_t keptAxisIndex = 0;
|
||||
for (auto [axis, isReduced] : llvm::enumerate(reducedAxes)) {
|
||||
if (isReduced) {
|
||||
if (expandCompactToKeepdims.empty())
|
||||
pendingLeadingReducedAxes.push_back(axis);
|
||||
else
|
||||
expandCompactToKeepdims.back().push_back(axis);
|
||||
fragmentOffsets.push_back(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
expandCompactToKeepdims.emplace_back();
|
||||
auto& group = expandCompactToKeepdims.back();
|
||||
group.append(pendingLeadingReducedAxes.begin(), pendingLeadingReducedAxes.end());
|
||||
pendingLeadingReducedAxes.clear();
|
||||
group.push_back(axis);
|
||||
int64_t dim = keepdimsType.getDimSize(axis);
|
||||
fragmentOffsets.push_back(
|
||||
(lane / keptAxisStrides[keptAxisIndex]) % dim);
|
||||
++keptAxisIndex;
|
||||
}
|
||||
if (!pendingLeadingReducedAxes.empty())
|
||||
expandCompactToKeepdims.back().append(pendingLeadingReducedAxes.begin(), pendingLeadingReducedAxes.end());
|
||||
|
||||
auto reshapeCompute =
|
||||
createSpatCompute<1>(rewriter, loc, TypeRange {keepdimsType}, {}, ValueRange {batchValue}, [&](Value input) {
|
||||
auto flatType =
|
||||
RankedTensorType::get({batchType.getDimSize(0)}, batchType.getElementType(), batchType.getEncoding());
|
||||
Value flat = tensor::CollapseShapeOp::create(rewriter, loc, flatType, input, collapseToFlat);
|
||||
Value compact = flat;
|
||||
if (compactKeptType != flatType)
|
||||
compact = tensor::ExpandShapeOp::create(rewriter, loc, compactKeptType, flat, expandFlatToCompact);
|
||||
Value keepdims = compact;
|
||||
if (keepdimsType != compactKeptType)
|
||||
keepdims = tensor::ExpandShapeOp::create(rewriter, loc, keepdimsType, compact, expandCompactToKeepdims);
|
||||
spatial::SpatYieldOp::create(rewriter, loc, keepdims);
|
||||
});
|
||||
return reshapeCompute.getResult(0);
|
||||
}
|
||||
SmallVector<int64_t> fragmentSizes(fragmentOffsets.size(), 1);
|
||||
SmallVector<int64_t> fragmentStrides(fragmentOffsets.size(), 1);
|
||||
return spatial::SpatBlueprintOp::create(
|
||||
rewriter, loc, keepdimsType, batchValue, ValueRange {},
|
||||
rewriter.getStringAttr("nchw"),
|
||||
rewriter.getStringAttr("fragmented"),
|
||||
rewriter.getDenseI64ArrayAttr(fragmentOffsets),
|
||||
rewriter.getDenseI64ArrayAttr(fragmentSizes),
|
||||
rewriter.getStringAttr("reduce_mean_keepdims_fragments"),
|
||||
rewriter.getStringAttr("fragment_assembly"),
|
||||
rewriter.getDenseI64ArrayAttr(operandIndices),
|
||||
rewriter.getDenseI64ArrayAttr(sourceSlots),
|
||||
rewriter.getDenseI64ArrayAttr(sourceOffsets),
|
||||
rewriter.getDenseI64ArrayAttr(fragmentStrides),
|
||||
rewriter.getStringAttr("disjoint"),
|
||||
rewriter.getStringAttr("complete"))
|
||||
.getOutput();
|
||||
}
|
||||
|
||||
static SmallVector<ReassociationIndices> buildCollapseReassociation(ArrayRef<bool> reducedAxes) {
|
||||
@@ -366,26 +369,36 @@ struct ReduceMeanToSpatialCompute : OpConversionPattern<ReduceMeanOp> {
|
||||
|
||||
Location loc = reduceMeanOp.getLoc();
|
||||
RankedTensorType leafType = getAllOnesType(inputType, resultType.getElementType());
|
||||
RankedTensorType compactKeptType = getCompactKeptType(inputType, resultType.getElementType(), reducedAxes);
|
||||
RankedTensorType keepdimsType = getKeepdimsType(inputType, resultType.getElementType(), reducedAxes);
|
||||
int64_t laneCount = 1;
|
||||
for (int64_t dim : compactKeptType.getShape())
|
||||
for (auto [dim, isReduced] : llvm::zip_equal(keepdimsType.getShape(), reducedAxes)) {
|
||||
if (isReduced)
|
||||
continue;
|
||||
if (dim <= 0 || laneCount > std::numeric_limits<int32_t>::max() / dim)
|
||||
return rewriter.notifyMatchFailure(
|
||||
reduceMeanOp, "ReduceMean physical lane count is not representable");
|
||||
laneCount *= dim;
|
||||
}
|
||||
RankedTensorType batchType = getLanePackedKeepdimsType(laneCount, leafType);
|
||||
|
||||
auto lanePackedKeepdims =
|
||||
buildReduceMeanKeepdimsBatch(adaptor.getData(), reducedAxes, batchType, leafType, rewriter, loc);
|
||||
if (failed(lanePackedKeepdims))
|
||||
return failure();
|
||||
Value reducedKeepdims =
|
||||
buildKeepdimsFromLanePackedBatch(*lanePackedKeepdims, keepdimsType, compactKeptType, reducedAxes, rewriter, loc);
|
||||
auto reducedKeepdims = buildReduceMeanKeepdimsBlueprint(
|
||||
*lanePackedKeepdims, keepdimsType, reducedAxes, rewriter, loc);
|
||||
if (failed(reducedKeepdims))
|
||||
return rewriter.notifyMatchFailure(
|
||||
reduceMeanOp,
|
||||
"cannot build physical-fragment ReduceMean keepdims reconstruction");
|
||||
|
||||
if (semantics->keepdims != 0) {
|
||||
rewriter.replaceOp(reduceMeanOp, reducedKeepdims);
|
||||
rewriter.replaceOp(reduceMeanOp, *reducedKeepdims);
|
||||
return success();
|
||||
}
|
||||
|
||||
Value reduced = squeezeReducedAxes(reducedKeepdims, resultType, reducedAxes, rewriter, loc);
|
||||
Value reduced = squeezeReducedAxes(
|
||||
*reducedKeepdims, resultType, reducedAxes, rewriter, loc);
|
||||
rewriter.replaceOp(reduceMeanOp, reduced);
|
||||
return success();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/RowStripLayoutUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/PlanLowering.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||
|
||||
@@ -24,7 +26,7 @@ using namespace mlir;
|
||||
namespace onnx_mlir {
|
||||
namespace {
|
||||
|
||||
static Value materializeTileTensor(ConversionPatternRewriter& rewriter, Location loc, Value tile) {
|
||||
static Value materializeTileTensor(PatternRewriter& rewriter, Location loc, Value tile) {
|
||||
auto tileType = cast<RankedTensorType>(tile.getType());
|
||||
Value empty = tensor::EmptyOp::create(rewriter, loc, tileType.getShape(), tileType.getElementType());
|
||||
return insertStaticSlice(rewriter, loc, tile, empty, getZeroOffsets(rewriter, tileType.getRank()));
|
||||
@@ -228,6 +230,23 @@ struct PoolToSpatialComputeBase : public OpConversionPattern<PoolOp> {
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<PoolOp, ONNXMaxPoolSingleOutOp>) {
|
||||
if (batchSize == 1) {
|
||||
auto plan = spatial::SpatMaxPool2DPlanOp::create(
|
||||
rewriter,
|
||||
loc,
|
||||
outType,
|
||||
x,
|
||||
rewriter.getDenseI64ArrayAttr({kernelHeight, kernelWidth}),
|
||||
rewriter.getDenseI64ArrayAttr({padTop, padLeft, padBottom, padRight}),
|
||||
rewriter.getDenseI64ArrayAttr({strideHeight, strideWidth}),
|
||||
rewriter.getDenseI64ArrayAttr({dilationHeight, dilationWidth}),
|
||||
rewriter.getStringAttr("nchw"));
|
||||
rewriter.replaceOp(poolOp, plan.getResult());
|
||||
return success();
|
||||
}
|
||||
}
|
||||
|
||||
const int64_t xbarSize = static_cast<int64_t>(crossbarSize.getValue());
|
||||
const int64_t channelTileCount = (channels + xbarSize - 1) / xbarSize;
|
||||
const int64_t outputPatchCount = batchSize * outputHeight * outputWidth;
|
||||
@@ -396,6 +415,220 @@ struct PoolToSpatialCompute<ONNXAveragePoolOp>
|
||||
|
||||
} // namespace
|
||||
|
||||
LogicalResult canLowerMaxPoolPlanToRowStrip(spatial::SpatMaxPool2DPlanOp planOp) {
|
||||
auto inputType = dyn_cast<RankedTensorType>(planOp.getInput().getType());
|
||||
auto outputType = dyn_cast<RankedTensorType>(planOp.getOutput().getType());
|
||||
if (!inputType || !outputType || !inputType.hasStaticShape() || !outputType.hasStaticShape())
|
||||
return failure();
|
||||
if (inputType.getRank() != 4 || outputType.getRank() != 4 || inputType.getDimSize(0) != 1
|
||||
|| outputType.getDimSize(0) != 1 || inputType.getDimSize(1) != outputType.getDimSize(1))
|
||||
return failure();
|
||||
if (llvm::any_of(planOp.getKernelShape(), [](int64_t value) { return value <= 0; })
|
||||
|| llvm::any_of(planOp.getStrides(), [](int64_t value) { return value <= 0; })
|
||||
|| llvm::any_of(planOp.getDilations(), [](int64_t value) { return value <= 0; }))
|
||||
return failure();
|
||||
return success();
|
||||
}
|
||||
|
||||
static Value createClampedPoolIndexTable(PatternRewriter& rewriter,
|
||||
Operation* anchorOp,
|
||||
int64_t outputSize,
|
||||
int64_t kernelSize,
|
||||
int64_t stride,
|
||||
int64_t dilation,
|
||||
int64_t padBegin,
|
||||
int64_t inputSize) {
|
||||
auto tableType = RankedTensorType::get({outputSize * kernelSize}, rewriter.getIndexType());
|
||||
SmallVector<Attribute> values;
|
||||
values.reserve(tableType.getNumElements());
|
||||
for (int64_t output = 0; output < outputSize; ++output)
|
||||
for (int64_t kernel = 0; kernel < kernelSize; ++kernel)
|
||||
values.push_back(rewriter.getIndexAttr(
|
||||
std::clamp(output * stride + kernel * dilation - padBegin, int64_t {0}, inputSize - 1)));
|
||||
return getOrCreateConstant(rewriter, anchorOp, DenseElementsAttr::get(tableType, values), tableType);
|
||||
}
|
||||
|
||||
static Value extractPoolIndex(PatternRewriter& rewriter,
|
||||
Location loc,
|
||||
Operation* anchorOp,
|
||||
Value table,
|
||||
Value outputIndex,
|
||||
int64_t kernelIndex,
|
||||
int64_t kernelSize) {
|
||||
Value tableIndex = arith::MulIOp::create(
|
||||
rewriter, loc, outputIndex, getOrCreateIndexConstant(rewriter, anchorOp, kernelSize));
|
||||
if (kernelIndex != 0)
|
||||
tableIndex = arith::AddIOp::create(
|
||||
rewriter, loc, tableIndex, getOrCreateIndexConstant(rewriter, anchorOp, kernelIndex));
|
||||
return tensor::ExtractOp::create(rewriter, loc, table, tableIndex);
|
||||
}
|
||||
|
||||
FailureOr<Value> lowerSelectedMaxPool2DPlan(spatial::SpatMaxPool2DPlanOp planOp,
|
||||
std::optional<Value> rowStripInput,
|
||||
PatternRewriter& rewriter) {
|
||||
if (failed(canLowerMaxPoolPlanToRowStrip(planOp)))
|
||||
return failure();
|
||||
|
||||
Location loc = planOp.getLoc();
|
||||
auto inputType = cast<RankedTensorType>(planOp.getInput().getType());
|
||||
auto outputType = cast<RankedTensorType>(planOp.getOutput().getType());
|
||||
const int64_t channels = inputType.getDimSize(1);
|
||||
const int64_t inputHeight = inputType.getDimSize(2);
|
||||
const int64_t inputWidth = inputType.getDimSize(3);
|
||||
const int64_t outputHeight = outputType.getDimSize(2);
|
||||
const int64_t outputWidth = outputType.getDimSize(3);
|
||||
const int64_t kernelHeight = planOp.getKernelShape()[0];
|
||||
const int64_t kernelWidth = planOp.getKernelShape()[1];
|
||||
Value input = rowStripInput.value_or(planOp.getInput());
|
||||
auto actualInputType = dyn_cast<RankedTensorType>(input.getType());
|
||||
const bool physicalInput = actualInputType == getRowStripStorageType(inputType);
|
||||
if (!physicalInput && actualInputType != inputType)
|
||||
return failure();
|
||||
|
||||
Operation* anchorOp = rewriter.getInsertionBlock()->getParentOp();
|
||||
Value rowTable = createClampedPoolIndexTable(rewriter,
|
||||
anchorOp,
|
||||
outputHeight,
|
||||
kernelHeight,
|
||||
planOp.getStrides()[0],
|
||||
planOp.getDilations()[0],
|
||||
planOp.getPads()[0],
|
||||
inputHeight);
|
||||
Value columnTable = createClampedPoolIndexTable(rewriter,
|
||||
anchorOp,
|
||||
outputWidth,
|
||||
kernelWidth,
|
||||
planOp.getStrides()[1],
|
||||
planOp.getDilations()[1],
|
||||
planOp.getPads()[1],
|
||||
inputWidth);
|
||||
auto inputFragmentType = getRowStripFragmentType(inputType);
|
||||
auto outputFragmentType = getRowStripFragmentType(outputType);
|
||||
auto outputStorageType = getRowStripStorageType(outputType);
|
||||
auto tileType = RankedTensorType::get({1, channels, 1, 1}, outputType.getElementType());
|
||||
auto batch = createSpatComputeBatch(
|
||||
rewriter,
|
||||
loc,
|
||||
TypeRange {outputStorageType},
|
||||
outputHeight,
|
||||
{},
|
||||
ValueRange {input},
|
||||
[&](detail::SpatComputeBatchBodyArgs args) -> LogicalResult {
|
||||
SmallVector<Value> inputRows;
|
||||
inputRows.reserve(kernelHeight);
|
||||
for (int64_t kernelRow = 0; kernelRow < kernelHeight; ++kernelRow) {
|
||||
Value sourceRow =
|
||||
extractPoolIndex(rewriter, loc, anchorOp, rowTable, args.lane, kernelRow, kernelHeight);
|
||||
if (physicalInput) {
|
||||
inputRows.push_back(
|
||||
extractRowStripFragment(args.inputs.front(), inputType, sourceRow, rewriter, loc));
|
||||
}
|
||||
else {
|
||||
SmallVector<OpFoldResult> offsets {
|
||||
rewriter.getIndexAttr(0), rewriter.getIndexAttr(0), sourceRow, rewriter.getIndexAttr(0)};
|
||||
inputRows.push_back(tensor::ExtractSliceOp::create(rewriter,
|
||||
loc,
|
||||
inputFragmentType,
|
||||
args.inputs.front(),
|
||||
offsets,
|
||||
SmallVector<OpFoldResult> {rewriter.getIndexAttr(1),
|
||||
rewriter.getIndexAttr(channels),
|
||||
rewriter.getIndexAttr(1),
|
||||
rewriter.getIndexAttr(inputWidth)},
|
||||
getUnitStrides(rewriter, 4)));
|
||||
}
|
||||
}
|
||||
|
||||
auto windowType = RankedTensorType::get(
|
||||
{1, channels, kernelHeight, inputWidth}, inputType.getElementType(), inputType.getEncoding());
|
||||
Value window = tensor::EmptyOp::create(
|
||||
rewriter, loc, windowType.getShape(), windowType.getElementType());
|
||||
for (int64_t kernelRow = 0; kernelRow < kernelHeight; ++kernelRow) {
|
||||
SmallVector<OpFoldResult> offsets {rewriter.getIndexAttr(0),
|
||||
rewriter.getIndexAttr(0),
|
||||
rewriter.getIndexAttr(kernelRow),
|
||||
rewriter.getIndexAttr(0)};
|
||||
window = tensor::InsertSliceOp::create(rewriter,
|
||||
loc,
|
||||
inputRows[kernelRow],
|
||||
window,
|
||||
offsets,
|
||||
SmallVector<OpFoldResult> {rewriter.getIndexAttr(1),
|
||||
rewriter.getIndexAttr(channels),
|
||||
rewriter.getIndexAttr(1),
|
||||
rewriter.getIndexAttr(inputWidth)},
|
||||
getUnitStrides(rewriter, 4));
|
||||
}
|
||||
|
||||
Value outputInit = tensor::EmptyOp::create(
|
||||
rewriter, loc, outputFragmentType.getShape(), outputFragmentType.getElementType());
|
||||
Operation* bodyAnchor = rewriter.getInsertionBlock()->getParentOp();
|
||||
Value c0 = getOrCreateIndexConstant(rewriter, bodyAnchor, 0);
|
||||
Value c1 = getOrCreateIndexConstant(rewriter, bodyAnchor, 1);
|
||||
Value cOutputWidth = getOrCreateIndexConstant(rewriter, bodyAnchor, outputWidth);
|
||||
auto outputLoop = buildNormalizedScfFor(
|
||||
rewriter,
|
||||
loc,
|
||||
c0,
|
||||
cOutputWidth,
|
||||
c1,
|
||||
ValueRange {outputInit},
|
||||
[&](OpBuilder&, Location nestedLoc, Value outputColumn, ValueRange iterArgs, SmallVectorImpl<Value>& yielded) {
|
||||
Value reduced;
|
||||
for (int64_t kernelRow = 0; kernelRow < kernelHeight; ++kernelRow) {
|
||||
for (int64_t kernelColumn = 0; kernelColumn < kernelWidth; ++kernelColumn) {
|
||||
Value sourceColumn = extractPoolIndex(rewriter,
|
||||
nestedLoc,
|
||||
bodyAnchor,
|
||||
columnTable,
|
||||
outputColumn,
|
||||
kernelColumn,
|
||||
kernelWidth);
|
||||
SmallVector<OpFoldResult> offsets {
|
||||
rewriter.getIndexAttr(0),
|
||||
rewriter.getIndexAttr(0),
|
||||
rewriter.getIndexAttr(kernelRow),
|
||||
sourceColumn};
|
||||
Value point = tensor::ExtractSliceOp::create(rewriter,
|
||||
nestedLoc,
|
||||
tileType,
|
||||
window,
|
||||
offsets,
|
||||
SmallVector<OpFoldResult> {rewriter.getIndexAttr(1),
|
||||
rewriter.getIndexAttr(channels),
|
||||
rewriter.getIndexAttr(1),
|
||||
rewriter.getIndexAttr(1)},
|
||||
getUnitStrides(rewriter, 4));
|
||||
reduced = reduced ? spatial::SpatVMaxOp::create(rewriter, nestedLoc, tileType, reduced, point).getResult()
|
||||
: materializeTileTensor(rewriter, nestedLoc, point);
|
||||
}
|
||||
}
|
||||
SmallVector<OpFoldResult> outputOffsets {
|
||||
rewriter.getIndexAttr(0), rewriter.getIndexAttr(0), rewriter.getIndexAttr(0), outputColumn};
|
||||
Value updated = tensor::InsertSliceOp::create(rewriter,
|
||||
nestedLoc,
|
||||
reduced,
|
||||
iterArgs.front(),
|
||||
outputOffsets,
|
||||
SmallVector<OpFoldResult> {rewriter.getIndexAttr(1),
|
||||
rewriter.getIndexAttr(channels),
|
||||
rewriter.getIndexAttr(1),
|
||||
rewriter.getIndexAttr(1)},
|
||||
getUnitStrides(rewriter, 4));
|
||||
yielded.push_back(updated);
|
||||
return success();
|
||||
});
|
||||
if (failed(outputLoop))
|
||||
return failure();
|
||||
insertRowStripFragment(
|
||||
outputLoop->results.front(), args.outputs.front(), outputType, args.lane, rewriter, loc);
|
||||
return success();
|
||||
});
|
||||
if (failed(batch))
|
||||
return failure();
|
||||
return batch->getResult(0);
|
||||
}
|
||||
|
||||
void populatePoolPatterns(RewritePatternSet& patterns, MLIRContext* ctx) {
|
||||
patterns.insert<PoolToSpatialCompute<ONNXMaxPoolSingleOutOp>>(ctx);
|
||||
patterns.insert<PoolToSpatialCompute<ONNXAveragePoolOp>>(ctx);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "src/Accelerators/PIM/Common/IR/WeightUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/ComputeRegionBuilder.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/WeightMaterialization.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
@@ -128,8 +129,6 @@ struct PromoteWeightLikeComputeInputsPattern : OpRewritePattern<spatial::SpatGra
|
||||
Block& oldBlock = compute.getBody().front();
|
||||
|
||||
rewriter.setInsertionPointAfter(compute);
|
||||
auto newCompute = spatial::SpatGraphCompute::create(
|
||||
rewriter, compute.getLoc(), compute.getResultTypes(), promoted->newWeights, promoted->newInputs);
|
||||
SmallVector<Type> newBlockArgTypes;
|
||||
SmallVector<Location> newBlockArgLocs;
|
||||
for (Value weight : promoted->newWeights) {
|
||||
@@ -138,10 +137,14 @@ struct PromoteWeightLikeComputeInputsPattern : OpRewritePattern<spatial::SpatGra
|
||||
}
|
||||
llvm::append_range(newBlockArgTypes, promoted->newInputTypes);
|
||||
llvm::append_range(newBlockArgLocs, promoted->newInputLocs);
|
||||
auto* newBlock = rewriter.createBlock(
|
||||
&newCompute.getBody(), newCompute.getBody().end(), TypeRange(newBlockArgTypes), newBlockArgLocs);
|
||||
newCompute.getProperties().setOperandSegmentSizes(
|
||||
{static_cast<int>(promoted->newWeights.size()), static_cast<int>(promoted->newInputs.size())});
|
||||
auto newCompute = createEmptySpatGraphCompute(rewriter,
|
||||
compute.getLoc(),
|
||||
compute.getResultTypes(),
|
||||
promoted->newWeights,
|
||||
promoted->newInputs,
|
||||
TypeRange(newBlockArgTypes),
|
||||
newBlockArgLocs);
|
||||
auto* newBlock = &newCompute.getBody().front();
|
||||
rewriter.setInsertionPointToStart(newBlock);
|
||||
|
||||
IRRewriter bodyRewriter(rewriter.getContext());
|
||||
@@ -193,12 +196,6 @@ struct PromoteWeightLikeComputeBatchInputsPattern : OpRewritePattern<spatial::Sp
|
||||
|
||||
rewriter.setInsertionPointAfter(compute);
|
||||
|
||||
auto laneCountAttr = pim::getCheckedI32Attr(
|
||||
rewriter, compute, static_cast<uint64_t>(compute.getLaneCount()), "promoted compute_batch lane count");
|
||||
if (failed(laneCountAttr))
|
||||
return failure();
|
||||
auto newCompute = spatial::SpatGraphComputeBatch::create(
|
||||
rewriter, compute.getLoc(), compute.getResultTypes(), *laneCountAttr, promoted->newWeights, promoted->newInputs);
|
||||
auto laneArg = compute.getLaneArgument();
|
||||
if (!laneArg)
|
||||
return rewriter.notifyMatchFailure(compute, "missing compute_batch lane block argument");
|
||||
@@ -223,23 +220,30 @@ struct PromoteWeightLikeComputeBatchInputsPattern : OpRewritePattern<spatial::Sp
|
||||
newBlockArgLocs.push_back(outputArg->getLoc());
|
||||
}
|
||||
|
||||
auto* newBlock = rewriter.createBlock(
|
||||
&newCompute.getBody(), newCompute.getBody().end(), TypeRange(newBlockArgTypes), newBlockArgLocs);
|
||||
newCompute.getProperties().setOperandSegmentSizes(
|
||||
{static_cast<int>(promoted->newWeights.size()), static_cast<int>(promoted->newInputs.size())});
|
||||
auto newCompute = createEmptySpatGraphComputeBatch(rewriter,
|
||||
compute.getLoc(),
|
||||
compute.getResultTypes(),
|
||||
compute.getLaneCount(),
|
||||
promoted->newWeights,
|
||||
promoted->newInputs,
|
||||
TypeRange(newBlockArgTypes),
|
||||
newBlockArgLocs);
|
||||
if (failed(newCompute))
|
||||
return failure();
|
||||
auto* newBlock = &(*newCompute).getBody().front();
|
||||
rewriter.setInsertionPointToStart(newBlock);
|
||||
|
||||
IRRewriter bodyRewriter(rewriter.getContext());
|
||||
bodyRewriter.setInsertionPointToStart(newBlock);
|
||||
|
||||
IRMapping mapper;
|
||||
auto newLaneArg = newCompute.getLaneArgument();
|
||||
auto newLaneArg = (*newCompute).getLaneArgument();
|
||||
if (!newLaneArg)
|
||||
return rewriter.notifyMatchFailure(compute, "missing rewritten compute_batch lane block argument");
|
||||
mapper.map(*laneArg, *newLaneArg);
|
||||
for (auto [weightIndex, weight] : llvm::enumerate(compute.getWeights())) {
|
||||
auto oldWeightArg = compute.getWeightArgument(weightIndex);
|
||||
auto newWeightArg = newCompute.getWeightArgument(weightIndex);
|
||||
auto newWeightArg = (*newCompute).getWeightArgument(weightIndex);
|
||||
if (!oldWeightArg || !newWeightArg)
|
||||
return rewriter.notifyMatchFailure(compute, "missing compute_batch weight block argument during rewrite");
|
||||
mapper.map(*oldWeightArg, *newWeightArg);
|
||||
@@ -249,7 +253,7 @@ struct PromoteWeightLikeComputeBatchInputsPattern : OpRewritePattern<spatial::Sp
|
||||
*promoted,
|
||||
bodyRewriter,
|
||||
mapper,
|
||||
[&](size_t index) { return newCompute.getInputArgument(index); },
|
||||
[&](size_t index) { return (*newCompute).getInputArgument(index); },
|
||||
rewriter)))
|
||||
return failure();
|
||||
for (auto resultIndex : llvm::seq<size_t>(0, compute.getNumResults())) {
|
||||
@@ -263,7 +267,7 @@ struct PromoteWeightLikeComputeBatchInputsPattern : OpRewritePattern<spatial::Sp
|
||||
for (Operation& op : oldBlock)
|
||||
rewriter.clone(op, mapper);
|
||||
|
||||
rewriter.replaceOp(compute, newCompute.getResults());
|
||||
rewriter.replaceOp(compute, (*newCompute).getResults());
|
||||
return success();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||
@@ -52,35 +52,12 @@ static FailureOr<Value> materializeTransposedConstant(Value input,
|
||||
return failure();
|
||||
}
|
||||
|
||||
if (denseAttr.isSplat())
|
||||
auto transposedAttr = transposeDenseElementsAttr(denseAttr, permutation);
|
||||
if (failed(transposedAttr) || transposedAttr->getType() != resultType)
|
||||
return failure();
|
||||
return getOrCreateConstant(rewriter,
|
||||
rewriter.getInsertionBlock()->getParentOp(),
|
||||
DenseElementsAttr::get(resultType, denseAttr.getSplatValue<Attribute>()),
|
||||
resultType);
|
||||
|
||||
SmallVector<Attribute> inputValues(denseAttr.getValues<Attribute>());
|
||||
SmallVector<Attribute> resultValues(inputValues.size());
|
||||
SmallVector<int64_t> inputStrides = computeRowMajorStrides(inputType.getShape());
|
||||
SmallVector<int64_t> resultStrides = computeRowMajorStrides(resultType.getShape());
|
||||
SmallVector<int64_t> inputIndices(inputType.getRank(), 0);
|
||||
|
||||
for (auto [linearIndex, value] : llvm::enumerate(inputValues)) {
|
||||
int64_t remaining = static_cast<int64_t>(linearIndex);
|
||||
for (int64_t dim = 0; dim < inputType.getRank(); ++dim) {
|
||||
inputIndices[dim] = inputStrides.empty() ? 0 : remaining / inputStrides[dim];
|
||||
remaining = inputStrides.empty() ? 0 : remaining % inputStrides[dim];
|
||||
}
|
||||
|
||||
int64_t resultLinearIndex = 0;
|
||||
for (int64_t dim = 0; dim < resultType.getRank(); ++dim)
|
||||
resultLinearIndex += inputIndices[permutation[dim]] * resultStrides[dim];
|
||||
|
||||
resultValues[resultLinearIndex] = value;
|
||||
}
|
||||
|
||||
return getOrCreateConstant(rewriter,
|
||||
rewriter.getInsertionBlock()->getParentOp(),
|
||||
DenseElementsAttr::get(resultType, resultValues),
|
||||
*transposedAttr,
|
||||
resultType);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,4 +18,11 @@ lowerSelectedConv2DPlan(spatial::SpatConv2DPlanOp planOp,
|
||||
mlir::LogicalResult canLowerConvPlanToRowStrip(spatial::SpatConv2DPlanOp planOp);
|
||||
mlir::LogicalResult canConsumeAndProduceRowStrip(spatial::SpatConv2DPlanOp planOp);
|
||||
|
||||
mlir::LogicalResult canLowerMaxPoolPlanToRowStrip(spatial::SpatMaxPool2DPlanOp planOp);
|
||||
|
||||
mlir::FailureOr<mlir::Value>
|
||||
lowerSelectedMaxPool2DPlan(spatial::SpatMaxPool2DPlanOp planOp,
|
||||
std::optional<mlir::Value> rowStripInput,
|
||||
mlir::PatternRewriter& rewriter);
|
||||
|
||||
} // namespace onnx_mlir
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
|
||||
#include "Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp"
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/BiasAddUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/RowStripLayoutUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/PlanLowering.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
#include "src/Accelerators/PIM/Pass/PIMPasses.h"
|
||||
#include "src/Accelerators/PIM/Pass/PIMPasses.h"
|
||||
|
||||
using namespace mlir;
|
||||
|
||||
@@ -19,7 +20,6 @@ namespace {
|
||||
static constexpr StringLiteral kLogicalLayout = "nchw";
|
||||
static constexpr StringLiteral kDenseLayout = "dense_nchw";
|
||||
static constexpr StringLiteral kRowStripLayout = "nchw_row_strip";
|
||||
static constexpr StringLiteral kRowStripIndexMap = "packed_hwc_rows_to_nchw";
|
||||
|
||||
enum class SelectedLayout {
|
||||
DenseNchw,
|
||||
@@ -34,8 +34,12 @@ static SelectedLayout getSelectedLayout(llvm::DenseMap<Value, SelectedLayout>& l
|
||||
static bool usesSelectedRowStrip(Operation* user, llvm::DenseMap<Value, SelectedLayout>& layouts) {
|
||||
if (auto reluPlan = dyn_cast<spatial::SpatReluPlanOp>(user))
|
||||
return getSelectedLayout(layouts, reluPlan.getResult()) == SelectedLayout::NchwRowStrip;
|
||||
if (auto biasAddPlan = dyn_cast<spatial::SpatBiasAddPlanOp>(user))
|
||||
return getSelectedLayout(layouts, biasAddPlan.getResult()) == SelectedLayout::NchwRowStrip;
|
||||
if (auto convPlan = dyn_cast<spatial::SpatConv2DPlanOp>(user))
|
||||
return getSelectedLayout(layouts, convPlan.getResult()) == SelectedLayout::NchwRowStrip;
|
||||
if (auto maxPoolPlan = dyn_cast<spatial::SpatMaxPool2DPlanOp>(user))
|
||||
return getSelectedLayout(layouts, maxPoolPlan.getResult()) == SelectedLayout::NchwRowStrip;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -49,19 +53,25 @@ static bool allUsersCanHandleRowStrip(Value value, llvm::DenseMap<Value, Selecte
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::pair<SmallVector<int64_t>, SmallVector<int64_t>> buildRowStripMetadata(RankedTensorType type) {
|
||||
SmallVector<int64_t> offsets;
|
||||
SmallVector<int64_t> sizes;
|
||||
const int64_t channels = type.getDimSize(1);
|
||||
const int64_t height = type.getDimSize(2);
|
||||
const int64_t width = type.getDimSize(3);
|
||||
offsets.reserve(height * 4);
|
||||
sizes.reserve(height * 4);
|
||||
for (int64_t row = 0; row < height; ++row) {
|
||||
offsets.append({0, 0, row, 0});
|
||||
sizes.append({1, channels, 1, width});
|
||||
static bool canConsumeRowStripAsUser(Operation* user) {
|
||||
if (isa<spatial::SpatReluPlanOp>(user))
|
||||
return true;
|
||||
if (auto biasAddPlan = dyn_cast<spatial::SpatBiasAddPlanOp>(user)) {
|
||||
auto resultType = dyn_cast<RankedTensorType>(biasAddPlan.getOutput().getType());
|
||||
return resultType && isSupportedBiasAddValue(biasAddPlan.getBias(), resultType);
|
||||
}
|
||||
return {offsets, sizes};
|
||||
if (auto convPlan = dyn_cast<spatial::SpatConv2DPlanOp>(user))
|
||||
return succeeded(canConsumeAndProduceRowStrip(convPlan));
|
||||
if (auto maxPoolPlan = dyn_cast<spatial::SpatMaxPool2DPlanOp>(user))
|
||||
return succeeded(canLowerMaxPoolPlanToRowStrip(maxPoolPlan));
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool hasRowStripConsumer(Value value) {
|
||||
for (Operation* user : value.getUsers())
|
||||
if (canConsumeRowStripAsUser(user))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool canSelectConvRowStrip(spatial::SpatConv2DPlanOp convPlan,
|
||||
@@ -85,11 +95,32 @@ static SelectedLayout chooseReluLayout(spatial::SpatReluPlanOp reluPlan,
|
||||
llvm::DenseMap<Value, SelectedLayout>& layouts) {
|
||||
if (getSelectedLayout(layouts, reluPlan.getInput()) != SelectedLayout::NchwRowStrip)
|
||||
return SelectedLayout::DenseNchw;
|
||||
if (!hasRowStripConsumer(reluPlan.getResult()))
|
||||
return SelectedLayout::DenseNchw;
|
||||
if (!allUsersCanHandleRowStrip(reluPlan.getResult(), layouts))
|
||||
return SelectedLayout::DenseNchw;
|
||||
return SelectedLayout::NchwRowStrip;
|
||||
}
|
||||
|
||||
static SelectedLayout chooseBiasAddLayout(spatial::SpatBiasAddPlanOp biasAddPlan,
|
||||
llvm::DenseMap<Value, SelectedLayout>& layouts) {
|
||||
if (getSelectedLayout(layouts, biasAddPlan.getInput()) != SelectedLayout::NchwRowStrip)
|
||||
return SelectedLayout::DenseNchw;
|
||||
auto resultType = dyn_cast<RankedTensorType>(biasAddPlan.getOutput().getType());
|
||||
if (!resultType || !isSupportedBiasAddValue(biasAddPlan.getBias(), resultType))
|
||||
return SelectedLayout::DenseNchw;
|
||||
if (!hasRowStripConsumer(biasAddPlan.getResult()))
|
||||
return SelectedLayout::DenseNchw;
|
||||
if (!allUsersCanHandleRowStrip(biasAddPlan.getResult(), layouts))
|
||||
return SelectedLayout::DenseNchw;
|
||||
return SelectedLayout::NchwRowStrip;
|
||||
}
|
||||
|
||||
static SelectedLayout chooseMaxPoolLayout(spatial::SpatMaxPool2DPlanOp maxPoolPlan) {
|
||||
return succeeded(canLowerMaxPoolPlanToRowStrip(maxPoolPlan)) ? SelectedLayout::NchwRowStrip
|
||||
: SelectedLayout::DenseNchw;
|
||||
}
|
||||
|
||||
static spatial::SpatBlueprintOp insertRowStripBlueprint(IRRewriter& rewriter, Value value) {
|
||||
auto outputType = cast<RankedTensorType>(value.getType());
|
||||
auto [offsets, sizes] = buildRowStripMetadata(outputType);
|
||||
@@ -108,6 +139,7 @@ static spatial::SpatBlueprintOp insertRowStripBlueprint(IRRewriter& rewriter, Va
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
@@ -173,6 +205,22 @@ struct SpatialLayoutPlanningPass final : PassWrapper<SpatialLayoutPlanningPass,
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (auto biasAddPlan = dyn_cast<spatial::SpatBiasAddPlanOp>(&op)) {
|
||||
SelectedLayout selected = chooseBiasAddLayout(biasAddPlan, layouts);
|
||||
if (layouts[biasAddPlan.getResult()] != selected) {
|
||||
layouts[biasAddPlan.getResult()] = selected;
|
||||
changed = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (auto maxPoolPlan = dyn_cast<spatial::SpatMaxPool2DPlanOp>(&op)) {
|
||||
SelectedLayout selected = chooseMaxPoolLayout(maxPoolPlan);
|
||||
if (layouts[maxPoolPlan.getResult()] != selected) {
|
||||
layouts[maxPoolPlan.getResult()] = selected;
|
||||
changed = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,8 +228,12 @@ struct SpatialLayoutPlanningPass final : PassWrapper<SpatialLayoutPlanningPass,
|
||||
Value producedValue;
|
||||
if (auto convPlan = dyn_cast<spatial::SpatConv2DPlanOp>(&op))
|
||||
producedValue = convPlan.getResult();
|
||||
else if (auto biasAddPlan = dyn_cast<spatial::SpatBiasAddPlanOp>(&op))
|
||||
producedValue = biasAddPlan.getResult();
|
||||
else if (auto reluPlan = dyn_cast<spatial::SpatReluPlanOp>(&op))
|
||||
producedValue = reluPlan.getResult();
|
||||
else if (auto maxPoolPlan = dyn_cast<spatial::SpatMaxPool2DPlanOp>(&op))
|
||||
producedValue = maxPoolPlan.getResult();
|
||||
else
|
||||
continue;
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
add_onnx_mlir_rewriter(SpatialToGraphviz)
|
||||
|
||||
add_pim_library(OMSpatialToGraphviz
|
||||
SpatialToGraphviz.cpp
|
||||
|
||||
EXCLUDE_FROM_OM_LIBS
|
||||
|
||||
LINK_LIBS PUBLIC
|
||||
MLIRTosaDialect
|
||||
OMCompilerOptions
|
||||
OMPimCommon
|
||||
OMONNXOps
|
||||
SpatialOps
|
||||
|
||||
ACCEL_INCLUDE_DIRS PRIVATE
|
||||
${PIM_GENERATED_INCLUDE_DIRS}
|
||||
)
|
||||
@@ -1,259 +0,0 @@
|
||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||
#include "mlir/Dialect/Tosa/IR/TosaOps.h"
|
||||
#include "mlir/IR/Block.h"
|
||||
#include "mlir/IR/Diagnostics.h"
|
||||
#include "mlir/IR/Value.h"
|
||||
#include "mlir/Pass/Pass.h"
|
||||
#include "mlir/Support/LLVM.h"
|
||||
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
|
||||
|
||||
#include "llvm/Support/Casting.h"
|
||||
#include "llvm/Support/Format.h"
|
||||
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
#include "src/Accelerators/PIM/Pass/PIMPasses.h"
|
||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||
|
||||
#define FORMAT_OPERATION(op) 'x' << llvm::format_hex_no_prefix(reinterpret_cast<size_t>(op), 0)
|
||||
#define FORMAT_ARGUMENT(computeOpPointer, argumentNum) llvm::format("Arg_%p_%u", computeOpPointer, argumentNum)
|
||||
|
||||
using namespace mlir;
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
namespace {
|
||||
|
||||
struct SpatialToGraphvizPass : public PassWrapper<SpatialToGraphvizPass, OperationPass<ModuleOp>> {
|
||||
|
||||
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(SpatialToGraphvizPass)
|
||||
|
||||
StringRef getArgument() const override { return "convert-spatial-to-graphviz"; }
|
||||
|
||||
StringRef getDescription() const override { return "Lower ONNX ops to Spatial ops."; }
|
||||
|
||||
SpatialToGraphvizPass(raw_ostream& os = llvm::errs())
|
||||
: os(os) {}
|
||||
SpatialToGraphvizPass(const SpatialToGraphvizPass& pass)
|
||||
: SpatialToGraphvizPass(pass.os) {}
|
||||
void runOnOperation() final;
|
||||
|
||||
private:
|
||||
raw_ostream& os;
|
||||
|
||||
/**
|
||||
* Draws the subgraph for a given spatial::SpatCompute, including:
|
||||
* 1. Input nodes (block arguments)
|
||||
* 2. Operations
|
||||
* 3. Edges between yield (output) and its users
|
||||
*
|
||||
* @param op The spatial::SpatCompute to draw the subgraph for.
|
||||
* @param computeNum The number of the compute operation.
|
||||
*/
|
||||
void drawComputeOpSubgraph(spatial::SpatCompute op, size_t computeNum) {
|
||||
os << "\tsubgraph cluster" << computeNum << " {\n\t\tlabel=\"Compute" << computeNum << "\";\n"
|
||||
<< "\t\tstyle=filled;\n"
|
||||
<< "\t\tcolor=lightblue;\n";
|
||||
|
||||
Block& block = op.getBody().front();
|
||||
|
||||
// Inputs
|
||||
size_t inputNum = 0;
|
||||
for (BlockArgument& input : block.getArguments()) {
|
||||
|
||||
auto fromOp = FORMAT_ARGUMENT(op.getOperation(), inputNum);
|
||||
|
||||
os << "\t\t" << fromOp << " [label=\"Arg" << inputNum << "\",shape=box];\n";
|
||||
for (auto userOp : input.getUsers())
|
||||
os << "\t\t" << fromOp << " -> " << FORMAT_OPERATION(userOp) << ";\n";
|
||||
inputNum++;
|
||||
}
|
||||
|
||||
// Iterate operations
|
||||
for (auto& childOp : block.getOperations()) {
|
||||
os << "\t\t" << FORMAT_OPERATION(&childOp) << " [label=\"" << childOp.getName() << "\"];\n";
|
||||
|
||||
drawEdgesFromOpToItsUsers(&childOp);
|
||||
}
|
||||
|
||||
os << "\t}\n";
|
||||
|
||||
// Draw edges from the yield to the users of this computeOp
|
||||
Operation* yieldOp = block.getTerminator();
|
||||
if (!isa<spatial::SpatYieldOp>(yieldOp)) {
|
||||
yieldOp->emitError("Terminator of block must be YieldOp ???");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto computeOpResult : op->getResults()) {
|
||||
for (auto& computeOpUse : computeOpResult.getUses()) {
|
||||
auto toOp = FORMAT_ARGUMENT(computeOpUse.getOwner(), computeOpUse.getOperandNumber());
|
||||
os << "\t" << FORMAT_OPERATION(yieldOp) << " -> " << toOp << ";\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Draws the subgraph for a concatOp.
|
||||
*
|
||||
* This function draws a subgraph for a concatOp. The subgraph consists of a
|
||||
* node for each input of the concatOp, as well as an output node. Edges are
|
||||
* created from the output node to each user of the concatOp.
|
||||
*
|
||||
* @param concatOp The concatOp for which the subgraph is drawn.
|
||||
* @param concatOpNum The number of the concatOp.
|
||||
*/
|
||||
void drawConcatOpSubgraph(Operation* concatOp, size_t concatOpNum) {
|
||||
os << "\tsubgraph clusterconcat" << concatOpNum << " {\n\t\tlabel=\"ConcatOp" << concatOpNum << "\";\n"
|
||||
<< "\t\tstyle=filled;\n"
|
||||
<< "\t\tcolor=orange;\n";
|
||||
|
||||
// Inputs
|
||||
size_t inputNum = 0;
|
||||
for (Value input : concatOp->getOperands()) {
|
||||
auto fromOp = FORMAT_ARGUMENT(concatOp, inputNum);
|
||||
|
||||
os << "\t\t" << fromOp << " [label=\"Input" << inputNum << "\"];\n";
|
||||
for (auto userOp : input.getUsers())
|
||||
os << "\t\t" << fromOp << " -> " << FORMAT_OPERATION(userOp) << ";\n";
|
||||
inputNum++;
|
||||
}
|
||||
|
||||
// Output
|
||||
os << "\t\t" << FORMAT_OPERATION(concatOp) << " [label=Out];\n";
|
||||
|
||||
os << "\t}\n";
|
||||
|
||||
// Edges from output to users
|
||||
|
||||
for (auto& computeOpUse : concatOp->getResult(0).getUses()) {
|
||||
os << "\t" << FORMAT_OPERATION(concatOp) << " -> "
|
||||
<< FORMAT_ARGUMENT(computeOpUse.getOwner(), computeOpUse.getOperandNumber()) << ";\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the ExtractSliceOp in the graph visualization.
|
||||
*
|
||||
* This function takes a tensor::ExtractSliceOp and adds the corresponding
|
||||
* node and edges to the graph visualization. It creates a node with the
|
||||
* label as the static offsets attribute of the sliceOp, and connects it to
|
||||
* the compute operations that use the result of the sliceOp.
|
||||
*
|
||||
* @param sliceOp The tensor::ExtractSliceOp to be drawn in the graph
|
||||
* visualization.
|
||||
*/
|
||||
void drawExtractSliceOp(tensor::ExtractSliceOp sliceOp) {
|
||||
auto nodeId = FORMAT_ARGUMENT(sliceOp.getOperation(), 0);
|
||||
os << "\t" << nodeId << " [label=\"Slice: ";
|
||||
sliceOp.getStaticOffsetsAttr().print(os);
|
||||
os << "\",color=lawngreen];\n";
|
||||
|
||||
for (auto& computeOpUse : sliceOp.getResult().getUses()) {
|
||||
os << "\t" << nodeId << " -> " << FORMAT_ARGUMENT(computeOpUse.getOwner(), computeOpUse.getOperandNumber())
|
||||
<< ";\n";
|
||||
}
|
||||
}
|
||||
|
||||
void drawBiasTileOp(tensor::ExtractSliceOp sliceOp) {
|
||||
auto nodeId = FORMAT_ARGUMENT(sliceOp.getOperation(), 0);
|
||||
os << "\t" << nodeId << " [label=\"Bias: ";
|
||||
sliceOp.getStaticOffsetsAttr().print(os);
|
||||
os << "\",color=lightpink];\n";
|
||||
|
||||
for (auto user : sliceOp.getResult().getUsers())
|
||||
os << "\t" << nodeId << " -> " << FORMAT_OPERATION(user) << ";\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws edges from the given operation to its users.
|
||||
*
|
||||
* @param fromOp The operation from which the edges are drawn.
|
||||
*/
|
||||
void drawEdgesFromOpToItsUsers(mlir::Operation* fromOp) {
|
||||
for (auto result : fromOp->getResults())
|
||||
for (auto userOp : result.getUsers())
|
||||
os << "\t\t" << FORMAT_OPERATION(fromOp) << " -> " << FORMAT_OPERATION(userOp) << ";\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws input node and edges for the given `funcOp`.
|
||||
*
|
||||
* @param funcOp The `funcOp` for which to draw input nodes and edges.
|
||||
*/
|
||||
void drawInputNodesAndEdges(func::FuncOp& funcOp) {
|
||||
os << "\tinput [label=\"Module Input\",color=green];\n";
|
||||
|
||||
size_t funcOpArgNum = 0;
|
||||
for (BlockArgument& arg : funcOp.getArguments()) {
|
||||
|
||||
for (auto& useOp : arg.getUses()) {
|
||||
os << "\tinput -> " << FORMAT_ARGUMENT(useOp.getOwner(), useOp.getOperandNumber()) << "[label=" << funcOpArgNum
|
||||
<< "];\n";
|
||||
}
|
||||
funcOpArgNum++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void SpatialToGraphvizPass::runOnOperation() {
|
||||
ModuleOp module = getOperation();
|
||||
|
||||
auto entryFunc = getPimEntryFunc(module);
|
||||
if (failed(entryFunc)) {
|
||||
module.emitError("failed to locate the PIM entry function for Spatial graph visualization");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
func::FuncOp func = *entryFunc;
|
||||
|
||||
os << "digraph G {\n"
|
||||
<< "\tnode [style=filled,color=white];\n";
|
||||
|
||||
size_t computeNum = 0;
|
||||
size_t concatNum = 0;
|
||||
|
||||
// Iterate over the ComputeOps within FuncOp:
|
||||
// 1. Print their subgraph
|
||||
// 2. Print the edges from its inputs to its outputs
|
||||
for (Operation& op : func.getOps()) {
|
||||
if (auto computeOp = dyn_cast<spatial::SpatCompute>(op)) {
|
||||
drawComputeOpSubgraph(computeOp, computeNum++);
|
||||
}
|
||||
else if (auto concatOp = dyn_cast<tensor::ConcatOp>(op)) {
|
||||
drawConcatOpSubgraph(concatOp, concatNum++);
|
||||
}
|
||||
else if (auto extractSliceOp = dyn_cast<tensor::ExtractSliceOp>(op)) {
|
||||
auto producerOp = extractSliceOp->getOperand(0).getDefiningOp();
|
||||
if (producerOp) {
|
||||
// Skip extractSliceOp if producer is constant weights (ONNXConstantOp)
|
||||
if (llvm::isa<ONNXConstantOp>(producerOp))
|
||||
continue;
|
||||
// If produced by tosa::ReshapeOp (i.e. it is a bias tile) connect
|
||||
// directly to its user, which is not a ComputeOp argument.
|
||||
if (llvm::isa<tosa::ReshapeOp>(producerOp)) {
|
||||
drawBiasTileOp(extractSliceOp);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
drawExtractSliceOp(extractSliceOp);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw input node, and edges to it users
|
||||
drawInputNodesAndEdges(func);
|
||||
|
||||
// Draw output node (use the return Operation - argument number=0 - as nodeId)
|
||||
auto returnOp = func.getBody().front().getTerminator();
|
||||
os << '\t' << FORMAT_ARGUMENT(returnOp, 0) << " [label=\"Module Output\",color=green];\n";
|
||||
|
||||
os << "}\n";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<Pass> createSpatialToGraphvizPass() { return std::make_unique<SpatialToGraphvizPass>(); }
|
||||
|
||||
} // namespace onnx_mlir
|
||||
@@ -29,6 +29,11 @@ static bool isUsedOnlyAsExplicitHostOperand(Value value) {
|
||||
});
|
||||
}
|
||||
|
||||
static bool isUsedOnlyByExtractSlices(Value value) {
|
||||
return !value.use_empty()
|
||||
&& llvm::all_of(value.getUsers(), [](Operation* user) { return isa<tensor::ExtractSliceOp>(user); });
|
||||
}
|
||||
|
||||
static FailureOr<unsigned> getDirectReturnOperandIndex(OpResult result) {
|
||||
if (!result.hasOneUse())
|
||||
return failure();
|
||||
@@ -51,12 +56,14 @@ collectFragmentAssemblyCopiesFromBlueprint(spatial::SpatBlueprintOp blueprint,
|
||||
return blueprint.emitOpError("fragment assembly lowering requires static ranked tensor results");
|
||||
|
||||
std::optional<ArrayRef<int64_t>> operandIndicesAttr = blueprint.getFragmentOperandIndices();
|
||||
std::optional<ArrayRef<int64_t>> sourceSlotsAttr = blueprint.getFragmentSourceSlots();
|
||||
std::optional<ArrayRef<int64_t>> fragmentStridesAttr = blueprint.getFragmentStrides();
|
||||
if (!operandIndicesAttr || !fragmentStridesAttr)
|
||||
if (!operandIndicesAttr || !sourceSlotsAttr || !fragmentStridesAttr)
|
||||
return blueprint.emitOpError(
|
||||
"fragment assembly lowering requires explicit operand indices and unit strides");
|
||||
"fragment assembly lowering requires explicit operand indices, source slots, and unit strides");
|
||||
|
||||
ArrayRef<int64_t> operandIndices = *operandIndicesAttr;
|
||||
ArrayRef<int64_t> sourceSlots = *sourceSlotsAttr;
|
||||
std::optional<ArrayRef<int64_t>> sourceOffsetsAttr = blueprint.getFragmentSourceOffsets();
|
||||
if (!sourceOffsetsAttr)
|
||||
return blueprint.emitOpError("fragment assembly lowering requires explicit source offsets");
|
||||
@@ -110,7 +117,11 @@ collectFragmentAssemblyCopiesFromBlueprint(spatial::SpatBlueprintOp blueprint,
|
||||
copy.sourceType = sourceType;
|
||||
copy.hostTargetIndex = hostTargetIndex;
|
||||
copy.lane = lane;
|
||||
copy.sourceByteOffset = (sourceOffsets[fragmentIndex] + relativeSourceOffset) * static_cast<int64_t>(elementSize);
|
||||
copy.sourceByteOffset =
|
||||
(getFragmentAssemblySourceElementOffset(
|
||||
sourceType, sourceSlots[fragmentIndex], sourceOffsets[fragmentIndex])
|
||||
+ relativeSourceOffset)
|
||||
* static_cast<int64_t>(elementSize);
|
||||
copy.hostByteOffset = hostElementOffset * static_cast<int64_t>(elementSize);
|
||||
copy.byteSize = chunkElements * static_cast<int64_t>(elementSize);
|
||||
copies.push_back(copy);
|
||||
@@ -141,7 +152,8 @@ collectTopLevelFragmentAssemblyCopies(OpResult result, RankedTensorType packedRe
|
||||
std::optional<StringRef> mode = blueprint.getMode();
|
||||
std::optional<ArrayRef<int64_t>> operandIndicesAttr = blueprint.getFragmentOperandIndices();
|
||||
std::optional<ArrayRef<int64_t>> sourceOffsetsAttr = blueprint.getFragmentSourceOffsets();
|
||||
if (!mode || *mode != "fragment_assembly" || !operandIndicesAttr || !sourceOffsetsAttr)
|
||||
std::optional<ArrayRef<int64_t>> sourceSlotsAttr = blueprint.getFragmentSourceSlots();
|
||||
if (!mode || *mode != "fragment_assembly" || !operandIndicesAttr || !sourceOffsetsAttr || !sourceSlotsAttr)
|
||||
return failure();
|
||||
if (!blueprint.getOutput().hasOneUse() || !isa<func::ReturnOp>(*blueprint.getOutput().getUsers().begin()))
|
||||
return failure();
|
||||
@@ -153,6 +165,9 @@ collectTopLevelFragmentAssemblyCopies(OpResult result, RankedTensorType packedRe
|
||||
|
||||
ArrayRef<int64_t> operandIndices = *operandIndicesAttr;
|
||||
ArrayRef<int64_t> sourceOffsets = *sourceOffsetsAttr;
|
||||
ArrayRef<int64_t> sourceSlots = *sourceSlotsAttr;
|
||||
if (sourceSlots.size() != operandIndices.size())
|
||||
return failure();
|
||||
ArrayRef<int64_t> flatOffsets = blueprint.getFragmentOffsets();
|
||||
ArrayRef<int64_t> flatSizes = blueprint.getFragmentSizes();
|
||||
ArrayRef<int64_t> flatStrides = *stridesAttr;
|
||||
@@ -174,7 +189,8 @@ collectTopLevelFragmentAssemblyCopies(OpResult result, RankedTensorType packedRe
|
||||
if (operandIndices[fragmentIndex] != static_cast<int64_t>(use.getOperandNumber()))
|
||||
continue;
|
||||
|
||||
int64_t sourceElementOffset = sourceOffsets[fragmentIndex];
|
||||
int64_t sourceElementOffset = getFragmentAssemblySourceElementOffset(
|
||||
packedResultType, sourceSlots[fragmentIndex], sourceOffsets[fragmentIndex]);
|
||||
int64_t lane = sourceElementOffset / payloadElementCount;
|
||||
if (lane < 0 || lane >= static_cast<int64_t>(laneCount))
|
||||
return failure();
|
||||
@@ -352,6 +368,7 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatSchedul
|
||||
rewriter.createBlock(&coreBatchOp.getBody(), coreBatchOp.getBody().end(), TypeRange(blockArgTypes), blockArgLocs);
|
||||
|
||||
IRMapping mapper;
|
||||
SmallPtrSet<Value, 4> hostResidentTensors;
|
||||
rewriter.setInsertionPointToStart(newBlock);
|
||||
auto oldLaneArg = computeBatchOp.getLaneArgument();
|
||||
if (!oldLaneArg)
|
||||
@@ -395,6 +412,11 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatSchedul
|
||||
if (isa<spatial::SpatYieldOp>(op))
|
||||
continue;
|
||||
|
||||
// Cloning a region-bearing operation may leave the rewriter inside that
|
||||
// region. Every old-block operation is lowered at the core-batch body
|
||||
// boundary.
|
||||
rewriter.setInsertionPointToEnd(newBlock);
|
||||
|
||||
if (auto blueprint = dyn_cast<spatial::SpatBlueprintOp>(op)) {
|
||||
std::optional<StringRef> modeAttr = blueprint.getMode();
|
||||
if (modeAttr && *modeAttr == "fragment_assembly") {
|
||||
@@ -513,8 +535,10 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatSchedul
|
||||
if (isa_and_present<memref::GetGlobalOp>(toTensorOp.getBuffer().getDefiningOp())) {
|
||||
Operation* cloned = rewriter.clone(op, mapper);
|
||||
auto clonedTensor = cloned->getResult(0);
|
||||
if (isUsedOnlyAsExplicitHostOperand(toTensorOp.getResult())) {
|
||||
if (isUsedOnlyAsExplicitHostOperand(toTensorOp.getResult())
|
||||
|| isUsedOnlyByExtractSlices(toTensorOp.getResult())) {
|
||||
mapper.map(toTensorOp.getResult(), clonedTensor);
|
||||
hostResidentTensors.insert(toTensorOp.getResult());
|
||||
continue;
|
||||
}
|
||||
auto clonedType = cast<ShapedType>(clonedTensor.getType());
|
||||
@@ -532,6 +556,28 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatSchedul
|
||||
}
|
||||
}
|
||||
|
||||
if (auto extractSlice = dyn_cast<tensor::ExtractSliceOp>(op);
|
||||
extractSlice && hostResidentTensors.contains(extractSlice.getSource())) {
|
||||
Operation* cloned = rewriter.clone(op, mapper);
|
||||
Value hostSlice = cloned->getResult(0);
|
||||
auto outputBuffer = createEmptyTensorFromShaped(rewriter, loc, cast<ShapedType>(hostSlice.getType()));
|
||||
Value zeroOffset = getOrCreateIndexConstant(rewriter, coreBatchOp.getOperation(), 0);
|
||||
auto sizeAttr = getTensorSizeInBytesAttr(rewriter, coreBatchOp.getOperation(), hostSlice);
|
||||
if (failed(sizeAttr))
|
||||
return failure();
|
||||
auto copied = pim::PimMemCopyHostToDevOp::create(rewriter,
|
||||
loc,
|
||||
outputBuffer.getType(),
|
||||
zeroOffset,
|
||||
zeroOffset,
|
||||
outputBuffer,
|
||||
hostSlice,
|
||||
*sizeAttr)
|
||||
.getOutput();
|
||||
mapper.map(extractSlice.getResult(), copied);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto [operandIndex, operand] : llvm::enumerate(op.getOperands())) {
|
||||
if (!isa<TensorType>(operand.getType()) || mapper.contains(operand))
|
||||
continue;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "mlir/IR/ValueRange.h"
|
||||
|
||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
|
||||
@@ -8,6 +9,8 @@
|
||||
#include <limits>
|
||||
|
||||
#include "Common.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/AffineUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||
@@ -57,6 +60,23 @@ bool hasLaterUserInBlock(mlir::Value value, Operation* operation) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool isTensorView(mlir::Value value) {
|
||||
return isa_and_nonnull<tensor::CastOp,
|
||||
tensor::CollapseShapeOp,
|
||||
tensor::ExpandShapeOp,
|
||||
tensor::ExtractSliceOp,
|
||||
tensor::ReshapeOp>(value.getDefiningOp());
|
||||
}
|
||||
|
||||
static bool isLoopCarriedOutput(mlir::Value operand, Operation* operation) {
|
||||
auto argument = dyn_cast<BlockArgument>(operand);
|
||||
if (!argument || argument.getArgNumber() == 0 || operation->getBlock() != argument.getOwner())
|
||||
return false;
|
||||
auto loop = dyn_cast_or_null<scf::ForOp>(argument.getOwner()->getParentOp());
|
||||
return loop && cast<scf::YieldOp>(loop.getBody()->getTerminator())
|
||||
.getOperand(argument.getArgNumber() - 1) == operation->getResult(0);
|
||||
}
|
||||
|
||||
mlir::Value getBestOutputTensorFromOperandsOrAllocate(RewriterBase& rewriter, Operation* operation) {
|
||||
assert("Only support operations with a single result" && operation->getNumResults() == 1);
|
||||
mlir::Value result = operation->getResult(0);
|
||||
@@ -65,7 +85,11 @@ mlir::Value getBestOutputTensorFromOperandsOrAllocate(RewriterBase& rewriter, Op
|
||||
|
||||
SmallVector<mlir::Value> operands = getOpOperandsSortedByUses(operation);
|
||||
auto validOperands = make_filter_range(operands, [operation, resultType](mlir::Value operand) {
|
||||
return operand.getType() == resultType && !hasLaterUserInBlock(operand, operation);
|
||||
return operand.getType() == resultType
|
||||
&& (!isa<BlockArgument>(operand) || isLoopCarriedOutput(operand, operation))
|
||||
&& !operand.getDefiningOp<arith::ConstantOp>()
|
||||
&& !isTensorView(operand)
|
||||
&& !hasLaterUserInBlock(operand, operation);
|
||||
});
|
||||
auto bestOperand = validOperands.begin();
|
||||
|
||||
@@ -191,22 +215,32 @@ forEachContiguousDestinationChunk(ArrayRef<int64_t> destShape,
|
||||
return visit(visit, 0);
|
||||
}
|
||||
|
||||
int64_t getFragmentAssemblySourceElementOffset(RankedTensorType sourceType,
|
||||
int64_t sourceSlot,
|
||||
int64_t sourceOffset) {
|
||||
assert(sourceType.getRank() > 0 && sourceType.hasStaticShape()
|
||||
&& "fragment assembly source must have a static leading slot dimension");
|
||||
return sourceSlot * (sourceType.getNumElements() / sourceType.getDimSize(0)) + sourceOffset;
|
||||
}
|
||||
|
||||
static mlir::Value
|
||||
createSteppedOffset(OpBuilder& builder, Location loc, mlir::Value start, mlir::Value index, int64_t stepBytes) {
|
||||
createSteppedOffset(OpBuilder& builder, Location loc, mlir::Value start, mlir::Value index,
|
||||
int64_t stepBytes, Operation *constantAnchor) {
|
||||
if (stepBytes == 0)
|
||||
return start;
|
||||
mlir::Value step = arith::ConstantIndexOp::create(builder, loc, stepBytes);
|
||||
mlir::Value scaled = arith::MulIOp::create(builder, loc, index, step).getResult();
|
||||
return arith::AddIOp::create(builder, loc, start, scaled).getResult();
|
||||
return createOrFoldAffineApply(
|
||||
builder, loc, builder.getAffineDimExpr(0) + builder.getAffineDimExpr(1) * stepBytes,
|
||||
ValueRange {start, index}, constantAnchor);
|
||||
}
|
||||
|
||||
static mlir::Value createIndexedOffset(OpBuilder& builder,
|
||||
Location loc,
|
||||
mlir::Value indexArg,
|
||||
ArrayRef<int64_t> values) {
|
||||
ArrayRef<int64_t> values,
|
||||
Operation *constantAnchor) {
|
||||
assert(!values.empty() && "expected lane-indexed values");
|
||||
if (llvm::all_of(values.drop_front(), [&](int64_t value) { return value == values.front(); }))
|
||||
return arith::ConstantIndexOp::create(builder, loc, values.front());
|
||||
return getOrCreateIndexConstant(builder, constantAnchor, values.front());
|
||||
|
||||
if (values.size() >= 2) {
|
||||
int64_t step = values[1] - values[0];
|
||||
@@ -214,21 +248,18 @@ static mlir::Value createIndexedOffset(OpBuilder& builder,
|
||||
return values[index] == values.front() + static_cast<int64_t>(index) * step;
|
||||
});
|
||||
if (arithmetic) {
|
||||
mlir::Value base = arith::ConstantIndexOp::create(builder, loc, values.front());
|
||||
mlir::Value stepValue = arith::ConstantIndexOp::create(builder, loc, step);
|
||||
mlir::Value scaledIndex = arith::MulIOp::create(builder, loc, indexArg, stepValue).getResult();
|
||||
return arith::AddIOp::create(builder, loc, base, scaledIndex).getResult();
|
||||
return createOrFoldAffineApply(
|
||||
builder, loc, builder.getAffineDimExpr(0) * step + values.front(),
|
||||
ValueRange {indexArg}, constantAnchor);
|
||||
}
|
||||
}
|
||||
|
||||
mlir::Value selected = arith::ConstantIndexOp::create(builder, loc, values.front());
|
||||
for (auto [lane, value] : llvm::enumerate(values.drop_front())) {
|
||||
mlir::Value indexValue = arith::ConstantIndexOp::create(builder, loc, static_cast<int64_t>(lane + 1));
|
||||
mlir::Value cmp = arith::CmpIOp::create(builder, loc, arith::CmpIPredicate::eq, indexArg, indexValue);
|
||||
mlir::Value candidate = arith::ConstantIndexOp::create(builder, loc, value);
|
||||
selected = arith::SelectOp::create(builder, loc, cmp, candidate, selected);
|
||||
}
|
||||
return selected;
|
||||
RankedTensorType tableType = RankedTensorType::get(
|
||||
{static_cast<int64_t>(values.size())}, builder.getI64Type());
|
||||
DenseElementsAttr tableAttr = DenseElementsAttr::get(tableType, values);
|
||||
mlir::Value table = getOrCreateConstant(builder, constantAnchor, tableAttr, tableType);
|
||||
mlir::Value selected = tensor::ExtractOp::create(builder, loc, table, ValueRange {indexArg});
|
||||
return arith::IndexCastOp::create(builder, loc, builder.getIndexType(), selected).getResult();
|
||||
}
|
||||
|
||||
struct FragmentAssemblyCopyRunFamily {
|
||||
@@ -433,11 +464,11 @@ static FailureOr<mlir::Value> emitFragmentAssemblyCopyRun(OpBuilder& builder,
|
||||
mlir::Value hostStart;
|
||||
mlir::Value sourceStart;
|
||||
if (laneArg) {
|
||||
hostStart = createIndexedOffset(builder, loc, *laneArg, run.hostStartBytesByLane);
|
||||
sourceStart = createIndexedOffset(builder, loc, *laneArg, run.sourceStartBytesByLane);
|
||||
hostStart = createIndexedOffset(builder, loc, *laneArg, run.hostStartBytesByLane, anchor);
|
||||
sourceStart = createIndexedOffset(builder, loc, *laneArg, run.sourceStartBytesByLane, anchor);
|
||||
} else {
|
||||
hostStart = arith::ConstantIndexOp::create(builder, loc, run.hostStartBytesByLane.front());
|
||||
sourceStart = arith::ConstantIndexOp::create(builder, loc, run.sourceStartBytesByLane.front());
|
||||
hostStart = getOrCreateIndexConstant(builder, anchor, run.hostStartBytesByLane.front());
|
||||
sourceStart = getOrCreateIndexConstant(builder, anchor, run.sourceStartBytesByLane.front());
|
||||
}
|
||||
|
||||
if (hostRunStartDelta)
|
||||
@@ -459,9 +490,9 @@ static FailureOr<mlir::Value> emitFragmentAssemblyCopyRun(OpBuilder& builder,
|
||||
.getOutput();
|
||||
}
|
||||
|
||||
mlir::Value lowerBound = arith::ConstantIndexOp::create(builder, loc, 0);
|
||||
mlir::Value upperBound = arith::ConstantIndexOp::create(builder, loc, run.count);
|
||||
mlir::Value step = arith::ConstantIndexOp::create(builder, loc, 1);
|
||||
mlir::Value lowerBound = getOrCreateIndexConstant(builder, anchor, 0);
|
||||
mlir::Value upperBound = getOrCreateIndexConstant(builder, anchor, run.count);
|
||||
mlir::Value step = getOrCreateIndexConstant(builder, anchor, 1);
|
||||
FailureOr<NormalizedLoopResult> loop = buildNormalizedScfFor(
|
||||
builder,
|
||||
loc,
|
||||
@@ -474,9 +505,10 @@ static FailureOr<mlir::Value> emitFragmentAssemblyCopyRun(OpBuilder& builder,
|
||||
mlir::Value flatIndex,
|
||||
ValueRange iterArgs,
|
||||
SmallVectorImpl<mlir::Value>& yielded) {
|
||||
mlir::Value hostOffset = createSteppedOffset(loopBuilder, bodyLoc, hostStart, flatIndex, run.hostStepBytes);
|
||||
mlir::Value hostOffset = createSteppedOffset(
|
||||
loopBuilder, bodyLoc, hostStart, flatIndex, run.hostStepBytes, anchor);
|
||||
mlir::Value sourceOffset =
|
||||
createSteppedOffset(loopBuilder, bodyLoc, sourceStart, flatIndex, run.sourceStepBytes);
|
||||
createSteppedOffset(loopBuilder, bodyLoc, sourceStart, flatIndex, run.sourceStepBytes, anchor);
|
||||
mlir::Value copied =
|
||||
pim::PimMemCopyDevToHostOp::create(loopBuilder,
|
||||
bodyLoc,
|
||||
@@ -506,9 +538,9 @@ static FailureOr<mlir::Value> emitFragmentAssemblyCopyRunFamily(OpBuilder& build
|
||||
return emitFragmentAssemblyCopyRun(
|
||||
builder, loc, family.prototype, hostTarget, anchor, laneArg, baseHostOffset);
|
||||
|
||||
mlir::Value lowerBound = arith::ConstantIndexOp::create(builder, loc, 0);
|
||||
mlir::Value upperBound = arith::ConstantIndexOp::create(builder, loc, family.sourceRunStartDeltas.size());
|
||||
mlir::Value step = arith::ConstantIndexOp::create(builder, loc, 1);
|
||||
mlir::Value lowerBound = getOrCreateIndexConstant(builder, anchor, 0);
|
||||
mlir::Value upperBound = getOrCreateIndexConstant(builder, anchor, family.sourceRunStartDeltas.size());
|
||||
mlir::Value step = getOrCreateIndexConstant(builder, anchor, 1);
|
||||
FailureOr<NormalizedLoopResult> outerLoop = buildNormalizedScfFor(
|
||||
builder,
|
||||
loc,
|
||||
@@ -522,9 +554,9 @@ static FailureOr<mlir::Value> emitFragmentAssemblyCopyRunFamily(OpBuilder& build
|
||||
ValueRange iterArgs,
|
||||
SmallVectorImpl<mlir::Value>& yielded) {
|
||||
mlir::Value sourceRunStartDelta =
|
||||
createIndexedOffset(loopBuilder, bodyLoc, runIndex, family.sourceRunStartDeltas);
|
||||
createIndexedOffset(loopBuilder, bodyLoc, runIndex, family.sourceRunStartDeltas, anchor);
|
||||
mlir::Value hostRunStartDelta =
|
||||
createIndexedOffset(loopBuilder, bodyLoc, runIndex, family.hostRunStartDeltas);
|
||||
createIndexedOffset(loopBuilder, bodyLoc, runIndex, family.hostRunStartDeltas, anchor);
|
||||
FailureOr<mlir::Value> copied = emitFragmentAssemblyCopyRun(loopBuilder,
|
||||
bodyLoc,
|
||||
family.prototype,
|
||||
|
||||
@@ -65,6 +65,10 @@ forEachContiguousDestinationChunk(llvm::ArrayRef<int64_t> destShape,
|
||||
llvm::function_ref<mlir::LogicalResult(llvm::ArrayRef<int64_t>, int64_t, int64_t)>
|
||||
callback);
|
||||
|
||||
int64_t getFragmentAssemblySourceElementOffset(mlir::RankedTensorType sourceType,
|
||||
int64_t sourceSlot,
|
||||
int64_t sourceOffset);
|
||||
|
||||
struct FragmentAssemblyCopy {
|
||||
mlir::Value source;
|
||||
mlir::RankedTensorType sourceType;
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
#include "Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||
#include "Conversion/SpatialToPim/SpatialToPimPass.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ShapingUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/SpatialToPim/Common.hpp"
|
||||
@@ -42,13 +44,15 @@ static FailureOr<Value> lowerFragmentAssemblyBlueprint(IRRewriter& rewriter,
|
||||
|
||||
std::optional<StringRef> modeAttr = blueprint.getMode();
|
||||
std::optional<ArrayRef<int64_t>> operandIndicesAttr = blueprint.getFragmentOperandIndices();
|
||||
std::optional<ArrayRef<int64_t>> sourceSlotsAttr = blueprint.getFragmentSourceSlots();
|
||||
std::optional<ArrayRef<int64_t>> sourceOffsetsAttr = blueprint.getFragmentSourceOffsets();
|
||||
std::optional<ArrayRef<int64_t>> fragmentStridesAttr = blueprint.getFragmentStrides();
|
||||
if (!modeAttr || *modeAttr != "fragment_assembly" || !operandIndicesAttr || !sourceOffsetsAttr
|
||||
|| !fragmentStridesAttr)
|
||||
if (!modeAttr || *modeAttr != "fragment_assembly" || !operandIndicesAttr || !sourceSlotsAttr
|
||||
|| !sourceOffsetsAttr || !fragmentStridesAttr)
|
||||
return blueprint.emitOpError("fragment assembly lowering requires explicit fragment metadata");
|
||||
|
||||
ArrayRef<int64_t> operandIndices = *operandIndicesAttr;
|
||||
ArrayRef<int64_t> sourceSlots = *sourceSlotsAttr;
|
||||
ArrayRef<int64_t> sourceOffsets = *sourceOffsetsAttr;
|
||||
ArrayRef<int64_t> flatOffsets = blueprint.getFragmentOffsets();
|
||||
ArrayRef<int64_t> flatSizes = blueprint.getFragmentSizes();
|
||||
@@ -100,7 +104,10 @@ static FailureOr<Value> lowerFragmentAssemblyBlueprint(IRRewriter& rewriter,
|
||||
copy.source = source;
|
||||
copy.sourceType = sourceType;
|
||||
copy.sourceByteOffset =
|
||||
(sourceOffsets[fragmentIndex] + relativeSourceOffset) * static_cast<int64_t>(elementSize);
|
||||
(getFragmentAssemblySourceElementOffset(
|
||||
sourceType, sourceSlots[fragmentIndex], sourceOffsets[fragmentIndex])
|
||||
+ relativeSourceOffset)
|
||||
* static_cast<int64_t>(elementSize);
|
||||
copy.hostByteOffset = hostElementOffset * static_cast<int64_t>(elementSize);
|
||||
copy.byteSize = chunkElements * static_cast<int64_t>(elementSize);
|
||||
copies.push_back(copy);
|
||||
@@ -180,16 +187,79 @@ static LogicalResult collectHelperComputeChain(spatial::SpatScheduledCompute com
|
||||
return success();
|
||||
}
|
||||
|
||||
static bool isHostMaterializableHelperOp(Operation* op) {
|
||||
if (isa<spatial::SpatYieldOp>(op))
|
||||
return true;
|
||||
if (isa<arith::ConstantOp>(op) || op->hasTrait<OpTrait::ConstantLike>())
|
||||
return true;
|
||||
if (auto blueprint = dyn_cast<spatial::SpatBlueprintOp>(op)) {
|
||||
std::optional<StringRef> mode = blueprint.getMode();
|
||||
return mode && *mode == "fragment_assembly";
|
||||
}
|
||||
return isShapingOnlyOp(op) || isPureIndexComputationOp(op);
|
||||
}
|
||||
|
||||
static FailureOr<DenseMap<Value, Attribute>>
|
||||
analyzeHostMaterializableHelper(spatial::SpatScheduledCompute computeOp) {
|
||||
DenseMap<Value, Attribute> folded;
|
||||
for (auto [weightIndex, weight] : llvm::enumerate(computeOp.getWeights())) {
|
||||
auto argument = computeOp.getWeightArgument(weightIndex);
|
||||
if (!argument)
|
||||
return failure();
|
||||
Attribute constant;
|
||||
if (matchPattern(weight, m_Constant(&constant)))
|
||||
folded[*argument] = constant;
|
||||
}
|
||||
Block& block = computeOp.getBody().front();
|
||||
for (Operation& op : block) {
|
||||
if (!isHostMaterializableHelperOp(&op))
|
||||
return failure();
|
||||
if (isa<spatial::SpatYieldOp, spatial::SpatBlueprintOp>(op)
|
||||
|| (isShapingOnlyOp(&op) && !isPureIndexComputationOp(&op)))
|
||||
continue;
|
||||
if (isa<arith::ConstantOp>(op) || op.hasTrait<OpTrait::ConstantLike>()) {
|
||||
for (Value result : op.getResults()) {
|
||||
Attribute constant;
|
||||
if (!matchPattern(result, m_Constant(&constant)))
|
||||
return failure();
|
||||
folded[result] = constant;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!isPureIndexComputationOp(&op) || op.getNumRegions() != 0)
|
||||
return failure();
|
||||
SmallVector<Attribute> operands;
|
||||
for (Value operand : op.getOperands()) {
|
||||
auto it = folded.find(operand);
|
||||
if (it == folded.end())
|
||||
return failure();
|
||||
operands.push_back(it->second);
|
||||
}
|
||||
SmallVector<OpFoldResult> results;
|
||||
if (failed(op.fold(operands, results))
|
||||
|| results.size() != op.getNumResults())
|
||||
return failure();
|
||||
for (auto [result, foldResult] : llvm::zip(op.getResults(), results)) {
|
||||
auto attribute = dyn_cast<Attribute>(foldResult);
|
||||
if (!attribute)
|
||||
return failure();
|
||||
folded[result] = attribute;
|
||||
}
|
||||
}
|
||||
return folded;
|
||||
}
|
||||
|
||||
static bool inlineInputlessHelperComputeForWeightLikeUsers(spatial::SpatScheduledCompute computeOp,
|
||||
IRRewriter& rewriter,
|
||||
OperationFolder& constantFolder) {
|
||||
if (!computeOp.getInputs().empty() || computeOp.getNumResults() != 1)
|
||||
return false;
|
||||
if (computeOp.getResult(0).use_empty())
|
||||
return false;
|
||||
if (!llvm::all_of(computeOp.getResult(0).getUsers(), [](Operation* user) {
|
||||
return isa<spatial::SpatScheduledCompute, spatial::SpatScheduledComputeBatch, pim::PimCoreOp, pim::PimCoreBatchOp>(user);
|
||||
}))
|
||||
return false;
|
||||
|
||||
Block& block = computeOp.getBody().front();
|
||||
if (block.getNumArguments() != computeOp.getWeights().size())
|
||||
return false;
|
||||
@@ -197,6 +267,9 @@ static bool inlineInputlessHelperComputeForWeightLikeUsers(spatial::SpatSchedule
|
||||
auto yieldOp = dyn_cast<spatial::SpatYieldOp>(block.getTerminator());
|
||||
if (!yieldOp || yieldOp.getNumOperands() != 1)
|
||||
return false;
|
||||
auto folded = analyzeHostMaterializableHelper(computeOp);
|
||||
if (failed(folded))
|
||||
return false;
|
||||
|
||||
rewriter.setInsertionPoint(computeOp);
|
||||
IRMapping mapping;
|
||||
@@ -218,6 +291,20 @@ static bool inlineInputlessHelperComputeForWeightLikeUsers(spatial::SpatSchedule
|
||||
}
|
||||
}
|
||||
|
||||
if (isa<arith::ConstantOp>(op) || op.hasTrait<OpTrait::ConstantLike>()
|
||||
|| isPureIndexComputationOp(&op)) {
|
||||
for (Value result : op.getResults()) {
|
||||
auto it = folded->find(result);
|
||||
if (it == folded->end())
|
||||
return false;
|
||||
mapping.map(
|
||||
result,
|
||||
getOrCreateConstant(constantFolder, computeOp, it->second,
|
||||
result.getType()));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
cloneMappedHelperOperands(&op, mapping, rewriter, constantFolder);
|
||||
Operation* clonedOp = rewriter.clone(op, mapping);
|
||||
for (auto [originalResult, newResult] : llvm::zip(op.getResults(), clonedOp->getResults()))
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||
|
||||
#include "src/Accelerators/PIM/Conversion/SpatialToPim/Common.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/SpatialToPim/Patterns.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
|
||||
@@ -18,6 +19,30 @@ static void copyRaptorDebugAttrs(Operation* source, Operation* target) {
|
||||
}
|
||||
}
|
||||
|
||||
static Value createDestinationByteOffset(PatternRewriter& rewriter,
|
||||
tensor::InsertSliceOp insert) {
|
||||
auto destinationType = cast<RankedTensorType>(insert.getDestType());
|
||||
SmallVector<int64_t> strides = computeRowMajorStrides(destinationType.getShape());
|
||||
int64_t elementBytes = getElementTypeSizeInBytes(destinationType.getElementType());
|
||||
Value total = arith::ConstantIndexOp::create(rewriter, insert.getLoc(), 0);
|
||||
for (auto [dimension, offset] : llvm::enumerate(insert.getMixedOffsets())) {
|
||||
int64_t scale = strides[dimension] * elementBytes;
|
||||
Value component;
|
||||
if (auto attribute = dyn_cast<Attribute>(offset)) {
|
||||
component = arith::ConstantIndexOp::create(
|
||||
rewriter, insert.getLoc(), cast<IntegerAttr>(attribute).getInt() * scale);
|
||||
} else {
|
||||
component = cast<Value>(offset);
|
||||
if (scale != 1)
|
||||
component = arith::MulIOp::create(
|
||||
rewriter, insert.getLoc(), component,
|
||||
arith::ConstantIndexOp::create(rewriter, insert.getLoc(), scale));
|
||||
}
|
||||
total = arith::AddIOp::create(rewriter, insert.getLoc(), total, component);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
struct ChannelSendLowering : OpRewritePattern<spatial::SpatChannelSendOp> {
|
||||
using OpRewritePattern::OpRewritePattern;
|
||||
|
||||
@@ -40,7 +65,21 @@ struct ChannelReceiveLowering : OpRewritePattern<spatial::SpatChannelReceiveOp>
|
||||
rewriter.eraseOp(op);
|
||||
return success();
|
||||
}
|
||||
auto outputType = cast<ShapedType>(op.getResult().getType());
|
||||
auto outputType = cast<RankedTensorType>(op.getResult().getType());
|
||||
tensor::InsertSliceOp destinationInsert;
|
||||
if (op->hasOneUse()) {
|
||||
auto insert = dyn_cast<tensor::InsertSliceOp>(*op->getUsers().begin());
|
||||
auto destinationType = insert
|
||||
? dyn_cast<RankedTensorType>(insert.getDestType()) : RankedTensorType();
|
||||
if (insert && insert.getSource() == op.getOutput()
|
||||
&& insert.getSourceType() == outputType
|
||||
&& insert->getBlock() == op->getBlock() && destinationType
|
||||
&& destinationType.hasStaticShape()
|
||||
&& isContiguousSubviewWithDynamicOffsets(
|
||||
destinationType.getShape(), insert.getMixedOffsets(),
|
||||
insert.getStaticSizes(), insert.getStaticStrides()))
|
||||
destinationInsert = insert;
|
||||
}
|
||||
Value outputBuffer =
|
||||
tensor::EmptyOp::create(rewriter, op.getLoc(), outputType.getShape(), outputType.getElementType()).getResult();
|
||||
auto sizeAttr = getTensorSizeInBytesAttr(rewriter, op.getOperation(), op.getResult());
|
||||
@@ -50,9 +89,21 @@ struct ChannelReceiveLowering : OpRewritePattern<spatial::SpatChannelReceiveOp>
|
||||
rewriter, op.getLoc(), op.getResult().getType(), outputBuffer, *sizeAttr, op.getSourceCoreId());
|
||||
copyRaptorDebugAttrs(op.getOperation(), receive.getOperation());
|
||||
Value received = receive.getOutput();
|
||||
if (!destinationInsert) {
|
||||
rewriter.replaceOp(op, received);
|
||||
return success();
|
||||
}
|
||||
|
||||
rewriter.setInsertionPoint(destinationInsert);
|
||||
Value targetOffset = createDestinationByteOffset(rewriter, destinationInsert);
|
||||
Value zero = arith::ConstantIndexOp::create(rewriter, op.getLoc(), 0);
|
||||
auto copy = pim::PimMemCopyOp::create(
|
||||
rewriter, op.getLoc(), destinationInsert.getDestType(), targetOffset, zero,
|
||||
destinationInsert.getDest(), received, *sizeAttr);
|
||||
rewriter.replaceOp(destinationInsert, copy.getOutput());
|
||||
rewriter.eraseOp(op);
|
||||
return success();
|
||||
}
|
||||
};
|
||||
|
||||
struct ExtractRowsLowering : OpRewritePattern<spatial::SpatExtractRowsOp> {
|
||||
|
||||
@@ -608,11 +608,12 @@ raptor::SpatialToPimPass::ReturnPathLoweringResult raptor::SpatialToPimPass::low
|
||||
for (auto [blueprint, operandNumber] : *fragmentAssemblyUses) {
|
||||
rewriter.setInsertionPointAfterValue(storedValue);
|
||||
std::optional<ArrayRef<int64_t>> operandIndicesAttr = blueprint.getFragmentOperandIndices();
|
||||
std::optional<ArrayRef<int64_t>> sourceSlotsAttr = blueprint.getFragmentSourceSlots();
|
||||
std::optional<ArrayRef<int64_t>> sourceOffsetsAttr = blueprint.getFragmentSourceOffsets();
|
||||
std::optional<ArrayRef<int64_t>> stridesAttr = blueprint.getFragmentStrides();
|
||||
if (!operandIndicesAttr || !sourceOffsetsAttr || !stridesAttr) {
|
||||
if (!operandIndicesAttr || !sourceSlotsAttr || !sourceOffsetsAttr || !stridesAttr) {
|
||||
blueprint.emitOpError(
|
||||
"fragment assembly lowering requires explicit operand, source-offset, and stride metadata");
|
||||
"fragment assembly lowering requires explicit operand, source-slot, source-offset, and stride metadata");
|
||||
return ReturnPathLoweringResult::Failure;
|
||||
}
|
||||
|
||||
@@ -626,6 +627,7 @@ raptor::SpatialToPimPass::ReturnPathLoweringResult raptor::SpatialToPimPass::low
|
||||
}
|
||||
|
||||
ArrayRef<int64_t> operandIndices = *operandIndicesAttr;
|
||||
ArrayRef<int64_t> sourceSlots = *sourceSlotsAttr;
|
||||
ArrayRef<int64_t> sourceOffsets = *sourceOffsetsAttr;
|
||||
ArrayRef<int64_t> flatOffsets = blueprint.getFragmentOffsets();
|
||||
ArrayRef<int64_t> flatSizes = blueprint.getFragmentSizes();
|
||||
@@ -668,7 +670,9 @@ raptor::SpatialToPimPass::ReturnPathLoweringResult raptor::SpatialToPimPass::low
|
||||
elementSize,
|
||||
producerOp,
|
||||
"fragment assembly host offset");
|
||||
auto sourceOffset = getCheckedByteOffset(sourceOffsets[fragmentIndex] + relativeSourceOffset,
|
||||
int64_t sourceElementOffset = getFragmentAssemblySourceElementOffset(
|
||||
sourceType, sourceSlots[fragmentIndex], sourceOffsets[fragmentIndex]);
|
||||
auto sourceOffset = getCheckedByteOffset(sourceElementOffset + relativeSourceOffset,
|
||||
elementSize,
|
||||
producerOp,
|
||||
"fragment assembly source offset");
|
||||
|
||||
@@ -12,7 +12,7 @@ include "src/Accelerators/PIM/Dialect/Pim/Pim.td"
|
||||
def spatToPimVMM : Pat<
|
||||
(SpatVMMOp:$srcOpRes $weight, $vector),
|
||||
(PimVMMOp $weight, $vector,
|
||||
(NativeCodeCall<"onnx_mlir::getBestOutputTensorFromOperandsOrAllocate($_builder, $0.getDefiningOp())"> $srcOpRes))
|
||||
(NativeCodeCall<"tensor::EmptyOp::create($_builder, $_loc, cast<ShapedType>($0.getType()).getShape(), cast<ShapedType>($0.getType()).getElementType())"> $srcOpRes))
|
||||
>;
|
||||
|
||||
def spatToPimVVDMul : Pat<
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "mlir/Conversion/AffineToStandard/AffineToStandard.h"
|
||||
#include "mlir/Dialect/Affine/IR/AffineOps.h"
|
||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||
#include "mlir/Dialect/Bufferization/IR/Bufferization.h"
|
||||
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
||||
#include "mlir/Dialect/Linalg/IR/Linalg.h"
|
||||
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||
#include "mlir/Dialect/SCF/Utils/Utils.h"
|
||||
@@ -113,7 +113,9 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
|
||||
memref::MemRefDialect,
|
||||
scf::SCFDialect,
|
||||
BuiltinDialect>();
|
||||
target.addLegalOp<spatial::SpatConcatOp,
|
||||
target.addLegalOp<linalg::MapOp,
|
||||
linalg::YieldOp,
|
||||
spatial::SpatConcatOp,
|
||||
spatial::SpatChannelReceiveOp,
|
||||
spatial::SpatChannelSendOp,
|
||||
spatial::SpatExtractRowsOp>();
|
||||
@@ -175,11 +177,11 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
|
||||
|
||||
RewritePatternSet coreBodyPatterns(ctx);
|
||||
populateCoreBodyPatterns(coreBodyPatterns);
|
||||
populateAffineToStdConversionPatterns(coreBodyPatterns);
|
||||
FrozenRewritePatternSet frozenCoreBodyPatterns(std::move(coreBodyPatterns));
|
||||
|
||||
ConversionTarget coreBodyTarget(*ctx);
|
||||
coreBodyTarget.addLegalDialect<PimDialect,
|
||||
coreBodyTarget.addLegalDialect<affine::AffineDialect,
|
||||
PimDialect,
|
||||
tensor::TensorDialect,
|
||||
arith::ArithDialect,
|
||||
bufferization::BufferizationDialect,
|
||||
@@ -187,7 +189,9 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
|
||||
memref::MemRefDialect,
|
||||
scf::SCFDialect,
|
||||
BuiltinDialect>();
|
||||
coreBodyTarget.addLegalOp<spatial::SpatConcatOp,
|
||||
coreBodyTarget.addLegalOp<linalg::MapOp,
|
||||
linalg::YieldOp,
|
||||
spatial::SpatConcatOp,
|
||||
spatial::SpatChannelReceiveOp,
|
||||
spatial::SpatChannelSendOp,
|
||||
spatial::SpatExtractRowsOp>();
|
||||
@@ -226,7 +230,8 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
|
||||
eraseUnusedTensorPackingOps(funcOp, rewriter);
|
||||
|
||||
ConversionTarget communicationTarget(*ctx);
|
||||
communicationTarget.addLegalDialect<PimDialect,
|
||||
communicationTarget.addLegalDialect<affine::AffineDialect,
|
||||
PimDialect,
|
||||
tensor::TensorDialect,
|
||||
arith::ArithDialect,
|
||||
bufferization::BufferizationDialect,
|
||||
@@ -234,7 +239,7 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
|
||||
memref::MemRefDialect,
|
||||
scf::SCFDialect,
|
||||
BuiltinDialect>();
|
||||
communicationTarget.addLegalOp<ModuleOp>();
|
||||
communicationTarget.addLegalOp<ModuleOp, linalg::MapOp, linalg::YieldOp>();
|
||||
communicationTarget.addIllegalOp<spatial::SpatConcatOp,
|
||||
spatial::SpatChannelReceiveOp,
|
||||
spatial::SpatChannelSendOp,
|
||||
|
||||
@@ -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_doc(pim Pim.td)
|
||||
|
||||
add_subdirectory(Analysis)
|
||||
add_subdirectory(Transforms/Bufferization)
|
||||
add_subdirectory(Transforms/MemoryCoalescing)
|
||||
add_subdirectory(Transforms/HostConstantFolding)
|
||||
add_subdirectory(Transforms/MemoryCoalescing)
|
||||
add_subdirectory(Transforms/LocalMemoryPlanning)
|
||||
add_subdirectory(Transforms/Verification)
|
||||
|
||||
add_pim_library(PimOps
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
#include "src/Accelerators/PIM/Common/IR/AddressAnalysis.hpp"
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Pim/Transforms/Bufferization/BufferizationUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Pim/Transforms/Bufferization/Common.hpp"
|
||||
|
||||
@@ -25,25 +23,8 @@ FailureOr<Value> materializeContiguousInputMemRef(Value memrefValue,
|
||||
auto shapedType = cast<ShapedType>(memrefValue.getType());
|
||||
auto contiguousType = MemRefType::get(shapedType.getShape(), shapedType.getElementType());
|
||||
Value contiguousBuffer = memref::AllocOp::create(rewriter, loc, contiguousType);
|
||||
auto sizeInBytes =
|
||||
getCheckedShapedTypeSizeInBytes(shapedType, contiguousBuffer.getDefiningOp(), "contiguous copy byte size");
|
||||
if (failed(sizeInBytes))
|
||||
return failure();
|
||||
Value zeroOffset = getOrCreateIndexConstant(rewriter, contiguousBuffer.getDefiningOp(), 0);
|
||||
auto sizeAttr =
|
||||
getCheckedI32Attr(rewriter, contiguousBuffer.getDefiningOp(), *sizeInBytes, "contiguous copy byte size");
|
||||
if (failed(sizeAttr))
|
||||
return failure();
|
||||
|
||||
if (isHostBackedPimAddress(memrefValue, knowledge)) {
|
||||
return PimMemCopyHostToDevOp::create(
|
||||
rewriter, loc, contiguousType, zeroOffset, zeroOffset, contiguousBuffer, memrefValue, *sizeAttr)
|
||||
.getOutput();
|
||||
}
|
||||
|
||||
return PimMemCopyOp::create(
|
||||
rewriter, loc, contiguousType, zeroOffset, zeroOffset, contiguousBuffer, memrefValue, *sizeAttr)
|
||||
.getOutput();
|
||||
memref::CopyOp::create(rewriter, loc, memrefValue, contiguousBuffer);
|
||||
return contiguousBuffer;
|
||||
}
|
||||
|
||||
Value allocateContiguousResultMemRefLike(Value memrefValue, Location loc, RewriterBase& rewriter) {
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
#include "Dialect/Pim/Transforms/Bufferization/Common.hpp"
|
||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
||||
|
||||
using namespace mlir;
|
||||
|
||||
static SmallVector<Region *> getSelectionRegions(OpResult result) {
|
||||
SmallVector<Region *> regions;
|
||||
if (auto selection = dyn_cast<scf::IndexSwitchOp>(result.getOwner()))
|
||||
for (Region ®ion : selection->getRegions())
|
||||
regions.push_back(®ion);
|
||||
else if (auto selection = dyn_cast<scf::IfOp>(result.getOwner())) {
|
||||
regions.push_back(&selection.getThenRegion());
|
||||
regions.push_back(&selection.getElseRegion());
|
||||
}
|
||||
return regions;
|
||||
}
|
||||
|
||||
static bool isCoreBatchInputArgument(Value value) {
|
||||
auto blockArg = dyn_cast<BlockArgument>(value);
|
||||
if (!blockArg)
|
||||
@@ -92,20 +105,46 @@ FailureOr<Value> onnx_mlir::pim::getPimAddressBase(Value value, const StaticValu
|
||||
}
|
||||
|
||||
bool onnx_mlir::pim::isHostBackedPimAddress(Value value, const StaticValueKnowledge& knowledge) {
|
||||
auto base = getPimStorageBase(value, knowledge);
|
||||
if (failed(base))
|
||||
llvm::SmallPtrSet<Value, 8> visited;
|
||||
std::function<bool(Value)> isHost = [&](Value current) {
|
||||
auto base = getPimStorageBase(current, knowledge);
|
||||
if (failed(base) || !visited.insert(*base).second)
|
||||
return false;
|
||||
|
||||
if (isCoreBatchInputArgument(*base))
|
||||
return true;
|
||||
|
||||
return isa_and_nonnull<memref::GetGlobalOp>(base->getDefiningOp());
|
||||
bool resultIsHost = isCoreBatchInputArgument(*base)
|
||||
|| isa_and_nonnull<memref::GetGlobalOp>(base->getDefiningOp());
|
||||
auto result = dyn_cast<OpResult>(*base);
|
||||
SmallVector<Region *> regions = result ? getSelectionRegions(result)
|
||||
: SmallVector<Region *>();
|
||||
if (!resultIsHost && !regions.empty())
|
||||
resultIsHost = llvm::all_of(regions, [&](Region *region) {
|
||||
auto yield = dyn_cast<scf::YieldOp>(region->front().getTerminator());
|
||||
return yield && result.getResultNumber() < yield.getNumOperands()
|
||||
&& isHost(yield.getOperand(result.getResultNumber()));
|
||||
});
|
||||
visited.erase(*base);
|
||||
return resultIsHost;
|
||||
};
|
||||
return isHost(value);
|
||||
}
|
||||
|
||||
bool onnx_mlir::pim::isDeviceLocalPimAddress(Value value, const StaticValueKnowledge& knowledge) {
|
||||
auto base = getPimStorageBase(value, knowledge);
|
||||
if (failed(base))
|
||||
llvm::SmallPtrSet<Value, 8> visited;
|
||||
std::function<bool(Value)> isDevice = [&](Value current) {
|
||||
auto base = getPimStorageBase(current, knowledge);
|
||||
if (failed(base) || !visited.insert(*base).second)
|
||||
return false;
|
||||
|
||||
return isa_and_nonnull<memref::AllocOp>(base->getDefiningOp());
|
||||
bool resultIsDevice = isa_and_nonnull<memref::AllocOp>(base->getDefiningOp());
|
||||
auto result = dyn_cast<OpResult>(*base);
|
||||
SmallVector<Region *> regions = result ? getSelectionRegions(result)
|
||||
: SmallVector<Region *>();
|
||||
if (!resultIsDevice && !regions.empty())
|
||||
resultIsDevice = llvm::all_of(regions, [&](Region *region) {
|
||||
auto yield = dyn_cast<scf::YieldOp>(region->front().getTerminator());
|
||||
return yield && result.getResultNumber() < yield.getNumOperands()
|
||||
&& isDevice(yield.getOperand(result.getResultNumber()));
|
||||
});
|
||||
visited.erase(*base);
|
||||
return resultIsDevice;
|
||||
};
|
||||
return isDevice(value);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||
|
||||
#include "llvm/Support/MathExtras.h"
|
||||
|
||||
#include "ContiguityPatterns.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
|
||||
@@ -33,6 +35,7 @@ struct CopyEndpointPlan {
|
||||
|
||||
struct CopyLoopPlan {
|
||||
SmallVector<int64_t> outerShape;
|
||||
int64_t outerElements = 0;
|
||||
int64_t chunkBytes = 0;
|
||||
ByteOffsetExpr targetBaseOffset;
|
||||
ByteOffsetExpr sourceBaseOffset;
|
||||
@@ -74,6 +77,24 @@ static void appendTerm(ByteOffsetExpr& expr, Value value, int64_t scale) {
|
||||
expr.terms.push_back(ByteOffsetTerm {value, scale});
|
||||
}
|
||||
|
||||
static FailureOr<int64_t> checkedPositiveMul(int64_t lhs, int64_t rhs) {
|
||||
int64_t result = 0;
|
||||
if (lhs < 0 || rhs < 0 || llvm::MulOverflow(lhs, rhs, result))
|
||||
return failure();
|
||||
return result;
|
||||
}
|
||||
|
||||
static FailureOr<int64_t> checkedPositiveProduct(ArrayRef<int64_t> values) {
|
||||
int64_t result = 1;
|
||||
for (int64_t value : values) {
|
||||
auto product = checkedPositiveMul(result, value);
|
||||
if (failed(product))
|
||||
return failure();
|
||||
result = *product;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static FailureOr<SmallVector<int64_t>> getStaticMemRefStrides(MemRefType type) {
|
||||
SmallVector<int64_t> strides;
|
||||
int64_t offset = 0;
|
||||
@@ -84,6 +105,191 @@ static FailureOr<SmallVector<int64_t>> getStaticMemRefStrides(MemRefType type) {
|
||||
return strides;
|
||||
}
|
||||
|
||||
static bool haveEquivalentStrides(MemRefType type, ArrayRef<int64_t> lhs,
|
||||
ArrayRef<int64_t> rhs) {
|
||||
if (lhs.size() != rhs.size()
|
||||
|| lhs.size() != static_cast<size_t>(type.getRank()))
|
||||
return false;
|
||||
for (auto [dimension, strides] : llvm::enumerate(llvm::zip(lhs, rhs)))
|
||||
if (type.getDimSize(dimension) != 1
|
||||
&& std::get<0>(strides) != std::get<1>(strides))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static FailureOr<SmallVector<int64_t>> getProvenMemRefStrides(Value value) {
|
||||
llvm::SmallPtrSet<Value, 8> visiting;
|
||||
std::function<FailureOr<SmallVector<int64_t>>(Value)> prove =
|
||||
[&](Value current) -> FailureOr<SmallVector<int64_t>> {
|
||||
auto type = dyn_cast<MemRefType>(current.getType());
|
||||
if (!type || !visiting.insert(current).second)
|
||||
return failure();
|
||||
if (auto strides = getStaticMemRefStrides(type); succeeded(strides)) {
|
||||
visiting.erase(current);
|
||||
return strides;
|
||||
}
|
||||
if (auto castOp = current.getDefiningOp<memref::CastOp>()) {
|
||||
auto strides = prove(castOp.getSource());
|
||||
visiting.erase(current);
|
||||
return strides;
|
||||
}
|
||||
if (auto subview = current.getDefiningOp<memref::SubViewOp>()) {
|
||||
auto sourceStrides = prove(subview.getSource());
|
||||
if (failed(sourceStrides) || subview.getSourceType().getRank() != subview.getType().getRank()) {
|
||||
visiting.erase(current);
|
||||
return failure();
|
||||
}
|
||||
SmallVector<int64_t> strides;
|
||||
for (auto [sourceStride, viewStride] :
|
||||
llvm::zip_equal(*sourceStrides, subview.getStaticStrides())) {
|
||||
if (ShapedType::isDynamic(viewStride) || viewStride < 0) {
|
||||
visiting.erase(current);
|
||||
return failure();
|
||||
}
|
||||
auto stride = checkedPositiveMul(sourceStride, viewStride);
|
||||
if (failed(stride)) {
|
||||
visiting.erase(current);
|
||||
return failure();
|
||||
}
|
||||
strides.push_back(*stride);
|
||||
}
|
||||
visiting.erase(current);
|
||||
return strides;
|
||||
}
|
||||
if (auto expand = current.getDefiningOp<memref::ExpandShapeOp>()) {
|
||||
auto sourceStrides = prove(expand.getSrc());
|
||||
auto resultType = dyn_cast<MemRefType>(expand.getResult().getType());
|
||||
auto sourceType = dyn_cast<MemRefType>(expand.getSrc().getType());
|
||||
if (failed(sourceStrides) || !sourceType || !resultType
|
||||
|| !resultType.hasStaticShape()
|
||||
|| sourceStrides->size() != static_cast<size_t>(sourceType.getRank())
|
||||
|| llvm::any_of(resultType.getShape(), [](int64_t dim) {
|
||||
return dim <= 0;
|
||||
})) {
|
||||
visiting.erase(current);
|
||||
return failure();
|
||||
}
|
||||
SmallVector<int64_t> strides(resultType.getRank());
|
||||
SmallVector<bool> assigned(resultType.getRank(), false);
|
||||
for (auto [sourceDim, group] :
|
||||
llvm::enumerate(expand.getReassociationIndices())) {
|
||||
if (sourceDim >= sourceStrides->size() || group.empty()) {
|
||||
visiting.erase(current);
|
||||
return failure();
|
||||
}
|
||||
int64_t stride = (*sourceStrides)[sourceDim];
|
||||
for (int64_t resultDim : llvm::reverse(group)) {
|
||||
if (resultDim < 0 || resultDim >= resultType.getRank()
|
||||
|| assigned[resultDim]) {
|
||||
visiting.erase(current);
|
||||
return failure();
|
||||
}
|
||||
strides[resultDim] = stride;
|
||||
assigned[resultDim] = true;
|
||||
auto nextStride = checkedPositiveMul(
|
||||
stride, resultType.getDimSize(resultDim));
|
||||
if (failed(nextStride)) {
|
||||
visiting.erase(current);
|
||||
return failure();
|
||||
}
|
||||
stride = *nextStride;
|
||||
}
|
||||
}
|
||||
if (llvm::is_contained(assigned, false)) {
|
||||
visiting.erase(current);
|
||||
return failure();
|
||||
}
|
||||
visiting.erase(current);
|
||||
return strides;
|
||||
}
|
||||
if (auto collapse = current.getDefiningOp<memref::CollapseShapeOp>()) {
|
||||
auto sourceStrides = prove(collapse.getSrc());
|
||||
auto sourceType = dyn_cast<MemRefType>(collapse.getSrc().getType());
|
||||
if (failed(sourceStrides) || !sourceType
|
||||
|| !sourceType.hasStaticShape()
|
||||
|| sourceStrides->size() != static_cast<size_t>(sourceType.getRank())) {
|
||||
visiting.erase(current);
|
||||
return failure();
|
||||
}
|
||||
SmallVector<int64_t> strides;
|
||||
for (ArrayRef<int64_t> group : collapse.getReassociationIndices()) {
|
||||
if (group.empty()) {
|
||||
visiting.erase(current);
|
||||
return failure();
|
||||
}
|
||||
for (int64_t dim : group)
|
||||
if (dim < 0 || dim >= sourceType.getRank()
|
||||
|| sourceType.getDimSize(dim) <= 0
|
||||
|| (*sourceStrides)[dim] < 0) {
|
||||
visiting.erase(current);
|
||||
return failure();
|
||||
}
|
||||
for (auto pair : llvm::zip(group.drop_back(), group.drop_front())) {
|
||||
int64_t outer = std::get<0>(pair);
|
||||
int64_t inner = std::get<1>(pair);
|
||||
auto expectedOuterStride = checkedPositiveMul(
|
||||
(*sourceStrides)[inner], sourceType.getDimSize(inner));
|
||||
if (failed(expectedOuterStride)
|
||||
|| (*sourceStrides)[outer] != *expectedOuterStride) {
|
||||
visiting.erase(current);
|
||||
return failure();
|
||||
}
|
||||
}
|
||||
strides.push_back((*sourceStrides)[group.back()]);
|
||||
}
|
||||
visiting.erase(current);
|
||||
return strides;
|
||||
}
|
||||
auto result = dyn_cast<OpResult>(current);
|
||||
SmallVector<Value, 4> alternatives;
|
||||
SmallVector<Region *> regions;
|
||||
if (result) {
|
||||
if (auto loop = dyn_cast<scf::ForOp>(result.getOwner())) {
|
||||
auto yield = dyn_cast<scf::YieldOp>(loop.getBody()->getTerminator());
|
||||
if (!yield || result.getResultNumber() >= loop.getInitArgs().size()
|
||||
|| result.getResultNumber() >= yield.getNumOperands()) {
|
||||
visiting.erase(current);
|
||||
return failure();
|
||||
}
|
||||
alternatives.push_back(loop.getInitArgs()[result.getResultNumber()]);
|
||||
alternatives.push_back(yield.getOperand(result.getResultNumber()));
|
||||
} else if (auto selection = dyn_cast<scf::IndexSwitchOp>(result.getOwner()))
|
||||
for (Region ®ion : selection->getRegions())
|
||||
regions.push_back(®ion);
|
||||
else if (auto selection = dyn_cast<scf::IfOp>(result.getOwner())) {
|
||||
regions.push_back(&selection.getThenRegion());
|
||||
regions.push_back(&selection.getElseRegion());
|
||||
}
|
||||
}
|
||||
for (Region *region : regions) {
|
||||
auto yield = dyn_cast<scf::YieldOp>(region->front().getTerminator());
|
||||
if (!yield || result.getResultNumber() >= yield.getNumOperands()) {
|
||||
visiting.erase(current);
|
||||
return failure();
|
||||
}
|
||||
alternatives.push_back(yield.getOperand(result.getResultNumber()));
|
||||
}
|
||||
if (alternatives.empty()) {
|
||||
visiting.erase(current);
|
||||
return failure();
|
||||
}
|
||||
std::optional<SmallVector<int64_t>> common;
|
||||
for (Value alternative : alternatives) {
|
||||
auto strides = prove(alternative);
|
||||
if (failed(strides)
|
||||
|| (common && !haveEquivalentStrides(type, *common, *strides))) {
|
||||
visiting.erase(current);
|
||||
return failure();
|
||||
}
|
||||
common = std::move(*strides);
|
||||
}
|
||||
visiting.erase(current);
|
||||
return common ? FailureOr<SmallVector<int64_t>>(std::move(*common))
|
||||
: FailureOr<SmallVector<int64_t>>(failure());
|
||||
};
|
||||
return prove(value);
|
||||
}
|
||||
|
||||
static FailureOr<int64_t> getShapedByteSize(MemRefType type) {
|
||||
if (!type.hasStaticShape() || !hasByteSizedElementType(type.getElementType()))
|
||||
return failure();
|
||||
@@ -119,22 +325,28 @@ inferLogicalCopyShape(MemRefType targetType, MemRefType sourceType, int64_t size
|
||||
return failure();
|
||||
}
|
||||
|
||||
static FailureOr<int64_t> getContiguousSuffixRank(MemRefType type, ArrayRef<int64_t> copyShape) {
|
||||
if (!type.hasStaticShape() || !hasByteSizedElementType(type.getElementType())
|
||||
static FailureOr<int64_t> getContiguousSuffixRank(Value value, ArrayRef<int64_t> copyShape) {
|
||||
auto type = dyn_cast<MemRefType>(value.getType());
|
||||
if (!type || !type.hasStaticShape() || !hasByteSizedElementType(type.getElementType())
|
||||
|| type.getRank() != static_cast<int64_t>(copyShape.size()))
|
||||
return failure();
|
||||
if (llvm::any_of(copyShape, [](int64_t dim) { return dim <= 0; }))
|
||||
return failure();
|
||||
|
||||
auto strides = getStaticMemRefStrides(type);
|
||||
auto strides = getProvenMemRefStrides(value);
|
||||
if (failed(strides))
|
||||
return failure();
|
||||
|
||||
int64_t expectedStride = 1;
|
||||
int64_t contiguousSuffixRank = 0;
|
||||
for (int64_t dim = type.getRank() - 1; dim >= 0; --dim) {
|
||||
if ((*strides)[dim] != expectedStride)
|
||||
if (copyShape[dim] != 1 && (*strides)[dim] != expectedStride)
|
||||
break;
|
||||
++contiguousSuffixRank;
|
||||
expectedStride *= copyShape[dim];
|
||||
auto nextStride = checkedPositiveMul(expectedStride, copyShape[dim]);
|
||||
if (failed(nextStride))
|
||||
return failure();
|
||||
expectedStride = *nextStride;
|
||||
}
|
||||
return contiguousSuffixRank;
|
||||
}
|
||||
@@ -174,18 +386,25 @@ static FailureOr<CopyEndpointPlan> analyzeCopyEndpoint(Value value, Value initia
|
||||
if (!sourceType || !sourceType.hasStaticShape() || !hasByteSizedElementType(sourceType.getElementType()))
|
||||
return failure();
|
||||
|
||||
auto sourceStrides = getStaticMemRefStrides(sourceType);
|
||||
auto sourceStrides = getProvenMemRefStrides(subviewOp.getSource());
|
||||
if (failed(sourceStrides))
|
||||
return failure();
|
||||
|
||||
int64_t elementByteWidth = static_cast<int64_t>(getElementTypeSizeInBytes(sourceType.getElementType()));
|
||||
for (auto [offset, stride] : llvm::zip_equal(subviewOp.getMixedOffsets(), *sourceStrides)) {
|
||||
int64_t byteScale = stride * elementByteWidth;
|
||||
auto byteScale = checkedPositiveMul(stride, elementByteWidth);
|
||||
if (failed(byteScale))
|
||||
return failure();
|
||||
if (auto attr = dyn_cast<Attribute>(offset)) {
|
||||
endpoint.offset.constant += cast<IntegerAttr>(attr).getInt() * byteScale;
|
||||
auto constantOffset = checkedPositiveMul(
|
||||
cast<IntegerAttr>(attr).getInt(), *byteScale);
|
||||
if (failed(constantOffset)
|
||||
|| llvm::AddOverflow(endpoint.offset.constant, *constantOffset,
|
||||
endpoint.offset.constant))
|
||||
return failure();
|
||||
continue;
|
||||
}
|
||||
appendTerm(endpoint.offset, cast<Value>(offset), byteScale);
|
||||
appendTerm(endpoint.offset, cast<Value>(offset), *byteScale);
|
||||
}
|
||||
|
||||
endpoint.base = subviewOp.getSource();
|
||||
@@ -204,17 +423,34 @@ analyzeCopyRewrite(Value target, Value source, Value targetOffset, Value sourceO
|
||||
if (!targetType || !sourceType || size <= 0)
|
||||
return failure();
|
||||
|
||||
auto logicalCopyShape = inferLogicalCopyShape(targetType, sourceType, size);
|
||||
if (failed(logicalCopyShape))
|
||||
return failure();
|
||||
|
||||
auto targetPlan = analyzeCopyEndpoint(target, targetOffset, targetType);
|
||||
auto sourcePlan = analyzeCopyEndpoint(source, sourceOffset, sourceType);
|
||||
if (failed(targetPlan) || failed(sourcePlan))
|
||||
return failure();
|
||||
|
||||
auto targetSuffixRank = getContiguousSuffixRank(targetType, *logicalCopyShape);
|
||||
auto sourceSuffixRank = getContiguousSuffixRank(sourceType, *logicalCopyShape);
|
||||
auto targetBytes = getShapedByteSize(targetType);
|
||||
auto sourceBytes = getShapedByteSize(sourceType);
|
||||
if (targetType.getElementType() == sourceType.getElementType() && succeeded(targetBytes) && succeeded(sourceBytes)
|
||||
&& size <= *targetBytes && size <= *sourceBytes) {
|
||||
auto targetSuffixRank = getContiguousSuffixRank(target, targetType.getShape());
|
||||
auto sourceSuffixRank = getContiguousSuffixRank(source, sourceType.getShape());
|
||||
if (succeeded(targetSuffixRank) && succeeded(sourceSuffixRank)
|
||||
&& *targetSuffixRank == targetType.getRank() && *sourceSuffixRank == sourceType.getRank()) {
|
||||
CopyRewritePlan plan;
|
||||
plan.kind = CopyRewritePlan::Kind::Direct;
|
||||
plan.target = *targetPlan;
|
||||
plan.source = *sourcePlan;
|
||||
plan.directBytes = size;
|
||||
return plan;
|
||||
}
|
||||
}
|
||||
|
||||
auto logicalCopyShape = inferLogicalCopyShape(targetType, sourceType, size);
|
||||
if (failed(logicalCopyShape))
|
||||
return failure();
|
||||
|
||||
auto targetSuffixRank = getContiguousSuffixRank(target, *logicalCopyShape);
|
||||
auto sourceSuffixRank = getContiguousSuffixRank(source, *logicalCopyShape);
|
||||
if (failed(targetSuffixRank) || failed(sourceSuffixRank))
|
||||
return failure();
|
||||
|
||||
@@ -229,8 +465,8 @@ analyzeCopyRewrite(Value target, Value source, Value targetOffset, Value sourceO
|
||||
return plan;
|
||||
}
|
||||
|
||||
auto targetStrides = getStaticMemRefStrides(targetType);
|
||||
auto sourceStrides = getStaticMemRefStrides(sourceType);
|
||||
auto targetStrides = getProvenMemRefStrides(target);
|
||||
auto sourceStrides = getProvenMemRefStrides(source);
|
||||
if (failed(targetStrides) || failed(sourceStrides))
|
||||
return failure();
|
||||
|
||||
@@ -240,11 +476,27 @@ analyzeCopyRewrite(Value target, Value source, Value targetOffset, Value sourceO
|
||||
plan.loop.sourceBaseOffset = plan.source.offset;
|
||||
plan.loop.outerShape.assign(logicalCopyShape->begin(), logicalCopyShape->end() - contiguousSuffixRank);
|
||||
SmallVector<int64_t> chunkShape(logicalCopyShape->end() - contiguousSuffixRank, logicalCopyShape->end());
|
||||
plan.loop.chunkBytes = getNumElements(chunkShape) * elementByteWidth;
|
||||
for (int64_t stride : ArrayRef<int64_t>(*targetStrides).take_front(plan.loop.outerShape.size()))
|
||||
plan.loop.targetOuterByteStrides.push_back(stride * elementByteWidth);
|
||||
for (int64_t stride : ArrayRef<int64_t>(*sourceStrides).take_front(plan.loop.outerShape.size()))
|
||||
plan.loop.sourceOuterByteStrides.push_back(stride * elementByteWidth);
|
||||
auto outerElements = checkedPositiveProduct(plan.loop.outerShape);
|
||||
auto chunkElements = checkedPositiveProduct(chunkShape);
|
||||
auto chunkBytes = failed(chunkElements)
|
||||
? FailureOr<int64_t>(failure())
|
||||
: checkedPositiveMul(*chunkElements, elementByteWidth);
|
||||
if (failed(outerElements) || failed(chunkBytes))
|
||||
return failure();
|
||||
plan.loop.outerElements = *outerElements;
|
||||
plan.loop.chunkBytes = *chunkBytes;
|
||||
for (int64_t stride : ArrayRef<int64_t>(*targetStrides).take_front(plan.loop.outerShape.size())) {
|
||||
auto byteStride = checkedPositiveMul(stride, elementByteWidth);
|
||||
if (failed(byteStride))
|
||||
return failure();
|
||||
plan.loop.targetOuterByteStrides.push_back(*byteStride);
|
||||
}
|
||||
for (int64_t stride : ArrayRef<int64_t>(*sourceStrides).take_front(plan.loop.outerShape.size())) {
|
||||
auto byteStride = checkedPositiveMul(stride, elementByteWidth);
|
||||
if (failed(byteStride))
|
||||
return failure();
|
||||
plan.loop.sourceOuterByteStrides.push_back(*byteStride);
|
||||
}
|
||||
if (plan.loop.chunkBytes <= 0)
|
||||
return failure();
|
||||
return plan;
|
||||
@@ -344,7 +596,7 @@ static LogicalResult rewriteCopyLikeOp(CopyOp copyOp,
|
||||
}
|
||||
|
||||
Value c0 = createIndexConstant(rewriter, anchorOp, 0);
|
||||
Value cUpper = createIndexConstant(rewriter, anchorOp, getNumElements(plan->loop.outerShape));
|
||||
Value cUpper = createIndexConstant(rewriter, anchorOp, plan->loop.outerElements);
|
||||
Value cStep = createIndexConstant(rewriter, anchorOp, 1);
|
||||
auto loop = buildNormalizedScfFor(
|
||||
rewriter,
|
||||
|
||||
@@ -35,8 +35,54 @@ static StaticValueKnowledge getEnclosingBufferizationKnowledge(Operation* op) {
|
||||
return knowledge;
|
||||
}
|
||||
|
||||
template <typename ConcreteModel, typename ConcreteOp>
|
||||
struct CopyOpInterface
|
||||
: DstBufferizableOpInterfaceExternalModel<ConcreteModel, ConcreteOp> {
|
||||
bool bufferizesToMemoryRead(Operation* op, OpOperand& opOperand, const AnalysisState& state) const {
|
||||
return cast<DestinationStyleOpInterface>(op).isDpsInput(&opOperand);
|
||||
}
|
||||
|
||||
bool bufferizesToMemoryWrite(Operation* op, OpOperand& opOperand, const AnalysisState& state) const {
|
||||
return cast<DestinationStyleOpInterface>(op).isDpsInit(&opOperand);
|
||||
}
|
||||
|
||||
AliasingValueList getAliasingValues(Operation* op,
|
||||
OpOperand& opOperand,
|
||||
const AnalysisState& state) const {
|
||||
auto dstOp = cast<DestinationStyleOpInterface>(op);
|
||||
if (dstOp.isDpsInit(&opOperand))
|
||||
return {{dstOp.getTiedOpResult(&opOperand), BufferRelation::Equivalent}};
|
||||
return {};
|
||||
}
|
||||
|
||||
AliasingOpOperandList getAliasingOpOperands(Operation* op,
|
||||
Value value,
|
||||
const AnalysisState& state) const {
|
||||
auto result = dyn_cast<OpResult>(value);
|
||||
if (!result || result.getDefiningOp() != op)
|
||||
return {};
|
||||
return {{cast<DestinationStyleOpInterface>(op).getTiedOpOperand(result), BufferRelation::Equivalent}};
|
||||
}
|
||||
|
||||
bool mustBufferizeInPlace(Operation* op, OpOperand& opOperand, const AnalysisState& state) const {
|
||||
return isa<UnrankedTensorType>(opOperand.get().getType());
|
||||
}
|
||||
|
||||
bool isWritable(Operation* op, Value value, const AnalysisState& state) const { return isa<OpResult>(value); }
|
||||
|
||||
bool isNotConflicting(Operation* op,
|
||||
OpOperand* read,
|
||||
OpOperand* write,
|
||||
const AnalysisState& state) const {
|
||||
if (read->getOwner() != op || write->getOwner() != op)
|
||||
return false;
|
||||
auto dstOp = cast<DestinationStyleOpInterface>(op);
|
||||
return dstOp.isDpsInput(read) && dstOp.isDpsInit(write);
|
||||
}
|
||||
};
|
||||
|
||||
struct MemCopyHostToDevOpInterface
|
||||
: DstBufferizableOpInterfaceExternalModel<MemCopyHostToDevOpInterface, PimMemCopyHostToDevOp> {
|
||||
: CopyOpInterface<MemCopyHostToDevOpInterface, PimMemCopyHostToDevOp> {
|
||||
LogicalResult bufferize(Operation* op,
|
||||
RewriterBase& rewriter,
|
||||
const BufferizationOptions& options,
|
||||
@@ -68,7 +114,7 @@ struct MemCopyHostToDevOpInterface
|
||||
};
|
||||
|
||||
struct MemCopyDevToHostOpInterface
|
||||
: DstBufferizableOpInterfaceExternalModel<MemCopyDevToHostOpInterface, PimMemCopyDevToHostOp> {
|
||||
: CopyOpInterface<MemCopyDevToHostOpInterface, PimMemCopyDevToHostOp> {
|
||||
LogicalResult bufferize(Operation* op,
|
||||
RewriterBase& rewriter,
|
||||
const BufferizationOptions& options,
|
||||
@@ -99,11 +145,7 @@ struct MemCopyDevToHostOpInterface
|
||||
}
|
||||
};
|
||||
|
||||
struct MemCopyOpInterface : DstBufferizableOpInterfaceExternalModel<MemCopyOpInterface, PimMemCopyOp> {
|
||||
bool bufferizesToMemoryRead(Operation* op, OpOperand& opOperand, const AnalysisState& state) const {
|
||||
return !cast<DestinationStyleOpInterface>(op).isDpsInit(&opOperand);
|
||||
}
|
||||
|
||||
struct MemCopyOpInterface : CopyOpInterface<MemCopyOpInterface, PimMemCopyOp> {
|
||||
LogicalResult bufferize(Operation* op,
|
||||
RewriterBase& rewriter,
|
||||
const BufferizationOptions& options,
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
#include "mlir/Dialect/Bufferization/IR/Bufferization.h"
|
||||
#include "mlir/Dialect/Bufferization/Transforms/OneShotAnalysis.h"
|
||||
#include "mlir/Dialect/Bufferization/Transforms/OneShotModuleBufferize.h"
|
||||
#include "mlir/Dialect/Bufferization/Transforms/Transforms.h"
|
||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
||||
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||
#include "mlir/IR/PatternMatch.h"
|
||||
#include "mlir/Pass/Pass.h"
|
||||
#include "mlir/Rewrite/PatternApplicator.h"
|
||||
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
|
||||
|
||||
#include "llvm/ADT/SmallPtrSet.h"
|
||||
#include "llvm/Support/Casting.h"
|
||||
|
||||
#include "Common/PimCommon.hpp"
|
||||
#include "Common/Support/Diagnostics.hpp"
|
||||
#include "Compiler/PimCodeGen.hpp"
|
||||
#include "Dialect/Pim/PimOps.hpp"
|
||||
#include "Dialect/Pim/Transforms/Bufferization/Common.hpp"
|
||||
@@ -52,7 +55,10 @@ static StaticValueKnowledge seedCoreBatchKnowledge(pim::PimCoreBatchOp coreBatch
|
||||
}
|
||||
|
||||
static LogicalResult
|
||||
lowerMemRefCopyToPimCopy(memref::CopyOp copyOp, PatternRewriter& rewriter, const StaticValueKnowledge& knowledge) {
|
||||
lowerMemRefCopyToPimCopy(memref::CopyOp copyOp,
|
||||
Value zeroOffset,
|
||||
PatternRewriter& rewriter,
|
||||
const StaticValueKnowledge& knowledge) {
|
||||
if (!copyOp->getParentOfType<pim::PimCoreOp>() && !copyOp->getParentOfType<pim::PimCoreBatchOp>())
|
||||
return failure();
|
||||
|
||||
@@ -63,7 +69,6 @@ lowerMemRefCopyToPimCopy(memref::CopyOp copyOp, PatternRewriter& rewriter, const
|
||||
if (sourceType.getElementType() != targetType.getElementType())
|
||||
return failure();
|
||||
|
||||
Value zeroOffset = getOrCreateIndexConstant(rewriter, copyOp, 0);
|
||||
auto sizeAttr = getMemRefSizeInBytesAttr(rewriter, copyOp.getOperation(), copyOp.getSource());
|
||||
if (failed(sizeAttr))
|
||||
return failure();
|
||||
@@ -116,34 +121,35 @@ lowerMemRefCopyToPimCopy(memref::CopyOp copyOp, PatternRewriter& rewriter, const
|
||||
return success();
|
||||
}
|
||||
|
||||
static LogicalResult verifyLoweredPimCopy(pim::PimMemCopyHostToDevOp copyOp, const StaticValueKnowledge& knowledge) {
|
||||
bool sourceIsHost = isHostBackedPimAddress(copyOp.getHostSource(), knowledge);
|
||||
bool targetIsHost = isHostBackedPimAddress(copyOp.getDeviceTarget(), knowledge);
|
||||
bool sourceIsDevice = isDeviceLocalPimAddress(copyOp.getHostSource(), knowledge);
|
||||
bool targetIsDevice = isDeviceLocalPimAddress(copyOp.getDeviceTarget(), knowledge);
|
||||
if (!sourceIsHost || !targetIsDevice || targetIsHost || sourceIsDevice)
|
||||
return copyOp.emitOpError("pim.memcp_hd requires a host-backed source and a device-local target");
|
||||
return success();
|
||||
}
|
||||
enum class ExpectedPimCopyDirection { HostToDevice, DeviceToHost, DeviceToDevice };
|
||||
|
||||
static LogicalResult verifyLoweredPimCopy(pim::PimMemCopyDevToHostOp copyOp, const StaticValueKnowledge& knowledge) {
|
||||
bool sourceIsHost = isHostBackedPimAddress(copyOp.getDeviceSource(), knowledge);
|
||||
bool targetIsHost = isHostBackedPimAddress(copyOp.getHostTarget(), knowledge);
|
||||
bool sourceIsDevice = isDeviceLocalPimAddress(copyOp.getDeviceSource(), knowledge);
|
||||
bool targetIsDevice = isDeviceLocalPimAddress(copyOp.getHostTarget(), knowledge);
|
||||
if (!targetIsHost || !sourceIsDevice || sourceIsHost || targetIsDevice)
|
||||
return copyOp.emitOpError("pim.memcp_dh requires a device-local source and a host-backed target");
|
||||
return success();
|
||||
}
|
||||
|
||||
static LogicalResult verifyLoweredPimCopy(pim::PimMemCopyOp copyOp, const StaticValueKnowledge& knowledge) {
|
||||
bool sourceIsHost = isHostBackedPimAddress(copyOp.getSource(), knowledge);
|
||||
bool targetIsHost = isHostBackedPimAddress(copyOp.getTarget(), knowledge);
|
||||
bool sourceIsDevice = isDeviceLocalPimAddress(copyOp.getSource(), knowledge);
|
||||
bool targetIsDevice = isDeviceLocalPimAddress(copyOp.getTarget(), knowledge);
|
||||
if (!sourceIsDevice || !targetIsDevice || sourceIsHost || targetIsHost)
|
||||
return copyOp.emitOpError("pim.memcp requires device-local source and target operands");
|
||||
return success();
|
||||
static LogicalResult verifyPimCopyEndpoints(Operation* copy,
|
||||
Value source,
|
||||
Value target,
|
||||
ExpectedPimCopyDirection direction,
|
||||
const StaticValueKnowledge& knowledge,
|
||||
bool emitDiagnostic) {
|
||||
struct ExpectedEndpoints {
|
||||
bool sourceHost, sourceDevice, targetHost, targetDevice;
|
||||
StringLiteral description;
|
||||
};
|
||||
static constexpr ExpectedEndpoints expected[] = {
|
||||
{true, false, false, true, "a host-backed source and a device-local target"},
|
||||
{false, true, true, false, "a device-local source and a host-backed target"},
|
||||
{false, true, false, true, "device-local source and target operands"},
|
||||
};
|
||||
const auto& endpoints = expected[static_cast<unsigned>(direction)];
|
||||
bool sourceHost = isHostBackedPimAddress(source, knowledge);
|
||||
bool sourceDevice = isDeviceLocalPimAddress(source, knowledge);
|
||||
bool targetHost = isHostBackedPimAddress(target, knowledge);
|
||||
bool targetDevice = isDeviceLocalPimAddress(target, knowledge);
|
||||
bool valid = sourceHost == endpoints.sourceHost && sourceDevice == endpoints.sourceDevice
|
||||
&& targetHost == endpoints.targetHost && targetDevice == endpoints.targetDevice;
|
||||
if (!valid && emitDiagnostic)
|
||||
copy->emitOpError() << "requires " << endpoints.description << ": source=" << source << " host=" << sourceHost
|
||||
<< " device=" << sourceDevice << ", target=" << target << " host=" << targetHost
|
||||
<< " device=" << targetDevice;
|
||||
return success(valid);
|
||||
}
|
||||
|
||||
struct PimBufferizationPass : PassWrapper<PimBufferizationPass, OperationPass<ModuleOp>> {
|
||||
@@ -162,12 +168,82 @@ private:
|
||||
LogicalResult verifyPimCopyAddressSpaces(ModuleOp moduleOp) const;
|
||||
};
|
||||
|
||||
static LogicalResult applyPatternsOnce(Operation* op, PatternApplicator& applicator, PatternRewriter& rewriter) {
|
||||
if (!op || !op->getBlock())
|
||||
return failure();
|
||||
static void materializeWritableConstantDestinations(func::FuncOp funcOp) {
|
||||
SmallVector<OpOperand*> constantBackedRoots;
|
||||
llvm::SmallPtrSet<OpOperand*, 8> seenRoots;
|
||||
auto collect = [&](Operation* coreOp) {
|
||||
coreOp->walk([&](tensor::InsertSliceOp insert) {
|
||||
OpOperand* root = &insert.getDestMutable();
|
||||
Value value = root->get();
|
||||
llvm::SmallDenseSet<Value, 4> visited;
|
||||
while (visited.insert(value).second) {
|
||||
if (value.getDefiningOp<arith::ConstantOp>()) {
|
||||
if (seenRoots.insert(root).second)
|
||||
constantBackedRoots.push_back(root);
|
||||
break;
|
||||
}
|
||||
auto argument = dyn_cast<BlockArgument>(value);
|
||||
auto loop = argument
|
||||
? dyn_cast_or_null<scf::ForOp>(argument.getOwner()->getParentOp())
|
||||
: scf::ForOp();
|
||||
if (!loop || argument.getArgNumber() == 0)
|
||||
break;
|
||||
auto initArgs = loop.getInitArgsMutable();
|
||||
root = &*(initArgs.begin() + argument.getArgNumber() - 1);
|
||||
value = root->get();
|
||||
}
|
||||
});
|
||||
};
|
||||
funcOp.walk([&](pim::PimCoreOp coreOp) { collect(coreOp); });
|
||||
funcOp.walk([&](pim::PimCoreBatchOp coreOp) { collect(coreOp); });
|
||||
|
||||
rewriter.setInsertionPoint(op);
|
||||
return applicator.matchAndRewrite(op, rewriter);
|
||||
for (OpOperand* root : constantBackedRoots) {
|
||||
OpBuilder builder(root->getOwner());
|
||||
auto allocation = bufferization::AllocTensorOp::create(
|
||||
builder, root->getOwner()->getLoc(), cast<RankedTensorType>(root->get().getType()),
|
||||
ValueRange {}, root->get());
|
||||
root->set(allocation.getResult());
|
||||
}
|
||||
}
|
||||
|
||||
static LogicalResult verifyPimCoresNeedNoTensorCopies(
|
||||
ModuleOp module, const bufferization::OneShotBufferizationOptions& baseOptions) {
|
||||
static constexpr StringLiteral kExistingAlloc = "raptor.existing_core_alloc";
|
||||
OwningOpRef<ModuleOp> clone = module.clone();
|
||||
clone->walk([&](bufferization::AllocTensorOp alloc) {
|
||||
if (alloc->getParentOfType<pim::PimCoreOp>()
|
||||
|| alloc->getParentOfType<pim::PimCoreBatchOp>())
|
||||
alloc->setAttr(kExistingAlloc, UnitAttr::get(module.getContext()));
|
||||
});
|
||||
|
||||
auto options = baseOptions;
|
||||
options.bufferizeFunctionBoundaries = false;
|
||||
options.opFilter.allowOperation([](Operation* op) {
|
||||
return isa<pim::PimCoreOp, pim::PimCoreBatchOp>(op)
|
||||
|| op->getParentOfType<pim::PimCoreOp>()
|
||||
|| op->getParentOfType<pim::PimCoreBatchOp>();
|
||||
});
|
||||
|
||||
bufferization::BufferizationState state;
|
||||
if (failed(bufferization::insertTensorCopies(*clone, options, state))) {
|
||||
module.emitError("official one-shot analysis failed while verifying PIM core copy freedom");
|
||||
return failure();
|
||||
}
|
||||
|
||||
CappedDiagnosticReporter diagnostics;
|
||||
clone->walk([&](bufferization::AllocTensorOp alloc) {
|
||||
if (alloc->hasAttr(kExistingAlloc)
|
||||
|| (!alloc->getParentOfType<pim::PimCoreOp>()
|
||||
&& !alloc->getParentOfType<pim::PimCoreBatchOp>()))
|
||||
return;
|
||||
Operation* requiredBy = alloc->getUsers().empty()
|
||||
? alloc.getOperation() : *alloc->getUsers().begin();
|
||||
diagnostics.report(requiredBy, [](Operation* op) {
|
||||
op->emitOpError("official one-shot bufferization requires a tensor copy inside a PIM core");
|
||||
});
|
||||
});
|
||||
diagnostics.emitSuppressedSummary(module, "required PIM core tensor copies");
|
||||
return success(!diagnostics.hasFailure());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -180,9 +256,21 @@ void PimBufferizationPass::runOnOperation() {
|
||||
options.allowUnknownOps = true;
|
||||
options.bufferizeFunctionBoundaries = true;
|
||||
options.setFunctionBoundaryTypeConversion(bufferization::LayoutMapOption::IdentityLayoutMap);
|
||||
bufferization::BufferizationState state;
|
||||
|
||||
if (failed(bufferization::runOneShotModuleBufferize(moduleOp, options, state))) {
|
||||
materializeWritableConstantDestinations(funcOp);
|
||||
if (failed(verifyPimCoresNeedNoTensorCopies(moduleOp, options))) {
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
auto hostOptions = options;
|
||||
hostOptions.opFilter.denyOperation([](Operation *op) {
|
||||
return op->getParentOfType<pim::PimCoreOp>()
|
||||
|| op->getParentOfType<pim::PimCoreBatchOp>();
|
||||
});
|
||||
bufferization::BufferizationState state;
|
||||
if (failed(bufferization::insertTensorCopies(moduleOp, hostOptions, state))
|
||||
|| failed(bufferization::bufferizeModuleOp(moduleOp, options, state))) {
|
||||
moduleOp.emitError("Failed to bufferize PIM and Spatial ops");
|
||||
signalPassFailure();
|
||||
return;
|
||||
@@ -208,11 +296,7 @@ void PimBufferizationPass::runOnOperation() {
|
||||
});
|
||||
});
|
||||
moduleOp.walk([&](pim::PimCoreBatchOp coreBatchOp) {
|
||||
llvm::SmallVector<unsigned, 2> lanes;
|
||||
lanes.push_back(0);
|
||||
if (coreBatchOp.getLaneCount() > 1)
|
||||
lanes.push_back(static_cast<unsigned>(coreBatchOp.getLaneCount() - 1));
|
||||
for (unsigned lane : lanes) {
|
||||
for (unsigned lane = 0; lane < coreBatchOp.getLaneCount(); ++lane) {
|
||||
StaticValueKnowledge knowledge = seedCoreBatchKnowledge(coreBatchOp, lane);
|
||||
(void) walkPimCoreBlockStructurally(
|
||||
coreBatchOp.getBody().front(), knowledge, [&](Operation& op, const StaticValueKnowledge& opKnowledge) {
|
||||
@@ -224,10 +308,11 @@ void PimBufferizationPass::runOnOperation() {
|
||||
});
|
||||
|
||||
bool hasFailed = false;
|
||||
Value zeroOffset = getOrCreateIndexConstant(rewriter, funcOp, 0);
|
||||
for (const MemRefCopyWorkItem& workItem : copyWorklist) {
|
||||
memref::CopyOp copyOp = workItem.copyOp;
|
||||
rewriter.setInsertionPoint(copyOp);
|
||||
if (failed(lowerMemRefCopyToPimCopy(copyOp, rewriter, workItem.knowledge)))
|
||||
if (failed(lowerMemRefCopyToPimCopy(copyOp, zeroOffset, rewriter, workItem.knowledge)))
|
||||
hasFailed = true;
|
||||
}
|
||||
if (hasFailed) {
|
||||
@@ -237,32 +322,10 @@ void PimBufferizationPass::runOnOperation() {
|
||||
|
||||
RewritePatternSet contiguityPatterns(ctx);
|
||||
populatePimContiguityNormalizationPatterns(contiguityPatterns);
|
||||
FrozenRewritePatternSet frozenContiguityPatterns(std::move(contiguityPatterns));
|
||||
PatternApplicator contiguityApplicator(frozenContiguityPatterns);
|
||||
contiguityApplicator.applyDefaultCostModel();
|
||||
|
||||
SmallVector<Operation*> contiguityWorklist;
|
||||
moduleOp.walk([&](Operation* op) {
|
||||
if (isa<pim::PimMemCopyOp, pim::PimMemCopyHostToDevOp, pim::PimMemCopyDevToHostOp>(op))
|
||||
contiguityWorklist.push_back(op);
|
||||
});
|
||||
|
||||
hasFailed = false;
|
||||
for (Operation* op : contiguityWorklist) {
|
||||
if (auto copyOp = dyn_cast<pim::PimMemCopyOp>(op); copyOp && pim::isNormalizedCopyOp(copyOp))
|
||||
continue;
|
||||
if (auto copyOp = dyn_cast<pim::PimMemCopyHostToDevOp>(op); copyOp && pim::isNormalizedCopyOp(copyOp))
|
||||
continue;
|
||||
if (auto copyOp = dyn_cast<pim::PimMemCopyDevToHostOp>(op); copyOp && pim::isNormalizedCopyOp(copyOp))
|
||||
continue;
|
||||
|
||||
if (failed(applyPatternsOnce(op, contiguityApplicator, rewriter))) {
|
||||
op->emitOpError("failed to normalize PIM copy contiguity");
|
||||
hasFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasFailed) {
|
||||
GreedyRewriteConfig contiguityConfig;
|
||||
contiguityConfig.enableFolding(false);
|
||||
if (failed(applyPatternsGreedily(moduleOp, std::move(contiguityPatterns), contiguityConfig))) {
|
||||
moduleOp.emitError("failed to normalize PIM copy contiguity during bufferization");
|
||||
signalPassFailure();
|
||||
return;
|
||||
@@ -302,57 +365,60 @@ void PimBufferizationPass::annotateWeightsMemrefs(ModuleOp moduleOp, func::FuncO
|
||||
|
||||
LogicalResult PimBufferizationPass::verifyContiguousRuntimeOperands(ModuleOp moduleOp) const {
|
||||
bool hasFailure = false;
|
||||
moduleOp.walk([&](Operation* op) {
|
||||
|
||||
auto verifyWithKnowledge = [&](auto coreLikeOp, const StaticValueKnowledge& initialKnowledge) {
|
||||
(void) walkPimCoreBlockStructurally(
|
||||
coreLikeOp.getBody().front(), initialKnowledge, [&](Operation& op, const StaticValueKnowledge& knowledge) {
|
||||
auto verifyOperand = [&](Value operand, unsigned operandIndex) {
|
||||
if (!isa<BaseMemRefType>(operand.getType()))
|
||||
return;
|
||||
if (succeeded(resolveContiguousAddress(operand)) || succeeded(compileContiguousAddressExpr(operand)))
|
||||
if (succeeded(resolveContiguousAddress(operand, knowledge)) || succeeded(compileContiguousAddressExpr(operand)))
|
||||
return;
|
||||
op->emitOpError() << "operand #" << operandIndex
|
||||
op.emitOpError() << "operand #" << operandIndex
|
||||
<< " is not backed by contiguous addressable storage after PIM bufferization";
|
||||
hasFailure = true;
|
||||
};
|
||||
|
||||
if (auto memCopyOp = dyn_cast<PimMemCopyOp>(op)) {
|
||||
if (auto memCopyOp = dyn_cast<PimMemCopyOp>(&op)) {
|
||||
if (!pim::isNormalizedCopyOp(memCopyOp)) {
|
||||
memCopyOp.emitOpError("must use base memref operands plus explicit byte offsets after bufferization");
|
||||
hasFailure = true;
|
||||
}
|
||||
verifyOperand(memCopyOp.getTarget(), 0);
|
||||
verifyOperand(memCopyOp.getSource(), 1);
|
||||
return;
|
||||
return success();
|
||||
}
|
||||
if (auto loadOp = dyn_cast<PimMemCopyHostToDevOp>(op)) {
|
||||
if (auto loadOp = dyn_cast<PimMemCopyHostToDevOp>(&op)) {
|
||||
if (!pim::isNormalizedCopyOp(loadOp)) {
|
||||
loadOp.emitOpError("must use base memref operands plus explicit byte offsets after bufferization");
|
||||
hasFailure = true;
|
||||
}
|
||||
verifyOperand(loadOp.getDeviceTarget(), 2);
|
||||
verifyOperand(loadOp.getHostSource(), 3);
|
||||
return;
|
||||
return success();
|
||||
}
|
||||
if (auto storeOp = dyn_cast<PimMemCopyDevToHostOp>(op)) {
|
||||
if (auto storeOp = dyn_cast<PimMemCopyDevToHostOp>(&op)) {
|
||||
if (!pim::isNormalizedCopyOp(storeOp)) {
|
||||
storeOp.emitOpError("must use base memref operands plus explicit byte offsets after bufferization");
|
||||
hasFailure = true;
|
||||
}
|
||||
verifyOperand(storeOp.getHostTarget(), 2);
|
||||
verifyOperand(storeOp.getDeviceSource(), 3);
|
||||
return;
|
||||
return success();
|
||||
}
|
||||
if (auto sendOp = dyn_cast<PimSendOp>(op)) {
|
||||
if (auto sendOp = dyn_cast<PimSendOp>(&op)) {
|
||||
verifyOperand(sendOp.getInput(), 0);
|
||||
return;
|
||||
return success();
|
||||
}
|
||||
if (auto receiveOp = dyn_cast<PimReceiveOp>(op)) {
|
||||
if (auto receiveOp = dyn_cast<PimReceiveOp>(&op)) {
|
||||
verifyOperand(receiveOp.getOutputBuffer(), 0);
|
||||
return;
|
||||
return success();
|
||||
}
|
||||
if (auto concatOp = dyn_cast<PimConcatOp>(op)) {
|
||||
if (auto concatOp = dyn_cast<PimConcatOp>(&op)) {
|
||||
verifyOperand(concatOp.getOutputBuffer(), 0);
|
||||
for (auto inputAndIndex : llvm::enumerate(concatOp.getInputs()))
|
||||
verifyOperand(inputAndIndex.value(), inputAndIndex.index() + 1);
|
||||
return;
|
||||
return success();
|
||||
}
|
||||
if (isa<PimTransposeOp,
|
||||
PimVMMOp,
|
||||
@@ -365,13 +431,21 @@ LogicalResult PimBufferizationPass::verifyContiguousRuntimeOperands(ModuleOp mod
|
||||
PimVReluOp,
|
||||
PimVTanhOp,
|
||||
PimVSigmOp,
|
||||
PimVSoftmaxOp>(op)) {
|
||||
for (auto operandAndIndex : llvm::enumerate(op->getOperands())) {
|
||||
if (auto vmmOp = dyn_cast<PimVMMOp>(op); vmmOp && operandAndIndex.index() == 0)
|
||||
PimVSoftmaxOp>(&op)) {
|
||||
for (auto operandAndIndex : llvm::enumerate(op.getOperands())) {
|
||||
if (auto vmmOp = dyn_cast<PimVMMOp>(&op); vmmOp && operandAndIndex.index() == 0)
|
||||
continue;
|
||||
verifyOperand(operandAndIndex.value(), operandAndIndex.index());
|
||||
}
|
||||
}
|
||||
return success();
|
||||
});
|
||||
};
|
||||
|
||||
moduleOp.walk([&](pim::PimCoreOp coreOp) { verifyWithKnowledge(coreOp, seedCoreKnowledge(coreOp)); });
|
||||
moduleOp.walk([&](pim::PimCoreBatchOp coreBatchOp) {
|
||||
StaticValueKnowledge knowledge = seedCoreBatchKnowledge(coreBatchOp, 0);
|
||||
verifyWithKnowledge(coreBatchOp, knowledge);
|
||||
});
|
||||
|
||||
if (hasFailure) {
|
||||
@@ -382,18 +456,25 @@ LogicalResult PimBufferizationPass::verifyContiguousRuntimeOperands(ModuleOp mod
|
||||
}
|
||||
|
||||
LogicalResult PimBufferizationPass::verifyPimCopyAddressSpaces(ModuleOp moduleOp) const {
|
||||
bool hasFailure = false;
|
||||
size_t failureCount = 0;
|
||||
auto verifyWithKnowledge = [&](auto coreLikeOp, const StaticValueKnowledge& initialKnowledge) {
|
||||
(void) walkPimCoreBlockStructurally(
|
||||
coreLikeOp.getBody().front(), initialKnowledge, [&](Operation& op, const StaticValueKnowledge& knowledge) {
|
||||
if (auto copyOp = dyn_cast<pim::PimMemCopyOp>(&op); copyOp && failed(verifyLoweredPimCopy(copyOp, knowledge)))
|
||||
hasFailure = true;
|
||||
if (auto copyOp = dyn_cast<pim::PimMemCopyOp>(&op);
|
||||
copyOp && failed(verifyPimCopyEndpoints(copyOp, copyOp.getSource(), copyOp.getTarget(),
|
||||
ExpectedPimCopyDirection::DeviceToDevice,
|
||||
knowledge, failureCount == 0)))
|
||||
++failureCount;
|
||||
if (auto copyOp = dyn_cast<pim::PimMemCopyHostToDevOp>(&op);
|
||||
copyOp && failed(verifyLoweredPimCopy(copyOp, knowledge)))
|
||||
hasFailure = true;
|
||||
copyOp && failed(verifyPimCopyEndpoints(copyOp, copyOp.getHostSource(), copyOp.getDeviceTarget(),
|
||||
ExpectedPimCopyDirection::HostToDevice,
|
||||
knowledge, failureCount == 0)))
|
||||
++failureCount;
|
||||
if (auto copyOp = dyn_cast<pim::PimMemCopyDevToHostOp>(&op);
|
||||
copyOp && failed(verifyLoweredPimCopy(copyOp, knowledge)))
|
||||
hasFailure = true;
|
||||
copyOp && failed(verifyPimCopyEndpoints(copyOp, copyOp.getDeviceSource(), copyOp.getHostTarget(),
|
||||
ExpectedPimCopyDirection::DeviceToHost,
|
||||
knowledge, failureCount == 0)))
|
||||
++failureCount;
|
||||
return success();
|
||||
});
|
||||
};
|
||||
@@ -403,7 +484,10 @@ LogicalResult PimBufferizationPass::verifyPimCopyAddressSpaces(ModuleOp moduleOp
|
||||
StaticValueKnowledge knowledge = seedCoreBatchKnowledge(coreBatchOp, 0);
|
||||
verifyWithKnowledge(coreBatchOp, knowledge);
|
||||
});
|
||||
return success(!hasFailure);
|
||||
if (failureCount != 0)
|
||||
moduleOp.emitError() << "found " << failureCount
|
||||
<< " PIM copy address-space violation(s); the first is reported above";
|
||||
return success(failureCount == 0);
|
||||
}
|
||||
|
||||
std::unique_ptr<Pass> createPimBufferizationPass() { return std::make_unique<PimBufferizationPass>(); }
|
||||
|
||||
@@ -41,7 +41,7 @@ struct HostConstantFoldingPass : PassWrapper<HostConstantFoldingPass, OperationP
|
||||
return;
|
||||
}
|
||||
|
||||
dumpModule(moduleOp, "pim3_folded");
|
||||
dumpModule(moduleOp, "pim2_folded");
|
||||
}
|
||||
|
||||
std::shared_ptr<const FrozenRewritePatternSet> patterns;
|
||||
|
||||
@@ -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
|
||||
)
|
||||
+258
-321
@@ -1,14 +1,18 @@
|
||||
#include "mlir/Dialect/Bufferization/IR/BufferizableOpInterface.h"
|
||||
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||
#include "mlir/Pass/Pass.h"
|
||||
#include "mlir/IR/Value.h"
|
||||
#include "mlir/Interfaces/DestinationStyleOpInterface.h"
|
||||
|
||||
#include "llvm/ADT/DenseMap.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 <fstream>
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
@@ -16,9 +20,13 @@
|
||||
|
||||
#include "Common/Support/CheckedArithmetic.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/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/Transforms/LocalMemoryPlanning/LocalMemoryPlanning.hpp"
|
||||
#include "src/Accelerators/PIM/Pass/PIMPasses.h"
|
||||
|
||||
using namespace llvm;
|
||||
using namespace mlir;
|
||||
@@ -26,24 +34,9 @@ using namespace onnx_mlir;
|
||||
|
||||
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 {
|
||||
uint64_t start = 0;
|
||||
uint64_t end = 0;
|
||||
Operation* startOp = nullptr;
|
||||
Operation* endOp = nullptr;
|
||||
Operation* firstTouchOp = nullptr;
|
||||
Operation* lastTouchOp = nullptr;
|
||||
uint64_t firstTouchPosition = 0;
|
||||
@@ -53,7 +46,6 @@ struct MemoryTouchInterval {
|
||||
bool endUsedFallback = false;
|
||||
bool escapesLoop = false;
|
||||
std::string fallbackReason;
|
||||
llvm::SmallVector<std::string, 8> aliasesFollowed;
|
||||
};
|
||||
|
||||
struct OperationOrdering {
|
||||
@@ -62,50 +54,6 @@ struct OperationOrdering {
|
||||
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) {
|
||||
if (text.size() <= maxLen)
|
||||
return text.str();
|
||||
@@ -113,21 +61,23 @@ static std::string abbreviate(StringRef text, size_t maxLen) {
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!op)
|
||||
return "<none>";
|
||||
std::string prefix = op->getName().getStringRef().str();
|
||||
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);
|
||||
return abbreviate(op->getName().getStringRef(), maxLen);
|
||||
}
|
||||
|
||||
static void assignOperationOrdering(Operation* op, OperationOrdering& ordering) {
|
||||
@@ -153,10 +103,6 @@ static OperationOrdering buildOperationOrdering(Operation* coreLikeOp) {
|
||||
return ordering;
|
||||
}
|
||||
|
||||
static bool isSupportedAliasOp(Operation* op) {
|
||||
return isa<memref::SubViewOp, memref::CastOp, memref::CollapseShapeOp, memref::ExpandShapeOp>(op);
|
||||
}
|
||||
|
||||
static bool isRuntimeMemoryTouchOp(Operation* op) {
|
||||
return isa<pim::PimMemCopyHostToDevOp,
|
||||
pim::PimMemCopyDevToHostOp,
|
||||
@@ -179,7 +125,8 @@ static bool isRuntimeMemoryTouchOp(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) {
|
||||
@@ -206,105 +153,49 @@ static void addFallbackReason(std::string& reason, StringRef newReason) {
|
||||
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 {
|
||||
uint64_t start = 0;
|
||||
uint64_t end = 0;
|
||||
Operation* startOp = nullptr;
|
||||
Operation* endOp = nullptr;
|
||||
bool escapedLoop = false;
|
||||
};
|
||||
|
||||
static OrderedTouchRange
|
||||
getEffectiveTouchRange(mlir::Value definingValue, Operation* user, const OperationOrdering& ordering) {
|
||||
OrderedTouchRange range {ordering.position.lookup(user), ordering.position.lookup(user), user, user, false};
|
||||
OrderedTouchRange range {ordering.position.lookup(user), ordering.position.lookup(user), false};
|
||||
for (Operation* current = user; current; current = current->getParentOp()) {
|
||||
auto forOp = dyn_cast<scf::ForOp>(current);
|
||||
if (!forOp || isWithin(definingValue, &forOp.getRegion()))
|
||||
continue;
|
||||
range.start = std::min(range.start, ordering.position.lookup(forOp));
|
||||
range.end = std::max(range.end, ordering.subtreeEnd.lookup(forOp));
|
||||
range.startOp = forOp;
|
||||
range.endOp = forOp;
|
||||
range.escapedLoop = true;
|
||||
}
|
||||
return range;
|
||||
}
|
||||
|
||||
static MemoryTouchInterval
|
||||
computeMemoryTouchInterval(memref::AllocOp allocOp, const OperationOrdering& ordering, uint64_t fallbackEnd) {
|
||||
static MemoryTouchInterval computeMemoryTouchInterval(memref::AllocOp allocOp,
|
||||
const OperationOrdering& ordering,
|
||||
uint64_t fallbackEnd) {
|
||||
MemoryTouchInterval interval;
|
||||
interval.start = ordering.position.lookup(allocOp);
|
||||
interval.end = interval.start;
|
||||
interval.startOp = allocOp;
|
||||
interval.endOp = allocOp;
|
||||
|
||||
SmallPtrSet<mlir::Value, 16> visitedValues;
|
||||
SmallPtrSet<Operation*, 32> visitedUsers;
|
||||
SmallVector<mlir::Value> pendingValues;
|
||||
pendingValues.push_back(allocOp.getResult());
|
||||
auto parentLoop = allocOp->getParentOfType<scf::ForOp>();
|
||||
|
||||
while (!pendingValues.empty()) {
|
||||
mlir::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)) {
|
||||
for (mlir::Value result : user->getResults()) {
|
||||
pendingValues.push_back(result);
|
||||
appendAliasDescription(interval.aliasesFollowed, 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);
|
||||
appendAliasDescription(interval.aliasesFollowed, 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));
|
||||
appendAliasDescription(interval.aliasesFollowed, forOp.getRegionIterArgs()[index]);
|
||||
appendAliasDescription(interval.aliasesFollowed, forOp.getResult(index));
|
||||
if (parentLoop && forOp != parentLoop)
|
||||
(void) pim::walkLocalMemoryUses(
|
||||
allocOp.getResult(),
|
||||
[&](mlir::Value value, Operation* user) {
|
||||
if (auto forOp = dyn_cast<scf::ForOp>(user);
|
||||
forOp && parentLoop && forOp != parentLoop && llvm::is_contained(forOp.getInitArgs(), value))
|
||||
interval.escapesLoop = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto yieldOp = dyn_cast<scf::YieldOp>(user)) {
|
||||
auto forOp = dyn_cast<scf::ForOp>(yieldOp->getParentOp());
|
||||
if (!forOp) {
|
||||
auto ifOp = dyn_cast<scf::IfOp>(yieldOp->getParentOp());
|
||||
auto indexSwitch = dyn_cast<scf::IndexSwitchOp>(yieldOp->getParentOp());
|
||||
if (!forOp && !ifOp && !indexSwitch)
|
||||
addFallbackReason(interval.fallbackReason, "yield without scf.for parent");
|
||||
}
|
||||
else {
|
||||
for (auto [index, operand] : llvm::enumerate(yieldOp.getOperands())) {
|
||||
if (operand != value)
|
||||
continue;
|
||||
pendingValues.push_back(forOp.getResult(index));
|
||||
appendAliasDescription(interval.aliasesFollowed, forOp.getResult(index));
|
||||
if (parentLoop && forOp == parentLoop)
|
||||
else if (forOp && parentLoop && forOp == parentLoop && llvm::is_contained(yieldOp.getOperands(), value))
|
||||
interval.escapesLoop = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isRuntimeMemoryTouchOp(user)) {
|
||||
uint64_t touchPosition = ordering.position.lookup(user);
|
||||
@@ -322,48 +213,38 @@ computeMemoryTouchInterval(memref::AllocOp allocOp, const OperationOrdering& ord
|
||||
if (!interval.hasRuntimeUse) {
|
||||
interval.start = range.start;
|
||||
interval.end = range.end;
|
||||
interval.startOp = range.startOp;
|
||||
interval.endOp = range.endOp;
|
||||
interval.hasRuntimeUse = true;
|
||||
}
|
||||
else {
|
||||
if (range.start < interval.start) {
|
||||
if (range.start < interval.start)
|
||||
interval.start = range.start;
|
||||
interval.startOp = range.startOp;
|
||||
}
|
||||
if (range.end > interval.end) {
|
||||
if (range.end > interval.end)
|
||||
interval.end = range.end;
|
||||
interval.endOp = range.endOp;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
return success();
|
||||
}
|
||||
|
||||
if (isIgnoredLivenessUser(user))
|
||||
continue;
|
||||
return success();
|
||||
|
||||
addFallbackReason(interval.fallbackReason, "unhandled user op");
|
||||
interval.endUsedFallback = true;
|
||||
}
|
||||
}
|
||||
return success();
|
||||
});
|
||||
|
||||
if (!interval.hasRuntimeUse) {
|
||||
interval.startUsedAllocFallback = true;
|
||||
interval.endUsedFallback = true;
|
||||
interval.start = ordering.position.lookup(allocOp);
|
||||
interval.end = fallbackEnd;
|
||||
interval.startOp = allocOp;
|
||||
interval.endOp = allocOp->getParentOp();
|
||||
interval.firstTouchPosition = interval.start;
|
||||
interval.lastTouchPosition = interval.end;
|
||||
addFallbackReason(interval.fallbackReason, "no runtime memory touch");
|
||||
return interval;
|
||||
}
|
||||
|
||||
if (interval.endUsedFallback) {
|
||||
if (interval.endUsedFallback)
|
||||
interval.end = std::max(interval.end, fallbackEnd);
|
||||
interval.endOp = allocOp->getParentOp();
|
||||
}
|
||||
|
||||
return interval;
|
||||
}
|
||||
@@ -389,10 +270,21 @@ static uint64_t getSlotLogicalBytes(const PlannedPhysicalSlot& slot, ArrayRef<Lo
|
||||
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
|
||||
|
||||
SmallVector<LocalAllocInterval, 0> onnx_mlir::buildLocalAllocIntervals(Operation* coreLikeOp,
|
||||
std::optional<unsigned> lane) {
|
||||
SmallVector<LocalAllocInterval, 0> onnx_mlir::buildLocalAllocIntervals(Operation* coreLikeOp) {
|
||||
SmallVector<LocalAllocInterval, 0> intervals;
|
||||
OperationOrdering ordering = buildOperationOrdering(coreLikeOp);
|
||||
if (ordering.position.empty())
|
||||
@@ -413,12 +305,9 @@ SmallVector<LocalAllocInterval, 0> onnx_mlir::buildLocalAllocIntervals(Operation
|
||||
LocalAllocInterval interval;
|
||||
interval.id = nextIntervalId++;
|
||||
interval.alloc = allocOp;
|
||||
interval.key = getMemoryValueKey(allocOp.getResult(), lane);
|
||||
interval.start = touchInterval.start;
|
||||
interval.end = touchInterval.end;
|
||||
interval.size = *checkedSize;
|
||||
interval.startOp = touchInterval.startOp;
|
||||
interval.endOp = touchInterval.endOp;
|
||||
interval.firstTouchOp = touchInterval.firstTouchOp;
|
||||
interval.lastTouchOp = touchInterval.lastTouchOp;
|
||||
interval.firstTouchPosition = touchInterval.firstTouchPosition;
|
||||
@@ -429,7 +318,9 @@ SmallVector<LocalAllocInterval, 0> onnx_mlir::buildLocalAllocIntervals(Operation
|
||||
interval.insideNestedRegion = isNestedAllocation(coreLikeOp, allocOp);
|
||||
interval.escapesLoop = touchInterval.escapesLoop;
|
||||
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));
|
||||
});
|
||||
|
||||
@@ -454,60 +345,94 @@ SmallVector<PlannedPhysicalSlot, 0> onnx_mlir::planPhysicalSlots(MutableArrayRef
|
||||
|
||||
for (size_t intervalIndex : intervalOrder) {
|
||||
LocalAllocInterval& interval = intervals[intervalIndex];
|
||||
PlannedPhysicalSlot* bestSlot = nullptr;
|
||||
auto bestKey = std::tuple<size_t, size_t, size_t, size_t>(std::numeric_limits<size_t>::max(),
|
||||
std::numeric_limits<size_t>::max(),
|
||||
std::numeric_limits<size_t>::max(),
|
||||
SmallVector<const PlannedPhysicalSlot*, 16> conflictingSlots;
|
||||
SmallVector<size_t, 16> candidateAddresses = {0};
|
||||
size_t currentPeak = 0;
|
||||
for (const PlannedPhysicalSlot& slot : slots) {
|
||||
currentPeak = std::max(currentPeak, slot.address + slot.requiredSize);
|
||||
if (llvm::any_of(slot.intervalIndices, [&](size_t otherIndex) {
|
||||
return intervalsOverlap(interval, intervals[otherIndex]);
|
||||
})) {
|
||||
conflictingSlots.push_back(&slot);
|
||||
size_t candidate = llvm::alignTo(slot.address + slot.requiredSize, 4);
|
||||
if (!llvm::is_contained(candidateAddresses, candidate))
|
||||
candidateAddresses.push_back(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
size_t bestAddress = std::numeric_limits<size_t>::max();
|
||||
auto bestKey = std::tuple<size_t, size_t>(std::numeric_limits<size_t>::max(),
|
||||
std::numeric_limits<size_t>::max());
|
||||
|
||||
for (size_t slotIndex = 0; slotIndex < slots.size(); ++slotIndex) {
|
||||
PlannedPhysicalSlot& slot = slots[slotIndex];
|
||||
bool compatible = true;
|
||||
for (size_t otherIndex : slot.intervalIndices) {
|
||||
if (intervalsOverlap(interval, intervals[otherIndex])) {
|
||||
compatible = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!compatible)
|
||||
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;
|
||||
|
||||
size_t resultingSize = std::max(slot.requiredSize, interval.size);
|
||||
size_t growth = resultingSize - slot.requiredSize;
|
||||
auto candidateKey =
|
||||
std::tuple<size_t, size_t, size_t, size_t>(growth, resultingSize, slot.intervalIndices.size(), slot.id);
|
||||
auto candidateKey = std::tuple<size_t, size_t>(std::max(currentPeak, candidate + interval.size), candidate);
|
||||
if (candidateKey < bestKey) {
|
||||
bestKey = candidateKey;
|
||||
bestSlot = &slot;
|
||||
bestAddress = candidate;
|
||||
}
|
||||
}
|
||||
assert(bestAddress != std::numeric_limits<size_t>::max() && "address after all conflicts must be available");
|
||||
|
||||
if (!bestSlot) {
|
||||
slots.push_back({slots.size(), interval.size, interval.size, 0, {intervalIndex}});
|
||||
auto reusable = llvm::find_if(slots, [&](const PlannedPhysicalSlot& slot) {
|
||||
return slot.address == bestAddress && slot.requiredSize == interval.size;
|
||||
});
|
||||
if (reusable != slots.end()) {
|
||||
reusable->intervalIndices.push_back(intervalIndex);
|
||||
interval.slotPlanIndex = static_cast<size_t>(reusable - slots.begin());
|
||||
}
|
||||
else {
|
||||
slots.push_back({slots.size(), interval.size, bestAddress, {intervalIndex}});
|
||||
interval.slotPlanIndex = slots.size() - 1;
|
||||
interval.physicalSlotId = slots.back().id;
|
||||
interval.physicalSlotSize = slots.back().requiredSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
MemoryPlanArtifacts onnx_mlir::buildMemoryPlanArtifacts(Operation* coreLikeOp,
|
||||
std::optional<unsigned> lane,
|
||||
CoreMemoryPlan onnx_mlir::buildCoreMemoryPlan(Operation* coreLikeOp) {
|
||||
CoreMemoryPlan plan;
|
||||
plan.coreLikeOp = coreLikeOp;
|
||||
plan.intervals = buildLocalAllocIntervals(coreLikeOp);
|
||||
plan.slots = planPhysicalSlots(plan.intervals);
|
||||
return plan;
|
||||
}
|
||||
|
||||
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) {
|
||||
MemoryPlanArtifacts artifacts;
|
||||
std::string artifacts;
|
||||
|
||||
uint64_t totalLogicalBytes = 0;
|
||||
uint64_t totalPhysicalBytes = 0;
|
||||
@@ -523,7 +448,6 @@ MemoryPlanArtifacts onnx_mlir::buildMemoryPlanArtifacts(Operation* coreLikeOp,
|
||||
for (const LocalAllocInterval& interval : intervals) {
|
||||
totalLogicalBytes += interval.size;
|
||||
largestLogicalAllocation = std::max(largestLogicalAllocation, interval.size);
|
||||
maximumAssignedAddress = std::max(maximumAssignedAddress, interval.assignedAddress + interval.physicalSlotSize);
|
||||
if (interval.startUsedAllocFallback || interval.endUsedFallback)
|
||||
++fallbackIntervals;
|
||||
if (!interval.hasRuntimeUse)
|
||||
@@ -533,22 +457,22 @@ MemoryPlanArtifacts onnx_mlir::buildMemoryPlanArtifacts(Operation* coreLikeOp,
|
||||
if (interval.escapesLoop)
|
||||
++loopEscapingIntervals;
|
||||
}
|
||||
for (const PlannedPhysicalSlot& slot : slots) {
|
||||
totalPhysicalBytes += slot.size;
|
||||
largestPhysicalSlot = std::max(largestPhysicalSlot, slot.size);
|
||||
if (slot.intervalIndices.size() > 1)
|
||||
reusedAllocations += slot.intervalIndices.size() - 1;
|
||||
for (size_t slotIndex = 0; slotIndex < slots.size(); ++slotIndex) {
|
||||
const PlannedPhysicalSlot& slot = slots[slotIndex];
|
||||
largestPhysicalSlot = std::max(largestPhysicalSlot, slot.requiredSize);
|
||||
maximumAssignedAddress = std::max(maximumAssignedAddress, slot.address + slot.requiredSize);
|
||||
if (hasAddressReuse(slotIndex, slots))
|
||||
reusedAllocations += slot.intervalIndices.size();
|
||||
}
|
||||
totalPhysicalBytes = maximumAssignedAddress;
|
||||
|
||||
uint64_t savedBytes = totalLogicalBytes >= totalPhysicalBytes ? totalLogicalBytes - totalPhysicalBytes : 0;
|
||||
double savedPercent =
|
||||
totalLogicalBytes == 0 ? 0.0 : 100.0 * static_cast<double>(savedBytes) / static_cast<double>(totalLogicalBytes);
|
||||
|
||||
raw_string_ostream os(artifacts.textReport);
|
||||
raw_string_ostream os(artifacts);
|
||||
os << "=== PIM Memory Liveness Report ===\n";
|
||||
os << "Op: " << coreLikeOp->getName() << "\n";
|
||||
if (lane)
|
||||
os << "Lane: " << *lane << "\n";
|
||||
os << "Summary:\n";
|
||||
os << " logical allocation bytes: " << formatReportMemory(totalLogicalBytes) << " (" << totalLogicalBytes << ")\n";
|
||||
os << " physical allocation bytes: " << formatReportMemory(totalPhysicalBytes) << " (" << totalPhysicalBytes
|
||||
@@ -556,32 +480,27 @@ MemoryPlanArtifacts onnx_mlir::buildMemoryPlanArtifacts(Operation* coreLikeOp,
|
||||
os << " saved bytes: " << formatReportMemory(savedBytes) << " (" << savedBytes << ")\n";
|
||||
os << " saved percent: " << format("%.2f%%", savedPercent) << "\n";
|
||||
os << " intervals: " << intervals.size() << "\n";
|
||||
os << " physical slots: " << slots.size() << "\n";
|
||||
os << " reused allocations: " << reusedAllocations << "\n";
|
||||
os << " address placements: " << slots.size() << "\n";
|
||||
os << " allocations sharing address space: " << reusedAllocations << "\n";
|
||||
os << " fallback intervals: " << fallbackIntervals << "\n";
|
||||
os << " intervals with no runtime memory touch: " << noRuntimeTouchIntervals << "\n";
|
||||
os << " nested allocations: " << nestedIntervals << "\n";
|
||||
os << " loop-escaping allocations: " << loopEscapingIntervals << "\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 << " peak physical memory: " << formatReportMemory(maximumAssignedAddress) << " (" << 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*> singleUseSlots;
|
||||
for (const PlannedPhysicalSlot& slot : slots)
|
||||
if (slot.intervalIndices.size() > 1)
|
||||
reusedSlots.push_back(&slot);
|
||||
for (size_t slotIndex = 0; slotIndex < slots.size(); ++slotIndex) {
|
||||
if (hasAddressReuse(slotIndex, slots))
|
||||
reusedSlots.push_back(&slots[slotIndex]);
|
||||
else
|
||||
singleUseSlots.push_back(&slot);
|
||||
singleUseSlots.push_back(&slots[slotIndex]);
|
||||
}
|
||||
|
||||
llvm::stable_sort(reusedSlots, [&](const PlannedPhysicalSlot* lhs, const PlannedPhysicalSlot* rhs) {
|
||||
uint64_t lhsLogicalBytes = getSlotLogicalBytes(*lhs, intervals);
|
||||
@@ -590,140 +509,66 @@ MemoryPlanArtifacts onnx_mlir::buildMemoryPlanArtifacts(Operation* coreLikeOp,
|
||||
return lhs->intervalIndices.size() > rhs->intervalIndices.size();
|
||||
if (lhsLogicalBytes != rhsLogicalBytes)
|
||||
return lhsLogicalBytes > rhsLogicalBytes;
|
||||
if (lhs->size != rhs->size)
|
||||
return lhs->size > rhs->size;
|
||||
if (lhs->requiredSize != rhs->requiredSize)
|
||||
return lhs->requiredSize > rhs->requiredSize;
|
||||
return lhs->id < rhs->id;
|
||||
});
|
||||
llvm::stable_sort(singleUseSlots, [&](const PlannedPhysicalSlot* lhs, const PlannedPhysicalSlot* rhs) {
|
||||
if (lhs->size != rhs->size)
|
||||
return lhs->size > rhs->size;
|
||||
if (lhs->requiredSize != rhs->requiredSize)
|
||||
return lhs->requiredSize > rhs->requiredSize;
|
||||
return lhs->id < rhs->id;
|
||||
});
|
||||
|
||||
constexpr size_t kSummaryReuseLimit = 6;
|
||||
constexpr size_t kSummaryOffenderLimit = 10;
|
||||
|
||||
os << "\nBest Reuse:\n";
|
||||
os << "\nBest Address Reuse:\n";
|
||||
if (reusedSlots.empty()) {
|
||||
os << " no slots were shared by multiple intervals\n";
|
||||
os << " no address ranges were reused\n";
|
||||
}
|
||||
else {
|
||||
for (const PlannedPhysicalSlot* slot : ArrayRef(reusedSlots).take_front(kSummaryReuseLimit)) {
|
||||
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)
|
||||
<< "\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";
|
||||
bool printedAttention = false;
|
||||
for (const PlannedPhysicalSlot* slot : ArrayRef(singleUseSlots).take_front(kSummaryOffenderLimit)) {
|
||||
const LocalAllocInterval& interval = intervals[slot->intervalIndices.front()];
|
||||
printedAttention = true;
|
||||
os << " slot #" << slot->id << " is single-use"
|
||||
<< " size=" << formatReportMemory(slot->size) << " interval=#" << interval.id
|
||||
<< " value=" << summarizeValue(interval.key.value, 56) << "\n";
|
||||
os << " first=" << summarizeOperation(interval.firstTouchOp, 40)
|
||||
<< " last=" << summarizeOperation(interval.lastTouchOp, 40)
|
||||
<< " size=" << formatReportMemory(slot->requiredSize) << " interval=#" << interval.id
|
||||
<< " value=" << abbreviate(interval.valueSummary, 56) << "\n";
|
||||
os << " first=" << abbreviate(interval.firstTouchSummary, 40)
|
||||
<< " last=" << abbreviate(interval.lastTouchSummary, 40)
|
||||
<< " nested=" << (interval.insideNestedRegion ? "yes" : "no")
|
||||
<< " escapes_loop=" << (interval.escapesLoop ? "yes" : "no") << "\n";
|
||||
}
|
||||
size_t fallbackPrinted = 0;
|
||||
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)
|
||||
if (singleUseSlots.empty())
|
||||
os << " no obvious blockers detected in this core\n";
|
||||
|
||||
if (reportLevel == PimMemoryReportFull) {
|
||||
os << "\nSlot Reuse:\n";
|
||||
for (const PlannedPhysicalSlot& slot : slots) {
|
||||
uint64_t slotLogicalBytes = getSlotLogicalBytes(slot, intervals);
|
||||
os << " slot #" << slot.id << " addr=" << slot.address << " size=" << formatReportMemory(slot.size) << " ("
|
||||
<< slot.size << ")"
|
||||
os << " slot #" << slot.id << " addr=" << slot.address << " size=" << formatReportMemory(slot.requiredSize)
|
||||
<< " (" << slot.requiredSize << ")"
|
||||
<< " intervals=" << slot.intervalIndices.size() << " logical_sum=" << formatReportMemory(slotLogicalBytes)
|
||||
<< "\n";
|
||||
for (size_t intervalIndex : slot.intervalIndices) {
|
||||
const LocalAllocInterval& interval = intervals[intervalIndex];
|
||||
mlir::Value allocValue = interval.key.value;
|
||||
os << " [" << interval.start << "," << interval.end << "]"
|
||||
<< " #" << interval.id << " logical=" << formatReportMemory(interval.size)
|
||||
<< " #" << interval.id << " logical=" << formatReportMemory(interval.size) << " (" << interval.size
|
||||
<< ")"
|
||||
<< " nested=" << (interval.insideNestedRegion ? "yes" : "no")
|
||||
<< " escapes_loop=" << (interval.escapesLoop ? "yes" : "no")
|
||||
<< " first=" << summarizeOperation(interval.firstTouchOp, 48)
|
||||
<< " last=" << summarizeOperation(interval.lastTouchOp, 48) << "\n";
|
||||
os << " value=" << summarizeValue(allocValue) << "\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";
|
||||
<< " first=" << abbreviate(interval.firstTouchSummary, 48)
|
||||
<< " last=" << abbreviate(interval.lastTouchSummary, 48) << "\n";
|
||||
os << " value=" << abbreviate(interval.valueSummary, 72) << "\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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -731,3 +576,95 @@ MemoryPlanArtifacts onnx_mlir::buildMemoryPlanArtifacts(Operation* coreLikeOp,
|
||||
|
||||
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
|
||||
+15
-14
@@ -6,10 +6,8 @@
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "src/Accelerators/PIM/Compiler/PimCodeGen.hpp"
|
||||
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
||||
|
||||
namespace onnx_mlir {
|
||||
@@ -17,12 +15,9 @@ namespace onnx_mlir {
|
||||
struct LocalAllocInterval {
|
||||
size_t id = 0;
|
||||
mlir::memref::AllocOp alloc;
|
||||
MemoryValueKey key;
|
||||
uint64_t start = 0;
|
||||
uint64_t end = 0;
|
||||
size_t size = 0;
|
||||
mlir::Operation* startOp = nullptr;
|
||||
mlir::Operation* endOp = nullptr;
|
||||
mlir::Operation* firstTouchOp = nullptr;
|
||||
mlir::Operation* lastTouchOp = nullptr;
|
||||
uint64_t firstTouchPosition = 0;
|
||||
@@ -33,28 +28,34 @@ struct LocalAllocInterval {
|
||||
bool insideNestedRegion = false;
|
||||
bool escapesLoop = false;
|
||||
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 physicalSlotId = std::numeric_limits<size_t>::max();
|
||||
size_t assignedAddress = 0;
|
||||
size_t physicalSlotSize = 0;
|
||||
};
|
||||
|
||||
struct PlannedPhysicalSlot {
|
||||
size_t id = std::numeric_limits<size_t>::max();
|
||||
size_t requiredSize = 0;
|
||||
size_t size = 0;
|
||||
size_t address = 0;
|
||||
llvm::SmallVector<size_t, 8> intervalIndices;
|
||||
};
|
||||
|
||||
llvm::SmallVector<LocalAllocInterval, 0> buildLocalAllocIntervals(mlir::Operation* coreLikeOp,
|
||||
std::optional<unsigned> lane);
|
||||
struct CoreMemoryPlan {
|
||||
mlir::Operation* coreLikeOp = nullptr;
|
||||
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);
|
||||
|
||||
MemoryPlanArtifacts buildMemoryPlanArtifacts(mlir::Operation* coreLikeOp,
|
||||
std::optional<unsigned> lane,
|
||||
CoreMemoryPlan buildCoreMemoryPlan(mlir::Operation* coreLikeOp);
|
||||
|
||||
mlir::LogicalResult assignPhysicalSlotAddresses(CoreMemoryPlan& plan, size_t addressLimit);
|
||||
|
||||
std::string buildMemoryPlanReport(mlir::Operation* coreLikeOp,
|
||||
llvm::ArrayRef<LocalAllocInterval> intervals,
|
||||
llvm::ArrayRef<PlannedPhysicalSlot> slots,
|
||||
size_t addressLimit,
|
||||
@@ -10,5 +10,6 @@ add_pim_library(OMPimMemoryCoalescing
|
||||
|
||||
LINK_LIBS PUBLIC
|
||||
OMPimCommon
|
||||
OMPimLocalMemoryLifetimeAnalysis
|
||||
PimOps
|
||||
)
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||
#include "mlir/Interfaces/DestinationStyleOpInterface.h"
|
||||
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/ADT/SmallPtrSet.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#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"
|
||||
|
||||
using namespace mlir;
|
||||
@@ -19,10 +17,6 @@ namespace pim {
|
||||
|
||||
namespace {
|
||||
|
||||
static bool isSupportedAliasOp(Operation* op) {
|
||||
return isa<memref::SubViewOp, memref::CastOp, memref::CollapseShapeOp, memref::ExpandShapeOp>(op);
|
||||
}
|
||||
|
||||
static bool isCandidateAllocType(MemRefType type) {
|
||||
return type && type.hasStaticShape() && type.getLayout().isIdentity()
|
||||
&& hasByteSizedElementType(type.getElementType());
|
||||
@@ -44,59 +38,21 @@ static void analyzeBlock(Block& block, MemoryCoalescingAnalysis& analysis);
|
||||
static FailureOr<uint64_t>
|
||||
getLastUseInstruction(memref::AllocOp allocOp, Block& scopeBlock, const DenseMap<Operation*, uint64_t>& opOrder) {
|
||||
uint64_t endInstruction = opOrder.lookup(allocOp);
|
||||
SmallPtrSet<Value, 16> visitedValues;
|
||||
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 (failed(walkLocalMemoryUses(allocOp.getResult(), [&](Value, Operation* user) {
|
||||
if (auto yieldOp = dyn_cast<scf::YieldOp>(user)) {
|
||||
auto forOp = dyn_cast<scf::ForOp>(yieldOp->getParentOp());
|
||||
if (!forOp)
|
||||
if (!isa<scf::ForOp>(yieldOp->getParentOp()))
|
||||
return failure();
|
||||
for (auto [index, operand] : llvm::enumerate(yieldOp.getOperands()))
|
||||
if (operand == value)
|
||||
pendingValues.push_back(forOp.getResult(index));
|
||||
}
|
||||
|
||||
Operation* orderedUser = getTopLevelAncestorInBlock(user, scopeBlock);
|
||||
if (!orderedUser)
|
||||
return failure();
|
||||
|
||||
auto order = opOrder.find(orderedUser);
|
||||
if (order == opOrder.end())
|
||||
return failure();
|
||||
endInstruction = std::max(endInstruction, order->second);
|
||||
}
|
||||
}
|
||||
return success();
|
||||
})))
|
||||
return failure();
|
||||
|
||||
return endInstruction;
|
||||
}
|
||||
@@ -133,7 +89,7 @@ static void analyzeBlock(Block& block, MemoryCoalescingAnalysis& analysis) {
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -150,6 +106,14 @@ uint64_t MemoryCoalescingAnalysis::getCandidateCount() const {
|
||||
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 analysis;
|
||||
if (!coreLikeOp || coreLikeOp->getNumRegions() != 1 || coreLikeOp->getRegion(0).empty())
|
||||
|
||||
@@ -10,7 +10,6 @@ namespace pim {
|
||||
|
||||
struct AllocationCandidate {
|
||||
mlir::memref::AllocOp alloc;
|
||||
mlir::Block* scopeBlock = nullptr;
|
||||
uint64_t startInstruction = 0;
|
||||
uint64_t endInstruction = 0;
|
||||
uint64_t sizeBytes = 0;
|
||||
@@ -27,6 +26,7 @@ struct MemoryCoalescingAnalysis {
|
||||
uint64_t skippedAllocations = 0;
|
||||
|
||||
uint64_t getCandidateCount() const;
|
||||
uint64_t getCandidateBytes() const;
|
||||
};
|
||||
|
||||
struct MemoryCoalescingStats {
|
||||
|
||||
@@ -1,209 +1,96 @@
|
||||
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
||||
#include "mlir/IR/PatternMatch.h"
|
||||
#include "mlir/Pass/Pass.h"
|
||||
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/Support/raw_os_ostream.h"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include "Common/IR/CompactAsmUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.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/Dialect/Pim/PimOps.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Pim/Transforms/MemoryCoalescing/MemoryCoalescing.hpp"
|
||||
#include "src/Accelerators/PIM/Pass/PIMPasses.h"
|
||||
|
||||
using namespace mlir;
|
||||
using namespace onnx_mlir::compact_asm;
|
||||
|
||||
namespace onnx_mlir {
|
||||
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 {
|
||||
uint64_t numCandidates = 0;
|
||||
uint64_t numSkipped = 0;
|
||||
uint64_t numRemoved = 0;
|
||||
uint64_t candidates = 0;
|
||||
uint64_t logicalBytes = 0;
|
||||
uint64_t skipped = 0;
|
||||
uint64_t removed = 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 {
|
||||
enum class Kind {
|
||||
Core,
|
||||
Batch
|
||||
};
|
||||
|
||||
Kind kind = Kind::Core;
|
||||
uint64_t id = 0;
|
||||
llvm::SmallVector<int32_t, 8> coreIds;
|
||||
std::string label;
|
||||
uint64_t coreCount = 1;
|
||||
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) {
|
||||
std::fstream file = openReportFile("memory_coalescing_report");
|
||||
if (!file.is_open())
|
||||
return;
|
||||
|
||||
llvm::raw_os_ostream os(file);
|
||||
CoalescingReportRow totalRow;
|
||||
CoalescingReportRow total;
|
||||
for (const CoalescingReportEntry& entry : entries) {
|
||||
CoalescingReportRow entryTotal = getTotalRow(entry);
|
||||
totalRow.numCandidates += entryTotal.numCandidates;
|
||||
totalRow.numSkipped += entryTotal.numSkipped;
|
||||
totalRow.numRemoved += entryTotal.numRemoved;
|
||||
totalRow.savedBytes += entryTotal.savedBytes;
|
||||
total.candidates += entry.row.candidates * entry.coreCount;
|
||||
total.logicalBytes += entry.row.logicalBytes * entry.coreCount;
|
||||
total.skipped += entry.row.skipped * entry.coreCount;
|
||||
total.removed += entry.row.removed * entry.coreCount;
|
||||
total.savedBytes += entry.row.savedBytes * entry.coreCount;
|
||||
}
|
||||
|
||||
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) }
|
||||
};
|
||||
printReportTotalsBlock(os, totalFields);
|
||||
if (!entries.empty())
|
||||
os << "\n";
|
||||
|
||||
llvm::SmallVector<CoalescingReportEntry, 32> sortedEntries(entries.begin(), entries.end());
|
||||
sortReportEntriesByFirstCore(sortedEntries);
|
||||
|
||||
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;
|
||||
printReportTotalsBlock(os,
|
||||
{{"Number of candidates", std::to_string(total.candidates)},
|
||||
{"Logical candidate bytes", formatReportMemory(total.logicalBytes)},
|
||||
{"Skipped allocations", std::to_string(total.skipped)},
|
||||
{"Removed allocations", std::to_string(total.removed)},
|
||||
{"Saved memory", formatReportMemory(total.savedBytes)}});
|
||||
for (const CoalescingReportEntry& entry : entries) {
|
||||
os << "\n" << entry.label << ":\n";
|
||||
printReportFlatFields(os,
|
||||
{{"Number of candidates", std::to_string(entry.row.candidates)},
|
||||
{"Logical candidate bytes", formatReportMemory(entry.row.logicalBytes)},
|
||||
{"Skipped allocations", std::to_string(entry.row.skipped)},
|
||||
{"Removed allocations", std::to_string(entry.row.removed)},
|
||||
{"Saved memory", formatReportMemory(entry.row.savedBytes)}});
|
||||
}
|
||||
|
||||
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();
|
||||
file.close();
|
||||
}
|
||||
|
||||
struct PimMemoryCoalescingPass : PassWrapper<PimMemoryCoalescingPass, OperationPass<ModuleOp>> {
|
||||
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(PimMemoryCoalescingPass)
|
||||
|
||||
StringRef getArgument() const override { return "pim-memory-coalescing"; }
|
||||
StringRef getDescription() const override { return "Analyze local PIM memory reuse opportunities"; }
|
||||
|
||||
PimMemoryCoalescingPass() = default;
|
||||
PimMemoryCoalescingPass(const PimMemoryCoalescingPass& pass) {}
|
||||
StringRef getDescription() const override { return "Safely coalesce compatible block-local PIM allocations"; }
|
||||
|
||||
void runOnOperation() override {
|
||||
IRRewriter rewriter(&getContext());
|
||||
SmallVector<CoalescingReportEntry, 32> reportEntries;
|
||||
uint64_t nextBatchId = 0;
|
||||
bool hasFailure = false;
|
||||
|
||||
getOperation().walk([&](Operation* op) {
|
||||
if (hasFailure || !isa<pim::PimCoreOp, pim::PimCoreBatchOp>(op))
|
||||
if (!isa<pim::PimCoreOp, pim::PimCoreBatchOp>(op))
|
||||
return;
|
||||
|
||||
auto analysis = pim::analyzeMemoryCoalescingCandidates(op);
|
||||
auto stats = pim::coalesceMemory(op, analysis, rewriter);
|
||||
CoalescingReportRow row {
|
||||
analysis.getCandidateCount(), stats.skippedAllocations, stats.removedAllocs, stats.savedBytes};
|
||||
|
||||
CoalescingReportRow row {analysis.getCandidateCount(),
|
||||
analysis.getCandidateBytes(),
|
||||
stats.skippedAllocations,
|
||||
stats.removedAllocs,
|
||||
stats.savedBytes};
|
||||
if (auto coreOp = dyn_cast<pim::PimCoreOp>(op)) {
|
||||
auto checkedCoreId =
|
||||
pim::checkedI32(static_cast<uint64_t>(coreOp.getCoreId()), coreOp, "memory coalescing core id");
|
||||
if (failed(checkedCoreId)) {
|
||||
hasFailure = true;
|
||||
reportEntries.push_back({"Core " + std::to_string(coreOp.getCoreId()), 1, row});
|
||||
return;
|
||||
}
|
||||
SmallVector<int32_t> coreIds = getBatchCoreIds(cast<pim::PimCoreBatchOp>(op));
|
||||
reportEntries.push_back(
|
||||
{CoalescingReportEntry::Kind::Core, static_cast<uint64_t>(coreOp.getCoreId()), {*checkedCoreId}, row});
|
||||
return;
|
||||
}
|
||||
|
||||
auto coreIds = getBatchCoreIds(cast<pim::PimCoreBatchOp>(op));
|
||||
CoalescingReportEntry entry;
|
||||
entry.kind = CoalescingReportEntry::Kind::Batch;
|
||||
entry.id = nextBatchId++;
|
||||
llvm::append_range(entry.coreIds, coreIds);
|
||||
entry.row = row;
|
||||
reportEntries.push_back(std::move(entry));
|
||||
{"Batch " + std::to_string(nextBatchId++), std::max<uint64_t>(1, coreIds.size()), row});
|
||||
});
|
||||
|
||||
if (hasFailure) {
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
emitReport(reportEntries);
|
||||
dumpModule(getOperation(), "pim2_coalesced");
|
||||
dumpModule(getOperation(), "pim3_coalesced");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
#include "src/Accelerators/PIM/Common/IR/SubviewUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/WeightUtils.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/Compiler/PimCompilerOptions.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Pim/Transforms/Bufferization/ContiguityPatterns.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
@@ -108,6 +109,63 @@ static bool isCoreWeightBlockArgument(Value value) {
|
||||
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) {
|
||||
return isa<pim::PimMemCopyHostToDevOp,
|
||||
pim::PimMemCopyDevToHostOp,
|
||||
@@ -148,8 +206,10 @@ static bool isHostAddressableValue(Value value, const StaticValueKnowledge& know
|
||||
return isa_and_nonnull<memref::GetGlobalOp>(base.getDefiningOp());
|
||||
}
|
||||
|
||||
|
||||
enum class CommunicationEventKind { Send, Receive };
|
||||
enum class CommunicationEventKind {
|
||||
Send,
|
||||
Receive
|
||||
};
|
||||
|
||||
struct CommunicationEvent {
|
||||
CommunicationEventKind kind = CommunicationEventKind::Send;
|
||||
@@ -157,19 +217,6 @@ struct CommunicationEvent {
|
||||
int64_t peerCoreId = 0;
|
||||
int64_t size = 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;
|
||||
};
|
||||
|
||||
@@ -183,7 +230,6 @@ constexpr StringLiteral kRaptorMinChannelIdAttr = "raptor.min_channel_id";
|
||||
constexpr StringLiteral kRaptorMaterializerAttr = "raptor.materializer";
|
||||
constexpr StringLiteral kRaptorCommOrderAttr = "raptor.comm_order";
|
||||
constexpr StringLiteral kRaptorCommTraceIdAttr = "raptor.comm_trace_id";
|
||||
constexpr StringLiteral kRaptorCommTraceKindAttr = "raptor.comm_trace_kind";
|
||||
constexpr StringLiteral kRaptorCommTracePhaseAttr = "raptor.comm_trace_phase";
|
||||
constexpr StringLiteral kRaptorCommTraceClassIdAttr = "raptor.comm_trace_class_id";
|
||||
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) {
|
||||
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;
|
||||
llvm::raw_string_ostream os(text);
|
||||
os << "core " << event.coreId << " " << getCommunicationEventKindName(event.kind) << " "
|
||||
<< (event.kind == CommunicationEventKind::Send ? "to" : "from") << " " << event.peerCoreId
|
||||
<< " size " << event.size << "B ordinal " << event.ordinal;
|
||||
if (event.minChannelId)
|
||||
os << " min_channel " << *event.minChannelId;
|
||||
if (event.commOrder)
|
||||
os << " comm_order " << *event.commOrder;
|
||||
if (!event.materializer.empty())
|
||||
os << " materializer " << event.materializer;
|
||||
if (event.traceId)
|
||||
os << " trace#" << *event.traceId;
|
||||
if (!event.tracePhase.empty())
|
||||
os << " phase " << event.tracePhase;
|
||||
if (event.traceClassId)
|
||||
os << " class " << event.traceClassKind << "#" << *event.traceClassId;
|
||||
if (event.traceBlockOrdinal)
|
||||
os << " block_ordinal " << *event.traceBlockOrdinal;
|
||||
if (!event.tracePayload.empty())
|
||||
os << " payload " << event.tracePayload;
|
||||
if (!event.traceMessages.empty())
|
||||
os << " messages {" << event.traceMessages << "}";
|
||||
if (!event.tracePrevOp.empty() || !event.traceNextOp.empty())
|
||||
os << " inserted_between [" << event.tracePrevOp << " | " << event.traceNextOp << "]";
|
||||
<< (event.kind == CommunicationEventKind::Send ? "to" : "from") << " " << event.peerCoreId << " size "
|
||||
<< event.size << "B ordinal " << event.ordinal;
|
||||
if (minChannelId)
|
||||
os << " min_channel " << *minChannelId;
|
||||
if (commOrder)
|
||||
os << " comm_order " << *commOrder;
|
||||
if (!materializer.empty())
|
||||
os << " materializer " << materializer;
|
||||
if (traceId)
|
||||
os << " trace#" << *traceId;
|
||||
if (!tracePhase.empty())
|
||||
os << " phase " << tracePhase;
|
||||
if (traceClassId)
|
||||
os << " class " << traceClassKind << "#" << *traceClassId;
|
||||
if (traceBlockOrdinal)
|
||||
os << " block_ordinal " << *traceBlockOrdinal;
|
||||
if (!tracePayload.empty())
|
||||
os << " payload " << tracePayload;
|
||||
if (!traceMessages.empty())
|
||||
os << " messages {" << traceMessages << "}";
|
||||
if (!tracePrevOp.empty() || !traceNextOp.empty())
|
||||
os << " inserted_between [" << tracePrevOp << " | " << traceNextOp << "]";
|
||||
return os.str();
|
||||
}
|
||||
|
||||
@@ -261,10 +320,8 @@ static bool areMatchedCommunicationEvents(const CommunicationEvent& lhs, const C
|
||||
|| (lhs.kind == CommunicationEventKind::Receive && rhs.kind == CommunicationEventKind::Send);
|
||||
}
|
||||
|
||||
|
||||
static std::optional<size_t> findMatchingCounterpartIndex(const CommunicationEventVector& events,
|
||||
const CommunicationEvent& event,
|
||||
size_t begin) {
|
||||
static std::optional<size_t>
|
||||
findMatchingCounterpartIndex(const CommunicationEventVector& events, const CommunicationEvent& event, size_t begin) {
|
||||
for (size_t index = begin; index < events.size(); ++index)
|
||||
if (areMatchedCommunicationEvents(event, events[index]))
|
||||
return index;
|
||||
@@ -288,8 +345,7 @@ static void printCounterpartProbe(llvm::raw_ostream& os,
|
||||
peerPc = peerPcIt->second;
|
||||
|
||||
os << " counterpart probe for " << formatCommunicationEvent(blockedEvent) << "\n";
|
||||
os << " peer core " << blockedEvent.peerCoreId << " current pc " << peerPc << " of " << peerEvents.size()
|
||||
<< "\n";
|
||||
os << " peer core " << blockedEvent.peerCoreId << " current pc " << peerPc << " of " << peerEvents.size() << "\n";
|
||||
|
||||
std::optional<size_t> nextMatch = findMatchingCounterpartIndex(peerEvents, blockedEvent, peerPc);
|
||||
std::optional<size_t> anyMatch = findMatchingCounterpartIndex(peerEvents, blockedEvent, 0);
|
||||
@@ -317,7 +373,8 @@ static void printCounterpartProbe(llvm::raw_ostream& os,
|
||||
return;
|
||||
|
||||
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) {
|
||||
os << (index == peerPc ? " pc => " : " ") << "#" << index << " "
|
||||
<< formatCommunicationEvent(peerEvents[index]) << "\n";
|
||||
@@ -327,39 +384,19 @@ static void printCounterpartProbe(llvm::raw_ostream& os,
|
||||
os << " ... " << (*nextMatch - end + 1) << " more peer communication event(s) before the counterpart\n";
|
||||
}
|
||||
|
||||
static CommunicationEvent makeCommunicationEvent(CommunicationEventKind kind,
|
||||
int64_t coreId,
|
||||
int64_t peerCoreId,
|
||||
int64_t size,
|
||||
uint64_t ordinal,
|
||||
Operation* op) {
|
||||
return CommunicationEvent {kind,
|
||||
coreId,
|
||||
peerCoreId,
|
||||
size,
|
||||
ordinal,
|
||||
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 CommunicationEvent makeCommunicationEvent(
|
||||
CommunicationEventKind kind, int64_t coreId, int64_t peerCoreId, int64_t size, uint64_t ordinal, Operation* op) {
|
||||
return CommunicationEvent {kind, coreId, peerCoreId, size, ordinal, op};
|
||||
}
|
||||
|
||||
static LogicalResult appendCoreCommunicationEvents(Block& block,
|
||||
const PimCoreCommunicationPlan& plan,
|
||||
int64_t coreId,
|
||||
const StaticValueKnowledge& initialKnowledge,
|
||||
SmallVectorImpl<CommunicationEvent>& events,
|
||||
pim::CappedDiagnosticReporter& diagnostics) {
|
||||
return walkPimCoreBlock(block, initialKnowledge, [&](Operation& op, const StaticValueKnowledge& knowledge) {
|
||||
return walkPimCoreCommunicationBlock(
|
||||
block, plan, initialKnowledge, [&](Operation& op, const StaticValueKnowledge& knowledge) {
|
||||
if (auto sendOp = dyn_cast<pim::PimSendOp>(&op)) {
|
||||
auto targetCoreId = resolveIndexValue(sendOp.getTargetCoreId(), knowledge);
|
||||
if (failed(targetCoreId)) {
|
||||
@@ -382,7 +419,8 @@ static LogicalResult appendCoreCommunicationEvents(Block& block,
|
||||
auto sourceCoreId = resolveIndexValue(receiveOp.getSourceCoreId(), knowledge);
|
||||
if (failed(sourceCoreId)) {
|
||||
diagnostics.report(&op, [](Operation* illegalOp) {
|
||||
illegalOp->emitOpError("cannot statically resolve receive source core for PIM communication deadlock check");
|
||||
illegalOp->emitOpError(
|
||||
"cannot statically resolve receive source core for PIM communication deadlock check");
|
||||
});
|
||||
return failure();
|
||||
}
|
||||
@@ -466,7 +504,8 @@ static void emitCommunicationDeadlockCycle(ModuleOp moduleOp,
|
||||
ArrayRef<int64_t> cycle) {
|
||||
printCommunicationDeadlockReport(coreEvents, programCounters, cycle);
|
||||
|
||||
auto diagnostic = moduleOp.emitError()
|
||||
auto diagnostic =
|
||||
moduleOp.emitError()
|
||||
<< "PIM communication deadlock check found a blocking send/receive cycle while statically simulating the "
|
||||
"expanded per-core communication streams; see the PIM static communication deadlock report above";
|
||||
|
||||
@@ -479,13 +518,14 @@ static void emitCommunicationDeadlockCycle(ModuleOp moduleOp,
|
||||
const CommunicationEvent& event = eventsIt->second[pcIt->second];
|
||||
Diagnostic& note = diagnostic.attachNote(event.op->getLoc());
|
||||
note << formatCommunicationEvent(event);
|
||||
if (!event.materializer.empty())
|
||||
note << " emitted by " << event.materializer;
|
||||
std::string materializer = getNearestStringAttr(event.op, kRaptorMaterializerAttr);
|
||||
if (!materializer.empty())
|
||||
note << " emitted by " << materializer;
|
||||
}
|
||||
}
|
||||
|
||||
static FailureOr<SmallVector<int64_t>> findCommunicationWaitCycle(
|
||||
const DenseMap<int64_t, CommunicationEventVector>& coreEvents,
|
||||
static FailureOr<SmallVector<int64_t>>
|
||||
findCommunicationWaitCycle(const DenseMap<int64_t, CommunicationEventVector>& coreEvents,
|
||||
const DenseMap<int64_t, size_t>& programCounters) {
|
||||
for (const auto& [startCoreId, events] : coreEvents) {
|
||||
auto startPcIt = programCounters.find(startCoreId);
|
||||
@@ -530,14 +570,20 @@ static LogicalResult verifyNoStaticCommunicationDeadlock(ModuleOp moduleOp,
|
||||
for (Operation& op : funcOp.getBody().front().getOperations()) {
|
||||
if (auto coreOp = dyn_cast<pim::PimCoreOp>(&op)) {
|
||||
int64_t coreId = coreOp.getCoreId();
|
||||
if (failed(appendCoreCommunicationEvents(
|
||||
coreOp.getBody().front(), coreId, StaticValueKnowledge {}, coreEvents[coreId], diagnostics)))
|
||||
PimCoreCommunicationPlan plan = buildPimCoreCommunicationPlan(coreOp.getBody().front());
|
||||
if (failed(appendCoreCommunicationEvents(coreOp.getBody().front(),
|
||||
plan,
|
||||
coreId,
|
||||
StaticValueKnowledge {},
|
||||
coreEvents[coreId],
|
||||
diagnostics)))
|
||||
hasFailure = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (auto coreBatchOp = dyn_cast<pim::PimCoreBatchOp>(&op)) {
|
||||
SmallVector<int32_t> coreIds = getBatchCoreIds(coreBatchOp);
|
||||
PimCoreCommunicationPlan plan = buildPimCoreCommunicationPlan(coreBatchOp.getBody().front());
|
||||
size_t laneCount = static_cast<size_t>(coreBatchOp.getLaneCount());
|
||||
for (size_t lane = 0; lane < laneCount; ++lane) {
|
||||
StaticValueKnowledge laneKnowledge;
|
||||
@@ -546,15 +592,18 @@ static LogicalResult verifyNoStaticCommunicationDeadlock(ModuleOp moduleOp,
|
||||
laneKnowledge.aliases[coreBatchOp.getInputArgument(inputIndex)] = coreBatchOp.getInputs()[inputIndex];
|
||||
|
||||
SmallVector<int32_t> laneCoreIds = getLaneChunkCoreIds(coreIds, laneCount, static_cast<unsigned>(lane));
|
||||
for (int32_t coreId : laneCoreIds) {
|
||||
if (failed(appendCoreCommunicationEvents(
|
||||
coreBatchOp.getBody().front(), coreId, laneKnowledge, coreEvents[coreId], diagnostics)))
|
||||
for (int32_t coreId : laneCoreIds)
|
||||
if (failed(appendCoreCommunicationEvents(coreBatchOp.getBody().front(),
|
||||
plan,
|
||||
coreId,
|
||||
laneKnowledge,
|
||||
coreEvents[coreId],
|
||||
diagnostics)))
|
||||
hasFailure = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasFailure)
|
||||
return failure();
|
||||
@@ -607,7 +656,8 @@ static LogicalResult verifyNoStaticCommunicationDeadlock(ModuleOp moduleOp,
|
||||
return failure();
|
||||
}
|
||||
|
||||
auto diagnostic = moduleOp.emitError()
|
||||
auto diagnostic =
|
||||
moduleOp.emitError()
|
||||
<< "PIM communication deadlock check stalled without finding a closed wait cycle; this usually means a "
|
||||
"send/receive peer is missing or ordered after a finished core";
|
||||
for (const auto& [coreId, events] : coreEvents) {
|
||||
@@ -652,6 +702,7 @@ struct VerificationPass : PassWrapper<VerificationPass, OperationPass<ModuleOp>>
|
||||
for (Operation& op : funcOp.getBody().front().getOperations()) {
|
||||
if (auto coreOp = dyn_cast<pim::PimCoreOp>(&op)) {
|
||||
(void) verifyCoreWeights(moduleOp, coreOp, diagnostics);
|
||||
(void) verifyLocalMemoryPlan(coreOp, diagnostics);
|
||||
StaticValueKnowledge knowledge;
|
||||
(void) verifyCoreLikeOperands(coreOp, knowledge, diagnostics);
|
||||
continue;
|
||||
@@ -659,6 +710,7 @@ struct VerificationPass : PassWrapper<VerificationPass, OperationPass<ModuleOp>>
|
||||
|
||||
if (auto coreBatchOp = dyn_cast<pim::PimCoreBatchOp>(&op)) {
|
||||
(void) verifyCoreWeights(moduleOp, coreBatchOp, diagnostics);
|
||||
(void) verifyLocalMemoryPlan(coreBatchOp, diagnostics);
|
||||
llvm::SmallVector<unsigned, 2> lanes;
|
||||
lanes.push_back(0);
|
||||
if (coreBatchOp.getLaneCount() > 1)
|
||||
|
||||
@@ -7,15 +7,26 @@ add_pim_library(SpatialOps
|
||||
SpatialOpsVerify.cpp
|
||||
SpatialOpsCanonicalization.cpp
|
||||
${PIM_SRC_ROOT}/Conversion/ONNXToSpatial/CompileTime.cpp
|
||||
Transforms/MergeComputeNodes/MergeComputeNodesPass.cpp
|
||||
Transforms/MergeComputeNodes/HostOutputFinalization.cpp
|
||||
Transforms/MergeComputeNodes/MaterializeMergeSchedule.cpp
|
||||
Transforms/MergeComputeNodes/ProjectedFragments.cpp
|
||||
Transforms/MergeComputeNodes/SpatialDataflowCsvExporter.cpp
|
||||
Transforms/MergeComputeNodes/Scheduling/ComputeGraph.cpp
|
||||
Transforms/MergeComputeNodes/Scheduling/ComputeInstanceUtils.cpp
|
||||
Transforms/MergeComputeNodes/DeferredCommunicationPlanning.cpp
|
||||
Transforms/MergeComputeNodes/DeferredProjectionAnalysis.cpp
|
||||
Transforms/MergeComputeNodes/DeferredTransferPlanning.cpp
|
||||
Transforms/MergeComputeNodes/DeferredCommunicationScheduling.cpp
|
||||
Transforms/MergeComputeNodes/DeferredBoundaryPlanning.cpp
|
||||
Transforms/MergeComputeNodes/DeferredCommunicationDeadlock.cpp
|
||||
Transforms/MergeComputeNodes/DeferredBoundaryRealization.cpp
|
||||
Transforms/MergeComputeNodes/DeferredResultRealization.cpp
|
||||
Transforms/MergeComputeNodes/DeferredCommunicationRealization.cpp
|
||||
Transforms/MergeComputeNodes/MergeComputeNodesPass.cpp
|
||||
Transforms/MergeComputeNodes/ScheduledComputeMaterialization.cpp
|
||||
Transforms/MergeComputeNodes/ScheduledComputePlanning.cpp
|
||||
Transforms/MergeComputeNodes/ScheduledComputeReport.cpp
|
||||
Transforms/MergeComputeNodes/ScheduledComputeVerification.cpp
|
||||
Transforms/MergeComputeNodes/SpatialDataflowCsvExporter.cpp
|
||||
Transforms/MergeComputeNodes/Scheduling/MergeSchedulingAnalysis.cpp
|
||||
Transforms/MergeComputeNodes/Scheduling/PeftScheduler.cpp
|
||||
Transforms/TrivialGraphComputeMergePass.cpp
|
||||
|
||||
EXCLUDE_FROM_OM_LIBS
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ include "mlir/IR/OpAsmInterface.td"
|
||||
include "mlir/IR/BuiltinTypes.td"
|
||||
include "mlir/IR/AttrTypeBase.td"
|
||||
include "mlir/IR/RegionKindInterface.td"
|
||||
include "mlir/Interfaces/ControlFlowInterfaces.td"
|
||||
include "mlir/Interfaces/ParallelCombiningOpInterface.td"
|
||||
include "mlir/Interfaces/SideEffectInterfaces.td"
|
||||
|
||||
@@ -27,7 +28,7 @@ def SpatTensor :
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
class SpatComputeLikeBase<string mnemonic> : SpatOp<mnemonic,
|
||||
[SingleBlock, AttrSizedOperandSegments,
|
||||
[AttrSizedOperandSegments,
|
||||
DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmBlockArgumentNames"]>]> {
|
||||
let summary = "Compute region with attached constant weights";
|
||||
|
||||
@@ -40,7 +41,7 @@ class SpatComputeLikeBase<string mnemonic> : SpatOp<mnemonic,
|
||||
Variadic<SpatTensor>:$outputs
|
||||
);
|
||||
|
||||
let regions = (region SizedRegion<1>:$body);
|
||||
let regions = (region MinSizedRegion<1>:$body);
|
||||
|
||||
let hasVerifier = 1;
|
||||
let hasFolder = 1;
|
||||
@@ -48,6 +49,7 @@ class SpatComputeLikeBase<string mnemonic> : SpatOp<mnemonic,
|
||||
}
|
||||
|
||||
def SpatGraphCompute : SpatComputeLikeBase<"graph_compute"> {
|
||||
let hasCanonicalizer = 1;
|
||||
let extraClassDeclaration = [{
|
||||
std::optional<::mlir::BlockArgument> getWeightArgument(unsigned idx);
|
||||
std::optional<::mlir::BlockArgument> getInputArgument(unsigned idx);
|
||||
@@ -76,7 +78,7 @@ def SpatScheduledCompute : SpatComputeLikeBase<"scheduled_compute"> {
|
||||
}
|
||||
|
||||
class SpatComputeBatchLikeBase<string mnemonic> : SpatOp<mnemonic,
|
||||
[SingleBlock, AttrSizedOperandSegments,
|
||||
[AttrSizedOperandSegments,
|
||||
DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmBlockArgumentNames"]>]> {
|
||||
let summary = "Tensor-native batch of equivalent compute lanes with shared weights and packed inputs";
|
||||
|
||||
@@ -90,13 +92,14 @@ class SpatComputeBatchLikeBase<string mnemonic> : SpatOp<mnemonic,
|
||||
Variadic<SpatTensor>:$outputs
|
||||
);
|
||||
|
||||
let regions = (region SizedRegion<1>:$body);
|
||||
let regions = (region MinSizedRegion<1>:$body);
|
||||
|
||||
let hasVerifier = 1;
|
||||
let hasCustomAssemblyFormat = 1;
|
||||
}
|
||||
|
||||
def SpatGraphComputeBatch : SpatComputeBatchLikeBase<"graph_compute_batch"> {
|
||||
let hasCanonicalizer = 1;
|
||||
let extraClassDeclaration = [{
|
||||
std::optional<::mlir::BlockArgument> getLaneArgument();
|
||||
std::optional<::mlir::BlockArgument> getWeightArgument(unsigned idx);
|
||||
@@ -113,6 +116,7 @@ def SpatGraphComputeBatch : SpatComputeBatchLikeBase<"graph_compute_batch"> {
|
||||
}
|
||||
|
||||
def SpatScheduledComputeBatch : SpatComputeBatchLikeBase<"scheduled_compute_batch"> {
|
||||
let hasCanonicalizer = 1;
|
||||
let extraClassDeclaration = [{
|
||||
std::optional<::mlir::BlockArgument> getLaneArgument();
|
||||
std::optional<::mlir::BlockArgument> getWeightArgument(unsigned idx);
|
||||
@@ -161,6 +165,58 @@ def SpatYieldOp : SpatOp<"yield", [Terminator]> {
|
||||
let hasCustomAssemblyFormat = 1;
|
||||
}
|
||||
|
||||
def SpatBlockYieldOp : SpatOp<"block_yield", [
|
||||
Terminator,
|
||||
DeclareOpInterfaceMethods<BranchOpInterface, ["getSuccessorForOperands"]>
|
||||
]> {
|
||||
let summary = "Terminate a scheduled structural compute block";
|
||||
|
||||
let arguments = (ins
|
||||
Variadic<AnyType>:$outputs
|
||||
);
|
||||
|
||||
let successors = (successor
|
||||
VariadicSuccessor<AnySuccessor>:$next
|
||||
);
|
||||
|
||||
let hasVerifier = 1;
|
||||
let hasCustomAssemblyFormat = 1;
|
||||
}
|
||||
|
||||
def SpatDeferredCommunicationOp : SpatOp<"deferred_communication", [SingleBlock]> {
|
||||
let summary = "Temporary scheduled payload derivation placeholder";
|
||||
|
||||
let arguments = (ins
|
||||
Variadic<SpatTensor>:$sources,
|
||||
OptionalAttr<I64Attr>:$specialization_count
|
||||
);
|
||||
|
||||
let results = (outs
|
||||
SpatTensor:$output
|
||||
);
|
||||
|
||||
let regions = (region SizedRegion<1>:$body);
|
||||
|
||||
let hasVerifier = 1;
|
||||
let hasCustomAssemblyFormat = 1;
|
||||
}
|
||||
|
||||
def SpatDeferredSourceSelectOp : SpatOp<"deferred_source_select", []> {
|
||||
let summary = "Select a deferred tensor source with a statically analyzable index";
|
||||
|
||||
let arguments = (ins
|
||||
Index:$selector,
|
||||
Variadic<SpatTensor>:$sources
|
||||
);
|
||||
|
||||
let results = (outs
|
||||
SpatTensor:$output
|
||||
);
|
||||
|
||||
let hasVerifier = 1;
|
||||
let hasCustomAssemblyFormat = 1;
|
||||
}
|
||||
|
||||
def SpatExtractRowsOp : SpatOp<"extract_rows", []> {
|
||||
let summary = "Extract every row of a rank-2 tensor as separate rank-2 row tensors";
|
||||
|
||||
@@ -232,6 +288,41 @@ def SpatReluPlanOp : SpatOp<"relu_plan", []> {
|
||||
let hasVerifier = 1;
|
||||
}
|
||||
|
||||
def SpatMaxPool2DPlanOp : SpatOp<"max_pool2d_plan", []> {
|
||||
let summary = "Layout-aware 2D NCHW MaxPool planning op";
|
||||
|
||||
let arguments = (ins
|
||||
SpatTensor:$input,
|
||||
DenseI64ArrayAttr:$kernelShape,
|
||||
DenseI64ArrayAttr:$pads,
|
||||
DenseI64ArrayAttr:$strides,
|
||||
DenseI64ArrayAttr:$dilations,
|
||||
StrAttr:$logicalLayout
|
||||
);
|
||||
|
||||
let results = (outs
|
||||
SpatTensor:$output
|
||||
);
|
||||
|
||||
let hasVerifier = 1;
|
||||
}
|
||||
|
||||
def SpatBiasAddPlanOp : SpatOp<"bias_add_plan", []> {
|
||||
let summary = "Layout-aware Conv-style bias add planning op";
|
||||
|
||||
let arguments = (ins
|
||||
SpatTensor:$input,
|
||||
SpatTensor:$bias,
|
||||
StrAttr:$logicalLayout
|
||||
);
|
||||
|
||||
let results = (outs
|
||||
SpatTensor:$output
|
||||
);
|
||||
|
||||
let hasVerifier = 1;
|
||||
}
|
||||
|
||||
def SpatBlueprintOp : SpatOp<"blueprint", []> {
|
||||
let summary = "Blueprint for assembling logical tensors from published fragments";
|
||||
|
||||
@@ -245,6 +336,7 @@ def SpatBlueprintOp : SpatOp<"blueprint", []> {
|
||||
StrAttr:$indexMap,
|
||||
OptionalAttr<StrAttr>:$mode,
|
||||
OptionalAttr<DenseI64ArrayAttr>:$fragmentOperandIndices,
|
||||
OptionalAttr<DenseI64ArrayAttr>:$fragmentSourceSlots,
|
||||
OptionalAttr<DenseI64ArrayAttr>:$fragmentSourceOffsets,
|
||||
OptionalAttr<DenseI64ArrayAttr>:$fragmentStrides,
|
||||
OptionalAttr<StrAttr>:$conflictPolicy,
|
||||
|
||||
@@ -10,6 +10,18 @@ using namespace mlir;
|
||||
|
||||
namespace onnx_mlir {
|
||||
namespace spatial {
|
||||
|
||||
RankedTensorType getGraphBatchPhysicalResultType(int64_t laneCount, RankedTensorType fragmentType) {
|
||||
SmallVector<int64_t> shape {laneCount};
|
||||
llvm::append_range(shape, fragmentType.getShape());
|
||||
return RankedTensorType::get(shape, fragmentType.getElementType(), fragmentType.getEncoding());
|
||||
}
|
||||
|
||||
FailureOr<RankedTensorType> getGraphBatchFragmentType(RankedTensorType physicalType, int64_t expectedLaneCount) {
|
||||
if (!physicalType || physicalType.getRank() < 1 || physicalType.getDimSize(0) != expectedLaneCount)
|
||||
return failure();
|
||||
return RankedTensorType::get(physicalType.getShape().drop_front(), physicalType.getElementType(), physicalType.getEncoding());
|
||||
}
|
||||
namespace {
|
||||
|
||||
std::optional<BlockArgument> getBlockArgument(Region& body, unsigned argIdx) {
|
||||
@@ -238,6 +250,15 @@ void SpatScheduledCompute::getAsmBlockArgumentNames(Region& region, OpAsmSetValu
|
||||
setComputeAsmBlockArgumentNames(*this, region, setNameFn);
|
||||
}
|
||||
|
||||
SuccessorOperands SpatBlockYieldOp::getSuccessorOperands(unsigned index) {
|
||||
assert(index == 0 && "invalid successor index");
|
||||
return SuccessorOperands(getOutputsMutable());
|
||||
}
|
||||
|
||||
Block* SpatBlockYieldOp::getSuccessorForOperands(ArrayRef<Attribute>) {
|
||||
return getOperation()->getNumSuccessors() == 0 ? nullptr : getOperation()->getSuccessor(0);
|
||||
}
|
||||
|
||||
std::optional<BlockArgument> SpatGraphComputeBatch::getLaneArgument() { return getBlockArgument(getBody(), 0); }
|
||||
std::optional<BlockArgument> SpatGraphComputeBatch::getWeightArgument(unsigned idx) {
|
||||
return getBlockArgument(getBody(), 1 + idx);
|
||||
|
||||
@@ -30,6 +30,10 @@
|
||||
namespace onnx_mlir {
|
||||
namespace spatial {
|
||||
|
||||
mlir::RankedTensorType getGraphBatchPhysicalResultType(int64_t laneCount, mlir::RankedTensorType fragmentType);
|
||||
mlir::FailureOr<mlir::RankedTensorType>
|
||||
getGraphBatchFragmentType(mlir::RankedTensorType physicalType, int64_t expectedLaneCount);
|
||||
|
||||
bool isGraphComputeLike(mlir::Operation* op);
|
||||
bool isGraphBatchComputeLike(mlir::Operation* op);
|
||||
bool isScheduledComputeLike(mlir::Operation* op);
|
||||
|
||||
@@ -160,7 +160,7 @@ void printComputeLikeOp(ComputeOpTy op, OpAsmPrinter& printer) {
|
||||
printer << " -> ";
|
||||
printCompressedTypeSequence(printer, op.getResultTypes());
|
||||
printer << " ";
|
||||
printer.printRegion(op.getBody(), /*printEntryBlockArgs=*/false);
|
||||
printer.printRegion(op.getBody(), /*printEntryBlockArgs=*/!op.getBody().hasOneBlock());
|
||||
}
|
||||
|
||||
template <typename ComputeOpTy>
|
||||
@@ -290,7 +290,7 @@ void printComputeBatchLikeOp(ComputeBatchOpTy op, OpAsmPrinter& printer) {
|
||||
printer << " -> ";
|
||||
printCompressedTypeSequence(printer, op.getResultTypes());
|
||||
printer << " ";
|
||||
printer.printRegion(op.getBody(), /*printEntryBlockArgs=*/false);
|
||||
printer.printRegion(op.getBody(), /*printEntryBlockArgs=*/!op.getBody().hasOneBlock());
|
||||
}
|
||||
|
||||
template <typename ComputeBatchOpTy>
|
||||
@@ -407,6 +407,130 @@ ParseResult SpatYieldOp::parse(OpAsmParser& parser, OperationState& result) {
|
||||
return parser.resolveOperands(outputs, outputTypes, parser.getCurrentLocation(), result.operands);
|
||||
}
|
||||
|
||||
void SpatBlockYieldOp::print(OpAsmPrinter& printer) {
|
||||
printer << " ";
|
||||
printCompressedValueSequence(printer, getOutputs());
|
||||
if (getOperation()->getNumSuccessors() != 0) {
|
||||
printer << " next ";
|
||||
printer.printSuccessor(getOperation()->getSuccessor(0));
|
||||
}
|
||||
printer.printOptionalAttrDict((*this)->getAttrs());
|
||||
printer << " : ";
|
||||
printCompressedTypeSequence(printer, getOutputs().getTypes());
|
||||
}
|
||||
|
||||
ParseResult SpatBlockYieldOp::parse(OpAsmParser& parser, OperationState& result) {
|
||||
SmallVector<OpAsmParser::UnresolvedOperand> outputs;
|
||||
SmallVector<Type> outputTypes;
|
||||
Block* successor = nullptr;
|
||||
|
||||
OpAsmParser::UnresolvedOperand firstOutput;
|
||||
OptionalParseResult firstOutputResult = parser.parseOptionalOperand(firstOutput);
|
||||
if (firstOutputResult.has_value()) {
|
||||
if (failed(*firstOutputResult))
|
||||
return failure();
|
||||
if (parseCompressedOperandEntryWithFirst(parser, firstOutput, outputs))
|
||||
return failure();
|
||||
while (succeeded(parser.parseOptionalComma()))
|
||||
if (parseOneCompressedOperandEntry(parser, outputs))
|
||||
return failure();
|
||||
}
|
||||
|
||||
if (succeeded(parser.parseOptionalKeyword("next")) && parser.parseSuccessor(successor))
|
||||
return failure();
|
||||
|
||||
if (parser.parseOptionalAttrDict(result.attributes) || parser.parseColon()
|
||||
|| parseCompressedTypeSequence(parser, outputTypes, /*allowEmpty=*/true))
|
||||
return failure();
|
||||
|
||||
if (outputs.size() != outputTypes.size())
|
||||
return parser.emitError(parser.getCurrentLocation(), "number of outputs and output types must match");
|
||||
if (parser.resolveOperands(outputs, outputTypes, parser.getCurrentLocation(), result.operands))
|
||||
return failure();
|
||||
if (successor)
|
||||
result.addSuccessors(successor);
|
||||
return success();
|
||||
}
|
||||
|
||||
void SpatDeferredCommunicationOp::print(OpAsmPrinter& printer) {
|
||||
printer << " ";
|
||||
printCompressedValueSequence(printer, getSources());
|
||||
printer.printOptionalAttrDict((*this)->getAttrs());
|
||||
printer << " : ";
|
||||
printCompressedTypeList(
|
||||
printer, getSources().getTypes(), ListDelimiter::Paren);
|
||||
printer << " -> ";
|
||||
printCompressedTypeSequence(printer, getOperation()->getResultTypes());
|
||||
printer << " ";
|
||||
printer.printRegion(getBody(), /*printEntryBlockArgs=*/false);
|
||||
}
|
||||
|
||||
ParseResult SpatDeferredCommunicationOp::parse(OpAsmParser& parser, OperationState& result) {
|
||||
SmallVector<OpAsmParser::UnresolvedOperand> sources;
|
||||
SmallVector<Type> sourceTypes, outputTypes;
|
||||
|
||||
if (parseCompressedOperandSequence(parser, sources) || parser.parseOptionalAttrDict(result.attributes)
|
||||
|| parser.parseColon()
|
||||
|| parseCompressedRepeatedList(
|
||||
parser, ListDelimiter::Paren, sourceTypes,
|
||||
[&](Type& type) { return parser.parseType(type); })
|
||||
|| parser.parseArrow()
|
||||
|| parseCompressedTypeSequence(
|
||||
parser, outputTypes, /*allowEmpty=*/false))
|
||||
return failure();
|
||||
|
||||
if (sources.size() != sourceTypes.size())
|
||||
return parser.emitError(parser.getCurrentLocation(), "number of sources and source types must match");
|
||||
|
||||
if (parser.resolveOperands(sources, sourceTypes, parser.getCurrentLocation(), result.operands))
|
||||
return failure();
|
||||
result.addTypes(outputTypes);
|
||||
|
||||
Region* body = result.addRegion();
|
||||
SmallVector<OpAsmParser::Argument> bodyArgs;
|
||||
for (Type type : sourceTypes) {
|
||||
OpAsmParser::Argument argument;
|
||||
argument.type = type;
|
||||
bodyArgs.push_back(argument);
|
||||
}
|
||||
if (auto count = dyn_cast_or_null<IntegerAttr>(
|
||||
result.attributes.get("specialization_count"));
|
||||
count && count.getInt() > 1) {
|
||||
OpAsmParser::Argument argument;
|
||||
argument.type = parser.getBuilder().getIndexType();
|
||||
bodyArgs.push_back(argument);
|
||||
}
|
||||
return parser.parseRegion(*body, bodyArgs);
|
||||
}
|
||||
|
||||
void SpatDeferredSourceSelectOp::print(OpAsmPrinter& printer) {
|
||||
printer << " " << getSelector() << " of ";
|
||||
printCompressedValueSequence(printer, getSources());
|
||||
printer.printOptionalAttrDict((*this)->getAttrs());
|
||||
printer << " : " << getOutput().getType();
|
||||
}
|
||||
|
||||
ParseResult SpatDeferredSourceSelectOp::parse(
|
||||
OpAsmParser& parser, OperationState& result) {
|
||||
OpAsmParser::UnresolvedOperand selector;
|
||||
SmallVector<OpAsmParser::UnresolvedOperand> sources;
|
||||
Type outputType;
|
||||
if (parser.parseOperand(selector) || parser.parseKeyword("of")
|
||||
|| parseCompressedOperandSequence(parser, sources)
|
||||
|| parser.parseOptionalAttrDict(result.attributes)
|
||||
|| parser.parseColon() || parser.parseType(outputType))
|
||||
return failure();
|
||||
if (parser.resolveOperand(selector, parser.getBuilder().getIndexType(),
|
||||
result.operands))
|
||||
return failure();
|
||||
SmallVector<Type> sourceTypes(sources.size(), outputType);
|
||||
if (parser.resolveOperands(sources, sourceTypes,
|
||||
parser.getCurrentLocation(), result.operands))
|
||||
return failure();
|
||||
result.addTypes(outputType);
|
||||
return success();
|
||||
}
|
||||
|
||||
void SpatExtractRowsOp::print(OpAsmPrinter& printer) {
|
||||
printer << " ";
|
||||
printer.printOperand(getInput());
|
||||
@@ -493,6 +617,10 @@ void SpatBlueprintOp::print(OpAsmPrinter& printer) {
|
||||
printer << " operandIndices ";
|
||||
printCompressedIntegerList(printer, *operandIndices);
|
||||
}
|
||||
if (std::optional<ArrayRef<int64_t>> sourceSlots = getFragmentSourceSlots()) {
|
||||
printer << " sourceSlots ";
|
||||
printCompressedIntegerList(printer, *sourceSlots);
|
||||
}
|
||||
if (std::optional<ArrayRef<int64_t>> sourceOffsets = getFragmentSourceOffsets()) {
|
||||
printer << " sourceOffsets ";
|
||||
printCompressedIntegerList(printer, *sourceOffsets);
|
||||
@@ -514,6 +642,7 @@ void SpatBlueprintOp::print(OpAsmPrinter& printer) {
|
||||
getIndexMapAttrName().getValue(),
|
||||
getModeAttrName().getValue(),
|
||||
getFragmentOperandIndicesAttrName().getValue(),
|
||||
getFragmentSourceSlotsAttrName().getValue(),
|
||||
getFragmentSourceOffsetsAttrName().getValue(),
|
||||
getFragmentStridesAttrName().getValue(),
|
||||
getConflictPolicyAttrName().getValue(),
|
||||
@@ -537,6 +666,7 @@ ParseResult SpatBlueprintOp::parse(OpAsmParser& parser, OperationState& result)
|
||||
SmallVector<int64_t> fragmentOffsets;
|
||||
SmallVector<int64_t> fragmentSizes;
|
||||
SmallVector<int64_t> fragmentOperandIndices;
|
||||
SmallVector<int64_t> fragmentSourceSlots;
|
||||
SmallVector<int64_t> fragmentSourceOffsets;
|
||||
SmallVector<int64_t> fragmentStrides;
|
||||
|
||||
@@ -554,6 +684,9 @@ ParseResult SpatBlueprintOp::parse(OpAsmParser& parser, OperationState& result)
|
||||
if (succeeded(parser.parseOptionalKeyword("operandIndices"))
|
||||
&& parseCompressedIntegerList(parser, fragmentOperandIndices))
|
||||
return failure();
|
||||
if (succeeded(parser.parseOptionalKeyword("sourceSlots"))
|
||||
&& parseCompressedIntegerList(parser, fragmentSourceSlots))
|
||||
return failure();
|
||||
if (succeeded(parser.parseOptionalKeyword("sourceOffsets"))
|
||||
&& parseCompressedIntegerList(parser, fragmentSourceOffsets))
|
||||
return failure();
|
||||
@@ -584,6 +717,8 @@ ParseResult SpatBlueprintOp::parse(OpAsmParser& parser, OperationState& result)
|
||||
result.addAttribute("mode", mode);
|
||||
if (!fragmentOperandIndices.empty())
|
||||
result.addAttribute("fragmentOperandIndices", builder.getDenseI64ArrayAttr(fragmentOperandIndices));
|
||||
if (!fragmentSourceSlots.empty())
|
||||
result.addAttribute("fragmentSourceSlots", builder.getDenseI64ArrayAttr(fragmentSourceSlots));
|
||||
if (!fragmentSourceOffsets.empty())
|
||||
result.addAttribute("fragmentSourceOffsets", builder.getDenseI64ArrayAttr(fragmentSourceOffsets));
|
||||
if (!fragmentStrides.empty())
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||
#include "mlir/IR/Block.h"
|
||||
#include "mlir/IR/IRMapping.h"
|
||||
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/Support/LogicalResult.h"
|
||||
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
|
||||
using namespace mlir;
|
||||
@@ -36,9 +42,208 @@ LogicalResult SpatGraphCompute::fold(FoldAdaptor adaptor, ::llvm::SmallVectorImp
|
||||
return foldComputeLike(*this, results);
|
||||
}
|
||||
|
||||
struct RemoveUnusedGraphComputeInputsPattern : OpRewritePattern<SpatGraphCompute> {
|
||||
using OpRewritePattern::OpRewritePattern;
|
||||
|
||||
LogicalResult matchAndRewrite(SpatGraphCompute compute, PatternRewriter& rewriter) const override {
|
||||
SmallVector<unsigned> unusedInputs;
|
||||
for (unsigned index = 0; index < compute.getInputs().size(); ++index) {
|
||||
auto argument = compute.getInputArgument(index);
|
||||
if (argument && argument->use_empty())
|
||||
unusedInputs.push_back(index);
|
||||
}
|
||||
if (unusedInputs.empty())
|
||||
return failure();
|
||||
|
||||
rewriter.modifyOpInPlace(compute, [&] {
|
||||
for (unsigned index : llvm::reverse(unusedInputs)) {
|
||||
compute.getBody().front().eraseArgument(compute.getWeights().size() + index);
|
||||
compute.getInputsMutable().erase(index);
|
||||
}
|
||||
});
|
||||
return success();
|
||||
}
|
||||
};
|
||||
|
||||
void SpatGraphCompute::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) {
|
||||
results.add<RemoveUnusedGraphComputeInputsPattern>(context);
|
||||
}
|
||||
|
||||
LogicalResult SpatScheduledCompute::fold(FoldAdaptor adaptor, ::llvm::SmallVectorImpl<::mlir::OpFoldResult>& results) {
|
||||
return foldComputeLike(*this, results);
|
||||
}
|
||||
|
||||
template <typename ScalarComputeOpTy>
|
||||
static ScalarComputeOpTy createEmptyScalarCompute(PatternRewriter& rewriter,
|
||||
Location loc,
|
||||
TypeRange resultTypes,
|
||||
ValueRange weights,
|
||||
ValueRange inputs) {
|
||||
auto computeOp = ScalarComputeOpTy::create(rewriter, loc, resultTypes, weights, inputs);
|
||||
SmallVector<Type> blockArgTypes;
|
||||
SmallVector<Location> blockArgLocs;
|
||||
blockArgTypes.reserve(weights.size() + inputs.size());
|
||||
blockArgLocs.reserve(weights.size() + inputs.size());
|
||||
for (Value weight : weights) {
|
||||
blockArgTypes.push_back(weight.getType());
|
||||
blockArgLocs.push_back(weight.getLoc());
|
||||
}
|
||||
for (Value input : inputs) {
|
||||
blockArgTypes.push_back(input.getType());
|
||||
blockArgLocs.push_back(input.getLoc());
|
||||
}
|
||||
rewriter.createBlock(&computeOp.getBody(), computeOp.getBody().end(), blockArgTypes, blockArgLocs);
|
||||
rewriter.setInsertionPointToStart(&computeOp.getBody().front());
|
||||
return computeOp;
|
||||
}
|
||||
|
||||
static SmallVector<OpFoldResult> remapMixedOffsets(ArrayRef<OpFoldResult> mixedOffsets, IRMapping& mapper) {
|
||||
SmallVector<OpFoldResult> remapped;
|
||||
remapped.reserve(mixedOffsets.size());
|
||||
for (OpFoldResult ofr : mixedOffsets) {
|
||||
if (auto value = dyn_cast<Value>(ofr))
|
||||
remapped.push_back(cast<Value>(mapper.lookupOrDefault(value)));
|
||||
else
|
||||
remapped.push_back(cast<Attribute>(ofr));
|
||||
}
|
||||
return remapped;
|
||||
}
|
||||
|
||||
static SmallVector<Value> createEmptyResults(PatternRewriter& rewriter, Location loc, TypeRange resultTypes) {
|
||||
SmallVector<Value> resultValues;
|
||||
resultValues.reserve(resultTypes.size());
|
||||
for (Type resultType : resultTypes) {
|
||||
auto tensorType = dyn_cast<RankedTensorType>(resultType);
|
||||
if (!tensorType || !tensorType.hasStaticShape())
|
||||
return {};
|
||||
resultValues.push_back(tensor::EmptyOp::create(rewriter, loc, tensorType.getShape(), tensorType.getElementType()));
|
||||
}
|
||||
return resultValues;
|
||||
}
|
||||
|
||||
template <typename ScalarComputeOpTy, typename ComputeBatchOpTy>
|
||||
static void copyCanonicalizedBatchAttrs(ScalarComputeOpTy compute, ComputeBatchOpTy batch, PatternRewriter& rewriter) {
|
||||
for (NamedAttribute attr : batch->getAttrs()) {
|
||||
if (attr.getName() == batch.getOperandSegmentSizesAttrName() || attr.getName() == batch.getLaneCountAttrName()
|
||||
|| attr.getName() == onnx_mlir::kCoreIdsAttrName)
|
||||
continue;
|
||||
compute->setAttr(attr.getName(), attr.getValue());
|
||||
}
|
||||
if constexpr (std::is_same_v<ComputeBatchOpTy, SpatScheduledComputeBatch>) {
|
||||
if (auto coreIds = batch->template getAttrOfType<DenseI32ArrayAttr>(onnx_mlir::kCoreIdsAttrName)) {
|
||||
assert(coreIds.size() == 1 && "single-lane scheduled compute_batch canonicalization expects exactly one core id");
|
||||
compute->setAttr(onnx_mlir::kCoreIdAttrName, rewriter.getI32IntegerAttr(coreIds.asArrayRef().front()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename ComputeBatchOpTy, typename ScalarComputeOpTy>
|
||||
struct CanonicalizeSingleLaneComputeBatchPattern : OpRewritePattern<ComputeBatchOpTy> {
|
||||
using OpRewritePattern<ComputeBatchOpTy>::OpRewritePattern;
|
||||
|
||||
LogicalResult matchAndRewrite(ComputeBatchOpTy compute, PatternRewriter& rewriter) const override {
|
||||
if (compute.getLaneCount() != 1)
|
||||
return rewriter.notifyMatchFailure(compute, "lane count is not 1");
|
||||
|
||||
Block& oldBlock = compute.getBody().front();
|
||||
auto oldLaneArg = compute.getLaneArgument();
|
||||
if (!oldLaneArg)
|
||||
return rewriter.notifyMatchFailure(compute, "missing compute_batch lane block argument");
|
||||
|
||||
rewriter.setInsertionPointAfter(compute);
|
||||
auto newCompute =
|
||||
createEmptyScalarCompute<ScalarComputeOpTy>(rewriter, compute.getLoc(), compute.getResultTypes(), compute.getWeights(), compute.getInputs());
|
||||
copyCanonicalizedBatchAttrs(newCompute, compute, rewriter);
|
||||
auto* newBlock = &newCompute.getBody().front();
|
||||
rewriter.setInsertionPointToStart(newBlock);
|
||||
|
||||
IRMapping mapper;
|
||||
Value zero = getOrCreateIndexConstant(rewriter, compute.getOperation(), 0);
|
||||
mapper.map(*oldLaneArg, zero);
|
||||
for (auto [index, weight] : llvm::enumerate(compute.getWeights())) {
|
||||
auto oldArg = compute.getWeightArgument(index);
|
||||
auto newArg = newCompute.getWeightArgument(index);
|
||||
if (!oldArg || !newArg)
|
||||
return rewriter.notifyMatchFailure(compute, "missing rewritten compute weight block argument");
|
||||
mapper.map(*oldArg, *newArg);
|
||||
}
|
||||
for (auto [index, input] : llvm::enumerate(compute.getInputs())) {
|
||||
auto oldArg = compute.getInputArgument(index);
|
||||
auto newArg = newCompute.getInputArgument(index);
|
||||
if (!oldArg || !newArg)
|
||||
return rewriter.notifyMatchFailure(compute, "missing rewritten compute input block argument");
|
||||
mapper.map(*oldArg, *newArg);
|
||||
}
|
||||
|
||||
SmallVector<Value> resultValues = createEmptyResults(rewriter, compute.getLoc(), compute.getResultTypes());
|
||||
if (resultValues.size() != compute.getNumResults())
|
||||
return rewriter.notifyMatchFailure(compute, "single-lane compute_batch canonicalization requires static ranked results");
|
||||
for (auto [index, resultValue] : llvm::enumerate(resultValues)) {
|
||||
auto oldOutputArg = compute.getOutputArgument(index);
|
||||
if (!oldOutputArg)
|
||||
return rewriter.notifyMatchFailure(compute, "missing compute_batch output block argument");
|
||||
mapper.map(*oldOutputArg, resultValue);
|
||||
}
|
||||
|
||||
auto oldInParallel = dyn_cast<SpatInParallelOp>(oldBlock.getTerminator());
|
||||
auto oldYield = dyn_cast<SpatYieldOp>(oldBlock.getTerminator());
|
||||
for (Operation& op : oldBlock.without_terminator())
|
||||
rewriter.clone(op, mapper);
|
||||
|
||||
if (oldYield) {
|
||||
SpatYieldOp::create(rewriter, oldYield.getLoc(), ValueRange {});
|
||||
rewriter.replaceOp(compute, newCompute.getResults());
|
||||
return success();
|
||||
}
|
||||
if (!oldInParallel)
|
||||
return rewriter.notifyMatchFailure(compute, "expected spat.in_parallel or empty spat.yield terminator");
|
||||
|
||||
DenseMap<BlockArgument, size_t> outputIndexByArg;
|
||||
for (size_t index = 0; index < compute.getNumResults(); ++index) {
|
||||
auto oldOutputArg = compute.getOutputArgument(index);
|
||||
if (!oldOutputArg)
|
||||
return rewriter.notifyMatchFailure(compute, "missing compute_batch output block argument");
|
||||
outputIndexByArg[*oldOutputArg] = index;
|
||||
}
|
||||
|
||||
for (Operation& op : oldInParallel.getRegion().front()) {
|
||||
auto insertSlice = dyn_cast<tensor::ParallelInsertSliceOp>(&op);
|
||||
if (!insertSlice)
|
||||
return rewriter.notifyMatchFailure(compute, "expected only tensor.parallel_insert_slice in spat.in_parallel");
|
||||
auto oldDest = dyn_cast<BlockArgument>(insertSlice.getDest());
|
||||
if (!oldDest)
|
||||
return rewriter.notifyMatchFailure(compute, "expected tensor.parallel_insert_slice destination to be a block argument");
|
||||
auto resultIndexIt = outputIndexByArg.find(oldDest);
|
||||
if (resultIndexIt == outputIndexByArg.end())
|
||||
return rewriter.notifyMatchFailure(compute, "unexpected tensor.parallel_insert_slice destination");
|
||||
size_t resultIndex = resultIndexIt->second;
|
||||
Value remappedSource = mapper.lookupOrDefault(insertSlice.getSource());
|
||||
auto remappedOffsets = remapMixedOffsets(insertSlice.getMixedOffsets(), mapper);
|
||||
auto remappedSizes = remapMixedOffsets(insertSlice.getMixedSizes(), mapper);
|
||||
auto remappedStrides = remapMixedOffsets(insertSlice.getMixedStrides(), mapper);
|
||||
resultValues[resultIndex] = tensor::InsertSliceOp::create(rewriter,
|
||||
insertSlice.getLoc(),
|
||||
remappedSource,
|
||||
resultValues[resultIndex],
|
||||
remappedOffsets,
|
||||
remappedSizes,
|
||||
remappedStrides)
|
||||
.getResult();
|
||||
}
|
||||
|
||||
SpatYieldOp::create(rewriter, oldInParallel.getLoc(), resultValues);
|
||||
rewriter.replaceOp(compute, newCompute.getResults());
|
||||
return success();
|
||||
}
|
||||
};
|
||||
|
||||
void SpatGraphComputeBatch::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) {
|
||||
results.add<CanonicalizeSingleLaneComputeBatchPattern<SpatGraphComputeBatch, SpatGraphCompute>>(context);
|
||||
}
|
||||
|
||||
void SpatScheduledComputeBatch::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) {
|
||||
results.add<CanonicalizeSingleLaneComputeBatchPattern<SpatScheduledComputeBatch, SpatScheduledCompute>>(context);
|
||||
}
|
||||
|
||||
} // namespace spatial
|
||||
} // namespace onnx_mlir
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "mlir/Dialect/Affine/IR/AffineOps.h"
|
||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||
#include "mlir/IR/AffineExpr.h"
|
||||
#include "mlir/IR/Block.h"
|
||||
@@ -59,6 +60,21 @@ static LogicalResult verifyStaticWeights(ComputeOpTy computeOp, StringRef kind)
|
||||
return success();
|
||||
}
|
||||
|
||||
static bool isStaticScfForInductionVar(Value value) {
|
||||
auto blockArg = dyn_cast<BlockArgument>(value);
|
||||
if (!blockArg)
|
||||
return false;
|
||||
|
||||
auto loop = dyn_cast_or_null<scf::ForOp>(blockArg.getOwner()->getParentOp());
|
||||
if (!loop || loop.getInductionVar() != value)
|
||||
return false;
|
||||
|
||||
std::optional<int64_t> lowerBound = matchConstantIndexValue(loop.getLowerBound());
|
||||
std::optional<int64_t> upperBound = matchConstantIndexValue(loop.getUpperBound());
|
||||
std::optional<int64_t> step = matchConstantIndexValue(loop.getStep());
|
||||
return lowerBound && upperBound && step && *step > 0 && *upperBound >= *lowerBound;
|
||||
}
|
||||
|
||||
static bool isStaticIndexExpr(Value value) {
|
||||
if (matchConstantIndexValue(value))
|
||||
return true;
|
||||
@@ -80,7 +96,7 @@ static bool isStaticIndexExpr(Value value) {
|
||||
}
|
||||
|
||||
static bool isSupportedLaneOffsetExpr(Value value, BlockArgument laneArg) {
|
||||
if (value == laneArg || isStaticIndexExpr(value))
|
||||
if (value == laneArg || isStaticIndexExpr(value) || isStaticScfForInductionVar(value))
|
||||
return true;
|
||||
|
||||
auto affineApply = value.getDefiningOp<affine::AffineApplyOp>();
|
||||
@@ -176,12 +192,18 @@ static bool isConstantExternalValue(Value value) {
|
||||
return definingOp && definingOp->hasTrait<OpTrait::ConstantLike>();
|
||||
}
|
||||
|
||||
static bool isRecordedDeferredCommunicationSource(Operation* op, Value value) {
|
||||
auto transfer = dyn_cast<SpatDeferredCommunicationOp>(op);
|
||||
return transfer && llvm::is_contained(transfer.getSources(), value);
|
||||
}
|
||||
|
||||
static LogicalResult verifyOnlyConstantExternalValues(Operation* ownerOp, Region& region, StringRef kind) {
|
||||
bool hasFailure = false;
|
||||
region.walk([&](Operation* op) {
|
||||
for (OpOperand& operand : op->getOpOperands()) {
|
||||
Value value = operand.get();
|
||||
if (isDefinedInsideRegion(value, region) || isConstantExternalValue(value))
|
||||
if (isDefinedInsideRegion(value, region) || isConstantExternalValue(value)
|
||||
|| isRecordedDeferredCommunicationSource(op, value))
|
||||
continue;
|
||||
|
||||
InFlightDiagnostic diagnostic =
|
||||
@@ -204,7 +226,7 @@ static LogicalResult verifyOnlyConstantExternalValues(Operation* ownerOp, Region
|
||||
}
|
||||
|
||||
template <typename ComputeBatchOpTy>
|
||||
static LogicalResult verifyBatchBody(ComputeBatchOpTy batchOp, Block& block) {
|
||||
static LogicalResult verifyBatchBody(ComputeBatchOpTy batchOp, Block& block, bool verifyLaneSliceOffsets = true) {
|
||||
if (batchOp.getNumResults() == 0) {
|
||||
auto yieldOp = dyn_cast_or_null<SpatYieldOp>(block.getTerminator());
|
||||
if (!yieldOp)
|
||||
@@ -219,6 +241,7 @@ static LogicalResult verifyBatchBody(ComputeBatchOpTy batchOp, Block& block) {
|
||||
auto laneArg = batchOp.getLaneArgument();
|
||||
if (!laneArg)
|
||||
return batchOp.emitError("compute_batch body must have a lane block argument");
|
||||
if (verifyLaneSliceOffsets)
|
||||
for (auto& bodyOp : block) {
|
||||
if (auto extractSlice = dyn_cast<tensor::ExtractSliceOp>(&bodyOp))
|
||||
if (failed(verifyStaticUnitStrideExtractSliceOp(extractSlice, *laneArg, "tensor.extract_slice")))
|
||||
@@ -436,6 +459,59 @@ LogicalResult SpatReluPlanOp::verify() {
|
||||
return success();
|
||||
}
|
||||
|
||||
LogicalResult SpatMaxPool2DPlanOp::verify() {
|
||||
if (failed(verifyPlanTensorTypes(getOperation(), getInput(), getOutput(), "spat.max_pool2d_plan")))
|
||||
return failure();
|
||||
auto inputType = dyn_cast<RankedTensorType>(getInput().getType());
|
||||
auto outputType = dyn_cast<RankedTensorType>(getOutput().getType());
|
||||
if (!inputType.hasStaticShape() || !outputType.hasStaticShape() || inputType.getRank() != 4
|
||||
|| outputType.getRank() != 4)
|
||||
return emitError("requires static rank-4 input and output tensors");
|
||||
if (getLogicalLayout() != "nchw")
|
||||
return emitError("requires logical layout \"nchw\"");
|
||||
if (getKernelShape().size() != 2 || getStrides().size() != 2 || getDilations().size() != 2)
|
||||
return emitError("requires two kernel, stride, and dilation values");
|
||||
if (getPads().size() != 4)
|
||||
return emitError("requires four pad values");
|
||||
if (inputType.getDimSize(0) != outputType.getDimSize(0)
|
||||
|| inputType.getDimSize(1) != outputType.getDimSize(1))
|
||||
return emitError("requires matching input/output batch and channel dimensions");
|
||||
return success();
|
||||
}
|
||||
|
||||
LogicalResult SpatBiasAddPlanOp::verify() {
|
||||
if (failed(verifyPlanTensorTypes(getOperation(), getInput(), getOutput(), "spat.bias_add_plan")))
|
||||
return failure();
|
||||
if (!isKnownLogicalLayout(getLogicalLayout()))
|
||||
return emitError("requires a known logical layout");
|
||||
|
||||
auto inputType = dyn_cast<RankedTensorType>(getInput().getType());
|
||||
auto biasType = dyn_cast<RankedTensorType>(getBias().getType());
|
||||
auto outputType = dyn_cast<RankedTensorType>(getOutput().getType());
|
||||
if (!inputType || !biasType || !outputType)
|
||||
return emitError("requires ranked tensor input, bias, and output");
|
||||
if (!inputType.hasStaticShape() || !biasType.hasStaticShape() || !outputType.hasStaticShape())
|
||||
return emitError("requires static tensor input, bias, and output");
|
||||
if (inputType != outputType)
|
||||
return emitError("requires matching input and output tensor types");
|
||||
if (outputType.getRank() != 4)
|
||||
return emitError("requires rank-4 input/output tensors");
|
||||
if (getLogicalLayout() != "nchw")
|
||||
return emitError("requires logical layout \"nchw\"");
|
||||
if (biasType.getElementType() != outputType.getElementType())
|
||||
return emitError("requires bias element type to match the output element type");
|
||||
|
||||
ArrayRef<int64_t> biasShape = biasType.getShape();
|
||||
const int64_t channels = outputType.getDimSize(1);
|
||||
const bool supported = biasShape.empty() || (biasShape.size() == 1 && biasShape[0] == channels)
|
||||
|| (biasShape.size() == 2 && biasShape[0] == 1 && biasShape[1] == channels)
|
||||
|| (biasShape.size() == 4 && biasShape[0] == 1 && biasShape[1] == channels
|
||||
&& biasShape[2] == 1 && biasShape[3] == 1);
|
||||
if (!supported)
|
||||
return emitError("requires scalar or per-channel bias broadcastable over NCHW");
|
||||
return success();
|
||||
}
|
||||
|
||||
LogicalResult SpatBlueprintOp::verify() {
|
||||
auto modeAttr = getModeAttr();
|
||||
bool isFragmentAssembly = modeAttr && modeAttr.getValue() == "fragment_assembly";
|
||||
@@ -491,20 +567,26 @@ LogicalResult SpatBlueprintOp::verify() {
|
||||
|
||||
auto stridesAttr = getFragmentStridesAttr();
|
||||
auto operandIndicesAttr = getFragmentOperandIndicesAttr();
|
||||
auto sourceSlotsAttr = getFragmentSourceSlotsAttr();
|
||||
auto sourceOffsetsAttr = getFragmentSourceOffsetsAttr();
|
||||
if (!operandIndicesAttr)
|
||||
return emitError("fragment assembly blueprint requires fragment operand indices");
|
||||
if (!sourceSlotsAttr)
|
||||
return emitError("fragment assembly blueprint requires physical fragment source slots");
|
||||
if (!sourceOffsetsAttr)
|
||||
return emitError("fragment assembly blueprint requires fragment source offsets");
|
||||
if (!stridesAttr)
|
||||
return emitError("fragment assembly blueprint requires fragment strides");
|
||||
ArrayRef<int64_t> operandIndices = operandIndicesAttr.asArrayRef();
|
||||
ArrayRef<int64_t> sourceSlots = sourceSlotsAttr.asArrayRef();
|
||||
ArrayRef<int64_t> sourceOffsets = sourceOffsetsAttr.asArrayRef();
|
||||
ArrayRef<int64_t> strides = stridesAttr.asArrayRef();
|
||||
if (strides.size() != offsets.size())
|
||||
return emitError("fragment stride and offset arrays must have the same length");
|
||||
if (sourceOffsets.size() != operandIndices.size())
|
||||
return emitError("fragment source offset count must match fragment operand index count");
|
||||
if (sourceSlots.size() != operandIndices.size())
|
||||
return emitError("fragment source slot count must match fragment operand index count");
|
||||
if (!getConflictPolicyAttr() || !getCoveragePolicyAttr())
|
||||
return emitError("fragment assembly blueprint requires conflict and coverage policies");
|
||||
if (getConflictPolicy() != "disjoint")
|
||||
@@ -539,14 +621,19 @@ LogicalResult SpatBlueprintOp::verify() {
|
||||
int64_t operandIndex = operandIndices[fragmentIndex];
|
||||
if (operandIndex < 0 || operandIndex >= operandCount)
|
||||
return emitError("fragment assembly operand index is out of range");
|
||||
if (sourceSlots[fragmentIndex] < 0)
|
||||
return emitError("fragment assembly physical source slot must be nonnegative");
|
||||
if (sourceOffsets[fragmentIndex] < 0)
|
||||
return emitError("fragment assembly source offsets must be nonnegative");
|
||||
|
||||
auto operandType = dyn_cast<RankedTensorType>(operands[operandIndex].getType());
|
||||
if (!operandType || !operandType.hasStaticShape())
|
||||
return emitError("fragment assembly blueprint requires static ranked tensor operands");
|
||||
if (operandType.getRank() != rank)
|
||||
return emitError("fragment assembly blueprint requires operand/result rank match");
|
||||
if (operandType.getRank() != rank + 1)
|
||||
return emitError("fragment assembly physical operand must have one leading source-slot dimension");
|
||||
if (sourceSlots[fragmentIndex] >= operandType.getDimSize(0))
|
||||
return emitError("fragment assembly physical source slot is out of range");
|
||||
auto fragmentType = RankedTensorType::get(operandType.getShape().drop_front(), operandType.getElementType(), operandType.getEncoding());
|
||||
|
||||
SmallVector<int64_t, 4> fragmentOffsets;
|
||||
SmallVector<int64_t, 4> fragmentSizes;
|
||||
@@ -562,12 +649,12 @@ LogicalResult SpatBlueprintOp::verify() {
|
||||
int64_t fragmentElements = 1;
|
||||
for (int64_t dim = 0; dim < rank; ++dim)
|
||||
fragmentElements *= fragmentSizes[dim];
|
||||
if (sourceOffsets[fragmentIndex] + fragmentElements > operandType.getNumElements())
|
||||
return emitError("fragment assembly source offset exceeds the operand bounds");
|
||||
if (sourceOffsets[fragmentIndex] + fragmentElements > fragmentType.getNumElements())
|
||||
return emitError("fragment assembly source offset exceeds the selected physical fragment bounds");
|
||||
SmallVector<int64_t, 4> sourceSliceOffsets =
|
||||
expandFlatElementIndex(sourceOffsets[fragmentIndex], operandType.getShape());
|
||||
expandFlatElementIndex(sourceOffsets[fragmentIndex], fragmentType.getShape());
|
||||
for (int64_t dim = 0; dim < rank; ++dim)
|
||||
if (sourceSliceOffsets[dim] + fragmentSizes[dim] > operandType.getDimSize(dim))
|
||||
if (sourceSliceOffsets[dim] + fragmentSizes[dim] > fragmentType.getDimSize(dim))
|
||||
return emitError("fragment assembly source offset must describe a valid unit-stride slice");
|
||||
|
||||
for (const auto& [existingOffsets, existingSizes] : slices) {
|
||||
@@ -630,7 +717,9 @@ LogicalResult verifyComputeResultsUses(Operation* op) {
|
||||
if (!isAnySpatialComputeLike(op))
|
||||
return op->emitError("verifyComputeResultUses: op is not a Spatial compute-like operation");
|
||||
if (!llvm::all_of(op->getResults(), [](Value result) {
|
||||
return llvm::all_of(result.getUsers(), [](Operation* op) {
|
||||
return llvm::all_of(result.getUsers(), [result](Operation* op) {
|
||||
if (isRecordedDeferredCommunicationSource(op, result))
|
||||
return true;
|
||||
return !isAnySpatialComputeLike(op->getParentOp());
|
||||
});
|
||||
})) {
|
||||
@@ -641,33 +730,41 @@ LogicalResult verifyComputeResultsUses(Operation* op) {
|
||||
|
||||
template <typename ComputeOpTy>
|
||||
LogicalResult verifyComputeLikeOp(ComputeOpTy compute, StringRef opName) {
|
||||
auto& block = compute.getBody().front();
|
||||
unsigned expectedArgCount = compute.getWeights().size() + compute.getInputs().size();
|
||||
bool isScheduled = isa<SpatScheduledCompute>(compute.getOperation());
|
||||
if (compute.getBody().empty())
|
||||
return compute.emitOpError("compute body must have at least one block");
|
||||
if (isScheduled && !compute.getBody().hasOneBlock())
|
||||
return compute.emitOpError("scheduled compute must have exactly one block");
|
||||
|
||||
SmallVector<Type> yieldedTypes;
|
||||
for (Block &block : compute.getBody()) {
|
||||
if (block.getNumArguments() != expectedArgCount)
|
||||
return compute.emitOpError("compute body must have weight and input block arguments");
|
||||
|
||||
for (auto [weightIndex, weight] : llvm::enumerate(compute.getWeights())) {
|
||||
auto blockArg = compute.getWeightArgument(weightIndex);
|
||||
if (!blockArg || blockArg->getType() != weight.getType())
|
||||
for (auto [weightIndex, weight] : llvm::enumerate(compute.getWeights()))
|
||||
if (block.getArgument(weightIndex).getType() != weight.getType())
|
||||
return compute.emitOpError("compute weight block argument types must match weight operand types exactly");
|
||||
}
|
||||
for (auto [inputIndex, input] : llvm::enumerate(compute.getInputs())) {
|
||||
auto blockArg = compute.getInputArgument(inputIndex);
|
||||
if (!blockArg || blockArg->getType() != input.getType())
|
||||
for (auto [inputIndex, input] : llvm::enumerate(compute.getInputs()))
|
||||
if (block.getArgument(compute.getWeights().size() + inputIndex).getType() != input.getType())
|
||||
return compute.emitOpError("compute input block argument types must match input operand types exactly");
|
||||
}
|
||||
|
||||
if (block.mightHaveTerminator()) {
|
||||
auto yieldOp = dyn_cast_or_null<SpatYieldOp>(block.getTerminator());
|
||||
if (!yieldOp)
|
||||
Operation* terminator = block.getTerminator();
|
||||
if (auto yieldOp = dyn_cast_or_null<SpatYieldOp>(terminator)) {
|
||||
llvm::append_range(yieldedTypes, yieldOp->getOperandTypes());
|
||||
continue;
|
||||
}
|
||||
auto blockYield = dyn_cast_or_null<SpatBlockYieldOp>(terminator);
|
||||
if (!blockYield || isScheduled)
|
||||
return compute.emitOpError("ComputeOp must have a single yield operation");
|
||||
llvm::append_range(yieldedTypes, blockYield->getOperandTypes());
|
||||
}
|
||||
|
||||
auto resultTypes = compute.getResultTypes();
|
||||
auto yieldTypes = yieldOp->getOperandTypes();
|
||||
if (resultTypes.size() != yieldTypes.size())
|
||||
return compute.emitOpError("ComputeOp must have same number of results as yieldOp operands");
|
||||
if (resultTypes.size() != yieldedTypes.size())
|
||||
return compute.emitOpError("ComputeOp must have same number of results as yielded operands");
|
||||
|
||||
for (auto it : llvm::reverse(llvm::zip(resultTypes, yieldTypes))) {
|
||||
for (auto it : llvm::reverse(llvm::zip(resultTypes, yieldedTypes))) {
|
||||
auto resultType = std::get<0>(it);
|
||||
auto yieldType = std::get<1>(it);
|
||||
|
||||
@@ -677,18 +774,18 @@ LogicalResult verifyComputeLikeOp(ComputeOpTy compute, StringRef opName) {
|
||||
if (auto resultRankedType = dyn_cast<RankedTensorType>(resultType)) {
|
||||
if (auto yieldRankedType = dyn_cast<RankedTensorType>(yieldType)) {
|
||||
if (resultRankedType.getEncoding() != yieldRankedType.getEncoding())
|
||||
return compute.emitOpError("ComputeOp output must have the same encoding as yieldOp operand");
|
||||
return compute.emitOpError("ComputeOp output has an encoding while yieldOp operand does not have one");
|
||||
}
|
||||
else {
|
||||
return compute.emitOpError("ComputeOp output has an encoding while yieldOp operand does not have one");
|
||||
return compute.emitOpError("ComputeOp output must have the same encoding as yieldOp operand");
|
||||
}
|
||||
}
|
||||
else if (dyn_cast<RankedTensorType>(yieldType)) {
|
||||
return compute.emitOpError("ComputeOp output must not have an encoding if yieldOp operand has one");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (compute.getBody().hasOneBlock())
|
||||
for (unsigned inputIndex = 0; inputIndex < compute.getInputs().size(); ++inputIndex)
|
||||
if (auto inputArg = compute.getInputArgument(inputIndex); !inputArg || inputArg->use_empty())
|
||||
return compute.emitOpError("ComputeOp block argument is not used");
|
||||
@@ -705,6 +802,93 @@ LogicalResult SpatGraphCompute::verify() { return verifyComputeLikeOp(*this, "sp
|
||||
|
||||
LogicalResult SpatScheduledCompute::verify() { return verifyComputeLikeOp(*this, "spat.scheduled_compute"); }
|
||||
|
||||
LogicalResult SpatBlockYieldOp::verify() {
|
||||
if (getOperation()->getNumSuccessors() > 1)
|
||||
return emitOpError("may target at most one next scheduled block");
|
||||
Operation* parent = getOperation()->getParentOp();
|
||||
if (!isa_and_nonnull<SpatScheduledCompute>(parent))
|
||||
return emitOpError("expected spat.scheduled_compute parent");
|
||||
if (getOperation()->getNumSuccessors() == 1) {
|
||||
Block* next = getOperation()->getSuccessor(0);
|
||||
if (getOperation()->getNumOperands() != next->getNumArguments())
|
||||
return emitOpError("successor operand count must match next block argument count");
|
||||
for (auto [operand, argument] : llvm::zip(getOperation()->getOperands(), next->getArguments()))
|
||||
if (operand.getType() != argument.getType())
|
||||
return emitOpError("successor operand types must match next block argument types");
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
LogicalResult SpatDeferredCommunicationOp::verify() {
|
||||
if (getSources().empty())
|
||||
return emitOpError("requires at least one source");
|
||||
auto specialization = (*this)->getAttrOfType<IntegerAttr>(
|
||||
"specialization_count");
|
||||
int64_t specializationCount = specialization ? specialization.getInt() : 1;
|
||||
if (specializationCount <= 0)
|
||||
return emitOpError("specialization_count must be positive");
|
||||
static constexpr StringLiteral staleAttributes[] = {
|
||||
"exchangeId", "logicalProducer", "logicalConsumer", "sourceClass", "targetClass", "sourceCore",
|
||||
"targetCore", "sourceLane", "targetLane", "transferKind", "resultIndex", "projectedTransfer",
|
||||
"hostOutputOwner", "source_cpus", "source_classes", "source_lane_ranges", "target_cpus",
|
||||
"target_classes", "target_lane_ranges", "batched", "source_operand_for_scheduled_lane",
|
||||
"multi_source_payload"};
|
||||
for (StringLiteral name : staleAttributes)
|
||||
if (getOperation()->hasAttr(name))
|
||||
return emitOpError() << "does not accept stale routing attribute '" << name
|
||||
<< "'; source selection and shaping belong in the body and routing is derived in Phase 2";
|
||||
if (getBody().empty())
|
||||
return emitOpError("spat.deferred_communication requires a body block");
|
||||
Block &body = getBody().front();
|
||||
unsigned expectedArguments = getSources().size()
|
||||
+ (specializationCount > 1 ? 1 : 0);
|
||||
if (body.getNumArguments() != expectedArguments)
|
||||
return emitOpError("body argument count must match sources plus the grouped specialization argument");
|
||||
for (auto [argument, source] : llvm::zip(
|
||||
body.getArguments().take_front(getSources().size()), getSources()))
|
||||
if (argument.getType() != source.getType())
|
||||
return emitOpError("body source argument types must match source operand types");
|
||||
if (specializationCount > 1
|
||||
&& !body.getArguments().back().getType().isIndex())
|
||||
return emitOpError("grouped specialization argument must have index type");
|
||||
auto yield = dyn_cast_or_null<SpatYieldOp>(body.getTerminator());
|
||||
if (!yield || yield.getOutputs().size() != 1)
|
||||
return emitOpError("body must yield exactly one fragment");
|
||||
Type fragmentType = yield.getOutputs().front().getType();
|
||||
Type outputType = getOutput().getType();
|
||||
if (specializationCount == 1)
|
||||
return fragmentType == outputType
|
||||
? success()
|
||||
: emitOpError("ordinary deferred yield type must match its output type");
|
||||
auto fragmentTensor = dyn_cast<RankedTensorType>(fragmentType);
|
||||
auto outputTensor = dyn_cast<RankedTensorType>(outputType);
|
||||
if (!fragmentTensor || !outputTensor || !fragmentTensor.hasStaticShape()
|
||||
|| !outputTensor.hasStaticShape())
|
||||
return emitOpError("grouped specialization requires static ranked tensor types");
|
||||
if (outputTensor.getRank() != fragmentTensor.getRank() + 1
|
||||
|| outputTensor.getDimSize(0) != specializationCount
|
||||
|| outputTensor.getShape().drop_front() != fragmentTensor.getShape()
|
||||
|| outputTensor.getElementType() != fragmentTensor.getElementType())
|
||||
return emitOpError("grouped output must have shape specialization_count x fragment shape");
|
||||
return success();
|
||||
}
|
||||
|
||||
LogicalResult SpatDeferredSourceSelectOp::verify() {
|
||||
if (getSources().empty())
|
||||
return emitOpError("requires at least one source");
|
||||
if (!getSelector().getType().isIndex())
|
||||
return emitOpError("requires an index selector");
|
||||
if (llvm::any_of(getSources(), [&](Value source) {
|
||||
return source.getType() != getOutput().getType();
|
||||
}))
|
||||
return emitOpError("source and output types must match");
|
||||
if (!getOperation()->getParentOfType<SpatDeferredCommunicationOp>()
|
||||
&& !getOperation()->getParentOfType<SpatScheduledCompute>()
|
||||
&& !getOperation()->getParentOfType<SpatScheduledComputeBatch>())
|
||||
return emitOpError("must be nested in deferred or scheduled computation");
|
||||
return success();
|
||||
}
|
||||
|
||||
template <typename ComputeBatchOpTy>
|
||||
LogicalResult verifyComputeBatchLikeOp(ComputeBatchOpTy batch, StringRef opName) {
|
||||
int32_t count = batch.getLaneCount();
|
||||
@@ -727,30 +911,33 @@ LogicalResult verifyComputeBatchLikeOp(ComputeBatchOpTy batch, StringRef opName)
|
||||
return batch.emitOpError("compute_batch coreIds values must be unique");
|
||||
}
|
||||
|
||||
Block& block = batch.getBody().front();
|
||||
if (batch.getBody().empty())
|
||||
return batch.emitOpError("compute_batch body must have at least one block");
|
||||
|
||||
unsigned expectedArgCount = 1 + batch.getWeights().size() + batch.getInputs().size() + batch.getNumResults();
|
||||
bool verifyLaneSliceOffsets = !isa<SpatScheduledComputeBatch>(batch.getOperation());
|
||||
for (Block& block : batch.getBody()) {
|
||||
if (block.getNumArguments() == 0)
|
||||
return batch.emitOpError("compute_batch body must have exactly one lane block argument");
|
||||
unsigned expectedArgCount = 1 + batch.getWeights().size() + batch.getInputs().size() + batch.getNumResults();
|
||||
if (block.getNumArguments() != expectedArgCount)
|
||||
return batch.emitOpError("compute_batch body block arguments must match lane, weight, input, and output operands/results");
|
||||
auto laneArg = batch.getLaneArgument();
|
||||
if (!laneArg || !laneArg->getType().isIndex())
|
||||
return batch.emitOpError(
|
||||
"compute_batch body block arguments must match lane, weight, input, and output operands/results");
|
||||
if (!block.getArgument(0).getType().isIndex())
|
||||
return batch.emitOpError("compute_batch first block argument must have index type");
|
||||
|
||||
for (auto [weightIndex, weight] : llvm::enumerate(batch.getWeights())) {
|
||||
auto blockArg = batch.getWeightArgument(weightIndex);
|
||||
if (!blockArg || blockArg->getType() != weight.getType())
|
||||
for (auto [weightIndex, weight] : llvm::enumerate(batch.getWeights()))
|
||||
if (block.getArgument(1 + weightIndex).getType() != weight.getType())
|
||||
return batch.emitOpError("compute_batch weight block argument types must match weight operand types exactly");
|
||||
}
|
||||
for (auto [inputIndex, input] : llvm::enumerate(batch.getInputs())) {
|
||||
auto blockArg = batch.getInputArgument(inputIndex);
|
||||
if (!blockArg || blockArg->getType() != input.getType())
|
||||
for (auto [inputIndex, input] : llvm::enumerate(batch.getInputs()))
|
||||
if (block.getArgument(1 + batch.getWeights().size() + inputIndex).getType() != input.getType())
|
||||
return batch.emitOpError("compute_batch input block argument types must match input operand types exactly");
|
||||
}
|
||||
for (auto [resultIndex, resultType] : llvm::enumerate(batch.getResultTypes())) {
|
||||
auto blockArg = batch.getOutputArgument(resultIndex);
|
||||
if (!blockArg || blockArg->getType() != resultType)
|
||||
for (auto [resultIndex, resultType] : llvm::enumerate(batch.getResultTypes()))
|
||||
if (block.getArgument(1 + batch.getWeights().size() + batch.getInputs().size() + resultIndex).getType()
|
||||
!= resultType)
|
||||
return batch.emitOpError("compute_batch output block argument types must match result types exactly");
|
||||
|
||||
if (failed(verifyBatchBody(batch, block, verifyLaneSliceOffsets)))
|
||||
return failure();
|
||||
}
|
||||
|
||||
if (failed(verifyComputeResultsUses(batch.getOperation())))
|
||||
@@ -759,7 +946,7 @@ LogicalResult verifyComputeBatchLikeOp(ComputeBatchOpTy batch, StringRef opName)
|
||||
return failure();
|
||||
if (failed(verifyOnlyConstantExternalValues(batch.getOperation(), batch.getBody(), opName)))
|
||||
return failure();
|
||||
return verifyBatchBody(batch, block);
|
||||
return success();
|
||||
}
|
||||
|
||||
LogicalResult SpatGraphComputeBatch::verify() { return verifyComputeBatchLikeOp(*this, "spat.graph_compute_batch"); }
|
||||
|
||||
@@ -0,0 +1,378 @@
|
||||
#include "DeferredBoundaryPlanning.hpp"
|
||||
#include "DeferredCommunicationScheduling.hpp"
|
||||
#include "DeferredTransferPlanning.hpp"
|
||||
|
||||
namespace onnx_mlir::spatial {
|
||||
using namespace mlir;
|
||||
namespace {
|
||||
|
||||
static BoundaryProgram &getBoundary(
|
||||
SmallVectorImpl<BoundaryProgram> &boundaries,
|
||||
DenseMap<BoundaryKey, unsigned> &indices, BoundaryKey key) {
|
||||
auto [it, inserted] = indices.try_emplace(key, boundaries.size());
|
||||
if (inserted)
|
||||
boundaries.push_back({key, {}});
|
||||
return boundaries[it->second];
|
||||
}
|
||||
|
||||
static LaneSet getReceiveLanes(const ScheduledTransferSlice &slice) {
|
||||
LaneInterval family = slice.family->targetLanes.intervals().front();
|
||||
unsigned begin = family.begin + slice.familyOffset;
|
||||
return LaneSet::range(begin, begin + slice.transferCount);
|
||||
}
|
||||
|
||||
static void appendSend(BoundaryProgram &boundary,
|
||||
const ScheduledTransferSlice &slice) {
|
||||
unsigned lane = slice.family->requirement->producer->scheduledLane;
|
||||
LaneSet lanes = LaneSet::range(lane, lane + 1);
|
||||
if (!boundary.instructions.empty())
|
||||
if (auto *run = std::get_if<EmitSendRun>(&boundary.instructions.back());
|
||||
run && haveSameTransferEmissionSignature(
|
||||
*run->slices.back().family, *slice.family)) {
|
||||
run->slices.push_back(slice);
|
||||
run->lanes = run->lanes.unite(lanes);
|
||||
return;
|
||||
}
|
||||
boundary.instructions.push_back(EmitSendRun {{slice}, lanes});
|
||||
}
|
||||
|
||||
static LogicalResult addCoverage(
|
||||
RequirementFamily &requirement, const LaneSet &lanes,
|
||||
DenseMap<RequirementFamily *, LaneSet> &coverage) {
|
||||
LaneSet &covered = coverage[&requirement];
|
||||
if (!covered.intersect(lanes).empty())
|
||||
return requirement.exchange->deferred.emitOpError(
|
||||
"deferred availability covers a target lane more than once");
|
||||
covered = covered.unite(lanes);
|
||||
return success();
|
||||
}
|
||||
|
||||
static bool canGroupLocalAvailability(RequirementFamily &lhs,
|
||||
RequirementFamily &rhs) {
|
||||
if (!(lhs.coordinate == rhs.coordinate)
|
||||
|| lhs.publicationFragmentType != rhs.publicationFragmentType
|
||||
|| lhs.producer->payload != rhs.producer->payload
|
||||
|| lhs.producerProjection.has_value()
|
||||
!= rhs.producerProjection.has_value())
|
||||
return false;
|
||||
if (!lhs.producerProjection)
|
||||
return true;
|
||||
auto ranks = [](const DeferredStaticSliceGeometry &geometry) {
|
||||
return std::tuple(geometry.offsets.size(), geometry.sizes.size(),
|
||||
geometry.strides.size());
|
||||
};
|
||||
return ranks(*lhs.producerProjection) == ranks(*rhs.producerProjection);
|
||||
}
|
||||
|
||||
struct CollectionTarget {
|
||||
const FragmentCollectionPlan *collection = nullptr;
|
||||
unsigned position = 0;
|
||||
};
|
||||
|
||||
static bool sameCollectionEmissionContract(
|
||||
const CollectionTarget &lhs, const CollectionTarget &rhs) {
|
||||
if (lhs.collection != rhs.collection)
|
||||
return false;
|
||||
if (lhs.collection->key.kind != FragmentCollectionKind::InsertAssembly)
|
||||
return true;
|
||||
const auto &entries =
|
||||
lhs.collection->key.exchange->program.insertAssembly->entries;
|
||||
const auto &left = entries[lhs.position];
|
||||
const auto &right = entries[rhs.position];
|
||||
return left.sourceTransform == right.sourceTransform
|
||||
&& left.sourceType == right.sourceType;
|
||||
}
|
||||
|
||||
static std::optional<EmitLocalCollectionRun> buildLocalConcat(
|
||||
const FragmentCollectionPlan &collection,
|
||||
const DenseMap<RequirementFamily *, LocalAvailabilityFamily *> &locals,
|
||||
unsigned targetLaneCount) {
|
||||
RankedTensorType type = collection.collectionType;
|
||||
if (collection.key.kind != FragmentCollectionKind::Leaf
|
||||
|| targetLaneCount != 1 || type.getRank() == 0
|
||||
|| collection.positionCount == 0
|
||||
|| type.getDimSize(0) != collection.positionCount)
|
||||
return std::nullopt;
|
||||
SmallVector<RequirementFamily *> requirements(collection.positionCount);
|
||||
for (const auto &entry : collection.requirements) {
|
||||
if (entry.position >= requirements.size() || requirements[entry.position])
|
||||
return std::nullopt;
|
||||
requirements[entry.position] = entry.family;
|
||||
}
|
||||
LaneSet all = LaneSet::all(targetLaneCount);
|
||||
EmitLocalCollectionRun run {&collection, 0, {}, all, true};
|
||||
Value payload;
|
||||
int64_t payloadBegin = 0;
|
||||
int64_t payloadEnd = 0;
|
||||
for (auto [position, requirement] : llvm::enumerate(requirements)) {
|
||||
if (!requirement)
|
||||
return std::nullopt;
|
||||
LocalAvailabilityFamily *local = locals.lookup(requirement);
|
||||
if (!local || !(requirement->targetLanes == all)
|
||||
|| !(local->targetLanes == all) || !requirement->graphLanes
|
||||
|| requirement->graphLanes->size() != 1
|
||||
|| requirement->graphLanes->valueAt(0) != static_cast<int64_t>(position)
|
||||
|| !requirement->producerLocalOffsets
|
||||
|| requirement->producerLocalOffsets->size() != 1)
|
||||
return std::nullopt;
|
||||
ProducedValue *producer = requirement->producer;
|
||||
int64_t payloadOffset =
|
||||
requirement->producerLocalOffsets->valueAt(0);
|
||||
if (static_cast<int64_t>(position) == payloadEnd) {
|
||||
if (!producer)
|
||||
return std::nullopt;
|
||||
auto payloadType = dyn_cast<RankedTensorType>(producer->payload.getType());
|
||||
if (!payloadType || payloadOffset != 0
|
||||
|| payloadType.getRank() != type.getRank()
|
||||
|| payloadType.getElementType() != type.getElementType()
|
||||
|| payloadType.getShape().drop_front() != type.getShape().drop_front())
|
||||
return std::nullopt;
|
||||
payloadBegin = position;
|
||||
payloadEnd = payloadBegin + payloadType.getDimSize(0);
|
||||
if (payloadEnd > collection.positionCount)
|
||||
return std::nullopt;
|
||||
payload = producer->payload;
|
||||
run.families.push_back(local);
|
||||
}
|
||||
if (producer->payload != payload
|
||||
|| payloadOffset != static_cast<int64_t>(position) - payloadBegin)
|
||||
return std::nullopt;
|
||||
}
|
||||
return payloadEnd == collection.positionCount
|
||||
? std::optional<EmitLocalCollectionRun>(std::move(run)) : std::nullopt;
|
||||
}
|
||||
|
||||
static bool canLoopLocalCollection(
|
||||
const EmitLocalCollectionRun &update, unsigned targetLaneCount) {
|
||||
if (!update.collection || update.concatenatePayloads
|
||||
|| update.families.size() != 1
|
||||
|| !(update.lanes == LaneSet::all(targetLaneCount)))
|
||||
return false;
|
||||
RequirementFamily &requirement = *update.families.front()->requirement;
|
||||
return requirement.targetLanes == update.lanes
|
||||
&& !requirement.producerProjection
|
||||
&& (!requirement.producerLocalOffsets
|
||||
|| requirement.producerLocalOffsets->size() == targetLaneCount);
|
||||
}
|
||||
|
||||
static bool haveSameLocalCollectionContract(
|
||||
const EmitLocalCollectionRun &lhs,
|
||||
const EmitLocalCollectionRun &rhs) {
|
||||
if (lhs.collection != rhs.collection)
|
||||
return false;
|
||||
RequirementFamily &left = *lhs.families.front()->requirement;
|
||||
RequirementFamily &right = *rhs.families.front()->requirement;
|
||||
if (left.producer->payload != right.producer->payload
|
||||
|| left.publicationFragmentType != right.publicationFragmentType)
|
||||
return false;
|
||||
if (lhs.collection->key.kind != FragmentCollectionKind::InsertAssembly)
|
||||
return true;
|
||||
const auto &entries =
|
||||
lhs.collection->key.exchange->program.insertAssembly->entries;
|
||||
const auto &leftEntry = entries[lhs.collectionPosition];
|
||||
const auto &rightEntry = entries[rhs.collectionPosition];
|
||||
return leftEntry.sourceTransform == rightEntry.sourceTransform
|
||||
&& leftEntry.sourceType == rightEntry.sourceType;
|
||||
}
|
||||
|
||||
static void appendLocalUpdates(
|
||||
BoundaryProgram &boundary,
|
||||
SmallVectorImpl<EmitLocalCollectionRun> &updates,
|
||||
unsigned targetLaneCount) {
|
||||
for (size_t index = 0; index < updates.size();) {
|
||||
EmitLocalCollectionRun &first = updates[index];
|
||||
if (!canLoopLocalCollection(first, targetLaneCount)) {
|
||||
boundary.instructions.push_back(std::move(first));
|
||||
++index;
|
||||
continue;
|
||||
}
|
||||
size_t end = index + 1;
|
||||
while (end < updates.size()
|
||||
&& canLoopLocalCollection(updates[end], targetLaneCount)
|
||||
&& haveSameLocalCollectionContract(first, updates[end]))
|
||||
++end;
|
||||
if (end - index == 1) {
|
||||
boundary.instructions.push_back(std::move(first));
|
||||
++index;
|
||||
continue;
|
||||
}
|
||||
EmitLocalCollectionLoopRun run;
|
||||
run.collection = first.collection;
|
||||
run.lanes = first.lanes;
|
||||
for (; index < end; ++index) {
|
||||
run.positions.push_back(updates[index].collectionPosition);
|
||||
run.families.push_back(updates[index].families.front());
|
||||
}
|
||||
boundary.instructions.push_back(std::move(run));
|
||||
}
|
||||
}
|
||||
|
||||
static void appendReceive(BoundaryProgram &boundary,
|
||||
const ScheduledTransferSlice &slice,
|
||||
CollectionTarget target) {
|
||||
RequirementFamily *requirement = slice.family->requirement;
|
||||
LaneSet lanes = getReceiveLanes(slice);
|
||||
if (!boundary.instructions.empty())
|
||||
if (auto *run = std::get_if<EmitReceiveAssemblyRun>(
|
||||
&boundary.instructions.back())) {
|
||||
RequirementFamily *previous = run->slices[
|
||||
run->entryOffsets[run->entryOffsets.size() - 2]].family->requirement;
|
||||
CollectionTarget previousTarget {run->collection, run->positions.back()};
|
||||
bool sameEntry = previous == requirement;
|
||||
if (sameEntry
|
||||
|| (sameCollectionEmissionContract(previousTarget, target)
|
||||
&& previous->publicationFragmentType
|
||||
== requirement->publicationFragmentType)) {
|
||||
run->slices.push_back(slice);
|
||||
if (sameEntry) {
|
||||
run->entryOffsets.back() = run->slices.size();
|
||||
run->entryLanes.back() = run->entryLanes.back().unite(lanes);
|
||||
} else {
|
||||
run->entryOffsets.push_back(run->slices.size());
|
||||
run->positions.push_back(target.position);
|
||||
run->entryLanes.push_back(lanes);
|
||||
}
|
||||
run->lanes = run->lanes.unite(lanes);
|
||||
return;
|
||||
}
|
||||
}
|
||||
boundary.instructions.push_back(EmitReceiveAssemblyRun {
|
||||
target.collection, {slice}, {0, 1}, {target.position}, {lanes}, lanes});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
FailureOr<DeferredBoundaryPlan> buildDeferredBoundaryPlan(
|
||||
DeferredTransferPlan &transfers,
|
||||
const ScheduledCommunicationPlan &schedule) {
|
||||
DeferredBoundaryPlan result;
|
||||
SmallVector<BoundaryProgram> boundaries;
|
||||
DenseMap<BoundaryKey, unsigned> indices;
|
||||
DenseMap<DeferredExchangePlan *, unsigned> resultSteps;
|
||||
DenseMap<RequirementFamily *, LaneSet> coverage;
|
||||
|
||||
for (const std::unique_ptr<DeferredExchangePlan> &exchange :
|
||||
transfers.exchanges) {
|
||||
auto plan = buildDeferredResultPlan(*exchange);
|
||||
if (failed(plan))
|
||||
return exchange->deferred.emitOpError(
|
||||
"cannot evaluate deferred result lane functions"), failure();
|
||||
result.results.push_back(std::move(*plan));
|
||||
}
|
||||
DenseMap<RequirementFamily *, CollectionTarget> collections;
|
||||
for (const DeferredResultPlan &plan : result.results)
|
||||
for (const FragmentCollectionPlan &collection : plan.collections)
|
||||
for (const FragmentCollectionPlan::Requirement &requirement :
|
||||
collection.requirements)
|
||||
if (!collections.try_emplace(
|
||||
requirement.family,
|
||||
CollectionTarget{&collection, requirement.position}).second)
|
||||
return requirement.family->exchange->deferred.emitOpError(
|
||||
"deferred requirement is owned by multiple fragment collections"),
|
||||
failure();
|
||||
|
||||
for (const ScheduledTransferSlice &slice : schedule.slices) {
|
||||
ExternalTransferFamily &family = *slice.family;
|
||||
BoundaryProgram &source = getBoundary(
|
||||
boundaries, indices,
|
||||
{family.sourceScheduled, slice.sourceInsertionStep});
|
||||
appendSend(source, slice);
|
||||
BoundaryProgram &target = getBoundary(
|
||||
boundaries, indices,
|
||||
{family.targetScheduled, slice.targetInsertionStep});
|
||||
CollectionTarget collection = collections.lookup(family.requirement);
|
||||
if (!collection.collection)
|
||||
return family.requirement->exchange->deferred.emitOpError(
|
||||
"deferred requirement has no complete result-owned collection"),
|
||||
failure();
|
||||
appendReceive(target, slice, collection);
|
||||
resultSteps[family.requirement->exchange] = std::max(
|
||||
resultSteps.lookup(family.requirement->exchange),
|
||||
slice.targetInsertionStep);
|
||||
if (failed(addCoverage(*family.requirement, getReceiveLanes(slice),
|
||||
coverage)))
|
||||
return failure();
|
||||
}
|
||||
|
||||
for (const std::unique_ptr<DeferredExchangePlan> &exchange :
|
||||
transfers.exchanges) {
|
||||
unsigned resultStep = resultSteps.lookup(exchange.get());
|
||||
for (LocalAvailabilityFamily &local : exchange->local)
|
||||
resultStep = std::max(
|
||||
resultStep, local.requirement->producer->step + 1);
|
||||
if (exchange->requirements.empty())
|
||||
resultStep = exchange->consumerStep;
|
||||
if (resultStep > exchange->consumerStep)
|
||||
return exchange->deferred.emitOpError(
|
||||
"deferred result boundary is later than its consumer"), failure();
|
||||
|
||||
BoundaryProgram &boundary = getBoundary(
|
||||
boundaries, indices, {exchange->target, resultStep});
|
||||
DenseMap<RequirementFamily *, LocalAvailabilityFamily *> localByRequirement;
|
||||
for (LocalAvailabilityFamily &local : exchange->local) {
|
||||
auto [it, inserted] = localByRequirement.try_emplace(local.requirement, &local);
|
||||
if (!inserted)
|
||||
it->second = nullptr;
|
||||
}
|
||||
DenseMap<const FragmentCollectionPlan *, std::optional<EmitLocalCollectionRun>> concatRuns;
|
||||
llvm::SmallPtrSet<const FragmentCollectionPlan *, 4> emittedConcats;
|
||||
SmallVector<EmitLocalCollectionRun> localUpdates;
|
||||
for (LocalAvailabilityFamily &local : exchange->local) {
|
||||
CollectionTarget target = collections.lookup(local.requirement);
|
||||
if (!target.collection)
|
||||
return exchange->deferred.emitOpError(
|
||||
"local availability has no complete result-owned collection"),
|
||||
failure();
|
||||
auto [concat, inserted] = concatRuns.try_emplace(target.collection);
|
||||
if (inserted)
|
||||
concat->second = buildLocalConcat(*target.collection,
|
||||
localByRequirement,
|
||||
exchange->targetLaneCount);
|
||||
if (concat->second) {
|
||||
if (emittedConcats.insert(target.collection).second)
|
||||
localUpdates.push_back(std::move(*concat->second));
|
||||
if (failed(addCoverage(*local.requirement, local.targetLanes, coverage)))
|
||||
return failure();
|
||||
continue;
|
||||
}
|
||||
auto grouped = llvm::find_if(localUpdates, [&](EmitLocalCollectionRun &update) {
|
||||
return update.lanes.intersect(local.targetLanes).empty()
|
||||
&& update.collection == target.collection
|
||||
&& update.collectionPosition == target.position
|
||||
&& canGroupLocalAvailability(*update.families.front()->requirement,
|
||||
*local.requirement);
|
||||
});
|
||||
if (grouped == localUpdates.end()) {
|
||||
localUpdates.push_back(EmitLocalCollectionRun {
|
||||
target.collection, target.position, {&local}, local.targetLanes,
|
||||
false});
|
||||
} else {
|
||||
grouped->families.push_back(&local);
|
||||
grouped->lanes = grouped->lanes.unite(local.targetLanes);
|
||||
}
|
||||
if (failed(addCoverage(*local.requirement, local.targetLanes, coverage)))
|
||||
return failure();
|
||||
}
|
||||
appendLocalUpdates(boundary, localUpdates, exchange->targetLaneCount);
|
||||
for (RequirementFamily &requirement : exchange->requirements)
|
||||
if (!(coverage.lookup(&requirement) == requirement.targetLanes))
|
||||
return exchange->deferred.emitOpError(
|
||||
"deferred availability does not cover every target lane exactly once"),
|
||||
failure();
|
||||
boundary.instructions.push_back(ProduceDeferredResult {exchange.get()});
|
||||
|
||||
}
|
||||
|
||||
DenseMap<ScheduledInfo *, unsigned> scheduledOrder;
|
||||
for (auto [index, scheduled] : llvm::enumerate(transfers.scheduled))
|
||||
scheduledOrder[&scheduled] = index;
|
||||
llvm::stable_sort(boundaries, [&](const BoundaryProgram &lhs,
|
||||
const BoundaryProgram &rhs) {
|
||||
return std::tie(scheduledOrder[lhs.key.first], lhs.key.second)
|
||||
< std::tie(scheduledOrder[rhs.key.first], rhs.key.second);
|
||||
});
|
||||
result.boundaries = std::move(boundaries);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace onnx_mlir::spatial
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user