MLIR-AIE 1.4.5.dev
AIEVecTransformOps.cpp
Go to the documentation of this file.
1//===- AIEVecTransformOps.cpp -----------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2023-2024 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7
9
10#include "mlir/Dialect/Affine/IR/AffineOps.h"
11#include "mlir/Dialect/Bufferization/IR/Bufferization.h"
12#include "mlir/Dialect/Linalg/IR/Linalg.h"
13#include "mlir/Dialect/Linalg/Utils/Utils.h"
14#include "mlir/Dialect/Transform/IR/TransformTypes.h"
15#include "mlir/Dialect/Transform/Interfaces/TransformInterfaces.h"
16#include "mlir/Dialect/Transform/Utils/Utils.h"
17#include "mlir/Dialect/Vector/IR/VectorOps.h"
18
19#include "llvm/ADT/DenseMap.h"
20#include "llvm/ADT/TypeSwitch.h"
21
22using namespace mlir;
23
24#define DEBUG_TYPE "aievec-transforms"
25
26//===----------------------------------------------------------------------===//
27// VectorizeContractionOp
28//===----------------------------------------------------------------------===//
29
30// Emit IR to convert the given tensor in the form tensor<...xMxNxTy> into a
31// tensor<...xvector<MxNxTy>>. It does so by bufferizing, casting, and
32// tensorizing.
33//
34// E.g., for a `%t : tensor<64x64x8x4xf32>`, it will generate the following IR:
35// ```
36// %0 = bufferization.to_memref %t : memref<64x64x8x4xf32>
37// %1 = vector.type_cast %0 : memref<64x64xvector<8x4xf32>>
38// %2 = bufferization.to_tensor %1 restrict : memref<64x64xvector<8x4xf32>>
39// ```
40static Value vectorizeTensor(OpBuilder &rewriter, Location loc, Value tensor) {
41 auto opTy = tensor.getType();
42 auto shapeTy = cast<ShapedType>(opTy);
43 auto shape = shapeTy.getShape();
44 auto elemTy = shapeTy.getElementType();
45 auto ToBufferOp = bufferization::ToBufferOp::create(
46 rewriter, loc, MemRefType::get(shape, elemTy), tensor);
47 auto rank = shape.size();
48 auto newShape = shape.slice(0, rank - 2);
49 auto opVecElemTy = VectorType::get(shape.slice(rank - 2, 2), elemTy);
50 auto opMemrefVecTy = MemRefType::get(newShape, opVecElemTy);
51 auto typeCastOp =
52 vector::TypeCastOp::create(rewriter, loc, opMemrefVecTy, ToBufferOp);
53 auto toTensorOp = bufferization::ToTensorOp::create(
54 rewriter, loc, RankedTensorType::get(newShape, opVecElemTy), typeCastOp);
55 toTensorOp.setRestrict(true);
56 return toTensorOp.getResult();
57}
58
59// Emit IR to convert the given tensor in the form tensor<...xvector<MxNxTy>>
60// into a tensor<...xMxNxTy>. It performs the inverse operation to
61// `vectorizeTensor` above.
62static Value scalarizeTensor(OpBuilder &rewriter, Location loc, Value tensor) {
63 auto opTy = tensor.getType();
64 auto shapeTy = cast<ShapedType>(opTy);
65
66 auto vecShape = shapeTy.getShape();
67 auto vecElemTy = cast<VectorType>(shapeTy.getElementType());
68 auto elemTy = vecElemTy.getElementType();
69 auto toBufferVecTyOp = bufferization::ToBufferOp::create(
70 rewriter, loc, MemRefType::get(vecShape, vecElemTy), tensor);
71
72 SmallVector<int64_t> scalShape;
73 for (auto d : shapeTy.getShape())
74 scalShape.push_back(d);
75 for (auto d : vecElemTy.getShape())
76 scalShape.push_back(d);
77 auto opMemrefScalTy = MemRefType::get(scalShape, elemTy);
78 auto typeCastOp = vector::TypeCastOp::create(rewriter, loc, opMemrefScalTy,
79 toBufferVecTyOp);
80
81 auto toTensorOp = bufferization::ToTensorOp::create(
82 rewriter, loc, RankedTensorType::get(scalShape, elemTy), typeCastOp);
83 toTensorOp.setRestrict(true);
84 return toTensorOp.getResult();
85}
86
87static bool vectorizeContractionOpBlock(OpBuilder &rewriter, Location loc,
88 Block &srcBlock, Block &dstBlock) {
89 auto *ctx = rewriter.getContext();
90 OpBuilder::InsertionGuard g(rewriter);
91 rewriter.setInsertionPointToStart(&dstBlock);
92 auto baA = static_cast<Value>(dstBlock.getArgument(0));
93 auto baB = static_cast<Value>(dstBlock.getArgument(1));
94 auto baC = static_cast<Value>(dstBlock.getArgument(2));
95 // Store vectorized values for op replacement
96 llvm::DenseMap<Value, Value> convertedValues;
97 convertedValues.try_emplace(srcBlock.getArgument(0), baA);
98 convertedValues.try_emplace(srcBlock.getArgument(1), baB);
99 convertedValues.try_emplace(srcBlock.getArgument(2), baC);
100 auto indexingMaps = rewriter.getAffineMapArrayAttr(
101 {AffineMap::getPermutationMap(ArrayRef<unsigned>{1, 0, 2}, ctx)
102 .dropResults(0),
103 AffineMap::getPermutationMap(ArrayRef<unsigned>{0, 2, 1}, ctx)
104 .dropResults(0),
105 AffineMap::getPermutationMap(ArrayRef<unsigned>{2, 0, 1}, ctx)
106 .dropResults(0)});
107 auto iteratorTypes = rewriter.getArrayAttr(
108 {vector::IteratorTypeAttr::get(ctx, vector::IteratorType::parallel),
109 vector::IteratorTypeAttr::get(ctx, vector::IteratorType::parallel),
110 vector::IteratorTypeAttr::get(ctx, vector::IteratorType::reduction)});
111 bool addOpFound = false, mulOpFound = false;
112 WalkResult walkResult = srcBlock.walk([&](Operation *op) {
113 return llvm::TypeSwitch<Operation *, WalkResult>(op)
114 .Case<arith::AddIOp, arith::AddFOp>([&](auto addOp) {
115 if (addOpFound)
116 return WalkResult::interrupt();
117 addOpFound = true;
118 auto lhs = addOp->getOperand(0);
119 auto rhs = addOp->getOperand(1);
120 Value opA, opB, opC;
121 auto lhsDefOp = lhs.getDefiningOp();
122 auto rhsDefOp = rhs.getDefiningOp();
123 if (isa_and_nonnull<arith::MulIOp, arith::MulFOp>(lhsDefOp)) {
124 opA = convertedValues[lhsDefOp->getOperand(0)];
125 opB = convertedValues[lhsDefOp->getOperand(1)];
126 opC = convertedValues[rhs];
127 } else if (isa_and_nonnull<arith::MulIOp, arith::MulFOp>(rhsDefOp)) {
128 opA = convertedValues[rhsDefOp->getOperand(0)];
129 opB = convertedValues[rhsDefOp->getOperand(1)];
130 opC = convertedValues[lhs];
131 } else
132 return WalkResult::interrupt();
133 auto conOp = vector::ContractionOp::create(
134 rewriter, loc, opA, opB, opC, indexingMaps, iteratorTypes);
135 convertedValues.try_emplace(op->getResult(0), conOp.getResult());
136 return WalkResult::advance();
137 })
138 .Case<arith::MulIOp, arith::MulFOp>([&](auto) {
139 if (mulOpFound)
140 return WalkResult::interrupt();
141 mulOpFound = true;
142 return WalkResult::skip();
143 })
144 .Case<linalg::YieldOp>([&](linalg::YieldOp yieldOp) {
145 linalg::YieldOp::create(rewriter, loc,
146 convertedValues[yieldOp.getValues()[0]]);
147 return WalkResult::advance(); // Or ::interrupt()
148 })
149 .Default([&](Operation *unaryOp) {
150 if (unaryOp->getNumResults() != 1 || unaryOp->getNumOperands() != 1)
151 return WalkResult::interrupt();
152 auto srcOpIn = unaryOp->getOperand(0);
153 auto srcOpInTy = srcOpIn.getType();
154 auto srcOpTy = unaryOp->getResultTypes()[0];
155 auto dstOpIn = convertedValues[srcOpIn];
156 Type dstOpTy = dstOpIn.getType();
157 if (srcOpInTy != srcOpTy) {
158 auto vecElemTy = dyn_cast<VectorType>(dstOpTy);
159 if (!vecElemTy)
160 return WalkResult::interrupt();
161 dstOpTy = VectorType::get(vecElemTy.getShape(), srcOpTy);
162 }
163 auto *newOp =
164 rewriter.create(loc, unaryOp->getName().getIdentifier(),
165 {dstOpIn}, {dstOpTy}, unaryOp->getAttrs());
166 convertedValues.try_emplace(unaryOp->getResult(0),
167 newOp->getResult(0));
168 return WalkResult::advance();
169 });
170 });
171 return mulOpFound && addOpFound && !walkResult.wasInterrupted();
172}
173
174DiagnosedSilenceableFailure transform::VectorizeContractionOp::applyToOne(
175 TransformRewriter &rewriter, linalg::GenericOp target,
176 ApplyToEachResultList &results, TransformState &state) {
177
178 auto *ctx = target.getContext();
179 SmallVector<Value> inputs = target.getInputs();
180 if (SmallVector<Value> outputs = target.getOutputs();
181 inputs.size() != 2 || outputs.size() != 1)
182 return emitSilenceableError() << "payload is not a contraction.";
183
184 // Split the iterators in two: inner contraction + remaining
185 SmallVector<utils::IteratorType> iterators = target.getIteratorTypesArray();
186 auto innerMostIterators =
187 SmallVector<utils::IteratorType>(iterators.end() - 3, iterators.end());
188 auto outerMostIterators =
189 SmallVector<utils::IteratorType>(iterators.begin(), iterators.end() - 3);
190
191 if (!linalg::isParallelIterator(innerMostIterators[0]) ||
192 !linalg::isParallelIterator(innerMostIterators[1]) ||
193 !linalg::isReductionIterator(innerMostIterators[2]))
194 return emitSilenceableError()
195 << "linalg.generic op innermost iterators don't correspond with a "
196 "gemm-like contraction.";
197
198 auto indexingMaps = target.getIndexingMapsArray();
199 //===
200 // Verify that the innermost dimensions are a contraction
201 //===
202
203 // 1. Build the indexing maps for the operands of a GEMM contraction
204 auto mmAidxMap =
205 AffineMap::getPermutationMap(ArrayRef<unsigned>{1, 0, 2}, ctx)
206 .dropResults(0);
207 auto mmBidxMap =
208 AffineMap::getPermutationMap(ArrayRef<unsigned>{0, 2, 1}, ctx)
209 .dropResults(0);
210 auto mmCidxMap =
211 AffineMap::getPermutationMap(ArrayRef<unsigned>{2, 0, 1}, ctx)
212 .dropResults(0);
213
214 // 2. Get the indexing maps for the 2 innermost dimmensions of each operand
215 SmallVector<int64_t> outerMostResults;
216 for (int64_t i = 0; i < indexingMaps[0].getNumResults() - 2; i++)
217 outerMostResults.push_back(i);
218
219 auto innerMostA = indexingMaps[0].dropResults(outerMostResults);
220 auto innerMostB = indexingMaps[1].dropResults(outerMostResults);
221 auto innerMostC = indexingMaps[2].dropResults(outerMostResults);
222
223 // 3. Compare the extended GEMM contraction indexing maps with the indexing
224 // maps of the innermost results.
225 int64_t numOuterMostDims = indexingMaps[0].getNumDims() - 3;
226 if (innerMostA != mmAidxMap.shiftDims(numOuterMostDims) ||
227 innerMostB != mmBidxMap.shiftDims(numOuterMostDims) ||
228 innerMostC != mmCidxMap.shiftDims(numOuterMostDims))
229 return emitSilenceableError()
230 << "linalg.generic op innermost indexing maps don't correspond with "
231 "a gemm-like contraction.";
232
233 //===
234 // Create new indexing maps for the vectorized operation
235 //===
236
237 SmallVector<AffineExpr> remOuterDims;
238 for (unsigned i = 0; i < numOuterMostDims; i++)
239 remOuterDims.push_back(getAffineDimExpr(i, ctx));
240 unsigned numResults = indexingMaps[0].getNumResults();
241 SmallVector<int64_t> positions = {numResults - 2, numResults - 1};
242 auto outerMostAidxMap =
243 indexingMaps[0].dropResults(positions).replaceDimsAndSymbols(
244 remOuterDims, {}, numOuterMostDims, 0);
245 auto outerMostBidxMap =
246 indexingMaps[1].dropResults(positions).replaceDimsAndSymbols(
247 remOuterDims, {}, numOuterMostDims, 0);
248 auto outerMostCidxMap =
249 indexingMaps[2].dropResults(positions).replaceDimsAndSymbols(
250 remOuterDims, {}, numOuterMostDims, 0);
251
252 rewriter.setInsertionPoint(target);
253 Location loc = target.getLoc();
254 // Insert reshape ops for input operands
255 auto opA = vectorizeTensor(rewriter, loc, target.getInputs()[0]);
256 auto opB = vectorizeTensor(rewriter, loc, target.getInputs()[1]);
257 auto opC = vectorizeTensor(rewriter, loc, target.getOutputs()[0]);
258
259 // Create new linalg.generic with vector arguments and vectorized basic block
260 auto newOp = linalg::GenericOp::create(
261 rewriter, loc, TypeRange({opC.getType()}), ValueRange({opA, opB}),
262 ValueRange({opC}),
263 SmallVector<AffineMap>(
264 {outerMostAidxMap, outerMostBidxMap, outerMostCidxMap}),
265 outerMostIterators);
266 auto &opBody = newOp->getRegion(0);
267 opBody.push_back(new Block());
268 auto &opBlock = opBody.front();
269 opBlock.addArguments({cast<TensorType>(opA.getType()).getElementType(),
270 cast<TensorType>(opB.getType()).getElementType(),
271 cast<TensorType>(opC.getType()).getElementType()},
272 {loc, loc, loc});
273 if (!vectorizeContractionOpBlock(rewriter, loc, target->getRegion(0).front(),
274 opBlock))
275 return emitSilenceableError()
276 << "linalg.generic op payload does not correspond with a "
277 "vectorizable contraction.";
278
279 // Insert reshape ops for output operand
280 auto res = scalarizeTensor(rewriter, loc, newOp.getResults()[0]);
281 rewriter.replaceOp(target, res);
282
283 results.push_back(newOp);
284
285 return DiagnosedSilenceableFailure::success();
286}
287
288#define GET_OP_CLASSES
289#include "aie/Dialect/AIEVec/TransformOps/AIEVecTransformOps.cpp.inc"