blob: 04d8e8cbf83131fb3ed587a4cb7b39175f2dad09 [file]
#define TORCH_ASSERT_ONLY_METHOD_OPERATORS
#include <ATen/core/Tensor.h>
#include <ATen/Dispatch.h>
#include <ATen/NamedTensorUtils.h>
#include <ATen/ScalarOps.h>
#include <ATen/TensorIndexing.h>
#include <ATen/TensorMeta.h>
#include <ATen/TensorOperators.h>
#include <ATen/WrapDimUtils.h>
#include <ATen/native/BinaryOps.h>
#include <ATen/native/ReduceOpsUtils.h>
#include <ATen/native/Resize.h>
#include <ATen/native/TensorCompare.h>
#include <ATen/native/TypeProperties.h>
#include <ATen/TensorSubclassLikeUtils.h>
#include <iostream>
#include <c10/util/Exception.h>
#ifndef AT_PER_OPERATOR_HEADERS
#include <ATen/Functions.h>
#include <ATen/NativeFunctions.h>
#else
#include <ATen/ops/_aminmax_native.h>
#include <ATen/ops/_assert_async_native.h>
#include <ATen/ops/_functional_assert_async_native.h>
#include <ATen/ops/_print_native.h>
#include <ATen/ops/_assert_scalar_native.h>
#include <ATen/ops/_functional_assert_scalar_native.h>
#include <ATen/ops/_make_per_tensor_quantized_tensor.h>
#include <ATen/ops/_unique.h>
#include <ATen/ops/allclose_native.h>
#include <ATen/ops/aminmax.h>
#include <ATen/ops/argsort_native.h>
#include <ATen/ops/cat.h>
#include <ATen/ops/clamp.h>
#include <ATen/ops/clamp_max.h>
#include <ATen/ops/clamp_max_native.h>
#include <ATen/ops/clamp_min.h>
#include <ATen/ops/clamp_min_native.h>
#include <ATen/ops/clamp_native.h>
#include <ATen/ops/clip_native.h>
#include <ATen/ops/empty.h>
#include <ATen/ops/empty_like.h>
#include <ATen/ops/eq.h>
#include <ATen/ops/fill.h>
#include <ATen/ops/imag.h>
#include <ATen/ops/index.h>
#include <ATen/ops/is_nonzero_native.h>
#include <ATen/ops/isclose.h>
#include <ATen/ops/isclose_native.h>
#include <ATen/ops/isfinite.h>
#include <ATen/ops/isfinite_native.h>
#include <ATen/ops/isin.h>
#include <ATen/ops/isin_native.h>
#include <ATen/ops/isinf.h>
#include <ATen/ops/isinf_native.h>
#include <ATen/ops/isnan_native.h>
#include <ATen/ops/isneginf_native.h>
#include <ATen/ops/isposinf_native.h>
#include <ATen/ops/isreal_native.h>
#include <ATen/ops/max.h>
#include <ATen/ops/max_native.h>
#include <ATen/ops/min.h>
#include <ATen/ops/min_native.h>
#include <ATen/ops/mode.h>
#include <ATen/ops/mode_native.h>
#include <ATen/ops/ne.h>
#include <ATen/ops/ones_like.h>
#include <ATen/ops/real.h>
#include <ATen/ops/result_type_native.h>
#include <ATen/ops/scalar_tensor.h>
#include <ATen/ops/where.h>
#include <ATen/ops/where_native.h>
#include <ATen/ops/zeros_like.h>
#include <iostream>
#include <utility>
#endif
namespace at::meta {
static inline void check_for_unsupported_isin_dtype(const ScalarType type) {
// Bail out for dtypes unsupported by the sorting algorithm to keep the interface consistent.
TORCH_CHECK(type != ScalarType::Bool &&
type != ScalarType::BFloat16 &&
type != ScalarType::ComplexFloat &&
type != ScalarType::ComplexDouble,
"Unsupported input type encountered for isin(): ", type);
}
TORCH_META_FUNC(clamp) (
const Tensor& self,
const OptionalScalarRef min,
const OptionalScalarRef max) {
if (!min && !max) {
TORCH_CHECK(false, "torch.clamp: At least one of 'min' or 'max' must not be None");
}
//Manual type promotion, since scalars have to participate in it
ScalarType result_type = self.scalar_type();
TORCH_CHECK(!isComplexType(result_type), "clamp is not supported for complex types");
//Floating is the highest supported
if (!isFloatingType(result_type)) {
at::native::ResultTypeState state = {};
state = at::native::update_result_type_state(self, state);
if (min) {
state = at::native::update_result_type_state(min.get(), state);
}
if (max) {
state = at::native::update_result_type_state(max.get(), state);
}
result_type = at::native::result_type(state);
//disallow type promoting inplace op
TORCH_CHECK((result_type == self.scalar_type()) ||
(!(maybe_get_output().defined()) || !(maybe_get_output().is_same(self))),
"result type ", result_type, " can't be cast to the desired output type ",
self.dtype());
}
//make sure scalars weren't complex
TORCH_CHECK(!isComplexType(result_type), "clamp is not supported for complex types");
build_unary_op(maybe_get_output(), self.to(result_type));
}
TORCH_META_FUNC2(clamp, Tensor) (
const Tensor& self,
const OptionalTensorRef min,
const OptionalTensorRef max) {
TORCH_CHECK(min || max, "torch.clamp: At least one of 'min' or 'max' must not be None");
TORCH_CHECK(!isComplexType(self.scalar_type()), "clamp is not supported for complex types");
#define CLAMP_CONFIG() \
TensorIteratorConfig() \
.set_check_mem_overlap(true) \
.add_output(maybe_get_output()) \
.add_const_input(self) \
.promote_inputs_to_common_dtype(true) \
.cast_common_dtype_to_outputs(true) \
.enforce_safe_casting_to_output(true)
if (min && max) {
build(CLAMP_CONFIG().add_const_input(*min).add_const_input(*max));
} else if (min) {
build(CLAMP_CONFIG().add_const_input(*min));
} else if (max) {
build(CLAMP_CONFIG().add_const_input(*max));
}
}
TORCH_META_FUNC(clamp_max) (
const Tensor& self,
const Scalar& max
) {
//we could wrap max into tensor and send to tensor overload,
//but relu is implemented via clamp_min, so for perf an uniformity reasons
//do a faster but correct thing
ScalarType result_type = self.scalar_type();
TORCH_CHECK(!isComplexType(result_type), "clamp is not supported for complex types");
TORCH_CHECK(!max.isComplex(), "clamp is not supported for complex types");
//Floating is the highest supported
if (!isFloatingType(result_type)) {
auto result_type = at::native::result_type(self, max);
TORCH_CHECK((result_type == self.scalar_type()) ||
(!(maybe_get_output().defined()) || !(maybe_get_output().is_same(self))),
"result type ", result_type, " can't be cast to the desired output type ",
self.dtype());
build_unary_op(maybe_get_output(), self.to(result_type));
} else {
build_borrowing_unary_op(maybe_get_output(), self);
}
}
TORCH_META_FUNC2(clamp_max, Tensor) (
const Tensor& self,
const Tensor& max
) {
build_borrowing_binary_op(maybe_get_output(), self, max);
}
TORCH_META_FUNC(clamp_min) (
const Tensor& self,
const Scalar& min
) {
ScalarType result_type = self.scalar_type();
TORCH_CHECK(!isComplexType(result_type), "clamp is not supported for complex types");
TORCH_CHECK(!min.isComplex(), "clamp is not supported for complex types");
//Floating is the highest supported
if (!isFloatingType(result_type)) {
auto result_type = at::native::result_type(self, min);
TORCH_CHECK((result_type == self.scalar_type() ||
!(maybe_get_output().defined()) || !(maybe_get_output().is_same(self))),
"result type ", result_type, " can't be cast to the desired output type ",
self.dtype());
build_unary_op(maybe_get_output(), self.to(result_type));
} else {
build_borrowing_unary_op(maybe_get_output(), self);
}
}
TORCH_META_FUNC2(clamp_min, Tensor) (
const Tensor& self,
const Tensor& min
) {
build_borrowing_binary_op(maybe_get_output(), self, min);
}
TORCH_META_FUNC2(isin, Tensor_Tensor) (
const Tensor& elements, const Tensor& test_elements, bool /*assume_unique*/, bool /*invert*/
) {
check_for_unsupported_isin_dtype(elements.scalar_type());
check_for_unsupported_isin_dtype(test_elements.scalar_type());
set_output_raw_strided(0, elements.sizes(), {}, TensorOptions(elements.device()).dtype(ScalarType::Bool));
}
TORCH_META_FUNC2(isin, Tensor_Scalar) (
const Tensor& elements, const c10::Scalar& test_elements, bool /*assume_unique*/, bool /*invert*/
) {
check_for_unsupported_isin_dtype(elements.scalar_type());
check_for_unsupported_isin_dtype(test_elements.type());
set_output_raw_strided(0, elements.sizes(), {}, TensorOptions(elements.device()).dtype(ScalarType::Bool));
}
TORCH_META_FUNC2(isin, Scalar_Tensor) (
const c10::Scalar& elements, const Tensor& test_elements, bool /*assume_unique*/, bool /*invert*/
) {
check_for_unsupported_isin_dtype(elements.type());
check_for_unsupported_isin_dtype(test_elements.scalar_type());
set_output_raw_strided(0, {0}, {}, TensorOptions(test_elements.device()).dtype(ScalarType::Bool));
}
TORCH_META_FUNC(isposinf) (const Tensor& self) {
TORCH_CHECK(!self.is_complex(), "isposinf does not support complex inputs.");
TORCH_CHECK(maybe_get_output().defined() ? maybe_get_output().dtype() == at::kBool : true,
"isposinf does not support non-boolean outputs.");
build_borrowing_unary_force_boolean_op(maybe_get_output(), self);
}
TORCH_META_FUNC(isneginf) (const Tensor& self) {
TORCH_CHECK(!self.is_complex(), "isneginf does not support complex inputs.");
TORCH_CHECK(maybe_get_output().defined() ? maybe_get_output().dtype() == at::kBool : true,
"isneginf does not support non-boolean outputs.");
build_borrowing_unary_force_boolean_op(maybe_get_output(), self);
}
static void check_unsupported_complex(const char* name, const Tensor& self) {
TORCH_CHECK(!self.is_complex(), name, ": does not support complex input");
}
TORCH_PRECOMPUTE_META_FUNC2(max, dim)
(const Tensor& self, int64_t dim, bool keepdim) {
dim = maybe_wrap_dim(dim, self.dim());
at::native::zero_numel_check_dims(self, dim, "max()");
check_unsupported_complex("max()", self);
resize_reduction_with_indices(*this, self, dim, keepdim, self.scalar_type());
return TORCH_PRECOMPUTE_STRUCT2(max, dim)()
.set_dim(maybe_wrap_dim(dim, self.dim()));
}
TORCH_PRECOMPUTE_META_FUNC2(min, dim)(const Tensor& self, int64_t dim, bool keepdim) {
dim = maybe_wrap_dim(dim, self.dim());
at::native::zero_numel_check_dims(self, dim, "min()");
check_unsupported_complex("min()", self);
resize_reduction_with_indices(*this, self, dim, keepdim, self.scalar_type());
return TORCH_PRECOMPUTE_STRUCT2(min, dim)()
.set_dim(maybe_wrap_dim(dim, self.dim()));
}
} // namespace at::meta
namespace at::native {
DEFINE_DISPATCH(where_kernel); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_DISPATCH(max_stub); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_DISPATCH(min_stub); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_DISPATCH(isposinf_stub); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_DISPATCH(isneginf_stub); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_DISPATCH(mode_stub); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_DISPATCH(clamp_stub); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_DISPATCH(clamp_scalar_stub); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_DISPATCH(clamp_min_scalar_stub); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_DISPATCH(clamp_max_scalar_stub); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_DISPATCH(isin_default_stub); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
bool allclose(const Tensor& self, const Tensor& other, double rtol, double atol, bool equal_nan) {
return at::isclose(self, other, rtol, atol, equal_nan).all().item<uint8_t>();
}
// Note [closeness]
// A number A is close to B when either:
//
// (1) A is equal to B, with NaNs comparing equal when equal_nan is true.
// (2) The error abs(A - B) is finite and less than the max error
// (atol + abs(rtol * B)).
//
// Note that this is consistent with NumPy's isclose but divergent from
// Python's isclose, which computes the max error symmetrically as
// max(rtol * max(abs(A), abs(B)), atol).
// TODO: use bitwise operator overloads once we add them
// TODO: revisit complex inputs and equal_nan=true after
// https://github.com/numpy/numpy/issues/15959 is resolved
Tensor isclose(const Tensor& self, const Tensor& other, double rtol, double atol, bool equal_nan) {
TORCH_CHECK(self.scalar_type() == other.scalar_type(), self.scalar_type(), " did not match ", other.scalar_type());
TORCH_CHECK(!(self.is_quantized() || other.is_quantized()),
"isclose is not supported for quantized inputs.");
// Checks that rtol and atol are non-negative
// Note: consistent with Python's isclose but divergent from NumPy's, which
// allows negative atol and rtol.
TORCH_CHECK(rtol >= 0, "rtol must be greater than or equal to zero, but got ", rtol);
TORCH_CHECK(atol >= 0, "atol must be greater than or equal to zero, but got ", atol);
// Computes equality closeness
Tensor close = self == other;
if (equal_nan && (self.is_floating_point() || self.is_complex())) {
// For CompositeCompliance, if `other` is a CCT and `self` is a regular Tensor,
// then we can't perform inplace op into `self` with `other`.
// NOTE: Inplacing into `close` is fine because it is generated from
// out-of-place with args `self` and `other`. So if either of them is
// a CCT then `close` will also be a `CCT`.
if (isTensorSubclassLike(other)) {
close.__ior__(self.isnan().bitwise_and(other.isnan()));
} else {
close.__ior__(self.isnan().__iand__(other.isnan()));
}
}
// In case of zero tolerances the closeness inequality degenerates to an equality check.
// In this case, the short-circuit prevents false positives as detailed in the paragraph below.
if (rtol == 0 && atol == 0){
return close;
}
// Note [closeness error computation]
// atol and rtol are provided as doubles, so the computation
// rtol * other will produce a float or complex tensor.
// When the difference (self - other) is compared to it then the
// tensor representing the difference will also be cast to float or complex.
// However, since (self - other) in uint8 is very likely to produce a
// negative value, this moves the cast forward so the difference is
// always computed in a float or complex type.
// If the values of the integer tensors cannot be exactly represented
// by the default scalar type then this may cause an incorrect result.
// Computes allowed and actual error
Tensor cast_self, cast_other;
cast_self = self.scalar_type() == at::kBool ? self.to(at::get_default_dtype()) : self;
if (c10::isIntegralType(self.scalar_type(), /*includeBool=*/true)) {
cast_other = other.to(at::get_default_dtype());
} else {
cast_other = other;
}
Tensor allowed_error = atol + (rtol * cast_other).abs();
Tensor actual_error = (cast_self - cast_other).abs();
// Computes finite closeness
close.__ior__(at::isfinite(actual_error).__iand__(actual_error <= allowed_error));
return close;
}
Tensor isnan(const Tensor& self) {
return self != self;
}
Tensor isreal(const Tensor& self) {
// Note: Integral and Floating tensor values are always real
if (c10::isIntegralType(self.scalar_type(), /*includeBool=*/true) ||
c10::isFloatingType(self.scalar_type())) {
return at::ones_like(self, at::kBool, at::MemoryFormat::Preserve);
}
return at::imag(self) == 0;
}
#if !defined(C10_MOBILE)
#define _AT_DISPATCH_INF_TYPES(TYPE, NAME, ...) \
AT_DISPATCH_FLOATING_TYPES_AND3( kHalf, kBFloat16, kFloat8_e5m2, \
TYPE, NAME, __VA_ARGS__)
#else
#define _AT_DISPATCH_INF_TYPES(TYPE, NAME, ...) \
AT_DISPATCH_FLOATING_TYPES_AND2(kHalf, kBFloat16, \
TYPE, NAME, __VA_ARGS__)
#endif
Tensor isinf(const Tensor &self) {
// Note: Integral tensor values are never infinite
if (c10::isIntegralType(self.scalar_type(), /*includeBool=*/true)) {
return at::zeros_like(self, at::kBool, at::MemoryFormat::Preserve);
}
// Note: a complex value is infinite when either part is infinite
if (self.is_complex()) {
return at::isinf(at::real(self)).__ior__
(at::isinf(at::imag(self)));
}
return _AT_DISPATCH_INF_TYPES(self.scalar_type(), "isinf", [&]() {
return self.abs() == std::numeric_limits<scalar_t>::infinity();
});
}
Tensor isfinite(const Tensor& self) {
// Note: Integral tensor values are always finite
if (c10::isIntegralType(self.scalar_type(), /*includeBool=*/true)) {
return at::ones_like(self, at::kBool, at::MemoryFormat::Preserve);
}
// Note: a complex value is finite iff both parts are finite
if (self.is_complex()) {
return at::isfinite(at::real(self)).__iand__(at::isfinite(at::imag(self)));
}
return _AT_DISPATCH_INF_TYPES(self.scalar_type(), "isfinite", [&]() {
return (self == self) * (self.abs() != std::numeric_limits<scalar_t>::infinity());
});
}
void _assert_async_cpu(const Tensor& self) {
TORCH_CHECK(native::is_nonzero(self), "Expected Tensor with single nonzero value, but got zero");
}
void _assert_async_msg_cpu(const Tensor& self, c10::string_view assert_msg) {
TORCH_CHECK(native::is_nonzero(self), assert_msg != "" ? assert_msg : "Assertion is failed");
}
void _assert_scalar(const Scalar& scalar, c10::string_view assert_msg) {
TORCH_SYM_CHECK(scalar.toSymBool(), assert_msg != "" ? assert_msg : "Assertion is failed");
}
Tensor _functional_assert_scalar(const Scalar& scalar, c10::string_view assert_msg, const Tensor& dep_token) {
_assert_scalar(scalar, assert_msg);
return dep_token.clone();
}
Tensor _functional_assert_async_msg_cpu(
const Tensor& self,
c10::string_view assert_msg,
const Tensor& dep_token) {
_assert_async_msg_cpu(self, assert_msg);
return dep_token.clone();
}
void _print(c10::string_view s) {
std::cout << s << "\n";
}
// Sorting-based algorithm for isin(); used when the number of test elements is large.
static void isin_sorting(
const Tensor& elements,
const Tensor& test_elements,
bool assume_unique,
bool invert,
const Tensor& out) {
// 1. Concatenate unique elements with unique test elements in 1D form. If
// assume_unique is true, skip calls to unique().
Tensor elements_flat, test_elements_flat, unique_order;
if (assume_unique) {
elements_flat = elements.ravel();
test_elements_flat = test_elements.ravel();
} else {
std::tie(elements_flat, unique_order) = at::_unique(
elements, /*sorted=*/ false, /*return_inverse=*/ true);
std::tie(test_elements_flat, std::ignore) = at::_unique(test_elements, /*sorted=*/ false);
}
// 2. Stable sort all elements, maintaining order indices to reverse the
// operation. Stable sort is necessary to keep elements before test
// elements within the sorted list.
Tensor all_elements = at::cat({std::move(elements_flat), std::move(test_elements_flat)});
auto [sorted_elements, sorted_order] = all_elements.sort(
/*stable=*/ true, /*dim=*/ 0, /*descending=*/ false);
// 3. Create a mask for locations of adjacent duplicate values within the
// sorted list. Duplicate values are in both elements and test elements.
Tensor duplicate_mask = at::empty_like(sorted_elements, TensorOptions(ScalarType::Bool));
Tensor sorted_except_first = sorted_elements.slice(0, 1, at::indexing::None);
Tensor sorted_except_last = sorted_elements.slice(0, 0, -1);
duplicate_mask.slice(0, 0, -1).copy_(
invert ? sorted_except_first.ne(sorted_except_last) : sorted_except_first.eq(sorted_except_last));
duplicate_mask.index_put_({-1}, invert);
// 4. Reorder the mask to match the pre-sorted element order.
Tensor mask = at::empty_like(duplicate_mask);
mask.index_copy_(0, sorted_order, duplicate_mask);
// 5. Index the mask to match the pre-unique element order. If
// assume_unique is true, just take the first N items of the mask,
// where N is the original number of elements.
if (assume_unique) {
out.copy_(mask.slice(0, 0, elements.numel()).view_as(out));
} else {
out.copy_(at::index(mask, {c10::optional<Tensor>(unique_order)}));
}
}
template<typename... Args>
Device out_device(Args&... inps){
for (const auto& i : {inps...}){
if (i.device() != at::kCPU) {
return i.device();
}
}
return at::kCPU;
}
Tensor& where_self_out(const Tensor& condition, const Tensor& self, const Tensor& other, Tensor& out) {
const auto result_type = at::native::result_type(self, other);
TORCH_CHECK(out.scalar_type() == result_type, "Expected out type to be ", result_type, " but got ", out.scalar_type());
auto self_ = self.scalar_type() != result_type ? self.to(result_type): self;
auto other_ = other.scalar_type() != result_type ? other.to(result_type): other;
auto condition_ = condition;
auto device = out_device(condition, self_, other_);
if (device != at::kCPU) { // allow CPU scalars on non-cpu device
if (condition.device() != device && condition.ndimension() == 0) {
condition_ = condition.to(device);
}
if (self_.device() != device && self_.ndimension() == 0) {
self_ = self_.to(device);
}
if (other_.device() != device && other_.ndimension() == 0) {
other_ = other_.to(device);
}
}
if (condition_.scalar_type() == ScalarType::Byte) {
TORCH_WARN_ONCE("where received a uint8 condition tensor. This behavior is deprecated and will be removed in a future version of PyTorch. Use a boolean condition instead.");
condition_ = condition_.to(kBool);
}
TORCH_CHECK(condition_.scalar_type() == kBool, "where expected condition to be a boolean tensor, but got a tensor with dtype ", condition_.scalar_type());
// if there's still a device mismatch, let tensoriterator error out with it
auto iter = at::TensorIteratorConfig()
.check_all_same_dtype(false)
.add_output(out)
.add_const_input(condition_)
.add_const_input(self_)
.add_const_input(other_)
.build();
where_kernel(iter.device_type(), iter);
return out;
}
Tensor where(const Tensor& condition, const Tensor& self, const Tensor& other) {
auto device = out_device(condition, self, other);
auto result_type = at::native::result_type(self, other);
Tensor ret = at::empty({0}, self.options().dtype(result_type).device(device));
at::native::where_self_out(condition, self, other, ret);
return ret;
}
Tensor where(const Tensor& condition, const Scalar& self, const Tensor& other) {
auto result_type = at::native::result_type(other, self);
auto self_converted = at::scalar_tensor(self, other.options().dtype(result_type));
auto other_converted = other.to(result_type);
return at::where(condition, self_converted, other_converted);
}
Tensor where(const Tensor& condition, const Tensor& self, const Scalar& other) {
auto result_type = at::native::result_type(self, other);
auto other_converted = at::scalar_tensor(other, self.options().dtype(result_type));
auto self_converted = self.to(result_type);
return at::where(condition, self_converted, other_converted);
}
Tensor where(const Tensor& condition, const Scalar& self, const Scalar& other) {
auto result_type = at::native::result_type(self, other);
const Tensor& other_t = at::scalar_tensor(other, condition.options().dtype(result_type));
const Tensor& self_t = at::scalar_tensor(self, condition.options().dtype(result_type));
return at::where(condition, self_t, other_t);
}
std::vector<Tensor> where(const Tensor& condition) {
return condition.nonzero_numpy();
}
std::tuple<Tensor, Tensor> mode(const Tensor& self, int64_t dim, bool keepdim) {
Tensor values = at::empty({0}, self.options());
Tensor indices = at::empty({0}, self.options().dtype(kLong));
return at::native::mode_out(self, dim, keepdim, values, indices);
}
std::tuple<Tensor &,Tensor &> mode_out(const Tensor& self, int64_t dim, bool keepdim,
Tensor& values, Tensor& indices) {
TORCH_CHECK(self.device().is_cpu() || self.is_cuda(),
"mode only supports CPU AND CUDA device type, got: ", self.device().type());
TORCH_CHECK(self.layout() == Layout::Strided,
"mode only supports strided layout, got: ", self.layout());
TORCH_CHECK(self.device() == values.device(),
"expected device '", self.device(), "' but got '",
values.device(), "' for values output");
TORCH_CHECK(self.device() == indices.device(),
"expected device '", self.device(), "' but got '",
indices.device(), "' for indices output");
TORCH_CHECK(self.scalar_type() == values.scalar_type(),
"expected scalar type '", self.scalar_type(), "' but got '",
values.scalar_type(), "' for values output");
TORCH_CHECK(indices.scalar_type() == ScalarType::Long,
"expected scalar type '", ScalarType::Long, "' but got '",
indices.scalar_type(), "' for indices output");
dim = maybe_wrap_dim(dim, self.dim());
if (self.numel() == 0) {
auto sizes = get_zero_numel_tensor_size(self, dim, keepdim, "mode()");
resize_output(values, sizes);
resize_output(indices, sizes);
return std::tie(values, indices);
}
else if (_dimreduce_return_trivial_no_ident(values, self, dim, keepdim, "mode")) {
AT_ASSERT(values.dim() == 0);
indices.resize_({}).fill_(0);
return std::forward_as_tuple(values, indices);
} else {
auto result = [&]() {
NoNamesGuard guard;
mode_stub(self.device().type(), values, indices, self, dim, keepdim);
return std::tuple<Tensor &,Tensor &>{values, indices};
}();
namedinference::propagate_names_for_reduction(std::get<0>(result), self, dim, keepdim);
namedinference::propagate_names_for_reduction(std::get<1>(result), self, dim, keepdim);
return result;
}
}
template <class Stub>
void minmax_out_impl(
const Tensor& self,
int64_t dim,
bool keepdim,
const Tensor& values,
const Tensor& indices,
Stub& stub) {
NoNamesGuard guard;
if (self.numel() > 0) {
if (self.numel() == 1 && self.dim() == 0) {
values.fill_(self);
indices.fill_(0);
} else {
stub(self.device().type(), values, indices, self, dim, keepdim);
}
}
}
TORCH_IMPL_FUNC(max_out)
(const Tensor& self,
int64_t dim,
bool keepdim,
const Tensor& values,
const Tensor& indices) {
minmax_out_impl(self, dim, keepdim, values, indices, max_stub);
}
TORCH_IMPL_FUNC(min_out)
(const Tensor& self,
int64_t dim,
bool keepdim,
const Tensor& values,
const Tensor& indices) {
minmax_out_impl(self, dim, keepdim, values, indices, min_stub);
}
std::tuple<Tensor, Tensor> qmax(const Tensor& self, int64_t dim, bool keepdim) {
TORCH_CHECK(self.qscheme() == at::kPerTensorAffine, "Max operator for quantized tensors only works for per tensor quantized tensors. "
"Please open an issue on https://github.com/pytorch/pytorch/issues if you need per channel quantized tensor support.");
Tensor max_indices = at::empty({0}, self.options().dtype(kLong));
Tensor max = at::empty({0}, self.options().dtype(toUnderlying(self.scalar_type())));
at::max_outf(self.int_repr(), dim, keepdim, max, max_indices);
// TODO: qscheme
return std::tuple<Tensor, Tensor>(
at::_make_per_tensor_quantized_tensor(max, self.q_scale(), self.q_zero_point()), max_indices);
}
std::tuple<Tensor, Tensor> qmin(const Tensor& self, int64_t dim, bool keepdim) {
TORCH_CHECK(self.qscheme() == at::kPerTensorAffine, "Min operator for quantized tensors only works for per tensor quantized tensors. "
"Please open an issue on https://github.com/pytorch/pytorch/issues if you need per channel quantized tensor support.");
Tensor min_indices = at::empty({0}, self.options().dtype(kLong));
Tensor min = at::empty({0}, self.options().dtype(toUnderlying(self.scalar_type())));
at::min_outf(self.int_repr(), dim, keepdim, min, min_indices);
return std::tuple<Tensor, Tensor>(
at::_make_per_tensor_quantized_tensor(min, self.q_scale(), self.q_zero_point()), min_indices);
}
// DEPRECATED: Use at::aminmax instead
std::tuple<Tensor, Tensor> _aminmax(const Tensor& self, int64_t dim, bool keepdim) {
TORCH_WARN_ONCE("_aminmax is deprecated as of PyTorch 1.11 and will be removed in a future release. Use aminmax instead."
" This warning will only appear once per process.");
return at::aminmax(self, dim, keepdim);
}
TORCH_IMPL_FUNC(clamp_out)
(
const Tensor& /*self*/,
const OptionalScalarRef min,
const OptionalScalarRef max,
const Tensor& result) {
using at::native::detail::ClampLimits;
if (min && max) {
if (min.get().toDouble() != min.get().toDouble() ||
max.get().toDouble() != max.get().toDouble()) {
at::fill_(const_cast<Tensor&>(result), std::numeric_limits<double>::quiet_NaN());
} else {
clamp_scalar_stub(device_type(), *this, min.get(), max.get());
}
} else if (max) {
clamp_max_scalar_stub(device_type(), *this, max.get());
} else if (min) {
clamp_min_scalar_stub(device_type(), *this, min.get());
}
}
TORCH_IMPL_FUNC(clamp_Tensor_out)
(const Tensor& self, const OptionalTensorRef min,
const OptionalTensorRef max, const Tensor&) {
if (min && max) {
clamp_stub(device_type(), *this);
} else if (min) {
maximum_stub(device_type(), *this);
} else if (max) {
minimum_stub(device_type(), *this);
}
}
TORCH_IMPL_FUNC(clamp_max_out)
(const Tensor& self, const Scalar& max, const Tensor& result) {
if (max.toDouble() != max.toDouble()) {
//TODO this is not great, building TI again is expensive, but I can't use
//fill_stub because fill is not structured
//this is a corner case anyway
at::fill_(const_cast<Tensor&>(result), wrapped_scalar_tensor(max));
} else {
clamp_max_scalar_stub(device_type(), *this, max);
}
}
TORCH_IMPL_FUNC(clamp_max_Tensor_out)
(const Tensor& self, const Tensor& max, const Tensor& result) {
minimum_stub(device_type(), *this);
}
TORCH_IMPL_FUNC(clamp_min_out)
(const Tensor& self, const Scalar& min, const Tensor& result) {
if (min.toDouble() != min.toDouble()) {
at::fill_(const_cast<Tensor&>(result), min);
} else {
clamp_min_scalar_stub(device_type(), *this, min);
}
}
TORCH_IMPL_FUNC(clamp_min_Tensor_out)
(const Tensor& self, const Tensor& min, const Tensor& result) {
maximum_stub(device_type(), *this);
}
// Implements the "clip" alias for clamp
Tensor& clip_out(const Tensor& self, const c10::optional<Scalar>& min, const c10::optional<Scalar>& max, Tensor& result) {
return at::clamp_outf(self, min, max, result);
}
Tensor& clip_out(const Tensor& self, const c10::optional<Tensor>& min, const c10::optional<Tensor>& max, Tensor& result) {
return at::clamp_outf(self, min, max, result);
}
Tensor clip(const Tensor& self, const c10::optional<Scalar>& min, const c10::optional<Scalar>& max) {
return at::clamp(self, min, max);
}
Tensor clip(const Tensor& self, const c10::optional<Tensor>& min, const c10::optional<Tensor>& max) {
return at::clamp(self, min, max);
}
Tensor& clip_(Tensor& self, const c10::optional<Scalar>& min, const c10::optional<Scalar>& max) {
return at::clamp_(self, min, max);
}
Tensor& clip_(Tensor& self, const c10::optional<Tensor>& min, const c10::optional<Tensor>& max) {
return at::clamp_(self, min, max);
}
// Named tensor overloads
std::tuple<Tensor, Tensor> min(const Tensor& self, Dimname dim, bool keepdim) {
return at::min(self, dimname_to_position(self, dim), keepdim);
}
std::tuple<Tensor &,Tensor &> min_out(const Tensor& self, Dimname dim, bool keepdim, Tensor& min, Tensor& min_indices) {
return at::min_out(min, min_indices, self, dimname_to_position(self, dim), keepdim);
}
std::tuple<Tensor, Tensor> max(const Tensor& self, Dimname dim, bool keepdim) {
return at::max(self, dimname_to_position(self, dim), keepdim);
}
std::tuple<Tensor&, Tensor&> max_out(const Tensor& self, Dimname dim, bool keepdim, Tensor& max, Tensor& max_indices) {
return at::max_out(max, max_indices, self, dimname_to_position(self, dim), keepdim);
}
static Tensor argmax(const Tensor& /*self*/, Dimname /*dim*/, bool /*keepdim*/) {
reportNYIDimnameOverload("argmax");
}
static Tensor argmin(const Tensor& /*self*/, Dimname /*dim*/, bool /*keepdim*/) {
reportNYIDimnameOverload("argmin");
}
Tensor argsort(const Tensor& /*self*/, Dimname /*dim*/, bool /*keepdim*/) {
reportNYIDimnameOverload("argsort");
}
std::tuple<Tensor, Tensor> mode(const Tensor& self, Dimname dim, bool keepdim) {
return at::mode(self, dimname_to_position(self, dim), keepdim);
}
std::tuple<Tensor &,Tensor &> mode_out(const Tensor& self, Dimname dim, bool keepdim, Tensor& values, Tensor& indices) {
return at::mode_out(values, indices, self, dimname_to_position(self, dim), keepdim);
}
TORCH_IMPL_FUNC(isin_Tensor_Tensor_out) (
const Tensor& elements, const Tensor& test_elements, bool assume_unique, bool invert, const Tensor& out
) {
if (elements.numel() == 0) {
return;
}
// Heuristic taken from numpy's implementation.
// See https://github.com/numpy/numpy/blob/fb215c76967739268de71aa4bda55dd1b062bc2e/numpy/lib/arraysetops.py#L575
if (test_elements.numel() < static_cast<int64_t>(
10.0f * std::pow(static_cast<double>(elements.numel()), 0.145))) {
out.fill_(invert);
isin_default_stub(elements.device().type(), elements, test_elements, invert, out);
} else {
isin_sorting(elements, test_elements, assume_unique, invert, out);
}
}
TORCH_IMPL_FUNC(isin_Tensor_Scalar_out) (
const Tensor& elements, const c10::Scalar& test_elements, bool assume_unique, bool invert, const Tensor& out
) {
// redispatch to eq / ne
if (invert) {
at::ne_out(const_cast<Tensor&>(out), elements, test_elements);
} else {
at::eq_out(const_cast<Tensor&>(out), elements, test_elements);
}
}
TORCH_IMPL_FUNC(isin_Scalar_Tensor_out) (
const c10::Scalar& elements, const Tensor& test_elements, bool assume_unique, bool invert, const Tensor& out
) {
// redispatch
at::isin_out(const_cast<Tensor&>(out), wrapped_scalar_tensor(elements, test_elements.device()),
test_elements, assume_unique, invert);
}
TORCH_IMPL_FUNC(isposinf_out) (const Tensor& self, const Tensor& result) {
if (c10::isIntegralType(self.scalar_type(), /*includeBool=*/true)) {
result.fill_(false);
} else {
isposinf_stub(device_type(), *this);
}
}
TORCH_IMPL_FUNC(isneginf_out) (const Tensor& self, const Tensor& result) {
if (c10::isIntegralType(self.scalar_type(), /*includeBool=*/true)) {
result.fill_(false);
} else {
isneginf_stub(device_type(), *this);
}
}
} // namespace at::native