Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fed6d343e5 | |||
| 871fcfa832 | |||
| 1f4f58de1c | |||
| 8338caf3f3 | |||
| 47f6715296 | |||
| 2bfc033af9 | |||
| 83a54e28e4 | |||
| cc9b025a35 | |||
| c4dd28a607 | |||
| 8d3eb929f6 | |||
| f5e1c2e706 | |||
| 94c96195b9 | |||
| 645539317b | |||
| 4a98e88e97 | |||
| f492400eda | |||
| e8f09fd67f | |||
| 78e97f9fd8 | |||
| 984f362623 | |||
| 568fd90542 | |||
| be0bcc9dcc | |||
| 62dd40ee89 | |||
| 2b4115699a | |||
| 3a985b3675 | |||
| 4ab24eb288 | |||
| e083c27d80 | |||
| 852bef7605 | |||
| 237654dadf | |||
| 6d69600bc1 | |||
| aec80529ca | |||
| 8ddbbcecfa | |||
| 90c4339808 | |||
| 08870de1a6 | |||
| a34ac223c0 | |||
| 0fa10b4074 | |||
| e166ff7e1d | |||
| a70a8f77cf | |||
| 800c0c4316 | |||
| 1e9e61f5a9 | |||
| 27410207c4 | |||
| cbc9808229 | |||
| 69021d56aa | |||
| dc5edd032c | |||
| e33f517221 | |||
| f94b3d1020 | |||
| 20cf40c9ba | |||
| 37a59054a5 | |||
| 2a8faf9c6b | |||
| 01b9d03fc6 | |||
| 501e6c76f3 | |||
| 3c2667f11e | |||
| 0a5e73c3ea | |||
| 636310d0cb | |||
| 356be6ccc2 | |||
| b678e55d3c | |||
| ab63498f3f | |||
| 7c3943bd06 | |||
| c0238c0d06 | |||
| ff36729140 | |||
| cf93caecd5 | |||
| 2d5b03c08f | |||
| a41f694cf0 | |||
| 8bb0babf1b | |||
| 819d8af0f7 | |||
| 832bd7f1f7 | |||
| 82b44a6387 | |||
| 7fcc765d6e |
@@ -1,92 +1,211 @@
|
|||||||
- Always read the full README.md before doing anything.
|
* Always read the full README.md before doing anything
|
||||||
- Build commands:
|
* Build commands:
|
||||||
- `cmake --build ./build_release`
|
* `cmake --build ./build_release`
|
||||||
- `cmake --build ./build_debug`
|
* `cmake --build ./build_debug`
|
||||||
- Never use `ninja` directly: it bypasses cmake's configuration and invalidates the build cache.
|
* Never use `ninja` directly: it bypasses cmake's configuration and invalidates the build cache
|
||||||
- Always tries the release version build first and ask before building with the debug version
|
* 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
|
||||||
|
|
||||||
|
* Clean architecture matters as much as making the immediate test pass
|
||||||
|
* Prefer fixes that preserve clear ownership boundaries, explicit invariants, and simple dataflow
|
||||||
|
* Do not stack compensating fixes on top of earlier mistakes. If the current approach is becoming messy, stop and explain why
|
||||||
|
* A correct fix should usually make the responsible producer, resolver, verifier, or lowering own the behavior directly
|
||||||
|
* Avoid late repair passes, defensive cleanup, or broad rewrites when a cleaner owner-side fix is possible
|
||||||
|
* Do not hide an upstream modeling bug by normalizing it later in the pipeline. Fix the producer when the producer owns the invariant
|
||||||
|
* Prefer patterns/rewrites for local IR canonicalization. Use module walks only when pass-level structural analysis genuinely requires them
|
||||||
|
* Prefer compact, structured designs over long case-by-case implementations
|
||||||
|
|
||||||
|
# Think before coding
|
||||||
|
|
||||||
|
* State assumptions explicitly before implementing when they affect the design
|
||||||
|
* If multiple interpretations exist, present them instead of silently choosing one
|
||||||
|
* If a simpler approach exists, say so and prefer it unless there is a clear reason not to
|
||||||
|
* If something is unclear, stop, name what is confusing, and ask
|
||||||
|
* If the requested or obvious approach would make the architecture worse, push back and propose a cleaner alternative
|
||||||
|
|
||||||
# Code changes
|
# Code changes
|
||||||
|
|
||||||
- Keep changes minimal and localized to the relevant parts of the code.
|
* Keep changes minimal and localized to the relevant parts of the code
|
||||||
- Preserve the existing naming conventions and coding style used in the surrounding code.
|
* Preserve the existing naming conventions and coding style used in the surrounding code
|
||||||
- Keep code easy to read, well organized, and suitable for future extensibility. A function must not be longer than
|
* Keep code easy to read, well organized, and suitable for future extensibility
|
||||||
200/250 lines for readability and cognitive complexity.
|
* A function must not exceed roughly 200/250 lines. If a change pushes a function beyond that, extract focused helpers
|
||||||
- Prefer clear naming and structure over comments. Add comments only when they materially improve clarity.
|
* Prefer clear naming and structure over comments. Add comments only when they materially improve clarity
|
||||||
- Do not rename symbols, move files, or restructure modules unless that is necessary for the requested change.
|
* Do not rename symbols, move files, or restructure modules unless that is necessary for the requested change
|
||||||
|
* Avoid duplicate ad-hoc logic. If the same concept appears in multiple places, consider whether it deserves a shared helper/API
|
||||||
|
* When adding a helper or API, ask:
|
||||||
|
* Could this be useful to another component now
|
||||||
|
* Is another component already implementing the same idea differently
|
||||||
|
* Is this likely to be needed by a future adjacent component
|
||||||
|
* What is the narrowest useful abstraction
|
||||||
|
* What is the correct ownership level for this API
|
||||||
|
* If a shared API is justified, place it at the lowest clean layer that can be used by all relevant consumers without creating dependency cycles or leaking policy across layers
|
||||||
|
* If an existing component should use a newly introduced shared API, refactor that component in the same patch when doing so is directly related and reduces duplication
|
||||||
|
* Do not create broad frameworks just because a helper might someday be useful. Shared APIs should encode a real reusable concept, not speculative generality
|
||||||
|
* If the reusable abstraction is plausible but not clearly needed yet, keep the code local and mention the possible future extraction separately
|
||||||
|
|
||||||
|
# Avoid case-listing designs
|
||||||
|
|
||||||
|
* Avoid solving problems with large chains of `if`/`else`, switches, or repeated special cases that enumerate every possible situation
|
||||||
|
* Long case listings tend to overfit the current tests, grow the codebase, and hide the underlying abstraction
|
||||||
|
* When you see a growing list of special cases, stop and look for the shared concept, data model, interface, or normalization step that would make the cases collapse
|
||||||
|
* Prefer table-driven logic, traits/interfaces, small reusable predicates, structured dispatch, or producer-side normalization when they express the invariant more directly
|
||||||
|
* A few explicit cases are fine when the domain is genuinely small and closed
|
||||||
|
* If the list is likely to grow, refactor toward a cleaner and more compact design instead of adding another branch
|
||||||
|
* When keeping a case list is the pragmatic choice, explain why the domain is closed or why a broader abstraction would be premature
|
||||||
|
|
||||||
|
# Ownership and invariants
|
||||||
|
|
||||||
|
Before implementing, identify the owner of the behavior:
|
||||||
|
|
||||||
|
* A producer should emit IR/data that satisfies the contract of the next stage
|
||||||
|
* A lowering should make representation changes explicit and semantically correct
|
||||||
|
* A resolver should resolve existing structure without silently changing semantics
|
||||||
|
* A verifier should reject invalid states with bounded, actionable diagnostics
|
||||||
|
* Codegen should assume verified invariants and fail clearly if they are violated
|
||||||
|
|
||||||
|
When fixing a bug:
|
||||||
|
|
||||||
|
* State the invariant that was violated
|
||||||
|
* State which component should own that invariant
|
||||||
|
* Fix that component directly
|
||||||
|
* Avoid fixes that merely mask the violation later in the pipeline
|
||||||
|
* Add or preserve verification if the invariant is important enough to regress
|
||||||
|
|
||||||
|
# Refactor and API policy
|
||||||
|
|
||||||
|
You may propose or implement a refactor when:
|
||||||
|
|
||||||
|
* the local fix would duplicate logic
|
||||||
|
* the local fix would violate a layer boundary
|
||||||
|
* the bug exists because responsibility is assigned to the wrong component
|
||||||
|
* multiple components already implement ad-hoc variants of the same concept
|
||||||
|
* a shared helper/API would make the code smaller, clearer, and easier to maintain
|
||||||
|
* existing callers can be migrated cleanly without broad churn
|
||||||
|
* the current implementation is turning into a long list of special cases instead of a structured solution
|
||||||
|
|
||||||
|
When proposing or implementing a refactor:
|
||||||
|
|
||||||
|
* Explain what responsibility is being moved or shared
|
||||||
|
* Justify why the new location is the right ownership level
|
||||||
|
* Keep the API narrow and named after the concept or invariant it represents
|
||||||
|
* Migrate directly related existing users when that improves compactness and consistency
|
||||||
|
* Separate changes required for correctness from optional cleanup
|
||||||
|
* Avoid unrelated renames, formatting changes, or module moves
|
||||||
|
* Do not expand a justified refactor beyond directly related callers
|
||||||
|
|
||||||
|
Do not refactor when:
|
||||||
|
|
||||||
|
* the issue is truly local and a local fix is clearer
|
||||||
|
* the abstraction would have only one user and no clear adjacent use
|
||||||
|
* the abstraction would mix policies from different layers
|
||||||
|
* the refactor would affect unrelated behavior
|
||||||
|
* the refactor is mainly aesthetic
|
||||||
|
|
||||||
# Working style
|
# Working style
|
||||||
|
|
||||||
- Infer style and conventions from the existing code before introducing new patterns.
|
* Infer style and conventions from the existing code before introducing new patterns
|
||||||
- When several implementation options are possible, prefer the simplest one that fits the current architecture and
|
* When several implementation options are possible, prefer the simplest one that fits the current architecture and minimizes churn
|
||||||
minimizes churn.
|
* Push back when the requested or obvious fix would make the architecture worse
|
||||||
- Avoid broad refactors unless I explicitly ask for them.
|
* If a cleaner fix requires a small refactor or shared helper/API, propose it explicitly and justify it
|
||||||
|
* Avoid broad refactors unless explicitly requested or clearly necessary for correctness and maintainability
|
||||||
|
* When tests fail, bucket failures by likely root cause and separate patch-related failures from pre-existing or out-of-scope failures
|
||||||
|
|
||||||
# Responses
|
# Simplicity first
|
||||||
|
|
||||||
- When showing code in chat, make it easy to copy-paste into the codebase.
|
* Minimum code that solves the problem cleanly. Nothing speculative
|
||||||
- Keep outputs focused on the changed parts.
|
* No features beyond what was asked
|
||||||
- At the end of the response, briefly list any bad practices, mistakes, or cleaner alternatives you noticed, separate
|
* No error handling for impossible scenarios
|
||||||
from the main solution.
|
* If you write 200 lines and it could be 50, rewrite it
|
||||||
|
* Ask: “Would a senior engineer say this is overcomplicated?” If yes, simplify
|
||||||
|
* Prefer direct, explicit code over generic machinery unless the generic machinery clearly reduces duplication and preserves boundaries
|
||||||
|
|
||||||
# Guidelines
|
# Fallbacks and defaults
|
||||||
|
|
||||||
## 1. Think Before Coding
|
* Avoid silent fallback behavior when the semantic category is unknown
|
||||||
|
* Do not treat “unknown” as “safe” unless the codebase already defines that convention
|
||||||
|
* If a value cannot be classified, either preserve the existing behavior deliberately or fail with a clear diagnostic
|
||||||
|
* When adding a fallback, state why it is semantically valid and what invariant makes it safe
|
||||||
|
|
||||||
**Don't assume. Don't hide confusion. Surface tradeoffs.**
|
# Surgical changes
|
||||||
|
|
||||||
Before implementing:
|
* Touch only what you must
|
||||||
|
* Clean up only the mess introduced by your own change
|
||||||
|
* Do not “improve” adjacent code, comments, or formatting
|
||||||
|
* Match existing style, even if you would personally do it differently
|
||||||
|
* If you notice unrelated dead code, bad abstractions, or fragile design, mention it separately. Do not delete or rewrite it unless asked
|
||||||
|
* When your changes create orphans, remove imports, variables, functions, or files made unused by your change
|
||||||
|
* Every changed line should trace directly to the requested fix, a required cleanup, or a justified reuse/refactor decision
|
||||||
|
|
||||||
- State your assumptions explicitly. If uncertain, ask.
|
# Diagnostics and verification
|
||||||
- If multiple interpretations exist, present them - don't pick silently.
|
|
||||||
- If a simpler approach exists, say so. Push back when warranted.
|
|
||||||
- If something is unclear, stop. Name what's confusing. Ask.
|
|
||||||
|
|
||||||
## 2. Simplicity First
|
* Use existing bounded diagnostic mechanisms for pass-level verification or codegen failures
|
||||||
|
* Do not emit unbounded repeated diagnostics from loops or parallel workers
|
||||||
|
* Diagnostics should identify the violated invariant and the relevant value/op when useful
|
||||||
|
* Verifiers should reject invalid states, not repair them
|
||||||
|
* Codegen should not compensate for invalid IR/data unless codegen is the owner of that invariant
|
||||||
|
* Do not make failing tests pass by weakening verifiers, assertions, or diagnostics unless the check itself is proven wrong
|
||||||
|
* If a check is too strict, explain the valid case it rejects and update the invariant accordingly
|
||||||
|
* Prefer fixing invalid IR/data producers over relaxing consumers
|
||||||
|
* If adding diagnostics only for debugging, remove them or cap them before finalizing
|
||||||
|
|
||||||
**Minimum code that solves the problem. Nothing speculative.**
|
# Temporary debugging code
|
||||||
|
|
||||||
- No features beyond what was asked.
|
* Temporary diagnostics, dumps, assertions, and debug-only helpers must be removed or intentionally converted into bounded permanent diagnostics before finalizing
|
||||||
- No error handling for impossible scenarios.
|
* If debug instrumentation remains, explain why it is useful as permanent infrastructure
|
||||||
- If you write 200 lines and it could be 50, rewrite it.
|
* Do not leave noisy validation output behind
|
||||||
|
|
||||||
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
|
# Performance awareness
|
||||||
|
|
||||||
## 3. Surgical Changes
|
* Avoid algorithmic regressions in compiler passes, especially repeated full-module walks, repeated expensive analyses, or per-op recomputation inside nested loops
|
||||||
|
* If a change adds a walk, cache, analysis, or structural traversal, justify why it is needed
|
||||||
|
* For hot paths, prefer preserving existing asymptotic behavior unless a better structure is part of the requested change
|
||||||
|
* If performance may change, mention the expected impact and suggest a targeted timing check
|
||||||
|
|
||||||
**Touch only what you must. Clean up only your own mess.**
|
# Goal-driven execution
|
||||||
|
|
||||||
When editing existing code:
|
|
||||||
|
|
||||||
- Don't "improve" adjacent code, comments, or formatting.
|
|
||||||
- Don't refactor things that aren't broken.
|
|
||||||
- Match existing style, even if you'd do it differently.
|
|
||||||
- If you notice unrelated dead code, mention it - don't delete it.
|
|
||||||
|
|
||||||
When your changes create orphans:
|
|
||||||
|
|
||||||
- Remove imports/variables/functions that YOUR changes made unused.
|
|
||||||
- Don't remove pre-existing dead code unless asked, but mention it.
|
|
||||||
|
|
||||||
The test: Every changed line should trace directly to the user's request.
|
|
||||||
|
|
||||||
## 4. Goal-Driven Execution
|
|
||||||
|
|
||||||
**Define success criteria. Loop until verified.**
|
|
||||||
|
|
||||||
Transform tasks into verifiable goals:
|
|
||||||
|
|
||||||
- "Add validation" → "Write tests for invalid inputs, then make them pass"
|
|
||||||
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
|
|
||||||
- "Refactor X" → "Ensure tests pass before and after"
|
|
||||||
|
|
||||||
For multi-step tasks, state a brief plan:
|
For multi-step tasks, state a brief plan:
|
||||||
|
|
||||||
```
|
|
||||||
1. [Step] → verify: [check]
|
1. [Step] → verify: [check]
|
||||||
2. [Step] → verify: [check]
|
2. [Step] → verify: [check]
|
||||||
3. [Step] → verify: [check]
|
3. [Step] → verify: [check]
|
||||||
```
|
|
||||||
|
|
||||||
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
|
Define success criteria before implementing:
|
||||||
|
|
||||||
---
|
* For bug fixes, success means reproducing or identifying the failure, fixing the responsible owner, and verifying the targeted case
|
||||||
|
* For refactors, success means preserving behavior while making ownership, reuse, or structure cleaner
|
||||||
|
* For validation changes, success means checking both valid and invalid cases when applicable
|
||||||
|
|
||||||
|
Transform tasks into verifiable goals:
|
||||||
|
|
||||||
|
* “Fix the bug” → identify the invariant, reproduce the failure, fix the owner, verify the targeted case
|
||||||
|
* “Add validation” → write or identify tests for invalid inputs, then make them pass/fail as expected
|
||||||
|
* “Refactor X” → preserve behavior before and after, then run relevant tests
|
||||||
|
|
||||||
|
# Final self-review
|
||||||
|
|
||||||
|
Before reporting completion, check:
|
||||||
|
|
||||||
|
* Did I fix the owner of the invariant rather than masking the issue downstream
|
||||||
|
* Did I avoid broad case lists and ad-hoc special handling
|
||||||
|
* Did I introduce a helper/API only at the right ownership level
|
||||||
|
* Did I migrate directly related duplicate logic when doing so improves compactness
|
||||||
|
* Did I avoid weakening verifiers or assertions unnecessarily
|
||||||
|
* Did I remove temporary debugging code or make it bounded and intentional
|
||||||
|
* Did I avoid unrelated formatting, renames, or cleanup
|
||||||
|
* Did I consider performance impact for added walks, analyses, caches, or repeated computations
|
||||||
|
* Did I run the required build/test commands
|
||||||
|
* Did I clearly report remaining failures or risks
|
||||||
|
|
||||||
|
When reporting back:
|
||||||
|
|
||||||
|
* Say what changed
|
||||||
|
* Say what was verified
|
||||||
|
* Say what remains
|
||||||
|
* When showing code in chat, make it easy to copy-paste into the codebase
|
||||||
|
* Keep outputs focused on the changed parts
|
||||||
|
* List bad practices, fragile assumptions, or cleaner alternatives separately
|
||||||
|
* If a change is intentionally pragmatic rather than architecturally ideal, say so and explain the tradeoff
|
||||||
|
|||||||
@@ -168,8 +168,8 @@ Each validation run writes artifacts in the model workspace, for example under
|
|||||||
|
|
||||||
The compiler currently dumps dialect snapshots such as `spatial0.mlir`,
|
The compiler currently dumps dialect snapshots such as `spatial0.mlir`,
|
||||||
`spatial1_dcp_merged.mlir`, `pim0.mlir`, `pim1_buff.mlir`,
|
`spatial1_dcp_merged.mlir`, `pim0.mlir`, `pim1_buff.mlir`,
|
||||||
`pim2_coalesced.mlir`, `pim3_folded.mlir`, and
|
`pim2_coalesced.mlir`, and `pim3_folded.mlir` when an output directory is
|
||||||
`pim4_materialized.mlir` when an output directory is available.
|
available.
|
||||||
|
|
||||||
To rerun the simulator manually with tracing after validation has produced a
|
To rerun the simulator manually with tracing after validation has produced a
|
||||||
`raptor/pim/` directory:
|
`raptor/pim/` directory:
|
||||||
|
|||||||
@@ -258,24 +258,23 @@ where
|
|||||||
|
|
||||||
let (memory, crossbars) = core.get_memory_crossbar();
|
let (memory, crossbars) = core.get_memory_crossbar();
|
||||||
let crossbar = crossbars.get_mut(group).unwrap();
|
let crossbar = crossbars.get_mut(group).unwrap();
|
||||||
let crossbar_stored_bytes = crossbar.stored_bytes();
|
|
||||||
let crossbar_byte_width = crossbar.width();
|
|
||||||
|
|
||||||
let crossbar_elem_width = crossbar_byte_width / size_of::<M>();
|
|
||||||
ensure!(
|
|
||||||
crossbar_byte_width % size_of::<M>() == 0,
|
|
||||||
"M not divisor of the crosbbar size"
|
|
||||||
);
|
|
||||||
|
|
||||||
let crossbar_height = crossbar.height();
|
let crossbar_height = crossbar.height();
|
||||||
let crossbar_byte_size = crossbar_byte_width * crossbar_height;
|
let crossbar_stored_bytes = crossbar.stored_bytes();
|
||||||
|
let bytes_per_column = crossbar_height * size_of::<M>();
|
||||||
|
ensure!(bytes_per_column != 0, "crossbar height can not be zero");
|
||||||
|
ensure!(
|
||||||
|
crossbar_stored_bytes % bytes_per_column == 0,
|
||||||
|
"Stored crossbar bytes do not describe an integral number of columns"
|
||||||
|
);
|
||||||
|
let crossbar_elem_width = crossbar_stored_bytes / bytes_per_column;
|
||||||
|
ensure!(crossbar_elem_width != 0, "Crossbar contains no stored columns");
|
||||||
|
|
||||||
let loads = memory
|
let loads = memory
|
||||||
.reserve_load(r1_val, crossbar_height * size_of::<F>())?
|
.reserve_load(r1_val, crossbar_height * size_of::<F>())?
|
||||||
.execute_load::<F>()?;
|
.execute_load::<F>()?;
|
||||||
let load = loads[0];
|
let load = loads[0];
|
||||||
let vec: Cow<[M]> = load.up();
|
let vec: Cow<[M]> = load.up();
|
||||||
let matrix = crossbar.load::<M>(crossbar_byte_size)?[0];
|
let matrix = crossbar.load::<M>(crossbar_stored_bytes)?[0];
|
||||||
|
|
||||||
// --- FAER IMPLEMENTATION ---
|
// --- FAER IMPLEMENTATION ---
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::cmp::min;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use anyhow::{Context, Result, bail, ensure};
|
use anyhow::{Context, Result, bail, ensure};
|
||||||
@@ -86,7 +87,7 @@ where {
|
|||||||
size,
|
size,
|
||||||
};
|
};
|
||||||
if self.memory.len() < address + size {
|
if self.memory.len() < address + size {
|
||||||
self.memory.resize((address + size) * 2, 0);
|
self.memory.resize(min((address + size) * 2, u32::MAX as usize), 0);
|
||||||
}
|
}
|
||||||
self.load_requests.push(load_request);
|
self.load_requests.push(load_request);
|
||||||
Ok(self)
|
Ok(self)
|
||||||
|
|||||||
@@ -117,10 +117,11 @@ add_pim_library(OMPIMAccel
|
|||||||
SpatialOps
|
SpatialOps
|
||||||
PimOps
|
PimOps
|
||||||
OMONNXToSpatial
|
OMONNXToSpatial
|
||||||
OMSpatialToGraphviz
|
|
||||||
OMSpatialToPim
|
OMSpatialToPim
|
||||||
OMPimCommon
|
OMPimCommon
|
||||||
OMPimBufferization
|
OMPimBufferization
|
||||||
OMPimStaticMemoryCoalescing
|
OMPimMemoryCoalescing
|
||||||
|
OMPimHostConstantFolding
|
||||||
|
OMPimVerification
|
||||||
MLIRTensorInferTypeOpInterfaceImpl
|
MLIRTensorInferTypeOpInterfaceImpl
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
add_pim_library(OMPimCommon
|
add_pim_library(OMPimCommon
|
||||||
|
IR/AffineUtils.cpp
|
||||||
IR/AddressAnalysis.cpp
|
IR/AddressAnalysis.cpp
|
||||||
IR/BatchCoreUtils.cpp
|
IR/BatchCoreUtils.cpp
|
||||||
IR/ConstantUtils.cpp
|
IR/ConstantUtils.cpp
|
||||||
IR/CoreBlockUtils.cpp
|
IR/CoreBlockUtils.cpp
|
||||||
IR/EntryPointUtils.cpp
|
IR/EntryPointUtils.cpp
|
||||||
|
IR/IndexingUtils.cpp
|
||||||
|
IR/LoopUtils.cpp
|
||||||
IR/ShapeUtils.cpp
|
IR/ShapeUtils.cpp
|
||||||
IR/SubviewUtils.cpp
|
IR/SubviewUtils.cpp
|
||||||
|
IR/TensorSliceUtils.cpp
|
||||||
IR/WeightUtils.cpp
|
IR/WeightUtils.cpp
|
||||||
|
Support/CheckedArithmetic.cpp
|
||||||
Support/DebugDump.cpp
|
Support/DebugDump.cpp
|
||||||
Support/Diagnostics.cpp
|
Support/Diagnostics.cpp
|
||||||
Support/FileSystemUtils.cpp
|
Support/FileSystemUtils.cpp
|
||||||
@@ -18,6 +23,8 @@ add_pim_library(OMPimCommon
|
|||||||
${PIM_PUBLIC_INCLUDE_DIRS}
|
${PIM_PUBLIC_INCLUDE_DIRS}
|
||||||
|
|
||||||
LINK_LIBS PUBLIC
|
LINK_LIBS PUBLIC
|
||||||
|
MLIRLinalgDialect
|
||||||
|
MLIRSCFDialect
|
||||||
onnx
|
onnx
|
||||||
SpatialOps
|
SpatialOps
|
||||||
PimOps
|
PimOps
|
||||||
|
|||||||
@@ -34,12 +34,25 @@ mlir::Value resolveAlias(mlir::Value value, const StaticValueKnowledge* knowledg
|
|||||||
|
|
||||||
llvm::FailureOr<CompiledIndexExpr> compileIndexValueImpl(mlir::Value value);
|
llvm::FailureOr<CompiledIndexExpr> compileIndexValueImpl(mlir::Value value);
|
||||||
llvm::FailureOr<CompiledAddressExpr> compileContiguousAddressExprImpl(mlir::Value value);
|
llvm::FailureOr<CompiledAddressExpr> compileContiguousAddressExprImpl(mlir::Value value);
|
||||||
|
mlir::Value resolveLoopCarriedAliasImpl(mlir::Value value, const StaticValueKnowledge* knowledge);
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
CompiledIndexExpr makeCompiledIndexExpr(Args&&... args) {
|
CompiledIndexExpr makeCompiledIndexExpr(Args&&... args) {
|
||||||
return CompiledIndexExpr(std::make_shared<CompiledIndexExprNode>(std::forward<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) {
|
mlir::Value resolveLoopCarriedAliasImpl(mlir::Value value, const StaticValueKnowledge* knowledge) {
|
||||||
value = resolveAlias(value, knowledge);
|
value = resolveAlias(value, knowledge);
|
||||||
|
|
||||||
@@ -56,6 +69,15 @@ mlir::Value resolveLoopCarriedAliasImpl(mlir::Value value, const StaticValueKnow
|
|||||||
return resolveLoopCarriedAliasImpl(tiedOperand->get(), knowledge);
|
return resolveLoopCarriedAliasImpl(tiedOperand->get(), knowledge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auto forOp = mlir::dyn_cast<mlir::scf::ForOp>(definingOp)) {
|
||||||
|
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())
|
||||||
|
return resolveForYieldedAliasToInit(forOp, yieldOp.getOperand(result.getResultNumber()), knowledge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (auto castOp = mlir::dyn_cast<mlir::memref::CastOp>(definingOp))
|
if (auto castOp = mlir::dyn_cast<mlir::memref::CastOp>(definingOp))
|
||||||
return resolveLoopCarriedAliasImpl(castOp.getSource(), knowledge);
|
return resolveLoopCarriedAliasImpl(castOp.getSource(), knowledge);
|
||||||
if (auto collapseOp = mlir::dyn_cast<mlir::memref::CollapseShapeOp>(definingOp))
|
if (auto collapseOp = mlir::dyn_cast<mlir::memref::CollapseShapeOp>(definingOp))
|
||||||
@@ -69,6 +91,16 @@ mlir::Value resolveLoopCarriedAliasImpl(mlir::Value value, const StaticValueKnow
|
|||||||
llvm::FailureOr<int64_t> resolveOpFoldResult(mlir::OpFoldResult ofr, const StaticValueKnowledge* knowledge);
|
llvm::FailureOr<int64_t> resolveOpFoldResult(mlir::OpFoldResult ofr, const StaticValueKnowledge* knowledge);
|
||||||
llvm::FailureOr<int64_t> resolveIndexValueImpl(mlir::Value value, const StaticValueKnowledge* knowledge);
|
llvm::FailureOr<int64_t> resolveIndexValueImpl(mlir::Value value, const StaticValueKnowledge* knowledge);
|
||||||
|
|
||||||
|
static llvm::FailureOr<llvm::SmallVector<int64_t>> getStaticMemRefStrides(mlir::MemRefType type) {
|
||||||
|
llvm::SmallVector<int64_t> strides;
|
||||||
|
int64_t offset = 0;
|
||||||
|
if (failed(type.getStridesAndOffset(strides, offset)))
|
||||||
|
return mlir::failure();
|
||||||
|
if (llvm::any_of(strides, mlir::ShapedType::isDynamic))
|
||||||
|
return mlir::failure();
|
||||||
|
return strides;
|
||||||
|
}
|
||||||
|
|
||||||
static llvm::FailureOr<int64_t> resolveConstantGlobalLoad(mlir::memref::LoadOp loadOp,
|
static llvm::FailureOr<int64_t> resolveConstantGlobalLoad(mlir::memref::LoadOp loadOp,
|
||||||
const StaticValueKnowledge* knowledge) {
|
const StaticValueKnowledge* knowledge) {
|
||||||
auto getGlobalOp = loadOp.getMemRef().getDefiningOp<mlir::memref::GetGlobalOp>();
|
auto getGlobalOp = loadOp.getMemRef().getDefiningOp<mlir::memref::GetGlobalOp>();
|
||||||
@@ -489,16 +521,25 @@ llvm::FailureOr<ResolvedContiguousAddress> resolveContiguousAddressImpl(mlir::Va
|
|||||||
return mlir::failure();
|
return mlir::failure();
|
||||||
|
|
||||||
auto yieldOp = mlir::cast<mlir::scf::YieldOp>(forOp.getBody()->getTerminator());
|
auto yieldOp = mlir::cast<mlir::scf::YieldOp>(forOp.getBody()->getTerminator());
|
||||||
mlir::Value yieldedValue = resolveLoopCarriedAliasImpl(yieldOp.getOperand(result.getResultNumber()), knowledge);
|
value = resolveForYieldedAliasToInit(forOp, yieldOp.getOperand(result.getResultNumber()), knowledge);
|
||||||
if (auto blockArgument = mlir::dyn_cast<mlir::BlockArgument>(yieldedValue)) {
|
continue;
|
||||||
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;
|
if (auto ifOp = mlir::dyn_cast<mlir::scf::IfOp>(definingOp)) {
|
||||||
|
auto result = mlir::dyn_cast<mlir::OpResult>(value);
|
||||||
|
if (!result)
|
||||||
|
return mlir::failure();
|
||||||
|
|
||||||
|
auto condition = resolveIndexValueImpl(ifOp.getCondition(), knowledge);
|
||||||
|
if (failed(condition))
|
||||||
|
return mlir::failure();
|
||||||
|
|
||||||
|
mlir::Region& selectedRegion = *condition != 0 ? ifOp.getThenRegion() : ifOp.getElseRegion();
|
||||||
|
auto yieldOp = mlir::dyn_cast<mlir::scf::YieldOp>(selectedRegion.front().getTerminator());
|
||||||
|
if (!yieldOp || result.getResultNumber() >= yieldOp.getNumOperands())
|
||||||
|
return mlir::failure();
|
||||||
|
|
||||||
|
value = resolveLoopCarriedAliasImpl(yieldOp.getOperand(result.getResultNumber()), knowledge);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -539,8 +580,10 @@ llvm::FailureOr<ResolvedContiguousAddress> resolveContiguousAddressImpl(mlir::Va
|
|||||||
if (!isMemoryContiguous(sourceType.getShape(), offsets, sizes, strides))
|
if (!isMemoryContiguous(sourceType.getShape(), offsets, sizes, strides))
|
||||||
return mlir::failure();
|
return mlir::failure();
|
||||||
|
|
||||||
auto sourceStrides = computeRowMajorStrides(sourceType.getShape());
|
auto sourceStrides = getStaticMemRefStrides(sourceType);
|
||||||
byteOffset += linearizeIndex(offsets, sourceStrides) * getElementTypeSizeInBytes(subviewType.getElementType());
|
if (failed(sourceStrides))
|
||||||
|
return mlir::failure();
|
||||||
|
byteOffset += linearizeIndex(offsets, *sourceStrides) * getElementTypeSizeInBytes(subviewType.getElementType());
|
||||||
value = resolveAlias(subviewOp.getSource(), knowledge);
|
value = resolveAlias(subviewOp.getSource(), knowledge);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -597,17 +640,35 @@ llvm::FailureOr<CompiledAddressExpr> compileContiguousAddressExprImpl(mlir::Valu
|
|||||||
return mlir::failure();
|
return mlir::failure();
|
||||||
|
|
||||||
auto yieldOp = mlir::cast<mlir::scf::YieldOp>(forOp.getBody()->getTerminator());
|
auto yieldOp = mlir::cast<mlir::scf::YieldOp>(forOp.getBody()->getTerminator());
|
||||||
mlir::Value yieldedValue = yieldOp.getOperand(result.getResultNumber());
|
value = resolveForYieldedAliasToInit(forOp, yieldOp.getOperand(result.getResultNumber()), nullptr);
|
||||||
if (auto blockArgument = mlir::dyn_cast<mlir::BlockArgument>(yieldedValue)) {
|
continue;
|
||||||
if (blockArgument.getOwner() == forOp.getBody() && blockArgument.getArgNumber() > 0
|
}
|
||||||
&& static_cast<unsigned>(blockArgument.getArgNumber() - 1) < forOp.getInitArgs().size()) {
|
|
||||||
value = forOp.getInitArgs()[blockArgument.getArgNumber() - 1];
|
if (auto ifOp = mlir::dyn_cast<mlir::scf::IfOp>(definingOp)) {
|
||||||
continue;
|
auto result = mlir::dyn_cast<mlir::OpResult>(value);
|
||||||
}
|
if (!result)
|
||||||
|
return mlir::failure();
|
||||||
|
|
||||||
|
auto thenYield = mlir::dyn_cast<mlir::scf::YieldOp>(ifOp.getThenRegion().front().getTerminator());
|
||||||
|
auto elseYield = mlir::dyn_cast<mlir::scf::YieldOp>(ifOp.getElseRegion().front().getTerminator());
|
||||||
|
if (!thenYield || !elseYield || result.getResultNumber() >= thenYield.getNumOperands()
|
||||||
|
|| result.getResultNumber() >= elseYield.getNumOperands()) {
|
||||||
|
return mlir::failure();
|
||||||
}
|
}
|
||||||
|
|
||||||
value = yieldedValue;
|
auto thenAddress = compileContiguousAddressExprImpl(thenYield.getOperand(result.getResultNumber()));
|
||||||
continue;
|
auto elseAddress = compileContiguousAddressExprImpl(elseYield.getOperand(result.getResultNumber()));
|
||||||
|
if (failed(thenAddress) || failed(elseAddress) || thenAddress->base != elseAddress->base)
|
||||||
|
return mlir::failure();
|
||||||
|
|
||||||
|
auto condition = compileIndexValueImpl(ifOp.getCondition());
|
||||||
|
if (failed(condition))
|
||||||
|
return mlir::failure();
|
||||||
|
|
||||||
|
CompiledIndexExprNode selectExpr;
|
||||||
|
selectExpr.kind = CompiledIndexExprNode::Kind::Select;
|
||||||
|
selectExpr.operands = {*condition, thenAddress->byteOffset, elseAddress->byteOffset};
|
||||||
|
return CompiledAddressExpr {thenAddress->base, makeCompiledIndexExpr(std::move(selectExpr))};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto subviewOp = mlir::dyn_cast<mlir::memref::SubViewOp>(definingOp)) {
|
if (auto subviewOp = mlir::dyn_cast<mlir::memref::SubViewOp>(definingOp)) {
|
||||||
@@ -616,40 +677,51 @@ llvm::FailureOr<CompiledAddressExpr> compileContiguousAddressExprImpl(mlir::Valu
|
|||||||
if (!sourceType || !subviewType || !sourceType.hasStaticShape() || !subviewType.hasStaticShape())
|
if (!sourceType || !subviewType || !sourceType.hasStaticShape() || !subviewType.hasStaticShape())
|
||||||
return mlir::failure();
|
return mlir::failure();
|
||||||
|
|
||||||
llvm::SmallVector<int64_t> staticOffsets;
|
|
||||||
staticOffsets.reserve(subviewOp.getMixedOffsets().size());
|
|
||||||
llvm::SmallVector<int64_t> staticSizes;
|
llvm::SmallVector<int64_t> staticSizes;
|
||||||
staticSizes.reserve(subviewOp.getMixedSizes().size());
|
staticSizes.reserve(subviewOp.getMixedSizes().size());
|
||||||
llvm::SmallVector<int64_t> staticStrides;
|
llvm::SmallVector<int64_t> staticStrides;
|
||||||
staticStrides.reserve(subviewOp.getMixedStrides().size());
|
staticStrides.reserve(subviewOp.getMixedStrides().size());
|
||||||
bool allStatic = true;
|
llvm::SmallVector<int64_t> staticOffsets;
|
||||||
|
staticOffsets.reserve(subviewOp.getMixedOffsets().size());
|
||||||
|
bool hasOnlyStaticOffsets = true;
|
||||||
|
|
||||||
for (mlir::OpFoldResult offset : subviewOp.getMixedOffsets())
|
for (mlir::OpFoldResult offset : subviewOp.getMixedOffsets())
|
||||||
if (auto attr = mlir::dyn_cast<mlir::Attribute>(offset))
|
if (auto attr = mlir::dyn_cast<mlir::Attribute>(offset))
|
||||||
staticOffsets.push_back(mlir::cast<mlir::IntegerAttr>(attr).getInt());
|
staticOffsets.push_back(mlir::cast<mlir::IntegerAttr>(attr).getInt());
|
||||||
else
|
else
|
||||||
allStatic = false;
|
hasOnlyStaticOffsets = false;
|
||||||
for (mlir::OpFoldResult size : subviewOp.getMixedSizes())
|
for (mlir::OpFoldResult size : subviewOp.getMixedSizes()) {
|
||||||
if (auto attr = mlir::dyn_cast<mlir::Attribute>(size))
|
auto attr = mlir::dyn_cast<mlir::Attribute>(size);
|
||||||
staticSizes.push_back(mlir::cast<mlir::IntegerAttr>(attr).getInt());
|
if (!attr)
|
||||||
else
|
return mlir::failure();
|
||||||
allStatic = false;
|
staticSizes.push_back(mlir::cast<mlir::IntegerAttr>(attr).getInt());
|
||||||
for (mlir::OpFoldResult stride : subviewOp.getMixedStrides())
|
}
|
||||||
if (auto attr = mlir::dyn_cast<mlir::Attribute>(stride))
|
for (mlir::OpFoldResult stride : subviewOp.getMixedStrides()) {
|
||||||
staticStrides.push_back(mlir::cast<mlir::IntegerAttr>(attr).getInt());
|
auto attr = mlir::dyn_cast<mlir::Attribute>(stride);
|
||||||
else
|
if (!attr)
|
||||||
allStatic = false;
|
return mlir::failure();
|
||||||
|
staticStrides.push_back(mlir::cast<mlir::IntegerAttr>(attr).getInt());
|
||||||
|
}
|
||||||
|
|
||||||
if (allStatic) {
|
if (!isContiguousSubviewWithDynamicOffsets(
|
||||||
|
sourceType.getShape(), subviewOp.getMixedOffsets(), staticSizes, staticStrides)) {
|
||||||
|
return mlir::failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasOnlyStaticOffsets) {
|
||||||
if (!isMemoryContiguous(sourceType.getShape(), staticOffsets, staticSizes, staticStrides))
|
if (!isMemoryContiguous(sourceType.getShape(), staticOffsets, staticSizes, staticStrides))
|
||||||
return mlir::failure();
|
return mlir::failure();
|
||||||
|
|
||||||
auto sourceStrides = computeRowMajorStrides(sourceType.getShape());
|
auto sourceStrides = getStaticMemRefStrides(sourceType);
|
||||||
|
if (failed(sourceStrides))
|
||||||
|
return mlir::failure();
|
||||||
constantByteOffset +=
|
constantByteOffset +=
|
||||||
linearizeIndex(staticOffsets, sourceStrides) * getElementTypeSizeInBytes(subviewType.getElementType());
|
linearizeIndex(staticOffsets, *sourceStrides) * getElementTypeSizeInBytes(subviewType.getElementType());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
llvm::SmallVector<int64_t> sourceStrides = computeRowMajorStrides(sourceType.getShape());
|
auto sourceStrides = getStaticMemRefStrides(sourceType);
|
||||||
|
if (failed(sourceStrides))
|
||||||
|
return mlir::failure();
|
||||||
CompiledIndexExpr offsetExpr;
|
CompiledIndexExpr offsetExpr;
|
||||||
{
|
{
|
||||||
CompiledIndexExprNode expr;
|
CompiledIndexExprNode expr;
|
||||||
@@ -658,7 +730,7 @@ llvm::FailureOr<CompiledAddressExpr> compileContiguousAddressExprImpl(mlir::Valu
|
|||||||
offsetExpr = makeCompiledIndexExpr(std::move(expr));
|
offsetExpr = makeCompiledIndexExpr(std::move(expr));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto [mixedOffset, sourceStride] : llvm::zip_equal(subviewOp.getMixedOffsets(), sourceStrides)) {
|
for (auto [mixedOffset, sourceStride] : llvm::zip_equal(subviewOp.getMixedOffsets(), *sourceStrides)) {
|
||||||
CompiledIndexExpr operandExpr;
|
CompiledIndexExpr operandExpr;
|
||||||
if (auto attr = mlir::dyn_cast<mlir::Attribute>(mixedOffset)) {
|
if (auto attr = mlir::dyn_cast<mlir::Attribute>(mixedOffset)) {
|
||||||
CompiledIndexExprNode expr;
|
CompiledIndexExprNode expr;
|
||||||
@@ -749,18 +821,12 @@ llvm::FailureOr<CompiledAddressExpr> compileContiguousAddressExprImpl(mlir::Valu
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
llvm::FailureOr<int64_t> resolveIndexValue(mlir::Value value) { return resolveIndexValueImpl(value, nullptr); }
|
|
||||||
|
|
||||||
llvm::FailureOr<int64_t> resolveIndexValue(mlir::Value value, const StaticValueKnowledge& knowledge) {
|
llvm::FailureOr<int64_t> resolveIndexValue(mlir::Value value, const StaticValueKnowledge& knowledge) {
|
||||||
return resolveIndexValueImpl(value, &knowledge);
|
return resolveIndexValueImpl(value, &knowledge);
|
||||||
}
|
}
|
||||||
|
|
||||||
llvm::FailureOr<CompiledIndexExpr> compileIndexExpr(mlir::Value value) { return compileIndexValueImpl(value); }
|
llvm::FailureOr<CompiledIndexExpr> compileIndexExpr(mlir::Value value) { return compileIndexValueImpl(value); }
|
||||||
|
|
||||||
llvm::FailureOr<ResolvedContiguousAddress> resolveContiguousAddress(mlir::Value value) {
|
|
||||||
return resolveContiguousAddressImpl(value, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
llvm::FailureOr<ResolvedContiguousAddress> resolveContiguousAddress(mlir::Value value,
|
llvm::FailureOr<ResolvedContiguousAddress> resolveContiguousAddress(mlir::Value value,
|
||||||
const StaticValueKnowledge& knowledge) {
|
const StaticValueKnowledge& knowledge) {
|
||||||
return resolveContiguousAddressImpl(value, &knowledge);
|
return resolveContiguousAddressImpl(value, &knowledge);
|
||||||
@@ -784,7 +850,7 @@ llvm::FailureOr<ResolvedContiguousAddress> CompiledAddressExpr::evaluate(const S
|
|||||||
auto resolvedOffset = byteOffset.evaluate(knowledge);
|
auto resolvedOffset = byteOffset.evaluate(knowledge);
|
||||||
if (failed(resolvedOffset))
|
if (failed(resolvedOffset))
|
||||||
return mlir::failure();
|
return mlir::failure();
|
||||||
return ResolvedContiguousAddress {base, *resolvedOffset};
|
return ResolvedContiguousAddress {resolveAlias(base, &knowledge), *resolvedOffset};
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -77,14 +77,12 @@ mlir::memref::GlobalOp lookupGlobalForGetGlobal(mlir::ModuleOp moduleOp, mlir::m
|
|||||||
|
|
||||||
/// Resolves a value to contiguous backing storage when that storage can be
|
/// Resolves a value to contiguous backing storage when that storage can be
|
||||||
/// proven statically from aliases, DPS ties, casts, and subviews.
|
/// proven statically from aliases, DPS ties, casts, and subviews.
|
||||||
llvm::FailureOr<ResolvedContiguousAddress> resolveContiguousAddress(mlir::Value value);
|
|
||||||
llvm::FailureOr<ResolvedContiguousAddress> resolveContiguousAddress(mlir::Value value,
|
llvm::FailureOr<ResolvedContiguousAddress> resolveContiguousAddress(mlir::Value value,
|
||||||
const StaticValueKnowledge& knowledge);
|
const StaticValueKnowledge& knowledge = {});
|
||||||
|
|
||||||
/// Statically evaluates index-like SSA values, including simple integer
|
/// Statically evaluates index-like SSA values, including simple integer
|
||||||
/// arithmetic and loop facts recorded in `knowledge`.
|
/// arithmetic and loop facts recorded in `knowledge`.
|
||||||
llvm::FailureOr<int64_t> resolveIndexValue(mlir::Value value);
|
llvm::FailureOr<int64_t> resolveIndexValue(mlir::Value value, const StaticValueKnowledge& knowledge = {});
|
||||||
llvm::FailureOr<int64_t> resolveIndexValue(mlir::Value value, const StaticValueKnowledge& knowledge);
|
|
||||||
llvm::FailureOr<CompiledIndexExpr> compileIndexExpr(mlir::Value value);
|
llvm::FailureOr<CompiledIndexExpr> compileIndexExpr(mlir::Value value);
|
||||||
|
|
||||||
/// Follows alias, view, and DPS chains to recover the backing value of a
|
/// Follows alias, view, and DPS chains to recover the backing value of a
|
||||||
|
|||||||
@@ -0,0 +1,219 @@
|
|||||||
|
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||||
|
#include "mlir/IR/Matchers.h"
|
||||||
|
|
||||||
|
#include "AffineUtils.hpp"
|
||||||
|
#include "ConstantUtils.hpp"
|
||||||
|
|
||||||
|
using namespace mlir;
|
||||||
|
|
||||||
|
namespace onnx_mlir {
|
||||||
|
|
||||||
|
static FailureOr<int64_t> floorDivSigned(int64_t lhs, int64_t rhs) {
|
||||||
|
if (rhs <= 0)
|
||||||
|
return failure();
|
||||||
|
|
||||||
|
int64_t quotient = lhs / rhs;
|
||||||
|
int64_t remainder = lhs % rhs;
|
||||||
|
if (remainder != 0 && lhs < 0)
|
||||||
|
--quotient;
|
||||||
|
return quotient;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<int64_t> ceilDivSigned(int64_t lhs, int64_t rhs) {
|
||||||
|
if (rhs <= 0)
|
||||||
|
return failure();
|
||||||
|
|
||||||
|
int64_t quotient = lhs / rhs;
|
||||||
|
int64_t remainder = lhs % rhs;
|
||||||
|
if (remainder != 0 && lhs > 0)
|
||||||
|
++quotient;
|
||||||
|
return quotient;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value createOrFoldAffineApply(
|
||||||
|
RewriterBase& rewriter, 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");
|
||||||
|
|
||||||
|
SmallVector<Attribute> operandConstants;
|
||||||
|
operandConstants.reserve(operands.size());
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
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 affine::AffineApplyOp::create(rewriter, loc, map, operands).getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
Value createOrFoldAffineApply(
|
||||||
|
RewriterBase& rewriter, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value affineMulConst(RewriterBase& rewriter, Location loc, Value value, int64_t multiplier, Operation* constantAnchor) {
|
||||||
|
assert(constantAnchor && "expected a valid constant anchor");
|
||||||
|
if (multiplier == 0)
|
||||||
|
return getOrCreateIndexConstant(rewriter, constantAnchor, 0);
|
||||||
|
if (multiplier == 1)
|
||||||
|
return value;
|
||||||
|
|
||||||
|
AffineExpr d0 = getAffineDimExpr(0, rewriter.getContext());
|
||||||
|
return createOrFoldAffineApply(rewriter, loc, d0 * multiplier, ValueRange {value}, constantAnchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value affineAddConst(RewriterBase& rewriter, Location loc, Value value, int64_t offset, Operation* constantAnchor) {
|
||||||
|
assert(constantAnchor && "expected a valid constant anchor");
|
||||||
|
if (offset == 0)
|
||||||
|
return value;
|
||||||
|
|
||||||
|
AffineExpr d0 = getAffineDimExpr(0, rewriter.getContext());
|
||||||
|
return createOrFoldAffineApply(rewriter, loc, d0 + offset, ValueRange {value}, constantAnchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value affineModConst(RewriterBase& rewriter, 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);
|
||||||
|
|
||||||
|
AffineExpr d0 = getAffineDimExpr(0, rewriter.getContext());
|
||||||
|
return createOrFoldAffineApply(rewriter, loc, d0 % divisor, ValueRange {value}, constantAnchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value affineFloorDivConst(
|
||||||
|
RewriterBase& rewriter, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value affineAddModConst(
|
||||||
|
RewriterBase& rewriter, Location loc, Value value, int64_t offset, int64_t divisor, Operation* constantAnchor) {
|
||||||
|
assert(constantAnchor && "expected a valid constant anchor");
|
||||||
|
assert(divisor > 0 && "expected a positive affine.mod divisor");
|
||||||
|
if (divisor == 1)
|
||||||
|
return getOrCreateIndexConstant(rewriter, constantAnchor, 0);
|
||||||
|
|
||||||
|
AffineExpr d0 = getAffineDimExpr(0, rewriter.getContext());
|
||||||
|
AffineExpr expr = d0;
|
||||||
|
if (offset != 0)
|
||||||
|
expr = expr + offset;
|
||||||
|
return createOrFoldAffineApply(rewriter, loc, expr % divisor, ValueRange {value}, constantAnchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value affineAddFloorDivConst(
|
||||||
|
RewriterBase& rewriter, Location loc, Value value, int64_t offset, int64_t divisor, Operation* constantAnchor) {
|
||||||
|
assert(constantAnchor && "expected a valid constant anchor");
|
||||||
|
assert(divisor > 0 && "expected a positive affine.floor_div divisor");
|
||||||
|
if (divisor == 1)
|
||||||
|
return offset == 0 ? value : affineAddConst(rewriter, loc, value, offset, constantAnchor);
|
||||||
|
|
||||||
|
AffineExpr d0 = getAffineDimExpr(0, rewriter.getContext());
|
||||||
|
AffineExpr expr = d0;
|
||||||
|
if (offset != 0)
|
||||||
|
expr = expr + offset;
|
||||||
|
return createOrFoldAffineApply(rewriter, loc, expr.floorDiv(divisor), ValueRange {value}, constantAnchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<int64_t> evaluateAffineExpr(AffineExpr expr, ArrayRef<int64_t> dims, ArrayRef<int64_t> symbols) {
|
||||||
|
if (auto constant = dyn_cast<AffineConstantExpr>(expr))
|
||||||
|
return constant.getValue();
|
||||||
|
if (auto dim = dyn_cast<AffineDimExpr>(expr)) {
|
||||||
|
unsigned position = dim.getPosition();
|
||||||
|
if (position >= dims.size())
|
||||||
|
return failure();
|
||||||
|
return dims[position];
|
||||||
|
}
|
||||||
|
if (auto symbol = dyn_cast<AffineSymbolExpr>(expr)) {
|
||||||
|
unsigned position = symbol.getPosition();
|
||||||
|
if (position >= symbols.size())
|
||||||
|
return failure();
|
||||||
|
return symbols[position];
|
||||||
|
}
|
||||||
|
|
||||||
|
auto binary = dyn_cast<AffineBinaryOpExpr>(expr);
|
||||||
|
if (!binary)
|
||||||
|
return failure();
|
||||||
|
|
||||||
|
FailureOr<int64_t> lhs = evaluateAffineExpr(binary.getLHS(), dims, symbols);
|
||||||
|
FailureOr<int64_t> rhs = evaluateAffineExpr(binary.getRHS(), dims, symbols);
|
||||||
|
if (failed(lhs) || failed(rhs))
|
||||||
|
return failure();
|
||||||
|
|
||||||
|
switch (binary.getKind()) {
|
||||||
|
case AffineExprKind::Add: return *lhs + *rhs;
|
||||||
|
case AffineExprKind::Mul: return *lhs * *rhs;
|
||||||
|
case AffineExprKind::FloorDiv: return floorDivSigned(*lhs, *rhs);
|
||||||
|
case AffineExprKind::CeilDiv: return ceilDivSigned(*lhs, *rhs);
|
||||||
|
case AffineExprKind::Mod: {
|
||||||
|
FailureOr<int64_t> div = floorDivSigned(*lhs, *rhs);
|
||||||
|
if (failed(div))
|
||||||
|
return failure();
|
||||||
|
return *lhs - *div * *rhs;
|
||||||
|
}
|
||||||
|
default: return failure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<int64_t> evaluateSingleResultAffineMap(AffineMap map, ArrayRef<int64_t> operands) {
|
||||||
|
if (map.getNumResults() != 1 || operands.size() != map.getNumInputs())
|
||||||
|
return failure();
|
||||||
|
|
||||||
|
ArrayRef<int64_t> dims(operands.data(), map.getNumDims());
|
||||||
|
ArrayRef<int64_t> symbols(operands.data() + map.getNumDims(), map.getNumSymbols());
|
||||||
|
return evaluateAffineExpr(map.getResult(0), dims, symbols);
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<int64_t> evaluateAffineApply(affine::AffineApplyOp affineApply, IndexValueResolver resolver) {
|
||||||
|
SmallVector<int64_t, 4> operands;
|
||||||
|
operands.reserve(affineApply.getMapOperands().size());
|
||||||
|
for (Value operand : affineApply.getMapOperands()) {
|
||||||
|
FailureOr<int64_t> folded = resolver(operand);
|
||||||
|
if (failed(folded))
|
||||||
|
return failure();
|
||||||
|
operands.push_back(*folded);
|
||||||
|
}
|
||||||
|
|
||||||
|
return evaluateSingleResultAffineMap(affineApply.getAffineMap(), operands);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isSingleResultSymbolFreeAffineMap(AffineMap map) { return map.getNumResults() == 1 && map.getNumSymbols() == 0; }
|
||||||
|
|
||||||
|
bool isDimAndConstantAffineExpr(AffineExpr expr) {
|
||||||
|
switch (expr.getKind()) {
|
||||||
|
case AffineExprKind::Constant:
|
||||||
|
case AffineExprKind::DimId: return true;
|
||||||
|
case AffineExprKind::SymbolId: return false;
|
||||||
|
case AffineExprKind::Add: {
|
||||||
|
auto binaryExpr = cast<AffineBinaryOpExpr>(expr);
|
||||||
|
return isDimAndConstantAffineExpr(binaryExpr.getLHS()) && isDimAndConstantAffineExpr(binaryExpr.getRHS());
|
||||||
|
}
|
||||||
|
case AffineExprKind::Mul: {
|
||||||
|
auto binaryExpr = cast<AffineBinaryOpExpr>(expr);
|
||||||
|
return (isa<AffineConstantExpr>(binaryExpr.getLHS()) && isDimAndConstantAffineExpr(binaryExpr.getRHS()))
|
||||||
|
|| (isa<AffineConstantExpr>(binaryExpr.getRHS()) && isDimAndConstantAffineExpr(binaryExpr.getLHS()));
|
||||||
|
}
|
||||||
|
case AffineExprKind::FloorDiv:
|
||||||
|
case AffineExprKind::CeilDiv:
|
||||||
|
case AffineExprKind::Mod: {
|
||||||
|
auto binaryExpr = cast<AffineBinaryOpExpr>(expr);
|
||||||
|
return isa<AffineConstantExpr>(binaryExpr.getRHS()) && isDimAndConstantAffineExpr(binaryExpr.getLHS());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
llvm_unreachable("unexpected affine expression kind");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace onnx_mlir
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "mlir/Dialect/Affine/IR/AffineOps.h"
|
||||||
|
#include "mlir/IR/AffineExpr.h"
|
||||||
|
#include "mlir/IR/PatternMatch.h"
|
||||||
|
#include "mlir/Support/LogicalResult.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/FunctionExtras.h"
|
||||||
|
|
||||||
|
namespace onnx_mlir {
|
||||||
|
|
||||||
|
using IndexValueResolver = llvm::function_ref<llvm::FailureOr<int64_t>(mlir::Value)>;
|
||||||
|
|
||||||
|
mlir::Value createOrFoldAffineApply(mlir::RewriterBase& rewriter,
|
||||||
|
mlir::Location loc,
|
||||||
|
mlir::AffineMap map,
|
||||||
|
mlir::ValueRange operands,
|
||||||
|
mlir::Operation* constantAnchor);
|
||||||
|
|
||||||
|
mlir::Value createOrFoldAffineApply(mlir::RewriterBase& rewriter,
|
||||||
|
mlir::Location loc,
|
||||||
|
mlir::AffineExpr expr,
|
||||||
|
mlir::ValueRange dims,
|
||||||
|
mlir::Operation* constantAnchor);
|
||||||
|
|
||||||
|
mlir::Value affineMulConst(mlir::RewriterBase& rewriter,
|
||||||
|
mlir::Location loc,
|
||||||
|
mlir::Value value,
|
||||||
|
int64_t multiplier,
|
||||||
|
mlir::Operation* constantAnchor);
|
||||||
|
|
||||||
|
mlir::Value affineAddConst(mlir::RewriterBase& rewriter,
|
||||||
|
mlir::Location loc,
|
||||||
|
mlir::Value value,
|
||||||
|
int64_t offset,
|
||||||
|
mlir::Operation* constantAnchor);
|
||||||
|
|
||||||
|
mlir::Value affineModConst(mlir::RewriterBase& rewriter,
|
||||||
|
mlir::Location loc,
|
||||||
|
mlir::Value value,
|
||||||
|
int64_t divisor,
|
||||||
|
mlir::Operation* constantAnchor);
|
||||||
|
|
||||||
|
mlir::Value affineFloorDivConst(mlir::RewriterBase& rewriter,
|
||||||
|
mlir::Location loc,
|
||||||
|
mlir::Value value,
|
||||||
|
int64_t divisor,
|
||||||
|
mlir::Operation* constantAnchor);
|
||||||
|
|
||||||
|
mlir::Value affineAddModConst(mlir::RewriterBase& rewriter,
|
||||||
|
mlir::Location loc,
|
||||||
|
mlir::Value value,
|
||||||
|
int64_t offset,
|
||||||
|
int64_t divisor,
|
||||||
|
mlir::Operation* constantAnchor);
|
||||||
|
|
||||||
|
mlir::Value affineAddFloorDivConst(mlir::RewriterBase& rewriter,
|
||||||
|
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 = {});
|
||||||
|
|
||||||
|
llvm::FailureOr<int64_t> evaluateSingleResultAffineMap(mlir::AffineMap map, llvm::ArrayRef<int64_t> operands);
|
||||||
|
|
||||||
|
llvm::FailureOr<int64_t> evaluateAffineApply(mlir::affine::AffineApplyOp affineApply, IndexValueResolver resolver);
|
||||||
|
|
||||||
|
bool isSingleResultSymbolFreeAffineMap(mlir::AffineMap map);
|
||||||
|
|
||||||
|
bool isDimAndConstantAffineExpr(mlir::AffineExpr expr);
|
||||||
|
|
||||||
|
} // namespace onnx_mlir
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.hpp"
|
#include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||||
|
|
||||||
namespace onnx_mlir {
|
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());
|
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> getLaneChunkCoreIds(llvm::ArrayRef<int32_t> coreIds, size_t laneCount, unsigned lane) {
|
||||||
llvm::SmallVector<int32_t> laneCoreIds;
|
llvm::SmallVector<int32_t> laneCoreIds;
|
||||||
laneCoreIds.reserve(coreIds.size() / laneCount);
|
laneCoreIds.reserve(coreIds.size() / laneCount);
|
||||||
@@ -17,4 +77,16 @@ llvm::SmallVector<int32_t> getLaneChunkCoreIds(llvm::ArrayRef<int32_t> coreIds,
|
|||||||
return laneCoreIds;
|
return laneCoreIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isExplicitHostMemCopyOperand(mlir::Operation* op, unsigned operandIndex) {
|
||||||
|
if (mlir::isa<pim::PimMemCopyHostToDevOp>(op))
|
||||||
|
return operandIndex == 3;
|
||||||
|
if (mlir::isa<pim::PimMemCopyDevToHostOp>(op))
|
||||||
|
return operandIndex == 2;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isExplicitDevToHostTargetOperand(mlir::Operation* op, unsigned operandIndex) {
|
||||||
|
return mlir::isa<pim::PimMemCopyDevToHostOp>(op) && operandIndex == 2;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -3,12 +3,30 @@
|
|||||||
#include "llvm/ADT/ArrayRef.h"
|
#include "llvm/ADT/ArrayRef.h"
|
||||||
#include "llvm/ADT/SmallVector.h"
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
|
|
||||||
llvm::SmallVector<int32_t> getBatchCoreIds(pim::PimCoreBatchOp coreBatchOp);
|
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);
|
llvm::SmallVector<int32_t> getLaneChunkCoreIds(llvm::ArrayRef<int32_t> coreIds, size_t laneCount, unsigned lane);
|
||||||
|
|
||||||
|
bool isExplicitHostMemCopyOperand(mlir::Operation* op, unsigned operandIndex);
|
||||||
|
|
||||||
|
bool isExplicitDevToHostTargetOperand(mlir::Operation* op, unsigned operandIndex);
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -1,36 +1,43 @@
|
|||||||
#include "mlir/Dialect/Affine/IR/AffineOps.h"
|
|
||||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||||
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
||||||
#include "mlir/IR/Builders.h"
|
#include "mlir/IR/Builders.h"
|
||||||
#include "mlir/IR/BuiltinOps.h"
|
#include "mlir/IR/BuiltinOps.h"
|
||||||
#include "mlir/IR/Dialect.h"
|
#include "mlir/IR/Dialect.h"
|
||||||
#include "mlir/IR/Matchers.h"
|
|
||||||
|
|
||||||
#include "ConstantUtils.hpp"
|
#include "ConstantUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
|
||||||
|
|
||||||
using namespace mlir;
|
using namespace mlir;
|
||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
|
|
||||||
Block* getHostConstantBlock(Operation* anchorOp) {
|
static std::optional<int64_t> getIndexConstantValue(arith::ConstantOp constantOp) {
|
||||||
|
if (!constantOp.getType().isIndex())
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
auto intAttr = dyn_cast<IntegerAttr>(constantOp.getValue());
|
||||||
|
if (!intAttr || !intAttr.getType().isIndex())
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
return intAttr.getInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
Block* getConstantInsertionBlock(Operation* anchorOp) {
|
||||||
assert(anchorOp && "expected a valid anchor operation");
|
assert(anchorOp && "expected a valid anchor operation");
|
||||||
|
|
||||||
for (Operation* current = anchorOp; current; current = current->getParentOp())
|
if (auto funcOp = dyn_cast<func::FuncOp>(anchorOp))
|
||||||
if (isa<spatial::SpatCompute, spatial::SpatComputeBatch, pim::PimCoreOp, pim::PimCoreBatchOp>(current))
|
return &funcOp.getBody().front();
|
||||||
return current->getBlock();
|
|
||||||
|
|
||||||
if (auto funcOp = anchorOp->getParentOfType<func::FuncOp>())
|
if (auto funcOp = anchorOp->getParentOfType<func::FuncOp>())
|
||||||
return &funcOp.getBody().front();
|
return &funcOp.getBody().front();
|
||||||
|
if (auto moduleOp = dyn_cast<ModuleOp>(anchorOp))
|
||||||
|
return moduleOp.getBody();
|
||||||
if (auto moduleOp = anchorOp->getParentOfType<ModuleOp>())
|
if (auto moduleOp = anchorOp->getParentOfType<ModuleOp>())
|
||||||
return moduleOp.getBody();
|
return moduleOp.getBody();
|
||||||
return anchorOp->getBlock();
|
return anchorOp->getBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
Value getOrCreateHostConstant(Operation* anchorOp, Attribute value, Type type, OperationFolder& folder) {
|
Value getOrCreateConstant(OperationFolder& folder, Operation* anchorOp, Attribute value, Type type) {
|
||||||
assert(anchorOp && "expected a valid anchor operation");
|
assert(anchorOp && "expected a valid anchor operation");
|
||||||
Block* hostBlock = getHostConstantBlock(anchorOp);
|
Block* hostBlock = getConstantInsertionBlock(anchorOp);
|
||||||
for (Operation& op : *hostBlock) {
|
for (Operation& op : *hostBlock) {
|
||||||
auto constantOp = dyn_cast<arith::ConstantOp>(&op);
|
auto constantOp = dyn_cast<arith::ConstantOp>(&op);
|
||||||
if (!constantOp || constantOp.getType() != type || constantOp.getValue() != value)
|
if (!constantOp || constantOp.getType() != type || constantOp.getValue() != value)
|
||||||
@@ -42,9 +49,9 @@ Value getOrCreateHostConstant(Operation* anchorOp, Attribute value, Type type, O
|
|||||||
return folder.getOrCreateConstant(hostBlock, arithDialect, value, type);
|
return folder.getOrCreateConstant(hostBlock, arithDialect, value, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
Value getOrCreateHostConstant(Operation* anchorOp, Attribute value, Type type, RewriterBase& rewriter) {
|
Value getOrCreateConstant(RewriterBase& rewriter, Operation* anchorOp, Attribute value, Type type) {
|
||||||
assert(anchorOp && "expected a valid anchor operation");
|
assert(anchorOp && "expected a valid anchor operation");
|
||||||
Block* hostBlock = getHostConstantBlock(anchorOp);
|
Block* hostBlock = getConstantInsertionBlock(anchorOp);
|
||||||
for (Operation& op : *hostBlock) {
|
for (Operation& op : *hostBlock) {
|
||||||
auto constantOp = dyn_cast<arith::ConstantOp>(&op);
|
auto constantOp = dyn_cast<arith::ConstantOp>(&op);
|
||||||
if (!constantOp || constantOp.getType() != type || constantOp.getValue() != value)
|
if (!constantOp || constantOp.getType() != type || constantOp.getValue() != value)
|
||||||
@@ -57,48 +64,94 @@ Value getOrCreateHostConstant(Operation* anchorOp, Attribute value, Type type, R
|
|||||||
return arith::ConstantOp::create(rewriter, anchorOp->getLoc(), type, cast<TypedAttr>(value)).getResult();
|
return arith::ConstantOp::create(rewriter, anchorOp->getLoc(), type, cast<TypedAttr>(value)).getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
Value getOrCreateHostConstantLike(arith::ConstantOp constantOp, OperationFolder& folder) {
|
Value getOrCreateConstantLike(OperationFolder& folder, arith::ConstantOp constantOp) {
|
||||||
return getOrCreateHostConstant(constantOp.getOperation(), constantOp.getValue(), constantOp.getType(), folder);
|
return getOrCreateConstant(folder, constantOp.getOperation(), constantOp.getValue(), constantOp.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
Value getOrCreateHostIndexConstant(Operation* anchorOp, int64_t value, OperationFolder& folder) {
|
Value getOrCreateIndexConstant(OperationFolder& folder, Operation* anchorOp, int64_t value) {
|
||||||
Builder builder(anchorOp->getContext());
|
Builder builder(anchorOp->getContext());
|
||||||
return getOrCreateHostConstant(anchorOp, builder.getIndexAttr(value), builder.getIndexType(), folder);
|
return getOrCreateConstant(folder, anchorOp, builder.getIndexAttr(value), builder.getIndexType());
|
||||||
}
|
}
|
||||||
|
|
||||||
Value getOrCreateHostIndexConstant(Operation* anchorOp, int64_t value, RewriterBase& rewriter) {
|
Value getOrCreateIndexConstant(RewriterBase& rewriter, Operation* anchorOp, int64_t value) {
|
||||||
Builder builder(anchorOp->getContext());
|
Builder builder(anchorOp->getContext());
|
||||||
return getOrCreateHostConstant(anchorOp, builder.getIndexAttr(value), builder.getIndexType(), rewriter);
|
return getOrCreateConstant(rewriter, anchorOp, builder.getIndexAttr(value), builder.getIndexType());
|
||||||
}
|
}
|
||||||
|
|
||||||
Value getOrCreateHostI32Constant(Operation* anchorOp, int32_t value, OperationFolder& folder) {
|
void hoistAndUniquifyIndexConstants(func::FuncOp funcOp, RewriterBase& rewriter) {
|
||||||
Builder builder(anchorOp->getContext());
|
if (funcOp.getBody().empty())
|
||||||
return getOrCreateHostConstant(anchorOp, builder.getI32IntegerAttr(value), builder.getI32Type(), folder);
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
Value getOrCreateHostI64Constant(Operation* anchorOp, int64_t value, OperationFolder& folder) {
|
Block& entryBlock = funcOp.getBody().front();
|
||||||
Builder builder(anchorOp->getContext());
|
DenseMap<int64_t, Value> canonicalByValue;
|
||||||
return getOrCreateHostConstant(anchorOp, builder.getI64IntegerAttr(value), builder.getI64Type(), folder);
|
SmallVector<arith::ConstantOp> constants;
|
||||||
}
|
|
||||||
|
|
||||||
Value createAffineApplyOrFoldedConstant(
|
funcOp.walk([&](arith::ConstantOp constantOp) {
|
||||||
RewriterBase& rewriter, Location loc, AffineMap map, ValueRange operands, Operation* anchorOp) {
|
if (!getIndexConstantValue(constantOp))
|
||||||
SmallVector<Attribute> operandConstants;
|
return;
|
||||||
operandConstants.reserve(operands.size());
|
constants.push_back(constantOp);
|
||||||
for (Value operand : operands) {
|
});
|
||||||
APInt constantValue;
|
|
||||||
if (!matchPattern(operand, m_ConstantInt(&constantValue)))
|
for (arith::ConstantOp constantOp : constants) {
|
||||||
return affine::AffineApplyOp::create(rewriter, loc, map, operands).getResult();
|
auto value = getIndexConstantValue(constantOp);
|
||||||
operandConstants.push_back(rewriter.getIndexAttr(constantValue.getSExtValue()));
|
if (!value || constantOp->getBlock() != &entryBlock)
|
||||||
|
continue;
|
||||||
|
canonicalByValue.try_emplace(*value, constantOp.getResult());
|
||||||
}
|
}
|
||||||
|
|
||||||
SmallVector<Attribute> foldedResults;
|
for (arith::ConstantOp constantOp : constants) {
|
||||||
if (succeeded(map.constantFold(operandConstants, foldedResults))) {
|
auto value = getIndexConstantValue(constantOp);
|
||||||
if (auto constantResult = dyn_cast<IntegerAttr>(foldedResults.front()))
|
if (!value)
|
||||||
return getOrCreateHostIndexConstant(anchorOp, constantResult.getInt(), rewriter);
|
continue;
|
||||||
|
|
||||||
|
Value canonical = canonicalByValue.lookup(*value);
|
||||||
|
if (!canonical) {
|
||||||
|
OpBuilder::InsertionGuard guard(rewriter);
|
||||||
|
rewriter.setInsertionPointToStart(&entryBlock);
|
||||||
|
Builder builder(funcOp.getContext());
|
||||||
|
canonical =
|
||||||
|
arith::ConstantOp::create(rewriter, constantOp.getLoc(), builder.getIndexType(), builder.getIndexAttr(*value));
|
||||||
|
canonicalByValue[*value] = canonical;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (constantOp.getResult() == canonical)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
constantOp.getResult().replaceAllUsesWith(canonical);
|
||||||
}
|
}
|
||||||
|
|
||||||
return affine::AffineApplyOp::create(rewriter, loc, map, operands).getResult();
|
for (arith::ConstantOp constantOp : llvm::reverse(constants)) {
|
||||||
|
auto value = getIndexConstantValue(constantOp);
|
||||||
|
if (!value)
|
||||||
|
continue;
|
||||||
|
if (constantOp.getResult() == canonicalByValue.lookup(*value))
|
||||||
|
continue;
|
||||||
|
if (constantOp.use_empty())
|
||||||
|
rewriter.eraseOp(constantOp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int64_t> matchConstantIndexValue(Value value) {
|
||||||
|
if (!value || !value.getType().isIndex())
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
if (auto constant = value.getDefiningOp<arith::ConstantIndexOp>())
|
||||||
|
return constant.value();
|
||||||
|
|
||||||
|
if (auto constant = value.getDefiningOp<arith::ConstantOp>())
|
||||||
|
if (auto intAttr = dyn_cast<IntegerAttr>(constant.getValue()); intAttr && intAttr.getType().isIndex())
|
||||||
|
return intAttr.getInt();
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int64_t> matchConstantIndexValue(OpFoldResult value) {
|
||||||
|
if (auto attr = dyn_cast<Attribute>(value))
|
||||||
|
if (auto intAttr = dyn_cast<IntegerAttr>(attr); intAttr && intAttr.getType().isIndex())
|
||||||
|
return intAttr.getInt();
|
||||||
|
if (auto operand = dyn_cast<Value>(value))
|
||||||
|
return matchConstantIndexValue(operand);
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -1,39 +1,33 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "mlir/Dialect/Affine/IR/AffineOps.h"
|
|
||||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||||
|
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
||||||
#include "mlir/IR/PatternMatch.h"
|
#include "mlir/IR/PatternMatch.h"
|
||||||
#include "mlir/IR/Value.h"
|
#include "mlir/IR/Value.h"
|
||||||
#include "mlir/Transforms/FoldUtils.h"
|
#include "mlir/Transforms/FoldUtils.h"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
|
|
||||||
mlir::Block* getHostConstantBlock(mlir::Operation* anchorOp);
|
mlir::Block* getConstantInsertionBlock(mlir::Operation* anchorOp);
|
||||||
|
|
||||||
mlir::Value getOrCreateHostConstant(mlir::Operation* anchorOp,
|
mlir::Value
|
||||||
mlir::Attribute value,
|
getOrCreateConstant(mlir::OperationFolder& folder, mlir::Operation* anchorOp, mlir::Attribute value, mlir::Type type);
|
||||||
mlir::Type type,
|
|
||||||
mlir::OperationFolder& folder);
|
|
||||||
|
|
||||||
mlir::Value getOrCreateHostConstant(mlir::Operation* anchorOp,
|
mlir::Value
|
||||||
mlir::Attribute value,
|
getOrCreateConstant(mlir::RewriterBase& rewriter, mlir::Operation* anchorOp, mlir::Attribute value, mlir::Type type);
|
||||||
mlir::Type type,
|
|
||||||
mlir::RewriterBase& rewriter);
|
|
||||||
|
|
||||||
mlir::Value getOrCreateHostConstantLike(mlir::arith::ConstantOp constantOp, mlir::OperationFolder& folder);
|
mlir::Value getOrCreateConstantLike(mlir::OperationFolder& folder, mlir::arith::ConstantOp constantOp);
|
||||||
|
|
||||||
mlir::Value getOrCreateHostIndexConstant(mlir::Operation* anchorOp, int64_t value, mlir::OperationFolder& folder);
|
mlir::Value getOrCreateIndexConstant(mlir::OperationFolder& folder, mlir::Operation* anchorOp, int64_t value);
|
||||||
|
|
||||||
mlir::Value getOrCreateHostIndexConstant(mlir::Operation* anchorOp, int64_t value, mlir::RewriterBase& rewriter);
|
mlir::Value getOrCreateIndexConstant(mlir::RewriterBase& rewriter, mlir::Operation* anchorOp, int64_t value);
|
||||||
|
|
||||||
mlir::Value getOrCreateHostI32Constant(mlir::Operation* anchorOp, int32_t value, mlir::OperationFolder& folder);
|
void hoistAndUniquifyIndexConstants(mlir::func::FuncOp funcOp, mlir::RewriterBase& rewriter);
|
||||||
|
|
||||||
mlir::Value getOrCreateHostI64Constant(mlir::Operation* anchorOp, int64_t value, mlir::OperationFolder& folder);
|
std::optional<int64_t> matchConstantIndexValue(mlir::Value value);
|
||||||
|
|
||||||
mlir::Value createAffineApplyOrFoldedConstant(mlir::RewriterBase& rewriter,
|
std::optional<int64_t> matchConstantIndexValue(mlir::OpFoldResult value);
|
||||||
mlir::Location loc,
|
|
||||||
mlir::AffineMap map,
|
|
||||||
mlir::ValueRange operands,
|
|
||||||
mlir::Operation* anchorOp);
|
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -74,6 +74,21 @@ walkPimCoreBlock(mlir::Block& block,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auto ifOp = mlir::dyn_cast<mlir::scf::IfOp>(op)) {
|
||||||
|
auto condition = resolveIndexValue(ifOp.getCondition(), knowledge);
|
||||||
|
if (failed(condition)) {
|
||||||
|
ifOp.emitOpError("requires statically evaluable scf.if condition for PIM codegen");
|
||||||
|
hasFailure = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
mlir::Region& selectedRegion = *condition != 0 ? ifOp.getThenRegion() : ifOp.getElseRegion();
|
||||||
|
if (!selectedRegion.empty())
|
||||||
|
if (failed(walkPimCoreBlock(selectedRegion.front(), knowledge, callback)))
|
||||||
|
hasFailure = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (failed(callback(op, knowledge)))
|
if (failed(callback(op, knowledge)))
|
||||||
hasFailure = true;
|
hasFailure = true;
|
||||||
}
|
}
|
||||||
@@ -128,6 +143,22 @@ mlir::LogicalResult walkPimCoreBlockStructurally(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auto ifOp = mlir::dyn_cast<mlir::scf::IfOp>(op)) {
|
||||||
|
if (failed(resolveIndexValue(ifOp.getCondition(), knowledge))) {
|
||||||
|
ifOp.emitOpError("requires statically evaluable scf.if condition for PIM verification");
|
||||||
|
hasFailure = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ifOp.getThenRegion().empty())
|
||||||
|
if (failed(walkPimCoreBlockStructurally(ifOp.getThenRegion().front(), knowledge, callback)))
|
||||||
|
hasFailure = true;
|
||||||
|
if (!ifOp.getElseRegion().empty())
|
||||||
|
if (failed(walkPimCoreBlockStructurally(ifOp.getElseRegion().front(), knowledge, callback)))
|
||||||
|
hasFailure = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (failed(callback(op, knowledge)))
|
if (failed(callback(op, knowledge)))
|
||||||
hasFailure = true;
|
hasFailure = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/IndexingUtils.hpp"
|
||||||
|
|
||||||
|
using namespace mlir;
|
||||||
|
|
||||||
|
namespace onnx_mlir {
|
||||||
|
|
||||||
|
int64_t normalizeAxis(int64_t axis, int64_t rank) { return axis >= 0 ? axis : rank + axis; }
|
||||||
|
|
||||||
|
FailureOr<int64_t> normalizeAxisChecked(int64_t axis, int64_t rank) {
|
||||||
|
int64_t normalizedAxis = normalizeAxis(axis, rank);
|
||||||
|
if (normalizedAxis < 0 || normalizedAxis >= rank)
|
||||||
|
return failure();
|
||||||
|
return normalizedAxis;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t normalizeIndex(int64_t index, int64_t dimSize) { return index >= 0 ? index : dimSize + index; }
|
||||||
|
|
||||||
|
static SmallVector<int64_t> normalizeAxesImpl(std::optional<ArrayAttr> axesAttr, int64_t rank) {
|
||||||
|
SmallVector<int64_t> normalizedAxes;
|
||||||
|
if (!axesAttr) {
|
||||||
|
normalizedAxes.reserve(rank);
|
||||||
|
for (int64_t axis = 0; axis < rank; ++axis)
|
||||||
|
normalizedAxes.push_back(axis);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
normalizedAxes.reserve(axesAttr->size());
|
||||||
|
for (Attribute attr : *axesAttr)
|
||||||
|
normalizedAxes.push_back(normalizeAxis(cast<IntegerAttr>(attr).getInt(), rank));
|
||||||
|
llvm::sort(normalizedAxes);
|
||||||
|
normalizedAxes.erase(std::unique(normalizedAxes.begin(), normalizedAxes.end()), normalizedAxes.end());
|
||||||
|
}
|
||||||
|
return normalizedAxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<SmallVector<int64_t>> normalizeAxesChecked(std::optional<ArrayAttr> axesAttr, int64_t rank) {
|
||||||
|
SmallVector<int64_t> normalizedAxes = normalizeAxesImpl(axesAttr, rank);
|
||||||
|
for (int64_t axis : normalizedAxes)
|
||||||
|
if (axis < 0 || axis >= rank)
|
||||||
|
return failure();
|
||||||
|
return normalizedAxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace onnx_mlir
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "mlir/IR/BuiltinAttributes.h"
|
||||||
|
#include "mlir/Support/LogicalResult.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace onnx_mlir {
|
||||||
|
|
||||||
|
int64_t normalizeAxis(int64_t axis, int64_t rank);
|
||||||
|
|
||||||
|
mlir::FailureOr<int64_t> normalizeAxisChecked(int64_t axis, int64_t rank);
|
||||||
|
|
||||||
|
int64_t normalizeIndex(int64_t index, int64_t dimSize);
|
||||||
|
|
||||||
|
mlir::FailureOr<llvm::SmallVector<int64_t>> normalizeAxesChecked(std::optional<mlir::ArrayAttr> axesAttr, int64_t rank);
|
||||||
|
|
||||||
|
} // namespace onnx_mlir
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||||
|
|
||||||
|
#include "llvm/Support/MathExtras.h"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "ConstantUtils.hpp"
|
||||||
|
#include "LoopUtils.hpp"
|
||||||
|
|
||||||
|
using namespace mlir;
|
||||||
|
|
||||||
|
namespace onnx_mlir {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
static std::optional<int64_t> getStaticTripCount(Value lowerBound, Value upperBound, Value step) {
|
||||||
|
auto lower = matchConstantIndexValue(lowerBound);
|
||||||
|
auto upper = matchConstantIndexValue(upperBound);
|
||||||
|
auto stepValue = matchConstantIndexValue(step);
|
||||||
|
if (!lower || !upper || !stepValue)
|
||||||
|
return std::nullopt;
|
||||||
|
if (*stepValue <= 0)
|
||||||
|
return std::nullopt;
|
||||||
|
if (*upper <= *lower)
|
||||||
|
return int64_t {0};
|
||||||
|
return llvm::divideCeil(*upper - *lower, *stepValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
static LogicalResult validateNormalizedLoopYields(Location loc, ValueRange initArgs, ArrayRef<Value> yieldedValues) {
|
||||||
|
if (yieldedValues.size() == initArgs.size())
|
||||||
|
return success();
|
||||||
|
|
||||||
|
emitError(loc) << "normalized loop body yielded " << yieldedValues.size() << " values for " << initArgs.size()
|
||||||
|
<< " iter args";
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<NormalizedLoopResult> buildNormalizedScfFor(OpBuilder& builder,
|
||||||
|
Location loc,
|
||||||
|
Value lowerBound,
|
||||||
|
Value upperBound,
|
||||||
|
Value step,
|
||||||
|
ValueRange initArgs,
|
||||||
|
NormalizedLoopBodyBuilder bodyBuilder) {
|
||||||
|
NormalizedLoopResult result;
|
||||||
|
|
||||||
|
if (auto stepValue = matchConstantIndexValue(step); stepValue && *stepValue <= 0) {
|
||||||
|
emitError(loc) << "normalized scf.for requires a positive step, got " << *stepValue;
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto tripCount = getStaticTripCount(lowerBound, upperBound, step)) {
|
||||||
|
if (*tripCount == 0) {
|
||||||
|
llvm::append_range(result.results, initArgs);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*tripCount == 1) {
|
||||||
|
result.inductionVar = lowerBound;
|
||||||
|
if (failed(bodyBuilder(builder, loc, lowerBound, initArgs, result.results)))
|
||||||
|
return failure();
|
||||||
|
if (failed(validateNormalizedLoopYields(loc, initArgs, result.results)))
|
||||||
|
return failure();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.loop = scf::ForOp::create(builder, loc, lowerBound, upperBound, step, initArgs);
|
||||||
|
result.inductionVar = result.loop.getInductionVar();
|
||||||
|
|
||||||
|
{
|
||||||
|
OpBuilder::InsertionGuard guard(builder);
|
||||||
|
Block* body = result.loop.getBody();
|
||||||
|
if (!body->empty())
|
||||||
|
if (auto yieldOp = dyn_cast<scf::YieldOp>(body->back()))
|
||||||
|
yieldOp->erase();
|
||||||
|
builder.setInsertionPointToEnd(body);
|
||||||
|
ValueRange iterArgs = result.loop.getRegionIterArgs();
|
||||||
|
if (failed(bodyBuilder(builder, loc, result.inductionVar, iterArgs, result.results))) {
|
||||||
|
result.loop.erase();
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
if (failed(validateNormalizedLoopYields(loc, initArgs, result.results))) {
|
||||||
|
result.loop.erase();
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
scf::YieldOp::create(builder, loc, result.results);
|
||||||
|
}
|
||||||
|
builder.setInsertionPointAfter(result.loop);
|
||||||
|
result.results.assign(result.loop.getResults().begin(), result.loop.getResults().end());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace onnx_mlir
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||||
|
#include "mlir/IR/Builders.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/STLFunctionalExtras.h"
|
||||||
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
|
||||||
|
namespace onnx_mlir {
|
||||||
|
|
||||||
|
struct NormalizedLoopResult {
|
||||||
|
mlir::Value inductionVar;
|
||||||
|
llvm::SmallVector<mlir::Value, 4> results;
|
||||||
|
mlir::scf::ForOp loop;
|
||||||
|
|
||||||
|
bool wasInlined() const { return !loop; }
|
||||||
|
};
|
||||||
|
|
||||||
|
using NormalizedLoopBodyBuilder = llvm::function_ref<mlir::LogicalResult(
|
||||||
|
mlir::OpBuilder&, mlir::Location, mlir::Value, mlir::ValueRange, llvm::SmallVectorImpl<mlir::Value>&)>;
|
||||||
|
|
||||||
|
mlir::FailureOr<NormalizedLoopResult> buildNormalizedScfFor(mlir::OpBuilder& builder,
|
||||||
|
mlir::Location loc,
|
||||||
|
mlir::Value lowerBound,
|
||||||
|
mlir::Value upperBound,
|
||||||
|
mlir::Value step,
|
||||||
|
mlir::ValueRange initArgs,
|
||||||
|
NormalizedLoopBodyBuilder bodyBuilder);
|
||||||
|
|
||||||
|
} // namespace onnx_mlir
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
#include "llvm/ADT/STLExtras.h"
|
#include "llvm/ADT/STLExtras.h"
|
||||||
|
#include "llvm/ADT/SmallVector.h"
|
||||||
#include "llvm/Support/ErrorHandling.h"
|
#include "llvm/Support/ErrorHandling.h"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
@@ -111,4 +114,132 @@ bool isMemoryContiguous(llvm::ArrayRef<int64_t> srcShape,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isContiguousSubviewWithDynamicOffsets(llvm::ArrayRef<int64_t> sourceShape,
|
||||||
|
llvm::ArrayRef<mlir::OpFoldResult> mixedOffsets,
|
||||||
|
llvm::ArrayRef<int64_t> staticSizes,
|
||||||
|
llvm::ArrayRef<int64_t> staticStrides) {
|
||||||
|
if (sourceShape.size() != mixedOffsets.size() || sourceShape.size() != staticSizes.size()
|
||||||
|
|| sourceShape.size() != staticStrides.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (llvm::any_of(staticStrides, [](int64_t stride) { return stride != 1; }))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto reversedTriples =
|
||||||
|
llvm::zip_equal(llvm::reverse(sourceShape), llvm::reverse(mixedOffsets), llvm::reverse(staticSizes));
|
||||||
|
|
||||||
|
auto firstNonZeroOrDynamicOffset = llvm::find_if(reversedTriples, [](auto triple) {
|
||||||
|
auto [_sourceDim, offset, _size] = triple;
|
||||||
|
if (auto attr = mlir::dyn_cast<mlir::Attribute>(offset))
|
||||||
|
return mlir::cast<mlir::IntegerAttr>(attr).getInt() != 0;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (firstNonZeroOrDynamicOffset != reversedTriples.end()) {
|
||||||
|
auto [sourceDim, offset, size] = *firstNonZeroOrDynamicOffset;
|
||||||
|
if (auto attr = mlir::dyn_cast<mlir::Attribute>(offset)) {
|
||||||
|
int64_t staticOffset = mlir::cast<mlir::IntegerAttr>(attr).getInt();
|
||||||
|
if (size > sourceDim - staticOffset)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
++firstNonZeroOrDynamicOffset;
|
||||||
|
for (auto it = firstNonZeroOrDynamicOffset; it != reversedTriples.end(); ++it)
|
||||||
|
if (std::get<2>(*it) != 1)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto reversedSizes = llvm::zip_equal(llvm::reverse(sourceShape), llvm::reverse(staticSizes));
|
||||||
|
auto firstDifferentSize = llvm::find_if(reversedSizes, [](auto pair) {
|
||||||
|
auto [sourceDim, size] = pair;
|
||||||
|
return size != sourceDim;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (firstDifferentSize != reversedSizes.end()) {
|
||||||
|
++firstDifferentSize;
|
||||||
|
for (auto it = firstDifferentSize; it != reversedSizes.end(); ++it)
|
||||||
|
if (std::get<1>(*it) != 1)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -1,15 +1,24 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "mlir/IR/BuiltinTypes.h"
|
#include "mlir/IR/BuiltinTypes.h"
|
||||||
|
#include "mlir/IR/OpDefinition.h"
|
||||||
|
#include "mlir/IR/PatternMatch.h"
|
||||||
#include "mlir/IR/Value.h"
|
#include "mlir/IR/Value.h"
|
||||||
|
|
||||||
#include "llvm/ADT/ArrayRef.h"
|
#include "llvm/ADT/ArrayRef.h"
|
||||||
|
#include "llvm/ADT/DenseMap.h"
|
||||||
#include "llvm/ADT/SmallVector.h"
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <optional>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace onnx_mlir {
|
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> computeRowMajorStrides(llvm::ArrayRef<int64_t> shape);
|
||||||
|
|
||||||
llvm::SmallVector<int64_t>
|
llvm::SmallVector<int64_t>
|
||||||
@@ -30,4 +39,74 @@ bool isMemoryContiguous(llvm::ArrayRef<int64_t> srcShape,
|
|||||||
llvm::ArrayRef<int64_t> sizes,
|
llvm::ArrayRef<int64_t> sizes,
|
||||||
llvm::ArrayRef<int64_t> strides);
|
llvm::ArrayRef<int64_t> strides);
|
||||||
|
|
||||||
|
bool isContiguousSubviewWithDynamicOffsets(llvm::ArrayRef<int64_t> sourceShape,
|
||||||
|
llvm::ArrayRef<mlir::OpFoldResult> mixedOffsets,
|
||||||
|
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
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -31,6 +31,19 @@ Value stripMemRefViewOps(Value value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Value stripMemRefAddressingOps(Value value) {
|
||||||
|
while (true) {
|
||||||
|
if (auto subviewOp = value.getDefiningOp<memref::SubViewOp>()) {
|
||||||
|
value = subviewOp.getSource();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Value strippedValue = stripMemRefViewOps(value);
|
||||||
|
if (strippedValue == value)
|
||||||
|
return value;
|
||||||
|
value = strippedValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool hasAllStaticSubviewParts(memref::SubViewOp subview) {
|
bool hasAllStaticSubviewParts(memref::SubViewOp subview) {
|
||||||
return llvm::all_of(subview.getStaticOffsets(), [](int64_t value) { return !ShapedType::isDynamic(value); })
|
return llvm::all_of(subview.getStaticOffsets(), [](int64_t value) { return !ShapedType::isDynamic(value); })
|
||||||
&& llvm::all_of(subview.getStaticSizes(), [](int64_t value) { return !ShapedType::isDynamic(value); })
|
&& llvm::all_of(subview.getStaticSizes(), [](int64_t value) { return !ShapedType::isDynamic(value); })
|
||||||
@@ -81,4 +94,13 @@ FailureOr<SmallVector<int64_t>> getStaticSubviewOffsets(const StaticSubviewInfo&
|
|||||||
return staticOffsets;
|
return staticOffsets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isMemRefBaseAddressableValue(Value value) {
|
||||||
|
value = stripMemRefAddressingOps(value);
|
||||||
|
if (isa<BlockArgument>(value))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
Operation* defOp = value.getDefiningOp();
|
||||||
|
return defOp && isa<memref::AllocOp, memref::GetGlobalOp>(defOp);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ mlir::Value stripMemRefCasts(mlir::Value value);
|
|||||||
|
|
||||||
mlir::Value stripMemRefViewOps(mlir::Value value);
|
mlir::Value stripMemRefViewOps(mlir::Value value);
|
||||||
|
|
||||||
|
mlir::Value stripMemRefAddressingOps(mlir::Value value);
|
||||||
|
|
||||||
bool hasAllStaticSubviewParts(mlir::memref::SubViewOp subview);
|
bool hasAllStaticSubviewParts(mlir::memref::SubViewOp subview);
|
||||||
|
|
||||||
llvm::FailureOr<StaticSubviewInfo> getStaticSubviewInfo(mlir::Value value);
|
llvm::FailureOr<StaticSubviewInfo> getStaticSubviewInfo(mlir::Value value);
|
||||||
@@ -27,4 +29,6 @@ llvm::FailureOr<StaticSubviewInfo> getStaticSubviewInfo(mlir::Value value);
|
|||||||
/// Returns the offsets in `info` as int64_t, failing if any offset is dynamic.
|
/// Returns the offsets in `info` as int64_t, failing if any offset is dynamic.
|
||||||
llvm::FailureOr<llvm::SmallVector<int64_t>> getStaticSubviewOffsets(const StaticSubviewInfo& info);
|
llvm::FailureOr<llvm::SmallVector<int64_t>> getStaticSubviewOffsets(const StaticSubviewInfo& info);
|
||||||
|
|
||||||
|
bool isMemRefBaseAddressableValue(mlir::Value value);
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
#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();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace onnx_mlir
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#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 {
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
} // namespace onnx_mlir
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#include "mlir/Dialect/Linalg/IR/Linalg.h"
|
||||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
#include "mlir/IR/BuiltinAttributes.h"
|
#include "mlir/IR/BuiltinAttributes.h"
|
||||||
#include "mlir/IR/BuiltinTypes.h"
|
#include "mlir/IR/BuiltinTypes.h"
|
||||||
@@ -46,26 +47,14 @@ CompiledIndexExpr mulExpr(CompiledIndexExpr lhs, int64_t rhs) {
|
|||||||
return makeBinaryExpr(CompiledIndexExprNode::Kind::Mul, std::move(lhs), makeConstantExpr(rhs));
|
return makeBinaryExpr(CompiledIndexExprNode::Kind::Mul, std::move(lhs), makeConstantExpr(rhs));
|
||||||
}
|
}
|
||||||
|
|
||||||
mlir::Value stripWeightViewOps(mlir::Value value) {
|
llvm::FailureOr<llvm::SmallVector<int64_t>> getStaticMemRefTypeStrides(mlir::MemRefType type) {
|
||||||
while (true) {
|
llvm::SmallVector<int64_t> strides;
|
||||||
if (auto subviewOp = value.getDefiningOp<mlir::memref::SubViewOp>()) {
|
int64_t offset = 0;
|
||||||
value = subviewOp.getSource();
|
if (failed(type.getStridesAndOffset(strides, offset)))
|
||||||
continue;
|
return mlir::failure();
|
||||||
}
|
if (llvm::is_contained(strides, mlir::ShapedType::kDynamic))
|
||||||
if (auto castOp = value.getDefiningOp<mlir::memref::CastOp>()) {
|
return mlir::failure();
|
||||||
value = castOp.getSource();
|
return strides;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (auto collapseOp = value.getDefiningOp<mlir::memref::CollapseShapeOp>()) {
|
|
||||||
value = collapseOp.getSrc();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (auto expandOp = value.getDefiningOp<mlir::memref::ExpandShapeOp>()) {
|
|
||||||
value = expandOp.getSrc();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename VMMOpTy, typename ParentOpTy>
|
template <typename VMMOpTy, typename ParentOpTy>
|
||||||
@@ -131,8 +120,8 @@ bool hasOnlySpatialMvmVmmWeightUses(mlir::Value value) {
|
|||||||
return expandShapeOp.getSrc() == currentValue && self(expandShapeOp.getResult(), self);
|
return expandShapeOp.getSrc() == currentValue && self(expandShapeOp.getResult(), self);
|
||||||
if (auto collapseShapeOp = mlir::dyn_cast<mlir::tensor::CollapseShapeOp>(user))
|
if (auto collapseShapeOp = mlir::dyn_cast<mlir::tensor::CollapseShapeOp>(user))
|
||||||
return collapseShapeOp.getSrc() == currentValue && self(collapseShapeOp.getResult(), self);
|
return collapseShapeOp.getSrc() == currentValue && self(collapseShapeOp.getResult(), self);
|
||||||
if (auto transposeOp = mlir::dyn_cast<mlir::ONNXTransposeOp>(user))
|
if (auto transposeOp = mlir::dyn_cast<mlir::linalg::TransposeOp>(user))
|
||||||
return transposeOp.getData() == currentValue && self(transposeOp.getResult(), self);
|
return transposeOp.getInput() == currentValue && self(transposeOp.getResult()[0], self);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
@@ -158,7 +147,7 @@ void walkPimMvmVmmWeightUses(mlir::Operation* root, llvm::function_ref<void(mlir
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::optional<unsigned> resolveWeightIndex(mlir::Operation* weightOwner, mlir::Value weight) {
|
std::optional<unsigned> resolveWeightIndex(mlir::Operation* weightOwner, mlir::Value weight) {
|
||||||
weight = stripWeightViewOps(weight);
|
weight = stripMemRefAddressingOps(weight);
|
||||||
|
|
||||||
if (auto coreOp = mlir::dyn_cast_or_null<pim::PimCoreOp>(weightOwner)) {
|
if (auto coreOp = mlir::dyn_cast_or_null<pim::PimCoreOp>(weightOwner)) {
|
||||||
for (unsigned weightIndex = 0; weightIndex < coreOp.getWeights().size(); ++weightIndex)
|
for (unsigned weightIndex = 0; weightIndex < coreOp.getWeights().size(); ++weightIndex)
|
||||||
@@ -177,16 +166,17 @@ std::optional<unsigned> resolveWeightIndex(mlir::Operation* weightOwner, mlir::V
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<unsigned> resolveWeightIndex(mlir::Operation* weightOwner, pim::PimVMMOp vmmOp) {
|
|
||||||
return resolveWeightIndex(weightOwner, vmmOp.getWeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
llvm::FailureOr<ResolvedWeightView>
|
llvm::FailureOr<ResolvedWeightView>
|
||||||
resolveWeightView(mlir::Operation* weightOwner, mlir::Value weight, const StaticValueKnowledge& knowledge) {
|
resolveWeightView(mlir::Operation* weightOwner, mlir::Value weight, const StaticValueKnowledge& knowledge) {
|
||||||
llvm::SmallVector<mlir::Operation*> viewOps;
|
llvm::SmallVector<mlir::Operation*> viewOps;
|
||||||
mlir::Value current = weight;
|
mlir::Value current = weight;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
if (mlir::Value directAlias = knowledge.aliases.lookup(current); directAlias && directAlias != current) {
|
||||||
|
current = directAlias;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (auto defOp = current.getDefiningOp()) {
|
if (auto defOp = current.getDefiningOp()) {
|
||||||
if (auto getGlobalOp = mlir::dyn_cast<mlir::memref::GetGlobalOp>(defOp)) {
|
if (auto getGlobalOp = mlir::dyn_cast<mlir::memref::GetGlobalOp>(defOp)) {
|
||||||
auto moduleOp = weightOwner ? weightOwner->getParentOfType<mlir::ModuleOp>() : mlir::ModuleOp {};
|
auto moduleOp = weightOwner ? weightOwner->getParentOfType<mlir::ModuleOp>() : mlir::ModuleOp {};
|
||||||
@@ -206,8 +196,6 @@ resolveWeightView(mlir::Operation* weightOwner, mlir::Value weight, const Static
|
|||||||
CompiledIndexExpr offsetExpr = makeConstantExpr(0);
|
CompiledIndexExpr offsetExpr = makeConstantExpr(0);
|
||||||
for (mlir::Operation* viewOp : llvm::reverse(viewOps)) {
|
for (mlir::Operation* viewOp : llvm::reverse(viewOps)) {
|
||||||
if (auto subview = mlir::dyn_cast<mlir::memref::SubViewOp>(viewOp)) {
|
if (auto subview = mlir::dyn_cast<mlir::memref::SubViewOp>(viewOp)) {
|
||||||
llvm::SmallVector<int64_t> nextStrides;
|
|
||||||
nextStrides.reserve(subview.getMixedOffsets().size());
|
|
||||||
for (auto [offset, stride, sourceStride] :
|
for (auto [offset, stride, sourceStride] :
|
||||||
llvm::zip_equal(subview.getMixedOffsets(), subview.getStaticStrides(), view.strides)) {
|
llvm::zip_equal(subview.getMixedOffsets(), subview.getStaticStrides(), view.strides)) {
|
||||||
CompiledIndexExpr offsetValue = makeConstantExpr(0);
|
CompiledIndexExpr offsetValue = makeConstantExpr(0);
|
||||||
@@ -227,29 +215,47 @@ resolveWeightView(mlir::Operation* weightOwner, mlir::Value weight, const Static
|
|||||||
return mlir::failure();
|
return mlir::failure();
|
||||||
}
|
}
|
||||||
offsetExpr = addExpr(std::move(offsetExpr), mulExpr(std::move(offsetValue), sourceStride));
|
offsetExpr = addExpr(std::move(offsetExpr), mulExpr(std::move(offsetValue), sourceStride));
|
||||||
nextStrides.push_back(stride * sourceStride);
|
|
||||||
}
|
}
|
||||||
view.shape.assign(subview.getStaticSizes().begin(), subview.getStaticSizes().end());
|
auto resultType = mlir::cast<mlir::MemRefType>(subview.getResult().getType());
|
||||||
view.strides = std::move(nextStrides);
|
auto resultStrides = getStaticMemRefTypeStrides(resultType);
|
||||||
|
if (failed(resultStrides))
|
||||||
|
return mlir::failure();
|
||||||
|
view.shape.assign(resultType.getShape().begin(), resultType.getShape().end());
|
||||||
|
view.strides = std::move(*resultStrides);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto collapse = mlir::dyn_cast<mlir::memref::CollapseShapeOp>(viewOp)) {
|
if (auto collapse = mlir::dyn_cast<mlir::memref::CollapseShapeOp>(viewOp)) {
|
||||||
if (view.strides != computeRowMajorStrides(view.shape))
|
|
||||||
return mlir::failure();
|
|
||||||
auto resultType = mlir::cast<mlir::MemRefType>(collapse.getResult().getType());
|
auto resultType = mlir::cast<mlir::MemRefType>(collapse.getResult().getType());
|
||||||
|
auto resultStrides = getStaticMemRefTypeStrides(resultType);
|
||||||
|
if (failed(resultStrides))
|
||||||
|
return mlir::failure();
|
||||||
view.shape.assign(resultType.getShape().begin(), resultType.getShape().end());
|
view.shape.assign(resultType.getShape().begin(), resultType.getShape().end());
|
||||||
view.strides = computeRowMajorStrides(view.shape);
|
view.strides = std::move(*resultStrides);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto expand = mlir::dyn_cast<mlir::memref::ExpandShapeOp>(viewOp)) {
|
if (auto expand = mlir::dyn_cast<mlir::memref::ExpandShapeOp>(viewOp)) {
|
||||||
if (view.strides != computeRowMajorStrides(view.shape))
|
|
||||||
return mlir::failure();
|
|
||||||
auto resultType = mlir::cast<mlir::MemRefType>(expand.getResult().getType());
|
auto resultType = mlir::cast<mlir::MemRefType>(expand.getResult().getType());
|
||||||
|
auto resultStrides = getStaticMemRefTypeStrides(resultType);
|
||||||
|
if (failed(resultStrides))
|
||||||
|
return mlir::failure();
|
||||||
view.shape.assign(resultType.getShape().begin(), resultType.getShape().end());
|
view.shape.assign(resultType.getShape().begin(), resultType.getShape().end());
|
||||||
view.strides = computeRowMajorStrides(view.shape);
|
view.strides = std::move(*resultStrides);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auto castOp = mlir::dyn_cast<mlir::memref::CastOp>(viewOp)) {
|
||||||
|
auto resultType = mlir::cast<mlir::MemRefType>(castOp.getResult().getType());
|
||||||
|
auto resultStrides = getStaticMemRefTypeStrides(resultType);
|
||||||
|
if (failed(resultStrides))
|
||||||
|
return mlir::failure();
|
||||||
|
view.shape.assign(resultType.getShape().begin(), resultType.getShape().end());
|
||||||
|
view.strides = std::move(*resultStrides);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mlir::failure();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto resolvedOffset = offsetExpr.evaluate(knowledge);
|
auto resolvedOffset = offsetExpr.evaluate(knowledge);
|
||||||
@@ -259,18 +265,26 @@ resolveWeightView(mlir::Operation* weightOwner, mlir::Value weight, const Static
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mlir::isa<mlir::memref::SubViewOp, mlir::memref::CollapseShapeOp, mlir::memref::ExpandShapeOp>(defOp)) {
|
if (auto subview = mlir::dyn_cast<mlir::memref::SubViewOp>(defOp)) {
|
||||||
viewOps.push_back(defOp);
|
viewOps.push_back(defOp);
|
||||||
if (auto subview = mlir::dyn_cast<mlir::memref::SubViewOp>(defOp))
|
current = subview.getSource();
|
||||||
current = subview.getSource();
|
continue;
|
||||||
else if (auto collapse = mlir::dyn_cast<mlir::memref::CollapseShapeOp>(defOp))
|
}
|
||||||
current = collapse.getSrc();
|
|
||||||
else
|
if (auto collapse = mlir::dyn_cast<mlir::memref::CollapseShapeOp>(defOp)) {
|
||||||
current = mlir::cast<mlir::memref::ExpandShapeOp>(defOp).getSrc();
|
viewOps.push_back(defOp);
|
||||||
|
current = collapse.getSrc();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto expand = mlir::dyn_cast<mlir::memref::ExpandShapeOp>(defOp)) {
|
||||||
|
viewOps.push_back(defOp);
|
||||||
|
current = expand.getSrc();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto castOp = mlir::dyn_cast<mlir::memref::CastOp>(defOp)) {
|
if (auto castOp = mlir::dyn_cast<mlir::memref::CastOp>(defOp)) {
|
||||||
|
viewOps.push_back(defOp);
|
||||||
current = castOp.getSource();
|
current = castOp.getSource();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -278,6 +292,11 @@ resolveWeightView(mlir::Operation* weightOwner, mlir::Value weight, const Static
|
|||||||
return mlir::failure();
|
return mlir::failure();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mlir::Value loopAlias = resolveLoopCarriedAlias(current, knowledge); loopAlias && loopAlias != current) {
|
||||||
|
current = loopAlias;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
auto weightIndex = resolveWeightIndex(weightOwner, current);
|
auto weightIndex = resolveWeightIndex(weightOwner, current);
|
||||||
if (!weightIndex)
|
if (!weightIndex)
|
||||||
return mlir::failure();
|
return mlir::failure();
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ bool hasOnlySpatialMvmVmmWeightUses(mlir::Value value);
|
|||||||
void walkPimMvmVmmWeightUses(mlir::Operation* root, llvm::function_ref<void(mlir::OpOperand&)> callback);
|
void walkPimMvmVmmWeightUses(mlir::Operation* root, llvm::function_ref<void(mlir::OpOperand&)> callback);
|
||||||
|
|
||||||
std::optional<unsigned> resolveWeightIndex(mlir::Operation* weightOwner, mlir::Value weight);
|
std::optional<unsigned> resolveWeightIndex(mlir::Operation* weightOwner, mlir::Value weight);
|
||||||
std::optional<unsigned> resolveWeightIndex(mlir::Operation* weightOwner, pim::PimVMMOp vmmOp);
|
|
||||||
llvm::FailureOr<ResolvedWeightView>
|
llvm::FailureOr<ResolvedWeightView>
|
||||||
resolveWeightView(mlir::Operation* weightOwner, mlir::Value weight, const StaticValueKnowledge& knowledge = {});
|
resolveWeightView(mlir::Operation* weightOwner, mlir::Value weight, const StaticValueKnowledge& knowledge = {});
|
||||||
|
|
||||||
|
|||||||
@@ -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/ConstantUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/IR/CoreBlockUtils.hpp"
|
#include "src/Accelerators/PIM/Common/IR/CoreBlockUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/IR/EntryPointUtils.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/ShapeUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/IR/WeightUtils.hpp"
|
#include "src/Accelerators/PIM/Common/IR/WeightUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/Support/DebugDump.hpp"
|
#include "src/Accelerators/PIM/Common/Support/DebugDump.hpp"
|
||||||
|
|||||||
@@ -0,0 +1,222 @@
|
|||||||
|
#include "llvm/Support/ErrorHandling.h"
|
||||||
|
#include "llvm/Support/raw_ostream.h"
|
||||||
|
|
||||||
|
#include "CheckedArithmetic.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||||
|
|
||||||
|
using namespace mlir;
|
||||||
|
|
||||||
|
namespace onnx_mlir::pim {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
static void emitCrashMessage(llvm::StringRef fieldName, llvm::StringRef message) {
|
||||||
|
llvm::errs() << "PIM " << fieldName << " " << message << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename To, typename From>
|
||||||
|
static FailureOr<To> checkedCastAtLocation(From value, Location loc, llvm::StringRef fieldName) {
|
||||||
|
static_assert(std::is_integral_v<To> && std::is_integral_v<From>, "checkedCastAtLocation requires integral types");
|
||||||
|
|
||||||
|
using ToLimits = std::numeric_limits<To>;
|
||||||
|
|
||||||
|
if constexpr (std::is_signed_v<From> == std::is_signed_v<To>) {
|
||||||
|
if (value < static_cast<From>(ToLimits::min()) || value > static_cast<From>(ToLimits::max())) {
|
||||||
|
emitCheckedArithmeticError(loc, fieldName, "is outside representable range");
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_signed_v<From>) {
|
||||||
|
using UnsignedFrom = std::make_unsigned_t<From>;
|
||||||
|
using UnsignedTo = std::make_unsigned_t<To>;
|
||||||
|
if (value < 0 || static_cast<UnsignedFrom>(value) > static_cast<UnsignedTo>(ToLimits::max())) {
|
||||||
|
emitCheckedArithmeticError(loc, fieldName, "is outside representable range");
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
using UnsignedFrom = std::make_unsigned_t<From>;
|
||||||
|
using UnsignedTo = std::conditional_t<std::is_signed_v<To>, std::make_unsigned_t<To>, To>;
|
||||||
|
if (static_cast<UnsignedFrom>(value) > static_cast<UnsignedTo>(ToLimits::max())) {
|
||||||
|
emitCheckedArithmeticError(loc, fieldName, "is outside representable range");
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<To>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename UInt>
|
||||||
|
FailureOr<UInt> checkedMulAtLocation(UInt lhs, UInt rhs, Location loc, llvm::StringRef fieldName) {
|
||||||
|
static_assert(std::is_integral_v<UInt> && std::is_unsigned_v<UInt>,
|
||||||
|
"checkedMulAtLocation requires unsigned integral types");
|
||||||
|
if (lhs != 0 && rhs > std::numeric_limits<UInt>::max() / lhs) {
|
||||||
|
emitCheckedArithmeticError(loc, fieldName, "multiplication overflow");
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
return lhs * rhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
InFlightDiagnostic emitCheckedArithmeticError(Operation* anchor, llvm::StringRef fieldName, llvm::StringRef message) {
|
||||||
|
assert(anchor && "expected arithmetic diagnostics to have an anchor op");
|
||||||
|
return anchor->emitOpError() << fieldName << " " << message;
|
||||||
|
}
|
||||||
|
|
||||||
|
InFlightDiagnostic emitCheckedArithmeticError(Location loc, llvm::StringRef fieldName, llvm::StringRef message) {
|
||||||
|
return emitError(loc) << "PIM " << fieldName << " " << message;
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<int32_t> checkedI32(int64_t value, Operation* anchor, llvm::StringRef fieldName) {
|
||||||
|
return checkedCast<int32_t>(value, anchor, fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<int32_t> checkedI32(uint64_t value, Operation* anchor, llvm::StringRef fieldName) {
|
||||||
|
return checkedCast<int32_t>(value, anchor, fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<uint8_t> checkedU8(uint64_t value, Operation* anchor, llvm::StringRef fieldName) {
|
||||||
|
return checkedCast<uint8_t>(value, anchor, fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<size_t> checkedSize(int64_t value, Operation* anchor, llvm::StringRef fieldName) {
|
||||||
|
return checkedCast<size_t>(value, anchor, fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<IntegerAttr>
|
||||||
|
getCheckedI32Attr(Builder& builder, Operation* anchor, int64_t value, llvm::StringRef fieldName) {
|
||||||
|
assert(anchor && "checked op-based attrs require a non-null diagnostic anchor");
|
||||||
|
auto checkedValue = checkedI32(value, anchor, fieldName);
|
||||||
|
if (failed(checkedValue))
|
||||||
|
return failure();
|
||||||
|
return builder.getI32IntegerAttr(*checkedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<IntegerAttr>
|
||||||
|
getCheckedI32Attr(Builder& builder, Operation* anchor, uint64_t value, llvm::StringRef fieldName) {
|
||||||
|
assert(anchor && "checked op-based attrs require a non-null diagnostic anchor");
|
||||||
|
auto checkedValue = checkedI32(value, anchor, fieldName);
|
||||||
|
if (failed(checkedValue))
|
||||||
|
return failure();
|
||||||
|
return builder.getI32IntegerAttr(*checkedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<IntegerAttr> getCheckedI32Attr(Builder& builder, Location loc, int64_t value, llvm::StringRef fieldName) {
|
||||||
|
auto checkedValue = checkedCastAtLocation<int32_t>(value, loc, fieldName);
|
||||||
|
if (failed(checkedValue))
|
||||||
|
return failure();
|
||||||
|
return builder.getI32IntegerAttr(*checkedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<IntegerAttr> getCheckedI32Attr(Builder& builder, Location loc, uint64_t value, llvm::StringRef fieldName) {
|
||||||
|
auto checkedValue = checkedCastAtLocation<int32_t>(value, loc, fieldName);
|
||||||
|
if (failed(checkedValue))
|
||||||
|
return failure();
|
||||||
|
return builder.getI32IntegerAttr(*checkedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<uint64_t> getCheckedShapedTypeSizeInBytes(ShapedType type, Operation* anchor, llvm::StringRef fieldName) {
|
||||||
|
assert(anchor && "checked op-based size helpers require a non-null diagnostic anchor");
|
||||||
|
if (!type.hasStaticShape()) {
|
||||||
|
emitCheckedArithmeticError(anchor, fieldName, "requires static shaped type");
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
if (!hasByteSizedElementType(type.getElementType())) {
|
||||||
|
emitCheckedArithmeticError(anchor, fieldName, "requires byte-sized element type");
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t elements = 1;
|
||||||
|
for (int64_t dim : type.getShape()) {
|
||||||
|
if (dim < 0) {
|
||||||
|
emitCheckedArithmeticError(anchor, fieldName, "requires nonnegative dimensions");
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto nextElements = checkedMul(elements, static_cast<uint64_t>(dim), anchor, fieldName);
|
||||||
|
if (failed(nextElements))
|
||||||
|
return failure();
|
||||||
|
elements = *nextElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkedMul(
|
||||||
|
elements, static_cast<uint64_t>(getElementTypeSizeInBytes(type.getElementType())), anchor, fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<uint64_t> getCheckedShapedTypeSizeInBytes(ShapedType type, Location loc, llvm::StringRef fieldName) {
|
||||||
|
if (!type.hasStaticShape()) {
|
||||||
|
emitCheckedArithmeticError(loc, fieldName, "requires static shaped type");
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
if (!hasByteSizedElementType(type.getElementType())) {
|
||||||
|
emitCheckedArithmeticError(loc, fieldName, "requires byte-sized element type");
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t elements = 1;
|
||||||
|
for (int64_t dim : type.getShape()) {
|
||||||
|
if (dim < 0) {
|
||||||
|
emitCheckedArithmeticError(loc, fieldName, "requires nonnegative dimensions");
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto nextElements = checkedMulAtLocation(elements, static_cast<uint64_t>(dim), loc, fieldName);
|
||||||
|
if (failed(nextElements))
|
||||||
|
return failure();
|
||||||
|
elements = *nextElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkedMulAtLocation(
|
||||||
|
elements, static_cast<uint64_t>(getElementTypeSizeInBytes(type.getElementType())), loc, fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t checkedI32OrCrash(int64_t value, llvm::StringRef fieldName) {
|
||||||
|
if (value < std::numeric_limits<int32_t>::min() || value > std::numeric_limits<int32_t>::max()) {
|
||||||
|
emitCrashMessage(fieldName, "is outside representable range");
|
||||||
|
llvm_unreachable("PIM checked arithmetic failure");
|
||||||
|
}
|
||||||
|
return static_cast<int32_t>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t checkedI32OrCrash(uint64_t value, llvm::StringRef fieldName) {
|
||||||
|
if (value > static_cast<uint64_t>(std::numeric_limits<int32_t>::max())) {
|
||||||
|
emitCrashMessage(fieldName, "is outside representable range");
|
||||||
|
llvm_unreachable("PIM checked arithmetic failure");
|
||||||
|
}
|
||||||
|
return static_cast<int32_t>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t checkedU8OrCrash(uint64_t value, llvm::StringRef fieldName) {
|
||||||
|
if (value > static_cast<uint64_t>(std::numeric_limits<uint8_t>::max())) {
|
||||||
|
emitCrashMessage(fieldName, "is outside representable range");
|
||||||
|
llvm_unreachable("PIM checked arithmetic failure");
|
||||||
|
}
|
||||||
|
return static_cast<uint8_t>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t checkedSizeOrCrash(int64_t value, llvm::StringRef fieldName) {
|
||||||
|
if (value < 0) {
|
||||||
|
emitCrashMessage(fieldName, "is outside representable range");
|
||||||
|
llvm_unreachable("PIM checked arithmetic failure");
|
||||||
|
}
|
||||||
|
return static_cast<size_t>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t checkedAddOrCrash(size_t lhs, size_t rhs, llvm::StringRef fieldName) {
|
||||||
|
if (rhs > std::numeric_limits<size_t>::max() - lhs) {
|
||||||
|
emitCrashMessage(fieldName, "addition overflow");
|
||||||
|
llvm_unreachable("PIM checked arithmetic failure");
|
||||||
|
}
|
||||||
|
return lhs + rhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t checkedMulOrCrash(size_t lhs, size_t rhs, llvm::StringRef fieldName) {
|
||||||
|
if (lhs != 0 && rhs > std::numeric_limits<size_t>::max() / lhs) {
|
||||||
|
emitCrashMessage(fieldName, "multiplication overflow");
|
||||||
|
llvm_unreachable("PIM checked arithmetic failure");
|
||||||
|
}
|
||||||
|
return lhs * rhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace onnx_mlir::pim
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "mlir/IR/Builders.h"
|
||||||
|
#include "mlir/IR/BuiltinTypeInterfaces.h"
|
||||||
|
#include "mlir/IR/Operation.h"
|
||||||
|
#include "mlir/Support/LogicalResult.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/StringRef.h"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace onnx_mlir::pim {
|
||||||
|
|
||||||
|
mlir::InFlightDiagnostic
|
||||||
|
emitCheckedArithmeticError(mlir::Operation* anchor, llvm::StringRef fieldName, llvm::StringRef message);
|
||||||
|
|
||||||
|
mlir::InFlightDiagnostic
|
||||||
|
emitCheckedArithmeticError(mlir::Location loc, llvm::StringRef fieldName, llvm::StringRef message);
|
||||||
|
|
||||||
|
template <typename To, typename From>
|
||||||
|
mlir::FailureOr<To> checkedCast(From value, mlir::Operation* anchor, llvm::StringRef fieldName) {
|
||||||
|
static_assert(std::is_integral_v<To> && std::is_integral_v<From>, "checkedCast requires integral types");
|
||||||
|
|
||||||
|
using ToLimits = std::numeric_limits<To>;
|
||||||
|
|
||||||
|
if constexpr (std::is_signed_v<From> == std::is_signed_v<To>) {
|
||||||
|
if (value < static_cast<From>(ToLimits::min()) || value > static_cast<From>(ToLimits::max())) {
|
||||||
|
emitCheckedArithmeticError(anchor, fieldName, "is outside representable range");
|
||||||
|
return mlir::failure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_signed_v<From>) {
|
||||||
|
using UnsignedFrom = std::make_unsigned_t<From>;
|
||||||
|
using UnsignedTo = std::make_unsigned_t<To>;
|
||||||
|
if (value < 0 || static_cast<UnsignedFrom>(value) > static_cast<UnsignedTo>(ToLimits::max())) {
|
||||||
|
emitCheckedArithmeticError(anchor, fieldName, "is outside representable range");
|
||||||
|
return mlir::failure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
using UnsignedFrom = std::make_unsigned_t<From>;
|
||||||
|
using UnsignedTo = std::conditional_t<std::is_signed_v<To>, std::make_unsigned_t<To>, To>;
|
||||||
|
if (static_cast<UnsignedFrom>(value) > static_cast<UnsignedTo>(ToLimits::max())) {
|
||||||
|
emitCheckedArithmeticError(anchor, fieldName, "is outside representable range");
|
||||||
|
return mlir::failure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<To>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename UInt>
|
||||||
|
mlir::FailureOr<UInt> checkedAdd(UInt lhs, UInt rhs, mlir::Operation* anchor, llvm::StringRef fieldName) {
|
||||||
|
static_assert(std::is_integral_v<UInt> && std::is_unsigned_v<UInt>, "checkedAdd requires unsigned integral types");
|
||||||
|
if (rhs > std::numeric_limits<UInt>::max() - lhs) {
|
||||||
|
emitCheckedArithmeticError(anchor, fieldName, "addition overflow");
|
||||||
|
return mlir::failure();
|
||||||
|
}
|
||||||
|
return lhs + rhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename UInt>
|
||||||
|
mlir::FailureOr<UInt> checkedMul(UInt lhs, UInt rhs, mlir::Operation* anchor, llvm::StringRef fieldName) {
|
||||||
|
static_assert(std::is_integral_v<UInt> && std::is_unsigned_v<UInt>, "checkedMul requires unsigned integral types");
|
||||||
|
if (lhs != 0 && rhs > std::numeric_limits<UInt>::max() / lhs) {
|
||||||
|
emitCheckedArithmeticError(anchor, fieldName, "multiplication overflow");
|
||||||
|
return mlir::failure();
|
||||||
|
}
|
||||||
|
return lhs * rhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
mlir::FailureOr<int32_t> checkedI32(int64_t value, mlir::Operation* anchor, llvm::StringRef fieldName);
|
||||||
|
mlir::FailureOr<int32_t> checkedI32(uint64_t value, mlir::Operation* anchor, llvm::StringRef fieldName);
|
||||||
|
|
||||||
|
mlir::FailureOr<uint8_t> checkedU8(uint64_t value, mlir::Operation* anchor, llvm::StringRef fieldName);
|
||||||
|
|
||||||
|
mlir::FailureOr<size_t> checkedSize(int64_t value, mlir::Operation* anchor, llvm::StringRef fieldName);
|
||||||
|
|
||||||
|
mlir::FailureOr<mlir::IntegerAttr>
|
||||||
|
getCheckedI32Attr(mlir::Builder& builder, mlir::Operation* anchor, int64_t value, llvm::StringRef fieldName);
|
||||||
|
|
||||||
|
mlir::FailureOr<mlir::IntegerAttr>
|
||||||
|
getCheckedI32Attr(mlir::Builder& builder, mlir::Operation* anchor, uint64_t value, llvm::StringRef fieldName);
|
||||||
|
|
||||||
|
mlir::FailureOr<mlir::IntegerAttr>
|
||||||
|
getCheckedI32Attr(mlir::Builder& builder, mlir::Location loc, int64_t value, llvm::StringRef fieldName);
|
||||||
|
|
||||||
|
mlir::FailureOr<mlir::IntegerAttr>
|
||||||
|
getCheckedI32Attr(mlir::Builder& builder, mlir::Location loc, uint64_t value, llvm::StringRef fieldName);
|
||||||
|
|
||||||
|
mlir::FailureOr<uint64_t>
|
||||||
|
getCheckedShapedTypeSizeInBytes(mlir::ShapedType type, mlir::Operation* anchor, llvm::StringRef fieldName);
|
||||||
|
|
||||||
|
mlir::FailureOr<uint64_t>
|
||||||
|
getCheckedShapedTypeSizeInBytes(mlir::ShapedType type, mlir::Location loc, llvm::StringRef fieldName);
|
||||||
|
|
||||||
|
int32_t checkedI32OrCrash(int64_t value, llvm::StringRef fieldName);
|
||||||
|
int32_t checkedI32OrCrash(uint64_t value, llvm::StringRef fieldName);
|
||||||
|
uint8_t checkedU8OrCrash(uint64_t value, llvm::StringRef fieldName);
|
||||||
|
size_t checkedSizeOrCrash(int64_t value, llvm::StringRef fieldName);
|
||||||
|
size_t checkedAddOrCrash(size_t lhs, size_t rhs, llvm::StringRef fieldName);
|
||||||
|
size_t checkedMulOrCrash(size_t lhs, size_t rhs, llvm::StringRef fieldName);
|
||||||
|
|
||||||
|
} // namespace onnx_mlir::pim
|
||||||
@@ -7,18 +7,26 @@
|
|||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
|
|
||||||
void dumpModule(mlir::ModuleOp moduleOp, const std::string& name) {
|
std::fstream openDialectDumpFileWithExtension(const std::string& name, llvm::StringRef destination, llvm::StringRef extension) {
|
||||||
std::string outputDir = getOutputDir();
|
std::string outputDir = getOutputDir();
|
||||||
if (outputDir.empty())
|
if (outputDir.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::string dialectsDir = (outputDir + destination).str();
|
||||||
|
createDirectory(dialectsDir);
|
||||||
|
return std::fstream(dialectsDir + "/" + name + "." + extension.str(), std::ios::out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dumpModule(mlir::ModuleOp moduleOp, const std::string& name, bool assumeVerified) {
|
||||||
|
std::fstream file = openDialectDumpFileWithExtension(name, "/dialects", "mlir");
|
||||||
|
if (!file.is_open())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::string dialectsDir = outputDir + "/dialects";
|
|
||||||
createDirectory(dialectsDir);
|
|
||||||
|
|
||||||
std::fstream file(dialectsDir + "/" + name + ".mlir", std::ios::out);
|
|
||||||
llvm::raw_os_ostream os(file);
|
llvm::raw_os_ostream os(file);
|
||||||
mlir::OpPrintingFlags flags;
|
mlir::OpPrintingFlags flags;
|
||||||
flags.elideLargeElementsAttrs();
|
flags.elideLargeElementsAttrs().enableDebugInfo(false, false);
|
||||||
|
if (assumeVerified)
|
||||||
|
flags.assumeVerified();
|
||||||
moduleOp.print(os, flags);
|
moduleOp.print(os, flags);
|
||||||
os.flush();
|
os.flush();
|
||||||
file.close();
|
file.close();
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "mlir/IR/BuiltinOps.h"
|
#include "mlir/IR/BuiltinOps.h"
|
||||||
|
#include "llvm/ADT/StringRef.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
|
|
||||||
/// Emits a MLIR snapshot under the current compiler output
|
/// Emits a MLIR snapshot under the current compiler output
|
||||||
/// directory for pass-level debugging.
|
/// directory for pass-level debugging.
|
||||||
void dumpModule(mlir::ModuleOp moduleOp, const std::string& name);
|
void dumpModule(mlir::ModuleOp moduleOp, const std::string& name, bool assumeVerified = false);
|
||||||
|
|
||||||
|
/// Opens a file under the same dialect dump directory used by dumpModule.
|
||||||
|
std::fstream openDialectDumpFileWithExtension(const std::string& name,llvm::StringRef destination = "/dialects", llvm::StringRef extension = "mlir");
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ struct CappedDiagnosticReporter {
|
|||||||
op->emitError() << "suppressed " << (numFailures - maxReportedFailures) << " additional " << failureDescription;
|
op->emitError() << "suppressed " << (numFailures - maxReportedFailures) << " additional " << failureDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void noteFailures(int64_t count) { numFailures += count; }
|
||||||
|
|
||||||
bool hasFailure() const { return numFailures != 0; }
|
bool hasFailure() const { return numFailures != 0; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -5,14 +5,30 @@
|
|||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
|
|
||||||
std::fstream openReportFile(const std::string& name) {
|
std::fstream openReportFileWithExtension(const std::string& name, llvm::StringRef extension) {
|
||||||
std::string outputDir = getOutputDir();
|
std::string outputDir = getOutputDir();
|
||||||
if (outputDir.empty())
|
if (outputDir.empty())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
std::string reportsDir = outputDir + "/reports";
|
std::string reportsDir = outputDir + "/reports";
|
||||||
createDirectory(reportsDir);
|
createDirectory(reportsDir);
|
||||||
return std::fstream(reportsDir + "/" + name + ".txt", std::ios::out);
|
return std::fstream(reportsDir + "/" + name + "." + extension.str(), std::ios::out);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::fstream openReportFile(const std::string& name) { return openReportFileWithExtension(name, "txt"); }
|
||||||
|
|
||||||
|
std::fstream openAppendedReportFileWithExtension(const std::string& name, llvm::StringRef extension) {
|
||||||
|
std::string outputDir = getOutputDir();
|
||||||
|
if (outputDir.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::string reportsDir = outputDir + "/reports";
|
||||||
|
createDirectory(reportsDir);
|
||||||
|
return std::fstream(reportsDir + "/" + name + "." + extension.str(), std::ios::out | std::ios::app);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::fstream openAppendedReportFile(const std::string& name) {
|
||||||
|
return openAppendedReportFileWithExtension(name, "txt");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string formatReportMemory(uint64_t bytes) {
|
std::string formatReportMemory(uint64_t bytes) {
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
|
|
||||||
std::fstream openReportFile(const std::string& name);
|
std::fstream openReportFile(const std::string& name);
|
||||||
|
std::fstream openReportFileWithExtension(const std::string& name, llvm::StringRef extension);
|
||||||
|
std::fstream openAppendedReportFile(const std::string& name);
|
||||||
|
std::fstream openAppendedReportFileWithExtension(const std::string& name, llvm::StringRef extension);
|
||||||
std::string formatReportMemory(uint64_t bytes);
|
std::string formatReportMemory(uint64_t bytes);
|
||||||
|
|
||||||
struct ReportField {
|
struct ReportField {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ add_pim_library(OMPimCompilerUtils
|
|||||||
PimCompilerUtils.cpp
|
PimCompilerUtils.cpp
|
||||||
PimArtifactWriter.cpp
|
PimArtifactWriter.cpp
|
||||||
PimCodeGen.cpp
|
PimCodeGen.cpp
|
||||||
|
PimMemoryLiveness.cpp
|
||||||
PimWeightEmitter.cpp
|
PimWeightEmitter.cpp
|
||||||
|
|
||||||
EXCLUDE_FROM_OM_LIBS
|
EXCLUDE_FROM_OM_LIBS
|
||||||
@@ -28,7 +29,9 @@ add_pim_library(OMPimCompilerUtils
|
|||||||
OMPimCompilerOptions
|
OMPimCompilerOptions
|
||||||
OMPimCommon
|
OMPimCommon
|
||||||
OMPimBufferization
|
OMPimBufferization
|
||||||
OMPimStaticMemoryCoalescing
|
OMPimMemoryCoalescing
|
||||||
|
OMPimHostConstantFolding
|
||||||
|
OMPimVerification
|
||||||
OMPimPasses
|
OMPimPasses
|
||||||
OMONNXToSpatial
|
OMONNXToSpatial
|
||||||
OMSpatialToPim
|
OMSpatialToPim
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
#include "llvm/Support/raw_ostream.h"
|
#include "llvm/Support/raw_ostream.h"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cassert>
|
|
||||||
#include <limits>
|
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||||
|
|
||||||
namespace onnx_mlir::pim_binary {
|
namespace onnx_mlir::pim_binary {
|
||||||
|
|
||||||
@@ -95,15 +95,10 @@ inline void writeInstructionRecord(llvm::raw_ostream& os, const InstructionRecor
|
|||||||
writeInt32LE(os, record.generic3);
|
writeInt32LE(os, record.generic3);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int32_t toI32(int64_t value) {
|
inline int32_t toI32(int64_t value) { return onnx_mlir::pim::checkedI32OrCrash(value, "binary field"); }
|
||||||
assert(value >= std::numeric_limits<int32_t>::min() && value <= std::numeric_limits<int32_t>::max()
|
|
||||||
&& "PIM binary field out of int32 range");
|
|
||||||
return static_cast<int32_t>(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline uint8_t toU8(int64_t value) {
|
inline uint8_t toU8(int64_t value) {
|
||||||
assert(value >= 0 && value <= std::numeric_limits<uint8_t>::max() && "PIM binary field out of uint8 range");
|
return onnx_mlir::pim::checkedU8OrCrash(static_cast<uint64_t>(value), "binary field");
|
||||||
return static_cast<uint8_t>(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int32_t getOptionalInt(const llvm::json::Object& object, llvm::StringRef key, int32_t defaultValue = 0) {
|
inline int32_t getOptionalInt(const llvm::json::Object& object, llvm::StringRef key, int32_t defaultValue = 0) {
|
||||||
|
|||||||
+452
-114
@@ -2,7 +2,6 @@
|
|||||||
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
||||||
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
||||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||||
#include "mlir/IR/AsmState.h"
|
|
||||||
#include "mlir/IR/Attributes.h"
|
#include "mlir/IR/Attributes.h"
|
||||||
#include "mlir/IR/BuiltinAttributes.h"
|
#include "mlir/IR/BuiltinAttributes.h"
|
||||||
#include "mlir/IR/BuiltinTypes.h"
|
#include "mlir/IR/BuiltinTypes.h"
|
||||||
@@ -25,20 +24,26 @@
|
|||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <limits>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <numeric>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "Common/IR/CompactAsmUtils.hpp"
|
#include "Common/IR/CompactAsmUtils.hpp"
|
||||||
#include "Common/PimCommon.hpp"
|
#include "Common/PimCommon.hpp"
|
||||||
|
#include "Common/Support/Diagnostics.hpp"
|
||||||
|
#include "Common/Support/CheckedArithmetic.hpp"
|
||||||
#include "Common/Support/ReportUtils.hpp"
|
#include "Common/Support/ReportUtils.hpp"
|
||||||
#include "Conversion/ONNXToSpatial/Common/Common.hpp"
|
#include "Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.hpp"
|
#include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/IR/WeightUtils.hpp"
|
#include "src/Accelerators/PIM/Common/IR/WeightUtils.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/Support/FileSystemUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Compiler/PimArtifactWriter.hpp"
|
#include "src/Accelerators/PIM/Compiler/PimArtifactWriter.hpp"
|
||||||
#include "src/Accelerators/PIM/Compiler/PimBinaryFormat.hpp"
|
#include "src/Accelerators/PIM/Compiler/PimBinaryFormat.hpp"
|
||||||
#include "src/Accelerators/PIM/Compiler/PimCodeGen.hpp"
|
#include "src/Accelerators/PIM/Compiler/PimCodeGen.hpp"
|
||||||
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Compiler/PimMemoryLiveness.hpp"
|
||||||
#include "src/Accelerators/PIM/Compiler/PimWeightEmitter.hpp"
|
#include "src/Accelerators/PIM/Compiler/PimWeightEmitter.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
||||||
|
|
||||||
@@ -71,32 +76,159 @@ static MemoryValueKey getMemoryValueKey(mlir::Value value, std::optional<unsigne
|
|||||||
return {value, getLaneForMemoryValue(value, lane)};
|
return {value, getLaneForMemoryValue(value, lane)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool isInsidePimCoreLikeOp(memref::AllocOp allocOp) {
|
||||||
|
return allocOp->getParentOfType<pim::PimCoreOp>() || allocOp->getParentOfType<pim::PimCoreBatchOp>();
|
||||||
|
}
|
||||||
|
|
||||||
|
static MemoryReportKind classifyMemoryReportKind(mlir::Value value) {
|
||||||
|
if (isa<mlir::BlockArgument>(value))
|
||||||
|
return MemoryReportKind::Input;
|
||||||
|
if (auto* op = value.getDefiningOp()) {
|
||||||
|
if (isa<memref::AllocOp>(op))
|
||||||
|
return MemoryReportKind::Alloca;
|
||||||
|
if (isa<memref::GetGlobalOp>(op))
|
||||||
|
return MemoryReportKind::Global;
|
||||||
|
}
|
||||||
|
return MemoryReportKind::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t getVectorByteSizeOrCrash(ShapedType type) {
|
||||||
|
auto byteSize = pim::getCheckedShapedTypeSizeInBytes(type, UnknownLoc::get(type.getContext()), "vector byte size");
|
||||||
|
if (failed(byteSize))
|
||||||
|
llvm_unreachable("Failed to compute checked vector byte size");
|
||||||
|
return pim::checkedI32OrCrash(*byteSize, "vector byte size");
|
||||||
|
}
|
||||||
|
|
||||||
|
static Operation* getDiagnosticAnchor(mlir::Value value) {
|
||||||
|
if (Operation* definingOp = value.getDefiningOp())
|
||||||
|
return definingOp;
|
||||||
|
if (auto blockArg = dyn_cast<BlockArgument>(value))
|
||||||
|
return blockArg.getOwner()->getParentOp();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PIM instruction immediates are serialized as signed int32_t fields today
|
||||||
|
// (`sldi` goes through checkedI32OrCrash), so local addresses must stay within
|
||||||
|
// the non-negative int32_t range.
|
||||||
|
static constexpr size_t kPimAddressLimit = static_cast<size_t>(std::numeric_limits<int32_t>::max());
|
||||||
|
|
||||||
|
static FailureOr<size_t> checkedAlignTo(size_t value, size_t alignment, Operation* anchor, StringRef fieldName) {
|
||||||
|
if (alignment == 0)
|
||||||
|
return value;
|
||||||
|
size_t remainder = value % alignment;
|
||||||
|
if (remainder == 0)
|
||||||
|
return value;
|
||||||
|
return pim::checkedAdd(value, alignment - remainder, anchor, fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void printMemoryOverflowDiagnostic(mlir::Value value,
|
||||||
|
const MemoryValueKey& key,
|
||||||
|
size_t requestedSize,
|
||||||
|
size_t currentFirstAvailableAddress,
|
||||||
|
size_t alignedEndAddress) {
|
||||||
|
llvm::errs() << "PIM local memory allocation overflow\n";
|
||||||
|
llvm::errs() << "Requested allocation size: " << requestedSize << " bytes\n";
|
||||||
|
llvm::errs() << "Current firstAvailableAddress: " << currentFirstAvailableAddress << "\n";
|
||||||
|
llvm::errs() << "Aligned end address: " << alignedEndAddress << "\n";
|
||||||
|
llvm::errs() << "Address limit: " << kPimAddressLimit << " (signed int32_t immediate range)\n";
|
||||||
|
if (key.lane)
|
||||||
|
llvm::errs() << "Lane: " << *key.lane << "\n";
|
||||||
|
llvm::errs() << "Value: ";
|
||||||
|
value.print(llvm::errs());
|
||||||
|
llvm::errs() << "\n";
|
||||||
|
llvm::errs() << "Value type: " << value.getType() << "\n";
|
||||||
|
if (Operation* definingOp = value.getDefiningOp()) {
|
||||||
|
llvm::errs() << "Defining op:\n";
|
||||||
|
definingOp->print(llvm::errs());
|
||||||
|
llvm::errs() << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
MemEntry* PimMemory::gatherMemEntry(mlir::Value value, std::optional<unsigned> lane) {
|
MemEntry* PimMemory::gatherMemEntry(mlir::Value value, std::optional<unsigned> lane) {
|
||||||
auto type = cast<ShapedType>(value.getType());
|
auto type = cast<ShapedType>(value.getType());
|
||||||
assert("Only static shape is supported" && type.hasStaticShape());
|
assert("Only static shape is supported" && type.hasStaticShape());
|
||||||
size_t allocSize = getShapedTypeSizeInBytes(type);
|
auto checkedAllocSize =
|
||||||
MemEntry memEntry = {0, allocSize};
|
pim::getCheckedShapedTypeSizeInBytes(type, UnknownLoc::get(type.getContext()), "memory allocation byte size");
|
||||||
return &memEntries.emplace_back(memEntry, getMemoryValueKey(value, lane)).first;
|
if (failed(checkedAllocSize))
|
||||||
|
llvm_unreachable("Failed to compute checked allocation byte size");
|
||||||
|
PendingMemEntry pending;
|
||||||
|
pending.memEntry = {0, *checkedAllocSize};
|
||||||
|
pending.key = getMemoryValueKey(value, lane);
|
||||||
|
pending.reportKind = classifyMemoryReportKind(value);
|
||||||
|
return &memEntries.emplace_back(std::move(pending)).memEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PimMemory::allocateGatheredMemory() {
|
void PimMemory::allocateGatheredMemory() {
|
||||||
llvm::sort(memEntries, [](auto a, auto b) -> bool { return a.first.size > b.first.size; });
|
llvm::sort(memEntries, [](const PendingMemEntry& lhs, const PendingMemEntry& rhs) {
|
||||||
for (auto& [memEntry, key] : memEntries)
|
return lhs.memEntry.size > rhs.memEntry.size;
|
||||||
allocateMemoryForValue(key, memEntry);
|
});
|
||||||
|
for (PendingMemEntry& pending : memEntries)
|
||||||
|
allocateMemoryForValue(pending.key, pending.memEntry, pending.reportKind);
|
||||||
memEntries.clear();
|
memEntries.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PimMemory::allocateMemoryForValue(const MemoryValueKey& key, MemEntry& memEntry) {
|
void PimMemory::allocateMemoryForValue(const MemoryValueKey& key, MemEntry& memEntry, MemoryReportKind reportKind) {
|
||||||
memEntry.address = firstAvailableAddress;
|
memEntry.address = firstAvailableAddress;
|
||||||
firstAvailableAddress += memEntry.size;
|
Operation* anchor = getDiagnosticAnchor(key.value);
|
||||||
// Alignment
|
auto checkedEnd = pim::checkedAdd(memEntry.address, memEntry.size, anchor, "local memory end");
|
||||||
if (size_t remainder = firstAvailableAddress % minAlignment)
|
FailureOr<size_t> checkedAlignedEnd = failure();
|
||||||
firstAvailableAddress += minAlignment - remainder;
|
if (succeeded(checkedEnd))
|
||||||
|
checkedAlignedEnd = checkedAlignTo(*checkedEnd, minAlignment, anchor, "local memory alignment");
|
||||||
|
bool startFits = memEntry.address <= kPimAddressLimit;
|
||||||
|
bool endFits = succeeded(checkedEnd) && *checkedEnd <= kPimAddressLimit;
|
||||||
|
bool alignedEndFits = succeeded(checkedAlignedEnd) && *checkedAlignedEnd <= kPimAddressLimit;
|
||||||
|
if (!startFits || !endFits || !alignedEndFits) {
|
||||||
|
printMemoryOverflowDiagnostic(key.value,
|
||||||
|
key,
|
||||||
|
memEntry.size,
|
||||||
|
firstAvailableAddress,
|
||||||
|
succeeded(checkedAlignedEnd) ? *checkedAlignedEnd : kPimAddressLimit);
|
||||||
|
llvm_unreachable("PIM local memory allocation overflow");
|
||||||
|
}
|
||||||
|
firstAvailableAddress = *checkedAlignedEnd;
|
||||||
|
|
||||||
ownedMemEntriesMap[key] = memEntry;
|
ownedMemEntriesMap[key] = memEntry;
|
||||||
globalMemEntriesMap[key] = memEntry;
|
globalMemEntriesMap[key] = memEntry;
|
||||||
|
|
||||||
|
switch (reportKind) {
|
||||||
|
case MemoryReportKind::Alloca: break;
|
||||||
|
case MemoryReportKind::Global:
|
||||||
|
++reportRow.numGlobal;
|
||||||
|
reportRow.sizeGlobal += memEntry.size;
|
||||||
|
break;
|
||||||
|
case MemoryReportKind::Input:
|
||||||
|
case MemoryReportKind::None: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysicalSlotInfo PimMemory::allocatePhysicalSlot(size_t slotSize, const MemoryValueKey& key) {
|
||||||
|
PhysicalSlotInfo slot;
|
||||||
|
slot.id = nextPhysicalSlotId++;
|
||||||
|
slot.address = firstAvailableAddress;
|
||||||
|
slot.size = slotSize;
|
||||||
|
|
||||||
|
Operation* anchor = getDiagnosticAnchor(key.value);
|
||||||
|
auto checkedEnd = pim::checkedAdd(slot.address, slot.size, anchor, "local memory end");
|
||||||
|
FailureOr<size_t> checkedAlignedEnd = failure();
|
||||||
|
if (succeeded(checkedEnd))
|
||||||
|
checkedAlignedEnd = checkedAlignTo(*checkedEnd, minAlignment, anchor, "local memory alignment");
|
||||||
|
bool startFits = slot.address <= kPimAddressLimit;
|
||||||
|
bool endFits = succeeded(checkedEnd) && *checkedEnd <= kPimAddressLimit;
|
||||||
|
bool alignedEndFits = succeeded(checkedAlignedEnd) && *checkedAlignedEnd <= kPimAddressLimit;
|
||||||
|
if (!startFits || !endFits || !alignedEndFits) {
|
||||||
|
printMemoryOverflowDiagnostic(key.value,
|
||||||
|
key,
|
||||||
|
slot.size,
|
||||||
|
firstAvailableAddress,
|
||||||
|
succeeded(checkedAlignedEnd) ? *checkedAlignedEnd : kPimAddressLimit);
|
||||||
|
llvm_unreachable("PIM local memory allocation overflow");
|
||||||
|
}
|
||||||
|
|
||||||
|
firstAvailableAddress = *checkedAlignedEnd;
|
||||||
|
localPhysicalSlots.push_back(slot);
|
||||||
|
return slot;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PimMemory::allocateHost(ModuleOp moduleOp, func::FuncOp funcOp) {
|
void PimMemory::allocateHost(ModuleOp moduleOp, func::FuncOp funcOp) {
|
||||||
@@ -127,7 +259,7 @@ void PimMemory::allocateHost(ModuleOp moduleOp, func::FuncOp funcOp) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
funcOp.walk([&](memref::AllocOp allocOp) {
|
funcOp.walk([&](memref::AllocOp allocOp) {
|
||||||
if (!allocOp->getParentOfType<pim::PimCoreOp>())
|
if (!isInsidePimCoreLikeOp(allocOp))
|
||||||
gatherMemEntry(allocOp.getResult());
|
gatherMemEntry(allocOp.getResult());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -138,9 +270,71 @@ void PimMemory::allocateHost(ModuleOp moduleOp, func::FuncOp funcOp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PimMemory::allocateCore(Operation* op, std::optional<unsigned> lane) {
|
void PimMemory::allocateCore(Operation* op, std::optional<unsigned> lane) {
|
||||||
op->walk([&](memref::AllocOp allocOp) { gatherMemEntry(allocOp, lane); });
|
auto intervals = buildLocalAllocIntervals(op, lane);
|
||||||
|
SmallVector<PlannedPhysicalSlot> plannedSlots = planPhysicalSlots(intervals);
|
||||||
|
|
||||||
allocateGatheredMemory();
|
SmallVector<size_t> slotOrder(plannedSlots.size());
|
||||||
|
std::iota(slotOrder.begin(), slotOrder.end(), 0);
|
||||||
|
llvm::stable_sort(slotOrder, [&](size_t lhsIndex, size_t rhsIndex) {
|
||||||
|
const PlannedPhysicalSlot& lhs = plannedSlots[lhsIndex];
|
||||||
|
const PlannedPhysicalSlot& rhs = plannedSlots[rhsIndex];
|
||||||
|
if (lhs.requiredSize != rhs.requiredSize)
|
||||||
|
return lhs.requiredSize > rhs.requiredSize;
|
||||||
|
return lhs.id < rhs.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
SmallVector<bool, 16> usedExistingSlots(localPhysicalSlots.size(), false);
|
||||||
|
for (size_t slotIndex : slotOrder) {
|
||||||
|
PlannedPhysicalSlot& slot = plannedSlots[slotIndex];
|
||||||
|
size_t bestExistingIndex = std::numeric_limits<size_t>::max();
|
||||||
|
auto bestKey = std::tuple<size_t, size_t, size_t>(
|
||||||
|
std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max());
|
||||||
|
|
||||||
|
for (size_t existingIndex = 0; existingIndex < localPhysicalSlots.size(); ++existingIndex) {
|
||||||
|
if (usedExistingSlots[existingIndex])
|
||||||
|
continue;
|
||||||
|
const PhysicalSlotInfo& existingSlot = localPhysicalSlots[existingIndex];
|
||||||
|
if (existingSlot.size < slot.requiredSize)
|
||||||
|
continue;
|
||||||
|
auto candidateKey =
|
||||||
|
std::tuple<size_t, size_t, size_t>(existingSlot.size - slot.requiredSize, existingSlot.size, existingSlot.id);
|
||||||
|
if (candidateKey < bestKey) {
|
||||||
|
bestKey = candidateKey;
|
||||||
|
bestExistingIndex = existingIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestExistingIndex != std::numeric_limits<size_t>::max()) {
|
||||||
|
const PhysicalSlotInfo& existingSlot = localPhysicalSlots[bestExistingIndex];
|
||||||
|
slot.id = existingSlot.id;
|
||||||
|
slot.address = existingSlot.address;
|
||||||
|
slot.size = existingSlot.size;
|
||||||
|
usedExistingSlots[bestExistingIndex] = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
PhysicalSlotInfo newSlot = allocatePhysicalSlot(slot.requiredSize, intervals[slot.intervalIndices.front()].key);
|
||||||
|
slot.id = newSlot.id;
|
||||||
|
slot.address = newSlot.address;
|
||||||
|
slot.size = newSlot.size;
|
||||||
|
usedExistingSlots.push_back(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t intervalIndex : slot.intervalIndices) {
|
||||||
|
LocalAllocInterval& interval = intervals[intervalIndex];
|
||||||
|
interval.physicalSlotId = slot.id;
|
||||||
|
interval.assignedAddress = slot.address;
|
||||||
|
interval.physicalSlotSize = slot.size;
|
||||||
|
MemEntry memEntry {slot.address, interval.size};
|
||||||
|
ownedMemEntriesMap[interval.key] = memEntry;
|
||||||
|
globalMemEntriesMap[interval.key] = memEntry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pimMemoryReport != PimMemoryReportNone) {
|
||||||
|
MemoryPlanArtifacts artifacts =
|
||||||
|
buildMemoryPlanArtifacts(op, lane, intervals, plannedSlots, kPimAddressLimit, pimMemoryReport);
|
||||||
|
livenessArtifacts.textReport += artifacts.textReport;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void printHostMemoryReportRow(raw_ostream& os, const MemoryReportRow& row) {
|
static void printHostMemoryReportRow(raw_ostream& os, const MemoryReportRow& row) {
|
||||||
@@ -181,20 +375,11 @@ static MemoryReportRow addMemoryReportRows(const MemoryReportRow& lhs, const Mem
|
|||||||
}
|
}
|
||||||
|
|
||||||
MemoryReportRow PimMemory::getReportRow() const {
|
MemoryReportRow PimMemory::getReportRow() const {
|
||||||
MemoryReportRow row;
|
MemoryReportRow row = reportRow;
|
||||||
for (auto& [key, memEntry] : ownedMemEntriesMap) {
|
row.numAlloca = localPhysicalSlots.size();
|
||||||
if (auto op = key.value.getDefiningOp()) {
|
row.sizeAlloca = 0;
|
||||||
if (isa<memref::AllocOp>(op)) {
|
for (const PhysicalSlotInfo& slot : localPhysicalSlots)
|
||||||
row.numAlloca++;
|
row.sizeAlloca += slot.size;
|
||||||
row.sizeAlloca += memEntry.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isa<memref::GetGlobalOp>(op)) {
|
|
||||||
row.numGlobal++;
|
|
||||||
row.sizeGlobal += memEntry.size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,29 +414,35 @@ size_t PimAcceleratorMemory::getValueAddress(mlir::Value value,
|
|||||||
const StaticValueKnowledge& knowledge,
|
const StaticValueKnowledge& knowledge,
|
||||||
std::optional<unsigned> lane) const {
|
std::optional<unsigned> lane) const {
|
||||||
value = resolveCachedAlias(value, knowledge);
|
value = resolveCachedAlias(value, knowledge);
|
||||||
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() << "\n";
|
|
||||||
llvm_unreachable("Failed to compile contiguous address");
|
|
||||||
}
|
|
||||||
compiledIt = compiledAddressExprs.try_emplace(value, *compiledExpr).first;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto resolvedAddress = compiledIt->second.evaluate(knowledge, lane);
|
FailureOr<ResolvedContiguousAddress> resolvedAddress = resolveContiguousAddress(value, knowledge);
|
||||||
if (failed(resolvedAddress)) {
|
if (failed(resolvedAddress)) {
|
||||||
errs() << "Failed to evaluate contiguous address for value: ";
|
auto compiledIt = compiledAddressExprs.find(value);
|
||||||
value.print(errs());
|
if (compiledIt == compiledAddressExprs.end()) {
|
||||||
errs() << "\n";
|
auto compiledExpr = compileContiguousAddressExpr(value);
|
||||||
if (auto* definingOp = value.getDefiningOp()) {
|
if (failed(compiledExpr)) {
|
||||||
errs() << "Defining op:\n";
|
errs() << "Failed to compile contiguous address for value: ";
|
||||||
definingOp->print(errs());
|
value.print(errs());
|
||||||
errs() << "\n";
|
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";
|
||||||
|
if (auto* definingOp = value.getDefiningOp()) {
|
||||||
|
errs() << "Defining op:\n";
|
||||||
|
definingOp->print(errs());
|
||||||
|
errs() << "\n";
|
||||||
|
}
|
||||||
|
llvm_unreachable("Failed to resolve contiguous address");
|
||||||
}
|
}
|
||||||
llvm_unreachable("Failed to resolve contiguous address");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MemoryValueKey key = getMemoryValueKey(resolvedAddress->base, lane);
|
MemoryValueKey key = getMemoryValueKey(resolvedAddress->base, lane);
|
||||||
@@ -270,7 +461,8 @@ size_t PimAcceleratorMemory::getValueAddress(mlir::Value value,
|
|||||||
llvm_unreachable("Missing mem entry");
|
llvm_unreachable("Missing mem entry");
|
||||||
}
|
}
|
||||||
|
|
||||||
return iter->second.address + resolvedAddress->byteOffset;
|
size_t byteOffset = pim::checkedSizeOrCrash(resolvedAddress->byteOffset, "resolved PIM byte offset");
|
||||||
|
return pim::checkedAddOrCrash(iter->second.address, byteOffset, "resolved PIM address");
|
||||||
}
|
}
|
||||||
|
|
||||||
llvm::FailureOr<int64_t> PimAcceleratorMemory::getIndexValue(mlir::Value value,
|
llvm::FailureOr<int64_t> PimAcceleratorMemory::getIndexValue(mlir::Value value,
|
||||||
@@ -289,8 +481,12 @@ llvm::FailureOr<int64_t> PimAcceleratorMemory::getIndexValue(mlir::Value value,
|
|||||||
void PimAcceleratorMemory::reportHost() { hostReportRow = hostMem.getReportRow(); }
|
void PimAcceleratorMemory::reportHost() { hostReportRow = hostMem.getReportRow(); }
|
||||||
|
|
||||||
void PimAcceleratorMemory::recordCoreReport(size_t coreId, const MemoryReportRow& row) {
|
void PimAcceleratorMemory::recordCoreReport(size_t coreId, const MemoryReportRow& row) {
|
||||||
reportEntries.push_back(
|
reportEntries.push_back({MemoryReportEntry::Kind::Core,
|
||||||
{MemoryReportEntry::Kind::Core, coreId, {static_cast<int32_t>(coreId)}, row, row.numAlloca, row.sizeAlloca});
|
coreId,
|
||||||
|
{pim::checkedI32OrCrash(coreId, "memory report core id")},
|
||||||
|
row,
|
||||||
|
row.numAlloca,
|
||||||
|
row.sizeAlloca});
|
||||||
}
|
}
|
||||||
|
|
||||||
void PimAcceleratorMemory::recordBatchReport(uint64_t batchId,
|
void PimAcceleratorMemory::recordBatchReport(uint64_t batchId,
|
||||||
@@ -314,13 +510,15 @@ void PimAcceleratorMemory::flushReport() {
|
|||||||
|
|
||||||
llvm::raw_os_ostream os(fileReport);
|
llvm::raw_os_ostream os(fileReport);
|
||||||
uint64_t totalGlobalMemory = hostReportRow.has_value() ? hostReportRow->sizeGlobal : 0;
|
uint64_t totalGlobalMemory = hostReportRow.has_value() ? hostReportRow->sizeGlobal : 0;
|
||||||
|
uint64_t totalWeightsMemory = totalWeightBytes;
|
||||||
uint64_t totalCoresMemory = 0;
|
uint64_t totalCoresMemory = 0;
|
||||||
for (const MemoryReportEntry& entry : reportEntries)
|
for (const MemoryReportEntry& entry : reportEntries)
|
||||||
totalCoresMemory += entry.totalAllocaBytes;
|
totalCoresMemory += entry.totalAllocaBytes;
|
||||||
|
|
||||||
llvm::SmallVector<ReportField, 2> totalFields = {
|
llvm::SmallVector<ReportField, 3> totalFields = {
|
||||||
{"Global memory", formatReportMemory(totalGlobalMemory)},
|
{"Global memory", formatReportMemory(totalGlobalMemory) },
|
||||||
{"Cores memory", formatReportMemory(totalCoresMemory) }
|
{"Weights memory", formatReportMemory(totalWeightsMemory)},
|
||||||
|
{"Cores memory", formatReportMemory(totalCoresMemory) }
|
||||||
};
|
};
|
||||||
printReportTotalsBlock(os, totalFields);
|
printReportTotalsBlock(os, totalFields);
|
||||||
|
|
||||||
@@ -394,30 +592,54 @@ void PimCodeGen::emitInstruction(const pim_binary::InstructionRecord& instructio
|
|||||||
++emittedInstructionCount;
|
++emittedInstructionCount;
|
||||||
if (coreJsonStream)
|
if (coreJsonStream)
|
||||||
*coreJsonStream << json::Value(pim_binary::makeInstructionJson(instruction)) << ',';
|
*coreJsonStream << json::Value(pim_binary::makeInstructionJson(instruction)) << ',';
|
||||||
|
updateScalarRegisterCache(instruction);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PimCodeGen::updateScalarRegisterCache(const pim_binary::InstructionRecord& instruction) const {
|
||||||
|
switch (instruction.opcode) {
|
||||||
|
case pim_binary::Opcode::sldi:
|
||||||
|
scalarRegisterValues[instruction.rd] = instruction.r2OrImm;
|
||||||
|
break;
|
||||||
|
case pim_binary::Opcode::sld:
|
||||||
|
case pim_binary::Opcode::sadd:
|
||||||
|
case pim_binary::Opcode::ssub:
|
||||||
|
case pim_binary::Opcode::smul:
|
||||||
|
case pim_binary::Opcode::saddi:
|
||||||
|
case pim_binary::Opcode::smuli:
|
||||||
|
scalarRegisterValues[instruction.rd].reset();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PimCodeGen::genSetRegisterImmediateUnsigned(size_t registerNumber, size_t immediate) const {
|
void PimCodeGen::genSetRegisterImmediateUnsigned(size_t registerNumber, size_t immediate) const {
|
||||||
|
auto registerIndex = pim::checkedU8OrCrash(registerNumber, "register number");
|
||||||
|
auto immediateValue = pim::checkedI32OrCrash(immediate, "register immediate");
|
||||||
|
if (scalarRegisterValues[registerIndex] == immediateValue)
|
||||||
|
return;
|
||||||
|
|
||||||
pim_binary::InstructionRecord instruction;
|
pim_binary::InstructionRecord instruction;
|
||||||
instruction.opcode = pim_binary::Opcode::sldi;
|
instruction.opcode = pim_binary::Opcode::sldi;
|
||||||
instruction.rd = static_cast<uint8_t>(registerNumber);
|
instruction.rd = registerIndex;
|
||||||
instruction.r2OrImm = static_cast<int32_t>(immediate);
|
instruction.r2OrImm = immediateValue;
|
||||||
emitInstruction(instruction);
|
emitInstruction(instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PimCodeGen::setupRd(size_t rdAddress, size_t rdOffset) const {
|
void PimCodeGen::setupRd(size_t rdAddress, size_t rdOffset) const {
|
||||||
genSetRegisterImmediateUnsigned(0, rdAddress + rdOffset);
|
genSetRegisterImmediateUnsigned(0, pim::checkedAddOrCrash(rdAddress, rdOffset, "rd address"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PimCodeGen::setupRdRs1(size_t rdAddress, size_t rdOffset, size_t rs1Address, size_t rs1Offset) const {
|
void PimCodeGen::setupRdRs1(size_t rdAddress, size_t rdOffset, size_t rs1Address, size_t rs1Offset) const {
|
||||||
genSetRegisterImmediateUnsigned(0, rdAddress + rdOffset);
|
genSetRegisterImmediateUnsigned(0, pim::checkedAddOrCrash(rdAddress, rdOffset, "rd address"));
|
||||||
genSetRegisterImmediateUnsigned(1, rs1Address + rs1Offset);
|
genSetRegisterImmediateUnsigned(1, pim::checkedAddOrCrash(rs1Address, rs1Offset, "rs1 address"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PimCodeGen::setupRdRs1Rs2(
|
void PimCodeGen::setupRdRs1Rs2(
|
||||||
size_t rdAddress, size_t rdOffset, size_t rs1Address, size_t rs1Offset, size_t rs2Address, size_t rs2Offset) const {
|
size_t rdAddress, size_t rdOffset, size_t rs1Address, size_t rs1Offset, size_t rs2Address, size_t rs2Offset) const {
|
||||||
genSetRegisterImmediateUnsigned(0, rdAddress + rdOffset);
|
genSetRegisterImmediateUnsigned(0, pim::checkedAddOrCrash(rdAddress, rdOffset, "rd address"));
|
||||||
genSetRegisterImmediateUnsigned(1, rs1Address + rs1Offset);
|
genSetRegisterImmediateUnsigned(1, pim::checkedAddOrCrash(rs1Address, rs1Offset, "rs1 address"));
|
||||||
genSetRegisterImmediateUnsigned(2, rs2Address + rs2Offset);
|
genSetRegisterImmediateUnsigned(2, pim::checkedAddOrCrash(rs2Address, rs2Offset, "rs2 address"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PimCodeGen::emitMemCopyOp(StringRef opName,
|
void PimCodeGen::emitMemCopyOp(StringRef opName,
|
||||||
@@ -435,8 +657,7 @@ void PimCodeGen::emitMemCopyOp(StringRef opName,
|
|||||||
instruction.r1 = 1;
|
instruction.r1 = 1;
|
||||||
instruction.generic1 = 0;
|
instruction.generic1 = 0;
|
||||||
instruction.generic2 = 0;
|
instruction.generic2 = 0;
|
||||||
instruction.generic3 = static_cast<int32_t>(size);
|
instruction.generic3 = pim::checkedI32OrCrash(size, sizeFieldName);
|
||||||
(void) sizeFieldName;
|
|
||||||
emitInstruction(instruction);
|
emitInstruction(instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,10 +667,10 @@ void PimCodeGen::emitCommunicationOp(StringRef opName, size_t bufferAddr, size_t
|
|||||||
pim_binary::InstructionRecord instruction;
|
pim_binary::InstructionRecord instruction;
|
||||||
instruction.opcode = pim_binary::opcodeFromString(opName);
|
instruction.opcode = pim_binary::opcodeFromString(opName);
|
||||||
instruction.rd = 0;
|
instruction.rd = 0;
|
||||||
instruction.r2OrImm = static_cast<int32_t>(remapCoreId(coreId));
|
instruction.r2OrImm = pim::checkedI32OrCrash(remapCoreId(coreId), "communication core id");
|
||||||
instruction.generic1 = 0;
|
instruction.generic1 = 0;
|
||||||
instruction.generic2 = 0;
|
instruction.generic2 = 0;
|
||||||
instruction.generic3 = static_cast<int32_t>(size);
|
instruction.generic3 = pim::checkedI32OrCrash(size, "communication byte size");
|
||||||
emitInstruction(instruction);
|
emitInstruction(instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,7 +683,7 @@ void PimCodeGen::emitMvmOp(size_t groupId, size_t rdAddr, size_t rdOffset, size_
|
|||||||
instruction.r1 = 1;
|
instruction.r1 = 1;
|
||||||
instruction.r2OrImm = 8;
|
instruction.r2OrImm = 8;
|
||||||
instruction.generic1 = 0;
|
instruction.generic1 = 0;
|
||||||
instruction.generic2 = static_cast<int32_t>(groupId);
|
instruction.generic2 = pim::checkedI32OrCrash(groupId, "mvm group id");
|
||||||
emitInstruction(instruction);
|
emitInstruction(instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,16 +700,6 @@ void PimCodeGen::codeGenLoadOp(pim::PimMemCopyHostToDevOp loadOp, const StaticVa
|
|||||||
loadOp.getSize());
|
loadOp.getSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
void PimCodeGen::codeGenLoadBatchOp(pim::PimMemCopyHostToDevBatchOp loadOp,
|
|
||||||
const StaticValueKnowledge& knowledge) const {
|
|
||||||
emitMemCopyOp("ld",
|
|
||||||
addressOf(loadOp.getDeviceTarget(), knowledge),
|
|
||||||
loadOp.getDeviceTargetOffset(),
|
|
||||||
addressOf(loadOp.getHostSource(), knowledge),
|
|
||||||
loadOp.getHostSourceOffset(),
|
|
||||||
loadOp.getSize());
|
|
||||||
}
|
|
||||||
|
|
||||||
void PimCodeGen::codeGenStoreOp(pim::PimMemCopyDevToHostOp storeOp, const StaticValueKnowledge& knowledge) const {
|
void PimCodeGen::codeGenStoreOp(pim::PimMemCopyDevToHostOp storeOp, const StaticValueKnowledge& knowledge) const {
|
||||||
auto hostTargetOffset = indexOf(storeOp.getHostTargetOffset(), knowledge);
|
auto hostTargetOffset = indexOf(storeOp.getHostTargetOffset(), knowledge);
|
||||||
auto deviceSourceOffset = indexOf(storeOp.getDeviceSourceOffset(), knowledge);
|
auto deviceSourceOffset = indexOf(storeOp.getDeviceSourceOffset(), knowledge);
|
||||||
@@ -503,11 +714,15 @@ void PimCodeGen::codeGenStoreOp(pim::PimMemCopyDevToHostOp storeOp, const Static
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PimCodeGen::codeGenLmvOp(pim::PimMemCopyOp lmvOp, const StaticValueKnowledge& knowledge) const {
|
void PimCodeGen::codeGenLmvOp(pim::PimMemCopyOp lmvOp, const StaticValueKnowledge& knowledge) const {
|
||||||
|
auto targetOffset = indexOf(lmvOp.getTargetOffset(), knowledge);
|
||||||
|
auto sourceOffset = indexOf(lmvOp.getSourceOffset(), knowledge);
|
||||||
|
assert(succeeded(targetOffset) && succeeded(sourceOffset)
|
||||||
|
&& "pim.memcp offsets must be statically resolvable during codegen");
|
||||||
emitMemCopyOp("lmv",
|
emitMemCopyOp("lmv",
|
||||||
addressOf(lmvOp.getTarget(), knowledge),
|
addressOf(lmvOp.getTarget(), knowledge),
|
||||||
lmvOp.getTargetOffset(),
|
*targetOffset,
|
||||||
addressOf(lmvOp.getSource(), knowledge),
|
addressOf(lmvOp.getSource(), knowledge),
|
||||||
lmvOp.getSourceOffset(),
|
*sourceOffset,
|
||||||
lmvOp.getSize(),
|
lmvOp.getSize(),
|
||||||
"len");
|
"len");
|
||||||
}
|
}
|
||||||
@@ -582,7 +797,7 @@ void PimCodeGen::codeGenVVAddOp(pim::PimVVAddOp vvaddOp, const StaticValueKnowle
|
|||||||
instruction.rd = 0;
|
instruction.rd = 0;
|
||||||
instruction.r1 = 1;
|
instruction.r1 = 1;
|
||||||
instruction.r2OrImm = 2;
|
instruction.r2OrImm = 2;
|
||||||
instruction.generic3 = static_cast<int32_t>(getShapedTypeSizeInBytes(cast<ShapedType>(vvaddOp.getLhs().getType())));
|
instruction.generic3 = getVectorByteSizeOrCrash(cast<ShapedType>(vvaddOp.getLhs().getType()));
|
||||||
emitInstruction(instruction);
|
emitInstruction(instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,7 +812,7 @@ void PimCodeGen::codeGenVVSubOp(pim::PimVVSubOp vvsubOp, const StaticValueKnowle
|
|||||||
instruction.rd = 0;
|
instruction.rd = 0;
|
||||||
instruction.r1 = 1;
|
instruction.r1 = 1;
|
||||||
instruction.r2OrImm = 2;
|
instruction.r2OrImm = 2;
|
||||||
instruction.generic3 = static_cast<int32_t>(getShapedTypeSizeInBytes(cast<ShapedType>(vvsubOp.getLhs().getType())));
|
instruction.generic3 = getVectorByteSizeOrCrash(cast<ShapedType>(vvsubOp.getLhs().getType()));
|
||||||
emitInstruction(instruction);
|
emitInstruction(instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -612,7 +827,7 @@ void PimCodeGen::codeGenVVMulOp(pim::PimVVMulOp vvmulOp, const StaticValueKnowle
|
|||||||
instruction.rd = 0;
|
instruction.rd = 0;
|
||||||
instruction.r1 = 1;
|
instruction.r1 = 1;
|
||||||
instruction.r2OrImm = 2;
|
instruction.r2OrImm = 2;
|
||||||
instruction.generic3 = static_cast<int32_t>(getShapedTypeSizeInBytes(cast<ShapedType>(vvmulOp.getLhs().getType())));
|
instruction.generic3 = getVectorByteSizeOrCrash(cast<ShapedType>(vvmulOp.getLhs().getType()));
|
||||||
emitInstruction(instruction);
|
emitInstruction(instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -627,7 +842,7 @@ void PimCodeGen::codeGenVVMaxOp(pim::PimVVMaxOp vvmaxOp, const StaticValueKnowle
|
|||||||
instruction.rd = 0;
|
instruction.rd = 0;
|
||||||
instruction.r1 = 1;
|
instruction.r1 = 1;
|
||||||
instruction.r2OrImm = 2;
|
instruction.r2OrImm = 2;
|
||||||
instruction.generic3 = static_cast<int32_t>(getShapedTypeSizeInBytes(cast<ShapedType>(vvmaxOp.getLhs().getType())));
|
instruction.generic3 = getVectorByteSizeOrCrash(cast<ShapedType>(vvmaxOp.getLhs().getType()));
|
||||||
emitInstruction(instruction);
|
emitInstruction(instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -642,7 +857,7 @@ void PimCodeGen::codeGenVVDMulOp(pim::PimVVDMulOp vvdmulOp, const StaticValueKno
|
|||||||
instruction.rd = 0;
|
instruction.rd = 0;
|
||||||
instruction.r1 = 1;
|
instruction.r1 = 1;
|
||||||
instruction.r2OrImm = 2;
|
instruction.r2OrImm = 2;
|
||||||
instruction.generic3 = static_cast<int32_t>(getShapedTypeSizeInBytes(cast<ShapedType>(vvdmulOp.getLhs().getType())));
|
instruction.generic3 = getVectorByteSizeOrCrash(cast<ShapedType>(vvdmulOp.getLhs().getType()));
|
||||||
emitInstruction(instruction);
|
emitInstruction(instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -657,7 +872,7 @@ void PimCodeGen::codeGenVAvgOp(pim::PimVAvgOp vavgOp, const StaticValueKnowledge
|
|||||||
instruction.r1 = 1;
|
instruction.r1 = 1;
|
||||||
instruction.r2OrImm = 1;
|
instruction.r2OrImm = 1;
|
||||||
instruction.generic1 = 1;
|
instruction.generic1 = 1;
|
||||||
instruction.generic3 = static_cast<int32_t>(getShapedTypeSizeInBytes(cast<ShapedType>(vavgOp.getInput().getType())));
|
instruction.generic3 = getVectorByteSizeOrCrash(cast<ShapedType>(vavgOp.getInput().getType()));
|
||||||
emitInstruction(instruction);
|
emitInstruction(instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -670,7 +885,7 @@ void PimCodeGen::codeGenVReluOp(pim::PimVReluOp vreluOp, const StaticValueKnowle
|
|||||||
instruction.opcode = pim_binary::Opcode::vrelu;
|
instruction.opcode = pim_binary::Opcode::vrelu;
|
||||||
instruction.rd = 0;
|
instruction.rd = 0;
|
||||||
instruction.r1 = 1;
|
instruction.r1 = 1;
|
||||||
instruction.generic3 = static_cast<int32_t>(getShapedTypeSizeInBytes(cast<ShapedType>(vreluOp.getInput().getType())));
|
instruction.generic3 = getVectorByteSizeOrCrash(cast<ShapedType>(vreluOp.getInput().getType()));
|
||||||
emitInstruction(instruction);
|
emitInstruction(instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -683,7 +898,7 @@ void PimCodeGen::codeGenVTanhOp(pim::PimVTanhOp vtanhOp, const StaticValueKnowle
|
|||||||
instruction.opcode = pim_binary::Opcode::vtanh;
|
instruction.opcode = pim_binary::Opcode::vtanh;
|
||||||
instruction.rd = 0;
|
instruction.rd = 0;
|
||||||
instruction.r1 = 1;
|
instruction.r1 = 1;
|
||||||
instruction.generic3 = static_cast<int32_t>(getShapedTypeSizeInBytes(cast<ShapedType>(vtanhOp.getInput().getType())));
|
instruction.generic3 = getVectorByteSizeOrCrash(cast<ShapedType>(vtanhOp.getInput().getType()));
|
||||||
emitInstruction(instruction);
|
emitInstruction(instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -696,7 +911,7 @@ void PimCodeGen::codeGenVSigmOp(pim::PimVSigmOp vsigmOp, const StaticValueKnowle
|
|||||||
instruction.opcode = pim_binary::Opcode::vsigm;
|
instruction.opcode = pim_binary::Opcode::vsigm;
|
||||||
instruction.rd = 0;
|
instruction.rd = 0;
|
||||||
instruction.r1 = 1;
|
instruction.r1 = 1;
|
||||||
instruction.generic3 = static_cast<int32_t>(getShapedTypeSizeInBytes(cast<ShapedType>(vsigmOp.getInput().getType())));
|
instruction.generic3 = getVectorByteSizeOrCrash(cast<ShapedType>(vsigmOp.getInput().getType()));
|
||||||
emitInstruction(instruction);
|
emitInstruction(instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -709,8 +924,7 @@ void PimCodeGen::codeGenVSoftmaxOp(pim::PimVSoftmaxOp vsoftmaxOp, const StaticVa
|
|||||||
instruction.opcode = pim_binary::Opcode::vsoftmax;
|
instruction.opcode = pim_binary::Opcode::vsoftmax;
|
||||||
instruction.rd = 0;
|
instruction.rd = 0;
|
||||||
instruction.r1 = 1;
|
instruction.r1 = 1;
|
||||||
instruction.generic3 =
|
instruction.generic3 = getVectorByteSizeOrCrash(cast<ShapedType>(vsoftmaxOp.getInput().getType()));
|
||||||
static_cast<int32_t>(getShapedTypeSizeInBytes(cast<ShapedType>(vsoftmaxOp.getInput().getType())));
|
|
||||||
emitInstruction(instruction);
|
emitInstruction(instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -811,11 +1025,44 @@ static SmallVector<Operation*> collectTopLevelCoreLikeOps(func::FuncOp funcOp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct CoreEmissionResult {
|
struct CoreEmissionResult {
|
||||||
|
static constexpr size_t kMaxStoredCodegenDiagnostics = 8;
|
||||||
|
|
||||||
|
struct DiagnosticRecord {
|
||||||
|
Operation* op = nullptr;
|
||||||
|
std::string message;
|
||||||
|
};
|
||||||
|
|
||||||
OnnxMlirCompilerErrorCodes status = CompilerSuccess;
|
OnnxMlirCompilerErrorCodes status = CompilerSuccess;
|
||||||
MemoryReportRow reportRow;
|
MemoryReportRow reportRow;
|
||||||
llvm::SmallVector<ResolvedWeightView, 8> usedWeights;
|
llvm::SmallVector<ResolvedWeightView, 8> usedWeights;
|
||||||
|
MemoryPlanArtifacts livenessArtifacts;
|
||||||
|
llvm::SmallVector<DiagnosticRecord, kMaxStoredCodegenDiagnostics> diagnostics;
|
||||||
|
size_t diagnosticCount = 0;
|
||||||
|
|
||||||
|
void recordDiagnostic(Operation* op, StringRef message) {
|
||||||
|
++diagnosticCount;
|
||||||
|
if (diagnostics.size() < kMaxStoredCodegenDiagnostics)
|
||||||
|
diagnostics.push_back({op, message.str()});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static StaticValueKnowledge seedCoreCodegenKnowledge(pim::PimCoreOp coreOp) {
|
||||||
|
StaticValueKnowledge knowledge;
|
||||||
|
for (auto [index, weight] : llvm::enumerate(coreOp.getWeights()))
|
||||||
|
knowledge.aliases[coreOp.getWeightArgument(index)] = weight;
|
||||||
|
return knowledge;
|
||||||
|
}
|
||||||
|
|
||||||
|
static StaticValueKnowledge seedCoreBatchCodegenKnowledge(pim::PimCoreBatchOp coreBatchOp, unsigned lane) {
|
||||||
|
StaticValueKnowledge knowledge;
|
||||||
|
knowledge.indexValues[coreBatchOp.getLaneArgument()] = lane;
|
||||||
|
for (auto [index, weight] : llvm::enumerate(coreBatchOp.getWeights()))
|
||||||
|
knowledge.aliases[coreBatchOp.getWeightArgument(index)] = weight;
|
||||||
|
for (auto [index, input] : llvm::enumerate(coreBatchOp.getInputs()))
|
||||||
|
knowledge.aliases[coreBatchOp.getInputArgument(index)] = input;
|
||||||
|
return knowledge;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename MapTy>
|
template <typename MapTy>
|
||||||
class ScopedMapBindings {
|
class ScopedMapBindings {
|
||||||
using KeyTy = typename MapTy::key_type;
|
using KeyTy = typename MapTy::key_type;
|
||||||
@@ -848,7 +1095,6 @@ public:
|
|||||||
|
|
||||||
enum class CompiledCoreOpKind : uint8_t {
|
enum class CompiledCoreOpKind : uint8_t {
|
||||||
Load,
|
Load,
|
||||||
LoadBatch,
|
|
||||||
Store,
|
Store,
|
||||||
Lmv,
|
Lmv,
|
||||||
Receive,
|
Receive,
|
||||||
@@ -872,7 +1118,8 @@ enum class CompiledCoreOpKind : uint8_t {
|
|||||||
struct CompiledCoreNode {
|
struct CompiledCoreNode {
|
||||||
enum class Kind : uint8_t {
|
enum class Kind : uint8_t {
|
||||||
Op,
|
Op,
|
||||||
Loop
|
Loop,
|
||||||
|
If
|
||||||
};
|
};
|
||||||
|
|
||||||
Kind kind = Kind::Op;
|
Kind kind = Kind::Op;
|
||||||
@@ -881,14 +1128,15 @@ struct CompiledCoreNode {
|
|||||||
CompiledIndexExpr lowerBound;
|
CompiledIndexExpr lowerBound;
|
||||||
CompiledIndexExpr upperBound;
|
CompiledIndexExpr upperBound;
|
||||||
CompiledIndexExpr step;
|
CompiledIndexExpr step;
|
||||||
|
CompiledIndexExpr condition;
|
||||||
std::unique_ptr<llvm::SmallVector<CompiledCoreNode, 8>> loopBody;
|
std::unique_ptr<llvm::SmallVector<CompiledCoreNode, 8>> loopBody;
|
||||||
|
std::unique_ptr<llvm::SmallVector<CompiledCoreNode, 8>> thenBody;
|
||||||
|
std::unique_ptr<llvm::SmallVector<CompiledCoreNode, 8>> elseBody;
|
||||||
};
|
};
|
||||||
|
|
||||||
static FailureOr<CompiledCoreOpKind> classifyCompiledCoreOpKind(Operation& op) {
|
static FailureOr<CompiledCoreOpKind> classifyCompiledCoreOpKind(Operation& op) {
|
||||||
if (isa<pim::PimMemCopyHostToDevOp>(op))
|
if (isa<pim::PimMemCopyHostToDevOp>(op))
|
||||||
return CompiledCoreOpKind::Load;
|
return CompiledCoreOpKind::Load;
|
||||||
if (isa<pim::PimMemCopyHostToDevBatchOp>(op))
|
|
||||||
return CompiledCoreOpKind::LoadBatch;
|
|
||||||
if (isa<pim::PimMemCopyDevToHostOp>(op))
|
if (isa<pim::PimMemCopyDevToHostOp>(op))
|
||||||
return CompiledCoreOpKind::Store;
|
return CompiledCoreOpKind::Store;
|
||||||
if (isa<pim::PimMemCopyOp>(op))
|
if (isa<pim::PimMemCopyOp>(op))
|
||||||
@@ -961,6 +1209,28 @@ compileCoreEmissionPlan(Block& block, Operation* weightOwner, llvm::SmallVectorI
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auto ifOp = dyn_cast<mlir::scf::IfOp>(op)) {
|
||||||
|
auto condition = compileIndexExpr(ifOp.getCondition());
|
||||||
|
if (failed(condition)) {
|
||||||
|
ifOp.emitOpError("requires statically evaluable scf.if condition for PIM codegen");
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
CompiledCoreNode ifNode;
|
||||||
|
ifNode.kind = CompiledCoreNode::Kind::If;
|
||||||
|
ifNode.op = ifOp.getOperation();
|
||||||
|
ifNode.condition = *condition;
|
||||||
|
ifNode.thenBody = std::make_unique<llvm::SmallVector<CompiledCoreNode, 8>>();
|
||||||
|
if (failed(compileCoreEmissionPlan(ifOp.getThenRegion().front(), weightOwner, *ifNode.thenBody)))
|
||||||
|
return failure();
|
||||||
|
ifNode.elseBody = std::make_unique<llvm::SmallVector<CompiledCoreNode, 8>>();
|
||||||
|
if (!ifOp.getElseRegion().empty())
|
||||||
|
if (failed(compileCoreEmissionPlan(ifOp.getElseRegion().front(), weightOwner, *ifNode.elseBody)))
|
||||||
|
return failure();
|
||||||
|
plan.push_back(std::move(ifNode));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
auto opKind = classifyCompiledCoreOpKind(op);
|
auto opKind = classifyCompiledCoreOpKind(op);
|
||||||
if (failed(opKind)) {
|
if (failed(opKind)) {
|
||||||
InFlightDiagnostic diag = op.emitError() << "unsupported codegen for op '" << op.getName().getStringRef() << "'";
|
InFlightDiagnostic diag = op.emitError() << "unsupported codegen for op '" << op.getName().getStringRef() << "'";
|
||||||
@@ -1023,13 +1293,30 @@ static LogicalResult executeCompiledCorePlan(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node.kind == CompiledCoreNode::Kind::If) {
|
||||||
|
auto condition = node.condition.evaluate(knowledge);
|
||||||
|
auto ifOp = cast<mlir::scf::IfOp>(node.op);
|
||||||
|
if (failed(condition)) {
|
||||||
|
ifOp.emitOpError("requires statically evaluable scf.if condition for PIM codegen");
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& selectedBody = *condition != 0 ? node.thenBody : node.elseBody;
|
||||||
|
if (selectedBody && failed(executeCompiledCorePlan(*selectedBody,
|
||||||
|
coreCodeGen,
|
||||||
|
knowledge,
|
||||||
|
resolveWeightSlot,
|
||||||
|
processedOperations,
|
||||||
|
batchLane,
|
||||||
|
batchLaneCount)))
|
||||||
|
return failure();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
switch (node.opKind) {
|
switch (node.opKind) {
|
||||||
case CompiledCoreOpKind::Load:
|
case CompiledCoreOpKind::Load:
|
||||||
coreCodeGen.codeGenLoadOp(cast<pim::PimMemCopyHostToDevOp>(node.op), knowledge);
|
coreCodeGen.codeGenLoadOp(cast<pim::PimMemCopyHostToDevOp>(node.op), knowledge);
|
||||||
break;
|
break;
|
||||||
case CompiledCoreOpKind::LoadBatch:
|
|
||||||
coreCodeGen.codeGenLoadBatchOp(cast<pim::PimMemCopyHostToDevBatchOp>(node.op), knowledge);
|
|
||||||
break;
|
|
||||||
case CompiledCoreOpKind::Store:
|
case CompiledCoreOpKind::Store:
|
||||||
coreCodeGen.codeGenStoreOp(cast<pim::PimMemCopyDevToHostOp>(node.op), knowledge);
|
coreCodeGen.codeGenStoreOp(cast<pim::PimMemCopyDevToHostOp>(node.op), knowledge);
|
||||||
break;
|
break;
|
||||||
@@ -1213,17 +1500,18 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
|||||||
auto linkCoreWeights =
|
auto linkCoreWeights =
|
||||||
[&](size_t coreId, ArrayRef<std::string> weightFiles, json::Array& xbarsPerGroup) -> OnnxMlirCompilerErrorCodes {
|
[&](size_t coreId, ArrayRef<std::string> weightFiles, json::Array& xbarsPerGroup) -> OnnxMlirCompilerErrorCodes {
|
||||||
auto coreWeightsDirPath = outputDirPath + "/core_" + std::to_string(coreId);
|
auto coreWeightsDirPath = outputDirPath + "/core_" + std::to_string(coreId);
|
||||||
if (auto error = sys::fs::create_directory(coreWeightsDirPath)) {
|
if (auto error = sys::fs::create_directory(coreWeightsDirPath); error && error != std::errc::file_exists) {
|
||||||
errs() << "Error creating core directory: " << coreWeightsDirPath << ": " << error.message() << '\n';
|
errs() << "Error creating core directory: " << coreWeightsDirPath << ": " << error.message() << '\n';
|
||||||
return InvalidOutputFileAccess;
|
return InvalidOutputFileAccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto [slot, fileName] : llvm::enumerate(weightFiles)) {
|
for (auto [slot, fileName] : llvm::enumerate(weightFiles)) {
|
||||||
xbarsPerGroup.push_back(static_cast<int64_t>(slot));
|
xbarsPerGroup.push_back(static_cast<int64_t>(slot));
|
||||||
if (auto error = sys::fs::create_link(outputDirPath + "/weights/" + fileName,
|
std::string sourcePath = outputDirPath + "/weights/" + fileName;
|
||||||
coreWeightsDirPath + "/crossbar_" + std::to_string(slot) + ".bin")) {
|
std::string targetPath = coreWeightsDirPath + "/crossbar_" + std::to_string(slot) + ".bin";
|
||||||
errs() << "Error creating link file: " << (outputDirPath + "/weights/" + fileName) << " to "
|
sys::fs::remove(targetPath);
|
||||||
<< (coreWeightsDirPath + "/crossbar_" + std::to_string(slot) + ".bin") << "\nError:" << error.message()
|
if (auto error = sys::fs::create_link(sourcePath, targetPath)) {
|
||||||
|
errs() << "Error creating link file: " << sourcePath << " to " << targetPath << "\nError:" << error.message()
|
||||||
<< '\n';
|
<< '\n';
|
||||||
return InvalidOutputFileAccess;
|
return InvalidOutputFileAccess;
|
||||||
}
|
}
|
||||||
@@ -1241,7 +1529,20 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
|||||||
const StaticValueKnowledge& knowledge) -> llvm::FailureOr<unsigned> {
|
const StaticValueKnowledge& knowledge) -> llvm::FailureOr<unsigned> {
|
||||||
auto weightView = onnx_mlir::resolveWeightView(job.coreLikeOp, vmmOp.getWeight(), knowledge);
|
auto weightView = onnx_mlir::resolveWeightView(job.coreLikeOp, vmmOp.getWeight(), knowledge);
|
||||||
if (failed(weightView)) {
|
if (failed(weightView)) {
|
||||||
vmmOp.emitOpError("requires a statically resolvable dense global weight view during PIM codegen");
|
std::string message;
|
||||||
|
llvm::raw_string_ostream os(message);
|
||||||
|
os << "requires a statically resolvable dense global weight view during PIM codegen; weight="
|
||||||
|
<< vmmOp.getWeight() << " type=" << vmmOp.getWeight().getType();
|
||||||
|
result.recordDiagnostic(vmmOp, os.str());
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
if (weightView->shape.size() != 2) {
|
||||||
|
std::string message;
|
||||||
|
llvm::raw_string_ostream os(message);
|
||||||
|
os << "requires a rank-2 matrix weight view during PIM codegen; resolved shape=[";
|
||||||
|
llvm::interleaveComma(weightView->shape, os);
|
||||||
|
os << "] weight=" << vmmOp.getWeight() << " type=" << vmmOp.getWeight().getType();
|
||||||
|
result.recordDiagnostic(vmmOp, os.str());
|
||||||
return failure();
|
return failure();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1282,15 +1583,16 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
|||||||
auto& deviceMemory = jobMemory.getOrCreateDeviceMem(job.emittedCoreId);
|
auto& deviceMemory = jobMemory.getOrCreateDeviceMem(job.emittedCoreId);
|
||||||
deviceMemory.allocateCore(coreOp);
|
deviceMemory.allocateCore(coreOp);
|
||||||
|
|
||||||
int64_t processedOperations = codeGenCoreOps(
|
StaticValueKnowledge knowledge = seedCoreCodegenKnowledge(coreOp);
|
||||||
coreOp.getBody().front(), coreCodeGen, StaticValueKnowledge {}, coreOp.getOperation(), resolveWeightSlot);
|
int64_t processedOperations =
|
||||||
|
codeGenCoreOps(coreOp.getBody().front(), coreCodeGen, knowledge, coreOp.getOperation(), resolveWeightSlot);
|
||||||
if (processedOperations < 0) {
|
if (processedOperations < 0) {
|
||||||
result.status = CompilerFailure;
|
result.status = CompilerFailure;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
assert(processedOperations > 0);
|
|
||||||
result.reportRow = deviceMemory.getReportRow();
|
result.reportRow = deviceMemory.getReportRow();
|
||||||
result.usedWeights = std::move(usedWeights);
|
result.usedWeights = std::move(usedWeights);
|
||||||
|
result.livenessArtifacts = deviceMemory.getLivenessArtifacts();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
auto coreBatchOp = cast<pim::PimCoreBatchOp>(job.coreLikeOp);
|
auto coreBatchOp = cast<pim::PimCoreBatchOp>(job.coreLikeOp);
|
||||||
@@ -1298,10 +1600,7 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
|||||||
auto& deviceMemory = jobMemory.getOrCreateDeviceMem(job.emittedCoreId);
|
auto& deviceMemory = jobMemory.getOrCreateDeviceMem(job.emittedCoreId);
|
||||||
|
|
||||||
for (unsigned lane : job.lanes) {
|
for (unsigned lane : job.lanes) {
|
||||||
StaticValueKnowledge knowledge;
|
StaticValueKnowledge knowledge = seedCoreBatchCodegenKnowledge(coreBatchOp, lane);
|
||||||
knowledge.indexValues[coreBatchOp.getLaneArgument()] = lane;
|
|
||||||
for (unsigned i = 0; i < coreBatchOp.getInputs().size(); ++i)
|
|
||||||
knowledge.aliases[coreBatchOp.getInputArgument(i)] = coreBatchOp.getInputs()[i];
|
|
||||||
|
|
||||||
deviceMemory.allocateCore(coreBatchOp, lane);
|
deviceMemory.allocateCore(coreBatchOp, lane);
|
||||||
coreCodeGen.setBatchLane(lane);
|
coreCodeGen.setBatchLane(lane);
|
||||||
@@ -1316,11 +1615,11 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
|||||||
result.status = CompilerFailure;
|
result.status = CompilerFailure;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
assert(processedOperations > 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result.reportRow = deviceMemory.getReportRow();
|
result.reportRow = deviceMemory.getReportRow();
|
||||||
result.usedWeights = std::move(usedWeights);
|
result.usedWeights = std::move(usedWeights);
|
||||||
|
result.livenessArtifacts = deviceMemory.getLivenessArtifacts();
|
||||||
}
|
}
|
||||||
|
|
||||||
pim_binary::patchInstructionCount(coreBinaryStream, coreCodeGen.getEmittedInstructionCount());
|
pim_binary::patchInstructionCount(coreBinaryStream, coreCodeGen.getEmittedInstructionCount());
|
||||||
@@ -1339,6 +1638,21 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
|||||||
mlir::parallelFor(
|
mlir::parallelFor(
|
||||||
moduleOp.getContext(), 0, jobs.size(), [&](size_t index) { jobResults[index] = emitJob(jobs[index]); });
|
moduleOp.getContext(), 0, jobs.size(), [&](size_t index) { jobResults[index] = emitJob(jobs[index]); });
|
||||||
|
|
||||||
|
pim::CappedDiagnosticReporter diagnostics;
|
||||||
|
Operation* summaryAnchor = nullptr;
|
||||||
|
for (const CoreEmissionResult& result : jobResults) {
|
||||||
|
if (!summaryAnchor && !result.diagnostics.empty())
|
||||||
|
summaryAnchor = result.diagnostics.front().op;
|
||||||
|
for (const CoreEmissionResult::DiagnosticRecord& diagnostic : result.diagnostics) {
|
||||||
|
diagnostics.report(diagnostic.op, [&](Operation* op) { op->emitError() << diagnostic.message; });
|
||||||
|
}
|
||||||
|
size_t unreportedCount = result.diagnosticCount - result.diagnostics.size();
|
||||||
|
diagnostics.noteFailures(static_cast<int64_t>(unreportedCount));
|
||||||
|
}
|
||||||
|
if (diagnostics.hasFailure())
|
||||||
|
diagnostics.emitSuppressedSummary(summaryAnchor ? summaryAnchor : moduleOp.getOperation(),
|
||||||
|
"PIM codegen diagnostic(s)");
|
||||||
|
|
||||||
for (size_t jobIndex = 0; jobIndex < jobs.size(); ++jobIndex)
|
for (size_t jobIndex = 0; jobIndex < jobs.size(); ++jobIndex)
|
||||||
if (jobResults[jobIndex].status != CompilerSuccess)
|
if (jobResults[jobIndex].status != CompilerSuccess)
|
||||||
return jobResults[jobIndex].status;
|
return jobResults[jobIndex].status;
|
||||||
@@ -1351,7 +1665,21 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
|||||||
request.weights = jobResults[jobIndex].usedWeights;
|
request.weights = jobResults[jobIndex].usedWeights;
|
||||||
weightRequests.push_back(std::move(request));
|
weightRequests.push_back(std::move(request));
|
||||||
}
|
}
|
||||||
auto mapCoreWeightToFileName = createAndPopulateWeightFolder(weightRequests, outputDirPath);
|
auto weightEmission = createAndPopulateWeightFolder(weightRequests, outputDirPath);
|
||||||
|
memory.setTotalWeightBytes(weightEmission.totalWeightBytes);
|
||||||
|
auto& mapCoreWeightToFileName = weightEmission.mapCoreWeightToFileName;
|
||||||
|
if (std::string reportsRoot = getOutputDir(); !reportsRoot.empty()) {
|
||||||
|
std::string reportsDir = reportsRoot + "/reports";
|
||||||
|
sys::fs::remove(reportsDir + "/pim_memory_liveness_report.txt");
|
||||||
|
sys::fs::remove(reportsDir + "/pim_memory_liveness_report.json");
|
||||||
|
sys::fs::remove(reportsDir + "/pim_memory_liveness_timeline.dot");
|
||||||
|
}
|
||||||
|
std::fstream livenessReportFile;
|
||||||
|
std::unique_ptr<llvm::raw_os_ostream> livenessReportOs;
|
||||||
|
if (pimMemoryReport != PimMemoryReportNone) {
|
||||||
|
livenessReportFile = openReportFileWithExtension("pim_memory_liveness_report", "txt");
|
||||||
|
livenessReportOs = std::make_unique<llvm::raw_os_ostream>(livenessReportFile);
|
||||||
|
}
|
||||||
|
|
||||||
for (size_t jobIndex = 0; jobIndex < jobs.size(); ++jobIndex) {
|
for (size_t jobIndex = 0; jobIndex < jobs.size(); ++jobIndex) {
|
||||||
const CoreEmissionJob& job = jobs[jobIndex];
|
const CoreEmissionJob& job = jobs[jobIndex];
|
||||||
@@ -1363,6 +1691,8 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
|||||||
return err;
|
return err;
|
||||||
xbarsPerArrayGroup["core" + std::to_string(job.emittedCoreId)] = std::move(xbarsPerGroup);
|
xbarsPerArrayGroup["core" + std::to_string(job.emittedCoreId)] = std::move(xbarsPerGroup);
|
||||||
memory.recordCoreReport(job.emittedCoreId, result.reportRow);
|
memory.recordCoreReport(job.emittedCoreId, result.reportRow);
|
||||||
|
if (livenessReportFile.is_open())
|
||||||
|
*livenessReportOs << "Core " << job.emittedCoreId << ":\n" << result.livenessArtifacts.textReport;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1379,7 +1709,7 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
|||||||
if (auto err = linkCoreWeights(job.emittedCoreId, mapCoreWeightToFileName[job.emittedCoreId], xbarsPerGroup))
|
if (auto err = linkCoreWeights(job.emittedCoreId, mapCoreWeightToFileName[job.emittedCoreId], xbarsPerGroup))
|
||||||
return err;
|
return err;
|
||||||
xbarsPerArrayGroup["core" + std::to_string(job.emittedCoreId)] = std::move(xbarsPerGroup);
|
xbarsPerArrayGroup["core" + std::to_string(job.emittedCoreId)] = std::move(xbarsPerGroup);
|
||||||
reportedCoreIds.push_back(static_cast<int32_t>(job.emittedCoreId));
|
reportedCoreIds.push_back(pim::checkedI32OrCrash(job.emittedCoreId, "batch report core id"));
|
||||||
if (!batchPerCoreRow)
|
if (!batchPerCoreRow)
|
||||||
batchPerCoreRow = result.reportRow;
|
batchPerCoreRow = result.reportRow;
|
||||||
batchRow = addMemoryReportRows(batchRow, result.reportRow);
|
batchRow = addMemoryReportRows(batchRow, result.reportRow);
|
||||||
@@ -1391,10 +1721,18 @@ OnnxMlirCompilerErrorCodes onnx_mlir::compileToPimCode(ModuleOp& moduleOp, std::
|
|||||||
batchPerCoreRow.value_or(MemoryReportRow {}),
|
batchPerCoreRow.value_or(MemoryReportRow {}),
|
||||||
batchRow.numAlloca,
|
batchRow.numAlloca,
|
||||||
batchRow.sizeAlloca);
|
batchRow.sizeAlloca);
|
||||||
|
if (livenessReportFile.is_open())
|
||||||
|
for (size_t jobIndex : group)
|
||||||
|
*livenessReportOs << "Batch " << batchReportId << " core " << jobs[jobIndex].emittedCoreId << ":\n"
|
||||||
|
<< jobResults[jobIndex].livenessArtifacts.textReport;
|
||||||
}
|
}
|
||||||
|
|
||||||
maxCoreId = nextEmittedCoreId == 0 ? 0 : nextEmittedCoreId - 1;
|
maxCoreId = nextEmittedCoreId == 0 ? 0 : nextEmittedCoreId - 1;
|
||||||
|
|
||||||
|
if (livenessReportFile.is_open()) {
|
||||||
|
livenessReportOs->flush();
|
||||||
|
livenessReportFile.close();
|
||||||
|
}
|
||||||
memory.flushReport();
|
memory.flushReport();
|
||||||
return writeConfigJson(funcOp, memory, maxCoreId, std::move(xbarsPerArrayGroup), outputDirPath);
|
return writeConfigJson(funcOp, memory, maxCoreId, std::move(xbarsPerArrayGroup), outputDirPath);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,15 @@
|
|||||||
#include "llvm-project/clang/include/clang/Basic/LLVM.h"
|
#include "llvm-project/clang/include/clang/Basic/LLVM.h"
|
||||||
#include "llvm/ADT/DenseMap.h"
|
#include "llvm/ADT/DenseMap.h"
|
||||||
#include "llvm/ADT/Hashing.h"
|
#include "llvm/ADT/Hashing.h"
|
||||||
|
#include "llvm/ADT/SmallVector.h"
|
||||||
#include "llvm/Support/JSON.h"
|
#include "llvm/Support/JSON.h"
|
||||||
#include "llvm/Support/raw_os_ostream.h"
|
#include "llvm/Support/raw_os_ostream.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "onnx-mlir/Compiler/OMCompilerTypes.h"
|
#include "onnx-mlir/Compiler/OMCompilerTypes.h"
|
||||||
#include "src/Accelerators/PIM/Common/IR/AddressAnalysis.hpp"
|
#include "src/Accelerators/PIM/Common/IR/AddressAnalysis.hpp"
|
||||||
@@ -26,6 +29,16 @@ struct MemEntry {
|
|||||||
size_t size;
|
size_t size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PhysicalSlotInfo {
|
||||||
|
size_t id = 0;
|
||||||
|
size_t address = 0;
|
||||||
|
size_t size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MemoryPlanArtifacts {
|
||||||
|
std::string textReport;
|
||||||
|
};
|
||||||
|
|
||||||
struct MemoryValueKey {
|
struct MemoryValueKey {
|
||||||
mlir::Value value;
|
mlir::Value value;
|
||||||
std::optional<unsigned> lane;
|
std::optional<unsigned> lane;
|
||||||
@@ -45,6 +58,19 @@ struct MemoryReportRow {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class MemoryReportKind {
|
||||||
|
None,
|
||||||
|
Alloca,
|
||||||
|
Global,
|
||||||
|
Input
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PendingMemEntry {
|
||||||
|
MemEntry memEntry;
|
||||||
|
MemoryValueKey key;
|
||||||
|
MemoryReportKind reportKind = MemoryReportKind::None;
|
||||||
|
};
|
||||||
|
|
||||||
struct MemoryReportEntry {
|
struct MemoryReportEntry {
|
||||||
enum class Kind {
|
enum class Kind {
|
||||||
Core,
|
Core,
|
||||||
@@ -60,16 +86,21 @@ struct MemoryReportEntry {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class PimMemory {
|
class PimMemory {
|
||||||
llvm::SmallVector<std::pair<MemEntry, MemoryValueKey>, 32> memEntries;
|
llvm::SmallVector<PendingMemEntry, 32> memEntries;
|
||||||
|
llvm::SmallVector<PhysicalSlotInfo, 32> localPhysicalSlots;
|
||||||
llvm::SmallDenseMap<MemoryValueKey, MemEntry, 32>& globalMemEntriesMap;
|
llvm::SmallDenseMap<MemoryValueKey, MemEntry, 32>& globalMemEntriesMap;
|
||||||
llvm::SmallDenseMap<MemoryValueKey, MemEntry, 32> ownedMemEntriesMap;
|
llvm::SmallDenseMap<MemoryValueKey, MemEntry, 32> ownedMemEntriesMap;
|
||||||
|
MemoryReportRow reportRow;
|
||||||
|
MemoryPlanArtifacts livenessArtifacts;
|
||||||
|
|
||||||
size_t minAlignment = 4;
|
size_t minAlignment = 4;
|
||||||
size_t firstAvailableAddress = 0;
|
size_t firstAvailableAddress = 0;
|
||||||
|
size_t nextPhysicalSlotId = 0;
|
||||||
|
|
||||||
MemEntry* gatherMemEntry(mlir::Value value, std::optional<unsigned> lane = std::nullopt);
|
MemEntry* gatherMemEntry(mlir::Value value, std::optional<unsigned> lane = std::nullopt);
|
||||||
void allocateGatheredMemory();
|
void allocateGatheredMemory();
|
||||||
void allocateMemoryForValue(const MemoryValueKey& key, MemEntry& memEntry);
|
void allocateMemoryForValue(const MemoryValueKey& key, MemEntry& memEntry, MemoryReportKind reportKind);
|
||||||
|
PhysicalSlotInfo allocatePhysicalSlot(size_t slotSize, const MemoryValueKey& key);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PimMemory(llvm::SmallDenseMap<MemoryValueKey, MemEntry, 32>& globalMemEntriesMap)
|
PimMemory(llvm::SmallDenseMap<MemoryValueKey, MemEntry, 32>& globalMemEntriesMap)
|
||||||
@@ -78,6 +109,7 @@ public:
|
|||||||
void allocateHost(mlir::ModuleOp moduleOp, mlir::func::FuncOp funcOp);
|
void allocateHost(mlir::ModuleOp moduleOp, mlir::func::FuncOp funcOp);
|
||||||
void allocateCore(mlir::Operation* op, std::optional<unsigned> lane = std::nullopt);
|
void allocateCore(mlir::Operation* op, std::optional<unsigned> lane = std::nullopt);
|
||||||
MemoryReportRow getReportRow() const;
|
MemoryReportRow getReportRow() const;
|
||||||
|
const MemoryPlanArtifacts& getLivenessArtifacts() const { return livenessArtifacts; }
|
||||||
void remove(mlir::Value val);
|
void remove(mlir::Value val);
|
||||||
|
|
||||||
size_t getFirstAvailableAddress() const { return firstAvailableAddress; }
|
size_t getFirstAvailableAddress() const { return firstAvailableAddress; }
|
||||||
@@ -94,6 +126,7 @@ private:
|
|||||||
std::fstream fileReport;
|
std::fstream fileReport;
|
||||||
std::optional<MemoryReportRow> hostReportRow;
|
std::optional<MemoryReportRow> hostReportRow;
|
||||||
llvm::SmallVector<MemoryReportEntry, 32> reportEntries;
|
llvm::SmallVector<MemoryReportEntry, 32> reportEntries;
|
||||||
|
uint64_t totalWeightBytes = 0;
|
||||||
mutable llvm::DenseMap<mlir::Value, CompiledIndexExpr> compiledIndexExprs;
|
mutable llvm::DenseMap<mlir::Value, CompiledIndexExpr> compiledIndexExprs;
|
||||||
mutable llvm::DenseMap<mlir::Value, CompiledAddressExpr> compiledAddressExprs;
|
mutable llvm::DenseMap<mlir::Value, CompiledAddressExpr> compiledAddressExprs;
|
||||||
|
|
||||||
@@ -118,6 +151,7 @@ public:
|
|||||||
const MemoryReportRow& perCoreRow,
|
const MemoryReportRow& perCoreRow,
|
||||||
uint64_t totalAllocaCount,
|
uint64_t totalAllocaCount,
|
||||||
uint64_t totalAllocaBytes);
|
uint64_t totalAllocaBytes);
|
||||||
|
void setTotalWeightBytes(uint64_t bytes) { totalWeightBytes = bytes; }
|
||||||
void flushReport();
|
void flushReport();
|
||||||
void clean(mlir::Operation* op);
|
void clean(mlir::Operation* op);
|
||||||
};
|
};
|
||||||
@@ -137,6 +171,7 @@ class PimCodeGen {
|
|||||||
const llvm::DenseMap<size_t, size_t>& emittedCoreIds;
|
const llvm::DenseMap<size_t, size_t>& emittedCoreIds;
|
||||||
std::optional<unsigned> batchLane;
|
std::optional<unsigned> batchLane;
|
||||||
mutable uint32_t emittedInstructionCount = 0;
|
mutable uint32_t emittedInstructionCount = 0;
|
||||||
|
mutable std::array<std::optional<int32_t>, 256> scalarRegisterValues = {};
|
||||||
|
|
||||||
size_t addressOf(mlir::Value value, const StaticValueKnowledge& knowledge) const {
|
size_t addressOf(mlir::Value value, const StaticValueKnowledge& knowledge) const {
|
||||||
return memory.getValueAddress(value, knowledge, batchLane);
|
return memory.getValueAddress(value, knowledge, batchLane);
|
||||||
@@ -144,6 +179,7 @@ class PimCodeGen {
|
|||||||
size_t remapCoreId(size_t coreId) const;
|
size_t remapCoreId(size_t coreId) const;
|
||||||
|
|
||||||
void emitInstruction(const pim_binary::InstructionRecord& instruction) const;
|
void emitInstruction(const pim_binary::InstructionRecord& instruction) const;
|
||||||
|
void updateScalarRegisterCache(const pim_binary::InstructionRecord& instruction) const;
|
||||||
|
|
||||||
void genSetRegisterImmediateUnsigned(size_t registerNumber, size_t immediate) const;
|
void genSetRegisterImmediateUnsigned(size_t registerNumber, size_t immediate) const;
|
||||||
void setupRd(size_t rdAddress, size_t rdOffset) const;
|
void setupRd(size_t rdAddress, size_t rdOffset) const;
|
||||||
@@ -175,7 +211,6 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void codeGenLoadOp(pim::PimMemCopyHostToDevOp loadOp, const StaticValueKnowledge& knowledge) const;
|
void codeGenLoadOp(pim::PimMemCopyHostToDevOp loadOp, const StaticValueKnowledge& knowledge) const;
|
||||||
void codeGenLoadBatchOp(pim::PimMemCopyHostToDevBatchOp loadOp, const StaticValueKnowledge& knowledge) const;
|
|
||||||
void codeGenStoreOp(pim::PimMemCopyDevToHostOp storeOp, const StaticValueKnowledge& knowledge) const;
|
void codeGenStoreOp(pim::PimMemCopyDevToHostOp storeOp, const StaticValueKnowledge& knowledge) const;
|
||||||
void codeGenLmvOp(pim::PimMemCopyOp lmvOp, const StaticValueKnowledge& knowledge) const;
|
void codeGenLmvOp(pim::PimMemCopyOp lmvOp, const StaticValueKnowledge& knowledge) const;
|
||||||
|
|
||||||
|
|||||||
@@ -22,22 +22,110 @@ llvm::cl::opt<PimMergeSchedulerType>
|
|||||||
llvm::cl::init(MergeSchedulerPeft),
|
llvm::cl::init(MergeSchedulerPeft),
|
||||||
llvm::cl::cat(OnnxMlirOptions));
|
llvm::cl::cat(OnnxMlirOptions));
|
||||||
|
|
||||||
|
llvm::cl::opt<PimMemoryReportLevel> pimMemoryReport(
|
||||||
|
"pim-memory-report",
|
||||||
|
llvm::cl::desc("Emit a human-readable PIM memory planning report"),
|
||||||
|
llvm::cl::values(clEnumValN(PimMemoryReportNone, "none", "Do not emit any PIM memory planning report")),
|
||||||
|
llvm::cl::values(
|
||||||
|
clEnumValN(PimMemoryReportSummary, "summary", "Emit a concise slot reuse report with key offenders")),
|
||||||
|
llvm::cl::values(clEnumValN(PimMemoryReportFull, "full", "Emit the full detailed PIM memory planning report")),
|
||||||
|
llvm::cl::init(PimMemoryReportNone),
|
||||||
|
llvm::cl::cat(OnnxMlirOptions));
|
||||||
|
|
||||||
|
llvm::cl::opt<PimConvLoweringType> pimConvLowering(
|
||||||
|
"pim-conv-lowering",
|
||||||
|
llvm::cl::desc("Convolution lowering strategy for PIM"),
|
||||||
|
llvm::cl::values(clEnumValN(PimConvLoweringAuto, "auto", "Select the Conv lowering strategy automatically")),
|
||||||
|
llvm::cl::values(clEnumValN(PimConvLoweringLegacy, "legacy", "Use the legacy explicit-im2col Conv lowering")),
|
||||||
|
llvm::cl::values(clEnumValN(PimConvLoweringDepthwise, "depthwise", "Force the depthwise-specialized Conv lowering")),
|
||||||
|
llvm::cl::values(
|
||||||
|
clEnumValN(PimConvLoweringPackedIm2Col, "packed-im2col", "Use explicit im2col with packed multi-position GEMM")),
|
||||||
|
llvm::cl::values(clEnumValN(PimConvLoweringStreamedPatch,
|
||||||
|
"streamed-patch",
|
||||||
|
"Use streamed/chunked im2col rows without multi-position packing")),
|
||||||
|
llvm::cl::values(clEnumValN(PimConvLoweringStreamedPacked,
|
||||||
|
"streamed-packed",
|
||||||
|
"Use streamed/chunked im2col rows with packed multi-position GEMM")),
|
||||||
|
llvm::cl::values(clEnumValN(PimConvLoweringOutputChannelTiled,
|
||||||
|
"output-channel-tiled",
|
||||||
|
"Force Conv lowering that relies on Gemm output-channel tiling")),
|
||||||
|
llvm::cl::values(
|
||||||
|
clEnumValN(PimConvLoweringInputKTiled, "input-k-tiled", "Force Conv lowering that relies on Gemm K tiling")),
|
||||||
|
llvm::cl::values(clEnumValN(PimConvLoweringTiled2D,
|
||||||
|
"tiled-2d",
|
||||||
|
"Force Conv lowering that relies on Gemm 2D K/C tiling")),
|
||||||
|
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 around MergeComputeNodes materialization"),
|
||||||
|
llvm::cl::values(clEnumValN(SpatialDataflowExportNone, "none", "Do not emit Spatial dataflow CSV reports")),
|
||||||
|
llvm::cl::values(clEnumValN(SpatialDataflowExportPre, "pre", "Emit pre-materialization Spatial dataflow CSV reports")),
|
||||||
|
llvm::cl::values(
|
||||||
|
clEnumValN(SpatialDataflowExportPost, "post", "Emit post-materialization Spatial dataflow CSV reports")),
|
||||||
|
llvm::cl::values(
|
||||||
|
clEnumValN(SpatialDataflowExportBoth, "both", "Emit both pre- and post-materialization Spatial dataflow CSV reports")),
|
||||||
|
llvm::cl::init(SpatialDataflowExportNone),
|
||||||
|
llvm::cl::cat(OnnxMlirOptions));
|
||||||
|
|
||||||
llvm::cl::opt<bool>
|
llvm::cl::opt<bool>
|
||||||
pimOnlyCodegen("pim-only-codegen",
|
pimOnlyCodegen("pim-only-codegen",
|
||||||
llvm::cl::desc("Only generate code for PIM (assume input is already in bufferized PIM IR)"),
|
llvm::cl::desc("Only generate code for PIM (assume input is already in bufferized PIM IR)"),
|
||||||
llvm::cl::init(false),
|
llvm::cl::init(false),
|
||||||
llvm::cl::cat(OnnxMlirOptions));
|
llvm::cl::cat(OnnxMlirOptions));
|
||||||
|
|
||||||
|
llvm::cl::opt<bool>
|
||||||
|
pimDisableMemoryCoalescing("pim-disable-memory-coalescing",
|
||||||
|
llvm::cl::desc("Skip the PIM memory coalescing pass (developer diagnostic option)"),
|
||||||
|
llvm::cl::init(false),
|
||||||
|
llvm::cl::cat(OnnxMlirOptions));
|
||||||
|
|
||||||
llvm::cl::opt<bool> useExperimentalConvImpl("use-experimental-conv-impl",
|
llvm::cl::opt<bool> useExperimentalConvImpl("use-experimental-conv-impl",
|
||||||
llvm::cl::desc("Use experimental implementation for convolution"),
|
llvm::cl::desc("Use experimental implementation for convolution"),
|
||||||
llvm::cl::init(false),
|
llvm::cl::init(false),
|
||||||
llvm::cl::cat(OnnxMlirOptions));
|
llvm::cl::cat(OnnxMlirOptions));
|
||||||
|
|
||||||
|
llvm::cl::opt<uint64_t> pimConvIm2colMaxElements(
|
||||||
|
"pim-conv-im2col-max-elements",
|
||||||
|
llvm::cl::desc("Maximum number of im2col elements to materialize globally for one Conv before streaming/chunking"),
|
||||||
|
llvm::cl::init(1ull << 20),
|
||||||
|
llvm::cl::cat(OnnxMlirOptions));
|
||||||
|
|
||||||
|
llvm::cl::opt<uint64_t> pimConvStreamChunkPositions(
|
||||||
|
"pim-conv-stream-chunk-positions",
|
||||||
|
llvm::cl::desc("Maximum number of Conv output positions to materialize in one streamed chunk"),
|
||||||
|
llvm::cl::init(1024),
|
||||||
|
llvm::cl::cat(OnnxMlirOptions));
|
||||||
|
|
||||||
|
llvm::cl::opt<bool> pimReportConvLowering("pim-report-conv-lowering",
|
||||||
|
llvm::cl::desc("Emit a bounded Conv lowering report"),
|
||||||
|
llvm::cl::init(true),
|
||||||
|
llvm::cl::cat(OnnxMlirOptions));
|
||||||
|
|
||||||
llvm::cl::opt<bool> pimEmitJson("pim-emit-json",
|
llvm::cl::opt<bool> pimEmitJson("pim-emit-json",
|
||||||
llvm::cl::desc("Also emit per-core JSON instruction files alongside binary .pim files"),
|
llvm::cl::desc("Also emit per-core JSON instruction files alongside binary .pim files"),
|
||||||
llvm::cl::init(false),
|
llvm::cl::init(false),
|
||||||
llvm::cl::cat(OnnxMlirOptions));
|
llvm::cl::cat(OnnxMlirOptions));
|
||||||
|
|
||||||
|
llvm::cl::opt<bool> pimDetectCommunicationDeadlock(
|
||||||
|
"pim-detect-communication-deadlock",
|
||||||
|
llvm::cl::desc("Expensively simulate the statically expanded PIM send/receive order at verification time and fail if a blocking communication deadlock is found"),
|
||||||
|
llvm::cl::init(false),
|
||||||
|
llvm::cl::cat(OnnxMlirOptions));
|
||||||
|
|
||||||
|
llvm::cl::opt<bool> pimMaterializeScalarFanoutGlobalOrder(
|
||||||
|
"pim-materialize-scalar-fanout-global-order",
|
||||||
|
llvm::cl::desc("Experimental expensive materializer mode: emit scalar-source fanout as globally ordered communication events instead of all-send fanout loops"),
|
||||||
|
llvm::cl::init(false),
|
||||||
|
llvm::cl::cat(OnnxMlirOptions));
|
||||||
|
|
||||||
|
llvm::cl::opt<bool> pimTraceCommunicationMaterialization(
|
||||||
|
"pim-trace-communication-materialization",
|
||||||
|
llvm::cl::desc("Emit verbose materializer-time diagnostics and provenance attributes for every Spatial communication op"),
|
||||||
|
llvm::cl::init(false),
|
||||||
|
llvm::cl::cat(OnnxMlirOptions));
|
||||||
|
|
||||||
llvm::cl::opt<size_t>
|
llvm::cl::opt<size_t>
|
||||||
crossbarSize("crossbar-size", llvm::cl::desc("Width and height of a single crossbar"), llvm::cl::init(2));
|
crossbarSize("crossbar-size", llvm::cl::desc("Width and height of a single crossbar"), llvm::cl::init(2));
|
||||||
|
|
||||||
|
|||||||
@@ -24,17 +24,52 @@ typedef enum {
|
|||||||
MergeSchedulerPeft = 0,
|
MergeSchedulerPeft = 0,
|
||||||
} PimMergeSchedulerType;
|
} PimMergeSchedulerType;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
PimMemoryReportNone = 0,
|
||||||
|
PimMemoryReportSummary = 1,
|
||||||
|
PimMemoryReportFull = 2,
|
||||||
|
} PimMemoryReportLevel;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
PimConvLoweringAuto = 0,
|
||||||
|
PimConvLoweringLegacy = 1,
|
||||||
|
PimConvLoweringDepthwise = 2,
|
||||||
|
PimConvLoweringPackedIm2Col = 3,
|
||||||
|
PimConvLoweringStreamedPatch = 4,
|
||||||
|
PimConvLoweringStreamedPacked = 5,
|
||||||
|
PimConvLoweringOutputChannelTiled = 6,
|
||||||
|
PimConvLoweringInputKTiled = 7,
|
||||||
|
PimConvLoweringTiled2D = 8,
|
||||||
|
} PimConvLoweringType;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SpatialDataflowExportNone = 0,
|
||||||
|
SpatialDataflowExportPre = 1,
|
||||||
|
SpatialDataflowExportPost = 2,
|
||||||
|
SpatialDataflowExportBoth = 3,
|
||||||
|
} PimSpatialDataflowExportType;
|
||||||
|
|
||||||
extern llvm::cl::OptionCategory OnnxMlirOptions;
|
extern llvm::cl::OptionCategory OnnxMlirOptions;
|
||||||
extern llvm::cl::opt<PimEmissionTargetType> pimEmissionTarget;
|
extern llvm::cl::opt<PimEmissionTargetType> pimEmissionTarget;
|
||||||
extern llvm::cl::opt<PimMergeSchedulerType> pimMergeScheduler;
|
extern llvm::cl::opt<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> pimOnlyCodegen;
|
||||||
|
extern llvm::cl::opt<bool> pimDisableMemoryCoalescing;
|
||||||
extern llvm::cl::opt<bool> useExperimentalConvImpl;
|
extern llvm::cl::opt<bool> useExperimentalConvImpl;
|
||||||
extern llvm::cl::opt<bool> pimEmitJson;
|
extern llvm::cl::opt<bool> pimEmitJson;
|
||||||
|
extern llvm::cl::opt<bool> pimReportConvLowering;
|
||||||
|
extern llvm::cl::opt<bool> pimDetectCommunicationDeadlock;
|
||||||
|
extern llvm::cl::opt<bool> pimMaterializeScalarFanoutGlobalOrder;
|
||||||
|
extern llvm::cl::opt<bool> pimTraceCommunicationMaterialization;
|
||||||
|
|
||||||
extern llvm::cl::opt<size_t> crossbarSize;
|
extern llvm::cl::opt<size_t> crossbarSize;
|
||||||
extern llvm::cl::opt<size_t> crossbarCountInCore;
|
extern llvm::cl::opt<size_t> crossbarCountInCore;
|
||||||
extern llvm::cl::opt<long> coresCount;
|
extern llvm::cl::opt<long> coresCount;
|
||||||
|
extern llvm::cl::opt<uint64_t> pimConvIm2colMaxElements;
|
||||||
|
extern llvm::cl::opt<uint64_t> pimConvStreamChunkPositions;
|
||||||
|
|
||||||
bool hasExplicitPimCoreCount();
|
bool hasExplicitPimCoreCount();
|
||||||
void verifyExplicitPimCoreCount();
|
void verifyExplicitPimCoreCount();
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ void addPassesPim(OwningOpRef<ModuleOp>& module,
|
|||||||
|
|
||||||
if (pimEmissionTarget >= EmitSpatial) {
|
if (pimEmissionTarget >= EmitSpatial) {
|
||||||
pm.addPass(createONNXToSpatialPass());
|
pm.addPass(createONNXToSpatialPass());
|
||||||
|
pm.addPass(createSpatialLayoutPlanningPass());
|
||||||
|
pm.addPass(createLowerSpatialPlansPass());
|
||||||
pm.addPass(createMergeComputeNodesPass());
|
pm.addPass(createMergeComputeNodesPass());
|
||||||
pm.addPass(createMessagePass("Onnx lowered to Spatial"));
|
pm.addPass(createMessagePass("Onnx lowered to Spatial"));
|
||||||
}
|
}
|
||||||
@@ -40,14 +42,14 @@ void addPassesPim(OwningOpRef<ModuleOp>& module,
|
|||||||
|
|
||||||
if (pimEmissionTarget >= EmitPimBufferized) {
|
if (pimEmissionTarget >= EmitPimBufferized) {
|
||||||
pm.addPass(createPimBufferizationPass());
|
pm.addPass(createPimBufferizationPass());
|
||||||
pm.addPass(createPimStaticMemoryCoalescingPass());
|
|
||||||
pm.addPass(createMessagePass("Pim bufferized"));
|
pm.addPass(createMessagePass("Pim bufferized"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pimEmissionTarget >= EmitPimCodegen) {
|
if (pimEmissionTarget >= EmitPimCodegen) {
|
||||||
pm.addPass(createPimHostConstantFoldingPass());
|
pm.addPass(createPimHostConstantFoldingPass());
|
||||||
pm.addPass(createMessagePass("Pim host constants folded"));
|
pm.addPass(createMessagePass("Pim host constants folded"));
|
||||||
pm.addPass(createPimMaterializeHostConstantsPass());
|
if (!pimDisableMemoryCoalescing)
|
||||||
|
pm.addPass(createPimMemoryCoalescingPass());
|
||||||
pm.addPass(createPimVerificationPass());
|
pm.addPass(createPimVerificationPass());
|
||||||
pm.addPass(createMessagePass("Pim verified"));
|
pm.addPass(createMessagePass("Pim verified"));
|
||||||
pm.addPass(createEmitPimCodePass());
|
pm.addPass(createEmitPimCodePass());
|
||||||
|
|||||||
@@ -0,0 +1,733 @@
|
|||||||
|
#include "mlir/Dialect/Bufferization/IR/BufferizableOpInterface.h"
|
||||||
|
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
||||||
|
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||||
|
#include "mlir/IR/Value.h"
|
||||||
|
#include "mlir/Interfaces/DestinationStyleOpInterface.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/DenseMap.h"
|
||||||
|
#include "llvm/ADT/STLExtras.h"
|
||||||
|
#include "llvm/ADT/SmallPtrSet.h"
|
||||||
|
#include "llvm/Support/raw_ostream.h"
|
||||||
|
|
||||||
|
#include <numeric>
|
||||||
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "Common/Support/CheckedArithmetic.hpp"
|
||||||
|
#include "Common/Support/ReportUtils.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Compiler/PimMemoryLiveness.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
||||||
|
|
||||||
|
using namespace llvm;
|
||||||
|
using namespace mlir;
|
||||||
|
using namespace onnx_mlir;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
static std::optional<unsigned> getLaneForMemoryValue(mlir::Value value, std::optional<unsigned> lane) {
|
||||||
|
if (!lane)
|
||||||
|
return std::nullopt;
|
||||||
|
auto allocOp = value.getDefiningOp<memref::AllocOp>();
|
||||||
|
if (!allocOp || !allocOp->getParentOfType<pim::PimCoreBatchOp>())
|
||||||
|
return std::nullopt;
|
||||||
|
return lane;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MemoryValueKey getMemoryValueKey(mlir::Value value, std::optional<unsigned> lane = std::nullopt) {
|
||||||
|
return {value, getLaneForMemoryValue(value, lane)};
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MemoryTouchInterval {
|
||||||
|
uint64_t start = 0;
|
||||||
|
uint64_t end = 0;
|
||||||
|
Operation* startOp = nullptr;
|
||||||
|
Operation* endOp = nullptr;
|
||||||
|
Operation* firstTouchOp = nullptr;
|
||||||
|
Operation* lastTouchOp = nullptr;
|
||||||
|
uint64_t firstTouchPosition = 0;
|
||||||
|
uint64_t lastTouchPosition = 0;
|
||||||
|
bool hasRuntimeUse = false;
|
||||||
|
bool startUsedAllocFallback = false;
|
||||||
|
bool endUsedFallback = false;
|
||||||
|
bool escapesLoop = false;
|
||||||
|
std::string fallbackReason;
|
||||||
|
llvm::SmallVector<std::string, 8> aliasesFollowed;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct OperationOrdering {
|
||||||
|
llvm::DenseMap<Operation*, uint64_t> position;
|
||||||
|
llvm::DenseMap<Operation*, uint64_t> subtreeEnd;
|
||||||
|
uint64_t nextPosition = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::string printValueToString(mlir::Value value) {
|
||||||
|
std::string text;
|
||||||
|
llvm::raw_string_ostream os(text);
|
||||||
|
value.print(os);
|
||||||
|
os.flush();
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string printOperationToString(Operation* op) {
|
||||||
|
if (!op)
|
||||||
|
return "<none>";
|
||||||
|
std::string text;
|
||||||
|
llvm::raw_string_ostream os(text);
|
||||||
|
op->print(os);
|
||||||
|
os.flush();
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string printLocationToString(Location loc) {
|
||||||
|
std::string text;
|
||||||
|
llvm::raw_string_ostream os(text);
|
||||||
|
loc.print(os);
|
||||||
|
os.flush();
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string collapseWhitespace(StringRef text) {
|
||||||
|
std::string out;
|
||||||
|
out.reserve(text.size());
|
||||||
|
bool lastWasSpace = false;
|
||||||
|
for (char c : text) {
|
||||||
|
bool isSpace = c == ' ' || c == '\n' || c == '\t' || c == '\r';
|
||||||
|
if (isSpace) {
|
||||||
|
if (!lastWasSpace && !out.empty())
|
||||||
|
out.push_back(' ');
|
||||||
|
lastWasSpace = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
out.push_back(c);
|
||||||
|
lastWasSpace = false;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string abbreviate(StringRef text, size_t maxLen) {
|
||||||
|
if (text.size() <= maxLen)
|
||||||
|
return text.str();
|
||||||
|
return (text.take_front(maxLen - 3) + "...").str();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string summarizeValue(mlir::Value value, size_t maxLen = 72) {
|
||||||
|
return abbreviate(collapseWhitespace(printValueToString(value)), maxLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string summarizeOperation(Operation* op, size_t maxLen = 96) {
|
||||||
|
if (!op)
|
||||||
|
return "<none>";
|
||||||
|
std::string prefix = op->getName().getStringRef().str();
|
||||||
|
std::string full = collapseWhitespace(printOperationToString(op));
|
||||||
|
if (full == prefix)
|
||||||
|
return prefix;
|
||||||
|
return abbreviate(prefix + " :: " + full, maxLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string summarizeLocation(Location loc, size_t maxLen = 88) {
|
||||||
|
return abbreviate(collapseWhitespace(printLocationToString(loc)), maxLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void assignOperationOrdering(Operation* op, OperationOrdering& ordering) {
|
||||||
|
uint64_t position = ordering.nextPosition++;
|
||||||
|
ordering.position[op] = position;
|
||||||
|
uint64_t end = position;
|
||||||
|
for (Region& region : op->getRegions())
|
||||||
|
for (Block& block : region)
|
||||||
|
for (Operation& nestedOp : block) {
|
||||||
|
assignOperationOrdering(&nestedOp, ordering);
|
||||||
|
end = std::max(end, ordering.subtreeEnd.lookup(&nestedOp));
|
||||||
|
}
|
||||||
|
ordering.subtreeEnd[op] = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
static OperationOrdering buildOperationOrdering(Operation* coreLikeOp) {
|
||||||
|
OperationOrdering ordering;
|
||||||
|
if (!coreLikeOp || coreLikeOp->getNumRegions() != 1 || coreLikeOp->getRegion(0).empty())
|
||||||
|
return ordering;
|
||||||
|
|
||||||
|
for (Operation& op : coreLikeOp->getRegion(0).front())
|
||||||
|
assignOperationOrdering(&op, ordering);
|
||||||
|
return ordering;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isSupportedAliasOp(Operation* op) {
|
||||||
|
return isa<memref::SubViewOp, memref::CastOp, memref::CollapseShapeOp, memref::ExpandShapeOp>(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isRuntimeMemoryTouchOp(Operation* op) {
|
||||||
|
return isa<pim::PimMemCopyHostToDevOp,
|
||||||
|
pim::PimMemCopyDevToHostOp,
|
||||||
|
pim::PimMemCopyOp,
|
||||||
|
pim::PimReceiveOp,
|
||||||
|
pim::PimSendOp,
|
||||||
|
pim::PimConcatOp,
|
||||||
|
pim::PimVMMOp,
|
||||||
|
pim::PimTransposeOp,
|
||||||
|
pim::PimVVAddOp,
|
||||||
|
pim::PimVVSubOp,
|
||||||
|
pim::PimVVMulOp,
|
||||||
|
pim::PimVVMaxOp,
|
||||||
|
pim::PimVVDMulOp,
|
||||||
|
pim::PimVAvgOp,
|
||||||
|
pim::PimVReluOp,
|
||||||
|
pim::PimVTanhOp,
|
||||||
|
pim::PimVSigmOp,
|
||||||
|
pim::PimVSoftmaxOp>(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isIgnoredLivenessUser(Operation* op) {
|
||||||
|
return isSupportedAliasOp(op) || isa<scf::ForOp, scf::YieldOp, memref::DeallocOp>(op) || isCoreStaticAddressOp(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isWithin(mlir::Value value, Region* region) {
|
||||||
|
if (!region)
|
||||||
|
return false;
|
||||||
|
if (auto blockArg = dyn_cast<BlockArgument>(value))
|
||||||
|
return blockArg.getOwner()->getParent() == region;
|
||||||
|
if (Operation* definingOp = value.getDefiningOp())
|
||||||
|
return definingOp->getParentRegion() == region || region->isAncestor(definingOp->getParentRegion());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isNestedAllocation(Operation* coreLikeOp, memref::AllocOp allocOp) {
|
||||||
|
if (!coreLikeOp || coreLikeOp->getNumRegions() != 1 || coreLikeOp->getRegion(0).empty())
|
||||||
|
return false;
|
||||||
|
return allocOp->getBlock() != &coreLikeOp->getRegion(0).front();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void addFallbackReason(std::string& reason, StringRef newReason) {
|
||||||
|
if (newReason.empty())
|
||||||
|
return;
|
||||||
|
if (!reason.empty())
|
||||||
|
reason += "; ";
|
||||||
|
reason += newReason.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void appendAliasDescription(llvm::SmallVectorImpl<std::string>& aliases, mlir::Value value) {
|
||||||
|
std::string text = printValueToString(value);
|
||||||
|
if (!llvm::is_contained(aliases, text))
|
||||||
|
aliases.push_back(std::move(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OrderedTouchRange {
|
||||||
|
uint64_t start = 0;
|
||||||
|
uint64_t end = 0;
|
||||||
|
Operation* startOp = nullptr;
|
||||||
|
Operation* endOp = nullptr;
|
||||||
|
bool escapedLoop = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
static OrderedTouchRange
|
||||||
|
getEffectiveTouchRange(mlir::Value definingValue, Operation* user, const OperationOrdering& ordering) {
|
||||||
|
OrderedTouchRange range {ordering.position.lookup(user), ordering.position.lookup(user), user, user, false};
|
||||||
|
for (Operation* current = user; current; current = current->getParentOp()) {
|
||||||
|
auto forOp = dyn_cast<scf::ForOp>(current);
|
||||||
|
if (!forOp || isWithin(definingValue, &forOp.getRegion()))
|
||||||
|
continue;
|
||||||
|
range.start = std::min(range.start, ordering.position.lookup(forOp));
|
||||||
|
range.end = std::max(range.end, ordering.subtreeEnd.lookup(forOp));
|
||||||
|
range.startOp = forOp;
|
||||||
|
range.endOp = forOp;
|
||||||
|
range.escapedLoop = true;
|
||||||
|
}
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MemoryTouchInterval
|
||||||
|
computeMemoryTouchInterval(memref::AllocOp allocOp, const OperationOrdering& ordering, uint64_t fallbackEnd) {
|
||||||
|
MemoryTouchInterval interval;
|
||||||
|
interval.start = ordering.position.lookup(allocOp);
|
||||||
|
interval.end = interval.start;
|
||||||
|
interval.startOp = allocOp;
|
||||||
|
interval.endOp = allocOp;
|
||||||
|
|
||||||
|
SmallPtrSet<mlir::Value, 16> visitedValues;
|
||||||
|
SmallPtrSet<Operation*, 32> visitedUsers;
|
||||||
|
SmallVector<mlir::Value> pendingValues;
|
||||||
|
pendingValues.push_back(allocOp.getResult());
|
||||||
|
auto parentLoop = allocOp->getParentOfType<scf::ForOp>();
|
||||||
|
|
||||||
|
while (!pendingValues.empty()) {
|
||||||
|
mlir::Value value = pendingValues.pop_back_val();
|
||||||
|
if (!visitedValues.insert(value).second)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (Operation* user : value.getUsers()) {
|
||||||
|
if (!visitedUsers.insert(user).second)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (isSupportedAliasOp(user)) {
|
||||||
|
for (mlir::Value result : user->getResults()) {
|
||||||
|
pendingValues.push_back(result);
|
||||||
|
appendAliasDescription(interval.aliasesFollowed, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto dpsOp = dyn_cast<DestinationStyleOpInterface>(user)) {
|
||||||
|
for (OpResult result : user->getResults()) {
|
||||||
|
OpOperand* tiedOperand = dpsOp.getTiedOpOperand(result);
|
||||||
|
if (!tiedOperand || tiedOperand->get() != value)
|
||||||
|
continue;
|
||||||
|
pendingValues.push_back(result);
|
||||||
|
appendAliasDescription(interval.aliasesFollowed, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto forOp = dyn_cast<scf::ForOp>(user)) {
|
||||||
|
for (auto [index, initArg] : llvm::enumerate(forOp.getInitArgs())) {
|
||||||
|
if (initArg != value)
|
||||||
|
continue;
|
||||||
|
pendingValues.push_back(forOp.getRegionIterArgs()[index]);
|
||||||
|
pendingValues.push_back(forOp.getResult(index));
|
||||||
|
appendAliasDescription(interval.aliasesFollowed, forOp.getRegionIterArgs()[index]);
|
||||||
|
appendAliasDescription(interval.aliasesFollowed, forOp.getResult(index));
|
||||||
|
if (parentLoop && forOp != parentLoop)
|
||||||
|
interval.escapesLoop = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto yieldOp = dyn_cast<scf::YieldOp>(user)) {
|
||||||
|
auto forOp = dyn_cast<scf::ForOp>(yieldOp->getParentOp());
|
||||||
|
if (!forOp) {
|
||||||
|
addFallbackReason(interval.fallbackReason, "yield without scf.for parent");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (auto [index, operand] : llvm::enumerate(yieldOp.getOperands())) {
|
||||||
|
if (operand != value)
|
||||||
|
continue;
|
||||||
|
pendingValues.push_back(forOp.getResult(index));
|
||||||
|
appendAliasDescription(interval.aliasesFollowed, forOp.getResult(index));
|
||||||
|
if (parentLoop && forOp == parentLoop)
|
||||||
|
interval.escapesLoop = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRuntimeMemoryTouchOp(user)) {
|
||||||
|
uint64_t touchPosition = ordering.position.lookup(user);
|
||||||
|
if (!interval.hasRuntimeUse || touchPosition < interval.firstTouchPosition) {
|
||||||
|
interval.firstTouchPosition = touchPosition;
|
||||||
|
interval.firstTouchOp = user;
|
||||||
|
}
|
||||||
|
if (!interval.hasRuntimeUse || touchPosition > interval.lastTouchPosition) {
|
||||||
|
interval.lastTouchPosition = touchPosition;
|
||||||
|
interval.lastTouchOp = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
OrderedTouchRange range = getEffectiveTouchRange(allocOp.getResult(), user, ordering);
|
||||||
|
interval.escapesLoop |= range.escapedLoop;
|
||||||
|
if (!interval.hasRuntimeUse) {
|
||||||
|
interval.start = range.start;
|
||||||
|
interval.end = range.end;
|
||||||
|
interval.startOp = range.startOp;
|
||||||
|
interval.endOp = range.endOp;
|
||||||
|
interval.hasRuntimeUse = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (range.start < interval.start) {
|
||||||
|
interval.start = range.start;
|
||||||
|
interval.startOp = range.startOp;
|
||||||
|
}
|
||||||
|
if (range.end > interval.end) {
|
||||||
|
interval.end = range.end;
|
||||||
|
interval.endOp = range.endOp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isIgnoredLivenessUser(user))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
addFallbackReason(interval.fallbackReason, "unhandled user op");
|
||||||
|
interval.endUsedFallback = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!interval.hasRuntimeUse) {
|
||||||
|
interval.startUsedAllocFallback = true;
|
||||||
|
interval.endUsedFallback = true;
|
||||||
|
interval.start = ordering.position.lookup(allocOp);
|
||||||
|
interval.end = fallbackEnd;
|
||||||
|
interval.startOp = allocOp;
|
||||||
|
interval.endOp = allocOp->getParentOp();
|
||||||
|
interval.firstTouchPosition = interval.start;
|
||||||
|
interval.lastTouchPosition = interval.end;
|
||||||
|
addFallbackReason(interval.fallbackReason, "no runtime memory touch");
|
||||||
|
return interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interval.endUsedFallback) {
|
||||||
|
interval.end = std::max(interval.end, fallbackEnd);
|
||||||
|
interval.endOp = allocOp->getParentOp();
|
||||||
|
}
|
||||||
|
|
||||||
|
return interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<size_t> getAllocSizeBytes(memref::AllocOp allocOp) {
|
||||||
|
auto type = dyn_cast<ShapedType>(allocOp.getType());
|
||||||
|
if (!type)
|
||||||
|
return failure();
|
||||||
|
auto checkedBytes = pim::getCheckedShapedTypeSizeInBytes(type, allocOp, "memory allocation byte size");
|
||||||
|
if (failed(checkedBytes))
|
||||||
|
return failure();
|
||||||
|
return pim::checkedSize(*checkedBytes, allocOp, "memory allocation byte size");
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool intervalsOverlap(const LocalAllocInterval& lhs, const LocalAllocInterval& rhs) {
|
||||||
|
return !(lhs.end < rhs.start || rhs.end < lhs.start);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t getSlotLogicalBytes(const PlannedPhysicalSlot& slot, ArrayRef<LocalAllocInterval> intervals) {
|
||||||
|
uint64_t slotLogicalBytes = 0;
|
||||||
|
for (size_t intervalIndex : slot.intervalIndices)
|
||||||
|
slotLogicalBytes += intervals[intervalIndex].size;
|
||||||
|
return slotLogicalBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
SmallVector<LocalAllocInterval, 0> onnx_mlir::buildLocalAllocIntervals(Operation* coreLikeOp,
|
||||||
|
std::optional<unsigned> lane) {
|
||||||
|
SmallVector<LocalAllocInterval, 0> intervals;
|
||||||
|
OperationOrdering ordering = buildOperationOrdering(coreLikeOp);
|
||||||
|
if (ordering.position.empty())
|
||||||
|
return intervals;
|
||||||
|
|
||||||
|
uint64_t fallbackEnd = ordering.nextPosition == 0 ? 0 : ordering.nextPosition - 1;
|
||||||
|
size_t nextIntervalId = 0;
|
||||||
|
coreLikeOp->walk([&](memref::AllocOp allocOp) {
|
||||||
|
auto checkedSize = getAllocSizeBytes(allocOp);
|
||||||
|
if (failed(checkedSize)) {
|
||||||
|
llvm::errs() << "Failed to compute local allocation size for value: ";
|
||||||
|
allocOp.getResult().print(llvm::errs());
|
||||||
|
llvm::errs() << "\n";
|
||||||
|
llvm_unreachable("Failed to compute local allocation size");
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryTouchInterval touchInterval = computeMemoryTouchInterval(allocOp, ordering, fallbackEnd);
|
||||||
|
LocalAllocInterval interval;
|
||||||
|
interval.id = nextIntervalId++;
|
||||||
|
interval.alloc = allocOp;
|
||||||
|
interval.key = getMemoryValueKey(allocOp.getResult(), lane);
|
||||||
|
interval.start = touchInterval.start;
|
||||||
|
interval.end = touchInterval.end;
|
||||||
|
interval.size = *checkedSize;
|
||||||
|
interval.startOp = touchInterval.startOp;
|
||||||
|
interval.endOp = touchInterval.endOp;
|
||||||
|
interval.firstTouchOp = touchInterval.firstTouchOp;
|
||||||
|
interval.lastTouchOp = touchInterval.lastTouchOp;
|
||||||
|
interval.firstTouchPosition = touchInterval.firstTouchPosition;
|
||||||
|
interval.lastTouchPosition = touchInterval.lastTouchPosition;
|
||||||
|
interval.startUsedAllocFallback = touchInterval.startUsedAllocFallback;
|
||||||
|
interval.endUsedFallback = touchInterval.endUsedFallback;
|
||||||
|
interval.hasRuntimeUse = touchInterval.hasRuntimeUse;
|
||||||
|
interval.insideNestedRegion = isNestedAllocation(coreLikeOp, allocOp);
|
||||||
|
interval.escapesLoop = touchInterval.escapesLoop;
|
||||||
|
interval.fallbackReason = std::move(touchInterval.fallbackReason);
|
||||||
|
interval.aliasesFollowed = std::move(touchInterval.aliasesFollowed);
|
||||||
|
intervals.push_back(std::move(interval));
|
||||||
|
});
|
||||||
|
|
||||||
|
return intervals;
|
||||||
|
}
|
||||||
|
|
||||||
|
SmallVector<PlannedPhysicalSlot, 0> onnx_mlir::planPhysicalSlots(MutableArrayRef<LocalAllocInterval> intervals) {
|
||||||
|
SmallVector<PlannedPhysicalSlot, 0> slots;
|
||||||
|
SmallVector<size_t> intervalOrder(intervals.size());
|
||||||
|
std::iota(intervalOrder.begin(), intervalOrder.end(), 0);
|
||||||
|
llvm::stable_sort(intervalOrder, [&](size_t lhsIndex, size_t rhsIndex) {
|
||||||
|
const LocalAllocInterval& lhs = intervals[lhsIndex];
|
||||||
|
const LocalAllocInterval& rhs = intervals[rhsIndex];
|
||||||
|
if (lhs.size != rhs.size)
|
||||||
|
return lhs.size > rhs.size;
|
||||||
|
if (lhs.start != rhs.start)
|
||||||
|
return lhs.start < rhs.start;
|
||||||
|
if (lhs.end != rhs.end)
|
||||||
|
return lhs.end < rhs.end;
|
||||||
|
return lhs.id < rhs.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (size_t intervalIndex : intervalOrder) {
|
||||||
|
LocalAllocInterval& interval = intervals[intervalIndex];
|
||||||
|
PlannedPhysicalSlot* bestSlot = nullptr;
|
||||||
|
auto bestKey = std::tuple<size_t, size_t, size_t, size_t>(std::numeric_limits<size_t>::max(),
|
||||||
|
std::numeric_limits<size_t>::max(),
|
||||||
|
std::numeric_limits<size_t>::max(),
|
||||||
|
std::numeric_limits<size_t>::max());
|
||||||
|
|
||||||
|
for (size_t slotIndex = 0; slotIndex < slots.size(); ++slotIndex) {
|
||||||
|
PlannedPhysicalSlot& slot = slots[slotIndex];
|
||||||
|
bool compatible = true;
|
||||||
|
for (size_t otherIndex : slot.intervalIndices) {
|
||||||
|
if (intervalsOverlap(interval, intervals[otherIndex])) {
|
||||||
|
compatible = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!compatible)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
size_t resultingSize = std::max(slot.requiredSize, interval.size);
|
||||||
|
size_t growth = resultingSize - slot.requiredSize;
|
||||||
|
auto candidateKey =
|
||||||
|
std::tuple<size_t, size_t, size_t, size_t>(growth, resultingSize, slot.intervalIndices.size(), slot.id);
|
||||||
|
if (candidateKey < bestKey) {
|
||||||
|
bestKey = candidateKey;
|
||||||
|
bestSlot = &slot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bestSlot) {
|
||||||
|
slots.push_back({slots.size(), interval.size, interval.size, 0, {intervalIndex}});
|
||||||
|
interval.slotPlanIndex = slots.size() - 1;
|
||||||
|
interval.physicalSlotId = slots.back().id;
|
||||||
|
interval.physicalSlotSize = slots.back().requiredSize;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bestSlot->requiredSize = std::max(bestSlot->requiredSize, interval.size);
|
||||||
|
bestSlot->size = bestSlot->requiredSize;
|
||||||
|
bestSlot->intervalIndices.push_back(intervalIndex);
|
||||||
|
interval.slotPlanIndex = static_cast<size_t>(bestSlot - slots.data());
|
||||||
|
interval.physicalSlotId = bestSlot->id;
|
||||||
|
interval.physicalSlotSize = bestSlot->requiredSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return slots;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryPlanArtifacts onnx_mlir::buildMemoryPlanArtifacts(Operation* coreLikeOp,
|
||||||
|
std::optional<unsigned> lane,
|
||||||
|
ArrayRef<LocalAllocInterval> intervals,
|
||||||
|
ArrayRef<PlannedPhysicalSlot> slots,
|
||||||
|
size_t addressLimit,
|
||||||
|
PimMemoryReportLevel reportLevel) {
|
||||||
|
MemoryPlanArtifacts artifacts;
|
||||||
|
|
||||||
|
uint64_t totalLogicalBytes = 0;
|
||||||
|
uint64_t totalPhysicalBytes = 0;
|
||||||
|
uint64_t fallbackIntervals = 0;
|
||||||
|
uint64_t noRuntimeTouchIntervals = 0;
|
||||||
|
uint64_t reusedAllocations = 0;
|
||||||
|
uint64_t nestedIntervals = 0;
|
||||||
|
uint64_t loopEscapingIntervals = 0;
|
||||||
|
size_t largestLogicalAllocation = 0;
|
||||||
|
size_t largestPhysicalSlot = 0;
|
||||||
|
size_t maximumAssignedAddress = 0;
|
||||||
|
|
||||||
|
for (const LocalAllocInterval& interval : intervals) {
|
||||||
|
totalLogicalBytes += interval.size;
|
||||||
|
largestLogicalAllocation = std::max(largestLogicalAllocation, interval.size);
|
||||||
|
maximumAssignedAddress = std::max(maximumAssignedAddress, interval.assignedAddress + interval.physicalSlotSize);
|
||||||
|
if (interval.startUsedAllocFallback || interval.endUsedFallback)
|
||||||
|
++fallbackIntervals;
|
||||||
|
if (!interval.hasRuntimeUse)
|
||||||
|
++noRuntimeTouchIntervals;
|
||||||
|
if (interval.insideNestedRegion)
|
||||||
|
++nestedIntervals;
|
||||||
|
if (interval.escapesLoop)
|
||||||
|
++loopEscapingIntervals;
|
||||||
|
}
|
||||||
|
for (const PlannedPhysicalSlot& slot : slots) {
|
||||||
|
totalPhysicalBytes += slot.size;
|
||||||
|
largestPhysicalSlot = std::max(largestPhysicalSlot, slot.size);
|
||||||
|
if (slot.intervalIndices.size() > 1)
|
||||||
|
reusedAllocations += slot.intervalIndices.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t savedBytes = totalLogicalBytes >= totalPhysicalBytes ? totalLogicalBytes - totalPhysicalBytes : 0;
|
||||||
|
double savedPercent =
|
||||||
|
totalLogicalBytes == 0 ? 0.0 : 100.0 * static_cast<double>(savedBytes) / static_cast<double>(totalLogicalBytes);
|
||||||
|
|
||||||
|
raw_string_ostream os(artifacts.textReport);
|
||||||
|
os << "=== PIM Memory Liveness Report ===\n";
|
||||||
|
os << "Op: " << coreLikeOp->getName() << "\n";
|
||||||
|
if (lane)
|
||||||
|
os << "Lane: " << *lane << "\n";
|
||||||
|
os << "Summary:\n";
|
||||||
|
os << " logical allocation bytes: " << formatReportMemory(totalLogicalBytes) << " (" << totalLogicalBytes << ")\n";
|
||||||
|
os << " physical allocation bytes: " << formatReportMemory(totalPhysicalBytes) << " (" << totalPhysicalBytes
|
||||||
|
<< ")\n";
|
||||||
|
os << " saved bytes: " << formatReportMemory(savedBytes) << " (" << savedBytes << ")\n";
|
||||||
|
os << " saved percent: " << format("%.2f%%", savedPercent) << "\n";
|
||||||
|
os << " intervals: " << intervals.size() << "\n";
|
||||||
|
os << " physical slots: " << slots.size() << "\n";
|
||||||
|
os << " reused allocations: " << reusedAllocations << "\n";
|
||||||
|
os << " fallback intervals: " << fallbackIntervals << "\n";
|
||||||
|
os << " intervals with no runtime memory touch: " << noRuntimeTouchIntervals << "\n";
|
||||||
|
os << " nested allocations: " << nestedIntervals << "\n";
|
||||||
|
os << " loop-escaping allocations: " << loopEscapingIntervals << "\n";
|
||||||
|
os << " largest logical allocation: " << largestLogicalAllocation << "\n";
|
||||||
|
os << " largest physical slot: " << largestPhysicalSlot << "\n";
|
||||||
|
os << " address limit: " << addressLimit << "\n";
|
||||||
|
os << " peak physical memory: " << formatReportMemory(maximumAssignedAddress) << " (" << maximumAssignedAddress
|
||||||
|
<< ")\n";
|
||||||
|
os << " maximum assigned address: " << maximumAssignedAddress << "\n";
|
||||||
|
|
||||||
|
os << "\nHow To Read:\n";
|
||||||
|
os << " `summary` only shows the strongest reuse cases and the worst offenders.\n";
|
||||||
|
os << " Use `--pim-memory-report=full` when you need the complete slot-by-slot and interval-by-interval dump.\n";
|
||||||
|
os << " Large single-use slots, fallback intervals, and nested single-use allocations are the best places\n";
|
||||||
|
os << " to inspect if allocations should be moved, sunk, or made easier to coalesce earlier in the pipeline.\n";
|
||||||
|
|
||||||
|
SmallVector<const PlannedPhysicalSlot*> reusedSlots;
|
||||||
|
SmallVector<const PlannedPhysicalSlot*> singleUseSlots;
|
||||||
|
for (const PlannedPhysicalSlot& slot : slots)
|
||||||
|
if (slot.intervalIndices.size() > 1)
|
||||||
|
reusedSlots.push_back(&slot);
|
||||||
|
else
|
||||||
|
singleUseSlots.push_back(&slot);
|
||||||
|
|
||||||
|
llvm::stable_sort(reusedSlots, [&](const PlannedPhysicalSlot* lhs, const PlannedPhysicalSlot* rhs) {
|
||||||
|
uint64_t lhsLogicalBytes = getSlotLogicalBytes(*lhs, intervals);
|
||||||
|
uint64_t rhsLogicalBytes = getSlotLogicalBytes(*rhs, intervals);
|
||||||
|
if (lhs->intervalIndices.size() != rhs->intervalIndices.size())
|
||||||
|
return lhs->intervalIndices.size() > rhs->intervalIndices.size();
|
||||||
|
if (lhsLogicalBytes != rhsLogicalBytes)
|
||||||
|
return lhsLogicalBytes > rhsLogicalBytes;
|
||||||
|
if (lhs->size != rhs->size)
|
||||||
|
return lhs->size > rhs->size;
|
||||||
|
return lhs->id < rhs->id;
|
||||||
|
});
|
||||||
|
llvm::stable_sort(singleUseSlots, [&](const PlannedPhysicalSlot* lhs, const PlannedPhysicalSlot* rhs) {
|
||||||
|
if (lhs->size != rhs->size)
|
||||||
|
return lhs->size > rhs->size;
|
||||||
|
return lhs->id < rhs->id;
|
||||||
|
});
|
||||||
|
|
||||||
|
constexpr size_t kSummaryReuseLimit = 6;
|
||||||
|
constexpr size_t kSummaryOffenderLimit = 10;
|
||||||
|
|
||||||
|
os << "\nBest Reuse:\n";
|
||||||
|
if (reusedSlots.empty()) {
|
||||||
|
os << " no slots were shared by multiple intervals\n";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (const PlannedPhysicalSlot* slot : ArrayRef(reusedSlots).take_front(kSummaryReuseLimit)) {
|
||||||
|
uint64_t slotLogicalBytes = getSlotLogicalBytes(*slot, intervals);
|
||||||
|
os << " slot #" << slot->id << " addr=" << slot->address << " size=" << formatReportMemory(slot->size)
|
||||||
|
<< " intervals=" << slot->intervalIndices.size() << " logical_sum=" << formatReportMemory(slotLogicalBytes)
|
||||||
|
<< "\n";
|
||||||
|
for (size_t intervalIndex : slot->intervalIndices) {
|
||||||
|
const LocalAllocInterval& interval = intervals[intervalIndex];
|
||||||
|
os << " #" << interval.id << " [" << interval.start << "," << interval.end << "]"
|
||||||
|
<< " logical=" << formatReportMemory(interval.size)
|
||||||
|
<< " first=" << summarizeOperation(interval.firstTouchOp, 40)
|
||||||
|
<< " last=" << summarizeOperation(interval.lastTouchOp, 40) << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
os << "\nTop Offenders:\n";
|
||||||
|
bool printedAttention = false;
|
||||||
|
for (const PlannedPhysicalSlot* slot : ArrayRef(singleUseSlots).take_front(kSummaryOffenderLimit)) {
|
||||||
|
const LocalAllocInterval& interval = intervals[slot->intervalIndices.front()];
|
||||||
|
printedAttention = true;
|
||||||
|
os << " slot #" << slot->id << " is single-use"
|
||||||
|
<< " size=" << formatReportMemory(slot->size) << " interval=#" << interval.id
|
||||||
|
<< " value=" << summarizeValue(interval.key.value, 56) << "\n";
|
||||||
|
os << " first=" << summarizeOperation(interval.firstTouchOp, 40)
|
||||||
|
<< " last=" << summarizeOperation(interval.lastTouchOp, 40)
|
||||||
|
<< " nested=" << (interval.insideNestedRegion ? "yes" : "no")
|
||||||
|
<< " escapes_loop=" << (interval.escapesLoop ? "yes" : "no") << "\n";
|
||||||
|
}
|
||||||
|
size_t fallbackPrinted = 0;
|
||||||
|
for (const LocalAllocInterval& interval : intervals) {
|
||||||
|
if (!(interval.startUsedAllocFallback || interval.endUsedFallback) || fallbackPrinted >= kSummaryOffenderLimit)
|
||||||
|
continue;
|
||||||
|
printedAttention = true;
|
||||||
|
++fallbackPrinted;
|
||||||
|
os << " fallback interval #" << interval.id << " size=" << formatReportMemory(interval.size)
|
||||||
|
<< " value=" << summarizeValue(interval.key.value, 56) << "\n";
|
||||||
|
os << " reason: " << (interval.fallbackReason.empty() ? "<none>" : interval.fallbackReason) << "\n";
|
||||||
|
}
|
||||||
|
size_t nestedPrinted = 0;
|
||||||
|
for (const LocalAllocInterval& interval : intervals) {
|
||||||
|
if (nestedPrinted >= kSummaryOffenderLimit)
|
||||||
|
break;
|
||||||
|
if (!(interval.insideNestedRegion && slots[interval.slotPlanIndex].intervalIndices.size() == 1))
|
||||||
|
continue;
|
||||||
|
printedAttention = true;
|
||||||
|
++nestedPrinted;
|
||||||
|
os << " nested single-use interval #" << interval.id << " slot #" << interval.physicalSlotId
|
||||||
|
<< " size=" << formatReportMemory(interval.size) << " value=" << summarizeValue(interval.key.value, 56)
|
||||||
|
<< "\n";
|
||||||
|
os << " hint: move or sink this alloc inside the nested region if the IR allows it.\n";
|
||||||
|
}
|
||||||
|
if (!printedAttention)
|
||||||
|
os << " no obvious blockers detected in this core\n";
|
||||||
|
|
||||||
|
if (reportLevel == PimMemoryReportFull) {
|
||||||
|
os << "\nSlot Reuse:\n";
|
||||||
|
for (const PlannedPhysicalSlot& slot : slots) {
|
||||||
|
uint64_t slotLogicalBytes = getSlotLogicalBytes(slot, intervals);
|
||||||
|
os << " slot #" << slot.id << " addr=" << slot.address << " size=" << formatReportMemory(slot.size) << " ("
|
||||||
|
<< slot.size << ")"
|
||||||
|
<< " intervals=" << slot.intervalIndices.size() << " logical_sum=" << formatReportMemory(slotLogicalBytes)
|
||||||
|
<< "\n";
|
||||||
|
for (size_t intervalIndex : slot.intervalIndices) {
|
||||||
|
const LocalAllocInterval& interval = intervals[intervalIndex];
|
||||||
|
mlir::Value allocValue = interval.key.value;
|
||||||
|
os << " [" << interval.start << "," << interval.end << "]"
|
||||||
|
<< " #" << interval.id << " logical=" << formatReportMemory(interval.size)
|
||||||
|
<< " nested=" << (interval.insideNestedRegion ? "yes" : "no")
|
||||||
|
<< " escapes_loop=" << (interval.escapesLoop ? "yes" : "no")
|
||||||
|
<< " first=" << summarizeOperation(interval.firstTouchOp, 48)
|
||||||
|
<< " last=" << summarizeOperation(interval.lastTouchOp, 48) << "\n";
|
||||||
|
os << " value=" << summarizeValue(allocValue) << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reportLevel == PimMemoryReportFull) {
|
||||||
|
os << "\nInterval Details:\n";
|
||||||
|
for (const LocalAllocInterval& interval : intervals) {
|
||||||
|
const PlannedPhysicalSlot& slot = slots[interval.slotPlanIndex];
|
||||||
|
mlir::Value allocValue = interval.key.value;
|
||||||
|
Operation* definingOp = allocValue.getDefiningOp();
|
||||||
|
os << " #" << interval.id << " slot=" << slot.id << " live=[" << interval.start << "," << interval.end << "]"
|
||||||
|
<< " logical=" << formatReportMemory(interval.size)
|
||||||
|
<< " slot_size=" << formatReportMemory(interval.physicalSlotSize) << " addr=" << interval.assignedAddress
|
||||||
|
<< "\n";
|
||||||
|
os << " value=" << summarizeValue(allocValue, 88) << "\n";
|
||||||
|
os << " type=" << allocValue.getType() << "\n";
|
||||||
|
os << " loc="
|
||||||
|
<< summarizeLocation(definingOp ? definingOp->getLoc() : UnknownLoc::get(coreLikeOp->getContext())) << "\n";
|
||||||
|
os << " nested=" << (interval.insideNestedRegion ? "yes" : "no")
|
||||||
|
<< " escapes_loop=" << (interval.escapesLoop ? "yes" : "no")
|
||||||
|
<< " start_fallback=" << (interval.startUsedAllocFallback ? "yes" : "no")
|
||||||
|
<< " end_fallback=" << (interval.endUsedFallback ? "yes" : "no") << "\n";
|
||||||
|
os << " first_use=" << summarizeOperation(interval.firstTouchOp) << " @" << interval.firstTouchPosition
|
||||||
|
<< "\n";
|
||||||
|
os << " last_use=" << summarizeOperation(interval.lastTouchOp) << " @" << interval.lastTouchPosition << "\n";
|
||||||
|
os << " slot_peers=";
|
||||||
|
bool first = true;
|
||||||
|
for (size_t otherIndex : slot.intervalIndices) {
|
||||||
|
if (intervals[otherIndex].id == interval.id)
|
||||||
|
continue;
|
||||||
|
if (!first)
|
||||||
|
os << ", ";
|
||||||
|
os << "#" << intervals[otherIndex].id;
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
if (first)
|
||||||
|
os << "<none>";
|
||||||
|
os << "\n";
|
||||||
|
if (!interval.fallbackReason.empty())
|
||||||
|
os << " fallback_reason=" << interval.fallbackReason << "\n";
|
||||||
|
if (!interval.aliasesFollowed.empty()) {
|
||||||
|
os << " aliases_followed=" << interval.aliasesFollowed.size() << "\n";
|
||||||
|
for (const std::string& alias : interval.aliasesFollowed)
|
||||||
|
os << " - " << abbreviate(collapseWhitespace(alias), 108) << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
os.flush();
|
||||||
|
|
||||||
|
return artifacts;
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/ArrayRef.h"
|
||||||
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "src/Accelerators/PIM/Compiler/PimCodeGen.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
||||||
|
|
||||||
|
namespace onnx_mlir {
|
||||||
|
|
||||||
|
struct LocalAllocInterval {
|
||||||
|
size_t id = 0;
|
||||||
|
mlir::memref::AllocOp alloc;
|
||||||
|
MemoryValueKey key;
|
||||||
|
uint64_t start = 0;
|
||||||
|
uint64_t end = 0;
|
||||||
|
size_t size = 0;
|
||||||
|
mlir::Operation* startOp = nullptr;
|
||||||
|
mlir::Operation* endOp = nullptr;
|
||||||
|
mlir::Operation* firstTouchOp = nullptr;
|
||||||
|
mlir::Operation* lastTouchOp = nullptr;
|
||||||
|
uint64_t firstTouchPosition = 0;
|
||||||
|
uint64_t lastTouchPosition = 0;
|
||||||
|
bool startUsedAllocFallback = false;
|
||||||
|
bool endUsedFallback = false;
|
||||||
|
bool hasRuntimeUse = false;
|
||||||
|
bool insideNestedRegion = false;
|
||||||
|
bool escapesLoop = false;
|
||||||
|
std::string fallbackReason;
|
||||||
|
llvm::SmallVector<std::string, 8> aliasesFollowed;
|
||||||
|
size_t slotPlanIndex = std::numeric_limits<size_t>::max();
|
||||||
|
size_t physicalSlotId = std::numeric_limits<size_t>::max();
|
||||||
|
size_t assignedAddress = 0;
|
||||||
|
size_t physicalSlotSize = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PlannedPhysicalSlot {
|
||||||
|
size_t id = std::numeric_limits<size_t>::max();
|
||||||
|
size_t requiredSize = 0;
|
||||||
|
size_t size = 0;
|
||||||
|
size_t address = 0;
|
||||||
|
llvm::SmallVector<size_t, 8> intervalIndices;
|
||||||
|
};
|
||||||
|
|
||||||
|
llvm::SmallVector<LocalAllocInterval, 0> buildLocalAllocIntervals(mlir::Operation* coreLikeOp,
|
||||||
|
std::optional<unsigned> lane);
|
||||||
|
|
||||||
|
llvm::SmallVector<PlannedPhysicalSlot, 0> planPhysicalSlots(llvm::MutableArrayRef<LocalAllocInterval> intervals);
|
||||||
|
|
||||||
|
MemoryPlanArtifacts buildMemoryPlanArtifacts(mlir::Operation* coreLikeOp,
|
||||||
|
std::optional<unsigned> lane,
|
||||||
|
llvm::ArrayRef<LocalAllocInterval> intervals,
|
||||||
|
llvm::ArrayRef<PlannedPhysicalSlot> slots,
|
||||||
|
size_t addressLimit,
|
||||||
|
PimMemoryReportLevel reportLevel);
|
||||||
|
|
||||||
|
} // namespace onnx_mlir
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
|
#include "Common/Support/CheckedArithmetic.hpp"
|
||||||
#include "Conversion/ONNXToSpatial/Common/Common.hpp"
|
#include "Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
||||||
@@ -18,15 +19,14 @@ using namespace mlir;
|
|||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
namespace {} // namespace
|
namespace {} // namespace
|
||||||
|
|
||||||
llvm::DenseMap<size_t, llvm::SmallVector<std::string, 8>>
|
WeightEmissionResult createAndPopulateWeightFolder(ArrayRef<WeightFileRequest> requests, StringRef outputDirPath) {
|
||||||
createAndPopulateWeightFolder(ArrayRef<WeightFileRequest> requests, StringRef outputDirPath) {
|
|
||||||
auto coreWeightsDirPath = outputDirPath + "/weights";
|
auto coreWeightsDirPath = outputDirPath + "/weights";
|
||||||
auto error = sys::fs::create_directory(coreWeightsDirPath);
|
auto error = sys::fs::create_directory(coreWeightsDirPath);
|
||||||
assert(!error && "Error creating weights directory");
|
assert(!error && "Error creating weights directory");
|
||||||
size_t indexFileName = 0;
|
size_t indexFileName = 0;
|
||||||
|
|
||||||
int64_t xbarSize = crossbarSize.getValue();
|
int64_t xbarSize = crossbarSize.getValue();
|
||||||
llvm::DenseMap<size_t, llvm::SmallVector<std::string, 8>> mapCoreWeightToFileName;
|
WeightEmissionResult result;
|
||||||
llvm::SmallVector<std::pair<ResolvedWeightView, std::string>, 16> materializedWeights;
|
llvm::SmallVector<std::pair<ResolvedWeightView, std::string>, 16> materializedWeights;
|
||||||
|
|
||||||
auto materializeWeight = [&](const ResolvedWeightView& weightView) -> std::string {
|
auto materializeWeight = [&](const ResolvedWeightView& weightView) -> std::string {
|
||||||
@@ -72,17 +72,22 @@ createAndPopulateWeightFolder(ArrayRef<WeightFileRequest> requests, StringRef ou
|
|||||||
|
|
||||||
weightFileStream.close();
|
weightFileStream.close();
|
||||||
materializedWeights.push_back({weightView, newFileName});
|
materializedWeights.push_back({weightView, newFileName});
|
||||||
|
uint64_t weightBytes = pim::checkedMulOrCrash(
|
||||||
|
pim::checkedMulOrCrash(static_cast<size_t>(xbarSize), static_cast<size_t>(xbarSize), "weight element count"),
|
||||||
|
elementByteWidth,
|
||||||
|
"weight byte size");
|
||||||
|
result.totalWeightBytes = pim::checkedAddOrCrash(result.totalWeightBytes, weightBytes, "total weight bytes");
|
||||||
return newFileName;
|
return newFileName;
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const WeightFileRequest& request : requests) {
|
for (const WeightFileRequest& request : requests) {
|
||||||
auto& coreFiles = mapCoreWeightToFileName[request.coreId];
|
auto& coreFiles = result.mapCoreWeightToFileName[request.coreId];
|
||||||
coreFiles.reserve(request.weights.size());
|
coreFiles.reserve(request.weights.size());
|
||||||
for (const ResolvedWeightView& weight : request.weights)
|
for (const ResolvedWeightView& weight : request.weights)
|
||||||
coreFiles.push_back(materializeWeight(weight));
|
coreFiles.push_back(materializeWeight(weight));
|
||||||
}
|
}
|
||||||
|
|
||||||
return mapCoreWeightToFileName;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "llvm/ADT/SmallVector.h"
|
#include "llvm/ADT/SmallVector.h"
|
||||||
#include "llvm/ADT/StringRef.h"
|
#include "llvm/ADT/StringRef.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "src/Accelerators/PIM/Common/IR/WeightUtils.hpp"
|
#include "src/Accelerators/PIM/Common/IR/WeightUtils.hpp"
|
||||||
@@ -17,7 +18,12 @@ struct WeightFileRequest {
|
|||||||
llvm::SmallVector<ResolvedWeightView, 8> weights;
|
llvm::SmallVector<ResolvedWeightView, 8> weights;
|
||||||
};
|
};
|
||||||
|
|
||||||
llvm::DenseMap<size_t, llvm::SmallVector<std::string, 8>>
|
struct WeightEmissionResult {
|
||||||
createAndPopulateWeightFolder(llvm::ArrayRef<WeightFileRequest> requests, llvm::StringRef outputDirPath);
|
llvm::DenseMap<size_t, llvm::SmallVector<std::string, 8>> mapCoreWeightToFileName;
|
||||||
|
uint64_t totalWeightBytes = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
WeightEmissionResult createAndPopulateWeightFolder(llvm::ArrayRef<WeightFileRequest> requests,
|
||||||
|
llvm::StringRef outputDirPath);
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
add_subdirectory(ONNXToSpatial)
|
add_subdirectory(ONNXToSpatial)
|
||||||
add_subdirectory(SpatialToGraphviz)
|
add_subdirectory(SpatialToPim)
|
||||||
add_subdirectory(SpatialToPim)
|
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ mlir_tablegen(ONNXToSpatial.hpp.inc -gen-rewriters "-I${ONNX_MLIR_SRC_ROOT}")
|
|||||||
add_public_tablegen_target(ONNXToSpatialIncGen)
|
add_public_tablegen_target(ONNXToSpatialIncGen)
|
||||||
|
|
||||||
add_pim_library(OMONNXToSpatial
|
add_pim_library(OMONNXToSpatial
|
||||||
ConversionPatterns.cpp
|
Patterns.cpp
|
||||||
CompileTime.cpp
|
CompileTime.cpp
|
||||||
ONNXToSpatialVerifier.cpp
|
ONNXToSpatialVerifier.cpp
|
||||||
PrePatterns.cpp
|
Patterns/Pre.cpp
|
||||||
PostPatterns.cpp
|
Patterns/Post.cpp
|
||||||
|
Patterns/GeneratedConversion.cpp
|
||||||
Patterns/Math/Conv.cpp
|
Patterns/Math/Conv.cpp
|
||||||
|
Patterns/Math/ConvGeometry.cpp
|
||||||
Patterns/Math/Elementwise.cpp
|
Patterns/Math/Elementwise.cpp
|
||||||
Patterns/Math/Gemm.cpp
|
Patterns/Math/Gemm.cpp
|
||||||
Patterns/Math/MatMul.cpp
|
Patterns/Math/MatMul.cpp
|
||||||
@@ -18,12 +20,21 @@ add_pim_library(OMONNXToSpatial
|
|||||||
Patterns/NN/Sigmoid.cpp
|
Patterns/NN/Sigmoid.cpp
|
||||||
Patterns/NN/Softmax.cpp
|
Patterns/NN/Softmax.cpp
|
||||||
Patterns/Tensor/Concat.cpp
|
Patterns/Tensor/Concat.cpp
|
||||||
|
Patterns/Tensor/Flatten.cpp
|
||||||
Patterns/Tensor/Gather.cpp
|
Patterns/Tensor/Gather.cpp
|
||||||
Patterns/Tensor/Resize.cpp
|
Patterns/Tensor/Resize.cpp
|
||||||
Patterns/Tensor/Reshape.cpp
|
Patterns/Tensor/Reshape.cpp
|
||||||
|
Patterns/Tensor/Slice.cpp
|
||||||
Patterns/Tensor/Split.cpp
|
Patterns/Tensor/Split.cpp
|
||||||
|
Patterns/Tensor/Transpose.cpp
|
||||||
ONNXToSpatialPass.cpp
|
ONNXToSpatialPass.cpp
|
||||||
|
SpatialLayoutPlanningPass.cpp
|
||||||
|
LowerSpatialPlansPass.cpp
|
||||||
|
Common/AttributeUtils.cpp
|
||||||
|
Common/BiasAddUtils.cpp
|
||||||
Common/ComputeRegionBuilder.cpp
|
Common/ComputeRegionBuilder.cpp
|
||||||
|
Common/MatrixProductLowering.cpp
|
||||||
|
Common/RowStripLayoutUtils.cpp
|
||||||
Common/ShapeTilingUtils.cpp
|
Common/ShapeTilingUtils.cpp
|
||||||
Common/WeightMaterialization.cpp
|
Common/WeightMaterialization.cpp
|
||||||
|
|
||||||
@@ -33,6 +44,7 @@ add_pim_library(OMONNXToSpatial
|
|||||||
ONNXToSpatialIncGen
|
ONNXToSpatialIncGen
|
||||||
|
|
||||||
LINK_LIBS PUBLIC
|
LINK_LIBS PUBLIC
|
||||||
|
MLIRLinalgDialect
|
||||||
MLIRSCFDialect
|
MLIRSCFDialect
|
||||||
MLIRTosaDialect
|
MLIRTosaDialect
|
||||||
OMCompilerOptions
|
OMCompilerOptions
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#include "mlir/IR/BuiltinAttributes.h"
|
||||||
|
|
||||||
|
#include "AttributeUtils.hpp"
|
||||||
|
|
||||||
|
using namespace mlir;
|
||||||
|
|
||||||
|
namespace onnx_mlir {
|
||||||
|
|
||||||
|
int64_t getI64Attr(ArrayAttr attr, size_t index) { return cast<IntegerAttr>(attr[index]).getInt(); }
|
||||||
|
|
||||||
|
int64_t getOptionalI64Attr(std::optional<ArrayAttr> attr, size_t index, int64_t defaultValue) {
|
||||||
|
return attr ? getI64Attr(*attr, index) : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
llvm::SmallVector<int64_t> getI64ArrayAttrValues(ArrayAttr attr) {
|
||||||
|
llvm::SmallVector<int64_t> values;
|
||||||
|
values.reserve(attr.size());
|
||||||
|
for (Attribute value : attr)
|
||||||
|
values.push_back(cast<IntegerAttr>(value).getInt());
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace onnx_mlir
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "mlir/IR/BuiltinAttributes.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace onnx_mlir {
|
||||||
|
|
||||||
|
int64_t getI64Attr(mlir::ArrayAttr attr, size_t index);
|
||||||
|
|
||||||
|
int64_t getOptionalI64Attr(std::optional<mlir::ArrayAttr> attr, size_t index, int64_t defaultValue);
|
||||||
|
|
||||||
|
llvm::SmallVector<int64_t> getI64ArrayAttrValues(mlir::ArrayAttr attr);
|
||||||
|
|
||||||
|
} // namespace onnx_mlir
|
||||||
@@ -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
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "AttributeUtils.hpp"
|
||||||
#include "ComputeRegionBuilder.hpp"
|
#include "ComputeRegionBuilder.hpp"
|
||||||
|
#include "MatrixProductLowering.hpp"
|
||||||
#include "ShapeTilingUtils.hpp"
|
#include "ShapeTilingUtils.hpp"
|
||||||
#include "WeightMaterialization.hpp"
|
#include "WeightMaterialization.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/TensorSliceUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ using namespace mlir;
|
|||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
|
|
||||||
Value sumTensors(ArrayRef<Value> tensors, ConversionPatternRewriter& rewriter) {
|
Value sumTensors(ArrayRef<Value> tensors, PatternRewriter& rewriter) {
|
||||||
if (tensors.size() == 1)
|
if (tensors.size() == 1)
|
||||||
return tensors[0];
|
return tensors[0];
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
#include "mlir/IR/Block.h"
|
#include "mlir/IR/Block.h"
|
||||||
#include "mlir/IR/BuiltinTypes.h"
|
#include "mlir/IR/BuiltinTypes.h"
|
||||||
#include "mlir/IR/ValueRange.h"
|
#include "mlir/IR/ValueRange.h"
|
||||||
@@ -7,9 +8,12 @@
|
|||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <limits>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
@@ -49,6 +53,63 @@ using InvokeWithBlockArgsResultT = typename InvokeWithBlockArgsResult<Fn, Seq>::
|
|||||||
template <typename Fn>
|
template <typename Fn>
|
||||||
using InvokeWithValueRangeResultT = std::invoke_result_t<Fn, mlir::ValueRange>;
|
using InvokeWithValueRangeResultT = std::invoke_result_t<Fn, mlir::ValueRange>;
|
||||||
|
|
||||||
|
struct SpatComputeBatchBodyArgs {
|
||||||
|
mlir::Value lane;
|
||||||
|
mlir::ValueRange weights;
|
||||||
|
mlir::ValueRange inputs;
|
||||||
|
mlir::ValueRange outputs;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline mlir::SmallVector<mlir::Type> getGraphComputeBlockArgTypes(mlir::ValueRange weights, mlir::ValueRange inputs) {
|
||||||
|
mlir::SmallVector<mlir::Type> blockArgTypes;
|
||||||
|
blockArgTypes.reserve(weights.size() + inputs.size());
|
||||||
|
for (mlir::Value weight : weights)
|
||||||
|
blockArgTypes.push_back(weight.getType());
|
||||||
|
for (mlir::Value input : inputs)
|
||||||
|
blockArgTypes.push_back(input.getType());
|
||||||
|
return blockArgTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline mlir::SmallVector<mlir::Location> getGraphComputeBlockArgLocs(mlir::Location defaultLoc,
|
||||||
|
mlir::ValueRange weights,
|
||||||
|
mlir::ValueRange inputs) {
|
||||||
|
mlir::SmallVector<mlir::Location> blockArgLocs;
|
||||||
|
blockArgLocs.reserve(weights.size() + inputs.size());
|
||||||
|
for (mlir::Value weight : weights)
|
||||||
|
blockArgLocs.push_back(weight.getLoc());
|
||||||
|
for (mlir::Value input : inputs)
|
||||||
|
blockArgLocs.push_back(input.getLoc());
|
||||||
|
return blockArgLocs;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline mlir::SmallVector<mlir::Type> getGraphComputeBatchBlockArgTypes(mlir::OpBuilder& builder,
|
||||||
|
mlir::TypeRange resultTypes,
|
||||||
|
mlir::ValueRange weights,
|
||||||
|
mlir::ValueRange inputs) {
|
||||||
|
mlir::SmallVector<mlir::Type> blockArgTypes {builder.getIndexType()};
|
||||||
|
blockArgTypes.reserve(1 + weights.size() + inputs.size() + resultTypes.size());
|
||||||
|
for (mlir::Value weight : weights)
|
||||||
|
blockArgTypes.push_back(weight.getType());
|
||||||
|
for (mlir::Value input : inputs)
|
||||||
|
blockArgTypes.push_back(input.getType());
|
||||||
|
llvm::append_range(blockArgTypes, resultTypes);
|
||||||
|
return blockArgTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline mlir::SmallVector<mlir::Location> getGraphComputeBatchBlockArgLocs(mlir::Location defaultLoc,
|
||||||
|
mlir::TypeRange resultTypes,
|
||||||
|
mlir::ValueRange weights,
|
||||||
|
mlir::ValueRange inputs) {
|
||||||
|
mlir::SmallVector<mlir::Location> blockArgLocs {defaultLoc};
|
||||||
|
blockArgLocs.reserve(1 + weights.size() + inputs.size() + resultTypes.size());
|
||||||
|
for (mlir::Value weight : weights)
|
||||||
|
blockArgLocs.push_back(weight.getLoc());
|
||||||
|
for (mlir::Value input : inputs)
|
||||||
|
blockArgLocs.push_back(input.getLoc());
|
||||||
|
blockArgLocs.append(resultTypes.size(), defaultLoc);
|
||||||
|
return blockArgLocs;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
template <typename RewriterT>
|
template <typename RewriterT>
|
||||||
@@ -76,26 +137,43 @@ inline mlir::Value createSpatConcat(RewriterT& rewriter, mlir::Location loc, int
|
|||||||
return spatial::SpatConcatOp::create(rewriter, loc, outputType, rewriter.getI64IntegerAttr(axis), inputs).getOutput();
|
return spatial::SpatConcatOp::create(rewriter, loc, outputType, rewriter.getI64IntegerAttr(axis), inputs).getOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a `spat.compute` with a fixed number of SSA inputs and erases it if
|
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.
|
/// the body callback reports failure.
|
||||||
template <size_t NumInputs, typename RewriterT, typename BodyFn>
|
template <size_t NumInputs, typename RewriterT, typename BodyFn>
|
||||||
auto createSpatCompute(RewriterT& rewriter,
|
auto createSpatGraphCompute(RewriterT& rewriter,
|
||||||
mlir::Location loc,
|
mlir::Location loc,
|
||||||
mlir::TypeRange resultTypes,
|
mlir::TypeRange resultTypes,
|
||||||
mlir::ValueRange weights,
|
mlir::ValueRange weights,
|
||||||
mlir::ValueRange inputs,
|
mlir::ValueRange inputs,
|
||||||
BodyFn&& body) {
|
BodyFn&& body) {
|
||||||
assert(inputs.size() == NumInputs && "NumInputs must match the number of input values");
|
assert(inputs.size() == NumInputs && "NumInputs must match the number of input values");
|
||||||
auto computeOp = spatial::SpatCompute::create(rewriter, loc, resultTypes, weights, inputs);
|
auto computeOp = createEmptySpatGraphCompute(rewriter, loc, resultTypes, weights, inputs);
|
||||||
|
auto* block = &computeOp.getBody().front();
|
||||||
auto* block = new mlir::Block();
|
|
||||||
for (mlir::Value weight : weights)
|
|
||||||
block->addArgument(weight.getType(), loc);
|
|
||||||
for (mlir::Value input : inputs)
|
|
||||||
block->addArgument(input.getType(), loc);
|
|
||||||
|
|
||||||
computeOp.getBody().push_back(block);
|
|
||||||
rewriter.setInsertionPointToStart(block);
|
|
||||||
|
|
||||||
using BodyResult = detail::InvokeWithBlockArgsResultT<std::decay_t<BodyFn>, std::make_index_sequence<NumInputs>>;
|
using BodyResult = detail::InvokeWithBlockArgsResultT<std::decay_t<BodyFn>, std::make_index_sequence<NumInputs>>;
|
||||||
if constexpr (std::is_same_v<BodyResult, void>) {
|
if constexpr (std::is_same_v<BodyResult, void>) {
|
||||||
@@ -113,32 +191,24 @@ auto createSpatCompute(RewriterT& rewriter,
|
|||||||
if (mlir::failed(bodyResult)) {
|
if (mlir::failed(bodyResult)) {
|
||||||
rewriter.setInsertionPointAfter(computeOp);
|
rewriter.setInsertionPointAfter(computeOp);
|
||||||
rewriter.eraseOp(computeOp);
|
rewriter.eraseOp(computeOp);
|
||||||
return mlir::FailureOr<spatial::SpatCompute>(mlir::failure());
|
return mlir::FailureOr<spatial::SpatGraphCompute>(mlir::failure());
|
||||||
}
|
}
|
||||||
rewriter.setInsertionPointAfter(computeOp);
|
rewriter.setInsertionPointAfter(computeOp);
|
||||||
return mlir::FailureOr<spatial::SpatCompute>(computeOp);
|
return mlir::FailureOr<spatial::SpatGraphCompute>(computeOp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a `spat.compute` whose body consumes the block arguments as a single
|
/// Builds a `spat.graph_compute` whose body consumes the block arguments as a single
|
||||||
/// `ValueRange`, which is convenient for variadic reductions/concats.
|
/// `ValueRange`, which is convenient for variadic reductions/concats.
|
||||||
template <typename RewriterT, typename BodyFn>
|
template <typename RewriterT, typename BodyFn>
|
||||||
auto createSpatCompute(RewriterT& rewriter,
|
auto createSpatGraphCompute(RewriterT& rewriter,
|
||||||
mlir::Location loc,
|
mlir::Location loc,
|
||||||
mlir::TypeRange resultTypes,
|
mlir::TypeRange resultTypes,
|
||||||
mlir::ValueRange weights,
|
mlir::ValueRange weights,
|
||||||
mlir::ValueRange inputs,
|
mlir::ValueRange inputs,
|
||||||
BodyFn&& body) {
|
BodyFn&& body) {
|
||||||
auto computeOp = spatial::SpatCompute::create(rewriter, loc, resultTypes, weights, inputs);
|
auto computeOp = createEmptySpatGraphCompute(rewriter, loc, resultTypes, weights, inputs);
|
||||||
|
auto* block = &computeOp.getBody().front();
|
||||||
auto* block = new mlir::Block();
|
|
||||||
for (mlir::Value weight : weights)
|
|
||||||
block->addArgument(weight.getType(), loc);
|
|
||||||
for (mlir::Value input : inputs)
|
|
||||||
block->addArgument(input.getType(), loc);
|
|
||||||
|
|
||||||
computeOp.getBody().push_back(block);
|
|
||||||
rewriter.setInsertionPointToStart(block);
|
|
||||||
|
|
||||||
using BodyResult = detail::InvokeWithValueRangeResultT<std::decay_t<BodyFn>>;
|
using BodyResult = detail::InvokeWithValueRangeResultT<std::decay_t<BodyFn>>;
|
||||||
if constexpr (std::is_same_v<BodyResult, void>) {
|
if constexpr (std::is_same_v<BodyResult, void>) {
|
||||||
@@ -152,13 +222,148 @@ auto createSpatCompute(RewriterT& rewriter,
|
|||||||
if (mlir::failed(bodyResult)) {
|
if (mlir::failed(bodyResult)) {
|
||||||
rewriter.setInsertionPointAfter(computeOp);
|
rewriter.setInsertionPointAfter(computeOp);
|
||||||
rewriter.eraseOp(computeOp);
|
rewriter.eraseOp(computeOp);
|
||||||
return mlir::FailureOr<spatial::SpatCompute>(mlir::failure());
|
return mlir::FailureOr<spatial::SpatGraphCompute>(mlir::failure());
|
||||||
}
|
}
|
||||||
rewriter.setInsertionPointAfter(computeOp);
|
rewriter.setInsertionPointAfter(computeOp);
|
||||||
return mlir::FailureOr<spatial::SpatCompute>(computeOp);
|
return mlir::FailureOr<spatial::SpatGraphCompute>(computeOp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mlir::Value sumTensors(mlir::ArrayRef<mlir::Value> tensors, mlir::ConversionPatternRewriter& rewriter);
|
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());
|
||||||
|
|
||||||
|
auto laneCountAttr = pim::getCheckedI32Attr(rewriter, loc, laneCount, "spatial compute_batch lane count");
|
||||||
|
if (mlir::failed(laneCountAttr))
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename RewriterT>
|
||||||
|
auto createEmptySpatGraphComputeBatch(RewriterT& rewriter,
|
||||||
|
mlir::Location loc,
|
||||||
|
mlir::TypeRange resultTypes,
|
||||||
|
int64_t laneCount,
|
||||||
|
mlir::ValueRange weights,
|
||||||
|
mlir::ValueRange inputs) {
|
||||||
|
auto blockArgTypes = detail::getGraphComputeBatchBlockArgTypes(rewriter, resultTypes, weights, inputs);
|
||||||
|
auto blockArgLocs = detail::getGraphComputeBatchBlockArgLocs(loc, resultTypes, weights, inputs);
|
||||||
|
return createEmptySpatGraphComputeBatch(
|
||||||
|
rewriter, loc, resultTypes, laneCount, weights, inputs, blockArgTypes, blockArgLocs);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename RewriterT, typename BodyFn>
|
||||||
|
auto createSpatGraphComputeBatch(RewriterT& rewriter,
|
||||||
|
mlir::Location loc,
|
||||||
|
mlir::TypeRange resultTypes,
|
||||||
|
int64_t laneCount,
|
||||||
|
mlir::ValueRange weights,
|
||||||
|
mlir::ValueRange inputs,
|
||||||
|
BodyFn&& body) {
|
||||||
|
auto batchOp = createEmptySpatGraphComputeBatch(rewriter, loc, resultTypes, laneCount, weights, inputs);
|
||||||
|
if (failed(batchOp))
|
||||||
|
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(mlir::failure());
|
||||||
|
auto* block = &(*batchOp).getBody().front();
|
||||||
|
|
||||||
|
detail::SpatComputeBatchBodyArgs args {
|
||||||
|
block->getArgument(0),
|
||||||
|
mlir::ValueRange(block->getArguments()).slice(1, weights.size()),
|
||||||
|
mlir::ValueRange(block->getArguments()).slice(1 + weights.size(), inputs.size()),
|
||||||
|
mlir::ValueRange(block->getArguments()).drop_front(1 + weights.size() + inputs.size())};
|
||||||
|
|
||||||
|
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 batchOp;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
auto bodyResult = std::forward<BodyFn>(body)(args);
|
||||||
|
if (mlir::failed(bodyResult)) {
|
||||||
|
rewriter.setInsertionPointAfter(*batchOp);
|
||||||
|
rewriter.eraseOp(*batchOp);
|
||||||
|
return mlir::FailureOr<spatial::SpatGraphComputeBatch>(mlir::failure());
|
||||||
|
}
|
||||||
|
rewriter.setInsertionPointAfter(*batchOp);
|
||||||
|
return batchOp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <size_t NumInputs, typename RewriterT, typename BodyFn>
|
||||||
|
auto createSpatCompute(RewriterT& rewriter,
|
||||||
|
mlir::Location loc,
|
||||||
|
mlir::TypeRange resultTypes,
|
||||||
|
mlir::ValueRange weights,
|
||||||
|
mlir::ValueRange inputs,
|
||||||
|
BodyFn&& body) {
|
||||||
|
return createSpatGraphCompute<NumInputs>(
|
||||||
|
rewriter, loc, resultTypes, weights, inputs, std::forward<BodyFn>(body));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename RewriterT, typename BodyFn>
|
||||||
|
auto createSpatCompute(RewriterT& rewriter,
|
||||||
|
mlir::Location loc,
|
||||||
|
mlir::TypeRange resultTypes,
|
||||||
|
mlir::ValueRange weights,
|
||||||
|
mlir::ValueRange inputs,
|
||||||
|
BodyFn&& body) {
|
||||||
|
return createSpatGraphCompute(rewriter, loc, resultTypes, weights, inputs, std::forward<BodyFn>(body));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename RewriterT, typename BodyFn>
|
||||||
|
auto createSpatComputeBatch(RewriterT& rewriter,
|
||||||
|
mlir::Location loc,
|
||||||
|
mlir::TypeRange resultTypes,
|
||||||
|
int64_t laneCount,
|
||||||
|
mlir::ValueRange weights,
|
||||||
|
mlir::ValueRange inputs,
|
||||||
|
BodyFn&& body) {
|
||||||
|
return createSpatGraphComputeBatch(
|
||||||
|
rewriter, loc, resultTypes, laneCount, weights, inputs, std::forward<BodyFn>(body));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void createParallelInsertSliceIntoBatchOutput(mlir::PatternRewriter& rewriter,
|
||||||
|
mlir::Location loc,
|
||||||
|
mlir::Value source,
|
||||||
|
mlir::Value dest,
|
||||||
|
mlir::ArrayRef<mlir::OpFoldResult> offsets,
|
||||||
|
mlir::ArrayRef<mlir::OpFoldResult> sizes,
|
||||||
|
mlir::ArrayRef<mlir::OpFoldResult> strides) {
|
||||||
|
auto inParallelOp = spatial::SpatInParallelOp::create(rewriter, loc);
|
||||||
|
rewriter.setInsertionPointToStart(&inParallelOp.getRegion().front());
|
||||||
|
mlir::tensor::ParallelInsertSliceOp::create(rewriter, loc, source, dest, offsets, sizes, strides);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename BodyFn>
|
||||||
|
mlir::Value materializeOrComputeUnary(mlir::Value input,
|
||||||
|
mlir::RankedTensorType resultType,
|
||||||
|
mlir::PatternRewriter& rewriter,
|
||||||
|
mlir::Location loc,
|
||||||
|
BodyFn&& build) {
|
||||||
|
auto&& buildFn = build;
|
||||||
|
if (isCompileTimeComputable(input))
|
||||||
|
return buildFn(input);
|
||||||
|
|
||||||
|
auto computeOp = createSpatCompute<1>(
|
||||||
|
rewriter, loc, mlir::TypeRange {resultType}, {}, mlir::ValueRange {input}, [&](mlir::Value computeInput) {
|
||||||
|
mlir::Value result = buildFn(computeInput);
|
||||||
|
spatial::SpatYieldOp::create(rewriter, loc, result);
|
||||||
|
});
|
||||||
|
return computeOp.getResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
mlir::Value sumTensors(mlir::ArrayRef<mlir::Value> tensors, mlir::PatternRewriter& rewriter);
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -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,239 @@
|
|||||||
|
#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 RankedTensorType::get({logicalType.getDimSize(2), logicalType.getDimSize(1), 1, logicalType.getDimSize(3)},
|
||||||
|
logicalType.getElementType(),
|
||||||
|
logicalType.getEncoding());
|
||||||
|
}
|
||||||
|
|
||||||
|
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};
|
||||||
|
}
|
||||||
|
|
||||||
|
SmallVector<OpFoldResult> buildRowStripFragmentOffsets(PatternRewriter& rewriter, OpFoldResult row) {
|
||||||
|
return {row, rewriter.getIndexAttr(0), rewriter.getIndexAttr(0), rewriter.getIndexAttr(0)};
|
||||||
|
}
|
||||||
|
|
||||||
|
SmallVector<OpFoldResult> buildRowStripFragmentSizes(PatternRewriter& rewriter, RankedTensorType logicalType) {
|
||||||
|
return {rewriter.getIndexAttr(1),
|
||||||
|
rewriter.getIndexAttr(logicalType.getDimSize(1)),
|
||||||
|
rewriter.getIndexAttr(1),
|
||||||
|
rewriter.getIndexAttr(logicalType.getDimSize(3))};
|
||||||
|
}
|
||||||
|
|
||||||
|
Value extractRowStripFragment(Value storage,
|
||||||
|
RankedTensorType logicalType,
|
||||||
|
OpFoldResult row,
|
||||||
|
PatternRewriter& rewriter,
|
||||||
|
Location loc) {
|
||||||
|
return tensor::ExtractSliceOp::create(rewriter,
|
||||||
|
loc,
|
||||||
|
getRowStripFragmentType(logicalType),
|
||||||
|
storage,
|
||||||
|
buildRowStripFragmentOffsets(rewriter, row),
|
||||||
|
buildRowStripFragmentSizes(rewriter, logicalType),
|
||||||
|
getUnitStrides(rewriter, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
void insertRowStripFragment(Value fragment,
|
||||||
|
Value output,
|
||||||
|
RankedTensorType logicalType,
|
||||||
|
OpFoldResult row,
|
||||||
|
PatternRewriter& rewriter,
|
||||||
|
Location loc) {
|
||||||
|
createParallelInsertSliceIntoBatchOutput(rewriter,
|
||||||
|
loc,
|
||||||
|
fragment,
|
||||||
|
output,
|
||||||
|
buildRowStripFragmentOffsets(rewriter, row),
|
||||||
|
buildRowStripFragmentSizes(rewriter, logicalType),
|
||||||
|
getUnitStrides(rewriter, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
materializeRowStripStorageToDense(Value storage, RankedTensorType logicalType, PatternRewriter& rewriter, Location loc) {
|
||||||
|
auto storageType = dyn_cast<RankedTensorType>(storage.getType());
|
||||||
|
if (!storageType || storageType != getRowStripStorageType(logicalType))
|
||||||
|
return failure();
|
||||||
|
|
||||||
|
auto batchOp = createSpatComputeBatch(
|
||||||
|
rewriter, loc, TypeRange {logicalType}, logicalType.getDimSize(2), {}, ValueRange {storage},
|
||||||
|
[&](detail::SpatComputeBatchBodyArgs args) {
|
||||||
|
Value fragment = extractRowStripFragment(args.inputs.front(), logicalType, args.lane, rewriter, loc);
|
||||||
|
createParallelInsertSliceIntoBatchOutput(rewriter,
|
||||||
|
loc,
|
||||||
|
fragment,
|
||||||
|
args.outputs.front(),
|
||||||
|
SmallVector<OpFoldResult> {rewriter.getIndexAttr(0),
|
||||||
|
rewriter.getIndexAttr(0),
|
||||||
|
args.lane,
|
||||||
|
rewriter.getIndexAttr(0)},
|
||||||
|
buildRowStripFragmentSizes(rewriter, logicalType),
|
||||||
|
getUnitStrides(rewriter, 4));
|
||||||
|
return success();
|
||||||
|
});
|
||||||
|
if (failed(batchOp))
|
||||||
|
return failure();
|
||||||
|
return batchOp->getResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
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> materializeRowStripStorageToDense(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
|
||||||
@@ -1,98 +1,25 @@
|
|||||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
|
||||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
#include "mlir/IR/Matchers.h"
|
|
||||||
|
|
||||||
#include "llvm/ADT/SmallVector.h"
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#include "ShapeTilingUtils.hpp"
|
#include "ShapeTilingUtils.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
|
||||||
|
|
||||||
using namespace mlir;
|
using namespace mlir;
|
||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
|
|
||||||
static Value getIndexValue(OpFoldResult result, ConversionPatternRewriter& rewriter, Location loc) {
|
|
||||||
if (auto attr = dyn_cast<Attribute>(result))
|
|
||||||
return arith::ConstantIndexOp::create(rewriter, loc, cast<IntegerAttr>(attr).getInt()).getResult();
|
|
||||||
return cast<Value>(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Value addIndexValues(Value lhs, Value rhs, ConversionPatternRewriter& rewriter, Location loc) {
|
|
||||||
APInt lhsConst;
|
|
||||||
if (matchPattern(lhs, m_ConstantInt(&lhsConst)) && lhsConst.isZero())
|
|
||||||
return rhs;
|
|
||||||
|
|
||||||
APInt rhsConst;
|
|
||||||
if (matchPattern(rhs, m_ConstantInt(&rhsConst)) && rhsConst.isZero())
|
|
||||||
return lhs;
|
|
||||||
|
|
||||||
return arith::AddIOp::create(rewriter, loc, lhs, rhs).getResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
static Value multiplyIndexValue(Value value, OpFoldResult factor, ConversionPatternRewriter& rewriter, Location loc) {
|
|
||||||
APInt factorConst;
|
|
||||||
if (auto attr = dyn_cast<Attribute>(factor))
|
|
||||||
factorConst = cast<IntegerAttr>(attr).getValue();
|
|
||||||
else if (!matchPattern(cast<Value>(factor), m_ConstantInt(&factorConst)))
|
|
||||||
return arith::MulIOp::create(rewriter, loc, value, cast<Value>(factor)).getResult();
|
|
||||||
|
|
||||||
if (factorConst.isZero())
|
|
||||||
return arith::ConstantIndexOp::create(rewriter, loc, 0).getResult();
|
|
||||||
if (factorConst.isOne())
|
|
||||||
return value;
|
|
||||||
|
|
||||||
auto factorValue = arith::ConstantIndexOp::create(rewriter, loc, factorConst.getSExtValue()).getResult();
|
|
||||||
return arith::MulIOp::create(rewriter, loc, value, factorValue).getResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isContiguousTensorSlice(Value source, RankedTensorType resultType, ArrayRef<OpFoldResult> strides) {
|
|
||||||
auto sourceType = dyn_cast<RankedTensorType>(source.getType());
|
|
||||||
if (!sourceType || !sourceType.hasStaticShape() || !resultType.hasStaticShape() || sourceType.getRank() != resultType.getRank())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (OpFoldResult stride : strides) {
|
|
||||||
APInt strideValue;
|
|
||||||
if (auto attr = dyn_cast<Attribute>(stride)) {
|
|
||||||
if (cast<IntegerAttr>(attr).getInt() != 1)
|
|
||||||
return false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!matchPattern(cast<Value>(stride), m_ConstantInt(&strideValue)) || !strideValue.isOne())
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto sizesAndShape = llvm::zip_equal(llvm::make_range(resultType.getShape().rbegin(), resultType.getShape().rend()),
|
|
||||||
llvm::make_range(sourceType.getShape().rbegin(), sourceType.getShape().rend()));
|
|
||||||
auto firstDifferentSize = std::find_if(sizesAndShape.begin(), sizesAndShape.end(), [&](auto sizeAndShape) -> bool {
|
|
||||||
auto [size, dimension] = sizeAndShape;
|
|
||||||
return size != dimension;
|
|
||||||
});
|
|
||||||
if (firstDifferentSize == sizesAndShape.end())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
++firstDifferentSize;
|
|
||||||
return std::all_of(firstDifferentSize, sizesAndShape.end(), [](auto sizeAndShape) {
|
|
||||||
auto [size, _dimension] = sizeAndShape;
|
|
||||||
return size == 1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
SmallVector<Value> sliceTensor(
|
SmallVector<Value> sliceTensor(
|
||||||
const Value& tensorToSlice, size_t axis, int64_t sliceSize, ConversionPatternRewriter& rewriter, Location loc) {
|
const Value& tensorToSlice, size_t axis, int64_t sliceSize, PatternRewriter& rewriter, Location loc) {
|
||||||
ArrayRef<long> shape = getTensorShape(tensorToSlice);
|
ArrayRef<long> shape = getTensorShape(tensorToSlice);
|
||||||
assert("Invalid axis" && axis < shape.size());
|
assert("Invalid axis" && axis < shape.size());
|
||||||
|
|
||||||
SmallVector<OpFoldResult> strides(shape.size(), rewriter.getIndexAttr(1));
|
SmallVector<OpFoldResult> strides(shape.size(), rewriter.getIndexAttr(1));
|
||||||
SmallVector<OpFoldResult> offsets(shape.size(), rewriter.getIndexAttr(0));
|
SmallVector<OpFoldResult> offsets = getZeroOffsets(rewriter, shape.size());
|
||||||
SmallVector<OpFoldResult> sizes;
|
SmallVector<OpFoldResult> sizes = getStaticSizes(rewriter, shape);
|
||||||
sizes.reserve(shape.size());
|
|
||||||
for (const auto size : shape)
|
|
||||||
sizes.push_back(rewriter.getIndexAttr(size));
|
|
||||||
sizes[axis] = rewriter.getIndexAttr(sliceSize);
|
sizes[axis] = rewriter.getIndexAttr(sliceSize);
|
||||||
|
|
||||||
long length = shape[axis];
|
long length = shape[axis];
|
||||||
@@ -132,7 +59,7 @@ SmallVector<Value> sliceTensor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
SmallVector<Value>
|
SmallVector<Value>
|
||||||
sliceVector(const Value& vectorToSlice, int64_t sliceSize, ConversionPatternRewriter& rewriter, Location loc) {
|
sliceVector(const Value& vectorToSlice, int64_t sliceSize, PatternRewriter& rewriter, Location loc) {
|
||||||
ArrayRef<long> shape = getTensorShape(vectorToSlice);
|
ArrayRef<long> shape = getTensorShape(vectorToSlice);
|
||||||
assert("Not a vector" && isVectorShape(shape));
|
assert("Not a vector" && isVectorShape(shape));
|
||||||
size_t axis = shape[0] != 1 ? 0 : 1;
|
size_t axis = shape[0] != 1 ? 0 : 1;
|
||||||
@@ -140,7 +67,7 @@ sliceVector(const Value& vectorToSlice, int64_t sliceSize, ConversionPatternRewr
|
|||||||
}
|
}
|
||||||
|
|
||||||
DenseMap<CoreId, SmallVector<Value>>
|
DenseMap<CoreId, SmallVector<Value>>
|
||||||
sliceVectorPerCrossbarPerCore(const Value& vectorToSlice, ConversionPatternRewriter& rewriter, Location loc) {
|
sliceVectorPerCrossbarPerCore(const Value& vectorToSlice, PatternRewriter& rewriter, Location loc) {
|
||||||
SmallVector<Value> slices = sliceVector(vectorToSlice, crossbarSize, rewriter, loc);
|
SmallVector<Value> slices = sliceVector(vectorToSlice, crossbarSize, rewriter, loc);
|
||||||
DenseMap<CoreId, SmallVector<Value>> slicesPerCore;
|
DenseMap<CoreId, SmallVector<Value>> slicesPerCore;
|
||||||
for (size_t sliceId = 0; sliceId < slices.size(); sliceId++) {
|
for (size_t sliceId = 0; sliceId < slices.size(); sliceId++) {
|
||||||
@@ -150,130 +77,4 @@ sliceVectorPerCrossbarPerCore(const Value& vectorToSlice, ConversionPatternRewri
|
|||||||
return slicesPerCore;
|
return slicesPerCore;
|
||||||
}
|
}
|
||||||
|
|
||||||
DenseMap<HSliceId, DenseMap<CoreId, SmallVector<Value>>> tileMatrix(
|
|
||||||
Value& matrixToTile, int64_t hSliceSize, int64_t vSliceSize, ConversionPatternRewriter& rewriter, Location& loc) {
|
|
||||||
assert("Not a matrix" && isMatrixShape(getTensorShape(matrixToTile)));
|
|
||||||
|
|
||||||
DenseMap<HSliceId, DenseMap<CoreId, SmallVector<Value>>> tiles;
|
|
||||||
|
|
||||||
SmallVector<Value> hSlices = sliceTensor(matrixToTile, 1, hSliceSize, rewriter, loc);
|
|
||||||
size_t numHSlices = hSlices.size();
|
|
||||||
for (size_t hSliceId = 0; hSliceId < numHSlices; hSliceId++) {
|
|
||||||
Value hSlice = hSlices[hSliceId];
|
|
||||||
SmallVector<Value> vSlices = sliceTensor(hSlice, 0, vSliceSize, rewriter, loc);
|
|
||||||
for (size_t vSliceId = 0; vSliceId < vSlices.size(); vSliceId++) {
|
|
||||||
size_t coreId = vSliceId / crossbarCountInCore;
|
|
||||||
Value vSlice = vSlices[vSliceId];
|
|
||||||
tiles[hSliceId][coreId].push_back(vSlice);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
Value broadcastToVector(Value scalarToBroadcast, int64_t length, ConversionPatternRewriter& rewriter, Location loc) {
|
|
||||||
auto oldType = cast<RankedTensorType>(scalarToBroadcast.getType());
|
|
||||||
Type elementType = oldType.getElementType();
|
|
||||||
int64_t shape[2] = {1, length};
|
|
||||||
Type type = oldType.cloneWith(ArrayRef(shape), elementType);
|
|
||||||
|
|
||||||
auto buildBroadcast = [&](Value input) -> Value {
|
|
||||||
auto zero = arith::ConstantIndexOp::create(rewriter, loc, 0).getResult();
|
|
||||||
SmallVector<Value> index(oldType.getRank(), zero);
|
|
||||||
auto elementValue = tensor::ExtractOp::create(rewriter, loc, input, index).getResult();
|
|
||||||
return tensor::SplatOp::create(rewriter, loc, type, elementValue);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isCompileTimeComputable(scalarToBroadcast))
|
|
||||||
return buildBroadcast(scalarToBroadcast);
|
|
||||||
|
|
||||||
auto broadcastCompute =
|
|
||||||
createSpatCompute<1>(rewriter, loc, TypeRange {type}, {}, ValueRange {scalarToBroadcast}, [&](Value input) {
|
|
||||||
spatial::SpatYieldOp::create(rewriter, loc, buildBroadcast(input));
|
|
||||||
});
|
|
||||||
return broadcastCompute.getResult(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Value materializeContiguousTensorSlice(Value source,
|
|
||||||
RankedTensorType resultType,
|
|
||||||
ArrayRef<OpFoldResult> offsets,
|
|
||||||
ArrayRef<OpFoldResult> strides,
|
|
||||||
ConversionPatternRewriter& rewriter,
|
|
||||||
Location loc) {
|
|
||||||
assert(resultType.hasStaticShape() && "expected static result type");
|
|
||||||
size_t rank = static_cast<size_t>(resultType.getRank());
|
|
||||||
assert(offsets.size() == rank && "expected rank-matching offsets");
|
|
||||||
assert(strides.size() == rank && "expected rank-matching strides");
|
|
||||||
|
|
||||||
SmallVector<OpFoldResult> sizes;
|
|
||||||
sizes.reserve(resultType.getRank());
|
|
||||||
for (int64_t size : resultType.getShape())
|
|
||||||
sizes.push_back(rewriter.getIndexAttr(size));
|
|
||||||
|
|
||||||
if (isContiguousTensorSlice(source, resultType, strides))
|
|
||||||
return tensor::ExtractSliceOp::create(rewriter, loc, resultType, source, offsets, sizes, strides).getResult();
|
|
||||||
|
|
||||||
if (resultType.getRank() == 0)
|
|
||||||
return tensor::ExtractSliceOp::create(rewriter, loc, resultType, source, offsets, sizes, strides).getResult();
|
|
||||||
|
|
||||||
Value init = tensor::EmptyOp::create(rewriter, loc, resultType.getShape(), resultType.getElementType()).getResult();
|
|
||||||
SmallVector<Value> zeroIndices(resultType.getRank());
|
|
||||||
for (Value& zeroIndex : zeroIndices)
|
|
||||||
zeroIndex = arith::ConstantIndexOp::create(rewriter, loc, 0).getResult();
|
|
||||||
|
|
||||||
SmallVector<Value> resultIndices;
|
|
||||||
resultIndices.reserve(resultType.getRank());
|
|
||||||
|
|
||||||
auto buildLoopNest = [&](auto&& self, unsigned dim, Value accumulator) -> Value {
|
|
||||||
if (dim == resultType.getRank()) {
|
|
||||||
SmallVector<Value> sourceIndices;
|
|
||||||
sourceIndices.reserve(resultType.getRank());
|
|
||||||
for (unsigned idx = 0; idx < resultType.getRank(); ++idx) {
|
|
||||||
Value offsetValue = getIndexValue(offsets[idx], rewriter, loc);
|
|
||||||
Value scaledIndex = multiplyIndexValue(resultIndices[idx], strides[idx], rewriter, loc);
|
|
||||||
sourceIndices.push_back(addIndexValues(offsetValue, scaledIndex, rewriter, loc));
|
|
||||||
}
|
|
||||||
|
|
||||||
SmallVector<OpFoldResult> sourceOffsets;
|
|
||||||
SmallVector<OpFoldResult> destinationOffsets;
|
|
||||||
SmallVector<OpFoldResult> unitSizes;
|
|
||||||
SmallVector<OpFoldResult> unitStrides;
|
|
||||||
sourceOffsets.reserve(resultType.getRank());
|
|
||||||
destinationOffsets.reserve(resultType.getRank());
|
|
||||||
unitSizes.reserve(resultType.getRank());
|
|
||||||
unitStrides.reserve(resultType.getRank());
|
|
||||||
for (Value index : sourceIndices)
|
|
||||||
sourceOffsets.push_back(index);
|
|
||||||
for (Value index : resultIndices)
|
|
||||||
destinationOffsets.push_back(index);
|
|
||||||
for (int64_t idx = 0; idx < resultType.getRank(); ++idx) {
|
|
||||||
unitSizes.push_back(rewriter.getIndexAttr(1));
|
|
||||||
unitStrides.push_back(rewriter.getIndexAttr(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto elementTensorType =
|
|
||||||
RankedTensorType::get(SmallVector<int64_t>(resultType.getRank(), 1), resultType.getElementType());
|
|
||||||
Value elementSlice =
|
|
||||||
tensor::ExtractSliceOp::create(rewriter, loc, elementTensorType, source, sourceOffsets, unitSizes, unitStrides)
|
|
||||||
.getResult();
|
|
||||||
return tensor::InsertSliceOp::create(
|
|
||||||
rewriter, loc, elementSlice, accumulator, destinationOffsets, unitSizes, unitStrides)
|
|
||||||
.getResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
Value lower = zeroIndices[dim];
|
|
||||||
Value upper = arith::ConstantIndexOp::create(rewriter, loc, resultType.getDimSize(dim)).getResult();
|
|
||||||
Value step = arith::ConstantIndexOp::create(rewriter, loc, 1).getResult();
|
|
||||||
auto loop = scf::ForOp::create(rewriter, loc, lower, upper, step, ValueRange {accumulator});
|
|
||||||
rewriter.setInsertionPointToStart(loop.getBody());
|
|
||||||
resultIndices.push_back(loop.getInductionVar());
|
|
||||||
Value updated = self(self, dim + 1, loop.getRegionIterArgs().front());
|
|
||||||
resultIndices.pop_back();
|
|
||||||
scf::YieldOp::create(rewriter, loc, updated);
|
|
||||||
rewriter.setInsertionPointAfter(loop);
|
|
||||||
return loop.getResult(0);
|
|
||||||
};
|
|
||||||
|
|
||||||
return buildLoopNest(buildLoopNest, 0, init);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -1,151 +1,31 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
#include "mlir/IR/BuiltinTypes.h"
|
#include "mlir/IR/ValueRange.h"
|
||||||
#include "mlir/IR/Value.h"
|
|
||||||
#include "mlir/Transforms/DialectConversion.h"
|
#include "mlir/Transforms/DialectConversion.h"
|
||||||
|
|
||||||
#include "llvm/ADT/ArrayRef.h"
|
|
||||||
#include "llvm/ADT/DenseMap.h"
|
|
||||||
#include "llvm/ADT/SmallVector.h"
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
|
||||||
#include <cassert>
|
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
||||||
#include <cstddef>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
|
|
||||||
template <class ShapedType>
|
|
||||||
inline auto getImageWidth(const ShapedType& shapedType) {
|
|
||||||
return shapedType.getDimSize(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class ShapedType>
|
|
||||||
inline auto getImageHeight(const ShapedType& shapedType) {
|
|
||||||
return shapedType.getDimSize(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class ShapedType>
|
|
||||||
inline auto getImageChannel(const ShapedType& shapedType) {
|
|
||||||
return shapedType.getDimSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class ShapedType>
|
|
||||||
inline auto getImageN(const ShapedType& shapedType) {
|
|
||||||
return shapedType.getDimSize(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class ShapedType>
|
|
||||||
inline auto getKernelWidth(const ShapedType& shapedType) {
|
|
||||||
return shapedType.getDimSize(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class ShapedType>
|
|
||||||
inline auto getKernelHeight(const ShapedType& shapedType) {
|
|
||||||
return shapedType.getDimSize(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class ShapedType>
|
|
||||||
inline auto getFilterCount(const ShapedType& shapedType) {
|
|
||||||
return shapedType.getDimSize(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
bool isVVectorShape(mlir::ArrayRef<T> shape) {
|
|
||||||
return shape.size() == 2 && shape[1] == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
T getVectorLength(mlir::ArrayRef<T> shape) {
|
|
||||||
assert(isVectorShape(shape));
|
|
||||||
return shape[0] != 1 ? shape[0] : shape[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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Slices a statically shaped tensor along one axis into contiguous pieces of
|
/// Slices a statically shaped tensor along one axis into contiguous pieces of
|
||||||
/// at most `sliceSize` elements.
|
/// at most `sliceSize` elements.
|
||||||
llvm::SmallVector<mlir::Value> sliceTensor(const mlir::Value& tensorToSlice,
|
llvm::SmallVector<mlir::Value> sliceTensor(const mlir::Value& tensorToSlice,
|
||||||
size_t axis,
|
size_t axis,
|
||||||
int64_t sliceSize,
|
int64_t sliceSize,
|
||||||
mlir::ConversionPatternRewriter& rewriter,
|
mlir::PatternRewriter& rewriter,
|
||||||
mlir::Location loc);
|
mlir::Location loc);
|
||||||
|
|
||||||
llvm::SmallVector<mlir::Value> sliceVector(const mlir::Value& vectorToSlice,
|
llvm::SmallVector<mlir::Value> sliceVector(const mlir::Value& vectorToSlice,
|
||||||
int64_t sliceSize,
|
int64_t sliceSize,
|
||||||
mlir::ConversionPatternRewriter& rewriter,
|
mlir::PatternRewriter& rewriter,
|
||||||
mlir::Location loc);
|
mlir::Location loc);
|
||||||
|
|
||||||
/// Partitions one logical vector into per-core crossbar-sized slices using the
|
/// Partitions one logical vector into per-core crossbar-sized slices using the
|
||||||
/// current PIM target geometry.
|
/// current PIM target geometry.
|
||||||
llvm::DenseMap<CoreId, llvm::SmallVector<mlir::Value>> sliceVectorPerCrossbarPerCore(
|
llvm::DenseMap<CoreId, llvm::SmallVector<mlir::Value>> sliceVectorPerCrossbarPerCore(
|
||||||
const mlir::Value& vectorToSlice, mlir::ConversionPatternRewriter& rewriter, mlir::Location loc);
|
const mlir::Value& vectorToSlice, mlir::PatternRewriter& rewriter, mlir::Location loc);
|
||||||
|
|
||||||
/// Tiles a matrix first across output columns and then across input rows so it
|
|
||||||
/// can be assigned to crossbars grouped by core.
|
|
||||||
llvm::DenseMap<HSliceId, llvm::DenseMap<CoreId, llvm::SmallVector<mlir::Value>>>
|
|
||||||
tileMatrix(mlir::Value& matrixToTile,
|
|
||||||
int64_t hSliceSize,
|
|
||||||
int64_t vSliceSize,
|
|
||||||
mlir::ConversionPatternRewriter& rewriter,
|
|
||||||
mlir::Location& loc);
|
|
||||||
|
|
||||||
mlir::Value broadcastToVector(mlir::Value scalarToBroadcast,
|
|
||||||
int64_t length,
|
|
||||||
mlir::ConversionPatternRewriter& rewriter,
|
|
||||||
mlir::Location loc);
|
|
||||||
|
|
||||||
mlir::Value materializeContiguousTensorSlice(mlir::Value source,
|
|
||||||
mlir::RankedTensorType resultType,
|
|
||||||
llvm::ArrayRef<mlir::OpFoldResult> offsets,
|
|
||||||
llvm::ArrayRef<mlir::OpFoldResult> strides,
|
|
||||||
mlir::ConversionPatternRewriter& rewriter,
|
|
||||||
mlir::Location loc);
|
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||||
|
#include "mlir/Dialect/Linalg/IR/Linalg.h"
|
||||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
#include "mlir/IR/BuiltinTypes.h"
|
#include "mlir/IR/BuiltinTypes.h"
|
||||||
#include "mlir/IR/IRMapping.h"
|
#include "mlir/IR/IRMapping.h"
|
||||||
@@ -18,9 +19,11 @@ using namespace mlir;
|
|||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
|
|
||||||
bool isWeightLikeComputeOperand(Value value) {
|
static bool isWeightMaterializationValue(Value value, bool requireMatrixShape) {
|
||||||
auto rankedType = dyn_cast<RankedTensorType>(value.getType());
|
auto rankedType = dyn_cast<RankedTensorType>(value.getType());
|
||||||
if (!rankedType || !isMatrixShape(rankedType.getShape()))
|
if (!rankedType)
|
||||||
|
return false;
|
||||||
|
if (requireMatrixShape && !isMatrixShape(rankedType.getShape()))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
llvm::SmallPtrSet<Operation*, 8> visited;
|
llvm::SmallPtrSet<Operation*, 8> visited;
|
||||||
@@ -28,8 +31,14 @@ bool isWeightLikeComputeOperand(Value value) {
|
|||||||
while (auto* definingOp = value.getDefiningOp()) {
|
while (auto* definingOp = value.getDefiningOp()) {
|
||||||
if (!visited.insert(definingOp).second)
|
if (!visited.insert(definingOp).second)
|
||||||
return false;
|
return false;
|
||||||
if (isa<arith::ConstantOp, ONNXConstantOp>(definingOp) || hasWeightAlways(definingOp))
|
if (isa<arith::ConstantOp, ONNXConstantOp>(definingOp) || hasWeightAlways(definingOp)) {
|
||||||
|
auto sourceType = dyn_cast<RankedTensorType>(value.getType());
|
||||||
|
if (!sourceType)
|
||||||
|
return false;
|
||||||
|
if (requireMatrixShape && !isMatrixShape(sourceType.getShape()))
|
||||||
|
return false;
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (auto extractSliceOp = dyn_cast<tensor::ExtractSliceOp>(definingOp)) {
|
if (auto extractSliceOp = dyn_cast<tensor::ExtractSliceOp>(definingOp)) {
|
||||||
value = extractSliceOp.getSource();
|
value = extractSliceOp.getSource();
|
||||||
@@ -43,8 +52,8 @@ bool isWeightLikeComputeOperand(Value value) {
|
|||||||
value = collapseShapeOp.getSrc();
|
value = collapseShapeOp.getSrc();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (auto transposeOp = dyn_cast<ONNXTransposeOp>(definingOp)) {
|
if (auto transposeOp = dyn_cast<linalg::TransposeOp>(definingOp)) {
|
||||||
value = transposeOp.getData();
|
value = transposeOp.getInput();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +63,8 @@ bool isWeightLikeComputeOperand(Value value) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isWeightLikeComputeOperand(Value value) { return isWeightMaterializationValue(value, /*requireMatrixShape=*/true); }
|
||||||
|
|
||||||
FailureOr<Value> materializeWeightLikeValueInBlock(Value value, IRRewriter& rewriter, IRMapping& mapper) {
|
FailureOr<Value> materializeWeightLikeValueInBlock(Value value, IRRewriter& rewriter, IRMapping& mapper) {
|
||||||
if (auto mapped = mapper.lookupOrNull(value))
|
if (auto mapped = mapper.lookupOrNull(value))
|
||||||
return cast<Value>(mapped);
|
return cast<Value>(mapped);
|
||||||
@@ -80,7 +91,7 @@ FailureOr<Value> materializeWeightLikeValueInBlock(Value value, IRRewriter& rewr
|
|||||||
return referencedValue.getResult();
|
return referencedValue.getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isa<tensor::ExtractSliceOp, tensor::ExpandShapeOp, tensor::CollapseShapeOp, ONNXTransposeOp>(definingOp))
|
if (!isa<tensor::ExtractSliceOp, tensor::ExpandShapeOp, tensor::CollapseShapeOp, linalg::TransposeOp>(definingOp))
|
||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
IRMapping localMapper;
|
IRMapping localMapper;
|
||||||
@@ -90,7 +101,7 @@ FailureOr<Value> materializeWeightLikeValueInBlock(Value value, IRRewriter& rewr
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isWeightLikeComputeOperand(operand)) {
|
if (isWeightMaterializationValue(operand, /*requireMatrixShape=*/false)) {
|
||||||
auto clonedOperand = materializeWeightLikeValueInBlock(operand, rewriter, mapper);
|
auto clonedOperand = materializeWeightLikeValueInBlock(operand, rewriter, mapper);
|
||||||
if (failed(clonedOperand))
|
if (failed(clonedOperand))
|
||||||
return failure();
|
return failure();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||||
|
#include "mlir/Dialect/Linalg/IR/Linalg.h"
|
||||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
#include "mlir/IR/BuiltinAttributes.h"
|
#include "mlir/IR/BuiltinAttributes.h"
|
||||||
#include "mlir/IR/BuiltinTypes.h"
|
#include "mlir/IR/BuiltinTypes.h"
|
||||||
@@ -7,10 +8,11 @@
|
|||||||
#include "llvm/ADT/SmallBitVector.h"
|
#include "llvm/ADT/SmallBitVector.h"
|
||||||
#include "llvm/ADT/SmallPtrSet.h"
|
#include "llvm/ADT/SmallPtrSet.h"
|
||||||
#include "llvm/ADT/SmallVector.h"
|
#include "llvm/ADT/SmallVector.h"
|
||||||
#include "llvm/Support/ErrorHandling.h"
|
|
||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#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/CompileTime.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||||
@@ -26,8 +28,7 @@ static bool hasStaticUnitStrides(tensor::ExtractSliceOp extractSliceOp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool hasConstantIndices(tensor::ExtractOp extractOp) {
|
static bool hasConstantIndices(tensor::ExtractOp extractOp) {
|
||||||
return llvm::all_of(extractOp.getIndices(),
|
return llvm::all_of(extractOp.getIndices(), [](Value index) { return matchConstantIndexValue(index).has_value(); });
|
||||||
[](Value index) { return isa_and_nonnull<arith::ConstantIndexOp>(index.getDefiningOp()); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isStaticTensorResult(Operation* op) {
|
static bool isStaticTensorResult(Operation* op) {
|
||||||
@@ -37,13 +38,6 @@ static bool isStaticTensorResult(Operation* op) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static SmallVector<int64_t> computeRowMajorStrides(ArrayRef<int64_t> shape) {
|
|
||||||
SmallVector<int64_t> strides(shape.size(), 1);
|
|
||||||
for (int64_t dim = static_cast<int64_t>(shape.size()) - 2; dim >= 0; --dim)
|
|
||||||
strides[dim] = strides[dim + 1] * shape[dim + 1];
|
|
||||||
return strides;
|
|
||||||
}
|
|
||||||
|
|
||||||
static FailureOr<DenseElementsAttr> transposeDenseElements(DenseElementsAttr denseAttr, ArrayRef<int64_t> perms) {
|
static FailureOr<DenseElementsAttr> transposeDenseElements(DenseElementsAttr denseAttr, ArrayRef<int64_t> perms) {
|
||||||
auto tensorType = dyn_cast<RankedTensorType>(denseAttr.getType());
|
auto tensorType = dyn_cast<RankedTensorType>(denseAttr.getType());
|
||||||
if (!tensorType)
|
if (!tensorType)
|
||||||
@@ -171,6 +165,16 @@ static DenseElementsAttr getHostConstantDenseElementsAttrImpl(Value value, llvm:
|
|||||||
return succeeded(transposedAttr) ? *transposedAttr : nullptr;
|
return succeeded(transposedAttr) ? *transposedAttr : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auto transposeOp = dyn_cast<linalg::TransposeOp>(definingOp)) {
|
||||||
|
auto inputAttr = getHostConstantDenseElementsAttrImpl(transposeOp.getInput(), visited);
|
||||||
|
if (!inputAttr)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
SmallVector<int64_t> perm(transposeOp.getPermutation().begin(), transposeOp.getPermutation().end());
|
||||||
|
auto transposedAttr = transposeDenseElements(inputAttr, perm);
|
||||||
|
return succeeded(transposedAttr) ? *transposedAttr : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
if (auto collapseShapeOp = dyn_cast<tensor::CollapseShapeOp>(definingOp)) {
|
if (auto collapseShapeOp = dyn_cast<tensor::CollapseShapeOp>(definingOp)) {
|
||||||
auto inputAttr = getHostConstantDenseElementsAttrImpl(collapseShapeOp.getSrc(), visited);
|
auto inputAttr = getHostConstantDenseElementsAttrImpl(collapseShapeOp.getSrc(), visited);
|
||||||
if (!inputAttr)
|
if (!inputAttr)
|
||||||
@@ -226,6 +230,9 @@ getCompileTimeSourceImpl(Operation* op, llvm::SmallPtrSetImpl<Operation*>& visit
|
|||||||
if (auto transposeOp = dyn_cast<ONNXTransposeOp>(op))
|
if (auto transposeOp = dyn_cast<ONNXTransposeOp>(op))
|
||||||
return getCompileTimeSourceImpl(transposeOp.getData().getDefiningOp(), visited, chainLength);
|
return getCompileTimeSourceImpl(transposeOp.getData().getDefiningOp(), visited, chainLength);
|
||||||
|
|
||||||
|
if (auto transposeOp = dyn_cast<linalg::TransposeOp>(op))
|
||||||
|
return getCompileTimeSourceImpl(transposeOp.getInput().getDefiningOp(), visited, chainLength);
|
||||||
|
|
||||||
if (auto collapseShapeOp = dyn_cast<tensor::CollapseShapeOp>(op))
|
if (auto collapseShapeOp = dyn_cast<tensor::CollapseShapeOp>(op))
|
||||||
return getCompileTimeSourceImpl(collapseShapeOp.getSrc().getDefiningOp(), visited, chainLength);
|
return getCompileTimeSourceImpl(collapseShapeOp.getSrc().getDefiningOp(), visited, chainLength);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,400 @@
|
|||||||
|
#include "mlir/Dialect/Affine/IR/AffineOps.h"
|
||||||
|
#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/Pass/Pass.h"
|
||||||
|
#include "mlir/Transforms/DialectConversion.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/DenseMap.h"
|
||||||
|
#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/Pass/PIMPasses.h"
|
||||||
|
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||||
|
|
||||||
|
using namespace mlir;
|
||||||
|
|
||||||
|
namespace onnx_mlir {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
static constexpr StringLiteral kDenseLayout = "dense_nchw";
|
||||||
|
static constexpr StringLiteral kRowStripLayout = "nchw_row_strip";
|
||||||
|
|
||||||
|
static FailureOr<RowStripPhysicalValue> getRowStripValue(llvm::DenseMap<Value, RowStripPhysicalValue>& rowStripValues,
|
||||||
|
Value value) {
|
||||||
|
auto it = rowStripValues.find(value);
|
||||||
|
if (it == rowStripValues.end())
|
||||||
|
return failure();
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<RowStripPhysicalValue> buildRowStripValue(spatial::SpatBlueprintOp blueprint,
|
||||||
|
Value storage) {
|
||||||
|
auto logicalType = dyn_cast<RankedTensorType>(blueprint.getOutput().getType());
|
||||||
|
if (!logicalType)
|
||||||
|
return blueprint.emitOpError("requires ranked logical output type"), failure();
|
||||||
|
RowStripPhysicalValue value;
|
||||||
|
value.storage = storage;
|
||||||
|
value.logicalType = logicalType;
|
||||||
|
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) {
|
||||||
|
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) {
|
||||||
|
if (rowStripValue.logicalType.getRank() != 4 || !rowStripValue.logicalType.hasStaticShape())
|
||||||
|
return failure();
|
||||||
|
auto [expectedOffsets, expectedSizes] = buildRowStripMetadata(rowStripValue.logicalType);
|
||||||
|
if (!llvm::equal(rowStripValue.fragmentOffsets, expectedOffsets) || !llvm::equal(rowStripValue.fragmentSizes, expectedSizes))
|
||||||
|
return failure();
|
||||||
|
return materializeRowStripStorageToDense(rowStripValue.storage, rowStripValue.logicalType, rewriter, loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LowerSpatialPlansPass final : PassWrapper<LowerSpatialPlansPass, OperationPass<ModuleOp>> {
|
||||||
|
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(LowerSpatialPlansPass)
|
||||||
|
|
||||||
|
StringRef getArgument() const override { return "lower-spatial-plans"; }
|
||||||
|
StringRef getDescription() const override { return "Lower selected Spatial planning ops to low-level Spatial IR."; }
|
||||||
|
|
||||||
|
void runOnOperation() override {
|
||||||
|
ModuleOp moduleOp = getOperation();
|
||||||
|
MLIRContext* ctx = moduleOp.getContext();
|
||||||
|
auto entryFunc = getPimEntryFunc(moduleOp);
|
||||||
|
if (failed(entryFunc)) {
|
||||||
|
moduleOp.emitError("failed to locate the PIM entry function during LowerSpatialPlans");
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
func::FuncOp funcOp = *entryFunc;
|
||||||
|
PatternRewriter rewriter(ctx);
|
||||||
|
llvm::DenseMap<Value, RowStripPhysicalValue> rowStripValues;
|
||||||
|
llvm::SmallPtrSet<Operation*, 16> eraseAfterLowering;
|
||||||
|
auto verifyLogicalPhase = [&](StringRef stage) -> bool {
|
||||||
|
if (succeeded(verifyLogicalSpatialGraphInvariants(*entryFunc)))
|
||||||
|
return true;
|
||||||
|
moduleOp.emitError() << "logical Spatial graph verification failed " << stage;
|
||||||
|
signalPassFailure();
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!verifyLogicalPhase("at the start of LowerSpatialPlans"))
|
||||||
|
return;
|
||||||
|
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 rowStripBlueprint = llvm::find_if(planOp.getResult().getUsers(), [](Operation* user) {
|
||||||
|
auto blueprint = dyn_cast<spatial::SpatBlueprintOp>(user);
|
||||||
|
return blueprint && blueprint.getPhysicalLayout() == kRowStripLayout;
|
||||||
|
});
|
||||||
|
if (rowStripBlueprint != planOp.getResult().getUsers().end()) {
|
||||||
|
rewriter.setInsertionPoint(planOp);
|
||||||
|
FailureOr<Value> lowered = lowerSelectedConv2DPlan(
|
||||||
|
planOp,
|
||||||
|
succeeded(rowStripInput) ? std::optional<Value> {rowStripInput->storage} : std::nullopt,
|
||||||
|
/*emitRowStripLayout=*/true,
|
||||||
|
rewriter);
|
||||||
|
if (failed(lowered)) {
|
||||||
|
planOp.emitOpError("failed to lower selected row-strip Spatial Conv plan");
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto blueprint = cast<spatial::SpatBlueprintOp>(*rowStripBlueprint);
|
||||||
|
FailureOr<RowStripPhysicalValue> rowStripValue = buildRowStripValue(blueprint, *lowered);
|
||||||
|
if (failed(rowStripValue)) {
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rowStripValues[blueprint.getResult()] = *rowStripValue;
|
||||||
|
eraseAfterLowering.insert(planOp);
|
||||||
|
eraseAfterLowering.insert(blueprint);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
rewriter.setInsertionPoint(planOp);
|
||||||
|
FailureOr<Value> lowered =
|
||||||
|
lowerSelectedConv2DPlan(planOp, std::nullopt, /*emitRowStripLayout=*/false, rewriter);
|
||||||
|
if (failed(lowered)) {
|
||||||
|
planOp.emitOpError("failed to lower selected Spatial Conv plan");
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rewriter.replaceOp(planOp, *lowered);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto planOp = dyn_cast<spatial::SpatReluPlanOp>(&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 Relu plan requires a row-strip blueprint result");
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<RowStripPhysicalValue> input = getRowStripValue(rowStripValues, planOp.getInput());
|
||||||
|
rewriter.setInsertionPoint(planOp);
|
||||||
|
FailureOr<Value> lowered = lowerRowStripRelu(*input, planOp, rewriter);
|
||||||
|
if (failed(lowered)) {
|
||||||
|
planOp.emitOpError("failed to lower selected row-strip Spatial Relu 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
rewriter.setInsertionPoint(planOp);
|
||||||
|
auto computeOp = createSpatCompute<1>(
|
||||||
|
rewriter, planOp.getLoc(), planOp.getOutput().getType(), {}, planOp.getInput(), [&](Value x) {
|
||||||
|
auto relu = spatial::SpatReluOp::create(rewriter, planOp.getLoc(), planOp.getOutput().getType(), x);
|
||||||
|
spatial::SpatYieldOp::create(rewriter, planOp.getLoc(), relu.getResult());
|
||||||
|
});
|
||||||
|
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) {
|
||||||
|
rewriter.replaceOp(materializeOp, materializeOp.getInput());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (materializeOp.getSourcePhysicalLayout() != kRowStripLayout
|
||||||
|
|| materializeOp.getTargetPhysicalLayout() != kDenseLayout) {
|
||||||
|
materializeOp.emitOpError("non-dense materialize_layout lowering is not supported yet");
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FailureOr<RowStripPhysicalValue> rowStripValue = getRowStripValue(rowStripValues, materializeOp.getInput());
|
||||||
|
if (failed(rowStripValue)) {
|
||||||
|
materializeOp.emitOpError("expected a row-strip blueprint input during row-strip materialization");
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rewriter.setInsertionPoint(materializeOp);
|
||||||
|
FailureOr<Value> dense = materializeRowStripToDense(*rowStripValue, materializeOp.getLoc(), rewriter);
|
||||||
|
if (failed(dense)) {
|
||||||
|
materializeOp.emitOpError("failed to materialize selected row-strip layout back to dense NCHW");
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rewriter.replaceOp(materializeOp, *dense);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (auto blueprintOp = dyn_cast<spatial::SpatBlueprintOp>(&op)) {
|
||||||
|
if (blueprintOp.getPhysicalLayout() == kDenseLayout) {
|
||||||
|
rewriter.replaceOp(blueprintOp, blueprintOp.getInput());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (blueprintOp.getPhysicalLayout() != kRowStripLayout) {
|
||||||
|
blueprintOp.emitOpError("non-dense blueprint lowering is not supported yet");
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!eraseAfterLowering.contains(blueprintOp)) {
|
||||||
|
blueprintOp.emitOpError("unhandled row-strip blueprint remained during LowerSpatialPlans");
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool erasedAny = true;
|
||||||
|
while (erasedAny) {
|
||||||
|
erasedAny = false;
|
||||||
|
for (Operation& op : llvm::make_early_inc_range(funcOp.getBody().front())) {
|
||||||
|
if (!eraseAfterLowering.contains(&op))
|
||||||
|
continue;
|
||||||
|
if (!op.use_empty())
|
||||||
|
continue;
|
||||||
|
eraseAfterLowering.erase(&op);
|
||||||
|
rewriter.eraseOp(&op);
|
||||||
|
erasedAny = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!eraseAfterLowering.empty()) {
|
||||||
|
for (Operation& op : funcOp.getBody().front())
|
||||||
|
if (eraseAfterLowering.contains(&op))
|
||||||
|
op.emitOpError("selected row-strip planning op could not be fully eliminated during LowerSpatialPlans");
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ConversionTarget helperTarget(*ctx);
|
||||||
|
helperTarget.addLegalDialect<spatial::SpatialDialect,
|
||||||
|
tensor::TensorDialect,
|
||||||
|
linalg::LinalgDialect,
|
||||||
|
affine::AffineDialect,
|
||||||
|
arith::ArithDialect,
|
||||||
|
scf::SCFDialect,
|
||||||
|
func::FuncDialect>();
|
||||||
|
helperTarget.addLegalOp<spatial::SpatGraphCompute, spatial::SpatGraphComputeBatch>();
|
||||||
|
helperTarget.addIllegalOp<ONNXGemmOp, ONNXTransposeOp>();
|
||||||
|
helperTarget.markOpRecursivelyLegal<spatial::SpatGraphCompute, spatial::SpatGraphComputeBatch>();
|
||||||
|
|
||||||
|
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 nestedHelperPatterns([&] {
|
||||||
|
RewritePatternSet patterns(ctx);
|
||||||
|
populateGemmPatterns(patterns, ctx);
|
||||||
|
populateTransposePatterns(patterns, ctx);
|
||||||
|
return patterns;
|
||||||
|
}());
|
||||||
|
ConversionTarget nestedHelperTarget(*ctx);
|
||||||
|
nestedHelperTarget.addLegalDialect<spatial::SpatialDialect,
|
||||||
|
tensor::TensorDialect,
|
||||||
|
linalg::LinalgDialect,
|
||||||
|
affine::AffineDialect,
|
||||||
|
arith::ArithDialect,
|
||||||
|
scf::SCFDialect,
|
||||||
|
func::FuncDialect>();
|
||||||
|
nestedHelperTarget.addIllegalOp<ONNXGemmOp, ONNXTransposeOp>();
|
||||||
|
SmallVector<Operation*> computeLikeOps;
|
||||||
|
funcOp.walk([&](Operation* op) {
|
||||||
|
if (isa<spatial::SpatGraphCompute, spatial::SpatGraphComputeBatch>(op))
|
||||||
|
computeLikeOps.push_back(op);
|
||||||
|
});
|
||||||
|
for (Operation* op : computeLikeOps) {
|
||||||
|
if (failed(applyFullConversion(op, nestedHelperTarget, nestedHelperPatterns))) {
|
||||||
|
op->emitOpError("failed to lower nested helper ONNX ops emitted by selected Spatial plan lowering");
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!verifyLogicalPhase("after nested helper conversions"))
|
||||||
|
return;
|
||||||
|
bool hasIllegalOps = false;
|
||||||
|
moduleOp.walk([&](Operation* op) {
|
||||||
|
if (isa<ONNXEntryPointOp>(op))
|
||||||
|
return;
|
||||||
|
if (isa<spatial::SpatConv2DPlanOp,
|
||||||
|
spatial::SpatBiasAddPlanOp,
|
||||||
|
spatial::SpatReluPlanOp,
|
||||||
|
spatial::SpatBlueprintOp,
|
||||||
|
spatial::SpatMaterializeLayoutOp>(op)
|
||||||
|
|| op->getDialect()->getNamespace() == "onnx") {
|
||||||
|
op->emitOpError("operation must not remain after LowerSpatialPlans");
|
||||||
|
hasIllegalOps = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
PassManager canonicalizationPM(ctx);
|
||||||
|
canonicalizationPM.addPass(createCanonicalizerPass());
|
||||||
|
if (failed(canonicalizationPM.run(moduleOp)))
|
||||||
|
moduleOp.emitWarning("failed to run LowerSpatialPlansPass canonicalization; continuing");
|
||||||
|
|
||||||
|
if (hasIllegalOps) {
|
||||||
|
signalPassFailure();
|
||||||
|
} else {
|
||||||
|
dumpModule(moduleOp, "spatial1_graph");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!verifyLogicalPhase("at the end of LowerSpatialPlans"))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::unique_ptr<Pass> createLowerSpatialPlansPass() { return std::make_unique<LowerSpatialPlansPass>(); }
|
||||||
|
|
||||||
|
} // namespace onnx_mlir
|
||||||
@@ -1,25 +1,25 @@
|
|||||||
#include "mlir/Dialect/Affine/IR/AffineOps.h"
|
#include "mlir/Dialect/Affine/IR/AffineOps.h"
|
||||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||||
#include "mlir/Dialect/Func/IR/FuncOps.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/SCF/IR/SCF.h"
|
||||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
#include "mlir/IR/IRMapping.h"
|
#include "mlir/IR/IRMapping.h"
|
||||||
#include "mlir/Pass/Pass.h"
|
#include "mlir/Pass/Pass.h"
|
||||||
#include "mlir/Pass/PassManager.h"
|
#include "mlir/Pass/PassManager.h"
|
||||||
#include "mlir/Transforms/Passes.h"
|
#include "mlir/Transforms/Passes.h"
|
||||||
#include "mlir/Transforms/WalkPatternRewriteDriver.h"
|
|
||||||
|
|
||||||
#include "llvm/ADT/SmallVector.h"
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
|
||||||
#include "Common/Common.hpp"
|
#include "Common/Common.hpp"
|
||||||
#include "Common/PimCommon.hpp"
|
#include "Common/PimCommon.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/ComputeRegionBuilder.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ConversionPatterns.hpp"
|
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/PostPatterns.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/PrePatterns.hpp"
|
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||||
|
#include "ONNXToSpatialVerifier.hpp"
|
||||||
|
|
||||||
using namespace mlir;
|
using namespace mlir;
|
||||||
|
|
||||||
@@ -43,10 +43,17 @@ struct ONNXToSpatialPass : PassWrapper<ONNXToSpatialPass, OperationPass<ModuleOp
|
|||||||
static void populateEmptyFunction(func::FuncOp funcOp) {
|
static void populateEmptyFunction(func::FuncOp funcOp) {
|
||||||
IRRewriter rewriter(funcOp.getContext());
|
IRRewriter rewriter(funcOp.getContext());
|
||||||
IRMapping mapper;
|
IRMapping mapper;
|
||||||
SmallVector<spatial::SpatCompute> computes(funcOp.getOps<spatial::SpatCompute>());
|
SmallVector<spatial::SpatGraphCompute> computes(funcOp.getOps<spatial::SpatGraphCompute>());
|
||||||
SmallVector<spatial::SpatComputeBatch> computeBatches(funcOp.getOps<spatial::SpatComputeBatch>());
|
SmallVector<spatial::SpatGraphComputeBatch> computeBatches(funcOp.getOps<spatial::SpatGraphComputeBatch>());
|
||||||
if (!computes.empty() || !computeBatches.empty())
|
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::SpatBlueprintOp> blueprints(funcOp.getOps<spatial::SpatBlueprintOp>());
|
||||||
|
SmallVector<spatial::SpatMaterializeLayoutOp> materializers(funcOp.getOps<spatial::SpatMaterializeLayoutOp>());
|
||||||
|
if (!computes.empty() || !computeBatches.empty() || !convPlans.empty() || !biasAddPlans.empty() || !reluPlans.empty()
|
||||||
|
|| !blueprints.empty() || !materializers.empty()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto returnOp = cast<func::ReturnOp>(funcOp.getFunctionBody().front().getTerminator());
|
auto returnOp = cast<func::ReturnOp>(funcOp.getFunctionBody().front().getTerminator());
|
||||||
rewriter.setInsertionPoint(returnOp);
|
rewriter.setInsertionPoint(returnOp);
|
||||||
@@ -60,16 +67,16 @@ static void populateEmptyFunction(func::FuncOp funcOp) {
|
|||||||
sourceLocs.push_back(source.getLoc());
|
sourceLocs.push_back(source.getLoc());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto newCompute = spatial::SpatCompute::create(
|
auto newCompute = createEmptySpatGraphCompute(
|
||||||
rewriter, returnOp.getLoc(), returnOp.getOperandTypes(), funcOp.getArguments(), {}, {});
|
rewriter, returnOp.getLoc(), returnOp.getOperandTypes(), {}, funcOp.getArguments(), sourceTypes, sourceLocs);
|
||||||
auto* newBlock = rewriter.createBlock(&newCompute.getBody(), newCompute.getBody().end(), sourceTypes, sourceLocs);
|
auto* newBlock = &newCompute.getBody().front();
|
||||||
for (auto [blockArg, computeArg] : llvm::zip(newBlock->getArguments(), newCompute.getOperands()))
|
for (auto [blockArg, computeArg] : llvm::zip(newBlock->getArguments(), newCompute.getOperands()))
|
||||||
mapper.map(computeArg, blockArg);
|
mapper.map(computeArg, blockArg);
|
||||||
newCompute.getProperties().setOperandSegmentSizes({0, static_cast<int>(sourceTypes.size())});
|
newCompute.getProperties().setOperandSegmentSizes({0, static_cast<int>(sourceTypes.size())});
|
||||||
|
|
||||||
rewriter.setInsertionPointToEnd(newBlock);
|
rewriter.setInsertionPointToEnd(newBlock);
|
||||||
for (Operation& op : funcOp.getOps())
|
for (Operation& op : funcOp.getOps())
|
||||||
if (!isa<spatial::SpatCompute, func::ReturnOp>(&op))
|
if (!isa<spatial::SpatGraphCompute, func::ReturnOp>(&op))
|
||||||
rewriter.clone(op, mapper);
|
rewriter.clone(op, mapper);
|
||||||
|
|
||||||
auto yield = spatial::SpatYieldOp::create(rewriter, funcOp.getLoc(), returnOp.getOperands());
|
auto yield = spatial::SpatYieldOp::create(rewriter, funcOp.getLoc(), returnOp.getOperands());
|
||||||
@@ -77,7 +84,7 @@ static void populateEmptyFunction(func::FuncOp funcOp) {
|
|||||||
yield.setOperand(i, mapper.lookupOrDefault(yield.getOperand(i)));
|
yield.setOperand(i, mapper.lookupOrDefault(yield.getOperand(i)));
|
||||||
|
|
||||||
for (Operation& op : llvm::make_early_inc_range(funcOp.getOps()))
|
for (Operation& op : llvm::make_early_inc_range(funcOp.getOps()))
|
||||||
if (!isa<spatial::SpatCompute, func::ReturnOp>(&op)) {
|
if (!isa<spatial::SpatGraphCompute, func::ReturnOp>(&op)) {
|
||||||
op.dropAllUses();
|
op.dropAllUses();
|
||||||
rewriter.eraseOp(&op);
|
rewriter.eraseOp(&op);
|
||||||
}
|
}
|
||||||
@@ -86,30 +93,6 @@ static void populateEmptyFunction(func::FuncOp funcOp) {
|
|||||||
returnOp.setOperand(index, computeResult);
|
returnOp.setOperand(index, computeResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void wrapTopLevelRuntimeTransposes(func::FuncOp funcOp) {
|
|
||||||
IRRewriter rewriter(funcOp.getContext());
|
|
||||||
Block& entryBlock = funcOp.getFunctionBody().front();
|
|
||||||
|
|
||||||
for (Operation& op : llvm::make_early_inc_range(entryBlock)) {
|
|
||||||
auto transposeOp = dyn_cast<ONNXTransposeOp>(&op);
|
|
||||||
if (!transposeOp || isCompileTimeOp(transposeOp))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Transpose stays globally legal because constant/view-only cases are
|
|
||||||
// allowed on the host. Any residual runtime transpose must be sunk into
|
|
||||||
// spat.compute before the host legality check.
|
|
||||||
auto resultType = transposeOp.getResult().getType();
|
|
||||||
rewriter.setInsertionPoint(transposeOp);
|
|
||||||
auto computeOp = createSpatCompute<1>(
|
|
||||||
rewriter, transposeOp.getLoc(), TypeRange {resultType}, {}, ValueRange {transposeOp.getData()}, [&](Value input) {
|
|
||||||
Value transposed =
|
|
||||||
ONNXTransposeOp::create(rewriter, transposeOp.getLoc(), resultType, input, transposeOp.getPermAttr());
|
|
||||||
spatial::SpatYieldOp::create(rewriter, transposeOp.getLoc(), transposed);
|
|
||||||
});
|
|
||||||
rewriter.replaceOp(transposeOp, computeOp.getResult(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ONNXToSpatialPass::runOnOperation() {
|
void ONNXToSpatialPass::runOnOperation() {
|
||||||
ModuleOp moduleOp = getOperation();
|
ModuleOp moduleOp = getOperation();
|
||||||
MLIRContext* ctx = &getContext();
|
MLIRContext* ctx = &getContext();
|
||||||
@@ -117,11 +100,12 @@ void ONNXToSpatialPass::runOnOperation() {
|
|||||||
ConversionTarget preTarget(*ctx);
|
ConversionTarget preTarget(*ctx);
|
||||||
preTarget.addLegalDialect<spatial::SpatialDialect,
|
preTarget.addLegalDialect<spatial::SpatialDialect,
|
||||||
ONNXDialect,
|
ONNXDialect,
|
||||||
|
linalg::LinalgDialect,
|
||||||
tensor::TensorDialect,
|
tensor::TensorDialect,
|
||||||
affine::AffineDialect,
|
affine::AffineDialect,
|
||||||
arith::ArithDialect,
|
arith::ArithDialect,
|
||||||
scf::SCFDialect>();
|
scf::SCFDialect>();
|
||||||
preTarget.addIllegalOp<ONNXConstantOp, ONNXFlattenOp>();
|
preTarget.addIllegalOp<ONNXConstantOp>();
|
||||||
|
|
||||||
RewritePatternSet prePatterns(ctx);
|
RewritePatternSet prePatterns(ctx);
|
||||||
populatePrePatterns(prePatterns, ctx);
|
populatePrePatterns(prePatterns, ctx);
|
||||||
@@ -138,30 +122,18 @@ void ONNXToSpatialPass::runOnOperation() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RewritePatternSet matmulPatterns(ctx);
|
|
||||||
populateMatMulRewritePatterns(matmulPatterns, ctx);
|
|
||||||
walkAndApplyPatterns(moduleOp, std::move(matmulPatterns));
|
|
||||||
|
|
||||||
bool hasUnloweredMatMul = false;
|
|
||||||
moduleOp.walk([&](ONNXMatMulOp matmulOp) {
|
|
||||||
hasUnloweredMatMul = true;
|
|
||||||
matmulOp.emitOpError("remaining ONNX MatMul before the required ONNX-to-Spatial conversion");
|
|
||||||
});
|
|
||||||
if (hasUnloweredMatMul) {
|
|
||||||
moduleOp.emitError("failed to lower all ONNX MatMul ops before ONNX-to-Spatial conversion");
|
|
||||||
signalPassFailure();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConversionTarget target(*ctx);
|
ConversionTarget target(*ctx);
|
||||||
target.addLegalDialect<spatial::SpatialDialect,
|
target.addLegalDialect<spatial::SpatialDialect,
|
||||||
ONNXDialect,
|
ONNXDialect,
|
||||||
|
linalg::LinalgDialect,
|
||||||
tensor::TensorDialect,
|
tensor::TensorDialect,
|
||||||
affine::AffineDialect,
|
affine::AffineDialect,
|
||||||
arith::ArithDialect,
|
arith::ArithDialect,
|
||||||
scf::SCFDialect>();
|
scf::SCFDialect>();
|
||||||
target.addIllegalOp<ONNXMatMulOp>();
|
target.addIllegalOp<ONNXMatMulOp>();
|
||||||
|
target.addIllegalOp<ONNXTransposeOp>();
|
||||||
target.addIllegalOp<ONNXAddOp>();
|
target.addIllegalOp<ONNXAddOp>();
|
||||||
|
target.addIllegalOp<ONNXSubOp>();
|
||||||
target.addIllegalOp<ONNXDivOp>();
|
target.addIllegalOp<ONNXDivOp>();
|
||||||
target.addIllegalOp<ONNXMulOp>();
|
target.addIllegalOp<ONNXMulOp>();
|
||||||
target.addIllegalOp<ONNXGemmOp>();
|
target.addIllegalOp<ONNXGemmOp>();
|
||||||
@@ -172,10 +144,13 @@ void ONNXToSpatialPass::runOnOperation() {
|
|||||||
target.addIllegalOp<ONNXSigmoidOp>();
|
target.addIllegalOp<ONNXSigmoidOp>();
|
||||||
target.addIllegalOp<ONNXSoftmaxOp>();
|
target.addIllegalOp<ONNXSoftmaxOp>();
|
||||||
target.addIllegalOp<ONNXConcatOp>();
|
target.addIllegalOp<ONNXConcatOp>();
|
||||||
|
target.addIllegalOp<ONNXFlattenOp>();
|
||||||
target.addIllegalOp<ONNXGatherOp>();
|
target.addIllegalOp<ONNXGatherOp>();
|
||||||
target.addIllegalOp<ONNXReshapeOp>();
|
target.addIllegalOp<ONNXReshapeOp>();
|
||||||
target.addIllegalOp<ONNXResizeOp>();
|
target.addIllegalOp<ONNXResizeOp>();
|
||||||
|
target.addIllegalOp<ONNXSliceOp>();
|
||||||
target.addIllegalOp<ONNXLRNOp>();
|
target.addIllegalOp<ONNXLRNOp>();
|
||||||
|
target.addIllegalOp<ONNXReduceMeanOp>();
|
||||||
target.addIllegalOp<ONNXReduceMeanV13Op>();
|
target.addIllegalOp<ONNXReduceMeanV13Op>();
|
||||||
target.addIllegalOp<ONNXSplitOp>();
|
target.addIllegalOp<ONNXSplitOp>();
|
||||||
|
|
||||||
@@ -187,33 +162,45 @@ void ONNXToSpatialPass::runOnOperation() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (failed(verifyLogicalSpatialGraphInvariants(*entryFunc))) {
|
||||||
|
moduleOp.emitError("logical Spatial graph verification failed after ONNX conversion");
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
ConversionTarget earlyPostTarget(*ctx);
|
ConversionTarget earlyPostTarget(*ctx);
|
||||||
earlyPostTarget.addLegalDialect<spatial::SpatialDialect,
|
earlyPostTarget.addLegalDialect<spatial::SpatialDialect,
|
||||||
ONNXDialect,
|
ONNXDialect,
|
||||||
|
linalg::LinalgDialect,
|
||||||
tensor::TensorDialect,
|
tensor::TensorDialect,
|
||||||
affine::AffineDialect,
|
affine::AffineDialect,
|
||||||
arith::ArithDialect,
|
arith::ArithDialect,
|
||||||
scf::SCFDialect>();
|
scf::SCFDialect>();
|
||||||
|
|
||||||
PassManager cleanupPM(ctx);
|
|
||||||
cleanupPM.addPass(createCanonicalizerPass());
|
|
||||||
if (failed(cleanupPM.run(moduleOp)))
|
|
||||||
moduleOp.emitWarning("failed to run ONNX-to-Spatial canonicalization cleanup; continuing");
|
|
||||||
|
|
||||||
annotateWeightsConstants(*entryFunc);
|
annotateWeightsConstants(*entryFunc);
|
||||||
|
|
||||||
|
if (failed(verifyLogicalSpatialGraphInvariants(*entryFunc))) {
|
||||||
|
moduleOp.emitError("logical Spatial graph verification failed after weight annotation");
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
ConversionTarget postTarget(*ctx);
|
ConversionTarget postTarget(*ctx);
|
||||||
postTarget.addLegalDialect<spatial::SpatialDialect,
|
postTarget.addLegalDialect<spatial::SpatialDialect,
|
||||||
ONNXDialect,
|
ONNXDialect,
|
||||||
|
linalg::LinalgDialect,
|
||||||
tensor::TensorDialect,
|
tensor::TensorDialect,
|
||||||
affine::AffineDialect,
|
affine::AffineDialect,
|
||||||
arith::ArithDialect,
|
arith::ArithDialect,
|
||||||
scf::SCFDialect>();
|
scf::SCFDialect>();
|
||||||
postTarget.addDynamicallyLegalOp<spatial::SpatCompute>(
|
postTarget.addDynamicallyLegalOp<spatial::SpatGraphCompute>(
|
||||||
[](spatial::SpatCompute computeOp) { return !requiresPostRewrite(computeOp); });
|
[](spatial::SpatGraphCompute computeOp) { return !requiresPostRewrite(computeOp); });
|
||||||
postTarget.addDynamicallyLegalOp<spatial::SpatComputeBatch>(
|
postTarget.addDynamicallyLegalOp<spatial::SpatGraphComputeBatch>(
|
||||||
[](spatial::SpatComputeBatch computeOp) { return !requiresPostRewrite(computeOp); });
|
[](spatial::SpatGraphComputeBatch computeOp) { return !requiresPostRewrite(computeOp); });
|
||||||
|
|
||||||
|
if (failed(verifyLogicalSpatialGraphInvariants(*entryFunc))) {
|
||||||
|
moduleOp.emitError("logical Spatial graph verification failed before post rewrites");
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
RewritePatternSet postPatterns(ctx);
|
RewritePatternSet postPatterns(ctx);
|
||||||
populatePostPatterns(postPatterns, ctx);
|
populatePostPatterns(postPatterns, ctx);
|
||||||
if (failed(applyPartialConversion(*entryFunc, postTarget, std::move(postPatterns)))) {
|
if (failed(applyPartialConversion(*entryFunc, postTarget, std::move(postPatterns)))) {
|
||||||
@@ -222,17 +209,24 @@ void ONNXToSpatialPass::runOnOperation() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
wrapTopLevelRuntimeTransposes(*entryFunc);
|
populateEmptyFunction(*entryFunc);
|
||||||
|
|
||||||
|
PassManager canonicalizationPM(ctx);
|
||||||
|
canonicalizationPM.addPass(createCanonicalizerPass());
|
||||||
|
if (failed(canonicalizationPM.run(moduleOp)))
|
||||||
|
moduleOp.emitWarning("failed to run ONNXToSpatial canonicalization; continuing");
|
||||||
|
|
||||||
|
dumpModule(moduleOp, "spatial0");
|
||||||
|
|
||||||
|
if (failed(verifyLogicalSpatialGraphInvariants(*entryFunc))) {
|
||||||
|
moduleOp.emitError("logical Spatial graph verification failed after ONNX-to-Spatial");
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (failed(verifyONNXToSpatial(*entryFunc))) {
|
if (failed(verifyONNXToSpatial(*entryFunc))) {
|
||||||
moduleOp.emitError("ONNX-to-Spatial host legality verification failed");
|
moduleOp.emitError("ONNX-to-Spatial host legality verification failed");
|
||||||
signalPassFailure();
|
signalPassFailure();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
populateEmptyFunction(*entryFunc);
|
|
||||||
|
|
||||||
dumpModule(moduleOp, "spatial0");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Pass> createONNXToSpatialPass() { return std::make_unique<ONNXToSpatialPass>(); }
|
std::unique_ptr<Pass> createONNXToSpatialPass() { return std::make_unique<ONNXToSpatialPass>(); }
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
||||||
|
#include "mlir/IR/BuiltinAttributes.h"
|
||||||
|
#include "mlir/IR/Diagnostics.h"
|
||||||
#include "mlir/Support/LLVM.h"
|
#include "mlir/Support/LLVM.h"
|
||||||
|
|
||||||
#include "Common/IR/WeightUtils.hpp"
|
#include "Common/IR/WeightUtils.hpp"
|
||||||
@@ -13,6 +15,8 @@ namespace onnx_mlir {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
constexpr StringLiteral kPhaseMarker = "phase-check";
|
||||||
|
|
||||||
void checkWeightUseChains(func::FuncOp func, pim::CappedDiagnosticReporter& diagnostics) {
|
void checkWeightUseChains(func::FuncOp func, pim::CappedDiagnosticReporter& diagnostics) {
|
||||||
func.walk([&](Operation* op) {
|
func.walk([&](Operation* op) {
|
||||||
if (!hasWeightAlways(op))
|
if (!hasWeightAlways(op))
|
||||||
@@ -23,134 +27,205 @@ void checkWeightUseChains(func::FuncOp func, pim::CappedDiagnosticReporter& diag
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
diagnostics.report(op, [&](Operation* illegalOp) {
|
diagnostics.report(op, [&](Operation* illegalOp) {
|
||||||
illegalOp->emitOpError(
|
illegalOp->emitOpError()
|
||||||
"weight-marked values may only flow through static view/slice helper chains into Spatial VMM weights");
|
<< kPhaseMarker
|
||||||
|
<< " weight-marked values may only flow through static view/slice helper chains into Spatial VMM weights";
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Region* getParentRegion(Value value) {
|
bool isRegionOrAncestorOf(Region& region, Region* candidate) {
|
||||||
if (auto blockArg = dyn_cast<BlockArgument>(value))
|
return candidate && (®ion == candidate || region.isAncestor(candidate));
|
||||||
return blockArg.getOwner()->getParent();
|
|
||||||
if (Operation* definingOp = value.getDefiningOp())
|
|
||||||
return definingOp->getParentRegion();
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isDefinedInsideRegion(Value value, Region& region) {
|
bool isValueDefinedInsideRegion(Value value, Region& region) {
|
||||||
Region* parentRegion = getParentRegion(value);
|
if (auto blockArg = dyn_cast<BlockArgument>(value))
|
||||||
return parentRegion && (®ion == parentRegion || region.isAncestor(parentRegion));
|
return isRegionOrAncestorOf(region, blockArg.getOwner()->getParent());
|
||||||
|
if (Operation* definingOp = value.getDefiningOp())
|
||||||
|
return isRegionOrAncestorOf(region, definingOp->getParentRegion());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isLegalExternalCapture(Value value, Region& region) {
|
||||||
|
if (isValueDefinedInsideRegion(value, region))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
Operation* definingOp = value.getDefiningOp();
|
||||||
|
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) || isRecordedDeferredCommunicationSource(nestedOp, value))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Operation* definingOp = value.getDefiningOp();
|
||||||
|
diagnostics.report(compute.getOperation(), [&](Operation* illegalOp) {
|
||||||
|
InFlightDiagnostic diag =
|
||||||
|
illegalOp->emitOpError() << kPhaseMarker << " " << kind << " body captures non-constant external operand #"
|
||||||
|
<< operand.getOperandNumber() << " used by " << nestedOp->getName().getStringRef();
|
||||||
|
diag << " (type " << value.getType() << ")";
|
||||||
|
if (definingOp)
|
||||||
|
diag.attachNote(definingOp->getLoc()) << "defining op is " << definingOp->getName().getStringRef();
|
||||||
|
else if (auto blockArg = dyn_cast<BlockArgument>(value)) {
|
||||||
|
if (Operation* owner = blockArg.getOwner()->getParentOp())
|
||||||
|
diag.attachNote(owner->getLoc())
|
||||||
|
<< "external block argument belongs to " << owner->getName().getStringRef();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isLegalHostBackedValue(Value value) {
|
bool isLegalHostBackedValue(Value value) {
|
||||||
Operation* definingOp = value.getDefiningOp();
|
Operation* definingOp = value.getDefiningOp();
|
||||||
if (!definingOp)
|
if (!definingOp)
|
||||||
return isa<BlockArgument>(value);
|
return isa<BlockArgument>(value);
|
||||||
|
|
||||||
if (isa<spatial::SpatChannelReceiveOp>(definingOp))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return definingOp->getDialect()->getNamespace() != "spat";
|
return definingOp->getDialect()->getNamespace() != "spat";
|
||||||
}
|
}
|
||||||
|
|
||||||
LogicalResult verifyComputeLikeInputs(Operation* computeLikeOp,
|
bool isScheduledPhase1Value(Value value) {
|
||||||
ValueRange inputs,
|
Operation* definingOp = value.getDefiningOp();
|
||||||
bool allowChannelReceiveInputs,
|
return isa_and_nonnull<spatial::SpatScheduledCompute, spatial::SpatScheduledComputeBatch>(definingOp);
|
||||||
StringRef kind,
|
}
|
||||||
pim::CappedDiagnosticReporter& diagnostics) {
|
|
||||||
for (auto [inputIndex, input] : llvm::enumerate(inputs)) {
|
template <typename ComputeOpTy>
|
||||||
unsigned currentInputIndex = inputIndex;
|
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();
|
Operation* definingOp = input.getDefiningOp();
|
||||||
if (allowChannelReceiveInputs && isa_and_nonnull<spatial::SpatChannelReceiveOp>(definingOp))
|
if (allowChannelReceiveInputs && isa_and_nonnull<spatial::SpatChannelReceiveOp>(definingOp))
|
||||||
continue;
|
continue;
|
||||||
|
if (isScheduledPhase1Value(input))
|
||||||
|
continue;
|
||||||
if (isLegalHostBackedValue(input))
|
if (isLegalHostBackedValue(input))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
diagnostics.report(computeLikeOp, [&](Operation* illegalOp) {
|
diagnostics.report(compute.getOperation(), [&](Operation* illegalOp) {
|
||||||
InFlightDiagnostic diagnostic = illegalOp->emitOpError() << kind << " input #" << currentInputIndex
|
InFlightDiagnostic diag = illegalOp->emitOpError()
|
||||||
<< (allowChannelReceiveInputs
|
<< kPhaseMarker << " " << kind << " input #" << currentInputIndex
|
||||||
? " must come from the host or an explicit "
|
<< (allowChannelReceiveInputs ? " must come from the host or explicit spat.channel_receive"
|
||||||
"spat.channel_receive"
|
: " must come from the host");
|
||||||
: " must come from the host");
|
|
||||||
if (definingOp)
|
if (definingOp)
|
||||||
diagnostic.attachNote(definingOp->getLoc()) << "illegal Spatial producer is " << definingOp->getName();
|
diag.attachNote(definingOp->getLoc()) << "illegal producer is " << definingOp->getName().getStringRef();
|
||||||
});
|
});
|
||||||
return failure();
|
|
||||||
}
|
}
|
||||||
return success();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void verifyNoExternalTensorCaptures(Operation* ownerOp,
|
template <typename ComputeOpTy>
|
||||||
Region& region,
|
void verifyNoNestedFragmentAssemblyBlueprints(ComputeOpTy compute,
|
||||||
StringRef kind,
|
pim::CappedDiagnosticReporter& diagnostics) {
|
||||||
pim::CappedDiagnosticReporter& diagnostics) {
|
compute.getBody().walk([&](spatial::SpatBlueprintOp blueprint) {
|
||||||
region.walk([&](Operation* op) {
|
std::optional<StringRef> mode = blueprint.getMode();
|
||||||
for (OpOperand& operand : op->getOpOperands()) {
|
if (!mode || *mode != "fragment_assembly")
|
||||||
Value value = operand.get();
|
return;
|
||||||
if (!isa<TensorType>(value.getType()))
|
diagnostics.report(blueprint.getOperation(), [&](Operation* illegalOp) {
|
||||||
continue;
|
illegalOp->emitOpError("fragment assembly blueprint must be host-level after merge materialization");
|
||||||
if (isDefinedInsideRegion(value, region) || isa<BlockArgument>(value))
|
});
|
||||||
continue;
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Operation* definingOp = value.getDefiningOp();
|
void verifyLogicalTopLevelOps(func::FuncOp funcOp, pim::CappedDiagnosticReporter& diagnostics) {
|
||||||
if (definingOp && definingOp->hasTrait<OpTrait::ConstantLike>())
|
for (Operation& op : funcOp.getOps()) {
|
||||||
continue;
|
if (isa<func::ReturnOp,
|
||||||
|
spatial::SpatGraphCompute,
|
||||||
|
spatial::SpatGraphComputeBatch,
|
||||||
|
spatial::SpatConv2DPlanOp,
|
||||||
|
spatial::SpatBiasAddPlanOp,
|
||||||
|
spatial::SpatReluPlanOp,
|
||||||
|
spatial::SpatBlueprintOp,
|
||||||
|
spatial::SpatMaterializeLayoutOp>(&op)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isa<spatial::SpatScheduledCompute, spatial::SpatScheduledComputeBatch>(&op)) {
|
||||||
|
diagnostics.report(&op, [&](Operation* illegalOp) {
|
||||||
|
illegalOp->emitOpError() << kPhaseMarker << " scheduled Spatial compute op is not allowed in logical graph phase";
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isa<spatial::SpatChannelReceiveOp, spatial::SpatChannelSendOp>(&op)) {
|
||||||
|
diagnostics.report(&op, [&](Operation* illegalOp) {
|
||||||
|
illegalOp->emitOpError() << kPhaseMarker
|
||||||
|
<< " explicit channel communication is not expected before merge materialization";
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isCompileTimeOp(&op))
|
||||||
|
continue;
|
||||||
|
|
||||||
diagnostics.report(ownerOp, [&](Operation* illegalOp) {
|
diagnostics.report(&op, [&](Operation* illegalOp) {
|
||||||
InFlightDiagnostic diagnostic = illegalOp->emitOpError() << kind << " body may not capture external tensor "
|
illegalOp->emitOpError()
|
||||||
<< "values";
|
<< kPhaseMarker << " non-foldable top-level runtime op remains in logical Spatial graph; lower it inside spat.graph_compute";
|
||||||
diagnostic.attachNote(op->getLoc())
|
});
|
||||||
<< "tensor operand #" << operand.getOperandNumber() << " is defined outside the compute body by "
|
}
|
||||||
<< (definingOp ? definingOp->getName().getStringRef() : StringRef("<block argument>"));
|
}
|
||||||
|
|
||||||
|
void verifyScheduledTopLevelOps(func::FuncOp funcOp, pim::CappedDiagnosticReporter& diagnostics) {
|
||||||
|
for (Operation& op : funcOp.getOps()) {
|
||||||
|
if (isa<spatial::SpatChannelSendOp, spatial::SpatChannelReceiveOp>(&op)) {
|
||||||
|
diagnostics.report(&op, [&](Operation* illegalOp) {
|
||||||
|
illegalOp->emitOpError() << kPhaseMarker << " real channel communication is not allowed in scheduled phase 1";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
LogicalResult verifyONNXToSpatial(func::FuncOp funcOp) {
|
LogicalResult verifyNoComputeBodyCaptures(func::FuncOp funcOp) {
|
||||||
pim::CappedDiagnosticReporter diagnostics;
|
pim::CappedDiagnosticReporter diagnostics;
|
||||||
|
for (auto compute : funcOp.getOps<spatial::SpatGraphCompute>())
|
||||||
for (Operation& op : funcOp.getOps()) {
|
verifyComputeBodyCaptures(compute, "graph_compute", diagnostics);
|
||||||
if (isa<func::ReturnOp, spatial::SpatCompute, spatial::SpatComputeBatch>(&op))
|
for (auto batch : funcOp.getOps<spatial::SpatGraphComputeBatch>())
|
||||||
continue;
|
verifyComputeBodyCaptures(batch, "graph_compute_batch", diagnostics);
|
||||||
if (isCompileTimeOp(&op))
|
for (auto compute : funcOp.getOps<spatial::SpatScheduledCompute>())
|
||||||
continue;
|
verifyComputeBodyCaptures(compute, "scheduled_compute", diagnostics);
|
||||||
|
for (auto batch : funcOp.getOps<spatial::SpatScheduledComputeBatch>())
|
||||||
diagnostics.report(&op, [](Operation* illegalOp) {
|
verifyComputeBodyCaptures(batch, "scheduled_compute_batch", diagnostics);
|
||||||
illegalOp->emitOpError(
|
diagnostics.emitSuppressedSummary(funcOp, "compute body capture verification failed");
|
||||||
"non-foldable top-level runtime op remains after ONNX-to-Spatial; lower it inside spat.compute");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
checkWeightUseChains(funcOp, diagnostics);
|
|
||||||
diagnostics.emitSuppressedSummary(funcOp, "ONNX-to-Spatial verification failed");
|
|
||||||
|
|
||||||
return success(!diagnostics.hasFailure());
|
return success(!diagnostics.hasFailure());
|
||||||
}
|
}
|
||||||
|
|
||||||
LogicalResult verifySpatialCommunicationInvariants(func::FuncOp funcOp) {
|
LogicalResult verifyONNXToSpatial(func::FuncOp funcOp) { return verifyLogicalSpatialGraphInvariants(funcOp); }
|
||||||
|
|
||||||
|
LogicalResult verifyLogicalSpatialGraphInvariants(func::FuncOp funcOp) {
|
||||||
pim::CappedDiagnosticReporter diagnostics;
|
pim::CappedDiagnosticReporter diagnostics;
|
||||||
|
verifyLogicalTopLevelOps(funcOp, diagnostics);
|
||||||
|
checkWeightUseChains(funcOp, diagnostics);
|
||||||
|
if (failed(verifyNoComputeBodyCaptures(funcOp)))
|
||||||
|
return failure();
|
||||||
|
diagnostics.emitSuppressedSummary(funcOp, "logical Spatial graph verification failed");
|
||||||
|
return success(!diagnostics.hasFailure());
|
||||||
|
}
|
||||||
|
|
||||||
for (auto computeOp : funcOp.getOps<spatial::SpatCompute>()) {
|
LogicalResult verifyScheduledSpatialInvariants(func::FuncOp funcOp) {
|
||||||
(void)verifyComputeLikeInputs(
|
pim::CappedDiagnosticReporter diagnostics;
|
||||||
computeOp.getOperation(), computeOp.getInputs(), /*allowChannelReceiveInputs=*/true, "spat.compute", diagnostics);
|
verifyScheduledTopLevelOps(funcOp, diagnostics);
|
||||||
verifyNoExternalTensorCaptures(computeOp.getOperation(), computeOp.getBody(), "spat.compute", diagnostics);
|
for (auto compute : funcOp.getOps<spatial::SpatScheduledCompute>()) {
|
||||||
|
verifyScheduledInputs(compute, /*allowChannelReceiveInputs=*/true, "spat.scheduled_compute", diagnostics);
|
||||||
|
verifyNoNestedFragmentAssemblyBlueprints(compute, diagnostics);
|
||||||
}
|
}
|
||||||
|
for (auto batch : funcOp.getOps<spatial::SpatScheduledComputeBatch>()) {
|
||||||
for (auto computeBatchOp : funcOp.getOps<spatial::SpatComputeBatch>()) {
|
verifyScheduledInputs(batch, /*allowChannelReceiveInputs=*/false, "spat.scheduled_compute_batch", diagnostics);
|
||||||
(void)verifyComputeLikeInputs(computeBatchOp.getOperation(),
|
verifyNoNestedFragmentAssemblyBlueprints(batch, diagnostics);
|
||||||
computeBatchOp.getInputs(),
|
|
||||||
/*allowChannelReceiveInputs=*/false,
|
|
||||||
"spat.compute_batch",
|
|
||||||
diagnostics);
|
|
||||||
verifyNoExternalTensorCaptures(
|
|
||||||
computeBatchOp.getOperation(), computeBatchOp.getBody(), "spat.compute_batch", diagnostics);
|
|
||||||
}
|
}
|
||||||
|
if (failed(verifyNoComputeBodyCaptures(funcOp)))
|
||||||
diagnostics.emitSuppressedSummary(funcOp, "Spatial communication invariant verification failed");
|
return failure();
|
||||||
|
diagnostics.emitSuppressedSummary(funcOp, "scheduled Spatial verification failed");
|
||||||
return success(!diagnostics.hasFailure());
|
return success(!diagnostics.hasFailure());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
|
|
||||||
mlir::LogicalResult verifyONNXToSpatial(mlir::func::FuncOp funcOp);
|
mlir::LogicalResult verifyONNXToSpatial(mlir::func::FuncOp funcOp);
|
||||||
mlir::LogicalResult verifySpatialCommunicationInvariants(mlir::func::FuncOp funcOp);
|
mlir::LogicalResult verifyNoComputeBodyCaptures(mlir::func::FuncOp funcOp);
|
||||||
|
mlir::LogicalResult verifyLogicalSpatialGraphInvariants(mlir::func::FuncOp funcOp);
|
||||||
|
mlir::LogicalResult verifyScheduledSpatialInvariants(mlir::func::FuncOp funcOp);
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
|
|||||||
+12
-9
@@ -1,20 +1,16 @@
|
|||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ConversionPatterns.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||||
|
|
||||||
using namespace mlir;
|
using namespace mlir;
|
||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
|
|
||||||
namespace {
|
void populatePrePatterns(RewritePatternSet& patterns, MLIRContext* ctx) { populateGeneratedPrePatterns(patterns, ctx); }
|
||||||
|
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ONNXToSpatial.hpp.inc"
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void populateConversionPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx) {
|
|
||||||
patterns.add<removeLRN>(ctx);
|
|
||||||
|
|
||||||
|
void populateConversionPatterns(RewritePatternSet& patterns, MLIRContext* ctx) {
|
||||||
|
populateGeneratedConversionPatterns(patterns, ctx);
|
||||||
populateElementwisePatterns(patterns, ctx);
|
populateElementwisePatterns(patterns, ctx);
|
||||||
|
populateMatMulRewritePatterns(patterns, ctx);
|
||||||
populateGemmPatterns(patterns, ctx);
|
populateGemmPatterns(patterns, ctx);
|
||||||
populateConvPatterns(patterns, ctx);
|
populateConvPatterns(patterns, ctx);
|
||||||
populatePoolPatterns(patterns, ctx);
|
populatePoolPatterns(patterns, ctx);
|
||||||
@@ -23,10 +19,17 @@ void populateConversionPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRCon
|
|||||||
populateSigmoidPatterns(patterns, ctx);
|
populateSigmoidPatterns(patterns, ctx);
|
||||||
populateSoftmaxPatterns(patterns, ctx);
|
populateSoftmaxPatterns(patterns, ctx);
|
||||||
populateConcatPatterns(patterns, ctx);
|
populateConcatPatterns(patterns, ctx);
|
||||||
|
populateFlattenPatterns(patterns, ctx);
|
||||||
populateGatherPatterns(patterns, ctx);
|
populateGatherPatterns(patterns, ctx);
|
||||||
populateResizePatterns(patterns, ctx);
|
populateResizePatterns(patterns, ctx);
|
||||||
populateReshapePatterns(patterns, ctx);
|
populateReshapePatterns(patterns, ctx);
|
||||||
|
populateSlicePatterns(patterns, ctx);
|
||||||
populateSplitPatterns(patterns, ctx);
|
populateSplitPatterns(patterns, ctx);
|
||||||
|
populateTransposePatterns(patterns, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void populatePostPatterns(RewritePatternSet& patterns, MLIRContext* ctx) {
|
||||||
|
populateWeightPromotionPatterns(patterns, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
+16
-13
@@ -1,38 +1,41 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
||||||
#include "mlir/IR/MLIRContext.h"
|
#include "mlir/IR/MLIRContext.h"
|
||||||
#include "mlir/Transforms/DialectConversion.h"
|
#include "mlir/Transforms/DialectConversion.h"
|
||||||
|
|
||||||
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
|
|
||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
|
|
||||||
|
void populatePrePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
void populateConversionPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
void populateConversionPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
|
void populatePostPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
|
|
||||||
|
void populateGeneratedPrePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
|
void populateGeneratedConversionPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
|
void populateWeightPromotionPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
|
|
||||||
void populateConvPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
void populateConvPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
|
|
||||||
void populateElementwisePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
void populateElementwisePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
|
|
||||||
void populateGemmPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
void populateGemmPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
|
|
||||||
void populateMatMulRewritePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
void populateMatMulRewritePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
|
|
||||||
void populatePoolPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
void populatePoolPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
|
|
||||||
void populateReduceMeanPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
void populateReduceMeanPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
|
|
||||||
void populateReluPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
void populateReluPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
|
|
||||||
void populateSigmoidPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
void populateSigmoidPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
|
|
||||||
void populateSoftmaxPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
void populateSoftmaxPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
|
|
||||||
void populateConcatPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
void populateConcatPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
|
void populateFlattenPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
void populateGatherPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
void populateGatherPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
|
|
||||||
void populateResizePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
void populateResizePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
|
|
||||||
void populateReshapePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
void populateReshapePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
|
void populateSlicePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
void populateSplitPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
void populateSplitPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
|
void populateTransposePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
||||||
|
|
||||||
|
bool requiresPostRewrite(spatial::SpatGraphCompute computeOp);
|
||||||
|
bool requiresPostRewrite(spatial::SpatGraphComputeBatch computeOp);
|
||||||
|
void annotateWeightsConstants(mlir::func::FuncOp funcOp);
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||||
|
|
||||||
|
using namespace mlir;
|
||||||
|
|
||||||
|
namespace onnx_mlir {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ONNXToSpatial.hpp.inc"
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void populateGeneratedConversionPatterns(RewritePatternSet& patterns, MLIRContext* ctx) {
|
||||||
|
patterns.add<removeLRN>(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace onnx_mlir
|
||||||
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,9 +5,9 @@
|
|||||||
|
|
||||||
#include "llvm/ADT/SmallVector.h"
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
|
||||||
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/BiasAddUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ConversionPatterns.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||||
|
|
||||||
@@ -47,43 +47,33 @@ static FailureOr<Value> materializeBroadcastedConstantTensor(Value value,
|
|||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
const int64_t rankOffset = static_cast<int64_t>(resultShape.size() - sourceShape.size());
|
const int64_t rankOffset = static_cast<int64_t>(resultShape.size() - sourceShape.size());
|
||||||
for (int64_t i = 0; i < static_cast<int64_t>(resultShape.size()); ++i) {
|
|
||||||
const int64_t sourceIndex = i - rankOffset;
|
|
||||||
const int64_t sourceDim = sourceIndex < 0 ? 1 : sourceShape[sourceIndex];
|
|
||||||
const int64_t resultDim = resultShape[i];
|
|
||||||
if (sourceDim != 1 && sourceDim != resultDim)
|
|
||||||
return failure();
|
|
||||||
}
|
|
||||||
|
|
||||||
SmallVector<Attribute> sourceValues(denseAttr.getValues<Attribute>());
|
|
||||||
SmallVector<int64_t> sourceStrides = computeRowMajorStrides(sourceShape);
|
SmallVector<int64_t> sourceStrides = computeRowMajorStrides(sourceShape);
|
||||||
SmallVector<int64_t> resultStrides = computeRowMajorStrides(resultShape);
|
SmallVector<int64_t> resultStrides = computeRowMajorStrides(resultShape);
|
||||||
|
SmallVector<Attribute> sourceValues(denseAttr.getValues<Attribute>());
|
||||||
SmallVector<Attribute> resultValues;
|
SmallVector<Attribute> resultValues;
|
||||||
resultValues.reserve(resultType.getNumElements());
|
resultValues.reserve(resultType.getNumElements());
|
||||||
|
|
||||||
for (int64_t flatIndex = 0; flatIndex < resultType.getNumElements(); ++flatIndex) {
|
for (int64_t flatIndex = 0; flatIndex < resultType.getNumElements(); ++flatIndex) {
|
||||||
int64_t remaining = flatIndex;
|
int64_t remaining = flatIndex;
|
||||||
int64_t sourceFlatIndex = 0;
|
int64_t sourceFlatIndex = 0;
|
||||||
|
|
||||||
for (int64_t i = 0; i < static_cast<int64_t>(resultShape.size()); ++i) {
|
for (int64_t i = 0; i < static_cast<int64_t>(resultShape.size()); ++i) {
|
||||||
const int64_t resultIndex = resultStrides.empty() ? 0 : remaining / resultStrides[i];
|
const int64_t resultIndex = resultStrides.empty() ? 0 : remaining / resultStrides[i];
|
||||||
remaining = resultStrides.empty() ? 0 : remaining % resultStrides[i];
|
remaining = resultStrides.empty() ? 0 : remaining % resultStrides[i];
|
||||||
|
|
||||||
const int64_t sourceIndex = i - rankOffset;
|
const int64_t sourceIndex = i - rankOffset;
|
||||||
if (sourceIndex < 0)
|
if (sourceIndex < 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const int64_t sourceDim = sourceShape[sourceIndex];
|
const int64_t sourceDim = sourceShape[sourceIndex];
|
||||||
|
const int64_t resultDim = resultShape[i];
|
||||||
|
if (sourceDim != 1 && sourceDim != resultDim)
|
||||||
|
return failure();
|
||||||
const int64_t mappedIndex = sourceDim == 1 ? 0 : resultIndex;
|
const int64_t mappedIndex = sourceDim == 1 ? 0 : resultIndex;
|
||||||
sourceFlatIndex += mappedIndex * sourceStrides[sourceIndex];
|
sourceFlatIndex += mappedIndex * sourceStrides[sourceIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
resultValues.push_back(sourceValues[sourceFlatIndex]);
|
resultValues.push_back(sourceValues[sourceFlatIndex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto broadcastedAttr = DenseElementsAttr::get(resultType, resultValues);
|
auto broadcastedAttr = DenseElementsAttr::get(resultType, resultValues);
|
||||||
return arith::ConstantOp::create(rewriter, loc, resultType, broadcastedAttr).getResult();
|
return getOrCreateConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), broadcastedAttr, resultType);
|
||||||
}
|
}
|
||||||
|
|
||||||
static FailureOr<Value>
|
static FailureOr<Value>
|
||||||
@@ -106,7 +96,7 @@ static FailureOr<Value> materializeReciprocalTensor(Value value,
|
|||||||
if (failed(broadcastedValue))
|
if (failed(broadcastedValue))
|
||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
auto denseAttr = dyn_cast<DenseFPElementsAttr>(getDenseConstantAttr(*broadcastedValue));
|
auto denseAttr = dyn_cast<DenseFPElementsAttr>(getHostConstDenseElementsAttr(*broadcastedValue));
|
||||||
if (!denseAttr)
|
if (!denseAttr)
|
||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
@@ -121,7 +111,7 @@ static FailureOr<Value> materializeReciprocalTensor(Value value,
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto reciprocalAttr = DenseFPElementsAttr::get(resultType, reciprocalValues);
|
auto reciprocalAttr = DenseFPElementsAttr::get(resultType, reciprocalValues);
|
||||||
return arith::ConstantOp::create(rewriter, loc, resultType, reciprocalAttr).getResult();
|
return getOrCreateConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), reciprocalAttr, resultType);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OnnxOp, typename SpatialOp>
|
template <typename OnnxOp, typename SpatialOp>
|
||||||
@@ -185,10 +175,46 @@ struct DivToSpatialCompute : OpConversionPattern<ONNXDivOp> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct AddToSpatialCompute : OpConversionPattern<ONNXAddOp> {
|
||||||
|
using OpConversionPattern::OpConversionPattern;
|
||||||
|
|
||||||
|
LogicalResult
|
||||||
|
matchAndRewrite(ONNXAddOp op, ONNXAddOpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override {
|
||||||
|
auto resultType = dyn_cast<RankedTensorType>(op.getResult().getType());
|
||||||
|
if (!resultType || !resultType.hasStaticShape())
|
||||||
|
return failure();
|
||||||
|
|
||||||
|
FailureOr<BiasAddPlanCandidate> candidate =
|
||||||
|
classifyBiasAddPlanCandidate(adaptor.getA(), adaptor.getB(), resultType);
|
||||||
|
if (succeeded(candidate)) {
|
||||||
|
auto plan = spatial::SpatBiasAddPlanOp::create(
|
||||||
|
rewriter, op.getLoc(), resultType, candidate->data, candidate->bias, rewriter.getStringAttr("nchw"));
|
||||||
|
rewriter.replaceOp(op, plan.getResult());
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lhs = prepareElementwiseOperand(adaptor.getA(), resultType, rewriter, op.getLoc());
|
||||||
|
if (failed(lhs))
|
||||||
|
return failure();
|
||||||
|
auto rhs = prepareElementwiseOperand(adaptor.getB(), resultType, rewriter, op.getLoc());
|
||||||
|
if (failed(rhs))
|
||||||
|
return failure();
|
||||||
|
|
||||||
|
auto computeOp =
|
||||||
|
createSpatCompute<2>(rewriter, op.getLoc(), resultType, {}, ValueRange {*lhs, *rhs}, [&](Value x, Value y) {
|
||||||
|
auto loweredOp = spatial::SpatVAddOp::create(rewriter, op.getLoc(), resultType, x, y);
|
||||||
|
spatial::SpatYieldOp::create(rewriter, op.getLoc(), loweredOp.getResult());
|
||||||
|
});
|
||||||
|
rewriter.replaceOp(op, computeOp);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void populateElementwisePatterns(RewritePatternSet& patterns, MLIRContext* ctx) {
|
void populateElementwisePatterns(RewritePatternSet& patterns, MLIRContext* ctx) {
|
||||||
patterns.add<BinaryElementwiseToSpatialCompute<ONNXAddOp, spatial::SpatVAddOp>>(ctx);
|
patterns.add<AddToSpatialCompute>(ctx);
|
||||||
|
patterns.add<BinaryElementwiseToSpatialCompute<ONNXSubOp, spatial::SpatVSubOp>>(ctx);
|
||||||
patterns.add<BinaryElementwiseToSpatialCompute<ONNXMulOp, spatial::SpatVMulOp>>(ctx);
|
patterns.add<BinaryElementwiseToSpatialCompute<ONNXMulOp, spatial::SpatVMulOp>>(ctx);
|
||||||
patterns.add<DivToSpatialCompute>(ctx);
|
patterns.add<DivToSpatialCompute>(ctx);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,9 @@
|
|||||||
#include <limits>
|
#include <limits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
|
#include "Common/IR/ConstantUtils.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/AffineUtils.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/Support/Diagnostics.hpp"
|
#include "src/Accelerators/PIM/Common/Support/Diagnostics.hpp"
|
||||||
@@ -50,70 +52,21 @@ materializeScaledConstantTensor(Value value, float factor, ConversionPatternRewr
|
|||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
auto scaledAttr = DenseFPElementsAttr::get(cast<RankedTensorType>(denseAttr.getType()), scaledValues);
|
auto scaledAttr = DenseFPElementsAttr::get(cast<RankedTensorType>(denseAttr.getType()), scaledValues);
|
||||||
return arith::ConstantOp::create(rewriter, loc, denseAttr.getType(), scaledAttr).getResult();
|
return getOrCreateConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), scaledAttr, denseAttr.getType());
|
||||||
}
|
|
||||||
|
|
||||||
static Value transposeForSpatial(Value value,
|
|
||||||
RankedTensorType resultType,
|
|
||||||
ArrayRef<int64_t> permutation,
|
|
||||||
ConversionPatternRewriter& rewriter,
|
|
||||||
Location loc) {
|
|
||||||
if (isCompileTimeComputable(value))
|
|
||||||
return ONNXTransposeOp::create(rewriter, loc, resultType, value, rewriter.getI64ArrayAttr(permutation));
|
|
||||||
|
|
||||||
auto computeOp = createSpatCompute<1>(rewriter, loc, TypeRange {resultType}, {}, value, [&](Value input) {
|
|
||||||
Value transposed = ONNXTransposeOp::create(rewriter, loc, resultType, input, rewriter.getI64ArrayAttr(permutation));
|
|
||||||
spatial::SpatYieldOp::create(rewriter, loc, transposed);
|
|
||||||
});
|
|
||||||
return computeOp.getResult(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Value createIndexConstant(ConversionPatternRewriter& rewriter, int64_t value) {
|
|
||||||
Operation* anchorOp = rewriter.getInsertionBlock()->getParentOp();
|
|
||||||
return getOrCreateHostIndexConstant(anchorOp, value, rewriter);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Value
|
|
||||||
createAffineApply(ConversionPatternRewriter& rewriter, Location loc, AffineExpr expr, ValueRange operands) {
|
|
||||||
AffineMap map = AffineMap::get(/*dimCount=*/operands.size(), /*symbolCount=*/0, expr);
|
|
||||||
Operation* anchorOp = rewriter.getInsertionBlock()->getParentOp();
|
|
||||||
return createAffineApplyOrFoldedConstant(rewriter, loc, map, operands, anchorOp);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Value
|
|
||||||
multiplyIndexByConstant(Value value, int64_t multiplier, ConversionPatternRewriter& rewriter, Location loc) {
|
|
||||||
if (multiplier == 0)
|
|
||||||
return createIndexConstant(rewriter, 0);
|
|
||||||
if (multiplier == 1)
|
|
||||||
return value;
|
|
||||||
|
|
||||||
MLIRContext* context = rewriter.getContext();
|
|
||||||
AffineExpr d0 = getAffineDimExpr(0, context);
|
|
||||||
return createAffineApply(rewriter, loc, d0 * multiplier, ValueRange {value});
|
|
||||||
}
|
|
||||||
|
|
||||||
static Value modIndexByConstant(Value value, int64_t divisor, ConversionPatternRewriter& rewriter, Location loc) {
|
|
||||||
if (divisor == 1)
|
|
||||||
return createIndexConstant(rewriter, 0);
|
|
||||||
|
|
||||||
MLIRContext* context = rewriter.getContext();
|
|
||||||
AffineExpr d0 = getAffineDimExpr(0, context);
|
|
||||||
return createAffineApply(rewriter, loc, d0 % divisor, ValueRange {value});
|
|
||||||
}
|
|
||||||
|
|
||||||
static Value createGemmBatchRow(Value lane, int64_t numOutRows, ConversionPatternRewriter& rewriter, Location loc) {
|
|
||||||
return modIndexByConstant(lane, numOutRows, rewriter, loc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value createGemmBatchKOffset(
|
static Value createGemmBatchKOffset(
|
||||||
Value lane, int64_t numOutRows, int64_t numKSlices, ConversionPatternRewriter& rewriter, Location loc) {
|
Value lane, int64_t numOutRows, int64_t numKSlices, ConversionPatternRewriter& rewriter, Location loc) {
|
||||||
if (numKSlices == 1)
|
if (numKSlices == 1)
|
||||||
return createIndexConstant(rewriter, 0);
|
return getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 0);
|
||||||
|
|
||||||
MLIRContext* context = rewriter.getContext();
|
MLIRContext* context = rewriter.getContext();
|
||||||
AffineExpr d0 = getAffineDimExpr(0, context);
|
AffineExpr d0 = getAffineDimExpr(0, context);
|
||||||
return createAffineApply(
|
return createOrFoldAffineApply(rewriter,
|
||||||
rewriter, loc, (d0.floorDiv(numOutRows) % numKSlices) * crossbarSize.getValue(), ValueRange {lane});
|
loc,
|
||||||
|
(d0.floorDiv(numOutRows) % numKSlices) * crossbarSize.getValue(),
|
||||||
|
ValueRange {lane},
|
||||||
|
rewriter.getInsertionBlock()->getParentOp());
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value createGemmBatchHOffset(Value lane,
|
static Value createGemmBatchHOffset(Value lane,
|
||||||
@@ -123,34 +76,15 @@ static Value createGemmBatchHOffset(Value lane,
|
|||||||
ConversionPatternRewriter& rewriter,
|
ConversionPatternRewriter& rewriter,
|
||||||
Location loc) {
|
Location loc) {
|
||||||
if (numOutHSlices == 1)
|
if (numOutHSlices == 1)
|
||||||
return createIndexConstant(rewriter, 0);
|
return getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 0);
|
||||||
|
|
||||||
MLIRContext* context = rewriter.getContext();
|
MLIRContext* context = rewriter.getContext();
|
||||||
AffineExpr d0 = getAffineDimExpr(0, context);
|
AffineExpr d0 = getAffineDimExpr(0, context);
|
||||||
return createAffineApply(
|
return createOrFoldAffineApply(rewriter,
|
||||||
rewriter, loc, d0.floorDiv(numOutRows * numKSlices) * crossbarSize.getValue(), ValueRange {lane});
|
loc,
|
||||||
}
|
d0.floorDiv(numOutRows * numKSlices) * crossbarSize.getValue(),
|
||||||
|
ValueRange {lane},
|
||||||
static Value
|
rewriter.getInsertionBlock()->getParentOp());
|
||||||
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 = arith::ConstantOp::create(
|
|
||||||
rewriter, loc, sourceType.getElementType(), rewriter.getZeroAttr(sourceType.getElementType()));
|
|
||||||
tensor::YieldOp::create(rewriter, loc, zero.getResult());
|
|
||||||
rewriter.setInsertionPointAfter(padOp);
|
|
||||||
return padOp.getResult();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static FailureOr<Value> materializePaddedConstantMatrix(Value value,
|
static FailureOr<Value> materializePaddedConstantMatrix(Value value,
|
||||||
@@ -180,7 +114,7 @@ static FailureOr<Value> materializePaddedConstantMatrix(Value value,
|
|||||||
resultValues[row * resultShape[1] + col] = sourceValues[row * sourceShape[1] + col];
|
resultValues[row * resultShape[1] + col] = sourceValues[row * sourceShape[1] + col];
|
||||||
|
|
||||||
auto resultAttr = DenseElementsAttr::get(resultType, resultValues);
|
auto resultAttr = DenseElementsAttr::get(resultType, resultValues);
|
||||||
return arith::ConstantOp::create(rewriter, loc, resultType, resultAttr).getResult();
|
return getOrCreateConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), resultAttr, resultType);
|
||||||
}
|
}
|
||||||
|
|
||||||
static FailureOr<Value> materializePaddedBroadcastedConstantTensor(Value value,
|
static FailureOr<Value> materializePaddedBroadcastedConstantTensor(Value value,
|
||||||
@@ -246,7 +180,7 @@ static FailureOr<Value> materializePaddedBroadcastedConstantTensor(Value value,
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto resultAttr = DenseElementsAttr::get(resultType, resultValues);
|
auto resultAttr = DenseElementsAttr::get(resultType, resultValues);
|
||||||
return arith::ConstantOp::create(rewriter, loc, resultType, resultAttr).getResult();
|
return getOrCreateConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), resultAttr, resultType);
|
||||||
}
|
}
|
||||||
|
|
||||||
static FailureOr<Value> prepareBias(Value c,
|
static FailureOr<Value> prepareBias(Value c,
|
||||||
@@ -276,119 +210,88 @@ static Value extractATile(
|
|||||||
return tensor::ExtractSliceOp::create(rewriter, loc, aTileType, a, offsets, sizes, strides).getResult();
|
return tensor::ExtractSliceOp::create(rewriter, loc, aTileType, a, offsets, sizes, strides).getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value createPaddedInputCompute(Value input,
|
static FailureOr<spatial::SpatComputeBatch> createVmmBatch(Value a,
|
||||||
RankedTensorType paddedInputType,
|
Value b,
|
||||||
ConversionPatternRewriter& rewriter,
|
RankedTensorType aType,
|
||||||
Location loc) {
|
RankedTensorType paddedBType,
|
||||||
auto inputType = cast<RankedTensorType>(input.getType());
|
RankedTensorType partialPiecesType,
|
||||||
if (inputType == paddedInputType)
|
int64_t numOutRows,
|
||||||
return input;
|
int64_t numKSlices,
|
||||||
|
int64_t numOutHSlices,
|
||||||
auto computeOp = createSpatCompute<1>(rewriter, loc, TypeRange {paddedInputType}, {}, input, [&](Value computeInput) {
|
ConversionPatternRewriter& rewriter,
|
||||||
Value paddedInput = createZeroPaddedTensor(computeInput, paddedInputType, rewriter, loc);
|
Location loc) {
|
||||||
spatial::SpatYieldOp::create(rewriter, loc, paddedInput);
|
|
||||||
});
|
|
||||||
|
|
||||||
return computeOp.getResult(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static spatial::SpatComputeBatch createVmmBatch(Value a,
|
|
||||||
Value b,
|
|
||||||
RankedTensorType aType,
|
|
||||||
RankedTensorType paddedBType,
|
|
||||||
RankedTensorType partialPiecesType,
|
|
||||||
int64_t numOutRows,
|
|
||||||
int64_t numKSlices,
|
|
||||||
int64_t numOutHSlices,
|
|
||||||
ConversionPatternRewriter& rewriter,
|
|
||||||
Location loc) {
|
|
||||||
const int64_t laneCount = partialPiecesType.getDimSize(0);
|
const int64_t laneCount = partialPiecesType.getDimSize(0);
|
||||||
auto batchOp = spatial::SpatComputeBatch::create(rewriter,
|
auto batchOp = createSpatComputeBatch(
|
||||||
loc,
|
rewriter,
|
||||||
TypeRange {partialPiecesType},
|
loc,
|
||||||
rewriter.getI32IntegerAttr(static_cast<int32_t>(laneCount)),
|
TypeRange {partialPiecesType},
|
||||||
ValueRange {b},
|
laneCount,
|
||||||
ValueRange {a});
|
ValueRange {b},
|
||||||
|
ValueRange {a},
|
||||||
|
[&](detail::SpatComputeBatchBodyArgs args) {
|
||||||
|
Value row =
|
||||||
|
onnx_mlir::affineModConst(rewriter, loc, args.lane, numOutRows, rewriter.getInsertionBlock()->getParentOp());
|
||||||
|
Value kOffset = createGemmBatchKOffset(args.lane, numOutRows, numKSlices, rewriter, loc);
|
||||||
|
Value hOffset = createGemmBatchHOffset(args.lane, numOutRows, numKSlices, numOutHSlices, rewriter, loc);
|
||||||
|
|
||||||
SmallVector<Type> blockArgTypes {rewriter.getIndexType(), paddedBType, aType, partialPiecesType};
|
auto aTileType =
|
||||||
SmallVector<Location> blockArgLocs(blockArgTypes.size(), loc);
|
RankedTensorType::get({1, static_cast<int64_t>(crossbarSize.getValue())}, aType.getElementType());
|
||||||
Block* body =
|
auto bTileType = RankedTensorType::get(
|
||||||
rewriter.createBlock(&batchOp.getBody(), batchOp.getBody().end(), TypeRange(blockArgTypes), blockArgLocs);
|
{static_cast<int64_t>(crossbarSize.getValue()), static_cast<int64_t>(crossbarSize.getValue())},
|
||||||
rewriter.setInsertionPointToEnd(body);
|
paddedBType.getElementType());
|
||||||
|
auto pieceType =
|
||||||
|
RankedTensorType::get({1, static_cast<int64_t>(crossbarSize.getValue())}, partialPiecesType.getElementType());
|
||||||
|
Value aTile = extractATile(args.inputs.front(), row, kOffset, aTileType, rewriter, loc);
|
||||||
|
|
||||||
auto lane = batchOp.getLaneArgument();
|
SmallVector<OpFoldResult> bOffsets {kOffset, hOffset};
|
||||||
auto weight = batchOp.getWeightArgument(0);
|
SmallVector<OpFoldResult> bSizes {rewriter.getIndexAttr(crossbarSize.getValue()),
|
||||||
auto input = batchOp.getInputArgument(0);
|
rewriter.getIndexAttr(crossbarSize.getValue())};
|
||||||
auto output = batchOp.getOutputArgument(0);
|
SmallVector<OpFoldResult> unitStrides = getUnitStrides(rewriter, 2);
|
||||||
assert(lane && weight && input && output && "malformed Gemm compute_batch body");
|
Value bTile = extractStaticSliceOrIdentity(
|
||||||
|
rewriter, loc, args.weights.front(), bTileType, bOffsets, bSizes, unitStrides);
|
||||||
|
Value piece = spatial::SpatVMMOp::create(rewriter, loc, pieceType, bTile, aTile).getResult();
|
||||||
|
|
||||||
Value row = createGemmBatchRow(*lane, numOutRows, rewriter, loc);
|
SmallVector<OpFoldResult> pieceOffsets {args.lane, rewriter.getIndexAttr(0)};
|
||||||
Value kOffset = createGemmBatchKOffset(*lane, numOutRows, numKSlices, rewriter, loc);
|
SmallVector<OpFoldResult> pieceSizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(crossbarSize.getValue())};
|
||||||
Value hOffset = createGemmBatchHOffset(*lane, numOutRows, numKSlices, numOutHSlices, rewriter, loc);
|
createParallelInsertSliceIntoBatchOutput(
|
||||||
|
rewriter, loc, piece, args.outputs.front(), pieceOffsets, pieceSizes, unitStrides);
|
||||||
auto aTileType = RankedTensorType::get({1, static_cast<int64_t>(crossbarSize.getValue())}, aType.getElementType());
|
});
|
||||||
auto bTileType = RankedTensorType::get(
|
if (failed(batchOp))
|
||||||
{static_cast<int64_t>(crossbarSize.getValue()), static_cast<int64_t>(crossbarSize.getValue())},
|
return failure();
|
||||||
paddedBType.getElementType());
|
return *batchOp;
|
||||||
auto pieceType =
|
|
||||||
RankedTensorType::get({1, static_cast<int64_t>(crossbarSize.getValue())}, partialPiecesType.getElementType());
|
|
||||||
Value aTile = extractATile(*input, row, kOffset, aTileType, rewriter, loc);
|
|
||||||
|
|
||||||
SmallVector<OpFoldResult> bOffsets {kOffset, hOffset};
|
|
||||||
SmallVector<OpFoldResult> bSizes {rewriter.getIndexAttr(crossbarSize.getValue()),
|
|
||||||
rewriter.getIndexAttr(crossbarSize.getValue())};
|
|
||||||
SmallVector<OpFoldResult> unitStrides {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
|
||||||
Value bTile =
|
|
||||||
tensor::ExtractSliceOp::create(rewriter, loc, bTileType, *weight, bOffsets, bSizes, unitStrides).getResult();
|
|
||||||
Value piece = spatial::SpatVMMOp::create(rewriter, loc, pieceType, bTile, aTile).getResult();
|
|
||||||
|
|
||||||
auto inParallelOp = spatial::SpatInParallelOp::create(rewriter, loc);
|
|
||||||
rewriter.setInsertionPointToStart(&inParallelOp.getRegion().front());
|
|
||||||
SmallVector<OpFoldResult> pieceOffsets {*lane, rewriter.getIndexAttr(0)};
|
|
||||||
SmallVector<OpFoldResult> pieceSizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(crossbarSize.getValue())};
|
|
||||||
tensor::ParallelInsertSliceOp::create(rewriter, loc, piece, *output, pieceOffsets, pieceSizes, unitStrides);
|
|
||||||
|
|
||||||
rewriter.setInsertionPointAfter(batchOp);
|
|
||||||
return batchOp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value createDynamicGemmBatchRow(
|
static Value
|
||||||
Value lane, int64_t numOutCols, ConversionPatternRewriter& rewriter, Location loc) {
|
createDynamicGemmBatchRow(Value lane, int64_t numOutCols, ConversionPatternRewriter& rewriter, Location loc) {
|
||||||
if (numOutCols == 1)
|
if (numOutCols == 1)
|
||||||
return lane;
|
return lane;
|
||||||
|
|
||||||
MLIRContext* context = rewriter.getContext();
|
MLIRContext* context = rewriter.getContext();
|
||||||
AffineExpr d0 = getAffineDimExpr(0, context);
|
AffineExpr d0 = getAffineDimExpr(0, context);
|
||||||
return createAffineApply(rewriter, loc, d0.floorDiv(numOutCols), ValueRange {lane});
|
return createOrFoldAffineApply(
|
||||||
|
rewriter, loc, d0.floorDiv(numOutCols), ValueRange {lane}, rewriter.getInsertionBlock()->getParentOp());
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value createDynamicGemmBatchColumn(
|
static Value extractDynamicGemmBColumn(
|
||||||
Value lane, int64_t numOutCols, ConversionPatternRewriter& rewriter, Location loc) {
|
Value matrix, Value column, RankedTensorType vectorType, ConversionPatternRewriter& rewriter, Location loc) {
|
||||||
return modIndexByConstant(lane, numOutCols, rewriter, loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Value
|
|
||||||
extractDynamicGemmBColumn(Value matrix, Value column, RankedTensorType vectorType, ConversionPatternRewriter& rewriter, Location loc) {
|
|
||||||
SmallVector<OpFoldResult> offsets {rewriter.getIndexAttr(0), column};
|
SmallVector<OpFoldResult> offsets {rewriter.getIndexAttr(0), column};
|
||||||
|
SmallVector<OpFoldResult> sizes {rewriter.getIndexAttr(vectorType.getDimSize(1)), rewriter.getIndexAttr(1)};
|
||||||
SmallVector<OpFoldResult> strides {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
SmallVector<OpFoldResult> strides {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
||||||
auto columnSliceType = RankedTensorType::get({vectorType.getDimSize(1), 1}, vectorType.getElementType());
|
auto columnSliceType = RankedTensorType::get({vectorType.getDimSize(1), 1}, vectorType.getElementType());
|
||||||
Value columnSlice = materializeContiguousTensorSlice(matrix, columnSliceType, offsets, strides, rewriter, loc);
|
Value columnSlice =
|
||||||
SmallVector<ReassociationIndices> collapseReassociation {ReassociationIndices {0, 1}};
|
tensor::ExtractSliceOp::create(rewriter, loc, columnSliceType, matrix, offsets, sizes, strides).getResult();
|
||||||
|
SmallVector<ReassociationIndices> collapseReassociation {
|
||||||
|
ReassociationIndices {0, 1}
|
||||||
|
};
|
||||||
auto collapsedType = RankedTensorType::get({vectorType.getDimSize(1)}, vectorType.getElementType());
|
auto collapsedType = RankedTensorType::get({vectorType.getDimSize(1)}, vectorType.getElementType());
|
||||||
Value collapsed =
|
Value collapsed =
|
||||||
tensor::CollapseShapeOp::create(rewriter, loc, collapsedType, columnSlice, collapseReassociation).getResult();
|
tensor::CollapseShapeOp::create(rewriter, loc, collapsedType, columnSlice, collapseReassociation).getResult();
|
||||||
SmallVector<ReassociationIndices> expandReassociation {ReassociationIndices {0, 1}};
|
SmallVector<ReassociationIndices> expandReassociation {
|
||||||
|
ReassociationIndices {0, 1}
|
||||||
|
};
|
||||||
return tensor::ExpandShapeOp::create(rewriter, loc, vectorType, collapsed, expandReassociation).getResult();
|
return tensor::ExpandShapeOp::create(rewriter, loc, vectorType, collapsed, expandReassociation).getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value extractTransposedBRow(
|
|
||||||
Value transposedB, Value row, RankedTensorType vectorType, ConversionPatternRewriter& rewriter, Location loc) {
|
|
||||||
SmallVector<OpFoldResult> offsets {row, rewriter.getIndexAttr(0)};
|
|
||||||
SmallVector<OpFoldResult> sizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(vectorType.getDimSize(1))};
|
|
||||||
SmallVector<OpFoldResult> strides {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
|
||||||
return tensor::ExtractSliceOp::create(rewriter, loc, vectorType, transposedB, offsets, sizes, strides).getResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
static Value extractDynamicGemmRowVector(
|
static Value extractDynamicGemmRowVector(
|
||||||
Value matrix, Value row, RankedTensorType vectorType, ConversionPatternRewriter& rewriter, Location loc) {
|
Value matrix, Value row, RankedTensorType vectorType, ConversionPatternRewriter& rewriter, Location loc) {
|
||||||
SmallVector<OpFoldResult> offsets {row, rewriter.getIndexAttr(0)};
|
SmallVector<OpFoldResult> offsets {row, rewriter.getIndexAttr(0)};
|
||||||
@@ -432,7 +335,7 @@ static Value createScalarTensorConstant(RankedTensorType scalarType,
|
|||||||
auto elementType = scalarType.getElementType();
|
auto elementType = scalarType.getElementType();
|
||||||
auto scalarAttr = rewriter.getFloatAttr(elementType, value);
|
auto scalarAttr = rewriter.getFloatAttr(elementType, value);
|
||||||
auto denseAttr = DenseElementsAttr::get(scalarType, scalarAttr);
|
auto denseAttr = DenseElementsAttr::get(scalarType, scalarAttr);
|
||||||
return arith::ConstantOp::create(rewriter, loc, scalarType, denseAttr).getResult();
|
return getOrCreateConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), denseAttr, scalarType);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value createBroadcastedBiasScalar(Value bias,
|
static Value createBroadcastedBiasScalar(Value bias,
|
||||||
@@ -444,13 +347,15 @@ static Value createBroadcastedBiasScalar(Value bias,
|
|||||||
Location loc) {
|
Location loc) {
|
||||||
SmallVector<OpFoldResult> unitStrides(biasType.getRank(), rewriter.getIndexAttr(1));
|
SmallVector<OpFoldResult> unitStrides(biasType.getRank(), rewriter.getIndexAttr(1));
|
||||||
if (biasType.getRank() == 1) {
|
if (biasType.getRank() == 1) {
|
||||||
SmallVector<OpFoldResult> offsets {
|
SmallVector<OpFoldResult> offsets {biasType.getDimSize(0) == 1 ? OpFoldResult(rewriter.getIndexAttr(0))
|
||||||
biasType.getDimSize(0) == 1 ? OpFoldResult(rewriter.getIndexAttr(0)) : OpFoldResult(column)};
|
: OpFoldResult(column)};
|
||||||
SmallVector<OpFoldResult> sizes {rewriter.getIndexAttr(1)};
|
SmallVector<OpFoldResult> sizes {rewriter.getIndexAttr(1)};
|
||||||
auto vectorType = RankedTensorType::get({1}, scalarType.getElementType());
|
auto vectorType = RankedTensorType::get({1}, scalarType.getElementType());
|
||||||
Value vector = tensor::ExtractSliceOp::create(rewriter, loc, vectorType, bias, offsets, sizes, unitStrides)
|
Value vector =
|
||||||
.getResult();
|
tensor::ExtractSliceOp::create(rewriter, loc, vectorType, bias, offsets, sizes, unitStrides).getResult();
|
||||||
SmallVector<ReassociationIndices> reassociation {ReassociationIndices {0, 1}};
|
SmallVector<ReassociationIndices> reassociation {
|
||||||
|
ReassociationIndices {0, 1}
|
||||||
|
};
|
||||||
return tensor::ExpandShapeOp::create(rewriter, loc, scalarType, vector, reassociation).getResult();
|
return tensor::ExpandShapeOp::create(rewriter, loc, scalarType, vector, reassociation).getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -466,116 +371,114 @@ static Value createBroadcastedBiasScalar(Value bias,
|
|||||||
return tensor::SplatOp::create(rewriter, loc, scalarType, scalar).getResult();
|
return tensor::SplatOp::create(rewriter, loc, scalarType, scalar).getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
static spatial::SpatComputeBatch createVvdmulBatch(Value a,
|
static FailureOr<spatial::SpatComputeBatch> createVvdmulBatch(Value a,
|
||||||
Value b,
|
Value b,
|
||||||
RankedTensorType aType,
|
RankedTensorType aType,
|
||||||
RankedTensorType bType,
|
RankedTensorType bType,
|
||||||
RankedTensorType scalarPiecesType,
|
RankedTensorType scalarPiecesType,
|
||||||
RankedTensorType outType,
|
RankedTensorType outType,
|
||||||
bool bAlreadyTransposed,
|
ConversionPatternRewriter& rewriter,
|
||||||
ConversionPatternRewriter& rewriter,
|
Location loc) {
|
||||||
Location loc) {
|
|
||||||
const int64_t numOutRows = outType.getDimSize(0);
|
const int64_t numOutRows = outType.getDimSize(0);
|
||||||
const int64_t numOutCols = outType.getDimSize(1);
|
const int64_t numOutCols = outType.getDimSize(1);
|
||||||
const int64_t reductionSize = aType.getDimSize(1);
|
const int64_t reductionSize = aType.getDimSize(1);
|
||||||
const int64_t laneCount = numOutRows * numOutCols;
|
const int64_t laneCount = numOutRows * numOutCols;
|
||||||
auto batchOp = spatial::SpatComputeBatch::create(rewriter,
|
auto batchOp = createSpatComputeBatch(
|
||||||
loc,
|
rewriter,
|
||||||
TypeRange {scalarPiecesType},
|
loc,
|
||||||
rewriter.getI32IntegerAttr(static_cast<int32_t>(laneCount)),
|
TypeRange {scalarPiecesType},
|
||||||
ValueRange {},
|
laneCount,
|
||||||
ValueRange {a, b});
|
ValueRange {},
|
||||||
|
ValueRange {a, b},
|
||||||
|
[&](detail::SpatComputeBatchBodyArgs args) {
|
||||||
|
Value row = createDynamicGemmBatchRow(args.lane, numOutCols, rewriter, loc);
|
||||||
|
Value column =
|
||||||
|
onnx_mlir::affineModConst(rewriter, loc, args.lane, numOutCols, rewriter.getInsertionBlock()->getParentOp());
|
||||||
|
|
||||||
SmallVector<Type> blockArgTypes {rewriter.getIndexType(), aType, bType, scalarPiecesType};
|
auto vectorType = RankedTensorType::get({1, reductionSize}, aType.getElementType());
|
||||||
SmallVector<Location> blockArgLocs(blockArgTypes.size(), loc);
|
auto scalarType = RankedTensorType::get({1, 1}, outType.getElementType());
|
||||||
Block* body =
|
Value aVector = extractDynamicGemmRowVector(args.inputs[0], row, vectorType, rewriter, loc);
|
||||||
rewriter.createBlock(&batchOp.getBody(), batchOp.getBody().end(), TypeRange(blockArgTypes), blockArgLocs);
|
Value bVector = extractDynamicGemmBColumn(args.inputs[1], column, vectorType, rewriter, loc);
|
||||||
rewriter.setInsertionPointToEnd(body);
|
Value scalar = spatial::SpatVVDMulOp::create(rewriter, loc, scalarType, aVector, bVector).getResult();
|
||||||
|
|
||||||
auto lane = batchOp.getLaneArgument();
|
SmallVector<OpFoldResult> outputOffsets {args.lane, rewriter.getIndexAttr(0)};
|
||||||
auto inputA = batchOp.getInputArgument(0);
|
SmallVector<OpFoldResult> scalarSizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
||||||
auto inputB = batchOp.getInputArgument(1);
|
SmallVector<OpFoldResult> unitStrides = getUnitStrides(rewriter, 2);
|
||||||
auto output = batchOp.getOutputArgument(0);
|
createParallelInsertSliceIntoBatchOutput(
|
||||||
assert(lane && inputA && inputB && output && "malformed dynamic Gemm compute_batch body");
|
rewriter, loc, scalar, args.outputs.front(), outputOffsets, scalarSizes, unitStrides);
|
||||||
|
});
|
||||||
Value row = createDynamicGemmBatchRow(*lane, numOutCols, rewriter, loc);
|
if (failed(batchOp))
|
||||||
Value column = createDynamicGemmBatchColumn(*lane, numOutCols, rewriter, loc);
|
return failure();
|
||||||
|
return *batchOp;
|
||||||
auto vectorType = RankedTensorType::get({1, reductionSize}, aType.getElementType());
|
|
||||||
auto scalarType = RankedTensorType::get({1, 1}, outType.getElementType());
|
|
||||||
Value aVector = extractDynamicGemmRowVector(*inputA, row, vectorType, rewriter, loc);
|
|
||||||
Value bVector = bAlreadyTransposed
|
|
||||||
? extractTransposedBRow(*inputB, column, vectorType, rewriter, loc)
|
|
||||||
: extractDynamicGemmBColumn(*inputB, column, vectorType, rewriter, loc);
|
|
||||||
Value scalar = spatial::SpatVVDMulOp::create(rewriter, loc, scalarType, aVector, bVector).getResult();
|
|
||||||
|
|
||||||
auto inParallelOp = spatial::SpatInParallelOp::create(rewriter, loc);
|
|
||||||
rewriter.setInsertionPointToStart(&inParallelOp.getRegion().front());
|
|
||||||
SmallVector<OpFoldResult> outputOffsets {*lane, rewriter.getIndexAttr(0)};
|
|
||||||
SmallVector<OpFoldResult> scalarSizes {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
|
||||||
SmallVector<OpFoldResult> unitStrides {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
|
||||||
tensor::ParallelInsertSliceOp::create(rewriter, loc, scalar, *output, outputOffsets, scalarSizes, unitStrides);
|
|
||||||
|
|
||||||
rewriter.setInsertionPointAfter(batchOp);
|
|
||||||
return batchOp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static spatial::SpatCompute createDynamicGemmOutputCompute(Value scalarPieces,
|
static FailureOr<spatial::SpatCompute> createDynamicGemmOutputCompute(Value scalarPieces,
|
||||||
Value bias,
|
Value bias,
|
||||||
RankedTensorType scalarPiecesType,
|
RankedTensorType scalarPiecesType,
|
||||||
RankedTensorType biasType,
|
RankedTensorType biasType,
|
||||||
RankedTensorType outType,
|
RankedTensorType outType,
|
||||||
float alpha,
|
float alpha,
|
||||||
float beta,
|
float beta,
|
||||||
ConversionPatternRewriter& rewriter,
|
ConversionPatternRewriter& rewriter,
|
||||||
Location loc) {
|
Location loc) {
|
||||||
const int64_t laneCount = scalarPiecesType.getDimSize(0);
|
const int64_t laneCount = scalarPiecesType.getDimSize(0);
|
||||||
const int64_t numOutCols = outType.getDimSize(1);
|
const int64_t numOutCols = outType.getDimSize(1);
|
||||||
SmallVector<Value> inputs {scalarPieces};
|
SmallVector<Value> inputs {scalarPieces};
|
||||||
if (bias)
|
if (bias)
|
||||||
inputs.push_back(bias);
|
inputs.push_back(bias);
|
||||||
|
|
||||||
return createSpatCompute(rewriter, loc, TypeRange {outType}, {}, inputs, [&](ValueRange blockArgs) {
|
return createSpatCompute(rewriter, loc, TypeRange {outType}, {}, inputs, [&](ValueRange blockArgs) -> LogicalResult {
|
||||||
Value pieces = blockArgs[0];
|
Value pieces = blockArgs[0];
|
||||||
Value biasArg = bias ? blockArgs[1] : Value();
|
Value biasArg = bias ? blockArgs[1] : Value();
|
||||||
auto scalarType = RankedTensorType::get({1, 1}, outType.getElementType());
|
auto scalarType = RankedTensorType::get({1, 1}, outType.getElementType());
|
||||||
Value outputInit = tensor::EmptyOp::create(rewriter, loc, outType.getShape(), outType.getElementType()).getResult();
|
Value outputInit = tensor::EmptyOp::create(rewriter, loc, outType.getShape(), outType.getElementType()).getResult();
|
||||||
Value c0 = createIndexConstant(rewriter, 0);
|
Value c0 = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 0);
|
||||||
Value c1 = createIndexConstant(rewriter, 1);
|
Value c1 = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 1);
|
||||||
Value cLaneCount = createIndexConstant(rewriter, laneCount);
|
Value cLaneCount = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), laneCount);
|
||||||
auto loop = scf::ForOp::create(rewriter, loc, c0, cLaneCount, c1, ValueRange {outputInit});
|
auto loop = buildNormalizedScfFor(
|
||||||
rewriter.setInsertionPointToStart(loop.getBody());
|
rewriter,
|
||||||
|
loc,
|
||||||
|
c0,
|
||||||
|
cLaneCount,
|
||||||
|
c1,
|
||||||
|
ValueRange {outputInit},
|
||||||
|
[&](OpBuilder&, Location nestedLoc, Value lane, ValueRange iterArgs, SmallVectorImpl<Value>& yielded) {
|
||||||
|
Value outputAcc = iterArgs.front();
|
||||||
|
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();
|
||||||
|
if (alpha != 1.0f) {
|
||||||
|
Value alphaTensor = createScalarTensorConstant(scalarType, alpha, rewriter, nestedLoc);
|
||||||
|
scalar = spatial::SpatVMulOp::create(rewriter, nestedLoc, scalarType, scalar, alphaTensor).getResult();
|
||||||
|
}
|
||||||
|
if (biasArg) {
|
||||||
|
Value biasScalar =
|
||||||
|
createBroadcastedBiasScalar(biasArg, biasType, row, column, scalarType, rewriter, nestedLoc);
|
||||||
|
if (beta != 1.0f) {
|
||||||
|
Value betaTensor = createScalarTensorConstant(scalarType, beta, rewriter, nestedLoc);
|
||||||
|
biasScalar =
|
||||||
|
spatial::SpatVMulOp::create(rewriter, nestedLoc, scalarType, biasScalar, betaTensor).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)
|
||||||
|
.getResult();
|
||||||
|
yielded.push_back(outputNext);
|
||||||
|
return success();
|
||||||
|
});
|
||||||
|
if (failed(loop))
|
||||||
|
return failure();
|
||||||
|
|
||||||
Value lane = loop.getInductionVar();
|
spatial::SpatYieldOp::create(rewriter, loc, loop->results.front());
|
||||||
Value outputAcc = loop.getRegionIterArgs().front();
|
return success();
|
||||||
Value row = createDynamicGemmBatchRow(lane, numOutCols, rewriter, loc);
|
|
||||||
Value column = createDynamicGemmBatchColumn(lane, numOutCols, rewriter, loc);
|
|
||||||
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, loc, scalarType, pieces, scalarOffsets, scalarSizes, unitStrides)
|
|
||||||
.getResult();
|
|
||||||
if (alpha != 1.0f) {
|
|
||||||
Value alphaTensor = createScalarTensorConstant(scalarType, alpha, rewriter, loc);
|
|
||||||
scalar = spatial::SpatVMulOp::create(rewriter, loc, scalarType, scalar, alphaTensor).getResult();
|
|
||||||
}
|
|
||||||
if (biasArg) {
|
|
||||||
Value biasScalar = createBroadcastedBiasScalar(biasArg, biasType, row, column, scalarType, rewriter, loc);
|
|
||||||
if (beta != 1.0f) {
|
|
||||||
Value betaTensor = createScalarTensorConstant(scalarType, beta, rewriter, loc);
|
|
||||||
biasScalar = spatial::SpatVMulOp::create(rewriter, loc, scalarType, biasScalar, betaTensor).getResult();
|
|
||||||
}
|
|
||||||
scalar = spatial::SpatVAddOp::create(rewriter, loc, scalarType, scalar, biasScalar).getResult();
|
|
||||||
}
|
|
||||||
SmallVector<OpFoldResult> outputOffsets {row, column};
|
|
||||||
Value outputNext =
|
|
||||||
tensor::InsertSliceOp::create(rewriter, loc, scalar, outputAcc, outputOffsets, scalarSizes, unitStrides)
|
|
||||||
.getResult();
|
|
||||||
scf::YieldOp::create(rewriter, loc, outputNext);
|
|
||||||
|
|
||||||
rewriter.setInsertionPointAfter(loop);
|
|
||||||
spatial::SpatYieldOp::create(rewriter, loc, loop.getResult(0));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -587,7 +490,11 @@ static Value createPartialGroupOffset(Value hSlice,
|
|||||||
Location loc) {
|
Location loc) {
|
||||||
MLIRContext* context = rewriter.getContext();
|
MLIRContext* context = rewriter.getContext();
|
||||||
AffineExpr d0 = getAffineDimExpr(0, context);
|
AffineExpr d0 = getAffineDimExpr(0, context);
|
||||||
return createAffineApply(rewriter, loc, d0 * (numKSlices * numOutRows) + kSlice * numOutRows, ValueRange {hSlice});
|
return createOrFoldAffineApply(rewriter,
|
||||||
|
loc,
|
||||||
|
d0 * (numKSlices * numOutRows) + kSlice * numOutRows,
|
||||||
|
ValueRange {hSlice},
|
||||||
|
rewriter.getInsertionBlock()->getParentOp());
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value extractReductionPiece(Value partialPiecesArg,
|
static Value extractReductionPiece(Value partialPiecesArg,
|
||||||
@@ -636,83 +543,92 @@ static Value reducePartialPiecesForHSlice(Value partialPiecesArg,
|
|||||||
return activePieces.front();
|
return activePieces.front();
|
||||||
}
|
}
|
||||||
|
|
||||||
static spatial::SpatCompute createReductionCompute(Value partialPieces,
|
static FailureOr<spatial::SpatCompute> createReductionCompute(Value partialPieces,
|
||||||
Value bias,
|
Value bias,
|
||||||
RankedTensorType partialPiecesType,
|
RankedTensorType partialPiecesType,
|
||||||
RankedTensorType outType,
|
RankedTensorType outType,
|
||||||
RankedTensorType paddedOutType,
|
RankedTensorType paddedOutType,
|
||||||
int64_t numKSlices,
|
int64_t numKSlices,
|
||||||
ConversionPatternRewriter& rewriter,
|
ConversionPatternRewriter& rewriter,
|
||||||
Location loc) {
|
Location loc) {
|
||||||
SmallVector<Value> inputs {partialPieces};
|
SmallVector<Value> inputs {partialPieces};
|
||||||
if (bias)
|
if (bias)
|
||||||
inputs.push_back(bias);
|
inputs.push_back(bias);
|
||||||
|
|
||||||
auto computeOp = createSpatCompute(rewriter, loc, TypeRange {outType}, {}, inputs, [&](ValueRange blockArgs) {
|
auto computeOp =
|
||||||
Value partialPiecesArg = blockArgs[0];
|
createSpatCompute(rewriter, loc, TypeRange {outType}, {}, inputs, [&](ValueRange blockArgs) -> LogicalResult {
|
||||||
Value biasArg = bias ? blockArgs[1] : Value();
|
Value partialPiecesArg = blockArgs[0];
|
||||||
if (biasArg && cast<RankedTensorType>(biasArg.getType()) != paddedOutType)
|
Value biasArg = bias ? blockArgs[1] : Value();
|
||||||
biasArg = createZeroPaddedTensor(biasArg, paddedOutType, rewriter, loc);
|
if (biasArg && cast<RankedTensorType>(biasArg.getType()) != paddedOutType)
|
||||||
|
biasArg = createZeroPaddedTensor(biasArg, paddedOutType, rewriter, loc);
|
||||||
|
|
||||||
const int64_t numOutRows = outType.getDimSize(0);
|
const int64_t numOutRows = outType.getDimSize(0);
|
||||||
const int64_t numOutHSlices = ceilIntegerDivide(outType.getDimSize(1), crossbarSize.getValue());
|
const int64_t numOutHSlices = ceilIntegerDivide(outType.getDimSize(1), crossbarSize.getValue());
|
||||||
auto pieceType = RankedTensorType::get({numOutRows, static_cast<int64_t>(crossbarSize.getValue())},
|
auto pieceType = RankedTensorType::get({numOutRows, static_cast<int64_t>(crossbarSize.getValue())},
|
||||||
partialPiecesType.getElementType());
|
partialPiecesType.getElementType());
|
||||||
|
|
||||||
Value outputInit =
|
Value outputInit =
|
||||||
tensor::EmptyOp::create(rewriter, loc, paddedOutType.getShape(), paddedOutType.getElementType()).getResult();
|
tensor::EmptyOp::create(rewriter, loc, paddedOutType.getShape(), paddedOutType.getElementType()).getResult();
|
||||||
SmallVector<OpFoldResult> unitStrides {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
SmallVector<OpFoldResult> unitStrides {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
||||||
SmallVector<OpFoldResult> pieceSizes {rewriter.getIndexAttr(numOutRows),
|
SmallVector<OpFoldResult> pieceSizes {rewriter.getIndexAttr(numOutRows),
|
||||||
rewriter.getIndexAttr(crossbarSize.getValue())};
|
rewriter.getIndexAttr(crossbarSize.getValue())};
|
||||||
|
|
||||||
auto buildOutputSlice = [&](Value outputAcc, Value hSlice) -> Value {
|
auto buildOutputSlice = [&](Value outputAcc, Value hSlice) -> Value {
|
||||||
Value reduced =
|
Value reduced =
|
||||||
reducePartialPiecesForHSlice(partialPiecesArg, hSlice, pieceType, numKSlices, numOutRows, rewriter, loc);
|
reducePartialPiecesForHSlice(partialPiecesArg, hSlice, pieceType, numKSlices, numOutRows, rewriter, loc);
|
||||||
Value hOffset = multiplyIndexByConstant(hSlice, crossbarSize.getValue(), rewriter, loc);
|
Value hOffset = onnx_mlir::affineMulConst(
|
||||||
if (biasArg) {
|
rewriter, loc, hSlice, crossbarSize.getValue(), rewriter.getInsertionBlock()->getParentOp());
|
||||||
SmallVector<OpFoldResult> biasOffsets {rewriter.getIndexAttr(0), hOffset};
|
if (biasArg) {
|
||||||
Value biasSlice =
|
SmallVector<OpFoldResult> biasOffsets {rewriter.getIndexAttr(0), hOffset};
|
||||||
tensor::ExtractSliceOp::create(rewriter, loc, pieceType, biasArg, biasOffsets, pieceSizes, unitStrides)
|
Value biasSlice =
|
||||||
.getResult();
|
tensor::ExtractSliceOp::create(rewriter, loc, pieceType, biasArg, biasOffsets, pieceSizes, unitStrides)
|
||||||
reduced = spatial::SpatVAddOp::create(rewriter, loc, pieceType, reduced, biasSlice).getResult();
|
.getResult();
|
||||||
|
reduced = spatial::SpatVAddOp::create(rewriter, loc, pieceType, reduced, biasSlice).getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
SmallVector<OpFoldResult> outputOffsets {rewriter.getIndexAttr(0), hOffset};
|
||||||
|
return tensor::InsertSliceOp::create(rewriter, loc, reduced, outputAcc, outputOffsets, pieceSizes, unitStrides)
|
||||||
|
.getResult();
|
||||||
|
};
|
||||||
|
|
||||||
|
Value paddedOutput = outputInit;
|
||||||
|
if (numOutHSlices == 1) {
|
||||||
|
Value hSlice = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 0);
|
||||||
|
paddedOutput = buildOutputSlice(outputInit, hSlice);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Value c0 = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 0);
|
||||||
|
Value c1 = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 1);
|
||||||
|
Value cOutHSlices =
|
||||||
|
getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), numOutHSlices);
|
||||||
|
auto hLoop = buildNormalizedScfFor(
|
||||||
|
rewriter,
|
||||||
|
loc,
|
||||||
|
c0,
|
||||||
|
cOutHSlices,
|
||||||
|
c1,
|
||||||
|
ValueRange {outputInit},
|
||||||
|
[&](OpBuilder&, Location, Value hSlice, ValueRange iterArgs, SmallVectorImpl<Value>& yielded) {
|
||||||
|
yielded.push_back(buildOutputSlice(iterArgs.front(), hSlice));
|
||||||
|
return success();
|
||||||
|
});
|
||||||
|
if (failed(hLoop))
|
||||||
|
return failure();
|
||||||
|
paddedOutput = hLoop->results.front();
|
||||||
}
|
}
|
||||||
|
|
||||||
SmallVector<OpFoldResult> outputOffsets {rewriter.getIndexAttr(0), hOffset};
|
Value result = paddedOutput;
|
||||||
return tensor::InsertSliceOp::create(rewriter, loc, reduced, outputAcc, outputOffsets, pieceSizes, unitStrides)
|
if (paddedOutType != outType) {
|
||||||
.getResult();
|
SmallVector<OpFoldResult> outputOffsets {rewriter.getIndexAttr(0), rewriter.getIndexAttr(0)};
|
||||||
};
|
SmallVector<OpFoldResult> outputSizes {rewriter.getIndexAttr(outType.getDimSize(0)),
|
||||||
|
rewriter.getIndexAttr(outType.getDimSize(1))};
|
||||||
Value paddedOutput = outputInit;
|
result =
|
||||||
if (numOutHSlices == 1) {
|
tensor::ExtractSliceOp::create(rewriter, loc, outType, paddedOutput, outputOffsets, outputSizes, unitStrides)
|
||||||
Value hSlice = createIndexConstant(rewriter, 0);
|
.getResult();
|
||||||
paddedOutput = buildOutputSlice(outputInit, hSlice);
|
}
|
||||||
}
|
spatial::SpatYieldOp::create(rewriter, loc, result);
|
||||||
else {
|
return success();
|
||||||
Value c0 = createIndexConstant(rewriter, 0);
|
});
|
||||||
Value c1 = createIndexConstant(rewriter, 1);
|
|
||||||
Value cOutHSlices = createIndexConstant(rewriter, numOutHSlices);
|
|
||||||
auto hLoop = scf::ForOp::create(rewriter, loc, c0, cOutHSlices, c1, ValueRange {outputInit});
|
|
||||||
rewriter.setInsertionPointToStart(hLoop.getBody());
|
|
||||||
|
|
||||||
Value hSlice = hLoop.getInductionVar();
|
|
||||||
Value outputAcc = hLoop.getRegionIterArgs().front();
|
|
||||||
scf::YieldOp::create(rewriter, loc, buildOutputSlice(outputAcc, hSlice));
|
|
||||||
|
|
||||||
rewriter.setInsertionPointAfter(hLoop);
|
|
||||||
paddedOutput = hLoop.getResult(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Value result = paddedOutput;
|
|
||||||
if (paddedOutType != outType) {
|
|
||||||
SmallVector<OpFoldResult> outputOffsets {rewriter.getIndexAttr(0), rewriter.getIndexAttr(0)};
|
|
||||||
SmallVector<OpFoldResult> outputSizes {rewriter.getIndexAttr(outType.getDimSize(0)),
|
|
||||||
rewriter.getIndexAttr(outType.getDimSize(1))};
|
|
||||||
result =
|
|
||||||
tensor::ExtractSliceOp::create(rewriter, loc, outType, paddedOutput, outputOffsets, outputSizes, unitStrides)
|
|
||||||
.getResult();
|
|
||||||
}
|
|
||||||
spatial::SpatYieldOp::create(rewriter, loc, result);
|
|
||||||
});
|
|
||||||
|
|
||||||
return computeOp;
|
return computeOp;
|
||||||
}
|
}
|
||||||
@@ -735,11 +651,6 @@ LogicalResult GemmToSpatialComputes::matchAndRewrite(ONNXGemmOp gemmOp,
|
|||||||
Value b = gemmOpAdaptor.getB();
|
Value b = gemmOpAdaptor.getB();
|
||||||
Value c = gemmOpAdaptor.getC();
|
Value c = gemmOpAdaptor.getC();
|
||||||
|
|
||||||
if (gemmOpAdaptor.getTransA()) {
|
|
||||||
gemmOp.emitOpError("requires transA=false before tiled Spatial Gemm lowering");
|
|
||||||
return failure();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto aType = dyn_cast<RankedTensorType>(a.getType());
|
auto aType = dyn_cast<RankedTensorType>(a.getType());
|
||||||
auto bType = dyn_cast<RankedTensorType>(b.getType());
|
auto bType = dyn_cast<RankedTensorType>(b.getType());
|
||||||
auto outType = dyn_cast<RankedTensorType>(gemmOp.getY().getType());
|
auto outType = dyn_cast<RankedTensorType>(gemmOp.getY().getType());
|
||||||
@@ -770,6 +681,20 @@ LogicalResult GemmToSpatialComputes::matchAndRewrite(ONNXGemmOp gemmOp,
|
|||||||
return failure();
|
return failure();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (gemmOpAdaptor.getTransA()) {
|
||||||
|
auto aShape = aType.getShape();
|
||||||
|
auto transposedType = RankedTensorType::get({aShape[1], aShape[0]}, aType.getElementType(), aType.getEncoding());
|
||||||
|
a = ONNXTransposeOp::create(rewriter, loc, transposedType, a, rewriter.getI64ArrayAttr({1, 0})).getResult();
|
||||||
|
aType = transposedType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gemmOpAdaptor.getTransB()) {
|
||||||
|
auto bShape = bType.getShape();
|
||||||
|
auto transposedType = RankedTensorType::get({bShape[1], bShape[0]}, bType.getElementType(), bType.getEncoding());
|
||||||
|
b = ONNXTransposeOp::create(rewriter, loc, transposedType, b, rewriter.getI64ArrayAttr({1, 0})).getResult();
|
||||||
|
bType = transposedType;
|
||||||
|
}
|
||||||
|
|
||||||
const int64_t numOutRows = outType.getDimSize(0);
|
const int64_t numOutRows = outType.getDimSize(0);
|
||||||
const int64_t numOutCols = outType.getDimSize(1);
|
const int64_t numOutCols = outType.getDimSize(1);
|
||||||
const int64_t reductionSize = aType.getDimSize(1);
|
const int64_t reductionSize = aType.getDimSize(1);
|
||||||
@@ -793,10 +718,8 @@ LogicalResult GemmToSpatialComputes::matchAndRewrite(ONNXGemmOp gemmOp,
|
|||||||
biasType = *verifiedBiasType;
|
biasType = *verifiedBiasType;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int64_t expectedBRows = gemmOpAdaptor.getTransB() ? numOutCols : reductionSize;
|
if (aType.getDimSize(0) != numOutRows || bType.getDimSize(0) != reductionSize
|
||||||
const int64_t expectedBCols = gemmOpAdaptor.getTransB() ? reductionSize : numOutCols;
|
|| bType.getDimSize(1) != numOutCols) {
|
||||||
if (aType.getDimSize(0) != numOutRows || bType.getDimSize(0) != expectedBRows
|
|
||||||
|| bType.getDimSize(1) != expectedBCols) {
|
|
||||||
gemmOp.emitOpError("has inconsistent A, B, and output shapes");
|
gemmOp.emitOpError("has inconsistent A, B, and output shapes");
|
||||||
return failure();
|
return failure();
|
||||||
}
|
}
|
||||||
@@ -808,11 +731,14 @@ LogicalResult GemmToSpatialComputes::matchAndRewrite(ONNXGemmOp gemmOp,
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto scalarPiecesType = RankedTensorType::get({laneCount64, 1}, outType.getElementType());
|
auto scalarPiecesType = RankedTensorType::get({laneCount64, 1}, outType.getElementType());
|
||||||
auto batchOp = createVvdmulBatch(
|
auto batchOp = createVvdmulBatch(a, b, aType, bType, scalarPiecesType, outType, rewriter, loc);
|
||||||
a, b, aType, bType, scalarPiecesType, outType, gemmOpAdaptor.getTransB(), rewriter, loc);
|
if (failed(batchOp))
|
||||||
|
return failure();
|
||||||
auto outputCompute = createDynamicGemmOutputCompute(
|
auto outputCompute = createDynamicGemmOutputCompute(
|
||||||
batchOp.getResult(0), hasC ? c : Value(), scalarPiecesType, biasType, outType, alpha, beta, rewriter, loc);
|
batchOp->getResult(0), hasC ? c : Value(), scalarPiecesType, biasType, outType, alpha, beta, rewriter, loc);
|
||||||
rewriter.replaceOp(gemmOp, outputCompute.getResults());
|
if (failed(outputCompute))
|
||||||
|
return failure();
|
||||||
|
rewriter.replaceOp(gemmOp, outputCompute->getResults());
|
||||||
return success();
|
return success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -824,13 +750,6 @@ LogicalResult GemmToSpatialComputes::matchAndRewrite(ONNXGemmOp gemmOp,
|
|||||||
b = *scaledB;
|
b = *scaledB;
|
||||||
bType = cast<RankedTensorType>(b.getType());
|
bType = cast<RankedTensorType>(b.getType());
|
||||||
|
|
||||||
if (gemmOpAdaptor.getTransB()) {
|
|
||||||
auto bShape = bType.getShape();
|
|
||||||
auto transposedType = RankedTensorType::get({bShape[1], bShape[0]}, bType.getElementType());
|
|
||||||
b = transposeForSpatial(b, transposedType, {1, 0}, rewriter, loc);
|
|
||||||
bType = cast<RankedTensorType>(b.getType());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aType.getDimSize(0) != numOutRows || bType.getDimSize(0) != reductionSize || bType.getDimSize(1) != numOutCols) {
|
if (aType.getDimSize(0) != numOutRows || bType.getDimSize(0) != reductionSize || bType.getDimSize(1) != numOutCols) {
|
||||||
gemmOp.emitOpError("has inconsistent A, B, and output shapes after transpose handling");
|
gemmOp.emitOpError("has inconsistent A, B, and output shapes after transpose handling");
|
||||||
return failure();
|
return failure();
|
||||||
@@ -887,10 +806,14 @@ LogicalResult GemmToSpatialComputes::matchAndRewrite(ONNXGemmOp gemmOp,
|
|||||||
RankedTensorType::get({laneCount64, static_cast<int64_t>(crossbarSize.getValue())}, outType.getElementType());
|
RankedTensorType::get({laneCount64, static_cast<int64_t>(crossbarSize.getValue())}, outType.getElementType());
|
||||||
auto batchOp =
|
auto batchOp =
|
||||||
createVmmBatch(a, b, aType, paddedBType, partialPiecesType, numOutRows, numKSlices, numOutHSlices, rewriter, loc);
|
createVmmBatch(a, b, aType, paddedBType, partialPiecesType, numOutRows, numKSlices, numOutHSlices, rewriter, loc);
|
||||||
|
if (failed(batchOp))
|
||||||
|
return failure();
|
||||||
auto reductionCompute = createReductionCompute(
|
auto reductionCompute = createReductionCompute(
|
||||||
batchOp.getResult(0), bias, partialPiecesType, outType, paddedOutType, numKSlices, rewriter, loc);
|
batchOp->getResult(0), bias, partialPiecesType, outType, paddedOutType, numKSlices, rewriter, loc);
|
||||||
|
if (failed(reductionCompute))
|
||||||
|
return failure();
|
||||||
|
|
||||||
rewriter.replaceOp(gemmOp, reductionCompute.getResults());
|
rewriter.replaceOp(gemmOp, reductionCompute->getResults());
|
||||||
return success();
|
return success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,18 @@
|
|||||||
|
#include "mlir/Dialect/Affine/IR/AffineOps.h"
|
||||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
#include "mlir/Transforms/DialectConversion.h"
|
#include "mlir/Transforms/DialectConversion.h"
|
||||||
|
|
||||||
#include "llvm/ADT/SmallVector.h"
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <numeric>
|
||||||
|
#include <optional>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/AffineUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ConversionPatterns.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||||
|
|
||||||
@@ -16,26 +21,85 @@ using namespace mlir;
|
|||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
static SmallVector<int64_t> normalizeAxes(ArrayAttr axesAttr, int64_t rank) {
|
struct ReduceMeanSemantics {
|
||||||
|
SmallVector<int64_t> axes;
|
||||||
|
int64_t keepdims = 1;
|
||||||
|
bool isIdentity = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool isNoneValueLike(Value value) { return isa_and_nonnull<ONNXNoneOp>(value.getDefiningOp()); }
|
||||||
|
|
||||||
|
static FailureOr<SmallVector<int64_t>> getConstantIntValues(Value value) {
|
||||||
|
auto denseAttr = dyn_cast_or_null<DenseIntElementsAttr>(getHostConstDenseElementsAttr(value));
|
||||||
|
if (!denseAttr)
|
||||||
|
return failure();
|
||||||
|
return SmallVector<int64_t>(denseAttr.getValues<int64_t>().begin(), denseAttr.getValues<int64_t>().end());
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<SmallVector<int64_t>> normalizeAxesChecked(ArrayRef<int64_t> axes, int64_t rank) {
|
||||||
SmallVector<int64_t> normalizedAxes;
|
SmallVector<int64_t> normalizedAxes;
|
||||||
if (!axesAttr) {
|
normalizedAxes.reserve(axes.size());
|
||||||
normalizedAxes.reserve(rank);
|
for (int64_t axis : axes) {
|
||||||
for (int64_t axis = 0; axis < rank; axis++)
|
auto normalizedAxis = normalizeAxisChecked(axis, rank);
|
||||||
normalizedAxes.push_back(axis);
|
if (failed(normalizedAxis))
|
||||||
return normalizedAxes;
|
return failure();
|
||||||
|
normalizedAxes.push_back(*normalizedAxis);
|
||||||
}
|
}
|
||||||
|
|
||||||
normalizedAxes.reserve(axesAttr.size());
|
|
||||||
for (Attribute attr : axesAttr) {
|
|
||||||
int64_t axis = cast<IntegerAttr>(attr).getInt();
|
|
||||||
normalizedAxes.push_back(axis >= 0 ? axis : rank + axis);
|
|
||||||
}
|
|
||||||
|
|
||||||
llvm::sort(normalizedAxes);
|
llvm::sort(normalizedAxes);
|
||||||
normalizedAxes.erase(std::unique(normalizedAxes.begin(), normalizedAxes.end()), normalizedAxes.end());
|
normalizedAxes.erase(std::unique(normalizedAxes.begin(), normalizedAxes.end()), normalizedAxes.end());
|
||||||
return normalizedAxes;
|
return normalizedAxes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename ReduceMeanOp, typename ReduceMeanOpAdaptor>
|
||||||
|
static FailureOr<ReduceMeanSemantics>
|
||||||
|
getReduceMeanSemantics(ReduceMeanOp reduceMeanOp, ReduceMeanOpAdaptor adaptor, int64_t inputRank) {
|
||||||
|
ReduceMeanSemantics semantics;
|
||||||
|
semantics.keepdims = reduceMeanOp.getKeepdims();
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<ReduceMeanOp, ONNXReduceMeanV13Op>) {
|
||||||
|
auto axes = onnx_mlir::normalizeAxesChecked(std::optional<ArrayAttr>(reduceMeanOp.getAxesAttr()), inputRank);
|
||||||
|
if (failed(axes))
|
||||||
|
return failure();
|
||||||
|
semantics.axes = std::move(*axes);
|
||||||
|
return semantics;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (isNoneValueLike(adaptor.getAxes())) {
|
||||||
|
if (reduceMeanOp.getNoopWithEmptyAxes() != 0) {
|
||||||
|
semantics.isIdentity = true;
|
||||||
|
return semantics;
|
||||||
|
}
|
||||||
|
|
||||||
|
semantics.axes.reserve(inputRank);
|
||||||
|
for (int64_t axis = 0; axis < inputRank; ++axis)
|
||||||
|
semantics.axes.push_back(axis);
|
||||||
|
return semantics;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto axes = getConstantIntValues(adaptor.getAxes());
|
||||||
|
if (failed(axes))
|
||||||
|
return failure();
|
||||||
|
|
||||||
|
if (axes->empty()) {
|
||||||
|
if (reduceMeanOp.getNoopWithEmptyAxes() != 0) {
|
||||||
|
semantics.isIdentity = true;
|
||||||
|
return semantics;
|
||||||
|
}
|
||||||
|
|
||||||
|
semantics.axes.reserve(inputRank);
|
||||||
|
for (int64_t axis = 0; axis < inputRank; ++axis)
|
||||||
|
semantics.axes.push_back(axis);
|
||||||
|
return semantics;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto normalizedAxes = normalizeAxesChecked(*axes, inputRank);
|
||||||
|
if (failed(normalizedAxes))
|
||||||
|
return failure();
|
||||||
|
semantics.axes = std::move(*normalizedAxes);
|
||||||
|
return semantics;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static SmallVector<bool> buildReducedAxesMask(ArrayRef<int64_t> axes, int64_t rank) {
|
static SmallVector<bool> buildReducedAxesMask(ArrayRef<int64_t> axes, int64_t rank) {
|
||||||
SmallVector<bool> reducedAxes(rank, false);
|
SmallVector<bool> reducedAxes(rank, false);
|
||||||
for (int64_t axis : axes) {
|
for (int64_t axis : axes) {
|
||||||
@@ -50,6 +114,184 @@ static RankedTensorType getAllOnesType(RankedTensorType inputType, Type elementT
|
|||||||
return RankedTensorType::get(SmallVector<int64_t>(inputType.getRank(), 1), elementType);
|
return RankedTensorType::get(SmallVector<int64_t>(inputType.getRank(), 1), elementType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static RankedTensorType getKeepdimsType(RankedTensorType inputType, Type elementType, ArrayRef<bool> reducedAxes) {
|
||||||
|
SmallVector<int64_t> shape;
|
||||||
|
shape.reserve(inputType.getRank());
|
||||||
|
for (auto [dim, isReduced] : llvm::zip_equal(inputType.getShape(), reducedAxes))
|
||||||
|
shape.push_back(isReduced ? 1 : dim);
|
||||||
|
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());
|
||||||
|
for (auto [dim, isReduced] : llvm::zip_equal(inputType.getShape(), reducedAxes))
|
||||||
|
shape.push_back(isReduced ? dim : 1);
|
||||||
|
return RankedTensorType::get(shape, inputType.getElementType(), inputType.getEncoding());
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
static SmallVector<int64_t> getKeptAxes(ArrayRef<bool> reducedAxes) {
|
||||||
|
SmallVector<int64_t> keptAxes;
|
||||||
|
for (auto [axis, isReduced] : llvm::enumerate(reducedAxes))
|
||||||
|
if (!isReduced)
|
||||||
|
keptAxes.push_back(static_cast<int64_t>(axis));
|
||||||
|
return keptAxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Value
|
||||||
|
computeLaneIndex(Value lane, int64_t stride, int64_t dimSize, ConversionPatternRewriter& rewriter, Location loc) {
|
||||||
|
if (dimSize == 1)
|
||||||
|
return getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 0);
|
||||||
|
|
||||||
|
MLIRContext* context = rewriter.getContext();
|
||||||
|
AffineExpr d0 = getAffineDimExpr(0, context);
|
||||||
|
AffineExpr expr = d0;
|
||||||
|
if (stride != 1)
|
||||||
|
expr = expr.floorDiv(stride);
|
||||||
|
if (dimSize != 1)
|
||||||
|
expr = expr % dimSize;
|
||||||
|
return createOrFoldAffineApply(rewriter, loc, expr, ValueRange {lane}, rewriter.getInsertionBlock()->getParentOp());
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<Value> buildReduceMeanKeepdimsBatch(Value input,
|
||||||
|
ArrayRef<bool> reducedAxes,
|
||||||
|
RankedTensorType batchType,
|
||||||
|
RankedTensorType leafType,
|
||||||
|
ConversionPatternRewriter& rewriter,
|
||||||
|
Location loc) {
|
||||||
|
auto inputType = cast<RankedTensorType>(input.getType());
|
||||||
|
auto sliceType = getReducedSliceType(inputType, reducedAxes);
|
||||||
|
SmallVector<int64_t> keptAxes = getKeptAxes(reducedAxes);
|
||||||
|
|
||||||
|
int64_t laneCount = 1;
|
||||||
|
SmallVector<int64_t> keptAxisStrides(keptAxes.size(), 1);
|
||||||
|
for (int64_t index = static_cast<int64_t>(keptAxes.size()) - 1; index >= 0; --index) {
|
||||||
|
keptAxisStrides[index] = laneCount;
|
||||||
|
int64_t dimSize = inputType.getDimSize(keptAxes[index]);
|
||||||
|
if (dimSize <= 0)
|
||||||
|
return failure();
|
||||||
|
if (laneCount > std::numeric_limits<int32_t>::max() / dimSize)
|
||||||
|
return failure();
|
||||||
|
laneCount *= dimSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
loc,
|
||||||
|
TypeRange {batchType},
|
||||||
|
laneCount,
|
||||||
|
{},
|
||||||
|
ValueRange {input},
|
||||||
|
[&](detail::SpatComputeBatchBodyArgs args) {
|
||||||
|
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));
|
||||||
|
sliceSizes.push_back(rewriter.getIndexAttr(inputType.getDimSize(axis)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value axisIndex = computeLaneIndex(
|
||||||
|
args.lane, keptAxisStrides[keptAxisIndex], inputType.getDimSize(axis), rewriter, loc);
|
||||||
|
++keptAxisIndex;
|
||||||
|
sliceOffsets.push_back(axisIndex);
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
if (!pendingLeadingReducedAxes.empty())
|
||||||
|
expandCompactToKeepdims.back().append(pendingLeadingReducedAxes.begin(), pendingLeadingReducedAxes.end());
|
||||||
|
|
||||||
|
auto reshapeCompute =
|
||||||
|
createSpatCompute<1>(rewriter, loc, TypeRange {keepdimsType}, {}, ValueRange {batchValue}, [&](Value input) {
|
||||||
|
auto flatType =
|
||||||
|
RankedTensorType::get({batchType.getDimSize(0)}, batchType.getElementType(), batchType.getEncoding());
|
||||||
|
Value flat = tensor::CollapseShapeOp::create(rewriter, loc, flatType, input, collapseToFlat);
|
||||||
|
Value compact = flat;
|
||||||
|
if (compactKeptType != flatType)
|
||||||
|
compact = tensor::ExpandShapeOp::create(rewriter, loc, compactKeptType, flat, expandFlatToCompact);
|
||||||
|
Value keepdims = compact;
|
||||||
|
if (keepdimsType != compactKeptType)
|
||||||
|
keepdims = tensor::ExpandShapeOp::create(rewriter, loc, keepdimsType, compact, expandCompactToKeepdims);
|
||||||
|
spatial::SpatYieldOp::create(rewriter, loc, keepdims);
|
||||||
|
});
|
||||||
|
return reshapeCompute.getResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
static SmallVector<ReassociationIndices> buildCollapseReassociation(ArrayRef<bool> reducedAxes) {
|
static SmallVector<ReassociationIndices> buildCollapseReassociation(ArrayRef<bool> reducedAxes) {
|
||||||
SmallVector<ReassociationIndices> reassociation;
|
SmallVector<ReassociationIndices> reassociation;
|
||||||
ReassociationIndices currentGroup;
|
ReassociationIndices currentGroup;
|
||||||
@@ -72,69 +314,13 @@ static SmallVector<ReassociationIndices> buildCollapseReassociation(ArrayRef<boo
|
|||||||
return reassociation;
|
return reassociation;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value
|
|
||||||
createAverageCompute(Value input, RankedTensorType resultType, ConversionPatternRewriter& rewriter, Location loc) {
|
|
||||||
constexpr size_t numInputs = 1;
|
|
||||||
auto computeOp = createSpatCompute<numInputs>(rewriter, loc, resultType, {}, ValueRange {input}, [&](Value x) {
|
|
||||||
auto avgOp = spatial::SpatVAvgOp::create(rewriter, loc, resultType, x);
|
|
||||||
spatial::SpatYieldOp::create(rewriter, loc, avgOp.getResult());
|
|
||||||
});
|
|
||||||
return computeOp.getResult(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Value concatValues(ValueRange inputs, int64_t axis, ConversionPatternRewriter& rewriter, Location loc) {
|
|
||||||
auto firstType = cast<RankedTensorType>(inputs.front().getType());
|
|
||||||
SmallVector<int64_t> outputShape(firstType.getShape().begin(), firstType.getShape().end());
|
|
||||||
int64_t concatDimSize = 0;
|
|
||||||
for (Value input : inputs)
|
|
||||||
concatDimSize += cast<RankedTensorType>(input.getType()).getDimSize(axis);
|
|
||||||
outputShape[axis] = concatDimSize;
|
|
||||||
auto resultType = RankedTensorType::get(outputShape, firstType.getElementType(), firstType.getEncoding());
|
|
||||||
|
|
||||||
if (llvm::all_of(inputs, isCompileTimeComputable))
|
|
||||||
return createSpatConcat(rewriter, loc, axis, inputs);
|
|
||||||
|
|
||||||
auto concatCompute = createSpatCompute(rewriter, loc, TypeRange {resultType}, {}, inputs, [&](ValueRange args) {
|
|
||||||
spatial::SpatYieldOp::create(rewriter, loc, createSpatConcat(rewriter, loc, axis, args));
|
|
||||||
});
|
|
||||||
return concatCompute.getResult(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Value buildReduceMeanKeepdims(Value input,
|
|
||||||
ArrayRef<bool> reducedAxes,
|
|
||||||
int64_t axis,
|
|
||||||
RankedTensorType leafType,
|
|
||||||
ConversionPatternRewriter& rewriter,
|
|
||||||
Location loc) {
|
|
||||||
int64_t rank = cast<RankedTensorType>(input.getType()).getRank();
|
|
||||||
if (axis == rank)
|
|
||||||
return createAverageCompute(input, leafType, rewriter, loc);
|
|
||||||
|
|
||||||
if (reducedAxes[axis])
|
|
||||||
return buildReduceMeanKeepdims(input, reducedAxes, axis + 1, leafType, rewriter, loc);
|
|
||||||
|
|
||||||
SmallVector<Value> slices = sliceTensor(input, axis, /*sliceSize=*/1, rewriter, loc);
|
|
||||||
SmallVector<Value> reducedSlices;
|
|
||||||
reducedSlices.reserve(slices.size());
|
|
||||||
for (Value slice : slices)
|
|
||||||
reducedSlices.push_back(buildReduceMeanKeepdims(slice, reducedAxes, axis + 1, leafType, rewriter, loc));
|
|
||||||
|
|
||||||
return concatValues(reducedSlices, axis, rewriter, loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Value squeezeReducedAxes(Value keepdimsValue,
|
static Value squeezeReducedAxes(Value keepdimsValue,
|
||||||
RankedTensorType resultType,
|
RankedTensorType resultType,
|
||||||
ArrayRef<bool> reducedAxes,
|
ArrayRef<bool> reducedAxes,
|
||||||
ConversionPatternRewriter& rewriter,
|
ConversionPatternRewriter& rewriter,
|
||||||
Location loc) {
|
Location loc) {
|
||||||
if (resultType.getRank() == 0) {
|
SmallVector<ReassociationIndices> reassociation =
|
||||||
SmallVector<Value> indices(cast<RankedTensorType>(keepdimsValue.getType()).getRank(),
|
resultType.getRank() == 0 ? SmallVector<ReassociationIndices> {} : buildCollapseReassociation(reducedAxes);
|
||||||
arith::ConstantIndexOp::create(rewriter, loc, 0));
|
|
||||||
Value element = tensor::ExtractOp::create(rewriter, loc, keepdimsValue, indices);
|
|
||||||
return tensor::FromElementsOp::create(rewriter, loc, resultType, ValueRange {element});
|
|
||||||
}
|
|
||||||
|
|
||||||
auto reassociation = buildCollapseReassociation(reducedAxes);
|
|
||||||
if (isCompileTimeComputable(keepdimsValue))
|
if (isCompileTimeComputable(keepdimsValue))
|
||||||
return tensor::CollapseShapeOp::create(rewriter, loc, resultType, keepdimsValue, reassociation).getResult();
|
return tensor::CollapseShapeOp::create(rewriter, loc, resultType, keepdimsValue, reassociation).getResult();
|
||||||
|
|
||||||
@@ -146,28 +332,55 @@ static Value squeezeReducedAxes(Value keepdimsValue,
|
|||||||
return squeezeCompute.getResult(0);
|
return squeezeCompute.getResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ReduceMeanToSpatialCompute : OpConversionPattern<ONNXReduceMeanV13Op> {
|
template <typename ReduceMeanOp>
|
||||||
using OpConversionPattern::OpConversionPattern;
|
struct ReduceMeanToSpatialCompute : OpConversionPattern<ReduceMeanOp> {
|
||||||
|
using OpConversionPattern<ReduceMeanOp>::OpConversionPattern;
|
||||||
|
using Adaptor = typename ReduceMeanOp::Adaptor;
|
||||||
|
|
||||||
LogicalResult matchAndRewrite(ONNXReduceMeanV13Op reduceMeanOp,
|
LogicalResult matchAndRewrite(ReduceMeanOp reduceMeanOp,
|
||||||
ONNXReduceMeanV13OpAdaptor adaptor,
|
Adaptor adaptor,
|
||||||
ConversionPatternRewriter& rewriter) const override {
|
ConversionPatternRewriter& rewriter) const override {
|
||||||
auto inputType = dyn_cast<RankedTensorType>(adaptor.getData().getType());
|
auto inputType = dyn_cast<RankedTensorType>(adaptor.getData().getType());
|
||||||
auto resultType = dyn_cast<RankedTensorType>(reduceMeanOp.getReduced().getType());
|
auto resultType = dyn_cast<RankedTensorType>(reduceMeanOp.getReduced().getType());
|
||||||
if (!inputType || !resultType || !inputType.hasStaticShape() || !resultType.hasStaticShape())
|
if (!inputType || !resultType || !inputType.hasStaticShape() || !resultType.hasStaticShape())
|
||||||
return failure();
|
return failure();
|
||||||
|
if (inputType.getRank() == 0) {
|
||||||
|
rewriter.replaceOp(reduceMeanOp, adaptor.getData());
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
SmallVector<int64_t> axes = normalizeAxes(reduceMeanOp.getAxesAttr(), inputType.getRank());
|
auto semantics = getReduceMeanSemantics(reduceMeanOp, adaptor, inputType.getRank());
|
||||||
SmallVector<bool> reducedAxes = buildReducedAxesMask(axes, inputType.getRank());
|
if (failed(semantics))
|
||||||
|
return rewriter.notifyMatchFailure(reduceMeanOp, "requires compile-time constant, in-range ReduceMean axes");
|
||||||
|
if (semantics->isIdentity) {
|
||||||
|
if (inputType != resultType)
|
||||||
|
return rewriter.notifyMatchFailure(
|
||||||
|
reduceMeanOp, "noop_with_empty_axes identity requires the result type to match the input type");
|
||||||
|
rewriter.replaceOp(reduceMeanOp, adaptor.getData());
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
SmallVector<bool> reducedAxes = buildReducedAxesMask(semantics->axes, inputType.getRank());
|
||||||
if (reducedAxes.empty() && inputType.getRank() != 0)
|
if (reducedAxes.empty() && inputType.getRank() != 0)
|
||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
Location loc = reduceMeanOp.getLoc();
|
Location loc = reduceMeanOp.getLoc();
|
||||||
RankedTensorType leafType = getAllOnesType(inputType, resultType.getElementType());
|
RankedTensorType leafType = getAllOnesType(inputType, resultType.getElementType());
|
||||||
Value reducedKeepdims =
|
RankedTensorType compactKeptType = getCompactKeptType(inputType, resultType.getElementType(), reducedAxes);
|
||||||
buildReduceMeanKeepdims(adaptor.getData(), reducedAxes, /*axis=*/0, leafType, rewriter, loc);
|
RankedTensorType keepdimsType = getKeepdimsType(inputType, resultType.getElementType(), reducedAxes);
|
||||||
|
int64_t laneCount = 1;
|
||||||
|
for (int64_t dim : compactKeptType.getShape())
|
||||||
|
laneCount *= dim;
|
||||||
|
RankedTensorType batchType = getLanePackedKeepdimsType(laneCount, leafType);
|
||||||
|
|
||||||
if (reduceMeanOp.getKeepdims() != 0) {
|
auto lanePackedKeepdims =
|
||||||
|
buildReduceMeanKeepdimsBatch(adaptor.getData(), reducedAxes, batchType, leafType, rewriter, loc);
|
||||||
|
if (failed(lanePackedKeepdims))
|
||||||
|
return failure();
|
||||||
|
Value reducedKeepdims =
|
||||||
|
buildKeepdimsFromLanePackedBatch(*lanePackedKeepdims, keepdimsType, compactKeptType, reducedAxes, rewriter, loc);
|
||||||
|
|
||||||
|
if (semantics->keepdims != 0) {
|
||||||
rewriter.replaceOp(reduceMeanOp, reducedKeepdims);
|
rewriter.replaceOp(reduceMeanOp, reducedKeepdims);
|
||||||
return success();
|
return success();
|
||||||
}
|
}
|
||||||
@@ -181,7 +394,7 @@ struct ReduceMeanToSpatialCompute : OpConversionPattern<ONNXReduceMeanV13Op> {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void populateReduceMeanPatterns(RewritePatternSet& patterns, MLIRContext* ctx) {
|
void populateReduceMeanPatterns(RewritePatternSet& patterns, MLIRContext* ctx) {
|
||||||
patterns.add<ReduceMeanToSpatialCompute>(ctx);
|
patterns.add<ReduceMeanToSpatialCompute<ONNXReduceMeanV13Op>, ReduceMeanToSpatialCompute<ONNXReduceMeanOp>>(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
} // namespace onnx_mlir
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||||
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||||
@@ -23,43 +24,26 @@ using namespace mlir;
|
|||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
template <typename ArrayAttrT>
|
static Value materializeTileTensor(ConversionPatternRewriter& rewriter, Location loc, Value tile) {
|
||||||
static int64_t getI64(ArrayAttrT arrayAttr, size_t index) {
|
|
||||||
return cast<IntegerAttr>(arrayAttr[index]).getInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename ArrayAttrT>
|
|
||||||
static int64_t getOptionalI64(std::optional<ArrayAttrT> arrayAttr, size_t index, int64_t defaultValue) {
|
|
||||||
return arrayAttr ? getI64(*arrayAttr, index) : defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Value materializeContiguousTile(ConversionPatternRewriter& rewriter, Location loc, Value tile) {
|
|
||||||
auto tileType = cast<RankedTensorType>(tile.getType());
|
auto tileType = cast<RankedTensorType>(tile.getType());
|
||||||
Value empty = tensor::EmptyOp::create(rewriter, loc, tileType.getShape(), tileType.getElementType());
|
Value empty = tensor::EmptyOp::create(rewriter, loc, tileType.getShape(), tileType.getElementType());
|
||||||
|
return insertStaticSlice(rewriter, loc, tile, empty, getZeroOffsets(rewriter, tileType.getRank()));
|
||||||
SmallVector<OpFoldResult> offsets(tileType.getRank(), rewriter.getIndexAttr(0));
|
|
||||||
SmallVector<OpFoldResult> sizes;
|
|
||||||
sizes.reserve(tileType.getRank());
|
|
||||||
for (int64_t dimSize : tileType.getShape())
|
|
||||||
sizes.push_back(rewriter.getIndexAttr(dimSize));
|
|
||||||
SmallVector<OpFoldResult> strides(tileType.getRank(), rewriter.getIndexAttr(1));
|
|
||||||
|
|
||||||
return tensor::InsertSliceOp::create(rewriter, loc, tile, empty, offsets, sizes, strides);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value
|
static Value
|
||||||
createPoolFillElement(ConversionPatternRewriter& rewriter, Location loc, Type elementType, bool useMinimumValue) {
|
createPoolFillElement(ConversionPatternRewriter& rewriter, Location loc, Type elementType, bool useMinimumValue) {
|
||||||
|
Operation* anchorOp = rewriter.getInsertionBlock()->getParentOp();
|
||||||
if (!useMinimumValue)
|
if (!useMinimumValue)
|
||||||
return arith::ConstantOp::create(rewriter, loc, elementType, rewriter.getZeroAttr(elementType));
|
return getOrCreateConstant(rewriter, anchorOp, rewriter.getZeroAttr(elementType), elementType);
|
||||||
|
|
||||||
if (auto floatType = dyn_cast<FloatType>(elementType)) {
|
if (auto floatType = dyn_cast<FloatType>(elementType)) {
|
||||||
auto minValue = llvm::APFloat::getInf(floatType.getFloatSemantics(), /*Negative=*/true);
|
auto minValue = llvm::APFloat::getInf(floatType.getFloatSemantics(), /*Negative=*/true);
|
||||||
return arith::ConstantOp::create(rewriter, loc, elementType, rewriter.getFloatAttr(floatType, minValue));
|
return getOrCreateConstant(rewriter, anchorOp, rewriter.getFloatAttr(floatType, minValue), elementType);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto integerType = dyn_cast<IntegerType>(elementType)) {
|
if (auto integerType = dyn_cast<IntegerType>(elementType)) {
|
||||||
auto minValue = llvm::APInt::getSignedMinValue(integerType.getWidth());
|
auto minValue = llvm::APInt::getSignedMinValue(integerType.getWidth());
|
||||||
return arith::ConstantOp::create(rewriter, loc, elementType, rewriter.getIntegerAttr(integerType, minValue));
|
return getOrCreateConstant(rewriter, anchorOp, rewriter.getIntegerAttr(integerType, minValue), elementType);
|
||||||
}
|
}
|
||||||
|
|
||||||
llvm_unreachable("unsupported pool element type");
|
llvm_unreachable("unsupported pool element type");
|
||||||
@@ -166,7 +150,7 @@ static FailureOr<Value> createAverageScaleTensor(ConversionPatternRewriter& rewr
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto scaleAttr = DenseElementsAttr::get(scaleType, scaleValues);
|
auto scaleAttr = DenseElementsAttr::get(scaleType, scaleValues);
|
||||||
return arith::ConstantOp::create(rewriter, loc, scaleType, scaleAttr).getResult();
|
return getOrCreateConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), scaleAttr, scaleType);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename PoolOp>
|
template <typename PoolOp>
|
||||||
@@ -197,12 +181,12 @@ struct PoolToSpatialComputeBase : public OpConversionPattern<PoolOp> {
|
|||||||
const int64_t inputWidth = xType.getDimSize(3);
|
const int64_t inputWidth = xType.getDimSize(3);
|
||||||
const int64_t outputHeight = outType.getDimSize(2);
|
const int64_t outputHeight = outType.getDimSize(2);
|
||||||
const int64_t outputWidth = outType.getDimSize(3);
|
const int64_t outputWidth = outType.getDimSize(3);
|
||||||
const int64_t kernelHeight = getI64(kernelAttr, 0);
|
const int64_t kernelHeight = getI64Attr(kernelAttr, 0);
|
||||||
const int64_t kernelWidth = getI64(kernelAttr, 1);
|
const int64_t kernelWidth = getI64Attr(kernelAttr, 1);
|
||||||
const int64_t strideHeight = getOptionalI64(poolOp.getStrides(), 0, 1);
|
const int64_t strideHeight = getOptionalI64Attr(poolOp.getStrides(), 0, 1);
|
||||||
const int64_t strideWidth = getOptionalI64(poolOp.getStrides(), 1, 1);
|
const int64_t strideWidth = getOptionalI64Attr(poolOp.getStrides(), 1, 1);
|
||||||
const int64_t dilationHeight = getOptionalI64(poolOp.getDilations(), 0, 1);
|
const int64_t dilationHeight = getOptionalI64Attr(poolOp.getDilations(), 0, 1);
|
||||||
const int64_t dilationWidth = getOptionalI64(poolOp.getDilations(), 1, 1);
|
const int64_t dilationWidth = getOptionalI64Attr(poolOp.getDilations(), 1, 1);
|
||||||
|
|
||||||
int64_t padTop = 0;
|
int64_t padTop = 0;
|
||||||
int64_t padLeft = 0;
|
int64_t padLeft = 0;
|
||||||
@@ -212,10 +196,10 @@ struct PoolToSpatialComputeBase : public OpConversionPattern<PoolOp> {
|
|||||||
if (auto padsAttr = poolOp.getPads()) {
|
if (auto padsAttr = poolOp.getPads()) {
|
||||||
if (padsAttr->size() != 4)
|
if (padsAttr->size() != 4)
|
||||||
return rewriter.notifyMatchFailure(poolOp, "pads must have four elements.");
|
return rewriter.notifyMatchFailure(poolOp, "pads must have four elements.");
|
||||||
padTop = getI64(*padsAttr, 0);
|
padTop = getI64Attr(*padsAttr, 0);
|
||||||
padLeft = getI64(*padsAttr, 1);
|
padLeft = getI64Attr(*padsAttr, 1);
|
||||||
padBottom = getI64(*padsAttr, 2);
|
padBottom = getI64Attr(*padsAttr, 2);
|
||||||
padRight = getI64(*padsAttr, 3);
|
padRight = getI64Attr(*padsAttr, 3);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
StringRef autoPad = poolOp.getAutoPad();
|
StringRef autoPad = poolOp.getAutoPad();
|
||||||
@@ -283,94 +267,111 @@ struct PoolToSpatialComputeBase : public OpConversionPattern<PoolOp> {
|
|||||||
createPaddedPoolInput(rewriter, loc, poolOp, xArg, xType, padTop, padLeft, padBottom, padRight);
|
createPaddedPoolInput(rewriter, loc, poolOp, xArg, xType, padTop, padLeft, padBottom, padRight);
|
||||||
Value pooledOutputInit = tensor::EmptyOp::create(rewriter, loc, outType.getShape(), outType.getElementType());
|
Value pooledOutputInit = tensor::EmptyOp::create(rewriter, loc, outType.getShape(), outType.getElementType());
|
||||||
|
|
||||||
Value c0 = arith::ConstantIndexOp::create(rewriter, loc, 0);
|
Operation* anchorOp = rewriter.getInsertionBlock()->getParentOp();
|
||||||
Value c1 = arith::ConstantIndexOp::create(rewriter, loc, 1);
|
Value c0 = getOrCreateIndexConstant(rewriter, anchorOp, 0);
|
||||||
Value cOutputPatchCount = arith::ConstantIndexOp::create(rewriter, loc, outputPatchCount);
|
Value c1 = getOrCreateIndexConstant(rewriter, anchorOp, 1);
|
||||||
Value cOutputPixelsPerBatch = arith::ConstantIndexOp::create(rewriter, loc, outputHeight * outputWidth);
|
Value cOutputPatchCount = getOrCreateIndexConstant(rewriter, anchorOp, outputPatchCount);
|
||||||
Value cOutputWidth = arith::ConstantIndexOp::create(rewriter, loc, outputWidth);
|
Value cOutputPixelsPerBatch = getOrCreateIndexConstant(rewriter, anchorOp, outputHeight * outputWidth);
|
||||||
Value cStrideHeight = arith::ConstantIndexOp::create(rewriter, loc, strideHeight);
|
Value cOutputWidth = getOrCreateIndexConstant(rewriter, anchorOp, outputWidth);
|
||||||
Value cStrideWidth = arith::ConstantIndexOp::create(rewriter, loc, strideWidth);
|
Value cStrideHeight = getOrCreateIndexConstant(rewriter, anchorOp, strideHeight);
|
||||||
|
Value cStrideWidth = getOrCreateIndexConstant(rewriter, anchorOp, strideWidth);
|
||||||
|
|
||||||
auto outputLoop = scf::ForOp::create(rewriter, loc, c0, cOutputPatchCount, c1, ValueRange {pooledOutputInit});
|
auto outputLoop = buildNormalizedScfFor(
|
||||||
rewriter.setInsertionPointToStart(outputLoop.getBody());
|
rewriter,
|
||||||
|
loc,
|
||||||
|
c0,
|
||||||
|
cOutputPatchCount,
|
||||||
|
c1,
|
||||||
|
ValueRange {pooledOutputInit},
|
||||||
|
[&](OpBuilder&,
|
||||||
|
Location nestedLoc,
|
||||||
|
Value outputPatchIndex,
|
||||||
|
ValueRange iterArgs,
|
||||||
|
SmallVectorImpl<Value>& yielded) {
|
||||||
|
Value pooledOutputAcc = iterArgs.front();
|
||||||
|
Value batchIndex = arith::DivUIOp::create(rewriter, nestedLoc, outputPatchIndex, cOutputPixelsPerBatch);
|
||||||
|
Value batchPatchIndex =
|
||||||
|
arith::RemUIOp::create(rewriter, nestedLoc, outputPatchIndex, cOutputPixelsPerBatch);
|
||||||
|
Value outHeightIndex = arith::DivUIOp::create(rewriter, nestedLoc, batchPatchIndex, cOutputWidth);
|
||||||
|
Value outWidthIndex = arith::RemUIOp::create(rewriter, nestedLoc, batchPatchIndex, cOutputWidth);
|
||||||
|
Value windowBaseH = arith::MulIOp::create(rewriter, nestedLoc, outHeightIndex, cStrideHeight);
|
||||||
|
Value windowBaseW = arith::MulIOp::create(rewriter, nestedLoc, outWidthIndex, cStrideWidth);
|
||||||
|
|
||||||
Value outputPatchIndex = outputLoop.getInductionVar();
|
Value updatedOutput = pooledOutputAcc;
|
||||||
Value pooledOutputAcc = outputLoop.getRegionIterArgs().front();
|
for (int64_t channelTile = 0; channelTile < channelTileCount; ++channelTile) {
|
||||||
|
const int64_t tileChannels = std::min<int64_t>(xbarSize, channels - channelTile * xbarSize);
|
||||||
|
auto tileType = RankedTensorType::get({1, tileChannels, 1, 1}, outType.getElementType());
|
||||||
|
Value reducedWindow =
|
||||||
|
createPoolFillTensor(rewriter, nestedLoc, tileType, std::is_same_v<PoolOp, ONNXMaxPoolSingleOutOp>);
|
||||||
|
|
||||||
Value batchIndex = arith::DivUIOp::create(rewriter, loc, outputPatchIndex, cOutputPixelsPerBatch);
|
for (int64_t kernelH = 0; kernelH < kernelHeight; ++kernelH) {
|
||||||
Value batchPatchIndex = arith::RemUIOp::create(rewriter, loc, outputPatchIndex, cOutputPixelsPerBatch);
|
Value paddedInH = windowBaseH;
|
||||||
Value outHeightIndex = arith::DivUIOp::create(rewriter, loc, batchPatchIndex, cOutputWidth);
|
if (kernelH * dilationHeight != 0) {
|
||||||
Value outWidthIndex = arith::RemUIOp::create(rewriter, loc, batchPatchIndex, cOutputWidth);
|
Value kernelHOffset = getOrCreateIndexConstant(rewriter, anchorOp, kernelH * dilationHeight);
|
||||||
Value windowBaseH = arith::MulIOp::create(rewriter, loc, outHeightIndex, cStrideHeight);
|
paddedInH = arith::AddIOp::create(rewriter, nestedLoc, paddedInH, kernelHOffset);
|
||||||
Value windowBaseW = arith::MulIOp::create(rewriter, loc, outWidthIndex, cStrideWidth);
|
}
|
||||||
|
|
||||||
Value updatedOutput = pooledOutputAcc;
|
for (int64_t kernelW = 0; kernelW < kernelWidth; ++kernelW) {
|
||||||
for (int64_t channelTile = 0; channelTile < channelTileCount; ++channelTile) {
|
Value paddedInW = windowBaseW;
|
||||||
const int64_t tileChannels = std::min<int64_t>(xbarSize, channels - channelTile * xbarSize);
|
if (kernelW * dilationWidth != 0) {
|
||||||
auto tileType = RankedTensorType::get({1, tileChannels, 1, 1}, outType.getElementType());
|
Value kernelWOffset = getOrCreateIndexConstant(rewriter, anchorOp, kernelW * dilationWidth);
|
||||||
Value reducedWindow =
|
paddedInW = arith::AddIOp::create(rewriter, nestedLoc, paddedInW, kernelWOffset);
|
||||||
createPoolFillTensor(rewriter, loc, tileType, std::is_same_v<PoolOp, ONNXMaxPoolSingleOutOp>);
|
}
|
||||||
|
|
||||||
for (int64_t kernelH = 0; kernelH < kernelHeight; ++kernelH) {
|
SmallVector<OpFoldResult> offsets = {
|
||||||
Value paddedInH = windowBaseH;
|
batchIndex, rewriter.getIndexAttr(channelTile * xbarSize), paddedInH, paddedInW};
|
||||||
if (kernelH * dilationHeight != 0) {
|
SmallVector<OpFoldResult> sizes = {rewriter.getIndexAttr(1),
|
||||||
Value kernelHOffset = arith::ConstantIndexOp::create(rewriter, loc, kernelH * dilationHeight);
|
rewriter.getIndexAttr(tileChannels),
|
||||||
paddedInH = arith::AddIOp::create(rewriter, loc, paddedInH, kernelHOffset);
|
rewriter.getIndexAttr(1),
|
||||||
}
|
rewriter.getIndexAttr(1)};
|
||||||
|
SmallVector<OpFoldResult> strides = {rewriter.getIndexAttr(1),
|
||||||
for (int64_t kernelW = 0; kernelW < kernelWidth; ++kernelW) {
|
rewriter.getIndexAttr(1),
|
||||||
Value paddedInW = windowBaseW;
|
rewriter.getIndexAttr(1),
|
||||||
if (kernelW * dilationWidth != 0) {
|
rewriter.getIndexAttr(1)};
|
||||||
Value kernelWOffset = arith::ConstantIndexOp::create(rewriter, loc, kernelW * dilationWidth);
|
Value windowValue =
|
||||||
paddedInW = arith::AddIOp::create(rewriter, loc, paddedInW, kernelWOffset);
|
tensor::ExtractSliceOp::create(rewriter, nestedLoc, tileType, paddedInput, offsets, sizes, strides);
|
||||||
|
windowValue = materializeTileTensor(rewriter, nestedLoc, windowValue);
|
||||||
|
reducedWindow = ReduceOp::create(rewriter, nestedLoc, tileType, reducedWindow, windowValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SmallVector<OpFoldResult> offsets = {
|
if constexpr (std::is_same_v<PoolOp, ONNXAveragePoolOp>) {
|
||||||
batchIndex, rewriter.getIndexAttr(channelTile * xbarSize), paddedInH, paddedInW};
|
SmallVector<OpFoldResult> scaleOffsets = {rewriter.getIndexAttr(0),
|
||||||
SmallVector<OpFoldResult> sizes = {rewriter.getIndexAttr(1),
|
rewriter.getIndexAttr(channelTile * xbarSize),
|
||||||
rewriter.getIndexAttr(tileChannels),
|
outHeightIndex,
|
||||||
rewriter.getIndexAttr(1),
|
outWidthIndex};
|
||||||
rewriter.getIndexAttr(1)};
|
SmallVector<OpFoldResult> scaleSizes = {rewriter.getIndexAttr(1),
|
||||||
SmallVector<OpFoldResult> strides = {
|
rewriter.getIndexAttr(tileChannels),
|
||||||
|
rewriter.getIndexAttr(1),
|
||||||
|
rewriter.getIndexAttr(1)};
|
||||||
|
SmallVector<OpFoldResult> scaleStrides = {rewriter.getIndexAttr(1),
|
||||||
|
rewriter.getIndexAttr(1),
|
||||||
|
rewriter.getIndexAttr(1),
|
||||||
|
rewriter.getIndexAttr(1)};
|
||||||
|
Value scaleSlice = tensor::ExtractSliceOp::create(
|
||||||
|
rewriter, nestedLoc, tileType, averageScaleTensor, scaleOffsets, scaleSizes, scaleStrides);
|
||||||
|
scaleSlice = materializeTileTensor(rewriter, nestedLoc, scaleSlice);
|
||||||
|
reducedWindow = spatial::SpatVMulOp::create(rewriter, nestedLoc, tileType, reducedWindow, scaleSlice);
|
||||||
|
}
|
||||||
|
|
||||||
|
SmallVector<OpFoldResult> outputOffsets = {
|
||||||
|
batchIndex, rewriter.getIndexAttr(channelTile * xbarSize), outHeightIndex, outWidthIndex};
|
||||||
|
SmallVector<OpFoldResult> outputSizes = {rewriter.getIndexAttr(1),
|
||||||
|
rewriter.getIndexAttr(tileChannels),
|
||||||
|
rewriter.getIndexAttr(1),
|
||||||
|
rewriter.getIndexAttr(1)};
|
||||||
|
SmallVector<OpFoldResult> outputStrides = {
|
||||||
rewriter.getIndexAttr(1), rewriter.getIndexAttr(1), rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
rewriter.getIndexAttr(1), rewriter.getIndexAttr(1), rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
||||||
Value windowValue =
|
updatedOutput = tensor::InsertSliceOp::create(
|
||||||
tensor::ExtractSliceOp::create(rewriter, loc, tileType, paddedInput, offsets, sizes, strides);
|
rewriter, nestedLoc, reducedWindow, updatedOutput, outputOffsets, outputSizes, outputStrides);
|
||||||
windowValue = materializeContiguousTile(rewriter, loc, windowValue);
|
|
||||||
reducedWindow = ReduceOp::create(rewriter, loc, tileType, reducedWindow, windowValue);
|
|
||||||
}
|
}
|
||||||
}
|
yielded.push_back(updatedOutput);
|
||||||
|
return success();
|
||||||
|
});
|
||||||
|
if (failed(outputLoop))
|
||||||
|
return failure();
|
||||||
|
|
||||||
if constexpr (std::is_same_v<PoolOp, ONNXAveragePoolOp>) {
|
spatial::SpatYieldOp::create(rewriter, loc, outputLoop->results.front());
|
||||||
SmallVector<OpFoldResult> scaleOffsets = {
|
|
||||||
rewriter.getIndexAttr(0), rewriter.getIndexAttr(channelTile * xbarSize), outHeightIndex, outWidthIndex};
|
|
||||||
SmallVector<OpFoldResult> scaleSizes = {rewriter.getIndexAttr(1),
|
|
||||||
rewriter.getIndexAttr(tileChannels),
|
|
||||||
rewriter.getIndexAttr(1),
|
|
||||||
rewriter.getIndexAttr(1)};
|
|
||||||
SmallVector<OpFoldResult> scaleStrides = {
|
|
||||||
rewriter.getIndexAttr(1), rewriter.getIndexAttr(1), rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
|
||||||
Value scaleSlice = tensor::ExtractSliceOp::create(
|
|
||||||
rewriter, loc, tileType, averageScaleTensor, scaleOffsets, scaleSizes, scaleStrides);
|
|
||||||
scaleSlice = materializeContiguousTile(rewriter, loc, scaleSlice);
|
|
||||||
reducedWindow = spatial::SpatVMulOp::create(rewriter, loc, tileType, reducedWindow, scaleSlice);
|
|
||||||
}
|
|
||||||
|
|
||||||
SmallVector<OpFoldResult> outputOffsets = {
|
|
||||||
batchIndex, rewriter.getIndexAttr(channelTile * xbarSize), outHeightIndex, outWidthIndex};
|
|
||||||
SmallVector<OpFoldResult> outputSizes = {rewriter.getIndexAttr(1),
|
|
||||||
rewriter.getIndexAttr(tileChannels),
|
|
||||||
rewriter.getIndexAttr(1),
|
|
||||||
rewriter.getIndexAttr(1)};
|
|
||||||
SmallVector<OpFoldResult> outputStrides = {
|
|
||||||
rewriter.getIndexAttr(1), rewriter.getIndexAttr(1), rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)};
|
|
||||||
updatedOutput = tensor::InsertSliceOp::create(
|
|
||||||
rewriter, loc, reducedWindow, updatedOutput, outputOffsets, outputSizes, outputStrides);
|
|
||||||
}
|
|
||||||
|
|
||||||
scf::YieldOp::create(rewriter, loc, updatedOutput);
|
|
||||||
|
|
||||||
rewriter.setInsertionPointAfter(outputLoop);
|
|
||||||
spatial::SpatYieldOp::create(rewriter, loc, outputLoop.getResult(0));
|
|
||||||
return success();
|
return success();
|
||||||
});
|
});
|
||||||
if (failed(computeOp))
|
if (failed(computeOp))
|
||||||
|
|||||||
@@ -16,12 +16,9 @@ struct ReluToSpatialCompute : OpConversionPattern<ONNXReluOp> {
|
|||||||
matchAndRewrite(ONNXReluOp reluOp, ONNXReluOpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override {
|
matchAndRewrite(ONNXReluOp reluOp, ONNXReluOpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override {
|
||||||
Location loc = reluOp.getLoc();
|
Location loc = reluOp.getLoc();
|
||||||
Type resultType = reluOp.getResult().getType();
|
Type resultType = reluOp.getResult().getType();
|
||||||
constexpr size_t numInputs = 1;
|
auto reluPlan = spatial::SpatReluPlanOp::create(
|
||||||
auto computeOp = createSpatCompute<numInputs>(rewriter, loc, resultType, {}, adaptor.getX(), [&](Value x) {
|
rewriter, loc, resultType, adaptor.getX(), rewriter.getStringAttr("nchw"));
|
||||||
auto spatReluOp = spatial::SpatReluOp::create(rewriter, loc, resultType, x);
|
rewriter.replaceOp(reluOp, reluPlan.getResult());
|
||||||
spatial::SpatYieldOp::create(rewriter, loc, spatReluOp.getResult());
|
|
||||||
});
|
|
||||||
rewriter.replaceOp(reluOp, computeOp);
|
|
||||||
return success();
|
return success();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
#include "mlir/Transforms/DialectConversion.h"
|
#include "mlir/Transforms/DialectConversion.h"
|
||||||
|
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ConversionPatterns.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||||
|
|
||||||
@@ -13,16 +14,6 @@ using namespace mlir;
|
|||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
static int64_t normalizeAxis(int64_t axis, int64_t rank) { return axis >= 0 ? axis : rank + axis; }
|
|
||||||
|
|
||||||
static 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Value buildLoopSoftmaxSlice(Value input,
|
static Value buildLoopSoftmaxSlice(Value input,
|
||||||
Value accumulator,
|
Value accumulator,
|
||||||
RankedTensorType inputType,
|
RankedTensorType inputType,
|
||||||
@@ -36,7 +27,7 @@ static Value buildLoopSoftmaxSlice(Value input,
|
|||||||
|
|
||||||
SmallVector<OpFoldResult> offsets;
|
SmallVector<OpFoldResult> offsets;
|
||||||
SmallVector<OpFoldResult> sizes;
|
SmallVector<OpFoldResult> sizes;
|
||||||
SmallVector<OpFoldResult> strides(rank, rewriter.getIndexAttr(1));
|
SmallVector<OpFoldResult> strides = getUnitStrides(rewriter, rank);
|
||||||
offsets.reserve(rank);
|
offsets.reserve(rank);
|
||||||
sizes.reserve(rank);
|
sizes.reserve(rank);
|
||||||
|
|
||||||
@@ -52,52 +43,65 @@ static Value buildLoopSoftmaxSlice(Value input,
|
|||||||
return tensor::InsertSliceOp::create(rewriter, loc, softmaxSlice, accumulator, offsets, sizes, strides);
|
return tensor::InsertSliceOp::create(rewriter, loc, softmaxSlice, accumulator, offsets, sizes, strides);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value buildLoopSoftmaxNest(Value input,
|
static FailureOr<Value> buildLoopSoftmaxNest(Value input,
|
||||||
Value accumulator,
|
Value accumulator,
|
||||||
RankedTensorType inputType,
|
RankedTensorType inputType,
|
||||||
int64_t axis,
|
int64_t axis,
|
||||||
SmallVectorImpl<Value>& outerIndices,
|
SmallVectorImpl<Value>& outerIndices,
|
||||||
ConversionPatternRewriter& rewriter,
|
ConversionPatternRewriter& rewriter,
|
||||||
Location loc) {
|
Location loc) {
|
||||||
if (axis == inputType.getRank() - 1)
|
if (axis == inputType.getRank() - 1)
|
||||||
return buildLoopSoftmaxSlice(input, accumulator, inputType, outerIndices, rewriter, loc);
|
return buildLoopSoftmaxSlice(input, accumulator, inputType, outerIndices, rewriter, loc);
|
||||||
|
|
||||||
Value c0 = arith::ConstantIndexOp::create(rewriter, loc, 0);
|
Operation* anchorOp = rewriter.getInsertionBlock()->getParentOp();
|
||||||
Value c1 = arith::ConstantIndexOp::create(rewriter, loc, 1);
|
Value c0 = getOrCreateIndexConstant(rewriter, anchorOp, 0);
|
||||||
Value cUpper = arith::ConstantIndexOp::create(rewriter, loc, inputType.getDimSize(axis));
|
Value c1 = getOrCreateIndexConstant(rewriter, anchorOp, 1);
|
||||||
|
Value cUpper = getOrCreateIndexConstant(rewriter, anchorOp, inputType.getDimSize(axis));
|
||||||
|
|
||||||
auto loop = scf::ForOp::create(rewriter, loc, c0, cUpper, c1, ValueRange {accumulator});
|
auto loop = buildNormalizedScfFor(
|
||||||
rewriter.setInsertionPointToStart(loop.getBody());
|
rewriter,
|
||||||
|
loc,
|
||||||
Value loopIndex = loop.getInductionVar();
|
c0,
|
||||||
Value loopAccumulator = loop.getRegionIterArgs().front();
|
cUpper,
|
||||||
outerIndices.push_back(loopIndex);
|
c1,
|
||||||
Value updatedAccumulator =
|
ValueRange {accumulator},
|
||||||
buildLoopSoftmaxNest(input, loopAccumulator, inputType, axis + 1, outerIndices, rewriter, loc);
|
[&](OpBuilder& builder, Location nestedLoc, Value loopIndex, ValueRange iterArgs, SmallVectorImpl<Value>& yielded) {
|
||||||
outerIndices.pop_back();
|
outerIndices.push_back(loopIndex);
|
||||||
|
auto updatedAccumulator =
|
||||||
scf::YieldOp::create(rewriter, loc, updatedAccumulator);
|
buildLoopSoftmaxNest(input, iterArgs.front(), inputType, axis + 1, outerIndices, rewriter, nestedLoc);
|
||||||
rewriter.setInsertionPointAfter(loop);
|
outerIndices.pop_back();
|
||||||
return loop.getResult(0);
|
if (failed(updatedAccumulator))
|
||||||
|
return failure();
|
||||||
|
yielded.push_back(*updatedAccumulator);
|
||||||
|
return success();
|
||||||
|
});
|
||||||
|
if (failed(loop))
|
||||||
|
return failure();
|
||||||
|
return loop->results.front();
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value createLoopSoftmaxCompute(Value input, ConversionPatternRewriter& rewriter, Location loc) {
|
static FailureOr<Value> createLoopSoftmaxCompute(Value input, ConversionPatternRewriter& rewriter, Location loc) {
|
||||||
auto inputType = cast<RankedTensorType>(input.getType());
|
auto inputType = cast<RankedTensorType>(input.getType());
|
||||||
constexpr size_t numInputs = 1;
|
constexpr size_t numInputs = 1;
|
||||||
auto computeOp =
|
auto computeOp = createSpatCompute<numInputs>(
|
||||||
createSpatCompute<numInputs>(rewriter, loc, TypeRange {inputType}, {}, ValueRange {input}, [&](Value x) {
|
rewriter, loc, TypeRange {inputType}, {}, ValueRange {input}, [&](Value x) -> LogicalResult {
|
||||||
if (inputType.getRank() == 1) {
|
if (inputType.getRank() == 1) {
|
||||||
Value softmax = spatial::SpatSoftmaxOp::create(rewriter, loc, inputType, x).getResult();
|
Value softmax = spatial::SpatSoftmaxOp::create(rewriter, loc, inputType, x).getResult();
|
||||||
spatial::SpatYieldOp::create(rewriter, loc, softmax);
|
spatial::SpatYieldOp::create(rewriter, loc, softmax);
|
||||||
return;
|
return success();
|
||||||
}
|
}
|
||||||
|
|
||||||
Value outputInit = tensor::EmptyOp::create(rewriter, loc, inputType.getShape(), inputType.getElementType());
|
Value outputInit = tensor::EmptyOp::create(rewriter, loc, inputType.getShape(), inputType.getElementType());
|
||||||
SmallVector<Value> outerIndices;
|
SmallVector<Value> outerIndices;
|
||||||
Value result = buildLoopSoftmaxNest(x, outputInit, inputType, /*axis=*/0, outerIndices, rewriter, loc);
|
auto result = buildLoopSoftmaxNest(x, outputInit, inputType, /*axis=*/0, outerIndices, rewriter, loc);
|
||||||
spatial::SpatYieldOp::create(rewriter, loc, result);
|
if (failed(result))
|
||||||
|
return failure();
|
||||||
|
spatial::SpatYieldOp::create(rewriter, loc, *result);
|
||||||
|
return success();
|
||||||
});
|
});
|
||||||
return computeOp.getResult(0);
|
if (failed(computeOp))
|
||||||
|
return failure();
|
||||||
|
return computeOp->getResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SoftmaxToSpatialCompute : OpConversionPattern<ONNXSoftmaxOp> {
|
struct SoftmaxToSpatialCompute : OpConversionPattern<ONNXSoftmaxOp> {
|
||||||
@@ -110,44 +114,40 @@ struct SoftmaxToSpatialCompute : OpConversionPattern<ONNXSoftmaxOp> {
|
|||||||
if (!inputType || !inputType.hasStaticShape())
|
if (!inputType || !inputType.hasStaticShape())
|
||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
int64_t axis = normalizeAxis(softmaxOp.getAxis(), inputType.getRank());
|
auto axis = normalizeAxisChecked(softmaxOp.getAxis(), inputType.getRank());
|
||||||
if (axis < 0 || axis >= inputType.getRank())
|
if (failed(axis))
|
||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
Value input = adaptor.getInput();
|
Value input = adaptor.getInput();
|
||||||
Value result;
|
Value result;
|
||||||
if (axis == inputType.getRank() - 1) {
|
if (*axis == inputType.getRank() - 1) {
|
||||||
result = createLoopSoftmaxCompute(input, rewriter, softmaxOp.getLoc());
|
auto computed = createLoopSoftmaxCompute(input, rewriter, softmaxOp.getLoc());
|
||||||
|
if (failed(computed))
|
||||||
|
return failure();
|
||||||
|
result = *computed;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
SmallVector<int64_t> permutation;
|
SmallVector<int64_t> permutation;
|
||||||
permutation.reserve(inputType.getRank());
|
permutation.reserve(inputType.getRank());
|
||||||
for (int64_t dim = 0; dim < inputType.getRank(); ++dim)
|
for (int64_t dim = 0; dim < inputType.getRank(); ++dim)
|
||||||
if (dim != axis)
|
if (dim != *axis)
|
||||||
permutation.push_back(dim);
|
permutation.push_back(dim);
|
||||||
permutation.push_back(axis);
|
permutation.push_back(*axis);
|
||||||
|
SmallVector<int64_t> inversePermutation = invertPermutation(permutation);
|
||||||
SmallVector<int64_t> inversePermutation(inputType.getRank());
|
|
||||||
for (auto [newIndex, oldIndex] : llvm::enumerate(permutation))
|
|
||||||
inversePermutation[oldIndex] = static_cast<int64_t>(newIndex);
|
|
||||||
|
|
||||||
auto transposedType = RankedTensorType::get(
|
auto transposedType = RankedTensorType::get(
|
||||||
permuteShape(inputType.getShape(), permutation), inputType.getElementType(), inputType.getEncoding());
|
permuteShape(inputType.getShape(), permutation), inputType.getElementType(), inputType.getEncoding());
|
||||||
auto preTransposeCompute =
|
Value transposedInput =
|
||||||
createSpatCompute<1>(rewriter, softmaxOp.getLoc(), TypeRange {transposedType}, {}, input, [&](Value x) {
|
ONNXTransposeOp::create(
|
||||||
Value transposed = ONNXTransposeOp::create(
|
rewriter, softmaxOp.getLoc(), transposedType, input, rewriter.getI64ArrayAttr(permutation))
|
||||||
rewriter, softmaxOp.getLoc(), transposedType, x, rewriter.getI64ArrayAttr(permutation));
|
.getResult();
|
||||||
spatial::SpatYieldOp::create(rewriter, softmaxOp.getLoc(), transposed);
|
auto transposedResult = createLoopSoftmaxCompute(transposedInput, rewriter, softmaxOp.getLoc());
|
||||||
});
|
if (failed(transposedResult))
|
||||||
Value transposedInput = preTransposeCompute.getResult(0);
|
return failure();
|
||||||
Value transposedResult = createLoopSoftmaxCompute(transposedInput, rewriter, softmaxOp.getLoc());
|
result =
|
||||||
auto postTransposeCompute =
|
ONNXTransposeOp::create(
|
||||||
createSpatCompute<1>(rewriter, softmaxOp.getLoc(), TypeRange {inputType}, {}, transposedResult, [&](Value x) {
|
rewriter, softmaxOp.getLoc(), inputType, *transposedResult, rewriter.getI64ArrayAttr(inversePermutation))
|
||||||
Value transposed = ONNXTransposeOp::create(
|
.getResult();
|
||||||
rewriter, softmaxOp.getLoc(), inputType, x, rewriter.getI64ArrayAttr(inversePermutation));
|
|
||||||
spatial::SpatYieldOp::create(rewriter, softmaxOp.getLoc(), transposed);
|
|
||||||
});
|
|
||||||
result = postTransposeCompute.getResult(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rewriter.replaceOp(softmaxOp, result);
|
rewriter.replaceOp(softmaxOp, result);
|
||||||
|
|||||||
@@ -0,0 +1,292 @@
|
|||||||
|
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||||
|
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
||||||
|
#include "mlir/Dialect/Linalg/IR/Linalg.h"
|
||||||
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
|
#include "mlir/IR/IRMapping.h"
|
||||||
|
#include "mlir/IR/PatternMatch.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/STLExtras.h"
|
||||||
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
|
||||||
|
#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"
|
||||||
|
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||||
|
|
||||||
|
using namespace mlir;
|
||||||
|
|
||||||
|
namespace onnx_mlir {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
static bool isWeightMaterializationHelperUser(Operation* op) {
|
||||||
|
return isa<tensor::ExtractSliceOp, tensor::ExpandShapeOp, tensor::CollapseShapeOp, linalg::TransposeOp>(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool canPromoteInputBlockArgument(BlockArgument arg) {
|
||||||
|
return !arg.use_empty() && llvm::all_of(arg.getUsers(), isWeightMaterializationHelperUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool canPromoteInputBlockArgument(std::optional<BlockArgument> arg) {
|
||||||
|
return arg && canPromoteInputBlockArgument(*arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isDirectConstantValue(Value value) {
|
||||||
|
return isa_and_nonnull<arith::ConstantOp, ONNXConstantOp>(value.getDefiningOp());
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PromotedOperands {
|
||||||
|
SmallVector<bool> promoteInput;
|
||||||
|
SmallVector<Value> newWeights;
|
||||||
|
SmallVector<Value> newInputs;
|
||||||
|
SmallVector<Type> newInputTypes;
|
||||||
|
SmallVector<Location> newInputLocs;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename ComputeOpTy>
|
||||||
|
static bool hasPromotableWeightLikeInputs(ComputeOpTy compute) {
|
||||||
|
for (auto [inputIdx, input] : llvm::enumerate(compute.getInputs())) {
|
||||||
|
if (!isWeightLikeComputeOperand(input))
|
||||||
|
continue;
|
||||||
|
if (isDirectConstantValue(input) && !canPromoteInputBlockArgument(compute.getInputArgument(inputIdx)))
|
||||||
|
continue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ComputeOpTy>
|
||||||
|
static FailureOr<PromotedOperands> computePromotedOperands(ComputeOpTy compute) {
|
||||||
|
PromotedOperands promoted;
|
||||||
|
promoted.promoteInput.assign(compute.getInputs().size(), false);
|
||||||
|
promoted.newWeights.append(compute.getWeights().begin(), compute.getWeights().end());
|
||||||
|
promoted.newWeights.reserve(compute.getWeights().size() + compute.getInputs().size());
|
||||||
|
promoted.newInputs.reserve(compute.getInputs().size());
|
||||||
|
promoted.newInputTypes.reserve(compute.getInputs().size());
|
||||||
|
promoted.newInputLocs.reserve(compute.getInputs().size());
|
||||||
|
|
||||||
|
bool needsRewrite = false;
|
||||||
|
for (auto [inputIdx, input] : llvm::enumerate(compute.getInputs())) {
|
||||||
|
if (!isWeightLikeComputeOperand(input))
|
||||||
|
goto keep_input;
|
||||||
|
if (isDirectConstantValue(input) && !canPromoteInputBlockArgument(compute.getInputArgument(inputIdx)))
|
||||||
|
goto keep_input;
|
||||||
|
promoted.promoteInput[inputIdx] = true;
|
||||||
|
promoted.newWeights.push_back(input);
|
||||||
|
needsRewrite = true;
|
||||||
|
continue;
|
||||||
|
|
||||||
|
keep_input:
|
||||||
|
promoted.newInputs.push_back(input);
|
||||||
|
promoted.newInputTypes.push_back(input.getType());
|
||||||
|
promoted.newInputLocs.push_back(input.getLoc());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!needsRewrite)
|
||||||
|
return failure();
|
||||||
|
return promoted;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ComputeOpTy>
|
||||||
|
static LogicalResult mapPromotedInputArguments(ComputeOpTy compute,
|
||||||
|
const PromotedOperands& promoted,
|
||||||
|
IRRewriter& bodyRewriter,
|
||||||
|
IRMapping& mapper,
|
||||||
|
std::function<std::optional<BlockArgument>(size_t)> getNewInputArg,
|
||||||
|
PatternRewriter& rewriter) {
|
||||||
|
size_t newInputIdx = 0;
|
||||||
|
for (auto [oldInputIdx, input] : llvm::enumerate(compute.getInputs())) {
|
||||||
|
auto oldArg = compute.getInputArgument(oldInputIdx);
|
||||||
|
if (!oldArg)
|
||||||
|
return rewriter.notifyMatchFailure(compute, "missing input block argument during rewrite");
|
||||||
|
if (!promoted.promoteInput[oldInputIdx]) {
|
||||||
|
auto newInputArg = getNewInputArg(newInputIdx++);
|
||||||
|
if (!newInputArg)
|
||||||
|
return rewriter.notifyMatchFailure(compute, "missing rewritten input block argument");
|
||||||
|
mapper.map(*oldArg, *newInputArg);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto clonedValue = materializeWeightLikeValueInBlock(input, bodyRewriter, mapper);
|
||||||
|
if (failed(clonedValue))
|
||||||
|
return rewriter.notifyMatchFailure(compute, "failed to materialize promoted weight-like operand");
|
||||||
|
mapper.map(*oldArg, *clonedValue);
|
||||||
|
}
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Promotes foldable helper chains from runtime inputs to weights to avoid artificial compute inputs.
|
||||||
|
struct PromoteWeightLikeComputeInputsPattern : OpRewritePattern<spatial::SpatGraphCompute> {
|
||||||
|
using OpRewritePattern<spatial::SpatGraphCompute>::OpRewritePattern;
|
||||||
|
|
||||||
|
LogicalResult matchAndRewrite(spatial::SpatGraphCompute compute, PatternRewriter& rewriter) const override {
|
||||||
|
auto promoted = computePromotedOperands(compute);
|
||||||
|
if (failed(promoted))
|
||||||
|
return rewriter.notifyMatchFailure(compute, "no weight-like inputs to promote");
|
||||||
|
Block& oldBlock = compute.getBody().front();
|
||||||
|
|
||||||
|
rewriter.setInsertionPointAfter(compute);
|
||||||
|
SmallVector<Type> newBlockArgTypes;
|
||||||
|
SmallVector<Location> newBlockArgLocs;
|
||||||
|
for (Value weight : promoted->newWeights) {
|
||||||
|
newBlockArgTypes.push_back(weight.getType());
|
||||||
|
newBlockArgLocs.push_back(weight.getLoc());
|
||||||
|
}
|
||||||
|
llvm::append_range(newBlockArgTypes, promoted->newInputTypes);
|
||||||
|
llvm::append_range(newBlockArgLocs, promoted->newInputLocs);
|
||||||
|
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());
|
||||||
|
bodyRewriter.setInsertionPointToStart(newBlock);
|
||||||
|
|
||||||
|
IRMapping mapper;
|
||||||
|
for (auto [weightIndex, weight] : llvm::enumerate(compute.getWeights())) {
|
||||||
|
auto oldWeightArg = compute.getWeightArgument(weightIndex);
|
||||||
|
auto newWeightArg = newCompute.getWeightArgument(weightIndex);
|
||||||
|
if (!oldWeightArg || !newWeightArg)
|
||||||
|
return rewriter.notifyMatchFailure(compute, "missing compute weight block argument during rewrite");
|
||||||
|
mapper.map(*oldWeightArg, *newWeightArg);
|
||||||
|
}
|
||||||
|
if (failed(mapPromotedInputArguments(
|
||||||
|
compute,
|
||||||
|
*promoted,
|
||||||
|
bodyRewriter,
|
||||||
|
mapper,
|
||||||
|
[&](size_t index) { return newCompute.getInputArgument(index); },
|
||||||
|
rewriter)))
|
||||||
|
return failure();
|
||||||
|
|
||||||
|
for (Operation& op : oldBlock.without_terminator())
|
||||||
|
rewriter.clone(op, mapper);
|
||||||
|
|
||||||
|
auto oldYield = cast<spatial::SpatYieldOp>(oldBlock.getTerminator());
|
||||||
|
SmallVector<Value> newYieldOperands;
|
||||||
|
newYieldOperands.reserve(oldYield.getOutputs().size());
|
||||||
|
for (Value operand : oldYield.getOutputs()) {
|
||||||
|
auto mapped = mapper.lookupOrNull(operand);
|
||||||
|
newYieldOperands.push_back(mapped ? cast<Value>(mapped) : operand);
|
||||||
|
}
|
||||||
|
spatial::SpatYieldOp::create(rewriter, oldYield.getLoc(), newYieldOperands);
|
||||||
|
|
||||||
|
rewriter.replaceOp(compute, newCompute.getResults());
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Promotes foldable batch helper chains to weights while preserving compact compute_batch IR.
|
||||||
|
struct PromoteWeightLikeComputeBatchInputsPattern : OpRewritePattern<spatial::SpatGraphComputeBatch> {
|
||||||
|
using OpRewritePattern<spatial::SpatGraphComputeBatch>::OpRewritePattern;
|
||||||
|
|
||||||
|
LogicalResult matchAndRewrite(spatial::SpatGraphComputeBatch compute, PatternRewriter& rewriter) const override {
|
||||||
|
auto promoted = computePromotedOperands(compute);
|
||||||
|
if (failed(promoted))
|
||||||
|
return rewriter.notifyMatchFailure(compute, "no weight-like batch inputs to promote");
|
||||||
|
Block& oldBlock = compute.getBody().front();
|
||||||
|
|
||||||
|
rewriter.setInsertionPointAfter(compute);
|
||||||
|
|
||||||
|
auto laneArg = compute.getLaneArgument();
|
||||||
|
if (!laneArg)
|
||||||
|
return rewriter.notifyMatchFailure(compute, "missing compute_batch lane block argument");
|
||||||
|
SmallVector<Type> newBlockArgTypes;
|
||||||
|
SmallVector<Location> newBlockArgLocs;
|
||||||
|
newBlockArgTypes.reserve(1 + promoted->newWeights.size() + promoted->newInputTypes.size()
|
||||||
|
+ compute.getNumResults());
|
||||||
|
newBlockArgLocs.reserve(1 + promoted->newWeights.size() + promoted->newInputLocs.size() + compute.getNumResults());
|
||||||
|
newBlockArgTypes.push_back(laneArg->getType());
|
||||||
|
newBlockArgLocs.push_back(laneArg->getLoc());
|
||||||
|
for (Value weight : promoted->newWeights) {
|
||||||
|
newBlockArgTypes.push_back(weight.getType());
|
||||||
|
newBlockArgLocs.push_back(weight.getLoc());
|
||||||
|
}
|
||||||
|
llvm::append_range(newBlockArgTypes, promoted->newInputTypes);
|
||||||
|
llvm::append_range(newBlockArgLocs, promoted->newInputLocs);
|
||||||
|
for (auto [resultIndex, resultType] : llvm::enumerate(compute.getResultTypes())) {
|
||||||
|
auto outputArg = compute.getOutputArgument(resultIndex);
|
||||||
|
if (!outputArg)
|
||||||
|
return rewriter.notifyMatchFailure(compute, "missing compute_batch output block argument");
|
||||||
|
newBlockArgTypes.push_back(resultType);
|
||||||
|
newBlockArgLocs.push_back(outputArg->getLoc());
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
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);
|
||||||
|
if (!oldWeightArg || !newWeightArg)
|
||||||
|
return rewriter.notifyMatchFailure(compute, "missing compute_batch weight block argument during rewrite");
|
||||||
|
mapper.map(*oldWeightArg, *newWeightArg);
|
||||||
|
}
|
||||||
|
if (failed(mapPromotedInputArguments(
|
||||||
|
compute,
|
||||||
|
*promoted,
|
||||||
|
bodyRewriter,
|
||||||
|
mapper,
|
||||||
|
[&](size_t index) { return (*newCompute).getInputArgument(index); },
|
||||||
|
rewriter)))
|
||||||
|
return failure();
|
||||||
|
for (auto resultIndex : llvm::seq<size_t>(0, compute.getNumResults())) {
|
||||||
|
auto outputArg = compute.getOutputArgument(resultIndex);
|
||||||
|
if (!outputArg)
|
||||||
|
return rewriter.notifyMatchFailure(compute, "missing compute_batch output block argument during rewrite");
|
||||||
|
mapper.map(*outputArg,
|
||||||
|
newBlock->getArgument(1 + promoted->newWeights.size() + promoted->newInputs.size() + resultIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Operation& op : oldBlock)
|
||||||
|
rewriter.clone(op, mapper);
|
||||||
|
|
||||||
|
rewriter.replaceOp(compute, (*newCompute).getResults());
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void populateWeightPromotionPatterns(RewritePatternSet& patterns, MLIRContext* ctx) {
|
||||||
|
patterns.add<PromoteWeightLikeComputeInputsPattern, PromoteWeightLikeComputeBatchInputsPattern>(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void annotateWeightsConstants(func::FuncOp funcOp) {
|
||||||
|
funcOp.walk([&](arith::ConstantOp constantOp) {
|
||||||
|
if (hasOnlySpatialMvmVmmWeightUses(constantOp.getResult()))
|
||||||
|
markWeightAlways(constantOp);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool requiresPostRewrite(spatial::SpatGraphCompute computeOp) { return hasPromotableWeightLikeInputs(computeOp); }
|
||||||
|
|
||||||
|
bool requiresPostRewrite(spatial::SpatGraphComputeBatch computeOp) { return hasPromotableWeightLikeInputs(computeOp); }
|
||||||
|
|
||||||
|
} // namespace onnx_mlir
|
||||||
+2
-3
@@ -1,6 +1,5 @@
|
|||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ConversionPatterns.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/PrePatterns.hpp"
|
|
||||||
|
|
||||||
using namespace mlir;
|
using namespace mlir;
|
||||||
|
|
||||||
@@ -12,7 +11,7 @@ namespace {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void populatePrePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx) {
|
void populateGeneratedPrePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx) {
|
||||||
patterns.add<onnxToArithConstant>(ctx);
|
patterns.add<onnxToArithConstant>(ctx);
|
||||||
patterns.add<convAddToConvWithBiasLeft>(ctx);
|
patterns.add<convAddToConvWithBiasLeft>(ctx);
|
||||||
patterns.add<convAddToConvWithBiasRight>(ctx);
|
patterns.add<convAddToConvWithBiasRight>(ctx);
|
||||||
@@ -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
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
#include "llvm/ADT/SmallVector.h"
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ConversionPatterns.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||||
|
|
||||||
@@ -15,24 +15,6 @@ using namespace mlir;
|
|||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
static int64_t normalizeAxis(int64_t axis, int64_t rank) { return axis >= 0 ? axis : rank + axis; }
|
|
||||||
|
|
||||||
static int64_t normalizeIndex(int64_t index, int64_t dimSize) { return index >= 0 ? index : dimSize + index; }
|
|
||||||
|
|
||||||
static Value
|
|
||||||
extractSliceAt(Value input, int64_t axis, int64_t offset, ConversionPatternRewriter& rewriter, Location loc) {
|
|
||||||
auto inputType = cast<RankedTensorType>(input.getType());
|
|
||||||
SmallVector<OpFoldResult> offsets(inputType.getRank(), rewriter.getIndexAttr(0));
|
|
||||||
SmallVector<OpFoldResult> sizes;
|
|
||||||
SmallVector<OpFoldResult> strides(inputType.getRank(), rewriter.getIndexAttr(1));
|
|
||||||
sizes.reserve(inputType.getRank());
|
|
||||||
for (int64_t dim : inputType.getShape())
|
|
||||||
sizes.push_back(rewriter.getIndexAttr(dim));
|
|
||||||
offsets[axis] = rewriter.getIndexAttr(offset);
|
|
||||||
sizes[axis] = rewriter.getIndexAttr(1);
|
|
||||||
return tensor::ExtractSliceOp::create(rewriter, loc, input, offsets, sizes, strides);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Value concatGatherSlices(Value data,
|
static Value concatGatherSlices(Value data,
|
||||||
int64_t axis,
|
int64_t axis,
|
||||||
ArrayRef<int64_t> indices,
|
ArrayRef<int64_t> indices,
|
||||||
@@ -45,7 +27,7 @@ static Value concatGatherSlices(Value data,
|
|||||||
int64_t normalizedIndex = normalizeIndex(index, axisDim);
|
int64_t normalizedIndex = normalizeIndex(index, axisDim);
|
||||||
if (normalizedIndex < 0 || normalizedIndex >= axisDim)
|
if (normalizedIndex < 0 || normalizedIndex >= axisDim)
|
||||||
return {};
|
return {};
|
||||||
slices.push_back(extractSliceAt(data, axis, normalizedIndex, rewriter, loc));
|
slices.push_back(extractAxisSlice(rewriter, loc, data, axis, normalizedIndex, /*size=*/1));
|
||||||
}
|
}
|
||||||
if (slices.empty())
|
if (slices.empty())
|
||||||
return {};
|
return {};
|
||||||
@@ -96,11 +78,11 @@ struct Gather : OpConversionPattern<ONNXGatherOp> {
|
|||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
int64_t rank = dataType.getRank();
|
int64_t rank = dataType.getRank();
|
||||||
int64_t axis = normalizeAxis(gatherOp.getAxis(), rank);
|
auto axis = normalizeAxisChecked(gatherOp.getAxis(), rank);
|
||||||
if (axis < 0 || axis >= rank)
|
if (failed(axis))
|
||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
int64_t axisDim = dataType.getShape()[axis];
|
int64_t axisDim = dataType.getShape()[*axis];
|
||||||
if (axisDim <= 0)
|
if (axisDim <= 0)
|
||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
@@ -116,7 +98,7 @@ struct Gather : OpConversionPattern<ONNXGatherOp> {
|
|||||||
[&](Value data) -> LogicalResult {
|
[&](Value data) -> LogicalResult {
|
||||||
Value result;
|
Value result;
|
||||||
if (indicesType.getRank() == 1) {
|
if (indicesType.getRank() == 1) {
|
||||||
result = concatGatherSlices(data, axis, flatIndices, axisDim, rewriter, loc);
|
result = concatGatherSlices(data, *axis, flatIndices, axisDim, rewriter, loc);
|
||||||
}
|
}
|
||||||
else if (indicesType.getRank() == 2) {
|
else if (indicesType.getRank() == 2) {
|
||||||
int64_t rowCount = indicesType.getShape()[0];
|
int64_t rowCount = indicesType.getShape()[0];
|
||||||
@@ -125,12 +107,13 @@ struct Gather : OpConversionPattern<ONNXGatherOp> {
|
|||||||
rows.reserve(rowCount);
|
rows.reserve(rowCount);
|
||||||
for (int64_t row = 0; row < rowCount; ++row) {
|
for (int64_t row = 0; row < rowCount; ++row) {
|
||||||
ArrayRef<int64_t> rowIndices(flatIndices.data() + row * rowWidth, rowWidth);
|
ArrayRef<int64_t> rowIndices(flatIndices.data() + row * rowWidth, rowWidth);
|
||||||
Value gatheredRow = concatGatherSlices(data, axis, rowIndices, axisDim, rewriter, loc);
|
Value gatheredRow =
|
||||||
|
concatGatherSlices(data, *axis, rowIndices, axisDim, rewriter, loc);
|
||||||
if (!gatheredRow)
|
if (!gatheredRow)
|
||||||
return failure();
|
return failure();
|
||||||
rows.push_back(addLeadingGatherDim(gatheredRow, axis, rewriter, loc));
|
rows.push_back(addLeadingGatherDim(gatheredRow, *axis, rewriter, loc));
|
||||||
}
|
}
|
||||||
result = createSpatConcat(rewriter, loc, /*axis=*/axis, rows);
|
result = createSpatConcat(rewriter, loc, /*axis=*/*axis, rows);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return failure();
|
return failure();
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ConversionPatterns.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||||
|
|
||||||
@@ -14,10 +14,6 @@ using namespace mlir;
|
|||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
static bool haveStaticPositiveShape(ArrayRef<int64_t> shape) {
|
|
||||||
return llvm::all_of(shape, [](int64_t dim) { return dim > 0; });
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool inferCollapseReassociation(ArrayRef<int64_t> sourceShape,
|
static bool inferCollapseReassociation(ArrayRef<int64_t> sourceShape,
|
||||||
ArrayRef<int64_t> resultShape,
|
ArrayRef<int64_t> resultShape,
|
||||||
SmallVector<ReassociationIndices>& reassociation) {
|
SmallVector<ReassociationIndices>& reassociation) {
|
||||||
@@ -106,7 +102,7 @@ struct Reshape : OpConversionPattern<ONNXReshapeOp> {
|
|||||||
auto resultType = dyn_cast<RankedTensorType>(reshapeOp.getReshaped().getType());
|
auto resultType = dyn_cast<RankedTensorType>(reshapeOp.getReshaped().getType());
|
||||||
if (!sourceType || !resultType || !sourceType.hasStaticShape() || !resultType.hasStaticShape())
|
if (!sourceType || !resultType || !sourceType.hasStaticShape() || !resultType.hasStaticShape())
|
||||||
return failure();
|
return failure();
|
||||||
if (!haveStaticPositiveShape(sourceType.getShape()) || !haveStaticPositiveShape(resultType.getShape()))
|
if (!hasStaticPositiveShape(sourceType) || !hasStaticPositiveShape(resultType))
|
||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
if (sourceType == resultType) {
|
if (sourceType == resultType) {
|
||||||
@@ -115,17 +111,9 @@ struct Reshape : OpConversionPattern<ONNXReshapeOp> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto replaceWithReshape = [&](auto buildReshape) -> LogicalResult {
|
auto replaceWithReshape = [&](auto buildReshape) -> LogicalResult {
|
||||||
if (isCompileTimeComputable(adaptor.getData())) {
|
Value reshaped =
|
||||||
rewriter.replaceOp(reshapeOp, buildReshape(adaptor.getData()));
|
materializeOrComputeUnary(adaptor.getData(), resultType, rewriter, reshapeOp.getLoc(), buildReshape);
|
||||||
return success();
|
rewriter.replaceOp(reshapeOp, reshaped);
|
||||||
}
|
|
||||||
|
|
||||||
auto computeOp = createSpatCompute<1>(
|
|
||||||
rewriter, reshapeOp.getLoc(), TypeRange {resultType}, {}, adaptor.getData(), [&](Value data) {
|
|
||||||
Value reshaped = buildReshape(data);
|
|
||||||
spatial::SpatYieldOp::create(rewriter, reshapeOp.getLoc(), reshaped);
|
|
||||||
});
|
|
||||||
rewriter.replaceOp(reshapeOp, computeOp.getResults());
|
|
||||||
return success();
|
return success();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,9 @@
|
|||||||
|
|
||||||
#include "llvm/ADT/STLExtras.h"
|
#include "llvm/ADT/STLExtras.h"
|
||||||
|
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ConversionPatterns.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||||
|
|
||||||
@@ -17,19 +18,20 @@ namespace {
|
|||||||
|
|
||||||
static Value buildNearestAsymmetricIndex(
|
static Value buildNearestAsymmetricIndex(
|
||||||
Value outputIndex, int64_t inputDim, int64_t outputDim, ConversionPatternRewriter& rewriter, Location loc) {
|
Value outputIndex, int64_t inputDim, int64_t outputDim, ConversionPatternRewriter& rewriter, Location loc) {
|
||||||
Value cInputDim = arith::ConstantIndexOp::create(rewriter, loc, inputDim);
|
Operation* anchorOp = rewriter.getInsertionBlock()->getParentOp();
|
||||||
Value cOutputDim = arith::ConstantIndexOp::create(rewriter, loc, outputDim);
|
Value cInputDim = getOrCreateIndexConstant(rewriter, anchorOp, inputDim);
|
||||||
Value cInputDimLast = arith::ConstantIndexOp::create(rewriter, loc, inputDim - 1);
|
Value cOutputDim = getOrCreateIndexConstant(rewriter, anchorOp, outputDim);
|
||||||
|
Value cInputDimLast = getOrCreateIndexConstant(rewriter, anchorOp, inputDim - 1);
|
||||||
Value scaledIndex = arith::MulIOp::create(rewriter, loc, outputIndex, cInputDim);
|
Value scaledIndex = arith::MulIOp::create(rewriter, loc, outputIndex, cInputDim);
|
||||||
Value inputIndex = arith::DivUIOp::create(rewriter, loc, scaledIndex, cOutputDim);
|
Value inputIndex = arith::DivUIOp::create(rewriter, loc, scaledIndex, cOutputDim);
|
||||||
return arith::MinUIOp::create(rewriter, loc, inputIndex, cInputDimLast);
|
return arith::MinUIOp::create(rewriter, loc, inputIndex, cInputDimLast);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value buildNearestResizeLoop(Value input,
|
static FailureOr<Value> buildNearestResizeLoop(Value input,
|
||||||
RankedTensorType inputType,
|
RankedTensorType inputType,
|
||||||
RankedTensorType resultType,
|
RankedTensorType resultType,
|
||||||
ConversionPatternRewriter& rewriter,
|
ConversionPatternRewriter& rewriter,
|
||||||
Location loc) {
|
Location loc) {
|
||||||
auto elemType = resultType.getElementType();
|
auto elemType = resultType.getElementType();
|
||||||
SmallVector<int64_t> unitShape(resultType.getRank(), 1);
|
SmallVector<int64_t> unitShape(resultType.getRank(), 1);
|
||||||
auto unitTensorType = RankedTensorType::get(unitShape, elemType);
|
auto unitTensorType = RankedTensorType::get(unitShape, elemType);
|
||||||
@@ -37,63 +39,104 @@ static Value buildNearestResizeLoop(Value input,
|
|||||||
SmallVector<OpFoldResult> unitSizes(resultType.getRank(), rewriter.getIndexAttr(1));
|
SmallVector<OpFoldResult> unitSizes(resultType.getRank(), rewriter.getIndexAttr(1));
|
||||||
SmallVector<OpFoldResult> unitStrides(resultType.getRank(), rewriter.getIndexAttr(1));
|
SmallVector<OpFoldResult> unitStrides(resultType.getRank(), rewriter.getIndexAttr(1));
|
||||||
|
|
||||||
Value c0 = arith::ConstantIndexOp::create(rewriter, loc, 0);
|
Operation* anchorOp = rewriter.getInsertionBlock()->getParentOp();
|
||||||
Value c1 = arith::ConstantIndexOp::create(rewriter, loc, 1);
|
Value c0 = getOrCreateIndexConstant(rewriter, anchorOp, 0);
|
||||||
Value cOutputN = arith::ConstantIndexOp::create(rewriter, loc, resultType.getDimSize(0));
|
Value c1 = getOrCreateIndexConstant(rewriter, anchorOp, 1);
|
||||||
Value cOutputC = arith::ConstantIndexOp::create(rewriter, loc, resultType.getDimSize(1));
|
Value cOutputN = getOrCreateIndexConstant(rewriter, anchorOp, resultType.getDimSize(0));
|
||||||
Value cOutputH = arith::ConstantIndexOp::create(rewriter, loc, resultType.getDimSize(2));
|
Value cOutputC = getOrCreateIndexConstant(rewriter, anchorOp, resultType.getDimSize(1));
|
||||||
Value cOutputW = arith::ConstantIndexOp::create(rewriter, loc, resultType.getDimSize(3));
|
Value cOutputH = getOrCreateIndexConstant(rewriter, anchorOp, resultType.getDimSize(2));
|
||||||
|
Value cOutputW = getOrCreateIndexConstant(rewriter, anchorOp, resultType.getDimSize(3));
|
||||||
|
|
||||||
Value outputInit = tensor::EmptyOp::create(rewriter, loc, resultType.getShape(), elemType);
|
Value outputInit = tensor::EmptyOp::create(rewriter, loc, resultType.getShape(), elemType);
|
||||||
|
|
||||||
auto batchLoop = scf::ForOp::create(rewriter, loc, c0, cOutputN, c1, ValueRange {outputInit});
|
auto batchLoop = buildNormalizedScfFor(
|
||||||
rewriter.setInsertionPointToStart(batchLoop.getBody());
|
rewriter,
|
||||||
|
loc,
|
||||||
|
c0,
|
||||||
|
cOutputN,
|
||||||
|
c1,
|
||||||
|
ValueRange {outputInit},
|
||||||
|
[&](OpBuilder&, Location nestedLoc, Value outputN, ValueRange batchIterArgs, SmallVectorImpl<Value>& batchYielded) {
|
||||||
|
Value outputBatchAcc = batchIterArgs.front();
|
||||||
|
Value inputN =
|
||||||
|
buildNearestAsymmetricIndex(outputN, inputType.getDimSize(0), resultType.getDimSize(0), rewriter, nestedLoc);
|
||||||
|
|
||||||
Value outputN = batchLoop.getInductionVar();
|
auto channelLoop = buildNormalizedScfFor(
|
||||||
Value outputBatchAcc = batchLoop.getRegionIterArgs().front();
|
rewriter,
|
||||||
Value inputN = buildNearestAsymmetricIndex(outputN, inputType.getDimSize(0), resultType.getDimSize(0), rewriter, loc);
|
nestedLoc,
|
||||||
|
c0,
|
||||||
|
cOutputC,
|
||||||
|
c1,
|
||||||
|
ValueRange {outputBatchAcc},
|
||||||
|
[&](OpBuilder&,
|
||||||
|
Location channelLoc,
|
||||||
|
Value outputC,
|
||||||
|
ValueRange channelIterArgs,
|
||||||
|
SmallVectorImpl<Value>& channelYielded) {
|
||||||
|
Value outputChannelAcc = channelIterArgs.front();
|
||||||
|
Value inputC = buildNearestAsymmetricIndex(
|
||||||
|
outputC, inputType.getDimSize(1), resultType.getDimSize(1), rewriter, channelLoc);
|
||||||
|
|
||||||
auto channelLoop = scf::ForOp::create(rewriter, loc, c0, cOutputC, c1, ValueRange {outputBatchAcc});
|
auto heightLoop = buildNormalizedScfFor(
|
||||||
rewriter.setInsertionPointToStart(channelLoop.getBody());
|
rewriter,
|
||||||
|
channelLoc,
|
||||||
|
c0,
|
||||||
|
cOutputH,
|
||||||
|
c1,
|
||||||
|
ValueRange {outputChannelAcc},
|
||||||
|
[&](OpBuilder&,
|
||||||
|
Location heightLoc,
|
||||||
|
Value outputH,
|
||||||
|
ValueRange heightIterArgs,
|
||||||
|
SmallVectorImpl<Value>& heightYielded) {
|
||||||
|
Value outputHeightAcc = heightIterArgs.front();
|
||||||
|
Value inputH = buildNearestAsymmetricIndex(
|
||||||
|
outputH, inputType.getDimSize(2), resultType.getDimSize(2), rewriter, heightLoc);
|
||||||
|
|
||||||
Value outputC = channelLoop.getInductionVar();
|
auto widthLoop = buildNormalizedScfFor(
|
||||||
Value outputChannelAcc = channelLoop.getRegionIterArgs().front();
|
rewriter,
|
||||||
Value inputC = buildNearestAsymmetricIndex(outputC, inputType.getDimSize(1), resultType.getDimSize(1), rewriter, loc);
|
heightLoc,
|
||||||
|
c0,
|
||||||
|
cOutputW,
|
||||||
|
c1,
|
||||||
|
ValueRange {outputHeightAcc},
|
||||||
|
[&](OpBuilder&,
|
||||||
|
Location widthLoc,
|
||||||
|
Value outputW,
|
||||||
|
ValueRange widthIterArgs,
|
||||||
|
SmallVectorImpl<Value>& widthYielded) {
|
||||||
|
Value outputWidthAcc = widthIterArgs.front();
|
||||||
|
Value inputW = buildNearestAsymmetricIndex(
|
||||||
|
outputW, inputType.getDimSize(3), resultType.getDimSize(3), rewriter, widthLoc);
|
||||||
|
|
||||||
auto heightLoop = scf::ForOp::create(rewriter, loc, c0, cOutputH, c1, ValueRange {outputChannelAcc});
|
SmallVector<OpFoldResult> inputOffsets = {inputN, inputC, inputH, inputW};
|
||||||
rewriter.setInsertionPointToStart(heightLoop.getBody());
|
Value inputSlice = tensor::ExtractSliceOp::create(
|
||||||
|
rewriter, widthLoc, unitTensorType, input, inputOffsets, unitSizes, unitStrides);
|
||||||
|
|
||||||
Value outputH = heightLoop.getInductionVar();
|
SmallVector<OpFoldResult> outputOffsets = {outputN, outputC, outputH, outputW};
|
||||||
Value outputHeightAcc = heightLoop.getRegionIterArgs().front();
|
Value updatedOutput = tensor::InsertSliceOp::create(
|
||||||
Value inputH = buildNearestAsymmetricIndex(outputH, inputType.getDimSize(2), resultType.getDimSize(2), rewriter, loc);
|
rewriter, widthLoc, inputSlice, outputWidthAcc, outputOffsets, unitSizes, unitStrides);
|
||||||
|
widthYielded.push_back(updatedOutput);
|
||||||
auto widthLoop = scf::ForOp::create(rewriter, loc, c0, cOutputW, c1, ValueRange {outputHeightAcc});
|
return success();
|
||||||
rewriter.setInsertionPointToStart(widthLoop.getBody());
|
});
|
||||||
|
if (failed(widthLoop))
|
||||||
Value outputW = widthLoop.getInductionVar();
|
return failure();
|
||||||
Value outputWidthAcc = widthLoop.getRegionIterArgs().front();
|
heightYielded.push_back(widthLoop->results.front());
|
||||||
Value inputW = buildNearestAsymmetricIndex(outputW, inputType.getDimSize(3), resultType.getDimSize(3), rewriter, loc);
|
return success();
|
||||||
|
});
|
||||||
SmallVector<OpFoldResult> inputOffsets = {inputN, inputC, inputH, inputW};
|
if (failed(heightLoop))
|
||||||
Value inputSlice =
|
return failure();
|
||||||
tensor::ExtractSliceOp::create(rewriter, loc, unitTensorType, input, inputOffsets, unitSizes, unitStrides);
|
channelYielded.push_back(heightLoop->results.front());
|
||||||
|
return success();
|
||||||
SmallVector<OpFoldResult> outputOffsets = {outputN, outputC, outputH, outputW};
|
});
|
||||||
Value updatedOutput =
|
if (failed(channelLoop))
|
||||||
tensor::InsertSliceOp::create(rewriter, loc, inputSlice, outputWidthAcc, outputOffsets, unitSizes, unitStrides);
|
return failure();
|
||||||
scf::YieldOp::create(rewriter, loc, updatedOutput);
|
batchYielded.push_back(channelLoop->results.front());
|
||||||
|
return success();
|
||||||
rewriter.setInsertionPointAfter(widthLoop);
|
});
|
||||||
scf::YieldOp::create(rewriter, loc, widthLoop.getResult(0));
|
if (failed(batchLoop))
|
||||||
|
return failure();
|
||||||
rewriter.setInsertionPointAfter(heightLoop);
|
return batchLoop->results.front();
|
||||||
scf::YieldOp::create(rewriter, loc, heightLoop.getResult(0));
|
|
||||||
|
|
||||||
rewriter.setInsertionPointAfter(channelLoop);
|
|
||||||
scf::YieldOp::create(rewriter, loc, channelLoop.getResult(0));
|
|
||||||
|
|
||||||
rewriter.setInsertionPointAfter(batchLoop);
|
|
||||||
return batchLoop.getResult(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Resize : OpConversionPattern<ONNXResizeOp> {
|
struct Resize : OpConversionPattern<ONNXResizeOp> {
|
||||||
@@ -118,12 +161,17 @@ struct Resize : OpConversionPattern<ONNXResizeOp> {
|
|||||||
|| llvm::any_of(resultType.getShape(), [](int64_t dim) { return dim <= 0; }))
|
|| llvm::any_of(resultType.getShape(), [](int64_t dim) { return dim <= 0; }))
|
||||||
return rewriter.notifyMatchFailure(resizeOp, "resize lowering requires positive static dimensions.");
|
return rewriter.notifyMatchFailure(resizeOp, "resize lowering requires positive static dimensions.");
|
||||||
|
|
||||||
auto computeOp =
|
auto computeOp = createSpatCompute<1>(
|
||||||
createSpatCompute<1>(rewriter, resizeOp.getLoc(), TypeRange {resultType}, {}, adaptor.getX(), [&](Value x) {
|
rewriter, resizeOp.getLoc(), TypeRange {resultType}, {}, adaptor.getX(), [&](Value x) -> LogicalResult {
|
||||||
Value result = buildNearestResizeLoop(x, inputType, resultType, rewriter, resizeOp.getLoc());
|
auto result = buildNearestResizeLoop(x, inputType, resultType, rewriter, resizeOp.getLoc());
|
||||||
spatial::SpatYieldOp::create(rewriter, resizeOp.getLoc(), result);
|
if (failed(result))
|
||||||
|
return failure();
|
||||||
|
spatial::SpatYieldOp::create(rewriter, resizeOp.getLoc(), *result);
|
||||||
|
return success();
|
||||||
});
|
});
|
||||||
rewriter.replaceOp(resizeOp, computeOp.getResults());
|
if (failed(computeOp))
|
||||||
|
return failure();
|
||||||
|
rewriter.replaceOp(resizeOp, computeOp->getResults());
|
||||||
return success();
|
return success();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,189 @@
|
|||||||
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
|
#include "mlir/IR/BuiltinAttributes.h"
|
||||||
|
#include "mlir/Transforms/DialectConversion.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
|
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||||
|
|
||||||
|
using namespace mlir;
|
||||||
|
|
||||||
|
namespace onnx_mlir {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
static FailureOr<SmallVector<int64_t>> getConstantIntValues(Value value) {
|
||||||
|
auto denseAttr = dyn_cast_or_null<DenseIntElementsAttr>(getHostConstDenseElementsAttr(value));
|
||||||
|
if (!denseAttr)
|
||||||
|
return failure();
|
||||||
|
return SmallVector<int64_t>(denseAttr.getValues<int64_t>().begin(), denseAttr.getValues<int64_t>().end());
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isNoneValueLike(Value value) { return isa_and_nonnull<ONNXNoneOp>(value.getDefiningOp()); }
|
||||||
|
|
||||||
|
static FailureOr<Value> buildSlice(Value data,
|
||||||
|
RankedTensorType dataType,
|
||||||
|
RankedTensorType resultType,
|
||||||
|
ArrayRef<int64_t> starts,
|
||||||
|
ArrayRef<int64_t> ends,
|
||||||
|
std::optional<ArrayRef<int64_t>> axes,
|
||||||
|
std::optional<ArrayRef<int64_t>> steps,
|
||||||
|
ConversionPatternRewriter& rewriter,
|
||||||
|
Location loc) {
|
||||||
|
int64_t rank = dataType.getRank();
|
||||||
|
if (!dataType.hasStaticShape() || !resultType.hasStaticShape() || resultType.getRank() != rank)
|
||||||
|
return failure();
|
||||||
|
|
||||||
|
if (starts.size() != ends.size())
|
||||||
|
return failure();
|
||||||
|
if (axes && axes->size() != starts.size())
|
||||||
|
return failure();
|
||||||
|
if (steps && steps->size() != starts.size())
|
||||||
|
return failure();
|
||||||
|
|
||||||
|
SmallVector<int64_t> normalizedAxes;
|
||||||
|
if (axes) {
|
||||||
|
SmallVector<bool> seenAxes(rank, false);
|
||||||
|
normalizedAxes.reserve(axes->size());
|
||||||
|
for (int64_t axis : *axes) {
|
||||||
|
auto normalizedAxis = normalizeAxisChecked(axis, rank);
|
||||||
|
if (failed(normalizedAxis))
|
||||||
|
return failure();
|
||||||
|
if (seenAxes[*normalizedAxis])
|
||||||
|
return failure();
|
||||||
|
seenAxes[*normalizedAxis] = true;
|
||||||
|
normalizedAxes.push_back(*normalizedAxis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (starts.size() > static_cast<size_t>(rank))
|
||||||
|
return failure();
|
||||||
|
normalizedAxes.reserve(starts.size());
|
||||||
|
for (size_t i = 0; i < starts.size(); ++i)
|
||||||
|
normalizedAxes.push_back(static_cast<int64_t>(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
SmallVector<int64_t> normalizedSteps;
|
||||||
|
if (steps)
|
||||||
|
normalizedSteps.assign(steps->begin(), steps->end());
|
||||||
|
else
|
||||||
|
normalizedSteps.assign(starts.size(), 1);
|
||||||
|
|
||||||
|
SmallVector<int64_t> computedShape(dataType.getShape().begin(), dataType.getShape().end());
|
||||||
|
SmallVector<OpFoldResult> offsets = getZeroOffsets(rewriter, rank);
|
||||||
|
SmallVector<OpFoldResult> sizes = getStaticSizes(rewriter, dataType.getShape());
|
||||||
|
SmallVector<OpFoldResult> strides = getUnitStrides(rewriter, rank);
|
||||||
|
|
||||||
|
for (auto [sliceIndex, axis] : llvm::enumerate(normalizedAxes)) {
|
||||||
|
int64_t step = normalizedSteps[sliceIndex];
|
||||||
|
if (step <= 0)
|
||||||
|
return failure();
|
||||||
|
|
||||||
|
int64_t dimSize = dataType.getShape()[axis];
|
||||||
|
int64_t start = starts[sliceIndex];
|
||||||
|
int64_t end = ends[sliceIndex];
|
||||||
|
|
||||||
|
start = normalizeIndex(start, dimSize);
|
||||||
|
end = normalizeIndex(end, dimSize);
|
||||||
|
|
||||||
|
start = std::clamp(start, int64_t {0}, dimSize);
|
||||||
|
end = std::clamp(end, int64_t {0}, dimSize);
|
||||||
|
|
||||||
|
int64_t extent = std::max(end - start, int64_t {0});
|
||||||
|
int64_t size = (extent + step - 1) / step;
|
||||||
|
|
||||||
|
offsets[axis] = rewriter.getIndexAttr(start);
|
||||||
|
sizes[axis] = rewriter.getIndexAttr(size);
|
||||||
|
strides[axis] = rewriter.getIndexAttr(step);
|
||||||
|
computedShape[axis] = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (llvm::ArrayRef(computedShape) != resultType.getShape())
|
||||||
|
return failure();
|
||||||
|
|
||||||
|
return tensor::ExtractSliceOp::create(rewriter, loc, resultType, data, offsets, sizes, strides).getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Slice final : OpConversionPattern<ONNXSliceOp> {
|
||||||
|
using OpConversionPattern::OpConversionPattern;
|
||||||
|
|
||||||
|
LogicalResult matchAndRewrite(ONNXSliceOp sliceOp,
|
||||||
|
ONNXSliceOpAdaptor adaptor,
|
||||||
|
ConversionPatternRewriter& rewriter) const override {
|
||||||
|
auto dataType = dyn_cast<RankedTensorType>(adaptor.getData().getType());
|
||||||
|
auto resultType = dyn_cast<RankedTensorType>(sliceOp.getResult().getType());
|
||||||
|
if (!dataType || !resultType || !dataType.hasStaticShape() || !resultType.hasStaticShape())
|
||||||
|
return failure();
|
||||||
|
|
||||||
|
auto starts = getConstantIntValues(adaptor.getStarts());
|
||||||
|
auto ends = getConstantIntValues(adaptor.getEnds());
|
||||||
|
if (failed(starts))
|
||||||
|
return rewriter.notifyMatchFailure(sliceOp, "requires compile-time constant starts");
|
||||||
|
if (failed(ends))
|
||||||
|
return rewriter.notifyMatchFailure(sliceOp, "requires compile-time constant ends");
|
||||||
|
|
||||||
|
std::optional<SmallVector<int64_t>> axes;
|
||||||
|
if (!isNoneValueLike(adaptor.getAxes())) {
|
||||||
|
auto parsedAxes = getConstantIntValues(adaptor.getAxes());
|
||||||
|
if (failed(parsedAxes))
|
||||||
|
return rewriter.notifyMatchFailure(sliceOp, "requires compile-time constant axes when present");
|
||||||
|
axes = std::move(*parsedAxes);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<SmallVector<int64_t>> steps;
|
||||||
|
if (!isNoneValueLike(adaptor.getSteps())) {
|
||||||
|
auto parsedSteps = getConstantIntValues(adaptor.getSteps());
|
||||||
|
if (failed(parsedSteps))
|
||||||
|
return rewriter.notifyMatchFailure(sliceOp, "requires compile-time constant steps when present");
|
||||||
|
steps = std::move(*parsedSteps);
|
||||||
|
if (llvm::any_of(*steps, [](int64_t step) { return step <= 0; }))
|
||||||
|
return rewriter.notifyMatchFailure(sliceOp, "supports only positive constant steps");
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayRef<int64_t> startsRef = *starts;
|
||||||
|
ArrayRef<int64_t> endsRef = *ends;
|
||||||
|
std::optional<ArrayRef<int64_t>> axesRef = axes ? std::optional<ArrayRef<int64_t>>(ArrayRef<int64_t>(*axes))
|
||||||
|
: std::nullopt;
|
||||||
|
std::optional<ArrayRef<int64_t>> stepsRef = steps ? std::optional<ArrayRef<int64_t>>(ArrayRef<int64_t>(*steps))
|
||||||
|
: std::nullopt;
|
||||||
|
|
||||||
|
Location loc = sliceOp.getLoc();
|
||||||
|
auto tryBuildSlice = [&](Value data) {
|
||||||
|
return buildSlice(data, dataType, resultType, startsRef, endsRef, axesRef, stepsRef, rewriter, loc);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isCompileTimeComputable(adaptor.getData())) {
|
||||||
|
auto sliced = tryBuildSlice(adaptor.getData());
|
||||||
|
if (failed(sliced))
|
||||||
|
return rewriter.notifyMatchFailure(sliceOp, "failed to normalize static slice parameters");
|
||||||
|
rewriter.replaceOp(sliceOp, *sliced);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto computeOp =
|
||||||
|
createSpatCompute<1>(rewriter, loc, TypeRange {resultType}, {}, adaptor.getData(), [&](Value data) {
|
||||||
|
auto sliced = tryBuildSlice(data);
|
||||||
|
if (failed(sliced))
|
||||||
|
return failure();
|
||||||
|
spatial::SpatYieldOp::create(rewriter, loc, *sliced);
|
||||||
|
return success();
|
||||||
|
});
|
||||||
|
if (failed(computeOp))
|
||||||
|
return rewriter.notifyMatchFailure(sliceOp, "failed to build runtime tensor.extract_slice lowering");
|
||||||
|
|
||||||
|
rewriter.replaceOp(sliceOp, computeOp->getResults());
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void populateSlicePatterns(RewritePatternSet& patterns, MLIRContext* ctx) { patterns.add<Slice>(ctx); }
|
||||||
|
|
||||||
|
} // namespace onnx_mlir
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ConversionPatterns.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||||
|
|
||||||
@@ -12,25 +12,6 @@ using namespace mlir;
|
|||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
static int64_t normalizeAxis(int64_t axis, int64_t rank) { return axis >= 0 ? axis : rank + axis; }
|
|
||||||
|
|
||||||
static Value extractSliceAt(
|
|
||||||
Value input, int64_t axis, int64_t offset, int64_t size, ConversionPatternRewriter& rewriter, Location loc) {
|
|
||||||
auto inputType = cast<RankedTensorType>(input.getType());
|
|
||||||
SmallVector<OpFoldResult> offsets(inputType.getRank(), rewriter.getIndexAttr(0));
|
|
||||||
SmallVector<OpFoldResult> sizes;
|
|
||||||
SmallVector<OpFoldResult> strides(inputType.getRank(), rewriter.getIndexAttr(1));
|
|
||||||
sizes.reserve(inputType.getRank());
|
|
||||||
for (int64_t dim : inputType.getShape())
|
|
||||||
sizes.push_back(rewriter.getIndexAttr(dim));
|
|
||||||
offsets[axis] = rewriter.getIndexAttr(offset);
|
|
||||||
sizes[axis] = rewriter.getIndexAttr(size);
|
|
||||||
SmallVector<int64_t> resultShape(inputType.getShape());
|
|
||||||
resultShape[axis] = size;
|
|
||||||
auto resultType = RankedTensorType::get(resultShape, inputType.getElementType());
|
|
||||||
return tensor::ExtractSliceOp::create(rewriter, loc, resultType, input, offsets, sizes, strides);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Split : OpConversionPattern<ONNXSplitOp> {
|
struct Split : OpConversionPattern<ONNXSplitOp> {
|
||||||
using OpConversionPattern::OpConversionPattern;
|
using OpConversionPattern::OpConversionPattern;
|
||||||
|
|
||||||
@@ -41,8 +22,8 @@ struct Split : OpConversionPattern<ONNXSplitOp> {
|
|||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
int64_t rank = inputType.getRank();
|
int64_t rank = inputType.getRank();
|
||||||
int64_t axis = normalizeAxis(splitOp.getAxis(), rank);
|
auto axis = normalizeAxisChecked(splitOp.getAxis(), rank);
|
||||||
if (axis < 0 || axis >= rank)
|
if (failed(axis))
|
||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
SmallVector<Value> outputs;
|
SmallVector<Value> outputs;
|
||||||
@@ -58,12 +39,12 @@ struct Split : OpConversionPattern<ONNXSplitOp> {
|
|||||||
if (!resultType || !resultType.hasStaticShape())
|
if (!resultType || !resultType.hasStaticShape())
|
||||||
return failure();
|
return failure();
|
||||||
resultTypes.push_back(resultType);
|
resultTypes.push_back(resultType);
|
||||||
sliceSizes.push_back(resultType.getShape()[axis]);
|
sliceSizes.push_back(resultType.getShape()[*axis]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCompileTimeComputable(adaptor.getInput())) {
|
if (isCompileTimeComputable(adaptor.getInput())) {
|
||||||
for (int64_t sliceSize : sliceSizes) {
|
for (int64_t sliceSize : sliceSizes) {
|
||||||
outputs.push_back(extractSliceAt(adaptor.getInput(), axis, offset, sliceSize, rewriter, splitOp.getLoc()));
|
outputs.push_back(extractAxisSlice(rewriter, splitOp.getLoc(), adaptor.getInput(), *axis, offset, sliceSize));
|
||||||
offset += sliceSize;
|
offset += sliceSize;
|
||||||
}
|
}
|
||||||
rewriter.replaceOp(splitOp, outputs);
|
rewriter.replaceOp(splitOp, outputs);
|
||||||
@@ -76,7 +57,8 @@ struct Split : OpConversionPattern<ONNXSplitOp> {
|
|||||||
runtimeOutputs.reserve(resultTypes.size());
|
runtimeOutputs.reserve(resultTypes.size());
|
||||||
int64_t runtimeOffset = 0;
|
int64_t runtimeOffset = 0;
|
||||||
for (int64_t sliceSize : sliceSizes) {
|
for (int64_t sliceSize : sliceSizes) {
|
||||||
runtimeOutputs.push_back(extractSliceAt(input, axis, runtimeOffset, sliceSize, rewriter, splitOp.getLoc()));
|
runtimeOutputs.push_back(
|
||||||
|
extractAxisSlice(rewriter, splitOp.getLoc(), input, *axis, runtimeOffset, sliceSize));
|
||||||
runtimeOffset += sliceSize;
|
runtimeOffset += sliceSize;
|
||||||
}
|
}
|
||||||
spatial::SpatYieldOp::create(rewriter, splitOp.getLoc(), runtimeOutputs);
|
spatial::SpatYieldOp::create(rewriter, splitOp.getLoc(), runtimeOutputs);
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||||
|
#include "mlir/Dialect/Linalg/IR/Linalg.h"
|
||||||
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
|
#include "mlir/Transforms/DialectConversion.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
|
||||||
|
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
||||||
|
|
||||||
|
using namespace mlir;
|
||||||
|
|
||||||
|
namespace onnx_mlir {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
static bool isInsideSpatialComputeRegion(Operation* op) {
|
||||||
|
return op->getParentOfType<spatial::SpatCompute>() || op->getParentOfType<spatial::SpatComputeBatch>();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Value createTransposeInit(Value input,
|
||||||
|
RankedTensorType resultType,
|
||||||
|
ArrayRef<int64_t> permutation,
|
||||||
|
ConversionPatternRewriter& rewriter,
|
||||||
|
Location loc) {
|
||||||
|
SmallVector<OpFoldResult> sizes;
|
||||||
|
sizes.reserve(resultType.getRank());
|
||||||
|
for (auto [resultDim, sourceDim] : llvm::zip_equal(resultType.getShape(), permutation)) {
|
||||||
|
if (!ShapedType::isDynamic(resultDim)) {
|
||||||
|
sizes.push_back(rewriter.getIndexAttr(resultDim));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
sizes.push_back(tensor::DimOp::create(rewriter, loc, input, sourceDim).getResult());
|
||||||
|
}
|
||||||
|
return tensor::EmptyOp::create(rewriter, loc, sizes, resultType.getElementType()).getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<Value> materializeTransposedConstant(Value input,
|
||||||
|
RankedTensorType resultType,
|
||||||
|
ArrayRef<int64_t> permutation,
|
||||||
|
ConversionPatternRewriter& rewriter,
|
||||||
|
Location loc) {
|
||||||
|
auto denseAttr = getHostConstDenseElementsAttr(input);
|
||||||
|
if (!denseAttr)
|
||||||
|
return failure();
|
||||||
|
|
||||||
|
auto inputType = dyn_cast<RankedTensorType>(denseAttr.getType());
|
||||||
|
if (!inputType || !inputType.hasStaticShape() || !resultType.hasStaticShape()
|
||||||
|
|| inputType.getRank() != resultType.getRank()
|
||||||
|
|| static_cast<int64_t>(permutation.size()) != inputType.getRank()) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getOrCreateConstant(rewriter,
|
||||||
|
rewriter.getInsertionBlock()->getParentOp(),
|
||||||
|
DenseElementsAttr::get(resultType, resultValues),
|
||||||
|
resultType);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TransposeToLinalgTranspose : OpConversionPattern<ONNXTransposeOp> {
|
||||||
|
using OpConversionPattern::OpConversionPattern;
|
||||||
|
|
||||||
|
LogicalResult matchAndRewrite(ONNXTransposeOp transposeOp,
|
||||||
|
ONNXTransposeOpAdaptor adaptor,
|
||||||
|
ConversionPatternRewriter& rewriter) const override {
|
||||||
|
auto inputType = dyn_cast<RankedTensorType>(adaptor.getData().getType());
|
||||||
|
auto resultType = dyn_cast<RankedTensorType>(transposeOp.getResult().getType());
|
||||||
|
if (!inputType || !resultType)
|
||||||
|
return failure();
|
||||||
|
|
||||||
|
auto permutation = getTransposePermutationChecked(transposeOp.getPermAttr(), inputType.getRank());
|
||||||
|
if (failed(permutation))
|
||||||
|
return failure();
|
||||||
|
if (isCompileTimeComputable(adaptor.getData())) {
|
||||||
|
auto constantTranspose =
|
||||||
|
materializeTransposedConstant(adaptor.getData(), resultType, *permutation, rewriter, transposeOp.getLoc());
|
||||||
|
if (succeeded(constantTranspose)) {
|
||||||
|
rewriter.replaceOp(transposeOp, *constantTranspose);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto buildTranspose = [&](Value input) -> Value {
|
||||||
|
Value init = createTransposeInit(input, resultType, *permutation, rewriter, transposeOp.getLoc());
|
||||||
|
return linalg::TransposeOp::create(rewriter, transposeOp.getLoc(), input, init, *permutation).getResult()[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isInsideSpatialComputeRegion(transposeOp.getOperation())) {
|
||||||
|
rewriter.replaceOp(transposeOp, buildTranspose(adaptor.getData()));
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto computeOp = createSpatCompute<1>(
|
||||||
|
rewriter, transposeOp.getLoc(), TypeRange {resultType}, {}, ValueRange {adaptor.getData()}, [&](Value input) {
|
||||||
|
spatial::SpatYieldOp::create(rewriter, transposeOp.getLoc(), buildTranspose(input));
|
||||||
|
});
|
||||||
|
rewriter.replaceOp(transposeOp, computeOp.getResult(0));
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void populateTransposePatterns(RewritePatternSet& patterns, MLIRContext* ctx) {
|
||||||
|
patterns.add<TransposeToLinalgTranspose>(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace onnx_mlir
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "mlir/IR/PatternMatch.h"
|
||||||
|
#include "mlir/Support/LogicalResult.h"
|
||||||
|
|
||||||
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
|
|
||||||
|
namespace onnx_mlir {
|
||||||
|
|
||||||
|
mlir::FailureOr<mlir::Value>
|
||||||
|
lowerSelectedConv2DPlan(spatial::SpatConv2DPlanOp planOp,
|
||||||
|
std::optional<mlir::Value> rowStripInput,
|
||||||
|
bool emitRowStripLayout,
|
||||||
|
mlir::PatternRewriter& rewriter);
|
||||||
|
|
||||||
|
mlir::LogicalResult canLowerConvPlanToRowStrip(spatial::SpatConv2DPlanOp planOp);
|
||||||
|
mlir::LogicalResult canConsumeAndProduceRowStrip(spatial::SpatConv2DPlanOp planOp);
|
||||||
|
|
||||||
|
} // namespace onnx_mlir
|
||||||
@@ -1,294 +0,0 @@
|
|||||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
|
||||||
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
|
||||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
|
||||||
#include "mlir/IR/IRMapping.h"
|
|
||||||
#include "mlir/IR/PatternMatch.h"
|
|
||||||
|
|
||||||
#include "llvm/ADT/STLExtras.h"
|
|
||||||
#include "llvm/ADT/SmallVector.h"
|
|
||||||
|
|
||||||
#include "src/Accelerators/PIM/Common/IR/WeightUtils.hpp"
|
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/WeightMaterialization.hpp"
|
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/PostPatterns.hpp"
|
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
|
||||||
#include "src/Dialect/ONNX/ONNXOps.hpp"
|
|
||||||
|
|
||||||
using namespace mlir;
|
|
||||||
|
|
||||||
namespace onnx_mlir {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
static bool isWeightMaterializationHelperUser(Operation* op) {
|
|
||||||
return isa<tensor::ExtractSliceOp, tensor::ExpandShapeOp, tensor::CollapseShapeOp, ONNXTransposeOp>(op);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool canPromoteInputBlockArgument(BlockArgument arg) {
|
|
||||||
return !arg.use_empty() && llvm::all_of(arg.getUsers(), isWeightMaterializationHelperUser);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool canPromoteInputBlockArgument(std::optional<BlockArgument> arg) {
|
|
||||||
return arg && canPromoteInputBlockArgument(*arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isDirectConstantValue(Value value) {
|
|
||||||
return isa_and_nonnull<arith::ConstantOp, ONNXConstantOp>(value.getDefiningOp());
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename ComputeOpTy>
|
|
||||||
static bool hasPromotableWeightLikeInputs(ComputeOpTy compute) {
|
|
||||||
for (auto [inputIdx, input] : llvm::enumerate(compute.getInputs())) {
|
|
||||||
if (!isWeightLikeComputeOperand(input))
|
|
||||||
continue;
|
|
||||||
if (isDirectConstantValue(input) && !canPromoteInputBlockArgument(compute.getInputArgument(inputIdx)))
|
|
||||||
continue;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Promotes foldable helper chains from runtime inputs to weights to avoid artificial compute inputs.
|
|
||||||
struct PromoteWeightLikeComputeInputsPattern : OpRewritePattern<spatial::SpatCompute> {
|
|
||||||
using OpRewritePattern<spatial::SpatCompute>::OpRewritePattern;
|
|
||||||
|
|
||||||
LogicalResult matchAndRewrite(spatial::SpatCompute compute, PatternRewriter& rewriter) const override {
|
|
||||||
SmallVector<bool> promoteInput(compute.getInputs().size(), false);
|
|
||||||
bool needsRewrite = false;
|
|
||||||
Block& oldBlock = compute.getBody().front();
|
|
||||||
for (auto [inputIdx, input] : llvm::enumerate(compute.getInputs())) {
|
|
||||||
if (!isWeightLikeComputeOperand(input))
|
|
||||||
continue;
|
|
||||||
if (isDirectConstantValue(input) && !canPromoteInputBlockArgument(compute.getInputArgument(inputIdx)))
|
|
||||||
continue;
|
|
||||||
promoteInput[inputIdx] = true;
|
|
||||||
needsRewrite = true;
|
|
||||||
}
|
|
||||||
if (!needsRewrite)
|
|
||||||
return rewriter.notifyMatchFailure(compute, "no weight-like inputs to promote");
|
|
||||||
|
|
||||||
rewriter.setInsertionPointAfter(compute);
|
|
||||||
|
|
||||||
SmallVector<Value> newWeights(compute.getWeights().begin(), compute.getWeights().end());
|
|
||||||
SmallVector<Value> newInputs;
|
|
||||||
SmallVector<Type> newInputTypes;
|
|
||||||
SmallVector<Location> newInputLocs;
|
|
||||||
newWeights.reserve(compute.getWeights().size() + compute.getInputs().size());
|
|
||||||
newInputs.reserve(compute.getInputs().size());
|
|
||||||
newInputTypes.reserve(compute.getInputs().size());
|
|
||||||
newInputLocs.reserve(compute.getInputs().size());
|
|
||||||
|
|
||||||
for (auto [inputIdx, input] : llvm::enumerate(compute.getInputs())) {
|
|
||||||
if (promoteInput[inputIdx]) {
|
|
||||||
newWeights.push_back(input);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
newInputs.push_back(input);
|
|
||||||
newInputTypes.push_back(input.getType());
|
|
||||||
newInputLocs.push_back(input.getLoc());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto newCompute =
|
|
||||||
spatial::SpatCompute::create(rewriter, compute.getLoc(), compute.getResultTypes(), newWeights, newInputs);
|
|
||||||
SmallVector<Type> newBlockArgTypes;
|
|
||||||
SmallVector<Location> newBlockArgLocs;
|
|
||||||
for (Value weight : newWeights) {
|
|
||||||
newBlockArgTypes.push_back(weight.getType());
|
|
||||||
newBlockArgLocs.push_back(weight.getLoc());
|
|
||||||
}
|
|
||||||
llvm::append_range(newBlockArgTypes, newInputTypes);
|
|
||||||
llvm::append_range(newBlockArgLocs, newInputLocs);
|
|
||||||
auto* newBlock = rewriter.createBlock(
|
|
||||||
&newCompute.getBody(), newCompute.getBody().end(), TypeRange(newBlockArgTypes), newBlockArgLocs);
|
|
||||||
newCompute.getProperties().setOperandSegmentSizes(
|
|
||||||
{static_cast<int>(newWeights.size()), static_cast<int>(newInputs.size())});
|
|
||||||
rewriter.setInsertionPointToStart(newBlock);
|
|
||||||
|
|
||||||
IRRewriter bodyRewriter(rewriter.getContext());
|
|
||||||
bodyRewriter.setInsertionPointToStart(newBlock);
|
|
||||||
|
|
||||||
IRMapping mapper;
|
|
||||||
for (auto [weightIndex, weight] : llvm::enumerate(compute.getWeights())) {
|
|
||||||
auto oldWeightArg = compute.getWeightArgument(weightIndex);
|
|
||||||
auto newWeightArg = newCompute.getWeightArgument(weightIndex);
|
|
||||||
if (!oldWeightArg || !newWeightArg)
|
|
||||||
return rewriter.notifyMatchFailure(compute, "missing compute weight block argument during rewrite");
|
|
||||||
mapper.map(*oldWeightArg, *newWeightArg);
|
|
||||||
}
|
|
||||||
size_t newInputIdx = 0;
|
|
||||||
for (auto [oldInputIdx, input] : llvm::enumerate(compute.getInputs())) {
|
|
||||||
auto oldArg = compute.getInputArgument(oldInputIdx);
|
|
||||||
if (!oldArg)
|
|
||||||
return rewriter.notifyMatchFailure(compute, "missing compute input block argument during rewrite");
|
|
||||||
if (!promoteInput[oldInputIdx]) {
|
|
||||||
auto newInputArg = newCompute.getInputArgument(newInputIdx++);
|
|
||||||
if (!newInputArg)
|
|
||||||
return rewriter.notifyMatchFailure(compute, "missing rewritten compute input block argument");
|
|
||||||
mapper.map(*oldArg, *newInputArg);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto clonedValue = materializeWeightLikeValueInBlock(input, bodyRewriter, mapper);
|
|
||||||
if (failed(clonedValue))
|
|
||||||
return rewriter.notifyMatchFailure(compute, "failed to materialize promoted weight-like operand");
|
|
||||||
mapper.map(*oldArg, *clonedValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Operation& op : oldBlock.without_terminator())
|
|
||||||
rewriter.clone(op, mapper);
|
|
||||||
|
|
||||||
auto oldYield = cast<spatial::SpatYieldOp>(oldBlock.getTerminator());
|
|
||||||
SmallVector<Value> newYieldOperands;
|
|
||||||
newYieldOperands.reserve(oldYield.getOutputs().size());
|
|
||||||
for (Value operand : oldYield.getOutputs()) {
|
|
||||||
auto mapped = mapper.lookupOrNull(operand);
|
|
||||||
newYieldOperands.push_back(mapped ? cast<Value>(mapped) : operand);
|
|
||||||
}
|
|
||||||
spatial::SpatYieldOp::create(rewriter, oldYield.getLoc(), newYieldOperands);
|
|
||||||
|
|
||||||
rewriter.replaceOp(compute, newCompute.getResults());
|
|
||||||
return success();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Promotes foldable batch helper chains to weights while preserving compact compute_batch IR.
|
|
||||||
struct PromoteWeightLikeComputeBatchInputsPattern : OpRewritePattern<spatial::SpatComputeBatch> {
|
|
||||||
using OpRewritePattern<spatial::SpatComputeBatch>::OpRewritePattern;
|
|
||||||
|
|
||||||
LogicalResult matchAndRewrite(spatial::SpatComputeBatch compute, PatternRewriter& rewriter) const override {
|
|
||||||
SmallVector<bool> promoteInput(compute.getInputs().size(), false);
|
|
||||||
bool needsRewrite = false;
|
|
||||||
Block& oldBlock = compute.getBody().front();
|
|
||||||
for (auto [inputIdx, input] : llvm::enumerate(compute.getInputs())) {
|
|
||||||
if (!isWeightLikeComputeOperand(input))
|
|
||||||
continue;
|
|
||||||
if (isDirectConstantValue(input) && !canPromoteInputBlockArgument(compute.getInputArgument(inputIdx)))
|
|
||||||
continue;
|
|
||||||
promoteInput[inputIdx] = true;
|
|
||||||
needsRewrite = true;
|
|
||||||
}
|
|
||||||
if (!needsRewrite)
|
|
||||||
return rewriter.notifyMatchFailure(compute, "no weight-like batch inputs to promote");
|
|
||||||
|
|
||||||
rewriter.setInsertionPointAfter(compute);
|
|
||||||
|
|
||||||
SmallVector<Value> newWeights(compute.getWeights().begin(), compute.getWeights().end());
|
|
||||||
SmallVector<Value> newInputs;
|
|
||||||
SmallVector<Type> newInputTypes;
|
|
||||||
SmallVector<Location> newInputLocs;
|
|
||||||
newWeights.reserve(compute.getWeights().size() + compute.getInputs().size());
|
|
||||||
newInputs.reserve(compute.getInputs().size());
|
|
||||||
newInputTypes.reserve(compute.getInputs().size());
|
|
||||||
newInputLocs.reserve(compute.getInputs().size());
|
|
||||||
|
|
||||||
for (auto [inputIdx, input] : llvm::enumerate(compute.getInputs())) {
|
|
||||||
if (promoteInput[inputIdx]) {
|
|
||||||
newWeights.push_back(input);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
newInputs.push_back(input);
|
|
||||||
newInputTypes.push_back(input.getType());
|
|
||||||
newInputLocs.push_back(input.getLoc());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto newCompute =
|
|
||||||
spatial::SpatComputeBatch::create(rewriter,
|
|
||||||
compute.getLoc(),
|
|
||||||
compute.getResultTypes(),
|
|
||||||
rewriter.getI32IntegerAttr(static_cast<int32_t>(compute.getLaneCount())),
|
|
||||||
newWeights,
|
|
||||||
newInputs);
|
|
||||||
auto laneArg = compute.getLaneArgument();
|
|
||||||
if (!laneArg)
|
|
||||||
return rewriter.notifyMatchFailure(compute, "missing compute_batch lane block argument");
|
|
||||||
SmallVector<Type> newBlockArgTypes;
|
|
||||||
SmallVector<Location> newBlockArgLocs;
|
|
||||||
newBlockArgTypes.reserve(1 + newWeights.size() + newInputTypes.size() + compute.getNumResults());
|
|
||||||
newBlockArgLocs.reserve(1 + newWeights.size() + newInputLocs.size() + compute.getNumResults());
|
|
||||||
newBlockArgTypes.push_back(laneArg->getType());
|
|
||||||
newBlockArgLocs.push_back(laneArg->getLoc());
|
|
||||||
for (Value weight : newWeights) {
|
|
||||||
newBlockArgTypes.push_back(weight.getType());
|
|
||||||
newBlockArgLocs.push_back(weight.getLoc());
|
|
||||||
}
|
|
||||||
llvm::append_range(newBlockArgTypes, newInputTypes);
|
|
||||||
llvm::append_range(newBlockArgLocs, newInputLocs);
|
|
||||||
for (auto [resultIndex, resultType] : llvm::enumerate(compute.getResultTypes())) {
|
|
||||||
auto outputArg = compute.getOutputArgument(resultIndex);
|
|
||||||
if (!outputArg)
|
|
||||||
return rewriter.notifyMatchFailure(compute, "missing compute_batch output block argument");
|
|
||||||
newBlockArgTypes.push_back(resultType);
|
|
||||||
newBlockArgLocs.push_back(outputArg->getLoc());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* newBlock = rewriter.createBlock(
|
|
||||||
&newCompute.getBody(), newCompute.getBody().end(), TypeRange(newBlockArgTypes), newBlockArgLocs);
|
|
||||||
newCompute.getProperties().setOperandSegmentSizes(
|
|
||||||
{static_cast<int>(newWeights.size()), static_cast<int>(newInputs.size())});
|
|
||||||
rewriter.setInsertionPointToStart(newBlock);
|
|
||||||
|
|
||||||
IRRewriter bodyRewriter(rewriter.getContext());
|
|
||||||
bodyRewriter.setInsertionPointToStart(newBlock);
|
|
||||||
|
|
||||||
IRMapping mapper;
|
|
||||||
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);
|
|
||||||
if (!oldWeightArg || !newWeightArg)
|
|
||||||
return rewriter.notifyMatchFailure(compute, "missing compute_batch weight block argument during rewrite");
|
|
||||||
mapper.map(*oldWeightArg, *newWeightArg);
|
|
||||||
}
|
|
||||||
size_t newInputIdx = 0;
|
|
||||||
for (auto [oldInputIdx, input] : llvm::enumerate(compute.getInputs())) {
|
|
||||||
auto oldArg = compute.getInputArgument(oldInputIdx);
|
|
||||||
if (!oldArg)
|
|
||||||
return rewriter.notifyMatchFailure(compute, "missing compute_batch input block argument during rewrite");
|
|
||||||
if (!promoteInput[oldInputIdx]) {
|
|
||||||
auto newInputArg = newCompute.getInputArgument(newInputIdx++);
|
|
||||||
if (!newInputArg)
|
|
||||||
return rewriter.notifyMatchFailure(compute, "missing rewritten compute_batch input block argument");
|
|
||||||
mapper.map(*oldArg, *newInputArg);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto clonedValue = materializeWeightLikeValueInBlock(input, bodyRewriter, mapper);
|
|
||||||
if (failed(clonedValue))
|
|
||||||
return rewriter.notifyMatchFailure(compute, "failed to materialize promoted batch weight-like operand");
|
|
||||||
mapper.map(*oldArg, *clonedValue);
|
|
||||||
}
|
|
||||||
for (auto resultIndex : llvm::seq<size_t>(0, compute.getNumResults())) {
|
|
||||||
auto outputArg = compute.getOutputArgument(resultIndex);
|
|
||||||
if (!outputArg)
|
|
||||||
return rewriter.notifyMatchFailure(compute, "missing compute_batch output block argument during rewrite");
|
|
||||||
mapper.map(*outputArg, newBlock->getArgument(1 + newWeights.size() + newInputs.size() + resultIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Operation& op : oldBlock)
|
|
||||||
rewriter.clone(op, mapper);
|
|
||||||
|
|
||||||
rewriter.replaceOp(compute, newCompute.getResults());
|
|
||||||
return success();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void populatePostPatterns(RewritePatternSet& patterns, MLIRContext* ctx) {
|
|
||||||
patterns.add<PromoteWeightLikeComputeInputsPattern, PromoteWeightLikeComputeBatchInputsPattern>(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
void annotateWeightsConstants(func::FuncOp funcOp) {
|
|
||||||
funcOp.walk([&](arith::ConstantOp constantOp) {
|
|
||||||
if (hasOnlySpatialMvmVmmWeightUses(constantOp.getResult()))
|
|
||||||
markWeightAlways(constantOp);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool requiresPostRewrite(spatial::SpatCompute computeOp) { return hasPromotableWeightLikeInputs(computeOp); }
|
|
||||||
|
|
||||||
bool requiresPostRewrite(spatial::SpatComputeBatch computeOp) { return hasPromotableWeightLikeInputs(computeOp); }
|
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
|
||||||
#include "mlir/IR/MLIRContext.h"
|
|
||||||
|
|
||||||
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
|
||||||
|
|
||||||
namespace onnx_mlir {
|
|
||||||
|
|
||||||
bool requiresPostRewrite(spatial::SpatCompute computeOp);
|
|
||||||
|
|
||||||
bool requiresPostRewrite(spatial::SpatComputeBatch computeOp);
|
|
||||||
|
|
||||||
void populatePostPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
|
||||||
|
|
||||||
void annotateWeightsConstants(mlir::func::FuncOp funcOp);
|
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "mlir/IR/MLIRContext.h"
|
|
||||||
#include "mlir/Transforms/DialectConversion.h"
|
|
||||||
|
|
||||||
namespace onnx_mlir {
|
|
||||||
|
|
||||||
void populatePrePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
|
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
||||||
|
#include "mlir/IR/PatternMatch.h"
|
||||||
|
#include "mlir/Pass/Pass.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/DenseMap.h"
|
||||||
|
|
||||||
|
#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"
|
||||||
|
|
||||||
|
using namespace mlir;
|
||||||
|
|
||||||
|
namespace onnx_mlir {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
static constexpr StringLiteral kLogicalLayout = "nchw";
|
||||||
|
static constexpr StringLiteral kDenseLayout = "dense_nchw";
|
||||||
|
static constexpr StringLiteral kRowStripLayout = "nchw_row_strip";
|
||||||
|
|
||||||
|
enum class SelectedLayout {
|
||||||
|
DenseNchw,
|
||||||
|
NchwRowStrip,
|
||||||
|
};
|
||||||
|
|
||||||
|
static SelectedLayout getSelectedLayout(llvm::DenseMap<Value, SelectedLayout>& layouts, Value value) {
|
||||||
|
auto it = layouts.find(value);
|
||||||
|
return it == layouts.end() ? SelectedLayout::DenseNchw : it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool allUsersCanHandleRowStrip(Value value, llvm::DenseMap<Value, SelectedLayout>& layouts) {
|
||||||
|
for (Operation* user : value.getUsers()) {
|
||||||
|
if (usesSelectedRowStrip(user, layouts))
|
||||||
|
continue;
|
||||||
|
// Dense-only users must be materialized explicitly.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
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());
|
||||||
|
if (inputLayout == SelectedLayout::NchwRowStrip)
|
||||||
|
return succeeded(canConsumeAndProduceRowStrip(convPlan));
|
||||||
|
return succeeded(canLowerConvPlanToRowStrip(convPlan));
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SelectedLayout chooseReluLayout(spatial::SpatReluPlanOp reluPlan,
|
||||||
|
llvm::DenseMap<Value, SelectedLayout>& layouts) {
|
||||||
|
if (getSelectedLayout(layouts, reluPlan.getInput()) != SelectedLayout::NchwRowStrip)
|
||||||
|
return SelectedLayout::DenseNchw;
|
||||||
|
if (!hasRowStripConsumer(reluPlan.getResult()))
|
||||||
|
return SelectedLayout::DenseNchw;
|
||||||
|
if (!allUsersCanHandleRowStrip(reluPlan.getResult(), layouts))
|
||||||
|
return SelectedLayout::DenseNchw;
|
||||||
|
return SelectedLayout::NchwRowStrip;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SelectedLayout chooseBiasAddLayout(spatial::SpatBiasAddPlanOp biasAddPlan,
|
||||||
|
llvm::DenseMap<Value, SelectedLayout>& layouts) {
|
||||||
|
if (getSelectedLayout(layouts, biasAddPlan.getInput()) != SelectedLayout::NchwRowStrip)
|
||||||
|
return SelectedLayout::DenseNchw;
|
||||||
|
auto resultType = dyn_cast<RankedTensorType>(biasAddPlan.getOutput().getType());
|
||||||
|
if (!resultType || !isSupportedBiasAddValue(biasAddPlan.getBias(), resultType))
|
||||||
|
return SelectedLayout::DenseNchw;
|
||||||
|
if (!hasRowStripConsumer(biasAddPlan.getResult()))
|
||||||
|
return SelectedLayout::DenseNchw;
|
||||||
|
if (!allUsersCanHandleRowStrip(biasAddPlan.getResult(), layouts))
|
||||||
|
return SelectedLayout::DenseNchw;
|
||||||
|
return SelectedLayout::NchwRowStrip;
|
||||||
|
}
|
||||||
|
|
||||||
|
static spatial::SpatBlueprintOp insertRowStripBlueprint(IRRewriter& rewriter, Value value) {
|
||||||
|
auto outputType = cast<RankedTensorType>(value.getType());
|
||||||
|
auto [offsets, sizes] = buildRowStripMetadata(outputType);
|
||||||
|
return spatial::SpatBlueprintOp::create(rewriter,
|
||||||
|
value.getLoc(),
|
||||||
|
outputType,
|
||||||
|
value,
|
||||||
|
ValueRange {},
|
||||||
|
rewriter.getStringAttr(kLogicalLayout),
|
||||||
|
rewriter.getStringAttr(kRowStripLayout),
|
||||||
|
rewriter.getDenseI64ArrayAttr(offsets),
|
||||||
|
rewriter.getDenseI64ArrayAttr(sizes),
|
||||||
|
rewriter.getStringAttr(kRowStripIndexMap),
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void materializeDenseUses(IRRewriter& rewriter,
|
||||||
|
Value layoutValue,
|
||||||
|
llvm::DenseMap<Value, SelectedLayout>& layouts) {
|
||||||
|
SmallVector<OpOperand*> denseUses;
|
||||||
|
for (OpOperand& use : layoutValue.getUses()) {
|
||||||
|
if (usesSelectedRowStrip(use.getOwner(), layouts))
|
||||||
|
continue;
|
||||||
|
denseUses.push_back(&use);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (OpOperand* use : denseUses) {
|
||||||
|
Operation* owner = use->getOwner();
|
||||||
|
rewriter.setInsertionPoint(owner);
|
||||||
|
auto materialized = spatial::SpatMaterializeLayoutOp::create(rewriter,
|
||||||
|
owner->getLoc(),
|
||||||
|
use->get().getType(),
|
||||||
|
use->get(),
|
||||||
|
rewriter.getStringAttr(kLogicalLayout),
|
||||||
|
rewriter.getStringAttr(kRowStripLayout),
|
||||||
|
rewriter.getStringAttr(kDenseLayout));
|
||||||
|
use->set(materialized.getResult());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SpatialLayoutPlanningPass final : PassWrapper<SpatialLayoutPlanningPass, OperationPass<ModuleOp>> {
|
||||||
|
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(SpatialLayoutPlanningPass)
|
||||||
|
|
||||||
|
StringRef getArgument() const override { return "spatial-layout-planning"; }
|
||||||
|
StringRef getDescription() const override { return "Select conservative Spatial layouts and insert reconciliation barriers."; }
|
||||||
|
|
||||||
|
void runOnOperation() override {
|
||||||
|
auto entryFunc = getPimEntryFunc(getOperation());
|
||||||
|
if (failed(entryFunc)) {
|
||||||
|
getOperation().emitError("failed to locate the PIM entry function during Spatial layout planning");
|
||||||
|
signalPassFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
func::FuncOp funcOp = *entryFunc;
|
||||||
|
IRRewriter rewriter(&getContext());
|
||||||
|
llvm::DenseMap<Value, SelectedLayout> layouts;
|
||||||
|
|
||||||
|
bool changed = true;
|
||||||
|
while (changed) {
|
||||||
|
changed = false;
|
||||||
|
for (Operation& op : llvm::make_early_inc_range(funcOp.getBody().front())) {
|
||||||
|
if (auto convPlan = dyn_cast<spatial::SpatConv2DPlanOp>(&op)) {
|
||||||
|
SelectedLayout selected = chooseConvLayout(convPlan, layouts);
|
||||||
|
if (layouts[convPlan.getResult()] != selected) {
|
||||||
|
layouts[convPlan.getResult()] = selected;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (auto reluPlan = dyn_cast<spatial::SpatReluPlanOp>(&op)) {
|
||||||
|
SelectedLayout selected = chooseReluLayout(reluPlan, layouts);
|
||||||
|
if (layouts[reluPlan.getResult()] != selected) {
|
||||||
|
layouts[reluPlan.getResult()] = selected;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Operation& op : llvm::make_early_inc_range(funcOp.getBody().front())) {
|
||||||
|
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
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (getSelectedLayout(layouts, producedValue) != SelectedLayout::NchwRowStrip)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
rewriter.setInsertionPointAfter(&op);
|
||||||
|
auto blueprint = insertRowStripBlueprint(rewriter, producedValue);
|
||||||
|
rewriter.replaceAllUsesExcept(producedValue, blueprint.getResult(), blueprint);
|
||||||
|
materializeDenseUses(rewriter, blueprint.getResult(), layouts);
|
||||||
|
}
|
||||||
|
if (failed(verifyLogicalSpatialGraphInvariants(*entryFunc))) {
|
||||||
|
getOperation().emitError("logical Spatial graph verification failed after SpatialLayoutPlanning");
|
||||||
|
signalPassFailure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::unique_ptr<Pass> createSpatialLayoutPlanningPass() { return std::make_unique<SpatialLayoutPlanningPass>(); }
|
||||||
|
|
||||||
|
} // namespace onnx_mlir
|
||||||
@@ -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,18 @@
|
|||||||
#include "mlir/Dialect/Bufferization/IR/Bufferization.h"
|
#include "mlir/Dialect/Bufferization/IR/Bufferization.h"
|
||||||
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
||||||
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
||||||
|
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
#include "mlir/IR/IRMapping.h"
|
#include "mlir/IR/IRMapping.h"
|
||||||
#include "mlir/IR/Matchers.h"
|
#include "mlir/IR/Matchers.h"
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
#include "Conversion/ONNXToSpatial/Common/Common.hpp"
|
#include "Conversion/ONNXToSpatial/Common/Common.hpp"
|
||||||
#include "Conversion/SpatialToPim/SpatialToPimPass.hpp"
|
#include "Conversion/SpatialToPim/SpatialToPimPass.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/SpatialToPim/Common.hpp"
|
#include "src/Accelerators/PIM/Conversion/SpatialToPim/Common.hpp"
|
||||||
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
|
||||||
|
|
||||||
@@ -18,29 +23,12 @@ using namespace onnx_mlir::pim;
|
|||||||
namespace onnx_mlir {
|
namespace onnx_mlir {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
static bool isExplicitHostOperand(Operation* op, unsigned operandIndex) {
|
|
||||||
if (isa<pim::PimMemCopyDevToHostOp>(op))
|
|
||||||
return operandIndex == 2;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isUsedOnlyAsExplicitHostOperand(Value value) {
|
static bool isUsedOnlyAsExplicitHostOperand(Value value) {
|
||||||
return !value.use_empty() && llvm::all_of(value.getUses(), [](OpOperand& use) {
|
return !value.use_empty() && llvm::all_of(value.getUses(), [](OpOperand& use) {
|
||||||
return isExplicitHostOperand(use.getOwner(), use.getOperandNumber());
|
return isExplicitDevToHostTargetOperand(use.getOwner(), use.getOperandNumber());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static SmallVector<int32_t> getPimCoreIdsForBatchOp(spatial::SpatComputeBatch 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)
|
|
||||||
coreIds.push_back(static_cast<int32_t>(fallbackCoreId++));
|
|
||||||
return coreIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
static FailureOr<unsigned> getDirectReturnOperandIndex(OpResult result) {
|
static FailureOr<unsigned> getDirectReturnOperandIndex(OpResult result) {
|
||||||
if (!result.hasOneUse())
|
if (!result.hasOneUse())
|
||||||
return failure();
|
return failure();
|
||||||
@@ -51,36 +39,218 @@ static FailureOr<unsigned> getDirectReturnOperandIndex(OpResult result) {
|
|||||||
return result.getUses().begin()->getOperandNumber();
|
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();
|
||||||
|
if (!mode || *mode != "fragment_assembly" || !operandIndicesAttr || !sourceOffsetsAttr)
|
||||||
|
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> 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 = 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) {
|
static Value createScaledIndexValue(IRRewriter& rewriter, Location loc, Value base, int64_t scale) {
|
||||||
if (scale == 1)
|
if (scale == 1)
|
||||||
return base;
|
return base;
|
||||||
|
|
||||||
auto scaleValue = arith::ConstantIndexOp::create(rewriter, loc, scale).getResult();
|
auto scaleValue = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), scale);
|
||||||
return arith::MulIOp::create(rewriter, loc, base, scaleValue).getResult();
|
return arith::MulIOp::create(rewriter, loc, base, scaleValue).getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value createHostTargetOffset(IRRewriter& rewriter,
|
static Value createHostTargetOffset(IRRewriter& rewriter,
|
||||||
tensor::ParallelInsertSliceOp insertSlice,
|
Location loc,
|
||||||
ShapedType destinationType,
|
ShapedType destinationType,
|
||||||
|
ArrayRef<OpFoldResult> mixedOffsets,
|
||||||
|
ArrayRef<int64_t> additionalOffsets,
|
||||||
IRMapping& mapper) {
|
IRMapping& mapper) {
|
||||||
int64_t elementBytes = static_cast<int64_t>(getElementTypeSizeInBytes(destinationType.getElementType()));
|
int64_t elementBytes = static_cast<int64_t>(getElementTypeSizeInBytes(destinationType.getElementType()));
|
||||||
SmallVector<int64_t> strides(destinationType.getRank(), 1);
|
SmallVector<int64_t> strides = computeRowMajorStrides(destinationType.getShape());
|
||||||
ArrayRef<int64_t> shape = destinationType.getShape();
|
|
||||||
for (int64_t dim = destinationType.getRank() - 2; dim >= 0; --dim)
|
|
||||||
strides[dim] = strides[dim + 1] * shape[dim + 1];
|
|
||||||
|
|
||||||
Value totalOffset;
|
Value totalOffset;
|
||||||
Location loc = insertSlice.getLoc();
|
for (auto [dim, offset] : llvm::enumerate(mixedOffsets)) {
|
||||||
for (auto [dim, offset] : llvm::enumerate(insertSlice.getMixedOffsets())) {
|
|
||||||
int64_t scale = strides[dim] * elementBytes;
|
int64_t scale = strides[dim] * elementBytes;
|
||||||
Value scaledOffset;
|
Value scaledOffset;
|
||||||
if (auto attr = dyn_cast<Attribute>(offset)) {
|
if (auto attr = dyn_cast<Attribute>(offset)) {
|
||||||
auto intAttr = dyn_cast<IntegerAttr>(attr);
|
auto intAttr = dyn_cast<IntegerAttr>(attr);
|
||||||
assert(intAttr && "expected integer offset attribute");
|
assert(intAttr && "expected integer offset attribute");
|
||||||
scaledOffset = arith::ConstantIndexOp::create(rewriter, loc, intAttr.getInt() * scale).getResult();
|
scaledOffset = getOrCreateIndexConstant(rewriter,
|
||||||
}
|
rewriter.getInsertionBlock()->getParentOp(),
|
||||||
else {
|
(intAttr.getInt() + additionalOffsets[dim]) * scale);
|
||||||
|
} else {
|
||||||
scaledOffset = createScaledIndexValue(rewriter, loc, mapper.lookupOrDefault(cast<Value>(offset)), scale);
|
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 =
|
totalOffset =
|
||||||
@@ -88,13 +258,26 @@ static Value createHostTargetOffset(IRRewriter& rewriter,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!totalOffset)
|
if (!totalOffset)
|
||||||
totalOffset = arith::ConstantIndexOp::create(rewriter, loc, 0).getResult();
|
totalOffset = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 0);
|
||||||
return totalOffset;
|
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
|
} // namespace
|
||||||
|
|
||||||
LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatComputeBatch computeBatchOp,
|
LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatScheduledComputeBatch computeBatchOp,
|
||||||
IRRewriter& rewriter) {
|
IRRewriter& rewriter) {
|
||||||
Location loc = computeBatchOp.getLoc();
|
Location loc = computeBatchOp.getLoc();
|
||||||
Block& oldBlock = computeBatchOp.getBody().front();
|
Block& oldBlock = computeBatchOp.getBody().front();
|
||||||
@@ -109,31 +292,52 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatCompute
|
|||||||
"resultful compute_batch lowering currently requires a spat.in_parallel terminator");
|
"resultful compute_batch lowering currently requires a spat.in_parallel terminator");
|
||||||
}
|
}
|
||||||
|
|
||||||
SmallVector<int32_t> 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());
|
SmallVector<Value> batchWeights(computeBatchOp.getWeights().begin(), computeBatchOp.getWeights().end());
|
||||||
SmallVector<Value> batchInputs;
|
SmallVector<Value> batchInputs;
|
||||||
if (!computeBatchOp.getInputs().empty())
|
if (!computeBatchOp.getInputs().empty())
|
||||||
batchInputs.append(computeBatchOp.getInputs().begin(), computeBatchOp.getInputs().end());
|
batchInputs.append(computeBatchOp.getInputs().begin(), computeBatchOp.getInputs().end());
|
||||||
|
|
||||||
rewriter.setInsertionPointAfter(computeBatchOp);
|
rewriter.setInsertionPointAfter(computeBatchOp);
|
||||||
auto coreBatchOp = pim::PimCoreBatchOp::create(rewriter,
|
auto laneCountAttr = pim::getCheckedI32Attr(
|
||||||
loc,
|
rewriter, computeBatchOp, static_cast<uint64_t>(computeBatchOp.getLaneCount()), "pim core_batch lane count");
|
||||||
rewriter.getI32IntegerAttr(computeBatchOp.getLaneCount()),
|
if (failed(laneCountAttr))
|
||||||
ValueRange(batchWeights),
|
return failure();
|
||||||
ValueRange(batchInputs));
|
auto coreBatchOp =
|
||||||
|
pim::PimCoreBatchOp::create(rewriter, loc, *laneCountAttr, ValueRange(batchWeights), ValueRange(batchInputs));
|
||||||
coreBatchOp.getProperties().setOperandSegmentSizes(
|
coreBatchOp.getProperties().setOperandSegmentSizes(
|
||||||
{static_cast<int>(batchWeights.size()), static_cast<int>(batchInputs.size())});
|
{static_cast<int>(batchWeights.size()), static_cast<int>(batchInputs.size())});
|
||||||
coreBatchOp->setAttr(onnx_mlir::kCoreIdsAttrName, rewriter.getDenseI32ArrayAttr(coreIds));
|
coreBatchOp->setAttr(onnx_mlir::kCoreIdsAttrName, rewriter.getDenseI32ArrayAttr(*coreIds));
|
||||||
|
|
||||||
SmallVector<unsigned> returnOperandIndices;
|
SmallVector<unsigned> returnOperandIndices;
|
||||||
|
SmallVector<SmallVector<FragmentAssemblyCopyRun, 1>, 4> fragmentAssemblyRunsByResult;
|
||||||
if (computeBatchOp.getNumResults() != 0) {
|
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())) {
|
for (auto [resultIndex, result] : llvm::enumerate(computeBatchOp.getResults())) {
|
||||||
|
if (result.use_empty())
|
||||||
|
continue;
|
||||||
FailureOr<unsigned> returnOperandIndex = getDirectReturnOperandIndex(cast<OpResult>(result));
|
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(
|
return computeBatchOp.emitOpError(
|
||||||
"resultful compute_batch lowering currently requires each result to be used directly by func.return");
|
"resultful compute_batch publication lowering requires static ranked tensor results");
|
||||||
returnOperandIndices[resultIndex] = *returnOperandIndex;
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,14 +370,12 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatCompute
|
|||||||
BlockArgument newArg = coreBatchOp.getInputArgument(inputIndex);
|
BlockArgument newArg = coreBatchOp.getInputArgument(inputIndex);
|
||||||
auto newArgType = cast<ShapedType>(newArg.getType());
|
auto newArgType = cast<ShapedType>(newArg.getType());
|
||||||
auto outputBuffer = createEmptyTensorFromShaped(rewriter, loc, newArgType);
|
auto outputBuffer = createEmptyTensorFromShaped(rewriter, loc, newArgType);
|
||||||
auto copied = pim::PimMemCopyHostToDevBatchOp::create(rewriter,
|
Value zeroOffset = getOrCreateIndexConstant(rewriter, coreBatchOp.getOperation(), 0);
|
||||||
loc,
|
auto sizeAttr = getTensorSizeInBytesAttr(rewriter, coreBatchOp.getOperation(), newArg);
|
||||||
outputBuffer.getType(),
|
if (failed(sizeAttr))
|
||||||
outputBuffer,
|
return failure();
|
||||||
newArg,
|
auto copied = pim::PimMemCopyHostToDevOp::create(
|
||||||
rewriter.getI32IntegerAttr(0),
|
rewriter, loc, outputBuffer.getType(), zeroOffset, zeroOffset, outputBuffer, newArg, *sizeAttr)
|
||||||
rewriter.getI32IntegerAttr(0),
|
|
||||||
getTensorSizeInBytesAttr(rewriter, newArg))
|
|
||||||
.getOutput();
|
.getOutput();
|
||||||
mapper.map(*oldArg, copied);
|
mapper.map(*oldArg, copied);
|
||||||
}
|
}
|
||||||
@@ -193,6 +395,18 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatCompute
|
|||||||
if (isa<spatial::SpatYieldOp>(op))
|
if (isa<spatial::SpatYieldOp>(op))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
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)) {
|
if (auto parallelOp = dyn_cast<spatial::SpatInParallelOp>(op)) {
|
||||||
auto firstOutputArg = computeBatchOp.getOutputArgument(0);
|
auto firstOutputArg = computeBatchOp.getOutputArgument(0);
|
||||||
if (!firstOutputArg)
|
if (!firstOutputArg)
|
||||||
@@ -209,12 +423,80 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatCompute
|
|||||||
unsigned resultIndex = outputArg.getArgNumber() - firstOutputArg->getArgNumber();
|
unsigned resultIndex = outputArg.getArgNumber() - firstOutputArg->getArgNumber();
|
||||||
if (resultIndex >= returnOperandIndices.size())
|
if (resultIndex >= returnOperandIndices.size())
|
||||||
return insertSlice.emitOpError("result index out of range while lowering host batch output");
|
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());
|
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());
|
Value hostTarget = getOrCreateHostOutputTensor(resultIndex, insertSlice.getLoc());
|
||||||
auto hostTargetType = cast<ShapedType>(hostTarget.getType());
|
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 hostTargetOffset = createHostTargetOffset(rewriter, insertSlice, hostTargetType, mapper);
|
||||||
Value zeroOffset = arith::ConstantIndexOp::create(rewriter, insertSlice.getLoc(), 0).getResult();
|
Value zeroOffset = getOrCreateIndexConstant(rewriter, coreBatchOp.getOperation(), 0);
|
||||||
|
auto sizeAttr = getTensorSizeInBytesAttr(rewriter, coreBatchOp.getOperation(), mappedSource);
|
||||||
|
if (failed(sizeAttr))
|
||||||
|
return failure();
|
||||||
pim::PimMemCopyDevToHostOp::create(rewriter,
|
pim::PimMemCopyDevToHostOp::create(rewriter,
|
||||||
insertSlice.getLoc(),
|
insertSlice.getLoc(),
|
||||||
hostTarget.getType(),
|
hostTarget.getType(),
|
||||||
@@ -222,7 +504,7 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatCompute
|
|||||||
zeroOffset,
|
zeroOffset,
|
||||||
hostTarget,
|
hostTarget,
|
||||||
mappedSource,
|
mappedSource,
|
||||||
getTensorSizeInBytesAttr(rewriter, mappedSource));
|
*sizeAttr);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -237,15 +519,14 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatCompute
|
|||||||
}
|
}
|
||||||
auto clonedType = cast<ShapedType>(clonedTensor.getType());
|
auto clonedType = cast<ShapedType>(clonedTensor.getType());
|
||||||
auto outputBuffer = createEmptyTensorFromShaped(rewriter, loc, clonedType);
|
auto outputBuffer = createEmptyTensorFromShaped(rewriter, loc, clonedType);
|
||||||
auto copied = pim::PimMemCopyHostToDevBatchOp::create(rewriter,
|
Value zeroOffset = getOrCreateIndexConstant(rewriter, coreBatchOp.getOperation(), 0);
|
||||||
loc,
|
auto sizeAttr = getTensorSizeInBytesAttr(rewriter, coreBatchOp.getOperation(), clonedTensor);
|
||||||
outputBuffer.getType(),
|
if (failed(sizeAttr))
|
||||||
outputBuffer,
|
return failure();
|
||||||
clonedTensor,
|
auto copied =
|
||||||
rewriter.getI32IntegerAttr(0),
|
pim::PimMemCopyHostToDevOp::create(
|
||||||
rewriter.getI32IntegerAttr(0),
|
rewriter, loc, outputBuffer.getType(), zeroOffset, zeroOffset, outputBuffer, clonedTensor, *sizeAttr)
|
||||||
getTensorSizeInBytesAttr(rewriter, clonedTensor))
|
.getOutput();
|
||||||
.getOutput();
|
|
||||||
mapper.map(toTensorOp.getResult(), copied);
|
mapper.map(toTensorOp.getResult(), copied);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -254,15 +535,21 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatCompute
|
|||||||
for (auto [operandIndex, operand] : llvm::enumerate(op.getOperands())) {
|
for (auto [operandIndex, operand] : llvm::enumerate(op.getOperands())) {
|
||||||
if (!isa<TensorType>(operand.getType()) || mapper.contains(operand))
|
if (!isa<TensorType>(operand.getType()) || mapper.contains(operand))
|
||||||
continue;
|
continue;
|
||||||
if (isExplicitHostOperand(&op, operandIndex))
|
if (isExplicitDevToHostTargetOperand(&op, operandIndex))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Operation* definingOp = operand.getDefiningOp();
|
Operation* definingOp = operand.getDefiningOp();
|
||||||
if (definingOp && definingOp->getBlock() == &oldBlock)
|
if (definingOp && definingOp->getBlock() == &oldBlock)
|
||||||
continue;
|
continue;
|
||||||
|
if (definingOp && definingOp->hasTrait<OpTrait::ConstantLike>())
|
||||||
|
continue;
|
||||||
|
|
||||||
return computeBatchOp.emitOpError(
|
InFlightDiagnostic diagnostic =
|
||||||
"expected external tensor communication to be materialized in Spatial before batch lowering");
|
computeBatchOp.emitOpError("expected external tensor communication to be materialized in Spatial before batch lowering");
|
||||||
|
diagnostic << " while cloning nested op '" << op.getName() << "' tensor operand #" << operandIndex;
|
||||||
|
if (definingOp)
|
||||||
|
diagnostic << " from external producer '" << definingOp->getName() << "'";
|
||||||
|
return diagnostic;
|
||||||
}
|
}
|
||||||
|
|
||||||
Operation* cloned = rewriter.clone(op, mapper);
|
Operation* cloned = rewriter.clone(op, mapper);
|
||||||
|
|||||||
@@ -3,15 +3,17 @@ mlir_tablegen(SpatialToPim.hpp.inc -gen-rewriters "-I${ONNX_MLIR_SRC_ROOT}")
|
|||||||
add_public_tablegen_target(SpatialToPimIncGen)
|
add_public_tablegen_target(SpatialToPimIncGen)
|
||||||
|
|
||||||
add_pim_library(OMSpatialToPim
|
add_pim_library(OMSpatialToPim
|
||||||
|
Patterns.cpp
|
||||||
SpatialToPimPass.cpp
|
SpatialToPimPass.cpp
|
||||||
BatchCoreLoweringPatterns.cpp
|
BatchCoreLoweringPatterns.cpp
|
||||||
ChannelLoweringPatterns.cpp
|
|
||||||
Common.cpp
|
Common.cpp
|
||||||
ComputeLikeRegionUtils.cpp
|
ComputeLikeRegionUtils.cpp
|
||||||
CoreLoweringPatterns.cpp
|
CoreLoweringPatterns.cpp
|
||||||
GlobalTensorMaterialization.cpp
|
|
||||||
ReturnPathNormalization.cpp
|
ReturnPathNormalization.cpp
|
||||||
TensorPackingPatterns.cpp
|
Patterns/ChannelLowering.cpp
|
||||||
|
Patterns/GlobalTensorMaterialization.cpp
|
||||||
|
Patterns/TensorPacking.cpp
|
||||||
|
Patterns/Transpose.cpp
|
||||||
|
|
||||||
EXCLUDE_FROM_OM_LIBS
|
EXCLUDE_FROM_OM_LIBS
|
||||||
|
|
||||||
@@ -19,6 +21,7 @@ add_pim_library(OMSpatialToPim
|
|||||||
SpatialToPimIncGen
|
SpatialToPimIncGen
|
||||||
|
|
||||||
LINK_LIBS PUBLIC
|
LINK_LIBS PUBLIC
|
||||||
|
MLIRLinalgDialect
|
||||||
MLIRSCFDialect
|
MLIRSCFDialect
|
||||||
MLIRSCFUtils
|
MLIRSCFUtils
|
||||||
MLIRTransformUtils
|
MLIRTransformUtils
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "mlir/IR/PatternMatch.h"
|
|
||||||
|
|
||||||
namespace onnx_mlir {
|
|
||||||
|
|
||||||
void populateChannelLoweringPatterns(mlir::RewritePatternSet& patterns);
|
|
||||||
|
|
||||||
} // namespace onnx_mlir
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user