xref: /aosp_15_r20/external/tensorflow/tensorflow/compiler/tests/reduce_ops_test.py (revision b6fb3261f9314811a0f4371741dbb8839866f948)
1# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ==============================================================================
15"""Tests for reduction operators."""
16
17import functools
18import itertools
19
20from absl.testing import parameterized
21import numpy as np
22
23from tensorflow.compiler.tests import xla_test
24from tensorflow.python.framework import dtypes
25from tensorflow.python.framework import errors_impl
26from tensorflow.python.framework import test_util
27from tensorflow.python.ops import array_ops
28from tensorflow.python.ops import math_ops
29from tensorflow.python.platform import googletest
30
31
32@parameterized.named_parameters(('32_bit_index', dtypes.int32),
33                                ('64_bit_index', dtypes.int64))
34class ReduceOpsTest(xla_test.XLATestCase, parameterized.TestCase):
35  def _testReduction(self,
36                     tf_reduce_fn,
37                     np_reduce_fn,
38                     dtype,
39                     test_inputs,
40                     index_dtype,
41                     rtol=1e-4,
42                     atol=1e-4):
43    """Tests that the output of 'tf_reduce_fn' matches numpy's output."""
44
45    for test_input in test_inputs:
46      with self.session() as sess:
47        with self.test_scope():
48          a = array_ops.placeholder(dtype)
49          index = array_ops.placeholder(index_dtype)
50          out = tf_reduce_fn(a, index)
51        result = sess.run(out, {a: test_input, index: [0]})
52        self.assertAllClose(
53            result, np_reduce_fn(test_input, axis=0), rtol=rtol, atol=atol)
54
55        result = sess.run(out, {a: test_input, index: [1]})
56        self.assertAllClose(
57            result, np_reduce_fn(test_input, axis=1), rtol=rtol, atol=atol)
58
59        result = sess.run(out, {a: test_input, index: [-1]})
60        self.assertAllClose(
61            result, np_reduce_fn(test_input, axis=1), rtol=rtol, atol=atol)
62
63        # MLIR bridge doesn't return the same error so it can't be matched
64        # directly.
65        if not test_util.is_mlir_bridge_enabled():
66          with self.assertRaisesWithPredicateMatch(
67              errors_impl.InvalidArgumentError, 'Invalid reduction dim'):
68            sess.run(out, {a: test_input, index: [-33]})
69
70          with self.assertRaisesWithPredicateMatch(
71              errors_impl.InvalidArgumentError, 'Invalid reduction dim'):
72            sess.run(out, {a: test_input, index: [2]})
73
74  REAL_DATA = [
75      np.zeros(shape=(2, 0)),
76      np.zeros(shape=(0, 30)),
77      np.arange(1, 7).reshape(2, 3),
78      np.arange(-10, -4).reshape(2, 3),
79      np.arange(-4, 2).reshape(2, 3),
80  ]
81  COMPLEX_DATA = [
82      np.zeros(shape=(2, 0)).astype(np.complex64),
83      np.zeros(shape=(0, 30)).astype(np.complex64),
84      np.arange(1, 13, dtype=np.float32).view(np.complex64).reshape(2, 3),
85      np.arange(-14, -2, dtype=np.float32).view(np.complex64).reshape(2, 3),
86      np.arange(-4, 8, dtype=np.float32).view(np.complex64).reshape(2, 3),
87  ]
88  NONEMPTY_REAL_DATA = [x for x in REAL_DATA if np.size(x) > 0]
89  NONEMPTY_COMPLEX_DATA = [x for x in COMPLEX_DATA if np.size(x) > 0]
90  BOOL_DATA = [
91      np.array([], dtype=np.bool_).reshape(2, 0),
92      np.array([], dtype=np.bool_).reshape(0, 3),
93      np.array([[False, True, False], [True, True, False]]),
94  ]
95  ONES = [np.ones([34000, 2])]
96
97  def testReduceSumF32(self, index_dtype):
98    self._testReduction(math_ops.reduce_sum, np.sum, np.float32, self.REAL_DATA,
99                        index_dtype)
100
101  def testReduceSumC64(self, index_dtype):
102    self._testReduction(math_ops.reduce_sum, np.sum, np.complex64,
103                        self.COMPLEX_DATA, index_dtype)
104
105  def testReduceProdF32(self, index_dtype):
106    self._testReduction(math_ops.reduce_prod, np.prod, np.float32,
107                        self.REAL_DATA, index_dtype)
108
109  def testReduceProdC64(self, index_dtype):
110    self._testReduction(math_ops.reduce_prod, np.prod, np.complex64,
111                        self.COMPLEX_DATA, index_dtype)
112
113  def testReduceMin(self, index_dtype):
114
115    def reference_min(dtype, inp, axis):
116      """Wrapper around np.amin that returns +infinity for an empty input."""
117      if inp.shape[axis] == 0:
118        if np.issubdtype(dtype, np.floating):
119          return np.full(inp.shape[0:axis] + inp.shape[axis + 1:], float('inf'))
120        return np.full(inp.shape[0:axis] + inp.shape[axis + 1:],
121                       np.iinfo(dtype).max)
122      return np.amin(inp, axis)
123
124    for dtype in set(self.all_types).intersection(
125        [np.float32, np.int32, np.int64]):
126      self._testReduction(math_ops.reduce_min,
127                          functools.partial(reference_min, dtype), dtype,
128                          self.REAL_DATA, index_dtype)
129
130  def testReduceMax(self, index_dtype):
131
132    def reference_max(dtype, inp, axis):
133      """Wrapper around np.amax that returns -infinity for an empty input."""
134      if inp.shape[axis] == 0:
135        if np.issubdtype(dtype, np.floating):
136          return np.full(inp.shape[0:axis] + inp.shape[axis + 1:],
137                         float('-inf'))
138        return np.full(inp.shape[0:axis] + inp.shape[axis + 1:],
139                       np.iinfo(dtype).min)
140      return np.amax(inp, axis)
141
142    for dtype in set(self.all_types).intersection(
143        [np.float32, np.int32, np.int64]):
144      self._testReduction(math_ops.reduce_max,
145                          functools.partial(reference_max, dtype), dtype,
146                          self.REAL_DATA, index_dtype)
147
148  def testReduceMeanF32(self, index_dtype):
149    # TODO(phawkins): mean on XLA currently returns 0 instead of NaN when
150    # reducing across zero inputs.
151    self._testReduction(math_ops.reduce_mean, np.mean, np.float32,
152                        self.NONEMPTY_REAL_DATA, index_dtype)
153
154  def testReduceMeanF16(self, index_dtype):
155    if np.float16 in self.all_types:
156      self._testReduction(math_ops.reduce_mean, np.mean, np.float16, self.ONES,
157                          index_dtype)
158
159  def testReduceMeanC64(self, index_dtype):
160    self._testReduction(math_ops.reduce_mean, np.mean, np.complex64,
161                        self.NONEMPTY_COMPLEX_DATA, index_dtype)
162
163  def testReduceAll(self, index_dtype):
164    self._testReduction(math_ops.reduce_all, np.all, np.bool_, self.BOOL_DATA,
165                        index_dtype)
166
167  def testReduceAny(self, index_dtype):
168    self._testReduction(math_ops.reduce_any, np.any, np.bool_, self.BOOL_DATA,
169                        index_dtype)
170
171  @test_util.disable_mlir_bridge('Error messages differ')
172  def testReduceSumWithDuplicateAxes(self, index_dtype):
173    with self.session() as sess:
174      with self.test_scope():
175        a = array_ops.placeholder(np.float32)
176        index = array_ops.placeholder(np.int32)
177        out = math_ops.reduce_sum(a, index)
178      with self.assertRaisesWithPredicateMatch(
179          errors_impl.InvalidArgumentError,
180          'Axes contains duplicate dimension'):
181        sess.run(out, {a: [10, 20, 30], index: [0, 0]})
182
183
184class ReduceOpPrecisionTest(xla_test.XLATestCase):
185
186  def _testReduceSum(self,
187                     expected_result,
188                     dtype,
189                     test_inputs,
190                     rtol=1e-3,
191                     atol=1e-4):
192    """Tests reduce sum on a list of input arrays.
193
194    For each array in test_inputs, check that performing reduce sum on the array
195    produces a value that is close to the expected result.
196
197    Args:
198      expected_result: the expected result.
199      dtype: the data type of the reduce sum operation.
200      test_inputs: a list of input arrays for the reduce sum operation.
201      rtol: the relative error.
202      atol: the absolute error.
203    """
204
205    for test_input in test_inputs:
206      with self.session() as sess:
207        with self.test_scope():
208          a = array_ops.placeholder(dtype)
209          index = array_ops.placeholder(dtypes.int32)
210          out = math_ops.reduce_sum(a, index)
211        result = sess.run(out, {
212            a: np.array(test_input, dtype=dtype),
213            index: [0]
214        })
215        # Compare the results using float32 type.
216        self.assertAllClose(
217            np.float32(result),
218            np.float32(expected_result),
219            rtol=rtol,
220            atol=atol)
221
222  def testReduceSumF16(self):
223    """Tests the reduce sum of float16 doesn't lose too much precision."""
224
225    if np.float16 not in self.all_types:
226      return
227
228    f16_max = np.finfo(np.float16).max
229    self._testReduceSum(
230        f16_max, np.float16,
231        itertools.permutations([f16_max, f16_max, f16_max * (-1.0)], 3))
232
233  def testReduceSumBF16(self):
234    """Tests the reduce sum of bfloat16 doesn't lose too much precision."""
235
236    if dtypes.bfloat16.as_numpy_dtype not in self.all_types:
237      return
238
239    bf16_max = np.float32(dtypes.bfloat16.max)
240    f32_max = dtypes.float32.max
241    value = min(bf16_max, f32_max - bf16_max) / 2
242    self._testReduceSum(
243        dtypes.bfloat16.as_numpy_dtype(value), dtypes.bfloat16.as_numpy_dtype,
244        itertools.permutations([bf16_max, value, bf16_max * (-1.0)], 3))
245
246
247if __name__ == '__main__':
248  googletest.main()
249