MLIR-AIE 1.4.5.dev
AIEVecOptimizations.cpp
Go to the documentation of this file.
1//===- AIEVecOptimizations.cpp - Patterns to optimize AIEVec ----*- C++ -*-===//
2//
3// Copyright (C) 2023 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7// This file contains conversions and rewrite that replace common AIEVec ops
8// with more complex, and performant AIEVec ops.
9//===----------------------------------------------------------------------===//
10
12
18
19#include "mlir/Dialect/Affine/IR/AffineOps.h"
20#include "mlir/Dialect/Func/IR/FuncOps.h"
21#include "mlir/Dialect/MemRef/IR/MemRef.h"
22#include "mlir/Dialect/SCF/IR/SCF.h"
23#include "mlir/IR/PatternMatch.h"
24#include "mlir/Pass/PassManager.h"
25#include "mlir/Transforms/DialectConversion.h"
26#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
27#include "mlir/Transforms/Passes.h"
28
29#define DEBUG_TYPE "aievec-optimize"
30
31using namespace llvm;
32using namespace mlir;
33using namespace arith;
34using namespace vector;
35using namespace xilinx;
36using namespace xilinx::aievec;
37
38//===----------------------------------------------------------------------===//
39// Utility functions
40//===----------------------------------------------------------------------===//
41namespace xilinx {
42namespace aievec {
43
44SmallVector<NamedAttribute>
45buildFMAOpSplatAttrForElemTy(aievec::aie1::FMAOp fmaOp, int64_t bcastPos,
46 int64_t step = 1);
47
48} // namespace aievec
49} // namespace xilinx
50
51static bool canFoldAIEShiftAndBroadcast(aievec::BroadcastOp op,
52 aievec::ShiftOp &shiftOp,
53 int32_t &idx) {
54 if (!op.getSource().getDefiningOp())
55 return false;
56
57 shiftOp = dyn_cast<aievec::ShiftOp>(op.getSource().getDefiningOp());
58
59 if (!shiftOp)
60 return false;
61
62 VectorType vType = cast<VectorType>(shiftOp->getResult(0).getType());
63 int32_t elemSize = getElementSizeInBits(vType);
64 auto constOp = cast<arith::ConstantOp>(shiftOp.getShift().getDefiningOp());
65 int32_t shiftBytes = cast<IntegerAttr>(constOp.getValue()).getInt();
66 idx = shiftBytes * 8 / elemSize + op.getIdx();
67
68 return idx > 0 && idx < (int32_t)getVectorLaneSize(vType);
69}
70
71template <typename AIEv1MACLikeOp,
72 typename = std::enable_if_t<
73 std::is_same_v<AIEv1MACLikeOp, aievec::aie1::FMAOp> ||
74 std::is_same_v<AIEv1MACLikeOp, aievec::aie1::FMAOp::Adaptor>>>
75static bool isSingleColumnInt16VectorTimesScalarMac(AIEv1MACLikeOp fmaOp) {
76 // lhs is a 32xi16 vector
77 VectorType lhsVTy = cast<VectorType>(fmaOp.getLhs().getType());
78 auto intTy = dyn_cast<IntegerType>(lhsVTy.getElementType());
79 if (!intTy || intTy.getWidth() != 16)
80 return false;
81 if (lhsVTy.getShape()[0] != 32)
82 return false;
83 // Attributes match a Vector x Scalar mac
84 if (fmaOp.getXoffsets() != "0x73727170" ||
85 fmaOp.getXoffsetsHi() != "0x77767574" || fmaOp.getXstart() != "0" ||
86 fmaOp.getXsquare() != "0x3120" || fmaOp.getZoffsets() != "0" ||
87 fmaOp.getZoffsetsHi() != "0" || fmaOp.getZstep() != "1")
88 return false;
89 // lhs op is a concat of a vector and a dense<0> constant vector
90 if (!fmaOp.getLhs().getDefiningOp())
91 return false;
92 aievec::ConcatOp concatOp =
93 dyn_cast<aievec::ConcatOp>(fmaOp.getLhs().getDefiningOp());
94 if (!concatOp)
95 return false;
96 auto tailVec = concatOp.getSources()[1];
97 if (!tailVec.getDefiningOp())
98 return false;
99 auto constOp = dyn_cast<arith::ConstantOp>(tailVec.getDefiningOp());
100 if (!constOp)
101 return false;
102 auto cstDense = dyn_cast<DenseIntElementsAttr>(constOp.getValue());
103 if (!cstDense)
104 return false;
105 return llvm::all_of(cstDense, [](const APInt &val) { return val == 0; });
106}
107
108static bool singleColumnFMAOpCanFold(aievec::aie1::FMAOp fmaOp) {
109 auto *accProdOp = fmaOp.getAcc().getDefiningOp();
110 if (!accProdOp)
111 return false;
112 auto accFmaOp = dyn_cast<aievec::aie1::FMAOp>(accProdOp);
113 if (!accFmaOp)
114 return false;
115 if (!isSingleColumnInt16VectorTimesScalarMac(accFmaOp))
116 return false;
117 return fmaOp.getRhs() == accFmaOp.getRhs() &&
118 !singleColumnFMAOpCanFold(accFmaOp);
119}
120
121//===----------------------------------------------------------------------===//
122// Lowering patterns
123//===----------------------------------------------------------------------===//
125 : public OpConversionPattern<aievec::aie1::FMAOp> {
126 using OpConversionPattern<aievec::aie1::FMAOp>::OpConversionPattern;
127
128 LogicalResult
129 matchAndRewrite(aievec::aie1::FMAOp fmaOp, OpAdaptor adaptor,
130 ConversionPatternRewriter &rewriter) const override {
131 if (!isSingleColumnInt16VectorTimesScalarMac(adaptor))
132 return failure();
133 auto *accProdOp = adaptor.getAcc().getDefiningOp();
134 if (!accProdOp)
135 return failure();
136 auto accFmaOp = dyn_cast<aievec::aie1::FMAOp>(accProdOp);
137 if (!accFmaOp)
138 return failure();
139 if (!isSingleColumnInt16VectorTimesScalarMac(accFmaOp))
140 return failure();
141 if (adaptor.getRhs() != accFmaOp.getRhs())
142 return failure();
143 auto accConcatOp =
144 cast<aievec::ConcatOp>(accFmaOp.getLhs().getDefiningOp());
145 auto fmaConcatOp = cast<aievec::ConcatOp>(adaptor.getLhs().getDefiningOp());
146 unsigned fmaZstart, accFmaZstart;
147 if (adaptor.getZstart().getAsInteger(10, fmaZstart) ||
148 accFmaOp.getZstart().getAsInteger(10, accFmaZstart))
149 return failure();
150 auto start = std::min(fmaZstart, accFmaZstart);
151 auto step = std::max(fmaZstart, accFmaZstart) - start;
152 auto lowV = accConcatOp.getSources()[0];
153 auto hiV = fmaConcatOp.getSources()[0];
154 if (accFmaZstart > fmaZstart)
155 std::swap(lowV, hiV);
156 auto newConcatOp = aievec::ConcatOp::create(
157 rewriter, fmaOp.getLoc(), adaptor.getLhs().getType(),
158 SmallVector<Value, 2>({lowV, hiV}));
159 auto newFmaOpAttr = buildFMAOpSplatAttrForElemTy(fmaOp, start, step);
160 rewriter.replaceOpWithNewOp<aievec::aie1::FMAOp>(
161 fmaOp, TypeRange({fmaOp.getResult().getType()}),
162 ValueRange({newConcatOp, adaptor.getRhs(), accFmaOp.getAcc()}),
163 newFmaOpAttr);
164 return success();
165 }
166};
167
169 : public OpConversionPattern<aievec::BroadcastOp> {
170 using OpConversionPattern<aievec::BroadcastOp>::OpConversionPattern;
171
172 LogicalResult
173 matchAndRewrite(aievec::BroadcastOp bcastOp, OpAdaptor adaptor,
174 ConversionPatternRewriter &rewriter) const override {
175 aievec::ShiftOp shiftOp = nullptr;
176 int32_t idx = 0;
177
178 if (!canFoldAIEShiftAndBroadcast(bcastOp, shiftOp, idx)) {
179 return failure();
180 }
181
182 VectorType resultType = cast<VectorType>(bcastOp.getResult().getType());
183
184 rewriter.replaceOpWithNewOp<aievec::BroadcastOp>(bcastOp, resultType,
185 shiftOp.getLhs(), idx);
186
187 return success();
188 }
189};
190
191//===----------------------------------------------------------------------===//
192// Pattern collection
193//===----------------------------------------------------------------------===//
194static void
195populateAIEVecV1TransformationPatterns(RewritePatternSet &patterns) {
196 patterns.add<MergeSingleColumnI16FMAOpPattern>(patterns.getContext());
197}
198
199static void
200populateAIEVecV2TransformationPatterns(RewritePatternSet &patterns) {
201 patterns.add<FoldAIEShiftAndBroadcast>(patterns.getContext());
202}
203
204//===----------------------------------------------------------------------===//
205// Legalizations
206//===----------------------------------------------------------------------===//
207
208static void
209configureAIEVecV1TransformationLegalizations(ConversionTarget &target) {
210 target.addLegalDialect<aievec::AIEVecDialect,
211 aievec::aie1::AIEVecAIE1Dialect>();
212 target.addDynamicallyLegalOp<aievec::aie1::FMAOp>(
213 [](aievec::aie1::FMAOp fmaOp) {
214 if (isSingleColumnInt16VectorTimesScalarMac(fmaOp))
215 return !singleColumnFMAOpCanFold(fmaOp);
216 return true;
217 });
218}
219
220static void
221configureAIEVecV2TransformationLegalizations(ConversionTarget &target) {
222 target.addDynamicallyLegalOp<xilinx::aievec::BroadcastOp>(
223 [](xilinx::aievec::BroadcastOp op) {
224 aievec::ShiftOp shiftOp = nullptr;
225 int32_t idx = 0;
226 return !canFoldAIEShiftAndBroadcast(op, shiftOp, idx);
227 });
228}
229
230//===----------------------------------------------------------------------===//
231// Lowering passes
232//===----------------------------------------------------------------------===//
234 : public PassWrapper<AIEVecTransformationPass, OperationPass<>> {
235 MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(AIEVecTransformationPass)
236
240
245
246 // In case we want to register this pass as a standalone pass for test
247 // purposes.
248 StringRef getArgument() const final { return "test-aievec-optimize"; }
249 StringRef getDescription() const final {
250 return "Optimize groups of simple aievec ops into complex aievec ops.";
251 }
252 void getDependentDialects(DialectRegistry &registry) const override {
253 // TODO: Review list of dependent dialects.
254 registry.insert<affine::AffineDialect, xilinx::aievec::AIEVecDialect,
255 aievec::aie1::AIEVecAIE1Dialect, arith::ArithDialect,
256 memref::MemRefDialect, scf::SCFDialect,
257 vector::VectorDialect>();
258 }
259
260 Option<std::string> aieTarget{
261 *this, "aie-target",
262 llvm::cl::desc("Select AIE version: \"aie\" or \"aie2\". This will "
263 "determine the vector size and available operations."),
264 llvm::cl::init("aie")};
265
266 void runOnOperation() override {
267 auto *op = getOperation();
268 MLIRContext *context = &getContext();
269 RewritePatternSet patterns(context);
270 ConversionTarget target(*context);
271 AIEArch aieVersion = AIEArch::AIE;
272 if (!aieTarget.empty()) {
273 std::string target = aieTarget;
274 if (target == "aieml" || target == "aie2" || target == "aie2p") {
275 aieVersion = AIEArch::AIE2;
276 } else if (target != "aie") {
277 op->emitError() << "unknown AIE target '" << aieTarget << "'";
278 signalPassFailure();
279 return;
280 }
281 }
282
283 if (aieVersion == AIEArch::AIE) {
284 populateAIEVecV1TransformationPatterns(patterns);
285 configureAIEVecV1TransformationLegalizations(target);
286 } else {
287 populateAIEVecV2TransformationPatterns(patterns);
288 configureAIEVecV2TransformationLegalizations(target);
289 }
290
291 if (failed(applyPartialConversion(op, target, std::move(patterns)))) {
292 signalPassFailure();
293 }
294 }
295};
296
297static std::unique_ptr<::mlir::Pass>
298createAIEVecTransformationPass(const OptimizeAIEVecOptions &options) {
299 return std::make_unique<AIEVecTransformationPass>(options);
300}
301
303 : public PassWrapper<AIEVecConvOpTransformationPass, OperationPass<>> {
304 MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(AIEVecConvOpTransformationPass)
305
309
315
316 // In case we want to register this pass as a standalone pass for test
317 // purposes.
318 StringRef getArgument() const final {
319 return "test-aievec-convolution-optimize";
320 }
321 StringRef getDescription() const final {
322 return "Optimize chains of macs into AIE2 conv ops.";
323 }
324 void getDependentDialects(DialectRegistry &registry) const override {
325 // TODO: Review list of dependent dialects.
326 registry.insert<affine::AffineDialect, xilinx::aievec::AIEVecDialect,
327 aievec::aie1::AIEVecAIE1Dialect, arith::ArithDialect,
328 memref::MemRefDialect, scf::SCFDialect,
329 vector::VectorDialect>();
330 }
331
332 Option<std::string> aieTarget{
333 *this, "aie-target",
334 llvm::cl::desc("Select AIE version: \"aie\" or \"aie2\". This will "
335 "determine the vector size and available operations."),
336 llvm::cl::init("aie")};
337
338 Option<unsigned> shiftParam{
339 *this, "shift",
340 llvm::cl::desc("Shift parameter for rounding and saturation."),
341 llvm::cl::init(0)};
342
343 void runOnOperation() override {
344 auto *op = getOperation();
345 MLIRContext *context = &getContext();
346 RewritePatternSet patterns(context);
347 ConversionTarget target(*context);
348 AIEArch aieVersion = AIEArch::AIE;
349 if (!aieTarget.empty()) {
350 std::string target = aieTarget;
351 if (target == "aieml" || target == "aie2" || target == "aie2p") {
352 aieVersion = AIEArch::AIE2;
353 } else if (target != "aie") {
354 op->emitError() << "unknown AIE target '" << aieTarget << "'";
355 signalPassFailure();
356 return;
357 }
358 }
359
360 AnalysisManager am = getAnalysisManager();
361 if (aieVersion == AIEArch::AIE2) {
364 }
365
366 if (failed(applyPartialConversion(op, target, std::move(patterns)))) {
367 signalPassFailure();
368 }
369 }
370};
371
372static std::unique_ptr<::mlir::Pass>
373createAIEVecConvOpTransformationPass(const OptimizeAIEVecOptions &options) {
374 return std::make_unique<AIEVecConvOpTransformationPass>(options);
375}
376
377//============================================================================//
378//=============== Main AIEVec2AIEVec Pipeline Configuration ==================//
379//============================================================================//
380
381void xilinx::aievec::buildOptimizeAIEVec(OpPassManager &pm,
382 const OptimizeAIEVecOptions &options) {
383 // Add AIEVec transformation pass.
384 pm.addPass(createAIEVecTransformationPass(options));
385
386 pm.addPass(createCSEPass());
387 pm.addPass(createCanonicalizerPass());
388
389 // Add generating aievec convolution ops pass
390 if (options.aieTarget == "aieml" || options.aieTarget == "aie2" ||
391 options.aieTarget == "aie2p") {
393 pm.addPass(createAIEVecConvOpTransformationPass(options));
394 }
395
396 // Add post-lowering canonicalization passes.
397 pm.addPass(createCSEPass());
398 pm.addPass(createCanonicalizerPass());
399}
unsigned getVectorLaneSize(mlir::VectorType type)
Definition AIEVecUtils.h:54
SmallVector< NamedAttribute > buildFMAOpSplatAttrForElemTy(aievec::aie1::FMAOp fmaOp, int64_t bcastPos, int64_t step=1)
std::unique_ptr< mlir::Pass > createAIEVecConvolutionAnalysisPass()
int32_t getElementSizeInBits(mlir::VectorType type)
Definition AIEVecUtils.h:48
void configureAIEVecConvOpTransformationLegalizations(ConversionTarget &target, AnalysisManager &am)
void populateAIEVecConvOpTransformationPatterns(RewritePatternSet &patterns, AnalysisManager &am, unsigned shiftParam)
void buildOptimizeAIEVec(mlir::OpPassManager &pm, const OptimizeAIEVecOptions &options)
AIEArch
Definition Passes.h:19
AIEVecConvOpTransformationPass(const OptimizeAIEVecOptions &options)
void getDependentDialects(DialectRegistry &registry) const override
StringRef getDescription() const final
AIEVecTransformationPass(const OptimizeAIEVecOptions &options)
AIEVecTransformationPass()=default
void getDependentDialects(DialectRegistry &registry) const override
StringRef getDescription() const final
StringRef getArgument() const final
Option< std::string > aieTarget
LogicalResult matchAndRewrite(aievec::BroadcastOp bcastOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::aie1::FMAOp fmaOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
Options for the "optimize-aievec" pipeline.
Definition Passes.h:60
PassOptions::Option< unsigned > shiftParam
Definition Passes.h:66
PassOptions::Option< std::string > aieTarget
Definition Passes.h:61