Files
2026-07-13 12:40:42 +08:00

1404 lines
54 KiB
C++

// 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 <Python.h>
#include <algorithm>
#include <cstdint>
#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<int64_t>(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<int64_t>(tensor.dims()));
} else {
to_expand = squeeze_ad_func(to_expand, {-1});
return expand_ad_func(to_expand, common::vectorize<int64_t>(tensor.dims()));
}
}
static inline std::vector<Tensor> expandTensors(std::vector<Tensor> indices) {
// expands bool to int tensors;
std::vector<Tensor> 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<Tensor> expand_outplace(
std::vector<Tensor> 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<Tensor> 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<int64_t>(sizes));
}
}
return result;
}
struct AdvancedIndex {
AdvancedIndex(Tensor src, std::vector<Tensor> indices);
Tensor src;
std::vector<Tensor> indices;
std::vector<int64_t> indexed_sizes;
std::vector<int64_t> indexed_strides;
std::vector<int64_t> src_sizes;
std::vector<int64_t> src_strides;
int64_t dims_before;
int64_t dims_after;
bool bool_case;
};
inline static void restride_src(std::vector<int64_t>* shape,
std::vector<int64_t>* strides,
int64_t dims_before,
int64_t dims_indexed,
std::vector<int64_t> 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<int64_t> reshape_indexer(Tensor* index,
int64_t dims_before,
int64_t dims_after) {
auto orig_shape = common::vectorize<int64_t>(index->dims());
auto shape = std::vector<int64_t>{};
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<Tensor> indices_list) {
uint32_t element_size_bytes = phi::SizeOf(src.dtype());
int64_t dims_before = 0, dims_after = 0, dims_indexed = 0;
std::vector<int64_t> shape_vec = common::vectorize<int64_t>(src.dims());
std::vector<int64_t> stride_vec = common::vectorize<int64_t>(src.strides());
std::vector<int64_t> replacement_shape;
std::vector<int64_t> idx_shape_vec = {};
std::vector<int64_t> 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<int64_t>(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<int64_t> 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 <typename T>
inline T GetDenseTensorValue(const phi::DenseTensor* x) {
T value = static_cast<T>(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<T>()[0];
} else {
value = x->data<T>()[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<Py_ssize_t>(GetDenseTensorValue<int32_t>(&tensor));
} else if (framework::TransToProtoVarType(tensor.type()) ==
framework::proto::VarType::INT64) {
return static_cast<Py_ssize_t>(GetDenseTensorValue<int64_t>(&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<int64_t>* slice_axes,
std::vector<int64_t>* slice_starts,
std::vector<int64_t>* slice_ends,
std::vector<int64_t>* slice_strides,
std::vector<int64_t>* decrease_axis,
std::vector<int64_t>* none_axes,
std::vector<int64_t>* infer_flags,
std::vector<int>* advanced_index_dim,
std::vector<Tensor>* 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<int64_t>(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<PySliceObject*>(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<DenseTensor>(),
egr::Controller::Instance().GenerateUniqueName());
py::object index_obj_tmp =
py::reinterpret_borrow<py::object>(slice_item);
py::object index_tmp = index_obj_tmp;
SetTensorFromPyArray(
static_cast<phi::DenseTensor*>(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<phi::DenseTensor*>(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<int>(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<int64_t>* slice_axes,
std::vector<int64_t>* slice_starts,
std::vector<int64_t>* slice_ends,
std::vector<int64_t>* slice_strides,
std::vector<int64_t>* decrease_axis,
std::vector<int64_t>* none_axes,
std::vector<int64_t>* 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<int> 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<Tensor>& 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<int>* advanced_index_dim,
std::vector<Tensor>* advanced_index,
bool is_for_setitem,
std::vector<Tensor>* transed_index,
std::vector<int>* trans_back_dim,
int* pos_of_new_dim,
int* rank_of_new_dim,
std::vector<int>* 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<int>(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<int> 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<Tensor> PrepareIndices(const Tensor& tensor,
const Tensor& bool_2_idx,
const Tensor& bool_index) {
std::vector<Tensor> 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<Tensor> 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<size_t>(tensor.dims().size())) {
indices.emplace_back(Tensor());
}
std::vector<Tensor> 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<std::vector<Tensor>, 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<Tensor>* 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<int64_t>(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<phi::Scalar>* values,
const bool trans_to_tensor) {
Tensor value_tensor;
if (PyCheckTensor(value_obj)) {
value_tensor = reinterpret_cast<TensorObject*>(value_obj)->tensor;
} else if (py::isinstance<py::array>(value_obj)) {
Tensor value_tensor_tmp(std::make_shared<DenseTensor>(),
egr::Controller::Instance().GenerateUniqueName());
py::object value_obj_tmp = py::reinterpret_borrow<py::object>(value_obj);
py::object value = value_obj_tmp;
if (tensor.dtype() == DataType::FLOAT32) {
if (!py::isinstance<py::array_t<float>>(value_obj_tmp)) {
value = pybind11::detail::CastNumpyArray<float>(value_obj_tmp);
}
} else if (tensor.dtype() == DataType::FLOAT64) {
if (!py::isinstance<py::array_t<double>>(value_obj_tmp)) {
value = pybind11::detail::CastNumpyArray<double>(value_obj_tmp);
}
} else if (tensor.dtype() == DataType::INT32) {
if (!py::isinstance<py::array_t<int32_t>>(value_obj_tmp)) {
value = pybind11::detail::CastNumpyArray<int32_t>(value_obj_tmp);
}
} else if (tensor.dtype() == DataType::INT64) {
if (!py::isinstance<py::array_t<int64_t>>(value_obj_tmp)) {
value = pybind11::detail::CastNumpyArray<int64_t>(value_obj_tmp);
}
} else if (tensor.dtype() == DataType::BOOL) {
if (!py::isinstance<py::array_t<bool>>(value_obj_tmp)) {
value = pybind11::detail::CastNumpyArray<bool>(value_obj_tmp);
}
} else if (tensor.dtype() == DataType::COMPLEX64) {
if (!py::isinstance<py::array_t<std::complex<float>>>(value_obj_tmp)) {
value = pybind11::detail::CastNumpyArray<std::complex<float>>(
value_obj_tmp);
}
} else if (tensor.dtype() == DataType::COMPLEX128) {
if (!py::isinstance<py::array_t<std::complex<double>>>(value_obj_tmp)) {
value = pybind11::detail::CastNumpyArray<std::complex<double>>(
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<phi::DenseTensor*>(value_tensor_tmp.impl().get()),
value,
tensor.place(),
false);
value_tensor = value_tensor_tmp;
} else {
py::object value_obj_tmp = py::reinterpret_borrow<py::object>(value_obj);
// convert the value to self data type
if (py::isinstance<py::float_>(value_obj_tmp) ||
py::isinstance<py::int_>(value_obj_tmp) ||
py::isinstance<py::bool_>(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<float>());
} else if (tensor.dtype() == DataType::FLOAT64) {
values->push_back(value_obj_tmp.cast<double>());
} 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<float>());
} else if (tensor.dtype() == DataType::INT64) {
values->push_back(value_obj_tmp.cast<double>());
} else if (tensor.dtype() == DataType::BOOL) {
values->push_back(value_obj_tmp.cast<bool>());
} else if (tensor.dtype() == DataType::COMPLEX64) {
values->push_back(value_obj_tmp.cast<std::complex<float>>());
} else if (tensor.dtype() == DataType::COMPLEX128) {
values->push_back(value_obj_tmp.cast<std::complex<double>>());
}
} 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<Tensor>* transed_index,
Tensor* tensor,
Tensor* sub_tensor,
Tensor* transed_sub_tensor,
std::vector<Tensor>* 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<size_t>(transed_sub_tensor->dims().size())) {
transed_index->emplace_back(Tensor());
}
*slice_offset =
static_cast<int64_t>(reinterpret_cast<char*>(sub_tensor->data()) -
reinterpret_cast<char*>(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<int64_t>(tensor->dims()));
} else {
*to_expand = squeeze_ad_func(*to_expand, {-1});
return expand_ad_func(*to_expand,
common::vectorize<int64_t>(tensor->dims()));
}
}
static void DispatchSetitemKernel(const int pos_of_new_dim,
bool* out_is_view,
std::vector<Tensor>* transed_index,
Tensor* tensor,
Tensor* sub_tensor,
Tensor* transed_sub_tensor,
Tensor* value_tensor,
std::vector<phi::Scalar>* 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<int64_t>(
reinterpret_cast<char*>(transed_sub_tensor->data()) -
reinterpret_cast<char*>(tensor->data()));
*transed_sub_tensor = index_elementwise_put__ad_func(
*tensor,
{mask_tensor},
(*values)[0],
common::vectorize<int64_t>(transed_sub_tensor->dims()),
common::vectorize<int64_t>(transed_sub_tensor->strides()),
common::vectorize<int64_t>(mask_tensor.dims()),
common::vectorize<int64_t>(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<Tensor> 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<Tensor> 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<int> trans_dim,
const int pos_of_new_dim,
bool* out_is_view,
std::vector<Tensor>* transed_index,
Tensor* tensor,
Tensor* self_tensor,
Tensor* sub_tensor,
Tensor* transed_sub_tensor,
Tensor* value_tensor,
std::vector<phi::Scalar>* 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<std::vector<Tensor>, 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<Tensor>* 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<int> 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<int>(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<const char*>(transed_tensor->data()) -
reinterpret_cast<const char*>(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<Tensor> 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<std::vector<Tensor>, 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