Fix slice upstream - Incompatible dimensions (#16818)

### Fix slice upstream - (MatMul) [ShapeInferenceError] Incompatible
dimensions

```
     2023-07-22 14:58:16.918478478 [I:onnxruntime:Default, constant_sharing.cc:256 ApplyImpl] Total shared scalar initializer count: 10
        2023-07-22 14:58:16.919494252 [W:onnxruntime:Default, graph.cc:108 MergeShapeInfo] Error merging shape info for output. 'onnx::Cast_424' source:{-1,31,-1,-1} target:{-1,32,-1,-1}. Falling back to lenient merge.
        2023-07-22 14:58:16.921014114 [W:onnxruntime:Default, graph.cc:108 MergeShapeInfo] Error merging shape info for output. 'onnx::MatMul_425' source:{-1,31,-1,-1} target:{-1,32,-1,-1}. Falling back to lenient merge.

Traceback (most recent call last):
  File "examples/onnxruntime/training/language-modeling/run_clm.py", line 594, in <module>
    main()
  File "examples/onnxruntime/training/language-modeling/run_clm.py", line 542, in main
    train_result = trainer.train(resume_from_checkpoint=checkpoint)
  File "/bert_ort/pengwa/optimum/optimum/onnxruntime/trainer.py", line 454, in train
    return inner_training_loop(
  File "/bert_ort/pengwa/optimum/optimum/onnxruntime/trainer.py", line 755, in _inner_training_loop
    tr_loss_step = self.training_step(model, inputs)
  File "/bert_ort/pengwa/py38/lib/python3.8/site-packages/transformers/trainer.py", line 2735, in training_step
    loss = self.compute_loss(model, inputs)
  File "/bert_ort/pengwa/optimum/optimum/onnxruntime/trainer.py", line 363, in compute_loss
    return model_with_loss(dict_inputs, return_outputs)
  File "/bert_ort/pengwa/py38/lib/python3.8/site-packages/torch/nn/modules/module.py", line 1110, in _call_impl
    return forward_call(*input, **kwargs)
  File "/bert_ort/pengwa/py38/lib/python3.8/site-packages/deepspeed/utils/nvtx.py", line 15, in wrapped_fn
    ret_val = func(*args, **kwargs)
  File "/bert_ort/pengwa/py38/lib/python3.8/site-packages/deepspeed/runtime/engine.py", line 1724, in forward
    loss = self.module(*inputs, **kwargs)
  File "/bert_ort/pengwa/py38/lib/python3.8/site-packages/torch/nn/modules/module.py", line 1110, in _call_impl
    return forward_call(*input, **kwargs)
  File "/bert_ort/pengwa/py38/lib/python3.8/site-packages/onnxruntime/training/ortmodule/_utils.py", line 384, in _forward
    return ortmodule._torch_module.forward(*inputs, **kwargs)
  File "/bert_ort/pengwa/py38/lib/python3.8/site-packages/onnxruntime/training/ortmodule/_utils.py", line 364, in _forward
    return torch_module_ort._execution_manager(torch_module_ort.is_training()).forward(*inputs, **kwargs)
  File "/bert_ort/pengwa/py38/lib/python3.8/site-packages/onnxruntime/training/ortmodule/_training_manager.py", line 345, in forward
    self._fallback_manager.handle_exception(
  File "/bert_ort/pengwa/py38/lib/python3.8/site-packages/onnxruntime/training/ortmodule/_fallback.py", line 157, in handle_exception
    raise exception
  File "/bert_ort/pengwa/py38/lib/python3.8/site-packages/onnxruntime/training/ortmodule/_training_manager.py", line 280, in forward
    self._build_graph(graph_transformer_config)
  File "/bert_ort/pengwa/py38/lib/python3.8/site-packages/onnxruntime/training/ortmodule/_logger.py", line 218, in wrapper
    result = func(graph_execution_manager, *args, **kwargs)
  File "/bert_ort/pengwa/py38/lib/python3.8/site-packages/onnxruntime/training/ortmodule/_training_manager.py", line 360, in _build_graph
    super()._build_graph(graph_transformer_config)
  File "/bert_ort/pengwa/py38/lib/python3.8/site-packages/onnxruntime/training/ortmodule/_graph_execution_manager.py", line 186, in _build_graph
    self._graph_builder.build(config)
RuntimeError: /bert_ort/pengwa/onnxruntime/orttraining/orttraining/python/orttraining_pybind_state.cc:823 onnxruntime::python::addObjectMethodsForTraining(pybind11::module&, onnxruntime::python::ExecutionProviderRegistrationFn)::<lambda(onnxruntime::training::OrtModuleGraphBuilder*, const onnxruntime::training::TrainingGraphTransformerConfiguration&)> [ONNXRuntimeError] : 1 : FAIL : Node (MatMul_403) Op (MatMul) [ShapeInferenceError] Incompatible dimensions

 
```

Missed using `axis` attribute for `Slice` op, so change to use `axes`
inputs instead.

### Motivation and Context
<!-- - Why is this change required? What problem does it solve?
- If it fixes an open issue, please link to the issue here. -->
This commit is contained in:
pengwa 2023-07-25 08:21:46 +08:00 committed by GitHub
parent b0279b14d8
commit f2c0470436
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 397 additions and 89 deletions

View file

@ -184,7 +184,9 @@ NodeArg* CreateInitializerFromVector(Graph& graph,
total_count *= dim;
}
ORT_ENFORCE(total_count == static_cast<int64_t>(values.size()));
ORT_ENFORCE(total_count == static_cast<int64_t>(values.size()),
"The total count of dims does not match the size of values. ",
"total_count: ", total_count, " values.size(): ", values.size());
const_tensor.set_raw_data(values.data(), values.size() * sizeof(int64_t));
return &graph_utils::AddInitializer(graph, const_tensor);

View file

@ -138,20 +138,65 @@ SliceInfo UpStreamGatherGraphTransformer::PropagateSlicingForInput(
std::to_string(!info.is_scalar_slice));
InlinedVector<NodeArg*> input_args;
input_args.reserve(slice_node.InputDefs().size());
input_args.resize(slice_node.InputDefs().size());
int axis_input_index = -1; // -1 means axis is passed in attribute.
if (std::holds_alternative<int>(info.axis_attr_name_or_input_index)) {
axis_input_index = std::get<int>(info.axis_attr_name_or_input_index);
}
auto create_axes_input = [&info, new_axis, &graph]() -> NodeArg* {
InlinedVector<int64_t> dims;
if (info.rank_of_axis_value == 1) {
dims.push_back(1);
}
return CreateInitializerFromVector(graph, dims, {new_axis}, graph.GenerateNodeArgName("axes"));
};
// The first slice op's data input should be current_node's current_node_input_index-th input.
// For some cases when rank changes, slice op's slice input should also be adapted.
input_args.push_back(current_node.MutableInputDefs()[current_node_input_index]);
for (size_t i = 1; i < slice_node.InputDefs().size(); ++i) {
input_args.push_back(slice_node.MutableInputDefs()[i]);
int i = 0;
for (; i < static_cast<int>(slice_node.InputDefs().size()); ++i) {
if (i == info.GetDataInputIndex()) {
input_args[i] = current_node.MutableInputDefs()[current_node_input_index];
} else if (axis_input_index != -1 && i == axis_input_index) {
if (info.non_negative_axis == new_axis) {
input_args[i] = slice_node.MutableInputDefs()[i];
} else {
input_args[i] = create_axes_input();
}
} else {
input_args[i] = slice_node.MutableInputDefs()[i];
}
}
// It is possible axes input is null.
if (axis_input_index != -1 && info.non_negative_axis != new_axis) {
for (; i <= axis_input_index; ++i) {
if (i == axis_input_index) {
input_args.push_back(create_axes_input());
} else {
NodeArg& empty_input = graph.GetOrCreateNodeArg("", nullptr);
input_args.push_back(&empty_input);
}
}
}
// Update the axis attribute if new_axis is not the same as the original slicing axis (which happens when data
// layout got changed by Transpose or Reshape ops)
onnxruntime::NodeAttributes attributes = slice_node.GetAttributes();
if (info.non_negative_axis != new_axis) {
attributes[info.axis_attr_name] =
ONNX_NAMESPACE::MakeAttribute(info.axis_attr_name, static_cast<int64_t>(new_axis));
if (axis_input_index == -1 && info.non_negative_axis != new_axis) {
std::string attr_name = std::get<std::string>(info.axis_attr_name_or_input_index);
if (info.rank_of_axis_value == 0) {
attributes[attr_name] =
ONNX_NAMESPACE::MakeAttribute(attr_name, static_cast<int64_t>(new_axis));
} else if (info.rank_of_axis_value == 1) {
attributes[attr_name] =
ONNX_NAMESPACE::MakeAttribute(attr_name, std::vector<int64_t>{static_cast<int64_t>(new_axis)});
} else {
ORT_THROW("Unexpected rank of axis attribute value: " + std::to_string(info.rank_of_axis_value));
}
}
InlinedVector<NodeArg*> output_args;
@ -183,7 +228,8 @@ SliceInfo UpStreamGatherGraphTransformer::PropagateSlicingForInput(
auto new_slice_out_arg = new_slice_node->MutableOutputDefs()[new_slice_output_index_to_connect];
UpdateSliceOutputShape(*new_slice_out_arg, new_axis, info.output_dim_on_axis);
auto new_slice_info = SliceInfo(graph, new_slice_node, info.is_scalar_slice, info.axis_attr_name, new_axis);
auto new_slice_info = SliceInfo(graph, new_slice_node, info.is_scalar_slice, info.axis_attr_name_or_input_index,
new_axis, info.rank_of_axis_value);
new_slice_info.entry_node_name = info.entry_node_name;
new_slice_info.entry_slice_arg_name = info.entry_slice_arg_name;
return new_slice_info;
@ -263,7 +309,8 @@ std::optional<SliceInfo> IsSupportedGatherND(Graph& graph, Node& node,
return std::nullopt;
}
return SliceInfo(graph, &node, false, "batch_dims", static_cast<int>(batch_dims), true);
return SliceInfo(graph, &node, false, "batch_dims", static_cast<int>(batch_dims),
0 /* rank of axis attribute value */, true);
}
std::optional<SliceInfo> IsSupportedGather(Graph& graph, Node& node,
@ -304,7 +351,7 @@ std::optional<SliceInfo> IsSupportedGather(Graph& graph, Node& node,
}
}
return SliceInfo(graph, &node, dim_size == 0, "axis", axis, true);
return SliceInfo(graph, &node, dim_size == 0, "axis", axis, 0 /* rank of axis attribute value */, true);
}
std::optional<SliceInfo> IsSupportedShrunkenGather(Graph& graph, Node& node,
@ -342,7 +389,7 @@ std::optional<SliceInfo> IsSupportedShrunkenGather(Graph& graph, Node& node,
return std::nullopt;
}
return SliceInfo(graph, &node, false /*is_slice_scalar*/, "axis", axis, true);
return SliceInfo(graph, &node, false /*is_slice_scalar*/, "axis", axis, 0 /* rank of axis attribute value */, true);
}
/**
@ -366,34 +413,37 @@ std::optional<SliceInfo> IsSupportedSlice(Graph& graph, Node& node,
const NodeArg* axes_input = node.InputDefs().size() > 3 ? node.InputDefs()[3] : nullptr;
if (data_input->Shape() == nullptr || starts_input->Shape() == nullptr || ends_input->Shape() == nullptr ||
(axes_input && axes_input->Shape() == nullptr)) {
(axes_input && axes_input->Exists() && axes_input->Shape() == nullptr)) {
LOG_DEBUG_INFO(logger, "Skip Slice node " + node.Name() + " due to undefined shape.");
return std::nullopt;
}
// Make sure starts/ends/axes/steps are all 1D tensors, since we only support single-dimension slicing.
if (starts_input->Shape()->dim_size() != 1 || ends_input->Shape()->dim_size() != 1 ||
(axes_input && axes_input->Shape()->dim_size() != 1)) {
(axes_input && axes_input->Exists() && axes_input->Shape()->dim_size() != 1)) {
LOG_DEBUG_INFO(logger, "Skip Slice node " + node.Name() + " due to unsupported dim size: " +
std::to_string(starts_input->Shape()->dim_size()) + ", " +
std::to_string(ends_input->Shape()->dim_size()) + ", " +
std::to_string(axes_input ? axes_input->Shape()->dim_size() : 0));
std::to_string(axes_input && axes_input->Exists() ? axes_input->Shape()->dim_size() : 0));
return std::nullopt;
}
// Try to parse the 'axes' value.
int axis = 0;
if (axes_input) {
if (axes_input && axes_input->Exists()) {
InlinedVector<int64_t> axes_values;
if (!graph_utils::IsConstantInitializer(graph, axes_input->Name()) ||
!optimizer_utils::AppendTensorFromInitializer(graph, *axes_input, axes_values, true) ||
axes_values.size() != 1) {
LOG_DEBUG_INFO(logger, "Skip Slice node " + node.Name() + " due to unsupported axes value.");
return std::nullopt;
}
axis = static_cast<int>(axes_values[0]);
} else {
// If 'axes' is not specified, then it is [0, .., r-1], so we force data rank to be 1.
if (data_input->Shape()->dim_size() != 1) {
LOG_DEBUG_INFO(logger, "Skip Slice node " + node.Name() + " due to unsupported data rank: " +
std::to_string(data_input->Shape()->dim_size()));
return std::nullopt;
}
}
@ -401,7 +451,8 @@ std::optional<SliceInfo> IsSupportedSlice(Graph& graph, Node& node,
if (axis < 0)
axis += data_input->Shape()->dim_size();
return SliceInfo(graph, &node, false /*is_slice_scalar*/, "axis", axis, true);
return SliceInfo(graph, &node, false /*is_slice_scalar*/, 3 /* axis input index */, axis,
1 /* rank of axes value */, true);
}
} // namespace

