This is based on empirical data from compiling 9 medium to large language and diffusion models with IREE. e2e, this improves compilation times by 0.33% in terms of `instructions:u` (same metric is used by the [CTMark for Clang](https://www.npopov.com/2024/01/01/This-year-in-LLVM-2023.html#compile-time-improvements)). I explored using other constants and these are the ones that performed best while keeping the sizes relatively small.
170 lines
6.9 KiB
C++
170 lines
6.9 KiB
C++
//===- IndexingMapOpInterface.cpp -- IndexingMapOpInterface impl ----------===//
|
|
//
|
|
// Part of the LLVM Project, 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 "mlir/Interfaces/IndexingMapOpInterface.h"
|
|
|
|
using namespace mlir;
|
|
|
|
namespace mlir {
|
|
#include "mlir/Interfaces/IndexingMapOpInterface.cpp.inc"
|
|
} // namespace mlir
|
|
|
|
static LogicalResult verifyIndexingMapOperandType(Operation *op, Type t,
|
|
unsigned operandNumber) {
|
|
// Non-shaped types are treated as scalars (rank-0). This includes builtin
|
|
// types (integer, float, complex) as well as custom dialect types.
|
|
if (!isa<ShapedType>(t))
|
|
return success();
|
|
|
|
// Vectors are allowed.
|
|
if (isa<VectorType>(t))
|
|
return success();
|
|
|
|
// MemRefs: must be ranked.
|
|
if (isa<UnrankedMemRefType>(t)) {
|
|
return op->emitOpError("operand #")
|
|
<< operandNumber << " must be a ranked memref, but got " << t;
|
|
}
|
|
if (isa<MemRefType>(t))
|
|
return success();
|
|
|
|
// Tensors: must be ranked.
|
|
if (isa<UnrankedTensorType>(t)) {
|
|
return op->emitOpError("operand #")
|
|
<< operandNumber << " must be a ranked tensor, but got " << t;
|
|
}
|
|
if (isa<RankedTensorType>(t))
|
|
return success();
|
|
|
|
// Any other shaped type is not supported by this interface.
|
|
return op->emitOpError("operand #")
|
|
<< operandNumber
|
|
<< " must be ranked tensor/memref, vector, or scalar, but got " << t;
|
|
}
|
|
|
|
LogicalResult mlir::IndexingMapOpInterface::verifyImpl() {
|
|
// All input/output operands must be indexed.
|
|
if (static_cast<int64_t>(getIndexingMapsArray().size()) !=
|
|
getOperation()->getNumOperands())
|
|
return this->emitOpError("expected the number of indexing_map (")
|
|
<< getIndexingMapsArray().size()
|
|
<< ") to be equal to the number of input/output operands ("
|
|
<< getOperation()->getNumOperands() << ")";
|
|
|
|
// Inline size chosen empirically based on compilation profiling.
|
|
// Profiled: 7.5M calls, avg=5.9+-3.1. N=8 covers 67% of cases inline.
|
|
SmallVector<int64_t, 8> allShapesSizes;
|
|
|
|
for (OpOperand &opOperand : getOperation()->getOpOperands()) {
|
|
Type ty = opOperand.get().getType();
|
|
if (failed(verifyIndexingMapOperandType(getOperation(), ty,
|
|
opOperand.getOperandNumber())))
|
|
return failure();
|
|
AffineMap indexingMap = getMatchingIndexingMap(&opOperand);
|
|
// Symbols disallowed.
|
|
if (indexingMap.getNumSymbols() != 0)
|
|
return this->emitOpError("unexpected symbols in indexing_map #")
|
|
<< opOperand.getOperandNumber();
|
|
// Handle scalars (non-shaped types: integer, float, complex, custom types,
|
|
// etc.).
|
|
if (!isa<ShapedType>(ty)) {
|
|
int64_t rank = 0;
|
|
if (indexingMap.getNumResults() != rank)
|
|
return this->emitOpError("expected operand #")
|
|
<< opOperand.getOperandNumber() << " rank (" << rank
|
|
<< ") to match the result rank of indexing_map ("
|
|
<< indexingMap.getNumResults() << ")";
|
|
continue;
|
|
}
|
|
SmallVector<int64_t> shape = getStaticOperandShape(&opOperand);
|
|
int64_t rank = shape.size();
|
|
|
|
// Result rank must match operand rank.
|
|
if (indexingMap.getNumResults() != rank)
|
|
return this->emitOpError("expected operand #")
|
|
<< opOperand.getOperandNumber() << " rank (" << rank
|
|
<< ") to match the result rank of indexing_map ("
|
|
<< indexingMap.getNumResults() << ")";
|
|
|
|
llvm::append_range(allShapesSizes, shape);
|
|
}
|
|
|
|
AffineMap invertedMap = getShapesToLoopsMap();
|
|
if (!invertedMap) {
|
|
std::string str;
|
|
llvm::raw_string_ostream os(str);
|
|
getLoopsToShapesMap().print(os);
|
|
return this->emitOpError("invalid indexing maps are non-invertible: ")
|
|
<< "(" << str << ")";
|
|
}
|
|
|
|
SmallVector<int64_t> endLoopRangeValues = invertedMap.compose(allShapesSizes);
|
|
|
|
// Check if given shapes match to inferred shapes.
|
|
SmallVector<int64_t> startLoopRangeValues(endLoopRangeValues.size(), 0);
|
|
// Verify only static cases since we can't get exact dimension sizes and
|
|
// loop ranges for dynamic cases in this stage.
|
|
if (llvm::none_of(endLoopRangeValues, ShapedType::isDynamic)) {
|
|
// Exclusive end range.
|
|
for (int64_t &range : endLoopRangeValues)
|
|
range -= 1;
|
|
for (OpOperand &opOperand : getOperation()->getOpOperands()) {
|
|
AffineMap indexingMap = getMatchingIndexingMap(&opOperand);
|
|
SmallVector<int64_t> startIndices =
|
|
indexingMap.compose(startLoopRangeValues);
|
|
SmallVector<int64_t> endIndices = indexingMap.compose(endLoopRangeValues);
|
|
SmallVector<int64_t> shape = getStaticOperandShape(&opOperand);
|
|
for (auto dim : llvm::seq<int64_t>(0, shape.size())) {
|
|
// Ignore dynamic dimension or the case that the dimension size is 0
|
|
if (ShapedType::isDynamic(shape[dim]) || shape[dim] == 0)
|
|
continue;
|
|
|
|
// The first index or last index should be the maximum or the minimum in
|
|
// the inferred index ranges since the range is increasing or
|
|
// decreasing. The size of dimensions of input/output operands and the
|
|
// maximum value + 1 in the inferred range should be the same. But, for
|
|
// now we check if the inferred ranges are in boundary of input/output
|
|
// operands' size or not in case that Affine Expressions are complicated
|
|
// such as d0 * 3
|
|
// + d1 since it is not easy to handle the issues.
|
|
// Found the case that this solution can't check, for example, (d0, d1)
|
|
// -> (d1 - d0)
|
|
int64_t inferredDimSize =
|
|
std::max(startIndices[dim], endIndices[dim]) + 1;
|
|
if (std::min(startIndices[dim], endIndices[dim]) < 0) {
|
|
std::string mapStr;
|
|
{
|
|
llvm::raw_string_ostream os(mapStr);
|
|
os << indexingMap;
|
|
}
|
|
return this->emitOpError(
|
|
"unexpected result less than 0 at expression #")
|
|
<< dim << " in " << mapStr;
|
|
}
|
|
if (isa<AffineDimExpr>(indexingMap.getResult(dim))) {
|
|
if (inferredDimSize != shape[dim]) {
|
|
return this->emitOpError("inferred input/output operand #")
|
|
<< opOperand.getOperandNumber() << " has shape's dimension #"
|
|
<< dim << " to be " << inferredDimSize << ", but found "
|
|
<< shape[dim];
|
|
}
|
|
} else {
|
|
if (inferredDimSize > shape[dim]) {
|
|
return this->emitOpError("inferred input/output operand #")
|
|
<< opOperand.getOperandNumber() << " has shape's dimension #"
|
|
<< dim << " to be greater than or equal to "
|
|
<< inferredDimSize << ", but found " << shape[dim];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return success();
|
|
}
|