1 /* Copyright 2018 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
16 // Need to #include Eigen's Tensor class first because Eigen/CXX11/FixedPoint
17 // depends on the file but doesn't include it. This breaks compilation on
18 // clang.
19 // clang-format off
20 #include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor"
21 // clang-format on
22 #include "third_party/eigen3/unsupported/Eigen/CXX11/FixedPoint"
23 #include "tensorflow/core/kernels/eigen_contraction_kernel.h"
24 #include "tensorflow/core/platform/test.h"
25
26 namespace Eigen {
27 namespace internal {
28
29 namespace {
30 template <typename Index, int NumDims>
RandomDims(int min_dim=1,int max_dim=20)31 Eigen::array<Index, NumDims> RandomDims(int min_dim = 1, int max_dim = 20) {
32 Eigen::array<Index, NumDims> dims;
33 for (int i = 0; i < NumDims; ++i) {
34 dims[i] = internal::random<int>(min_dim, max_dim);
35 }
36 return dims;
37 }
38 } // namespace
39
40 using Scalar = float;
41 using Index = Eigen::Index;
42
TEST(EigenMkldnnTest,GemmPackColMajor)43 TEST(EigenMkldnnTest, GemmPackColMajor) {
44 // Packing with gemm_pack_colmajor_block is the same as taking a slice of 2
45 // dimensional Tensor.
46
47 // Mkldnn pack and gemm are used only in Tensor contractions, and it's
48 // guaranteed that Tensors will have ColMajor layout.
49 static const int Options = ColMajor;
50
51 using DataMapper = blas_data_mapper<Scalar, Index, ColMajor>;
52 using GemmPackColMajor =
53 gemm_pack_colmajor_block<Scalar, Index, DataMapper, ColMajor>;
54 using Tensor2d = Tensor<Scalar, 2, Options, Index>;
55
56 Eigen::array<Index, 2> dims = RandomDims<Index, 2>(1, 500);
57
58 // Create a tensor initialized with random data.
59 Tensor2d src(dims);
60 src.setRandom();
61
62 // Pick a random slice of src tensor.
63 Eigen::array<Index, 2> slice_start = RandomDims<Index, 2>(0, 250);
64 Eigen::array<Index, 2> slice_size = RandomDims<Index, 2>(100, 500);
65
66 // Make sure that slice start + size do not overflow tensor dims.
67 for (int i = 0; i < 2; ++i) {
68 slice_start[i] = numext::mini(dims[i] - 1, slice_start[i]);
69 slice_size[i] = numext::mini(slice_size[i], dims[i] - slice_start[i]);
70 }
71
72 // Prepare tensors for packing and slicing results.
73 Tensor2d pack_dst(slice_size[0], slice_size[1]);
74 Tensor2d slice_dst(slice_size[0], slice_size[1]);
75
76 // Pack memory using gemm_pack_colmajor_block.
77 DataMapper data_mapper(src.data(), dims[0]);
78 GemmPackColMajor gemm_pack;
79 gemm_pack(pack_dst.data(),
80 data_mapper.getSubMapper(slice_start[0], slice_start[1]),
81 slice_size[0], slice_size[1]);
82
83 // Slice the source tensor.
84 slice_dst = src.slice(slice_start, slice_size);
85
86 // Verify that dst tensors are equal.
87 EXPECT_EQ(pack_dst.dimensions().TotalSize(),
88 slice_dst.dimensions().TotalSize());
89 for (size_t i = 0; i < pack_dst.dimensions().TotalSize(); ++i) {
90 Scalar packed = pack_dst.coeff(i);
91 Scalar sliced = slice_dst.coeff(i);
92 EXPECT_EQ(packed, sliced);
93 }
94 }
95
TEST(EigenMkldnnTest,MkldnnGemm)96 TEST(EigenMkldnnTest, MkldnnGemm) {
97 // Mkldnn pack and gemm are used only in Tensor contractions, and it's
98 // guaranteed that Tensors will have ColMajor layout.
99 static const int Options = ColMajor;
100
101 using Tensor2d = Tensor<Scalar, 2, Options, Index>;
102
103 int m = internal::random<int>(1, 100);
104 int n = internal::random<int>(1, 100);
105 int k = internal::random<int>(1, 100);
106
107 Tensor2d lhs(m, k);
108 lhs.setRandom();
109
110 Tensor2d rhs(k, n);
111 rhs.setRandom();
112
113 // Compute matmul with mkldnn gemm kernel.
114 using OutputMapper = blas_data_mapper<Scalar, Index, ColMajor>;
115 using MkldnnGemmKernel =
116 dnnl_gemm_kernel<Scalar, Index, OutputMapper, ColMajor>;
117
118 Tensor2d mkldnn_result(m, n);
119 mkldnn_result.setRandom();
120 OutputMapper output_mapper(mkldnn_result.data(), m);
121
122 MkldnnGemmKernel gemm_kernel;
123 gemm_kernel(output_mapper, lhs.data(), rhs.data(), m, k, n, /*alpha=*/1.0,
124 /*beta=*/0.0);
125
126 // Compute matmul with Eigen::Matrix.
127 using Matrix = Eigen::Matrix<Scalar, Dynamic, Dynamic, ColMajor>;
128 using MatrixMap = Map<Eigen::Matrix<Scalar, Dynamic, Dynamic, ColMajor>>;
129
130 MatrixMap lhs_mat(lhs.data(), m, k);
131 MatrixMap rhs_mat(rhs.data(), k, n);
132
133 Matrix matmul_result(m, n);
134 matmul_result.setZero();
135 matmul_result = lhs_mat * rhs_mat;
136
137 // Verify that results are equal.
138 for (Index i = 0; i < m * n; ++i) {
139 Scalar gemm = mkldnn_result(i);
140 Scalar matmul = matmul_result(i % m, i / m);
141
142 Scalar delta = std::abs(gemm - matmul);
143
144 // NOTE(rmlarsen): Compute proper forward error bound.
145 Scalar sum = Scalar(0.0);
146 for (int k1 = 0; k1 < k; ++k1) {
147 sum += std::abs(lhs_mat(i % m, k1) * rhs_mat(k1, i / m));
148 }
149 Scalar epsilon = std::numeric_limits<Scalar>::epsilon();
150 Scalar upper_bound = Scalar(1.01) * epsilon * k * sum;
151
152 EXPECT_LE(delta, upper_bound);
153 }
154 }
155
TEST(EigenMkldnnTest,MkldnnGemmQInt8xQUInt8)156 TEST(EigenMkldnnTest, MkldnnGemmQInt8xQUInt8) {
157 // Mkldnn pack and gemm are used only in Tensor contractions, and it's
158 // guaranteed that Tensors will have ColMajor layout.
159 static const int Options = ColMajor;
160
161 using Tensor2dQInt8 = Eigen::Tensor<Eigen::QInt8, 2, Options, Index>;
162 using Tensor2dQUInt8 = Eigen::Tensor<Eigen::QUInt8, 2, Options, Index>;
163 using Tensor2dQInt32 = Eigen::Tensor<Eigen::QInt32, 2, Options, Index>;
164
165 int m = internal::random<int>(1, 1000);
166 int n = internal::random<int>(1, 1000);
167 int k = internal::random<int>(1, 1000);
168
169 Tensor2dQInt8 lhs(m, k);
170 lhs.setRandom();
171
172 Tensor2dQUInt8 rhs(k, n);
173 rhs.setRandom();
174 // NOTE: 's8*u8 + s8*u8 -> s16' saturation might lead to incorrect results. In
175 // practice in FusedConv2DBiasActivationKernel we use 7 bit inputs.
176 rhs = rhs.clip(0, 127);
177
178 Eigen::array<Eigen::IndexPair<Eigen::DenseIndex>, 1> contract_dims;
179 contract_dims[0].first = 1;
180 contract_dims[0].second = 0;
181
182 Tensor2dQInt32 res = lhs.contract(rhs, contract_dims);
183
184 // Compute matmul with Eigen::Matrix. We explicitly cast inputs to int32_t not
185 // to test QInt8->QInt32 type promotion during accumulation.
186 using Matrix = Eigen::Matrix<int32_t, Dynamic, Dynamic, ColMajor>;
187
188 Matrix lhs_mat(m, k);
189 Matrix rhs_mat(k, n);
190
191 for (int i = 0; i < m; ++i) {
192 for (int j = 0; j < k; ++j) {
193 lhs_mat(i, j) = static_cast<int32_t>(lhs(i, j));
194 }
195 }
196
197 for (int i = 0; i < k; ++i) {
198 for (int j = 0; j < n; ++j) {
199 rhs_mat(i, j) = static_cast<int32_t>(rhs(i, j));
200 }
201 }
202
203 Matrix matmul_result(m, n);
204 matmul_result.setZero();
205 matmul_result = lhs_mat * rhs_mat;
206
207 // Verify that results are equal.
208 for (Index i = 0; i < m; ++i) {
209 for (Index j = 0; j < n; ++j) {
210 Scalar gemm = res(i, j);
211 Scalar matmul = matmul_result(i, j);
212 EXPECT_EQ(gemm, matmul);
213 }
214 }
215 }
216
217 } // namespace internal
218 } // namespace Eigen
219