| // This file is part of Eigen, a lightweight C++ template library | |
| // for linear algebra. | |
| // | |
| // Copyright (C) 2014 Benoit Steiner <benoit.steiner.goog@gmail.com> | |
| // | |
| // This Source Code Form is subject to the terms of the Mozilla | |
| // Public License v. 2.0. If a copy of the MPL was not distributed | |
| // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
| // SPDX-License-Identifier: MPL-2.0 | |
| #define EIGEN_USE_THREADS | |
| #include "main.h" | |
| #include <atomic> | |
| #include <iostream> | |
| #include <Eigen/Tensor> | |
| using Eigen::Tensor; | |
| class TestAllocator : public Allocator { | |
| public: | |
| ~TestAllocator() EIGEN_OVERRIDE {} | |
| EIGEN_DEVICE_FUNC void* allocate(size_t num_bytes) const EIGEN_OVERRIDE { | |
| const_cast<TestAllocator*>(this)->alloc_count_++; | |
| return internal::aligned_malloc(num_bytes); | |
| } | |
| EIGEN_DEVICE_FUNC void deallocate(void* buffer) const EIGEN_OVERRIDE { | |
| const_cast<TestAllocator*>(this)->dealloc_count_++; | |
| internal::aligned_free(buffer); | |
| } | |
| int alloc_count() const { return alloc_count_; } | |
| int dealloc_count() const { return dealloc_count_; } | |
| private: | |
| int alloc_count_ = 0; | |
| int dealloc_count_ = 0; | |
| }; | |
| template <typename = void> | |
| void test_multithread_elementwise() { | |
| Tensor<float, 3> in1(200, 30, 70); | |
| Tensor<float, 3> in2(200, 30, 70); | |
| Tensor<double, 3> out(200, 30, 70); | |
| in1.setRandom(); | |
| in2.setRandom(); | |
| Eigen::ThreadPool tp(internal::random<int>(3, 11)); | |
| Eigen::ThreadPoolDevice thread_pool_device(&tp, internal::random<int>(3, 11)); | |
| out.device(thread_pool_device) = (in1 + in2 * 3.14f).cast<double>(); | |
| for (int i = 0; i < 200; ++i) { | |
| for (int j = 0; j < 30; ++j) { | |
| for (int k = 0; k < 70; ++k) { | |
| VERIFY_IS_APPROX(out(i, j, k), static_cast<double>(in1(i, j, k) + in2(i, j, k) * 3.14f)); | |
| } | |
| } | |
| } | |
| } | |
| template <typename = void> | |
| void test_async_multithread_elementwise() { | |
| Tensor<float, 3> in1(200, 30, 70); | |
| Tensor<float, 3> in2(200, 30, 70); | |
| Tensor<double, 3> out(200, 30, 70); | |
| in1.setRandom(); | |
| in2.setRandom(); | |
| Eigen::ThreadPool tp(internal::random<int>(3, 11)); | |
| Eigen::ThreadPoolDevice thread_pool_device(&tp, internal::random<int>(3, 11)); | |
| Eigen::Barrier b(1); | |
| out.device(thread_pool_device, [&b]() { b.Notify(); }) = (in1 + in2 * 3.14f).cast<double>(); | |
| b.Wait(); | |
| for (int i = 0; i < 200; ++i) { | |
| for (int j = 0; j < 30; ++j) { | |
| for (int k = 0; k < 70; ++k) { | |
| VERIFY_IS_APPROX(out(i, j, k), static_cast<double>(in1(i, j, k) + in2(i, j, k) * 3.14f)); | |
| } | |
| } | |
| } | |
| } | |
| template <typename = void> | |
| void test_multithread_chip() { | |
| Tensor<float, 5> in(2, 3, 5, 7, 11); | |
| Tensor<float, 4> out(3, 5, 7, 11); | |
| in.setRandom(); | |
| Eigen::ThreadPool tp(internal::random<int>(3, 11)); | |
| Eigen::ThreadPoolDevice thread_pool_device(&tp, internal::random<int>(3, 11)); | |
| out.device(thread_pool_device) = in.chip(1, 0); | |
| for (int i = 0; i < 3; ++i) { | |
| for (int j = 0; j < 5; ++j) { | |
| for (int k = 0; k < 7; ++k) { | |
| for (int l = 0; l < 11; ++l) { | |
| VERIFY_IS_EQUAL(out(i, j, k, l), in(1, i, j, k, l)); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| template <typename = void> | |
| void test_async_multithread_chip() { | |
| Tensor<float, 5> in(2, 3, 5, 7, 11); | |
| Tensor<float, 4> out(3, 5, 7, 11); | |
| in.setRandom(); | |
| Eigen::ThreadPool tp(internal::random<int>(3, 11)); | |
| Eigen::ThreadPoolDevice thread_pool_device(&tp, internal::random<int>(3, 11)); | |
| Eigen::Barrier b(1); | |
| out.device(thread_pool_device, [&b]() { b.Notify(); }) = in.chip(1, 0); | |
| b.Wait(); | |
| for (int i = 0; i < 3; ++i) { | |
| for (int j = 0; j < 5; ++j) { | |
| for (int k = 0; k < 7; ++k) { | |
| for (int l = 0; l < 11; ++l) { | |
| VERIFY_IS_EQUAL(out(i, j, k, l), in(1, i, j, k, l)); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| template <typename = void> | |
| void test_multithread_volume_patch() { | |
| Tensor<float, 5> in(4, 2, 3, 5, 7); | |
| Tensor<float, 6> out(4, 1, 1, 1, 2 * 3 * 5, 7); | |
| in.setRandom(); | |
| Eigen::ThreadPool tp(internal::random<int>(3, 11)); | |
| Eigen::ThreadPoolDevice thread_pool_device(&tp, internal::random<int>(3, 11)); | |
| out.device(thread_pool_device) = in.extract_volume_patches(1, 1, 1); | |
| for (int i = 0; i < in.size(); ++i) { | |
| VERIFY_IS_EQUAL(in.data()[i], out.data()[i]); | |
| } | |
| } | |
| template <typename = void> | |
| void test_async_multithread_volume_patch() { | |
| Tensor<float, 5> in(4, 2, 3, 5, 7); | |
| Tensor<float, 6> out(4, 1, 1, 1, 2 * 3 * 5, 7); | |
| in.setRandom(); | |
| Eigen::ThreadPool tp(internal::random<int>(3, 11)); | |
| Eigen::ThreadPoolDevice thread_pool_device(&tp, internal::random<int>(3, 11)); | |
| Eigen::Barrier b(1); | |
| out.device(thread_pool_device, [&b]() { b.Notify(); }) = in.extract_volume_patches(1, 1, 1); | |
| b.Wait(); | |
| for (int i = 0; i < in.size(); ++i) { | |
| VERIFY_IS_EQUAL(in.data()[i], out.data()[i]); | |
| } | |
| } | |
| // Stateful-but-copyable unary functor: flags an overlap when two threads call | |
| // the same instance concurrently. The executors must copy the evaluator (and | |
| // with it the functor) into each task, so no instance is ever called | |
| // concurrently even though the expression is evaluated by multiple threads. | |
| struct OverlapDetectingOp { | |
| explicit OverlapDetectingOp(std::atomic<int>* overlaps) : overlaps_(overlaps) {} | |
| OverlapDetectingOp(const OverlapDetectingOp& other) : overlaps_(other.overlaps_) {} | |
| float operator()(const float& x) const { | |
| if (busy_.exchange(true)) overlaps_->fetch_add(1); | |
| const float result = x + 1.0f; | |
| busy_.store(false); | |
| return result; | |
| } | |
| std::atomic<int>* overlaps_; | |
| mutable std::atomic<bool> busy_{false}; | |
| }; | |
| // The driver expression uses reverse() because its block() has always read | |
| // the argument through coeff() with BlockAccess independent of the argument, | |
| // so these tests exercise the tiled executor's per-task evaluator copy | |
| // regardless of how any one evaluator's traits evolve. | |
| template <typename = void> | |
| void test_multithread_tiled_stateful_functor() { | |
| Tensor<float, 4> in(64, 64, 64, 8); | |
| in.setRandom(); | |
| const array<bool, 4> rev = {{false, true, true, false}}; | |
| Tensor<float, 4> expected(64, 64, 64, 8); | |
| expected = (in + 1.0f).reverse(rev); | |
| std::atomic<int> overlaps(0); | |
| Eigen::ThreadPool tp(4); | |
| Eigen::ThreadPoolDevice thread_pool_device(&tp, 4); | |
| Tensor<float, 4> out(64, 64, 64, 8); | |
| out.device(thread_pool_device) = in.unaryExpr(OverlapDetectingOp(&overlaps)).reverse(rev); | |
| VERIFY_IS_EQUAL(overlaps.load(), 0); | |
| for (int i = 0; i < out.size(); ++i) { | |
| VERIFY_IS_EQUAL(out.data()[i], expected.data()[i]); | |
| } | |
| } | |
| template <typename = void> | |
| void test_async_multithread_tiled_stateful_functor() { | |
| Tensor<float, 4> in(64, 64, 64, 8); | |
| in.setRandom(); | |
| const array<bool, 4> rev = {{false, true, true, false}}; | |
| Tensor<float, 4> expected(64, 64, 64, 8); | |
| expected = (in + 1.0f).reverse(rev); | |
| std::atomic<int> overlaps(0); | |
| Eigen::ThreadPool tp(4); | |
| Eigen::ThreadPoolDevice thread_pool_device(&tp, 4); | |
| Tensor<float, 4> out(64, 64, 64, 8); | |
| Eigen::Barrier b(1); | |
| out.device(thread_pool_device, [&b]() { b.Notify(); }) = in.unaryExpr(OverlapDetectingOp(&overlaps)).reverse(rev); | |
| b.Wait(); | |
| VERIFY_IS_EQUAL(overlaps.load(), 0); | |
| for (int i = 0; i < out.size(); ++i) { | |
| VERIFY_IS_EQUAL(out.data()[i], expected.data()[i]); | |
| } | |
| } | |
| template <typename = void> | |
| void test_multithread_compound_assignment() { | |
| Tensor<float, 3> in1(2, 3, 7); | |
| Tensor<float, 3> in2(2, 3, 7); | |
| Tensor<float, 3> out(2, 3, 7); | |
| in1.setRandom(); | |
| in2.setRandom(); | |
| Eigen::ThreadPool tp(internal::random<int>(3, 11)); | |
| Eigen::ThreadPoolDevice thread_pool_device(&tp, internal::random<int>(3, 11)); | |
| out.device(thread_pool_device) = in1; | |
| out.device(thread_pool_device) += in2 * 3.14f; | |
| for (int i = 0; i < 2; ++i) { | |
| for (int j = 0; j < 3; ++j) { | |
| for (int k = 0; k < 7; ++k) { | |
| VERIFY_IS_APPROX(out(i, j, k), in1(i, j, k) + in2(i, j, k) * 3.14f); | |
| } | |
| } | |
| } | |
| } | |
| template <int DataLayout> | |
| void test_multithread_contraction() { | |
| Tensor<float, 4, DataLayout> t_left(30, 50, 37, 31); | |
| Tensor<float, 5, DataLayout> t_right(37, 31, 70, 2, 10); | |
| Tensor<float, 5, DataLayout> t_result(30, 50, 70, 2, 10); | |
| t_left.setRandom(); | |
| t_right.setRandom(); | |
| // this contraction should be equivalent to a single matrix multiplication | |
| typedef Tensor<float, 1>::DimensionPair DimPair; | |
| Eigen::array<DimPair, 2> dims{{DimPair(2, 0), DimPair(3, 1)}}; | |
| typedef Map<Matrix<float, Dynamic, Dynamic, DataLayout>> MapXf; | |
| MapXf m_left(t_left.data(), 1500, 1147); | |
| MapXf m_right(t_right.data(), 1147, 1400); | |
| Matrix<float, Dynamic, Dynamic, DataLayout> m_result(1500, 1400); | |
| Eigen::ThreadPool tp(4); | |
| Eigen::ThreadPoolDevice thread_pool_device(&tp, 4); | |
| // compute results by separate methods | |
| t_result.device(thread_pool_device) = t_left.contract(t_right, dims); | |
| m_result = m_left * m_right; | |
| // The tensor contraction and the matrix product sum the k=1147 terms in different orders; their results differ | |
| // by a small multiple of eps, covered here as absolute slack before the relative check. | |
| for (ptrdiff_t i = 0; i < t_result.size(); i++) { | |
| VERIFY(&t_result.data()[i] != &m_result.data()[i]); | |
| if (fabsf(t_result(i) - m_result(i)) < 128 * NumTraits<float>::epsilon()) { | |
| continue; | |
| } | |
| if (Eigen::internal::isApprox(t_result(i), m_result(i), 128 * NumTraits<float>::epsilon())) { | |
| continue; | |
| } | |
| std::cout << "mismatch detected at index " << i << ": " << t_result(i) << " vs " << m_result(i) << std::endl; | |
| assert(false); | |
| } | |
| } | |
| // Issue #1648: lock the threaded dispatch of gemv-shape contractions | |
| // (TensorContractionThreadPool forces num_threads = 1 for n == 1 and routes | |
| // through evalProductSequential, which now selects one of four direct GEMV | |
| // fast paths). Covers y = A^T * x where the LHS contracted dim is the | |
| // contiguous one — the path that motivated the fix. | |
| template <int DataLayout> | |
| void test_multithread_gemv_transpose() { | |
| for (int M : {17, 64, 129}) { | |
| for (int N : {16, 33, 100}) { | |
| Tensor<float, 2, DataLayout> t_left(M, N); | |
| Tensor<float, 1, DataLayout> t_right(M); | |
| t_left.setRandom(); | |
| t_right.setRandom(); | |
| typedef Tensor<float, 1>::DimensionPair DimPair; | |
| Eigen::array<DimPair, 1> dims{{DimPair(0, 0)}}; | |
| Eigen::ThreadPool tp(4); | |
| Eigen::ThreadPoolDevice thread_pool_device(&tp, 4); | |
| Tensor<float, 1, DataLayout> t_result(N); | |
| t_result.device(thread_pool_device) = t_left.contract(t_right, dims); | |
| typedef Map<const Matrix<float, Dynamic, Dynamic, DataLayout>> MapMat; | |
| typedef Map<const Matrix<float, Dynamic, 1>> MapVec; | |
| MapMat m_left(t_left.data(), M, N); | |
| MapVec m_right(t_right.data(), M); | |
| Matrix<float, Dynamic, 1> m_result = m_left.transpose() * m_right; | |
| for (int j = 0; j < N; ++j) { | |
| VERIFY(internal::isApprox(t_result(j), m_result(j), 16 * NumTraits<float>::epsilon())); | |
| } | |
| } | |
| } | |
| } | |
| template <int DataLayout> | |
| void test_contraction_corner_cases() { | |
| Tensor<float, 2, DataLayout> t_left(32, 500); | |
| Tensor<float, 2, DataLayout> t_right(32, 28 * 28); | |
| Tensor<float, 2, DataLayout> t_result(500, 28 * 28); | |
| t_left = (t_left.constant(-0.5f) + t_left.random()) * 2.0f; | |
| t_right = (t_right.constant(-0.6f) + t_right.random()) * 2.0f; | |
| t_result = t_result.constant(NAN); | |
| // this contraction should be equivalent to a single matrix multiplication | |
| typedef Tensor<float, 1>::DimensionPair DimPair; | |
| Eigen::array<DimPair, 1> dims{{DimPair(0, 0)}}; | |
| typedef Map<Matrix<float, Dynamic, Dynamic, DataLayout>> MapXf; | |
| MapXf m_left(t_left.data(), 32, 500); | |
| MapXf m_right(t_right.data(), 32, 28 * 28); | |
| Matrix<float, Dynamic, Dynamic, DataLayout> m_result(500, 28 * 28); | |
| Eigen::ThreadPool tp(12); | |
| Eigen::ThreadPoolDevice thread_pool_device(&tp, 12); | |
| // compute results by separate methods | |
| t_result.device(thread_pool_device) = t_left.contract(t_right, dims); | |
| m_result = m_left.transpose() * m_right; | |
| for (ptrdiff_t i = 0; i < t_result.size(); i++) { | |
| assert(!(numext::isnan)(t_result.data()[i])); | |
| if (fabsf(t_result.data()[i] - m_result.data()[i]) >= 256 * NumTraits<float>::epsilon()) { | |
| std::cout << "mismatch detected at index " << i << " : " << t_result.data()[i] << " vs " << m_result.data()[i] | |
| << std::endl; | |
| assert(false); | |
| } | |
| } | |
| t_left.resize(32, 1); | |
| t_left = (t_left.constant(-0.5f) + t_left.random()) * 2.0f; | |
| t_result.resize(1, 28 * 28); | |
| t_result = t_result.constant(NAN); | |
| t_result.device(thread_pool_device) = t_left.contract(t_right, dims); | |
| new (&m_left) MapXf(t_left.data(), 32, 1); | |
| m_result = m_left.transpose() * m_right; | |
| for (ptrdiff_t i = 0; i < t_result.size(); i++) { | |
| assert(!(numext::isnan)(t_result.data()[i])); | |
| if (fabsf(t_result.data()[i] - m_result.data()[i]) >= 256 * NumTraits<float>::epsilon()) { | |
| std::cout << "mismatch detected: " << t_result.data()[i] << " vs " << m_result.data()[i] << std::endl; | |
| assert(false); | |
| } | |
| } | |
| t_left.resize(32, 500); | |
| t_right.resize(32, 4); | |
| t_left = (t_left.constant(-0.5f) + t_left.random()) * 2.0f; | |
| t_right = (t_right.constant(-0.6f) + t_right.random()) * 2.0f; | |
| t_result.resize(500, 4); | |
| t_result = t_result.constant(NAN); | |
| t_result.device(thread_pool_device) = t_left.contract(t_right, dims); | |
| new (&m_left) MapXf(t_left.data(), 32, 500); | |
| new (&m_right) MapXf(t_right.data(), 32, 4); | |
| m_result = m_left.transpose() * m_right; | |
| for (ptrdiff_t i = 0; i < t_result.size(); i++) { | |
| assert(!(numext::isnan)(t_result.data()[i])); | |
| if (fabsf(t_result.data()[i] - m_result.data()[i]) >= 256 * NumTraits<float>::epsilon()) { | |
| std::cout << "mismatch detected: " << t_result.data()[i] << " vs " << m_result.data()[i] << std::endl; | |
| assert(false); | |
| } | |
| } | |
| t_left.resize(32, 1); | |
| t_right.resize(32, 4); | |
| t_left = (t_left.constant(-0.5f) + t_left.random()) * 2.0f; | |
| t_right = (t_right.constant(-0.6f) + t_right.random()) * 2.0f; | |
| t_result.resize(1, 4); | |
| t_result = t_result.constant(NAN); | |
| t_result.device(thread_pool_device) = t_left.contract(t_right, dims); | |
| new (&m_left) MapXf(t_left.data(), 32, 1); | |
| new (&m_right) MapXf(t_right.data(), 32, 4); | |
| m_result = m_left.transpose() * m_right; | |
| for (ptrdiff_t i = 0; i < t_result.size(); i++) { | |
| assert(!(numext::isnan)(t_result.data()[i])); | |
| if (fabsf(t_result.data()[i] - m_result.data()[i]) >= 256 * NumTraits<float>::epsilon()) { | |
| std::cout << "mismatch detected: " << t_result.data()[i] << " vs " << m_result.data()[i] << std::endl; | |
| assert(false); | |
| } | |
| } | |
| } | |
| template <int DataLayout> | |
| void test_multithread_contraction_agrees_with_singlethread() { | |
| int contract_size = internal::random<int>(1, 5000); | |
| Tensor<float, 3, DataLayout> left(internal::random<int>(1, 80), contract_size, internal::random<int>(1, 100)); | |
| Tensor<float, 4, DataLayout> right(internal::random<int>(1, 25), internal::random<int>(1, 37), contract_size, | |
| internal::random<int>(1, 51)); | |
| left.setRandom(); | |
| right.setRandom(); | |
| // add constants to shift values away from 0 for more precision | |
| left += left.constant(1.5f); | |
| right += right.constant(1.5f); | |
| typedef Tensor<float, 1>::DimensionPair DimPair; | |
| Eigen::array<DimPair, 1> dims{{DimPair(1, 2)}}; | |
| Eigen::ThreadPool tp(internal::random<int>(2, 11)); | |
| Eigen::ThreadPoolDevice thread_pool_device(&tp, internal::random<int>(2, 11)); | |
| Tensor<float, 5, DataLayout> st_result; | |
| st_result = left.contract(right, dims); | |
| Tensor<float, 5, DataLayout> tp_result(st_result.dimensions()); | |
| tp_result.device(thread_pool_device) = left.contract(right, dims); | |
| VERIFY(dimensions_match(st_result.dimensions(), tp_result.dimensions())); | |
| for (ptrdiff_t i = 0; i < st_result.size(); i++) { | |
| // if both of the values are very small, then do nothing (because the test will fail | |
| // due to numerical precision issues when values are small) | |
| if (numext::abs(st_result.data()[i] - tp_result.data()[i]) >= 1024 * NumTraits<float>::epsilon()) { | |
| VERIFY_IS_APPROX(st_result.data()[i], tp_result.data()[i]); | |
| } | |
| } | |
| } | |
| // Apply Sqrt to all output elements. | |
| struct SqrtOutputKernel { | |
| template <typename Index, typename Scalar> | |
| EIGEN_ALWAYS_INLINE void operator()(const internal::blas_data_mapper<Scalar, Index, ColMajor>& output_mapper, | |
| const TensorContractionParams&, Index, Index, Index num_rows, | |
| Index num_cols) const { | |
| for (int i = 0; i < num_rows; ++i) { | |
| for (int j = 0; j < num_cols; ++j) { | |
| output_mapper(i, j) = std::sqrt(output_mapper(i, j)); | |
| } | |
| } | |
| } | |
| }; | |
| template <int DataLayout> | |
| static void test_multithread_contraction_with_output_kernel() { | |
| typedef Tensor<float, 1>::DimensionPair DimPair; | |
| const int num_threads = internal::random<int>(2, 11); | |
| ThreadPool threads(num_threads); | |
| Eigen::ThreadPoolDevice device(&threads, num_threads); | |
| Tensor<float, 4, DataLayout> t_left(30, 50, 8, 31); | |
| Tensor<float, 5, DataLayout> t_right(8, 31, 7, 20, 10); | |
| Tensor<float, 5, DataLayout> t_result(30, 50, 7, 20, 10); | |
| t_left.setRandom(); | |
| t_right.setRandom(); | |
| // Put trash in mat4 to verify contraction clears output memory. | |
| t_result.setRandom(); | |
| // Add a little offset so that the results won't be close to zero. | |
| t_left += t_left.constant(1.0f); | |
| t_right += t_right.constant(1.0f); | |
| typedef Map<Eigen::Matrix<float, Dynamic, Dynamic, DataLayout>> MapXf; | |
| MapXf m_left(t_left.data(), 1500, 248); | |
| MapXf m_right(t_right.data(), 248, 1400); | |
| Eigen::Matrix<float, Dynamic, Dynamic, DataLayout> m_result(1500, 1400); | |
| // this contraction should be equivalent to a single matrix multiplication | |
| Eigen::array<DimPair, 2> dims{{DimPair(2, 0), DimPair(3, 1)}}; | |
| // compute results by separate methods | |
| t_result.device(device) = t_left.contract(t_right, dims, SqrtOutputKernel()); | |
| m_result = m_left * m_right; | |
| for (Index i = 0; i < t_result.dimensions().TotalSize(); i++) { | |
| VERIFY(&t_result.data()[i] != &m_result.data()[i]); | |
| VERIFY_IS_APPROX(t_result.data()[i], std::sqrt(m_result.data()[i])); | |
| } | |
| } | |
| template <int DataLayout> | |
| void test_async_multithread_contraction_agrees_with_singlethread() { | |
| int contract_size = internal::random<int>(100, 500); | |
| Tensor<float, 3, DataLayout> left(internal::random<int>(10, 40), contract_size, internal::random<int>(10, 40)); | |
| Tensor<float, 4, DataLayout> right(internal::random<int>(1, 20), internal::random<int>(1, 20), contract_size, | |
| internal::random<int>(1, 20)); | |
| left.setRandom(); | |
| right.setRandom(); | |
| // add constants to shift values away from 0 for more precision | |
| left += left.constant(1.5f); | |
| right += right.constant(1.5f); | |
| typedef Tensor<float, 1>::DimensionPair DimPair; | |
| Eigen::array<DimPair, 1> dims{{DimPair(1, 2)}}; | |
| Eigen::ThreadPool tp(internal::random<int>(2, 11)); | |
| Eigen::ThreadPoolDevice thread_pool_device(&tp, internal::random<int>(8, 32)); | |
| Tensor<float, 5, DataLayout> st_result; | |
| st_result = left.contract(right, dims); | |
| Tensor<float, 5, DataLayout> tp_result(st_result.dimensions()); | |
| Eigen::Barrier barrier(1); | |
| tp_result.device(thread_pool_device, [&barrier]() { barrier.Notify(); }) = left.contract(right, dims); | |
| barrier.Wait(); | |
| VERIFY(dimensions_match(st_result.dimensions(), tp_result.dimensions())); | |
| for (ptrdiff_t i = 0; i < st_result.size(); i++) { | |
| // if both of the values are very small, then do nothing (because the test | |
| // will fail due to numerical precision issues when values are small) | |
| if (numext::abs(st_result.data()[i] - tp_result.data()[i]) >= 1024 * NumTraits<float>::epsilon()) { | |
| VERIFY_IS_APPROX(st_result.data()[i], tp_result.data()[i]); | |
| } | |
| } | |
| } | |
| // We are triggering 'evalShardedByInnerDim' optimization. | |
| template <int DataLayout> | |
| static void test_sharded_by_inner_dim_contraction() { | |
| using DimPair = typename Tensor<float, 1>::DimensionPair; | |
| const int num_threads = internal::random<int>(4, 16); | |
| ThreadPool threads(num_threads); | |
| Eigen::ThreadPoolDevice device(&threads, num_threads); | |
| const Index k = 30000 * PacketType<float, Eigen::ThreadPoolDevice>::size; | |
| Tensor<float, 2, DataLayout> t_left(2, k); | |
| Tensor<float, 2, DataLayout> t_right(k, 10); | |
| Tensor<float, 2, DataLayout> t_result(2, 10); | |
| t_left.setRandom(); | |
| t_right.setRandom(); | |
| // Put trash in t_result to verify contraction clears output memory. | |
| t_result.setRandom(); | |
| // Add a little offset so that the results won't be close to zero. | |
| t_left += t_left.constant(1.0f); | |
| t_right += t_right.constant(1.0f); | |
| using MapXf = Map<Eigen::Matrix<float, Dynamic, Dynamic, DataLayout>>; | |
| MapXf m_left(t_left.data(), 2, k); | |
| MapXf m_right(t_right.data(), k, 10); | |
| Eigen::Matrix<float, Dynamic, Dynamic, DataLayout> m_result(2, 10); | |
| // this contraction should be equivalent to a single matrix multiplication | |
| Eigen::array<DimPair, 1> dims{{DimPair(1, 0)}}; | |
| auto contraction = t_left.contract(t_right, dims); | |
| using ContractionEval = TensorEvaluator<const decltype(contraction), Eigen::ThreadPoolDevice>; | |
| ContractionEval eval(contraction, device); | |
| const int nt_k = eval.numThreadsInnerDim(2, 10, k); | |
| VERIFY(nt_k > 1); | |
| VERIFY(ContractionEval::shardByInnerDim(2, 10, k, /*num_threads=*/1, nt_k)); | |
| // compute results by separate methods | |
| t_result.device(device) = contraction; | |
| m_result = m_left * m_right; | |
| for (Index i = 0; i < t_result.dimensions().TotalSize(); i++) { | |
| VERIFY_IS_APPROX(t_result.data()[i], m_result.data()[i]); | |
| } | |
| } | |
| // We are triggering 'evalShardedByInnerDim' optimization with output kernel. | |
| template <int DataLayout> | |
| static void test_sharded_by_inner_dim_contraction_with_output_kernel() { | |
| using DimPair = typename Tensor<float, 1>::DimensionPair; | |
| const int num_threads = internal::random<int>(4, 16); | |
| ThreadPool threads(num_threads); | |
| Eigen::ThreadPoolDevice device(&threads, num_threads); | |
| const Index k = 30000 * PacketType<float, Eigen::ThreadPoolDevice>::size; | |
| Tensor<float, 2, DataLayout> t_left(2, k); | |
| Tensor<float, 2, DataLayout> t_right(k, 10); | |
| Tensor<float, 2, DataLayout> t_result(2, 10); | |
| t_left.setRandom(); | |
| t_right.setRandom(); | |
| // Put trash in t_result to verify contraction clears output memory. | |
| t_result.setRandom(); | |
| // Add a little offset so that the results won't be close to zero. | |
| t_left += t_left.constant(1.0f); | |
| t_right += t_right.constant(1.0f); | |
| using MapXf = Map<Eigen::Matrix<float, Dynamic, Dynamic, DataLayout>>; | |
| MapXf m_left(t_left.data(), 2, k); | |
| MapXf m_right(t_right.data(), k, 10); | |
| Eigen::Matrix<float, Dynamic, Dynamic, DataLayout> m_result(2, 10); | |
| // this contraction should be equivalent to a single matrix multiplication | |
| Eigen::array<DimPair, 1> dims{{DimPair(1, 0)}}; | |
| auto contraction = t_left.contract(t_right, dims, SqrtOutputKernel()); | |
| using ContractionEval = TensorEvaluator<const decltype(contraction), Eigen::ThreadPoolDevice>; | |
| ContractionEval eval(contraction, device); | |
| const int nt_k = eval.numThreadsInnerDim(2, 10, k); | |
| VERIFY(nt_k > 1); | |
| VERIFY(ContractionEval::shardByInnerDim(2, 10, k, /*num_threads=*/1, nt_k)); | |
| // compute results by separate methods | |
| t_result.device(device) = contraction; | |
| m_result = m_left * m_right; | |
| for (Index i = 0; i < t_result.dimensions().TotalSize(); i++) { | |
| VERIFY_IS_APPROX(t_result.data()[i], std::sqrt(m_result.data()[i])); | |
| } | |
| } | |
| // We are triggering 'evalShardedByInnerDim' optimization. | |
| template <int DataLayout> | |
| static void test_async_sharded_by_inner_dim_contraction() { | |
| using DimPair = typename Tensor<float, 1>::DimensionPair; | |
| const int num_threads = internal::random<int>(4, 16); | |
| ThreadPool threads(num_threads); | |
| Eigen::ThreadPoolDevice device(&threads, num_threads); | |
| const Index k = 30000 * PacketType<float, Eigen::ThreadPoolDevice>::size; | |
| Tensor<float, 2, DataLayout> t_left(2, k); | |
| Tensor<float, 2, DataLayout> t_right(k, 10); | |
| Tensor<float, 2, DataLayout> t_result(2, 10); | |
| t_left.setRandom(); | |
| t_right.setRandom(); | |
| // Put trash in t_result to verify contraction clears output memory. | |
| t_result.setRandom(); | |
| // Add a little offset so that the results won't be close to zero. | |
| t_left += t_left.constant(1.0f); | |
| t_right += t_right.constant(1.0f); | |
| using MapXf = Map<Eigen::Matrix<float, Dynamic, Dynamic, DataLayout>>; | |
| MapXf m_left(t_left.data(), 2, k); | |
| MapXf m_right(t_right.data(), k, 10); | |
| Eigen::Matrix<float, Dynamic, Dynamic, DataLayout> m_result(2, 10); | |
| // this contraction should be equivalent to a single matrix multiplication | |
| Eigen::array<DimPair, 1> dims{{DimPair(1, 0)}}; | |
| auto contraction = t_left.contract(t_right, dims); | |
| using ContractionEval = TensorEvaluator<const decltype(contraction), Eigen::ThreadPoolDevice>; | |
| ContractionEval eval(contraction, device); | |
| const int nt_k = eval.numThreadsInnerDim(2, 10, k); | |
| VERIFY(nt_k > 1); | |
| VERIFY(ContractionEval::shardByInnerDim(2, 10, k, /*num_threads=*/1, nt_k)); | |
| // compute results by separate methods | |
| Eigen::Barrier barrier(1); | |
| t_result.device(device, [&barrier]() { barrier.Notify(); }) = contraction; | |
| barrier.Wait(); | |
| m_result = m_left * m_right; | |
| for (Index i = 0; i < t_result.dimensions().TotalSize(); i++) { | |
| VERIFY_IS_APPROX(t_result.data()[i], m_result.data()[i]); | |
| } | |
| } | |
| // We are triggering 'evalShardedByInnerDim' optimization with output kernel. | |
| template <int DataLayout> | |
| static void test_async_sharded_by_inner_dim_contraction_with_output_kernel() { | |
| using DimPair = typename Tensor<float, 1>::DimensionPair; | |
| const int num_threads = internal::random<int>(4, 16); | |
| ThreadPool threads(num_threads); | |
| Eigen::ThreadPoolDevice device(&threads, num_threads); | |
| const Index k = 30000 * PacketType<float, Eigen::ThreadPoolDevice>::size; | |
| Tensor<float, 2, DataLayout> t_left(2, k); | |
| Tensor<float, 2, DataLayout> t_right(k, 10); | |
| Tensor<float, 2, DataLayout> t_result(2, 10); | |
| t_left.setRandom(); | |
| t_right.setRandom(); | |
| // Put trash in t_result to verify contraction clears output memory. | |
| t_result.setRandom(); | |
| // Add a little offset so that the results won't be close to zero. | |
| t_left += t_left.constant(1.0f); | |
| t_right += t_right.constant(1.0f); | |
| using MapXf = Map<Eigen::Matrix<float, Dynamic, Dynamic, DataLayout>>; | |
| MapXf m_left(t_left.data(), 2, k); | |
| MapXf m_right(t_right.data(), k, 10); | |
| Eigen::Matrix<float, Dynamic, Dynamic, DataLayout> m_result(2, 10); | |
| // this contraction should be equivalent to a single matrix multiplication | |
| Eigen::array<DimPair, 1> dims{{DimPair(1, 0)}}; | |
| auto contraction = t_left.contract(t_right, dims, SqrtOutputKernel()); | |
| using ContractionEval = TensorEvaluator<const decltype(contraction), Eigen::ThreadPoolDevice>; | |
| ContractionEval eval(contraction, device); | |
| const int nt_k = eval.numThreadsInnerDim(2, 10, k); | |
| VERIFY(nt_k > 1); | |
| VERIFY(ContractionEval::shardByInnerDim(2, 10, k, /*num_threads=*/1, nt_k)); | |
| // compute results by separate methods | |
| Eigen::Barrier barrier(1); | |
| t_result.device(device, [&barrier]() { barrier.Notify(); }) = contraction; | |
| barrier.Wait(); | |
| m_result = m_left * m_right; | |
| for (Index i = 0; i < t_result.dimensions().TotalSize(); i++) { | |
| VERIFY_IS_APPROX(t_result.data()[i], std::sqrt(m_result.data()[i])); | |
| } | |
| } | |
| template <int DataLayout> | |
| static void test_sharded_by_inner_dim_cost_model() { | |
| using DimPair = typename Tensor<float, 1>::DimensionPair; | |
| Eigen::array<DimPair, 1> dims{{DimPair(1, 0)}}; | |
| ThreadPool threads(8); | |
| Eigen::ThreadPoolDevice device(&threads, 8); | |
| Tensor<float, 2, DataLayout> t_left(2, 2); | |
| Tensor<float, 2, DataLayout> t_right(2, 2); | |
| auto expr = t_left.contract(t_right, dims); | |
| using ContractionEval = TensorEvaluator<const decltype(expr), Eigen::ThreadPoolDevice>; | |
| ContractionEval eval(expr, device); | |
| // Scale k by the packet width so the parallel cost (6.6e4..2.6e5 cycles across FMA and panel-width regimes) sits | |
| // between the old hardcoded threshold (~5.7e4 cycles) and the TensorCostModel threshold (~4.0e5 cycles) under every | |
| // SIMD width. | |
| const Index f32_packet_size = PacketType<float, Eigen::ThreadPoolDevice>::size; | |
| const Index moderate_f32_k = 64 * f32_packet_size; | |
| VERIFY_IS_EQUAL(eval.numThreadsInnerDim(32, 64, moderate_f32_k), 1); | |
| VERIFY(!ContractionEval::shardByInnerDim(32, 64, moderate_f32_k, /*num_threads=*/1, | |
| eval.numThreadsInnerDim(32, 64, moderate_f32_k))); | |
| // Cover the bfloat16 contraction shape (16 x 128 x k) that motivated the fix. | |
| Tensor<Eigen::bfloat16, 2, DataLayout> bf16_left(2, 2); | |
| Tensor<Eigen::bfloat16, 2, DataLayout> bf16_right(2, 2); | |
| auto bf16_expr = bf16_left.contract(bf16_right, dims); | |
| using Bf16ContractionEval = TensorEvaluator<const decltype(bf16_expr), Eigen::ThreadPoolDevice>; | |
| Bf16ContractionEval bf16_eval(bf16_expr, device); | |
| const Index bf16_packet_size = PacketType<Eigen::bfloat16, Eigen::ThreadPoolDevice>::size; | |
| const Index moderate_bf16_k = 64 * bf16_packet_size; | |
| VERIFY_IS_EQUAL(bf16_eval.numThreadsInnerDim(16, 128, moderate_bf16_k), 1); | |
| VERIFY(!Bf16ContractionEval::shardByInnerDim(16, 128, moderate_bf16_k, /*num_threads=*/1, | |
| bf16_eval.numThreadsInnerDim(16, 128, moderate_bf16_k))); | |
| // Deep inner dimension with tiny outer dimensions should still shard by k. | |
| const Index deep_f32_k = 30000 * f32_packet_size; | |
| const int nt_k = eval.numThreadsInnerDim(2, 10, deep_f32_k); | |
| VERIFY(nt_k > 1); | |
| VERIFY(ContractionEval::shardByInnerDim(2, 10, deep_f32_k, /*num_threads=*/1, nt_k)); | |
| } | |
| template <int DataLayout> | |
| void test_full_contraction() { | |
| int contract_size1 = internal::random<int>(1, 500); | |
| int contract_size2 = internal::random<int>(1, 500); | |
| Tensor<float, 2, DataLayout> left(contract_size1, contract_size2); | |
| Tensor<float, 2, DataLayout> right(contract_size1, contract_size2); | |
| left.setRandom(); | |
| right.setRandom(); | |
| // add constants to shift values away from 0 for more precision | |
| left += left.constant(1.5f); | |
| right += right.constant(1.5f); | |
| typedef Tensor<float, 2>::DimensionPair DimPair; | |
| Eigen::array<DimPair, 2> dims{{DimPair(0, 0), DimPair(1, 1)}}; | |
| Eigen::ThreadPool tp(internal::random<int>(2, 11)); | |
| Eigen::ThreadPoolDevice thread_pool_device(&tp, internal::random<int>(2, 11)); | |
| Tensor<float, 0, DataLayout> st_result; | |
| st_result = left.contract(right, dims); | |
| Tensor<float, 0, DataLayout> tp_result; | |
| tp_result.device(thread_pool_device) = left.contract(right, dims); | |
| VERIFY(dimensions_match(st_result.dimensions(), tp_result.dimensions())); | |
| // if both of the values are very small, then do nothing (because the test will fail | |
| // due to numerical precision issues when values are small) | |
| if (numext::abs(st_result() - tp_result()) >= 1024 * NumTraits<float>::epsilon()) { | |
| VERIFY_IS_APPROX(st_result(), tp_result()); | |
| } | |
| } | |
| template <int DataLayout> | |
| void test_multithreaded_reductions() { | |
| const int num_threads = internal::random<int>(3, 11); | |
| ThreadPool thread_pool(num_threads); | |
| Eigen::ThreadPoolDevice thread_pool_device(&thread_pool, num_threads); | |
| const int num_rows = internal::random<int>(13, 732); | |
| const int num_cols = internal::random<int>(13, 732); | |
| Tensor<float, 2, DataLayout> t1(num_rows, num_cols); | |
| t1.setRandom(); | |
| Tensor<float, 0, DataLayout> full_redux; | |
| full_redux = t1.sum(); | |
| Tensor<float, 0, DataLayout> full_redux_tp; | |
| full_redux_tp.device(thread_pool_device) = t1.sum(); | |
| // Check that the single threaded and the multi threaded reductions return | |
| // the same result. | |
| VERIFY_IS_APPROX(full_redux(), full_redux_tp()); | |
| } | |
| // reduce() squares each value, so partial results cannot be merged through it. | |
| struct SquaredSumReducer { | |
| void reduce(const double t, double* accum) const { *accum += t * t; } | |
| double initialize() const { return 0; } | |
| double finalize(const double accum) const { return accum; } | |
| }; | |
| template <typename = void> | |
| void test_multithreaded_value_transforming_full_reduction() { | |
| ThreadPool thread_pool(4); | |
| Eigen::ThreadPoolDevice thread_pool_device(&thread_pool, 4); | |
| Tensor<double, 1> t(1 << 20); | |
| t.setConstant(1.0); | |
| Tensor<double, 0> result; | |
| result.device(thread_pool_device) = t.reduce(array<Index, 1>{{0}}, SquaredSumReducer()); | |
| VERIFY_IS_EQUAL(result(), static_cast<double>(t.size())); | |
| } | |
| // argmax()/argmin() take the sharded full reduction; each extremum recurs in every shard, so the | |
| // merge must keep the first occurrence. | |
| template <typename = void> | |
| void test_multithreaded_argmax() { | |
| STATIC_CHECK((internal::reducer_can_reorder_accumulators<internal::ArgMaxPairReducer<Pair<Index, float>>>::value)); | |
| STATIC_CHECK((internal::reducer_can_reorder_accumulators<internal::ArgMinPairReducer<Pair<Index, float>>>::value)); | |
| ThreadPool thread_pool(4); | |
| Eigen::ThreadPoolDevice thread_pool_device(&thread_pool, 4); | |
| Tensor<float, 1> t(1 << 20); | |
| for (Index i = 0; i < t.size(); ++i) t(i) = static_cast<float>((i + 500) % 1000); | |
| Tensor<Index, 0> result; | |
| result.device(thread_pool_device) = t.argmax(); | |
| VERIFY_IS_EQUAL(result(), Index(499)); | |
| result.device(thread_pool_device) = t.argmin(); | |
| VERIFY_IS_EQUAL(result(), Index(500)); | |
| } | |
| template <typename = void> | |
| void test_multithreaded_complex_reduction() { | |
| using Scalar = std::complex<float>; | |
| constexpr Index size = 4096; | |
| // Preserve the mapped source and fixed-size mapped destination from the issue #1647 backtrace: this exact | |
| // instantiation triggers the old GCC compiler bug. | |
| Tensor<Scalar, 1, RowMajor> storage(size); | |
| storage.setConstant(Scalar(1.0f, 2.0f)); | |
| TensorMap<Tensor<Scalar, 1, RowMajor>> input(storage.data(), size); | |
| EIGEN_ALIGN_MAX Scalar output_storage; | |
| TensorMap<TensorFixedSize<Scalar, Sizes<>, RowMajor>, Aligned> output(&output_storage); | |
| ThreadPool thread_pool(2); | |
| ThreadPoolDevice thread_pool_device(&thread_pool, 2); | |
| output.device(thread_pool_device) = input.sum(); | |
| VERIFY_IS_EQUAL(output(), Scalar(static_cast<float>(size), static_cast<float>(2 * size))); | |
| } | |
| template <typename = void> | |
| void test_multithreaded_complex_partial_reductions() { | |
| using Scalar = std::complex<float>; | |
| constexpr Index outer_size = 32; | |
| constexpr Index reduced_size = 257; | |
| Tensor<Scalar, 2, RowMajor> input(outer_size, reduced_size); | |
| for (Index i = 0; i < outer_size; ++i) { | |
| for (Index j = 0; j < reduced_size; ++j) { | |
| input(i, j) = Scalar(static_cast<float>(i + 1), static_cast<float>(2 * i + 1)); | |
| } | |
| } | |
| ThreadPool thread_pool(2); | |
| ThreadPoolDevice thread_pool_device(&thread_pool, 2); | |
| IndexList<type2index<1>> static_reduction_dim; | |
| Tensor<Scalar, 1, RowMajor> static_result(outer_size); | |
| static_result.device(thread_pool_device) = input.sum(static_reduction_dim); | |
| array<Index, 1> runtime_reduction_dim{1}; | |
| Tensor<Scalar, 1, RowMajor> runtime_result(outer_size); | |
| runtime_result.device(thread_pool_device) = input.sum(runtime_reduction_dim); | |
| for (Index i = 0; i < outer_size; ++i) { | |
| const Scalar expected = static_cast<float>(reduced_size) * input(i, 0); | |
| VERIFY_IS_EQUAL(static_result(i), expected); | |
| VERIFY_IS_EQUAL(runtime_result(i), expected); | |
| } | |
| } | |
| template <typename = void> | |
| void test_memcpy() { | |
| for (int i = 0; i < 5; ++i) { | |
| const int num_threads = internal::random<int>(3, 11); | |
| Eigen::ThreadPool tp(num_threads); | |
| Eigen::ThreadPoolDevice thread_pool_device(&tp, num_threads); | |
| const int size = internal::random<int>(13, 7632); | |
| Tensor<float, 1> t1(size); | |
| t1.setRandom(); | |
| std::vector<float> result(size); | |
| thread_pool_device.memcpy(&result[0], t1.data(), size * sizeof(float)); | |
| for (int j = 0; j < size; j++) { | |
| VERIFY_IS_EQUAL(t1(j), result[j]); | |
| } | |
| } | |
| } | |
| // The executors copy the evaluator per task; a copy must not destroy the forced-eval buffer's elements. | |
| template <typename = void> | |
| void test_multithread_forced_eval_non_pod() { | |
| Eigen::ThreadPool tp(4); | |
| Eigen::ThreadPoolDevice thread_pool_device(&tp, 4); | |
| Tensor<std::string, 1> in(64), out(64); | |
| for (int i = 0; i < 64; ++i) in(i) = std::string(40, static_cast<char>('a' + i % 26)); | |
| out.device(thread_pool_device) = in.eval(); | |
| for (int i = 0; i < 64; ++i) VERIFY_IS_EQUAL(out(i), in(i)); | |
| } | |
| template <typename = void> | |
| void test_enqueue_with_args() { | |
| Eigen::ThreadPool tp(2); | |
| Eigen::ThreadPoolDevice thread_pool_device(&tp, 2); | |
| Eigen::Barrier barrier(2); | |
| std::atomic<int> sum(0); | |
| thread_pool_device.enqueue([&sum](Eigen::Barrier* b, int x) { sum += x, b->Notify(); }, &barrier, 3); | |
| thread_pool_device.enqueueNoNotification([&sum](Eigen::Barrier* b) { sum += 4, b->Notify(); }, &barrier); | |
| barrier.Wait(); | |
| VERIFY_IS_EQUAL(sum.load(), 7); | |
| } | |
| // The async path constructs the forced-eval buffer's non-POD elements before evaluating into them. | |
| template <typename = void> | |
| void test_async_forced_eval_non_pod() { | |
| Eigen::ThreadPool tp(4); | |
| Eigen::ThreadPoolDevice thread_pool_device(&tp, 4); | |
| Tensor<std::string, 1> in(64), out(64); | |
| for (int i = 0; i < 64; ++i) in(i) = std::string(40, static_cast<char>('a' + i % 26)); | |
| Eigen::Barrier done(1); | |
| out.device(thread_pool_device, [&done]() { done.Notify(); }) = in.eval(); | |
| done.Wait(); | |
| for (int i = 0; i < 64; ++i) VERIFY_IS_EQUAL(out(i), in(i)); | |
| } | |
| // The single-block async path frees its scratch before the done callback, after which the device may be destroyed. | |
| template <typename = void> | |
| void test_async_single_block_scratch_freed_before_done() { | |
| TestAllocator allocator; | |
| Eigen::ThreadPool tp(4); | |
| Eigen::ThreadPoolDevice thread_pool_device(&tp, 4, &allocator); | |
| Tensor<float, 3> src(4, 5, 6), other(6, 5, 4), dst(6, 5, 4); | |
| src.setRandom(); | |
| other.setRandom(); | |
| Eigen::array<Index, 3> perm{{2, 1, 0}}; | |
| // The binary op does not hand the destination buffer to the shuffle, so its block is materialized in scratch. | |
| const auto expr = src.shuffle(perm) + other; | |
| int deallocs_at_done = -1; | |
| Eigen::Barrier done(1); | |
| auto on_done = [&]() { | |
| deallocs_at_done = allocator.dealloc_count(); | |
| done.Notify(); | |
| }; | |
| using Assign = TensorAssignOp<decltype(dst), const decltype(expr)>; | |
| using Executor = internal::TensorAsyncExecutor<const Assign, ThreadPoolDevice, decltype(on_done), | |
| /*Vectorizable=*/true, internal::TiledEvaluation::On>; | |
| // With no async subexpressions and a single block, runAsync evaluates and signals on this thread. | |
| Executor::runAsync(Assign(dst, expr), thread_pool_device, on_done); | |
| done.Wait(); | |
| VERIFY(allocator.alloc_count() > 0); | |
| VERIFY_IS_EQUAL(allocator.dealloc_count(), deallocs_at_done); | |
| } | |
| template <typename = void> | |
| void test_multithread_random() { | |
| Eigen::ThreadPool tp(2); | |
| Eigen::ThreadPoolDevice device(&tp, 2); | |
| Tensor<float, 1> t(1 << 20); | |
| t.device(device) = t.random<Eigen::internal::NormalRandomGenerator<float>>(); | |
| } | |
| template <int DataLayout> | |
| void test_multithread_shuffle(Allocator* allocator) { | |
| Tensor<float, 4, DataLayout> tensor(17, 5, 7, 11); | |
| tensor.setRandom(); | |
| const int num_threads = internal::random<int>(2, 11); | |
| ThreadPool threads(num_threads); | |
| Eigen::ThreadPoolDevice device(&threads, num_threads, allocator); | |
| Tensor<float, 4, DataLayout> shuffle(7, 5, 11, 17); | |
| array<ptrdiff_t, 4> shuffles = {{2, 1, 3, 0}}; | |
| shuffle.device(device) = tensor.shuffle(shuffles); | |
| for (int i = 0; i < 17; ++i) { | |
| for (int j = 0; j < 5; ++j) { | |
| for (int k = 0; k < 7; ++k) { | |
| for (int l = 0; l < 11; ++l) { | |
| VERIFY_IS_EQUAL(tensor(i, j, k, l), shuffle(k, j, l, i)); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| template <typename = void> | |
| void test_threadpool_allocate(TestAllocator* allocator) { | |
| const int num_threads = internal::random<int>(2, 11); | |
| const int num_allocs = internal::random<int>(2, 11); | |
| ThreadPool threads(num_threads); | |
| Eigen::ThreadPoolDevice device(&threads, num_threads, allocator); | |
| for (int a = 0; a < num_allocs; ++a) { | |
| void* ptr = device.allocate(512); | |
| device.deallocate(ptr); | |
| } | |
| VERIFY(allocator != nullptr); | |
| VERIFY_IS_EQUAL(allocator->alloc_count(), num_allocs); | |
| VERIFY_IS_EQUAL(allocator->dealloc_count(), num_allocs); | |
| } | |
| template <int DataLayout> | |
| void test_multithread_zero_dim_contraction() { | |
| const int num_threads = internal::random<int>(2, 11); | |
| ThreadPool threads(num_threads); | |
| Eigen::ThreadPoolDevice device(&threads, num_threads); | |
| // 2D contraction: {10, 0} * {0, 20} -> {10, 20} | |
| { | |
| Tensor<float, 2, DataLayout> t_left(10, 0); | |
| Tensor<float, 2, DataLayout> t_right(0, 20); | |
| Tensor<float, 2, DataLayout> t_result(10, 20); | |
| t_result.setConstant(123.0f); | |
| typedef Tensor<float, 1>::DimensionPair DimPair; | |
| Eigen::array<DimPair, 1> dims{{DimPair(1, 0)}}; | |
| t_result.device(device) = t_left.contract(t_right, dims); | |
| VERIFY_IS_EQUAL(t_result.dimension(0), 10); | |
| VERIFY_IS_EQUAL(t_result.dimension(1), 20); | |
| for (Index i = 0; i < t_result.size(); ++i) { | |
| VERIFY_IS_EQUAL(t_result.data()[i], 0.0f); | |
| } | |
| } | |
| // 2D contraction with non-empty dim >= 48: {64, 0} * {0, 64} -> {64, 64} | |
| { | |
| Tensor<float, 2, DataLayout> t_left(64, 0); | |
| Tensor<float, 2, DataLayout> t_right(0, 64); | |
| Tensor<float, 2, DataLayout> t_result(64, 64); | |
| t_result.setConstant(123.0f); | |
| typedef Tensor<float, 1>::DimensionPair DimPair; | |
| Eigen::array<DimPair, 1> dims{{DimPair(1, 0)}}; | |
| t_result.device(device) = t_left.contract(t_right, dims); | |
| VERIFY_IS_EQUAL(t_result.dimension(0), 64); | |
| VERIFY_IS_EQUAL(t_result.dimension(1), 64); | |
| for (Index i = 0; i < t_result.size(); ++i) { | |
| VERIFY_IS_EQUAL(t_result.data()[i], 0.0f); | |
| } | |
| } | |
| // Multidim contraction: {5, 0, 4} * {0, 3, 4} with dims {1, 0} and {2, 2} -> {5, 3} | |
| { | |
| Tensor<float, 3, DataLayout> t_left(5, 0, 4); | |
| Tensor<float, 3, DataLayout> t_right(0, 3, 4); | |
| Tensor<float, 2, DataLayout> t_result(5, 3); | |
| t_result.setConstant(123.0f); | |
| typedef Tensor<float, 1>::DimensionPair DimPair; | |
| Eigen::array<DimPair, 2> dims{{DimPair(1, 0), DimPair(2, 2)}}; | |
| t_result.device(device) = t_left.contract(t_right, dims); | |
| VERIFY_IS_EQUAL(t_result.dimension(0), 5); | |
| VERIFY_IS_EQUAL(t_result.dimension(1), 3); | |
| for (Index i = 0; i < t_result.size(); ++i) { | |
| VERIFY_IS_EQUAL(t_result.data()[i], 0.0f); | |
| } | |
| } | |
| } | |
| template <int DataLayout> | |
| void test_async_multithread_zero_dim_contraction() { | |
| const int num_threads = internal::random<int>(2, 11); | |
| ThreadPool threads(num_threads); | |
| Eigen::ThreadPoolDevice device(&threads, num_threads); | |
| Tensor<float, 2, DataLayout> t_left(10, 0); | |
| Tensor<float, 2, DataLayout> t_right(0, 20); | |
| Tensor<float, 2, DataLayout> t_result(10, 20); | |
| t_result.setConstant(123.0f); | |
| typedef Tensor<float, 1>::DimensionPair DimPair; | |
| Eigen::array<DimPair, 1> dims{{DimPair(1, 0)}}; | |
| Eigen::Barrier barrier(1); | |
| t_result.device(device, [&barrier]() { barrier.Notify(); }) = t_left.contract(t_right, dims); | |
| barrier.Wait(); | |
| VERIFY_IS_EQUAL(t_result.dimension(0), 10); | |
| VERIFY_IS_EQUAL(t_result.dimension(1), 20); | |
| for (Index i = 0; i < t_result.size(); ++i) { | |
| VERIFY_IS_EQUAL(t_result.data()[i], 0.0f); | |
| } | |
| } | |
| template <typename Scalar> | |
| struct AddBiasOutputKernel { | |
| Scalar bias; | |
| explicit AddBiasOutputKernel(Scalar b) : bias(b) {} | |
| template <typename Index, typename ResScalar> | |
| EIGEN_ALWAYS_INLINE void operator()(const internal::blas_data_mapper<ResScalar, Index, ColMajor>& output_mapper, | |
| const TensorContractionParams&, Index, Index, Index num_rows, | |
| Index num_cols) const { | |
| for (Index i = 0; i < num_rows; ++i) { | |
| for (Index j = 0; j < num_cols; ++j) { | |
| output_mapper(i, j) += bias; | |
| } | |
| } | |
| } | |
| }; | |
| template <int DataLayout> | |
| void test_multithread_zero_dim_contraction_with_output_kernel() { | |
| const int num_threads = internal::random<int>(2, 11); | |
| ThreadPool threads(num_threads); | |
| Eigen::ThreadPoolDevice device(&threads, num_threads); | |
| const float bias = 42.0f; | |
| typedef Tensor<float, 1>::DimensionPair DimPair; | |
| Eigen::array<DimPair, 1> dims{{DimPair(1, 0)}}; | |
| // {10, 0} * {0, 20} -> {10, 20} | |
| { | |
| Tensor<float, 2, DataLayout> t_left(10, 0); | |
| Tensor<float, 2, DataLayout> t_right(0, 20); | |
| Tensor<float, 2, DataLayout> t_result(10, 20); | |
| t_result.setConstant(123.0f); | |
| t_result.device(device) = t_left.contract(t_right, dims, AddBiasOutputKernel<float>(bias)); | |
| VERIFY_IS_EQUAL(t_result.dimension(0), 10); | |
| VERIFY_IS_EQUAL(t_result.dimension(1), 20); | |
| for (Index i = 0; i < t_result.size(); ++i) { | |
| VERIFY_IS_EQUAL(t_result.data()[i], bias); | |
| } | |
| } | |
| // {64, 0} * {0, 64} -> {64, 64} | |
| { | |
| Tensor<float, 2, DataLayout> t_left(64, 0); | |
| Tensor<float, 2, DataLayout> t_right(0, 64); | |
| Tensor<float, 2, DataLayout> t_result(64, 64); | |
| t_result.setConstant(123.0f); | |
| t_result.device(device) = t_left.contract(t_right, dims, AddBiasOutputKernel<float>(bias)); | |
| VERIFY_IS_EQUAL(t_result.dimension(0), 64); | |
| VERIFY_IS_EQUAL(t_result.dimension(1), 64); | |
| for (Index i = 0; i < t_result.size(); ++i) { | |
| VERIFY_IS_EQUAL(t_result.data()[i], bias); | |
| } | |
| } | |
| } | |
| template <int DataLayout> | |
| void test_async_multithread_zero_dim_contraction_with_output_kernel() { | |
| const int num_threads = internal::random<int>(2, 11); | |
| ThreadPool threads(num_threads); | |
| Eigen::ThreadPoolDevice device(&threads, num_threads); | |
| const float bias = 42.0f; | |
| typedef Tensor<float, 1>::DimensionPair DimPair; | |
| Eigen::array<DimPair, 1> dims{{DimPair(1, 0)}}; | |
| // {10, 0} * {0, 20} -> {10, 20} | |
| { | |
| Tensor<float, 2, DataLayout> t_left(10, 0); | |
| Tensor<float, 2, DataLayout> t_right(0, 20); | |
| Tensor<float, 2, DataLayout> t_result(10, 20); | |
| t_result.setConstant(123.0f); | |
| Eigen::Barrier barrier(1); | |
| t_result.device(device, [&barrier]() { barrier.Notify(); }) = | |
| t_left.contract(t_right, dims, AddBiasOutputKernel<float>(bias)); | |
| barrier.Wait(); | |
| VERIFY_IS_EQUAL(t_result.dimension(0), 10); | |
| VERIFY_IS_EQUAL(t_result.dimension(1), 20); | |
| for (Index i = 0; i < t_result.size(); ++i) { | |
| VERIFY_IS_EQUAL(t_result.data()[i], bias); | |
| } | |
| } | |
| // {64, 0} * {0, 64} -> {64, 64} | |
| { | |
| Tensor<float, 2, DataLayout> t_left(64, 0); | |
| Tensor<float, 2, DataLayout> t_right(0, 64); | |
| Tensor<float, 2, DataLayout> t_result(64, 64); | |
| t_result.setConstant(123.0f); | |
| Eigen::Barrier barrier(1); | |
| t_result.device(device, [&barrier]() { barrier.Notify(); }) = | |
| t_left.contract(t_right, dims, AddBiasOutputKernel<float>(bias)); | |
| barrier.Wait(); | |
| VERIFY_IS_EQUAL(t_result.dimension(0), 64); | |
| VERIFY_IS_EQUAL(t_result.dimension(1), 64); | |
| for (Index i = 0; i < t_result.size(); ++i) { | |
| VERIFY_IS_EQUAL(t_result.data()[i], bias); | |
| } | |
| } | |
| } | |
| template <int DataLayout> | |
| void test_multithread_zero_dim_outer_zeros() { | |
| const int num_threads = internal::random<int>(2, 11); | |
| ThreadPool threads(num_threads); | |
| Eigen::ThreadPoolDevice device(&threads, num_threads); | |
| typedef Tensor<float, 1>::DimensionPair DimPair; | |
| Eigen::array<DimPair, 1> dims{{DimPair(1, 0)}}; | |
| // Outer zero on LHS: {0, 5} * {5, 10} -> {0, 10} | |
| { | |
| Tensor<float, 2, DataLayout> t_left(0, 5); | |
| Tensor<float, 2, DataLayout> t_right(5, 10); | |
| t_right.setRandom(); | |
| Tensor<float, 2, DataLayout> t_result(0, 10); | |
| t_result.device(device) = t_left.contract(t_right, dims); | |
| VERIFY_IS_EQUAL(t_result.dimension(0), 0); | |
| VERIFY_IS_EQUAL(t_result.dimension(1), 10); | |
| VERIFY_IS_EQUAL(t_result.size(), 0); | |
| } | |
| // Outer zero on RHS: {10, 5} * {5, 0} -> {10, 0} | |
| { | |
| Tensor<float, 2, DataLayout> t_left(10, 5); | |
| Tensor<float, 2, DataLayout> t_right(5, 0); | |
| t_left.setRandom(); | |
| Tensor<float, 2, DataLayout> t_result(10, 0); | |
| t_result.device(device) = t_left.contract(t_right, dims); | |
| VERIFY_IS_EQUAL(t_result.dimension(0), 10); | |
| VERIFY_IS_EQUAL(t_result.dimension(1), 0); | |
| VERIFY_IS_EQUAL(t_result.size(), 0); | |
| } | |
| // Outer zero on both: {0, 5} * {5, 0} -> {0, 0} | |
| { | |
| Tensor<float, 2, DataLayout> t_left(0, 5); | |
| Tensor<float, 2, DataLayout> t_right(5, 0); | |
| Tensor<float, 2, DataLayout> t_result(0, 0); | |
| t_result.device(device) = t_left.contract(t_right, dims); | |
| VERIFY_IS_EQUAL(t_result.dimension(0), 0); | |
| VERIFY_IS_EQUAL(t_result.dimension(1), 0); | |
| VERIFY_IS_EQUAL(t_result.size(), 0); | |
| } | |
| // Async zero outer dim | |
| { | |
| Tensor<float, 2, DataLayout> t_left(0, 5); | |
| Tensor<float, 2, DataLayout> t_right(5, 10); | |
| t_right.setRandom(); | |
| Tensor<float, 2, DataLayout> t_result(0, 10); | |
| Eigen::Barrier barrier(1); | |
| t_result.device(device, [&barrier]() { barrier.Notify(); }) = t_left.contract(t_right, dims); | |
| barrier.Wait(); | |
| VERIFY_IS_EQUAL(t_result.dimension(0), 0); | |
| VERIFY_IS_EQUAL(t_result.dimension(1), 10); | |
| VERIFY_IS_EQUAL(t_result.size(), 0); | |
| } | |
| } | |
| EIGEN_DECLARE_TEST(tensor_thread_pool) { | |
| CALL_SUBTEST_1(test_multithread_elementwise<>()); | |
| CALL_SUBTEST_1(test_async_multithread_elementwise<>()); | |
| CALL_SUBTEST_1(test_multithread_compound_assignment<>()); | |
| CALL_SUBTEST_2(test_multithread_contraction<ColMajor>()); | |
| CALL_SUBTEST_2(test_multithread_contraction<RowMajor>()); | |
| CALL_SUBTEST_2(test_multithread_gemv_transpose<ColMajor>()); | |
| CALL_SUBTEST_2(test_multithread_gemv_transpose<RowMajor>()); | |
| CALL_SUBTEST_3(test_multithread_chip<>()); | |
| CALL_SUBTEST_3(test_async_multithread_chip<>()); | |
| CALL_SUBTEST_4(test_multithread_volume_patch<>()); | |
| CALL_SUBTEST_4(test_async_multithread_volume_patch<>()); | |
| CALL_SUBTEST_4(test_multithread_tiled_stateful_functor<>()); | |
| CALL_SUBTEST_4(test_async_multithread_tiled_stateful_functor<>()); | |
| CALL_SUBTEST_5(test_multithread_contraction_agrees_with_singlethread<ColMajor>()); | |
| CALL_SUBTEST_5(test_multithread_contraction_agrees_with_singlethread<RowMajor>()); | |
| CALL_SUBTEST_5(test_multithread_contraction_with_output_kernel<ColMajor>()); | |
| CALL_SUBTEST_5(test_multithread_contraction_with_output_kernel<RowMajor>()); | |
| CALL_SUBTEST_6(test_async_multithread_contraction_agrees_with_singlethread<ColMajor>()); | |
| CALL_SUBTEST_6(test_async_multithread_contraction_agrees_with_singlethread<RowMajor>()); | |
| // Test EvalShardedByInnerDimContext parallelization strategy. | |
| CALL_SUBTEST_7(test_sharded_by_inner_dim_contraction<ColMajor>()); | |
| CALL_SUBTEST_7(test_sharded_by_inner_dim_contraction<RowMajor>()); | |
| CALL_SUBTEST_7(test_sharded_by_inner_dim_contraction_with_output_kernel<ColMajor>()); | |
| CALL_SUBTEST_7(test_sharded_by_inner_dim_contraction_with_output_kernel<RowMajor>()); | |
| CALL_SUBTEST_7(test_sharded_by_inner_dim_cost_model<ColMajor>()); | |
| CALL_SUBTEST_7(test_sharded_by_inner_dim_cost_model<RowMajor>()); | |
| CALL_SUBTEST_8(test_async_sharded_by_inner_dim_contraction<ColMajor>()); | |
| CALL_SUBTEST_8(test_async_sharded_by_inner_dim_contraction<RowMajor>()); | |
| CALL_SUBTEST_8(test_async_sharded_by_inner_dim_contraction_with_output_kernel<ColMajor>()); | |
| CALL_SUBTEST_8(test_async_sharded_by_inner_dim_contraction_with_output_kernel<RowMajor>()); | |
| // Exercise various cases that have been problematic in the past. | |
| CALL_SUBTEST_9(test_contraction_corner_cases<ColMajor>()); | |
| CALL_SUBTEST_9(test_contraction_corner_cases<RowMajor>()); | |
| CALL_SUBTEST_10(test_full_contraction<ColMajor>()); | |
| CALL_SUBTEST_10(test_full_contraction<RowMajor>()); | |
| CALL_SUBTEST_11(test_multithreaded_reductions<ColMajor>()); | |
| CALL_SUBTEST_11(test_multithreaded_reductions<RowMajor>()); | |
| CALL_SUBTEST_11(test_multithreaded_complex_reduction<>()); | |
| CALL_SUBTEST_11(test_multithreaded_value_transforming_full_reduction<>()); | |
| CALL_SUBTEST_11(test_multithreaded_argmax<>()); | |
| CALL_SUBTEST_11(test_multithreaded_complex_partial_reductions<>()); | |
| CALL_SUBTEST_12(test_memcpy<>()); | |
| CALL_SUBTEST_12(test_multithread_random<>()); | |
| CALL_SUBTEST_12(test_multithread_forced_eval_non_pod<>()); | |
| CALL_SUBTEST_12(test_enqueue_with_args<>()); | |
| CALL_SUBTEST_12(test_async_forced_eval_non_pod<>()); | |
| CALL_SUBTEST_12(test_async_single_block_scratch_freed_before_done<>()); | |
| TestAllocator test_allocator; | |
| CALL_SUBTEST_13(test_multithread_shuffle<ColMajor>(nullptr)); | |
| CALL_SUBTEST_13(test_multithread_shuffle<RowMajor>(&test_allocator)); | |
| CALL_SUBTEST_13(test_threadpool_allocate<>(&test_allocator)); | |
| CALL_SUBTEST_14(test_multithread_zero_dim_contraction<ColMajor>()); | |
| CALL_SUBTEST_14(test_multithread_zero_dim_contraction<RowMajor>()); | |
| CALL_SUBTEST_14(test_async_multithread_zero_dim_contraction<ColMajor>()); | |
| CALL_SUBTEST_14(test_async_multithread_zero_dim_contraction<RowMajor>()); | |
| CALL_SUBTEST_14(test_multithread_zero_dim_contraction_with_output_kernel<ColMajor>()); | |
| CALL_SUBTEST_14(test_multithread_zero_dim_contraction_with_output_kernel<RowMajor>()); | |
| CALL_SUBTEST_14(test_async_multithread_zero_dim_contraction_with_output_kernel<ColMajor>()); | |
| CALL_SUBTEST_14(test_async_multithread_zero_dim_contraction_with_output_kernel<RowMajor>()); | |
| CALL_SUBTEST_14(test_multithread_zero_dim_outer_zeros<ColMajor>()); | |
| CALL_SUBTEST_14(test_multithread_zero_dim_outer_zeros<RowMajor>()); | |
| // Force CMake to split this test. | |
| // EIGEN_SUFFIXES;1;2;3;4;5;6;7;8;9;10;11;12;13;14 | |
| } |