Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ab54243fda | |||
| 5f42da36ae | |||
| ae67d720c6 | |||
| d788178749 | |||
| c744f388dc | |||
| 51fdb830e5 | |||
| d1a29ace3c | |||
| 61e3ea9996 | |||
| fed6d343e5 | |||
| 871fcfa832 | |||
| 1f4f58de1c | |||
| 8338caf3f3 | |||
| 47f6715296 | |||
| 2bfc033af9 | |||
| 83a54e28e4 | |||
| cc9b025a35 | |||
| c4dd28a607 | |||
| 8d3eb929f6 | |||
| f5e1c2e706 |
+1
-1
@@ -11,4 +11,4 @@ build_*
|
|||||||
compile.sh
|
compile.sh
|
||||||
pimcomp_utils/*
|
pimcomp_utils/*
|
||||||
|
|
||||||
**/__*
|
**/__pycache__/
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
* Always read the full README.md before doing anything
|
* Always read the full README.md before doing anything
|
||||||
|
* Always read the full invariants/GRAPH_COMPUTE_BATCH_INVARIANT.md before modifying Spatial graph IR, Blueprint handling, or MergeComputeNodes.
|
||||||
* Build commands:
|
* Build commands:
|
||||||
* `cmake --build ./build_release`
|
* `cmake --build ./build_release`
|
||||||
* `cmake --build ./build_debug`
|
* `cmake --build ./build_debug`
|
||||||
|
|||||||
@@ -99,12 +99,13 @@ Pass these to `onnx-mlir` when compiling for PIM:
|
|||||||
- `--core-count=<N>` - required positive core count for PIM compilation.
|
- `--core-count=<N>` - required positive core count for PIM compilation.
|
||||||
- `--crossbar-size=<N>` - crossbar width/height. Default in code is `2`.
|
- `--crossbar-size=<N>` - crossbar width/height. Default in code is `2`.
|
||||||
- `--crossbar-count=<N>` - crossbars per core. Default in code is `256`.
|
- `--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.
|
|
||||||
- `--pim-only-codegen` - assume input is already bufferized PIM IR and only run
|
- `--pim-only-codegen` - assume input is already bufferized PIM IR and only run
|
||||||
the codegen tail.
|
the codegen tail.
|
||||||
- `--pim-emit-json` - also emit `core_*.json` instruction files alongside
|
- `--pim-emit-json` - also emit `core_*.json` instruction files alongside
|
||||||
`core_*.pim`.
|
`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.
|
- `--use-experimental-conv-impl` - use the alternate convolution lowering.
|
||||||
- `--ignore-concat-error` - soft-fail a ConcatOp corner case.
|
- `--ignore-concat-error` - soft-fail a ConcatOp corner case.
|
||||||
|
|
||||||
@@ -167,7 +168,8 @@ Each validation run writes artifacts in the model workspace, for example under
|
|||||||
- `simulation/out.bin` - raw simulator output used for comparison.
|
- `simulation/out.bin` - raw simulator output used for comparison.
|
||||||
|
|
||||||
The compiler currently dumps dialect snapshots such as `spatial0.mlir`,
|
The compiler currently dumps dialect snapshots such as `spatial0.mlir`,
|
||||||
`spatial1_dcp_merged.mlir`, `pim0.mlir`, `pim1_buff.mlir`,
|
`spatial1_graph.mlir`, `spatial2_trivial_merged.mlir`,
|
||||||
|
`spatial3_scheduled_no_comm.mlir`, `spatial4_scheduled.mlir`, `pim0.mlir`, `pim1_buff.mlir`,
|
||||||
`pim2_coalesced.mlir`, and `pim3_folded.mlir` when an output directory is
|
`pim2_coalesced.mlir`, and `pim3_folded.mlir` when an output directory is
|
||||||
available.
|
available.
|
||||||
|
|
||||||
|
|||||||
@@ -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 contain this instruction:
|
||||||
|
|
||||||
|
```text
|
||||||
|
* Always read the full invariants/GRAPH_COMPUTE_BATCH_INVARIANT.md before modifying Spatial graph IR, Blueprint handling, or MergeComputeNodes.
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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,4 @@
|
|||||||
|
colorama>=0.4.6,<1
|
||||||
|
numpy>=1.26.4,<3
|
||||||
|
onnx>=1.17,<2
|
||||||
|
-e ./tools/raptor_graph_explorer[dev]
|
||||||
@@ -117,7 +117,6 @@ add_pim_library(OMPIMAccel
|
|||||||
SpatialOps
|
SpatialOps
|
||||||
PimOps
|
PimOps
|
||||||
OMONNXToSpatial
|
OMONNXToSpatial
|
||||||
OMSpatialToGraphviz
|
|
||||||
OMSpatialToPim
|
OMSpatialToPim
|
||||||
OMPimCommon
|
OMPimCommon
|
||||||
OMPimBufferization
|
OMPimBufferization
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ add_pim_library(OMPimCommon
|
|||||||
IR/IndexingUtils.cpp
|
IR/IndexingUtils.cpp
|
||||||
IR/LoopUtils.cpp
|
IR/LoopUtils.cpp
|
||||||
IR/ShapeUtils.cpp
|
IR/ShapeUtils.cpp
|
||||||
|
IR/ShapingUtils.cpp
|
||||||
|
IR/StaticIntSequence.cpp
|
||||||
|
IR/StaticIntGrid.cpp
|
||||||
IR/SubviewUtils.cpp
|
IR/SubviewUtils.cpp
|
||||||
IR/TensorSliceUtils.cpp
|
IR/TensorSliceUtils.cpp
|
||||||
IR/WeightUtils.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/Arith/IR/Arith.h"
|
||||||
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
||||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||||
@@ -7,6 +8,7 @@
|
|||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
#include "src/Accelerators/PIM/Common/IR/AddressAnalysis.hpp"
|
#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/Common/IR/ShapeUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.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)
|
if (!definingOp)
|
||||||
return mlir::failure();
|
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))
|
if (auto indexCastOp = mlir::dyn_cast<mlir::arith::IndexCastOp>(definingOp))
|
||||||
return resolveIndexValueImpl(indexCastOp.getIn(), knowledge);
|
return resolveIndexValueImpl(indexCastOp.getIn(), knowledge);
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ static FailureOr<int64_t> ceilDivSigned(int64_t lhs, int64_t rhs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Value createOrFoldAffineApply(
|
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(constantAnchor && "expected a valid constant anchor");
|
||||||
assert(map.getNumResults() == 1 && "affine.apply expects a single-result affine map");
|
assert(map.getNumResults() == 1 && "affine.apply expects a single-result affine map");
|
||||||
|
|
||||||
@@ -40,91 +40,91 @@ Value createOrFoldAffineApply(
|
|||||||
for (Value operand : operands) {
|
for (Value operand : operands) {
|
||||||
std::optional<int64_t> constantValue = matchConstantIndexValue(operand);
|
std::optional<int64_t> constantValue = matchConstantIndexValue(operand);
|
||||||
if (!constantValue)
|
if (!constantValue)
|
||||||
return affine::AffineApplyOp::create(rewriter, loc, map, operands).getResult();
|
return affine::AffineApplyOp::create(builder, loc, map, operands).getResult();
|
||||||
operandConstants.push_back(rewriter.getIndexAttr(*constantValue));
|
operandConstants.push_back(builder.getIndexAttr(*constantValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
SmallVector<Attribute> foldedResults;
|
SmallVector<Attribute> foldedResults;
|
||||||
if (succeeded(map.constantFold(operandConstants, foldedResults)) && foldedResults.size() == 1)
|
if (succeeded(map.constantFold(operandConstants, foldedResults)) && foldedResults.size() == 1)
|
||||||
if (auto constantResult = dyn_cast<IntegerAttr>(foldedResults.front()))
|
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(
|
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);
|
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");
|
assert(constantAnchor && "expected a valid constant anchor");
|
||||||
if (multiplier == 0)
|
if (multiplier == 0)
|
||||||
return getOrCreateIndexConstant(rewriter, constantAnchor, 0);
|
return getOrCreateIndexConstant(builder, constantAnchor, 0);
|
||||||
if (multiplier == 1)
|
if (multiplier == 1)
|
||||||
return value;
|
return value;
|
||||||
|
|
||||||
AffineExpr d0 = getAffineDimExpr(0, rewriter.getContext());
|
AffineExpr d0 = getAffineDimExpr(0, builder.getContext());
|
||||||
return createOrFoldAffineApply(rewriter, loc, d0 * multiplier, ValueRange {value}, constantAnchor);
|
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");
|
assert(constantAnchor && "expected a valid constant anchor");
|
||||||
if (offset == 0)
|
if (offset == 0)
|
||||||
return value;
|
return value;
|
||||||
|
|
||||||
AffineExpr d0 = getAffineDimExpr(0, rewriter.getContext());
|
AffineExpr d0 = getAffineDimExpr(0, builder.getContext());
|
||||||
return createOrFoldAffineApply(rewriter, loc, d0 + offset, ValueRange {value}, constantAnchor);
|
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(constantAnchor && "expected a valid constant anchor");
|
||||||
assert(divisor > 0 && "expected a positive affine.mod divisor");
|
assert(divisor > 0 && "expected a positive affine.mod divisor");
|
||||||
if (divisor == 1)
|
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());
|
||||||
return createOrFoldAffineApply(rewriter, loc, d0 % divisor, ValueRange {value}, constantAnchor);
|
return createOrFoldAffineApply(builder, loc, d0 % divisor, ValueRange {value}, constantAnchor);
|
||||||
}
|
}
|
||||||
|
|
||||||
Value affineFloorDivConst(
|
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(constantAnchor && "expected a valid constant anchor");
|
||||||
assert(divisor > 0 && "expected a positive affine.floor_div divisor");
|
assert(divisor > 0 && "expected a positive affine.floor_div divisor");
|
||||||
if (divisor == 1)
|
if (divisor == 1)
|
||||||
return value;
|
return value;
|
||||||
|
|
||||||
AffineExpr d0 = getAffineDimExpr(0, rewriter.getContext());
|
AffineExpr d0 = getAffineDimExpr(0, builder.getContext());
|
||||||
return createOrFoldAffineApply(rewriter, loc, d0.floorDiv(divisor), ValueRange {value}, constantAnchor);
|
return createOrFoldAffineApply(builder, loc, d0.floorDiv(divisor), ValueRange {value}, constantAnchor);
|
||||||
}
|
}
|
||||||
|
|
||||||
Value affineAddModConst(
|
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(constantAnchor && "expected a valid constant anchor");
|
||||||
assert(divisor > 0 && "expected a positive affine.mod divisor");
|
assert(divisor > 0 && "expected a positive affine.mod divisor");
|
||||||
if (divisor == 1)
|
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;
|
AffineExpr expr = d0;
|
||||||
if (offset != 0)
|
if (offset != 0)
|
||||||
expr = expr + offset;
|
expr = expr + offset;
|
||||||
return createOrFoldAffineApply(rewriter, loc, expr % divisor, ValueRange {value}, constantAnchor);
|
return createOrFoldAffineApply(builder, loc, expr % divisor, ValueRange {value}, constantAnchor);
|
||||||
}
|
}
|
||||||
|
|
||||||
Value affineAddFloorDivConst(
|
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(constantAnchor && "expected a valid constant anchor");
|
||||||
assert(divisor > 0 && "expected a positive affine.floor_div divisor");
|
assert(divisor > 0 && "expected a positive affine.floor_div divisor");
|
||||||
if (divisor == 1)
|
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;
|
AffineExpr expr = d0;
|
||||||
if (offset != 0)
|
if (offset != 0)
|
||||||
expr = expr + offset;
|
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) {
|
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)>;
|
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::Location loc,
|
||||||
mlir::AffineMap map,
|
mlir::AffineMap map,
|
||||||
mlir::ValueRange operands,
|
mlir::ValueRange operands,
|
||||||
mlir::Operation* constantAnchor);
|
mlir::Operation* constantAnchor);
|
||||||
|
|
||||||
mlir::Value createOrFoldAffineApply(mlir::RewriterBase& rewriter,
|
mlir::Value createOrFoldAffineApply(mlir::OpBuilder& builder,
|
||||||
mlir::Location loc,
|
mlir::Location loc,
|
||||||
mlir::AffineExpr expr,
|
mlir::AffineExpr expr,
|
||||||
mlir::ValueRange dims,
|
mlir::ValueRange dims,
|
||||||
mlir::Operation* constantAnchor);
|
mlir::Operation* constantAnchor);
|
||||||
|
|
||||||
mlir::Value affineMulConst(mlir::RewriterBase& rewriter,
|
mlir::Value affineMulConst(mlir::OpBuilder& builder,
|
||||||
mlir::Location loc,
|
mlir::Location loc,
|
||||||
mlir::Value value,
|
mlir::Value value,
|
||||||
int64_t multiplier,
|
int64_t multiplier,
|
||||||
mlir::Operation* constantAnchor);
|
mlir::Operation* constantAnchor);
|
||||||
|
|
||||||
mlir::Value affineAddConst(mlir::RewriterBase& rewriter,
|
mlir::Value affineAddConst(mlir::OpBuilder& builder,
|
||||||
mlir::Location loc,
|
mlir::Location loc,
|
||||||
mlir::Value value,
|
mlir::Value value,
|
||||||
int64_t offset,
|
int64_t offset,
|
||||||
mlir::Operation* constantAnchor);
|
mlir::Operation* constantAnchor);
|
||||||
|
|
||||||
mlir::Value affineModConst(mlir::RewriterBase& rewriter,
|
mlir::Value affineModConst(mlir::OpBuilder& builder,
|
||||||
mlir::Location loc,
|
mlir::Location loc,
|
||||||
mlir::Value value,
|
mlir::Value value,
|
||||||
int64_t divisor,
|
int64_t divisor,
|
||||||
mlir::Operation* constantAnchor);
|
mlir::Operation* constantAnchor);
|
||||||
|
|
||||||
mlir::Value affineFloorDivConst(mlir::RewriterBase& rewriter,
|
mlir::Value affineFloorDivConst(mlir::OpBuilder& builder,
|
||||||
mlir::Location loc,
|
mlir::Location loc,
|
||||||
mlir::Value value,
|
mlir::Value value,
|
||||||
int64_t divisor,
|
int64_t divisor,
|
||||||
mlir::Operation* constantAnchor);
|
mlir::Operation* constantAnchor);
|
||||||
|
|
||||||
mlir::Value affineAddModConst(mlir::RewriterBase& rewriter,
|
mlir::Value affineAddModConst(mlir::OpBuilder& builder,
|
||||||
mlir::Location loc,
|
mlir::Location loc,
|
||||||
mlir::Value value,
|
mlir::Value value,
|
||||||
int64_t offset,
|
int64_t offset,
|
||||||
int64_t divisor,
|
int64_t divisor,
|
||||||
mlir::Operation* constantAnchor);
|
mlir::Operation* constantAnchor);
|
||||||
|
|
||||||
mlir::Value affineAddFloorDivConst(mlir::RewriterBase& rewriter,
|
mlir::Value affineAddFloorDivConst(mlir::OpBuilder& builder,
|
||||||
mlir::Location loc,
|
mlir::Location loc,
|
||||||
mlir::Value value,
|
mlir::Value value,
|
||||||
int64_t offset,
|
int64_t offset,
|
||||||
|
|||||||
@@ -10,6 +10,32 @@ using namespace mlir;
|
|||||||
|
|
||||||
namespace onnx_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) {
|
static std::optional<int64_t> getIndexConstantValue(arith::ConstantOp constantOp) {
|
||||||
if (!constantOp.getType().isIndex())
|
if (!constantOp.getType().isIndex())
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
@@ -49,7 +75,7 @@ Value getOrCreateConstant(OperationFolder& folder, Operation* anchorOp, Attribut
|
|||||||
return folder.getOrCreateConstant(hostBlock, arithDialect, value, type);
|
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");
|
assert(anchorOp && "expected a valid anchor operation");
|
||||||
Block* hostBlock = getConstantInsertionBlock(anchorOp);
|
Block* hostBlock = getConstantInsertionBlock(anchorOp);
|
||||||
for (Operation& op : *hostBlock) {
|
for (Operation& op : *hostBlock) {
|
||||||
@@ -59,9 +85,16 @@ Value getOrCreateConstant(RewriterBase& rewriter, Operation* anchorOp, Attribute
|
|||||||
return constantOp.getResult();
|
return constantOp.getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
OpBuilder::InsertionGuard guard(rewriter);
|
OpBuilder::InsertionGuard guard(builder);
|
||||||
rewriter.setInsertionPointToStart(hostBlock);
|
builder.setInsertionPointToStart(hostBlock);
|
||||||
return arith::ConstantOp::create(rewriter, anchorOp->getLoc(), type, cast<TypedAttr>(value)).getResult();
|
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) {
|
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());
|
return getOrCreateConstant(folder, anchorOp, builder.getIndexAttr(value), builder.getIndexType());
|
||||||
}
|
}
|
||||||
|
|
||||||
Value getOrCreateIndexConstant(RewriterBase& rewriter, Operation* anchorOp, int64_t value) {
|
Value getOrCreateIndexConstant(OpBuilder& builder, Operation* anchorOp, int64_t value) {
|
||||||
Builder builder(anchorOp->getContext());
|
return getOrCreateConstant(builder, anchorOp, builder.getIndexAttr(value), builder.getIndexType());
|
||||||
return getOrCreateConstant(rewriter, anchorOp, builder.getIndexAttr(value), builder.getIndexType());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void hoistAndUniquifyIndexConstants(func::FuncOp funcOp, RewriterBase& rewriter) {
|
void hoistAndUniquifyIndexConstants(func::FuncOp funcOp, RewriterBase& rewriter) {
|
||||||
|
|||||||
@@ -6,23 +6,42 @@
|
|||||||
#include "mlir/IR/Value.h"
|
#include "mlir/IR/Value.h"
|
||||||
#include "mlir/Transforms/FoldUtils.h"
|
#include "mlir/Transforms/FoldUtils.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/DenseMap.h"
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
namespace onnx_mlir {
|
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::Block* getConstantInsertionBlock(mlir::Operation* anchorOp);
|
||||||
|
|
||||||
mlir::Value
|
mlir::Value
|
||||||
getOrCreateConstant(mlir::OperationFolder& folder, mlir::Operation* anchorOp, mlir::Attribute value, mlir::Type type);
|
getOrCreateConstant(mlir::OperationFolder& folder, mlir::Operation* anchorOp, mlir::Attribute value, mlir::Type type);
|
||||||
|
|
||||||
mlir::Value
|
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 getOrCreateConstantLike(mlir::OperationFolder& folder, mlir::arith::ConstantOp constantOp);
|
||||||
|
|
||||||
mlir::Value getOrCreateIndexConstant(mlir::OperationFolder& folder, mlir::Operation* anchorOp, int64_t value);
|
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);
|
void hoistAndUniquifyIndexConstants(mlir::func::FuncOp funcOp, mlir::RewriterBase& rewriter);
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#include "mlir/Dialect/Affine/IR/AffineOps.h"
|
||||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||||
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
||||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||||
@@ -10,7 +11,8 @@
|
|||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
|
|
||||||
bool isCoreStaticAddressOp(mlir::Operation* op) {
|
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::AddIOp,
|
||||||
mlir::arith::SubIOp,
|
mlir::arith::SubIOp,
|
||||||
mlir::arith::MulIOp,
|
mlir::arith::MulIOp,
|
||||||
@@ -36,9 +38,10 @@ bool isCoreStaticAddressOp(mlir::Operation* op) {
|
|||||||
|
|
||||||
mlir::LogicalResult
|
mlir::LogicalResult
|
||||||
walkPimCoreBlock(mlir::Block& block,
|
walkPimCoreBlock(mlir::Block& block,
|
||||||
const StaticValueKnowledge& knowledge,
|
const StaticValueKnowledge& initialKnowledge,
|
||||||
llvm::function_ref<mlir::LogicalResult(mlir::Operation&, const StaticValueKnowledge&)> callback) {
|
llvm::function_ref<mlir::LogicalResult(mlir::Operation&, const StaticValueKnowledge&)> callback) {
|
||||||
bool hasFailure = false;
|
bool hasFailure = false;
|
||||||
|
StaticValueKnowledge knowledge = initialKnowledge;
|
||||||
for (mlir::Operation& op : block) {
|
for (mlir::Operation& op : block) {
|
||||||
if (mlir::isa<pim::PimHaltOp, mlir::scf::YieldOp>(op) || isCoreStaticAddressOp(&op))
|
if (mlir::isa<pim::PimHaltOp, mlir::scf::YieldOp>(op) || isCoreStaticAddressOp(&op))
|
||||||
continue;
|
continue;
|
||||||
@@ -74,6 +77,42 @@ walkPimCoreBlock(mlir::Block& block,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 codegen");
|
||||||
|
hasFailure = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
mlir::Region& selectedRegion = *condition != 0 ? ifOp.getThenRegion() : ifOp.getElseRegion();
|
||||||
|
if (!selectedRegion.empty())
|
||||||
|
if (failed(walkPimCoreBlock(selectedRegion.front(), knowledge, callback)))
|
||||||
|
hasFailure = true;
|
||||||
|
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 codegen");
|
||||||
|
hasFailure = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
mlir::Region* selected = &switchOp.getDefaultRegion();
|
||||||
|
for (auto [caseValue, caseRegion] : llvm::zip(switchOp.getCases(), switchOp.getCaseRegions()))
|
||||||
|
if (caseValue == *selector) {
|
||||||
|
selected = &caseRegion;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (failed(walkPimCoreBlock(selected->front(), knowledge, callback)))
|
||||||
|
hasFailure = true;
|
||||||
|
auto yield = mlir::cast<mlir::scf::YieldOp>(selected->front().getTerminator());
|
||||||
|
for (auto [result, yielded] : llvm::zip(switchOp.getResults(), yield.getOperands()))
|
||||||
|
knowledge.aliases[result] = resolveLoopCarriedAlias(yielded, knowledge);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (failed(callback(op, knowledge)))
|
if (failed(callback(op, knowledge)))
|
||||||
hasFailure = true;
|
hasFailure = true;
|
||||||
}
|
}
|
||||||
@@ -82,9 +121,10 @@ walkPimCoreBlock(mlir::Block& block,
|
|||||||
|
|
||||||
mlir::LogicalResult walkPimCoreBlockStructurally(
|
mlir::LogicalResult walkPimCoreBlockStructurally(
|
||||||
mlir::Block& block,
|
mlir::Block& block,
|
||||||
const StaticValueKnowledge& knowledge,
|
const StaticValueKnowledge& initialKnowledge,
|
||||||
llvm::function_ref<mlir::LogicalResult(mlir::Operation&, const StaticValueKnowledge&)> callback) {
|
llvm::function_ref<mlir::LogicalResult(mlir::Operation&, const StaticValueKnowledge&)> callback) {
|
||||||
bool hasFailure = false;
|
bool hasFailure = false;
|
||||||
|
StaticValueKnowledge knowledge = initialKnowledge;
|
||||||
for (mlir::Operation& op : block) {
|
for (mlir::Operation& op : block) {
|
||||||
if (mlir::isa<pim::PimHaltOp, mlir::scf::YieldOp>(op) || isCoreStaticAddressOp(&op))
|
if (mlir::isa<pim::PimHaltOp, mlir::scf::YieldOp>(op) || isCoreStaticAddressOp(&op))
|
||||||
continue;
|
continue;
|
||||||
@@ -128,6 +168,44 @@ mlir::LogicalResult walkPimCoreBlockStructurally(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auto ifOp = mlir::dyn_cast<mlir::scf::IfOp>(op)) {
|
||||||
|
if (failed(resolveIndexValue(ifOp.getCondition(), knowledge))) {
|
||||||
|
ifOp.emitOpError("requires statically evaluable scf.if condition for PIM verification");
|
||||||
|
hasFailure = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ifOp.getThenRegion().empty())
|
||||||
|
if (failed(walkPimCoreBlockStructurally(ifOp.getThenRegion().front(), knowledge, callback)))
|
||||||
|
hasFailure = true;
|
||||||
|
if (!ifOp.getElseRegion().empty())
|
||||||
|
if (failed(walkPimCoreBlockStructurally(ifOp.getElseRegion().front(), knowledge, callback)))
|
||||||
|
hasFailure = true;
|
||||||
|
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 verification");
|
||||||
|
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 (failed(walkPimCoreBlockStructurally(region.front(), knowledge, callback)))
|
||||||
|
hasFailure = true;
|
||||||
|
auto yield = mlir::cast<mlir::scf::YieldOp>(selected->front().getTerminator());
|
||||||
|
for (auto [result, yielded] : llvm::zip(switchOp.getResults(), yield.getOperands()))
|
||||||
|
knowledge.aliases[result] = resolveLoopCarriedAlias(yielded, knowledge);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (failed(callback(op, knowledge)))
|
if (failed(callback(op, knowledge)))
|
||||||
hasFailure = true;
|
hasFailure = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
.getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
Value extractStaticSliceOrIdentity(RewriterBase& rewriter,
|
Value extractStaticSliceOrIdentity(OpBuilder& rewriter,
|
||||||
Location loc,
|
Location loc,
|
||||||
Value source,
|
Value source,
|
||||||
RankedTensorType resultType,
|
RankedTensorType resultType,
|
||||||
@@ -52,7 +52,8 @@ Value extractStaticSliceOrIdentity(RewriterBase& rewriter,
|
|||||||
if (isIdentitySlice)
|
if (isIdentitySlice)
|
||||||
return source;
|
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(
|
Value insertStaticSlice(
|
||||||
@@ -68,4 +69,56 @@ Value insertStaticSlice(
|
|||||||
.getResult();
|
.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) {
|
||||||
|
return tensor::InsertSliceOp::create(builder, loc, source, dest,
|
||||||
|
geometry.offsets, geometry.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
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -8,10 +8,16 @@
|
|||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
|
|
||||||
|
struct MixedSliceGeometry {
|
||||||
|
llvm::SmallVector<mlir::OpFoldResult> offsets;
|
||||||
|
llvm::SmallVector<mlir::OpFoldResult> sizes;
|
||||||
|
llvm::SmallVector<mlir::OpFoldResult> strides;
|
||||||
|
};
|
||||||
|
|
||||||
mlir::Value extractAxisSlice(
|
mlir::Value extractAxisSlice(
|
||||||
mlir::PatternRewriter& rewriter, mlir::Location loc, mlir::Value source, int64_t axis, int64_t offset, int64_t size);
|
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::Location loc,
|
||||||
mlir::Value source,
|
mlir::Value source,
|
||||||
mlir::RankedTensorType resultType,
|
mlir::RankedTensorType resultType,
|
||||||
@@ -25,4 +31,22 @@ mlir::Value insertStaticSlice(mlir::PatternRewriter& rewriter,
|
|||||||
mlir::Value dest,
|
mlir::Value dest,
|
||||||
llvm::ArrayRef<mlir::OpFoldResult> offsets);
|
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
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -7,18 +7,26 @@
|
|||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
|
|
||||||
void dumpModule(mlir::ModuleOp moduleOp, const std::string& name) {
|
std::fstream openDialectDumpFileWithExtension(const std::string& name, llvm::StringRef destination, llvm::StringRef extension) {
|
||||||
std::string outputDir = getOutputDir();
|
std::string outputDir = getOutputDir();
|
||||||
if (outputDir.empty())
|
if (outputDir.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::string dialectsDir = (outputDir + destination).str();
|
||||||
|
createDirectory(dialectsDir);
|
||||||
|
return std::fstream(dialectsDir + "/" + name + "." + extension.str(), std::ios::out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dumpModule(mlir::ModuleOp moduleOp, const std::string& name, bool assumeVerified) {
|
||||||
|
std::fstream file = openDialectDumpFileWithExtension(name, "/dialects", "mlir");
|
||||||
|
if (!file.is_open())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::string dialectsDir = outputDir + "/dialects";
|
|
||||||
createDirectory(dialectsDir);
|
|
||||||
|
|
||||||
std::fstream file(dialectsDir + "/" + name + ".mlir", std::ios::out);
|
|
||||||
llvm::raw_os_ostream os(file);
|
llvm::raw_os_ostream os(file);
|
||||||
mlir::OpPrintingFlags flags;
|
mlir::OpPrintingFlags flags;
|
||||||
flags.elideLargeElementsAttrs().enableDebugInfo(true, false);
|
flags.elideLargeElementsAttrs().enableDebugInfo(false, false);
|
||||||
|
if (assumeVerified)
|
||||||
|
flags.assumeVerified();
|
||||||
moduleOp.print(os, flags);
|
moduleOp.print(os, flags);
|
||||||
os.flush();
|
os.flush();
|
||||||
file.close();
|
file.close();
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "mlir/IR/BuiltinOps.h"
|
#include "mlir/IR/BuiltinOps.h"
|
||||||
|
#include "llvm/ADT/StringRef.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
|
|
||||||
/// Emits a MLIR snapshot under the current compiler output
|
/// Emits a MLIR snapshot under the current compiler output
|
||||||
/// directory for pass-level debugging.
|
/// 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");
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ add_pim_library(OMPimCompilerUtils
|
|||||||
${PIM_COMPILER_INCLUDE_DIRS}
|
${PIM_COMPILER_INCLUDE_DIRS}
|
||||||
|
|
||||||
LINK_LIBS PUBLIC
|
LINK_LIBS PUBLIC
|
||||||
|
MLIRAffineToStandard
|
||||||
OMPimCompilerOptions
|
OMPimCompilerOptions
|
||||||
OMPimCommon
|
OMPimCommon
|
||||||
OMPimBufferization
|
OMPimBufferization
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ void PimMemory::allocateHost(ModuleOp moduleOp, func::FuncOp funcOp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PimMemory::allocateCore(Operation* op, std::optional<unsigned> lane) {
|
void PimMemory::allocateCore(Operation* op, std::optional<unsigned> lane) {
|
||||||
auto intervals = buildLocalAllocIntervals(op, lane);
|
auto intervals = buildLocalAllocIntervals(op, lane, pimMemoryReport == PimMemoryReportFull);
|
||||||
SmallVector<PlannedPhysicalSlot> plannedSlots = planPhysicalSlots(intervals);
|
SmallVector<PlannedPhysicalSlot> plannedSlots = planPhysicalSlots(intervals);
|
||||||
|
|
||||||
SmallVector<size_t> slotOrder(plannedSlots.size());
|
SmallVector<size_t> slotOrder(plannedSlots.size());
|
||||||
@@ -414,6 +414,9 @@ size_t PimAcceleratorMemory::getValueAddress(mlir::Value value,
|
|||||||
const StaticValueKnowledge& knowledge,
|
const StaticValueKnowledge& knowledge,
|
||||||
std::optional<unsigned> lane) const {
|
std::optional<unsigned> lane) const {
|
||||||
value = resolveCachedAlias(value, knowledge);
|
value = resolveCachedAlias(value, knowledge);
|
||||||
|
|
||||||
|
FailureOr<ResolvedContiguousAddress> resolvedAddress = resolveContiguousAddress(value, knowledge);
|
||||||
|
if (failed(resolvedAddress)) {
|
||||||
auto compiledIt = compiledAddressExprs.find(value);
|
auto compiledIt = compiledAddressExprs.find(value);
|
||||||
if (compiledIt == compiledAddressExprs.end()) {
|
if (compiledIt == compiledAddressExprs.end()) {
|
||||||
auto compiledExpr = compileContiguousAddressExpr(value);
|
auto compiledExpr = compileContiguousAddressExpr(value);
|
||||||
@@ -427,7 +430,7 @@ size_t PimAcceleratorMemory::getValueAddress(mlir::Value value,
|
|||||||
compiledIt = compiledAddressExprs.try_emplace(value, *compiledExpr).first;
|
compiledIt = compiledAddressExprs.try_emplace(value, *compiledExpr).first;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto resolvedAddress = compiledIt->second.evaluate(knowledge, lane);
|
resolvedAddress = compiledIt->second.evaluate(knowledge, lane);
|
||||||
if (failed(resolvedAddress)) {
|
if (failed(resolvedAddress)) {
|
||||||
errs() << "Failed to evaluate contiguous address for value: ";
|
errs() << "Failed to evaluate contiguous address for value: ";
|
||||||
value.print(errs());
|
value.print(errs());
|
||||||
@@ -440,6 +443,7 @@ size_t PimAcceleratorMemory::getValueAddress(mlir::Value value,
|
|||||||
}
|
}
|
||||||
llvm_unreachable("Failed to resolve contiguous address");
|
llvm_unreachable("Failed to resolve contiguous address");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MemoryValueKey key = getMemoryValueKey(resolvedAddress->base, lane);
|
MemoryValueKey key = getMemoryValueKey(resolvedAddress->base, lane);
|
||||||
auto iter = memEntriesMap.find(key);
|
auto iter = memEntriesMap.find(key);
|
||||||
@@ -1114,7 +1118,9 @@ enum class CompiledCoreOpKind : uint8_t {
|
|||||||
struct CompiledCoreNode {
|
struct CompiledCoreNode {
|
||||||
enum class Kind : uint8_t {
|
enum class Kind : uint8_t {
|
||||||
Op,
|
Op,
|
||||||
Loop
|
Loop,
|
||||||
|
If,
|
||||||
|
IndexSwitch
|
||||||
};
|
};
|
||||||
|
|
||||||
Kind kind = Kind::Op;
|
Kind kind = Kind::Op;
|
||||||
@@ -1123,7 +1129,13 @@ struct CompiledCoreNode {
|
|||||||
CompiledIndexExpr lowerBound;
|
CompiledIndexExpr lowerBound;
|
||||||
CompiledIndexExpr upperBound;
|
CompiledIndexExpr upperBound;
|
||||||
CompiledIndexExpr step;
|
CompiledIndexExpr step;
|
||||||
|
CompiledIndexExpr condition;
|
||||||
std::unique_ptr<llvm::SmallVector<CompiledCoreNode, 8>> loopBody;
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
static FailureOr<CompiledCoreOpKind> classifyCompiledCoreOpKind(Operation& op) {
|
static FailureOr<CompiledCoreOpKind> classifyCompiledCoreOpKind(Operation& op) {
|
||||||
@@ -1201,6 +1213,53 @@ compileCoreEmissionPlan(Block& block, Operation* weightOwner, llvm::SmallVectorI
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auto ifOp = dyn_cast<mlir::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 ifNode;
|
||||||
|
ifNode.kind = CompiledCoreNode::Kind::If;
|
||||||
|
ifNode.op = ifOp.getOperation();
|
||||||
|
ifNode.condition = *condition;
|
||||||
|
ifNode.thenBody = std::make_unique<llvm::SmallVector<CompiledCoreNode, 8>>();
|
||||||
|
if (failed(compileCoreEmissionPlan(ifOp.getThenRegion().front(), weightOwner, *ifNode.thenBody)))
|
||||||
|
return failure();
|
||||||
|
ifNode.elseBody = std::make_unique<llvm::SmallVector<CompiledCoreNode, 8>>();
|
||||||
|
if (!ifOp.getElseRegion().empty())
|
||||||
|
if (failed(compileCoreEmissionPlan(ifOp.getElseRegion().front(), weightOwner, *ifNode.elseBody)))
|
||||||
|
return failure();
|
||||||
|
plan.push_back(std::move(ifNode));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto switchOp = dyn_cast<mlir::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 switchNode;
|
||||||
|
switchNode.kind = CompiledCoreNode::Kind::IndexSwitch;
|
||||||
|
switchNode.op = switchOp.getOperation();
|
||||||
|
switchNode.condition = *selector;
|
||||||
|
llvm::append_range(switchNode.caseValues, switchOp.getCases());
|
||||||
|
for (mlir::Region& region : switchOp.getCaseRegions()) {
|
||||||
|
auto body = std::make_unique<llvm::SmallVector<CompiledCoreNode, 8>>();
|
||||||
|
if (failed(compileCoreEmissionPlan(region.front(), weightOwner, *body)))
|
||||||
|
return failure();
|
||||||
|
switchNode.caseBodies.push_back(std::move(body));
|
||||||
|
}
|
||||||
|
switchNode.defaultBody = std::make_unique<llvm::SmallVector<CompiledCoreNode, 8>>();
|
||||||
|
if (failed(compileCoreEmissionPlan(
|
||||||
|
switchOp.getDefaultRegion().front(), weightOwner, *switchNode.defaultBody)))
|
||||||
|
return failure();
|
||||||
|
plan.push_back(std::move(switchNode));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
auto opKind = classifyCompiledCoreOpKind(op);
|
auto opKind = classifyCompiledCoreOpKind(op);
|
||||||
if (failed(opKind)) {
|
if (failed(opKind)) {
|
||||||
InFlightDiagnostic diag = op.emitError() << "unsupported codegen for op '" << op.getName().getStringRef() << "'";
|
InFlightDiagnostic diag = op.emitError() << "unsupported codegen for op '" << op.getName().getStringRef() << "'";
|
||||||
@@ -1263,6 +1322,51 @@ static LogicalResult executeCompiledCorePlan(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node.kind == CompiledCoreNode::Kind::If) {
|
||||||
|
auto condition = node.condition.evaluate(knowledge);
|
||||||
|
auto ifOp = cast<mlir::scf::IfOp>(node.op);
|
||||||
|
if (failed(condition)) {
|
||||||
|
ifOp.emitOpError("requires statically evaluable scf.if condition for PIM codegen");
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& selectedBody = *condition != 0 ? node.thenBody : node.elseBody;
|
||||||
|
if (selectedBody && failed(executeCompiledCorePlan(*selectedBody,
|
||||||
|
coreCodeGen,
|
||||||
|
knowledge,
|
||||||
|
resolveWeightSlot,
|
||||||
|
processedOperations,
|
||||||
|
batchLane,
|
||||||
|
batchLaneCount)))
|
||||||
|
return failure();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.kind == CompiledCoreNode::Kind::IndexSwitch) {
|
||||||
|
auto selector = node.condition.evaluate(knowledge);
|
||||||
|
auto switchOp = cast<mlir::scf::IndexSwitchOp>(node.op);
|
||||||
|
if (failed(selector)) {
|
||||||
|
switchOp.emitOpError("requires a statically evaluable scf.index_switch selector for PIM codegen");
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
const llvm::SmallVectorImpl<CompiledCoreNode>* selectedBody = node.defaultBody.get();
|
||||||
|
mlir::Region* selectedRegion = &switchOp.getDefaultRegion();
|
||||||
|
for (auto [index, caseValue] : llvm::enumerate(node.caseValues))
|
||||||
|
if (caseValue == *selector) {
|
||||||
|
selectedBody = node.caseBodies[index].get();
|
||||||
|
selectedRegion = &switchOp.getCaseRegions()[index];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (failed(executeCompiledCorePlan(*selectedBody, coreCodeGen, knowledge,
|
||||||
|
resolveWeightSlot, processedOperations,
|
||||||
|
batchLane, batchLaneCount)))
|
||||||
|
return failure();
|
||||||
|
auto yield = cast<mlir::scf::YieldOp>(selectedRegion->front().getTerminator());
|
||||||
|
for (auto [result, yielded] : llvm::zip(switchOp.getResults(), yield.getOperands()))
|
||||||
|
knowledge.aliases[result] = resolveLoopCarriedAlias(yielded, knowledge);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
switch (node.opKind) {
|
switch (node.opKind) {
|
||||||
case CompiledCoreOpKind::Load:
|
case CompiledCoreOpKind::Load:
|
||||||
coreCodeGen.codeGenLoadOp(cast<pim::PimMemCopyHostToDevOp>(node.op), knowledge);
|
coreCodeGen.codeGenLoadOp(cast<pim::PimMemCopyHostToDevOp>(node.op), knowledge);
|
||||||
@@ -1363,6 +1467,36 @@ static int64_t codeGenCoreOps(
|
|||||||
return failed(result) ? -1 : static_cast<int64_t>(processedOperations);
|
return failed(result) ? -1 : static_cast<int64_t>(processedOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static OnnxMlirCompilerErrorCodes emitEmptyCoreArtifacts(StringRef outputDirPath, size_t emittedCoreId) {
|
||||||
|
std::string outputCorePath =
|
||||||
|
(outputDirPath + "/core_" + std::to_string(emittedCoreId) + ".pim").str();
|
||||||
|
std::error_code errorCode;
|
||||||
|
raw_fd_ostream coreBinaryStream(outputCorePath, errorCode, sys::fs::OF_None);
|
||||||
|
if (errorCode) {
|
||||||
|
errs() << "Error while opening core file `" << outputCorePath << "`: " << errorCode.message() << '\n';
|
||||||
|
return InvalidOutputFileAccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
pim_binary::writeHeader(coreBinaryStream);
|
||||||
|
pim_binary::patchInstructionCount(coreBinaryStream, 0);
|
||||||
|
coreBinaryStream.close();
|
||||||
|
|
||||||
|
if (!pimEmitJson.getValue())
|
||||||
|
return CompilerSuccess;
|
||||||
|
|
||||||
|
std::string outputCoreJsonPath =
|
||||||
|
(outputDirPath + "/core_" + std::to_string(emittedCoreId) + ".json").str();
|
||||||
|
errorCode = std::error_code();
|
||||||
|
raw_fd_ostream coreJsonStream(outputCoreJsonPath, errorCode);
|
||||||
|
if (errorCode) {
|
||||||
|
errs() << "Error while opening core json file `" << outputCoreJsonPath << "`: " << errorCode.message() << '\n';
|
||||||
|
return InvalidOutputFileAccess;
|
||||||
|
}
|
||||||
|
coreJsonStream << "[]";
|
||||||
|
coreJsonStream.close();
|
||||||
|
return CompilerSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::string& outputDirPath) {
|
OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::string& outputDirPath) {
|
||||||
if (!outputDirPath.empty()) {
|
if (!outputDirPath.empty()) {
|
||||||
if (auto error = sys::fs::create_directory(outputDirPath)) {
|
if (auto error = sys::fs::create_directory(outputDirPath)) {
|
||||||
@@ -1607,6 +1741,13 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
|||||||
if (jobResults[jobIndex].status != CompilerSuccess)
|
if (jobResults[jobIndex].status != CompilerSuccess)
|
||||||
return jobResults[jobIndex].status;
|
return jobResults[jobIndex].status;
|
||||||
|
|
||||||
|
if (jobs.empty()) {
|
||||||
|
if (auto err = emitEmptyCoreArtifacts(outputDirPath, 0))
|
||||||
|
return err;
|
||||||
|
xbarsPerArrayGroup["core0"] = json::Array {};
|
||||||
|
memory.recordCoreReport(0, MemoryReportRow {});
|
||||||
|
}
|
||||||
|
|
||||||
llvm::SmallVector<WeightFileRequest, 8> weightRequests;
|
llvm::SmallVector<WeightFileRequest, 8> weightRequests;
|
||||||
weightRequests.reserve(jobs.size());
|
weightRequests.reserve(jobs.size());
|
||||||
for (size_t jobIndex = 0; jobIndex < jobs.size(); ++jobIndex) {
|
for (size_t jobIndex = 0; jobIndex < jobs.size(); ++jobIndex) {
|
||||||
|
|||||||
@@ -15,13 +15,6 @@ llvm::cl::opt<PimEmissionTargetType> pimEmissionTarget(
|
|||||||
llvm::cl::init(EmitPimCodegen),
|
llvm::cl::init(EmitPimCodegen),
|
||||||
llvm::cl::cat(OnnxMlirOptions));
|
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(
|
llvm::cl::opt<PimMemoryReportLevel> pimMemoryReport(
|
||||||
"pim-memory-report",
|
"pim-memory-report",
|
||||||
llvm::cl::desc("Emit a human-readable PIM memory planning report"),
|
llvm::cl::desc("Emit a human-readable PIM memory planning report"),
|
||||||
@@ -57,6 +50,22 @@ llvm::cl::opt<PimConvLoweringType> pimConvLowering(
|
|||||||
llvm::cl::init(PimConvLoweringAuto),
|
llvm::cl::init(PimConvLoweringAuto),
|
||||||
llvm::cl::cat(OnnxMlirOptions));
|
llvm::cl::cat(OnnxMlirOptions));
|
||||||
|
|
||||||
|
llvm::cl::opt<PimSpatialDataflowExportType> pimExportSpatialDataflow(
|
||||||
|
"pim-export-spatial-dataflow",
|
||||||
|
llvm::cl::desc("Emit Gephi-importable CSV dataflow reports for Spatial pipeline snapshots"),
|
||||||
|
llvm::cl::values(clEnumValN(SpatialDataflowExportNone, "none", "Do not emit Spatial dataflow CSV reports")),
|
||||||
|
llvm::cl::values(
|
||||||
|
clEnumValN(SpatialDataflowExportSpatial1, "spatial1", "Emit spatial1 graph dataflow CSV reports")),
|
||||||
|
llvm::cl::values(
|
||||||
|
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));
|
||||||
|
|
||||||
llvm::cl::opt<bool>
|
llvm::cl::opt<bool>
|
||||||
pimOnlyCodegen("pim-only-codegen",
|
pimOnlyCodegen("pim-only-codegen",
|
||||||
llvm::cl::desc("Only generate code for PIM (assume input is already in bufferized PIM IR)"),
|
llvm::cl::desc("Only generate code for PIM (assume input is already in bufferized PIM IR)"),
|
||||||
|
|||||||
@@ -20,10 +20,6 @@ typedef enum {
|
|||||||
EmitPimCodegen = 3
|
EmitPimCodegen = 3
|
||||||
} PimEmissionTargetType;
|
} PimEmissionTargetType;
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
MergeSchedulerPeft = 0,
|
|
||||||
} PimMergeSchedulerType;
|
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
PimMemoryReportNone = 0,
|
PimMemoryReportNone = 0,
|
||||||
PimMemoryReportSummary = 1,
|
PimMemoryReportSummary = 1,
|
||||||
@@ -42,11 +38,20 @@ typedef enum {
|
|||||||
PimConvLoweringTiled2D = 8,
|
PimConvLoweringTiled2D = 8,
|
||||||
} PimConvLoweringType;
|
} PimConvLoweringType;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SpatialDataflowExportNone = 0,
|
||||||
|
SpatialDataflowExportSpatial1 = 1,
|
||||||
|
SpatialDataflowExportSpatial2 = 2,
|
||||||
|
SpatialDataflowExportSpatial3 = 3,
|
||||||
|
SpatialDataflowExportSpatial4 = 4,
|
||||||
|
SpatialDataflowExportAll = 5,
|
||||||
|
} PimSpatialDataflowExportType;
|
||||||
|
|
||||||
extern llvm::cl::OptionCategory OnnxMlirOptions;
|
extern llvm::cl::OptionCategory OnnxMlirOptions;
|
||||||
extern llvm::cl::opt<PimEmissionTargetType> pimEmissionTarget;
|
extern llvm::cl::opt<PimEmissionTargetType> pimEmissionTarget;
|
||||||
extern llvm::cl::opt<PimMergeSchedulerType> pimMergeScheduler;
|
|
||||||
extern llvm::cl::opt<PimMemoryReportLevel> pimMemoryReport;
|
extern llvm::cl::opt<PimMemoryReportLevel> pimMemoryReport;
|
||||||
extern llvm::cl::opt<PimConvLoweringType> pimConvLowering;
|
extern llvm::cl::opt<PimConvLoweringType> pimConvLowering;
|
||||||
|
extern llvm::cl::opt<PimSpatialDataflowExportType> pimExportSpatialDataflow;
|
||||||
|
|
||||||
extern llvm::cl::opt<bool> pimOnlyCodegen;
|
extern llvm::cl::opt<bool> pimOnlyCodegen;
|
||||||
extern llvm::cl::opt<bool> pimDisableMemoryCoalescing;
|
extern llvm::cl::opt<bool> pimDisableMemoryCoalescing;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#include "mlir/Conversion/AffineToStandard/AffineToStandard.h"
|
||||||
#include "mlir/Transforms/Passes.h"
|
#include "mlir/Transforms/Passes.h"
|
||||||
|
|
||||||
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
||||||
@@ -31,6 +32,7 @@ void addPassesPim(OwningOpRef<ModuleOp>& module,
|
|||||||
pm.addPass(createONNXToSpatialPass());
|
pm.addPass(createONNXToSpatialPass());
|
||||||
pm.addPass(createSpatialLayoutPlanningPass());
|
pm.addPass(createSpatialLayoutPlanningPass());
|
||||||
pm.addPass(createLowerSpatialPlansPass());
|
pm.addPass(createLowerSpatialPlansPass());
|
||||||
|
pm.addPass(createTrivialGraphComputeMergePass());
|
||||||
pm.addPass(createMergeComputeNodesPass());
|
pm.addPass(createMergeComputeNodesPass());
|
||||||
pm.addPass(createMessagePass("Onnx lowered to Spatial"));
|
pm.addPass(createMessagePass("Onnx lowered to Spatial"));
|
||||||
}
|
}
|
||||||
@@ -46,6 +48,7 @@ void addPassesPim(OwningOpRef<ModuleOp>& module,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (pimEmissionTarget >= EmitPimCodegen) {
|
if (pimEmissionTarget >= EmitPimCodegen) {
|
||||||
|
pm.addPass(mlir::createLowerAffinePass());
|
||||||
pm.addPass(createPimHostConstantFoldingPass());
|
pm.addPass(createPimHostConstantFoldingPass());
|
||||||
pm.addPass(createMessagePass("Pim host constants folded"));
|
pm.addPass(createMessagePass("Pim host constants folded"));
|
||||||
if (!pimDisableMemoryCoalescing)
|
if (!pimDisableMemoryCoalescing)
|
||||||
|
|||||||
@@ -154,7 +154,10 @@ static OperationOrdering buildOperationOrdering(Operation* coreLikeOp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool isSupportedAliasOp(Operation* op) {
|
static bool isSupportedAliasOp(Operation* op) {
|
||||||
return isa<memref::SubViewOp, memref::CastOp, memref::CollapseShapeOp, memref::ExpandShapeOp>(op);
|
return isa<memref::SubViewOp,
|
||||||
|
memref::CastOp,
|
||||||
|
memref::CollapseShapeOp,
|
||||||
|
memref::ExpandShapeOp>(op);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isRuntimeMemoryTouchOp(Operation* op) {
|
static bool isRuntimeMemoryTouchOp(Operation* op) {
|
||||||
@@ -237,12 +240,19 @@ getEffectiveTouchRange(mlir::Value definingValue, Operation* user, const Operati
|
|||||||
}
|
}
|
||||||
|
|
||||||
static MemoryTouchInterval
|
static MemoryTouchInterval
|
||||||
computeMemoryTouchInterval(memref::AllocOp allocOp, const OperationOrdering& ordering, uint64_t fallbackEnd) {
|
computeMemoryTouchInterval(memref::AllocOp allocOp,
|
||||||
|
const OperationOrdering& ordering,
|
||||||
|
uint64_t fallbackEnd,
|
||||||
|
bool includeAliasDescriptions) {
|
||||||
MemoryTouchInterval interval;
|
MemoryTouchInterval interval;
|
||||||
interval.start = ordering.position.lookup(allocOp);
|
interval.start = ordering.position.lookup(allocOp);
|
||||||
interval.end = interval.start;
|
interval.end = interval.start;
|
||||||
interval.startOp = allocOp;
|
interval.startOp = allocOp;
|
||||||
interval.endOp = allocOp;
|
interval.endOp = allocOp;
|
||||||
|
auto recordAlias = [&](mlir::Value value) {
|
||||||
|
if (includeAliasDescriptions)
|
||||||
|
appendAliasDescription(interval.aliasesFollowed, value);
|
||||||
|
};
|
||||||
|
|
||||||
SmallPtrSet<mlir::Value, 16> visitedValues;
|
SmallPtrSet<mlir::Value, 16> visitedValues;
|
||||||
SmallPtrSet<Operation*, 32> visitedUsers;
|
SmallPtrSet<Operation*, 32> visitedUsers;
|
||||||
@@ -262,7 +272,7 @@ computeMemoryTouchInterval(memref::AllocOp allocOp, const OperationOrdering& ord
|
|||||||
if (isSupportedAliasOp(user)) {
|
if (isSupportedAliasOp(user)) {
|
||||||
for (mlir::Value result : user->getResults()) {
|
for (mlir::Value result : user->getResults()) {
|
||||||
pendingValues.push_back(result);
|
pendingValues.push_back(result);
|
||||||
appendAliasDescription(interval.aliasesFollowed, result);
|
recordAlias(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,7 +282,7 @@ computeMemoryTouchInterval(memref::AllocOp allocOp, const OperationOrdering& ord
|
|||||||
if (!tiedOperand || tiedOperand->get() != value)
|
if (!tiedOperand || tiedOperand->get() != value)
|
||||||
continue;
|
continue;
|
||||||
pendingValues.push_back(result);
|
pendingValues.push_back(result);
|
||||||
appendAliasDescription(interval.aliasesFollowed, result);
|
recordAlias(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,8 +292,8 @@ computeMemoryTouchInterval(memref::AllocOp allocOp, const OperationOrdering& ord
|
|||||||
continue;
|
continue;
|
||||||
pendingValues.push_back(forOp.getRegionIterArgs()[index]);
|
pendingValues.push_back(forOp.getRegionIterArgs()[index]);
|
||||||
pendingValues.push_back(forOp.getResult(index));
|
pendingValues.push_back(forOp.getResult(index));
|
||||||
appendAliasDescription(interval.aliasesFollowed, forOp.getRegionIterArgs()[index]);
|
recordAlias(forOp.getRegionIterArgs()[index]);
|
||||||
appendAliasDescription(interval.aliasesFollowed, forOp.getResult(index));
|
recordAlias(forOp.getResult(index));
|
||||||
if (parentLoop && forOp != parentLoop)
|
if (parentLoop && forOp != parentLoop)
|
||||||
interval.escapesLoop = true;
|
interval.escapesLoop = true;
|
||||||
}
|
}
|
||||||
@@ -291,7 +301,25 @@ computeMemoryTouchInterval(memref::AllocOp allocOp, const OperationOrdering& ord
|
|||||||
|
|
||||||
if (auto yieldOp = dyn_cast<scf::YieldOp>(user)) {
|
if (auto yieldOp = dyn_cast<scf::YieldOp>(user)) {
|
||||||
auto forOp = dyn_cast<scf::ForOp>(yieldOp->getParentOp());
|
auto forOp = dyn_cast<scf::ForOp>(yieldOp->getParentOp());
|
||||||
if (!forOp) {
|
auto ifOp = dyn_cast<scf::IfOp>(yieldOp->getParentOp());
|
||||||
|
auto indexSwitch = dyn_cast<scf::IndexSwitchOp>(yieldOp->getParentOp());
|
||||||
|
if (ifOp) {
|
||||||
|
for (auto [index, operand] : llvm::enumerate(yieldOp.getOperands())) {
|
||||||
|
if (operand != value)
|
||||||
|
continue;
|
||||||
|
pendingValues.push_back(ifOp.getResult(index));
|
||||||
|
recordAlias(ifOp.getResult(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (indexSwitch) {
|
||||||
|
for (auto [index, operand] : llvm::enumerate(yieldOp.getOperands())) {
|
||||||
|
if (operand != value)
|
||||||
|
continue;
|
||||||
|
pendingValues.push_back(indexSwitch.getResult(index));
|
||||||
|
recordAlias(indexSwitch.getResult(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!forOp) {
|
||||||
addFallbackReason(interval.fallbackReason, "yield without scf.for parent");
|
addFallbackReason(interval.fallbackReason, "yield without scf.for parent");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -299,7 +327,7 @@ computeMemoryTouchInterval(memref::AllocOp allocOp, const OperationOrdering& ord
|
|||||||
if (operand != value)
|
if (operand != value)
|
||||||
continue;
|
continue;
|
||||||
pendingValues.push_back(forOp.getResult(index));
|
pendingValues.push_back(forOp.getResult(index));
|
||||||
appendAliasDescription(interval.aliasesFollowed, forOp.getResult(index));
|
recordAlias(forOp.getResult(index));
|
||||||
if (parentLoop && forOp == parentLoop)
|
if (parentLoop && forOp == parentLoop)
|
||||||
interval.escapesLoop = true;
|
interval.escapesLoop = true;
|
||||||
}
|
}
|
||||||
@@ -392,7 +420,8 @@ static uint64_t getSlotLogicalBytes(const PlannedPhysicalSlot& slot, ArrayRef<Lo
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
SmallVector<LocalAllocInterval, 0> onnx_mlir::buildLocalAllocIntervals(Operation* coreLikeOp,
|
SmallVector<LocalAllocInterval, 0> onnx_mlir::buildLocalAllocIntervals(Operation* coreLikeOp,
|
||||||
std::optional<unsigned> lane) {
|
std::optional<unsigned> lane,
|
||||||
|
bool includeAliasDescriptions) {
|
||||||
SmallVector<LocalAllocInterval, 0> intervals;
|
SmallVector<LocalAllocInterval, 0> intervals;
|
||||||
OperationOrdering ordering = buildOperationOrdering(coreLikeOp);
|
OperationOrdering ordering = buildOperationOrdering(coreLikeOp);
|
||||||
if (ordering.position.empty())
|
if (ordering.position.empty())
|
||||||
@@ -409,7 +438,8 @@ SmallVector<LocalAllocInterval, 0> onnx_mlir::buildLocalAllocIntervals(Operation
|
|||||||
llvm_unreachable("Failed to compute local allocation size");
|
llvm_unreachable("Failed to compute local allocation size");
|
||||||
}
|
}
|
||||||
|
|
||||||
MemoryTouchInterval touchInterval = computeMemoryTouchInterval(allocOp, ordering, fallbackEnd);
|
MemoryTouchInterval touchInterval =
|
||||||
|
computeMemoryTouchInterval(allocOp, ordering, fallbackEnd, includeAliasDescriptions);
|
||||||
LocalAllocInterval interval;
|
LocalAllocInterval interval;
|
||||||
interval.id = nextIntervalId++;
|
interval.id = nextIntervalId++;
|
||||||
interval.alloc = allocOp;
|
interval.alloc = allocOp;
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ struct PlannedPhysicalSlot {
|
|||||||
};
|
};
|
||||||
|
|
||||||
llvm::SmallVector<LocalAllocInterval, 0> buildLocalAllocIntervals(mlir::Operation* coreLikeOp,
|
llvm::SmallVector<LocalAllocInterval, 0> buildLocalAllocIntervals(mlir::Operation* coreLikeOp,
|
||||||
std::optional<unsigned> lane);
|
std::optional<unsigned> lane,
|
||||||
|
bool includeAliasDescriptions = true);
|
||||||
|
|
||||||
llvm::SmallVector<PlannedPhysicalSlot, 0> planPhysicalSlots(llvm::MutableArrayRef<LocalAllocInterval> intervals);
|
llvm::SmallVector<PlannedPhysicalSlot, 0> planPhysicalSlots(llvm::MutableArrayRef<LocalAllocInterval> intervals);
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
add_subdirectory(ONNXToSpatial)
|
add_subdirectory(ONNXToSpatial)
|
||||||
add_subdirectory(SpatialToGraphviz)
|
|
||||||
add_subdirectory(SpatialToPim)
|
add_subdirectory(SpatialToPim)
|
||||||
@@ -20,6 +20,7 @@ add_pim_library(OMONNXToSpatial
|
|||||||
Patterns/NN/Sigmoid.cpp
|
Patterns/NN/Sigmoid.cpp
|
||||||
Patterns/NN/Softmax.cpp
|
Patterns/NN/Softmax.cpp
|
||||||
Patterns/Tensor/Concat.cpp
|
Patterns/Tensor/Concat.cpp
|
||||||
|
Patterns/Tensor/Flatten.cpp
|
||||||
Patterns/Tensor/Gather.cpp
|
Patterns/Tensor/Gather.cpp
|
||||||
Patterns/Tensor/Resize.cpp
|
Patterns/Tensor/Resize.cpp
|
||||||
Patterns/Tensor/Reshape.cpp
|
Patterns/Tensor/Reshape.cpp
|
||||||
@@ -30,8 +31,10 @@ add_pim_library(OMONNXToSpatial
|
|||||||
SpatialLayoutPlanningPass.cpp
|
SpatialLayoutPlanningPass.cpp
|
||||||
LowerSpatialPlansPass.cpp
|
LowerSpatialPlansPass.cpp
|
||||||
Common/AttributeUtils.cpp
|
Common/AttributeUtils.cpp
|
||||||
|
Common/BiasAddUtils.cpp
|
||||||
Common/ComputeRegionBuilder.cpp
|
Common/ComputeRegionBuilder.cpp
|
||||||
Common/MatrixProductLowering.cpp
|
Common/MatrixProductLowering.cpp
|
||||||
|
Common/RowStripLayoutUtils.cpp
|
||||||
Common/ShapeTilingUtils.cpp
|
Common/ShapeTilingUtils.cpp
|
||||||
Common/WeightMaterialization.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 <utility>
|
||||||
|
|
||||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
#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/Conversion/ONNXToSpatial/CompileTime.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
|
|
||||||
@@ -60,6 +61,56 @@ struct SpatComputeBatchBodyArgs {
|
|||||||
mlir::ValueRange outputs;
|
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
|
} // namespace detail
|
||||||
|
|
||||||
template <typename RewriterT>
|
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();
|
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
|
/// Builds a `spat.graph_compute` with a fixed number of SSA inputs and erases it if
|
||||||
/// the body callback reports failure.
|
/// the body callback reports failure.
|
||||||
template <size_t NumInputs, typename RewriterT, typename BodyFn>
|
template <size_t NumInputs, typename RewriterT, typename BodyFn>
|
||||||
@@ -97,16 +173,8 @@ auto createSpatGraphCompute(RewriterT& rewriter,
|
|||||||
mlir::ValueRange inputs,
|
mlir::ValueRange inputs,
|
||||||
BodyFn&& body) {
|
BodyFn&& body) {
|
||||||
assert(inputs.size() == NumInputs && "NumInputs must match the number of input values");
|
assert(inputs.size() == NumInputs && "NumInputs must match the number of input values");
|
||||||
auto computeOp = spatial::SpatGraphCompute::create(rewriter, loc, resultTypes, weights, inputs);
|
auto computeOp = createEmptySpatGraphCompute(rewriter, loc, resultTypes, weights, inputs);
|
||||||
|
auto* block = &computeOp.getBody().front();
|
||||||
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);
|
|
||||||
|
|
||||||
using BodyResult = detail::InvokeWithBlockArgsResultT<std::decay_t<BodyFn>, std::make_index_sequence<NumInputs>>;
|
using BodyResult = detail::InvokeWithBlockArgsResultT<std::decay_t<BodyFn>, std::make_index_sequence<NumInputs>>;
|
||||||
if constexpr (std::is_same_v<BodyResult, void>) {
|
if constexpr (std::is_same_v<BodyResult, void>) {
|
||||||
@@ -140,16 +208,8 @@ auto createSpatGraphCompute(RewriterT& rewriter,
|
|||||||
mlir::ValueRange weights,
|
mlir::ValueRange weights,
|
||||||
mlir::ValueRange inputs,
|
mlir::ValueRange inputs,
|
||||||
BodyFn&& body) {
|
BodyFn&& body) {
|
||||||
auto computeOp = spatial::SpatGraphCompute::create(rewriter, loc, resultTypes, weights, inputs);
|
auto computeOp = createEmptySpatGraphCompute(rewriter, loc, resultTypes, weights, inputs);
|
||||||
|
auto* block = &computeOp.getBody().front();
|
||||||
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);
|
|
||||||
|
|
||||||
using BodyResult = detail::InvokeWithValueRangeResultT<std::decay_t<BodyFn>>;
|
using BodyResult = detail::InvokeWithValueRangeResultT<std::decay_t<BodyFn>>;
|
||||||
if constexpr (std::is_same_v<BodyResult, void>) {
|
if constexpr (std::is_same_v<BodyResult, void>) {
|
||||||
@@ -170,14 +230,15 @@ auto createSpatGraphCompute(RewriterT& rewriter,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename RewriterT, typename BodyFn>
|
template <typename RewriterT>
|
||||||
auto createSpatGraphComputeBatch(RewriterT& rewriter,
|
auto createEmptySpatGraphComputeBatch(RewriterT& rewriter,
|
||||||
mlir::Location loc,
|
mlir::Location loc,
|
||||||
mlir::TypeRange resultTypes,
|
mlir::TypeRange resultTypes,
|
||||||
int64_t laneCount,
|
int64_t laneCount,
|
||||||
mlir::ValueRange weights,
|
mlir::ValueRange weights,
|
||||||
mlir::ValueRange inputs,
|
mlir::ValueRange inputs,
|
||||||
BodyFn&& body) {
|
mlir::TypeRange blockArgTypes,
|
||||||
|
llvm::ArrayRef<mlir::Location> blockArgLocs) {
|
||||||
if (laneCount <= 0 || laneCount > std::numeric_limits<int32_t>::max())
|
if (laneCount <= 0 || laneCount > std::numeric_limits<int32_t>::max())
|
||||||
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(mlir::failure());
|
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(mlir::failure());
|
||||||
|
|
||||||
@@ -186,27 +247,36 @@ auto createSpatGraphComputeBatch(RewriterT& rewriter,
|
|||||||
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(mlir::failure());
|
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(mlir::failure());
|
||||||
|
|
||||||
auto batchOp = spatial::SpatGraphComputeBatch::create(rewriter, loc, resultTypes, *laneCountAttr, weights, inputs);
|
auto batchOp = spatial::SpatGraphComputeBatch::create(rewriter, loc, resultTypes, *laneCountAttr, weights, inputs);
|
||||||
|
rewriter.createBlock(&batchOp.getBody(), batchOp.getBody().end(), blockArgTypes, blockArgLocs);
|
||||||
|
rewriter.setInsertionPointToStart(&batchOp.getBody().front());
|
||||||
|
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(batchOp);
|
||||||
|
}
|
||||||
|
|
||||||
mlir::SmallVector<mlir::Type> blockArgTypes {rewriter.getIndexType()};
|
template <typename RewriterT>
|
||||||
mlir::SmallVector<mlir::Location> blockArgLocs {loc};
|
auto createEmptySpatGraphComputeBatch(RewriterT& rewriter,
|
||||||
blockArgTypes.reserve(1 + weights.size() + inputs.size() + resultTypes.size());
|
mlir::Location loc,
|
||||||
blockArgLocs.reserve(1 + weights.size() + inputs.size() + resultTypes.size());
|
mlir::TypeRange resultTypes,
|
||||||
for (mlir::Value weight : weights) {
|
int64_t laneCount,
|
||||||
blockArgTypes.push_back(weight.getType());
|
mlir::ValueRange weights,
|
||||||
blockArgLocs.push_back(weight.getLoc());
|
mlir::ValueRange inputs) {
|
||||||
}
|
auto blockArgTypes = detail::getGraphComputeBatchBlockArgTypes(rewriter, resultTypes, weights, inputs);
|
||||||
for (mlir::Value input : inputs) {
|
auto blockArgLocs = detail::getGraphComputeBatchBlockArgLocs(loc, resultTypes, weights, inputs);
|
||||||
blockArgTypes.push_back(input.getType());
|
return createEmptySpatGraphComputeBatch(
|
||||||
blockArgLocs.push_back(input.getLoc());
|
rewriter, loc, resultTypes, laneCount, weights, inputs, blockArgTypes, blockArgLocs);
|
||||||
}
|
}
|
||||||
for (mlir::Type resultType : resultTypes) {
|
|
||||||
blockArgTypes.push_back(resultType);
|
|
||||||
blockArgLocs.push_back(loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* block =
|
template <typename RewriterT, typename BodyFn>
|
||||||
rewriter.createBlock(&batchOp.getBody(), batchOp.getBody().end(), mlir::TypeRange(blockArgTypes), blockArgLocs);
|
auto createSpatGraphComputeBatch(RewriterT& rewriter,
|
||||||
rewriter.setInsertionPointToStart(block);
|
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 {
|
detail::SpatComputeBatchBodyArgs args {
|
||||||
block->getArgument(0),
|
block->getArgument(0),
|
||||||
@@ -217,18 +287,18 @@ auto createSpatGraphComputeBatch(RewriterT& rewriter,
|
|||||||
using BodyResult = std::invoke_result_t<BodyFn, detail::SpatComputeBatchBodyArgs>;
|
using BodyResult = std::invoke_result_t<BodyFn, detail::SpatComputeBatchBodyArgs>;
|
||||||
if constexpr (std::is_same_v<BodyResult, void>) {
|
if constexpr (std::is_same_v<BodyResult, void>) {
|
||||||
std::forward<BodyFn>(body)(args);
|
std::forward<BodyFn>(body)(args);
|
||||||
rewriter.setInsertionPointAfter(batchOp);
|
rewriter.setInsertionPointAfter(*batchOp);
|
||||||
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(batchOp);
|
return batchOp;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
auto bodyResult = std::forward<BodyFn>(body)(args);
|
auto bodyResult = std::forward<BodyFn>(body)(args);
|
||||||
if (mlir::failed(bodyResult)) {
|
if (mlir::failed(bodyResult)) {
|
||||||
rewriter.setInsertionPointAfter(batchOp);
|
rewriter.setInsertionPointAfter(*batchOp);
|
||||||
rewriter.eraseOp(batchOp);
|
rewriter.eraseOp(*batchOp);
|
||||||
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(mlir::failure());
|
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(mlir::failure());
|
||||||
}
|
}
|
||||||
rewriter.setInsertionPointAfter(batchOp);
|
rewriter.setInsertionPointAfter(*batchOp);
|
||||||
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(batchOp);
|
return batchOp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,6 +347,46 @@ inline void createParallelInsertSliceIntoBatchOutput(mlir::PatternRewriter& rewr
|
|||||||
mlir::tensor::ParallelInsertSliceOp::create(rewriter, loc, source, dest, offsets, sizes, strides);
|
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>
|
template <typename BodyFn>
|
||||||
mlir::Value materializeOrComputeUnary(mlir::Value input,
|
mlir::Value materializeOrComputeUnary(mlir::Value input,
|
||||||
mlir::RankedTensorType resultType,
|
mlir::RankedTensorType resultType,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
|
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
#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"
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
|
|
||||||
using namespace mlir;
|
using namespace mlir;
|
||||||
@@ -38,6 +39,30 @@ Value createPaddedInputCompute(Value input,
|
|||||||
if (inputType == paddedInputType)
|
if (inputType == paddedInputType)
|
||||||
return input;
|
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) {
|
auto computeOp = createSpatCompute<1>(rewriter, loc, TypeRange {paddedInputType}, {}, input, [&](Value computeInput) {
|
||||||
Value paddedInput = createZeroPaddedTensor(computeInput, paddedInputType, rewriter, loc);
|
Value paddedInput = createZeroPaddedTensor(computeInput, paddedInputType, rewriter, loc);
|
||||||
spatial::SpatYieldOp::create(rewriter, loc, paddedInput);
|
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/SmallPtrSet.h"
|
||||||
#include "llvm/ADT/SmallVector.h"
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
|
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.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/Conversion/ONNXToSpatial/CompileTime.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||||
@@ -21,24 +23,7 @@ using namespace mlir;
|
|||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
|
|
||||||
namespace {
|
FailureOr<DenseElementsAttr> transposeDenseElementsAttr(DenseElementsAttr denseAttr, ArrayRef<int64_t> perms) {
|
||||||
|
|
||||||
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) {
|
|
||||||
auto tensorType = dyn_cast<RankedTensorType>(denseAttr.getType());
|
auto tensorType = dyn_cast<RankedTensorType>(denseAttr.getType());
|
||||||
if (!tensorType)
|
if (!tensorType)
|
||||||
return failure();
|
return failure();
|
||||||
@@ -59,7 +44,45 @@ static FailureOr<DenseElementsAttr> transposeDenseElements(DenseElementsAttr den
|
|||||||
|
|
||||||
auto transposedType = RankedTensorType::get(transposedShape, tensorType.getElementType(), tensorType.getEncoding());
|
auto transposedType = RankedTensorType::get(transposedShape, tensorType.getElementType(), tensorType.getEncoding());
|
||||||
if (denseAttr.isSplat())
|
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> originalValues(denseAttr.getValues<Attribute>());
|
||||||
SmallVector<Attribute> transposedValues(originalValues.size());
|
SmallVector<Attribute> transposedValues(originalValues.size());
|
||||||
@@ -84,16 +107,30 @@ static FailureOr<DenseElementsAttr> transposeDenseElements(DenseElementsAttr den
|
|||||||
return DenseElementsAttr::get(transposedType, transposedValues);
|
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) {
|
static FailureOr<DenseElementsAttr> reshapeDenseElements(DenseElementsAttr denseAttr, RankedTensorType resultType) {
|
||||||
auto sourceType = dyn_cast<RankedTensorType>(denseAttr.getType());
|
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();
|
return failure();
|
||||||
|
|
||||||
if (denseAttr.isSplat())
|
return DenseElementsAttr::getFromRawBuffer(resultType, denseAttr.getRawData());
|
||||||
return DenseElementsAttr::get(resultType, denseAttr.getSplatValue<Attribute>());
|
|
||||||
|
|
||||||
SmallVector<Attribute> values(denseAttr.getValues<Attribute>());
|
|
||||||
return DenseElementsAttr::get(resultType, values);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static FailureOr<DenseElementsAttr> extractSliceDenseElements(DenseElementsAttr denseAttr,
|
static FailureOr<DenseElementsAttr> extractSliceDenseElements(DenseElementsAttr denseAttr,
|
||||||
@@ -161,7 +198,7 @@ static DenseElementsAttr getHostConstantDenseElementsAttrImpl(Value value, llvm:
|
|||||||
perm.reserve(transposeOp.getPermAttr().size());
|
perm.reserve(transposeOp.getPermAttr().size());
|
||||||
for (IntegerAttr attr : transposeOp.getPermAttr().getAsRange<IntegerAttr>())
|
for (IntegerAttr attr : transposeOp.getPermAttr().getAsRange<IntegerAttr>())
|
||||||
perm.push_back(attr.getInt());
|
perm.push_back(attr.getInt());
|
||||||
auto transposedAttr = transposeDenseElements(inputAttr, perm);
|
auto transposedAttr = transposeDenseElementsAttr(inputAttr, perm);
|
||||||
return succeeded(transposedAttr) ? *transposedAttr : nullptr;
|
return succeeded(transposedAttr) ? *transposedAttr : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +208,7 @@ static DenseElementsAttr getHostConstantDenseElementsAttrImpl(Value value, llvm:
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
SmallVector<int64_t> perm(transposeOp.getPermutation().begin(), transposeOp.getPermutation().end());
|
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;
|
return succeeded(transposedAttr) ? *transposedAttr : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,6 +256,9 @@ getCompileTimeSourceImpl(Operation* op, llvm::SmallPtrSetImpl<Operation*>& visit
|
|||||||
|
|
||||||
chainLength += 1;
|
chainLength += 1;
|
||||||
|
|
||||||
|
if (!isShapingOnlyOp(op))
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
if (auto extractOp = dyn_cast<tensor::ExtractOp>(op))
|
if (auto extractOp = dyn_cast<tensor::ExtractOp>(op))
|
||||||
return hasConstantIndices(extractOp)
|
return hasConstantIndices(extractOp)
|
||||||
? getCompileTimeSourceImpl(extractOp.getTensor().getDefiningOp(), visited, chainLength)
|
? getCompileTimeSourceImpl(extractOp.getTensor().getDefiningOp(), visited, chainLength)
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
#include "mlir/IR/Operation.h"
|
#include "mlir/IR/Operation.h"
|
||||||
#include "mlir/IR/Value.h"
|
#include "mlir/IR/Value.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/ArrayRef.h"
|
||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
|
|
||||||
struct CompileTimeSource {
|
struct CompileTimeSource {
|
||||||
@@ -19,4 +21,7 @@ bool isCompileTimeOp(mlir::Operation* op);
|
|||||||
|
|
||||||
mlir::DenseElementsAttr getHostConstDenseElementsAttr(mlir::Value value);
|
mlir::DenseElementsAttr getHostConstDenseElementsAttr(mlir::Value value);
|
||||||
|
|
||||||
|
mlir::FailureOr<mlir::DenseElementsAttr> transposeDenseElementsAttr(
|
||||||
|
mlir::DenseElementsAttr denseAttr, llvm::ArrayRef<int64_t> permutation);
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -11,12 +11,16 @@
|
|||||||
#include "llvm/ADT/SmallPtrSet.h"
|
#include "llvm/ADT/SmallPtrSet.h"
|
||||||
|
|
||||||
#include "Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp"
|
#include "Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp"
|
||||||
|
#include "mlir/Transforms/Passes.h"
|
||||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/Support/DebugDump.hpp"
|
#include "src/Accelerators/PIM/Common/Support/DebugDump.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/BiasAddUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.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/Patterns.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/PlanLowering.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/PlanLowering.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.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/Accelerators/PIM/Pass/PIMPasses.h"
|
||||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||||
|
|
||||||
@@ -28,14 +32,6 @@ namespace {
|
|||||||
static constexpr StringLiteral kDenseLayout = "dense_nchw";
|
static constexpr StringLiteral kDenseLayout = "dense_nchw";
|
||||||
static constexpr StringLiteral kRowStripLayout = "nchw_row_strip";
|
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,
|
static FailureOr<RowStripPhysicalValue> getRowStripValue(llvm::DenseMap<Value, RowStripPhysicalValue>& rowStripValues,
|
||||||
Value value) {
|
Value value) {
|
||||||
auto it = rowStripValues.find(value);
|
auto it = rowStripValues.find(value);
|
||||||
@@ -45,112 +41,93 @@ static FailureOr<RowStripPhysicalValue> getRowStripValue(llvm::DenseMap<Value, R
|
|||||||
}
|
}
|
||||||
|
|
||||||
static FailureOr<RowStripPhysicalValue> buildRowStripValue(spatial::SpatBlueprintOp blueprint,
|
static FailureOr<RowStripPhysicalValue> buildRowStripValue(spatial::SpatBlueprintOp blueprint,
|
||||||
Value physicalValue) {
|
Value storage) {
|
||||||
auto logicalType = dyn_cast<RankedTensorType>(blueprint.getOutput().getType());
|
auto logicalType = dyn_cast<RankedTensorType>(blueprint.getOutput().getType());
|
||||||
if (!logicalType)
|
if (!logicalType)
|
||||||
return blueprint.emitOpError("requires ranked logical output type"), failure();
|
return blueprint.emitOpError("requires ranked logical output type"), failure();
|
||||||
RowStripPhysicalValue value;
|
RowStripPhysicalValue value;
|
||||||
value.physicalValue = physicalValue;
|
value.storage = storage;
|
||||||
value.logicalType = logicalType;
|
value.logicalType = logicalType;
|
||||||
value.fragmentOffsets.append(blueprint.getFragmentOffsets().begin(), blueprint.getFragmentOffsets().end());
|
value.fragmentOffsets.append(blueprint.getFragmentOffsets().begin(), blueprint.getFragmentOffsets().end());
|
||||||
value.fragmentSizes.append(blueprint.getFragmentSizes().begin(), blueprint.getFragmentSizes().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;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
static FailureOr<Value>
|
static FailureOr<Value>
|
||||||
lowerRowStripRelu(const RowStripPhysicalValue& input, spatial::SpatReluPlanOp planOp, PatternRewriter& rewriter) {
|
lowerRowStripRelu(const RowStripPhysicalValue& input, spatial::SpatReluPlanOp planOp, PatternRewriter& rewriter) {
|
||||||
auto packedType = cast<RankedTensorType>(input.physicalValue.getType());
|
return applyRowStripRelu(input.storage, input.logicalType, rewriter, planOp.getLoc());
|
||||||
auto computeOp =
|
}
|
||||||
createSpatCompute<1>(rewriter, planOp.getLoc(), TypeRange {packedType}, {}, input.physicalValue, [&](Value x) {
|
|
||||||
auto relu = spatial::SpatReluOp::create(rewriter, planOp.getLoc(), packedType, x);
|
static FailureOr<Value> lowerRowStripBiasAdd(const RowStripPhysicalValue& input,
|
||||||
spatial::SpatYieldOp::create(rewriter, planOp.getLoc(), relu.getResult());
|
spatial::SpatBiasAddPlanOp planOp,
|
||||||
});
|
PatternRewriter& rewriter) {
|
||||||
return computeOp.getResult(0);
|
return applyRowStripBiasAdd(input.storage, input.logicalType, planOp.getBias(), rewriter, planOp.getLoc());
|
||||||
}
|
}
|
||||||
|
|
||||||
static FailureOr<Value>
|
static FailureOr<Value>
|
||||||
materializeRowStripToDense(const RowStripPhysicalValue& rowStripValue, Location loc, PatternRewriter& rewriter) {
|
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())
|
if (rowStripValue.logicalType.getRank() != 4 || !rowStripValue.logicalType.hasStaticShape())
|
||||||
return failure();
|
return failure();
|
||||||
if (rowStripValue.indexMap != "packed_hwc_rows_to_nchw")
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
return failure();
|
||||||
|
|
||||||
const int64_t rank = rowStripValue.logicalType.getRank();
|
auto batch = createSpatComputeBatch(rewriter, loc, TypeRange {resultType}, producer.getLaneCount(), {}, ValueRange {input, bias},
|
||||||
const int64_t fragmentCount = rowStripValue.fragmentOffsets.size() / rank;
|
[&](detail::SpatComputeBatchBodyArgs args) -> LogicalResult {
|
||||||
const int64_t packedWidth = packedType.getDimSize(1);
|
FailureOr<Value> fragment = extractGraphBatchPhysicalFragment(rewriter, loc, args.inputs[0], args.lane, *inputFragmentType);
|
||||||
const int64_t packedChannels = packedType.getDimSize(2);
|
if (failed(fragment))
|
||||||
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)
|
|
||||||
return failure();
|
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);
|
||||||
auto packedSliceType =
|
if (!biasFragment)
|
||||||
RankedTensorType::get({1, packedWidth, packedChannels}, packedType.getElementType(), packedType.getEncoding());
|
return failure();
|
||||||
auto expandedType =
|
Value added = spatial::SpatVAddOp::create(rewriter, loc, *inputFragmentType, *fragment, biasFragment);
|
||||||
RankedTensorType::get({1, 1, packedWidth, packedChannels}, packedType.getElementType(), packedType.getEncoding());
|
MixedSliceGeometry outputSlice;
|
||||||
auto logicalFragmentType =
|
outputSlice.offsets.assign(inputFragmentType->getRank(), rewriter.getIndexAttr(0));
|
||||||
RankedTensorType::get({1, packedChannels, 1, packedWidth}, packedType.getElementType(), packedType.getEncoding());
|
outputSlice.sizes.push_back(rewriter.getIndexAttr(1));
|
||||||
auto batchOp = createSpatComputeBatch(
|
outputSlice.strides.assign(inputFragmentType->getRank(), rewriter.getIndexAttr(1));
|
||||||
rewriter,
|
for (int64_t dim : outputFragmentType->getShape())
|
||||||
loc,
|
outputSlice.sizes.push_back(rewriter.getIndexAttr(dim));
|
||||||
TypeRange {rowStripValue.logicalType},
|
Value output = extractMixedSliceOrIdentity(rewriter, loc, added, *outputFragmentType, outputSlice);
|
||||||
fragmentCount,
|
if (!output)
|
||||||
{},
|
return failure();
|
||||||
ValueRange {rowStripValue.physicalValue},
|
publishGraphBatchPhysicalFragment(rewriter, loc, output, args.outputs.front(), args.lane);
|
||||||
[&](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));
|
|
||||||
|
|
||||||
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));
|
|
||||||
return success();
|
return success();
|
||||||
});
|
});
|
||||||
if (failed(batchOp))
|
if (failed(batch))
|
||||||
return failure();
|
return failure();
|
||||||
return batchOp->getResult(0);
|
return batch->getResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, OperationPass<ModuleOp>> {
|
struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, OperationPass<ModuleOp>> {
|
||||||
@@ -193,7 +170,7 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
|
|||||||
rewriter.setInsertionPoint(planOp);
|
rewriter.setInsertionPoint(planOp);
|
||||||
FailureOr<Value> lowered = lowerSelectedConv2DPlan(
|
FailureOr<Value> lowered = lowerSelectedConv2DPlan(
|
||||||
planOp,
|
planOp,
|
||||||
succeeded(rowStripInput) ? std::optional<Value> {rowStripInput->physicalValue} : std::nullopt,
|
succeeded(rowStripInput) ? std::optional<Value> {rowStripInput->storage} : std::nullopt,
|
||||||
/*emitRowStripLayout=*/true,
|
/*emitRowStripLayout=*/true,
|
||||||
rewriter);
|
rewriter);
|
||||||
if (failed(lowered)) {
|
if (failed(lowered)) {
|
||||||
@@ -265,6 +242,71 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
|
|||||||
rewriter.replaceOp(planOp, computeOp.getResults());
|
rewriter.replaceOp(planOp, computeOp.getResults());
|
||||||
continue;
|
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 (auto materializeOp = dyn_cast<spatial::SpatMaterializeLayoutOp>(&op)) {
|
||||||
if (materializeOp.getSourcePhysicalLayout() == kDenseLayout
|
if (materializeOp.getSourcePhysicalLayout() == kDenseLayout
|
||||||
&& materializeOp.getTargetPhysicalLayout() == kDenseLayout) {
|
&& materializeOp.getTargetPhysicalLayout() == kDenseLayout) {
|
||||||
@@ -294,6 +336,8 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (auto blueprintOp = dyn_cast<spatial::SpatBlueprintOp>(&op)) {
|
if (auto blueprintOp = dyn_cast<spatial::SpatBlueprintOp>(&op)) {
|
||||||
|
if (std::optional<StringRef> mode = blueprintOp.getMode(); mode && *mode == "fragment_assembly")
|
||||||
|
continue;
|
||||||
if (blueprintOp.getPhysicalLayout() == kDenseLayout) {
|
if (blueprintOp.getPhysicalLayout() == kDenseLayout) {
|
||||||
rewriter.replaceOp(blueprintOp, blueprintOp.getInput());
|
rewriter.replaceOp(blueprintOp, blueprintOp.getInput());
|
||||||
continue;
|
continue;
|
||||||
@@ -345,17 +389,25 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
|
|||||||
RewritePatternSet helperPatterns(ctx);
|
RewritePatternSet helperPatterns(ctx);
|
||||||
populateGemmPatterns(helperPatterns, ctx);
|
populateGemmPatterns(helperPatterns, ctx);
|
||||||
populateTransposePatterns(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");
|
moduleOp.emitError("failed to lower helper ONNX ops emitted by selected Spatial plan lowering");
|
||||||
signalPassFailure();
|
signalPassFailure();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
FrozenRewritePatternSet nestedHelperPatterns([&] {
|
}
|
||||||
RewritePatternSet patterns(ctx);
|
|
||||||
populateGemmPatterns(patterns, ctx);
|
|
||||||
populateTransposePatterns(patterns, ctx);
|
|
||||||
return patterns;
|
|
||||||
}());
|
|
||||||
ConversionTarget nestedHelperTarget(*ctx);
|
ConversionTarget nestedHelperTarget(*ctx);
|
||||||
nestedHelperTarget.addLegalDialect<spatial::SpatialDialect,
|
nestedHelperTarget.addLegalDialect<spatial::SpatialDialect,
|
||||||
tensor::TensorDialect,
|
tensor::TensorDialect,
|
||||||
@@ -371,7 +423,8 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
|
|||||||
computeLikeOps.push_back(op);
|
computeLikeOps.push_back(op);
|
||||||
});
|
});
|
||||||
for (Operation* op : computeLikeOps) {
|
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");
|
op->emitOpError("failed to lower nested helper ONNX ops emitted by selected Spatial plan lowering");
|
||||||
signalPassFailure();
|
signalPassFailure();
|
||||||
return;
|
return;
|
||||||
@@ -383,19 +436,37 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
|
|||||||
moduleOp.walk([&](Operation* op) {
|
moduleOp.walk([&](Operation* op) {
|
||||||
if (isa<ONNXEntryPointOp>(op))
|
if (isa<ONNXEntryPointOp>(op))
|
||||||
return;
|
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::SpatReluPlanOp,
|
||||||
spatial::SpatBlueprintOp,
|
|
||||||
spatial::SpatMaterializeLayoutOp>(op)
|
spatial::SpatMaterializeLayoutOp>(op)
|
||||||
|| op->getDialect()->getNamespace() == "onnx") {
|
|| op->getDialect()->getNamespace() == "onnx") {
|
||||||
op->emitOpError("operation must not remain after LowerSpatialPlans");
|
op->emitOpError("operation must not remain after LowerSpatialPlans");
|
||||||
hasIllegalOps = true;
|
hasIllegalOps = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (hasIllegalOps)
|
|
||||||
|
PassManager canonicalizationPM(ctx);
|
||||||
|
canonicalizationPM.addPass(createCanonicalizerPass());
|
||||||
|
if (failed(canonicalizationPM.run(moduleOp)))
|
||||||
|
moduleOp.emitWarning("failed to run LowerSpatialPlansPass canonicalization; continuing");
|
||||||
|
|
||||||
|
if (hasIllegalOps) {
|
||||||
signalPassFailure();
|
signalPassFailure();
|
||||||
else
|
} else {
|
||||||
dumpModule(moduleOp, "spatial1_premerge");
|
dumpModule(moduleOp, "spatial1_graph");
|
||||||
|
spatial::SpatialDataflowExportStage exportMode = spatial::getSpatialDataflowExportStage();
|
||||||
|
if (spatial::shouldExportSpatialDataflowStage(exportMode, spatial::SpatialDataflowExportStage::Spatial1)
|
||||||
|
&& failed(spatial::exportSpatialDataflowCsvGraph(funcOp, "spatial1_graph"))) {
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!verifyLogicalPhase("at the end of LowerSpatialPlans"))
|
if (!verifyLogicalPhase("at the end of LowerSpatialPlans"))
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
#include "Common/Common.hpp"
|
#include "Common/Common.hpp"
|
||||||
#include "Common/PimCommon.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/CompileTime.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||||
@@ -45,11 +46,12 @@ static void populateEmptyFunction(func::FuncOp funcOp) {
|
|||||||
SmallVector<spatial::SpatGraphCompute> computes(funcOp.getOps<spatial::SpatGraphCompute>());
|
SmallVector<spatial::SpatGraphCompute> computes(funcOp.getOps<spatial::SpatGraphCompute>());
|
||||||
SmallVector<spatial::SpatGraphComputeBatch> computeBatches(funcOp.getOps<spatial::SpatGraphComputeBatch>());
|
SmallVector<spatial::SpatGraphComputeBatch> computeBatches(funcOp.getOps<spatial::SpatGraphComputeBatch>());
|
||||||
SmallVector<spatial::SpatConv2DPlanOp> convPlans(funcOp.getOps<spatial::SpatConv2DPlanOp>());
|
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::SpatReluPlanOp> reluPlans(funcOp.getOps<spatial::SpatReluPlanOp>());
|
||||||
SmallVector<spatial::SpatBlueprintOp> blueprints(funcOp.getOps<spatial::SpatBlueprintOp>());
|
SmallVector<spatial::SpatBlueprintOp> blueprints(funcOp.getOps<spatial::SpatBlueprintOp>());
|
||||||
SmallVector<spatial::SpatMaterializeLayoutOp> materializers(funcOp.getOps<spatial::SpatMaterializeLayoutOp>());
|
SmallVector<spatial::SpatMaterializeLayoutOp> materializers(funcOp.getOps<spatial::SpatMaterializeLayoutOp>());
|
||||||
if (!computes.empty() || !computeBatches.empty() || !convPlans.empty() || !reluPlans.empty() || !blueprints.empty()
|
if (!computes.empty() || !computeBatches.empty() || !convPlans.empty() || !biasAddPlans.empty() || !reluPlans.empty()
|
||||||
|| !materializers.empty()) {
|
|| !blueprints.empty() || !materializers.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,9 +67,9 @@ static void populateEmptyFunction(func::FuncOp funcOp) {
|
|||||||
sourceLocs.push_back(source.getLoc());
|
sourceLocs.push_back(source.getLoc());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto newCompute = spatial::SpatGraphCompute::create(
|
auto newCompute = createEmptySpatGraphCompute(
|
||||||
rewriter, returnOp.getLoc(), returnOp.getOperandTypes(), funcOp.getArguments(), {}, {});
|
rewriter, returnOp.getLoc(), returnOp.getOperandTypes(), {}, funcOp.getArguments(), sourceTypes, sourceLocs);
|
||||||
auto* newBlock = rewriter.createBlock(&newCompute.getBody(), newCompute.getBody().end(), sourceTypes, sourceLocs);
|
auto* newBlock = &newCompute.getBody().front();
|
||||||
for (auto [blockArg, computeArg] : llvm::zip(newBlock->getArguments(), newCompute.getOperands()))
|
for (auto [blockArg, computeArg] : llvm::zip(newBlock->getArguments(), newCompute.getOperands()))
|
||||||
mapper.map(computeArg, blockArg);
|
mapper.map(computeArg, blockArg);
|
||||||
newCompute.getProperties().setOperandSegmentSizes({0, static_cast<int>(sourceTypes.size())});
|
newCompute.getProperties().setOperandSegmentSizes({0, static_cast<int>(sourceTypes.size())});
|
||||||
@@ -103,7 +105,7 @@ void ONNXToSpatialPass::runOnOperation() {
|
|||||||
affine::AffineDialect,
|
affine::AffineDialect,
|
||||||
arith::ArithDialect,
|
arith::ArithDialect,
|
||||||
scf::SCFDialect>();
|
scf::SCFDialect>();
|
||||||
preTarget.addIllegalOp<ONNXConstantOp, ONNXFlattenOp>();
|
preTarget.addIllegalOp<ONNXConstantOp>();
|
||||||
|
|
||||||
RewritePatternSet prePatterns(ctx);
|
RewritePatternSet prePatterns(ctx);
|
||||||
populatePrePatterns(prePatterns, ctx);
|
populatePrePatterns(prePatterns, ctx);
|
||||||
@@ -142,6 +144,7 @@ void ONNXToSpatialPass::runOnOperation() {
|
|||||||
target.addIllegalOp<ONNXSigmoidOp>();
|
target.addIllegalOp<ONNXSigmoidOp>();
|
||||||
target.addIllegalOp<ONNXSoftmaxOp>();
|
target.addIllegalOp<ONNXSoftmaxOp>();
|
||||||
target.addIllegalOp<ONNXConcatOp>();
|
target.addIllegalOp<ONNXConcatOp>();
|
||||||
|
target.addIllegalOp<ONNXFlattenOp>();
|
||||||
target.addIllegalOp<ONNXGatherOp>();
|
target.addIllegalOp<ONNXGatherOp>();
|
||||||
target.addIllegalOp<ONNXReshapeOp>();
|
target.addIllegalOp<ONNXReshapeOp>();
|
||||||
target.addIllegalOp<ONNXResizeOp>();
|
target.addIllegalOp<ONNXResizeOp>();
|
||||||
@@ -173,11 +176,6 @@ void ONNXToSpatialPass::runOnOperation() {
|
|||||||
arith::ArithDialect,
|
arith::ArithDialect,
|
||||||
scf::SCFDialect>();
|
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);
|
annotateWeightsConstants(*entryFunc);
|
||||||
|
|
||||||
if (failed(verifyLogicalSpatialGraphInvariants(*entryFunc))) {
|
if (failed(verifyLogicalSpatialGraphInvariants(*entryFunc))) {
|
||||||
@@ -213,13 +211,18 @@ void ONNXToSpatialPass::runOnOperation() {
|
|||||||
|
|
||||||
populateEmptyFunction(*entryFunc);
|
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))) {
|
if (failed(verifyLogicalSpatialGraphInvariants(*entryFunc))) {
|
||||||
moduleOp.emitError("logical Spatial graph verification failed after ONNX-to-Spatial");
|
moduleOp.emitError("logical Spatial graph verification failed after ONNX-to-Spatial");
|
||||||
signalPassFailure();
|
signalPassFailure();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dumpModule(moduleOp, "spatial0");
|
|
||||||
|
|
||||||
if (failed(verifyONNXToSpatial(*entryFunc))) {
|
if (failed(verifyONNXToSpatial(*entryFunc))) {
|
||||||
moduleOp.emitError("ONNX-to-Spatial host legality verification failed");
|
moduleOp.emitError("ONNX-to-Spatial host legality verification failed");
|
||||||
signalPassFailure();
|
signalPassFailure();
|
||||||
|
|||||||
@@ -56,13 +56,18 @@ bool isLegalExternalCapture(Value value, Region& region) {
|
|||||||
return definingOp && definingOp->hasTrait<OpTrait::ConstantLike>();
|
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>
|
template <typename ComputeOpTy>
|
||||||
void verifyComputeBodyCaptures(ComputeOpTy compute, StringRef kind, pim::CappedDiagnosticReporter& diagnostics) {
|
void verifyComputeBodyCaptures(ComputeOpTy compute, StringRef kind, pim::CappedDiagnosticReporter& diagnostics) {
|
||||||
Region& body = compute.getBody();
|
Region& body = compute.getBody();
|
||||||
body.walk([&](Operation* nestedOp) {
|
body.walk([&](Operation* nestedOp) {
|
||||||
for (OpOperand& operand : nestedOp->getOpOperands()) {
|
for (OpOperand& operand : nestedOp->getOpOperands()) {
|
||||||
Value value = operand.get();
|
Value value = operand.get();
|
||||||
if (isLegalExternalCapture(value, body))
|
if (isLegalExternalCapture(value, body) || isRecordedDeferredCommunicationSource(nestedOp, value))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Operation* definingOp = value.getDefiningOp();
|
Operation* definingOp = value.getDefiningOp();
|
||||||
@@ -90,21 +95,29 @@ bool isLegalHostBackedValue(Value value) {
|
|||||||
return definingOp->getDialect()->getNamespace() != "spat";
|
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>
|
template <typename ComputeOpTy>
|
||||||
void verifyScheduledInputs(ComputeOpTy compute,
|
void verifyScheduledInputs(ComputeOpTy compute,
|
||||||
bool allowChannelReceiveInputs,
|
bool allowChannelReceiveInputs,
|
||||||
StringRef kind,
|
StringRef kind,
|
||||||
pim::CappedDiagnosticReporter& diagnostics) {
|
pim::CappedDiagnosticReporter& diagnostics) {
|
||||||
for (auto [inputIndex, input] : llvm::enumerate(compute.getInputs())) {
|
for (auto [inputIndex, input] : llvm::enumerate(compute.getInputs())) {
|
||||||
|
size_t currentInputIndex = inputIndex;
|
||||||
Operation* definingOp = input.getDefiningOp();
|
Operation* definingOp = input.getDefiningOp();
|
||||||
if (allowChannelReceiveInputs && isa_and_nonnull<spatial::SpatChannelReceiveOp>(definingOp))
|
if (allowChannelReceiveInputs && isa_and_nonnull<spatial::SpatChannelReceiveOp>(definingOp))
|
||||||
continue;
|
continue;
|
||||||
|
if (isScheduledPhase1Value(input))
|
||||||
|
continue;
|
||||||
if (isLegalHostBackedValue(input))
|
if (isLegalHostBackedValue(input))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
diagnostics.report(compute.getOperation(), [&](Operation* illegalOp) {
|
diagnostics.report(compute.getOperation(), [&](Operation* illegalOp) {
|
||||||
InFlightDiagnostic diag = illegalOp->emitOpError()
|
InFlightDiagnostic diag = illegalOp->emitOpError()
|
||||||
<< kPhaseMarker << " " << kind << " input #" << inputIndex
|
<< kPhaseMarker << " " << kind << " input #" << currentInputIndex
|
||||||
<< (allowChannelReceiveInputs ? " must come from the host or explicit spat.channel_receive"
|
<< (allowChannelReceiveInputs ? " must come from the host or explicit spat.channel_receive"
|
||||||
: " must come from the host");
|
: " must come from the host");
|
||||||
if (definingOp)
|
if (definingOp)
|
||||||
@@ -132,6 +145,7 @@ void verifyLogicalTopLevelOps(func::FuncOp funcOp, pim::CappedDiagnosticReporter
|
|||||||
spatial::SpatGraphCompute,
|
spatial::SpatGraphCompute,
|
||||||
spatial::SpatGraphComputeBatch,
|
spatial::SpatGraphComputeBatch,
|
||||||
spatial::SpatConv2DPlanOp,
|
spatial::SpatConv2DPlanOp,
|
||||||
|
spatial::SpatBiasAddPlanOp,
|
||||||
spatial::SpatReluPlanOp,
|
spatial::SpatReluPlanOp,
|
||||||
spatial::SpatBlueprintOp,
|
spatial::SpatBlueprintOp,
|
||||||
spatial::SpatMaterializeLayoutOp>(&op)) {
|
spatial::SpatMaterializeLayoutOp>(&op)) {
|
||||||
@@ -162,9 +176,9 @@ void verifyLogicalTopLevelOps(func::FuncOp funcOp, pim::CappedDiagnosticReporter
|
|||||||
|
|
||||||
void verifyScheduledTopLevelOps(func::FuncOp funcOp, pim::CappedDiagnosticReporter& diagnostics) {
|
void verifyScheduledTopLevelOps(func::FuncOp funcOp, pim::CappedDiagnosticReporter& diagnostics) {
|
||||||
for (Operation& op : funcOp.getOps()) {
|
for (Operation& op : funcOp.getOps()) {
|
||||||
if (isa<spatial::SpatGraphCompute, spatial::SpatGraphComputeBatch>(&op)) {
|
if (isa<spatial::SpatChannelSendOp, spatial::SpatChannelReceiveOp>(&op)) {
|
||||||
diagnostics.report(&op, [&](Operation* illegalOp) {
|
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";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ void populateConversionPatterns(RewritePatternSet& patterns, MLIRContext* ctx) {
|
|||||||
populateSigmoidPatterns(patterns, ctx);
|
populateSigmoidPatterns(patterns, ctx);
|
||||||
populateSoftmaxPatterns(patterns, ctx);
|
populateSoftmaxPatterns(patterns, ctx);
|
||||||
populateConcatPatterns(patterns, ctx);
|
populateConcatPatterns(patterns, ctx);
|
||||||
|
populateFlattenPatterns(patterns, ctx);
|
||||||
populateGatherPatterns(patterns, ctx);
|
populateGatherPatterns(patterns, ctx);
|
||||||
populateResizePatterns(patterns, ctx);
|
populateResizePatterns(patterns, ctx);
|
||||||
populateReshapePatterns(patterns, ctx);
|
populateReshapePatterns(patterns, ctx);
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ void populateReluPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext*
|
|||||||
void populateSigmoidPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
void populateSigmoidPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
void populateSoftmaxPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
void populateSoftmaxPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
void populateConcatPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
void populateConcatPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
|
void populateFlattenPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
void populateGatherPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
void populateGatherPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
void populateResizePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
void populateResizePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
void populateReshapePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
void populateReshapePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
#include "llvm/ADT/SmallVector.h"
|
#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/Common/Common.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
@@ -47,38 +47,28 @@ static FailureOr<Value> materializeBroadcastedConstantTensor(Value value,
|
|||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
const int64_t rankOffset = static_cast<int64_t>(resultShape.size() - sourceShape.size());
|
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> sourceStrides = computeRowMajorStrides(sourceShape);
|
||||||
SmallVector<int64_t> resultStrides = computeRowMajorStrides(resultShape);
|
SmallVector<int64_t> resultStrides = computeRowMajorStrides(resultShape);
|
||||||
|
SmallVector<Attribute> sourceValues(denseAttr.getValues<Attribute>());
|
||||||
SmallVector<Attribute> resultValues;
|
SmallVector<Attribute> resultValues;
|
||||||
resultValues.reserve(resultType.getNumElements());
|
resultValues.reserve(resultType.getNumElements());
|
||||||
|
|
||||||
for (int64_t flatIndex = 0; flatIndex < resultType.getNumElements(); ++flatIndex) {
|
for (int64_t flatIndex = 0; flatIndex < resultType.getNumElements(); ++flatIndex) {
|
||||||
int64_t remaining = flatIndex;
|
int64_t remaining = flatIndex;
|
||||||
int64_t sourceFlatIndex = 0;
|
int64_t sourceFlatIndex = 0;
|
||||||
|
|
||||||
for (int64_t i = 0; i < static_cast<int64_t>(resultShape.size()); ++i) {
|
for (int64_t i = 0; i < static_cast<int64_t>(resultShape.size()); ++i) {
|
||||||
const int64_t resultIndex = resultStrides.empty() ? 0 : remaining / resultStrides[i];
|
const int64_t resultIndex = resultStrides.empty() ? 0 : remaining / resultStrides[i];
|
||||||
remaining = resultStrides.empty() ? 0 : remaining % resultStrides[i];
|
remaining = resultStrides.empty() ? 0 : remaining % resultStrides[i];
|
||||||
|
|
||||||
const int64_t sourceIndex = i - rankOffset;
|
const int64_t sourceIndex = i - rankOffset;
|
||||||
if (sourceIndex < 0)
|
if (sourceIndex < 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const int64_t sourceDim = sourceShape[sourceIndex];
|
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;
|
const int64_t mappedIndex = sourceDim == 1 ? 0 : resultIndex;
|
||||||
sourceFlatIndex += mappedIndex * sourceStrides[sourceIndex];
|
sourceFlatIndex += mappedIndex * sourceStrides[sourceIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
resultValues.push_back(sourceValues[sourceFlatIndex]);
|
resultValues.push_back(sourceValues[sourceFlatIndex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +96,7 @@ static FailureOr<Value> materializeReciprocalTensor(Value value,
|
|||||||
if (failed(broadcastedValue))
|
if (failed(broadcastedValue))
|
||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
auto denseAttr = dyn_cast<DenseFPElementsAttr>(getDenseConstantAttr(*broadcastedValue));
|
auto denseAttr = dyn_cast<DenseFPElementsAttr>(getHostConstDenseElementsAttr(*broadcastedValue));
|
||||||
if (!denseAttr)
|
if (!denseAttr)
|
||||||
return failure();
|
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
|
} // namespace
|
||||||
|
|
||||||
void populateElementwisePatterns(RewritePatternSet& patterns, MLIRContext* ctx) {
|
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<ONNXSubOp, spatial::SpatVSubOp>>(ctx);
|
||||||
patterns.add<BinaryElementwiseToSpatialCompute<ONNXMulOp, spatial::SpatVMulOp>>(ctx);
|
patterns.add<BinaryElementwiseToSpatialCompute<ONNXMulOp, spatial::SpatVMulOp>>(ctx);
|
||||||
patterns.add<DivToSpatialCompute>(ctx);
|
patterns.add<DivToSpatialCompute>(ctx);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#include "src/Accelerators/PIM/Common/IR/AffineUtils.hpp"
|
#include "src/Accelerators/PIM/Common/IR/AffineUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
|
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.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/PimCommon.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/Support/Diagnostics.hpp"
|
#include "src/Accelerators/PIM/Common/Support/Diagnostics.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.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);
|
rewriter, loc, args.weights.front(), bTileType, bOffsets, bSizes, unitStrides);
|
||||||
Value piece = spatial::SpatVMMOp::create(rewriter, loc, pieceType, bTile, aTile).getResult();
|
Value piece = spatial::SpatVMMOp::create(rewriter, loc, pieceType, bTile, aTile).getResult();
|
||||||
|
|
||||||
SmallVector<OpFoldResult> pieceOffsets {args.lane, rewriter.getIndexAttr(0)};
|
publishGraphBatchPhysicalFragment(rewriter, loc, piece, args.outputs.front(), args.lane);
|
||||||
SmallVector<OpFoldResult> pieceSizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(crossbarSize.getValue())};
|
|
||||||
createParallelInsertSliceIntoBatchOutput(
|
|
||||||
rewriter, loc, piece, args.outputs.front(), pieceOffsets, pieceSizes, unitStrides);
|
|
||||||
});
|
});
|
||||||
if (failed(batchOp))
|
if (failed(batchOp))
|
||||||
return failure();
|
return failure();
|
||||||
@@ -401,11 +399,7 @@ static FailureOr<spatial::SpatComputeBatch> createVvdmulBatch(Value a,
|
|||||||
Value bVector = extractDynamicGemmBColumn(args.inputs[1], column, vectorType, rewriter, loc);
|
Value bVector = extractDynamicGemmBColumn(args.inputs[1], column, vectorType, rewriter, loc);
|
||||||
Value scalar = spatial::SpatVVDMulOp::create(rewriter, loc, scalarType, aVector, bVector).getResult();
|
Value scalar = spatial::SpatVVDMulOp::create(rewriter, loc, scalarType, aVector, bVector).getResult();
|
||||||
|
|
||||||
SmallVector<OpFoldResult> outputOffsets {args.lane, rewriter.getIndexAttr(0)};
|
publishGraphBatchPhysicalFragment(rewriter, loc, scalar, args.outputs.front(), args.lane);
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
if (failed(batchOp))
|
if (failed(batchOp))
|
||||||
return failure();
|
return failure();
|
||||||
@@ -447,15 +441,14 @@ static FailureOr<spatial::SpatCompute> createDynamicGemmOutputCompute(Value scal
|
|||||||
Value row = createDynamicGemmBatchRow(lane, numOutCols, rewriter, nestedLoc);
|
Value row = createDynamicGemmBatchRow(lane, numOutCols, rewriter, nestedLoc);
|
||||||
Value column =
|
Value column =
|
||||||
onnx_mlir::affineModConst(rewriter, nestedLoc, lane, numOutCols, rewriter.getInsertionBlock()->getParentOp());
|
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> scalarSizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
||||||
SmallVector<OpFoldResult> unitStrides {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
SmallVector<OpFoldResult> unitStrides {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
||||||
Value scalar = tensor::ExtractSliceOp::create(
|
FailureOr<Value> scalar = extractGraphBatchPhysicalFragment(rewriter, nestedLoc, pieces, lane, scalarType);
|
||||||
rewriter, nestedLoc, scalarType, pieces, scalarOffsets, scalarSizes, unitStrides)
|
if (failed(scalar))
|
||||||
.getResult();
|
return failure();
|
||||||
if (alpha != 1.0f) {
|
if (alpha != 1.0f) {
|
||||||
Value alphaTensor = createScalarTensorConstant(scalarType, alpha, rewriter, nestedLoc);
|
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) {
|
if (biasArg) {
|
||||||
Value biasScalar =
|
Value biasScalar =
|
||||||
@@ -465,11 +458,11 @@ static FailureOr<spatial::SpatCompute> createDynamicGemmOutputCompute(Value scal
|
|||||||
biasScalar =
|
biasScalar =
|
||||||
spatial::SpatVMulOp::create(rewriter, nestedLoc, scalarType, biasScalar, betaTensor).getResult();
|
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};
|
SmallVector<OpFoldResult> outputOffsets {row, column};
|
||||||
Value outputNext =
|
Value outputNext =
|
||||||
tensor::InsertSliceOp::create(rewriter, nestedLoc, scalar, outputAcc, outputOffsets, scalarSizes, unitStrides)
|
tensor::InsertSliceOp::create(rewriter, nestedLoc, *scalar, outputAcc, outputOffsets, scalarSizes, unitStrides)
|
||||||
.getResult();
|
.getResult();
|
||||||
yielded.push_back(outputNext);
|
yielded.push_back(outputNext);
|
||||||
return success();
|
return success();
|
||||||
@@ -505,14 +498,13 @@ static Value extractReductionPiece(Value partialPiecesArg,
|
|||||||
int64_t numOutRows,
|
int64_t numOutRows,
|
||||||
ConversionPatternRewriter& rewriter,
|
ConversionPatternRewriter& rewriter,
|
||||||
Location loc) {
|
Location loc) {
|
||||||
SmallVector<OpFoldResult> unitStrides {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
SmallVector<OpFoldResult> unitStrides {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
||||||
SmallVector<OpFoldResult> pieceSizes {rewriter.getIndexAttr(numOutRows),
|
SmallVector<OpFoldResult> pieceSizes {rewriter.getIndexAttr(numOutRows), rewriter.getIndexAttr(1), rewriter.getIndexAttr(crossbarSize.getValue())};
|
||||||
rewriter.getIndexAttr(crossbarSize.getValue())};
|
|
||||||
SmallVector<OpFoldResult> pieceOffsets {
|
SmallVector<OpFoldResult> pieceOffsets {
|
||||||
createPartialGroupOffset(hSlice, kSlice, numKSlices, numOutRows, rewriter, loc), rewriter.getIndexAttr(0)};
|
createPartialGroupOffset(hSlice, kSlice, numKSlices, numOutRows, rewriter, loc), rewriter.getIndexAttr(0), rewriter.getIndexAttr(0)};
|
||||||
return tensor::ExtractSliceOp::create(
|
return extractMixedSliceOrIdentity(
|
||||||
rewriter, loc, pieceType, partialPiecesArg, pieceOffsets, pieceSizes, unitStrides)
|
rewriter, loc, partialPiecesArg, pieceType,
|
||||||
.getResult();
|
{pieceOffsets, pieceSizes, unitStrides});
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value reducePartialPiecesForHSlice(Value partialPiecesArg,
|
static Value reducePartialPiecesForHSlice(Value partialPiecesArg,
|
||||||
@@ -730,7 +722,7 @@ LogicalResult GemmToSpatialComputes::matchAndRewrite(ONNXGemmOp gemmOp,
|
|||||||
return failure();
|
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);
|
auto batchOp = createVvdmulBatch(a, b, aType, bType, scalarPiecesType, outType, rewriter, loc);
|
||||||
if (failed(batchOp))
|
if (failed(batchOp))
|
||||||
return failure();
|
return failure();
|
||||||
@@ -802,8 +794,8 @@ LogicalResult GemmToSpatialComputes::matchAndRewrite(ONNXGemmOp gemmOp,
|
|||||||
return failure();
|
return failure();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto partialPiecesType =
|
auto partialPiecesType = spatial::getGraphBatchPhysicalResultType(
|
||||||
RankedTensorType::get({laneCount64, static_cast<int64_t>(crossbarSize.getValue())}, outType.getElementType());
|
laneCount64, RankedTensorType::get({1, static_cast<int64_t>(crossbarSize.getValue())}, outType.getElementType()));
|
||||||
auto batchOp =
|
auto batchOp =
|
||||||
createVmmBatch(a, b, aType, paddedBType, partialPiecesType, numOutRows, numKSlices, numOutHSlices, rewriter, loc);
|
createVmmBatch(a, b, aType, paddedBType, partialPiecesType, numOutRows, numKSlices, numOutHSlices, rewriter, loc);
|
||||||
if (failed(batchOp))
|
if (failed(batchOp))
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include "src/Accelerators/PIM/Common/IR/AffineUtils.hpp"
|
#include "src/Accelerators/PIM/Common/IR/AffineUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/IR/LoopUtils.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/Common/Common.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||||
@@ -299,22 +300,14 @@ static Value extractBatchedATile(Value a,
|
|||||||
RankedTensorType aTileType,
|
RankedTensorType aTileType,
|
||||||
PatternRewriter& rewriter,
|
PatternRewriter& rewriter,
|
||||||
Location loc) {
|
Location loc) {
|
||||||
auto aSliceType = RankedTensorType::get({1, 1, aTileType.getDimSize(1)}, aTileType.getElementType());
|
|
||||||
Value sourceBatchIndex =
|
Value sourceBatchIndex =
|
||||||
mapOutputBatchIndexToSourceBatchIndex(outputBatchIndex, sourceBatchShape, outputBatchShape, rewriter, loc);
|
mapOutputBatchIndexToSourceBatchIndex(outputBatchIndex, sourceBatchShape, outputBatchShape, rewriter, loc);
|
||||||
SmallVector<OpFoldResult> offsets {OpFoldResult(sourceBatchIndex), row, kOffset};
|
SmallVector<OpFoldResult> offsets {OpFoldResult(sourceBatchIndex), row, kOffset};
|
||||||
SmallVector<OpFoldResult> sizes {
|
SmallVector<OpFoldResult> sizes {
|
||||||
rewriter.getIndexAttr(1), rewriter.getIndexAttr(1), rewriter.getIndexAttr(aTileType.getDimSize(1))};
|
rewriter.getIndexAttr(1), rewriter.getIndexAttr(1), rewriter.getIndexAttr(aTileType.getDimSize(1))};
|
||||||
auto slice =
|
return extractMixedSliceOrIdentity(
|
||||||
tensor::ExtractSliceOp::create(rewriter, loc, aSliceType, a, offsets, sizes, getUnitStrides(rewriter, 3));
|
rewriter, loc, a, aTileType,
|
||||||
return tensor::CollapseShapeOp::create(rewriter,
|
{offsets, sizes, getUnitStrides(rewriter, 3)});
|
||||||
loc,
|
|
||||||
aTileType,
|
|
||||||
slice,
|
|
||||||
SmallVector<ReassociationIndices> {
|
|
||||||
{0, 1},
|
|
||||||
{2}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value extractBatchedBTile(Value b,
|
static Value extractBatchedBTile(Value b,
|
||||||
@@ -326,24 +319,15 @@ static Value extractBatchedBTile(Value b,
|
|||||||
RankedTensorType bTileType,
|
RankedTensorType bTileType,
|
||||||
PatternRewriter& rewriter,
|
PatternRewriter& rewriter,
|
||||||
Location loc) {
|
Location loc) {
|
||||||
auto bSliceType =
|
|
||||||
RankedTensorType::get({1, bTileType.getDimSize(0), bTileType.getDimSize(1)}, bTileType.getElementType());
|
|
||||||
Value sourceBatchIndex =
|
Value sourceBatchIndex =
|
||||||
mapOutputBatchIndexToSourceBatchIndex(outputBatchIndex, sourceBatchShape, outputBatchShape, rewriter, loc);
|
mapOutputBatchIndexToSourceBatchIndex(outputBatchIndex, sourceBatchShape, outputBatchShape, rewriter, loc);
|
||||||
SmallVector<OpFoldResult> offsets {OpFoldResult(sourceBatchIndex), kOffset, hOffset};
|
SmallVector<OpFoldResult> offsets {OpFoldResult(sourceBatchIndex), kOffset, hOffset};
|
||||||
SmallVector<OpFoldResult> sizes {rewriter.getIndexAttr(1),
|
SmallVector<OpFoldResult> sizes {rewriter.getIndexAttr(1),
|
||||||
rewriter.getIndexAttr(bTileType.getDimSize(0)),
|
rewriter.getIndexAttr(bTileType.getDimSize(0)),
|
||||||
rewriter.getIndexAttr(bTileType.getDimSize(1))};
|
rewriter.getIndexAttr(bTileType.getDimSize(1))};
|
||||||
auto slice =
|
return extractMixedSliceOrIdentity(
|
||||||
tensor::ExtractSliceOp::create(rewriter, loc, bSliceType, b, offsets, sizes, getUnitStrides(rewriter, 3));
|
rewriter, loc, b, bTileType,
|
||||||
return tensor::CollapseShapeOp::create(rewriter,
|
{offsets, sizes, getUnitStrides(rewriter, 3)});
|
||||||
loc,
|
|
||||||
bTileType,
|
|
||||||
slice,
|
|
||||||
SmallVector<ReassociationIndices> {
|
|
||||||
{0, 1},
|
|
||||||
{2}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value getBatchLaneIndex(
|
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);
|
args.weights.front(), bBatchShape, outputBatchShape, batch, kOffset, hOffset, bTileType, rewriter, loc);
|
||||||
Value piece = spatial::SpatVMMOp::create(rewriter, loc, pieceType, bTile, aTile).getResult();
|
Value piece = spatial::SpatVMMOp::create(rewriter, loc, pieceType, bTile, aTile).getResult();
|
||||||
|
|
||||||
SmallVector<OpFoldResult> pieceOffsets {args.lane, rewriter.getIndexAttr(0)};
|
publishGraphBatchPhysicalFragment(rewriter, loc, piece, args.outputs.front(), args.lane);
|
||||||
SmallVector<OpFoldResult> pieceSizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(crossbarSize.getValue())};
|
|
||||||
createParallelInsertSliceIntoBatchOutput(
|
|
||||||
rewriter, loc, piece, args.outputs.front(), pieceOffsets, pieceSizes, getUnitStrides(rewriter, 2));
|
|
||||||
});
|
});
|
||||||
if (failed(batchOp))
|
if (failed(batchOp))
|
||||||
return failure();
|
return failure();
|
||||||
@@ -451,22 +432,14 @@ static Value extractDynamicBatchedRowVector(Value matrix,
|
|||||||
RankedTensorType vectorType,
|
RankedTensorType vectorType,
|
||||||
PatternRewriter& rewriter,
|
PatternRewriter& rewriter,
|
||||||
Location loc) {
|
Location loc) {
|
||||||
auto rowSliceType = RankedTensorType::get({1, 1, vectorType.getDimSize(1)}, vectorType.getElementType());
|
|
||||||
Value sourceBatchIndex =
|
Value sourceBatchIndex =
|
||||||
mapOutputBatchIndexToSourceBatchIndex(outputBatchIndex, sourceBatchShape, outputBatchShape, rewriter, loc);
|
mapOutputBatchIndexToSourceBatchIndex(outputBatchIndex, sourceBatchShape, outputBatchShape, rewriter, loc);
|
||||||
SmallVector<OpFoldResult> offsets {OpFoldResult(sourceBatchIndex), row, rewriter.getIndexAttr(0)};
|
SmallVector<OpFoldResult> offsets {OpFoldResult(sourceBatchIndex), row, rewriter.getIndexAttr(0)};
|
||||||
SmallVector<OpFoldResult> sizes {
|
SmallVector<OpFoldResult> sizes {
|
||||||
rewriter.getIndexAttr(1), rewriter.getIndexAttr(1), rewriter.getIndexAttr(vectorType.getDimSize(1))};
|
rewriter.getIndexAttr(1), rewriter.getIndexAttr(1), rewriter.getIndexAttr(vectorType.getDimSize(1))};
|
||||||
auto rowSlice =
|
return extractMixedSliceOrIdentity(
|
||||||
tensor::ExtractSliceOp::create(rewriter, loc, rowSliceType, matrix, offsets, sizes, getUnitStrides(rewriter, 3));
|
rewriter, loc, matrix, vectorType,
|
||||||
return tensor::CollapseShapeOp::create(rewriter,
|
{offsets, sizes, getUnitStrides(rewriter, 3)});
|
||||||
loc,
|
|
||||||
vectorType,
|
|
||||||
rowSlice,
|
|
||||||
SmallVector<ReassociationIndices> {
|
|
||||||
{0, 1},
|
|
||||||
{2}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static FailureOr<spatial::SpatComputeBatch> createBatchedVvdmulBatch(Value a,
|
static FailureOr<spatial::SpatComputeBatch> createBatchedVvdmulBatch(Value a,
|
||||||
@@ -506,10 +479,7 @@ static FailureOr<spatial::SpatComputeBatch> createBatchedVvdmulBatch(Value a,
|
|||||||
Value bVector = extractDynamicBatchedBColumn(
|
Value bVector = extractDynamicBatchedBColumn(
|
||||||
args.inputs[1], bBatchShape, outputBatchShape, batch, column, vectorType, rewriter, loc);
|
args.inputs[1], bBatchShape, outputBatchShape, batch, column, vectorType, rewriter, loc);
|
||||||
Value scalar = spatial::SpatVVDMulOp::create(rewriter, loc, scalarType, aVector, bVector).getResult();
|
Value scalar = spatial::SpatVVDMulOp::create(rewriter, loc, scalarType, aVector, bVector).getResult();
|
||||||
SmallVector<OpFoldResult> outputOffsets {args.lane, rewriter.getIndexAttr(0)};
|
publishGraphBatchPhysicalFragment(rewriter, loc, scalar, args.outputs.front(), args.lane);
|
||||||
SmallVector<OpFoldResult> scalarSizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
|
||||||
createParallelInsertSliceIntoBatchOutput(
|
|
||||||
rewriter, loc, scalar, args.outputs.front(), outputOffsets, scalarSizes, getUnitStrides(rewriter, 2));
|
|
||||||
});
|
});
|
||||||
if (failed(batchOp))
|
if (failed(batchOp))
|
||||||
return failure();
|
return failure();
|
||||||
@@ -525,7 +495,6 @@ static FailureOr<Value> createBatchedDynamicOutputCompute(Value scalarPieces,
|
|||||||
const int64_t numOutRows = outType.getDimSize(1);
|
const int64_t numOutRows = outType.getDimSize(1);
|
||||||
const int64_t numOutCols = outType.getDimSize(2);
|
const int64_t numOutCols = outType.getDimSize(2);
|
||||||
auto scalarType = RankedTensorType::get({1, 1}, outType.getElementType());
|
auto scalarType = RankedTensorType::get({1, 1}, outType.getElementType());
|
||||||
auto outputScalarType = RankedTensorType::get({1, 1, 1}, outType.getElementType());
|
|
||||||
|
|
||||||
auto computeOp = createSpatCompute<1>(
|
auto computeOp = createSpatCompute<1>(
|
||||||
rewriter, loc, TypeRange {outType}, {}, ValueRange {scalarPieces}, [&](Value pieces) -> LogicalResult {
|
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 batchLane = affineModConst(rewriter, nestedLoc, lane, numOutRows * numOutCols, anchorOp);
|
||||||
Value row = affineFloorDivConst(rewriter, nestedLoc, batchLane, numOutCols, anchorOp);
|
Value row = affineFloorDivConst(rewriter, nestedLoc, batchLane, numOutCols, anchorOp);
|
||||||
Value column = affineModConst(rewriter, nestedLoc, batchLane, numOutCols, anchorOp);
|
Value column = affineModConst(rewriter, nestedLoc, batchLane, numOutCols, anchorOp);
|
||||||
SmallVector<OpFoldResult> scalarOffsets {lane, rewriter.getIndexAttr(0)};
|
FailureOr<Value> scalar = extractGraphBatchPhysicalFragment(rewriter, nestedLoc, pieces, lane, scalarType);
|
||||||
SmallVector<OpFoldResult> scalarSizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
if (failed(scalar))
|
||||||
Value scalar = tensor::ExtractSliceOp::create(
|
return failure();
|
||||||
rewriter, nestedLoc, scalarType, pieces, scalarOffsets, scalarSizes, getUnitStrides(rewriter, 2));
|
|
||||||
Value expanded = tensor::ExpandShapeOp::create(rewriter,
|
|
||||||
nestedLoc,
|
|
||||||
outputScalarType,
|
|
||||||
scalar,
|
|
||||||
SmallVector<ReassociationIndices> {
|
|
||||||
{0},
|
|
||||||
{1, 2}
|
|
||||||
});
|
|
||||||
SmallVector<OpFoldResult> outputOffsets {batch, row, column};
|
SmallVector<OpFoldResult> outputOffsets {batch, row, column};
|
||||||
SmallVector<OpFoldResult> outputSizes = {
|
SmallVector<OpFoldResult> outputSizes = {
|
||||||
rewriter.getIndexAttr(1), rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
rewriter.getIndexAttr(1), rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
||||||
Value next =
|
Value next =
|
||||||
tensor::InsertSliceOp::create(
|
tensor::InsertSliceOp::create(
|
||||||
rewriter, nestedLoc, expanded, outputAcc, outputOffsets, outputSizes, getUnitStrides(rewriter, 3))
|
rewriter, nestedLoc, *scalar, outputAcc, outputOffsets, outputSizes, getUnitStrides(rewriter, 3))
|
||||||
.getResult();
|
.getResult();
|
||||||
yielded.push_back(next);
|
yielded.push_back(next);
|
||||||
return success();
|
return success();
|
||||||
@@ -596,10 +556,11 @@ static Value extractBatchedReductionPiece(Value partialPiecesArg,
|
|||||||
Value kOffset = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), kSlice * numOutRows);
|
Value kOffset = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), kSlice * numOutRows);
|
||||||
Value batchAndHSlice = arith::AddIOp::create(rewriter, loc, batchOffset, hOffset);
|
Value batchAndHSlice = arith::AddIOp::create(rewriter, loc, batchOffset, hOffset);
|
||||||
Value pieceOffset = arith::AddIOp::create(rewriter, loc, batchAndHSlice, kOffset);
|
Value pieceOffset = arith::AddIOp::create(rewriter, loc, batchAndHSlice, kOffset);
|
||||||
SmallVector<OpFoldResult> offsets {pieceOffset, rewriter.getIndexAttr(0)};
|
SmallVector<OpFoldResult> offsets {pieceOffset, rewriter.getIndexAttr(0), rewriter.getIndexAttr(0)};
|
||||||
SmallVector<OpFoldResult> sizes {rewriter.getIndexAttr(numOutRows), rewriter.getIndexAttr(crossbarSize.getValue())};
|
SmallVector<OpFoldResult> sizes {rewriter.getIndexAttr(numOutRows), rewriter.getIndexAttr(1), rewriter.getIndexAttr(crossbarSize.getValue())};
|
||||||
return tensor::ExtractSliceOp::create(
|
return extractMixedSliceOrIdentity(
|
||||||
rewriter, loc, pieceType, partialPiecesArg, offsets, sizes, getUnitStrides(rewriter, 2));
|
rewriter, loc, partialPiecesArg, pieceType,
|
||||||
|
{offsets, sizes, getUnitStrides(rewriter, 3)});
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value reduceBatchedPartialPiecesForHSlice(Value partialPiecesArg,
|
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());
|
const int64_t numOutHSlices = ceilIntegerDivide(outType.getDimSize(2), crossbarSize.getValue());
|
||||||
auto pieceType = RankedTensorType::get({numOutRows, static_cast<int64_t>(crossbarSize.getValue())},
|
auto pieceType = RankedTensorType::get({numOutRows, static_cast<int64_t>(crossbarSize.getValue())},
|
||||||
partialPiecesType.getElementType());
|
partialPiecesType.getElementType());
|
||||||
auto outputSliceType = RankedTensorType::get({1, numOutRows, static_cast<int64_t>(crossbarSize.getValue())},
|
|
||||||
partialPiecesType.getElementType());
|
|
||||||
|
|
||||||
Value outputInit =
|
Value outputInit =
|
||||||
tensor::EmptyOp::create(rewriter, loc, paddedOutType.getShape(), paddedOutType.getElementType()).getResult();
|
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 outputAcc = hIterArgs.front();
|
||||||
Value reduced = reduceBatchedPartialPiecesForHSlice(
|
Value reduced = reduceBatchedPartialPiecesForHSlice(
|
||||||
partialPiecesArg, batch, hSlice, pieceType, numKSlices, numOutHSlices, numOutRows, rewriter, hLoc);
|
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(
|
Value hOffset = affineMulConst(
|
||||||
rewriter, hLoc, hSlice, crossbarSize.getValue(), rewriter.getInsertionBlock()->getParentOp());
|
rewriter, hLoc, hSlice, crossbarSize.getValue(), rewriter.getInsertionBlock()->getParentOp());
|
||||||
SmallVector<OpFoldResult> outputOffsets {batch, rewriter.getIndexAttr(0), hOffset};
|
SmallVector<OpFoldResult> outputOffsets {batch, rewriter.getIndexAttr(0), hOffset};
|
||||||
@@ -693,7 +644,7 @@ static FailureOr<Value> createBatchedReductionCompute(Value partialPieces,
|
|||||||
rewriter.getIndexAttr(crossbarSize.getValue())};
|
rewriter.getIndexAttr(crossbarSize.getValue())};
|
||||||
Value next =
|
Value next =
|
||||||
tensor::InsertSliceOp::create(
|
tensor::InsertSliceOp::create(
|
||||||
rewriter, hLoc, expandedReduced, outputAcc, outputOffsets, outputSizes, getUnitStrides(rewriter, 3))
|
rewriter, hLoc, reduced, outputAcc, outputOffsets, outputSizes, getUnitStrides(rewriter, 3))
|
||||||
.getResult();
|
.getResult();
|
||||||
hYielded.push_back(next);
|
hYielded.push_back(next);
|
||||||
return success();
|
return success();
|
||||||
@@ -917,9 +868,7 @@ struct MatMulToGemm : OpRewritePattern<ONNXMatMulOp> {
|
|||||||
if (failed(shapeInfo) || shapeInfo->lhsWasVector || shapeInfo->rhsWasVector)
|
if (failed(shapeInfo) || shapeInfo->lhsWasVector || shapeInfo->rhsWasVector)
|
||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
const bool hasNonSingletonOutputBatch =
|
if (!shapeInfo->outputBatchShape.empty())
|
||||||
!shapeInfo->outputBatchShape.empty() && getStaticShapeElementCount(shapeInfo->outputBatchShape) != 1;
|
|
||||||
if (hasNonSingletonOutputBatch)
|
|
||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
Location loc = matmulOp.getLoc();
|
Location loc = matmulOp.getLoc();
|
||||||
@@ -1021,8 +970,8 @@ struct MatMulBatchedToSpatialComputes : OpRewritePattern<ONNXMatMulOp> {
|
|||||||
if (succeeded(paddedRhs)) {
|
if (succeeded(paddedRhs)) {
|
||||||
Value paddedLhs = createPaddedInputCompute(plan.lhs, paddedLhsType, rewriter, loc);
|
Value paddedLhs = createPaddedInputCompute(plan.lhs, paddedLhsType, rewriter, loc);
|
||||||
const int64_t laneCount = plan.batch * plan.m * numKSlices * numOutHSlices;
|
const int64_t laneCount = plan.batch * plan.m * numKSlices * numOutHSlices;
|
||||||
auto partialPiecesType = RankedTensorType::get({laneCount, static_cast<int64_t>(crossbarSize.getValue())},
|
auto partialPiecesType = spatial::getGraphBatchPhysicalResultType(
|
||||||
shapeInfo->outType.getElementType());
|
laneCount, RankedTensorType::get({1, static_cast<int64_t>(crossbarSize.getValue())}, shapeInfo->outType.getElementType()));
|
||||||
auto batchOp = createBatchedVmmBatch(paddedLhs,
|
auto batchOp = createBatchedVmmBatch(paddedLhs,
|
||||||
*paddedRhs,
|
*paddedRhs,
|
||||||
paddedLhsType,
|
paddedLhsType,
|
||||||
@@ -1063,7 +1012,8 @@ struct MatMulBatchedToSpatialComputes : OpRewritePattern<ONNXMatMulOp> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const int64_t laneCount = plan.batch * plan.m * plan.n;
|
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,
|
auto batchOp = createBatchedVvdmulBatch(plan.lhs,
|
||||||
plan.lhsBatchShape,
|
plan.lhsBatchShape,
|
||||||
plan.rhs,
|
plan.rhs,
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
#include "llvm/ADT/SmallVector.h"
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <numeric>
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
@@ -122,14 +121,6 @@ static RankedTensorType getKeepdimsType(RankedTensorType inputType, Type element
|
|||||||
return RankedTensorType::get(shape, elementType, inputType.getEncoding());
|
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) {
|
static RankedTensorType getReducedSliceType(RankedTensorType inputType, ArrayRef<bool> reducedAxes) {
|
||||||
SmallVector<int64_t> shape;
|
SmallVector<int64_t> shape;
|
||||||
shape.reserve(inputType.getRank());
|
shape.reserve(inputType.getRank());
|
||||||
@@ -139,9 +130,7 @@ static RankedTensorType getReducedSliceType(RankedTensorType inputType, ArrayRef
|
|||||||
}
|
}
|
||||||
|
|
||||||
static RankedTensorType getLanePackedKeepdimsType(int64_t laneCount, RankedTensorType leafType) {
|
static RankedTensorType getLanePackedKeepdimsType(int64_t laneCount, RankedTensorType leafType) {
|
||||||
SmallVector<int64_t> shape(leafType.getShape().begin(), leafType.getShape().end());
|
return spatial::getGraphBatchPhysicalResultType(laneCount, leafType);
|
||||||
shape.front() = laneCount;
|
|
||||||
return RankedTensorType::get(shape, leafType.getElementType(), leafType.getEncoding());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static SmallVector<int64_t> getKeptAxes(ArrayRef<bool> reducedAxes) {
|
static SmallVector<int64_t> getKeptAxes(ArrayRef<bool> reducedAxes) {
|
||||||
@@ -191,12 +180,9 @@ static FailureOr<Value> buildReduceMeanKeepdimsBatch(Value input,
|
|||||||
|
|
||||||
SmallVector<OpFoldResult> sliceOffsets;
|
SmallVector<OpFoldResult> sliceOffsets;
|
||||||
SmallVector<OpFoldResult> sliceSizes;
|
SmallVector<OpFoldResult> sliceSizes;
|
||||||
SmallVector<OpFoldResult> insertOffsets;
|
|
||||||
SmallVector<OpFoldResult> insertSizes(inputType.getRank(), rewriter.getIndexAttr(1));
|
|
||||||
SmallVector<OpFoldResult> unitStrides = getUnitStrides(rewriter, inputType.getRank());
|
SmallVector<OpFoldResult> unitStrides = getUnitStrides(rewriter, inputType.getRank());
|
||||||
sliceOffsets.reserve(inputType.getRank());
|
sliceOffsets.reserve(inputType.getRank());
|
||||||
sliceSizes.reserve(inputType.getRank());
|
sliceSizes.reserve(inputType.getRank());
|
||||||
insertOffsets.reserve(inputType.getRank());
|
|
||||||
|
|
||||||
auto batchOp =
|
auto batchOp =
|
||||||
createSpatComputeBatch(rewriter,
|
createSpatComputeBatch(rewriter,
|
||||||
@@ -209,7 +195,6 @@ static FailureOr<Value> buildReduceMeanKeepdimsBatch(Value input,
|
|||||||
size_t keptAxisIndex = 0;
|
size_t keptAxisIndex = 0;
|
||||||
sliceOffsets.clear();
|
sliceOffsets.clear();
|
||||||
sliceSizes.clear();
|
sliceSizes.clear();
|
||||||
insertOffsets.clear();
|
|
||||||
for (auto [axis, isReduced] : llvm::enumerate(reducedAxes)) {
|
for (auto [axis, isReduced] : llvm::enumerate(reducedAxes)) {
|
||||||
if (isReduced) {
|
if (isReduced) {
|
||||||
sliceOffsets.push_back(rewriter.getIndexAttr(0));
|
sliceOffsets.push_back(rewriter.getIndexAttr(0));
|
||||||
@@ -224,72 +209,90 @@ static FailureOr<Value> buildReduceMeanKeepdimsBatch(Value input,
|
|||||||
sliceSizes.push_back(rewriter.getIndexAttr(1));
|
sliceSizes.push_back(rewriter.getIndexAttr(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
insertOffsets.push_back(args.lane);
|
|
||||||
insertOffsets.append(inputType.getRank() - 1, rewriter.getIndexAttr(0));
|
|
||||||
|
|
||||||
Value slice = tensor::ExtractSliceOp::create(
|
Value slice = tensor::ExtractSliceOp::create(
|
||||||
rewriter, loc, sliceType, args.inputs.front(), sliceOffsets, sliceSizes, unitStrides);
|
rewriter, loc, sliceType, args.inputs.front(), sliceOffsets, sliceSizes, unitStrides);
|
||||||
Value reduced = spatial::SpatVAvgOp::create(rewriter, loc, leafType, slice).getResult();
|
Value reduced = spatial::SpatVAvgOp::create(rewriter, loc, leafType, slice).getResult();
|
||||||
createParallelInsertSliceIntoBatchOutput(
|
publishGraphBatchPhysicalFragment(rewriter, loc, reduced, args.outputs.front(), args.lane);
|
||||||
rewriter, loc, reduced, args.outputs.front(), insertOffsets, insertSizes, unitStrides);
|
|
||||||
});
|
});
|
||||||
if (failed(batchOp))
|
if (failed(batchOp))
|
||||||
return failure();
|
return failure();
|
||||||
return (*batchOp).getResult(0);
|
return (*batchOp).getResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value buildKeepdimsFromLanePackedBatch(Value batchValue,
|
static FailureOr<Value> buildReduceMeanKeepdimsBlueprint(
|
||||||
RankedTensorType keepdimsType,
|
Value batchValue, RankedTensorType keepdimsType,
|
||||||
RankedTensorType compactKeptType,
|
ArrayRef<bool> reducedAxes, ConversionPatternRewriter& rewriter,
|
||||||
ArrayRef<bool> reducedAxes,
|
|
||||||
ConversionPatternRewriter& rewriter,
|
|
||||||
Location loc) {
|
Location loc) {
|
||||||
auto batchType = cast<RankedTensorType>(batchValue.getType());
|
auto batchType = dyn_cast<RankedTensorType>(batchValue.getType());
|
||||||
if (batchType == keepdimsType)
|
int64_t rank = keepdimsType.getRank();
|
||||||
return batchValue;
|
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 {{}};
|
int64_t laneCount = 1;
|
||||||
for (int64_t axis = 0; axis < batchType.getRank(); ++axis)
|
SmallVector<int64_t> keptAxes;
|
||||||
collapseToFlat.front().push_back(axis);
|
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);
|
SmallVector<int64_t> operandIndices(laneCount, 0);
|
||||||
for (int64_t axis = 0; axis < compactKeptType.getRank(); ++axis)
|
SmallVector<int64_t> sourceSlots;
|
||||||
expandFlatToCompact.front().push_back(axis);
|
SmallVector<int64_t> sourceOffsets(laneCount, 0);
|
||||||
|
SmallVector<int64_t> fragmentOffsets;
|
||||||
SmallVector<ReassociationIndices> expandCompactToKeepdims;
|
sourceSlots.reserve(laneCount);
|
||||||
ReassociationIndices pendingLeadingReducedAxes;
|
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)) {
|
for (auto [axis, isReduced] : llvm::enumerate(reducedAxes)) {
|
||||||
if (isReduced) {
|
if (isReduced) {
|
||||||
if (expandCompactToKeepdims.empty())
|
fragmentOffsets.push_back(0);
|
||||||
pendingLeadingReducedAxes.push_back(axis);
|
|
||||||
else
|
|
||||||
expandCompactToKeepdims.back().push_back(axis);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
int64_t dim = keepdimsType.getDimSize(axis);
|
||||||
expandCompactToKeepdims.emplace_back();
|
fragmentOffsets.push_back(
|
||||||
auto& group = expandCompactToKeepdims.back();
|
(lane / keptAxisStrides[keptAxisIndex]) % dim);
|
||||||
group.append(pendingLeadingReducedAxes.begin(), pendingLeadingReducedAxes.end());
|
++keptAxisIndex;
|
||||||
pendingLeadingReducedAxes.clear();
|
|
||||||
group.push_back(axis);
|
|
||||||
}
|
}
|
||||||
if (!pendingLeadingReducedAxes.empty())
|
}
|
||||||
expandCompactToKeepdims.back().append(pendingLeadingReducedAxes.begin(), pendingLeadingReducedAxes.end());
|
SmallVector<int64_t> fragmentSizes(fragmentOffsets.size(), 1);
|
||||||
|
SmallVector<int64_t> fragmentStrides(fragmentOffsets.size(), 1);
|
||||||
auto reshapeCompute =
|
return spatial::SpatBlueprintOp::create(
|
||||||
createSpatCompute<1>(rewriter, loc, TypeRange {keepdimsType}, {}, ValueRange {batchValue}, [&](Value input) {
|
rewriter, loc, keepdimsType, batchValue, ValueRange {},
|
||||||
auto flatType =
|
rewriter.getStringAttr("nchw"),
|
||||||
RankedTensorType::get({batchType.getDimSize(0)}, batchType.getElementType(), batchType.getEncoding());
|
rewriter.getStringAttr("fragmented"),
|
||||||
Value flat = tensor::CollapseShapeOp::create(rewriter, loc, flatType, input, collapseToFlat);
|
rewriter.getDenseI64ArrayAttr(fragmentOffsets),
|
||||||
Value compact = flat;
|
rewriter.getDenseI64ArrayAttr(fragmentSizes),
|
||||||
if (compactKeptType != flatType)
|
rewriter.getStringAttr("reduce_mean_keepdims_fragments"),
|
||||||
compact = tensor::ExpandShapeOp::create(rewriter, loc, compactKeptType, flat, expandFlatToCompact);
|
rewriter.getStringAttr("fragment_assembly"),
|
||||||
Value keepdims = compact;
|
rewriter.getDenseI64ArrayAttr(operandIndices),
|
||||||
if (keepdimsType != compactKeptType)
|
rewriter.getDenseI64ArrayAttr(sourceSlots),
|
||||||
keepdims = tensor::ExpandShapeOp::create(rewriter, loc, keepdimsType, compact, expandCompactToKeepdims);
|
rewriter.getDenseI64ArrayAttr(sourceOffsets),
|
||||||
spatial::SpatYieldOp::create(rewriter, loc, keepdims);
|
rewriter.getDenseI64ArrayAttr(fragmentStrides),
|
||||||
});
|
rewriter.getStringAttr("disjoint"),
|
||||||
return reshapeCompute.getResult(0);
|
rewriter.getStringAttr("complete"))
|
||||||
|
.getOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
static SmallVector<ReassociationIndices> buildCollapseReassociation(ArrayRef<bool> reducedAxes) {
|
static SmallVector<ReassociationIndices> buildCollapseReassociation(ArrayRef<bool> reducedAxes) {
|
||||||
@@ -366,26 +369,36 @@ struct ReduceMeanToSpatialCompute : OpConversionPattern<ReduceMeanOp> {
|
|||||||
|
|
||||||
Location loc = reduceMeanOp.getLoc();
|
Location loc = reduceMeanOp.getLoc();
|
||||||
RankedTensorType leafType = getAllOnesType(inputType, resultType.getElementType());
|
RankedTensorType leafType = getAllOnesType(inputType, resultType.getElementType());
|
||||||
RankedTensorType compactKeptType = getCompactKeptType(inputType, resultType.getElementType(), reducedAxes);
|
|
||||||
RankedTensorType keepdimsType = getKeepdimsType(inputType, resultType.getElementType(), reducedAxes);
|
RankedTensorType keepdimsType = getKeepdimsType(inputType, resultType.getElementType(), reducedAxes);
|
||||||
int64_t laneCount = 1;
|
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;
|
laneCount *= dim;
|
||||||
|
}
|
||||||
RankedTensorType batchType = getLanePackedKeepdimsType(laneCount, leafType);
|
RankedTensorType batchType = getLanePackedKeepdimsType(laneCount, leafType);
|
||||||
|
|
||||||
auto lanePackedKeepdims =
|
auto lanePackedKeepdims =
|
||||||
buildReduceMeanKeepdimsBatch(adaptor.getData(), reducedAxes, batchType, leafType, rewriter, loc);
|
buildReduceMeanKeepdimsBatch(adaptor.getData(), reducedAxes, batchType, leafType, rewriter, loc);
|
||||||
if (failed(lanePackedKeepdims))
|
if (failed(lanePackedKeepdims))
|
||||||
return failure();
|
return failure();
|
||||||
Value reducedKeepdims =
|
auto reducedKeepdims = buildReduceMeanKeepdimsBlueprint(
|
||||||
buildKeepdimsFromLanePackedBatch(*lanePackedKeepdims, keepdimsType, compactKeptType, reducedAxes, rewriter, loc);
|
*lanePackedKeepdims, keepdimsType, reducedAxes, rewriter, loc);
|
||||||
|
if (failed(reducedKeepdims))
|
||||||
|
return rewriter.notifyMatchFailure(
|
||||||
|
reduceMeanOp,
|
||||||
|
"cannot build physical-fragment ReduceMean keepdims reconstruction");
|
||||||
|
|
||||||
if (semantics->keepdims != 0) {
|
if (semantics->keepdims != 0) {
|
||||||
rewriter.replaceOp(reduceMeanOp, reducedKeepdims);
|
rewriter.replaceOp(reduceMeanOp, *reducedKeepdims);
|
||||||
return success();
|
return success();
|
||||||
}
|
}
|
||||||
|
|
||||||
Value reduced = squeezeReducedAxes(reducedKeepdims, resultType, reducedAxes, rewriter, loc);
|
Value reduced = squeezeReducedAxes(
|
||||||
|
*reducedKeepdims, resultType, reducedAxes, rewriter, loc);
|
||||||
rewriter.replaceOp(reduceMeanOp, reduced);
|
rewriter.replaceOp(reduceMeanOp, reduced);
|
||||||
return success();
|
return success();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include "src/Accelerators/PIM/Common/IR/WeightUtils.hpp"
|
#include "src/Accelerators/PIM/Common/IR/WeightUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.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/Common/WeightMaterialization.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
@@ -128,8 +129,6 @@ struct PromoteWeightLikeComputeInputsPattern : OpRewritePattern<spatial::SpatGra
|
|||||||
Block& oldBlock = compute.getBody().front();
|
Block& oldBlock = compute.getBody().front();
|
||||||
|
|
||||||
rewriter.setInsertionPointAfter(compute);
|
rewriter.setInsertionPointAfter(compute);
|
||||||
auto newCompute = spatial::SpatGraphCompute::create(
|
|
||||||
rewriter, compute.getLoc(), compute.getResultTypes(), promoted->newWeights, promoted->newInputs);
|
|
||||||
SmallVector<Type> newBlockArgTypes;
|
SmallVector<Type> newBlockArgTypes;
|
||||||
SmallVector<Location> newBlockArgLocs;
|
SmallVector<Location> newBlockArgLocs;
|
||||||
for (Value weight : promoted->newWeights) {
|
for (Value weight : promoted->newWeights) {
|
||||||
@@ -138,10 +137,14 @@ struct PromoteWeightLikeComputeInputsPattern : OpRewritePattern<spatial::SpatGra
|
|||||||
}
|
}
|
||||||
llvm::append_range(newBlockArgTypes, promoted->newInputTypes);
|
llvm::append_range(newBlockArgTypes, promoted->newInputTypes);
|
||||||
llvm::append_range(newBlockArgLocs, promoted->newInputLocs);
|
llvm::append_range(newBlockArgLocs, promoted->newInputLocs);
|
||||||
auto* newBlock = rewriter.createBlock(
|
auto newCompute = createEmptySpatGraphCompute(rewriter,
|
||||||
&newCompute.getBody(), newCompute.getBody().end(), TypeRange(newBlockArgTypes), newBlockArgLocs);
|
compute.getLoc(),
|
||||||
newCompute.getProperties().setOperandSegmentSizes(
|
compute.getResultTypes(),
|
||||||
{static_cast<int>(promoted->newWeights.size()), static_cast<int>(promoted->newInputs.size())});
|
promoted->newWeights,
|
||||||
|
promoted->newInputs,
|
||||||
|
TypeRange(newBlockArgTypes),
|
||||||
|
newBlockArgLocs);
|
||||||
|
auto* newBlock = &newCompute.getBody().front();
|
||||||
rewriter.setInsertionPointToStart(newBlock);
|
rewriter.setInsertionPointToStart(newBlock);
|
||||||
|
|
||||||
IRRewriter bodyRewriter(rewriter.getContext());
|
IRRewriter bodyRewriter(rewriter.getContext());
|
||||||
@@ -193,12 +196,6 @@ struct PromoteWeightLikeComputeBatchInputsPattern : OpRewritePattern<spatial::Sp
|
|||||||
|
|
||||||
rewriter.setInsertionPointAfter(compute);
|
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();
|
auto laneArg = compute.getLaneArgument();
|
||||||
if (!laneArg)
|
if (!laneArg)
|
||||||
return rewriter.notifyMatchFailure(compute, "missing compute_batch lane block argument");
|
return rewriter.notifyMatchFailure(compute, "missing compute_batch lane block argument");
|
||||||
@@ -223,23 +220,30 @@ struct PromoteWeightLikeComputeBatchInputsPattern : OpRewritePattern<spatial::Sp
|
|||||||
newBlockArgLocs.push_back(outputArg->getLoc());
|
newBlockArgLocs.push_back(outputArg->getLoc());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* newBlock = rewriter.createBlock(
|
auto newCompute = createEmptySpatGraphComputeBatch(rewriter,
|
||||||
&newCompute.getBody(), newCompute.getBody().end(), TypeRange(newBlockArgTypes), newBlockArgLocs);
|
compute.getLoc(),
|
||||||
newCompute.getProperties().setOperandSegmentSizes(
|
compute.getResultTypes(),
|
||||||
{static_cast<int>(promoted->newWeights.size()), static_cast<int>(promoted->newInputs.size())});
|
compute.getLaneCount(),
|
||||||
|
promoted->newWeights,
|
||||||
|
promoted->newInputs,
|
||||||
|
TypeRange(newBlockArgTypes),
|
||||||
|
newBlockArgLocs);
|
||||||
|
if (failed(newCompute))
|
||||||
|
return failure();
|
||||||
|
auto* newBlock = &(*newCompute).getBody().front();
|
||||||
rewriter.setInsertionPointToStart(newBlock);
|
rewriter.setInsertionPointToStart(newBlock);
|
||||||
|
|
||||||
IRRewriter bodyRewriter(rewriter.getContext());
|
IRRewriter bodyRewriter(rewriter.getContext());
|
||||||
bodyRewriter.setInsertionPointToStart(newBlock);
|
bodyRewriter.setInsertionPointToStart(newBlock);
|
||||||
|
|
||||||
IRMapping mapper;
|
IRMapping mapper;
|
||||||
auto newLaneArg = newCompute.getLaneArgument();
|
auto newLaneArg = (*newCompute).getLaneArgument();
|
||||||
if (!newLaneArg)
|
if (!newLaneArg)
|
||||||
return rewriter.notifyMatchFailure(compute, "missing rewritten compute_batch lane block argument");
|
return rewriter.notifyMatchFailure(compute, "missing rewritten compute_batch lane block argument");
|
||||||
mapper.map(*laneArg, *newLaneArg);
|
mapper.map(*laneArg, *newLaneArg);
|
||||||
for (auto [weightIndex, weight] : llvm::enumerate(compute.getWeights())) {
|
for (auto [weightIndex, weight] : llvm::enumerate(compute.getWeights())) {
|
||||||
auto oldWeightArg = compute.getWeightArgument(weightIndex);
|
auto oldWeightArg = compute.getWeightArgument(weightIndex);
|
||||||
auto newWeightArg = newCompute.getWeightArgument(weightIndex);
|
auto newWeightArg = (*newCompute).getWeightArgument(weightIndex);
|
||||||
if (!oldWeightArg || !newWeightArg)
|
if (!oldWeightArg || !newWeightArg)
|
||||||
return rewriter.notifyMatchFailure(compute, "missing compute_batch weight block argument during rewrite");
|
return rewriter.notifyMatchFailure(compute, "missing compute_batch weight block argument during rewrite");
|
||||||
mapper.map(*oldWeightArg, *newWeightArg);
|
mapper.map(*oldWeightArg, *newWeightArg);
|
||||||
@@ -249,7 +253,7 @@ struct PromoteWeightLikeComputeBatchInputsPattern : OpRewritePattern<spatial::Sp
|
|||||||
*promoted,
|
*promoted,
|
||||||
bodyRewriter,
|
bodyRewriter,
|
||||||
mapper,
|
mapper,
|
||||||
[&](size_t index) { return newCompute.getInputArgument(index); },
|
[&](size_t index) { return (*newCompute).getInputArgument(index); },
|
||||||
rewriter)))
|
rewriter)))
|
||||||
return failure();
|
return failure();
|
||||||
for (auto resultIndex : llvm::seq<size_t>(0, compute.getNumResults())) {
|
for (auto resultIndex : llvm::seq<size_t>(0, compute.getNumResults())) {
|
||||||
@@ -263,7 +267,7 @@ struct PromoteWeightLikeComputeBatchInputsPattern : OpRewritePattern<spatial::Sp
|
|||||||
for (Operation& op : oldBlock)
|
for (Operation& op : oldBlock)
|
||||||
rewriter.clone(op, mapper);
|
rewriter.clone(op, mapper);
|
||||||
|
|
||||||
rewriter.replaceOp(compute, newCompute.getResults());
|
rewriter.replaceOp(compute, (*newCompute).getResults());
|
||||||
return success();
|
return success();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
|
#include "mlir/Transforms/DialectConversion.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
|
||||||
|
#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"
|
||||||
|
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||||
|
|
||||||
|
using namespace mlir;
|
||||||
|
|
||||||
|
namespace onnx_mlir {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
static FailureOr<int64_t> normalizeFlattenAxis(int64_t axis, int64_t rank) {
|
||||||
|
int64_t normalizedAxis = axis < 0 ? rank + axis : axis;
|
||||||
|
if (normalizedAxis < 0 || normalizedAxis > rank)
|
||||||
|
return failure();
|
||||||
|
return normalizedAxis;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t product(ArrayRef<int64_t> values) {
|
||||||
|
int64_t result = 1;
|
||||||
|
for (int64_t value : values)
|
||||||
|
result *= value;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SmallVector<ReassociationIndices> getCollapseTo1DReassociation(int64_t rank) {
|
||||||
|
SmallVector<ReassociationIndices> reassociation(1);
|
||||||
|
reassociation.front().reserve(rank);
|
||||||
|
for (int64_t dim = 0; dim < rank; ++dim)
|
||||||
|
reassociation.front().push_back(dim);
|
||||||
|
return reassociation;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SmallVector<ReassociationIndices> getExpandFrom1DReassociation(int64_t rank) {
|
||||||
|
SmallVector<ReassociationIndices> reassociation(1);
|
||||||
|
reassociation.front().reserve(rank);
|
||||||
|
for (int64_t dim = 0; dim < rank; ++dim)
|
||||||
|
reassociation.front().push_back(dim);
|
||||||
|
return reassociation;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Value buildFlatten(Value input,
|
||||||
|
RankedTensorType sourceType,
|
||||||
|
RankedTensorType resultType,
|
||||||
|
int64_t axis,
|
||||||
|
ConversionPatternRewriter& rewriter,
|
||||||
|
Location loc) {
|
||||||
|
if (sourceType == resultType)
|
||||||
|
return input;
|
||||||
|
|
||||||
|
if (axis > 0 && axis < sourceType.getRank()) {
|
||||||
|
SmallVector<ReassociationIndices> reassociation(2);
|
||||||
|
for (int64_t dim = 0; dim < axis; ++dim)
|
||||||
|
reassociation[0].push_back(dim);
|
||||||
|
for (int64_t dim = axis; dim < sourceType.getRank(); ++dim)
|
||||||
|
reassociation[1].push_back(dim);
|
||||||
|
return tensor::CollapseShapeOp::create(rewriter, loc, resultType, input, reassociation);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value flattened = input;
|
||||||
|
if (sourceType.getRank() != 1) {
|
||||||
|
auto flatType = RankedTensorType::get({sourceType.getNumElements()}, sourceType.getElementType());
|
||||||
|
flattened = tensor::CollapseShapeOp::create(
|
||||||
|
rewriter, loc, flatType, flattened, getCollapseTo1DReassociation(sourceType.getRank()));
|
||||||
|
}
|
||||||
|
return tensor::ExpandShapeOp::create(
|
||||||
|
rewriter, loc, resultType, flattened, getExpandFrom1DReassociation(resultType.getRank()));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Flatten : OpConversionPattern<ONNXFlattenOp> {
|
||||||
|
using OpConversionPattern::OpConversionPattern;
|
||||||
|
|
||||||
|
LogicalResult matchAndRewrite(ONNXFlattenOp flattenOp,
|
||||||
|
ONNXFlattenOpAdaptor adaptor,
|
||||||
|
ConversionPatternRewriter& rewriter) const override {
|
||||||
|
auto sourceType = dyn_cast<RankedTensorType>(adaptor.getInput().getType());
|
||||||
|
auto resultType = dyn_cast<RankedTensorType>(flattenOp.getOperation()->getResult(0).getType());
|
||||||
|
if (!sourceType || !resultType || !sourceType.hasStaticShape() || !resultType.hasStaticShape())
|
||||||
|
return failure();
|
||||||
|
if (!hasStaticPositiveShape(sourceType) || !hasStaticPositiveShape(resultType) || resultType.getRank() != 2)
|
||||||
|
return failure();
|
||||||
|
|
||||||
|
auto axis = normalizeFlattenAxis(flattenOp.getAxis(), sourceType.getRank());
|
||||||
|
if (failed(axis))
|
||||||
|
return failure();
|
||||||
|
|
||||||
|
int64_t outerDim = product(sourceType.getShape().take_front(*axis));
|
||||||
|
int64_t innerDim = product(sourceType.getShape().drop_front(*axis));
|
||||||
|
if (resultType.getShape()[0] != outerDim || resultType.getShape()[1] != innerDim)
|
||||||
|
return failure();
|
||||||
|
|
||||||
|
auto replaceWithFlatten = [&](auto build) -> LogicalResult {
|
||||||
|
Value flattened = materializeOrComputeUnary(adaptor.getInput(), resultType, rewriter, flattenOp.getLoc(), build);
|
||||||
|
rewriter.replaceOp(flattenOp, flattened);
|
||||||
|
return success();
|
||||||
|
};
|
||||||
|
|
||||||
|
return replaceWithFlatten([&](Value input) {
|
||||||
|
return buildFlatten(input, sourceType, resultType, *axis, rewriter, flattenOp.getLoc());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void populateFlattenPatterns(RewritePatternSet& patterns, MLIRContext* ctx) { patterns.add<Flatten>(ctx); }
|
||||||
|
|
||||||
|
} // namespace onnx_mlir
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
#include "llvm/ADT/SmallVector.h"
|
#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/Common/Common.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||||
@@ -52,35 +52,12 @@ static FailureOr<Value> materializeTransposedConstant(Value input,
|
|||||||
return failure();
|
return failure();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (denseAttr.isSplat())
|
auto transposedAttr = transposeDenseElementsAttr(denseAttr, permutation);
|
||||||
|
if (failed(transposedAttr) || transposedAttr->getType() != resultType)
|
||||||
|
return failure();
|
||||||
return getOrCreateConstant(rewriter,
|
return getOrCreateConstant(rewriter,
|
||||||
rewriter.getInsertionBlock()->getParentOp(),
|
rewriter.getInsertionBlock()->getParentOp(),
|
||||||
DenseElementsAttr::get(resultType, denseAttr.getSplatValue<Attribute>()),
|
*transposedAttr,
|
||||||
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),
|
|
||||||
resultType);
|
resultType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,11 @@
|
|||||||
|
|
||||||
#include "Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp"
|
#include "Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/PimCommon.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/Conversion/ONNXToSpatial/PlanLowering.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
#include "src/Accelerators/PIM/Pass/PIMPasses.h"
|
#include "src/Accelerators/PIM/Pass/PIMPasses.h"
|
||||||
#include "src/Accelerators/PIM/Pass/PIMPasses.h"
|
|
||||||
|
|
||||||
using namespace mlir;
|
using namespace mlir;
|
||||||
|
|
||||||
@@ -19,7 +20,6 @@ namespace {
|
|||||||
static constexpr StringLiteral kLogicalLayout = "nchw";
|
static constexpr StringLiteral kLogicalLayout = "nchw";
|
||||||
static constexpr StringLiteral kDenseLayout = "dense_nchw";
|
static constexpr StringLiteral kDenseLayout = "dense_nchw";
|
||||||
static constexpr StringLiteral kRowStripLayout = "nchw_row_strip";
|
static constexpr StringLiteral kRowStripLayout = "nchw_row_strip";
|
||||||
static constexpr StringLiteral kRowStripIndexMap = "packed_hwc_rows_to_nchw";
|
|
||||||
|
|
||||||
enum class SelectedLayout {
|
enum class SelectedLayout {
|
||||||
DenseNchw,
|
DenseNchw,
|
||||||
@@ -34,6 +34,8 @@ static SelectedLayout getSelectedLayout(llvm::DenseMap<Value, SelectedLayout>& l
|
|||||||
static bool usesSelectedRowStrip(Operation* user, llvm::DenseMap<Value, SelectedLayout>& layouts) {
|
static bool usesSelectedRowStrip(Operation* user, llvm::DenseMap<Value, SelectedLayout>& layouts) {
|
||||||
if (auto reluPlan = dyn_cast<spatial::SpatReluPlanOp>(user))
|
if (auto reluPlan = dyn_cast<spatial::SpatReluPlanOp>(user))
|
||||||
return getSelectedLayout(layouts, reluPlan.getResult()) == SelectedLayout::NchwRowStrip;
|
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))
|
if (auto convPlan = dyn_cast<spatial::SpatConv2DPlanOp>(user))
|
||||||
return getSelectedLayout(layouts, convPlan.getResult()) == SelectedLayout::NchwRowStrip;
|
return getSelectedLayout(layouts, convPlan.getResult()) == SelectedLayout::NchwRowStrip;
|
||||||
return false;
|
return false;
|
||||||
@@ -49,21 +51,26 @@ static bool allUsersCanHandleRowStrip(Value value, llvm::DenseMap<Value, Selecte
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::pair<SmallVector<int64_t>, SmallVector<int64_t>> buildRowStripMetadata(RankedTensorType type) {
|
static bool canConsumeRowStripAsUser(Operation* user) {
|
||||||
SmallVector<int64_t> offsets;
|
if (isa<spatial::SpatReluPlanOp>(user))
|
||||||
SmallVector<int64_t> sizes;
|
return true;
|
||||||
const int64_t channels = type.getDimSize(1);
|
if (auto biasAddPlan = dyn_cast<spatial::SpatBiasAddPlanOp>(user)) {
|
||||||
const int64_t height = type.getDimSize(2);
|
auto resultType = dyn_cast<RankedTensorType>(biasAddPlan.getOutput().getType());
|
||||||
const int64_t width = type.getDimSize(3);
|
return resultType && isSupportedBiasAddValue(biasAddPlan.getBias(), resultType);
|
||||||
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};
|
if (auto convPlan = dyn_cast<spatial::SpatConv2DPlanOp>(user))
|
||||||
|
return succeeded(canConsumeAndProduceRowStrip(convPlan));
|
||||||
|
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,
|
static bool canSelectConvRowStrip(spatial::SpatConv2DPlanOp convPlan,
|
||||||
llvm::DenseMap<Value, SelectedLayout>& layouts) {
|
llvm::DenseMap<Value, SelectedLayout>& layouts) {
|
||||||
SelectedLayout inputLayout = getSelectedLayout(layouts, convPlan.getInput());
|
SelectedLayout inputLayout = getSelectedLayout(layouts, convPlan.getInput());
|
||||||
@@ -76,6 +83,9 @@ static SelectedLayout chooseConvLayout(spatial::SpatConv2DPlanOp convPlan,
|
|||||||
llvm::DenseMap<Value, SelectedLayout>& layouts) {
|
llvm::DenseMap<Value, SelectedLayout>& layouts) {
|
||||||
if (!canSelectConvRowStrip(convPlan, layouts))
|
if (!canSelectConvRowStrip(convPlan, layouts))
|
||||||
return SelectedLayout::DenseNchw;
|
return SelectedLayout::DenseNchw;
|
||||||
|
if (getSelectedLayout(layouts, convPlan.getInput()) != SelectedLayout::NchwRowStrip
|
||||||
|
&& !hasRowStripConsumer(convPlan.getResult()))
|
||||||
|
return SelectedLayout::DenseNchw;
|
||||||
if (!allUsersCanHandleRowStrip(convPlan.getResult(), layouts))
|
if (!allUsersCanHandleRowStrip(convPlan.getResult(), layouts))
|
||||||
return SelectedLayout::DenseNchw;
|
return SelectedLayout::DenseNchw;
|
||||||
return SelectedLayout::NchwRowStrip;
|
return SelectedLayout::NchwRowStrip;
|
||||||
@@ -85,11 +95,27 @@ static SelectedLayout chooseReluLayout(spatial::SpatReluPlanOp reluPlan,
|
|||||||
llvm::DenseMap<Value, SelectedLayout>& layouts) {
|
llvm::DenseMap<Value, SelectedLayout>& layouts) {
|
||||||
if (getSelectedLayout(layouts, reluPlan.getInput()) != SelectedLayout::NchwRowStrip)
|
if (getSelectedLayout(layouts, reluPlan.getInput()) != SelectedLayout::NchwRowStrip)
|
||||||
return SelectedLayout::DenseNchw;
|
return SelectedLayout::DenseNchw;
|
||||||
|
if (!hasRowStripConsumer(reluPlan.getResult()))
|
||||||
|
return SelectedLayout::DenseNchw;
|
||||||
if (!allUsersCanHandleRowStrip(reluPlan.getResult(), layouts))
|
if (!allUsersCanHandleRowStrip(reluPlan.getResult(), layouts))
|
||||||
return SelectedLayout::DenseNchw;
|
return SelectedLayout::DenseNchw;
|
||||||
return SelectedLayout::NchwRowStrip;
|
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 spatial::SpatBlueprintOp insertRowStripBlueprint(IRRewriter& rewriter, Value value) {
|
static spatial::SpatBlueprintOp insertRowStripBlueprint(IRRewriter& rewriter, Value value) {
|
||||||
auto outputType = cast<RankedTensorType>(value.getType());
|
auto outputType = cast<RankedTensorType>(value.getType());
|
||||||
auto [offsets, sizes] = buildRowStripMetadata(outputType);
|
auto [offsets, sizes] = buildRowStripMetadata(outputType);
|
||||||
@@ -108,6 +134,7 @@ static spatial::SpatBlueprintOp insertRowStripBlueprint(IRRewriter& rewriter, Va
|
|||||||
nullptr,
|
nullptr,
|
||||||
nullptr,
|
nullptr,
|
||||||
nullptr,
|
nullptr,
|
||||||
|
nullptr,
|
||||||
nullptr);
|
nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,6 +200,14 @@ struct SpatialLayoutPlanningPass final : PassWrapper<SpatialLayoutPlanningPass,
|
|||||||
}
|
}
|
||||||
continue;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,6 +215,8 @@ struct SpatialLayoutPlanningPass final : PassWrapper<SpatialLayoutPlanningPass,
|
|||||||
Value producedValue;
|
Value producedValue;
|
||||||
if (auto convPlan = dyn_cast<spatial::SpatConv2DPlanOp>(&op))
|
if (auto convPlan = dyn_cast<spatial::SpatConv2DPlanOp>(&op))
|
||||||
producedValue = convPlan.getResult();
|
producedValue = convPlan.getResult();
|
||||||
|
else if (auto biasAddPlan = dyn_cast<spatial::SpatBiasAddPlanOp>(&op))
|
||||||
|
producedValue = biasAddPlan.getResult();
|
||||||
else if (auto reluPlan = dyn_cast<spatial::SpatReluPlanOp>(&op))
|
else if (auto reluPlan = dyn_cast<spatial::SpatReluPlanOp>(&op))
|
||||||
producedValue = reluPlan.getResult();
|
producedValue = reluPlan.getResult();
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -141,7 +141,8 @@ collectTopLevelFragmentAssemblyCopies(OpResult result, RankedTensorType packedRe
|
|||||||
std::optional<StringRef> mode = blueprint.getMode();
|
std::optional<StringRef> mode = blueprint.getMode();
|
||||||
std::optional<ArrayRef<int64_t>> operandIndicesAttr = blueprint.getFragmentOperandIndices();
|
std::optional<ArrayRef<int64_t>> operandIndicesAttr = blueprint.getFragmentOperandIndices();
|
||||||
std::optional<ArrayRef<int64_t>> sourceOffsetsAttr = blueprint.getFragmentSourceOffsets();
|
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();
|
return failure();
|
||||||
if (!blueprint.getOutput().hasOneUse() || !isa<func::ReturnOp>(*blueprint.getOutput().getUsers().begin()))
|
if (!blueprint.getOutput().hasOneUse() || !isa<func::ReturnOp>(*blueprint.getOutput().getUsers().begin()))
|
||||||
return failure();
|
return failure();
|
||||||
@@ -153,6 +154,9 @@ collectTopLevelFragmentAssemblyCopies(OpResult result, RankedTensorType packedRe
|
|||||||
|
|
||||||
ArrayRef<int64_t> operandIndices = *operandIndicesAttr;
|
ArrayRef<int64_t> operandIndices = *operandIndicesAttr;
|
||||||
ArrayRef<int64_t> sourceOffsets = *sourceOffsetsAttr;
|
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> flatOffsets = blueprint.getFragmentOffsets();
|
||||||
ArrayRef<int64_t> flatSizes = blueprint.getFragmentSizes();
|
ArrayRef<int64_t> flatSizes = blueprint.getFragmentSizes();
|
||||||
ArrayRef<int64_t> flatStrides = *stridesAttr;
|
ArrayRef<int64_t> flatStrides = *stridesAttr;
|
||||||
@@ -174,7 +178,8 @@ collectTopLevelFragmentAssemblyCopies(OpResult result, RankedTensorType packedRe
|
|||||||
if (operandIndices[fragmentIndex] != static_cast<int64_t>(use.getOperandNumber()))
|
if (operandIndices[fragmentIndex] != static_cast<int64_t>(use.getOperandNumber()))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
int64_t sourceElementOffset = sourceOffsets[fragmentIndex];
|
int64_t sourceElementOffset =
|
||||||
|
sourceSlots[fragmentIndex] * payloadElementCount + sourceOffsets[fragmentIndex];
|
||||||
int64_t lane = sourceElementOffset / payloadElementCount;
|
int64_t lane = sourceElementOffset / payloadElementCount;
|
||||||
if (lane < 0 || lane >= static_cast<int64_t>(laneCount))
|
if (lane < 0 || lane >= static_cast<int64_t>(laneCount))
|
||||||
return failure();
|
return failure();
|
||||||
@@ -395,6 +400,11 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatSchedul
|
|||||||
if (isa<spatial::SpatYieldOp>(op))
|
if (isa<spatial::SpatYieldOp>(op))
|
||||||
continue;
|
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)) {
|
if (auto blueprint = dyn_cast<spatial::SpatBlueprintOp>(op)) {
|
||||||
std::optional<StringRef> modeAttr = blueprint.getMode();
|
std::optional<StringRef> modeAttr = blueprint.getMode();
|
||||||
if (modeAttr && *modeAttr == "fragment_assembly") {
|
if (modeAttr && *modeAttr == "fragment_assembly") {
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
#include "Common.hpp"
|
#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/Common/IR/LoopUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||||
@@ -192,21 +194,23 @@ forEachContiguousDestinationChunk(ArrayRef<int64_t> destShape,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static mlir::Value
|
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)
|
if (stepBytes == 0)
|
||||||
return start;
|
return start;
|
||||||
mlir::Value step = arith::ConstantIndexOp::create(builder, loc, stepBytes);
|
return createOrFoldAffineApply(
|
||||||
mlir::Value scaled = arith::MulIOp::create(builder, loc, index, step).getResult();
|
builder, loc, builder.getAffineDimExpr(0) + builder.getAffineDimExpr(1) * stepBytes,
|
||||||
return arith::AddIOp::create(builder, loc, start, scaled).getResult();
|
ValueRange {start, index}, constantAnchor);
|
||||||
}
|
}
|
||||||
|
|
||||||
static mlir::Value createIndexedOffset(OpBuilder& builder,
|
static mlir::Value createIndexedOffset(OpBuilder& builder,
|
||||||
Location loc,
|
Location loc,
|
||||||
mlir::Value indexArg,
|
mlir::Value indexArg,
|
||||||
ArrayRef<int64_t> values) {
|
ArrayRef<int64_t> values,
|
||||||
|
Operation *constantAnchor) {
|
||||||
assert(!values.empty() && "expected lane-indexed values");
|
assert(!values.empty() && "expected lane-indexed values");
|
||||||
if (llvm::all_of(values.drop_front(), [&](int64_t value) { return value == values.front(); }))
|
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) {
|
if (values.size() >= 2) {
|
||||||
int64_t step = values[1] - values[0];
|
int64_t step = values[1] - values[0];
|
||||||
@@ -214,21 +218,18 @@ static mlir::Value createIndexedOffset(OpBuilder& builder,
|
|||||||
return values[index] == values.front() + static_cast<int64_t>(index) * step;
|
return values[index] == values.front() + static_cast<int64_t>(index) * step;
|
||||||
});
|
});
|
||||||
if (arithmetic) {
|
if (arithmetic) {
|
||||||
mlir::Value base = arith::ConstantIndexOp::create(builder, loc, values.front());
|
return createOrFoldAffineApply(
|
||||||
mlir::Value stepValue = arith::ConstantIndexOp::create(builder, loc, step);
|
builder, loc, builder.getAffineDimExpr(0) * step + values.front(),
|
||||||
mlir::Value scaledIndex = arith::MulIOp::create(builder, loc, indexArg, stepValue).getResult();
|
ValueRange {indexArg}, constantAnchor);
|
||||||
return arith::AddIOp::create(builder, loc, base, scaledIndex).getResult();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mlir::Value selected = arith::ConstantIndexOp::create(builder, loc, values.front());
|
RankedTensorType tableType = RankedTensorType::get(
|
||||||
for (auto [lane, value] : llvm::enumerate(values.drop_front())) {
|
{static_cast<int64_t>(values.size())}, builder.getI64Type());
|
||||||
mlir::Value indexValue = arith::ConstantIndexOp::create(builder, loc, static_cast<int64_t>(lane + 1));
|
DenseElementsAttr tableAttr = DenseElementsAttr::get(tableType, values);
|
||||||
mlir::Value cmp = arith::CmpIOp::create(builder, loc, arith::CmpIPredicate::eq, indexArg, indexValue);
|
mlir::Value table = getOrCreateConstant(builder, constantAnchor, tableAttr, tableType);
|
||||||
mlir::Value candidate = arith::ConstantIndexOp::create(builder, loc, value);
|
mlir::Value selected = tensor::ExtractOp::create(builder, loc, table, ValueRange {indexArg});
|
||||||
selected = arith::SelectOp::create(builder, loc, cmp, candidate, selected);
|
return arith::IndexCastOp::create(builder, loc, builder.getIndexType(), selected).getResult();
|
||||||
}
|
|
||||||
return selected;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FragmentAssemblyCopyRunFamily {
|
struct FragmentAssemblyCopyRunFamily {
|
||||||
@@ -433,11 +434,11 @@ static FailureOr<mlir::Value> emitFragmentAssemblyCopyRun(OpBuilder& builder,
|
|||||||
mlir::Value hostStart;
|
mlir::Value hostStart;
|
||||||
mlir::Value sourceStart;
|
mlir::Value sourceStart;
|
||||||
if (laneArg) {
|
if (laneArg) {
|
||||||
hostStart = createIndexedOffset(builder, loc, *laneArg, run.hostStartBytesByLane);
|
hostStart = createIndexedOffset(builder, loc, *laneArg, run.hostStartBytesByLane, anchor);
|
||||||
sourceStart = createIndexedOffset(builder, loc, *laneArg, run.sourceStartBytesByLane);
|
sourceStart = createIndexedOffset(builder, loc, *laneArg, run.sourceStartBytesByLane, anchor);
|
||||||
} else {
|
} else {
|
||||||
hostStart = arith::ConstantIndexOp::create(builder, loc, run.hostStartBytesByLane.front());
|
hostStart = getOrCreateIndexConstant(builder, anchor, run.hostStartBytesByLane.front());
|
||||||
sourceStart = arith::ConstantIndexOp::create(builder, loc, run.sourceStartBytesByLane.front());
|
sourceStart = getOrCreateIndexConstant(builder, anchor, run.sourceStartBytesByLane.front());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hostRunStartDelta)
|
if (hostRunStartDelta)
|
||||||
@@ -459,9 +460,9 @@ static FailureOr<mlir::Value> emitFragmentAssemblyCopyRun(OpBuilder& builder,
|
|||||||
.getOutput();
|
.getOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
mlir::Value lowerBound = arith::ConstantIndexOp::create(builder, loc, 0);
|
mlir::Value lowerBound = getOrCreateIndexConstant(builder, anchor, 0);
|
||||||
mlir::Value upperBound = arith::ConstantIndexOp::create(builder, loc, run.count);
|
mlir::Value upperBound = getOrCreateIndexConstant(builder, anchor, run.count);
|
||||||
mlir::Value step = arith::ConstantIndexOp::create(builder, loc, 1);
|
mlir::Value step = getOrCreateIndexConstant(builder, anchor, 1);
|
||||||
FailureOr<NormalizedLoopResult> loop = buildNormalizedScfFor(
|
FailureOr<NormalizedLoopResult> loop = buildNormalizedScfFor(
|
||||||
builder,
|
builder,
|
||||||
loc,
|
loc,
|
||||||
@@ -474,9 +475,10 @@ static FailureOr<mlir::Value> emitFragmentAssemblyCopyRun(OpBuilder& builder,
|
|||||||
mlir::Value flatIndex,
|
mlir::Value flatIndex,
|
||||||
ValueRange iterArgs,
|
ValueRange iterArgs,
|
||||||
SmallVectorImpl<mlir::Value>& yielded) {
|
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 =
|
mlir::Value sourceOffset =
|
||||||
createSteppedOffset(loopBuilder, bodyLoc, sourceStart, flatIndex, run.sourceStepBytes);
|
createSteppedOffset(loopBuilder, bodyLoc, sourceStart, flatIndex, run.sourceStepBytes, anchor);
|
||||||
mlir::Value copied =
|
mlir::Value copied =
|
||||||
pim::PimMemCopyDevToHostOp::create(loopBuilder,
|
pim::PimMemCopyDevToHostOp::create(loopBuilder,
|
||||||
bodyLoc,
|
bodyLoc,
|
||||||
@@ -506,9 +508,9 @@ static FailureOr<mlir::Value> emitFragmentAssemblyCopyRunFamily(OpBuilder& build
|
|||||||
return emitFragmentAssemblyCopyRun(
|
return emitFragmentAssemblyCopyRun(
|
||||||
builder, loc, family.prototype, hostTarget, anchor, laneArg, baseHostOffset);
|
builder, loc, family.prototype, hostTarget, anchor, laneArg, baseHostOffset);
|
||||||
|
|
||||||
mlir::Value lowerBound = arith::ConstantIndexOp::create(builder, loc, 0);
|
mlir::Value lowerBound = getOrCreateIndexConstant(builder, anchor, 0);
|
||||||
mlir::Value upperBound = arith::ConstantIndexOp::create(builder, loc, family.sourceRunStartDeltas.size());
|
mlir::Value upperBound = getOrCreateIndexConstant(builder, anchor, family.sourceRunStartDeltas.size());
|
||||||
mlir::Value step = arith::ConstantIndexOp::create(builder, loc, 1);
|
mlir::Value step = getOrCreateIndexConstant(builder, anchor, 1);
|
||||||
FailureOr<NormalizedLoopResult> outerLoop = buildNormalizedScfFor(
|
FailureOr<NormalizedLoopResult> outerLoop = buildNormalizedScfFor(
|
||||||
builder,
|
builder,
|
||||||
loc,
|
loc,
|
||||||
@@ -522,9 +524,9 @@ static FailureOr<mlir::Value> emitFragmentAssemblyCopyRunFamily(OpBuilder& build
|
|||||||
ValueRange iterArgs,
|
ValueRange iterArgs,
|
||||||
SmallVectorImpl<mlir::Value>& yielded) {
|
SmallVectorImpl<mlir::Value>& yielded) {
|
||||||
mlir::Value sourceRunStartDelta =
|
mlir::Value sourceRunStartDelta =
|
||||||
createIndexedOffset(loopBuilder, bodyLoc, runIndex, family.sourceRunStartDeltas);
|
createIndexedOffset(loopBuilder, bodyLoc, runIndex, family.sourceRunStartDeltas, anchor);
|
||||||
mlir::Value hostRunStartDelta =
|
mlir::Value hostRunStartDelta =
|
||||||
createIndexedOffset(loopBuilder, bodyLoc, runIndex, family.hostRunStartDeltas);
|
createIndexedOffset(loopBuilder, bodyLoc, runIndex, family.hostRunStartDeltas, anchor);
|
||||||
FailureOr<mlir::Value> copied = emitFragmentAssemblyCopyRun(loopBuilder,
|
FailureOr<mlir::Value> copied = emitFragmentAssemblyCopyRun(loopBuilder,
|
||||||
bodyLoc,
|
bodyLoc,
|
||||||
family.prototype,
|
family.prototype,
|
||||||
|
|||||||
@@ -10,7 +10,9 @@
|
|||||||
#include "Conversion/ONNXToSpatial/Common/Common.hpp"
|
#include "Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||||
#include "Conversion/SpatialToPim/SpatialToPimPass.hpp"
|
#include "Conversion/SpatialToPim/SpatialToPimPass.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.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/LoopUtils.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/ShapingUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/SpatialToPim/Common.hpp"
|
#include "src/Accelerators/PIM/Conversion/SpatialToPim/Common.hpp"
|
||||||
@@ -180,16 +182,79 @@ static LogicalResult collectHelperComputeChain(spatial::SpatScheduledCompute com
|
|||||||
return success();
|
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,
|
static bool inlineInputlessHelperComputeForWeightLikeUsers(spatial::SpatScheduledCompute computeOp,
|
||||||
IRRewriter& rewriter,
|
IRRewriter& rewriter,
|
||||||
OperationFolder& constantFolder) {
|
OperationFolder& constantFolder) {
|
||||||
if (!computeOp.getInputs().empty() || computeOp.getNumResults() != 1)
|
if (!computeOp.getInputs().empty() || computeOp.getNumResults() != 1)
|
||||||
return false;
|
return false;
|
||||||
|
if (computeOp.getResult(0).use_empty())
|
||||||
|
return false;
|
||||||
if (!llvm::all_of(computeOp.getResult(0).getUsers(), [](Operation* user) {
|
if (!llvm::all_of(computeOp.getResult(0).getUsers(), [](Operation* user) {
|
||||||
return isa<spatial::SpatScheduledCompute, spatial::SpatScheduledComputeBatch, pim::PimCoreOp, pim::PimCoreBatchOp>(user);
|
return isa<spatial::SpatScheduledCompute, spatial::SpatScheduledComputeBatch, pim::PimCoreOp, pim::PimCoreBatchOp>(user);
|
||||||
}))
|
}))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Block& block = computeOp.getBody().front();
|
Block& block = computeOp.getBody().front();
|
||||||
if (block.getNumArguments() != computeOp.getWeights().size())
|
if (block.getNumArguments() != computeOp.getWeights().size())
|
||||||
return false;
|
return false;
|
||||||
@@ -197,6 +262,9 @@ static bool inlineInputlessHelperComputeForWeightLikeUsers(spatial::SpatSchedule
|
|||||||
auto yieldOp = dyn_cast<spatial::SpatYieldOp>(block.getTerminator());
|
auto yieldOp = dyn_cast<spatial::SpatYieldOp>(block.getTerminator());
|
||||||
if (!yieldOp || yieldOp.getNumOperands() != 1)
|
if (!yieldOp || yieldOp.getNumOperands() != 1)
|
||||||
return false;
|
return false;
|
||||||
|
auto folded = analyzeHostMaterializableHelper(computeOp);
|
||||||
|
if (failed(folded))
|
||||||
|
return false;
|
||||||
|
|
||||||
rewriter.setInsertionPoint(computeOp);
|
rewriter.setInsertionPoint(computeOp);
|
||||||
IRMapping mapping;
|
IRMapping mapping;
|
||||||
@@ -218,6 +286,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);
|
cloneMappedHelperOperands(&op, mapping, rewriter, constantFolder);
|
||||||
Operation* clonedOp = rewriter.clone(op, mapping);
|
Operation* clonedOp = rewriter.clone(op, mapping);
|
||||||
for (auto [originalResult, newResult] : llvm::zip(op.getResults(), clonedOp->getResults()))
|
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 "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
|
|
||||||
#include "src/Accelerators/PIM/Conversion/SpatialToPim/Common.hpp"
|
#include "src/Accelerators/PIM/Conversion/SpatialToPim/Common.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/SpatialToPim/Patterns.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/Pim/PimOps.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.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> {
|
struct ChannelSendLowering : OpRewritePattern<spatial::SpatChannelSendOp> {
|
||||||
using OpRewritePattern::OpRewritePattern;
|
using OpRewritePattern::OpRewritePattern;
|
||||||
|
|
||||||
@@ -40,7 +65,21 @@ struct ChannelReceiveLowering : OpRewritePattern<spatial::SpatChannelReceiveOp>
|
|||||||
rewriter.eraseOp(op);
|
rewriter.eraseOp(op);
|
||||||
return success();
|
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 =
|
Value outputBuffer =
|
||||||
tensor::EmptyOp::create(rewriter, op.getLoc(), outputType.getShape(), outputType.getElementType()).getResult();
|
tensor::EmptyOp::create(rewriter, op.getLoc(), outputType.getShape(), outputType.getElementType()).getResult();
|
||||||
auto sizeAttr = getTensorSizeInBytesAttr(rewriter, op.getOperation(), op.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());
|
rewriter, op.getLoc(), op.getResult().getType(), outputBuffer, *sizeAttr, op.getSourceCoreId());
|
||||||
copyRaptorDebugAttrs(op.getOperation(), receive.getOperation());
|
copyRaptorDebugAttrs(op.getOperation(), receive.getOperation());
|
||||||
Value received = receive.getOutput();
|
Value received = receive.getOutput();
|
||||||
|
if (!destinationInsert) {
|
||||||
rewriter.replaceOp(op, received);
|
rewriter.replaceOp(op, received);
|
||||||
return success();
|
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> {
|
struct ExtractRowsLowering : OpRewritePattern<spatial::SpatExtractRowsOp> {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#include "mlir/Conversion/AffineToStandard/AffineToStandard.h"
|
|
||||||
#include "mlir/Dialect/Affine/IR/AffineOps.h"
|
#include "mlir/Dialect/Affine/IR/AffineOps.h"
|
||||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||||
#include "mlir/Dialect/Bufferization/IR/Bufferization.h"
|
#include "mlir/Dialect/Bufferization/IR/Bufferization.h"
|
||||||
@@ -175,11 +174,11 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
|
|||||||
|
|
||||||
RewritePatternSet coreBodyPatterns(ctx);
|
RewritePatternSet coreBodyPatterns(ctx);
|
||||||
populateCoreBodyPatterns(coreBodyPatterns);
|
populateCoreBodyPatterns(coreBodyPatterns);
|
||||||
populateAffineToStdConversionPatterns(coreBodyPatterns);
|
|
||||||
FrozenRewritePatternSet frozenCoreBodyPatterns(std::move(coreBodyPatterns));
|
FrozenRewritePatternSet frozenCoreBodyPatterns(std::move(coreBodyPatterns));
|
||||||
|
|
||||||
ConversionTarget coreBodyTarget(*ctx);
|
ConversionTarget coreBodyTarget(*ctx);
|
||||||
coreBodyTarget.addLegalDialect<PimDialect,
|
coreBodyTarget.addLegalDialect<affine::AffineDialect,
|
||||||
|
PimDialect,
|
||||||
tensor::TensorDialect,
|
tensor::TensorDialect,
|
||||||
arith::ArithDialect,
|
arith::ArithDialect,
|
||||||
bufferization::BufferizationDialect,
|
bufferization::BufferizationDialect,
|
||||||
@@ -226,7 +225,8 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
|
|||||||
eraseUnusedTensorPackingOps(funcOp, rewriter);
|
eraseUnusedTensorPackingOps(funcOp, rewriter);
|
||||||
|
|
||||||
ConversionTarget communicationTarget(*ctx);
|
ConversionTarget communicationTarget(*ctx);
|
||||||
communicationTarget.addLegalDialect<PimDialect,
|
communicationTarget.addLegalDialect<affine::AffineDialect,
|
||||||
|
PimDialect,
|
||||||
tensor::TensorDialect,
|
tensor::TensorDialect,
|
||||||
arith::ArithDialect,
|
arith::ArithDialect,
|
||||||
bufferization::BufferizationDialect,
|
bufferization::BufferizationDialect,
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
|
|
||||||
#include "src/Accelerators/PIM/Common/IR/AddressAnalysis.hpp"
|
#include "src/Accelerators/PIM/Common/IR/AddressAnalysis.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
|
||||||
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
|
||||||
#include "src/Accelerators/PIM/Dialect/Pim/Transforms/Bufferization/BufferizationUtils.hpp"
|
#include "src/Accelerators/PIM/Dialect/Pim/Transforms/Bufferization/BufferizationUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Pim/Transforms/Bufferization/Common.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 shapedType = cast<ShapedType>(memrefValue.getType());
|
||||||
auto contiguousType = MemRefType::get(shapedType.getShape(), shapedType.getElementType());
|
auto contiguousType = MemRefType::get(shapedType.getShape(), shapedType.getElementType());
|
||||||
Value contiguousBuffer = memref::AllocOp::create(rewriter, loc, contiguousType);
|
Value contiguousBuffer = memref::AllocOp::create(rewriter, loc, contiguousType);
|
||||||
auto sizeInBytes =
|
memref::CopyOp::create(rewriter, loc, memrefValue, contiguousBuffer);
|
||||||
getCheckedShapedTypeSizeInBytes(shapedType, contiguousBuffer.getDefiningOp(), "contiguous copy byte size");
|
return contiguousBuffer;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Value allocateContiguousResultMemRefLike(Value memrefValue, Location loc, RewriterBase& rewriter) {
|
Value allocateContiguousResultMemRefLike(Value memrefValue, Location loc, RewriterBase& rewriter) {
|
||||||
|
|||||||
@@ -1,10 +1,23 @@
|
|||||||
#include "Dialect/Pim/Transforms/Bufferization/Common.hpp"
|
#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/PimCommon.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
||||||
|
|
||||||
using namespace mlir;
|
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) {
|
static bool isCoreBatchInputArgument(Value value) {
|
||||||
auto blockArg = dyn_cast<BlockArgument>(value);
|
auto blockArg = dyn_cast<BlockArgument>(value);
|
||||||
if (!blockArg)
|
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) {
|
bool onnx_mlir::pim::isHostBackedPimAddress(Value value, const StaticValueKnowledge& knowledge) {
|
||||||
auto base = getPimStorageBase(value, knowledge);
|
llvm::SmallPtrSet<Value, 8> visited;
|
||||||
if (failed(base))
|
std::function<bool(Value)> isHost = [&](Value current) {
|
||||||
|
auto base = getPimStorageBase(current, knowledge);
|
||||||
|
if (failed(base) || !visited.insert(*base).second)
|
||||||
return false;
|
return false;
|
||||||
|
bool resultIsHost = isCoreBatchInputArgument(*base)
|
||||||
if (isCoreBatchInputArgument(*base))
|
|| isa_and_nonnull<memref::GetGlobalOp>(base->getDefiningOp());
|
||||||
return true;
|
auto result = dyn_cast<OpResult>(*base);
|
||||||
|
SmallVector<Region *> regions = result ? getSelectionRegions(result)
|
||||||
return isa_and_nonnull<memref::GetGlobalOp>(base->getDefiningOp());
|
: 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) {
|
bool onnx_mlir::pim::isDeviceLocalPimAddress(Value value, const StaticValueKnowledge& knowledge) {
|
||||||
auto base = getPimStorageBase(value, knowledge);
|
llvm::SmallPtrSet<Value, 8> visited;
|
||||||
if (failed(base))
|
std::function<bool(Value)> isDevice = [&](Value current) {
|
||||||
|
auto base = getPimStorageBase(current, knowledge);
|
||||||
|
if (failed(base) || !visited.insert(*base).second)
|
||||||
return false;
|
return false;
|
||||||
|
bool resultIsDevice = isa_and_nonnull<memref::AllocOp>(base->getDefiningOp());
|
||||||
return 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/MemRef/IR/MemRef.h"
|
||||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||||
|
|
||||||
|
#include "llvm/Support/MathExtras.h"
|
||||||
|
|
||||||
#include "ContiguityPatterns.hpp"
|
#include "ContiguityPatterns.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
|
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
|
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
|
||||||
@@ -33,6 +35,7 @@ struct CopyEndpointPlan {
|
|||||||
|
|
||||||
struct CopyLoopPlan {
|
struct CopyLoopPlan {
|
||||||
SmallVector<int64_t> outerShape;
|
SmallVector<int64_t> outerShape;
|
||||||
|
int64_t outerElements = 0;
|
||||||
int64_t chunkBytes = 0;
|
int64_t chunkBytes = 0;
|
||||||
ByteOffsetExpr targetBaseOffset;
|
ByteOffsetExpr targetBaseOffset;
|
||||||
ByteOffsetExpr sourceBaseOffset;
|
ByteOffsetExpr sourceBaseOffset;
|
||||||
@@ -74,6 +77,24 @@ static void appendTerm(ByteOffsetExpr& expr, Value value, int64_t scale) {
|
|||||||
expr.terms.push_back(ByteOffsetTerm {value, 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) {
|
static FailureOr<SmallVector<int64_t>> getStaticMemRefStrides(MemRefType type) {
|
||||||
SmallVector<int64_t> strides;
|
SmallVector<int64_t> strides;
|
||||||
int64_t offset = 0;
|
int64_t offset = 0;
|
||||||
@@ -84,6 +105,165 @@ static FailureOr<SmallVector<int64_t>> getStaticMemRefStrides(MemRefType type) {
|
|||||||
return strides;
|
return strides;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<Region *> regions;
|
||||||
|
if (result) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (regions.empty()) {
|
||||||
|
visiting.erase(current);
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
std::optional<SmallVector<int64_t>> common;
|
||||||
|
for (Region *region : regions) {
|
||||||
|
auto yield = dyn_cast<scf::YieldOp>(region->front().getTerminator());
|
||||||
|
if (!yield || result.getResultNumber() >= yield.getNumOperands()) {
|
||||||
|
visiting.erase(current);
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
auto strides = prove(yield.getOperand(result.getResultNumber()));
|
||||||
|
if (failed(strides) || (common && *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) {
|
static FailureOr<int64_t> getShapedByteSize(MemRefType type) {
|
||||||
if (!type.hasStaticShape() || !hasByteSizedElementType(type.getElementType()))
|
if (!type.hasStaticShape() || !hasByteSizedElementType(type.getElementType()))
|
||||||
return failure();
|
return failure();
|
||||||
@@ -119,12 +299,15 @@ inferLogicalCopyShape(MemRefType targetType, MemRefType sourceType, int64_t size
|
|||||||
return failure();
|
return failure();
|
||||||
}
|
}
|
||||||
|
|
||||||
static FailureOr<int64_t> getContiguousSuffixRank(MemRefType type, ArrayRef<int64_t> copyShape) {
|
static FailureOr<int64_t> getContiguousSuffixRank(Value value, ArrayRef<int64_t> copyShape) {
|
||||||
if (!type.hasStaticShape() || !hasByteSizedElementType(type.getElementType())
|
auto type = dyn_cast<MemRefType>(value.getType());
|
||||||
|
if (!type || !type.hasStaticShape() || !hasByteSizedElementType(type.getElementType())
|
||||||
|| type.getRank() != static_cast<int64_t>(copyShape.size()))
|
|| type.getRank() != static_cast<int64_t>(copyShape.size()))
|
||||||
return failure();
|
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))
|
if (failed(strides))
|
||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
@@ -134,7 +317,10 @@ static FailureOr<int64_t> getContiguousSuffixRank(MemRefType type, ArrayRef<int6
|
|||||||
if ((*strides)[dim] != expectedStride)
|
if ((*strides)[dim] != expectedStride)
|
||||||
break;
|
break;
|
||||||
++contiguousSuffixRank;
|
++contiguousSuffixRank;
|
||||||
expectedStride *= copyShape[dim];
|
auto nextStride = checkedPositiveMul(expectedStride, copyShape[dim]);
|
||||||
|
if (failed(nextStride))
|
||||||
|
return failure();
|
||||||
|
expectedStride = *nextStride;
|
||||||
}
|
}
|
||||||
return contiguousSuffixRank;
|
return contiguousSuffixRank;
|
||||||
}
|
}
|
||||||
@@ -174,18 +360,25 @@ static FailureOr<CopyEndpointPlan> analyzeCopyEndpoint(Value value, Value initia
|
|||||||
if (!sourceType || !sourceType.hasStaticShape() || !hasByteSizedElementType(sourceType.getElementType()))
|
if (!sourceType || !sourceType.hasStaticShape() || !hasByteSizedElementType(sourceType.getElementType()))
|
||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
auto sourceStrides = getStaticMemRefStrides(sourceType);
|
auto sourceStrides = getProvenMemRefStrides(subviewOp.getSource());
|
||||||
if (failed(sourceStrides))
|
if (failed(sourceStrides))
|
||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
int64_t elementByteWidth = static_cast<int64_t>(getElementTypeSizeInBytes(sourceType.getElementType()));
|
int64_t elementByteWidth = static_cast<int64_t>(getElementTypeSizeInBytes(sourceType.getElementType()));
|
||||||
for (auto [offset, stride] : llvm::zip_equal(subviewOp.getMixedOffsets(), *sourceStrides)) {
|
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)) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
appendTerm(endpoint.offset, cast<Value>(offset), byteScale);
|
appendTerm(endpoint.offset, cast<Value>(offset), *byteScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoint.base = subviewOp.getSource();
|
endpoint.base = subviewOp.getSource();
|
||||||
@@ -204,17 +397,34 @@ analyzeCopyRewrite(Value target, Value source, Value targetOffset, Value sourceO
|
|||||||
if (!targetType || !sourceType || size <= 0)
|
if (!targetType || !sourceType || size <= 0)
|
||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
auto logicalCopyShape = inferLogicalCopyShape(targetType, sourceType, size);
|
|
||||||
if (failed(logicalCopyShape))
|
|
||||||
return failure();
|
|
||||||
|
|
||||||
auto targetPlan = analyzeCopyEndpoint(target, targetOffset, targetType);
|
auto targetPlan = analyzeCopyEndpoint(target, targetOffset, targetType);
|
||||||
auto sourcePlan = analyzeCopyEndpoint(source, sourceOffset, sourceType);
|
auto sourcePlan = analyzeCopyEndpoint(source, sourceOffset, sourceType);
|
||||||
if (failed(targetPlan) || failed(sourcePlan))
|
if (failed(targetPlan) || failed(sourcePlan))
|
||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
auto targetSuffixRank = getContiguousSuffixRank(targetType, *logicalCopyShape);
|
auto targetBytes = getShapedByteSize(targetType);
|
||||||
auto sourceSuffixRank = getContiguousSuffixRank(sourceType, *logicalCopyShape);
|
auto sourceBytes = getShapedByteSize(sourceType);
|
||||||
|
if (targetType.getElementType() == sourceType.getElementType() && succeeded(targetBytes) && succeeded(sourceBytes)
|
||||||
|
&& *targetBytes == size && *sourceBytes == size) {
|
||||||
|
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))
|
if (failed(targetSuffixRank) || failed(sourceSuffixRank))
|
||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
@@ -229,8 +439,8 @@ analyzeCopyRewrite(Value target, Value source, Value targetOffset, Value sourceO
|
|||||||
return plan;
|
return plan;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto targetStrides = getStaticMemRefStrides(targetType);
|
auto targetStrides = getProvenMemRefStrides(target);
|
||||||
auto sourceStrides = getStaticMemRefStrides(sourceType);
|
auto sourceStrides = getProvenMemRefStrides(source);
|
||||||
if (failed(targetStrides) || failed(sourceStrides))
|
if (failed(targetStrides) || failed(sourceStrides))
|
||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
@@ -240,11 +450,27 @@ analyzeCopyRewrite(Value target, Value source, Value targetOffset, Value sourceO
|
|||||||
plan.loop.sourceBaseOffset = plan.source.offset;
|
plan.loop.sourceBaseOffset = plan.source.offset;
|
||||||
plan.loop.outerShape.assign(logicalCopyShape->begin(), logicalCopyShape->end() - contiguousSuffixRank);
|
plan.loop.outerShape.assign(logicalCopyShape->begin(), logicalCopyShape->end() - contiguousSuffixRank);
|
||||||
SmallVector<int64_t> chunkShape(logicalCopyShape->end() - contiguousSuffixRank, logicalCopyShape->end());
|
SmallVector<int64_t> chunkShape(logicalCopyShape->end() - contiguousSuffixRank, logicalCopyShape->end());
|
||||||
plan.loop.chunkBytes = getNumElements(chunkShape) * elementByteWidth;
|
auto outerElements = checkedPositiveProduct(plan.loop.outerShape);
|
||||||
for (int64_t stride : ArrayRef<int64_t>(*targetStrides).take_front(plan.loop.outerShape.size()))
|
auto chunkElements = checkedPositiveProduct(chunkShape);
|
||||||
plan.loop.targetOuterByteStrides.push_back(stride * elementByteWidth);
|
auto chunkBytes = failed(chunkElements)
|
||||||
for (int64_t stride : ArrayRef<int64_t>(*sourceStrides).take_front(plan.loop.outerShape.size()))
|
? FailureOr<int64_t>(failure())
|
||||||
plan.loop.sourceOuterByteStrides.push_back(stride * elementByteWidth);
|
: 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)
|
if (plan.loop.chunkBytes <= 0)
|
||||||
return failure();
|
return failure();
|
||||||
return plan;
|
return plan;
|
||||||
@@ -344,7 +570,7 @@ static LogicalResult rewriteCopyLikeOp(CopyOp copyOp,
|
|||||||
}
|
}
|
||||||
|
|
||||||
Value c0 = createIndexConstant(rewriter, anchorOp, 0);
|
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);
|
Value cStep = createIndexConstant(rewriter, anchorOp, 1);
|
||||||
auto loop = buildNormalizedScfFor(
|
auto loop = buildNormalizedScfFor(
|
||||||
rewriter,
|
rewriter,
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
#include "mlir/Dialect/Bufferization/IR/Bufferization.h"
|
#include "mlir/Dialect/Bufferization/IR/Bufferization.h"
|
||||||
#include "mlir/Dialect/Bufferization/Transforms/OneShotAnalysis.h"
|
#include "mlir/Dialect/Bufferization/Transforms/OneShotAnalysis.h"
|
||||||
#include "mlir/Dialect/Bufferization/Transforms/OneShotModuleBufferize.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/Func/IR/FuncOps.h"
|
||||||
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
||||||
|
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
|
#include "mlir/IR/Dominance.h"
|
||||||
#include "mlir/IR/PatternMatch.h"
|
#include "mlir/IR/PatternMatch.h"
|
||||||
#include "mlir/Pass/Pass.h"
|
#include "mlir/Pass/Pass.h"
|
||||||
#include "mlir/Rewrite/PatternApplicator.h"
|
#include "mlir/Rewrite/PatternApplicator.h"
|
||||||
|
|
||||||
#include "llvm/ADT/SmallPtrSet.h"
|
#include "llvm/ADT/SmallPtrSet.h"
|
||||||
|
#include "llvm/ADT/SmallSet.h"
|
||||||
#include "llvm/Support/Casting.h"
|
#include "llvm/Support/Casting.h"
|
||||||
|
|
||||||
#include "Common/PimCommon.hpp"
|
#include "Common/PimCommon.hpp"
|
||||||
@@ -116,33 +121,57 @@ lowerMemRefCopyToPimCopy(memref::CopyOp copyOp, PatternRewriter& rewriter, const
|
|||||||
return success();
|
return success();
|
||||||
}
|
}
|
||||||
|
|
||||||
static LogicalResult verifyLoweredPimCopy(pim::PimMemCopyHostToDevOp copyOp, const StaticValueKnowledge& knowledge) {
|
static LogicalResult verifyLoweredPimCopy(pim::PimMemCopyHostToDevOp copyOp,
|
||||||
|
const StaticValueKnowledge& knowledge,
|
||||||
|
bool emitDiagnostic) {
|
||||||
bool sourceIsHost = isHostBackedPimAddress(copyOp.getHostSource(), knowledge);
|
bool sourceIsHost = isHostBackedPimAddress(copyOp.getHostSource(), knowledge);
|
||||||
bool targetIsHost = isHostBackedPimAddress(copyOp.getDeviceTarget(), knowledge);
|
bool targetIsHost = isHostBackedPimAddress(copyOp.getDeviceTarget(), knowledge);
|
||||||
bool sourceIsDevice = isDeviceLocalPimAddress(copyOp.getHostSource(), knowledge);
|
bool sourceIsDevice = isDeviceLocalPimAddress(copyOp.getHostSource(), knowledge);
|
||||||
bool targetIsDevice = isDeviceLocalPimAddress(copyOp.getDeviceTarget(), knowledge);
|
bool targetIsDevice = isDeviceLocalPimAddress(copyOp.getDeviceTarget(), knowledge);
|
||||||
if (!sourceIsHost || !targetIsDevice || targetIsHost || sourceIsDevice)
|
if (!sourceIsHost || !targetIsDevice || targetIsHost || sourceIsDevice) {
|
||||||
return copyOp.emitOpError("pim.memcp_hd requires a host-backed source and a device-local target");
|
if (emitDiagnostic)
|
||||||
|
copyOp.emitOpError() << "pim.memcp_hd requires a host-backed source and a device-local target: source="
|
||||||
|
<< copyOp.getHostSource() << " host=" << sourceIsHost << " device=" << sourceIsDevice
|
||||||
|
<< ", target=" << copyOp.getDeviceTarget() << " host=" << targetIsHost
|
||||||
|
<< " device=" << targetIsDevice;
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
return success();
|
return success();
|
||||||
}
|
}
|
||||||
|
|
||||||
static LogicalResult verifyLoweredPimCopy(pim::PimMemCopyDevToHostOp copyOp, const StaticValueKnowledge& knowledge) {
|
static LogicalResult verifyLoweredPimCopy(pim::PimMemCopyDevToHostOp copyOp,
|
||||||
|
const StaticValueKnowledge& knowledge,
|
||||||
|
bool emitDiagnostic) {
|
||||||
bool sourceIsHost = isHostBackedPimAddress(copyOp.getDeviceSource(), knowledge);
|
bool sourceIsHost = isHostBackedPimAddress(copyOp.getDeviceSource(), knowledge);
|
||||||
bool targetIsHost = isHostBackedPimAddress(copyOp.getHostTarget(), knowledge);
|
bool targetIsHost = isHostBackedPimAddress(copyOp.getHostTarget(), knowledge);
|
||||||
bool sourceIsDevice = isDeviceLocalPimAddress(copyOp.getDeviceSource(), knowledge);
|
bool sourceIsDevice = isDeviceLocalPimAddress(copyOp.getDeviceSource(), knowledge);
|
||||||
bool targetIsDevice = isDeviceLocalPimAddress(copyOp.getHostTarget(), knowledge);
|
bool targetIsDevice = isDeviceLocalPimAddress(copyOp.getHostTarget(), knowledge);
|
||||||
if (!targetIsHost || !sourceIsDevice || sourceIsHost || targetIsDevice)
|
if (!targetIsHost || !sourceIsDevice || sourceIsHost || targetIsDevice) {
|
||||||
return copyOp.emitOpError("pim.memcp_dh requires a device-local source and a host-backed target");
|
if (emitDiagnostic)
|
||||||
|
copyOp.emitOpError() << "pim.memcp_dh requires a device-local source and a host-backed target: source="
|
||||||
|
<< copyOp.getDeviceSource() << " host=" << sourceIsHost << " device=" << sourceIsDevice
|
||||||
|
<< ", target=" << copyOp.getHostTarget() << " host=" << targetIsHost
|
||||||
|
<< " device=" << targetIsDevice;
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
return success();
|
return success();
|
||||||
}
|
}
|
||||||
|
|
||||||
static LogicalResult verifyLoweredPimCopy(pim::PimMemCopyOp copyOp, const StaticValueKnowledge& knowledge) {
|
static LogicalResult verifyLoweredPimCopy(pim::PimMemCopyOp copyOp,
|
||||||
|
const StaticValueKnowledge& knowledge,
|
||||||
|
bool emitDiagnostic) {
|
||||||
bool sourceIsHost = isHostBackedPimAddress(copyOp.getSource(), knowledge);
|
bool sourceIsHost = isHostBackedPimAddress(copyOp.getSource(), knowledge);
|
||||||
bool targetIsHost = isHostBackedPimAddress(copyOp.getTarget(), knowledge);
|
bool targetIsHost = isHostBackedPimAddress(copyOp.getTarget(), knowledge);
|
||||||
bool sourceIsDevice = isDeviceLocalPimAddress(copyOp.getSource(), knowledge);
|
bool sourceIsDevice = isDeviceLocalPimAddress(copyOp.getSource(), knowledge);
|
||||||
bool targetIsDevice = isDeviceLocalPimAddress(copyOp.getTarget(), knowledge);
|
bool targetIsDevice = isDeviceLocalPimAddress(copyOp.getTarget(), knowledge);
|
||||||
if (!sourceIsDevice || !targetIsDevice || sourceIsHost || targetIsHost)
|
if (!sourceIsDevice || !targetIsDevice || sourceIsHost || targetIsHost) {
|
||||||
return copyOp.emitOpError("pim.memcp requires device-local source and target operands");
|
if (emitDiagnostic)
|
||||||
|
copyOp.emitOpError() << "pim.memcp requires device-local source and target operands: source="
|
||||||
|
<< copyOp.getSource() << " host=" << sourceIsHost << " device=" << sourceIsDevice
|
||||||
|
<< ", target=" << copyOp.getTarget() << " host=" << targetIsHost
|
||||||
|
<< " device=" << targetIsDevice;
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
return success();
|
return success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +199,109 @@ static LogicalResult applyPatternsOnce(Operation* op, PatternApplicator& applica
|
|||||||
return applicator.matchAndRewrite(op, rewriter);
|
return applicator.matchAndRewrite(op, rewriter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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); });
|
||||||
|
|
||||||
|
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 verifyConflictFreePimCoreWrites(
|
||||||
|
func::FuncOp funcOp, const bufferization::OneShotBufferizationOptions& options) {
|
||||||
|
bufferization::AnalysisState analysisState(options);
|
||||||
|
DominanceInfo dominance(funcOp);
|
||||||
|
size_t violationCount = 0;
|
||||||
|
|
||||||
|
auto verifyCore = [&](Operation* coreOp) {
|
||||||
|
coreOp->walk([&](Operation* writeOp) {
|
||||||
|
for (OpOperand& write : writeOp->getOpOperands()) {
|
||||||
|
if (!isa<TensorType>(write.get().getType()) || !analysisState.bufferizesToMemoryWrite(write))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
SmallVector<Value, 8> worklist {write.get()};
|
||||||
|
llvm::SmallDenseSet<Value, 8> visited;
|
||||||
|
bool hasConflict = false;
|
||||||
|
Value conflictingAlias;
|
||||||
|
Operation* conflictingUse = nullptr;
|
||||||
|
while (!worklist.empty() && !hasConflict) {
|
||||||
|
Value alias = worklist.pop_back_val();
|
||||||
|
if (!visited.insert(alias).second)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (OpOperand& use : alias.getUses()) {
|
||||||
|
bool usePrecedesWrite = false;
|
||||||
|
for (Operation* ancestor = use.getOwner(); ancestor; ancestor = ancestor->getParentOp())
|
||||||
|
if (ancestor == writeOp || dominance.properlyDominates(ancestor, writeOp)) {
|
||||||
|
usePrecedesWrite = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (usePrecedesWrite || analysisState.insideMutuallyExclusiveRegions(use.getOwner(), writeOp))
|
||||||
|
continue;
|
||||||
|
hasConflict = true;
|
||||||
|
conflictingAlias = alias;
|
||||||
|
conflictingUse = use.getOwner();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alias.getDefiningOp<tensor::ExtractSliceOp>()
|
||||||
|
&& llvm::all_of(alias.getUses(), [&](OpOperand& use) {
|
||||||
|
return use.getOwner() == writeOp;
|
||||||
|
}))
|
||||||
|
continue;
|
||||||
|
if (isa<OpResult>(alias))
|
||||||
|
for (bufferization::AliasingOpOperand tied : analysisState.getAliasingOpOperands(alias).getAliases())
|
||||||
|
worklist.push_back(tied.opOperand->get());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasConflict)
|
||||||
|
continue;
|
||||||
|
if (violationCount++ == 0)
|
||||||
|
writeOp->emitOpError() << "PIM core tensor write may modify an alias used later: operand #"
|
||||||
|
<< write.getOperandNumber() << " (" << write.get() << "), alias="
|
||||||
|
<< conflictingAlias << ", later use=" << *conflictingUse;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
funcOp.walk([&](pim::PimCoreOp coreOp) { verifyCore(coreOp); });
|
||||||
|
funcOp.walk([&](pim::PimCoreBatchOp coreOp) { verifyCore(coreOp); });
|
||||||
|
if (violationCount != 0)
|
||||||
|
funcOp.emitError() << "found " << violationCount
|
||||||
|
<< " non-linear PIM tensor write(s); the first is reported above";
|
||||||
|
return success(violationCount == 0);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void PimBufferizationPass::runOnOperation() {
|
void PimBufferizationPass::runOnOperation() {
|
||||||
@@ -180,9 +312,27 @@ void PimBufferizationPass::runOnOperation() {
|
|||||||
options.allowUnknownOps = true;
|
options.allowUnknownOps = true;
|
||||||
options.bufferizeFunctionBoundaries = true;
|
options.bufferizeFunctionBoundaries = true;
|
||||||
options.setFunctionBoundaryTypeConversion(bufferization::LayoutMapOption::IdentityLayoutMap);
|
options.setFunctionBoundaryTypeConversion(bufferization::LayoutMapOption::IdentityLayoutMap);
|
||||||
bufferization::BufferizationState state;
|
|
||||||
|
|
||||||
if (failed(bufferization::runOneShotModuleBufferize(moduleOp, options, state))) {
|
materializeWritableConstantDestinations(funcOp);
|
||||||
|
if (failed(verifyConflictFreePimCoreWrites(funcOp, 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))) {
|
||||||
|
moduleOp.emitError("Failed to bufferize PIM and Spatial ops");
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (failed(bufferization::bufferizeModuleOp(
|
||||||
|
moduleOp, options, state))) {
|
||||||
moduleOp.emitError("Failed to bufferize PIM and Spatial ops");
|
moduleOp.emitError("Failed to bufferize PIM and Spatial ops");
|
||||||
signalPassFailure();
|
signalPassFailure();
|
||||||
return;
|
return;
|
||||||
@@ -302,57 +452,60 @@ void PimBufferizationPass::annotateWeightsMemrefs(ModuleOp moduleOp, func::FuncO
|
|||||||
|
|
||||||
LogicalResult PimBufferizationPass::verifyContiguousRuntimeOperands(ModuleOp moduleOp) const {
|
LogicalResult PimBufferizationPass::verifyContiguousRuntimeOperands(ModuleOp moduleOp) const {
|
||||||
bool hasFailure = false;
|
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) {
|
auto verifyOperand = [&](Value operand, unsigned operandIndex) {
|
||||||
if (!isa<BaseMemRefType>(operand.getType()))
|
if (!isa<BaseMemRefType>(operand.getType()))
|
||||||
return;
|
return;
|
||||||
if (succeeded(resolveContiguousAddress(operand)) || succeeded(compileContiguousAddressExpr(operand)))
|
if (succeeded(resolveContiguousAddress(operand, knowledge)) || succeeded(compileContiguousAddressExpr(operand)))
|
||||||
return;
|
return;
|
||||||
op->emitOpError() << "operand #" << operandIndex
|
op.emitOpError() << "operand #" << operandIndex
|
||||||
<< " is not backed by contiguous addressable storage after PIM bufferization";
|
<< " is not backed by contiguous addressable storage after PIM bufferization";
|
||||||
hasFailure = true;
|
hasFailure = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (auto memCopyOp = dyn_cast<PimMemCopyOp>(op)) {
|
if (auto memCopyOp = dyn_cast<PimMemCopyOp>(&op)) {
|
||||||
if (!pim::isNormalizedCopyOp(memCopyOp)) {
|
if (!pim::isNormalizedCopyOp(memCopyOp)) {
|
||||||
memCopyOp.emitOpError("must use base memref operands plus explicit byte offsets after bufferization");
|
memCopyOp.emitOpError("must use base memref operands plus explicit byte offsets after bufferization");
|
||||||
hasFailure = true;
|
hasFailure = true;
|
||||||
}
|
}
|
||||||
verifyOperand(memCopyOp.getTarget(), 0);
|
verifyOperand(memCopyOp.getTarget(), 0);
|
||||||
verifyOperand(memCopyOp.getSource(), 1);
|
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)) {
|
if (!pim::isNormalizedCopyOp(loadOp)) {
|
||||||
loadOp.emitOpError("must use base memref operands plus explicit byte offsets after bufferization");
|
loadOp.emitOpError("must use base memref operands plus explicit byte offsets after bufferization");
|
||||||
hasFailure = true;
|
hasFailure = true;
|
||||||
}
|
}
|
||||||
verifyOperand(loadOp.getDeviceTarget(), 2);
|
verifyOperand(loadOp.getDeviceTarget(), 2);
|
||||||
verifyOperand(loadOp.getHostSource(), 3);
|
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)) {
|
if (!pim::isNormalizedCopyOp(storeOp)) {
|
||||||
storeOp.emitOpError("must use base memref operands plus explicit byte offsets after bufferization");
|
storeOp.emitOpError("must use base memref operands plus explicit byte offsets after bufferization");
|
||||||
hasFailure = true;
|
hasFailure = true;
|
||||||
}
|
}
|
||||||
verifyOperand(storeOp.getHostTarget(), 2);
|
verifyOperand(storeOp.getHostTarget(), 2);
|
||||||
verifyOperand(storeOp.getDeviceSource(), 3);
|
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);
|
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);
|
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);
|
verifyOperand(concatOp.getOutputBuffer(), 0);
|
||||||
for (auto inputAndIndex : llvm::enumerate(concatOp.getInputs()))
|
for (auto inputAndIndex : llvm::enumerate(concatOp.getInputs()))
|
||||||
verifyOperand(inputAndIndex.value(), inputAndIndex.index() + 1);
|
verifyOperand(inputAndIndex.value(), inputAndIndex.index() + 1);
|
||||||
return;
|
return success();
|
||||||
}
|
}
|
||||||
if (isa<PimTransposeOp,
|
if (isa<PimTransposeOp,
|
||||||
PimVMMOp,
|
PimVMMOp,
|
||||||
@@ -365,13 +518,21 @@ LogicalResult PimBufferizationPass::verifyContiguousRuntimeOperands(ModuleOp mod
|
|||||||
PimVReluOp,
|
PimVReluOp,
|
||||||
PimVTanhOp,
|
PimVTanhOp,
|
||||||
PimVSigmOp,
|
PimVSigmOp,
|
||||||
PimVSoftmaxOp>(op)) {
|
PimVSoftmaxOp>(&op)) {
|
||||||
for (auto operandAndIndex : llvm::enumerate(op->getOperands())) {
|
for (auto operandAndIndex : llvm::enumerate(op.getOperands())) {
|
||||||
if (auto vmmOp = dyn_cast<PimVMMOp>(op); vmmOp && operandAndIndex.index() == 0)
|
if (auto vmmOp = dyn_cast<PimVMMOp>(&op); vmmOp && operandAndIndex.index() == 0)
|
||||||
continue;
|
continue;
|
||||||
verifyOperand(operandAndIndex.value(), operandAndIndex.index());
|
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) {
|
if (hasFailure) {
|
||||||
@@ -382,18 +543,19 @@ LogicalResult PimBufferizationPass::verifyContiguousRuntimeOperands(ModuleOp mod
|
|||||||
}
|
}
|
||||||
|
|
||||||
LogicalResult PimBufferizationPass::verifyPimCopyAddressSpaces(ModuleOp moduleOp) const {
|
LogicalResult PimBufferizationPass::verifyPimCopyAddressSpaces(ModuleOp moduleOp) const {
|
||||||
bool hasFailure = false;
|
size_t failureCount = 0;
|
||||||
auto verifyWithKnowledge = [&](auto coreLikeOp, const StaticValueKnowledge& initialKnowledge) {
|
auto verifyWithKnowledge = [&](auto coreLikeOp, const StaticValueKnowledge& initialKnowledge) {
|
||||||
(void) walkPimCoreBlockStructurally(
|
(void) walkPimCoreBlockStructurally(
|
||||||
coreLikeOp.getBody().front(), initialKnowledge, [&](Operation& op, const StaticValueKnowledge& knowledge) {
|
coreLikeOp.getBody().front(), initialKnowledge, [&](Operation& op, const StaticValueKnowledge& knowledge) {
|
||||||
if (auto copyOp = dyn_cast<pim::PimMemCopyOp>(&op); copyOp && failed(verifyLoweredPimCopy(copyOp, knowledge)))
|
if (auto copyOp = dyn_cast<pim::PimMemCopyOp>(&op);
|
||||||
hasFailure = true;
|
copyOp && failed(verifyLoweredPimCopy(copyOp, knowledge, failureCount == 0)))
|
||||||
|
++failureCount;
|
||||||
if (auto copyOp = dyn_cast<pim::PimMemCopyHostToDevOp>(&op);
|
if (auto copyOp = dyn_cast<pim::PimMemCopyHostToDevOp>(&op);
|
||||||
copyOp && failed(verifyLoweredPimCopy(copyOp, knowledge)))
|
copyOp && failed(verifyLoweredPimCopy(copyOp, knowledge, failureCount == 0)))
|
||||||
hasFailure = true;
|
++failureCount;
|
||||||
if (auto copyOp = dyn_cast<pim::PimMemCopyDevToHostOp>(&op);
|
if (auto copyOp = dyn_cast<pim::PimMemCopyDevToHostOp>(&op);
|
||||||
copyOp && failed(verifyLoweredPimCopy(copyOp, knowledge)))
|
copyOp && failed(verifyLoweredPimCopy(copyOp, knowledge, failureCount == 0)))
|
||||||
hasFailure = true;
|
++failureCount;
|
||||||
return success();
|
return success();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -403,7 +565,10 @@ LogicalResult PimBufferizationPass::verifyPimCopyAddressSpaces(ModuleOp moduleOp
|
|||||||
StaticValueKnowledge knowledge = seedCoreBatchKnowledge(coreBatchOp, 0);
|
StaticValueKnowledge knowledge = seedCoreBatchKnowledge(coreBatchOp, 0);
|
||||||
verifyWithKnowledge(coreBatchOp, knowledge);
|
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>(); }
|
std::unique_ptr<Pass> createPimBufferizationPass() { return std::make_unique<PimBufferizationPass>(); }
|
||||||
|
|||||||
@@ -7,14 +7,26 @@ add_pim_library(SpatialOps
|
|||||||
SpatialOpsVerify.cpp
|
SpatialOpsVerify.cpp
|
||||||
SpatialOpsCanonicalization.cpp
|
SpatialOpsCanonicalization.cpp
|
||||||
${PIM_SRC_ROOT}/Conversion/ONNXToSpatial/CompileTime.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/Scheduling/ComputeGraph.cpp
|
Transforms/MergeComputeNodes/Scheduling/ComputeGraph.cpp
|
||||||
Transforms/MergeComputeNodes/Scheduling/ComputeInstanceUtils.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/MergeSchedulingAnalysis.cpp
|
||||||
Transforms/MergeComputeNodes/Scheduling/PeftScheduler.cpp
|
Transforms/MergeComputeNodes/Scheduling/PeftScheduler.cpp
|
||||||
|
Transforms/TrivialGraphComputeMergePass.cpp
|
||||||
|
|
||||||
EXCLUDE_FROM_OM_LIBS
|
EXCLUDE_FROM_OM_LIBS
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ include "mlir/IR/OpAsmInterface.td"
|
|||||||
include "mlir/IR/BuiltinTypes.td"
|
include "mlir/IR/BuiltinTypes.td"
|
||||||
include "mlir/IR/AttrTypeBase.td"
|
include "mlir/IR/AttrTypeBase.td"
|
||||||
include "mlir/IR/RegionKindInterface.td"
|
include "mlir/IR/RegionKindInterface.td"
|
||||||
|
include "mlir/Interfaces/ControlFlowInterfaces.td"
|
||||||
include "mlir/Interfaces/ParallelCombiningOpInterface.td"
|
include "mlir/Interfaces/ParallelCombiningOpInterface.td"
|
||||||
include "mlir/Interfaces/SideEffectInterfaces.td"
|
include "mlir/Interfaces/SideEffectInterfaces.td"
|
||||||
|
|
||||||
@@ -27,7 +28,7 @@ def SpatTensor :
|
|||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
class SpatComputeLikeBase<string mnemonic> : SpatOp<mnemonic,
|
class SpatComputeLikeBase<string mnemonic> : SpatOp<mnemonic,
|
||||||
[SingleBlock, AttrSizedOperandSegments,
|
[AttrSizedOperandSegments,
|
||||||
DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmBlockArgumentNames"]>]> {
|
DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmBlockArgumentNames"]>]> {
|
||||||
let summary = "Compute region with attached constant weights";
|
let summary = "Compute region with attached constant weights";
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ class SpatComputeLikeBase<string mnemonic> : SpatOp<mnemonic,
|
|||||||
Variadic<SpatTensor>:$outputs
|
Variadic<SpatTensor>:$outputs
|
||||||
);
|
);
|
||||||
|
|
||||||
let regions = (region SizedRegion<1>:$body);
|
let regions = (region MinSizedRegion<1>:$body);
|
||||||
|
|
||||||
let hasVerifier = 1;
|
let hasVerifier = 1;
|
||||||
let hasFolder = 1;
|
let hasFolder = 1;
|
||||||
@@ -48,6 +49,7 @@ class SpatComputeLikeBase<string mnemonic> : SpatOp<mnemonic,
|
|||||||
}
|
}
|
||||||
|
|
||||||
def SpatGraphCompute : SpatComputeLikeBase<"graph_compute"> {
|
def SpatGraphCompute : SpatComputeLikeBase<"graph_compute"> {
|
||||||
|
let hasCanonicalizer = 1;
|
||||||
let extraClassDeclaration = [{
|
let extraClassDeclaration = [{
|
||||||
std::optional<::mlir::BlockArgument> getWeightArgument(unsigned idx);
|
std::optional<::mlir::BlockArgument> getWeightArgument(unsigned idx);
|
||||||
std::optional<::mlir::BlockArgument> getInputArgument(unsigned idx);
|
std::optional<::mlir::BlockArgument> getInputArgument(unsigned idx);
|
||||||
@@ -76,7 +78,7 @@ def SpatScheduledCompute : SpatComputeLikeBase<"scheduled_compute"> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class SpatComputeBatchLikeBase<string mnemonic> : SpatOp<mnemonic,
|
class SpatComputeBatchLikeBase<string mnemonic> : SpatOp<mnemonic,
|
||||||
[SingleBlock, AttrSizedOperandSegments,
|
[AttrSizedOperandSegments,
|
||||||
DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmBlockArgumentNames"]>]> {
|
DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmBlockArgumentNames"]>]> {
|
||||||
let summary = "Tensor-native batch of equivalent compute lanes with shared weights and packed inputs";
|
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
|
Variadic<SpatTensor>:$outputs
|
||||||
);
|
);
|
||||||
|
|
||||||
let regions = (region SizedRegion<1>:$body);
|
let regions = (region MinSizedRegion<1>:$body);
|
||||||
|
|
||||||
let hasVerifier = 1;
|
let hasVerifier = 1;
|
||||||
let hasCustomAssemblyFormat = 1;
|
let hasCustomAssemblyFormat = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
def SpatGraphComputeBatch : SpatComputeBatchLikeBase<"graph_compute_batch"> {
|
def SpatGraphComputeBatch : SpatComputeBatchLikeBase<"graph_compute_batch"> {
|
||||||
|
let hasCanonicalizer = 1;
|
||||||
let extraClassDeclaration = [{
|
let extraClassDeclaration = [{
|
||||||
std::optional<::mlir::BlockArgument> getLaneArgument();
|
std::optional<::mlir::BlockArgument> getLaneArgument();
|
||||||
std::optional<::mlir::BlockArgument> getWeightArgument(unsigned idx);
|
std::optional<::mlir::BlockArgument> getWeightArgument(unsigned idx);
|
||||||
@@ -113,6 +116,7 @@ def SpatGraphComputeBatch : SpatComputeBatchLikeBase<"graph_compute_batch"> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def SpatScheduledComputeBatch : SpatComputeBatchLikeBase<"scheduled_compute_batch"> {
|
def SpatScheduledComputeBatch : SpatComputeBatchLikeBase<"scheduled_compute_batch"> {
|
||||||
|
let hasCanonicalizer = 1;
|
||||||
let extraClassDeclaration = [{
|
let extraClassDeclaration = [{
|
||||||
std::optional<::mlir::BlockArgument> getLaneArgument();
|
std::optional<::mlir::BlockArgument> getLaneArgument();
|
||||||
std::optional<::mlir::BlockArgument> getWeightArgument(unsigned idx);
|
std::optional<::mlir::BlockArgument> getWeightArgument(unsigned idx);
|
||||||
@@ -161,6 +165,58 @@ def SpatYieldOp : SpatOp<"yield", [Terminator]> {
|
|||||||
let hasCustomAssemblyFormat = 1;
|
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", []> {
|
def SpatExtractRowsOp : SpatOp<"extract_rows", []> {
|
||||||
let summary = "Extract every row of a rank-2 tensor as separate rank-2 row tensors";
|
let summary = "Extract every row of a rank-2 tensor as separate rank-2 row tensors";
|
||||||
|
|
||||||
@@ -232,6 +288,22 @@ def SpatReluPlanOp : SpatOp<"relu_plan", []> {
|
|||||||
let hasVerifier = 1;
|
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", []> {
|
def SpatBlueprintOp : SpatOp<"blueprint", []> {
|
||||||
let summary = "Blueprint for assembling logical tensors from published fragments";
|
let summary = "Blueprint for assembling logical tensors from published fragments";
|
||||||
|
|
||||||
@@ -245,6 +317,7 @@ def SpatBlueprintOp : SpatOp<"blueprint", []> {
|
|||||||
StrAttr:$indexMap,
|
StrAttr:$indexMap,
|
||||||
OptionalAttr<StrAttr>:$mode,
|
OptionalAttr<StrAttr>:$mode,
|
||||||
OptionalAttr<DenseI64ArrayAttr>:$fragmentOperandIndices,
|
OptionalAttr<DenseI64ArrayAttr>:$fragmentOperandIndices,
|
||||||
|
OptionalAttr<DenseI64ArrayAttr>:$fragmentSourceSlots,
|
||||||
OptionalAttr<DenseI64ArrayAttr>:$fragmentSourceOffsets,
|
OptionalAttr<DenseI64ArrayAttr>:$fragmentSourceOffsets,
|
||||||
OptionalAttr<DenseI64ArrayAttr>:$fragmentStrides,
|
OptionalAttr<DenseI64ArrayAttr>:$fragmentStrides,
|
||||||
OptionalAttr<StrAttr>:$conflictPolicy,
|
OptionalAttr<StrAttr>:$conflictPolicy,
|
||||||
|
|||||||
@@ -10,6 +10,18 @@ using namespace mlir;
|
|||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
namespace spatial {
|
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 {
|
namespace {
|
||||||
|
|
||||||
std::optional<BlockArgument> getBlockArgument(Region& body, unsigned argIdx) {
|
std::optional<BlockArgument> getBlockArgument(Region& body, unsigned argIdx) {
|
||||||
@@ -238,6 +250,15 @@ void SpatScheduledCompute::getAsmBlockArgumentNames(Region& region, OpAsmSetValu
|
|||||||
setComputeAsmBlockArgumentNames(*this, region, setNameFn);
|
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::getLaneArgument() { return getBlockArgument(getBody(), 0); }
|
||||||
std::optional<BlockArgument> SpatGraphComputeBatch::getWeightArgument(unsigned idx) {
|
std::optional<BlockArgument> SpatGraphComputeBatch::getWeightArgument(unsigned idx) {
|
||||||
return getBlockArgument(getBody(), 1 + idx);
|
return getBlockArgument(getBody(), 1 + idx);
|
||||||
|
|||||||
@@ -30,6 +30,10 @@
|
|||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
namespace spatial {
|
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 isGraphComputeLike(mlir::Operation* op);
|
||||||
bool isGraphBatchComputeLike(mlir::Operation* op);
|
bool isGraphBatchComputeLike(mlir::Operation* op);
|
||||||
bool isScheduledComputeLike(mlir::Operation* op);
|
bool isScheduledComputeLike(mlir::Operation* op);
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ void printComputeLikeOp(ComputeOpTy op, OpAsmPrinter& printer) {
|
|||||||
printer << " -> ";
|
printer << " -> ";
|
||||||
printCompressedTypeSequence(printer, op.getResultTypes());
|
printCompressedTypeSequence(printer, op.getResultTypes());
|
||||||
printer << " ";
|
printer << " ";
|
||||||
printer.printRegion(op.getBody(), /*printEntryBlockArgs=*/false);
|
printer.printRegion(op.getBody(), /*printEntryBlockArgs=*/!op.getBody().hasOneBlock());
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename ComputeOpTy>
|
template <typename ComputeOpTy>
|
||||||
@@ -290,7 +290,7 @@ void printComputeBatchLikeOp(ComputeBatchOpTy op, OpAsmPrinter& printer) {
|
|||||||
printer << " -> ";
|
printer << " -> ";
|
||||||
printCompressedTypeSequence(printer, op.getResultTypes());
|
printCompressedTypeSequence(printer, op.getResultTypes());
|
||||||
printer << " ";
|
printer << " ";
|
||||||
printer.printRegion(op.getBody(), /*printEntryBlockArgs=*/false);
|
printer.printRegion(op.getBody(), /*printEntryBlockArgs=*/!op.getBody().hasOneBlock());
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename ComputeBatchOpTy>
|
template <typename ComputeBatchOpTy>
|
||||||
@@ -407,6 +407,130 @@ ParseResult SpatYieldOp::parse(OpAsmParser& parser, OperationState& result) {
|
|||||||
return parser.resolveOperands(outputs, outputTypes, parser.getCurrentLocation(), result.operands);
|
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) {
|
void SpatExtractRowsOp::print(OpAsmPrinter& printer) {
|
||||||
printer << " ";
|
printer << " ";
|
||||||
printer.printOperand(getInput());
|
printer.printOperand(getInput());
|
||||||
@@ -493,6 +617,10 @@ void SpatBlueprintOp::print(OpAsmPrinter& printer) {
|
|||||||
printer << " operandIndices ";
|
printer << " operandIndices ";
|
||||||
printCompressedIntegerList(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()) {
|
if (std::optional<ArrayRef<int64_t>> sourceOffsets = getFragmentSourceOffsets()) {
|
||||||
printer << " sourceOffsets ";
|
printer << " sourceOffsets ";
|
||||||
printCompressedIntegerList(printer, *sourceOffsets);
|
printCompressedIntegerList(printer, *sourceOffsets);
|
||||||
@@ -514,6 +642,7 @@ void SpatBlueprintOp::print(OpAsmPrinter& printer) {
|
|||||||
getIndexMapAttrName().getValue(),
|
getIndexMapAttrName().getValue(),
|
||||||
getModeAttrName().getValue(),
|
getModeAttrName().getValue(),
|
||||||
getFragmentOperandIndicesAttrName().getValue(),
|
getFragmentOperandIndicesAttrName().getValue(),
|
||||||
|
getFragmentSourceSlotsAttrName().getValue(),
|
||||||
getFragmentSourceOffsetsAttrName().getValue(),
|
getFragmentSourceOffsetsAttrName().getValue(),
|
||||||
getFragmentStridesAttrName().getValue(),
|
getFragmentStridesAttrName().getValue(),
|
||||||
getConflictPolicyAttrName().getValue(),
|
getConflictPolicyAttrName().getValue(),
|
||||||
@@ -537,6 +666,7 @@ ParseResult SpatBlueprintOp::parse(OpAsmParser& parser, OperationState& result)
|
|||||||
SmallVector<int64_t> fragmentOffsets;
|
SmallVector<int64_t> fragmentOffsets;
|
||||||
SmallVector<int64_t> fragmentSizes;
|
SmallVector<int64_t> fragmentSizes;
|
||||||
SmallVector<int64_t> fragmentOperandIndices;
|
SmallVector<int64_t> fragmentOperandIndices;
|
||||||
|
SmallVector<int64_t> fragmentSourceSlots;
|
||||||
SmallVector<int64_t> fragmentSourceOffsets;
|
SmallVector<int64_t> fragmentSourceOffsets;
|
||||||
SmallVector<int64_t> fragmentStrides;
|
SmallVector<int64_t> fragmentStrides;
|
||||||
|
|
||||||
@@ -554,6 +684,9 @@ ParseResult SpatBlueprintOp::parse(OpAsmParser& parser, OperationState& result)
|
|||||||
if (succeeded(parser.parseOptionalKeyword("operandIndices"))
|
if (succeeded(parser.parseOptionalKeyword("operandIndices"))
|
||||||
&& parseCompressedIntegerList(parser, fragmentOperandIndices))
|
&& parseCompressedIntegerList(parser, fragmentOperandIndices))
|
||||||
return failure();
|
return failure();
|
||||||
|
if (succeeded(parser.parseOptionalKeyword("sourceSlots"))
|
||||||
|
&& parseCompressedIntegerList(parser, fragmentSourceSlots))
|
||||||
|
return failure();
|
||||||
if (succeeded(parser.parseOptionalKeyword("sourceOffsets"))
|
if (succeeded(parser.parseOptionalKeyword("sourceOffsets"))
|
||||||
&& parseCompressedIntegerList(parser, fragmentSourceOffsets))
|
&& parseCompressedIntegerList(parser, fragmentSourceOffsets))
|
||||||
return failure();
|
return failure();
|
||||||
@@ -584,6 +717,8 @@ ParseResult SpatBlueprintOp::parse(OpAsmParser& parser, OperationState& result)
|
|||||||
result.addAttribute("mode", mode);
|
result.addAttribute("mode", mode);
|
||||||
if (!fragmentOperandIndices.empty())
|
if (!fragmentOperandIndices.empty())
|
||||||
result.addAttribute("fragmentOperandIndices", builder.getDenseI64ArrayAttr(fragmentOperandIndices));
|
result.addAttribute("fragmentOperandIndices", builder.getDenseI64ArrayAttr(fragmentOperandIndices));
|
||||||
|
if (!fragmentSourceSlots.empty())
|
||||||
|
result.addAttribute("fragmentSourceSlots", builder.getDenseI64ArrayAttr(fragmentSourceSlots));
|
||||||
if (!fragmentSourceOffsets.empty())
|
if (!fragmentSourceOffsets.empty())
|
||||||
result.addAttribute("fragmentSourceOffsets", builder.getDenseI64ArrayAttr(fragmentSourceOffsets));
|
result.addAttribute("fragmentSourceOffsets", builder.getDenseI64ArrayAttr(fragmentSourceOffsets));
|
||||||
if (!fragmentStrides.empty())
|
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/Block.h"
|
||||||
|
#include "mlir/IR/IRMapping.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/DenseMap.h"
|
||||||
#include "llvm/ADT/STLExtras.h"
|
#include "llvm/ADT/STLExtras.h"
|
||||||
#include "llvm/Support/LogicalResult.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"
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
|
|
||||||
using namespace mlir;
|
using namespace mlir;
|
||||||
@@ -36,9 +42,208 @@ LogicalResult SpatGraphCompute::fold(FoldAdaptor adaptor, ::llvm::SmallVectorImp
|
|||||||
return foldComputeLike(*this, results);
|
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) {
|
LogicalResult SpatScheduledCompute::fold(FoldAdaptor adaptor, ::llvm::SmallVectorImpl<::mlir::OpFoldResult>& results) {
|
||||||
return foldComputeLike(*this, 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 spatial
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "mlir/Dialect/Affine/IR/AffineOps.h"
|
#include "mlir/Dialect/Affine/IR/AffineOps.h"
|
||||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||||
|
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
#include "mlir/IR/AffineExpr.h"
|
#include "mlir/IR/AffineExpr.h"
|
||||||
#include "mlir/IR/Block.h"
|
#include "mlir/IR/Block.h"
|
||||||
@@ -59,6 +60,21 @@ static LogicalResult verifyStaticWeights(ComputeOpTy computeOp, StringRef kind)
|
|||||||
return success();
|
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) {
|
static bool isStaticIndexExpr(Value value) {
|
||||||
if (matchConstantIndexValue(value))
|
if (matchConstantIndexValue(value))
|
||||||
return true;
|
return true;
|
||||||
@@ -80,7 +96,7 @@ static bool isStaticIndexExpr(Value value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool isSupportedLaneOffsetExpr(Value value, BlockArgument laneArg) {
|
static bool isSupportedLaneOffsetExpr(Value value, BlockArgument laneArg) {
|
||||||
if (value == laneArg || isStaticIndexExpr(value))
|
if (value == laneArg || isStaticIndexExpr(value) || isStaticScfForInductionVar(value))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
auto affineApply = value.getDefiningOp<affine::AffineApplyOp>();
|
auto affineApply = value.getDefiningOp<affine::AffineApplyOp>();
|
||||||
@@ -176,12 +192,18 @@ static bool isConstantExternalValue(Value value) {
|
|||||||
return definingOp && definingOp->hasTrait<OpTrait::ConstantLike>();
|
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) {
|
static LogicalResult verifyOnlyConstantExternalValues(Operation* ownerOp, Region& region, StringRef kind) {
|
||||||
bool hasFailure = false;
|
bool hasFailure = false;
|
||||||
region.walk([&](Operation* op) {
|
region.walk([&](Operation* op) {
|
||||||
for (OpOperand& operand : op->getOpOperands()) {
|
for (OpOperand& operand : op->getOpOperands()) {
|
||||||
Value value = operand.get();
|
Value value = operand.get();
|
||||||
if (isDefinedInsideRegion(value, region) || isConstantExternalValue(value))
|
if (isDefinedInsideRegion(value, region) || isConstantExternalValue(value)
|
||||||
|
|| isRecordedDeferredCommunicationSource(op, value))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
InFlightDiagnostic diagnostic =
|
InFlightDiagnostic diagnostic =
|
||||||
@@ -204,7 +226,7 @@ static LogicalResult verifyOnlyConstantExternalValues(Operation* ownerOp, Region
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename ComputeBatchOpTy>
|
template <typename ComputeBatchOpTy>
|
||||||
static LogicalResult verifyBatchBody(ComputeBatchOpTy batchOp, Block& block) {
|
static LogicalResult verifyBatchBody(ComputeBatchOpTy batchOp, Block& block, bool verifyLaneSliceOffsets = true) {
|
||||||
if (batchOp.getNumResults() == 0) {
|
if (batchOp.getNumResults() == 0) {
|
||||||
auto yieldOp = dyn_cast_or_null<SpatYieldOp>(block.getTerminator());
|
auto yieldOp = dyn_cast_or_null<SpatYieldOp>(block.getTerminator());
|
||||||
if (!yieldOp)
|
if (!yieldOp)
|
||||||
@@ -219,6 +241,7 @@ static LogicalResult verifyBatchBody(ComputeBatchOpTy batchOp, Block& block) {
|
|||||||
auto laneArg = batchOp.getLaneArgument();
|
auto laneArg = batchOp.getLaneArgument();
|
||||||
if (!laneArg)
|
if (!laneArg)
|
||||||
return batchOp.emitError("compute_batch body must have a lane block argument");
|
return batchOp.emitError("compute_batch body must have a lane block argument");
|
||||||
|
if (verifyLaneSliceOffsets)
|
||||||
for (auto& bodyOp : block) {
|
for (auto& bodyOp : block) {
|
||||||
if (auto extractSlice = dyn_cast<tensor::ExtractSliceOp>(&bodyOp))
|
if (auto extractSlice = dyn_cast<tensor::ExtractSliceOp>(&bodyOp))
|
||||||
if (failed(verifyStaticUnitStrideExtractSliceOp(extractSlice, *laneArg, "tensor.extract_slice")))
|
if (failed(verifyStaticUnitStrideExtractSliceOp(extractSlice, *laneArg, "tensor.extract_slice")))
|
||||||
@@ -436,6 +459,39 @@ LogicalResult SpatReluPlanOp::verify() {
|
|||||||
return success();
|
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() {
|
LogicalResult SpatBlueprintOp::verify() {
|
||||||
auto modeAttr = getModeAttr();
|
auto modeAttr = getModeAttr();
|
||||||
bool isFragmentAssembly = modeAttr && modeAttr.getValue() == "fragment_assembly";
|
bool isFragmentAssembly = modeAttr && modeAttr.getValue() == "fragment_assembly";
|
||||||
@@ -491,20 +547,26 @@ LogicalResult SpatBlueprintOp::verify() {
|
|||||||
|
|
||||||
auto stridesAttr = getFragmentStridesAttr();
|
auto stridesAttr = getFragmentStridesAttr();
|
||||||
auto operandIndicesAttr = getFragmentOperandIndicesAttr();
|
auto operandIndicesAttr = getFragmentOperandIndicesAttr();
|
||||||
|
auto sourceSlotsAttr = getFragmentSourceSlotsAttr();
|
||||||
auto sourceOffsetsAttr = getFragmentSourceOffsetsAttr();
|
auto sourceOffsetsAttr = getFragmentSourceOffsetsAttr();
|
||||||
if (!operandIndicesAttr)
|
if (!operandIndicesAttr)
|
||||||
return emitError("fragment assembly blueprint requires fragment operand indices");
|
return emitError("fragment assembly blueprint requires fragment operand indices");
|
||||||
|
if (!sourceSlotsAttr)
|
||||||
|
return emitError("fragment assembly blueprint requires physical fragment source slots");
|
||||||
if (!sourceOffsetsAttr)
|
if (!sourceOffsetsAttr)
|
||||||
return emitError("fragment assembly blueprint requires fragment source offsets");
|
return emitError("fragment assembly blueprint requires fragment source offsets");
|
||||||
if (!stridesAttr)
|
if (!stridesAttr)
|
||||||
return emitError("fragment assembly blueprint requires fragment strides");
|
return emitError("fragment assembly blueprint requires fragment strides");
|
||||||
ArrayRef<int64_t> operandIndices = operandIndicesAttr.asArrayRef();
|
ArrayRef<int64_t> operandIndices = operandIndicesAttr.asArrayRef();
|
||||||
|
ArrayRef<int64_t> sourceSlots = sourceSlotsAttr.asArrayRef();
|
||||||
ArrayRef<int64_t> sourceOffsets = sourceOffsetsAttr.asArrayRef();
|
ArrayRef<int64_t> sourceOffsets = sourceOffsetsAttr.asArrayRef();
|
||||||
ArrayRef<int64_t> strides = stridesAttr.asArrayRef();
|
ArrayRef<int64_t> strides = stridesAttr.asArrayRef();
|
||||||
if (strides.size() != offsets.size())
|
if (strides.size() != offsets.size())
|
||||||
return emitError("fragment stride and offset arrays must have the same length");
|
return emitError("fragment stride and offset arrays must have the same length");
|
||||||
if (sourceOffsets.size() != operandIndices.size())
|
if (sourceOffsets.size() != operandIndices.size())
|
||||||
return emitError("fragment source offset count must match fragment operand index count");
|
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())
|
if (!getConflictPolicyAttr() || !getCoveragePolicyAttr())
|
||||||
return emitError("fragment assembly blueprint requires conflict and coverage policies");
|
return emitError("fragment assembly blueprint requires conflict and coverage policies");
|
||||||
if (getConflictPolicy() != "disjoint")
|
if (getConflictPolicy() != "disjoint")
|
||||||
@@ -539,14 +601,19 @@ LogicalResult SpatBlueprintOp::verify() {
|
|||||||
int64_t operandIndex = operandIndices[fragmentIndex];
|
int64_t operandIndex = operandIndices[fragmentIndex];
|
||||||
if (operandIndex < 0 || operandIndex >= operandCount)
|
if (operandIndex < 0 || operandIndex >= operandCount)
|
||||||
return emitError("fragment assembly operand index is out of range");
|
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)
|
if (sourceOffsets[fragmentIndex] < 0)
|
||||||
return emitError("fragment assembly source offsets must be nonnegative");
|
return emitError("fragment assembly source offsets must be nonnegative");
|
||||||
|
|
||||||
auto operandType = dyn_cast<RankedTensorType>(operands[operandIndex].getType());
|
auto operandType = dyn_cast<RankedTensorType>(operands[operandIndex].getType());
|
||||||
if (!operandType || !operandType.hasStaticShape())
|
if (!operandType || !operandType.hasStaticShape())
|
||||||
return emitError("fragment assembly blueprint requires static ranked tensor operands");
|
return emitError("fragment assembly blueprint requires static ranked tensor operands");
|
||||||
if (operandType.getRank() != rank)
|
if (operandType.getRank() != rank + 1)
|
||||||
return emitError("fragment assembly blueprint requires operand/result rank match");
|
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> fragmentOffsets;
|
||||||
SmallVector<int64_t, 4> fragmentSizes;
|
SmallVector<int64_t, 4> fragmentSizes;
|
||||||
@@ -562,12 +629,12 @@ LogicalResult SpatBlueprintOp::verify() {
|
|||||||
int64_t fragmentElements = 1;
|
int64_t fragmentElements = 1;
|
||||||
for (int64_t dim = 0; dim < rank; ++dim)
|
for (int64_t dim = 0; dim < rank; ++dim)
|
||||||
fragmentElements *= fragmentSizes[dim];
|
fragmentElements *= fragmentSizes[dim];
|
||||||
if (sourceOffsets[fragmentIndex] + fragmentElements > operandType.getNumElements())
|
if (sourceOffsets[fragmentIndex] + fragmentElements > fragmentType.getNumElements())
|
||||||
return emitError("fragment assembly source offset exceeds the operand bounds");
|
return emitError("fragment assembly source offset exceeds the selected physical fragment bounds");
|
||||||
SmallVector<int64_t, 4> sourceSliceOffsets =
|
SmallVector<int64_t, 4> sourceSliceOffsets =
|
||||||
expandFlatElementIndex(sourceOffsets[fragmentIndex], operandType.getShape());
|
expandFlatElementIndex(sourceOffsets[fragmentIndex], fragmentType.getShape());
|
||||||
for (int64_t dim = 0; dim < rank; ++dim)
|
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");
|
return emitError("fragment assembly source offset must describe a valid unit-stride slice");
|
||||||
|
|
||||||
for (const auto& [existingOffsets, existingSizes] : slices) {
|
for (const auto& [existingOffsets, existingSizes] : slices) {
|
||||||
@@ -630,7 +697,9 @@ LogicalResult verifyComputeResultsUses(Operation* op) {
|
|||||||
if (!isAnySpatialComputeLike(op))
|
if (!isAnySpatialComputeLike(op))
|
||||||
return op->emitError("verifyComputeResultUses: op is not a Spatial compute-like operation");
|
return op->emitError("verifyComputeResultUses: op is not a Spatial compute-like operation");
|
||||||
if (!llvm::all_of(op->getResults(), [](Value result) {
|
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());
|
return !isAnySpatialComputeLike(op->getParentOp());
|
||||||
});
|
});
|
||||||
})) {
|
})) {
|
||||||
@@ -641,33 +710,41 @@ LogicalResult verifyComputeResultsUses(Operation* op) {
|
|||||||
|
|
||||||
template <typename ComputeOpTy>
|
template <typename ComputeOpTy>
|
||||||
LogicalResult verifyComputeLikeOp(ComputeOpTy compute, StringRef opName) {
|
LogicalResult verifyComputeLikeOp(ComputeOpTy compute, StringRef opName) {
|
||||||
auto& block = compute.getBody().front();
|
|
||||||
unsigned expectedArgCount = compute.getWeights().size() + compute.getInputs().size();
|
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)
|
if (block.getNumArguments() != expectedArgCount)
|
||||||
return compute.emitOpError("compute body must have weight and input block arguments");
|
return compute.emitOpError("compute body must have weight and input block arguments");
|
||||||
|
|
||||||
for (auto [weightIndex, weight] : llvm::enumerate(compute.getWeights())) {
|
for (auto [weightIndex, weight] : llvm::enumerate(compute.getWeights()))
|
||||||
auto blockArg = compute.getWeightArgument(weightIndex);
|
if (block.getArgument(weightIndex).getType() != weight.getType())
|
||||||
if (!blockArg || blockArg->getType() != weight.getType())
|
|
||||||
return compute.emitOpError("compute weight block argument types must match weight operand types exactly");
|
return compute.emitOpError("compute weight block argument types must match weight operand types exactly");
|
||||||
}
|
for (auto [inputIndex, input] : llvm::enumerate(compute.getInputs()))
|
||||||
for (auto [inputIndex, input] : llvm::enumerate(compute.getInputs())) {
|
if (block.getArgument(compute.getWeights().size() + inputIndex).getType() != input.getType())
|
||||||
auto blockArg = compute.getInputArgument(inputIndex);
|
|
||||||
if (!blockArg || blockArg->getType() != input.getType())
|
|
||||||
return compute.emitOpError("compute input block argument types must match input operand types exactly");
|
return compute.emitOpError("compute input block argument types must match input operand types exactly");
|
||||||
}
|
|
||||||
|
|
||||||
if (block.mightHaveTerminator()) {
|
Operation* terminator = block.getTerminator();
|
||||||
auto yieldOp = dyn_cast_or_null<SpatYieldOp>(block.getTerminator());
|
if (auto yieldOp = dyn_cast_or_null<SpatYieldOp>(terminator)) {
|
||||||
if (!yieldOp)
|
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");
|
return compute.emitOpError("ComputeOp must have a single yield operation");
|
||||||
|
llvm::append_range(yieldedTypes, blockYield->getOperandTypes());
|
||||||
|
}
|
||||||
|
|
||||||
auto resultTypes = compute.getResultTypes();
|
auto resultTypes = compute.getResultTypes();
|
||||||
auto yieldTypes = yieldOp->getOperandTypes();
|
if (resultTypes.size() != yieldedTypes.size())
|
||||||
if (resultTypes.size() != yieldTypes.size())
|
return compute.emitOpError("ComputeOp must have same number of results as yielded operands");
|
||||||
return compute.emitOpError("ComputeOp must have same number of results as yieldOp 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 resultType = std::get<0>(it);
|
||||||
auto yieldType = std::get<1>(it);
|
auto yieldType = std::get<1>(it);
|
||||||
|
|
||||||
@@ -677,18 +754,18 @@ LogicalResult verifyComputeLikeOp(ComputeOpTy compute, StringRef opName) {
|
|||||||
if (auto resultRankedType = dyn_cast<RankedTensorType>(resultType)) {
|
if (auto resultRankedType = dyn_cast<RankedTensorType>(resultType)) {
|
||||||
if (auto yieldRankedType = dyn_cast<RankedTensorType>(yieldType)) {
|
if (auto yieldRankedType = dyn_cast<RankedTensorType>(yieldType)) {
|
||||||
if (resultRankedType.getEncoding() != yieldRankedType.getEncoding())
|
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 {
|
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)) {
|
else if (dyn_cast<RankedTensorType>(yieldType)) {
|
||||||
return compute.emitOpError("ComputeOp output must not have an encoding if yieldOp operand has one");
|
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)
|
for (unsigned inputIndex = 0; inputIndex < compute.getInputs().size(); ++inputIndex)
|
||||||
if (auto inputArg = compute.getInputArgument(inputIndex); !inputArg || inputArg->use_empty())
|
if (auto inputArg = compute.getInputArgument(inputIndex); !inputArg || inputArg->use_empty())
|
||||||
return compute.emitOpError("ComputeOp block argument is not used");
|
return compute.emitOpError("ComputeOp block argument is not used");
|
||||||
@@ -705,6 +782,93 @@ LogicalResult SpatGraphCompute::verify() { return verifyComputeLikeOp(*this, "sp
|
|||||||
|
|
||||||
LogicalResult SpatScheduledCompute::verify() { return verifyComputeLikeOp(*this, "spat.scheduled_compute"); }
|
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>
|
template <typename ComputeBatchOpTy>
|
||||||
LogicalResult verifyComputeBatchLikeOp(ComputeBatchOpTy batch, StringRef opName) {
|
LogicalResult verifyComputeBatchLikeOp(ComputeBatchOpTy batch, StringRef opName) {
|
||||||
int32_t count = batch.getLaneCount();
|
int32_t count = batch.getLaneCount();
|
||||||
@@ -727,30 +891,33 @@ LogicalResult verifyComputeBatchLikeOp(ComputeBatchOpTy batch, StringRef opName)
|
|||||||
return batch.emitOpError("compute_batch coreIds values must be unique");
|
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)
|
if (block.getNumArguments() == 0)
|
||||||
return batch.emitOpError("compute_batch body must have exactly one lane block argument");
|
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)
|
if (block.getNumArguments() != expectedArgCount)
|
||||||
return batch.emitOpError("compute_batch body block arguments must match lane, weight, input, and output operands/results");
|
return batch.emitOpError(
|
||||||
auto laneArg = batch.getLaneArgument();
|
"compute_batch body block arguments must match lane, weight, input, and output operands/results");
|
||||||
if (!laneArg || !laneArg->getType().isIndex())
|
if (!block.getArgument(0).getType().isIndex())
|
||||||
return batch.emitOpError("compute_batch first block argument must have index type");
|
return batch.emitOpError("compute_batch first block argument must have index type");
|
||||||
|
|
||||||
for (auto [weightIndex, weight] : llvm::enumerate(batch.getWeights())) {
|
for (auto [weightIndex, weight] : llvm::enumerate(batch.getWeights()))
|
||||||
auto blockArg = batch.getWeightArgument(weightIndex);
|
if (block.getArgument(1 + weightIndex).getType() != weight.getType())
|
||||||
if (!blockArg || blockArg->getType() != weight.getType())
|
|
||||||
return batch.emitOpError("compute_batch weight block argument types must match weight operand types exactly");
|
return batch.emitOpError("compute_batch weight block argument types must match weight operand types exactly");
|
||||||
}
|
for (auto [inputIndex, input] : llvm::enumerate(batch.getInputs()))
|
||||||
for (auto [inputIndex, input] : llvm::enumerate(batch.getInputs())) {
|
if (block.getArgument(1 + batch.getWeights().size() + inputIndex).getType() != input.getType())
|
||||||
auto blockArg = batch.getInputArgument(inputIndex);
|
|
||||||
if (!blockArg || blockArg->getType() != input.getType())
|
|
||||||
return batch.emitOpError("compute_batch input block argument types must match input operand types exactly");
|
return batch.emitOpError("compute_batch input block argument types must match input operand types exactly");
|
||||||
}
|
for (auto [resultIndex, resultType] : llvm::enumerate(batch.getResultTypes()))
|
||||||
for (auto [resultIndex, resultType] : llvm::enumerate(batch.getResultTypes())) {
|
if (block.getArgument(1 + batch.getWeights().size() + batch.getInputs().size() + resultIndex).getType()
|
||||||
auto blockArg = batch.getOutputArgument(resultIndex);
|
!= resultType)
|
||||||
if (!blockArg || blockArg->getType() != resultType)
|
|
||||||
return batch.emitOpError("compute_batch output block argument types must match result types exactly");
|
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())))
|
if (failed(verifyComputeResultsUses(batch.getOperation())))
|
||||||
@@ -759,7 +926,7 @@ LogicalResult verifyComputeBatchLikeOp(ComputeBatchOpTy batch, StringRef opName)
|
|||||||
return failure();
|
return failure();
|
||||||
if (failed(verifyOnlyConstantExternalValues(batch.getOperation(), batch.getBody(), opName)))
|
if (failed(verifyOnlyConstantExternalValues(batch.getOperation(), batch.getBody(), opName)))
|
||||||
return failure();
|
return failure();
|
||||||
return verifyBatchBody(batch, block);
|
return success();
|
||||||
}
|
}
|
||||||
|
|
||||||
LogicalResult SpatGraphComputeBatch::verify() { return verifyComputeBatchLikeOp(*this, "spat.graph_compute_batch"); }
|
LogicalResult SpatGraphComputeBatch::verify() { return verifyComputeBatchLikeOp(*this, "spat.graph_compute_batch"); }
|
||||||
|
|||||||
@@ -0,0 +1,314 @@
|
|||||||
|
#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 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();
|
||||||
|
}
|
||||||
|
for (EmitLocalCollectionRun &update : localUpdates)
|
||||||
|
boundary.instructions.push_back(std::move(update));
|
||||||
|
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
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "DeferredCommunicationScheduling.hpp"
|
||||||
|
#include "DeferredResultRealization.hpp"
|
||||||
|
|
||||||
|
namespace onnx_mlir::spatial {
|
||||||
|
|
||||||
|
struct DeferredTransferPlan;
|
||||||
|
|
||||||
|
using BoundaryKey = std::pair<ScheduledInfo *, unsigned>;
|
||||||
|
|
||||||
|
struct EmitSendRun {
|
||||||
|
llvm::SmallVector<ScheduledTransferSlice> slices;
|
||||||
|
LaneSet lanes;
|
||||||
|
};
|
||||||
|
struct EmitLocalCollectionRun {
|
||||||
|
const FragmentCollectionPlan* collection = nullptr;
|
||||||
|
unsigned collectionPosition = 0;
|
||||||
|
llvm::SmallVector<LocalAvailabilityFamily*> families;
|
||||||
|
LaneSet lanes;
|
||||||
|
bool concatenatePayloads = false;
|
||||||
|
};
|
||||||
|
struct EmitReceiveAssemblyRun {
|
||||||
|
const FragmentCollectionPlan* collection = nullptr;
|
||||||
|
llvm::SmallVector<ScheduledTransferSlice> slices;
|
||||||
|
llvm::SmallVector<size_t> entryOffsets;
|
||||||
|
llvm::SmallVector<unsigned> positions;
|
||||||
|
llvm::SmallVector<LaneSet> entryLanes;
|
||||||
|
LaneSet lanes;
|
||||||
|
};
|
||||||
|
struct ProduceDeferredResult {
|
||||||
|
DeferredExchangePlan* exchange = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
using BoundaryInstruction =
|
||||||
|
std::variant<EmitSendRun, EmitLocalCollectionRun, EmitReceiveAssemblyRun,
|
||||||
|
ProduceDeferredResult>;
|
||||||
|
struct BoundaryProgram {
|
||||||
|
BoundaryKey key;
|
||||||
|
llvm::SmallVector<BoundaryInstruction, 0> instructions;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DeferredBoundaryPlan {
|
||||||
|
llvm::SmallVector<BoundaryProgram, 0> boundaries;
|
||||||
|
llvm::SmallVector<DeferredResultPlan, 0> results;
|
||||||
|
};
|
||||||
|
|
||||||
|
mlir::FailureOr<DeferredBoundaryPlan> buildDeferredBoundaryPlan(DeferredTransferPlan& transfers,
|
||||||
|
const ScheduledCommunicationPlan& schedule);
|
||||||
|
|
||||||
|
} // namespace onnx_mlir::spatial
|
||||||
@@ -0,0 +1,766 @@
|
|||||||
|
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||||
|
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||||
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
|
#include "DeferredBoundaryRealization.hpp"
|
||||||
|
#include "DeferredResultRealization.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/StaticIntGrid.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/StaticIntSequence.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/TensorSliceUtils.hpp"
|
||||||
|
#include <array>
|
||||||
|
namespace onnx_mlir::spatial {
|
||||||
|
using namespace mlir;
|
||||||
|
namespace {
|
||||||
|
struct LogicalTransferMetadataView {
|
||||||
|
StaticIntSequenceChain channels;
|
||||||
|
StaticIntSequenceChain parents;
|
||||||
|
StaticIntSequenceChain parentCounts;
|
||||||
|
StaticIntSequenceChain sourceCores;
|
||||||
|
StaticIntSequenceChain targetCores;
|
||||||
|
StaticIntSequenceChain targetLanes;
|
||||||
|
StaticIntSequenceChain localOffsets;
|
||||||
|
SmallVector<StaticIntSequenceChain> projectionOffsets;
|
||||||
|
SmallVector<StaticIntSequenceChain> projectionSizes;
|
||||||
|
SmallVector<StaticIntSequenceChain> projectionStrides;
|
||||||
|
|
||||||
|
size_t size() const { return channels.size(); }
|
||||||
|
};
|
||||||
|
using MetadataMember = StaticIntSequenceChain LogicalTransferMetadataView::*;
|
||||||
|
static constexpr std::array<MetadataMember, 3> transferMetadataMembers{
|
||||||
|
&LogicalTransferMetadataView::channels, &LogicalTransferMetadataView::sourceCores, &LogicalTransferMetadataView::targetCores};
|
||||||
|
struct TransferGrids {
|
||||||
|
std::array<StaticIntGrid, 3> values;
|
||||||
|
StaticIntGrid &channels() { return values[0]; }
|
||||||
|
StaticIntGrid &sourceCores() { return values[1]; }
|
||||||
|
StaticIntGrid &targetCores() { return values[2]; }
|
||||||
|
};
|
||||||
|
template <typename Build> static FailureOr<TransferGrids> buildTransferGrids(Build build) {
|
||||||
|
auto channels = build(transferMetadataMembers[0]);
|
||||||
|
auto sourceCores = build(transferMetadataMembers[1]);
|
||||||
|
auto targetCores = build(transferMetadataMembers[2]);
|
||||||
|
if (failed(channels) || failed(sourceCores) || failed(targetCores))
|
||||||
|
return failure();
|
||||||
|
return TransferGrids{{std::move(*channels), std::move(*sourceCores), std::move(*targetCores)}};
|
||||||
|
}
|
||||||
|
using GridGeometry = DeferredGridSliceGeometry;
|
||||||
|
using StaticGeometryMember = SmallVector<StaticIntSequence> DeferredStaticSliceGeometry::*;
|
||||||
|
using MetadataGeometryMember = SmallVector<StaticIntSequenceChain> LogicalTransferMetadataView::*;
|
||||||
|
static constexpr std::array<StaticGeometryMember, 3> staticGeometryMembers{&DeferredStaticSliceGeometry::offsets, &DeferredStaticSliceGeometry::sizes,
|
||||||
|
&DeferredStaticSliceGeometry::strides};
|
||||||
|
static constexpr std::array<MetadataGeometryMember, 3> metadataGeometryMembers{
|
||||||
|
&LogicalTransferMetadataView::projectionOffsets, &LogicalTransferMetadataView::projectionSizes, &LogicalTransferMetadataView::projectionStrides};
|
||||||
|
static MixedSliceGeometry lookupGeometry(const GridGeometry &geometry, Value row, Value lane, Operation *anchor, DeferredEmissionContext &context,
|
||||||
|
Location loc) {
|
||||||
|
MixedSliceGeometry result;
|
||||||
|
std::array<SmallVectorImpl<OpFoldResult> *, 3> targets{&result.offsets, &result.sizes, &result.strides};
|
||||||
|
for (auto [source, target] : llvm::zip_equal(geometry, targets))
|
||||||
|
for (const StaticIntGrid &grid : source)
|
||||||
|
target->push_back(grid.emitFoldedLookup(row, lane, anchor, context.constants, context.rewriter, loc));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
static FailureOr<Value> emitLaneCondition(const LaneSet &lanes, Value lane, unsigned laneCount, Operation *anchor, DeferredEmissionContext &context,
|
||||||
|
Location loc) {
|
||||||
|
SmallVector<std::pair<size_t, size_t>> intervals;
|
||||||
|
for (LaneInterval interval : lanes.intervals())
|
||||||
|
intervals.push_back({interval.begin, interval.end});
|
||||||
|
auto active = StaticIntGrid::laneIntervals(laneCount, intervals, 1, 0);
|
||||||
|
if (failed(active))
|
||||||
|
return failure();
|
||||||
|
Value selected = active->emitLookup(context.constants.getIndex(0), lane, anchor, context.constants, context.rewriter, loc);
|
||||||
|
return arith::CmpIOp::create(context.rewriter, loc, arith::CmpIPredicate::ne, selected, context.constants.getIndex(0)).getResult();
|
||||||
|
}
|
||||||
|
static void appendMetadata(const ScheduledTransferSlice &slice, LogicalTransferMetadataView &metadata) {
|
||||||
|
ExternalTransferFamily &family = *slice.family;
|
||||||
|
LaneInterval familyLanes = family.targetLanes.intervals().front();
|
||||||
|
LaneInterval requirementLanes = family.requirement->targetLanes.intervals().front();
|
||||||
|
size_t count = slice.transferCount;
|
||||||
|
size_t familyIndex = slice.familyOffset;
|
||||||
|
size_t targetLane = familyLanes.begin + familyIndex;
|
||||||
|
metadata.channels.append(family.channelIds, familyIndex, count);
|
||||||
|
metadata.parents.append(StaticIntSequence::uniform(family.requirement->exchange->exchangeId, count));
|
||||||
|
metadata.parentCounts.append(StaticIntSequence::uniform(family.requirement->exchange->externalTransferCount, count));
|
||||||
|
metadata.sourceCores.append(family.sourceCores, familyIndex, count);
|
||||||
|
metadata.targetCores.append(family.targetCores, familyIndex, count);
|
||||||
|
metadata.targetLanes.append(StaticIntSequence::affine(targetLane, 1, count));
|
||||||
|
if (family.requirement->producerLocalOffsets)
|
||||||
|
metadata.localOffsets.append(*family.requirement->producerLocalOffsets, targetLane - requirementLanes.begin, count);
|
||||||
|
else
|
||||||
|
metadata.localOffsets.append(StaticIntSequence::uniform(0, count));
|
||||||
|
if (family.requirement->producerProjection) {
|
||||||
|
const DeferredStaticSliceGeometry &geometry = *family.requirement->producerProjection;
|
||||||
|
if (metadata.projectionOffsets.empty()) {
|
||||||
|
metadata.projectionOffsets.resize(geometry.offsets.size());
|
||||||
|
metadata.projectionSizes.resize(geometry.sizes.size());
|
||||||
|
metadata.projectionStrides.resize(geometry.strides.size());
|
||||||
|
}
|
||||||
|
size_t geometryIndex = targetLane - requirementLanes.begin;
|
||||||
|
for (auto [target, source] : llvm::zip_equal(metadata.projectionOffsets, geometry.offsets))
|
||||||
|
target.append(source, geometryIndex, count);
|
||||||
|
for (auto [target, source] : llvm::zip_equal(metadata.projectionSizes, geometry.sizes))
|
||||||
|
target.append(source, geometryIndex, count);
|
||||||
|
for (auto [target, source] : llvm::zip_equal(metadata.projectionStrides, geometry.strides))
|
||||||
|
target.append(source, geometryIndex, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static LogicalTransferMetadataView buildMetadataView(ArrayRef<ScheduledTransferSlice> slices) {
|
||||||
|
LogicalTransferMetadataView metadata;
|
||||||
|
for (const ScheduledTransferSlice &slice : slices)
|
||||||
|
appendMetadata(slice, metadata);
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
static StaticIntSequence canonicalizePadded(const StaticIntSequenceChain &chain, size_t count, int64_t defaultValue) {
|
||||||
|
if (chain.size() == count)
|
||||||
|
return chain.canonicalize();
|
||||||
|
StaticIntSequenceChain padded;
|
||||||
|
chain.forEachSegment([&](const StaticIntSequence &sequence, size_t begin, size_t length) { padded.append(sequence, begin, length); });
|
||||||
|
padded.append(StaticIntSequence::uniform(defaultValue, count - chain.size()));
|
||||||
|
return padded.canonicalize();
|
||||||
|
}
|
||||||
|
static void setLogicalTransferMetadata(Operation *op, const LogicalTransferMetadataView &metadata) {
|
||||||
|
size_t logicalCount = metadata.size();
|
||||||
|
OpBuilder builder(op);
|
||||||
|
if (logicalCount == 1) {
|
||||||
|
op->setAttr("raptor.exchange_id", builder.getI64IntegerAttr(metadata.channels.valueAt(0)));
|
||||||
|
op->setAttr("raptor.channel_id", builder.getI64IntegerAttr(metadata.channels.valueAt(0)));
|
||||||
|
op->setAttr("raptor.parent_exchange_id", builder.getI64IntegerAttr(metadata.parents.valueAt(0)));
|
||||||
|
op->setAttr("raptor.parent_transfer_count", builder.getI64IntegerAttr(metadata.parentCounts.valueAt(0)));
|
||||||
|
op->setAttr("raptor.source_core", builder.getI64IntegerAttr(metadata.sourceCores.valueAt(0)));
|
||||||
|
op->setAttr("raptor.target_core", builder.getI64IntegerAttr(metadata.targetCores.valueAt(0)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
op->setAttr("raptor.batch_transfer_count", builder.getI64IntegerAttr(logicalCount));
|
||||||
|
setStaticIntSequenceAttr(op, "raptor.batch_channel_ids", metadata.channels.canonicalize(), logicalCount);
|
||||||
|
setStaticIntSequenceAttr(op, "raptor.batch_source_cores", metadata.sourceCores.canonicalize(), logicalCount);
|
||||||
|
setStaticIntSequenceAttr(op, "raptor.batch_target_cores", metadata.targetCores.canonicalize(), logicalCount);
|
||||||
|
setStaticIntSequenceAttr(op, "raptor.batch_parent_exchange_ids", metadata.parents.canonicalize(), logicalCount);
|
||||||
|
setStaticIntSequenceAttr(op, "raptor.batch_parent_transfer_counts", metadata.parentCounts.canonicalize(), logicalCount);
|
||||||
|
}
|
||||||
|
static Value lookup(ArrayRef<int64_t> table, Value position, Operation *anchor, DeferredEmissionContext &context, Location loc) {
|
||||||
|
return emitStaticIntLookup(StaticIntSequence::fromValues(table), position, anchor, context.constants, context.rewriter, loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<Value> materializeSendPayload(const RequirementFamily &requirement, Value localOffset, const MixedSliceGeometry *producerProjection,
|
||||||
|
DeferredEmissionContext &context, Location loc) {
|
||||||
|
Value payload = requirement.producer->payload;
|
||||||
|
if (producerProjection) {
|
||||||
|
auto fragmentType = dyn_cast<RankedTensorType>(requirement.publicationFragmentType);
|
||||||
|
if (!fragmentType)
|
||||||
|
return failure();
|
||||||
|
return extractMixedSliceOrIdentity(context.rewriter, loc, payload, fragmentType, *producerProjection);
|
||||||
|
}
|
||||||
|
if (payload.getType() == requirement.publicationFragmentType)
|
||||||
|
return payload;
|
||||||
|
auto payloadType = dyn_cast<RankedTensorType>(payload.getType());
|
||||||
|
auto fragmentType = dyn_cast<RankedTensorType>(requirement.publicationFragmentType);
|
||||||
|
if (!payloadType || !fragmentType || !requirement.graphLanes || payloadType.getRank() != fragmentType.getRank() + 1)
|
||||||
|
return failure();
|
||||||
|
MixedSliceGeometry geometry;
|
||||||
|
geometry.offsets.assign(payloadType.getRank(), context.rewriter.getIndexAttr(0));
|
||||||
|
geometry.sizes.push_back(context.rewriter.getIndexAttr(1));
|
||||||
|
geometry.strides.assign(payloadType.getRank(), context.rewriter.getIndexAttr(1));
|
||||||
|
geometry.offsets.front() = localOffset;
|
||||||
|
for (int64_t dimension : fragmentType.getShape())
|
||||||
|
geometry.sizes.push_back(context.rewriter.getIndexAttr(dimension));
|
||||||
|
return extractMixedSliceOrIdentity(context.rewriter, loc, payload, fragmentType, geometry);
|
||||||
|
}
|
||||||
|
|
||||||
|
static LogicalResult emitSendRun(const EmitSendRun &run, Value lane, unsigned laneCount, DeferredEmissionContext &context) {
|
||||||
|
SmallVector<LogicalTransferMetadataView, 0> metadataByLane(laneCount);
|
||||||
|
for (const ScheduledTransferSlice &slice : run.slices) {
|
||||||
|
unsigned sourceLane = slice.family->requirement->producer->scheduledLane;
|
||||||
|
appendMetadata(slice, metadataByLane[sourceLane]);
|
||||||
|
}
|
||||||
|
LogicalTransferMetadataView logical = buildMetadataView(run.slices);
|
||||||
|
size_t actionCount = 0;
|
||||||
|
for (const LogicalTransferMetadataView &laneMetadata : metadataByLane)
|
||||||
|
actionCount = std::max(actionCount, laneMetadata.size());
|
||||||
|
auto buildGrid = [&](auto member, int64_t defaultValue) {
|
||||||
|
SmallVector<StaticIntSequence> columns;
|
||||||
|
for (const LogicalTransferMetadataView &metadata : metadataByLane)
|
||||||
|
columns.push_back(canonicalizePadded(metadata.*member, actionCount, defaultValue));
|
||||||
|
return StaticIntGrid::fromColumns(actionCount, columns, defaultValue);
|
||||||
|
};
|
||||||
|
auto transferGrids = buildTransferGrids([&](MetadataMember member) { return buildGrid(member, (logical.*member).valueAt(0)); });
|
||||||
|
FailureOr<StaticIntGrid> localOffsets = buildGrid(&LogicalTransferMetadataView::localOffsets, logical.localOffsets.valueAt(0));
|
||||||
|
if (failed(transferGrids) || failed(localOffsets))
|
||||||
|
return failure();
|
||||||
|
GridGeometry projectionGrids;
|
||||||
|
for (auto [geometryIndex, sourceMember] : llvm::enumerate(metadataGeometryMembers)) {
|
||||||
|
const auto &logicalValues = logical.*sourceMember;
|
||||||
|
for (size_t dimension = 0; dimension < logicalValues.size(); ++dimension) {
|
||||||
|
int64_t defaultValue = logicalValues[dimension].valueAt(0);
|
||||||
|
SmallVector<StaticIntSequence> columns;
|
||||||
|
for (const LogicalTransferMetadataView &metadata : metadataByLane) {
|
||||||
|
const auto &values = metadata.*sourceMember;
|
||||||
|
columns.push_back(dimension < values.size() ? canonicalizePadded(values[dimension], actionCount, defaultValue)
|
||||||
|
: StaticIntSequence::uniform(defaultValue, actionCount));
|
||||||
|
}
|
||||||
|
auto grid = StaticIntGrid::fromColumns(actionCount, columns, defaultValue);
|
||||||
|
if (failed(grid))
|
||||||
|
return failure();
|
||||||
|
projectionGrids[geometryIndex].push_back(std::move(*grid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SmallVector<int64_t> counts(laneCount);
|
||||||
|
for (unsigned sourceLane = 0; sourceLane < laneCount; ++sourceLane) {
|
||||||
|
const LogicalTransferMetadataView &source = metadataByLane[sourceLane];
|
||||||
|
counts[sourceLane] = source.size();
|
||||||
|
}
|
||||||
|
ExternalTransferFamily &firstFamily = *run.slices.front().family;
|
||||||
|
RequirementFamily &requirement = *firstFamily.requirement;
|
||||||
|
Operation *anchor = requirement.exchange->deferred;
|
||||||
|
Location loc = requirement.exchange->deferred.getLoc();
|
||||||
|
auto emitOne = [&](Value action, Value runtimeLane) -> LogicalResult {
|
||||||
|
Value localOffset = localOffsets->emitLookup(action, runtimeLane, anchor, context.constants, context.rewriter, loc);
|
||||||
|
MixedSliceGeometry projection = lookupGeometry(projectionGrids, action, runtimeLane, anchor, context, loc);
|
||||||
|
auto payload = materializeSendPayload(requirement, localOffset, projectionGrids[0].empty() ? nullptr : &projection, context, loc);
|
||||||
|
if (failed(payload))
|
||||||
|
return failure();
|
||||||
|
auto send = SpatChannelSendOp::create(
|
||||||
|
context.rewriter, loc, transferGrids->channels().emitLookup(action, runtimeLane, anchor, context.constants, context.rewriter, loc),
|
||||||
|
transferGrids->sourceCores().emitLookup(action, runtimeLane, anchor, context.constants, context.rewriter, loc),
|
||||||
|
transferGrids->targetCores().emitLookup(action, runtimeLane, anchor, context.constants, context.rewriter, loc), *payload);
|
||||||
|
setLogicalTransferMetadata(send, logical);
|
||||||
|
return success();
|
||||||
|
};
|
||||||
|
Value runtimeLane = lane ? lane : context.constants.getIndex(0);
|
||||||
|
if (actionCount == 1)
|
||||||
|
return emitOne(context.constants.getIndex(0), runtimeLane);
|
||||||
|
bool uniformCount = true;
|
||||||
|
std::optional<int64_t> firstCount;
|
||||||
|
for (LaneInterval interval : run.lanes.intervals())
|
||||||
|
for (unsigned activeLane = interval.begin; activeLane < interval.end; ++activeLane) {
|
||||||
|
if (!firstCount)
|
||||||
|
firstCount = counts[activeLane];
|
||||||
|
uniformCount &= counts[activeLane] == *firstCount;
|
||||||
|
}
|
||||||
|
Value count = uniformCount ? context.constants.getIndex(*firstCount) : lookup(counts, runtimeLane, anchor, context, loc);
|
||||||
|
auto loop =
|
||||||
|
buildNormalizedScfFor(context.rewriter, loc, context.constants.getIndex(0), count, context.constants.getIndex(1), ValueRange{},
|
||||||
|
[&](OpBuilder &, Location, Value index, ValueRange, SmallVectorImpl<Value> &) { return emitOne(index, runtimeLane); });
|
||||||
|
return success(succeeded(loop));
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<Value> emitReceiveValue(ArrayRef<ScheduledTransferSlice> slices, Value lane, unsigned laneCount, DeferredEmissionContext &context) {
|
||||||
|
LogicalTransferMetadataView metadata = buildMetadataView(slices);
|
||||||
|
RequirementFamily &requirement = *slices.front().family->requirement;
|
||||||
|
Operation *anchor = requirement.exchange->deferred;
|
||||||
|
auto buildGrid = [&](const StaticIntSequenceChain &values) {
|
||||||
|
int64_t defaultValue = values.valueAt(0);
|
||||||
|
SmallVector<int64_t> lanes(laneCount, defaultValue);
|
||||||
|
for (size_t index = 0; index < metadata.size(); ++index) lanes[metadata.targetLanes.valueAt(index)] = values.valueAt(index);
|
||||||
|
StaticIntSequence row = StaticIntSequence::fromValues(lanes);
|
||||||
|
return StaticIntGrid::fromRows(ArrayRef<StaticIntSequence>(row));
|
||||||
|
};
|
||||||
|
auto grids = buildTransferGrids([&](MetadataMember member) { return buildGrid(metadata.*member); });
|
||||||
|
if (failed(grids)) return failure();
|
||||||
|
Value position = lane ? lane : context.constants.getIndex(0);
|
||||||
|
Value row = context.constants.getIndex(0);
|
||||||
|
auto receive = SpatChannelReceiveOp::create(context.rewriter, anchor->getLoc(), requirement.publicationFragmentType,
|
||||||
|
grids->channels().emitLookup(row, position, anchor, context.constants, context.rewriter, anchor->getLoc()),
|
||||||
|
grids->sourceCores().emitLookup(row, position, anchor, context.constants, context.rewriter, anchor->getLoc()),
|
||||||
|
grids->targetCores().emitLookup(row, position, anchor, context.constants, context.rewriter, anchor->getLoc()));
|
||||||
|
setLogicalTransferMetadata(receive, metadata);
|
||||||
|
return receive.getOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Insert>
|
||||||
|
static FailureOr<Value> emitReceiveAssembly(const EmitReceiveAssemblyRun &run, Value lane, unsigned laneCount, Value initial,
|
||||||
|
DeferredEmissionContext &context, Insert insert) {
|
||||||
|
if (run.entryOffsets.size() != run.positions.size() + 1 || run.entryLanes.size() != run.positions.size() || run.positions.empty())
|
||||||
|
return failure();
|
||||||
|
LogicalTransferMetadataView logical = buildMetadataView(run.slices);
|
||||||
|
size_t actionCount = run.positions.size();
|
||||||
|
SmallVector<int64_t> counts(laneCount);
|
||||||
|
std::optional<TransferGrids> transferGrids;
|
||||||
|
std::optional<StaticIntGrid> positions;
|
||||||
|
SmallVector<LogicalTransferMetadataView, 0> metadataByEntry;
|
||||||
|
bool rectangular = run.lanes.size() == laneCount && llvm::all_of(run.entryLanes, [&](const LaneSet &lanes) { return lanes.size() == laneCount; });
|
||||||
|
for (size_t entry = 0; rectangular && entry < run.positions.size(); ++entry) {
|
||||||
|
ArrayRef<ScheduledTransferSlice> slices =
|
||||||
|
ArrayRef(run.slices).slice(run.entryOffsets[entry], run.entryOffsets[entry + 1] - run.entryOffsets[entry]);
|
||||||
|
SmallVector<const ScheduledTransferSlice *> ordered;
|
||||||
|
for (const ScheduledTransferSlice &slice : slices)
|
||||||
|
ordered.push_back(&slice);
|
||||||
|
llvm::sort(ordered, [](const ScheduledTransferSlice *left, const ScheduledTransferSlice *right) {
|
||||||
|
LaneInterval leftFamily = left->family->targetLanes.intervals().front();
|
||||||
|
LaneInterval rightFamily = right->family->targetLanes.intervals().front();
|
||||||
|
return leftFamily.begin + left->familyOffset < rightFamily.begin + right->familyOffset;
|
||||||
|
});
|
||||||
|
unsigned covered = 0;
|
||||||
|
LogicalTransferMetadataView metadata;
|
||||||
|
for (const ScheduledTransferSlice *slice : ordered) {
|
||||||
|
LaneInterval family = slice->family->targetLanes.intervals().front();
|
||||||
|
unsigned begin = family.begin + slice->familyOffset;
|
||||||
|
if (begin != covered) {
|
||||||
|
rectangular = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
appendMetadata(*slice, metadata);
|
||||||
|
covered += slice->transferCount;
|
||||||
|
}
|
||||||
|
rectangular &= covered == laneCount;
|
||||||
|
if (rectangular)
|
||||||
|
metadataByEntry.push_back(std::move(metadata));
|
||||||
|
}
|
||||||
|
if (rectangular) {
|
||||||
|
auto buildRows = [&](auto member) {
|
||||||
|
SmallVector<StaticIntSequence> rows;
|
||||||
|
for (const LogicalTransferMetadataView &metadata : metadataByEntry)
|
||||||
|
rows.push_back((metadata.*member).canonicalize());
|
||||||
|
return StaticIntGrid::fromRows(rows);
|
||||||
|
};
|
||||||
|
auto grids = buildTransferGrids(buildRows);
|
||||||
|
SmallVector<StaticIntSequence> positionRows;
|
||||||
|
for (unsigned position : run.positions)
|
||||||
|
positionRows.push_back(StaticIntSequence::uniform(position, laneCount));
|
||||||
|
auto positionGrid = StaticIntGrid::fromRows(positionRows);
|
||||||
|
if (failed(grids) || failed(positionGrid))
|
||||||
|
return failure();
|
||||||
|
transferGrids = std::move(*grids);
|
||||||
|
positions = std::move(*positionGrid);
|
||||||
|
llvm::fill(counts, actionCount);
|
||||||
|
} else {
|
||||||
|
SmallVector<LogicalTransferMetadataView, 0> metadataByLane(laneCount);
|
||||||
|
SmallVector<StaticIntSequenceChain, 0> positionsByLane(laneCount);
|
||||||
|
for (size_t entry = 0; entry < run.positions.size(); ++entry) {
|
||||||
|
ArrayRef<ScheduledTransferSlice> slices =
|
||||||
|
ArrayRef(run.slices).slice(run.entryOffsets[entry], run.entryOffsets[entry + 1] - run.entryOffsets[entry]);
|
||||||
|
for (const ScheduledTransferSlice &slice : slices) {
|
||||||
|
LaneInterval family = slice.family->targetLanes.intervals().front();
|
||||||
|
unsigned begin = family.begin + slice.familyOffset;
|
||||||
|
for (unsigned targetLane = begin; targetLane < begin + slice.transferCount; ++targetLane) {
|
||||||
|
ScheduledTransferSlice selected = slice;
|
||||||
|
selected.familyOffset += targetLane - begin;
|
||||||
|
selected.transferCount = 1;
|
||||||
|
appendMetadata(selected, metadataByLane[targetLane]);
|
||||||
|
positionsByLane[targetLane].append(
|
||||||
|
StaticIntSequence::uniform(run.positions[entry], 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actionCount = 0;
|
||||||
|
for (unsigned targetLane = 0; targetLane < laneCount; ++targetLane) {
|
||||||
|
counts[targetLane] = metadataByLane[targetLane].size();
|
||||||
|
actionCount = std::max(actionCount, metadataByLane[targetLane].size());
|
||||||
|
}
|
||||||
|
if (actionCount == 0)
|
||||||
|
return failure();
|
||||||
|
auto buildGrid = [&](auto member) {
|
||||||
|
const StaticIntSequenceChain &first = logical.*member;
|
||||||
|
int64_t defaultValue = first.valueAt(0);
|
||||||
|
SmallVector<StaticIntSequence> columns;
|
||||||
|
for (const LogicalTransferMetadataView &metadata : metadataByLane)
|
||||||
|
columns.push_back(canonicalizePadded(metadata.*member, actionCount, defaultValue));
|
||||||
|
return StaticIntGrid::fromColumns(actionCount, columns, defaultValue);
|
||||||
|
};
|
||||||
|
auto grids = buildTransferGrids(buildGrid);
|
||||||
|
SmallVector<StaticIntSequence> positionColumns;
|
||||||
|
for (const StaticIntSequenceChain &values : positionsByLane)
|
||||||
|
positionColumns.push_back(
|
||||||
|
canonicalizePadded(values, actionCount, run.positions.front()));
|
||||||
|
auto positionGrid = StaticIntGrid::fromColumns(
|
||||||
|
actionCount, positionColumns, run.positions.front());
|
||||||
|
if (failed(grids) || failed(positionGrid))
|
||||||
|
return failure();
|
||||||
|
transferGrids = std::move(*grids);
|
||||||
|
positions = std::move(*positionGrid);
|
||||||
|
}
|
||||||
|
if (!run.collection)
|
||||||
|
return failure();
|
||||||
|
Operation *anchor = run.collection->key.exchange->deferred.getOperation();
|
||||||
|
Location loc = anchor->getLoc();
|
||||||
|
Value runtimeLane = lane ? lane : context.constants.getIndex(0);
|
||||||
|
auto emitEntry = [&](Value entry, Value current) -> FailureOr<Value> {
|
||||||
|
Type fragmentType = run.slices.front().family->requirement->publicationFragmentType;
|
||||||
|
auto receive =
|
||||||
|
SpatChannelReceiveOp::create(context.rewriter, loc, fragmentType,
|
||||||
|
transferGrids->channels().emitLookup(entry, runtimeLane, anchor, context.constants, context.rewriter, loc),
|
||||||
|
transferGrids->sourceCores().emitLookup(entry, runtimeLane, anchor, context.constants, context.rewriter, loc),
|
||||||
|
transferGrids->targetCores().emitLookup(entry, runtimeLane, anchor, context.constants, context.rewriter, loc));
|
||||||
|
setLogicalTransferMetadata(receive, logical);
|
||||||
|
Value position = positions->emitLookup(
|
||||||
|
entry, runtimeLane, anchor, context.constants, context.rewriter, loc);
|
||||||
|
return insert(receive.getOutput(), position, entry, runtimeLane, current);
|
||||||
|
};
|
||||||
|
if (actionCount == 1 && llvm::all_of(counts, [](int64_t count) { return count == 1; }))
|
||||||
|
return emitEntry(context.constants.getIndex(0), initial);
|
||||||
|
Value count = emitStaticIntLookup(StaticIntSequence::fromValues(counts), runtimeLane, anchor, context.constants, context.rewriter, loc);
|
||||||
|
auto loop = buildNormalizedScfFor(context.rewriter, loc, context.constants.getIndex(0), count, context.constants.getIndex(1), ValueRange{initial},
|
||||||
|
[&](OpBuilder &, Location, Value entry, ValueRange iterArgs, SmallVectorImpl<Value> &yielded) -> LogicalResult {
|
||||||
|
auto value = emitEntry(entry, iterArgs.front());
|
||||||
|
if (failed(value))
|
||||||
|
return failure();
|
||||||
|
yielded.push_back(*value);
|
||||||
|
return success();
|
||||||
|
});
|
||||||
|
if (failed(loop))
|
||||||
|
return failure();
|
||||||
|
return loop->results.front();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Emit>
|
||||||
|
static LogicalResult emitCollectionUpdate(const LaneSet &lanes, Value lane, unsigned laneCount, FragmentCollectionKey key, Value current,
|
||||||
|
Operation *anchor, DeferredEmissionContext &context, Emit emit, bool local = false) {
|
||||||
|
FailureOr<Value> value;
|
||||||
|
if (local && lanes.size() != laneCount) {
|
||||||
|
if (!lane) return failure();
|
||||||
|
auto condition = emitLaneCondition(lanes, lane, laneCount, anchor, context, anchor->getLoc());
|
||||||
|
if (failed(condition)) return failure();
|
||||||
|
auto conditional = scf::IfOp::create(context.rewriter, anchor->getLoc(), TypeRange{current.getType()}, *condition, true);
|
||||||
|
OpBuilder::InsertionGuard guard(context.rewriter);
|
||||||
|
context.rewriter.setInsertionPointToStart(&conditional.getThenRegion().front());
|
||||||
|
value = emit(current);
|
||||||
|
if (failed(value)) return failure();
|
||||||
|
scf::YieldOp::create(context.rewriter, anchor->getLoc(), *value);
|
||||||
|
context.rewriter.setInsertionPointToStart(&conditional.getElseRegion().front());
|
||||||
|
scf::YieldOp::create(context.rewriter, anchor->getLoc(), current);
|
||||||
|
value = conditional.getResult(0);
|
||||||
|
} else value = emit(current);
|
||||||
|
if (failed(value))
|
||||||
|
return failure();
|
||||||
|
context.fragmentCollections[key] = *value;
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<Value> insertProjectionFragment(Value fragment, Value specialization, Value position, Value geometryRow, Value runtimeLane,
|
||||||
|
Value assembled, const DeferredProjectionLeafTemplate &leaf, const GridGeometry &geometry,
|
||||||
|
DeferredExchangePlan &exchange, bool grouped, DeferredEmissionContext &context) {
|
||||||
|
Value shaped = fragment;
|
||||||
|
if (leaf.form == DeferredLeafForm::GraphBatchProjection) {
|
||||||
|
SmallVector<int64_t> shape(leaf.leadingRankReduced ? leaf.reconstructedType.getShape() : leaf.reconstructedType.getShape().drop_front());
|
||||||
|
shaped = extractMixedSliceOrIdentity(context.rewriter, exchange.deferred.getLoc(), shaped,
|
||||||
|
RankedTensorType::get(shape, leaf.reconstructedType.getElementType()),
|
||||||
|
lookupGeometry(geometry, geometryRow, runtimeLane, exchange.deferred, context, exchange.deferred.getLoc()));
|
||||||
|
if (!shaped) return failure();
|
||||||
|
}
|
||||||
|
auto sourceType = dyn_cast<RankedTensorType>(shaped.getType());
|
||||||
|
auto assembledType = dyn_cast<RankedTensorType>(assembled.getType());
|
||||||
|
int64_t rankDifference = sourceType && assembledType ? assembledType.getRank() - sourceType.getRank() : 0;
|
||||||
|
if (rankDifference < 0 || rankDifference > 2 || (grouped && rankDifference == 0)) return failure();
|
||||||
|
if (rankDifference == 0 && sourceType != assembledType) return failure();
|
||||||
|
MixedSliceGeometry slice;
|
||||||
|
slice.offsets.assign(assembledType.getRank(), context.rewriter.getIndexAttr(0));
|
||||||
|
if (rankDifference) slice.offsets.front() = grouped ? specialization : position;
|
||||||
|
if (rankDifference == 2) slice.offsets[1] = position;
|
||||||
|
slice.sizes.assign(rankDifference, context.rewriter.getIndexAttr(1));
|
||||||
|
for (int64_t dimension : sourceType.getShape()) slice.sizes.push_back(context.rewriter.getIndexAttr(dimension));
|
||||||
|
slice.strides.assign(assembledType.getRank(), context.rewriter.getIndexAttr(1));
|
||||||
|
return insertMixedSlice(context.rewriter, exchange.deferred.getLoc(), shaped, assembled, slice);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Value createCollectionInitial(const FragmentCollectionPlan &collection, DeferredEmissionContext &context) {
|
||||||
|
DeferredExchangePlan &exchange = *collection.key.exchange;
|
||||||
|
if (collection.key.kind == FragmentCollectionKind::InsertAssembly)
|
||||||
|
return context.rewriter.clone(*exchange.program.insertAssembly->initialValue)->getResult(0);
|
||||||
|
return tensor::EmptyOp::create(context.rewriter, exchange.deferred.getLoc(), collection.collectionType.getShape(),
|
||||||
|
collection.collectionType.getElementType());
|
||||||
|
}
|
||||||
|
|
||||||
|
static LogicalResult emitLeafCollectionUpdate(const EmitReceiveAssemblyRun &run, Value lane, unsigned laneCount,
|
||||||
|
const DeferredResultPlan &resultPlan, DeferredEmissionContext &context) {
|
||||||
|
const FragmentCollectionPlan &collection = *run.collection;
|
||||||
|
DeferredExchangePlan &exchange = *collection.key.exchange;
|
||||||
|
unsigned leafIndex = collection.key.leafIndex;
|
||||||
|
const DeferredProjectionLeafTemplate &leaf = exchange.program.leaves[leafIndex];
|
||||||
|
bool grouped = collection.key.kind == FragmentCollectionKind::GroupedLeaf;
|
||||||
|
FragmentCollectionKey key = collection.key;
|
||||||
|
if (collection.key.kind == FragmentCollectionKind::Leaf && collection.positionCount == 1
|
||||||
|
&& run.lanes.size() == laneCount
|
||||||
|
&& llvm::all_of(run.positions, [](unsigned position) { return position == 0; })
|
||||||
|
&& run.slices.front().family->requirement->publicationFragmentType == collection.collectionType) {
|
||||||
|
auto value = emitReceiveValue(run.slices, lane, laneCount, context);
|
||||||
|
if (failed(value)) return failure();
|
||||||
|
context.fragmentCollections[key] = *value;
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
Value current = context.fragmentCollections.lookup(collection.key);
|
||||||
|
if (!current) current = createCollectionInitial(collection, context);
|
||||||
|
const GridGeometry &geometry = resultPlan.innerGeometry[leafIndex];
|
||||||
|
auto emit = [&](Value initial) {
|
||||||
|
return emitReceiveAssembly(run, lane, laneCount, initial, context,
|
||||||
|
[&](Value fragment, Value position, Value, Value runtimeLane, Value assembled) -> FailureOr<Value> {
|
||||||
|
Value specialization = context.constants.getIndex(0);
|
||||||
|
Value leafPosition = position;
|
||||||
|
if (grouped) {
|
||||||
|
Value divisor = context.constants.getIndex(collection.positionCount);
|
||||||
|
specialization = arith::DivUIOp::create(context.rewriter, exchange.deferred.getLoc(), position, divisor);
|
||||||
|
leafPosition = arith::RemUIOp::create(context.rewriter, exchange.deferred.getLoc(), position, divisor);
|
||||||
|
}
|
||||||
|
return insertProjectionFragment(fragment, specialization, leafPosition, grouped ? specialization : context.constants.getIndex(0), runtimeLane,
|
||||||
|
assembled, leaf, geometry, exchange, grouped, context);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return emitCollectionUpdate(run.lanes, lane, laneCount, key, current, exchange.deferred, context, emit);
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<Value> transformAssemblySource(Value fragment, const DeferredInsertAssemblyEntryTemplate &entry,
|
||||||
|
DeferredExchangePlan &exchange, DeferredEmissionContext &context) {
|
||||||
|
switch (entry.sourceTransform) {
|
||||||
|
case DeferredAssemblySourceTransform::Identity:
|
||||||
|
return fragment.getType() == entry.sourceType ? FailureOr<Value>(fragment) : FailureOr<Value>(failure());
|
||||||
|
case DeferredAssemblySourceTransform::AddLeadingUnitDimension:
|
||||||
|
return addLeadingUnitTensorDimension(context.rewriter, exchange.deferred.getLoc(), fragment);
|
||||||
|
case DeferredAssemblySourceTransform::RemoveLeadingUnitDimension:
|
||||||
|
return removeLeadingUnitTensorDimension(context.rewriter, exchange.deferred.getLoc(), fragment, entry.sourceType);
|
||||||
|
}
|
||||||
|
llvm_unreachable("unknown deferred assembly source transform");
|
||||||
|
}
|
||||||
|
|
||||||
|
static LogicalResult emitInsertAssemblyUpdate(const EmitReceiveAssemblyRun &run, Value lane, unsigned laneCount,
|
||||||
|
const DeferredResultPlan &resultPlan, DeferredEmissionContext &context) {
|
||||||
|
const FragmentCollectionPlan &collection = *run.collection;
|
||||||
|
DeferredExchangePlan &exchange = *collection.key.exchange;
|
||||||
|
const DeferredInsertAssemblyTemplate &assembly = *exchange.program.insertAssembly;
|
||||||
|
if (run.positions.empty()) return failure();
|
||||||
|
const DeferredInsertAssemblyEntryTemplate &sourceEntry = assembly.entries[run.positions.front()];
|
||||||
|
Value current = context.fragmentCollections.lookup(collection.key);
|
||||||
|
if (!current) current = createCollectionInitial(collection, context);
|
||||||
|
auto emit = [&](Value initial) {
|
||||||
|
return emitReceiveAssembly(run, lane, laneCount, initial, context,
|
||||||
|
[&](Value fragment, Value position, Value, Value runtimeLane, Value assembled) -> FailureOr<Value> {
|
||||||
|
auto shaped = transformAssemblySource(fragment, sourceEntry, exchange, context);
|
||||||
|
if (failed(shaped) || shaped->getType() != sourceEntry.sourceType) return failure();
|
||||||
|
return insertMixedSlice(context.rewriter, exchange.deferred.getLoc(), *shaped, assembled,
|
||||||
|
lookupGeometry(resultPlan.assemblyGeometry, position, runtimeLane, exchange.deferred, context, exchange.deferred.getLoc()));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return emitCollectionUpdate(run.lanes, lane, laneCount, collection.key, current, exchange.deferred, context, emit);
|
||||||
|
}
|
||||||
|
|
||||||
|
static LogicalResult emitConditionalSendRun(const EmitSendRun &run, Value lane, unsigned laneCount, DeferredEmissionContext &context) {
|
||||||
|
if (run.lanes.size() == laneCount)
|
||||||
|
return emitSendRun(run, lane, laneCount, context);
|
||||||
|
if (!lane)
|
||||||
|
return failure();
|
||||||
|
Operation *anchor = run.slices.front().family->sourceScheduled->op;
|
||||||
|
Location loc = run.slices.front().family->requirement->exchange->deferred.getLoc();
|
||||||
|
auto condition = emitLaneCondition(run.lanes, lane, laneCount, anchor, context, loc);
|
||||||
|
if (failed(condition))
|
||||||
|
return failure();
|
||||||
|
auto conditional = scf::IfOp::create(context.rewriter, loc, TypeRange{}, *condition, false);
|
||||||
|
Block &block = conditional.getThenRegion().front();
|
||||||
|
auto yield = cast<scf::YieldOp>(block.getTerminator());
|
||||||
|
OpBuilder::InsertionGuard guard(context.rewriter);
|
||||||
|
context.rewriter.setInsertionPoint(yield);
|
||||||
|
return emitSendRun(run, lane, laneCount, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<Value> materializeLocalValue(const EmitLocalCollectionRun &local, Value lane, unsigned laneCount, DeferredEmissionContext &context) {
|
||||||
|
RequirementFamily &reference = *local.families.front()->requirement;
|
||||||
|
Value fragment = reference.producer->payload;
|
||||||
|
if (reference.producerProjection || fragment.getType() != reference.publicationFragmentType) {
|
||||||
|
auto buildLaneGrid = [&](auto getSequence, int64_t defaultValue) {
|
||||||
|
SmallVector<int64_t> values(laneCount, defaultValue);
|
||||||
|
for (LocalAvailabilityFamily *family : local.families) {
|
||||||
|
RequirementFamily &requirement = *family->requirement;
|
||||||
|
const StaticIntSequence *sequence = getSequence(requirement);
|
||||||
|
if (!sequence)
|
||||||
|
continue;
|
||||||
|
LaneInterval lanes = requirement.targetLanes.intervals().front();
|
||||||
|
size_t laneSize = lanes.end - lanes.begin;
|
||||||
|
if (lanes.end > laneCount || sequence->size() < laneSize)
|
||||||
|
return FailureOr<StaticIntGrid>(failure());
|
||||||
|
for (size_t offset = 0; offset < laneSize; ++offset)
|
||||||
|
values[lanes.begin + offset] = sequence->valueAt(offset);
|
||||||
|
}
|
||||||
|
StaticIntSequence row = StaticIntSequence::fromValues(values);
|
||||||
|
return StaticIntGrid::fromRows(ArrayRef<StaticIntSequence>(row));
|
||||||
|
};
|
||||||
|
auto offsets = buildLaneGrid(
|
||||||
|
[](RequirementFamily &requirement) { return requirement.producerLocalOffsets ? &*requirement.producerLocalOffsets : nullptr; }, 0);
|
||||||
|
GridGeometry projectionGrids;
|
||||||
|
for (auto [geometryIndex, sourceMember] : llvm::enumerate(staticGeometryMembers)) {
|
||||||
|
if (!reference.producerProjection)
|
||||||
|
break;
|
||||||
|
StaticGeometryMember member = sourceMember;
|
||||||
|
const auto &referenceValues = reference.producerProjection.value().*member;
|
||||||
|
for (size_t dimension = 0; dimension < referenceValues.size(); ++dimension) {
|
||||||
|
auto grid = buildLaneGrid(
|
||||||
|
[&](RequirementFamily &requirement) -> const StaticIntSequence * {
|
||||||
|
if (!requirement.producerProjection)
|
||||||
|
return nullptr;
|
||||||
|
const auto &values = requirement.producerProjection.value().*member;
|
||||||
|
return dimension < values.size() ? &values[dimension] : nullptr;
|
||||||
|
},
|
||||||
|
referenceValues[dimension].valueAt(0));
|
||||||
|
if (failed(grid))
|
||||||
|
return failure();
|
||||||
|
projectionGrids[geometryIndex].push_back(std::move(*grid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (failed(offsets))
|
||||||
|
return failure();
|
||||||
|
Location loc = reference.exchange->deferred.getLoc();
|
||||||
|
Value position = lane ? lane : context.constants.getIndex(0);
|
||||||
|
Value row = context.constants.getIndex(0);
|
||||||
|
MixedSliceGeometry projection = lookupGeometry(projectionGrids, row, position, reference.exchange->deferred, context, loc);
|
||||||
|
auto materialized =
|
||||||
|
materializeSendPayload(reference, offsets->emitLookup(row, position, reference.exchange->deferred, context.constants, context.rewriter, loc),
|
||||||
|
projectionGrids[0].empty() ? nullptr : &projection, context, loc);
|
||||||
|
if (failed(materialized))
|
||||||
|
return failure();
|
||||||
|
fragment = *materialized;
|
||||||
|
}
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const DeferredResultPlan *findResultPlan(ArrayRef<DeferredResultPlan> results, DeferredExchangePlan *exchange) {
|
||||||
|
auto it = llvm::find_if(results, [&](const DeferredResultPlan &result) { return result.exchange == exchange; });
|
||||||
|
return it == results.end() ? nullptr : &*it;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LogicalResult emitLocalCollectionUpdate(const EmitLocalCollectionRun &update, Value lane, unsigned laneCount, const DeferredResultPlan &resultPlan,
|
||||||
|
DeferredEmissionContext &context) {
|
||||||
|
const FragmentCollectionPlan &collection = *update.collection;
|
||||||
|
RequirementFamily &requirement = *update.families.front()->requirement;
|
||||||
|
DeferredExchangePlan &exchange = *requirement.exchange;
|
||||||
|
if (update.concatenatePayloads) {
|
||||||
|
SmallVector<Value> payloads;
|
||||||
|
for (LocalAvailabilityFamily *family : update.families)
|
||||||
|
payloads.push_back(family->requirement->producer->payload);
|
||||||
|
context.fragmentCollections[collection.key] = payloads.size() == 1
|
||||||
|
? payloads.front()
|
||||||
|
: SpatConcatOp::create(
|
||||||
|
context.rewriter, exchange.deferred.getLoc(),
|
||||||
|
collection.collectionType, context.rewriter.getI64IntegerAttr(0),
|
||||||
|
payloads).getOutput();
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
auto materialize = [&]() { return materializeLocalValue(update, lane, laneCount, context); };
|
||||||
|
if (collection.key.kind == FragmentCollectionKind::Leaf
|
||||||
|
&& collection.positionCount == 1
|
||||||
|
&& update.lanes.size() == laneCount
|
||||||
|
&& requirement.publicationFragmentType == collection.collectionType) {
|
||||||
|
auto fragment = materialize();
|
||||||
|
if (failed(fragment)) return failure();
|
||||||
|
context.fragmentCollections[collection.key] = *fragment;
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
Value current = context.fragmentCollections.lookup(collection.key);
|
||||||
|
if (!current) current = createCollectionInitial(collection, context);
|
||||||
|
if (collection.key.kind == FragmentCollectionKind::InsertAssembly) {
|
||||||
|
const auto &entry = exchange.program.insertAssembly->entries[update.collectionPosition];
|
||||||
|
auto emit = [&](Value assembled) -> FailureOr<Value> {
|
||||||
|
auto fragment = materialize();
|
||||||
|
if (failed(fragment)) return failure();
|
||||||
|
auto shaped = transformAssemblySource(*fragment, entry, exchange, context);
|
||||||
|
if (failed(shaped) || shaped->getType() != entry.sourceType) return failure();
|
||||||
|
return insertMixedSlice(context.rewriter, exchange.deferred.getLoc(), *shaped, assembled,
|
||||||
|
lookupGeometry(resultPlan.assemblyGeometry, context.constants.getIndex(update.collectionPosition), lane ? lane : context.constants.getIndex(0),
|
||||||
|
exchange.deferred, context, exchange.deferred.getLoc()));
|
||||||
|
};
|
||||||
|
return emitCollectionUpdate(update.lanes, lane, laneCount, collection.key, current, exchange.deferred, context, emit, true);
|
||||||
|
}
|
||||||
|
bool grouped = collection.key.kind == FragmentCollectionKind::GroupedLeaf;
|
||||||
|
unsigned specialization = grouped ? update.collectionPosition / collection.positionCount : 0;
|
||||||
|
unsigned leafPosition = grouped ? update.collectionPosition % collection.positionCount : update.collectionPosition;
|
||||||
|
unsigned leafIndex = collection.key.leafIndex;
|
||||||
|
const DeferredProjectionLeafTemplate &leaf = exchange.program.leaves[leafIndex];
|
||||||
|
auto emit = [&](Value assembled) -> FailureOr<Value> {
|
||||||
|
auto fragment = materialize();
|
||||||
|
if (failed(fragment)) return failure();
|
||||||
|
return insertProjectionFragment(*fragment, context.constants.getIndex(specialization),
|
||||||
|
context.constants.getIndex(leafPosition), context.constants.getIndex(specialization),
|
||||||
|
lane ? lane : context.constants.getIndex(0), assembled, leaf, resultPlan.innerGeometry[leafIndex], exchange, grouped, context);
|
||||||
|
};
|
||||||
|
return emitCollectionUpdate(update.lanes, lane, laneCount, collection.key, current, exchange.deferred, context, emit, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<SmallVector<Value>> emitInstructions(ArrayRef<BoundaryInstruction> instructions, Value lane, unsigned laneCount,
|
||||||
|
ArrayRef<DeferredResultPlan> results, DeferredEmissionContext &context) {
|
||||||
|
SmallVector<Value> produced;
|
||||||
|
for (const BoundaryInstruction &instruction : instructions) {
|
||||||
|
if (auto send = std::get_if<EmitSendRun>(&instruction)) {
|
||||||
|
if (failed(emitConditionalSendRun(*send, lane, laneCount, context)))
|
||||||
|
return failure();
|
||||||
|
} else if (auto update = std::get_if<EmitLocalCollectionRun>(&instruction)) {
|
||||||
|
DeferredExchangePlan *exchange = update->collection->key.exchange;
|
||||||
|
const DeferredResultPlan *resultPlan = findResultPlan(results, exchange);
|
||||||
|
LogicalResult emitted = resultPlan
|
||||||
|
? emitLocalCollectionUpdate(*update, lane, laneCount, *resultPlan, context)
|
||||||
|
: failure();
|
||||||
|
if (failed(emitted))
|
||||||
|
return exchange->deferred.emitOpError(
|
||||||
|
"failed to update fragment collection from local availability"), failure();
|
||||||
|
} else if (auto assembly = std::get_if<EmitReceiveAssemblyRun>(&instruction)) {
|
||||||
|
DeferredExchangePlan *exchange = assembly->collection->key.exchange;
|
||||||
|
const DeferredResultPlan *resultPlan = findResultPlan(results, exchange);
|
||||||
|
LogicalResult emitted = resultPlan
|
||||||
|
? (assembly->collection->key.kind == FragmentCollectionKind::InsertAssembly
|
||||||
|
? emitInsertAssemblyUpdate(*assembly, lane, laneCount, *resultPlan, context)
|
||||||
|
: emitLeafCollectionUpdate(*assembly, lane, laneCount, *resultPlan, context))
|
||||||
|
: failure();
|
||||||
|
if (failed(emitted))
|
||||||
|
return exchange->deferred.emitOpError(
|
||||||
|
"failed to update fragment collection from received availability"), failure();
|
||||||
|
} else if (auto result = std::get_if<ProduceDeferredResult>(&instruction)) {
|
||||||
|
const DeferredResultPlan *plan = findResultPlan(results, result->exchange);
|
||||||
|
if (!plan)
|
||||||
|
return failure();
|
||||||
|
auto value = realizeDeferredResult(*plan, lane, context);
|
||||||
|
if (failed(value))
|
||||||
|
return result->exchange->deferred.emitOpError(
|
||||||
|
"failed to realize deferred result from fragment collections"), failure();
|
||||||
|
produced.push_back(*value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return produced;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SmallVector<DeferredExchangePlan *> getProducedExchanges(ArrayRef<BoundaryInstruction> instructions) {
|
||||||
|
SmallVector<DeferredExchangePlan *> exchanges;
|
||||||
|
for (const BoundaryInstruction &instruction : instructions)
|
||||||
|
if (auto result = std::get_if<ProduceDeferredResult>(&instruction))
|
||||||
|
exchanges.push_back(result->exchange);
|
||||||
|
return exchanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setInsertionAtBoundary(IRRewriter &rewriter, const BoundaryKey &key) {
|
||||||
|
ScheduledInfo &scheduled = *key.first;
|
||||||
|
if (key.second < scheduled.stepAnchors.size() && scheduled.stepAnchors[key.second]->getBlock()) {
|
||||||
|
rewriter.setInsertionPoint(scheduled.stepAnchors[key.second]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rewriter.setInsertionPoint(scheduled.blocks.front()->getTerminator());
|
||||||
|
}
|
||||||
|
|
||||||
|
static LogicalResult replaceResults(ArrayRef<DeferredExchangePlan *> exchanges, ValueRange replacements,
|
||||||
|
DeferredReplacementMap &deferredReplacements) {
|
||||||
|
if (exchanges.size() != replacements.size())
|
||||||
|
return failure();
|
||||||
|
for (auto [exchange, replacement] : llvm::zip_equal(exchanges, replacements)) {
|
||||||
|
deferredReplacements.insert({exchange->deferred, replacement});
|
||||||
|
}
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
static LogicalResult emitBoundary(const BoundaryProgram &boundary, ArrayRef<DeferredResultPlan> results, DeferredEmissionContext &context,
|
||||||
|
DeferredReplacementMap &replacements) {
|
||||||
|
setInsertionAtBoundary(context.rewriter, boundary.key);
|
||||||
|
unsigned laneCount = boundary.key.first->cores.size();
|
||||||
|
Value lane;
|
||||||
|
if (auto batch = dyn_cast<SpatScheduledComputeBatch>(boundary.key.first->op))
|
||||||
|
lane = *batch.getLaneArgument();
|
||||||
|
SmallVector<DeferredExchangePlan *> exchanges = getProducedExchanges(boundary.instructions);
|
||||||
|
auto values = emitInstructions(boundary.instructions, lane, laneCount, results, context);
|
||||||
|
return failed(values) ? failure() : replaceResults(exchanges, *values, replacements);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
LogicalResult realizeDeferredBoundaries(ArrayRef<BoundaryProgram> boundaries, ArrayRef<DeferredResultPlan> results, DeferredEmissionContext &context,
|
||||||
|
DeferredReplacementMap &replacements) {
|
||||||
|
ScheduledInfo *scheduled = nullptr;
|
||||||
|
for (const BoundaryProgram &boundary : boundaries) {
|
||||||
|
if (scheduled != boundary.key.first) {
|
||||||
|
context.fragmentCollections.clear();
|
||||||
|
scheduled = boundary.key.first;
|
||||||
|
}
|
||||||
|
if (failed(emitBoundary(boundary, results, context, replacements)))
|
||||||
|
return boundary.key.first->op->emitOpError("phase 2 failed to realize a communication boundary");
|
||||||
|
}
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace onnx_mlir::spatial
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "llvm/ADT/MapVector.h"
|
||||||
|
|
||||||
|
#include "mlir/IR/PatternMatch.h"
|
||||||
|
|
||||||
|
#include "DeferredBoundaryPlanning.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
|
||||||
|
|
||||||
|
namespace onnx_mlir::spatial {
|
||||||
|
|
||||||
|
struct FragmentCollectionKeyInfo {
|
||||||
|
static FragmentCollectionKey getEmptyKey() {
|
||||||
|
return {llvm::DenseMapInfo<DeferredExchangePlan*>::getEmptyKey()};
|
||||||
|
}
|
||||||
|
static FragmentCollectionKey getTombstoneKey() {
|
||||||
|
return {llvm::DenseMapInfo<DeferredExchangePlan*>::getTombstoneKey()};
|
||||||
|
}
|
||||||
|
static unsigned getHashValue(const FragmentCollectionKey& key) {
|
||||||
|
return llvm::hash_combine(
|
||||||
|
key.exchange, key.kind, key.leafIndex);
|
||||||
|
}
|
||||||
|
static bool isEqual(const FragmentCollectionKey& lhs,
|
||||||
|
const FragmentCollectionKey& rhs) {
|
||||||
|
return lhs == rhs;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DeferredEmissionContext {
|
||||||
|
DeferredEmissionContext(mlir::IRRewriter& rewriter,
|
||||||
|
ConstantPool& constants)
|
||||||
|
: rewriter(rewriter), constants(constants) {}
|
||||||
|
|
||||||
|
mlir::IRRewriter& rewriter;
|
||||||
|
ConstantPool& constants;
|
||||||
|
llvm::DenseMap<FragmentCollectionKey, mlir::Value,
|
||||||
|
FragmentCollectionKeyInfo> fragmentCollections;
|
||||||
|
};
|
||||||
|
|
||||||
|
using DeferredReplacementMap =
|
||||||
|
llvm::MapVector<mlir::Operation*, mlir::Value>;
|
||||||
|
|
||||||
|
mlir::LogicalResult realizeDeferredBoundaries(mlir::ArrayRef<BoundaryProgram> boundaries,
|
||||||
|
mlir::ArrayRef<DeferredResultPlan> results,
|
||||||
|
DeferredEmissionContext& context,
|
||||||
|
DeferredReplacementMap& replacements);
|
||||||
|
|
||||||
|
} // namespace onnx_mlir::spatial
|
||||||
+372
@@ -0,0 +1,372 @@
|
|||||||
|
#include "DeferredCommunicationDeadlock.hpp"
|
||||||
|
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/StaticIntSequence.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
|
|
||||||
|
#include "llvm/ADT/DenseMap.h"
|
||||||
|
|
||||||
|
namespace onnx_mlir::spatial {
|
||||||
|
using namespace mlir;
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
enum class EventKind { Compute, Send, Receive };
|
||||||
|
|
||||||
|
struct Event {
|
||||||
|
EventKind kind = EventKind::Compute;
|
||||||
|
uint64_t channel = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PlannedStreamCursor {
|
||||||
|
unsigned step = 0;
|
||||||
|
size_t slice = 0;
|
||||||
|
size_t offset = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::optional<Event> getPlannedHead(
|
||||||
|
unsigned stream, PlannedStreamCursor &cursor, unsigned stepCount,
|
||||||
|
const ScheduledCommunicationPlan &plan) {
|
||||||
|
while (cursor.slice < plan.slices.size()) {
|
||||||
|
const ScheduledTransferSlice &slice = plan.slices[cursor.slice];
|
||||||
|
ExternalTransferFamily &family = *slice.family;
|
||||||
|
size_t begin = slice.familyOffset + cursor.offset;
|
||||||
|
size_t length = slice.transferCount - cursor.offset;
|
||||||
|
auto source = family.sourceStreams.find(stream, begin, length);
|
||||||
|
auto target = family.targetStreams.find(stream, begin, length);
|
||||||
|
std::optional<size_t> index = source;
|
||||||
|
EventKind kind = EventKind::Send;
|
||||||
|
unsigned insertionStep = slice.sourceInsertionStep;
|
||||||
|
if (target && (!index || *target < *index)) {
|
||||||
|
index = *target;
|
||||||
|
kind = EventKind::Receive;
|
||||||
|
insertionStep = slice.targetInsertionStep;
|
||||||
|
}
|
||||||
|
if (!index) {
|
||||||
|
++cursor.slice;
|
||||||
|
cursor.offset = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cursor.offset = *index - slice.familyOffset;
|
||||||
|
if (cursor.step < insertionStep)
|
||||||
|
return Event {EventKind::Compute, 0};
|
||||||
|
return Event {kind, static_cast<uint64_t>(
|
||||||
|
family.channelIds.valueAt(*index))};
|
||||||
|
}
|
||||||
|
if (cursor.step < stepCount)
|
||||||
|
return Event {EventKind::Compute, 0};
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LogicalResult simulatePlanned(
|
||||||
|
Operation *anchor, ArrayRef<unsigned> stepCounts,
|
||||||
|
const ScheduledCommunicationPlan &plan) {
|
||||||
|
SmallVector<PlannedStreamCursor> cursors(stepCounts.size());
|
||||||
|
DenseMap<uint64_t, unsigned> sends, receives;
|
||||||
|
SmallVector<unsigned> readyComputes;
|
||||||
|
SmallVector<uint64_t> readyChannels;
|
||||||
|
size_t computeCursor = 0, channelCursor = 0;
|
||||||
|
unsigned finished = 0;
|
||||||
|
auto registerHead = [&](unsigned stream) {
|
||||||
|
auto head = getPlannedHead(
|
||||||
|
stream, cursors[stream], stepCounts[stream], plan);
|
||||||
|
if (!head) {
|
||||||
|
++finished;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (head->kind == EventKind::Compute) {
|
||||||
|
readyComputes.push_back(stream);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto &own = head->kind == EventKind::Send ? sends : receives;
|
||||||
|
auto &peer = head->kind == EventKind::Send ? receives : sends;
|
||||||
|
own[head->channel] = stream;
|
||||||
|
if (peer.contains(head->channel))
|
||||||
|
readyChannels.push_back(head->channel);
|
||||||
|
};
|
||||||
|
for (unsigned stream = 0; stream < stepCounts.size(); ++stream)
|
||||||
|
registerHead(stream);
|
||||||
|
while (computeCursor != readyComputes.size()
|
||||||
|
|| channelCursor != readyChannels.size()) {
|
||||||
|
if (computeCursor != readyComputes.size()) {
|
||||||
|
unsigned stream = readyComputes[computeCursor++];
|
||||||
|
++cursors[stream].step;
|
||||||
|
registerHead(stream);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
uint64_t channel = readyChannels[channelCursor++];
|
||||||
|
auto send = sends.find(channel);
|
||||||
|
auto receive = receives.find(channel);
|
||||||
|
if (send == sends.end() || receive == receives.end())
|
||||||
|
continue;
|
||||||
|
unsigned source = send->second;
|
||||||
|
unsigned target = receive->second;
|
||||||
|
sends.erase(send);
|
||||||
|
receives.erase(receive);
|
||||||
|
++cursors[source].offset;
|
||||||
|
++cursors[target].offset;
|
||||||
|
registerHead(source);
|
||||||
|
registerHead(target);
|
||||||
|
}
|
||||||
|
if (finished == stepCounts.size())
|
||||||
|
return success();
|
||||||
|
InFlightDiagnostic diagnostic = anchor->emitError(
|
||||||
|
"planned communication rendezvous simulation made no progress");
|
||||||
|
unsigned reported = 0;
|
||||||
|
for (unsigned stream = 0;
|
||||||
|
stream < stepCounts.size() && reported < 8; ++stream) {
|
||||||
|
auto head = getPlannedHead(
|
||||||
|
stream, cursors[stream], stepCounts[stream], plan);
|
||||||
|
if (!head)
|
||||||
|
continue;
|
||||||
|
diagnostic << (reported++ == 0 ? "; blocked " : ", ")
|
||||||
|
<< "stream " << stream << " at channel " << head->channel;
|
||||||
|
}
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RealizedOperation {
|
||||||
|
bool send = false;
|
||||||
|
StaticIntSequence channels;
|
||||||
|
StaticIntSequence parents;
|
||||||
|
StaticIntSequence counts;
|
||||||
|
StaticIntSequence sources;
|
||||||
|
StaticIntSequence targets;
|
||||||
|
};
|
||||||
|
|
||||||
|
static FailureOr<RealizedOperation> parseRealizedOperation(Operation *op) {
|
||||||
|
bool scalar = op->hasAttr("raptor.channel_id");
|
||||||
|
bool batch = op->hasAttr("raptor.batch_channel_ids");
|
||||||
|
if (scalar == batch) {
|
||||||
|
op->emitOpError(
|
||||||
|
"must have exactly one scalar or compact metadata form");
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
size_t size = 1;
|
||||||
|
if (batch) {
|
||||||
|
auto count = op->getAttrOfType<IntegerAttr>(
|
||||||
|
"raptor.batch_transfer_count");
|
||||||
|
if (!count || count.getInt() <= 0)
|
||||||
|
return op->emitOpError("has invalid compact transfer count"), failure();
|
||||||
|
size = count.getInt();
|
||||||
|
}
|
||||||
|
auto channels = getStaticIntSequenceAttr(
|
||||||
|
op, scalar ? "raptor.channel_id" : "raptor.batch_channel_ids", size);
|
||||||
|
auto parents = getStaticIntSequenceAttr(
|
||||||
|
op, scalar ? "raptor.parent_exchange_id"
|
||||||
|
: "raptor.batch_parent_exchange_ids", size);
|
||||||
|
auto counts = getStaticIntSequenceAttr(
|
||||||
|
op, scalar ? "raptor.parent_transfer_count"
|
||||||
|
: "raptor.batch_parent_transfer_counts", size);
|
||||||
|
auto sources = getStaticIntSequenceAttr(
|
||||||
|
op, scalar ? "raptor.source_core" : "raptor.batch_source_cores", size);
|
||||||
|
auto targets = getStaticIntSequenceAttr(
|
||||||
|
op, scalar ? "raptor.target_core" : "raptor.batch_target_cores", size);
|
||||||
|
if (failed(channels) || failed(parents) || failed(counts)
|
||||||
|
|| failed(sources) || failed(targets))
|
||||||
|
return failure();
|
||||||
|
if (scalar) {
|
||||||
|
auto exchange = op->getAttrOfType<IntegerAttr>("raptor.exchange_id");
|
||||||
|
if (!exchange || exchange.getInt() != channels->valueAt(0)) {
|
||||||
|
op->emitOpError("has inconsistent scalar exchange metadata");
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RealizedOperation {isa<SpatChannelSendOp>(op),
|
||||||
|
*channels, *parents, *counts, *sources, *targets};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void appendEventsByCore(
|
||||||
|
DenseMap<int64_t, StaticIntSequenceChain> &result,
|
||||||
|
const StaticIntSequence &channels, const StaticIntSequence &cores,
|
||||||
|
size_t begin, size_t count, bool send) {
|
||||||
|
size_t end = begin + count;
|
||||||
|
cores.forEachEqualRun(
|
||||||
|
[&](int64_t core, 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;
|
||||||
|
SmallVector<int64_t> events;
|
||||||
|
events.reserve(selectedEnd - selectedBegin);
|
||||||
|
for (size_t index = selectedBegin; index < selectedEnd; ++index)
|
||||||
|
events.push_back(2 * channels.valueAt(index) + (send ? 0 : 1));
|
||||||
|
result[core].append(StaticIntSequence::fromValues(events));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static LogicalResult compareEventSequences(
|
||||||
|
func::FuncOp funcOp,
|
||||||
|
const DenseMap<int64_t, StaticIntSequenceChain> &expected,
|
||||||
|
const DenseMap<int64_t, StaticIntSequenceChain> &actual) {
|
||||||
|
if (expected.size() != actual.size())
|
||||||
|
return funcOp.emitOpError(
|
||||||
|
"realized communication stream set differs from plan");
|
||||||
|
for (const auto &[core, sequence] : expected) {
|
||||||
|
auto found = actual.find(core);
|
||||||
|
if (found == actual.end())
|
||||||
|
return funcOp.emitOpError()
|
||||||
|
<< "realized communication stream is missing on core " << core;
|
||||||
|
StaticIntSequenceChainCursor expectedCursor(sequence);
|
||||||
|
StaticIntSequenceChainCursor actualCursor(found->second);
|
||||||
|
uint64_t ordinal = 0;
|
||||||
|
while (!expectedCursor.done() && !actualCursor.done()
|
||||||
|
&& expectedCursor.value() == actualCursor.value()) {
|
||||||
|
expectedCursor.advance();
|
||||||
|
actualCursor.advance();
|
||||||
|
++ordinal;
|
||||||
|
}
|
||||||
|
if (expectedCursor.done() && actualCursor.done())
|
||||||
|
continue;
|
||||||
|
auto describe = [](StaticIntSequenceChainCursor &cursor) {
|
||||||
|
if (cursor.done())
|
||||||
|
return std::pair<StringRef, int64_t>("none", -1);
|
||||||
|
int64_t event = cursor.value();
|
||||||
|
return std::pair<StringRef, int64_t>(
|
||||||
|
event % 2 == 0 ? "send" : "receive", event / 2);
|
||||||
|
};
|
||||||
|
auto [expectedKind, expectedChannel] = describe(expectedCursor);
|
||||||
|
auto [actualKind, actualChannel] = describe(actualCursor);
|
||||||
|
return funcOp.emitOpError()
|
||||||
|
<< "realized communication order differs on core " << core
|
||||||
|
<< " at ordinal " << ordinal << ": expected " << expectedKind
|
||||||
|
<< " channel " << expectedChannel << ", actual " << actualKind
|
||||||
|
<< " channel " << actualChannel;
|
||||||
|
}
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
LogicalResult verifyPlannedCommunicationDeadlockFree(
|
||||||
|
Operation *anchor, ArrayRef<unsigned> stepCounts,
|
||||||
|
const ScheduledCommunicationPlan &plan) {
|
||||||
|
SmallVector<std::pair<int64_t, int64_t>> familyChannels;
|
||||||
|
DenseMap<ExternalTransferFamily *, unsigned> familyIndex;
|
||||||
|
for (const ScheduledTransferSlice &slice : plan.slices) {
|
||||||
|
ExternalTransferFamily *family = slice.family;
|
||||||
|
if (!familyIndex.try_emplace(family, familyIndex.size()).second)
|
||||||
|
continue;
|
||||||
|
size_t count = family->channelIds.size();
|
||||||
|
if (count == 0)
|
||||||
|
return anchor->emitError(
|
||||||
|
"planned communication family has no channels");
|
||||||
|
int64_t first = family->channelIds.valueAt(0);
|
||||||
|
for (size_t index = 1; index < count; ++index)
|
||||||
|
if (family->channelIds.valueAt(index)
|
||||||
|
!= first + static_cast<int64_t>(index))
|
||||||
|
return anchor->emitError(
|
||||||
|
"planned communication family has non-consecutive channels");
|
||||||
|
familyChannels.emplace_back(
|
||||||
|
first, first + static_cast<int64_t>(count));
|
||||||
|
}
|
||||||
|
llvm::sort(familyChannels);
|
||||||
|
int64_t nextChannel = 0;
|
||||||
|
for (auto [firstChannel, endChannel] : familyChannels) {
|
||||||
|
if (firstChannel != nextChannel)
|
||||||
|
return anchor->emitError(
|
||||||
|
"planned communication channels are not exactly contiguous");
|
||||||
|
nextChannel = endChannel;
|
||||||
|
}
|
||||||
|
if (static_cast<uint64_t>(nextChannel) != plan.logicalTransferCount)
|
||||||
|
return anchor->emitError(
|
||||||
|
"planned communication channel count is inconsistent");
|
||||||
|
|
||||||
|
for (const ScheduledTransferSlice &slice : plan.slices) {
|
||||||
|
ExternalTransferFamily &family = *slice.family;
|
||||||
|
for (size_t offset = 0; offset < slice.transferCount; ++offset) {
|
||||||
|
size_t index = slice.familyOffset + offset;
|
||||||
|
unsigned source = family.sourceStreams.valueAt(index);
|
||||||
|
unsigned target = family.targetStreams.valueAt(index);
|
||||||
|
if (source >= stepCounts.size() || target >= stepCounts.size()
|
||||||
|
|| slice.sourceInsertionStep > stepCounts[source]
|
||||||
|
|| slice.targetInsertionStep > stepCounts[target]
|
||||||
|
|| slice.sourceInsertionStep <= family.requirement->producer->step
|
||||||
|
|| slice.targetInsertionStep
|
||||||
|
> family.requirement->exchange->consumerStep)
|
||||||
|
return anchor->emitError(
|
||||||
|
"communication plan references an invalid stream step");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return simulatePlanned(anchor, stepCounts, plan);
|
||||||
|
}
|
||||||
|
|
||||||
|
LogicalResult verifyRealizedCommunicationDeadlockFree(
|
||||||
|
func::FuncOp funcOp, const ScheduledCommunicationPlan &plan) {
|
||||||
|
SmallVector<ExternalTransferFamily *> familyByChannel(
|
||||||
|
plan.logicalTransferCount);
|
||||||
|
DenseMap<ExternalTransferFamily *, unsigned> familyIndex;
|
||||||
|
for (const ScheduledTransferSlice &slice : plan.slices) {
|
||||||
|
ExternalTransferFamily *family = slice.family;
|
||||||
|
if (!familyIndex.try_emplace(family, familyIndex.size()).second)
|
||||||
|
continue;
|
||||||
|
for (size_t index = 0; index < family->channelIds.size(); ++index)
|
||||||
|
familyByChannel[family->channelIds.valueAt(index)] = family;
|
||||||
|
}
|
||||||
|
|
||||||
|
DenseMap<int64_t, StaticIntSequenceChain> expected;
|
||||||
|
for (const ScheduledTransferSlice &slice : plan.slices) {
|
||||||
|
ExternalTransferFamily &family = *slice.family;
|
||||||
|
appendEventsByCore(expected, family.channelIds, family.sourceCores,
|
||||||
|
slice.familyOffset, slice.transferCount, true);
|
||||||
|
appendEventsByCore(expected, family.channelIds, family.targetCores,
|
||||||
|
slice.familyOffset, slice.transferCount, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
DenseMap<int64_t, StaticIntSequenceChain> actual;
|
||||||
|
SmallVector<std::unique_ptr<StaticIntSequence>> actualChannels;
|
||||||
|
bool invalid = false;
|
||||||
|
funcOp.walk([&](Operation *op) {
|
||||||
|
if (invalid || !isa<SpatChannelSendOp, SpatChannelReceiveOp>(op))
|
||||||
|
return;
|
||||||
|
auto realized = parseRealizedOperation(op);
|
||||||
|
if (failed(realized)) {
|
||||||
|
invalid = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Type payloadType = realized->send
|
||||||
|
? cast<SpatChannelSendOp>(op).getInput().getType()
|
||||||
|
: cast<SpatChannelReceiveOp>(op).getOutput().getType();
|
||||||
|
for (size_t index = 0; index < realized->channels.size(); ++index) {
|
||||||
|
int64_t channel = realized->channels.valueAt(index);
|
||||||
|
if (channel < 0
|
||||||
|
|| static_cast<uint64_t>(channel) >= familyByChannel.size()) {
|
||||||
|
op->emitOpError("references an unknown logical channel");
|
||||||
|
invalid = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ExternalTransferFamily *family = familyByChannel[channel];
|
||||||
|
if (!family) {
|
||||||
|
op->emitOpError("references an unknown logical channel");
|
||||||
|
invalid = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
size_t familyOffset = channel - family->channelIds.valueAt(0);
|
||||||
|
RequirementFamily &requirement = *family->requirement;
|
||||||
|
if (realized->parents.valueAt(index)
|
||||||
|
!= static_cast<int64_t>(requirement.exchange->exchangeId)
|
||||||
|
|| realized->counts.valueAt(index)
|
||||||
|
!= requirement.exchange->externalTransferCount
|
||||||
|
|| realized->sources.valueAt(index)
|
||||||
|
!= family->sourceCores.valueAt(familyOffset)
|
||||||
|
|| realized->targets.valueAt(index)
|
||||||
|
!= family->targetCores.valueAt(familyOffset)
|
||||||
|
|| payloadType != requirement.publicationFragmentType) {
|
||||||
|
op->emitOpError(
|
||||||
|
"logical transfer metadata differs from its symbolic family");
|
||||||
|
invalid = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (invalid)
|
||||||
|
return;
|
||||||
|
actualChannels.push_back(
|
||||||
|
std::make_unique<StaticIntSequence>(std::move(realized->channels)));
|
||||||
|
appendEventsByCore(actual, *actualChannels.back(),
|
||||||
|
realized->send ? realized->sources : realized->targets,
|
||||||
|
0, actualChannels.back()->size(), realized->send);
|
||||||
|
});
|
||||||
|
if (invalid)
|
||||||
|
return failure();
|
||||||
|
return compareEventSequences(funcOp, expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace onnx_mlir::spatial
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "DeferredCommunicationScheduling.hpp"
|
||||||
|
|
||||||
|
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
||||||
|
|
||||||
|
namespace onnx_mlir::spatial {
|
||||||
|
|
||||||
|
mlir::LogicalResult verifyPlannedCommunicationDeadlockFree(
|
||||||
|
mlir::Operation *anchor,
|
||||||
|
mlir::ArrayRef<unsigned> stepCounts,
|
||||||
|
const ScheduledCommunicationPlan &plan);
|
||||||
|
|
||||||
|
mlir::LogicalResult verifyRealizedCommunicationDeadlockFree(
|
||||||
|
mlir::func::FuncOp funcOp,
|
||||||
|
const ScheduledCommunicationPlan &plan);
|
||||||
|
|
||||||
|
} // namespace onnx_mlir::spatial
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
|
#include "mlir/IR/Operation.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/ArrayRef.h"
|
||||||
|
#include "llvm/ADT/DenseMap.h"
|
||||||
|
#include "llvm/ADT/SetVector.h"
|
||||||
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/StaticIntSequence.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
|
|
||||||
|
namespace onnx_mlir::spatial {
|
||||||
|
|
||||||
|
struct LaneInterval {
|
||||||
|
unsigned begin = 0;
|
||||||
|
unsigned end = 0;
|
||||||
|
|
||||||
|
bool operator==(const LaneInterval& other) const { return begin == other.begin && end == other.end; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class LaneSet {
|
||||||
|
public:
|
||||||
|
static LaneSet all(unsigned laneCount) { return range(0, laneCount); }
|
||||||
|
static LaneSet range(unsigned begin, unsigned end) {
|
||||||
|
LaneSet lanes;
|
||||||
|
if (begin < end)
|
||||||
|
lanes.ranges.push_back({begin, end});
|
||||||
|
return lanes;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty() const { return ranges.empty(); }
|
||||||
|
size_t size() const {
|
||||||
|
size_t result = 0;
|
||||||
|
for (LaneInterval range : ranges)
|
||||||
|
result += range.end - range.begin;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
bool contains(unsigned lane) const {
|
||||||
|
return llvm::any_of(ranges, [&](LaneInterval range) { return range.begin <= lane && lane < range.end; });
|
||||||
|
}
|
||||||
|
llvm::ArrayRef<LaneInterval> intervals() const { return ranges; }
|
||||||
|
|
||||||
|
LaneSet intersect(const LaneSet& other) const {
|
||||||
|
LaneSet result;
|
||||||
|
for (LaneInterval lhs : ranges)
|
||||||
|
for (LaneInterval rhs : other.ranges) {
|
||||||
|
unsigned begin = std::max(lhs.begin, rhs.begin);
|
||||||
|
unsigned end = std::min(lhs.end, rhs.end);
|
||||||
|
if (begin < end)
|
||||||
|
result.append(begin, end);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
LaneSet subtract(const LaneSet& other) const {
|
||||||
|
LaneSet result;
|
||||||
|
for (LaneInterval source : ranges) {
|
||||||
|
unsigned cursor = source.begin;
|
||||||
|
for (LaneInterval removed : other.ranges) {
|
||||||
|
if (removed.end <= cursor || removed.begin >= source.end)
|
||||||
|
continue;
|
||||||
|
if (cursor < removed.begin)
|
||||||
|
result.append(cursor, std::min(removed.begin, source.end));
|
||||||
|
cursor = std::max(cursor, removed.end);
|
||||||
|
if (cursor >= source.end)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (cursor < source.end)
|
||||||
|
result.append(cursor, source.end);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
LaneSet unite(const LaneSet& other) const {
|
||||||
|
llvm::SmallVector<LaneInterval, 4> combined(ranges.begin(), ranges.end());
|
||||||
|
llvm::append_range(combined, other.ranges);
|
||||||
|
llvm::sort(combined, [](LaneInterval lhs, LaneInterval rhs) { return lhs.begin < rhs.begin; });
|
||||||
|
LaneSet normalized;
|
||||||
|
for (LaneInterval range : combined)
|
||||||
|
normalized.append(range.begin, range.end);
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const LaneSet& other) const { return ranges == other.ranges; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void append(unsigned begin, unsigned end) {
|
||||||
|
if (begin >= end)
|
||||||
|
return;
|
||||||
|
if (!ranges.empty() && begin <= ranges.back().end) {
|
||||||
|
ranges.back().end = std::max(ranges.back().end, end);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ranges.push_back({begin, end});
|
||||||
|
}
|
||||||
|
|
||||||
|
llvm::SmallVector<LaneInterval, 2> ranges;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RequirementCoordinate {
|
||||||
|
unsigned specializationIndex = 0;
|
||||||
|
unsigned leafIndex = 0;
|
||||||
|
unsigned selectedPosition = 0;
|
||||||
|
|
||||||
|
bool operator==(const RequirementCoordinate& other) const {
|
||||||
|
return specializationIndex == other.specializationIndex
|
||||||
|
&& leafIndex == other.leafIndex
|
||||||
|
&& selectedPosition == other.selectedPosition;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class DeferredLeafForm {
|
||||||
|
DirectSource,
|
||||||
|
ScalarProjection,
|
||||||
|
GraphBatchProjection
|
||||||
|
};
|
||||||
|
enum class DeferredAssemblySourceTransform {
|
||||||
|
Identity,
|
||||||
|
AddLeadingUnitDimension,
|
||||||
|
RemoveLeadingUnitDimension
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DeferredSliceTemplate {
|
||||||
|
llvm::SmallVector<mlir::OpFoldResult> offsets;
|
||||||
|
llvm::SmallVector<mlir::OpFoldResult> sizes;
|
||||||
|
llvm::SmallVector<mlir::OpFoldResult> strides;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DeferredStaticSliceGeometry {
|
||||||
|
llvm::SmallVector<StaticIntSequence> offsets;
|
||||||
|
llvm::SmallVector<StaticIntSequence> sizes;
|
||||||
|
llvm::SmallVector<StaticIntSequence> strides;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DeferredProjectionLeafTemplate {
|
||||||
|
DeferredLeafForm form = DeferredLeafForm::DirectSource;
|
||||||
|
mlir::Value sourceRoot;
|
||||||
|
mlir::Value replacementRoot;
|
||||||
|
DeferredSliceTemplate leadingGeometry;
|
||||||
|
DeferredSliceTemplate innerGeometry;
|
||||||
|
mlir::RankedTensorType reconstructedType;
|
||||||
|
bool leadingRankReduced = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DeferredInsertAssemblyEntryTemplate {
|
||||||
|
RequirementCoordinate coordinate;
|
||||||
|
DeferredAssemblySourceTransform sourceTransform = DeferredAssemblySourceTransform::Identity;
|
||||||
|
mlir::RankedTensorType sourceType;
|
||||||
|
DeferredSliceTemplate targetGeometry;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DeferredInsertAssemblyTemplate {
|
||||||
|
mlir::tensor::EmptyOp initialValue;
|
||||||
|
mlir::RankedTensorType resultType;
|
||||||
|
llvm::SmallVector<DeferredInsertAssemblyEntryTemplate> entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DeferredProgramTemplate {
|
||||||
|
SpatDeferredCommunicationOp deferred;
|
||||||
|
unsigned specializationCount = 1;
|
||||||
|
mlir::Value specializationArgument;
|
||||||
|
mlir::RankedTensorType specializationFragmentType;
|
||||||
|
mlir::Value scheduledLane;
|
||||||
|
mlir::Value yieldedValue;
|
||||||
|
llvm::SmallVector<DeferredProjectionLeafTemplate, 0> leaves;
|
||||||
|
llvm::SmallVector<mlir::Operation*> residualOps;
|
||||||
|
std::optional<DeferredInsertAssemblyTemplate> insertAssembly;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ScheduledInfo;
|
||||||
|
|
||||||
|
struct ProducedValue {
|
||||||
|
ScheduledInfo* scheduled = nullptr;
|
||||||
|
unsigned step = 0;
|
||||||
|
unsigned resultIndex = 0;
|
||||||
|
int64_t graphId = -1;
|
||||||
|
int64_t core = -1;
|
||||||
|
int64_t laneStart = 0;
|
||||||
|
int64_t laneCount = 1;
|
||||||
|
unsigned scheduledLane = 0;
|
||||||
|
int64_t publishedSlotStart = 0;
|
||||||
|
int64_t publishedSlotCount = 1;
|
||||||
|
mlir::Value payload;
|
||||||
|
mlir::Value published;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ScheduledInfo {
|
||||||
|
mlir::Operation* op = nullptr;
|
||||||
|
llvm::SmallVector<mlir::Block*> blocks;
|
||||||
|
llvm::SmallVector<mlir::Operation*> stepAnchors;
|
||||||
|
llvm::SmallVector<int64_t> cores;
|
||||||
|
unsigned stepCount = 0;
|
||||||
|
llvm::SmallVector<ProducedValue*> produced;
|
||||||
|
llvm::SmallVector<unsigned> streamIds;
|
||||||
|
|
||||||
|
bool isBatch() const { return mlir::isa<SpatScheduledComputeBatch>(op); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DeferredExchangePlan;
|
||||||
|
|
||||||
|
struct RequirementFamily {
|
||||||
|
DeferredExchangePlan* exchange = nullptr;
|
||||||
|
RequirementCoordinate coordinate;
|
||||||
|
LaneSet targetLanes;
|
||||||
|
ProducedValue* producer = nullptr;
|
||||||
|
mlir::Type publicationFragmentType;
|
||||||
|
std::optional<StaticIntSequence> graphLanes;
|
||||||
|
std::optional<StaticIntSequence> producerLocalOffsets;
|
||||||
|
std::optional<DeferredStaticSliceGeometry> producerProjection;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LocalAvailabilityFamily {
|
||||||
|
RequirementFamily* requirement = nullptr;
|
||||||
|
LaneSet targetLanes;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ExternalTransferFamily {
|
||||||
|
RequirementFamily* requirement = nullptr;
|
||||||
|
LaneSet targetLanes;
|
||||||
|
ScheduledInfo* sourceScheduled = nullptr;
|
||||||
|
ScheduledInfo* targetScheduled = nullptr;
|
||||||
|
StaticIntSequence sourceStreams = StaticIntSequence::uniform(0, 1);
|
||||||
|
StaticIntSequence targetStreams = StaticIntSequence::uniform(0, 1);
|
||||||
|
StaticIntSequence sourceCores = StaticIntSequence::uniform(0, 1);
|
||||||
|
StaticIntSequence targetCores = StaticIntSequence::uniform(0, 1);
|
||||||
|
StaticIntSequence channelIds = StaticIntSequence::uniform(0, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DeferredExchangePlan {
|
||||||
|
uint64_t exchangeId = 0;
|
||||||
|
SpatDeferredCommunicationOp deferred;
|
||||||
|
ScheduledInfo* target = nullptr;
|
||||||
|
unsigned consumerStep = 0;
|
||||||
|
unsigned targetLaneCount = 1;
|
||||||
|
DeferredProgramTemplate program;
|
||||||
|
llvm::SmallVector<RequirementFamily, 0> requirements;
|
||||||
|
llvm::SmallVector<LocalAvailabilityFamily> local;
|
||||||
|
llvm::SmallVector<ExternalTransferFamily, 0> external;
|
||||||
|
unsigned externalTransferCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace onnx_mlir::spatial
|
||||||
+485
@@ -0,0 +1,485 @@
|
|||||||
|
#include "DeferredCommunicationPlanning.hpp"
|
||||||
|
|
||||||
|
#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/IRMapping.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/SmallPtrSet.h"
|
||||||
|
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/AffineUtils.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/ShapingUtils.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/StaticIntSequence.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/TensorSliceUtils.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
||||||
|
|
||||||
|
namespace onnx_mlir::spatial {
|
||||||
|
using namespace mlir;
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
static Value getBlockOperand(Block &block, ValueRange operands, Value value, unsigned firstArgument = 0) {
|
||||||
|
auto it = llvm::find(operands, value);
|
||||||
|
assert(it != operands.end() && "missing scheduled operand");
|
||||||
|
return block.getArgument(firstArgument + std::distance(operands.begin(), it));
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<Value> getOriginalProducerValue(const ProducerValueRef &producer) {
|
||||||
|
auto outputs = getComputeInstanceOutputValues(producer.instance);
|
||||||
|
if (producer.resultIndex >= outputs.size())
|
||||||
|
return failure();
|
||||||
|
return outputs[producer.resultIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
static SmallVector<Value> getBlueprintFragments(SpatBlueprintOp blueprint) {
|
||||||
|
SmallVector<Value> fragments {blueprint.getInput()};
|
||||||
|
llvm::append_range(fragments, blueprint.getFragments());
|
||||||
|
return fragments;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<Value> buildBlueprintReconstruction(
|
||||||
|
OpBuilder &builder, Location loc, SpatBlueprintOp blueprint,
|
||||||
|
ValueRange sourceBlockArgs) {
|
||||||
|
auto resultType = dyn_cast<RankedTensorType>(blueprint.getOutput().getType());
|
||||||
|
auto operandIndices = blueprint.getFragmentOperandIndices();
|
||||||
|
auto sourceSlots = blueprint.getFragmentSourceSlots();
|
||||||
|
auto sourceOffsets = blueprint.getFragmentSourceOffsets();
|
||||||
|
auto strides = blueprint.getFragmentStrides();
|
||||||
|
if (!resultType || !resultType.hasStaticShape() || !operandIndices ||
|
||||||
|
!sourceSlots || !sourceOffsets || !strides)
|
||||||
|
return blueprint.emitOpError("phase 1 requires complete static fragment assembly metadata"), failure();
|
||||||
|
int64_t rank = resultType.getRank();
|
||||||
|
ArrayRef<int64_t> offsets = blueprint.getFragmentOffsets();
|
||||||
|
ArrayRef<int64_t> sizes = blueprint.getFragmentSizes();
|
||||||
|
if (offsets.size() != sizes.size() || offsets.size() != strides->size() ||
|
||||||
|
offsets.size() != operandIndices->size() * rank ||
|
||||||
|
sourceSlots->size() != operandIndices->size() ||
|
||||||
|
sourceOffsets->size() != operandIndices->size())
|
||||||
|
return blueprint.emitOpError("phase 1 fragment assembly metadata has inconsistent sizes"), failure();
|
||||||
|
|
||||||
|
Value result = tensor::EmptyOp::create(builder, loc, resultType.getShape(),
|
||||||
|
resultType.getElementType());
|
||||||
|
for (auto [fragmentIndex, operandIndex] : llvm::enumerate(*operandIndices)) {
|
||||||
|
if (operandIndex < 0 || operandIndex >= static_cast<int64_t>(sourceBlockArgs.size()))
|
||||||
|
return blueprint.emitOpError("phase 1 fragment assembly operand index is out of range"), failure();
|
||||||
|
auto physicalType = dyn_cast<RankedTensorType>(sourceBlockArgs[operandIndex].getType());
|
||||||
|
if (!physicalType || !physicalType.hasStaticShape() || physicalType.getRank() != rank + 1)
|
||||||
|
return blueprint.emitOpError("phase 1 fragment assembly source is not a physical fragment batch"), failure();
|
||||||
|
SmallVector<int64_t> fragmentShape(physicalType.getShape().drop_front());
|
||||||
|
int64_t linearOffset = (*sourceOffsets)[fragmentIndex];
|
||||||
|
SmallVector<int64_t> sourceCoordinates(rank);
|
||||||
|
for (int64_t dim = rank - 1; dim >= 0; --dim) {
|
||||||
|
sourceCoordinates[dim] = linearOffset % fragmentShape[dim];
|
||||||
|
linearOffset /= fragmentShape[dim];
|
||||||
|
}
|
||||||
|
if (linearOffset != 0)
|
||||||
|
return blueprint.emitOpError("phase 1 fragment source offset is out of range"), failure();
|
||||||
|
|
||||||
|
SmallVector<OpFoldResult> sliceOffsets, sliceSizes, sliceStrides;
|
||||||
|
sliceOffsets.push_back(builder.getIndexAttr((*sourceSlots)[fragmentIndex]));
|
||||||
|
sliceSizes.push_back(builder.getIndexAttr(1));
|
||||||
|
sliceStrides.push_back(builder.getIndexAttr(1));
|
||||||
|
for (int64_t dim = 0; dim < rank; ++dim) {
|
||||||
|
int64_t index = fragmentIndex * rank + dim;
|
||||||
|
int64_t size = sizes[index];
|
||||||
|
if ((*strides)[index] != 1 || sourceCoordinates[dim] < 0 || size <= 0 ||
|
||||||
|
sourceCoordinates[dim] + size > fragmentShape[dim])
|
||||||
|
return blueprint.emitOpError("phase 1 fragment geometry is unsupported"), failure();
|
||||||
|
sliceOffsets.push_back(builder.getIndexAttr(sourceCoordinates[dim]));
|
||||||
|
sliceSizes.push_back(builder.getIndexAttr(size));
|
||||||
|
sliceStrides.push_back(builder.getIndexAttr(1));
|
||||||
|
}
|
||||||
|
SmallVector<int64_t> selectedFragmentShape;
|
||||||
|
selectedFragmentShape.reserve(rank);
|
||||||
|
for (int64_t dim = 0; dim < rank; ++dim)
|
||||||
|
selectedFragmentShape.push_back(sizes[fragmentIndex * rank + dim]);
|
||||||
|
auto fragmentType = RankedTensorType::get(
|
||||||
|
selectedFragmentShape, resultType.getElementType());
|
||||||
|
Value fragment = extractMixedSliceOrIdentity(
|
||||||
|
builder, loc, sourceBlockArgs[operandIndex], fragmentType,
|
||||||
|
{sliceOffsets, sliceSizes, sliceStrides});
|
||||||
|
SmallVector<OpFoldResult> targetOffsets, targetSizes, targetStrides;
|
||||||
|
for (int64_t dim = 0; dim < rank; ++dim) {
|
||||||
|
int64_t index = fragmentIndex * rank + dim;
|
||||||
|
targetOffsets.push_back(builder.getIndexAttr(offsets[index]));
|
||||||
|
targetSizes.push_back(builder.getIndexAttr(sizes[index]));
|
||||||
|
targetStrides.push_back(builder.getIndexAttr((*strides)[index]));
|
||||||
|
}
|
||||||
|
result = tensor::InsertSliceOp::create(builder, loc, fragment, result,
|
||||||
|
targetOffsets, targetSizes,
|
||||||
|
targetStrides);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<Value> buildSelectedDeferredSource(OpBuilder &builder, Location loc,
|
||||||
|
Operation *diagnosticOwner,
|
||||||
|
Value scheduledLane,
|
||||||
|
ValueRange sourceBlockArgs,
|
||||||
|
ArrayRef<int64_t> sourceOperandForScheduledLane) {
|
||||||
|
if (sourceBlockArgs.size() == 1)
|
||||||
|
return sourceBlockArgs.front();
|
||||||
|
if (!scheduledLane || sourceOperandForScheduledLane.empty())
|
||||||
|
return diagnosticOwner->emitOpError("multiple deferred sources require the enclosing scheduled lane"), failure();
|
||||||
|
for (int64_t sourceIndex : sourceOperandForScheduledLane) {
|
||||||
|
if (sourceIndex < 0 || sourceIndex >= static_cast<int64_t>(sourceBlockArgs.size()))
|
||||||
|
return diagnosticOwner->emitOpError("deferred source mapping operand is out of range"), failure();
|
||||||
|
}
|
||||||
|
StaticIntSequence sourceIndices = StaticIntSequence::fromValues(
|
||||||
|
sourceOperandForScheduledLane);
|
||||||
|
if (sourceIndices.getKind() == StaticIntSequenceKind::Uniform)
|
||||||
|
return sourceBlockArgs[sourceIndices.valueAt(0)];
|
||||||
|
Value selector;
|
||||||
|
if (sourceIndices.getKind() == StaticIntSequenceKind::Affine) {
|
||||||
|
int64_t step = sourceIndices.valueAt(1) - sourceIndices.valueAt(0);
|
||||||
|
selector = affineMulConst(
|
||||||
|
builder, loc, scheduledLane, step, diagnosticOwner);
|
||||||
|
selector = affineAddConst(
|
||||||
|
builder, loc, selector, sourceIndices.valueAt(0),
|
||||||
|
diagnosticOwner);
|
||||||
|
} else {
|
||||||
|
struct Run { int64_t value; size_t end; };
|
||||||
|
SmallVector<Run> runs;
|
||||||
|
sourceIndices.forEachEqualRun(
|
||||||
|
[&](int64_t value, size_t begin, size_t count) {
|
||||||
|
runs.push_back({value, begin + count});
|
||||||
|
});
|
||||||
|
selector = arith::ConstantIndexOp::create(
|
||||||
|
builder, loc, runs.back().value);
|
||||||
|
for (const Run &run : llvm::reverse(ArrayRef(runs).drop_back())) {
|
||||||
|
Value end = arith::ConstantIndexOp::create(builder, loc, run.end);
|
||||||
|
Value before = arith::CmpIOp::create(
|
||||||
|
builder, loc, arith::CmpIPredicate::ult, scheduledLane, end);
|
||||||
|
Value value = arith::ConstantIndexOp::create(builder, loc, run.value);
|
||||||
|
selector = arith::SelectOp::create(
|
||||||
|
builder, loc, before, value, selector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SpatDeferredSourceSelectOp::create(
|
||||||
|
builder, loc, sourceBlockArgs.front().getType(), selector,
|
||||||
|
sourceBlockArgs).getOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isDeferredPayloadCandidateOp(Operation *op) {
|
||||||
|
return isShapingOnlyOp(op) || isCompileTimeOp(op) || isPureIndexComputationOp(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isTopLevelDeferredOperation(Operation *op, Block &body,
|
||||||
|
const DeferredInputPlan &plan) {
|
||||||
|
(void)plan;
|
||||||
|
return op->getBlock() == &body
|
||||||
|
&& (isDeferredPayloadCandidateOp(op) || isa<scf::ForOp>(op));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isEligible(Value value, Block &body, const DeferredInputPlan &plan,
|
||||||
|
llvm::SmallPtrSetImpl<Operation *> &seen) {
|
||||||
|
if (value == plan.graphInput || value == plan.graphLane || value == plan.scheduledLane)
|
||||||
|
return true;
|
||||||
|
auto arg = dyn_cast<BlockArgument>(value);
|
||||||
|
if (arg)
|
||||||
|
return false;
|
||||||
|
Operation *op = value.getDefiningOp();
|
||||||
|
if (op && op->hasTrait<OpTrait::ConstantLike>())
|
||||||
|
return true;
|
||||||
|
if (!op || !isTopLevelDeferredOperation(op, body, plan) || !seen.insert(op).second)
|
||||||
|
return op && seen.contains(op);
|
||||||
|
return llvm::all_of(op->getOperands(), [&](Value operand) { return isEligible(operand, body, plan, seen); });
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<Value> clonePayloadRoot(Value root, Block &body, const DeferredInputPlan &plan,
|
||||||
|
OpBuilder &builder, SpatDeferredCommunicationOp transfer,
|
||||||
|
Value selectedSource, Value boundGraphLane) {
|
||||||
|
IRMapping mapping;
|
||||||
|
mapping.map(plan.graphInput, selectedSource);
|
||||||
|
std::function<FailureOr<Value>(Value)> cloneScheduledLane = [&](Value value) -> FailureOr<Value> {
|
||||||
|
if (mapping.contains(value)) return mapping.lookup(value);
|
||||||
|
if (value == plan.scheduledLane) return value;
|
||||||
|
if (auto argument = dyn_cast<BlockArgument>(value);
|
||||||
|
argument && argument.getOwner() == &transfer.getBody().front()
|
||||||
|
&& argument.getArgNumber() >= transfer.getSources().size())
|
||||||
|
return value;
|
||||||
|
if (isa<BlockArgument>(value))
|
||||||
|
return transfer.emitOpError("phase 1 payload shaping captures an unsupported block argument"), failure();
|
||||||
|
Operation *op = value.getDefiningOp();
|
||||||
|
if (!op || (!isDeferredPayloadCandidateOp(op) && !op->hasTrait<OpTrait::ConstantLike>()))
|
||||||
|
return transfer.emitOpError("phase 1 cannot clone the scheduled graph-lane expression"), failure();
|
||||||
|
for (Value operand : op->getOperands()) if (failed(cloneScheduledLane(operand))) return failure();
|
||||||
|
Operation *copy = builder.clone(*op, mapping);
|
||||||
|
for (auto pair : llvm::zip(op->getResults(), copy->getResults())) mapping.map(std::get<0>(pair), std::get<1>(pair));
|
||||||
|
return mapping.lookup(value);
|
||||||
|
};
|
||||||
|
std::function<FailureOr<Value>(Value)> clone = [&](Value value) -> FailureOr<Value> {
|
||||||
|
if (mapping.contains(value)) return mapping.lookup(value);
|
||||||
|
if (value == plan.graphLane) {
|
||||||
|
auto mappedLane = cloneScheduledLane(boundGraphLane ? boundGraphLane : plan.scheduledGraphLane);
|
||||||
|
if (failed(mappedLane)) return failure();
|
||||||
|
mapping.map(value, *mappedLane);
|
||||||
|
return *mappedLane;
|
||||||
|
}
|
||||||
|
if (isa<BlockArgument>(value))
|
||||||
|
return transfer.emitOpError("phase 1 payload shaping captures an unsupported block argument"), failure();
|
||||||
|
Operation *op = value.getDefiningOp();
|
||||||
|
if (!op || (!isTopLevelDeferredOperation(op, body, plan) && !op->hasTrait<OpTrait::ConstantLike>()))
|
||||||
|
return transfer.emitOpError("phase 1 payload shaping contains an unsupported operation"), failure();
|
||||||
|
for (Value operand : op->getOperands()) if (failed(clone(operand))) return failure();
|
||||||
|
Operation *copy = builder.clone(*op, mapping);
|
||||||
|
for (auto pair : llvm::zip(op->getResults(), copy->getResults())) mapping.map(std::get<0>(pair), std::get<1>(pair));
|
||||||
|
return mapping.lookup(value);
|
||||||
|
};
|
||||||
|
return clone(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool dependsOnGraphLane(Value value, Value graphLane, Block &body,
|
||||||
|
const DeferredInputPlan &plan,
|
||||||
|
llvm::SmallPtrSetImpl<Operation *> &seen) {
|
||||||
|
if (value == graphLane)
|
||||||
|
return true;
|
||||||
|
Operation *op = value.getDefiningOp();
|
||||||
|
if (!op || !isTopLevelDeferredOperation(op, body, plan) || !seen.insert(op).second)
|
||||||
|
return false;
|
||||||
|
if (auto loop = dyn_cast<scf::ForOp>(op)) {
|
||||||
|
bool depends = false;
|
||||||
|
loop.getRegion().walk([&](Operation *nested) {
|
||||||
|
depends |= llvm::is_contained(nested->getOperands(), graphLane);
|
||||||
|
});
|
||||||
|
if (depends)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return llvm::any_of(op->getOperands(), [&](Value operand) {
|
||||||
|
return dependsOnGraphLane(operand, graphLane, body, plan, seen);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void collectClosure(Value value, Block &body, const DeferredInputPlan &plan,
|
||||||
|
llvm::SmallPtrSetImpl<Operation *> &ops) {
|
||||||
|
Operation *op = value.getDefiningOp();
|
||||||
|
if (!op || !isTopLevelDeferredOperation(op, body, plan) || !ops.insert(op).second) return;
|
||||||
|
if (auto loop = dyn_cast<scf::ForOp>(op))
|
||||||
|
loop.getRegion().walk([&](Operation *nested) { ops.insert(nested); });
|
||||||
|
for (Value operand : op->getOperands())
|
||||||
|
if (operand != plan.graphInput && operand != plan.graphLane) collectClosure(operand, body, plan, ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool isDeferredFragmentAssemblyInput(
|
||||||
|
Value input, const ComputeInstance &consumerInstance) {
|
||||||
|
auto blueprint = input.getDefiningOp<SpatBlueprintOp>();
|
||||||
|
if (!blueprint || blueprint.getMode() != "fragment_assembly")
|
||||||
|
return false;
|
||||||
|
return llvm::all_of(getBlueprintFragments(blueprint), [&](Value fragment) {
|
||||||
|
return getProducerValueRef(fragment, &consumerInstance).has_value();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
LogicalResult prepareSingleCpuInput(OpBuilder &, Location loc, Value input, BlockArgument graphInput,
|
||||||
|
const ComputeInstance &consumerInstance, const MergeScheduleResult &,
|
||||||
|
ValueRange scheduledInputs, Block &block, unsigned firstInputArgument,
|
||||||
|
const DenseMap<ProducerValueKey, MaterializedProducerRef> &availableValues,
|
||||||
|
Value graphLane, Value scheduledGraphLane,
|
||||||
|
DeferredInputPlan &plan) {
|
||||||
|
plan = {graphInput, {}, {}, {}, graphLane, scheduledGraphLane, {}, {}, {}, {}, 1, nullptr};
|
||||||
|
if (isDeferredFragmentAssemblyInput(input, consumerInstance)) {
|
||||||
|
plan.blueprint = input.getDefiningOp<SpatBlueprintOp>();
|
||||||
|
plan.originalSources = getBlueprintFragments(plan.blueprint);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
auto producer = getProducerValueRef(input, &consumerInstance);
|
||||||
|
if (!producer) { plan.availableValue = getBlockOperand(block, scheduledInputs, input, firstInputArgument); return success(); }
|
||||||
|
ProducerValueKey key {producer->instance, producer->resultIndex};
|
||||||
|
auto batch = dyn_cast<SpatComputeBatch>(producer->instance.op);
|
||||||
|
bool hasCompleteValue = !batch
|
||||||
|
|| (producer->instance.laneStart == 0
|
||||||
|
&& producer->instance.laneCount
|
||||||
|
== static_cast<uint32_t>(batch.getLaneCount()));
|
||||||
|
if (auto available = availableValues.find(key);
|
||||||
|
hasCompleteValue && available != availableValues.end()) {
|
||||||
|
plan.availableValue = available->second.payload;
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
auto source = getOriginalProducerValue(*producer);
|
||||||
|
if (failed(source)) return emitError(loc) << "cannot resolve original graph producer value";
|
||||||
|
plan.originalSources.push_back(*source);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
LogicalResult prepareMultiCpuTupleInput(OpBuilder &, Location loc, Value input, BlockArgument graphInput,
|
||||||
|
const ComputeStepTuple &tuple, const PeftClassPlan &,
|
||||||
|
const MergeScheduleResult &, ValueRange scheduledInputs, Block &block,
|
||||||
|
unsigned firstInputArgument, Value graphLane, Value scheduledGraphLane, Value scheduledLane,
|
||||||
|
DeferredInputPlan &plan) {
|
||||||
|
const ComputeInstance &representative = tuple.instances.front();
|
||||||
|
plan = {graphInput, {}, {}, {}, graphLane, scheduledGraphLane, scheduledLane, {}, {}, {}, 1, nullptr};
|
||||||
|
if (isDeferredFragmentAssemblyInput(input, representative)) {
|
||||||
|
plan.blueprint = input.getDefiningOp<SpatBlueprintOp>();
|
||||||
|
plan.originalSources = getBlueprintFragments(plan.blueprint);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
auto producer = getProducerValueRef(input, &representative);
|
||||||
|
if (!producer) { plan.availableValue = getBlockOperand(block, scheduledInputs, input, firstInputArgument); return success(); }
|
||||||
|
auto inputs = getComputeInstanceInputs(representative);
|
||||||
|
auto it = llvm::find(inputs, input);
|
||||||
|
if (it == inputs.end()) return emitError(loc) << "cannot resolve scheduled batch step input";
|
||||||
|
unsigned inputIndex = std::distance(inputs.begin(), it);
|
||||||
|
for (const ComputeInstance &instance : tuple.instances) {
|
||||||
|
auto laneInputs = getComputeInstanceInputs(instance);
|
||||||
|
if (inputIndex >= laneInputs.size()) return emitError(loc) << "scheduled batch step input out of range";
|
||||||
|
auto laneProducer = getProducerValueRef(laneInputs[inputIndex], &instance);
|
||||||
|
if (!laneProducer) return emitError(loc) << "scheduled batch step mixes host and producer inputs";
|
||||||
|
auto source = getOriginalProducerValue(*laneProducer);
|
||||||
|
if (failed(source)) return emitError(loc) << "cannot resolve original graph producer value";
|
||||||
|
auto sourceIt = llvm::find(plan.originalSources, *source);
|
||||||
|
if (sourceIt == plan.originalSources.end()) { plan.sourceOperandForScheduledLane.push_back(plan.originalSources.size()); plan.originalSources.push_back(*source); }
|
||||||
|
else plan.sourceOperandForScheduledLane.push_back(std::distance(plan.originalSources.begin(), sourceIt));
|
||||||
|
}
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
LogicalResult materializeDeferredPayloadDemands(OpBuilder &builder, Location loc, Block &body,
|
||||||
|
ArrayRef<DeferredInputPlan> plans, IRMapping &mapper,
|
||||||
|
llvm::SmallPtrSetImpl<Operation *> &absorbed) {
|
||||||
|
for (const DeferredInputPlan &plan : plans) {
|
||||||
|
if (plan.availableValue) { mapper.map(plan.graphInput, plan.availableValue); continue; }
|
||||||
|
SmallVector<Value> roots;
|
||||||
|
bool needsIdentity = false;
|
||||||
|
SmallVector<Value> worklist {plan.graphInput};
|
||||||
|
llvm::SmallDenseSet<Value, 32> seen;
|
||||||
|
while (!worklist.empty()) {
|
||||||
|
Value value = worklist.pop_back_val();
|
||||||
|
if (!seen.insert(value).second) continue;
|
||||||
|
for (OpOperand &use : value.getUses()) {
|
||||||
|
Operation *user = use.getOwner();
|
||||||
|
if (!isTopLevelDeferredOperation(user, body, plan)) { needsIdentity = true; continue; }
|
||||||
|
llvm::SmallPtrSet<Operation *, 16> eligibility;
|
||||||
|
if (!isEligible(user->getResult(0), body, plan, eligibility)) { needsIdentity = true; continue; }
|
||||||
|
for (Value result : user->getResults()) {
|
||||||
|
bool hasShapingUse = llvm::any_of(result.getUses(), [&](OpOperand &next) { return isTopLevelDeferredOperation(next.getOwner(), body, plan); });
|
||||||
|
bool hasOtherUse = llvm::any_of(result.getUses(), [&](OpOperand &next) { return !isTopLevelDeferredOperation(next.getOwner(), body, plan); });
|
||||||
|
if (hasOtherUse) roots.push_back(result);
|
||||||
|
if (hasShapingUse) worklist.push_back(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (needsIdentity) roots.push_back(plan.graphInput);
|
||||||
|
llvm::sort(roots, [](Value a, Value b) { return a.getAsOpaquePointer() < b.getAsOpaquePointer(); });
|
||||||
|
roots.erase(std::unique(roots.begin(), roots.end()), roots.end());
|
||||||
|
Value sharedSource;
|
||||||
|
if (!plan.blueprint) {
|
||||||
|
OpBuilder::InsertPoint restore = builder.saveInsertionPoint();
|
||||||
|
if (plan.scalarizedGraphLaneBase && plan.originalSources.size() > 1) {
|
||||||
|
Operation *loop = builder.getInsertionBlock()->getParentOp();
|
||||||
|
if (loop && !isa<scf::ForOp>(loop))
|
||||||
|
loop = loop->getParentOfType<scf::ForOp>();
|
||||||
|
if (loop)
|
||||||
|
builder.setInsertionPoint(loop);
|
||||||
|
else if (plan.scalarizedHoistBlock)
|
||||||
|
builder.setInsertionPointToEnd(plan.scalarizedHoistBlock);
|
||||||
|
else
|
||||||
|
return emitError(loc) << "phase 1 scalarized deferred source is missing a hoist point";
|
||||||
|
}
|
||||||
|
auto selected = buildSelectedDeferredSource(
|
||||||
|
builder, loc, plan.graphInput.getOwner()->getParentOp(),
|
||||||
|
plan.scheduledLane, plan.originalSources,
|
||||||
|
plan.sourceOperandForScheduledLane);
|
||||||
|
if (failed(selected))
|
||||||
|
return failure();
|
||||||
|
sharedSource = *selected;
|
||||||
|
builder.restoreInsertionPoint(restore);
|
||||||
|
}
|
||||||
|
for (Value root : roots) {
|
||||||
|
llvm::SmallPtrSet<Operation *, 16> laneDependencies;
|
||||||
|
bool scalarize = plan.scalarizedGraphLaneBase
|
||||||
|
&& dependsOnGraphLane(root, plan.graphLane, body, plan, laneDependencies);
|
||||||
|
OpBuilder::InsertPoint restore = builder.saveInsertionPoint();
|
||||||
|
Operation *loop = nullptr;
|
||||||
|
if (scalarize) {
|
||||||
|
loop = builder.getInsertionBlock()->getParentOp();
|
||||||
|
if (loop && !isa<scf::ForOp>(loop))
|
||||||
|
loop = loop->getParentOfType<scf::ForOp>();
|
||||||
|
if (loop)
|
||||||
|
builder.setInsertionPoint(loop);
|
||||||
|
else if (plan.scalarizedHoistBlock)
|
||||||
|
builder.setInsertionPointToEnd(plan.scalarizedHoistBlock);
|
||||||
|
else
|
||||||
|
return emitError(loc) << "phase 1 scalarized deferred payload is missing a hoist point";
|
||||||
|
}
|
||||||
|
unsigned count = scalarize ? plan.scalarizedLaneCount : 1;
|
||||||
|
bool grouped = count > 1;
|
||||||
|
auto fragmentType = dyn_cast<RankedTensorType>(root.getType());
|
||||||
|
if (!fragmentType || !fragmentType.hasStaticShape())
|
||||||
|
return emitError(loc) << "phase 1 deferred payload requires a static ranked tensor";
|
||||||
|
Type outputType = fragmentType;
|
||||||
|
if (grouped) {
|
||||||
|
SmallVector<int64_t> groupedShape {static_cast<int64_t>(count)};
|
||||||
|
llvm::append_range(groupedShape, fragmentType.getShape());
|
||||||
|
outputType = RankedTensorType::get(groupedShape,
|
||||||
|
fragmentType.getElementType());
|
||||||
|
}
|
||||||
|
auto specialization = scalarize
|
||||||
|
? builder.getI64IntegerAttr(count)
|
||||||
|
: IntegerAttr();
|
||||||
|
SmallVector<Value> transferSources;
|
||||||
|
if (plan.blueprint)
|
||||||
|
llvm::append_range(transferSources, plan.originalSources);
|
||||||
|
else
|
||||||
|
transferSources.push_back(sharedSource);
|
||||||
|
auto transfer = SpatDeferredCommunicationOp::create(
|
||||||
|
builder, loc, outputType, transferSources, specialization);
|
||||||
|
SmallVector<Type> bodyTypes(transfer.getSources().getTypes());
|
||||||
|
if (grouped)
|
||||||
|
bodyTypes.push_back(builder.getIndexType());
|
||||||
|
Block *deferred = builder.createBlock(&transfer.getBody(), transfer.getBody().end(),
|
||||||
|
bodyTypes, SmallVector<Location>(bodyTypes.size(), loc));
|
||||||
|
builder.setInsertionPointToStart(deferred);
|
||||||
|
ValueRange sourceArguments = deferred->getArguments().take_front(
|
||||||
|
transfer.getSources().size());
|
||||||
|
auto selected = plan.blueprint
|
||||||
|
? buildBlueprintReconstruction(builder, loc, plan.blueprint,
|
||||||
|
sourceArguments)
|
||||||
|
: FailureOr<Value>(sourceArguments.front());
|
||||||
|
if (failed(selected)) return failure();
|
||||||
|
Value boundGraphLane;
|
||||||
|
if (grouped)
|
||||||
|
boundGraphLane = arith::AddIOp::create(
|
||||||
|
builder, loc, plan.scalarizedGraphLaneBase,
|
||||||
|
deferred->getArguments().back());
|
||||||
|
else if (scalarize)
|
||||||
|
boundGraphLane = plan.scalarizedGraphLaneBase;
|
||||||
|
auto payload = clonePayloadRoot(root, body, plan, builder, transfer, *selected, boundGraphLane);
|
||||||
|
if (failed(payload)) return failure();
|
||||||
|
SpatYieldOp::create(builder, loc, *payload);
|
||||||
|
builder.setInsertionPointAfter(transfer);
|
||||||
|
if (grouped) {
|
||||||
|
builder.restoreInsertionPoint(restore);
|
||||||
|
SmallVector<OpFoldResult> offsets {
|
||||||
|
plan.scalarizedLocalLane};
|
||||||
|
SmallVector<OpFoldResult> sizes {builder.getIndexAttr(1)};
|
||||||
|
SmallVector<OpFoldResult> strides {builder.getIndexAttr(1)};
|
||||||
|
for (int64_t dimension : fragmentType.getShape()) {
|
||||||
|
offsets.push_back(builder.getIndexAttr(0));
|
||||||
|
sizes.push_back(builder.getIndexAttr(dimension));
|
||||||
|
strides.push_back(builder.getIndexAttr(1));
|
||||||
|
}
|
||||||
|
mapper.map(root, extractMixedSliceOrIdentity(
|
||||||
|
builder, loc, transfer.getOutput(), fragmentType,
|
||||||
|
{offsets, sizes, strides}));
|
||||||
|
} else {
|
||||||
|
mapper.map(root, transfer.getOutput());
|
||||||
|
}
|
||||||
|
collectClosure(root, body, plan, absorbed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SmallVector<Operation *> notFullyAbsorbed;
|
||||||
|
for (Operation *op : absorbed) {
|
||||||
|
bool allResultsMapped = llvm::all_of(op->getResults(), [&](Value result) {
|
||||||
|
return mapper.contains(result) || llvm::all_of(result.getUses(), [&](OpOperand &use) { return absorbed.contains(use.getOwner()); });
|
||||||
|
});
|
||||||
|
if (!allResultsMapped)
|
||||||
|
notFullyAbsorbed.push_back(op);
|
||||||
|
}
|
||||||
|
for (Operation *op : notFullyAbsorbed)
|
||||||
|
absorbed.erase(op);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace onnx_mlir::spatial
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ScheduledComputePlan.hpp"
|
||||||
|
|
||||||
|
namespace onnx_mlir {
|
||||||
|
namespace spatial {
|
||||||
|
|
||||||
|
// A graph input is either already available in the scheduled block, or is a
|
||||||
|
// graph result whose individual deterministic payloads are materialized later.
|
||||||
|
struct DeferredInputPlan {
|
||||||
|
BlockArgument graphInput;
|
||||||
|
Value availableValue;
|
||||||
|
SmallVector<Value> originalSources;
|
||||||
|
SmallVector<int64_t> sourceOperandForScheduledLane;
|
||||||
|
Value graphLane;
|
||||||
|
Value scheduledGraphLane;
|
||||||
|
Value scheduledLane;
|
||||||
|
SpatBlueprintOp blueprint;
|
||||||
|
Value scalarizedLocalLane;
|
||||||
|
Value scalarizedGraphLaneBase;
|
||||||
|
int64_t scalarizedLaneCount = 1;
|
||||||
|
Block *scalarizedHoistBlock = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool isDeferredFragmentAssemblyInput(Value input,
|
||||||
|
const ComputeInstance &consumerInstance);
|
||||||
|
|
||||||
|
LogicalResult prepareSingleCpuInput(OpBuilder &builder, Location loc, Value input,
|
||||||
|
BlockArgument graphInput,
|
||||||
|
const ComputeInstance &consumerInstance,
|
||||||
|
const MergeScheduleResult &schedule,
|
||||||
|
ValueRange scheduledInputs, Block &block,
|
||||||
|
unsigned firstInputArgument,
|
||||||
|
const DenseMap<ProducerValueKey, MaterializedProducerRef> &availableValues,
|
||||||
|
Value graphLane, Value scheduledGraphLane,
|
||||||
|
DeferredInputPlan &plan);
|
||||||
|
|
||||||
|
LogicalResult prepareMultiCpuTupleInput(OpBuilder &builder, Location loc, Value input,
|
||||||
|
BlockArgument graphInput,
|
||||||
|
const ComputeStepTuple &stepTuple,
|
||||||
|
const PeftClassPlan &peftClassPlan,
|
||||||
|
const MergeScheduleResult &schedule,
|
||||||
|
ValueRange scheduledInputs, Block &block,
|
||||||
|
unsigned firstInputArgument, Value graphLane, Value scheduledGraphLane,
|
||||||
|
Value scheduledLane, DeferredInputPlan &plan);
|
||||||
|
|
||||||
|
LogicalResult materializeDeferredPayloadDemands(OpBuilder &builder, Location loc,
|
||||||
|
Block &graphBody,
|
||||||
|
ArrayRef<DeferredInputPlan> plans,
|
||||||
|
IRMapping &mapper,
|
||||||
|
llvm::SmallPtrSetImpl<Operation *> &absorbed);
|
||||||
|
|
||||||
|
} // namespace spatial
|
||||||
|
} // namespace onnx_mlir
|
||||||
+171
@@ -0,0 +1,171 @@
|
|||||||
|
#include "DeferredCommunicationRealization.hpp"
|
||||||
|
|
||||||
|
#include "DeferredBoundaryPlanning.hpp"
|
||||||
|
#include "DeferredBoundaryRealization.hpp"
|
||||||
|
#include "DeferredCommunicationDeadlock.hpp"
|
||||||
|
#include "DeferredCommunicationScheduling.hpp"
|
||||||
|
#include "DeferredTransferPlanning.hpp"
|
||||||
|
|
||||||
|
#include "mlir/IR/Dominance.h"
|
||||||
|
|
||||||
|
namespace onnx_mlir::spatial {
|
||||||
|
using namespace mlir;
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
static LogicalResult replaceFinalGraphPublications(
|
||||||
|
func::FuncOp funcOp, DeferredTransferPlan &plan) {
|
||||||
|
for (Operation &op : funcOp.getOps()) {
|
||||||
|
if (!isa<SpatGraphCompute, SpatGraphComputeBatch>(op))
|
||||||
|
continue;
|
||||||
|
auto graphId = op.getAttrOfType<IntegerAttr>("scheduled.graph_id");
|
||||||
|
if (!graphId)
|
||||||
|
continue;
|
||||||
|
for (auto [resultIndex, result] : llvm::enumerate(op.getResults())) {
|
||||||
|
SmallVector<OpOperand *> externalUses;
|
||||||
|
for (OpOperand &use : result.getUses()) {
|
||||||
|
Operation *user = use.getOwner();
|
||||||
|
if (isa<SpatGraphCompute, SpatGraphComputeBatch,
|
||||||
|
SpatDeferredCommunicationOp>(user))
|
||||||
|
continue;
|
||||||
|
if (auto blueprint = dyn_cast<SpatBlueprintOp>(user)) {
|
||||||
|
bool blueprintEscapes = llvm::any_of(
|
||||||
|
blueprint.getOutput().getUses(), [](OpOperand &blueprintUse) {
|
||||||
|
return !isa<SpatGraphCompute, SpatGraphComputeBatch,
|
||||||
|
SpatDeferredCommunicationOp>(
|
||||||
|
blueprintUse.getOwner());
|
||||||
|
});
|
||||||
|
if (!blueprintEscapes)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
externalUses.push_back(&use);
|
||||||
|
}
|
||||||
|
if (externalUses.empty())
|
||||||
|
continue;
|
||||||
|
SmallVector<Value> exact;
|
||||||
|
for (ProducedValue *produced :
|
||||||
|
plan.producedByGraph.lookup(graphId.getInt()))
|
||||||
|
if (produced->resultIndex == resultIndex
|
||||||
|
&& produced->published
|
||||||
|
&& produced->published.getType() == result.getType()
|
||||||
|
&& !llvm::is_contained(exact, produced->published))
|
||||||
|
exact.push_back(produced->published);
|
||||||
|
if (exact.size() != 1)
|
||||||
|
return op.emitOpError(
|
||||||
|
"phase 2 final publication ownership changed after planning");
|
||||||
|
for (OpOperand *use : externalUses) {
|
||||||
|
Operation *consumer = use->getOwner();
|
||||||
|
Operation *producer = exact.front().getDefiningOp();
|
||||||
|
if (consumer->getBlock() == producer->getBlock()
|
||||||
|
&& consumer->isBeforeInBlock(producer))
|
||||||
|
consumer->moveAfter(producer);
|
||||||
|
use->set(exact.front());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
static LogicalResult eraseOldGraph(func::FuncOp funcOp,
|
||||||
|
IRRewriter &rewriter) {
|
||||||
|
SmallVector<Operation *> oldGraph;
|
||||||
|
for (Operation &op : funcOp.getOps())
|
||||||
|
if (isa<SpatGraphCompute, SpatGraphComputeBatch, SpatBlueprintOp>(op))
|
||||||
|
oldGraph.push_back(&op);
|
||||||
|
for (Operation *op : llvm::reverse(oldGraph)) {
|
||||||
|
if (auto blueprint = dyn_cast<SpatBlueprintOp>(op)) {
|
||||||
|
if (blueprint.getOutput().use_empty())
|
||||||
|
rewriter.eraseOp(blueprint);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!op->use_empty())
|
||||||
|
return op->emitOpError(
|
||||||
|
"phase 2 cannot erase an old graph compute with live results");
|
||||||
|
rewriter.eraseOp(op);
|
||||||
|
}
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
static LogicalResult eraseDeferredSourceSelectors(
|
||||||
|
func::FuncOp funcOp, IRRewriter &rewriter) {
|
||||||
|
SmallVector<SpatDeferredSourceSelectOp> selectors;
|
||||||
|
funcOp.walk([&](SpatDeferredSourceSelectOp selector) {
|
||||||
|
selectors.push_back(selector);
|
||||||
|
});
|
||||||
|
for (SpatDeferredSourceSelectOp selector : llvm::reverse(selectors)) {
|
||||||
|
if (!selector.getOutput().use_empty())
|
||||||
|
return selector.emitOpError(
|
||||||
|
"phase 2 left a live deferred source selection");
|
||||||
|
rewriter.eraseOp(selector);
|
||||||
|
}
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
static LogicalResult verifyDominance(func::FuncOp funcOp) {
|
||||||
|
DominanceInfo dominance(funcOp);
|
||||||
|
WalkResult result = funcOp.walk([&](Operation *op) {
|
||||||
|
for (auto [index, operand] : llvm::enumerate(op->getOperands()))
|
||||||
|
if (!dominance.dominates(operand, op)) {
|
||||||
|
op->emitOpError() << "phase 2 produced non-dominating operand "
|
||||||
|
<< index << ": " << operand;
|
||||||
|
return WalkResult::interrupt();
|
||||||
|
}
|
||||||
|
return WalkResult::advance();
|
||||||
|
});
|
||||||
|
return success(!result.wasInterrupted());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
LogicalResult realizeDeferredCommunication(
|
||||||
|
func::FuncOp funcOp,
|
||||||
|
const ScheduledComputeMaterializationResult &materialization) {
|
||||||
|
auto transfers = buildDeferredTransferPlan(funcOp, materialization);
|
||||||
|
if (failed(transfers))
|
||||||
|
return funcOp.emitOpError(
|
||||||
|
"phase 2 failed to build symbolic transfer families");
|
||||||
|
auto schedule = scheduleDeferredCommunication(funcOp, *transfers);
|
||||||
|
if (failed(schedule)
|
||||||
|
|| failed(verifyPlannedCommunicationDeadlockFree(
|
||||||
|
funcOp, transfers->stepCounts, *schedule)))
|
||||||
|
return funcOp.emitOpError(
|
||||||
|
"phase 2 failed to schedule symbolic communication");
|
||||||
|
auto boundaries = buildDeferredBoundaryPlan(*transfers, *schedule);
|
||||||
|
if (failed(boundaries))
|
||||||
|
return funcOp.emitOpError(
|
||||||
|
"phase 2 failed to build sparse boundary programs");
|
||||||
|
|
||||||
|
IRRewriter rewriter(funcOp.getContext());
|
||||||
|
if (failed(retargetDeferredPublications(funcOp, *transfers))
|
||||||
|
|| failed(replaceFinalGraphPublications(funcOp, *transfers)))
|
||||||
|
return failure();
|
||||||
|
ConstantPool constants(funcOp, rewriter);
|
||||||
|
DeferredEmissionContext context(rewriter, constants);
|
||||||
|
DeferredReplacementMap replacements;
|
||||||
|
if (failed(realizeDeferredBoundaries(
|
||||||
|
boundaries->boundaries, boundaries->results, context, replacements)))
|
||||||
|
return failure();
|
||||||
|
for (auto [op, replacement] : replacements) {
|
||||||
|
if (op->getResult(0) == replacement)
|
||||||
|
return op->emitOpError(
|
||||||
|
"phase 2 cannot replace deferred communication with itself");
|
||||||
|
op->getResult(0).replaceAllUsesWith(replacement);
|
||||||
|
if (!op->use_empty())
|
||||||
|
return op->emitOpError(
|
||||||
|
"phase 2 cannot erase deferred communication with live uses");
|
||||||
|
rewriter.eraseOp(op);
|
||||||
|
}
|
||||||
|
if (failed(eraseDeferredSourceSelectors(funcOp, rewriter))
|
||||||
|
|| failed(eraseOldGraph(funcOp, rewriter))
|
||||||
|
|| failed(verifyDominance(funcOp))
|
||||||
|
|| failed(verifyRealizedCommunicationDeadlockFree(funcOp, *schedule)))
|
||||||
|
return failure();
|
||||||
|
bool deferredRemains = false;
|
||||||
|
funcOp.walk([&](SpatDeferredCommunicationOp deferred) {
|
||||||
|
deferred.emitOpError(
|
||||||
|
"phase 2 left an unrealized deferred communication");
|
||||||
|
deferredRemains = true;
|
||||||
|
});
|
||||||
|
return success(!deferredRemains);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace onnx_mlir::spatial
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
||||||
|
|
||||||
|
namespace onnx_mlir::spatial {
|
||||||
|
|
||||||
|
struct ScheduledComputeMaterializationResult;
|
||||||
|
|
||||||
|
mlir::LogicalResult realizeDeferredCommunication(
|
||||||
|
mlir::func::FuncOp funcOp,
|
||||||
|
const ScheduledComputeMaterializationResult &materialization);
|
||||||
|
|
||||||
|
} // namespace onnx_mlir::spatial
|
||||||
+397
@@ -0,0 +1,397 @@
|
|||||||
|
#include "llvm/ADT/MapVector.h"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
#include "DeferredCommunicationScheduling.hpp"
|
||||||
|
#include "DeferredTransferPlanning.hpp"
|
||||||
|
|
||||||
|
namespace onnx_mlir::spatial {
|
||||||
|
using namespace mlir;
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using TransferEmissionSignature =
|
||||||
|
std::tuple<ScheduledInfo*, Value, Type, bool, bool, bool>;
|
||||||
|
|
||||||
|
static TransferEmissionSignature getTransferEmissionSignature(
|
||||||
|
const ExternalTransferFamily& family) {
|
||||||
|
ProducedValue* producer = family.requirement->producer;
|
||||||
|
return {producer->scheduled,
|
||||||
|
producer->payload,
|
||||||
|
family.requirement->publicationFragmentType,
|
||||||
|
family.requirement->graphLanes.has_value(),
|
||||||
|
family.requirement->producerProjection.has_value(),
|
||||||
|
producer->scheduled->isBatch()};
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StreamThreshold {
|
||||||
|
unsigned stream = 0;
|
||||||
|
unsigned completedStep = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TransferGroup {
|
||||||
|
DeferredExchangePlan* exchange = nullptr;
|
||||||
|
SmallVector<ScheduledTransferSlice> ordered;
|
||||||
|
SmallVector<StreamThreshold> dependencies;
|
||||||
|
unsigned unsatisfied = 0;
|
||||||
|
bool scheduled = false;
|
||||||
|
std::tuple<unsigned, unsigned, unsigned, uint64_t> originalPriority;
|
||||||
|
std::tuple<unsigned, unsigned, unsigned,
|
||||||
|
std::tuple<unsigned, unsigned, unsigned, uint64_t>> staticPriority;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StreamProgress {
|
||||||
|
unsigned completedStep = 0;
|
||||||
|
SmallVector<unsigned> pendingAtStep;
|
||||||
|
SmallVector<SmallVector<unsigned>> unlockedAtStep;
|
||||||
|
bool queued = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
static size_t hashSignature(const TransferEmissionSignature& signature) {
|
||||||
|
return llvm::hash_combine(std::get<0>(signature),
|
||||||
|
std::get<1>(signature).getAsOpaquePointer(),
|
||||||
|
std::get<2>(signature).getAsOpaquePointer(),
|
||||||
|
std::get<3>(signature),
|
||||||
|
std::get<4>(signature),
|
||||||
|
std::get<5>(signature));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool betterStaticPriority(const TransferGroup& lhs, const TransferGroup& rhs) {
|
||||||
|
return lhs.staticPriority < rhs.staticPriority;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool orderPermutationCycles(SmallVectorImpl<ScheduledTransferSlice>& slices) {
|
||||||
|
if (slices.size() < 2)
|
||||||
|
return false;
|
||||||
|
DenseMap<unsigned, unsigned> edgeBySource;
|
||||||
|
DenseMap<unsigned, unsigned> edgeByTarget;
|
||||||
|
for (auto [index, slice] : llvm::enumerate(slices)) {
|
||||||
|
ExternalTransferFamily& family = *slice.family;
|
||||||
|
if (slice.transferCount != 1 || family.sourceScheduled != family.targetScheduled)
|
||||||
|
return false;
|
||||||
|
unsigned source = family.requirement->producer->scheduledLane;
|
||||||
|
unsigned target = family.targetLanes.intervals().front().begin + slice.familyOffset;
|
||||||
|
if (!edgeBySource.try_emplace(source, index).second || !edgeByTarget.try_emplace(target, index).second)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SmallVector<ScheduledTransferSlice> ordered;
|
||||||
|
SmallVector<bool> emitted(slices.size());
|
||||||
|
auto appendChain = [&](unsigned source) {
|
||||||
|
while (true) {
|
||||||
|
auto edge = edgeBySource.find(source);
|
||||||
|
if (edge == edgeBySource.end() || emitted[edge->second])
|
||||||
|
break;
|
||||||
|
unsigned index = edge->second;
|
||||||
|
emitted[index] = true;
|
||||||
|
ordered.push_back(slices[index]);
|
||||||
|
ExternalTransferFamily& family = *slices[index].family;
|
||||||
|
source = family.targetLanes.intervals().front().begin + slices[index].familyOffset;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
SmallVector<unsigned> pathStarts;
|
||||||
|
for (auto [source, index] : edgeBySource)
|
||||||
|
if (!edgeByTarget.count(source))
|
||||||
|
pathStarts.push_back(source);
|
||||||
|
llvm::sort(pathStarts);
|
||||||
|
for (unsigned source : pathStarts)
|
||||||
|
appendChain(source);
|
||||||
|
while (ordered.size() != slices.size()) {
|
||||||
|
unsigned source = std::numeric_limits<unsigned>::max();
|
||||||
|
for (auto [candidate, index] : edgeBySource)
|
||||||
|
if (!emitted[index])
|
||||||
|
source = std::min(source, candidate);
|
||||||
|
appendChain(source);
|
||||||
|
}
|
||||||
|
slices = std::move(ordered);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void orderGroupSlices(TransferGroup& group) {
|
||||||
|
SmallVector<TransferEmissionSignature> signatures;
|
||||||
|
DenseMap<size_t, SmallVector<unsigned>> signatureIdsByHash;
|
||||||
|
SmallVector<SmallVector<ScheduledTransferSlice>> slicesBySignature;
|
||||||
|
SmallVector<unsigned> order;
|
||||||
|
for (ExternalTransferFamily& family : group.exchange->external) {
|
||||||
|
ScheduledTransferSlice slice {&family, 0, family.targetLanes.size()};
|
||||||
|
TransferEmissionSignature signature = getTransferEmissionSignature(family);
|
||||||
|
size_t hash = hashSignature(signature);
|
||||||
|
std::optional<unsigned> id;
|
||||||
|
for (unsigned candidate : signatureIdsByHash.lookup(hash))
|
||||||
|
if (signatures[candidate] == signature) {
|
||||||
|
id = candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!id) {
|
||||||
|
id = signatures.size();
|
||||||
|
signatures.push_back(signature);
|
||||||
|
signatureIdsByHash[hash].push_back(*id);
|
||||||
|
slicesBySignature.emplace_back();
|
||||||
|
order.push_back(*id);
|
||||||
|
}
|
||||||
|
slicesBySignature[*id].push_back(slice);
|
||||||
|
}
|
||||||
|
llvm::stable_sort(order, [&](unsigned lhs, unsigned rhs) {
|
||||||
|
auto count = [&](unsigned id) {
|
||||||
|
size_t result = 0;
|
||||||
|
for (const ScheduledTransferSlice& slice : slicesBySignature[id])
|
||||||
|
result += slice.transferCount;
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
return count(lhs) > count(rhs);
|
||||||
|
});
|
||||||
|
for (unsigned id : order) {
|
||||||
|
if (!orderPermutationCycles(slicesBySignature[id]))
|
||||||
|
llvm::stable_sort(slicesBySignature[id], [](const ScheduledTransferSlice& lhs, const ScheduledTransferSlice& rhs) {
|
||||||
|
ExternalTransferFamily& left = *lhs.family;
|
||||||
|
ExternalTransferFamily& right = *rhs.family;
|
||||||
|
return std::tuple(left.sourceStreams.valueAt(lhs.familyOffset), left.targetLanes.intervals().front().begin)
|
||||||
|
> std::tuple(right.sourceStreams.valueAt(rhs.familyOffset), right.targetLanes.intervals().front().begin);
|
||||||
|
});
|
||||||
|
llvm::append_range(group.ordered, slicesBySignature[id]);
|
||||||
|
}
|
||||||
|
unsigned instructionCount = 0;
|
||||||
|
unsigned absorbedTransfers = 0;
|
||||||
|
unsigned transferCount = 0;
|
||||||
|
std::optional<TransferEmissionSignature> previousSignature;
|
||||||
|
RequirementFamily* previousRequirement = nullptr;
|
||||||
|
for (const ScheduledTransferSlice& slice : group.ordered) {
|
||||||
|
TransferEmissionSignature signature =
|
||||||
|
getTransferEmissionSignature(*slice.family);
|
||||||
|
if (!previousSignature || *previousSignature != signature)
|
||||||
|
++instructionCount;
|
||||||
|
else
|
||||||
|
absorbedTransfers += slice.transferCount;
|
||||||
|
previousSignature = signature;
|
||||||
|
if (previousRequirement != slice.family->requirement)
|
||||||
|
++instructionCount;
|
||||||
|
previousRequirement = slice.family->requirement;
|
||||||
|
transferCount += slice.transferCount;
|
||||||
|
}
|
||||||
|
group.staticPriority =
|
||||||
|
{instructionCount,
|
||||||
|
std::numeric_limits<unsigned>::max() - absorbedTransfers,
|
||||||
|
transferCount,
|
||||||
|
group.originalPriority};
|
||||||
|
}
|
||||||
|
|
||||||
|
static SmallVector<TransferGroup> buildGroups(DeferredTransferPlan& plan) {
|
||||||
|
SmallVector<TransferGroup> groups;
|
||||||
|
for (const std::unique_ptr<DeferredExchangePlan>& exchange : plan.exchanges) {
|
||||||
|
if (exchange->external.empty())
|
||||||
|
continue;
|
||||||
|
TransferGroup group;
|
||||||
|
group.exchange = exchange.get();
|
||||||
|
DenseMap<unsigned, unsigned> thresholdByStream;
|
||||||
|
unsigned minSource = std::numeric_limits<unsigned>::max();
|
||||||
|
unsigned minTarget = std::numeric_limits<unsigned>::max();
|
||||||
|
for (ExternalTransferFamily& family : exchange->external) {
|
||||||
|
unsigned source = family.sourceStreams.valueAt(0);
|
||||||
|
thresholdByStream[source] = std::max(thresholdByStream.lookup(source), family.requirement->producer->step + 1);
|
||||||
|
minSource = std::min(minSource, source);
|
||||||
|
for (size_t index = 0; index < family.targetStreams.size(); ++index)
|
||||||
|
minTarget = std::min<unsigned>(minTarget, family.targetStreams.valueAt(index));
|
||||||
|
}
|
||||||
|
for (LocalAvailabilityFamily& local : exchange->local)
|
||||||
|
for (LaneInterval interval : local.targetLanes.intervals())
|
||||||
|
for (unsigned lane = interval.begin; lane < interval.end; ++lane) {
|
||||||
|
unsigned stream = exchange->target->streamIds[lane];
|
||||||
|
thresholdByStream[stream] = std::max(thresholdByStream.lookup(stream), local.requirement->producer->step + 1);
|
||||||
|
}
|
||||||
|
SmallVector<unsigned> streams;
|
||||||
|
for (auto [stream, threshold] : thresholdByStream)
|
||||||
|
streams.push_back(stream);
|
||||||
|
llvm::sort(streams);
|
||||||
|
for (unsigned stream : streams)
|
||||||
|
group.dependencies.push_back({stream, thresholdByStream.lookup(stream)});
|
||||||
|
group.unsatisfied =
|
||||||
|
llvm::count_if(group.dependencies, [](StreamThreshold dependency) { return dependency.completedStep != 0; });
|
||||||
|
group.originalPriority = {exchange->consumerStep, minSource, minTarget, exchange->exchangeId};
|
||||||
|
orderGroupSlices(group);
|
||||||
|
groups.push_back(std::move(group));
|
||||||
|
}
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned tailExtension(const TransferGroup& group,
|
||||||
|
const TransferEmissionSignature& tail,
|
||||||
|
unsigned tailBoundary,
|
||||||
|
ArrayRef<StreamProgress> streams) {
|
||||||
|
unsigned extension = 0;
|
||||||
|
for (const ScheduledTransferSlice& slice : group.ordered) {
|
||||||
|
ExternalTransferFamily& family = *slice.family;
|
||||||
|
if (!(getTransferEmissionSignature(family) == tail))
|
||||||
|
break;
|
||||||
|
for (size_t index = 0; index < slice.transferCount; ++index) {
|
||||||
|
size_t familyIndex = slice.familyOffset + index;
|
||||||
|
if (streams[family.sourceStreams.valueAt(familyIndex)].completedStep
|
||||||
|
!= tailBoundary)
|
||||||
|
return extension;
|
||||||
|
++extension;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool haveSameTransferEmissionSignature(
|
||||||
|
const ExternalTransferFamily& lhs, const ExternalTransferFamily& rhs) {
|
||||||
|
return getTransferEmissionSignature(lhs) == getTransferEmissionSignature(rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<ScheduledCommunicationPlan> scheduleDeferredCommunication(func::FuncOp funcOp, DeferredTransferPlan& plan) {
|
||||||
|
SmallVector<TransferGroup> groups = buildGroups(plan);
|
||||||
|
SmallVector<StreamProgress> streams(plan.stepCounts.size());
|
||||||
|
for (auto [stream, progress] : llvm::enumerate(streams)) {
|
||||||
|
progress.pendingAtStep.resize(plan.stepCounts[stream]);
|
||||||
|
progress.unlockedAtStep.resize(plan.stepCounts[stream] + 1);
|
||||||
|
}
|
||||||
|
for (const std::unique_ptr<DeferredExchangePlan>& exchange : plan.exchanges)
|
||||||
|
for (ExternalTransferFamily& family : exchange->external)
|
||||||
|
for (size_t index = 0; index < family.targetStreams.size(); ++index)
|
||||||
|
++streams[family.targetStreams.valueAt(index)].pendingAtStep[exchange->consumerStep];
|
||||||
|
for (auto [groupIndex, group] : llvm::enumerate(groups))
|
||||||
|
for (StreamThreshold dependency : group.dependencies)
|
||||||
|
streams[dependency.stream].unlockedAtStep[dependency.completedStep].push_back(groupIndex);
|
||||||
|
|
||||||
|
auto compare = [&](unsigned lhs, unsigned rhs) { return betterStaticPriority(groups[rhs], groups[lhs]); };
|
||||||
|
using Heap = std::priority_queue<unsigned, std::vector<unsigned>, decltype(compare)>;
|
||||||
|
Heap ready(compare);
|
||||||
|
DenseMap<size_t, std::unique_ptr<Heap>> bySignature;
|
||||||
|
auto addReady = [&](unsigned index) {
|
||||||
|
TransferGroup& group = groups[index];
|
||||||
|
ready.push(index);
|
||||||
|
auto& bucket = bySignature[hashSignature(
|
||||||
|
getTransferEmissionSignature(*group.ordered.front().family))];
|
||||||
|
if (!bucket)
|
||||||
|
bucket = std::make_unique<Heap>(compare);
|
||||||
|
bucket->push(index);
|
||||||
|
};
|
||||||
|
for (auto [index, group] : llvm::enumerate(groups))
|
||||||
|
if (group.unsatisfied == 0)
|
||||||
|
addReady(index);
|
||||||
|
|
||||||
|
std::queue<unsigned> advanceable;
|
||||||
|
auto enqueue = [&](unsigned stream) {
|
||||||
|
StreamProgress& progress = streams[stream];
|
||||||
|
if (!progress.queued && progress.completedStep < plan.stepCounts[stream]
|
||||||
|
&& progress.pendingAtStep[progress.completedStep] == 0) {
|
||||||
|
progress.queued = true;
|
||||||
|
advanceable.push(stream);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (unsigned stream = 0; stream < streams.size(); ++stream)
|
||||||
|
enqueue(stream);
|
||||||
|
auto advance = [&] {
|
||||||
|
bool changed = false;
|
||||||
|
while (!advanceable.empty()) {
|
||||||
|
unsigned stream = advanceable.front();
|
||||||
|
advanceable.pop();
|
||||||
|
StreamProgress& progress = streams[stream];
|
||||||
|
progress.queued = false;
|
||||||
|
if (progress.completedStep == plan.stepCounts[stream] || progress.pendingAtStep[progress.completedStep] != 0)
|
||||||
|
continue;
|
||||||
|
++progress.completedStep;
|
||||||
|
changed = true;
|
||||||
|
for (unsigned groupIndex : progress.unlockedAtStep[progress.completedStep])
|
||||||
|
if (--groups[groupIndex].unsatisfied == 0)
|
||||||
|
addReady(groupIndex);
|
||||||
|
enqueue(stream);
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
};
|
||||||
|
|
||||||
|
ScheduledCommunicationPlan result;
|
||||||
|
unsigned finishedGroups = 0;
|
||||||
|
while (finishedGroups != groups.size()) {
|
||||||
|
bool progressed = advance();
|
||||||
|
std::optional<unsigned> chosen;
|
||||||
|
unsigned bestExtension = 0;
|
||||||
|
if (!result.slices.empty()) {
|
||||||
|
ScheduledTransferSlice& tailSlice = result.slices.back();
|
||||||
|
ExternalTransferFamily& tailFamily = *tailSlice.family;
|
||||||
|
TransferEmissionSignature tail = getTransferEmissionSignature(tailFamily);
|
||||||
|
size_t hash = hashSignature(tail);
|
||||||
|
auto bucket = bySignature.find(hash);
|
||||||
|
SmallVector<unsigned> inspected;
|
||||||
|
while (bucket != bySignature.end() && !bucket->second->empty()) {
|
||||||
|
unsigned candidate = bucket->second->top();
|
||||||
|
bucket->second->pop();
|
||||||
|
TransferGroup& group = groups[candidate];
|
||||||
|
if (group.scheduled)
|
||||||
|
continue;
|
||||||
|
inspected.push_back(candidate);
|
||||||
|
unsigned extension = tailExtension(
|
||||||
|
group, tail, tailSlice.sourceInsertionStep, streams);
|
||||||
|
if (extension > bestExtension
|
||||||
|
|| (extension == bestExtension && extension != 0
|
||||||
|
&& (!chosen || betterStaticPriority(group, groups[*chosen])))) {
|
||||||
|
chosen = candidate;
|
||||||
|
bestExtension = extension;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (unsigned candidate : inspected)
|
||||||
|
if (!chosen || candidate != *chosen)
|
||||||
|
bucket->second->push(candidate);
|
||||||
|
}
|
||||||
|
while (!chosen && !ready.empty()) {
|
||||||
|
unsigned candidate = ready.top();
|
||||||
|
ready.pop();
|
||||||
|
if (!groups[candidate].scheduled)
|
||||||
|
chosen = candidate;
|
||||||
|
}
|
||||||
|
if (chosen) {
|
||||||
|
TransferGroup& group = groups[*chosen];
|
||||||
|
group.scheduled = true;
|
||||||
|
++finishedGroups;
|
||||||
|
for (ScheduledTransferSlice slice : group.ordered) {
|
||||||
|
ExternalTransferFamily& family = *slice.family;
|
||||||
|
size_t end = slice.familyOffset + slice.transferCount;
|
||||||
|
for (size_t begin = slice.familyOffset; begin < end;) {
|
||||||
|
unsigned sourceStep = streams[
|
||||||
|
family.sourceStreams.valueAt(begin)].completedStep;
|
||||||
|
unsigned targetStep = streams[
|
||||||
|
family.targetStreams.valueAt(begin)].completedStep;
|
||||||
|
size_t next = begin + 1;
|
||||||
|
while (next < end
|
||||||
|
&& streams[family.sourceStreams.valueAt(next)].completedStep
|
||||||
|
== sourceStep
|
||||||
|
&& streams[family.targetStreams.valueAt(next)].completedStep
|
||||||
|
== targetStep)
|
||||||
|
++next;
|
||||||
|
result.slices.push_back(
|
||||||
|
{&family, begin, next - begin, sourceStep, targetStep});
|
||||||
|
result.logicalTransferCount += next - begin;
|
||||||
|
begin = next;
|
||||||
|
}
|
||||||
|
for (size_t index = slice.familyOffset; index < end; ++index) {
|
||||||
|
unsigned stream = family.targetStreams.valueAt(index);
|
||||||
|
unsigned& pending = streams[stream].pendingAtStep[group.exchange->consumerStep];
|
||||||
|
--pending;
|
||||||
|
if (pending == 0 && streams[stream].completedStep == group.exchange->consumerStep)
|
||||||
|
enqueue(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
progressed = true;
|
||||||
|
}
|
||||||
|
if (progressed)
|
||||||
|
continue;
|
||||||
|
InFlightDiagnostic diagnostic =
|
||||||
|
funcOp.emitOpError("global communication scheduler made no progress before IR mutation");
|
||||||
|
unsigned reported = 0;
|
||||||
|
for (const TransferGroup& group : groups) {
|
||||||
|
if (group.scheduled || reported++ == 8)
|
||||||
|
continue;
|
||||||
|
diagnostic << "; blocked parent " << group.exchange->exchangeId;
|
||||||
|
auto dependency = llvm::find_if(group.dependencies, [&](StreamThreshold item) {
|
||||||
|
return streams[item.stream].completedStep < item.completedStep;
|
||||||
|
});
|
||||||
|
if (dependency != group.dependencies.end())
|
||||||
|
diagnostic << " stream " << dependency->stream << " requires " << dependency->completedStep;
|
||||||
|
}
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace onnx_mlir::spatial
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
||||||
|
|
||||||
|
#include "DeferredCommunicationModel.hpp"
|
||||||
|
|
||||||
|
namespace onnx_mlir::spatial {
|
||||||
|
|
||||||
|
struct DeferredTransferPlan;
|
||||||
|
|
||||||
|
struct ScheduledTransferSlice {
|
||||||
|
ExternalTransferFamily* family = nullptr;
|
||||||
|
size_t familyOffset = 0;
|
||||||
|
size_t transferCount = 0;
|
||||||
|
unsigned sourceInsertionStep = 0;
|
||||||
|
unsigned targetInsertionStep = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ScheduledCommunicationPlan {
|
||||||
|
llvm::SmallVector<ScheduledTransferSlice> slices;
|
||||||
|
uint64_t logicalTransferCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool haveSameTransferEmissionSignature(
|
||||||
|
const ExternalTransferFamily& lhs, const ExternalTransferFamily& rhs);
|
||||||
|
|
||||||
|
mlir::FailureOr<ScheduledCommunicationPlan> scheduleDeferredCommunication(mlir::func::FuncOp funcOp,
|
||||||
|
DeferredTransferPlan& plan);
|
||||||
|
|
||||||
|
} // namespace onnx_mlir::spatial
|
||||||
@@ -0,0 +1,621 @@
|
|||||||
|
#include "DeferredProjectionAnalysis.hpp"
|
||||||
|
|
||||||
|
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||||
|
#include "mlir/Dialect/Linalg/IR/Linalg.h"
|
||||||
|
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||||
|
#include "mlir/IR/Matchers.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/SmallPtrSet.h"
|
||||||
|
#include "llvm/ADT/SmallSet.h"
|
||||||
|
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/ShapingUtils.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
||||||
|
|
||||||
|
namespace onnx_mlir::spatial {
|
||||||
|
using namespace mlir;
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
static FailureOr<int64_t> getSignedInt64(IntegerAttr value) {
|
||||||
|
return value.getValue().isSignedIntN(64)
|
||||||
|
? FailureOr<int64_t>(value.getValue().getSExtValue())
|
||||||
|
: FailureOr<int64_t>(failure());
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<int64_t> evaluate(
|
||||||
|
Value value, const StaticIndexEnvironment &environment,
|
||||||
|
llvm::SmallDenseSet<Value, 16> &visiting);
|
||||||
|
|
||||||
|
static FailureOr<int64_t> evaluateDenseExtract(
|
||||||
|
tensor::ExtractOp extract, const StaticIndexEnvironment &environment,
|
||||||
|
llvm::SmallDenseSet<Value, 16> &visiting) {
|
||||||
|
auto constant = extract.getTensor().getDefiningOp<arith::ConstantOp>();
|
||||||
|
auto elements = constant
|
||||||
|
? dyn_cast<DenseIntElementsAttr>(constant.getValue())
|
||||||
|
: DenseIntElementsAttr();
|
||||||
|
auto type = elements
|
||||||
|
? dyn_cast<RankedTensorType>(elements.getType()) : RankedTensorType();
|
||||||
|
if (!elements || !type || !type.hasStaticShape()
|
||||||
|
|| extract.getIndices().size() != static_cast<size_t>(type.getRank()))
|
||||||
|
return failure();
|
||||||
|
int64_t linear = 0;
|
||||||
|
for (auto [index, dim] : llvm::zip(extract.getIndices(), type.getShape())) {
|
||||||
|
auto folded = evaluate(index, environment, visiting);
|
||||||
|
if (failed(folded) || *folded < 0 || *folded >= dim
|
||||||
|
|| llvm::MulOverflow(linear, dim, linear)
|
||||||
|
|| llvm::AddOverflow(linear, *folded, linear))
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
APInt value = elements.getValues<APInt>()[linear];
|
||||||
|
return value.isSignedIntN(64)
|
||||||
|
? FailureOr<int64_t>(value.getSExtValue())
|
||||||
|
: FailureOr<int64_t>(failure());
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<int64_t> evaluate(
|
||||||
|
Value value, const StaticIndexEnvironment &environment,
|
||||||
|
llvm::SmallDenseSet<Value, 16> &visiting) {
|
||||||
|
if (auto it = environment.bindings.find(value);
|
||||||
|
it != environment.bindings.end())
|
||||||
|
return it->second;
|
||||||
|
Attribute constant;
|
||||||
|
if (matchPattern(value, m_Constant(&constant)))
|
||||||
|
if (auto integer = dyn_cast_or_null<IntegerAttr>(constant))
|
||||||
|
return getSignedInt64(integer);
|
||||||
|
if (isa<BlockArgument>(value) || !value.getDefiningOp()
|
||||||
|
|| !visiting.insert(value).second)
|
||||||
|
return failure();
|
||||||
|
if (auto extract = value.getDefiningOp<tensor::ExtractOp>()) {
|
||||||
|
auto result = evaluateDenseExtract(extract, environment, visiting);
|
||||||
|
visiting.erase(value);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
Operation *op = value.getDefiningOp();
|
||||||
|
if (op->getNumRegions() != 0 || !isPureIndexComputationOp(op)) {
|
||||||
|
visiting.erase(value);
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
SmallVector<Attribute> operands;
|
||||||
|
Builder builder(op->getContext());
|
||||||
|
for (Value operand : op->getOperands()) {
|
||||||
|
auto folded = evaluate(operand, environment, visiting);
|
||||||
|
if (failed(folded)) {
|
||||||
|
visiting.erase(value);
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
operands.push_back(builder.getIntegerAttr(operand.getType(), *folded));
|
||||||
|
}
|
||||||
|
SmallVector<OpFoldResult> results;
|
||||||
|
if (failed(op->fold(operands, results)) || results.size() != 1) {
|
||||||
|
visiting.erase(value);
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
FailureOr<int64_t> result = failure();
|
||||||
|
if (auto attr = dyn_cast<Attribute>(results.front())) {
|
||||||
|
if (auto integer = dyn_cast<IntegerAttr>(attr))
|
||||||
|
result = getSignedInt64(integer);
|
||||||
|
} else if (auto folded = dyn_cast<Value>(results.front())) {
|
||||||
|
result = evaluate(folded, environment, visiting);
|
||||||
|
}
|
||||||
|
visiting.erase(value);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<std::optional<unsigned>> sourceArgument(
|
||||||
|
Value value, SpatDeferredCommunicationOp deferred,
|
||||||
|
const StaticIndexEnvironment &environment) {
|
||||||
|
while (auto cast = value.getDefiningOp<tensor::CastOp>())
|
||||||
|
value = cast.getSource();
|
||||||
|
if (auto argument = dyn_cast<BlockArgument>(value);
|
||||||
|
argument && argument.getOwner() == &deferred.getBody().front()
|
||||||
|
&& argument.getArgNumber() < deferred.getSources().size())
|
||||||
|
return std::optional<unsigned>(argument.getArgNumber());
|
||||||
|
auto result = dyn_cast<OpResult>(value);
|
||||||
|
auto compactSelection = result
|
||||||
|
? dyn_cast<SpatDeferredSourceSelectOp>(result.getOwner())
|
||||||
|
: SpatDeferredSourceSelectOp();
|
||||||
|
if (compactSelection && result.getResultNumber() == 0) {
|
||||||
|
auto selector = evaluateDeferredIndex(
|
||||||
|
compactSelection.getSelector(), environment);
|
||||||
|
if (failed(selector) || *selector < 0
|
||||||
|
|| *selector >= static_cast<int64_t>(
|
||||||
|
compactSelection.getSources().size()))
|
||||||
|
return failure();
|
||||||
|
return sourceArgument(
|
||||||
|
compactSelection.getSources()[*selector], deferred, environment);
|
||||||
|
}
|
||||||
|
return std::optional<unsigned>();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void collectSourceArguments(Value value,
|
||||||
|
SpatDeferredCommunicationOp deferred,
|
||||||
|
llvm::SmallSet<unsigned, 4> &indices) {
|
||||||
|
while (auto cast = value.getDefiningOp<tensor::CastOp>())
|
||||||
|
value = cast.getSource();
|
||||||
|
if (auto argument = dyn_cast<BlockArgument>(value)) {
|
||||||
|
if (argument.getOwner() == &deferred.getBody().front()
|
||||||
|
&& argument.getArgNumber() < deferred.getSources().size())
|
||||||
|
indices.insert(argument.getArgNumber());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto result = dyn_cast<OpResult>(value);
|
||||||
|
auto compactSelection = result
|
||||||
|
? dyn_cast<SpatDeferredSourceSelectOp>(result.getOwner())
|
||||||
|
: SpatDeferredSourceSelectOp();
|
||||||
|
if (compactSelection && result.getResultNumber() == 0) {
|
||||||
|
for (Value source : compactSelection.getSources())
|
||||||
|
collectSourceArguments(source, deferred, indices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Value getEnclosingScheduledLane(
|
||||||
|
SpatDeferredCommunicationOp deferred,
|
||||||
|
SpatScheduledComputeBatch scheduled) {
|
||||||
|
Block *block = deferred->getBlock();
|
||||||
|
while (block && block->getParentOp() != scheduled) {
|
||||||
|
Operation *parent = block->getParentOp();
|
||||||
|
block = parent ? parent->getBlock() : nullptr;
|
||||||
|
}
|
||||||
|
return block && !block->empty() ? block->getArgument(0) : Value();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool originatesFromDeferredSource(
|
||||||
|
Value value, SpatDeferredCommunicationOp deferred,
|
||||||
|
llvm::SmallDenseSet<Value, 16> &visited) {
|
||||||
|
if (!visited.insert(value).second)
|
||||||
|
return false;
|
||||||
|
if (auto argument = dyn_cast<BlockArgument>(value))
|
||||||
|
return argument.getOwner() == &deferred.getBody().front()
|
||||||
|
&& argument.getArgNumber() < deferred.getSources().size();
|
||||||
|
Operation *op = value.getDefiningOp();
|
||||||
|
return op && llvm::any_of(op->getOperands(), [&](Value operand) {
|
||||||
|
return originatesFromDeferredSource(operand, deferred, visited);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool originatesFromDeferredSource(
|
||||||
|
Value value, SpatDeferredCommunicationOp deferred) {
|
||||||
|
llvm::SmallDenseSet<Value, 16> visited;
|
||||||
|
return originatesFromDeferredSource(value, deferred, visited);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isInsideDeferredLoop(Operation *op,
|
||||||
|
SpatDeferredCommunicationOp deferred) {
|
||||||
|
for (Operation *parent = op->getParentOp(); parent && parent != deferred;
|
||||||
|
parent = parent->getParentOp())
|
||||||
|
if (isa<scf::ForOp>(parent))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<SmallVector<unsigned>>
|
||||||
|
getPossibleDeferredSourceOperandIndices(
|
||||||
|
Value sourceRoot, SpatDeferredCommunicationOp deferred) {
|
||||||
|
llvm::SmallSet<unsigned, 4> indices;
|
||||||
|
collectSourceArguments(sourceRoot, deferred, indices);
|
||||||
|
if (indices.empty())
|
||||||
|
return failure();
|
||||||
|
return SmallVector<unsigned>(indices.begin(), indices.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
static LogicalResult validateDeferredProgram(
|
||||||
|
SpatDeferredCommunicationOp deferred, Block &body, Value scheduledLane,
|
||||||
|
Value specialization, int64_t laneCount, int64_t specializationCount) {
|
||||||
|
auto specializes = [&](Value value, auto &&accept) {
|
||||||
|
for (int64_t specializationIndex = 0;
|
||||||
|
specializationIndex < specializationCount; ++specializationIndex)
|
||||||
|
for (int64_t lane = 0; lane < laneCount; ++lane) {
|
||||||
|
StaticIndexEnvironment environment;
|
||||||
|
if (scheduledLane)
|
||||||
|
environment.bindings[scheduledLane] = lane;
|
||||||
|
if (specialization)
|
||||||
|
environment.bindings[specialization] = specializationIndex;
|
||||||
|
auto result = evaluateDeferredIndex(value, environment);
|
||||||
|
if (failed(result) || !accept(*result))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
WalkResult result = body.walk([&](Operation *op) -> WalkResult {
|
||||||
|
auto reject = [&](StringRef message) {
|
||||||
|
op->emitOpError(message);
|
||||||
|
return WalkResult::interrupt();
|
||||||
|
};
|
||||||
|
if (isa<SpatYieldOp, scf::YieldOp>(op)
|
||||||
|
|| (isa<linalg::YieldOp>(op)
|
||||||
|
&& isa<linalg::TransposeOp>(op->getParentOp())))
|
||||||
|
return WalkResult::advance();
|
||||||
|
if (auto selection = dyn_cast<SpatDeferredSourceSelectOp>(op)) {
|
||||||
|
if (selection->getBlock() != &body)
|
||||||
|
return reject("must be a top-level deferred source selector");
|
||||||
|
if (selection.getSources().empty()
|
||||||
|
|| !selection.getSelector().getType().isIndex()
|
||||||
|
|| llvm::any_of(selection.getSources(), [&](Value source) {
|
||||||
|
return source.getType() != selection.getOutput().getType()
|
||||||
|
|| failed(getPossibleDeferredSourceOperandIndices(
|
||||||
|
source, deferred));
|
||||||
|
}))
|
||||||
|
return reject("has invalid deferred source operands or types");
|
||||||
|
if (!specializes(selection.getSelector(), [&](int64_t index) {
|
||||||
|
return index >= 0
|
||||||
|
&& index < static_cast<int64_t>(selection.getSources().size());
|
||||||
|
}))
|
||||||
|
return reject("source selector does not specialize to a valid source");
|
||||||
|
return WalkResult::advance();
|
||||||
|
}
|
||||||
|
if (auto loop = dyn_cast<scf::ForOp>(op)) {
|
||||||
|
for (Value bound : {loop.getLowerBound(), loop.getUpperBound()})
|
||||||
|
if (!specializes(bound, [](int64_t) { return true; }))
|
||||||
|
return reject("has a loop bound that does not specialize");
|
||||||
|
if (!specializes(loop.getStep(), [](int64_t step) { return step > 0; }))
|
||||||
|
return reject("has a non-positive or non-static deferred loop step");
|
||||||
|
return WalkResult::advance();
|
||||||
|
}
|
||||||
|
if (op->getNumRegions() != 0 && !isa<linalg::TransposeOp>(op))
|
||||||
|
return reject("contains an unsupported region operation");
|
||||||
|
if (!isShapingOnlyOp(op) && !isCompileTimeOp(op)
|
||||||
|
&& !isPureIndexComputationOp(op))
|
||||||
|
return reject("contains an unsupported deferred operation");
|
||||||
|
for (Value operand : op->getOperands())
|
||||||
|
if (isInsideDeferredLoop(op, deferred)
|
||||||
|
&& originatesFromDeferredSource(operand, deferred))
|
||||||
|
return reject("projects a deferred source inside a residual loop");
|
||||||
|
return WalkResult::advance();
|
||||||
|
});
|
||||||
|
return failure(result.wasInterrupted());
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool haveLeadingUnitDifference(RankedTensorType larger,
|
||||||
|
RankedTensorType smaller) {
|
||||||
|
return larger && smaller && larger.hasStaticShape() && smaller.hasStaticShape()
|
||||||
|
&& larger.getRank() == smaller.getRank() + 1
|
||||||
|
&& larger.getDimSize(0) == 1
|
||||||
|
&& larger.getShape().drop_front() == smaller.getShape();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional<DeferredAssemblySourceTransform> getSourceTransform(
|
||||||
|
RankedTensorType publication, RankedTensorType inserted) {
|
||||||
|
if (publication == inserted)
|
||||||
|
return DeferredAssemblySourceTransform::Identity;
|
||||||
|
if (haveLeadingUnitDifference(inserted, publication))
|
||||||
|
return DeferredAssemblySourceTransform::AddLeadingUnitDimension;
|
||||||
|
if (haveLeadingUnitDifference(publication, inserted))
|
||||||
|
return DeferredAssemblySourceTransform::RemoveLeadingUnitDimension;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<std::optional<DeferredInsertAssemblyTemplate>>
|
||||||
|
analyzeInsertAssembly(const DeferredProgramTemplate &program) {
|
||||||
|
auto finalInsert = program.yieldedValue.getDefiningOp<tensor::InsertSliceOp>();
|
||||||
|
if (!finalInsert || program.leaves.empty())
|
||||||
|
return std::optional<DeferredInsertAssemblyTemplate>();
|
||||||
|
DenseMap<Value, unsigned> leafByRoot;
|
||||||
|
for (auto [index, leaf] : llvm::enumerate(program.leaves))
|
||||||
|
if (!leafByRoot.try_emplace(leaf.replacementRoot, index).second)
|
||||||
|
return std::optional<DeferredInsertAssemblyTemplate>();
|
||||||
|
|
||||||
|
SmallPtrSet<Operation *, 32> consumed;
|
||||||
|
SmallVector<DeferredInsertAssemblyEntryTemplate> reverseEntries;
|
||||||
|
llvm::SmallDenseSet<unsigned, 16> insertedLeaves;
|
||||||
|
Value current = program.yieldedValue;
|
||||||
|
while (auto insert = current.getDefiningOp<tensor::InsertSliceOp>()) {
|
||||||
|
Value source = insert.getSource();
|
||||||
|
Operation *shape = source.getDefiningOp();
|
||||||
|
if (isa_and_nonnull<tensor::CollapseShapeOp, tensor::ExpandShapeOp>(shape))
|
||||||
|
source = shape->getOperand(0);
|
||||||
|
if (source.getDefiningOp<tensor::CollapseShapeOp>()
|
||||||
|
|| source.getDefiningOp<tensor::ExpandShapeOp>())
|
||||||
|
return std::optional<DeferredInsertAssemblyTemplate>();
|
||||||
|
auto leaf = leafByRoot.find(source);
|
||||||
|
if (leaf == leafByRoot.end()
|
||||||
|
|| !insertedLeaves.insert(leaf->second).second)
|
||||||
|
return std::optional<DeferredInsertAssemblyTemplate>();
|
||||||
|
auto publicationType = dyn_cast<RankedTensorType>(source.getType());
|
||||||
|
auto insertedType = dyn_cast<RankedTensorType>(insert.getSourceType());
|
||||||
|
auto transform = getSourceTransform(publicationType, insertedType);
|
||||||
|
if (!transform)
|
||||||
|
return std::optional<DeferredInsertAssemblyTemplate>();
|
||||||
|
DeferredInsertAssemblyEntryTemplate entry;
|
||||||
|
entry.coordinate = {0, leaf->second, 0};
|
||||||
|
entry.sourceTransform = *transform;
|
||||||
|
entry.sourceType = insertedType;
|
||||||
|
entry.targetGeometry = {SmallVector<OpFoldResult>(insert.getMixedOffsets()),
|
||||||
|
SmallVector<OpFoldResult>(insert.getMixedSizes()),
|
||||||
|
SmallVector<OpFoldResult>(insert.getMixedStrides())};
|
||||||
|
reverseEntries.push_back(std::move(entry));
|
||||||
|
consumed.insert(insert);
|
||||||
|
if (shape)
|
||||||
|
consumed.insert(shape);
|
||||||
|
current = insert.getDest();
|
||||||
|
}
|
||||||
|
auto initial = current.getDefiningOp<tensor::EmptyOp>();
|
||||||
|
auto resultType = dyn_cast<RankedTensorType>(program.yieldedValue.getType());
|
||||||
|
if (!initial || !initial.getDynamicSizes().empty() || !resultType
|
||||||
|
|| initial.getType() != resultType
|
||||||
|
|| insertedLeaves.size() != program.leaves.size())
|
||||||
|
return std::optional<DeferredInsertAssemblyTemplate>();
|
||||||
|
consumed.insert(initial);
|
||||||
|
if (llvm::any_of(program.residualOps,
|
||||||
|
[&](Operation *op) { return !consumed.contains(op); }))
|
||||||
|
return std::optional<DeferredInsertAssemblyTemplate>();
|
||||||
|
DeferredInsertAssemblyTemplate assembly;
|
||||||
|
assembly.initialValue = initial;
|
||||||
|
assembly.resultType = resultType;
|
||||||
|
assembly.entries.assign(reverseEntries.rbegin(), reverseEntries.rend());
|
||||||
|
return std::optional<DeferredInsertAssemblyTemplate>(std::move(assembly));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
FailureOr<int64_t> evaluateDeferredIndex(
|
||||||
|
Value value, const StaticIndexEnvironment &environment) {
|
||||||
|
llvm::SmallDenseSet<Value, 16> visiting;
|
||||||
|
return evaluate(value, environment, visiting);
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<int64_t> evaluateDeferredIndex(
|
||||||
|
OpFoldResult value, const StaticIndexEnvironment &environment) {
|
||||||
|
if (auto attr = dyn_cast<Attribute>(value))
|
||||||
|
if (auto integer = dyn_cast<IntegerAttr>(attr))
|
||||||
|
return getSignedInt64(integer);
|
||||||
|
if (auto dynamic = dyn_cast<Value>(value))
|
||||||
|
return evaluateDeferredIndex(dynamic, environment);
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
DeferredLaneValueEvaluator::DeferredLaneValueEvaluator(
|
||||||
|
const DeferredProgramTemplate &program, unsigned laneCount,
|
||||||
|
unsigned specializationIndex)
|
||||||
|
: program(program), laneCount(laneCount),
|
||||||
|
specializationIndex(specializationIndex) {}
|
||||||
|
|
||||||
|
FailureOr<StaticIntSequence> DeferredLaneValueEvaluator::evaluate(Value value) {
|
||||||
|
if (auto it = values.find(value); it != values.end())
|
||||||
|
return it->second;
|
||||||
|
if (value == program.scheduledLane) {
|
||||||
|
StaticIntSequence result = StaticIntSequence::affine(0, 1, laneCount);
|
||||||
|
values.try_emplace(value, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
SmallVector<int64_t> evaluated;
|
||||||
|
evaluated.reserve(laneCount);
|
||||||
|
for (unsigned lane = 0; lane < laneCount; ++lane) {
|
||||||
|
StaticIndexEnvironment environment;
|
||||||
|
if (program.scheduledLane)
|
||||||
|
environment.bindings[program.scheduledLane] = lane;
|
||||||
|
if (program.specializationArgument)
|
||||||
|
environment.bindings[program.specializationArgument] =
|
||||||
|
specializationIndex;
|
||||||
|
auto result = evaluateDeferredIndex(value, environment);
|
||||||
|
if (failed(result))
|
||||||
|
return failure();
|
||||||
|
evaluated.push_back(*result);
|
||||||
|
}
|
||||||
|
StaticIntSequence result = StaticIntSequence::fromValues(evaluated);
|
||||||
|
values.try_emplace(value, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<StaticIntSequence> DeferredLaneValueEvaluator::evaluate(
|
||||||
|
OpFoldResult value) {
|
||||||
|
if (auto attr = dyn_cast<Attribute>(value)) {
|
||||||
|
auto integer = dyn_cast<IntegerAttr>(attr);
|
||||||
|
if (!integer)
|
||||||
|
return failure();
|
||||||
|
auto number = getSignedInt64(integer);
|
||||||
|
return succeeded(number)
|
||||||
|
? FailureOr<StaticIntSequence>(
|
||||||
|
StaticIntSequence::uniform(*number, laneCount))
|
||||||
|
: FailureOr<StaticIntSequence>(failure());
|
||||||
|
}
|
||||||
|
return evaluate(cast<Value>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<StaticIntSequence>
|
||||||
|
DeferredLaneValueEvaluator::resolveSourceOperandIndices(Value sourceRoot) {
|
||||||
|
if (auto it = sourceOperands.find(sourceRoot); it != sourceOperands.end())
|
||||||
|
return it->second;
|
||||||
|
SmallVector<int64_t> indices;
|
||||||
|
indices.reserve(laneCount);
|
||||||
|
for (unsigned lane = 0; lane < laneCount; ++lane) {
|
||||||
|
StaticIndexEnvironment environment;
|
||||||
|
if (program.scheduledLane)
|
||||||
|
environment.bindings[program.scheduledLane] = lane;
|
||||||
|
if (program.specializationArgument)
|
||||||
|
environment.bindings[program.specializationArgument] =
|
||||||
|
specializationIndex;
|
||||||
|
auto index = sourceArgument(sourceRoot, program.deferred, environment);
|
||||||
|
if (failed(index) || !*index)
|
||||||
|
return failure();
|
||||||
|
indices.push_back(**index);
|
||||||
|
}
|
||||||
|
StaticIntSequence result = StaticIntSequence::fromValues(indices);
|
||||||
|
sourceOperands.try_emplace(sourceRoot, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<DeferredProgramTemplate> analyzeDeferredProgramTemplate(
|
||||||
|
SpatDeferredCommunicationOp deferred) {
|
||||||
|
if (!deferred.getBody().hasOneBlock())
|
||||||
|
return deferred.emitOpError(
|
||||||
|
"deferred program must have exactly one body block"), failure();
|
||||||
|
Block &body = deferred.getBody().front();
|
||||||
|
auto yield = dyn_cast<SpatYieldOp>(body.getTerminator());
|
||||||
|
if (!yield || yield.getOutputs().size() != 1)
|
||||||
|
return deferred.emitOpError(
|
||||||
|
"requires one deferred yielded value"), failure();
|
||||||
|
DeferredProgramTemplate program;
|
||||||
|
program.deferred = deferred;
|
||||||
|
program.yieldedValue = yield.getOutputs().front();
|
||||||
|
auto specialization = deferred->getAttrOfType<IntegerAttr>(
|
||||||
|
"specialization_count");
|
||||||
|
program.specializationCount = specialization
|
||||||
|
? specialization.getInt()
|
||||||
|
: 1;
|
||||||
|
if (program.specializationCount > 1) {
|
||||||
|
program.specializationArgument = body.getArguments().back();
|
||||||
|
program.specializationFragmentType = dyn_cast<RankedTensorType>(
|
||||||
|
program.yieldedValue.getType());
|
||||||
|
if (!program.specializationFragmentType)
|
||||||
|
return deferred.emitOpError(
|
||||||
|
"grouped deferred fragment must be a ranked tensor"), failure();
|
||||||
|
}
|
||||||
|
if (auto scheduled = deferred->getParentOfType<SpatScheduledComputeBatch>())
|
||||||
|
program.scheduledLane = getEnclosingScheduledLane(deferred, scheduled);
|
||||||
|
int64_t laneCount = 1;
|
||||||
|
if (auto scheduled = deferred->getParentOfType<SpatScheduledComputeBatch>())
|
||||||
|
laneCount = scheduled.getLaneCount();
|
||||||
|
if (failed(validateDeferredProgram(
|
||||||
|
deferred, body, program.scheduledLane,
|
||||||
|
program.specializationArgument, laneCount,
|
||||||
|
program.specializationCount)))
|
||||||
|
return failure();
|
||||||
|
|
||||||
|
llvm::SmallDenseSet<Value, 32> visited;
|
||||||
|
std::function<LogicalResult(Value)> visit = [&](Value value) -> LogicalResult {
|
||||||
|
if (!visited.insert(value).second)
|
||||||
|
return success();
|
||||||
|
if (auto slice = value.getDefiningOp<tensor::ExtractSliceOp>()) {
|
||||||
|
auto sources = getPossibleDeferredSourceOperandIndices(
|
||||||
|
slice.getSource(), deferred);
|
||||||
|
bool graphProjection = succeeded(sources)
|
||||||
|
&& llvm::all_of(*sources, [&](unsigned index) {
|
||||||
|
auto result = dyn_cast<OpResult>(deferred.getSources()[index]);
|
||||||
|
return result && isa<SpatGraphComputeBatch>(result.getOwner());
|
||||||
|
});
|
||||||
|
bool scalarProjection = succeeded(sources)
|
||||||
|
&& llvm::all_of(*sources, [&](unsigned index) {
|
||||||
|
auto result = dyn_cast<OpResult>(deferred.getSources()[index]);
|
||||||
|
return result && isa<SpatGraphCompute>(result.getOwner());
|
||||||
|
});
|
||||||
|
if (graphProjection || scalarProjection) {
|
||||||
|
DeferredProjectionLeafTemplate leaf;
|
||||||
|
leaf.form = graphProjection ? DeferredLeafForm::GraphBatchProjection
|
||||||
|
: DeferredLeafForm::ScalarProjection;
|
||||||
|
leaf.sourceRoot = slice.getSource();
|
||||||
|
leaf.replacementRoot = value;
|
||||||
|
leaf.leadingGeometry = {
|
||||||
|
SmallVector<OpFoldResult>(slice.getMixedOffsets()),
|
||||||
|
SmallVector<OpFoldResult>(slice.getMixedSizes()),
|
||||||
|
SmallVector<OpFoldResult>(slice.getMixedStrides())};
|
||||||
|
if (graphProjection)
|
||||||
|
leaf.innerGeometry = {
|
||||||
|
SmallVector<OpFoldResult>(
|
||||||
|
ArrayRef(slice.getMixedOffsets()).drop_front()),
|
||||||
|
SmallVector<OpFoldResult>(
|
||||||
|
ArrayRef(slice.getMixedSizes()).drop_front()),
|
||||||
|
SmallVector<OpFoldResult>(
|
||||||
|
ArrayRef(slice.getMixedStrides()).drop_front())};
|
||||||
|
leaf.reconstructedType = cast<RankedTensorType>(value.getType());
|
||||||
|
if (graphProjection
|
||||||
|
&& slice.getSourceType().getRank()
|
||||||
|
== leaf.reconstructedType.getRank() + 1
|
||||||
|
&& slice.getMixedSizes().size()
|
||||||
|
== static_cast<size_t>(slice.getSourceType().getRank())) {
|
||||||
|
ArrayRef<OpFoldResult> innerSizes =
|
||||||
|
ArrayRef(slice.getMixedSizes()).drop_front();
|
||||||
|
leaf.leadingRankReduced = llvm::equal(
|
||||||
|
innerSizes, leaf.reconstructedType.getShape(),
|
||||||
|
[](OpFoldResult size, int64_t dimension) {
|
||||||
|
return getConstantIntValue(size) == dimension;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
program.leaves.push_back(std::move(leaf));
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (succeeded(getPossibleDeferredSourceOperandIndices(value, deferred))) {
|
||||||
|
auto type = dyn_cast<RankedTensorType>(value.getType());
|
||||||
|
if (!type)
|
||||||
|
return deferred.emitOpError(
|
||||||
|
"deferred source is not a ranked tensor");
|
||||||
|
program.leaves.push_back({DeferredLeafForm::DirectSource, value, value,
|
||||||
|
{}, {}, type});
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
if (value.getType().isIndex() || isa<IntegerType>(value.getType()))
|
||||||
|
return success();
|
||||||
|
Operation *op = value.getDefiningOp();
|
||||||
|
if (!op || (op->getBlock() != &body && !isa<scf::ForOp>(op)))
|
||||||
|
return deferred.emitOpError(
|
||||||
|
"deferred residual escapes its verified body");
|
||||||
|
if (auto loop = dyn_cast<scf::ForOp>(op)) {
|
||||||
|
for (Value init : loop.getInitArgs())
|
||||||
|
if (failed(visit(init)))
|
||||||
|
return failure();
|
||||||
|
} else {
|
||||||
|
for (Value operand : op->getOperands())
|
||||||
|
if (failed(visit(operand)))
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
program.residualOps.push_back(op);
|
||||||
|
return success();
|
||||||
|
};
|
||||||
|
if (failed(visit(program.yieldedValue)))
|
||||||
|
return failure();
|
||||||
|
auto assembly = analyzeInsertAssembly(program);
|
||||||
|
if (failed(assembly))
|
||||||
|
return failure();
|
||||||
|
program.insertAssembly = std::move(*assembly);
|
||||||
|
return program;
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<const GraphBatchPublicationMap *> getGraphBatchPublicationMap(
|
||||||
|
SpatGraphComputeBatch graphBatch, unsigned resultIndex,
|
||||||
|
GraphBatchPublicationCache &cache) {
|
||||||
|
std::pair<Operation *, unsigned> key {graphBatch.getOperation(), resultIndex};
|
||||||
|
if (auto it = cache.find(key); it != cache.end())
|
||||||
|
return &it->second;
|
||||||
|
auto resultType = dyn_cast<RankedTensorType>(
|
||||||
|
graphBatch.getResult(resultIndex).getType());
|
||||||
|
auto output = graphBatch.getOutputArgument(resultIndex);
|
||||||
|
auto lane = graphBatch.getLaneArgument();
|
||||||
|
if (!resultType || !output || !lane || resultType.getRank() == 0)
|
||||||
|
return graphBatch.emitOpError(
|
||||||
|
"graph batch publication is malformed"), failure();
|
||||||
|
tensor::ParallelInsertSliceOp publication;
|
||||||
|
auto parallel = dyn_cast<SpatInParallelOp>(
|
||||||
|
graphBatch.getBody().front().getTerminator());
|
||||||
|
if (!parallel)
|
||||||
|
return graphBatch.emitOpError(
|
||||||
|
"graph batch lacks publication region"), failure();
|
||||||
|
for (Operation &op : parallel.getRegion().front())
|
||||||
|
if (auto insert = dyn_cast<tensor::ParallelInsertSliceOp>(op);
|
||||||
|
insert && insert.getDest() == *output)
|
||||||
|
publication = insert;
|
||||||
|
auto fragment = publication
|
||||||
|
? dyn_cast<RankedTensorType>(publication.getSource().getType())
|
||||||
|
: RankedTensorType();
|
||||||
|
if (!publication || !fragment
|
||||||
|
|| resultType.getRank() != fragment.getRank() + 1)
|
||||||
|
return graphBatch.emitOpError(
|
||||||
|
"graph publication fragment type is invalid"), failure();
|
||||||
|
GraphBatchPublicationMap map;
|
||||||
|
map.physicalResultType = resultType;
|
||||||
|
map.publicationFragmentType = fragment;
|
||||||
|
map.graphLaneToPhysicalSlot.resize(graphBatch.getLaneCount(), -1);
|
||||||
|
map.physicalSlotToGraphLane.resize(resultType.getDimSize(0), -1);
|
||||||
|
for (int64_t index = 0; index < graphBatch.getLaneCount(); ++index) {
|
||||||
|
StaticIndexEnvironment environment;
|
||||||
|
environment.bindings[*lane] = index;
|
||||||
|
auto slot = evaluateDeferredIndex(
|
||||||
|
publication.getMixedOffsets().front(), environment);
|
||||||
|
auto size = evaluateDeferredIndex(
|
||||||
|
publication.getMixedSizes().front(), environment);
|
||||||
|
auto stride = evaluateDeferredIndex(
|
||||||
|
publication.getMixedStrides().front(), environment);
|
||||||
|
if (failed(slot) || failed(size) || failed(stride)
|
||||||
|
|| *size != 1 || *stride != 1 || *slot < 0
|
||||||
|
|| *slot >= resultType.getDimSize(0)
|
||||||
|
|| map.physicalSlotToGraphLane[*slot] != -1)
|
||||||
|
return graphBatch.emitOpError(
|
||||||
|
"graph publication leading geometry is invalid"), failure();
|
||||||
|
map.graphLaneToPhysicalSlot[index] = *slot;
|
||||||
|
map.physicalSlotToGraphLane[*slot] = index;
|
||||||
|
}
|
||||||
|
if (llvm::is_contained(map.physicalSlotToGraphLane, -1))
|
||||||
|
return graphBatch.emitOpError(
|
||||||
|
"graph publication has a missing physical slot"), failure();
|
||||||
|
return &cache.try_emplace(key, std::move(map)).first->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace onnx_mlir::spatial
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "DeferredCommunicationModel.hpp"
|
||||||
|
|
||||||
|
#include "llvm/ADT/DenseMap.h"
|
||||||
|
|
||||||
|
namespace onnx_mlir::spatial {
|
||||||
|
|
||||||
|
struct StaticIndexEnvironment {
|
||||||
|
llvm::DenseMap<mlir::Value, int64_t> bindings;
|
||||||
|
};
|
||||||
|
|
||||||
|
mlir::FailureOr<int64_t> evaluateDeferredIndex(
|
||||||
|
mlir::Value value, const StaticIndexEnvironment &environment);
|
||||||
|
mlir::FailureOr<int64_t> evaluateDeferredIndex(
|
||||||
|
mlir::OpFoldResult value, const StaticIndexEnvironment &environment);
|
||||||
|
|
||||||
|
mlir::FailureOr<DeferredProgramTemplate> analyzeDeferredProgramTemplate(
|
||||||
|
SpatDeferredCommunicationOp deferred);
|
||||||
|
|
||||||
|
class DeferredLaneValueEvaluator {
|
||||||
|
public:
|
||||||
|
DeferredLaneValueEvaluator(const DeferredProgramTemplate &program,
|
||||||
|
unsigned laneCount,
|
||||||
|
unsigned specializationIndex = 0);
|
||||||
|
|
||||||
|
mlir::FailureOr<StaticIntSequence> evaluate(mlir::Value value);
|
||||||
|
mlir::FailureOr<StaticIntSequence> evaluate(mlir::OpFoldResult value);
|
||||||
|
mlir::FailureOr<StaticIntSequence> resolveSourceOperandIndices(
|
||||||
|
mlir::Value sourceRoot);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const DeferredProgramTemplate &program;
|
||||||
|
unsigned laneCount;
|
||||||
|
unsigned specializationIndex;
|
||||||
|
llvm::DenseMap<mlir::Value, StaticIntSequence> values;
|
||||||
|
llvm::DenseMap<mlir::Value, StaticIntSequence> sourceOperands;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GraphBatchPublicationMap {
|
||||||
|
mlir::RankedTensorType physicalResultType;
|
||||||
|
mlir::RankedTensorType publicationFragmentType;
|
||||||
|
llvm::SmallVector<int64_t> graphLaneToPhysicalSlot;
|
||||||
|
llvm::SmallVector<int64_t> physicalSlotToGraphLane;
|
||||||
|
};
|
||||||
|
|
||||||
|
using GraphBatchPublicationCache =
|
||||||
|
llvm::DenseMap<std::pair<mlir::Operation *, unsigned>,
|
||||||
|
GraphBatchPublicationMap>;
|
||||||
|
|
||||||
|
mlir::FailureOr<const GraphBatchPublicationMap *> getGraphBatchPublicationMap(
|
||||||
|
SpatGraphComputeBatch graphBatch, unsigned resultIndex,
|
||||||
|
GraphBatchPublicationCache &cache);
|
||||||
|
|
||||||
|
} // namespace onnx_mlir::spatial
|
||||||
@@ -0,0 +1,338 @@
|
|||||||
|
#include "DeferredResultRealization.hpp"
|
||||||
|
#include "DeferredBoundaryRealization.hpp"
|
||||||
|
#include "DeferredProjectionAnalysis.hpp"
|
||||||
|
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||||
|
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||||
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
|
#include "mlir/IR/IRMapping.h"
|
||||||
|
#include "llvm/ADT/DenseSet.h"
|
||||||
|
#include <array>
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/TensorSliceUtils.hpp"
|
||||||
|
namespace onnx_mlir::spatial {
|
||||||
|
using namespace mlir; namespace {
|
||||||
|
static MixedSliceGeometry leadingSlice(OpBuilder &builder, RankedTensorType type, OpFoldResult position) {
|
||||||
|
MixedSliceGeometry geometry;
|
||||||
|
geometry.offsets.assign(type.getRank() + 1, builder.getIndexAttr(0));
|
||||||
|
geometry.offsets.front() = position;
|
||||||
|
geometry.sizes.push_back(builder.getIndexAttr(1));
|
||||||
|
for (int64_t dimension : type.getShape()) geometry.sizes.push_back(builder.getIndexAttr(dimension));
|
||||||
|
geometry.strides.assign(type.getRank() + 1, builder.getIndexAttr(1));
|
||||||
|
return geometry;
|
||||||
|
}
|
||||||
|
static LogicalResult verifyCollectionCoverage(
|
||||||
|
DeferredExchangePlan &exchange, FragmentCollectionPlan &collection,
|
||||||
|
unsigned slotCount) {
|
||||||
|
LaneSet all = LaneSet::all(exchange.targetLaneCount);
|
||||||
|
SmallVector<LaneSet, 0> coverage(slotCount);
|
||||||
|
for (const FragmentCollectionPlan::Requirement &entry :
|
||||||
|
collection.requirements) {
|
||||||
|
if (entry.position >= coverage.size()
|
||||||
|
|| !coverage[entry.position]
|
||||||
|
.intersect(entry.family->targetLanes).empty())
|
||||||
|
return exchange.deferred.emitOpError(
|
||||||
|
"fragment collection has overlapping coverage at slot ")
|
||||||
|
<< entry.position;
|
||||||
|
coverage[entry.position] =
|
||||||
|
coverage[entry.position].unite(entry.family->targetLanes);
|
||||||
|
}
|
||||||
|
for (auto [position, lanes] : llvm::enumerate(coverage))
|
||||||
|
if (!(lanes == all))
|
||||||
|
return exchange.deferred.emitOpError(
|
||||||
|
"fragment collection has incomplete coverage at slot ")
|
||||||
|
<< position;
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
static LogicalResult buildLeafCollections(DeferredExchangePlan &exchange,
|
||||||
|
DeferredResultPlan &plan) {
|
||||||
|
unsigned specializationCount = exchange.program.specializationCount;
|
||||||
|
for (auto [leafIndex, leaf] : llvm::enumerate(exchange.program.leaves)) {
|
||||||
|
SmallVector<RequirementFamily *> requirements;
|
||||||
|
unsigned positionCount = 0;
|
||||||
|
for (RequirementFamily &requirement : exchange.requirements)
|
||||||
|
if (requirement.coordinate.leafIndex == leafIndex) {
|
||||||
|
requirements.push_back(&requirement);
|
||||||
|
positionCount = std::max(
|
||||||
|
positionCount, requirement.coordinate.selectedPosition + 1);
|
||||||
|
}
|
||||||
|
if (requirements.empty())
|
||||||
|
return exchange.deferred.emitOpError(
|
||||||
|
"cannot form fragment collection for leaf ")
|
||||||
|
<< leafIndex << ": no requirements";
|
||||||
|
auto fragmentType = dyn_cast<RankedTensorType>(
|
||||||
|
requirements.front()->publicationFragmentType);
|
||||||
|
if (!fragmentType || llvm::any_of(requirements,
|
||||||
|
[&](RequirementFamily *item) {
|
||||||
|
return item->publicationFragmentType != fragmentType;
|
||||||
|
}))
|
||||||
|
return exchange.deferred.emitOpError(
|
||||||
|
"cannot form fragment collection for leaf ")
|
||||||
|
<< leafIndex
|
||||||
|
<< ": publication fragment types differ or are unranked";
|
||||||
|
RankedTensorType normalized = fragmentType;
|
||||||
|
if (leaf.form == DeferredLeafForm::GraphBatchProjection)
|
||||||
|
normalized = leaf.leadingRankReduced
|
||||||
|
? leaf.reconstructedType
|
||||||
|
: RankedTensorType::get(
|
||||||
|
leaf.reconstructedType.getShape().drop_front(),
|
||||||
|
leaf.reconstructedType.getElementType());
|
||||||
|
bool direct = positionCount == 1 && normalized == leaf.reconstructedType;
|
||||||
|
bool leading = leaf.reconstructedType.getRank() == normalized.getRank() + 1
|
||||||
|
&& leaf.reconstructedType.getDimSize(0) == positionCount
|
||||||
|
&& leaf.reconstructedType.getShape().drop_front()
|
||||||
|
== normalized.getShape();
|
||||||
|
if (!direct && !leading)
|
||||||
|
return exchange.deferred.emitOpError(
|
||||||
|
"cannot form fragment collection for leaf ")
|
||||||
|
<< leafIndex << ": " << positionCount << " fragments of "
|
||||||
|
<< normalized << " do not reconstruct " << leaf.reconstructedType;
|
||||||
|
FragmentCollectionKind kind = specializationCount == 1
|
||||||
|
? FragmentCollectionKind::Leaf
|
||||||
|
: FragmentCollectionKind::GroupedLeaf;
|
||||||
|
SmallVector<int64_t> shape;
|
||||||
|
if (specializationCount > 1)
|
||||||
|
shape.push_back(specializationCount);
|
||||||
|
llvm::append_range(shape, leaf.reconstructedType.getShape());
|
||||||
|
FragmentCollectionPlan collection;
|
||||||
|
collection.key = {&exchange, kind, static_cast<unsigned>(leafIndex)};
|
||||||
|
collection.collectionType = RankedTensorType::get(
|
||||||
|
shape, leaf.reconstructedType.getElementType());
|
||||||
|
collection.positionCount = positionCount;
|
||||||
|
for (RequirementFamily *requirement : requirements)
|
||||||
|
collection.requirements.push_back({
|
||||||
|
requirement,
|
||||||
|
requirement->coordinate.specializationIndex * positionCount
|
||||||
|
+ requirement->coordinate.selectedPosition});
|
||||||
|
if (failed(verifyCollectionCoverage(
|
||||||
|
exchange, collection, specializationCount * positionCount)))
|
||||||
|
return failure();
|
||||||
|
plan.collections.push_back(std::move(collection));
|
||||||
|
}
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool supportsAssemblyTransform(
|
||||||
|
Type publicationType, const DeferredInsertAssemblyEntryTemplate &entry) {
|
||||||
|
auto publication = dyn_cast<RankedTensorType>(publicationType);
|
||||||
|
RankedTensorType source = entry.sourceType;
|
||||||
|
if (!publication || !source)
|
||||||
|
return false;
|
||||||
|
switch (entry.sourceTransform) {
|
||||||
|
case DeferredAssemblySourceTransform::Identity:
|
||||||
|
return publication == source;
|
||||||
|
case DeferredAssemblySourceTransform::AddLeadingUnitDimension:
|
||||||
|
return source.getRank() == publication.getRank() + 1
|
||||||
|
&& source.getDimSize(0) == 1
|
||||||
|
&& source.getShape().drop_front() == publication.getShape();
|
||||||
|
case DeferredAssemblySourceTransform::RemoveLeadingUnitDimension:
|
||||||
|
return publication.getRank() == source.getRank() + 1
|
||||||
|
&& publication.getDimSize(0) == 1
|
||||||
|
&& publication.getShape().drop_front() == source.getShape();
|
||||||
|
}
|
||||||
|
llvm_unreachable("unknown deferred assembly source transform");
|
||||||
|
}
|
||||||
|
|
||||||
|
static LogicalResult buildInsertAssemblyCollection(
|
||||||
|
DeferredExchangePlan &exchange, DeferredResultPlan &plan) {
|
||||||
|
if (exchange.program.specializationCount != 1)
|
||||||
|
return exchange.deferred.emitOpError(
|
||||||
|
"grouped specialization insert assembly is unsupported");
|
||||||
|
const DeferredInsertAssemblyTemplate &assembly =
|
||||||
|
*exchange.program.insertAssembly;
|
||||||
|
FragmentCollectionPlan collection;
|
||||||
|
collection.key = {&exchange, FragmentCollectionKind::InsertAssembly, 0};
|
||||||
|
collection.collectionType = assembly.resultType;
|
||||||
|
collection.positionCount = assembly.entries.size();
|
||||||
|
llvm::DenseSet<RequirementFamily *> collected;
|
||||||
|
for (auto [position, entry] : llvm::enumerate(assembly.entries)) {
|
||||||
|
bool found = false;
|
||||||
|
for (RequirementFamily &requirement : exchange.requirements) {
|
||||||
|
if (!(requirement.coordinate == entry.coordinate))
|
||||||
|
continue;
|
||||||
|
if (!supportsAssemblyTransform(
|
||||||
|
requirement.publicationFragmentType, entry))
|
||||||
|
return exchange.deferred.emitOpError(
|
||||||
|
"insert assembly source transform does not match publication type at entry ")
|
||||||
|
<< position;
|
||||||
|
if (!collected.insert(&requirement).second)
|
||||||
|
return exchange.deferred.emitOpError(
|
||||||
|
"insert assembly requirement is owned by multiple entries at entry ")
|
||||||
|
<< position;
|
||||||
|
collection.requirements.push_back(
|
||||||
|
{&requirement, static_cast<unsigned>(position)});
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
if (!found)
|
||||||
|
return exchange.deferred.emitOpError(
|
||||||
|
"insert assembly entry has no requirement at entry ")
|
||||||
|
<< position;
|
||||||
|
}
|
||||||
|
if (collected.size() != exchange.requirements.size())
|
||||||
|
return exchange.deferred.emitOpError(
|
||||||
|
"insert assembly does not own every deferred requirement");
|
||||||
|
if (failed(verifyCollectionCoverage(
|
||||||
|
exchange, collection, assembly.entries.size())))
|
||||||
|
return failure();
|
||||||
|
plan.collections.push_back(std::move(collection));
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
using TemplateGeometryMember = SmallVector<OpFoldResult> DeferredSliceTemplate::*;
|
||||||
|
static constexpr std::array<TemplateGeometryMember, 3> templateGeometryMembers{
|
||||||
|
&DeferredSliceTemplate::offsets, &DeferredSliceTemplate::sizes,
|
||||||
|
&DeferredSliceTemplate::strides};
|
||||||
|
|
||||||
|
template <typename GetValue>
|
||||||
|
static FailureOr<StaticIntGrid> buildGrid(
|
||||||
|
const DeferredProgramTemplate &program, unsigned laneCount,
|
||||||
|
unsigned rowCount, bool specializeRows, GetValue getValue) {
|
||||||
|
SmallVector<StaticIntSequence> rows;
|
||||||
|
for (unsigned row = 0; row < rowCount; ++row) {
|
||||||
|
DeferredLaneValueEvaluator evaluator(
|
||||||
|
program, laneCount, specializeRows ? row : 0);
|
||||||
|
auto sequence = evaluator.evaluate(getValue(row));
|
||||||
|
if (failed(sequence)) return failure();
|
||||||
|
rows.push_back(std::move(*sequence));
|
||||||
|
}
|
||||||
|
return StaticIntGrid::fromRows(rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename GetGeometry>
|
||||||
|
static FailureOr<DeferredGridSliceGeometry> buildGeometryGrids(
|
||||||
|
const DeferredProgramTemplate &program, unsigned laneCount,
|
||||||
|
unsigned rowCount, bool specializeRows, GetGeometry getGeometry) {
|
||||||
|
DeferredGridSliceGeometry result;
|
||||||
|
const DeferredSliceTemplate &first = getGeometry(0);
|
||||||
|
for (auto [group, sourceMember] : llvm::enumerate(templateGeometryMembers)) {
|
||||||
|
TemplateGeometryMember member = sourceMember;
|
||||||
|
for (unsigned dimension = 0;
|
||||||
|
dimension < (first.*member).size(); ++dimension) {
|
||||||
|
auto grid = buildGrid(program, laneCount, rowCount, specializeRows,
|
||||||
|
[&](unsigned row) { return (getGeometry(row).*member)[dimension]; });
|
||||||
|
if (failed(grid)) return failure();
|
||||||
|
result[group].push_back(std::move(*grid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
static Value cloneResidual(DeferredExchangePlan &exchange, IRMapping &mapping, DeferredEmissionContext &context) {
|
||||||
|
for (Operation *op : exchange.program.residualOps) {
|
||||||
|
Operation *copy = context.rewriter.clone(*op, mapping);
|
||||||
|
for (auto [oldValue, newValue] : llvm::zip(op->getResults(), copy->getResults())) mapping.map(oldValue, newValue);
|
||||||
|
}
|
||||||
|
return mapping.lookupOrDefault(exchange.program.yieldedValue);
|
||||||
|
}
|
||||||
|
static FailureOr<Value> realizeOne(const DeferredResultPlan &plan, Value lane, DeferredEmissionContext &context) {
|
||||||
|
DeferredExchangePlan &exchange = *plan.exchange;
|
||||||
|
if (exchange.program.insertAssembly) {
|
||||||
|
Value collection = context.fragmentCollections.lookup(
|
||||||
|
{&exchange, FragmentCollectionKind::InsertAssembly, 0});
|
||||||
|
return collection && collection.getType() == exchange.program.insertAssembly->resultType
|
||||||
|
? FailureOr<Value>(collection) : FailureOr<Value>(failure());
|
||||||
|
}
|
||||||
|
IRMapping mapping;
|
||||||
|
if (exchange.program.scheduledLane) mapping.map(exchange.program.scheduledLane, lane);
|
||||||
|
for (auto &[value, grid] : plan.residualValues) {
|
||||||
|
if (mapping.contains(value)) continue;
|
||||||
|
Value selected = grid.emitLookup(
|
||||||
|
context.constants.getIndex(0),
|
||||||
|
lane ? lane : context.constants.getIndex(0), exchange.deferred,
|
||||||
|
context.constants, context.rewriter, exchange.deferred.getLoc());
|
||||||
|
if (!value.getType().isIndex()) selected = arith::IndexCastOp::create(context.rewriter, exchange.deferred.getLoc(), value.getType(), selected);
|
||||||
|
mapping.map(value, selected);
|
||||||
|
}
|
||||||
|
for (unsigned index = 0; index < exchange.program.leaves.size(); ++index) {
|
||||||
|
Value value = context.fragmentCollections.lookup(
|
||||||
|
{&exchange, FragmentCollectionKind::Leaf, index});
|
||||||
|
if (!value || value.getType() != exchange.program.leaves[index].reconstructedType) {
|
||||||
|
exchange.deferred.emitOpError("failed to reconstruct deferred result leaf ") << index;
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
mapping.map(exchange.program.leaves[index].replacementRoot, value);
|
||||||
|
}
|
||||||
|
Value result = cloneResidual(exchange, mapping, context);
|
||||||
|
Type expected = exchange.program.specializationCount > 1 ? Type(exchange.program.specializationFragmentType) : exchange.deferred.getOutput().getType();
|
||||||
|
return result && result.getType() == expected ? FailureOr<Value>(result) : FailureOr<Value>(failure());
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
FailureOr<DeferredResultPlan> buildDeferredResultPlan(DeferredExchangePlan &exchange) {
|
||||||
|
DeferredResultPlan plan;
|
||||||
|
plan.exchange = &exchange;
|
||||||
|
if (failed(exchange.program.insertAssembly
|
||||||
|
? buildInsertAssemblyCollection(exchange, plan)
|
||||||
|
: buildLeafCollections(exchange, plan))) return failure();
|
||||||
|
unsigned specializations = exchange.program.specializationCount;
|
||||||
|
for (const auto &leaf : exchange.program.leaves) {
|
||||||
|
auto geometry = buildGeometryGrids(exchange.program,
|
||||||
|
exchange.targetLaneCount, specializations, true,
|
||||||
|
[&](unsigned) -> const DeferredSliceTemplate & {
|
||||||
|
return leaf.innerGeometry;
|
||||||
|
});
|
||||||
|
if (failed(geometry)) return failure();
|
||||||
|
plan.innerGeometry.push_back(std::move(*geometry));
|
||||||
|
}
|
||||||
|
if (exchange.program.insertAssembly) {
|
||||||
|
const auto &entries = exchange.program.insertAssembly->entries;
|
||||||
|
auto geometry = buildGeometryGrids(exchange.program,
|
||||||
|
exchange.targetLaneCount, entries.size(), false,
|
||||||
|
[&](unsigned row) -> const DeferredSliceTemplate & {
|
||||||
|
return entries[row].targetGeometry;
|
||||||
|
});
|
||||||
|
if (failed(geometry)) return failure();
|
||||||
|
plan.assemblyGeometry = std::move(*geometry);
|
||||||
|
}
|
||||||
|
llvm::DenseSet<Value> residualValues;
|
||||||
|
for (Operation *op : exchange.program.residualOps)
|
||||||
|
op->walk([&](Operation *nested) {
|
||||||
|
for (Value operand : nested->getOperands())
|
||||||
|
if (operand.getType().isIndex() || isa<IntegerType>(operand.getType()))
|
||||||
|
residualValues.insert(operand);
|
||||||
|
});
|
||||||
|
for (Value value : residualValues) {
|
||||||
|
auto grid = buildGrid(exchange.program, exchange.targetLaneCount,
|
||||||
|
specializations, true,
|
||||||
|
[&](unsigned) { return OpFoldResult(value); });
|
||||||
|
if (succeeded(grid))
|
||||||
|
plan.residualValues.try_emplace(value, std::move(*grid));
|
||||||
|
}
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
FailureOr<Value> realizeDeferredResult(const DeferredResultPlan &plan, Value lane, DeferredEmissionContext &context) {
|
||||||
|
DeferredExchangePlan &exchange = *plan.exchange;
|
||||||
|
if (exchange.program.specializationCount == 1) return realizeOne(plan, lane, context);
|
||||||
|
auto outputType = dyn_cast<RankedTensorType>(exchange.deferred.getOutput().getType());
|
||||||
|
RankedTensorType fragmentType = exchange.program.specializationFragmentType;
|
||||||
|
if (!outputType || !fragmentType || outputType.getRank() != fragmentType.getRank() + 1 || outputType.getDimSize(0) != exchange.program.specializationCount || outputType.getShape().drop_front() != fragmentType.getShape()) return failure();
|
||||||
|
if (exchange.program.insertAssembly) return failure();
|
||||||
|
SmallVector<Value> leafStacks;
|
||||||
|
for (auto [index, leaf] : llvm::enumerate(exchange.program.leaves)) {
|
||||||
|
FragmentCollectionKey key{&exchange, FragmentCollectionKind::GroupedLeaf, static_cast<unsigned>(index)};
|
||||||
|
Value collection = context.fragmentCollections.lookup(key);
|
||||||
|
SmallVector<int64_t> shape{exchange.program.specializationCount};
|
||||||
|
llvm::append_range(shape, leaf.reconstructedType.getShape());
|
||||||
|
if (!collection || collection.getType() != RankedTensorType::get(shape, leaf.reconstructedType.getElementType())) return failure();
|
||||||
|
leafStacks.push_back(collection);
|
||||||
|
}
|
||||||
|
Location loc = exchange.deferred.getLoc();
|
||||||
|
Value initial = tensor::EmptyOp::create(context.rewriter, loc, outputType.getShape(), outputType.getElementType());
|
||||||
|
auto loop = buildNormalizedScfFor(context.rewriter, loc, context.constants.getIndex(0), context.constants.getIndex(exchange.program.specializationCount), context.constants.getIndex(1), ValueRange{initial},
|
||||||
|
[&](OpBuilder &, Location, Value specialization, ValueRange iterArgs, SmallVectorImpl<Value> &yielded) -> LogicalResult {
|
||||||
|
IRMapping mapping;
|
||||||
|
if (exchange.program.scheduledLane) mapping.map(exchange.program.scheduledLane, lane);
|
||||||
|
if (exchange.program.specializationArgument) mapping.map(exchange.program.specializationArgument, specialization);
|
||||||
|
Value runtimeLane = lane ? lane : context.constants.getIndex(0);
|
||||||
|
for (auto &[value, grid] : plan.residualValues) {
|
||||||
|
Value selected = grid.emitLookup(specialization, runtimeLane, exchange.deferred, context.constants, context.rewriter, loc);
|
||||||
|
if (!value.getType().isIndex()) selected = arith::IndexCastOp::create(context.rewriter, loc, value.getType(), selected);
|
||||||
|
mapping.map(value, selected);
|
||||||
|
}
|
||||||
|
for (auto [index, leaf] : llvm::enumerate(exchange.program.leaves)) { Value selected = extractMixedSliceOrIdentity(context.rewriter, loc, leafStacks[index], leaf.reconstructedType, leadingSlice(context.rewriter, leaf.reconstructedType, specialization)); if (!selected) return failure(); mapping.map(leaf.replacementRoot, selected); }
|
||||||
|
Value fragment = cloneResidual(exchange, mapping, context);
|
||||||
|
if (!fragment || fragment.getType() != fragmentType) return failure();
|
||||||
|
yielded.push_back(insertMixedSlice(context.rewriter, loc, fragment, iterArgs.front(), leadingSlice(context.rewriter, fragmentType, specialization)));
|
||||||
|
return success();
|
||||||
|
});
|
||||||
|
return failed(loop) || loop->results.size() != 1 ? FailureOr<Value>(failure()) : FailureOr<Value>(loop->results.front());
|
||||||
|
}
|
||||||
|
} // namespace onnx_mlir::spatial
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "DeferredCommunicationModel.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/StaticIntGrid.hpp"
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace onnx_mlir::spatial {
|
||||||
|
|
||||||
|
struct DeferredEmissionContext;
|
||||||
|
|
||||||
|
enum class FragmentCollectionKind {
|
||||||
|
Leaf,
|
||||||
|
GroupedLeaf,
|
||||||
|
InsertAssembly
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FragmentCollectionKey {
|
||||||
|
DeferredExchangePlan *exchange = nullptr;
|
||||||
|
FragmentCollectionKind kind = FragmentCollectionKind::Leaf;
|
||||||
|
unsigned leafIndex = 0;
|
||||||
|
|
||||||
|
bool operator==(const FragmentCollectionKey &other) const {
|
||||||
|
return exchange == other.exchange && kind == other.kind
|
||||||
|
&& leafIndex == other.leafIndex;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FragmentCollectionPlan {
|
||||||
|
FragmentCollectionKey key;
|
||||||
|
mlir::RankedTensorType collectionType;
|
||||||
|
unsigned positionCount = 0;
|
||||||
|
struct Requirement {
|
||||||
|
RequirementFamily *family = nullptr;
|
||||||
|
unsigned position = 0;
|
||||||
|
};
|
||||||
|
llvm::SmallVector<Requirement> requirements;
|
||||||
|
};
|
||||||
|
|
||||||
|
using DeferredGridSliceGeometry =
|
||||||
|
std::array<llvm::SmallVector<StaticIntGrid, 0>, 3>;
|
||||||
|
|
||||||
|
struct DeferredResultPlan {
|
||||||
|
DeferredExchangePlan* exchange = nullptr;
|
||||||
|
llvm::SmallVector<FragmentCollectionPlan, 0> collections;
|
||||||
|
llvm::SmallVector<DeferredGridSliceGeometry, 0> innerGeometry;
|
||||||
|
DeferredGridSliceGeometry assemblyGeometry;
|
||||||
|
llvm::DenseMap<mlir::Value, StaticIntGrid> residualValues;
|
||||||
|
};
|
||||||
|
|
||||||
|
mlir::FailureOr<DeferredResultPlan>
|
||||||
|
buildDeferredResultPlan(DeferredExchangePlan& exchange);
|
||||||
|
|
||||||
|
mlir::FailureOr<mlir::Value> realizeDeferredResult(const DeferredResultPlan& plan,
|
||||||
|
mlir::Value lane,
|
||||||
|
DeferredEmissionContext& context);
|
||||||
|
|
||||||
|
} // namespace onnx_mlir::spatial
|
||||||
@@ -0,0 +1,475 @@
|
|||||||
|
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/MapVector.h"
|
||||||
|
|
||||||
|
#include "DeferredProjectionAnalysis.hpp"
|
||||||
|
#include "DeferredTransferPlanning.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||||
|
|
||||||
|
namespace onnx_mlir::spatial {
|
||||||
|
using namespace mlir;
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
static FailureOr<unsigned> getStepIndex(
|
||||||
|
ScheduledInfo& info, SpatDeferredCommunicationOp deferred) {
|
||||||
|
Operation *position = deferred;
|
||||||
|
Block *block = info.blocks.front();
|
||||||
|
while (position && position->getBlock() != block)
|
||||||
|
position = position->getParentOp();
|
||||||
|
if (!position)
|
||||||
|
return failure();
|
||||||
|
for (unsigned step : llvm::reverse(
|
||||||
|
llvm::seq<unsigned>(0, info.stepAnchors.size())))
|
||||||
|
if (info.stepAnchors[step] == position
|
||||||
|
|| info.stepAnchors[step]->isBeforeInBlock(position))
|
||||||
|
return step;
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
static LogicalResult collectScheduledOperations(
|
||||||
|
const ScheduledComputeMaterializationResult &materialization,
|
||||||
|
DeferredTransferPlan &plan) {
|
||||||
|
unsigned nextStream = 0;
|
||||||
|
for (const ScheduledMaterializationRecord &record :
|
||||||
|
materialization.materializedSchedules) {
|
||||||
|
Operation &op = *record.scheduledOp;
|
||||||
|
ScheduledInfo info;
|
||||||
|
info.op = &op;
|
||||||
|
Region& body = isa<SpatScheduledCompute>(op) ? cast<SpatScheduledCompute>(op).getBody()
|
||||||
|
: cast<SpatScheduledComputeBatch>(op).getBody();
|
||||||
|
if (!body.hasOneBlock())
|
||||||
|
return op.emitOpError("phase 2 requires canonical one-block scheduled IR");
|
||||||
|
info.blocks.push_back(&body.front());
|
||||||
|
info.stepCount = record.runs.empty() ? record.stepPlans.size()
|
||||||
|
: record.runs.size();
|
||||||
|
info.stepAnchors = record.stepAnchors;
|
||||||
|
if (llvm::any_of(info.stepAnchors,
|
||||||
|
[](Operation *anchor) { return !anchor; }))
|
||||||
|
return op.emitOpError("phase 2 scheduled step anchor is missing");
|
||||||
|
for (size_t core : record.cpus)
|
||||||
|
info.cores.push_back(core);
|
||||||
|
for (size_t lane = 0; lane < info.cores.size(); ++lane)
|
||||||
|
info.streamIds.push_back(nextStream++);
|
||||||
|
plan.scheduled.push_back(std::move(info));
|
||||||
|
}
|
||||||
|
plan.stepCounts.resize(nextStream);
|
||||||
|
for (ScheduledInfo& info : plan.scheduled)
|
||||||
|
for (unsigned stream : info.streamIds)
|
||||||
|
plan.stepCounts[stream] = info.stepCount;
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
static LogicalResult collectProducedValues(
|
||||||
|
const ScheduledComputeMaterializationResult &materialization,
|
||||||
|
DeferredTransferPlan &plan) {
|
||||||
|
if (plan.scheduled.size() != materialization.materializedSchedules.size())
|
||||||
|
return failure();
|
||||||
|
for (auto [recordIndex, record] :
|
||||||
|
llvm::enumerate(materialization.materializedSchedules)) {
|
||||||
|
ScheduledInfo &info = plan.scheduled[recordIndex];
|
||||||
|
DenseMap<ComputeInstance, std::pair<unsigned, unsigned>> coordinates;
|
||||||
|
if (record.runs.empty()) {
|
||||||
|
for (auto [step, stepPlan] : llvm::enumerate(record.stepPlans))
|
||||||
|
for (auto [lane, instance] :
|
||||||
|
llvm::enumerate(stepPlan.stepTuple.instances))
|
||||||
|
coordinates[instance] = {step, lane};
|
||||||
|
} else {
|
||||||
|
for (auto [step, run] : llvm::enumerate(record.runs))
|
||||||
|
for (const ComputeInstance &instance : run.instances)
|
||||||
|
coordinates[instance] = {step, 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const MaterializedStepValue &value : record.stepValues) {
|
||||||
|
auto coordinate = coordinates.find(value.instance);
|
||||||
|
if (coordinate == coordinates.end()
|
||||||
|
|| coordinate->second.second >= info.cores.size()
|
||||||
|
|| value.graphId < 0 || value.laneStart < 0
|
||||||
|
|| value.laneCount <= 0 || !value.payload)
|
||||||
|
return info.op->emitOpError(
|
||||||
|
"phase 2 received an invalid scheduled materialization record");
|
||||||
|
unsigned step = coordinate->second.first;
|
||||||
|
unsigned lane = coordinate->second.second;
|
||||||
|
auto produced = std::make_unique<ProducedValue>(ProducedValue {
|
||||||
|
&info, step, value.resultIndex, value.graphId, info.cores[lane],
|
||||||
|
value.laneStart, value.laneCount, lane, value.payloadLaneStart,
|
||||||
|
value.payloadLaneCount, value.payload, value.published});
|
||||||
|
info.produced.push_back(produced.get());
|
||||||
|
plan.producedByGraph[produced->graphId].push_back(produced.get());
|
||||||
|
plan.producedStorage.push_back(std::move(produced));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<ProducedValue*> findProducer(DeferredTransferPlan& plan,
|
||||||
|
Operation* diagnosticOwner,
|
||||||
|
int64_t graphId,
|
||||||
|
unsigned resultIndex,
|
||||||
|
std::optional<int64_t> graphLane) {
|
||||||
|
ProducedValue* match = nullptr;
|
||||||
|
for (ProducedValue* produced : plan.producedByGraph.lookup(graphId)) {
|
||||||
|
if (produced->resultIndex != resultIndex
|
||||||
|
|| (graphLane && (*graphLane < produced->laneStart || *graphLane >= produced->laneStart + produced->laneCount)))
|
||||||
|
continue;
|
||||||
|
if (match)
|
||||||
|
return diagnosticOwner->emitOpError("phase 2 cannot uniquely resolve graph publication ownership"), failure();
|
||||||
|
match = produced;
|
||||||
|
}
|
||||||
|
if (!match)
|
||||||
|
return diagnosticOwner->emitOpError("phase 2 cannot map a graph lane to a scheduled producer"), failure();
|
||||||
|
if (graphLane) {
|
||||||
|
int64_t relative = *graphLane - match->laneStart;
|
||||||
|
if (relative < 0 || relative >= match->laneCount
|
||||||
|
|| relative >= match->publishedSlotCount)
|
||||||
|
return diagnosticOwner->emitOpError(
|
||||||
|
"phase 2 graph lane is outside its scheduled publication window"), failure();
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LogicalResult buildRequirementFamilies(DeferredTransferPlan& plan,
|
||||||
|
DeferredExchangePlan& exchange,
|
||||||
|
GraphBatchPublicationCache& publicationCache) {
|
||||||
|
for (unsigned specialization = 0;
|
||||||
|
specialization < exchange.program.specializationCount;
|
||||||
|
++specialization) {
|
||||||
|
DeferredLaneValueEvaluator evaluator(
|
||||||
|
exchange.program, exchange.targetLaneCount, specialization);
|
||||||
|
for (auto leafItem : llvm::enumerate(exchange.program.leaves)) {
|
||||||
|
unsigned leafIndex = leafItem.index();
|
||||||
|
const DeferredProjectionLeafTemplate &leaf = leafItem.value();
|
||||||
|
auto sourceIndices = evaluator.resolveSourceOperandIndices(leaf.sourceRoot);
|
||||||
|
if (failed(sourceIndices))
|
||||||
|
return failure();
|
||||||
|
DeferredStaticSliceGeometry geometry;
|
||||||
|
auto evaluate = [&](ArrayRef<OpFoldResult> values,
|
||||||
|
SmallVectorImpl<StaticIntSequence> &target) {
|
||||||
|
for (OpFoldResult value : values) {
|
||||||
|
auto sequence = evaluator.evaluate(value);
|
||||||
|
if (failed(sequence))
|
||||||
|
return failure();
|
||||||
|
target.push_back(std::move(*sequence));
|
||||||
|
}
|
||||||
|
return success();
|
||||||
|
};
|
||||||
|
if (failed(evaluate(leaf.leadingGeometry.offsets, geometry.offsets))
|
||||||
|
|| failed(evaluate(leaf.leadingGeometry.sizes, geometry.sizes))
|
||||||
|
|| failed(evaluate(leaf.leadingGeometry.strides, geometry.strides)))
|
||||||
|
return failure();
|
||||||
|
struct Source {
|
||||||
|
OpResult value;
|
||||||
|
int64_t graphId;
|
||||||
|
const GraphBatchPublicationMap *publication = nullptr;
|
||||||
|
ProducedValue *scalarProducer = nullptr;
|
||||||
|
};
|
||||||
|
SmallVector<Source> sources;
|
||||||
|
unsigned positionCount = 1;
|
||||||
|
for (unsigned lane = 0; lane < exchange.targetLaneCount; ++lane) {
|
||||||
|
Value value = exchange.deferred.getSources()[sourceIndices->valueAt(lane)];
|
||||||
|
while (auto selection = value.getDefiningOp<SpatDeferredSourceSelectOp>()) {
|
||||||
|
auto selectors = evaluator.evaluate(selection.getSelector());
|
||||||
|
if (failed(selectors))
|
||||||
|
return exchange.deferred.emitOpError(
|
||||||
|
"phase 2 cannot evaluate deferred source selection"), failure();
|
||||||
|
int64_t selected = selectors->valueAt(lane);
|
||||||
|
if (selected < 0 || selected >= static_cast<int64_t>(selection.getSources().size()))
|
||||||
|
return exchange.deferred.emitOpError(
|
||||||
|
"phase 2 deferred source selection is out of range"), failure();
|
||||||
|
value = selection.getSources()[selected];
|
||||||
|
}
|
||||||
|
auto source = dyn_cast<OpResult>(value);
|
||||||
|
if (!source)
|
||||||
|
return exchange.deferred.emitOpError(
|
||||||
|
"phase 2 requires graph-result deferred sources"), failure();
|
||||||
|
auto graphId = source.getOwner()->getAttrOfType<IntegerAttr>("scheduled.graph_id");
|
||||||
|
if (!graphId)
|
||||||
|
return exchange.deferred.emitOpError(
|
||||||
|
"phase 2 cannot identify graph producer"), failure();
|
||||||
|
Source resolved {source, graphId.getInt()};
|
||||||
|
if (auto batch = dyn_cast<SpatGraphComputeBatch>(source.getOwner())) {
|
||||||
|
auto publication = getGraphBatchPublicationMap(
|
||||||
|
batch, source.getResultNumber(), publicationCache);
|
||||||
|
if (failed(publication))
|
||||||
|
return failure();
|
||||||
|
if (leaf.form != DeferredLeafForm::GraphBatchProjection)
|
||||||
|
positionCount = std::max<unsigned>(
|
||||||
|
positionCount, (*publication)->physicalSlotToGraphLane.size());
|
||||||
|
}
|
||||||
|
else if (isa<SpatGraphCompute>(source.getOwner())) {
|
||||||
|
auto producer = findProducer(plan, exchange.deferred, graphId.getInt(),
|
||||||
|
source.getResultNumber(), std::nullopt);
|
||||||
|
if (failed(producer))
|
||||||
|
return failure();
|
||||||
|
resolved.scalarProducer = *producer;
|
||||||
|
}
|
||||||
|
sources.push_back(resolved);
|
||||||
|
}
|
||||||
|
for (Source &source : sources)
|
||||||
|
if (auto batch = dyn_cast<SpatGraphComputeBatch>(source.value.getOwner()))
|
||||||
|
source.publication = &publicationCache.find(
|
||||||
|
{batch, source.value.getResultNumber()})->second;
|
||||||
|
if (leaf.form == DeferredLeafForm::GraphBatchProjection)
|
||||||
|
for (unsigned lane = 0; lane < exchange.targetLaneCount; ++lane)
|
||||||
|
positionCount = std::max<unsigned>(
|
||||||
|
positionCount, geometry.sizes.front().valueAt(lane));
|
||||||
|
|
||||||
|
for (unsigned position = 0; position < positionCount; ++position) {
|
||||||
|
SmallVector<ProducedValue *> producers(exchange.targetLaneCount);
|
||||||
|
SmallVector<Type> fragmentTypes(exchange.targetLaneCount);
|
||||||
|
SmallVector<int64_t> graphLanes(exchange.targetLaneCount, -1);
|
||||||
|
SmallVector<int64_t> localOffsets(exchange.targetLaneCount, -1);
|
||||||
|
for (unsigned lane = 0; lane < exchange.targetLaneCount; ++lane) {
|
||||||
|
Source source = sources[lane];
|
||||||
|
if (source.publication) {
|
||||||
|
int64_t slot = position;
|
||||||
|
if (leaf.form == DeferredLeafForm::GraphBatchProjection) {
|
||||||
|
if (position >= geometry.sizes.front().valueAt(lane))
|
||||||
|
continue;
|
||||||
|
slot = geometry.offsets.front().valueAt(lane)
|
||||||
|
+ static_cast<int64_t>(position)
|
||||||
|
* geometry.strides.front().valueAt(lane);
|
||||||
|
}
|
||||||
|
else if (position >= source.publication->physicalSlotToGraphLane.size())
|
||||||
|
continue;
|
||||||
|
if (slot < 0 || slot >= static_cast<int64_t>(
|
||||||
|
source.publication->physicalSlotToGraphLane.size()))
|
||||||
|
return exchange.deferred.emitOpError(
|
||||||
|
"projection physical slot is outside publication map"),
|
||||||
|
failure();
|
||||||
|
graphLanes[lane] = source.publication->physicalSlotToGraphLane[slot];
|
||||||
|
fragmentTypes[lane] = source.publication->publicationFragmentType;
|
||||||
|
auto producer = findProducer(
|
||||||
|
plan, exchange.deferred, source.graphId,
|
||||||
|
source.value.getResultNumber(),
|
||||||
|
graphLanes[lane]);
|
||||||
|
if (failed(producer))
|
||||||
|
return failure();
|
||||||
|
producers[lane] = *producer;
|
||||||
|
localOffsets[lane] = graphLanes[lane] - (*producer)->laneStart;
|
||||||
|
if (!(*producer)->scheduled->isBatch())
|
||||||
|
localOffsets[lane] += (*producer)->publishedSlotStart;
|
||||||
|
}
|
||||||
|
else if (position == 0 && source.scalarProducer) {
|
||||||
|
fragmentTypes[lane] = leaf.form == DeferredLeafForm::ScalarProjection
|
||||||
|
? Type(leaf.reconstructedType)
|
||||||
|
: source.value.getType();
|
||||||
|
producers[lane] = source.scalarProducer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StaticIntSequence graphLaneSequence =
|
||||||
|
StaticIntSequence::fromValues(graphLanes);
|
||||||
|
StaticIntSequence localOffsetSequence =
|
||||||
|
StaticIntSequence::fromValues(localOffsets);
|
||||||
|
for (unsigned begin = 0; begin < exchange.targetLaneCount;) {
|
||||||
|
if (!producers[begin]) {
|
||||||
|
++begin;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
unsigned end = begin + 1;
|
||||||
|
while (end < exchange.targetLaneCount
|
||||||
|
&& producers[end] == producers[begin]
|
||||||
|
&& fragmentTypes[end] == fragmentTypes[begin])
|
||||||
|
++end;
|
||||||
|
RequirementFamily family;
|
||||||
|
family.exchange = &exchange;
|
||||||
|
family.coordinate = {specialization, leafIndex, position};
|
||||||
|
family.targetLanes = LaneSet::range(begin, end);
|
||||||
|
family.producer = producers[begin];
|
||||||
|
family.publicationFragmentType = fragmentTypes[begin];
|
||||||
|
if (graphLanes[begin] >= 0) {
|
||||||
|
family.graphLanes = graphLaneSequence.slice(begin, end - begin);
|
||||||
|
family.producerLocalOffsets =
|
||||||
|
localOffsetSequence.slice(begin, end - begin);
|
||||||
|
}
|
||||||
|
else if (leaf.form == DeferredLeafForm::ScalarProjection) {
|
||||||
|
family.producerProjection.emplace();
|
||||||
|
auto slice = [&](ArrayRef<StaticIntSequence> source,
|
||||||
|
SmallVectorImpl<StaticIntSequence> &target) {
|
||||||
|
for (const StaticIntSequence &sequence : source)
|
||||||
|
target.push_back(sequence.slice(begin, end - begin));
|
||||||
|
};
|
||||||
|
slice(geometry.offsets, family.producerProjection->offsets);
|
||||||
|
slice(geometry.sizes, family.producerProjection->sizes);
|
||||||
|
slice(geometry.strides, family.producerProjection->strides);
|
||||||
|
}
|
||||||
|
exchange.requirements.push_back(std::move(family));
|
||||||
|
begin = end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void buildAvailabilityFamilies(DeferredExchangePlan& exchange, uint64_t& nextChannel) {
|
||||||
|
for (RequirementFamily& requirement : exchange.requirements) {
|
||||||
|
for (LaneInterval interval : requirement.targetLanes.intervals()) {
|
||||||
|
unsigned runBegin = interval.begin;
|
||||||
|
bool runLocal = false;
|
||||||
|
bool haveRun = false;
|
||||||
|
auto flush = [&](unsigned end) {
|
||||||
|
if (!haveRun || runBegin == end)
|
||||||
|
return;
|
||||||
|
LaneSet lanes = LaneSet::range(runBegin, end);
|
||||||
|
if (runLocal) {
|
||||||
|
exchange.local.push_back({&requirement, lanes});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
size_t count = end - runBegin;
|
||||||
|
unsigned sourceStream = requirement.producer->scheduled->streamIds[requirement.producer->scheduledLane];
|
||||||
|
SmallVector<int64_t> targetStreams, targetCores;
|
||||||
|
for (unsigned lane = runBegin; lane < end; ++lane) {
|
||||||
|
targetStreams.push_back(exchange.target->streamIds[lane]);
|
||||||
|
targetCores.push_back(exchange.target->cores[lane]);
|
||||||
|
}
|
||||||
|
ExternalTransferFamily family;
|
||||||
|
family.requirement = &requirement;
|
||||||
|
family.targetLanes = lanes;
|
||||||
|
family.sourceScheduled = requirement.producer->scheduled;
|
||||||
|
family.targetScheduled = exchange.target;
|
||||||
|
family.sourceStreams = StaticIntSequence::uniform(sourceStream, count);
|
||||||
|
family.targetStreams = StaticIntSequence::fromValues(targetStreams);
|
||||||
|
family.sourceCores = StaticIntSequence::uniform(requirement.producer->core, count);
|
||||||
|
family.targetCores = StaticIntSequence::fromValues(targetCores);
|
||||||
|
family.channelIds = StaticIntSequence::affine(nextChannel, 1, count);
|
||||||
|
nextChannel += count;
|
||||||
|
exchange.externalTransferCount += count;
|
||||||
|
exchange.external.push_back(std::move(family));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (unsigned lane = interval.begin; lane < interval.end; ++lane) {
|
||||||
|
unsigned sourceStream = requirement.producer->scheduled->streamIds[requirement.producer->scheduledLane];
|
||||||
|
bool local =
|
||||||
|
sourceStream == exchange.target->streamIds[lane] && requirement.producer->step < exchange.consumerStep;
|
||||||
|
if (haveRun && local != runLocal) {
|
||||||
|
flush(lane);
|
||||||
|
runBegin = lane;
|
||||||
|
}
|
||||||
|
runLocal = local;
|
||||||
|
haveRun = true;
|
||||||
|
}
|
||||||
|
flush(interval.end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static LogicalResult buildExchanges(func::FuncOp funcOp, DeferredTransferPlan& plan) {
|
||||||
|
DenseMap<Operation*, ScheduledInfo*> scheduledByOp;
|
||||||
|
for (ScheduledInfo& scheduled : plan.scheduled)
|
||||||
|
scheduledByOp[scheduled.op] = &scheduled;
|
||||||
|
SmallVector<SpatDeferredCommunicationOp> deferredOps;
|
||||||
|
funcOp.walk([&](SpatDeferredCommunicationOp op) { deferredOps.push_back(op); });
|
||||||
|
GraphBatchPublicationCache publicationCache;
|
||||||
|
uint64_t nextChannel = 0;
|
||||||
|
for (SpatDeferredCommunicationOp deferred : deferredOps) {
|
||||||
|
Operation* targetOp = deferred->getParentOfType<SpatScheduledCompute>();
|
||||||
|
if (!targetOp)
|
||||||
|
targetOp = deferred->getParentOfType<SpatScheduledComputeBatch>();
|
||||||
|
ScheduledInfo* target = scheduledByOp.lookup(targetOp);
|
||||||
|
auto step = target ? getStepIndex(*target, deferred)
|
||||||
|
: FailureOr<unsigned>(failure());
|
||||||
|
auto program = analyzeDeferredProgramTemplate(deferred);
|
||||||
|
if (!target || failed(step) || failed(program))
|
||||||
|
return deferred.emitOpError("phase 2 cannot normalize deferred communication");
|
||||||
|
auto exchange = std::make_unique<DeferredExchangePlan>();
|
||||||
|
exchange->exchangeId = plan.exchanges.size();
|
||||||
|
exchange->deferred = deferred;
|
||||||
|
exchange->target = target;
|
||||||
|
exchange->consumerStep = *step;
|
||||||
|
exchange->targetLaneCount = target->cores.size();
|
||||||
|
exchange->program = std::move(*program);
|
||||||
|
if (failed(buildRequirementFamilies(plan, *exchange, publicationCache)))
|
||||||
|
return failure();
|
||||||
|
buildAvailabilityFamilies(*exchange, nextChannel);
|
||||||
|
plan.exchanges.push_back(std::move(exchange));
|
||||||
|
}
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
static LogicalResult
|
||||||
|
retargetBlueprint(DeferredTransferPlan& plan, SpatBlueprintOp blueprint, GraphBatchPublicationCache& publicationCache) {
|
||||||
|
if (blueprint.getMode() != "fragment_assembly")
|
||||||
|
return success();
|
||||||
|
bool escapesScheduledGraph = llvm::any_of(
|
||||||
|
blueprint.getOutput().getUses(), [](OpOperand &use) {
|
||||||
|
return !isa<SpatGraphCompute, SpatGraphComputeBatch,
|
||||||
|
SpatDeferredCommunicationOp>(use.getOwner());
|
||||||
|
});
|
||||||
|
if (!escapesScheduledGraph)
|
||||||
|
return success();
|
||||||
|
auto operandIndices = blueprint.getFragmentOperandIndices();
|
||||||
|
auto sourceSlots = blueprint.getFragmentSourceSlots();
|
||||||
|
if (!operandIndices || !sourceSlots)
|
||||||
|
return blueprint.emitOpError("phase 2 requires explicit Blueprint fragment ownership");
|
||||||
|
SmallVector<Value> oldOperands {blueprint.getInput()};
|
||||||
|
llvm::append_range(oldOperands, blueprint.getFragments());
|
||||||
|
SmallVector<Value> publications;
|
||||||
|
SmallVector<int64_t> newOperands, newSlots;
|
||||||
|
for (auto [fragmentIndex, operandIndex] : llvm::enumerate(*operandIndices)) {
|
||||||
|
Value source = oldOperands[operandIndex];
|
||||||
|
auto result = dyn_cast<OpResult>(source);
|
||||||
|
auto batch = result ? dyn_cast<SpatGraphComputeBatch>(result.getOwner()) : SpatGraphComputeBatch();
|
||||||
|
int64_t slot = (*sourceSlots)[fragmentIndex];
|
||||||
|
if (batch) {
|
||||||
|
auto graphId = batch->getAttrOfType<IntegerAttr>("scheduled.graph_id");
|
||||||
|
auto map = getGraphBatchPublicationMap(batch, result.getResultNumber(), publicationCache);
|
||||||
|
if (!graphId || failed(map) || slot < 0 || slot >= static_cast<int64_t>((*map)->physicalSlotToGraphLane.size()))
|
||||||
|
return blueprint.emitOpError("phase 2 cannot resolve Blueprint fragment ownership");
|
||||||
|
int64_t graphLane = (*map)->physicalSlotToGraphLane[slot];
|
||||||
|
auto producer = findProducer(plan, blueprint, graphId.getInt(), result.getResultNumber(), graphLane);
|
||||||
|
if (failed(producer))
|
||||||
|
return failure();
|
||||||
|
if (!(*producer)->published)
|
||||||
|
return blueprint.emitOpError(
|
||||||
|
"phase 2 Blueprint source has no scheduled publication"), failure();
|
||||||
|
source = (*producer)->published;
|
||||||
|
slot = (*producer)->publishedSlotStart + graphLane - (*producer)->laneStart;
|
||||||
|
if (slot < (*producer)->publishedSlotStart
|
||||||
|
|| slot >= (*producer)->publishedSlotStart
|
||||||
|
+ (*producer)->publishedSlotCount)
|
||||||
|
return blueprint.emitOpError(
|
||||||
|
"phase 2 Blueprint slot is outside its scheduled publication window"), failure();
|
||||||
|
}
|
||||||
|
if (Operation* producer = source.getDefiningOp();
|
||||||
|
producer && blueprint->getBlock() == producer->getBlock() && blueprint->isBeforeInBlock(producer))
|
||||||
|
blueprint->moveAfter(producer);
|
||||||
|
auto it = llvm::find(publications, source);
|
||||||
|
if (it == publications.end()) {
|
||||||
|
publications.push_back(source);
|
||||||
|
it = std::prev(publications.end());
|
||||||
|
}
|
||||||
|
newOperands.push_back(std::distance(publications.begin(), it));
|
||||||
|
newSlots.push_back(slot);
|
||||||
|
}
|
||||||
|
blueprint->setOperands(publications);
|
||||||
|
OpBuilder builder(blueprint);
|
||||||
|
blueprint->setAttr("fragmentOperandIndices", builder.getDenseI64ArrayAttr(newOperands));
|
||||||
|
blueprint->setAttr("fragmentSourceSlots", builder.getDenseI64ArrayAttr(newSlots));
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
FailureOr<DeferredTransferPlan> buildDeferredTransferPlan(
|
||||||
|
func::FuncOp funcOp,
|
||||||
|
const ScheduledComputeMaterializationResult &materialization) {
|
||||||
|
DeferredTransferPlan plan;
|
||||||
|
if (failed(collectScheduledOperations(materialization, plan))
|
||||||
|
|| failed(collectProducedValues(materialization, plan))
|
||||||
|
|| failed(buildExchanges(funcOp, plan)))
|
||||||
|
return failure();
|
||||||
|
return std::move(plan);
|
||||||
|
}
|
||||||
|
|
||||||
|
LogicalResult retargetDeferredPublications(func::FuncOp funcOp, DeferredTransferPlan& plan) {
|
||||||
|
GraphBatchPublicationCache publicationCache;
|
||||||
|
SmallVector<SpatBlueprintOp> blueprints;
|
||||||
|
funcOp.walk([&](SpatBlueprintOp blueprint) { blueprints.push_back(blueprint); });
|
||||||
|
for (SpatBlueprintOp blueprint : blueprints)
|
||||||
|
if (failed(retargetBlueprint(plan, blueprint, publicationCache)))
|
||||||
|
return failure();
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace onnx_mlir::spatial
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
||||||
|
|
||||||
|
#include "DeferredCommunicationModel.hpp"
|
||||||
|
#include "ScheduledComputeMaterialization.hpp"
|
||||||
|
|
||||||
|
namespace onnx_mlir::spatial {
|
||||||
|
|
||||||
|
struct DeferredTransferPlan {
|
||||||
|
llvm::SmallVector<ScheduledInfo, 0> scheduled;
|
||||||
|
llvm::SmallVector<std::unique_ptr<ProducedValue>> producedStorage;
|
||||||
|
llvm::DenseMap<int64_t, llvm::SmallVector<ProducedValue*>> producedByGraph;
|
||||||
|
llvm::SmallVector<std::unique_ptr<DeferredExchangePlan>> exchanges;
|
||||||
|
llvm::SmallVector<unsigned> stepCounts;
|
||||||
|
};
|
||||||
|
|
||||||
|
mlir::FailureOr<DeferredTransferPlan>
|
||||||
|
buildDeferredTransferPlan(mlir::func::FuncOp funcOp,
|
||||||
|
const ScheduledComputeMaterializationResult &materialization);
|
||||||
|
|
||||||
|
mlir::LogicalResult retargetDeferredPublications(mlir::func::FuncOp funcOp, DeferredTransferPlan& plan);
|
||||||
|
|
||||||
|
} // namespace onnx_mlir::spatial
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
#include "HostOutputFinalization.hpp"
|
|
||||||
|
|
||||||
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
|
||||||
#include "mlir/IR/BuiltinTypes.h"
|
|
||||||
#include "mlir/IR/PatternMatch.h"
|
|
||||||
|
|
||||||
#include "llvm/ADT/DenseMap.h"
|
|
||||||
#include "llvm/ADT/DenseSet.h"
|
|
||||||
#include "llvm/ADT/STLExtras.h"
|
|
||||||
|
|
||||||
#include "MaterializedClassState.hpp"
|
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
|
||||||
|
|
||||||
using namespace mlir;
|
|
||||||
|
|
||||||
namespace onnx_mlir::spatial {
|
|
||||||
|
|
||||||
LogicalResult finalizeProjectedHostOutputFragments(MaterializerState& state) {
|
|
||||||
if (state.pendingProjectedHostOutputFragments.empty())
|
|
||||||
return success();
|
|
||||||
|
|
||||||
DenseMap<Value, SmallVector<PendingProjectedHostOutputFragment*, 16>> byOutput;
|
|
||||||
for (PendingProjectedHostOutputFragment& fragment : state.pendingProjectedHostOutputFragments)
|
|
||||||
byOutput[fragment.originalOutput].push_back(&fragment);
|
|
||||||
|
|
||||||
SmallVector<Value, 8> outputs;
|
|
||||||
outputs.reserve(byOutput.size());
|
|
||||||
|
|
||||||
auto returnOp = dyn_cast<func::ReturnOp>(state.func.getBody().front().getTerminator());
|
|
||||||
if (!returnOp)
|
|
||||||
return state.func.emitError("expected func.return terminator while finalizing projected host output fragments");
|
|
||||||
|
|
||||||
DenseSet<Value> seenOutputs;
|
|
||||||
for (Value returned : returnOp.getOperands()) {
|
|
||||||
if (!byOutput.contains(returned) || !seenOutputs.insert(returned).second)
|
|
||||||
continue;
|
|
||||||
outputs.push_back(returned);
|
|
||||||
}
|
|
||||||
if (outputs.size() != byOutput.size())
|
|
||||||
return state.func.emitError("projected host output fragments must be keyed by returned logical host outputs");
|
|
||||||
|
|
||||||
for (Value originalOutput : outputs) {
|
|
||||||
if (isa_and_present<SpatScheduledCompute, SpatScheduledComputeBatch>(originalOutput.getDefiningOp())) {
|
|
||||||
return state.func.emitError(
|
|
||||||
"projected host output assembly must be keyed by the original logical host output, not by a materialized scheduled result");
|
|
||||||
}
|
|
||||||
|
|
||||||
auto resultType = dyn_cast<RankedTensorType>(originalOutput.getType());
|
|
||||||
if (!resultType || !resultType.hasStaticShape())
|
|
||||||
return state.func.emitError("projected host output must have static ranked tensor type");
|
|
||||||
|
|
||||||
SmallVector<PendingProjectedHostOutputFragment*, 16>& fragments = byOutput[originalOutput];
|
|
||||||
llvm::sort(fragments, [](const PendingProjectedHostOutputFragment* lhs,
|
|
||||||
const PendingProjectedHostOutputFragment* rhs) {
|
|
||||||
if (lhs->sourceClass != rhs->sourceClass)
|
|
||||||
return lhs->sourceClass < rhs->sourceClass;
|
|
||||||
if (lhs->publicationResultIndex != rhs->publicationResultIndex)
|
|
||||||
return lhs->publicationResultIndex < rhs->publicationResultIndex;
|
|
||||||
if (lhs->sourceFragmentOrdinal != rhs->sourceFragmentOrdinal)
|
|
||||||
return lhs->sourceFragmentOrdinal < rhs->sourceFragmentOrdinal;
|
|
||||||
return std::lexicographical_compare(lhs->offsets.begin(),
|
|
||||||
lhs->offsets.end(),
|
|
||||||
rhs->offsets.begin(),
|
|
||||||
rhs->offsets.end());
|
|
||||||
});
|
|
||||||
|
|
||||||
state.rewriter.setInsertionPoint(returnOp);
|
|
||||||
Location loc = fragments.front()->loc;
|
|
||||||
SmallVector<Value, 16> blueprintOperands;
|
|
||||||
SmallVector<int64_t, 16> fragmentOperandIndices;
|
|
||||||
SmallVector<int64_t, 16> fragmentSourceOffsets;
|
|
||||||
SmallVector<int64_t, 64> flatOffsets;
|
|
||||||
SmallVector<int64_t, 64> flatSizes;
|
|
||||||
SmallVector<int64_t, 64> flatStrides;
|
|
||||||
DenseMap<Value, int64_t> operandIndicesByValue;
|
|
||||||
|
|
||||||
for (PendingProjectedHostOutputFragment* fragmentRecord : fragments) {
|
|
||||||
if (fragmentRecord->sourceClass >= state.classes.size())
|
|
||||||
return state.func.emitError("projected host output fragment references an invalid source class");
|
|
||||||
|
|
||||||
MaterializedClass& sourceClass = state.classes[fragmentRecord->sourceClass];
|
|
||||||
if (fragmentRecord->publicationResultIndex >= sourceClass.op->getNumResults()) {
|
|
||||||
return sourceClass.op->emitError("projected host output fragment references an invalid publication result")
|
|
||||||
<< " sourceClass=" << sourceClass.id
|
|
||||||
<< " resultIndex=" << fragmentRecord->publicationResultIndex
|
|
||||||
<< " resultCount=" << sourceClass.op->getNumResults();
|
|
||||||
}
|
|
||||||
|
|
||||||
Value operand = sourceClass.op->getResult(fragmentRecord->publicationResultIndex);
|
|
||||||
auto [operandIt, inserted] =
|
|
||||||
operandIndicesByValue.try_emplace(operand, static_cast<int64_t>(blueprintOperands.size()));
|
|
||||||
if (inserted)
|
|
||||||
blueprintOperands.push_back(operand);
|
|
||||||
fragmentOperandIndices.push_back(operandIt->second);
|
|
||||||
fragmentSourceOffsets.push_back(fragmentRecord->sourceElementOffset);
|
|
||||||
llvm::append_range(flatOffsets, fragmentRecord->offsets);
|
|
||||||
llvm::append_range(flatSizes, fragmentRecord->sizes);
|
|
||||||
llvm::append_range(flatStrides, fragmentRecord->strides);
|
|
||||||
|
|
||||||
auto operandType = dyn_cast<RankedTensorType>(operand.getType());
|
|
||||||
if (!operandType || !operandType.hasStaticShape())
|
|
||||||
return state.func.emitError("projected host output assembly requires static ranked tensor operands");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blueprintOperands.empty())
|
|
||||||
return state.func.emitError("missing projected host output fragments");
|
|
||||||
|
|
||||||
Value input = blueprintOperands.front();
|
|
||||||
ValueRange extraFragments = ValueRange(blueprintOperands).drop_front();
|
|
||||||
auto blueprint = SpatBlueprintOp::create(
|
|
||||||
state.rewriter,
|
|
||||||
loc,
|
|
||||||
resultType,
|
|
||||||
input,
|
|
||||||
extraFragments,
|
|
||||||
state.rewriter.getStringAttr("nchw"),
|
|
||||||
state.rewriter.getStringAttr("fragmented"),
|
|
||||||
state.rewriter.getDenseI64ArrayAttr(flatOffsets),
|
|
||||||
state.rewriter.getDenseI64ArrayAttr(flatSizes),
|
|
||||||
state.rewriter.getStringAttr("identity"),
|
|
||||||
state.rewriter.getStringAttr("fragment_assembly"),
|
|
||||||
state.rewriter.getDenseI64ArrayAttr(fragmentOperandIndices),
|
|
||||||
state.rewriter.getDenseI64ArrayAttr(fragmentSourceOffsets),
|
|
||||||
state.rewriter.getDenseI64ArrayAttr(flatStrides),
|
|
||||||
state.rewriter.getStringAttr("disjoint"),
|
|
||||||
state.rewriter.getStringAttr("complete"));
|
|
||||||
|
|
||||||
state.hostReplacements[originalOutput] = blueprint.getOutput();
|
|
||||||
}
|
|
||||||
|
|
||||||
return success();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace onnx_mlir::spatial
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "mlir/Support/LogicalResult.h"
|
|
||||||
|
|
||||||
namespace onnx_mlir::spatial {
|
|
||||||
|
|
||||||
struct MaterializerState;
|
|
||||||
|
|
||||||
mlir::LogicalResult finalizeProjectedHostOutputFragments(MaterializerState& state);
|
|
||||||
|
|
||||||
} // namespace onnx_mlir::spatial
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,17 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
|
||||||
#include "mlir/Support/LogicalResult.h"
|
|
||||||
|
|
||||||
#include "Scheduling/MergeSchedule.hpp"
|
|
||||||
|
|
||||||
namespace onnx_mlir {
|
|
||||||
namespace spatial {
|
|
||||||
|
|
||||||
class MergeScheduleMaterializer {
|
|
||||||
public:
|
|
||||||
mlir::LogicalResult run(mlir::func::FuncOp func, const MergeScheduleResult& schedule, int64_t& nextChannelId);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace spatial
|
|
||||||
} // namespace onnx_mlir
|
|
||||||
@@ -1,252 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
|
||||||
#include "mlir/IR/PatternMatch.h"
|
|
||||||
#include "mlir/Transforms/FoldUtils.h"
|
|
||||||
|
|
||||||
#include "llvm/ADT/ArrayRef.h"
|
|
||||||
#include "llvm/ADT/DenseMap.h"
|
|
||||||
#include "llvm/ADT/DenseSet.h"
|
|
||||||
#include "llvm/ADT/SmallVector.h"
|
|
||||||
|
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
#include "MaterializeMergeSchedule.hpp"
|
|
||||||
#include "MergeMessages.hpp"
|
|
||||||
#include "MergeScheduleKeys.hpp"
|
|
||||||
#include "ProjectedFragments.hpp"
|
|
||||||
#include "Scheduling/ComputeInstanceUtils.hpp"
|
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
|
||||||
|
|
||||||
namespace onnx_mlir::spatial {
|
|
||||||
|
|
||||||
struct MaterializedClass {
|
|
||||||
ClassId id = 0;
|
|
||||||
llvm::SmallVector<CpuId, 8> cpus;
|
|
||||||
mlir::Operation* op = nullptr;
|
|
||||||
mlir::Block* body = nullptr;
|
|
||||||
bool isBatch = false;
|
|
||||||
|
|
||||||
llvm::DenseMap<CpuId, unsigned> cpuToLane;
|
|
||||||
llvm::SmallVector<mlir::Value, 8> weights;
|
|
||||||
llvm::SmallVector<mlir::Value, 8> inputs;
|
|
||||||
llvm::SmallVector<mlir::Value, 4> hostOutputs;
|
|
||||||
llvm::DenseMap<mlir::Value, unsigned> publicationOutputToResultIndex;
|
|
||||||
llvm::DenseMap<mlir::Value, mlir::BlockArgument> weightArgs;
|
|
||||||
llvm::DenseMap<mlir::Value, mlir::BlockArgument> inputArgs;
|
|
||||||
llvm::DenseMap<mlir::Value, unsigned> hostOutputToResultIndex;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PackedScalarRunSlot {
|
|
||||||
llvm::SmallVector<ProducerKey, 8> keys;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class PackedScalarRunKind {
|
|
||||||
Materialized,
|
|
||||||
DeferredReceive,
|
|
||||||
DeferredLocalCompute
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PackedScalarRunValue {
|
|
||||||
ClassId targetClass = 0;
|
|
||||||
mlir::Operation* sourceOp = nullptr;
|
|
||||||
size_t resultIndex = 0;
|
|
||||||
PackedScalarRunKind kind = PackedScalarRunKind::Materialized;
|
|
||||||
|
|
||||||
mlir::Value packed;
|
|
||||||
|
|
||||||
mlir::RankedTensorType fragmentType;
|
|
||||||
llvm::SmallVector<PackedScalarRunSlot, 8> slots;
|
|
||||||
MessageVector messages;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct IndexedBatchRunValue {
|
|
||||||
ClassId targetClass = 0;
|
|
||||||
mlir::Operation* sourceOp = nullptr;
|
|
||||||
size_t resultIndex = 0;
|
|
||||||
mlir::Value packed;
|
|
||||||
mlir::RankedTensorType fragmentType;
|
|
||||||
llvm::SmallVector<PackedScalarRunSlot, 8> slots;
|
|
||||||
MessageVector messages;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct LogicalSlotRange {
|
|
||||||
SlotId start = 0;
|
|
||||||
SlotId count = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MaterializationRunSlot {
|
|
||||||
llvm::SmallVector<ComputeInstance, 8> peers;
|
|
||||||
};
|
|
||||||
|
|
||||||
using MaterializationRun = llvm::SmallVector<MaterializationRunSlot, 8>;
|
|
||||||
|
|
||||||
struct OutputDestinationGroup {
|
|
||||||
llvm::SmallVector<size_t, 4> resultIndices;
|
|
||||||
llvm::SmallVector<ClassId, 4> destinationClasses;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct BatchRunSendPlan {
|
|
||||||
size_t resultIndex = 0;
|
|
||||||
ClassId destinationClass = 0;
|
|
||||||
MessageVector messages;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class TensorDemandActionKind {
|
|
||||||
DestinationFanout,
|
|
||||||
SameClassIndexedFragment,
|
|
||||||
TerminalBlueprintPublication,
|
|
||||||
WholeTensorBarrier
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class WholeTensorBarrierReason {
|
|
||||||
FunctionReturnWithoutBlueprint,
|
|
||||||
DenseLogicalConsumer
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TensorDemandAction {
|
|
||||||
TensorDemandActionKind kind = TensorDemandActionKind::DestinationFanout;
|
|
||||||
std::optional<ClassId> destinationClass;
|
|
||||||
std::optional<WholeTensorBarrierReason> barrierReason;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RunOutputDemand {
|
|
||||||
size_t resultIndex = 0;
|
|
||||||
mlir::Value originalOutput;
|
|
||||||
mlir::RankedTensorType fragmentType;
|
|
||||||
llvm::SmallVector<TensorDemandAction, 4> actions;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CompactRunPlan {
|
|
||||||
llvm::SmallVector<RunOutputDemand, 4> outputs;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class BatchInputDemandKind {
|
|
||||||
LaneFragment,
|
|
||||||
ProjectedFragment,
|
|
||||||
WholeTensorBarrier
|
|
||||||
};
|
|
||||||
|
|
||||||
struct BatchInputDemand {
|
|
||||||
BatchInputDemandKind kind = BatchInputDemandKind::LaneFragment;
|
|
||||||
std::optional<ProducerKey> wholeTensorProducer;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CloneIndexingContext {
|
|
||||||
std::optional<mlir::Value> runSlotIndex;
|
|
||||||
std::optional<mlir::Value> projectionSlotIndex;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MaterializerState;
|
|
||||||
|
|
||||||
class AvailableValueStore {
|
|
||||||
public:
|
|
||||||
struct ExactBatchFragmentRecord {
|
|
||||||
ProducerKey key;
|
|
||||||
mlir::Value value;
|
|
||||||
};
|
|
||||||
|
|
||||||
void record(ProducerKey key, ClassId classId, mlir::Value value) {
|
|
||||||
exactValues[key][classId] = value;
|
|
||||||
|
|
||||||
auto batch = mlir::dyn_cast_or_null<SpatComputeBatch>(key.instance.op);
|
|
||||||
if (!batch || key.instance.laneCount == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
WholeBatchAssemblyLookupKey lookupKey {batch.getOperation(), key.resultIndex, classId};
|
|
||||||
llvm::SmallVector<ExactBatchFragmentRecord, 16>& bucket = exactBatchFragmentsByProducerResultClass[lookupKey];
|
|
||||||
for (ExactBatchFragmentRecord& record : bucket) {
|
|
||||||
if (!(record.key == key))
|
|
||||||
continue;
|
|
||||||
record.value = value;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
bucket.push_back({key, value});
|
|
||||||
}
|
|
||||||
|
|
||||||
void recordPackedRun(PackedScalarRunValue run) {
|
|
||||||
size_t runIndex = packedScalarRuns.size();
|
|
||||||
packedScalarRuns.push_back(std::move(run));
|
|
||||||
const PackedScalarRunValue& storedRun = packedScalarRuns[runIndex];
|
|
||||||
WholeBatchAssemblyLookupKey lookupKey {storedRun.sourceOp, storedRun.resultIndex, storedRun.targetClass};
|
|
||||||
packedRunsByProducerResultClass[lookupKey].push_back(runIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void recordIndexedBatchRun(IndexedBatchRunValue run) { indexedBatchRuns.push_back(std::move(run)); }
|
|
||||||
|
|
||||||
std::optional<mlir::Value> lookupExact(ProducerKey key, ClassId classId) const;
|
|
||||||
std::optional<mlir::Value> lookup(MaterializerState& state, ProducerKey key, ClassId classId);
|
|
||||||
IndexedBatchRunValue* lookupIndexedBatchRun(ProducerKey key, ClassId classId);
|
|
||||||
|
|
||||||
llvm::ArrayRef<size_t> getPackedRunIndicesForWholeBatch(WholeBatchAssemblyLookupKey key) const {
|
|
||||||
auto it = packedRunsByProducerResultClass.find(key);
|
|
||||||
if (it == packedRunsByProducerResultClass.end())
|
|
||||||
return {};
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
llvm::ArrayRef<ExactBatchFragmentRecord> getExactFragmentsForWholeBatch(WholeBatchAssemblyLookupKey key) const {
|
|
||||||
auto it = exactBatchFragmentsByProducerResultClass.find(key);
|
|
||||||
if (it == exactBatchFragmentsByProducerResultClass.end())
|
|
||||||
return {};
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
PackedScalarRunValue& getPackedRun(size_t index) { return packedScalarRuns[index]; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::optional<mlir::Value> lookupPackedRun(MaterializerState& state, ProducerKey key, ClassId classId);
|
|
||||||
|
|
||||||
llvm::DenseMap<ProducerKey, llvm::DenseMap<ClassId, mlir::Value>, ProducerKeyInfo> exactValues;
|
|
||||||
llvm::SmallVector<PackedScalarRunValue, 8> packedScalarRuns;
|
|
||||||
llvm::SmallVector<IndexedBatchRunValue, 8> indexedBatchRuns;
|
|
||||||
llvm::DenseMap<WholeBatchAssemblyLookupKey,
|
|
||||||
llvm::SmallVector<ExactBatchFragmentRecord, 16>,
|
|
||||||
WholeBatchAssemblyLookupKeyInfo>
|
|
||||||
exactBatchFragmentsByProducerResultClass;
|
|
||||||
llvm::DenseMap<WholeBatchAssemblyLookupKey, llvm::SmallVector<size_t, 16>, WholeBatchAssemblyLookupKeyInfo>
|
|
||||||
packedRunsByProducerResultClass;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MaterializerState {
|
|
||||||
mlir::func::FuncOp func;
|
|
||||||
const MergeScheduleResult& schedule;
|
|
||||||
mlir::IRRewriter rewriter;
|
|
||||||
mlir::OperationFolder constantFolder;
|
|
||||||
int64_t& nextChannelId;
|
|
||||||
llvm::SmallVector<MaterializedClass, 8> classes;
|
|
||||||
llvm::DenseMap<CpuId, ClassId> cpuToClass;
|
|
||||||
llvm::DenseMap<CpuId, llvm::SmallVector<ComputeInstance, 32>> logicalInstancesByCpu;
|
|
||||||
llvm::DenseMap<ComputeInstance, LogicalSlotRange> scheduledInstanceToLogicalSlots;
|
|
||||||
llvm::DenseMap<ComputeInstance, ComputeInstance> logicalInstanceToScheduledChunk;
|
|
||||||
llvm::DenseSet<ClassSlotKey> materializedLogicalSlots;
|
|
||||||
|
|
||||||
llvm::DenseMap<ProducerKey, llvm::SmallVector<ClassId, 4>, ProducerKeyInfo> producerDestClasses;
|
|
||||||
llvm::DenseMap<SameClassConsumerLookupKey, llvm::SmallVector<ProducerKey, 4>, SameClassConsumerLookupKeyInfo>
|
|
||||||
sameClassConsumerIndex;
|
|
||||||
llvm::DenseMap<ProjectedBatchInputKey, AffineProjectedInputSliceMatch, ProjectedBatchInputKeyInfo>
|
|
||||||
projectedInputMatches;
|
|
||||||
llvm::DenseSet<ProjectedBatchInputKey, ProjectedBatchInputKeyInfo> nonProjectedInputs;
|
|
||||||
llvm::DenseMap<mlir::Value, bool> liveExternalUseCache;
|
|
||||||
llvm::DenseMap<mlir::Operation*, llvm::SmallVector<mlir::Type, 4>> batchOutputFragmentTypesCache;
|
|
||||||
llvm::DenseMap<ComputeInstance, llvm::SmallVector<mlir::Value, 4>, llvm::DenseMapInfo<ComputeInstance>>
|
|
||||||
computeInstanceOutputsCache;
|
|
||||||
llvm::DenseMap<ProducerKey, llvm::DenseMap<ClassId, ProjectedTransferDescriptor>, ProducerKeyInfo>
|
|
||||||
projectedTransfers;
|
|
||||||
llvm::DenseMap<mlir::Operation*, llvm::DenseMap<ClassId, ProjectedExtractReplacement>>
|
|
||||||
projectedExtractReplacements;
|
|
||||||
AvailableValueStore availableValues;
|
|
||||||
llvm::DenseMap<mlir::Value, mlir::Value> hostReplacements;
|
|
||||||
llvm::DenseMap<mlir::Value, ClassId> hostOutputOwners;
|
|
||||||
llvm::SmallVector<PendingProjectedHostOutputFragment, 32> pendingProjectedHostOutputFragments;
|
|
||||||
llvm::DenseSet<mlir::Operation*> oldComputeOps;
|
|
||||||
|
|
||||||
MaterializerState(mlir::func::FuncOp func, const MergeScheduleResult& schedule, int64_t& nextChannelId)
|
|
||||||
: func(func),
|
|
||||||
schedule(schedule),
|
|
||||||
rewriter(func.getContext()),
|
|
||||||
constantFolder(func.getContext()),
|
|
||||||
nextChannelId(nextChannelId) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace onnx_mlir::spatial
|
|
||||||
@@ -1,391 +1,115 @@
|
|||||||
#include "mlir/Analysis/TopologicalSortUtils.h"
|
#include "ScheduledComputeMaterialization.hpp"
|
||||||
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
#include "ScheduledComputeReport.hpp"
|
||||||
#include "mlir/IR/IRMapping.h"
|
#include "ScheduledComputeVerification.hpp"
|
||||||
#include "mlir/IR/Location.h"
|
#include "SpatialDataflowCsvExporter.hpp"
|
||||||
#include "mlir/IR/PatternMatch.h"
|
#include "DeferredCommunicationRealization.hpp"
|
||||||
#include "mlir/IR/Region.h"
|
|
||||||
#include "mlir/IR/Value.h"
|
|
||||||
#include "mlir/IR/ValueRange.h"
|
|
||||||
#include "mlir/Pass/Pass.h"
|
#include "mlir/Pass/Pass.h"
|
||||||
#include "mlir/Support/LLVM.h"
|
|
||||||
|
|
||||||
#include "llvm/ADT/DenseMap.h"
|
|
||||||
#include "llvm/ADT/STLExtras.h"
|
|
||||||
#include "llvm/ADT/SmallSet.h"
|
|
||||||
#include "llvm/ADT/SmallVector.h"
|
|
||||||
#include "llvm/Support/raw_os_ostream.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <fstream>
|
|
||||||
#include <memory>
|
|
||||||
#include <optional>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "MaterializeMergeSchedule.hpp"
|
|
||||||
#include "Scheduling/ComputeGraph.hpp"
|
|
||||||
#include "Scheduling/ComputeInstanceUtils.hpp"
|
|
||||||
#include "Scheduling/MergeSchedulingAnalysis.hpp"
|
#include "Scheduling/MergeSchedulingAnalysis.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.hpp"
|
|
||||||
#include "src/Accelerators/PIM/Common/IR/CompactAsmUtils.hpp"
|
|
||||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
|
||||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
|
||||||
#include "src/Accelerators/PIM/Common/Support/ReportUtils.hpp"
|
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/Support/DebugDump.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Pass/PIMPasses.h"
|
||||||
|
|
||||||
using namespace mlir;
|
using namespace mlir;
|
||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
|
namespace spatial {
|
||||||
namespace {
|
namespace {
|
||||||
using namespace onnx_mlir::compact_asm;
|
|
||||||
using SpatCompute = spatial::SpatGraphCompute;
|
|
||||||
using SpatComputeBatch = spatial::SpatGraphComputeBatch;
|
|
||||||
|
|
||||||
bool isTrivialSerialMergeCandidate(SpatCompute compute) {
|
struct MergeComputeNodesPass final : PassWrapper<MergeComputeNodesPass, OperationPass<ModuleOp>> {
|
||||||
if (!compute->hasOneUse())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto& use = *compute->getUses().begin();
|
|
||||||
auto user = dyn_cast<SpatCompute>(use.getOwner());
|
|
||||||
return user && user.getInputs().size() == 1 && use.getOperandNumber() >= user.getWeights().size();
|
|
||||||
}
|
|
||||||
|
|
||||||
SmallVector<size_t> appendMissingWeightsAndBuildIndexMap(SmallVectorImpl<Value>& targetWeights,
|
|
||||||
ValueRange sourceWeights) {
|
|
||||||
DenseMap<Value, SmallVector<size_t, 4>> targetWeightIndices;
|
|
||||||
for (auto [weightIndex, weight] : llvm::enumerate(targetWeights))
|
|
||||||
targetWeightIndices[weight].push_back(weightIndex);
|
|
||||||
|
|
||||||
DenseMap<Value, size_t> usedSourceWeightOccurrences;
|
|
||||||
SmallVector<size_t> sourceToTargetIndex;
|
|
||||||
sourceToTargetIndex.reserve(sourceWeights.size());
|
|
||||||
for (Value weight : sourceWeights) {
|
|
||||||
size_t occurrence = usedSourceWeightOccurrences[weight]++;
|
|
||||||
auto& matchingIndices = targetWeightIndices[weight];
|
|
||||||
if (occurrence >= matchingIndices.size()) {
|
|
||||||
size_t newIndex = targetWeights.size();
|
|
||||||
targetWeights.push_back(weight);
|
|
||||||
matchingIndices.push_back(newIndex);
|
|
||||||
sourceToTargetIndex.push_back(newIndex);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
sourceToTargetIndex.push_back(matchingIndices[occurrence]);
|
|
||||||
}
|
|
||||||
return sourceToTargetIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
void mergeTriviallyConnectedComputes(func::FuncOp funcOp) {
|
|
||||||
Location loc = funcOp.getLoc();
|
|
||||||
IRRewriter rewriter(funcOp->getContext());
|
|
||||||
SmallVector<SpatCompute> trivialComputes;
|
|
||||||
llvm::SmallSet<SpatCompute, 8> toErase;
|
|
||||||
|
|
||||||
for (auto compute : funcOp.getOps<SpatCompute>())
|
|
||||||
if (isTrivialSerialMergeCandidate(compute))
|
|
||||||
trivialComputes.push_back(compute);
|
|
||||||
|
|
||||||
while (!trivialComputes.empty()) {
|
|
||||||
auto compute = trivialComputes.front();
|
|
||||||
|
|
||||||
if (compute.use_empty()) {
|
|
||||||
std::swap(trivialComputes.front(), trivialComputes.back());
|
|
||||||
trivialComputes.pop_back();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& computeUse = *compute->getUses().begin();
|
|
||||||
auto child = cast<SpatCompute>(computeUse.getOwner());
|
|
||||||
auto usedResult = cast<OpResult>(computeUse.get()).getResultNumber();
|
|
||||||
auto childInputIndex = computeUse.getOperandNumber() - child.getWeights().size();
|
|
||||||
|
|
||||||
rewriter.setInsertionPointAfter(compute.getOperation());
|
|
||||||
SmallVector<Value> mergedWeights(compute.getWeights().begin(), compute.getWeights().end());
|
|
||||||
SmallVector<size_t> childWeightToNewIndex = appendMissingWeightsAndBuildIndexMap(mergedWeights, child.getWeights());
|
|
||||||
SmallVector<Value> mergedInputs(compute.getInputs().begin(), compute.getInputs().end());
|
|
||||||
auto newCompute = SpatCompute::create(rewriter, loc, child.getResultTypes(), mergedWeights, mergedInputs);
|
|
||||||
Block* newBody = rewriter.createBlock(&newCompute.getBodyRegion());
|
|
||||||
for (Value weight : mergedWeights)
|
|
||||||
newBody->addArgument(weight.getType(), loc);
|
|
||||||
for (Value input : mergedInputs)
|
|
||||||
newBody->addArgument(input.getType(), loc);
|
|
||||||
|
|
||||||
IRMapping mapper;
|
|
||||||
for (auto [weightIndex, _] : llvm::enumerate(compute.getWeights())) {
|
|
||||||
auto oldWeightArg = compute.getWeightArgument(weightIndex);
|
|
||||||
auto newWeightArg = newCompute.getWeightArgument(weightIndex);
|
|
||||||
assert(oldWeightArg && newWeightArg && "expected compute weight block arguments");
|
|
||||||
mapper.map(*oldWeightArg, *newWeightArg);
|
|
||||||
}
|
|
||||||
for (auto [inputIndex, _] : llvm::enumerate(compute.getInputs())) {
|
|
||||||
auto oldInputArg = compute.getInputArgument(inputIndex);
|
|
||||||
auto newInputArg = newCompute.getInputArgument(inputIndex);
|
|
||||||
assert(oldInputArg && newInputArg && "expected compute input block arguments");
|
|
||||||
mapper.map(*oldInputArg, *newInputArg);
|
|
||||||
}
|
|
||||||
for (auto [oldIndex, weight] : llvm::enumerate(child.getWeights())) {
|
|
||||||
auto oldWeightArg = child.getWeightArgument(oldIndex);
|
|
||||||
auto newWeightArg = newCompute.getWeightArgument(childWeightToNewIndex[oldIndex]);
|
|
||||||
assert(oldWeightArg && newWeightArg && "expected child compute weight block arguments");
|
|
||||||
mapper.map(*oldWeightArg, *newWeightArg);
|
|
||||||
}
|
|
||||||
|
|
||||||
rewriter.setInsertionPointToEnd(newBody);
|
|
||||||
auto computeYield = cast<spatial::SpatYieldOp>(compute.getBody().front().getTerminator());
|
|
||||||
for (Operation& op : compute.getBody().front().without_terminator())
|
|
||||||
rewriter.clone(op, mapper);
|
|
||||||
auto childInputArg = child.getInputArgument(childInputIndex);
|
|
||||||
assert(childInputArg && "expected child compute input block argument");
|
|
||||||
mapper.map(*childInputArg, mapper.lookupOrDefault(computeYield.getOperand(usedResult)));
|
|
||||||
|
|
||||||
rewriter.setInsertionPointToEnd(newBody);
|
|
||||||
for (auto& op : child.getBody().front())
|
|
||||||
rewriter.clone(op, mapper);
|
|
||||||
|
|
||||||
child.replaceAllUsesWith(newCompute);
|
|
||||||
toErase.insert(child);
|
|
||||||
|
|
||||||
std::swap(trivialComputes.front(), trivialComputes.back());
|
|
||||||
trivialComputes.pop_back();
|
|
||||||
toErase.insert(compute);
|
|
||||||
|
|
||||||
if (isTrivialSerialMergeCandidate(newCompute))
|
|
||||||
trivialComputes.push_back(newCompute);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto compute : toErase) {
|
|
||||||
for (Value result : compute->getResults())
|
|
||||||
result.dropAllUses();
|
|
||||||
compute.erase();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void generateReport(func::FuncOp funcOp, const std::string& name, size_t usedCpuCount = 0) {
|
|
||||||
std::fstream file = openReportFile(name);
|
|
||||||
if (!file.is_open())
|
|
||||||
return;
|
|
||||||
llvm::raw_os_ostream os(file);
|
|
||||||
|
|
||||||
struct ReportRow {
|
|
||||||
uint64_t id = 0;
|
|
||||||
uint64_t logicalComputeCount = 0;
|
|
||||||
uint64_t crossbarCount = 0;
|
|
||||||
uint64_t instructionCount = 0;
|
|
||||||
bool isRebatched = false;
|
|
||||||
SmallVector<int32_t> coreIds;
|
|
||||||
};
|
|
||||||
|
|
||||||
//TODO Used for report refactor
|
|
||||||
struct CollectorConcatRow {
|
|
||||||
uint64_t computeId = 0;
|
|
||||||
int32_t coreId = -1;
|
|
||||||
uint64_t operandCount = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
uint64_t totalComputeOps = 0;
|
|
||||||
uint64_t totalLogicalComputes = 0;
|
|
||||||
uint64_t totalBatchComputeOps = 0;
|
|
||||||
uint64_t totalInstructionCount = 0;
|
|
||||||
uint64_t totalCrossbarCount = 0;
|
|
||||||
uint64_t nextBatchId = 0;
|
|
||||||
//TODO Used for report refactor
|
|
||||||
std::vector<ReportRow> collectedData;
|
|
||||||
//TODO Used for report refactor
|
|
||||||
std::vector<CollectorConcatRow> collectorConcatRows;
|
|
||||||
|
|
||||||
auto getPerInstanceCrossbarCount = [&](Operation* op) -> uint64_t {
|
|
||||||
return static_cast<uint64_t>(spatial::collectDistinctCrossbarWeights(op).size());
|
|
||||||
};
|
|
||||||
|
|
||||||
for (Operation& op : funcOp.getBody().front()) {
|
|
||||||
if (auto spatCompute = dyn_cast<spatial::SpatScheduledCompute>(&op)) {
|
|
||||||
uint64_t numInst = spatial::countComputeBodyInstructions(spatCompute.getBody());
|
|
||||||
uint64_t perInstanceCrossbarCount = getPerInstanceCrossbarCount(spatCompute.getOperation());
|
|
||||||
SmallVector<int32_t> coreIds;
|
|
||||||
auto coreId = getOptionalScheduledCoreId(spatCompute, "merge compute core id");
|
|
||||||
if (failed(coreId))
|
|
||||||
return;
|
|
||||||
if (*coreId)
|
|
||||||
coreIds.push_back(**coreId);
|
|
||||||
uint64_t computeId = totalComputeOps++;
|
|
||||||
collectedData.push_back({computeId, 1, perInstanceCrossbarCount, numInst, false, coreIds});
|
|
||||||
uint64_t maxConcatOperands = 0;
|
|
||||||
spatCompute.getBody().walk([&](spatial::SpatConcatOp concatOp) {
|
|
||||||
maxConcatOperands = std::max<uint64_t>(maxConcatOperands, concatOp.getInputs().size());
|
|
||||||
});
|
|
||||||
//TODO 128 is a magic number
|
|
||||||
if (maxConcatOperands >= 128 && !coreIds.empty())
|
|
||||||
collectorConcatRows.push_back({computeId, coreIds.front(), maxConcatOperands});
|
|
||||||
totalLogicalComputes += 1;
|
|
||||||
totalInstructionCount += numInst;
|
|
||||||
totalCrossbarCount += perInstanceCrossbarCount;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (auto batch = dyn_cast<spatial::SpatScheduledComputeBatch>(&op)) {
|
|
||||||
uint64_t numInst = spatial::countComputeBodyInstructions(batch.getBody());
|
|
||||||
uint64_t logicalCount = static_cast<uint64_t>(batch.getLaneCount());
|
|
||||||
uint64_t perInstanceCrossbarCount = getPerInstanceCrossbarCount(batch.getOperation());
|
|
||||||
SmallVector<int32_t> coreIds;
|
|
||||||
auto optionalCoreIds = getOptionalScheduledBatchCoreIds(batch, "merge compute_batch core id");
|
|
||||||
if (failed(optionalCoreIds))
|
|
||||||
return;
|
|
||||||
if (*optionalCoreIds)
|
|
||||||
coreIds = std::move(**optionalCoreIds);
|
|
||||||
collectedData.push_back(
|
|
||||||
{nextBatchId++, logicalCount, perInstanceCrossbarCount * logicalCount, numInst, true, coreIds});
|
|
||||||
totalComputeOps += 1;
|
|
||||||
totalLogicalComputes += logicalCount;
|
|
||||||
totalBatchComputeOps += 1;
|
|
||||||
totalInstructionCount += numInst * logicalCount;
|
|
||||||
totalCrossbarCount += perInstanceCrossbarCount * logicalCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
llvm::SmallVector<ReportField, 6> totalFields = {
|
|
||||||
{"Used cores", std::to_string(usedCpuCount) },
|
|
||||||
{"Number of top-level compute ops", std::to_string(totalComputeOps) },
|
|
||||||
{"Number of logical computes", std::to_string(totalLogicalComputes) },
|
|
||||||
{"Number of top-level batch compute ops", std::to_string(totalBatchComputeOps) },
|
|
||||||
{"Number of instructions", std::to_string(totalInstructionCount)},
|
|
||||||
{"Number of used crossbars", std::to_string(totalCrossbarCount) }
|
|
||||||
};
|
|
||||||
printReportTotalsBlock(os, totalFields);
|
|
||||||
if (!collectedData.empty() || !collectorConcatRows.empty())
|
|
||||||
os << "\n";
|
|
||||||
|
|
||||||
if (!collectorConcatRows.empty()) {
|
|
||||||
os << "Collector concat materialization:\n";
|
|
||||||
for (const CollectorConcatRow& row : collectorConcatRows)
|
|
||||||
os << "\tmaterialization_kind = single_collector_concat, compute = " << row.computeId
|
|
||||||
<< ", concat_operand_count = " << row.operandCount << ", collector_core = " << row.coreId << "\n";
|
|
||||||
os << "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
sortReportEntriesByFirstCore(collectedData);
|
|
||||||
|
|
||||||
for (uint64_t cI = 0; cI < totalComputeOps; ++cI) {
|
|
||||||
uint64_t lastIndex = cI;
|
|
||||||
ReportRow current = collectedData[cI];
|
|
||||||
|
|
||||||
for (uint64_t nI = cI + 1; nI < totalComputeOps; ++nI) {
|
|
||||||
ReportRow next = collectedData[nI];
|
|
||||||
if (current.isRebatched == next.isRebatched && current.crossbarCount == next.crossbarCount
|
|
||||||
&& current.instructionCount == next.instructionCount
|
|
||||||
&& current.logicalComputeCount == next.logicalComputeCount)
|
|
||||||
lastIndex = nI;
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current.isRebatched) {
|
|
||||||
os << "Batch ";
|
|
||||||
for (uint64_t index = cI; index <= lastIndex; ++index) {
|
|
||||||
if (index != cI)
|
|
||||||
os << ",\n ";
|
|
||||||
os << collectedData[index].id << " (cores ";
|
|
||||||
if (collectedData[index].coreIds.empty())
|
|
||||||
os << "unknown";
|
|
||||||
else
|
|
||||||
printCompressedIntegerEntries(os, ArrayRef<int32_t>(collectedData[index].coreIds));
|
|
||||||
os << ")";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
os << "Compute ";
|
|
||||||
SmallVector<uint64_t> opIds;
|
|
||||||
opIds.reserve(lastIndex - cI + 1);
|
|
||||||
for (uint64_t index = cI; index <= lastIndex; ++index)
|
|
||||||
opIds.push_back(collectedData[index].id);
|
|
||||||
printCompressedIntegerEntries(os, ArrayRef<uint64_t>(opIds));
|
|
||||||
}
|
|
||||||
|
|
||||||
os << ":\n";
|
|
||||||
uint64_t perCoreLogicalComputeCount = current.isRebatched ? 1 : current.logicalComputeCount;
|
|
||||||
uint64_t perCoreInstructionCount = current.instructionCount;
|
|
||||||
uint64_t perCoreCrossbarCount =
|
|
||||||
current.logicalComputeCount == 0 ? 0 : current.crossbarCount / current.logicalComputeCount;
|
|
||||||
uint64_t totalEntryInstructionCount = current.instructionCount * current.logicalComputeCount;
|
|
||||||
|
|
||||||
llvm::SmallVector<ReportField, 3> perCoreFields = {
|
|
||||||
{"Number of logical computes", std::to_string(perCoreLogicalComputeCount)},
|
|
||||||
{"Number of instructions", std::to_string(perCoreInstructionCount) },
|
|
||||||
{"Number of used crossbars", std::to_string(perCoreCrossbarCount) }
|
|
||||||
};
|
|
||||||
if (current.isRebatched) {
|
|
||||||
llvm::SmallVector<ReportField, 3> totalEntryFields = {
|
|
||||||
{"Number of logical computes", std::to_string(current.logicalComputeCount)},
|
|
||||||
{"Number of instructions", std::to_string(totalEntryInstructionCount) },
|
|
||||||
{"Number of used crossbars", std::to_string(current.crossbarCount) }
|
|
||||||
};
|
|
||||||
printReportPerCoreAndTotalFields(os, perCoreFields, totalEntryFields);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
printReportFlatFields(os, perCoreFields);
|
|
||||||
}
|
|
||||||
printReportEntrySeparator(os, lastIndex + 1 < totalComputeOps);
|
|
||||||
cI = lastIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
os.flush();
|
|
||||||
file.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MergeComputeNodesPass : PassWrapper<MergeComputeNodesPass, OperationPass<func::FuncOp>> {
|
|
||||||
|
|
||||||
private:
|
|
||||||
int64_t nextChannelId = 0;
|
|
||||||
|
|
||||||
public:
|
|
||||||
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(MergeComputeNodesPass)
|
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(MergeComputeNodesPass)
|
||||||
|
|
||||||
StringRef getArgument() const override { return "pim-merge-compute-nodes-pass"; }
|
StringRef getArgument() const override { return "pim-merge-compute-nodes"; }
|
||||||
StringRef getDescription() const override {
|
StringRef getDescription() const override {
|
||||||
return "Merge Spatial-Compute-Nodes in order to reduce the total "
|
return "Materialize scheduled Spatial compute with deferred communication placeholders.";
|
||||||
"execution time";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LogicalResult initialize(MLIRContext* context) override { return success(); }
|
|
||||||
|
|
||||||
void runOnOperation() override {
|
void runOnOperation() override {
|
||||||
func::FuncOp func = getOperation();
|
ModuleOp moduleOp = getOperation();
|
||||||
if (failed(verifyLogicalSpatialGraphInvariants(func))) {
|
auto entryFunc = getPimEntryFunc(moduleOp);
|
||||||
func.emitOpError("logical Spatial graph verification failed at the start of MergeComputeNodes");
|
if (failed(entryFunc)) {
|
||||||
signalPassFailure();
|
moduleOp.emitError("failed to locate the PIM entry function during MergeComputeNodes");
|
||||||
return;
|
|
||||||
}
|
|
||||||
mergeTriviallyConnectedComputes(func);
|
|
||||||
if (failed(verifyLogicalSpatialGraphInvariants(func))) {
|
|
||||||
func.emitOpError("logical Spatial graph verification failed after trivial merge simplification");
|
|
||||||
signalPassFailure();
|
signalPassFailure();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const spatial::MergeScheduleResult* analysisResult = nullptr;
|
func::FuncOp funcOp = *entryFunc;
|
||||||
analysisResult = &getAnalysis<spatial::MergeSchedulingAnalysis>().getResult();
|
MergeScheduleResult schedule = MergeSchedulingAnalysis(funcOp).getResult();
|
||||||
if (failed(spatial::MergeScheduleMaterializer().run(func, *analysisResult, nextChannelId))) {
|
PatternRewriter rewriter(moduleOp.getContext());
|
||||||
|
FailureOr<ScheduledComputeMaterializationResult> materialization =
|
||||||
|
materializeScheduledCompute(funcOp, schedule, rewriter);
|
||||||
|
if (failed(materialization)) {
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Phase 1 is intentionally dumped before its verifier: malformed deferred
|
||||||
|
// payloads must be diagnosed from the producer-owned body.
|
||||||
|
dumpModule(moduleOp, "spatial3_scheduled_no_comm", /*assumeVerified=*/true);
|
||||||
|
if (failed(verifyMaterializedScheduleMapping(funcOp,
|
||||||
|
schedule,
|
||||||
|
materialization->peftClassPlans,
|
||||||
|
materialization->graphComputeToBlockMap,
|
||||||
|
materialization->materializedSchedules))) {
|
||||||
|
moduleOp.emitError("scheduled Spatial materialization mapping verification failed");
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (failed(verifyDeferredTransferPhase1Invariants(funcOp))) {
|
||||||
|
moduleOp.emitError("scheduled Spatial deferred communication verification failed");
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (failed(verifyScheduledMaterializationRecords(materialization->materializedSchedules))) {
|
||||||
|
moduleOp.emitError("scheduled Spatial materialization record verification failed");
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (failed(verifyScheduledSpatialInvariants(funcOp))) {
|
||||||
|
moduleOp.emitError("scheduled Spatial phase 1 verification failed");
|
||||||
signalPassFailure();
|
signalPassFailure();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sortTopologically(&func.getBody().front())) {
|
SpatialDataflowExportStage exportMode = getSpatialDataflowExportStage();
|
||||||
func.emitOpError("failed to topologically order merged Spatial IR");
|
if (shouldExportSpatialDataflowStage(exportMode, SpatialDataflowExportStage::Spatial3)
|
||||||
|
&& failed(exportSpatialDataflowCsvScheduled(
|
||||||
|
funcOp, materialization->materializedSchedules,
|
||||||
|
"spatial3_scheduled_no_comm", "spatial3"))) {
|
||||||
signalPassFailure();
|
signalPassFailure();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (failed(verifyScheduledSpatialInvariants(func))) {
|
|
||||||
func.emitOpError("scheduled Spatial verification failed after merge materialization");
|
dumpScheduledComputeReport(moduleOp,
|
||||||
|
funcOp,
|
||||||
|
schedule,
|
||||||
|
materialization->peftClassPlans,
|
||||||
|
materialization->materializedSchedules);
|
||||||
|
if (failed(realizeDeferredCommunication(funcOp, *materialization))) {
|
||||||
|
moduleOp.emitError("MergeComputeNodes phase 2 communication realization failed");
|
||||||
signalPassFailure();
|
signalPassFailure();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dumpModule(cast<ModuleOp>(func->getParentOp()), "spatial1_merged");
|
dumpModule(moduleOp, "spatial4_scheduled", /*assumeVerified=*/true);
|
||||||
generateReport(func, "spatial_merge_report", analysisResult->cpuToLastComputeMap.size());
|
if (failed(verifyScheduledResultsLive(materialization->materializedSchedules))
|
||||||
|
|| failed(verifyScheduledSpatialInvariants(funcOp))) {
|
||||||
|
moduleOp.emitError("scheduled Spatial phase 2 verification failed");
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (shouldExportSpatialDataflowStage(exportMode, SpatialDataflowExportStage::Spatial4)
|
||||||
|
&& failed(exportSpatialDataflowCsvScheduled(
|
||||||
|
funcOp, materialization->materializedSchedules,
|
||||||
|
"spatial4_scheduled", "spatial4"))) {
|
||||||
|
signalPassFailure();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
} // namespace spatial
|
||||||
|
|
||||||
std::unique_ptr<Pass> createMergeComputeNodesPass() { return std::make_unique<MergeComputeNodesPass>(); }
|
std::unique_ptr<Pass> createMergeComputeNodesPass() { return std::make_unique<spatial::MergeComputeNodesPass>(); }
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "llvm/ADT/ArrayRef.h"
|
|
||||||
#include "llvm/ADT/SmallVector.h"
|
|
||||||
#include "llvm/ADT/StringRef.h"
|
|
||||||
|
|
||||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
|
||||||
|
|
||||||
namespace onnx_mlir::spatial {
|
|
||||||
|
|
||||||
using CpuId = size_t;
|
|
||||||
|
|
||||||
inline mlir::FailureOr<int32_t> getCheckedCoreId(mlir::Operation* anchor, CpuId cpu, llvm::StringRef fieldName) {
|
|
||||||
return pim::checkedI32(static_cast<uint64_t>(cpu), anchor, fieldName);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline mlir::FailureOr<llvm::SmallVector<int32_t, 8>>
|
|
||||||
getCheckedCoreIds(mlir::Operation* anchor, llvm::ArrayRef<CpuId> cpus, llvm::StringRef fieldName) {
|
|
||||||
llvm::SmallVector<int32_t, 8> coreIds;
|
|
||||||
coreIds.reserve(cpus.size());
|
|
||||||
for (CpuId cpu : cpus) {
|
|
||||||
auto checkedCoreId = getCheckedCoreId(anchor, cpu, fieldName);
|
|
||||||
if (mlir::failed(checkedCoreId))
|
|
||||||
return mlir::failure();
|
|
||||||
coreIds.push_back(*checkedCoreId);
|
|
||||||
}
|
|
||||||
return coreIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MessageVector {
|
|
||||||
llvm::SmallVector<int64_t, 16> channelIds;
|
|
||||||
llvm::SmallVector<int32_t, 16> sourceCoreIds;
|
|
||||||
llvm::SmallVector<int32_t, 16> targetCoreIds;
|
|
||||||
|
|
||||||
size_t size() const { return channelIds.size(); }
|
|
||||||
bool empty() const { return channelIds.empty(); }
|
|
||||||
|
|
||||||
mlir::LogicalResult verify(mlir::Operation* anchor) const {
|
|
||||||
if (channelIds.size() != sourceCoreIds.size() || channelIds.size() != targetCoreIds.size())
|
|
||||||
return anchor->emitError("message metadata is inconsistent");
|
|
||||||
return mlir::success();
|
|
||||||
}
|
|
||||||
|
|
||||||
void append(int64_t channelId, int32_t sourceCoreId, int32_t targetCoreId) {
|
|
||||||
channelIds.push_back(channelId);
|
|
||||||
sourceCoreIds.push_back(sourceCoreId);
|
|
||||||
targetCoreIds.push_back(targetCoreId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void append(llvm::ArrayRef<int64_t> channels, llvm::ArrayRef<int32_t> sources, llvm::ArrayRef<int32_t> targets) {
|
|
||||||
assert(channels.size() == sources.size() && "channel/source count mismatch");
|
|
||||||
assert(channels.size() == targets.size() && "channel/target count mismatch");
|
|
||||||
llvm::append_range(channelIds, channels);
|
|
||||||
llvm::append_range(sourceCoreIds, sources);
|
|
||||||
llvm::append_range(targetCoreIds, targets);
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageVector slice(size_t offset, size_t count) const {
|
|
||||||
MessageVector result;
|
|
||||||
result.append(llvm::ArrayRef<int64_t>(channelIds).slice(offset, count),
|
|
||||||
llvm::ArrayRef<int32_t>(sourceCoreIds).slice(offset, count),
|
|
||||||
llvm::ArrayRef<int32_t>(targetCoreIds).slice(offset, count));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace onnx_mlir::spatial
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "llvm/ADT/DenseMap.h"
|
|
||||||
#include "llvm/ADT/SmallVector.h"
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <limits>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include "Scheduling/ComputeInstanceUtils.hpp"
|
|
||||||
|
|
||||||
namespace onnx_mlir::spatial {
|
|
||||||
|
|
||||||
using ClassId = size_t;
|
|
||||||
using SlotId = size_t;
|
|
||||||
|
|
||||||
struct ProducerKey {
|
|
||||||
ComputeInstance instance;
|
|
||||||
size_t resultIndex = 0;
|
|
||||||
|
|
||||||
bool operator==(const ProducerKey& other) const {
|
|
||||||
return instance == other.instance && resultIndex == other.resultIndex;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ProducerKeyInfo {
|
|
||||||
static ProducerKey getEmptyKey() {
|
|
||||||
return {llvm::DenseMapInfo<ComputeInstance>::getEmptyKey(), std::numeric_limits<size_t>::max()};
|
|
||||||
}
|
|
||||||
|
|
||||||
static ProducerKey getTombstoneKey() {
|
|
||||||
return {llvm::DenseMapInfo<ComputeInstance>::getTombstoneKey(), std::numeric_limits<size_t>::max()};
|
|
||||||
}
|
|
||||||
|
|
||||||
static unsigned getHashValue(const ProducerKey& key) {
|
|
||||||
return llvm::hash_combine(llvm::DenseMapInfo<ComputeInstance>::getHashValue(key.instance), key.resultIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isEqual(const ProducerKey& lhs, const ProducerKey& rhs) { return lhs == rhs; }
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SameClassConsumerLookupKey {
|
|
||||||
mlir::Operation* sourceOp = nullptr;
|
|
||||||
size_t resultIndex = 0;
|
|
||||||
ClassId classId = 0;
|
|
||||||
|
|
||||||
bool operator==(const SameClassConsumerLookupKey& other) const {
|
|
||||||
return sourceOp == other.sourceOp && resultIndex == other.resultIndex && classId == other.classId;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SameClassConsumerLookupKeyInfo {
|
|
||||||
static SameClassConsumerLookupKey getEmptyKey() {
|
|
||||||
return {llvm::DenseMapInfo<mlir::Operation*>::getEmptyKey(), std::numeric_limits<size_t>::max(),
|
|
||||||
std::numeric_limits<ClassId>::max()};
|
|
||||||
}
|
|
||||||
|
|
||||||
static SameClassConsumerLookupKey getTombstoneKey() {
|
|
||||||
return {llvm::DenseMapInfo<mlir::Operation*>::getTombstoneKey(), std::numeric_limits<size_t>::max(),
|
|
||||||
std::numeric_limits<ClassId>::max()};
|
|
||||||
}
|
|
||||||
|
|
||||||
static unsigned getHashValue(const SameClassConsumerLookupKey& key) {
|
|
||||||
return llvm::hash_combine(llvm::DenseMapInfo<mlir::Operation*>::getHashValue(key.sourceOp),
|
|
||||||
key.resultIndex,
|
|
||||||
key.classId);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isEqual(const SameClassConsumerLookupKey& lhs, const SameClassConsumerLookupKey& rhs) {
|
|
||||||
return lhs == rhs;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct WholeBatchAssemblyLookupKey {
|
|
||||||
mlir::Operation* sourceOp = nullptr;
|
|
||||||
size_t resultIndex = 0;
|
|
||||||
ClassId classId = 0;
|
|
||||||
|
|
||||||
bool operator==(const WholeBatchAssemblyLookupKey& other) const {
|
|
||||||
return sourceOp == other.sourceOp && resultIndex == other.resultIndex && classId == other.classId;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct WholeBatchAssemblyLookupKeyInfo {
|
|
||||||
static WholeBatchAssemblyLookupKey getEmptyKey() {
|
|
||||||
return {llvm::DenseMapInfo<mlir::Operation*>::getEmptyKey(), std::numeric_limits<size_t>::max(),
|
|
||||||
std::numeric_limits<ClassId>::max()};
|
|
||||||
}
|
|
||||||
|
|
||||||
static WholeBatchAssemblyLookupKey getTombstoneKey() {
|
|
||||||
return {llvm::DenseMapInfo<mlir::Operation*>::getTombstoneKey(), std::numeric_limits<size_t>::max(),
|
|
||||||
std::numeric_limits<ClassId>::max()};
|
|
||||||
}
|
|
||||||
|
|
||||||
static unsigned getHashValue(const WholeBatchAssemblyLookupKey& key) {
|
|
||||||
return llvm::hash_combine(llvm::DenseMapInfo<mlir::Operation*>::getHashValue(key.sourceOp),
|
|
||||||
key.resultIndex,
|
|
||||||
key.classId);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isEqual(const WholeBatchAssemblyLookupKey& lhs, const WholeBatchAssemblyLookupKey& rhs) {
|
|
||||||
return lhs == rhs;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using ClassSlotKey = std::pair<ClassId, SlotId>;
|
|
||||||
|
|
||||||
struct ProjectedBatchInputKey {
|
|
||||||
mlir::Operation* consumerOp = nullptr;
|
|
||||||
unsigned inputIndex = 0;
|
|
||||||
|
|
||||||
bool operator==(const ProjectedBatchInputKey& other) const {
|
|
||||||
return consumerOp == other.consumerOp && inputIndex == other.inputIndex;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ProjectedBatchInputKeyInfo {
|
|
||||||
static ProjectedBatchInputKey getEmptyKey() {
|
|
||||||
return {llvm::DenseMapInfo<mlir::Operation*>::getEmptyKey(), std::numeric_limits<unsigned>::max()};
|
|
||||||
}
|
|
||||||
|
|
||||||
static ProjectedBatchInputKey getTombstoneKey() {
|
|
||||||
return {llvm::DenseMapInfo<mlir::Operation*>::getTombstoneKey(), std::numeric_limits<unsigned>::max()};
|
|
||||||
}
|
|
||||||
|
|
||||||
static unsigned getHashValue(const ProjectedBatchInputKey& key) {
|
|
||||||
return llvm::hash_combine(key.consumerOp, key.inputIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isEqual(const ProjectedBatchInputKey& lhs, const ProjectedBatchInputKey& rhs) { return lhs == rhs; }
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace onnx_mlir::spatial
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
#include "ProjectedFragments.hpp"
|
|
||||||
|
|
||||||
#include "mlir/IR/BuiltinTypes.h"
|
|
||||||
|
|
||||||
namespace onnx_mlir::spatial {
|
|
||||||
|
|
||||||
static mlir::FailureOr<mlir::RankedTensorType> getPackedBatchTensorType(mlir::Type laneType, size_t laneCount) {
|
|
||||||
auto tensorType = mlir::dyn_cast<mlir::RankedTensorType>(laneType);
|
|
||||||
if (!tensorType || !tensorType.hasStaticShape() || tensorType.getRank() == 0)
|
|
||||||
return mlir::failure();
|
|
||||||
|
|
||||||
llvm::SmallVector<int64_t, 4> shape(tensorType.getShape());
|
|
||||||
shape[0] *= static_cast<int64_t>(laneCount);
|
|
||||||
return mlir::RankedTensorType::get(shape, tensorType.getElementType(), tensorType.getEncoding());
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned getProjectedFragmentsPerLogicalSlot(llvm::ArrayRef<int64_t> loopTripCounts) {
|
|
||||||
unsigned fragmentsPerLogicalSlot = 1;
|
|
||||||
for (int64_t tripCount : loopTripCounts) {
|
|
||||||
assert(tripCount > 0 && "projected loop trip counts must be positive");
|
|
||||||
fragmentsPerLogicalSlot *= static_cast<unsigned>(tripCount);
|
|
||||||
}
|
|
||||||
return fragmentsPerLogicalSlot;
|
|
||||||
}
|
|
||||||
|
|
||||||
mlir::LogicalResult verifyProjectedFragmentLayout(mlir::Operation* anchor, const ProjectedFragmentLayout& layout) {
|
|
||||||
if (!layout.fragmentType || layout.fragmentShape.empty())
|
|
||||||
return anchor->emitError("projected fragment layout is missing fragment type metadata");
|
|
||||||
if (layout.fragmentShape.size() != static_cast<size_t>(layout.fragmentType.getRank()))
|
|
||||||
return anchor->emitError("projected fragment layout rank does not match fragment type");
|
|
||||||
if (layout.payloadFragmentCount == 0 || layout.fragmentsPerLogicalSlot == 0)
|
|
||||||
return anchor->emitError("projected fragment layout has an invalid fragment count");
|
|
||||||
if (layout.payloadFragmentCount % layout.fragmentsPerLogicalSlot != 0)
|
|
||||||
return anchor->emitError("projected fragment layout payload fragment count is incompatible with logical slots");
|
|
||||||
return mlir::success();
|
|
||||||
}
|
|
||||||
|
|
||||||
mlir::FailureOr<mlir::RankedTensorType>
|
|
||||||
getProjectedPayloadType(mlir::Operation* anchor, mlir::RankedTensorType fragmentType, unsigned payloadFragmentCount) {
|
|
||||||
auto packedType = getPackedBatchTensorType(fragmentType, payloadFragmentCount);
|
|
||||||
if (mlir::failed(packedType)) {
|
|
||||||
anchor->emitError("cannot create projected payload type");
|
|
||||||
return mlir::failure();
|
|
||||||
}
|
|
||||||
return *packedType;
|
|
||||||
}
|
|
||||||
|
|
||||||
llvm::SmallVector<llvm::SmallVector<int64_t, 16>, 4>
|
|
||||||
buildProjectedFragmentOffsetsByDim(llvm::ArrayRef<llvm::SmallVector<int64_t, 4>> fragmentOffsets, size_t rank) {
|
|
||||||
llvm::SmallVector<llvm::SmallVector<int64_t, 16>, 4> fragmentOffsetsByDim(rank);
|
|
||||||
for (llvm::ArrayRef<int64_t> offsets : fragmentOffsets) {
|
|
||||||
assert(offsets.size() == rank && "projected offset rank mismatch");
|
|
||||||
for (size_t dim = 0; dim < rank; ++dim)
|
|
||||||
fragmentOffsetsByDim[dim].push_back(offsets[dim]);
|
|
||||||
}
|
|
||||||
return fragmentOffsetsByDim;
|
|
||||||
}
|
|
||||||
|
|
||||||
mlir::LogicalResult verifyProjectedTransferDescriptor(mlir::Operation* anchor,
|
|
||||||
const ProjectedTransferDescriptor& descriptor) {
|
|
||||||
if (mlir::failed(verifyProjectedFragmentLayout(anchor, descriptor.layout)))
|
|
||||||
return mlir::failure();
|
|
||||||
if (!descriptor.payloadType)
|
|
||||||
return anchor->emitError("projected transfer descriptor is missing payload type");
|
|
||||||
if (descriptor.fragmentOffsets.empty())
|
|
||||||
return anchor->emitError("projected transfer descriptor expected at least one fragment offset");
|
|
||||||
if (descriptor.fragmentOffsetsByDim.size() != descriptor.layout.fragmentShape.size())
|
|
||||||
return anchor->emitError("projected transfer descriptor dimension-major offsets are inconsistent");
|
|
||||||
for (llvm::ArrayRef<int64_t> dimOffsets : descriptor.fragmentOffsetsByDim)
|
|
||||||
if (dimOffsets.size() != descriptor.fragmentOffsets.size())
|
|
||||||
return anchor->emitError("projected transfer descriptor dimension-major offsets are inconsistent");
|
|
||||||
for (llvm::ArrayRef<int64_t> offsets : descriptor.fragmentOffsets)
|
|
||||||
if (offsets.size() != descriptor.layout.fragmentShape.size())
|
|
||||||
return anchor->emitError("projected transfer offset rank does not match fragment rank");
|
|
||||||
return mlir::success();
|
|
||||||
}
|
|
||||||
|
|
||||||
mlir::LogicalResult verifyProjectedSendDescriptor(mlir::Operation* anchor,
|
|
||||||
const ProjectedTransferDescriptor& descriptor,
|
|
||||||
const MessageVector& messages) {
|
|
||||||
if (mlir::failed(verifyProjectedTransferDescriptor(anchor, descriptor)))
|
|
||||||
return mlir::failure();
|
|
||||||
if (messages.size() * descriptor.layout.payloadFragmentCount != descriptor.fragmentOffsets.size())
|
|
||||||
return anchor->emitError("projected send descriptor metadata is inconsistent");
|
|
||||||
return mlir::success();
|
|
||||||
}
|
|
||||||
|
|
||||||
mlir::LogicalResult finalizeProjectedTransferDescriptor(mlir::Operation* anchor,
|
|
||||||
ProjectedTransferDescriptor& descriptor) {
|
|
||||||
descriptor.fragmentOffsetsByDim =
|
|
||||||
buildProjectedFragmentOffsetsByDim(descriptor.fragmentOffsets, descriptor.layout.fragmentShape.size());
|
|
||||||
|
|
||||||
auto payloadType =
|
|
||||||
getProjectedPayloadType(anchor, descriptor.layout.fragmentType, descriptor.layout.payloadFragmentCount);
|
|
||||||
if (mlir::failed(payloadType))
|
|
||||||
return mlir::failure();
|
|
||||||
if (descriptor.payloadType && descriptor.payloadType != *payloadType)
|
|
||||||
return anchor->emitError("projected transfer descriptor payload type does not match projected layout");
|
|
||||||
descriptor.payloadType = *payloadType;
|
|
||||||
|
|
||||||
return verifyProjectedTransferDescriptor(anchor, descriptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // 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