blob: 893481772227e14a5f537cf6a65031ad44026c67 [file] [edit]
// SPDX-FileCopyrightText: The Eigen Authors
// SPDX-License-Identifier: MPL-2.0
// GEMMs of 16-bit operands: cast to float (lazily converted while packing), mixed with a float operand, and the
// native product, which accumulates in float.
#include <benchmark/benchmark.h>
#include <Eigen/Core>
using namespace Eigen;
#ifndef SCALAR
#define SCALAR Eigen::half
#endif
using Scalar = SCALAR;
using Mat = Matrix<Scalar, Dynamic, Dynamic>;
template <typename Dst, typename Expected>
bool close_enough(const Dst& dst, const Expected& expected, float tol) {
return (dst.template cast<float>() - expected).norm() <= tol * expected.norm();
}
struct Operands {
MatrixXf af, bf, expected;
Mat a, b;
Operands(Index m, Index n, Index k) {
a = MatrixXf::Random(m, k).cast<Scalar>();
b = MatrixXf::Random(k, n).cast<Scalar>();
af = a.cast<float>();
bf = b.cast<float>();
expected = af * bf;
}
};
template <typename Dst, typename Xpr>
EIGEN_DONT_INLINE void assign(Dst& dst, const Xpr& xpr) {
dst.noalias() = xpr;
}
template <typename Dst, typename Xpr>
EIGEN_DONT_INLINE void add(Dst& dst, const Xpr& xpr) {
dst.noalias() += xpr;
}
static void set_counters(benchmark::State& state) {
state.counters["GFLOPS"] =
benchmark::Counter(2.0 * state.range(0) * state.range(1) * state.range(2),
benchmark::Counter::kIsIterationInvariantRate, benchmark::Counter::kIs1000);
}
// Kernel speed of light: the float product of float operands.
static void BM_Float(benchmark::State& state) {
Operands op(state.range(0), state.range(1), state.range(2));
MatrixXf c(op.af.rows(), op.bf.cols());
for (auto _ : state) {
assign(c, op.af * op.bf);
benchmark::DoNotOptimize(c.data());
benchmark::ClobberMemory();
}
set_counters(state);
}
// C (float) = A.cast<float>() * B.cast<float>().
static void BM_CastCast(benchmark::State& state) {
Operands op(state.range(0), state.range(1), state.range(2));
MatrixXf c(op.af.rows(), op.bf.cols());
assign(c, op.a.cast<float>() * op.b.cast<float>());
if (!close_enough(c, op.expected, 1e-5f)) return state.SkipWithError("validation failed");
for (auto _ : state) {
assign(c, op.a.cast<float>() * op.b.cast<float>());
benchmark::DoNotOptimize(c.data());
benchmark::ClobberMemory();
}
set_counters(state);
}
// C (float) = A (float) * B.cast<float>().
static void BM_FloatCast(benchmark::State& state) {
Operands op(state.range(0), state.range(1), state.range(2));
MatrixXf c(op.af.rows(), op.bf.cols());
assign(c, op.af * op.b.cast<float>());
if (!close_enough(c, op.expected, 1e-5f)) return state.SkipWithError("validation failed");
for (auto _ : state) {
assign(c, op.af * op.b.cast<float>());
benchmark::DoNotOptimize(c.data());
benchmark::ClobberMemory();
}
set_counters(state);
}
// C (float) = A.cast<float>() * B (float).
static void BM_CastFloat(benchmark::State& state) {
Operands op(state.range(0), state.range(1), state.range(2));
MatrixXf c(op.af.rows(), op.bf.cols());
assign(c, op.a.cast<float>() * op.bf);
if (!close_enough(c, op.expected, 1e-5f)) return state.SkipWithError("validation failed");
for (auto _ : state) {
assign(c, op.a.cast<float>() * op.bf);
benchmark::DoNotOptimize(c.data());
benchmark::ClobberMemory();
}
set_counters(state);
}
// C (16-bit) = (A.cast<float>() * B.cast<float>()).cast<Scalar>(): widening spelled out by the user.
static void BM_CastCastNarrow(benchmark::State& state) {
Operands op(state.range(0), state.range(1), state.range(2));
Mat c(op.a.rows(), op.b.cols());
assign(c, (op.a.cast<float>() * op.b.cast<float>()).cast<Scalar>());
if (!close_enough(c, op.expected, 1e-2f)) return state.SkipWithError("validation failed");
for (auto _ : state) {
assign(c, (op.a.cast<float>() * op.b.cast<float>()).cast<Scalar>());
benchmark::DoNotOptimize(c.data());
benchmark::ClobberMemory();
}
set_counters(state);
}
// C (16-bit) = A * B, the native product. The loose bound admits a product summed in the 16-bit type.
static void BM_Native(benchmark::State& state) {
Operands op(state.range(0), state.range(1), state.range(2));
Mat c(op.a.rows(), op.b.cols());
assign(c, op.a * op.b);
if (!close_enough(c, op.expected, 1e-1f)) return state.SkipWithError("validation failed");
for (auto _ : state) {
assign(c, op.a * op.b);
benchmark::DoNotOptimize(c.data());
benchmark::ClobberMemory();
}
set_counters(state);
}
// C (16-bit) += A * B.
static void BM_NativeAdd(benchmark::State& state) {
Operands op(state.range(0), state.range(1), state.range(2));
Mat c = Mat::Zero(op.a.rows(), op.b.cols());
add(c, op.a * op.b);
if (!close_enough(c, op.expected, 1e-1f)) return state.SkipWithError("validation failed");
for (auto _ : state) {
add(c, op.a * op.b);
benchmark::DoNotOptimize(c.data());
benchmark::ClobberMemory();
}
set_counters(state);
}
// clang-format off
#define EIGEN_GEMM_CAST_SHAPES \
->Args({32, 32, 32})->Args({64, 64, 64})->Args({128, 128, 128})->Args({256, 256, 256}) \
->Args({512, 512, 512})->Args({1024, 1024, 1024}) \
->Args({4096, 128, 128})->Args({128, 4096, 128})->Args({128, 128, 4096})
BENCHMARK(BM_Float) EIGEN_GEMM_CAST_SHAPES;
BENCHMARK(BM_CastCast) EIGEN_GEMM_CAST_SHAPES;
BENCHMARK(BM_FloatCast) EIGEN_GEMM_CAST_SHAPES;
BENCHMARK(BM_CastFloat) EIGEN_GEMM_CAST_SHAPES;
BENCHMARK(BM_CastCastNarrow) EIGEN_GEMM_CAST_SHAPES;
BENCHMARK(BM_Native) EIGEN_GEMM_CAST_SHAPES;
BENCHMARK(BM_NativeAdd) EIGEN_GEMM_CAST_SHAPES;
// clang-format on