30 Commits

Author SHA1 Message Date
ilgeco 852bef7605 ReduceMean + resnet
Validate Operations / validate-operations (push) Has been cancelled
2026-06-10 14:30:10 +02:00
ilgeco 237654dadf Fix direct import
Validate Operations / validate-operations (push) Has been cancelled
2026-06-10 12:14:20 +02:00
ilgeco 6d69600bc1 Yolo Image Validator + new accept rule
Validate Operations / validate-operations (push) Has been cancelled
2026-06-10 11:59:43 +02:00
NiccoloN aec80529ca much faster MaterializeMergeSchedule.cpp
Validate Operations / validate-operations (push) Has been cancelled
2026-06-05 18:22:59 +02:00
ilgeco 8ddbbcecfa Added support for SliceOp
Validate Operations / validate-operations (push) Has been cancelled
2026-06-05 17:36:51 +02:00
ilgeco 90c4339808 SpatialSubOp
Validate Operations / validate-operations (push) Has been cancelled
2026-06-05 17:12:16 +02:00
ilgeco 08870de1a6 Merge branch 'refactorone' of chef.heaplab.deib.polimi.it:nnicolosi/Raptor into refactorone 2026-06-05 16:43:50 +02:00
NiccoloN a34ac223c0 fix remaining failing tests
Validate Operations / validate-operations (push) Has been cancelled
remove unsupported tests
2026-06-05 15:27:11 +02:00
NiccoloN 0fa10b4074 better Conv.cpp and fixed broken conv op validation test
Validate Operations / validate-operations (push) Has been cancelled
2026-06-05 13:35:27 +02:00
NiccoloN e166ff7e1d better AGENTS.md
Validate Operations / validate-operations (push) Has been cancelled
2026-06-05 11:36:01 +02:00
ilgeco a70a8f77cf Merge branch 'refactorone' of chef.heaplab.deib.polimi.it:nnicolosi/Raptor into refactorone 2026-06-05 10:20:09 +02:00
ilgeco 800c0c4316 Python peft and new summary report 2026-06-05 10:20:02 +02:00
NiccoloN 1e9e61f5a9 remove useless MaterializeHostConstantsPass.cpp and fix lowering before instead
Validate Operations / validate-operations (push) Has been cancelled
avoid spammy pim codegen diagnostics
2026-06-05 10:06:28 +02:00
ilgeco 27410207c4 New corner case test
Validate Operations / validate-operations (push) Has been cancelled
2026-06-04 16:00:48 +02:00
NiccoloN cbc9808229 more generalized MaterializeMergeSchedule.cpp for better memory usage after materialization
Validate Operations / validate-operations (push) Has been cancelled
2026-06-04 12:44:57 +02:00
NiccoloN 69021d56aa automatic code reformat
Validate Operations / validate-operations (push) Has been cancelled
2026-06-03 19:43:56 +02:00
NiccoloN dc5edd032c Merge branch 'refactorone' of chef.heaplab.deib.polimi.it:nnicolosi/Raptor into refactorone 2026-06-03 19:40:53 +02:00
NiccoloN e33f517221 faster scheduling: split batches into numCores tasks before scheduling instead of numLanes tasks 2026-06-03 19:40:34 +02:00
ilgeco f94b3d1020 Merge branch 'refactorone' of chef.heaplab.deib.polimi.it:nnicolosi/Raptor into refactorone
Validate Operations / validate-operations (push) Has been cancelled
2026-06-03 18:15:33 +02:00
ilgeco 20cf40c9ba Memory Liveness 2026-06-03 18:15:30 +02:00
NiccoloN 37a59054a5 better loop compaction in MaterializeMergeSchedule.cpp
Validate Operations / validate-operations (push) Has been cancelled
2026-06-03 16:01:19 +02:00
ilgeco 2a8faf9c6b Merge branch 'refactorone' of chef.heaplab.deib.polimi.it:nnicolosi/Raptor into refactorone 2026-06-03 13:49:42 +02:00
ilgeco 01b9d03fc6 Early warning on memory address 2026-06-03 13:49:39 +02:00
NiccoloN 501e6c76f3 better memory report
Validate Operations / validate-operations (push) Has been cancelled
capped vector allocations at u32::MAX in rust simulator
2026-06-03 13:48:42 +02:00
ilgeco 3c2667f11e Fix memory bug
Validate Operations / validate-operations (push) Has been cancelled
2026-06-03 12:59:58 +02:00
NiccoloN 0a5e73c3ea better transpose pattern and cleanup
Validate Operations / validate-operations (push) Has been cancelled
2026-06-03 12:26:31 +02:00
NiccoloN 636310d0cb add shared loop creation helpers
Validate Operations / validate-operations (push) Has been cancelled
add shared checked arithmetic helpers
refactor pim passes into Pim/Transforms
more robust memory coalescing pass
2026-06-01 16:49:06 +02:00
NiccoloN 356be6ccc2 uniquify constants produced by affine lowering
Validate Operations / validate-operations (push) Has been cancelled
2026-06-01 10:52:25 +02:00
NiccoloN b678e55d3c compact memory contiguity with for loops
Validate Operations / validate-operations (push) Has been cancelled
2026-05-31 18:47:59 +02:00
NiccoloN ab63498f3f normalize affine arithmetic helpers
Validate Operations / validate-operations (push) Has been cancelled
2026-05-30 16:37:28 +02:00
192 changed files with 10681 additions and 4131 deletions
+183 -65
View File
@@ -1,92 +1,210 @@
- 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
# 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
+2 -2
View File
@@ -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:
@@ -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)
+3 -1
View File
@@ -121,6 +121,8 @@ add_pim_library(OMPIMAccel
OMSpatialToPim OMSpatialToPim
OMPimCommon OMPimCommon
OMPimBufferization OMPimBufferization
OMPimStaticMemoryCoalescing OMPimMemoryCoalescing
OMPimHostConstantFolding
OMPimVerification
MLIRTensorInferTypeOpInterfaceImpl MLIRTensorInferTypeOpInterfaceImpl
) )
+4
View File
@@ -1,12 +1,15 @@
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/LoopUtils.cpp
IR/ShapeUtils.cpp IR/ShapeUtils.cpp
IR/SubviewUtils.cpp IR/SubviewUtils.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
@@ -19,6 +22,7 @@ add_pim_library(OMPimCommon
LINK_LIBS PUBLIC LINK_LIBS PUBLIC
MLIRLinalgDialect MLIRLinalgDialect
MLIRSCFDialect
onnx onnx
SpatialOps SpatialOps
PimOps PimOps
+22 -6
View File
@@ -69,6 +69,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>();
@@ -539,8 +549,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;
} }
@@ -651,12 +663,16 @@ llvm::FailureOr<CompiledAddressExpr> compileContiguousAddressExprImpl(mlir::Valu
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;
@@ -665,7 +681,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;
+182
View File
@@ -0,0 +1,182 @@
#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 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);
}
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
+55
View File
@@ -0,0 +1,55 @@
#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 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);
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
+83 -24
View File
@@ -1,32 +1,35 @@
#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 {
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) { 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 (isa<spatial::SpatCompute, spatial::SpatComputeBatch, pim::PimCoreOp, pim::PimCoreBatchOp>(current))
return current->getBlock();
if (auto funcOp = dyn_cast<func::FuncOp>(anchorOp)) if (auto funcOp = dyn_cast<func::FuncOp>(anchorOp))
return &funcOp.getBody().front(); return &funcOp.getBody().front();
if (auto funcOp = anchorOp->getParentOfType<func::FuncOp>())
return &funcOp.getBody().front();
if (auto moduleOp = dyn_cast<ModuleOp>(anchorOp)) if (auto moduleOp = dyn_cast<ModuleOp>(anchorOp))
return moduleOp.getBody(); return moduleOp.getBody();
if (auto funcOp = anchorOp->getParentOfType<func::FuncOp>())
return &funcOp.getBody().front();
if (auto moduleOp = anchorOp->getParentOfType<ModuleOp>()) if (auto moduleOp = anchorOp->getParentOfType<ModuleOp>())
return moduleOp.getBody(); return moduleOp.getBody();
return anchorOp->getBlock(); return anchorOp->getBlock();
@@ -75,24 +78,80 @@ Value getOrCreateIndexConstant(RewriterBase& rewriter, Operation* anchorOp, int6
return getOrCreateConstant(rewriter, anchorOp, builder.getIndexAttr(value), builder.getIndexType()); return getOrCreateConstant(rewriter, anchorOp, builder.getIndexAttr(value), builder.getIndexType());
} }
Value createAffineApplyOrFoldedConstant( void hoistAndUniquifyIndexConstants(func::FuncOp funcOp, RewriterBase& rewriter) {
RewriterBase& rewriter, Location loc, AffineMap map, ValueRange operands, Operation* anchorOp) { if (funcOp.getBody().empty())
SmallVector<Attribute> operandConstants; return;
operandConstants.reserve(operands.size());
for (Value operand : operands) { Block& entryBlock = funcOp.getBody().front();
APInt constantValue; DenseMap<int64_t, Value> canonicalByValue;
if (!matchPattern(operand, m_ConstantInt(&constantValue))) SmallVector<arith::ConstantOp> constants;
return affine::AffineApplyOp::create(rewriter, loc, map, operands).getResult();
operandConstants.push_back(rewriter.getIndexAttr(constantValue.getSExtValue())); funcOp.walk([&](arith::ConstantOp constantOp) {
if (!getIndexConstantValue(constantOp))
return;
constants.push_back(constantOp);
});
for (arith::ConstantOp constantOp : constants) {
auto value = getIndexConstantValue(constantOp);
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 getOrCreateIndexConstant(rewriter, anchorOp, constantResult.getInt()); 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
+8 -6
View File
@@ -1,11 +1,13 @@
#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* getConstantInsertionBlock(mlir::Operation* anchorOp); mlir::Block* getConstantInsertionBlock(mlir::Operation* anchorOp);
@@ -22,10 +24,10 @@ mlir::Value getOrCreateIndexConstant(mlir::OperationFolder& folder, mlir::Operat
mlir::Value getOrCreateIndexConstant(mlir::RewriterBase& rewriter, mlir::Operation* anchorOp, int64_t value); mlir::Value getOrCreateIndexConstant(mlir::RewriterBase& rewriter, mlir::Operation* anchorOp, int64_t value);
mlir::Value createAffineApplyOrFoldedConstant(mlir::RewriterBase& rewriter, void hoistAndUniquifyIndexConstants(mlir::func::FuncOp funcOp, mlir::RewriterBase& rewriter);
mlir::Location loc,
mlir::AffineMap map, std::optional<int64_t> matchConstantIndexValue(mlir::Value value);
mlir::ValueRange operands,
mlir::Operation* anchorOp); std::optional<int64_t> matchConstantIndexValue(mlir::OpFoldResult value);
} // namespace onnx_mlir } // namespace onnx_mlir
+96
View File
@@ -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
+30
View File
@@ -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
+62 -18
View File
@@ -47,6 +47,16 @@ 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));
} }
llvm::FailureOr<llvm::SmallVector<int64_t>> getStaticMemRefTypeStrides(mlir::MemRefType type) {
llvm::SmallVector<int64_t> strides;
int64_t offset = 0;
if (failed(type.getStridesAndOffset(strides, offset)))
return mlir::failure();
if (llvm::is_contained(strides, mlir::ShapedType::kDynamic))
return mlir::failure();
return strides;
}
template <typename VMMOpTy, typename ParentOpTy> template <typename VMMOpTy, typename ParentOpTy>
bool hasVmmWeightUse(ParentOpTy parentOp, unsigned weightIndex) { bool hasVmmWeightUse(ParentOpTy parentOp, unsigned weightIndex) {
auto weightArg = parentOp.getWeightArgument(weightIndex); auto weightArg = parentOp.getWeightArgument(weightIndex);
@@ -162,6 +172,11 @@ resolveWeightView(mlir::Operation* weightOwner, mlir::Value weight, const Static
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 {};
@@ -181,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);
@@ -202,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);
@@ -234,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;
} }
@@ -253,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();
@@ -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
+2
View File
@@ -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:
+4 -2
View File
@@ -5,16 +5,18 @@
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::string formatReportMemory(uint64_t bytes) { std::string formatReportMemory(uint64_t bytes) {
const char* units[] = {"B", "KB", "MB", "GB", "TB", "PB", "EB"}; const char* units[] = {"B", "KB", "MB", "GB", "TB", "PB", "EB"};
int i = 0; int i = 0;
+1
View File
@@ -11,6 +11,7 @@
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::string formatReportMemory(uint64_t bytes); std::string formatReportMemory(uint64_t bytes);
struct ReportField { struct ReportField {
+4 -1
View File
@@ -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
+4 -9
View File
@@ -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) {
+350 -71
View File
@@ -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;
} }
@@ -235,6 +420,7 @@ size_t PimAcceleratorMemory::getValueAddress(mlir::Value value,
if (failed(compiledExpr)) { if (failed(compiledExpr)) {
errs() << "Failed to compile contiguous address for value: "; errs() << "Failed to compile contiguous address for value: ";
value.print(errs()); value.print(errs());
errs() << " : " << value.getType();
errs() << "\n"; errs() << "\n";
llvm_unreachable("Failed to compile contiguous address"); llvm_unreachable("Failed to compile contiguous address");
} }
@@ -245,6 +431,7 @@ size_t PimAcceleratorMemory::getValueAddress(mlir::Value value,
if (failed(resolvedAddress)) { if (failed(resolvedAddress)) {
errs() << "Failed to evaluate contiguous address for value: "; errs() << "Failed to evaluate contiguous address for value: ";
value.print(errs()); value.print(errs());
errs() << " : " << value.getType();
errs() << "\n"; errs() << "\n";
if (auto* definingOp = value.getDefiningOp()) { if (auto* definingOp = value.getDefiningOp()) {
errs() << "Defining op:\n"; errs() << "Defining op:\n";
@@ -270,7 +457,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 +477,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 +506,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);
@@ -400,24 +594,24 @@ void PimCodeGen::genSetRegisterImmediateUnsigned(size_t registerNumber, size_t i
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 = static_cast<uint8_t>(registerNumber);
instruction.r2OrImm = static_cast<int32_t>(immediate); instruction.r2OrImm = pim::checkedI32OrCrash(immediate, "register immediate");
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 +629,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 +639,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 +655,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);
} }
@@ -493,11 +686,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");
} }
@@ -572,7 +769,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);
} }
@@ -587,7 +784,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);
} }
@@ -602,7 +799,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);
} }
@@ -617,7 +814,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);
} }
@@ -632,7 +829,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);
} }
@@ -647,7 +844,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);
} }
@@ -660,7 +857,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);
} }
@@ -673,7 +870,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);
} }
@@ -686,7 +883,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);
} }
@@ -699,8 +896,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);
} }
@@ -801,11 +997,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;
@@ -1226,7 +1455,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();
} }
@@ -1267,15 +1509,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);
@@ -1283,10 +1526,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);
@@ -1301,11 +1541,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());
@@ -1324,6 +1564,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;
@@ -1336,7 +1591,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];
@@ -1348,6 +1617,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;
} }
} }
@@ -1364,7 +1635,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);
@@ -1376,10 +1647,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);
} }
+35 -2
View File
@@ -5,12 +5,14 @@
#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 <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 +28,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 +57,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 +85,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 +108,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 +125,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 +150,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);
}; };
+16
View File
@@ -22,12 +22,28 @@ 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<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),
+8
View File
@@ -24,11 +24,19 @@ typedef enum {
MergeSchedulerPeft = 0, MergeSchedulerPeft = 0,
} PimMergeSchedulerType; } PimMergeSchedulerType;
typedef enum {
PimMemoryReportNone = 0,
PimMemoryReportSummary = 1,
PimMemoryReportFull = 2,
} PimMemoryReportLevel;
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<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;
+2 -2
View File
@@ -40,14 +40,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());
+733
View File
@@ -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;
}
+63
View File
@@ -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
+10 -5
View File
@@ -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
+8 -2
View File
@@ -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
@@ -22,6 +22,7 @@ add_pim_library(OMONNXToSpatial
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 Patterns/Tensor/Transpose.cpp
ONNXToSpatialPass.cpp ONNXToSpatialPass.cpp
@@ -12,6 +12,7 @@
#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/Conversion/ONNXToSpatial/CompileTime.hpp"
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp" #include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
@@ -180,8 +181,11 @@ auto createSpatComputeBatch(RewriterT& rewriter,
if (laneCount <= 0 || laneCount > std::numeric_limits<int32_t>::max()) if (laneCount <= 0 || laneCount > std::numeric_limits<int32_t>::max())
return mlir::FailureOr<spatial::SpatComputeBatch>(mlir::failure()); return mlir::FailureOr<spatial::SpatComputeBatch>(mlir::failure());
auto batchOp = spatial::SpatComputeBatch::create( auto laneCountAttr = pim::getCheckedI32Attr(rewriter, loc, laneCount, "spatial compute_batch lane count");
rewriter, loc, resultTypes, rewriter.getI32IntegerAttr(static_cast<int32_t>(laneCount)), weights, inputs); if (mlir::failed(laneCountAttr))
return mlir::FailureOr<spatial::SpatComputeBatch>(mlir::failure());
auto batchOp = spatial::SpatComputeBatch::create(rewriter, loc, resultTypes, *laneCountAttr, weights, inputs);
mlir::SmallVector<mlir::Type> blockArgTypes {rewriter.getIndexType()}; mlir::SmallVector<mlir::Type> blockArgTypes {rewriter.getIndexType()};
mlir::SmallVector<mlir::Location> blockArgLocs {loc}; mlir::SmallVector<mlir::Location> blockArgLocs {loc};
@@ -1,11 +1,6 @@
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "llvm/ADT/APInt.h"
#include <algorithm> #include <algorithm>
#include "IndexingUtils.hpp" #include "IndexingUtils.hpp"
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
using namespace mlir; using namespace mlir;
@@ -47,46 +42,4 @@ FailureOr<SmallVector<int64_t>> normalizeAxesChecked(std::optional<ArrayAttr> ax
return normalizedAxes; return normalizedAxes;
} }
Value createAffineApplyOrFoldedConstant(PatternRewriter& 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);
}
Value multiplyIndexByConstant(PatternRewriter& rewriter, Operation* anchorOp, Value value, int64_t multiplier) {
if (multiplier == 0)
return getOrCreateIndexConstant(rewriter, anchorOp, 0);
if (multiplier == 1)
return value;
MLIRContext* context = rewriter.getContext();
AffineExpr d0 = getAffineDimExpr(0, context);
return createAffineApplyOrFoldedConstant(rewriter, anchorOp->getLoc(), d0 * multiplier, ValueRange {value});
}
Value modIndexByConstant(PatternRewriter& rewriter, Location loc, Value value, int64_t divisor) {
if (divisor == 1)
return getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 0);
MLIRContext* context = rewriter.getContext();
AffineExpr d0 = getAffineDimExpr(0, context);
return createAffineApplyOrFoldedConstant(rewriter, loc, d0 % divisor, ValueRange {value});
}
Value floorDivIndexByConstant(PatternRewriter& rewriter, Location loc, Value value, int64_t divisor) {
if (divisor == 1)
return value;
MLIRContext* context = rewriter.getContext();
AffineExpr d0 = getAffineDimExpr(0, context);
return createAffineApplyOrFoldedConstant(rewriter, loc, d0.floorDiv(divisor), ValueRange {value});
}
Value getOrMaterializeIndexValue(PatternRewriter& rewriter, OpFoldResult value) {
if (auto attr = dyn_cast<Attribute>(value))
return getOrCreateIndexConstant(
rewriter, rewriter.getInsertionBlock()->getParentOp(), cast<IntegerAttr>(attr).getInt());
return cast<Value>(value);
}
} // namespace onnx_mlir } // namespace onnx_mlir
@@ -1,11 +1,7 @@
#pragma once #pragma once
#include "mlir/IR/AffineExpr.h"
#include "mlir/IR/BuiltinAttributes.h" #include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/Value.h"
#include "mlir/Interfaces/FoldInterfaces.h"
#include "mlir/Support/LogicalResult.h" #include "mlir/Support/LogicalResult.h"
#include "mlir/Transforms/DialectConversion.h"
#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/SmallVector.h"
@@ -21,21 +17,4 @@ int64_t normalizeIndex(int64_t index, int64_t dimSize);
mlir::FailureOr<llvm::SmallVector<int64_t>> normalizeAxesChecked(std::optional<mlir::ArrayAttr> axesAttr, int64_t rank); mlir::FailureOr<llvm::SmallVector<int64_t>> normalizeAxesChecked(std::optional<mlir::ArrayAttr> axesAttr, int64_t rank);
mlir::Value createAffineApplyOrFoldedConstant(mlir::PatternRewriter& rewriter,
mlir::Location loc,
mlir::AffineExpr expr,
mlir::ValueRange operands);
mlir::Value multiplyIndexByConstant(mlir::PatternRewriter& rewriter,
mlir::Operation* anchorOp,
mlir::Value value,
int64_t multiplier);
mlir::Value modIndexByConstant(mlir::PatternRewriter& rewriter, mlir::Location loc, mlir::Value value, int64_t divisor);
mlir::Value
floorDivIndexByConstant(mlir::PatternRewriter& rewriter, mlir::Location loc, mlir::Value value, int64_t divisor);
mlir::Value getOrMaterializeIndexValue(mlir::PatternRewriter& rewriter, mlir::OpFoldResult value);
} // namespace onnx_mlir } // namespace onnx_mlir
@@ -10,8 +10,6 @@
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.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"
#include "src/Dialect/ONNX/ONNXOps.hpp"
using namespace mlir; using namespace mlir;
@@ -68,14 +66,6 @@ FailureOr<SmallVector<int64_t>> getTransposePermutationChecked(std::optional<Arr
return permutation; return permutation;
} }
Value transposeMaybeInCompute(
Value value, RankedTensorType resultType, ArrayRef<int64_t> permutation, PatternRewriter& rewriter, Location loc) {
auto buildTranspose = [&](Value input) -> Value {
return ONNXTransposeOp::create(rewriter, loc, resultType, input, rewriter.getI64ArrayAttr(permutation)).getResult();
};
return materializeOrComputeUnary(value, resultType, rewriter, loc, buildTranspose);
}
SmallVector<OpFoldResult> getUnitStrides(PatternRewriter& rewriter, int64_t rank) { SmallVector<OpFoldResult> getUnitStrides(PatternRewriter& rewriter, int64_t rank) {
return SmallVector<OpFoldResult>(rank, rewriter.getIndexAttr(1)); return SmallVector<OpFoldResult>(rank, rewriter.getIndexAttr(1));
} }
@@ -78,12 +78,6 @@ llvm::SmallVector<int64_t> invertPermutation(mlir::ArrayRef<int64_t> permutation
mlir::FailureOr<llvm::SmallVector<int64_t>> getTransposePermutationChecked(std::optional<mlir::ArrayAttr> permAttr, mlir::FailureOr<llvm::SmallVector<int64_t>> getTransposePermutationChecked(std::optional<mlir::ArrayAttr> permAttr,
int64_t rank); int64_t rank);
mlir::Value transposeMaybeInCompute(mlir::Value value,
mlir::RankedTensorType resultType,
mlir::ArrayRef<int64_t> permutation,
mlir::PatternRewriter& rewriter,
mlir::Location loc);
llvm::SmallVector<mlir::OpFoldResult> getUnitStrides(mlir::PatternRewriter& rewriter, int64_t rank); llvm::SmallVector<mlir::OpFoldResult> getUnitStrides(mlir::PatternRewriter& rewriter, int64_t rank);
llvm::SmallVector<mlir::OpFoldResult> getZeroOffsets(mlir::PatternRewriter& rewriter, int64_t rank); llvm::SmallVector<mlir::OpFoldResult> getZeroOffsets(mlir::PatternRewriter& rewriter, int64_t rank);
@@ -11,6 +11,7 @@
#include <utility> #include <utility>
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp" #include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
#include "src/Accelerators/PIM/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"
@@ -27,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) {
@@ -8,7 +8,6 @@
#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"
@@ -114,21 +113,6 @@ 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,
@@ -140,6 +124,7 @@ void ONNXToSpatialPass::runOnOperation() {
target.addIllegalOp<ONNXMatMulOp>(); target.addIllegalOp<ONNXMatMulOp>();
target.addIllegalOp<ONNXTransposeOp>(); 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>();
@@ -153,7 +138,9 @@ void ONNXToSpatialPass::runOnOperation() {
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>();
@@ -165,10 +152,6 @@ void ONNXToSpatialPass::runOnOperation() {
return; return;
} }
RewritePatternSet transposePatterns(ctx);
populateTransposePatterns(transposePatterns, ctx);
walkAndApplyPatterns(moduleOp, std::move(transposePatterns));
ConversionTarget earlyPostTarget(*ctx); ConversionTarget earlyPostTarget(*ctx);
earlyPostTarget.addLegalDialect<spatial::SpatialDialect, earlyPostTarget.addLegalDialect<spatial::SpatialDialect,
ONNXDialect, ONNXDialect,
@@ -206,15 +189,14 @@ void ONNXToSpatialPass::runOnOperation() {
return; return;
} }
if (failed(verifyONNXToSpatial(*entryFunc))) {
moduleOp.emitError("ONNX-to-Spatial host legality verification failed");
signalPassFailure();
return;
}
populateEmptyFunction(*entryFunc); populateEmptyFunction(*entryFunc);
dumpModule(moduleOp, "spatial0"); dumpModule(moduleOp, "spatial0");
if (failed(verifyONNXToSpatial(*entryFunc))) {
moduleOp.emitError("ONNX-to-Spatial host legality verification failed");
signalPassFailure();
}
} }
std::unique_ptr<Pass> createONNXToSpatialPass() { return std::make_unique<ONNXToSpatialPass>(); } std::unique_ptr<Pass> createONNXToSpatialPass() { return std::make_unique<ONNXToSpatialPass>(); }
@@ -10,6 +10,7 @@ void populatePrePatterns(RewritePatternSet& patterns, MLIRContext* ctx) { popula
void populateConversionPatterns(RewritePatternSet& patterns, MLIRContext* ctx) { void populateConversionPatterns(RewritePatternSet& patterns, MLIRContext* ctx) {
populateGeneratedConversionPatterns(patterns, 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);
@@ -21,6 +22,7 @@ void populateConversionPatterns(RewritePatternSet& patterns, MLIRContext* 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); populateTransposePatterns(patterns, ctx);
} }
@@ -29,6 +29,7 @@ void populateConcatPatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext
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); void populateTransposePatterns(mlir::RewritePatternSet& patterns, mlir::MLIRContext* ctx);
File diff suppressed because it is too large Load Diff
@@ -189,6 +189,7 @@ struct DivToSpatialCompute : OpConversionPattern<ONNXDivOp> {
void populateElementwisePatterns(RewritePatternSet& patterns, MLIRContext* ctx) { void populateElementwisePatterns(RewritePatternSet& patterns, MLIRContext* ctx) {
patterns.add<BinaryElementwiseToSpatialCompute<ONNXAddOp, spatial::SpatVAddOp>>(ctx); patterns.add<BinaryElementwiseToSpatialCompute<ONNXAddOp, spatial::SpatVAddOp>>(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);
} }
@@ -14,6 +14,8 @@
#include <utility> #include <utility>
#include "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"
@@ -60,8 +62,11 @@ static Value createGemmBatchKOffset(
MLIRContext* context = rewriter.getContext(); MLIRContext* context = rewriter.getContext();
AffineExpr d0 = getAffineDimExpr(0, context); AffineExpr d0 = getAffineDimExpr(0, context);
return createAffineApplyOrFoldedConstant( 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,
@@ -75,8 +80,11 @@ static Value createGemmBatchHOffset(Value lane,
MLIRContext* context = rewriter.getContext(); MLIRContext* context = rewriter.getContext();
AffineExpr d0 = getAffineDimExpr(0, context); AffineExpr d0 = getAffineDimExpr(0, context);
return createAffineApplyOrFoldedConstant( 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 static Value
@@ -240,16 +248,16 @@ static Value createPaddedInputCompute(Value input,
return computeOp.getResult(0); return computeOp.getResult(0);
} }
static spatial::SpatComputeBatch createVmmBatch(Value a, static FailureOr<spatial::SpatComputeBatch> createVmmBatch(Value a,
Value b, Value b,
RankedTensorType aType, RankedTensorType aType,
RankedTensorType paddedBType, RankedTensorType paddedBType,
RankedTensorType partialPiecesType, RankedTensorType partialPiecesType,
int64_t numOutRows, int64_t numOutRows,
int64_t numKSlices, int64_t numKSlices,
int64_t numOutHSlices, int64_t numOutHSlices,
ConversionPatternRewriter& rewriter, ConversionPatternRewriter& rewriter,
Location loc) { Location loc) {
const int64_t laneCount = partialPiecesType.getDimSize(0); const int64_t laneCount = partialPiecesType.getDimSize(0);
auto batchOp = createSpatComputeBatch( auto batchOp = createSpatComputeBatch(
rewriter, rewriter,
@@ -259,7 +267,8 @@ static spatial::SpatComputeBatch createVmmBatch(Value a,
ValueRange {b}, ValueRange {b},
ValueRange {a}, ValueRange {a},
[&](detail::SpatComputeBatchBodyArgs args) { [&](detail::SpatComputeBatchBodyArgs args) {
Value row = onnx_mlir::modIndexByConstant(rewriter, loc, args.lane, numOutRows); Value row =
onnx_mlir::affineModConst(rewriter, loc, args.lane, numOutRows, rewriter.getInsertionBlock()->getParentOp());
Value kOffset = createGemmBatchKOffset(args.lane, numOutRows, numKSlices, rewriter, loc); Value kOffset = createGemmBatchKOffset(args.lane, numOutRows, numKSlices, rewriter, loc);
Value hOffset = createGemmBatchHOffset(args.lane, numOutRows, numKSlices, numOutHSlices, rewriter, loc); Value hOffset = createGemmBatchHOffset(args.lane, numOutRows, numKSlices, numOutHSlices, rewriter, loc);
@@ -286,7 +295,8 @@ static spatial::SpatComputeBatch createVmmBatch(Value a,
createParallelInsertSliceIntoBatchOutput( createParallelInsertSliceIntoBatchOutput(
rewriter, loc, piece, args.outputs.front(), pieceOffsets, pieceSizes, unitStrides); rewriter, loc, piece, args.outputs.front(), pieceOffsets, pieceSizes, unitStrides);
}); });
assert(succeeded(batchOp) && "expected Gemm VMM batch construction to succeed"); if (failed(batchOp))
return failure();
return *batchOp; return *batchOp;
} }
@@ -297,7 +307,8 @@ createDynamicGemmBatchRow(Value lane, int64_t numOutCols, ConversionPatternRewri
MLIRContext* context = rewriter.getContext(); MLIRContext* context = rewriter.getContext();
AffineExpr d0 = getAffineDimExpr(0, context); AffineExpr d0 = getAffineDimExpr(0, context);
return createAffineApplyOrFoldedConstant(rewriter, loc, d0.floorDiv(numOutCols), ValueRange {lane}); return createOrFoldAffineApply(
rewriter, loc, d0.floorDiv(numOutCols), ValueRange {lane}, rewriter.getInsertionBlock()->getParentOp());
} }
static Value extractDynamicGemmBColumn( static Value extractDynamicGemmBColumn(
@@ -320,14 +331,6 @@ static Value extractDynamicGemmBColumn(
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)};
@@ -407,15 +410,14 @@ 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);
@@ -429,13 +431,13 @@ static spatial::SpatComputeBatch createVvdmulBatch(Value a,
ValueRange {a, b}, ValueRange {a, b},
[&](detail::SpatComputeBatchBodyArgs args) { [&](detail::SpatComputeBatchBodyArgs args) {
Value row = createDynamicGemmBatchRow(args.lane, numOutCols, rewriter, loc); Value row = createDynamicGemmBatchRow(args.lane, numOutCols, rewriter, loc);
Value column = onnx_mlir::modIndexByConstant(rewriter, loc, args.lane, numOutCols); Value column =
onnx_mlir::affineModConst(rewriter, loc, args.lane, numOutCols, rewriter.getInsertionBlock()->getParentOp());
auto vectorType = RankedTensorType::get({1, reductionSize}, aType.getElementType()); auto vectorType = RankedTensorType::get({1, reductionSize}, aType.getElementType());
auto scalarType = RankedTensorType::get({1, 1}, outType.getElementType()); auto scalarType = RankedTensorType::get({1, 1}, outType.getElementType());
Value aVector = extractDynamicGemmRowVector(args.inputs[0], row, vectorType, rewriter, loc); Value aVector = extractDynamicGemmRowVector(args.inputs[0], row, vectorType, rewriter, loc);
Value bVector = bAlreadyTransposed ? extractTransposedBRow(args.inputs[1], column, vectorType, rewriter, loc) Value bVector = extractDynamicGemmBColumn(args.inputs[1], column, vectorType, rewriter, loc);
: extractDynamicGemmBColumn(args.inputs[1], column, vectorType, rewriter, loc);
Value scalar = spatial::SpatVVDMulOp::create(rewriter, loc, scalarType, aVector, bVector).getResult(); Value scalar = spatial::SpatVVDMulOp::create(rewriter, loc, scalarType, aVector, bVector).getResult();
SmallVector<OpFoldResult> outputOffsets {args.lane, rewriter.getIndexAttr(0)}; SmallVector<OpFoldResult> outputOffsets {args.lane, rewriter.getIndexAttr(0)};
@@ -444,26 +446,27 @@ static spatial::SpatComputeBatch createVvdmulBatch(Value a,
createParallelInsertSliceIntoBatchOutput( createParallelInsertSliceIntoBatchOutput(
rewriter, loc, scalar, args.outputs.front(), outputOffsets, scalarSizes, unitStrides); rewriter, loc, scalar, args.outputs.front(), outputOffsets, scalarSizes, unitStrides);
}); });
assert(succeeded(batchOp) && "expected Gemm VVDMul batch construction to succeed"); if (failed(batchOp))
return failure();
return *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());
@@ -471,39 +474,50 @@ static spatial::SpatCompute createDynamicGemmOutputCompute(Value scalarPieces,
Value c0 = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 0); Value c0 = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 0);
Value c1 = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 1); Value c1 = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 1);
Value cLaneCount = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 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 = onnx_mlir::modIndexByConstant(rewriter, loc, lane, numOutCols);
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));
}); });
} }
@@ -515,8 +529,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 createAffineApplyOrFoldedConstant( return createOrFoldAffineApply(rewriter,
rewriter, loc, d0 * (numKSlices * numOutRows) + kSlice * numOutRows, ValueRange {hSlice}); loc,
d0 * (numKSlices * numOutRows) + kSlice * numOutRows,
ValueRange {hSlice},
rewriter.getInsertionBlock()->getParentOp());
} }
static Value extractReductionPiece(Value partialPiecesArg, static Value extractReductionPiece(Value partialPiecesArg,
@@ -565,85 +582,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 = onnx_mlir::multiplyIndexByConstant( Value hOffset = onnx_mlir::affineMulConst(
rewriter, rewriter.getInsertionBlock()->getParentOp(), hSlice, crossbarSize.getValue()); rewriter, loc, hSlice, crossbarSize.getValue(), rewriter.getInsertionBlock()->getParentOp());
if (biasArg) { if (biasArg) {
SmallVector<OpFoldResult> biasOffsets {rewriter.getIndexAttr(0), hOffset}; SmallVector<OpFoldResult> biasOffsets {rewriter.getIndexAttr(0), hOffset};
Value biasSlice = Value biasSlice =
tensor::ExtractSliceOp::create(rewriter, loc, pieceType, biasArg, biasOffsets, pieceSizes, unitStrides) tensor::ExtractSliceOp::create(rewriter, loc, pieceType, biasArg, biasOffsets, pieceSizes, unitStrides)
.getResult(); .getResult();
reduced = spatial::SpatVAddOp::create(rewriter, loc, pieceType, reduced, biasSlice).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 = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 0); .getResult();
paddedOutput = buildOutputSlice(outputInit, hSlice); }
} spatial::SpatYieldOp::create(rewriter, loc, result);
else { return success();
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 = 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;
} }
@@ -666,11 +690,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());
@@ -701,6 +720,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);
@@ -724,10 +757,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();
} }
@@ -739,11 +770,14 @@ LogicalResult GemmToSpatialComputes::matchAndRewrite(ONNXGemmOp gemmOp,
} }
auto scalarPiecesType = RankedTensorType::get({laneCount64, 1}, outType.getElementType()); auto scalarPiecesType = RankedTensorType::get({laneCount64, 1}, outType.getElementType());
auto batchOp = auto batchOp = createVvdmulBatch(a, b, aType, bType, scalarPiecesType, outType, rewriter, loc);
createVvdmulBatch(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();
} }
@@ -755,13 +789,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 = transposeMaybeInCompute(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();
@@ -818,10 +845,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
@@ -6,7 +6,10 @@
#include <algorithm> #include <algorithm>
#include <numeric> #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/Patterns.hpp" #include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
@@ -18,6 +21,85 @@ using namespace mlir;
namespace onnx_mlir { namespace onnx_mlir {
namespace { namespace {
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;
normalizedAxes.reserve(axes.size());
for (int64_t axis : axes) {
auto normalizedAxis = normalizeAxisChecked(axis, rank);
if (failed(normalizedAxis))
return failure();
normalizedAxes.push_back(*normalizedAxis);
}
llvm::sort(normalizedAxes);
normalizedAxes.erase(std::unique(normalizedAxes.begin(), normalizedAxes.end()), normalizedAxes.end());
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) {
@@ -82,7 +164,7 @@ computeLaneIndex(Value lane, int64_t stride, int64_t dimSize, ConversionPatternR
expr = expr.floorDiv(stride); expr = expr.floorDiv(stride);
if (dimSize != 1) if (dimSize != 1)
expr = expr % dimSize; expr = expr % dimSize;
return createAffineApplyOrFoldedConstant(rewriter, loc, expr, ValueRange {lane}); return createOrFoldAffineApply(rewriter, loc, expr, ValueRange {lane}, rewriter.getInsertionBlock()->getParentOp());
} }
static FailureOr<Value> buildReduceMeanKeepdimsBatch(Value input, static FailureOr<Value> buildReduceMeanKeepdimsBatch(Value input,
@@ -237,14 +319,8 @@ static Value squeezeReducedAxes(Value keepdimsValue,
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);
getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), 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();
@@ -256,11 +332,13 @@ 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());
@@ -271,10 +349,18 @@ struct ReduceMeanToSpatialCompute : OpConversionPattern<ONNXReduceMeanV13Op> {
return success(); return success();
} }
auto axes = normalizeAxesChecked(std::optional<ArrayAttr>(reduceMeanOp.getAxesAttr()), inputType.getRank()); auto semantics = getReduceMeanSemantics(reduceMeanOp, adaptor, inputType.getRank());
if (failed(axes)) if (failed(semantics))
return failure(); return rewriter.notifyMatchFailure(reduceMeanOp, "requires compile-time constant, in-range ReduceMean axes");
SmallVector<bool> reducedAxes = buildReducedAxesMask(*axes, inputType.getRank()); 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();
@@ -294,7 +380,7 @@ struct ReduceMeanToSpatialCompute : OpConversionPattern<ONNXReduceMeanV13Op> {
Value reducedKeepdims = Value reducedKeepdims =
buildKeepdimsFromLanePackedBatch(*lanePackedKeepdims, keepdimsType, compactKeptType, reducedAxes, rewriter, loc); buildKeepdimsFromLanePackedBatch(*lanePackedKeepdims, keepdimsType, compactKeptType, reducedAxes, rewriter, loc);
if (reduceMeanOp.getKeepdims() != 0) { if (semantics->keepdims != 0) {
rewriter.replaceOp(reduceMeanOp, reducedKeepdims); rewriter.replaceOp(reduceMeanOp, reducedKeepdims);
return success(); return success();
} }
@@ -308,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"
@@ -275,86 +276,102 @@ struct PoolToSpatialComputeBase : public OpConversionPattern<PoolOp> {
Value cStrideHeight = getOrCreateIndexConstant(rewriter, anchorOp, strideHeight); Value cStrideHeight = getOrCreateIndexConstant(rewriter, anchorOp, strideHeight);
Value cStrideWidth = getOrCreateIndexConstant(rewriter, anchorOp, strideWidth); 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 = getOrCreateIndexConstant(rewriter, anchorOp, 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 = getOrCreateIndexConstant(rewriter, anchorOp, 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 = materializeTileTensor(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 = materializeTileTensor(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))
@@ -3,6 +3,7 @@
#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/Patterns.hpp" #include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp" #include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
@@ -42,13 +43,13 @@ 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);
@@ -57,38 +58,50 @@ static Value buildLoopSoftmaxNest(Value input,
Value c1 = getOrCreateIndexConstant(rewriter, anchorOp, 1); Value c1 = getOrCreateIndexConstant(rewriter, anchorOp, 1);
Value cUpper = getOrCreateIndexConstant(rewriter, anchorOp, inputType.getDimSize(axis)); 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> {
@@ -108,7 +121,10 @@ struct SoftmaxToSpatialCompute : OpConversionPattern<ONNXSoftmaxOp> {
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;
@@ -121,9 +137,17 @@ struct SoftmaxToSpatialCompute : OpConversionPattern<ONNXSoftmaxOp> {
auto transposedType = RankedTensorType::get( auto transposedType = RankedTensorType::get(
permuteShape(inputType.getShape(), permutation), inputType.getElementType(), inputType.getEncoding()); permuteShape(inputType.getShape(), permutation), inputType.getElementType(), inputType.getEncoding());
Value transposedInput = transposeMaybeInCompute(input, transposedType, permutation, rewriter, softmaxOp.getLoc()); Value transposedInput =
Value transposedResult = createLoopSoftmaxCompute(transposedInput, rewriter, softmaxOp.getLoc()); ONNXTransposeOp::create(
result = transposeMaybeInCompute(transposedResult, inputType, inversePermutation, rewriter, softmaxOp.getLoc()); rewriter, softmaxOp.getLoc(), transposedType, input, rewriter.getI64ArrayAttr(permutation))
.getResult();
auto transposedResult = createLoopSoftmaxCompute(transposedInput, rewriter, softmaxOp.getLoc());
if (failed(transposedResult))
return failure();
result =
ONNXTransposeOp::create(
rewriter, softmaxOp.getLoc(), inputType, *transposedResult, rewriter.getI64ArrayAttr(inversePermutation))
.getResult();
} }
rewriter.replaceOp(softmaxOp, result); rewriter.replaceOp(softmaxOp, result);
@@ -9,6 +9,7 @@
#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/SmallVector.h"
#include "src/Accelerators/PIM/Common/IR/WeightUtils.hpp" #include "src/Accelerators/PIM/Common/IR/WeightUtils.hpp"
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/WeightMaterialization.hpp" #include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/WeightMaterialization.hpp"
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp" #include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp" #include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
@@ -192,13 +193,12 @@ struct PromoteWeightLikeComputeBatchInputsPattern : OpRewritePattern<spatial::Sp
rewriter.setInsertionPointAfter(compute); rewriter.setInsertionPointAfter(compute);
auto newCompute = auto laneCountAttr = pim::getCheckedI32Attr(
spatial::SpatComputeBatch::create(rewriter, rewriter, compute, static_cast<uint64_t>(compute.getLaneCount()), "promoted compute_batch lane count");
compute.getLoc(), if (failed(laneCountAttr))
compute.getResultTypes(), return failure();
rewriter.getI32IntegerAttr(static_cast<int32_t>(compute.getLaneCount())), auto newCompute = spatial::SpatComputeBatch::create(
promoted->newWeights, rewriter, compute.getLoc(), compute.getResultTypes(), *laneCountAttr, promoted->newWeights, promoted->newInputs);
promoted->newInputs);
auto laneArg = compute.getLaneArgument(); auto laneArg = compute.getLaneArgument();
if (!laneArg) if (!laneArg)
return rewriter.notifyMatchFailure(compute, "missing compute_batch lane block argument"); return rewriter.notifyMatchFailure(compute, "missing compute_batch lane block argument");
@@ -5,6 +5,7 @@
#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/Patterns.hpp" #include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp"
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp" #include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
@@ -26,11 +27,11 @@ static Value buildNearestAsymmetricIndex(
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);
@@ -48,54 +49,94 @@ static Value buildNearestResizeLoop(Value input,
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> {
@@ -120,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
@@ -15,6 +15,10 @@ using namespace mlir;
namespace onnx_mlir { namespace onnx_mlir {
namespace { namespace {
static bool isInsideSpatialComputeRegion(Operation* op) {
return op->getParentOfType<spatial::SpatCompute>() || op->getParentOfType<spatial::SpatComputeBatch>();
}
static Value createTransposeInit(Value input, static Value createTransposeInit(Value input,
RankedTensorType resultType, RankedTensorType resultType,
ArrayRef<int64_t> permutation, ArrayRef<int64_t> permutation,
@@ -102,10 +106,22 @@ struct TransposeToLinalgTranspose : OpConversionPattern<ONNXTransposeOp> {
return success(); return success();
} }
} }
Value init = createTransposeInit(adaptor.getData(), resultType, *permutation, rewriter, transposeOp.getLoc());
Value transposed = auto buildTranspose = [&](Value input) -> Value {
linalg::TransposeOp::create(rewriter, transposeOp.getLoc(), adaptor.getData(), init, *permutation).getResult()[0]; Value init = createTransposeInit(input, resultType, *permutation, rewriter, transposeOp.getLoc());
rewriter.replaceOp(transposeOp, transposed); 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(); return success();
} }
}; };
@@ -10,6 +10,7 @@
#include "Conversion/SpatialToPim/SpatialToPimPass.hpp" #include "Conversion/SpatialToPim/SpatialToPimPass.hpp"
#include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.hpp" #include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.hpp"
#include "src/Accelerators/PIM/Common/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"
@@ -25,14 +26,21 @@ static bool isUsedOnlyAsExplicitHostOperand(Value value) {
}); });
} }
static SmallVector<int32_t> getPimCoreIdsForBatchOp(spatial::SpatComputeBatch computeBatchOp, size_t& fallbackCoreId) { static FailureOr<SmallVector<int32_t>> getPimCoreIdsForBatchOp(spatial::SpatComputeBatch computeBatchOp,
size_t& fallbackCoreId) {
if (auto coreIdsAttr = computeBatchOp->getAttrOfType<DenseI32ArrayAttr>(onnx_mlir::kCoreIdsAttrName)) if (auto coreIdsAttr = computeBatchOp->getAttrOfType<DenseI32ArrayAttr>(onnx_mlir::kCoreIdsAttrName))
return SmallVector<int32_t>(coreIdsAttr.asArrayRef().begin(), coreIdsAttr.asArrayRef().end()); return SmallVector<int32_t>(coreIdsAttr.asArrayRef().begin(), coreIdsAttr.asArrayRef().end());
SmallVector<int32_t> coreIds; SmallVector<int32_t> coreIds;
coreIds.reserve(static_cast<size_t>(computeBatchOp.getLaneCount())); coreIds.reserve(static_cast<size_t>(computeBatchOp.getLaneCount()));
for (uint32_t lane = 0; lane < computeBatchOp.getLaneCount(); ++lane) for (uint32_t lane = 0; lane < computeBatchOp.getLaneCount(); ++lane) {
coreIds.push_back(static_cast<int32_t>(fallbackCoreId++)); auto checkedCoreId =
pim::checkedI32(static_cast<uint64_t>(fallbackCoreId), computeBatchOp, "fallback spatial compute_batch core id");
if (failed(checkedCoreId))
return failure();
coreIds.push_back(*checkedCoreId);
++fallbackCoreId;
}
return coreIds; return coreIds;
} }
@@ -102,21 +110,24 @@ 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 = getPimCoreIdsForBatchOp(computeBatchOp, coreId);
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;
if (computeBatchOp.getNumResults() != 0) { if (computeBatchOp.getNumResults() != 0) {
@@ -160,14 +171,11 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatCompute
auto newArgType = cast<ShapedType>(newArg.getType()); auto newArgType = cast<ShapedType>(newArg.getType());
auto outputBuffer = createEmptyTensorFromShaped(rewriter, loc, newArgType); auto outputBuffer = createEmptyTensorFromShaped(rewriter, loc, newArgType);
Value zeroOffset = getOrCreateIndexConstant(rewriter, coreBatchOp.getOperation(), 0); Value zeroOffset = getOrCreateIndexConstant(rewriter, coreBatchOp.getOperation(), 0);
auto copied = pim::PimMemCopyHostToDevOp::create(rewriter, auto sizeAttr = getTensorSizeInBytesAttr(rewriter, coreBatchOp.getOperation(), newArg);
loc, if (failed(sizeAttr))
outputBuffer.getType(), return failure();
zeroOffset, auto copied = pim::PimMemCopyHostToDevOp::create(
zeroOffset, rewriter, loc, outputBuffer.getType(), zeroOffset, zeroOffset, outputBuffer, newArg, *sizeAttr)
outputBuffer,
newArg,
getTensorSizeInBytesAttr(rewriter, newArg))
.getOutput(); .getOutput();
mapper.map(*oldArg, copied); mapper.map(*oldArg, copied);
} }
@@ -209,6 +217,9 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatCompute
auto hostTargetType = cast<ShapedType>(hostTarget.getType()); auto hostTargetType = cast<ShapedType>(hostTarget.getType());
Value hostTargetOffset = createHostTargetOffset(rewriter, insertSlice, hostTargetType, mapper); Value hostTargetOffset = createHostTargetOffset(rewriter, insertSlice, hostTargetType, mapper);
Value zeroOffset = getOrCreateIndexConstant(rewriter, coreBatchOp.getOperation(), 0); 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(),
@@ -216,7 +227,7 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeBatchOp(spatial::SpatCompute
zeroOffset, zeroOffset,
hostTarget, hostTarget,
mappedSource, mappedSource,
getTensorSizeInBytesAttr(rewriter, mappedSource)); *sizeAttr);
} }
continue; continue;
} }
@@ -232,15 +243,13 @@ 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);
Value zeroOffset = getOrCreateIndexConstant(rewriter, coreBatchOp.getOperation(), 0); Value zeroOffset = getOrCreateIndexConstant(rewriter, coreBatchOp.getOperation(), 0);
auto copied = pim::PimMemCopyHostToDevOp::create(rewriter, auto sizeAttr = getTensorSizeInBytesAttr(rewriter, coreBatchOp.getOperation(), clonedTensor);
loc, if (failed(sizeAttr))
outputBuffer.getType(), return failure();
zeroOffset, auto copied =
zeroOffset, pim::PimMemCopyHostToDevOp::create(
outputBuffer, rewriter, loc, outputBuffer.getType(), zeroOffset, zeroOffset, outputBuffer, clonedTensor, *sizeAttr)
clonedTensor, .getOutput();
getTensorSizeInBytesAttr(rewriter, clonedTensor))
.getOutput();
mapper.map(toTensorOp.getResult(), copied); mapper.map(toTensorOp.getResult(), copied);
continue; continue;
} }
+6 -2
View File
@@ -5,14 +5,18 @@
#include <cassert> #include <cassert>
#include "Common.hpp" #include "Common.hpp"
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
using namespace llvm; using namespace llvm;
using namespace mlir; using namespace mlir;
namespace onnx_mlir { namespace onnx_mlir {
IntegerAttr getTensorSizeInBytesAttr(Builder& builder, mlir::Value value) { FailureOr<IntegerAttr> getTensorSizeInBytesAttr(Builder& builder, Operation* anchor, mlir::Value value) {
return builder.getI32IntegerAttr(static_cast<int32_t>(getShapedTypeSizeInBytes(cast<ShapedType>(value.getType())))); auto byteSize = pim::getCheckedShapedTypeSizeInBytes(cast<ShapedType>(value.getType()), anchor, "tensor byte size");
if (failed(byteSize))
return failure();
return pim::getCheckedI32Attr(builder, anchor, *byteSize, "tensor byte size");
} }
Operation* getEarliestUserWithinBlock(mlir::Value value) { Operation* getEarliestUserWithinBlock(mlir::Value value) {
+3 -1
View File
@@ -1,12 +1,14 @@
#pragma once #pragma once
#include "mlir/Dialect/Tensor/IR/Tensor.h" #include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/Support/LogicalResult.h"
#include "src/Accelerators/PIM/Common/PimCommon.hpp" #include "src/Accelerators/PIM/Common/PimCommon.hpp"
namespace onnx_mlir { namespace onnx_mlir {
mlir::IntegerAttr getTensorSizeInBytesAttr(mlir::Builder& builder, mlir::Value value); mlir::FailureOr<mlir::IntegerAttr>
getTensorSizeInBytesAttr(mlir::Builder& builder, mlir::Operation* anchor, mlir::Value value);
template <class T> template <class T>
size_t rangeLength(const mlir::iterator_range<T> range) { size_t rangeLength(const mlir::iterator_range<T> range) {
@@ -9,6 +9,7 @@
#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/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"
#include "src/Dialect/ONNX/ONNXOps.hpp" #include "src/Dialect/ONNX/ONNXOps.hpp"
@@ -54,10 +55,15 @@ cloneMappedHelperOperands(Operation* op, IRMapping& mapping, IRRewriter& rewrite
} }
} }
static int32_t getPimCoreIdForComputeOp(spatial::SpatCompute computeOp, size_t& fallbackCoreId) { static FailureOr<int32_t> getPimCoreIdForComputeOp(spatial::SpatCompute computeOp, size_t& fallbackCoreId) {
if (auto spatialCoreIdAttr = computeOp->getAttrOfType<IntegerAttr>(onnx_mlir::kCoreIdAttrName)) if (auto spatialCoreIdAttr = computeOp->getAttrOfType<IntegerAttr>(onnx_mlir::kCoreIdAttrName))
return static_cast<int32_t>(spatialCoreIdAttr.getInt()); return pim::checkedI32(spatialCoreIdAttr.getInt(), computeOp, "spatial compute core id");
return static_cast<int32_t>(fallbackCoreId++); auto checkedCoreId =
pim::checkedI32(static_cast<uint64_t>(fallbackCoreId), computeOp, "fallback spatial compute core id");
if (failed(checkedCoreId))
return failure();
++fallbackCoreId;
return *checkedCoreId;
} }
static LogicalResult collectHelperComputeChain(spatial::SpatCompute computeOp, static LogicalResult collectHelperComputeChain(spatial::SpatCompute computeOp,
@@ -163,10 +169,12 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeOp(spatial::SpatCompute comp
rewriter.setInsertionPoint(getEarliestUserWithinBlock(*blockArg)); rewriter.setInsertionPoint(getEarliestUserWithinBlock(*blockArg));
auto outputType = cast<ShapedType>(blockArg->getType()); auto outputType = cast<ShapedType>(blockArg->getType());
auto outputBuffer = createEmptyTensorFromShaped(rewriter, receiveOp.getLoc(), outputType); auto outputBuffer = createEmptyTensorFromShaped(rewriter, receiveOp.getLoc(), outputType);
auto sizeAttr = getTensorSizeInBytesAttr(rewriter, *blockArg); auto sizeAttr = getTensorSizeInBytesAttr(rewriter, computeOp.getOperation(), *blockArg);
if (failed(sizeAttr))
return failure();
Value received = Value received =
PimReceiveOp::create( PimReceiveOp::create(
rewriter, receiveOp.getLoc(), outputBuffer.getType(), outputBuffer, sizeAttr, receiveOp.getSourceCoreId()) rewriter, receiveOp.getLoc(), outputBuffer.getType(), outputBuffer, *sizeAttr, receiveOp.getSourceCoreId())
.getOutput(); .getOutput();
blockArg->replaceAllUsesWith(received); blockArg->replaceAllUsesWith(received);
markOpToRemove(receiveOp); markOpToRemove(receiveOp);
@@ -206,8 +214,13 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeOp(spatial::SpatCompute comp
if (!computeOp.getWeights().empty()) if (!computeOp.getWeights().empty())
computeWeights.append(computeOp.getWeights().begin(), computeOp.getWeights().end()); computeWeights.append(computeOp.getWeights().begin(), computeOp.getWeights().end());
rewriter.setInsertionPointAfter(computeOp); rewriter.setInsertionPointAfter(computeOp);
auto coreOp = PimCoreOp::create( auto checkedCoreId = getPimCoreIdForComputeOp(computeOp, coreId);
rewriter, loc, ValueRange(computeWeights), rewriter.getI32IntegerAttr(getPimCoreIdForComputeOp(computeOp, coreId))); if (failed(checkedCoreId))
return failure();
auto coreIdAttr = pim::getCheckedI32Attr(rewriter, computeOp, static_cast<int64_t>(*checkedCoreId), "pim core id");
if (failed(coreIdAttr))
return failure();
auto coreOp = PimCoreOp::create(rewriter, loc, ValueRange(computeWeights), *coreIdAttr);
rewriter.setInsertionPointToStart(&block); rewriter.setInsertionPointToStart(&block);
auto& coreOpBlocks = coreOp.getBody().getBlocks(); auto& coreOpBlocks = coreOp.getBody().getBlocks();
for (auto [inputIndex, input] : llvm::enumerate(computeOp.getInputs())) { for (auto [inputIndex, input] : llvm::enumerate(computeOp.getInputs())) {
@@ -226,6 +239,9 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeOp(spatial::SpatCompute comp
if (!inputType) if (!inputType)
return computeOp.emitOpError("expected shaped compute input during pim.core lowering"); return computeOp.emitOpError("expected shaped compute input during pim.core lowering");
auto outputBuffer = createEmptyTensorFromShaped(rewriter, loc, inputType); auto outputBuffer = createEmptyTensorFromShaped(rewriter, loc, inputType);
auto sizeAttr = getTensorSizeInBytesAttr(rewriter, computeOp.getOperation(), input);
if (failed(sizeAttr))
return failure();
auto copied = auto copied =
PimMemCopyHostToDevOp::create(rewriter, PimMemCopyHostToDevOp::create(rewriter,
loc, loc,
@@ -234,7 +250,7 @@ LogicalResult raptor::SpatialToPimPass::lowerComputeOp(spatial::SpatCompute comp
getOrCreateIndexConstant(constantFolder, outputBuffer.getOperation(), 0), getOrCreateIndexConstant(constantFolder, outputBuffer.getOperation(), 0),
outputBuffer, outputBuffer,
input, input,
getTensorSizeInBytesAttr(rewriter, input)) *sizeAttr)
.getOutput(); .getOutput();
blockArg->replaceAllUsesWith(copied); blockArg->replaceAllUsesWith(copied);
} }
@@ -14,8 +14,10 @@ struct ChannelSendLowering : OpRewritePattern<spatial::SpatChannelSendOp> {
using OpRewritePattern::OpRewritePattern; using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(spatial::SpatChannelSendOp op, PatternRewriter& rewriter) const override { LogicalResult matchAndRewrite(spatial::SpatChannelSendOp op, PatternRewriter& rewriter) const override {
pim::PimSendOp::create( auto sizeAttr = getTensorSizeInBytesAttr(rewriter, op.getOperation(), op.getInput());
rewriter, op.getLoc(), op.getInput(), getTensorSizeInBytesAttr(rewriter, op.getInput()), op.getTargetCoreId()); if (failed(sizeAttr))
return failure();
pim::PimSendOp::create(rewriter, op.getLoc(), op.getInput(), *sizeAttr, op.getTargetCoreId());
rewriter.eraseOp(op); rewriter.eraseOp(op);
return success(); return success();
} }
@@ -32,12 +34,11 @@ struct ChannelReceiveLowering : OpRewritePattern<spatial::SpatChannelReceiveOp>
auto outputType = cast<ShapedType>(op.getResult().getType()); auto outputType = cast<ShapedType>(op.getResult().getType());
Value outputBuffer = Value outputBuffer =
tensor::EmptyOp::create(rewriter, op.getLoc(), outputType.getShape(), outputType.getElementType()).getResult(); tensor::EmptyOp::create(rewriter, op.getLoc(), outputType.getShape(), outputType.getElementType()).getResult();
Value received = pim::PimReceiveOp::create(rewriter, auto sizeAttr = getTensorSizeInBytesAttr(rewriter, op.getOperation(), op.getResult());
op.getLoc(), if (failed(sizeAttr))
op.getResult().getType(), return failure();
outputBuffer, Value received = pim::PimReceiveOp::create(
getTensorSizeInBytesAttr(rewriter, op.getResult()), rewriter, op.getLoc(), op.getResult().getType(), outputBuffer, *sizeAttr, op.getSourceCoreId())
op.getSourceCoreId())
.getOutput(); .getOutput();
rewriter.replaceOp(op, received); rewriter.replaceOp(op, received);
return success(); return success();
@@ -12,6 +12,7 @@
#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/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"
#include "src/Dialect/ONNX/ONNXOps.hpp" #include "src/Dialect/ONNX/ONNXOps.hpp"
@@ -71,6 +72,20 @@ static SmallVector<int64_t> expandFlatElementIndex(int64_t flatIndex, ArrayRef<i
return indices; return indices;
} }
static FailureOr<int64_t>
getCheckedByteOffset(int64_t elementOffset, size_t elementSize, Operation* anchor, StringRef fieldName) {
if (elementOffset < 0) {
anchor->emitOpError() << fieldName << " requires a nonnegative element offset";
return failure();
}
auto byteOffset =
pim::checkedMul(static_cast<uint64_t>(elementOffset), static_cast<uint64_t>(elementSize), anchor, fieldName);
if (failed(byteOffset))
return failure();
return pim::checkedCast<int64_t>(*byteOffset, anchor, fieldName);
}
static LogicalResult collectHelperComputeChain(spatial::SpatCompute computeOp, static LogicalResult collectHelperComputeChain(spatial::SpatCompute computeOp,
SmallVectorImpl<Operation*>& helperChain) { SmallVectorImpl<Operation*>& helperChain) {
if (computeOp.getInputs().size() != 1 || computeOp.getNumResults() != 1) if (computeOp.getInputs().size() != 1 || computeOp.getNumResults() != 1)
@@ -360,18 +375,72 @@ static void cloneHelperChain(Value sourceValue,
} }
} }
static Value emitHostCopy(IRRewriter& rewriter, static bool isHostStaticReturnValue(Value value) {
Location loc, llvm::SmallPtrSet<Operation*, 8> visited;
Value outputTensor, while (Operation* definingOp = value.getDefiningOp()) {
Value sourceValue, if (!visited.insert(definingOp).second)
int32_t hostTargetOffset, return false;
int32_t deviceSourceOffset, if (isa<arith::ConstantOp>(definingOp) || definingOp->hasTrait<OpTrait::ConstantLike>())
int32_t sizeInBytes, return true;
OperationFolder& constantFolder) { if (!isReturnHelperChainOp(definingOp) || definingOp->getNumOperands() != 1)
return false;
value = definingOp->getOperand(0);
}
return false;
}
static FailureOr<Value>
materializeHostStaticReturnValue(IRRewriter& rewriter, Value value, OperationFolder& constantFolder) {
llvm::SmallVector<Operation*> chain;
llvm::SmallPtrSet<Operation*, 8> visited;
while (Operation* definingOp = value.getDefiningOp()) {
if (!visited.insert(definingOp).second)
return failure();
chain.push_back(definingOp);
if (isa<arith::ConstantOp>(definingOp) || definingOp->hasTrait<OpTrait::ConstantLike>())
break;
if (!isReturnHelperChainOp(definingOp) || definingOp->getNumOperands() != 1)
return failure();
value = definingOp->getOperand(0);
}
if (chain.empty())
return failure();
IRMapping mapping;
Value clonedValue;
for (Operation* op : llvm::reverse(chain)) {
if (auto constantOp = dyn_cast<arith::ConstantOp>(op)) {
clonedValue = getOrCreateConstantLike(constantFolder, constantOp);
mapping.map(op->getResult(0), clonedValue);
continue;
}
Operation* clonedOp = rewriter.clone(*op, mapping);
for (auto [originalResult, newResult] : llvm::zip(op->getResults(), clonedOp->getResults()))
mapping.map(originalResult, newResult);
clonedValue = clonedOp->getResult(0);
rewriter.setInsertionPointAfter(clonedOp);
}
return clonedValue;
}
static FailureOr<Value> emitHostCopy(IRRewriter& rewriter,
Location loc,
Value outputTensor,
Value sourceValue,
int64_t hostTargetOffset,
int64_t deviceSourceOffset,
uint64_t sizeInBytes,
OperationFolder& constantFolder) {
Operation* anchorOp = sourceValue.getDefiningOp() ? sourceValue.getDefiningOp() : outputTensor.getDefiningOp(); Operation* anchorOp = sourceValue.getDefiningOp() ? sourceValue.getDefiningOp() : outputTensor.getDefiningOp();
assert(anchorOp && "expected a concrete op anchor for return-path host copy constants"); assert(anchorOp && "expected a concrete op anchor for return-path host copy constants");
Value hostTargetOffsetValue = getOrCreateIndexConstant(constantFolder, anchorOp, hostTargetOffset); Value hostTargetOffsetValue = getOrCreateIndexConstant(constantFolder, anchorOp, hostTargetOffset);
Value deviceSourceOffsetValue = getOrCreateIndexConstant(constantFolder, anchorOp, deviceSourceOffset); Value deviceSourceOffsetValue = getOrCreateIndexConstant(constantFolder, anchorOp, deviceSourceOffset);
auto sizeAttr = pim::getCheckedI32Attr(rewriter, anchorOp, sizeInBytes, "return-path host copy byte size");
if (failed(sizeAttr))
return failure();
return PimMemCopyDevToHostOp::create(rewriter, return PimMemCopyDevToHostOp::create(rewriter,
loc, loc,
outputTensor.getType(), outputTensor.getType(),
@@ -379,7 +448,7 @@ static Value emitHostCopy(IRRewriter& rewriter,
deviceSourceOffsetValue, deviceSourceOffsetValue,
outputTensor, outputTensor,
sourceValue, sourceValue,
rewriter.getI32IntegerAttr(sizeInBytes)) *sizeAttr)
.getOutput(); .getOutput();
} }
@@ -426,25 +495,45 @@ raptor::SpatialToPimPass::ReturnPathLoweringResult raptor::SpatialToPimPass::low
OperationFolder constantFolder(producerOp->getContext()); OperationFolder constantFolder(producerOp->getContext());
auto storedTensorType = cast<TensorType>(storedValue.getType()); auto storedTensorType = cast<TensorType>(storedValue.getType());
auto materializeDirectHostReturn = [&](size_t returnIndex,
Value sourceValue,
ArrayRef<Operation*> helperChain) -> ReturnPathLoweringResult {
rewriter.setInsertionPointAfter(producerOp);
auto hostStaticValue = materializeHostStaticReturnValue(rewriter, sourceValue, constantFolder);
if (failed(hostStaticValue))
return ReturnPathLoweringResult::Failure;
Value hostReturnValue = *hostStaticValue;
if (!helperChain.empty())
cloneHelperChain(hostReturnValue, helperChain, rewriter, constantFolder, hostReturnValue);
outputTensors[returnIndex] =
[hostReturnValue](IRRewriter& rewriter, Location loc) -> Value { return hostReturnValue; };
return ReturnPathLoweringResult::Handled;
};
if (auto returnUse = analyzeReturnUse(producedValue)) { if (auto returnUse = analyzeReturnUse(producedValue)) {
if (isHostStaticReturnValue(storedValue)) {
for (Operation* op : returnUse->helperChain)
markOpToRemove(op);
return materializeDirectHostReturn(returnUse->returnIndex, storedValue, returnUse->helperChain);
}
Value currentStoredValue = storedValue; Value currentStoredValue = storedValue;
cloneHelperChain(storedValue, returnUse->helperChain, rewriter, constantFolder, currentStoredValue); cloneHelperChain(storedValue, returnUse->helperChain, rewriter, constantFolder, currentStoredValue);
for (Operation* op : returnUse->helperChain) for (Operation* op : returnUse->helperChain)
markOpToRemove(op); markOpToRemove(op);
auto storedType = cast<ShapedType>(currentStoredValue.getType()); auto storedType = cast<ShapedType>(currentStoredValue.getType());
size_t elementSize = getElementTypeSizeInBytes(storedType.getElementType()); auto byteSize = pim::getCheckedShapedTypeSizeInBytes(storedType, producerOp, "return-path host copy byte size");
if (failed(byteSize))
return ReturnPathLoweringResult::Failure;
if (auto storedOp = currentStoredValue.getDefiningOp()) if (auto storedOp = currentStoredValue.getDefiningOp())
rewriter.setInsertionPointAfter(storedOp); rewriter.setInsertionPointAfter(storedOp);
Value outputTensor = outputTensors[returnUse->returnIndex](rewriter, loc); Value outputTensor = outputTensors[returnUse->returnIndex](rewriter, loc);
emitHostCopy(rewriter, auto copied = emitHostCopy(rewriter, loc, outputTensor, currentStoredValue, 0, 0, *byteSize, constantFolder);
loc, if (failed(copied))
outputTensor, return ReturnPathLoweringResult::Failure;
currentStoredValue,
0,
0,
static_cast<int32_t>(storedType.getNumElements() * elementSize),
constantFolder);
return ReturnPathLoweringResult::Handled; return ReturnPathLoweringResult::Handled;
} }
@@ -455,23 +544,27 @@ raptor::SpatialToPimPass::ReturnPathLoweringResult raptor::SpatialToPimPass::low
if (isa<func::ReturnOp>(resultUser)) { if (isa<func::ReturnOp>(resultUser)) {
size_t resultIndexInReturn = resultUse.getOperandNumber(); size_t resultIndexInReturn = resultUse.getOperandNumber();
size_t elementSize = getElementTypeSizeInBytes(storedTensorType.getElementType()); if (isHostStaticReturnValue(storedValue))
return materializeDirectHostReturn(resultIndexInReturn, storedValue, {});
auto byteSize =
pim::getCheckedShapedTypeSizeInBytes(storedTensorType, producerOp, "return-path host copy byte size");
if (failed(byteSize))
return ReturnPathLoweringResult::Failure;
rewriter.setInsertionPointAfterValue(storedValue); rewriter.setInsertionPointAfterValue(storedValue);
Value outputTensor = outputTensors[resultIndexInReturn](rewriter, loc); Value outputTensor = outputTensors[resultIndexInReturn](rewriter, loc);
emitHostCopy(rewriter, auto copied = emitHostCopy(rewriter, loc, outputTensor, storedValue, 0, 0, *byteSize, constantFolder);
loc, if (failed(copied))
outputTensor, return ReturnPathLoweringResult::Failure;
storedValue,
0,
0,
static_cast<int32_t>(storedTensorType.getNumElements() * elementSize),
constantFolder);
return ReturnPathLoweringResult::Handled; return ReturnPathLoweringResult::Handled;
} }
} }
if (auto concatReturnUse = analyzeConcatReturnUse(producedValue)) { if (auto concatReturnUse = analyzeConcatReturnUse(producedValue)) {
size_t elementSize = getElementTypeSizeInBytes(storedTensorType.getElementType()); size_t elementSize = getElementTypeSizeInBytes(storedTensorType.getElementType());
auto storedByteSize =
pim::getCheckedShapedTypeSizeInBytes(storedTensorType, producerOp, "concat return-path copy byte size");
if (failed(storedByteSize))
return ReturnPathLoweringResult::Failure;
for (Operation* concatOp : concatReturnUse->concatChain) for (Operation* concatOp : concatReturnUse->concatChain)
markOpToRemove(concatOp); markOpToRemove(concatOp);
@@ -480,14 +573,13 @@ raptor::SpatialToPimPass::ReturnPathLoweringResult raptor::SpatialToPimPass::low
Value outputTensor = outputTensors[concatReturnUse->returnIndex](rewriter, loc); Value outputTensor = outputTensors[concatReturnUse->returnIndex](rewriter, loc);
auto outputType = cast<ShapedType>(outputTensor.getType()); auto outputType = cast<ShapedType>(outputTensor.getType());
int64_t flatOffset = computeFlatElementIndex(concatReturnUse->sliceOffsets, outputType.getShape()); int64_t flatOffset = computeFlatElementIndex(concatReturnUse->sliceOffsets, outputType.getShape());
emitHostCopy(rewriter, auto hostOffset = getCheckedByteOffset(flatOffset, elementSize, producerOp, "concat return-path host offset");
loc, if (failed(hostOffset))
outputTensor, return ReturnPathLoweringResult::Failure;
storedValue, auto copied =
static_cast<int32_t>(flatOffset * elementSize), emitHostCopy(rewriter, loc, outputTensor, storedValue, *hostOffset, 0, *storedByteSize, constantFolder);
0, if (failed(copied))
static_cast<int32_t>(storedTensorType.getNumElements() * elementSize), return ReturnPathLoweringResult::Failure;
constantFolder);
return ReturnPathLoweringResult::Handled; return ReturnPathLoweringResult::Handled;
} }
@@ -531,14 +623,18 @@ raptor::SpatialToPimPass::ReturnPathLoweringResult raptor::SpatialToPimPass::low
rewriter.setInsertionPointAfter(elementSlice); rewriter.setInsertionPointAfter(elementSlice);
int64_t destinationFlatOffset = computeFlatElementIndex(destinationIndices, outputType.getShape()); int64_t destinationFlatOffset = computeFlatElementIndex(destinationIndices, outputType.getShape());
outputTensor = emitHostCopy(rewriter, auto hostOffset =
loc, getCheckedByteOffset(destinationFlatOffset, elementSize, producerOp, "concat helper return-path host offset");
outputTensor, if (failed(hostOffset))
elementSlice.getResult(), return ReturnPathLoweringResult::Failure;
static_cast<int32_t>(destinationFlatOffset * elementSize), auto elementByteSize = pim::checkedCast<uint64_t>(elementSize, producerOp, "return-path scalar copy byte size");
0, if (failed(elementByteSize))
static_cast<int32_t>(elementSize), return ReturnPathLoweringResult::Failure;
constantFolder); auto copied = emitHostCopy(
rewriter, loc, outputTensor, elementSlice.getResult(), *hostOffset, 0, *elementByteSize, constantFolder);
if (failed(copied))
return ReturnPathLoweringResult::Failure;
outputTensor = *copied;
} }
return ReturnPathLoweringResult::Handled; return ReturnPathLoweringResult::Handled;
} }
@@ -27,6 +27,12 @@ def spatToPimVVAdd : Pat<
(NativeCodeCall<"onnx_mlir::getBestOutputTensorFromOperandsOrAllocate($_builder, $0.getDefiningOp())"> $srcOpRes)) (NativeCodeCall<"onnx_mlir::getBestOutputTensorFromOperandsOrAllocate($_builder, $0.getDefiningOp())"> $srcOpRes))
>; >;
def spatToPimVVSub : Pat<
(SpatVSubOp:$srcOpRes $a, $b),
(PimVVSubOp $a, $b,
(NativeCodeCall<"onnx_mlir::getBestOutputTensorFromOperandsOrAllocate($_builder, $0.getDefiningOp())"> $srcOpRes))
>;
def spatToPimVVMul : Pat< def spatToPimVVMul : Pat<
(SpatVMulOp:$srcOpRes $a, $b), (SpatVMulOp:$srcOpRes $a, $b),
(PimVVMulOp $a, $b, (PimVVMulOp $a, $b,
@@ -25,7 +25,9 @@
#include <cassert> #include <cassert>
#include <utility> #include <utility>
#include "Common/IR/ConstantUtils.hpp"
#include "Common/PimCommon.hpp" #include "Common/PimCommon.hpp"
#include "Common/Support/CheckedArithmetic.hpp"
#include "Conversion/ONNXToSpatial/Common/Common.hpp" #include "Conversion/ONNXToSpatial/Common/Common.hpp"
#include "Conversion/SpatialToPim/Common.hpp" #include "Conversion/SpatialToPim/Common.hpp"
#include "Conversion/SpatialToPim/Patterns.hpp" #include "Conversion/SpatialToPim/Patterns.hpp"
@@ -74,21 +76,28 @@ static memref::GlobalOp getOrCreateZeroGlobal(IRRewriter& rewriter, Location loc
IntegerAttr {}); IntegerAttr {});
} }
static Value createZeroedDeviceHVector(IRRewriter& rewriter, static FailureOr<Value> createZeroedDeviceHVector(IRRewriter& rewriter,
Location loc, Location loc,
RankedTensorType tensorType, RankedTensorType tensorType,
OperationFolder& constantFolder) { OperationFolder& constantFolder) {
auto outputBuffer = createEmptyTensorFromShaped(rewriter, loc, tensorType); auto outputBuffer = createEmptyTensorFromShaped(rewriter, loc, tensorType);
auto zeroGlobal = getOrCreateZeroGlobal(rewriter, loc, tensorType); auto zeroGlobal = getOrCreateZeroGlobal(rewriter, loc, tensorType);
auto zeroValue = memref::GetGlobalOp::create(rewriter, loc, zeroGlobal.getType(), zeroGlobal.getName()); auto zeroValue = memref::GetGlobalOp::create(rewriter, loc, zeroGlobal.getType(), zeroGlobal.getName());
auto zeroIndex = getOrCreateIndexConstant(constantFolder, outputBuffer.getOperation(), 0); auto zeroIndex = getOrCreateIndexConstant(constantFolder, outputBuffer.getOperation(), 0);
auto sizeAttr = rewriter.getI32IntegerAttr(static_cast<int32_t>(getShapedTypeSizeInBytes(tensorType))); auto byteSize =
pim::getCheckedShapedTypeSizeInBytes(tensorType, outputBuffer.getOperation(), "host-to-device zero copy byte size");
if (failed(byteSize))
return failure();
auto sizeAttr =
pim::getCheckedI32Attr(rewriter, outputBuffer.getOperation(), *byteSize, "host-to-device zero copy byte size");
if (failed(sizeAttr))
return failure();
return PimMemCopyHostToDevOp::create( return PimMemCopyHostToDevOp::create(
rewriter, loc, tensorType, zeroIndex, zeroIndex, outputBuffer, zeroValue, sizeAttr) rewriter, loc, tensorType, zeroIndex, zeroIndex, outputBuffer, zeroValue, *sizeAttr)
.getOutput(); .getOutput();
} }
static Value static FailureOr<Value>
padHVectorInputToCrossbarSize(IRRewriter& rewriter, Location loc, Value vector, OperationFolder& constantFolder) { padHVectorInputToCrossbarSize(IRRewriter& rewriter, Location loc, Value vector, OperationFolder& constantFolder) {
auto vectorType = cast<RankedTensorType>(vector.getType()); auto vectorType = cast<RankedTensorType>(vector.getType());
ArrayRef<int64_t> shape = vectorType.getShape(); ArrayRef<int64_t> shape = vectorType.getShape();
@@ -100,10 +109,18 @@ padHVectorInputToCrossbarSize(IRRewriter& rewriter, Location loc, Value vector,
auto paddedType = RankedTensorType::get( auto paddedType = RankedTensorType::get(
{shape[0], static_cast<int64_t>(crossbarSize)}, vectorType.getElementType(), vectorType.getEncoding()); {shape[0], static_cast<int64_t>(crossbarSize)}, vectorType.getElementType(), vectorType.getEncoding());
Value zeroed = createZeroedDeviceHVector(rewriter, loc, paddedType, constantFolder); auto zeroed = createZeroedDeviceHVector(rewriter, loc, paddedType, constantFolder);
auto zeroAttr = rewriter.getI32IntegerAttr(0); if (failed(zeroed))
auto sizeAttr = rewriter.getI32IntegerAttr(static_cast<int32_t>(getShapedTypeSizeInBytes(vectorType))); return failure();
return PimMemCopyOp::create(rewriter, loc, paddedType, zeroed, vector, zeroAttr, zeroAttr, sizeAttr).getOutput(); Value zeroIndex = getOrCreateIndexConstant(constantFolder, zeroed->getDefiningOp(), 0);
auto byteSize =
pim::getCheckedShapedTypeSizeInBytes(vectorType, zeroed->getDefiningOp(), "device padding copy byte size");
if (failed(byteSize))
return failure();
auto sizeAttr = pim::getCheckedI32Attr(rewriter, zeroed->getDefiningOp(), *byteSize, "device padding copy byte size");
if (failed(sizeAttr))
return failure();
return PimMemCopyOp::create(rewriter, loc, paddedType, zeroIndex, zeroIndex, *zeroed, vector, *sizeAttr).getOutput();
} }
void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() { void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
@@ -233,7 +250,11 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
} }
} }
enlargeVMMOutTensorsToCrossbarSize(funcOp, rewriter); if (failed(enlargeVMMOutTensorsToCrossbarSize(funcOp, rewriter))) {
funcOp.emitOpError("failed to enlarge VMM output tensors to crossbar size");
signalPassFailure();
return;
}
replaceReturnWithOutputBuffers(returnOp, rewriter); replaceReturnWithOutputBuffers(returnOp, rewriter);
eraseOpsToRemove(); eraseOpsToRemove();
@@ -264,12 +285,15 @@ void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() {
signalPassFailure(); signalPassFailure();
return; return;
} }
hoistAndUniquifyIndexConstants(funcOp, rewriter);
// Dump to file for debug // Dump to file for debug
dumpModule(moduleOp, "pim0"); dumpModule(moduleOp, "pim0");
} }
void raptor::SpatialToPimPass::enlargeVMMOutTensorsToCrossbarSize(func::FuncOp funcOp, IRRewriter& rewriter) { LogicalResult raptor::SpatialToPimPass::enlargeVMMOutTensorsToCrossbarSize(func::FuncOp funcOp, IRRewriter& rewriter) {
OperationFolder constantFolder(funcOp.getContext()); OperationFolder constantFolder(funcOp.getContext());
bool hasFailure = false;
funcOp.walk([&](PimVMMOp vmmOp) { funcOp.walk([&](PimVMMOp vmmOp) {
auto outputType = cast<RankedTensorType>(vmmOp.getOutput().getType()); auto outputType = cast<RankedTensorType>(vmmOp.getOutput().getType());
ArrayRef<int64_t> outputShape = outputType.getShape(); ArrayRef<int64_t> outputShape = outputType.getShape();
@@ -277,19 +301,23 @@ void raptor::SpatialToPimPass::enlargeVMMOutTensorsToCrossbarSize(func::FuncOp f
assert(outputShape[1] <= static_cast<int64_t>(crossbarSize) && "output width must fit in one crossbar"); assert(outputShape[1] <= static_cast<int64_t>(crossbarSize) && "output width must fit in one crossbar");
rewriter.setInsertionPoint(vmmOp); rewriter.setInsertionPoint(vmmOp);
Value paddedInput = padHVectorInputToCrossbarSize(rewriter, vmmOp.getLoc(), vmmOp.getInput(), constantFolder); auto paddedInput = padHVectorInputToCrossbarSize(rewriter, vmmOp.getLoc(), vmmOp.getInput(), constantFolder);
if (failed(paddedInput)) {
hasFailure = true;
return WalkResult::interrupt();
}
auto paddedOutputType = RankedTensorType::get( auto paddedOutputType = RankedTensorType::get(
{outputShape[0], static_cast<int64_t>(crossbarSize)}, outputType.getElementType(), outputType.getEncoding()); {outputShape[0], static_cast<int64_t>(crossbarSize)}, outputType.getElementType(), outputType.getEncoding());
Value paddedOutputBuffer = outputShape[1] == static_cast<int64_t>(crossbarSize) Value paddedOutputBuffer = outputShape[1] == static_cast<int64_t>(crossbarSize)
? vmmOp.getOutputBuffer() ? vmmOp.getOutputBuffer()
: createEmptyTensorFromShaped(rewriter, vmmOp.getLoc(), paddedOutputType).getResult(); : createEmptyTensorFromShaped(rewriter, vmmOp.getLoc(), paddedOutputType).getResult();
vmmOp.getInputMutable().assign(paddedInput); vmmOp.getInputMutable().assign(*paddedInput);
vmmOp.getOutputBufferMutable().assign(paddedOutputBuffer); vmmOp.getOutputBufferMutable().assign(paddedOutputBuffer);
vmmOp.getOutput().setType(paddedOutputType); vmmOp.getOutput().setType(paddedOutputType);
if (outputShape[1] == static_cast<int64_t>(crossbarSize)) if (outputShape[1] == static_cast<int64_t>(crossbarSize))
return; return WalkResult::advance();
SmallVector<OpFoldResult> offsets = {rewriter.getIndexAttr(0), rewriter.getIndexAttr(0)}; SmallVector<OpFoldResult> offsets = {rewriter.getIndexAttr(0), rewriter.getIndexAttr(0)};
SmallVector<OpFoldResult> sizes = {rewriter.getIndexAttr(outputShape[0]), rewriter.getIndexAttr(outputShape[1])}; SmallVector<OpFoldResult> sizes = {rewriter.getIndexAttr(outputShape[0]), rewriter.getIndexAttr(outputShape[1])};
@@ -299,13 +327,16 @@ void raptor::SpatialToPimPass::enlargeVMMOutTensorsToCrossbarSize(func::FuncOp f
tensor::ExtractSliceOp::create(rewriter, vmmOp.getLoc(), outputType, vmmOp.getOutput(), offsets, sizes, strides); tensor::ExtractSliceOp::create(rewriter, vmmOp.getLoc(), outputType, vmmOp.getOutput(), offsets, sizes, strides);
SmallPtrSet<Operation*, 2> exceptions = {vmmOp, sliceOp}; SmallPtrSet<Operation*, 2> exceptions = {vmmOp, sliceOp};
vmmOp.getOutput().replaceAllUsesExcept(sliceOp.getResult(), exceptions); vmmOp.getOutput().replaceAllUsesExcept(sliceOp.getResult(), exceptions);
return WalkResult::advance();
}); });
return success(!hasFailure);
} }
LogicalResult raptor::SpatialToPimPass::allocateAndInitializeCoreLocalVariables(func::FuncOp funcOp, LogicalResult raptor::SpatialToPimPass::allocateAndInitializeCoreLocalVariables(func::FuncOp funcOp,
IRRewriter& rewriter) { IRRewriter& rewriter) {
Location loc = funcOp.getLoc(); Location loc = funcOp.getLoc();
OperationFolder constantFolder(funcOp.getContext()); OperationFolder constantFolder(funcOp.getContext());
bool hasFailure = false;
auto insertMemCopyHostToDev = [&](Value inputTensor, int64_t elementsOffset) { auto insertMemCopyHostToDev = [&](Value inputTensor, int64_t elementsOffset) {
auto tensorType = cast<ShapedType>(inputTensor.getType()); auto tensorType = cast<ShapedType>(inputTensor.getType());
@@ -316,17 +347,28 @@ LogicalResult raptor::SpatialToPimPass::allocateAndInitializeCoreLocalVariables(
rewriter.setInsertionPointAfter(inputTensor.getDefiningOp()); rewriter.setInsertionPointAfter(inputTensor.getDefiningOp());
auto deviceTensor = tensor::EmptyOp::create(rewriter, loc, tensorType.getShape(), elementType); auto deviceTensor = tensor::EmptyOp::create(rewriter, loc, tensorType.getShape(), elementType);
auto offsetBytes = pim::checkedMul(
static_cast<size_t>(elementsOffset), elementByteSize, deviceTensor.getOperation(), "host input byte offset");
auto byteSize =
pim::getCheckedShapedTypeSizeInBytes(tensorType, deviceTensor.getOperation(), "host input copy byte size");
auto sizeAttr =
succeeded(byteSize)
? pim::getCheckedI32Attr(rewriter, deviceTensor.getOperation(), *byteSize, "host input copy byte size")
: FailureOr<IntegerAttr>(failure());
if (failed(offsetBytes) || failed(sizeAttr)) {
hasFailure = true;
return;
}
auto memCopyHostToDevOp = PimMemCopyHostToDevOp::create( auto memCopyHostToDevOp = PimMemCopyHostToDevOp::create(
rewriter, rewriter,
loc, loc,
tensorType, tensorType,
getOrCreateIndexConstant(constantFolder, deviceTensor.getOperation(), 0), getOrCreateIndexConstant(constantFolder, deviceTensor.getOperation(), 0),
getOrCreateIndexConstant( getOrCreateIndexConstant(constantFolder, deviceTensor.getOperation(), static_cast<int64_t>(*offsetBytes)),
constantFolder, deviceTensor.getOperation(), static_cast<int64_t>(elementsOffset * elementByteSize)),
deviceTensor, deviceTensor,
inputTensor, inputTensor,
rewriter.getI32IntegerAttr(static_cast<int32_t>(tensorType.getNumElements() * elementByteSize))); *sizeAttr);
rewriter.replaceAllUsesExcept(inputTensor, memCopyHostToDevOp.getResult(), {memCopyHostToDevOp}); rewriter.replaceAllUsesExcept(inputTensor, memCopyHostToDevOp.getResult(), {memCopyHostToDevOp});
}; };
@@ -344,7 +386,7 @@ LogicalResult raptor::SpatialToPimPass::allocateAndInitializeCoreLocalVariables(
} }
} }
return success(); return success(!hasFailure);
} }
void raptor::SpatialToPimPass::markOpToRemove(Operation* op) { void raptor::SpatialToPimPass::markOpToRemove(Operation* op) {
@@ -64,7 +64,7 @@ private:
void markOpToRemove(mlir::Operation* op); void markOpToRemove(mlir::Operation* op);
void eraseOpsToRemove(); void eraseOpsToRemove();
void enlargeVMMOutTensorsToCrossbarSize(mlir::func::FuncOp funcOp, mlir::IRRewriter& rewriter); mlir::LogicalResult enlargeVMMOutTensorsToCrossbarSize(mlir::func::FuncOp funcOp, mlir::IRRewriter& rewriter);
}; };
} // namespace raptor } // namespace raptor
+3 -1
View File
@@ -2,7 +2,9 @@ add_onnx_mlir_dialect(Pim pim)
add_onnx_mlir_dialect_doc(pim Pim.td) add_onnx_mlir_dialect_doc(pim Pim.td)
add_subdirectory(Transforms/Bufferization) add_subdirectory(Transforms/Bufferization)
add_subdirectory(Transforms/StaticMemoryCoalescing) add_subdirectory(Transforms/MemoryCoalescing)
add_subdirectory(Transforms/HostConstantFolding)
add_subdirectory(Transforms/Verification)
add_pim_library(PimOps add_pim_library(PimOps
PimOps.hpp PimOps.hpp
+5 -3
View File
@@ -176,10 +176,10 @@ def PimMemCopyOp : PimOp<"memcp", [DestinationStyleOpInterface]> {
let summary = "Copy a memory region within the same memory space"; let summary = "Copy a memory region within the same memory space";
let arguments = (ins let arguments = (ins
Index:$targetOffset,
Index:$sourceOffset,
PimTensor:$target, PimTensor:$target,
PimTensor:$source, PimTensor:$source,
I32Attr:$targetOffset,
I32Attr:$sourceOffset,
I32Attr:$size I32Attr:$size
); );
@@ -194,7 +194,9 @@ def PimMemCopyOp : PimOp<"memcp", [DestinationStyleOpInterface]> {
}]; }];
let assemblyFormat = [{ let assemblyFormat = [{
`(` $target `,` $source `)` attr-dict `:` `(` type($target) `,` type($source) `)` `->` type($output) `[` $targetOffset `,` $sourceOffset `]`
`(` $target `,` $source `)` attr-dict
`:` type($target) `,` type($source) `->` type($output)
}]; }];
} }
@@ -3,36 +3,50 @@
#include "src/Accelerators/PIM/Common/IR/AddressAnalysis.hpp" #include "src/Accelerators/PIM/Common/IR/AddressAnalysis.hpp"
#include "src/Accelerators/PIM/Common/PimCommon.hpp" #include "src/Accelerators/PIM/Common/PimCommon.hpp"
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp" #include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
#include "src/Accelerators/PIM/Dialect/Pim/Transforms/Bufferization/BufferizationUtils.hpp" #include "src/Accelerators/PIM/Dialect/Pim/Transforms/Bufferization/BufferizationUtils.hpp"
#include "src/Accelerators/PIM/Dialect/Pim/Transforms/Bufferization/Common.hpp"
using namespace mlir; using namespace mlir;
using namespace bufferization; using namespace bufferization;
namespace onnx_mlir::pim { namespace onnx_mlir::pim {
Value materializeContiguousInputMemRef(Value memrefValue, Location loc, RewriterBase& rewriter) { FailureOr<Value> materializeContiguousInputMemRef(Value memrefValue, Location loc, RewriterBase& rewriter) {
if (succeeded(resolveContiguousAddress(memrefValue)) || succeeded(compileContiguousAddressExpr(memrefValue))) bool isContiguous =
succeeded(resolveContiguousAddress(memrefValue)) || succeeded(compileContiguousAddressExpr(memrefValue));
if (isContiguous && isDeviceLocalPimAddress(memrefValue))
return memrefValue; return memrefValue;
auto shapedType = cast<ShapedType>(memrefValue.getType()); auto shapedType = cast<ShapedType>(memrefValue.getType());
auto contiguousType = MemRefType::get(shapedType.getShape(), shapedType.getElementType()); auto contiguousType = MemRefType::get(shapedType.getShape(), shapedType.getElementType());
Value contiguousBuffer = memref::AllocOp::create(rewriter, loc, contiguousType); Value contiguousBuffer = memref::AllocOp::create(rewriter, loc, contiguousType);
auto sizeInBytes = getShapedTypeSizeInBytes(shapedType); auto sizeInBytes =
getCheckedShapedTypeSizeInBytes(shapedType, contiguousBuffer.getDefiningOp(), "contiguous copy byte size");
if (failed(sizeInBytes))
return failure();
Value zeroOffset = getOrCreateIndexConstant(rewriter, contiguousBuffer.getDefiningOp(), 0);
auto sizeAttr =
getCheckedI32Attr(rewriter, contiguousBuffer.getDefiningOp(), *sizeInBytes, "contiguous copy byte size");
if (failed(sizeAttr))
return failure();
return PimMemCopyOp::create(rewriter, if (isHostBackedPimAddress(memrefValue)) {
loc, return PimMemCopyHostToDevOp::create(
contiguousType, rewriter, loc, contiguousType, zeroOffset, zeroOffset, contiguousBuffer, memrefValue, *sizeAttr)
contiguousBuffer, .getOutput();
memrefValue, }
rewriter.getI32IntegerAttr(0),
rewriter.getI32IntegerAttr(0), return PimMemCopyOp::create(
rewriter.getI32IntegerAttr(sizeInBytes)) rewriter, loc, contiguousType, zeroOffset, zeroOffset, contiguousBuffer, memrefValue, *sizeAttr)
.getOutput(); .getOutput();
} }
Value allocateContiguousResultMemRefLike(Value memrefValue, Location loc, RewriterBase& rewriter) { Value allocateContiguousResultMemRefLike(Value memrefValue, Location loc, RewriterBase& rewriter) {
if (succeeded(resolveContiguousAddress(memrefValue))) bool isContiguous =
succeeded(resolveContiguousAddress(memrefValue)) || succeeded(compileContiguousAddressExpr(memrefValue));
if (isContiguous && isDeviceLocalPimAddress(memrefValue))
return memrefValue; return memrefValue;
auto shapedType = cast<ShapedType>(memrefValue.getType()); auto shapedType = cast<ShapedType>(memrefValue.getType());
@@ -5,7 +5,8 @@
namespace onnx_mlir::pim { namespace onnx_mlir::pim {
mlir::Value materializeContiguousInputMemRef(mlir::Value memrefValue, mlir::Location loc, mlir::RewriterBase& rewriter); llvm::FailureOr<mlir::Value>
materializeContiguousInputMemRef(mlir::Value memrefValue, mlir::Location loc, mlir::RewriterBase& rewriter);
mlir::Value mlir::Value
allocateContiguousResultMemRefLike(mlir::Value memrefValue, mlir::Location loc, mlir::RewriterBase& rewriter); allocateContiguousResultMemRefLike(mlir::Value memrefValue, mlir::Location loc, mlir::RewriterBase& rewriter);
@@ -1,7 +1,3 @@
set(LLVM_TARGET_DEFINITIONS PimBufferization.td)
mlir_tablegen(PimBufferization.hpp.inc -gen-rewriters "-I${ONNX_MLIR_SRC_ROOT}")
add_public_tablegen_target(PimBufferizationIncGen)
add_pim_library(OMPimBufferization add_pim_library(OMPimBufferization
PimBufferizationPass.cpp PimBufferizationPass.cpp
BufferizationUtils.hpp BufferizationUtils.hpp
@@ -15,9 +11,6 @@ add_pim_library(OMPimBufferization
EXCLUDE_FROM_OM_LIBS EXCLUDE_FROM_OM_LIBS
DEPENDS
PimBufferizationIncGen
LINK_LIBS PUBLIC LINK_LIBS PUBLIC
OMPimCommon OMPimCommon
PimOps PimOps
@@ -1,10 +1,111 @@
#include "Dialect/Pim/Transforms/Bufferization/Common.hpp" #include "Dialect/Pim/Transforms/Bufferization/Common.hpp"
#include "src/Accelerators/PIM/Common/PimCommon.hpp" #include "src/Accelerators/PIM/Common/PimCommon.hpp"
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
using namespace mlir; using namespace mlir;
IntegerAttr onnx_mlir::pim::getMemRefSizeInBytesAttr(OpBuilder& builder, Value memref) { static bool isCoreBatchInputArgument(Value value) {
auto type = mlir::cast<MemRefType>(memref.getType()); auto blockArg = dyn_cast<BlockArgument>(value);
int32_t sizeInBytes = static_cast<int32_t>(getShapedTypeSizeInBytes(type)); if (!blockArg)
return builder.getI32IntegerAttr(sizeInBytes); return false;
auto coreBatchOp = dyn_cast_or_null<onnx_mlir::pim::PimCoreBatchOp>(blockArg.getOwner()->getParentOp());
if (!coreBatchOp)
return false;
unsigned firstInputArg = 1 + coreBatchOp.getWeights().size();
return static_cast<unsigned>(blockArg.getArgNumber()) >= firstInputArg;
}
static FailureOr<Value> getPimStorageBase(Value value, const onnx_mlir::StaticValueKnowledge& knowledge) {
llvm::SmallPtrSet<Value, 8> visited;
while (value && visited.insert(value).second) {
Value alias = resolveLoopCarriedAlias(value, knowledge);
if (alias)
value = alias;
if (auto aliased = knowledge.aliases.lookup(value)) {
value = aliased;
continue;
}
if (auto base = onnx_mlir::pim::getPimAddressBase(value, knowledge); succeeded(base))
return base;
if (isa<BlockArgument>(value))
return value;
Operation* definingOp = value.getDefiningOp();
if (!definingOp)
return value;
if (auto subviewOp = dyn_cast<memref::SubViewOp>(definingOp)) {
value = subviewOp.getSource();
continue;
}
if (auto collapseOp = dyn_cast<memref::CollapseShapeOp>(definingOp)) {
value = collapseOp.getSrc();
continue;
}
if (auto expandOp = dyn_cast<memref::ExpandShapeOp>(definingOp)) {
value = expandOp.getSrc();
continue;
}
if (auto castOp = dyn_cast<memref::CastOp>(definingOp)) {
value = castOp.getSource();
continue;
}
return value;
}
if (value)
return value;
return failure();
}
FailureOr<IntegerAttr> onnx_mlir::pim::getMemRefSizeInBytesAttr(OpBuilder& builder, Operation* anchor, Value memref) {
auto type = mlir::cast<MemRefType>(memref.getType());
auto byteSize = getCheckedShapedTypeSizeInBytes(type, anchor, "memref byte size");
if (failed(byteSize))
return failure();
return getCheckedI32Attr(builder, anchor, *byteSize, "memref byte size");
}
FailureOr<Value> onnx_mlir::pim::getPimAddressBase(Value value, const StaticValueKnowledge& knowledge) {
Value alias = resolveLoopCarriedAlias(value, knowledge);
if (alias)
value = alias;
auto resolved = resolveContiguousAddress(value, knowledge);
if (succeeded(resolved))
return resolved->base;
auto compiled = compileContiguousAddressExpr(value);
if (failed(compiled)) {
if (isa<BlockArgument>(value))
return value;
return failure();
}
return compiled->base;
}
bool onnx_mlir::pim::isHostBackedPimAddress(Value value, const StaticValueKnowledge& knowledge) {
auto base = getPimStorageBase(value, knowledge);
if (failed(base))
return false;
if (isCoreBatchInputArgument(*base))
return true;
return isa_and_nonnull<memref::GetGlobalOp>(base->getDefiningOp());
}
bool onnx_mlir::pim::isDeviceLocalPimAddress(Value value, const StaticValueKnowledge& knowledge) {
auto base = getPimStorageBase(value, knowledge);
if (failed(base))
return false;
return isa_and_nonnull<memref::AllocOp>(base->getDefiningOp());
} }
@@ -2,10 +2,19 @@
#include "mlir/Dialect/MemRef/IR/MemRef.h" #include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "src/Accelerators/PIM/Common/IR/AddressAnalysis.hpp"
namespace onnx_mlir { namespace onnx_mlir {
namespace pim { namespace pim {
mlir::IntegerAttr getMemRefSizeInBytesAttr(mlir::OpBuilder& builder, mlir::Value memref); mlir::FailureOr<mlir::IntegerAttr>
getMemRefSizeInBytesAttr(mlir::OpBuilder& builder, mlir::Operation* anchor, mlir::Value memref);
mlir::FailureOr<mlir::Value> getPimAddressBase(mlir::Value value, const StaticValueKnowledge& knowledge = {});
bool isHostBackedPimAddress(mlir::Value value, const StaticValueKnowledge& knowledge = {});
bool isDeviceLocalPimAddress(mlir::Value value, const StaticValueKnowledge& knowledge = {});
} // namespace pim } // namespace pim
} // namespace onnx_mlir } // namespace onnx_mlir
@@ -3,220 +3,374 @@
#include "mlir/Dialect/SCF/IR/SCF.h" #include "mlir/Dialect/SCF/IR/SCF.h"
#include "ContiguityPatterns.hpp" #include "ContiguityPatterns.hpp"
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
#include "src/Accelerators/PIM/Common/IR/LoopUtils.hpp"
#include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp" #include "src/Accelerators/PIM/Common/IR/ShapeUtils.hpp"
#include "src/Accelerators/PIM/Common/IR/SubviewUtils.hpp"
#include "src/Accelerators/PIM/Common/PimCommon.hpp" #include "src/Accelerators/PIM/Common/PimCommon.hpp"
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp" #include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
using namespace mlir; using namespace mlir;
namespace onnx_mlir::pim { namespace onnx_mlir::pim {
namespace { namespace {
static bool isStaticSubviewContiguous(const StaticSubviewInfo& info) { struct ByteOffsetTerm {
if (llvm::any_of(info.strides, [](int64_t stride) { return stride != 1; })) Value value;
return false; int64_t scale = 0;
};
return isContiguousSubviewWithDynamicOffsets(info.sourceShape, info.offsets, info.sizes, info.strides); struct ByteOffsetExpr {
int64_t constant = 0;
SmallVector<ByteOffsetTerm> terms;
};
struct CopyEndpointPlan {
Value base;
MemRefType originalType;
MemRefType baseType;
ByteOffsetExpr offset;
};
struct CopyLoopPlan {
SmallVector<int64_t> outerShape;
int64_t chunkBytes = 0;
ByteOffsetExpr targetBaseOffset;
ByteOffsetExpr sourceBaseOffset;
SmallVector<int64_t> targetOuterByteStrides;
SmallVector<int64_t> sourceOuterByteStrides;
};
struct CopyRewritePlan {
enum class Kind {
Direct,
Loop
} kind = Kind::Direct;
CopyEndpointPlan target;
CopyEndpointPlan source;
int64_t directBytes = 0;
CopyLoopPlan loop;
};
static bool isViewLike(Value value) {
Operation* defOp = value.getDefiningOp();
return defOp
&& isa<memref::SubViewOp,
memref::ReinterpretCastOp,
memref::CollapseShapeOp,
memref::ExpandShapeOp,
memref::CastOp>(defOp);
} }
static OpFoldResult addConstantOffset(OpFoldResult baseOffset, int64_t extraOffset, PatternRewriter& rewriter) { template <typename CopyOp>
if (extraOffset == 0) static bool isNormalizedCopyLikeOp(CopyOp copyOp, Value target, Value source, Value targetOffset, Value sourceOffset) {
return baseOffset; auto targetType = dyn_cast<MemRefType>(target.getType());
auto sourceType = dyn_cast<MemRefType>(source.getType());
return targetType && sourceType && !isViewLike(target) && !isViewLike(source) && targetOffset.getType().isIndex()
&& sourceOffset.getType().isIndex() && copyOp.getSize() > 0;
}
if (auto attr = dyn_cast<Attribute>(baseOffset)) { static void appendTerm(ByteOffsetExpr& expr, Value value, int64_t scale) {
auto integerAttr = dyn_cast<IntegerAttr>(attr); if (scale != 0)
assert(integerAttr && "expected integer offset attribute"); expr.terms.push_back(ByteOffsetTerm {value, scale});
return rewriter.getIndexAttr(integerAttr.getInt() + extraOffset); }
static FailureOr<SmallVector<int64_t>> getStaticMemRefStrides(MemRefType type) {
SmallVector<int64_t> strides;
int64_t offset = 0;
if (failed(type.getStridesAndOffset(strides, offset)))
return failure();
if (llvm::any_of(strides, ShapedType::isDynamic))
return failure();
return strides;
}
static FailureOr<int64_t> getShapedByteSize(MemRefType type) {
if (!type.hasStaticShape() || !hasByteSizedElementType(type.getElementType()))
return failure();
auto byteSize =
pim::getCheckedShapedTypeSizeInBytes(type, UnknownLoc::get(type.getContext()), "normalized copy byte size");
if (failed(byteSize))
return failure();
if (*byteSize > static_cast<uint64_t>(std::numeric_limits<int64_t>::max()))
return failure();
return static_cast<int64_t>(*byteSize);
}
static FailureOr<SmallVector<int64_t>>
inferLogicalCopyShape(MemRefType targetType, MemRefType sourceType, int64_t size) {
if (!targetType.hasStaticShape() || !sourceType.hasStaticShape())
return failure();
if (targetType.getElementType() != sourceType.getElementType() || targetType.getRank() != sourceType.getRank())
return failure();
auto targetBytes = getShapedByteSize(targetType);
auto sourceBytes = getShapedByteSize(sourceType);
if (failed(targetBytes) || failed(sourceBytes))
return failure();
bool targetMatches = *targetBytes == size;
bool sourceMatches = *sourceBytes == size;
if (targetMatches && sourceMatches && targetType.getShape() != sourceType.getShape())
return failure();
if (targetMatches)
return SmallVector<int64_t>(targetType.getShape().begin(), targetType.getShape().end());
if (sourceMatches)
return SmallVector<int64_t>(sourceType.getShape().begin(), sourceType.getShape().end());
return failure();
}
static FailureOr<int64_t> getContiguousSuffixRank(MemRefType type, ArrayRef<int64_t> copyShape) {
if (!type.hasStaticShape() || !hasByteSizedElementType(type.getElementType())
|| type.getRank() != static_cast<int64_t>(copyShape.size()))
return failure();
auto strides = getStaticMemRefStrides(type);
if (failed(strides))
return failure();
int64_t expectedStride = 1;
int64_t contiguousSuffixRank = 0;
for (int64_t dim = type.getRank() - 1; dim >= 0; --dim) {
if ((*strides)[dim] != expectedStride)
break;
++contiguousSuffixRank;
expectedStride *= copyShape[dim];
}
return contiguousSuffixRank;
}
static FailureOr<CopyEndpointPlan> analyzeCopyEndpoint(Value value, Value initialByteOffset, MemRefType logicalType) {
if (!logicalType.hasStaticShape() || !hasByteSizedElementType(logicalType.getElementType()))
return failure();
CopyEndpointPlan endpoint;
endpoint.base = value;
endpoint.originalType = logicalType;
appendTerm(endpoint.offset, initialByteOffset, 1);
while (true) {
if (auto castOp = endpoint.base.getDefiningOp<memref::CastOp>()) {
endpoint.base = castOp.getSource();
continue;
}
if (auto collapseOp = endpoint.base.getDefiningOp<memref::CollapseShapeOp>()) {
endpoint.base = collapseOp.getSrc();
continue;
}
if (auto expandOp = endpoint.base.getDefiningOp<memref::ExpandShapeOp>()) {
endpoint.base = expandOp.getSrc();
continue;
}
if (auto reinterpretOp = endpoint.base.getDefiningOp<memref::ReinterpretCastOp>()) {
endpoint.base = reinterpretOp.getSource();
continue;
}
auto subviewOp = endpoint.base.getDefiningOp<memref::SubViewOp>();
if (!subviewOp)
break;
auto sourceType = dyn_cast<MemRefType>(subviewOp.getSource().getType());
if (!sourceType || !sourceType.hasStaticShape() || !hasByteSizedElementType(sourceType.getElementType()))
return failure();
auto sourceStrides = getStaticMemRefStrides(sourceType);
if (failed(sourceStrides))
return failure();
int64_t elementByteWidth = static_cast<int64_t>(getElementTypeSizeInBytes(sourceType.getElementType()));
for (auto [offset, stride] : llvm::zip_equal(subviewOp.getMixedOffsets(), *sourceStrides)) {
int64_t byteScale = stride * elementByteWidth;
if (auto attr = dyn_cast<Attribute>(offset)) {
endpoint.offset.constant += cast<IntegerAttr>(attr).getInt() * byteScale;
continue;
}
appendTerm(endpoint.offset, cast<Value>(offset), byteScale);
}
endpoint.base = subviewOp.getSource();
} }
auto value = cast<Value>(baseOffset); endpoint.baseType = dyn_cast<MemRefType>(endpoint.base.getType());
auto cst = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), extraOffset); if (!endpoint.baseType)
return arith::AddIOp::create(rewriter, value.getLoc(), value, cst).getResult(); return failure();
return endpoint;
} }
static Value buildSubviewChunk(const StaticSubviewInfo& info, static FailureOr<CopyRewritePlan>
ArrayRef<int64_t> outerIndices, analyzeCopyRewrite(Value target, Value source, Value targetOffset, Value sourceOffset, int64_t size) {
Location loc, auto targetType = dyn_cast<MemRefType>(target.getType());
PatternRewriter& rewriter) { auto sourceType = dyn_cast<MemRefType>(source.getType());
SmallVector<OpFoldResult> chunkOffsets; if (!targetType || !sourceType || size <= 0)
SmallVector<OpFoldResult> chunkSizes; return failure();
SmallVector<OpFoldResult> chunkStrides;
chunkOffsets.reserve(info.offsets.size());
chunkSizes.reserve(info.sizes.size());
chunkStrides.reserve(info.strides.size());
for (size_t dim = 0; dim < info.sizes.size(); ++dim) { auto logicalCopyShape = inferLogicalCopyShape(targetType, sourceType, size);
int64_t extraOffset = dim + 1 < info.sizes.size() ? outerIndices[dim] * info.strides[dim] : 0; if (failed(logicalCopyShape))
chunkOffsets.push_back(addConstantOffset(info.offsets[dim], extraOffset, rewriter)); return failure();
chunkSizes.push_back(rewriter.getIndexAttr(dim + 1 < info.sizes.size() ? 1 : info.sizes.back()));
chunkStrides.push_back(rewriter.getIndexAttr(info.strides[dim])); auto targetPlan = analyzeCopyEndpoint(target, targetOffset, targetType);
auto sourcePlan = analyzeCopyEndpoint(source, sourceOffset, sourceType);
if (failed(targetPlan) || failed(sourcePlan))
return failure();
auto targetSuffixRank = getContiguousSuffixRank(targetType, *logicalCopyShape);
auto sourceSuffixRank = getContiguousSuffixRank(sourceType, *logicalCopyShape);
if (failed(targetSuffixRank) || failed(sourceSuffixRank))
return failure();
CopyRewritePlan plan;
plan.target = *targetPlan;
plan.source = *sourcePlan;
int64_t contiguousSuffixRank = std::min(*targetSuffixRank, *sourceSuffixRank);
if (contiguousSuffixRank == static_cast<int64_t>(logicalCopyShape->size())) {
plan.kind = CopyRewritePlan::Kind::Direct;
plan.directBytes = size;
return plan;
} }
return memref::SubViewOp::create(rewriter, loc, info.source, chunkOffsets, chunkSizes, chunkStrides); auto targetStrides = getStaticMemRefStrides(targetType);
auto sourceStrides = getStaticMemRefStrides(sourceType);
if (failed(targetStrides) || failed(sourceStrides))
return failure();
int64_t elementByteWidth = static_cast<int64_t>(getElementTypeSizeInBytes(targetType.getElementType()));
plan.kind = CopyRewritePlan::Kind::Loop;
plan.loop.targetBaseOffset = plan.target.offset;
plan.loop.sourceBaseOffset = plan.source.offset;
plan.loop.outerShape.assign(logicalCopyShape->begin(), logicalCopyShape->end() - contiguousSuffixRank);
SmallVector<int64_t> chunkShape(logicalCopyShape->end() - contiguousSuffixRank, logicalCopyShape->end());
plan.loop.chunkBytes = getNumElements(chunkShape) * elementByteWidth;
for (int64_t stride : ArrayRef<int64_t>(*targetStrides).take_front(plan.loop.outerShape.size()))
plan.loop.targetOuterByteStrides.push_back(stride * elementByteWidth);
for (int64_t stride : ArrayRef<int64_t>(*sourceStrides).take_front(plan.loop.outerShape.size()))
plan.loop.sourceOuterByteStrides.push_back(stride * elementByteWidth);
if (plan.loop.chunkBytes <= 0)
return failure();
return plan;
} }
static SmallVector<Value> delinearizeIndexValue(Value linearIndex, static Value createIndexConstant(PatternRewriter& rewriter, Operation* anchorOp, int64_t value) {
ArrayRef<int64_t> shape, return getOrCreateIndexConstant(rewriter, anchorOp, value);
ArrayRef<int64_t> strides, }
PatternRewriter& rewriter) {
static Value addIndexValues(PatternRewriter& rewriter, Location loc, Value lhs, Value rhs) {
if (auto constant = getConstantIntValue(lhs); constant && *constant == 0)
return rhs;
if (auto constant = getConstantIntValue(rhs); constant && *constant == 0)
return lhs;
return arith::AddIOp::create(rewriter, loc, lhs, rhs).getResult();
}
static Value mulIndexValue(PatternRewriter& rewriter, Location loc, Operation* anchorOp, Value value, int64_t scale) {
if (scale == 0)
return createIndexConstant(rewriter, anchorOp, 0);
if (scale == 1)
return value;
Value scaleValue = createIndexConstant(rewriter, anchorOp, scale);
return arith::MulIOp::create(rewriter, loc, value, scaleValue).getResult();
}
static Value
materializeByteOffset(PatternRewriter& rewriter, Location loc, Operation* anchorOp, const ByteOffsetExpr& expr) {
Value result = createIndexConstant(rewriter, anchorOp, expr.constant);
for (const ByteOffsetTerm& term : expr.terms)
result = addIndexValues(rewriter, loc, result, mulIndexValue(rewriter, loc, anchorOp, term.value, term.scale));
return result;
}
static SmallVector<Value> materializeDelinearizedIndices(
PatternRewriter& rewriter, Location loc, Operation* anchorOp, Value linearIndex, ArrayRef<int64_t> shape) {
SmallVector<Value> indices; SmallVector<Value> indices;
indices.reserve(shape.size()); if (shape.empty())
return indices;
auto rowMajorStrides = computeRowMajorStrides(shape);
Value remaining = linearIndex; Value remaining = linearIndex;
for (auto [_dim, stride] : llvm::enumerate(strides)) { for (auto [dim, stride] : llvm::enumerate(rowMajorStrides)) {
auto cStride = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), stride); if (dim + 1 == rowMajorStrides.size()) {
Value index = arith::DivUIOp::create(rewriter, linearIndex.getLoc(), remaining, cStride); indices.push_back(remaining);
break;
}
Value strideValue = createIndexConstant(rewriter, anchorOp, stride);
Value index = arith::DivUIOp::create(rewriter, loc, remaining, strideValue);
indices.push_back(index); indices.push_back(index);
remaining = arith::RemUIOp::create(rewriter, linearIndex.getLoc(), remaining, cStride); remaining = arith::RemUIOp::create(rewriter, loc, remaining, strideValue);
} }
return indices; return indices;
} }
static OpFoldResult addDynamicOffset(OpFoldResult baseOffset, Value extraOffset, PatternRewriter& rewriter) { static Value materializeOuterByteOffset(PatternRewriter& rewriter,
if (auto attr = dyn_cast<Attribute>(baseOffset)) { Location loc,
auto integerAttr = cast<IntegerAttr>(attr); Operation* anchorOp,
if (integerAttr.getInt() == 0) const ByteOffsetExpr& baseOffset,
return extraOffset; ArrayRef<Value> outerIndices,
ArrayRef<int64_t> outerByteStrides) {
auto cst = getOrCreateIndexConstant(rewriter, rewriter.getInsertionBlock()->getParentOp(), integerAttr.getInt()); Value result = materializeByteOffset(rewriter, loc, anchorOp, baseOffset);
return arith::AddIOp::create(rewriter, extraOffset.getLoc(), cst, extraOffset).getResult(); for (auto [index, stride] : llvm::zip_equal(outerIndices, outerByteStrides))
} result = addIndexValues(rewriter, loc, result, mulIndexValue(rewriter, loc, anchorOp, index, stride));
return result;
auto value = cast<Value>(baseOffset);
return arith::AddIOp::create(rewriter, value.getLoc(), value, extraOffset).getResult();
}
static Value buildDynamicSubviewChunk(const StaticSubviewInfo& info,
ArrayRef<Value> outerIndices,
Location loc,
PatternRewriter& rewriter) {
SmallVector<OpFoldResult> chunkOffsets;
SmallVector<OpFoldResult> chunkSizes;
SmallVector<OpFoldResult> chunkStrides;
chunkOffsets.reserve(info.offsets.size());
chunkSizes.reserve(info.sizes.size());
chunkStrides.reserve(info.strides.size());
for (size_t dim = 0; dim < info.sizes.size(); ++dim) {
if (dim + 1 < info.sizes.size()) {
assert(info.strides[dim] == 1 && "loop-based subview rewrite requires unit strides");
chunkOffsets.push_back(addDynamicOffset(info.offsets[dim], outerIndices[dim], rewriter));
chunkSizes.push_back(rewriter.getIndexAttr(1));
}
else {
chunkOffsets.push_back(info.offsets[dim]);
chunkSizes.push_back(rewriter.getIndexAttr(info.sizes.back()));
}
chunkStrides.push_back(rewriter.getIndexAttr(info.strides[dim]));
}
return memref::SubViewOp::create(rewriter, loc, info.source, chunkOffsets, chunkSizes, chunkStrides);
}
static Value buildContiguousChunk(
Value source, ArrayRef<int64_t> copyShape, ArrayRef<Value> outerIndices, Location loc, PatternRewriter& rewriter) {
SmallVector<OpFoldResult> chunkOffsets;
SmallVector<OpFoldResult> chunkSizes;
SmallVector<OpFoldResult> chunkStrides;
chunkOffsets.reserve(copyShape.size());
chunkSizes.reserve(copyShape.size());
chunkStrides.reserve(copyShape.size());
for (size_t dim = 0; dim < copyShape.size(); ++dim) {
chunkOffsets.push_back(dim + 1 < copyShape.size() ? OpFoldResult(outerIndices[dim]) : rewriter.getIndexAttr(0));
chunkSizes.push_back(rewriter.getIndexAttr(dim + 1 < copyShape.size() ? 1 : copyShape.back()));
chunkStrides.push_back(rewriter.getIndexAttr(1));
}
return memref::SubViewOp::create(rewriter, loc, source, chunkOffsets, chunkSizes, chunkStrides);
} }
template <typename CopyOp, typename CreateCopyOp> template <typename CopyOp, typename CreateCopyOp>
static LogicalResult rewriteSubviewCopyLikeOp(CopyOp copyOp, static LogicalResult rewriteCopyLikeOp(CopyOp copyOp,
Value dst, Value target,
Value src, Value source,
int64_t dstOffset, Value targetOffset,
int64_t srcOffset, Value sourceOffset,
int64_t size, Value replacementValue,
bool allowLoopRewrite, CreateCopyOp createCopyOp,
PatternRewriter& rewriter, PatternRewriter& rewriter) {
CreateCopyOp createCopyOp) { if (isNormalizedCopyLikeOp(copyOp, target, source, targetOffset, sourceOffset))
auto srcSubview = getStaticSubviewInfo(src);
auto dstSubview = getStaticSubviewInfo(dst);
const bool splitSrc = succeeded(srcSubview) && !isStaticSubviewContiguous(*srcSubview);
const bool splitDst = succeeded(dstSubview) && !isStaticSubviewContiguous(*dstSubview);
if (!splitSrc && !splitDst)
return failure(); return failure();
auto sourceType = dyn_cast<MemRefType>(src.getType()); auto plan = analyzeCopyRewrite(target, source, targetOffset, sourceOffset, copyOp.getSize());
auto dstType = dyn_cast<MemRefType>(dst.getType()); if (failed(plan))
if (!sourceType || !dstType || !sourceType.hasStaticShape() || !dstType.hasStaticShape())
return failure();
if (sourceType.getElementType() != dstType.getElementType())
return failure(); return failure();
if (splitSrc && (srcOffset != 0 || llvm::any_of(srcSubview->strides, [](int64_t stride) { return stride != 1; }))) Location loc = copyOp.getLoc();
return failure(); Operation* anchorOp = copyOp.getOperation();
if (splitDst && (dstOffset != 0 || llvm::any_of(dstSubview->strides, [](int64_t stride) { return stride != 1; }))) if (plan->kind == CopyRewritePlan::Kind::Direct) {
return failure(); Value newTargetOffset = materializeByteOffset(rewriter, loc, anchorOp, plan->target.offset);
Value newSourceOffset = materializeByteOffset(rewriter, loc, anchorOp, plan->source.offset);
ArrayRef<int64_t> copyShape = splitSrc ? ArrayRef<int64_t>(srcSubview->sizes) : ArrayRef<int64_t>(dstSubview->sizes); auto checkedDirectBytes = pim::checkedI32(plan->directBytes, anchorOp, "normalized direct copy byte size");
if (splitSrc && splitDst && copyShape != ArrayRef<int64_t>(dstSubview->sizes)) if (failed(checkedDirectBytes))
return failure(); return failure();
auto newCopyOp =
if (!hasByteSizedElementType(sourceType.getElementType())) createCopyOp(loc, plan->target.base, plan->source.base, newTargetOffset, newSourceOffset, *checkedDirectBytes);
return failure(); assert(isNormalizedCopyOp(newCopyOp) && "copy normalization emitted a non-normalized copy");
const int64_t elementByteWidth = static_cast<int64_t>(getElementTypeSizeInBytes(sourceType.getElementType())); rewriter.replaceOp(copyOp, replacementValue);
const int64_t totalBytes = getNumElements(copyShape) * elementByteWidth;
if (size != totalBytes)
return failure();
const int64_t sliceBytes = copyShape.back() * elementByteWidth;
if (sliceBytes <= 0)
return failure();
SmallVector<int64_t> outerShape(copyShape.begin(), copyShape.end() - 1);
auto outerStrides = computeRowMajorStrides(outerShape);
const int64_t numSlices = outerShape.empty() ? 1 : getNumElements(outerShape);
const bool sourceShapeMatchesCopyShape = llvm::equal(sourceType.getShape(), copyShape);
const bool dstShapeMatchesCopyShape = llvm::equal(dstType.getShape(), copyShape);
if (allowLoopRewrite && numSlices > 1 && srcOffset == 0 && dstOffset == 0
&& sourceType.getRank() == static_cast<int64_t>(copyShape.size())
&& dstType.getRank() == static_cast<int64_t>(copyShape.size()) && (splitSrc || sourceShapeMatchesCopyShape)
&& (splitDst || dstShapeMatchesCopyShape)) {
auto c0 = getOrCreateIndexConstant(rewriter, copyOp, 0);
auto cUpper = getOrCreateIndexConstant(rewriter, copyOp, numSlices);
auto cStep = getOrCreateIndexConstant(rewriter, copyOp, 1);
auto loop = scf::ForOp::create(rewriter, copyOp.getLoc(), c0, cUpper, cStep, ValueRange {});
rewriter.setInsertionPointToStart(loop.getBody());
SmallVector<Value> outerIndices =
outerShape.empty() ? SmallVector<Value> {}
: delinearizeIndexValue(loop.getInductionVar(), outerShape, outerStrides, rewriter);
Value chunkDst = splitDst ? buildDynamicSubviewChunk(*dstSubview, outerIndices, copyOp.getLoc(), rewriter)
: buildContiguousChunk(dst, copyShape, outerIndices, copyOp.getLoc(), rewriter);
Value chunkSrc = splitSrc ? buildDynamicSubviewChunk(*srcSubview, outerIndices, copyOp.getLoc(), rewriter)
: buildContiguousChunk(src, copyShape, outerIndices, copyOp.getLoc(), rewriter);
createCopyOp(cast<MemRefType>(chunkDst.getType()), chunkDst, chunkSrc, 0, 0, sliceBytes);
return success(); return success();
} }
rewriter.setInsertionPoint(copyOp); Value c0 = createIndexConstant(rewriter, anchorOp, 0);
for (int64_t linearIndex = 0; linearIndex < numSlices; ++linearIndex) { Value cUpper = createIndexConstant(rewriter, anchorOp, getNumElements(plan->loop.outerShape));
SmallVector<int64_t> outerIndices = Value cStep = createIndexConstant(rewriter, anchorOp, 1);
outerShape.empty() ? SmallVector<int64_t> {} : delinearizeIndex(linearIndex, outerShape, outerStrides); auto loop = buildNormalizedScfFor(
Value chunkDst = splitDst ? buildSubviewChunk(*dstSubview, outerIndices, copyOp.getLoc(), rewriter) : dst; rewriter,
Value chunkSrc = splitSrc ? buildSubviewChunk(*srcSubview, outerIndices, copyOp.getLoc(), rewriter) : src; loc,
const int64_t srcByteOffset = splitSrc ? 0 : srcOffset + linearIndex * sliceBytes; c0,
const int64_t dstByteOffset = splitDst ? 0 : dstOffset + linearIndex * sliceBytes; cUpper,
createCopyOp(cast<MemRefType>(chunkDst.getType()), chunkDst, chunkSrc, dstByteOffset, srcByteOffset, sliceBytes); cStep,
} ValueRange {},
[&](OpBuilder&, Location nestedLoc, Value inductionVar, ValueRange iterArgs, SmallVectorImpl<Value>& yielded) {
SmallVector<Value> outerIndices =
materializeDelinearizedIndices(rewriter, nestedLoc, anchorOp, inductionVar, plan->loop.outerShape);
Value loopTargetOffset = materializeOuterByteOffset(
rewriter, nestedLoc, anchorOp, plan->loop.targetBaseOffset, outerIndices, plan->loop.targetOuterByteStrides);
Value loopSourceOffset = materializeOuterByteOffset(
rewriter, nestedLoc, anchorOp, plan->loop.sourceBaseOffset, outerIndices, plan->loop.sourceOuterByteStrides);
auto checkedChunkBytes = pim::checkedI32(plan->loop.chunkBytes, anchorOp, "normalized loop copy byte size");
if (failed(checkedChunkBytes))
return failure();
auto newCopyOp = createCopyOp(
nestedLoc, plan->target.base, plan->source.base, loopTargetOffset, loopSourceOffset, *checkedChunkBytes);
assert(isNormalizedCopyOp(newCopyOp) && "copy normalization emitted a non-normalized copy");
return success();
});
if (failed(loop))
return failure();
rewriter.replaceOp(copyOp, replacementValue);
return success(); return success();
} }
@@ -224,34 +378,24 @@ struct NormalizeCoreSubviewCopyPattern final : OpRewritePattern<pim::PimMemCopyO
using OpRewritePattern::OpRewritePattern; using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(pim::PimMemCopyOp copyOp, PatternRewriter& rewriter) const override { LogicalResult matchAndRewrite(pim::PimMemCopyOp copyOp, PatternRewriter& rewriter) const override {
if (!copyOp->getParentOfType<pim::PimCoreOp>() && !copyOp->getParentOfType<pim::PimCoreBatchOp>()) return rewriteCopyLikeOp(
return failure();
auto status = rewriteSubviewCopyLikeOp(
copyOp, copyOp,
copyOp.getTarget(), copyOp.getTarget(),
copyOp.getSource(), copyOp.getSource(),
copyOp.getTargetOffset(), copyOp.getTargetOffset(),
copyOp.getSourceOffset(), copyOp.getSourceOffset(),
copyOp.getSize(), copyOp.getTarget(),
/*allowLoopRewrite=*/true, [&](Location loc, Value target, Value source, Value targetOffset, Value sourceOffset, int32_t size) {
rewriter, return pim::PimMemCopyOp::create(rewriter,
[&]( loc,
MemRefType resultType, Value dst, Value src, int64_t dstByteOffset, int64_t srcByteOffset, int64_t sliceBytes) { cast<MemRefType>(target.getType()),
pim::PimMemCopyOp::create(rewriter, targetOffset,
copyOp.getLoc(), sourceOffset,
resultType, target,
dst, source,
src, rewriter.getI32IntegerAttr(size));
rewriter.getI32IntegerAttr(static_cast<int32_t>(dstByteOffset)), },
rewriter.getI32IntegerAttr(static_cast<int32_t>(srcByteOffset)), rewriter);
rewriter.getI32IntegerAttr(static_cast<int32_t>(sliceBytes)));
});
if (failed(status))
return failure();
rewriter.replaceOp(copyOp, copyOp.getTarget());
return success();
} }
}; };
@@ -259,38 +403,24 @@ struct NormalizeHostSubviewLoadPattern final : OpRewritePattern<pim::PimMemCopyH
using OpRewritePattern::OpRewritePattern; using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(pim::PimMemCopyHostToDevOp copyOp, PatternRewriter& rewriter) const override { LogicalResult matchAndRewrite(pim::PimMemCopyHostToDevOp copyOp, PatternRewriter& rewriter) const override {
auto dstOffset = resolveIndexValue(copyOp.getDeviceTargetOffset()); return rewriteCopyLikeOp(
auto srcOffset = resolveIndexValue(copyOp.getHostSourceOffset());
if (failed(dstOffset) || failed(srcOffset))
return failure();
auto status = rewriteSubviewCopyLikeOp(
copyOp, copyOp,
copyOp.getDeviceTarget(), copyOp.getDeviceTarget(),
copyOp.getHostSource(), copyOp.getHostSource(),
*dstOffset, copyOp.getDeviceTargetOffset(),
*srcOffset, copyOp.getHostSourceOffset(),
copyOp.getSize(), copyOp.getDeviceTarget(),
/*allowLoopRewrite=*/true, [&](Location loc, Value target, Value source, Value targetOffset, Value sourceOffset, int32_t size) {
rewriter, return pim::PimMemCopyHostToDevOp::create(rewriter,
[&]( loc,
MemRefType resultType, Value dst, Value src, int64_t dstByteOffset, int64_t srcByteOffset, int64_t sliceBytes) { cast<MemRefType>(target.getType()),
Value dstOffsetValue = getOrCreateIndexConstant(rewriter, copyOp, dstByteOffset); targetOffset,
Value srcOffsetValue = getOrCreateIndexConstant(rewriter, copyOp, srcByteOffset); sourceOffset,
pim::PimMemCopyHostToDevOp::create(rewriter, target,
copyOp.getLoc(), source,
resultType, rewriter.getI32IntegerAttr(size));
dstOffsetValue, },
srcOffsetValue, rewriter);
dst,
src,
rewriter.getI32IntegerAttr(static_cast<int32_t>(sliceBytes)));
});
if (failed(status))
return failure();
rewriter.replaceOp(copyOp, copyOp.getDeviceTarget());
return success();
} }
}; };
@@ -298,43 +428,43 @@ struct NormalizeHostSubviewStorePattern final : OpRewritePattern<pim::PimMemCopy
using OpRewritePattern::OpRewritePattern; using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(pim::PimMemCopyDevToHostOp copyOp, PatternRewriter& rewriter) const override { LogicalResult matchAndRewrite(pim::PimMemCopyDevToHostOp copyOp, PatternRewriter& rewriter) const override {
auto dstOffset = resolveIndexValue(copyOp.getHostTargetOffset()); return rewriteCopyLikeOp(
auto srcOffset = resolveIndexValue(copyOp.getDeviceSourceOffset());
if (failed(dstOffset) || failed(srcOffset))
return failure();
auto status = rewriteSubviewCopyLikeOp(
copyOp, copyOp,
copyOp.getHostTarget(), copyOp.getHostTarget(),
copyOp.getDeviceSource(), copyOp.getDeviceSource(),
*dstOffset, copyOp.getHostTargetOffset(),
*srcOffset, copyOp.getDeviceSourceOffset(),
copyOp.getSize(), copyOp.getHostTarget(),
/*allowLoopRewrite=*/false, [&](Location loc, Value target, Value source, Value targetOffset, Value sourceOffset, int32_t size) {
rewriter, return pim::PimMemCopyDevToHostOp::create(rewriter,
[&]( loc,
MemRefType resultType, Value dst, Value src, int64_t dstByteOffset, int64_t srcByteOffset, int64_t sliceBytes) { cast<MemRefType>(target.getType()),
Value dstOffsetValue = getOrCreateIndexConstant(rewriter, copyOp, dstByteOffset); targetOffset,
Value srcOffsetValue = getOrCreateIndexConstant(rewriter, copyOp, srcByteOffset); sourceOffset,
pim::PimMemCopyDevToHostOp::create(rewriter, target,
copyOp.getLoc(), source,
resultType, rewriter.getI32IntegerAttr(size));
dstOffsetValue, },
srcOffsetValue, rewriter);
dst,
src,
rewriter.getI32IntegerAttr(static_cast<int32_t>(sliceBytes)));
});
if (failed(status))
return failure();
rewriter.replaceOp(copyOp, copyOp.getHostTarget());
return success();
} }
}; };
} // namespace } // namespace
bool isNormalizedCopyOp(pim::PimMemCopyOp op) {
return isNormalizedCopyLikeOp(op, op.getTarget(), op.getSource(), op.getTargetOffset(), op.getSourceOffset());
}
bool isNormalizedCopyOp(pim::PimMemCopyHostToDevOp op) {
return isNormalizedCopyLikeOp(
op, op.getDeviceTarget(), op.getHostSource(), op.getDeviceTargetOffset(), op.getHostSourceOffset());
}
bool isNormalizedCopyOp(pim::PimMemCopyDevToHostOp op) {
return isNormalizedCopyLikeOp(
op, op.getHostTarget(), op.getDeviceSource(), op.getHostTargetOffset(), op.getDeviceSourceOffset());
}
void populatePimContiguityNormalizationPatterns(RewritePatternSet& patterns) { void populatePimContiguityNormalizationPatterns(RewritePatternSet& patterns) {
patterns.add<NormalizeCoreSubviewCopyPattern, NormalizeHostSubviewLoadPattern, NormalizeHostSubviewStorePattern>( patterns.add<NormalizeCoreSubviewCopyPattern, NormalizeHostSubviewLoadPattern, NormalizeHostSubviewStorePattern>(
patterns.getContext()); patterns.getContext());
@@ -2,8 +2,14 @@
#include "mlir/IR/PatternMatch.h" #include "mlir/IR/PatternMatch.h"
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
namespace onnx_mlir::pim { namespace onnx_mlir::pim {
bool isNormalizedCopyOp(pim::PimMemCopyOp op);
bool isNormalizedCopyOp(pim::PimMemCopyHostToDevOp op);
bool isNormalizedCopyOp(pim::PimMemCopyDevToHostOp op);
void populatePimContiguityNormalizationPatterns(mlir::RewritePatternSet& patterns); void populatePimContiguityNormalizationPatterns(mlir::RewritePatternSet& patterns);
} // namespace onnx_mlir::pim } // namespace onnx_mlir::pim
@@ -101,10 +101,10 @@ struct MemCopyOpInterface : DstBufferizableOpInterfaceExternalModel<MemCopyOpInt
replaceOpWithNewBufferizedOp<PimMemCopyOp>(rewriter, replaceOpWithNewBufferizedOp<PimMemCopyOp>(rewriter,
memCopyOp, memCopyOp,
targetOpt->getType(), targetOpt->getType(),
memCopyOp.getTargetOffset(),
memCopyOp.getSourceOffset(),
*targetOpt, *targetOpt,
*sourceOpt, *sourceOpt,
memCopyOp.getTargetOffsetAttr(),
memCopyOp.getSourceOffsetAttr(),
memCopyOp.getSizeAttr()); memCopyOp.getSizeAttr());
return success(); return success();
} }
@@ -148,7 +148,10 @@ struct ConcatOpInterface : DstBufferizableOpInterfaceExternalModel<ConcatOpInter
auto inputOpt = getBufferOrValue(rewriter, input, options, state); auto inputOpt = getBufferOrValue(rewriter, input, options, state);
if (failed(inputOpt)) if (failed(inputOpt))
return failure(); return failure();
inputs.push_back(materializeContiguousInputMemRef(*inputOpt, op->getLoc(), rewriter)); auto contiguous = materializeContiguousInputMemRef(*inputOpt, op->getLoc(), rewriter);
if (failed(contiguous))
return failure();
inputs.push_back(*contiguous);
} }
auto outputBufferOpt = getBufferOrValue(rewriter, concatOp.getOutputBuffer(), options, state); auto outputBufferOpt = getBufferOrValue(rewriter, concatOp.getOutputBuffer(), options, state);
@@ -179,12 +182,12 @@ struct SendOpInterface : BufferizableOpInterface::ExternalModel<SendOpInterface,
auto inputOpt = getBufferOrValue(rewriter, sendOp.getInput(), options, state); auto inputOpt = getBufferOrValue(rewriter, sendOp.getInput(), options, state);
if (failed(inputOpt)) if (failed(inputOpt))
return failure(); return failure();
auto contiguousInput = materializeContiguousInputMemRef(*inputOpt, op->getLoc(), rewriter);
if (failed(contiguousInput))
return failure();
replaceOpWithNewBufferizedOp<PimSendOp>(rewriter, replaceOpWithNewBufferizedOp<PimSendOp>(
op, rewriter, op, *contiguousInput, sendOp.getSizeAttr(), sendOp.getTargetCoreId());
materializeContiguousInputMemRef(*inputOpt, op->getLoc(), rewriter),
sendOp.getSizeAttr(),
sendOp.getTargetCoreId());
return success(); return success();
} }
}; };
@@ -407,11 +410,13 @@ struct TransposeOpInterface : DstBufferizableOpInterfaceExternalModel<TransposeO
if (failed(outputBufferOpt)) if (failed(outputBufferOpt))
return failure(); return failure();
Value contiguousInput = materializeContiguousInputMemRef(*inputOpt, op->getLoc(), rewriter); auto contiguousInput = materializeContiguousInputMemRef(*inputOpt, op->getLoc(), rewriter);
if (failed(contiguousInput))
return failure();
Value contiguousOutput = allocateContiguousResultMemRefLike(*outputBufferOpt, op->getLoc(), rewriter); Value contiguousOutput = allocateContiguousResultMemRefLike(*outputBufferOpt, op->getLoc(), rewriter);
replaceOpWithNewBufferizedOp<PimTransposeOp>( replaceOpWithNewBufferizedOp<PimTransposeOp>(
rewriter, op, contiguousOutput.getType(), contiguousInput, transposeOp.getPermutation(), contiguousOutput); rewriter, op, contiguousOutput.getType(), *contiguousInput, transposeOp.getPermutation(), contiguousOutput);
return success(); return success();
} }
}; };
@@ -451,11 +456,13 @@ struct VMMOpInterface : DstBufferizableOpInterfaceExternalModel<VMMOpInterface,
if (failed(outputBufferOpt)) if (failed(outputBufferOpt))
return failure(); return failure();
Value contiguousInput = materializeContiguousInputMemRef(*inputOpt, op->getLoc(), rewriter); auto contiguousInput = materializeContiguousInputMemRef(*inputOpt, op->getLoc(), rewriter);
if (failed(contiguousInput))
return failure();
Value contiguousOutput = allocateContiguousResultMemRefLike(*outputBufferOpt, op->getLoc(), rewriter); Value contiguousOutput = allocateContiguousResultMemRefLike(*outputBufferOpt, op->getLoc(), rewriter);
replaceOpWithNewBufferizedOp<PimVMMOp>( replaceOpWithNewBufferizedOp<PimVMMOp>(
rewriter, op, contiguousOutput.getType(), *weightOpt, contiguousInput, contiguousOutput); rewriter, op, contiguousOutput.getType(), *weightOpt, *contiguousInput, contiguousOutput);
return success(); return success();
} }
}; };
@@ -490,12 +497,16 @@ struct BinaryDstOpInterface : DstBufferizableOpInterfaceExternalModel<BinaryDstO
if (failed(outputBufferOpt)) if (failed(outputBufferOpt))
return failure(); return failure();
Value contiguousLhs = materializeContiguousInputMemRef(*lhsOpt, op->getLoc(), rewriter); auto contiguousLhs = materializeContiguousInputMemRef(*lhsOpt, op->getLoc(), rewriter);
Value contiguousRhs = materializeContiguousInputMemRef(*rhsOpt, op->getLoc(), rewriter); if (failed(contiguousLhs))
return failure();
auto contiguousRhs = materializeContiguousInputMemRef(*rhsOpt, op->getLoc(), rewriter);
if (failed(contiguousRhs))
return failure();
Value contiguousOutput = allocateContiguousResultMemRefLike(*outputBufferOpt, op->getLoc(), rewriter); Value contiguousOutput = allocateContiguousResultMemRefLike(*outputBufferOpt, op->getLoc(), rewriter);
replaceOpWithNewBufferizedOp<OpTy>( replaceOpWithNewBufferizedOp<OpTy>(
rewriter, op, contiguousOutput.getType(), contiguousLhs, contiguousRhs, contiguousOutput); rewriter, op, contiguousOutput.getType(), *contiguousLhs, *contiguousRhs, contiguousOutput);
return success(); return success();
} }
}; };
@@ -523,12 +534,16 @@ struct VVDMulOpInterface : DstBufferizableOpInterfaceExternalModel<VVDMulOpInter
if (failed(outputBufferOpt)) if (failed(outputBufferOpt))
return failure(); return failure();
Value contiguousLhs = materializeContiguousInputMemRef(*lhsOpt, op->getLoc(), rewriter); auto contiguousLhs = materializeContiguousInputMemRef(*lhsOpt, op->getLoc(), rewriter);
Value contiguousRhs = materializeContiguousInputMemRef(*rhsOpt, op->getLoc(), rewriter); if (failed(contiguousLhs))
return failure();
auto contiguousRhs = materializeContiguousInputMemRef(*rhsOpt, op->getLoc(), rewriter);
if (failed(contiguousRhs))
return failure();
Value contiguousOutput = allocateContiguousResultMemRefLike(*outputBufferOpt, op->getLoc(), rewriter); Value contiguousOutput = allocateContiguousResultMemRefLike(*outputBufferOpt, op->getLoc(), rewriter);
replaceOpWithNewBufferizedOp<PimVVDMulOp>( replaceOpWithNewBufferizedOp<PimVVDMulOp>(
rewriter, op, contiguousOutput.getType(), contiguousLhs, contiguousRhs, contiguousOutput); rewriter, op, contiguousOutput.getType(), *contiguousLhs, *contiguousRhs, contiguousOutput);
return success(); return success();
} }
}; };
@@ -559,10 +574,12 @@ struct UnaryDstOpInterface : DstBufferizableOpInterfaceExternalModel<UnaryDstOpI
if (failed(outputBufferOpt)) if (failed(outputBufferOpt))
return failure(); return failure();
Value contiguousInput = materializeContiguousInputMemRef(*inputOpt, op->getLoc(), rewriter); auto contiguousInput = materializeContiguousInputMemRef(*inputOpt, op->getLoc(), rewriter);
if (failed(contiguousInput))
return failure();
Value contiguousOutput = allocateContiguousResultMemRefLike(*outputBufferOpt, op->getLoc(), rewriter); Value contiguousOutput = allocateContiguousResultMemRefLike(*outputBufferOpt, op->getLoc(), rewriter);
replaceOpWithNewBufferizedOp<OpTy>(rewriter, op, contiguousOutput.getType(), contiguousInput, contiguousOutput); replaceOpWithNewBufferizedOp<OpTy>(rewriter, op, contiguousOutput.getType(), *contiguousInput, contiguousOutput);
return success(); return success();
} }
}; };
@@ -1,19 +0,0 @@
#ifndef PIM_BUFFERIZATION
#define PIM_BUFFERIZATION
#ifndef OP_BASE
include "mlir/IR/PatternBase.td"
include "mlir/Dialect/MemRef/IR/MemRefOps.td"
include "src/Accelerators/PIM/Dialect/Pim/Pim.td"
#endif // OP_BASE
def memrefCopyToPimMemCopyOp : Pat<
(CopyOp $src, $dst),
(PimMemCopyOp $dst, $src,
ConstantAttr<I32Attr, "0">,
ConstantAttr<I32Attr, "0">,
(NativeCodeCall<"pim::getMemRefSizeInBytesAttr($_builder, $0)"> $src),
(returnType $dst))
>;
#endif // PIM_BUFFERIZATION
@@ -6,8 +6,9 @@
#include "mlir/Dialect/Tensor/IR/Tensor.h" #include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/IR/PatternMatch.h" #include "mlir/IR/PatternMatch.h"
#include "mlir/Pass/Pass.h" #include "mlir/Pass/Pass.h"
#include "mlir/Transforms/GreedyPatternRewriteDriver.h" #include "mlir/Rewrite/PatternApplicator.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/Support/Casting.h" #include "llvm/Support/Casting.h"
#include "Common/PimCommon.hpp" #include "Common/PimCommon.hpp"
@@ -15,6 +16,7 @@
#include "Dialect/Pim/PimOps.hpp" #include "Dialect/Pim/PimOps.hpp"
#include "Dialect/Pim/Transforms/Bufferization/Common.hpp" #include "Dialect/Pim/Transforms/Bufferization/Common.hpp"
#include "Dialect/Pim/Transforms/Bufferization/ContiguityPatterns.hpp" #include "Dialect/Pim/Transforms/Bufferization/ContiguityPatterns.hpp"
#include "src/Accelerators/PIM/Common/IR/CoreBlockUtils.hpp"
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp" #include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
#include "src/Accelerators/PIM/Pass/PIMPasses.h" #include "src/Accelerators/PIM/Pass/PIMPasses.h"
#include "src/Compiler/CompilerOptions.hpp" #include "src/Compiler/CompilerOptions.hpp"
@@ -27,7 +29,92 @@ namespace onnx_mlir {
namespace { namespace {
#include "Dialect/Pim/Transforms/Bufferization/PimBufferization.hpp.inc" struct MemRefCopyWorkItem {
memref::CopyOp copyOp;
StaticValueKnowledge knowledge;
};
static StaticValueKnowledge seedCoreKnowledge(pim::PimCoreOp coreOp) {
StaticValueKnowledge knowledge;
for (auto [index, weight] : llvm::enumerate(coreOp.getWeights()))
knowledge.aliases[coreOp.getWeightArgument(index)] = weight;
return knowledge;
}
static StaticValueKnowledge seedCoreBatchKnowledge(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;
}
static LogicalResult
lowerMemRefCopyToPimCopy(memref::CopyOp copyOp, PatternRewriter& rewriter, const StaticValueKnowledge& knowledge) {
if (!copyOp->getParentOfType<pim::PimCoreOp>() && !copyOp->getParentOfType<pim::PimCoreBatchOp>())
return failure();
auto sourceType = dyn_cast<MemRefType>(copyOp.getSource().getType());
auto targetType = dyn_cast<MemRefType>(copyOp.getTarget().getType());
if (!sourceType || !targetType || !sourceType.hasStaticShape() || !targetType.hasStaticShape())
return failure();
if (sourceType.getElementType() != targetType.getElementType())
return failure();
Value zeroOffset = getOrCreateIndexConstant(rewriter, copyOp, 0);
auto sizeAttr = getMemRefSizeInBytesAttr(rewriter, copyOp.getOperation(), copyOp.getSource());
if (failed(sizeAttr))
return failure();
bool sourceIsHost = isHostBackedPimAddress(copyOp.getSource(), knowledge);
bool targetIsHost = isHostBackedPimAddress(copyOp.getTarget(), knowledge);
bool sourceIsDevice = isDeviceLocalPimAddress(copyOp.getSource(), knowledge);
bool targetIsDevice = isDeviceLocalPimAddress(copyOp.getTarget(), knowledge);
if (targetIsDevice && sourceIsHost) {
pim::PimMemCopyHostToDevOp::create(rewriter,
copyOp.getLoc(),
copyOp.getTarget().getType(),
zeroOffset,
zeroOffset,
copyOp.getTarget(),
copyOp.getSource(),
*sizeAttr);
}
else if (targetIsHost && sourceIsDevice) {
pim::PimMemCopyDevToHostOp::create(rewriter,
copyOp.getLoc(),
copyOp.getTarget().getType(),
zeroOffset,
zeroOffset,
copyOp.getTarget(),
copyOp.getSource(),
*sizeAttr);
}
else if (targetIsDevice && sourceIsDevice) {
pim::PimMemCopyOp::create(rewriter,
copyOp.getLoc(),
copyOp.getTarget().getType(),
zeroOffset,
zeroOffset,
copyOp.getTarget(),
copyOp.getSource(),
*sizeAttr);
}
else {
copyOp.emitOpError() << "failed to classify memref.copy endpoints: source=" << copyOp.getSource()
<< " type=" << copyOp.getSource().getType() << " host=" << sourceIsHost
<< " device=" << sourceIsDevice << ", target=" << copyOp.getTarget()
<< " type=" << copyOp.getTarget().getType() << " host=" << targetIsHost
<< " device=" << targetIsDevice;
return failure();
}
rewriter.eraseOp(copyOp);
return success();
}
struct PimBufferizationPass : PassWrapper<PimBufferizationPass, OperationPass<ModuleOp>> { struct PimBufferizationPass : PassWrapper<PimBufferizationPass, OperationPass<ModuleOp>> {
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(PimBufferizationPass) MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(PimBufferizationPass)
@@ -44,6 +131,14 @@ private:
LogicalResult verifyContiguousRuntimeOperands(ModuleOp moduleOp) const; LogicalResult verifyContiguousRuntimeOperands(ModuleOp moduleOp) const;
}; };
static LogicalResult applyPatternsOnce(Operation* op, PatternApplicator& applicator, PatternRewriter& rewriter) {
if (!op || !op->getBlock())
return failure();
rewriter.setInsertionPoint(op);
return applicator.matchAndRewrite(op, rewriter);
}
} // namespace } // namespace
void PimBufferizationPass::runOnOperation() { void PimBufferizationPass::runOnOperation() {
@@ -63,35 +158,81 @@ void PimBufferizationPass::runOnOperation() {
} }
MLIRContext* ctx = moduleOp.getContext(); MLIRContext* ctx = moduleOp.getContext();
ConversionTarget target(*ctx); PatternRewriter rewriter(ctx);
target.addLegalDialect<PimDialect>();
RewritePatternSet patterns(ctx); SmallVector<MemRefCopyWorkItem> copyWorklist;
populateWithGenerated(patterns); llvm::SmallPtrSet<Operation*, 16> seenCopyOps;
auto addCopyOp = [&](memref::CopyOp copyOp, const StaticValueKnowledge& knowledge) {
if (seenCopyOps.insert(copyOp.getOperation()).second)
copyWorklist.push_back({copyOp, knowledge});
};
// Only convert memref.copy → pim.memcp inside pim.core / pim.core_batch bodies. moduleOp.walk([&](pim::PimCoreOp coreOp) {
// Host-level copies (e.g. from split/slice ops) must remain as memref.copy for CPU lowering. StaticValueKnowledge knowledge = seedCoreKnowledge(coreOp);
FrozenRewritePatternSet frozenPatterns(std::move(patterns)); (void) walkPimCoreBlockStructurally(
bool hasFailed = false; coreOp.getBody().front(), knowledge, [&](Operation& op, const StaticValueKnowledge& opKnowledge) {
moduleOp.walk<WalkOrder::PreOrder>([&](Operation* op) { if (auto copyOp = dyn_cast<memref::CopyOp>(&op))
if (!isa<pim::PimCoreOp, pim::PimCoreBatchOp>(op)) addCopyOp(copyOp, opKnowledge);
return WalkResult::advance(); return success();
if (failed(applyPartialConversion(op, target, frozenPatterns))) });
hasFailed = true;
return WalkResult::skip();
}); });
moduleOp.walk([&](pim::PimCoreBatchOp coreBatchOp) {
llvm::SmallVector<unsigned, 2> lanes;
lanes.push_back(0);
if (coreBatchOp.getLaneCount() > 1)
lanes.push_back(static_cast<unsigned>(coreBatchOp.getLaneCount() - 1));
for (unsigned lane : lanes) {
StaticValueKnowledge knowledge = seedCoreBatchKnowledge(coreBatchOp, lane);
(void) walkPimCoreBlockStructurally(
coreBatchOp.getBody().front(), knowledge, [&](Operation& op, const StaticValueKnowledge& opKnowledge) {
if (auto copyOp = dyn_cast<memref::CopyOp>(&op))
addCopyOp(copyOp, opKnowledge);
return success();
});
}
});
bool hasFailed = false;
for (const MemRefCopyWorkItem& workItem : copyWorklist) {
memref::CopyOp copyOp = workItem.copyOp;
rewriter.setInsertionPoint(copyOp);
if (failed(lowerMemRefCopyToPimCopy(copyOp, rewriter, workItem.knowledge)))
hasFailed = true;
}
if (hasFailed) { if (hasFailed) {
moduleOp.emitError("failed to lower memref.copy-like ops inside PIM core bodies during bufferization");
signalPassFailure(); signalPassFailure();
return; return;
} }
// After this pass, executable PIM ops must only use contiguous/addressable memrefs.
// Later PIM codegen passes may verify this invariant but must not repair it.
RewritePatternSet contiguityPatterns(ctx); RewritePatternSet contiguityPatterns(ctx);
populatePimContiguityNormalizationPatterns(contiguityPatterns); populatePimContiguityNormalizationPatterns(contiguityPatterns);
if (failed(applyPatternsGreedily(moduleOp, std::move(contiguityPatterns)))) { FrozenRewritePatternSet frozenContiguityPatterns(std::move(contiguityPatterns));
moduleOp.emitError("failed to normalize PIM runtime operand contiguity during bufferization"); PatternApplicator contiguityApplicator(frozenContiguityPatterns);
contiguityApplicator.applyDefaultCostModel();
SmallVector<Operation*> contiguityWorklist;
moduleOp.walk([&](Operation* op) {
if (isa<pim::PimMemCopyOp, pim::PimMemCopyHostToDevOp, pim::PimMemCopyDevToHostOp>(op))
contiguityWorklist.push_back(op);
});
hasFailed = false;
for (Operation* op : contiguityWorklist) {
if (auto copyOp = dyn_cast<pim::PimMemCopyOp>(op); copyOp && pim::isNormalizedCopyOp(copyOp))
continue;
if (auto copyOp = dyn_cast<pim::PimMemCopyHostToDevOp>(op); copyOp && pim::isNormalizedCopyOp(copyOp))
continue;
if (auto copyOp = dyn_cast<pim::PimMemCopyDevToHostOp>(op); copyOp && pim::isNormalizedCopyOp(copyOp))
continue;
if (failed(applyPatternsOnce(op, contiguityApplicator, rewriter))) {
op->emitOpError("failed to normalize PIM copy contiguity");
hasFailed = true;
}
}
if (hasFailed) {
moduleOp.emitError("failed to normalize PIM copy contiguity during bufferization");
signalPassFailure(); signalPassFailure();
return; return;
} }
@@ -138,16 +279,28 @@ LogicalResult PimBufferizationPass::verifyContiguousRuntimeOperands(ModuleOp mod
}; };
if (auto memCopyOp = dyn_cast<PimMemCopyOp>(op)) { if (auto memCopyOp = dyn_cast<PimMemCopyOp>(op)) {
if (!pim::isNormalizedCopyOp(memCopyOp)) {
memCopyOp.emitOpError("must use base memref operands plus explicit byte offsets after bufferization");
hasFailure = true;
}
verifyOperand(memCopyOp.getTarget(), 0); verifyOperand(memCopyOp.getTarget(), 0);
verifyOperand(memCopyOp.getSource(), 1); verifyOperand(memCopyOp.getSource(), 1);
return; return;
} }
if (auto loadOp = dyn_cast<PimMemCopyHostToDevOp>(op)) { if (auto loadOp = dyn_cast<PimMemCopyHostToDevOp>(op)) {
if (!pim::isNormalizedCopyOp(loadOp)) {
loadOp.emitOpError("must use base memref operands plus explicit byte offsets after bufferization");
hasFailure = true;
}
verifyOperand(loadOp.getDeviceTarget(), 2); verifyOperand(loadOp.getDeviceTarget(), 2);
verifyOperand(loadOp.getHostSource(), 3); verifyOperand(loadOp.getHostSource(), 3);
return; return;
} }
if (auto storeOp = dyn_cast<PimMemCopyDevToHostOp>(op)) { if (auto storeOp = dyn_cast<PimMemCopyDevToHostOp>(op)) {
if (!pim::isNormalizedCopyOp(storeOp)) {
storeOp.emitOpError("must use base memref operands plus explicit byte offsets after bufferization");
hasFailure = true;
}
verifyOperand(storeOp.getHostTarget(), 2); verifyOperand(storeOp.getHostTarget(), 2);
verifyOperand(storeOp.getDeviceSource(), 3); verifyOperand(storeOp.getDeviceSource(), 3);
return; return;
@@ -0,0 +1,12 @@
add_pim_library(OMPimHostConstantFolding
Common.cpp
Patterns/Constant.cpp
HostConstantFoldingPass.cpp
Patterns/Subview.cpp
EXCLUDE_FROM_OM_LIBS
LINK_LIBS PUBLIC
MLIRLinalgDialect
OMPimCommon
)
@@ -7,6 +7,7 @@
#include "../Common.hpp" #include "../Common.hpp"
#include "../Patterns.hpp" #include "../Patterns.hpp"
#include "src/Accelerators/PIM/Common/PimCommon.hpp" #include "src/Accelerators/PIM/Common/PimCommon.hpp"
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp" #include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
using namespace mlir; using namespace mlir;
@@ -120,15 +121,15 @@ struct FoldConstantCoreMapPattern final : OpRewritePattern<linalg::MapOp> {
rewriter.setInsertionPoint(mapOp); rewriter.setInsertionPoint(mapOp);
auto getGlobalOp = memref::GetGlobalOp::create(rewriter, mapOp.getLoc(), initType, globalOp.getName()); auto getGlobalOp = memref::GetGlobalOp::create(rewriter, mapOp.getLoc(), initType, globalOp.getName());
auto sizeInBytes = getShapedTypeSizeInBytes(initType); auto sizeInBytes = pim::getCheckedShapedTypeSizeInBytes(initType, mapOp, "host constant folding byte size");
pim::PimMemCopyOp::create(rewriter, if (failed(sizeInBytes))
mapOp.getLoc(), return failure();
initType, Value zeroOffset = getOrCreateIndexConstant(rewriter, mapOp, 0);
mapOp.getInit(), auto sizeAttr = pim::getCheckedI32Attr(rewriter, mapOp, *sizeInBytes, "host constant folding byte size");
getGlobalOp.getResult(), if (failed(sizeAttr))
rewriter.getI32IntegerAttr(0), return failure();
rewriter.getI32IntegerAttr(0), pim::PimMemCopyHostToDevOp::create(
rewriter.getI32IntegerAttr(sizeInBytes)); rewriter, mapOp.getLoc(), initType, zeroOffset, zeroOffset, mapOp.getInit(), getGlobalOp.getResult(), *sizeAttr);
rewriter.eraseOp(mapOp); rewriter.eraseOp(mapOp);
return success(); return success();
} }
@@ -487,7 +488,9 @@ struct FoldConstantMemCpPattern final : OpRewritePattern<pim::PimMemCopyOp> {
if (!allocType || !allocType.hasStaticShape()) if (!allocType || !allocType.hasStaticShape())
return failure(); return failure();
if (copyOp.getTargetOffset() != 0 || copyOp.getSourceOffset() != 0) auto targetOffset = resolveIndexValue(copyOp.getTargetOffset());
auto sourceOffset = resolveIndexValue(copyOp.getSourceOffset());
if (failed(targetOffset) || failed(sourceOffset) || *targetOffset != 0 || *sourceOffset != 0)
return failure(); return failure();
auto moduleOp = copyOp->getParentOfType<ModuleOp>(); auto moduleOp = copyOp->getParentOfType<ModuleOp>();
@@ -0,0 +1,14 @@
add_pim_library(OMPimMemoryCoalescing
MemoryCoalescing.cpp
MemoryCoalescing.hpp
MemoryCoalescingPass.cpp
EXCLUDE_FROM_OM_LIBS
INCLUDE_DIRS PUBLIC
${PIM_PUBLIC_INCLUDE_DIRS}
LINK_LIBS PUBLIC
OMPimCommon
PimOps
)
@@ -0,0 +1,230 @@
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/Interfaces/DestinationStyleOpInterface.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallVector.h"
#include <limits>
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
#include "src/Accelerators/PIM/Dialect/Pim/Transforms/MemoryCoalescing/MemoryCoalescing.hpp"
using namespace mlir;
namespace onnx_mlir {
namespace pim {
namespace {
static bool isSupportedAliasOp(Operation* op) {
return isa<memref::SubViewOp, memref::CastOp, memref::CollapseShapeOp, memref::ExpandShapeOp>(op);
}
static bool isCandidateAllocType(MemRefType type) {
return type && type.hasStaticShape() && type.getLayout().isIdentity()
&& hasByteSizedElementType(type.getElementType());
}
static uint64_t getTypeSizeBytes(MemRefType type) {
return static_cast<uint64_t>(type.getNumElements() * getElementTypeSizeInBytes(type.getElementType()));
}
static Operation* getTopLevelAncestorInBlock(Operation* op, Block& block) {
Operation* current = op;
while (current && current->getBlock() != &block)
current = current->getParentOp();
return current;
}
static void analyzeBlock(Block& block, MemoryCoalescingAnalysis& analysis);
static FailureOr<uint64_t>
getLastUseInstruction(memref::AllocOp allocOp, Block& scopeBlock, const DenseMap<Operation*, uint64_t>& opOrder) {
uint64_t endInstruction = opOrder.lookup(allocOp);
SmallPtrSet<Value, 16> visitedValues;
SmallPtrSet<Operation*, 16> visitedUsers;
SmallVector<Value> pendingValues;
pendingValues.push_back(allocOp.getResult());
while (!pendingValues.empty()) {
Value value = pendingValues.pop_back_val();
if (!visitedValues.insert(value).second)
continue;
for (Operation* user : value.getUsers()) {
if (!visitedUsers.insert(user).second)
continue;
if (isSupportedAliasOp(user))
llvm::append_range(pendingValues, user->getResults());
if (auto dpsOp = dyn_cast<DestinationStyleOpInterface>(user)) {
for (OpResult result : user->getResults()) {
OpOperand* tiedOperand = dpsOp.getTiedOpOperand(result);
if (tiedOperand && tiedOperand->get() == value)
pendingValues.push_back(result);
}
}
if (auto forOp = dyn_cast<scf::ForOp>(user)) {
for (auto [index, initArg] : llvm::enumerate(forOp.getInitArgs())) {
if (initArg != value)
continue;
pendingValues.push_back(forOp.getRegionIterArgs()[index]);
pendingValues.push_back(forOp.getResult(index));
}
}
if (auto yieldOp = dyn_cast<scf::YieldOp>(user)) {
auto forOp = dyn_cast<scf::ForOp>(yieldOp->getParentOp());
if (!forOp)
return failure();
for (auto [index, operand] : llvm::enumerate(yieldOp.getOperands()))
if (operand == value)
pendingValues.push_back(forOp.getResult(index));
}
Operation* orderedUser = getTopLevelAncestorInBlock(user, scopeBlock);
if (!orderedUser)
return failure();
auto order = opOrder.find(orderedUser);
if (order == opOrder.end())
return failure();
endInstruction = std::max(endInstruction, order->second);
}
}
return endInstruction;
}
static void analyzeBlock(Block& block, MemoryCoalescingAnalysis& analysis) {
for (Operation& op : block)
for (Region& region : op.getRegions())
for (Block& nestedBlock : region)
analyzeBlock(nestedBlock, analysis);
DenseMap<Operation*, uint64_t> opOrder;
uint64_t nextInstruction = 0;
for (Operation& op : block)
opOrder.try_emplace(&op, nextInstruction++);
MemoryCoalescingBlockAnalysis blockAnalysis;
blockAnalysis.block = &block;
for (Operation& op : block) {
auto allocOp = dyn_cast<memref::AllocOp>(&op);
if (!allocOp)
continue;
auto allocType = dyn_cast<MemRefType>(allocOp.getType());
if (!isCandidateAllocType(allocType)) {
++blockAnalysis.skippedAllocations;
continue;
}
auto endInstruction = getLastUseInstruction(allocOp, block, opOrder);
if (failed(endInstruction)) {
++blockAnalysis.skippedAllocations;
continue;
}
blockAnalysis.candidates.push_back(
AllocationCandidate {allocOp, &block, opOrder.lookup(allocOp), *endInstruction, getTypeSizeBytes(allocType)});
}
analysis.skippedAllocations += blockAnalysis.skippedAllocations;
if (!blockAnalysis.candidates.empty() || blockAnalysis.skippedAllocations != 0)
analysis.blocks.push_back(std::move(blockAnalysis));
}
} // namespace
uint64_t MemoryCoalescingAnalysis::getCandidateCount() const {
uint64_t total = 0;
for (const MemoryCoalescingBlockAnalysis& block : blocks)
total += block.candidates.size();
return total;
}
MemoryCoalescingAnalysis analyzeMemoryCoalescingCandidates(Operation* coreLikeOp) {
MemoryCoalescingAnalysis analysis;
if (!coreLikeOp || coreLikeOp->getNumRegions() != 1 || coreLikeOp->getRegion(0).empty())
return analysis;
analyzeBlock(coreLikeOp->getRegion(0).front(), analysis);
return analysis;
}
MemoryCoalescingStats
coalesceMemory(Operation* coreLikeOp, const MemoryCoalescingAnalysis& analysis, RewriterBase& rewriter) {
(void) coreLikeOp;
MemoryCoalescingStats stats;
stats.skippedAllocations = analysis.skippedAllocations;
for (const MemoryCoalescingBlockAnalysis& blockAnalysis : analysis.blocks) {
auto candidates = blockAnalysis.candidates;
llvm::sort(candidates, [](const AllocationCandidate& lhs, const AllocationCandidate& rhs) {
if (lhs.startInstruction != rhs.startInstruction)
return lhs.startInstruction < rhs.startInstruction;
return lhs.endInstruction < rhs.endInstruction;
});
struct ActiveStorage {
memref::AllocOp root;
uint64_t endInstruction = 0;
};
SmallVector<ActiveStorage> active;
SmallVector<memref::AllocOp> freeList;
for (AllocationCandidate& candidate : candidates) {
for (auto it = active.begin(); it != active.end();) {
if (it->endInstruction < candidate.startInstruction) {
freeList.push_back(it->root);
it = active.erase(it);
continue;
}
++it;
}
auto bestFit = freeList.end();
uint64_t bestFitBytes = std::numeric_limits<uint64_t>::max();
auto candidateType = cast<MemRefType>(candidate.alloc.getType());
for (auto it = freeList.begin(); it != freeList.end(); ++it) {
auto freeType = cast<MemRefType>((*it).getType());
if (freeType != candidateType)
continue;
uint64_t freeBytes = getTypeSizeBytes(freeType);
if (freeBytes < candidate.sizeBytes || freeBytes >= bestFitBytes)
continue;
bestFit = it;
bestFitBytes = freeBytes;
}
if (bestFit == freeList.end()) {
active.push_back(ActiveStorage {candidate.alloc, candidate.endInstruction});
continue;
}
memref::AllocOp root = *bestFit;
freeList.erase(bestFit);
candidate.alloc.getResult().replaceAllUsesWith(root.getResult());
rewriter.eraseOp(candidate.alloc);
active.push_back(ActiveStorage {root, candidate.endInstruction});
++stats.removedAllocs;
stats.savedBytes += candidate.sizeBytes;
}
}
return stats;
}
} // namespace pim
} // namespace onnx_mlir
@@ -0,0 +1,44 @@
#pragma once
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/IR/PatternMatch.h"
#include "llvm/ADT/SmallVector.h"
namespace onnx_mlir {
namespace pim {
struct AllocationCandidate {
mlir::memref::AllocOp alloc;
mlir::Block* scopeBlock = nullptr;
uint64_t startInstruction = 0;
uint64_t endInstruction = 0;
uint64_t sizeBytes = 0;
};
struct MemoryCoalescingBlockAnalysis {
mlir::Block* block = nullptr;
llvm::SmallVector<AllocationCandidate> candidates;
uint64_t skippedAllocations = 0;
};
struct MemoryCoalescingAnalysis {
llvm::SmallVector<MemoryCoalescingBlockAnalysis> blocks;
uint64_t skippedAllocations = 0;
uint64_t getCandidateCount() const;
};
struct MemoryCoalescingStats {
uint64_t removedAllocs = 0;
uint64_t savedBytes = 0;
uint64_t skippedAllocations = 0;
};
MemoryCoalescingAnalysis analyzeMemoryCoalescingCandidates(mlir::Operation* coreLikeOp);
MemoryCoalescingStats
coalesceMemory(mlir::Operation* coreLikeOp, const MemoryCoalescingAnalysis& analysis, mlir::RewriterBase& rewriter);
} // namespace pim
} // namespace onnx_mlir
@@ -10,10 +10,11 @@
#include "Common/IR/CompactAsmUtils.hpp" #include "Common/IR/CompactAsmUtils.hpp"
#include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.hpp" #include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.hpp"
#include "src/Accelerators/PIM/Common/PimCommon.hpp" #include "src/Accelerators/PIM/Common/PimCommon.hpp"
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
#include "src/Accelerators/PIM/Common/Support/DebugDump.hpp" #include "src/Accelerators/PIM/Common/Support/DebugDump.hpp"
#include "src/Accelerators/PIM/Common/Support/ReportUtils.hpp" #include "src/Accelerators/PIM/Common/Support/ReportUtils.hpp"
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp" #include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
#include "src/Accelerators/PIM/Dialect/Pim/Transforms/StaticMemoryCoalescing/StaticMemoryCoalescing.hpp" #include "src/Accelerators/PIM/Dialect/Pim/Transforms/MemoryCoalescing/MemoryCoalescing.hpp"
#include "src/Accelerators/PIM/Pass/PIMPasses.h" #include "src/Accelerators/PIM/Pass/PIMPasses.h"
using namespace mlir; using namespace mlir;
@@ -22,9 +23,9 @@ using namespace onnx_mlir::compact_asm;
namespace onnx_mlir { namespace onnx_mlir {
namespace { namespace {
// This pass assumes bufferization has already normalized executable PIM // This pass is an IR cleanup step after bufferization. It only rewrites
// operands. It only reuses compatible local allocations with non-overlapping // obviously compatible local allocations with non-overlapping lifetimes inside
// lifetimes; it does not repair memory contiguity. // the same block and leaves the final physical memory planning to codegen.
struct CoalescingReportRow { struct CoalescingReportRow {
uint64_t numCandidates = 0; uint64_t numCandidates = 0;
@@ -151,34 +152,39 @@ static void emitReport(ArrayRef<CoalescingReportEntry> entries) {
file.close(); file.close();
} }
struct StaticMemoryCoalescingPass : PassWrapper<StaticMemoryCoalescingPass, OperationPass<ModuleOp>> { struct PimMemoryCoalescingPass : PassWrapper<PimMemoryCoalescingPass, OperationPass<ModuleOp>> {
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(StaticMemoryCoalescingPass) MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(PimMemoryCoalescingPass)
StringRef getArgument() const override { return "pim-static-memory-coalescing"; } StringRef getArgument() const override { return "pim-memory-coalescing"; }
StringRef getDescription() const override { return "Analyze static local PIM memory reuse opportunities"; } StringRef getDescription() const override { return "Analyze local PIM memory reuse opportunities"; }
StaticMemoryCoalescingPass() = default; PimMemoryCoalescingPass() = default;
StaticMemoryCoalescingPass(const StaticMemoryCoalescingPass& pass) {} PimMemoryCoalescingPass(const PimMemoryCoalescingPass& pass) {}
void runOnOperation() override { void runOnOperation() override {
IRRewriter rewriter(&getContext()); IRRewriter rewriter(&getContext());
SmallVector<CoalescingReportEntry, 32> reportEntries; SmallVector<CoalescingReportEntry, 32> reportEntries;
uint64_t nextBatchId = 0; uint64_t nextBatchId = 0;
bool hasFailure = false;
getOperation().walk([&](Operation* op) { getOperation().walk([&](Operation* op) {
if (!isa<pim::PimCoreOp, pim::PimCoreBatchOp>(op)) if (hasFailure || !isa<pim::PimCoreOp, pim::PimCoreBatchOp>(op))
return; return;
auto analysis = pim::analyzeStaticMemoryCoalescingCandidates(op); auto analysis = pim::analyzeMemoryCoalescingCandidates(op);
auto stats = pim::coalesceStaticMemory(op, rewriter); auto stats = pim::coalesceMemory(op, analysis, rewriter);
CoalescingReportRow row { CoalescingReportRow row {
analysis.candidates.size(), stats.skippedAllocations, stats.removedAllocs, stats.savedBytes}; analysis.getCandidateCount(), stats.skippedAllocations, stats.removedAllocs, stats.savedBytes};
if (auto coreOp = dyn_cast<pim::PimCoreOp>(op)) { if (auto coreOp = dyn_cast<pim::PimCoreOp>(op)) {
reportEntries.push_back({CoalescingReportEntry::Kind::Core, auto checkedCoreId =
static_cast<uint64_t>(coreOp.getCoreId()), pim::checkedI32(static_cast<uint64_t>(coreOp.getCoreId()), coreOp, "memory coalescing core id");
{static_cast<int32_t>(coreOp.getCoreId())}, if (failed(checkedCoreId)) {
row}); hasFailure = true;
return;
}
reportEntries.push_back(
{CoalescingReportEntry::Kind::Core, static_cast<uint64_t>(coreOp.getCoreId()), {*checkedCoreId}, row});
return; return;
} }
@@ -191,6 +197,11 @@ struct StaticMemoryCoalescingPass : PassWrapper<StaticMemoryCoalescingPass, Oper
reportEntries.push_back(std::move(entry)); reportEntries.push_back(std::move(entry));
}); });
if (hasFailure) {
signalPassFailure();
return;
}
emitReport(reportEntries); emitReport(reportEntries);
dumpModule(getOperation(), "pim2_coalesced"); dumpModule(getOperation(), "pim2_coalesced");
} }
@@ -198,6 +209,6 @@ struct StaticMemoryCoalescingPass : PassWrapper<StaticMemoryCoalescingPass, Oper
} // namespace } // namespace
std::unique_ptr<Pass> createPimStaticMemoryCoalescingPass() { return std::make_unique<StaticMemoryCoalescingPass>(); } std::unique_ptr<Pass> createPimMemoryCoalescingPass() { return std::make_unique<PimMemoryCoalescingPass>(); }
} // namespace onnx_mlir } // namespace onnx_mlir
@@ -1,14 +0,0 @@
add_pim_library(OMPimStaticMemoryCoalescing
StaticMemoryCoalescing.cpp
StaticMemoryCoalescing.hpp
StaticMemoryCoalescingPass.cpp
EXCLUDE_FROM_OM_LIBS
INCLUDE_DIRS PUBLIC
${PIM_PUBLIC_INCLUDE_DIRS}
LINK_LIBS PUBLIC
OMPimCommon
PimOps
)
@@ -1,179 +0,0 @@
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/Interfaces/DestinationStyleOpInterface.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallVector.h"
#include <limits>
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
#include "src/Accelerators/PIM/Dialect/Pim/Transforms/StaticMemoryCoalescing/StaticMemoryCoalescing.hpp"
using namespace mlir;
namespace onnx_mlir {
namespace pim {
namespace {
static bool isSupportedAliasOp(Operation* op) {
return isa<memref::SubViewOp, memref::CastOp, memref::CollapseShapeOp, memref::ExpandShapeOp>(op);
}
static bool isCandidateAllocType(MemRefType type) {
return type && type.hasStaticShape() && type.getLayout().isIdentity()
&& hasByteSizedElementType(type.getElementType());
}
static uint64_t getTypeSizeBytes(MemRefType type) {
return static_cast<uint64_t>(type.getNumElements() * getElementTypeSizeInBytes(type.getElementType()));
}
static FailureOr<uint64_t>
getLastUseInstruction(memref::AllocOp allocOp, Block& body, const DenseMap<Operation*, uint64_t>& opOrder) {
uint64_t endInstruction = opOrder.lookup(allocOp);
SmallPtrSet<Operation*, 16> visited;
SmallVector<Value> pendingValues;
pendingValues.push_back(allocOp.getResult());
while (!pendingValues.empty()) {
Value value = pendingValues.pop_back_val();
for (Operation* user : value.getUsers()) {
if (user->getBlock() != &body)
return failure();
if (!visited.insert(user).second)
continue;
if (isSupportedAliasOp(user))
for (Value result : user->getResults())
pendingValues.push_back(result);
if (auto forOp = dyn_cast<scf::ForOp>(user)) {
for (auto [index, initArg] : llvm::enumerate(forOp.getInitArgs()))
if (initArg == value)
pendingValues.push_back(forOp.getResult(index));
}
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);
}
}
auto order = opOrder.find(user);
if (order == opOrder.end())
return failure();
endInstruction = std::max(endInstruction, order->second);
}
}
return endInstruction;
}
} // namespace
StaticMemoryCoalescingAnalysis analyzeStaticMemoryCoalescingCandidates(Operation* coreLikeOp) {
StaticMemoryCoalescingAnalysis analysis;
if (!coreLikeOp || coreLikeOp->getNumRegions() != 1 || coreLikeOp->getRegion(0).empty())
return analysis;
Block& body = coreLikeOp->getRegion(0).front();
DenseMap<Operation*, uint64_t> opOrder;
uint64_t nextInstruction = 0;
for (Operation& op : body)
opOrder.try_emplace(&op, nextInstruction++);
for (Operation& op : body) {
auto allocOp = dyn_cast<memref::AllocOp>(&op);
if (!allocOp)
continue;
auto allocType = dyn_cast<MemRefType>(allocOp.getType());
if (!isCandidateAllocType(allocType)) {
++analysis.skippedAllocations;
continue;
}
auto endInstruction = getLastUseInstruction(allocOp, body, opOrder);
if (failed(endInstruction)) {
++analysis.skippedAllocations;
continue;
}
analysis.candidates.push_back(
StaticAllocationCandidate {allocOp, opOrder.lookup(allocOp), *endInstruction, getTypeSizeBytes(allocType)});
}
return analysis;
}
StaticMemoryCoalescingStats coalesceStaticMemory(Operation* coreLikeOp, RewriterBase& rewriter) {
StaticMemoryCoalescingStats stats;
auto analysis = analyzeStaticMemoryCoalescingCandidates(coreLikeOp);
stats.skippedAllocations = analysis.skippedAllocations;
llvm::sort(analysis.candidates, [](const StaticAllocationCandidate& lhs, const StaticAllocationCandidate& rhs) {
if (lhs.startInstruction != rhs.startInstruction)
return lhs.startInstruction < rhs.startInstruction;
return lhs.endInstruction < rhs.endInstruction;
});
struct ActiveStorage {
memref::AllocOp root;
uint64_t endInstruction = 0;
};
SmallVector<ActiveStorage> active;
SmallVector<memref::AllocOp> freeList;
for (StaticAllocationCandidate& candidate : analysis.candidates) {
for (auto it = active.begin(); it != active.end();) {
if (it->endInstruction < candidate.startInstruction) {
freeList.push_back(it->root);
it = active.erase(it);
continue;
}
++it;
}
auto bestFit = freeList.end();
uint64_t bestFitBytes = std::numeric_limits<uint64_t>::max();
auto candidateType = cast<MemRefType>(candidate.alloc.getType());
for (auto it = freeList.begin(); it != freeList.end(); ++it) {
auto freeType = cast<MemRefType>((*it).getType());
if (freeType != candidateType)
continue;
uint64_t freeBytes = getTypeSizeBytes(freeType);
if (freeBytes < candidate.sizeBytes || freeBytes >= bestFitBytes)
continue;
bestFit = it;
bestFitBytes = freeBytes;
}
if (bestFit == freeList.end()) {
active.push_back(ActiveStorage {candidate.alloc, candidate.endInstruction});
continue;
}
memref::AllocOp root = *bestFit;
freeList.erase(bestFit);
candidate.alloc.getResult().replaceAllUsesWith(root.getResult());
rewriter.eraseOp(candidate.alloc);
active.push_back(ActiveStorage {root, candidate.endInstruction});
++stats.removedAllocs;
stats.savedBytes += candidate.sizeBytes;
}
return stats;
}
} // namespace pim
} // namespace onnx_mlir
@@ -1,34 +0,0 @@
#pragma once
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/IR/PatternMatch.h"
#include "llvm/ADT/SmallVector.h"
namespace onnx_mlir {
namespace pim {
struct StaticAllocationCandidate {
mlir::memref::AllocOp alloc;
uint64_t startInstruction = 0;
uint64_t endInstruction = 0;
uint64_t sizeBytes = 0;
};
struct StaticMemoryCoalescingAnalysis {
llvm::SmallVector<StaticAllocationCandidate> candidates;
uint64_t skippedAllocations = 0;
};
struct StaticMemoryCoalescingStats {
uint64_t removedAllocs = 0;
uint64_t savedBytes = 0;
uint64_t skippedAllocations = 0;
};
StaticMemoryCoalescingAnalysis analyzeStaticMemoryCoalescingCandidates(mlir::Operation* coreLikeOp);
StaticMemoryCoalescingStats coalesceStaticMemory(mlir::Operation* coreLikeOp, mlir::RewriterBase& rewriter);
} // namespace pim
} // namespace onnx_mlir
@@ -0,0 +1,11 @@
add_pim_library(OMPimVerification
VerificationPass.cpp
EXCLUDE_FROM_OM_LIBS
LINK_LIBS PUBLIC
OMPimCommon
OMPimBufferization
PimOps
SpatialOps
)
@@ -13,6 +13,7 @@
#include "src/Accelerators/PIM/Common/PimCommon.hpp" #include "src/Accelerators/PIM/Common/PimCommon.hpp"
#include "src/Accelerators/PIM/Common/Support/Diagnostics.hpp" #include "src/Accelerators/PIM/Common/Support/Diagnostics.hpp"
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp" #include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
#include "src/Accelerators/PIM/Dialect/Pim/Transforms/Bufferization/ContiguityPatterns.hpp"
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp" #include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
using namespace mlir; using namespace mlir;
@@ -45,19 +46,6 @@ static bool isCodegenAddressableValue(Value value) {
|| isa<memref::AllocOp, memref::GetGlobalOp>(compiledAddress->base.getDefiningOp()); || isa<memref::AllocOp, memref::GetGlobalOp>(compiledAddress->base.getDefiningOp());
} }
static bool isCodegenAddressableValue(Value value, const StaticValueKnowledge& knowledge) {
auto resolvedAddress = resolveContiguousAddress(value, knowledge);
if (succeeded(resolvedAddress))
return isa<BlockArgument>(resolvedAddress->base)
|| isa<memref::AllocOp, memref::GetGlobalOp>(resolvedAddress->base.getDefiningOp());
auto compiledAddress = compileContiguousAddressExpr(value);
if (failed(compiledAddress))
return false;
return isa<BlockArgument>(compiledAddress->base)
|| isa<memref::AllocOp, memref::GetGlobalOp>(compiledAddress->base.getDefiningOp());
}
static bool isConstantGlobalView(Value value) { static bool isConstantGlobalView(Value value) {
while (true) { while (true) {
Operation* defOp = value.getDefiningOp(); Operation* defOp = value.getDefiningOp();
@@ -137,6 +125,24 @@ static bool isSupportedCoreInstructionOp(Operation* op) {
memref::GetGlobalOp>(op); memref::GetGlobalOp>(op);
} }
static bool isHostAddressableValue(Value value, const StaticValueKnowledge& knowledge) {
auto resolvedAddress = resolveContiguousAddress(value, knowledge);
Value base;
if (succeeded(resolvedAddress)) {
base = resolvedAddress->base;
}
else {
auto compiledAddress = compileContiguousAddressExpr(value);
if (failed(compiledAddress))
return false;
base = compiledAddress->base;
}
if (isa<BlockArgument>(base))
return true;
return isa_and_nonnull<memref::GetGlobalOp>(base.getDefiningOp());
}
struct VerificationPass : PassWrapper<VerificationPass, OperationPass<ModuleOp>> { struct VerificationPass : PassWrapper<VerificationPass, OperationPass<ModuleOp>> {
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(VerificationPass) MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(VerificationPass)
@@ -310,10 +316,10 @@ private:
} }
if (isExplicitHostMemCopyOperand(&op, operandIndex)) { if (isExplicitHostMemCopyOperand(&op, operandIndex)) {
if (!isCodegenAddressableValue(operand, knowledge)) { if (!isHostAddressableValue(operand, knowledge)) {
diagnostics.report(&op, [&](Operation* illegalOp) { diagnostics.report(&op, [&](Operation* illegalOp) {
illegalOp->emitOpError() << "host operand #" << operandIndex illegalOp->emitOpError() << "host operand #" << operandIndex
<< " is not backed by contiguous addressable storage"; << " must be backed by host-addressable storage";
}); });
hasFailure = true; hasFailure = true;
} }
@@ -333,6 +339,12 @@ private:
} }
if (auto storeOp = dyn_cast<pim::PimMemCopyDevToHostOp>(op)) { if (auto storeOp = dyn_cast<pim::PimMemCopyDevToHostOp>(op)) {
if (!pim::isNormalizedCopyOp(storeOp)) {
diagnostics.report(&op, [](Operation* illegalOp) {
illegalOp->emitOpError("must use base memref operands plus explicit byte offsets after bufferization");
});
hasFailure = true;
}
if (failed(resolveIndexValue(storeOp.getHostTargetOffset(), knowledge)) if (failed(resolveIndexValue(storeOp.getHostTargetOffset(), knowledge))
|| failed(resolveIndexValue(storeOp.getDeviceSourceOffset(), knowledge))) { || failed(resolveIndexValue(storeOp.getDeviceSourceOffset(), knowledge))) {
diagnostics.report(&op, [](Operation* illegalOp) { diagnostics.report(&op, [](Operation* illegalOp) {
@@ -343,6 +355,12 @@ private:
} }
if (auto loadOp = dyn_cast<pim::PimMemCopyHostToDevOp>(op)) { if (auto loadOp = dyn_cast<pim::PimMemCopyHostToDevOp>(op)) {
if (!pim::isNormalizedCopyOp(loadOp)) {
diagnostics.report(&op, [](Operation* illegalOp) {
illegalOp->emitOpError("must use base memref operands plus explicit byte offsets after bufferization");
});
hasFailure = true;
}
if (failed(resolveIndexValue(loadOp.getDeviceTargetOffset(), knowledge)) if (failed(resolveIndexValue(loadOp.getDeviceTargetOffset(), knowledge))
|| failed(resolveIndexValue(loadOp.getHostSourceOffset(), knowledge))) { || failed(resolveIndexValue(loadOp.getHostSourceOffset(), knowledge))) {
diagnostics.report(&op, [](Operation* illegalOp) { diagnostics.report(&op, [](Operation* illegalOp) {
@@ -351,6 +369,22 @@ private:
hasFailure = true; hasFailure = true;
} }
} }
if (auto copyOp = dyn_cast<pim::PimMemCopyOp>(op)) {
if (!pim::isNormalizedCopyOp(copyOp)) {
diagnostics.report(&op, [](Operation* illegalOp) {
illegalOp->emitOpError("must use base memref operands plus explicit byte offsets after bufferization");
});
hasFailure = true;
}
if (failed(resolveIndexValue(copyOp.getTargetOffset(), knowledge))
|| failed(resolveIndexValue(copyOp.getSourceOffset(), knowledge))) {
diagnostics.report(&op, [](Operation* illegalOp) {
illegalOp->emitOpError("offset operands must be statically evaluable for PIM codegen");
});
hasFailure = true;
}
}
return success(!hasFailure); return success(!hasFailure);
}); });
} }
+19
View File
@@ -257,6 +257,25 @@ def SpatVAddOp : SpatOp<"vadd", []> {
}]; }];
} }
def SpatVSubOp : SpatOp<"vsub", []> {
let summary = "Element-wise subtraction between two tensors; rhs must match lhs or be 1x1";
let arguments = (ins
SpatTensor:$lhs,
SpatTensor:$rhs
);
let results = (outs
SpatTensor:$output
);
let hasVerifier = 1;
let assemblyFormat = [{
$lhs `,` $rhs attr-dict `:` `(` type($lhs) `,` type($rhs) `)` `->` type($output)
}];
}
def SpatVMulOp : SpatOp<"vmul", []> { def SpatVMulOp : SpatOp<"vmul", []> {
let summary = "Element-wise multiplication between two tensors; rhs must match lhs or be 1x1"; let summary = "Element-wise multiplication between two tensors; rhs must match lhs or be 1x1";
+13 -34
View File
@@ -13,6 +13,8 @@
#include "llvm/ADT/DenseSet.h" #include "llvm/ADT/DenseSet.h"
#include "llvm/Support/LogicalResult.h" #include "llvm/Support/LogicalResult.h"
#include "src/Accelerators/PIM/Common/IR/AffineUtils.hpp"
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.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/CompileTime.hpp" #include "src/Accelerators/PIM/Conversion/ONNXToSpatial/CompileTime.hpp"
@@ -56,48 +58,19 @@ static LogicalResult verifyStaticWeights(ComputeOpTy computeOp, StringRef kind)
return success(); return success();
} }
static bool isConstantIndexLike(Value value) {
APInt constantValue;
return matchPattern(value, m_ConstantInt(&constantValue));
}
static bool isSupportedLaneAffineExpr(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 isSupportedLaneAffineExpr(binaryExpr.getLHS()) && isSupportedLaneAffineExpr(binaryExpr.getRHS());
}
case AffineExprKind::Mul: {
auto binaryExpr = cast<AffineBinaryOpExpr>(expr);
return (isa<AffineConstantExpr>(binaryExpr.getLHS()) && isSupportedLaneAffineExpr(binaryExpr.getRHS()))
|| (isa<AffineConstantExpr>(binaryExpr.getRHS()) && isSupportedLaneAffineExpr(binaryExpr.getLHS()));
}
case AffineExprKind::FloorDiv:
case AffineExprKind::CeilDiv:
case AffineExprKind::Mod: {
auto binaryExpr = cast<AffineBinaryOpExpr>(expr);
return isa<AffineConstantExpr>(binaryExpr.getRHS()) && isSupportedLaneAffineExpr(binaryExpr.getLHS());
}
}
llvm_unreachable("unexpected affine expression kind");
}
static bool isSupportedLaneOffsetExpr(Value value, BlockArgument laneArg) { static bool isSupportedLaneOffsetExpr(Value value, BlockArgument laneArg) {
if (value == laneArg || isConstantIndexLike(value)) if (value == laneArg || matchConstantIndexValue(value))
return true; return true;
auto affineApply = value.getDefiningOp<affine::AffineApplyOp>(); auto affineApply = value.getDefiningOp<affine::AffineApplyOp>();
if (affineApply) { if (affineApply) {
if (affineApply.getAffineMap().getNumResults() != 1 || affineApply.getAffineMap().getNumSymbols() != 0) if (!isSingleResultSymbolFreeAffineMap(affineApply.getAffineMap()))
return false; return false;
if (!llvm::all_of(affineApply.getMapOperands(), if (!llvm::all_of(affineApply.getMapOperands(),
[&](Value operand) { return isSupportedLaneOffsetExpr(operand, laneArg); })) { [&](Value operand) { return isSupportedLaneOffsetExpr(operand, laneArg); })) {
return false; return false;
} }
return isSupportedLaneAffineExpr(affineApply.getAffineMap().getResult(0)); return isDimAndConstantAffineExpr(affineApply.getAffineMap().getResult(0));
} }
auto extractOp = value.getDefiningOp<tensor::ExtractOp>(); auto extractOp = value.getDefiningOp<tensor::ExtractOp>();
@@ -112,8 +85,8 @@ static bool isSupportedLaneOffsetExpr(Value value, BlockArgument laneArg) {
auto addOp = value.getDefiningOp<arith::AddIOp>(); auto addOp = value.getDefiningOp<arith::AddIOp>();
if (!addOp) if (!addOp)
return false; return false;
return (addOp.getLhs() == laneArg && isConstantIndexLike(addOp.getRhs())) return (addOp.getLhs() == laneArg && matchConstantIndexValue(addOp.getRhs()))
|| (addOp.getRhs() == laneArg && isConstantIndexLike(addOp.getLhs())); || (addOp.getRhs() == laneArg && matchConstantIndexValue(addOp.getLhs()));
} }
static LogicalResult static LogicalResult
@@ -281,6 +254,12 @@ LogicalResult SpatVAddOp::verify() {
return OpTrait::impl::verifySameOperandsAndResultType(*this); return OpTrait::impl::verifySameOperandsAndResultType(*this);
} }
LogicalResult SpatVSubOp::verify() {
if (failed(OpTrait::impl::verifyAtLeastNOperands(*this, 2)))
return failure();
return OpTrait::impl::verifySameOperandsAndResultType(*this);
}
LogicalResult SpatVMaxOp::verify() { LogicalResult SpatVMaxOp::verify() {
if (failed(OpTrait::impl::verifyAtLeastNOperands(*this, 2))) if (failed(OpTrait::impl::verifyAtLeastNOperands(*this, 2)))
return failure(); return failure();
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,5 @@
#include "mlir/Analysis/TopologicalSortUtils.h" #include "mlir/Analysis/TopologicalSortUtils.h"
#include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/IR/IRMapping.h" #include "mlir/IR/IRMapping.h"
#include "mlir/IR/Location.h" #include "mlir/IR/Location.h"
#include "mlir/IR/PatternMatch.h" #include "mlir/IR/PatternMatch.h"
@@ -14,20 +13,14 @@
#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/SmallVector.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/raw_os_ostream.h" #include "llvm/Support/raw_os_ostream.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm> #include <algorithm>
#include <chrono>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <cstdlib>
#include <fstream> #include <fstream>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <tuple>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -37,6 +30,7 @@
#include "Scheduling/MergeSchedulingAnalysis.hpp" #include "Scheduling/MergeSchedulingAnalysis.hpp"
#include "src/Accelerators/PIM/Common/IR/CompactAsmUtils.hpp" #include "src/Accelerators/PIM/Common/IR/CompactAsmUtils.hpp"
#include "src/Accelerators/PIM/Common/PimCommon.hpp" #include "src/Accelerators/PIM/Common/PimCommon.hpp"
#include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp"
#include "src/Accelerators/PIM/Common/Support/ReportUtils.hpp" #include "src/Accelerators/PIM/Common/Support/ReportUtils.hpp"
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp" #include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp"
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp" #include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
@@ -50,99 +44,16 @@ using SpatCompute = spatial::SpatCompute;
using SpatComputeBatch = spatial::SpatComputeBatch; using SpatComputeBatch = spatial::SpatComputeBatch;
using spatial::getProducerValueRef; using spatial::getProducerValueRef;
bool isMergeProfilingEnabled() { return std::getenv("RAPTOR_PROFILE_MERGE") != nullptr; }
class ScopedMergePhaseTimer {
public:
explicit ScopedMergePhaseTimer(StringRef phaseName)
: enabled(isMergeProfilingEnabled()), phase(phaseName.str()) {
if (enabled)
start = std::chrono::steady_clock::now();
}
~ScopedMergePhaseTimer() {
if (!enabled)
return;
auto elapsed = std::chrono::steady_clock::now() - start;
double millis = std::chrono::duration<double, std::milli>(elapsed).count();
llvm::errs() << "[merge-profile] " << phase << ": " << llvm::formatv("{0:F3}", millis) << " ms\n";
}
private:
bool enabled = false;
std::string phase;
std::chrono::steady_clock::time_point start;
};
struct MergeIrCounts {
uint64_t topLevelComputeCount = 0;
uint64_t topLevelComputeBatchCount = 0;
uint64_t scalarChannelSendCount = 0;
uint64_t scalarChannelReceiveCount = 0;
uint64_t wvmmCount = 0;
uint64_t vaddCount = 0;
uint64_t scfForCount = 0;
};
MergeIrCounts collectMergeIrCounts(func::FuncOp funcOp) {
MergeIrCounts counts;
auto countComputeBodyOps = [&](Operation* op) {
op->walk([&](Operation* nestedOp) {
if (isa<spatial::SpatChannelSendOp>(nestedOp))
++counts.scalarChannelSendCount;
else if (isa<spatial::SpatChannelReceiveOp>(nestedOp))
++counts.scalarChannelReceiveCount;
else if (isa<spatial::SpatVMMOp>(nestedOp))
++counts.wvmmCount;
else if (isa<spatial::SpatVAddOp>(nestedOp))
++counts.vaddCount;
else if (isa<scf::ForOp>(nestedOp))
++counts.scfForCount;
});
};
for (auto compute : funcOp.getOps<SpatCompute>()) {
++counts.topLevelComputeCount;
countComputeBodyOps(compute.getOperation());
}
for (auto batch : funcOp.getOps<SpatComputeBatch>()) {
++counts.topLevelComputeBatchCount;
countComputeBodyOps(batch.getOperation());
}
return counts;
}
void emitMergeIrCounts(StringRef phaseName, func::FuncOp funcOp) {
if (!isMergeProfilingEnabled())
return;
MergeIrCounts counts = collectMergeIrCounts(funcOp);
llvm::errs() << "[merge-profile] " << phaseName << " counts:"
<< " compute=" << counts.topLevelComputeCount << " compute_batch=" << counts.topLevelComputeBatchCount
<< " scalar_send=" << counts.scalarChannelSendCount
<< " scalar_recv=" << counts.scalarChannelReceiveCount << " wvmm=" << counts.wvmmCount
<< " vadd=" << counts.vaddCount << " scf_for=" << counts.scfForCount << "\n";
}
static std::optional<int32_t> getComputeCoreId(SpatCompute compute) { static std::optional<int32_t> getComputeCoreId(SpatCompute compute) {
if (auto coreIdAttr = compute->getAttrOfType<IntegerAttr>(onnx_mlir::kCoreIdAttrName)) if (auto coreIdAttr = compute->getAttrOfType<IntegerAttr>(onnx_mlir::kCoreIdAttrName)) {
return static_cast<int32_t>(coreIdAttr.getInt()); auto checkedCoreId = pim::checkedI32(coreIdAttr.getInt(), compute, "merge compute core id");
if (failed(checkedCoreId))
return std::nullopt;
return *checkedCoreId;
}
return std::nullopt; return std::nullopt;
} }
struct ComputeMotifInfo {
uint64_t instructionCount = 0;
uint64_t weightedVmmCount = 0;
};
void appendUnique(SmallVector<size_t>& values, size_t value) {
if (!llvm::is_contained(values, value))
values.push_back(value);
}
bool isTrivialSerialMergeCandidate(SpatCompute compute) { bool isTrivialSerialMergeCandidate(SpatCompute compute) {
if (!compute->hasOneUse()) if (!compute->hasOneUse())
return false; return false;
@@ -261,212 +172,6 @@ void mergeTriviallyConnectedComputes(func::FuncOp funcOp) {
} }
} }
void emitMotifProfile(func::FuncOp funcOp) {
if (!std::getenv("DCP_MOTIF_PROFILE"))
return;
SmallVector<SpatCompute> computes(funcOp.getOps<SpatCompute>());
DenseMap<SpatCompute, size_t> computeToIndex;
computeToIndex.reserve(computes.size());
for (auto [index, compute] : llvm::enumerate(computes))
computeToIndex[compute] = index;
SmallVector<ComputeMotifInfo> computeInfos(computes.size());
SmallVector<SmallVector<size_t>> parents(computes.size());
SmallVector<SmallVector<size_t>> children(computes.size());
uint64_t weightedVmmNodeCount = 0;
uint64_t weightedVmmOpCount = 0;
uint64_t edgeCount = 0;
for (auto [index, compute] : llvm::enumerate(computes)) {
ComputeMotifInfo& info = computeInfos[index];
info.instructionCount = spatial::countComputeBodyInstructions(compute.getBody());
compute.getBody().walk([&](spatial::SpatVMMOp) { info.weightedVmmCount++; });
if (info.weightedVmmCount > 0) {
weightedVmmNodeCount++;
weightedVmmOpCount += info.weightedVmmCount;
}
for (Value input : compute.getInputs()) {
auto parent = dyn_cast<SpatCompute>(input.getDefiningOp());
if (!parent || parent == compute)
continue;
auto parentIt = computeToIndex.find(parent);
if (parentIt == computeToIndex.end())
continue;
size_t parentIndex = parentIt->second;
size_t oldParentCount = parents[index].size();
appendUnique(parents[index], parentIndex);
if (parents[index].size() != oldParentCount) {
appendUnique(children[parentIndex], index);
edgeCount++;
}
}
}
uint64_t maxFanIn = 0;
uint64_t maxFanOut = 0;
uint64_t fanIn16 = 0;
uint64_t fanIn64 = 0;
uint64_t fanIn256 = 0;
uint64_t fanOut16 = 0;
uint64_t fanOut64 = 0;
uint64_t fanOut256 = 0;
for (size_t index = 0; index < computes.size(); ++index) {
uint64_t fanIn = parents[index].size();
uint64_t fanOut = children[index].size();
maxFanIn = std::max(maxFanIn, fanIn);
maxFanOut = std::max(maxFanOut, fanOut);
fanIn16 += fanIn >= 16;
fanIn64 += fanIn >= 64;
fanIn256 += fanIn >= 256;
fanOut16 += fanOut >= 16;
fanOut64 += fanOut >= 64;
fanOut256 += fanOut >= 256;
}
uint64_t serialChainCount = 0;
uint64_t serialChainNodeCount = 0;
uint64_t maxSerialChain = 0;
for (size_t index = 0; index < computes.size(); ++index) {
if (parents[index].size() == 1 && children[parents[index][0]].size() == 1)
continue;
uint64_t chainLength = 1;
size_t current = index;
while (children[current].size() == 1) {
size_t child = children[current][0];
if (parents[child].size() != 1)
break;
chainLength++;
current = child;
}
if (chainLength >= 2) {
serialChainCount++;
serialChainNodeCount += chainLength;
maxSerialChain = std::max(maxSerialChain, chainLength);
}
}
SmallVector<size_t> incomingEdgeCount;
incomingEdgeCount.reserve(parents.size());
for (ArrayRef<size_t> parentList : parents)
incomingEdgeCount.push_back(parentList.size());
SmallVector<uint64_t> level(computes.size(), 0);
SmallVector<size_t> readyNodes;
readyNodes.reserve(computes.size());
for (size_t index = 0; index < computes.size(); ++index)
if (incomingEdgeCount[index] == 0)
readyNodes.push_back(index);
size_t readyIndex = 0;
while (readyIndex != readyNodes.size()) {
size_t current = readyNodes[readyIndex++];
for (size_t child : children[current]) {
level[child] = std::max(level[child], level[current] + 1);
assert(incomingEdgeCount[child] > 0 && "incoming edge count underflow");
incomingEdgeCount[child]--;
if (incomingEdgeCount[child] == 0)
readyNodes.push_back(child);
}
}
SmallVector<uint64_t> weightedVmmNodesByLevel;
for (size_t index = 0; index < computes.size(); ++index) {
if (computeInfos[index].weightedVmmCount == 0)
continue;
if (weightedVmmNodesByLevel.size() <= level[index])
weightedVmmNodesByLevel.resize(level[index] + 1, 0);
weightedVmmNodesByLevel[level[index]]++;
}
uint64_t maxWeightedVmmLevel = 0;
uint64_t wideWeightedVmmLevels64 = 0;
uint64_t wideWeightedVmmLevels256 = 0;
for (uint64_t count : weightedVmmNodesByLevel) {
maxWeightedVmmLevel = std::max(maxWeightedVmmLevel, count);
wideWeightedVmmLevels64 += count >= 64;
wideWeightedVmmLevels256 += count >= 256;
}
using ShapeKey = std::tuple<uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t>;
SmallVector<ShapeKey> weightedVmmShapeKeys;
for (auto [index, compute] : llvm::enumerate(computes)) {
const ComputeMotifInfo& info = computeInfos[index];
if (info.weightedVmmCount == 0)
continue;
weightedVmmShapeKeys.push_back({info.instructionCount,
info.weightedVmmCount,
static_cast<uint64_t>(compute.getWeights().size()),
static_cast<uint64_t>(compute.getInputs().size()),
static_cast<uint64_t>(parents[index].size()),
static_cast<uint64_t>(children[index].size())});
}
llvm::sort(weightedVmmShapeKeys);
SmallVector<std::pair<uint64_t, ShapeKey>> weightedVmmShapeCounts;
for (size_t index = 0; index < weightedVmmShapeKeys.size();) {
size_t next = index + 1;
while (next < weightedVmmShapeKeys.size() && weightedVmmShapeKeys[next] == weightedVmmShapeKeys[index])
next++;
weightedVmmShapeCounts.push_back({next - index, weightedVmmShapeKeys[index]});
index = next;
}
llvm::sort(weightedVmmShapeCounts, [](const auto& lhs, const auto& rhs) {
if (lhs.first != rhs.first)
return lhs.first > rhs.first;
return lhs.second < rhs.second;
});
llvm::errs() << llvm::formatv("[DCP-MOTIF] computes={0} edges={1} wvmmNodes={2} wvmmOps={3} "
"serialChains={4} serialChainNodes={5} maxSerialChain={6} "
"maxFanIn={7} maxFanOut={8} fanIn>=16/64/256={9}/{10}/{11} "
"fanOut>=16/64/256={12}/{13}/{14} topoVisited={15}\n",
computes.size(),
edgeCount,
weightedVmmNodeCount,
weightedVmmOpCount,
serialChainCount,
serialChainNodeCount,
maxSerialChain,
maxFanIn,
maxFanOut,
fanIn16,
fanIn64,
fanIn256,
fanOut16,
fanOut64,
fanOut256,
readyNodes.size());
llvm::errs() << llvm::formatv("[DCP-MOTIF] wvmmLevels={0} maxWvmmLevel={1} wideWvmmLevels>=64/256={2}/{3} "
"shapeGroups={4}\n",
weightedVmmNodesByLevel.size(),
maxWeightedVmmLevel,
wideWeightedVmmLevels64,
wideWeightedVmmLevels256,
weightedVmmShapeCounts.size());
for (size_t rank = 0, end = std::min<size_t>(weightedVmmShapeCounts.size(), 5); rank < end; ++rank) {
auto [count, shape] = weightedVmmShapeCounts[rank];
auto [insts, vmmOps, weights, inputs, fanIn, fanOut] = shape;
llvm::errs() << llvm::formatv("[DCP-MOTIF] wvmmShape rank={0} count={1} insts={2} vmmOps={3} "
"weights={4} inputs={5} fanIn={6} fanOut={7}\n",
rank,
count,
insts,
vmmOps,
weights,
inputs,
fanIn,
fanOut);
}
}
void generateReport(func::FuncOp funcOp, const std::string& name, size_t usedCpuCount = 0) { void generateReport(func::FuncOp funcOp, const std::string& name, size_t usedCpuCount = 0) {
std::fstream file = openReportFile(name); std::fstream file = openReportFile(name);
if (!file.is_open()) if (!file.is_open())
@@ -623,44 +328,27 @@ public:
void runOnOperation() override { void runOnOperation() override {
func::FuncOp func = getOperation(); func::FuncOp func = getOperation();
{ mergeTriviallyConnectedComputes(func);
ScopedMergePhaseTimer timer("trivial-serial-merge");
mergeTriviallyConnectedComputes(func);
}
if (std::getenv("DCP_MOTIF_PROFILE"))
emitMotifProfile(func);
const spatial::MergeScheduleResult* analysisResult = nullptr; const spatial::MergeScheduleResult* analysisResult = nullptr;
{ analysisResult = &getAnalysis<spatial::MergeSchedulingAnalysis>().getResult();
ScopedMergePhaseTimer timer("scheduling-analysis"); if (failed(spatial::MergeScheduleMaterializer().run(func, *analysisResult, nextChannelId))) {
analysisResult = &getAnalysis<spatial::MergeSchedulingAnalysis>().getResult(); signalPassFailure();
} return;
{
ScopedMergePhaseTimer timer("schedule-materialization");
if (failed(spatial::MergeScheduleMaterializer().run(func, *analysisResult, nextChannelId))) {
signalPassFailure();
return;
}
} }
emitMergeIrCounts("after-materialization", func); if (!sortTopologically(&func.getBody().front())) {
func.emitOpError("failed to topologically order merged Spatial IR");
{ signalPassFailure();
ScopedMergePhaseTimer timer("cleanup-topological-sort-report"); return;
if (!sortTopologically(&func.getBody().front())) {
func.emitOpError("failed to topologically order merged Spatial IR");
signalPassFailure();
return;
}
if (failed(verifySpatialCommunicationInvariants(func))) {
func.emitOpError("merged Spatial communication invariant verification failed");
signalPassFailure();
return;
}
emitMergeIrCounts("final-post-merge", func);
dumpModule(cast<ModuleOp>(func->getParentOp()), "spatial1_dcp_merged");
generateReport(func, "spatial_merge_report", analysisResult->cpuToLastComputeMap.size());
} }
if (failed(verifySpatialCommunicationInvariants(func))) {
func.emitOpError("merged Spatial communication invariant verification failed");
signalPassFailure();
return;
}
dumpModule(cast<ModuleOp>(func->getParentOp()), "spatial1_merged");
generateReport(func, "spatial_merge_report", analysisResult->cpuToLastComputeMap.size());
} }
}; };
@@ -21,6 +21,8 @@
#include "ComputeGraph.hpp" #include "ComputeGraph.hpp"
#include "ComputeInstanceUtils.hpp" #include "ComputeInstanceUtils.hpp"
#include "src/Accelerators/PIM/Common/IR/AffineUtils.hpp"
#include "src/Accelerators/PIM/Common/IR/ConstantUtils.hpp"
#include "src/Support/TypeUtilities.hpp" #include "src/Support/TypeUtilities.hpp"
namespace onnx_mlir { namespace onnx_mlir {
@@ -103,6 +105,28 @@ bool isProjectedBatchOffset(OpFoldResult offset, Value laneArg) {
&& succeeded(evaluateIndexLike(offset, bindings, /*lane=*/1, laneArg)); && succeeded(evaluateIndexLike(offset, bindings, /*lane=*/1, laneArg));
} }
std::optional<uint32_t> getConstantExtractLane(tensor::ExtractSliceOp extract) {
if (extract.getMixedOffsets().empty())
return std::nullopt;
OpFoldResult offset = extract.getMixedOffsets().front();
if (auto attr = llvm::dyn_cast<Attribute>(offset)) {
auto intAttr = dyn_cast<IntegerAttr>(attr);
if (!intAttr || intAttr.getInt() < 0)
return std::nullopt;
return static_cast<uint32_t>(intAttr.getInt());
}
Value offsetValue = llvm::cast<Value>(offset);
if (auto constantIndex = offsetValue.getDefiningOp<arith::ConstantIndexOp>()) {
if (constantIndex.value() < 0)
return std::nullopt;
return static_cast<uint32_t>(constantIndex.value());
}
return std::nullopt;
}
std::optional<Cost> getBatchProjectedInputTransferCost(SpatComputeBatch batch, Value input) { std::optional<Cost> getBatchProjectedInputTransferCost(SpatComputeBatch batch, Value input) {
auto inputIt = llvm::find(batch.getInputs(), input); auto inputIt = llvm::find(batch.getInputs(), input);
if (inputIt == batch.getInputs().end()) if (inputIt == batch.getInputs().end())
@@ -141,6 +165,101 @@ Cost getInputTransferCost(const ComputeInstance& consumerInstance, Value input)
return static_cast<Cost>(getSizeInBytes(inputType)); return static_cast<Cost>(getSizeInBytes(inputType));
} }
uint32_t getLaneOverlapCount(const ComputeInstance& lhs, const ComputeInstance& rhs) {
uint32_t lhsEnd = lhs.laneStart + lhs.laneCount;
uint32_t rhsEnd = rhs.laneStart + rhs.laneCount;
return std::max(lhs.laneStart, rhs.laneStart) < std::min(lhsEnd, rhsEnd)
? std::min(lhsEnd, rhsEnd) - std::max(lhs.laneStart, rhs.laneStart)
: 0;
}
Cost scaleTransferCostByLaneCount(Cost totalCost, uint32_t totalLaneCount, uint32_t fragmentLaneCount) {
assert(totalLaneCount > 0 && "laneCount must be positive");
assert(fragmentLaneCount > 0 && "fragmentLaneCount must be positive");
if (fragmentLaneCount >= totalLaneCount)
return totalCost;
return checkedMultiply(totalCost, static_cast<Cost>(fragmentLaneCount)) / static_cast<Cost>(totalLaneCount);
}
SmallVector<ProducerValueRef, 4> collectProducerValueRefs(Value value, const ComputeInstance& consumerInstance) {
SmallVector<ProducerValueRef, 4> producers;
Operation* op = value.getDefiningOp();
if (!op)
return producers;
while (auto extract = dyn_cast<tensor::ExtractSliceOp>(op)) {
Value source = extract.getSource();
auto batch = dyn_cast_or_null<SpatComputeBatch>(source.getDefiningOp());
if (batch && batch.getNumResults() != 0) {
if (std::optional<uint32_t> lane = getConstantExtractLane(extract)) {
ComputeInstance instance = getBatchChunkForLane(batch, *lane);
producers.push_back({instance, 0});
return producers;
}
if (isa<SpatComputeBatch>(consumerInstance.op))
for (ComputeInstance instance :
getBatchChunksForRange(batch, consumerInstance.laneStart, consumerInstance.laneCount))
producers.push_back({instance, 0});
else
for (ComputeInstance instance : getBatchChunksForRange(batch, 0, static_cast<uint32_t>(batch.getLaneCount())))
producers.push_back({instance, 0});
return producers;
}
value = source;
op = value.getDefiningOp();
if (!op)
return producers;
}
if (auto compute = dyn_cast<SpatCompute>(op)) {
producers.push_back({
ComputeInstance {compute.getOperation(), 0, 1},
static_cast<size_t>(cast<OpResult>(value).getResultNumber())
});
return producers;
}
if (auto batch = dyn_cast<SpatComputeBatch>(op)) {
if (batch.getNumResults() != 0) {
uint32_t laneStart = isa<SpatComputeBatch>(consumerInstance.op) ? consumerInstance.laneStart : 0;
uint32_t laneCount = isa<SpatComputeBatch>(consumerInstance.op) ? consumerInstance.laneCount
: static_cast<uint32_t>(batch.getLaneCount());
for (ComputeInstance instance : getBatchChunksForRange(batch, laneStart, laneCount))
producers.push_back({instance, 0});
return producers;
}
uint32_t lane = cast<OpResult>(value).getResultNumber();
ComputeInstance instance = getBatchChunkForLane(batch, lane);
producers.push_back({instance, lane - instance.laneStart});
return producers;
}
return producers;
}
Cost getProducerTransferCost(Value input,
const ComputeInstance& consumerInstance,
const ProducerValueRef& producerRef) {
Cost transferCost = getInputTransferCost(consumerInstance, input);
auto producerBatch = dyn_cast<SpatComputeBatch>(producerRef.instance.op);
if (!producerBatch || producerBatch.getNumResults() == 0)
return transferCost;
if (auto consumerBatch = dyn_cast<SpatComputeBatch>(consumerInstance.op)) {
if (std::optional<Cost> projectedCost = getBatchProjectedInputTransferCost(consumerBatch, input)) {
uint32_t overlapLaneCount = getLaneOverlapCount(consumerInstance, producerRef.instance);
assert(overlapLaneCount > 0 && "projected batch edge must overlap consumer lanes");
return checkedMultiply(*projectedCost, static_cast<Cost>(overlapLaneCount));
}
}
return scaleTransferCostByLaneCount(
transferCost, static_cast<uint32_t>(producerBatch.getLaneCount()), producerRef.instance.laneCount);
}
static CrossbarWeight getOpaqueCrossbarWeight(Value value, std::optional<uint32_t> lane) { static CrossbarWeight getOpaqueCrossbarWeight(Value value, std::optional<uint32_t> lane) {
CrossbarWeight weight; CrossbarWeight weight;
weight.opaqueValue = value; weight.opaqueValue = value;
@@ -148,57 +267,6 @@ static CrossbarWeight getOpaqueCrossbarWeight(Value value, std::optional<uint32_
return weight; return weight;
} }
static 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();
auto floorDiv = [](int64_t value, int64_t divisor) -> FailureOr<int64_t> {
if (divisor <= 0)
return failure();
if (value >= 0)
return value / divisor;
return -((-value + divisor - 1) / divisor);
};
switch (binary.getKind()) {
case AffineExprKind::Add: return *lhs + *rhs;
case AffineExprKind::Mul: return *lhs * *rhs;
case AffineExprKind::FloorDiv: return floorDiv(*lhs, *rhs);
case AffineExprKind::CeilDiv:
if (*rhs <= 0)
return failure();
return (*lhs + *rhs - 1) / *rhs;
case AffineExprKind::Mod: {
FailureOr<int64_t> div = floorDiv(*lhs, *rhs);
if (failed(div))
return failure();
return *lhs - *div * *rhs;
}
default: return failure();
}
}
static FailureOr<int64_t> static FailureOr<int64_t>
evaluateIndexLike(Value value, const DenseMap<Value, int64_t>& bindings, std::optional<uint32_t> lane, Value laneArg); evaluateIndexLike(Value value, const DenseMap<Value, int64_t>& bindings, std::optional<uint32_t> lane, Value laneArg);
@@ -222,12 +290,8 @@ evaluateIndexLike(Value value, const DenseMap<Value, int64_t>& bindings, std::op
if (auto it = bindings.find(value); it != bindings.end()) if (auto it = bindings.find(value); it != bindings.end())
return it->second; return it->second;
if (auto constant = value.getDefiningOp<arith::ConstantIndexOp>()) if (std::optional<int64_t> constant = matchConstantIndexValue(value))
return constant.value(); return *constant;
if (auto constant = value.getDefiningOp<arith::ConstantOp>())
if (auto intAttr = dyn_cast<IntegerAttr>(constant.getValue()))
return intAttr.getInt();
if (auto extract = value.getDefiningOp<tensor::ExtractOp>()) { if (auto extract = value.getDefiningOp<tensor::ExtractOp>()) {
auto constant = extract.getTensor().getDefiningOp<arith::ConstantOp>(); auto constant = extract.getTensor().getDefiningOp<arith::ConstantOp>();
@@ -245,26 +309,11 @@ evaluateIndexLike(Value value, const DenseMap<Value, int64_t>& bindings, std::op
return failure(); return failure();
} }
auto affineApply = value.getDefiningOp<affine::AffineApplyOp>(); if (auto affineApply = value.getDefiningOp<affine::AffineApplyOp>())
if (!affineApply) return evaluateAffineApply(affineApply,
return failure(); [&](Value operand) { return evaluateIndexLike(operand, bindings, lane, laneArg); });
AffineMap map = affineApply.getAffineMap(); return failure();
if (map.getNumResults() != 1)
return failure();
SmallVector<int64_t, 4> operands;
operands.reserve(affineApply.getMapOperands().size());
for (Value operand : affineApply.getMapOperands()) {
FailureOr<int64_t> folded = evaluateIndexLike(operand, bindings, lane, laneArg);
if (failed(folded))
return failure();
operands.push_back(*folded);
}
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);
} }
static FailureOr<SmallVector<int64_t, 4>> evaluateIndexList(ArrayRef<OpFoldResult> values, static FailureOr<SmallVector<int64_t, 4>> evaluateIndexList(ArrayRef<OpFoldResult> values,
@@ -526,25 +575,13 @@ ComputeGraph buildComputeGraph(Operation* entryOp) {
for (const auto& [targetIndex, node] : llvm::enumerate(graph.nodes)) { for (const auto& [targetIndex, node] : llvm::enumerate(graph.nodes)) {
llvm::SmallVector<Value, 4> inputs = getComputeInstanceInputs(node.instance); llvm::SmallVector<Value, 4> inputs = getComputeInstanceInputs(node.instance);
for (Value input : inputs) { for (Value input : inputs) {
Cost transferCost = getInputTransferCost(node.instance, input); for (const ProducerValueRef& producerRef : collectProducerValueRefs(input, node.instance)) {
if (auto producerBatch = dyn_cast_or_null<SpatComputeBatch>(input.getDefiningOp()); auto producerIt = graph.instanceToIndex.find(producerRef.instance);
producerBatch && producerBatch.getNumResults() != 0 && !isa<SpatComputeBatch>(node.instance.op)) { if (producerIt == graph.instanceToIndex.end())
for (uint32_t lane = 0; lane < static_cast<uint32_t>(producerBatch.getLaneCount()); ++lane) { continue;
auto producerIt = graph.instanceToIndex.find(getBatchChunkForLane(producerBatch, lane)); rawEdges.push_back(
if (producerIt == graph.instanceToIndex.end()) {producerIt->second, targetIndex, getProducerTransferCost(input, node.instance, producerRef)});
continue;
rawEdges.push_back({producerIt->second, targetIndex, transferCost});
}
continue;
} }
auto producerInstance = getComputeProducerInstance(input, &node.instance);
if (!producerInstance)
continue;
auto producerIt = graph.instanceToIndex.find(*producerInstance);
if (producerIt == graph.instanceToIndex.end())
continue;
rawEdges.push_back({producerIt->second, targetIndex, transferCost});
} }
} }
@@ -20,17 +20,66 @@ size_t getSchedulingCpuBudget() {
size_t getBatchChunkTargetCount(int32_t laneCount) { size_t getBatchChunkTargetCount(int32_t laneCount) {
assert(laneCount > 0 && "laneCount must be positive"); assert(laneCount > 0 && "laneCount must be positive");
return static_cast<size_t>(laneCount); return std::min(static_cast<size_t>(laneCount), getSchedulingCpuBudget());
}
BatchChunkRange getBatchChunkRange(int32_t laneCount, size_t chunkIndex) {
assert(laneCount > 0 && "laneCount must be positive");
size_t chunkCount = getBatchChunkTargetCount(laneCount);
assert(chunkIndex < chunkCount && "chunkIndex out of range");
size_t laneCountSize = static_cast<size_t>(laneCount);
size_t baseChunkSize = laneCountSize / chunkCount;
size_t remainder = laneCountSize % chunkCount;
size_t extraBefore = std::min(chunkIndex, remainder);
size_t start = chunkIndex * baseChunkSize + extraBefore;
size_t count = baseChunkSize + (chunkIndex < remainder ? 1 : 0);
assert(count > 0 && "chunk size must be positive");
return {static_cast<uint32_t>(start), static_cast<uint32_t>(count)};
}
size_t getBatchChunkIndexForLane(int32_t laneCount, uint32_t lane) {
assert(laneCount > 0 && "laneCount must be positive");
assert(lane < static_cast<uint32_t>(laneCount) && "lane out of range");
size_t chunkCount = getBatchChunkTargetCount(laneCount);
size_t laneCountSize = static_cast<size_t>(laneCount);
size_t baseChunkSize = laneCountSize / chunkCount;
size_t remainder = laneCountSize % chunkCount;
size_t largeChunkSize = baseChunkSize + 1;
size_t laneIndex = static_cast<size_t>(lane);
size_t largerChunkLanes = remainder * largeChunkSize;
if (laneIndex < largerChunkLanes)
return laneIndex / largeChunkSize;
return remainder + ((laneIndex - largerChunkLanes) / baseChunkSize);
} }
ComputeInstance getBatchChunkForIndex(SpatComputeBatch batch, size_t chunkIndex) { ComputeInstance getBatchChunkForIndex(SpatComputeBatch batch, size_t chunkIndex) {
assert(chunkIndex < static_cast<size_t>(batch.getLaneCount()) && "chunkIndex out of range"); BatchChunkRange chunk = getBatchChunkRange(batch.getLaneCount(), chunkIndex);
return {batch.getOperation(), static_cast<uint32_t>(chunkIndex), 1}; return {batch.getOperation(), chunk.laneStart, chunk.laneCount};
} }
ComputeInstance getBatchChunkForLane(SpatComputeBatch batch, uint32_t lane) { ComputeInstance getBatchChunkForLane(SpatComputeBatch batch, uint32_t lane) {
assert(lane < static_cast<uint32_t>(batch.getLaneCount()) && "lane out of range"); return getBatchChunkForIndex(batch, getBatchChunkIndexForLane(batch.getLaneCount(), lane));
return {batch.getOperation(), lane, 1}; }
llvm::SmallVector<ComputeInstance, 4>
getBatchChunksForRange(SpatComputeBatch batch, uint32_t laneStart, uint32_t laneCount) {
llvm::SmallVector<ComputeInstance, 4> chunks;
if (laneCount == 0)
return chunks;
uint32_t laneEnd = laneStart + laneCount;
assert(laneEnd >= laneStart && "lane range overflow");
assert(laneEnd <= static_cast<uint32_t>(batch.getLaneCount()) && "lane range out of bounds");
size_t firstChunk = getBatchChunkIndexForLane(batch.getLaneCount(), laneStart);
size_t lastChunk = getBatchChunkIndexForLane(batch.getLaneCount(), laneEnd - 1);
chunks.reserve(lastChunk - firstChunk + 1);
for (size_t chunkIndex = firstChunk; chunkIndex <= lastChunk; ++chunkIndex)
chunks.push_back(getBatchChunkForIndex(batch, chunkIndex));
return chunks;
} }
static std::optional<uint32_t> getConstantExtractLane(tensor::ExtractSliceOp extract) { static std::optional<uint32_t> getConstantExtractLane(tensor::ExtractSliceOp extract) {
@@ -21,10 +21,19 @@ struct ProducerValueRef {
size_t resultIndex = 0; size_t resultIndex = 0;
}; };
struct BatchChunkRange {
uint32_t laneStart = 0;
uint32_t laneCount = 0;
};
size_t getSchedulingCpuBudget(); size_t getSchedulingCpuBudget();
size_t getBatchChunkTargetCount(int32_t laneCount); size_t getBatchChunkTargetCount(int32_t laneCount);
BatchChunkRange getBatchChunkRange(int32_t laneCount, size_t chunkIndex);
size_t getBatchChunkIndexForLane(int32_t laneCount, uint32_t lane);
ComputeInstance getBatchChunkForIndex(SpatComputeBatch batch, size_t chunkIndex); ComputeInstance getBatchChunkForIndex(SpatComputeBatch batch, size_t chunkIndex);
ComputeInstance getBatchChunkForLane(SpatComputeBatch batch, uint32_t lane); ComputeInstance getBatchChunkForLane(SpatComputeBatch batch, uint32_t lane);
llvm::SmallVector<ComputeInstance, 4>
getBatchChunksForRange(SpatComputeBatch batch, uint32_t laneStart, uint32_t laneCount);
std::optional<ProducerValueRef> getProducerValueRef(mlir::Value value, std::optional<ProducerValueRef> getProducerValueRef(mlir::Value value,
const ComputeInstance* consumerInstance = nullptr); const ComputeInstance* consumerInstance = nullptr);
@@ -1,11 +1,9 @@
#include "mlir/IR/Threading.h" #include "mlir/IR/Threading.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallSet.h"
#include "llvm/Support/ErrorHandling.h" #include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FormatVariadic.h" #include "llvm/Support/FormatVariadic.h"
#include <functional>
#include <limits> #include <limits>
#include <queue> #include <queue>
#include <vector> #include <vector>
-6
View File
@@ -1,11 +1,5 @@
add_pim_library(OMPimPasses add_pim_library(OMPimPasses
MessagePass.cpp MessagePass.cpp
PimCodegen/HostConstantFolding/Common.cpp
PimCodegen/HostConstantFolding/Patterns/Constant.cpp
PimCodegen/HostConstantFolding/HostConstantFoldingPass.cpp
PimCodegen/HostConstantFolding/Patterns/Subview.cpp
PimCodegen/MaterializeHostConstantsPass.cpp
PimCodegen/VerificationPass.cpp
PimCodegen/EmitPimCodePass.cpp PimCodegen/EmitPimCodePass.cpp
EXCLUDE_FROM_OM_LIBS EXCLUDE_FROM_OM_LIBS
+1 -3
View File
@@ -15,14 +15,12 @@ std::unique_ptr<mlir::Pass> createSpatialToPimPass();
std::unique_ptr<mlir::Pass> createPimBufferizationPass(); std::unique_ptr<mlir::Pass> createPimBufferizationPass();
std::unique_ptr<mlir::Pass> createPimStaticMemoryCoalescingPass(); std::unique_ptr<mlir::Pass> createPimMemoryCoalescingPass();
std::unique_ptr<mlir::Pass> createMergeComputeNodesPass(); std::unique_ptr<mlir::Pass> createMergeComputeNodesPass();
std::unique_ptr<mlir::Pass> createPimHostConstantFoldingPass(); std::unique_ptr<mlir::Pass> createPimHostConstantFoldingPass();
std::unique_ptr<mlir::Pass> createPimMaterializeHostConstantsPass();
std::unique_ptr<mlir::Pass> createPimVerificationPass(); std::unique_ptr<mlir::Pass> createPimVerificationPass();
std::unique_ptr<mlir::Pass> createEmitPimCodePass(); std::unique_ptr<mlir::Pass> createEmitPimCodePass();
@@ -1,157 +0,0 @@
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/Dominance.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/Pass/Pass.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/MathExtras.h"
#include "src/Accelerators/PIM/Common/IR/BatchCoreUtils.hpp"
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
#include "src/Accelerators/PIM/Dialect/Pim/PimOps.hpp"
using namespace mlir;
namespace onnx_mlir {
namespace {
template <typename CoreOpTy>
static void materializeHostConstantsInCore(CoreOpTy coreOp,
IRRewriter& rewriter,
OperationFolder& constantFolder,
bool& hasFailure) {
DenseMap<Value, DenseMap<int64_t, DenseMap<Type, Value>>> materializedValues;
DominanceInfo dominance(coreOp);
SmallVector<Operation*> ops;
coreOp.getBody().front().walk([&](Operation* op) {
if (!isa<pim::PimHaltOp, scf::YieldOp>(op))
ops.push_back(op);
});
for (Operation* op : ops) {
if (auto loadOp = dyn_cast<memref::LoadOp>(op); loadOp && loadOp.getType().isIndex())
continue;
for (OpOperand& operand : op->getOpOperands()) {
Value originalValue = operand.get();
if (!isa<BaseMemRefType>(originalValue.getType()) || isExplicitHostMemCopyOperand(op, operand.getOperandNumber()))
continue;
auto resolvedAddress = resolveContiguousAddress(originalValue);
if (failed(resolvedAddress))
continue;
auto getGlobalOp = dyn_cast_or_null<memref::GetGlobalOp>(resolvedAddress->base.getDefiningOp());
if (!getGlobalOp)
continue;
auto originalType = dyn_cast<MemRefType>(originalValue.getType());
if (!originalType || !originalType.hasStaticShape()) {
op->emitOpError("host constant materialization requires a static memref operand");
hasFailure = true;
continue;
}
auto& cachedByOffset = materializedValues[resolvedAddress->base];
auto& cachedByType = cachedByOffset[resolvedAddress->byteOffset];
auto cachedValue = cachedByType.find(originalType);
if (cachedValue != cachedByType.end() && dominance.properlyDominates(cachedValue->second, op)) {
operand.set(cachedValue->second);
continue;
}
int64_t totalBytes = -1;
if (auto type = dyn_cast<ShapedType>(originalValue.getType()); type && type.hasStaticShape())
totalBytes = static_cast<int64_t>(getShapedTypeSizeInBytes(type));
if (totalBytes < 0 || !llvm::isInt<32>(totalBytes) || !llvm::isInt<32>(resolvedAddress->byteOffset)) {
op->emitOpError("host constant materialization requires 32-bit copy sizes and offsets");
hasFailure = true;
continue;
}
auto contiguousType = MemRefType::get(originalType.getShape(), originalType.getElementType());
rewriter.setInsertionPoint(op);
Value localAlloc = memref::AllocOp::create(rewriter, op->getLoc(), contiguousType);
Value deviceDst = localAlloc;
if (contiguousType != originalType)
deviceDst = memref::CastOp::create(rewriter, op->getLoc(), originalType, localAlloc);
Value zeroOffset = getOrCreateIndexConstant(constantFolder, op, 0);
Value hostOffset = getOrCreateIndexConstant(constantFolder, op, resolvedAddress->byteOffset);
Value copiedValue =
pim::PimMemCopyHostToDevOp::create(rewriter,
op->getLoc(),
originalType,
zeroOffset,
hostOffset,
deviceDst,
getGlobalOp.getResult(),
rewriter.getI32IntegerAttr(static_cast<int32_t>(totalBytes)))
.getOutput();
cachedByType[originalType] = copiedValue;
operand.set(copiedValue);
}
}
}
struct MaterializeHostConstantsPass : PassWrapper<MaterializeHostConstantsPass, OperationPass<ModuleOp>> {
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(MaterializeHostConstantsPass)
StringRef getArgument() const override { return "materialize-pim-host-constants"; }
StringRef getDescription() const override {
return "Materialize explicit host-to-device copies for constant globals used by PIM runtime ops";
}
void runOnOperation() override {
ModuleOp moduleOp = getOperation();
IRRewriter rewriter(moduleOp.getContext());
OperationFolder constantFolder(moduleOp.getContext());
bool hasFailure = false;
for (func::FuncOp funcOp : moduleOp.getOps<func::FuncOp>()) {
if (funcOp.isExternal())
continue;
for (pim::PimCoreOp coreOp : funcOp.getOps<pim::PimCoreOp>())
materializeHostConstantsInCore(coreOp, rewriter, constantFolder, hasFailure);
for (pim::PimCoreBatchOp coreBatchOp : funcOp.getOps<pim::PimCoreBatchOp>())
materializeHostConstantsInCore(coreBatchOp, rewriter, constantFolder, hasFailure);
SmallVector<Operation*> hostCompactOps;
for (Operation& op : funcOp.getBody().front())
if (isa<pim::PimConcatOp>(op))
hostCompactOps.push_back(&op);
for (Operation* op : hostCompactOps) {
rewriter.setInsertionPoint(op);
auto concatOp = cast<pim::PimConcatOp>(op);
concatOp.emitOpError("host-side concat must be folded away or lowered into pim.core before materialization");
hasFailure = true;
}
}
if (hasFailure) {
moduleOp.emitError("PIM host-constant materialization failed; see diagnostics above");
signalPassFailure();
return;
}
dumpModule(moduleOp, "pim4_materialized");
}
};
} // namespace
std::unique_ptr<Pass> createPimMaterializeHostConstantsPass() {
return std::make_unique<MaterializeHostConstantsPass>();
}
} // namespace onnx_mlir
+1 -2
View File
@@ -75,10 +75,9 @@ void PimAccelerator::registerPasses(int optLevel) const {
registerPass(createSpatialToGraphvizPass); registerPass(createSpatialToGraphvizPass);
registerPass(createSpatialToPimPass); registerPass(createSpatialToPimPass);
registerPass(createPimBufferizationPass); registerPass(createPimBufferizationPass);
registerPass(createPimStaticMemoryCoalescingPass); registerPass(createPimMemoryCoalescingPass);
registerPass(createMergeComputeNodesPass); registerPass(createMergeComputeNodesPass);
registerPass(createPimHostConstantFoldingPass); registerPass(createPimHostConstantFoldingPass);
registerPass(createPimMaterializeHostConstantsPass);
registerPass(createPimVerificationPass); registerPass(createPimVerificationPass);
registerPass(createEmitPimCodePass); registerPass(createEmitPimCodePass);
} }
+6
View File
@@ -30,3 +30,9 @@ add_pim_unittest(LabeledListTest
OMPimCommon OMPimCommon
) )
add_pim_unittest(PimMemoryLivenessPlannerTest
PimMemoryLivenessPlannerTest.cpp
LINK_LIBS PRIVATE
OMPimCompilerUtils
)

Some files were not shown because too many files have changed in this diff Show More