// Copyright (c) 2025 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 "paddle/common/array.h" #include "paddle/phi/backends/context_pool.h" #include "paddle/phi/backends/gpu/gpu_context.h" #include "paddle/phi/common/int_array.h" #include "paddle/phi/common/memory_utils.h" #include "paddle/phi/common/place.h" #include "paddle/phi/core/dense_tensor.h" #include "paddle/phi/kernels/cast_kernel.h" #include "paddle/phi/kernels/elementwise_add_kernel.h" #include "paddle/phi/kernels/elementwise_kernel.h" #include "paddle/phi/kernels/elementwise_multiply_kernel.h" #include "paddle/phi/kernels/expand_kernel.h" #include "paddle/phi/kernels/full_kernel.h" #include "paddle/phi/kernels/funcs/indexing.h" #include "paddle/phi/kernels/nonzero_kernel.h" #include "paddle/phi/kernels/slice_kernel.h" #include "paddle/phi/kernels/transpose_kernel.h" #if defined(__NVCC__) || defined(__HIPCC__) #ifdef __NVCC__ #include #include #elif defined(__HIPCC__) #include #endif #endif #ifdef PADDLE_WITH_CUDA #include "paddle/phi/backends/gpu/cuda/cuda_graph_with_memory_pool.h" #endif namespace phi { namespace funcs { static inline std::vector infer_size_dimvector( const std::vector& a, const std::vector& b) { // Use ptrdiff_t to ensure signed comparison. auto dimsA = a.size(); auto dimsB = b.size(); auto ndim = dimsA > dimsB ? dimsA : dimsB; std::vector expandedSizes = 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; expandedSizes[i] = sizeA == 1 ? sizeB : sizeA; } return expandedSizes; } static inline std::vector compute_strides( const std::vector& input_dims, // value_tensor const std::vector& input_strides, const int64_t& input_elesize, const int64_t& ndim, const std::vector* shape_, std::vector* stride_size) { std::vector stride_bytes(ndim, 0); const auto& original_shape = input_dims; const auto& original_stride = input_strides; int64_t element_size_in_bytes = input_elesize; int offset = ndim - original_shape.size(); if (offset > 0) stride_bytes.resize(ndim, 0); else stride_bytes.resize(ndim); for (size_t i = 0; i < original_shape.size(); i++) { if (original_shape[i] == 1 && (*shape_)[offset + i] != 1) { stride_bytes[offset + i] = 0; } else { stride_bytes[offset + i] = original_stride[i] * element_size_in_bytes; } } stride_size->push_back(stride_bytes.size()); return stride_bytes; } static inline std::vector compute_shapes( const std::vector>& input_dims) { std::vector shape_; for (size_t i = 0; i < input_dims.size(); i++) { auto shape = input_dims[i]; if (shape_.empty()) { shape_ = shape; } else if (!(shape == shape_)) { shape_ = infer_size_dimvector(shape_, shape); } } return shape_; } template static inline void permute_dimensions(const std::vector& stride_size, const std::vector& perm, std::array* strides_array, std::vector* shape_) { auto reorder = [perm](std::vector data) { auto res = std::vector(data.size(), 0); for (size_t i = 0; i < perm.size(); i++) { res[i] = data[perm[i]]; } return res; }; // Update shape and strides *shape_ = reorder(*shape_); std::array, N> temp_strides; for (int64_t i = 0; i < N; i++) { if ((*strides_array)[i] != nullptr) { std::vector original_data((*strides_array)[i], (*strides_array)[i] + stride_size[i]); temp_strides[i] = reorder(original_data); for (int64_t j = 0; j < stride_size[i]; j++) { (*strides_array)[i][j] = temp_strides[i][j]; } } } } template static inline void reorder_dimensions(const std::vector& stride_size, std::vector* shape_, std::array* strides_array) { // Sort the dimensions based on strides in ascending order with reduced dims // at the front. NOTE: that this inverts the order of C-contiguous tensors. // strides[0] is the fastest moving dimension instead of strides[ndim - 1]. // See NOTE: [Computing output strides] and inline comments for more detailed // description auto ndim = shape_->size(); std::vector perm_; perm_.resize(ndim); if (ndim == 1) { perm_[0] = 0; return; } // initialize perm with n-1, n-2, ..., 1, 0 std::iota(perm_.rbegin(), perm_.rend(), 0); // returns 1 if the dim0 should come after dim1, -1 if dim0 should come // before dim1, and 0 if the comparison is ambiguous. auto should_swap = [&](size_t dim0, size_t dim1) { for (int64_t arg = 0; arg < N; arg++) { // ignore undefined or incorrectly sized tensors if ((*strides_array)[arg] == nullptr) { continue; } int64_t stride0 = (*strides_array)[arg][dim0]; int64_t stride1 = (*strides_array)[arg][dim1]; // move on to the next input if one of the dimensions is broadcasted if (stride0 == 0 || stride1 == 0) { continue; // it is important to return here only with strict comparisons, for // equal strides we try to break the tie later by comparing // corresponding dimensions or if that does not work, moving on to the // next tensor } else if (stride0 < stride1) { return -1; } else if (stride0 > stride1) { return 1; } else { // equal strides, use dimensions themselves as the tie-breaker. // at this point, with zero strides out of the way, we are guaranteed // that operand dimensions are equal to shape_ auto t_dim0 = (*shape_)[dim0]; auto t_dim1 = (*shape_)[dim1]; // return only if dimensions should be swapped, otherwise move on to the // next tensor if (t_dim0 > t_dim1) { return 1; } } } return 0; }; // insertion sort with support for ambiguous comparisons for (size_t i = 1; i < ndim; i++) { int dim1 = i; for (int dim0 = i - 1; dim0 >= 0; dim0--) { int comparison = should_swap(perm_[dim0], perm_[dim1]); if (comparison > 0) { std::swap(perm_[dim0], perm_[dim1]); dim1 = dim0; } else if (comparison < 0) { break; } } } // perform re-ordering of shape and strides permute_dimensions(stride_size, perm_, strides_array, shape_); } static inline std::vector compatible_stride( const std::vector* shape_, const int64_t& ndim, const int64_t& element_size) { std::vector stride; int64_t next_stride = element_size; for (int64_t dim = 0; dim < ndim; ++dim) { stride.push_back(next_stride); next_stride *= (*shape_)[dim]; } return stride; } template static inline void allocate_or_resize_outputs( const std::vector* shape_, const int64_t element_size, const int64_t ndim, std::array* strides_array) { std::vector stride_bytes = compatible_stride(shape_, ndim, static_cast(element_size)); if (strides_array && (*strides_array)[0]) { std::copy(stride_bytes.begin(), stride_bytes.end(), (*strides_array)[0]); } } template static inline void coalesce_dimensions(const int64_t& ndim, std::array* strides_array, std::vector* stride_size, std::vector* shape_) { if (ndim <= 1) { return; } // We can coalesce two adjacent dimensions if either dim has size 1 or if: // shape[n] * stride[n] == stride[n + 1]. auto can_coalesce = [&](int dim0, int dim1) { auto shape0 = (*shape_)[dim0]; auto shape1 = (*shape_)[dim1]; if (shape0 == 1 || shape1 == 1) { return true; } for (int64_t i = 0; i < N; i++) { auto& stride = (*strides_array)[i]; if (shape0 * stride[dim0] != stride[dim1]) { return false; } } return true; }; // replace each operands stride at dim0 with its stride at dim1 auto replace_stride = [&](int dim0, int dim1) { for (int64_t i = 0; i < N; i++) { auto& stride = (*strides_array)[i]; stride[dim0] = stride[dim1]; } }; int prev_dim = 0; for (int64_t dim = 1; dim < ndim; dim++) { if (can_coalesce(prev_dim, dim)) { if ((*shape_)[prev_dim] == 1) { replace_stride(prev_dim, dim); } (*shape_)[prev_dim] *= (*shape_)[dim]; } else { prev_dim++; if (prev_dim != dim) { replace_stride(prev_dim, dim); (*shape_)[prev_dim] = (*shape_)[dim]; } } } (*shape_).resize(prev_dim + 1); for (int64_t i = 0; i < N; i++) { (*stride_size)[i] = shape_->size(); } } template static inline void CopyStride( const std::vector& output_dims, // value_tensor const std::vector& output_strides, const int64_t& output_elesize, const std::vector& input_dims, // input_tensor const std::vector& input_strides, const int64_t& input_elesize, std::vector* desired_shape, std::array* strides_array, int64_t* numel, std::array, N>& strides_vec) { // NOLINT int ndim = output_dims.size(); std::vector stride_size; *desired_shape = compute_shapes({input_dims, output_dims}); strides_vec[0] = compute_strides(output_dims, // input_tensor output_strides, output_elesize, ndim, desired_shape, &stride_size); strides_vec[1] = compute_strides(input_dims, // value_tensor input_strides, input_elesize, ndim, desired_shape, &stride_size); for (size_t i = 0; i < N; i++) { (*strides_array)[i] = strides_vec[i].data(); } reorder_dimensions(stride_size, desired_shape, strides_array); coalesce_dimensions(ndim, strides_array, &stride_size, desired_shape); int64_t num = 1; for (size_t i = 0; i < desired_shape->size(); i++) { num *= (*desired_shape)[i]; } *numel = num; } template static inline void IndexPutStride( const std::vector& output_dims, // input_tensor const std::vector& output_strides, const int64_t& output_elesize, const std::vector& input_dims, // value_tensor const std::vector& input_strides, const int64_t& input_elesize, const std::vector& index_dims, // index_tensor const std::vector& index_strides, const int64_t& index_elesize, std::vector* desired_shape, std::array* strides_array, int64_t* numel, std::array, N>& strides_vec) { // NOLINT int ndim = output_dims.size(); std::vector stride_size; *desired_shape = compute_shapes({input_dims, output_dims, index_dims}); strides_vec[0] = compute_strides(output_dims, // input_tensor output_strides, output_elesize, ndim, desired_shape, &stride_size); strides_vec[1] = compute_strides(input_dims, // value_tensor input_strides, input_elesize, ndim, desired_shape, &stride_size); strides_vec[2] = compute_strides(index_dims, // index_tensor index_strides, index_elesize, ndim, desired_shape, &stride_size); for (size_t i = 0; i < N; i++) { (*strides_array)[i] = strides_vec[i].data(); } reorder_dimensions(stride_size, desired_shape, strides_array); coalesce_dimensions(ndim, strides_array, &stride_size, desired_shape); int64_t num = 1; for (size_t i = 0; i < desired_shape->size(); i++) { num *= (*desired_shape)[i]; } *numel = num; } template static inline void IndexGetStride( const std::vector& output_dims, const std::vector& output_strides, const int64_t& output_elesize, const std::vector& input_dims, const std::vector& input_strides, const int64_t& input_elesize, const std::vector& index_dims, const std::vector& index_strides, const int64_t& index_elesize, std::vector* desired_shape, std::array* strides_array, int64_t* numel, std::array, N>& strides_vec) { // NOLINT int ndim = output_dims.size(); std::vector stride_size; *desired_shape = compute_shapes({input_dims, output_dims, index_dims}); strides_vec[0] = compute_strides(input_dims, input_strides, input_elesize, ndim, desired_shape, &stride_size); strides_vec[1] = compute_strides(output_dims, output_strides, output_elesize, ndim, desired_shape, &stride_size); strides_vec[2] = compute_strides(index_dims, index_strides, index_elesize, ndim, desired_shape, &stride_size); for (size_t i = 0; i < N; i++) { (*strides_array)[i] = strides_vec[i].data(); } reorder_dimensions(stride_size, desired_shape, strides_array); allocate_or_resize_outputs( desired_shape, output_elesize, ndim, strides_array); coalesce_dimensions(ndim, strides_array, &stride_size, desired_shape); int64_t num = 1; for (size_t i = 0; i < desired_shape->size(); i++) { num *= (*desired_shape)[i]; } *numel = num; } static inline void cal_shape_stride(const std::vector& index_dims, int64_t* num_indices, std::vector* shape_tmp, std::vector* stride_tmp) { std::vector index_dims_; std::vector index_stride_; bool tmp_flag = false; for (unsigned i = 0; i < index_dims.size(); i++) { if (index_dims[i] == -1) { if (!tmp_flag) { *num_indices = i; tmp_flag = true; continue; } else { break; } } if (!tmp_flag) { index_dims_.push_back(index_dims[i]); } else { shape_tmp->push_back(index_dims[i]); } } int shape_size = shape_tmp->size(); stride_tmp->resize(shape_size); if (shape_size > 0) { (*stride_tmp)[shape_size - 1] = 1; } if (shape_size > 1) { for (int i = shape_size - 2; i >= 0; i--) { (*stride_tmp)[i] = (*stride_tmp)[i + 1] * (*shape_tmp)[i + 1]; } } } template static inline void ScatterAddStride( const std::vector& output_dims, const std::vector& output_strides, const int64_t& output_elesize, const std::vector& input_dims, const std::vector& input_strides, const int64_t& input_elesize, const std::vector& index_dims, const std::vector& index_strides, const int64_t& index_elesize, std::vector* desired_shape, std::array* strides_array, int64_t* numel, std::array, N>& strides_vec) { // NOLINT int ndim = output_dims.size(); std::vector stride_size; *desired_shape = compute_shapes({input_dims, output_dims, index_dims}); strides_vec[0] = compute_strides(input_dims, input_strides, input_elesize, ndim, desired_shape, &stride_size); strides_vec[1] = compute_strides(output_dims, output_strides, output_elesize, ndim, desired_shape, &stride_size); strides_vec[2] = compute_strides(index_dims, index_strides, index_elesize, ndim, desired_shape, &stride_size); for (size_t i = 0; i < N; i++) { (*strides_array)[i] = strides_vec[i].data(); } reorder_dimensions(stride_size, desired_shape, strides_array); coalesce_dimensions(ndim, strides_array, &stride_size, desired_shape); int64_t num = 1; for (size_t i = 0; i < desired_shape->size(); i++) { num *= (*desired_shape)[i]; } *numel = num; } static inline bool hasContiguousSubspace(const std::vector& tl) { auto isDefined = [](const DenseTensor& tensor) { return tensor.initialized(); }; auto isNull = [](const DenseTensor& tensor) { return !tensor.initialized(); }; auto start = std::find_if(tl.begin(), tl.end(), isDefined); auto stop = std::find_if(tl.rbegin(), tl.rend(), isDefined); auto it = std::find_if(start, stop.base(), isNull); return it == stop.base(); } #if defined(PADDLE_WITH_CUDA) static inline std::vector expandTensors( const GPUContext& dev_ctx, const std::vector& indices) { std::vector result; for (const auto& index : indices) { if (index == nullptr) { result.emplace_back(); continue; } if (index->dtype() == DataType::BOOL) { DenseTensor bool_2_idx; phi::NonZeroKernel(dev_ctx, *index, &bool_2_idx); for (int j = 0; j < index->dims().size(); ++j) { DenseTensor sliced_tensor; phi::SliceKernel( dev_ctx, bool_2_idx, {1}, {j}, {j + 1}, {1}, {}, &sliced_tensor); result.emplace_back(sliced_tensor); } } else { result.emplace_back(*index); } } return result; } static inline std::vector expand_outplace( const GPUContext& dev_ctx, const std::vector& to_expand) { bool first = true; DDim target_shape; for (size_t i = 0; i < to_expand.size(); ++i) { if (!to_expand[i].initialized()) continue; if (first) { target_shape = to_expand[i].dims(); first = false; } else { target_shape = InferSizeSymdimvector(target_shape, to_expand[i].dims()); } } std::vector result(to_expand.size()); for (size_t i = 0; i < to_expand.size(); ++i) { if (!to_expand[i].initialized()) continue; if (to_expand[i].dims() == target_shape) { result[i] = to_expand[i]; } else { phi::ExpandKernel( dev_ctx, to_expand[i], phi::IntArray(vectorize(target_shape)), &result[i]); } } return result; } template inline std::tuple, std::vector> transposeToFrontAndInvPerm(const GPUContext& dev_ctx, const DenseTensor& self, const std::vector& indices) { std::vector dims; std::vector inv_perm; std::vector transposed_indices; dims.reserve(self.dims().size()); inv_perm.resize(self.dims().size()); for (int i = 0; i < static_cast(self.dims().size()); ++i) { if (indices[i].initialized()) { dims.push_back(i); transposed_indices.emplace_back(indices[i]); } } for (int i = 0; i < static_cast(self.dims().size()); ++i) { if (!indices[i].initialized()) { dims.push_back(i); transposed_indices.emplace_back(); } } for (int i = 0; i < static_cast(self.dims().size()); ++i) { inv_perm[dims[i]] = i; } DenseTensor transposed_self; TransposeKernel(dev_ctx, self, dims, &transposed_self); return std::make_tuple(transposed_self, transposed_indices, inv_perm); } static inline std::vector computeLinearStride( const DenseTensor& tensor) { auto sizes = vectorize(tensor.dims()); std::vector stride(sizes.size()); if (stride.empty()) { return stride; } stride.back() = 1; std::partial_sum(sizes.rbegin(), sizes.rend() - 1, stride.rbegin() + 1, std::multiplies()); return stride; } static inline DenseTensor wrapIndexOnce(const GPUContext& dev_ctx, const DenseTensor& index, const int64_t& dim, const int64_t& dim_size, bool check_range) { DenseTensor dim_size_tensor; dim_size_tensor.Resize(index.dims()); dev_ctx.Alloc(&dim_size_tensor); auto* dim_size_data = dim_size_tensor.data(); auto numel = index.numel(); std::vector host_data(numel, dim_size); const int64_t* stable_hd = phi::backends::gpu::RestoreHostMemIfCapturingCUDAGraph(host_data.data(), host_data.size()); phi::memory_utils::Copy(dev_ctx.GetPlace(), dim_size_data, CPUPlace(), stable_hd, numel * sizeof(int64_t), dev_ctx.stream()); return phi::Remainder(dev_ctx, index, dim_size_tensor); } static inline std::tuple computeLinearIndex(const GPUContext& dev_ctx, const DenseTensor& src, const std::vector& indices, bool check_range) { std::vector strides = computeLinearStride(src); DenseTensor linearIndex; int64_t nElemBefore = 1, nElemAfter = 1, strideBefore = 0; for (int64_t i = 0; i < src.dims().size(); ++i) { if (indices[i].initialized()) { auto wrapped_index = wrapIndexOnce(dev_ctx, indices[i], i, src.dims()[i], check_range); auto strides_tensor = phi::Full( dev_ctx, vectorize(wrapped_index.dims()), phi::Scalar(strides[i])); auto scaled_index = phi::Multiply( dev_ctx, wrapped_index, strides_tensor); if (linearIndex.initialized()) { phi::AddKernel( dev_ctx, linearIndex, scaled_index, &linearIndex); } else { linearIndex = scaled_index; if (i > 0) { strideBefore = src.strides()[i - 1]; } } } else if (linearIndex.initialized()) { nElemAfter *= src.dims()[i]; } else { nElemBefore *= src.dims()[i]; } } return std::make_tuple( std::move(linearIndex), nElemBefore, strideBefore, nElemAfter); } template static inline std::tuple> makeLinearIndex(const GPUContext& dev_ctx, const DenseTensor& self, const std::vector& orig, bool check_range) { auto indices = expandTensors(dev_ctx, orig); for (auto& idx : indices) { if (idx.initialized() && idx.dtype() == DataType::INT32) { idx = Cast(dev_ctx, idx, DataType::INT64); } } indices = expand_outplace(dev_ctx, std::move(indices)); while (indices.size() < static_cast(self.dims().size())) { indices.emplace_back(); } std::vector inverse_perm; DenseTensor transposed_self = self; std::vector transposed_indices; std::vector inv_perm; if (!hasContiguousSubspace(indices)) { auto [tmp_self, tmp_indices, tmp_perm] = transposeToFrontAndInvPerm(dev_ctx, self, indices); transposed_self = std::move(tmp_self); transposed_indices = std::move(tmp_indices); inv_perm = std::move(tmp_perm); } else { transposed_indices = indices; } auto [linear_index, n_elem_before, stride_before, n_elem_after] = computeLinearIndex( dev_ctx, transposed_self, transposed_indices, check_range); return std::make_tuple(linear_index, transposed_self, n_elem_before, stride_before, n_elem_after, inv_perm); } #endif inline bool are_expandable(const std::vector& shape1, const std::vector& shape2) { size_t ndim1 = shape1.size(); size_t ndim2 = shape2.size(); size_t ndim = std::min(ndim1, ndim2); for (int64_t i = static_cast(ndim) - 1; i >= 0; --i) { auto dim1 = shape1[--ndim1]; auto dim2 = shape2[--ndim2]; if (dim1 == dim2 || dim1 == 1 || dim2 == 1) { continue; } return false; } return true; } inline int64_t LargestIndex(const DenseTensor& tensor) { int64_t result = 0; const auto& dims = tensor.dims(); const auto& strides = tensor.strides(); for (int i = 0; i < dims.size(); ++i) { result += (dims[i] - 1) * strides[i]; } return result; } inline int GetNumBits(uint64_t max_val) { if (max_val == 0) return 1; int num_bits = 1; while (max_val > 1) { max_val >>= 1; num_bits++; } return num_bits; } } // namespace funcs } // namespace phi