[Mlir-commits] [mlir] [mlir][tosa] Add allow-non-finites option for float min/max reduction (PR #222140)
Hariprasad Ravishankar
llvmlistbot at llvm.org
Tue Sep 8 13:55:50 PDT 2026
https://github.com/hariprasadravi updated https://github.com/llvm/llvm-project/pull/222140
>From 11901f3aa4e20a6454ab83afd22c5df5d50dfe11 Mon Sep 17 00:00:00 2001
From: Hariprasad Ravishankar
<hravisha at ls-01-1c-a0-b8-77-e7-5d.dhcp.mathworks.com>
Date: Tue, 8 Sep 2026 16:21:47 -0400
Subject: [PATCH] [mlir][tosa] Add allow-non-finites option for float min/max
reduction identity
TosaToLinalg seeds float min/max reductions with the largest finite value
of the element type, but the TOSA spec asks for the infinite bound.
Section 2.9.4 seeds REDUCE_MIN with maximum_s<in_out_t>(), 4.5.1 defines
that as the type maximum after make_signed, 4.5.2 says make_signed is a
no-op for floats, and Table 5 in 1.9 gives that maximum as +infinity for
fp16, bf16, fp32 and fp64. REDUCE_MAX and ARGMAX are the mirror image.
The practical symptom is that the seed clamps: reducing a tensor that is
all +Inf returns 3.40282347E+38.
Add an allow-non-finites option to tosa-to-linalg that selects the
conforming seed, reusing the name and meaning of the option added to
ConvertTorchToLinalg in llvm/torch-mlir#4363. Without it, a graph routed
through ConvertTorchToTosa gets the finite seed regardless of what the
frontend asked for, so the two backends disagree on the same model.
The default is false, which keeps the current output of this pass. That
mirrors torch-mlir, where true was the no-op default because it already
emitted -inf; in both cases the default preserves existing behaviour and
setting the flag consistently makes the two paths agree.
The infinite seed is only used when APFloat::semanticsHasInf() also
holds. IEEEFloat::makeInf is llvm_unreachable under FiniteOnly semantics
and returns a NaN under NanOnly semantics such as f8E4M3FN, which would
poison the reduction through NaN-propagating arith.minimumf.
ARGMAX is updated for consistency but is NFC in effect, since its
running max only feeds the UGT/OGT predicate and its value output is
discarded.
max_pool2d in TosaToLinalgNamed.cpp is wrong the same way but is left
alone here: its initial value doubles as the tensor.pad padding value,
which needs separate review.
Co-authored-by: Claude Opus 5 <noreply at anthropic.com>
---
mlir/include/mlir/Conversion/Passes.td | 9 +-
.../Conversion/TosaToLinalg/TosaToLinalg.h | 8 +-
.../Conversion/TosaToLinalg/TosaToLinalg.cpp | 80 +++++---
.../TosaToLinalg/TosaToLinalgPass.cpp | 15 +-
.../TosaToLinalg/tosa-to-linalg.mlir | 177 +++++++++++++++++-
5 files changed, 254 insertions(+), 35 deletions(-)
diff --git a/mlir/include/mlir/Conversion/Passes.td b/mlir/include/mlir/Conversion/Passes.td
index f13cb9a801139..24c264c11a816 100644
--- a/mlir/include/mlir/Conversion/Passes.td
+++ b/mlir/include/mlir/Conversion/Passes.td
@@ -1421,7 +1421,14 @@ def TosaToLinalg
"Disable tosa decompositions pass">,
Option<"aggressiveReduceConstant", "aggressive-reduce-constant",
"bool", /*default=*/"false",
- "Always perform the reduce constant optimization">
+ "Always perform the reduce constant optimization">,
+ Option<"allowNonFinites", "allow-non-finites", "bool",
+ /*default=*/"false",
+ "When enabled, float min/max reductions are seeded with the "
+ "infinite identity mandated by the TOSA specification. When "
+ "disabled (default), the largest finite value is used instead. "
+ "This controls only compiler-generated identity values; NaN "
+ "results required by nan_mode are unaffected.">
];
}
diff --git a/mlir/include/mlir/Conversion/TosaToLinalg/TosaToLinalg.h b/mlir/include/mlir/Conversion/TosaToLinalg/TosaToLinalg.h
index c1d28528a2f90..06914be338aaf 100644
--- a/mlir/include/mlir/Conversion/TosaToLinalg/TosaToLinalg.h
+++ b/mlir/include/mlir/Conversion/TosaToLinalg/TosaToLinalg.h
@@ -24,7 +24,8 @@ namespace mlir {
namespace tosa {
-std::unique_ptr<Pass> createTosaToLinalg();
+std::unique_ptr<Pass>
+createTosaToLinalg(const TosaToLinalgOptions &options = TosaToLinalgOptions());
std::unique_ptr<Pass> createTosaToLinalgNamed(
const TosaToLinalgNamedOptions &options = TosaToLinalgNamedOptions());
@@ -46,8 +47,9 @@ void addTosaToLinalgPasses(
void registerTosaToLinalgPipelines();
/// Populates conversion passes from TOSA dialect to Linalg dialect.
-void populateTosaToLinalgConversionPatterns(const TypeConverter &converter,
- RewritePatternSet *patterns);
+void populateTosaToLinalgConversionPatterns(
+ const TypeConverter &converter, RewritePatternSet *patterns,
+ const TosaToLinalgOptions &options = TosaToLinalgOptions());
/// Populates conversion passes from TOSA dialect to Linalg named operations.
void populateTosaToLinalgNamedConversionPatterns(
diff --git a/mlir/lib/Conversion/TosaToLinalg/TosaToLinalg.cpp b/mlir/lib/Conversion/TosaToLinalg/TosaToLinalg.cpp
index b7eb0a3aed546..300ce91819e19 100644
--- a/mlir/lib/Conversion/TosaToLinalg/TosaToLinalg.cpp
+++ b/mlir/lib/Conversion/TosaToLinalg/TosaToLinalg.cpp
@@ -1056,10 +1056,26 @@ elementwiseMatchAndRewriteHelper(Operation *operation, ValueRange operands,
targetShape, converter);
}
+// Returns the identity value to seed a float min/max reduction with. TOSA seeds
+// REDUCE_MIN with maximum_s<in_out_t>() and REDUCE_MAX/ARGMAX with
+// minimum_s<in_out_t>(), and for floating-point types those bounds are
+// +/-infinity rather than the largest finite value. Only use them when the
+// caller opted in *and* the format can represent them: APFloat::getInf() is
+// unreachable for FiniteOnly semantics and silently returns a NaN for NanOnly
+// semantics such as f8E4M3FN, which would poison the whole reduction through
+// NaN-propagating arith.minimumf/arith.maximumf.
+static APFloat getFloatMinMaxIdentity(const llvm::fltSemantics &semantics,
+ bool negative, bool allowNonFinites) {
+ if (allowNonFinites && APFloat::semanticsHasInf(semantics))
+ return APFloat::getInf(semantics, negative);
+ return APFloat::getLargest(semantics, negative);
+}
+
// Returns the constant initial value for a given reduction operation. The
// attribute type varies depending on the element type required.
static TypedAttr createInitialValueForReduceOp(Operation *op, Type elementTy,
- PatternRewriter &rewriter) {
+ PatternRewriter &rewriter,
+ bool allowNonFinites) {
if (isa<tosa::ReduceSumOp>(op) && isa<FloatType>(elementTy))
return rewriter.getFloatAttr(elementTy, 0.0);
@@ -1074,8 +1090,9 @@ static TypedAttr createInitialValueForReduceOp(Operation *op, Type elementTy,
if (isa<tosa::ReduceMinOp>(op) && isa<FloatType>(elementTy))
return rewriter.getFloatAttr(
- elementTy, APFloat::getLargest(
- cast<FloatType>(elementTy).getFloatSemantics(), false));
+ elementTy,
+ getFloatMinMaxIdentity(cast<FloatType>(elementTy).getFloatSemantics(),
+ /*negative=*/false, allowNonFinites));
if (isa<tosa::ReduceMinOp>(op) && isa<IntegerType>(elementTy))
return rewriter.getIntegerAttr(
@@ -1083,8 +1100,9 @@ static TypedAttr createInitialValueForReduceOp(Operation *op, Type elementTy,
if (isa<tosa::ReduceMaxOp>(op) && isa<FloatType>(elementTy))
return rewriter.getFloatAttr(
- elementTy, APFloat::getLargest(
- cast<FloatType>(elementTy).getFloatSemantics(), true));
+ elementTy,
+ getFloatMinMaxIdentity(cast<FloatType>(elementTy).getFloatSemantics(),
+ /*negative=*/true, allowNonFinites));
if (isa<tosa::ReduceMaxOp>(op) && isa<IntegerType>(elementTy))
return rewriter.getIntegerAttr(
@@ -1098,8 +1116,9 @@ static TypedAttr createInitialValueForReduceOp(Operation *op, Type elementTy,
if (isa<tosa::ArgMaxOp>(op) && isa<FloatType>(elementTy))
return rewriter.getFloatAttr(
- elementTy, APFloat::getLargest(
- cast<FloatType>(elementTy).getFloatSemantics(), true));
+ elementTy,
+ getFloatMinMaxIdentity(cast<FloatType>(elementTy).getFloatSemantics(),
+ /*negative=*/true, allowNonFinites));
if (isa<tosa::ArgMaxOp>(op) && isa<IntegerType>(elementTy))
return rewriter.getIntegerAttr(
@@ -1161,7 +1180,8 @@ static Value createLinalgBodyCalculationForReduceOp(Operation *op,
// that reduces across the specified axis.
template <typename OpTy>
static LogicalResult reduceMatchAndRewriteHelper(OpTy op, uint64_t axis,
- PatternRewriter &rewriter) {
+ PatternRewriter &rewriter,
+ bool allowNonFinites) {
auto loc = op->getLoc();
auto inputTy = dyn_cast<RankedTensorType>(op->getOperand(0).getType());
auto resultTy = dyn_cast<RankedTensorType>(op->getResult(0).getType());
@@ -1195,7 +1215,8 @@ static LogicalResult reduceMatchAndRewriteHelper(OpTy op, uint64_t axis,
tensor::EmptyOp::create(rewriter, loc, reduceShape, accTy, dynDims)
.getResult();
- auto fillValueAttr = createInitialValueForReduceOp(op, accTy, rewriter);
+ auto fillValueAttr =
+ createInitialValueForReduceOp(op, accTy, rewriter, allowNonFinites);
if (!fillValueAttr)
return rewriter.notifyMatchFailure(
op, "No initial value found for reduction operation");
@@ -2238,12 +2259,17 @@ class IdentityNConverter : public OpRewritePattern<SrcOp> {
template <typename SrcOp>
class ReduceConverter : public OpRewritePattern<SrcOp> {
public:
- using OpRewritePattern<SrcOp>::OpRewritePattern;
+ ReduceConverter(MLIRContext *context, bool allowNonFinites)
+ : OpRewritePattern<SrcOp>(context), allowNonFinites(allowNonFinites) {}
LogicalResult matchAndRewrite(SrcOp reduceOp,
PatternRewriter &rewriter) const final {
- return reduceMatchAndRewriteHelper(reduceOp, reduceOp.getAxis(), rewriter);
+ return reduceMatchAndRewriteHelper(reduceOp, reduceOp.getAxis(), rewriter,
+ allowNonFinites);
}
+
+private:
+ bool allowNonFinites;
};
class ReverseConverter : public OpRewritePattern<tosa::ReverseOp> {
@@ -2387,7 +2413,9 @@ struct TileConverter : public OpConversionPattern<tosa::TileOp> {
// current value exceeds the running max.
class ArgMaxConverter : public OpRewritePattern<tosa::ArgMaxOp> {
public:
- using OpRewritePattern<tosa::ArgMaxOp>::OpRewritePattern;
+ ArgMaxConverter(MLIRContext *context, bool allowNonFinites)
+ : OpRewritePattern<tosa::ArgMaxOp>(context),
+ allowNonFinites(allowNonFinites) {}
LogicalResult matchAndRewrite(tosa::ArgMaxOp argmaxOp,
PatternRewriter &rewriter) const final {
@@ -2429,8 +2457,8 @@ class ArgMaxConverter : public OpRewritePattern<tosa::ArgMaxOp> {
tensor::EmptyOp::create(rewriter, loc, resultTy.getShape(), inElementTy,
dynDims)
.getResult();
- auto fillValueMaxAttr =
- createInitialValueForReduceOp(argmaxOp, inElementTy, rewriter);
+ auto fillValueMaxAttr = createInitialValueForReduceOp(
+ argmaxOp, inElementTy, rewriter, allowNonFinites);
if (!fillValueMaxAttr)
return rewriter.notifyMatchFailure(
@@ -2518,6 +2546,9 @@ class ArgMaxConverter : public OpRewritePattern<tosa::ArgMaxOp> {
rewriter.replaceOp(argmaxOp, linalgOp.getResult(0));
return success();
}
+
+private:
+ bool allowNonFinites;
};
class GatherConverter : public OpConversionPattern<tosa::GatherOp> {
@@ -3061,7 +3092,8 @@ struct FFT2dConverter final : OpRewritePattern<FFT2dOp> {
} // namespace
void mlir::tosa::populateTosaToLinalgConversionPatterns(
- const TypeConverter &converter, RewritePatternSet *patterns) {
+ const TypeConverter &converter, RewritePatternSet *patterns,
+ const TosaToLinalgOptions &options) {
// We have multiple resize coverters to handle degenerate cases.
patterns->add<GenericResizeConverter>(patterns->getContext(),
@@ -3115,13 +3147,6 @@ void mlir::tosa::populateTosaToLinalgConversionPatterns(
patterns->add<
IdentityNConverter<tosa::IdentityOp>,
- ReduceConverter<tosa::ReduceAllOp>,
- ReduceConverter<tosa::ReduceAnyOp>,
- ReduceConverter<tosa::ReduceMinOp>,
- ReduceConverter<tosa::ReduceMaxOp>,
- ReduceConverter<tosa::ReduceSumOp>,
- ReduceConverter<tosa::ReduceProductOp>,
- ArgMaxConverter,
GatherConverter,
RescaleConverter,
ReverseConverter,
@@ -3129,5 +3154,16 @@ void mlir::tosa::populateTosaToLinalgConversionPatterns(
FFT2dConverter,
TableConverter,
TileConverter>(patterns->getContext());
+
+ // Reductions seeded with a float min/max identity need to know whether
+ // non-finite values are available on the target.
+ patterns->add<
+ ReduceConverter<tosa::ReduceAllOp>,
+ ReduceConverter<tosa::ReduceAnyOp>,
+ ReduceConverter<tosa::ReduceMinOp>,
+ ReduceConverter<tosa::ReduceMaxOp>,
+ ReduceConverter<tosa::ReduceSumOp>,
+ ReduceConverter<tosa::ReduceProductOp>,
+ ArgMaxConverter>(patterns->getContext(), options.allowNonFinites);
// clang-format on
}
diff --git a/mlir/lib/Conversion/TosaToLinalg/TosaToLinalgPass.cpp b/mlir/lib/Conversion/TosaToLinalg/TosaToLinalgPass.cpp
index ca09299619bdb..0ce1cd14ea7f1 100644
--- a/mlir/lib/Conversion/TosaToLinalg/TosaToLinalgPass.cpp
+++ b/mlir/lib/Conversion/TosaToLinalg/TosaToLinalgPass.cpp
@@ -38,6 +38,9 @@ using namespace mlir;
namespace {
struct TosaToLinalg : public impl::TosaToLinalgBase<TosaToLinalg> {
public:
+ TosaToLinalg(const TosaToLinalgOptions &options)
+ : impl::TosaToLinalgBase<TosaToLinalg>(options) {}
+
void getDependentDialects(DialectRegistry ®istry) const override {
registry
.insert<arith::ArithDialect, linalg::LinalgDialect, math::MathDialect,
@@ -68,15 +71,19 @@ struct TosaToLinalg : public impl::TosaToLinalgBase<TosaToLinalg> {
tosa::populateTosaTypeConversion(converter);
FunctionOpInterface func = getOperation();
- mlir::tosa::populateTosaToLinalgConversionPatterns(converter, &patterns);
+ TosaToLinalgOptions options;
+ options.allowNonFinites = allowNonFinites;
+ mlir::tosa::populateTosaToLinalgConversionPatterns(converter, &patterns,
+ options);
if (failed(applyFullConversion(func, target, std::move(patterns))))
signalPassFailure();
}
};
} // namespace
-std::unique_ptr<Pass> mlir::tosa::createTosaToLinalg() {
- return std::make_unique<TosaToLinalg>();
+std::unique_ptr<Pass>
+mlir::tosa::createTosaToLinalg(const TosaToLinalgOptions &options) {
+ return std::make_unique<TosaToLinalg>(options);
}
void mlir::tosa::addTosaToLinalgPasses(
@@ -114,7 +121,7 @@ void mlir::tosa::addTosaToLinalgPasses(
}
if (validationOptions)
pm.addPass(tosa::createTosaValidation(*validationOptions));
- pm.addNestedPass<func::FuncOp>(tosa::createTosaToLinalg());
+ pm.addNestedPass<func::FuncOp>(tosa::createTosaToLinalg(options));
}
//===----------------------------------------------------------------------===//
diff --git a/mlir/test/Conversion/TosaToLinalg/tosa-to-linalg.mlir b/mlir/test/Conversion/TosaToLinalg/tosa-to-linalg.mlir
index a803ee7d99153..811e6a2ebf914 100644
--- a/mlir/test/Conversion/TosaToLinalg/tosa-to-linalg.mlir
+++ b/mlir/test/Conversion/TosaToLinalg/tosa-to-linalg.mlir
@@ -1,4 +1,5 @@
-// RUN: mlir-opt --split-input-file -pass-pipeline="builtin.module(func.func(tosa-to-linalg))" %s -verify-diagnostics -o -| FileCheck %s
+// RUN: mlir-opt --split-input-file -pass-pipeline="builtin.module(func.func(tosa-to-linalg))" %s -verify-diagnostics -o -| FileCheck %s --check-prefixes=CHECK,FINITE
+// RUN: mlir-opt --split-input-file -pass-pipeline="builtin.module(func.func(tosa-to-linalg{allow-non-finites=true}))" %s -verify-diagnostics -o -| FileCheck %s --check-prefixes=CHECK,NONFINITE
// CHECK: #[[$MAP0:.*]] = affine_map<() -> ()>
@@ -1014,13 +1015,15 @@ func.func @reduce_float(%arg0: tensor<5x4xf32>) -> () {
// CHECK: arith.mulf
%2 = tosa.reduce_product %arg0 {axis = 0 : i32} : (tensor<5x4xf32>) -> tensor<1x4xf32>
- // CHECK: arith.constant 3.40282347E+38 : f32
+ // FINITE: arith.constant 3.40282347E+38 : f32
+ // NONFINITE: arith.constant 0x7F800000 : f32
// CHECK: linalg.fill
// CHECK: linalg.reduce
// CHECK: arith.minimumf
%3 = tosa.reduce_min %arg0 {axis = 0 : i32} : (tensor<5x4xf32>) -> tensor<1x4xf32>
- // CHECK: arith.constant -3.40282347E+38 : f32
+ // FINITE: arith.constant -3.40282347E+38 : f32
+ // NONFINITE: arith.constant 0xFF800000 : f32
// CHECK: linalg.fill
// CHECK: linalg.reduce
// CHECK: arith.maximumf
@@ -1100,7 +1103,8 @@ func.func @reduce_float_dyn_multiple(%arg0: tensor<?x?xf32>) -> () {
// CHECK: %[[C0:.+]] = arith.constant 0
// CHECK: %[[DYN:.+]] = tensor.dim %[[ARG0]], %[[C0]]
// CHECK: %[[INIT:.+]] = tensor.empty(%[[DYN]])
- // CHECK: %[[CMIN:.+]] = arith.constant -3.40282347E+38
+ // FINITE: %[[CMIN:.+]] = arith.constant -3.40282347E+38
+ // NONFINITE: %[[CMIN:.+]] = arith.constant 0xFF800000
// CHECK: %[[FILL:.+]] = linalg.fill ins(%[[CMIN]]{{.*}}outs(%[[INIT]]
// CHECK: %[[REDUCE:.+]] = linalg.reduce ins(%[[ARG0]] : tensor<?x?xf32>) outs(%[[FILL]] : tensor<?xf32>) dimensions = [1]
// CHECK: (%[[ARG1:.*]]: f32, %[[ARG2:.*]]: f32) {
@@ -1814,7 +1818,8 @@ func.func @argmax(%arg0 : tensor<3x2xi32>, %arg1 : tensor<6xf32>) -> () {
// CHECK: linalg.yield [[SELECT_IDX]], [[SELECT_VAL]]
%1 = tosa.argmax %arg0 { axis = 1 : i32} : (tensor<3x2xi32>) -> tensor<3xi32>
- // CHECK: arith.constant -3.40282347E+38 : f32
+ // FINITE: arith.constant -3.40282347E+38 : f32
+ // NONFINITE: arith.constant 0xFF800000 : f32
// CHECK: linalg.index
// CHECK: arith.index_cast
// CHECK: arith.cmpf ugt
@@ -2679,3 +2684,165 @@ func.func @negate_i64_no_noop_cast(%arg0: tensor<4xi64>, %in_zp: tensor<1xi64>,
%neg = tosa.negate %arg0, %in_zp, %out_zp : (tensor<4xi64>, tensor<1xi64>, tensor<1xi64>) -> tensor<4xi64>
return %neg : tensor<4xi64>
}
+
+// -----
+
+// The TOSA identity for a float REDUCE_MIN is maximum_s<in_out_t>(), which
+// Table 5 of the specification gives as +infinity for fp16_t/bf16_t/fp32_t.
+// CHECK-LABEL: @reduce_min_f16
+func.func @reduce_min_f16(%arg0: tensor<5x4xf16>) -> () {
+ // FINITE: arith.constant 6.550400e+04 : f16
+ // NONFINITE: arith.constant 0x7C00 : f16
+ // CHECK: linalg.fill
+ // CHECK: linalg.reduce
+ // CHECK: arith.minimumf
+ %0 = tosa.reduce_min %arg0 {axis = 0 : i32} : (tensor<5x4xf16>) -> tensor<1x4xf16>
+ return
+}
+
+// -----
+
+// CHECK-LABEL: @reduce_max_f16
+func.func @reduce_max_f16(%arg0: tensor<5x4xf16>) -> () {
+ // FINITE: arith.constant -6.550400e+04 : f16
+ // NONFINITE: arith.constant 0xFC00 : f16
+ // CHECK: linalg.fill
+ // CHECK: linalg.reduce
+ // CHECK: arith.maximumf
+ %0 = tosa.reduce_max %arg0 {axis = 0 : i32} : (tensor<5x4xf16>) -> tensor<1x4xf16>
+ return
+}
+
+// -----
+
+// CHECK-LABEL: @reduce_min_bf16
+func.func @reduce_min_bf16(%arg0: tensor<5x4xbf16>) -> () {
+ // FINITE: arith.constant 3.389530e+38 : bf16
+ // NONFINITE: arith.constant 0x7F80 : bf16
+ // CHECK: linalg.fill
+ // CHECK: linalg.reduce
+ // CHECK: arith.minimumf
+ %0 = tosa.reduce_min %arg0 {axis = 0 : i32} : (tensor<5x4xbf16>) -> tensor<1x4xbf16>
+ return
+}
+
+// -----
+
+// CHECK-LABEL: @reduce_max_bf16
+func.func @reduce_max_bf16(%arg0: tensor<5x4xbf16>) -> () {
+ // FINITE: arith.constant -3.389530e+38 : bf16
+ // NONFINITE: arith.constant 0xFF80 : bf16
+ // CHECK: linalg.fill
+ // CHECK: linalg.reduce
+ // CHECK: arith.maximumf
+ %0 = tosa.reduce_max %arg0 {axis = 0 : i32} : (tensor<5x4xbf16>) -> tensor<1x4xbf16>
+ return
+}
+
+// -----
+
+// CHECK-LABEL: @reduce_min_f64
+func.func @reduce_min_f64(%arg0: tensor<5x4xf64>) -> () {
+ // FINITE: arith.constant 1.7976931348623157E+308 : f64
+ // NONFINITE: arith.constant 0x7FF0000000000000 : f64
+ // CHECK: linalg.fill
+ // CHECK: linalg.reduce
+ // CHECK: arith.minimumf
+ %0 = tosa.reduce_min %arg0 {axis = 0 : i32} : (tensor<5x4xf64>) -> tensor<1x4xf64>
+ return
+}
+
+// -----
+
+// CHECK-LABEL: @reduce_max_f64
+func.func @reduce_max_f64(%arg0: tensor<5x4xf64>) -> () {
+ // FINITE: arith.constant -1.7976931348623157E+308 : f64
+ // NONFINITE: arith.constant 0xFFF0000000000000 : f64
+ // CHECK: linalg.fill
+ // CHECK: linalg.reduce
+ // CHECK: arith.maximumf
+ %0 = tosa.reduce_max %arg0 {axis = 0 : i32} : (tensor<5x4xf64>) -> tensor<1x4xf64>
+ return
+}
+
+// -----
+
+// f8E4M3FN has NanOnly semantics: it has no encoding for an infinity, and
+// APFloat::getInf() would silently hand back a NaN that arith.minimumf then
+// propagates through the whole reduction. Both configurations must therefore
+// keep the finite identity, so this is deliberately checked with a shared
+// prefix rather than a FINITE/NONFINITE pair.
+// CHECK-LABEL: @reduce_min_f8E4M3FN_stays_finite
+func.func @reduce_min_f8E4M3FN_stays_finite(%arg0: tensor<5x4xf8E4M3FN>) -> () {
+ // CHECK: arith.constant 4.480000e+02 : f8E4M3FN
+ // CHECK-NOT: arith.constant 0x
+ // CHECK: linalg.fill
+ // CHECK: linalg.reduce
+ // CHECK: arith.minimumf
+ %0 = tosa.reduce_min %arg0 {axis = 0 : i32} : (tensor<5x4xf8E4M3FN>) -> tensor<1x4xf8E4M3FN>
+ return
+}
+
+// -----
+
+// CHECK-LABEL: @reduce_max_f8E4M3FN_stays_finite
+func.func @reduce_max_f8E4M3FN_stays_finite(%arg0: tensor<5x4xf8E4M3FN>) -> () {
+ // CHECK: arith.constant -4.480000e+02 : f8E4M3FN
+ // CHECK-NOT: arith.constant 0x
+ // CHECK: linalg.fill
+ // CHECK: linalg.reduce
+ // CHECK: arith.maximumf
+ %0 = tosa.reduce_max %arg0 {axis = 0 : i32} : (tensor<5x4xf8E4M3FN>) -> tensor<1x4xf8E4M3FN>
+ return
+}
+
+// -----
+
+// The seed feeds the nan_mode = IGNORE path too, where it must remain a min/max
+// identity rather than a NaN: the accumulator only reaches the final select
+// unchanged when every lane was NaN, and that select overwrites it with a NaN.
+// CHECK-LABEL: @reduce_min_nan_ignore_seed
+func.func @reduce_min_nan_ignore_seed(%arg0: tensor<5x4xf32>) -> () {
+ // FINITE: arith.constant 3.40282347E+38 : f32
+ // NONFINITE: arith.constant 0x7F800000 : f32
+ // CHECK: linalg.fill
+ // CHECK: linalg.reduce
+ // CHECK: arith.minimumf
+ // CHECK: arith.cmpf uno
+ // CHECK: arith.select
+ // CHECK: arith.andi
+ // CHECK: arith.constant 0x7FC00000 : f32
+ // CHECK: linalg.select
+ %0 = tosa.reduce_min %arg0 {axis = 0 : i32, nan_mode = IGNORE} : (tensor<5x4xf32>) -> tensor<1x4xf32>
+ return
+}
+
+// -----
+
+// CHECK-LABEL: @reduce_max_nan_ignore_seed
+func.func @reduce_max_nan_ignore_seed(%arg0: tensor<5x4xf32>) -> () {
+ // FINITE: arith.constant -3.40282347E+38 : f32
+ // NONFINITE: arith.constant 0xFF800000 : f32
+ // CHECK: linalg.fill
+ // CHECK: linalg.reduce
+ // CHECK: arith.maximumf
+ // CHECK: arith.cmpf uno
+ // CHECK: arith.select
+ // CHECK: arith.andi
+ // CHECK: arith.constant 0x7FC00000 : f32
+ // CHECK: linalg.select
+ %0 = tosa.reduce_max %arg0 {axis = 0 : i32, nan_mode = IGNORE} : (tensor<5x4xf32>) -> tensor<1x4xf32>
+ return
+}
+
+// -----
+
+// CHECK-LABEL: @argmax_f16
+func.func @argmax_f16(%arg0: tensor<6xf16>) -> () {
+ // FINITE: arith.constant -6.550400e+04 : f16
+ // NONFINITE: arith.constant 0xFC00 : f16
+ // CHECK: linalg.generic
+ // CHECK: arith.cmpf ugt
+ %0 = tosa.argmax %arg0 {axis = 0 : i32} : (tensor<6xf16>) -> tensor<i32>
+ return
+}
More information about the Mlir-commits
mailing list