This patch adds GPU benchmarks for the exp (`exp`, `expf`, `expf16`) and log (`log`, `logf`, `logf16`) families of math functions. Adding these benchmarks revealed a key limitation in the existing framework: the input generation mechanism was hardcoded to a single strategy that sampled numbers with a uniform distribution of their unbiased exponents. While this strategy is effective for values spanning multiple orders of magnitude, it is not suitable for linear ranges. The previous framework lacked the flexibility to support this. ### Summary of Changes **1. Framework Refactoring for Flexible Input Sampling:** The GPU benchmark framework was refactored to support multiple, pluggable input sampling strategies. * **`Random.h`:** A new header was created to house the `RandomGenerator` and the new distribution classes. * **Distribution Classes:** Two sampling strategies were implemented: * `UniformExponent`: Formalizes the previous logic of sampling numbers with a uniform distribution of their unbiased exponents. It can now also be configured to produce only positive values, which is essential for functions like `log`. * `UniformLinear`: A new strategy that samples numbers from a uniform distribution over a linear interval `[min, max)`. * **`MathPerf` Update:** The `MathPerf` class was updated with a generic `run_throughput` method that is templated on a distribution object. This makes the framework extensible to future sampling strategies. **2. New Benchmarks for `exp` and `log`:** Using the newly refactored framework, benchmarks were added for `exp`, `expf`, `expf16`, `log`, `logf`, and `logf16`. The test intervals were carefully chosen to measure the performance of distinct behavioral regions of each function.
191 lines
6.2 KiB
C++
191 lines
6.2 KiB
C++
//===-- Pseudo-random number generation utilities ---------------*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef LLVM_LIBC_BENCHMARKS_GPU_RANDOM_H
|
|
#define LLVM_LIBC_BENCHMARKS_GPU_RANDOM_H
|
|
|
|
#include "hdr/stdint_proxy.h"
|
|
#include "src/__support/CPP/algorithm.h"
|
|
#include "src/__support/CPP/optional.h"
|
|
#include "src/__support/CPP/type_traits.h"
|
|
#include "src/__support/FPUtil/FPBits.h"
|
|
#include "src/__support/macros/attributes.h"
|
|
#include "src/__support/macros/config.h"
|
|
#include "src/__support/macros/properties/types.h"
|
|
#include "src/__support/sign.h"
|
|
|
|
namespace LIBC_NAMESPACE_DECL {
|
|
namespace benchmarks {
|
|
|
|
// Pseudo-random number generator (PRNG) that produces unsigned 64-bit, 32-bit,
|
|
// and 16-bit integers. The implementation is based on the xorshift* generator,
|
|
// seeded using SplitMix64 for robust initialization. For more details, see:
|
|
// https://en.wikipedia.org/wiki/Xorshift
|
|
class RandomGenerator {
|
|
uint64_t state;
|
|
|
|
static LIBC_INLINE uint64_t splitmix64(uint64_t x) noexcept {
|
|
x += 0x9E3779B97F4A7C15ULL;
|
|
x = (x ^ (x >> 30)) * 0xBF58476D1CE4E5B9ULL;
|
|
x = (x ^ (x >> 27)) * 0x94D049BB133111EBULL;
|
|
x = (x ^ (x >> 31));
|
|
return x ? x : 0x9E3779B97F4A7C15ULL;
|
|
}
|
|
|
|
public:
|
|
explicit LIBC_INLINE RandomGenerator(uint64_t seed) noexcept
|
|
: state(splitmix64(seed)) {}
|
|
|
|
LIBC_INLINE uint64_t next64() noexcept {
|
|
uint64_t x = state;
|
|
x ^= x >> 12;
|
|
x ^= x << 25;
|
|
x ^= x >> 27;
|
|
state = x;
|
|
return x * 0x2545F4914F6CDD1DULL;
|
|
}
|
|
|
|
LIBC_INLINE uint32_t next32() noexcept {
|
|
return static_cast<uint32_t>(next64() >> 32);
|
|
}
|
|
|
|
LIBC_INLINE uint16_t next16() noexcept {
|
|
return static_cast<uint16_t>(next64() >> 48);
|
|
}
|
|
};
|
|
|
|
// Generates random floating-point numbers where the unbiased binary exponent
|
|
// is sampled uniformly in `[min_exp, max_exp]`. The significand bits are
|
|
// always randomized, while the sign is randomized by default but can be fixed.
|
|
// Evenly covers orders of magnitude; never yields Inf/NaN.
|
|
template <typename T> class UniformExponent {
|
|
static_assert(cpp::is_same_v<T, float16> || cpp::is_same_v<T, float> ||
|
|
cpp::is_same_v<T, double>,
|
|
"UniformExponent supports float16, float, and double");
|
|
|
|
using FPBits = LIBC_NAMESPACE::fputil::FPBits<T>;
|
|
using Storage = typename FPBits::StorageType;
|
|
|
|
public:
|
|
explicit UniformExponent(int min_exp = -FPBits::EXP_BIAS,
|
|
int max_exp = FPBits::EXP_BIAS,
|
|
cpp::optional<Sign> forced_sign = cpp::nullopt)
|
|
: min_exp(clamp_exponent(cpp::min(min_exp, max_exp))),
|
|
max_exp(clamp_exponent(cpp::max(min_exp, max_exp))),
|
|
forced_sign(forced_sign) {}
|
|
|
|
LIBC_INLINE T operator()(RandomGenerator &rng) const noexcept {
|
|
// Sample unbiased exponent e uniformly in [min_exp, max_exp] without modulo
|
|
// bias, using rejection sampling
|
|
auto sample_in_range = [&](uint64_t r) -> int32_t {
|
|
const uint64_t range = static_cast<uint64_t>(
|
|
static_cast<int64_t>(max_exp) - static_cast<int64_t>(min_exp) + 1);
|
|
const uint64_t threshold = (-range) % range;
|
|
while (r < threshold)
|
|
r = rng.next64();
|
|
return static_cast<int32_t>(min_exp + static_cast<int64_t>(r % range));
|
|
};
|
|
const int32_t e = sample_in_range(rng.next64());
|
|
|
|
// Start from random bits to get random sign and mantissa
|
|
FPBits xbits([&] {
|
|
if constexpr (cpp::is_same_v<T, double>)
|
|
return FPBits(rng.next64());
|
|
else if constexpr (cpp::is_same_v<T, float>)
|
|
return FPBits(rng.next32());
|
|
else
|
|
return FPBits(rng.next16());
|
|
}());
|
|
|
|
if (e == -FPBits::EXP_BIAS) {
|
|
// Subnormal: biased exponent must be 0; ensure mantissa != 0 to avoid 0
|
|
xbits.set_biased_exponent(Storage(0));
|
|
if (xbits.get_mantissa() == Storage(0))
|
|
xbits.set_mantissa(Storage(1));
|
|
} else {
|
|
// Normal: biased exponent in [1, 2 * FPBits::EXP_BIAS]
|
|
const int32_t biased = e + FPBits::EXP_BIAS;
|
|
xbits.set_biased_exponent(static_cast<Storage>(biased));
|
|
}
|
|
|
|
if (forced_sign)
|
|
xbits.set_sign(*forced_sign);
|
|
|
|
return xbits.get_val();
|
|
}
|
|
|
|
private:
|
|
static LIBC_INLINE int clamp_exponent(int val) noexcept {
|
|
if (val < -FPBits::EXP_BIAS)
|
|
return -FPBits::EXP_BIAS;
|
|
|
|
if (val > FPBits::EXP_BIAS)
|
|
return FPBits::EXP_BIAS;
|
|
|
|
return val;
|
|
}
|
|
|
|
const int min_exp;
|
|
const int max_exp;
|
|
const cpp::optional<Sign> forced_sign;
|
|
};
|
|
|
|
// Generates random floating-point numbers that are uniformly distributed on
|
|
// a linear scale. Values are sampled from `[min_val, max_val)`.
|
|
template <typename T> class UniformLinear {
|
|
static_assert(cpp::is_same_v<T, float16> || cpp::is_same_v<T, float> ||
|
|
cpp::is_same_v<T, double>,
|
|
"UniformLinear supports float16, float, and double");
|
|
|
|
using FPBits = LIBC_NAMESPACE::fputil::FPBits<T>;
|
|
using Storage = typename FPBits::StorageType;
|
|
|
|
static constexpr T MAX_NORMAL = FPBits::max_normal().get_val();
|
|
|
|
public:
|
|
explicit UniformLinear(T min_val = -MAX_NORMAL, T max_val = MAX_NORMAL)
|
|
: min_val(clamp_val(cpp::min(min_val, max_val))),
|
|
max_val(clamp_val(cpp::max(min_val, max_val))) {}
|
|
|
|
LIBC_INLINE T operator()(RandomGenerator &rng) const noexcept {
|
|
double u = standard_uniform(rng.next64());
|
|
double a = static_cast<double>(min_val);
|
|
double b = static_cast<double>(max_val);
|
|
double y = a + (b - a) * u;
|
|
return static_cast<T>(y);
|
|
}
|
|
|
|
private:
|
|
static LIBC_INLINE T clamp_val(T val) noexcept {
|
|
if (val < -MAX_NORMAL)
|
|
return -MAX_NORMAL;
|
|
|
|
if (val > MAX_NORMAL)
|
|
return MAX_NORMAL;
|
|
|
|
return val;
|
|
}
|
|
|
|
static LIBC_INLINE double standard_uniform(uint64_t x) noexcept {
|
|
constexpr int PREC_BITS =
|
|
LIBC_NAMESPACE::fputil::FPBits<double>::SIG_LEN + 1;
|
|
constexpr int SHIFT_BITS = LIBC_NAMESPACE::fputil::FPBits<double>::EXP_LEN;
|
|
constexpr double INV = 1.0 / static_cast<double>(1ULL << PREC_BITS);
|
|
|
|
return static_cast<double>(x >> SHIFT_BITS) * INV;
|
|
}
|
|
|
|
const T min_val;
|
|
const T max_val;
|
|
};
|
|
|
|
} // namespace benchmarks
|
|
} // namespace LIBC_NAMESPACE_DECL
|
|
|
|
#endif
|