// Copyright 2026 The TensorFlow Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ============================================================================== // RUN: tfr-opt %s -tfr-decompose -verify-diagnostics | FileCheck %s // Definitions for ops that are being used in the tests. // ex) tf.MyOp refers to tfr.func @tf__my_op // CHECK-LABEL: @tf__fake_no_op tfr.func @tf__fake_no_op(%arg0: !tfr.tensor) -> !tfr.tensor { tfr.return %arg0 : !tfr.tensor // CHECK-NEXT: tfr.return %arg0 : !tfr.tensor } // CHECK-LABEL: @tf__intermediate tfr.func @tf__intermediate(%arg0: !tfr.tensor) -> !tfr.tensor { %0 = tfr.call @tf__risc(%arg0) : (!tfr.tensor) -> !tfr.tensor tfr.return %0 : !tfr.tensor // CHECK-NEXT: %[[id:.*]] = tfr.call @tf__risc(%arg0) : (!tfr.tensor) -> !tfr.tensor // CHECK-NEXT: tfr.return %[[id]] : !tfr.tensor } // CHECK-LABEL: @tf__fused_n tfr.func @tf__fused_n( %arg0: !tfr.tensor, %arg1: !tfr.tensor_list, %arg2: index {tfr.name="A",tfr.default=1:index}) -> !tfr.tensor_list { %0 = tfr.call @tf__intermediate(%arg0) : (!tfr.tensor) -> !tfr.tensor %1 = tfr.get_element %arg1[%arg2] : (!tfr.tensor_list, index) -> !tfr.tensor %2 = tfr.call @tf__intermediate(%1) : (!tfr.tensor) -> !tfr.tensor %3 = "tfr.build_list"(%0, %2) : (!tfr.tensor, !tfr.tensor) -> !tfr.tensor_list tfr.return %3 : !tfr.tensor_list // CHECK-NEXT: %[[id1:.*]] = tfr.call @tf__intermediate(%arg0) : (!tfr.tensor) -> !tfr.tensor // CHECK-NEXT: %[[ge:.*]] = tfr.get_element %arg1[%arg2] : (!tfr.tensor_list, index) -> !tfr.tensor // CHECK-NEXT: %[[id2:.*]] = tfr.call @tf__intermediate(%[[ge]]) : (!tfr.tensor) -> !tfr.tensor // CHECK-NEXT: %[[bl:.*]] = "tfr.build_list"(%[[id1]], %[[id2]]) : (!tfr.tensor, !tfr.tensor) -> !tfr.tensor_list // CHECK-NEXT: tfr.return %[[bl]] : !tfr.tensor_list } // CHECK-LABEL: @tf__my_max_pool tfr.func @tf__my_max_pool(%input_: !tfr.tensor, %stride_w: i64{tfr.name="stride_w"}, %stride_h: i64{tfr.name="stride_h"}) -> (!tfr.tensor) { %cst_1 = arith.constant 1 : i64 %stride = "tfr.build_list"(%cst_1, %stride_w, %stride_h, %cst_1) : (i64, i64, i64, i64) -> !tfr.attr %filter = tfr.constant [1, 2, 2, 1] -> !tfr.attr %padding = tfr.constant "VALID" -> !tfr.attr %explicit_paddings = tfr.constant [] -> !tfr.attr %data_format = tfr.constant "NHWC" -> !tfr.attr %MaxPool = tfr.call @tf__max_pool(%input_, %stride, %filter, %padding, %explicit_paddings, %data_format) : (!tfr.tensor, !tfr.attr, !tfr.attr, !tfr.attr, !tfr.attr, !tfr.attr) -> (!tfr.tensor) tfr.return %MaxPool : !tfr.tensor // CHECK: tf__max_pool } // CHECK-LABEL: @tf__cast_float tfr.func @tf__cast_float(%input_: !tfr.tensor, %out_type: !tfr.attr{tfr.name="out_type"}) -> (!tfr.tensor) { %false = arith.constant false %cast = tfr.call @tf__cast(%input_, %out_type, %false) : (!tfr.tensor, !tfr.attr, i1) -> (!tfr.tensor) tfr.return %cast : !tfr.tensor } // end op definitions // CHECK-LABEL: decompose_tf_no_op func.func @decompose_tf_no_op(%arg0: tensor<1x2x3x4x!tf_type.string>) -> tensor<1x2x3x4x!tf_type.string> { %0 = "tf.FakeNoOp"(%arg0) : (tensor<1x2x3x4x!tf_type.string>) -> tensor<1x2x3x4x!tf_type.string> func.return %0 : tensor<1x2x3x4x!tf_type.string> // CHECK-NEXT: return %arg0 } // CHECK-LABEL: decompose_tf_intermediate func.func @decompose_tf_intermediate(%arg0: tensor<1x2x3x4x!tf_type.string>) -> tensor<1x2x3x4x!tf_type.string> { %0 = "tf.Intermediate"(%arg0) : (tensor<1x2x3x4x!tf_type.string>) -> tensor<1x2x3x4x!tf_type.string> func.return %0 : tensor<1x2x3x4x!tf_type.string> // CHECK-NEXT: %[[casted:.*]] = "tfr.cast"(%arg0) : (tensor<1x2x3x4x!tf_type.string>) -> !tfr.tensor // CHECK-NEXT: %[[id:.*]] = tfr.call @tf__risc(%[[casted]]) : (!tfr.tensor) -> !tfr.tensor // CHECK-NEXT: %[[back:.*]] = "tfr.cast"(%[[id]]) : (!tfr.tensor) -> tensor<1x2x3x4x!tf_type.string> // CHECK-NEXT: return %[[back]] } // CHECK-LABEL: decompose_fused_n_default func.func @decompose_fused_n_default(%arg0: tensor<1x2x3x4x!tf_type.string>, %arg1: tensor, %arg2: tensor) -> tensor { %0:2 = "tf.FusedN"(%arg0, %arg1, %arg2) : (tensor<1x2x3x4x!tf_type.string>, tensor, tensor) -> (tensor<1x2x3x4x!tf_type.string>, tensor) func.return %0#1 : tensor // CHECK-NEXT: %[[in0:.*]] = "tfr.cast"(%arg0) : (tensor<1x2x3x4x!tf_type.string>) -> !tfr.tensor // CHECK-NEXT: %[[in2:.*]] = "tfr.cast"(%arg2) : (tensor) -> !tfr.tensor // CHECK-NEXT: %[[id0:.*]] = tfr.call @tf__risc(%[[in0]]) : (!tfr.tensor) -> !tfr.tensor // CHECK-NEXT: %[[id2:.*]] = tfr.call @tf__risc(%[[in2]]) : (!tfr.tensor) -> !tfr.tensor // CHECK-NEXT: %[[back:.*]] = "tfr.cast"(%[[id2]]) : (!tfr.tensor) -> tensor // CHECK-NEXT: return %[[back]] : tensor } // CHECK-LABEL: decompose_fused_n func.func @decompose_fused_n(%arg0: tensor<1x2x3x4x!tf_type.string>, %arg1: tensor, %arg2: tensor) -> tensor { %0:2 = "tf.FusedN"(%arg0, %arg1, %arg2) {A=0:index} : (tensor<1x2x3x4x!tf_type.string>, tensor, tensor) -> (tensor<1x2x3x4x!tf_type.string>, tensor) func.return %0#1 : tensor // CHECK-NEXT: %[[in0:.*]] = "tfr.cast"(%arg0) : (tensor<1x2x3x4x!tf_type.string>) -> !tfr.tensor // CHECK-NEXT: %[[in1:.*]] = "tfr.cast"(%arg1) : (tensor) -> !tfr.tensor // CHECK-NEXT: %[[id0:.*]] = tfr.call @tf__risc(%[[in0]]) : (!tfr.tensor) -> !tfr.tensor // CHECK-NEXT: %[[id1:.*]] = tfr.call @tf__risc(%[[in1]]) : (!tfr.tensor) -> !tfr.tensor // CHECK-NEXT: %[[back:.*]] = "tfr.cast"(%[[id1]]) : (!tfr.tensor) -> tensor // CHECK-NEXT: return %[[back]] : tensor } // CHECK-LABEL: attribute_propagate_direct func.func @attribute_propagate_direct(%arg0: tensor<1x2x3x4x!tf_type.string>) -> tensor<1x2x3x4x!tf_type.string> { %0 = "tf.Intermediate"(%arg0) {_tpu_replicate, device="hello"} : (tensor<1x2x3x4x!tf_type.string>) -> tensor<1x2x3x4x!tf_type.string> func.return %0 : tensor<1x2x3x4x!tf_type.string> // CHECK-NEXT: %[[casted:.*]] = "tfr.cast"(%arg0) : (tensor<1x2x3x4x!tf_type.string>) -> !tfr.tensor // CHECK-NEXT: %[[id:.*]] = tfr.call @tf__risc(%[[casted]]) {_tpu_replicate, device = "hello"} // CHECK-NEXT: %[[back:.*]] = "tfr.cast"(%[[id]]) : (!tfr.tensor) -> tensor<1x2x3x4x!tf_type.string> // CHECK-NEXT: return %[[back]] } // CHECK-LABEL: attribute_propagate func.func @attribute_propagate(%arg0: tensor<1x2x3x4x!tf_type.string>, %arg1: tensor, %arg2: tensor) -> tensor { %0:2 = "tf.FusedN"(%arg0, %arg1, %arg2) {A=0:index, _tpu_replicate, device="hello"} : (tensor<1x2x3x4x!tf_type.string>, tensor, tensor) -> (tensor<1x2x3x4x!tf_type.string>, tensor) func.return %0#1 : tensor // CHECK-NEXT: %[[in0:.*]] = "tfr.cast"(%arg0) : (tensor<1x2x3x4x!tf_type.string>) -> !tfr.tensor // CHECK-NEXT: %[[in1:.*]] = "tfr.cast"(%arg1) : (tensor) -> !tfr.tensor // CHECK-NEXT: %[[id0:.*]] = tfr.call @tf__risc(%[[in0]]) {_tpu_replicate, device = "hello"} // CHECK-NEXT: %[[id1:.*]] = tfr.call @tf__risc(%[[in1]]) {_tpu_replicate, device = "hello"} // CHECK-NEXT: %[[back:.*]] = "tfr.cast"(%[[id1]]) : (!tfr.tensor) -> tensor // CHECK-NEXT: return %[[back]] : tensor } // CHECK: attribute_cast func.func @attribute_cast(%arg0: tensor<1x4x4x1xf32>) -> tensor<1x2x2x1xf32> { %0 = "tfr.cast"(%arg0) : (tensor<1x4x4x1xf32>) -> !tfr.tensor %stride_i32 = arith.constant 2 : i32 %1 = tfr.call @tf__my_max_pool(%0, %stride_i32, %stride_i32) : (!tfr.tensor, i32, i32) -> !tfr.tensor %2 = "tfr.cast"(%1) : (!tfr.tensor) -> tensor<1x2x2x1xf32> func.return %2 : tensor<1x2x2x1xf32> // CHECK: tf__max_pool } // CHECK-LABEL: no_tf_canonicalization func.func @no_tf_canonicalization(%arg0: tensor<8xi1>, %arg1: tensor<8x3xf32>, %arg2: tensor<8x3xf32>) -> tensor<8x3xf32> { %0 = "tf.Select"(%arg0, %arg1, %arg2) : (tensor<8xi1>, tensor<8x3xf32>, tensor<8x3xf32>) -> tensor<8x3xf32> func.return %0: tensor<8x3xf32> // CHECK: "tf.Select" } // CHECK-LABEL: denied_attribute func.func @denied_attribute(%arg0: tensor<1x2x3x4x!tf_type.string>, %arg1: tensor, %arg2: tensor) -> tensor { // expected-error@+1 {{Denied unregistered attribute was found: denied_attr}} %0:2 = "tf.FusedN"(%arg0, %arg1, %arg2) {A=0:index, denied_attr} : (tensor<1x2x3x4x!tf_type.string>, tensor, tensor) -> (tensor<1x2x3x4x!tf_type.string>, tensor) func.return %0#1 : tensor // CHECK-NEXT: "tf.FusedN"(%arg0, %arg1, %arg2) {A = 0 : index, denied_attr} } // CHECK-LABEL: quantized_tensor func.func @quantized_tensor(%arg0: tensor<1x10x!quant.uniform>) -> tensor<1x10x!quant.uniform> { %0 = "tf.Intermediate"(%arg0) : (tensor<1x10x!quant.uniform>) -> tensor<1x10x!quant.uniform> func.return %0 : tensor<1x10x!quant.uniform> // CHECK: "tfr.cast"(%[[arg0:.*]]) : (tensor<1x10x!quant.uniform>) -> !tfr.tensor // CHECK: "tfr.cast"(%[[result:.*]]) : (!tfr.tensor) -> tensor<1x10x!quant.uniform> } // CHECK-LABEL: decompose_quant_act_range func.func @decompose_quant_act_range() -> !tfr.tensor_list { %scale = arith.constant 0.1 : f32 %zp = arith.constant 42 : i64 %none_attr = tfr.constant "NONE" -> !tfr.attr %relu_attr = tfr.constant "RELU" -> !tfr.attr %relu6_attr = tfr.constant "RELU6" -> !tfr.attr %reluN1_1_attr = tfr.constant "RELU_N1_TO_1" -> !tfr.attr %none:2 = "tfr.quant_act_range"(%none_attr, %scale, %zp) : (!tfr.attr, f32, i64) -> (!tfr.tensor, !tfr.tensor) %relu:2 = "tfr.quant_act_range"(%relu_attr, %scale, %zp) : (!tfr.attr, f32, i64) -> (!tfr.tensor, !tfr.tensor) %relu6:2 = "tfr.quant_act_range"(%relu6_attr, %scale, %zp) : (!tfr.attr, f32, i64) -> (!tfr.tensor, !tfr.tensor) %reluN1_1:2 = "tfr.quant_act_range"(%reluN1_1_attr, %scale, %zp) : (!tfr.attr, f32, i64) -> (!tfr.tensor, !tfr.tensor) %result = "tfr.build_list"( %none#0, %none#1, %relu#0, %relu#1, %relu6#0, %relu6#1, %reluN1_1#0, %reluN1_1#1) : ( !tfr.tensor, !tfr.tensor, !tfr.tensor, !tfr.tensor, !tfr.tensor, !tfr.tensor, !tfr.tensor, !tfr.tensor) -> !tfr.tensor_list func.return %result : !tfr.tensor_list // CHECK-DAG: %[[N_128:.*]] = arith.constant -128 : i32 // CHECK-DAG: %[[N32:.*]] = arith.constant 32 : i32 // CHECK-DAG: %[[N42:.*]] = arith.constant 42 : i32 // CHECK-DAG: %[[N52:.*]] = arith.constant 52 : i32 // CHECK-DAG: %[[N102:.*]] = arith.constant 102 : i32 // CHECK-DAG: %[[N127:.*]] = arith.constant 127 : i32 // CHECK-NEXT: %[[none_min:.*]] = "tfr.constant_tensor"(%[[N_128]]) // CHECK-NEXT: %[[none_max:.*]] = "tfr.constant_tensor"(%[[N127]]) // CHECK-NEXT: %[[relu_min:.*]] = "tfr.constant_tensor"(%[[N42]]) // CHECK-NEXT: %[[relu_max:.*]] = "tfr.constant_tensor"(%[[N127]]) // CHECK-NEXT: %[[relu6_min:.*]] = "tfr.constant_tensor"(%[[N42]]) // CHECK-NEXT: %[[relu6_max:.*]] = "tfr.constant_tensor"(%[[N102]]) // CHECK-NEXT: %[[reluN1_1_min:.*]] = "tfr.constant_tensor"(%[[N32]]) // CHECK-NEXT: %[[reluN1_1_max:.*]] = "tfr.constant_tensor"(%[[N52]]) // CHECK-NEXT: %[[result:.*]] = "tfr.build_list"(%[[none_min]], %[[none_max]], %[[relu_min]], %[[relu_max]], // CHECK-SAME: %[[relu6_min]], %[[relu6_max]], %[[reluN1_1_min]], %[[reluN1_1_max]] // CHECK-NEXT: return %[[result]] } // CHECK-LABEL: decompose_quant_act_range_invalid func.func @decompose_quant_act_range_invalid() -> (!tfr.tensor, !tfr.tensor) { %scale = arith.constant 0.1 : f32 %zp = arith.constant 42 : i64 %elu_attr = tfr.constant "ELU" -> !tfr.attr %min, %max = "tfr.quant_act_range"(%elu_attr, %scale, %zp) : (!tfr.attr, f32, i64) -> (!tfr.tensor, !tfr.tensor) func.return %min, %max : !tfr.tensor, !tfr.tensor // CHECK: %[[elu_attr:.*]] = tfr.constant "ELU" -> !tfr.attr // CHECK: %[[min:.*]], %[[max:.*]] = tfr.quant_act_range(%[[elu_attr]] // CHECK: return %[[min]], %[[max]] } // CHECK-LABEL: decompose_quant_scale_factor func.func @decompose_quant_scale_factor() -> (!tfr.tensor, !tfr.tensor) { %output_scale = arith.constant 0.1 : f32 %input_scale = arith.constant 0.25 : f32 %filter_scale = arith.constant 0.4 : f32 %input_scale_tensor = "tfr.constant_tensor"(%input_scale) : (f32) -> !tfr.tensor %filter_scale_tensor = "tfr.constant_tensor"(%filter_scale) : (f32) -> !tfr.tensor %list = "tfr.build_list"(%input_scale_tensor, %filter_scale_tensor) : (!tfr.tensor, !tfr.tensor) -> !tfr.tensor_list %out = "tfr.quant_scale_factor"(%output_scale, %list) : (f32, !tfr.tensor_list) -> !tfr.tensor %perchannel_scale = arith.constant dense<[0.4, 4.0]> : tensor<2xf32> %perchannel_scale_tensor = "tfr.cast"(%perchannel_scale) : (tensor<2xf32>) -> !tfr.tensor %list2 = "tfr.build_list"(%input_scale_tensor, %perchannel_scale_tensor) : (!tfr.tensor, !tfr.tensor) -> !tfr.tensor_list %perchannel = "tfr.quant_scale_factor"(%output_scale, %list2) : (f32, !tfr.tensor_list) -> !tfr.tensor func.return %out, %perchannel : !tfr.tensor, !tfr.tensor // CHECK-DAG: %[[scale_factors:.*]] = "tf.Const"() <{value = dense<[1.000000e+00, 1.000000e+01]> : tensor<2xf32>}> : () -> tensor<2xf32> // CHECK-DAG: %[[scale_factor:.*]] = "tf.Const"() <{value = dense<1.000000e+00> : tensor}> : () -> tensor // CHECK: %[[cast:.*]] = "tfr.cast"(%[[scale_factor]]) : (tensor) -> !tfr.tensor // CHECK: %[[cast_perchannel:.*]] = "tfr.cast"(%[[scale_factors]]) : (tensor<2xf32>) -> !tfr.tensor // CHECK: return %[[cast]], %[[cast_perchannel]] : !tfr.tensor, !tfr.tensor } // CHECK-LABEL: decompose_quant_scale_factor_invalid func.func @decompose_quant_scale_factor_invalid() -> !tfr.tensor { %output_scale = arith.constant 0.1 : f32 %input_scale = arith.constant 0.25 : f32 %filter_scale = arith.constant 0.4 : f32 %input_scale_tensor = "tfr.constant_tensor"(%input_scale) : (f32) -> !tfr.tensor %filter_scale_tensor = "tfr.constant_tensor"(%filter_scale) : (f32) -> !tfr.tensor %list = "tfr.build_list"(%input_scale_tensor, %filter_scale_tensor, %input_scale_tensor) : (!tfr.tensor, !tfr.tensor, !tfr.tensor) -> !tfr.tensor_list %out = "tfr.quant_scale_factor"(%output_scale, %list) : (f32, !tfr.tensor_list) -> !tfr.tensor func.return %out : !tfr.tensor // CHECK-DAG: %[[cst_0:.*]] = arith.constant 1.000000e-01 : f32 // CHECK-DAG: %[[cst_1:.*]] = "tf.Const"() <{value = dense<2.500000e-01> : tensor}> : () -> tensor // CHECK-DAG: %[[cst_2:.*]] = "tf.Const"() <{value = dense<4.000000e-01> : tensor}> : () -> tensor // CHECK: %[[tfrcast0:.*]] = "tfr.cast"(%[[cst_1]]) : (tensor) -> !tfr.tensor // CHECK: %[[tfrcast1:.*]] = "tfr.cast"(%[[cst_2]]) : (tensor) -> !tfr.tensor // CHECK: %[[list:.*]] = "tfr.build_list"(%[[tfrcast0]], %[[tfrcast1]], %[[tfrcast0]]) : (!tfr.tensor, !tfr.tensor, !tfr.tensor) -> !tfr.tensor_list // CHECK: %[[qsf:.*]] = tfr.quant_scale_factor(%[[cst_0]], %[[list]]) : (f32, !tfr.tensor_list) -> !tfr.tensor // CHECK: return %[[qsf]] : !tfr.tensor } // CHECK-LABEL: decompose_quant_rescale func.func @decompose_quant_rescale(%arg0: tensor<2xi32>) -> !tfr.tensor { %zp = arith.constant 67 : i64 %cst = "tf.Const"() {value = dense<1.0> : tensor} : () -> tensor %scale_factor = "tfr.cast"(%cst) : (tensor) -> !tfr.tensor %input = "tfr.cast"(%arg0) : (tensor<2xi32>) -> !tfr.tensor %rescaled = "tfr.quant_rescale"(%input, %scale_factor, %zp) : (!tfr.tensor, !tfr.tensor, i64) -> !tfr.tensor func.return %rescaled : !tfr.tensor // CHECK-DAG: %[[f32:.*]] = tfr.constant f32 -> !tfr.attr // CHECK-DAG: %[[i32:.*]] = tfr.constant i32 -> !tfr.attr // CHECK-DAG: %[[scale_cst:.*]] = "tf.Const"() <{value = dense<1.000000e+00> : tensor}> : () -> tensor // CHECK-DAG: %false = arith.constant false // CHECK-DAG: %[[zp_cst:.*]] = "tf.Const"() <{value = dense<67> : tensor}> : () -> tensor // CHECK: %[[zp:.*]] = "tfr.cast"(%[[zp_cst]]) : (tensor) -> !tfr.tensor // CHECK: %[[scale:.*]] = "tfr.cast"(%[[scale_cst]]) : (tensor) -> !tfr.tensor // CHECK: %[[input:.*]] = "tfr.cast"(%arg0) : (tensor<2xi32>) -> !tfr.tensor // CHECK: %[[cast:.*]] = tfr.call @tf__cast(%[[input]], %[[f32]], %false) : (!tfr.tensor, !tfr.attr, i1) -> !tfr.tensor // CHECK: %[[rescaled:.*]] = tfr.call @tf__mul(%[[cast]], %[[scale]]) : (!tfr.tensor, !tfr.tensor) -> !tfr.tensor // CHECK: %[[rounded:.*]] = tfr.call @tf__round(%[[rescaled]]) : (!tfr.tensor) -> !tfr.tensor // CHECK: %[[zp_cast:.*]] = tfr.call @tf__cast(%[[zp]], %[[f32]], %false) : (!tfr.tensor, !tfr.attr, i1) -> !tfr.tensor // CHECK: %[[recentered:.*]] = tfr.call @tf__add(%[[rounded]], %[[zp_cast]]) : (!tfr.tensor, !tfr.tensor) -> !tfr.tensor // CHECK: %[[cast_i32:.*]] = tfr.call @tf__cast(%[[recentered]], %[[i32]], %false) : (!tfr.tensor, !tfr.attr, i1) -> !tfr.tensor // CHECK: return %[[cast_i32]] : !tfr.tensor } // CHECK-LABEL: decompose_output_type func.func @decompose_output_type(%arg0: tensor<2xf32>) -> tensor<2xi32> { %0 = "tf.CastFloat"(%arg0) : (tensor<2xf32>) -> tensor<2xi32> func.return %0: tensor<2xi32> // CHECK: %[[i32:.*]] = tfr.constant i32 -> !tfr.attr // CHECK: tfr.call @tf__cast(%[[casted_arg:.*]], %[[i32]], %false) : (!tfr.tensor, !tfr.attr, i1) -> !tfr.tensor }