mirror of
https://github.com/saymrwulf/onnxruntime.git
synced 2026-07-30 20:18:08 +00:00
Add logic to NNAPI EP to exclude pre-processing involving dynamic shapes when partitioning (#10452)
* wip * wip * wip * save * address pr comments * address pr comments Co-authored-by: rachguo <rachguo@rachguos-Mini.attlocal.net>
This commit is contained in:
parent
6076a262dc
commit
97b8f6f394
7 changed files with 94 additions and 37 deletions
|
|
@ -519,27 +519,6 @@ bool IsNodeSupportedInGroup(const NodeUnit& node_unit, const GraphViewer& graph_
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsInputSupported(const NodeArg& input, const std::string& parent_name) {
|
|
||||||
const auto& input_name = input.Name();
|
|
||||||
const auto* shape_proto = input.Shape();
|
|
||||||
// We do not support input with no shape
|
|
||||||
if (!shape_proto) {
|
|
||||||
LOGS_DEFAULT(VERBOSE) << "Input [" << input_name << "] of [" << parent_name
|
|
||||||
<< "] has no shape";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& dim : shape_proto->dim()) {
|
|
||||||
// For now we do not support dynamic shape
|
|
||||||
if (!dim.has_dim_value()) {
|
|
||||||
LOGS_DEFAULT(WARNING) << "Dynamic shape is not supported for now, for input:" << input_name;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Shape2String(const std::vector<uint32_t>& shape) {
|
std::string Shape2String(const std::vector<uint32_t>& shape) {
|
||||||
std::ostringstream os;
|
std::ostringstream os;
|
||||||
os << "[ ";
|
os << "[ ";
|
||||||
|
|
|
||||||
|
|
@ -147,9 +147,6 @@ bool IsNodeSupportedInGroup(const NodeUnit& node_unit, const GraphViewer& graph_
|
||||||
const OpSupportCheckParams& params,
|
const OpSupportCheckParams& params,
|
||||||
const std::unordered_set<std::string>& node_outputs_in_group);
|
const std::unordered_set<std::string>& node_outputs_in_group);
|
||||||
|
|
||||||
// If a graph input is supported by NNAPI
|
|
||||||
bool IsInputSupported(const NodeArg& input, const std::string& parent_name);
|
|
||||||
|
|
||||||
// If an NNAPI partition node group is valid
|
// If an NNAPI partition node group is valid
|
||||||
bool IsValidSupportedNodeGroup(const std::vector<const Node*>& supported_node_group);
|
bool IsValidSupportedNodeGroup(const std::vector<const Node*>& supported_node_group);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -153,26 +153,35 @@ bool BaseOpSupportChecker::IsOpSupported(const InitializedTensorSet& initializer
|
||||||
|
|
||||||
bool BaseOpSupportChecker::HasSupportedInputs(const NodeUnit& node_unit) const {
|
bool BaseOpSupportChecker::HasSupportedInputs(const NodeUnit& node_unit) const {
|
||||||
// We do not support unknown(null) input shape
|
// We do not support unknown(null) input shape
|
||||||
auto has_shape = [](const NodeArg& node_arg, const std::string& name, const std::string op_type) {
|
auto has_supported_shape = [](const NodeArg& node_arg, const std::string& name, const std::string op_type) {
|
||||||
if (!node_arg.Shape()) {
|
const auto* shape_proto = node_arg.Shape();
|
||||||
|
if (!shape_proto) {
|
||||||
LOGS_DEFAULT(VERBOSE) << "Node [" << name << "] type [" << op_type
|
LOGS_DEFAULT(VERBOSE) << "Node [" << name << "] type [" << op_type
|
||||||
<< "] Input [" << node_arg.Name() << "] has no shape";
|
<< "] Input [" << node_arg.Name() << "] has no shape";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We do not support dynamic shape input for now
|
||||||
|
for (const auto& dim : shape_proto->dim()) {
|
||||||
|
if (!dim.has_dim_value()) {
|
||||||
|
LOGS_DEFAULT(VERBOSE) << "Dynamic shape is not supported for now, for input:" << node_arg.Name();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const auto& input : node_unit.Inputs()) {
|
for (const auto& input : node_unit.Inputs()) {
|
||||||
if (!has_shape(input.node_arg, node_unit.Name(), node_unit.OpType()))
|
if (!has_supported_shape(input.node_arg, node_unit.Name(), node_unit.OpType()))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (input.quant_param.has_value()) {
|
if (input.quant_param.has_value()) {
|
||||||
if (!has_shape(input.quant_param->scale, node_unit.Name(), node_unit.OpType()))
|
if (!has_supported_shape(input.quant_param->scale, node_unit.Name(), node_unit.OpType()))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// zero point is optional
|
// zero point is optional
|
||||||
if (input.quant_param->zero_point &&
|
if (input.quant_param->zero_point &&
|
||||||
!has_shape(*input.quant_param->zero_point, node_unit.Name(), node_unit.OpType()))
|
!has_supported_shape(*input.quant_param->zero_point, node_unit.Name(), node_unit.OpType()))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1681,7 +1690,7 @@ bool FlattenOpSupportChecker::IsOpSupportedImpl(const InitializedTensorSet& /* i
|
||||||
GetFlattenOutputShape(node_unit, input_shape, dim_1, dim_2);
|
GetFlattenOutputShape(node_unit, input_shape, dim_1, dim_2);
|
||||||
|
|
||||||
if (dim_1 == 0 && dim_2 == 0) {
|
if (dim_1 == 0 && dim_2 == 0) {
|
||||||
LOGS_DEFAULT(VERBOSE) << "The dynamical input shape " << Shape2String(input_shape)
|
LOGS_DEFAULT(VERBOSE) << "The dynamic input shape " << Shape2String(input_shape)
|
||||||
<< " is not supported";
|
<< " is not supported";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -110,13 +110,6 @@ NnapiExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_view
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable NNAPI if the graph has any unsupported inputs
|
|
||||||
for (const auto* input : graph_viewer.GetInputs()) {
|
|
||||||
if (!nnapi::IsInputSupported(*input, "graph")) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all the NodeUnits in the graph_viewer
|
// Get all the NodeUnits in the graph_viewer
|
||||||
std::vector<std::unique_ptr<NodeUnit>> node_unit_holder;
|
std::vector<std::unique_ptr<NodeUnit>> node_unit_holder;
|
||||||
std::unordered_map<const Node*, const NodeUnit*> node_unit_map;
|
std::unordered_map<const Node*, const NodeUnit*> node_unit_map;
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,38 @@ TEST(NnapiExecutionProviderTest, ReshapeFlattenTest) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Since NNAPI EP does not support dynamic shape input and we now switch from the approach of immediately rejecting
|
||||||
|
// the whole graph in NNAPI EP if it has a dynamic input to check at individual operator support check level, we have a
|
||||||
|
// separated test here.
|
||||||
|
// Please see BaseOpBuilder::HasSupportedInputs in <repo_root>/onnxruntime/core/providers/nnapi/nnapi_builtin/builders/op_support_checker.cc
|
||||||
|
TEST(NnapiExecutionProviderTest, DynamicGraphInputTest) {
|
||||||
|
const ORTCHAR_T* model_file_name = ORT_TSTR("testdata/ep_dynamic_graph_input_test.onnx");
|
||||||
|
|
||||||
|
#if defined(__ANDROID__)
|
||||||
|
std::vector<int64_t> dims_mul_x = {1, 1, 4, 4};
|
||||||
|
std::vector<float> values_mul_x = {1.0f, 2.0f, 3.0f, 4.0f, 1.0f, 2.0f, 3.0f, 4.0f, 1.0f, 2.0f, 3.0f, 4.0f, 1.0f, 2.0f, 3.0f, 4.0f};
|
||||||
|
OrtValue ml_value_x;
|
||||||
|
CreateMLValue<float>(TestNnapiExecutionProvider()->GetAllocator(0, OrtMemTypeDefault), dims_mul_x, values_mul_x,
|
||||||
|
&ml_value_x);
|
||||||
|
|
||||||
|
NameMLValMap feeds;
|
||||||
|
feeds.insert(std::make_pair("X", ml_value_x));
|
||||||
|
|
||||||
|
RunAndVerifyOutputsWithEP(model_file_name, "NnapiExecutionProviderTest.DynamicGraphInputTest",
|
||||||
|
std::make_unique<NnapiExecutionProvider>(0),
|
||||||
|
feeds);
|
||||||
|
#else
|
||||||
|
// test load only
|
||||||
|
SessionOptions so;
|
||||||
|
InferenceSessionWrapper session_object{so, GetEnvironment()};
|
||||||
|
ASSERT_STATUS_OK(session_object.RegisterExecutionProvider(std::make_unique<NnapiExecutionProvider>(0)));
|
||||||
|
ASSERT_STATUS_OK(session_object.Load(model_file_name));
|
||||||
|
ASSERT_STATUS_OK(session_object.Initialize());
|
||||||
|
ASSERT_EQ(CountAssignedNodes(session_object.GetGraph(), kNnapiExecutionProvider), 1)
|
||||||
|
<< "Exactly one node (Add) should have been taken by the NNAPI EP";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// This is to test the uint8 handling of operators without "QLinear" such as Concat and Transpose
|
// This is to test the uint8 handling of operators without "QLinear" such as Concat and Transpose
|
||||||
// NNAPI will require scale and zero point for inputs of all quantized operations
|
// NNAPI will require scale and zero point for inputs of all quantized operations
|
||||||
// For these operators without "Qlinear", there is no information about the scale and zero point, we can
|
// For these operators without "Qlinear", there is no information about the scale and zero point, we can
|
||||||
|
|
|
||||||
BIN
onnxruntime/test/testdata/ep_dynamic_graph_input_test.onnx
vendored
Normal file
BIN
onnxruntime/test/testdata/ep_dynamic_graph_input_test.onnx
vendored
Normal file
Binary file not shown.
47
onnxruntime/test/testdata/ep_dynamic_graph_input_test.py
vendored
Normal file
47
onnxruntime/test/testdata/ep_dynamic_graph_input_test.py
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
import onnx
|
||||||
|
from onnx import helper
|
||||||
|
from onnx import TensorProto
|
||||||
|
|
||||||
|
|
||||||
|
# Since NNAPI EP does not support dynamic shape input and we now switch from the approach of immediately rejecting
|
||||||
|
# the whole graph in NNAPI EP if it has a dynamic input to checking the dynamic shape at individual operator support check level,
|
||||||
|
# We have a separated test here using a graph with dynamic input that becomes fixed after a Resize
|
||||||
|
# Please see BaseOpBuilder::HasSupportedInputs in <repo_root>/onnxruntime/core/providers/nnapi/nnapi_builtin/builders/op_support_checker.cc
|
||||||
|
def GenerateModel(model_name):
|
||||||
|
nodes = [
|
||||||
|
helper.make_node("Resize", ["X", "", "", "Resize_1_sizes"], [
|
||||||
|
"Resize_1_output"], "resize_1", mode="cubic"),
|
||||||
|
helper.make_node(
|
||||||
|
"Add", ["Resize_1_output", "Add_2_input"], ["Y"], "add"),
|
||||||
|
]
|
||||||
|
|
||||||
|
initializers = [
|
||||||
|
helper.make_tensor('Resize_1_sizes', TensorProto.INT64, [
|
||||||
|
4], [1, 1, 3, 3]),
|
||||||
|
helper.make_tensor('Add_2_input', TensorProto.FLOAT, [1, 1, 3, 3], [
|
||||||
|
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0])
|
||||||
|
]
|
||||||
|
|
||||||
|
inputs = [
|
||||||
|
helper.make_tensor_value_info(
|
||||||
|
'X', TensorProto.FLOAT, ["1", "1", "N", "N"]), # used dim_param here
|
||||||
|
]
|
||||||
|
|
||||||
|
outputs = [
|
||||||
|
helper.make_tensor_value_info('Y', TensorProto.FLOAT, [1, 1, 3, 3]),
|
||||||
|
]
|
||||||
|
|
||||||
|
graph = helper.make_graph(
|
||||||
|
nodes,
|
||||||
|
"EP_Dynamic_Graph_Input_Test",
|
||||||
|
inputs,
|
||||||
|
outputs,
|
||||||
|
initializers
|
||||||
|
)
|
||||||
|
|
||||||
|
model = helper.make_model(graph)
|
||||||
|
onnx.save(model, model_name)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
GenerateModel('ep_dynamic_graph_input_test.onnx')
|
||||||
Loading…
Reference in a new issue