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