blob: f97394857f2c678f72387c952fb5b07e4baf9c7b [file] [edit]
// Copyright 2025 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "iree/compiler/GlobalOptimization/Passes.h"
#include "mlir/Dialect/Linalg/IR/Linalg.h"
#include "mlir/Dialect/Linalg/Transforms/Transforms.h"
#include "mlir/IR/AffineExpr.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Transforms/WalkPatternRewriteDriver.h"
namespace mlir::iree_compiler::GlobalOptimization {
#define GEN_PASS_DEF_CONVERTSTRIDEDCONTRACTIONTOCONTRACTIONPASS
#include "iree/compiler/GlobalOptimization/Passes.h.inc"
namespace {
class ConvertStridedContractionToContraction
: public OpRewritePattern<linalg::GenericOp> {
public:
using Base::Base;
LogicalResult matchAndRewrite(linalg::GenericOp op,
PatternRewriter &rewriter) const override {
// Check if the generic op satisfies all other conditions for being a
// contraction.
if (op.getNumDpsInputs() != 2 || op.getNumDpsInits() != 1) {
return failure();
}
if (op.getNumReductionLoops() == 0) {
return failure();
}
if (!mlir::linalg::detail::isContractionBody(
*op.getBlock(), [](Operation *first, Operation *second) {
if ((isa<arith::MulFOp>(first) && isa<arith::AddFOp>(second)) ||
(isa<arith::MulIOp>(first) && isa<arith::AddIOp>(second))) {
return true;
}
return false;
})) {
return failure();
}
SmallVector<AffineMap> mapRange = op.getIndexingMapsArray();
unsigned inputPos = op.getDpsInputOperand(0)->getOperandNumber();
unsigned filterPos = op.getDpsInputOperand(1)->getOperandNumber();
unsigned resInitPos = op.getDpsInitOperand(0)->getOperandNumber();
AffineMap inputMap = mapRange[inputPos];
AffineMap filterMap = mapRange[filterPos];
AffineMap resultMap = mapRange[resInitPos];
// For now, we are only handling the case where the first input is the
// only non-projected permutation.
if (!filterMap.isProjectedPermutation() ||
!resultMap.isProjectedPermutation()) {
return failure();
}
if (inputMap.isProjectedPermutation()) {
return failure();
}
SmallVector<int64_t> staticShape = op.getStaticLoopRanges();
llvm::SmallDenseMap<unsigned, int64_t> strides;
SmallVector<AffineExpr> replacementExprs;
Value input = op.getDpsInputs()[0];
auto inputTy = dyn_cast<RankedTensorType>(input.getType());
if (!inputTy) {
return failure();
}
SmallVector<int64_t> inputShape(inputTy.getShape());
replacementExprs.reserve(inputMap.getNumResults());
// Walk through input map and look for expressions of the form `dim * cst`.
for (auto [pos, expr] : llvm::enumerate(inputMap.getResults())) {
// Skip dim exprs and constant exprs.
if (isa<AffineDimExpr>(expr) || isa<AffineConstantExpr>(expr)) {
replacementExprs.push_back(expr);
continue;
}
// Look at binary op expressions.
auto binexpr = dyn_cast<AffineBinaryOpExpr>(expr);
// Fail if we see some unexpected kind of expression.
if (!binexpr) {
return failure();
}
auto rhs = dyn_cast<AffineConstantExpr>(binexpr.getRHS());
auto lhs = dyn_cast<AffineDimExpr>(binexpr.getLHS());
// Binary expressions must be of the form `dim * cst`.
if (!rhs || !lhs || binexpr.getKind() != AffineExprKind::Mul) {
replacementExprs.push_back(expr);
continue;
}
strides.insert(std::pair<unsigned, int64_t>(pos, rhs.getValue()));
int64_t newSize = staticShape[lhs.getPosition()];
if (newSize == ShapedType::kDynamic || newSize == 0) {
return failure();
}
inputShape[pos] = newSize;
replacementExprs.push_back(lhs);
}
// Fail if we don't have any work to do.
if (strides.empty()) {
return failure();
}
mapRange[inputPos] =
AffineMap::get(inputMap.getNumDims(), inputMap.getNumSymbols(),
replacementExprs, op.getContext());
auto sliceTy = RankedTensorType::get(inputShape, inputTy.getElementType());
unsigned rank = inputTy.getRank();
SmallVector<OpFoldResult> vOffset(rank, rewriter.getIndexAttr(0));
SmallVector<OpFoldResult> vSizes;
SmallVector<OpFoldResult> vStride(rank, rewriter.getIndexAttr(1));
Location loc = op.getLoc();
for (unsigned i = 0; i < inputTy.getRank(); i++) {
if (strides.contains(i)) {
vStride[i] = rewriter.getIndexAttr(strides.at(i));
}
if (inputShape[i] != ShapedType::kDynamic) {
vSizes.push_back(rewriter.getIndexAttr(inputShape[i]));
continue;
}
vSizes.push_back(rewriter.createOrFold<tensor::DimOp>(loc, input, i));
}
Value extractedSlice = tensor::ExtractSliceOp::create(
rewriter, loc, sliceTy, input, vOffset, vSizes, vStride);
rewriter.startOpModification(op);
op.setIndexingMapsAttr(rewriter.getAffineMapArrayAttr(mapRange));
op.setOperand(0, extractedSlice);
rewriter.finalizeOpModification(op);
return success();
}
};
struct ConvertStridedContractionToContractionPass
: impl::ConvertStridedContractionToContractionPassBase<
ConvertStridedContractionToContractionPass> {
void getDependentDialects(DialectRegistry &registry) const override {
registry.insert<arith::ArithDialect, tensor::TensorDialect>();
}
void runOnOperation() override {
MLIRContext *context = &getContext();
RewritePatternSet patterns(&getContext());
patterns.insert<ConvertStridedContractionToContraction>(context);
walkAndApplyPatterns(getOperation(), std::move(patterns));
}
};
} // namespace
} // namespace mlir::iree_compiler::GlobalOptimization