• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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