Skip to content

Commit 53e904c

Browse files
authored
[TorchToTosa] Fix i32 and i64 aten.clamp lowering (#4576)
Tosa clamp does not support i32 and i64 operands so lowering aten.clamp directly to tosa.clamp results in invalid tosa for these values. This PR updates the lowering for scalar aten.clamp `AtenClampOp` so i8 and i16 operands continue to use tosa.clamp, while i32 and i64 are lowered as `y = min(max(x, min_value), max_value)`, in the same way as the existing lowering of the tensor aten.clamp `AtenClampTensorOp`
1 parent 24e93b0 commit 53e904c

4 files changed

Lines changed: 420 additions & 89 deletions

File tree

lib/Conversion/TorchToTosa/TorchToTosa.cpp

Lines changed: 164 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -5991,13 +5991,93 @@ LogicalResult ConvertAtenOp<AtenIscloseOp>::matchAndRewriteImpl(
59915991
return success();
59925992
}
59935993

5994+
static LogicalResult
5995+
rewriteClampAsMinimumMaximumOp(Operation *op, TensorType resultType, Value self,
5996+
Value min, Value max,
5997+
ConversionPatternRewriter &rewriter) {
5998+
if (mlir::tosa::EqualizeRanks(rewriter, op->getLoc(), self, min).failed())
5999+
return rewriter.notifyMatchFailure(op, "failed to equalize self and min");
6000+
6001+
auto selfType = cast<RankedTensorType>(self.getType());
6002+
auto minType = cast<RankedTensorType>(min.getType());
6003+
6004+
self = tosa::tosaCastTensorToType(rewriter, self,
6005+
selfType.clone(resultType.getElementType()))
6006+
.value();
6007+
min = tosa::tosaCastTensorToType(rewriter, min,
6008+
minType.clone(resultType.getElementType()))
6009+
.value();
6010+
6011+
auto maxRank = cast<RankedTensorType>(self.getType()).getRank();
6012+
auto dynamicIntermediateType =
6013+
RankedTensorType::get(SmallVector<int64_t>(maxRank, ShapedType::kDynamic),
6014+
resultType.getElementType());
6015+
6016+
// max(xi, min_valuei)
6017+
// Use default NaN Propagation mode "PROPAGATE" for tosa.maximum
6018+
auto minThresholdCheck = tosa::CreateOpAndInfer<tosa::MaximumOp>(
6019+
rewriter, op->getLoc(), dynamicIntermediateType, self, min,
6020+
tosa::NanPropagationModeAttr::get(rewriter.getContext(),
6021+
tosa::NanPropagationMode::PROPAGATE));
6022+
6023+
Value tmp = minThresholdCheck.getResult();
6024+
6025+
if (mlir::tosa::EqualizeRanks(rewriter, op->getLoc(), tmp, max).failed())
6026+
return rewriter.notifyMatchFailure(
6027+
op, "failed to equalize intermediate and max");
6028+
6029+
auto maxType = cast<RankedTensorType>(max.getType());
6030+
max = tosa::tosaCastTensorToType(rewriter, max,
6031+
maxType.clone(resultType.getElementType()))
6032+
.value();
6033+
6034+
// yi = min(max(xi, min_valuei), max_valuei)
6035+
// Use default NaN Propagation mode "PROPAGATE" for tosa.minimum
6036+
auto result = tosa::CreateOpAndInfer<tosa::MinimumOp>(
6037+
rewriter, op->getLoc(), resultType, tmp, max,
6038+
tosa::NanPropagationModeAttr::get(rewriter.getContext(),
6039+
tosa::NanPropagationMode::PROPAGATE));
6040+
6041+
rewriter.replaceOp(op, result);
6042+
return success();
6043+
}
6044+
6045+
static LogicalResult validateClampBoundsInValidRange(
6046+
ConversionPatternRewriter &rewriter, Operation *op, IntegerType intType,
6047+
std::optional<int64_t> minInt, std::optional<int64_t> maxInt) {
6048+
auto isOutOfRange = [&](int64_t value) {
6049+
switch (intType.getWidth()) {
6050+
case 8:
6051+
return value < std::numeric_limits<int8_t>::min() ||
6052+
value > std::numeric_limits<int8_t>::max();
6053+
case 16:
6054+
return value < std::numeric_limits<int16_t>::min() ||
6055+
value > std::numeric_limits<int16_t>::max();
6056+
case 32:
6057+
return value < std::numeric_limits<int32_t>::min() ||
6058+
value > std::numeric_limits<int32_t>::max();
6059+
case 64:
6060+
return false;
6061+
default:
6062+
return true;
6063+
}
6064+
};
6065+
6066+
if ((minInt && isOutOfRange(*minInt)) || (maxInt && isOutOfRange(*maxInt))) {
6067+
return rewriter.notifyMatchFailure(
6068+
op, "explicit clamp bound is not representable in result integer type");
6069+
}
6070+
6071+
return success();
6072+
}
6073+
59946074
template <>
59956075
LogicalResult ConvertAtenOp<AtenClampOp>::matchAndRewriteImpl(
59966076
AtenClampOp op, OpAdaptor adaptor,
59976077
ConversionPatternRewriter &rewriter) const {
5998-
6078+
auto self = adaptor.getSelf();
59996079
// Not a tensor type.
6000-
auto selfType = dyn_cast<TensorType>(adaptor.getSelf().getType());
6080+
auto selfType = dyn_cast<TensorType>(self.getType());
60016081
if (!selfType)
60026082
return rewriter.notifyMatchFailure(
60036083
op, "only tensor types input are currently supported");
@@ -6013,7 +6093,6 @@ LogicalResult ConvertAtenOp<AtenClampOp>::matchAndRewriteImpl(
60136093
return rewriter.notifyMatchFailure(
60146094
op, "only tensor types output are currently supported");
60156095
auto outElemTy = outType.getElementType();
6016-
Value self = adaptor.getSelf();
60176096
if (selfType != outType) {
60186097
auto castedSelf = tosa::tosaCastTensorToType(rewriter, self, outType);
60196098
if (!castedSelf)
@@ -6055,34 +6134,76 @@ LogicalResult ConvertAtenOp<AtenClampOp>::matchAndRewriteImpl(
60556134
}
60566135
}
60576136

6058-
if (!isa<mlir::FloatType>(outElemTy)) {
6059-
IntegerAttr minIntAttr, maxIntAttr;
6060-
if (failed(tosa::getIntegerClampAttrs(rewriter, op, outElemTy, minInt,
6061-
maxInt, minIntAttr, maxIntAttr))) {
6062-
return failure();
6063-
}
6064-
6065-
rewriter.replaceOpWithNewOp<tosa::ClampOp>(
6066-
op, outType, self, minIntAttr, maxIntAttr,
6067-
/*nan_mode=*/
6068-
tosa::NanPropagationModeAttr::get(rewriter.getContext(),
6069-
tosa::NanPropagationMode::PROPAGATE));
6070-
} else {
6071-
FloatAttr minFloatAttr, maxFloatAttr;
6072-
if (failed(tosa::getFloatClampAttrs(rewriter, op, outElemTy, minFloat,
6073-
maxFloat, minFloatAttr,
6074-
maxFloatAttr))) {
6075-
return failure();
6076-
}
6137+
return TypeSwitch<Type, LogicalResult>(outElemTy)
6138+
.Case<mlir::IntegerType>([&](auto intType) -> LogicalResult {
6139+
if (failed(validateClampBoundsInValidRange(rewriter, op, intType,
6140+
minInt, maxInt)))
6141+
return failure();
60776142

6078-
rewriter.replaceOpWithNewOp<tosa::ClampOp>(
6079-
op, outType, self, minFloatAttr, maxFloatAttr,
6080-
/*nan_mode=*/
6081-
tosa::NanPropagationModeAttr::get(rewriter.getContext(),
6082-
tosa::NanPropagationMode::PROPAGATE));
6083-
}
6143+
IntegerAttr minIntAttr, maxIntAttr;
6144+
if (failed(tosa::getIntegerClampAttrs(rewriter, op, outElemTy, minInt,
6145+
maxInt, minIntAttr,
6146+
maxIntAttr))) {
6147+
return failure();
6148+
}
6149+
// tosa.clamp does not support integer tensors wider than 16 bits.
6150+
//
6151+
// We use the following formula for 32-bit and 64-bit integers:
6152+
// yi = min(max(xi, min_valuei), max_valuei)
6153+
switch (intType.getWidth()) {
6154+
case 8:
6155+
case 16:
6156+
if (minIntAttr.getInt() > maxIntAttr.getInt())
6157+
minIntAttr = maxIntAttr;
6158+
rewriter.replaceOpWithNewOp<tosa::ClampOp>(
6159+
op, outType, self, minIntAttr, maxIntAttr,
6160+
/*nan_mode=*/
6161+
tosa::NanPropagationModeAttr::get(
6162+
rewriter.getContext(), tosa::NanPropagationMode::PROPAGATE));
6163+
return success();
6164+
case 32: {
6165+
int32_t minValue = static_cast<int32_t>(minIntAttr.getInt());
6166+
int32_t maxValue = static_cast<int32_t>(maxIntAttr.getInt());
6167+
Value min =
6168+
tosa::getConstTensor<int32_t>(rewriter, op, minValue, {}).value();
6169+
Value max =
6170+
tosa::getConstTensor<int32_t>(rewriter, op, maxValue, {}).value();
6171+
return rewriteClampAsMinimumMaximumOp(op, outType, self, min, max,
6172+
rewriter);
6173+
}
6174+
case 64: {
6175+
int64_t minValue = static_cast<int64_t>(minIntAttr.getInt());
6176+
int64_t maxValue = static_cast<int64_t>(maxIntAttr.getInt());
6177+
Value min =
6178+
tosa::getConstTensor<int64_t>(rewriter, op, minValue, {}).value();
6179+
Value max =
6180+
tosa::getConstTensor<int64_t>(rewriter, op, maxValue, {}).value();
6181+
return rewriteClampAsMinimumMaximumOp(op, outType, self, min, max,
6182+
rewriter);
6183+
}
6184+
default:
6185+
return rewriter.notifyMatchFailure(op, "Unsupported integer width");
6186+
}
6187+
})
6188+
.Case<mlir::FloatType>([&](auto) -> LogicalResult {
6189+
FloatAttr minFloatAttr, maxFloatAttr;
6190+
if (failed(tosa::getFloatClampAttrs(rewriter, op, outElemTy, minFloat,
6191+
maxFloat, minFloatAttr,
6192+
maxFloatAttr))) {
6193+
return failure();
6194+
}
60846195

6085-
return success();
6196+
rewriter.replaceOpWithNewOp<tosa::ClampOp>(
6197+
op, outType, self, minFloatAttr, maxFloatAttr,
6198+
/*nan_mode=*/
6199+
tosa::NanPropagationModeAttr::get(
6200+
rewriter.getContext(), tosa::NanPropagationMode::PROPAGATE));
6201+
return success();
6202+
})
6203+
.Default([&](Type) -> LogicalResult {
6204+
return rewriter.notifyMatchFailure(op,
6205+
"unsupported clamp element type");
6206+
});
60866207
}
60876208

60886209
// Legalization for aten.clamp.Tensor
@@ -6106,6 +6227,8 @@ LogicalResult ConvertAtenOp<AtenClampTensorOp>::matchAndRewriteImpl(
61066227

61076228
auto resultType =
61086229
dyn_cast<TensorType>(typeConverter->convertType(op.getType()));
6230+
if (!resultType)
6231+
return rewriter.notifyMatchFailure(op, "expected tensor result type");
61096232

61106233
// Get min tensor. If None, there is no lower bound.
61116234
Value min;
@@ -6126,6 +6249,11 @@ LogicalResult ConvertAtenOp<AtenClampTensorOp>::matchAndRewriteImpl(
61266249
return tosa::getConstTensor<int8_t>(
61276250
rewriter, op, std::numeric_limits<int8_t>::min(), {})
61286251
.value();
6252+
case 16:
6253+
return tosa::getConstTensor<int16_t>(
6254+
rewriter, op, std::numeric_limits<int16_t>::min(),
6255+
{})
6256+
.value();
61296257
case 32:
61306258
return tosa::getConstTensor<int32_t>(
61316259
rewriter, op, std::numeric_limits<int32_t>::min(),
@@ -6160,6 +6288,11 @@ LogicalResult ConvertAtenOp<AtenClampTensorOp>::matchAndRewriteImpl(
61606288
return tosa::getConstTensor<int8_t>(
61616289
rewriter, op, std::numeric_limits<int8_t>::max(), {})
61626290
.value();
6291+
case 16:
6292+
return tosa::getConstTensor<int16_t>(
6293+
rewriter, op, std::numeric_limits<int16_t>::max(),
6294+
{})
6295+
.value();
61636296
case 32:
61646297
return tosa::getConstTensor<int32_t>(
61656298
rewriter, op, std::numeric_limits<int32_t>::max(),
@@ -6175,33 +6308,8 @@ LogicalResult ConvertAtenOp<AtenClampTensorOp>::matchAndRewriteImpl(
61756308
});
61766309
}
61776310

6178-
if (mlir::tosa::EqualizeRanks(rewriter, op->getLoc(), self, min).failed() ||
6179-
mlir::tosa::EqualizeRanks(rewriter, op->getLoc(), self, max).failed())
6180-
return rewriter.notifyMatchFailure(
6181-
op, "Failed to equalize ranks among operands and result");
6182-
6183-
self = tosa::tosaCastTensorToType(rewriter, self, resultType).value();
6184-
min = tosa::tosaCastTensorToType(rewriter, min, resultType).value();
6185-
max = tosa::tosaCastTensorToType(rewriter, max, resultType).value();
6186-
6187-
// max(xi, min_valuei)
6188-
// Use default NaN Propagation mode "PROPAGATE" for tosa.maximum
6189-
auto minThresholdCheck = tosa::MaximumOp::create(
6190-
rewriter, op->getLoc(), resultType, self, min,
6191-
/*nan_mode=*/
6192-
tosa::NanPropagationModeAttr::get(rewriter.getContext(),
6193-
tosa::NanPropagationMode::PROPAGATE));
6194-
6195-
// yi = min(max(xi, min_valuei), max_valuei)
6196-
// Use default NaN Propagation mode "PROPAGATE" for tosa.minimum
6197-
auto result = tosa::MinimumOp::create(
6198-
rewriter, op->getLoc(), resultType, minThresholdCheck, max,
6199-
/*nan_mode=*/
6200-
tosa::NanPropagationModeAttr::get(rewriter.getContext(),
6201-
tosa::NanPropagationMode::PROPAGATE));
6202-
6203-
rewriter.replaceOp(op, result);
6204-
return success();
6311+
return rewriteClampAsMinimumMaximumOp(op, resultType, self, min, max,
6312+
rewriter);
62056313
}
62066314

62076315
template <>

projects/pt1/e2e_testing/xfail_sets.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"ElementwiseAtanTensorBFloat16SpecialValuesModule_basic",
2727
"ElementwiseFloatTensorGtIntTensorModule_basic",
2828
"ElementwiseClampIntToFloatModule_basic",
29+
"ElementwiseClampInt16Module_basic",
2930
# TODO: The values are extremely close to the golden values, but the test fails because of strict rtol/atol.
3031
"AtenInstanceNormModuleFp16_basic",
3132
"AtenIntMM_basic",
@@ -518,6 +519,7 @@
518519
"ReflectionPad3dModuleFront_basic",
519520
"ReflectionPad3dModuleBack_basic",
520521
"ElementwiseClampIntToFloatModule_basic",
522+
"ElementwiseClampInt16Module_basic",
521523
# error: argument must be a memref of f32, f64, i32, i64, i8, i1, c32, c64, but got 'memref<3x5xbf16>'
522524
"ElementwiseClampMaxModule_bfloat16",
523525
"ElementwiseClampMinModule_bfloat16",
@@ -554,6 +556,7 @@
554556
"DiagonalWithStaticShapeModule_basic",
555557
"EinsumStaticDiagonalDimensionModule_basic",
556558
"ElementwiseAtanTensorBFloat16SpecialValuesModule_basic",
559+
"ElementwiseClampInt16Module_basic",
557560
"ElementwiseClampIntToFloatModule_basic",
558561
"ElementwiseRemainderScalarModule_Bool_NegativeDivisor_basic",
559562
"ElementwiseRemainderScalarModule_Float_NegativeDividend_basic",
@@ -2249,9 +2252,12 @@
22492252
"ElementwiseCeluModule_basic",
22502253
"ElementwiseCeluStaticModule_basic",
22512254
"ElementwiseClampMaxModule_basic",
2255+
"ElementwiseClampInt8MinGreaterThanMaxModule_basic",
22522256
"ElementwiseClampMinModule_basic",
22532257
"ElementwiseClampModule_basic",
22542258
"ElementwiseClampTensorInt8Module_basic",
2259+
"ElementwiseClampInt32Module_basic",
2260+
"ElementwiseClampInt64Module_basic",
22552261
"ElementwiseCloneChannelsLastMemoryFormatModule_basic",
22562262
"ElementwiseCloneContiguousModule_basic",
22572263
"ElementwiseCloneModule_basic",
@@ -3550,6 +3556,7 @@
35503556
ONNX_XFAIL_SET = ONNX_XFAIL_SET | {
35513557
"Aten_CastLongModule_basic",
35523558
"Aten_CastFloatModule_basic",
3559+
"ElementwiseClampInt16Module_basic",
35533560
}
35543561

35553562
if torch_version_for_comparison() < version.parse("2.4.0.dev"):
@@ -4056,6 +4063,7 @@
40564063
"ElementwiseClampMinModule_bfloat16",
40574064
"ElementwiseClampModule_bfloat16",
40584065
"ElementwiseReluModule_bfloat16",
4066+
"ElementwiseClampInt16Module_basic", # 'memref<3x5xi16>'
40594067
}
40604068

40614069
ONNX_TOSA_CRASHING_SET = {
@@ -5195,4 +5203,5 @@
51955203
# error: 'memref.cast' op operand type 'memref<2x6x4x3xf32>' and result type 'memref<2x6x5x3xf32>' are cast incompatible
51965204
# torch.onnx.export produces onnx.MaxPool op with incorrect output shape of 2x6x5x3 instead of 2x6x4x3
51975205
"MaxPool2dStaticCeilModeTrueReduceOutputModule_basic",
5206+
"ElementwiseClampInt16Module_basic",
51985207
}

0 commit comments

Comments
 (0)