[Mlir-commits] [mlir] [mlir][tosa] Add F32 to F16 type narrowing pass (PR #218900)
Davide Grohmann
llvmlistbot at llvm.org
Wed Sep 2 07:22:20 PDT 2026
https://github.com/davidegrohmann updated https://github.com/llvm/llvm-project/pull/218900
>From e70230786276b9c8b02715729df4ac70de504e2a Mon Sep 17 00:00:00 2001
From: Davide Grohmann <davide.grohmann at arm.com>
Date: Tue, 25 Aug 2026 13:14:33 +0200
Subject: [PATCH] [mlir][tosa] Add F32 to F16 type narrowing pass
Add `tosa-narrow-f32-to-f16`, extending the shared TOSA type narrowing
infrastructure with support for destructively converting F32 tensors,
scalar attributes, dense constants, and resource-backed constants to
F16.
The pass supports conservative and aggressive rewriting, optional
function-boundary conversion, and optional preservation of F32
accumulator types.
Add coverage for constant precision loss, function boundaries,
regions, dense resources, aggressive rewriting, and accumulator-type
preservation.
Signed-off-by: Davide Grohmann <davide.grohmann at arm.com>
Change-Id: I2cfbc7334994540a02819a5743b7a810e5b0a803
---
.../mlir/Dialect/Tosa/Transforms/Passes.td | 30 +++
.../Tosa/Transforms/TosaNarrowTypes.cpp | 181 ++++++++++----
.../tosa-narrow-f32-to-f16-aggressive.mlir | 135 +++++++++++
.../Dialect/Tosa/tosa-narrow-f32-to-f16.mlir | 223 ++++++++++++++++++
4 files changed, 528 insertions(+), 41 deletions(-)
create mode 100644 mlir/test/Dialect/Tosa/tosa-narrow-f32-to-f16-aggressive.mlir
create mode 100644 mlir/test/Dialect/Tosa/tosa-narrow-f32-to-f16.mlir
diff --git a/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td b/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td
index 96a0b080d6a6f..b2c50e5c7c661 100644
--- a/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td
+++ b/mlir/include/mlir/Dialect/Tosa/Transforms/Passes.td
@@ -245,6 +245,36 @@ def TosaNarrowF64ToF32Pass : Pass<"tosa-narrow-f64-to-f32", "func::FuncOp"> {
];
}
+def TosaNarrowF32ToF16Pass : Pass<"tosa-narrow-f32-to-f16", "func::FuncOp"> {
+ let summary = "Narrow F32 TOSA operations to F16";
+ let description = [{
+ This pass destructively narrows TOSA operations with 32-bit floating-point
+ tensor types to 16-bit floating-point tensor types. It can reduce model
+ size and improve execution time on backends with native F16 support.
+
+ This conversion is not correctness-preserving and does not perform range
+ analysis. Before applying this pass, users must verify that all possible
+ input and intermediate values can be represented with the required range
+ and precision in F16. Otherwise, the conversion may reduce accuracy or
+ produce incorrect results.
+ }];
+
+ let options = [
+ Option<"aggressiveRewrite", "aggressive-rewrite", "bool", "false",
+ "If enabled, all TOSA operations are rewritten, regardless of whether the narrowing "
+ "is safe. This option may lead to data loss if not used carefully.">,
+ Option<"convertFunctionBoundaries", "convert-function-boundaries", "bool", "false",
+ "If enabled, the pass will convert function I/O types as well. Otherwise casts will "
+ "be inserted at the I/O boundaries.">,
+ Option<"convertAccumulatorType", "convert-accumulator-type", "bool", "false",
+ "If enabled, accumulator type attributes are narrowed as well.">
+ ];
+
+ let dependentDialects = [
+ "func::FuncDialect"
+ ];
+}
+
def TosaInputShape : Pass<"tosa-experimental-input-shape", "func::FuncOp"> {
let summary = "Override dynamic function arguments to specified static shapes.";
let description = [{
diff --git a/mlir/lib/Dialect/Tosa/Transforms/TosaNarrowTypes.cpp b/mlir/lib/Dialect/Tosa/Transforms/TosaNarrowTypes.cpp
index 1ec465124dfb4..f783835eec3eb 100644
--- a/mlir/lib/Dialect/Tosa/Transforms/TosaNarrowTypes.cpp
+++ b/mlir/lib/Dialect/Tosa/Transforms/TosaNarrowTypes.cpp
@@ -18,6 +18,7 @@
#include <algorithm>
#include <limits>
#include <type_traits>
+#include <utility>
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/Func/Transforms/FuncConversions.h"
@@ -32,6 +33,7 @@ namespace mlir {
namespace tosa {
#define GEN_PASS_DEF_TOSANARROWI64TOI32PASS
#define GEN_PASS_DEF_TOSANARROWF64TOF32PASS
+#define GEN_PASS_DEF_TOSANARROWF32TOF16PASS
#include "mlir/Dialect/Tosa/Transforms/Passes.h.inc"
} // namespace tosa
} // namespace mlir
@@ -42,7 +44,7 @@ using namespace mlir::tosa;
namespace {
// Narrowing mode for this pass.
-enum class TosaNarrowKind { Int64ToInt32, Float64ToFloat32 };
+enum class TosaNarrowKind { Int64ToInt32, Float64ToFloat32, Float32ToFloat16 };
// ---------------------------------------------------------------------------
// Shared helpers
@@ -59,6 +61,8 @@ template <TosaNarrowKind Kind>
bool isSourceFloat(FloatType type) {
if constexpr (Kind == TosaNarrowKind::Float64ToFloat32)
return type.isF64();
+ if constexpr (Kind == TosaNarrowKind::Float32ToFloat16)
+ return type.isF32();
return false;
}
@@ -77,6 +81,8 @@ Type convertFloat(FloatType type) {
return type;
if constexpr (Kind == TosaNarrowKind::Float64ToFloat32)
return Float32Type::get(type.getContext());
+ if constexpr (Kind == TosaNarrowKind::Float32ToFloat16)
+ return Float16Type::get(type.getContext());
return type;
}
@@ -147,7 +153,8 @@ FailureOr<Attribute> tryConvertScalarAttribute(Attribute attribute,
return IntegerAttr::get(convertedType, convertedValue.value());
}
}
- } else if constexpr (Kind == TosaNarrowKind::Float64ToFloat32) {
+ } else if constexpr (Kind == TosaNarrowKind::Float64ToFloat32 ||
+ Kind == TosaNarrowKind::Float32ToFloat16) {
if (const auto floatAttr = dyn_cast<FloatAttr>(attribute)) {
if (const auto floatType = dyn_cast<FloatType>(floatAttr.getType());
floatType && isSourceFloat<Kind>(floatType)) {
@@ -207,7 +214,8 @@ FailureOr<Attribute>
convertDenseFPElementsAttr(ShapedType type, DenseFPElementsAttr attr,
const TypeConverter &typeConverter,
bool allowLossyConversion) {
- if constexpr (Kind != TosaNarrowKind::Float64ToFloat32)
+ if constexpr (Kind != TosaNarrowKind::Float64ToFloat32 &&
+ Kind != TosaNarrowKind::Float32ToFloat16)
return attr;
const auto oldElementType = dyn_cast<FloatType>(type.getElementType());
@@ -247,11 +255,16 @@ FailureOr<Attribute> convertDenseResourceElementsAttr(
ShapedType type, DenseResourceElementsAttr attr,
const TypeConverter &typeConverter, bool allowLossyConversion) {
static_assert(Kind == TosaNarrowKind::Int64ToInt32 ||
- Kind == TosaNarrowKind::Float64ToFloat32);
- using From =
- std::conditional_t<Kind == TosaNarrowKind::Int64ToInt32, int64_t, double>;
- using To =
- std::conditional_t<Kind == TosaNarrowKind::Int64ToInt32, int32_t, float>;
+ Kind == TosaNarrowKind::Float64ToFloat32 ||
+ Kind == TosaNarrowKind::Float32ToFloat16);
+ using From = std::conditional_t<
+ Kind == TosaNarrowKind::Int64ToInt32, int64_t,
+ std::conditional_t<Kind == TosaNarrowKind::Float64ToFloat32, double,
+ float>>;
+ using To = std::conditional_t<
+ Kind == TosaNarrowKind::Int64ToInt32, int32_t,
+ std::conditional_t<Kind == TosaNarrowKind::Float64ToFloat32, float,
+ uint16_t>>;
if (Kind == TosaNarrowKind::Int64ToInt32 &&
!isa<DenseI64ResourceElementsAttr>(attr)) {
@@ -263,20 +276,41 @@ FailureOr<Attribute> convertDenseResourceElementsAttr(
return attr;
}
- auto narrow = [](From value) {
- if constexpr (Kind == TosaNarrowKind::Int64ToInt32) {
- value = std::clamp<From>(value, std::numeric_limits<To>::min(),
- std::numeric_limits<To>::max());
- }
-
- return static_cast<To>(value);
- };
+ if (Kind == TosaNarrowKind::Float32ToFloat16 &&
+ !isa<DenseF32ResourceElementsAttr>(attr)) {
+ return attr;
+ }
const auto newType =
dyn_cast_or_null<ShapedType>(typeConverter.convertType(type));
- if (!newType) {
+ if (!newType)
return failure();
- }
+
+ const auto newElementType = dyn_cast<FloatType>(newType.getElementType());
+
+ auto narrow = [newElementType,
+ allowLossyConversion](From value) -> FailureOr<To> {
+ if constexpr (Kind == TosaNarrowKind::Int64ToInt32) {
+ From clamped = std::clamp<From>(value, std::numeric_limits<To>::min(),
+ std::numeric_limits<To>::max());
+ if (!allowLossyConversion && clamped != value)
+ return failure();
+ return static_cast<To>(clamped);
+ } else if constexpr (Kind == TosaNarrowKind::Float64ToFloat32) {
+ To converted = static_cast<To>(value);
+ if (!allowLossyConversion && converted != value)
+ return failure();
+ return converted;
+ } else {
+ FailureOr<APFloat> converted = convertFloatConstant(
+ newElementType, APFloat(value), allowLossyConversion);
+ if (failed(converted))
+ return failure();
+ // Resource blobs require a trivially copyable storage type. Serialize
+ // the converted APFloat only at this boundary.
+ return static_cast<To>(converted->bitcastToAPInt().getZExtValue());
+ }
+ };
const std::optional<ArrayRef<From>> values =
tryGetDenseResourceValues<From>(attr);
@@ -287,12 +321,10 @@ FailureOr<Attribute> convertDenseResourceElementsAttr(
SmallVector<To> newValues;
newValues.reserve(values->size());
for (From value : *values) {
- const To convertedValue = narrow(value);
- if (!allowLossyConversion && convertedValue != value) {
+ FailureOr<To> convertedValue = narrow(value);
+ if (failed(convertedValue))
return failure();
- }
-
- newValues.push_back(convertedValue);
+ newValues.push_back(*convertedValue);
}
AsmResourceBlob blob = HeapAsmResourceBlob::allocateAndCopyInferAlign(
@@ -337,7 +369,8 @@ verifyCastDoesNotLosePrecision(Operation *op, ShapedType inputType,
elementInputIntType.getWidth() > elementResultIntType.getWidth())
return rewriter.notifyMatchFailure(
op, "Narrowing cast may lead to data loss.");
- } else if constexpr (Kind == TosaNarrowKind::Float64ToFloat32) {
+ } else if constexpr (Kind == TosaNarrowKind::Float64ToFloat32 ||
+ Kind == TosaNarrowKind::Float32ToFloat16) {
const auto elementInputFloatType =
dyn_cast<FloatType>(inputType.getElementType());
const auto elementResultFloatType =
@@ -362,7 +395,8 @@ template <TosaNarrowKind Kind>
LogicalResult convertGenericOp(Operation *op, ValueRange operands,
ConversionPatternRewriter &rewriter,
const TypeConverter *typeConverter,
- bool allowLossyConversion) {
+ bool allowLossyConversion,
+ bool convertAccumulatorType = false) {
SmallVector<Type, 4> newResults;
if (failed(typeConverter->convertTypes(op->getResultTypes(), newResults)))
return failure();
@@ -390,13 +424,21 @@ LogicalResult convertGenericOp(Operation *op, ValueRange operands,
}
if (const auto typeAttr = dyn_cast<TypeAttr>(attribute)) {
- FailureOr<Attribute> convertedAttr =
- convertAttributeWithTypeConverter<Kind>(typeAttr, typeAttr.getValue(),
- typeConverter);
- if (failed(convertedAttr))
+ if (!convertAccumulatorType &&
+ namedAttribute.getName().getValue() == "acc_type") {
+ state.addAttribute(namedAttribute.getName(), attribute);
+ continue;
+ }
+ if (!typeNeedsConversion<Kind>(typeAttr.getValue())) {
+ state.addAttribute(namedAttribute.getName(), attribute);
+ continue;
+ }
+ Type convertedType = typeConverter->convertType(typeAttr.getValue());
+ if (!convertedType)
return rewriter.notifyMatchFailure(op,
"Failed to convert type attribute.");
- state.addAttribute(namedAttribute.getName(), convertedAttr.value());
+ state.addAttribute(namedAttribute.getName(),
+ TypeAttr::get(convertedType));
continue;
}
@@ -445,9 +487,10 @@ template <TosaNarrowKind Kind>
class ConvertGenericOp : public ConversionPattern {
public:
ConvertGenericOp(TypeConverter &typeConverter, MLIRContext *context,
- bool allowLossyConversion)
+ bool allowLossyConversion, bool convertAccumulatorType)
: ConversionPattern(typeConverter, MatchAnyOpTypeTag{}, 0, context),
- allowLossyConversion(allowLossyConversion) {}
+ allowLossyConversion(allowLossyConversion),
+ convertAccumulatorType(convertAccumulatorType) {}
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
@@ -458,11 +501,39 @@ class ConvertGenericOp : public ConversionPattern {
"Support for operations other than TOSA has not been implemented.");
return convertGenericOp<Kind>(op, operands, rewriter, typeConverter,
- allowLossyConversion);
+ allowLossyConversion, convertAccumulatorType);
}
private:
const bool allowLossyConversion;
+ const bool convertAccumulatorType;
+};
+
+template <TosaNarrowKind Kind>
+class ConvertAccumulatorTypeOp : public ConversionPattern {
+public:
+ ConvertAccumulatorTypeOp(TypeConverter &typeConverter, MLIRContext *context)
+ : ConversionPattern(typeConverter, MatchAnyOpTypeTag{}, 1, context) {}
+
+ LogicalResult
+ matchAndRewrite(Operation *op, ArrayRef<Value> /*operands*/,
+ ConversionPatternRewriter &rewriter) const final {
+ if (!isa<tosa::TosaOp>(op))
+ return failure();
+
+ const auto accumulatorType = op->getAttrOfType<TypeAttr>("acc_type");
+ if (!accumulatorType ||
+ !typeNeedsConversion<Kind>(accumulatorType.getValue()))
+ return failure();
+
+ Type convertedType = typeConverter->convertType(accumulatorType.getValue());
+ if (!convertedType)
+ return failure();
+
+ rewriter.modifyOpInPlace(
+ op, [&] { op->setAttr("acc_type", TypeAttr::get(convertedType)); });
+ return success();
+ }
};
template <typename OpTy, TosaNarrowKind Kind>
@@ -581,11 +652,12 @@ class ConvertClampOpWithBoundsChecking
}
};
-// Shared implementation for both narrowing passes; the mode decides which
+// Shared implementation for the narrowing passes; the mode decides which
// element types and attribute payloads participate.
template <TosaNarrowKind Kind>
LogicalResult runTosaNarrowing(Operation *op, bool aggressiveRewrite,
- bool convertFunctionBoundaries) {
+ bool convertFunctionBoundaries,
+ bool convertAccumulatorType = false) {
MLIRContext *context = op->getContext();
const bool allowLossyConversion = aggressiveRewrite;
@@ -663,7 +735,8 @@ LogicalResult runTosaNarrowing(Operation *op, bool aggressiveRewrite,
return TypeConverter::AttributeConversionResult::result(
converted.value());
});
- } else if constexpr (Kind == TosaNarrowKind::Float64ToFloat32) {
+ } else if constexpr (Kind == TosaNarrowKind::Float64ToFloat32 ||
+ Kind == TosaNarrowKind::Float32ToFloat16) {
typeConverter.addTypeAttributeConversion(
[allowLossyConversion](FloatType /*type*/, FloatAttr attribute)
-> TypeConverter::AttributeConversionResult {
@@ -689,9 +762,15 @@ LogicalResult runTosaNarrowing(Operation *op, bool aggressiveRewrite,
ConversionTarget target(*context);
target.addDynamicallyLegalDialect<tosa::TosaDialect>(
- [&typeConverter](Operation *op) {
- return typeConverter.isLegal(op->getResultTypes()) &&
- typeConverter.isLegal(op->getOperandTypes());
+ [&typeConverter, convertAccumulatorType](Operation *op) {
+ if (!typeConverter.isLegal(op->getResultTypes()) ||
+ !typeConverter.isLegal(op->getOperandTypes()))
+ return false;
+ if (!convertAccumulatorType)
+ return true;
+ const auto accumulatorType = op->getAttrOfType<TypeAttr>("acc_type");
+ return !accumulatorType ||
+ !typeNeedsConversion<Kind>(accumulatorType.getValue());
});
if (convertFunctionBoundaries) {
target.addDynamicallyLegalOp<func::FuncOp>(
@@ -717,9 +796,11 @@ LogicalResult runTosaNarrowing(Operation *op, bool aggressiveRewrite,
patterns, typeConverter);
populateReturnOpTypeConversionPattern(patterns, typeConverter);
}
+ if (convertAccumulatorType)
+ patterns.add<ConvertAccumulatorTypeOp<Kind>>(typeConverter, context);
if (aggressiveRewrite) {
- patterns.add<ConvertGenericOp<Kind>>(typeConverter, context,
- allowLossyConversion);
+ patterns.add<ConvertGenericOp<Kind>>(
+ typeConverter, context, allowLossyConversion, convertAccumulatorType);
} else {
if constexpr (Kind == TosaNarrowKind::Int64ToInt32) {
patterns.add<ConvertArgMaxOpWithBoundsChecking>(typeConverter, context);
@@ -790,4 +871,22 @@ struct TosaNarrowF64ToF32
}
};
+struct TosaNarrowF32ToF16
+ : public tosa::impl::TosaNarrowF32ToF16PassBase<TosaNarrowF32ToF16> {
+ TosaNarrowF32ToF16() = default;
+
+ explicit TosaNarrowF32ToF16(const TosaNarrowF32ToF16PassOptions &options) {
+ this->aggressiveRewrite = options.aggressiveRewrite;
+ this->convertFunctionBoundaries = options.convertFunctionBoundaries;
+ this->convertAccumulatorType = options.convertAccumulatorType;
+ }
+
+ void runOnOperation() override {
+ if (failed(runTosaNarrowing<TosaNarrowKind::Float32ToFloat16>(
+ getOperation(), this->aggressiveRewrite,
+ this->convertFunctionBoundaries, this->convertAccumulatorType)))
+ signalPassFailure();
+ }
+};
+
} // namespace
diff --git a/mlir/test/Dialect/Tosa/tosa-narrow-f32-to-f16-aggressive.mlir b/mlir/test/Dialect/Tosa/tosa-narrow-f32-to-f16-aggressive.mlir
new file mode 100644
index 0000000000000..fc1f0aebe08cd
--- /dev/null
+++ b/mlir/test/Dialect/Tosa/tosa-narrow-f32-to-f16-aggressive.mlir
@@ -0,0 +1,135 @@
+// RUN: mlir-opt -split-input-file -verify-diagnostics -tosa-narrow-f32-to-f16="aggressive-rewrite=1" %s | FileCheck %s --allow-unused-prefixes --check-prefixes=COMMON,DEFAULT
+// RUN: mlir-opt -split-input-file -verify-diagnostics -tosa-narrow-f32-to-f16="aggressive-rewrite=1 convert-function-boundaries=1" %s | FileCheck %s --allow-unused-prefixes --check-prefixes=COMMON,FUNCBOUND
+// RUN: mlir-opt -split-input-file -verify-diagnostics -tosa-narrow-f32-to-f16="aggressive-rewrite=1 convert-accumulator-type=1" %s | FileCheck %s --allow-unused-prefixes --check-prefixes=COMMON,CONVERT-ACC
+
+// -----
+
+// CHECK-LABEL: test_f32_add
+// DEFAULT: %[[IN0:.*]]: tensor<13x21x1xf32>, %[[IN1:.*]]: tensor<13x21x3xf32>
+// FUNCBOUND: %[[IN0:.*]]: tensor<13x21x1xf16>, %[[IN1:.*]]: tensor<13x21x3xf16>
+func.func @test_f32_add(%arg0: tensor<13x21x1xf32>, %arg1: tensor<13x21x3xf32>) -> tensor<13x21x3xf32> {
+ // DEFAULT-DAG: %[[CAST0:.*]] = tosa.cast %[[IN0]] : (tensor<13x21x1xf32>) -> tensor<13x21x1xf16>
+ // DEFAULT-DAG: %[[CAST1:.*]] = tosa.cast %[[IN1]] : (tensor<13x21x3xf32>) -> tensor<13x21x3xf16>
+ // COMMON: %[[ADD:.*]] = tosa.add %{{.*}}, %{{.*}} : (tensor<13x21x1xf16>, tensor<13x21x3xf16>) -> tensor<13x21x3xf16>
+ %0 = tosa.add %arg0, %arg1 : (tensor<13x21x1xf32>, tensor<13x21x3xf32>) -> tensor<13x21x3xf32>
+ // DEFAULT: %[[OUT:.*]] = tosa.cast %[[ADD]] : (tensor<13x21x3xf16>) -> tensor<13x21x3xf32>
+ // DEFAULT: return %[[OUT]] : tensor<13x21x3xf32>
+ // FUNCBOUND: return %[[ADD]] : tensor<13x21x3xf16>
+ return %0 : tensor<13x21x3xf32>
+}
+
+// -----
+
+// CHECK-LABEL: test_f32_regions
+// DEFAULT: %[[IN0:.*]]: tensor<1xf32>, %[[IN1:.*]]: tensor<1xf32>
+func.func @test_f32_regions(%arg0: tensor<1xf32>, %arg1: tensor<1xf32>, %arg2: tensor<i1>) -> tensor<1xf32> {
+ // DEFAULT-DAG: %[[CAST0:.*]] = tosa.cast %[[IN0]] : (tensor<1xf32>) -> tensor<1xf16>
+ // DEFAULT-DAG: %[[CAST1:.*]] = tosa.cast %[[IN1]] : (tensor<1xf32>) -> tensor<1xf16>
+ // COMMON: %[[IF:.*]] = tosa.cond_if %arg2 : tensor<i1> -> tensor<1xf16>
+ %0 = tosa.cond_if %arg2 : tensor<i1> -> tensor<1xf32> {
+ // COMMON: %[[ADD:.*]] = tosa.add %{{.*}}, %{{.*}} : (tensor<1xf16>, tensor<1xf16>) -> tensor<1xf16>
+ %1 = tosa.add %arg0, %arg1 : (tensor<1xf32>, tensor<1xf32>) -> tensor<1xf32>
+ tosa.yield %1 : tensor<1xf32>
+ } else {
+ // COMMON: %[[SUB:.*]] = tosa.sub %{{.*}}, %{{.*}} : (tensor<1xf16>, tensor<1xf16>) -> tensor<1xf16>
+ %1 = tosa.sub %arg0, %arg1 : (tensor<1xf32>, tensor<1xf32>) -> tensor<1xf32>
+ tosa.yield %1 : tensor<1xf32>
+ }
+ // DEFAULT: %[[OUT:.*]] = tosa.cast %[[IF]] : (tensor<1xf16>) -> tensor<1xf32>
+ // DEFAULT: return %[[OUT]] : tensor<1xf32>
+ // FUNCBOUND: return %[[IF]] : tensor<1xf16>
+ return %0 : tensor<1xf32>
+}
+
+// -----
+
+// CHECK-LABEL: test_convert_input_parameters
+// DEFAULT: %[[IN:.*]]: tensor<1x3xf32>
+// FUNCBOUND: %[[IN:.*]]: tensor<1x3xf16>
+func.func @test_convert_input_parameters(%arg0: tensor<1x3xf32>) -> tensor<1x3xf16> {
+ // DEFAULT: %[[CAST_IN:.*]] = tosa.cast %[[IN]] : (tensor<1x3xf32>) -> tensor<1x3xf16>
+ // DEFAULT: %[[IDENTITY:.*]] = tosa.identity %[[CAST_IN]] : (tensor<1x3xf16>) -> tensor<1x3xf16>
+ // FUNCBOUND: %[[IDENTITY:.*]] = tosa.identity %[[IN]] : (tensor<1x3xf16>) -> tensor<1x3xf16>
+ %0 = tosa.identity %arg0 : (tensor<1x3xf32>) -> tensor<1x3xf32>
+ // COMMON: %[[TO_F16:.*]] = tosa.cast %{{.*}} {{.*}}: (tensor<1x3xf16>) -> tensor<1x3xf16>
+ %1 = tosa.cast %0 {input_unsigned = false} : (tensor<1x3xf32>) -> tensor<1x3xf16>
+ // DEFAULT: return %[[TO_F16]] : tensor<1x3xf16>
+ // FUNCBOUND: return %[[TO_F16]] : tensor<1x3xf16>
+ return %1 : tensor<1x3xf16>
+}
+
+// -----
+
+// CHECK-LABEL: test_f32_const
+func.func @test_f32_const() -> tensor<2xf32> {
+ // COMMON: %[[CONST:.*]] = "tosa.const"() <{values = dense<[1.000000e+00, 2.000000e+00]> : tensor<2xf16>}> : () -> tensor<2xf16>
+ %0 = "tosa.const"() <{values = dense<[1.000000e+00, 2.000000e+00]> : tensor<2xf32>}> : () -> tensor<2xf32>
+ // DEFAULT: %[[OUT:.*]] = tosa.cast %[[CONST]] : (tensor<2xf16>) -> tensor<2xf32>
+ // DEFAULT: return %[[OUT]] : tensor<2xf32>
+ // FUNCBOUND: return %[[CONST]] : tensor<2xf16>
+ return %0 : tensor<2xf32>
+}
+
+// -----
+
+// DEFAULT-LABEL: test_f32_accumulator
+// FUNCBOUND-LABEL: test_f32_accumulator
+// CONVERT-ACC-LABEL: test_f32_accumulator
+func.func @test_f32_accumulator(%arg0: tensor<1x4x4x1xf32>, %arg1: tensor<1xf32>, %arg2: tensor<1xf32>) -> tensor<1x3x3x1xf32> {
+ // COMMON: tosa.avg_pool2d
+ // DEFAULT-SAME: acc_type = f32
+ // FUNCBOUND-SAME: acc_type = f32
+ // CONVERT-ACC-SAME: acc_type = f16
+ // COMMON-SAME: (tensor<1x4x4x1xf16>, tensor<1xf16>, tensor<1xf16>) -> tensor<1x3x3x1xf16>
+ %0 = tosa.avg_pool2d %arg0, %arg1, %arg2 {acc_type = f32, kernel = array<i64: 2, 2>, pad = array<i64: 0, 0, 0, 0>, stride = array<i64: 1, 1>} : (tensor<1x4x4x1xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x3x3x1xf32>
+ return %0 : tensor<1x3x3x1xf32>
+}
+
+// -----
+
+// DEFAULT-LABEL: test_f32_conv2d_accumulator
+// FUNCBOUND-LABEL: test_f32_conv2d_accumulator
+// CONVERT-ACC-LABEL: test_f32_conv2d_accumulator
+func.func @test_f32_conv2d_accumulator(%arg0: tensor<1x4x4x1xf32>, %arg1: tensor<1x2x2x1xf32>, %arg2: tensor<1xf32>, %arg3: tensor<1xf32>, %arg4: tensor<1xf32>) -> tensor<1x3x3x1xf32> {
+ // COMMON: tosa.conv2d
+ // DEFAULT-SAME: acc_type = f32
+ // FUNCBOUND-SAME: acc_type = f32
+ // CONVERT-ACC-SAME: acc_type = f16
+ // COMMON-SAME: (tensor<1x4x4x1xf16>, tensor<1x2x2x1xf16>, tensor<1xf16>, tensor<1xf16>, tensor<1xf16>) -> tensor<1x3x3x1xf16>
+ %0 = tosa.conv2d %arg0, %arg1, %arg2, %arg3, %arg4 {acc_type = f32, dilation = array<i64: 1, 1>, pad = array<i64: 0, 0, 0, 0>, stride = array<i64: 1, 1>} : (tensor<1x4x4x1xf32>, tensor<1x2x2x1xf32>, tensor<1xf32>, tensor<1xf32>, tensor<1xf32>) -> tensor<1x3x3x1xf32>
+ return %0 : tensor<1x3x3x1xf32>
+}
+
+// -----
+
+// DEFAULT-LABEL: test_accumulator_only_f32
+// FUNCBOUND-LABEL: test_accumulator_only_f32
+// CONVERT-ACC-LABEL: test_accumulator_only_f32
+func.func @test_accumulator_only_f32(%arg0: tensor<1x4x4x1xf16>, %arg1: tensor<1xf16>, %arg2: tensor<1xf16>) -> tensor<1x3x3x1xf16> {
+ // COMMON: tosa.avg_pool2d
+ // DEFAULT-SAME: acc_type = f32
+ // FUNCBOUND-SAME: acc_type = f32
+ // CONVERT-ACC-SAME: acc_type = f16
+ %0 = tosa.avg_pool2d %arg0, %arg1, %arg2 {acc_type = f32, kernel = array<i64: 2, 2>, pad = array<i64: 0, 0, 0, 0>, stride = array<i64: 1, 1>} : (tensor<1x4x4x1xf16>, tensor<1xf16>, tensor<1xf16>) -> tensor<1x3x3x1xf16>
+ return %0 : tensor<1x3x3x1xf16>
+}
+
+// -----
+
+// CHECK-LABEL: test_dense_resource_f32
+func.func @test_dense_resource_f32() -> tensor<1x2xf32> {
+ // COMMON: %[[CONST:.*]] = "tosa.const"() <{values = dense_resource<resource> : tensor<1x2xf16>}> : () -> tensor<1x2xf16>
+ %0 = "tosa.const"() <{values = dense_resource<resource> : tensor<1x2xf32>}> : () -> tensor<1x2xf32>
+ // DEFAULT: %[[OUT_CAST:.*]] = tosa.cast %[[CONST]] : (tensor<1x2xf16>) -> tensor<1x2xf32>
+ // DEFAULT: return %[[OUT_CAST]] : tensor<1x2xf32>
+ // FUNCBOUND: return %[[CONST]] : tensor<1x2xf16>
+ return %0 : tensor<1x2xf32>
+}
+{-#
+ dialect_resources: {
+ builtin: {
+ // COMMON: resource: "0x0200000000BC0040"
+ resource: "0x04000000000080bf00000040"
+ }
+ }
+#-}
diff --git a/mlir/test/Dialect/Tosa/tosa-narrow-f32-to-f16.mlir b/mlir/test/Dialect/Tosa/tosa-narrow-f32-to-f16.mlir
new file mode 100644
index 0000000000000..a5328cf12e177
--- /dev/null
+++ b/mlir/test/Dialect/Tosa/tosa-narrow-f32-to-f16.mlir
@@ -0,0 +1,223 @@
+// RUN: mlir-opt -split-input-file -verify-diagnostics -tosa-narrow-f32-to-f16="convert-function-boundaries=0" %s | FileCheck %s --allow-unused-prefixes --check-prefixes=COMMON,DEFAULT,PRESERVE-ACC
+// RUN: mlir-opt -split-input-file -verify-diagnostics -tosa-narrow-f32-to-f16="convert-function-boundaries=1" %s | FileCheck %s --allow-unused-prefixes --check-prefixes=COMMON,FUNCBOUND,PRESERVE-ACC
+// RUN: mlir-opt -split-input-file -verify-diagnostics -tosa-narrow-f32-to-f16="convert-accumulator-type=1" %s | FileCheck %s --allow-unused-prefixes --check-prefixes=COMMON,DEFAULT,CONVERT-ACC
+
+// -----
+
+// CHECK-LABEL: test_accumulator_only_f32
+func.func @test_accumulator_only_f32(%arg0: tensor<1x4x4x1xf16>, %arg1: tensor<1xf16>, %arg2: tensor<1xf16>) -> tensor<1x3x3x1xf16> {
+ // PRESERVE-ACC: tosa.avg_pool2d
+ // PRESERVE-ACC-SAME: acc_type = f32
+ // CONVERT-ACC: tosa.avg_pool2d
+ // CONVERT-ACC-SAME: acc_type = f16
+ %0 = tosa.avg_pool2d %arg0, %arg1, %arg2 {acc_type = f32, kernel = array<i64: 2, 2>, pad = array<i64: 0, 0, 0, 0>, stride = array<i64: 1, 1>} : (tensor<1x4x4x1xf16>, tensor<1xf16>, tensor<1xf16>) -> tensor<1x3x3x1xf16>
+ return %0 : tensor<1x3x3x1xf16>
+}
+
+// -----
+
+// CHECK-LABEL: test_f32_identity_chain
+func.func @test_f32_identity_chain(%arg0: tensor<1xf32>) -> tensor<1xf32> {
+ // DEFAULT: %[[CAST_IN:.*]] = tosa.cast %arg0 : (tensor<1xf32>) -> tensor<1xf16>
+ // DEFAULT: %[[ID1:.*]] = tosa.identity %[[CAST_IN]] : (tensor<1xf16>) -> tensor<1xf16>
+ // FUNCBOUND: %[[ID1:.*]] = tosa.identity %arg0 : (tensor<1xf16>) -> tensor<1xf16>
+ %0 = tosa.identity %arg0 : (tensor<1xf32>) -> tensor<1xf32>
+ // COMMON: %[[ID2:.*]] = tosa.identity %[[ID1]] : (tensor<1xf16>) -> tensor<1xf16>
+ %1 = tosa.identity %0 : (tensor<1xf32>) -> tensor<1xf32>
+ // DEFAULT: %[[CAST_OUT:.*]] = tosa.cast %[[ID2]] : (tensor<1xf16>) -> tensor<1xf32>
+ // DEFAULT: return %[[CAST_OUT]] : tensor<1xf32>
+ // FUNCBOUND: return %[[ID2]] : tensor<1xf16>
+ return %1 : tensor<1xf32>
+}
+
+// -----
+
+// CHECK-LABEL: test_f32_const
+func.func @test_f32_const() -> tensor<2xf32> {
+ // COMMON: %[[CONST:.*]] = "tosa.const"() <{values = dense<[-1.000000e+00, 2.000000e+00]> : tensor<2xf16>}> : () -> tensor<2xf16>
+ %0 = "tosa.const"() <{values = dense<[-1.000000e+00, 2.000000e+00]> : tensor<2xf32>}> : () -> tensor<2xf32>
+ // DEFAULT: %[[OUT:.*]] = tosa.cast %[[CONST]] : (tensor<2xf16>) -> tensor<2xf32>
+ // DEFAULT: return %[[OUT]] : tensor<2xf32>
+ // FUNCBOUND: return %[[CONST]] : tensor<2xf16>
+ return %0 : tensor<2xf32>
+}
+
+// -----
+
+// CHECK-LABEL: test_f32_const_precision_loss
+func.func @test_f32_const_precision_loss() -> tensor<1xf32> {
+ // expected-error @+2 {{failed to legalize operation 'tosa.const'}}
+ // The value exceeds the finite f16 range.
+ %0 = "tosa.const"() <{values = dense<65536.0> : tensor<1xf32>}> : () -> tensor<1xf32>
+ return %0 : tensor<1xf32>
+}
+
+// -----
+
+// CHECK-LABEL: test_f32_const_negative_precision_loss
+func.func @test_f32_const_negative_precision_loss() -> tensor<1xf32> {
+ // expected-error @+2 {{failed to legalize operation 'tosa.const'}}
+ // The value exceeds the finite f16 range.
+ %0 = "tosa.const"() <{values = dense<-65536.0> : tensor<1xf32>}> : () -> tensor<1xf32>
+ return %0 : tensor<1xf32>
+}
+
+// -----
+
+// CHECK-LABEL: test_f32_const_precision_loss_small
+func.func @test_f32_const_precision_loss_small() -> tensor<1xf32> {
+ // expected-error @+2 {{failed to legalize operation 'tosa.const'}}
+ // Too small: underflows to zero when narrowed to f16.
+ %0 = "tosa.const"() <{values = dense<1.0e-40> : tensor<1xf32>}> : () -> tensor<1xf32>
+ return %0 : tensor<1xf32>
+}
+
+// -----
+
+// CHECK-LABEL: test_f32_concat
+// DEFAULT: %[[A0:.*]]: tensor<13x21x3xf32>, %[[A1:.*]]: tensor<13x21x3xf32>
+// FUNCBOUND: %[[A0:.*]]: tensor<13x21x3xf16>, %[[A1:.*]]: tensor<13x21x3xf16>
+func.func @test_f32_concat(%arg0: tensor<13x21x3xf32>, %arg1: tensor<13x21x3xf32>) -> tensor<26x21x3xf32> {
+ // DEFAULT-DAG: %[[CAST0:.*]] = tosa.cast %[[A0]] : (tensor<13x21x3xf32>) -> tensor<13x21x3xf16>
+ // DEFAULT-DAG: %[[CAST1:.*]] = tosa.cast %[[A1]] : (tensor<13x21x3xf32>) -> tensor<13x21x3xf16>
+ // COMMON: %[[CONCAT:.*]] = tosa.concat %{{.*}}, %{{.*}} {axis = 0 : i32} : (tensor<13x21x3xf16>, tensor<13x21x3xf16>) -> tensor<26x21x3xf16>
+ %0 = tosa.concat %arg0, %arg1 {axis = 0 : i32} : (tensor<13x21x3xf32>, tensor<13x21x3xf32>) -> tensor<26x21x3xf32>
+ // DEFAULT: %[[CAST_OUT:.*]] = tosa.cast %[[CONCAT]] : (tensor<26x21x3xf16>) -> tensor<26x21x3xf32>
+ // DEFAULT: return %[[CAST_OUT]] : tensor<26x21x3xf32>
+ // FUNCBOUND: return %[[CONCAT]] : tensor<26x21x3xf16>
+ return %0 : tensor<26x21x3xf32>
+}
+
+// -----
+
+// CHECK-LABEL: test_f32_pad
+func.func @test_f32_pad(%arg0: tensor<13x21x3xf32>, %arg1: tensor<1xf32>) -> tensor<15x23x5xf32> {
+ %padding = tosa.const_shape {values = dense<1> : tensor<6xindex>} : () -> !tosa.shape<6>
+ // DEFAULT-DAG: %[[IN_CAST:.*]] = tosa.cast %arg0 : (tensor<13x21x3xf32>) -> tensor<13x21x3xf16>
+ // DEFAULT-DAG: %[[PAD_CAST:.*]] = tosa.cast %arg1 : (tensor<1xf32>) -> tensor<1xf16>
+ // COMMON: %[[PAD:.*]] = tosa.pad %{{.*}}, %{{.*}}, %{{.*}} : (tensor<13x21x3xf16>, !tosa.shape<6>, tensor<1xf16>) -> tensor<15x23x5xf16>
+ %1 = tosa.pad %arg0, %padding, %arg1 : (tensor<13x21x3xf32>, !tosa.shape<6>, tensor<1xf32>) -> tensor<15x23x5xf32>
+ // DEFAULT: %[[OUT_CAST:.*]] = tosa.cast %[[PAD]] : (tensor<15x23x5xf16>) -> tensor<15x23x5xf32>
+ // DEFAULT: return %[[OUT_CAST]] : tensor<15x23x5xf32>
+ // FUNCBOUND: return %[[PAD]] : tensor<15x23x5xf16>
+ return %1 : tensor<15x23x5xf32>
+}
+
+// -----
+
+// CHECK-LABEL: test_f32_reshape
+func.func @test_f32_reshape(%arg0: tensor<13x21x3xf32>) -> tensor<1x819xf32> {
+ %shape = tosa.const_shape {values = dense<[1, 819]> : tensor<2xindex>} : () -> !tosa.shape<2>
+ // COMMON: %[[RESHAPE:.*]] = tosa.reshape %{{.*}}, %{{.*}} : (tensor<13x21x3xf16>, !tosa.shape<2>) -> tensor<1x819xf16>
+ %0 = tosa.reshape %arg0, %shape : (tensor<13x21x3xf32>, !tosa.shape<2>) -> tensor<1x819xf32>
+ // DEFAULT: %[[OUT_CAST:.*]] = tosa.cast %[[RESHAPE]] : (tensor<1x819xf16>) -> tensor<1x819xf32>
+ // DEFAULT: return %[[OUT_CAST]] : tensor<1x819xf32>
+ // FUNCBOUND: return %[[RESHAPE]] : tensor<1x819xf16>
+ return %0 : tensor<1x819xf32>
+}
+
+// -----
+
+// CHECK-LABEL: test_f32_reverse
+func.func @test_f32_reverse(%arg0: tensor<13x21x3xf32>) -> tensor<13x21x3xf32> {
+ // COMMON: %[[REV:.*]] = tosa.reverse %{{.*}} {axis = 0 : i32} : (tensor<13x21x3xf16>) -> tensor<13x21x3xf16>
+ %0 = tosa.reverse %arg0 {axis = 0 : i32} : (tensor<13x21x3xf32>) -> tensor<13x21x3xf32>
+ // DEFAULT: %[[OUT:.*]] = tosa.cast %[[REV]] : (tensor<13x21x3xf16>) -> tensor<13x21x3xf32>
+ // DEFAULT: return %[[OUT]] : tensor<13x21x3xf32>
+ // FUNCBOUND: return %[[REV]] : tensor<13x21x3xf16>
+ return %0 : tensor<13x21x3xf32>
+}
+
+// -----
+
+// CHECK-LABEL: test_f32_slice
+func.func @test_f32_slice(%arg0: tensor<13x21x3xf32>) -> tensor<4x11x1xf32> {
+ %size = tosa.const_shape {values = dense<[4, 11, 1]> : tensor<3xindex>} : () -> !tosa.shape<3>
+ %start = tosa.const_shape {values = dense<[6, 8, 0]> : tensor<3xindex>} : () -> !tosa.shape<3>
+ // COMMON: %[[SLICE:.*]] = tosa.slice %{{.*}}, %{{.*}}, %{{.*}} : (tensor<13x21x3xf16>, !tosa.shape<3>, !tosa.shape<3>) -> tensor<4x11x1xf16>
+ %0 = tosa.slice %arg0, %start, %size : (tensor<13x21x3xf32>, !tosa.shape<3>, !tosa.shape<3>) -> tensor<4x11x1xf32>
+ // DEFAULT: %[[OUT:.*]] = tosa.cast %[[SLICE]] : (tensor<4x11x1xf16>) -> tensor<4x11x1xf32>
+ // DEFAULT: return %[[OUT]] : tensor<4x11x1xf32>
+ // FUNCBOUND: return %[[SLICE]] : tensor<4x11x1xf16>
+ return %0 : tensor<4x11x1xf32>
+}
+
+// -----
+
+// CHECK-LABEL: test_f32_tile
+func.func @test_f32_tile(%arg0: tensor<13x21x3xf32>) -> tensor<39x21x6xf32> {
+ %multipliers = tosa.const_shape { values = dense<[3, 1, 2]> : tensor<3xindex> } : () -> !tosa.shape<3>
+ // COMMON: %[[TILE:.*]] = tosa.tile %{{.*}}, %{{.*}} : (tensor<13x21x3xf16>, !tosa.shape<3>) -> tensor<39x21x6xf16>
+ %0 = tosa.tile %arg0, %multipliers : (tensor<13x21x3xf32>, !tosa.shape<3>) -> tensor<39x21x6xf32>
+ // DEFAULT: %[[OUT:.*]] = tosa.cast %[[TILE]] : (tensor<39x21x6xf16>) -> tensor<39x21x6xf32>
+ // DEFAULT: return %[[OUT]] : tensor<39x21x6xf32>
+ // FUNCBOUND: return %[[TILE]] : tensor<39x21x6xf16>
+ return %0 : tensor<39x21x6xf32>
+}
+
+// -----
+
+// CHECK-LABEL: test_f32_transpose
+func.func @test_f32_transpose(%arg0: tensor<13x21x3xf32>) -> tensor<3x13x21xf32> {
+ // COMMON: %[[TRANSPOSE:.*]] = tosa.transpose %{{.*}} {perms = array<i32: 2, 0, 1>} : (tensor<13x21x3xf16>) -> tensor<3x13x21xf16>
+ %0 = tosa.transpose %arg0 {perms = array<i32: 2, 0, 1>} : (tensor<13x21x3xf32>) -> tensor<3x13x21xf32>
+ // DEFAULT: %[[OUT:.*]] = tosa.cast %[[TRANSPOSE]] : (tensor<3x13x21xf16>) -> tensor<3x13x21xf32>
+ // DEFAULT: return %[[OUT]] : tensor<3x13x21xf32>
+ // FUNCBOUND: return %[[TRANSPOSE]] : tensor<3x13x21xf16>
+ return %0 : tensor<3x13x21xf32>
+}
+
+// -----
+
+module {
+// CHECK-LABEL: test_f32_regions
+func.func @test_f32_regions(%arg0: tensor<1xf32>, %arg1: tensor<1xf32>, %arg2: tensor<i1>) -> tensor<1xf32> {
+ // COMMON: %[[IF_RESULT:.*]] = tosa.cond_if %arg2 : tensor<i1> -> tensor<1xf16>
+ %0 = tosa.cond_if %arg2 : tensor<i1> -> tensor<1xf32> {
+ // COMMON: %[[ID0:.*]] = tosa.identity %{{.*}} : (tensor<1xf16>) -> tensor<1xf16>
+ %1 = tosa.identity %arg0 : (tensor<1xf32>) -> tensor<1xf32>
+ // COMMON: tosa.yield %[[ID0]] : tensor<1xf16>
+ tosa.yield %1 : tensor<1xf32>
+ } else {
+ // COMMON: %[[ID1:.*]] = tosa.identity %{{.*}} : (tensor<1xf16>) -> tensor<1xf16>
+ %1 = tosa.identity %arg1 : (tensor<1xf32>) -> tensor<1xf32>
+ // COMMON: tosa.yield %[[ID1]] : tensor<1xf16>
+ tosa.yield %1 : tensor<1xf32>
+ }
+ // DEFAULT: %[[OUT:.*]] = tosa.cast %[[IF_RESULT]] : (tensor<1xf16>) -> tensor<1xf32>
+ // DEFAULT: return %[[OUT]] : tensor<1xf32>
+ // FUNCBOUND: return %[[IF_RESULT]] : tensor<1xf16>
+ return %0 : tensor<1xf32>
+}
+}
+
+// -----
+
+module {
+// CHECK-LABEL: test_f32_add_diagnostic
+func.func @test_f32_add_diagnostic(%arg0: tensor<13x21x1xf32>, %arg1: tensor<13x21x3xf32>) -> tensor<13x21x3xf32> {
+ // expected-error @+1 {{failed to legalize operation 'tosa.add'}}
+ %0 = tosa.add %arg0, %arg1 : (tensor<13x21x1xf32>, tensor<13x21x3xf32>) -> tensor<13x21x3xf32>
+ return %0 : tensor<13x21x3xf32>
+}
+}
+
+// -----
+
+// CHECK-LABEL: test_dense_resource_f32
+func.func @test_dense_resource_f32() -> tensor<1x2xf32> {
+ // COMMON: %[[CONST:.*]] = "tosa.const"() <{values = dense_resource<resource> : tensor<1x2xf16>}> : () -> tensor<1x2xf16>
+ %0 = "tosa.const"() <{values = dense_resource<resource> : tensor<1x2xf32>}> : () -> tensor<1x2xf32>
+ // DEFAULT: %[[OUT_CAST:.*]] = tosa.cast %[[CONST]] : (tensor<1x2xf16>) -> tensor<1x2xf32>
+ // DEFAULT: return %[[OUT_CAST]] : tensor<1x2xf32>
+ // FUNCBOUND: return %[[CONST]] : tensor<1x2xf16>
+ return %0 : tensor<1x2xf32>
+}
+{-#
+ dialect_resources: {
+ builtin: {
+ // COMMON: resource: "0x0200000000BC0040"
+ resource: "0x04000000000080bf00000040"
+ }
+ }
+#-}
More information about the Mlir-commits
mailing list