[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