blob: 5bdbdb5d4ddb6a40f1c620a60261678f8b85de34 [file]
#include <ATen/ATen.h>
#include <ATen/CPUApplyUtils.h>
#include <ATen/Config.h>
#include <ATen/Dispatch.h>
#include <ATen/ExpandUtils.h>
#include <ATen/NativeFunctions.h>
#include <c10/util/Exception.h>
#include <ATen/Utils.h>
#include <ATen/CPUGenerator.h>
#include <ATen/core/DistributionsHelper.h>
#include <ATen/native/Distributions.h>
#include <ATen/native/DispatchStub.h>
#include <ATen/native/UnaryOps.h>
#include <ATen/NamedTensorUtils.h>
#include <type_traits>
#include <functional>
#include <assert.h>
#include <cpuinfo.h>
#include <float.h>
#include <TH/THMath.h>
namespace {
/*
* This section is a counterpart to Distributions.cu
*
*/
// The function `sample_poisson`
// is adapted from Numpy's distributions.c implementation.
// It is MIT licensed, so here is the copyright:
/* Copyright 2005 Robert Kern (robert.kern@gmail.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
int64_t sample_poisson(double lambda, at::CPUGenerator* generator) {
at::uniform_real_distribution<double> standard_uniform(0.0, 1.0);
if (lambda >= 10) {
// transformed rejection method, (Hoermann, 1993)
int64_t k;
double U, V, a, b, invalpha, vr, us;
double slam = std::sqrt(lambda);
double loglam = std::log(lambda);
b = 0.931 + 2.53 * slam;
a = -0.059 + 0.02483 * b;
invalpha = 1.1239 + 1.1328 / (b - 3.4);
vr = 0.9277 - 3.6224 / (b - 2);
while (1) {
U = standard_uniform(generator) - 0.5;
V = standard_uniform(generator);
us = 0.5 - std::fabs(U);
k = (int64_t)std::floor((2 * a / us + b) * U + lambda + 0.43);
if ((us >= 0.07) && (V <= vr)) {
return k;
}
if ((k < 0) || ((us < 0.013) && (V > us))) {
continue;
}
if ((std::log(V) + std::log(invalpha) - std::log(a / (us * us) + b)) <=
(-lambda + k * loglam - std::lgamma((double)k + 1))) {
return k;
}
}
} else if (lambda == 0) {
return 0;
} else {
int64_t X;
double prod, U, enlam;
enlam = std::exp(-lambda);
X = 0;
prod = 1.0;
while (1) {
U = standard_uniform(generator);
prod *= U;
if (prod > enlam) {
X += 1;
} else {
return X;
}
}
}
}
} // namespace
namespace at {
namespace native {
Tensor bernoulli(const Tensor& self, Generator* gen) {
return at::empty_like(self).bernoulli_(self, gen);
}
Tensor bernoulli(const Tensor& self, double p, Generator* gen) {
return at::empty_like(self).bernoulli_(p, gen);
}
Tensor& bernoulli_out(Tensor& result, const Tensor& self, Generator* gen) {
// result.resize_as_(self) requires self to have same dtype as result, so we
// use resize_ instead.
// TODO: Fix resize_as_. See pytorch/pytorch#11665.
result.resize_(self.sizes()).bernoulli_(self, gen);
#ifdef BUILD_NAMEDTENSOR
namedinference::propagate_names(result, self);
#endif
return result;
}
Tensor& bernoulli_tensor_cpu_(Tensor& self, const Tensor& p_, Generator* gen) {
AT_DISPATCH_ALL_TYPES_AND(at::ScalarType::Bool, self.scalar_type(), "bernoulli_tensor_cpu_self_", [&] {
CPUGenerator* generator = get_generator_or_default<CPUGenerator>(gen, detail::getDefaultCPUGenerator());
// See Note [Acquire lock when using random generators]
std::lock_guard<std::mutex> lock(generator->mutex_);
using self_t = scalar_t;
if (p_.scalar_type() == kDouble) {
auto p = std::get<0>(expand_inplace(self, p_.to(kCPU)));
CPU_tensor_apply2<self_t, double>(
self, p, [generator](self_t& ret_val, double& p_val) {
at::bernoulli_distribution<double> bernoulli(p_val);
ret_val = static_cast<self_t>(bernoulli(generator));
});
} else {
AT_DISPATCH_FLOATING_TYPES(p_.scalar_type(), "bernoulli_tensor_cpu_p_", [&] {
auto p = std::get<0>(expand_inplace(self, p_.to(kCPU)));
using p_t = scalar_t;
CPU_tensor_apply2<self_t, p_t>(
self, p, [generator](self_t& ret_val, p_t& p_val) {
at::bernoulli_distribution<float> bernoulli(p_val);
ret_val = static_cast<self_t>(bernoulli(generator));
});
});
}
});
return self;
}
DEFINE_DISPATCH(bernoulli_mkl_stub);
Tensor& bernoulli_scalar_cpu_(Tensor& self, double p, Generator* gen) {
TORCH_CHECK(0 <= p && p <= 1, "bernoulli_ expects p to be in [0, 1], but got p=", p);
#if AT_MKL_ENABLED()
if (cpuinfo_initialize() && cpuinfo_vendor_intel == cpuinfo_get_processor(0)->core->vendor) {
bernoulli_mkl_stub(kCPU, self, p, gen);
return self;
}
#endif
AT_DISPATCH_ALL_TYPES_AND(at::ScalarType::Bool, self.scalar_type(), "bernoulli_scalar_cpu_", [&] {
CPUGenerator* generator = get_generator_or_default<CPUGenerator>(gen, detail::getDefaultCPUGenerator());
// See Note [Acquire lock when using random generators]
std::lock_guard<std::mutex> lock(generator->mutex_);
CPU_tensor_apply1<scalar_t>(
self, [generator, p](scalar_t& ret_val) {
at::bernoulli_distribution<double> bernoulli(p);
ret_val = static_cast<scalar_t>(bernoulli(generator));
});
});
return self;
}
Tensor _standard_gamma_grad_cpu(const Tensor& self, const Tensor& output) {
Tensor ret = at::empty(self.sizes(), self.options());
AT_DISPATCH_FLOATING_TYPES(self.scalar_type(), "_standard_gamma_grad_cpu", [&] {
CPU_tensor_apply3<scalar_t, scalar_t, scalar_t>(ret, self, output,
[](scalar_t& ret_val, const scalar_t& self_val, const scalar_t &output_val) {
ret_val = standard_gamma_grad_one<scalar_t, double>(self_val, output_val);
}
);
});
return ret;
}
Tensor _dirichlet_grad_cpu(const Tensor& x, const Tensor& alpha, const Tensor& total) {
Tensor ret = at::empty(x.sizes(), x.options());
AT_DISPATCH_FLOATING_TYPES(x.scalar_type(), "_dirichlet_grad_cpu", [&] {
CPU_tensor_apply4<scalar_t, scalar_t, scalar_t, scalar_t>(ret, x, alpha, total,
[](scalar_t& ret_val, const scalar_t& x_val, const scalar_t& alpha_val, const scalar_t& total_val) {
ret_val = dirichlet_grad_one<scalar_t, double>(x_val, alpha_val, total_val);
}
);
});
return ret;
}
/*
* This section is a counterpart to Distributions.cu
*/
Tensor _s_poisson_cpu(const Tensor& lambda, Generator *gen) {
Tensor ret = at::zeros(lambda.sizes(), lambda.options());
AT_DISPATCH_FLOATING_TYPES(ret.scalar_type(), "poisson_cpu", [&] {
CPUGenerator* generator = get_generator_or_default<CPUGenerator>(gen, detail::getDefaultCPUGenerator());
// See Note [Acquire lock when using random generators]
std::lock_guard<std::mutex> lock(generator->mutex_);
CPU_tensor_apply2<scalar_t, scalar_t>(ret, lambda,
[generator](scalar_t& ret_val, const scalar_t& lambda){
ret_val = static_cast<scalar_t>(sample_poisson(static_cast<double>(lambda), generator));
}
);
});
return ret;
}
Tensor _s_gamma_cpu(const Tensor& alpha, Generator *gen) {
Tensor ret = at::zeros(alpha.sizes(), alpha.options());
AT_DISPATCH_FLOATING_TYPES(ret.scalar_type(), "gamma_cpu", [&] {
CPUGenerator* generator = get_generator_or_default<CPUGenerator>(gen, detail::getDefaultCPUGenerator());
// See Note [Acquire lock when using random generators]
std::lock_guard<std::mutex> lock(generator->mutex_);
CPU_tensor_apply2<scalar_t, scalar_t>(ret, alpha,
[generator](scalar_t& ret_val, const scalar_t& alpha){
auto uniform_lambda = [generator] () {
at::uniform_real_distribution<double> standard_uniform(0.0, 1.0);
return standard_uniform(generator);
};
BaseSampler<double, decltype(uniform_lambda)> standard_uniform(uniform_lambda);
auto normal_lambda = [generator] () {
at::normal_distribution<double> normal(0.0, 1.0);
return normal(generator);
};
BaseSampler<double, decltype(normal_lambda)> standard_normal(normal_lambda);
auto sample = sample_gamma<scalar_t, double, decltype(uniform_lambda), decltype(normal_lambda)>(alpha, standard_uniform, standard_normal);
ret_val = std::max(std::numeric_limits<scalar_t>::min(), (scalar_t) sample);
}
);
});
return ret;
}
Tensor _s_dirichlet_cpu(const Tensor& alpha, Generator *gen) {
Tensor ret = at::zeros(alpha.sizes(), alpha.options());
AT_DISPATCH_FLOATING_TYPES(ret.scalar_type(), "dirichlet", [&] {
Tensor gamma = at::zeros(alpha.sizes(), alpha.options().dtype(ScalarType::Double));
CPUGenerator* generator = get_generator_or_default<CPUGenerator>(gen, detail::getDefaultCPUGenerator());
// See Note [Acquire lock when using random generators]
std::lock_guard<std::mutex> lock(generator->mutex_);
/* Generate gamma sample by casting alpha to double to prevent underflow. */
CPU_tensor_apply2<double, scalar_t>(gamma, alpha,
[generator](double& ret_val, const scalar_t& alpha){
auto uniform_lambda = [generator] () {
at::uniform_real_distribution<double> standard_uniform(0.0, 1.0);
return standard_uniform(generator);
};
BaseSampler<double, decltype(uniform_lambda)> standard_uniform(uniform_lambda);
auto normal_lambda = [generator] () {
at::normal_distribution<double> normal(0.0, 1.0);
return normal(generator);
};
BaseSampler<double, decltype(normal_lambda)> standard_normal(normal_lambda);
auto sample = sample_gamma<double, double, decltype(uniform_lambda), decltype(normal_lambda)>
(alpha, standard_uniform, standard_normal);
ret_val = std::max(std::numeric_limits<double>::min(), sample);
}
);
/* Normalize and cast back to scalar_t. */
Tensor gamma_sum = gamma.sum(-1, true).expand(alpha.sizes());
CPU_tensor_apply3<scalar_t, double , double>(ret, gamma, gamma_sum,
[](scalar_t& ret_val, const double& gamma, const double& gamma_sum){
ret_val = gamma / gamma_sum;
auto min_val = std::numeric_limits<scalar_t>::min();
auto max_val = std::nexttoward(static_cast<scalar_t>(1.0f), 0.0f);
ret_val = std::min(max_val, std::max(min_val, ret_val));
ret_val = static_cast<scalar_t>(ret_val);
}
);
});
return ret;
}
/* The largest consecutive integer representable in float32 (2^24) */
constexpr int64_t FLOAT32_MAX_CONSECUTIVE_INT = 1 << (FLT_MANT_DIG);
Tensor& multinomial_out(Tensor& result, const Tensor& self, int64_t n_sample, bool with_replacement, Generator *gen) {
TORCH_CHECK(result.device() == self.device(), "multinomial arguments must have the same device");
TORCH_CHECK(self.dim() > 0 && self.dim() <= 2, "prob_dist must be 1 or 2 dim");
TORCH_CHECK(at::isFloatingType(self.scalar_type()),
"multinomial only supports floating-point dtypes for input, got: ", self.scalar_type());
TORCH_CHECK(result.scalar_type() == ScalarType::Long,
"multinomial expects Long tensor out, got: ", result.scalar_type());
TORCH_CHECK(n_sample > 0, "cannot sample n_sample <= 0 samples");
int64_t n_categories = self.size(-1);
TORCH_CHECK(with_replacement || (n_sample <= n_categories),
"cannot sample n_sample > prob_dist.size(-1) samples without replacement");
// Since the index tensor is float, numCategories cannot exceed max
// float integer precision
TORCH_CHECK(n_categories <= FLOAT32_MAX_CONSECUTIVE_INT, "number of categories cannot exceed 2^24");
if (self.dim() > 1) {
int64_t n_dist = self.size(-2);
result.resize_({n_dist, n_sample});
} else {
result.resize_({n_sample});
}
multinomial_stub(result.type().device_type(), result, self, n_sample, with_replacement, gen);
return result;
}
Tensor multinomial(const Tensor& self, int64_t n_sample, bool with_replacement, Generator *gen) {
Tensor result = at::empty({0}, self.options().dtype(kLong));
native::multinomial_out(result, self, n_sample, with_replacement, gen);
return result;
}
DEFINE_DISPATCH(multinomial_stub);
}} // namespace at::native