blob: bec5ed377ec09b88b38ed505ca01be2482abc2cb [file]
#include <ATen/ATen.h>
#include <ATen/NamedTensorUtils.h>
#include <ATen/WrapDimUtils.h>
#include <ATen/core/op_registration/op_registration.h>
#include <ATen/NestedTensorImpl.h>
#include <c10/core/DispatchKey.h>
#include <c10/util/Exception.h>
namespace at {
namespace native {
inline std::vector<int64_t> construct_opt_sizes(const at::Tensor& sizes) {
// torch.tensor([]) is considered to have `dim() = 1` and `size(0) = 0`
// torch.nested_tensor([]) should also has `dim() = 1` and `size(0) = 0`
if (sizes.dim() == 0) {
return std::vector<int64_t>({0});
}
TORCH_INTERNAL_ASSERT_DEBUG_ONLY(sizes.dim() == 2);
std::vector<int64_t> result(1, sizes.sizes()[0]);
if (sizes.dim() > 0) {
size_t nested_dim = result.size();
int64_t* sizes_ptr = sizes.data_ptr<int64_t>();
result.resize(nested_dim + sizes.sizes()[1]);
int64_t sizes_size_0 = sizes.sizes()[0];
int64_t sizes_size_1 = sizes.sizes()[1];
for (const auto i : c10::irange(sizes_size_1)) {
result[nested_dim + i] = sizes_ptr[i];
}
for (const auto j : c10::irange(sizes_size_1)) {
for (const auto i : c10::irange(sizes_size_0)) {
if (result[nested_dim + j] &&
(result[nested_dim + j] != sizes_ptr[i * sizes.size(1) + j])) {
result[nested_dim + j] = -1;
}
}
}
}
return result;
}
// assume contiguous, we can construct stride from size
inline at::Tensor construct_nested_stride_tensor(const at::Tensor& sizes) {
// empty `sizes` means empty nested tensor, so return empty strides
if (sizes.dim() == 0) {
return sizes;
}
TORCH_INTERNAL_ASSERT_DEBUG_ONLY(sizes.dim() == 2);
int64_t orig_dim = sizes.size(1);
// `sizes`.sizes() = ntensors x 0 means empty but shaped `sizes`
// in this case strides is also empty but shaped
if (orig_dim == 0) {
return sizes;
}
at::Tensor strides = sizes.new_empty(sizes.sizes());
const int64_t* sizes_ptr = sizes.data_ptr<int64_t>();
int64_t* strides_ptr = strides.data_ptr<int64_t>();
for (int64_t i = 0; i < sizes.size(0); i++) {
strides_ptr[orig_dim - 1] = 1;
int64_t product = sizes_ptr[orig_dim - 1];
for (int64_t j = orig_dim - 2; j >= 0; j--) {
strides_ptr[j] = product;
product *= sizes_ptr[j];
}
sizes_ptr += orig_dim;
strides_ptr += orig_dim;
}
return strides;
}
// assume contiguous, we can construct offsets from size
inline std::vector<int64_t> construct_offsets(const at::Tensor& sizes) {
// empty `sizes` means empty nested tensor, so return empty strides
if (sizes.dim() == 0) {
return std::vector<int64_t>();
}
int64_t ntensors = sizes.size(0),
orig_dim = sizes.size(1);
std::vector<int64_t> offsets(ntensors);
// nesting scalars has easy offsets
if (orig_dim == 0) {
std::iota(offsets.begin(), offsets.end(), 0);
return offsets;
}
const int64_t* sizes_ptr = sizes.data_ptr<int64_t>();
offsets[0] = 0;
for (int64_t i = 0; i < ntensors - 1; i++) {
int64_t row_product = sizes_ptr[0];
for (int64_t j = 1; j < orig_dim; j++) {
row_product *= sizes_ptr[j];
}
offsets[i + 1] = offsets[i] + row_product;
sizes_ptr += orig_dim;
}
return offsets;
}
// [Note: Nested Tensor Autograd] The Nested Tensor key is a functionality
// key and therefore getAutogradRelatedKeySetFromBackend will return the
// wrong autograd key. For this specific impl we make sure to register the
// correct Autograd key which is AutogradNestedTensor
c10::DispatchKeySet generate_nested_key_set(at::Tensor buffer) {
c10::DispatchKeySet key_set =
c10::DispatchKeySet(DispatchKey::NestedTensor) | c10::DispatchKeySet{buffer.key_set().highestBackendKey()};
// Add AutogradNestedTensor specific keys
key_set = key_set | inplace_or_view_ks | autograd_nested;
return key_set;
}
NestedTensorImpl::NestedTensorImpl(
at::Tensor buffer,
at::Tensor nested_size_tensor,
at::Tensor nested_stride_tensor,
std::vector<int64_t> offsets)
: TensorImpl(
Storage(buffer.storage()),
generate_nested_key_set(buffer),
buffer.dtype()),
nested_size_tensor_(std::move(nested_size_tensor)),
nested_stride_tensor_(std::move(nested_stride_tensor)),
offsets_(std::move(offsets)),
opt_sizes_(construct_opt_sizes(nested_size_tensor_))
{
auto buffer_size_vec{buffer.unsafeGetTensorImpl()->sizes()};
TORCH_INTERNAL_ASSERT(
buffer_size_vec.size() == 1,
"NestedTensorImpl buffer is required to be 1 dimensional but got a buffer with ",
buffer.dim(),
" dimensions.");
buffer_size_ = buffer_size_vec[0];
TORCH_WARN_ONCE(
"The PyTorch API of nested tensors is in prototype stage and will change "
"in the near future.");
TORCH_INTERNAL_ASSERT(buffer.is_cuda() || buffer.is_cpu(), "NestedTensorImpl buffer must be either CUDA or CPU but got ", buffer.device());
TORCH_INTERNAL_ASSERT(nested_size_tensor_.is_contiguous());
int64_t size_dim = nested_size_tensor_.dim();
TORCH_INTERNAL_ASSERT(size_dim == 0 || size_dim == 2);
TORCH_INTERNAL_ASSERT(nested_stride_tensor_.is_contiguous());
TORCH_INTERNAL_ASSERT(nested_stride_tensor_.dim() == size_dim);
TORCH_INTERNAL_ASSERT(nested_stride_tensor_.sizes() == nested_size_tensor_.sizes());
TORCH_INTERNAL_ASSERT((size_dim == 0 && (int64_t)offsets_.empty())
|| (size_dim == 2 && nested_size_tensor_.size(0) == (int64_t)offsets_.size()));
refresh_dim();
set_sizes_strides_policy(c10::TensorImpl::SizesStridesPolicy::CustomSizes);
}
// assume contiguous, `nested_stride_tensor` and `offsets`
// can be infered from `nested_size_tensor`
NestedTensorImpl::NestedTensorImpl(
at::Tensor buffer,
at::Tensor nested_size_tensor)
: NestedTensorImpl(
buffer,
nested_size_tensor,
construct_nested_stride_tensor(nested_size_tensor),
construct_offsets(nested_size_tensor))
{}
void NestedTensorImpl::refresh_dim() {
const auto my_dim = nested_size_tensor_.dim() ? nested_size_tensor_.sizes()[1] + 1 : 1;
sizes_and_strides_.resize(my_dim);
TORCH_INTERNAL_ASSERT_DEBUG_ONLY(dim() == my_dim);
}
int64_t NestedTensorImpl::dim_custom() const {
return dim_default();
}
// Currently sizes and strides assume contiguous
int64_t NestedTensorImpl::numel_custom() const {
if (nested_size_tensor_.dim() == 0) {
return 0;
}
constexpr auto numel_max = std::min(
static_cast<uint64_t>(std::numeric_limits<int64_t>::max()),
static_cast<uint64_t>(std::numeric_limits<size_t>::max()));
const auto nt_dim = nested_size_tensor_.size(1);
const int64_t* sizes_ptr = nested_size_tensor_.data_ptr<int64_t>();
uint64_t num_elements{0};
for (const auto i : c10::irange(nested_size_tensor_.size(0))) {
uint64_t n = 1;
const auto start{sizes_ptr + i * nt_dim};
const auto end{start + nt_dim};
bool overflows = c10::safe_multiplies_u64(start, end, &n);
num_elements += n;
overflows |= (num_elements > numel_max);
TORCH_CHECK(!overflows, "numel: integer multiplication overflow");
}
return static_cast<int64_t>(num_elements);
}
c10::SymInt NestedTensorImpl::sym_numel_custom() const {
return NestedTensorImpl::numel_custom();
}
bool NestedTensorImpl::is_contiguous_custom(MemoryFormat) const {
TORCH_CHECK(false, "is_contiguous is disabled.");
}
IntArrayRef NestedTensorImpl::sizes_custom() const {
TORCH_CHECK(false, "Internal error: NestedTensorImpl doesn't support sizes. Please file an issue on https://github.com/pytorch/nestedtensor");
}
c10::SymIntArrayRef NestedTensorImpl::sym_sizes_custom() const {
TORCH_CHECK(false, "Internal error: NestedTensorImpl doesn't support sizes. Please file an issue on https://github.com/pytorch/nestedtensor");
}
c10::SymIntArrayRef NestedTensorImpl::sym_sizes() const {
return sym_sizes_custom();
}
IntArrayRef NestedTensorImpl::strides_custom() const {
TORCH_CHECK(false, "Internal error: NestedTensorImpl doesn't support strides. Please file an issue on https://github.com/pytorch/nestedtensor");
}
const char* NestedTensorImpl::tensorimpl_type_name() const {
return "NestedTensorImpl";
}
} // namespace native
} // namespace at