// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #pragma once #include #include #include #include "paddle/fluid/eager/api/all.h" #include "paddle/fluid/eager/api/generated/eager_generated/forwards/dygraph_functions.h" #include "paddle/fluid/eager/utils.h" #include "paddle/fluid/framework/convert_utils.h" #include "paddle/fluid/framework/scope_guard.h" #include "paddle/fluid/imperative/amp_utils.h" #include "paddle/fluid/pybind/tensor_py.h" #include "paddle/phi/common/data_type.h" #include "paddle/phi/core/compat/convert_utils.h" #include "paddle/phi/core/dense_tensor.h" #include "paddle/phi/kernels/funcs/common_infer_shape_functions.h" #include "paddle/phi/kernels/funcs/slice_utils.h" #include "paddle/phi/kernels/funcs/strided_slice.h" #include "paddle/utils/pybind.h" #include "pybind11/numpy.h" #include "pybind11/pybind11.h" #include "pybind11/stl.h" using egr::ConvertAllInputsToDistTensor; using egr::InputsContainDistTensor; namespace py = pybind11; namespace paddle { namespace pybind { static inline common::DDim infer_size_symdimvector(common::DDim a, common::DDim b) { // Use ptrdiff_t to ensure signed comparison. auto dimsA = a.size(); auto dimsB = b.size(); auto ndim = dimsA > dimsB ? dimsA : dimsB; common::DDim expandedSizes = common::make_ddim(std::vector(ndim, 0)); for (int64_t i = ndim - 1; i >= 0; --i) { int64_t offset = ndim - 1 - i; int64_t dimA = dimsA - 1 - offset; int64_t dimB = dimsB - 1 - offset; auto sizeA = (dimA >= 0) ? a[dimA] : 1; auto sizeB = (dimB >= 0) ? b[dimB] : 1; PADDLE_ENFORCE_EQ(sizeA == sizeB || sizeA == 1 || sizeB == 1, true, common::errors::Fatal("The size of tensor a (%d) must " "match the size of tensor b (%d) " "at non-singleton dimension %d", sizeA, sizeB, i)); // 1s map to the other size (even 0). expandedSizes[i] = sizeA == 1 ? sizeB : sizeA; } return expandedSizes; } static inline Tensor expand_inplace(Tensor tensor, Tensor to_expand) { if (tensor.dims() == to_expand.dims()) { return to_expand; } else if (tensor.dims()[0] == to_expand.dims()[0]) { return expand_ad_func(to_expand, common::vectorize(tensor.dims())); } else { to_expand = squeeze_ad_func(to_expand, {-1}); return expand_ad_func(to_expand, common::vectorize(tensor.dims())); } } static inline std::vector expandTensors(std::vector indices) { // expands bool to int tensors; std::vector result; for (auto& index : indices) { if (index.dtype() == DataType::BOOL) { auto bool_2_idx = nonzero_ad_func(index); for (int j = 0; j < index.dims().size(); j++) { Tensor sliced_tensor = slice_ad_func(bool_2_idx, {1}, {j}, {j + 1}, {1}, {1}); result.emplace_back(sliced_tensor); } } else { result.emplace_back(index); } } return result; } static inline std::vector expand_outplace( std::vector to_expand) { // expands a list of Tensors; ignores undefined (null) tensors bool first = true; common::DDim sizes; for (size_t i = 0; i < to_expand.size(); i++) { if (!to_expand[i].defined()) { continue; } else if (first) { sizes = to_expand[i].dims(); first = false; } else { sizes = infer_size_symdimvector(sizes, to_expand[i].dims()); } } std::vector result(to_expand.size()); for (size_t i = 0; i < to_expand.size(); i++) { if (!to_expand[i].defined()) { continue; } else if (to_expand[i].dims() == sizes) { result[i] = to_expand[i]; } else { result[i] = expand_ad_func(to_expand[i], common::vectorize(sizes)); } } return result; } struct AdvancedIndex { AdvancedIndex(Tensor src, std::vector indices); Tensor src; std::vector indices; std::vector indexed_sizes; std::vector indexed_strides; std::vector src_sizes; std::vector src_strides; int64_t dims_before; int64_t dims_after; bool bool_case; }; inline static void restride_src(std::vector* shape, std::vector* strides, int64_t dims_before, int64_t dims_indexed, std::vector replacement_shape) { int64_t end = dims_before + dims_indexed; shape->erase(shape->begin() + dims_before, shape->begin() + end); strides->erase(strides->begin() + dims_before, strides->begin() + end); shape->insert(shape->begin() + dims_before, replacement_shape.begin(), replacement_shape.end()); strides->insert(strides->begin() + dims_before, replacement_shape.size(), 0); } // move to cuda kernel inline static std::vector reshape_indexer(Tensor* index, int64_t dims_before, int64_t dims_after) { auto orig_shape = common::vectorize(index->dims()); auto shape = std::vector{}; shape.insert(shape.end(), dims_before, 1); shape.insert(shape.end(), orig_shape.begin(), orig_shape.end()); shape.insert(shape.end(), dims_after, 1); return shape; } inline AdvancedIndex::AdvancedIndex(Tensor src, std::vector indices_list) { uint32_t element_size_bytes = phi::SizeOf(src.dtype()); int64_t dims_before = 0, dims_after = 0, dims_indexed = 0; std::vector shape_vec = common::vectorize(src.dims()); std::vector stride_vec = common::vectorize(src.strides()); std::vector replacement_shape; std::vector idx_shape_vec = {}; std::vector idx_stride_vec = {}; for (size_t dim = 0; dim < indices_list.size(); dim++) { if (!indices_list[dim].defined()) { if (dims_indexed == 0) { dims_before++; } else { dims_after++; } } else { dims_indexed++; replacement_shape = common::vectorize(indices_list[dim].dims()); idx_shape_vec.push_back(shape_vec[dim]); idx_stride_vec.push_back(stride_vec[dim] * element_size_bytes); } } this->dims_before = dims_before; this->dims_after = dims_after; restride_src( &shape_vec, &stride_vec, dims_before, dims_indexed, replacement_shape); this->src_sizes = shape_vec; this->src_strides = stride_vec; this->indexed_sizes = idx_shape_vec; this->indexed_strides = idx_stride_vec; // use dims_before and dims_after / move to cuda kernel for (auto& index : indices_list) { if (index.defined()) { std::vector vec_size = reshape_indexer(&index, dims_before, dims_after); this->indices.push_back(index); this->indexed_sizes.push_back(-1); this->indexed_sizes.insert( this->indexed_sizes.end(), vec_size.begin(), vec_size.end()); } } } template inline T GetDenseTensorValue(const phi::DenseTensor* x) { T value = static_cast(0); if (!(x->place().GetType() == phi::AllocationType::CPU)) { DenseTensor cpu_x; framework::TensorCopy(*x, CPUPlace(), &cpu_x); #if defined(PADDLE_WITH_CUSTOM_DEVICE) phi::DeviceContextPool& pool = phi::DeviceContextPool::Instance(); const phi::DeviceContext* dev_ctx = pool.Get(x->place()); dev_ctx->Wait(); #endif value = cpu_x.data()[0]; } else { value = x->data()[0]; } return value; } static Py_ssize_t GetSliceIndexFromPyObject(PyObject* obj); // Slice related methods static bool PyCheckInteger(PyObject* obj) { return PyLong_Check(obj) && !PyBool_Check(obj); } static bool IsNumpyType(PyObject* obj) { // It is not a good way to judge the type of obj by its type'name. Maybe using // `PyArray_IsScalar` will be better. However, this interface cannot be used // by including pybind11, and it needs to compile with numpy. auto type_name = std::string(Py_TYPE(obj)->tp_name); return type_name == "numpy.int64" || type_name == "numpy.longlong" || type_name == "numpy.int32" || type_name == "numpy.int16"; } static bool IsNumpyArray(PyObject* obj) { auto type_name = std::string(Py_TYPE(obj)->tp_name); return type_name == "numpy.ndarray"; } static Py_ssize_t GetSliceIndexFromTensor(const phi::DenseTensor& tensor) { if (tensor.numel() == 1) { if (framework::TransToProtoVarType(tensor.type()) == framework::proto::VarType::INT32) { return static_cast(GetDenseTensorValue(&tensor)); } else if (framework::TransToProtoVarType(tensor.type()) == framework::proto::VarType::INT64) { return static_cast(GetDenseTensorValue(&tensor)); } else { PADDLE_THROW(common::errors::InvalidArgument( "Currently, the type of tensor in slice indices only allows " "int32 and int64, please check the type of index tensor.")); } } else { PADDLE_THROW(common::errors::InvalidArgument( "Currently, tensor in slice indices only allows 1 element, " "but received %d.", tensor.numel())); } } // NOTE(zhiqiu): Revised version of PySlice_GetIndices. From: // https://github.com/python/cpython/blob/8d21aa21f2cbc6d50aab3f420bb23be1d081dac4/Objects/sliceobject.c#L103 // Original PySlice_GetIndices return wrong result when // slice_item contains long int, such as arr[:180L]. // NOT sure why this happens !!! // Besides, PySlice_GetIndices cannot raise error when float in slice item. // So, I make a revised version of PySlice_GetIndices, named to // _PySlice_GetIndices. Try to use _PySlice_Unpack which is more robust than // PySlice_GetIndices in the future. static int _PySlice_GetIndices(PySliceObject* r, Py_ssize_t length, Py_ssize_t* start, Py_ssize_t* stop, Py_ssize_t* step) { /* XXX support long ints */ if (r->step == Py_None) { *step = 1; } else { if (PyCheckInteger(r->step) || IsNumpyType(r->step)) { *step = PyLong_AsLong(r->step); } else if (PyCheckTensor(r->step)) { *step = GetSliceIndexFromPyObject(r->step); } else { PADDLE_THROW(common::errors::InvalidArgument( "Currently, slice indices only allows None, integers, " "tensor(int) and numpy(int) in slice item, but received %s.", std::string(Py_TYPE(r->step)->tp_name))); } } if (r->start == Py_None) { *start = *step < 0 ? length - 1 : 0; } else { if (PyCheckInteger(r->start) || IsNumpyType(r->start)) { *start = PyLong_AsLong(r->start); } else if (PyCheckTensor(r->start)) { *start = GetSliceIndexFromPyObject(r->start); } else { PADDLE_THROW(common::errors::InvalidArgument( "Currently, slice indices only allows None, integers, " "tensor(int) and numpy(int) in slice item, but received %s.", std::string(Py_TYPE(r->start)->tp_name))); } } if (r->stop == Py_None) { *stop = *step < 0 ? -length - 1 : length; } else { if (PyCheckInteger(r->stop) || IsNumpyType(r->stop)) { *stop = PyLong_AsLong(r->stop); } else if (PyCheckTensor(r->stop)) { *stop = GetSliceIndexFromPyObject(r->stop); } else { PADDLE_THROW(common::errors::InvalidArgument( "Currently, slice indices only allows None, integers, " "tensor(int) and numpy(int) in slice item, but received %s.", std::string(Py_TYPE(r->stop)->tp_name))); } } // normalize start and stop bool dummy_zero_dim_out = false; phi::funcs::normalize_interval( *start, *stop, *step, length, start, stop, &dummy_zero_dim_out); // return value below seems to be useless... if (*stop > length) return -1; if (*start >= length) return -1; if (*step == 0) return -1; return 0; } static void ParseIndex(const Tensor& tensor, PyObject* index, std::vector* slice_axes, std::vector* slice_starts, std::vector* slice_ends, std::vector* slice_strides, std::vector* decrease_axis, std::vector* none_axes, std::vector* infer_flags, std::vector* advanced_index_dim, std::vector* advanced_index, bool* has_advanced_index, bool* use_strided_slice) { // for case 0-size tensor in slice PADDLE_ENFORCE_EQ( tensor.defined(), true, common::errors::InvalidArgument("tensor has not been defined")); const auto& shape = tensor.dims(); const int rank = shape.size(); const int size = PyTuple_GET_SIZE(index); // Check Ellipsis is valid int specified_dims = 0; int ell_count = 0; for (int dim = 0; dim < size; ++dim) { PyObject* slice_item = PyTuple_GetItem(index, dim); if (slice_item == Py_Ellipsis) { ell_count++; } else if (slice_item != Py_None && !PyBool_Check(slice_item)) { specified_dims++; } } PADDLE_ENFORCE_LE(ell_count, 1, common::errors::InvalidArgument( "An index can only have a single ellipsis ('...')")); // deal with indexing_item int none_count = 0; for (int64_t i = 0, current_dim = 0, estimated_dim = 0; i < size; ++i) { PyObject* slice_item = PyTuple_GetItem(index, i); infer_flags->push_back(1); int64_t dim_len = shape[current_dim]; if (PyCheckInteger(slice_item) || IsNumpyType(slice_item)) { // integer, PyLong_AsLong supports both int and long int64_t start = static_cast(PyLong_AsLong(slice_item)); auto s_t = start; start = start < 0 ? start + dim_len : start; PADDLE_ENFORCE( 0 <= start && start < dim_len, common::errors::OutOfRange("The starting index %d of slice is out " "of bounds in tensor %d-th axis, it " "should be in the range of [%d, %d).", s_t, current_dim, -dim_len, dim_len)); slice_axes->push_back(current_dim); slice_starts->push_back(start); slice_ends->push_back(start + 1); slice_strides->push_back(1); decrease_axis->push_back(current_dim); current_dim++; } else if (PySlice_Check(slice_item)) { // slice item Py_ssize_t start, end, step; PySliceObject* p = reinterpret_cast(slice_item); _PySlice_GetIndices(p, dim_len, &start, &end, &step); // :: or : or 0:dim_len:1 if (start == 0 && end == dim_len && step == 1) { current_dim++; estimated_dim++; continue; } slice_axes->push_back(current_dim); slice_starts->push_back(start); slice_ends->push_back(end); slice_strides->push_back(step); estimated_dim++; current_dim++; if (step != 1) { *use_strided_slice = true; } } else if (slice_item == Py_Ellipsis) { current_dim += rank - specified_dims; estimated_dim += rank - specified_dims; } else if (slice_item == Py_None) { none_axes->push_back(current_dim + none_count); none_count++; } else if (PyBool_Check(slice_item)) { *has_advanced_index = true; none_axes->push_back(current_dim + none_count); none_count++; bool index_ele = (slice_item == Py_True); auto slice_tensor = full_ad_func({1}, index_ele, DataType::BOOL, tensor.place()); advanced_index->push_back(std::move(slice_tensor)); (*advanced_index_dim)[estimated_dim] = estimated_dim; estimated_dim++; } else if (PyCheckTensor(slice_item) || IsNumpyArray(slice_item)) { Tensor slice_tensor; if (IsNumpyArray(slice_item)) { Tensor index_tensor_tmp( std::make_shared(), egr::Controller::Instance().GenerateUniqueName()); py::object index_obj_tmp = py::reinterpret_borrow(slice_item); py::object index_tmp = index_obj_tmp; SetTensorFromPyArray( static_cast(index_tensor_tmp.impl().get()), index_tmp, tensor.place(), false); slice_tensor = index_tensor_tmp; } else { slice_tensor = CastPyArg2Tensor(slice_item, 0); } if (slice_tensor.shape().size() == 0) { if (slice_tensor.dtype() != DataType::BOOL) { // 0-D int tensor is same with scalar PADDLE_ENFORCE_EQ( slice_tensor.is_dense_tensor(), true, common::errors::InvalidArgument( "Now, Tensor in indexing only support DenseTensor.")); Py_ssize_t s_t = GetSliceIndexFromTensor( (*static_cast(slice_tensor.impl().get()))); auto start = s_t < 0 ? s_t + dim_len : s_t; PADDLE_ENFORCE(0 <= start && start < dim_len, common::errors::OutOfRange( "The starting index %d of slice is out " "of bounds in tensor %d-th axis, it " "should be in the range of [%d, %d).", s_t, current_dim, -dim_len, dim_len)); slice_axes->push_back(current_dim); slice_starts->push_back(start); slice_ends->push_back(start + 1); slice_strides->push_back(1); decrease_axis->push_back(current_dim); current_dim++; } else { // 0-D bool Tensor, same as single PY-bool. *has_advanced_index = true; none_axes->push_back(current_dim + none_count); none_count++; slice_tensor = unsqueeze_ad_func(slice_tensor, {-1}); advanced_index->push_back(std::move(slice_tensor)); (*advanced_index_dim)[estimated_dim] = estimated_dim; estimated_dim++; } } else { *has_advanced_index = true; if (slice_tensor.dtype() == DataType::BOOL) { // bool tensor consumes (rank of index tensor) dimensions of input // tensor for (size_t i = 0; i < slice_tensor.shape().size(); i++) { PADDLE_ENFORCE_EQ(slice_tensor.shape()[i], dim_len, common::errors::OutOfRange( "The shape of boolean index %d did not match " "indexed tensor %d along axis %d.", slice_tensor.shape()[0], dim_len, current_dim)); (*advanced_index_dim)[estimated_dim] = estimated_dim; estimated_dim++; current_dim++; dim_len = shape[current_dim]; } } else { // int tensor consumes only one dimension of input tensor // Check: if the dimension is valid and has size 0 while the // index tensor has elements, any index is out of bounds. // Skip this check when current_dim >= rank (too many indices), // which is caught later at the end of ParseIndex. if (current_dim < rank && dim_len == 0 && slice_tensor.numel() > 0) { PADDLE_THROW(common::errors::OutOfRange( "index is out of bounds for dimension %d with size 0", static_cast(current_dim))); } (*advanced_index_dim)[estimated_dim] = estimated_dim; estimated_dim++; current_dim++; } advanced_index->push_back(std::move(slice_tensor)); } } else { PADDLE_THROW(common::errors::InvalidArgument( "Currently, Tensor.__indices__() only allows indexing " "by Boolean, Integers, Slices, Ellipsis, None, Tuples of these types " "and List / Tensor of Bool and Integers, but received " "%s in %dth slice item", std::string(Py_TYPE(slice_item)->tp_name), i + 1)); } } // valid_index is the number of dimensions exclude None index const int valid_indices = size - none_axes->size() - ell_count; PADDLE_ENFORCE_EQ(valid_indices <= rank, true, common::errors::InvalidArgument( "Too many indices (%d) for tensor of dimension %d.", valid_indices, rank)); } static Tensor getTensorWithBasicIndexing(const Tensor& tensor, std::vector* slice_axes, std::vector* slice_starts, std::vector* slice_ends, std::vector* slice_strides, std::vector* decrease_axis, std::vector* none_axes, std::vector* infer_flags, bool* use_strided_slice, bool* out_is_view) { Tensor out; if (slice_axes->empty()) { out = tensor; } else { *out_is_view = true; if (!(*use_strided_slice)) { eager_gil_scoped_release guard; out = slice_ad_func(tensor, *slice_axes, *slice_starts, *slice_ends, *infer_flags, *decrease_axis); } else { eager_gil_scoped_release guard; std::vector slice_axes_int32(slice_axes->begin(), slice_axes->end()); out = strided_slice_ad_func( tensor, slice_axes_int32, *slice_starts, *slice_ends, *slice_strides); if (!decrease_axis->empty()) { out = squeeze_ad_func(out, *decrease_axis); } } } if (!none_axes->empty()) { *out_is_view = true; eager_gil_scoped_release guard; // Deal with cases that decrease_axes is not empty // For example: // # x.shape: (2,3,4) // out = x[0, 0:2, None] # out.shape : (2, 1, 4) for (auto& axis : *(none_axes)) { int len = 0; for (int64_t da : *decrease_axis) { if (da < axis) { len++; } } axis -= len; } out = unsqueeze_ad_func(out, *none_axes); } return out; } inline static bool MaskedFillDispatching(const Tensor& tensor, const std::vector& indices, Tensor* mask_tensor, Tensor* value_tensor) { if (value_tensor->initialized() && value_tensor->numel() != 1) { return false; } if (indices.size() != 1) return false; int64_t num_ind = 0; if ((indices)[0].dtype() != DataType::BOOL) { return false; } else { num_ind += (indices)[0].shape().size(); } *mask_tensor = (indices)[0]; for (size_t i = num_ind; i < tensor.shape().size(); i++) { *mask_tensor = unsqueeze_ad_func(*mask_tensor, {-1}); } return true; } static Tensor dealWithAdvancedIndex(const Tensor& tensor, std::vector* advanced_index_dim, std::vector* advanced_index, bool is_for_setitem, std::vector* transed_index, std::vector* trans_back_dim, int* pos_of_new_dim, int* rank_of_new_dim, std::vector* trans_dim, bool* out_is_view) { *rank_of_new_dim = 0; int p = 0; for (size_t i = 0; i < advanced_index_dim->size(); ++i) { auto index_dim = (*advanced_index_dim)[i]; if (index_dim != -1) { // sum of each advanced_index_tensor's rank equals to number of non -1 // element in advanced_index_dim auto index = (*advanced_index)[p++]; if (index_dim == 0) { // case 1: advanced indices at axis 0, the new dim will be at first. *pos_of_new_dim = 0; } else if (index_dim > 0 && trans_dim->size() > 0 && (*trans_dim)[trans_dim->size() - 1] != index_dim - 1) { // case 2: there are not adjacent advanced indices, the new dim will // be at first. *pos_of_new_dim = 0; } else { *pos_of_new_dim = std::min(index_dim, *pos_of_new_dim); } if (index.dtype() == DataType::BOOL) { *rank_of_new_dim = std::max(*rank_of_new_dim, 1); i--; for (size_t j = 0; j < index.shape().size(); j++) { i++; index_dim = (*advanced_index_dim)[i]; trans_dim->push_back(index_dim); } transed_index->push_back(std::move(index)); } else { *rank_of_new_dim = std::max(*rank_of_new_dim, static_cast(index.shape().size())); trans_dim->push_back(index_dim); transed_index->push_back(std::move(index)); } } } for (size_t i = 0; i < tensor.shape().size(); ++i) { if ((*advanced_index_dim)[i] == -1) { trans_dim->push_back(i); } } Tensor transed_tensor; // skip transform if the `trans_dim` is original order. std::vector original_dim_order(tensor.shape().size()); std::iota(original_dim_order.begin(), original_dim_order.end(), 0); if (original_dim_order == *trans_dim) { transed_tensor = tensor; } else { *out_is_view = true; if (FLAGS_use_stride_kernel && *pos_of_new_dim != 0) { transed_tensor = tensor; } else { transed_tensor = transpose_ad_func(tensor, *trans_dim); } } if (is_for_setitem) { trans_back_dim->resize(trans_dim->size()); std::iota(trans_back_dim->begin(), trans_back_dim->end(), 0); std::sort(trans_back_dim->begin(), trans_back_dim->end(), [&trans_dim](int left, int right) { return (*trans_dim)[left] < (*trans_dim)[right]; }); } return transed_tensor; } static std::vector PrepareIndices(const Tensor& tensor, const Tensor& bool_2_idx, const Tensor& bool_index) { std::vector indices; for (int64_t j = 0; j < bool_2_idx.shape()[1]; ++j) { Tensor sliced_tensor = slice_ad_func(bool_2_idx, {1}, {j}, {j + 1}, {1}, {}); Tensor sliced_tensor_c = sliced_tensor.contiguous(); sliced_tensor_c.reshape({sliced_tensor.dims()[0]}); indices.emplace_back(sliced_tensor_c); } return indices; } static Tensor getValueForBoolTensor(const Tensor& tensor, const Tensor& self_tensor, const Tensor& bool_index, const int64_t slice_offset, const int64_t pos_of_new_dim) { PADDLE_ENFORCE(bool_index.shape().size() <= tensor.shape().size(), common::errors::InvalidArgument( "The dims of bool index doesn't match indexed array, " "the dims of bool index except to be equal or less " "than %d, but received %d}.", tensor.shape().size(), bool_index.shape().size())); auto tensor_shape = tensor.shape(); size_t i = 0; if (FLAGS_use_stride_kernel) { while (i < bool_index.shape().size()) { PADDLE_ENFORCE_EQ( bool_index.shape()[i], tensor_shape[i + pos_of_new_dim], common::errors::OutOfRange( "The dimension of bool index doesn't match indexed array along " "dimension %d, the target dimension is %d, but received %d", i, tensor_shape[i + pos_of_new_dim], bool_index.shape()[i])); i++; } } else { while (i < bool_index.shape().size()) { PADDLE_ENFORCE_EQ( bool_index.shape()[i], tensor_shape[i], common::errors::OutOfRange( "The dimension of bool index doesn't match indexed array along " "dimension %d, the target dimension is %d, but received %d", i, tensor_shape[i], bool_index.shape()[i])); i++; } } const phi::distributed::ProcessMesh* mesh = nullptr; if (InputsContainDistTensor(&mesh, tensor, self_tensor, bool_index)) { ConvertAllInputsToDistTensor(mesh, tensor, self_tensor, bool_index); } if (bool_index.shape().size() == tensor_shape.size()) { return masked_select_ad_func(tensor, bool_index); } auto bool_2_idx = nonzero_ad_func(bool_index); if (FLAGS_use_stride_kernel && self_tensor.is_contiguous()) { std::vector indices = PrepareIndices(tensor, bool_2_idx, bool_index); for (int i = 0; i < pos_of_new_dim; ++i) { indices.insert(indices.begin(), Tensor()); } while (indices.size() < static_cast(tensor.dims().size())) { indices.emplace_back(Tensor()); } std::vector indices_int64; for (auto& indice : indices) { if (indice.defined() && indice.dtype() == DataType::INT32) { indice = indice.cast(DataType::INT64); // int32 -> int64 } indices_int64.push_back(indice); } // AMP Logic if (egr::Controller::Instance().GetAMPLevel() != paddle::imperative::AmpLevel::O0) { auto op_name = phi::TransToFluidOpName("index_elementwise_get"); paddle::small_vector, egr::kSlotSmallVectorSize> amp_tensors_vector = {{self_tensor}}; auto amp_dst_dtype = paddle::imperative::GetAmpDestDtype(op_name, amp_tensors_vector); auto new_self_tensor = paddle::imperative::AmpAutoCast( "self_tensor", self_tensor, amp_dst_dtype, op_name); auto new_tensor = paddle::imperative::AmpAutoCast( "tensor", tensor, amp_dst_dtype, op_name); { paddle::imperative::AutoCastGuard guard( egr::Controller::Instance().GetCurrentAmpAttrs(), paddle::imperative::AmpLevel::O0); AdvancedIndex ad = AdvancedIndex(new_tensor, indices_int64); const bool is_combined = false; const bool accumulate = false; return index_elementwise_get_ad_func(new_self_tensor, ad.indices, ad.src_sizes, ad.src_strides, ad.indexed_sizes, ad.indexed_strides, slice_offset, accumulate, is_combined); } } AdvancedIndex ad = AdvancedIndex(tensor, indices_int64); const bool is_combined = false; const bool accumulate = false; return index_elementwise_get_ad_func(self_tensor, ad.indices, ad.src_sizes, ad.src_strides, ad.indexed_sizes, ad.indexed_strides, slice_offset, accumulate, is_combined); } else { if (bool_index.shape().size() == 1) return gather_ad_func(tensor, bool_2_idx); return gather_nd_ad_func(tensor, bool_2_idx); } } static void ParseBoolAndBroadcastIndices(std::vector* advanced_index) { for (size_t i = 0; i < advanced_index->size(); i++) { if ((*advanced_index)[i].dtype() == DataType::BOOL) { Tensor bool_2_idx = nonzero_ad_func((*advanced_index)[i]); Tensor bool_2_idx_sliced = slice_ad_func(bool_2_idx, {1}, {0}, {1}, {1}, {1}); (*advanced_index)[i] = bool_2_idx_sliced; } } if (advanced_index->size() > 1) { bool need_broadcast = false; common::DDim common_shape = common::make_ddim((*advanced_index)[0].shape()); for (size_t i = 1; i < advanced_index->size(); ++i) { common::DDim current_shape = common::make_ddim((*advanced_index)[i].shape()); if (current_shape != common_shape) { need_broadcast = true; common_shape = phi::funcs::BroadcastTwoDims(current_shape, common_shape, -1); } } if (need_broadcast) { // Here advanced_index has been checked ContainDistTensor // and transed in dealWithAdvancedIndex auto common_shape_vec = common::vectorize(common_shape); for (size_t i = 0; i < advanced_index->size(); ++i) { auto current_shape = (*advanced_index)[i].shape(); if (current_shape != common_shape_vec) { (*advanced_index)[i] = expand_ad_func((*advanced_index)[i], common_shape_vec); } } } } } static Tensor dealWithValues(const Tensor& tensor, PyObject* value_obj, std::vector* values, const bool trans_to_tensor) { Tensor value_tensor; if (PyCheckTensor(value_obj)) { value_tensor = reinterpret_cast(value_obj)->tensor; } else if (py::isinstance(value_obj)) { Tensor value_tensor_tmp(std::make_shared(), egr::Controller::Instance().GenerateUniqueName()); py::object value_obj_tmp = py::reinterpret_borrow(value_obj); py::object value = value_obj_tmp; if (tensor.dtype() == DataType::FLOAT32) { if (!py::isinstance>(value_obj_tmp)) { value = pybind11::detail::CastNumpyArray(value_obj_tmp); } } else if (tensor.dtype() == DataType::FLOAT64) { if (!py::isinstance>(value_obj_tmp)) { value = pybind11::detail::CastNumpyArray(value_obj_tmp); } } else if (tensor.dtype() == DataType::INT32) { if (!py::isinstance>(value_obj_tmp)) { value = pybind11::detail::CastNumpyArray(value_obj_tmp); } } else if (tensor.dtype() == DataType::INT64) { if (!py::isinstance>(value_obj_tmp)) { value = pybind11::detail::CastNumpyArray(value_obj_tmp); } } else if (tensor.dtype() == DataType::BOOL) { if (!py::isinstance>(value_obj_tmp)) { value = pybind11::detail::CastNumpyArray(value_obj_tmp); } } else if (tensor.dtype() == DataType::COMPLEX64) { if (!py::isinstance>>(value_obj_tmp)) { value = pybind11::detail::CastNumpyArray>( value_obj_tmp); } } else if (tensor.dtype() == DataType::COMPLEX128) { if (!py::isinstance>>(value_obj_tmp)) { value = pybind11::detail::CastNumpyArray>( value_obj_tmp); } } else { PADDLE_THROW(common::errors::InvalidArgument( "When assign a numpy.np value to a paddle.Tensor, " "the data type of the paddle.Tensor must be bool, " "float32, float64, complex64, complex128, int32 or int64, " "please check the type of tensor.")); } SetTensorFromPyArray( static_cast(value_tensor_tmp.impl().get()), value, tensor.place(), false); value_tensor = value_tensor_tmp; } else { py::object value_obj_tmp = py::reinterpret_borrow(value_obj); // convert the value to self data type if (py::isinstance(value_obj_tmp) || py::isinstance(value_obj_tmp) || py::isinstance(value_obj_tmp) || PyComplex_Check(value_obj)) { if (tensor.dtype() == DataType::FLOAT32 || tensor.dtype() == DataType::FLOAT16 || tensor.dtype() == DataType::BFLOAT16) { values->push_back(value_obj_tmp.cast()); } else if (tensor.dtype() == DataType::FLOAT64) { values->push_back(value_obj_tmp.cast()); } else if (tensor.dtype() == DataType::INT32 || tensor.dtype() == DataType::INT16 || tensor.dtype() == DataType::INT8 || tensor.dtype() == DataType::UINT8) { values->push_back(value_obj_tmp.cast()); } else if (tensor.dtype() == DataType::INT64) { values->push_back(value_obj_tmp.cast()); } else if (tensor.dtype() == DataType::BOOL) { values->push_back(value_obj_tmp.cast()); } else if (tensor.dtype() == DataType::COMPLEX64) { values->push_back(value_obj_tmp.cast>()); } else if (tensor.dtype() == DataType::COMPLEX128) { values->push_back(value_obj_tmp.cast>()); } } else { PADDLE_THROW(common::errors::InvalidArgument( "Value type error. The assign value allows " "Tensor, numpy.ndarray, integer, float, complex or bool, " "but received %s.", Py_TYPE(value_obj))); } if (trans_to_tensor && (*values).size() > 1) { value_tensor = full_ad_func({1}, (*values)[0], tensor.dtype(), tensor.place()); } } return value_tensor; } static void DealWithIndex(const int pos_of_new_dim, int64_t* slice_offset, std::vector* transed_index, Tensor* tensor, Tensor* sub_tensor, Tensor* transed_sub_tensor, std::vector* transed_index_int64) { for (int i = 0; i < pos_of_new_dim; ++i) { transed_index->insert(transed_index->begin(), Tensor()); } while (transed_index->size() < static_cast(transed_sub_tensor->dims().size())) { transed_index->emplace_back(Tensor()); } *slice_offset = static_cast(reinterpret_cast(sub_tensor->data()) - reinterpret_cast(tensor->data())); for (auto& indice : *transed_index) { if (indice.defined() && indice.dtype() == DataType::INT32) { indice = indice.cast(DataType::INT64); // int32 -> int64 } transed_index_int64->push_back(indice); } } static inline Tensor expand_inplace(Tensor* tensor, Tensor* to_expand) { if (tensor->dims() == to_expand->dims()) { return *to_expand; } else if (tensor->dims()[0] == to_expand->dims()[0]) { return expand_ad_func(*to_expand, common::vectorize(tensor->dims())); } else { *to_expand = squeeze_ad_func(*to_expand, {-1}); return expand_ad_func(*to_expand, common::vectorize(tensor->dims())); } } static void DispatchSetitemKernel(const int pos_of_new_dim, bool* out_is_view, std::vector* transed_index, Tensor* tensor, Tensor* sub_tensor, Tensor* transed_sub_tensor, Tensor* value_tensor, std::vector* values) { Tensor mask_tensor; if (MaskedFillDispatching( *transed_sub_tensor, *transed_index, &mask_tensor, value_tensor)) { if (value_tensor->initialized()) { if (!*out_is_view) { *transed_sub_tensor = masked_fill__ad_func( *transed_sub_tensor, mask_tensor, *value_tensor); return; } } else { if (*out_is_view) { mask_tensor = expand_inplace(transed_sub_tensor, &mask_tensor); int64_t slice_offset = static_cast( reinterpret_cast(transed_sub_tensor->data()) - reinterpret_cast(tensor->data())); *transed_sub_tensor = index_elementwise_put__ad_func( *tensor, {mask_tensor}, (*values)[0], common::vectorize(transed_sub_tensor->dims()), common::vectorize(transed_sub_tensor->strides()), common::vectorize(mask_tensor.dims()), common::vectorize(mask_tensor.strides()), slice_offset); *out_is_view = false; return; } else { Tensor value_tmp_tensor = full_ad_func({1}, (*values)[0], tensor->dtype(), tensor->place()); *transed_sub_tensor = masked_fill__ad_func( *transed_sub_tensor, mask_tensor, value_tmp_tensor); return; } } } if (FLAGS_use_stride_kernel) { if (value_tensor->initialized()) { *transed_index = expandTensors(*transed_index); *transed_index = expand_outplace(*transed_index); std::vector transed_index_int64; int64_t slice_offset; DealWithIndex(pos_of_new_dim, &slice_offset, transed_index, tensor, sub_tensor, transed_sub_tensor, &transed_index_int64); AdvancedIndex ad = AdvancedIndex(*transed_sub_tensor, transed_index_int64); PADDLE_ENFORCE_EQ( phi::funcs::CheckIsDimsMatchBool(common::make_ddim(ad.src_sizes), value_tensor->dims()), true, common::errors::InvalidArgument( "shape mismatch: value tensor of shape %s cannot be " "broadcast to indexing result of shape %s.", value_tensor->dims().to_str(), common::make_ddim(ad.src_sizes).to_str())); *transed_sub_tensor = index_elementwise_put_with_tensor__ad_func(*tensor, ad.indices, *value_tensor, ad.src_sizes, ad.src_strides, ad.indexed_sizes, ad.indexed_strides, slice_offset); // New kernel does not need to transpose back, so set out_is_view to // false. Remove when all cases use this branch. *out_is_view = false; } else { *transed_index = expandTensors(*transed_index); *transed_index = expand_outplace(*transed_index); std::vector transed_index_int64; int64_t slice_offset; DealWithIndex(pos_of_new_dim, &slice_offset, transed_index, tensor, sub_tensor, transed_sub_tensor, &transed_index_int64); AdvancedIndex ad = AdvancedIndex(*transed_sub_tensor, transed_index_int64); *transed_sub_tensor = index_elementwise_put__ad_func(*tensor, ad.indices, (*values)[0], ad.src_sizes, ad.src_strides, ad.indexed_sizes, ad.indexed_strides, slice_offset); // New kernel does not need to transpose back, so set out_is_view to // false. Remove when all cases use this branch. *out_is_view = false; } } else { // TODO(czy): remove in the future if (value_tensor->initialized()) { *transed_sub_tensor = index_put__ad_func( *transed_sub_tensor, *transed_index, *value_tensor); } else { Tensor value_tmp_tensor = full_ad_func({1}, (*values)[0], tensor->dtype(), tensor->place()); *transed_sub_tensor = index_put__ad_func( *transed_sub_tensor, *transed_index, value_tmp_tensor); } } } static void ApplySetitem(const std::vector trans_dim, const int pos_of_new_dim, bool* out_is_view, std::vector* transed_index, Tensor* tensor, Tensor* self_tensor, Tensor* sub_tensor, Tensor* transed_sub_tensor, Tensor* value_tensor, std::vector* values) { if (!value_tensor->initialized() && (*values).size() == 0) return; if (value_tensor->initialized()) { if (self_tensor->dtype() != value_tensor->dtype()) { if (egr::Controller::Instance().GetAMPLevel() != paddle::imperative::AmpLevel::O0) { paddle::small_vector, egr::kSlotSmallVectorSize> tmps = {{*self_tensor}, {*value_tensor}}; auto amp_dtype = paddle::imperative::GetAmpDestDtype("index_put", tmps); *self_tensor = paddle::imperative::AmpAutoCast( self_tensor->name(), *self_tensor, amp_dtype, "index_put"); *value_tensor = paddle::imperative::AmpAutoCast( value_tensor->name(), *value_tensor, amp_dtype, "index_put"); } if (self_tensor->dtype() != value_tensor->dtype()) { *value_tensor = cast_ad_func(*value_tensor, self_tensor->dtype()); } } if (value_tensor->dims().size() > 1 && pos_of_new_dim != 0) { if (!FLAGS_use_stride_kernel) { *value_tensor = transpose_ad_func(*value_tensor, trans_dim); } } const phi::distributed::ProcessMesh* mesh = nullptr; if (InputsContainDistTensor( &mesh, *self_tensor, *transed_sub_tensor, *value_tensor)) { ConvertAllInputsToDistTensor( mesh, *self_tensor, *transed_sub_tensor, *value_tensor); } DispatchSetitemKernel(pos_of_new_dim, out_is_view, transed_index, tensor, sub_tensor, transed_sub_tensor, value_tensor, values); } else { const phi::distributed::ProcessMesh* mesh = nullptr; if (InputsContainDistTensor(&mesh, *self_tensor, *transed_sub_tensor)) { ConvertAllInputsToDistTensor(mesh, *self_tensor, *transed_sub_tensor); } DispatchSetitemKernel(pos_of_new_dim, out_is_view, transed_index, tensor, sub_tensor, transed_sub_tensor, value_tensor, values); } } static void ApplyGetitem(const int index_size, const int pos_of_new_dim, const int rank_of_new_dim, std::vector* transed_index, Tensor* tensor, Tensor* self_tensor, Tensor* sub_tensor, Tensor* transed_tensor, Tensor* out) { auto handle_transpose = [&](Tensor& out) { if (pos_of_new_dim != 0) { std::vector perm(out.shape().size(), 0); int tmp1 = rank_of_new_dim, tmp2 = 0, tmp3 = pos_of_new_dim + rank_of_new_dim; for (int i = 0; i < static_cast(out.shape().size()); ++i) { if (i < pos_of_new_dim) { perm[i] = tmp1++; } else if (i >= pos_of_new_dim && i < pos_of_new_dim + rank_of_new_dim) { perm[i] = tmp2++; } else { perm[i] = tmp3++; } } out = transpose_ad_func(out, perm); } }; if (transed_index->size() == 1 && (*transed_index)[0].dtype() == DataType::BOOL) { // get value for bool tensor const int64_t slice_offset = reinterpret_cast(transed_tensor->data()) - reinterpret_cast(self_tensor->data()); *out = getValueForBoolTensor(*transed_tensor, (*self_tensor), (*transed_index)[0], slice_offset, pos_of_new_dim); if (!FLAGS_use_stride_kernel) { handle_transpose(*out); } return; } else { // get value for int tensor ParseBoolAndBroadcastIndices(transed_index); bool has_empty_index = false; for (const auto& tmp_tensor : *transed_index) { if (!tmp_tensor.initialized()) { has_empty_index = true; break; } } if (FLAGS_use_stride_kernel && !has_empty_index && self_tensor->is_contiguous()) { const phi::distributed::ProcessMesh* mesh = nullptr; if (InputsContainDistTensor( &mesh, *self_tensor, *transed_tensor, *transed_index)) { ConvertAllInputsToDistTensor( mesh, *self_tensor, *transed_tensor, *transed_index); } *transed_index = expandTensors(*transed_index); *transed_index = expand_outplace(*transed_index); std::vector transed_index_int64; int64_t slice_offset; DealWithIndex(pos_of_new_dim, &slice_offset, transed_index, tensor, sub_tensor, transed_tensor, &transed_index_int64); // AMP Logic if (egr::Controller::Instance().GetAMPLevel() != paddle::imperative::AmpLevel::O0) { auto op_name = phi::TransToFluidOpName("index_elementwise_get"); paddle::small_vector, egr::kSlotSmallVectorSize> amp_tensors_vector = {{*self_tensor}}; auto amp_dst_dtype = paddle::imperative::GetAmpDestDtype(op_name, amp_tensors_vector); auto new_self_tensor = paddle::imperative::AmpAutoCast( "self_tensor", *self_tensor, amp_dst_dtype, op_name); auto new_transed_tensor = paddle::imperative::AmpAutoCast( "transed_tensor", *transed_tensor, amp_dst_dtype, op_name); { paddle::imperative::AutoCastGuard guard( egr::Controller::Instance().GetCurrentAmpAttrs(), paddle::imperative::AmpLevel::O0); AdvancedIndex ad = AdvancedIndex(new_transed_tensor, transed_index_int64); const bool is_combined = (index_size == 1) ? false : true; const bool accumulate = true; *out = index_elementwise_get_ad_func(new_self_tensor, ad.indices, ad.src_sizes, ad.src_strides, ad.indexed_sizes, ad.indexed_strides, slice_offset, accumulate, is_combined); } return; } AdvancedIndex ad = AdvancedIndex(*transed_tensor, transed_index_int64); // is_combined: // Distinguishes between regular indexing (single index) and combined // indexing (multiple indices). When false (single index case), enables // optimized backward pass using IndexPutWithSortKernel for better // performance. const bool is_combined = (index_size == 1) ? false : true; const bool accumulate = true; *out = index_elementwise_get_ad_func(*self_tensor, ad.indices, ad.src_sizes, ad.src_strides, ad.indexed_sizes, ad.indexed_strides, slice_offset, accumulate, is_combined); return; } else { Tensor transed_advanced_index_tensor; if (transed_index->size() > 1) { transed_advanced_index_tensor = stack_ad_func(*transed_index, -1); } else { // fast path for single index tensor, since stack is much slower than // unsqueeze transed_advanced_index_tensor = unsqueeze_ad_func((*transed_index)[0], {-1}); } const phi::distributed::ProcessMesh* mesh = nullptr; if (InputsContainDistTensor( &mesh, *transed_tensor, transed_advanced_index_tensor)) { ConvertAllInputsToDistTensor( mesh, *transed_tensor, transed_advanced_index_tensor); } *out = gather_nd_ad_func(*transed_tensor, transed_advanced_index_tensor); handle_transpose(*out); return; } } handle_transpose(*out); } } // namespace pybind } // namespace paddle