[C++] Correctly handle scalar inputs in reduction ops, enforce Transpose perm attribute matches input rank. (#17041)

### Description

This PR addresses the following issues related to the use of the
functions in ORT.

- https://github.com/microsoft/onnxruntime/issues/16492
- https://github.com/microsoft/onnxruntime/issues/16997
- https://github.com/microsoft/onnxruntime/issues/14678
- Partially addresses
https://github.com/microsoft/onnxruntime/issues/16813

The optimization case for a scalar input did not correctly recognize it
as such.
Transpose kernel assumed that `perm` attribute would always match input
tensor rank.

### Motivation and Context
The issues causes crashes and erratic behavior.
This commit is contained in:
Dmitri Smirnov 2023-08-08 14:47:01 -07:00 committed by GitHub
parent fb11c67368
commit c424e42594
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 53 additions and 9 deletions

View file

@ -1008,6 +1008,8 @@ ONNX_NAMESPACE::TensorProto TensorToTensorProto(const Tensor& tensor, const std:
common::Status ConstantNodeProtoToTensorProto(const ONNX_NAMESPACE::NodeProto& node,
const Path& model_path,
ONNX_NAMESPACE::TensorProto& tensor, const std::string& tensor_name) {
ORT_RETURN_IF_NOT(node.attribute_size() > 0, "Constant node: ", node.name(), " has no data attributes");
const AttributeProto& constant_attribute = node.attribute(0);
switch (constant_attribute.type()) {

View file

@ -595,10 +595,14 @@ FastReduceKind OptimizeShapeForFastReduce(gsl::span<const int64_t> input_shape,
TensorShapeVector& fast_axes,
bool keep_dims, bool noop_with_empty_axes) {
if (input_shape.empty()) {
fast_shape.assign(input_shape.begin(), input_shape.end());
fast_output_shape = fast_shape;
fast_axes.assign(reduced_axes.begin(), reduced_axes.end());
return FastReduceKind::kNone;
fast_shape.clear();
fast_output_shape.clear();
// XXX: Should we enforce the absence of the axes in the scalar input case?
// The operator spec refers to Numpy which returns error because axes can not possibly contain any valid
// value in scalar case, but pytorch simply ignores it.
// ORT_ENFORCE(reduced_axes.empty(), "With scalar input shape, axis can not contain valid values");
fast_axes.clear();
return FastReduceKind::kEmpty;
}
InlinedHashSet<int64_t> axes;

View file

@ -62,20 +62,24 @@ class TransposeBase {
Status ComputeOutputShape(const Tensor& X, TensorShapeVector& output_dims, InlinedVector<size_t>& default_perm,
const InlinedVector<size_t>*& p_perm) const {
size_t rank = X.Shape().NumDimensions();
const size_t rank = X.Shape().NumDimensions();
const auto& input_dims = X.Shape().GetDims();
// Determine permutation to use:
// If no permutation was specified in the attributes, the default is [rank-1, ..., 0]
default_perm.resize(rank);
if (perm_specified_)
p_perm = &perm_;
else {
// Determine permutation to use:
// If no permutation was specified in the attributes, the default is [rank-1, ..., 0]
default_perm.resize(rank);
for (size_t i = 0; i < rank; ++i) default_perm[i] = rank - i - 1;
p_perm = &default_perm;
}
if (p_perm->size() != rank) {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
"perm size: ", p_perm->size(), " does not match input rank: ", std::to_string(rank));
}
// Determine shape of output
output_dims.resize(rank);
for (size_t i = 0; i < rank; i++) {

View file

@ -3215,6 +3215,24 @@ TEST(ReductionOpTest, OptimizeShapeForFastReduce_ReduceDimWithZero1) {
ASSERT_EQ(fast_axes, expected_fast_axes);
}
TEST(ReductionOpTest, OptimizeShapeForFastReduce_ReduceDimWithScalarInputAxesPresent) {
FastReduceKind fast_kind;
TensorShapeVector fast_shape, fast_output_shape, fast_axes;
TensorShapeVector expected_fast_shape, expected_fast_output_shape, expected_fast_axes;
// R - keep_dims=1 - noop=false
fast_kind = OptimizeShapeForFastReduce(
EmptySpan<int64_t>(), AsSpan<int64_t>({1, 2, 3}),
fast_shape, fast_output_shape, fast_axes, true);
expected_fast_shape = {};
expected_fast_axes = {};
expected_fast_output_shape = {};
ASSERT_EQ(fast_kind, FastReduceKind::kEmpty);
ASSERT_EQ(fast_output_shape, expected_fast_output_shape);
ASSERT_EQ(fast_shape, expected_fast_shape);
ASSERT_EQ(fast_axes, expected_fast_axes);
}
TEST(ReductionOpTest, OptimizeShapeForFastReduce_ReduceDimWithZero1b) {
FastReduceKind fast_kind;
TensorShapeVector fast_shape, fast_output_shape, fast_axes;

View file

@ -24,6 +24,22 @@ TEST(TransposeOpTest, IsTransposeReshapeTest) {
ASSERT_FALSE(IsTransposeReshape(perm, input_dims));
}
// Negative test, making sure it fails
TEST(TransposeOpTest, PermRankDoesNotMatchTensorRank) {
const std::vector<float> input_vals(1 * 2 * 3 * 4, 0.f);
const std::vector<int64_t> perm{0, 2, 1};
OpTester test("Transpose");
test.AddAttribute("perm", perm);
test.AddInput<float>("X", {1, 2, 3, 4}, input_vals);
// Output is not very relevant
test.AddOutput<float>("Y", {1, 3, 2, 4}, input_vals);
// This failure comes from shape inference, because in this case it knows the input dims.
// But in the real world, the model can supply different input dims at runtime.
test.Run(OpTester::ExpectResult::kExpectFailure,
"Node:node1 Output:Y [ShapeInferenceError] Mismatch between number of source and target dimensions. Source=3 Target=4");
}
// Some of the tests can't run on TensorrtExecutionProvider because of errors.
// Those tests will fallback to other EPs.