/* Copyright (c) 2021 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/infermeta/unary.h" #include #include "paddle/phi/common/data_type.h" #include "paddle/phi/core/enforce.h" #include "paddle/phi/core/infermeta_utils.h" namespace phi { void UnchangedInferMeta(const MetaTensor& x, MetaTensor* out) { out->share_meta(x); } void FlattenInferMeta(const MetaTensor& x, int start_axis, int stop_axis, MetaTensor* out) { auto x_dims = x.dims(); int in_dims_size = x_dims.size(); if (start_axis < 0) { start_axis = start_axis + in_dims_size; } if (stop_axis < 0) { stop_axis = stop_axis + in_dims_size; } PADDLE_ENFORCE_GE(stop_axis, start_axis, paddle::platform::errors::InvalidArgument( "The stop_axis should be greater" "than or equal to start_axis.")); int64_t outer = 1; std::vector out_shape; out_shape.reserve(in_dims_size - stop_axis + start_axis); for (int i = 0; i < start_axis; ++i) { out_shape.push_back(x_dims[i]); } for (int i = start_axis; i <= stop_axis; i++) { if (x_dims[i] == -1 || outer == -1) { outer = -1; } else { outer *= x_dims[i]; } } out_shape.push_back(outer); for (int i = stop_axis + 1; i < in_dims_size; i++) { out_shape.push_back(x_dims[i]); } const auto& out_dims = phi::make_ddim(out_shape); out->set_dims(out_dims); out->set_dtype(x.dtype()); out->set_layout(x.layout()); if (x_dims[0] == out_dims[0]) { // Only pass LoD when the first dimension of output and Input(X) // are the same. out->share_lod(x); } } void CastInferMeta(const MetaTensor& x, DataType out_dtype, MetaTensor* out) { out->set_dims(x.dims()); out->set_dtype(out_dtype); out->set_layout(x.layout()); } void CopyToInferMeta(const MetaTensor& x, Backend backend, bool blocking, MetaTensor* out) { UnchangedInferMeta(x, out); } void CreateLikeInferMeta(const MetaTensor& x, DataType dtype, MetaTensor* out) { out->set_dims(x.dims()); out->set_dtype(dtype == DataType::UNDEFINED ? x.dtype() : dtype); out->set_layout(x.layout()); } static phi::DDim ValidateShape(const std::vector shape, const phi::DDim& in_dims) { const int64_t in_size = phi::product(in_dims); auto in_dims_vec = phi::vectorize(in_dims); bool all_positive = std::all_of(in_dims_vec.cbegin(), in_dims_vec.cend(), [](int64_t i) { return i > 0; }); // only one dimension can be set to -1, whose size will be automatically // infered. const int64_t unk_dim_val = -1; const int64_t copy_dim_val = 0; std::vector output_shape(shape.size(), 0); int64_t capacity = 1; int unk_dim_idx = -1; for (size_t i = 0; i < shape.size(); ++i) { if (shape[i] == unk_dim_val) { PADDLE_ENFORCE_EQ( unk_dim_idx, -1, paddle::platform::errors::InvalidArgument( "Only one dimension value of 'shape' in ReshapeOp can " "be -1. But received shape = [%s], shape[%d] is also -1.", phi::make_ddim(shape), i)); unk_dim_idx = i; } else if (shape[i] == copy_dim_val) { PADDLE_ENFORCE_LT( static_cast(i), in_dims.size(), paddle::platform::errors::InvalidArgument( "The index of 0 in `shape` must be less than " "the input tensor X's dimensions. " "But received shape = [%s], shape[%d] = 0, X's shape = [%s], " "X's dimensions = %d.", phi::make_ddim(shape), i, in_dims, in_dims.size())); } else { PADDLE_ENFORCE_GT( shape[i], 0, paddle::platform::errors::InvalidArgument( "Each dimension value of 'shape' in ReshapeOp must not " "be negative except one unknown dimension. " "But received shape = [%s], shape[%d] = %d.", phi::make_ddim(shape), i, shape[i])); } // NOTE all non-zero values will be converted to True (include negative // value) capacity *= (shape[i] ? shape[i] : in_dims[i]); output_shape[i] = (shape[i] ? static_cast(shape[i]) : in_dims[i]); } if (unk_dim_idx != -1) { if (all_positive) { // in_size < 0 and is un-determinate in compile time, skip the check, // for example, in_dims = [-1, 8, 1, 1], shape = [-1, 3, 8], // capacity = -24, in_size = -8, output_shape[0] = 0 // the following check will fail. output_shape[unk_dim_idx] = -in_size / capacity; PADDLE_ENFORCE_EQ( output_shape[unk_dim_idx] * capacity, -in_size, paddle::platform::errors::InvalidArgument( "The 'shape' attribute in ReshapeOp is invalid. " "The input tensor X'size must be divisible by known " "capacity of 'shape'. " "But received X's shape = [%s], X's size = %d, " "'shape' is [%s], known capacity of 'shape' is %d.", in_dims, in_size, phi::make_ddim(shape), capacity)); } else { output_shape[unk_dim_idx] = -1; } } else { if (all_positive) { PADDLE_ENFORCE_EQ( capacity, in_size, paddle::platform::errors::InvalidArgument( "The 'shape' in ReshapeOp is invalid. " "The input tensor X'size must be equal to the capacity of " "'shape'. " "But received X's shape = [%s], X's size = %d, 'shape' is " "[%s], the capacity of 'shape' is %d.", in_dims, in_size, phi::make_ddim(shape), capacity)); } } // support reshape with zero-input(input tensor with product(shape) == 0) // by now we require that if the input tensor is zero shape, the target // shape of output must be zero if (in_size == 0) { PADDLE_ENFORCE_LE( capacity, in_size, paddle::platform::errors::InvalidArgument( "The 'shape' in ReshapeOp is invalid. " "The input tensor X's shape = [%s], X's capacity = %d." "But the target shape of Out is [%s], the " "capacity of 'Out' is %d.", in_dims, in_size, phi::make_ddim(shape), capacity)); } return phi::make_ddim(output_shape); } void InferMetaFromVecValue(const MetaTensor& x, const std::vector& shape, MetaTensor* out) { PADDLE_ENFORCE_EQ(!shape.empty(), true, phi::errors::InvalidArgument( "The parameter 'shape' in ReshapeOp must be set. " "But received 'shape' is empty.")); auto x_dims = x.dims(); auto out_dims = ValidateShape(shape, x_dims); out->set_dims(out_dims); out->set_dtype(x.dtype()); out->set_layout(x.layout()); if (x_dims[0] == out_dims[0]) { // Only pass LoD when the first dimension of output and Input(X) // are the same. out->share_lod(x); } } void ReshapeInferMeta(const MetaTensor& x, const ScalarArray& shape, MetaTensor* out, MetaConfig config) { auto& shape_data = shape.GetData(); PADDLE_ENFORCE_NOT_NULL(out, phi::errors::InvalidArgument( "Output(Out) of ReshapeOp should not be null.")); if (!config.is_runtime && shape.FromTensor()) { out->set_dims(phi::make_ddim(shape_data)); out->share_lod(x); return; } PADDLE_ENFORCE_GT(shape_data.size(), 0, phi::errors::InvalidArgument( "The shape's size in ReshapeOp can't be zero.")); InferMetaFromVecValue(x, shape_data, out); } void ReshapeWithXShapeInferMeta(const MetaTensor& x, const ScalarArray& shape, MetaTensor* xshape, MetaTensor* out, MetaConfig config) { PADDLE_ENFORCE_NOT_NULL( xshape, phi::errors::InvalidArgument( "Output(XShape) of ReshapeOp should not be null.")); const auto& x_dims = x.dims(); std::vector xshape_dims(x_dims.size() + 1); xshape_dims[0] = 0; for (int i = 0; i < x_dims.size(); ++i) { xshape_dims[i + 1] = x_dims[i]; } xshape->set_dims(phi::make_ddim(xshape_dims)); xshape->share_lod(x); ReshapeInferMeta(x, shape, out, config); } /* Why not use ReduceInferMeta directly? Because we need make InferMetaFunction's args follow the design of api.yaml */ void SumInferMeta(const MetaTensor& x, const std::vector& axis, DataType dtype, bool keep_dim, MetaTensor* out) { ReduceInferMetaBase(x, axis, keep_dim, dtype, out); } void ReduceInferMetaBase(const MetaTensor& x, const std::vector& axis, bool keep_dim, DataType dtype, MetaTensor* out) { bool reduce_all = true; std::set dims_set(axis.begin(), axis.end()); for (int64_t i = 0; i < x.dims().size(); ++i) { if (dims_set.find(i) == dims_set.end()) { reduce_all = false; break; } } std::vector out_dim_vector; if (keep_dim) { for (int64_t i = 0; i < x.dims().size(); ++i) { if (reduce_all || dims_set.find(i) != dims_set.end()) { out_dim_vector.push_back(1); } else { out_dim_vector.push_back(x.dims().at(i)); } } } else { for (int64_t i = 0; i < x.dims().size(); ++i) { if (reduce_all || dims_set.find(i) != dims_set.end()) { continue; } else { out_dim_vector.push_back(x.dims().at(i)); } } if (out_dim_vector.size() == 0) { out_dim_vector.push_back(1); } } DDim out_dim = phi::make_ddim(out_dim_vector); DataType out_dtype; if (dtype != DataType::UNDEFINED) { out_dtype = dtype; } else { if (x.dtype() == DataType::BOOL || x.dtype() == DataType::INT32 || x.dtype() == DataType::INT64) { out_dtype = DataType::INT64; } else { out_dtype = x.dtype(); } } out->set_dims(out_dim); out->set_dtype(out_dtype); out->set_layout(x.layout()); } void ReduceInferMeta(const MetaTensor& x, const std::vector& axis, bool keep_dim, MetaTensor* out) { ReduceInferMetaBase(x, axis, keep_dim, DataType::UNDEFINED, out); } void TransferLayoutInferMeta(const MetaTensor& x, DataLayout layout, MetaTensor* out) { out->set_dims(x.dims()); out->set_dtype(x.dtype()); out->set_layout(layout); } void SplitInferMeta(const MetaTensor& x, const ScalarArray& num_or_sections, const Scalar& axis, std::vector* out, MetaConfig config) { int axis_value = axis.to(); int rank = x.dims().size(); PADDLE_ENFORCE_EQ( axis_value >= -rank && axis_value < rank, true, paddle::platform::errors::InvalidArgument( "The axis is expected to be in range of [%d, %d), but got %d", -rank, rank, axis_value)); if (axis_value < 0) { axis_value = axis_value + rank; } auto input_axis_dim = x.dims().at(axis_value); auto num_or_sections_data = num_or_sections.GetData(); // step1: get formated sections std::vector sections; // num_or_sections is a number if (num_or_sections_data.size() == 1) { int num = num_or_sections_data.at(0); PADDLE_ENFORCE_EQ(input_axis_dim % num, 0, paddle::platform::errors::InvalidArgument( "The input's size along the split dimension " "must be evenly divisible by Attr(num_or_sections). " "But received Attr(num_or_sections) " "= %d, input(X)'s shape = [%s], Attr(dim) = %d.", num, x.dims(), axis_value)); for (int i = 0; i < num; ++i) { sections.push_back(input_axis_dim / num); } } else { // num_or_sections is a sections const int unknow_dim_val = -1; int unknow_dim_idx = -1; int num_of_unknow = 0; int sum_of_section = 0; for (size_t i = 0; i < num_or_sections_data.size(); ++i) { sections.push_back(num_or_sections_data[i]); if (num_or_sections_data[i] == unknow_dim_val) { num_of_unknow++; unknow_dim_idx = i; } else { sum_of_section += num_or_sections_data[i]; } } if (config.is_runtime) { PADDLE_ENFORCE_LE(num_of_unknow, 1, paddle::platform::errors::InvalidArgument( "Only one dimension value of Attr(num_or_sections) " "in SplitOp can be -1. " "But received Attr(num_or_sections) = [%s].", phi::make_ddim(num_or_sections_data))); } if (unknow_dim_idx != -1) { // for example, input shape = [4 ,5], axis = 1, sections = [2, 3, -1]. // input_axis_dim = 5, sum_of_sections = 5. // the following check will fail. PADDLE_ENFORCE_LT( sum_of_section, input_axis_dim, paddle::platform::errors::InvalidArgument( "Sum of Attr(num_or_sections) other than unknown section " "must be less than the input's " "size " "along the split dimension. But received Attr(num_or_sections) " "= [%s], input(X)'s shape = [%s], Attr(dim) = %d.", phi::make_ddim(num_or_sections_data), x.dims(), axis_value)); if (config.is_runtime) { sections[unknow_dim_idx] = input_axis_dim - sum_of_section; } } else { PADDLE_ENFORCE_EQ( sum_of_section, input_axis_dim, paddle::platform::errors::InvalidArgument( "Sum of Attr(num_or_sections) must be equal to the input's " "size " "along the split dimension. But received Attr(num_or_sections)" " = [%s], input(X)'s shape = [%s], Attr(dim) = %d.", phi::make_ddim(num_or_sections_data), x.dims(), axis_value)); } } // setp2: fill out dims std::vector out_dims(sections.size(), x.dims()); if (config.is_runtime || input_axis_dim > 0) { for (size_t i = 0; i < sections.size(); ++i) { out_dims[i][axis_value] = sections[i]; } } else { for (size_t i = 0; i < sections.size(); ++i) { out_dims[i][axis_value] = -1; } } for (size_t i = 0; i < sections.size(); ++i) { if (axis_value != 0) { // Only pass LoD when not spliting along the first dim. (*out)[i].set_dtype(x.dtype()); (*out)[i].set_dims(out_dims[i]); (*out)[i].set_layout(x.layout()); } else { (*out)[i].set_dtype(x.dtype()); (*out)[i].set_dims(out_dims[i]); (*out)[i].set_layout(x.layout()); (*out)[i].share_lod(x); } } } void TraceInferMeta( const MetaTensor& x, int offset, int axis1, int axis2, MetaTensor* out) { int dim1 = axis1; int dim2 = axis2; auto x_dims = x.dims(); int dim1_ = dim1 < 0 ? x_dims.size() + dim1 : dim1; int dim2_ = dim2 < 0 ? x_dims.size() + dim2 : dim2; PADDLE_ENFORCE_GE( x_dims.size(), 2, phi::errors::OutOfRange( "Input's dim is out of range (expected at least 2, but got %ld).", x_dims.size())); PADDLE_ENFORCE_LT( dim1_, x_dims.size(), phi::errors::OutOfRange( "Attr(dim1) is out of range (expected to be in range of [%ld, " "%ld], but got %ld).", -(x_dims.size()), (x_dims.size() - 1), dim1)); PADDLE_ENFORCE_LT( dim2_, x_dims.size(), phi::errors::OutOfRange( "Attr(dim2) is out of range (expected to be in range of [%ld, " "%ld], but got %ld).", -(x_dims.size()), (x_dims.size() - 1), dim2)); PADDLE_ENFORCE_NE( dim1_, dim2_, phi::errors::InvalidArgument("The dimensions should not be identical " "%ld vs %ld.", dim1, dim2)); auto sizes = vectorize(x_dims); if (x_dims.size() == 2) { sizes.clear(); sizes.push_back(1); } else { sizes.erase(sizes.begin() + std::max(dim1_, dim2_)); sizes.erase(sizes.begin() + std::min(dim1_, dim2_)); } out->set_dims(phi::make_ddim(sizes)); } } // namespace phi PD_REGISTER_INFER_META_FN(copy_to, phi::CopyToInferMeta); PD_REGISTER_INFER_META_FN(split, phi::SplitInferMeta);