|
| 1 | +#include "ondrix/Conversion/OndspVectorization/OndspVectorization.h" |
| 2 | +#include "ondrix/Conversion/Utils/MemRefLayoutUtils.h" |
| 3 | +#include "ondrix/Conversion/Utils/ReductionUtils.h" |
| 4 | + |
| 5 | +#include "ondrix/Dialect/ondsp/IR/OndspDialect.h" |
| 6 | +#include "ondrix/Dialect/ondsp/IR/OndspOps.h" |
| 7 | +#include "ondrix/Dialect/ondsp/IR/OndspSemantics.h" |
| 8 | + |
| 9 | +#include "mlir/Dialect/Arith/IR/Arith.h" |
| 10 | +#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h" |
| 11 | +#include "mlir/Dialect/Math/IR/Math.h" |
| 12 | +#include "mlir/Dialect/MemRef/IR/MemRef.h" |
| 13 | +#include "mlir/Dialect/SCF/IR/SCF.h" |
| 14 | +#include "mlir/Dialect/Vector/IR/VectorOps.h" |
| 15 | +#include "mlir/IR/BuiltinOps.h" |
| 16 | +#include "mlir/Pass/Pass.h" |
| 17 | +#include "mlir/Transforms/DialectConversion.h" |
| 18 | + |
| 19 | +namespace ondrix { |
| 20 | +#define GEN_PASS_DEF_VECTORIZEONDSPFPFASTMEMREFREDUCE |
| 21 | +#include "ondrix/Conversion/Passes.h.inc" |
| 22 | +} // namespace ondrix |
| 23 | + |
| 24 | +using namespace mlir; |
| 25 | + |
| 26 | +namespace { |
| 27 | + |
| 28 | +/// Largest accepted lane count, bounding every index the rewrite derives. |
| 29 | +constexpr int64_t kMaxVectorWidth = 4096; |
| 30 | + |
| 31 | +bool isSupportedFastMemRefReduction(ondrix::ondsp::ReduceMacOp op) { |
| 32 | + auto numeric = dyn_cast<ondrix::ondsp::FpAttr>(op.getNumeric()); |
| 33 | + if (!numeric || !numeric.getFormat().isF32() || |
| 34 | + numeric.getContract() != ondrix::ondsp::FpContractMode::Fast || op.getProduct() || |
| 35 | + !op.getInitial().getType().isF32()) |
| 36 | + return false; |
| 37 | + auto lhsType = dyn_cast<MemRefType>(op.getLhs().getType()); |
| 38 | + auto rhsType = dyn_cast<MemRefType>(op.getRhs().getType()); |
| 39 | + return lhsType && rhsType && lhsType.getRank() == 1 && rhsType.getRank() == 1 && |
| 40 | + lhsType.getElementType().isF32() && rhsType.getElementType().isF32() && |
| 41 | + ondrix::conversion::hasDefaultLLVMVectorMemorySpace(lhsType) && |
| 42 | + ondrix::conversion::hasDefaultLLVMVectorMemorySpace(rhsType) && |
| 43 | + isLastMemrefDimUnitStride(lhsType) && isLastMemrefDimUnitStride(rhsType); |
| 44 | +} |
| 45 | + |
| 46 | +class FastReduceMacOpVectorization final : public OpConversionPattern<ondrix::ondsp::ReduceMacOp> { |
| 47 | +public: |
| 48 | + FastReduceMacOpVectorization(MLIRContext *context, int64_t vectorWidth) |
| 49 | + : OpConversionPattern(context), vectorWidth(vectorWidth) {} |
| 50 | + |
| 51 | + LogicalResult matchAndRewrite(ondrix::ondsp::ReduceMacOp op, OpAdaptor adaptor, |
| 52 | + ConversionPatternRewriter &rewriter) const override { |
| 53 | + if (!isSupportedFastMemRefReduction(op)) |
| 54 | + return failure(); |
| 55 | + |
| 56 | + Type elementType = rewriter.getF32Type(); |
| 57 | + FailureOr<ondrix::conversion::RankOneReductionBounds> bounds = |
| 58 | + ondrix::conversion::createRankOneMemRefReductionBounds( |
| 59 | + op, adaptor.getLhs(), adaptor.getRhs(), elementType, "fast f32 memref vectorization", |
| 60 | + rewriter); |
| 61 | + if (failed(bounds)) |
| 62 | + return failure(); |
| 63 | + |
| 64 | + Location loc = op.getLoc(); |
| 65 | + auto fastFlags = |
| 66 | + arith::FastMathFlagsAttr::get(getContext(), ondrix::ondsp::getFastContractFlags()); |
| 67 | + Value vectorStep = rewriter.create<arith::ConstantIndexOp>(loc, vectorWidth); |
| 68 | + Value remainder = rewriter.create<arith::RemUIOp>(loc, bounds->upperBound, vectorStep); |
| 69 | + Value vectorEnd = rewriter.create<arith::SubIOp>(loc, bounds->upperBound, remainder); |
| 70 | + auto vectorType = VectorType::get({vectorWidth}, elementType); |
| 71 | + Value zeroLanes = rewriter.create<arith::ConstantOp>( |
| 72 | + loc, vectorType, DenseElementsAttr::get(vectorType, rewriter.getF32FloatAttr(0.0f))); |
| 73 | + |
| 74 | + auto vectorLoop = rewriter.create<scf::ForOp>( |
| 75 | + loc, bounds->lowerBound, vectorEnd, vectorStep, ValueRange{zeroLanes}, |
| 76 | + [&](OpBuilder &builder, Location bodyLoc, Value base, ValueRange iterArgs) { |
| 77 | + Value lhs = builder.create<vector::LoadOp>(bodyLoc, vectorType, adaptor.getLhs(), base); |
| 78 | + Value rhs = builder.create<vector::LoadOp>(bodyLoc, vectorType, adaptor.getRhs(), base); |
| 79 | + Value next = builder.create<math::FmaOp>(bodyLoc, lhs, rhs, iterArgs.front(), fastFlags); |
| 80 | + builder.create<scf::YieldOp>(bodyLoc, next); |
| 81 | + }); |
| 82 | + |
| 83 | + // MLIR 17's vector.reduction carries no fastmath attribute; it lowers to |
| 84 | + // the ordered llvm.intr.vector.reduce.fadd, a fixed deterministic |
| 85 | + // regrouping the declared relaxation authorizes. |
| 86 | + Value reduced = rewriter.create<vector::ReductionOp>(loc, vector::CombiningKind::ADD, |
| 87 | + vectorLoop.getResult(0)); |
| 88 | + |
| 89 | + Value scalarStep = rewriter.create<arith::ConstantIndexOp>(loc, 1); |
| 90 | + auto tailLoop = rewriter.create<scf::ForOp>( |
| 91 | + loc, vectorEnd, bounds->upperBound, scalarStep, ValueRange{reduced}, |
| 92 | + [&](OpBuilder &builder, Location bodyLoc, Value index, ValueRange iterArgs) { |
| 93 | + Value lhs = builder.create<memref::LoadOp>(bodyLoc, adaptor.getLhs(), index); |
| 94 | + Value rhs = builder.create<memref::LoadOp>(bodyLoc, adaptor.getRhs(), index); |
| 95 | + Value next = builder.create<math::FmaOp>(bodyLoc, lhs, rhs, iterArgs.front(), fastFlags); |
| 96 | + builder.create<scf::YieldOp>(bodyLoc, next); |
| 97 | + }); |
| 98 | + |
| 99 | + rewriter.replaceOpWithNewOp<arith::AddFOp>(op, tailLoop.getResult(0), adaptor.getInitial(), |
| 100 | + fastFlags); |
| 101 | + return success(); |
| 102 | + } |
| 103 | + |
| 104 | +private: |
| 105 | + int64_t vectorWidth; |
| 106 | +}; |
| 107 | + |
| 108 | +class VectorizeOndspFpFastMemRefReducePass final |
| 109 | + : public ondrix::impl::VectorizeOndspFpFastMemRefReduceBase< |
| 110 | + VectorizeOndspFpFastMemRefReducePass> { |
| 111 | +public: |
| 112 | + using ondrix::impl::VectorizeOndspFpFastMemRefReduceBase< |
| 113 | + VectorizeOndspFpFastMemRefReducePass>::VectorizeOndspFpFastMemRefReduceBase; |
| 114 | + |
| 115 | + void runOnOperation() override { |
| 116 | + if (vectorWidth <= 1) { |
| 117 | + getOperation().emitError("vector-width must be greater than one"); |
| 118 | + signalPassFailure(); |
| 119 | + return; |
| 120 | + } |
| 121 | + if (vectorWidth > kMaxVectorWidth) { |
| 122 | + getOperation().emitError("vector-width must not exceed ") << kMaxVectorWidth; |
| 123 | + signalPassFailure(); |
| 124 | + return; |
| 125 | + } |
| 126 | + |
| 127 | + RewritePatternSet patterns(&getContext()); |
| 128 | + patterns.add<FastReduceMacOpVectorization>(&getContext(), vectorWidth); |
| 129 | + |
| 130 | + ConversionTarget target(getContext()); |
| 131 | + target.addLegalDialect<arith::ArithDialect, cf::ControlFlowDialect, math::MathDialect, |
| 132 | + memref::MemRefDialect, ondrix::ondsp::OndspDialect, scf::SCFDialect, |
| 133 | + vector::VectorDialect>(); |
| 134 | + target.addDynamicallyLegalOp<ondrix::ondsp::ReduceMacOp>( |
| 135 | + [](ondrix::ondsp::ReduceMacOp op) { return !isSupportedFastMemRefReduction(op); }); |
| 136 | + |
| 137 | + if (failed(applyPartialConversion(getOperation(), target, std::move(patterns)))) |
| 138 | + signalPassFailure(); |
| 139 | + } |
| 140 | +}; |
| 141 | + |
| 142 | +} // namespace |
| 143 | + |
| 144 | +std::unique_ptr<Pass> ondrix::createVectorizeOndspFpFastMemRefReducePass() { |
| 145 | + return std::make_unique<VectorizeOndspFpFastMemRefReducePass>(); |
| 146 | +} |
| 147 | + |
| 148 | +std::unique_ptr<Pass> ondrix::createVectorizeOndspFpFastMemRefReducePass( |
| 149 | + const ondrix::VectorizeOndspFpFastMemRefReduceOptions &options) { |
| 150 | + return std::make_unique<VectorizeOndspFpFastMemRefReducePass>(options); |
| 151 | +} |
0 commit comments