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

624 lines
18 KiB
C++

// 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.
#include "paddle/phi/kernels/funcs/dense_tensor_iterator.h"
namespace phi {
void DenseOperandInfo::tensor(DenseTensor*&& tensor) {
tensor_base_ = std::move(tensor);
}
DenseTensorIteratorConfig& DenseTensorIteratorConfig::add_borrowed_output(
const DenseTensor& output) {
PADDLE_ENFORCE_EQ(num_inputs_,
0,
"Keep in mind that you have to add all outputs first "
"before adding any input.");
tensors_.push_back(&output);
num_outputs_++;
return *this;
}
DenseTensorIteratorConfig& DenseTensorIteratorConfig::add_borrowed_input(
const DenseTensor& input) {
tensors_.push_back(&input);
num_inputs_++;
return *this;
}
DenseTensorIteratorConfig& DenseTensorIteratorConfig::add_borrowed_const_input(
const DenseTensor& input) {
const_tensor_indices_.push_back(tensors_.size());
tensors_.push_back(&input);
num_inputs_++;
return *this;
}
void DenseTensorIteratorBase::reorder_dimensions() {
perm_.resize(ndim());
if (ndim() == 1) {
perm_[0] = 0;
return;
}
std::iota(perm_.rbegin(), perm_.rend(), 0);
auto should_swap = [&](size_t dim0, size_t dim1) {
for (auto arg = 0; arg < ntensors(); arg++) {
if (operands_[arg].stride_bytes.empty() || operands_[arg].will_resize) {
continue;
}
int64_t stride0 = operands_[arg].stride_bytes[dim0];
int64_t stride1 = operands_[arg].stride_bytes[dim1];
if (is_reduction_ && operands_[arg].is_output) {
if ((stride0 == 0) != (stride1 == 0)) {
return stride1 == 0 ? 1 : -1;
}
}
if (stride0 == 0 || stride1 == 0) {
continue;
} else if (stride0 < stride1) {
return -1;
} else if (stride0 > stride1) {
return 1;
} else {
auto t_dim0 = shape_[dim0];
auto t_dim1 = shape_[dim1];
if (t_dim0 > t_dim1) {
return 1;
}
}
}
return 0;
};
for (auto 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;
}
}
}
permute_dimensions(perm_);
}
void DenseTensorIteratorBase::permute_dimensions(std::vector<int64_t> perm) {
PADDLE_ENFORCE_EQ(
perm.size(),
static_cast<unsigned>(ndim()),
"perm.size() must equal to ndim in DenseDenseTensorIterator");
auto reorder = [perm](std::vector<int64_t> data) {
auto res = std::vector<int64_t>(data.size(), 0);
for (size_t i = 0; i < perm.size(); i++) {
res[i] = data[perm[i]];
}
return res;
};
shape_ = reorder(shape_);
for (auto& op : operands_) {
if (!op.stride_bytes.empty()) {
op.stride_bytes = reorder(op.stride_bytes);
}
}
}
std::vector<int64_t> DenseTensorIteratorBase::compatible_stride(
int64_t element_size) const {
std::vector<int64_t> stride;
int64_t next_stride = element_size;
for (auto dim = 0; dim < ndim(); dim++) {
stride.push_back(next_stride);
next_stride *= shape_[dim];
}
return stride;
}
std::vector<int64_t> DenseTensorIteratorBase::invert_perm(
std::vector<int64_t> input) const {
auto res = std::vector<int64_t>(input.size());
for (auto dim = 0; dim < ndim(); dim++) {
res[perm_[dim]] = input[dim];
}
return res;
}
void DenseTensorIteratorBase::allocate_or_resize_outputs() {
for (size_t i = 0; i < num_outputs_; i++) {
auto& op = operands_[i];
bool valid_stride = op.tensor().strides().size() == -1 ? false : true;
bool reduce_pass = false;
if (is_reduction_ && !valid_stride && op.is_output) {
reduce_pass = true;
}
if (!reduce_pass &&
(!op.tensor().initialized() || op.will_resize || !valid_stride)) {
auto element_size = phi::SizeOf(op.tensor().dtype());
op.stride_bytes = compatible_stride(static_cast<int64_t>(element_size));
bool inverted = true;
for (auto j = 0; j < ndim(); j++) {
if (perm_[j] != ndim() - j - 1) {
inverted = false;
break;
}
}
auto tensor_shape = invert_perm(shape_);
if (inverted) {
set_output_raw_strided(i, tensor_shape, {});
} else {
auto tensor_stride = invert_perm(op.stride_bytes);
for (auto dim = 0; dim < ndim(); dim++) {
tensor_stride[dim] /= static_cast<int64_t>(element_size);
}
set_output_raw_strided(i, tensor_shape, tensor_stride);
}
op.current_dtype = op.target_dtype;
} else if (op.tensor().initialized()) {
set_output_raw_strided(i, vectorize<int64_t>(op.tensor().dims()), {});
}
}
}
void DenseTensorIteratorBase::set_output_raw_strided(
int64_t output_idx,
std::vector<int64_t> sizes,
std::vector<int64_t> strides) {
PADDLE_THROW(
common::errors::Fatal("Virtual Set Output Stride, Unsupported!"));
}
void DenseTensorIterator::set_output_raw_strided(int64_t output_idx,
std::vector<int64_t> sizes,
std::vector<int64_t> strides) {
auto& op = operands_[output_idx];
bool valid_stride = op.tensor().strides().size() == -1 ? false : true;
if (!op.tensor().initialized() || !valid_stride) {
if (strides.empty()) {
auto meta = op.tensor().meta();
auto new_dims = make_ddim(sizes);
auto new_strides = meta.calc_strides(new_dims);
meta.dims = new_dims;
meta.strides = new_strides;
op.tensor().set_meta(meta);
} else {
auto meta = op.tensor().meta();
auto new_dims = make_ddim(sizes);
auto new_strides = make_ddim(strides);
meta.dims = new_dims;
meta.strides = new_strides;
op.tensor().set_meta(meta);
}
op.current_dtype = op.target_dtype;
} else if (op.will_resize) {
PADDLE_THROW(common::errors::Fatal("Operator Resize not Implemented!"));
}
}
void DenseTensorIteratorBase::coalesce_dimensions() {
if (ndim() <= 1) {
return;
}
auto can_coalesce = [&](int dim0, int dim1) {
auto shape0 = shape_[dim0];
auto shape1 = shape_[dim1];
if (shape0 == 1 || shape1 == 1) {
return true;
}
for (auto i = 0; i < ntensors(); i++) {
auto& stride = operands_[i].stride_bytes;
if (shape0 * stride[dim0] != stride[dim1]) {
return false;
}
}
return true;
};
auto replace_stride = [&](int dim0, int dim1) {
for (auto i = 0; i < ntensors(); i++) {
auto& stride = operands_[i].stride_bytes;
stride[dim0] = stride[dim1];
}
};
int prev_dim = 0;
for (auto 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 (auto i = 0; i < ntensors(); i++) {
operands_[i].stride_bytes.resize(ndim());
}
has_coalesced_dimensions_ = true;
}
int64_t DenseTensorIteratorBase::numel() const {
int64_t numel = 1;
for (int64_t size : shape_) {
numel *= size;
}
return numel;
}
void* DenseTensorIteratorBase::data_ptr(int64_t arg) const {
return static_cast<void*>(operands_[arg].data);
}
static inline std::vector<int64_t> infer_size_dimvector(
std::vector<int64_t> a, std::vector<int64_t> b) {
auto dimsA = a.size();
auto dimsB = b.size();
auto ndim = dimsA > dimsB ? dimsA : dimsB;
std::vector<int64_t> expandedSizes = 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;
expandedSizes[i] = sizeA == 1 ? sizeB : sizeA;
}
return expandedSizes;
}
void DenseTensorIteratorBase::populate_operands(
DenseTensorIteratorConfig& config) {
for (size_t idx = 0; idx < config.tensors_.size(); idx++) {
auto& tensor = config.tensors_[idx];
operands_.emplace_back(std::move(const_cast<DenseTensor*>(tensor)));
if (idx < static_cast<size_t>(config.num_outputs_)) {
operands_[idx].is_output = true;
}
}
num_outputs_ = config.num_outputs_;
}
FastSetupType DenseTensorIteratorBase::compute_fast_setup_type(
const DenseTensorIteratorConfig& config) {
if (is_reduction_ || !all_ops_same_shape_) {
return FastSetupType::NONE;
}
bool is_contiguous = true;
for (const auto& op : operands_) {
if (op.tensor().initialized() && !op.will_resize) {
is_contiguous &= op.tensor().meta().is_contiguous();
}
}
if (is_contiguous) {
return FastSetupType::CONTIGUOUS;
}
return FastSetupType::NONE;
}
bool DenseTensorIteratorBase::fast_set_up(
const DenseTensorIteratorConfig& config) {
FastSetupType setup_type = compute_fast_setup_type(config);
if (setup_type == FastSetupType::NONE) {
return false;
}
switch (setup_type) {
case FastSetupType::CONTIGUOUS: {
for (size_t i = 0; i < num_outputs_; i++) {
set_output_raw_strided(i, shape_, {});
}
break;
}
default:
PADDLE_THROW(common::errors::Fatal("Unsupported Fast Setup Type!"));
}
if (ndim() > 1) {
has_coalesced_dimensions_ = true;
}
if (ndim() >= 1) {
shape_[0] = numel();
shape_.resize(1);
}
for (auto& op : operands_) {
auto element_size_in_bytes = phi::SizeOf(op.tensor().dtype());
op.stride_bytes.resize(ndim());
if (ndim() > 0) {
op.stride_bytes[0] = element_size_in_bytes;
}
}
return true;
}
int DenseTensorIteratorBase::num_reduce_dims() const {
int count = 0;
for (int dim = 0; dim < ndim(); dim++) {
if (operands_[0].stride_bytes[dim] == 0) {
count++;
}
}
return count;
}
int64_t DenseTensorIteratorBase::num_output_elements() const {
int64_t elem = 1;
for (int dim = 0; dim < ndim(); dim++) {
if (operands_[0].stride_bytes[dim] != 0 || shape_[dim] == 0) {
elem *= shape_[dim];
}
}
return elem;
}
void DenseTensorIteratorBase::compute_shape(
const DenseTensorIteratorConfig& config) {
all_ops_same_shape_ = true;
bool has_scalars = false;
bool has_tensors = false;
for (auto& op : operands_) {
bool valid_stride = op.tensor().strides().size() == -1 ? false : true;
if (!op.tensor().initialized() || !valid_stride) continue;
if (config.resize_outputs_ && op.is_output) continue;
auto shape = vectorize<int64_t>(op.tensor().dims());
if (shape.empty()) {
has_scalars = true;
} else {
has_tensors = true;
}
if (has_scalars && has_tensors) {
all_ops_same_shape_ = false;
}
if (shape_.empty()) {
shape_ = shape;
} else if (!(shape == shape_)) {
all_ops_same_shape_ = false;
shape_ = infer_size_dimvector(shape_, shape);
}
}
all_ops_are_scalars_ = !has_tensors;
}
void DenseTensorIteratorBase::compute_strides(
const DenseTensorIteratorConfig& config) {
for (auto& op : operands_) {
bool valid_stride = op.tensor().strides().size() == -1 ? false : true;
bool reduce_pass = false;
bool out_pass = false;
if (is_alloc_out_ && op.is_output) out_pass = true;
std::vector<int64_t> tmp_shape = vectorize<int64_t>(op.tensor().dims());
std::vector<int64_t> tmp_stride = vectorize<int64_t>(op.tensor().strides());
if (is_reduction_ && !valid_stride && op.is_output) {
tmp_stride = std::vector<int64_t>(shape_.size(), 0);
tmp_shape = std::vector<int64_t>(shape_.size(), 1);
reduce_pass = true;
}
if (out_pass || reduce_pass ||
op.tensor().initialized() && !op.will_resize && valid_stride) {
std::vector<int64_t> original_shape;
original_shape = config.static_shape_
? shape_
: vectorize<int64_t>(op.tensor().dims());
if (op.is_output && reduce_pass) original_shape = tmp_shape;
std::vector<int64_t> original_stride;
original_stride = vectorize<int64_t>(op.tensor().strides());
if (op.is_output && reduce_pass) original_stride = tmp_stride;
auto element_size_in_bytes = phi::SizeOf(op.tensor().dtype());
auto offset = ndim() - original_shape.size();
if (offset > 0)
op.stride_bytes.resize(ndim(), 0);
else
op.stride_bytes.resize(ndim());
for (size_t i = 0; i < original_shape.size(); i++) {
if (original_shape[i] == 1 && shape_[offset + i] != 1) {
op.stride_bytes[offset + i] = 0;
} else {
op.stride_bytes[offset + i] =
original_stride[i] * element_size_in_bytes;
}
}
}
}
}
void DenseTensorIteratorBase::build(DenseTensorIteratorConfig& config) {
is_reduction_ = config.is_reduction_;
is_alloc_out_ = config.is_alloc_out_;
populate_operands(config);
compute_shape(config);
if (!fast_set_up(config)) {
compute_strides(config);
reorder_dimensions();
allocate_or_resize_outputs();
coalesce_dimensions();
}
for (auto& op : operands_) {
op.data = const_cast<void*>(op.tensor().data());
}
int64_t ndim_offsets = (ndim() ? ndim() : 1);
view_offsets_ = std::vector<int64_t>(ndim_offsets, 0);
}
DimIter::DimIter(std::vector<int64_t> shape, int64_t start, int64_t end)
: shape(shape),
start(start),
end(end),
values(shape.size()),
offset(start) {
std::fill(values.begin(), values.end(), 0);
if (start == 0) {
return;
}
int64_t linear_offset = start;
auto ndim = values.size();
for (size_t dim = 0; dim < ndim; dim++) {
int64_t size = shape[dim];
if (size > 0) {
values[dim] = linear_offset % size;
linear_offset /= size;
}
}
}
bool DimIter::iter_to_end() const { return offset >= end; }
void DimIter::iter_to_next(const std::array<int64_t, 2>& step) {
offset += step[0] * step[1];
auto ndim = values.size();
int64_t overflow = step[0];
size_t i = 0;
if (step[1] != 1) {
i = 1;
overflow = step[1];
}
for (; i < ndim && overflow > 0; i++) {
auto size = shape[i];
auto prev = values[i];
auto value = prev + overflow;
if (value >= size) {
overflow = 1;
value -= size;
} else {
overflow = 0;
}
values[i] = static_cast<int64_t>(value);
}
}
std::array<int64_t, 2> DimIter::iter_for_step() const {
int64_t step0 = std::min(shape[0] - values[0], end - offset);
int64_t step1 = 1;
if (step0 == shape[0] && !shape.empty() && shape.size() > 1) {
step1 = std::min(shape[1] - values[1], (end - offset) / shape[0]);
}
return {step0, step1};
}
void DenseTensorIteratorBase::narrow(int dim, int64_t start, int64_t size) {
shape_[dim] = size;
view_offsets_[dim] += start;
for (auto& op : operands_) {
op.data = (static_cast<char*>(op.data)) + op.stride_bytes[dim] * start;
}
if (size == 1 && !is_reduction_) {
coalesce_dimensions();
}
}
bool DenseTensorIteratorBase::is_dim_reduced(int dim) const {
for (auto& op : operands_) {
if (op.is_output && op.stride_bytes[dim] == 0 && shape_[dim] > 1) {
return true;
}
}
return false;
}
std::unique_ptr<DenseTensorIterator> DenseTensorIteratorBase::split(int dim) {
auto split_iter = std::make_unique<DenseTensorIterator>(*this);
bool has_overlap = is_dim_reduced(dim);
int64_t split_size = shape_[dim] / 2;
int64_t remaining_size = shape_[dim] - split_size;
split_iter->narrow(dim, 0, split_size);
split_iter->final_output_ &= !has_overlap;
narrow(dim, split_size, remaining_size);
accumulate_ |= has_overlap;
return split_iter;
}
int DenseTensorIteratorBase::get_dim_to_split() const {
int64_t max_extent = -1;
int dim_to_split = -1;
for (int dim = ndim() - 1; dim >= 0; --dim) {
const int64_t size = shape_[dim];
if (size == 0) {
continue;
}
for (auto& op : operands_) {
const int64_t extent = (size - 1) * std::abs(op.stride_bytes[dim]);
if (extent > max_extent) {
max_extent = extent;
dim_to_split = dim;
}
}
}
return dim_to_split;
}
bool DenseTensorIteratorBase::can_use_32bit_indexing() const {
constexpr int64_t max_32bit_value = std::numeric_limits<int32_t>::max();
if (numel() > max_32bit_value) {
return false;
}
for (auto& op : operands_) {
int64_t max_offset = 1;
for (int dim = 0; dim < ndim(); ++dim) {
max_offset += (shape_[dim] - 1) * op.stride_bytes[dim];
}
if (max_offset > max_32bit_value) {
return false;
}
}
return true;
}
Tensor32BitSplitter DenseTensorIteratorBase::with_32bit_indexing() const {
return Tensor32BitSplitter(*this);
}
Tensor32BitSplitter::iterator::iterator(const DenseTensorIteratorBase& iter) {
iterator_stack_.emplace_back(std::make_unique<DenseTensorIterator>(iter));
iterator_stack_.emplace_back(nullptr);
++(*this);
}
Tensor32BitSplitter::iterator& Tensor32BitSplitter::iterator::operator++() {
iterator_stack_.pop_back();
while (!iterator_stack_.empty() &&
!iterator_stack_.back()->can_use_32bit_indexing()) {
auto& current_iter = *iterator_stack_.back();
int split_dim = current_iter.get_dim_to_split();
iterator_stack_.emplace_back(current_iter.split(split_dim));
}
return *this;
}
DenseTensorIterator& Tensor32BitSplitter::iterator::operator*() const {
return *iterator_stack_.back();
}
Tensor32BitSplitter::iterator Tensor32BitSplitter::begin() const {
return Tensor32BitSplitter::iterator(source_iterator_);
}
Tensor32BitSplitter::iterator Tensor32BitSplitter::end() const {
return Tensor32BitSplitter::iterator();
}
} // namespace phi