| // This file is part of Eigen, a lightweight C++ template library |
| // for linear algebra. |
| // |
| // 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-FileCopyrightText: The Eigen Authors |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| // Tensor contraction on GpuDevice against cuBLAS, for square matrices from 256 to 2048 and a skinny shape. Eigen's |
| // contraction is a hand-written tile kernel from 2014 with no tensor cores, so the ratio here is the gap a library |
| // dispatch would close; reporting FLOP/s for both makes it a one-line read. |
| |
| #include "gpu_bench_common.h" |
| |
| #include <cublas_v2.h> |
| |
| namespace { |
| |
| using eigen_bench::DeviceBuffer; |
| |
| const int kLaunchesPerIteration = 2; |
| const int kIterations = 10; |
| |
| #define EIGEN_BENCH_CUBLAS_CHECK(expr) \ |
| do { \ |
| const cublasStatus_t eigen_bench_status = (expr); \ |
| if (eigen_bench_status != CUBLAS_STATUS_SUCCESS) { \ |
| std::fprintf(stderr, "%s:%d: %s: cuBLAS status %d\n", __FILE__, __LINE__, #expr, \ |
| static_cast<int>(eigen_bench_status)); \ |
| std::abort(); \ |
| } \ |
| } while (0) |
| |
| template <typename T> |
| using Mat = Eigen::TensorMap<Eigen::Tensor<T, 2>, Eigen::Aligned>; |
| |
| template <typename T> |
| cublasStatus_t gemm(cublasHandle_t handle, int m, int n, int k, const T* a, const T* b, T* c); |
| template <> |
| cublasStatus_t gemm<float>(cublasHandle_t handle, int m, int n, int k, const float* a, const float* b, float* c) { |
| const float alpha = 1.0f, beta = 0.0f; |
| return cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, m, n, k, &alpha, a, m, b, k, &beta, c, m); |
| } |
| template <> |
| cublasStatus_t gemm<double>(cublasHandle_t handle, int m, int n, int k, const double* a, const double* b, double* c) { |
| const double alpha = 1.0, beta = 0.0; |
| return cublasDgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, m, n, k, &alpha, a, m, b, k, &beta, c, m); |
| } |
| |
| // C = A * B, column major in both implementations. Validate every timed shape, including filtered runs. |
| template <typename T, bool UseCublas> |
| void runContraction(benchmark::State& state) { |
| const int64_t m = state.range(0), k = state.range(1), n = state.range(2); |
| DeviceBuffer<T> a(m * k), b(k * n), eigen_c(m * n), cublas_c(m * n); |
| a.fillRandom(); |
| b.fillRandom(); |
| Eigen::GpuStreamDevice stream; |
| Eigen::GpuDevice device(&stream); |
| Mat<T> ta(a.data(), m, k), tb(b.data(), k, n), tc(eigen_c.data(), m, n); |
| const Eigen::array<Eigen::IndexPair<Eigen::Index>, 1> dims{Eigen::IndexPair<Eigen::Index>(1, 0)}; |
| const auto eigen_enqueue = [&] { tc.device(device) = ta.contract(tb, dims); }; |
| cublasHandle_t handle = nullptr; |
| EIGEN_BENCH_CUBLAS_CHECK(cublasCreate(&handle)); |
| EIGEN_BENCH_CUBLAS_CHECK(cublasSetStream(handle, device.stream())); |
| const auto cublas_enqueue = [&] { |
| EIGEN_BENCH_CUBLAS_CHECK(gemm<T>(handle, static_cast<int>(m), static_cast<int>(n), static_cast<int>(k), a.data(), |
| b.data(), cublas_c.data())); |
| }; |
| eigen_enqueue(); |
| cublas_enqueue(); |
| device.synchronize(); |
| // The implementations accumulate in different orders; allow error proportional to the contracted extent. |
| eigen_bench::requireClose(eigen_c.toHost(), cublas_c.toHost(), |
| k * static_cast<double>(std::numeric_limits<T>::epsilon()), "contraction"); |
| const auto enqueue = [&] { |
| if (UseCublas) { |
| cublas_enqueue(); |
| } else { |
| eigen_enqueue(); |
| } |
| }; |
| eigen_bench::warmUp(device.stream(), enqueue); |
| eigen_bench::timeLaunches(state, device.stream(), enqueue, kLaunchesPerIteration); |
| state.counters["FLOP/s"] = benchmark::Counter(2.0 * m * n * k, benchmark::Counter::kIsIterationInvariantRate); |
| EIGEN_BENCH_CUBLAS_CHECK(cublasDestroy(handle)); |
| } |
| |
| template <typename T> |
| void BM_Contraction(benchmark::State& state) { |
| runContraction<T, false>(state); |
| } |
| |
| template <typename T> |
| void BM_CublasGemm(benchmark::State& state) { |
| runContraction<T, true>(state); |
| } |
| |
| } // namespace |
| |
| #define EIGEN_GPU_CONTRACTION_SHAPES(NAME, TYPE) \ |
| BENCHMARK_TEMPLATE(NAME, TYPE) \ |
| ->Args({256, 256, 256}) \ |
| ->Args({512, 512, 512}) \ |
| ->Args({1024, 1024, 1024}) \ |
| ->Args({2048, 2048, 2048}) \ |
| ->Args({4096, 64, 4096}) \ |
| ->UseManualTime() \ |
| ->Iterations(kIterations); |
| |
| EIGEN_GPU_CONTRACTION_SHAPES(BM_Contraction, float) |
| EIGEN_GPU_CONTRACTION_SHAPES(BM_Contraction, double) |
| EIGEN_GPU_CONTRACTION_SHAPES(BM_CublasGemm, float) |
| EIGEN_GPU_CONTRACTION_SHAPES(BM_CublasGemm, double) |
| EIGEN_GPU_BENCHMARK_MAIN() |