MLIR 24.0.0git
BlockPackMatmul.cpp
Go to the documentation of this file.
1//===- BlockPackMatmul.cpp - Linalg matmul block packing ------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
10
18#include "llvm/ADT/SmallVector.h"
19
20#include <optional>
21
22namespace mlir {
23#define GEN_PASS_DEF_LINALGBLOCKPACKMATMUL
24#include "mlir/Dialect/Linalg/Passes.h.inc"
25} // namespace mlir
26
27using namespace mlir;
28using namespace mlir::linalg;
29
30/// Return constant range span or nullopt, otherwise.
31static std::optional<int64_t> getConstantRange(const Range &range) {
32 std::optional<int64_t> stride = getConstantIntValue(range.stride);
33 if (!stride || *stride != 1)
34 return std::nullopt;
35 std::optional<int64_t> offset = getConstantIntValue(range.offset);
36 if (!offset)
37 return std::nullopt;
38 std::optional<int64_t> size = getConstantIntValue(range.size);
39 if (!size)
40 return std::nullopt;
41 return (*size - *offset);
42}
43
44/// Return true if all dimensions are fully divisible by the respective tiles.
45static bool validateFullTilesOnDims(linalg::LinalgOp linalgOp,
47 ArrayRef<int64_t> dims) {
48 if (dims.size() != tiles.size() || tiles.empty())
49 return false;
50
51 FailureOr<ContractionDimensions> contractDims =
52 inferContractionDims(linalgOp);
53 if (failed(contractDims))
54 return false;
55 unsigned batchDimsOffset = contractDims->batch.size();
56
57 // Skip the batch dimension if present.
58 // Offset all dimensions accordingly.
59 SmallVector<int64_t, 3> offsetDims(dims);
60 for (int64_t &offsetDim : offsetDims)
61 offsetDim += batchDimsOffset;
62
63 auto tileOp = cast<TilingInterface>(linalgOp.getOperation());
64 OpBuilder builder(tileOp);
65 OpBuilder::InsertionGuard guard(builder);
66 SmallVector<Range> iterationDomain = tileOp.getIterationDomain(builder);
67
68 for (auto dim : llvm::enumerate(offsetDims)) {
69 if (dim.value() >= static_cast<int64_t>(iterationDomain.size()))
70 return false;
71
72 std::optional<int64_t> tileSize = getConstantIntValue(tiles[dim.index()]);
73 std::optional<int64_t> rangeOnDim =
74 getConstantRange(iterationDomain[dim.value()]);
75
76 // If the tile factor or the range are non-constant, the tile size is
77 // considered to be invalid.
78 if (!tileSize || !rangeOnDim)
79 return false;
80
81 // The dimension must be fully divisible by the tile.
82 if (*rangeOnDim % *tileSize != 0)
83 return false;
84 }
85
86 return true;
87}
88
89/// Return failure or packed matmul with one of its operands transposed.
90static FailureOr<PackTransposeResult>
91transposePackedMatmul(RewriterBase &rewriter, linalg::LinalgOp linalgOp,
92 linalg::PackOp packOp, AffineMap operandMap,
93 ArrayRef<unsigned> blocksStartDimPos,
94 bool transposeOuterBlocks, bool transposeInnerBlocks) {
95 // TODO: Support Memref PackOp. Temporarily return failure.
96 if (!packOp.hasPureTensorSemantics())
97 return failure();
98
99 assert(operandMap.getNumDims() >= 4 &&
100 "expected at least 4D prepacked matmul");
101 assert(blocksStartDimPos.size() >= 2 &&
102 "expected starting outer and inner block positions");
103
104 // Bias toward innermost dimensions.
105 unsigned outerBlockPos = operandMap.getNumResults() - 4;
106 unsigned innerBlockPos = operandMap.getNumResults() - 2;
107
108 // Transpose control options define the desired block and element layout.
109 // Block transposition (outer dimensions) or element transposition (inner
110 // dimensions) may not be necessary depending on the original matmul data
111 // layout.
112 bool isOuterTransposed =
113 operandMap.getDimPosition(outerBlockPos) != blocksStartDimPos.end()[-2];
114 bool isInnerTransposed =
115 operandMap.getDimPosition(innerBlockPos) != blocksStartDimPos.back();
116
117 // Transpose only the dimensions that need that to conform to the provided
118 // transpotion settings.
119 SmallVector<int64_t> innerPerm = {0, 1};
120 if (isInnerTransposed != transposeInnerBlocks)
121 innerPerm = {1, 0};
122 SmallVector<int64_t> outerPerm = {0, 1};
123 if (isOuterTransposed != transposeOuterBlocks)
124 outerPerm = {1, 0};
125
126 // Leave the outer dimensions, like batch, unchanged by offsetting all
127 // outer dimensions permutations.
128 SmallVector<int64_t> offsetPerms;
129 for (auto i : llvm::seq(0u, outerBlockPos))
130 offsetPerms.push_back(i);
131 for (auto perm : outerPerm)
132 offsetPerms.push_back(perm + outerBlockPos);
133 outerPerm = offsetPerms;
134
135 FailureOr<PackTransposeResult> packTransposedMatmul =
136 packTranspose(rewriter, packOp, linalgOp,
137 /*maybeUnPackOp=*/nullptr, outerPerm, innerPerm);
138
139 return packTransposedMatmul;
140}
141
142/// Pack a matmul operation into blocked 4D layout.
143FailureOr<PackResult>
144linalg::blockPackMatmul(RewriterBase &rewriter, linalg::LinalgOp linalgOp,
145 const ControlBlockPackMatmulFn &controlPackMatmul) {
146 // Check to not let go the batch_matmul with extended semantic, through this
147 // transform.
148 if (auto *batchMatmulOp = dyn_cast<linalg::BatchMatmulOp>(&linalgOp)) {
149 if (batchMatmulOp->hasUserDefinedMaps()) {
150 return rewriter.notifyMatchFailure(
151 *batchMatmulOp,
152 "only batch_matmul ops with non-extended semantics are supported");
153 }
154 }
155
156 if (linalgOp.hasPureBufferSemantics())
157 return rewriter.notifyMatchFailure(linalgOp, "require tensor semantics");
158
159 std::optional<BlockPackMatmulOptions> options = controlPackMatmul(linalgOp);
160 if (!options)
161 return rewriter.notifyMatchFailure(linalgOp, "invalid packing options");
162
163 if (options->blockFactors.size() != 3)
164 return rewriter.notifyMatchFailure(linalgOp, "require 3 tile factors");
165
166 bool hasScalable = !options->scalableBlockFactors.empty();
167 if (hasScalable && options->scalableBlockFactors.size() != 3)
168 return rewriter.notifyMatchFailure(
169 linalgOp, "scalableBlockFactors must be empty or have 3 elements");
170
171 // Scalable tile sizes are non-constant at compile time, so they can never
172 // satisfy the full-tile divisibility check. Reject early before creating
173 // any ops to avoid modifying IR before returning notifyMatchFailure.
174 if (!options->allowPadding && hasScalable)
175 return rewriter.notifyMatchFailure(
176 linalgOp, "scalable block factors require padding");
177
179 for (auto [idx, factor] : llvm::enumerate(options->blockFactors)) {
180 bool isScalable = hasScalable && options->scalableBlockFactors[idx];
181 if (!isScalable) {
182 mnkTiles.push_back(rewriter.getIndexAttr(factor));
183 continue;
184 }
185 Value cst =
186 arith::ConstantIndexOp::create(rewriter, linalgOp.getLoc(), factor);
187 Value vscale = vector::VectorScaleOp::create(rewriter, linalgOp.getLoc(),
188 rewriter.getIndexType());
189 mnkTiles.push_back(
190 arith::MulIOp::create(rewriter, linalgOp.getLoc(), cst, vscale)
191 .getResult());
192 }
193
194 // If padding is disabled, make sure that dimensions can be packed cleanly.
195 if (!options->allowPadding &&
196 !validateFullTilesOnDims(linalgOp, mnkTiles, options->mnkOrder)) {
197 return rewriter.notifyMatchFailure(linalgOp,
198 "expect packing full tiles only");
199 }
200
201 OpBuilder::InsertionGuard guard(rewriter);
202 // The op is replaced, we need to set the insertion point after it.
203 rewriter.setInsertionPointAfter(linalgOp);
204
205 // Pack the matmul operation into blocked layout with two levels of
206 // subdivision:
207 // - major 2D blocks - outer dimensions, consist of minor blocks
208 // - minor 2D blocks - inner dimensions, consist of scalar elements
209 FailureOr<PackResult> packedMatmul = packMatmulGreedily(
210 rewriter, linalgOp, mnkTiles, options->mnkPaddedSizesNextMultipleOf,
211 options->mnkOrder);
212 if (failed(packedMatmul))
213 return failure();
214
215 assert(packedMatmul->packOps.size() == 3 &&
216 "invalid number of pack ops after matmul packing");
217 assert(packedMatmul->unPackOps.size() == 1 &&
218 "invalid number of unpack ops after matmul packing");
219
220 FailureOr<ContractionDimensions> contractDims =
221 inferContractionDims(packedMatmul->packedLinalgOp);
222 if (failed(contractDims))
223 return failure();
224
225 auto genericOp =
226 dyn_cast<linalg::GenericOp>(packedMatmul->packedLinalgOp.getOperation());
227 SmallVector<AffineMap> maps = genericOp.getIndexingMapsArray();
228
229 // Transpose LHS matrix according to the options.
230 FailureOr<PackTransposeResult> packedLhs = transposePackedMatmul(
231 rewriter, packedMatmul->packedLinalgOp, packedMatmul->packOps[0], maps[0],
232 contractDims->m, options->lhsTransposeOuterBlocks,
233 options->lhsTransposeInnerBlocks);
234 if (failed(packedLhs))
235 return failure();
236
237 // Update results.
238 packedMatmul->packOps[0] = packedLhs->transposedPackOp;
239 packedMatmul->packedLinalgOp = packedLhs->transposedLinalgOp;
240
241 // Transpose RHS matrix according to the options.
242 FailureOr<PackTransposeResult> packedRhs = transposePackedMatmul(
243 rewriter, packedMatmul->packedLinalgOp, packedMatmul->packOps[1], maps[1],
244 contractDims->k, options->rhsTransposeOuterBlocks,
245 options->rhsTransposeInnerBlocks);
246 if (failed(packedRhs))
247 return failure();
248
249 // Update results.
250 packedMatmul->packOps[1] = packedRhs->transposedPackOp;
251 packedMatmul->packedLinalgOp = packedRhs->transposedLinalgOp;
252
253 return packedMatmul;
254}
255
256namespace {
257template <typename OpTy>
258struct BlockPackMatmul : public OpRewritePattern<OpTy> {
259 BlockPackMatmul(MLIRContext *context, ControlBlockPackMatmulFn fun,
260 PatternBenefit benefit = 1)
261 : OpRewritePattern<OpTy>(context, benefit), controlFn(std::move(fun)) {}
262
263 LogicalResult matchAndRewrite(OpTy linalgOp,
264 PatternRewriter &rewriter) const override {
265 FailureOr<PackResult> packedMatmul =
266 blockPackMatmul(rewriter, linalgOp, controlFn);
267 if (failed(packedMatmul))
268 return failure();
269 return success();
270 }
271
272private:
273 ControlBlockPackMatmulFn controlFn;
274};
275
276template <>
277struct BlockPackMatmul<linalg::GenericOp>
278 : public OpRewritePattern<linalg::GenericOp> {
279 BlockPackMatmul(MLIRContext *context, ControlBlockPackMatmulFn fun,
280 PatternBenefit benefit = 1)
281 : OpRewritePattern<linalg::GenericOp>(context, benefit),
282 controlFn(std::move(fun)) {}
283
284 LogicalResult matchAndRewrite(linalg::GenericOp linalgOp,
285 PatternRewriter &rewriter) const override {
286 // Match suitable generics.
287 if (!linalg::isaContractionOpInterface(linalgOp)) {
288 return rewriter.notifyMatchFailure(linalgOp, "not a contraction");
289 }
290
291 using MapList = ArrayRef<ArrayRef<AffineExpr>>;
292 auto infer = [&](MapList m) {
293 return AffineMap::inferFromExprList(m, linalgOp.getContext());
294 };
295
296 AffineExpr i, j, k;
297 bindDims(linalgOp->getContext(), i, j, k);
298 SmallVector<AffineMap> maps = linalgOp.getIndexingMapsArray();
299
300 // For now, only match simple matmuls.
301 if (!(maps == infer({{i, k}, {k, j}, {i, j}}) ||
302 maps == infer({{k, i}, {k, j}, {i, j}}) ||
303 maps == infer({{i, k}, {j, k}, {i, j}}))) {
304 return rewriter.notifyMatchFailure(linalgOp, "not a suitable matmul");
305 }
306
307 FailureOr<PackResult> packedMatmul =
308 blockPackMatmul(rewriter, linalgOp, controlFn);
309 if (failed(packedMatmul))
310 return failure();
311 return success();
312 }
313
314private:
315 ControlBlockPackMatmulFn controlFn;
316};
317
318/// Convert linalg matmul ops to block layout and back.
319struct LinalgBlockPackMatmul
320 : public impl::LinalgBlockPackMatmulBase<LinalgBlockPackMatmul> {
321 using LinalgBlockPackMatmulBase::LinalgBlockPackMatmulBase;
322
323 void runOnOperation() override {
324 Operation *op = getOperation();
325 RewritePatternSet patterns(&getContext());
326
327 ControlBlockPackMatmulFn controlFn =
328 [&](linalg::LinalgOp op) -> BlockPackMatmulOptions {
329 BlockPackMatmulOptions options;
330
331 // Parse block-factors strings. Each element is either "N" (static) or
332 // "[N]" (scalable, i.e. N * vscale at runtime).
333 for (const std::string &blockFactor : *blockFactors) {
334 StringRef factor(blockFactor);
335 if (factor.starts_with("[") && factor.ends_with("]")) {
336 int64_t val = 0;
337 factor.drop_front().drop_back().getAsInteger(10, val);
338 options.blockFactors.push_back(val);
339 options.scalableBlockFactors.push_back(true);
340 } else {
341 int64_t val = 0;
342 factor.getAsInteger(10, val);
343 options.blockFactors.push_back(val);
344 options.scalableBlockFactors.push_back(false);
345 }
346 }
347 // If all flags are false, clear the vector so blockPackMatmul can take
348 // the cheaper static path.
349 if (llvm::none_of(options.scalableBlockFactors, [](bool b) { return b; }))
350 options.scalableBlockFactors.clear();
351
352 options.allowPadding = allowPadding;
353 options.mnkPaddedSizesNextMultipleOf =
354 SmallVector<int64_t>{*mnkPaddedSizesNextMultipleOf};
355 if (!mnkOrder.empty())
356 options.mnkOrder = SmallVector<int64_t>{*mnkOrder};
357 options.lhsTransposeOuterBlocks = lhsTransposeOuterBlocks;
358 options.lhsTransposeInnerBlocks = lhsTransposeInnerBlocks;
359 options.rhsTransposeOuterBlocks = rhsTransposeOuterBlocks;
360 options.rhsTransposeInnerBlocks = rhsTransposeInnerBlocks;
361 return options;
362 };
363
365 if (failed(applyPatternsGreedily(op, std::move(patterns))))
369} // namespace
370
372 RewritePatternSet &patterns, const ControlBlockPackMatmulFn &controlFn) {
373 patterns.add<BlockPackMatmul<linalg::GenericOp>,
374 BlockPackMatmul<linalg::MatmulOp>,
375 BlockPackMatmul<linalg::BatchMatmulOp>>(patterns.getContext(),
376 controlFn);
return success()
static FailureOr< PackTransposeResult > transposePackedMatmul(RewriterBase &rewriter, linalg::LinalgOp linalgOp, linalg::PackOp packOp, AffineMap operandMap, ArrayRef< unsigned > blocksStartDimPos, bool transposeOuterBlocks, bool transposeInnerBlocks)
Return failure or packed matmul with one of its operands transposed.
static bool validateFullTilesOnDims(linalg::LinalgOp linalgOp, ArrayRef< OpFoldResult > tiles, ArrayRef< int64_t > dims)
Return true if all dimensions are fully divisible by the respective tiles.
static std::optional< int64_t > getConstantRange(const Range &range)
Return constant range span or nullopt, otherwise.
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
b getContext())
static llvm::ManagedStatic< PassManagerOptions > options
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition AffineMap.h:46
unsigned getDimPosition(unsigned idx) const
Extracts the position of the dimensional expression at the given result, when the caller knows it is ...
unsigned getNumDims() const
unsigned getNumResults() const
static SmallVector< AffineMap, 4 > inferFromExprList(ArrayRef< ArrayRef< AffineExpr > > exprsList, MLIRContext *context)
Returns a vector of AffineMaps; each with as many results as exprs.size(), as many dims as the larges...
IntegerAttr getIndexAttr(int64_t value)
Definition Builders.cpp:116
IndexType getIndexType()
Definition Builders.cpp:59
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
RAII guard to reset the insertion point of the builder when destroyed.
Definition Builders.h:351
This class helps build Operations.
Definition Builders.h:210
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Definition Builders.h:415
void signalPassFailure()
Signal that some invariant was broken when running.
Definition Pass.h:226
This class represents the benefit of a pattern match in a unitless scheme that ranges from 0 (very li...
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.
This class coordinates the application of a rewrite on a set of IR, providing a way for clients to tr...
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,...
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:397
::mlir::Pass::Option< bool > rhsTransposeInnerBlocks
void populateBlockPackMatmulPatterns(RewritePatternSet &patterns, const ControlBlockPackMatmulFn &controlFn)
Patterns to block pack Linalg matmul ops.
FailureOr< PackTransposeResult > packTranspose(RewriterBase &rewriter, linalg::PackOp packOp, linalg::LinalgOp linalgOp, linalg::UnPackOp maybeUnPackOp, ArrayRef< int64_t > outerPerm, ArrayRef< int64_t > innerPerm)
Transpose a single PackOp -> LinalgOp -> UnPackOp chain and return the transposed PackOp -> LinalgOp ...
std::function< std::optional< BlockPackMatmulOptions >(linalg::LinalgOp)> ControlBlockPackMatmulFn
Function type which is used to control matmul packing.
FailureOr< PackResult > blockPackMatmul(RewriterBase &rewriter, linalg::LinalgOp linalgOp, const ControlBlockPackMatmulFn &controlPackMatmul)
Pack a matmul operation into blocked 4D layout.
FailureOr< ContractionDimensions > inferContractionDims(LinalgOp linalgOp)
Find at least 2 parallel (m and n) and 1 reduction (k) dimension candidates that form a matmul subcom...
FailureOr< PackResult > packMatmulGreedily(RewriterBase &rewriter, LinalgOp linalgOp, ArrayRef< OpFoldResult > mnkPackedSizes, ArrayRef< int64_t > mnkPaddedSizesNextMultipleOf, ArrayRef< int64_t > mnkOrder)
Pack a LinalgOp by greedily inferring matmul dimensions (m, n, k) where m and n are proper parallel d...
bool isaContractionOpInterface(LinalgOp linalgOp)
Checks whether linalgOp conforms to ContractionOpInterface.
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
Include the generated interface declarations.
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
void bindDims(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to DimExpr at positions: [0 .
Definition AffineExpr.h:311
LogicalResult applyPatternsGreedily(Region &region, const FrozenRewritePatternSet &patterns, GreedyRewriteConfig config=GreedyRewriteConfig(), bool *changed=nullptr)
Rewrite ops in the given region, which must be isolated from above, by repeatedly applying the highes...
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
Represents a range (offset, size, and stride) where each element of the triple may be dynamic or stat...
OpFoldResult stride
OpFoldResult size
OpFoldResult offset