#if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION) #pragma once #include #include #include #include #include #include #include #include #include #ifdef CPU_CAPABILITY_AVX2 #include #include #endif namespace at::native::templates::cpu { namespace { // ==================================================== Random ======================================================== template void random_from_to_kernel(TensorIteratorBase& iter, uint64_t range, int64_t base, RNG generator) { AT_DISPATCH_V2(iter.dtype(), "random_from_to_kernel_cpu", AT_WRAP([&] { std::lock_guard lock(generator->mutex_); cpu_serial_kernel(iter, [range, base, generator]() -> scalar_t { uniform_int_from_to_distribution random(range, base); return random(generator); }); }), kBool, kHalf, kBFloat16, AT_EXPAND(AT_ALL_TYPES), AT_EXPAND(AT_BAREBONES_UNSIGNED_TYPES)); } // This is the special kernel to handle single specific case: // from(inclusive) = std::numeric_limits::lowest() // to(exclusive) = None (= std::numeric_limits::max() + 1) template void random_full_64_bits_range_kernel(TensorIteratorBase& iter, RNG generator) { AT_DISPATCH_ALL_TYPES_AND(at::ScalarType::BFloat16, iter.dtype(), "random_full_64_bits_range_kernel_cpu", [&] { if constexpr (std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v) { std::lock_guard lock(generator->mutex_); cpu_serial_kernel(iter, [generator]() -> scalar_t { uniform_int_full_range_distribution random; return random(generator); }); } else { TORCH_CHECK(false, "random_full_64_bits_range_kernel_cpu handles only int64, double, float and bfloat16"); } }); } template struct RandomFromToKernel { void operator()(TensorIteratorBase& iter, uint64_t range, int64_t base, std::optional gen) { random_from_to_kernel(iter, range, base, check_generator(gen)); } void operator()(TensorIteratorBase& iter, std::optional gen) { random_full_64_bits_range_kernel(iter, check_generator(gen)); } }; template void random_kernel(TensorIteratorBase& iter, RNG generator) { std::lock_guard lock(generator->mutex_); AT_DISPATCH_ALL_TYPES_AND3(at::ScalarType::Half, at::ScalarType::BFloat16, at::ScalarType::Bool, iter.dtype(), "random_kernel_cpu", [&] { cpu_serial_kernel(iter, [generator]() -> scalar_t { uniform_int_distribution random; return random(generator); }); }); } template struct RandomKernel { void operator()(TensorIteratorBase& iter, std::optional gen) { random_kernel(iter, check_generator(gen)); } }; // ==================================================== Normal ======================================================== // Box-Muller transform on 16 elements (8 pairs of uniforms). // Primary template is scalar; float specialization uses SIMD when available. // Constructed once with mean/std so constants are not rebuilt per call. template struct NormalFill16 { opmath_t mean_; opmath_t std_; NormalFill16(opmath_t mean, opmath_t std) : mean_(mean), std_(std) {} void operator()(opmath_t* data) const { for (const auto j : c10::irange(8)) { const opmath_t u1 = 1 - data[j]; // [0, 1) -> (0, 1] for log. const opmath_t u2 = data[j + 8]; const opmath_t radius = std::sqrt(-2 * std::log(u1)); const opmath_t theta = 2.0f * c10::pi * u2; data[j] = std::fma(radius * std::cos(theta), std_, mean_); data[j + 8] = std::fma(radius * std::sin(theta), std_, mean_); } } }; #if defined(CPU_CAPABILITY_AVX2) template <> struct NormalFill16 { __m256 mean_; __m256 std_; __m256 two_pi_ = _mm256_set1_ps(2.0f * c10::pi); __m256 one_ = _mm256_set1_ps(1.0f); __m256 minus_two_ = _mm256_set1_ps(-2.0f); NormalFill16(float mean, float std) : mean_(_mm256_set1_ps(mean)), std_(_mm256_set1_ps(std)) {} void operator()(float* data) const { const __m256 u1 = _mm256_sub_ps(one_, _mm256_loadu_ps(data)); const __m256 u2 = _mm256_loadu_ps(data + 8); // sincos256_ps and log256_ps are from avx_mathfun.h const __m256 radius = _mm256_sqrt_ps(_mm256_mul_ps(minus_two_, log256_ps(u1))); const __m256 theta = _mm256_mul_ps(two_pi_, u2); __m256 sintheta, costheta; sincos256_ps(theta, &sintheta, &costheta); const __m256 n1 = _mm256_mul_ps(radius, costheta); const __m256 n2 = _mm256_mul_ps(radius, sintheta); _mm256_storeu_ps(data, _mm256_fmadd_ps(n1, std_, mean_)); _mm256_storeu_ps(data + 8, _mm256_fmadd_ps(n2, std_, mean_)); } }; #elif defined(__VSX__) || defined(CPU_CAPABILITY_VSX) template <> struct NormalFill16::size() == 8> { using Vec = Vectorized; Vec mean_; Vec std_; Vec two_pi_ = Vec(2.0f * c10::pi); Vec one_ = Vec(1.0f); Vec minus_two_ = Vec(-2.0f); NormalFill16(float mean, float std) : mean_(mean), std_(std) {} void operator()(float* data) const { Vec u1 = one_ - Vec::loadu(data); Vec u2 = Vec::loadu(data + 8); Vec radius = (minus_two_ * u1.log()).sqrt(); Vec theta = two_pi_ * u2; Vec output1 = radius * theta.cos() * std_ + mean_; Vec output2 = radius * theta.sin() * std_ + mean_; output1.store(data); output2.store(data + 8); } }; #endif template void normal_fill(const TensorBase &self, double mean, double std, RNG generator) { using opmath_t = at::opmath_type; scalar_t *data = self.data_ptr(); auto size = self.numel(); std::lock_guard lock(generator->mutex_); at::uniform_real_distribution uniform(0, 1); NormalFill16 normal_fill_16( static_cast(mean), static_cast(std)); if constexpr (std::is_same_v) { // float/double: generate uniform samples directly into the output buffer, // then apply Box-Muller in-place. for (const auto i : c10::irange(size)) { data[i] = uniform(generator); } for (int64_t i = 0; i < size - 15; i += 16) { normal_fill_16(data + i); } // Recompute the last 16 values. if (size % 16 != 0) { data = data + size - 16; for (const auto i : c10::irange(16)) { data[i] = uniform(generator); } normal_fill_16(data); } } else { // bf16/fp16: generate in opmath_t precision using a stack buffer, // apply Box-Muller, then cast down to scalar_t. opmath_t buf[16]; for (int64_t i = 0; i < size - 15; i += 16) { for (const auto j : c10::irange(16)) { buf[j] = uniform(generator); } normal_fill_16(buf); for (const auto j : c10::irange(16)) { data[i + j] = static_cast(buf[j]); } } // Recompute the last 16 values. if (size % 16 != 0) { int64_t offset = size - 16; for (const auto j : c10::irange(16)) { buf[j] = uniform(generator); } normal_fill_16(buf); for (const auto j : c10::irange(16)) { data[offset + j] = static_cast(buf[j]); } } } } template void normal_kernel(const TensorBase &self, double mean, double std, RNG generator) { auto size = self.numel(); AT_DISPATCH_FLOATING_TYPES_AND2(kHalf, kBFloat16, self.scalar_type(), "normal_kernel_cpu", [&] { if (size >= 16 && self.is_contiguous()) { normal_fill(self, mean, std, generator); } else { auto iter = TensorIterator::borrowing_nullary_op(self); std::lock_guard lock(generator->mutex_); cpu_serial_kernel(iter, [mean, std, generator]() -> scalar_t { at::normal_distribution normal(mean, std); return static_cast(normal(generator)); }); } }); } template struct NormalKernel { void operator()(Tensor& self, double mean, double std, std::optional gen) { normal_kernel(self, mean, std, check_generator(gen)); } }; // ==================================================== Uniform ======================================================= template void uniform_kernel(TensorIteratorBase& iter, double from_, double to_, RNG generator) { AT_DISPATCH_FLOATING_TYPES_AND2(kHalf, kBFloat16, iter.dtype(), "uniform_kernel_cpu", [&]() { std::lock_guard lock(generator->mutex_); using opmath_t = at::opmath_type; auto from = static_cast(from_); auto to = static_cast(to_); auto to_scalar = static_cast(to_); auto from_scalar = static_cast(from_); at::uniform_real_distribution uniform(from, to); cpu_serial_kernel(iter, [&uniform, generator, to_scalar, from_scalar]() -> scalar_t { auto value = static_cast(uniform(generator)); // Clamp if the float→scalar_t cast rounded up to the upper bound return value == to_scalar ? from_scalar : value; }); }); } template struct UniformKernel { void operator()(TensorIteratorBase& iter, double from, double to, std::optional gen) { uniform_kernel(iter, from, to, check_generator(gen)); } }; // ==================================================== Cauchy ======================================================== template void cauchy_kernel(TensorIteratorBase& iter, double median, double sigma, RNG generator) { AT_DISPATCH_FLOATING_TYPES_AND2(kHalf, kBFloat16, iter.dtype(), "cauchy_cpu", [&]() { std::lock_guard lock(generator->mutex_); at::cauchy_distribution cauchy(median, sigma); cpu_serial_kernel(iter, [&cauchy, generator]() -> scalar_t { return static_cast(cauchy(generator)); }); }); } template struct CauchyKernel { void operator()(TensorIteratorBase& iter, double median, double sigma, std::optional gen) { cauchy_kernel(iter, median, sigma, check_generator(gen)); } }; // ================================================== LogNormal ======================================================= template void log_normal_kernel(TensorIteratorBase& iter, double mean, double std, RNG generator) { AT_DISPATCH_FLOATING_TYPES_AND2(at::ScalarType::Half, at::ScalarType::BFloat16, iter.dtype(), "log_normal_cpu", [&]() { std::lock_guard lock(generator->mutex_); at::lognormal_distribution logNormal(mean, std); cpu_serial_kernel(iter, [&logNormal, generator]() -> scalar_t { return static_cast(logNormal(generator)); }); }); } template struct LogNormalKernel { void operator()(TensorIteratorBase& iter, double mean, double std, std::optional gen) { log_normal_kernel(iter, mean, std, check_generator(gen)); } }; // =================================================== Geometric ====================================================== template void geometric_kernel(TensorIteratorBase& iter, double p, RNG generator) { AT_DISPATCH_ALL_TYPES_AND2(at::ScalarType::Half, at::ScalarType::BFloat16, iter.dtype(), "geometric_cpu", [&]() { std::lock_guard lock(generator->mutex_); at::geometric_distribution geometric(p); cpu_serial_kernel(iter, [&geometric, generator]() -> scalar_t { return static_cast(geometric(generator)); }); }); } template struct GeometricKernel { void operator()(TensorIteratorBase& iter, double p, std::optional gen) { geometric_kernel(iter, p, check_generator(gen)); } }; // ================================================== Exponential ===================================================== template void exponential_kernel(TensorIteratorBase& iter, double lambda, RNG generator) { TORCH_CHECK(isFloatingType(iter.dtype()), "Exponential distribution is a continuous probability distribution. dtype must be a floating point but you specified ", iter.dtype()); AT_DISPATCH_FLOATING_TYPES_AND2(at::ScalarType::Half, at::ScalarType::BFloat16, iter.dtype(), "exponential_cpu", [&]() { std::lock_guard lock(generator->mutex_); at::exponential_distribution exponential(lambda); cpu_serial_kernel(iter, [&exponential, generator]() -> scalar_t { return static_cast(exponential(generator)); }); }); } template struct ExponentialKernel { void operator()(TensorIteratorBase& iter, double lambda, std::optional gen) { exponential_kernel(iter, lambda, check_generator(gen)); } }; // ================================================== Bernoulli ======================================================= template void bernoulli_kernel(const TensorBase &self, const TensorBase &p_, RNG generator) { AT_DISPATCH_ALL_TYPES_AND3(at::ScalarType::Bool, at::ScalarType::BFloat16, at::ScalarType::Half, self.scalar_type(), "bernoulli_tensor_cpu_self_", [&] { // See Note [Acquire lock when using random generators] std::lock_guard lock(generator->mutex_); using self_t = scalar_t; auto p_cpu = p_.to(kCPU); auto p = expand_inplace(self, p_cpu); auto iter = TensorIteratorConfig() .add_output(self) .add_const_input(*p) .check_all_same_dtype(false) .build(); if (p->scalar_type() == kDouble) { cpu_serial_kernel(iter, [&](const double p_val) -> self_t { at::bernoulli_distribution bernoulli(p_val); return static_cast(bernoulli(generator)); }); } else { AT_DISPATCH_FLOATING_TYPES_AND2(at::ScalarType::BFloat16, at::ScalarType::Half, p->scalar_type(), "bernoulli_tensor_cpu_p_", [&] { using p_t = scalar_t; cpu_serial_kernel(iter, [&](const p_t p_val) -> self_t { at::bernoulli_distribution bernoulli(p_val); return static_cast(bernoulli(generator)); }); }); } }); } template void bernoulli_kernel(const TensorBase &self, double p, RNG generator) { AT_DISPATCH_ALL_TYPES_AND3(at::ScalarType::Bool, at::ScalarType::BFloat16, at::ScalarType::Half, self.scalar_type(), "bernoulli_scalar_cpu_", [&] { // See Note [Acquire lock when using random generators] std::lock_guard lock(generator->mutex_); auto iter = TensorIterator::borrowing_nullary_op(self); cpu_serial_kernel(iter, [p, generator]() -> scalar_t { at::bernoulli_distribution bernoulli(p); return static_cast(bernoulli(generator)); }); }); } template struct BernoulliKernel { void operator()(const TensorBase &self, double p, std::optional gen) { bernoulli_kernel(self, p, check_generator(gen)); } void operator()(const TensorBase &self, const TensorBase &p_, std::optional gen) { bernoulli_kernel(self, p_, check_generator(gen)); } }; }} #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)