16#include "llvm/ADT/SmallVectorExtras.h"
17#include "llvm/Support/DebugLog.h"
19#define DEBUG_TYPE "x86-prepare-vector-contract-loops"
24 if (loop.getNumRegionIterArgs() != 1) {
25 LDBG() <<
"reject loop at " << loop.getLoc() <<
": expected one iter_arg";
29 auto yield = cast<scf::YieldOp>(loop.getBody()->getTerminator());
30 for (
Operation &op : loop.getBody()->without_terminator()) {
31 auto contract = dyn_cast<vector::ContractionOp>(&op);
34 if (
contract.getKind() != vector::CombiningKind::ADD ||
35 contract.getAcc() != loop.getRegionIterArgs().front() ||
37 yield->getOperand(0) !=
contract.getResult()) {
38 LDBG() <<
"reject contraction at " <<
contract.getLoc()
39 <<
": not a single loop-carried accumulation";
43 auto iterators =
contract.getIteratorTypesArray();
44 auto maps =
contract.getIndexingMapsArray();
49 auto par = vector::IteratorType::parallel;
50 auto red = vector::IteratorType::reduction;
51 if (llvm::equal(iterators,
ArrayRef{par, par, red}) &&
56 LDBG() <<
"reject contraction at " <<
contract.getLoc()
57 <<
": noncanonical matmul layout or iterators";
65 while (isa_and_present<vector::ShapeCastOp, vector::BroadcastOp>(
68 auto sourceType = dyn_cast<VectorType>(source.
getType());
69 if (!sourceType || sourceType.getNumElements() !=
70 cast<VectorType>(v.
getType()).getNumElements())
108 return std::pair(v,
int64_t(0));
112 int64_t j) -> std::optional<std::pair<Value, int64_t>> {
113 if (factor % ways != 0)
115 auto lane =
matchSplit(source, factor / ways);
118 lane->second += factor / ways *
j;
121 if (
auto deinterleave = v.
getDefiningOp<vector::DeinterleaveOp>())
122 return matchSource(deinterleave.getSource(), 2,
123 cast<OpResult>(v).getResultNumber());
127 extract ? extract.getSource().getDefiningOp<vector::TransposeOp>()
128 : vector::TransposeOp();
129 auto type = dyn_cast<VectorType>(v.
getType());
130 if (!transpose || !type || extract.hasDynamicPosition() ||
131 transpose.getSourceVectorType().isScalable())
135 auto isNonUnit = [&](
int64_t dim) {
return shape[dim] != 1; };
137 llvm::filter_to_vector(llvm::seq<int64_t>(0,
shape.size()), isNonUnit);
142 std::rotate(order.begin(), std::prev(order.end()), order.end());
144 if (llvm::filter_to_vector(perm, isNonUnit) != order)
150 if (ways * type.getNumElements() !=
151 transpose.getSourceVectorType().getNumElements())
156 .getStaticPosition()[llvm::find(perm, order.front()) - perm.begin()];
157 if (j < 0 || j >= ways)
160 return matchSource(transpose.getVector(), ways,
j);
187 if (
auto interleave = v.
getDefiningOp<vector::InterleaveOp>()) {
188 parts = {interleave.getLhs(), interleave.getRhs()};
189 }
else if (
auto insert = v.
getDefiningOp<vector::InsertStridedSliceOp>()) {
190 int64_t ways = insert.getDestVectorType().getShape().back();
191 if (ways < 2 || factor % ways != 0)
196 for (
int64_t i = 0; i < ways; ++i) {
197 auto insert = v.
getDefiningOp<vector::InsertStridedSliceOp>();
205 if (insert.getSourceVectorType().getShape() !=
ArrayRef(
slice) ||
208 parts[
j] = insert.getValueToStore();
209 v = insert.getDest();
216 if (factor % ways != 0)
219 for (
auto [
j, part] : llvm::enumerate(parts)) {
220 auto inner =
matchJoin(part, factor / ways);
223 for (
auto [i, lane] : llvm::enumerate(*inner))
224 lanes[ways * i +
j] = lane;
270 auto type = cast<VectorType>(rhs.getType());
271 int64_t k = type.getDimSize(0) / factor, n = type.getDimSize(1);
278 if (
shape.size() >= 3) {
279 size_t leading =
shape.size() - 3;
280 auto expected = llvm::to_vector(llvm::seq<int64_t>(0,
shape.size()));
281 std::swap(expected[leading + 1], expected[leading + 2]);
283 llvm::all_of(
shape.take_front(leading),
284 [](
int64_t dim) { return dim == 1; }) &&
294 auto lanes =
matchJoin(transpose.getVector(), factor);
299 for (
auto [
j, lane] : llvm::enumerate(*lanes)) {
302 if (!laneTranspose || laneTranspose.getPermutation() !=
ArrayRef(swap) ||
303 laneTranspose.getSourceVectorType().getShape() !=
307 auto source =
matchSplit(laneTranspose.getVector(), factor);
308 if (!source || source->second !=
int64_t(
j) ||
309 (src && source->first != src))
319 return read && read->getParentOp() == loop &&
320 isa<MemRefType>(read.getBase().getType()) &&
321 loop.isDefinedOutsideOfLoop(read.getBase()) &&
322 read.getPermutationMap().isMinorIdentity() && !read.getMask() &&
323 !read.hasOutOfBoundsDim();
328 vector::TransferReadOp read;
330 SmallVector<ReassociationIndices> reassociation;
337static std::optional<ExpandedRead>
342 auto type = cast<MemRefType>(read.getBase().getType());
343 if (type.getElementType() != read.getVectorType().getElementType() ||
347 if (!lastIndex || *lastIndex < 0 || *lastIndex % factor != 0)
351 if (type.isDynamicDim(type.getRank() - 1)) {
354 if (read.getVectorType().isScalable() ||
355 failed(type.getStridesAndOffset(strides, offset)))
361 shape.back() = *lastIndex + read.getVectorType().getShape().back();
362 view = type = MemRefType::get(
363 shape, type.getElementType(),
364 StridedLayoutAttr::get(type.getContext(), offset, strides),
365 type.getMemorySpace());
367 if (type.getShape().back() % factor != 0)
371 shape.back() /= factor;
372 shape.push_back(factor);
374 for (
int64_t i = 0; i < type.getRank(); ++i)
375 reassociation.push_back(i == type.getRank() - 1
378 FailureOr<MemRefType> expanded =
379 memref::ExpandShapeOp::computeExpandedType(type,
shape, reassociation);
380 if (failed(expanded))
382 return ExpandedRead{read, *expanded, reassociation, *lastIndex / factor,
387 ExpandedRead &info, VectorType resultType) {
390 Value base = info.read.getBase();
395 memref::ExtractStridedMetadataOp::create(rewriter, loc, base);
397 sizes.back() = rewriter.
getIndexAttr(info.view.getShape().back());
398 base = memref::ReinterpretCastOp::create(
399 rewriter, loc, info.view, base, metadata.getConstifiedMixedOffset(),
400 sizes, metadata.getConstifiedMixedStrides());
402 Value expanded = memref::ExpandShapeOp::create(rewriter, loc, info.type, base,
411 auto packed = vector::TransferReadOp::create(
412 rewriter, loc, resultType, expanded,
indices, info.read.getPadding());
413 packed.setInBoundsAttr(
428 LogicalResult matchAndRewrite(vector::ContractionOp
contract,
429 PatternRewriter &rewriter)
const override {
430 auto loop = dyn_cast<scf::ForOp>(
contract->getParentOp());
433 "not directly in a for loop");
436 contract,
"not the loop's canonical contraction");
438 auto lhsType =
contract.getLhsType();
439 auto rhsType =
contract.getRhsType();
440 Type element = lhsType.getElementType();
441 assert(lhsType.getRank() == 2 && rhsType.getRank() == 2 &&
442 lhsType.getDimSize(1) == rhsType.getDimSize(0) &&
443 element == rhsType.getElementType() &&
444 "expected canonical matmul contraction");
445 if (lhsType.isScalable() || rhsType.isScalable())
449 "expected bf16 or i8 operands");
451 if (lhsType.getDimSize(1) % factor != 0)
453 contract,
"K dimension is not divisible by VNNI factor");
458 "RHS does not unpack a packed vector");
459 VectorType rhsPackedType =
460 VectorType::get({lhsType.getDimSize(1) / factor, rhsType.getDimSize(1),
463 auto lhsRead =
contract.getLhs().getDefiningOp<vector::TransferReadOp>();
465 auto rhsRead = packedSource.
getDefiningOp<vector::TransferReadOp>();
468 contract,
"LHS read cannot be expanded into VNNI layout");
471 "RHS is not a simple transfer read");
472 std::optional<ExpandedRead> splitRhs;
473 if (rhsRead.getVectorType() != rhsPackedType) {
474 auto flatType = rhsRead.getVectorType();
475 if (flatType.getRank() != 2 ||
476 flatType.getDimSize(0) != rhsPackedType.getDimSize(0) ||
477 flatType.getDimSize(1) % factor != 0 ||
478 flatType.getDimSize(1) / factor != rhsPackedType.getDimSize(1))
480 contract,
"flat RHS shape is not VNNI compatible");
483 contract,
"RHS read cannot be expanded into VNNI layout");
487 contract,
"packed RHS has nonzero inner offset");
491 VectorType lhsPackedType =
492 VectorType::get({lhsType.getDimSize(0), lhsType.getDimSize(1) / factor,
498 : rhsRead.getResult();
510 {vector::IteratorTypeAttr::get(ctx, vector::IteratorType::parallel),
511 vector::IteratorTypeAttr::get(ctx, vector::IteratorType::parallel),
512 vector::IteratorTypeAttr::get(ctx, vector::IteratorType::reduction),
513 vector::IteratorTypeAttr::get(ctx, vector::IteratorType::reduction)});
517 LDBG() <<
"Replaced contract with VNNI-compatible contraction";
static bool isSimpleReadOutsideLoop(vector::TransferReadOp read, scf::ForOp loop)
static Value buildExpandedRead(PatternRewriter &rewriter, Location loc, ExpandedRead &info, VectorType resultType)
static Value skipReshapes(Value v)
static Value matchUnpackedRhs(Value rhs, int64_t factor)
static std::optional< ExpandedRead > getExpandableRead(vector::TransferReadOp read, unsigned factor, scf::ForOp loop)
static vector::ContractionOp getCandidate(scf::ForOp loop)
static std::optional< SmallVector< Value, 4 > > matchJoin(Value v, int64_t factor)
static std::optional< std::pair< Value, int64_t > > matchSplit(Value v, int64_t factor)
static void contract(RootOrderingGraph &graph, ArrayRef< Value > cycle, const DenseMap< Value, unsigned > &parentDepths, DenseMap< Value, Value > &actualSource, DenseMap< Value, Value > &actualTarget)
Contracts the specified cycle in the given graph in-place.
static SmallVector< Value > slice(OpBuilder &b, Location loc, Value value, VectorType nativeType)
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
IntegerAttr getIndexAttr(int64_t value)
AffineExpr getAffineDimExpr(unsigned position)
ArrayAttr getArrayAttr(ArrayRef< Attribute > value)
MLIRContext * getContext() const
ArrayAttr getBoolArrayAttr(ArrayRef< bool > values)
ArrayAttr getAffineMapArrayAttr(ArrayRef< AffineMap > values)
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
MLIRContext is the top-level object for a collection of MLIR operations.
RAII guard to reset the insertion point of the builder when destroyed.
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Operation is the basic unit of execution within MLIR.
Value getOperand(unsigned idx)
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
std::enable_if_t<!std::is_convertible< CallbackT, Twine >::value, LogicalResult > notifyMatchFailure(Location loc, CallbackT &&reasonCallback)
Used to notify the listener that the IR failed to be rewritten because of a match failure,...
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
bool isInteger() const
Return true if this is an integer type (with the specified width).
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Type getType() const
Return the type of this value.
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
void populatePrepareVectorContractLoopsPatterns(RewritePatternSet &patterns)
Include the generated interface declarations.
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
SmallVector< int64_t, 2 > ReassociationIndices
SmallVector< IntTy > extractFromIntegerArrayAttr(Attribute attr)
Extract integer values from the assumed ArrayAttr of IntegerAttr.
AffineExpr getAffineDimExpr(unsigned position, MLIRContext *context)
These free functions allow clients of the API to not use classes in detail.
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
OpRewritePattern(MLIRContext *context, PatternBenefit benefit=1, ArrayRef< StringRef > generatedNames={})
Patterns must specify the root operation name they match against, and can also specify the benefit of...
Eliminates variable at the specified position using Fourier-Motzkin variable elimination.