blob: 4e09e56828fa4a1f95956350005102bca932c3fb [file]
// Copyright 2022 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/Codegen/Utils/LinalgOpInfo.h"
#include "mlir/Dialect/Linalg/IR/Linalg.h"
#include "mlir/Dialect/Linalg/IR/LinalgInterfaces.h"
using namespace mlir::linalg;
namespace mlir::iree_compiler {
/// Returns true if `map` is a tranpose. A transpose map is a projected
/// permutation with or without zeros in results where there exist at least two
/// dimensions di and dj such that di < dj and result_pos(di) > result_pos(dj).
/// Examples:
///
/// (d0, d1, d2) -> (d0, d2) is not a transpose map.
/// (d0, d1, d2) -> (d2, d0) is a transpose map.
/// (d0, d1, d2) -> (d1, d2) is not a transpose map.
/// (d0, d1, d2) -> (d0, 0, d1) is not a transpose map.
/// (d0, d1, d2) -> (d2, 0, d1) is a transpose map.
/// (d0, d1, d2) -> (d1, 0) is not a transpose map.
///
// TODO(dcaballe): Discern between "memcopy" transposes and "shuffle"
// transposes.
// TODO(dcaballe): Move to Affine utils?
static bool isTransposeMap(AffineMap map) {
// A transpose map must be a projected permutation with or without
// broadcasted/reduction dimensions.
if (!map.isProjectedPermutation(/*allowZeroInResults=*/true)) {
return false;
}
// Check that the projected permutation has at least two result dimensions
// that are actually transposed by comparing its input position.
unsigned prevDim = 0;
for (AffineExpr expr : map.getResults()) {
if (auto constExpr = dyn_cast<AffineConstantExpr>(expr)) {
// Constant zero expression, guaranteed by 'allowZeroInResults' above.
continue;
} else if (auto dimExpr = dyn_cast<AffineDimExpr>(expr)) {
if (prevDim > dimExpr.getPosition()) {
return true;
}
prevDim = dimExpr.getPosition();
} else {
return false;
}
}
return false;
}
/// The default filter passes all op operands.
static bool defaultTransposeMapFilter(AffineMap map) { return true; }
LinalgOpInfo::LinalgOpInfo(linalg::LinalgOp linalgOp)
: transposeMapFilter(defaultTransposeMapFilter) {
computeInfo(linalgOp);
}
LinalgOpInfo::LinalgOpInfo(linalg::LinalgOp linalgOp,
TransposeMapFilter transposeMapFilter)
: transposeMapFilter(transposeMapFilter) {
computeInfo(linalgOp);
}
/// Returns true if a LinalgOp implements a transpose.
// TODO(dcaballe):
// * Consider transpose + reductions.
// * Consider input and output transposes.
static SmallVector<OpOperand *>
computeTransposeInfo(LinalgOp linalgOp,
LinalgOpInfo::TransposeMapFilter transposeMapFilter) {
SmallVector<OpOperand *> transposeOperands;
// Reductions are not supported.
if (linalgOp.getNumReductionLoops() > 0) {
return transposeOperands;
}
// Inverse map to use transfer op permutation logic.
AffineMap outputInversedMap = inversePermutation(
linalgOp.getMatchingIndexingMap(linalgOp.getDpsInitOperand(0)));
SmallVector<AffineMap> inputInversedMaps;
for (OpOperand *linalgOperand : linalgOp.getDpsInputOperands()) {
auto map = linalgOp.getMatchingIndexingMap(linalgOperand);
if (!map.isProjectedPermutation(/*allowZeroInResults=*/true)) {
return transposeOperands;
}
AffineMap inverseMap = inverseAndBroadcastProjectedPermutation(map);
if (isTransposeMap(inverseMap) && transposeMapFilter(inverseMap)) {
transposeOperands.push_back(linalgOperand);
}
}
// Multiple outputs are not supported yet.
if (linalgOp.getNumDpsInits() != 1) {
return transposeOperands;
}
if (isTransposeMap(outputInversedMap) &&
transposeMapFilter(outputInversedMap)) {
transposeOperands.push_back(linalgOp.getDpsInitOperand(0));
}
return transposeOperands;
}
static bool computeReductionInfo(LinalgOp linalgOp) {
return linalgOp.getNumReductionLoops() >= 1;
}
static bool computeDynamicInfo(LinalgOp linalgOp) {
return linalgOp.hasDynamicShape();
}
void LinalgOpInfo::computeInfo(LinalgOp linalgOp) {
transposeOperands = computeTransposeInfo(linalgOp, transposeMapFilter);
reductionTrait = computeReductionInfo(linalgOp);
dynamicTrait = computeDynamicInfo(linalgOp);
}
bool isMatmulOrBatchMatmul(linalg::LinalgOp linalgOp) {
// (Batch) matmul should be a reduction op with 2/3 parallel dimensions.
if (!linalg::isaContractionOpInterface(linalgOp) ||
!llvm::is_contained({2u, 3u}, linalgOp.getNumParallelLoops()))
return false;
// Also exclude the case of matvec, which has only one non-unit parallel dim.
// They should go down different pipelines.
int nonUnitParallelDimCount = 0;
SmallVector<int64_t, 4> bounds = linalgOp.getStaticLoopRanges();
FailureOr<mlir::linalg::ContractionDimensions> contractionDims =
mlir::linalg::inferContractionDims(linalgOp);
assert(succeeded(contractionDims) && "Could not infer contraction dims");
for (auto mDim : contractionDims->m) {
nonUnitParallelDimCount += bounds[mDim] != 1;
}
for (auto nDim : contractionDims->n) {
nonUnitParallelDimCount += bounds[nDim] != 1;
}
return nonUnitParallelDimCount > 1;
}
} // namespace mlir::iree_compiler