Files
Raptor/src/PIM/Dialect/Spatial/SpatialOpsCanonicalization.cpp
T
NiccoloN 871fcfa832
Validate Operations / validate-operations (push) Has been cancelled
a new new beginning phase 1
2026-07-08 22:53:53 +02:00

222 lines
9.9 KiB
C++

#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/IR/Block.h"
#include "mlir/IR/IRMapping.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/LogicalResult.h"
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
using namespace mlir;
namespace onnx_mlir {
namespace spatial {
template <typename ComputeOpTy>
LogicalResult foldComputeLike(ComputeOpTy compute, ::llvm::SmallVectorImpl<::mlir::OpFoldResult>& results) {
Block& block = compute.getBody().front();
if (!llvm::hasSingleElement(block))
return failure();
auto yieldOp = dyn_cast<SpatYieldOp>(block.front());
if (!yieldOp)
return failure();
for (Value yieldedValue : yieldOp.getOperands()) {
if (auto blockArg = dyn_cast<BlockArgument>(yieldedValue)) {
if (blockArg.getOwner() == &block) {
results.push_back(compute.getOperand(blockArg.getArgNumber()));
continue;
}
}
results.push_back(yieldedValue);
}
return success();
}
LogicalResult SpatGraphCompute::fold(FoldAdaptor adaptor, ::llvm::SmallVectorImpl<::mlir::OpFoldResult>& results) {
return foldComputeLike(*this, results);
}
LogicalResult SpatScheduledCompute::fold(FoldAdaptor adaptor, ::llvm::SmallVectorImpl<::mlir::OpFoldResult>& results) {
return foldComputeLike(*this, results);
}
template <typename ScalarComputeOpTy>
static ScalarComputeOpTy createEmptyScalarCompute(PatternRewriter& rewriter,
Location loc,
TypeRange resultTypes,
ValueRange weights,
ValueRange inputs) {
auto computeOp = ScalarComputeOpTy::create(rewriter, loc, resultTypes, weights, inputs);
SmallVector<Type> blockArgTypes;
SmallVector<Location> blockArgLocs;
blockArgTypes.reserve(weights.size() + inputs.size());
blockArgLocs.reserve(weights.size() + inputs.size());
for (Value weight : weights) {
blockArgTypes.push_back(weight.getType());
blockArgLocs.push_back(weight.getLoc());
}
for (Value input : inputs) {
blockArgTypes.push_back(input.getType());
blockArgLocs.push_back(input.getLoc());
}
rewriter.createBlock(&computeOp.getBody(), computeOp.getBody().end(), blockArgTypes, blockArgLocs);
rewriter.setInsertionPointToStart(&computeOp.getBody().front());
return computeOp;
}
static SmallVector<OpFoldResult> remapMixedOffsets(ArrayRef<OpFoldResult> mixedOffsets, IRMapping& mapper) {
SmallVector<OpFoldResult> remapped;
remapped.reserve(mixedOffsets.size());
for (OpFoldResult ofr : mixedOffsets) {
if (auto value = dyn_cast<Value>(ofr))
remapped.push_back(cast<Value>(mapper.lookupOrDefault(value)));
else
remapped.push_back(cast<Attribute>(ofr));
}
return remapped;
}
static SmallVector<Value> createEmptyResults(PatternRewriter& rewriter, Location loc, TypeRange resultTypes) {
SmallVector<Value> resultValues;
resultValues.reserve(resultTypes.size());
for (Type resultType : resultTypes) {
auto tensorType = dyn_cast<RankedTensorType>(resultType);
if (!tensorType || !tensorType.hasStaticShape())
return {};
resultValues.push_back(tensor::EmptyOp::create(rewriter, loc, tensorType.getShape(), tensorType.getElementType()));
}
return resultValues;
}
template <typename ScalarComputeOpTy, typename ComputeBatchOpTy>
static void copyCanonicalizedBatchAttrs(ScalarComputeOpTy compute, ComputeBatchOpTy batch, PatternRewriter& rewriter) {
for (NamedAttribute attr : batch->getAttrs()) {
if (attr.getName() == batch.getOperandSegmentSizesAttrName() || attr.getName() == batch.getLaneCountAttrName()
|| attr.getName() == onnx_mlir::kCoreIdsAttrName)
continue;
compute->setAttr(attr.getName(), attr.getValue());
}
if constexpr (std::is_same_v<ComputeBatchOpTy, SpatScheduledComputeBatch>) {
if (auto coreIds = batch->template getAttrOfType<DenseI32ArrayAttr>(onnx_mlir::kCoreIdsAttrName)) {
assert(coreIds.size() == 1 && "single-lane scheduled compute_batch canonicalization expects exactly one core id");
compute->setAttr(onnx_mlir::kCoreIdAttrName, rewriter.getI32IntegerAttr(coreIds.asArrayRef().front()));
}
}
}
template <typename ComputeBatchOpTy, typename ScalarComputeOpTy>
struct CanonicalizeSingleLaneComputeBatchPattern : OpRewritePattern<ComputeBatchOpTy> {
using OpRewritePattern<ComputeBatchOpTy>::OpRewritePattern;
LogicalResult matchAndRewrite(ComputeBatchOpTy compute, PatternRewriter& rewriter) const override {
if (compute.getLaneCount() != 1)
return rewriter.notifyMatchFailure(compute, "lane count is not 1");
Block& oldBlock = compute.getBody().front();
auto oldLaneArg = compute.getLaneArgument();
if (!oldLaneArg)
return rewriter.notifyMatchFailure(compute, "missing compute_batch lane block argument");
rewriter.setInsertionPointAfter(compute);
auto newCompute =
createEmptyScalarCompute<ScalarComputeOpTy>(rewriter, compute.getLoc(), compute.getResultTypes(), compute.getWeights(), compute.getInputs());
copyCanonicalizedBatchAttrs(newCompute, compute, rewriter);
auto* newBlock = &newCompute.getBody().front();
rewriter.setInsertionPointToStart(newBlock);
IRMapping mapper;
Value zero = arith::ConstantIndexOp::create(rewriter, compute.getLoc(), 0);
mapper.map(*oldLaneArg, zero);
for (auto [index, weight] : llvm::enumerate(compute.getWeights())) {
auto oldArg = compute.getWeightArgument(index);
auto newArg = newCompute.getWeightArgument(index);
if (!oldArg || !newArg)
return rewriter.notifyMatchFailure(compute, "missing rewritten compute weight block argument");
mapper.map(*oldArg, *newArg);
}
for (auto [index, input] : llvm::enumerate(compute.getInputs())) {
auto oldArg = compute.getInputArgument(index);
auto newArg = newCompute.getInputArgument(index);
if (!oldArg || !newArg)
return rewriter.notifyMatchFailure(compute, "missing rewritten compute input block argument");
mapper.map(*oldArg, *newArg);
}
SmallVector<Value> resultValues = createEmptyResults(rewriter, compute.getLoc(), compute.getResultTypes());
if (resultValues.size() != compute.getNumResults())
return rewriter.notifyMatchFailure(compute, "single-lane compute_batch canonicalization requires static ranked results");
for (auto [index, resultValue] : llvm::enumerate(resultValues)) {
auto oldOutputArg = compute.getOutputArgument(index);
if (!oldOutputArg)
return rewriter.notifyMatchFailure(compute, "missing compute_batch output block argument");
mapper.map(*oldOutputArg, resultValue);
}
auto oldInParallel = dyn_cast<SpatInParallelOp>(oldBlock.getTerminator());
auto oldYield = dyn_cast<SpatYieldOp>(oldBlock.getTerminator());
for (Operation& op : oldBlock.without_terminator())
rewriter.clone(op, mapper);
if (oldYield) {
SpatYieldOp::create(rewriter, oldYield.getLoc(), ValueRange {});
rewriter.replaceOp(compute, newCompute.getResults());
return success();
}
if (!oldInParallel)
return rewriter.notifyMatchFailure(compute, "expected spat.in_parallel or empty spat.yield terminator");
DenseMap<BlockArgument, size_t> outputIndexByArg;
for (size_t index = 0; index < compute.getNumResults(); ++index) {
auto oldOutputArg = compute.getOutputArgument(index);
if (!oldOutputArg)
return rewriter.notifyMatchFailure(compute, "missing compute_batch output block argument");
outputIndexByArg[*oldOutputArg] = index;
}
for (Operation& op : oldInParallel.getRegion().front()) {
auto insertSlice = dyn_cast<tensor::ParallelInsertSliceOp>(&op);
if (!insertSlice)
return rewriter.notifyMatchFailure(compute, "expected only tensor.parallel_insert_slice in spat.in_parallel");
auto oldDest = dyn_cast<BlockArgument>(insertSlice.getDest());
if (!oldDest)
return rewriter.notifyMatchFailure(compute, "expected tensor.parallel_insert_slice destination to be a block argument");
auto resultIndexIt = outputIndexByArg.find(oldDest);
if (resultIndexIt == outputIndexByArg.end())
return rewriter.notifyMatchFailure(compute, "unexpected tensor.parallel_insert_slice destination");
size_t resultIndex = resultIndexIt->second;
Value remappedSource = mapper.lookupOrDefault(insertSlice.getSource());
auto remappedOffsets = remapMixedOffsets(insertSlice.getMixedOffsets(), mapper);
auto remappedSizes = remapMixedOffsets(insertSlice.getMixedSizes(), mapper);
auto remappedStrides = remapMixedOffsets(insertSlice.getMixedStrides(), mapper);
resultValues[resultIndex] = tensor::InsertSliceOp::create(rewriter,
insertSlice.getLoc(),
remappedSource,
resultValues[resultIndex],
remappedOffsets,
remappedSizes,
remappedStrides)
.getResult();
}
SpatYieldOp::create(rewriter, oldInParallel.getLoc(), resultValues);
rewriter.replaceOp(compute, newCompute.getResults());
return success();
}
};
void SpatGraphComputeBatch::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) {
results.add<CanonicalizeSingleLaneComputeBatchPattern<SpatGraphComputeBatch, SpatGraphCompute>>(context);
}
void SpatScheduledComputeBatch::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) {
results.add<CanonicalizeSingleLaneComputeBatchPattern<SpatScheduledComputeBatch, SpatScheduledCompute>>(context);
}
} // namespace spatial
} // namespace onnx_mlir