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"
25#include "llvm/ADT/SmallVectorExtras.h"
26
27#include <type_traits>
28
29using namespace mlir;
30using namespace mlir::tosa;
31
33 TypedAttr padAttr, OpBuilder &rewriter) {
34 // Input should be padded only if necessary.
35 if (llvm::all_of(pad, [](int64_t p) { return p == 0; }))
36 return input;
37
38 ShapedType inputTy = cast<ShapedType>(input.getType());
39 Type inputETy = inputTy.getElementType();
40 auto inputShape = inputTy.getShape();
41
42 assert((inputShape.size() * 2) == pad.size());
43
44 SmallVector<int64_t, 4> paddedShape;
47 for (size_t i : llvm::seq(inputShape.size())) {
48 auto lowPad = pad[i * 2];
49 auto highPad = pad[i * 2 + 1];
50 if (ShapedType::isDynamic(inputShape[i]))
51 paddedShape.push_back(inputShape[i]);
52 else
53 paddedShape.push_back(inputShape[i] + highPad + lowPad);
54 lowIndices.push_back(rewriter.getIndexAttr(lowPad));
55 highIndices.push_back(rewriter.getIndexAttr(highPad));
56 }
57
58 Value padValue = arith::ConstantOp::create(rewriter, loc, padAttr);
59
60 return tensor::PadOp::create(rewriter, loc,
61 RankedTensorType::get(paddedShape, inputETy),
62 input, lowIndices, highIndices, padValue);
63}
64
65static mlir::Value
67 Value conv, Value result,
68 ArrayRef<AffineMap> indexingMaps) {
69 ShapedType resultTy = cast<ShapedType>(conv.getType());
70 return linalg::GenericOp::create(
71 rewriter, loc, resultTy, ValueRange({bias, conv}), result,
72 indexingMaps, getNParallelLoopsAttrs(resultTy.getRank()),
73 [](OpBuilder &builder, Location loc, ValueRange args) {
74 Value biasVal = args[0];
75 Type resType = args[1].getType();
76 if (resType != biasVal.getType()) {
77 biasVal =
78 arith::ExtSIOp::create(builder, loc, resType, biasVal);
79 }
80 Value added =
81 arith::AddIOp::create(builder, loc, biasVal, args[1]);
82 linalg::YieldOp::create(builder, loc, added);
83 })
84 .getResult(0);
85}
86
87// Construct the affine map that a linalg generic would use to broadcast the
88// source tensor into the shape of the result tensor.
90 Value result) {
91 ShapedType resultTy = cast<ShapedType>(result.getType());
92 ShapedType sourceTy = cast<ShapedType>(source.getType());
93 const int64_t resultRank = resultTy.getRank();
94 const int64_t sourceRank = sourceTy.getRank();
95
96 // The source tensor is broadcast to all the outer dimensions of the
97 // result tensor.
98 SmallVector<AffineExpr> sourceDims;
99 // In the case of a rank one source tensor with a single element TOSA
100 // specifies that the value be broadcast meaning we need an edge case for a
101 // constant map.
102 assert(sourceTy.hasStaticShape() &&
103 "Dynamic broadcasting shapes not supported!");
104 if (sourceRank == 1 && sourceTy.getDimSize(0) == 1) {
105 sourceDims.push_back(rewriter.getAffineConstantExpr(0));
106 } else {
107 for (auto dim : llvm::seq<int64_t>(0, sourceRank)) {
108 auto expr = rewriter.getAffineDimExpr(dim + resultRank - sourceRank);
109 sourceDims.push_back(expr);
110 }
111 }
112
113 return AffineMap::get(/*dimCount=*/resultRank,
114 /*symbolCount=*/0, sourceDims, rewriter.getContext());
115}
116
117// Broadcast the source value to all the outer dimensions of the result value.
118// If required, the element type is expanded using an arith.extsi or arith.extf
119// operation as appropriate.
121 Location loc, Value source,
122 Value result) {
123 ShapedType resultTy = cast<ShapedType>(result.getType());
124 const int64_t resultRank = resultTy.getRank();
125 // Creating maps for the input and output of the broacast-like generic op.
126 SmallVector<AffineMap, 2> indexingMaps;
127 indexingMaps.push_back(getBroadcastingMap(rewriter, source, result));
128 indexingMaps.push_back(rewriter.getMultiDimIdentityMap(resultRank));
129
130 // Build the broadcast-like operation as a linalg.generic.
131 return linalg::GenericOp::create(
132 rewriter, loc, resultTy, ValueRange({source}), result,
133 indexingMaps, getNParallelLoopsAttrs(resultTy.getRank()),
134 [&resultTy](OpBuilder &builder, Location loc, ValueRange args) {
135 Value biasVal = args[0];
136 Type resType = args[1].getType();
137 if (resType != biasVal.getType()) {
138 biasVal =
139 resultTy.getElementType().isFloat()
140 ? arith::ExtFOp::create(
141 builder, loc, TypeRange{resType},
142 ValueRange{biasVal}, arith::ExtFOp::Properties{})
143 .getResult()
144 : arith::ExtSIOp::create(builder, loc, resType,
145 biasVal)
146 .getResult();
147 }
148 linalg::YieldOp::create(builder, loc, biasVal);
149 })
150 .getResult(0);
151}
152
154 ImplicitLocOpBuilder &builder) {
155 return arith::ConstantIndexOp::create(builder, attr);
156}
157
158// Calculating the output width/height using the formula:
159// H = ((IH+pad_top+pad_bottom-(dilation_y*(KH-1)+1))/stride_y)+1
160// W = ((IW+pad_left+pad_right-(dilation_x*(KW-1)+1))/stride_x)+1
161
163 int64_t padBeforeAttr,
164 int64_t padAfterAttr, Value kernelDim,
165 int64_t strideAttr,
166 int64_t dilationAttr,
167 OpBuilder &rewriter) {
168 ImplicitLocOpBuilder builder(loc, rewriter);
169 auto one = arith::ConstantOp::create(rewriter, loc,
170 IntegerAttr::get(inputDim.getType(), 1));
171 Value padBefore = reifyConstantDim(padBeforeAttr, builder);
172 Value paddedBefore = arith::AddIOp::create(builder, inputDim, padBefore);
173 Value padAfter = reifyConstantDim(padAfterAttr, builder);
174 Value paddedAfter = arith::AddIOp::create(builder, paddedBefore, padAfter);
175
176 Value subOne = arith::SubIOp::create(builder, kernelDim, one);
177 Value dilation = reifyConstantDim(dilationAttr, builder);
178 Value dilated = arith::MulIOp::create(builder, dilation, subOne);
179 Value addOne = arith::AddIOp::create(builder, dilated, one);
180
181 Value subtract = arith::SubIOp::create(builder, paddedAfter, addOne);
182 Value stride = reifyConstantDim(strideAttr, builder);
183 Value divide = arith::DivUIOp::create(builder, subtract, stride);
184 return arith::AddIOp::create(builder, divide, one);
185}
186
187// Creates a vector of the dynamic output dims for Conv2D and Depthwise_Conv2D
189 Location loc, Value input, Value weight, ShapedType resultTy,
190 ArrayRef<int64_t> padAttr, ArrayRef<int64_t> strideAttr,
191 ArrayRef<int64_t> dilationAttr, ArrayRef<int64_t> inputSizeDims,
192 ArrayRef<int64_t> kernelSizeDims, OpBuilder &rewriter) {
193 ShapedType inputTy = cast<ShapedType>(input.getType());
194 int64_t inputRank = inputTy.getRank();
195
196 SmallVector<Value> dynDims;
197 dynDims.resize(resultTy.getRank());
198
199 for (uint32_t i = 0, s = inputSizeDims.size(); i < s; ++i) {
200 int64_t inputDim = inputSizeDims[i];
201 int64_t kernelDim = kernelSizeDims[i];
202 if (resultTy.isDynamicDim(inputDim)) {
203 auto padTop = padAttr[i * 2];
204 auto padBottom = padAttr[i * 2 + 1];
205 auto stride = strideAttr[i];
206 auto dilation = dilationAttr[i];
207 Value initDynDim = tensor::DimOp::create(rewriter, loc, input, inputDim);
208 Value kernelDynDim =
209 tensor::DimOp::create(rewriter, loc, weight, kernelDim);
210 // H = F(IH, pad_top, pad_bottom, dilation_y, KH, stride_y)
211 dynDims[inputDim] =
212 getConvOrPoolOutputDim(loc, initDynDim, padTop, padBottom,
213 kernelDynDim, stride, dilation, rewriter);
214 }
215 }
216
217 // Get the batch/channels dimensions.
218 for (int i = 0; i < inputRank; i++) {
219 if (resultTy.isDynamicDim(i) && !dynDims[i])
220 dynDims[i] = tensor::DimOp::create(rewriter, loc, input, i);
221 }
222
223 SmallVector<Value> filteredDims = condenseValues(dynDims);
224 return filteredDims;
225}
226
227// Creates a map to collapse the last dimension of the Depthwise convolution op
228// due to a shape mismatch
230 int64_t outputRank, SmallVector<ReassociationExprs, 4> &reassociationMap,
231 OpBuilder &rewriter) {
232 reassociationMap.resize(outputRank);
233 for (int i = 0; i < outputRank; i++) {
234 reassociationMap[i].push_back(rewriter.getAffineDimExpr(i));
235 }
236 reassociationMap[outputRank - 1].push_back(
237 rewriter.getAffineDimExpr(outputRank));
238}
239
240namespace {
241
242template <typename TosaConvOp, typename LinalgConvOp, typename LinalgConvQOp>
243class ConvConverter : public OpConversionPattern<TosaConvOp> {
244public:
245 using OpConversionPattern<TosaConvOp>::OpConversionPattern;
246 LogicalResult
247 matchAndRewrite(TosaConvOp op, typename TosaConvOp::Adaptor adaptor,
248 ConversionPatternRewriter &rewriter) const final {
249 Location loc = op->getLoc();
250 Value input = op->getOperand(0);
251 Value weight = op->getOperand(1);
252 Value bias = op->getOperand(2);
253
254 ShapedType inputTy = cast<ShapedType>(input.getType());
255 ShapedType weightTy = cast<ShapedType>(weight.getType());
256 ShapedType biasTy = cast<ShapedType>(bias.getType());
257 ShapedType resultTy = cast<ShapedType>(op->getResult(0).getType());
258
259 Type inputETy = inputTy.getElementType();
260
261 DenseI64ArrayAttr padAttr = op.getPadAttr();
262 DenseI64ArrayAttr strideTosaAttr = op.getStrideAttr();
263 DenseI64ArrayAttr dilationTosaAttr = op.getDilationAttr();
264
265 Type accETy = op.getAccType();
266 Type accTy = RankedTensorType::get(resultTy.getShape(), accETy);
267
268 // Get and verify zero points.
269 FailureOr<int64_t> maybeIZp = op.getInputZeroPoint();
270 if (failed(maybeIZp))
271 return rewriter.notifyMatchFailure(
272 op, "input zero point cannot be statically determined");
273
274 FailureOr<int64_t> maybeWZp = op.getWeightZeroPoint();
275 if (failed(maybeWZp))
276 return rewriter.notifyMatchFailure(
277 op, "weight zero point cannot be statically determined");
278
279 const int64_t inputZpVal = *maybeIZp;
280 const int64_t weightZpVal = *maybeWZp;
281
282 if (op.verifyInputZeroPoint(inputZpVal).failed())
283 return rewriter.notifyMatchFailure(
284 op, "input zero point must be zero for non-int8 integer types");
285
286 if (op.verifyWeightZeroPoint(weightZpVal).failed())
287 return rewriter.notifyMatchFailure(
288 op, "weight zero point must be zero for non-int8 integer types");
289
290 bool hasZp = (inputZpVal != 0) || (weightZpVal != 0);
291
292 if (!weightTy.hasStaticShape() || !biasTy.hasStaticShape())
293 return rewriter.notifyMatchFailure(
294 op, "tosa.conv ops require static shapes for weight and bias");
295
296 if (inputETy.isUnsignedInteger())
297 return rewriter.notifyMatchFailure(
298 op, "tosa.conv ops does not support unsigned integer input");
299
300 llvm::SmallVector<int64_t> inputSizeDims;
301 llvm::SmallVector<int64_t> kernelSizeDims;
302 for (int i = 1; i < resultTy.getRank() - 1; i++) {
303 inputSizeDims.push_back(i);
304 kernelSizeDims.push_back(i);
305 }
306
307 SmallVector<Value> filteredDims = inferDynamicDimsForConv(
308 loc, input, weight, resultTy, padAttr.asArrayRef(),
309 strideTosaAttr.asArrayRef(), dilationTosaAttr.asArrayRef(),
310 inputSizeDims, kernelSizeDims, rewriter);
311
312 auto weightShape = weightTy.getShape();
313
314 // Apply padding as necessary.
315 TypedAttr zeroAttr = rewriter.getZeroAttr(inputETy);
316 if (hasZp) {
317 int64_t intMin =
318 APInt::getSignedMinValue(inputETy.getIntOrFloatBitWidth())
319 .getSExtValue();
320 int64_t intMax =
321 APInt::getSignedMaxValue(inputETy.getIntOrFloatBitWidth())
322 .getSExtValue();
323
324 if (inputZpVal < intMin || inputZpVal > intMax)
325 return rewriter.notifyMatchFailure(
326 op, "tosa.conv op quantization has zp outside of input range");
327
328 zeroAttr = rewriter.getIntegerAttr(inputETy, inputZpVal);
329 }
330
331 llvm::SmallVector<int64_t> pad;
332 pad.resize(2, 0);
333 llvm::append_range(pad, padAttr.asArrayRef());
334 pad.resize(pad.size() + 2, 0);
335 input = applyPad(loc, input, pad, zeroAttr, rewriter);
336
337 if (4 == inputTy.getRank()) {
338 // For 2D convolutions, we need to check if the target convolution op
339 // wants a HWCF kernel layout.
340 bool wantHwcf =
341 hasZp ? std::is_same_v<LinalgConvQOp, linalg::Conv2DNhwcHwcfQOp>
342 : std::is_same_v<LinalgConvOp, linalg::Conv2DNhwcHwcfOp>;
343 if (wantHwcf) {
344 // Transpose the kernel to match dimension ordering of the linalg
345 // convolution operation.
346 // TODO(suderman): See if this can be efficiently folded - check whether
347 // the input is used anywhere else, if not fold the constant.
348 SmallVector<int32_t> weightPerm;
349 for (int i = 1; i < resultTy.getRank(); i++)
350 weightPerm.push_back(i);
351 weightPerm.push_back(0);
352
353 SmallVector<int64_t> newWeightShape;
354 for (auto dim : weightPerm)
355 newWeightShape.push_back(weightShape[dim]);
356 auto weightPermAttr = rewriter.getDenseI32ArrayAttr(weightPerm);
357 Type newWeightTy =
358 RankedTensorType::get(newWeightShape, weightTy.getElementType());
359 weight = tosa::TransposeOp::create(rewriter, loc, newWeightTy, weight,
360 weightPermAttr);
361 }
362 }
363
364 // For Conv3D transpose the kernel to match dimension ordering of the linalg
365 // convolution operation. Conv2D has a 1-1 mapping in linalg so better to
366 // map directly and then transpose later if desired.
367 if (5 == inputTy.getRank()) {
368 // TODO(suderman): See if this can be efficiently folded - check whether
369 // the input is used anywhere else, if not fold the constant.
370 SmallVector<int32_t> weightPerm;
371 for (int i = 1; i < resultTy.getRank(); i++)
372 weightPerm.push_back(i);
373 weightPerm.push_back(0);
374
375 SmallVector<int64_t> newWeightShape;
376 for (auto dim : weightPerm)
377 newWeightShape.push_back(weightShape[dim]);
378 auto weightPermAttr = rewriter.getDenseI32ArrayAttr(weightPerm);
379 Type newWeightTy =
380 RankedTensorType::get(newWeightShape, weightTy.getElementType());
381 weight = tosa::TransposeOp::create(rewriter, loc, newWeightTy, weight,
382 weightPermAttr);
383 }
384
385 // Extract the attributes for convolution.
386 ArrayRef<int64_t> stride = strideTosaAttr;
387 ArrayRef<int64_t> dilation = dilationTosaAttr;
388
389 // Create the convolution op.
390 auto strideAttr = rewriter.getI64TensorAttr(stride);
391 auto dilationAttr = rewriter.getI64TensorAttr(dilation);
392
393 Value biasEmptyTensor = tensor::EmptyOp::create(
394 rewriter, loc, resultTy.getShape(), accETy, filteredDims);
395
396 Value broadcastBias =
397 linalgBroadcastAndMaybeExt(rewriter, loc, bias, biasEmptyTensor);
398
399 if (hasZp) {
400 auto iZp = rewriter.getI32IntegerAttr(inputZpVal);
401 auto kZp = rewriter.getI32IntegerAttr(weightZpVal);
402
403 auto iZpVal = arith::ConstantOp::create(rewriter, loc, iZp);
404 auto kZpVal = arith::ConstantOp::create(rewriter, loc, kZp);
405
406 Value conv = LinalgConvQOp::create(
407 rewriter, loc, resultTy,
408 ValueRange{input, weight, iZpVal, kZpVal},
409 ValueRange{broadcastBias}, strideAttr, dilationAttr)
410 ->getResult(0);
411
412 rewriter.replaceOp(op, conv);
413 return success();
414 }
415
416 Value conv = LinalgConvOp::create(
417 rewriter, loc, accTy, ValueRange{input, weight},
418 ValueRange{broadcastBias}, strideAttr, dilationAttr)
419 ->getResult(0);
420
421 // We may need to truncate back to the result type if the accumulator was
422 // wider than the result.
423 if (resultTy != accTy)
424 conv = tosa::CastOp::create(rewriter, loc, resultTy, conv);
425
426 rewriter.replaceOp(op, conv);
427 return success();
428 }
429};
430
431class DepthwiseConvConverter
432 : public OpConversionPattern<tosa::DepthwiseConv2DOp> {
433public:
434 using OpConversionPattern<tosa::DepthwiseConv2DOp>::OpConversionPattern;
435 LogicalResult
436 matchAndRewrite(tosa::DepthwiseConv2DOp op, OpAdaptor adaptor,
437 ConversionPatternRewriter &rewriter) const final {
438 Location loc = op->getLoc();
439 Value input = op->getOperand(0);
440 Value weight = op->getOperand(1);
441 Value bias = op->getOperand(2);
442
443 ShapedType inputTy = cast<ShapedType>(input.getType());
444 ShapedType weightTy = cast<ShapedType>(weight.getType());
445 ShapedType biasTy = cast<ShapedType>(bias.getType());
446 ShapedType resultTy = cast<ShapedType>(op->getResult(0).getType());
447 int64_t resultRank = resultTy.getRank();
448
449 Type inputETy = inputTy.getElementType();
450 Type resultETy = resultTy.getElementType();
451
452 auto padAttr = op.getPadAttr();
453 auto strideTosaAttr = op.getStrideAttr();
454 auto dilationTosaAttr = op.getDilationAttr();
455
456 Type accETy = op.getAccType();
457
458 if (!weightTy.hasStaticShape() || !biasTy.hasStaticShape())
459 return rewriter.notifyMatchFailure(
460 op, "tosa.depthwise_conv ops require static shapes");
461
462 // Compute output dynamic dims
463 SmallVector<Value> filteredDims = inferDynamicDimsForConv(
464 loc, input, weight, resultTy, padAttr.asArrayRef(),
465 strideTosaAttr.asArrayRef(), dilationTosaAttr.asArrayRef(),
466 /*inputSizeDims=*/{1, 2},
467 /*kernelSizeDims=*/{0, 1}, rewriter);
468
469 // Get and verify zero points.
470
471 FailureOr<int64_t> maybeIZp = op.getInputZeroPoint();
472 FailureOr<int64_t> maybeWZp = op.getWeightZeroPoint();
473 if (failed(maybeIZp))
474 return rewriter.notifyMatchFailure(
475 op, "input zero point cannot be statically determined");
476 if (failed(maybeWZp))
477 return rewriter.notifyMatchFailure(
478 op, "weight zero point cannot be statically determined");
479
480 const int64_t inputZpVal = *maybeIZp;
481 const int64_t weightZpVal = *maybeWZp;
482
483 if (op.verifyInputZeroPoint(inputZpVal).failed())
484 return rewriter.notifyMatchFailure(
485 op, "input zero point must be zero for non-int8 integer types");
486
487 if (op.verifyWeightZeroPoint(weightZpVal).failed())
488 return rewriter.notifyMatchFailure(
489 op, "weight zero point must be zero for non-int8 integer types");
490
491 bool hasNullZps = (inputZpVal == 0) && (weightZpVal == 0);
492 auto weightShape = weightTy.getShape();
493 auto resultShape = resultTy.getShape();
494
495 // Apply padding as necessary.
496 TypedAttr zeroAttr = rewriter.getZeroAttr(inputETy);
497 if (!hasNullZps) {
498 int64_t intMin =
499 APInt::getSignedMinValue(inputETy.getIntOrFloatBitWidth())
500 .getSExtValue();
501 int64_t intMax =
502 APInt::getSignedMaxValue(inputETy.getIntOrFloatBitWidth())
503 .getSExtValue();
504
505 if (inputZpVal < intMin || inputZpVal > intMax)
506 return rewriter.notifyMatchFailure(
507 op, "tosa.depthwise_conv op quantization has zp outside of input "
508 "range");
509
510 zeroAttr = rewriter.getIntegerAttr(inputETy, inputZpVal);
511 }
512
513 llvm::SmallVector<int64_t> pad;
514 pad.resize(2, 0);
515 llvm::append_range(pad, padAttr.asArrayRef());
516 pad.resize(pad.size() + 2, 0);
517
518 input = applyPad(loc, input, pad, zeroAttr, rewriter);
519
520 // Extract the attributes for convolution.
521 ArrayRef<int64_t> stride = strideTosaAttr;
522 ArrayRef<int64_t> dilation = dilationTosaAttr;
523
524 // Create the convolution op.
525 auto strideAttr = rewriter.getI64TensorAttr(stride);
526 auto dilationAttr = rewriter.getI64TensorAttr(dilation);
527 ShapedType linalgConvTy =
528 RankedTensorType::get({resultShape[0], resultShape[1], resultShape[2],
529 weightShape[2], weightShape[3]},
530 accETy);
531
532 auto resultZeroAttr = rewriter.getZeroAttr(accETy);
533 Value emptyTensor = tensor::EmptyOp::create(
534 rewriter, loc, linalgConvTy.getShape(), accETy, filteredDims);
535 Value zero = arith::ConstantOp::create(rewriter, loc, resultZeroAttr);
536 Value zeroTensor = linalg::FillOp::create(rewriter, loc, ValueRange{zero},
537 ValueRange{emptyTensor})
538 .result();
539
540 Value biasEmptyTensor = tensor::EmptyOp::create(
541 rewriter, loc, resultTy.getShape(), resultETy, filteredDims);
542
543 // Broadcast the initial value to the output tensor before convolving.
544 SmallVector<AffineMap, 4> indexingMaps;
545 indexingMaps.push_back(getBroadcastingMap(rewriter, bias, biasEmptyTensor));
546 indexingMaps.push_back(rewriter.getMultiDimIdentityMap(resultRank));
547 indexingMaps.push_back(rewriter.getMultiDimIdentityMap(resultRank));
548
549 if (hasNullZps) {
550 Value conv = linalg::DepthwiseConv2DNhwcHwcmOp::create(
551 rewriter, loc, linalgConvTy, ValueRange{input, weight},
552 ValueRange{zeroTensor}, strideAttr, dilationAttr)
553 .getResult(0);
554
555 // We may need to truncate back to the result type if the accumulator was
556 // wider than the result.
557 if (accETy != resultETy)
558 conv = tosa::CastOp::create(
559 rewriter, loc,
560 RankedTensorType::get(cast<ShapedType>(conv.getType()).getShape(),
561 resultETy),
562 conv);
563
564 SmallVector<ReassociationExprs, 4> reassociationMap;
565 createDepthwiseConvCollapseMap(resultRank, reassociationMap, rewriter);
566 Value convReshape = tensor::CollapseShapeOp::create(
567 rewriter, loc, resultTy, conv, reassociationMap);
568
569 Value result =
570 linalg::GenericOp::create(
571 rewriter, loc, resultTy, ValueRange({bias, convReshape}),
572 biasEmptyTensor, indexingMaps, getNParallelLoopsAttrs(resultRank),
573 [&](OpBuilder &nestedBuilder, Location nestedLoc,
574 ValueRange args) {
575 Value added;
576 if (llvm::isa<FloatType>(inputETy))
577 added = arith::AddFOp::create(nestedBuilder, loc, args[0],
578 args[1]);
579 else
580 added = arith::AddIOp::create(nestedBuilder, loc, args[0],
581 args[1]);
582 linalg::YieldOp::create(nestedBuilder, nestedLoc, added);
583 })
584 .getResult(0);
585 rewriter.replaceOp(op, result);
586 } else {
587 IntegerAttr iZp = rewriter.getI32IntegerAttr(inputZpVal);
588 IntegerAttr wZp = rewriter.getI32IntegerAttr(weightZpVal);
589 auto iZpVal = arith::ConstantOp::create(rewriter, loc, iZp);
590 auto kZpVal = arith::ConstantOp::create(rewriter, loc, wZp);
591 Value conv = linalg::DepthwiseConv2DNhwcHwcmQOp::create(
592 rewriter, loc, linalgConvTy,
593 ValueRange{input, weight, iZpVal, kZpVal},
594 ValueRange{zeroTensor}, strideAttr, dilationAttr)
595 .getResult(0);
596 SmallVector<ReassociationExprs, 4> reassociationMap;
597 createDepthwiseConvCollapseMap(resultRank, reassociationMap, rewriter);
598 Value convReshape = tensor::CollapseShapeOp::create(
599 rewriter, loc, resultTy, conv, reassociationMap);
601 rewriter, loc, bias, convReshape, biasEmptyTensor, indexingMaps);
602 rewriter.replaceOp(op, result);
603 }
604 return success();
605 }
606};
607
608class MatMulConverter : public OpConversionPattern<tosa::MatMulOp> {
609public:
610 using OpConversionPattern<tosa::MatMulOp>::OpConversionPattern;
611 LogicalResult
612 matchAndRewrite(tosa::MatMulOp op, OpAdaptor adaptor,
613 ConversionPatternRewriter &rewriter) const final {
614 Location loc = op.getLoc();
615
616 auto outputTy = cast<ShapedType>(op.getType());
617 auto outputElementTy = outputTy.getElementType();
618
619 SmallVector<Value> dynDims;
620 dynDims.resize(cast<ShapedType>(op->getResult(0).getType()).getRank());
621
622 if (!outputTy.hasRank() || outputTy.isDynamicDim(0)) {
623 dynDims[0] = tensor::DimOp::create(rewriter, loc, op->getOperand(0), 0);
624 }
625
626 if (!outputTy.hasRank() || outputTy.isDynamicDim(1)) {
627 dynDims[1] = tensor::DimOp::create(rewriter, loc, op->getOperand(0), 1);
628 }
629
630 if (!outputTy.hasRank() || outputTy.isDynamicDim(2)) {
631 dynDims[2] = tensor::DimOp::create(rewriter, loc, op->getOperand(1), 2);
632 }
633
634 SmallVector<Value> filteredDims = condenseValues(dynDims);
635
636 auto zeroAttr = rewriter.getZeroAttr(outputElementTy);
637 Value zero = arith::ConstantOp::create(rewriter, loc, zeroAttr);
638 auto emptyTensor =
639 tensor::EmptyOp::create(rewriter, loc, outputTy.getShape(),
640 outputTy.getElementType(), filteredDims);
641 Value zeroTensor = linalg::FillOp::create(rewriter, loc, ValueRange{zero},
642 ValueRange{emptyTensor})
643 .result();
644
645 FailureOr<int64_t> maybeAZp = op.getAZeroPoint();
646 FailureOr<int64_t> maybeBZp = op.getBZeroPoint();
647 if (failed(maybeAZp))
648 return rewriter.notifyMatchFailure(
649 op, "input a zero point cannot be statically determined");
650 if (failed(maybeBZp))
651 return rewriter.notifyMatchFailure(
652 op, "input b zero point cannot be statically determined");
653
654 const int64_t aZpVal = *maybeAZp;
655 const int64_t bZpVal = *maybeBZp;
656
657 if (op.verifyAZeroPoint(aZpVal).failed())
658 return rewriter.notifyMatchFailure(
659 op, "input a zero point must be zero for non-int8 integer types");
660
661 if (op.verifyBZeroPoint(bZpVal).failed())
662 return rewriter.notifyMatchFailure(
663 op, "input b zero point must be zero for non-int8 integer types");
664
665 if (aZpVal == 0 && bZpVal == 0) {
666 rewriter.replaceOpWithNewOp<linalg::BatchMatmulOp>(
667 op, TypeRange{op.getType()},
668 ValueRange{adaptor.getA(), adaptor.getB()}, ValueRange{zeroTensor});
669 return success();
670 }
671
672 auto aZp = arith::ConstantOp::create(rewriter, loc,
673 rewriter.getI32IntegerAttr(aZpVal));
674 auto bZp = arith::ConstantOp::create(rewriter, loc,
675 rewriter.getI32IntegerAttr(bZpVal));
676 rewriter.replaceOpWithNewOp<linalg::QuantizedBatchMatmulOp>(
677 op, TypeRange{op.getType()},
678 ValueRange{adaptor.getA(), adaptor.getB(), aZp, bZp}, zeroTensor);
679
680 return success();
681 }
682};
683
685getMatMulTBatchCollapseReassociation(int64_t rank) {
686 SmallVector<ReassociationIndices> reassociation(3);
687 for (int64_t dim = 0; dim < rank - 2; ++dim)
688 reassociation[0].push_back(dim);
689 reassociation[1].push_back(rank - 2);
690 reassociation[2].push_back(rank - 1);
691 return reassociation;
692}
693
694class MatMulTConverter : public OpConversionPattern<tosa::MatMulTOp> {
695public:
696 using OpConversionPattern<tosa::MatMulTOp>::OpConversionPattern;
697 LogicalResult
698 matchAndRewrite(tosa::MatMulTOp op, OpAdaptor adaptor,
699 ConversionPatternRewriter &rewriter) const final {
700 Location loc = op.getLoc();
701 auto aType = dyn_cast<RankedTensorType>(adaptor.getA().getType());
702 auto bType = dyn_cast<RankedTensorType>(adaptor.getB().getType());
703 auto outputType = dyn_cast<RankedTensorType>(op.getType());
704 if (!aType || !bType || !outputType)
705 return rewriter.notifyMatchFailure(op, "only supports ranked tensors");
706
707 FailureOr<int64_t> maybeAZp = op.getAZeroPoint();
708 FailureOr<int64_t> maybeBZp = op.getBZeroPoint();
709 if (failed(maybeAZp))
710 return rewriter.notifyMatchFailure(
711 op, "input A zero point cannot be statically determined");
712 if (failed(maybeBZp))
713 return rewriter.notifyMatchFailure(
714 op, "input B zero point cannot be statically determined");
715
716 const int64_t aZpVal = *maybeAZp;
717 const int64_t bZpVal = *maybeBZp;
718
719 if (aZpVal != 0 || bZpVal != 0)
720 return rewriter.notifyMatchFailure(
721 op, "nonzero zero points require the generic MATMUL_T lowering");
722
723 const int64_t outputRank = outputType.getRank();
724 auto [a, b, zeroTensor] = prepareMatMulTOperands(
725 rewriter, loc, adaptor.getA(), adaptor.getB(), outputType);
726
727 if (outputRank == 2) {
728 rewriter.replaceOpWithNewOp<linalg::MatmulTransposeBOp>(
729 op, TypeRange{outputType}, ValueRange{a, b}, ValueRange{zeroTensor});
730 return success();
731 }
732
733 if (outputRank == 3) {
734 rewriter.replaceOpWithNewOp<linalg::BatchMatmulTransposeBOp>(
735 op, TypeRange{outputType}, ValueRange{a, b}, ValueRange{zeroTensor});
736 return success();
737 }
738
739 SmallVector<ReassociationIndices> reassociation =
740 getMatMulTBatchCollapseReassociation(outputRank);
741 auto collapse = [&](Value value) {
742 auto type = cast<RankedTensorType>(value.getType());
743 auto collapsedType =
744 tensor::CollapseShapeOp::inferCollapsedType(type, reassociation);
745 return tensor::CollapseShapeOp::create(rewriter, loc, collapsedType,
746 value, reassociation);
747 };
748 Value collapsedA = collapse(a);
749 Value collapsedB = collapse(b);
750 Value collapsedInit = collapse(zeroTensor);
751 auto collapsedOutputType = cast<RankedTensorType>(collapsedInit.getType());
752 Value collapsedResult =
754 rewriter, loc, TypeRange{collapsedOutputType},
755 ValueRange{collapsedA, collapsedB}, ValueRange{collapsedInit})
756 .getResult(0);
757 Value result = tensor::ExpandShapeOp::create(
758 rewriter, loc, outputType, collapsedResult, reassociation,
759 tensor::getMixedSizes(rewriter, loc, zeroTensor));
760 rewriter.replaceOp(op, result);
761 return success();
762 }
763};
764
765class MaxPool2dConverter : public OpConversionPattern<tosa::MaxPool2dOp> {
766public:
767 MaxPool2dConverter(const TypeConverter &typeConverter, MLIRContext *context,
768 bool allowNonFinites)
769 : OpConversionPattern(typeConverter, context),
770 allowNonFinites(allowNonFinites) {}
771
772 // Compute the dynamic output sizes of the maxpool operation.
773 static SmallVector<Value>
774 computeDynamicOutputSizes(tosa::MaxPool2dOp op, OpAdaptor adaptor,
775 ConversionPatternRewriter &rewriter) {
776 TensorType resultTy = op.getType();
777 Location loc = op.getLoc();
778
779 Value input = adaptor.getInput();
780 ArrayRef<int64_t> kernel = op.getKernel();
781 ArrayRef<int64_t> pad = op.getPad();
782 ArrayRef<int64_t> stride = op.getStride();
783
784 SmallVector<Value> dynamicDims;
785
786 // Batch dimension
787 if (resultTy.isDynamicDim(0))
788 dynamicDims.push_back(tensor::DimOp::create(rewriter, loc, input, 0));
789
790 // Height/width dimensions
791 for (int64_t dim : {1, 2}) {
792 if (!resultTy.isDynamicDim(dim))
793 continue;
794
795 // Index into the attribute arrays
796 int64_t index = dim - 1;
797
798 // Input height/width
799 Value ihw = tensor::DimOp::create(rewriter, loc, input, dim);
800
801 // Kernel height/width
802 Value khw = arith::ConstantIndexOp::create(rewriter, loc, kernel[index]);
803
804 // Output height/width
805 Value ohw = getConvOrPoolOutputDim(loc, ihw, pad[index * 2],
806 pad[index * 2 + 1], khw, stride[index],
807 /*dilationAttr=*/1, rewriter);
808 dynamicDims.push_back(ohw);
809 }
810
811 // Channel dimension
812 if (resultTy.isDynamicDim(3))
813 dynamicDims.push_back(tensor::DimOp::create(rewriter, loc, input, 3));
814
815 return dynamicDims;
816 }
817
818 LogicalResult
819 matchAndRewrite(tosa::MaxPool2dOp op, OpAdaptor adaptor,
820 ConversionPatternRewriter &rewriter) const final {
821 Location loc = op.getLoc();
822 Value input = adaptor.getInput();
823 ShapedType inputTy = cast<ShapedType>(input.getType());
824
825 bool isUnsigned = op.getType().getElementType().isUnsignedInteger();
826 ShapedType resultTy =
827 getTypeConverter()->convertType<ShapedType>(op.getType());
828 if (!resultTy)
829 return rewriter.notifyMatchFailure(op, "failed to convert type");
830 Type resultETy = inputTy.getElementType();
831 NanPropagationMode nanMode = op.getNanMode();
832
833 SmallVector<Value> dynamicDims =
834 computeDynamicOutputSizes(op, adaptor, rewriter);
835
836 // Determine what the initial value needs to be for the max pool op.
837 TypedAttr initialAttr;
838 if (resultETy.isF32() || resultETy.isBF16() || resultETy.isF16()) {
839 const llvm::fltSemantics &semantics =
840 cast<FloatType>(resultETy).getFloatSemantics();
841 if (llvm::APFloat::semanticsHasNaN(semantics))
842 initialAttr = rewriter.getFloatAttr(
843 resultETy,
844 nanMode == NanPropagationMode::IGNORE
845 ? APFloat::getNaN(semantics)
846 : getFloatMinMaxIdentity(semantics,
847 /*negative=*/true, allowNonFinites));
848 }
849
850 else if (isUnsigned)
851 initialAttr = rewriter.getIntegerAttr(
852 resultETy, APInt::getZero(resultETy.getIntOrFloatBitWidth()));
853 else if (isa<IntegerType>(resultETy))
854 initialAttr = rewriter.getIntegerAttr(
855 resultETy,
856 APInt::getSignedMinValue(resultETy.getIntOrFloatBitWidth()));
857
858 if (!initialAttr)
859 return rewriter.notifyMatchFailure(
860 op, "Unsupported initial value for tosa.maxpool_2d op");
861
862 // Apply padding as necessary.
863 llvm::SmallVector<int64_t> pad;
864 pad.resize(2, 0);
865 llvm::append_range(pad, op.getPad());
866 pad.resize(pad.size() + 2, 0);
867
868 // The initial value doubles as the padding value. This is safe for every
869 // seed above because the verifier rejects a pad as wide as the kernel on
870 // any side, so no window consists purely of padding.
871 Value paddedInput = applyPad(loc, input, pad, initialAttr, rewriter);
872
873 Value initialValue = arith::ConstantOp::create(rewriter, loc, initialAttr);
874
875 ArrayRef<int64_t> kernel = op.getKernel();
876 ArrayRef<int64_t> stride = op.getStride();
877
878 Attribute strideAttr = rewriter.getI64VectorAttr(stride);
879 Attribute dilationAttr = rewriter.getI64VectorAttr({1, 1});
880
881 // Create the linalg op that performs pooling.
882 Value emptyTensor =
883 tensor::EmptyOp::create(rewriter, loc, resultTy.getShape(),
884 resultTy.getElementType(), dynamicDims);
885
886 Value filledEmptyTensor =
887 linalg::FillOp::create(rewriter, loc, initialValue, emptyTensor)
888 .result();
889
890 Value fakeWindowDims =
891 tensor::EmptyOp::create(rewriter, loc, kernel, resultETy);
892
893 if (isUnsigned) {
894 rewriter.replaceOpWithNewOp<linalg::PoolingNhwcMaxUnsignedOp>(
895 op, ArrayRef<Type>{resultTy}, ValueRange{paddedInput, fakeWindowDims},
896 filledEmptyTensor, strideAttr, dilationAttr);
897 return llvm::success();
898 }
899
900 auto resultOp = linalg::PoolingNhwcMaxOp::create(
901 rewriter, op->getLoc(), ArrayRef<Type>{resultTy},
902 ValueRange{paddedInput, fakeWindowDims}, filledEmptyTensor, strideAttr,
903 dilationAttr);
904
905 rewriter.replaceOp(op, resultOp);
906
907 // NaN propagation has no meaning for non floating point types.
908 if (!isa<FloatType>(getElementTypeOrSelf(inputTy)))
909 return success();
910
911 // "PROPAGATE" mode matches the behaviour of the LinAlg named op, so no
912 // compare and select materialization is required.
913 //
914 // In the case of "IGNORE" we need to insert a compare and select. Since
915 // we've already produced a named op we will just take its body and modify
916 // it to include the appropriate checks. A NaN accumulator represents a
917 // window with no finite values yet. The first finite input replaces it;
918 // input NaNs are ignored. An all-NaN window therefore remains NaN.
919 if (nanMode == NanPropagationMode::IGNORE) {
920 auto genericOp = linalg::GenericOp::create(
921 rewriter, loc, resultOp.getType(0), resultOp.getInputs(),
922 resultOp.getOutputs(), resultOp.getIndexingMapsArray(),
923 resultOp.getIteratorTypesArray(),
924 [&](OpBuilder &opBuilder, Location loc, ValueRange blockArgs) {
925 IRMapping map;
926 auto oldBlock = resultOp.getRegion().begin();
927 auto oldArgs = oldBlock->getArguments();
928 auto &oldMaxOp = *resultOp.getBlock()->begin();
929 map.map(oldArgs, blockArgs);
930 auto *newOp = opBuilder.clone(oldMaxOp, map);
931 Value inputIsNaN =
932 arith::CmpFOp::create(opBuilder, loc, arith::CmpFPredicate::UNO,
933 blockArgs.front(), blockArgs.front());
934 Value accumulatorIsNaN =
935 arith::CmpFOp::create(opBuilder, loc, arith::CmpFPredicate::UNO,
936 blockArgs.back(), blockArgs.back());
937 Value finiteMaximum =
938 arith::SelectOp::create(opBuilder, loc, accumulatorIsNaN,
939 blockArgs.front(), newOp->getResult(0));
940 Value result = arith::SelectOp::create(
941 opBuilder, loc, inputIsNaN, blockArgs.back(), finiteMaximum);
942 linalg::YieldOp::create(opBuilder, loc, result);
943 });
944 rewriter.replaceOp(resultOp, genericOp);
945 }
946
947 return success();
948 }
949
950private:
951 bool allowNonFinites;
952};
953
954class AvgPool2dConverter : public OpRewritePattern<tosa::AvgPool2dOp> {
955public:
956 using OpRewritePattern<tosa::AvgPool2dOp>::OpRewritePattern;
957
958 LogicalResult matchAndRewrite(tosa::AvgPool2dOp op,
959 PatternRewriter &rewriter) const final {
960 Location loc = op.getLoc();
961 Value input = op.getInput();
962 ShapedType inputTy = cast<ShapedType>(input.getType());
963 Type inElementTy = inputTy.getElementType();
964
965 ShapedType resultTy = cast<ShapedType>(op.getType());
966 Type resultETy = cast<ShapedType>(op.getType()).getElementType();
967
968 Type accETy = op.getAccType();
969 ShapedType accTy = resultTy.clone(accETy);
970
971 auto dynamicDimsOr =
972 checkHasDynamicBatchDims(rewriter, op, {input, op.getOutput()});
973 if (!dynamicDimsOr.has_value())
974 return failure();
975 SmallVector<Value> dynamicDims = *dynamicDimsOr;
976
977 FailureOr<int64_t> maybeIZp = op.getInputZeroPoint();
978 FailureOr<int64_t> maybeOZp = op.getOutputZeroPoint();
979 if (failed(maybeIZp))
980 return rewriter.notifyMatchFailure(
981 op, "input zero point could not be statically determined");
982 if (failed(maybeOZp))
983 return rewriter.notifyMatchFailure(
984 op, "output zero point could not be statically determined");
985
986 const int64_t inputZpVal = *maybeIZp;
987 const int64_t outputZpVal = *maybeOZp;
988
989 // Apply padding as necessary.
990 llvm::SmallVector<int64_t> pad;
991 pad.resize(2, 0);
992 llvm::append_range(pad, op.getPad());
993 pad.resize(pad.size() + 2, 0);
994 TypedAttr padAttr = rewriter.getZeroAttr(inElementTy);
995 // Unsupported element type
996 if (!padAttr)
997 return failure();
998 Value paddedInput = applyPad(loc, input, pad, padAttr, rewriter);
999
1000 auto initialAttr = rewriter.getZeroAttr(accETy);
1001 Value initialValue = arith::ConstantOp::create(rewriter, loc, initialAttr);
1002
1003 ArrayRef<int64_t> kernel = op.getKernel();
1004 ArrayRef<int64_t> stride = op.getStride();
1005
1006 Attribute strideAttr = rewriter.getI64VectorAttr(stride);
1007 Attribute dilationAttr = rewriter.getI64VectorAttr({1, 1});
1008
1009 // Create the linalg op that performs pooling.
1010 Value poolEmptyTensor = tensor::EmptyOp::create(
1011 rewriter, loc, accTy.getShape(), accETy, dynamicDims);
1012
1013 Value filledEmptyTensor =
1014 linalg::FillOp::create(rewriter, loc, ValueRange{initialValue},
1015 ValueRange{poolEmptyTensor})
1016 .result();
1017
1018 Value fakeWindowDims =
1019 tensor::EmptyOp::create(rewriter, loc, kernel, accETy);
1020
1021 // Sum across the pooled region.
1022 Value poolingOp = linalg::PoolingNhwcSumOp::create(
1023 rewriter, loc, ArrayRef<Type>{accTy},
1024 ValueRange{paddedInput, fakeWindowDims},
1025 filledEmptyTensor, strideAttr, dilationAttr)
1026 .getResult(0);
1027
1028 // Normalize the summed value by the number of elements grouped in each
1029 // pool.
1030 Value iH = tensor::DimOp::create(rewriter, loc, poolingOp, 1);
1031 Value iW = tensor::DimOp::create(rewriter, loc, poolingOp, 2);
1032
1033 auto one = arith::ConstantIndexOp::create(rewriter, loc, 1);
1034 iH = arith::SubIOp::create(rewriter, loc, iH, one);
1035 iW = arith::SubIOp::create(rewriter, loc, iW, one);
1036
1037 Value genericEmptyTensor = tensor::EmptyOp::create(
1038 rewriter, loc, resultTy.getShape(), resultETy, dynamicDims);
1039
1040 auto affineMap = rewriter.getMultiDimIdentityMap(resultTy.getRank());
1041 auto genericOp = linalg::GenericOp::create(
1042 rewriter, loc, ArrayRef<Type>({resultTy}), ValueRange{poolingOp},
1043 ValueRange{genericEmptyTensor},
1044 ArrayRef<AffineMap>({affineMap, affineMap}),
1045 getNParallelLoopsAttrs(resultTy.getRank()),
1046 [&](OpBuilder &b, Location loc, ValueRange args) {
1047 auto zero = arith::ConstantIndexOp::create(rewriter, loc, 0);
1048
1049 // Determines what the portion of valid input is covered by the
1050 // kernel.
1051 auto padFn = [&](Value valid, Value pos, int64_t pad) -> Value {
1052 if (pad == 0)
1053 return valid;
1054
1055 auto padVal = arith::ConstantIndexOp::create(rewriter, loc, pad);
1056 Value dpos = arith::SubIOp::create(rewriter, loc, pos, padVal);
1057
1058 Value offset = arith::MinSIOp::create(rewriter, loc, dpos, zero);
1059 return arith::AddIOp::create(rewriter, loc, valid, offset)
1060 ->getResult(0);
1061 };
1062
1063 auto coverageFn = [&](int64_t i, Value isize) -> Value {
1064 Value strideVal =
1065 arith::ConstantIndexOp::create(rewriter, loc, stride[i - 1]);
1066 Value val =
1067 arith::ConstantIndexOp::create(rewriter, loc, kernel[i - 1]);
1068
1069 // Find the position relative to the input tensor's ends.
1070 Value left = linalg::IndexOp::create(rewriter, loc, i);
1071 Value right = arith::SubIOp::create(rewriter, loc, isize, left);
1072 left = arith::MulIOp::create(rewriter, loc, left, strideVal);
1073 right = arith::MulIOp::create(rewriter, loc, right, strideVal);
1074
1075 // Determine how much padding was included.
1076 val = padFn(val, left, pad[i * 2]);
1077 val = padFn(val, right, pad[i * 2 + 1]);
1078 return arith::MaxSIOp::create(rewriter, loc, one, val);
1079 };
1080
1081 // Compute the indices from either end.
1082 Value kH3 = coverageFn(1, iH);
1083 Value kW3 = coverageFn(2, iW);
1084
1085 // Compute the total number of elements and normalize.
1086 auto count = arith::IndexCastOp::create(
1087 rewriter, loc, rewriter.getI32Type(),
1088 arith::MulIOp::create(rewriter, loc, kH3, kW3));
1089
1090 // Divide by the number of summed values. For floats this is just
1091 // a div however for quantized values input normalization had
1092 // to be applied.
1093 Value poolVal = args[0];
1094 if (isa<FloatType>(accETy)) {
1095 auto countF = arith::SIToFPOp::create(rewriter, loc, accETy, count);
1096 poolVal = arith::DivFOp::create(rewriter, loc, poolVal, countF)
1097 ->getResult(0);
1098 if (accETy.getIntOrFloatBitWidth() >
1099 resultETy.getIntOrFloatBitWidth())
1100 poolVal =
1101 arith::TruncFOp::create(rewriter, loc, resultETy, poolVal);
1102 } else {
1103
1104 // If we have quantization information we need to apply an offset
1105 // for the input zp value.
1106 if (inputZpVal != 0) {
1107 auto inputZp = arith::ConstantOp::create(
1108 rewriter, loc, b.getIntegerAttr(accETy, inputZpVal));
1109 Value offset =
1110 arith::MulIOp::create(rewriter, loc, accETy, count, inputZp);
1111 poolVal =
1112 arith::SubIOp::create(rewriter, loc, accETy, poolVal, offset);
1113 }
1114
1115 // Compute: k = 32 - count_leading_zeros(value - 1)
1116 Value one32 = arith::ConstantOp::create(
1117 rewriter, loc, rewriter.getI32IntegerAttr(1));
1118 Value thirtyTwo32 = arith::ConstantOp::create(
1119 rewriter, loc, rewriter.getI32IntegerAttr(32));
1120
1121 Value countSubOne =
1122 arith::SubIOp::create(rewriter, loc, count, one32);
1123 Value leadingZeros =
1124 math::CountLeadingZerosOp::create(rewriter, loc, countSubOne);
1125 Value k =
1126 arith::SubIOp::create(rewriter, loc, thirtyTwo32, leadingZeros);
1127
1128 // Compute: numerator = ((1 << 30) + 1) << k
1129 Value k64 =
1130 arith::ExtUIOp::create(rewriter, loc, rewriter.getI64Type(), k);
1131 Value thirtyShiftPlusOne = arith::ConstantOp::create(
1132 rewriter, loc, rewriter.getI64IntegerAttr((1 << 30) + 1));
1133 Value numerator =
1134 arith::ShLIOp::create(rewriter, loc, thirtyShiftPlusOne, k64);
1135
1136 // Compute: scale.multiplier = numerator / value;
1137 Value count64 = arith::ExtUIOp::create(
1138 rewriter, loc, rewriter.getI64Type(), count);
1139 Value multiplier =
1140 arith::DivUIOp::create(rewriter, loc, numerator, count64);
1141 multiplier = arith::TruncIOp::create(
1142 rewriter, loc, rewriter.getI32Type(), multiplier);
1143
1144 // Compute: scale.shift = 30 + k
1145 Value k8 =
1146 arith::TruncIOp::create(rewriter, loc, rewriter.getI8Type(), k);
1147 Value thirty8 = arith::ConstantOp::create(
1148 rewriter, loc, rewriter.getI8IntegerAttr(30));
1149 Value shift = arith::AddIOp::create(rewriter, loc, k8, thirty8);
1150
1151 auto roundingAttr = RoundingModeAttr::get(
1152 rewriter.getContext(), RoundingMode::SINGLE_ROUND);
1153
1154 auto scaled = tosa::ApplyScaleOp::create(
1155 rewriter, loc, rewriter.getI32Type(), poolVal,
1156 multiplier, shift, roundingAttr)
1157 .getResult();
1158
1159 // If we have quantization information we need to apply output
1160 // zeropoint.
1161 if (outputZpVal != 0) {
1162 auto outputZp = arith::ConstantOp::create(
1163 rewriter, loc,
1164 b.getIntegerAttr(scaled.getType(), outputZpVal));
1165 scaled = arith::AddIOp::create(rewriter, loc, scaled, outputZp)
1166 .getResult();
1167 }
1168
1169 // Apply Clip.
1170 int64_t outBitwidth = resultETy.getIntOrFloatBitWidth();
1171
1173 rewriter, loc, accETy,
1174 APInt::getSignedMinValue(outBitwidth).getSExtValue());
1176 rewriter, loc, accETy,
1177 APInt::getSignedMaxValue(outBitwidth).getSExtValue());
1178 auto clamp = clampIntHelper(loc, scaled, min, max, rewriter,
1179 /*isUnsigned=*/false);
1180
1181 poolVal = clamp;
1182 // Convert type.
1183 if (resultETy != clamp.getType()) {
1184 poolVal =
1185 arith::TruncIOp::create(rewriter, loc, resultETy, poolVal);
1186 }
1187 }
1188
1189 linalg::YieldOp::create(rewriter, loc, poolVal);
1190 });
1191
1192 rewriter.replaceOp(op, genericOp.getResult(0));
1193 return success();
1194 }
1195};
1196
1197class TransposeConverter : public OpRewritePattern<tosa::TransposeOp> {
1198public:
1199 using OpRewritePattern<tosa::TransposeOp>::OpRewritePattern;
1200
1201 LogicalResult matchAndRewrite(tosa::TransposeOp op,
1202 PatternRewriter &rewriter) const final {
1203 const llvm::ArrayRef<int32_t> constantPerms = op.getPerms();
1204
1205 Location loc = op.getLoc();
1206 // The verifier should have made sure we have a valid TOSA permutation
1207 // tensor. isPermutationVector doesn't actually check the TOSA perms we
1208 // expect.
1209 SmallVector<OpFoldResult> inputSizes =
1210 tensor::getMixedSizes(rewriter, loc, op.getInput1());
1211 auto permutedSizes =
1212 applyTOSAPermutation<OpFoldResult>(inputSizes, constantPerms);
1213
1214 auto permutedInit =
1215 tensor::EmptyOp::create(rewriter, loc, permutedSizes,
1216 op.getInput1().getType().getElementType());
1217 rewriter.replaceOpWithNewOp<linalg::TransposeOp>(
1218 op, op.getInput1(), permutedInit,
1219 llvm::map_to_vector(constantPerms,
1220 [](int32_t v) -> int64_t { return v; }));
1221 return success();
1222 }
1223};
1224} // namespace
1225
1227 const TypeConverter &converter, RewritePatternSet *patterns,
1229 if (options.preferConv2DKernelLayoutHWCF) {
1230 patterns->add<ConvConverter<tosa::Conv2DOp, linalg::Conv2DNhwcHwcfOp,
1231 linalg::Conv2DNhwcHwcfQOp>>(
1232 patterns->getContext());
1233 } else {
1234 patterns->add<ConvConverter<tosa::Conv2DOp, linalg::Conv2DNhwcFhwcOp,
1235 linalg::Conv2DNhwcFhwcQOp>>(
1236 patterns->getContext());
1237 }
1238 patterns->add<
1239 // clang-format off
1240 ConvConverter<tosa::Conv3DOp, linalg::Conv3DNdhwcDhwcfOp, linalg::Conv3DNdhwcDhwcfQOp>,
1241 DepthwiseConvConverter,
1242 MatMulConverter,
1243 MatMulTConverter,
1244 AvgPool2dConverter,
1245 TransposeConverter
1246 >(patterns->getContext());
1247
1248 patterns->add<
1249 MaxPool2dConverter
1250 >(converter, patterns->getContext(), options.allowNonFinites);
1251 // clang-format on
1252}
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)
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
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
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 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 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:389
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={})
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:734
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)
APFloat getFloatMinMaxIdentity(const llvm::fltSemantics &semantics, bool negative, bool allowNonFinites)
MatMulTOperands prepareMatMulTOperands(PatternRewriter &rewriter, Location loc, Value a, Value b, RankedTensorType outputType)
Broadcast the batch dimensions of the ranked operands and create a zero-filled result tensor for eith...
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.
Include the generated interface declarations.
detail::DenseArrayAttrImpl< int64_t > DenseI64ArrayAttr
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...