blob: e8017db8509856a8ec59bbe5d63acd7394b6b830 [file]
// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2014 Benoit Steiner <benoit.steiner.goog@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// SPDX-License-Identifier: MPL-2.0
#define EIGEN_TEST_NO_LONGDOUBLE
#define EIGEN_TEST_NO_COMPLEX
#define EIGEN_DEFAULT_DENSE_INDEX_TYPE int
#define EIGEN_USE_GPU
#include "main.h"
#include <Eigen/Tensor>
void test_gpu_random_uniform() {
Tensor<float, 2> out(72, 97);
out.setZero();
std::size_t out_bytes = out.size() * sizeof(float);
float* d_out;
gpuMalloc((void**)(&d_out), out_bytes);
Eigen::GpuStreamDevice stream;
Eigen::GpuDevice gpu_device(&stream);
Eigen::TensorMap<Eigen::Tensor<float, 2> > gpu_out(d_out, 72, 97);
gpu_out.device(gpu_device) = gpu_out.random();
assert(gpuMemcpyAsync(out.data(), d_out, out_bytes, gpuMemcpyDeviceToHost, gpu_device.stream()) == gpuSuccess);
assert(gpuStreamSynchronize(gpu_device.stream()) == gpuSuccess);
// For now we just check this code doesn't crash.
// TODO: come up with a valid test of randomness
}
void test_gpu_random_normal() {
Tensor<float, 2> out(72, 97);
out.setZero();
std::size_t out_bytes = out.size() * sizeof(float);
float* d_out;
gpuMalloc((void**)(&d_out), out_bytes);
Eigen::GpuStreamDevice stream;
Eigen::GpuDevice gpu_device(&stream);
Eigen::TensorMap<Eigen::Tensor<float, 2> > gpu_out(d_out, 72, 97);
Eigen::internal::NormalRandomGenerator<float> gen(true);
gpu_out.device(gpu_device) = gpu_out.random(gen);
assert(gpuMemcpyAsync(out.data(), d_out, out_bytes, gpuMemcpyDeviceToHost, gpu_device.stream()) == gpuSuccess);
assert(gpuStreamSynchronize(gpu_device.stream()) == gpuSuccess);
}
template <typename Scalar>
void test_gpu_random_uniform_range(int rows, int cols) {
Tensor<Scalar, 2> out(rows, cols);
out.setZero();
std::size_t out_bytes = out.size() * sizeof(Scalar);
Scalar* d_out;
gpuMalloc((void**)(&d_out), out_bytes);
Eigen::GpuStreamDevice stream;
Eigen::GpuDevice gpu_device(&stream);
Eigen::TensorMap<Eigen::Tensor<Scalar, 2> > gpu_out(d_out, rows, cols);
gpu_out.device(gpu_device) = gpu_out.random();
assert(gpuMemcpyAsync(out.data(), d_out, out_bytes, gpuMemcpyDeviceToHost, gpu_device.stream()) == gpuSuccess);
assert(gpuStreamSynchronize(gpu_device.stream()) == gpuSuccess);
// All uniform draws must lie in [0, 1).
int num_out_of_range = 0;
for (int i = 0; i < out.size(); ++i) {
if (!(out.data()[i] >= Scalar(0.0f) && out.data()[i] < Scalar(1.0f))) ++num_out_of_range;
}
VERIFY_IS_EQUAL(num_out_of_range, 0);
}
template <typename Scalar>
void test_gpu_random_normal_all_finite(int rows, int cols) {
Tensor<Scalar, 2> out(rows, cols);
out.setZero();
std::size_t out_bytes = out.size() * sizeof(Scalar);
Scalar* d_out;
gpuMalloc((void**)(&d_out), out_bytes);
Eigen::GpuStreamDevice stream;
Eigen::GpuDevice gpu_device(&stream);
Eigen::TensorMap<Eigen::Tensor<Scalar, 2> > gpu_out(d_out, rows, cols);
Eigen::internal::NormalRandomGenerator<Scalar> gen(true);
gpu_out.device(gpu_device) = gpu_out.random(gen);
assert(gpuMemcpyAsync(out.data(), d_out, out_bytes, gpuMemcpyDeviceToHost, gpu_device.stream()) == gpuSuccess);
assert(gpuStreamSynchronize(gpu_device.stream()) == gpuSuccess);
// Regression test for 16-bit types: the deviate must be computed in float,
// otherwise log(0) in the rejection algorithm emits NaN/Inf.
int num_not_finite = 0;
for (int i = 0; i < out.size(); ++i) {
if (!(numext::isfinite)(out.data()[i])) ++num_not_finite;
}
VERIFY_IS_EQUAL(num_not_finite, 0);
}
// Fills n elements on the device through `gen` and returns them. MapOptions selects the executor path: Aligned
// takes the packet path, Unaligned the scalar one.
template <typename Scalar, int MapOptions, typename Generator>
static Tensor<Scalar, 1> device_random_fill(int n, const Generator& gen) {
const std::size_t bytes = n * sizeof(Scalar);
Eigen::GpuStreamDevice stream;
Eigen::GpuDevice gpu_device(&stream);
Scalar* d_out = static_cast<Scalar*>(gpu_device.allocate(bytes));
Eigen::TensorMap<Eigen::Tensor<Scalar, 1>, MapOptions> gpu_out(d_out, n);
gpu_out.device(gpu_device) = gpu_out.random(gen);
Tensor<Scalar, 1> out(n);
gpu_device.memcpyDeviceToHost(out.data(), d_out, bytes);
gpu_device.synchronize();
gpu_device.deallocate(d_out);
return out;
}
// The generator travels into the kernel by value, so every thread starts from the same state, yet each element has
// to come from its own stream. An unaligned map takes the scalar path and an aligned one the packet path; both have
// to produce a 64-bucket histogram within 5% of flat (6.4 standard deviations at n = 2^20) with almost every
// element different from element 0 (a correct 23-bit uniform repeats a given value about n / 2^23 times).
template <int MapOptions>
static void test_gpu_random_uniform_distribution() {
const int n = 1 << 20;
const int num_buckets = 64;
const int expected_per_bucket = n / num_buckets;
const int bucket_tolerance = expected_per_bucket / 20;
const int max_equal_to_first = n / 100;
const Tensor<float, 1> out =
device_random_fill<float, MapOptions>(n, Eigen::internal::UniformRandomGenerator<float>());
Eigen::Array<int, num_buckets, 1> histogram = Eigen::Array<int, num_buckets, 1>::Zero();
int num_out_of_range = 0;
int num_equal_to_first = 0;
for (int i = 0; i < n; ++i) {
const float x = out(i);
if (!(x >= 0.0f && x < 1.0f)) {
++num_out_of_range;
continue;
}
++histogram(static_cast<int>(x * num_buckets));
if (x == out(0)) ++num_equal_to_first;
}
VERIFY_IS_EQUAL(num_out_of_range, 0);
VERIFY(num_equal_to_first < max_equal_to_first);
VERIFY((histogram - expected_per_bucket).abs().maxCoeff() <= bucket_tolerance);
}
// Same two paths for the normal generator: sample mean, variance and the mass within one standard deviation,
// each allowed about ten standard errors at n = 2^20, plus the distinctness check.
template <int MapOptions>
static void test_gpu_random_normal_distribution() {
const int n = 1 << 20;
const double mean_tolerance = 0.01;
const double variance_tolerance = 0.02;
const double one_sigma_mass = 0.682689;
const double one_sigma_tolerance = 0.005;
const int max_equal_to_first = n / 100;
const Tensor<float, 1> out =
device_random_fill<float, MapOptions>(n, Eigen::internal::NormalRandomGenerator<float>());
double sum = 0.0;
double sum_of_squares = 0.0;
int num_within_one_sigma = 0;
int num_equal_to_first = 0;
for (int i = 0; i < n; ++i) {
const double x = out(i);
sum += x;
sum_of_squares += x * x;
if (std::abs(x) < 1.0) ++num_within_one_sigma;
if (out(i) == out(0)) ++num_equal_to_first;
}
const double mean = sum / n;
const double variance = sum_of_squares / n - mean * mean;
VERIFY(std::abs(mean) < mean_tolerance);
VERIFY(std::abs(variance - 1.0) < variance_tolerance);
VERIFY(std::abs(double(num_within_one_sigma) / n - one_sigma_mass) < one_sigma_tolerance);
VERIFY(num_equal_to_first < max_equal_to_first);
}
// A seeded uniform generator is a pure function of (seed, index): the device fill reproduces the host fill bit for
// bit whatever path either side takes (scalar or packet, at any packet width). The odd length leaves a scalar tail
// behind the packet loop.
template <typename Scalar, int MapOptions>
static void test_gpu_random_matches_host() {
const int n = (1 << 16) + 3;
const uint64_t seed = 0x9e3779b97f4a7c15ULL;
Eigen::internal::UniformRandomGenerator<Scalar> gen(seed);
Tensor<Scalar, 1> host(n);
host = host.random(gen);
const Tensor<Scalar, 1> out = device_random_fill<Scalar, MapOptions>(n, gen);
for (int i = 0; i < n; ++i) {
VERIFY_IS_EQUAL(out(i), host(i));
}
}
static void test_complex() {
Tensor<std::complex<float>, 1> vec(6);
vec.setRandom();
// Fixme: we should check that the generated numbers follow a uniform
// distribution instead.
for (int i = 1; i < 6; ++i) {
VERIFY_IS_NOT_EQUAL(vec(i), vec(i - 1));
}
}
EIGEN_DECLARE_TEST(tensor_random_gpu) {
CALL_SUBTEST(test_gpu_random_uniform());
CALL_SUBTEST(test_gpu_random_normal());
CALL_SUBTEST(test_gpu_random_uniform_range<Eigen::half>(256, 256));
CALL_SUBTEST(test_gpu_random_uniform_range<Eigen::bfloat16>(256, 256));
CALL_SUBTEST(test_gpu_random_normal_all_finite<Eigen::half>(1024, 2048));
CALL_SUBTEST(test_gpu_random_normal_all_finite<Eigen::bfloat16>(512, 512));
CALL_SUBTEST(test_gpu_random_uniform_distribution<Eigen::Unaligned>());
CALL_SUBTEST(test_gpu_random_uniform_distribution<Eigen::Aligned>());
CALL_SUBTEST(test_gpu_random_normal_distribution<Eigen::Unaligned>());
CALL_SUBTEST(test_gpu_random_normal_distribution<Eigen::Aligned>());
CALL_SUBTEST((test_gpu_random_matches_host<float, Eigen::Unaligned>()));
CALL_SUBTEST((test_gpu_random_matches_host<float, Eigen::Aligned>()));
CALL_SUBTEST((test_gpu_random_matches_host<double, Eigen::Unaligned>()));
CALL_SUBTEST((test_gpu_random_matches_host<double, Eigen::Aligned>()));
CALL_SUBTEST(test_complex());
}