View file

@ -25,11 +25,21 @@ struct SliceInfo : public UpstreamOperatorInfoBase {
public:
SliceInfo(const Graph& graph, Node* slice_node,
bool is_slice_scalar,
const std::string& slice_axis_attr_name,
std::variant<std::string, int> axis_name_or_index,
int slice_axis,
int rank_of_axis,
bool is_entry_node_ptr = false)
: UpstreamOperatorInfoBase(slice_node, is_entry_node_ptr), is_scalar_slice(is_slice_scalar) {
axis_attr_name = slice_axis_attr_name;
axis_attr_name_or_input_index = axis_name_or_index;
rank_of_axis_value = rank_of_axis;
if (std::holds_alternative<int>(axis_name_or_index)) {
int axis_input_index = std::get<int>(axis_name_or_index);
ORT_ENFORCE(axis_input_index >= 0, "Axis input index is invalid");
}
ORT_ENFORCE(rank_of_axis_value == 0 || rank_of_axis_value == 1, "Rank of axis value is invalid: " +
std::to_string(rank_of_axis_value));
const NodeArg* input = node_ptr->InputDefs()[kSliceDataInputIndex_];
const NodeArg* output = node_ptr->OutputDefs()[kSliceOutputIndex_];
@ -65,8 +75,16 @@ struct SliceInfo : public UpstreamOperatorInfoBase {
}
bool is_scalar_slice; // whether the slice is a scalar, if it is after Gather, the rank will be reduced by 1.
std::string axis_attr_name;
// The index of the input that contains the axis value. If it is a string, then axis will be treated as an attribute.
std::variant<std::string, int> axis_attr_name_or_input_index;
int non_negative_axis; // The axis to slice on
// The rank of value for axis attribute. For example, for Gather, its axis attribute is a scalar, so the rank is 0.
// For Slice, its axes attribute is a 1D tensor, so the rank is 1.
int rank_of_axis_value;
std::string entry_slice_arg_name;
int input_rank; // rank of the Gather data input tensor

View file

@ -18,6 +18,7 @@
#include "core/common/span_utils.h"
#include "core/framework/data_types.h"
#include "core/framework/ort_value.h"
#include "core/framework/tensorprotoutils.h"
#include "core/graph/graph_utils.h"
#include "core/graph/graph_viewer.h"
#include "core/graph/model.h"
@ -1597,93 +1598,329 @@ Test graph includes multiple equivalent subgraphs as below.
Add an Identity node because currently we don't allow Slice generates graph output.
*/
TEST(ComputeOptimizerTests, SliceElementwiseOps_PropagationOnTwoBranches) {
const logging::Logger* logger = &logging::LoggingManager::DefaultLogger();
InlinedVector<int64_t> starts_indices;
auto pre_graph_checker = [&starts_indices](Graph& graph) -> Status {
auto op_count_pre = CountOpsInGraph(graph);
TEST_RETURN_IF_NOT(op_count_pre.size() == 3U);
TEST_RETURN_IF_NOT(op_count_pre["Add"] == 1);
TEST_RETURN_IF_NOT(op_count_pre["Slice"] == 1);
TEST_RETURN_IF_NOT(op_count_pre["Identity"] == 1);
// 0: no input, 1: has input, 2: empty input
std::vector<std::tuple<std::optional<int>, std::vector<int64_t>, int, int, bool>> has_axes_and_has_steps_pairs{
{std::nullopt, {4, 32, 256}, 0, 0, false}, // {axis, data_shape, has_axes, has_steps, expected to propagate}
{1, {4, 32, 256}, 1, 0, true},
{1, {4, 32, 256}, 1, 1, true},
{1, {4, 32, 256}, 1, 2, true},
{std::nullopt, {4, 32, 256}, 2, 0, false},
{std::nullopt, {4, 32, 256}, 2, 1, false},
{std::nullopt, {4, 32, 256}, 2, 2, false},
for (Node& node : graph.Nodes()) {
if (node.OpType() == "Slice") {
TEST_RETURN_IF_NOT(starts_indices.empty());
constexpr bool require_constant = true;
NodeArg* initializer_node_arg = graph.GetNodeArg(node.InputDefs()[1]->Name());
TEST_RETURN_IF_NOT(optimizer_utils::AppendTensorFromInitializer(graph, *initializer_node_arg, starts_indices,
require_constant));
}
}
return Status::OK();
{std::nullopt, {256}, 0, 0, true},
{0, {256}, 1, 0, true},
{0, {256}, 1, 1, true},
{0, {256}, 1, 2, true},
{std::nullopt, {256}, 2, 0, true},
{std::nullopt, {256}, 2, 1, true},
{std::nullopt, {256}, 2, 2, true},
};
auto post_graph_checker = [&starts_indices](Graph& graph) {
auto op_count_post = CountOpsInGraph(graph);
TEST_RETURN_IF_NOT(op_count_post.size() == 3U);
TEST_RETURN_IF_NOT(op_count_post["Add"] == 1);
TEST_RETURN_IF_NOT(op_count_post["Slice"] == 2);
TEST_RETURN_IF_NOT(op_count_post["Identity"] == 1);
for (auto p : has_axes_and_has_steps_pairs) {
std::optional<int> axis = std::get<0>(p);
std::vector<int64_t> data_shape = std::get<1>(p);
int has_axes = std::get<2>(p);
int has_steps = std::get<3>(p);
bool expected_to_propagate = std::get<4>(p);
for (Node& node : graph.Nodes()) {
if (node.OpType() == "Add") {
const auto& input_defs = node.InputDefs();
const logging::Logger* logger = &logging::LoggingManager::DefaultLogger();
InlinedVector<int64_t> starts_indices;
auto pre_graph_checker = [&starts_indices](Graph& graph) -> Status {
auto op_count_pre = CountOpsInGraph(graph);
TEST_RETURN_IF_NOT(op_count_pre.size() == 3U);
TEST_RETURN_IF_NOT(op_count_pre["Add"] == 1);
TEST_RETURN_IF_NOT(op_count_pre["Slice"] == 1);
TEST_RETURN_IF_NOT(op_count_pre["Identity"] == 1);
{
auto producer_node = graph.GetProducerNode(input_defs[0]->Name());
TEST_RETURN_IF_NOT(producer_node != nullptr);
TEST_RETURN_IF_NOT(producer_node->OpType() == "Slice");
InlinedVector<int64_t> values;
for (Node& node : graph.Nodes()) {
if (node.OpType() == "Slice") {
TEST_RETURN_IF_NOT(starts_indices.empty());
constexpr bool require_constant = true;
NodeArg* initializer_node_arg = graph.GetNodeArg(producer_node->InputDefs()[1]->Name());
TEST_RETURN_IF_NOT(optimizer_utils::AppendTensorFromInitializer(graph, *initializer_node_arg, values,
NodeArg* initializer_node_arg = graph.GetNodeArg(node.InputDefs()[1]->Name());
TEST_RETURN_IF_NOT(optimizer_utils::AppendTensorFromInitializer(graph, *initializer_node_arg, starts_indices,
require_constant));
for (size_t i = 0; i < values.size(); i++) {
TEST_RETURN_IF_NOT(values[i] == starts_indices[i]);
}
}
}
return Status::OK();
};
{
auto producer_node = graph.GetProducerNode(input_defs[1]->Name());
TEST_RETURN_IF_NOT(producer_node != nullptr);
TEST_RETURN_IF_NOT(producer_node->OpType() == "Slice");
auto post_graph_checker = [&starts_indices, expected_to_propagate](Graph& graph) {
auto op_count_post = CountOpsInGraph(graph);
TEST_RETURN_IF_NOT(op_count_post.size() == 3U);
TEST_RETURN_IF_NOT(op_count_post["Add"] == 1);
if (expected_to_propagate) {
TEST_RETURN_IF_NOT(op_count_post["Slice"] == 2);
} else {
TEST_RETURN_IF_NOT(op_count_post["Slice"] == 1);
}
TEST_RETURN_IF_NOT(op_count_post["Identity"] == 1);
InlinedVector<int64_t> values;
constexpr bool require_constant = true;
NodeArg* initializer_node_arg = graph.GetNodeArg(producer_node->InputDefs()[1]->Name());
TEST_RETURN_IF_NOT(optimizer_utils::AppendTensorFromInitializer(graph, *initializer_node_arg, values, require_constant));
for (size_t i = 0; i < values.size(); i++) {
TEST_RETURN_IF_NOT(values[i] == starts_indices[i]);
for (Node& node : graph.Nodes()) {
if (node.OpType() == "Add") {
const auto& input_defs = node.InputDefs();
{
auto producer_node = graph.GetProducerNode(input_defs[0]->Name());
if (expected_to_propagate) {
TEST_RETURN_IF_NOT(producer_node != nullptr);
TEST_RETURN_IF_NOT(producer_node->OpType() == "Slice");
InlinedVector<int64_t> values;
constexpr bool require_constant = true;
NodeArg* initializer_node_arg = graph.GetNodeArg(producer_node->InputDefs()[1]->Name());
TEST_RETURN_IF_NOT(optimizer_utils::AppendTensorFromInitializer(graph, *initializer_node_arg, values,
require_constant));
for (size_t i = 0; i < values.size(); i++) {
TEST_RETURN_IF_NOT(values[i] == starts_indices[i]);
}
} else {
TEST_RETURN_IF_NOT(producer_node == nullptr);
}
}
{
auto producer_node = graph.GetProducerNode(input_defs[1]->Name());
if (expected_to_propagate) {
TEST_RETURN_IF_NOT(producer_node != nullptr);
TEST_RETURN_IF_NOT(producer_node->OpType() == "Slice");
InlinedVector<int64_t> values;
constexpr bool require_constant = true;
NodeArg* initializer_node_arg = graph.GetNodeArg(producer_node->InputDefs()[1]->Name());
TEST_RETURN_IF_NOT(optimizer_utils::AppendTensorFromInitializer(graph, *initializer_node_arg, values, require_constant));
for (size_t i = 0; i < values.size(); i++) {
TEST_RETURN_IF_NOT(values[i] == starts_indices[i]);
}
} else {
TEST_RETURN_IF_NOT(producer_node == nullptr);
}
}
}
}
}
return Status::OK();
return Status::OK();
};
auto build_test_case = [has_axes, has_steps, &data_shape, axis](ModelTestBuilder& builder) {
auto* input1_arg = builder.MakeInput<int64_t>(data_shape);
auto* input2_arg = builder.MakeInput<int64_t>(data_shape);
auto* add_out = builder.MakeIntermediate();
builder.AddNode("Add", {input1_arg, input2_arg}, {add_out});
auto* starts_initializer = builder.MakeInitializer<int64_t>({1}, {0});
auto* ends_initializer = builder.MakeInitializer<int64_t>({1}, {-1});
std::vector<NodeArg*> slice_inputs;
slice_inputs = {add_out, starts_initializer, ends_initializer};
NodeArg* axes_initializer = nullptr;
NodeArg* steps_initializer = nullptr;
if (has_axes == 0 && has_steps == 0) {
// nothing
} else if (has_axes == 1 && has_steps == 0) {
axes_initializer = builder.MakeInitializer<int64_t>({1}, {axis.value()});
slice_inputs.push_back(axes_initializer);
} else if (has_axes == 1 && has_steps == 1) {
axes_initializer = builder.MakeInitializer<int64_t>({1}, {axis.value()});
slice_inputs.push_back(axes_initializer);
steps_initializer = builder.MakeInitializer<int64_t>({1}, {1});
slice_inputs.push_back(steps_initializer);
} else if (has_axes == 1 && has_steps == 2) {
axes_initializer = builder.MakeInitializer<int64_t>({1}, {axis.value()});
slice_inputs.push_back(axes_initializer);
steps_initializer = builder.MakeEmptyInput();
slice_inputs.push_back(steps_initializer);
} else if (has_axes == 2 && has_steps == 0) {
axes_initializer = builder.MakeEmptyInput();
slice_inputs.push_back(axes_initializer);
} else if (has_axes == 2 && has_steps == 1) {
axes_initializer = builder.MakeEmptyInput();
slice_inputs.push_back(axes_initializer);
steps_initializer = builder.MakeInitializer<int64_t>({1}, {1});
slice_inputs.push_back(steps_initializer);
} else if (has_axes == 2 && has_steps == 2) {
axes_initializer = builder.MakeEmptyInput();
slice_inputs.push_back(axes_initializer);
steps_initializer = builder.MakeEmptyInput();
slice_inputs.push_back(steps_initializer);
}
auto* slice_out = builder.MakeIntermediate();
builder.AddNode("Slice", slice_inputs,
{slice_out});
auto* identity_out = builder.MakeOutput();
builder.AddNode("Identity", {slice_out}, {identity_out});
};
std::unique_ptr<GraphTransformer> transformer = std::make_unique<UpStreamGatherGraphTransformer>();
ASSERT_STATUS_OK(TestGraphTransformer(build_test_case, 14, *logger, std::move(transformer),
TransformerLevel::Level1,
1, pre_graph_checker, post_graph_checker));
}
}
/*
Test graph includes multiple equivalent subgraphs as below.
graph input [2, 4, 32, 256] (float)
|
Transpose[perms=[0, 2, 1, 3]]
|
[2, 32, 4, 256]
| starts:(0) ends: (-1) axes: (1) steps: (1)
\ \ | / /
\ \ | / /
\ \ | / /
\ \ | / /
\ \ | / /
Slice
|
Identity
|
graph output [2, 31, 4, 256] (float)
Add an Identity node because currently, we don't allow Slice generates graph output.
*/
TEST(ComputeOptimizerTests, SliceTranspose_Propagation) {
// 0: no input, 1: has input, 2: empty input
std::vector<std::tuple<int, int, bool>> has_axes_and_has_steps_pairs{
{0, 0, false}, // {has_axes, has_steps, expected to propagate}
{1, 0, true},
{1, 1, true},
{1, 2, true},
{2, 0, false},
{2, 1, false},
{2, 2, false},
};
auto build_test_case = [](ModelTestBuilder& builder) {
auto* input1_arg = builder.MakeInput<int64_t>({{4, 32, 256}});
auto* input2_arg = builder.MakeInput<int64_t>({{4, 32, 256}});
auto* add_out = builder.MakeIntermediate();
builder.AddNode("Add", {input1_arg, input2_arg}, {add_out});
for (auto p : has_axes_and_has_steps_pairs) {
int has_axes = std::get<0>(p);
int has_steps = std::get<1>(p);
bool expected_to_propagate = std::get<2>(p);
auto* starts_initializer = builder.MakeInitializer<int64_t>({1}, {0});
auto* ends_initializer = builder.MakeInitializer<int64_t>({1}, {-1});
auto* axes_initializer = builder.MakeInitializer<int64_t>({1}, {1});
auto* steps_initializer = builder.MakeInitializer<int64_t>({1}, {1});
auto* slice_out = builder.MakeIntermediate();
builder.AddNode("Slice", {add_out, starts_initializer, ends_initializer, axes_initializer, steps_initializer},
{slice_out});
const logging::Logger* logger = &logging::LoggingManager::DefaultLogger();
InlinedVector<int64_t> starts_indices;
auto pre_graph_checker = [&starts_indices](Graph& graph) -> Status {
auto op_count_pre = CountOpsInGraph(graph);
TEST_RETURN_IF_NOT(op_count_pre.size() == 3U);
TEST_RETURN_IF_NOT(op_count_pre["Transpose"] == 1);
TEST_RETURN_IF_NOT(op_count_pre["Slice"] == 1);
TEST_RETURN_IF_NOT(op_count_pre["Identity"] == 1);
auto* identity_out = builder.MakeOutput();
builder.AddNode("Identity", {slice_out}, {identity_out});
};
for (Node& node : graph.Nodes()) {
if (node.OpType() == "Slice") {
TEST_RETURN_IF_NOT(starts_indices.empty());
constexpr bool require_constant = true;
NodeArg* initializer_node_arg = graph.GetNodeArg(node.InputDefs()[1]->Name());
TEST_RETURN_IF_NOT(optimizer_utils::AppendTensorFromInitializer(graph, *initializer_node_arg, starts_indices,
require_constant));
}
}
return Status::OK();
};
std::unique_ptr<GraphTransformer> transformer = std::make_unique<UpStreamGatherGraphTransformer>();
ASSERT_STATUS_OK(TestGraphTransformer(build_test_case, 14, *logger, std::move(transformer),
TransformerLevel::Level1,
1, pre_graph_checker, post_graph_checker));
auto post_graph_checker = [&starts_indices, expected_to_propagate](Graph& graph) {
auto op_count_post = CountOpsInGraph(graph);
TEST_RETURN_IF_NOT(op_count_post.size() == 3U);
TEST_RETURN_IF_NOT(op_count_post["Transpose"] == 1);
TEST_RETURN_IF_NOT(op_count_post["Slice"] == 1);
TEST_RETURN_IF_NOT(op_count_post["Identity"] == 1);
for (Node& node : graph.Nodes()) {
if (node.OpType() == "Transpose") {
const auto& input_defs = node.InputDefs();
auto producer_node = graph.GetProducerNode(input_defs[0]->Name());
if (expected_to_propagate) {
TEST_RETURN_IF_NOT(producer_node != nullptr);
TEST_RETURN_IF_NOT(producer_node->OpType() == "Slice");
InlinedVector<int64_t> values;
constexpr bool require_constant = true;
NodeArg* initializer_node_arg = graph.GetNodeArg(producer_node->InputDefs()[1]->Name());
TEST_RETURN_IF_NOT(optimizer_utils::AppendTensorFromInitializer(graph, *initializer_node_arg, values,
require_constant));
for (size_t i = 0; i < values.size(); i++) {
TEST_RETURN_IF_NOT(values[i] == starts_indices[i]);
}
const ONNX_NAMESPACE::TensorShapeProto* slice_out_shape = producer_node->OutputDefs()[0]->Shape();
TEST_RETURN_IF_NOT(slice_out_shape != nullptr);
TEST_RETURN_IF_NOT(slice_out_shape->dim_size() == 4);
TEST_RETURN_IF_NOT(utils::HasDimValue(slice_out_shape->dim(0)) && slice_out_shape->dim(0).dim_value() == 2);
TEST_RETURN_IF_NOT(utils::HasDimValue(slice_out_shape->dim(1)) && slice_out_shape->dim(1).dim_value() == 4);
TEST_RETURN_IF_NOT(utils::HasDimValue(slice_out_shape->dim(2)) && slice_out_shape->dim(2).dim_value() == 31);
TEST_RETURN_IF_NOT(utils::HasDimValue(slice_out_shape->dim(3)) && slice_out_shape->dim(3).dim_value() == 256);
} else {
TEST_RETURN_IF_NOT(producer_node == nullptr);
}
}
}
return Status::OK();
};
auto build_test_case = [has_axes, has_steps](ModelTestBuilder& builder) {
auto* input1_arg = builder.MakeInput<int64_t>({{2, 4, 32, 256}});
auto* trans_out = builder.MakeIntermediate();
builder.AddNode("Transpose", {input1_arg}, {trans_out})
.AddAttribute("perm", std::vector<int64_t>{0, 2, 1, 3});
std::vector<NodeArg*> slice_inputs;
auto* starts_initializer = builder.MakeInitializer<int64_t>({1}, {0});
auto* ends_initializer = builder.MakeInitializer<int64_t>({1}, {-1});
slice_inputs = {trans_out, starts_initializer, ends_initializer};
NodeArg* axes_initializer = nullptr;
NodeArg* steps_initializer = nullptr;
if (has_axes == 0 && has_steps == 0) {
// nothing
} else if (has_axes == 1 && has_steps == 0) {
axes_initializer = builder.MakeInitializer<int64_t>({1}, {1});
slice_inputs.push_back(axes_initializer);
} else if (has_axes == 1 && has_steps == 1) {
axes_initializer = builder.MakeInitializer<int64_t>({1}, {1});
slice_inputs.push_back(axes_initializer);
steps_initializer = builder.MakeInitializer<int64_t>({1}, {1});
slice_inputs.push_back(steps_initializer);
} else if (has_axes == 1 && has_steps == 2) {
axes_initializer = builder.MakeInitializer<int64_t>({1}, {1});
slice_inputs.push_back(axes_initializer);
steps_initializer = builder.MakeEmptyInput();
slice_inputs.push_back(steps_initializer);
} else if (has_axes == 2 && has_steps == 0) {
axes_initializer = builder.MakeEmptyInput();
slice_inputs.push_back(axes_initializer);
} else if (has_axes == 2 && has_steps == 1) {
axes_initializer = builder.MakeEmptyInput();
slice_inputs.push_back(axes_initializer);
steps_initializer = builder.MakeInitializer<int64_t>({1}, {1});
slice_inputs.push_back(steps_initializer);
} else if (has_axes == 2 && has_steps == 2) {
axes_initializer = builder.MakeEmptyInput();
slice_inputs.push_back(axes_initializer);
steps_initializer = builder.MakeEmptyInput();
slice_inputs.push_back(steps_initializer);
}
auto* slice_out = builder.MakeIntermediate();
builder.AddNode("Slice", slice_inputs,
{slice_out});
auto* identity_out = builder.MakeOutput();
builder.AddNode("Identity", {slice_out}, {identity_out});
};
std::unique_ptr<GraphTransformer> transformer = std::make_unique<UpStreamGatherGraphTransformer>();
ASSERT_STATUS_OK(TestGraphTransformer(build_test_case, 14, *logger, std::move(transformer),
TransformerLevel::Level1,
1, pre_graph_checker, post_graph_checker));
}
}
/*