MLIR 24.0.0git
TosaToLinalgNamed.cpp
Go to the documentation of this file.
1//===- TosaToLinalgNamed.cpp - Lowering Tosa to Linalg Named Ops ----------===//
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//
9// These rewriters lower from the Tosa to the Linalg named ops.
10//
11//===----------------------------------------------------------------------===//
12
13#include "TosaToLinalgUtils.h"
23#include "mlir/IR/Matchers.h"
26#include "llvm/ADT/SmallVectorExtras.h"
27#include "llvm/ADT/TypeSwitch.h"
28
29#include <optional>
30#include <type_traits>
31
32using namespace mlir;
33using namespace mlir::tosa;
34
35// Return the linalg.elementwise kind that is semantically equivalent to the
36// given TOSA elementwise op for the provided element type, or std::nullopt if
37// the op has no direct linalg.elementwise counterpart. Only ops whose TOSA
38// lowering maps to a single linalg.elementwise function are handled; the more
39// involved lowerings (e.g. quantized negate, mul with shift, casts) are left to
40// the generic TosaToLinalg conversion.
41static std::optional<linalg::ElementwiseKind>
43 using linalg::ElementwiseKind;
44 const bool isFloat = isa<FloatType>(elementTy);
45 const bool isSignlessInt = elementTy.isSignlessInteger();
46 const bool isBoolean = elementTy.isInteger(1);
47
48 // Unary ops. linalg.elementwise only defines these on floating point types.
49 if (isFloat) {
50 if (isa<tosa::AbsOp>(op))
51 return ElementwiseKind::abs;
52 if (isa<tosa::CeilOp>(op))
53 return ElementwiseKind::ceil;
54 if (isa<tosa::FloorOp>(op))
55 return ElementwiseKind::floor;
56 if (isa<tosa::ExpOp>(op))
57 return ElementwiseKind::exp;
58 if (isa<tosa::LogOp>(op))
59 return ElementwiseKind::log;
60 if (isa<tosa::RsqrtOp>(op))
61 return ElementwiseKind::rsqrt;
62 if (isa<tosa::SinOp>(op))
63 return ElementwiseKind::sin;
64 if (isa<tosa::CosOp>(op))
65 return ElementwiseKind::cos;
66 if (isa<tosa::TanhOp>(op))
67 return ElementwiseKind::tanh;
68 if (isa<tosa::ErfOp>(op))
69 return ElementwiseKind::erf;
70 if (isa<tosa::ReciprocalOp>(op))
71 return ElementwiseKind::reciprocal;
72 if (isa<tosa::PowOp>(op))
73 return ElementwiseKind::powf;
74 }
75
76 // Binary ops defined on both floating point and (non-boolean) integers.
77 if (isFloat || (isSignlessInt && !isBoolean)) {
78 if (isa<tosa::AddOp>(op))
79 return ElementwiseKind::add;
80 if (isa<tosa::SubOp>(op))
81 return ElementwiseKind::sub;
82 }
83
84 // Signed integer division.
85 if (isSignlessInt && !isBoolean && isa<tosa::IntDivOp>(op))
86 return ElementwiseKind::div;
87
88 // Multiply maps to a plain elementwise multiply only when no rescaling shift
89 // is requested (the shift must be a constant zero) and the inputs already
90 // share the result element type. The shifted/rescaled integer variant is left
91 // to the generic conversion.
92 if (auto mulOp = dyn_cast<tosa::MulOp>(op)) {
93 DenseElementsAttr shiftAttr;
94 const bool zeroShift =
95 matchPattern(mulOp.getShift(), m_Constant(&shiftAttr)) &&
96 shiftAttr.getValues<APInt>()[0].isZero();
97 const bool matchingTypes =
98 cast<ShapedType>(mulOp.getInput1().getType()).getElementType() ==
99 elementTy &&
100 cast<ShapedType>(mulOp.getInput2().getType()).getElementType() ==
101 elementTy;
102 if ((isFloat || (isSignlessInt && !isBoolean)) && zeroShift &&
103 matchingTypes)
104 return ElementwiseKind::mul;
105 }
106
107 // Negate maps to floating-point negation only when the zero points are zero.
108 // The quantized integer variant is left to the generic conversion.
109 if (auto negateOp = dyn_cast<tosa::NegateOp>(op)) {
110 if (isFloat) {
111 FailureOr<int64_t> inZp = negateOp.getInput1ZeroPoint();
112 FailureOr<int64_t> outZp = negateOp.getOutputZeroPoint();
113 if (succeeded(inZp) && succeeded(outZp) && *inZp == 0 && *outZp == 0)
114 return ElementwiseKind::negf;
115 }
116 }
117
118 // Maximum/minimum. The NaN "IGNORE" mode requires an additional compare and
119 // select that linalg.elementwise cannot express, so only the default
120 // "PROPAGATE" mode is handled for floating point types.
121 auto nanModeIsPropagate = [](auto tosaOp) {
122 return tosaOp.getNanMode() == NanPropagationMode::PROPAGATE;
123 };
124 if (isFloat || (isSignlessInt && !isBoolean)) {
125 if (auto maxOp = dyn_cast<tosa::MaximumOp>(op)) {
126 if (!isFloat || nanModeIsPropagate(maxOp))
127 return ElementwiseKind::max_signed;
128 }
129 if (auto minOp = dyn_cast<tosa::MinimumOp>(op)) {
130 if (!isFloat || nanModeIsPropagate(minOp))
131 return ElementwiseKind::min_signed;
132 }
133 }
134
135 // Select (ternary) for valid element types.
136 if ((isFloat || isSignlessInt || isBoolean) && isa<tosa::SelectOp>(op))
137 return ElementwiseKind::select;
138
139 return std::nullopt;
140}
141
142std::optional<linalg::ElementwiseKind>
144 if (op->getNumResults() != 1)
145 return std::nullopt;
146
147 // Make sure all operands and result have static sizes
148 for (Value operand : op->getOperands()) {
149 auto operandTy = dyn_cast<RankedTensorType>(operand.getType());
150 if (!operandTy || !operandTy.hasStaticShape())
151 return std::nullopt;
152 }
153 auto resultTy = dyn_cast<RankedTensorType>(op->getResult(0).getType());
154 if (!resultTy || !resultTy.hasStaticShape())
155 return std::nullopt;
156
157 return getElementwiseKind(op, resultTy.getElementType());
158}
159
161 TypedAttr padAttr, OpBuilder &rewriter) {
162 // Input should be padded only if necessary.
163 if (llvm::all_of(pad, [](int64_t p) { return p == 0; }))
164 return input;
165
166 ShapedType inputTy = cast<ShapedType>(input.getType());
167 Type inputETy = inputTy.getElementType();
168 auto inputShape = inputTy.getShape();
169
170 assert((inputShape.size() * 2) == pad.size());
171
172 SmallVector<int64_t, 4> paddedShape;
175 for (size_t i : llvm::seq(inputShape.size())) {
176 auto lowPad = pad[i * 2];
177 auto highPad = pad[i * 2 + 1];
178 if (ShapedType::isDynamic(inputShape[i]))
179 paddedShape.push_back(inputShape[i]);
180 else
181 paddedShape.push_back(inputShape[i] + highPad + lowPad);
182 lowIndices.push_back(rewriter.getIndexAttr(lowPad));
183 highIndices.push_back(rewriter.getIndexAttr(highPad));
184 }
185
186 Value padValue = arith::ConstantOp::create(rewriter, loc, padAttr);
187
188 return tensor::PadOp::create(rewriter, loc,
189 RankedTensorType::get(paddedShape, inputETy),
190 input, lowIndices, highIndices, padValue);
191}
192
193static mlir::Value
195 Value conv, Value result,
196 ArrayRef<AffineMap> indexingMaps) {
197 ShapedType resultTy = cast<ShapedType>(conv.getType());
198 return linalg::GenericOp::create(
199 rewriter, loc, resultTy, ValueRange({bias, conv}), result,
200 indexingMaps, getNParallelLoopsAttrs(resultTy.getRank()),
201 [](OpBuilder &builder, Location loc, ValueRange args) {
202 Value biasVal = args[0];
203 Type resType = args[1].getType();
204 if (resType != biasVal.getType()) {
205 biasVal =
206 arith::ExtSIOp::create(builder, loc, resType, biasVal);
207 }
208 Value added =
209 arith::AddIOp::create(builder, loc, biasVal, args[1]);
210 linalg::YieldOp::create(builder, loc, added);
211 })
212 .getResult(0);
213}
214
215// Construct the affine map that a linalg generic would use to broadcast the
216// source tensor into the shape of the result tensor.
218 Value result) {
219 ShapedType resultTy = cast<ShapedType>(result.getType());
220 ShapedType sourceTy = cast<ShapedType>(source.getType());
221 const int64_t resultRank = resultTy.getRank();
222 const int64_t sourceRank = sourceTy.getRank();
223
224 // The source tensor is broadcast to all the outer dimensions of the
225 // result tensor.
226 SmallVector<AffineExpr> sourceDims;
227 // In the case of a rank one source tensor with a single element TOSA
228 // specifies that the value be broadcast meaning we need an edge case for a
229 // constant map.
230 assert(sourceTy.hasStaticShape() &&
231 "Dynamic broadcasting shapes not supported!");
232 if (sourceRank == 1 && sourceTy.getDimSize(0) == 1) {
233 sourceDims.push_back(rewriter.getAffineConstantExpr(0));
234 } else {
235 for (auto dim : llvm::seq<int64_t>(0, sourceRank)) {
236 auto expr = rewriter.getAffineDimExpr(dim + resultRank - sourceRank);
237 sourceDims.push_back(expr);
238 }
239 }
240
241 return AffineMap::get(/*dimCount=*/resultRank,
242 /*symbolCount=*/0, sourceDims, rewriter.getContext());
243}
244
245// Broadcast the source value to all the outer dimensions of the result value.
246// If required, the element type is expanded using an arith.extsi or arith.extf
247// operation as appropriate.
249 Location loc, Value source,
250 Value result) {
251 ShapedType resultTy = cast<ShapedType>(result.getType());
252 const int64_t resultRank = resultTy.getRank();
253 // Creating maps for the input and output of the broacast-like generic op.
254 SmallVector<AffineMap, 2> indexingMaps;
255 indexingMaps.push_back(getBroadcastingMap(rewriter, source, result));
256 indexingMaps.push_back(rewriter.getMultiDimIdentityMap(resultRank));
257
258 // Build the broadcast-like operation as a linalg.generic.
259 return linalg::GenericOp::create(
260 rewriter, loc, resultTy, ValueRange({source}), result,
261 indexingMaps, getNParallelLoopsAttrs(resultTy.getRank()),
262 [&resultTy](OpBuilder &builder, Location loc, ValueRange args) {
263 Value biasVal = args[0];
264 Type resType = args[1].getType();
265 if (resType != biasVal.getType()) {
266 biasVal =
267 resultTy.getElementType().isFloat()
268 ? arith::ExtFOp::create(
269 builder, loc, TypeRange{resType},
270 ValueRange{biasVal}, arith::ExtFOp::Properties{})
271 .getResult()
272 : arith::ExtSIOp::create(builder, loc, resType,
273 biasVal)
274 .getResult();
275 }
276 linalg::YieldOp::create(builder, loc, biasVal);
277 })
278 .getResult(0);
279}
280
282 ImplicitLocOpBuilder &builder) {
283 return arith::ConstantIndexOp::create(builder, attr);
284}
285
286// Calculating the output width/height using the formula:
287// H = ((IH+pad_top+pad_bottom-(dilation_y*(KH-1)+1))/stride_y)+1
288// W = ((IW+pad_left+pad_right-(dilation_x*(KW-1)+1))/stride_x)+1
289
291 int64_t padBeforeAttr,
292 int64_t padAfterAttr, Value kernelDim,
293 int64_t strideAttr,
294 int64_t dilationAttr,
295 OpBuilder &rewriter) {
296 ImplicitLocOpBuilder builder(loc, rewriter);
297 auto one = arith::ConstantOp::create(rewriter, loc,
298 IntegerAttr::get(inputDim.getType(), 1));
299 Value padBefore = reifyConstantDim(padBeforeAttr, builder);
300 Value paddedBefore = arith::AddIOp::create(builder, inputDim, padBefore);
301 Value padAfter = reifyConstantDim(padAfterAttr, builder);
302 Value paddedAfter = arith::AddIOp::create(builder, paddedBefore, padAfter);
303
304 Value subOne = arith::SubIOp::create(builder, kernelDim, one);
305 Value dilation = reifyConstantDim(dilationAttr, builder);
306 Value dilated = arith::MulIOp::create(builder, dilation, subOne);
307 Value addOne = arith::AddIOp::create(builder, dilated, one);
308
309 Value subtract = arith::SubIOp::create(builder, paddedAfter, addOne);
310 Value stride = reifyConstantDim(strideAttr, builder);
311 Value divide = arith::DivUIOp::create(builder, subtract, stride);
312 return arith::AddIOp::create(builder, divide, one);
313}
314
315// Creates a vector of the dynamic output dims for Conv2D and Depthwise_Conv2D
317 Location loc, Value input, Value weight, ShapedType resultTy,
318 ArrayRef<int64_t> padAttr, ArrayRef<int64_t> strideAttr,
319 ArrayRef<int64_t> dilationAttr, ArrayRef<int64_t> inputSizeDims,
320 ArrayRef<int64_t> kernelSizeDims, OpBuilder &rewriter) {
321 ShapedType inputTy = cast<ShapedType>(input.getType());
322 int64_t inputRank = inputTy.getRank();
323
324 SmallVector<Value> dynDims;
325 dynDims.resize(resultTy.getRank());
326
327 for (uint32_t i = 0, s = inputSizeDims.size(); i < s; ++i) {
328 int64_t inputDim = inputSizeDims[i];
329 int64_t kernelDim = kernelSizeDims[i];
330 if (resultTy.isDynamicDim(inputDim)) {
331 auto padTop = padAttr[i * 2];
332 auto padBottom = padAttr[i * 2 + 1];
333 auto stride = strideAttr[i];
334 auto dilation = dilationAttr[i];
335 Value initDynDim = tensor::DimOp::create(rewriter, loc, input, inputDim);
336 Value kernelDynDim =
337 tensor::DimOp::create(rewriter, loc, weight, kernelDim);
338 // H = F(IH, pad_top, pad_bottom, dilation_y, KH, stride_y)
339 dynDims[inputDim] =
340 getConvOrPoolOutputDim(loc, initDynDim, padTop, padBottom,
341 kernelDynDim, stride, dilation, rewriter);
342 }
343 }
344
345 // Get the batch/channels dimensions.
346 for (int i = 0; i < inputRank; i++) {
347 if (resultTy.isDynamicDim(i) && !dynDims[i])
348 dynDims[i] = tensor::DimOp::create(rewriter, loc, input, i);
349 }
350
351 SmallVector<Value> filteredDims = condenseValues(dynDims);
352 return filteredDims;
353}
354
355// Creates a map to collapse the last dimension of the Depthwise convolution op
356// due to a shape mismatch
358 int64_t outputRank, SmallVector<ReassociationExprs, 4> &reassociationMap,
359 OpBuilder &rewriter) {
360 reassociationMap.resize(outputRank);
361 for (int i = 0; i < outputRank; i++) {
362 reassociationMap[i].push_back(rewriter.getAffineDimExpr(i));
363 }
364 reassociationMap[outputRank - 1].push_back(
365 rewriter.getAffineDimExpr(outputRank));
366}
367
368namespace {
369
370template <typename TosaConvOp, typename LinalgConvOp, typename LinalgConvQOp>
371class ConvConverter : public OpConversionPattern<TosaConvOp> {
372public:
373 using OpConversionPattern<TosaConvOp>::OpConversionPattern;
374 LogicalResult
375 matchAndRewrite(TosaConvOp op, typename TosaConvOp::Adaptor adaptor,
376 ConversionPatternRewriter &rewriter) const final {
377 Location loc = op->getLoc();
378 Value input = op->getOperand(0);
379 Value weight = op->getOperand(1);
380 Value bias = op->getOperand(2);
381
382 ShapedType inputTy = cast<ShapedType>(input.getType());
383 ShapedType weightTy = cast<ShapedType>(weight.getType());
384 ShapedType biasTy = cast<ShapedType>(bias.getType());
385 ShapedType resultTy = cast<ShapedType>(op->getResult(0).getType());
386
387 Type inputETy = inputTy.getElementType();
388
389 DenseI64ArrayAttr padAttr = op.getPadAttr();
390 DenseI64ArrayAttr strideTosaAttr = op.getStrideAttr();
391 DenseI64ArrayAttr dilationTosaAttr = op.getDilationAttr();
392
393 Type accETy = op.getAccType();
394 Type accTy = RankedTensorType::get(resultTy.getShape(), accETy);
395
396 // Get and verify zero points.
397 FailureOr<int64_t> maybeIZp = op.getInputZeroPoint();
398 if (failed(maybeIZp))
399 return rewriter.notifyMatchFailure(
400 op, "input zero point cannot be statically determined");
401
402 FailureOr<int64_t> maybeWZp = op.getWeightZeroPoint();
403 if (failed(maybeWZp))
404 return rewriter.notifyMatchFailure(
405 op, "weight zero point cannot be statically determined");
406
407 const int64_t inputZpVal = *maybeIZp;
408 const int64_t weightZpVal = *maybeWZp;
409
410 if (op.verifyInputZeroPoint(inputZpVal).failed())
411 return rewriter.notifyMatchFailure(
412 op, "input zero point must be zero for non-int8 integer types");
413
414 if (op.verifyWeightZeroPoint(weightZpVal).failed())
415 return rewriter.notifyMatchFailure(
416 op, "weight zero point must be zero for non-int8 integer types");
417
418 bool hasZp = (inputZpVal != 0) || (weightZpVal != 0);
419
420 if (!weightTy.hasStaticShape() || !biasTy.hasStaticShape())
421 return rewriter.notifyMatchFailure(
422 op, "tosa.conv ops require static shapes for weight and bias");
423
424 if (inputETy.isUnsignedInteger())
425 return rewriter.notifyMatchFailure(
426 op, "tosa.conv ops does not support unsigned integer input");
427
428 llvm::SmallVector<int64_t> inputSizeDims;
429 llvm::SmallVector<int64_t> kernelSizeDims;
430 for (int i = 1; i < resultTy.getRank() - 1; i++) {
431 inputSizeDims.push_back(i);
432 kernelSizeDims.push_back(i);
433 }
434
435 SmallVector<Value> filteredDims = inferDynamicDimsForConv(
436 loc, input, weight, resultTy, padAttr.asArrayRef(),
437 strideTosaAttr.asArrayRef(), dilationTosaAttr.asArrayRef(),
438 inputSizeDims, kernelSizeDims, rewriter);
439
440 auto weightShape = weightTy.getShape();
441
442 // Apply padding as necessary.
443 TypedAttr zeroAttr = rewriter.getZeroAttr(inputETy);
444 if (hasZp) {
445 int64_t intMin =
446 APInt::getSignedMinValue(inputETy.getIntOrFloatBitWidth())
447 .getSExtValue();
448 int64_t intMax =
449 APInt::getSignedMaxValue(inputETy.getIntOrFloatBitWidth())
450 .getSExtValue();
451
452 if (inputZpVal < intMin || inputZpVal > intMax)
453 return rewriter.notifyMatchFailure(
454 op, "tosa.conv op quantization has zp outside of input range");
455
456 zeroAttr = rewriter.getIntegerAttr(inputETy, inputZpVal);
457 }
458
459 llvm::SmallVector<int64_t> pad;
460 pad.resize(2, 0);
461 llvm::append_range(pad, padAttr.asArrayRef());
462 pad.resize(pad.size() + 2, 0);
463 input = applyPad(loc, input, pad, zeroAttr, rewriter);
464
465 if (4 == inputTy.getRank()) {
466 // For 2D convolutions, we need to check if the target convolution op
467 // wants a HWCF kernel layout.
468 bool wantHwcf =
469 hasZp ? std::is_same_v<LinalgConvQOp, linalg::Conv2DNhwcHwcfQOp>
470 : std::is_same_v<LinalgConvOp, linalg::Conv2DNhwcHwcfOp>;
471 if (wantHwcf) {
472 // Transpose the kernel to match dimension ordering of the linalg
473 // convolution operation.
474 // TODO(suderman): See if this can be efficiently folded - check whether
475 // the input is used anywhere else, if not fold the constant.
476 SmallVector<int32_t> weightPerm;
477 for (int i = 1; i < resultTy.getRank(); i++)
478 weightPerm.push_back(i);
479 weightPerm.push_back(0);
480
481 SmallVector<int64_t> newWeightShape;
482 for (auto dim : weightPerm)
483 newWeightShape.push_back(weightShape[dim]);
484 auto weightPermAttr = rewriter.getDenseI32ArrayAttr(weightPerm);
485 Type newWeightTy =
486 RankedTensorType::get(newWeightShape, weightTy.getElementType());
487 weight = tosa::TransposeOp::create(rewriter, loc, newWeightTy, weight,
488 weightPermAttr);
489 }
490 }
491
492 // For Conv3D transpose the kernel to match dimension ordering of the linalg
493 // convolution operation. Conv2D has a 1-1 mapping in linalg so better to
494 // map directly and then transpose later if desired.
495 if (5 == inputTy.getRank()) {
496 // TODO(suderman): See if this can be efficiently folded - check whether
497 // the input is used anywhere else, if not fold the constant.
498 SmallVector<int32_t> weightPerm;
499 for (int i = 1; i < resultTy.getRank(); i++)
500 weightPerm.push_back(i);
501 weightPerm.push_back(0);
502
503 SmallVector<int64_t> newWeightShape;
504 for (auto dim : weightPerm)
505 newWeightShape.push_back(weightShape[dim]);
506 auto weightPermAttr = rewriter.getDenseI32ArrayAttr(weightPerm);
507 Type newWeightTy =
508 RankedTensorType::get(newWeightShape, weightTy.getElementType());
509 weight = tosa::TransposeOp::create(rewriter, loc, newWeightTy, weight,
510 weightPermAttr);
511 }
512
513 // Extract the attributes for convolution.
514 ArrayRef<int64_t> stride = strideTosaAttr;
515 ArrayRef<int64_t> dilation = dilationTosaAttr;
516
517 // Create the convolution op.
518 auto strideAttr = rewriter.getI64TensorAttr(stride);
519 auto dilationAttr = rewriter.getI64TensorAttr(dilation);
520
521 Value biasEmptyTensor = tensor::EmptyOp::create(
522 rewriter, loc, resultTy.getShape(), accETy, filteredDims);
523
524 Value broadcastBias =
525 linalgBroadcastAndMaybeExt(rewriter, loc, bias, biasEmptyTensor);
526
527 if (hasZp) {
528 auto iZp = rewriter.getI32IntegerAttr(inputZpVal);
529 auto kZp = rewriter.getI32IntegerAttr(weightZpVal);
530
531 auto iZpVal = arith::ConstantOp::create(rewriter, loc, iZp);
532 auto kZpVal = arith::ConstantOp::create(rewriter, loc, kZp);
533
534 Value conv = LinalgConvQOp::create(
535 rewriter, loc, resultTy,
536 ValueRange{input, weight, iZpVal, kZpVal},
537 ValueRange{broadcastBias}, strideAttr, dilationAttr)
538 ->getResult(0);
539
540 rewriter.replaceOp(op, conv);
541 return success();
542 }
543
544 Value conv = LinalgConvOp::create(
545 rewriter, loc, accTy, ValueRange{input, weight},
546 ValueRange{broadcastBias}, strideAttr, dilationAttr)
547 ->getResult(0);
548
549 // We may need to truncate back to the result type if the accumulator was
550 // wider than the result.
551 if (resultTy != accTy)
552 conv = tosa::CastOp::create(rewriter, loc, resultTy, conv);
553
554 rewriter.replaceOp(op, conv);
555 return success();
556 }
557};
558
559class DepthwiseConvConverter
560 : public OpConversionPattern<tosa::DepthwiseConv2DOp> {
561public:
562 using OpConversionPattern<tosa::DepthwiseConv2DOp>::OpConversionPattern;
563 LogicalResult
564 matchAndRewrite(tosa::DepthwiseConv2DOp op, OpAdaptor adaptor,
565 ConversionPatternRewriter &rewriter) const final {
566 Location loc = op->getLoc();
567 Value input = op->getOperand(0);
568 Value weight = op->getOperand(1);
569 Value bias = op->getOperand(2);
570
571 ShapedType inputTy = cast<ShapedType>(input.getType());
572 ShapedType weightTy = cast<ShapedType>(weight.getType());
573 ShapedType biasTy = cast<ShapedType>(bias.getType());
574 ShapedType resultTy = cast<ShapedType>(op->getResult(0).getType());
575 int64_t resultRank = resultTy.getRank();
576
577 Type inputETy = inputTy.getElementType();
578 Type resultETy = resultTy.getElementType();
579
580 auto padAttr = op.getPadAttr();
581 auto strideTosaAttr = op.getStrideAttr();
582 auto dilationTosaAttr = op.getDilationAttr();
583
584 Type accETy = op.getAccType();
585
586 if (!weightTy.hasStaticShape() || !biasTy.hasStaticShape())
587 return rewriter.notifyMatchFailure(
588 op, "tosa.depthwise_conv ops require static shapes");
589
590 // Compute output dynamic dims
591 SmallVector<Value> filteredDims = inferDynamicDimsForConv(
592 loc, input, weight, resultTy, padAttr.asArrayRef(),
593 strideTosaAttr.asArrayRef(), dilationTosaAttr.asArrayRef(),
594 /*inputSizeDims=*/{1, 2},
595 /*kernelSizeDims=*/{0, 1}, rewriter);
596
597 // Get and verify zero points.
598
599 FailureOr<int64_t> maybeIZp = op.getInputZeroPoint();
600 FailureOr<int64_t> maybeWZp = op.getWeightZeroPoint();
601 if (failed(maybeIZp))
602 return rewriter.notifyMatchFailure(
603 op, "input zero point cannot be statically determined");
604 if (failed(maybeWZp))
605 return rewriter.notifyMatchFailure(
606 op, "weight zero point cannot be statically determined");
607
608 const int64_t inputZpVal = *maybeIZp;
609 const int64_t weightZpVal = *maybeWZp;
610
611 if (op.verifyInputZeroPoint(inputZpVal).failed())
612 return rewriter.notifyMatchFailure(
613 op, "input zero point must be zero for non-int8 integer types");
614
615 if (op.verifyWeightZeroPoint(weightZpVal).failed())
616 return rewriter.notifyMatchFailure(
617 op, "weight zero point must be zero for non-int8 integer types");
618
619 bool hasNullZps = (inputZpVal == 0) && (weightZpVal == 0);
620 auto weightShape = weightTy.getShape();
621 auto resultShape = resultTy.getShape();
622
623 // Apply padding as necessary.
624 TypedAttr zeroAttr = rewriter.getZeroAttr(inputETy);
625 if (!hasNullZps) {
626 int64_t intMin =
627 APInt::getSignedMinValue(inputETy.getIntOrFloatBitWidth())
628 .getSExtValue();
629 int64_t intMax =
630 APInt::getSignedMaxValue(inputETy.getIntOrFloatBitWidth())
631 .getSExtValue();
632
633 if (inputZpVal < intMin || inputZpVal > intMax)
634 return rewriter.notifyMatchFailure(
635 op, "tosa.depthwise_conv op quantization has zp outside of input "
636 "range");
637
638 zeroAttr = rewriter.getIntegerAttr(inputETy, inputZpVal);
639 }
640
641 llvm::SmallVector<int64_t> pad;
642 pad.resize(2, 0);
643 llvm::append_range(pad, padAttr.asArrayRef());
644 pad.resize(pad.size() + 2, 0);
645
646 input = applyPad(loc, input, pad, zeroAttr, rewriter);
647
648 // Extract the attributes for convolution.
649 ArrayRef<int64_t> stride = strideTosaAttr;
650 ArrayRef<int64_t> dilation = dilationTosaAttr;
651
652 // Create the convolution op.
653 auto strideAttr = rewriter.getI64TensorAttr(stride);
654 auto dilationAttr = rewriter.getI64TensorAttr(dilation);
655 ShapedType linalgConvTy =
656 RankedTensorType::get({resultShape[0], resultShape[1], resultShape[2],
657 weightShape[2], weightShape[3]},
658 accETy);
659
660 auto resultZeroAttr = rewriter.getZeroAttr(accETy);
661 Value emptyTensor = tensor::EmptyOp::create(
662 rewriter, loc, linalgConvTy.getShape(), accETy, filteredDims);
663 Value zero = arith::ConstantOp::create(rewriter, loc, resultZeroAttr);
664 Value zeroTensor = linalg::FillOp::create(rewriter, loc, ValueRange{zero},
665 ValueRange{emptyTensor})
666 .result();
667
668 Value biasEmptyTensor = tensor::EmptyOp::create(
669 rewriter, loc, resultTy.getShape(), resultETy, filteredDims);
670
671 // Broadcast the initial value to the output tensor before convolving.
672 SmallVector<AffineMap, 4> indexingMaps;
673 indexingMaps.push_back(getBroadcastingMap(rewriter, bias, biasEmptyTensor));
674 indexingMaps.push_back(rewriter.getMultiDimIdentityMap(resultRank));
675 indexingMaps.push_back(rewriter.getMultiDimIdentityMap(resultRank));
676
677 if (hasNullZps) {
678 Value conv = linalg::DepthwiseConv2DNhwcHwcmOp::create(
679 rewriter, loc, linalgConvTy, ValueRange{input, weight},
680 ValueRange{zeroTensor}, strideAttr, dilationAttr)
681 .getResult(0);
682
683 // We may need to truncate back to the result type if the accumulator was
684 // wider than the result.
685 if (accETy != resultETy)
686 conv = tosa::CastOp::create(
687 rewriter, loc,
688 RankedTensorType::get(cast<ShapedType>(conv.getType()).getShape(),
689 resultETy),
690 conv);
691
692 SmallVector<ReassociationExprs, 4> reassociationMap;
693 createDepthwiseConvCollapseMap(resultRank, reassociationMap, rewriter);
694 Value convReshape = tensor::CollapseShapeOp::create(
695 rewriter, loc, resultTy, conv, reassociationMap);
696
697 Value result =
698 linalg::GenericOp::create(
699 rewriter, loc, resultTy, ValueRange({bias, convReshape}),
700 biasEmptyTensor, indexingMaps, getNParallelLoopsAttrs(resultRank),
701 [&](OpBuilder &nestedBuilder, Location nestedLoc,
702 ValueRange args) {
703 Value added;
704 if (llvm::isa<FloatType>(inputETy))
705 added = arith::AddFOp::create(nestedBuilder, loc, args[0],
706 args[1]);
707 else
708 added = arith::AddIOp::create(nestedBuilder, loc, args[0],
709 args[1]);
710 linalg::YieldOp::create(nestedBuilder, nestedLoc, added);
711 })
712 .getResult(0);
713 rewriter.replaceOp(op, result);
714 } else {
715 IntegerAttr iZp = rewriter.getI32IntegerAttr(inputZpVal);
716 IntegerAttr wZp = rewriter.getI32IntegerAttr(weightZpVal);
717 auto iZpVal = arith::ConstantOp::create(rewriter, loc, iZp);
718 auto kZpVal = arith::ConstantOp::create(rewriter, loc, wZp);
719 Value conv = linalg::DepthwiseConv2DNhwcHwcmQOp::create(
720 rewriter, loc, linalgConvTy,
721 ValueRange{input, weight, iZpVal, kZpVal},
722 ValueRange{zeroTensor}, strideAttr, dilationAttr)
723 .getResult(0);
724 SmallVector<ReassociationExprs, 4> reassociationMap;
725 createDepthwiseConvCollapseMap(resultRank, reassociationMap, rewriter);
726 Value convReshape = tensor::CollapseShapeOp::create(
727 rewriter, loc, resultTy, conv, reassociationMap);
729 rewriter, loc, bias, convReshape, biasEmptyTensor, indexingMaps);
730 rewriter.replaceOp(op, result);
731 }
732 return success();
733 }
734};
735
736// Lower TOSA elementwise ops that have a direct linalg.elementwise named op
737// counterpart. Unlike linalg.generic, linalg.elementwise dispatches on a single
738// function kind and infers the body, so the conversion is a handful of checks
739// (kind, ranks, static shapes) followed by construction of the indexing maps
740// and the named op.
741template <typename TosaOp>
742class ElementwiseConverter : public OpConversionPattern<TosaOp> {
743public:
744 using OpConversionPattern<TosaOp>::OpConversionPattern;
745 using typename OpConversionPattern<TosaOp>::OpAdaptor;
746
747 LogicalResult
748 matchAndRewrite(TosaOp op, OpAdaptor adaptor,
749 ConversionPatternRewriter &rewriter) const final {
750 std::optional<linalg::ElementwiseKind> kind =
752 if (!kind)
753 return rewriter.notifyMatchFailure(
754 op, "op has no linalg.elementwise counterpart");
755
756 Location loc = op.getLoc();
757 auto resultTy = cast<RankedTensorType>(op.getType());
758 const int64_t rank = resultTy.getRank();
759
760 const unsigned arity =
761 llvm::to_underlying(linalg::getArityGroupAndKind(*kind).arityGroup);
762
763 // Trailing metadata operands (e.g. MulOp's shift, NegateOp's zero points)
764 // are not forwarded to linalg.elementwise.
765 ValueRange inputs = adaptor.getOperands().take_front(arity);
766
767 // Build one indexing map per input, broadcasting static size-1 dimensions,
768 // followed by the identity map for the output.
769 SmallVector<AffineMap> indexingMaps;
770 for (Value operand : inputs) {
771 ArrayRef<int64_t> shape = cast<ShapedType>(operand.getType()).getShape();
772 SmallVector<AffineExpr> exprs;
773 for (const auto &dim : llvm::enumerate(shape)) {
774 bool requiresBroadcast =
775 dim.value() == 1 && resultTy.getDimSize(dim.index()) != 1;
776 exprs.push_back(requiresBroadcast
777 ? rewriter.getAffineConstantExpr(0)
778 : rewriter.getAffineDimExpr(dim.index()));
779 }
780 indexingMaps.push_back(AffineMap::get(rank, /*symbolCount=*/0, exprs,
781 rewriter.getContext()));
782 }
783 indexingMaps.push_back(rewriter.getMultiDimIdentityMap(rank));
784
785 // Shapes are guaranteed static by isConvertibleToLinalgElementwise.
786 Value emptyTensor = tensor::EmptyOp::create(
787 rewriter, loc, resultTy.getShape(), resultTy.getElementType());
788
789 rewriter.replaceOpWithNewOp<linalg::ElementwiseOp>(
790 op, inputs, ValueRange{emptyTensor}, *kind,
791 rewriter.getAffineMapArrayAttr(indexingMaps));
792 return success();
793 }
794};
795
797getMatMulBatchCollapseReassociation(int64_t rank) {
798 SmallVector<ReassociationIndices> reassociation(3);
799 for (int64_t dim = 0; dim < rank - 2; ++dim)
800 reassociation[0].push_back(dim);
801 reassociation[1].push_back(rank - 2);
802 reassociation[2].push_back(rank - 1);
803 return reassociation;
804}
805
806class MatMulConverter : public OpConversionPattern<tosa::MatMulOp> {
807public:
808 using OpConversionPattern<tosa::MatMulOp>::OpConversionPattern;
809 LogicalResult
810 matchAndRewrite(tosa::MatMulOp op, OpAdaptor adaptor,
811 ConversionPatternRewriter &rewriter) const final {
812 Location loc = op.getLoc();
813 auto aType = dyn_cast<RankedTensorType>(adaptor.getA().getType());
814 auto bType = dyn_cast<RankedTensorType>(adaptor.getB().getType());
815 auto outputType = dyn_cast<RankedTensorType>(op.getType());
816 if (!aType || !bType || !outputType)
817 return rewriter.notifyMatchFailure(op, "only supports ranked tensors");
818
819 FailureOr<int64_t> maybeAZp = op.getAZeroPoint();
820 FailureOr<int64_t> maybeBZp = op.getBZeroPoint();
821 if (failed(maybeAZp))
822 return rewriter.notifyMatchFailure(
823 op, "input A zero point cannot be statically determined");
824 if (failed(maybeBZp))
825 return rewriter.notifyMatchFailure(
826 op, "input B zero point cannot be statically determined");
827
828 const int64_t aZpVal = *maybeAZp;
829 const int64_t bZpVal = *maybeBZp;
830
831 if (op.verifyAZeroPoint(aZpVal).failed())
832 return rewriter.notifyMatchFailure(
833 op, "input A zero point must be zero for non-int8 integer types");
834
835 if (op.verifyBZeroPoint(bZpVal).failed())
836 return rewriter.notifyMatchFailure(
837 op, "input B zero point must be zero for non-int8 integer types");
838
839 const int64_t outputRank = outputType.getRank();
840 auto [a, b, zeroTensor] =
841 prepareMatMulOperands(rewriter, loc, adaptor.getA(), adaptor.getB(),
842 outputType, /*transposeB=*/false);
843
844 Value aZp, bZp;
845 const bool hasNonzeroZeroPoint = aZpVal != 0 || bZpVal != 0;
846 if (hasNonzeroZeroPoint) {
847 aZp = arith::ConstantOp::create(rewriter, loc,
848 rewriter.getI32IntegerAttr(aZpVal));
849 bZp = arith::ConstantOp::create(rewriter, loc,
850 rewriter.getI32IntegerAttr(bZpVal));
851 }
852
853 if (outputRank == 2) {
854 if (!hasNonzeroZeroPoint) {
855 rewriter.replaceOpWithNewOp<linalg::MatmulOp>(op, TypeRange{outputType},
856 ValueRange{a, b},
857 ValueRange{zeroTensor});
858 } else {
859 rewriter.replaceOpWithNewOp<linalg::QuantizedMatmulOp>(
860 op, TypeRange{outputType}, ValueRange{a, b, aZp, bZp},
861 ValueRange{zeroTensor});
862 }
863 return success();
864 }
865
866 if (outputRank == 3) {
867 if (!hasNonzeroZeroPoint) {
868 rewriter.replaceOpWithNewOp<linalg::BatchMatmulOp>(
869 op, TypeRange{outputType}, ValueRange{a, b},
870 ValueRange{zeroTensor});
871 } else {
872 rewriter.replaceOpWithNewOp<linalg::QuantizedBatchMatmulOp>(
873 op, TypeRange{outputType}, ValueRange{a, b, aZp, bZp},
874 ValueRange{zeroTensor});
875 }
876 return success();
877 }
878
879 SmallVector<ReassociationIndices> reassociation =
880 getMatMulBatchCollapseReassociation(outputRank);
881 auto collapse = [&](Value value) {
882 auto type = cast<RankedTensorType>(value.getType());
883 auto collapsedType =
884 tensor::CollapseShapeOp::inferCollapsedType(type, reassociation);
885 return tensor::CollapseShapeOp::create(rewriter, loc, collapsedType,
886 value, reassociation);
887 };
888
889 Value collapsedA = collapse(a);
890 Value collapsedB = collapse(b);
891 Value collapsedInit = collapse(zeroTensor);
892 auto collapsedOutputType = cast<RankedTensorType>(collapsedInit.getType());
893 Value collapsedResult;
894
895 if (!hasNonzeroZeroPoint) {
896 collapsedResult =
897 linalg::BatchMatmulOp::create(
898 rewriter, loc, TypeRange{collapsedOutputType},
899 ValueRange{collapsedA, collapsedB}, ValueRange{collapsedInit})
900 .getResult(0);
901 } else {
902 collapsedResult = linalg::QuantizedBatchMatmulOp::create(
903 rewriter, loc, TypeRange{collapsedOutputType},
904 ValueRange{collapsedA, collapsedB, aZp, bZp},
905 ValueRange{collapsedInit})
906 .getResult(0);
907 }
908
909 Value result = tensor::ExpandShapeOp::create(
910 rewriter, loc, outputType, collapsedResult, reassociation,
911 tensor::getMixedSizes(rewriter, loc, zeroTensor));
912 rewriter.replaceOp(op, result);
913 return success();
914 }
915};
916
917class MatMulTConverter : public OpConversionPattern<tosa::MatMulTOp> {
918public:
919 using OpConversionPattern<tosa::MatMulTOp>::OpConversionPattern;
920 LogicalResult
921 matchAndRewrite(tosa::MatMulTOp op, OpAdaptor adaptor,
922 ConversionPatternRewriter &rewriter) const final {
923 Location loc = op.getLoc();
924 auto aType = dyn_cast<RankedTensorType>(adaptor.getA().getType());
925 auto bType = dyn_cast<RankedTensorType>(adaptor.getB().getType());
926 auto outputType = dyn_cast<RankedTensorType>(op.getType());
927 if (!aType || !bType || !outputType)
928 return rewriter.notifyMatchFailure(op, "only supports ranked tensors");
929
930 FailureOr<int64_t> maybeAZp = op.getAZeroPoint();
931 FailureOr<int64_t> maybeBZp = op.getBZeroPoint();
932 if (failed(maybeAZp))
933 return rewriter.notifyMatchFailure(
934 op, "input A zero point cannot be statically determined");
935 if (failed(maybeBZp))
936 return rewriter.notifyMatchFailure(
937 op, "input B zero point cannot be statically determined");
938
939 const int64_t aZpVal = *maybeAZp;
940 const int64_t bZpVal = *maybeBZp;
941
942 if (aZpVal != 0 || bZpVal != 0)
943 return rewriter.notifyMatchFailure(
944 op, "nonzero zero points require the generic MATMUL_T lowering");
945
946 const int64_t outputRank = outputType.getRank();
947 auto [a, b, zeroTensor] =
948 prepareMatMulOperands(rewriter, loc, adaptor.getA(), adaptor.getB(),
949 outputType, /*transposeB=*/true);
950
951 if (outputRank == 2) {
952 rewriter.replaceOpWithNewOp<linalg::MatmulTransposeBOp>(
953 op, TypeRange{outputType}, ValueRange{a, b}, ValueRange{zeroTensor});
954 return success();
955 }
956
957 if (outputRank == 3) {
958 rewriter.replaceOpWithNewOp<linalg::BatchMatmulTransposeBOp>(
959 op, TypeRange{outputType}, ValueRange{a, b}, ValueRange{zeroTensor});
960 return success();
961 }
962
963 SmallVector<ReassociationIndices> reassociation =
964 getMatMulBatchCollapseReassociation(outputRank);
965 auto collapse = [&](Value value) {
966 auto type = cast<RankedTensorType>(value.getType());
967 auto collapsedType =
968 tensor::CollapseShapeOp::inferCollapsedType(type, reassociation);
969 return tensor::CollapseShapeOp::create(rewriter, loc, collapsedType,
970 value, reassociation);
971 };
972 Value collapsedA = collapse(a);
973 Value collapsedB = collapse(b);
974 Value collapsedInit = collapse(zeroTensor);
975 auto collapsedOutputType = cast<RankedTensorType>(collapsedInit.getType());
976 Value collapsedResult =
978 rewriter, loc, TypeRange{collapsedOutputType},
979 ValueRange{collapsedA, collapsedB}, ValueRange{collapsedInit})
980 .getResult(0);
981 Value result = tensor::ExpandShapeOp::create(
982 rewriter, loc, outputType, collapsedResult, reassociation,
983 tensor::getMixedSizes(rewriter, loc, zeroTensor));
984 rewriter.replaceOp(op, result);
985 return success();
986 }
987};
988
989class MaxPool2dConverter : public OpConversionPattern<tosa::MaxPool2dOp> {
990public:
991 MaxPool2dConverter(const TypeConverter &typeConverter, MLIRContext *context,
992 bool allowNonFinites)
993 : OpConversionPattern(typeConverter, context),
994 allowNonFinites(allowNonFinites) {}
995
996 // Compute the dynamic output sizes of the maxpool operation.
997 static SmallVector<Value>
998 computeDynamicOutputSizes(tosa::MaxPool2dOp op, OpAdaptor adaptor,
999 ConversionPatternRewriter &rewriter) {
1000 TensorType resultTy = op.getType();
1001 Location loc = op.getLoc();
1002
1003 Value input = adaptor.getInput();
1004 ArrayRef<int64_t> kernel = op.getKernel();
1005 ArrayRef<int64_t> pad = op.getPad();
1006 ArrayRef<int64_t> stride = op.getStride();
1007
1008 SmallVector<Value> dynamicDims;
1009
1010 // Batch dimension
1011 if (resultTy.isDynamicDim(0))
1012 dynamicDims.push_back(tensor::DimOp::create(rewriter, loc, input, 0));
1013
1014 // Height/width dimensions
1015 for (int64_t dim : {1, 2}) {
1016 if (!resultTy.isDynamicDim(dim))
1017 continue;
1018
1019 // Index into the attribute arrays
1020 int64_t index = dim - 1;
1021
1022 // Input height/width
1023 Value ihw = tensor::DimOp::create(rewriter, loc, input, dim);
1024
1025 // Kernel height/width
1026 Value khw = arith::ConstantIndexOp::create(rewriter, loc, kernel[index]);
1027
1028 // Output height/width
1029 Value ohw = getConvOrPoolOutputDim(loc, ihw, pad[index * 2],
1030 pad[index * 2 + 1], khw, stride[index],
1031 /*dilationAttr=*/1, rewriter);
1032 dynamicDims.push_back(ohw);
1033 }
1034
1035 // Channel dimension
1036 if (resultTy.isDynamicDim(3))
1037 dynamicDims.push_back(tensor::DimOp::create(rewriter, loc, input, 3));
1038
1039 return dynamicDims;
1040 }
1041
1042 LogicalResult
1043 matchAndRewrite(tosa::MaxPool2dOp op, OpAdaptor adaptor,
1044 ConversionPatternRewriter &rewriter) const final {
1045 Location loc = op.getLoc();
1046 Value input = adaptor.getInput();
1047 ShapedType inputTy = cast<ShapedType>(input.getType());
1048
1049 bool isUnsigned = op.getType().getElementType().isUnsignedInteger();
1050 ShapedType resultTy =
1051 getTypeConverter()->convertType<ShapedType>(op.getType());
1052 if (!resultTy)
1053 return rewriter.notifyMatchFailure(op, "failed to convert type");
1054 Type resultETy = inputTy.getElementType();
1055 NanPropagationMode nanMode = op.getNanMode();
1056
1057 SmallVector<Value> dynamicDims =
1058 computeDynamicOutputSizes(op, adaptor, rewriter);
1059
1060 // Determine what the initial value needs to be for the max pool op.
1061 TypedAttr initialAttr;
1062 if (resultETy.isF32() || resultETy.isBF16() || resultETy.isF16()) {
1063 const llvm::fltSemantics &semantics =
1064 cast<FloatType>(resultETy).getFloatSemantics();
1065 if (llvm::APFloat::semanticsHasNaN(semantics))
1066 initialAttr = rewriter.getFloatAttr(
1067 resultETy,
1068 nanMode == NanPropagationMode::IGNORE
1069 ? APFloat::getNaN(semantics)
1070 : getFloatMinMaxIdentity(semantics,
1071 /*negative=*/true, allowNonFinites));
1072 }
1073
1074 else if (isUnsigned)
1075 initialAttr = rewriter.getIntegerAttr(
1076 resultETy, APInt::getZero(resultETy.getIntOrFloatBitWidth()));
1077 else if (isa<IntegerType>(resultETy))
1078 initialAttr = rewriter.getIntegerAttr(
1079 resultETy,
1080 APInt::getSignedMinValue(resultETy.getIntOrFloatBitWidth()));
1081
1082 if (!initialAttr)
1083 return rewriter.notifyMatchFailure(
1084 op, "Unsupported initial value for tosa.maxpool_2d op");
1085
1086 // Apply padding as necessary.
1087 llvm::SmallVector<int64_t> pad;
1088 pad.resize(2, 0);
1089 llvm::append_range(pad, op.getPad());
1090 pad.resize(pad.size() + 2, 0);
1091
1092 // The initial value doubles as the padding value. This is safe for every
1093 // seed above because the verifier rejects a pad as wide as the kernel on
1094 // any side, so no window consists purely of padding.
1095 Value paddedInput = applyPad(loc, input, pad, initialAttr, rewriter);
1096
1097 Value initialValue = arith::ConstantOp::create(rewriter, loc, initialAttr);
1098
1099 ArrayRef<int64_t> kernel = op.getKernel();
1100 ArrayRef<int64_t> stride = op.getStride();
1101
1102 Attribute strideAttr = rewriter.getI64VectorAttr(stride);
1103 Attribute dilationAttr = rewriter.getI64VectorAttr({1, 1});
1104
1105 // Create the linalg op that performs pooling.
1106 Value emptyTensor =
1107 tensor::EmptyOp::create(rewriter, loc, resultTy.getShape(),
1108 resultTy.getElementType(), dynamicDims);
1109
1110 Value filledEmptyTensor =
1111 linalg::FillOp::create(rewriter, loc, initialValue, emptyTensor)
1112 .result();
1113
1114 Value fakeWindowDims =
1115 tensor::EmptyOp::create(rewriter, loc, kernel, resultETy);
1116
1117 if (isUnsigned) {
1118 rewriter.replaceOpWithNewOp<linalg::PoolingNhwcMaxUnsignedOp>(
1119 op, ArrayRef<Type>{resultTy}, ValueRange{paddedInput, fakeWindowDims},
1120 filledEmptyTensor, strideAttr, dilationAttr);
1121 return llvm::success();
1122 }
1123
1124 auto resultOp = linalg::PoolingNhwcMaxOp::create(
1125 rewriter, op->getLoc(), ArrayRef<Type>{resultTy},
1126 ValueRange{paddedInput, fakeWindowDims}, filledEmptyTensor, strideAttr,
1127 dilationAttr);
1128
1129 rewriter.replaceOp(op, resultOp);
1130
1131 // NaN propagation has no meaning for non floating point types.
1132 if (!isa<FloatType>(getElementTypeOrSelf(inputTy)))
1133 return success();
1134
1135 // "PROPAGATE" mode matches the behaviour of the LinAlg named op, so no
1136 // compare and select materialization is required.
1137 //
1138 // In the case of "IGNORE" we need to insert a compare and select. Since
1139 // we've already produced a named op we will just take its body and modify
1140 // it to include the appropriate checks. A NaN accumulator represents a
1141 // window with no finite values yet. The first finite input replaces it;
1142 // input NaNs are ignored. An all-NaN window therefore remains NaN.
1143 if (nanMode == NanPropagationMode::IGNORE) {
1144 auto genericOp = linalg::GenericOp::create(
1145 rewriter, loc, resultOp.getType(0), resultOp.getInputs(),
1146 resultOp.getOutputs(), resultOp.getIndexingMapsArray(),
1147 resultOp.getIteratorTypesArray(),
1148 [&](OpBuilder &opBuilder, Location loc, ValueRange blockArgs) {
1149 IRMapping map;
1150 auto oldBlock = resultOp.getRegion().begin();
1151 auto oldArgs = oldBlock->getArguments();
1152 auto &oldMaxOp = *resultOp.getBlock()->begin();
1153 map.map(oldArgs, blockArgs);
1154 auto *newOp = opBuilder.clone(oldMaxOp, map);
1155 Value inputIsNaN =
1156 arith::CmpFOp::create(opBuilder, loc, arith::CmpFPredicate::UNO,
1157 blockArgs.front(), blockArgs.front());
1158 Value accumulatorIsNaN =
1159 arith::CmpFOp::create(opBuilder, loc, arith::CmpFPredicate::UNO,
1160 blockArgs.back(), blockArgs.back());
1161 Value finiteMaximum =
1162 arith::SelectOp::create(opBuilder, loc, accumulatorIsNaN,
1163 blockArgs.front(), newOp->getResult(0));
1164 Value result = arith::SelectOp::create(
1165 opBuilder, loc, inputIsNaN, blockArgs.back(), finiteMaximum);
1166 linalg::YieldOp::create(opBuilder, loc, result);
1167 });
1168 rewriter.replaceOp(resultOp, genericOp);
1169 }
1170
1171 return success();
1172 }
1173
1174private:
1175 bool allowNonFinites;
1176};
1177
1178class AvgPool2dConverter : public OpRewritePattern<tosa::AvgPool2dOp> {
1179public:
1180 using OpRewritePattern<tosa::AvgPool2dOp>::OpRewritePattern;
1181
1182 LogicalResult matchAndRewrite(tosa::AvgPool2dOp op,
1183 PatternRewriter &rewriter) const final {
1184 Location loc = op.getLoc();
1185 Value input = op.getInput();
1186 ShapedType inputTy = cast<ShapedType>(input.getType());
1187 Type inElementTy = inputTy.getElementType();
1188
1189 ShapedType resultTy = cast<ShapedType>(op.getType());
1190 Type resultETy = cast<ShapedType>(op.getType()).getElementType();
1191
1192 Type accETy = op.getAccType();
1193 ShapedType accTy = resultTy.clone(accETy);
1194
1195 auto dynamicDimsOr =
1196 checkHasDynamicBatchDims(rewriter, op, {input, op.getOutput()});
1197 if (!dynamicDimsOr.has_value())
1198 return failure();
1199 SmallVector<Value> dynamicDims = *dynamicDimsOr;
1200
1201 FailureOr<int64_t> maybeIZp = op.getInputZeroPoint();
1202 FailureOr<int64_t> maybeOZp = op.getOutputZeroPoint();
1203 if (failed(maybeIZp))
1204 return rewriter.notifyMatchFailure(
1205 op, "input zero point could not be statically determined");
1206 if (failed(maybeOZp))
1207 return rewriter.notifyMatchFailure(
1208 op, "output zero point could not be statically determined");
1209
1210 const int64_t inputZpVal = *maybeIZp;
1211 const int64_t outputZpVal = *maybeOZp;
1212
1213 // Apply padding as necessary.
1214 llvm::SmallVector<int64_t> pad;
1215 pad.resize(2, 0);
1216 llvm::append_range(pad, op.getPad());
1217 pad.resize(pad.size() + 2, 0);
1218 TypedAttr padAttr = rewriter.getZeroAttr(inElementTy);
1219 // Unsupported element type
1220 if (!padAttr)
1221 return failure();
1222 Value paddedInput = applyPad(loc, input, pad, padAttr, rewriter);
1223
1224 auto initialAttr = rewriter.getZeroAttr(accETy);
1225 Value initialValue = arith::ConstantOp::create(rewriter, loc, initialAttr);
1226
1227 ArrayRef<int64_t> kernel = op.getKernel();
1228 ArrayRef<int64_t> stride = op.getStride();
1229
1230 Attribute strideAttr = rewriter.getI64VectorAttr(stride);
1231 Attribute dilationAttr = rewriter.getI64VectorAttr({1, 1});
1232
1233 // Create the linalg op that performs pooling.
1234 Value poolEmptyTensor = tensor::EmptyOp::create(
1235 rewriter, loc, accTy.getShape(), accETy, dynamicDims);
1236
1237 Value filledEmptyTensor =
1238 linalg::FillOp::create(rewriter, loc, ValueRange{initialValue},
1239 ValueRange{poolEmptyTensor})
1240 .result();
1241
1242 Value fakeWindowDims =
1243 tensor::EmptyOp::create(rewriter, loc, kernel, accETy);
1244
1245 // Sum across the pooled region.
1246 Value poolingOp = linalg::PoolingNhwcSumOp::create(
1247 rewriter, loc, ArrayRef<Type>{accTy},
1248 ValueRange{paddedInput, fakeWindowDims},
1249 filledEmptyTensor, strideAttr, dilationAttr)
1250 .getResult(0);
1251
1252 // Normalize the summed value by the number of elements grouped in each
1253 // pool.
1254 Value iH = tensor::DimOp::create(rewriter, loc, poolingOp, 1);
1255 Value iW = tensor::DimOp::create(rewriter, loc, poolingOp, 2);
1256
1257 auto one = arith::ConstantIndexOp::create(rewriter, loc, 1);
1258 iH = arith::SubIOp::create(rewriter, loc, iH, one);
1259 iW = arith::SubIOp::create(rewriter, loc, iW, one);
1260
1261 Value genericEmptyTensor = tensor::EmptyOp::create(
1262 rewriter, loc, resultTy.getShape(), resultETy, dynamicDims);
1263
1264 auto affineMap = rewriter.getMultiDimIdentityMap(resultTy.getRank());
1265 auto genericOp = linalg::GenericOp::create(
1266 rewriter, loc, ArrayRef<Type>({resultTy}), ValueRange{poolingOp},
1267 ValueRange{genericEmptyTensor},
1268 ArrayRef<AffineMap>({affineMap, affineMap}),
1269 getNParallelLoopsAttrs(resultTy.getRank()),
1270 [&](OpBuilder &b, Location loc, ValueRange args) {
1271 auto zero = arith::ConstantIndexOp::create(rewriter, loc, 0);
1272
1273 // Determines what the portion of valid input is covered by the
1274 // kernel.
1275 auto padFn = [&](Value valid, Value pos, int64_t pad) -> Value {
1276 if (pad == 0)
1277 return valid;
1278
1279 auto padVal = arith::ConstantIndexOp::create(rewriter, loc, pad);
1280 Value dpos = arith::SubIOp::create(rewriter, loc, pos, padVal);
1281
1282 Value offset = arith::MinSIOp::create(rewriter, loc, dpos, zero);
1283 return arith::AddIOp::create(rewriter, loc, valid, offset)
1284 ->getResult(0);
1285 };
1286
1287 auto coverageFn = [&](int64_t i, Value isize) -> Value {
1288 Value strideVal =
1289 arith::ConstantIndexOp::create(rewriter, loc, stride[i - 1]);
1290 Value val =
1291 arith::ConstantIndexOp::create(rewriter, loc, kernel[i - 1]);
1292
1293 // Find the position relative to the input tensor's ends.
1294 Value left = linalg::IndexOp::create(rewriter, loc, i);
1295 Value right = arith::SubIOp::create(rewriter, loc, isize, left);
1296 left = arith::MulIOp::create(rewriter, loc, left, strideVal);
1297 right = arith::MulIOp::create(rewriter, loc, right, strideVal);
1298
1299 // Determine how much padding was included.
1300 val = padFn(val, left, pad[i * 2]);
1301 val = padFn(val, right, pad[i * 2 + 1]);
1302 return arith::MaxSIOp::create(rewriter, loc, one, val);
1303 };
1304
1305 // Compute the indices from either end.
1306 Value kH3 = coverageFn(1, iH);
1307 Value kW3 = coverageFn(2, iW);
1308
1309 // Compute the total number of elements and normalize.
1310 auto count = arith::IndexCastOp::create(
1311 rewriter, loc, rewriter.getI32Type(),
1312 arith::MulIOp::create(rewriter, loc, kH3, kW3));
1313
1314 // Divide by the number of summed values. For floats this is just
1315 // a div however for quantized values input normalization had
1316 // to be applied.
1317 Value poolVal = args[0];
1318 if (isa<FloatType>(accETy)) {
1319 auto countF = arith::SIToFPOp::create(rewriter, loc, accETy, count);
1320 poolVal = arith::DivFOp::create(rewriter, loc, poolVal, countF)
1321 ->getResult(0);
1322 if (accETy.getIntOrFloatBitWidth() >
1323 resultETy.getIntOrFloatBitWidth())
1324 poolVal =
1325 arith::TruncFOp::create(rewriter, loc, resultETy, poolVal);
1326 } else {
1327
1328 // If we have quantization information we need to apply an offset
1329 // for the input zp value.
1330 if (inputZpVal != 0) {
1331 auto inputZp = arith::ConstantOp::create(
1332 rewriter, loc, b.getIntegerAttr(accETy, inputZpVal));
1333 Value offset =
1334 arith::MulIOp::create(rewriter, loc, accETy, count, inputZp);
1335 poolVal =
1336 arith::SubIOp::create(rewriter, loc, accETy, poolVal, offset);
1337 }
1338
1339 // Compute: k = 32 - count_leading_zeros(value - 1)
1340 Value one32 = arith::ConstantOp::create(
1341 rewriter, loc, rewriter.getI32IntegerAttr(1));
1342 Value thirtyTwo32 = arith::ConstantOp::create(
1343 rewriter, loc, rewriter.getI32IntegerAttr(32));
1344
1345 Value countSubOne =
1346 arith::SubIOp::create(rewriter, loc, count, one32);
1347 Value leadingZeros =
1348 math::CountLeadingZerosOp::create(rewriter, loc, countSubOne);
1349 Value k =
1350 arith::SubIOp::create(rewriter, loc, thirtyTwo32, leadingZeros);
1351
1352 // Compute: numerator = ((1 << 30) + 1) << k
1353 Value k64 =
1354 arith::ExtUIOp::create(rewriter, loc, rewriter.getI64Type(), k);
1355 Value thirtyShiftPlusOne = arith::ConstantOp::create(
1356 rewriter, loc, rewriter.getI64IntegerAttr((1 << 30) + 1));
1357 Value numerator =
1358 arith::ShLIOp::create(rewriter, loc, thirtyShiftPlusOne, k64);
1359
1360 // Compute: scale.multiplier = numerator / value;
1361 Value count64 = arith::ExtUIOp::create(
1362 rewriter, loc, rewriter.getI64Type(), count);
1363 Value multiplier =
1364 arith::DivUIOp::create(rewriter, loc, numerator, count64);
1365 multiplier = arith::TruncIOp::create(
1366 rewriter, loc, rewriter.getI32Type(), multiplier);
1367
1368 // Compute: scale.shift = 30 + k
1369 Value k8 =
1370 arith::TruncIOp::create(rewriter, loc, rewriter.getI8Type(), k);
1371 Value thirty8 = arith::ConstantOp::create(
1372 rewriter, loc, rewriter.getI8IntegerAttr(30));
1373 Value shift = arith::AddIOp::create(rewriter, loc, k8, thirty8);
1374
1375 auto roundingAttr = RoundingModeAttr::get(
1376 rewriter.getContext(), RoundingMode::SINGLE_ROUND);
1377
1378 auto scaled = tosa::ApplyScaleOp::create(
1379 rewriter, loc, rewriter.getI32Type(), poolVal,
1380 multiplier, shift, roundingAttr)
1381 .getResult();
1382
1383 // If we have quantization information we need to apply output
1384 // zeropoint.
1385 if (outputZpVal != 0) {
1386 auto outputZp = arith::ConstantOp::create(
1387 rewriter, loc,
1388 b.getIntegerAttr(scaled.getType(), outputZpVal));
1389 scaled = arith::AddIOp::create(rewriter, loc, scaled, outputZp)
1390 .getResult();
1391 }
1392
1393 // Apply Clip.
1394 int64_t outBitwidth = resultETy.getIntOrFloatBitWidth();
1395
1397 rewriter, loc, accETy,
1398 APInt::getSignedMinValue(outBitwidth).getSExtValue());
1400 rewriter, loc, accETy,
1401 APInt::getSignedMaxValue(outBitwidth).getSExtValue());
1402 auto clamp = clampIntHelper(loc, scaled, min, max, rewriter,
1403 /*isUnsigned=*/false);
1404
1405 poolVal = clamp;
1406 // Convert type.
1407 if (resultETy != clamp.getType()) {
1408 poolVal =
1409 arith::TruncIOp::create(rewriter, loc, resultETy, poolVal);
1410 }
1411 }
1412
1413 linalg::YieldOp::create(rewriter, loc, poolVal);
1414 });
1415
1416 rewriter.replaceOp(op, genericOp.getResult(0));
1417 return success();
1418 }
1419};
1420
1421class TransposeConverter : public OpRewritePattern<tosa::TransposeOp> {
1422public:
1423 using OpRewritePattern<tosa::TransposeOp>::OpRewritePattern;
1424
1425 LogicalResult matchAndRewrite(tosa::TransposeOp op,
1426 PatternRewriter &rewriter) const final {
1427 const llvm::ArrayRef<int32_t> constantPerms = op.getPerms();
1428
1429 Location loc = op.getLoc();
1430 // The verifier should have made sure we have a valid TOSA permutation
1431 // tensor. isPermutationVector doesn't actually check the TOSA perms we
1432 // expect.
1433 SmallVector<OpFoldResult> inputSizes =
1434 tensor::getMixedSizes(rewriter, loc, op.getInput1());
1435 auto permutedSizes =
1436 applyTOSAPermutation<OpFoldResult>(inputSizes, constantPerms);
1437
1438 auto permutedInit =
1439 tensor::EmptyOp::create(rewriter, loc, permutedSizes,
1440 op.getInput1().getType().getElementType());
1441 rewriter.replaceOpWithNewOp<linalg::TransposeOp>(
1442 op, op.getInput1(), permutedInit,
1443 llvm::map_to_vector(constantPerms,
1444 [](int32_t v) -> int64_t { return v; }));
1445 return success();
1446 }
1447};
1448} // namespace
1449
1451 const TypeConverter &converter, RewritePatternSet *patterns,
1453 if (options.preferConv2DKernelLayoutHWCF) {
1454 patterns->add<ConvConverter<tosa::Conv2DOp, linalg::Conv2DNhwcHwcfOp,
1455 linalg::Conv2DNhwcHwcfQOp>>(
1456 patterns->getContext());
1457 } else {
1458 patterns->add<ConvConverter<tosa::Conv2DOp, linalg::Conv2DNhwcFhwcOp,
1459 linalg::Conv2DNhwcFhwcQOp>>(
1460 patterns->getContext());
1461 }
1462 patterns->add<
1463 // clang-format off
1464 ConvConverter<tosa::Conv3DOp, linalg::Conv3DNdhwcDhwcfOp, linalg::Conv3DNdhwcDhwcfQOp>,
1465 DepthwiseConvConverter,
1466 MatMulConverter,
1467 MatMulTConverter,
1468 AvgPool2dConverter,
1469 TransposeConverter
1470 >(patterns->getContext());
1471
1472 patterns->add<
1473 ElementwiseConverter<tosa::AbsOp>,
1474 ElementwiseConverter<tosa::CeilOp>,
1475 ElementwiseConverter<tosa::FloorOp>,
1476 ElementwiseConverter<tosa::ExpOp>,
1477 ElementwiseConverter<tosa::LogOp>,
1478 ElementwiseConverter<tosa::RsqrtOp>,
1479 ElementwiseConverter<tosa::SinOp>,
1480 ElementwiseConverter<tosa::CosOp>,
1481 ElementwiseConverter<tosa::TanhOp>,
1482 ElementwiseConverter<tosa::ErfOp>,
1483 ElementwiseConverter<tosa::ReciprocalOp>,
1484 ElementwiseConverter<tosa::PowOp>,
1485 ElementwiseConverter<tosa::AddOp>,
1486 ElementwiseConverter<tosa::SubOp>,
1487 ElementwiseConverter<tosa::IntDivOp>,
1488 ElementwiseConverter<tosa::MulOp>,
1489 ElementwiseConverter<tosa::NegateOp>,
1490 ElementwiseConverter<tosa::MaximumOp>,
1491 ElementwiseConverter<tosa::MinimumOp>,
1492 ElementwiseConverter<tosa::SelectOp>
1493 >(patterns->getContext());
1494
1495 patterns->add<
1496 MaxPool2dConverter
1497 >(converter, patterns->getContext(), options.allowNonFinites);
1498 // clang-format on
1499}
return success()
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
memberIdxs push_back(ArrayAttr::get(parser.getContext(), values))
static llvm::ManagedStatic< PassManagerOptions > options
static Value clamp(ImplicitLocOpBuilder &builder, Value value, Value lowerBound, Value upperBound)
static Value max(ImplicitLocOpBuilder &builder, Value value, Value bound)
static Value min(ImplicitLocOpBuilder &builder, Value value, Value bound)
static AffineMap getBroadcastingMap(PatternRewriter &rewriter, Value source, Value result)
static mlir::Value applyPad(Location loc, Value input, ArrayRef< int64_t > pad, TypedAttr padAttr, OpBuilder &rewriter)
static void createDepthwiseConvCollapseMap(int64_t outputRank, SmallVector< ReassociationExprs, 4 > &reassociationMap, OpBuilder &rewriter)
static mlir::Value linalgIntBroadcastExtSIAdd(PatternRewriter &rewriter, Location loc, Value bias, Value conv, Value result, ArrayRef< AffineMap > indexingMaps)
static mlir::Value getConvOrPoolOutputDim(Location loc, Value inputDim, int64_t padBeforeAttr, int64_t padAfterAttr, Value kernelDim, int64_t strideAttr, int64_t dilationAttr, OpBuilder &rewriter)
static mlir::Value linalgBroadcastAndMaybeExt(PatternRewriter &rewriter, Location loc, Value source, Value result)
static mlir::Value reifyConstantDim(int64_t attr, ImplicitLocOpBuilder &builder)
static SmallVector< Value > inferDynamicDimsForConv(Location loc, Value input, Value weight, ShapedType resultTy, ArrayRef< int64_t > padAttr, ArrayRef< int64_t > strideAttr, ArrayRef< int64_t > dilationAttr, ArrayRef< int64_t > inputSizeDims, ArrayRef< int64_t > kernelSizeDims, OpBuilder &rewriter)
static std::optional< linalg::ElementwiseKind > getElementwiseKind(Operation *op, Type elementTy)
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition AffineMap.h:46
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
IntegerAttr getIndexAttr(int64_t value)
Definition Builders.cpp:116
AffineMap getMultiDimIdentityMap(unsigned rank)
Definition Builders.cpp:396
AffineExpr getAffineConstantExpr(int64_t constant)
Definition Builders.cpp:381
AffineExpr getAffineDimExpr(unsigned position)
Definition Builders.cpp:373
MLIRContext * getContext() const
Definition Builders.h:56
An attribute that represents a reference to a dense vector or tensor object.
auto getValues() const
Return the held element values as a range of the given type.
ImplicitLocOpBuilder maintains a 'current location', allowing use of the create<> method without spec...
Definition Builders.h:632
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
This class helps build Operations.
Definition Builders.h:210
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:434
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:405
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:431
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 provides an abstraction over the various different ranges of value types.
Definition TypeRange.h:40
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
bool isSignlessInteger() const
Return true if this is a signless integer type (with the specified width).
Definition Types.cpp:66
bool isF32() const
Definition Types.cpp:40
bool isUnsignedInteger() const
Return true if this is an unsigned integer type (with the specified width).
Definition Types.cpp:90
bool isInteger() const
Return true if this is an integer type (with the specified width).
Definition Types.cpp:58
bool isF16() const
Definition Types.cpp:38
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
Definition Types.cpp:124
bool isBF16() const
Definition Types.cpp:37
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:392
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:434
static ConstantIntOp create(OpBuilder &builder, Location location, int64_t value, unsigned width)
Definition ArithOps.cpp:333
static BatchMatmulTransposeBOp create(OpBuilder &builder, Location location, ValueRange inputs, ValueRange outputs, ArrayRef< NamedAttribute > attributes={})
ArityGroupAndKind getArityGroupAndKind(ElementwiseKind kind)
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:853
SmallVector< OpFoldResult > getMixedSizes(OpBuilder &builder, Location loc, Value value)
Return the dimensions of the given tensor value.
Definition TensorOps.cpp:91
SmallVector< T > applyTOSAPermutation(ArrayRef< T > input, ArrayRef< int32_t > perms)
SmallVector< utils::IteratorType > getNParallelLoopsAttrs(unsigned nParallelLoops)
SmallVector< Value > condenseValues(const SmallVector< Value > &values)
std::optional< SmallVector< Value > > checkHasDynamicBatchDims(PatternRewriter &rewriter, Op op, ArrayRef< Value > params)
MatMulTOperands prepareMatMulOperands(PatternRewriter &rewriter, Location loc, Value a, Value b, RankedTensorType outputType, bool transposeB)
Broadcast the batch dimensions of the ranked operands and create a zero-filled result tensor for eith...
APFloat getFloatMinMaxIdentity(const llvm::fltSemantics &semantics, bool negative, bool allowNonFinites)
Value clampIntHelper(Location loc, Value arg, Value min, Value max, OpBuilder &rewriter, bool isUnsigned)
void populateTosaToLinalgNamedConversionPatterns(const TypeConverter &converter, RewritePatternSet *patterns, const TosaToLinalgNamedOptions &options)
Populates conversion passes from TOSA dialect to Linalg named operations.
std::optional< linalg::ElementwiseKind > isConvertibleToLinalgElementwise(Operation *op)
Returns the linalg.elementwise kind that the TosaToLinalgNamed conversion lowers op to,...
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition Matchers.h:490
detail::DenseArrayAttrImpl< int64_t > DenseI64ArrayAttr
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
ElementwiseArityGroup arityGroup
Definition Linalg.h:147