| //===-- 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 |