Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ae67d720c6 | |||
| d788178749 | |||
| c744f388dc | |||
| 51fdb830e5 | |||
| d1a29ace3c | |||
| 61e3ea9996 | |||
| fed6d343e5 | |||
| 871fcfa832 | |||
| 1f4f58de1c | |||
| 8338caf3f3 | |||
| 47f6715296 | |||
| 2bfc033af9 | |||
| 83a54e28e4 | |||
| cc9b025a35 | |||
| c4dd28a607 | |||
| 8d3eb929f6 | |||
| f5e1c2e706 | |||
| 94c96195b9 | |||
| 645539317b | |||
| 4a98e88e97 | |||
| f492400eda | |||
| e8f09fd67f | |||
| 78e97f9fd8 | |||
| 984f362623 | |||
| 568fd90542 |
@@ -1,134 +0,0 @@
|
||||
# the name by which the project can be referenced within Serena
|
||||
project_name: raptor
|
||||
|
||||
# list of languages for which language servers are started; choose from:
|
||||
# al angular ansible bash clojure
|
||||
# cpp cpp_ccls crystal csharp csharp_omnisharp
|
||||
# dart elixir elm erlang fortran
|
||||
# fsharp go groovy haskell haxe
|
||||
# hlsl html java json julia
|
||||
# kotlin lean4 lua luau markdown
|
||||
# matlab msl nix ocaml pascal
|
||||
# perl php php_phpactor powershell python
|
||||
# python_jedi python_ty r rego ruby
|
||||
# ruby_solargraph rust scala scss solidity
|
||||
# svelte swift systemverilog terraform toml
|
||||
# typescript typescript_vts vue yaml zig
|
||||
# (This list may be outdated. For the current list, see values of Language enum here:
|
||||
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
|
||||
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
|
||||
# Note:
|
||||
# - For C, use cpp
|
||||
# - For JavaScript, use typescript
|
||||
# - For Angular projects, use angular (subsumes typescript+html; requires `npm install` in the project root)
|
||||
# - For Svelte projects, use svelte (subsumes typescript/javascript for .svelte projects; requires npm)
|
||||
# - For SCSS / Sass / plain CSS, use scss (some-sass-language-server handles all three)
|
||||
# - For Free Pascal/Lazarus, use pascal
|
||||
# Special requirements:
|
||||
# Some languages require additional setup/installations.
|
||||
# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
|
||||
# When using multiple languages, the first language server that supports a given file will be used for that file.
|
||||
# The first language is the default language and the respective language server will be used as a fallback.
|
||||
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
|
||||
languages:
|
||||
- cpp
|
||||
- rust
|
||||
- python
|
||||
|
||||
# the encoding used by text files in the project
|
||||
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
|
||||
encoding: utf-8
|
||||
|
||||
# list of additional paths to ignore in this project.
|
||||
# Same syntax as gitignore, so you can use * and **.
|
||||
# Note: global ignored_paths from serena_config.yml are also applied additively.
|
||||
ignored_paths:
|
||||
|
||||
# list of mode names that are to be activated by default, overriding the setting in the global configuration.
|
||||
# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
|
||||
# If the setting is undefined/empty, the default_modes from the global configuration (serena_config.yml) apply.
|
||||
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
|
||||
# Therefore, you can set this to [] if you do not want the default modes defined in the global config to apply
|
||||
# for this project.
|
||||
# This setting can, in turn, be overridden by CLI parameters (--mode).
|
||||
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
|
||||
default_modes:
|
||||
|
||||
# list of mode names to be activated additionally for this project, e.g. ["query-projects"]
|
||||
# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
|
||||
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
|
||||
added_modes:
|
||||
|
||||
# list of tool names to exclude.
|
||||
# This extends the existing exclusions (e.g. from the global configuration)
|
||||
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||
excluded_tools: []
|
||||
|
||||
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
|
||||
# This extends the existing inclusions (e.g. from the global configuration).
|
||||
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||
included_optional_tools: []
|
||||
|
||||
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
|
||||
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
|
||||
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||
fixed_tools: []
|
||||
|
||||
# time budget (seconds) per tool call for the retrieval of additional symbol information
|
||||
# such as docstrings or parameter information.
|
||||
# This overrides the corresponding setting in the global configuration; see the documentation there.
|
||||
# If null or missing, use the setting from the global configuration.
|
||||
symbol_info_budget:
|
||||
|
||||
# The language backend to use for this project.
|
||||
# If not set, the global setting from serena_config.yml is used.
|
||||
# Valid values: LSP, JetBrains
|
||||
# Note: the backend is fixed at startup. If a project with a different backend
|
||||
# is activated post-init, an error will be returned.
|
||||
language_backend:
|
||||
|
||||
# line ending convention to use when writing source files.
|
||||
# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default)
|
||||
# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings.
|
||||
line_ending:
|
||||
|
||||
# list of regex patterns which, when matched, mark a memory entry as read‑only.
|
||||
# Extends the list from the global configuration, merging the two lists.
|
||||
read_only_memory_patterns: []
|
||||
|
||||
# list of regex patterns for memories to completely ignore.
|
||||
# Matching memories will not appear in list_memories or activate_project output
|
||||
# and cannot be accessed via read_memory or write_memory.
|
||||
# To access ignored memory files, use the read_file tool on the raw file path.
|
||||
# Extends the list from the global configuration, merging the two lists.
|
||||
# Example: ["_archive/.*", "_episodes/.*"]
|
||||
ignored_memory_patterns: []
|
||||
|
||||
# advanced configuration option allowing to configure language server-specific options.
|
||||
# Maps the language key to the options.
|
||||
# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available.
|
||||
# No documentation on options means no options are available.
|
||||
ls_specific_settings: {}
|
||||
|
||||
# list of additional workspace folder paths for cross-package reference support (e.g. in monorepos).
|
||||
# Paths can be absolute or relative to the project root.
|
||||
# Each folder is registered as an LSP workspace folder, enabling language servers to discover
|
||||
# symbols and references across package boundaries.
|
||||
# Currently supported for: TypeScript.
|
||||
# Example:
|
||||
# additional_workspace_folders:
|
||||
# - ../sibling-package
|
||||
# - ../shared-lib
|
||||
additional_workspace_folders: []
|
||||
|
||||
# whether the project is in read-only mode
|
||||
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
|
||||
# Added on 2025-04-18
|
||||
read_only: false
|
||||
|
||||
# whether to use project's .gitignore files to ignore files
|
||||
ignore_all_files_in_gitignore: true
|
||||
|
||||
# initial prompt for the project. It will always be given to the LLM upon activating the project
|
||||
# (contrary to the memories, which are loaded on demand).
|
||||
initial_prompt: ''
|
||||
@@ -1,4 +1,5 @@
|
||||
* 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:
|
||||
* `cmake --build ./build_release`
|
||||
* `cmake --build ./build_debug`
|
||||
@@ -6,6 +7,7 @@
|
||||
* Always try the release build first before building with the debug version
|
||||
* Use the debug build only when it is useful to obtain a clear stack trace with symbols, inspect names, place breakpoints, or test a small case interactively
|
||||
* The debug build is very slow, so use it only on small fast tests such as operation validations, not on network validations
|
||||
* Always prepend rtk to shell commands if missing and if rtk is available
|
||||
|
||||
# Core engineering philosophy
|
||||
|
||||
|
||||
@@ -105,6 +105,9 @@ Pass these to `onnx-mlir` when compiling for PIM:
|
||||
the codegen tail.
|
||||
- `--pim-emit-json` - also emit `core_*.json` instruction files alongside
|
||||
`core_*.pim`.
|
||||
- `--pim-export-spatial-dataflow=<none|spatial1|spatial2|spatial3|all>` - control Spatial
|
||||
dataflow CSV reports. The default `all` emits graph, scheduled, and realized
|
||||
snapshots under `reports/`.
|
||||
- `--use-experimental-conv-impl` - use the alternate convolution lowering.
|
||||
- `--ignore-concat-error` - soft-fail a ConcatOp corner case.
|
||||
|
||||
@@ -167,7 +170,8 @@ Each validation run writes artifacts in the model workspace, for example under
|
||||
- `simulation/out.bin` - raw simulator output used for comparison.
|
||||
|
||||
The compiler currently dumps dialect snapshots such as `spatial0.mlir`,
|
||||
`spatial1_dcp_merged.mlir`, `pim0.mlir`, `pim1_buff.mlir`,
|
||||
`spatial1_graph.mlir`, `spatial2_scheduled_no_comm.mlir`,
|
||||
`spatial3_scheduled.mlir`, `pim0.mlir`, `pim1_buff.mlir`,
|
||||
`pim2_coalesced.mlir`, and `pim3_folded.mlir` when an output directory is
|
||||
available.
|
||||
|
||||
|
||||
Submodule backend-simulators/pim/pimsim-nn updated: 3e3442b663...6d3b898e6b
@@ -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.
|
||||
+1
-1
Submodule onnx-mlir updated: 82018d7ce5...eb54c2afc4
@@ -117,7 +117,6 @@ add_pim_library(OMPIMAccel
|
||||
SpatialOps
|
||||
PimOps
|
||||
OMONNXToSpatial
|
||||
OMSpatialToGraphviz
|
||||
OMSpatialToPim
|
||||
OMPimCommon
|
||||
OMPimBufferization
|
||||
|
||||
@@ -5,9 +5,13 @@ add_pim_library(OMPimCommon
|
||||
IR/ConstantUtils.cpp
|
||||
IR/CoreBlockUtils.cpp
|
||||
IR/EntryPointUtils.cpp
|
||||
IR/IndexingUtils.cpp
|
||||
IR/LoopUtils.cpp
|
||||
IR/ShapeUtils.cpp
|
||||
IR/ShapingUtils.cpp
|
||||
IR/StaticIntSequence.cpp
|
||||
IR/SubviewUtils.cpp
|
||||
IR/TensorSliceUtils.cpp
|
||||
IR/WeightUtils.cpp
|
||||
Support/CheckedArithmetic.cpp
|
||||
Support/DebugDump.cpp
|
||||
|
||||
@@ -34,12 +34,25 @@ mlir::Value resolveAlias(mlir::Value value, const StaticValueKnowledge* knowledg
|
||||
|
||||
llvm::FailureOr<CompiledIndexExpr> compileIndexValueImpl(mlir::Value value);
|
||||
llvm::FailureOr<CompiledAddressExpr> compileContiguousAddressExprImpl(mlir::Value value);
|
||||
mlir::Value resolveLoopCarriedAliasImpl(mlir::Value value, const StaticValueKnowledge* knowledge);
|
||||
|
||||
template <typename... Args>
|
||||
CompiledIndexExpr makeCompiledIndexExpr(Args&&... args) {
|
||||
return CompiledIndexExpr(std::make_shared<CompiledIndexExprNode>(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
static mlir::Value resolveForYieldedAliasToInit(mlir::scf::ForOp forOp,
|
||||
mlir::Value yieldedValue,
|
||||
const StaticValueKnowledge* knowledge) {
|
||||
yieldedValue = resolveLoopCarriedAliasImpl(yieldedValue, knowledge);
|
||||
if (auto blockArgument = mlir::dyn_cast<mlir::BlockArgument>(yieldedValue)) {
|
||||
if (blockArgument.getOwner() == forOp.getBody() && blockArgument.getArgNumber() > 0
|
||||
&& static_cast<unsigned>(blockArgument.getArgNumber() - 1) < forOp.getInitArgs().size())
|
||||
return resolveLoopCarriedAliasImpl(forOp.getInitArgs()[blockArgument.getArgNumber() - 1], knowledge);
|
||||
}
|
||||
return yieldedValue;
|
||||
}
|
||||
|
||||
mlir::Value resolveLoopCarriedAliasImpl(mlir::Value value, const StaticValueKnowledge* knowledge) {
|
||||
value = resolveAlias(value, knowledge);
|
||||
|
||||
@@ -60,15 +73,8 @@ mlir::Value resolveLoopCarriedAliasImpl(mlir::Value value, const StaticValueKnow
|
||||
auto result = mlir::dyn_cast<mlir::OpResult>(value);
|
||||
if (result) {
|
||||
auto yieldOp = mlir::dyn_cast<mlir::scf::YieldOp>(forOp.getBody()->getTerminator());
|
||||
if (yieldOp && result.getResultNumber() < yieldOp.getNumOperands()) {
|
||||
mlir::Value yieldedValue = resolveLoopCarriedAliasImpl(yieldOp.getOperand(result.getResultNumber()), knowledge);
|
||||
if (auto blockArgument = mlir::dyn_cast<mlir::BlockArgument>(yieldedValue)) {
|
||||
if (blockArgument.getOwner() == forOp.getBody() && blockArgument.getArgNumber() > 0
|
||||
&& static_cast<unsigned>(blockArgument.getArgNumber() - 1) < forOp.getInitArgs().size())
|
||||
return resolveLoopCarriedAliasImpl(forOp.getInitArgs()[blockArgument.getArgNumber() - 1], knowledge);
|
||||
}
|
||||
return yieldedValue;
|
||||
}
|
||||
if (yieldOp && result.getResultNumber() < yieldOp.getNumOperands())
|
||||
return resolveForYieldedAliasToInit(forOp, yieldOp.getOperand(result.getResultNumber()), knowledge);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -515,16 +521,7 @@ llvm::FailureOr<ResolvedContiguousAddress> resolveContiguousAddressImpl(mlir::Va
|
||||
return mlir::failure();
|
||||
|
||||
auto yieldOp = mlir::cast<mlir::scf::YieldOp>(forOp.getBody()->getTerminator());
|
||||
mlir::Value yieldedValue = resolveLoopCarriedAliasImpl(yieldOp.getOperand(result.getResultNumber()), knowledge);
|
||||
if (auto blockArgument = mlir::dyn_cast<mlir::BlockArgument>(yieldedValue)) {
|
||||
if (blockArgument.getOwner() == forOp.getBody() && blockArgument.getArgNumber() > 0
|
||||
&& static_cast<unsigned>(blockArgument.getArgNumber() - 1) < forOp.getInitArgs().size()) {
|
||||
value = resolveAlias(forOp.getInitArgs()[blockArgument.getArgNumber() - 1], knowledge);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
value = yieldedValue;
|
||||
value = resolveForYieldedAliasToInit(forOp, yieldOp.getOperand(result.getResultNumber()), knowledge);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -643,16 +640,7 @@ llvm::FailureOr<CompiledAddressExpr> compileContiguousAddressExprImpl(mlir::Valu
|
||||
return mlir::failure();
|
||||
|
||||
auto yieldOp = mlir::cast<mlir::scf::YieldOp>(forOp.getBody()->getTerminator());
|
||||
mlir::Value yieldedValue = yieldOp.getOperand(result.getResultNumber());
|
||||
if (auto blockArgument = mlir::dyn_cast<mlir::BlockArgument>(yieldedValue)) {
|
||||
if (blockArgument.getOwner() == forOp.getBody() && blockArgument.getArgNumber() > 0
|
||||
&& static_cast<unsigned>(blockArgument.getArgNumber() - 1) < forOp.getInitArgs().size()) {
|
||||
value = forOp.getInitArgs()[blockArgument.getArgNumber() - 1];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
value = yieldedValue;
|
||||
value = resolveForYieldedAliasToInit(forOp, yieldOp.getOperand(result.getResultNumber()), nullptr);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -862,7 +850,7 @@ llvm::FailureOr<ResolvedContiguousAddress> CompiledAddressExpr::evaluate(const S
|
||||
auto resolvedOffset = byteOffset.evaluate(knowledge);
|
||||
if (failed(resolvedOffset))
|
||||
return mlir::failure();
|
||||
return ResolvedContiguousAddress {base, *resolvedOffset};
|
||||
return ResolvedContiguousAddress {resolveAlias(base, &knowledge), *resolvedOffset};
|
||||
}
|
||||
|
||||
} // namespace onnx_mlir
|
||||
|
||||
@@ -31,7 +31,7 @@ static FailureOr<int64_t> ceilDivSigned(int64_t lhs, int64_t rhs) {
|
||||
}
|
||||
|
||||
Value createOrFoldAffineApply(
|
||||
RewriterBase& rewriter, Location loc, AffineMap map, ValueRange operands, Operation* constantAnchor) {
|
||||
OpBuilder& builder, Location loc, AffineMap map, ValueRange operands, Operation* constantAnchor) {
|
||||
assert(constantAnchor && "expected a valid constant anchor");
|
||||
assert(map.getNumResults() == 1 && "affine.apply expects a single-result affine map");
|
||||
|
||||
@@ -40,54 +40,91 @@ Value createOrFoldAffineApply(
|
||||
for (Value operand : operands) {
|
||||
std::optional<int64_t> constantValue = matchConstantIndexValue(operand);
|
||||
if (!constantValue)
|
||||
return affine::AffineApplyOp::create(rewriter, loc, map, operands).getResult();
|
||||
operandConstants.push_back(rewriter.getIndexAttr(*constantValue));
|
||||
return affine::AffineApplyOp::create(builder, loc, map, operands).getResult();
|
||||
operandConstants.push_back(builder.getIndexAttr(*constantValue));
|
||||
}
|
||||
|
||||
SmallVector<Attribute> foldedResults;
|
||||
if (succeeded(map.constantFold(operandConstants, foldedResults)) && foldedResults.size() == 1)
|
||||
if (auto constantResult = dyn_cast<IntegerAttr>(foldedResults.front()))
|
||||
return getOrCreateIndexConstant(rewriter, constantAnchor, constantResult.getInt());
|
||||
return getOrCreateIndexConstant(builder, constantAnchor, constantResult.getInt());
|
||||
|
||||
return affine::AffineApplyOp::create(rewriter, loc, map, operands).getResult();
|
||||
return affine::AffineApplyOp::create(builder, loc, map, operands).getResult();
|
||||
}
|
||||
|
||||
Value createOrFoldAffineApply(
|
||||
RewriterBase& rewriter, Location loc, AffineExpr expr, ValueRange dims, Operation* constantAnchor) {
|
||||
OpBuilder& builder, Location loc, AffineExpr expr, ValueRange dims, Operation* constantAnchor) {
|
||||
AffineMap map = AffineMap::get(/*dimCount=*/dims.size(), /*symbolCount=*/0, expr);
|
||||
return createOrFoldAffineApply(rewriter, loc, map, dims, constantAnchor);
|
||||
return createOrFoldAffineApply(builder, loc, map, dims, constantAnchor);
|
||||
}
|
||||
|
||||
Value affineMulConst(RewriterBase& rewriter, Location loc, Value value, int64_t multiplier, Operation* constantAnchor) {
|
||||
Value affineMulConst(OpBuilder& builder, Location loc, Value value, int64_t multiplier, Operation* constantAnchor) {
|
||||
assert(constantAnchor && "expected a valid constant anchor");
|
||||
if (multiplier == 0)
|
||||
return getOrCreateIndexConstant(rewriter, constantAnchor, 0);
|
||||
return getOrCreateIndexConstant(builder, constantAnchor, 0);
|
||||
if (multiplier == 1)
|
||||
return value;
|
||||
|
||||
AffineExpr d0 = getAffineDimExpr(0, rewriter.getContext());
|
||||
return createOrFoldAffineApply(rewriter, loc, d0 * multiplier, ValueRange {value}, constantAnchor);
|
||||
AffineExpr d0 = getAffineDimExpr(0, builder.getContext());
|
||||
return createOrFoldAffineApply(builder, loc, d0 * multiplier, ValueRange {value}, constantAnchor);
|
||||
}
|
||||
|
||||
Value affineModConst(RewriterBase& rewriter, Location loc, Value value, int64_t divisor, Operation* constantAnchor) {
|
||||
Value affineAddConst(OpBuilder& builder, Location loc, Value value, int64_t offset, Operation* constantAnchor) {
|
||||
assert(constantAnchor && "expected a valid constant anchor");
|
||||
if (offset == 0)
|
||||
return value;
|
||||
|
||||
AffineExpr d0 = getAffineDimExpr(0, builder.getContext());
|
||||
return createOrFoldAffineApply(builder, loc, d0 + offset, ValueRange {value}, constantAnchor);
|
||||
}
|
||||
|
||||
Value affineModConst(OpBuilder& builder, Location loc, Value value, int64_t divisor, Operation* constantAnchor) {
|
||||
assert(constantAnchor && "expected a valid constant anchor");
|
||||
assert(divisor > 0 && "expected a positive affine.mod divisor");
|
||||
if (divisor == 1)
|
||||
return getOrCreateIndexConstant(rewriter, constantAnchor, 0);
|
||||
return getOrCreateIndexConstant(builder, constantAnchor, 0);
|
||||
|
||||
AffineExpr d0 = getAffineDimExpr(0, rewriter.getContext());
|
||||
return createOrFoldAffineApply(rewriter, loc, d0 % divisor, ValueRange {value}, constantAnchor);
|
||||
AffineExpr d0 = getAffineDimExpr(0, builder.getContext());
|
||||
return createOrFoldAffineApply(builder, loc, d0 % divisor, ValueRange {value}, constantAnchor);
|
||||
}
|
||||
|
||||
Value affineFloorDivConst(
|
||||
RewriterBase& rewriter, Location loc, Value value, int64_t divisor, Operation* constantAnchor) {
|
||||
OpBuilder& builder, Location loc, Value value, int64_t divisor, Operation* constantAnchor) {
|
||||
assert(constantAnchor && "expected a valid constant anchor");
|
||||
assert(divisor > 0 && "expected a positive affine.floor_div divisor");
|
||||
if (divisor == 1)
|
||||
return value;
|
||||
|
||||
AffineExpr d0 = getAffineDimExpr(0, rewriter.getContext());
|
||||
return createOrFoldAffineApply(rewriter, loc, d0.floorDiv(divisor), ValueRange {value}, constantAnchor);
|
||||
AffineExpr d0 = getAffineDimExpr(0, builder.getContext());
|
||||
return createOrFoldAffineApply(builder, loc, d0.floorDiv(divisor), ValueRange {value}, constantAnchor);
|
||||
}
|
||||
|
||||
Value affineAddModConst(
|
||||
OpBuilder& builder, Location loc, Value value, int64_t offset, int64_t divisor, Operation* constantAnchor) {
|
||||
assert(constantAnchor && "expected a valid constant anchor");
|
||||
assert(divisor > 0 && "expected a positive affine.mod divisor");
|
||||
if (divisor == 1)
|
||||
return getOrCreateIndexConstant(builder, constantAnchor, 0);
|
||||
|
||||
AffineExpr d0 = getAffineDimExpr(0, builder.getContext());
|
||||
AffineExpr expr = d0;
|
||||
if (offset != 0)
|
||||
expr = expr + offset;
|
||||
return createOrFoldAffineApply(builder, loc, expr % divisor, ValueRange {value}, constantAnchor);
|
||||
}
|
||||
|
||||
Value affineAddFloorDivConst(
|
||||
OpBuilder& builder, Location loc, Value value, int64_t offset, int64_t divisor, Operation* constantAnchor) {
|
||||
assert(constantAnchor && "expected a valid constant anchor");
|
||||
assert(divisor > 0 && "expected a positive affine.floor_div divisor");
|
||||
if (divisor == 1)
|
||||
return offset == 0 ? value : affineAddConst(builder, loc, value, offset, constantAnchor);
|
||||
|
||||
AffineExpr d0 = getAffineDimExpr(0, builder.getContext());
|
||||
AffineExpr expr = d0;
|
||||
if (offset != 0)
|
||||
expr = expr + offset;
|
||||
return createOrFoldAffineApply(builder, loc, expr.floorDiv(divisor), ValueRange {value}, constantAnchor);
|
||||
}
|
||||
|
||||
FailureOr<int64_t> evaluateAffineExpr(AffineExpr expr, ArrayRef<int64_t> dims, ArrayRef<int64_t> symbols) {
|
||||
|
||||
@@ -11,36 +11,56 @@ namespace onnx_mlir {
|
||||
|
||||
using IndexValueResolver = llvm::function_ref<llvm::FailureOr<int64_t>(mlir::Value)>;
|
||||
|
||||
mlir::Value createOrFoldAffineApply(mlir::RewriterBase& rewriter,
|
||||
mlir::Value createOrFoldAffineApply(mlir::OpBuilder& builder,
|
||||
mlir::Location loc,
|
||||
mlir::AffineMap map,
|
||||
mlir::ValueRange operands,
|
||||
mlir::Operation* constantAnchor);
|
||||
|
||||
mlir::Value createOrFoldAffineApply(mlir::RewriterBase& rewriter,
|
||||
mlir::Value createOrFoldAffineApply(mlir::OpBuilder& builder,
|
||||
mlir::Location loc,
|
||||
mlir::AffineExpr expr,
|
||||
mlir::ValueRange dims,
|
||||
mlir::Operation* constantAnchor);
|
||||
|
||||
mlir::Value affineMulConst(mlir::RewriterBase& rewriter,
|
||||
mlir::Value affineMulConst(mlir::OpBuilder& builder,
|
||||
mlir::Location loc,
|
||||
mlir::Value value,
|
||||
int64_t multiplier,
|
||||
mlir::Operation* constantAnchor);
|
||||
|
||||
mlir::Value affineModConst(mlir::RewriterBase& rewriter,
|
||||
mlir::Value affineAddConst(mlir::OpBuilder& builder,
|
||||
mlir::Location loc,
|
||||
mlir::Value value,
|
||||
int64_t offset,
|
||||
mlir::Operation* constantAnchor);
|
||||
|
||||
mlir::Value affineModConst(mlir::OpBuilder& builder,
|
||||
mlir::Location loc,
|
||||
mlir::Value value,
|
||||
int64_t divisor,
|
||||
mlir::Operation* constantAnchor);
|
||||
|
||||
mlir::Value affineFloorDivConst(mlir::RewriterBase& rewriter,
|
||||
mlir::Value affineFloorDivConst(mlir::OpBuilder& builder,
|
||||
mlir::Location loc,
|
||||
mlir::Value value,
|
||||
int64_t divisor,
|
||||
mlir::Operation* constantAnchor);
|
||||
|
||||
mlir::Value affineAddModConst(mlir::OpBuilder& builder,
|
||||
mlir::Location loc,
|
||||
mlir::Value value,
|
||||
int64_t offset,
|
||||
int64_t divisor,
|
||||
mlir::Operation* constantAnchor);
|
||||
|
||||
mlir::Value affineAddFloorDivConst(mlir::OpBuilder& builder,
|
||||
mlir::Location loc,
|
||||
mlir::Value value,
|
||||
int64_t offset,
|
||||
int64_t divisor,
|
||||
mlir::Operation* constantAnchor);
|
||||
|
||||
llvm::FailureOr<int64_t>
|
||||
evaluateAffineExpr(mlir::AffineExpr expr, llvm::ArrayRef<int64_t> dims, llvm::ArrayRef<int64_t> symbols = {});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
@@ -9,6 +10,65 @@ llvm::SmallVector<int32_t> getBatchCoreIds(pim::PimCoreBatchOp coreBatchOp) {
|
||||
return llvm::SmallVector<int32_t>(coreIdsAttr.asArrayRef().begin(), coreIdsAttr.asArrayRef().end());
|
||||
}
|
||||
|
||||
mlir::FailureOr<std::optional<int32_t>>
|
||||
getOptionalScheduledCoreId(spatial::SpatScheduledCompute computeOp, llvm::StringRef fieldName) {
|
||||
auto coreIdAttr = computeOp->getAttrOfType<mlir::IntegerAttr>(onnx_mlir::kCoreIdAttrName);
|
||||
if (!coreIdAttr)
|
||||
return std::optional<int32_t> {};
|
||||
if (coreIdAttr.getInt() < 0) {
|
||||
computeOp.emitOpError() << fieldName << " must be non-negative";
|
||||
return mlir::failure();
|
||||
}
|
||||
auto checkedCoreId = pim::checkedI32(coreIdAttr.getInt(), computeOp, fieldName);
|
||||
if (mlir::failed(checkedCoreId))
|
||||
return mlir::failure();
|
||||
return std::optional<int32_t> {*checkedCoreId};
|
||||
}
|
||||
|
||||
mlir::FailureOr<int32_t> getRequiredScheduledCoreId(spatial::SpatScheduledCompute computeOp, llvm::StringRef fieldName) {
|
||||
auto coreId = getOptionalScheduledCoreId(computeOp, fieldName);
|
||||
if (mlir::failed(coreId))
|
||||
return mlir::failure();
|
||||
if (!*coreId) {
|
||||
computeOp.emitOpError() << "missing required " << onnx_mlir::kCoreIdAttrName;
|
||||
return mlir::failure();
|
||||
}
|
||||
return **coreId;
|
||||
}
|
||||
|
||||
mlir::FailureOr<std::optional<llvm::SmallVector<int32_t>>>
|
||||
getOptionalScheduledBatchCoreIds(spatial::SpatScheduledComputeBatch computeBatchOp, llvm::StringRef fieldName) {
|
||||
auto coreIdsAttr = computeBatchOp->getAttrOfType<mlir::DenseI32ArrayAttr>(onnx_mlir::kCoreIdsAttrName);
|
||||
if (!coreIdsAttr)
|
||||
return std::optional<llvm::SmallVector<int32_t>> {};
|
||||
|
||||
llvm::SmallVector<int32_t> coreIds;
|
||||
coreIds.reserve(coreIdsAttr.size());
|
||||
for (int32_t coreId : coreIdsAttr.asArrayRef()) {
|
||||
if (coreId < 0) {
|
||||
computeBatchOp.emitOpError() << fieldName << " values must be non-negative";
|
||||
return mlir::failure();
|
||||
}
|
||||
auto checkedCoreId = pim::checkedI32(static_cast<int64_t>(coreId), computeBatchOp, fieldName);
|
||||
if (mlir::failed(checkedCoreId))
|
||||
return mlir::failure();
|
||||
coreIds.push_back(*checkedCoreId);
|
||||
}
|
||||
return std::optional<llvm::SmallVector<int32_t>> {std::move(coreIds)};
|
||||
}
|
||||
|
||||
mlir::FailureOr<llvm::SmallVector<int32_t>>
|
||||
getRequiredScheduledBatchCoreIds(spatial::SpatScheduledComputeBatch computeBatchOp, llvm::StringRef fieldName) {
|
||||
auto coreIds = getOptionalScheduledBatchCoreIds(computeBatchOp, fieldName);
|
||||
if (mlir::failed(coreIds))
|
||||
return mlir::failure();
|
||||
if (!*coreIds) {
|
||||
computeBatchOp.emitOpError() << "missing required " << onnx_mlir::kCoreIdsAttrName;
|
||||
return mlir::failure();
|
||||
}
|
||||
return std::move(**coreIds);
|
||||
}
|
||||
|
||||
llvm::SmallVector<int32_t> getLaneChunkCoreIds(llvm::ArrayRef<int32_t> coreIds, size_t laneCount, unsigned lane) {
|
||||
llvm::SmallVector<int32_t> laneCoreIds;
|
||||
laneCoreIds.reserve(coreIds.size() / laneCount);
|
||||
|
||||
@@ -3,12 +3,26 @@
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
llvm::SmallVector<int32_t> getBatchCoreIds(pim::PimCoreBatchOp coreBatchOp);
|
||||
|
||||
mlir::FailureOr<std::optional<int32_t>>
|
||||
getOptionalScheduledCoreId(spatial::SpatScheduledCompute computeOp, llvm::StringRef fieldName);
|
||||
|
||||
mlir::FailureOr<int32_t> getRequiredScheduledCoreId(spatial::SpatScheduledCompute computeOp, llvm::StringRef fieldName);
|
||||
|
||||
mlir::FailureOr<std::optional<llvm::SmallVector<int32_t>>>
|
||||
getOptionalScheduledBatchCoreIds(spatial::SpatScheduledComputeBatch computeBatchOp, llvm::StringRef fieldName);
|
||||
|
||||
mlir::FailureOr<llvm::SmallVector<int32_t>>
|
||||
getRequiredScheduledBatchCoreIds(spatial::SpatScheduledComputeBatch computeBatchOp, llvm::StringRef fieldName);
|
||||
|
||||
llvm::SmallVector<int32_t> getLaneChunkCoreIds(llvm::ArrayRef<int32_t> coreIds, size_t laneCount, unsigned lane);
|
||||
|
||||
bool isExplicitHostMemCopyOperand(mlir::Operation* op, unsigned operandIndex);
|
||||
|
||||
@@ -10,6 +10,32 @@ using namespace mlir;
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
ConstantPool::ConstantPool(Operation *constantAnchor, OpBuilder &builder)
|
||||
: anchor(constantAnchor), block(getConstantInsertionBlock(constantAnchor)),
|
||||
builder(builder) {
|
||||
for (Operation &op : *block)
|
||||
if (auto constant = dyn_cast<arith::ConstantOp>(&op))
|
||||
cache.try_emplace(
|
||||
std::make_pair(constant.getType(), constant.getValue()),
|
||||
constant.getResult());
|
||||
}
|
||||
|
||||
Value ConstantPool::getIndex(int64_t value) {
|
||||
return get(builder.getIndexType(), builder.getIndexAttr(value));
|
||||
}
|
||||
|
||||
Value ConstantPool::get(Type type, Attribute value) {
|
||||
auto key = std::make_pair(type, value);
|
||||
if (Value existing = cache.lookup(key))
|
||||
return existing;
|
||||
OpBuilder::InsertionGuard guard(builder);
|
||||
builder.setInsertionPointToStart(block);
|
||||
Value constant = arith::ConstantOp::create(
|
||||
builder, anchor->getLoc(), type, cast<TypedAttr>(value)).getResult();
|
||||
cache.try_emplace(key, constant);
|
||||
return constant;
|
||||
}
|
||||
|
||||
static std::optional<int64_t> getIndexConstantValue(arith::ConstantOp constantOp) {
|
||||
if (!constantOp.getType().isIndex())
|
||||
return std::nullopt;
|
||||
@@ -49,7 +75,7 @@ Value getOrCreateConstant(OperationFolder& folder, Operation* anchorOp, Attribut
|
||||
return folder.getOrCreateConstant(hostBlock, arithDialect, value, type);
|
||||
}
|
||||
|
||||
Value getOrCreateConstant(RewriterBase& rewriter, Operation* anchorOp, Attribute value, Type type) {
|
||||
Value getOrCreateConstant(OpBuilder& builder, Operation* anchorOp, Attribute value, Type type) {
|
||||
assert(anchorOp && "expected a valid anchor operation");
|
||||
Block* hostBlock = getConstantInsertionBlock(anchorOp);
|
||||
for (Operation& op : *hostBlock) {
|
||||
@@ -59,9 +85,16 @@ Value getOrCreateConstant(RewriterBase& rewriter, Operation* anchorOp, Attribute
|
||||
return constantOp.getResult();
|
||||
}
|
||||
|
||||
OpBuilder::InsertionGuard guard(rewriter);
|
||||
rewriter.setInsertionPointToStart(hostBlock);
|
||||
return arith::ConstantOp::create(rewriter, anchorOp->getLoc(), type, cast<TypedAttr>(value)).getResult();
|
||||
OpBuilder::InsertionGuard guard(builder);
|
||||
builder.setInsertionPointToStart(hostBlock);
|
||||
return arith::ConstantOp::create(builder, anchorOp->getLoc(), type, cast<TypedAttr>(value)).getResult();
|
||||
}
|
||||
|
||||
Value createConstantAtHostBlockStart(OpBuilder& builder, Operation* anchorOp, TypedAttr value) {
|
||||
assert(anchorOp && "expected a valid anchor operation");
|
||||
OpBuilder::InsertionGuard guard(builder);
|
||||
builder.setInsertionPointToStart(getConstantInsertionBlock(anchorOp));
|
||||
return arith::ConstantOp::create(builder, anchorOp->getLoc(), value).getResult();
|
||||
}
|
||||
|
||||
Value getOrCreateConstantLike(OperationFolder& folder, arith::ConstantOp constantOp) {
|
||||
@@ -73,9 +106,8 @@ Value getOrCreateIndexConstant(OperationFolder& folder, Operation* anchorOp, int
|
||||
return getOrCreateConstant(folder, anchorOp, builder.getIndexAttr(value), builder.getIndexType());
|
||||
}
|
||||
|
||||
Value getOrCreateIndexConstant(RewriterBase& rewriter, Operation* anchorOp, int64_t value) {
|
||||
Builder builder(anchorOp->getContext());
|
||||
return getOrCreateConstant(rewriter, anchorOp, builder.getIndexAttr(value), builder.getIndexType());
|
||||
Value getOrCreateIndexConstant(OpBuilder& builder, Operation* anchorOp, int64_t value) {
|
||||
return getOrCreateConstant(builder, anchorOp, builder.getIndexAttr(value), builder.getIndexType());
|
||||
}
|
||||
|
||||
void hoistAndUniquifyIndexConstants(func::FuncOp funcOp, RewriterBase& rewriter) {
|
||||
|
||||
@@ -6,23 +6,42 @@
|
||||
#include "mlir/IR/Value.h"
|
||||
#include "mlir/Transforms/FoldUtils.h"
|
||||
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
class ConstantPool {
|
||||
public:
|
||||
ConstantPool(mlir::Operation *constantAnchor, mlir::OpBuilder &builder);
|
||||
|
||||
mlir::Value getIndex(int64_t value);
|
||||
mlir::Value get(mlir::Type type, mlir::Attribute value);
|
||||
|
||||
private:
|
||||
mlir::Operation *anchor;
|
||||
mlir::Block *block;
|
||||
mlir::OpBuilder &builder;
|
||||
llvm::DenseMap<std::pair<mlir::Type, mlir::Attribute>, mlir::Value> cache;
|
||||
};
|
||||
|
||||
mlir::Block* getConstantInsertionBlock(mlir::Operation* anchorOp);
|
||||
|
||||
mlir::Value
|
||||
getOrCreateConstant(mlir::OperationFolder& folder, mlir::Operation* anchorOp, mlir::Attribute value, mlir::Type type);
|
||||
|
||||
mlir::Value
|
||||
getOrCreateConstant(mlir::RewriterBase& rewriter, mlir::Operation* anchorOp, mlir::Attribute value, mlir::Type type);
|
||||
getOrCreateConstant(mlir::OpBuilder& builder, mlir::Operation* anchorOp, mlir::Attribute value, mlir::Type type);
|
||||
|
||||
mlir::Value
|
||||
createConstantAtHostBlockStart(mlir::OpBuilder& builder, mlir::Operation* anchorOp, mlir::TypedAttr value);
|
||||
|
||||
mlir::Value getOrCreateConstantLike(mlir::OperationFolder& folder, mlir::arith::ConstantOp constantOp);
|
||||
|
||||
mlir::Value getOrCreateIndexConstant(mlir::OperationFolder& folder, mlir::Operation* anchorOp, int64_t value);
|
||||
|
||||
mlir::Value getOrCreateIndexConstant(mlir::RewriterBase& rewriter, mlir::Operation* anchorOp, int64_t value);
|
||||
mlir::Value getOrCreateIndexConstant(mlir::OpBuilder& builder, mlir::Operation* anchorOp, int64_t value);
|
||||
|
||||
void hoistAndUniquifyIndexConstants(mlir::func::FuncOp funcOp, mlir::RewriterBase& rewriter);
|
||||
|
||||
|
||||
@@ -36,9 +36,10 @@ bool isCoreStaticAddressOp(mlir::Operation* op) {
|
||||
|
||||
mlir::LogicalResult
|
||||
walkPimCoreBlock(mlir::Block& block,
|
||||
const StaticValueKnowledge& knowledge,
|
||||
const StaticValueKnowledge& initialKnowledge,
|
||||
llvm::function_ref<mlir::LogicalResult(mlir::Operation&, const StaticValueKnowledge&)> callback) {
|
||||
bool hasFailure = false;
|
||||
StaticValueKnowledge knowledge = initialKnowledge;
|
||||
for (mlir::Operation& op : block) {
|
||||
if (mlir::isa<pim::PimHaltOp, mlir::scf::YieldOp>(op) || isCoreStaticAddressOp(&op))
|
||||
continue;
|
||||
@@ -74,6 +75,42 @@ walkPimCoreBlock(mlir::Block& block,
|
||||
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)))
|
||||
hasFailure = true;
|
||||
}
|
||||
@@ -82,9 +119,10 @@ walkPimCoreBlock(mlir::Block& block,
|
||||
|
||||
mlir::LogicalResult walkPimCoreBlockStructurally(
|
||||
mlir::Block& block,
|
||||
const StaticValueKnowledge& knowledge,
|
||||
const StaticValueKnowledge& initialKnowledge,
|
||||
llvm::function_ref<mlir::LogicalResult(mlir::Operation&, const StaticValueKnowledge&)> callback) {
|
||||
bool hasFailure = false;
|
||||
StaticValueKnowledge knowledge = initialKnowledge;
|
||||
for (mlir::Operation& op : block) {
|
||||
if (mlir::isa<pim::PimHaltOp, mlir::scf::YieldOp>(op) || isCoreStaticAddressOp(&op))
|
||||
continue;
|
||||
@@ -128,6 +166,44 @@ mlir::LogicalResult walkPimCoreBlockStructurally(
|
||||
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)))
|
||||
hasFailure = true;
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include "IndexingUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/IndexingUtils.hpp"
|
||||
|
||||
using namespace mlir;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
||||
|
||||
namespace onnx_mlir {
|
||||
@@ -163,4 +166,80 @@ bool isContiguousSubviewWithDynamicOffsets(llvm::ArrayRef<int64_t> sourceShape,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool hasStaticPositiveShape(llvm::ArrayRef<int64_t> shape) {
|
||||
return llvm::all_of(shape, [](int64_t dim) { return dim > 0; });
|
||||
}
|
||||
|
||||
bool hasStaticPositiveShape(mlir::RankedTensorType type) {
|
||||
return type.hasStaticShape() && hasStaticPositiveShape(type.getShape());
|
||||
}
|
||||
|
||||
int64_t getStaticShapeElementCount(llvm::ArrayRef<int64_t> shape) {
|
||||
return std::accumulate(shape.begin(), shape.end(), int64_t {1}, std::multiplies<int64_t> {});
|
||||
}
|
||||
|
||||
llvm::SmallVector<int64_t> permuteShape(llvm::ArrayRef<int64_t> shape, llvm::ArrayRef<int64_t> permutation) {
|
||||
llvm::SmallVector<int64_t> permutedShape;
|
||||
permutedShape.reserve(permutation.size());
|
||||
for (int64_t axis : permutation)
|
||||
permutedShape.push_back(shape[axis]);
|
||||
return permutedShape;
|
||||
}
|
||||
|
||||
llvm::SmallVector<int64_t> invertPermutation(llvm::ArrayRef<int64_t> permutation) {
|
||||
llvm::SmallVector<int64_t> inversePermutation(permutation.size());
|
||||
for (auto [newIndex, oldIndex] : llvm::enumerate(permutation))
|
||||
inversePermutation[oldIndex] = static_cast<int64_t>(newIndex);
|
||||
return inversePermutation;
|
||||
}
|
||||
|
||||
mlir::FailureOr<llvm::SmallVector<int64_t>>
|
||||
getTransposePermutationChecked(std::optional<mlir::ArrayAttr> permAttr, int64_t rank) {
|
||||
llvm::SmallVector<int64_t> permutation;
|
||||
if (!permAttr) {
|
||||
permutation.reserve(rank);
|
||||
for (int64_t dim = rank - 1; dim >= 0; --dim)
|
||||
permutation.push_back(dim);
|
||||
return permutation;
|
||||
}
|
||||
|
||||
if (static_cast<int64_t>(permAttr->size()) != rank)
|
||||
return mlir::failure();
|
||||
|
||||
permutation.reserve(permAttr->size());
|
||||
llvm::SmallVector<bool> seen(rank, false);
|
||||
for (mlir::IntegerAttr attr : permAttr->getAsRange<mlir::IntegerAttr>()) {
|
||||
int64_t axis = attr.getInt();
|
||||
if (axis < 0 || axis >= rank || seen[axis])
|
||||
return mlir::failure();
|
||||
seen[axis] = true;
|
||||
permutation.push_back(axis);
|
||||
}
|
||||
return permutation;
|
||||
}
|
||||
|
||||
llvm::SmallVector<mlir::OpFoldResult> getStaticIndexAttrs(mlir::Builder& builder, llvm::ArrayRef<int64_t> values) {
|
||||
llvm::SmallVector<mlir::OpFoldResult> attrs;
|
||||
attrs.reserve(values.size());
|
||||
for (int64_t value : values)
|
||||
attrs.push_back(builder.getIndexAttr(value));
|
||||
return attrs;
|
||||
}
|
||||
|
||||
llvm::SmallVector<mlir::OpFoldResult> getUnitStrides(mlir::PatternRewriter& rewriter, int64_t rank) {
|
||||
return llvm::SmallVector<mlir::OpFoldResult>(rank, rewriter.getIndexAttr(1));
|
||||
}
|
||||
|
||||
llvm::SmallVector<mlir::OpFoldResult> getZeroOffsets(mlir::PatternRewriter& rewriter, int64_t rank) {
|
||||
return llvm::SmallVector<mlir::OpFoldResult>(rank, rewriter.getIndexAttr(0));
|
||||
}
|
||||
|
||||
llvm::SmallVector<mlir::OpFoldResult> getStaticSizes(mlir::PatternRewriter& rewriter, llvm::ArrayRef<int64_t> shape) {
|
||||
llvm::SmallVector<mlir::OpFoldResult> sizes;
|
||||
sizes.reserve(shape.size());
|
||||
for (int64_t dim : shape)
|
||||
sizes.push_back(rewriter.getIndexAttr(dim));
|
||||
return sizes;
|
||||
}
|
||||
|
||||
} // namespace onnx_mlir
|
||||
|
||||
@@ -2,15 +2,23 @@
|
||||
|
||||
#include "mlir/IR/BuiltinTypes.h"
|
||||
#include "mlir/IR/OpDefinition.h"
|
||||
#include "mlir/IR/PatternMatch.h"
|
||||
#include "mlir/IR/Value.h"
|
||||
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
using HSliceId = size_t;
|
||||
using CoreId = size_t;
|
||||
|
||||
llvm::SmallVector<int64_t> computeRowMajorStrides(llvm::ArrayRef<int64_t> shape);
|
||||
|
||||
llvm::SmallVector<int64_t>
|
||||
@@ -36,4 +44,69 @@ bool isContiguousSubviewWithDynamicOffsets(llvm::ArrayRef<int64_t> sourceShape,
|
||||
llvm::ArrayRef<int64_t> staticSizes,
|
||||
llvm::ArrayRef<int64_t> staticStrides);
|
||||
|
||||
template <class A, class B, class C = std::common_type_t<A, B>>
|
||||
constexpr C ceilIntegerDivide(A a, B b) {
|
||||
static_assert(std::is_integral_v<A>, "A must be an integer type");
|
||||
static_assert(std::is_integral_v<B>, "B must be an integer type");
|
||||
C ac = static_cast<C>(a);
|
||||
C bc = static_cast<C>(b);
|
||||
return 1 + (ac - 1) / bc;
|
||||
}
|
||||
|
||||
template <class A, class B, class C = std::common_type_t<A, B>>
|
||||
constexpr std::pair<C, C> ceilIntegerDivideWithRemainder(A a, B b) {
|
||||
static_assert(std::is_integral_v<A>, "A must be an integer type");
|
||||
static_assert(std::is_integral_v<B>, "B must be an integer type");
|
||||
C ac = static_cast<C>(a);
|
||||
C bc = static_cast<C>(b);
|
||||
return {ceilIntegerDivide(ac, bc), ac % bc};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool isVectorShape(mlir::ArrayRef<T> shape) {
|
||||
return shape.size() == 2 && (shape[0] == 1 || shape[1] == 1);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool isMatrixShape(mlir::ArrayRef<T> shape) {
|
||||
return shape.size() == 2;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool isHVectorShape(mlir::ArrayRef<T> shape) {
|
||||
return shape.size() == 2 && shape[0] == 1;
|
||||
}
|
||||
|
||||
inline auto getTensorShape(mlir::Value tensor) {
|
||||
return mlir::cast<mlir::RankedTensorType>(tensor.getType()).getShape();
|
||||
}
|
||||
|
||||
inline bool haveSameStaticShape(mlir::Value lhs, mlir::Value rhs) {
|
||||
auto lhsType = mlir::dyn_cast<mlir::RankedTensorType>(lhs.getType());
|
||||
auto rhsType = mlir::dyn_cast<mlir::RankedTensorType>(rhs.getType());
|
||||
return lhsType && rhsType && lhsType.hasStaticShape() && rhsType.hasStaticShape()
|
||||
&& lhsType.getShape() == rhsType.getShape();
|
||||
}
|
||||
|
||||
bool hasStaticPositiveShape(mlir::ArrayRef<int64_t> shape);
|
||||
|
||||
bool hasStaticPositiveShape(mlir::RankedTensorType type);
|
||||
|
||||
int64_t getStaticShapeElementCount(mlir::ArrayRef<int64_t> shape);
|
||||
|
||||
llvm::SmallVector<int64_t> permuteShape(mlir::ArrayRef<int64_t> shape, mlir::ArrayRef<int64_t> permutation);
|
||||
|
||||
llvm::SmallVector<int64_t> invertPermutation(mlir::ArrayRef<int64_t> permutation);
|
||||
|
||||
mlir::FailureOr<llvm::SmallVector<int64_t>> getTransposePermutationChecked(std::optional<mlir::ArrayAttr> permAttr,
|
||||
int64_t rank);
|
||||
|
||||
llvm::SmallVector<mlir::OpFoldResult> getStaticIndexAttrs(mlir::Builder& builder, llvm::ArrayRef<int64_t> values);
|
||||
|
||||
llvm::SmallVector<mlir::OpFoldResult> getUnitStrides(mlir::PatternRewriter& rewriter, int64_t rank);
|
||||
|
||||
llvm::SmallVector<mlir::OpFoldResult> getZeroOffsets(mlir::PatternRewriter& rewriter, int64_t rank);
|
||||
|
||||
llvm::SmallVector<mlir::OpFoldResult> getStaticSizes(mlir::PatternRewriter& rewriter, llvm::ArrayRef<int64_t> shape);
|
||||
|
||||
} // namespace onnx_mlir
|
||||
|
||||
@@ -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,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,117 @@
|
||||
#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; }
|
||||
|
||||
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
|
||||
@@ -0,0 +1,123 @@
|
||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||
|
||||
#include "src/Accelerators/PIM/Common/IR/TensorSliceUtils.hpp"
|
||||
|
||||
using namespace mlir;
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
Value extractAxisSlice(
|
||||
PatternRewriter& rewriter, Location loc, Value source, int64_t axis, int64_t offset, int64_t size) {
|
||||
auto sourceType = cast<RankedTensorType>(source.getType());
|
||||
SmallVector<int64_t> resultShape(sourceType.getShape());
|
||||
resultShape[axis] = size;
|
||||
auto resultType = RankedTensorType::get(resultShape, sourceType.getElementType(), sourceType.getEncoding());
|
||||
|
||||
SmallVector<OpFoldResult> offsets = getZeroOffsets(rewriter, sourceType.getRank());
|
||||
SmallVector<OpFoldResult> sizes = getStaticSizes(rewriter, sourceType.getShape());
|
||||
offsets[axis] = rewriter.getIndexAttr(offset);
|
||||
sizes[axis] = rewriter.getIndexAttr(size);
|
||||
return tensor::ExtractSliceOp::create(
|
||||
rewriter, loc, resultType, source, offsets, sizes, getUnitStrides(rewriter, sourceType.getRank()))
|
||||
.getResult();
|
||||
}
|
||||
|
||||
Value extractStaticSliceOrIdentity(RewriterBase& rewriter,
|
||||
Location loc,
|
||||
Value source,
|
||||
RankedTensorType resultType,
|
||||
ArrayRef<OpFoldResult> offsets,
|
||||
ArrayRef<OpFoldResult> sizes,
|
||||
ArrayRef<OpFoldResult> strides) {
|
||||
auto sourceType = cast<RankedTensorType>(source.getType());
|
||||
size_t rank = static_cast<size_t>(sourceType.getRank());
|
||||
|
||||
bool isIdentitySlice =
|
||||
sourceType == resultType && sourceType.hasStaticShape() && offsets.size() == rank && sizes.size() == rank
|
||||
&& strides.size() == rank;
|
||||
if (isIdentitySlice) {
|
||||
ArrayRef<int64_t> sourceShape = sourceType.getShape();
|
||||
for (auto [dim, offset, size, stride] : llvm::zip_equal(sourceShape, offsets, sizes, strides)) {
|
||||
std::optional<int64_t> staticOffset = mlir::getConstantIntValue(offset);
|
||||
std::optional<int64_t> staticSize = mlir::getConstantIntValue(size);
|
||||
std::optional<int64_t> staticStride = mlir::getConstantIntValue(stride);
|
||||
if (!staticOffset || !staticSize || !staticStride || *staticOffset != 0 || *staticSize != dim
|
||||
|| *staticStride != 1) {
|
||||
isIdentitySlice = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isIdentitySlice)
|
||||
return source;
|
||||
|
||||
return tensor::ExtractSliceOp::create(rewriter, loc, resultType, source, offsets, sizes, strides).getResult();
|
||||
}
|
||||
|
||||
Value insertStaticSlice(
|
||||
PatternRewriter& rewriter, Location loc, Value source, Value dest, ArrayRef<OpFoldResult> offsets) {
|
||||
auto sourceType = cast<RankedTensorType>(source.getType());
|
||||
return tensor::InsertSliceOp::create(rewriter,
|
||||
loc,
|
||||
source,
|
||||
dest,
|
||||
offsets,
|
||||
getStaticSizes(rewriter, sourceType.getShape()),
|
||||
getUnitStrides(rewriter, sourceType.getRank()))
|
||||
.getResult();
|
||||
}
|
||||
|
||||
Value extractMixedSliceOrIdentity(RewriterBase &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
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||
#include "mlir/IR/ValueRange.h"
|
||||
#include "mlir/Transforms/DialectConversion.h"
|
||||
|
||||
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
struct MixedSliceGeometry {
|
||||
llvm::SmallVector<mlir::OpFoldResult> offsets;
|
||||
llvm::SmallVector<mlir::OpFoldResult> sizes;
|
||||
llvm::SmallVector<mlir::OpFoldResult> strides;
|
||||
};
|
||||
|
||||
mlir::Value extractAxisSlice(
|
||||
mlir::PatternRewriter& rewriter, mlir::Location loc, mlir::Value source, int64_t axis, int64_t offset, int64_t size);
|
||||
|
||||
mlir::Value extractStaticSliceOrIdentity(mlir::RewriterBase& rewriter,
|
||||
mlir::Location loc,
|
||||
mlir::Value source,
|
||||
mlir::RankedTensorType resultType,
|
||||
llvm::ArrayRef<mlir::OpFoldResult> offsets,
|
||||
llvm::ArrayRef<mlir::OpFoldResult> sizes,
|
||||
llvm::ArrayRef<mlir::OpFoldResult> strides);
|
||||
|
||||
mlir::Value insertStaticSlice(mlir::PatternRewriter& rewriter,
|
||||
mlir::Location loc,
|
||||
mlir::Value source,
|
||||
mlir::Value dest,
|
||||
llvm::ArrayRef<mlir::OpFoldResult> offsets);
|
||||
|
||||
mlir::Value extractMixedSliceOrIdentity(mlir::RewriterBase &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
|
||||
@@ -1,315 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/ADT/ilist_node.h"
|
||||
#include "llvm/ADT/simple_ilist.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
template <typename NodeT>
|
||||
class LabeledList;
|
||||
|
||||
template <typename NodeT>
|
||||
class LabeledListNode : public llvm::ilist_node<NodeT> {
|
||||
friend class LabeledList<NodeT>;
|
||||
|
||||
public:
|
||||
using Label = uint64_t;
|
||||
|
||||
LabeledListNode() = default;
|
||||
LabeledListNode(const LabeledListNode&) = delete;
|
||||
LabeledListNode(LabeledListNode&&) = default;
|
||||
LabeledListNode& operator=(LabeledListNode&&) = delete;
|
||||
|
||||
~LabeledListNode() { assert(owner_ == nullptr && "destroying a linked LabeledListNode"); }
|
||||
|
||||
bool isLinked() const { return owner_ != nullptr; }
|
||||
Label getOrderLabel() const { return label; }
|
||||
|
||||
friend bool operator<(const LabeledListNode& lft, const LabeledListNode& rgt) { return lft.label < rgt.label; }
|
||||
|
||||
private:
|
||||
const void* owner_ = nullptr;
|
||||
Label label = 0;
|
||||
};
|
||||
|
||||
template <typename NodeT>
|
||||
class LabeledList {
|
||||
|
||||
using Label = typename NodeT::Label;
|
||||
|
||||
static constexpr Label kLowerSentinel = 0;
|
||||
static constexpr Label kUpperSentinel = std::numeric_limits<Label>::max();
|
||||
static constexpr Label kRelabelGap = 2;
|
||||
|
||||
public:
|
||||
using List = llvm::simple_ilist<NodeT>;
|
||||
using Iterator = typename List::iterator;
|
||||
using RIterator = typename List::reverse_iterator;
|
||||
using ConstIterator = typename List::const_iterator;
|
||||
|
||||
LabeledList() = default;
|
||||
LabeledList(const LabeledList&) = delete;
|
||||
LabeledList& operator=(const LabeledList&) = delete;
|
||||
LabeledList(LabeledList&&) = delete;
|
||||
LabeledList& operator=(LabeledList&&) = delete;
|
||||
|
||||
~LabeledList() { clear(); }
|
||||
|
||||
bool empty() const { return size_ == 0; }
|
||||
size_t size() const { return size_; }
|
||||
|
||||
NodeT* front() { return empty() ? nullptr : &nodes_.front(); }
|
||||
const NodeT* front() const { return empty() ? nullptr : &nodes_.front(); }
|
||||
|
||||
NodeT* back() { return empty() ? nullptr : &nodes_.back(); }
|
||||
const NodeT* back() const { return empty() ? nullptr : &nodes_.back(); }
|
||||
|
||||
static NodeT* previous(NodeT* node) {
|
||||
if (!node || !owner(node))
|
||||
return nullptr;
|
||||
auto* list = owner(node);
|
||||
auto it = node->getIterator();
|
||||
if (it == list->nodes_.begin())
|
||||
return nullptr;
|
||||
return &*std::prev(it);
|
||||
}
|
||||
|
||||
static const NodeT* previous(const NodeT* node) {
|
||||
if (!node || !owner(node))
|
||||
return nullptr;
|
||||
const auto* list = owner(node);
|
||||
auto it = const_cast<NodeT*>(node)->getIterator();
|
||||
if (it == list->nodes_.begin())
|
||||
return nullptr;
|
||||
return &*std::prev(it);
|
||||
}
|
||||
|
||||
static NodeT* next(NodeT* node) {
|
||||
if (!node || !owner(node))
|
||||
return nullptr;
|
||||
auto* list = owner(node);
|
||||
auto it = std::next(node->getIterator());
|
||||
if (it == list->nodes_.end())
|
||||
return nullptr;
|
||||
return &*it;
|
||||
}
|
||||
|
||||
static const NodeT* next(const NodeT* node) {
|
||||
if (!node || !owner(node))
|
||||
return nullptr;
|
||||
const auto* list = owner(node);
|
||||
auto it = std::next(const_cast<NodeT*>(node)->getIterator());
|
||||
if (it == list->nodes_.end())
|
||||
return nullptr;
|
||||
return &*it;
|
||||
}
|
||||
|
||||
bool contains(const NodeT* node) const { return node && node->owner_ == this; }
|
||||
|
||||
Label getOrderLabel(const NodeT* node) const {
|
||||
assert(contains(node) && "node must belong to this list");
|
||||
return node->label;
|
||||
}
|
||||
|
||||
bool comesBefore(const NodeT* lhs, const NodeT* rhs) const {
|
||||
assert(contains(lhs) && contains(rhs) && "nodes must belong to this list");
|
||||
return lhs->label < rhs->label;
|
||||
}
|
||||
|
||||
void pushFront(NodeT* node) { insertBefore(front(), node); }
|
||||
|
||||
void pushBack(NodeT* node) { insertBefore(nullptr, node); }
|
||||
|
||||
void insertBefore(NodeT* nextNode, NodeT* node) {
|
||||
assert(node && "cannot insert a null node");
|
||||
assert(!node->owner_ && "node is already linked");
|
||||
assert(nextNode == nullptr || contains(nextNode));
|
||||
|
||||
Iterator nextIt = nextNode ? getIteratorFor(nextNode) : nodes_.end();
|
||||
nodes_.insert(nextIt, *node);
|
||||
node->owner_ = this;
|
||||
++size_;
|
||||
assignLabel(getIteratorFor(node));
|
||||
}
|
||||
|
||||
void insertAfter(NodeT* prevNode, NodeT* node) {
|
||||
assert(prevNode == nullptr || contains(prevNode));
|
||||
if (prevNode == nullptr)
|
||||
insertBefore(front(), node);
|
||||
else
|
||||
insertBefore(next(prevNode), node);
|
||||
}
|
||||
|
||||
void remove(NodeT* node) {
|
||||
assert(contains(node) && "node must belong to this list");
|
||||
nodes_.remove(*node);
|
||||
node->owner_ = nullptr;
|
||||
node->label = 0;
|
||||
--size_;
|
||||
}
|
||||
|
||||
void moveBefore(NodeT* node, NodeT* nextNode) {
|
||||
assert(contains(node) && "node must belong to this list");
|
||||
assert(nextNode == nullptr || contains(nextNode));
|
||||
|
||||
Iterator nodeIt = getIteratorFor(node);
|
||||
Iterator nextIt = nextNode ? getIteratorFor(nextNode) : nodes_.end();
|
||||
if (nodeIt == nextIt || std::next(nodeIt) == nextIt)
|
||||
return;
|
||||
|
||||
nodes_.splice(nextIt, nodes_, nodeIt);
|
||||
assignLabel(getIteratorFor(node));
|
||||
}
|
||||
|
||||
void moveAfter(NodeT* node, NodeT* prevNode) {
|
||||
assert(contains(node) && "node must belong to this list");
|
||||
assert(prevNode == nullptr || contains(prevNode));
|
||||
|
||||
Iterator nextIt = prevNode ? std::next(getIteratorFor(prevNode)) : nodes_.begin();
|
||||
if (getIteratorFor(node) == nextIt)
|
||||
return;
|
||||
moveBefore(node, nextIt == nodes_.end() ? nullptr : &*nextIt);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
while (!nodes_.empty()) {
|
||||
NodeT* node = &nodes_.front();
|
||||
node->owner_ = nullptr;
|
||||
node->label = 0;
|
||||
nodes_.remove(*node);
|
||||
}
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
Iterator begin() { return nodes_.begin(); }
|
||||
Iterator end() { return nodes_.end(); }
|
||||
|
||||
RIterator rbegin() { return nodes_.rbegin(); }
|
||||
RIterator rend() { return nodes_.rend(); }
|
||||
|
||||
private:
|
||||
static const LabeledList* owner(const NodeT* node) { return static_cast<const LabeledList*>(node->owner_); }
|
||||
static LabeledList* owner(NodeT* node) { return static_cast<LabeledList*>(const_cast<void*>(node->owner_)); }
|
||||
|
||||
static Label lowerLabel(const NodeT* node) { return node ? node->label : kLowerSentinel; }
|
||||
static Label upperLabel(const NodeT* node) { return node ? node->label : kUpperSentinel; }
|
||||
|
||||
static Label labelGap(Label lower, Label upper) {
|
||||
assert(lower < upper && "labels must be strictly ordered");
|
||||
return upper - lower;
|
||||
}
|
||||
|
||||
static bool hasMidpoint(Label lower, Label upper) { return labelGap(lower, upper) > 1; }
|
||||
|
||||
static bool hasRelabelSlack(Label lower, Label upper, size_t nodeCount) {
|
||||
Label gap = labelGap(lower, upper);
|
||||
return gap / static_cast<Label>(nodeCount + 1) >= kRelabelGap;
|
||||
}
|
||||
|
||||
Iterator getIteratorFor(NodeT* node) { return node->getIterator(); }
|
||||
ConstIterator getiteratorFor(const NodeT* node) const { return node->getIterator(); }
|
||||
|
||||
NodeT* previousNode(Iterator it) {
|
||||
if (it == nodes_.begin())
|
||||
return nullptr;
|
||||
return &*std::prev(it);
|
||||
}
|
||||
|
||||
const NodeT* previousNode(ConstIterator it) const {
|
||||
if (it == nodes_.begin())
|
||||
return nullptr;
|
||||
return &*std::prev(it);
|
||||
}
|
||||
|
||||
NodeT* nextNode(Iterator it) {
|
||||
++it;
|
||||
if (it == nodes_.end())
|
||||
return nullptr;
|
||||
return &*it;
|
||||
}
|
||||
|
||||
const NodeT* nextNode(ConstIterator it) const {
|
||||
++it;
|
||||
if (it == nodes_.end())
|
||||
return nullptr;
|
||||
return &*it;
|
||||
}
|
||||
|
||||
void assignLabel(Iterator it) {
|
||||
Label lower = lowerLabel(previousNode(it));
|
||||
Label upper = upperLabel(nextNode(it));
|
||||
if (hasMidpoint(lower, upper)) {
|
||||
(*it).label = lower + static_cast<Label>(labelGap(lower, upper) / 2);
|
||||
return;
|
||||
}
|
||||
|
||||
relabelAround(it);
|
||||
}
|
||||
|
||||
void relabelAround(Iterator center) {
|
||||
size_t targetCount = 1;
|
||||
while (true) {
|
||||
Iterator left = center;
|
||||
Iterator right = center;
|
||||
size_t actualCount = 1;
|
||||
expandWindow(center, targetCount, left, right, actualCount);
|
||||
|
||||
Label lower = lowerLabel(previousNode(left));
|
||||
Label upper = upperLabel(nextNode(right));
|
||||
if (hasRelabelSlack(lower, upper, actualCount)) {
|
||||
relabelWindow(left, actualCount, lower, upper);
|
||||
return;
|
||||
}
|
||||
|
||||
if (left == nodes_.begin() && nextNode(right) == nullptr) {
|
||||
assert(hasRelabelSlack(lower, upper, actualCount) && "label space exhausted");
|
||||
relabelWindow(left, actualCount, lower, upper);
|
||||
return;
|
||||
}
|
||||
|
||||
targetCount *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
void expandWindow(Iterator center, size_t targetCount, Iterator& left, Iterator& right, size_t& actualCount) {
|
||||
left = center;
|
||||
right = center;
|
||||
actualCount = 1;
|
||||
|
||||
while (actualCount < targetCount && (left != nodes_.begin() || nextNode(right) != nullptr)) {
|
||||
if (left != nodes_.begin()) {
|
||||
--left;
|
||||
++actualCount;
|
||||
if (actualCount == targetCount)
|
||||
break;
|
||||
}
|
||||
if (nextNode(right) != nullptr) {
|
||||
++right;
|
||||
++actualCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void relabelWindow(Iterator left, size_t nodeCount, Label lower, Label upper) {
|
||||
assert(nodeCount > 0 && "relabel window must not be empty");
|
||||
Label step = labelGap(lower, upper) / static_cast<Label>(nodeCount + 1);
|
||||
assert(step >= 1 && "relabel step must be positive");
|
||||
|
||||
Iterator it = left;
|
||||
for (size_t index = 1; index <= nodeCount; ++index) {
|
||||
(*it).label = lower + step * index;
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
List nodes_;
|
||||
size_t size_ = 0;
|
||||
};
|
||||
|
||||
} // namespace onnx_mlir
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/CoreBlockUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/EntryPointUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/IndexingUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/WeightUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/Support/DebugDump.hpp"
|
||||
|
||||
@@ -7,18 +7,26 @@
|
||||
|
||||
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();
|
||||
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;
|
||||
|
||||
std::string dialectsDir = outputDir + "/dialects";
|
||||
createDirectory(dialectsDir);
|
||||
|
||||
std::fstream file(dialectsDir + "/" + name + ".mlir", std::ios::out);
|
||||
llvm::raw_os_ostream os(file);
|
||||
mlir::OpPrintingFlags flags;
|
||||
flags.elideLargeElementsAttrs().enableDebugInfo(true, false);
|
||||
flags.elideLargeElementsAttrs().enableDebugInfo(false, false);
|
||||
if (assumeVerified)
|
||||
flags.assumeVerified();
|
||||
moduleOp.print(os, flags);
|
||||
os.flush();
|
||||
file.close();
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "mlir/IR/BuiltinOps.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
/// Emits a MLIR snapshot under the current compiler output
|
||||
/// directory for pass-level debugging.
|
||||
void dumpModule(mlir::ModuleOp moduleOp, const std::string& name);
|
||||
void dumpModule(mlir::ModuleOp moduleOp, const std::string& name, bool assumeVerified = false);
|
||||
|
||||
/// Opens a file under the same dialect dump directory used by dumpModule.
|
||||
std::fstream openDialectDumpFileWithExtension(const std::string& name,llvm::StringRef destination = "/dialects", llvm::StringRef extension = "mlir");
|
||||
|
||||
} // namespace onnx_mlir
|
||||
|
||||
+164
-23
@@ -270,7 +270,7 @@ void PimMemory::allocateHost(ModuleOp moduleOp, func::FuncOp funcOp) {
|
||||
}
|
||||
|
||||
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<size_t> slotOrder(plannedSlots.size());
|
||||
@@ -414,31 +414,35 @@ size_t PimAcceleratorMemory::getValueAddress(mlir::Value value,
|
||||
const StaticValueKnowledge& knowledge,
|
||||
std::optional<unsigned> lane) const {
|
||||
value = resolveCachedAlias(value, knowledge);
|
||||
auto compiledIt = compiledAddressExprs.find(value);
|
||||
if (compiledIt == compiledAddressExprs.end()) {
|
||||
auto compiledExpr = compileContiguousAddressExpr(value);
|
||||
if (failed(compiledExpr)) {
|
||||
errs() << "Failed to compile contiguous address for value: ";
|
||||
|
||||
FailureOr<ResolvedContiguousAddress> resolvedAddress = resolveContiguousAddress(value, knowledge);
|
||||
if (failed(resolvedAddress)) {
|
||||
auto compiledIt = compiledAddressExprs.find(value);
|
||||
if (compiledIt == compiledAddressExprs.end()) {
|
||||
auto compiledExpr = compileContiguousAddressExpr(value);
|
||||
if (failed(compiledExpr)) {
|
||||
errs() << "Failed to compile contiguous address for value: ";
|
||||
value.print(errs());
|
||||
errs() << " : " << value.getType();
|
||||
errs() << "\n";
|
||||
llvm_unreachable("Failed to compile contiguous address");
|
||||
}
|
||||
compiledIt = compiledAddressExprs.try_emplace(value, *compiledExpr).first;
|
||||
}
|
||||
|
||||
resolvedAddress = compiledIt->second.evaluate(knowledge, lane);
|
||||
if (failed(resolvedAddress)) {
|
||||
errs() << "Failed to evaluate contiguous address for value: ";
|
||||
value.print(errs());
|
||||
errs() << " : " << value.getType();
|
||||
errs() << "\n";
|
||||
llvm_unreachable("Failed to compile contiguous address");
|
||||
if (auto* definingOp = value.getDefiningOp()) {
|
||||
errs() << "Defining op:\n";
|
||||
definingOp->print(errs());
|
||||
errs() << "\n";
|
||||
}
|
||||
llvm_unreachable("Failed to resolve contiguous address");
|
||||
}
|
||||
compiledIt = compiledAddressExprs.try_emplace(value, *compiledExpr).first;
|
||||
}
|
||||
|
||||
auto resolvedAddress = compiledIt->second.evaluate(knowledge, lane);
|
||||
if (failed(resolvedAddress)) {
|
||||
errs() << "Failed to evaluate contiguous address for value: ";
|
||||
value.print(errs());
|
||||
errs() << " : " << value.getType();
|
||||
errs() << "\n";
|
||||
if (auto* definingOp = value.getDefiningOp()) {
|
||||
errs() << "Defining op:\n";
|
||||
definingOp->print(errs());
|
||||
errs() << "\n";
|
||||
}
|
||||
llvm_unreachable("Failed to resolve contiguous address");
|
||||
}
|
||||
|
||||
MemoryValueKey key = getMemoryValueKey(resolvedAddress->base, lane);
|
||||
@@ -1114,7 +1118,9 @@ enum class CompiledCoreOpKind : uint8_t {
|
||||
struct CompiledCoreNode {
|
||||
enum class Kind : uint8_t {
|
||||
Op,
|
||||
Loop
|
||||
Loop,
|
||||
If,
|
||||
IndexSwitch
|
||||
};
|
||||
|
||||
Kind kind = Kind::Op;
|
||||
@@ -1123,7 +1129,13 @@ struct CompiledCoreNode {
|
||||
CompiledIndexExpr lowerBound;
|
||||
CompiledIndexExpr upperBound;
|
||||
CompiledIndexExpr step;
|
||||
CompiledIndexExpr condition;
|
||||
std::unique_ptr<llvm::SmallVector<CompiledCoreNode, 8>> loopBody;
|
||||
std::unique_ptr<llvm::SmallVector<CompiledCoreNode, 8>> thenBody;
|
||||
std::unique_ptr<llvm::SmallVector<CompiledCoreNode, 8>> elseBody;
|
||||
llvm::SmallVector<int64_t> caseValues;
|
||||
llvm::SmallVector<std::unique_ptr<llvm::SmallVector<CompiledCoreNode, 8>>> caseBodies;
|
||||
std::unique_ptr<llvm::SmallVector<CompiledCoreNode, 8>> defaultBody;
|
||||
};
|
||||
|
||||
static FailureOr<CompiledCoreOpKind> classifyCompiledCoreOpKind(Operation& op) {
|
||||
@@ -1201,6 +1213,53 @@ compileCoreEmissionPlan(Block& block, Operation* weightOwner, llvm::SmallVectorI
|
||||
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);
|
||||
if (failed(opKind)) {
|
||||
InFlightDiagnostic diag = op.emitError() << "unsupported codegen for op '" << op.getName().getStringRef() << "'";
|
||||
@@ -1263,6 +1322,51 @@ static LogicalResult executeCompiledCorePlan(
|
||||
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) {
|
||||
case CompiledCoreOpKind::Load:
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!outputDirPath.empty()) {
|
||||
if (auto error = sys::fs::create_directory(outputDirPath)) {
|
||||
@@ -1607,6 +1741,13 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
||||
if (jobResults[jobIndex].status != CompilerSuccess)
|
||||
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;
|
||||
weightRequests.reserve(jobs.size());
|
||||
for (size_t jobIndex = 0; jobIndex < jobs.size(); ++jobIndex) {
|
||||
|
||||
@@ -57,6 +57,20 @@ llvm::cl::opt<PimConvLoweringType> pimConvLowering(
|
||||
llvm::cl::init(PimConvLoweringAuto),
|
||||
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 scheduled dataflow CSV reports")),
|
||||
llvm::cl::values(
|
||||
clEnumValN(SpatialDataflowExportSpatial3, "spatial3", "Emit spatial3 realized dataflow CSV reports")),
|
||||
llvm::cl::values(clEnumValN(SpatialDataflowExportAll, "all", "Emit all Spatial dataflow CSV reports")),
|
||||
llvm::cl::init(SpatialDataflowExportNone),
|
||||
llvm::cl::cat(OnnxMlirOptions));
|
||||
|
||||
llvm::cl::opt<bool>
|
||||
pimOnlyCodegen("pim-only-codegen",
|
||||
llvm::cl::desc("Only generate code for PIM (assume input is already in bufferized PIM IR)"),
|
||||
|
||||
@@ -42,11 +42,20 @@ typedef enum {
|
||||
PimConvLoweringTiled2D = 8,
|
||||
} PimConvLoweringType;
|
||||
|
||||
typedef enum {
|
||||
SpatialDataflowExportNone = 0,
|
||||
SpatialDataflowExportSpatial1 = 1,
|
||||
SpatialDataflowExportSpatial2 = 2,
|
||||
SpatialDataflowExportSpatial3 = 3,
|
||||
SpatialDataflowExportAll = 4,
|
||||
} PimSpatialDataflowExportType;
|
||||
|
||||
extern llvm::cl::OptionCategory OnnxMlirOptions;
|
||||
extern llvm::cl::opt<PimEmissionTargetType> pimEmissionTarget;
|
||||
extern llvm::cl::opt<PimMergeSchedulerType> pimMergeScheduler;
|
||||
extern llvm::cl::opt<PimMemoryReportLevel> pimMemoryReport;
|
||||
extern llvm::cl::opt<PimConvLoweringType> pimConvLowering;
|
||||
extern llvm::cl::opt<PimSpatialDataflowExportType> pimExportSpatialDataflow;
|
||||
|
||||
extern llvm::cl::opt<bool> pimOnlyCodegen;
|
||||
extern llvm::cl::opt<bool> pimDisableMemoryCoalescing;
|
||||
|
||||
@@ -237,12 +237,19 @@ getEffectiveTouchRange(mlir::Value definingValue, Operation* user, const Operati
|
||||
}
|
||||
|
||||
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;
|
||||
interval.start = ordering.position.lookup(allocOp);
|
||||
interval.end = interval.start;
|
||||
interval.startOp = allocOp;
|
||||
interval.endOp = allocOp;
|
||||
auto recordAlias = [&](mlir::Value value) {
|
||||
if (includeAliasDescriptions)
|
||||
appendAliasDescription(interval.aliasesFollowed, value);
|
||||
};
|
||||
|
||||
SmallPtrSet<mlir::Value, 16> visitedValues;
|
||||
SmallPtrSet<Operation*, 32> visitedUsers;
|
||||
@@ -262,7 +269,7 @@ computeMemoryTouchInterval(memref::AllocOp allocOp, const OperationOrdering& ord
|
||||
if (isSupportedAliasOp(user)) {
|
||||
for (mlir::Value result : user->getResults()) {
|
||||
pendingValues.push_back(result);
|
||||
appendAliasDescription(interval.aliasesFollowed, result);
|
||||
recordAlias(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,7 +279,7 @@ computeMemoryTouchInterval(memref::AllocOp allocOp, const OperationOrdering& ord
|
||||
if (!tiedOperand || tiedOperand->get() != value)
|
||||
continue;
|
||||
pendingValues.push_back(result);
|
||||
appendAliasDescription(interval.aliasesFollowed, result);
|
||||
recordAlias(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,8 +289,8 @@ computeMemoryTouchInterval(memref::AllocOp allocOp, const OperationOrdering& ord
|
||||
continue;
|
||||
pendingValues.push_back(forOp.getRegionIterArgs()[index]);
|
||||
pendingValues.push_back(forOp.getResult(index));
|
||||
appendAliasDescription(interval.aliasesFollowed, forOp.getRegionIterArgs()[index]);
|
||||
appendAliasDescription(interval.aliasesFollowed, forOp.getResult(index));
|
||||
recordAlias(forOp.getRegionIterArgs()[index]);
|
||||
recordAlias(forOp.getResult(index));
|
||||
if (parentLoop && forOp != parentLoop)
|
||||
interval.escapesLoop = true;
|
||||
}
|
||||
@@ -291,7 +298,25 @@ computeMemoryTouchInterval(memref::AllocOp allocOp, const OperationOrdering& ord
|
||||
|
||||
if (auto yieldOp = dyn_cast<scf::YieldOp>(user)) {
|
||||
auto forOp = dyn_cast<scf::ForOp>(yieldOp->getParentOp());
|
||||
if (!forOp) {
|
||||
auto ifOp = dyn_cast<scf::IfOp>(yieldOp->getParentOp());
|
||||
auto indexSwitch = dyn_cast<scf::IndexSwitchOp>(yieldOp->getParentOp());
|
||||
if (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");
|
||||
}
|
||||
else {
|
||||
@@ -299,7 +324,7 @@ computeMemoryTouchInterval(memref::AllocOp allocOp, const OperationOrdering& ord
|
||||
if (operand != value)
|
||||
continue;
|
||||
pendingValues.push_back(forOp.getResult(index));
|
||||
appendAliasDescription(interval.aliasesFollowed, forOp.getResult(index));
|
||||
recordAlias(forOp.getResult(index));
|
||||
if (parentLoop && forOp == parentLoop)
|
||||
interval.escapesLoop = true;
|
||||
}
|
||||
@@ -392,7 +417,8 @@ static uint64_t getSlotLogicalBytes(const PlannedPhysicalSlot& slot, ArrayRef<Lo
|
||||
} // namespace
|
||||
|
||||
SmallVector<LocalAllocInterval, 0> onnx_mlir::buildLocalAllocIntervals(Operation* coreLikeOp,
|
||||
std::optional<unsigned> lane) {
|
||||
std::optional<unsigned> lane,
|
||||
bool includeAliasDescriptions) {
|
||||
SmallVector<LocalAllocInterval, 0> intervals;
|
||||
OperationOrdering ordering = buildOperationOrdering(coreLikeOp);
|
||||
if (ordering.position.empty())
|
||||
@@ -409,7 +435,8 @@ SmallVector<LocalAllocInterval, 0> onnx_mlir::buildLocalAllocIntervals(Operation
|
||||
llvm_unreachable("Failed to compute local allocation size");
|
||||
}
|
||||
|
||||
MemoryTouchInterval touchInterval = computeMemoryTouchInterval(allocOp, ordering, fallbackEnd);
|
||||
MemoryTouchInterval touchInterval =
|
||||
computeMemoryTouchInterval(allocOp, ordering, fallbackEnd, includeAliasDescriptions);
|
||||
LocalAllocInterval interval;
|
||||
interval.id = nextIntervalId++;
|
||||
interval.alloc = allocOp;
|
||||
|
||||
@@ -49,7 +49,8 @@ struct PlannedPhysicalSlot {
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
add_subdirectory(ONNXToSpatial)
|
||||
add_subdirectory(SpatialToGraphviz)
|
||||
add_subdirectory(SpatialToPim)
|
||||
add_subdirectory(SpatialToPim)
|
||||
|
||||
@@ -10,6 +10,7 @@ add_pim_library(OMONNXToSpatial
|
||||
Patterns/Post.cpp
|
||||
Patterns/GeneratedConversion.cpp
|
||||
Patterns/Math/Conv.cpp
|
||||
Patterns/Math/ConvGeometry.cpp
|
||||
Patterns/Math/Elementwise.cpp
|
||||
Patterns/Math/Gemm.cpp
|
||||
Patterns/Math/MatMul.cpp
|
||||
@@ -19,6 +20,7 @@ add_pim_library(OMONNXToSpatial
|
||||
Patterns/NN/Sigmoid.cpp
|
||||
Patterns/NN/Softmax.cpp
|
||||
Patterns/Tensor/Concat.cpp
|
||||
Patterns/Tensor/Flatten.cpp
|
||||
Patterns/Tensor/Gather.cpp
|
||||
Patterns/Tensor/Resize.cpp
|
||||
Patterns/Tensor/Reshape.cpp
|
||||
@@ -29,8 +31,10 @@ add_pim_library(OMONNXToSpatial
|
||||
SpatialLayoutPlanningPass.cpp
|
||||
LowerSpatialPlansPass.cpp
|
||||
Common/AttributeUtils.cpp
|
||||
Common/BiasAddUtils.cpp
|
||||
Common/ComputeRegionBuilder.cpp
|
||||
Common/IndexingUtils.cpp
|
||||
Common/MatrixProductLowering.cpp
|
||||
Common/RowStripLayoutUtils.cpp
|
||||
Common/ShapeTilingUtils.cpp
|
||||
Common/WeightMaterialization.cpp
|
||||
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
#include "mlir/IR/BuiltinAttributes.h"
|
||||
#include "mlir/IR/BuiltinTypes.h"
|
||||
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/BiasAddUtils.hpp"
|
||||
|
||||
using namespace mlir;
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
LogicalResult isSupportedBiasAddShape(RankedTensorType biasType, RankedTensorType resultType) {
|
||||
if (!biasType || !resultType || !biasType.hasStaticShape() || !resultType.hasStaticShape())
|
||||
return failure();
|
||||
if (resultType.getRank() != 4)
|
||||
return failure();
|
||||
if (biasType.getElementType() != resultType.getElementType())
|
||||
return failure();
|
||||
|
||||
const int64_t channels = resultType.getDimSize(1);
|
||||
ArrayRef<int64_t> shape = biasType.getShape();
|
||||
if (shape.empty())
|
||||
return success();
|
||||
if (shape.size() == 1)
|
||||
return success(shape[0] == channels);
|
||||
if (shape.size() == 2)
|
||||
return success(shape[0] == 1 && shape[1] == channels);
|
||||
if (shape.size() == 4)
|
||||
return success(shape[0] == 1 && shape[1] == channels && shape[2] == 1 && shape[3] == 1);
|
||||
return failure();
|
||||
}
|
||||
|
||||
FailureOr<SmallVector<Attribute>> getBiasChannelValues(DenseElementsAttr denseAttr, RankedTensorType resultType) {
|
||||
auto biasType = dyn_cast<RankedTensorType>(denseAttr.getType());
|
||||
if (!biasType || failed(isSupportedBiasAddShape(biasType, resultType)))
|
||||
return failure();
|
||||
|
||||
const int64_t channels = resultType.getDimSize(1);
|
||||
if (denseAttr.isSplat()) {
|
||||
return SmallVector<Attribute>(channels, denseAttr.getSplatValue<Attribute>());
|
||||
}
|
||||
|
||||
SmallVector<Attribute> flattened(denseAttr.getValues<Attribute>());
|
||||
if (biasType.getRank() == 1)
|
||||
return flattened;
|
||||
if (biasType.getRank() == 2)
|
||||
return flattened;
|
||||
|
||||
SmallVector<Attribute> channelValues;
|
||||
channelValues.reserve(channels);
|
||||
const int64_t channelStride = biasType.getDimSize(2) * biasType.getDimSize(3);
|
||||
for (int64_t channel = 0; channel < channels; ++channel)
|
||||
channelValues.push_back(flattened[channel * channelStride]);
|
||||
return channelValues;
|
||||
}
|
||||
|
||||
bool isSupportedBiasAddValue(Value bias, RankedTensorType resultType, DenseElementsAttr* denseAttr) {
|
||||
auto attr = getHostConstDenseElementsAttr(bias);
|
||||
if (!attr)
|
||||
return false;
|
||||
auto biasType = dyn_cast<RankedTensorType>(attr.getType());
|
||||
if (!biasType || failed(isSupportedBiasAddShape(biasType, resultType)))
|
||||
return false;
|
||||
if (failed(getBiasChannelValues(attr, resultType)))
|
||||
return false;
|
||||
if (denseAttr)
|
||||
*denseAttr = attr;
|
||||
return true;
|
||||
}
|
||||
|
||||
FailureOr<BiasAddPlanCandidate> classifyBiasAddPlanCandidate(Value lhs, Value rhs, RankedTensorType resultType) {
|
||||
auto lhsType = dyn_cast<RankedTensorType>(lhs.getType());
|
||||
auto rhsType = dyn_cast<RankedTensorType>(rhs.getType());
|
||||
if (!lhsType || !rhsType)
|
||||
return failure();
|
||||
if (lhsType == resultType && isSupportedBiasAddValue(rhs, resultType))
|
||||
return BiasAddPlanCandidate {lhs, rhs};
|
||||
if (rhsType == resultType && isSupportedBiasAddValue(lhs, resultType))
|
||||
return BiasAddPlanCandidate {rhs, lhs};
|
||||
return failure();
|
||||
}
|
||||
|
||||
FailureOr<Value>
|
||||
materializeDenseBiasAddTensor(Value bias, RankedTensorType resultType, RewriterBase& rewriter, Location loc) {
|
||||
DenseElementsAttr denseAttr;
|
||||
if (!isSupportedBiasAddValue(bias, resultType, &denseAttr))
|
||||
return failure();
|
||||
|
||||
FailureOr<SmallVector<Attribute>> channelValues = getBiasChannelValues(denseAttr, resultType);
|
||||
if (failed(channelValues))
|
||||
return failure();
|
||||
|
||||
SmallVector<Attribute> resultValues;
|
||||
resultValues.reserve(resultType.getNumElements());
|
||||
const int64_t batches = resultType.getDimSize(0);
|
||||
const int64_t channels = resultType.getDimSize(1);
|
||||
const int64_t height = resultType.getDimSize(2);
|
||||
const int64_t width = resultType.getDimSize(3);
|
||||
for (int64_t n = 0; n < batches; ++n)
|
||||
for (int64_t c = 0; c < channels; ++c)
|
||||
for (int64_t h = 0; h < height; ++h)
|
||||
for (int64_t w = 0; w < width; ++w)
|
||||
resultValues.push_back((*channelValues)[c]);
|
||||
|
||||
auto resultAttr = DenseElementsAttr::get(resultType, resultValues);
|
||||
return getOrCreateConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), resultAttr, resultType);
|
||||
}
|
||||
|
||||
} // namespace onnx_mlir
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "mlir/IR/BuiltinAttributes.h"
|
||||
#include "mlir/IR/BuiltinTypes.h"
|
||||
#include "mlir/IR/PatternMatch.h"
|
||||
#include "mlir/IR/Value.h"
|
||||
#include "mlir/Support/LogicalResult.h"
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
struct BiasAddPlanCandidate {
|
||||
mlir::Value data;
|
||||
mlir::Value bias;
|
||||
};
|
||||
|
||||
mlir::LogicalResult isSupportedBiasAddShape(mlir::RankedTensorType biasType, mlir::RankedTensorType resultType);
|
||||
bool isSupportedBiasAddValue(mlir::Value bias,
|
||||
mlir::RankedTensorType resultType,
|
||||
mlir::DenseElementsAttr* denseAttr = nullptr);
|
||||
mlir::FailureOr<llvm::SmallVector<mlir::Attribute>>
|
||||
getBiasChannelValues(mlir::DenseElementsAttr denseAttr, mlir::RankedTensorType resultType);
|
||||
mlir::FailureOr<BiasAddPlanCandidate> classifyBiasAddPlanCandidate(mlir::Value lhs,
|
||||
mlir::Value rhs,
|
||||
mlir::RankedTensorType resultType);
|
||||
mlir::FailureOr<mlir::Value> materializeDenseBiasAddTensor(mlir::Value bias,
|
||||
mlir::RankedTensorType resultType,
|
||||
mlir::RewriterBase& rewriter,
|
||||
mlir::Location loc);
|
||||
|
||||
} // namespace onnx_mlir
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
#include "AttributeUtils.hpp"
|
||||
#include "ComputeRegionBuilder.hpp"
|
||||
#include "IndexingUtils.hpp"
|
||||
#include "MatrixProductLowering.hpp"
|
||||
#include "ShapeTilingUtils.hpp"
|
||||
#include "WeightMaterialization.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/TensorSliceUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||
|
||||
@@ -60,6 +60,56 @@ struct SpatComputeBatchBodyArgs {
|
||||
mlir::ValueRange outputs;
|
||||
};
|
||||
|
||||
inline mlir::SmallVector<mlir::Type> getGraphComputeBlockArgTypes(mlir::ValueRange weights, mlir::ValueRange inputs) {
|
||||
mlir::SmallVector<mlir::Type> blockArgTypes;
|
||||
blockArgTypes.reserve(weights.size() + inputs.size());
|
||||
for (mlir::Value weight : weights)
|
||||
blockArgTypes.push_back(weight.getType());
|
||||
for (mlir::Value input : inputs)
|
||||
blockArgTypes.push_back(input.getType());
|
||||
return blockArgTypes;
|
||||
}
|
||||
|
||||
inline mlir::SmallVector<mlir::Location> getGraphComputeBlockArgLocs(mlir::Location defaultLoc,
|
||||
mlir::ValueRange weights,
|
||||
mlir::ValueRange inputs) {
|
||||
mlir::SmallVector<mlir::Location> blockArgLocs;
|
||||
blockArgLocs.reserve(weights.size() + inputs.size());
|
||||
for (mlir::Value weight : weights)
|
||||
blockArgLocs.push_back(weight.getLoc());
|
||||
for (mlir::Value input : inputs)
|
||||
blockArgLocs.push_back(input.getLoc());
|
||||
return blockArgLocs;
|
||||
}
|
||||
|
||||
inline mlir::SmallVector<mlir::Type> getGraphComputeBatchBlockArgTypes(mlir::OpBuilder& builder,
|
||||
mlir::TypeRange resultTypes,
|
||||
mlir::ValueRange weights,
|
||||
mlir::ValueRange inputs) {
|
||||
mlir::SmallVector<mlir::Type> blockArgTypes {builder.getIndexType()};
|
||||
blockArgTypes.reserve(1 + weights.size() + inputs.size() + resultTypes.size());
|
||||
for (mlir::Value weight : weights)
|
||||
blockArgTypes.push_back(weight.getType());
|
||||
for (mlir::Value input : inputs)
|
||||
blockArgTypes.push_back(input.getType());
|
||||
llvm::append_range(blockArgTypes, resultTypes);
|
||||
return blockArgTypes;
|
||||
}
|
||||
|
||||
inline mlir::SmallVector<mlir::Location> getGraphComputeBatchBlockArgLocs(mlir::Location defaultLoc,
|
||||
mlir::TypeRange resultTypes,
|
||||
mlir::ValueRange weights,
|
||||
mlir::ValueRange inputs) {
|
||||
mlir::SmallVector<mlir::Location> blockArgLocs {defaultLoc};
|
||||
blockArgLocs.reserve(1 + weights.size() + inputs.size() + resultTypes.size());
|
||||
for (mlir::Value weight : weights)
|
||||
blockArgLocs.push_back(weight.getLoc());
|
||||
for (mlir::Value input : inputs)
|
||||
blockArgLocs.push_back(input.getLoc());
|
||||
blockArgLocs.append(resultTypes.size(), defaultLoc);
|
||||
return blockArgLocs;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename RewriterT>
|
||||
@@ -87,6 +137,31 @@ inline mlir::Value createSpatConcat(RewriterT& rewriter, mlir::Location loc, int
|
||||
return spatial::SpatConcatOp::create(rewriter, loc, outputType, rewriter.getI64IntegerAttr(axis), inputs).getOutput();
|
||||
}
|
||||
|
||||
template <typename RewriterT>
|
||||
spatial::SpatGraphCompute createEmptySpatGraphCompute(RewriterT& rewriter,
|
||||
mlir::Location loc,
|
||||
mlir::TypeRange resultTypes,
|
||||
mlir::ValueRange weights,
|
||||
mlir::ValueRange inputs,
|
||||
mlir::TypeRange blockArgTypes,
|
||||
llvm::ArrayRef<mlir::Location> blockArgLocs) {
|
||||
auto computeOp = spatial::SpatGraphCompute::create(rewriter, loc, resultTypes, weights, inputs);
|
||||
rewriter.createBlock(&computeOp.getBody(), computeOp.getBody().end(), blockArgTypes, blockArgLocs);
|
||||
rewriter.setInsertionPointToStart(&computeOp.getBody().front());
|
||||
return computeOp;
|
||||
}
|
||||
|
||||
template <typename RewriterT>
|
||||
spatial::SpatGraphCompute createEmptySpatGraphCompute(RewriterT& rewriter,
|
||||
mlir::Location loc,
|
||||
mlir::TypeRange resultTypes,
|
||||
mlir::ValueRange weights,
|
||||
mlir::ValueRange inputs) {
|
||||
auto blockArgTypes = detail::getGraphComputeBlockArgTypes(weights, inputs);
|
||||
auto blockArgLocs = detail::getGraphComputeBlockArgLocs(loc, weights, inputs);
|
||||
return createEmptySpatGraphCompute(rewriter, loc, resultTypes, weights, inputs, blockArgTypes, blockArgLocs);
|
||||
}
|
||||
|
||||
/// Builds a `spat.graph_compute` with a fixed number of SSA inputs and erases it if
|
||||
/// the body callback reports failure.
|
||||
template <size_t NumInputs, typename RewriterT, typename BodyFn>
|
||||
@@ -97,16 +172,8 @@ auto createSpatGraphCompute(RewriterT& rewriter,
|
||||
mlir::ValueRange inputs,
|
||||
BodyFn&& body) {
|
||||
assert(inputs.size() == NumInputs && "NumInputs must match the number of input values");
|
||||
auto computeOp = spatial::SpatGraphCompute::create(rewriter, loc, resultTypes, weights, inputs);
|
||||
|
||||
auto* block = new mlir::Block();
|
||||
for (mlir::Value weight : weights)
|
||||
block->addArgument(weight.getType(), loc);
|
||||
for (mlir::Value input : inputs)
|
||||
block->addArgument(input.getType(), loc);
|
||||
|
||||
computeOp.getBody().push_back(block);
|
||||
rewriter.setInsertionPointToStart(block);
|
||||
auto computeOp = createEmptySpatGraphCompute(rewriter, loc, resultTypes, weights, inputs);
|
||||
auto* block = &computeOp.getBody().front();
|
||||
|
||||
using BodyResult = detail::InvokeWithBlockArgsResultT<std::decay_t<BodyFn>, std::make_index_sequence<NumInputs>>;
|
||||
if constexpr (std::is_same_v<BodyResult, void>) {
|
||||
@@ -140,16 +207,8 @@ auto createSpatGraphCompute(RewriterT& rewriter,
|
||||
mlir::ValueRange weights,
|
||||
mlir::ValueRange inputs,
|
||||
BodyFn&& body) {
|
||||
auto computeOp = spatial::SpatGraphCompute::create(rewriter, loc, resultTypes, weights, inputs);
|
||||
|
||||
auto* block = new mlir::Block();
|
||||
for (mlir::Value weight : weights)
|
||||
block->addArgument(weight.getType(), loc);
|
||||
for (mlir::Value input : inputs)
|
||||
block->addArgument(input.getType(), loc);
|
||||
|
||||
computeOp.getBody().push_back(block);
|
||||
rewriter.setInsertionPointToStart(block);
|
||||
auto computeOp = createEmptySpatGraphCompute(rewriter, loc, resultTypes, weights, inputs);
|
||||
auto* block = &computeOp.getBody().front();
|
||||
|
||||
using BodyResult = detail::InvokeWithValueRangeResultT<std::decay_t<BodyFn>>;
|
||||
if constexpr (std::is_same_v<BodyResult, void>) {
|
||||
@@ -170,14 +229,15 @@ auto createSpatGraphCompute(RewriterT& rewriter,
|
||||
}
|
||||
}
|
||||
|
||||
template <typename RewriterT, typename BodyFn>
|
||||
auto createSpatGraphComputeBatch(RewriterT& rewriter,
|
||||
mlir::Location loc,
|
||||
mlir::TypeRange resultTypes,
|
||||
int64_t laneCount,
|
||||
mlir::ValueRange weights,
|
||||
mlir::ValueRange inputs,
|
||||
BodyFn&& body) {
|
||||
template <typename RewriterT>
|
||||
auto createEmptySpatGraphComputeBatch(RewriterT& rewriter,
|
||||
mlir::Location loc,
|
||||
mlir::TypeRange resultTypes,
|
||||
int64_t laneCount,
|
||||
mlir::ValueRange weights,
|
||||
mlir::ValueRange inputs,
|
||||
mlir::TypeRange blockArgTypes,
|
||||
llvm::ArrayRef<mlir::Location> blockArgLocs) {
|
||||
if (laneCount <= 0 || laneCount > std::numeric_limits<int32_t>::max())
|
||||
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(mlir::failure());
|
||||
|
||||
@@ -186,27 +246,36 @@ auto createSpatGraphComputeBatch(RewriterT& rewriter,
|
||||
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(mlir::failure());
|
||||
|
||||
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()};
|
||||
mlir::SmallVector<mlir::Location> blockArgLocs {loc};
|
||||
blockArgTypes.reserve(1 + weights.size() + inputs.size() + resultTypes.size());
|
||||
blockArgLocs.reserve(1 + weights.size() + inputs.size() + resultTypes.size());
|
||||
for (mlir::Value weight : weights) {
|
||||
blockArgTypes.push_back(weight.getType());
|
||||
blockArgLocs.push_back(weight.getLoc());
|
||||
}
|
||||
for (mlir::Value input : inputs) {
|
||||
blockArgTypes.push_back(input.getType());
|
||||
blockArgLocs.push_back(input.getLoc());
|
||||
}
|
||||
for (mlir::Type resultType : resultTypes) {
|
||||
blockArgTypes.push_back(resultType);
|
||||
blockArgLocs.push_back(loc);
|
||||
}
|
||||
template <typename RewriterT>
|
||||
auto createEmptySpatGraphComputeBatch(RewriterT& rewriter,
|
||||
mlir::Location loc,
|
||||
mlir::TypeRange resultTypes,
|
||||
int64_t laneCount,
|
||||
mlir::ValueRange weights,
|
||||
mlir::ValueRange inputs) {
|
||||
auto blockArgTypes = detail::getGraphComputeBatchBlockArgTypes(rewriter, resultTypes, weights, inputs);
|
||||
auto blockArgLocs = detail::getGraphComputeBatchBlockArgLocs(loc, resultTypes, weights, inputs);
|
||||
return createEmptySpatGraphComputeBatch(
|
||||
rewriter, loc, resultTypes, laneCount, weights, inputs, blockArgTypes, blockArgLocs);
|
||||
}
|
||||
|
||||
auto* block =
|
||||
rewriter.createBlock(&batchOp.getBody(), batchOp.getBody().end(), mlir::TypeRange(blockArgTypes), blockArgLocs);
|
||||
rewriter.setInsertionPointToStart(block);
|
||||
template <typename RewriterT, typename BodyFn>
|
||||
auto createSpatGraphComputeBatch(RewriterT& rewriter,
|
||||
mlir::Location loc,
|
||||
mlir::TypeRange resultTypes,
|
||||
int64_t laneCount,
|
||||
mlir::ValueRange weights,
|
||||
mlir::ValueRange inputs,
|
||||
BodyFn&& body) {
|
||||
auto batchOp = createEmptySpatGraphComputeBatch(rewriter, loc, resultTypes, laneCount, weights, inputs);
|
||||
if (failed(batchOp))
|
||||
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(mlir::failure());
|
||||
auto* block = &(*batchOp).getBody().front();
|
||||
|
||||
detail::SpatComputeBatchBodyArgs args {
|
||||
block->getArgument(0),
|
||||
@@ -217,18 +286,18 @@ auto createSpatGraphComputeBatch(RewriterT& rewriter,
|
||||
using BodyResult = std::invoke_result_t<BodyFn, detail::SpatComputeBatchBodyArgs>;
|
||||
if constexpr (std::is_same_v<BodyResult, void>) {
|
||||
std::forward<BodyFn>(body)(args);
|
||||
rewriter.setInsertionPointAfter(batchOp);
|
||||
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(batchOp);
|
||||
rewriter.setInsertionPointAfter(*batchOp);
|
||||
return batchOp;
|
||||
}
|
||||
else {
|
||||
auto bodyResult = std::forward<BodyFn>(body)(args);
|
||||
if (mlir::failed(bodyResult)) {
|
||||
rewriter.setInsertionPointAfter(batchOp);
|
||||
rewriter.eraseOp(batchOp);
|
||||
rewriter.setInsertionPointAfter(*batchOp);
|
||||
rewriter.eraseOp(*batchOp);
|
||||
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(mlir::failure());
|
||||
}
|
||||
rewriter.setInsertionPointAfter(batchOp);
|
||||
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(batchOp);
|
||||
rewriter.setInsertionPointAfter(*batchOp);
|
||||
return batchOp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,6 +346,52 @@ inline void createParallelInsertSliceIntoBatchOutput(mlir::PatternRewriter& rewr
|
||||
mlir::tensor::ParallelInsertSliceOp::create(rewriter, loc, source, dest, offsets, sizes, strides);
|
||||
}
|
||||
|
||||
inline void publishGraphBatchPhysicalFragment(mlir::PatternRewriter& rewriter,
|
||||
mlir::Location loc,
|
||||
mlir::Value fragment,
|
||||
mlir::Value output,
|
||||
mlir::Value physicalSlot) {
|
||||
auto fragmentType = mlir::cast<mlir::RankedTensorType>(fragment.getType());
|
||||
mlir::SmallVector<mlir::OpFoldResult> offsets {physicalSlot};
|
||||
mlir::SmallVector<mlir::OpFoldResult> sizes {rewriter.getIndexAttr(1)};
|
||||
mlir::SmallVector<mlir::OpFoldResult> strides {rewriter.getIndexAttr(1)};
|
||||
for (int64_t dim : fragmentType.getShape()) {
|
||||
offsets.push_back(rewriter.getIndexAttr(0));
|
||||
sizes.push_back(rewriter.getIndexAttr(dim));
|
||||
strides.push_back(rewriter.getIndexAttr(1));
|
||||
}
|
||||
createParallelInsertSliceIntoBatchOutput(rewriter, loc, fragment, output, offsets, sizes, strides);
|
||||
}
|
||||
|
||||
inline mlir::FailureOr<mlir::Value>
|
||||
extractGraphBatchPhysicalFragment(mlir::PatternRewriter& rewriter,
|
||||
mlir::Location loc,
|
||||
mlir::Value physicalBatch,
|
||||
mlir::OpFoldResult slot,
|
||||
mlir::RankedTensorType fragmentType) {
|
||||
if (fragmentType.getRank() == 0)
|
||||
return mlir::failure();
|
||||
auto physicalType = mlir::dyn_cast<mlir::RankedTensorType>(physicalBatch.getType());
|
||||
if (!physicalType || physicalType.getRank() != fragmentType.getRank() + 1)
|
||||
return mlir::failure();
|
||||
mlir::SmallVector<int64_t> selectedShape {1};
|
||||
llvm::append_range(selectedShape, fragmentType.getShape());
|
||||
auto selectedType = mlir::RankedTensorType::get(selectedShape, fragmentType.getElementType(), fragmentType.getEncoding());
|
||||
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));
|
||||
}
|
||||
mlir::Value selected = mlir::tensor::ExtractSliceOp::create(rewriter, loc, selectedType, physicalBatch, offsets, sizes, strides);
|
||||
mlir::SmallVector<mlir::ReassociationIndices> reassociation {{0, 1}};
|
||||
for (int64_t dim = 2; dim <= fragmentType.getRank(); ++dim)
|
||||
reassociation.push_back({dim});
|
||||
return mlir::tensor::CollapseShapeOp::create(rewriter, loc, fragmentType, selected, reassociation).getResult();
|
||||
}
|
||||
|
||||
template <typename BodyFn>
|
||||
mlir::Value materializeOrComputeUnary(mlir::Value input,
|
||||
mlir::RankedTensorType resultType,
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
#include "MatrixProductLowering.hpp"
|
||||
|
||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
|
||||
using namespace mlir;
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
Value createZeroPaddedTensor(Value value, RankedTensorType resultType, PatternRewriter& rewriter, Location loc) {
|
||||
auto sourceType = cast<RankedTensorType>(value.getType());
|
||||
SmallVector<OpFoldResult> lowPads(sourceType.getRank(), rewriter.getIndexAttr(0));
|
||||
SmallVector<OpFoldResult> highPads;
|
||||
highPads.reserve(sourceType.getRank());
|
||||
for (auto [sourceDim, resultDim] : llvm::zip(sourceType.getShape(), resultType.getShape()))
|
||||
highPads.push_back(rewriter.getIndexAttr(resultDim - sourceDim));
|
||||
|
||||
auto padOp = tensor::PadOp::create(rewriter, loc, resultType, value, lowPads, highPads);
|
||||
auto* padBlock = new Block();
|
||||
for (int64_t i = 0; i < sourceType.getRank(); ++i)
|
||||
padBlock->addArgument(rewriter.getIndexType(), loc);
|
||||
padOp.getRegion().push_back(padBlock);
|
||||
rewriter.setInsertionPointToStart(padBlock);
|
||||
auto zero = getOrCreateConstant(
|
||||
rewriter, padOp.getOperation(), rewriter.getZeroAttr(sourceType.getElementType()), sourceType.getElementType());
|
||||
tensor::YieldOp::create(rewriter, loc, zero);
|
||||
rewriter.setInsertionPointAfter(padOp);
|
||||
return padOp.getResult();
|
||||
}
|
||||
|
||||
Value createPaddedInputCompute(Value input,
|
||||
RankedTensorType paddedInputType,
|
||||
PatternRewriter& rewriter,
|
||||
Location loc) {
|
||||
auto inputType = cast<RankedTensorType>(input.getType());
|
||||
if (inputType == paddedInputType)
|
||||
return input;
|
||||
|
||||
auto computeOp = createSpatCompute<1>(rewriter, loc, TypeRange {paddedInputType}, {}, input, [&](Value computeInput) {
|
||||
Value paddedInput = createZeroPaddedTensor(computeInput, paddedInputType, rewriter, loc);
|
||||
spatial::SpatYieldOp::create(rewriter, loc, paddedInput);
|
||||
});
|
||||
return computeOp.getResult(0);
|
||||
}
|
||||
|
||||
} // namespace onnx_mlir
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "mlir/IR/BuiltinTypes.h"
|
||||
#include "mlir/IR/Location.h"
|
||||
#include "mlir/IR/Value.h"
|
||||
#include "mlir/Transforms/DialectConversion.h"
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
mlir::Value createZeroPaddedTensor(mlir::Value value,
|
||||
mlir::RankedTensorType resultType,
|
||||
mlir::PatternRewriter& rewriter,
|
||||
mlir::Location loc);
|
||||
|
||||
mlir::Value createPaddedInputCompute(mlir::Value input,
|
||||
mlir::RankedTensorType paddedInputType,
|
||||
mlir::PatternRewriter& rewriter,
|
||||
mlir::Location loc);
|
||||
|
||||
} // namespace onnx_mlir
|
||||
@@ -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
|
||||
@@ -3,9 +3,6 @@
|
||||
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "IndexingUtils.hpp"
|
||||
#include "ShapeTilingUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
||||
@@ -15,73 +12,6 @@ using namespace mlir;
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
bool hasStaticPositiveShape(ArrayRef<int64_t> shape) {
|
||||
return llvm::all_of(shape, [](int64_t dim) { return dim > 0; });
|
||||
}
|
||||
|
||||
bool hasStaticPositiveShape(RankedTensorType type) {
|
||||
return type.hasStaticShape() && hasStaticPositiveShape(type.getShape());
|
||||
}
|
||||
|
||||
int64_t getStaticShapeElementCount(ArrayRef<int64_t> shape) {
|
||||
return std::accumulate(shape.begin(), shape.end(), int64_t {1}, std::multiplies<int64_t> {});
|
||||
}
|
||||
|
||||
SmallVector<int64_t> permuteShape(ArrayRef<int64_t> shape, ArrayRef<int64_t> permutation) {
|
||||
SmallVector<int64_t> permutedShape;
|
||||
permutedShape.reserve(permutation.size());
|
||||
for (int64_t axis : permutation)
|
||||
permutedShape.push_back(shape[axis]);
|
||||
return permutedShape;
|
||||
}
|
||||
|
||||
SmallVector<int64_t> invertPermutation(ArrayRef<int64_t> permutation) {
|
||||
SmallVector<int64_t> inversePermutation(permutation.size());
|
||||
for (auto [newIndex, oldIndex] : llvm::enumerate(permutation))
|
||||
inversePermutation[oldIndex] = static_cast<int64_t>(newIndex);
|
||||
return inversePermutation;
|
||||
}
|
||||
|
||||
FailureOr<SmallVector<int64_t>> getTransposePermutationChecked(std::optional<ArrayAttr> permAttr, int64_t rank) {
|
||||
SmallVector<int64_t> permutation;
|
||||
if (!permAttr) {
|
||||
permutation.reserve(rank);
|
||||
for (int64_t dim = rank - 1; dim >= 0; --dim)
|
||||
permutation.push_back(dim);
|
||||
return permutation;
|
||||
}
|
||||
|
||||
if (static_cast<int64_t>(permAttr->size()) != rank)
|
||||
return failure();
|
||||
|
||||
permutation.reserve(permAttr->size());
|
||||
SmallVector<bool> seen(rank, false);
|
||||
for (IntegerAttr attr : permAttr->getAsRange<IntegerAttr>()) {
|
||||
int64_t axis = attr.getInt();
|
||||
if (axis < 0 || axis >= rank || seen[axis])
|
||||
return failure();
|
||||
seen[axis] = true;
|
||||
permutation.push_back(axis);
|
||||
}
|
||||
return permutation;
|
||||
}
|
||||
|
||||
SmallVector<OpFoldResult> getUnitStrides(PatternRewriter& rewriter, int64_t rank) {
|
||||
return SmallVector<OpFoldResult>(rank, rewriter.getIndexAttr(1));
|
||||
}
|
||||
|
||||
SmallVector<OpFoldResult> getZeroOffsets(PatternRewriter& rewriter, int64_t rank) {
|
||||
return SmallVector<OpFoldResult>(rank, rewriter.getIndexAttr(0));
|
||||
}
|
||||
|
||||
SmallVector<OpFoldResult> getStaticSizes(PatternRewriter& rewriter, ArrayRef<int64_t> shape) {
|
||||
SmallVector<OpFoldResult> sizes;
|
||||
sizes.reserve(shape.size());
|
||||
for (int64_t dim : shape)
|
||||
sizes.push_back(rewriter.getIndexAttr(dim));
|
||||
return sizes;
|
||||
}
|
||||
|
||||
SmallVector<Value> sliceTensor(
|
||||
const Value& tensorToSlice, size_t axis, int64_t sliceSize, PatternRewriter& rewriter, Location loc) {
|
||||
ArrayRef<long> shape = getTensorShape(tensorToSlice);
|
||||
@@ -147,33 +77,4 @@ sliceVectorPerCrossbarPerCore(const Value& vectorToSlice, PatternRewriter& rewri
|
||||
return slicesPerCore;
|
||||
}
|
||||
|
||||
Value extractAxisSlice(
|
||||
PatternRewriter& rewriter, Location loc, Value source, int64_t axis, int64_t offset, int64_t size) {
|
||||
auto sourceType = cast<RankedTensorType>(source.getType());
|
||||
SmallVector<int64_t> resultShape(sourceType.getShape());
|
||||
resultShape[axis] = size;
|
||||
auto resultType = RankedTensorType::get(resultShape, sourceType.getElementType(), sourceType.getEncoding());
|
||||
|
||||
SmallVector<OpFoldResult> offsets = getZeroOffsets(rewriter, sourceType.getRank());
|
||||
SmallVector<OpFoldResult> sizes = getStaticSizes(rewriter, sourceType.getShape());
|
||||
offsets[axis] = rewriter.getIndexAttr(offset);
|
||||
sizes[axis] = rewriter.getIndexAttr(size);
|
||||
return tensor::ExtractSliceOp::create(
|
||||
rewriter, loc, resultType, source, offsets, sizes, getUnitStrides(rewriter, sourceType.getRank()))
|
||||
.getResult();
|
||||
}
|
||||
|
||||
Value insertStaticSlice(
|
||||
PatternRewriter& rewriter, Location loc, Value source, Value dest, ArrayRef<OpFoldResult> offsets) {
|
||||
auto sourceType = cast<RankedTensorType>(source.getType());
|
||||
return tensor::InsertSliceOp::create(rewriter,
|
||||
loc,
|
||||
source,
|
||||
dest,
|
||||
offsets,
|
||||
getStaticSizes(rewriter, sourceType.getShape()),
|
||||
getUnitStrides(rewriter, sourceType.getRank()))
|
||||
.getResult();
|
||||
}
|
||||
|
||||
} // namespace onnx_mlir
|
||||
|
||||
@@ -1,89 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||
#include "mlir/IR/BuiltinTypes.h"
|
||||
#include "mlir/IR/Value.h"
|
||||
#include "mlir/IR/ValueRange.h"
|
||||
#include "mlir/Transforms/DialectConversion.h"
|
||||
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
using HSliceId = size_t;
|
||||
using CoreId = size_t;
|
||||
|
||||
template <class A, class B, class C = std::common_type_t<A, B>>
|
||||
constexpr C ceilIntegerDivide(A a, B b) {
|
||||
static_assert(std::is_integral_v<A>, "A must be an integer type");
|
||||
static_assert(std::is_integral_v<B>, "B must be an integer type");
|
||||
C ac = static_cast<C>(a);
|
||||
C bc = static_cast<C>(b);
|
||||
return 1 + (ac - 1) / bc;
|
||||
}
|
||||
|
||||
template <class A, class B, class C = std::common_type_t<A, B>>
|
||||
constexpr std::pair<C, C> ceilIntegerDivideWithRemainder(A a, B b) {
|
||||
static_assert(std::is_integral_v<A>, "A must be an integer type");
|
||||
static_assert(std::is_integral_v<B>, "B must be an integer type");
|
||||
C ac = static_cast<C>(a);
|
||||
C bc = static_cast<C>(b);
|
||||
return {ceilIntegerDivide(ac, bc), ac % bc};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool isVectorShape(mlir::ArrayRef<T> shape) {
|
||||
return shape.size() == 2 && (shape[0] == 1 || shape[1] == 1);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool isMatrixShape(mlir::ArrayRef<T> shape) {
|
||||
return shape.size() == 2;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool isHVectorShape(mlir::ArrayRef<T> shape) {
|
||||
return shape.size() == 2 && shape[0] == 1;
|
||||
}
|
||||
|
||||
inline auto getTensorShape(mlir::Value tensor) {
|
||||
return mlir::cast<mlir::RankedTensorType>(tensor.getType()).getShape();
|
||||
}
|
||||
|
||||
inline bool haveSameStaticShape(mlir::Value lhs, mlir::Value rhs) {
|
||||
auto lhsType = mlir::dyn_cast<mlir::RankedTensorType>(lhs.getType());
|
||||
auto rhsType = mlir::dyn_cast<mlir::RankedTensorType>(rhs.getType());
|
||||
return lhsType && rhsType && lhsType.hasStaticShape() && rhsType.hasStaticShape()
|
||||
&& lhsType.getShape() == rhsType.getShape();
|
||||
}
|
||||
|
||||
bool hasStaticPositiveShape(mlir::ArrayRef<int64_t> shape);
|
||||
|
||||
bool hasStaticPositiveShape(mlir::RankedTensorType type);
|
||||
|
||||
int64_t getStaticShapeElementCount(mlir::ArrayRef<int64_t> shape);
|
||||
|
||||
llvm::SmallVector<int64_t> permuteShape(mlir::ArrayRef<int64_t> shape, mlir::ArrayRef<int64_t> permutation);
|
||||
|
||||
llvm::SmallVector<int64_t> invertPermutation(mlir::ArrayRef<int64_t> permutation);
|
||||
|
||||
mlir::FailureOr<llvm::SmallVector<int64_t>> getTransposePermutationChecked(std::optional<mlir::ArrayAttr> permAttr,
|
||||
int64_t rank);
|
||||
|
||||
llvm::SmallVector<mlir::OpFoldResult> getUnitStrides(mlir::PatternRewriter& rewriter, int64_t rank);
|
||||
|
||||
llvm::SmallVector<mlir::OpFoldResult> getZeroOffsets(mlir::PatternRewriter& rewriter, int64_t rank);
|
||||
|
||||
llvm::SmallVector<mlir::OpFoldResult> getStaticSizes(mlir::PatternRewriter& rewriter, mlir::ArrayRef<int64_t> shape);
|
||||
|
||||
/// Slices a statically shaped tensor along one axis into contiguous pieces of
|
||||
/// at most `sliceSize` elements.
|
||||
llvm::SmallVector<mlir::Value> sliceTensor(const mlir::Value& tensorToSlice,
|
||||
@@ -102,13 +28,4 @@ llvm::SmallVector<mlir::Value> sliceVector(const mlir::Value& vectorToSlice,
|
||||
llvm::DenseMap<CoreId, llvm::SmallVector<mlir::Value>> sliceVectorPerCrossbarPerCore(
|
||||
const mlir::Value& vectorToSlice, mlir::PatternRewriter& rewriter, mlir::Location loc);
|
||||
|
||||
mlir::Value extractAxisSlice(
|
||||
mlir::PatternRewriter& rewriter, mlir::Location loc, mlir::Value source, int64_t axis, int64_t offset, int64_t size);
|
||||
|
||||
mlir::Value insertStaticSlice(mlir::PatternRewriter& rewriter,
|
||||
mlir::Location loc,
|
||||
mlir::Value source,
|
||||
mlir::Value dest,
|
||||
llvm::ArrayRef<mlir::OpFoldResult> offsets);
|
||||
|
||||
} // namespace onnx_mlir
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
#include "llvm/ADT/SmallPtrSet.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ShapingUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||
@@ -21,24 +23,7 @@ using namespace mlir;
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
namespace {
|
||||
|
||||
static bool hasStaticUnitStrides(tensor::ExtractSliceOp extractSliceOp) {
|
||||
return llvm::all_of(extractSliceOp.getStaticStrides(), [](int64_t stride) { return stride == 1; });
|
||||
}
|
||||
|
||||
static bool hasConstantIndices(tensor::ExtractOp extractOp) {
|
||||
return llvm::all_of(extractOp.getIndices(), [](Value index) { return matchConstantIndexValue(index).has_value(); });
|
||||
}
|
||||
|
||||
static bool isStaticTensorResult(Operation* op) {
|
||||
return llvm::all_of(op->getResultTypes(), [](Type type) {
|
||||
auto shapedType = dyn_cast<ShapedType>(type);
|
||||
return shapedType && shapedType.hasStaticShape();
|
||||
});
|
||||
}
|
||||
|
||||
static FailureOr<DenseElementsAttr> transposeDenseElements(DenseElementsAttr denseAttr, ArrayRef<int64_t> perms) {
|
||||
FailureOr<DenseElementsAttr> transposeDenseElementsAttr(DenseElementsAttr denseAttr, ArrayRef<int64_t> perms) {
|
||||
auto tensorType = dyn_cast<RankedTensorType>(denseAttr.getType());
|
||||
if (!tensorType)
|
||||
return failure();
|
||||
@@ -59,7 +44,45 @@ static FailureOr<DenseElementsAttr> transposeDenseElements(DenseElementsAttr den
|
||||
|
||||
auto transposedType = RankedTensorType::get(transposedShape, tensorType.getElementType(), tensorType.getEncoding());
|
||||
if (denseAttr.isSplat())
|
||||
return DenseElementsAttr::get(transposedType, denseAttr.getSplatValue<Attribute>());
|
||||
return DenseElementsAttr::getFromRawBuffer(transposedType, denseAttr.getRawData());
|
||||
|
||||
const unsigned elementBitWidth = tensorType.getElementTypeBitWidth();
|
||||
const ArrayRef<char> inputData = denseAttr.getRawData();
|
||||
if (elementBitWidth % 8 == 0) {
|
||||
const size_t elementBytes = elementBitWidth / 8;
|
||||
const size_t expectedBytes = denseAttr.getNumElements() * elementBytes;
|
||||
if (inputData.size() == expectedBytes) {
|
||||
SmallVector<char> transposedData(expectedBytes);
|
||||
if (rank == 2 && perms[0] == 1 && perms[1] == 0) {
|
||||
const int64_t rows = tensorType.getDimSize(0);
|
||||
const int64_t columns = tensorType.getDimSize(1);
|
||||
for (int64_t row = 0; row < rows; ++row)
|
||||
for (int64_t column = 0; column < columns; ++column)
|
||||
std::memcpy(transposedData.data() + (column * rows + row) * elementBytes,
|
||||
inputData.data() + (row * columns + column) * elementBytes,
|
||||
elementBytes);
|
||||
return DenseElementsAttr::getFromRawBuffer(transposedType, transposedData);
|
||||
}
|
||||
|
||||
SmallVector<int64_t> originalStrides = computeRowMajorStrides(tensorType.getShape());
|
||||
SmallVector<int64_t> transposedStrides = computeRowMajorStrides(transposedShape);
|
||||
SmallVector<int64_t> originalIndices(rank);
|
||||
for (int64_t linearIndex = 0; linearIndex < tensorType.getNumElements(); ++linearIndex) {
|
||||
int64_t remaining = linearIndex;
|
||||
for (int64_t dim = 0; dim < rank; ++dim) {
|
||||
originalIndices[dim] = originalStrides.empty() ? 0 : remaining / originalStrides[dim];
|
||||
remaining = originalStrides.empty() ? 0 : remaining % originalStrides[dim];
|
||||
}
|
||||
int64_t transposedLinearIndex = 0;
|
||||
for (int64_t dim = 0; dim < rank; ++dim)
|
||||
transposedLinearIndex += originalIndices[perms[dim]] * transposedStrides[dim];
|
||||
std::memcpy(transposedData.data() + transposedLinearIndex * elementBytes,
|
||||
inputData.data() + linearIndex * elementBytes,
|
||||
elementBytes);
|
||||
}
|
||||
return DenseElementsAttr::getFromRawBuffer(transposedType, transposedData);
|
||||
}
|
||||
}
|
||||
|
||||
SmallVector<Attribute> originalValues(denseAttr.getValues<Attribute>());
|
||||
SmallVector<Attribute> transposedValues(originalValues.size());
|
||||
@@ -84,16 +107,30 @@ static FailureOr<DenseElementsAttr> transposeDenseElements(DenseElementsAttr den
|
||||
return DenseElementsAttr::get(transposedType, transposedValues);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
static bool hasStaticUnitStrides(tensor::ExtractSliceOp extractSliceOp) {
|
||||
return llvm::all_of(extractSliceOp.getStaticStrides(), [](int64_t stride) { return stride == 1; });
|
||||
}
|
||||
|
||||
static bool hasConstantIndices(tensor::ExtractOp extractOp) {
|
||||
return llvm::all_of(extractOp.getIndices(), [](Value index) { return matchConstantIndexValue(index).has_value(); });
|
||||
}
|
||||
|
||||
static bool isStaticTensorResult(Operation* op) {
|
||||
return llvm::all_of(op->getResultTypes(), [](Type type) {
|
||||
auto shapedType = dyn_cast<ShapedType>(type);
|
||||
return shapedType && shapedType.hasStaticShape();
|
||||
});
|
||||
}
|
||||
|
||||
static FailureOr<DenseElementsAttr> reshapeDenseElements(DenseElementsAttr denseAttr, RankedTensorType resultType) {
|
||||
auto sourceType = dyn_cast<RankedTensorType>(denseAttr.getType());
|
||||
if (!sourceType || !resultType || sourceType.getNumElements() != resultType.getNumElements())
|
||||
if (!sourceType || !resultType || sourceType.getNumElements() != resultType.getNumElements()
|
||||
|| sourceType.getElementType() != resultType.getElementType())
|
||||
return failure();
|
||||
|
||||
if (denseAttr.isSplat())
|
||||
return DenseElementsAttr::get(resultType, denseAttr.getSplatValue<Attribute>());
|
||||
|
||||
SmallVector<Attribute> values(denseAttr.getValues<Attribute>());
|
||||
return DenseElementsAttr::get(resultType, values);
|
||||
return DenseElementsAttr::getFromRawBuffer(resultType, denseAttr.getRawData());
|
||||
}
|
||||
|
||||
static FailureOr<DenseElementsAttr> extractSliceDenseElements(DenseElementsAttr denseAttr,
|
||||
@@ -161,7 +198,7 @@ static DenseElementsAttr getHostConstantDenseElementsAttrImpl(Value value, llvm:
|
||||
perm.reserve(transposeOp.getPermAttr().size());
|
||||
for (IntegerAttr attr : transposeOp.getPermAttr().getAsRange<IntegerAttr>())
|
||||
perm.push_back(attr.getInt());
|
||||
auto transposedAttr = transposeDenseElements(inputAttr, perm);
|
||||
auto transposedAttr = transposeDenseElementsAttr(inputAttr, perm);
|
||||
return succeeded(transposedAttr) ? *transposedAttr : nullptr;
|
||||
}
|
||||
|
||||
@@ -171,7 +208,7 @@ static DenseElementsAttr getHostConstantDenseElementsAttrImpl(Value value, llvm:
|
||||
return nullptr;
|
||||
|
||||
SmallVector<int64_t> perm(transposeOp.getPermutation().begin(), transposeOp.getPermutation().end());
|
||||
auto transposedAttr = transposeDenseElements(inputAttr, perm);
|
||||
auto transposedAttr = transposeDenseElementsAttr(inputAttr, perm);
|
||||
return succeeded(transposedAttr) ? *transposedAttr : nullptr;
|
||||
}
|
||||
|
||||
@@ -219,6 +256,9 @@ getCompileTimeSourceImpl(Operation* op, llvm::SmallPtrSetImpl<Operation*>& visit
|
||||
|
||||
chainLength += 1;
|
||||
|
||||
if (!isShapingOnlyOp(op))
|
||||
return std::nullopt;
|
||||
|
||||
if (auto extractOp = dyn_cast<tensor::ExtractOp>(op))
|
||||
return hasConstantIndices(extractOp)
|
||||
? getCompileTimeSourceImpl(extractOp.getTensor().getDefiningOp(), visited, chainLength)
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "mlir/IR/Operation.h"
|
||||
#include "mlir/IR/Value.h"
|
||||
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
struct CompileTimeSource {
|
||||
@@ -19,4 +21,7 @@ bool isCompileTimeOp(mlir::Operation* op);
|
||||
|
||||
mlir::DenseElementsAttr getHostConstDenseElementsAttr(mlir::Value value);
|
||||
|
||||
mlir::FailureOr<mlir::DenseElementsAttr> transposeDenseElementsAttr(
|
||||
mlir::DenseElementsAttr denseAttr, llvm::ArrayRef<int64_t> permutation);
|
||||
|
||||
} // namespace onnx_mlir
|
||||
|
||||
@@ -11,12 +11,16 @@
|
||||
#include "llvm/ADT/SmallPtrSet.h"
|
||||
|
||||
#include "Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp"
|
||||
#include "mlir/Transforms/Passes.h"
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "src/Accelerators/PIM/Common/Support/DebugDump.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/BiasAddUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/RowStripLayoutUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/PlanLowering.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/Transforms/MergeComputeNodes/SpatialDataflowCsvExporter.hpp"
|
||||
#include "src/Accelerators/PIM/Pass/PIMPasses.h"
|
||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||
|
||||
@@ -28,14 +32,6 @@ namespace {
|
||||
static constexpr StringLiteral kDenseLayout = "dense_nchw";
|
||||
static constexpr StringLiteral kRowStripLayout = "nchw_row_strip";
|
||||
|
||||
struct RowStripPhysicalValue {
|
||||
Value physicalValue;
|
||||
RankedTensorType logicalType;
|
||||
SmallVector<int64_t, 16> fragmentOffsets;
|
||||
SmallVector<int64_t, 16> fragmentSizes;
|
||||
std::string indexMap;
|
||||
};
|
||||
|
||||
static FailureOr<RowStripPhysicalValue> getRowStripValue(llvm::DenseMap<Value, RowStripPhysicalValue>& rowStripValues,
|
||||
Value value) {
|
||||
auto it = rowStripValues.find(value);
|
||||
@@ -44,113 +40,43 @@ static FailureOr<RowStripPhysicalValue> getRowStripValue(llvm::DenseMap<Value, R
|
||||
return it->second;
|
||||
}
|
||||
|
||||
static FailureOr<RowStripPhysicalValue> buildRowStripValue(spatial::SpatReconciliatorOp reconciliator,
|
||||
Value physicalValue) {
|
||||
auto logicalType = dyn_cast<RankedTensorType>(reconciliator.getOutput().getType());
|
||||
static FailureOr<RowStripPhysicalValue> buildRowStripValue(spatial::SpatBlueprintOp blueprint,
|
||||
Value storage) {
|
||||
auto logicalType = dyn_cast<RankedTensorType>(blueprint.getOutput().getType());
|
||||
if (!logicalType)
|
||||
return reconciliator.emitOpError("requires ranked logical output type"), failure();
|
||||
return blueprint.emitOpError("requires ranked logical output type"), failure();
|
||||
RowStripPhysicalValue value;
|
||||
value.physicalValue = physicalValue;
|
||||
value.storage = storage;
|
||||
value.logicalType = logicalType;
|
||||
value.fragmentOffsets.append(reconciliator.getFragmentOffsets().begin(), reconciliator.getFragmentOffsets().end());
|
||||
value.fragmentSizes.append(reconciliator.getFragmentSizes().begin(), reconciliator.getFragmentSizes().end());
|
||||
value.indexMap = reconciliator.getIndexMap().str();
|
||||
value.fragmentOffsets.append(blueprint.getFragmentOffsets().begin(), blueprint.getFragmentOffsets().end());
|
||||
value.fragmentSizes.append(blueprint.getFragmentSizes().begin(), blueprint.getFragmentSizes().end());
|
||||
if (blueprint.getIndexMap() != kRowStripIndexMap)
|
||||
return blueprint.emitOpError("requires the canonical row-strip index map"), failure();
|
||||
auto storageType = dyn_cast<RankedTensorType>(storage.getType());
|
||||
if (!storageType || storageType != getRowStripStorageType(logicalType))
|
||||
return blueprint.emitOpError("requires physical row-strip fragment storage"), failure();
|
||||
return value;
|
||||
}
|
||||
|
||||
static FailureOr<Value>
|
||||
lowerRowStripRelu(const RowStripPhysicalValue& input, spatial::SpatReluPlanOp planOp, PatternRewriter& rewriter) {
|
||||
auto packedType = cast<RankedTensorType>(input.physicalValue.getType());
|
||||
auto computeOp =
|
||||
createSpatCompute<1>(rewriter, planOp.getLoc(), TypeRange {packedType}, {}, input.physicalValue, [&](Value x) {
|
||||
auto relu = spatial::SpatReluOp::create(rewriter, planOp.getLoc(), packedType, x);
|
||||
spatial::SpatYieldOp::create(rewriter, planOp.getLoc(), relu.getResult());
|
||||
});
|
||||
return computeOp.getResult(0);
|
||||
return applyRowStripRelu(input.storage, input.logicalType, rewriter, planOp.getLoc());
|
||||
}
|
||||
|
||||
static FailureOr<Value> lowerRowStripBiasAdd(const RowStripPhysicalValue& input,
|
||||
spatial::SpatBiasAddPlanOp planOp,
|
||||
PatternRewriter& rewriter) {
|
||||
return applyRowStripBiasAdd(input.storage, input.logicalType, planOp.getBias(), rewriter, planOp.getLoc());
|
||||
}
|
||||
|
||||
static FailureOr<Value>
|
||||
materializeRowStripToDense(const RowStripPhysicalValue& rowStripValue, Location loc, PatternRewriter& rewriter) {
|
||||
auto packedType = dyn_cast<RankedTensorType>(rowStripValue.physicalValue.getType());
|
||||
if (!packedType || packedType.getRank() != 3 || !packedType.hasStaticShape())
|
||||
return failure();
|
||||
if (rowStripValue.logicalType.getRank() != 4 || !rowStripValue.logicalType.hasStaticShape())
|
||||
return failure();
|
||||
if (rowStripValue.indexMap != "packed_hwc_rows_to_nchw")
|
||||
auto [expectedOffsets, expectedSizes] = buildRowStripMetadata(rowStripValue.logicalType);
|
||||
if (!llvm::equal(rowStripValue.fragmentOffsets, expectedOffsets) || !llvm::equal(rowStripValue.fragmentSizes, expectedSizes))
|
||||
return failure();
|
||||
|
||||
const int64_t rank = rowStripValue.logicalType.getRank();
|
||||
const int64_t fragmentCount = rowStripValue.fragmentOffsets.size() / rank;
|
||||
const int64_t packedWidth = packedType.getDimSize(1);
|
||||
const int64_t packedChannels = packedType.getDimSize(2);
|
||||
if (fragmentCount != packedType.getDimSize(0))
|
||||
return failure();
|
||||
for (int64_t fragmentIndex = 0; fragmentIndex < fragmentCount; ++fragmentIndex) {
|
||||
if (rowStripValue.fragmentOffsets[fragmentIndex * rank + 0] != 0
|
||||
|| rowStripValue.fragmentOffsets[fragmentIndex * rank + 1] != 0
|
||||
|| rowStripValue.fragmentOffsets[fragmentIndex * rank + 2] != fragmentIndex
|
||||
|| rowStripValue.fragmentOffsets[fragmentIndex * rank + 3] != 0)
|
||||
return failure();
|
||||
if (rowStripValue.fragmentSizes[fragmentIndex * rank + 0] != 1
|
||||
|| rowStripValue.fragmentSizes[fragmentIndex * rank + 1] != packedChannels
|
||||
|| rowStripValue.fragmentSizes[fragmentIndex * rank + 2] != 1
|
||||
|| rowStripValue.fragmentSizes[fragmentIndex * rank + 3] != packedWidth)
|
||||
return failure();
|
||||
}
|
||||
|
||||
auto packedSliceType =
|
||||
RankedTensorType::get({1, packedWidth, packedChannels}, packedType.getElementType(), packedType.getEncoding());
|
||||
auto expandedType =
|
||||
RankedTensorType::get({1, 1, packedWidth, packedChannels}, packedType.getElementType(), packedType.getEncoding());
|
||||
auto logicalFragmentType =
|
||||
RankedTensorType::get({1, packedChannels, 1, packedWidth}, packedType.getElementType(), packedType.getEncoding());
|
||||
auto batchOp = createSpatComputeBatch(
|
||||
rewriter,
|
||||
loc,
|
||||
TypeRange {rowStripValue.logicalType},
|
||||
fragmentCount,
|
||||
{},
|
||||
ValueRange {rowStripValue.physicalValue},
|
||||
[&](detail::SpatComputeBatchBodyArgs args) {
|
||||
SmallVector<OpFoldResult> packedOffsets {args.lane, rewriter.getIndexAttr(0), rewriter.getIndexAttr(0)};
|
||||
SmallVector<OpFoldResult> packedSizes {
|
||||
rewriter.getIndexAttr(1), rewriter.getIndexAttr(packedWidth), rewriter.getIndexAttr(packedChannels)};
|
||||
Value packedSlice = tensor::ExtractSliceOp::create(
|
||||
rewriter, loc, packedSliceType, args.inputs.front(), packedOffsets, packedSizes, getUnitStrides(rewriter, 3));
|
||||
|
||||
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();
|
||||
});
|
||||
if (failed(batchOp))
|
||||
return failure();
|
||||
return batchOp->getResult(0);
|
||||
return createRowStripAssemblyBlueprint(rowStripValue.storage, rowStripValue.logicalType, rewriter, loc);
|
||||
}
|
||||
|
||||
struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, OperationPass<ModuleOp>> {
|
||||
@@ -175,7 +101,7 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
|
||||
auto verifyLogicalPhase = [&](StringRef stage) -> bool {
|
||||
if (succeeded(verifyLogicalSpatialGraphInvariants(*entryFunc)))
|
||||
return true;
|
||||
moduleOp.emitError() << "RAPTOR_PHASE_CHECK logical Spatial graph verification failed " << stage;
|
||||
moduleOp.emitError() << "logical Spatial graph verification failed " << stage;
|
||||
signalPassFailure();
|
||||
return false;
|
||||
};
|
||||
@@ -185,15 +111,15 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
|
||||
for (Operation& op : llvm::make_early_inc_range(funcOp.getBody().front())) {
|
||||
if (auto planOp = dyn_cast<spatial::SpatConv2DPlanOp>(&op)) {
|
||||
FailureOr<RowStripPhysicalValue> rowStripInput = getRowStripValue(rowStripValues, planOp.getInput());
|
||||
auto rowStripReconciliator = llvm::find_if(planOp.getResult().getUsers(), [](Operation* user) {
|
||||
auto reconciliator = dyn_cast<spatial::SpatReconciliatorOp>(user);
|
||||
return reconciliator && reconciliator.getPhysicalLayout() == kRowStripLayout;
|
||||
auto rowStripBlueprint = llvm::find_if(planOp.getResult().getUsers(), [](Operation* user) {
|
||||
auto blueprint = dyn_cast<spatial::SpatBlueprintOp>(user);
|
||||
return blueprint && blueprint.getPhysicalLayout() == kRowStripLayout;
|
||||
});
|
||||
if (rowStripReconciliator != planOp.getResult().getUsers().end()) {
|
||||
if (rowStripBlueprint != planOp.getResult().getUsers().end()) {
|
||||
rewriter.setInsertionPoint(planOp);
|
||||
FailureOr<Value> lowered = lowerSelectedConv2DPlan(
|
||||
planOp,
|
||||
succeeded(rowStripInput) ? std::optional<Value> {rowStripInput->physicalValue} : std::nullopt,
|
||||
succeeded(rowStripInput) ? std::optional<Value> {rowStripInput->storage} : std::nullopt,
|
||||
/*emitRowStripLayout=*/true,
|
||||
rewriter);
|
||||
if (failed(lowered)) {
|
||||
@@ -201,15 +127,15 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
auto reconciliator = cast<spatial::SpatReconciliatorOp>(*rowStripReconciliator);
|
||||
FailureOr<RowStripPhysicalValue> rowStripValue = buildRowStripValue(reconciliator, *lowered);
|
||||
auto blueprint = cast<spatial::SpatBlueprintOp>(*rowStripBlueprint);
|
||||
FailureOr<RowStripPhysicalValue> rowStripValue = buildRowStripValue(blueprint, *lowered);
|
||||
if (failed(rowStripValue)) {
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
rowStripValues[reconciliator.getResult()] = *rowStripValue;
|
||||
rowStripValues[blueprint.getResult()] = *rowStripValue;
|
||||
eraseAfterLowering.insert(planOp);
|
||||
eraseAfterLowering.insert(reconciliator);
|
||||
eraseAfterLowering.insert(blueprint);
|
||||
continue;
|
||||
}
|
||||
rewriter.setInsertionPoint(planOp);
|
||||
@@ -226,12 +152,12 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
|
||||
|
||||
if (auto planOp = dyn_cast<spatial::SpatReluPlanOp>(&op)) {
|
||||
if (succeeded(getRowStripValue(rowStripValues, planOp.getInput()))) {
|
||||
auto outputReconciliator = llvm::find_if(planOp.getResult().getUsers(), [](Operation* user) {
|
||||
auto reconciliator = dyn_cast<spatial::SpatReconciliatorOp>(user);
|
||||
return reconciliator && reconciliator.getPhysicalLayout() == kRowStripLayout;
|
||||
auto outputBlueprint = llvm::find_if(planOp.getResult().getUsers(), [](Operation* user) {
|
||||
auto blueprint = dyn_cast<spatial::SpatBlueprintOp>(user);
|
||||
return blueprint && blueprint.getPhysicalLayout() == kRowStripLayout;
|
||||
});
|
||||
if (outputReconciliator == planOp.getResult().getUsers().end()) {
|
||||
planOp.emitOpError("row-strip Relu plan requires a row-strip reconciliator result");
|
||||
if (outputBlueprint == planOp.getResult().getUsers().end()) {
|
||||
planOp.emitOpError("row-strip Relu plan requires a row-strip blueprint result");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
@@ -244,15 +170,15 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
auto reconciliator = cast<spatial::SpatReconciliatorOp>(*outputReconciliator);
|
||||
FailureOr<RowStripPhysicalValue> output = buildRowStripValue(reconciliator, *lowered);
|
||||
auto blueprint = cast<spatial::SpatBlueprintOp>(*outputBlueprint);
|
||||
FailureOr<RowStripPhysicalValue> output = buildRowStripValue(blueprint, *lowered);
|
||||
if (failed(output)) {
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
rowStripValues[reconciliator.getResult()] = *output;
|
||||
rowStripValues[blueprint.getResult()] = *output;
|
||||
eraseAfterLowering.insert(planOp);
|
||||
eraseAfterLowering.insert(reconciliator);
|
||||
eraseAfterLowering.insert(blueprint);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -265,6 +191,64 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
|
||||
rewriter.replaceOp(planOp, computeOp.getResults());
|
||||
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;
|
||||
}
|
||||
auto computeOp = createSpatCompute<2>(rewriter,
|
||||
planOp.getLoc(),
|
||||
planOp.getOutput().getType(),
|
||||
{},
|
||||
ValueRange {planOp.getInput(), *denseBias},
|
||||
[&](Value x, Value y) {
|
||||
auto added = spatial::SpatVAddOp::create(
|
||||
rewriter, planOp.getLoc(), planOp.getOutput().getType(), x, y);
|
||||
spatial::SpatYieldOp::create(rewriter, planOp.getLoc(), added.getResult());
|
||||
});
|
||||
rewriter.replaceOp(planOp, computeOp.getResults());
|
||||
continue;
|
||||
}
|
||||
if (auto materializeOp = dyn_cast<spatial::SpatMaterializeLayoutOp>(&op)) {
|
||||
if (materializeOp.getSourcePhysicalLayout() == kDenseLayout
|
||||
&& materializeOp.getTargetPhysicalLayout() == kDenseLayout) {
|
||||
@@ -279,7 +263,7 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
|
||||
}
|
||||
FailureOr<RowStripPhysicalValue> rowStripValue = getRowStripValue(rowStripValues, materializeOp.getInput());
|
||||
if (failed(rowStripValue)) {
|
||||
materializeOp.emitOpError("expected a row-strip reconciliator input during row-strip materialization");
|
||||
materializeOp.emitOpError("expected a row-strip blueprint input during row-strip materialization");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
@@ -293,18 +277,20 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
|
||||
rewriter.replaceOp(materializeOp, *dense);
|
||||
continue;
|
||||
}
|
||||
if (auto reconciliatorOp = dyn_cast<spatial::SpatReconciliatorOp>(&op)) {
|
||||
if (reconciliatorOp.getPhysicalLayout() == kDenseLayout) {
|
||||
rewriter.replaceOp(reconciliatorOp, reconciliatorOp.getInput());
|
||||
if (auto blueprintOp = dyn_cast<spatial::SpatBlueprintOp>(&op)) {
|
||||
if (std::optional<StringRef> mode = blueprintOp.getMode(); mode && *mode == "fragment_assembly")
|
||||
continue;
|
||||
if (blueprintOp.getPhysicalLayout() == kDenseLayout) {
|
||||
rewriter.replaceOp(blueprintOp, blueprintOp.getInput());
|
||||
continue;
|
||||
}
|
||||
if (reconciliatorOp.getPhysicalLayout() != kRowStripLayout) {
|
||||
reconciliatorOp.emitOpError("non-dense reconciliator lowering is not supported yet");
|
||||
if (blueprintOp.getPhysicalLayout() != kRowStripLayout) {
|
||||
blueprintOp.emitOpError("non-dense blueprint lowering is not supported yet");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
if (!eraseAfterLowering.contains(reconciliatorOp)) {
|
||||
reconciliatorOp.emitOpError("unhandled row-strip reconciliator remained during LowerSpatialPlans");
|
||||
if (!eraseAfterLowering.contains(blueprintOp)) {
|
||||
blueprintOp.emitOpError("unhandled row-strip blueprint remained during LowerSpatialPlans");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
@@ -345,17 +331,25 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
|
||||
RewritePatternSet helperPatterns(ctx);
|
||||
populateGemmPatterns(helperPatterns, ctx);
|
||||
populateTransposePatterns(helperPatterns, ctx);
|
||||
if (failed(applyPartialConversion(moduleOp, helperTarget, std::move(helperPatterns)))) {
|
||||
moduleOp.emitError("failed to lower helper ONNX ops emitted by selected Spatial plan lowering");
|
||||
signalPassFailure();
|
||||
return;
|
||||
FrozenRewritePatternSet frozenHelperPatterns(
|
||||
std::move(helperPatterns));
|
||||
SmallVector<Operation*> topLevelHelperOps;
|
||||
funcOp.walk([&](Operation* op) {
|
||||
if (isa<spatial::SpatGraphCompute,
|
||||
spatial::SpatGraphComputeBatch>(op))
|
||||
return WalkResult::skip();
|
||||
if (isa<ONNXGemmOp, ONNXTransposeOp>(op))
|
||||
topLevelHelperOps.push_back(op);
|
||||
return WalkResult::advance();
|
||||
});
|
||||
for (Operation *helper : topLevelHelperOps) {
|
||||
if (failed(applyPartialConversion(
|
||||
helper, helperTarget, frozenHelperPatterns))) {
|
||||
moduleOp.emitError("failed to lower helper ONNX ops emitted by selected Spatial plan lowering");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
}
|
||||
FrozenRewritePatternSet nestedHelperPatterns([&] {
|
||||
RewritePatternSet patterns(ctx);
|
||||
populateGemmPatterns(patterns, ctx);
|
||||
populateTransposePatterns(patterns, ctx);
|
||||
return patterns;
|
||||
}());
|
||||
ConversionTarget nestedHelperTarget(*ctx);
|
||||
nestedHelperTarget.addLegalDialect<spatial::SpatialDialect,
|
||||
tensor::TensorDialect,
|
||||
@@ -371,7 +365,8 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
|
||||
computeLikeOps.push_back(op);
|
||||
});
|
||||
for (Operation* op : computeLikeOps) {
|
||||
if (failed(applyFullConversion(op, nestedHelperTarget, nestedHelperPatterns))) {
|
||||
if (failed(applyFullConversion(
|
||||
op, nestedHelperTarget, frozenHelperPatterns))) {
|
||||
op->emitOpError("failed to lower nested helper ONNX ops emitted by selected Spatial plan lowering");
|
||||
signalPassFailure();
|
||||
return;
|
||||
@@ -383,19 +378,37 @@ struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, Operatio
|
||||
moduleOp.walk([&](Operation* op) {
|
||||
if (isa<ONNXEntryPointOp>(op))
|
||||
return;
|
||||
if (isa<spatial::SpatConv2DPlanOp,
|
||||
spatial::SpatReluPlanOp,
|
||||
spatial::SpatReconciliatorOp,
|
||||
spatial::SpatMaterializeLayoutOp>(op)
|
||||
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::SpatMaterializeLayoutOp>(op)
|
||||
|| op->getDialect()->getNamespace() == "onnx") {
|
||||
op->emitOpError("operation must not remain after LowerSpatialPlans");
|
||||
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();
|
||||
else
|
||||
dumpModule(moduleOp, "spatial1_premerge");
|
||||
} else {
|
||||
dumpModule(moduleOp, "spatial1_graph");
|
||||
spatial::SpatialDataflowExportStage exportMode = spatial::getSpatialDataflowExportStage();
|
||||
if (spatial::shouldExportSpatialDataflowStage(exportMode, spatial::SpatialDataflowExportStage::Spatial1)
|
||||
&& failed(spatial::exportSpatialDataflowCsvGraph(funcOp, "spatial1_graph"))) {
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!verifyLogicalPhase("at the end of LowerSpatialPlans"))
|
||||
return;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "Common/Common.hpp"
|
||||
#include "Common/PimCommon.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/ComputeRegionBuilder.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||
@@ -45,11 +46,12 @@ static void populateEmptyFunction(func::FuncOp funcOp) {
|
||||
SmallVector<spatial::SpatGraphCompute> computes(funcOp.getOps<spatial::SpatGraphCompute>());
|
||||
SmallVector<spatial::SpatGraphComputeBatch> computeBatches(funcOp.getOps<spatial::SpatGraphComputeBatch>());
|
||||
SmallVector<spatial::SpatConv2DPlanOp> convPlans(funcOp.getOps<spatial::SpatConv2DPlanOp>());
|
||||
SmallVector<spatial::SpatBiasAddPlanOp> biasAddPlans(funcOp.getOps<spatial::SpatBiasAddPlanOp>());
|
||||
SmallVector<spatial::SpatReluPlanOp> reluPlans(funcOp.getOps<spatial::SpatReluPlanOp>());
|
||||
SmallVector<spatial::SpatReconciliatorOp> reconciliators(funcOp.getOps<spatial::SpatReconciliatorOp>());
|
||||
SmallVector<spatial::SpatBlueprintOp> blueprints(funcOp.getOps<spatial::SpatBlueprintOp>());
|
||||
SmallVector<spatial::SpatMaterializeLayoutOp> materializers(funcOp.getOps<spatial::SpatMaterializeLayoutOp>());
|
||||
if (!computes.empty() || !computeBatches.empty() || !convPlans.empty() || !reluPlans.empty() || !reconciliators.empty()
|
||||
|| !materializers.empty()) {
|
||||
if (!computes.empty() || !computeBatches.empty() || !convPlans.empty() || !biasAddPlans.empty() || !reluPlans.empty()
|
||||
|| !blueprints.empty() || !materializers.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -65,9 +67,9 @@ static void populateEmptyFunction(func::FuncOp funcOp) {
|
||||
sourceLocs.push_back(source.getLoc());
|
||||
}
|
||||
|
||||
auto newCompute = spatial::SpatGraphCompute::create(
|
||||
rewriter, returnOp.getLoc(), returnOp.getOperandTypes(), funcOp.getArguments(), {}, {});
|
||||
auto* newBlock = rewriter.createBlock(&newCompute.getBody(), newCompute.getBody().end(), sourceTypes, sourceLocs);
|
||||
auto newCompute = createEmptySpatGraphCompute(
|
||||
rewriter, returnOp.getLoc(), returnOp.getOperandTypes(), {}, funcOp.getArguments(), sourceTypes, sourceLocs);
|
||||
auto* newBlock = &newCompute.getBody().front();
|
||||
for (auto [blockArg, computeArg] : llvm::zip(newBlock->getArguments(), newCompute.getOperands()))
|
||||
mapper.map(computeArg, blockArg);
|
||||
newCompute.getProperties().setOperandSegmentSizes({0, static_cast<int>(sourceTypes.size())});
|
||||
@@ -103,7 +105,7 @@ void ONNXToSpatialPass::runOnOperation() {
|
||||
affine::AffineDialect,
|
||||
arith::ArithDialect,
|
||||
scf::SCFDialect>();
|
||||
preTarget.addIllegalOp<ONNXConstantOp, ONNXFlattenOp>();
|
||||
preTarget.addIllegalOp<ONNXConstantOp>();
|
||||
|
||||
RewritePatternSet prePatterns(ctx);
|
||||
populatePrePatterns(prePatterns, ctx);
|
||||
@@ -142,6 +144,7 @@ void ONNXToSpatialPass::runOnOperation() {
|
||||
target.addIllegalOp<ONNXSigmoidOp>();
|
||||
target.addIllegalOp<ONNXSoftmaxOp>();
|
||||
target.addIllegalOp<ONNXConcatOp>();
|
||||
target.addIllegalOp<ONNXFlattenOp>();
|
||||
target.addIllegalOp<ONNXGatherOp>();
|
||||
target.addIllegalOp<ONNXReshapeOp>();
|
||||
target.addIllegalOp<ONNXResizeOp>();
|
||||
@@ -160,7 +163,7 @@ void ONNXToSpatialPass::runOnOperation() {
|
||||
}
|
||||
|
||||
if (failed(verifyLogicalSpatialGraphInvariants(*entryFunc))) {
|
||||
moduleOp.emitError("RAPTOR_PHASE_CHECK logical Spatial graph verification failed after ONNX conversion");
|
||||
moduleOp.emitError("logical Spatial graph verification failed after ONNX conversion");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
@@ -173,15 +176,10 @@ void ONNXToSpatialPass::runOnOperation() {
|
||||
arith::ArithDialect,
|
||||
scf::SCFDialect>();
|
||||
|
||||
PassManager cleanupPM(ctx);
|
||||
cleanupPM.addPass(createCanonicalizerPass());
|
||||
if (failed(cleanupPM.run(moduleOp)))
|
||||
moduleOp.emitWarning("failed to run ONNX-to-Spatial canonicalization cleanup; continuing");
|
||||
|
||||
annotateWeightsConstants(*entryFunc);
|
||||
|
||||
if (failed(verifyLogicalSpatialGraphInvariants(*entryFunc))) {
|
||||
moduleOp.emitError("RAPTOR_PHASE_CHECK logical Spatial graph verification failed after weight annotation");
|
||||
moduleOp.emitError("logical Spatial graph verification failed after weight annotation");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
@@ -199,7 +197,7 @@ void ONNXToSpatialPass::runOnOperation() {
|
||||
[](spatial::SpatGraphComputeBatch computeOp) { return !requiresPostRewrite(computeOp); });
|
||||
|
||||
if (failed(verifyLogicalSpatialGraphInvariants(*entryFunc))) {
|
||||
moduleOp.emitError("RAPTOR_PHASE_CHECK logical Spatial graph verification failed before post rewrites");
|
||||
moduleOp.emitError("logical Spatial graph verification failed before post rewrites");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
@@ -213,13 +211,18 @@ void ONNXToSpatialPass::runOnOperation() {
|
||||
|
||||
populateEmptyFunction(*entryFunc);
|
||||
|
||||
if (failed(verifyLogicalSpatialGraphInvariants(*entryFunc))) {
|
||||
moduleOp.emitError("RAPTOR_PHASE_CHECK logical Spatial graph verification failed after ONNX-to-Spatial");
|
||||
PassManager canonicalizationPM(ctx);
|
||||
canonicalizationPM.addPass(createCanonicalizerPass());
|
||||
if (failed(canonicalizationPM.run(moduleOp)))
|
||||
moduleOp.emitWarning("failed to run ONNXToSpatial canonicalization; continuing");
|
||||
|
||||
dumpModule(moduleOp, "spatial0");
|
||||
|
||||
if (failed(verifyLogicalSpatialGraphInvariants(*entryFunc))) {
|
||||
moduleOp.emitError("logical Spatial graph verification failed after ONNX-to-Spatial");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
dumpModule(moduleOp, "spatial0");
|
||||
|
||||
if (failed(verifyONNXToSpatial(*entryFunc))) {
|
||||
moduleOp.emitError("ONNX-to-Spatial host legality verification failed");
|
||||
signalPassFailure();
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace onnx_mlir {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr StringLiteral kPhaseMarker = "RAPTOR_PHASE_CHECK";
|
||||
constexpr StringLiteral kPhaseMarker = "phase-check";
|
||||
|
||||
void checkWeightUseChains(func::FuncOp func, pim::CappedDiagnosticReporter& diagnostics) {
|
||||
func.walk([&](Operation* op) {
|
||||
@@ -56,13 +56,18 @@ bool isLegalExternalCapture(Value value, Region& region) {
|
||||
return definingOp && definingOp->hasTrait<OpTrait::ConstantLike>();
|
||||
}
|
||||
|
||||
bool isRecordedDeferredCommunicationSource(Operation* op, Value value) {
|
||||
auto transfer = dyn_cast<spatial::SpatDeferredCommunicationOp>(op);
|
||||
return transfer && llvm::is_contained(transfer.getSources(), value);
|
||||
}
|
||||
|
||||
template <typename ComputeOpTy>
|
||||
void verifyComputeBodyCaptures(ComputeOpTy compute, StringRef kind, pim::CappedDiagnosticReporter& diagnostics) {
|
||||
Region& body = compute.getBody();
|
||||
body.walk([&](Operation* nestedOp) {
|
||||
for (OpOperand& operand : nestedOp->getOpOperands()) {
|
||||
Value value = operand.get();
|
||||
if (isLegalExternalCapture(value, body))
|
||||
if (isLegalExternalCapture(value, body) || isRecordedDeferredCommunicationSource(nestedOp, value))
|
||||
continue;
|
||||
|
||||
Operation* definingOp = value.getDefiningOp();
|
||||
@@ -90,21 +95,29 @@ bool isLegalHostBackedValue(Value value) {
|
||||
return definingOp->getDialect()->getNamespace() != "spat";
|
||||
}
|
||||
|
||||
bool isScheduledPhase1Value(Value value) {
|
||||
Operation* definingOp = value.getDefiningOp();
|
||||
return isa_and_nonnull<spatial::SpatScheduledCompute, spatial::SpatScheduledComputeBatch>(definingOp);
|
||||
}
|
||||
|
||||
template <typename ComputeOpTy>
|
||||
void verifyScheduledInputs(ComputeOpTy compute,
|
||||
bool allowChannelReceiveInputs,
|
||||
StringRef kind,
|
||||
pim::CappedDiagnosticReporter& diagnostics) {
|
||||
for (auto [inputIndex, input] : llvm::enumerate(compute.getInputs())) {
|
||||
size_t currentInputIndex = inputIndex;
|
||||
Operation* definingOp = input.getDefiningOp();
|
||||
if (allowChannelReceiveInputs && isa_and_nonnull<spatial::SpatChannelReceiveOp>(definingOp))
|
||||
continue;
|
||||
if (isScheduledPhase1Value(input))
|
||||
continue;
|
||||
if (isLegalHostBackedValue(input))
|
||||
continue;
|
||||
|
||||
diagnostics.report(compute.getOperation(), [&](Operation* illegalOp) {
|
||||
InFlightDiagnostic diag = illegalOp->emitOpError()
|
||||
<< kPhaseMarker << " " << kind << " input #" << inputIndex
|
||||
<< kPhaseMarker << " " << kind << " input #" << currentInputIndex
|
||||
<< (allowChannelReceiveInputs ? " must come from the host or explicit spat.channel_receive"
|
||||
: " must come from the host");
|
||||
if (definingOp)
|
||||
@@ -113,14 +126,28 @@ void verifyScheduledInputs(ComputeOpTy compute,
|
||||
}
|
||||
}
|
||||
|
||||
template <typename ComputeOpTy>
|
||||
void verifyNoNestedFragmentAssemblyBlueprints(ComputeOpTy compute,
|
||||
pim::CappedDiagnosticReporter& diagnostics) {
|
||||
compute.getBody().walk([&](spatial::SpatBlueprintOp blueprint) {
|
||||
std::optional<StringRef> mode = blueprint.getMode();
|
||||
if (!mode || *mode != "fragment_assembly")
|
||||
return;
|
||||
diagnostics.report(blueprint.getOperation(), [&](Operation* illegalOp) {
|
||||
illegalOp->emitOpError("fragment assembly blueprint must be host-level after merge materialization");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void verifyLogicalTopLevelOps(func::FuncOp funcOp, pim::CappedDiagnosticReporter& diagnostics) {
|
||||
for (Operation& op : funcOp.getOps()) {
|
||||
if (isa<func::ReturnOp,
|
||||
spatial::SpatGraphCompute,
|
||||
spatial::SpatGraphComputeBatch,
|
||||
spatial::SpatConv2DPlanOp,
|
||||
spatial::SpatBiasAddPlanOp,
|
||||
spatial::SpatReluPlanOp,
|
||||
spatial::SpatReconciliatorOp,
|
||||
spatial::SpatBlueprintOp,
|
||||
spatial::SpatMaterializeLayoutOp>(&op)) {
|
||||
continue;
|
||||
}
|
||||
@@ -149,9 +176,9 @@ void verifyLogicalTopLevelOps(func::FuncOp funcOp, pim::CappedDiagnosticReporter
|
||||
|
||||
void verifyScheduledTopLevelOps(func::FuncOp funcOp, pim::CappedDiagnosticReporter& diagnostics) {
|
||||
for (Operation& op : funcOp.getOps()) {
|
||||
if (isa<spatial::SpatGraphCompute, spatial::SpatGraphComputeBatch>(&op)) {
|
||||
if (isa<spatial::SpatChannelSendOp, spatial::SpatChannelReceiveOp>(&op)) {
|
||||
diagnostics.report(&op, [&](Operation* illegalOp) {
|
||||
illegalOp->emitOpError() << kPhaseMarker << " graph Spatial compute op remained after merge materialization";
|
||||
illegalOp->emitOpError() << kPhaseMarker << " real channel communication is not allowed in scheduled phase 1";
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -188,10 +215,14 @@ LogicalResult verifyLogicalSpatialGraphInvariants(func::FuncOp funcOp) {
|
||||
LogicalResult verifyScheduledSpatialInvariants(func::FuncOp funcOp) {
|
||||
pim::CappedDiagnosticReporter diagnostics;
|
||||
verifyScheduledTopLevelOps(funcOp, diagnostics);
|
||||
for (auto compute : funcOp.getOps<spatial::SpatScheduledCompute>())
|
||||
for (auto compute : funcOp.getOps<spatial::SpatScheduledCompute>()) {
|
||||
verifyScheduledInputs(compute, /*allowChannelReceiveInputs=*/true, "spat.scheduled_compute", diagnostics);
|
||||
for (auto batch : funcOp.getOps<spatial::SpatScheduledComputeBatch>())
|
||||
verifyNoNestedFragmentAssemblyBlueprints(compute, diagnostics);
|
||||
}
|
||||
for (auto batch : funcOp.getOps<spatial::SpatScheduledComputeBatch>()) {
|
||||
verifyScheduledInputs(batch, /*allowChannelReceiveInputs=*/false, "spat.scheduled_compute_batch", diagnostics);
|
||||
verifyNoNestedFragmentAssemblyBlueprints(batch, diagnostics);
|
||||
}
|
||||
if (failed(verifyNoComputeBodyCaptures(funcOp)))
|
||||
return failure();
|
||||
diagnostics.emitSuppressedSummary(funcOp, "scheduled Spatial verification failed");
|
||||
|
||||
@@ -19,6 +19,7 @@ void populateConversionPatterns(RewritePatternSet& patterns, MLIRContext* ctx) {
|
||||
populateSigmoidPatterns(patterns, ctx);
|
||||
populateSoftmaxPatterns(patterns, ctx);
|
||||
populateConcatPatterns(patterns, ctx);
|
||||
populateFlattenPatterns(patterns, ctx);
|
||||
populateGatherPatterns(patterns, ctx);
|
||||
populateResizePatterns(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 populateSoftmaxPatterns(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 populateResizePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||
void populateReshapePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,77 @@
|
||||
#include "ConvGeometry.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
bool isDepthwiseConv(int64_t group, int64_t numChannelsIn, int64_t numChannelsOut, int64_t numChannelsInPerGroup) {
|
||||
return group == numChannelsIn && numChannelsInPerGroup == 1 && numChannelsOut % group == 0;
|
||||
}
|
||||
|
||||
ConvGeometry buildConvGeometry(const ConvLoweringState& state) {
|
||||
ConvGeometry geo {
|
||||
state.batchSize,
|
||||
state.numChannelsIn,
|
||||
state.xHeight,
|
||||
state.xWidth,
|
||||
state.numChannelsOut,
|
||||
state.wHeight,
|
||||
state.wWidth,
|
||||
state.outHeight,
|
||||
state.outWidth,
|
||||
state.group,
|
||||
state.numChannelsInPerGroup,
|
||||
state.numChannelsOutPerGroup,
|
||||
state.numChannelsInPerGroup * state.wHeight * state.wWidth,
|
||||
state.numChannelsOutPerGroup,
|
||||
state.batchSize * state.outHeight * state.outWidth,
|
||||
static_cast<int64_t>(crossbarSize.getValue()),
|
||||
1,
|
||||
0,
|
||||
state.hasBias,
|
||||
isDepthwiseConv(state.group, state.numChannelsIn, state.numChannelsOut, state.numChannelsInPerGroup),
|
||||
};
|
||||
geo.pack = std::max<int64_t>(1, geo.xbarSize / std::max<int64_t>(geo.k, geo.c));
|
||||
geo.im2colElements = static_cast<uint64_t>(std::max<int64_t>(0, geo.p)) * static_cast<uint64_t>(std::max<int64_t>(0, geo.k));
|
||||
return geo;
|
||||
}
|
||||
|
||||
uint64_t chooseStreamChunkPositions(const ConvGeometry& geo, int64_t packFactor) {
|
||||
const uint64_t patchElements = static_cast<uint64_t>(std::max<int64_t>(1, geo.k));
|
||||
uint64_t chunkPositions = std::max<uint64_t>(1, pimConvIm2colMaxElements / patchElements);
|
||||
chunkPositions = std::min<uint64_t>(chunkPositions, static_cast<uint64_t>(std::max<int64_t>(1, geo.p)));
|
||||
chunkPositions = std::min<uint64_t>(chunkPositions, std::max<uint64_t>(1, pimConvStreamChunkPositions));
|
||||
|
||||
if (packFactor > 1 && chunkPositions > static_cast<uint64_t>(packFactor)) {
|
||||
chunkPositions -= chunkPositions % static_cast<uint64_t>(packFactor);
|
||||
chunkPositions = std::max<uint64_t>(chunkPositions, static_cast<uint64_t>(packFactor));
|
||||
}
|
||||
return std::max<uint64_t>(1, chunkPositions);
|
||||
}
|
||||
|
||||
RowInterval computeConvInputRowsForOutputRows(RowInterval outputRows, const ConvLoweringState& state) {
|
||||
const int64_t rawBegin = outputRows.begin * state.strideHeight - state.padHeightBegin;
|
||||
const int64_t rawEnd =
|
||||
(outputRows.end - 1) * state.strideHeight - state.padHeightBegin + state.dilationHeight * (state.wHeight - 1) + 1;
|
||||
return {std::max<int64_t>(0, rawBegin), std::min<int64_t>(state.xHeight, rawEnd)};
|
||||
}
|
||||
|
||||
ConvRowDemand buildConvRowDemand(RowInterval outputRows, const ConvLoweringState& state) {
|
||||
ConvRowDemand demand;
|
||||
demand.outputRows = outputRows;
|
||||
demand.neededInputRows = computeConvInputRowsForOutputRows(outputRows, state);
|
||||
demand.acquiredInputRows = demand.neededInputRows;
|
||||
|
||||
const int64_t rawBegin = outputRows.begin * state.strideHeight - state.padHeightBegin;
|
||||
const int64_t rawEnd =
|
||||
(outputRows.end - 1) * state.strideHeight - state.padHeightBegin + state.dilationHeight * (state.wHeight - 1) + 1;
|
||||
demand.topHaloRows = std::max<int64_t>(0, -rawBegin);
|
||||
demand.bottomHaloRows = std::max<int64_t>(0, rawEnd - state.xHeight);
|
||||
demand.acquiredInputRows = demand.neededInputRows;
|
||||
return demand;
|
||||
}
|
||||
|
||||
} // namespace onnx_mlir
|
||||
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
|
||||
#include "mlir/IR/BuiltinTypes.h"
|
||||
#include "mlir/IR/Value.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
struct ConvLoweringState {
|
||||
mlir::Value x;
|
||||
mlir::Value w;
|
||||
mlir::Value b;
|
||||
mlir::RankedTensorType xType;
|
||||
mlir::RankedTensorType wType;
|
||||
mlir::RankedTensorType outType;
|
||||
int64_t batchSize;
|
||||
int64_t numChannelsIn;
|
||||
int64_t xHeight;
|
||||
int64_t xWidth;
|
||||
int64_t numChannelsOut;
|
||||
int64_t wHeight;
|
||||
int64_t wWidth;
|
||||
int64_t outHeight;
|
||||
int64_t outWidth;
|
||||
int64_t group;
|
||||
int64_t numChannelsInPerGroup;
|
||||
int64_t numChannelsOutPerGroup;
|
||||
int64_t padHeightBegin;
|
||||
int64_t padHeightEnd;
|
||||
int64_t padWidthBegin;
|
||||
int64_t padWidthEnd;
|
||||
int64_t strideHeight;
|
||||
int64_t strideWidth;
|
||||
int64_t dilationHeight;
|
||||
int64_t dilationWidth;
|
||||
bool hasBias;
|
||||
};
|
||||
|
||||
struct ConvGeometry {
|
||||
int64_t batchSize;
|
||||
int64_t numChannelsIn;
|
||||
int64_t xHeight;
|
||||
int64_t xWidth;
|
||||
int64_t numChannelsOut;
|
||||
int64_t wHeight;
|
||||
int64_t wWidth;
|
||||
int64_t outHeight;
|
||||
int64_t outWidth;
|
||||
int64_t group;
|
||||
int64_t numChannelsInPerGroup;
|
||||
int64_t numChannelsOutPerGroup;
|
||||
int64_t k;
|
||||
int64_t c;
|
||||
int64_t p;
|
||||
int64_t xbarSize;
|
||||
int64_t pack;
|
||||
uint64_t im2colElements;
|
||||
bool hasBias;
|
||||
bool isDepthwise;
|
||||
};
|
||||
|
||||
struct RowInterval {
|
||||
int64_t begin = 0;
|
||||
int64_t end = 0;
|
||||
};
|
||||
|
||||
struct ConvRowDemand {
|
||||
RowInterval outputRows;
|
||||
RowInterval neededInputRows;
|
||||
RowInterval acquiredInputRows;
|
||||
int64_t topHaloRows = 0;
|
||||
int64_t bottomHaloRows = 0;
|
||||
};
|
||||
|
||||
bool isDepthwiseConv(int64_t group, int64_t numChannelsIn, int64_t numChannelsOut, int64_t numChannelsInPerGroup);
|
||||
|
||||
ConvGeometry buildConvGeometry(const ConvLoweringState& state);
|
||||
|
||||
uint64_t chooseStreamChunkPositions(const ConvGeometry& geo, int64_t packFactor);
|
||||
|
||||
RowInterval computeConvInputRowsForOutputRows(RowInterval outputRows, const ConvLoweringState& state);
|
||||
|
||||
ConvRowDemand buildConvRowDemand(RowInterval outputRows, const ConvLoweringState& state);
|
||||
|
||||
} // namespace onnx_mlir
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/BiasAddUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
@@ -47,38 +47,28 @@ static FailureOr<Value> materializeBroadcastedConstantTensor(Value value,
|
||||
return failure();
|
||||
|
||||
const int64_t rankOffset = static_cast<int64_t>(resultShape.size() - sourceShape.size());
|
||||
for (int64_t i = 0; i < static_cast<int64_t>(resultShape.size()); ++i) {
|
||||
const int64_t sourceIndex = i - rankOffset;
|
||||
const int64_t sourceDim = sourceIndex < 0 ? 1 : sourceShape[sourceIndex];
|
||||
const int64_t resultDim = resultShape[i];
|
||||
if (sourceDim != 1 && sourceDim != resultDim)
|
||||
return failure();
|
||||
}
|
||||
|
||||
SmallVector<Attribute> sourceValues(denseAttr.getValues<Attribute>());
|
||||
SmallVector<int64_t> sourceStrides = computeRowMajorStrides(sourceShape);
|
||||
SmallVector<int64_t> resultStrides = computeRowMajorStrides(resultShape);
|
||||
|
||||
SmallVector<Attribute> sourceValues(denseAttr.getValues<Attribute>());
|
||||
SmallVector<Attribute> resultValues;
|
||||
resultValues.reserve(resultType.getNumElements());
|
||||
|
||||
for (int64_t flatIndex = 0; flatIndex < resultType.getNumElements(); ++flatIndex) {
|
||||
int64_t remaining = flatIndex;
|
||||
int64_t sourceFlatIndex = 0;
|
||||
|
||||
for (int64_t i = 0; i < static_cast<int64_t>(resultShape.size()); ++i) {
|
||||
const int64_t resultIndex = resultStrides.empty() ? 0 : remaining / resultStrides[i];
|
||||
remaining = resultStrides.empty() ? 0 : remaining % resultStrides[i];
|
||||
|
||||
const int64_t sourceIndex = i - rankOffset;
|
||||
if (sourceIndex < 0)
|
||||
continue;
|
||||
|
||||
const int64_t sourceDim = sourceShape[sourceIndex];
|
||||
const int64_t resultDim = resultShape[i];
|
||||
if (sourceDim != 1 && sourceDim != resultDim)
|
||||
return failure();
|
||||
const int64_t mappedIndex = sourceDim == 1 ? 0 : resultIndex;
|
||||
sourceFlatIndex += mappedIndex * sourceStrides[sourceIndex];
|
||||
}
|
||||
|
||||
resultValues.push_back(sourceValues[sourceFlatIndex]);
|
||||
}
|
||||
|
||||
@@ -106,7 +96,7 @@ static FailureOr<Value> materializeReciprocalTensor(Value value,
|
||||
if (failed(broadcastedValue))
|
||||
return failure();
|
||||
|
||||
auto denseAttr = dyn_cast<DenseFPElementsAttr>(getDenseConstantAttr(*broadcastedValue));
|
||||
auto denseAttr = dyn_cast<DenseFPElementsAttr>(getHostConstDenseElementsAttr(*broadcastedValue));
|
||||
if (!denseAttr)
|
||||
return failure();
|
||||
|
||||
@@ -185,10 +175,45 @@ struct DivToSpatialCompute : OpConversionPattern<ONNXDivOp> {
|
||||
}
|
||||
};
|
||||
|
||||
struct AddToSpatialCompute : OpConversionPattern<ONNXAddOp> {
|
||||
using OpConversionPattern::OpConversionPattern;
|
||||
|
||||
LogicalResult
|
||||
matchAndRewrite(ONNXAddOp op, ONNXAddOpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override {
|
||||
auto resultType = dyn_cast<RankedTensorType>(op.getResult().getType());
|
||||
if (!resultType || !resultType.hasStaticShape())
|
||||
return failure();
|
||||
|
||||
FailureOr<BiasAddPlanCandidate> candidate =
|
||||
classifyBiasAddPlanCandidate(adaptor.getA(), adaptor.getB(), resultType);
|
||||
if (succeeded(candidate)) {
|
||||
auto plan = spatial::SpatBiasAddPlanOp::create(
|
||||
rewriter, op.getLoc(), resultType, candidate->data, candidate->bias, rewriter.getStringAttr("nchw"));
|
||||
rewriter.replaceOp(op, plan.getResult());
|
||||
return success();
|
||||
}
|
||||
|
||||
auto lhs = prepareElementwiseOperand(adaptor.getA(), resultType, rewriter, op.getLoc());
|
||||
if (failed(lhs))
|
||||
return failure();
|
||||
auto rhs = prepareElementwiseOperand(adaptor.getB(), resultType, rewriter, op.getLoc());
|
||||
if (failed(rhs))
|
||||
return failure();
|
||||
|
||||
auto computeOp =
|
||||
createSpatCompute<2>(rewriter, op.getLoc(), resultType, {}, ValueRange {*lhs, *rhs}, [&](Value x, Value y) {
|
||||
auto loweredOp = spatial::SpatVAddOp::create(rewriter, op.getLoc(), resultType, x, y);
|
||||
spatial::SpatYieldOp::create(rewriter, op.getLoc(), loweredOp.getResult());
|
||||
});
|
||||
rewriter.replaceOp(op, computeOp);
|
||||
return success();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void populateElementwisePatterns(RewritePatternSet& patterns, MLIRContext* ctx) {
|
||||
patterns.add<BinaryElementwiseToSpatialCompute<ONNXAddOp, spatial::SpatVAddOp>>(ctx);
|
||||
patterns.add<AddToSpatialCompute>(ctx);
|
||||
patterns.add<BinaryElementwiseToSpatialCompute<ONNXSubOp, spatial::SpatVSubOp>>(ctx);
|
||||
patterns.add<BinaryElementwiseToSpatialCompute<ONNXMulOp, spatial::SpatVMulOp>>(ctx);
|
||||
patterns.add<DivToSpatialCompute>(ctx);
|
||||
|
||||
@@ -87,28 +87,6 @@ static Value createGemmBatchHOffset(Value lane,
|
||||
rewriter.getInsertionBlock()->getParentOp());
|
||||
}
|
||||
|
||||
static Value
|
||||
createZeroPaddedTensor(Value value, RankedTensorType resultType, ConversionPatternRewriter& rewriter, Location loc) {
|
||||
auto sourceType = cast<RankedTensorType>(value.getType());
|
||||
SmallVector<OpFoldResult> lowPads(sourceType.getRank(), rewriter.getIndexAttr(0));
|
||||
SmallVector<OpFoldResult> highPads;
|
||||
highPads.reserve(sourceType.getRank());
|
||||
for (auto [sourceDim, resultDim] : llvm::zip(sourceType.getShape(), resultType.getShape()))
|
||||
highPads.push_back(rewriter.getIndexAttr(resultDim - sourceDim));
|
||||
|
||||
auto padOp = tensor::PadOp::create(rewriter, loc, resultType, value, lowPads, highPads);
|
||||
auto* padBlock = new Block();
|
||||
for (int64_t i = 0; i < sourceType.getRank(); ++i)
|
||||
padBlock->addArgument(rewriter.getIndexType(), loc);
|
||||
padOp.getRegion().push_back(padBlock);
|
||||
rewriter.setInsertionPointToStart(padBlock);
|
||||
auto zero = getOrCreateConstant(
|
||||
rewriter, padOp.getOperation(), rewriter.getZeroAttr(sourceType.getElementType()), sourceType.getElementType());
|
||||
tensor::YieldOp::create(rewriter, loc, zero);
|
||||
rewriter.setInsertionPointAfter(padOp);
|
||||
return padOp.getResult();
|
||||
}
|
||||
|
||||
static FailureOr<Value> materializePaddedConstantMatrix(Value value,
|
||||
RankedTensorType resultType,
|
||||
ConversionPatternRewriter& rewriter,
|
||||
@@ -232,22 +210,6 @@ static Value extractATile(
|
||||
return tensor::ExtractSliceOp::create(rewriter, loc, aTileType, a, offsets, sizes, strides).getResult();
|
||||
}
|
||||
|
||||
static Value createPaddedInputCompute(Value input,
|
||||
RankedTensorType paddedInputType,
|
||||
ConversionPatternRewriter& rewriter,
|
||||
Location loc) {
|
||||
auto inputType = cast<RankedTensorType>(input.getType());
|
||||
if (inputType == paddedInputType)
|
||||
return input;
|
||||
|
||||
auto computeOp = createSpatCompute<1>(rewriter, loc, TypeRange {paddedInputType}, {}, input, [&](Value computeInput) {
|
||||
Value paddedInput = createZeroPaddedTensor(computeInput, paddedInputType, rewriter, loc);
|
||||
spatial::SpatYieldOp::create(rewriter, loc, paddedInput);
|
||||
});
|
||||
|
||||
return computeOp.getResult(0);
|
||||
}
|
||||
|
||||
static FailureOr<spatial::SpatComputeBatch> createVmmBatch(Value a,
|
||||
Value b,
|
||||
RankedTensorType aType,
|
||||
@@ -285,15 +247,11 @@ static FailureOr<spatial::SpatComputeBatch> createVmmBatch(Value a,
|
||||
SmallVector<OpFoldResult> bSizes {rewriter.getIndexAttr(crossbarSize.getValue()),
|
||||
rewriter.getIndexAttr(crossbarSize.getValue())};
|
||||
SmallVector<OpFoldResult> unitStrides = getUnitStrides(rewriter, 2);
|
||||
Value bTile =
|
||||
tensor::ExtractSliceOp::create(rewriter, loc, bTileType, args.weights.front(), bOffsets, bSizes, unitStrides)
|
||||
.getResult();
|
||||
Value bTile = extractStaticSliceOrIdentity(
|
||||
rewriter, loc, args.weights.front(), bTileType, bOffsets, bSizes, unitStrides);
|
||||
Value piece = spatial::SpatVMMOp::create(rewriter, loc, pieceType, bTile, aTile).getResult();
|
||||
|
||||
SmallVector<OpFoldResult> pieceOffsets {args.lane, rewriter.getIndexAttr(0)};
|
||||
SmallVector<OpFoldResult> pieceSizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(crossbarSize.getValue())};
|
||||
createParallelInsertSliceIntoBatchOutput(
|
||||
rewriter, loc, piece, args.outputs.front(), pieceOffsets, pieceSizes, unitStrides);
|
||||
publishGraphBatchPhysicalFragment(rewriter, loc, piece, args.outputs.front(), args.lane);
|
||||
});
|
||||
if (failed(batchOp))
|
||||
return failure();
|
||||
@@ -440,11 +398,7 @@ static FailureOr<spatial::SpatComputeBatch> createVvdmulBatch(Value a,
|
||||
Value bVector = extractDynamicGemmBColumn(args.inputs[1], column, vectorType, rewriter, loc);
|
||||
Value scalar = spatial::SpatVVDMulOp::create(rewriter, loc, scalarType, aVector, bVector).getResult();
|
||||
|
||||
SmallVector<OpFoldResult> outputOffsets {args.lane, rewriter.getIndexAttr(0)};
|
||||
SmallVector<OpFoldResult> scalarSizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
||||
SmallVector<OpFoldResult> unitStrides = getUnitStrides(rewriter, 2);
|
||||
createParallelInsertSliceIntoBatchOutput(
|
||||
rewriter, loc, scalar, args.outputs.front(), outputOffsets, scalarSizes, unitStrides);
|
||||
publishGraphBatchPhysicalFragment(rewriter, loc, scalar, args.outputs.front(), args.lane);
|
||||
});
|
||||
if (failed(batchOp))
|
||||
return failure();
|
||||
@@ -486,15 +440,14 @@ static FailureOr<spatial::SpatCompute> createDynamicGemmOutputCompute(Value scal
|
||||
Value row = createDynamicGemmBatchRow(lane, numOutCols, rewriter, nestedLoc);
|
||||
Value column =
|
||||
onnx_mlir::affineModConst(rewriter, nestedLoc, lane, numOutCols, rewriter.getInsertionBlock()->getParentOp());
|
||||
SmallVector<OpFoldResult> scalarOffsets {lane, rewriter.getIndexAttr(0)};
|
||||
SmallVector<OpFoldResult> scalarSizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
||||
SmallVector<OpFoldResult> unitStrides {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
||||
Value scalar = tensor::ExtractSliceOp::create(
|
||||
rewriter, nestedLoc, scalarType, pieces, scalarOffsets, scalarSizes, unitStrides)
|
||||
.getResult();
|
||||
FailureOr<Value> scalar = extractGraphBatchPhysicalFragment(rewriter, nestedLoc, pieces, lane, scalarType);
|
||||
if (failed(scalar))
|
||||
return failure();
|
||||
if (alpha != 1.0f) {
|
||||
Value alphaTensor = createScalarTensorConstant(scalarType, alpha, rewriter, nestedLoc);
|
||||
scalar = spatial::SpatVMulOp::create(rewriter, nestedLoc, scalarType, scalar, alphaTensor).getResult();
|
||||
*scalar = spatial::SpatVMulOp::create(rewriter, nestedLoc, scalarType, *scalar, alphaTensor).getResult();
|
||||
}
|
||||
if (biasArg) {
|
||||
Value biasScalar =
|
||||
@@ -504,11 +457,11 @@ static FailureOr<spatial::SpatCompute> createDynamicGemmOutputCompute(Value scal
|
||||
biasScalar =
|
||||
spatial::SpatVMulOp::create(rewriter, nestedLoc, scalarType, biasScalar, betaTensor).getResult();
|
||||
}
|
||||
scalar = spatial::SpatVAddOp::create(rewriter, nestedLoc, scalarType, scalar, biasScalar).getResult();
|
||||
*scalar = spatial::SpatVAddOp::create(rewriter, nestedLoc, scalarType, *scalar, biasScalar).getResult();
|
||||
}
|
||||
SmallVector<OpFoldResult> outputOffsets {row, column};
|
||||
Value outputNext =
|
||||
tensor::InsertSliceOp::create(rewriter, nestedLoc, scalar, outputAcc, outputOffsets, scalarSizes, unitStrides)
|
||||
tensor::InsertSliceOp::create(rewriter, nestedLoc, *scalar, outputAcc, outputOffsets, scalarSizes, unitStrides)
|
||||
.getResult();
|
||||
yielded.push_back(outputNext);
|
||||
return success();
|
||||
@@ -544,14 +497,13 @@ static Value extractReductionPiece(Value partialPiecesArg,
|
||||
int64_t numOutRows,
|
||||
ConversionPatternRewriter& rewriter,
|
||||
Location loc) {
|
||||
SmallVector<OpFoldResult> unitStrides {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
||||
SmallVector<OpFoldResult> pieceSizes {rewriter.getIndexAttr(numOutRows),
|
||||
rewriter.getIndexAttr(crossbarSize.getValue())};
|
||||
SmallVector<OpFoldResult> unitStrides {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
||||
SmallVector<OpFoldResult> pieceSizes {rewriter.getIndexAttr(numOutRows), rewriter.getIndexAttr(1), rewriter.getIndexAttr(crossbarSize.getValue())};
|
||||
SmallVector<OpFoldResult> pieceOffsets {
|
||||
createPartialGroupOffset(hSlice, kSlice, numKSlices, numOutRows, rewriter, loc), rewriter.getIndexAttr(0)};
|
||||
return tensor::ExtractSliceOp::create(
|
||||
rewriter, loc, pieceType, partialPiecesArg, pieceOffsets, pieceSizes, unitStrides)
|
||||
.getResult();
|
||||
createPartialGroupOffset(hSlice, kSlice, numKSlices, numOutRows, rewriter, loc), rewriter.getIndexAttr(0), rewriter.getIndexAttr(0)};
|
||||
auto selectedType = RankedTensorType::get({numOutRows, 1, static_cast<int64_t>(crossbarSize.getValue())}, pieceType.getElementType());
|
||||
Value selected = tensor::ExtractSliceOp::create(rewriter, loc, selectedType, partialPiecesArg, pieceOffsets, pieceSizes, unitStrides);
|
||||
return tensor::CollapseShapeOp::create(rewriter, loc, pieceType, selected, SmallVector<ReassociationIndices> {{0, 1}, {2}});
|
||||
}
|
||||
|
||||
static Value reducePartialPiecesForHSlice(Value partialPiecesArg,
|
||||
@@ -769,7 +721,7 @@ LogicalResult GemmToSpatialComputes::matchAndRewrite(ONNXGemmOp gemmOp,
|
||||
return failure();
|
||||
}
|
||||
|
||||
auto scalarPiecesType = RankedTensorType::get({laneCount64, 1}, outType.getElementType());
|
||||
auto scalarPiecesType = spatial::getGraphBatchPhysicalResultType(laneCount64, RankedTensorType::get({1, 1}, outType.getElementType()));
|
||||
auto batchOp = createVvdmulBatch(a, b, aType, bType, scalarPiecesType, outType, rewriter, loc);
|
||||
if (failed(batchOp))
|
||||
return failure();
|
||||
@@ -841,8 +793,8 @@ LogicalResult GemmToSpatialComputes::matchAndRewrite(ONNXGemmOp gemmOp,
|
||||
return failure();
|
||||
}
|
||||
|
||||
auto partialPiecesType =
|
||||
RankedTensorType::get({laneCount64, static_cast<int64_t>(crossbarSize.getValue())}, outType.getElementType());
|
||||
auto partialPiecesType = spatial::getGraphBatchPhysicalResultType(
|
||||
laneCount64, RankedTensorType::get({1, static_cast<int64_t>(crossbarSize.getValue())}, outType.getElementType()));
|
||||
auto batchOp =
|
||||
createVmmBatch(a, b, aType, paddedBType, partialPiecesType, numOutRows, numKSlices, numOutHSlices, rewriter, loc);
|
||||
if (failed(batchOp))
|
||||
|
||||
@@ -255,42 +255,6 @@ static Value transposeLastTwoDims(Value value, PatternRewriter& rewriter, Locati
|
||||
return createONNXTranspose(resultType, {0, 2, 1});
|
||||
}
|
||||
|
||||
static Value createZeroPaddedTensor(Value value, RankedTensorType resultType, PatternRewriter& rewriter, Location loc) {
|
||||
auto sourceType = cast<RankedTensorType>(value.getType());
|
||||
SmallVector<OpFoldResult> lowPads(sourceType.getRank(), rewriter.getIndexAttr(0));
|
||||
SmallVector<OpFoldResult> highPads;
|
||||
highPads.reserve(sourceType.getRank());
|
||||
for (auto [sourceDim, resultDim] : llvm::zip(sourceType.getShape(), resultType.getShape()))
|
||||
highPads.push_back(rewriter.getIndexAttr(resultDim - sourceDim));
|
||||
|
||||
auto padOp = tensor::PadOp::create(rewriter, loc, resultType, value, lowPads, highPads);
|
||||
auto* padBlock = new Block();
|
||||
for (int64_t i = 0; i < sourceType.getRank(); ++i)
|
||||
padBlock->addArgument(rewriter.getIndexType(), loc);
|
||||
padOp.getRegion().push_back(padBlock);
|
||||
rewriter.setInsertionPointToStart(padBlock);
|
||||
auto zero = getOrCreateConstant(
|
||||
rewriter, padOp.getOperation(), rewriter.getZeroAttr(sourceType.getElementType()), sourceType.getElementType());
|
||||
tensor::YieldOp::create(rewriter, loc, zero);
|
||||
rewriter.setInsertionPointAfter(padOp);
|
||||
return padOp.getResult();
|
||||
}
|
||||
|
||||
static Value createPaddedBatchedInputCompute(Value input,
|
||||
RankedTensorType paddedInputType,
|
||||
PatternRewriter& rewriter,
|
||||
Location loc) {
|
||||
auto inputType = cast<RankedTensorType>(input.getType());
|
||||
if (inputType == paddedInputType)
|
||||
return input;
|
||||
|
||||
auto computeOp = createSpatCompute<1>(rewriter, loc, TypeRange {paddedInputType}, {}, input, [&](Value computeInput) {
|
||||
Value paddedInput = createZeroPaddedTensor(computeInput, paddedInputType, rewriter, loc);
|
||||
spatial::SpatYieldOp::create(rewriter, loc, paddedInput);
|
||||
});
|
||||
return computeOp.getResult(0);
|
||||
}
|
||||
|
||||
static FailureOr<Value> materializePaddedBatchedWeight(Value value,
|
||||
ArrayRef<int64_t> sourceBatchShape,
|
||||
ArrayRef<int64_t> targetBatchShape,
|
||||
@@ -434,10 +398,7 @@ static FailureOr<spatial::SpatComputeBatch> createBatchedVmmBatch(Value a,
|
||||
args.weights.front(), bBatchShape, outputBatchShape, batch, kOffset, hOffset, bTileType, rewriter, loc);
|
||||
Value piece = spatial::SpatVMMOp::create(rewriter, loc, pieceType, bTile, aTile).getResult();
|
||||
|
||||
SmallVector<OpFoldResult> pieceOffsets {args.lane, rewriter.getIndexAttr(0)};
|
||||
SmallVector<OpFoldResult> pieceSizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(crossbarSize.getValue())};
|
||||
createParallelInsertSliceIntoBatchOutput(
|
||||
rewriter, loc, piece, args.outputs.front(), pieceOffsets, pieceSizes, getUnitStrides(rewriter, 2));
|
||||
publishGraphBatchPhysicalFragment(rewriter, loc, piece, args.outputs.front(), args.lane);
|
||||
});
|
||||
if (failed(batchOp))
|
||||
return failure();
|
||||
@@ -542,10 +503,7 @@ static FailureOr<spatial::SpatComputeBatch> createBatchedVvdmulBatch(Value a,
|
||||
Value bVector = extractDynamicBatchedBColumn(
|
||||
args.inputs[1], bBatchShape, outputBatchShape, batch, column, vectorType, rewriter, loc);
|
||||
Value scalar = spatial::SpatVVDMulOp::create(rewriter, loc, scalarType, aVector, bVector).getResult();
|
||||
SmallVector<OpFoldResult> outputOffsets {args.lane, rewriter.getIndexAttr(0)};
|
||||
SmallVector<OpFoldResult> scalarSizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
||||
createParallelInsertSliceIntoBatchOutput(
|
||||
rewriter, loc, scalar, args.outputs.front(), outputOffsets, scalarSizes, getUnitStrides(rewriter, 2));
|
||||
publishGraphBatchPhysicalFragment(rewriter, loc, scalar, args.outputs.front(), args.lane);
|
||||
});
|
||||
if (failed(batchOp))
|
||||
return failure();
|
||||
@@ -584,14 +542,13 @@ static FailureOr<Value> createBatchedDynamicOutputCompute(Value scalarPieces,
|
||||
Value batchLane = affineModConst(rewriter, nestedLoc, lane, numOutRows * numOutCols, anchorOp);
|
||||
Value row = affineFloorDivConst(rewriter, nestedLoc, batchLane, numOutCols, anchorOp);
|
||||
Value column = affineModConst(rewriter, nestedLoc, batchLane, numOutCols, anchorOp);
|
||||
SmallVector<OpFoldResult> scalarOffsets {lane, rewriter.getIndexAttr(0)};
|
||||
SmallVector<OpFoldResult> scalarSizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
||||
Value scalar = tensor::ExtractSliceOp::create(
|
||||
rewriter, nestedLoc, scalarType, pieces, scalarOffsets, scalarSizes, getUnitStrides(rewriter, 2));
|
||||
FailureOr<Value> scalar = extractGraphBatchPhysicalFragment(rewriter, nestedLoc, pieces, lane, scalarType);
|
||||
if (failed(scalar))
|
||||
return failure();
|
||||
Value expanded = tensor::ExpandShapeOp::create(rewriter,
|
||||
nestedLoc,
|
||||
outputScalarType,
|
||||
scalar,
|
||||
*scalar,
|
||||
SmallVector<ReassociationIndices> {
|
||||
{0},
|
||||
{1, 2}
|
||||
@@ -632,10 +589,11 @@ static Value extractBatchedReductionPiece(Value partialPiecesArg,
|
||||
Value kOffset = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), kSlice * numOutRows);
|
||||
Value batchAndHSlice = arith::AddIOp::create(rewriter, loc, batchOffset, hOffset);
|
||||
Value pieceOffset = arith::AddIOp::create(rewriter, loc, batchAndHSlice, kOffset);
|
||||
SmallVector<OpFoldResult> offsets {pieceOffset, rewriter.getIndexAttr(0)};
|
||||
SmallVector<OpFoldResult> sizes {rewriter.getIndexAttr(numOutRows), rewriter.getIndexAttr(crossbarSize.getValue())};
|
||||
return tensor::ExtractSliceOp::create(
|
||||
rewriter, loc, pieceType, partialPiecesArg, offsets, sizes, getUnitStrides(rewriter, 2));
|
||||
SmallVector<OpFoldResult> offsets {pieceOffset, rewriter.getIndexAttr(0), rewriter.getIndexAttr(0)};
|
||||
SmallVector<OpFoldResult> sizes {rewriter.getIndexAttr(numOutRows), rewriter.getIndexAttr(1), rewriter.getIndexAttr(crossbarSize.getValue())};
|
||||
auto selectedType = RankedTensorType::get({numOutRows, 1, static_cast<int64_t>(crossbarSize.getValue())}, pieceType.getElementType());
|
||||
Value selected = tensor::ExtractSliceOp::create(rewriter, loc, selectedType, partialPiecesArg, offsets, sizes, getUnitStrides(rewriter, 3));
|
||||
return tensor::CollapseShapeOp::create(rewriter, loc, pieceType, selected, SmallVector<ReassociationIndices> {{0, 1}, {2}});
|
||||
}
|
||||
|
||||
static Value reduceBatchedPartialPiecesForHSlice(Value partialPiecesArg,
|
||||
@@ -953,9 +911,7 @@ struct MatMulToGemm : OpRewritePattern<ONNXMatMulOp> {
|
||||
if (failed(shapeInfo) || shapeInfo->lhsWasVector || shapeInfo->rhsWasVector)
|
||||
return failure();
|
||||
|
||||
const bool hasNonSingletonOutputBatch =
|
||||
!shapeInfo->outputBatchShape.empty() && getStaticShapeElementCount(shapeInfo->outputBatchShape) != 1;
|
||||
if (hasNonSingletonOutputBatch)
|
||||
if (!shapeInfo->outputBatchShape.empty())
|
||||
return failure();
|
||||
|
||||
Location loc = matmulOp.getLoc();
|
||||
@@ -1055,10 +1011,10 @@ struct MatMulBatchedToSpatialComputes : OpRewritePattern<ONNXMatMulOp> {
|
||||
auto paddedRhs =
|
||||
materializePaddedBatchedWeight(plan.rhs, plan.rhsBatchShape, plan.outputBatchShape, paddedRhsType, rewriter);
|
||||
if (succeeded(paddedRhs)) {
|
||||
Value paddedLhs = createPaddedBatchedInputCompute(plan.lhs, paddedLhsType, rewriter, loc);
|
||||
Value paddedLhs = createPaddedInputCompute(plan.lhs, paddedLhsType, rewriter, loc);
|
||||
const int64_t laneCount = plan.batch * plan.m * numKSlices * numOutHSlices;
|
||||
auto partialPiecesType = RankedTensorType::get({laneCount, static_cast<int64_t>(crossbarSize.getValue())},
|
||||
shapeInfo->outType.getElementType());
|
||||
auto partialPiecesType = spatial::getGraphBatchPhysicalResultType(
|
||||
laneCount, RankedTensorType::get({1, static_cast<int64_t>(crossbarSize.getValue())}, shapeInfo->outType.getElementType()));
|
||||
auto batchOp = createBatchedVmmBatch(paddedLhs,
|
||||
*paddedRhs,
|
||||
paddedLhsType,
|
||||
@@ -1099,7 +1055,8 @@ struct MatMulBatchedToSpatialComputes : OpRewritePattern<ONNXMatMulOp> {
|
||||
}
|
||||
}
|
||||
const int64_t laneCount = plan.batch * plan.m * plan.n;
|
||||
auto scalarPiecesType = RankedTensorType::get({laneCount, 1}, shapeInfo->outType.getElementType());
|
||||
auto scalarPiecesType = spatial::getGraphBatchPhysicalResultType(
|
||||
laneCount, RankedTensorType::get({1, 1}, shapeInfo->outType.getElementType()));
|
||||
auto batchOp = createBatchedVvdmulBatch(plan.lhs,
|
||||
plan.lhsBatchShape,
|
||||
plan.rhs,
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
|
||||
@@ -122,14 +121,6 @@ static RankedTensorType getKeepdimsType(RankedTensorType inputType, Type element
|
||||
return RankedTensorType::get(shape, elementType, inputType.getEncoding());
|
||||
}
|
||||
|
||||
static RankedTensorType getCompactKeptType(RankedTensorType inputType, Type elementType, ArrayRef<bool> reducedAxes) {
|
||||
SmallVector<int64_t> shape;
|
||||
for (auto [dim, isReduced] : llvm::zip_equal(inputType.getShape(), reducedAxes))
|
||||
if (!isReduced)
|
||||
shape.push_back(dim);
|
||||
return RankedTensorType::get(shape, elementType, inputType.getEncoding());
|
||||
}
|
||||
|
||||
static RankedTensorType getReducedSliceType(RankedTensorType inputType, ArrayRef<bool> reducedAxes) {
|
||||
SmallVector<int64_t> shape;
|
||||
shape.reserve(inputType.getRank());
|
||||
@@ -139,9 +130,7 @@ static RankedTensorType getReducedSliceType(RankedTensorType inputType, ArrayRef
|
||||
}
|
||||
|
||||
static RankedTensorType getLanePackedKeepdimsType(int64_t laneCount, RankedTensorType leafType) {
|
||||
SmallVector<int64_t> shape(leafType.getShape().begin(), leafType.getShape().end());
|
||||
shape.front() = laneCount;
|
||||
return RankedTensorType::get(shape, leafType.getElementType(), leafType.getEncoding());
|
||||
return spatial::getGraphBatchPhysicalResultType(laneCount, leafType);
|
||||
}
|
||||
|
||||
static SmallVector<int64_t> getKeptAxes(ArrayRef<bool> reducedAxes) {
|
||||
@@ -191,12 +180,9 @@ static FailureOr<Value> buildReduceMeanKeepdimsBatch(Value input,
|
||||
|
||||
SmallVector<OpFoldResult> sliceOffsets;
|
||||
SmallVector<OpFoldResult> sliceSizes;
|
||||
SmallVector<OpFoldResult> insertOffsets;
|
||||
SmallVector<OpFoldResult> insertSizes(inputType.getRank(), rewriter.getIndexAttr(1));
|
||||
SmallVector<OpFoldResult> unitStrides = getUnitStrides(rewriter, inputType.getRank());
|
||||
sliceOffsets.reserve(inputType.getRank());
|
||||
sliceSizes.reserve(inputType.getRank());
|
||||
insertOffsets.reserve(inputType.getRank());
|
||||
|
||||
auto batchOp =
|
||||
createSpatComputeBatch(rewriter,
|
||||
@@ -209,7 +195,6 @@ static FailureOr<Value> buildReduceMeanKeepdimsBatch(Value input,
|
||||
size_t keptAxisIndex = 0;
|
||||
sliceOffsets.clear();
|
||||
sliceSizes.clear();
|
||||
insertOffsets.clear();
|
||||
for (auto [axis, isReduced] : llvm::enumerate(reducedAxes)) {
|
||||
if (isReduced) {
|
||||
sliceOffsets.push_back(rewriter.getIndexAttr(0));
|
||||
@@ -224,72 +209,90 @@ static FailureOr<Value> buildReduceMeanKeepdimsBatch(Value input,
|
||||
sliceSizes.push_back(rewriter.getIndexAttr(1));
|
||||
}
|
||||
|
||||
insertOffsets.push_back(args.lane);
|
||||
insertOffsets.append(inputType.getRank() - 1, rewriter.getIndexAttr(0));
|
||||
|
||||
Value slice = tensor::ExtractSliceOp::create(
|
||||
rewriter, loc, sliceType, args.inputs.front(), sliceOffsets, sliceSizes, unitStrides);
|
||||
Value reduced = spatial::SpatVAvgOp::create(rewriter, loc, leafType, slice).getResult();
|
||||
createParallelInsertSliceIntoBatchOutput(
|
||||
rewriter, loc, reduced, args.outputs.front(), insertOffsets, insertSizes, unitStrides);
|
||||
publishGraphBatchPhysicalFragment(rewriter, loc, reduced, args.outputs.front(), args.lane);
|
||||
});
|
||||
if (failed(batchOp))
|
||||
return failure();
|
||||
return (*batchOp).getResult(0);
|
||||
}
|
||||
|
||||
static Value buildKeepdimsFromLanePackedBatch(Value batchValue,
|
||||
RankedTensorType keepdimsType,
|
||||
RankedTensorType compactKeptType,
|
||||
ArrayRef<bool> reducedAxes,
|
||||
ConversionPatternRewriter& rewriter,
|
||||
Location loc) {
|
||||
auto batchType = cast<RankedTensorType>(batchValue.getType());
|
||||
if (batchType == keepdimsType)
|
||||
return batchValue;
|
||||
static FailureOr<Value> buildReduceMeanKeepdimsBlueprint(
|
||||
Value batchValue, RankedTensorType keepdimsType,
|
||||
ArrayRef<bool> reducedAxes, ConversionPatternRewriter& rewriter,
|
||||
Location loc) {
|
||||
auto batchType = dyn_cast<RankedTensorType>(batchValue.getType());
|
||||
int64_t rank = keepdimsType.getRank();
|
||||
if (!batchType || !batchType.hasStaticShape()
|
||||
|| !keepdimsType.hasStaticShape()
|
||||
|| static_cast<int64_t>(reducedAxes.size()) != rank
|
||||
|| batchType.getRank() != rank + 1
|
||||
|| batchType.getElementType() != keepdimsType.getElementType())
|
||||
return failure();
|
||||
|
||||
SmallVector<ReassociationIndices> collapseToFlat {{}};
|
||||
for (int64_t axis = 0; axis < batchType.getRank(); ++axis)
|
||||
collapseToFlat.front().push_back(axis);
|
||||
|
||||
SmallVector<ReassociationIndices> expandFlatToCompact(1);
|
||||
for (int64_t axis = 0; axis < compactKeptType.getRank(); ++axis)
|
||||
expandFlatToCompact.front().push_back(axis);
|
||||
|
||||
SmallVector<ReassociationIndices> expandCompactToKeepdims;
|
||||
ReassociationIndices pendingLeadingReducedAxes;
|
||||
int64_t laneCount = 1;
|
||||
SmallVector<int64_t> keptAxes;
|
||||
SmallVector<int64_t> keptAxisStrides;
|
||||
for (auto [axis, isReduced] : llvm::enumerate(reducedAxes)) {
|
||||
if (isReduced) {
|
||||
if (expandCompactToKeepdims.empty())
|
||||
pendingLeadingReducedAxes.push_back(axis);
|
||||
else
|
||||
expandCompactToKeepdims.back().push_back(axis);
|
||||
continue;
|
||||
}
|
||||
|
||||
expandCompactToKeepdims.emplace_back();
|
||||
auto& group = expandCompactToKeepdims.back();
|
||||
group.append(pendingLeadingReducedAxes.begin(), pendingLeadingReducedAxes.end());
|
||||
pendingLeadingReducedAxes.clear();
|
||||
group.push_back(axis);
|
||||
int64_t dim = keepdimsType.getDimSize(axis);
|
||||
if (dim <= 0 || (isReduced && dim != 1))
|
||||
return failure();
|
||||
if (!isReduced)
|
||||
keptAxes.push_back(axis);
|
||||
}
|
||||
if (!pendingLeadingReducedAxes.empty())
|
||||
expandCompactToKeepdims.back().append(pendingLeadingReducedAxes.begin(), pendingLeadingReducedAxes.end());
|
||||
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();
|
||||
|
||||
auto reshapeCompute =
|
||||
createSpatCompute<1>(rewriter, loc, TypeRange {keepdimsType}, {}, ValueRange {batchValue}, [&](Value input) {
|
||||
auto flatType =
|
||||
RankedTensorType::get({batchType.getDimSize(0)}, batchType.getElementType(), batchType.getEncoding());
|
||||
Value flat = tensor::CollapseShapeOp::create(rewriter, loc, flatType, input, collapseToFlat);
|
||||
Value compact = flat;
|
||||
if (compactKeptType != flatType)
|
||||
compact = tensor::ExpandShapeOp::create(rewriter, loc, compactKeptType, flat, expandFlatToCompact);
|
||||
Value keepdims = compact;
|
||||
if (keepdimsType != compactKeptType)
|
||||
keepdims = tensor::ExpandShapeOp::create(rewriter, loc, keepdimsType, compact, expandCompactToKeepdims);
|
||||
spatial::SpatYieldOp::create(rewriter, loc, keepdims);
|
||||
});
|
||||
return reshapeCompute.getResult(0);
|
||||
SmallVector<int64_t> operandIndices(laneCount, 0);
|
||||
SmallVector<int64_t> sourceSlots;
|
||||
SmallVector<int64_t> sourceOffsets(laneCount, 0);
|
||||
SmallVector<int64_t> fragmentOffsets;
|
||||
sourceSlots.reserve(laneCount);
|
||||
fragmentOffsets.reserve(laneCount * rank);
|
||||
for (int64_t lane = 0; lane < laneCount; ++lane) {
|
||||
sourceSlots.push_back(lane);
|
||||
size_t keptAxisIndex = 0;
|
||||
for (auto [axis, isReduced] : llvm::enumerate(reducedAxes)) {
|
||||
if (isReduced) {
|
||||
fragmentOffsets.push_back(0);
|
||||
continue;
|
||||
}
|
||||
int64_t dim = keepdimsType.getDimSize(axis);
|
||||
fragmentOffsets.push_back(
|
||||
(lane / keptAxisStrides[keptAxisIndex]) % dim);
|
||||
++keptAxisIndex;
|
||||
}
|
||||
}
|
||||
SmallVector<int64_t> fragmentSizes(fragmentOffsets.size(), 1);
|
||||
SmallVector<int64_t> fragmentStrides(fragmentOffsets.size(), 1);
|
||||
return spatial::SpatBlueprintOp::create(
|
||||
rewriter, loc, keepdimsType, batchValue, ValueRange {},
|
||||
rewriter.getStringAttr("nchw"),
|
||||
rewriter.getStringAttr("fragmented"),
|
||||
rewriter.getDenseI64ArrayAttr(fragmentOffsets),
|
||||
rewriter.getDenseI64ArrayAttr(fragmentSizes),
|
||||
rewriter.getStringAttr("reduce_mean_keepdims_fragments"),
|
||||
rewriter.getStringAttr("fragment_assembly"),
|
||||
rewriter.getDenseI64ArrayAttr(operandIndices),
|
||||
rewriter.getDenseI64ArrayAttr(sourceSlots),
|
||||
rewriter.getDenseI64ArrayAttr(sourceOffsets),
|
||||
rewriter.getDenseI64ArrayAttr(fragmentStrides),
|
||||
rewriter.getStringAttr("disjoint"),
|
||||
rewriter.getStringAttr("complete"))
|
||||
.getOutput();
|
||||
}
|
||||
|
||||
static SmallVector<ReassociationIndices> buildCollapseReassociation(ArrayRef<bool> reducedAxes) {
|
||||
@@ -366,26 +369,36 @@ struct ReduceMeanToSpatialCompute : OpConversionPattern<ReduceMeanOp> {
|
||||
|
||||
Location loc = reduceMeanOp.getLoc();
|
||||
RankedTensorType leafType = getAllOnesType(inputType, resultType.getElementType());
|
||||
RankedTensorType compactKeptType = getCompactKeptType(inputType, resultType.getElementType(), reducedAxes);
|
||||
RankedTensorType keepdimsType = getKeepdimsType(inputType, resultType.getElementType(), reducedAxes);
|
||||
int64_t laneCount = 1;
|
||||
for (int64_t dim : compactKeptType.getShape())
|
||||
for (auto [dim, isReduced] : llvm::zip_equal(keepdimsType.getShape(), reducedAxes)) {
|
||||
if (isReduced)
|
||||
continue;
|
||||
if (dim <= 0 || laneCount > std::numeric_limits<int32_t>::max() / dim)
|
||||
return rewriter.notifyMatchFailure(
|
||||
reduceMeanOp, "ReduceMean physical lane count is not representable");
|
||||
laneCount *= dim;
|
||||
}
|
||||
RankedTensorType batchType = getLanePackedKeepdimsType(laneCount, leafType);
|
||||
|
||||
auto lanePackedKeepdims =
|
||||
buildReduceMeanKeepdimsBatch(adaptor.getData(), reducedAxes, batchType, leafType, rewriter, loc);
|
||||
if (failed(lanePackedKeepdims))
|
||||
return failure();
|
||||
Value reducedKeepdims =
|
||||
buildKeepdimsFromLanePackedBatch(*lanePackedKeepdims, keepdimsType, compactKeptType, reducedAxes, rewriter, loc);
|
||||
auto reducedKeepdims = buildReduceMeanKeepdimsBlueprint(
|
||||
*lanePackedKeepdims, keepdimsType, reducedAxes, rewriter, loc);
|
||||
if (failed(reducedKeepdims))
|
||||
return rewriter.notifyMatchFailure(
|
||||
reduceMeanOp,
|
||||
"cannot build physical-fragment ReduceMean keepdims reconstruction");
|
||||
|
||||
if (semantics->keepdims != 0) {
|
||||
rewriter.replaceOp(reduceMeanOp, reducedKeepdims);
|
||||
rewriter.replaceOp(reduceMeanOp, *reducedKeepdims);
|
||||
return success();
|
||||
}
|
||||
|
||||
Value reduced = squeezeReducedAxes(reducedKeepdims, resultType, reducedAxes, rewriter, loc);
|
||||
Value reduced = squeezeReducedAxes(
|
||||
*reducedKeepdims, resultType, reducedAxes, rewriter, loc);
|
||||
rewriter.replaceOp(reduceMeanOp, reduced);
|
||||
return success();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "src/Accelerators/PIM/Common/IR/WeightUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/ComputeRegionBuilder.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/WeightMaterialization.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
@@ -128,8 +129,6 @@ struct PromoteWeightLikeComputeInputsPattern : OpRewritePattern<spatial::SpatGra
|
||||
Block& oldBlock = compute.getBody().front();
|
||||
|
||||
rewriter.setInsertionPointAfter(compute);
|
||||
auto newCompute = spatial::SpatGraphCompute::create(
|
||||
rewriter, compute.getLoc(), compute.getResultTypes(), promoted->newWeights, promoted->newInputs);
|
||||
SmallVector<Type> newBlockArgTypes;
|
||||
SmallVector<Location> newBlockArgLocs;
|
||||
for (Value weight : promoted->newWeights) {
|
||||
@@ -138,10 +137,14 @@ struct PromoteWeightLikeComputeInputsPattern : OpRewritePattern<spatial::SpatGra
|
||||
}
|
||||
llvm::append_range(newBlockArgTypes, promoted->newInputTypes);
|
||||
llvm::append_range(newBlockArgLocs, promoted->newInputLocs);
|
||||
auto* newBlock = rewriter.createBlock(
|
||||
&newCompute.getBody(), newCompute.getBody().end(), TypeRange(newBlockArgTypes), newBlockArgLocs);
|
||||
newCompute.getProperties().setOperandSegmentSizes(
|
||||
{static_cast<int>(promoted->newWeights.size()), static_cast<int>(promoted->newInputs.size())});
|
||||
auto newCompute = createEmptySpatGraphCompute(rewriter,
|
||||
compute.getLoc(),
|
||||
compute.getResultTypes(),
|
||||
promoted->newWeights,
|
||||
promoted->newInputs,
|
||||
TypeRange(newBlockArgTypes),
|
||||
newBlockArgLocs);
|
||||
auto* newBlock = &newCompute.getBody().front();
|
||||
rewriter.setInsertionPointToStart(newBlock);
|
||||
|
||||
IRRewriter bodyRewriter(rewriter.getContext());
|
||||
@@ -193,12 +196,6 @@ struct PromoteWeightLikeComputeBatchInputsPattern : OpRewritePattern<spatial::Sp
|
||||
|
||||
rewriter.setInsertionPointAfter(compute);
|
||||
|
||||
auto laneCountAttr = pim::getCheckedI32Attr(
|
||||
rewriter, compute, static_cast<uint64_t>(compute.getLaneCount()), "promoted compute_batch lane count");
|
||||
if (failed(laneCountAttr))
|
||||
return failure();
|
||||
auto newCompute = spatial::SpatGraphComputeBatch::create(
|
||||
rewriter, compute.getLoc(), compute.getResultTypes(), *laneCountAttr, promoted->newWeights, promoted->newInputs);
|
||||
auto laneArg = compute.getLaneArgument();
|
||||
if (!laneArg)
|
||||
return rewriter.notifyMatchFailure(compute, "missing compute_batch lane block argument");
|
||||
@@ -223,23 +220,30 @@ struct PromoteWeightLikeComputeBatchInputsPattern : OpRewritePattern<spatial::Sp
|
||||
newBlockArgLocs.push_back(outputArg->getLoc());
|
||||
}
|
||||
|
||||
auto* newBlock = rewriter.createBlock(
|
||||
&newCompute.getBody(), newCompute.getBody().end(), TypeRange(newBlockArgTypes), newBlockArgLocs);
|
||||
newCompute.getProperties().setOperandSegmentSizes(
|
||||
{static_cast<int>(promoted->newWeights.size()), static_cast<int>(promoted->newInputs.size())});
|
||||
auto newCompute = createEmptySpatGraphComputeBatch(rewriter,
|
||||
compute.getLoc(),
|
||||
compute.getResultTypes(),
|
||||
compute.getLaneCount(),
|
||||
promoted->newWeights,
|
||||
promoted->newInputs,
|
||||
TypeRange(newBlockArgTypes),
|
||||
newBlockArgLocs);
|
||||
if (failed(newCompute))
|
||||
return failure();
|
||||
auto* newBlock = &(*newCompute).getBody().front();
|
||||
rewriter.setInsertionPointToStart(newBlock);
|
||||
|
||||
IRRewriter bodyRewriter(rewriter.getContext());
|
||||
bodyRewriter.setInsertionPointToStart(newBlock);
|
||||
|
||||
IRMapping mapper;
|
||||
auto newLaneArg = newCompute.getLaneArgument();
|
||||
auto newLaneArg = (*newCompute).getLaneArgument();
|
||||
if (!newLaneArg)
|
||||
return rewriter.notifyMatchFailure(compute, "missing rewritten compute_batch lane block argument");
|
||||
mapper.map(*laneArg, *newLaneArg);
|
||||
for (auto [weightIndex, weight] : llvm::enumerate(compute.getWeights())) {
|
||||
auto oldWeightArg = compute.getWeightArgument(weightIndex);
|
||||
auto newWeightArg = newCompute.getWeightArgument(weightIndex);
|
||||
auto newWeightArg = (*newCompute).getWeightArgument(weightIndex);
|
||||
if (!oldWeightArg || !newWeightArg)
|
||||
return rewriter.notifyMatchFailure(compute, "missing compute_batch weight block argument during rewrite");
|
||||
mapper.map(*oldWeightArg, *newWeightArg);
|
||||
@@ -249,7 +253,7 @@ struct PromoteWeightLikeComputeBatchInputsPattern : OpRewritePattern<spatial::Sp
|
||||
*promoted,
|
||||
bodyRewriter,
|
||||
mapper,
|
||||
[&](size_t index) { return newCompute.getInputArgument(index); },
|
||||
[&](size_t index) { return (*newCompute).getInputArgument(index); },
|
||||
rewriter)))
|
||||
return failure();
|
||||
for (auto resultIndex : llvm::seq<size_t>(0, compute.getNumResults())) {
|
||||
@@ -263,7 +267,7 @@ struct PromoteWeightLikeComputeBatchInputsPattern : OpRewritePattern<spatial::Sp
|
||||
for (Operation& op : oldBlock)
|
||||
rewriter.clone(op, mapper);
|
||||
|
||||
rewriter.replaceOp(compute, newCompute.getResults());
|
||||
rewriter.replaceOp(compute, (*newCompute).getResults());
|
||||
return success();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||
@@ -52,35 +52,12 @@ static FailureOr<Value> materializeTransposedConstant(Value input,
|
||||
return failure();
|
||||
}
|
||||
|
||||
if (denseAttr.isSplat())
|
||||
return getOrCreateConstant(rewriter,
|
||||
rewriter.getInsertionBlock()->getParentOp(),
|
||||
DenseElementsAttr::get(resultType, denseAttr.getSplatValue<Attribute>()),
|
||||
resultType);
|
||||
|
||||
SmallVector<Attribute> inputValues(denseAttr.getValues<Attribute>());
|
||||
SmallVector<Attribute> resultValues(inputValues.size());
|
||||
SmallVector<int64_t> inputStrides = computeRowMajorStrides(inputType.getShape());
|
||||
SmallVector<int64_t> resultStrides = computeRowMajorStrides(resultType.getShape());
|
||||
SmallVector<int64_t> inputIndices(inputType.getRank(), 0);
|
||||
|
||||
for (auto [linearIndex, value] : llvm::enumerate(inputValues)) {
|
||||
int64_t remaining = static_cast<int64_t>(linearIndex);
|
||||
for (int64_t dim = 0; dim < inputType.getRank(); ++dim) {
|
||||
inputIndices[dim] = inputStrides.empty() ? 0 : remaining / inputStrides[dim];
|
||||
remaining = inputStrides.empty() ? 0 : remaining % inputStrides[dim];
|
||||
}
|
||||
|
||||
int64_t resultLinearIndex = 0;
|
||||
for (int64_t dim = 0; dim < resultType.getRank(); ++dim)
|
||||
resultLinearIndex += inputIndices[permutation[dim]] * resultStrides[dim];
|
||||
|
||||
resultValues[resultLinearIndex] = value;
|
||||
}
|
||||
|
||||
auto transposedAttr = transposeDenseElementsAttr(denseAttr, permutation);
|
||||
if (failed(transposedAttr) || transposedAttr->getType() != resultType)
|
||||
return failure();
|
||||
return getOrCreateConstant(rewriter,
|
||||
rewriter.getInsertionBlock()->getParentOp(),
|
||||
DenseElementsAttr::get(resultType, resultValues),
|
||||
*transposedAttr,
|
||||
resultType);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
|
||||
#include "Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp"
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/BiasAddUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/RowStripLayoutUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/PlanLowering.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
#include "src/Accelerators/PIM/Pass/PIMPasses.h"
|
||||
#include "src/Accelerators/PIM/Pass/PIMPasses.h"
|
||||
|
||||
using namespace mlir;
|
||||
|
||||
@@ -19,7 +20,6 @@ namespace {
|
||||
static constexpr StringLiteral kLogicalLayout = "nchw";
|
||||
static constexpr StringLiteral kDenseLayout = "dense_nchw";
|
||||
static constexpr StringLiteral kRowStripLayout = "nchw_row_strip";
|
||||
static constexpr StringLiteral kRowStripIndexMap = "packed_hwc_rows_to_nchw";
|
||||
|
||||
enum class SelectedLayout {
|
||||
DenseNchw,
|
||||
@@ -34,6 +34,8 @@ static SelectedLayout getSelectedLayout(llvm::DenseMap<Value, SelectedLayout>& l
|
||||
static bool usesSelectedRowStrip(Operation* user, llvm::DenseMap<Value, SelectedLayout>& layouts) {
|
||||
if (auto reluPlan = dyn_cast<spatial::SpatReluPlanOp>(user))
|
||||
return getSelectedLayout(layouts, reluPlan.getResult()) == SelectedLayout::NchwRowStrip;
|
||||
if (auto biasAddPlan = dyn_cast<spatial::SpatBiasAddPlanOp>(user))
|
||||
return getSelectedLayout(layouts, biasAddPlan.getResult()) == SelectedLayout::NchwRowStrip;
|
||||
if (auto convPlan = dyn_cast<spatial::SpatConv2DPlanOp>(user))
|
||||
return getSelectedLayout(layouts, convPlan.getResult()) == SelectedLayout::NchwRowStrip;
|
||||
return false;
|
||||
@@ -49,21 +51,26 @@ static bool allUsersCanHandleRowStrip(Value value, llvm::DenseMap<Value, Selecte
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::pair<SmallVector<int64_t>, SmallVector<int64_t>> buildRowStripMetadata(RankedTensorType type) {
|
||||
SmallVector<int64_t> offsets;
|
||||
SmallVector<int64_t> sizes;
|
||||
const int64_t channels = type.getDimSize(1);
|
||||
const int64_t height = type.getDimSize(2);
|
||||
const int64_t width = type.getDimSize(3);
|
||||
offsets.reserve(height * 4);
|
||||
sizes.reserve(height * 4);
|
||||
for (int64_t row = 0; row < height; ++row) {
|
||||
offsets.append({0, 0, row, 0});
|
||||
sizes.append({1, channels, 1, width});
|
||||
static bool canConsumeRowStripAsUser(Operation* user) {
|
||||
if (isa<spatial::SpatReluPlanOp>(user))
|
||||
return true;
|
||||
if (auto biasAddPlan = dyn_cast<spatial::SpatBiasAddPlanOp>(user)) {
|
||||
auto resultType = dyn_cast<RankedTensorType>(biasAddPlan.getOutput().getType());
|
||||
return resultType && isSupportedBiasAddValue(biasAddPlan.getBias(), resultType);
|
||||
}
|
||||
return {offsets, sizes};
|
||||
if (auto convPlan = dyn_cast<spatial::SpatConv2DPlanOp>(user))
|
||||
return succeeded(canConsumeAndProduceRowStrip(convPlan));
|
||||
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,
|
||||
llvm::DenseMap<Value, SelectedLayout>& layouts) {
|
||||
SelectedLayout inputLayout = getSelectedLayout(layouts, convPlan.getInput());
|
||||
@@ -76,6 +83,9 @@ static SelectedLayout chooseConvLayout(spatial::SpatConv2DPlanOp convPlan,
|
||||
llvm::DenseMap<Value, SelectedLayout>& layouts) {
|
||||
if (!canSelectConvRowStrip(convPlan, layouts))
|
||||
return SelectedLayout::DenseNchw;
|
||||
if (getSelectedLayout(layouts, convPlan.getInput()) != SelectedLayout::NchwRowStrip
|
||||
&& !hasRowStripConsumer(convPlan.getResult()))
|
||||
return SelectedLayout::DenseNchw;
|
||||
if (!allUsersCanHandleRowStrip(convPlan.getResult(), layouts))
|
||||
return SelectedLayout::DenseNchw;
|
||||
return SelectedLayout::NchwRowStrip;
|
||||
@@ -85,15 +95,31 @@ static SelectedLayout chooseReluLayout(spatial::SpatReluPlanOp reluPlan,
|
||||
llvm::DenseMap<Value, SelectedLayout>& layouts) {
|
||||
if (getSelectedLayout(layouts, reluPlan.getInput()) != SelectedLayout::NchwRowStrip)
|
||||
return SelectedLayout::DenseNchw;
|
||||
if (!hasRowStripConsumer(reluPlan.getResult()))
|
||||
return SelectedLayout::DenseNchw;
|
||||
if (!allUsersCanHandleRowStrip(reluPlan.getResult(), layouts))
|
||||
return SelectedLayout::DenseNchw;
|
||||
return SelectedLayout::NchwRowStrip;
|
||||
}
|
||||
|
||||
static spatial::SpatReconciliatorOp insertRowStripReconciliator(IRRewriter& rewriter, Value value) {
|
||||
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) {
|
||||
auto outputType = cast<RankedTensorType>(value.getType());
|
||||
auto [offsets, sizes] = buildRowStripMetadata(outputType);
|
||||
return spatial::SpatReconciliatorOp::create(rewriter,
|
||||
return spatial::SpatBlueprintOp::create(rewriter,
|
||||
value.getLoc(),
|
||||
outputType,
|
||||
value,
|
||||
@@ -107,6 +133,8 @@ static spatial::SpatReconciliatorOp insertRowStripReconciliator(IRRewriter& rewr
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
@@ -172,6 +200,14 @@ struct SpatialLayoutPlanningPass final : PassWrapper<SpatialLayoutPlanningPass,
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (auto biasAddPlan = dyn_cast<spatial::SpatBiasAddPlanOp>(&op)) {
|
||||
SelectedLayout selected = chooseBiasAddLayout(biasAddPlan, layouts);
|
||||
if (layouts[biasAddPlan.getResult()] != selected) {
|
||||
layouts[biasAddPlan.getResult()] = selected;
|
||||
changed = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +215,8 @@ struct SpatialLayoutPlanningPass final : PassWrapper<SpatialLayoutPlanningPass,
|
||||
Value producedValue;
|
||||
if (auto convPlan = dyn_cast<spatial::SpatConv2DPlanOp>(&op))
|
||||
producedValue = convPlan.getResult();
|
||||
else if (auto biasAddPlan = dyn_cast<spatial::SpatBiasAddPlanOp>(&op))
|
||||
producedValue = biasAddPlan.getResult();
|
||||
else if (auto reluPlan = dyn_cast<spatial::SpatReluPlanOp>(&op))
|
||||
producedValue = reluPlan.getResult();
|
||||
else
|
||||
@@ -188,12 +226,12 @@ struct SpatialLayoutPlanningPass final : PassWrapper<SpatialLayoutPlanningPass,
|
||||
continue;
|
||||
|
||||
rewriter.setInsertionPointAfter(&op);
|
||||
auto reconciliator = insertRowStripReconciliator(rewriter, producedValue);
|
||||
rewriter.replaceAllUsesExcept(producedValue, reconciliator.getResult(), reconciliator);
|
||||
materializeDenseUses(rewriter, reconciliator.getResult(), layouts);
|
||||
auto blueprint = insertRowStripBlueprint(rewriter, producedValue);
|
||||
rewriter.replaceAllUsesExcept(producedValue, blueprint.getResult(), blueprint);
|
||||
materializeDenseUses(rewriter, blueprint.getResult(), layouts);
|
||||
}
|
||||
if (failed(verifyLogicalSpatialGraphInvariants(*entryFunc))) {
|
||||
getOperation().emitError("RAPTOR_PHASE_CHECK logical Spatial graph verification failed after SpatialLayoutPlanning");
|
||||
getOperation().emitError("logical Spatial graph verification failed after SpatialLayoutPlanning");
|
||||
signalPassFailure();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -2,13 +2,16 @@
|
||||
#include "mlir/Dialect/Bufferization/IR/Bufferization.h"
|
||||
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
||||
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||
#include "mlir/IR/IRMapping.h"
|
||||
#include "mlir/IR/Matchers.h"
|
||||
#include <limits>
|
||||
|
||||
#include "Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||
#include "Conversion/SpatialToPim/SpatialToPimPass.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/SpatialToPim/Common.hpp"
|
||||
@@ -26,100 +29,6 @@ static bool isUsedOnlyAsExplicitHostOperand(Value value) {
|
||||
});
|
||||
}
|
||||
|
||||
static bool isMaterializableExternalTensorOp(Operation* op) {
|
||||
return isa<spatial::SpatChannelReceiveOp,
|
||||
spatial::SpatExtractRowsOp,
|
||||
tensor::ExtractSliceOp,
|
||||
tensor::ExpandShapeOp,
|
||||
tensor::CollapseShapeOp>(op);
|
||||
}
|
||||
|
||||
//TODO REMOVE THIS UGLY FIX
|
||||
//TODO: Remove this helper once compute_batch external tensor captures are
|
||||
// fixed at the producer side.
|
||||
//
|
||||
// This function is a temporary SpatialToPim repair path. It clones selected
|
||||
// external tensor producers, such as channel_receive and tensor view/slice ops,
|
||||
// into the new pim.core_batch body when the old spat.compute_batch body refers
|
||||
// to tensor values defined outside the batch.
|
||||
//
|
||||
// The real invariant should be stronger:
|
||||
//
|
||||
// A spat.compute_batch body must not capture external tensor values.
|
||||
// Every tensor used inside the body must be either:
|
||||
// - a compute_batch block argument,
|
||||
// - defined inside the compute_batch body,
|
||||
// - or a legal constant-like value.
|
||||
//
|
||||
// If this invariant is violated, the responsible producer, most likely merge
|
||||
// schedule materialization, should emit verifier-clean Spatial IR instead of
|
||||
// relying on SpatialToPim to clone external producer chains later.
|
||||
//
|
||||
// After that producer-side fix:
|
||||
// 1. remove isMaterializableExternalTensorOp,
|
||||
// 2. remove materializeExternalTensorValue,
|
||||
// 3. make lowerComputeBatchOp emit a hard diagnostic for any unmapped external
|
||||
// tensor operand,
|
||||
// 4. keep/strengthen the Spatial verifier so the invalid capture is rejected
|
||||
// before SpatialToPim.
|
||||
//
|
||||
// Be careful not to replace every external tensor capture with a normal
|
||||
// compute_batch input blindly: host-backed tensors and explicit inter-core
|
||||
// communication have different semantics. In particular, channel_receive-like
|
||||
// values should be materialized through the communication model, not silently
|
||||
// treated as host inputs.
|
||||
static FailureOr<Value> materializeExternalTensorValue(IRRewriter& rewriter,
|
||||
Location loc,
|
||||
Block& oldBlock,
|
||||
Value value,
|
||||
IRMapping& mapper) {
|
||||
if (mapper.contains(value))
|
||||
return mapper.lookup(value);
|
||||
|
||||
if (!isa<TensorType>(value.getType()))
|
||||
return value;
|
||||
|
||||
Operation* definingOp = value.getDefiningOp();
|
||||
if (!definingOp || definingOp->hasTrait<OpTrait::ConstantLike>())
|
||||
return failure();
|
||||
|
||||
if (definingOp->getBlock() == &oldBlock)
|
||||
return failure();
|
||||
|
||||
if (!isMaterializableExternalTensorOp(definingOp))
|
||||
return failure();
|
||||
|
||||
for (Value operand : definingOp->getOperands()) {
|
||||
FailureOr<Value> materializedOperand = materializeExternalTensorValue(rewriter, loc, oldBlock, operand, mapper);
|
||||
if (succeeded(materializedOperand))
|
||||
mapper.map(operand, *materializedOperand);
|
||||
}
|
||||
|
||||
Operation* cloned = rewriter.clone(*definingOp, mapper);
|
||||
for (auto [originalResult, clonedResult] : llvm::zip(definingOp->getResults(), cloned->getResults()))
|
||||
mapper.map(originalResult, clonedResult);
|
||||
|
||||
return mapper.lookup(value);
|
||||
}
|
||||
|
||||
static FailureOr<SmallVector<int32_t>> getPimCoreIdsForBatchOp(spatial::SpatScheduledComputeBatch computeBatchOp,
|
||||
size_t& fallbackCoreId) {
|
||||
if (auto coreIdsAttr = computeBatchOp->getAttrOfType<DenseI32ArrayAttr>(onnx_mlir::kCoreIdsAttrName))
|
||||
return SmallVector<int32_t>(coreIdsAttr.asArrayRef().begin(), coreIdsAttr.asArrayRef().end());
|
||||
|
||||
SmallVector<int32_t> coreIds;
|
||||
coreIds.reserve(static_cast<size_t>(computeBatchOp.getLaneCount()));
|
||||
for (uint32_t lane = 0; lane < computeBatchOp.getLaneCount(); ++lane) {
|
||||
auto checkedCoreId =
|
||||
pim::checkedI32(static_cast<uint64_t>(fallbackCoreId), computeBatchOp, "fallback spatial compute_batch core id");
|
||||
if (failed(checkedCoreId))
|
||||
return failure();
|
||||
coreIds.push_back(*checkedCoreId);
|
||||
++fallbackCoreId;
|
||||
}
|
||||
return coreIds;
|
||||
}
|
||||
|
||||
static FailureOr<unsigned> getDirectReturnOperandIndex(OpResult result) {
|
||||
if (!result.hasOneUse())
|
||||
return failure();
|
||||
@@ -130,6 +39,188 @@ static FailureOr<unsigned> getDirectReturnOperandIndex(OpResult result) {
|
||||
return result.getUses().begin()->getOperandNumber();
|
||||
}
|
||||
|
||||
static FailureOr<SmallVector<FragmentAssemblyCopy, 8>>
|
||||
collectFragmentAssemblyCopiesFromBlueprint(spatial::SpatBlueprintOp blueprint,
|
||||
IRMapping& mapper,
|
||||
int64_t lane,
|
||||
unsigned hostTargetIndex,
|
||||
Value fixedSource = {}) {
|
||||
SmallVector<FragmentAssemblyCopy, 8> copies;
|
||||
auto resultType = dyn_cast<RankedTensorType>(blueprint.getOutput().getType());
|
||||
if (!resultType || !resultType.hasStaticShape())
|
||||
return blueprint.emitOpError("fragment assembly lowering requires static ranked tensor results");
|
||||
|
||||
std::optional<ArrayRef<int64_t>> operandIndicesAttr = blueprint.getFragmentOperandIndices();
|
||||
std::optional<ArrayRef<int64_t>> fragmentStridesAttr = blueprint.getFragmentStrides();
|
||||
if (!operandIndicesAttr || !fragmentStridesAttr)
|
||||
return blueprint.emitOpError(
|
||||
"fragment assembly lowering requires explicit operand indices and unit strides");
|
||||
|
||||
ArrayRef<int64_t> operandIndices = *operandIndicesAttr;
|
||||
std::optional<ArrayRef<int64_t>> sourceOffsetsAttr = blueprint.getFragmentSourceOffsets();
|
||||
if (!sourceOffsetsAttr)
|
||||
return blueprint.emitOpError("fragment assembly lowering requires explicit source offsets");
|
||||
ArrayRef<int64_t> sourceOffsets = *sourceOffsetsAttr;
|
||||
ArrayRef<int64_t> flatOffsets = blueprint.getFragmentOffsets();
|
||||
ArrayRef<int64_t> flatSizes = blueprint.getFragmentSizes();
|
||||
ArrayRef<int64_t> flatStrides = *fragmentStridesAttr;
|
||||
int64_t rank = resultType.getRank();
|
||||
|
||||
SmallVector<Value> fragmentOperands {blueprint.getInput()};
|
||||
llvm::append_range(fragmentOperands, blueprint.getFragments());
|
||||
if (failed(validateFragmentAssemblyMetadata(blueprint,
|
||||
rank,
|
||||
fragmentOperands.size(),
|
||||
operandIndices,
|
||||
sourceOffsets,
|
||||
flatOffsets,
|
||||
flatSizes,
|
||||
flatStrides)))
|
||||
return failure();
|
||||
|
||||
SmallVector<int64_t> hostStrides = computeRowMajorStrides(resultType.getShape());
|
||||
for (int64_t fragmentIndex = 0; fragmentIndex < static_cast<int64_t>(operandIndices.size()); ++fragmentIndex) {
|
||||
Value source = fixedSource ? fixedSource : mapper.lookupOrDefault(fragmentOperands[operandIndices[fragmentIndex]]);
|
||||
auto sourceType = dyn_cast<RankedTensorType>(source.getType());
|
||||
if (!sourceType || !sourceType.hasStaticShape())
|
||||
return blueprint.emitOpError("fragment assembly lowering requires static ranked tensor operands");
|
||||
|
||||
size_t elementSize = getElementTypeSizeInBytes(sourceType.getElementType());
|
||||
SmallVector<int64_t, 4> fragmentOffsets;
|
||||
SmallVector<int64_t, 4> fragmentSizes;
|
||||
for (int64_t dim = 0; dim < rank; ++dim) {
|
||||
int64_t flatIndex = fragmentIndex * rank + dim;
|
||||
if (flatStrides[flatIndex] != 1)
|
||||
return blueprint.emitOpError("fragment assembly lowering only supports unit strides");
|
||||
fragmentOffsets.push_back(flatOffsets[flatIndex]);
|
||||
fragmentSizes.push_back(flatSizes[flatIndex]);
|
||||
}
|
||||
|
||||
if (failed(forEachContiguousDestinationChunk(
|
||||
resultType.getShape(),
|
||||
fragmentOffsets,
|
||||
fragmentSizes,
|
||||
[&](ArrayRef<int64_t> chunkOffsets, int64_t relativeSourceOffset, int64_t chunkElements) -> LogicalResult {
|
||||
int64_t hostElementOffset = 0;
|
||||
for (auto [dim, offset] : llvm::enumerate(chunkOffsets))
|
||||
hostElementOffset += offset * hostStrides[dim];
|
||||
|
||||
FragmentAssemblyCopy copy;
|
||||
copy.source = source;
|
||||
copy.sourceType = sourceType;
|
||||
copy.hostTargetIndex = hostTargetIndex;
|
||||
copy.lane = lane;
|
||||
copy.sourceByteOffset = (sourceOffsets[fragmentIndex] + relativeSourceOffset) * static_cast<int64_t>(elementSize);
|
||||
copy.hostByteOffset = hostElementOffset * static_cast<int64_t>(elementSize);
|
||||
copy.byteSize = chunkElements * static_cast<int64_t>(elementSize);
|
||||
copies.push_back(copy);
|
||||
return success();
|
||||
})))
|
||||
return failure();
|
||||
}
|
||||
|
||||
return copies;
|
||||
}
|
||||
|
||||
static FailureOr<SmallVector<FragmentAssemblyCopy, 8>>
|
||||
collectTopLevelFragmentAssemblyCopies(OpResult result, RankedTensorType packedResultType, uint32_t laneCount) {
|
||||
SmallVector<FragmentAssemblyCopy, 8> copies;
|
||||
if (!packedResultType.hasStaticShape() || laneCount == 0)
|
||||
return failure();
|
||||
|
||||
int64_t packedElementCount = packedResultType.getNumElements();
|
||||
if (packedElementCount % static_cast<int64_t>(laneCount) != 0)
|
||||
return failure();
|
||||
int64_t payloadElementCount = packedElementCount / static_cast<int64_t>(laneCount);
|
||||
size_t elementSize = getElementTypeSizeInBytes(packedResultType.getElementType());
|
||||
|
||||
for (OpOperand& use : result.getUses()) {
|
||||
auto blueprint = dyn_cast<spatial::SpatBlueprintOp>(use.getOwner());
|
||||
if (!blueprint || blueprint->getParentOp() != blueprint->getParentOfType<func::FuncOp>())
|
||||
return failure();
|
||||
std::optional<StringRef> mode = blueprint.getMode();
|
||||
std::optional<ArrayRef<int64_t>> operandIndicesAttr = blueprint.getFragmentOperandIndices();
|
||||
std::optional<ArrayRef<int64_t>> sourceOffsetsAttr = blueprint.getFragmentSourceOffsets();
|
||||
std::optional<ArrayRef<int64_t>> sourceSlotsAttr = blueprint.getFragmentSourceSlots();
|
||||
if (!mode || *mode != "fragment_assembly" || !operandIndicesAttr || !sourceOffsetsAttr || !sourceSlotsAttr)
|
||||
return failure();
|
||||
if (!blueprint.getOutput().hasOneUse() || !isa<func::ReturnOp>(*blueprint.getOutput().getUsers().begin()))
|
||||
return failure();
|
||||
|
||||
auto hostResultType = dyn_cast<RankedTensorType>(blueprint.getOutput().getType());
|
||||
std::optional<ArrayRef<int64_t>> stridesAttr = blueprint.getFragmentStrides();
|
||||
if (!hostResultType || !hostResultType.hasStaticShape() || !stridesAttr)
|
||||
return failure();
|
||||
|
||||
ArrayRef<int64_t> operandIndices = *operandIndicesAttr;
|
||||
ArrayRef<int64_t> sourceOffsets = *sourceOffsetsAttr;
|
||||
ArrayRef<int64_t> sourceSlots = *sourceSlotsAttr;
|
||||
if (sourceSlots.size() != operandIndices.size())
|
||||
return failure();
|
||||
ArrayRef<int64_t> flatOffsets = blueprint.getFragmentOffsets();
|
||||
ArrayRef<int64_t> flatSizes = blueprint.getFragmentSizes();
|
||||
ArrayRef<int64_t> flatStrides = *stridesAttr;
|
||||
int64_t rank = hostResultType.getRank();
|
||||
unsigned returnIndex = blueprint.getOutput().getUses().begin()->getOperandNumber();
|
||||
SmallVector<Value> fragmentOperands {blueprint.getInput()};
|
||||
llvm::append_range(fragmentOperands, blueprint.getFragments());
|
||||
if (failed(validateFragmentAssemblyMetadata(blueprint,
|
||||
rank,
|
||||
fragmentOperands.size(),
|
||||
operandIndices,
|
||||
sourceOffsets,
|
||||
flatOffsets,
|
||||
flatSizes,
|
||||
flatStrides)))
|
||||
return failure();
|
||||
SmallVector<int64_t> hostStrides = computeRowMajorStrides(hostResultType.getShape());
|
||||
for (int64_t fragmentIndex = 0; fragmentIndex < static_cast<int64_t>(operandIndices.size()); ++fragmentIndex) {
|
||||
if (operandIndices[fragmentIndex] != static_cast<int64_t>(use.getOperandNumber()))
|
||||
continue;
|
||||
|
||||
int64_t sourceElementOffset =
|
||||
sourceSlots[fragmentIndex] * payloadElementCount + sourceOffsets[fragmentIndex];
|
||||
int64_t lane = sourceElementOffset / payloadElementCount;
|
||||
if (lane < 0 || lane >= static_cast<int64_t>(laneCount))
|
||||
return failure();
|
||||
SmallVector<int64_t, 4> fragmentOffsets;
|
||||
SmallVector<int64_t, 4> fragmentSizes;
|
||||
for (int64_t dim = 0; dim < rank; ++dim) {
|
||||
int64_t flatIndex = fragmentIndex * rank + dim;
|
||||
if (flatStrides[flatIndex] != 1)
|
||||
return failure();
|
||||
fragmentOffsets.push_back(flatOffsets[flatIndex]);
|
||||
fragmentSizes.push_back(flatSizes[flatIndex]);
|
||||
}
|
||||
|
||||
if (failed(forEachContiguousDestinationChunk(
|
||||
hostResultType.getShape(),
|
||||
fragmentOffsets,
|
||||
fragmentSizes,
|
||||
[&](ArrayRef<int64_t> chunkOffsets, int64_t relativeSourceOffset, int64_t chunkElements) -> LogicalResult {
|
||||
int64_t hostElementOffset = 0;
|
||||
for (auto [dim, offset] : llvm::enumerate(chunkOffsets))
|
||||
hostElementOffset += offset * hostStrides[dim];
|
||||
|
||||
FragmentAssemblyCopy copy;
|
||||
copy.source = result;
|
||||
copy.sourceType = packedResultType;
|
||||
copy.hostTargetIndex = returnIndex;
|
||||
copy.lane = lane;
|
||||
copy.sourceByteOffset =
|
||||
((sourceElementOffset % payloadElementCount) + relativeSourceOffset) * static_cast<int64_t>(elementSize);
|
||||
copy.hostByteOffset = hostElementOffset * static_cast<int64_t>(elementSize);
|
||||
copy.byteSize = chunkElements * static_cast<int64_t>(elementSize);
|
||||
copies.push_back(copy);
|
||||
return success();
|
||||
})))
|
||||
return failure();
|
||||
}
|
||||
}
|
||||
|
||||
return copies;
|
||||
}
|
||||
|
||||
static Value createScaledIndexValue(IRRewriter& rewriter, Location loc, Value base, int64_t scale) {
|
||||
if (scale == 1)
|
||||
return base;
|
||||
@@ -139,25 +230,32 @@ static Value createScaledIndexValue(IRRewriter& rewriter, Location loc, Value ba
|
||||
}
|
||||
|
||||
static Value createHostTargetOffset(IRRewriter& rewriter,
|
||||
tensor::ParallelInsertSliceOp insertSlice,
|
||||
Location loc,
|
||||
ShapedType destinationType,
|
||||
ArrayRef<OpFoldResult> mixedOffsets,
|
||||
ArrayRef<int64_t> additionalOffsets,
|
||||
IRMapping& mapper) {
|
||||
int64_t elementBytes = static_cast<int64_t>(getElementTypeSizeInBytes(destinationType.getElementType()));
|
||||
SmallVector<int64_t> strides = computeRowMajorStrides(destinationType.getShape());
|
||||
|
||||
Value totalOffset;
|
||||
Location loc = insertSlice.getLoc();
|
||||
for (auto [dim, offset] : llvm::enumerate(insertSlice.getMixedOffsets())) {
|
||||
for (auto [dim, offset] : llvm::enumerate(mixedOffsets)) {
|
||||
int64_t scale = strides[dim] * elementBytes;
|
||||
Value scaledOffset;
|
||||
if (auto attr = dyn_cast<Attribute>(offset)) {
|
||||
auto intAttr = dyn_cast<IntegerAttr>(attr);
|
||||
assert(intAttr && "expected integer offset attribute");
|
||||
scaledOffset =
|
||||
getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), intAttr.getInt() * scale);
|
||||
}
|
||||
else {
|
||||
scaledOffset = getOrCreateIndexConstant(rewriter,
|
||||
rewriter.getInsertionBlock()->getParentOp(),
|
||||
(intAttr.getInt() + additionalOffsets[dim]) * scale);
|
||||
} else {
|
||||
scaledOffset = createScaledIndexValue(rewriter, loc, mapper.lookupOrDefault(cast<Value>(offset)), scale);
|
||||
if (additionalOffsets[dim] != 0) {
|
||||
Value staticOffset = getOrCreateIndexConstant(rewriter,
|
||||
rewriter.getInsertionBlock()->getParentOp(),
|
||||
additionalOffsets[dim] * scale);
|
||||
scaledOffset = arith::AddIOp::create(rewriter, loc, scaledOffset, staticOffset).getResult();
|
||||
}
|
||||
}
|
||||
|
||||
totalOffset =
|
||||
@@ -169,6 +267,19 @@ static Value createHostTargetOffset(IRRewriter& rewriter,
|
||||
return totalOffset;
|
||||
}
|
||||
|
||||
static Value createHostTargetOffset(IRRewriter& rewriter,
|
||||
tensor::ParallelInsertSliceOp insertSlice,
|
||||
ShapedType destinationType,
|
||||
IRMapping& mapper) {
|
||||
SmallVector<int64_t> zeroOffsets(destinationType.getRank(), 0);
|
||||
return createHostTargetOffset(rewriter,
|
||||
insertSlice.getLoc(),
|
||||
destinationType,
|
||||
insertSlice.getMixedOffsets(),
|
||||
zeroOffsets,
|
||||
mapper);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatScheduledComputeBatch computeBatchOp,
|
||||
@@ -186,7 +297,7 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatSchedul
|
||||
"resultful compute_batch lowering currently requires a spat.in_parallel terminator");
|
||||
}
|
||||
|
||||
auto coreIds = getPimCoreIdsForBatchOp(computeBatchOp, coreId);
|
||||
auto coreIds = getRequiredScheduledBatchCoreIds(computeBatchOp, "spatial compute_batch core id");
|
||||
if (failed(coreIds))
|
||||
return failure();
|
||||
SmallVector<Value> batchWeights(computeBatchOp.getWeights().begin(), computeBatchOp.getWeights().end());
|
||||
@@ -206,14 +317,32 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatSchedul
|
||||
coreBatchOp->setAttr(onnx_mlir::kCoreIdsAttrName, rewriter.getDenseI32ArrayAttr(*coreIds));
|
||||
|
||||
SmallVector<unsigned> returnOperandIndices;
|
||||
SmallVector<SmallVector<FragmentAssemblyCopyRun, 1>, 4> fragmentAssemblyRunsByResult;
|
||||
if (computeBatchOp.getNumResults() != 0) {
|
||||
returnOperandIndices.resize(computeBatchOp.getNumResults());
|
||||
returnOperandIndices.resize(computeBatchOp.getNumResults(), std::numeric_limits<unsigned>::max());
|
||||
fragmentAssemblyRunsByResult.resize(computeBatchOp.getNumResults());
|
||||
for (auto [resultIndex, result] : llvm::enumerate(computeBatchOp.getResults())) {
|
||||
if (result.use_empty())
|
||||
continue;
|
||||
FailureOr<unsigned> returnOperandIndex = getDirectReturnOperandIndex(cast<OpResult>(result));
|
||||
if (failed(returnOperandIndex))
|
||||
if (succeeded(returnOperandIndex)) {
|
||||
returnOperandIndices[resultIndex] = *returnOperandIndex;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto resultType = dyn_cast<RankedTensorType>(result.getType());
|
||||
if (!resultType || !resultType.hasStaticShape())
|
||||
return computeBatchOp.emitOpError(
|
||||
"resultful compute_batch lowering currently requires each result to be used directly by func.return");
|
||||
returnOperandIndices[resultIndex] = *returnOperandIndex;
|
||||
"resultful compute_batch publication lowering requires static ranked tensor results");
|
||||
FailureOr<SmallVector<FragmentAssemblyCopy, 8>> fragmentAssemblyCopies =
|
||||
collectTopLevelFragmentAssemblyCopies(cast<OpResult>(result), resultType, computeBatchOp.getLaneCount());
|
||||
if (failed(fragmentAssemblyCopies))
|
||||
return computeBatchOp.emitOpError("failed to collect top-level fragment assembly copies for compute_batch result");
|
||||
FailureOr<SmallVector<FragmentAssemblyCopyRun, 8>> fragmentAssemblyRuns =
|
||||
groupFragmentAssemblyCopyRuns(*fragmentAssemblyCopies, computeBatchOp.getLaneCount());
|
||||
if (failed(fragmentAssemblyRuns))
|
||||
return computeBatchOp.emitOpError("failed to group top-level fragment assembly copies into regular runs");
|
||||
fragmentAssemblyRunsByResult[resultIndex].assign(fragmentAssemblyRuns->begin(), fragmentAssemblyRuns->end());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,6 +400,23 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatSchedul
|
||||
if (isa<spatial::SpatYieldOp>(op))
|
||||
continue;
|
||||
|
||||
// Cloning a region-bearing operation may leave the rewriter inside that
|
||||
// region. Every old-block operation is lowered at the core-batch body
|
||||
// boundary.
|
||||
rewriter.setInsertionPointToEnd(newBlock);
|
||||
|
||||
if (auto blueprint = dyn_cast<spatial::SpatBlueprintOp>(op)) {
|
||||
std::optional<StringRef> modeAttr = blueprint.getMode();
|
||||
if (modeAttr && *modeAttr == "fragment_assembly") {
|
||||
for (Operation* user : blueprint.getOutput().getUsers()) {
|
||||
if (!isa<tensor::ParallelInsertSliceOp>(user))
|
||||
return blueprint.emitOpError(
|
||||
"fragment assembly blueprint lowering expects only tensor.parallel_insert_slice users");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto parallelOp = dyn_cast<spatial::SpatInParallelOp>(op)) {
|
||||
auto firstOutputArg = computeBatchOp.getOutputArgument(0);
|
||||
if (!firstOutputArg)
|
||||
@@ -287,10 +433,75 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatSchedul
|
||||
unsigned resultIndex = outputArg.getArgNumber() - firstOutputArg->getArgNumber();
|
||||
if (resultIndex >= returnOperandIndices.size())
|
||||
return insertSlice.emitOpError("result index out of range while lowering host batch output");
|
||||
bool hasDirectReturn = returnOperandIndices[resultIndex] != std::numeric_limits<unsigned>::max();
|
||||
bool hasFragmentAssembly = resultIndex < fragmentAssemblyRunsByResult.size()
|
||||
&& !fragmentAssemblyRunsByResult[resultIndex].empty();
|
||||
if (!hasDirectReturn && !hasFragmentAssembly)
|
||||
continue;
|
||||
|
||||
Value mappedSource = mapper.lookup(insertSlice.getSource());
|
||||
|
||||
if (hasFragmentAssembly) {
|
||||
BlockArgument laneArg = coreBatchOp.getLaneArgument();
|
||||
auto mappedSourceType = dyn_cast<ShapedType>(mappedSource.getType());
|
||||
if (!mappedSourceType || !mappedSourceType.hasStaticShape())
|
||||
return insertSlice.emitOpError("fragment assembly batch lowering requires a static ranked lane-local source");
|
||||
DenseMap<unsigned, Value> updatedOutputs;
|
||||
for (const FragmentAssemblyCopyRun& run : fragmentAssemblyRunsByResult[resultIndex]) {
|
||||
Value outputTensor = updatedOutputs.lookup(run.hostTargetIndex);
|
||||
if (!outputTensor)
|
||||
outputTensor = outputTensors[run.hostTargetIndex](rewriter, insertSlice.getLoc());
|
||||
FragmentAssemblyCopyRun mappedRun = run;
|
||||
mappedRun.source = mappedSource;
|
||||
FailureOr<Value> updated =
|
||||
emitFragmentAssemblyCopyRuns(rewriter,
|
||||
insertSlice.getLoc(),
|
||||
ArrayRef<FragmentAssemblyCopyRun> {mappedRun},
|
||||
outputTensor,
|
||||
coreBatchOp.getOperation(),
|
||||
laneArg);
|
||||
if (failed(updated))
|
||||
return failure();
|
||||
updatedOutputs[run.hostTargetIndex] = *updated;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Value hostTarget = getOrCreateHostOutputTensor(resultIndex, insertSlice.getLoc());
|
||||
auto hostTargetType = cast<ShapedType>(hostTarget.getType());
|
||||
if (auto blueprint =
|
||||
insertSlice.getSource().getDefiningOp<spatial::SpatBlueprintOp>()) {
|
||||
std::optional<StringRef> modeAttr = blueprint.getMode();
|
||||
if (modeAttr && *modeAttr == "fragment_assembly") {
|
||||
FailureOr<SmallVector<FragmentAssemblyCopy, 8>> fragmentAssemblyCopies =
|
||||
collectFragmentAssemblyCopiesFromBlueprint(blueprint, mapper, /*lane=*/0, /*hostTargetIndex=*/0);
|
||||
if (failed(fragmentAssemblyCopies))
|
||||
return failure();
|
||||
FailureOr<SmallVector<FragmentAssemblyCopyRun, 8>> fragmentAssemblyRuns =
|
||||
groupFragmentAssemblyCopyRuns(*fragmentAssemblyCopies, /*laneCount=*/1);
|
||||
if (failed(fragmentAssemblyRuns))
|
||||
return failure();
|
||||
SmallVector<int64_t> zeroOffsets(hostTargetType.getRank(), 0);
|
||||
Value baseHostOffset = createHostTargetOffset(rewriter,
|
||||
blueprint.getLoc(),
|
||||
hostTargetType,
|
||||
insertSlice.getMixedOffsets(),
|
||||
zeroOffsets,
|
||||
mapper);
|
||||
FailureOr<Value> updatedHostTarget = emitFragmentAssemblyCopyRuns(rewriter,
|
||||
blueprint.getLoc(),
|
||||
*fragmentAssemblyRuns,
|
||||
hostTarget,
|
||||
coreBatchOp.getOperation(),
|
||||
std::nullopt,
|
||||
baseHostOffset);
|
||||
if (failed(updatedHostTarget))
|
||||
return failure();
|
||||
hostOutputTensors[resultIndex] = *updatedHostTarget;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Value hostTargetOffset = createHostTargetOffset(rewriter, insertSlice, hostTargetType, mapper);
|
||||
Value zeroOffset = getOrCreateIndexConstant(rewriter, coreBatchOp.getOperation(), 0);
|
||||
auto sizeAttr = getTensorSizeInBytesAttr(rewriter, coreBatchOp.getOperation(), mappedSource);
|
||||
@@ -343,9 +554,6 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatSchedul
|
||||
if (definingOp && definingOp->hasTrait<OpTrait::ConstantLike>())
|
||||
continue;
|
||||
|
||||
if (succeeded(materializeExternalTensorValue(rewriter, loc, oldBlock, operand, mapper)))
|
||||
continue;
|
||||
|
||||
InFlightDiagnostic diagnostic =
|
||||
computeBatchOp.emitOpError("expected external tensor communication to be materialized in Spatial before batch lowering");
|
||||
diagnostic << " while cloning nested op '" << op.getName() << "' tensor operand #" << operandIndex;
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
#include "mlir/IR/ValueRange.h"
|
||||
|
||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
|
||||
#include "Common.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/AffineUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
||||
|
||||
using namespace llvm;
|
||||
using namespace mlir;
|
||||
@@ -72,4 +80,488 @@ mlir::Value getBestOutputTensorFromOperandsOrAllocate(RewriterBase& rewriter, Op
|
||||
rewriter, operation->getLoc(), resultShapedType.getShape(), resultShapedType.getElementType());
|
||||
}
|
||||
|
||||
LogicalResult validateFragmentAssemblyMetadata(spatial::SpatBlueprintOp blueprint,
|
||||
int64_t resultRank,
|
||||
size_t operandCount,
|
||||
ArrayRef<int64_t> operandIndices,
|
||||
ArrayRef<int64_t> sourceOffsets,
|
||||
ArrayRef<int64_t> flatOffsets,
|
||||
ArrayRef<int64_t> flatSizes,
|
||||
ArrayRef<int64_t> flatStrides) {
|
||||
if (operandIndices.size() != sourceOffsets.size())
|
||||
return blueprint.emitOpError("fragment assembly operand index and source offset counts must match");
|
||||
if (flatOffsets.size() != flatSizes.size())
|
||||
return blueprint.emitOpError("fragment assembly offset and size arrays must have matching lengths");
|
||||
if (flatStrides.size() != flatOffsets.size())
|
||||
return blueprint.emitOpError("fragment assembly stride and offset arrays must have matching lengths");
|
||||
if (flatOffsets.size() != operandIndices.size() * static_cast<size_t>(resultRank))
|
||||
return blueprint.emitOpError("fragment assembly metadata must provide one rank-sized offset/size/stride tuple per fragment");
|
||||
|
||||
for (auto [fragmentIndex, operandIndex] : llvm::enumerate(operandIndices)) {
|
||||
if (operandIndex < 0 || operandIndex >= static_cast<int64_t>(operandCount))
|
||||
return blueprint.emitOpError("fragment assembly operand index is out of range");
|
||||
if (sourceOffsets[fragmentIndex] < 0)
|
||||
return blueprint.emitOpError("fragment assembly source offsets must be nonnegative");
|
||||
}
|
||||
|
||||
return success();
|
||||
}
|
||||
|
||||
static SmallVector<int64_t, 4> expandFlatElementIndex(int64_t flatIndex, ArrayRef<int64_t> shape) {
|
||||
SmallVector<int64_t, 4> indices(shape.size(), 0);
|
||||
for (int64_t dim = static_cast<int64_t>(shape.size()) - 1; dim >= 0; --dim) {
|
||||
indices[dim] = flatIndex % shape[dim];
|
||||
flatIndex /= shape[dim];
|
||||
}
|
||||
return indices;
|
||||
}
|
||||
|
||||
FailureOr<SmallVector<int64_t, 4>>
|
||||
getStaticSliceOffsetsForElementOffset(Operation* anchor,
|
||||
ShapedType sourceType,
|
||||
ArrayRef<int64_t> fragmentShape,
|
||||
int64_t sourceElementOffset,
|
||||
StringRef fieldName) {
|
||||
if (!sourceType.hasStaticShape())
|
||||
return (anchor->emitOpError() << fieldName << " requires a static source shape"), failure();
|
||||
if (sourceElementOffset < 0)
|
||||
return (anchor->emitOpError() << fieldName << " requires a nonnegative source element offset"), failure();
|
||||
if (sourceType.getRank() != static_cast<int64_t>(fragmentShape.size()))
|
||||
return (anchor->emitOpError() << fieldName << " requires fragment rank to match source rank"), failure();
|
||||
|
||||
int64_t sourceElementCount = sourceType.getNumElements();
|
||||
int64_t fragmentElementCount = 1;
|
||||
for (int64_t dim = 0; dim < sourceType.getRank(); ++dim) {
|
||||
if (fragmentShape[dim] < 0)
|
||||
return (anchor->emitOpError() << fieldName << " requires nonnegative fragment sizes"), failure();
|
||||
fragmentElementCount *= fragmentShape[dim];
|
||||
}
|
||||
if (sourceElementOffset + fragmentElementCount > sourceElementCount)
|
||||
return (anchor->emitOpError() << fieldName << " exceeds the source tensor bounds"), failure();
|
||||
|
||||
SmallVector<int64_t, 4> sliceOffsets = expandFlatElementIndex(sourceElementOffset, sourceType.getShape());
|
||||
for (int64_t dim = 0; dim < sourceType.getRank(); ++dim) {
|
||||
if (sliceOffsets[dim] + fragmentShape[dim] > sourceType.getDimSize(dim))
|
||||
return (anchor->emitOpError() << fieldName << " does not describe a valid unit-stride slice"), failure();
|
||||
}
|
||||
return sliceOffsets;
|
||||
}
|
||||
|
||||
LogicalResult
|
||||
forEachContiguousDestinationChunk(ArrayRef<int64_t> destShape,
|
||||
ArrayRef<int64_t> baseOffsets,
|
||||
ArrayRef<int64_t> sizes,
|
||||
llvm::function_ref<LogicalResult(ArrayRef<int64_t>, int64_t, int64_t)> callback) {
|
||||
int64_t rank = static_cast<int64_t>(sizes.size());
|
||||
int64_t suffixStart = rank - 1;
|
||||
while (suffixStart > 0 && sizes[suffixStart] == destShape[suffixStart])
|
||||
--suffixStart;
|
||||
if (sizes[suffixStart] == destShape[suffixStart] && suffixStart == 0)
|
||||
suffixStart = 0;
|
||||
else
|
||||
++suffixStart;
|
||||
|
||||
int64_t chunkElements = 1;
|
||||
for (int64_t dim = suffixStart; dim < rank; ++dim)
|
||||
chunkElements *= sizes[dim];
|
||||
|
||||
SmallVector<int64_t, 4> prefixExtents(sizes.begin(), sizes.begin() + suffixStart);
|
||||
SmallVector<int64_t, 4> current(prefixExtents.size(), 0);
|
||||
int64_t sourceChunkOrdinal = 0;
|
||||
|
||||
auto visit = [&](auto&& visit, int64_t dim) -> LogicalResult {
|
||||
if (dim == static_cast<int64_t>(prefixExtents.size())) {
|
||||
SmallVector<int64_t, 4> chunkOffsets(baseOffsets.begin(), baseOffsets.end());
|
||||
for (int64_t prefixDim = 0; prefixDim < static_cast<int64_t>(current.size()); ++prefixDim)
|
||||
chunkOffsets[prefixDim] += current[prefixDim];
|
||||
if (failed(callback(chunkOffsets, sourceChunkOrdinal * chunkElements, chunkElements)))
|
||||
return failure();
|
||||
++sourceChunkOrdinal;
|
||||
return success();
|
||||
}
|
||||
|
||||
for (int64_t index = 0; index < prefixExtents[dim]; ++index) {
|
||||
current[dim] = index;
|
||||
if (failed(visit(visit, dim + 1)))
|
||||
return failure();
|
||||
}
|
||||
return success();
|
||||
};
|
||||
|
||||
if (prefixExtents.empty())
|
||||
return callback(baseOffsets, 0, chunkElements);
|
||||
return visit(visit, 0);
|
||||
}
|
||||
|
||||
static mlir::Value
|
||||
createSteppedOffset(OpBuilder& builder, Location loc, mlir::Value start, mlir::Value index,
|
||||
int64_t stepBytes, Operation *constantAnchor) {
|
||||
if (stepBytes == 0)
|
||||
return start;
|
||||
return createOrFoldAffineApply(
|
||||
builder, loc, builder.getAffineDimExpr(0) + builder.getAffineDimExpr(1) * stepBytes,
|
||||
ValueRange {start, index}, constantAnchor);
|
||||
}
|
||||
|
||||
static mlir::Value createIndexedOffset(OpBuilder& builder,
|
||||
Location loc,
|
||||
mlir::Value indexArg,
|
||||
ArrayRef<int64_t> values,
|
||||
Operation *constantAnchor) {
|
||||
assert(!values.empty() && "expected lane-indexed values");
|
||||
if (llvm::all_of(values.drop_front(), [&](int64_t value) { return value == values.front(); }))
|
||||
return getOrCreateIndexConstant(builder, constantAnchor, values.front());
|
||||
|
||||
if (values.size() >= 2) {
|
||||
int64_t step = values[1] - values[0];
|
||||
bool arithmetic = llvm::all_of(llvm::seq<size_t>(2, values.size()), [&](size_t index) {
|
||||
return values[index] == values.front() + static_cast<int64_t>(index) * step;
|
||||
});
|
||||
if (arithmetic) {
|
||||
return createOrFoldAffineApply(
|
||||
builder, loc, builder.getAffineDimExpr(0) * step + values.front(),
|
||||
ValueRange {indexArg}, constantAnchor);
|
||||
}
|
||||
}
|
||||
|
||||
RankedTensorType tableType = RankedTensorType::get(
|
||||
{static_cast<int64_t>(values.size())}, builder.getI64Type());
|
||||
DenseElementsAttr tableAttr = DenseElementsAttr::get(tableType, values);
|
||||
mlir::Value table = getOrCreateConstant(builder, constantAnchor, tableAttr, tableType);
|
||||
mlir::Value selected = tensor::ExtractOp::create(builder, loc, table, ValueRange {indexArg});
|
||||
return arith::IndexCastOp::create(builder, loc, builder.getIndexType(), selected).getResult();
|
||||
}
|
||||
|
||||
struct FragmentAssemblyCopyRunFamily {
|
||||
FragmentAssemblyCopyRun prototype;
|
||||
SmallVector<int64_t, 8> sourceRunStartDeltas;
|
||||
SmallVector<int64_t, 8> hostRunStartDeltas;
|
||||
};
|
||||
|
||||
static bool computeUniformRunStartDelta(ArrayRef<int64_t> prototypeStarts,
|
||||
ArrayRef<int64_t> runStarts,
|
||||
int64_t& delta) {
|
||||
if (prototypeStarts.size() != runStarts.size() || prototypeStarts.empty())
|
||||
return false;
|
||||
|
||||
delta = runStarts.front() - prototypeStarts.front();
|
||||
return llvm::all_of(llvm::zip_equal(prototypeStarts, runStarts), [&](auto pair) {
|
||||
auto [prototypeStart, runStart] = pair;
|
||||
return runStart - prototypeStart == delta;
|
||||
});
|
||||
}
|
||||
|
||||
static bool canMergeFragmentAssemblyCopyRunIntoFamily(const FragmentAssemblyCopyRunFamily& family,
|
||||
const FragmentAssemblyCopyRun& run,
|
||||
int64_t& sourceRunStartDelta,
|
||||
int64_t& hostRunStartDelta) {
|
||||
const FragmentAssemblyCopyRun& prototype = family.prototype;
|
||||
if (prototype.source != run.source || prototype.sourceType != run.sourceType
|
||||
|| prototype.hostTargetIndex != run.hostTargetIndex || prototype.count != run.count
|
||||
|| prototype.sourceStepBytes != run.sourceStepBytes || prototype.hostStepBytes != run.hostStepBytes
|
||||
|| prototype.byteSize != run.byteSize)
|
||||
return false;
|
||||
|
||||
if (!computeUniformRunStartDelta(prototype.sourceStartBytesByLane, run.sourceStartBytesByLane, sourceRunStartDelta))
|
||||
return false;
|
||||
return computeUniformRunStartDelta(prototype.hostStartBytesByLane, run.hostStartBytesByLane, hostRunStartDelta);
|
||||
}
|
||||
|
||||
static SmallVector<FragmentAssemblyCopyRunFamily, 8>
|
||||
groupFragmentAssemblyCopyRunFamilies(ArrayRef<FragmentAssemblyCopyRun> runs) {
|
||||
auto compareRunStarts = [](ArrayRef<int64_t> lhs, ArrayRef<int64_t> rhs) {
|
||||
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
|
||||
};
|
||||
|
||||
SmallVector<FragmentAssemblyCopyRun, 8> sortedRuns(runs.begin(), runs.end());
|
||||
llvm::sort(sortedRuns, [&](const FragmentAssemblyCopyRun& lhs, const FragmentAssemblyCopyRun& rhs) {
|
||||
if (lhs.hostTargetIndex != rhs.hostTargetIndex)
|
||||
return lhs.hostTargetIndex < rhs.hostTargetIndex;
|
||||
if (lhs.source != rhs.source)
|
||||
return lhs.source.getAsOpaquePointer() < rhs.source.getAsOpaquePointer();
|
||||
if (lhs.byteSize != rhs.byteSize)
|
||||
return lhs.byteSize < rhs.byteSize;
|
||||
if (lhs.count != rhs.count)
|
||||
return lhs.count < rhs.count;
|
||||
if (lhs.sourceStepBytes != rhs.sourceStepBytes)
|
||||
return lhs.sourceStepBytes < rhs.sourceStepBytes;
|
||||
if (lhs.hostStepBytes != rhs.hostStepBytes)
|
||||
return lhs.hostStepBytes < rhs.hostStepBytes;
|
||||
if (compareRunStarts(lhs.sourceStartBytesByLane, rhs.sourceStartBytesByLane))
|
||||
return true;
|
||||
if (compareRunStarts(rhs.sourceStartBytesByLane, lhs.sourceStartBytesByLane))
|
||||
return false;
|
||||
return compareRunStarts(lhs.hostStartBytesByLane, rhs.hostStartBytesByLane);
|
||||
});
|
||||
|
||||
SmallVector<FragmentAssemblyCopyRunFamily, 8> families;
|
||||
for (const FragmentAssemblyCopyRun& run : sortedRuns) {
|
||||
int64_t sourceRunStartDelta = 0;
|
||||
int64_t hostRunStartDelta = 0;
|
||||
if (!families.empty()
|
||||
&& canMergeFragmentAssemblyCopyRunIntoFamily(
|
||||
families.back(), run, sourceRunStartDelta, hostRunStartDelta)) {
|
||||
families.back().sourceRunStartDeltas.push_back(sourceRunStartDelta);
|
||||
families.back().hostRunStartDeltas.push_back(hostRunStartDelta);
|
||||
continue;
|
||||
}
|
||||
|
||||
FragmentAssemblyCopyRunFamily family;
|
||||
family.prototype = run;
|
||||
family.sourceRunStartDeltas.push_back(0);
|
||||
family.hostRunStartDeltas.push_back(0);
|
||||
families.push_back(std::move(family));
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
FailureOr<SmallVector<FragmentAssemblyCopyRun, 8>>
|
||||
groupFragmentAssemblyCopyRuns(ArrayRef<FragmentAssemblyCopy> copies, uint32_t laneCount) {
|
||||
if (laneCount == 0)
|
||||
return failure();
|
||||
|
||||
struct LaneLocalCopyRun {
|
||||
FragmentAssemblyCopyRun run;
|
||||
int64_t lane = 0;
|
||||
};
|
||||
|
||||
SmallVector<FragmentAssemblyCopy, 8> sortedCopies(copies.begin(), copies.end());
|
||||
llvm::sort(sortedCopies, [](const FragmentAssemblyCopy& lhs, const FragmentAssemblyCopy& rhs) {
|
||||
if (lhs.hostTargetIndex != rhs.hostTargetIndex)
|
||||
return lhs.hostTargetIndex < rhs.hostTargetIndex;
|
||||
if (lhs.source != rhs.source)
|
||||
return lhs.source.getAsOpaquePointer() < rhs.source.getAsOpaquePointer();
|
||||
if (lhs.lane != rhs.lane)
|
||||
return lhs.lane < rhs.lane;
|
||||
if (lhs.byteSize != rhs.byteSize)
|
||||
return lhs.byteSize < rhs.byteSize;
|
||||
if (lhs.sourceByteOffset != rhs.sourceByteOffset)
|
||||
return lhs.sourceByteOffset < rhs.sourceByteOffset;
|
||||
return lhs.hostByteOffset < rhs.hostByteOffset;
|
||||
});
|
||||
|
||||
SmallVector<LaneLocalCopyRun, 8> laneRuns;
|
||||
for (const FragmentAssemblyCopy& copy : sortedCopies) {
|
||||
if (copy.lane < 0 || copy.lane >= static_cast<int64_t>(laneCount))
|
||||
return failure();
|
||||
|
||||
if (!laneRuns.empty()) {
|
||||
LaneLocalCopyRun& laneRun = laneRuns.back();
|
||||
FragmentAssemblyCopyRun& run = laneRun.run;
|
||||
if (run.source == copy.source && run.sourceType == copy.sourceType
|
||||
&& run.hostTargetIndex == copy.hostTargetIndex && laneRun.lane == copy.lane && run.byteSize == copy.byteSize
|
||||
&& run.sourceStartBytesByLane.size() == 1 && run.hostStartBytesByLane.size() == 1) {
|
||||
int64_t previousSourceOffset = run.sourceStartBytesByLane.front() + (run.count - 1) * run.sourceStepBytes;
|
||||
int64_t previousHostOffset = run.hostStartBytesByLane.front() + (run.count - 1) * run.hostStepBytes;
|
||||
int64_t sourceDelta = copy.sourceByteOffset - previousSourceOffset;
|
||||
int64_t hostDelta = copy.hostByteOffset - previousHostOffset;
|
||||
if (run.count == 1) {
|
||||
run.sourceStepBytes = sourceDelta;
|
||||
run.hostStepBytes = hostDelta;
|
||||
++run.count;
|
||||
continue;
|
||||
}
|
||||
if (run.sourceStepBytes == sourceDelta && run.hostStepBytes == hostDelta) {
|
||||
++run.count;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaneLocalCopyRun laneRun;
|
||||
laneRun.run.source = copy.source;
|
||||
laneRun.run.sourceType = copy.sourceType;
|
||||
laneRun.run.hostTargetIndex = copy.hostTargetIndex;
|
||||
laneRun.run.count = 1;
|
||||
laneRun.run.byteSize = copy.byteSize;
|
||||
laneRun.run.sourceStartBytesByLane.push_back(copy.sourceByteOffset);
|
||||
laneRun.run.hostStartBytesByLane.push_back(copy.hostByteOffset);
|
||||
laneRun.lane = copy.lane;
|
||||
laneRuns.push_back(std::move(laneRun));
|
||||
}
|
||||
|
||||
SmallVector<FragmentAssemblyCopyRun, 8> mergedRuns;
|
||||
for (const LaneLocalCopyRun& laneRun : laneRuns) {
|
||||
size_t laneIndex = static_cast<size_t>(laneRun.lane);
|
||||
auto mergedIt = llvm::find_if(mergedRuns, [&](const FragmentAssemblyCopyRun& run) {
|
||||
return run.source == laneRun.run.source && run.sourceType == laneRun.run.sourceType
|
||||
&& run.hostTargetIndex == laneRun.run.hostTargetIndex && run.count == laneRun.run.count
|
||||
&& run.byteSize == laneRun.run.byteSize && run.sourceStepBytes == laneRun.run.sourceStepBytes
|
||||
&& run.hostStepBytes == laneRun.run.hostStepBytes && laneIndex < run.sourceStartBytesByLane.size()
|
||||
&& run.sourceStartBytesByLane[laneIndex] == std::numeric_limits<int64_t>::min();
|
||||
});
|
||||
|
||||
if (mergedIt == mergedRuns.end()) {
|
||||
FragmentAssemblyCopyRun merged = laneRun.run;
|
||||
merged.sourceStartBytesByLane.assign(laneCount, std::numeric_limits<int64_t>::min());
|
||||
merged.hostStartBytesByLane.assign(laneCount, std::numeric_limits<int64_t>::min());
|
||||
merged.sourceStartBytesByLane[laneIndex] = laneRun.run.sourceStartBytesByLane.front();
|
||||
merged.hostStartBytesByLane[laneIndex] = laneRun.run.hostStartBytesByLane.front();
|
||||
mergedRuns.push_back(std::move(merged));
|
||||
continue;
|
||||
}
|
||||
|
||||
mergedIt->sourceStartBytesByLane[laneIndex] = laneRun.run.sourceStartBytesByLane.front();
|
||||
mergedIt->hostStartBytesByLane[laneIndex] = laneRun.run.hostStartBytesByLane.front();
|
||||
}
|
||||
|
||||
for (const FragmentAssemblyCopyRun& run : mergedRuns) {
|
||||
if (llvm::any_of(run.sourceStartBytesByLane,
|
||||
[](int64_t value) { return value == std::numeric_limits<int64_t>::min(); }))
|
||||
return failure();
|
||||
if (llvm::any_of(run.hostStartBytesByLane,
|
||||
[](int64_t value) { return value == std::numeric_limits<int64_t>::min(); }))
|
||||
return failure();
|
||||
}
|
||||
|
||||
return mergedRuns;
|
||||
}
|
||||
|
||||
static FailureOr<mlir::Value> emitFragmentAssemblyCopyRun(OpBuilder& builder,
|
||||
Location loc,
|
||||
const FragmentAssemblyCopyRun& run,
|
||||
mlir::Value hostTarget,
|
||||
Operation* anchor,
|
||||
std::optional<mlir::Value> laneArg,
|
||||
mlir::Value baseHostOffset,
|
||||
mlir::Value sourceRunStartDelta = {},
|
||||
mlir::Value hostRunStartDelta = {}) {
|
||||
auto sizeAttr = pim::getCheckedI32Attr(builder, anchor, run.byteSize, "fragment assembly host copy byte size");
|
||||
if (failed(sizeAttr))
|
||||
return failure();
|
||||
|
||||
mlir::Value hostStart;
|
||||
mlir::Value sourceStart;
|
||||
if (laneArg) {
|
||||
hostStart = createIndexedOffset(builder, loc, *laneArg, run.hostStartBytesByLane, anchor);
|
||||
sourceStart = createIndexedOffset(builder, loc, *laneArg, run.sourceStartBytesByLane, anchor);
|
||||
} else {
|
||||
hostStart = getOrCreateIndexConstant(builder, anchor, run.hostStartBytesByLane.front());
|
||||
sourceStart = getOrCreateIndexConstant(builder, anchor, run.sourceStartBytesByLane.front());
|
||||
}
|
||||
|
||||
if (hostRunStartDelta)
|
||||
hostStart = arith::AddIOp::create(builder, loc, hostStart, hostRunStartDelta).getResult();
|
||||
if (sourceRunStartDelta)
|
||||
sourceStart = arith::AddIOp::create(builder, loc, sourceStart, sourceRunStartDelta).getResult();
|
||||
if (baseHostOffset)
|
||||
hostStart = arith::AddIOp::create(builder, loc, baseHostOffset, hostStart).getResult();
|
||||
|
||||
if (run.count == 1) {
|
||||
return pim::PimMemCopyDevToHostOp::create(builder,
|
||||
loc,
|
||||
hostTarget.getType(),
|
||||
hostStart,
|
||||
sourceStart,
|
||||
hostTarget,
|
||||
run.source,
|
||||
*sizeAttr)
|
||||
.getOutput();
|
||||
}
|
||||
|
||||
mlir::Value lowerBound = getOrCreateIndexConstant(builder, anchor, 0);
|
||||
mlir::Value upperBound = getOrCreateIndexConstant(builder, anchor, run.count);
|
||||
mlir::Value step = getOrCreateIndexConstant(builder, anchor, 1);
|
||||
FailureOr<NormalizedLoopResult> loop = buildNormalizedScfFor(
|
||||
builder,
|
||||
loc,
|
||||
lowerBound,
|
||||
upperBound,
|
||||
step,
|
||||
ValueRange {hostTarget},
|
||||
[&](OpBuilder& loopBuilder,
|
||||
Location bodyLoc,
|
||||
mlir::Value flatIndex,
|
||||
ValueRange iterArgs,
|
||||
SmallVectorImpl<mlir::Value>& yielded) {
|
||||
mlir::Value hostOffset = createSteppedOffset(
|
||||
loopBuilder, bodyLoc, hostStart, flatIndex, run.hostStepBytes, anchor);
|
||||
mlir::Value sourceOffset =
|
||||
createSteppedOffset(loopBuilder, bodyLoc, sourceStart, flatIndex, run.sourceStepBytes, anchor);
|
||||
mlir::Value copied =
|
||||
pim::PimMemCopyDevToHostOp::create(loopBuilder,
|
||||
bodyLoc,
|
||||
iterArgs.front().getType(),
|
||||
hostOffset,
|
||||
sourceOffset,
|
||||
iterArgs.front(),
|
||||
run.source,
|
||||
*sizeAttr)
|
||||
.getOutput();
|
||||
yielded.push_back(copied);
|
||||
return success();
|
||||
});
|
||||
if (failed(loop))
|
||||
return failure();
|
||||
return loop->results.front();
|
||||
}
|
||||
|
||||
static FailureOr<mlir::Value> emitFragmentAssemblyCopyRunFamily(OpBuilder& builder,
|
||||
Location loc,
|
||||
const FragmentAssemblyCopyRunFamily& family,
|
||||
mlir::Value hostTarget,
|
||||
Operation* anchor,
|
||||
std::optional<mlir::Value> laneArg,
|
||||
mlir::Value baseHostOffset) {
|
||||
if (family.sourceRunStartDeltas.size() == 1)
|
||||
return emitFragmentAssemblyCopyRun(
|
||||
builder, loc, family.prototype, hostTarget, anchor, laneArg, baseHostOffset);
|
||||
|
||||
mlir::Value lowerBound = getOrCreateIndexConstant(builder, anchor, 0);
|
||||
mlir::Value upperBound = getOrCreateIndexConstant(builder, anchor, family.sourceRunStartDeltas.size());
|
||||
mlir::Value step = getOrCreateIndexConstant(builder, anchor, 1);
|
||||
FailureOr<NormalizedLoopResult> outerLoop = buildNormalizedScfFor(
|
||||
builder,
|
||||
loc,
|
||||
lowerBound,
|
||||
upperBound,
|
||||
step,
|
||||
ValueRange {hostTarget},
|
||||
[&](OpBuilder& loopBuilder,
|
||||
Location bodyLoc,
|
||||
mlir::Value runIndex,
|
||||
ValueRange iterArgs,
|
||||
SmallVectorImpl<mlir::Value>& yielded) {
|
||||
mlir::Value sourceRunStartDelta =
|
||||
createIndexedOffset(loopBuilder, bodyLoc, runIndex, family.sourceRunStartDeltas, anchor);
|
||||
mlir::Value hostRunStartDelta =
|
||||
createIndexedOffset(loopBuilder, bodyLoc, runIndex, family.hostRunStartDeltas, anchor);
|
||||
FailureOr<mlir::Value> copied = emitFragmentAssemblyCopyRun(loopBuilder,
|
||||
bodyLoc,
|
||||
family.prototype,
|
||||
iterArgs.front(),
|
||||
anchor,
|
||||
laneArg,
|
||||
baseHostOffset,
|
||||
sourceRunStartDelta,
|
||||
hostRunStartDelta);
|
||||
if (failed(copied))
|
||||
return failure();
|
||||
yielded.push_back(*copied);
|
||||
return success();
|
||||
});
|
||||
if (failed(outerLoop))
|
||||
return failure();
|
||||
return outerLoop->results.front();
|
||||
}
|
||||
|
||||
FailureOr<mlir::Value> emitFragmentAssemblyCopyRuns(IRRewriter& rewriter,
|
||||
Location loc,
|
||||
ArrayRef<FragmentAssemblyCopyRun> runs,
|
||||
mlir::Value hostTarget,
|
||||
Operation* anchor,
|
||||
std::optional<mlir::Value> laneArg,
|
||||
mlir::Value baseHostOffset) {
|
||||
for (const FragmentAssemblyCopyRunFamily& family : groupFragmentAssemblyCopyRunFamilies(runs)) {
|
||||
FailureOr<mlir::Value> updatedHostTarget =
|
||||
emitFragmentAssemblyCopyRunFamily(rewriter, loc, family, hostTarget, anchor, laneArg, baseHostOffset);
|
||||
if (failed(updatedHostTarget))
|
||||
return failure();
|
||||
hostTarget = *updatedHostTarget;
|
||||
}
|
||||
|
||||
return hostTarget;
|
||||
}
|
||||
|
||||
} // namespace onnx_mlir
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/STLFunctionalExtras.h"
|
||||
|
||||
#include "mlir/IR/BuiltinTypes.h"
|
||||
#include "mlir/IR/Builders.h"
|
||||
#include "mlir/IR/Value.h"
|
||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||
#include "mlir/Support/LogicalResult.h"
|
||||
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
|
||||
namespace onnx_mlir::spatial {
|
||||
class SpatBlueprintOp;
|
||||
}
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
mlir::FailureOr<mlir::IntegerAttr>
|
||||
@@ -29,6 +42,62 @@ mlir::SmallVector<mlir::Value> getOpOperandsSortedByUses(mlir::Operation* operat
|
||||
|
||||
mlir::Value getBestOutputTensorFromOperandsOrAllocate(mlir::RewriterBase& rewriter, mlir::Operation* operation);
|
||||
|
||||
mlir::LogicalResult validateFragmentAssemblyMetadata(onnx_mlir::spatial::SpatBlueprintOp blueprint,
|
||||
int64_t resultRank,
|
||||
size_t operandCount,
|
||||
llvm::ArrayRef<int64_t> operandIndices,
|
||||
llvm::ArrayRef<int64_t> sourceOffsets,
|
||||
llvm::ArrayRef<int64_t> flatOffsets,
|
||||
llvm::ArrayRef<int64_t> flatSizes,
|
||||
llvm::ArrayRef<int64_t> flatStrides);
|
||||
|
||||
mlir::FailureOr<mlir::SmallVector<int64_t, 4>>
|
||||
getStaticSliceOffsetsForElementOffset(mlir::Operation* anchor,
|
||||
mlir::ShapedType sourceType,
|
||||
llvm::ArrayRef<int64_t> fragmentShape,
|
||||
int64_t sourceElementOffset,
|
||||
llvm::StringRef fieldName);
|
||||
|
||||
mlir::LogicalResult
|
||||
forEachContiguousDestinationChunk(llvm::ArrayRef<int64_t> destShape,
|
||||
llvm::ArrayRef<int64_t> baseOffsets,
|
||||
llvm::ArrayRef<int64_t> sizes,
|
||||
llvm::function_ref<mlir::LogicalResult(llvm::ArrayRef<int64_t>, int64_t, int64_t)>
|
||||
callback);
|
||||
|
||||
struct FragmentAssemblyCopy {
|
||||
mlir::Value source;
|
||||
mlir::RankedTensorType sourceType;
|
||||
unsigned hostTargetIndex = 0;
|
||||
int64_t lane = 0;
|
||||
int64_t sourceByteOffset = 0;
|
||||
int64_t hostByteOffset = 0;
|
||||
int64_t byteSize = 0;
|
||||
};
|
||||
|
||||
struct FragmentAssemblyCopyRun {
|
||||
mlir::Value source;
|
||||
mlir::RankedTensorType sourceType;
|
||||
unsigned hostTargetIndex = 0;
|
||||
int64_t count = 0;
|
||||
int64_t sourceStepBytes = 0;
|
||||
int64_t hostStepBytes = 0;
|
||||
int64_t byteSize = 0;
|
||||
mlir::SmallVector<int64_t, 8> sourceStartBytesByLane;
|
||||
mlir::SmallVector<int64_t, 8> hostStartBytesByLane;
|
||||
};
|
||||
|
||||
mlir::FailureOr<mlir::SmallVector<FragmentAssemblyCopyRun, 8>>
|
||||
groupFragmentAssemblyCopyRuns(llvm::ArrayRef<FragmentAssemblyCopy> copies, uint32_t laneCount = 1);
|
||||
|
||||
mlir::FailureOr<mlir::Value> emitFragmentAssemblyCopyRuns(mlir::IRRewriter& rewriter,
|
||||
mlir::Location loc,
|
||||
llvm::ArrayRef<FragmentAssemblyCopyRun> runs,
|
||||
mlir::Value hostTarget,
|
||||
mlir::Operation* anchor,
|
||||
std::optional<mlir::Value> laneArg = std::nullopt,
|
||||
mlir::Value baseHostOffset = {});
|
||||
|
||||
inline mlir::tensor::EmptyOp
|
||||
createEmptyTensorFromShaped(mlir::IRRewriter& rewriter, mlir::Location loc, mlir::ShapedType shapedType) {
|
||||
return mlir::tensor::EmptyOp::create(rewriter, loc, shapedType.getShape(), shapedType.getElementType());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
||||
#include "mlir/Dialect/Linalg/IR/Linalg.h"
|
||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||
#include "mlir/Dialect/Tosa/IR/TosaOps.h"
|
||||
#include "mlir/IR/IRMapping.h"
|
||||
@@ -8,6 +9,10 @@
|
||||
|
||||
#include "Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||
#include "Conversion/SpatialToPim/SpatialToPimPass.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ShapingUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/SpatialToPim/Common.hpp"
|
||||
@@ -30,6 +35,90 @@ static bool isChannelUseChainOp(Operation* op) {
|
||||
pim::PimTransposeOp>(op);
|
||||
}
|
||||
|
||||
static FailureOr<Value> lowerFragmentAssemblyBlueprint(IRRewriter& rewriter,
|
||||
spatial::SpatBlueprintOp blueprint,
|
||||
IRMapping& mapping) {
|
||||
auto resultType = dyn_cast<ShapedType>(blueprint.getOutput().getType());
|
||||
if (!resultType || !resultType.hasStaticShape())
|
||||
return blueprint.emitOpError("fragment assembly lowering requires a static ranked tensor result");
|
||||
|
||||
std::optional<StringRef> modeAttr = blueprint.getMode();
|
||||
std::optional<ArrayRef<int64_t>> operandIndicesAttr = blueprint.getFragmentOperandIndices();
|
||||
std::optional<ArrayRef<int64_t>> sourceOffsetsAttr = blueprint.getFragmentSourceOffsets();
|
||||
std::optional<ArrayRef<int64_t>> fragmentStridesAttr = blueprint.getFragmentStrides();
|
||||
if (!modeAttr || *modeAttr != "fragment_assembly" || !operandIndicesAttr || !sourceOffsetsAttr
|
||||
|| !fragmentStridesAttr)
|
||||
return blueprint.emitOpError("fragment assembly lowering requires explicit fragment metadata");
|
||||
|
||||
ArrayRef<int64_t> operandIndices = *operandIndicesAttr;
|
||||
ArrayRef<int64_t> sourceOffsets = *sourceOffsetsAttr;
|
||||
ArrayRef<int64_t> flatOffsets = blueprint.getFragmentOffsets();
|
||||
ArrayRef<int64_t> flatSizes = blueprint.getFragmentSizes();
|
||||
ArrayRef<int64_t> flatStrides = *fragmentStridesAttr;
|
||||
int64_t rank = resultType.getRank();
|
||||
|
||||
SmallVector<Value> fragmentOperands {blueprint.getInput()};
|
||||
llvm::append_range(fragmentOperands, blueprint.getFragments());
|
||||
if (failed(validateFragmentAssemblyMetadata(blueprint,
|
||||
rank,
|
||||
fragmentOperands.size(),
|
||||
operandIndices,
|
||||
sourceOffsets,
|
||||
flatOffsets,
|
||||
flatSizes,
|
||||
flatStrides)))
|
||||
return failure();
|
||||
|
||||
SmallVector<int64_t> hostStrides = computeRowMajorStrides(resultType.getShape());
|
||||
SmallVector<FragmentAssemblyCopy, 8> copies;
|
||||
for (int64_t fragmentIndex = 0; fragmentIndex < static_cast<int64_t>(operandIndices.size()); ++fragmentIndex) {
|
||||
int64_t operandIndex = operandIndices[fragmentIndex];
|
||||
|
||||
SmallVector<int64_t, 4> fragmentOffsets;
|
||||
SmallVector<int64_t, 4> fragmentSizes;
|
||||
for (int64_t dim = 0; dim < rank; ++dim) {
|
||||
int64_t flatIndex = fragmentIndex * rank + dim;
|
||||
if (flatStrides[flatIndex] != 1)
|
||||
return blueprint.emitOpError("fragment assembly lowering only supports unit strides");
|
||||
fragmentOffsets.push_back(flatOffsets[flatIndex]);
|
||||
fragmentSizes.push_back(flatSizes[flatIndex]);
|
||||
}
|
||||
|
||||
Value source = mapping.lookupOrDefault(fragmentOperands[operandIndex]);
|
||||
auto sourceType = dyn_cast<RankedTensorType>(source.getType());
|
||||
if (!sourceType || !sourceType.hasStaticShape())
|
||||
return blueprint.emitOpError("fragment assembly lowering requires static ranked tensor operands");
|
||||
size_t elementSize = getElementTypeSizeInBytes(sourceType.getElementType());
|
||||
if (failed(forEachContiguousDestinationChunk(
|
||||
resultType.getShape(),
|
||||
fragmentOffsets,
|
||||
fragmentSizes,
|
||||
[&](ArrayRef<int64_t> chunkOffsets, int64_t relativeSourceOffset, int64_t chunkElements) -> LogicalResult {
|
||||
int64_t hostElementOffset = 0;
|
||||
for (auto [dim, offset] : llvm::enumerate(chunkOffsets))
|
||||
hostElementOffset += offset * hostStrides[dim];
|
||||
|
||||
FragmentAssemblyCopy copy;
|
||||
copy.source = source;
|
||||
copy.sourceType = sourceType;
|
||||
copy.sourceByteOffset =
|
||||
(sourceOffsets[fragmentIndex] + relativeSourceOffset) * static_cast<int64_t>(elementSize);
|
||||
copy.hostByteOffset = hostElementOffset * static_cast<int64_t>(elementSize);
|
||||
copy.byteSize = chunkElements * static_cast<int64_t>(elementSize);
|
||||
copies.push_back(copy);
|
||||
return success();
|
||||
})))
|
||||
return failure();
|
||||
}
|
||||
|
||||
Value currentOutput = createEmptyTensorFromShaped(rewriter, blueprint.getLoc(), resultType);
|
||||
FailureOr<SmallVector<FragmentAssemblyCopyRun, 8>> runs = groupFragmentAssemblyCopyRuns(copies);
|
||||
if (failed(runs))
|
||||
return failure();
|
||||
return emitFragmentAssemblyCopyRuns(
|
||||
rewriter, blueprint.getLoc(), *runs, currentOutput, blueprint.getOperation());
|
||||
}
|
||||
|
||||
static void
|
||||
cloneMappedHelperOperands(Operation* op, IRMapping& mapping, IRRewriter& rewriter, OperationFolder& constantFolder) {
|
||||
for (Value operand : op->getOperands()) {
|
||||
@@ -55,17 +144,6 @@ cloneMappedHelperOperands(Operation* op, IRMapping& mapping, IRRewriter& rewrite
|
||||
}
|
||||
}
|
||||
|
||||
static FailureOr<int32_t> getPimCoreIdForComputeOp(spatial::SpatScheduledCompute computeOp, size_t& fallbackCoreId) {
|
||||
if (auto spatialCoreIdAttr = computeOp->getAttrOfType<IntegerAttr>(onnx_mlir::kCoreIdAttrName))
|
||||
return pim::checkedI32(spatialCoreIdAttr.getInt(), computeOp, "spatial compute core id");
|
||||
auto checkedCoreId =
|
||||
pim::checkedI32(static_cast<uint64_t>(fallbackCoreId), computeOp, "fallback spatial compute core id");
|
||||
if (failed(checkedCoreId))
|
||||
return failure();
|
||||
++fallbackCoreId;
|
||||
return *checkedCoreId;
|
||||
}
|
||||
|
||||
static LogicalResult collectHelperComputeChain(spatial::SpatScheduledCompute computeOp,
|
||||
SmallVectorImpl<Operation*>& helperChain,
|
||||
bool requireReturnUse = true) {
|
||||
@@ -104,16 +182,79 @@ static LogicalResult collectHelperComputeChain(spatial::SpatScheduledCompute com
|
||||
return success();
|
||||
}
|
||||
|
||||
static bool isHostMaterializableHelperOp(Operation* op) {
|
||||
if (isa<spatial::SpatYieldOp>(op))
|
||||
return true;
|
||||
if (isa<arith::ConstantOp>(op) || op->hasTrait<OpTrait::ConstantLike>())
|
||||
return true;
|
||||
if (auto blueprint = dyn_cast<spatial::SpatBlueprintOp>(op)) {
|
||||
std::optional<StringRef> mode = blueprint.getMode();
|
||||
return mode && *mode == "fragment_assembly";
|
||||
}
|
||||
return isShapingOnlyOp(op) || isPureIndexComputationOp(op);
|
||||
}
|
||||
|
||||
static FailureOr<DenseMap<Value, Attribute>>
|
||||
analyzeHostMaterializableHelper(spatial::SpatScheduledCompute computeOp) {
|
||||
DenseMap<Value, Attribute> folded;
|
||||
for (auto [weightIndex, weight] : llvm::enumerate(computeOp.getWeights())) {
|
||||
auto argument = computeOp.getWeightArgument(weightIndex);
|
||||
if (!argument)
|
||||
return failure();
|
||||
Attribute constant;
|
||||
if (matchPattern(weight, m_Constant(&constant)))
|
||||
folded[*argument] = constant;
|
||||
}
|
||||
Block& block = computeOp.getBody().front();
|
||||
for (Operation& op : block) {
|
||||
if (!isHostMaterializableHelperOp(&op))
|
||||
return failure();
|
||||
if (isa<spatial::SpatYieldOp, spatial::SpatBlueprintOp>(op)
|
||||
|| (isShapingOnlyOp(&op) && !isPureIndexComputationOp(&op)))
|
||||
continue;
|
||||
if (isa<arith::ConstantOp>(op) || op.hasTrait<OpTrait::ConstantLike>()) {
|
||||
for (Value result : op.getResults()) {
|
||||
Attribute constant;
|
||||
if (!matchPattern(result, m_Constant(&constant)))
|
||||
return failure();
|
||||
folded[result] = constant;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!isPureIndexComputationOp(&op) || op.getNumRegions() != 0)
|
||||
return failure();
|
||||
SmallVector<Attribute> operands;
|
||||
for (Value operand : op.getOperands()) {
|
||||
auto it = folded.find(operand);
|
||||
if (it == folded.end())
|
||||
return failure();
|
||||
operands.push_back(it->second);
|
||||
}
|
||||
SmallVector<OpFoldResult> results;
|
||||
if (failed(op.fold(operands, results))
|
||||
|| results.size() != op.getNumResults())
|
||||
return failure();
|
||||
for (auto [result, foldResult] : llvm::zip(op.getResults(), results)) {
|
||||
auto attribute = dyn_cast<Attribute>(foldResult);
|
||||
if (!attribute)
|
||||
return failure();
|
||||
folded[result] = attribute;
|
||||
}
|
||||
}
|
||||
return folded;
|
||||
}
|
||||
|
||||
static bool inlineInputlessHelperComputeForWeightLikeUsers(spatial::SpatScheduledCompute computeOp,
|
||||
IRRewriter& rewriter,
|
||||
OperationFolder& constantFolder) {
|
||||
if (!computeOp.getInputs().empty() || computeOp.getNumResults() != 1)
|
||||
return false;
|
||||
if (computeOp.getResult(0).use_empty())
|
||||
return false;
|
||||
if (!llvm::all_of(computeOp.getResult(0).getUsers(), [](Operation* user) {
|
||||
return isa<spatial::SpatScheduledCompute, spatial::SpatScheduledComputeBatch, pim::PimCoreOp, pim::PimCoreBatchOp>(user);
|
||||
}))
|
||||
return false;
|
||||
|
||||
Block& block = computeOp.getBody().front();
|
||||
if (block.getNumArguments() != computeOp.getWeights().size())
|
||||
return false;
|
||||
@@ -121,6 +262,9 @@ static bool inlineInputlessHelperComputeForWeightLikeUsers(spatial::SpatSchedule
|
||||
auto yieldOp = dyn_cast<spatial::SpatYieldOp>(block.getTerminator());
|
||||
if (!yieldOp || yieldOp.getNumOperands() != 1)
|
||||
return false;
|
||||
auto folded = analyzeHostMaterializableHelper(computeOp);
|
||||
if (failed(folded))
|
||||
return false;
|
||||
|
||||
rewriter.setInsertionPoint(computeOp);
|
||||
IRMapping mapping;
|
||||
@@ -131,6 +275,31 @@ static bool inlineInputlessHelperComputeForWeightLikeUsers(spatial::SpatSchedule
|
||||
mapping.map(*weightArg, weight);
|
||||
}
|
||||
for (Operation& op : block.without_terminator()) {
|
||||
if (auto blueprint = dyn_cast<spatial::SpatBlueprintOp>(op)) {
|
||||
std::optional<StringRef> modeAttr = blueprint.getMode();
|
||||
if (modeAttr && *modeAttr == "fragment_assembly") {
|
||||
auto lowered = lowerFragmentAssemblyBlueprint(rewriter, blueprint, mapping);
|
||||
if (failed(lowered))
|
||||
return false;
|
||||
mapping.map(blueprint.getOutput(), *lowered);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (isa<arith::ConstantOp>(op) || op.hasTrait<OpTrait::ConstantLike>()
|
||||
|| isPureIndexComputationOp(&op)) {
|
||||
for (Value result : op.getResults()) {
|
||||
auto it = folded->find(result);
|
||||
if (it == folded->end())
|
||||
return false;
|
||||
mapping.map(
|
||||
result,
|
||||
getOrCreateConstant(constantFolder, computeOp, it->second,
|
||||
result.getType()));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
cloneMappedHelperOperands(&op, mapping, rewriter, constantFolder);
|
||||
Operation* clonedOp = rewriter.clone(op, mapping);
|
||||
for (auto [originalResult, newResult] : llvm::zip(op.getResults(), clonedOp->getResults()))
|
||||
@@ -214,7 +383,7 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeOp(spatial::SpatScheduledCom
|
||||
if (!computeOp.getWeights().empty())
|
||||
computeWeights.append(computeOp.getWeights().begin(), computeOp.getWeights().end());
|
||||
rewriter.setInsertionPointAfter(computeOp);
|
||||
auto checkedCoreId = getPimCoreIdForComputeOp(computeOp, coreId);
|
||||
auto checkedCoreId = getRequiredScheduledCoreId(computeOp, "spatial compute core id");
|
||||
if (failed(checkedCoreId))
|
||||
return failure();
|
||||
auto coreIdAttr = pim::getCheckedI32Attr(rewriter, computeOp, static_cast<int64_t>(*checkedCoreId), "pim core id");
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#include "mlir/Transforms/DialectConversion.h"
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/SpatialToPim/Common.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/SpatialToPim/Patterns.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
|
||||
using namespace mlir;
|
||||
|
||||
@@ -11,6 +15,92 @@ namespace raptor {
|
||||
|
||||
} // namespace raptor
|
||||
|
||||
struct LowerFragmentAssemblyBlueprintPattern
|
||||
: OpConversionPattern<spatial::SpatBlueprintOp> {
|
||||
using OpConversionPattern::OpConversionPattern;
|
||||
|
||||
LogicalResult matchAndRewrite(spatial::SpatBlueprintOp op,
|
||||
OpAdaptor adaptor,
|
||||
ConversionPatternRewriter& rewriter) const override {
|
||||
std::optional<StringRef> modeAttr = op.getMode();
|
||||
if (!modeAttr || *modeAttr != "fragment_assembly")
|
||||
return failure();
|
||||
|
||||
auto resultType = dyn_cast<ShapedType>(op.getOutput().getType());
|
||||
if (!resultType || !resultType.hasStaticShape())
|
||||
return op.emitOpError("fragment assembly lowering requires a static ranked tensor result");
|
||||
|
||||
std::optional<ArrayRef<int64_t>> operandIndicesAttr = op.getFragmentOperandIndices();
|
||||
std::optional<ArrayRef<int64_t>> sourceOffsetsAttr = op.getFragmentSourceOffsets();
|
||||
std::optional<ArrayRef<int64_t>> fragmentStridesAttr = op.getFragmentStrides();
|
||||
if (!operandIndicesAttr || !sourceOffsetsAttr || !fragmentStridesAttr)
|
||||
return op.emitOpError("fragment assembly lowering requires explicit fragment metadata");
|
||||
|
||||
ArrayRef<int64_t> operandIndices = *operandIndicesAttr;
|
||||
ArrayRef<int64_t> sourceOffsets = *sourceOffsetsAttr;
|
||||
ArrayRef<int64_t> flatOffsets = op.getFragmentOffsets();
|
||||
ArrayRef<int64_t> flatSizes = op.getFragmentSizes();
|
||||
ArrayRef<int64_t> flatStrides = *fragmentStridesAttr;
|
||||
int64_t rank = resultType.getRank();
|
||||
|
||||
SmallVector<Value> fragmentOperands {adaptor.getInput()};
|
||||
llvm::append_range(fragmentOperands, adaptor.getFragments());
|
||||
if (failed(validateFragmentAssemblyMetadata(
|
||||
op, rank, fragmentOperands.size(), operandIndices, sourceOffsets, flatOffsets, flatSizes, flatStrides)))
|
||||
return failure();
|
||||
|
||||
Value currentOutput =
|
||||
tensor::EmptyOp::create(rewriter, op.getLoc(), resultType.getShape(), resultType.getElementType()).getResult();
|
||||
for (int64_t fragmentIndex = 0; fragmentIndex < static_cast<int64_t>(operandIndices.size()); ++fragmentIndex) {
|
||||
int64_t operandIndex = operandIndices[fragmentIndex];
|
||||
|
||||
SmallVector<int64_t, 4> fragmentOffsets;
|
||||
for (int64_t dim = 0; dim < rank; ++dim) {
|
||||
int64_t flatIndex = fragmentIndex * rank + dim;
|
||||
if (flatStrides[flatIndex] != 1)
|
||||
return op.emitOpError("fragment assembly lowering only supports unit strides");
|
||||
fragmentOffsets.push_back(flatOffsets[flatIndex]);
|
||||
}
|
||||
|
||||
Value source = fragmentOperands[operandIndex];
|
||||
auto sourceType = dyn_cast<RankedTensorType>(source.getType());
|
||||
if (!sourceType || !sourceType.hasStaticShape())
|
||||
return op.emitOpError("fragment assembly lowering requires static ranked tensor operands");
|
||||
|
||||
SmallVector<int64_t, 4> fragmentShape;
|
||||
fragmentShape.reserve(rank);
|
||||
for (int64_t dim = 0; dim < rank; ++dim)
|
||||
fragmentShape.push_back(flatSizes[fragmentIndex * rank + dim]);
|
||||
|
||||
Value fragment = source;
|
||||
if (llvm::to_vector(sourceType.getShape()) != fragmentShape || sourceOffsets[fragmentIndex] != 0) {
|
||||
FailureOr<SmallVector<int64_t, 4>> extractOffsets = getStaticSliceOffsetsForElementOffset(
|
||||
op, sourceType, fragmentShape, sourceOffsets[fragmentIndex], "fragment assembly source slice");
|
||||
if (failed(extractOffsets))
|
||||
return failure();
|
||||
fragment = tensor::ExtractSliceOp::create(rewriter,
|
||||
op.getLoc(),
|
||||
source,
|
||||
getStaticIndexAttrs(rewriter, *extractOffsets),
|
||||
getStaticIndexAttrs(rewriter, fragmentShape),
|
||||
getUnitStrides(rewriter, rank));
|
||||
}
|
||||
|
||||
currentOutput = tensor::InsertSliceOp::create(rewriter,
|
||||
op.getLoc(),
|
||||
fragment,
|
||||
currentOutput,
|
||||
getStaticIndexAttrs(rewriter, fragmentOffsets),
|
||||
getStaticIndexAttrs(rewriter, fragmentShape),
|
||||
getUnitStrides(rewriter, rank))
|
||||
.getResult();
|
||||
}
|
||||
|
||||
rewriter.replaceOp(op, currentOutput);
|
||||
return success();
|
||||
}
|
||||
};
|
||||
|
||||
void populateInitialPatterns(RewritePatternSet& patterns) {
|
||||
raptor::populateWithGenerated(patterns);
|
||||
populateTransposeLoweringPatterns(patterns);
|
||||
@@ -19,6 +109,7 @@ void populateInitialPatterns(RewritePatternSet& patterns) {
|
||||
void populateCoreBodyPatterns(RewritePatternSet& patterns) {
|
||||
raptor::populateWithGenerated(patterns);
|
||||
populateTransposeLoweringPatterns(patterns);
|
||||
patterns.add<LowerFragmentAssemblyBlueprintPattern>(patterns.getContext());
|
||||
}
|
||||
|
||||
} // namespace onnx_mlir
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "mlir/Dialect/Bufferization/IR/Bufferization.h"
|
||||
#include "mlir/Dialect/Linalg/IR/Linalg.h"
|
||||
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||
#include "mlir/Dialect/Tosa/IR/TosaOps.h"
|
||||
#include "mlir/IR/BuiltinOps.h"
|
||||
@@ -11,6 +12,7 @@
|
||||
|
||||
#include "Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||
#include "Conversion/SpatialToPim/SpatialToPimPass.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||
#include "src/Accelerators/PIM/Conversion/SpatialToPim/Common.hpp"
|
||||
@@ -149,6 +151,40 @@ static std::optional<ReturnUseInfo> analyzeReturnUse(Value value) {
|
||||
};
|
||||
}
|
||||
|
||||
static FailureOr<SmallVector<std::pair<spatial::SpatBlueprintOp, size_t>, 4>>
|
||||
analyzeTopLevelFragmentAssemblyUses(Value value) {
|
||||
SmallVector<std::pair<spatial::SpatBlueprintOp, size_t>, 4> uses;
|
||||
for (OpOperand& use : value.getUses()) {
|
||||
auto blueprint = dyn_cast<spatial::SpatBlueprintOp>(use.getOwner());
|
||||
if (!blueprint || blueprint->getParentOp() != blueprint->getParentOfType<func::FuncOp>())
|
||||
return failure();
|
||||
std::optional<StringRef> mode = blueprint.getMode();
|
||||
if (!mode || *mode != "fragment_assembly")
|
||||
return failure();
|
||||
if (!blueprint.getOutput().hasOneUse() || !isa<func::ReturnOp>(*blueprint.getOutput().getUsers().begin()))
|
||||
return failure();
|
||||
std::optional<ArrayRef<int64_t>> operandIndicesAttr = blueprint.getFragmentOperandIndices();
|
||||
std::optional<ArrayRef<int64_t>> sourceOffsetsAttr = blueprint.getFragmentSourceOffsets();
|
||||
std::optional<ArrayRef<int64_t>> stridesAttr = blueprint.getFragmentStrides();
|
||||
auto resultType = dyn_cast<RankedTensorType>(blueprint.getOutput().getType());
|
||||
if (!operandIndicesAttr || !sourceOffsetsAttr || !stridesAttr || !resultType || !resultType.hasStaticShape())
|
||||
return failure();
|
||||
SmallVector<Value> fragmentOperands {blueprint.getInput()};
|
||||
llvm::append_range(fragmentOperands, blueprint.getFragments());
|
||||
if (failed(validateFragmentAssemblyMetadata(blueprint,
|
||||
resultType.getRank(),
|
||||
fragmentOperands.size(),
|
||||
*operandIndicesAttr,
|
||||
*sourceOffsetsAttr,
|
||||
blueprint.getFragmentOffsets(),
|
||||
blueprint.getFragmentSizes(),
|
||||
*stridesAttr)))
|
||||
return failure();
|
||||
uses.emplace_back(blueprint, use.getOperandNumber());
|
||||
}
|
||||
return uses;
|
||||
}
|
||||
|
||||
static std::optional<ConcatReturnUseInfo> analyzeConcatReturnUse(Value value) {
|
||||
auto getConcatResult = [](Operation* op) -> Value {
|
||||
if (auto tensorConcat = dyn_cast<tensor::ConcatOp>(op))
|
||||
@@ -559,6 +595,115 @@ raptor::SpatialToPimPass::ReturnPathLoweringResult raptor::SpatialToPimPass::low
|
||||
}
|
||||
}
|
||||
|
||||
FailureOr<SmallVector<std::pair<spatial::SpatBlueprintOp, size_t>, 4>> fragmentAssemblyUses =
|
||||
analyzeTopLevelFragmentAssemblyUses(producedValue);
|
||||
if (succeeded(fragmentAssemblyUses)) {
|
||||
auto sourceType = dyn_cast<RankedTensorType>(storedValue.getType());
|
||||
if (!sourceType || !sourceType.hasStaticShape()) {
|
||||
producerOp->emitOpError("fragment assembly publication requires a static ranked tensor source");
|
||||
return ReturnPathLoweringResult::Failure;
|
||||
}
|
||||
|
||||
size_t elementSize = getElementTypeSizeInBytes(sourceType.getElementType());
|
||||
for (auto [blueprint, operandNumber] : *fragmentAssemblyUses) {
|
||||
rewriter.setInsertionPointAfterValue(storedValue);
|
||||
std::optional<ArrayRef<int64_t>> operandIndicesAttr = blueprint.getFragmentOperandIndices();
|
||||
std::optional<ArrayRef<int64_t>> sourceOffsetsAttr = blueprint.getFragmentSourceOffsets();
|
||||
std::optional<ArrayRef<int64_t>> stridesAttr = blueprint.getFragmentStrides();
|
||||
if (!operandIndicesAttr || !sourceOffsetsAttr || !stridesAttr) {
|
||||
blueprint.emitOpError(
|
||||
"fragment assembly lowering requires explicit operand, source-offset, and stride metadata");
|
||||
return ReturnPathLoweringResult::Failure;
|
||||
}
|
||||
|
||||
size_t returnIndex = blueprint.getOutput().getUses().begin()->getOperandNumber();
|
||||
Value outputTensor = outputTensors[returnIndex](rewriter, loc);
|
||||
auto outputType = dyn_cast<RankedTensorType>(outputTensor.getType());
|
||||
auto resultType = dyn_cast<RankedTensorType>(blueprint.getOutput().getType());
|
||||
if (!outputType || !resultType || !resultType.hasStaticShape()) {
|
||||
blueprint.emitOpError("fragment assembly lowering requires static ranked host outputs");
|
||||
return ReturnPathLoweringResult::Failure;
|
||||
}
|
||||
|
||||
ArrayRef<int64_t> operandIndices = *operandIndicesAttr;
|
||||
ArrayRef<int64_t> sourceOffsets = *sourceOffsetsAttr;
|
||||
ArrayRef<int64_t> flatOffsets = blueprint.getFragmentOffsets();
|
||||
ArrayRef<int64_t> flatSizes = blueprint.getFragmentSizes();
|
||||
ArrayRef<int64_t> flatStrides = *stridesAttr;
|
||||
int64_t rank = resultType.getRank();
|
||||
if (failed(validateFragmentAssemblyMetadata(blueprint,
|
||||
rank,
|
||||
1 + blueprint.getFragments().size(),
|
||||
operandIndices,
|
||||
sourceOffsets,
|
||||
flatOffsets,
|
||||
flatSizes,
|
||||
flatStrides)))
|
||||
return ReturnPathLoweringResult::Failure;
|
||||
SmallVector<FragmentAssemblyCopy, 8> copies;
|
||||
for (int64_t fragmentIndex = 0; fragmentIndex < static_cast<int64_t>(operandIndices.size()); ++fragmentIndex) {
|
||||
if (operandIndices[fragmentIndex] != static_cast<int64_t>(operandNumber))
|
||||
continue;
|
||||
|
||||
SmallVector<int64_t, 4> fragmentOffsets;
|
||||
SmallVector<int64_t, 4> fragmentSizes;
|
||||
for (int64_t dim = 0; dim < rank; ++dim) {
|
||||
int64_t flatIndex = fragmentIndex * rank + dim;
|
||||
if (flatStrides[flatIndex] != 1) {
|
||||
blueprint.emitOpError("fragment assembly lowering only supports unit strides");
|
||||
return ReturnPathLoweringResult::Failure;
|
||||
}
|
||||
fragmentOffsets.push_back(flatOffsets[flatIndex]);
|
||||
fragmentSizes.push_back(flatSizes[flatIndex]);
|
||||
}
|
||||
|
||||
bool failedChunk = false;
|
||||
if (failed(forEachContiguousDestinationChunk(
|
||||
outputType.getShape(),
|
||||
fragmentOffsets,
|
||||
fragmentSizes,
|
||||
[&](ArrayRef<int64_t> chunkOffsets, int64_t relativeSourceOffset, int64_t chunkElements) -> LogicalResult {
|
||||
auto hostOffset =
|
||||
getCheckedByteOffset(computeFlatElementIndex(chunkOffsets, outputType.getShape()),
|
||||
elementSize,
|
||||
producerOp,
|
||||
"fragment assembly host offset");
|
||||
auto sourceOffset = getCheckedByteOffset(sourceOffsets[fragmentIndex] + relativeSourceOffset,
|
||||
elementSize,
|
||||
producerOp,
|
||||
"fragment assembly source offset");
|
||||
auto fragmentBytes =
|
||||
getCheckedByteOffset(chunkElements, elementSize, producerOp, "fragment assembly host copy byte size");
|
||||
if (failed(hostOffset) || failed(sourceOffset) || failed(fragmentBytes)) {
|
||||
failedChunk = true;
|
||||
return failure();
|
||||
}
|
||||
FragmentAssemblyCopy copy;
|
||||
copy.source = storedValue;
|
||||
copy.sourceType = sourceType;
|
||||
copy.hostByteOffset = *hostOffset;
|
||||
copy.sourceByteOffset = *sourceOffset;
|
||||
copy.byteSize = *fragmentBytes;
|
||||
copies.push_back(copy);
|
||||
return success();
|
||||
})))
|
||||
failedChunk = true;
|
||||
if (failedChunk)
|
||||
return ReturnPathLoweringResult::Failure;
|
||||
}
|
||||
FailureOr<SmallVector<FragmentAssemblyCopyRun, 8>> runs = groupFragmentAssemblyCopyRuns(copies);
|
||||
if (failed(runs))
|
||||
return ReturnPathLoweringResult::Failure;
|
||||
FailureOr<Value> updatedOutput =
|
||||
emitFragmentAssemblyCopyRuns(rewriter, blueprint.getLoc(), *runs, outputTensor, producerOp);
|
||||
if (failed(updatedOutput))
|
||||
return ReturnPathLoweringResult::Failure;
|
||||
outputTensor = *updatedOutput;
|
||||
markOpToRemove(blueprint.getOperation());
|
||||
}
|
||||
return ReturnPathLoweringResult::Handled;
|
||||
}
|
||||
|
||||
if (auto concatReturnUse = analyzeConcatReturnUse(producedValue)) {
|
||||
size_t elementSize = getElementTypeSizeInBytes(storedTensorType.getElementType());
|
||||
auto storedByteSize =
|
||||
@@ -669,6 +814,16 @@ void raptor::SpatialToPimPass::replaceReturnWithOutputBuffers(func::ReturnOp ret
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto blueprint = dyn_cast<spatial::SpatBlueprintOp>(op)) {
|
||||
std::optional<StringRef> mode = blueprint.getMode();
|
||||
if (mode && *mode == "fragment_assembly") {
|
||||
markOpToRemove(blueprint.getOperation());
|
||||
for (Value operand : blueprint->getOperands())
|
||||
markOwnedReturnChain(operand.getDefiningOp(), markOwnedReturnChain);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto computeOp = dyn_cast<spatial::SpatScheduledCompute>(op)) {
|
||||
markOpToRemove(computeOp);
|
||||
if (!computeOp.getInputs().empty())
|
||||
|
||||
@@ -44,121 +44,29 @@ using namespace pim;
|
||||
|
||||
namespace onnx_mlir {
|
||||
|
||||
static memref::GlobalOp getOrCreateZeroGlobal(IRRewriter& rewriter, Location loc, RankedTensorType tensorType) {
|
||||
auto moduleOp = rewriter.getBlock()->getParentOp()->getParentOfType<ModuleOp>();
|
||||
auto memRefType = MemRefType::get(tensorType.getShape(), tensorType.getElementType());
|
||||
auto zeroAttr = DenseElementsAttr::get(tensorType, rewriter.getZeroAttr(tensorType.getElementType()));
|
||||
|
||||
for (auto globalOp : moduleOp.getOps<memref::GlobalOp>()) {
|
||||
if (!globalOp.getConstant() || globalOp.getType() != memRefType || !globalOp.getInitialValue())
|
||||
continue;
|
||||
if (dyn_cast<DenseElementsAttr>(*globalOp.getInitialValue()) == zeroAttr)
|
||||
return globalOp;
|
||||
}
|
||||
|
||||
std::string nameStem;
|
||||
llvm::raw_string_ostream nameStream(nameStem);
|
||||
nameStream << "__pim_zero_" << tensorType.getRank() << "d_" << tensorType.getNumElements();
|
||||
nameStream.flush();
|
||||
|
||||
std::string symbolName = nameStem;
|
||||
unsigned suffix = 0;
|
||||
while (SymbolTable::lookupSymbolIn(moduleOp, symbolName))
|
||||
symbolName = (nameStem + "_" + Twine(suffix++)).str();
|
||||
|
||||
OpBuilder::InsertionGuard guard(rewriter);
|
||||
rewriter.setInsertionPointToStart(moduleOp.getBody());
|
||||
return memref::GlobalOp::create(rewriter,
|
||||
loc,
|
||||
rewriter.getStringAttr(symbolName),
|
||||
rewriter.getStringAttr("private"),
|
||||
TypeAttr::get(memRefType),
|
||||
zeroAttr,
|
||||
rewriter.getUnitAttr(),
|
||||
IntegerAttr {});
|
||||
}
|
||||
|
||||
static FailureOr<Value> createZeroedDeviceHVector(IRRewriter& rewriter,
|
||||
Location loc,
|
||||
RankedTensorType tensorType,
|
||||
OperationFolder& constantFolder) {
|
||||
auto outputBuffer = createEmptyTensorFromShaped(rewriter, loc, tensorType);
|
||||
auto zeroGlobal = getOrCreateZeroGlobal(rewriter, loc, tensorType);
|
||||
auto zeroValue = memref::GetGlobalOp::create(rewriter, loc, zeroGlobal.getType(), zeroGlobal.getName());
|
||||
auto zeroIndex = getOrCreateIndexConstant(constantFolder, outputBuffer.getOperation(), 0);
|
||||
auto byteSize =
|
||||
pim::getCheckedShapedTypeSizeInBytes(tensorType, outputBuffer.getOperation(), "host-to-device zero copy byte size");
|
||||
if (failed(byteSize))
|
||||
return failure();
|
||||
auto sizeAttr =
|
||||
pim::getCheckedI32Attr(rewriter, outputBuffer.getOperation(), *byteSize, "host-to-device zero copy byte size");
|
||||
if (failed(sizeAttr))
|
||||
return failure();
|
||||
return PimMemCopyHostToDevOp::create(
|
||||
rewriter, loc, tensorType, zeroIndex, zeroIndex, outputBuffer, zeroValue, *sizeAttr)
|
||||
.getOutput();
|
||||
}
|
||||
|
||||
static bool isHostBackedMemRefValue(Value value) {
|
||||
while (Operation* definingOp = value.getDefiningOp()) {
|
||||
if (auto subviewOp = dyn_cast<memref::SubViewOp>(definingOp)) {
|
||||
value = subviewOp.getSource();
|
||||
continue;
|
||||
}
|
||||
if (auto castOp = dyn_cast<memref::CastOp>(definingOp)) {
|
||||
value = castOp.getSource();
|
||||
continue;
|
||||
}
|
||||
if (auto collapseOp = dyn_cast<memref::CollapseShapeOp>(definingOp)) {
|
||||
value = collapseOp.getSrc();
|
||||
continue;
|
||||
}
|
||||
if (auto expandOp = dyn_cast<memref::ExpandShapeOp>(definingOp)) {
|
||||
value = expandOp.getSrc();
|
||||
continue;
|
||||
}
|
||||
return isa<memref::GetGlobalOp>(definingOp);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool isHostBackedTensorValue(Value value) {
|
||||
while (Operation* definingOp = value.getDefiningOp()) {
|
||||
if (auto extractSliceOp = dyn_cast<tensor::ExtractSliceOp>(definingOp)) {
|
||||
auto sourceType = dyn_cast<RankedTensorType>(extractSliceOp.getSource().getType());
|
||||
auto resultType = dyn_cast<RankedTensorType>(extractSliceOp.getResult().getType());
|
||||
if (!sourceType || !resultType || !sourceType.hasStaticShape() || !resultType.hasStaticShape())
|
||||
return false;
|
||||
if (!onnx_mlir::isContiguousSubviewWithDynamicOffsets(sourceType.getShape(),
|
||||
extractSliceOp.getMixedOffsets(),
|
||||
extractSliceOp.getStaticSizes(),
|
||||
extractSliceOp.getStaticStrides())) {
|
||||
return false;
|
||||
}
|
||||
value = extractSliceOp.getSource();
|
||||
continue;
|
||||
}
|
||||
if (auto collapseOp = dyn_cast<tensor::CollapseShapeOp>(definingOp)) {
|
||||
value = collapseOp.getSrc();
|
||||
continue;
|
||||
}
|
||||
if (auto expandOp = dyn_cast<tensor::ExpandShapeOp>(definingOp)) {
|
||||
value = expandOp.getSrc();
|
||||
continue;
|
||||
}
|
||||
if (auto castOp = dyn_cast<tensor::CastOp>(definingOp)) {
|
||||
value = castOp.getSource();
|
||||
continue;
|
||||
}
|
||||
if (auto toTensorOp = dyn_cast<bufferization::ToTensorOp>(definingOp))
|
||||
return isHostBackedMemRefValue(toTensorOp.getBuffer());
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static FailureOr<Value>
|
||||
padHVectorInputToCrossbarSize(IRRewriter& rewriter, Location loc, Value vector, OperationFolder& constantFolder) {
|
||||
createZeroPaddedTensor(IRRewriter& rewriter, Location loc, Value value, RankedTensorType resultType) {
|
||||
auto sourceType = cast<RankedTensorType>(value.getType());
|
||||
SmallVector<OpFoldResult> lowPads(sourceType.getRank(), rewriter.getIndexAttr(0));
|
||||
SmallVector<OpFoldResult> highPads;
|
||||
highPads.reserve(sourceType.getRank());
|
||||
for (auto [sourceDim, resultDim] : llvm::zip(sourceType.getShape(), resultType.getShape()))
|
||||
highPads.push_back(rewriter.getIndexAttr(resultDim - sourceDim));
|
||||
|
||||
auto padOp = tensor::PadOp::create(rewriter, loc, resultType, value, lowPads, highPads);
|
||||
auto* padBlock = new Block();
|
||||
for (int64_t i = 0; i < sourceType.getRank(); ++i)
|
||||
padBlock->addArgument(rewriter.getIndexType(), loc);
|
||||
padOp.getRegion().push_back(padBlock);
|
||||
rewriter.setInsertionPointToStart(padBlock);
|
||||
auto zero = getOrCreateConstant(
|
||||
rewriter, padOp.getOperation(), rewriter.getZeroAttr(sourceType.getElementType()), sourceType.getElementType());
|
||||
tensor::YieldOp::create(rewriter, loc, zero);
|
||||
rewriter.setInsertionPointAfter(padOp);
|
||||
return padOp.getResult();
|
||||
}
|
||||
|
||||
static FailureOr<Value> padHVectorInputToCrossbarSize(IRRewriter& rewriter, Location loc, Value vector) {
|
||||
auto vectorType = cast<RankedTensorType>(vector.getType());
|
||||
ArrayRef<int64_t> shape = vectorType.getShape();
|
||||
assert(isHVectorShape(shape) && "expected a horizontal vector");
|
||||
@@ -169,26 +77,10 @@ padHVectorInputToCrossbarSize(IRRewriter& rewriter, Location loc, Value vector,
|
||||
|
||||
auto paddedType = RankedTensorType::get(
|
||||
{shape[0], static_cast<int64_t>(crossbarSize)}, vectorType.getElementType(), vectorType.getEncoding());
|
||||
auto zeroed = createZeroedDeviceHVector(rewriter, loc, paddedType, constantFolder);
|
||||
if (failed(zeroed))
|
||||
return failure();
|
||||
Value zeroIndex = getOrCreateIndexConstant(constantFolder, zeroed->getDefiningOp(), 0);
|
||||
auto byteSize =
|
||||
pim::getCheckedShapedTypeSizeInBytes(vectorType, zeroed->getDefiningOp(), "device padding copy byte size");
|
||||
if (failed(byteSize))
|
||||
return failure();
|
||||
auto sizeAttr = pim::getCheckedI32Attr(rewriter, zeroed->getDefiningOp(), *byteSize, "device padding copy byte size");
|
||||
if (failed(sizeAttr))
|
||||
return failure();
|
||||
if (isHostBackedTensorValue(vector)) {
|
||||
return PimMemCopyHostToDevOp::create(rewriter, loc, paddedType, zeroIndex, zeroIndex, *zeroed, vector, *sizeAttr)
|
||||
.getOutput();
|
||||
}
|
||||
return PimMemCopyOp::create(rewriter, loc, paddedType, zeroIndex, zeroIndex, *zeroed, vector, *sizeAttr).getOutput();
|
||||
return createZeroPaddedTensor(rewriter, loc, vector, paddedType);
|
||||
}
|
||||
|
||||
void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
|
||||
coreId = 0;
|
||||
outputTensors.clear();
|
||||
operationsToRemove.clear();
|
||||
ModuleOp moduleOp = getOperation();
|
||||
@@ -203,7 +95,7 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
|
||||
func::FuncOp funcOp = *entryFunc;
|
||||
if (failed(verifyScheduledSpatialInvariants(funcOp))) {
|
||||
funcOp.emitOpError(
|
||||
"RAPTOR_PHASE_CHECK scheduled Spatial verification failed at the start of SpatialToPim");
|
||||
"scheduled Spatial verification failed at the start of SpatialToPim");
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
@@ -362,7 +254,6 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
|
||||
}
|
||||
|
||||
LogicalResult raptor::SpatialToPimPass::enlargeVMMOutTensorsToCrossbarSize(func::FuncOp funcOp, IRRewriter& rewriter) {
|
||||
OperationFolder constantFolder(funcOp.getContext());
|
||||
bool hasFailure = false;
|
||||
funcOp.walk([&](PimVMMOp vmmOp) {
|
||||
auto outputType = cast<RankedTensorType>(vmmOp.getOutput().getType());
|
||||
@@ -371,7 +262,7 @@ LogicalResult raptor::SpatialToPimPass::enlargeVMMOutTensorsToCrossbarSize(func:
|
||||
assert(outputShape[1] <= static_cast<int64_t>(crossbarSize) && "output width must fit in one crossbar");
|
||||
|
||||
rewriter.setInsertionPoint(vmmOp);
|
||||
auto paddedInput = padHVectorInputToCrossbarSize(rewriter, vmmOp.getLoc(), vmmOp.getInput(), constantFolder);
|
||||
auto paddedInput = padHVectorInputToCrossbarSize(rewriter, vmmOp.getLoc(), vmmOp.getInput());
|
||||
if (failed(paddedInput)) {
|
||||
hasFailure = true;
|
||||
return WalkResult::interrupt();
|
||||
|
||||
@@ -36,7 +36,6 @@ private:
|
||||
using OutputTensorFactory = std::function<mlir::Value(mlir::IRRewriter& rewriter, mlir::Location loc)>;
|
||||
|
||||
llvm::SmallVector<OutputTensorFactory> outputTensors;
|
||||
size_t coreId = 0;
|
||||
llvm::SmallVector<mlir::Operation*> operationsToRemove;
|
||||
|
||||
mlir::LogicalResult allocateAndInitializeCoreLocalVariables(mlir::func::FuncOp funcOp, mlir::IRRewriter& rewriter);
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
#include "Dialect/Pim/Transforms/Bufferization/Common.hpp"
|
||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
||||
|
||||
using namespace mlir;
|
||||
|
||||
static SmallVector<Region *> getSelectionRegions(OpResult result) {
|
||||
SmallVector<Region *> regions;
|
||||
if (auto selection = dyn_cast<scf::IndexSwitchOp>(result.getOwner()))
|
||||
for (Region ®ion : selection->getRegions())
|
||||
regions.push_back(®ion);
|
||||
else if (auto selection = dyn_cast<scf::IfOp>(result.getOwner())) {
|
||||
regions.push_back(&selection.getThenRegion());
|
||||
regions.push_back(&selection.getElseRegion());
|
||||
}
|
||||
return regions;
|
||||
}
|
||||
|
||||
static bool isCoreBatchInputArgument(Value value) {
|
||||
auto blockArg = dyn_cast<BlockArgument>(value);
|
||||
if (!blockArg)
|
||||
@@ -92,20 +105,46 @@ FailureOr<Value> onnx_mlir::pim::getPimAddressBase(Value value, const StaticValu
|
||||
}
|
||||
|
||||
bool onnx_mlir::pim::isHostBackedPimAddress(Value value, const StaticValueKnowledge& knowledge) {
|
||||
auto base = getPimStorageBase(value, knowledge);
|
||||
if (failed(base))
|
||||
return false;
|
||||
|
||||
if (isCoreBatchInputArgument(*base))
|
||||
return true;
|
||||
|
||||
return isa_and_nonnull<memref::GetGlobalOp>(base->getDefiningOp());
|
||||
llvm::SmallPtrSet<Value, 8> visited;
|
||||
std::function<bool(Value)> isHost = [&](Value current) {
|
||||
auto base = getPimStorageBase(current, knowledge);
|
||||
if (failed(base) || !visited.insert(*base).second)
|
||||
return false;
|
||||
bool resultIsHost = isCoreBatchInputArgument(*base)
|
||||
|| isa_and_nonnull<memref::GetGlobalOp>(base->getDefiningOp());
|
||||
auto result = dyn_cast<OpResult>(*base);
|
||||
SmallVector<Region *> regions = result ? getSelectionRegions(result)
|
||||
: SmallVector<Region *>();
|
||||
if (!resultIsHost && !regions.empty())
|
||||
resultIsHost = llvm::all_of(regions, [&](Region *region) {
|
||||
auto yield = dyn_cast<scf::YieldOp>(region->front().getTerminator());
|
||||
return yield && result.getResultNumber() < yield.getNumOperands()
|
||||
&& isHost(yield.getOperand(result.getResultNumber()));
|
||||
});
|
||||
visited.erase(*base);
|
||||
return resultIsHost;
|
||||
};
|
||||
return isHost(value);
|
||||
}
|
||||
|
||||
bool onnx_mlir::pim::isDeviceLocalPimAddress(Value value, const StaticValueKnowledge& knowledge) {
|
||||
auto base = getPimStorageBase(value, knowledge);
|
||||
if (failed(base))
|
||||
return false;
|
||||
|
||||
return isa_and_nonnull<memref::AllocOp>(base->getDefiningOp());
|
||||
llvm::SmallPtrSet<Value, 8> visited;
|
||||
std::function<bool(Value)> isDevice = [&](Value current) {
|
||||
auto base = getPimStorageBase(current, knowledge);
|
||||
if (failed(base) || !visited.insert(*base).second)
|
||||
return false;
|
||||
bool resultIsDevice = isa_and_nonnull<memref::AllocOp>(base->getDefiningOp());
|
||||
auto result = dyn_cast<OpResult>(*base);
|
||||
SmallVector<Region *> regions = result ? getSelectionRegions(result)
|
||||
: SmallVector<Region *>();
|
||||
if (!resultIsDevice && !regions.empty())
|
||||
resultIsDevice = llvm::all_of(regions, [&](Region *region) {
|
||||
auto yield = dyn_cast<scf::YieldOp>(region->front().getTerminator());
|
||||
return yield && result.getResultNumber() < yield.getNumOperands()
|
||||
&& isDevice(yield.getOperand(result.getResultNumber()));
|
||||
});
|
||||
visited.erase(*base);
|
||||
return resultIsDevice;
|
||||
};
|
||||
return isDevice(value);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||
|
||||
#include "llvm/Support/MathExtras.h"
|
||||
|
||||
#include "ContiguityPatterns.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
|
||||
@@ -33,6 +35,7 @@ struct CopyEndpointPlan {
|
||||
|
||||
struct CopyLoopPlan {
|
||||
SmallVector<int64_t> outerShape;
|
||||
int64_t outerElements = 0;
|
||||
int64_t chunkBytes = 0;
|
||||
ByteOffsetExpr targetBaseOffset;
|
||||
ByteOffsetExpr sourceBaseOffset;
|
||||
@@ -74,6 +77,24 @@ static void appendTerm(ByteOffsetExpr& expr, Value value, int64_t scale) {
|
||||
expr.terms.push_back(ByteOffsetTerm {value, scale});
|
||||
}
|
||||
|
||||
static FailureOr<int64_t> checkedPositiveMul(int64_t lhs, int64_t rhs) {
|
||||
int64_t result = 0;
|
||||
if (lhs < 0 || rhs < 0 || llvm::MulOverflow(lhs, rhs, result))
|
||||
return failure();
|
||||
return result;
|
||||
}
|
||||
|
||||
static FailureOr<int64_t> checkedPositiveProduct(ArrayRef<int64_t> values) {
|
||||
int64_t result = 1;
|
||||
for (int64_t value : values) {
|
||||
auto product = checkedPositiveMul(result, value);
|
||||
if (failed(product))
|
||||
return failure();
|
||||
result = *product;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static FailureOr<SmallVector<int64_t>> getStaticMemRefStrides(MemRefType type) {
|
||||
SmallVector<int64_t> strides;
|
||||
int64_t offset = 0;
|
||||
@@ -84,6 +105,165 @@ static FailureOr<SmallVector<int64_t>> getStaticMemRefStrides(MemRefType type) {
|
||||
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) {
|
||||
if (!type.hasStaticShape() || !hasByteSizedElementType(type.getElementType()))
|
||||
return failure();
|
||||
@@ -119,12 +299,15 @@ inferLogicalCopyShape(MemRefType targetType, MemRefType sourceType, int64_t size
|
||||
return failure();
|
||||
}
|
||||
|
||||
static FailureOr<int64_t> getContiguousSuffixRank(MemRefType type, ArrayRef<int64_t> copyShape) {
|
||||
if (!type.hasStaticShape() || !hasByteSizedElementType(type.getElementType())
|
||||
static FailureOr<int64_t> getContiguousSuffixRank(Value value, ArrayRef<int64_t> copyShape) {
|
||||
auto type = dyn_cast<MemRefType>(value.getType());
|
||||
if (!type || !type.hasStaticShape() || !hasByteSizedElementType(type.getElementType())
|
||||
|| type.getRank() != static_cast<int64_t>(copyShape.size()))
|
||||
return failure();
|
||||
if (llvm::any_of(copyShape, [](int64_t dim) { return dim <= 0; }))
|
||||
return failure();
|
||||
|
||||
auto strides = getStaticMemRefStrides(type);
|
||||
auto strides = getProvenMemRefStrides(value);
|
||||
if (failed(strides))
|
||||
return failure();
|
||||
|
||||
@@ -134,7 +317,10 @@ static FailureOr<int64_t> getContiguousSuffixRank(MemRefType type, ArrayRef<int6
|
||||
if ((*strides)[dim] != expectedStride)
|
||||
break;
|
||||
++contiguousSuffixRank;
|
||||
expectedStride *= copyShape[dim];
|
||||
auto nextStride = checkedPositiveMul(expectedStride, copyShape[dim]);
|
||||
if (failed(nextStride))
|
||||
return failure();
|
||||
expectedStride = *nextStride;
|
||||
}
|
||||
return contiguousSuffixRank;
|
||||
}
|
||||
@@ -174,18 +360,25 @@ static FailureOr<CopyEndpointPlan> analyzeCopyEndpoint(Value value, Value initia
|
||||
if (!sourceType || !sourceType.hasStaticShape() || !hasByteSizedElementType(sourceType.getElementType()))
|
||||
return failure();
|
||||
|
||||
auto sourceStrides = getStaticMemRefStrides(sourceType);
|
||||
auto sourceStrides = getProvenMemRefStrides(subviewOp.getSource());
|
||||
if (failed(sourceStrides))
|
||||
return failure();
|
||||
|
||||
int64_t elementByteWidth = static_cast<int64_t>(getElementTypeSizeInBytes(sourceType.getElementType()));
|
||||
for (auto [offset, stride] : llvm::zip_equal(subviewOp.getMixedOffsets(), *sourceStrides)) {
|
||||
int64_t byteScale = stride * elementByteWidth;
|
||||
auto byteScale = checkedPositiveMul(stride, elementByteWidth);
|
||||
if (failed(byteScale))
|
||||
return failure();
|
||||
if (auto attr = dyn_cast<Attribute>(offset)) {
|
||||
endpoint.offset.constant += cast<IntegerAttr>(attr).getInt() * byteScale;
|
||||
auto constantOffset = checkedPositiveMul(
|
||||
cast<IntegerAttr>(attr).getInt(), *byteScale);
|
||||
if (failed(constantOffset)
|
||||
|| llvm::AddOverflow(endpoint.offset.constant, *constantOffset,
|
||||
endpoint.offset.constant))
|
||||
return failure();
|
||||
continue;
|
||||
}
|
||||
appendTerm(endpoint.offset, cast<Value>(offset), byteScale);
|
||||
appendTerm(endpoint.offset, cast<Value>(offset), *byteScale);
|
||||
}
|
||||
|
||||
endpoint.base = subviewOp.getSource();
|
||||
@@ -204,17 +397,34 @@ analyzeCopyRewrite(Value target, Value source, Value targetOffset, Value sourceO
|
||||
if (!targetType || !sourceType || size <= 0)
|
||||
return failure();
|
||||
|
||||
auto logicalCopyShape = inferLogicalCopyShape(targetType, sourceType, size);
|
||||
if (failed(logicalCopyShape))
|
||||
return failure();
|
||||
|
||||
auto targetPlan = analyzeCopyEndpoint(target, targetOffset, targetType);
|
||||
auto sourcePlan = analyzeCopyEndpoint(source, sourceOffset, sourceType);
|
||||
if (failed(targetPlan) || failed(sourcePlan))
|
||||
return failure();
|
||||
|
||||
auto targetSuffixRank = getContiguousSuffixRank(targetType, *logicalCopyShape);
|
||||
auto sourceSuffixRank = getContiguousSuffixRank(sourceType, *logicalCopyShape);
|
||||
auto targetBytes = getShapedByteSize(targetType);
|
||||
auto sourceBytes = getShapedByteSize(sourceType);
|
||||
if (targetType.getElementType() == sourceType.getElementType() && succeeded(targetBytes) && succeeded(sourceBytes)
|
||||
&& *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))
|
||||
return failure();
|
||||
|
||||
@@ -229,8 +439,8 @@ analyzeCopyRewrite(Value target, Value source, Value targetOffset, Value sourceO
|
||||
return plan;
|
||||
}
|
||||
|
||||
auto targetStrides = getStaticMemRefStrides(targetType);
|
||||
auto sourceStrides = getStaticMemRefStrides(sourceType);
|
||||
auto targetStrides = getProvenMemRefStrides(target);
|
||||
auto sourceStrides = getProvenMemRefStrides(source);
|
||||
if (failed(targetStrides) || failed(sourceStrides))
|
||||
return failure();
|
||||
|
||||
@@ -240,11 +450,27 @@ analyzeCopyRewrite(Value target, Value source, Value targetOffset, Value sourceO
|
||||
plan.loop.sourceBaseOffset = plan.source.offset;
|
||||
plan.loop.outerShape.assign(logicalCopyShape->begin(), logicalCopyShape->end() - contiguousSuffixRank);
|
||||
SmallVector<int64_t> chunkShape(logicalCopyShape->end() - contiguousSuffixRank, logicalCopyShape->end());
|
||||
plan.loop.chunkBytes = getNumElements(chunkShape) * elementByteWidth;
|
||||
for (int64_t stride : ArrayRef<int64_t>(*targetStrides).take_front(plan.loop.outerShape.size()))
|
||||
plan.loop.targetOuterByteStrides.push_back(stride * elementByteWidth);
|
||||
for (int64_t stride : ArrayRef<int64_t>(*sourceStrides).take_front(plan.loop.outerShape.size()))
|
||||
plan.loop.sourceOuterByteStrides.push_back(stride * elementByteWidth);
|
||||
auto outerElements = checkedPositiveProduct(plan.loop.outerShape);
|
||||
auto chunkElements = checkedPositiveProduct(chunkShape);
|
||||
auto chunkBytes = failed(chunkElements)
|
||||
? FailureOr<int64_t>(failure())
|
||||
: checkedPositiveMul(*chunkElements, elementByteWidth);
|
||||
if (failed(outerElements) || failed(chunkBytes))
|
||||
return failure();
|
||||
plan.loop.outerElements = *outerElements;
|
||||
plan.loop.chunkBytes = *chunkBytes;
|
||||
for (int64_t stride : ArrayRef<int64_t>(*targetStrides).take_front(plan.loop.outerShape.size())) {
|
||||
auto byteStride = checkedPositiveMul(stride, elementByteWidth);
|
||||
if (failed(byteStride))
|
||||
return failure();
|
||||
plan.loop.targetOuterByteStrides.push_back(*byteStride);
|
||||
}
|
||||
for (int64_t stride : ArrayRef<int64_t>(*sourceStrides).take_front(plan.loop.outerShape.size())) {
|
||||
auto byteStride = checkedPositiveMul(stride, elementByteWidth);
|
||||
if (failed(byteStride))
|
||||
return failure();
|
||||
plan.loop.sourceOuterByteStrides.push_back(*byteStride);
|
||||
}
|
||||
if (plan.loop.chunkBytes <= 0)
|
||||
return failure();
|
||||
return plan;
|
||||
@@ -344,7 +570,7 @@ static LogicalResult rewriteCopyLikeOp(CopyOp copyOp,
|
||||
}
|
||||
|
||||
Value c0 = createIndexConstant(rewriter, anchorOp, 0);
|
||||
Value cUpper = createIndexConstant(rewriter, anchorOp, getNumElements(plan->loop.outerShape));
|
||||
Value cUpper = createIndexConstant(rewriter, anchorOp, plan->loop.outerElements);
|
||||
Value cStep = createIndexConstant(rewriter, anchorOp, 1);
|
||||
auto loop = buildNormalizedScfFor(
|
||||
rewriter,
|
||||
|
||||
@@ -302,76 +302,87 @@ void PimBufferizationPass::annotateWeightsMemrefs(ModuleOp moduleOp, func::FuncO
|
||||
|
||||
LogicalResult PimBufferizationPass::verifyContiguousRuntimeOperands(ModuleOp moduleOp) const {
|
||||
bool hasFailure = false;
|
||||
moduleOp.walk([&](Operation* op) {
|
||||
auto verifyOperand = [&](Value operand, unsigned operandIndex) {
|
||||
if (!isa<BaseMemRefType>(operand.getType()))
|
||||
return;
|
||||
if (succeeded(resolveContiguousAddress(operand)) || succeeded(compileContiguousAddressExpr(operand)))
|
||||
return;
|
||||
op->emitOpError() << "operand #" << operandIndex
|
||||
<< " is not backed by contiguous addressable storage after PIM bufferization";
|
||||
hasFailure = true;
|
||||
};
|
||||
|
||||
if (auto memCopyOp = dyn_cast<PimMemCopyOp>(op)) {
|
||||
if (!pim::isNormalizedCopyOp(memCopyOp)) {
|
||||
memCopyOp.emitOpError("must use base memref operands plus explicit byte offsets after bufferization");
|
||||
hasFailure = true;
|
||||
}
|
||||
verifyOperand(memCopyOp.getTarget(), 0);
|
||||
verifyOperand(memCopyOp.getSource(), 1);
|
||||
return;
|
||||
}
|
||||
if (auto loadOp = dyn_cast<PimMemCopyHostToDevOp>(op)) {
|
||||
if (!pim::isNormalizedCopyOp(loadOp)) {
|
||||
loadOp.emitOpError("must use base memref operands plus explicit byte offsets after bufferization");
|
||||
hasFailure = true;
|
||||
}
|
||||
verifyOperand(loadOp.getDeviceTarget(), 2);
|
||||
verifyOperand(loadOp.getHostSource(), 3);
|
||||
return;
|
||||
}
|
||||
if (auto storeOp = dyn_cast<PimMemCopyDevToHostOp>(op)) {
|
||||
if (!pim::isNormalizedCopyOp(storeOp)) {
|
||||
storeOp.emitOpError("must use base memref operands plus explicit byte offsets after bufferization");
|
||||
hasFailure = true;
|
||||
}
|
||||
verifyOperand(storeOp.getHostTarget(), 2);
|
||||
verifyOperand(storeOp.getDeviceSource(), 3);
|
||||
return;
|
||||
}
|
||||
if (auto sendOp = dyn_cast<PimSendOp>(op)) {
|
||||
verifyOperand(sendOp.getInput(), 0);
|
||||
return;
|
||||
}
|
||||
if (auto receiveOp = dyn_cast<PimReceiveOp>(op)) {
|
||||
verifyOperand(receiveOp.getOutputBuffer(), 0);
|
||||
return;
|
||||
}
|
||||
if (auto concatOp = dyn_cast<PimConcatOp>(op)) {
|
||||
verifyOperand(concatOp.getOutputBuffer(), 0);
|
||||
for (auto inputAndIndex : llvm::enumerate(concatOp.getInputs()))
|
||||
verifyOperand(inputAndIndex.value(), inputAndIndex.index() + 1);
|
||||
return;
|
||||
}
|
||||
if (isa<PimTransposeOp,
|
||||
PimVMMOp,
|
||||
PimVVAddOp,
|
||||
PimVVSubOp,
|
||||
PimVVMulOp,
|
||||
PimVVMaxOp,
|
||||
PimVVDMulOp,
|
||||
PimVAvgOp,
|
||||
PimVReluOp,
|
||||
PimVTanhOp,
|
||||
PimVSigmOp,
|
||||
PimVSoftmaxOp>(op)) {
|
||||
for (auto operandAndIndex : llvm::enumerate(op->getOperands())) {
|
||||
if (auto vmmOp = dyn_cast<PimVMMOp>(op); vmmOp && operandAndIndex.index() == 0)
|
||||
continue;
|
||||
verifyOperand(operandAndIndex.value(), operandAndIndex.index());
|
||||
}
|
||||
}
|
||||
auto verifyWithKnowledge = [&](auto coreLikeOp, const StaticValueKnowledge& initialKnowledge) {
|
||||
(void) walkPimCoreBlockStructurally(
|
||||
coreLikeOp.getBody().front(), initialKnowledge, [&](Operation& op, const StaticValueKnowledge& knowledge) {
|
||||
auto verifyOperand = [&](Value operand, unsigned operandIndex) {
|
||||
if (!isa<BaseMemRefType>(operand.getType()))
|
||||
return;
|
||||
if (succeeded(resolveContiguousAddress(operand, knowledge)) || succeeded(compileContiguousAddressExpr(operand)))
|
||||
return;
|
||||
op.emitOpError() << "operand #" << operandIndex
|
||||
<< " is not backed by contiguous addressable storage after PIM bufferization";
|
||||
hasFailure = true;
|
||||
};
|
||||
|
||||
if (auto memCopyOp = dyn_cast<PimMemCopyOp>(&op)) {
|
||||
if (!pim::isNormalizedCopyOp(memCopyOp)) {
|
||||
memCopyOp.emitOpError("must use base memref operands plus explicit byte offsets after bufferization");
|
||||
hasFailure = true;
|
||||
}
|
||||
verifyOperand(memCopyOp.getTarget(), 0);
|
||||
verifyOperand(memCopyOp.getSource(), 1);
|
||||
return success();
|
||||
}
|
||||
if (auto loadOp = dyn_cast<PimMemCopyHostToDevOp>(&op)) {
|
||||
if (!pim::isNormalizedCopyOp(loadOp)) {
|
||||
loadOp.emitOpError("must use base memref operands plus explicit byte offsets after bufferization");
|
||||
hasFailure = true;
|
||||
}
|
||||
verifyOperand(loadOp.getDeviceTarget(), 2);
|
||||
verifyOperand(loadOp.getHostSource(), 3);
|
||||
return success();
|
||||
}
|
||||
if (auto storeOp = dyn_cast<PimMemCopyDevToHostOp>(&op)) {
|
||||
if (!pim::isNormalizedCopyOp(storeOp)) {
|
||||
storeOp.emitOpError("must use base memref operands plus explicit byte offsets after bufferization");
|
||||
hasFailure = true;
|
||||
}
|
||||
verifyOperand(storeOp.getHostTarget(), 2);
|
||||
verifyOperand(storeOp.getDeviceSource(), 3);
|
||||
return success();
|
||||
}
|
||||
if (auto sendOp = dyn_cast<PimSendOp>(&op)) {
|
||||
verifyOperand(sendOp.getInput(), 0);
|
||||
return success();
|
||||
}
|
||||
if (auto receiveOp = dyn_cast<PimReceiveOp>(&op)) {
|
||||
verifyOperand(receiveOp.getOutputBuffer(), 0);
|
||||
return success();
|
||||
}
|
||||
if (auto concatOp = dyn_cast<PimConcatOp>(&op)) {
|
||||
verifyOperand(concatOp.getOutputBuffer(), 0);
|
||||
for (auto inputAndIndex : llvm::enumerate(concatOp.getInputs()))
|
||||
verifyOperand(inputAndIndex.value(), inputAndIndex.index() + 1);
|
||||
return success();
|
||||
}
|
||||
if (isa<PimTransposeOp,
|
||||
PimVMMOp,
|
||||
PimVVAddOp,
|
||||
PimVVSubOp,
|
||||
PimVVMulOp,
|
||||
PimVVMaxOp,
|
||||
PimVVDMulOp,
|
||||
PimVAvgOp,
|
||||
PimVReluOp,
|
||||
PimVTanhOp,
|
||||
PimVSigmOp,
|
||||
PimVSoftmaxOp>(&op)) {
|
||||
for (auto operandAndIndex : llvm::enumerate(op.getOperands())) {
|
||||
if (auto vmmOp = dyn_cast<PimVMMOp>(&op); vmmOp && operandAndIndex.index() == 0)
|
||||
continue;
|
||||
verifyOperand(operandAndIndex.value(), operandAndIndex.index());
|
||||
}
|
||||
}
|
||||
return success();
|
||||
});
|
||||
};
|
||||
|
||||
moduleOp.walk([&](pim::PimCoreOp coreOp) { verifyWithKnowledge(coreOp, seedCoreKnowledge(coreOp)); });
|
||||
moduleOp.walk([&](pim::PimCoreBatchOp coreBatchOp) {
|
||||
StaticValueKnowledge knowledge = seedCoreBatchKnowledge(coreBatchOp, 0);
|
||||
verifyWithKnowledge(coreBatchOp, knowledge);
|
||||
});
|
||||
|
||||
if (hasFailure) {
|
||||
|
||||
@@ -7,10 +7,22 @@ add_pim_library(SpatialOps
|
||||
SpatialOpsVerify.cpp
|
||||
SpatialOpsCanonicalization.cpp
|
||||
${PIM_SRC_ROOT}/Conversion/ONNXToSpatial/CompileTime.cpp
|
||||
Transforms/MergeComputeNodes/MergeComputeNodesPass.cpp
|
||||
Transforms/MergeComputeNodes/MaterializeMergeSchedule.cpp
|
||||
Transforms/MergeComputeNodes/Scheduling/ComputeGraph.cpp
|
||||
Transforms/MergeComputeNodes/Scheduling/ComputeInstanceUtils.cpp
|
||||
Transforms/MergeComputeNodes/DeferredCommunicationPlanning.cpp
|
||||
Transforms/MergeComputeNodes/DeferredProjectionAnalysis.cpp
|
||||
Transforms/MergeComputeNodes/DeferredTransferPlanning.cpp
|
||||
Transforms/MergeComputeNodes/DeferredCommunicationScheduling.cpp
|
||||
Transforms/MergeComputeNodes/DeferredBoundaryPlanning.cpp
|
||||
Transforms/MergeComputeNodes/DeferredCommunicationDeadlock.cpp
|
||||
Transforms/MergeComputeNodes/DeferredBoundaryRealization.cpp
|
||||
Transforms/MergeComputeNodes/DeferredResultRealization.cpp
|
||||
Transforms/MergeComputeNodes/DeferredCommunicationRealization.cpp
|
||||
Transforms/MergeComputeNodes/MergeComputeNodesPass.cpp
|
||||
Transforms/MergeComputeNodes/ScheduledComputeMaterialization.cpp
|
||||
Transforms/MergeComputeNodes/ScheduledComputeReport.cpp
|
||||
Transforms/MergeComputeNodes/ScheduledComputeVerification.cpp
|
||||
Transforms/MergeComputeNodes/SpatialDataflowCsvExporter.cpp
|
||||
Transforms/MergeComputeNodes/Scheduling/MergeSchedulingAnalysis.cpp
|
||||
Transforms/MergeComputeNodes/Scheduling/PeftScheduler.cpp
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ include "mlir/IR/OpAsmInterface.td"
|
||||
include "mlir/IR/BuiltinTypes.td"
|
||||
include "mlir/IR/AttrTypeBase.td"
|
||||
include "mlir/IR/RegionKindInterface.td"
|
||||
include "mlir/Interfaces/ControlFlowInterfaces.td"
|
||||
include "mlir/Interfaces/ParallelCombiningOpInterface.td"
|
||||
include "mlir/Interfaces/SideEffectInterfaces.td"
|
||||
|
||||
@@ -27,7 +28,7 @@ def SpatTensor :
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
class SpatComputeLikeBase<string mnemonic> : SpatOp<mnemonic,
|
||||
[SingleBlock, AttrSizedOperandSegments,
|
||||
[AttrSizedOperandSegments,
|
||||
DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmBlockArgumentNames"]>]> {
|
||||
let summary = "Compute region with attached constant weights";
|
||||
|
||||
@@ -40,7 +41,7 @@ class SpatComputeLikeBase<string mnemonic> : SpatOp<mnemonic,
|
||||
Variadic<SpatTensor>:$outputs
|
||||
);
|
||||
|
||||
let regions = (region SizedRegion<1>:$body);
|
||||
let regions = (region MinSizedRegion<1>:$body);
|
||||
|
||||
let hasVerifier = 1;
|
||||
let hasFolder = 1;
|
||||
@@ -76,7 +77,7 @@ def SpatScheduledCompute : SpatComputeLikeBase<"scheduled_compute"> {
|
||||
}
|
||||
|
||||
class SpatComputeBatchLikeBase<string mnemonic> : SpatOp<mnemonic,
|
||||
[SingleBlock, AttrSizedOperandSegments,
|
||||
[AttrSizedOperandSegments,
|
||||
DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmBlockArgumentNames"]>]> {
|
||||
let summary = "Tensor-native batch of equivalent compute lanes with shared weights and packed inputs";
|
||||
|
||||
@@ -90,13 +91,14 @@ class SpatComputeBatchLikeBase<string mnemonic> : SpatOp<mnemonic,
|
||||
Variadic<SpatTensor>:$outputs
|
||||
);
|
||||
|
||||
let regions = (region SizedRegion<1>:$body);
|
||||
let regions = (region MinSizedRegion<1>:$body);
|
||||
|
||||
let hasVerifier = 1;
|
||||
let hasCustomAssemblyFormat = 1;
|
||||
}
|
||||
|
||||
def SpatGraphComputeBatch : SpatComputeBatchLikeBase<"graph_compute_batch"> {
|
||||
let hasCanonicalizer = 1;
|
||||
let extraClassDeclaration = [{
|
||||
std::optional<::mlir::BlockArgument> getLaneArgument();
|
||||
std::optional<::mlir::BlockArgument> getWeightArgument(unsigned idx);
|
||||
@@ -113,6 +115,7 @@ def SpatGraphComputeBatch : SpatComputeBatchLikeBase<"graph_compute_batch"> {
|
||||
}
|
||||
|
||||
def SpatScheduledComputeBatch : SpatComputeBatchLikeBase<"scheduled_compute_batch"> {
|
||||
let hasCanonicalizer = 1;
|
||||
let extraClassDeclaration = [{
|
||||
std::optional<::mlir::BlockArgument> getLaneArgument();
|
||||
std::optional<::mlir::BlockArgument> getWeightArgument(unsigned idx);
|
||||
@@ -161,6 +164,41 @@ def SpatYieldOp : SpatOp<"yield", [Terminator]> {
|
||||
let hasCustomAssemblyFormat = 1;
|
||||
}
|
||||
|
||||
def SpatBlockYieldOp : SpatOp<"block_yield", [
|
||||
Terminator,
|
||||
DeclareOpInterfaceMethods<BranchOpInterface, ["getSuccessorForOperands"]>
|
||||
]> {
|
||||
let summary = "Terminate a scheduled structural compute block";
|
||||
|
||||
let arguments = (ins
|
||||
Variadic<AnyType>:$outputs
|
||||
);
|
||||
|
||||
let successors = (successor
|
||||
VariadicSuccessor<AnySuccessor>:$next
|
||||
);
|
||||
|
||||
let hasVerifier = 1;
|
||||
let hasCustomAssemblyFormat = 1;
|
||||
}
|
||||
|
||||
def SpatDeferredCommunicationOp : SpatOp<"deferred_communication", [SingleBlock]> {
|
||||
let summary = "Temporary scheduled payload derivation placeholder";
|
||||
|
||||
let arguments = (ins
|
||||
Variadic<SpatTensor>:$sources
|
||||
);
|
||||
|
||||
let results = (outs
|
||||
SpatTensor:$output
|
||||
);
|
||||
|
||||
let regions = (region SizedRegion<1>:$body);
|
||||
|
||||
let hasVerifier = 1;
|
||||
let hasCustomAssemblyFormat = 1;
|
||||
}
|
||||
|
||||
def SpatExtractRowsOp : SpatOp<"extract_rows", []> {
|
||||
let summary = "Extract every row of a rank-2 tensor as separate rank-2 row tensors";
|
||||
|
||||
@@ -232,8 +270,24 @@ def SpatReluPlanOp : SpatOp<"relu_plan", []> {
|
||||
let hasVerifier = 1;
|
||||
}
|
||||
|
||||
def SpatReconciliatorOp : SpatOp<"reconciliator", []> {
|
||||
let summary = "Logical-to-physical layout record or explicit fragment assembly";
|
||||
def SpatBiasAddPlanOp : SpatOp<"bias_add_plan", []> {
|
||||
let summary = "Layout-aware Conv-style bias add planning op";
|
||||
|
||||
let arguments = (ins
|
||||
SpatTensor:$input,
|
||||
SpatTensor:$bias,
|
||||
StrAttr:$logicalLayout
|
||||
);
|
||||
|
||||
let results = (outs
|
||||
SpatTensor:$output
|
||||
);
|
||||
|
||||
let hasVerifier = 1;
|
||||
}
|
||||
|
||||
def SpatBlueprintOp : SpatOp<"blueprint", []> {
|
||||
let summary = "Blueprint for assembling logical tensors from published fragments";
|
||||
|
||||
let arguments = (ins
|
||||
SpatTensor:$input,
|
||||
@@ -245,6 +299,8 @@ def SpatReconciliatorOp : SpatOp<"reconciliator", []> {
|
||||
StrAttr:$indexMap,
|
||||
OptionalAttr<StrAttr>:$mode,
|
||||
OptionalAttr<DenseI64ArrayAttr>:$fragmentOperandIndices,
|
||||
OptionalAttr<DenseI64ArrayAttr>:$fragmentSourceSlots,
|
||||
OptionalAttr<DenseI64ArrayAttr>:$fragmentSourceOffsets,
|
||||
OptionalAttr<DenseI64ArrayAttr>:$fragmentStrides,
|
||||
OptionalAttr<StrAttr>:$conflictPolicy,
|
||||
OptionalAttr<StrAttr>:$coveragePolicy
|
||||
@@ -255,6 +311,7 @@ def SpatReconciliatorOp : SpatOp<"reconciliator", []> {
|
||||
);
|
||||
|
||||
let hasVerifier = 1;
|
||||
let hasCustomAssemblyFormat = 1;
|
||||
}
|
||||
|
||||
def SpatMaterializeLayoutOp : SpatOp<"materialize_layout", []> {
|
||||
|
||||
@@ -10,6 +10,18 @@ using namespace mlir;
|
||||
|
||||
namespace onnx_mlir {
|
||||
namespace spatial {
|
||||
|
||||
RankedTensorType getGraphBatchPhysicalResultType(int64_t laneCount, RankedTensorType fragmentType) {
|
||||
SmallVector<int64_t> shape {laneCount};
|
||||
llvm::append_range(shape, fragmentType.getShape());
|
||||
return RankedTensorType::get(shape, fragmentType.getElementType(), fragmentType.getEncoding());
|
||||
}
|
||||
|
||||
FailureOr<RankedTensorType> getGraphBatchFragmentType(RankedTensorType physicalType, int64_t expectedLaneCount) {
|
||||
if (!physicalType || physicalType.getRank() < 1 || physicalType.getDimSize(0) != expectedLaneCount)
|
||||
return failure();
|
||||
return RankedTensorType::get(physicalType.getShape().drop_front(), physicalType.getElementType(), physicalType.getEncoding());
|
||||
}
|
||||
namespace {
|
||||
|
||||
std::optional<BlockArgument> getBlockArgument(Region& body, unsigned argIdx) {
|
||||
@@ -238,6 +250,15 @@ void SpatScheduledCompute::getAsmBlockArgumentNames(Region& region, OpAsmSetValu
|
||||
setComputeAsmBlockArgumentNames(*this, region, setNameFn);
|
||||
}
|
||||
|
||||
SuccessorOperands SpatBlockYieldOp::getSuccessorOperands(unsigned index) {
|
||||
assert(index == 0 && "invalid successor index");
|
||||
return SuccessorOperands(getOutputsMutable());
|
||||
}
|
||||
|
||||
Block* SpatBlockYieldOp::getSuccessorForOperands(ArrayRef<Attribute>) {
|
||||
return getOperation()->getNumSuccessors() == 0 ? nullptr : getOperation()->getSuccessor(0);
|
||||
}
|
||||
|
||||
std::optional<BlockArgument> SpatGraphComputeBatch::getLaneArgument() { return getBlockArgument(getBody(), 0); }
|
||||
std::optional<BlockArgument> SpatGraphComputeBatch::getWeightArgument(unsigned idx) {
|
||||
return getBlockArgument(getBody(), 1 + idx);
|
||||
|
||||
@@ -30,6 +30,10 @@
|
||||
namespace onnx_mlir {
|
||||
namespace spatial {
|
||||
|
||||
mlir::RankedTensorType getGraphBatchPhysicalResultType(int64_t laneCount, mlir::RankedTensorType fragmentType);
|
||||
mlir::FailureOr<mlir::RankedTensorType>
|
||||
getGraphBatchFragmentType(mlir::RankedTensorType physicalType, int64_t expectedLaneCount);
|
||||
|
||||
bool isGraphComputeLike(mlir::Operation* op);
|
||||
bool isGraphBatchComputeLike(mlir::Operation* op);
|
||||
bool isScheduledComputeLike(mlir::Operation* op);
|
||||
|
||||
@@ -32,6 +32,14 @@ static IntegerAttr getI32Attr(OpAsmParser& parser, int32_t value) {
|
||||
return parser.getBuilder().getI32IntegerAttr(value);
|
||||
}
|
||||
|
||||
static ParseResult parseBareStringAttr(OpAsmParser& parser, StringAttr& attr) {
|
||||
StringRef value;
|
||||
if (parser.parseKeyword(&value))
|
||||
return failure();
|
||||
attr = parser.getBuilder().getStringAttr(value);
|
||||
return success();
|
||||
}
|
||||
|
||||
static void printBlockArgumentList(OpAsmPrinter& printer, ArrayRef<BlockArgument> arguments) {
|
||||
printer << "(";
|
||||
for (auto [index, argument] : llvm::enumerate(arguments)) {
|
||||
@@ -152,7 +160,7 @@ void printComputeLikeOp(ComputeOpTy op, OpAsmPrinter& printer) {
|
||||
printer << " -> ";
|
||||
printCompressedTypeSequence(printer, op.getResultTypes());
|
||||
printer << " ";
|
||||
printer.printRegion(op.getBody(), /*printEntryBlockArgs=*/false);
|
||||
printer.printRegion(op.getBody(), /*printEntryBlockArgs=*/!op.getBody().hasOneBlock());
|
||||
}
|
||||
|
||||
template <typename ComputeOpTy>
|
||||
@@ -282,7 +290,7 @@ void printComputeBatchLikeOp(ComputeBatchOpTy op, OpAsmPrinter& printer) {
|
||||
printer << " -> ";
|
||||
printCompressedTypeSequence(printer, op.getResultTypes());
|
||||
printer << " ";
|
||||
printer.printRegion(op.getBody(), /*printEntryBlockArgs=*/false);
|
||||
printer.printRegion(op.getBody(), /*printEntryBlockArgs=*/!op.getBody().hasOneBlock());
|
||||
}
|
||||
|
||||
template <typename ComputeBatchOpTy>
|
||||
@@ -399,6 +407,89 @@ ParseResult SpatYieldOp::parse(OpAsmParser& parser, OperationState& result) {
|
||||
return parser.resolveOperands(outputs, outputTypes, parser.getCurrentLocation(), result.operands);
|
||||
}
|
||||
|
||||
void SpatBlockYieldOp::print(OpAsmPrinter& printer) {
|
||||
printer << " ";
|
||||
printCompressedValueSequence(printer, getOutputs());
|
||||
if (getOperation()->getNumSuccessors() != 0) {
|
||||
printer << " next ";
|
||||
printer.printSuccessor(getOperation()->getSuccessor(0));
|
||||
}
|
||||
printer.printOptionalAttrDict((*this)->getAttrs());
|
||||
printer << " : ";
|
||||
printCompressedTypeSequence(printer, getOutputs().getTypes());
|
||||
}
|
||||
|
||||
ParseResult SpatBlockYieldOp::parse(OpAsmParser& parser, OperationState& result) {
|
||||
SmallVector<OpAsmParser::UnresolvedOperand> outputs;
|
||||
SmallVector<Type> outputTypes;
|
||||
Block* successor = nullptr;
|
||||
|
||||
OpAsmParser::UnresolvedOperand firstOutput;
|
||||
OptionalParseResult firstOutputResult = parser.parseOptionalOperand(firstOutput);
|
||||
if (firstOutputResult.has_value()) {
|
||||
if (failed(*firstOutputResult))
|
||||
return failure();
|
||||
if (parseCompressedOperandEntryWithFirst(parser, firstOutput, outputs))
|
||||
return failure();
|
||||
while (succeeded(parser.parseOptionalComma()))
|
||||
if (parseOneCompressedOperandEntry(parser, outputs))
|
||||
return failure();
|
||||
}
|
||||
|
||||
if (succeeded(parser.parseOptionalKeyword("next")) && parser.parseSuccessor(successor))
|
||||
return failure();
|
||||
|
||||
if (parser.parseOptionalAttrDict(result.attributes) || parser.parseColon()
|
||||
|| parseCompressedTypeSequence(parser, outputTypes, /*allowEmpty=*/true))
|
||||
return failure();
|
||||
|
||||
if (outputs.size() != outputTypes.size())
|
||||
return parser.emitError(parser.getCurrentLocation(), "number of outputs and output types must match");
|
||||
if (parser.resolveOperands(outputs, outputTypes, parser.getCurrentLocation(), result.operands))
|
||||
return failure();
|
||||
if (successor)
|
||||
result.addSuccessors(successor);
|
||||
return success();
|
||||
}
|
||||
|
||||
void SpatDeferredCommunicationOp::print(OpAsmPrinter& printer) {
|
||||
printer << " ";
|
||||
printCompressedValueSequence(printer, getSources());
|
||||
printer.printOptionalAttrDict((*this)->getAttrs());
|
||||
printer << " : ";
|
||||
printer.printFunctionalType(getSources().getTypes(), getOperation()->getResultTypes());
|
||||
printer << " ";
|
||||
printer.printRegion(getBody(), /*printEntryBlockArgs=*/false);
|
||||
}
|
||||
|
||||
ParseResult SpatDeferredCommunicationOp::parse(OpAsmParser& parser, OperationState& result) {
|
||||
SmallVector<OpAsmParser::UnresolvedOperand> sources;
|
||||
Type functionTypeStorage;
|
||||
|
||||
if (parseCompressedOperandSequence(parser, sources) || parser.parseOptionalAttrDict(result.attributes)
|
||||
|| parser.parseColon() || parser.parseType(functionTypeStorage))
|
||||
return failure();
|
||||
|
||||
auto functionType = dyn_cast<FunctionType>(functionTypeStorage);
|
||||
if (!functionType)
|
||||
return parser.emitError(parser.getCurrentLocation(), "expected deferred communication function type");
|
||||
if (sources.size() != functionType.getNumInputs())
|
||||
return parser.emitError(parser.getCurrentLocation(), "number of sources and source types must match");
|
||||
|
||||
if (parser.resolveOperands(sources, functionType.getInputs(), parser.getCurrentLocation(), result.operands))
|
||||
return failure();
|
||||
result.addTypes(functionType.getResults());
|
||||
|
||||
Region* body = result.addRegion();
|
||||
SmallVector<OpAsmParser::Argument> bodyArgs;
|
||||
for (Type type : functionType.getInputs()) {
|
||||
OpAsmParser::Argument argument;
|
||||
argument.type = type;
|
||||
bodyArgs.push_back(argument);
|
||||
}
|
||||
return parser.parseRegion(*body, bodyArgs);
|
||||
}
|
||||
|
||||
void SpatExtractRowsOp::print(OpAsmPrinter& printer) {
|
||||
printer << " ";
|
||||
printer.printOperand(getInput());
|
||||
@@ -466,6 +557,142 @@ ParseResult SpatConcatOp::parse(OpAsmParser& parser, OperationState& result) {
|
||||
return success();
|
||||
}
|
||||
|
||||
void SpatBlueprintOp::print(OpAsmPrinter& printer) {
|
||||
SmallVector<Value> operands {getInput()};
|
||||
llvm::append_range(operands, getFragments());
|
||||
|
||||
printer << " fragments";
|
||||
printCompressedValueList(printer, operands, ListDelimiter::Paren);
|
||||
printer << " layout " << getLogicalLayout();
|
||||
printer << " physical " << getPhysicalLayout();
|
||||
printer << " offsets ";
|
||||
printCompressedIntegerList(printer, getFragmentOffsets());
|
||||
printer << " sizes ";
|
||||
printCompressedIntegerList(printer, getFragmentSizes());
|
||||
printer << " map " << getIndexMap();
|
||||
if (std::optional<StringRef> mode = getMode())
|
||||
printer << " mode " << *mode;
|
||||
if (std::optional<ArrayRef<int64_t>> operandIndices = getFragmentOperandIndices()) {
|
||||
printer << " operandIndices ";
|
||||
printCompressedIntegerList(printer, *operandIndices);
|
||||
}
|
||||
if (std::optional<ArrayRef<int64_t>> sourceSlots = getFragmentSourceSlots()) {
|
||||
printer << " sourceSlots ";
|
||||
printCompressedIntegerList(printer, *sourceSlots);
|
||||
}
|
||||
if (std::optional<ArrayRef<int64_t>> sourceOffsets = getFragmentSourceOffsets()) {
|
||||
printer << " sourceOffsets ";
|
||||
printCompressedIntegerList(printer, *sourceOffsets);
|
||||
}
|
||||
if (std::optional<ArrayRef<int64_t>> strides = getFragmentStrides()) {
|
||||
printer << " strides ";
|
||||
printCompressedIntegerList(printer, *strides);
|
||||
}
|
||||
if (std::optional<StringRef> conflictPolicy = getConflictPolicy())
|
||||
printer << " conflict " << *conflictPolicy;
|
||||
if (std::optional<StringRef> coveragePolicy = getCoveragePolicy())
|
||||
printer << " coverage " << *coveragePolicy;
|
||||
|
||||
printer.printOptionalAttrDict((*this)->getAttrs(),
|
||||
{getLogicalLayoutAttrName().getValue(),
|
||||
getPhysicalLayoutAttrName().getValue(),
|
||||
getFragmentOffsetsAttrName().getValue(),
|
||||
getFragmentSizesAttrName().getValue(),
|
||||
getIndexMapAttrName().getValue(),
|
||||
getModeAttrName().getValue(),
|
||||
getFragmentOperandIndicesAttrName().getValue(),
|
||||
getFragmentSourceSlotsAttrName().getValue(),
|
||||
getFragmentSourceOffsetsAttrName().getValue(),
|
||||
getFragmentStridesAttrName().getValue(),
|
||||
getConflictPolicyAttrName().getValue(),
|
||||
getCoveragePolicyAttrName().getValue()});
|
||||
printer << " : ";
|
||||
printCompressedTypeList(printer, TypeRange(operands), ListDelimiter::Paren);
|
||||
printer << " -> ";
|
||||
printer.printType(getOutput().getType());
|
||||
}
|
||||
|
||||
ParseResult SpatBlueprintOp::parse(OpAsmParser& parser, OperationState& result) {
|
||||
SmallVector<OpAsmParser::UnresolvedOperand> operands;
|
||||
SmallVector<Type> operandTypes;
|
||||
Type outputType;
|
||||
StringAttr logicalLayout;
|
||||
StringAttr physicalLayout;
|
||||
StringAttr indexMap;
|
||||
StringAttr mode;
|
||||
StringAttr conflictPolicy;
|
||||
StringAttr coveragePolicy;
|
||||
SmallVector<int64_t> fragmentOffsets;
|
||||
SmallVector<int64_t> fragmentSizes;
|
||||
SmallVector<int64_t> fragmentOperandIndices;
|
||||
SmallVector<int64_t> fragmentSourceSlots;
|
||||
SmallVector<int64_t> fragmentSourceOffsets;
|
||||
SmallVector<int64_t> fragmentStrides;
|
||||
|
||||
if (parser.parseKeyword("fragments")
|
||||
|| parseCompressedOperandList(parser, ListDelimiter::Paren, operands)
|
||||
|| parser.parseKeyword("layout") || parseBareStringAttr(parser, logicalLayout)
|
||||
|| parser.parseKeyword("physical") || parseBareStringAttr(parser, physicalLayout)
|
||||
|| parser.parseKeyword("offsets") || parseCompressedIntegerList(parser, fragmentOffsets)
|
||||
|| parser.parseKeyword("sizes") || parseCompressedIntegerList(parser, fragmentSizes)
|
||||
|| parser.parseKeyword("map") || parseBareStringAttr(parser, indexMap))
|
||||
return failure();
|
||||
|
||||
if (succeeded(parser.parseOptionalKeyword("mode")) && parseBareStringAttr(parser, mode))
|
||||
return failure();
|
||||
if (succeeded(parser.parseOptionalKeyword("operandIndices"))
|
||||
&& parseCompressedIntegerList(parser, fragmentOperandIndices))
|
||||
return failure();
|
||||
if (succeeded(parser.parseOptionalKeyword("sourceSlots"))
|
||||
&& parseCompressedIntegerList(parser, fragmentSourceSlots))
|
||||
return failure();
|
||||
if (succeeded(parser.parseOptionalKeyword("sourceOffsets"))
|
||||
&& parseCompressedIntegerList(parser, fragmentSourceOffsets))
|
||||
return failure();
|
||||
if (succeeded(parser.parseOptionalKeyword("strides")) && parseCompressedIntegerList(parser, fragmentStrides))
|
||||
return failure();
|
||||
if (succeeded(parser.parseOptionalKeyword("conflict")) && parseBareStringAttr(parser, conflictPolicy))
|
||||
return failure();
|
||||
if (succeeded(parser.parseOptionalKeyword("coverage")) && parseBareStringAttr(parser, coveragePolicy))
|
||||
return failure();
|
||||
|
||||
if (parser.parseOptionalAttrDict(result.attributes) || parser.parseColon()
|
||||
|| parseCompressedRepeatedList(
|
||||
parser, ListDelimiter::Paren, operandTypes, [&](Type& type) { return parser.parseType(type); })
|
||||
|| parser.parseArrow() || parser.parseType(outputType))
|
||||
return failure();
|
||||
if (operands.empty())
|
||||
return parser.emitError(parser.getCurrentLocation(), "spat.blueprint requires at least one fragment operand");
|
||||
if (operands.size() != operandTypes.size())
|
||||
return parser.emitError(parser.getCurrentLocation(), "number of fragment operands and types must match");
|
||||
|
||||
auto& builder = parser.getBuilder();
|
||||
result.addAttribute("logicalLayout", logicalLayout);
|
||||
result.addAttribute("physicalLayout", physicalLayout);
|
||||
result.addAttribute("fragmentOffsets", builder.getDenseI64ArrayAttr(fragmentOffsets));
|
||||
result.addAttribute("fragmentSizes", builder.getDenseI64ArrayAttr(fragmentSizes));
|
||||
result.addAttribute("indexMap", indexMap);
|
||||
if (mode)
|
||||
result.addAttribute("mode", mode);
|
||||
if (!fragmentOperandIndices.empty())
|
||||
result.addAttribute("fragmentOperandIndices", builder.getDenseI64ArrayAttr(fragmentOperandIndices));
|
||||
if (!fragmentSourceSlots.empty())
|
||||
result.addAttribute("fragmentSourceSlots", builder.getDenseI64ArrayAttr(fragmentSourceSlots));
|
||||
if (!fragmentSourceOffsets.empty())
|
||||
result.addAttribute("fragmentSourceOffsets", builder.getDenseI64ArrayAttr(fragmentSourceOffsets));
|
||||
if (!fragmentStrides.empty())
|
||||
result.addAttribute("fragmentStrides", builder.getDenseI64ArrayAttr(fragmentStrides));
|
||||
if (conflictPolicy)
|
||||
result.addAttribute("conflictPolicy", conflictPolicy);
|
||||
if (coveragePolicy)
|
||||
result.addAttribute("coveragePolicy", coveragePolicy);
|
||||
|
||||
if (parser.resolveOperands(operands, operandTypes, parser.getCurrentLocation(), result.operands))
|
||||
return failure();
|
||||
result.addTypes(outputType);
|
||||
return success();
|
||||
}
|
||||
|
||||
void SpatGraphCompute::print(OpAsmPrinter& printer) { printComputeLikeOp(*this, printer); }
|
||||
ParseResult SpatGraphCompute::parse(OpAsmParser& parser, OperationState& result) {
|
||||
return parseComputeLikeOp<SpatGraphCompute>(parser, result);
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||
#include "mlir/IR/Block.h"
|
||||
#include "mlir/IR/IRMapping.h"
|
||||
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/Support/LogicalResult.h"
|
||||
|
||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
|
||||
using namespace mlir;
|
||||
@@ -40,5 +46,177 @@ LogicalResult SpatScheduledCompute::fold(FoldAdaptor adaptor, ::llvm::SmallVecto
|
||||
return foldComputeLike(*this, results);
|
||||
}
|
||||
|
||||
template <typename ScalarComputeOpTy>
|
||||
static ScalarComputeOpTy createEmptyScalarCompute(PatternRewriter& rewriter,
|
||||
Location loc,
|
||||
TypeRange resultTypes,
|
||||
ValueRange weights,
|
||||
ValueRange inputs) {
|
||||
auto computeOp = ScalarComputeOpTy::create(rewriter, loc, resultTypes, weights, inputs);
|
||||
SmallVector<Type> blockArgTypes;
|
||||
SmallVector<Location> blockArgLocs;
|
||||
blockArgTypes.reserve(weights.size() + inputs.size());
|
||||
blockArgLocs.reserve(weights.size() + inputs.size());
|
||||
for (Value weight : weights) {
|
||||
blockArgTypes.push_back(weight.getType());
|
||||
blockArgLocs.push_back(weight.getLoc());
|
||||
}
|
||||
for (Value input : inputs) {
|
||||
blockArgTypes.push_back(input.getType());
|
||||
blockArgLocs.push_back(input.getLoc());
|
||||
}
|
||||
rewriter.createBlock(&computeOp.getBody(), computeOp.getBody().end(), blockArgTypes, blockArgLocs);
|
||||
rewriter.setInsertionPointToStart(&computeOp.getBody().front());
|
||||
return computeOp;
|
||||
}
|
||||
|
||||
static SmallVector<OpFoldResult> remapMixedOffsets(ArrayRef<OpFoldResult> mixedOffsets, IRMapping& mapper) {
|
||||
SmallVector<OpFoldResult> remapped;
|
||||
remapped.reserve(mixedOffsets.size());
|
||||
for (OpFoldResult ofr : mixedOffsets) {
|
||||
if (auto value = dyn_cast<Value>(ofr))
|
||||
remapped.push_back(cast<Value>(mapper.lookupOrDefault(value)));
|
||||
else
|
||||
remapped.push_back(cast<Attribute>(ofr));
|
||||
}
|
||||
return remapped;
|
||||
}
|
||||
|
||||
static SmallVector<Value> createEmptyResults(PatternRewriter& rewriter, Location loc, TypeRange resultTypes) {
|
||||
SmallVector<Value> resultValues;
|
||||
resultValues.reserve(resultTypes.size());
|
||||
for (Type resultType : resultTypes) {
|
||||
auto tensorType = dyn_cast<RankedTensorType>(resultType);
|
||||
if (!tensorType || !tensorType.hasStaticShape())
|
||||
return {};
|
||||
resultValues.push_back(tensor::EmptyOp::create(rewriter, loc, tensorType.getShape(), tensorType.getElementType()));
|
||||
}
|
||||
return resultValues;
|
||||
}
|
||||
|
||||
template <typename ScalarComputeOpTy, typename ComputeBatchOpTy>
|
||||
static void copyCanonicalizedBatchAttrs(ScalarComputeOpTy compute, ComputeBatchOpTy batch, PatternRewriter& rewriter) {
|
||||
for (NamedAttribute attr : batch->getAttrs()) {
|
||||
if (attr.getName() == batch.getOperandSegmentSizesAttrName() || attr.getName() == batch.getLaneCountAttrName()
|
||||
|| attr.getName() == onnx_mlir::kCoreIdsAttrName)
|
||||
continue;
|
||||
compute->setAttr(attr.getName(), attr.getValue());
|
||||
}
|
||||
if constexpr (std::is_same_v<ComputeBatchOpTy, SpatScheduledComputeBatch>) {
|
||||
if (auto coreIds = batch->template getAttrOfType<DenseI32ArrayAttr>(onnx_mlir::kCoreIdsAttrName)) {
|
||||
assert(coreIds.size() == 1 && "single-lane scheduled compute_batch canonicalization expects exactly one core id");
|
||||
compute->setAttr(onnx_mlir::kCoreIdAttrName, rewriter.getI32IntegerAttr(coreIds.asArrayRef().front()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename ComputeBatchOpTy, typename ScalarComputeOpTy>
|
||||
struct CanonicalizeSingleLaneComputeBatchPattern : OpRewritePattern<ComputeBatchOpTy> {
|
||||
using OpRewritePattern<ComputeBatchOpTy>::OpRewritePattern;
|
||||
|
||||
LogicalResult matchAndRewrite(ComputeBatchOpTy compute, PatternRewriter& rewriter) const override {
|
||||
if (compute.getLaneCount() != 1)
|
||||
return rewriter.notifyMatchFailure(compute, "lane count is not 1");
|
||||
|
||||
Block& oldBlock = compute.getBody().front();
|
||||
auto oldLaneArg = compute.getLaneArgument();
|
||||
if (!oldLaneArg)
|
||||
return rewriter.notifyMatchFailure(compute, "missing compute_batch lane block argument");
|
||||
|
||||
rewriter.setInsertionPointAfter(compute);
|
||||
auto newCompute =
|
||||
createEmptyScalarCompute<ScalarComputeOpTy>(rewriter, compute.getLoc(), compute.getResultTypes(), compute.getWeights(), compute.getInputs());
|
||||
copyCanonicalizedBatchAttrs(newCompute, compute, rewriter);
|
||||
auto* newBlock = &newCompute.getBody().front();
|
||||
rewriter.setInsertionPointToStart(newBlock);
|
||||
|
||||
IRMapping mapper;
|
||||
Value zero = getOrCreateIndexConstant(rewriter, compute.getOperation(), 0);
|
||||
mapper.map(*oldLaneArg, zero);
|
||||
for (auto [index, weight] : llvm::enumerate(compute.getWeights())) {
|
||||
auto oldArg = compute.getWeightArgument(index);
|
||||
auto newArg = newCompute.getWeightArgument(index);
|
||||
if (!oldArg || !newArg)
|
||||
return rewriter.notifyMatchFailure(compute, "missing rewritten compute weight block argument");
|
||||
mapper.map(*oldArg, *newArg);
|
||||
}
|
||||
for (auto [index, input] : llvm::enumerate(compute.getInputs())) {
|
||||
auto oldArg = compute.getInputArgument(index);
|
||||
auto newArg = newCompute.getInputArgument(index);
|
||||
if (!oldArg || !newArg)
|
||||
return rewriter.notifyMatchFailure(compute, "missing rewritten compute input block argument");
|
||||
mapper.map(*oldArg, *newArg);
|
||||
}
|
||||
|
||||
SmallVector<Value> resultValues = createEmptyResults(rewriter, compute.getLoc(), compute.getResultTypes());
|
||||
if (resultValues.size() != compute.getNumResults())
|
||||
return rewriter.notifyMatchFailure(compute, "single-lane compute_batch canonicalization requires static ranked results");
|
||||
for (auto [index, resultValue] : llvm::enumerate(resultValues)) {
|
||||
auto oldOutputArg = compute.getOutputArgument(index);
|
||||
if (!oldOutputArg)
|
||||
return rewriter.notifyMatchFailure(compute, "missing compute_batch output block argument");
|
||||
mapper.map(*oldOutputArg, resultValue);
|
||||
}
|
||||
|
||||
auto oldInParallel = dyn_cast<SpatInParallelOp>(oldBlock.getTerminator());
|
||||
auto oldYield = dyn_cast<SpatYieldOp>(oldBlock.getTerminator());
|
||||
for (Operation& op : oldBlock.without_terminator())
|
||||
rewriter.clone(op, mapper);
|
||||
|
||||
if (oldYield) {
|
||||
SpatYieldOp::create(rewriter, oldYield.getLoc(), ValueRange {});
|
||||
rewriter.replaceOp(compute, newCompute.getResults());
|
||||
return success();
|
||||
}
|
||||
if (!oldInParallel)
|
||||
return rewriter.notifyMatchFailure(compute, "expected spat.in_parallel or empty spat.yield terminator");
|
||||
|
||||
DenseMap<BlockArgument, size_t> outputIndexByArg;
|
||||
for (size_t index = 0; index < compute.getNumResults(); ++index) {
|
||||
auto oldOutputArg = compute.getOutputArgument(index);
|
||||
if (!oldOutputArg)
|
||||
return rewriter.notifyMatchFailure(compute, "missing compute_batch output block argument");
|
||||
outputIndexByArg[*oldOutputArg] = index;
|
||||
}
|
||||
|
||||
for (Operation& op : oldInParallel.getRegion().front()) {
|
||||
auto insertSlice = dyn_cast<tensor::ParallelInsertSliceOp>(&op);
|
||||
if (!insertSlice)
|
||||
return rewriter.notifyMatchFailure(compute, "expected only tensor.parallel_insert_slice in spat.in_parallel");
|
||||
auto oldDest = dyn_cast<BlockArgument>(insertSlice.getDest());
|
||||
if (!oldDest)
|
||||
return rewriter.notifyMatchFailure(compute, "expected tensor.parallel_insert_slice destination to be a block argument");
|
||||
auto resultIndexIt = outputIndexByArg.find(oldDest);
|
||||
if (resultIndexIt == outputIndexByArg.end())
|
||||
return rewriter.notifyMatchFailure(compute, "unexpected tensor.parallel_insert_slice destination");
|
||||
size_t resultIndex = resultIndexIt->second;
|
||||
Value remappedSource = mapper.lookupOrDefault(insertSlice.getSource());
|
||||
auto remappedOffsets = remapMixedOffsets(insertSlice.getMixedOffsets(), mapper);
|
||||
auto remappedSizes = remapMixedOffsets(insertSlice.getMixedSizes(), mapper);
|
||||
auto remappedStrides = remapMixedOffsets(insertSlice.getMixedStrides(), mapper);
|
||||
resultValues[resultIndex] = tensor::InsertSliceOp::create(rewriter,
|
||||
insertSlice.getLoc(),
|
||||
remappedSource,
|
||||
resultValues[resultIndex],
|
||||
remappedOffsets,
|
||||
remappedSizes,
|
||||
remappedStrides)
|
||||
.getResult();
|
||||
}
|
||||
|
||||
SpatYieldOp::create(rewriter, oldInParallel.getLoc(), resultValues);
|
||||
rewriter.replaceOp(compute, newCompute.getResults());
|
||||
return success();
|
||||
}
|
||||
};
|
||||
|
||||
void SpatGraphComputeBatch::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) {
|
||||
results.add<CanonicalizeSingleLaneComputeBatchPattern<SpatGraphComputeBatch, SpatGraphCompute>>(context);
|
||||
}
|
||||
|
||||
void SpatScheduledComputeBatch::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) {
|
||||
results.add<CanonicalizeSingleLaneComputeBatchPattern<SpatScheduledComputeBatch, SpatScheduledCompute>>(context);
|
||||
}
|
||||
|
||||
} // namespace spatial
|
||||
} // namespace onnx_mlir
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "mlir/Dialect/Affine/IR/AffineOps.h"
|
||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||
#include "mlir/IR/AffineExpr.h"
|
||||
#include "mlir/IR/Block.h"
|
||||
@@ -59,6 +60,21 @@ static LogicalResult verifyStaticWeights(ComputeOpTy computeOp, StringRef kind)
|
||||
return success();
|
||||
}
|
||||
|
||||
static bool isStaticScfForInductionVar(Value value) {
|
||||
auto blockArg = dyn_cast<BlockArgument>(value);
|
||||
if (!blockArg)
|
||||
return false;
|
||||
|
||||
auto loop = dyn_cast_or_null<scf::ForOp>(blockArg.getOwner()->getParentOp());
|
||||
if (!loop || loop.getInductionVar() != value)
|
||||
return false;
|
||||
|
||||
std::optional<int64_t> lowerBound = matchConstantIndexValue(loop.getLowerBound());
|
||||
std::optional<int64_t> upperBound = matchConstantIndexValue(loop.getUpperBound());
|
||||
std::optional<int64_t> step = matchConstantIndexValue(loop.getStep());
|
||||
return lowerBound && upperBound && step && *step > 0 && *upperBound >= *lowerBound;
|
||||
}
|
||||
|
||||
static bool isStaticIndexExpr(Value value) {
|
||||
if (matchConstantIndexValue(value))
|
||||
return true;
|
||||
@@ -80,7 +96,7 @@ static bool isStaticIndexExpr(Value value) {
|
||||
}
|
||||
|
||||
static bool isSupportedLaneOffsetExpr(Value value, BlockArgument laneArg) {
|
||||
if (value == laneArg || isStaticIndexExpr(value))
|
||||
if (value == laneArg || isStaticIndexExpr(value) || isStaticScfForInductionVar(value))
|
||||
return true;
|
||||
|
||||
auto affineApply = value.getDefiningOp<affine::AffineApplyOp>();
|
||||
@@ -176,12 +192,18 @@ static bool isConstantExternalValue(Value value) {
|
||||
return definingOp && definingOp->hasTrait<OpTrait::ConstantLike>();
|
||||
}
|
||||
|
||||
static bool isRecordedDeferredCommunicationSource(Operation* op, Value value) {
|
||||
auto transfer = dyn_cast<SpatDeferredCommunicationOp>(op);
|
||||
return transfer && llvm::is_contained(transfer.getSources(), value);
|
||||
}
|
||||
|
||||
static LogicalResult verifyOnlyConstantExternalValues(Operation* ownerOp, Region& region, StringRef kind) {
|
||||
bool hasFailure = false;
|
||||
region.walk([&](Operation* op) {
|
||||
for (OpOperand& operand : op->getOpOperands()) {
|
||||
Value value = operand.get();
|
||||
if (isDefinedInsideRegion(value, region) || isConstantExternalValue(value))
|
||||
if (isDefinedInsideRegion(value, region) || isConstantExternalValue(value)
|
||||
|| isRecordedDeferredCommunicationSource(op, value))
|
||||
continue;
|
||||
|
||||
InFlightDiagnostic diagnostic =
|
||||
@@ -203,8 +225,35 @@ static LogicalResult verifyOnlyConstantExternalValues(Operation* ownerOp, Region
|
||||
return success(!hasFailure);
|
||||
}
|
||||
|
||||
static LogicalResult verifyYieldTypes(Operation* op, Region& region, TypeRange resultTypes, StringRef kind) {
|
||||
if (region.empty())
|
||||
return op->emitOpError() << kind << " requires a body block";
|
||||
Block& block = region.front();
|
||||
auto yield = dyn_cast_or_null<SpatYieldOp>(block.getTerminator());
|
||||
if (!yield)
|
||||
return op->emitOpError() << kind << " body must terminate with spat.yield";
|
||||
if (yield.getOutputs().size() != resultTypes.size())
|
||||
return op->emitOpError() << kind << " yield operand count must match result count";
|
||||
for (auto [yieldType, resultType] : llvm::zip(yield.getOutputs().getTypes(), resultTypes))
|
||||
if (yieldType != resultType)
|
||||
return op->emitOpError() << kind << " yield operand types must match result types";
|
||||
return success();
|
||||
}
|
||||
|
||||
static LogicalResult verifyRegionArguments(Operation* op, Region& region, ValueRange operands, StringRef kind) {
|
||||
if (region.empty())
|
||||
return op->emitOpError() << kind << " requires a body block";
|
||||
Block& block = region.front();
|
||||
if (block.getNumArguments() != operands.size())
|
||||
return op->emitOpError() << kind << " body argument count must match operand count";
|
||||
for (auto [arg, operand] : llvm::zip(block.getArguments(), operands))
|
||||
if (arg.getType() != operand.getType())
|
||||
return op->emitOpError() << kind << " body argument types must match operand types";
|
||||
return success();
|
||||
}
|
||||
|
||||
template <typename ComputeBatchOpTy>
|
||||
static LogicalResult verifyBatchBody(ComputeBatchOpTy batchOp, Block& block) {
|
||||
static LogicalResult verifyBatchBody(ComputeBatchOpTy batchOp, Block& block, bool verifyLaneSliceOffsets = true) {
|
||||
if (batchOp.getNumResults() == 0) {
|
||||
auto yieldOp = dyn_cast_or_null<SpatYieldOp>(block.getTerminator());
|
||||
if (!yieldOp)
|
||||
@@ -219,11 +268,12 @@ static LogicalResult verifyBatchBody(ComputeBatchOpTy batchOp, Block& block) {
|
||||
auto laneArg = batchOp.getLaneArgument();
|
||||
if (!laneArg)
|
||||
return batchOp.emitError("compute_batch body must have a lane block argument");
|
||||
for (auto& bodyOp : block) {
|
||||
if (auto extractSlice = dyn_cast<tensor::ExtractSliceOp>(&bodyOp))
|
||||
if (failed(verifyStaticUnitStrideExtractSliceOp(extractSlice, *laneArg, "tensor.extract_slice")))
|
||||
return failure();
|
||||
}
|
||||
if (verifyLaneSliceOffsets)
|
||||
for (auto& bodyOp : block) {
|
||||
if (auto extractSlice = dyn_cast<tensor::ExtractSliceOp>(&bodyOp))
|
||||
if (failed(verifyStaticUnitStrideExtractSliceOp(extractSlice, *laneArg, "tensor.extract_slice")))
|
||||
return failure();
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
@@ -436,10 +486,43 @@ LogicalResult SpatReluPlanOp::verify() {
|
||||
return success();
|
||||
}
|
||||
|
||||
LogicalResult SpatReconciliatorOp::verify() {
|
||||
LogicalResult SpatBiasAddPlanOp::verify() {
|
||||
if (failed(verifyPlanTensorTypes(getOperation(), getInput(), getOutput(), "spat.bias_add_plan")))
|
||||
return failure();
|
||||
if (!isKnownLogicalLayout(getLogicalLayout()))
|
||||
return emitError("requires a known logical layout");
|
||||
|
||||
auto inputType = dyn_cast<RankedTensorType>(getInput().getType());
|
||||
auto biasType = dyn_cast<RankedTensorType>(getBias().getType());
|
||||
auto outputType = dyn_cast<RankedTensorType>(getOutput().getType());
|
||||
if (!inputType || !biasType || !outputType)
|
||||
return emitError("requires ranked tensor input, bias, and output");
|
||||
if (!inputType.hasStaticShape() || !biasType.hasStaticShape() || !outputType.hasStaticShape())
|
||||
return emitError("requires static tensor input, bias, and output");
|
||||
if (inputType != outputType)
|
||||
return emitError("requires matching input and output tensor types");
|
||||
if (outputType.getRank() != 4)
|
||||
return emitError("requires rank-4 input/output tensors");
|
||||
if (getLogicalLayout() != "nchw")
|
||||
return emitError("requires logical layout \"nchw\"");
|
||||
if (biasType.getElementType() != outputType.getElementType())
|
||||
return emitError("requires bias element type to match the output element type");
|
||||
|
||||
ArrayRef<int64_t> biasShape = biasType.getShape();
|
||||
const int64_t channels = outputType.getDimSize(1);
|
||||
const bool supported = biasShape.empty() || (biasShape.size() == 1 && biasShape[0] == channels)
|
||||
|| (biasShape.size() == 2 && biasShape[0] == 1 && biasShape[1] == channels)
|
||||
|| (biasShape.size() == 4 && biasShape[0] == 1 && biasShape[1] == channels
|
||||
&& biasShape[2] == 1 && biasShape[3] == 1);
|
||||
if (!supported)
|
||||
return emitError("requires scalar or per-channel bias broadcastable over NCHW");
|
||||
return success();
|
||||
}
|
||||
|
||||
LogicalResult SpatBlueprintOp::verify() {
|
||||
auto modeAttr = getModeAttr();
|
||||
bool isFragmentAssembly = modeAttr && modeAttr.getValue() == "fragment_assembly";
|
||||
if (!isFragmentAssembly && failed(verifyPlanTensorTypes(getOperation(), getInput(), getOutput(), "spat.reconciliator")))
|
||||
if (!isFragmentAssembly && failed(verifyPlanTensorTypes(getOperation(), getInput(), getOutput(), "spat.blueprint")))
|
||||
return failure();
|
||||
if (!isKnownLogicalLayout(getLogicalLayout()))
|
||||
return emitError("requires a known logical layout");
|
||||
@@ -482,28 +565,41 @@ LogicalResult SpatReconciliatorOp::verify() {
|
||||
if (failed(verifyBoundsOnly({})))
|
||||
return failure();
|
||||
if (!getFragments().empty())
|
||||
return emitError("legacy reconciliator does not accept extra fragment operands");
|
||||
if (getFragmentStridesAttr() || getConflictPolicyAttr() || getCoveragePolicyAttr())
|
||||
return emitError("legacy reconciliator does not accept fragment assembly attributes");
|
||||
return emitError("legacy blueprint does not accept extra fragment operands");
|
||||
if (getFragmentSourceOffsetsAttr() || getFragmentStridesAttr() || getConflictPolicyAttr()
|
||||
|| getCoveragePolicyAttr())
|
||||
return emitError("legacy blueprint does not accept fragment assembly attributes");
|
||||
return success();
|
||||
}
|
||||
|
||||
auto stridesAttr = getFragmentStridesAttr();
|
||||
auto operandIndicesAttr = getFragmentOperandIndicesAttr();
|
||||
auto sourceSlotsAttr = getFragmentSourceSlotsAttr();
|
||||
auto sourceOffsetsAttr = getFragmentSourceOffsetsAttr();
|
||||
if (!operandIndicesAttr)
|
||||
return emitError("fragment assembly reconciliator 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)
|
||||
return emitError("fragment assembly blueprint requires fragment source offsets");
|
||||
if (!stridesAttr)
|
||||
return emitError("fragment assembly reconciliator requires fragment strides");
|
||||
return emitError("fragment assembly blueprint requires fragment strides");
|
||||
ArrayRef<int64_t> operandIndices = operandIndicesAttr.asArrayRef();
|
||||
ArrayRef<int64_t> sourceSlots = sourceSlotsAttr.asArrayRef();
|
||||
ArrayRef<int64_t> sourceOffsets = sourceOffsetsAttr.asArrayRef();
|
||||
ArrayRef<int64_t> strides = stridesAttr.asArrayRef();
|
||||
if (strides.size() != offsets.size())
|
||||
return emitError("fragment stride and offset arrays must have the same length");
|
||||
if (sourceOffsets.size() != operandIndices.size())
|
||||
return emitError("fragment source offset count must match fragment operand index count");
|
||||
if (sourceSlots.size() != operandIndices.size())
|
||||
return emitError("fragment source slot count must match fragment operand index count");
|
||||
if (!getConflictPolicyAttr() || !getCoveragePolicyAttr())
|
||||
return emitError("fragment assembly reconciliator requires conflict and coverage policies");
|
||||
return emitError("fragment assembly blueprint requires conflict and coverage policies");
|
||||
if (getConflictPolicy() != "disjoint")
|
||||
return emitError("fragment assembly reconciliator currently supports only conflict_policy=\"disjoint\"");
|
||||
return emitError("fragment assembly blueprint currently supports only conflict_policy=\"disjoint\"");
|
||||
if (getCoveragePolicy() != "complete" && getCoveragePolicy() != "partial")
|
||||
return emitError("fragment assembly reconciliator coverage_policy must be \"complete\" or \"partial\"");
|
||||
return emitError("fragment assembly blueprint coverage_policy must be \"complete\" or \"partial\"");
|
||||
|
||||
SmallVector<Value> operands;
|
||||
operands.push_back(getInput());
|
||||
@@ -511,7 +607,7 @@ LogicalResult SpatReconciliatorOp::verify() {
|
||||
int64_t operandCount = static_cast<int64_t>(operands.size());
|
||||
int64_t fragmentCount = static_cast<int64_t>(operandIndices.size());
|
||||
if (operandCount == 0)
|
||||
return emitError("fragment assembly reconciliator requires at least one operand");
|
||||
return emitError("fragment assembly blueprint requires at least one operand");
|
||||
if (static_cast<int64_t>(offsets.size()) != fragmentCount * rank)
|
||||
return emitError("fragment assembly metadata count must match operand count * result rank");
|
||||
if (failed(verifyBoundsOnly(strides)))
|
||||
@@ -519,17 +615,32 @@ LogicalResult SpatReconciliatorOp::verify() {
|
||||
|
||||
SmallVector<std::pair<SmallVector<int64_t, 4>, SmallVector<int64_t, 4>>, 8> slices;
|
||||
slices.reserve(static_cast<size_t>(fragmentCount));
|
||||
SmallVector<SmallVector<SmallVector<int64_t, 4>, 4>, 8> sizesByOperand(static_cast<size_t>(operandCount));
|
||||
SmallVector<int64_t, 8> fragmentCountsByOperand(static_cast<size_t>(operandCount), 0);
|
||||
auto expandFlatElementIndex = [](int64_t flatIndex, ArrayRef<int64_t> shape) {
|
||||
SmallVector<int64_t, 4> indices(shape.size(), 0);
|
||||
for (int64_t dim = static_cast<int64_t>(shape.size()) - 1; dim >= 0; --dim) {
|
||||
indices[dim] = flatIndex % shape[dim];
|
||||
flatIndex /= shape[dim];
|
||||
}
|
||||
return indices;
|
||||
};
|
||||
for (int64_t fragmentIndex = 0; fragmentIndex < fragmentCount; ++fragmentIndex) {
|
||||
int64_t operandIndex = operandIndices[fragmentIndex];
|
||||
if (operandIndex < 0 || operandIndex >= operandCount)
|
||||
return emitError("fragment assembly operand index is out of range");
|
||||
if (sourceSlots[fragmentIndex] < 0)
|
||||
return emitError("fragment assembly physical source slot must be nonnegative");
|
||||
if (sourceOffsets[fragmentIndex] < 0)
|
||||
return emitError("fragment assembly source offsets must be nonnegative");
|
||||
|
||||
auto operandType = dyn_cast<RankedTensorType>(operands[operandIndex].getType());
|
||||
if (!operandType || !operandType.hasStaticShape())
|
||||
return emitError("fragment assembly reconciliator requires static ranked tensor operands");
|
||||
if (operandType.getRank() != rank)
|
||||
return emitError("fragment assembly reconciliator requires operand/result rank match");
|
||||
return emitError("fragment assembly blueprint requires static ranked tensor operands");
|
||||
if (operandType.getRank() != rank + 1)
|
||||
return emitError("fragment assembly physical operand must have one leading source-slot dimension");
|
||||
if (sourceSlots[fragmentIndex] >= operandType.getDimSize(0))
|
||||
return emitError("fragment assembly physical source slot is out of range");
|
||||
auto fragmentType = RankedTensorType::get(operandType.getShape().drop_front(), operandType.getElementType(), operandType.getEncoding());
|
||||
|
||||
SmallVector<int64_t, 4> fragmentOffsets;
|
||||
SmallVector<int64_t, 4> fragmentSizes;
|
||||
@@ -541,7 +652,17 @@ LogicalResult SpatReconciliatorOp::verify() {
|
||||
fragmentSizes.push_back(sizes[flatIndex]);
|
||||
}
|
||||
|
||||
sizesByOperand[static_cast<size_t>(operandIndex)].push_back(fragmentSizes);
|
||||
++fragmentCountsByOperand[static_cast<size_t>(operandIndex)];
|
||||
int64_t fragmentElements = 1;
|
||||
for (int64_t dim = 0; dim < rank; ++dim)
|
||||
fragmentElements *= fragmentSizes[dim];
|
||||
if (sourceOffsets[fragmentIndex] + fragmentElements > fragmentType.getNumElements())
|
||||
return emitError("fragment assembly source offset exceeds the selected physical fragment bounds");
|
||||
SmallVector<int64_t, 4> sourceSliceOffsets =
|
||||
expandFlatElementIndex(sourceOffsets[fragmentIndex], fragmentType.getShape());
|
||||
for (int64_t dim = 0; dim < rank; ++dim)
|
||||
if (sourceSliceOffsets[dim] + fragmentSizes[dim] > fragmentType.getDimSize(dim))
|
||||
return emitError("fragment assembly source offset must describe a valid unit-stride slice");
|
||||
|
||||
for (const auto& [existingOffsets, existingSizes] : slices) {
|
||||
bool overlaps = true;
|
||||
@@ -556,34 +677,14 @@ LogicalResult SpatReconciliatorOp::verify() {
|
||||
}
|
||||
}
|
||||
if (overlaps)
|
||||
return emitError("fragment assembly reconciliator requires disjoint static slices");
|
||||
return emitError("fragment assembly blueprint requires disjoint static slices");
|
||||
}
|
||||
slices.push_back({std::move(fragmentOffsets), std::move(fragmentSizes)});
|
||||
}
|
||||
|
||||
for (int64_t operandIndex = 0; operandIndex < operandCount; ++operandIndex) {
|
||||
if (sizesByOperand[static_cast<size_t>(operandIndex)].empty())
|
||||
return emitError("fragment assembly reconciliator requires every operand to contribute at least one fragment");
|
||||
|
||||
auto operandType = cast<RankedTensorType>(operands[operandIndex].getType());
|
||||
ArrayRef<int64_t> operandShape = operandType.getShape();
|
||||
auto& fragmentShapes = sizesByOperand[static_cast<size_t>(operandIndex)];
|
||||
if (fragmentShapes.size() == 1) {
|
||||
if (!llvm::equal(operandShape, fragmentShapes.front()))
|
||||
return emitError("single-fragment reconciliator operand shape must match declared fragment size");
|
||||
continue;
|
||||
}
|
||||
|
||||
ArrayRef<int64_t> fragmentShape = fragmentShapes.front();
|
||||
for (ArrayRef<int64_t> otherShape : fragmentShapes)
|
||||
if (!llvm::equal(fragmentShape, otherShape))
|
||||
return emitError("packed reconciliator operand requires equal fragment sizes per operand");
|
||||
if (llvm::equal(operandShape, fragmentShape))
|
||||
continue;
|
||||
if (!llvm::equal(operandShape.drop_front(), fragmentShape.drop_front()))
|
||||
return emitError("packed reconciliator operand must match fragment shape on non-packed dimensions");
|
||||
if (operandShape.front() != static_cast<int64_t>(fragmentShapes.size()) * fragmentShape.front())
|
||||
return emitError("packed reconciliator operand first dimension must equal fragment_count * fragment_size");
|
||||
if (fragmentCountsByOperand[static_cast<size_t>(operandIndex)] == 0)
|
||||
return emitError("fragment assembly blueprint requires every operand to contribute at least one fragment");
|
||||
}
|
||||
|
||||
if (getCoveragePolicy() == "complete") {
|
||||
@@ -623,7 +724,9 @@ LogicalResult verifyComputeResultsUses(Operation* op) {
|
||||
if (!isAnySpatialComputeLike(op))
|
||||
return op->emitError("verifyComputeResultUses: op is not a Spatial compute-like operation");
|
||||
if (!llvm::all_of(op->getResults(), [](Value result) {
|
||||
return llvm::all_of(result.getUsers(), [](Operation* op) {
|
||||
return llvm::all_of(result.getUsers(), [result](Operation* op) {
|
||||
if (isRecordedDeferredCommunicationSource(op, result))
|
||||
return true;
|
||||
return !isAnySpatialComputeLike(op->getParentOp());
|
||||
});
|
||||
})) {
|
||||
@@ -634,57 +737,68 @@ LogicalResult verifyComputeResultsUses(Operation* op) {
|
||||
|
||||
template <typename ComputeOpTy>
|
||||
LogicalResult verifyComputeLikeOp(ComputeOpTy compute, StringRef opName) {
|
||||
auto& block = compute.getBody().front();
|
||||
unsigned expectedArgCount = compute.getWeights().size() + compute.getInputs().size();
|
||||
if (block.getNumArguments() != expectedArgCount)
|
||||
return compute.emitOpError("compute body must have weight and input block arguments");
|
||||
bool isScheduled = isa<SpatScheduledCompute>(compute.getOperation());
|
||||
if (compute.getBody().empty())
|
||||
return compute.emitOpError("compute body must have at least one block");
|
||||
|
||||
for (auto [weightIndex, weight] : llvm::enumerate(compute.getWeights())) {
|
||||
auto blockArg = compute.getWeightArgument(weightIndex);
|
||||
if (!blockArg || blockArg->getType() != weight.getType())
|
||||
return compute.emitOpError("compute weight block argument types must match weight operand types exactly");
|
||||
}
|
||||
for (auto [inputIndex, input] : llvm::enumerate(compute.getInputs())) {
|
||||
auto blockArg = compute.getInputArgument(inputIndex);
|
||||
if (!blockArg || blockArg->getType() != input.getType())
|
||||
return compute.emitOpError("compute input block argument types must match input operand types exactly");
|
||||
}
|
||||
SmallVector<Type> yieldedTypes;
|
||||
for (Block& block : compute.getBody()) {
|
||||
if ((!isScheduled && block.getNumArguments() != expectedArgCount)
|
||||
|| (isScheduled && block.getNumArguments() < expectedArgCount))
|
||||
return compute.emitOpError("compute body must have weight and input block arguments");
|
||||
|
||||
if (block.mightHaveTerminator()) {
|
||||
auto yieldOp = dyn_cast_or_null<SpatYieldOp>(block.getTerminator());
|
||||
if (!yieldOp)
|
||||
for (auto [weightIndex, weight] : llvm::enumerate(compute.getWeights()))
|
||||
if (block.getArgument(weightIndex).getType() != weight.getType())
|
||||
return compute.emitOpError("compute weight block argument types must match weight operand types exactly");
|
||||
for (auto [inputIndex, input] : llvm::enumerate(compute.getInputs()))
|
||||
if (block.getArgument(compute.getWeights().size() + inputIndex).getType() != input.getType())
|
||||
return compute.emitOpError("compute input block argument types must match input operand types exactly");
|
||||
|
||||
Operation* terminator = block.getTerminator();
|
||||
if (auto yieldOp = dyn_cast_or_null<SpatYieldOp>(terminator)) {
|
||||
auto realized = compute->template getAttrOfType<BoolAttr>("scheduled.realized");
|
||||
if (isScheduled && (!realized || !realized.getValue() || !compute.getBody().hasOneBlock()))
|
||||
return compute.emitOpError("scheduled compute blocks must terminate with spat.block_yield");
|
||||
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");
|
||||
if (blockYield->getNumSuccessors() == 0)
|
||||
llvm::append_range(yieldedTypes, blockYield->getOperandTypes());
|
||||
}
|
||||
|
||||
auto resultTypes = compute.getResultTypes();
|
||||
auto yieldTypes = yieldOp->getOperandTypes();
|
||||
if (resultTypes.size() != yieldTypes.size())
|
||||
return compute.emitOpError("ComputeOp must have same number of results as yieldOp operands");
|
||||
auto resultTypes = compute.getResultTypes();
|
||||
if (resultTypes.size() != yieldedTypes.size())
|
||||
return compute.emitOpError("ComputeOp must have same number of results as yielded operands");
|
||||
|
||||
for (auto it : llvm::reverse(llvm::zip(resultTypes, yieldTypes))) {
|
||||
auto resultType = std::get<0>(it);
|
||||
auto yieldType = std::get<1>(it);
|
||||
for (auto it : llvm::reverse(llvm::zip(resultTypes, yieldedTypes))) {
|
||||
auto resultType = std::get<0>(it);
|
||||
auto yieldType = std::get<1>(it);
|
||||
|
||||
if (resultType != yieldType || failed(verifyCompatibleShape(resultType, yieldType)))
|
||||
return compute.emitOpError("ComputeOp output must be of the same type as yieldOp operand");
|
||||
if (resultType != yieldType || failed(verifyCompatibleShape(resultType, yieldType)))
|
||||
return compute.emitOpError("ComputeOp output must be of the same type as yieldOp operand");
|
||||
|
||||
if (auto resultRankedType = dyn_cast<RankedTensorType>(resultType)) {
|
||||
if (auto yieldRankedType = dyn_cast<RankedTensorType>(yieldType)) {
|
||||
if (resultRankedType.getEncoding() != yieldRankedType.getEncoding())
|
||||
return compute.emitOpError("ComputeOp output must have the same encoding as yieldOp operand");
|
||||
}
|
||||
else {
|
||||
if (auto resultRankedType = dyn_cast<RankedTensorType>(resultType)) {
|
||||
if (auto yieldRankedType = dyn_cast<RankedTensorType>(yieldType)) {
|
||||
if (resultRankedType.getEncoding() != yieldRankedType.getEncoding())
|
||||
return compute.emitOpError("ComputeOp output has an encoding while yieldOp operand does not have one");
|
||||
}
|
||||
}
|
||||
else if (dyn_cast<RankedTensorType>(yieldType)) {
|
||||
return compute.emitOpError("ComputeOp output must not have an encoding if yieldOp operand has one");
|
||||
else {
|
||||
return compute.emitOpError("ComputeOp output must have the same encoding as yieldOp operand");
|
||||
}
|
||||
}
|
||||
else if (dyn_cast<RankedTensorType>(yieldType)) {
|
||||
return compute.emitOpError("ComputeOp output must not have an encoding if yieldOp operand has one");
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned inputIndex = 0; inputIndex < compute.getInputs().size(); ++inputIndex)
|
||||
if (auto inputArg = compute.getInputArgument(inputIndex); !inputArg || inputArg->use_empty())
|
||||
return compute.emitOpError("ComputeOp block argument is not used");
|
||||
if (compute.getBody().hasOneBlock())
|
||||
for (unsigned inputIndex = 0; inputIndex < compute.getInputs().size(); ++inputIndex)
|
||||
if (auto inputArg = compute.getInputArgument(inputIndex); !inputArg || inputArg->use_empty())
|
||||
return compute.emitOpError("ComputeOp block argument is not used");
|
||||
if (failed(verifyStaticWeights(compute, opName)))
|
||||
return failure();
|
||||
if (failed(verifyOnlyConstantExternalValues(compute.getOperation(), compute.getBody(), opName)))
|
||||
@@ -698,6 +812,41 @@ LogicalResult SpatGraphCompute::verify() { return verifyComputeLikeOp(*this, "sp
|
||||
|
||||
LogicalResult SpatScheduledCompute::verify() { return verifyComputeLikeOp(*this, "spat.scheduled_compute"); }
|
||||
|
||||
LogicalResult SpatBlockYieldOp::verify() {
|
||||
if (getOperation()->getNumSuccessors() > 1)
|
||||
return emitOpError("may target at most one next scheduled block");
|
||||
Operation* parent = getOperation()->getParentOp();
|
||||
if (!isa_and_nonnull<SpatScheduledCompute>(parent))
|
||||
return emitOpError("expected spat.scheduled_compute parent");
|
||||
if (getOperation()->getNumSuccessors() == 1) {
|
||||
Block* next = getOperation()->getSuccessor(0);
|
||||
if (getOperation()->getNumOperands() != next->getNumArguments())
|
||||
return emitOpError("successor operand count must match next block argument count");
|
||||
for (auto [operand, argument] : llvm::zip(getOperation()->getOperands(), next->getArguments()))
|
||||
if (operand.getType() != argument.getType())
|
||||
return emitOpError("successor operand types must match next block argument types");
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
LogicalResult SpatDeferredCommunicationOp::verify() {
|
||||
if (getSources().empty())
|
||||
return emitOpError("requires at least one source");
|
||||
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 (failed(verifyRegionArguments(getOperation(), getBody(), getSources(), "spat.deferred_communication")))
|
||||
return failure();
|
||||
return verifyYieldTypes(getOperation(), getBody(), getOperation()->getResultTypes(), "spat.deferred_communication");
|
||||
}
|
||||
|
||||
template <typename ComputeBatchOpTy>
|
||||
LogicalResult verifyComputeBatchLikeOp(ComputeBatchOpTy batch, StringRef opName) {
|
||||
int32_t count = batch.getLaneCount();
|
||||
@@ -720,30 +869,33 @@ LogicalResult verifyComputeBatchLikeOp(ComputeBatchOpTy batch, StringRef opName)
|
||||
return batch.emitOpError("compute_batch coreIds values must be unique");
|
||||
}
|
||||
|
||||
Block& block = batch.getBody().front();
|
||||
if (block.getNumArguments() == 0)
|
||||
return batch.emitOpError("compute_batch body must have exactly one lane block argument");
|
||||
unsigned expectedArgCount = 1 + batch.getWeights().size() + batch.getInputs().size() + batch.getNumResults();
|
||||
if (block.getNumArguments() != expectedArgCount)
|
||||
return batch.emitOpError("compute_batch body block arguments must match lane, weight, input, and output operands/results");
|
||||
auto laneArg = batch.getLaneArgument();
|
||||
if (!laneArg || !laneArg->getType().isIndex())
|
||||
return batch.emitOpError("compute_batch first block argument must have index type");
|
||||
if (batch.getBody().empty())
|
||||
return batch.emitOpError("compute_batch body must have at least one block");
|
||||
|
||||
for (auto [weightIndex, weight] : llvm::enumerate(batch.getWeights())) {
|
||||
auto blockArg = batch.getWeightArgument(weightIndex);
|
||||
if (!blockArg || blockArg->getType() != weight.getType())
|
||||
return batch.emitOpError("compute_batch weight block argument types must match weight operand types exactly");
|
||||
}
|
||||
for (auto [inputIndex, input] : llvm::enumerate(batch.getInputs())) {
|
||||
auto blockArg = batch.getInputArgument(inputIndex);
|
||||
if (!blockArg || blockArg->getType() != input.getType())
|
||||
return batch.emitOpError("compute_batch input block argument types must match input operand types exactly");
|
||||
}
|
||||
for (auto [resultIndex, resultType] : llvm::enumerate(batch.getResultTypes())) {
|
||||
auto blockArg = batch.getOutputArgument(resultIndex);
|
||||
if (!blockArg || blockArg->getType() != resultType)
|
||||
return batch.emitOpError("compute_batch output block argument types must match result types exactly");
|
||||
unsigned expectedArgCount = 1 + batch.getWeights().size() + batch.getInputs().size() + batch.getNumResults();
|
||||
bool verifyLaneSliceOffsets = !isa<SpatScheduledComputeBatch>(batch.getOperation());
|
||||
for (Block& block : batch.getBody()) {
|
||||
if (block.getNumArguments() == 0)
|
||||
return batch.emitOpError("compute_batch body must have exactly one lane block argument");
|
||||
if (block.getNumArguments() != expectedArgCount)
|
||||
return batch.emitOpError(
|
||||
"compute_batch body block arguments must match lane, weight, input, and output operands/results");
|
||||
if (!block.getArgument(0).getType().isIndex())
|
||||
return batch.emitOpError("compute_batch first block argument must have index type");
|
||||
|
||||
for (auto [weightIndex, weight] : llvm::enumerate(batch.getWeights()))
|
||||
if (block.getArgument(1 + weightIndex).getType() != weight.getType())
|
||||
return batch.emitOpError("compute_batch weight block argument types must match weight operand types exactly");
|
||||
for (auto [inputIndex, input] : llvm::enumerate(batch.getInputs()))
|
||||
if (block.getArgument(1 + batch.getWeights().size() + inputIndex).getType() != input.getType())
|
||||
return batch.emitOpError("compute_batch input block argument types must match input operand types exactly");
|
||||
for (auto [resultIndex, resultType] : llvm::enumerate(batch.getResultTypes()))
|
||||
if (block.getArgument(1 + batch.getWeights().size() + batch.getInputs().size() + resultIndex).getType()
|
||||
!= resultType)
|
||||
return batch.emitOpError("compute_batch output block argument types must match result types exactly");
|
||||
|
||||
if (failed(verifyBatchBody(batch, block, verifyLaneSliceOffsets)))
|
||||
return failure();
|
||||
}
|
||||
|
||||
if (failed(verifyComputeResultsUses(batch.getOperation())))
|
||||
@@ -752,7 +904,7 @@ LogicalResult verifyComputeBatchLikeOp(ComputeBatchOpTy batch, StringRef opName)
|
||||
return failure();
|
||||
if (failed(verifyOnlyConstantExternalValues(batch.getOperation(), batch.getBody(), opName)))
|
||||
return failure();
|
||||
return verifyBatchBody(batch, block);
|
||||
return success();
|
||||
}
|
||||
|
||||
LogicalResult SpatGraphComputeBatch::verify() { return verifyComputeBatchLikeOp(*this, "spat.graph_compute_batch"); }
|
||||
|
||||
@@ -0,0 +1,655 @@
|
||||
#include "llvm/ADT/MapVector.h"
|
||||
|
||||
#include "DeferredBoundaryPlanning.hpp"
|
||||
#include "DeferredCommunicationScheduling.hpp"
|
||||
#include "DeferredProjectionAnalysis.hpp"
|
||||
#include "DeferredTransferPlanning.hpp"
|
||||
|
||||
namespace onnx_mlir::spatial {
|
||||
using namespace mlir;
|
||||
namespace {
|
||||
|
||||
enum class BoundaryEventKind { Send, Receive };
|
||||
|
||||
struct BoundaryEvent {
|
||||
BoundaryEventKind kind = BoundaryEventKind::Send;
|
||||
ScheduledTransferSlice slice;
|
||||
LaneSet activeLanes;
|
||||
TransferEmissionSignature emission;
|
||||
};
|
||||
|
||||
struct SequenceNode {
|
||||
unsigned previous = 0;
|
||||
unsigned instruction = 0;
|
||||
};
|
||||
|
||||
struct IntervalClass {
|
||||
LaneSet lanes;
|
||||
unsigned sequence = 0;
|
||||
};
|
||||
|
||||
enum class SemanticKind {
|
||||
Send,
|
||||
Availability,
|
||||
Result
|
||||
};
|
||||
|
||||
struct SemanticKey {
|
||||
SemanticKind kind = SemanticKind::Send;
|
||||
TransferEmissionSignature emission;
|
||||
DeferredExchangePlan* exchange = nullptr;
|
||||
RequirementCoordinate coordinate;
|
||||
Type fragmentType;
|
||||
|
||||
bool operator==(const SemanticKey& other) const {
|
||||
if (kind != other.kind)
|
||||
return false;
|
||||
if (kind == SemanticKind::Send)
|
||||
return emission == other.emission;
|
||||
if (kind == SemanticKind::Result)
|
||||
return exchange == other.exchange;
|
||||
return exchange == other.exchange && coordinate == other.coordinate
|
||||
&& fragmentType == other.fragmentType;
|
||||
}
|
||||
};
|
||||
|
||||
struct PendingToken {
|
||||
LaneSet lanes;
|
||||
unsigned semantic = 0;
|
||||
std::variant<unsigned, LocalAvailabilityFamily*, DeferredExchangePlan*> value;
|
||||
};
|
||||
|
||||
struct SequenceCursor {
|
||||
LaneSet lanes;
|
||||
unsigned position = 0;
|
||||
};
|
||||
|
||||
struct CanonicalAction {
|
||||
SemanticKey key;
|
||||
SmallVector<ScheduledTransferSlice> slices;
|
||||
SmallVector<LocalAvailabilityFamily*> locals;
|
||||
LaneSet instructionLanes;
|
||||
LaneSet receiveLanes;
|
||||
LaneSet localLanes;
|
||||
DeferredExchangePlan* result = nullptr;
|
||||
};
|
||||
|
||||
struct BoundaryWork {
|
||||
BoundaryProgram program;
|
||||
SmallVector<BoundaryEvent> events;
|
||||
};
|
||||
|
||||
static unsigned getBoundaryIndex(SmallVectorImpl<BoundaryWork>& boundaries,
|
||||
DenseMap<BoundaryKey, unsigned>& indices,
|
||||
BoundaryKey key) {
|
||||
if (auto it = indices.find(key); it != indices.end())
|
||||
return it->second;
|
||||
unsigned index = boundaries.size();
|
||||
indices[key] = index;
|
||||
BoundaryWork work;
|
||||
work.program.key = key;
|
||||
boundaries.push_back(std::move(work));
|
||||
return index;
|
||||
}
|
||||
|
||||
static size_t hashSemanticKey(const SemanticKey& key) {
|
||||
if (key.kind == SemanticKind::Send)
|
||||
return llvm::hash_combine(key.kind,
|
||||
key.emission.scheduled,
|
||||
key.emission.payload.getAsOpaquePointer(),
|
||||
key.emission.fragmentType.getAsOpaquePointer(),
|
||||
key.emission.hasGraphLane,
|
||||
key.emission.sourceIsBatch);
|
||||
if (key.kind == SemanticKind::Result)
|
||||
return llvm::hash_combine(key.kind, key.exchange);
|
||||
return llvm::hash_combine(key.kind, key.exchange,
|
||||
key.coordinate.leafIndex,
|
||||
key.coordinate.selectedPosition,
|
||||
key.fragmentType.getAsOpaquePointer());
|
||||
}
|
||||
|
||||
static unsigned internSemantic(const SemanticKey& key,
|
||||
SmallVectorImpl<SemanticKey>& keys,
|
||||
DenseMap<size_t, SmallVector<unsigned>>& byHash) {
|
||||
size_t hash = hashSemanticKey(key);
|
||||
for (unsigned candidate : byHash.lookup(hash))
|
||||
if (keys[candidate] == key)
|
||||
return candidate;
|
||||
unsigned id = keys.size();
|
||||
keys.push_back(key);
|
||||
byHash[hash].push_back(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
static SemanticKey getEventKey(const BoundaryEvent& event) {
|
||||
if (event.kind == BoundaryEventKind::Send) {
|
||||
SemanticKey key;
|
||||
key.kind = SemanticKind::Send;
|
||||
key.emission = event.emission;
|
||||
return key;
|
||||
}
|
||||
RequirementFamily& requirement = *event.slice.family->requirement;
|
||||
SemanticKey key;
|
||||
key.kind = SemanticKind::Availability;
|
||||
key.exchange = requirement.exchange;
|
||||
key.coordinate = requirement.coordinate;
|
||||
key.fragmentType = requirement.publicationFragmentType;
|
||||
return key;
|
||||
}
|
||||
|
||||
static SemanticKey getLocalKey(LocalAvailabilityFamily& local) {
|
||||
RequirementFamily& requirement = *local.requirement;
|
||||
SemanticKey key;
|
||||
key.kind = SemanticKind::Availability;
|
||||
key.exchange = requirement.exchange;
|
||||
key.coordinate = requirement.coordinate;
|
||||
key.fragmentType = requirement.publicationFragmentType;
|
||||
return key;
|
||||
}
|
||||
|
||||
static SmallVector<ScheduledTransferSlice> intersectReceiveSlice(const ScheduledTransferSlice& slice,
|
||||
const LaneSet& lanes) {
|
||||
LaneInterval family = slice.family->targetLanes.intervals().front();
|
||||
unsigned sliceBegin = family.begin + slice.familyOffset;
|
||||
LaneSet active = LaneSet::range(sliceBegin, sliceBegin + slice.transferCount).intersect(lanes);
|
||||
SmallVector<ScheduledTransferSlice> result;
|
||||
for (LaneInterval selected : active.intervals()) {
|
||||
ScheduledTransferSlice part = slice;
|
||||
part.familyOffset += selected.begin - sliceBegin;
|
||||
part.transferCount = selected.end - selected.begin;
|
||||
result.push_back(part);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void mergeSequenceCursors(SmallVectorImpl<SequenceCursor>& states) {
|
||||
DenseMap<unsigned, unsigned> byPosition;
|
||||
SmallVector<SequenceCursor> merged;
|
||||
for (SequenceCursor& state : states) {
|
||||
auto [it, inserted] = byPosition.try_emplace(state.position, merged.size());
|
||||
if (inserted)
|
||||
merged.push_back(std::move(state));
|
||||
else
|
||||
merged[it->second].lanes = merged[it->second].lanes.unite(state.lanes);
|
||||
}
|
||||
states = std::move(merged);
|
||||
}
|
||||
|
||||
static LogicalResult appendReplayToken(const PendingToken& token,
|
||||
const LaneSet& lanes,
|
||||
CanonicalAction& action,
|
||||
ArrayRef<BoundaryEvent> events) {
|
||||
if (auto eventId = std::get_if<unsigned>(&token.value)) {
|
||||
const BoundaryEvent& event = events[*eventId];
|
||||
if (event.kind == BoundaryEventKind::Send) {
|
||||
LaneSet active = event.activeLanes.intersect(lanes);
|
||||
if (!active.empty())
|
||||
action.slices.push_back(event.slice);
|
||||
action.instructionLanes =
|
||||
action.instructionLanes.unite(active);
|
||||
}
|
||||
else {
|
||||
llvm::append_range(action.slices, intersectReceiveSlice(event.slice, lanes));
|
||||
action.receiveLanes = action.receiveLanes.unite(lanes);
|
||||
}
|
||||
return success();
|
||||
}
|
||||
if (auto local = std::get_if<LocalAvailabilityFamily*>(&token.value)) {
|
||||
if (!llvm::is_contained(action.locals, *local))
|
||||
action.locals.push_back(*local);
|
||||
action.localLanes = action.localLanes.unite(lanes);
|
||||
return success();
|
||||
}
|
||||
action.result = *std::get_if<DeferredExchangePlan*>(&token.value);
|
||||
return success();
|
||||
}
|
||||
|
||||
static FailureOr<SmallVector<CanonicalAction, 0>> collectCanonicalActions(
|
||||
ArrayRef<unsigned> sequence, ArrayRef<PendingToken> tokens,
|
||||
ArrayRef<SemanticKey> semantics, ArrayRef<BoundaryEvent> events,
|
||||
const LaneSet& lanes) {
|
||||
SmallVector<CanonicalAction, 0> actions;
|
||||
for (unsigned semantic : sequence) {
|
||||
CanonicalAction action;
|
||||
action.key = semantics[semantic];
|
||||
actions.push_back(std::move(action));
|
||||
}
|
||||
SmallVector<SequenceCursor> states {
|
||||
{lanes, 0}
|
||||
};
|
||||
for (const PendingToken& token : tokens) {
|
||||
SmallVector<SequenceCursor> next;
|
||||
for (const SequenceCursor& state : states) {
|
||||
LaneSet intersection = state.lanes.intersect(token.lanes);
|
||||
LaneSet difference = state.lanes.subtract(token.lanes);
|
||||
if (!difference.empty())
|
||||
next.push_back({difference, state.position});
|
||||
if (intersection.empty())
|
||||
continue;
|
||||
if (state.position == sequence.size()) {
|
||||
next.push_back({intersection, state.position});
|
||||
continue;
|
||||
}
|
||||
if (state.position >= sequence.size() || sequence[state.position] != token.semantic
|
||||
|| failed(appendReplayToken(token, intersection, actions[state.position], events)))
|
||||
return failure();
|
||||
next.push_back({intersection, state.position + 1});
|
||||
}
|
||||
mergeSequenceCursors(next);
|
||||
states = std::move(next);
|
||||
}
|
||||
if (llvm::any_of(states, [&](const SequenceCursor& state) { return state.position != sequence.size(); }))
|
||||
return failure();
|
||||
return actions;
|
||||
}
|
||||
|
||||
static bool matchesAssembly(ArrayRef<CanonicalAction> actions, size_t begin, SmallVectorImpl<unsigned>& entryOrder) {
|
||||
if (begin >= actions.size())
|
||||
return false;
|
||||
const CanonicalAction& first = actions[begin];
|
||||
DeferredExchangePlan* exchange = first.key.exchange;
|
||||
auto& assembly = exchange->program.insertAssembly;
|
||||
if (first.key.kind != SemanticKind::Availability || !first.locals.empty()
|
||||
|| !assembly || assembly->entries.empty()
|
||||
|| begin + assembly->entries.size() > actions.size())
|
||||
return false;
|
||||
SmallVector<bool> matched(assembly->entries.size());
|
||||
for (size_t offset = 0; offset < assembly->entries.size(); ++offset) {
|
||||
const CanonicalAction& action = actions[begin + offset];
|
||||
if (action.key.kind != SemanticKind::Availability || !action.locals.empty()
|
||||
|| action.key.exchange != exchange || action.slices.empty())
|
||||
return false;
|
||||
std::optional<unsigned> entry;
|
||||
for (auto [entryIndex, candidate] : llvm::enumerate(assembly->entries))
|
||||
if (!matched[entryIndex] && action.key.coordinate == candidate.coordinate) {
|
||||
entry = entryIndex;
|
||||
break;
|
||||
}
|
||||
if (!entry)
|
||||
return false;
|
||||
matched[*entry] = true;
|
||||
entryOrder.push_back(*entry);
|
||||
}
|
||||
if (assembly->entries.size() > 1
|
||||
&& llvm::any_of(ArrayRef(assembly->entries).drop_front(), [&](const DeferredInsertAssemblyEntryTemplate& entry) {
|
||||
return entry.sourceTransform != assembly->entries.front().sourceTransform
|
||||
|| entry.sourceType != assembly->entries.front().sourceType;
|
||||
}))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool matchesProjectionAssembly(ArrayRef<CanonicalAction> actions,
|
||||
size_t begin,
|
||||
const LaneSet& lanes,
|
||||
unsigned& leafIndex,
|
||||
SmallVectorImpl<unsigned>& positions) {
|
||||
if (begin >= actions.size() || lanes.empty())
|
||||
return false;
|
||||
const CanonicalAction& first = actions[begin];
|
||||
DeferredExchangePlan* exchange = first.key.exchange;
|
||||
if (first.key.kind != SemanticKind::Availability || !first.locals.empty()
|
||||
|| !exchange || exchange->program.insertAssembly
|
||||
|| first.key.coordinate.leafIndex >= exchange->program.leaves.size())
|
||||
return false;
|
||||
leafIndex = first.key.coordinate.leafIndex;
|
||||
const DeferredProjectionLeafTemplate& leaf = exchange->program.leaves[leafIndex];
|
||||
if (leaf.form != DeferredLeafForm::DirectSource)
|
||||
return false;
|
||||
unsigned representative = lanes.intervals().front().begin;
|
||||
unsigned positionCount = 0;
|
||||
unsigned requirementCount = 0;
|
||||
Type fragmentType;
|
||||
for (RequirementFamily& requirement : exchange->requirements) {
|
||||
if (requirement.coordinate.leafIndex != leafIndex || !requirement.targetLanes.contains(representative))
|
||||
continue;
|
||||
++requirementCount;
|
||||
positionCount = std::max(positionCount, requirement.coordinate.selectedPosition + 1);
|
||||
if (fragmentType && fragmentType != requirement.publicationFragmentType)
|
||||
return false;
|
||||
fragmentType = requirement.publicationFragmentType;
|
||||
}
|
||||
auto fragment = dyn_cast<RankedTensorType>(fragmentType);
|
||||
if (positionCount < 2 || requirementCount != positionCount || !fragment
|
||||
|| leaf.reconstructedType.getRank() != fragment.getRank() + 1
|
||||
|| leaf.reconstructedType.getDimSize(0) != positionCount
|
||||
|| leaf.reconstructedType.getShape().drop_front() != fragment.getShape())
|
||||
return false;
|
||||
SmallVector<bool> seen(positionCount);
|
||||
for (size_t offset = 0; begin + offset < actions.size(); ++offset) {
|
||||
const CanonicalAction& action = actions[begin + offset];
|
||||
if (action.key.kind != SemanticKind::Availability || !action.locals.empty()
|
||||
|| action.key.exchange != exchange
|
||||
|| action.key.coordinate.leafIndex != leafIndex || action.key.fragmentType != fragmentType)
|
||||
break;
|
||||
unsigned position = action.key.coordinate.selectedPosition;
|
||||
if (action.slices.empty() || position >= positionCount || seen[position])
|
||||
return false;
|
||||
seen[position] = true;
|
||||
positions.push_back(position);
|
||||
}
|
||||
if (positions.size() < 2)
|
||||
return false;
|
||||
for (RequirementFamily& requirement : exchange->requirements) {
|
||||
if (requirement.coordinate.leafIndex != leafIndex || !requirement.targetLanes.contains(representative)
|
||||
|| seen[requirement.coordinate.selectedPosition])
|
||||
continue;
|
||||
bool local = llvm::any_of(exchange->local, [&](const LocalAvailabilityFamily& availability) {
|
||||
return availability.requirement == &requirement && availability.targetLanes.contains(representative);
|
||||
});
|
||||
if (!local)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static BoundaryInstructionList materializeInstructions(ArrayRef<CanonicalAction> actions,
|
||||
const LaneSet& lanes) {
|
||||
BoundaryInstructionList result;
|
||||
auto& instructions = result.instructions;
|
||||
for (size_t index = 0; index < actions.size();) {
|
||||
const CanonicalAction& action = actions[index];
|
||||
if (action.key.kind == SemanticKind::Send) {
|
||||
EmitSendRun run;
|
||||
run.lanes = action.instructionLanes;
|
||||
do {
|
||||
llvm::append_range(run.slices, actions[index].slices);
|
||||
run.lanes = run.lanes.unite(actions[index].instructionLanes);
|
||||
++index;
|
||||
}
|
||||
while (index < actions.size() && actions[index].key.kind == SemanticKind::Send
|
||||
&& actions[index].key.emission == action.key.emission);
|
||||
if (!run.slices.empty())
|
||||
instructions.push_back(std::move(run));
|
||||
continue;
|
||||
}
|
||||
SmallVector<unsigned> assemblyEntries;
|
||||
if (matchesAssembly(actions, index, assemblyEntries)) {
|
||||
EmitReceiveAssemblyRun run;
|
||||
run.lanes = lanes;
|
||||
run.assemblyEntries = assemblyEntries;
|
||||
run.entryOffsets.push_back(0);
|
||||
for (size_t offset = 0; offset < assemblyEntries.size(); ++offset) {
|
||||
llvm::append_range(run.slices, actions[index + offset].slices);
|
||||
run.entryOffsets.push_back(run.slices.size());
|
||||
}
|
||||
instructions.push_back(std::move(run));
|
||||
index += assemblyEntries.size();
|
||||
continue;
|
||||
}
|
||||
unsigned projectionLeaf = 0;
|
||||
SmallVector<unsigned> projectionPositions;
|
||||
if (matchesProjectionAssembly(actions, index, lanes, projectionLeaf, projectionPositions)) {
|
||||
EmitReceiveAssemblyRun run;
|
||||
run.lanes = lanes;
|
||||
run.projectionLeaf = projectionLeaf;
|
||||
run.assemblyEntries = projectionPositions;
|
||||
run.entryOffsets.push_back(0);
|
||||
for (size_t offset = 0; offset < projectionPositions.size(); ++offset) {
|
||||
llvm::append_range(run.slices, actions[index + offset].slices);
|
||||
run.entryOffsets.push_back(run.slices.size());
|
||||
}
|
||||
instructions.push_back(std::move(run));
|
||||
index += projectionPositions.size();
|
||||
continue;
|
||||
}
|
||||
if (action.key.kind == SemanticKind::Availability) {
|
||||
ResolveAvailability availability;
|
||||
availability.exchange = action.key.exchange;
|
||||
availability.coordinate = action.key.coordinate;
|
||||
availability.fragmentType = action.key.fragmentType;
|
||||
if (!action.slices.empty()) {
|
||||
EmitReceiveRun receive;
|
||||
receive.slices = action.slices;
|
||||
receive.entryOffsets = {0, receive.slices.size()};
|
||||
receive.lanes = action.receiveLanes;
|
||||
availability.alternatives.push_back(
|
||||
{receive.lanes, AvailabilitySource(std::move(receive))});
|
||||
}
|
||||
if (!action.locals.empty()) {
|
||||
MaterializeLocalFamily local {action.locals, action.localLanes};
|
||||
availability.alternatives.push_back(
|
||||
{local.lanes, AvailabilitySource(std::move(local))});
|
||||
}
|
||||
instructions.push_back(std::move(availability));
|
||||
++index;
|
||||
continue;
|
||||
}
|
||||
instructions.push_back(ProduceDeferredResult {action.result, lanes});
|
||||
++index;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void addTokenToClasses(const LaneSet& active,
|
||||
unsigned semantic,
|
||||
SmallVectorImpl<IntervalClass>& classes,
|
||||
SmallVectorImpl<SequenceNode>& nodes,
|
||||
DenseMap<std::pair<unsigned, unsigned>, unsigned>& interned) {
|
||||
SmallVector<IntervalClass> next;
|
||||
for (const IntervalClass& current : classes) {
|
||||
LaneSet intersection = current.lanes.intersect(active);
|
||||
LaneSet difference = current.lanes.subtract(active);
|
||||
if (!difference.empty())
|
||||
next.push_back({difference, current.sequence});
|
||||
if (intersection.empty())
|
||||
continue;
|
||||
auto key = std::make_pair(current.sequence, semantic);
|
||||
auto [it, inserted] = interned.try_emplace(key, nodes.size());
|
||||
if (inserted)
|
||||
nodes.push_back({current.sequence, semantic});
|
||||
next.push_back({intersection, it->second});
|
||||
}
|
||||
classes = std::move(next);
|
||||
}
|
||||
|
||||
struct SequenceClass {
|
||||
LaneSet lanes;
|
||||
SmallVector<unsigned> sequence;
|
||||
};
|
||||
|
||||
static SmallVector<SequenceClass>
|
||||
buildSequenceClasses(unsigned laneCount, ArrayRef<PendingToken> tokens) {
|
||||
SmallVector<IntervalClass> classes {
|
||||
{LaneSet::all(laneCount), 0}
|
||||
};
|
||||
SmallVector<SequenceNode> nodes {
|
||||
{0, 0}
|
||||
};
|
||||
DenseMap<std::pair<unsigned, unsigned>, unsigned> interned;
|
||||
for (const PendingToken& token : tokens)
|
||||
addTokenToClasses(token.lanes, token.semantic, classes, nodes, interned);
|
||||
|
||||
SmallVector<SequenceClass> result;
|
||||
DenseMap<unsigned, unsigned> classBySequence;
|
||||
SmallVector<unsigned> classSequences;
|
||||
for (const IntervalClass& interval : classes) {
|
||||
auto [it, inserted] = classBySequence.try_emplace(
|
||||
interval.sequence, result.size());
|
||||
if (inserted) {
|
||||
result.push_back({interval.lanes, {}});
|
||||
classSequences.push_back(interval.sequence);
|
||||
}
|
||||
else
|
||||
result[it->second].lanes = result[it->second].lanes.unite(interval.lanes);
|
||||
}
|
||||
for (auto [behavior, sequence] : llvm::zip_equal(result, classSequences)) {
|
||||
for (unsigned node = sequence; node != 0; node = nodes[node].previous)
|
||||
behavior.sequence.push_back(nodes[node].instruction);
|
||||
std::reverse(behavior.sequence.begin(), behavior.sequence.end());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static SmallVector<DeferredExchangePlan*>
|
||||
getProducedExchanges(const BoundaryInstructionList& list) {
|
||||
SmallVector<DeferredExchangePlan*> result;
|
||||
for (const BoundaryInstruction& instruction : list.instructions)
|
||||
if (auto produced = std::get_if<ProduceDeferredResult>(&instruction))
|
||||
result.push_back(produced->exchange);
|
||||
return result;
|
||||
}
|
||||
|
||||
static LogicalResult buildCanonicalBoundary(
|
||||
BoundaryProgram& boundary, ArrayRef<BoundaryEvent> events,
|
||||
ArrayRef<PendingToken> tokens, ArrayRef<SemanticKey> semantics) {
|
||||
unsigned laneCount = boundary.key.scheduled->cores.size();
|
||||
SmallVector<SequenceClass> classes =
|
||||
buildSequenceClasses(laneCount, tokens);
|
||||
if (classes.empty())
|
||||
return failure();
|
||||
size_t prefix = classes.front().sequence.size();
|
||||
for (const SequenceClass& behavior : ArrayRef(classes).drop_front()) {
|
||||
prefix = std::min(prefix, behavior.sequence.size());
|
||||
size_t index = 0;
|
||||
while (index < prefix
|
||||
&& behavior.sequence[index] == classes.front().sequence[index])
|
||||
++index;
|
||||
prefix = index;
|
||||
}
|
||||
auto prefixActions = collectCanonicalActions(
|
||||
ArrayRef(classes.front().sequence).take_front(prefix), tokens, semantics,
|
||||
events, LaneSet::all(laneCount));
|
||||
if (failed(prefixActions))
|
||||
return failure();
|
||||
boundary.root = materializeInstructions(*prefixActions, LaneSet::all(laneCount));
|
||||
if (classes.size() == 1)
|
||||
return success();
|
||||
|
||||
auto dispatch = std::make_unique<LaneDispatch>();
|
||||
SmallVector<int64_t> classIds(laneCount);
|
||||
for (auto [classId, behavior] : llvm::enumerate(classes)) {
|
||||
for (LaneInterval interval : behavior.lanes.intervals())
|
||||
for (unsigned lane = interval.begin; lane < interval.end; ++lane)
|
||||
classIds[lane] = classId;
|
||||
auto actions = collectCanonicalActions(
|
||||
behavior.sequence, tokens, semantics, events, behavior.lanes);
|
||||
if (failed(actions))
|
||||
return failure();
|
||||
dispatch->branches.push_back(materializeInstructions(
|
||||
ArrayRef(*actions).drop_front(prefix), behavior.lanes));
|
||||
auto produced = getProducedExchanges(dispatch->branches.back());
|
||||
if (classId == 0)
|
||||
dispatch->producedExchanges = std::move(produced);
|
||||
else if (produced != dispatch->producedExchanges)
|
||||
return failure();
|
||||
}
|
||||
dispatch->branchByLane = StaticIntSequence::fromValues(classIds);
|
||||
boundary.root.instructions.push_back(std::move(dispatch));
|
||||
return success();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
FailureOr<DeferredBoundaryPlan> buildDeferredBoundaryPlan(DeferredTransferPlan& transfers,
|
||||
const ScheduledCommunicationPlan& schedule) {
|
||||
DeferredBoundaryPlan result;
|
||||
SmallVector<BoundaryWork> boundaries;
|
||||
DenseMap<BoundaryKey, unsigned> boundaryIndices;
|
||||
DenseMap<DeferredExchangePlan*, unsigned> resultBoundarySteps;
|
||||
for (const ScheduledTransferSlice& slice : schedule.slices) {
|
||||
ExternalTransferFamily& family = *slice.family;
|
||||
auto [resultStep, inserted] =
|
||||
resultBoundarySteps.try_emplace(family.requirement->exchange, slice.targetInsertionStep);
|
||||
if (!inserted)
|
||||
resultStep->second = std::max(resultStep->second, slice.targetInsertionStep);
|
||||
unsigned sourceIndex =
|
||||
getBoundaryIndex(boundaries, boundaryIndices,
|
||||
{family.sourceScheduled, slice.sourceInsertionStep});
|
||||
unsigned targetIndex =
|
||||
getBoundaryIndex(boundaries, boundaryIndices,
|
||||
{family.targetScheduled, slice.targetInsertionStep});
|
||||
unsigned sourceLane = family.requirement->producer->scheduledLane;
|
||||
boundaries[sourceIndex].events.push_back({BoundaryEventKind::Send,
|
||||
slice,
|
||||
LaneSet::range(sourceLane, sourceLane + 1),
|
||||
getTransferEmissionSignature(family)});
|
||||
LaneInterval familyLanes = family.targetLanes.intervals().front();
|
||||
unsigned targetBegin = familyLanes.begin + slice.familyOffset;
|
||||
boundaries[targetIndex].events.push_back({BoundaryEventKind::Receive,
|
||||
slice,
|
||||
LaneSet::range(targetBegin, targetBegin + slice.transferCount),
|
||||
getTransferEmissionSignature(family)});
|
||||
}
|
||||
|
||||
DenseMap<BoundaryKey, SmallVector<LocalAvailabilityFamily*>> locals;
|
||||
DenseMap<BoundaryKey, SmallVector<DeferredExchangePlan*>> exchanges;
|
||||
for (const std::unique_ptr<DeferredExchangePlan>& exchange : transfers.exchanges) {
|
||||
auto resultStep = resultBoundarySteps.find(exchange.get());
|
||||
unsigned step = resultStep == resultBoundarySteps.end() ? exchange->consumerStep : resultStep->second;
|
||||
BoundaryKey key {exchange->target, step};
|
||||
getBoundaryIndex(boundaries, boundaryIndices, key);
|
||||
for (LocalAvailabilityFamily& local : exchange->local)
|
||||
locals[key].push_back(&local);
|
||||
exchanges[key].push_back(exchange.get());
|
||||
auto resultPlan = buildDeferredResultPlan(*exchange);
|
||||
if (failed(resultPlan))
|
||||
return exchange->deferred.emitOpError("cannot evaluate deferred result lane functions"), failure();
|
||||
result.results.push_back(std::move(*resultPlan));
|
||||
}
|
||||
|
||||
DenseMap<ScheduledInfo*, unsigned> scheduledOrder;
|
||||
for (auto [index, scheduled] : llvm::enumerate(transfers.scheduled))
|
||||
scheduledOrder[&scheduled] = index;
|
||||
llvm::stable_sort(boundaries, [&](const BoundaryWork& lhs, const BoundaryWork& rhs) {
|
||||
return std::tie(scheduledOrder[lhs.program.key.scheduled], lhs.program.key.insertionStep)
|
||||
< std::tie(scheduledOrder[rhs.program.key.scheduled], rhs.program.key.insertionStep);
|
||||
});
|
||||
for (BoundaryWork& work : boundaries) {
|
||||
BoundaryProgram& boundary = work.program;
|
||||
SmallVector<PendingToken> tokens;
|
||||
SmallVector<SemanticKey> semantics;
|
||||
DenseMap<size_t, SmallVector<unsigned>> semanticsByHash;
|
||||
DenseMap<unsigned, SmallVector<LocalAvailabilityFamily*>> localsBySemantic;
|
||||
SmallPtrSet<LocalAvailabilityFamily*, 8> emittedLocals;
|
||||
for (LocalAvailabilityFamily* local : locals[boundary.key]) {
|
||||
unsigned semantic =
|
||||
internSemantic(getLocalKey(*local), semantics, semanticsByHash);
|
||||
localsBySemantic[semantic].push_back(local);
|
||||
}
|
||||
SmallVector<unsigned> eventSemantics;
|
||||
for (BoundaryEvent& event : work.events) {
|
||||
unsigned semantic =
|
||||
internSemantic(getEventKey(event), semantics, semanticsByHash);
|
||||
eventSemantics.push_back(semantic);
|
||||
}
|
||||
llvm::SmallDenseSet<unsigned, 8> emittedAvailabilities;
|
||||
for (auto [eventId, event] : llvm::enumerate(work.events)) {
|
||||
unsigned semantic = eventSemantics[eventId];
|
||||
if (event.kind == BoundaryEventKind::Send) {
|
||||
tokens.push_back({LaneSet::all(
|
||||
boundary.key.scheduled->cores.size()),
|
||||
semantic, static_cast<unsigned>(eventId)});
|
||||
continue;
|
||||
}
|
||||
tokens.push_back(
|
||||
{event.activeLanes, semantic, static_cast<unsigned>(eventId)});
|
||||
if (emittedAvailabilities.insert(semantic).second) {
|
||||
for (LocalAvailabilityFamily* local : localsBySemantic[semantic]) {
|
||||
tokens.push_back({local->targetLanes, semantic, local});
|
||||
emittedLocals.insert(local);
|
||||
}
|
||||
localsBySemantic.erase(semantic);
|
||||
}
|
||||
}
|
||||
for (LocalAvailabilityFamily* local : locals[boundary.key])
|
||||
if (!emittedLocals.contains(local)) {
|
||||
unsigned semantic =
|
||||
internSemantic(getLocalKey(*local), semantics, semanticsByHash);
|
||||
tokens.push_back({local->targetLanes, semantic, local});
|
||||
}
|
||||
for (DeferredExchangePlan* exchange : exchanges[boundary.key]) {
|
||||
SemanticKey key;
|
||||
key.kind = SemanticKind::Result;
|
||||
key.exchange = exchange;
|
||||
unsigned semantic = internSemantic(key, semantics, semanticsByHash);
|
||||
tokens.push_back({LaneSet::all(exchange->targetLaneCount), semantic, exchange});
|
||||
}
|
||||
if (failed(buildCanonicalBoundary(boundary, work.events, tokens, semantics)))
|
||||
return boundary.key.scheduled->op->emitOpError("cannot construct canonical boundary program"), failure();
|
||||
result.boundaries.push_back(std::move(boundary));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace onnx_mlir::spatial
|
||||
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
|
||||
#include "DeferredCommunicationScheduling.hpp"
|
||||
#include "DeferredResultRealization.hpp"
|
||||
|
||||
namespace onnx_mlir::spatial {
|
||||
|
||||
struct DeferredTransferPlan;
|
||||
|
||||
struct BoundaryKey {
|
||||
ScheduledInfo* scheduled = nullptr;
|
||||
unsigned insertionStep = 0;
|
||||
|
||||
bool operator==(const BoundaryKey& other) const {
|
||||
return scheduled == other.scheduled
|
||||
&& insertionStep == other.insertionStep;
|
||||
}
|
||||
};
|
||||
|
||||
struct EmitSendRun {
|
||||
llvm::SmallVector<ScheduledTransferSlice> slices;
|
||||
LaneSet lanes;
|
||||
};
|
||||
struct EmitReceiveRun {
|
||||
llvm::SmallVector<ScheduledTransferSlice> slices;
|
||||
llvm::SmallVector<size_t> entryOffsets;
|
||||
LaneSet lanes;
|
||||
};
|
||||
struct EmitReceiveAssemblyRun {
|
||||
llvm::SmallVector<ScheduledTransferSlice> slices;
|
||||
llvm::SmallVector<size_t> entryOffsets;
|
||||
llvm::SmallVector<unsigned> assemblyEntries;
|
||||
std::optional<unsigned> projectionLeaf;
|
||||
LaneSet lanes;
|
||||
};
|
||||
struct MaterializeLocalFamily {
|
||||
llvm::SmallVector<LocalAvailabilityFamily*> families;
|
||||
LaneSet lanes;
|
||||
};
|
||||
using AvailabilitySource =
|
||||
std::variant<EmitReceiveRun, MaterializeLocalFamily>;
|
||||
struct AvailabilityAlternative {
|
||||
LaneSet lanes;
|
||||
AvailabilitySource source;
|
||||
};
|
||||
struct ResolveAvailability {
|
||||
DeferredExchangePlan* exchange = nullptr;
|
||||
RequirementCoordinate coordinate;
|
||||
mlir::Type fragmentType;
|
||||
llvm::SmallVector<AvailabilityAlternative, 2> alternatives;
|
||||
};
|
||||
struct ProduceDeferredResult {
|
||||
DeferredExchangePlan* exchange = nullptr;
|
||||
LaneSet lanes;
|
||||
};
|
||||
|
||||
struct BoundaryInstructionList;
|
||||
struct LaneDispatch;
|
||||
using BoundaryInstruction = std::variant<EmitSendRun, ResolveAvailability,
|
||||
EmitReceiveAssemblyRun, ProduceDeferredResult,
|
||||
std::unique_ptr<LaneDispatch>>;
|
||||
struct BoundaryInstructionList {
|
||||
llvm::SmallVector<BoundaryInstruction, 0> instructions;
|
||||
};
|
||||
struct LaneDispatch {
|
||||
StaticIntSequence branchByLane = StaticIntSequence::uniform(0, 1);
|
||||
llvm::SmallVector<BoundaryInstructionList> branches;
|
||||
llvm::SmallVector<DeferredExchangePlan*> producedExchanges;
|
||||
};
|
||||
struct BoundaryProgram {
|
||||
BoundaryKey key;
|
||||
BoundaryInstructionList root;
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
namespace llvm {
|
||||
template <>
|
||||
struct DenseMapInfo<onnx_mlir::spatial::BoundaryKey> {
|
||||
static onnx_mlir::spatial::BoundaryKey getEmptyKey() {
|
||||
return {DenseMapInfo<onnx_mlir::spatial::ScheduledInfo*>::getEmptyKey(), 0};
|
||||
}
|
||||
static onnx_mlir::spatial::BoundaryKey getTombstoneKey() {
|
||||
return {DenseMapInfo<onnx_mlir::spatial::ScheduledInfo*>::getTombstoneKey(), 0};
|
||||
}
|
||||
static unsigned getHashValue(const onnx_mlir::spatial::BoundaryKey& key) {
|
||||
return hash_combine(key.scheduled, key.insertionStep);
|
||||
}
|
||||
static bool isEqual(const onnx_mlir::spatial::BoundaryKey& lhs,
|
||||
const onnx_mlir::spatial::BoundaryKey& rhs) {
|
||||
return lhs == rhs;
|
||||
}
|
||||
};
|
||||
} // namespace llvm
|
||||
@@ -0,0 +1,824 @@
|
||||
#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/AffineUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/StaticIntSequence.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/TensorSliceUtils.hpp"
|
||||
|
||||
namespace onnx_mlir::spatial {
|
||||
using namespace mlir;
|
||||
namespace {
|
||||
|
||||
struct LogicalTransferMetadataView {
|
||||
StaticIntSequenceChain channels;
|
||||
StaticIntSequenceChain parents;
|
||||
StaticIntSequenceChain parentCounts;
|
||||
StaticIntSequenceChain sourceCores;
|
||||
StaticIntSequenceChain targetCores;
|
||||
StaticIntSequenceChain targetLanes;
|
||||
StaticIntSequenceChain localOffsets;
|
||||
|
||||
size_t size() const { return channels.size(); }
|
||||
};
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
static LogicalTransferMetadataView
|
||||
buildMetadataView(ArrayRef<ScheduledTransferSlice> slices) {
|
||||
LogicalTransferMetadataView metadata;
|
||||
for (const ScheduledTransferSlice& slice : slices)
|
||||
appendMetadata(slice, metadata);
|
||||
return metadata;
|
||||
}
|
||||
|
||||
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 OpFoldResult lookupGeometry(
|
||||
ArrayRef<int64_t> table, Value position, Operation* anchor, DeferredEmissionContext& context, Location loc) {
|
||||
StaticIntSequence sequence = StaticIntSequence::fromValues(table);
|
||||
if (sequence.getKind() == StaticIntSequenceKind::Uniform)
|
||||
return context.rewriter.getIndexAttr(sequence.valueAt(0));
|
||||
return emitStaticIntLookup(sequence, position, anchor, context.constants, context.rewriter, loc);
|
||||
}
|
||||
|
||||
static FailureOr<Value> materializeSendPayload(const RequirementFamily& requirement,
|
||||
Value localOffset,
|
||||
DeferredEmissionContext& context,
|
||||
Location loc) {
|
||||
Value payload = requirement.producer->payload;
|
||||
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));
|
||||
SmallVector<int64_t> unitShape {1};
|
||||
llvm::append_range(unitShape, fragmentType.getShape());
|
||||
auto unitType = RankedTensorType::get(unitShape, fragmentType.getElementType());
|
||||
Value unit = extractMixedSliceOrIdentity(context.rewriter, loc, payload, unitType, geometry);
|
||||
return removeLeadingUnitTensorDimension(context.rewriter, loc, unit, fragmentType);
|
||||
}
|
||||
|
||||
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());
|
||||
SmallVector<int64_t> channelTable(
|
||||
actionCount * laneCount, logical.channels.valueAt(0));
|
||||
SmallVector<int64_t> sourceTable(
|
||||
actionCount * laneCount, logical.sourceCores.valueAt(0));
|
||||
SmallVector<int64_t> targetTable(
|
||||
actionCount * laneCount, logical.targetCores.valueAt(0));
|
||||
SmallVector<int64_t> offsetTable(
|
||||
actionCount * laneCount, logical.localOffsets.valueAt(0));
|
||||
SmallVector<int64_t> counts(laneCount);
|
||||
for (unsigned sourceLane = 0; sourceLane < laneCount; ++sourceLane) {
|
||||
const LogicalTransferMetadataView& source = metadataByLane[sourceLane];
|
||||
counts[sourceLane] = source.size();
|
||||
for (size_t action = 0; action < source.size(); ++action) {
|
||||
size_t index = action * laneCount + sourceLane;
|
||||
channelTable[index] = source.channels.valueAt(action);
|
||||
sourceTable[index] = source.sourceCores.valueAt(action);
|
||||
targetTable[index] = source.targetCores.valueAt(action);
|
||||
offsetTable[index] = source.localOffsets.valueAt(action);
|
||||
}
|
||||
}
|
||||
ExternalTransferFamily& firstFamily = *run.slices.front().family;
|
||||
RequirementFamily& requirement = *firstFamily.requirement;
|
||||
Operation* anchor = requirement.exchange->deferred;
|
||||
Location loc = requirement.exchange->deferred.getLoc();
|
||||
auto emitOne = [&](Value position) -> LogicalResult {
|
||||
Value localOffset = lookup(offsetTable, position, anchor, context, loc);
|
||||
auto payload = materializeSendPayload(requirement, localOffset, context, loc);
|
||||
if (failed(payload))
|
||||
return failure();
|
||||
auto send = SpatChannelSendOp::create(context.rewriter,
|
||||
loc,
|
||||
lookup(channelTable, position, anchor, context, loc),
|
||||
lookup(sourceTable, position, anchor, context, loc),
|
||||
lookup(targetTable, position, anchor, context, loc),
|
||||
*payload);
|
||||
setLogicalTransferMetadata(send, logical);
|
||||
return success();
|
||||
};
|
||||
Value runtimeLane = lane ? lane : context.constants.getIndex(0);
|
||||
if (actionCount == 1)
|
||||
return emitOne(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>&) {
|
||||
Value base = affineMulConst(context.rewriter, loc, index, laneCount, anchor);
|
||||
Value position = arith::AddIOp::create(context.rewriter, loc, base, runtimeLane);
|
||||
return emitOne(position);
|
||||
});
|
||||
return success(succeeded(loop));
|
||||
}
|
||||
|
||||
static FailureOr<Value>
|
||||
emitReceiveValue(const EmitReceiveRun& run, Value lane, unsigned laneCount, DeferredEmissionContext& context) {
|
||||
LogicalTransferMetadataView metadata = buildMetadataView(run.slices);
|
||||
RequirementFamily& requirement = *run.slices.front().family->requirement;
|
||||
Operation* anchor = requirement.exchange->deferred;
|
||||
Location loc = requirement.exchange->deferred.getLoc();
|
||||
SmallVector<int64_t> channelTable(laneCount, metadata.channels.valueAt(0));
|
||||
SmallVector<int64_t> sourceTable(laneCount, metadata.sourceCores.valueAt(0));
|
||||
SmallVector<int64_t> targetTable(laneCount, metadata.targetCores.valueAt(0));
|
||||
for (size_t index = 0; index < metadata.size(); ++index) {
|
||||
unsigned targetLane = metadata.targetLanes.valueAt(index);
|
||||
channelTable[targetLane] = metadata.channels.valueAt(index);
|
||||
sourceTable[targetLane] = metadata.sourceCores.valueAt(index);
|
||||
targetTable[targetLane] = metadata.targetCores.valueAt(index);
|
||||
}
|
||||
Value position = lane ? lane : context.constants.getIndex(0);
|
||||
auto receive = SpatChannelReceiveOp::create(context.rewriter,
|
||||
loc,
|
||||
requirement.publicationFragmentType,
|
||||
lookup(channelTable, position, anchor, context, loc),
|
||||
lookup(sourceTable, position, anchor, context, loc),
|
||||
lookup(targetTable, position, anchor, context, loc));
|
||||
setLogicalTransferMetadata(receive, metadata);
|
||||
return receive.getOutput();
|
||||
}
|
||||
|
||||
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();
|
||||
SmallVector<int64_t> active(laneCount);
|
||||
for (LaneInterval interval : run.lanes.intervals())
|
||||
for (unsigned index = interval.begin; index < interval.end; ++index)
|
||||
active[index] = 1;
|
||||
Location loc = run.slices.front().family->requirement->exchange->deferred.getLoc();
|
||||
Value selected = lookup(active, lane, run.slices.front().family->sourceScheduled->op, context, loc);
|
||||
Value condition =
|
||||
arith::CmpIOp::create(context.rewriter, loc, arith::CmpIPredicate::ne, selected, context.constants.getIndex(0));
|
||||
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 MaterializeLocalFamily& local,
|
||||
Value lane,
|
||||
unsigned laneCount,
|
||||
DeferredEmissionContext& context) {
|
||||
RequirementFamily& reference = *local.families.front()->requirement;
|
||||
Value fragment = reference.producer->payload;
|
||||
if (fragment.getType() != reference.publicationFragmentType) {
|
||||
SmallVector<int64_t> offsets(laneCount);
|
||||
for (LocalAvailabilityFamily* family : local.families) {
|
||||
RequirementFamily& requirement = *family->requirement;
|
||||
LaneInterval requirementLanes = requirement.targetLanes.intervals().front();
|
||||
for (LaneInterval interval : family->targetLanes.intervals())
|
||||
for (unsigned targetLane = interval.begin; targetLane < interval.end; ++targetLane)
|
||||
offsets[targetLane] = requirement.producerLocalOffsets->valueAt(targetLane - requirementLanes.begin);
|
||||
}
|
||||
Location loc = reference.exchange->deferred.getLoc();
|
||||
Value position = lane ? lane : context.constants.getIndex(0);
|
||||
auto materialized = materializeSendPayload(
|
||||
reference, lookup(offsets, position, reference.exchange->deferred, context, loc), 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 FailureOr<Value> applyAssemblyTransform(Value source,
|
||||
const DeferredInsertAssemblyEntryTemplate& entry,
|
||||
DeferredEmissionContext& context,
|
||||
Location loc) {
|
||||
auto sourceType = dyn_cast<RankedTensorType>(source.getType());
|
||||
auto targetType = entry.sourceType;
|
||||
if (!sourceType)
|
||||
return failure();
|
||||
if (sourceType == targetType)
|
||||
return source;
|
||||
if (targetType.getRank() == sourceType.getRank() + 1 && targetType.getDimSize(0) == 1
|
||||
&& targetType.getShape().drop_front() == sourceType.getShape())
|
||||
return addLeadingUnitTensorDimension(context.rewriter, loc, source);
|
||||
if (sourceType.getRank() != targetType.getRank() + 1 || sourceType.getDimSize(0) != 1
|
||||
|| sourceType.getShape().drop_front() != targetType.getShape())
|
||||
return failure();
|
||||
return removeLeadingUnitTensorDimension(context.rewriter, loc, source, targetType);
|
||||
}
|
||||
|
||||
static FailureOr<Value> emitProjectionAssemblyRun(const EmitReceiveAssemblyRun& run,
|
||||
Value lane,
|
||||
unsigned laneCount,
|
||||
DeferredEmissionContext& context) {
|
||||
if (!run.projectionLeaf || run.slices.empty() || run.entryOffsets.size() != run.assemblyEntries.size() + 1)
|
||||
return failure();
|
||||
DeferredExchangePlan* exchange = run.slices.front().family->requirement->exchange;
|
||||
unsigned leafIndex = *run.projectionLeaf;
|
||||
if (leafIndex >= exchange->program.leaves.size())
|
||||
return failure();
|
||||
const DeferredProjectionLeafTemplate& leaf = exchange->program.leaves[leafIndex];
|
||||
size_t entryCount = run.assemblyEntries.size();
|
||||
LogicalTransferMetadataView metadata = buildMetadataView(run.slices);
|
||||
SmallVector<int64_t> channelTable(
|
||||
entryCount * laneCount, metadata.channels.valueAt(0));
|
||||
SmallVector<int64_t> sourceTable(
|
||||
entryCount * laneCount, metadata.sourceCores.valueAt(0));
|
||||
SmallVector<int64_t> targetTable(
|
||||
entryCount * laneCount, metadata.targetCores.valueAt(0));
|
||||
SmallVector<int64_t> positions(entryCount * laneCount);
|
||||
for (size_t entry = 0; entry < entryCount; ++entry) {
|
||||
ArrayRef<ScheduledTransferSlice> slices =
|
||||
ArrayRef(run.slices).slice(run.entryOffsets[entry], run.entryOffsets[entry + 1] - run.entryOffsets[entry]);
|
||||
LogicalTransferMetadataView item = buildMetadataView(slices);
|
||||
for (size_t index = 0; index < item.size(); ++index) {
|
||||
size_t tableIndex = entry * laneCount + item.targetLanes.valueAt(index);
|
||||
channelTable[tableIndex] = item.channels.valueAt(index);
|
||||
sourceTable[tableIndex] = item.sourceCores.valueAt(index);
|
||||
targetTable[tableIndex] = item.targetCores.valueAt(index);
|
||||
positions[tableIndex] = run.assemblyEntries[entry];
|
||||
}
|
||||
}
|
||||
Location loc = exchange->deferred.getLoc();
|
||||
Value initial = tensor::EmptyOp::create(
|
||||
context.rewriter, loc, leaf.reconstructedType.getShape(), leaf.reconstructedType.getElementType());
|
||||
auto emitEntry = [&](Value entry, Value current) -> FailureOr<Value> {
|
||||
Value tableIndex = entry;
|
||||
if (lane) {
|
||||
Value base = affineMulConst(context.rewriter, loc, entry, laneCount, exchange->deferred);
|
||||
tableIndex = arith::AddIOp::create(context.rewriter, loc, base, lane);
|
||||
}
|
||||
Type fragmentType = run.slices.front().family->requirement->publicationFragmentType;
|
||||
auto receive = SpatChannelReceiveOp::create(context.rewriter,
|
||||
loc,
|
||||
fragmentType,
|
||||
lookup(channelTable, tableIndex, exchange->deferred, context, loc),
|
||||
lookup(sourceTable, tableIndex, exchange->deferred, context, loc),
|
||||
lookup(targetTable, tableIndex, exchange->deferred, context, loc));
|
||||
setLogicalTransferMetadata(receive, metadata);
|
||||
auto source = addLeadingUnitTensorDimension(context.rewriter, loc, receive.getOutput());
|
||||
if (failed(source))
|
||||
return failure();
|
||||
auto sourceType = cast<RankedTensorType>(source->getType());
|
||||
MixedSliceGeometry geometry;
|
||||
geometry.offsets.assign(sourceType.getRank(), context.rewriter.getIndexAttr(0));
|
||||
geometry.offsets.front() = lookupGeometry(positions, tableIndex, exchange->deferred, context, loc);
|
||||
for (int64_t dimension : sourceType.getShape())
|
||||
geometry.sizes.push_back(context.rewriter.getIndexAttr(dimension));
|
||||
geometry.strides.assign(sourceType.getRank(), context.rewriter.getIndexAttr(1));
|
||||
return insertMixedSlice(context.rewriter, loc, *source, current, geometry);
|
||||
};
|
||||
auto loop = buildNormalizedScfFor(
|
||||
context.rewriter,
|
||||
loc,
|
||||
context.constants.getIndex(0),
|
||||
context.constants.getIndex(entryCount),
|
||||
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();
|
||||
Value result = loop->results.front();
|
||||
context.projectionAssemblies[{exchange, leafIndex}] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
static FailureOr<Value> emitAssemblyRun(const EmitReceiveAssemblyRun& run,
|
||||
Value lane,
|
||||
unsigned laneCount,
|
||||
ArrayRef<DeferredResultPlan> results,
|
||||
DeferredEmissionContext& context) {
|
||||
DeferredExchangePlan* exchange = run.slices.front().family->requirement->exchange;
|
||||
const DeferredResultPlan* resultPlan = findResultPlan(results, exchange);
|
||||
if (!resultPlan)
|
||||
return failure();
|
||||
auto& assembly = exchange->program.insertAssembly;
|
||||
size_t entryCount = run.entryOffsets.empty() ? 0 : run.entryOffsets.size() - 1;
|
||||
if (!assembly || assembly->entries.size() != entryCount)
|
||||
return failure();
|
||||
if (run.assemblyEntries.size() != entryCount)
|
||||
return failure();
|
||||
LogicalTransferMetadataView metadata = buildMetadataView(run.slices);
|
||||
SmallVector<int64_t> channelTable(
|
||||
entryCount * laneCount, metadata.channels.valueAt(0));
|
||||
SmallVector<int64_t> sourceTable(
|
||||
entryCount * laneCount, metadata.sourceCores.valueAt(0));
|
||||
SmallVector<int64_t> targetTable(
|
||||
entryCount * laneCount, metadata.targetCores.valueAt(0));
|
||||
unsigned rank = assembly->resultType.getRank();
|
||||
SmallVector<SmallVector<int64_t>> geometryOffsets(rank, SmallVector<int64_t>(entryCount * laneCount));
|
||||
SmallVector<SmallVector<int64_t>> geometrySizes(rank, SmallVector<int64_t>(entryCount * laneCount, 1));
|
||||
SmallVector<SmallVector<int64_t>> geometryStrides(rank, SmallVector<int64_t>(entryCount * laneCount, 1));
|
||||
for (size_t entryIndex = 0; entryIndex < entryCount; ++entryIndex) {
|
||||
unsigned assemblyEntry = run.assemblyEntries[entryIndex];
|
||||
if (assemblyEntry >= resultPlan->assemblyGeometry.size())
|
||||
return failure();
|
||||
ArrayRef<ScheduledTransferSlice> entrySlices =
|
||||
ArrayRef(run.slices)
|
||||
.slice(run.entryOffsets[entryIndex], run.entryOffsets[entryIndex + 1] - run.entryOffsets[entryIndex]);
|
||||
LogicalTransferMetadataView entry = buildMetadataView(entrySlices);
|
||||
for (size_t index = 0; index < entry.size(); ++index) {
|
||||
size_t tableIndex =
|
||||
entryIndex * laneCount + entry.targetLanes.valueAt(index);
|
||||
channelTable[tableIndex] = entry.channels.valueAt(index);
|
||||
sourceTable[tableIndex] = entry.sourceCores.valueAt(index);
|
||||
targetTable[tableIndex] = entry.targetCores.valueAt(index);
|
||||
unsigned targetLane = entry.targetLanes.valueAt(index);
|
||||
for (unsigned dimension = 0; dimension < rank; ++dimension) {
|
||||
geometryOffsets[dimension][tableIndex] =
|
||||
resultPlan->assemblyGeometry[assemblyEntry].offsets[dimension].valueAt(targetLane);
|
||||
geometrySizes[dimension][tableIndex] =
|
||||
resultPlan->assemblyGeometry[assemblyEntry].sizes[dimension].valueAt(targetLane);
|
||||
geometryStrides[dimension][tableIndex] =
|
||||
resultPlan->assemblyGeometry[assemblyEntry].strides[dimension].valueAt(targetLane);
|
||||
}
|
||||
}
|
||||
}
|
||||
Location loc = exchange->deferred.getLoc();
|
||||
Operation* initial = context.rewriter.clone(*assembly->initialValue);
|
||||
Value assembled = initial->getResult(0);
|
||||
auto emitEntry = [&](Value entryIndex, unsigned staticEntry, Value current) -> FailureOr<Value> {
|
||||
Value tableIndex = entryIndex;
|
||||
if (lane) {
|
||||
Value base = affineMulConst(context.rewriter, loc, entryIndex, laneCount, exchange->deferred);
|
||||
AffineExpr d0 = context.rewriter.getAffineDimExpr(0);
|
||||
AffineExpr d1 = context.rewriter.getAffineDimExpr(1);
|
||||
tableIndex = createOrFoldAffineApply(context.rewriter, loc, d0 + d1, ValueRange {base, lane}, exchange->deferred);
|
||||
}
|
||||
Type fragmentType = run.slices[run.entryOffsets[staticEntry]].family->requirement->publicationFragmentType;
|
||||
auto receive = SpatChannelReceiveOp::create(context.rewriter,
|
||||
loc,
|
||||
fragmentType,
|
||||
lookup(channelTable, tableIndex, exchange->deferred, context, loc),
|
||||
lookup(sourceTable, tableIndex, exchange->deferred, context, loc),
|
||||
lookup(targetTable, tableIndex, exchange->deferred, context, loc));
|
||||
setLogicalTransferMetadata(receive, metadata);
|
||||
auto source =
|
||||
applyAssemblyTransform(receive.getOutput(), assembly->entries[run.assemblyEntries[staticEntry]], context, loc);
|
||||
if (failed(source))
|
||||
return failure();
|
||||
MixedSliceGeometry geometry;
|
||||
for (unsigned dimension = 0; dimension < rank; ++dimension) {
|
||||
geometry.offsets.push_back(
|
||||
lookupGeometry(geometryOffsets[dimension], tableIndex, exchange->deferred, context, loc));
|
||||
geometry.sizes.push_back(lookupGeometry(geometrySizes[dimension], tableIndex, exchange->deferred, context, loc));
|
||||
geometry.strides.push_back(
|
||||
lookupGeometry(geometryStrides[dimension], tableIndex, exchange->deferred, context, loc));
|
||||
}
|
||||
return insertMixedSlice(context.rewriter, loc, *source, current, geometry);
|
||||
};
|
||||
if (entryCount == 1) {
|
||||
auto value = emitEntry(context.constants.getIndex(0), 0, assembled);
|
||||
if (failed(value))
|
||||
return failure();
|
||||
context.assemblies[exchange] = *value;
|
||||
return *value;
|
||||
}
|
||||
const DeferredInsertAssemblyEntryTemplate& firstEntry = assembly->entries[run.assemblyEntries.front()];
|
||||
bool sameTransform = llvm::all_of(ArrayRef(run.assemblyEntries).drop_front(), [&](unsigned entryIndex) {
|
||||
const DeferredInsertAssemblyEntryTemplate& entry = assembly->entries[entryIndex];
|
||||
return entry.sourceTransform == firstEntry.sourceTransform && entry.sourceType == firstEntry.sourceType;
|
||||
});
|
||||
if (!sameTransform)
|
||||
return failure();
|
||||
auto loop = buildNormalizedScfFor(
|
||||
context.rewriter,
|
||||
loc,
|
||||
context.constants.getIndex(0),
|
||||
context.constants.getIndex(entryCount),
|
||||
context.constants.getIndex(1),
|
||||
ValueRange {assembled},
|
||||
[&](OpBuilder&, Location, Value index, ValueRange iterArgs, SmallVectorImpl<Value>& yielded) -> LogicalResult {
|
||||
auto value = emitEntry(index, 0, iterArgs.front());
|
||||
if (failed(value))
|
||||
return failure();
|
||||
yielded.push_back(*value);
|
||||
return success();
|
||||
});
|
||||
if (failed(loop))
|
||||
return failure();
|
||||
context.assemblies[exchange] = loop->results.front();
|
||||
return loop->results.front();
|
||||
}
|
||||
|
||||
static RequirementFamily* getAvailabilityRequirement(const AvailabilityAlternative& alternative) {
|
||||
return std::visit(
|
||||
[](const auto& value) -> RequirementFamily* {
|
||||
using Alternative = std::decay_t<decltype(value)>;
|
||||
if constexpr (std::is_same_v<Alternative, EmitReceiveRun>)
|
||||
return value.slices.front().family->requirement;
|
||||
else
|
||||
return value.families.front()->requirement;
|
||||
},
|
||||
alternative.source);
|
||||
}
|
||||
|
||||
static FailureOr<Value> emitAvailabilityAlternative(const AvailabilityAlternative& alternative,
|
||||
Value lane,
|
||||
unsigned laneCount,
|
||||
DeferredEmissionContext& context) {
|
||||
if (auto receive = std::get_if<EmitReceiveRun>(&alternative.source))
|
||||
return emitReceiveValue(*receive, lane, laneCount, context);
|
||||
return materializeLocalValue(
|
||||
std::get<MaterializeLocalFamily>(alternative.source), lane, laneCount,
|
||||
context);
|
||||
}
|
||||
|
||||
static void recordAvailability(const ResolveAvailability& availability,
|
||||
Value value,
|
||||
DeferredEmissionContext& context) {
|
||||
for (const AvailabilityAlternative& alternative : availability.alternatives) {
|
||||
if (auto receive = std::get_if<EmitReceiveRun>(&alternative.source)) {
|
||||
for (const ScheduledTransferSlice& slice : receive->slices)
|
||||
context.receives[slice.family->requirement] = value;
|
||||
continue;
|
||||
}
|
||||
for (LocalAvailabilityFamily* family :
|
||||
std::get<MaterializeLocalFamily>(alternative.source).families)
|
||||
context.receives[family->requirement] = value;
|
||||
}
|
||||
}
|
||||
|
||||
static FailureOr<SmallVector<Value>> emitSelection(
|
||||
Value branch, ArrayRef<Type> resultTypes, size_t branchCount, Location loc,
|
||||
DeferredEmissionContext& context,
|
||||
llvm::function_ref<LogicalResult(Region&, size_t)> emitRegion) {
|
||||
SmallVector<Value> results;
|
||||
if (branchCount == 2) {
|
||||
Value condition = arith::CmpIOp::create(context.rewriter, loc,
|
||||
arith::CmpIPredicate::eq, branch, context.constants.getIndex(0));
|
||||
auto selection = scf::IfOp::create(
|
||||
context.rewriter, loc, resultTypes, condition, true);
|
||||
if (failed(emitRegion(selection.getThenRegion(), 0))
|
||||
|| failed(emitRegion(selection.getElseRegion(), 1)))
|
||||
return failure();
|
||||
llvm::append_range(results, selection.getResults());
|
||||
return results;
|
||||
}
|
||||
SmallVector<int64_t> cases;
|
||||
for (size_t index = 0; index + 1 < branchCount; ++index)
|
||||
cases.push_back(index);
|
||||
auto selection = scf::IndexSwitchOp::create(
|
||||
context.rewriter, loc, resultTypes, branch, cases, cases.size());
|
||||
for (auto [index, region] : llvm::enumerate(selection.getCaseRegions()))
|
||||
if (failed(emitRegion(region, index)))
|
||||
return failure();
|
||||
if (failed(emitRegion(selection.getDefaultRegion(), branchCount - 1)))
|
||||
return failure();
|
||||
llvm::append_range(results, selection.getResults());
|
||||
return results;
|
||||
}
|
||||
|
||||
static LogicalResult emitAvailability(const ResolveAvailability& availability,
|
||||
Value lane,
|
||||
unsigned laneCount,
|
||||
DeferredEmissionContext& context) {
|
||||
if (availability.alternatives.empty())
|
||||
return failure();
|
||||
if (availability.alternatives.size() == 1) {
|
||||
auto value = emitAvailabilityAlternative(availability.alternatives.front(), lane, laneCount, context);
|
||||
if (failed(value))
|
||||
return failure();
|
||||
recordAvailability(availability, *value, context);
|
||||
return success();
|
||||
}
|
||||
if (!lane)
|
||||
return failure();
|
||||
const AvailabilityAlternative& first = availability.alternatives.front();
|
||||
RequirementFamily* requirement = getAvailabilityRequirement(first);
|
||||
Location loc = requirement->exchange->deferred.getLoc();
|
||||
SmallVector<Type> resultTypes {requirement->publicationFragmentType};
|
||||
auto emitRegion = [&](Region& region, const AvailabilityAlternative& alternative) {
|
||||
OpBuilder::InsertionGuard guard(context.rewriter);
|
||||
Block& block = region.empty() ? *context.rewriter.createBlock(®ion)
|
||||
: region.front();
|
||||
auto yield = block.empty() ? scf::YieldOp() : dyn_cast<scf::YieldOp>(&block.back());
|
||||
if (yield)
|
||||
context.rewriter.setInsertionPoint(yield);
|
||||
else
|
||||
context.rewriter.setInsertionPointToEnd(&block);
|
||||
auto value = emitAvailabilityAlternative(alternative, lane, laneCount, context);
|
||||
if (failed(value))
|
||||
return failure();
|
||||
if (yield)
|
||||
yield->setOperands(ValueRange {*value});
|
||||
else
|
||||
scf::YieldOp::create(context.rewriter, loc, ValueRange {*value});
|
||||
return success();
|
||||
};
|
||||
SmallVector<int64_t> branchByLane(laneCount);
|
||||
SmallVector<bool> assigned(laneCount);
|
||||
for (auto [branch, alternative] : llvm::enumerate(availability.alternatives))
|
||||
for (LaneInterval interval : alternative.lanes.intervals())
|
||||
for (unsigned activeLane = interval.begin; activeLane < interval.end;
|
||||
++activeLane) {
|
||||
if (assigned[activeLane])
|
||||
return failure();
|
||||
assigned[activeLane] = true;
|
||||
branchByLane[activeLane] = branch;
|
||||
}
|
||||
Value branch = emitStaticIntLookup(StaticIntSequence::fromValues(branchByLane),
|
||||
lane,
|
||||
requirement->exchange->deferred,
|
||||
context.constants,
|
||||
context.rewriter,
|
||||
loc);
|
||||
auto selected = emitSelection(branch, resultTypes,
|
||||
availability.alternatives.size(), loc, context,
|
||||
[&](Region& region, size_t index) {
|
||||
return emitRegion(region, availability.alternatives[index]);
|
||||
});
|
||||
if (failed(selected))
|
||||
return failure();
|
||||
Value value = selected->front();
|
||||
recordAvailability(availability, value, context);
|
||||
return success();
|
||||
}
|
||||
|
||||
static FailureOr<SmallVector<Value>> emitInstructions(
|
||||
const BoundaryInstructionList& list, Value lane, unsigned laneCount,
|
||||
ArrayRef<DeferredResultPlan> results, DeferredEmissionContext& context);
|
||||
|
||||
static FailureOr<SmallVector<Value>> emitDispatch(
|
||||
const LaneDispatch& dispatch, Value lane, unsigned laneCount,
|
||||
ArrayRef<DeferredResultPlan> results, DeferredEmissionContext& context) {
|
||||
if (dispatch.branches.empty())
|
||||
return failure();
|
||||
if (dispatch.branches.size() == 1)
|
||||
return emitInstructions(
|
||||
dispatch.branches.front(), lane, laneCount, results, context);
|
||||
if (!lane)
|
||||
return failure();
|
||||
|
||||
SmallVector<Type> resultTypes;
|
||||
for (DeferredExchangePlan* exchange : dispatch.producedExchanges)
|
||||
resultTypes.push_back(exchange->deferred.getOutput().getType());
|
||||
Location loc = dispatch.producedExchanges.empty()
|
||||
? lane.getLoc()
|
||||
: dispatch.producedExchanges.front()->deferred.getLoc();
|
||||
Value branch = emitStaticIntLookup(dispatch.branchByLane,
|
||||
lane,
|
||||
dispatch.producedExchanges.empty()
|
||||
? lane.getDefiningOp()
|
||||
: dispatch.producedExchanges.front()->deferred,
|
||||
context.constants,
|
||||
context.rewriter,
|
||||
loc);
|
||||
auto receives = context.receives;
|
||||
auto assemblies = context.assemblies;
|
||||
auto projectionAssemblies = context.projectionAssemblies;
|
||||
auto emitRegion = [&](Region& region,
|
||||
const BoundaryInstructionList& instructions) {
|
||||
context.receives = receives;
|
||||
context.assemblies = assemblies;
|
||||
context.projectionAssemblies = projectionAssemblies;
|
||||
OpBuilder::InsertionGuard guard(context.rewriter);
|
||||
Block& block = region.empty() ? *context.rewriter.createBlock(®ion)
|
||||
: region.front();
|
||||
auto yield = block.empty() ? scf::YieldOp()
|
||||
: dyn_cast<scf::YieldOp>(&block.back());
|
||||
if (yield)
|
||||
context.rewriter.setInsertionPoint(yield);
|
||||
else
|
||||
context.rewriter.setInsertionPointToEnd(&block);
|
||||
auto values = emitInstructions(
|
||||
instructions, lane, laneCount, results, context);
|
||||
if (failed(values))
|
||||
return failure();
|
||||
if (yield)
|
||||
yield->setOperands(*values);
|
||||
else
|
||||
scf::YieldOp::create(context.rewriter, loc, *values);
|
||||
return success();
|
||||
};
|
||||
|
||||
auto produced = emitSelection(branch, resultTypes, dispatch.branches.size(),
|
||||
loc, context, [&](Region& region, size_t index) {
|
||||
return emitRegion(region, dispatch.branches[index]);
|
||||
});
|
||||
if (failed(produced))
|
||||
return failure();
|
||||
context.receives = std::move(receives);
|
||||
context.assemblies = std::move(assemblies);
|
||||
context.projectionAssemblies = std::move(projectionAssemblies);
|
||||
return *produced;
|
||||
}
|
||||
|
||||
static FailureOr<SmallVector<Value>> emitInstructions(
|
||||
const BoundaryInstructionList& list, Value lane, unsigned laneCount,
|
||||
ArrayRef<DeferredResultPlan> results, DeferredEmissionContext& context) {
|
||||
SmallVector<Value> produced;
|
||||
for (const BoundaryInstruction& instruction : list.instructions) {
|
||||
if (auto send = std::get_if<EmitSendRun>(&instruction)) {
|
||||
if (failed(emitConditionalSendRun(*send, lane, laneCount, context)))
|
||||
return failure();
|
||||
continue;
|
||||
}
|
||||
if (auto availability = std::get_if<ResolveAvailability>(&instruction)) {
|
||||
if (failed(emitAvailability(*availability, lane, laneCount, context)))
|
||||
return failure();
|
||||
continue;
|
||||
}
|
||||
if (auto assembly = std::get_if<EmitReceiveAssemblyRun>(&instruction)) {
|
||||
auto value = assembly->projectionLeaf
|
||||
? emitProjectionAssemblyRun(
|
||||
*assembly, lane, laneCount, context)
|
||||
: emitAssemblyRun(
|
||||
*assembly, lane, laneCount, results, context);
|
||||
if (failed(value))
|
||||
return failure();
|
||||
continue;
|
||||
}
|
||||
if (auto result = std::get_if<ProduceDeferredResult>(&instruction)) {
|
||||
const DeferredResultPlan* plan = findResultPlan(results, result->exchange);
|
||||
if (!plan)
|
||||
return failure();
|
||||
auto value = realizeDeferredResult(*plan, result->lanes, lane, context);
|
||||
if (failed(value))
|
||||
return failure();
|
||||
produced.push_back(*value);
|
||||
continue;
|
||||
}
|
||||
auto values = emitDispatch(
|
||||
**std::get_if<std::unique_ptr<LaneDispatch>>(&instruction), lane,
|
||||
laneCount, results, context);
|
||||
if (failed(values))
|
||||
return failure();
|
||||
llvm::append_range(produced, *values);
|
||||
}
|
||||
return produced;
|
||||
}
|
||||
|
||||
static SmallVector<DeferredExchangePlan*> getProducedExchanges(
|
||||
const BoundaryInstructionList& list) {
|
||||
SmallVector<DeferredExchangePlan*> exchanges;
|
||||
for (const BoundaryInstruction& instruction : list.instructions) {
|
||||
if (auto result = std::get_if<ProduceDeferredResult>(&instruction))
|
||||
exchanges.push_back(result->exchange);
|
||||
else if (auto dispatch =
|
||||
std::get_if<std::unique_ptr<LaneDispatch>>(&instruction))
|
||||
llvm::append_range(exchanges, (*dispatch)->producedExchanges);
|
||||
}
|
||||
return exchanges;
|
||||
}
|
||||
|
||||
static void setInsertionAtBoundary(IRRewriter& rewriter, const BoundaryKey& key) {
|
||||
ScheduledInfo& scheduled = *key.scheduled;
|
||||
if (key.insertionStep < scheduled.stepAnchors.size() && scheduled.stepAnchors[key.insertionStep]->getBlock()) {
|
||||
rewriter.setInsertionPoint(scheduled.stepAnchors[key.insertionStep]);
|
||||
return;
|
||||
}
|
||||
rewriter.setInsertionPoint(scheduled.blocks.front()->getTerminator());
|
||||
}
|
||||
|
||||
static LogicalResult
|
||||
replaceResults(ArrayRef<DeferredExchangePlan*> exchanges, ValueRange replacements, DeferredEraseSet& erase) {
|
||||
if (exchanges.size() != replacements.size())
|
||||
return failure();
|
||||
for (auto [exchange, replacement] : llvm::zip_equal(exchanges, replacements)) {
|
||||
exchange->deferred.getOutput().replaceAllUsesWith(replacement);
|
||||
erase.insert(exchange->deferred);
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
static LogicalResult emitBoundary(const BoundaryProgram& boundary,
|
||||
ArrayRef<DeferredResultPlan> results,
|
||||
DeferredEmissionContext& context,
|
||||
DeferredEraseSet& erase) {
|
||||
setInsertionAtBoundary(context.rewriter, boundary.key);
|
||||
unsigned laneCount = boundary.key.scheduled->cores.size();
|
||||
Value lane;
|
||||
if (auto batch = dyn_cast<SpatScheduledComputeBatch>(boundary.key.scheduled->op))
|
||||
lane = *batch.getLaneArgument();
|
||||
SmallVector<DeferredExchangePlan*> exchanges =
|
||||
getProducedExchanges(boundary.root);
|
||||
auto values = emitInstructions(
|
||||
boundary.root, lane, laneCount, results, context);
|
||||
return failed(values) ? failure()
|
||||
: replaceResults(exchanges, *values, erase);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
LogicalResult realizeDeferredBoundaries(ArrayRef<BoundaryProgram> boundaries,
|
||||
ArrayRef<DeferredResultPlan> results,
|
||||
DeferredEmissionContext& context,
|
||||
DeferredEraseSet& erase) {
|
||||
ScheduledInfo* scheduled = nullptr;
|
||||
for (const BoundaryProgram& boundary : boundaries) {
|
||||
if (scheduled != boundary.key.scheduled) {
|
||||
context.receives.clear();
|
||||
context.assemblies.clear();
|
||||
context.projectionAssemblies.clear();
|
||||
scheduled = boundary.key.scheduled;
|
||||
}
|
||||
if (failed(emitBoundary(boundary, results, context, erase)))
|
||||
return boundary.key.scheduled->op->emitOpError("phase 2 failed to realize a communication boundary");
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
} // namespace onnx_mlir::spatial
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "mlir/IR/PatternMatch.h"
|
||||
|
||||
#include "DeferredBoundaryPlanning.hpp"
|
||||
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
|
||||
|
||||
namespace onnx_mlir::spatial {
|
||||
|
||||
struct DeferredEmissionContext {
|
||||
DeferredEmissionContext(mlir::IRRewriter& rewriter,
|
||||
ConstantPool& constants)
|
||||
: rewriter(rewriter), constants(constants) {}
|
||||
|
||||
mlir::IRRewriter& rewriter;
|
||||
ConstantPool& constants;
|
||||
llvm::DenseMap<RequirementFamily*, mlir::Value> receives;
|
||||
llvm::DenseMap<DeferredExchangePlan*, mlir::Value> assemblies;
|
||||
llvm::DenseMap<std::pair<DeferredExchangePlan*, unsigned>, mlir::Value>
|
||||
projectionAssemblies;
|
||||
};
|
||||
|
||||
using DeferredEraseSet = llvm::SetVector<mlir::Operation*>;
|
||||
|
||||
mlir::LogicalResult realizeDeferredBoundaries(mlir::ArrayRef<BoundaryProgram> boundaries,
|
||||
mlir::ArrayRef<DeferredResultPlan> results,
|
||||
DeferredEmissionContext& context,
|
||||
DeferredEraseSet& erase);
|
||||
|
||||
} // namespace onnx_mlir::spatial
|
||||
+462
@@ -0,0 +1,462 @@
|
||||
#include "DeferredCommunicationDeadlock.hpp"
|
||||
|
||||
#include "src/Accelerators/PIM/Common/IR/StaticIntSequence.hpp"
|
||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
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 {
|
||||
Operation *op = nullptr;
|
||||
bool send = false;
|
||||
StaticIntSequence channels;
|
||||
StaticIntSequence parents;
|
||||
StaticIntSequence counts;
|
||||
StaticIntSequence sources;
|
||||
StaticIntSequence targets;
|
||||
};
|
||||
|
||||
static FailureOr<size_t> getBatchTransferCount(Operation *op) {
|
||||
if (auto count = op->getAttrOfType<IntegerAttr>(
|
||||
"raptor.batch_transfer_count")) {
|
||||
if (count.getInt() > 0)
|
||||
return count.getInt();
|
||||
return op->emitOpError("has invalid compact transfer count"), failure();
|
||||
}
|
||||
if (op->hasAttr("raptor.batch_channel_ids_encoding"))
|
||||
return op->emitOpError("is missing compact transfer count"), failure();
|
||||
Attribute channels = op->getAttr("raptor.batch_channel_ids");
|
||||
if (auto array = dyn_cast_or_null<DenseI64ArrayAttr>(channels))
|
||||
return array.empty()
|
||||
? FailureOr<size_t>(failure())
|
||||
: FailureOr<size_t>(array.size());
|
||||
if (auto elements = dyn_cast_or_null<DenseIntElementsAttr>(channels);
|
||||
elements && elements.getNumElements() > 0)
|
||||
return elements.getNumElements();
|
||||
return op->emitOpError("has invalid legacy compact transfer metadata"),
|
||||
failure();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
auto batchCount = scalar ? FailureOr<size_t>(1)
|
||||
: getBatchTransferCount(op);
|
||||
if (failed(batchCount))
|
||||
return failure();
|
||||
size_t size = *batchCount;
|
||||
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 {op, isa<SpatChannelSendOp>(op),
|
||||
*channels, *parents, *counts, *sources, *targets};
|
||||
}
|
||||
|
||||
struct CoreTransferSequences {
|
||||
DenseMap<int64_t, StaticIntSequenceChain> sends;
|
||||
DenseMap<int64_t, StaticIntSequenceChain> receives;
|
||||
DenseMap<int64_t, StaticIntSequenceChain> events;
|
||||
};
|
||||
|
||||
struct ExpectedFamily {
|
||||
ExternalTransferFamily *family = nullptr;
|
||||
int64_t firstChannel = 0;
|
||||
int64_t endChannel = 0;
|
||||
};
|
||||
|
||||
static void appendByCore(DenseMap<int64_t, StaticIntSequenceChain> &result,
|
||||
const StaticIntSequence &channels,
|
||||
const StaticIntSequence &cores, size_t begin,
|
||||
size_t count) {
|
||||
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)
|
||||
result[core].append(
|
||||
channels, selectedBegin, selectedEnd - selectedBegin);
|
||||
});
|
||||
}
|
||||
|
||||
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 compareSequences(
|
||||
func::FuncOp funcOp,
|
||||
const DenseMap<int64_t, StaticIntSequenceChain> &expected,
|
||||
const DenseMap<int64_t, StaticIntSequenceChain> &actual, StringRef kind) {
|
||||
if (expected.size() != actual.size())
|
||||
return funcOp.emitOpError()
|
||||
<< "realized " << kind << " stream set differs from plan";
|
||||
for (const auto &[core, sequence] : expected) {
|
||||
auto found = actual.find(core);
|
||||
if (found == actual.end())
|
||||
return funcOp.emitOpError() << "realized " << kind
|
||||
<< " 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;
|
||||
return funcOp.emitOpError()
|
||||
<< "realized " << kind << " logical order differs on core "
|
||||
<< core << " at ordinal " << ordinal << ": expected channel "
|
||||
<< (expectedCursor.done() ? -1 : expectedCursor.value())
|
||||
<< ", actual channel "
|
||||
<< (actualCursor.done() ? -1 : actualCursor.value());
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
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) {
|
||||
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<ExpectedFamily> families;
|
||||
DenseMap<ExternalTransferFamily *, unsigned> familyIndex;
|
||||
for (const ScheduledTransferSlice &slice : plan.slices) {
|
||||
ExternalTransferFamily *family = slice.family;
|
||||
if (familyIndex.count(family))
|
||||
continue;
|
||||
size_t count = family->channelIds.size();
|
||||
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 funcOp.emitOpError(
|
||||
"planned communication family has non-consecutive channels");
|
||||
familyIndex[family] = families.size();
|
||||
families.push_back({family, first, first + static_cast<int64_t>(count)});
|
||||
}
|
||||
llvm::sort(families, [](const ExpectedFamily &lhs,
|
||||
const ExpectedFamily &rhs) {
|
||||
return lhs.firstChannel < rhs.firstChannel;
|
||||
});
|
||||
familyIndex.clear();
|
||||
std::map<int64_t, unsigned> familyByFirstChannel;
|
||||
int64_t nextChannel = 0;
|
||||
for (auto [index, expected] : llvm::enumerate(families)) {
|
||||
if (expected.firstChannel != nextChannel)
|
||||
return funcOp.emitOpError(
|
||||
"planned communication channels are not exactly contiguous");
|
||||
nextChannel = expected.endChannel;
|
||||
familyIndex[expected.family] = index;
|
||||
familyByFirstChannel.emplace(expected.firstChannel, index);
|
||||
}
|
||||
if (static_cast<uint64_t>(nextChannel) != plan.logicalTransferCount)
|
||||
return funcOp.emitOpError(
|
||||
"planned communication channel count is inconsistent");
|
||||
|
||||
CoreTransferSequences expected;
|
||||
for (const ScheduledTransferSlice &slice : plan.slices) {
|
||||
ExternalTransferFamily &family = *slice.family;
|
||||
appendByCore(expected.sends, family.channelIds, family.sourceCores,
|
||||
slice.familyOffset, slice.transferCount);
|
||||
appendByCore(expected.receives, family.channelIds, family.targetCores,
|
||||
slice.familyOffset, slice.transferCount);
|
||||
appendEventsByCore(expected.events, family.channelIds, family.sourceCores,
|
||||
slice.familyOffset, slice.transferCount, true);
|
||||
appendEventsByCore(expected.events, family.channelIds, family.targetCores,
|
||||
slice.familyOffset, slice.transferCount, false);
|
||||
}
|
||||
|
||||
CoreTransferSequences 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);
|
||||
auto upper = familyByFirstChannel.upper_bound(channel);
|
||||
if (upper == familyByFirstChannel.begin()) {
|
||||
op->emitOpError("references an unknown logical channel");
|
||||
invalid = true;
|
||||
return;
|
||||
}
|
||||
ExpectedFamily &expected = families[std::prev(upper)->second];
|
||||
if (channel >= expected.endChannel) {
|
||||
op->emitOpError("references an unknown logical channel");
|
||||
invalid = true;
|
||||
return;
|
||||
}
|
||||
ExternalTransferFamily &family = *expected.family;
|
||||
size_t familyOffset = channel - expected.firstChannel;
|
||||
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)));
|
||||
appendByCore(realized->send ? actual.sends : actual.receives,
|
||||
*actualChannels.back(),
|
||||
realized->send ? realized->sources : realized->targets,
|
||||
0, actualChannels.back()->size());
|
||||
appendEventsByCore(actual.events, *actualChannels.back(),
|
||||
realized->send ? realized->sources : realized->targets,
|
||||
0, actualChannels.back()->size(), realized->send);
|
||||
});
|
||||
if (invalid)
|
||||
return failure();
|
||||
if (failed(compareSequences(
|
||||
funcOp, expected.sends, actual.sends, "send"))
|
||||
|| failed(compareSequences(
|
||||
funcOp, expected.receives, actual.receives, "receive"))
|
||||
|| failed(compareEventSequences(
|
||||
funcOp, expected.events, actual.events)))
|
||||
return failure();
|
||||
return success();
|
||||
}
|
||||
|
||||
} // 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,236 @@
|
||||
#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 leafIndex = 0;
|
||||
unsigned selectedPosition = 0;
|
||||
|
||||
bool operator==(const RequirementCoordinate& other) const {
|
||||
return leafIndex == other.leafIndex && selectedPosition == other.selectedPosition;
|
||||
}
|
||||
};
|
||||
|
||||
enum class DeferredLeafForm {
|
||||
DirectSource,
|
||||
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 DeferredProjectionLeafTemplate {
|
||||
DeferredLeafForm form = DeferredLeafForm::DirectSource;
|
||||
mlir::Value sourceRoot;
|
||||
mlir::Value replacementRoot;
|
||||
mlir::tensor::ExtractSliceOp leadingProjection;
|
||||
DeferredSliceTemplate leadingGeometry;
|
||||
DeferredSliceTemplate innerGeometry;
|
||||
mlir::RankedTensorType reconstructedType;
|
||||
};
|
||||
|
||||
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;
|
||||
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;
|
||||
llvm::SmallVector<int64_t> stepSourceIds;
|
||||
llvm::SmallVector<int64_t> resultOffsets;
|
||||
llvm::SmallVector<int64_t> resultCounts;
|
||||
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;
|
||||
};
|
||||
|
||||
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
|
||||
+432
@@ -0,0 +1,432 @@
|
||||
#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/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));
|
||||
SmallVector<int64_t> selectedShape {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));
|
||||
selectedShape.push_back(size);
|
||||
}
|
||||
auto selectedType = RankedTensorType::get(selectedShape, resultType.getElementType());
|
||||
Value selected = tensor::ExtractSliceOp::create(
|
||||
builder, loc, selectedType, sourceBlockArgs[operandIndex], sliceOffsets,
|
||||
sliceSizes, sliceStrides);
|
||||
SmallVector<int64_t> fragmentResultShape(selectedShape.begin() + 1,
|
||||
selectedShape.end());
|
||||
auto fragmentType = RankedTensorType::get(fragmentResultShape,
|
||||
resultType.getElementType());
|
||||
SmallVector<ReassociationIndices> reassociation {{0, 1}};
|
||||
for (int64_t dim = 1; dim < rank; ++dim)
|
||||
reassociation.push_back({dim + 1});
|
||||
Value fragment = tensor::CollapseShapeOp::create(
|
||||
builder, loc, fragmentType, selected, reassociation);
|
||||
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> buildIndexSwitchSelection(OpBuilder &builder, Location loc,
|
||||
Value selector, ValueRange candidates,
|
||||
Operation *diagnosticOwner) {
|
||||
if (candidates.empty())
|
||||
return diagnosticOwner->emitOpError("direct selection requires at least one candidate"), failure();
|
||||
Type type = candidates.front().getType();
|
||||
if (llvm::any_of(candidates, [&](Value candidate) { return candidate.getType() != type; }))
|
||||
return diagnosticOwner->emitOpError("direct selection requires identical candidate types"), failure();
|
||||
if (candidates.size() == 1)
|
||||
return candidates.front();
|
||||
|
||||
SmallVector<int64_t> cases;
|
||||
for (int64_t index = 0; index < static_cast<int64_t>(candidates.size()) - 1; ++index)
|
||||
cases.push_back(index);
|
||||
auto selection = scf::IndexSwitchOp::create(
|
||||
builder, loc, TypeRange {type}, selector, cases, cases.size());
|
||||
auto buildYield = [&](Region ®ion, Value candidate) {
|
||||
OpBuilder::InsertionGuard guard(builder);
|
||||
Block *block = builder.createBlock(®ion);
|
||||
builder.setInsertionPointToEnd(block);
|
||||
scf::YieldOp::create(builder, loc, candidate);
|
||||
};
|
||||
for (auto [region, candidate] : llvm::zip(selection.getCaseRegions(), candidates.drop_back()))
|
||||
buildYield(region, candidate);
|
||||
// The scheduled-lane verifier guarantees an in-range selector, so default is
|
||||
// the final lane without an otherwise-unreachable extra branch.
|
||||
buildYield(selection.getDefaultRegion(), candidates.back());
|
||||
return selection.getResult(0);
|
||||
}
|
||||
|
||||
static FailureOr<Value> buildSelectedDeferredSource(OpBuilder &builder, Location loc,
|
||||
SpatDeferredCommunicationOp transfer,
|
||||
Value scheduledLane,
|
||||
ValueRange sourceBlockArgs,
|
||||
ArrayRef<int64_t> sourceOperandForScheduledLane) {
|
||||
if (sourceBlockArgs.size() == 1)
|
||||
return sourceBlockArgs.front();
|
||||
if (!scheduledLane || sourceOperandForScheduledLane.empty())
|
||||
return transfer.emitOpError("multiple deferred sources require the enclosing scheduled lane"), failure();
|
||||
auto scheduled = transfer->getParentOfType<SpatScheduledComputeBatch>();
|
||||
if (!scheduled || sourceOperandForScheduledLane.size() != static_cast<size_t>(scheduled.getLaneCount()))
|
||||
return transfer.emitOpError("deferred source mapping must cover every scheduled lane"), failure();
|
||||
SmallVector<Value> candidates;
|
||||
candidates.reserve(sourceOperandForScheduledLane.size());
|
||||
for (int64_t sourceIndex : sourceOperandForScheduledLane) {
|
||||
if (sourceIndex < 0 || sourceIndex >= static_cast<int64_t>(sourceBlockArgs.size()))
|
||||
return transfer.emitOpError("deferred source mapping operand is out of range"), failure();
|
||||
candidates.push_back(sourceBlockArgs[sourceIndex]);
|
||||
}
|
||||
return buildIndexSwitchSelection(builder, loc, scheduledLane, candidates, transfer.getOperation());
|
||||
}
|
||||
|
||||
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 (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,
|
||||
ArrayRef<ProducerValueKey> carriedKeys, 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 carried = llvm::find(carriedKeys, key);
|
||||
if (carried != carriedKeys.end()) {
|
||||
plan.availableValue = block.getArgument(firstInputArgument + scheduledInputs.size() + std::distance(carriedKeys.begin(), carried));
|
||||
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());
|
||||
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";
|
||||
}
|
||||
SmallVector<Value> payloads;
|
||||
unsigned count = scalarize ? plan.scalarizedLaneCount : 1;
|
||||
for (unsigned offset = 0; offset < count; ++offset) {
|
||||
auto transfer = SpatDeferredCommunicationOp::create(builder, loc, root.getType(), plan.originalSources);
|
||||
Block *deferred = builder.createBlock(&transfer.getBody(), transfer.getBody().end(),
|
||||
TypeRange {transfer.getSources().getTypes()}, SmallVector<Location>(transfer.getSources().size(), loc));
|
||||
builder.setInsertionPointToStart(deferred);
|
||||
auto selected = plan.blueprint
|
||||
? buildBlueprintReconstruction(builder, loc, plan.blueprint,
|
||||
deferred->getArguments())
|
||||
: buildSelectedDeferredSource(builder, loc, transfer,
|
||||
plan.scheduledLane,
|
||||
deferred->getArguments(),
|
||||
plan.sourceOperandForScheduledLane);
|
||||
if (failed(selected)) return failure();
|
||||
Value boundGraphLane;
|
||||
if (scalarize) {
|
||||
boundGraphLane = affineAddConst(
|
||||
builder, loc, plan.scalarizedGraphLaneBase, offset, transfer.getOperation());
|
||||
}
|
||||
auto payload = clonePayloadRoot(root, body, plan, builder, transfer, *selected, boundGraphLane);
|
||||
if (failed(payload)) return failure();
|
||||
SpatYieldOp::create(builder, loc, *payload);
|
||||
payloads.push_back(transfer.getOutput());
|
||||
builder.setInsertionPointAfter(transfer);
|
||||
}
|
||||
if (scalarize) {
|
||||
builder.restoreInsertionPoint(restore);
|
||||
auto selected = buildIndexSwitchSelection(
|
||||
builder, loc, plan.scalarizedLocalLane, payloads, root.getDefiningOp());
|
||||
if (failed(selected)) return failure();
|
||||
mapper.map(root, *selected);
|
||||
} else {
|
||||
mapper.map(root, payloads.front());
|
||||
}
|
||||
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,
|
||||
ArrayRef<ProducerValueKey> carriedKeys,
|
||||
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
|
||||
+272
@@ -0,0 +1,272 @@
|
||||
#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 validateScalarLinearization(ScheduledInfo &info) {
|
||||
auto scheduled = cast<SpatScheduledCompute>(info.op);
|
||||
for (unsigned index = 1; index < info.blocks.size(); ++index) {
|
||||
auto previous = dyn_cast<SpatBlockYieldOp>(
|
||||
info.blocks[index - 1]->getTerminator());
|
||||
if (!previous
|
||||
|| previous.getOutputs().size()
|
||||
!= info.blocks[index]->getNumArguments())
|
||||
return scheduled.emitOpError(
|
||||
"phase 2 cannot linearize malformed scalar scheduled blocks");
|
||||
for (auto [argument, value] : llvm::zip(
|
||||
info.blocks[index]->getArguments(), previous.getOutputs())) {
|
||||
if (argument.getType() == value.getType())
|
||||
continue;
|
||||
for (Operation *user : argument.getUsers())
|
||||
if (!isa<SpatBlockYieldOp>(user))
|
||||
return scheduled.emitOpError(
|
||||
"phase 2 cannot linearize a live mismatched carried value");
|
||||
}
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
static LogicalResult linearizeScalar(ScheduledInfo &info,
|
||||
IRRewriter &rewriter) {
|
||||
auto scheduled = cast<SpatScheduledCompute>(info.op);
|
||||
if (failed(validateScalarLinearization(info)))
|
||||
return failure();
|
||||
Block *first = info.blocks.front();
|
||||
SmallVector<SmallVector<Value>> incoming(info.blocks.size());
|
||||
for (unsigned index = 1; index < info.blocks.size(); ++index) {
|
||||
auto previous = cast<SpatBlockYieldOp>(
|
||||
info.blocks[index - 1]->getTerminator());
|
||||
incoming[index].assign(previous.getOutputs().begin(),
|
||||
previous.getOutputs().end());
|
||||
}
|
||||
IRMapping carried;
|
||||
for (unsigned index = 1; index < info.blocks.size(); ++index)
|
||||
for (auto [argument, value] : llvm::zip(
|
||||
info.blocks[index]->getArguments(), incoming[index])) {
|
||||
Value resolved = carried.lookupOrDefault(value);
|
||||
if (argument.getType() == resolved.getType()) {
|
||||
carried.map(argument, resolved);
|
||||
argument.replaceAllUsesWith(resolved);
|
||||
}
|
||||
}
|
||||
for (unsigned index = 1; index < info.blocks.size(); ++index)
|
||||
for (Operation &op : llvm::make_early_inc_range(
|
||||
info.blocks[index]->without_terminator()))
|
||||
op.moveBefore(first->getTerminator());
|
||||
for (Block *block : info.blocks)
|
||||
cast<SpatBlockYieldOp>(block->getTerminator()).erase();
|
||||
SmallVector<Value> outputs(scheduled.getNumResults());
|
||||
for (ProducedValue *produced : info.produced) {
|
||||
unsigned result = info.resultOffsets[produced->step]
|
||||
+ produced->resultIndex;
|
||||
outputs[result] = produced->payload;
|
||||
}
|
||||
if (llvm::any_of(outputs, [](Value output) { return !output; }))
|
||||
return scheduled.emitOpError(
|
||||
"phase 2 cannot recover every scheduled scalar result");
|
||||
rewriter.setInsertionPointToEnd(first);
|
||||
SpatYieldOp::create(rewriter, scheduled.getLoc(), outputs);
|
||||
scheduled->setAttr("scheduled.realized", rewriter.getBoolAttr(true));
|
||||
for (unsigned index = 1; index < info.blocks.size(); ++index) {
|
||||
if (llvm::any_of(info.blocks[index]->getArguments(),
|
||||
[](BlockArgument argument) {
|
||||
return !argument.use_empty();
|
||||
}))
|
||||
return scheduled.emitOpError(
|
||||
"phase 2 scalar linearization left a live block argument");
|
||||
info.blocks[index]->erase();
|
||||
}
|
||||
info.blocks.assign(1, first);
|
||||
return success();
|
||||
}
|
||||
|
||||
static LogicalResult linearizeBatch(ScheduledInfo &info,
|
||||
IRRewriter &rewriter) {
|
||||
auto scheduled = cast<SpatScheduledComputeBatch>(info.op);
|
||||
Block *first = info.blocks.front();
|
||||
SmallVector<SpatInParallelOp> terminators;
|
||||
for (Block *block : info.blocks) {
|
||||
auto parallel = dyn_cast<SpatInParallelOp>(block->getTerminator());
|
||||
if (!parallel)
|
||||
return scheduled.emitOpError(
|
||||
"phase 2 cannot linearize a batch block without spat.in_parallel");
|
||||
terminators.push_back(parallel);
|
||||
}
|
||||
for (unsigned index = 1; index < info.blocks.size(); ++index)
|
||||
for (auto [argument, firstArgument] : llvm::zip(
|
||||
info.blocks[index]->getArguments(), first->getArguments())) {
|
||||
if (argument.getType() != firstArgument.getType())
|
||||
return scheduled.emitOpError(
|
||||
"phase 2 cannot linearize incompatible batch block arguments");
|
||||
argument.replaceAllUsesWith(firstArgument);
|
||||
}
|
||||
for (unsigned index = 1; index < info.blocks.size(); ++index)
|
||||
for (Operation &op : llvm::make_early_inc_range(
|
||||
info.blocks[index]->without_terminator()))
|
||||
op.moveBefore(first->getTerminator());
|
||||
rewriter.setInsertionPoint(first->getTerminator());
|
||||
auto combined = SpatInParallelOp::create(rewriter, scheduled.getLoc());
|
||||
Block &combinedBlock = combined.getRegion().front();
|
||||
for (SpatInParallelOp parallel : terminators)
|
||||
for (Operation &op : llvm::make_early_inc_range(
|
||||
parallel.getRegion().front()))
|
||||
op.moveBefore(&combinedBlock, combinedBlock.end());
|
||||
for (SpatInParallelOp parallel : terminators)
|
||||
parallel.erase();
|
||||
scheduled->setAttr("scheduled.realized", rewriter.getBoolAttr(true));
|
||||
for (unsigned index = 1; index < info.blocks.size(); ++index) {
|
||||
if (llvm::any_of(info.blocks[index]->getArguments(),
|
||||
[](BlockArgument argument) {
|
||||
return !argument.use_empty();
|
||||
}))
|
||||
return scheduled.emitOpError(
|
||||
"phase 2 batch linearization left a live block argument");
|
||||
info.blocks[index]->erase();
|
||||
}
|
||||
info.blocks.assign(1, first);
|
||||
return success();
|
||||
}
|
||||
|
||||
static LogicalResult linearizeScheduled(
|
||||
DeferredTransferPlan &plan, IRRewriter &rewriter) {
|
||||
for (ScheduledInfo &info : plan.scheduled) {
|
||||
LogicalResult result = info.isBatch()
|
||||
? linearizeBatch(info, rewriter) : linearizeScalar(info, rewriter);
|
||||
if (failed(result))
|
||||
return failure();
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
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())
|
||||
if (!isa<SpatGraphCompute, SpatGraphComputeBatch>(use.getOwner()))
|
||||
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.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 *> graphOps;
|
||||
for (Operation &op : funcOp.getOps())
|
||||
if (isa<SpatGraphCompute, SpatGraphComputeBatch>(op))
|
||||
graphOps.push_back(&op);
|
||||
for (Operation *op : llvm::reverse(graphOps)) {
|
||||
if (!op->use_empty())
|
||||
return op->emitOpError(
|
||||
"phase 2 cannot erase an old graph compute with live results");
|
||||
rewriter.eraseOp(op);
|
||||
}
|
||||
SmallVector<SpatBlueprintOp> deadBlueprints;
|
||||
funcOp.walk([&](SpatBlueprintOp blueprint) {
|
||||
if (blueprint.getOutput().use_empty())
|
||||
deadBlueprints.push_back(blueprint);
|
||||
});
|
||||
for (SpatBlueprintOp blueprint : deadBlueprints)
|
||||
rewriter.eraseOp(blueprint);
|
||||
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) {
|
||||
auto transfers = buildDeferredTransferPlan(funcOp);
|
||||
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(linearizeScheduled(*transfers, rewriter)))
|
||||
return failure();
|
||||
ConstantPool constants(funcOp, rewriter);
|
||||
DeferredEmissionContext context(rewriter, constants);
|
||||
DeferredEraseSet erase;
|
||||
if (failed(realizeDeferredBoundaries(
|
||||
boundaries->boundaries, boundaries->results, context, erase)))
|
||||
return failure();
|
||||
for (Operation *op : erase) {
|
||||
if (!op->use_empty())
|
||||
return op->emitOpError(
|
||||
"phase 2 cannot erase deferred communication with live uses");
|
||||
rewriter.eraseOp(op);
|
||||
}
|
||||
if (failed(retargetDeferredPublications(funcOp, *transfers))
|
||||
|| failed(replaceFinalGraphPublications(funcOp, *transfers))
|
||||
|| 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
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
||||
|
||||
namespace onnx_mlir::spatial {
|
||||
|
||||
mlir::LogicalResult realizeDeferredCommunication(mlir::func::FuncOp funcOp);
|
||||
|
||||
} // namespace onnx_mlir::spatial
|
||||
+409
@@ -0,0 +1,409 @@
|
||||
#include "llvm/ADT/MapVector.h"
|
||||
|
||||
#include <limits>
|
||||
#include <queue>
|
||||
|
||||
#include "DeferredCommunicationScheduling.hpp"
|
||||
#include "DeferredTransferPlanning.hpp"
|
||||
|
||||
namespace onnx_mlir::spatial {
|
||||
using namespace mlir;
|
||||
namespace {
|
||||
|
||||
struct StreamThreshold {
|
||||
unsigned stream = 0;
|
||||
unsigned completedStep = 0;
|
||||
};
|
||||
|
||||
struct TransferGroup {
|
||||
DeferredExchangePlan* exchange = nullptr;
|
||||
SmallVector<ScheduledTransferSlice> ordered;
|
||||
SmallVector<StreamThreshold> dependencies;
|
||||
unsigned unsatisfied = 0;
|
||||
bool ready = false;
|
||||
bool scheduled = false;
|
||||
BoundaryCost cost;
|
||||
std::tuple<unsigned, unsigned, unsigned, uint64_t> originalPriority;
|
||||
std::optional<TransferEmissionSignature> firstSignature;
|
||||
};
|
||||
|
||||
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(signature.scheduled,
|
||||
signature.payload.getAsOpaquePointer(),
|
||||
signature.fragmentType.getAsOpaquePointer(),
|
||||
signature.hasGraphLane,
|
||||
signature.sourceIsBatch);
|
||||
}
|
||||
|
||||
static bool betterStaticPriority(const TransferGroup& lhs, const TransferGroup& rhs) {
|
||||
auto left = std::tuple(lhs.cost.instructionCount,
|
||||
lhs.cost.branchRegions,
|
||||
std::numeric_limits<unsigned>::max()
|
||||
- lhs.cost.absorbedTransfers,
|
||||
lhs.cost.lookupEntries,
|
||||
lhs.originalPriority);
|
||||
auto right = std::tuple(rhs.cost.instructionCount,
|
||||
rhs.cost.branchRegions,
|
||||
std::numeric_limits<unsigned>::max()
|
||||
- rhs.cost.absorbedTransfers,
|
||||
rhs.cost.lookupEntries,
|
||||
rhs.originalPriority);
|
||||
return left < right;
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
group.cost = estimateCanonicalBoundaryCost({}, group.ordered);
|
||||
if (!group.ordered.empty())
|
||||
group.firstSignature = getTransferEmissionSignature(*group.ordered.front().family);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
TransferEmissionSignature getTransferEmissionSignature(const ExternalTransferFamily& family) {
|
||||
ProducedValue* producer = family.requirement->producer;
|
||||
return {producer->scheduled,
|
||||
producer->payload,
|
||||
family.requirement->publicationFragmentType,
|
||||
family.requirement->graphLanes.has_value(),
|
||||
producer->scheduled->isBatch()};
|
||||
}
|
||||
|
||||
BoundaryCost estimateCanonicalBoundaryCost(
|
||||
ArrayRef<ScheduledTransferSlice> existingTail,
|
||||
ArrayRef<ScheduledTransferSlice> candidate) {
|
||||
BoundaryCost cost;
|
||||
std::optional<TransferEmissionSignature> previous;
|
||||
RequirementFamily* previousRequirement = nullptr;
|
||||
if (!existingTail.empty()) {
|
||||
previous = getTransferEmissionSignature(*existingTail.back().family);
|
||||
previousRequirement = existingTail.back().family->requirement;
|
||||
}
|
||||
for (const ScheduledTransferSlice& slice : candidate) {
|
||||
TransferEmissionSignature signature = getTransferEmissionSignature(*slice.family);
|
||||
if (!previous || !(*previous == signature))
|
||||
++cost.instructionCount;
|
||||
else
|
||||
cost.absorbedTransfers += slice.transferCount;
|
||||
previous = signature;
|
||||
if (previousRequirement != slice.family->requirement)
|
||||
++cost.instructionCount;
|
||||
previousRequirement = slice.family->requirement;
|
||||
cost.lookupEntries += slice.transferCount;
|
||||
}
|
||||
return cost;
|
||||
}
|
||||
|
||||
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];
|
||||
if (group.ready || group.scheduled)
|
||||
return;
|
||||
group.ready = true;
|
||||
ready.push(index);
|
||||
if (group.firstSignature) {
|
||||
auto& bucket = bySignature[hashSignature(*group.firstSignature)];
|
||||
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.ready || 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].ready && !groups[candidate].scheduled)
|
||||
chosen = candidate;
|
||||
}
|
||||
if (chosen) {
|
||||
TransferGroup& group = groups[*chosen];
|
||||
group.ready = false;
|
||||
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
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
#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;
|
||||
};
|
||||
|
||||
struct TransferEmissionSignature {
|
||||
ScheduledInfo* scheduled = nullptr;
|
||||
mlir::Value payload;
|
||||
mlir::Type fragmentType;
|
||||
bool hasGraphLane = false;
|
||||
bool sourceIsBatch = false;
|
||||
|
||||
bool operator==(const TransferEmissionSignature& other) const {
|
||||
return scheduled == other.scheduled && payload == other.payload
|
||||
&& fragmentType == other.fragmentType
|
||||
&& hasGraphLane == other.hasGraphLane
|
||||
&& sourceIsBatch == other.sourceIsBatch;
|
||||
}
|
||||
};
|
||||
|
||||
struct BoundaryCost {
|
||||
unsigned instructionCount = 0;
|
||||
unsigned branchRegions = 0;
|
||||
unsigned absorbedTransfers = 0;
|
||||
unsigned lookupEntries = 0;
|
||||
};
|
||||
|
||||
TransferEmissionSignature getTransferEmissionSignature(const ExternalTransferFamily& family);
|
||||
|
||||
BoundaryCost estimateCanonicalBoundaryCost(
|
||||
mlir::ArrayRef<ScheduledTransferSlice> existingTail,
|
||||
mlir::ArrayRef<ScheduledTransferSlice> candidate);
|
||||
|
||||
mlir::FailureOr<ScheduledCommunicationPlan> scheduleDeferredCommunication(mlir::func::FuncOp funcOp,
|
||||
DeferredTransferPlan& plan);
|
||||
|
||||
} // namespace onnx_mlir::spatial
|
||||
@@ -0,0 +1,638 @@
|
||||
#include "DeferredProjectionAnalysis.hpp"
|
||||
|
||||
#include "mlir/Dialect/Arith/IR/Arith.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 selection = result
|
||||
? dyn_cast<scf::IndexSwitchOp>(result.getOwner()) : scf::IndexSwitchOp();
|
||||
if (!selection || result.getResultNumber() != 0
|
||||
|| selection.getNumResults() != 1)
|
||||
return std::optional<unsigned>();
|
||||
auto selector = evaluateDeferredIndex(selection.getArg(), environment);
|
||||
if (failed(selector))
|
||||
return failure();
|
||||
Region *selected = &selection.getDefaultRegion();
|
||||
for (auto [caseValue, region] :
|
||||
llvm::zip(selection.getCases(), selection.getCaseRegions()))
|
||||
if (caseValue == *selector) {
|
||||
selected = ®ion;
|
||||
break;
|
||||
}
|
||||
auto yield = selected->hasOneBlock()
|
||||
? dyn_cast<scf::YieldOp>(selected->front().getTerminator())
|
||||
: scf::YieldOp();
|
||||
return yield && yield.getResults().size() == 1
|
||||
? sourceArgument(yield.getResults().front(), deferred, environment)
|
||||
: FailureOr<std::optional<unsigned>>(failure());
|
||||
}
|
||||
|
||||
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 selection = result
|
||||
? dyn_cast<scf::IndexSwitchOp>(result.getOwner()) : scf::IndexSwitchOp();
|
||||
if (!selection || result.getResultNumber() != 0)
|
||||
return;
|
||||
for (Region ®ion : selection.getCaseRegions())
|
||||
collectSourceArguments(
|
||||
cast<scf::YieldOp>(region.front().getTerminator()).getResults().front(),
|
||||
deferred, indices);
|
||||
collectSourceArguments(
|
||||
cast<scf::YieldOp>(selection.getDefaultRegion().front().getTerminator())
|
||||
.getResults().front(), 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 isAllowedStaticIndexExpression(
|
||||
Value value, Value scheduledLane,
|
||||
llvm::SmallDenseSet<Value, 16> &visiting) {
|
||||
if (value == scheduledLane)
|
||||
return true;
|
||||
Attribute constant;
|
||||
if (matchPattern(value, m_Constant(&constant)))
|
||||
return true;
|
||||
if (isa<BlockArgument>(value) || !value.getDefiningOp()
|
||||
|| !visiting.insert(value).second)
|
||||
return false;
|
||||
Operation *op = value.getDefiningOp();
|
||||
bool allowed = op->getNumRegions() == 0
|
||||
&& (isPureIndexComputationOp(op) || isCompileTimeOp(op))
|
||||
&& llvm::all_of(op->getOperands(), [&](Value operand) {
|
||||
return isAllowedStaticIndexExpression(
|
||||
operand, scheduledLane, visiting);
|
||||
});
|
||||
visiting.erase(value);
|
||||
return allowed;
|
||||
}
|
||||
|
||||
static bool isAllowedStaticIndexExpression(Value value,
|
||||
Value scheduledLane) {
|
||||
llvm::SmallDenseSet<Value, 16> visiting;
|
||||
return isAllowedStaticIndexExpression(value, scheduledLane, visiting);
|
||||
}
|
||||
|
||||
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 && (isa<scf::IndexSwitchOp>(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 LogicalResult verifyCanonicalSourceSelector(
|
||||
scf::IndexSwitchOp selection, SpatDeferredCommunicationOp deferred,
|
||||
Value scheduledLane, int64_t laneCount) {
|
||||
if (selection->getBlock() != &deferred.getBody().front()
|
||||
|| selection.getNumResults() != 1
|
||||
|| !selection.getArg().getType().isIndex()
|
||||
|| !isAllowedStaticIndexExpression(selection.getArg(), scheduledLane))
|
||||
return selection.emitOpError(
|
||||
"is not a canonical deferred source selector");
|
||||
if (laneCount < 2
|
||||
|| selection.getCases().size() != static_cast<size_t>(laneCount - 1))
|
||||
return selection.emitOpError(
|
||||
"must cover every non-default scheduled lane");
|
||||
for (auto [index, caseValue] : llvm::enumerate(selection.getCases()))
|
||||
if (caseValue != static_cast<int64_t>(index))
|
||||
return selection.emitOpError(
|
||||
"must use consecutive scheduled-lane cases starting at zero");
|
||||
auto verifyRegion = [&](Region ®ion) -> LogicalResult {
|
||||
auto yield = region.hasOneBlock()
|
||||
? dyn_cast<scf::YieldOp>(region.front().getTerminator()) : scf::YieldOp();
|
||||
if (!yield || yield.getResults().size() != 1)
|
||||
return selection.emitOpError(
|
||||
"source-selector region must yield one result");
|
||||
for (Operation &op : region.front().without_terminator())
|
||||
if (!isa<tensor::CastOp>(op))
|
||||
return selection.emitOpError(
|
||||
"source-selector regions may contain only tensor.cast");
|
||||
llvm::SmallSet<unsigned, 4> indices;
|
||||
collectSourceArguments(yield.getResults().front(), deferred, indices);
|
||||
return success(indices.size() == 1);
|
||||
};
|
||||
for (Region ®ion : selection.getCaseRegions())
|
||||
if (failed(verifyRegion(region)))
|
||||
return failure();
|
||||
return verifyRegion(selection.getDefaultRegion());
|
||||
}
|
||||
|
||||
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 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 = {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
|
||||
|
||||
LogicalResult verifyDeferredProgramContract(
|
||||
SpatDeferredCommunicationOp deferred) {
|
||||
if (!deferred.getBody().hasOneBlock())
|
||||
return deferred.emitOpError(
|
||||
"deferred program must have exactly one body block");
|
||||
Block &body = deferred.getBody().front();
|
||||
auto terminator = dyn_cast<SpatYieldOp>(body.getTerminator());
|
||||
if (!terminator || terminator.getOutputs().size() != 1)
|
||||
return deferred.emitOpError(
|
||||
"deferred program must have exactly one yielded value");
|
||||
Value scheduledLane;
|
||||
int64_t laneCount = 1;
|
||||
if (auto scheduled = deferred->getParentOfType<SpatScheduledComputeBatch>()) {
|
||||
scheduledLane = getEnclosingScheduledLane(deferred, scheduled);
|
||||
laneCount = scheduled.getLaneCount();
|
||||
}
|
||||
bool invalid = false;
|
||||
deferred.getBody().walk([&](Operation *op) {
|
||||
auto reject = [&](StringRef message) {
|
||||
op->emitOpError(message);
|
||||
invalid = true;
|
||||
};
|
||||
if (invalid || isa<SpatYieldOp, scf::YieldOp>(op))
|
||||
return;
|
||||
if (auto selection = dyn_cast<scf::IndexSwitchOp>(op)) {
|
||||
if (failed(verifyCanonicalSourceSelector(
|
||||
selection, deferred, scheduledLane, laneCount)))
|
||||
invalid = true;
|
||||
return;
|
||||
}
|
||||
if (auto loop = dyn_cast<scf::ForOp>(op)) {
|
||||
for (Value bound : {loop.getLowerBound(), loop.getUpperBound(),
|
||||
loop.getStep()})
|
||||
if (!isAllowedStaticIndexExpression(bound, scheduledLane))
|
||||
return reject("has a non-static deferred loop bound");
|
||||
for (int64_t lane = 0; lane < laneCount; ++lane) {
|
||||
StaticIndexEnvironment environment;
|
||||
if (scheduledLane)
|
||||
environment.bindings[scheduledLane] = lane;
|
||||
auto lower = evaluateDeferredIndex(loop.getLowerBound(), environment);
|
||||
auto upper = evaluateDeferredIndex(loop.getUpperBound(), environment);
|
||||
auto step = evaluateDeferredIndex(loop.getStep(), environment);
|
||||
if (failed(lower) || failed(upper) || failed(step) || *step <= 0)
|
||||
return reject("has a loop bound that does not specialize");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (op->getNumRegions() != 0)
|
||||
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 success(!invalid);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
DeferredLaneValueEvaluator::DeferredLaneValueEvaluator(
|
||||
const DeferredProgramTemplate &program, unsigned laneCount)
|
||||
: program(program), laneCount(laneCount) {}
|
||||
|
||||
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;
|
||||
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;
|
||||
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) {
|
||||
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();
|
||||
if (auto scheduled = deferred->getParentOfType<SpatScheduledComputeBatch>())
|
||||
program.scheduledLane = getEnclosingScheduledLane(deferred, scheduled);
|
||||
|
||||
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());
|
||||
});
|
||||
if (graphProjection) {
|
||||
DeferredProjectionLeafTemplate leaf;
|
||||
leaf.form = DeferredLeafForm::GraphBatchProjection;
|
||||
leaf.sourceRoot = slice.getSource();
|
||||
leaf.replacementRoot = value;
|
||||
leaf.leadingProjection = slice;
|
||||
leaf.leadingGeometry = {
|
||||
SmallVector<OpFoldResult>(slice.getMixedOffsets()),
|
||||
SmallVector<OpFoldResult>(slice.getMixedSizes()),
|
||||
SmallVector<OpFoldResult>(slice.getMixedStrides())};
|
||||
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());
|
||||
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) {
|
||||
GraphBatchPublicationKey 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,87 @@
|
||||
#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::LogicalResult verifyDeferredProgramContract(
|
||||
SpatDeferredCommunicationOp deferred);
|
||||
|
||||
mlir::FailureOr<DeferredProgramTemplate> analyzeDeferredProgramTemplate(
|
||||
SpatDeferredCommunicationOp deferred);
|
||||
|
||||
class DeferredLaneValueEvaluator {
|
||||
public:
|
||||
DeferredLaneValueEvaluator(const DeferredProgramTemplate &program,
|
||||
unsigned laneCount);
|
||||
|
||||
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;
|
||||
llvm::DenseMap<mlir::Value, StaticIntSequence> values;
|
||||
llvm::DenseMap<mlir::Value, StaticIntSequence> sourceOperands;
|
||||
};
|
||||
|
||||
mlir::FailureOr<llvm::SmallVector<unsigned>>
|
||||
getPossibleDeferredSourceOperandIndices(
|
||||
mlir::Value sourceRoot, SpatDeferredCommunicationOp deferred);
|
||||
|
||||
struct GraphBatchPublicationMap {
|
||||
mlir::RankedTensorType physicalResultType;
|
||||
mlir::RankedTensorType publicationFragmentType;
|
||||
llvm::SmallVector<int64_t> graphLaneToPhysicalSlot;
|
||||
llvm::SmallVector<int64_t> physicalSlotToGraphLane;
|
||||
};
|
||||
|
||||
struct GraphBatchPublicationKey {
|
||||
mlir::Operation *graphBatch = nullptr;
|
||||
unsigned resultIndex = 0;
|
||||
bool operator==(const GraphBatchPublicationKey &other) const {
|
||||
return graphBatch == other.graphBatch && resultIndex == other.resultIndex;
|
||||
}
|
||||
};
|
||||
|
||||
using GraphBatchPublicationCache =
|
||||
llvm::DenseMap<GraphBatchPublicationKey, GraphBatchPublicationMap>;
|
||||
|
||||
mlir::FailureOr<const GraphBatchPublicationMap *> getGraphBatchPublicationMap(
|
||||
SpatGraphComputeBatch graphBatch, unsigned resultIndex,
|
||||
GraphBatchPublicationCache &cache);
|
||||
|
||||
} // namespace onnx_mlir::spatial
|
||||
|
||||
namespace llvm {
|
||||
template <> struct DenseMapInfo<onnx_mlir::spatial::GraphBatchPublicationKey> {
|
||||
static onnx_mlir::spatial::GraphBatchPublicationKey getEmptyKey() {
|
||||
return {DenseMapInfo<mlir::Operation *>::getEmptyKey(), 0};
|
||||
}
|
||||
static onnx_mlir::spatial::GraphBatchPublicationKey getTombstoneKey() {
|
||||
return {DenseMapInfo<mlir::Operation *>::getTombstoneKey(), 0};
|
||||
}
|
||||
static unsigned getHashValue(
|
||||
const onnx_mlir::spatial::GraphBatchPublicationKey &key) {
|
||||
return hash_combine(key.graphBatch, key.resultIndex);
|
||||
}
|
||||
static bool isEqual(
|
||||
const onnx_mlir::spatial::GraphBatchPublicationKey &lhs,
|
||||
const onnx_mlir::spatial::GraphBatchPublicationKey &rhs) {
|
||||
return lhs == rhs;
|
||||
}
|
||||
};
|
||||
} // namespace llvm
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user