#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION) // Philox Counter based RNG implementation for Metal // Borrowed from aten/src/ATen/core/PhiloxRNGEngine.h // Which in turn borrowed from // http://www.thesalmons.org/john/random123/papers/random123sc11.pdf #pragma once #include namespace c10 { namespace metal { namespace detail { constexpr float uint32_to_uniform_float(uint32_t value) { // maximum value such that `MAX_INT * scale < 1.0` (with float rounding) constexpr float scale = 4.6566127342e-10; return static_cast(value & 0x7FFFFFFF) * scale; } constexpr uint2 splitlong(ulong v) { return uint2(v >> 32, v & 0xffffffff); } } // namespace detail namespace philox4 { constexpr uint2 mulhilo(uint a, uint b) { auto rc = static_cast(a) * b; return detail::splitlong(rc); } inline uint4 single_round(uint4 ctr, uint2 key) { constexpr uint kPhiloxSA = 0xD2511F53; constexpr uint kPhiloxSB = 0xCD9E8D57; auto rc0 = mulhilo(kPhiloxSA, ctr.x); auto rc1 = mulhilo(kPhiloxSB, ctr.z); return uint4(rc1.x ^ ctr.y ^ key.x, rc1.y, rc0.x ^ ctr.w ^ key.y, rc0.y); } inline uint4 multiple_rounds(uint4 ctr, uint2 key, uint rounds) { constexpr uint2 kPhilox10 = {0x9E3779B9, 0xBB67AE85}; for (uint round = 0; round < rounds - 1; ++round) { ctr = single_round(ctr, key); key += kPhilox10; } return ctr; } inline uint4 rand(long seed, long index) { uint4 ctr = 0; ctr.zw = detail::splitlong(index); return multiple_rounds(ctr, detail::splitlong(seed), 10); } } // namespace philox4 inline float randn(long seed, long index) { auto value = philox4::rand(seed, index); float u1 = 1.0 - detail::uint32_to_uniform_float(value.x); float u2 = 1.0 - detail::uint32_to_uniform_float(value.y); return ::metal::sqrt(-2.0 * ::metal::log(u1)) * ::metal::cos(2.0 * M_PI_F * u2); } // Box-Muller transform: convert two raw Philox uint32 outputs into two // independent N(0, 1) samples. The radial uniform `u1` is clamped away from // `0` so `log(u1)` stays finite; the angular uniform `u2` needs no clamp // because `sin` / `cos` are bounded over the entire domain. `sincos` is the // fused intrinsic that returns `sin` and writes `cos` in one call. inline float2 box_muller_from_philox(uint2 raw) { constexpr float eps = ::metal::numeric_limits::epsilon(); float u1 = ::metal::max(detail::uint32_to_uniform_float(raw.x), eps); float u2 = detail::uint32_to_uniform_float(raw.y); float r = ::metal::precise::sqrt(-2.0f * ::metal::precise::log(u1)); float c; float s = ::metal::precise::sincos(2.0f * M_PI_F * u2, c); return r * float2(c, s); } inline float rand(long seed, long index) { auto value = philox4::rand(seed, index); return detail::uint32_to_uniform_float(value.x); } inline long randint64(long seed, long index, long low, long high) { auto range = high - low; auto value = philox4::rand(seed, index); // TODO: Implement better algorithm for large ranges return low + static_cast(detail::uint32_to_uniform_float(value.x) * range); } } // namespace metal } // namespace c10 #else #error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined." #endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)