1 // This file is part of Eigen, a lightweight C++ template library
2 // for linear algebra.
3 //
4 // This Source Code Form is subject to the terms of the Mozilla
5 // Public License v. 2.0. If a copy of the MPL was not distributed
6 // with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
7
8 // clang-format off
9 #include "main.h"
10 #include <Eigen/CXX11/Tensor>
11 // clang-format on
12
13 // -------------------------------------------------------------------------- //
14 // A set of tests for TensorBlockIO: copying data between tensor blocks.
15
16 template <int NumDims>
RandomDims(Index min,Index max)17 static DSizes<Index, NumDims> RandomDims(Index min, Index max) {
18 DSizes<Index, NumDims> dims;
19 for (int i = 0; i < NumDims; ++i) {
20 dims[i] = internal::random<Index>(min, max);
21 }
22 return DSizes<Index, NumDims>(dims);
23 }
24
RandomBlockShape()25 static internal::TensorBlockShapeType RandomBlockShape() {
26 return internal::random<bool>()
27 ? internal::TensorBlockShapeType::kUniformAllDims
28 : internal::TensorBlockShapeType::kSkewedInnerDims;
29 }
30
31 template <int NumDims>
RandomTargetBlockSize(const DSizes<Index,NumDims> & dims)32 static size_t RandomTargetBlockSize(const DSizes<Index, NumDims>& dims) {
33 return internal::random<size_t>(1, dims.TotalSize());
34 }
35
36 template <int Layout, int NumDims>
GetInputIndex(Index output_index,const array<Index,NumDims> & output_to_input_dim_map,const array<Index,NumDims> & input_strides,const array<Index,NumDims> & output_strides)37 static Index GetInputIndex(Index output_index,
38 const array<Index, NumDims>& output_to_input_dim_map,
39 const array<Index, NumDims>& input_strides,
40 const array<Index, NumDims>& output_strides) {
41 int input_index = 0;
42 if (Layout == ColMajor) {
43 for (int i = NumDims - 1; i > 0; --i) {
44 const Index idx = output_index / output_strides[i];
45 input_index += idx * input_strides[output_to_input_dim_map[i]];
46 output_index -= idx * output_strides[i];
47 }
48 return input_index +
49 output_index * input_strides[output_to_input_dim_map[0]];
50 } else {
51 for (int i = 0; i < NumDims - 1; ++i) {
52 const Index idx = output_index / output_strides[i];
53 input_index += idx * input_strides[output_to_input_dim_map[i]];
54 output_index -= idx * output_strides[i];
55 }
56 return input_index +
57 output_index * input_strides[output_to_input_dim_map[NumDims - 1]];
58 }
59 }
60
61 template <typename T, int NumDims, int Layout>
test_block_io_copy_data_from_source_to_target()62 static void test_block_io_copy_data_from_source_to_target() {
63 using TensorBlockIO = internal::TensorBlockIO<T, Index, NumDims, Layout>;
64 using IODst = typename TensorBlockIO::Dst;
65 using IOSrc = typename TensorBlockIO::Src;
66
67 // Generate a random input Tensor.
68 DSizes<Index, NumDims> dims = RandomDims<NumDims>(1, 30);
69 Tensor<T, NumDims, Layout> input(dims);
70 input.setRandom();
71
72 // Write data to an output Tensor.
73 Tensor<T, NumDims, Layout> output(dims);
74
75 // Construct a tensor block mapper.
76 using TensorBlockMapper =
77 internal::TensorBlockMapper<NumDims, Layout, Index>;
78 TensorBlockMapper block_mapper(
79 dims, {RandomBlockShape(), RandomTargetBlockSize(dims), {0, 0, 0}});
80
81 // We will copy data from input to output through this buffer.
82 Tensor<T, NumDims, Layout> block(block_mapper.blockDimensions());
83
84 // Precompute strides for TensorBlockIO::Copy.
85 auto input_strides = internal::strides<Layout>(dims);
86 auto output_strides = internal::strides<Layout>(dims);
87
88 const T* input_data = input.data();
89 T* output_data = output.data();
90 T* block_data = block.data();
91
92 for (int i = 0; i < block_mapper.blockCount(); ++i) {
93 auto desc = block_mapper.blockDescriptor(i);
94
95 auto blk_dims = desc.dimensions();
96 auto blk_strides = internal::strides<Layout>(blk_dims);
97
98 {
99 // Read from input into a block buffer.
100 IODst dst(blk_dims, blk_strides, block_data, 0);
101 IOSrc src(input_strides, input_data, desc.offset());
102
103 TensorBlockIO::Copy(dst, src);
104 }
105
106 {
107 // Write from block buffer to output.
108 IODst dst(blk_dims, output_strides, output_data, desc.offset());
109 IOSrc src(blk_strides, block_data, 0);
110
111 TensorBlockIO::Copy(dst, src);
112 }
113 }
114
115 for (int i = 0; i < dims.TotalSize(); ++i) {
116 VERIFY_IS_EQUAL(input_data[i], output_data[i]);
117 }
118 }
119
120 template <typename T, int NumDims, int Layout>
test_block_io_copy_using_reordered_dimensions()121 static void test_block_io_copy_using_reordered_dimensions() {
122 // Generate a random input Tensor.
123 DSizes<Index, NumDims> dims = RandomDims<NumDims>(1, 30);
124 Tensor<T, NumDims, Layout> input(dims);
125 input.setRandom();
126
127 // Create a random dimension re-ordering/shuffle.
128 std::vector<int> shuffle;
129
130 for (int i = 0; i < NumDims; ++i) shuffle.push_back(i);
131 std::shuffle(shuffle.begin(), shuffle.end(), std::mt19937(g_seed));
132
133 DSizes<Index, NumDims> output_tensor_dims;
134 DSizes<Index, NumDims> input_to_output_dim_map;
135 DSizes<Index, NumDims> output_to_input_dim_map;
136 for (Index i = 0; i < NumDims; ++i) {
137 output_tensor_dims[shuffle[i]] = dims[i];
138 input_to_output_dim_map[i] = shuffle[i];
139 output_to_input_dim_map[shuffle[i]] = i;
140 }
141
142 // Write data to an output Tensor.
143 Tensor<T, NumDims, Layout> output(output_tensor_dims);
144
145 // Construct a tensor block mapper.
146 // NOTE: Tensor block mapper works with shuffled dimensions.
147 using TensorBlockMapper =
148 internal::TensorBlockMapper<NumDims, Layout, Index>;
149 TensorBlockMapper block_mapper(output_tensor_dims,
150 {RandomBlockShape(),
151 RandomTargetBlockSize(output_tensor_dims),
152 {0, 0, 0}});
153
154 // We will copy data from input to output through this buffer.
155 Tensor<T, NumDims, Layout> block(block_mapper.blockDimensions());
156
157 // Precompute strides for TensorBlockIO::Copy.
158 auto input_strides = internal::strides<Layout>(dims);
159 auto output_strides = internal::strides<Layout>(output_tensor_dims);
160
161 const T* input_data = input.data();
162 T* output_data = output.data();
163 T* block_data = block.data();
164
165 for (Index i = 0; i < block_mapper.blockCount(); ++i) {
166 auto desc = block_mapper.blockDescriptor(i);
167
168 const Index first_coeff_index = GetInputIndex<Layout, NumDims>(
169 desc.offset(), output_to_input_dim_map, input_strides,
170 output_strides);
171
172 // NOTE: Block dimensions are in the same order as output dimensions.
173
174 using TensorBlockIO = internal::TensorBlockIO<T, Index, NumDims, Layout>;
175 using IODst = typename TensorBlockIO::Dst;
176 using IOSrc = typename TensorBlockIO::Src;
177
178 auto blk_dims = desc.dimensions();
179 auto blk_strides = internal::strides<Layout>(blk_dims);
180
181 {
182 // Read from input into a block buffer.
183 IODst dst(blk_dims, blk_strides, block_data, 0);
184 IOSrc src(input_strides, input_data, first_coeff_index);
185
186 // TODO(ezhulenev): Remove when fully switched to TensorBlock.
187 DSizes<int, NumDims> dim_map;
188 for (int j = 0; j < NumDims; ++j)
189 dim_map[j] = static_cast<int>(output_to_input_dim_map[j]);
190 TensorBlockIO::Copy(dst, src, /*dst_to_src_dim_map=*/dim_map);
191 }
192
193 {
194 // We need to convert block dimensions from output to input order.
195 auto dst_dims = blk_dims;
196 for (int out_dim = 0; out_dim < NumDims; ++out_dim) {
197 dst_dims[output_to_input_dim_map[out_dim]] = blk_dims[out_dim];
198 }
199
200 // Write from block buffer to output.
201 IODst dst(dst_dims, input_strides, output_data, first_coeff_index);
202 IOSrc src(blk_strides, block_data, 0);
203
204 // TODO(ezhulenev): Remove when fully switched to TensorBlock.
205 DSizes<int, NumDims> dim_map;
206 for (int j = 0; j < NumDims; ++j)
207 dim_map[j] = static_cast<int>(input_to_output_dim_map[j]);
208 TensorBlockIO::Copy(dst, src, /*dst_to_src_dim_map=*/dim_map);
209 }
210 }
211
212 for (Index i = 0; i < dims.TotalSize(); ++i) {
213 VERIFY_IS_EQUAL(input_data[i], output_data[i]);
214 }
215 }
216
217 // This is the special case for reading data with reordering, when dimensions
218 // before/after reordering are the same. Squeezing reads along inner dimensions
219 // in this case is illegal, because we reorder innermost dimension.
220 template <int Layout>
test_block_io_copy_using_reordered_dimensions_do_not_squeeze()221 static void test_block_io_copy_using_reordered_dimensions_do_not_squeeze() {
222 DSizes<Index, 3> tensor_dims(7, 9, 7);
223 DSizes<Index, 3> block_dims = tensor_dims;
224
225 DSizes<int, 3> block_to_tensor_dim;
226 block_to_tensor_dim[0] = 2;
227 block_to_tensor_dim[1] = 1;
228 block_to_tensor_dim[2] = 0;
229
230 auto tensor_strides = internal::strides<Layout>(tensor_dims);
231 auto block_strides = internal::strides<Layout>(block_dims);
232
233 Tensor<float, 3, Layout> block(block_dims);
234 Tensor<float, 3, Layout> tensor(tensor_dims);
235 tensor.setRandom();
236
237 float* tensor_data = tensor.data();
238 float* block_data = block.data();
239
240 using TensorBlockIO = internal::TensorBlockIO<float, Index, 3, Layout>;
241 using IODst = typename TensorBlockIO::Dst;
242 using IOSrc = typename TensorBlockIO::Src;
243
244 // Read from a tensor into a block.
245 IODst dst(block_dims, block_strides, block_data, 0);
246 IOSrc src(tensor_strides, tensor_data, 0);
247
248 TensorBlockIO::Copy(dst, src, /*dst_to_src_dim_map=*/block_to_tensor_dim);
249
250 TensorMap<Tensor<float, 3, Layout> > block_tensor(block_data, block_dims);
251 TensorMap<Tensor<float, 3, Layout> > tensor_tensor(tensor_data, tensor_dims);
252
253 for (Index d0 = 0; d0 < tensor_dims[0]; ++d0) {
254 for (Index d1 = 0; d1 < tensor_dims[1]; ++d1) {
255 for (Index d2 = 0; d2 < tensor_dims[2]; ++d2) {
256 float block_value = block_tensor(d2, d1, d0);
257 float tensor_value = tensor_tensor(d0, d1, d2);
258 VERIFY_IS_EQUAL(block_value, tensor_value);
259 }
260 }
261 }
262 }
263
264 // This is the special case for reading data with reordering, when dimensions
265 // before/after reordering are the same. Squeezing reads in this case is allowed
266 // because we reorder outer dimensions.
267 template <int Layout>
test_block_io_copy_using_reordered_dimensions_squeeze()268 static void test_block_io_copy_using_reordered_dimensions_squeeze() {
269 DSizes<Index, 4> tensor_dims(7, 5, 9, 9);
270 DSizes<Index, 4> block_dims = tensor_dims;
271
272 DSizes<int, 4> block_to_tensor_dim;
273 block_to_tensor_dim[0] = 0;
274 block_to_tensor_dim[1] = 1;
275 block_to_tensor_dim[2] = 3;
276 block_to_tensor_dim[3] = 2;
277
278 auto tensor_strides = internal::strides<Layout>(tensor_dims);
279 auto block_strides = internal::strides<Layout>(block_dims);
280
281 Tensor<float, 4, Layout> block(block_dims);
282 Tensor<float, 4, Layout> tensor(tensor_dims);
283 tensor.setRandom();
284
285 float* tensor_data = tensor.data();
286 float* block_data = block.data();
287
288 using TensorBlockIO = internal::TensorBlockIO<float, Index, 4, Layout>;
289 using IODst = typename TensorBlockIO::Dst;
290 using IOSrc = typename TensorBlockIO::Src;
291
292 // Read from a tensor into a block.
293 IODst dst(block_dims, block_strides, block_data, 0);
294 IOSrc src(tensor_strides, tensor_data, 0);
295
296 TensorBlockIO::Copy(dst, src, /*dst_to_src_dim_map=*/block_to_tensor_dim);
297
298 TensorMap<Tensor<float, 4, Layout> > block_tensor(block_data, block_dims);
299 TensorMap<Tensor<float, 4, Layout> > tensor_tensor(tensor_data, tensor_dims);
300
301 for (Index d0 = 0; d0 < tensor_dims[0]; ++d0) {
302 for (Index d1 = 0; d1 < tensor_dims[1]; ++d1) {
303 for (Index d2 = 0; d2 < tensor_dims[2]; ++d2) {
304 for (Index d3 = 0; d3 < tensor_dims[3]; ++d3) {
305 float block_value = block_tensor(d0, d1, d3, d2);
306 float tensor_value = tensor_tensor(d0, d1, d2, d3);
307 VERIFY_IS_EQUAL(block_value, tensor_value);
308 }
309 }
310 }
311 }
312 }
313
314 template <int Layout>
test_block_io_zero_stride()315 static void test_block_io_zero_stride() {
316 DSizes<Index, 5> rnd_dims = RandomDims<5>(1, 30);
317
318 DSizes<Index, 5> input_tensor_dims = rnd_dims;
319 input_tensor_dims[0] = 1;
320 input_tensor_dims[2] = 1;
321 input_tensor_dims[4] = 1;
322
323 Tensor<float, 5, Layout> input(input_tensor_dims);
324 input.setRandom();
325
326 DSizes<Index, 5> output_tensor_dims = rnd_dims;
327
328 auto input_tensor_strides = internal::strides<Layout>(input_tensor_dims);
329 auto output_tensor_strides = internal::strides<Layout>(output_tensor_dims);
330
331 auto input_tensor_strides_with_zeros = input_tensor_strides;
332 input_tensor_strides_with_zeros[0] = 0;
333 input_tensor_strides_with_zeros[2] = 0;
334 input_tensor_strides_with_zeros[4] = 0;
335
336 Tensor<float, 5, Layout> output(output_tensor_dims);
337 output.setRandom();
338
339 using TensorBlockIO = internal::TensorBlockIO<float, Index, 5, Layout>;
340 using IODst = typename TensorBlockIO::Dst;
341 using IOSrc = typename TensorBlockIO::Src;
342
343 // Write data from input to output with broadcasting in dims [0, 2, 4].
344 IODst dst(output_tensor_dims, output_tensor_strides, output.data(), 0);
345 IOSrc src(input_tensor_strides_with_zeros, input.data(), 0);
346 TensorBlockIO::Copy(dst, src);
347
348 for (int i = 0; i < output_tensor_dims[0]; ++i) {
349 for (int j = 0; j < output_tensor_dims[1]; ++j) {
350 for (int k = 0; k < output_tensor_dims[2]; ++k) {
351 for (int l = 0; l < output_tensor_dims[3]; ++l) {
352 for (int m = 0; m < output_tensor_dims[4]; ++m) {
353 float input_value = input(0, j, 0, l, 0);
354 float output_value = output(i, j, k, l, m);
355 VERIFY_IS_EQUAL(input_value, output_value);
356 }
357 }
358 }
359 }
360 }
361 }
362
363 template <int Layout>
test_block_io_squeeze_ones()364 static void test_block_io_squeeze_ones() {
365 using TensorBlockIO = internal::TensorBlockIO<float, Index, 5, Layout>;
366 using IODst = typename TensorBlockIO::Dst;
367 using IOSrc = typename TensorBlockIO::Src;
368
369 // Total size > 1.
370 {
371 DSizes<Index, 5> block_sizes(1, 2, 1, 2, 1);
372 auto strides = internal::strides<Layout>(block_sizes);
373
374 // Create a random input tensor.
375 Tensor<float, 5> input(block_sizes);
376 input.setRandom();
377
378 Tensor<float, 5> output(block_sizes);
379
380 IODst dst(block_sizes, strides, output.data(), 0);
381 IOSrc src(strides, input.data());
382 TensorBlockIO::Copy(dst, src);
383
384 for (Index i = 0; i < block_sizes.TotalSize(); ++i) {
385 VERIFY_IS_EQUAL(output.data()[i], input.data()[i]);
386 }
387 }
388
389 // Total size == 1.
390 {
391 DSizes<Index, 5> block_sizes(1, 1, 1, 1, 1);
392 auto strides = internal::strides<Layout>(block_sizes);
393
394 // Create a random input tensor.
395 Tensor<float, 5> input(block_sizes);
396 input.setRandom();
397
398 Tensor<float, 5> output(block_sizes);
399
400 IODst dst(block_sizes, strides, output.data(), 0);
401 IOSrc src(strides, input.data());
402 TensorBlockIO::Copy(dst, src);
403
404 for (Index i = 0; i < block_sizes.TotalSize(); ++i) {
405 VERIFY_IS_EQUAL(output.data()[i], input.data()[i]);
406 }
407 }
408 }
409
410 #define CALL_SUBTESTS(NAME) \
411 CALL_SUBTEST((NAME<float, 1, RowMajor>())); \
412 CALL_SUBTEST((NAME<float, 2, RowMajor>())); \
413 CALL_SUBTEST((NAME<float, 4, RowMajor>())); \
414 CALL_SUBTEST((NAME<float, 5, RowMajor>())); \
415 CALL_SUBTEST((NAME<float, 1, ColMajor>())); \
416 CALL_SUBTEST((NAME<float, 2, ColMajor>())); \
417 CALL_SUBTEST((NAME<float, 4, ColMajor>())); \
418 CALL_SUBTEST((NAME<float, 5, ColMajor>())); \
419 CALL_SUBTEST((NAME<bool, 1, RowMajor>())); \
420 CALL_SUBTEST((NAME<bool, 2, RowMajor>())); \
421 CALL_SUBTEST((NAME<bool, 4, RowMajor>())); \
422 CALL_SUBTEST((NAME<bool, 5, RowMajor>())); \
423 CALL_SUBTEST((NAME<bool, 1, ColMajor>())); \
424 CALL_SUBTEST((NAME<bool, 2, ColMajor>())); \
425 CALL_SUBTEST((NAME<bool, 4, ColMajor>())); \
426 CALL_SUBTEST((NAME<bool, 5, ColMajor>()))
427
EIGEN_DECLARE_TEST(cxx11_tensor_block_io)428 EIGEN_DECLARE_TEST(cxx11_tensor_block_io) {
429 // clang-format off
430 CALL_SUBTESTS(test_block_io_copy_data_from_source_to_target);
431 CALL_SUBTESTS(test_block_io_copy_using_reordered_dimensions);
432
433 CALL_SUBTEST(test_block_io_copy_using_reordered_dimensions_do_not_squeeze<RowMajor>());
434 CALL_SUBTEST(test_block_io_copy_using_reordered_dimensions_do_not_squeeze<ColMajor>());
435
436 CALL_SUBTEST(test_block_io_copy_using_reordered_dimensions_squeeze<RowMajor>());
437 CALL_SUBTEST(test_block_io_copy_using_reordered_dimensions_squeeze<ColMajor>());
438
439 CALL_SUBTEST(test_block_io_zero_stride<RowMajor>());
440 CALL_SUBTEST(test_block_io_zero_stride<ColMajor>());
441
442 CALL_SUBTEST(test_block_io_squeeze_ones<RowMajor>());
443 CALL_SUBTEST(test_block_io_squeeze_ones<ColMajor>());
444 // clang-format on
445 }
446