Files
tensorflow--tensorflow/tensorflow/compiler/mlir/lite/utils/utils.h
T
wehub-resource-sync 8a852e4b4e
cffconvert / validate (push) Has been skipped
License Check / license-check (push) Failing after 2s
chore: import upstream snapshot with attribution
2026-07-13 12:14:16 +08:00

608 lines
23 KiB
C++

/* Copyright 2024 The TensorFlow 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.
==============================================================================*/
#ifndef TENSORFLOW_COMPILER_MLIR_LITE_UTILS_UTILS_H_
#define TENSORFLOW_COMPILER_MLIR_LITE_UTILS_UTILS_H_
#include <algorithm>
#include <complex>
#include <cstddef>
#include <cstdint>
#include <set>
#include <utility>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/ErrorHandling.h"
#include "mlir/Dialect/Traits.h" // from @llvm-project
#include "mlir/IR/Attributes.h" // from @llvm-project
#include "mlir/IR/Builders.h" // from @llvm-project
#include "mlir/IR/BuiltinAttributeInterfaces.h" // from @llvm-project
#include "mlir/IR/BuiltinAttributes.h" // from @llvm-project
#include "mlir/IR/BuiltinTypeInterfaces.h" // from @llvm-project
#include "mlir/IR/BuiltinTypes.h" // from @llvm-project
#include "mlir/IR/Matchers.h" // from @llvm-project
#include "mlir/IR/Operation.h" // from @llvm-project
#include "mlir/IR/Types.h" // from @llvm-project
#include "mlir/IR/Value.h" // from @llvm-project
#include "mlir/Support/LLVM.h" // from @llvm-project
namespace mlir {
namespace TFL {
using llvm::ArrayRef;
using mlir::Operation;
using mlir::ShapedType;
using mlir::Value;
// Returns true if the value is the min float value.
inline bool IsNegInfiniteValue(APFloat value) {
if (!value.isNegative()) return false;
return value.isInfinity();
}
// Returns true if the value is the max float value.
inline bool IsPosInfiniteValue(APFloat value) {
if (value.isNegative()) return false;
return value.isInfinity();
}
// Returns 1D 32-bit dense elements attribute with the given values.
inline DenseIntElementsAttr GetI32ElementsAttr(ArrayRef<int32_t> values,
Builder* builder) {
RankedTensorType ty = mlir::RankedTensorType::get(
{static_cast<int32_t>(values.size())}, builder->getIntegerType(32));
return DenseIntElementsAttr::get(ty, values);
}
inline DenseIntElementsAttr GetI64ElementsAttr(ArrayRef<int64_t> values,
Builder* builder) {
RankedTensorType ty = RankedTensorType::get(
{static_cast<int64_t>(values.size())}, builder->getIntegerType(64));
return DenseIntElementsAttr::get(ty, values);
}
// Returns true if all tensor value in `values` has static shape and same shape.
inline bool OpHasSameStaticShapes(Operation* op) {
auto values = op->getOperands();
int operand_num = 0;
ArrayRef<int64_t> shape;
for (Value value : values) {
auto shaped_type = mlir::dyn_cast<ShapedType>(value.getType());
if (!shaped_type || !shaped_type.hasStaticShape()) {
return false;
}
if (operand_num == 0) {
shape = shaped_type.getShape();
} else {
if (shape != shaped_type.getShape()) {
return false;
}
}
++operand_num;
}
return true;
}
// Utility function to map final permutation to initial permutation
// initial -> permutation1 -> permutation2 -> final
inline DenseElementsAttr RemapPermutation(Value permutation1,
DenseElementsAttr perm2_const) {
SmallVector<int32_t> initial_permutation;
DenseElementsAttr perm1_const;
SmallVector<int32_t> new_permutation;
if (matchPattern(permutation1, m_Constant(&perm1_const))) {
for (int32_t idx = 0; idx < perm1_const.getNumElements(); ++idx) {
initial_permutation.push_back(idx);
}
for (auto perm : perm2_const.getValues<APInt>()) {
new_permutation.push_back(
initial_permutation[perm1_const
.getValues<APInt>()[perm.getSExtValue()]
.getSExtValue()]);
}
}
return mlir::DenseElementsAttr::get(
RankedTensorType::get(
{static_cast<int>(new_permutation.size())},
mlir::IntegerType::get(permutation1.getContext(), 32)),
llvm::ArrayRef(new_permutation));
}
// Utility function to map final permutation to initial permutation
// initial -> permutation1 -> permutation2 -> final
inline DenseElementsAttr RemapPermutation(Value permutation1,
Value permutation2) {
DenseElementsAttr perm2_const;
(void)matchPattern(permutation2, m_Constant(&perm2_const));
return RemapPermutation(permutation1, perm2_const);
}
inline bool IsTransposeNoop(Value permutation) {
DenseElementsAttr perm_values_attr;
if (!matchPattern(permutation, m_Constant(&perm_values_attr))) return false;
for (const auto& [idx, perm_value] :
llvm::enumerate(perm_values_attr.getValues<APInt>())) {
if (perm_value.getSExtValue() != idx) {
return false;
}
}
return true;
}
// Returns true if the transpose op is trivial. Trivial means that
// the permutation is a cyclic permutation of the original shape with only the
// identity dimensions permuted.
inline bool IsTransposeTrivial(llvm::ArrayRef<int64_t> input_shape,
Value perm) {
DenseElementsAttr perm_values_attr;
if (!matchPattern(perm, m_Constant(&perm_values_attr))) return false;
SmallVector<int64_t, 8> perm_values;
for (const auto& dim : perm_values_attr.getValues<APInt>()) {
// Valid range is [-input_shape.size(), input_shape.size()).
int64_t p = dim.getSExtValue();
if (p < 0) {
p += input_shape.size();
}
if (p < 0 || p >= input_shape.size()) {
return false;
}
perm_values.push_back(p);
}
// This should never happen unless the input graph is malformed.
if (input_shape.size() != perm_values.size()) {
return false;
}
SmallVector<int, 8> old_major_index_ordering;
SmallVector<int, 8> new_major_index_ordering;
for (int i = 0, end = input_shape.size(); i < end; i++) {
if (input_shape[i] != 1) {
old_major_index_ordering.push_back(i);
}
if (input_shape[perm_values[i]] != 1) {
new_major_index_ordering.push_back(perm_values[i]);
}
}
return (old_major_index_ordering == new_major_index_ordering);
}
// Returns the permutation that maps the input shape to the output shape.
// This is only valid for trivial reshape ops.
inline DenseElementsAttr GetPermutationFromTrivialReshape(
mlir::ShapedType input_type, mlir::ShapedType output_type) {
ArrayRef<int64_t> in_shape = input_type.getShape();
ArrayRef<int64_t> out_shape = output_type.getShape();
// Get the indexes of the non-identity dimensions and the identity dimensions
// in the input shape.
SmallVector<int32_t> input_nonidentity_dims_index_array;
SmallVector<int32_t> input_identity_dims_index_array;
// Since the reshape is trivial, the input and output shapes should have the
// same number of dimensions. And the non-identity dimensions must be in the
// same cyclic order.
for (size_t idx = 0; idx < in_shape.size(); ++idx) {
if (in_shape[idx] != 1) {
input_nonidentity_dims_index_array.push_back(idx);
} else {
input_identity_dims_index_array.push_back(idx);
}
}
// Get the permutation that maps the input shape to the output shape.
SmallVector<int32_t> permutation;
size_t nonidentity_dims_index_poiter = 0;
size_t identity_dims_index_pointer = 0;
for (auto out_dim : out_shape) {
if (out_dim != 1) {
permutation.push_back(
input_nonidentity_dims_index_array[nonidentity_dims_index_poiter++]);
} else {
permutation.push_back(
input_identity_dims_index_array[identity_dims_index_pointer++]);
}
}
return mlir::DenseElementsAttr::get(
RankedTensorType::get(
{static_cast<int>(permutation.size())},
mlir::IntegerType::get(input_type.getContext(), 32)),
llvm::ArrayRef(permutation));
}
// Returns true if the reshape op is equivalent to a transpose op.
// This is true if the reshape op is a trivial reshape op, meaning no change in
// the order of non-identity dimensions.
inline bool IsReshapeEquivalentToTranspose(mlir::ShapedType input_type,
mlir::ShapedType output_type) {
std::vector<int64_t> in_shape{input_type.getShape().vec()};
std::vector<int64_t> out_shape{output_type.getShape().vec()};
// If the reshape changes the number of dimensions so it cannot be interpreted
// as a transpose.
if (in_shape.size() != out_shape.size()) {
return false;
}
in_shape.erase(std::remove(in_shape.begin(), in_shape.end(), 1),
in_shape.end());
out_shape.erase(std::remove(out_shape.begin(), out_shape.end(), 1),
out_shape.end());
return in_shape == out_shape;
}
// Checks if all elements in the constant attribute value are 1.
inline bool IsAllOnesConstant(Attribute value) {
auto values = mlir::cast<DenseElementsAttr>(value).getValues<int32_t>();
return !std::any_of(values.begin(), values.end(),
[](int32_t element_value) { return element_value != 1; });
}
// Checks if all elements in the constant attribute value are non-negative.
inline bool HasNonNegativeValues(Attribute value) {
auto values = mlir::cast<DenseElementsAttr>(value).getValues<APInt>();
return !std::any_of(
values.begin(), values.end(),
[](const APInt& element_value) { return element_value.isNegative(); });
}
// Utility function to get the offset between two dense attribute values.
inline TypedAttr GetOffSet(Attribute begin, Attribute end) {
auto begin_values = mlir::cast<DenseElementsAttr>(begin).getValues<int32_t>();
auto end_values = mlir::cast<DenseElementsAttr>(end).getValues<int32_t>();
SmallVector<int32_t> offsets;
if (begin_values.size() == end_values.size()) {
for (size_t i = 0; i < begin_values.size(); ++i) {
offsets.push_back(end_values[i] - begin_values[i]);
}
}
return mlir::DenseElementsAttr::get(
RankedTensorType::get({static_cast<int>(offsets.size())},
mlir::IntegerType::get(begin.getContext(), 32)),
llvm::ArrayRef(offsets));
}
// Check if the offset between two dense attribute values is non-negative.
inline bool HasNonNegativeOffset(Attribute begin, Attribute end) {
return HasNonNegativeValues(GetOffSet(begin, end));
}
// Return true if the permutation value only swaps the last two dimensions
inline bool AreLastTwoDimsTransposed(Value permutation) {
if (!permutation) return false;
DenseElementsAttr perm_values_attr;
if (!matchPattern(permutation, m_Constant(&perm_values_attr))) return false;
auto perm_values = perm_values_attr.getValues<APInt>();
size_t idx = 0;
for (; idx < perm_values_attr.size() - 2; ++idx) {
if (perm_values[idx].getSExtValue() != idx) return false;
}
return (perm_values[idx].getSExtValue() == perm_values_attr.size() - 1) &&
(perm_values[idx + 1].getSExtValue() == idx);
}
// Gets the new type after transposing the last 2 dimensions.
inline Type TransposeLastTwoDims(Type type) {
auto shaped_type = mlir::dyn_cast<ShapedType>(type);
if (!shaped_type.hasStaticShape() || shaped_type.getRank() < 2) {
return nullptr;
}
int rank = shaped_type.getRank();
if (rank < 2) {
return nullptr;
}
SmallVector<int64_t> new_shape(shaped_type.getShape().begin(),
shaped_type.getShape().end());
std::swap(new_shape[rank - 1], new_shape[rank - 2]);
return shaped_type.clone(new_shape);
}
// Returns a ShapedType for a permutation and the shape of input after
// applying the permutation to the given shape through a transpose.
inline mlir::ShapedType GetTransposedType(
Value input, llvm::ArrayRef<int64_t> permutation_array) {
auto input_type = mlir::cast<ShapedType>(input.getType());
if (permutation_array.size() != input_type.getRank()) {
return nullptr;
}
llvm::SmallVector<int64_t> transposed_shape(permutation_array.size());
for (int64_t i = 0; i < permutation_array.size(); ++i) {
transposed_shape[i] = input_type.getDimSize(permutation_array[i]);
}
auto transposed_type =
RankedTensorType::get(transposed_shape, input_type.getElementType());
return transposed_type;
}
// Return the resultant shape if the shape of the supplied attribute/value is
// expanded by n leading 1s'.
inline SmallVector<int32_t> GetExpandedShape(Value input_val, int n) {
auto input_shape = mlir::cast<ShapedType>(input_val.getType()).getShape();
SmallVector<int32_t> expanded_shape;
expanded_shape.reserve(input_shape.size() + n);
for (int i = 0; i < n; ++i) {
expanded_shape.push_back(1);
}
expanded_shape.insert(expanded_shape.end(), input_shape.begin(),
input_shape.end());
return expanded_shape;
}
// Return the resultant shape as a DenseElementsAttr if the shape of the
// supplied attribute/value is expanded by n leading 1s'.
inline DenseElementsAttr GetExpandedShapeAttr(Value input_val, int n) {
auto expanded_shape = GetExpandedShape(input_val, n);
return mlir::DenseElementsAttr::get(
RankedTensorType::get({static_cast<int>(expanded_shape.size())},
mlir::IntegerType::get(input_val.getContext(), 32)),
llvm::ArrayRef(expanded_shape));
}
// Return the resultant shape type if the shape of the supplied attribute/value
// is expanded by n leading 1s'.
inline mlir::ShapedType GetExpandedShapeType(Value input_val, int n) {
auto expanded_shape = GetExpandedShape(input_val, n);
return RankedTensorType::get(
SmallVector<int64_t>{expanded_shape.begin(), expanded_shape.end()},
mlir::cast<ShapedType>(input_val.getType()).getElementType());
}
// Returns shape of a ranked tensor as a SmallVector.
// Precondition: input_value's is ranked tensor.
// Returns a squeezed shape when `squeeze_leading_ones` is set to true.
inline SmallVector<int32_t> GetShape(Value input_value,
bool squeeze_leading_ones = false) {
auto output_shape =
mlir::dyn_cast<ShapedType>(input_value.getType()).getShape();
SmallVector<int32_t> shape;
shape.reserve(output_shape.size());
bool can_squeeze = true;
for (size_t dim_idx = 0; dim_idx < output_shape.size(); ++dim_idx) {
int64_t dim = output_shape[dim_idx];
if (squeeze_leading_ones && can_squeeze && dim == 1) {
continue;
} else if (can_squeeze && dim != 1) {
can_squeeze = false;
}
shape.push_back(ShapedType::isDynamic(dim) ? -1
: static_cast<int32_t>(dim));
}
return shape;
}
// Returns shape of a ranked tensor as a DenseElementsAttr.
// Precondition: input_value's is ranked tensor.
// Returns a squeezed shape when `squeeze_leading_ones` is set to true.
inline DenseElementsAttr GetShapeAttr(Value input_value,
bool squeeze_leading_ones = false) {
SmallVector<int32_t> shape = GetShape(input_value, squeeze_leading_ones);
return mlir::DenseElementsAttr::get(
RankedTensorType::get(
{static_cast<int>(shape.size())},
mlir::IntegerType::get(input_value.getContext(), 32)),
llvm::ArrayRef(shape));
}
// Returns the value of a constant attribute as an int array, if the value is
// not a constant, returns an error status.
inline absl::StatusOr<SmallVector<int32_t>> GetValueAsIntArray(Value value) {
DenseElementsAttr values_const_attr;
if (!matchPattern(value, m_Constant(&values_const_attr))) {
return absl::InvalidArgumentError("Value is not a constant.");
}
SmallVector<int32_t> values;
for (const auto& value : values_const_attr.getValues<APInt>()) {
values.push_back(value.getSExtValue());
}
return values;
}
////////////////////////////////////////////////////////////////////////////////
///////////////// OP BROADCASTING UTILITIES ////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Returns whether the resultant type of any broadcastable operation with
// operands `a` and `b` matches `expected_output`. Returns false if `a` is not
// broadcast-compatible with `b`.
inline bool OperandsBroadcastToOutputType(Type a, Type b,
Type expected_output) {
Type output_element_type =
mlir::cast<ShapedType>(expected_output).getElementType();
Type broadcasted_type =
OpTrait::util::getBroadcastedType(a, b, output_element_type);
return broadcasted_type != Type() && broadcasted_type == expected_output;
}
// Returns int, float or complex DenseElementsAttr with scalar shape with the
// given element type and the integer value.
template <typename T>
DenseElementsAttr GetScalarOfType(Type ty, T raw_value) {
RankedTensorType scalar_ty = RankedTensorType::get({}, ty);
if (auto float_ty = mlir::dyn_cast<FloatType>(ty)) {
FloatAttr attr = FloatAttr::get(float_ty, raw_value);
return DenseElementsAttr::get(scalar_ty, attr);
} else if (auto int_ty = mlir::dyn_cast<IntegerType>(ty)) {
IntegerAttr attr = IntegerAttr::get(int_ty, raw_value);
return DenseElementsAttr::get(scalar_ty, attr);
} else if (auto complex_ty = mlir::dyn_cast<ComplexType>(ty)) {
Type complex_element_ty = complex_ty.getElementType();
if (complex_element_ty.isF32()) {
return DenseElementsAttr::get(
scalar_ty, static_cast<std::complex<float>>(raw_value));
} else if (complex_element_ty.isF64()) {
return DenseElementsAttr::get(
scalar_ty, static_cast<std::complex<double>>(raw_value));
}
}
llvm_unreachable("unsupported type");
}
// Checks if reduction axes and broadcast axes are disjoint.
// Broadcast axes are derived by comparing the shape of `input_val` to the shape
// represented by `target_shape_attr` according to standard broadcasting rules.
// Returns true if the sets of axes are disjoint, false otherwise or on error.
inline bool AreBroadcastAndReductionAxesIndependent(
mlir::Value input_val, const mlir::Attribute& indices_attr,
const mlir::Attribute& target_shape_attr) {
// 1. Get input type and shape.
// Use llvm::dyn_cast for safer casting.
auto ranked_input_type =
llvm::dyn_cast<mlir::RankedTensorType>(input_val.getType());
if (!ranked_input_type) {
// Consider logging or error emission if builder context is
// available/needed.
return false; // Expect ranked type.
}
llvm::ArrayRef<int64_t> input_shape = ranked_input_type.getShape();
const int64_t input_rank = ranked_input_type.getRank();
// 2. Validate and extract reduction axes.
// Use llvm::dyn_cast for safer casting.
auto indices = llvm::dyn_cast<mlir::DenseElementsAttr>(indices_attr);
if (!indices || !indices.getElementType().isIntOrIndex()) {
return false; // Invalid indices attribute.
}
// Use std::set for efficient storage and lookup of axes.
std::set<int64_t> reduction_axes_set;
if (!indices.empty()) { // Only process if there are reduction axes.
if (input_rank == 0) {
// It's invalid to specify reduction axes for a scalar (rank 0) input.
return false;
}
// Iterate using range-based for loop and structured binding (if applicable)
// or direct value access.
for (const mlir::APInt& axis_val : indices.getValues<mlir::APInt>()) {
int64_t axis =
axis_val.getSExtValue(); // Use sign extension for neg axes.
// Normalize axis and check bounds.
if (axis < -input_rank || axis >= input_rank) {
return false; // Axis out of bounds.
}
if (axis < 0) {
axis += input_rank; // Convert negative axis to positive.
}
reduction_axes_set.insert(axis);
}
}
// If there are no reduction axes, they are trivially independent of any
// broadcast axes.
if (reduction_axes_set.empty()) {
return true;
}
// 3. Validate and extract target shape for broadcast.
// Use llvm::dyn_cast for safer casting.
auto target_shape_value_attr =
llvm::dyn_cast<mlir::DenseElementsAttr>(target_shape_attr);
if (!target_shape_value_attr ||
!target_shape_value_attr.getElementType().isIntOrIndex()) {
return false; // Invalid target shape attribute.
}
// Use llvm::SmallVector for efficient shape storage.
llvm::SmallVector<int64_t, 4> target_shape_vec;
target_shape_vec.reserve(
target_shape_value_attr.getNumElements()); // Pre-allocate
for (const mlir::APInt& shape_val :
target_shape_value_attr.getValues<mlir::APInt>()) {
// Assuming shape dimensions should be non-negative, consider getZExtValue.
// However, getSExtValue is safe if intermediate calculations handle signs.
target_shape_vec.push_back(shape_val.getSExtValue());
}
// Use llvm::ArrayRef for safe, non-owning view of the shape vector.
llvm::ArrayRef<int64_t> target_shape = target_shape_vec;
const int64_t target_rank = target_shape.size();
// 4. Determine broadcast axes based on standard broadcasting rules.
std::set<int64_t> broadcast_axes_set;
const int64_t max_rank = std::max(input_rank, target_rank);
// Iterate through dimensions, aligning from the right (trailing dimensions).
for (int64_t i = 0; i < max_rank; ++i) {
// Calculate indices relative to the end of the shape arrays.
const int64_t input_dim_idx = input_rank - 1 - i;
const int64_t target_dim_idx = target_rank - 1 - i;
// Treat dimensions missing due to lower rank as having size 1.
const int64_t input_dim =
(input_dim_idx >= 0) ? input_shape[input_dim_idx] : 1;
const int64_t target_dim =
(target_dim_idx >= 0) ? target_shape[target_dim_idx] : 1;
// Check for incompatible shapes (dimensions differ and neither is 1).
// This indicates an invalid broadcast according to NumPy rules.
if (input_dim != target_dim && input_dim != 1 && target_dim != 1) {
// Consider if the specific broadcast op allows other behaviors (e.g.,
// -1). For standard rules, this is an incompatibility.
return false;
}
// An axis in the *input* tensor is involved in broadcasting if its size is
// 1 and the corresponding target dimension size is greater than 1.
if (input_dim == 1 && target_dim > 1) {
// Ensure the axis index is valid for the input tensor's rank.
if (input_dim_idx >= 0) {
broadcast_axes_set.insert(input_dim_idx);
}
// Note: If input_dim_idx < 0, broadcasting occurs due to rank difference,
// but it doesn't correspond to an axis *within* the original input
// tensor.
}
}
// 5. Check for intersection between the set of reduction axes and the set of
// broadcast axes derived above.
for (int64_t reduction_axis : reduction_axes_set) {
if (broadcast_axes_set.count(reduction_axis)) {
// Found an axis that is present in both sets.
return false;
}
}
// 6. No overlapping axes were found.
return true;
}
} // namespace TFL
} // namespace mlir
#endif // TENSORFLOW_COMPILER_MLIR_LITE_UTILS_UTILS_H_