mirror of
https://github.com/saymrwulf/onnxruntime.git
synced 2026-07-28 20:11:22 +00:00
Change the input to NNAPI EP ModelBuilder from ModelProto to GraphViewer (#4389)
* init version to use graph instead of model_proto for IsOpSupported * move add to modelbuilder to use graph node * move the rest of model_builder to use graph instead of modelproto * remove redundant code * Clear some redundant code * merge master and some minor style changes * move check if an initializer is external to individual op instead the whole graph * Addressed comments * Change the GetType and GetShape to log waring info inside to simplify the caller, remove some redundant onnxruntime namespace * add squeeze op support, some more code style clean up * fix a bug where duplicate output can be added to a subgraph, some other minor logging changes
This commit is contained in:
parent
632b2896f3
commit
7baf374939
12 changed files with 665 additions and 722 deletions
|
|
@ -0,0 +1,99 @@
|
|||
//
|
||||
// Created by daquexian on 8/3/18.
|
||||
//
|
||||
|
||||
#include <core/common/safeint.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "helper.h"
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
std::string GetErrorCause(int error_code) {
|
||||
switch (error_code) {
|
||||
case ANEURALNETWORKS_NO_ERROR:
|
||||
return "ANEURALNETWORKS_NO_ERROR";
|
||||
case ANEURALNETWORKS_OUT_OF_MEMORY:
|
||||
return "ANEURALNETWORKS_OUT_OF_MEMORY";
|
||||
case ANEURALNETWORKS_INCOMPLETE:
|
||||
return "ANEURALNETWORKS_INCOMPLETE";
|
||||
case ANEURALNETWORKS_UNEXPECTED_NULL:
|
||||
return "ANEURALNETWORKS_UNEXPECTED_NULL";
|
||||
case ANEURALNETWORKS_BAD_DATA:
|
||||
return "ANEURALNETWORKS_BAD_DATA";
|
||||
case ANEURALNETWORKS_OP_FAILED:
|
||||
return "ANEURALNETWORKS_OP_FAILED";
|
||||
case ANEURALNETWORKS_BAD_STATE:
|
||||
return "ANEURALNETWORKS_BAD_STATE";
|
||||
case ANEURALNETWORKS_UNMAPPABLE:
|
||||
return "ANEURALNETWORKS_UNMAPPABLE";
|
||||
case ANEURALNETWORKS_OUTPUT_INSUFFICIENT_SIZE:
|
||||
return "ANEURALNETWORKS_OUTPUT_INSUFFICIENT_SIZE";
|
||||
case ANEURALNETWORKS_UNAVAILABLE_DEVICE:
|
||||
return "ANEURALNETWORKS_UNAVAILABLE_DEVICE";
|
||||
|
||||
default:
|
||||
return "Unknown error code: " + std::to_string(error_code);
|
||||
}
|
||||
}
|
||||
|
||||
NodeAttrHelper::NodeAttrHelper(const onnxruntime::Node& node)
|
||||
: node_attributes_(node.GetAttributes()) {}
|
||||
|
||||
float NodeAttrHelper::Get(const std::string& key, float def_val) const {
|
||||
if (HasAttr(key))
|
||||
return node_attributes_.at(key).f();
|
||||
|
||||
return def_val;
|
||||
}
|
||||
|
||||
int32_t NodeAttrHelper::Get(const std::string& key, int32_t def_val) const {
|
||||
if (HasAttr(key))
|
||||
return SafeInt<int32_t>(node_attributes_.at(key).i());
|
||||
|
||||
return def_val;
|
||||
}
|
||||
|
||||
string NodeAttrHelper::Get(const std::string& key, const string& def_val) const {
|
||||
if (HasAttr(key))
|
||||
return node_attributes_.at(key).s();
|
||||
|
||||
return def_val;
|
||||
}
|
||||
|
||||
vector<int32_t> NodeAttrHelper::Get(const std::string& key, const vector<int32_t>& def_val) const {
|
||||
if (HasAttr(key)) {
|
||||
const auto& attr(node_attributes_.at(key));
|
||||
std::vector<int32_t> v;
|
||||
v.reserve(static_cast<size_t>(attr.ints_size()));
|
||||
for (int j = 0; j < attr.ints_size(); j++) {
|
||||
int64_t val = attr.ints(j);
|
||||
v.push_back(SafeInt<int32_t>(val));
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
return def_val;
|
||||
}
|
||||
|
||||
vector<float> NodeAttrHelper::Get(const std::string& key, const vector<float>& def_val) const {
|
||||
if (HasAttr(key)) {
|
||||
const auto& attr(node_attributes_.at(key));
|
||||
std::vector<float> v;
|
||||
v.reserve(static_cast<size_t>(attr.ints_size()));
|
||||
for (int j = 0; j < attr.ints_size(); j++) {
|
||||
v.push_back(attr.floats(j));
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
return def_val;
|
||||
}
|
||||
|
||||
bool NodeAttrHelper::HasAttr(const std::string& key) const {
|
||||
return Contains(node_attributes_, key);
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
//
|
||||
#pragma once
|
||||
|
||||
#include <core/common/common.h>
|
||||
#include <core/graph/graph.h>
|
||||
#include <string>
|
||||
|
||||
#include "core/providers/nnapi/nnapi_builtin/nnapi_lib/NeuralNetworksTypes.h"
|
||||
|
|
@ -34,30 +34,23 @@ inline bool Contains(const Map& map, const Key& key) {
|
|||
return map.find(key) != map.end();
|
||||
}
|
||||
|
||||
inline std::string GetErrorCause(int error_code) {
|
||||
switch (error_code) {
|
||||
case ANEURALNETWORKS_NO_ERROR:
|
||||
return "ANEURALNETWORKS_NO_ERROR";
|
||||
case ANEURALNETWORKS_OUT_OF_MEMORY:
|
||||
return "ANEURALNETWORKS_OUT_OF_MEMORY";
|
||||
case ANEURALNETWORKS_INCOMPLETE:
|
||||
return "ANEURALNETWORKS_INCOMPLETE";
|
||||
case ANEURALNETWORKS_UNEXPECTED_NULL:
|
||||
return "ANEURALNETWORKS_UNEXPECTED_NULL";
|
||||
case ANEURALNETWORKS_BAD_DATA:
|
||||
return "ANEURALNETWORKS_BAD_DATA";
|
||||
case ANEURALNETWORKS_OP_FAILED:
|
||||
return "ANEURALNETWORKS_OP_FAILED";
|
||||
case ANEURALNETWORKS_BAD_STATE:
|
||||
return "ANEURALNETWORKS_BAD_STATE";
|
||||
case ANEURALNETWORKS_UNMAPPABLE:
|
||||
return "ANEURALNETWORKS_UNMAPPABLE";
|
||||
case ANEURALNETWORKS_OUTPUT_INSUFFICIENT_SIZE:
|
||||
return "ANEURALNETWORKS_OUTPUT_INSUFFICIENT_SIZE";
|
||||
case ANEURALNETWORKS_UNAVAILABLE_DEVICE:
|
||||
return "ANEURALNETWORKS_UNAVAILABLE_DEVICE";
|
||||
std::string GetErrorCause(int error_code);
|
||||
|
||||
default:
|
||||
return "Unknown error code: " + std::to_string(error_code);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Wrapping onnxruntime::Node for retrieving attribute values
|
||||
*/
|
||||
class NodeAttrHelper {
|
||||
public:
|
||||
NodeAttrHelper(const onnxruntime::Node& proto);
|
||||
|
||||
float Get(const std::string& key, float def_val) const;
|
||||
int32_t Get(const std::string& key, int32_t def_val) const;
|
||||
std::vector<float> Get(const std::string& key, const std::vector<float>& def_val) const;
|
||||
std::vector<int32_t> Get(const std::string& key, const std::vector<int32_t>& def_val) const;
|
||||
std::string Get(const std::string& key, const std::string& def_val) const;
|
||||
|
||||
bool HasAttr(const std::string& key) const;
|
||||
|
||||
private:
|
||||
const onnxruntime::NodeAttributes& node_attributes_;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
#include <core/common/logging/logging.h>
|
||||
#include <core/common/safeint.h>
|
||||
|
||||
#include "core/common/safeint.h"
|
||||
#include "core/providers/nnapi/nnapi_builtin/nnapi_lib/nnapi_implementation.h"
|
||||
#include "helper.h"
|
||||
#include "model_builder.h"
|
||||
#include "node_attr_helper.h"
|
||||
#include "op_builder.h"
|
||||
|
||||
namespace onnxruntime {
|
||||
namespace nnapi {
|
||||
|
|
@ -15,14 +15,8 @@ namespace nnapi {
|
|||
using namespace android::nn::wrapper;
|
||||
using std::vector;
|
||||
|
||||
const float* GetTensorFloatDataA(const ONNX_NAMESPACE::TensorProto& tensor) {
|
||||
return tensor.float_data().empty()
|
||||
? reinterpret_cast<const float*>(tensor.raw_data().data())
|
||||
: tensor.float_data().data();
|
||||
}
|
||||
|
||||
ModelBuilder::ModelBuilder(ONNX_NAMESPACE::ModelProto& model_proto)
|
||||
: nnapi_(NnApiImplementation()), model_proto_(model_proto) {
|
||||
ModelBuilder::ModelBuilder(const GraphViewer& graph_view)
|
||||
: nnapi_(NnApiImplementation()), graph_view_(graph_view) {
|
||||
GetAllInitializers();
|
||||
op_builders_ = CreateOpBuilders();
|
||||
}
|
||||
|
|
@ -31,21 +25,20 @@ int32_t ModelBuilder::GetAndroidSdkVer() const {
|
|||
return nnapi_ ? nnapi_->android_sdk_version : 0;
|
||||
}
|
||||
|
||||
bool ModelBuilder::IsNodeSupported(
|
||||
const ONNX_NAMESPACE::NodeProto& node) {
|
||||
if (auto* opBuilder = GetOpBuilder(node)) {
|
||||
return opBuilder->IsOpSupported(*this, node);
|
||||
bool ModelBuilder::IsNodeSupported(const Node& node) {
|
||||
if (auto* op_builder = GetOpBuilder(node)) {
|
||||
return op_builder->IsOpSupported(*this, node);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsValidSupportedNodesVec(const std::vector<int>& supported_node_vec,
|
||||
const ONNX_NAMESPACE::ModelProto& model_proto) {
|
||||
bool IsValidSupportedNodesVec(const std::vector<int>& supported_node_vec, const GraphViewer& graph_view) {
|
||||
if (!supported_node_vec.empty()) {
|
||||
if (supported_node_vec.size() == 1) {
|
||||
const auto& node = model_proto.graph().node(supported_node_vec[0]);
|
||||
const auto& op = node.op_type();
|
||||
const auto& node_indices = graph_view.GetNodesInTopologicalOrder();
|
||||
const auto* node(graph_view.GetNode(node_indices[supported_node_vec[0]]));
|
||||
const auto& op = node->OpType();
|
||||
// It is not worth it to perform a single Reshape/Dropout/Identity operator
|
||||
// which is only copying the data in NNAPI
|
||||
// If this is the case, let it fall back
|
||||
|
|
@ -58,7 +51,7 @@ bool IsValidSupportedNodesVec(const std::vector<int>& supported_node_vec,
|
|||
return true;
|
||||
}
|
||||
return false;
|
||||
} // namespace nnapi
|
||||
}
|
||||
|
||||
std::vector<std::vector<int>> ModelBuilder::GetSupportedNodes() {
|
||||
std::vector<std::vector<int>> supported_node_vecs;
|
||||
|
|
@ -73,25 +66,26 @@ std::vector<std::vector<int>> ModelBuilder::GetSupportedNodes() {
|
|||
#endif
|
||||
|
||||
std::vector<int> supported_node_vec;
|
||||
for (int i = 0; i < model_proto_.graph().node_size(); i++) {
|
||||
const auto& node(model_proto_.graph().node(i));
|
||||
bool supported = IsNodeSupported(node);
|
||||
LOGS_DEFAULT(VERBOSE) << "Operator type: [" << node.op_type()
|
||||
const auto& node_indices = graph_view_.GetNodesInTopologicalOrder();
|
||||
for (size_t i = 0; i < node_indices.size(); i++) {
|
||||
const auto* node(graph_view_.GetNode(node_indices[i]));
|
||||
bool supported = IsNodeSupported(*node);
|
||||
LOGS_DEFAULT(VERBOSE) << "Operator type: [" << node->OpType()
|
||||
<< "] index: [" << i
|
||||
<< "] name: [" << node.name()
|
||||
<< "] name: [" << node->Name()
|
||||
<< "] supported: [" << supported
|
||||
<< "]";
|
||||
if (supported) {
|
||||
supported_node_vec.push_back(i);
|
||||
} else {
|
||||
if (IsValidSupportedNodesVec(supported_node_vec, model_proto_)) {
|
||||
if (IsValidSupportedNodesVec(supported_node_vec, graph_view_)) {
|
||||
supported_node_vecs.push_back(supported_node_vec);
|
||||
supported_node_vec.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (IsValidSupportedNodesVec(supported_node_vec, model_proto_))
|
||||
if (IsValidSupportedNodesVec(supported_node_vec, graph_view_))
|
||||
supported_node_vecs.push_back(supported_node_vec);
|
||||
|
||||
LOGS_DEFAULT(VERBOSE) << "Support vectors size is " << supported_node_vecs.size();
|
||||
|
|
@ -156,16 +150,16 @@ void ModelBuilder::GetTargetDevices() {
|
|||
const std::string nnapi_cpu("nnapi-reference");
|
||||
uint32_t num_devices = 0;
|
||||
THROW_ON_ERROR_WITH_NOTE(nnapi_->ANeuralNetworks_getDeviceCount(&num_devices),
|
||||
"Getting list of available devices");
|
||||
"Getting count of available devices");
|
||||
|
||||
for (uint32_t i = 0; i < num_devices; i++) {
|
||||
ANeuralNetworksDevice* device = nullptr;
|
||||
const char* device_name = nullptr;
|
||||
THROW_ON_ERROR_WITH_NOTE(nnapi_->ANeuralNetworks_getDevice(i, &device),
|
||||
"Getting list of available devices");
|
||||
"Getting " + std::to_string(i) + "th device");
|
||||
|
||||
THROW_ON_ERROR_WITH_NOTE(nnapi_->ANeuralNetworksDevice_getName(device, &device_name),
|
||||
"Getting list of available devices");
|
||||
"Getting " + std::to_string(i) + "th device's name");
|
||||
|
||||
bool device_is_cpu = nnapi_cpu == device_name;
|
||||
if ((target_device_option_ == TargetDeviceOption::CPU_DISABLED && !device_is_cpu) ||
|
||||
|
|
@ -177,27 +171,30 @@ void ModelBuilder::GetTargetDevices() {
|
|||
}
|
||||
|
||||
void ModelBuilder::GetAllInitializers() {
|
||||
for (const auto& tensor : model_proto_.graph().initializer()) {
|
||||
initializers_.emplace(tensor.name(), tensor);
|
||||
for (const auto& pair : graph_view_.GetAllInitializedTensors()) {
|
||||
initializers_.emplace(pair.first, *pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
void ModelBuilder::PreprocessInitializers() {
|
||||
for (const auto& node : model_proto_.graph().node()) {
|
||||
if (auto* opBuilder = GetOpBuilder(node)) {
|
||||
opBuilder->AddInitializersToSkip(*this, node);
|
||||
const auto& node_indices = graph_view_.GetNodesInTopologicalOrder();
|
||||
for (size_t i = 0; i < node_indices.size(); i++) {
|
||||
const auto* node(graph_view_.GetNode(node_indices[i]));
|
||||
if (auto* op_builder = GetOpBuilder(*node)) {
|
||||
op_builder->AddInitializersToSkip(*this, *node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelBuilder::RegisterInitializers() {
|
||||
// First pass to get all the stats of the initializers
|
||||
auto initializer_size = model_proto_.graph().initializer_size();
|
||||
auto initializer_size = initializers_.size();
|
||||
std::vector<std::tuple<uint32_t, size_t, size_t>> initializers(initializer_size);
|
||||
size_t sizeAll = 0;
|
||||
|
||||
for (int i = 0; i < initializer_size; ++i) {
|
||||
const auto& tensor = model_proto_.graph().initializer(i);
|
||||
int i = 0;
|
||||
for (const auto& pair : initializers_) {
|
||||
const auto& tensor = pair.second;
|
||||
const auto& name = tensor.name();
|
||||
if (Contains(skipped_initializers_, name))
|
||||
continue;
|
||||
|
|
@ -226,23 +223,24 @@ void ModelBuilder::RegisterInitializers() {
|
|||
const size_t size = operand_type.GetOperandBlobByteSize();
|
||||
const size_t padded_size = GetPaddedByteSize(size);
|
||||
sizeAll += padded_size;
|
||||
initializers[i] = std::make_tuple(index, size, padded_size);
|
||||
initializers[i++] = std::make_tuple(index, size, padded_size);
|
||||
}
|
||||
|
||||
// 2nd pass copies all the initializer data into NNAPI shared memory
|
||||
i = 0;
|
||||
nnapi_model_->mem_initializers_ =
|
||||
std::make_unique<Model::NNMemory>(nnapi_, "mem_initializers_", sizeAll);
|
||||
|
||||
// 2nd pass to copy all the initializers into shared memory
|
||||
size_t offset = 0;
|
||||
for (int i = 0; i < initializer_size; ++i) {
|
||||
const auto& tensor = model_proto_.graph().initializer(i);
|
||||
for (const auto& pair : initializers_) {
|
||||
const auto& tensor = pair.second;
|
||||
if (Contains(skipped_initializers_, tensor.name()))
|
||||
continue;
|
||||
|
||||
uint32_t index;
|
||||
size_t size, padded_size;
|
||||
std::tie(index, size, padded_size) = initializers[i];
|
||||
std::tie(index, size, padded_size) = initializers[i++];
|
||||
const char* src = nullptr;
|
||||
if (tensor.data_type() == ONNX_NAMESPACE::TensorProto_DataType_FLOAT) {
|
||||
src = tensor.float_data().empty()
|
||||
|
|
@ -258,9 +256,8 @@ void ModelBuilder::RegisterInitializers() {
|
|||
}
|
||||
|
||||
void ModelBuilder::RegisterModelInputs() {
|
||||
for (int32_t input_idx = 0; input_idx < model_proto_.graph().input_size(); input_idx++) {
|
||||
const auto& input(model_proto_.graph().input(input_idx));
|
||||
std::string input_name = input.name();
|
||||
for (const auto* node_arg : graph_view_.GetInputs()) {
|
||||
const auto& input_name = node_arg->Name();
|
||||
|
||||
{ // input should not be an initializer
|
||||
if (Contains(operands_, input_name))
|
||||
|
|
@ -270,15 +267,21 @@ void ModelBuilder::RegisterModelInputs() {
|
|||
continue;
|
||||
}
|
||||
|
||||
const auto* shape_proto = node_arg->Shape();
|
||||
ORT_ENFORCE(shape_proto != nullptr, "shape_proto cannot be null for input: " + input_name);
|
||||
Shaper::Shape shape;
|
||||
for (const auto& dim : input.type().tensor_type().shape().dim()) {
|
||||
|
||||
for (const auto& dim : shape_proto->dim()) {
|
||||
// NNAPI uses 0 for dynamic dimension, which is the default value for dim.dim_value()
|
||||
shape.push_back(SafeInt<uint32_t>(dim.dim_value()));
|
||||
}
|
||||
|
||||
Type type = Type::TENSOR_FLOAT32;
|
||||
if (input.type().tensor_type().has_elem_type()) {
|
||||
switch (input.type().tensor_type().elem_type()) {
|
||||
const auto* type_proto = node_arg->TypeAsProto();
|
||||
if (!type_proto || !type_proto->tensor_type().has_elem_type()) {
|
||||
ORT_THROW("The input of graph doesn't have elem_type: " + input_name);
|
||||
} else {
|
||||
switch (type_proto->tensor_type().elem_type()) {
|
||||
case ONNX_NAMESPACE::TensorProto_DataType_FLOAT:
|
||||
type = Type::TENSOR_FLOAT32;
|
||||
break;
|
||||
|
|
@ -286,27 +289,23 @@ void ModelBuilder::RegisterModelInputs() {
|
|||
// TODO: support other type
|
||||
ORT_THROW("The input of graph doesn't have valid type, name: " +
|
||||
input_name + " type: " +
|
||||
std::to_string(input.type().tensor_type().elem_type()));
|
||||
std::to_string(type_proto->tensor_type().elem_type()));
|
||||
}
|
||||
} else {
|
||||
ORT_THROW("The input of graph doesn't have elem_type: " +
|
||||
input_name);
|
||||
}
|
||||
|
||||
OperandType operand_type(type, shape);
|
||||
shaper_.AddShape(input_name, operand_type.dimensions);
|
||||
|
||||
auto index = AddNewOperand(input_name, operand_type, false /* is_nhwc */);
|
||||
|
||||
input_index_vec_.push_back(index);
|
||||
nnapi_model_->AddInput(input_name, operand_type);
|
||||
}
|
||||
} // namespace nnapi
|
||||
}
|
||||
|
||||
void ModelBuilder::RegisterModelOutputs() {
|
||||
for (int32_t output_idx = 0; output_idx < model_proto_.graph().output_size(); output_idx++) {
|
||||
const auto& output(model_proto_.graph().output(output_idx));
|
||||
const std::string& output_name(output.name());
|
||||
for (const auto* node_arg : graph_view_.GetOutputs()) {
|
||||
const auto& output_name = node_arg->Name();
|
||||
|
||||
if (!Contains(operands_, output_name)) {
|
||||
ORT_THROW("The output of graph is not registered" + output_name);
|
||||
}
|
||||
|
|
@ -331,9 +330,7 @@ void ModelBuilder::RegisterModelShaper() {
|
|||
uint32_t ModelBuilder::AddNewOperand(const std::string& name,
|
||||
const OperandType& operand_type,
|
||||
bool is_nhwc) {
|
||||
THROW_ON_ERROR(nnapi_->ANeuralNetworksModel_addOperand(
|
||||
nnapi_model_->model_, &operand_type.operandType));
|
||||
auto idx = next_index_++;
|
||||
auto idx = AddNewNNAPIOperand(operand_type);
|
||||
RegisterOperand(name, idx, operand_type, is_nhwc);
|
||||
return idx;
|
||||
}
|
||||
|
|
@ -399,12 +396,13 @@ uint32_t ModelBuilder::AddOperandFromPersistMemoryBuffer(
|
|||
}
|
||||
|
||||
void ModelBuilder::AddOperations() {
|
||||
for (const auto& node : model_proto_.graph().node()) {
|
||||
if (auto* opBuilder = GetOpBuilder(node)) {
|
||||
opBuilder->AddToModelBuilder(*this, node);
|
||||
const auto& node_indices = graph_view_.GetNodesInTopologicalOrder();
|
||||
for (size_t i = 0; i < node_indices.size(); i++) {
|
||||
const auto* node(graph_view_.GetNode(node_indices[i]));
|
||||
if (auto* op_builder = GetOpBuilder(*node)) {
|
||||
op_builder->AddToModelBuilder(*this, *node);
|
||||
} else {
|
||||
throw std::invalid_argument(
|
||||
"Node not supported" + node.name());
|
||||
ORT_THROW("Node [" + node->Name() + "], type [" + node->OpType() + "] is not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -472,48 +470,44 @@ std::unique_ptr<Model> ModelBuilder::Compile() {
|
|||
return std::move(nnapi_model_);
|
||||
}
|
||||
|
||||
int32_t ModelBuilder::FindActivation(const std::string& output) {
|
||||
int32_t ModelBuilder::FindActivation(const Node& node, const NodeArg& output) {
|
||||
int32_t fuse_code = ANEURALNETWORKS_FUSED_NONE;
|
||||
const ONNX_NAMESPACE::NodeProto* activationNode{nullptr};
|
||||
std::string node_name;
|
||||
for (const auto& _node : model_proto_.graph().node()) {
|
||||
if (_node.op_type() == "Relu" && output == _node.input(0)) {
|
||||
fuse_code = ANEURALNETWORKS_FUSED_RELU;
|
||||
activationNode = &_node;
|
||||
for (auto it = node.OutputEdgesBegin(), end = node.OutputEdgesEnd(); it != end; ++it) {
|
||||
const auto& dst_node = it->GetNode();
|
||||
const auto* dst_input = dst_node.InputDefs()[it->GetDstArgIndex()];
|
||||
if (dst_node.OpType() == "Relu") {
|
||||
if (&output == dst_input) {
|
||||
fuse_code = ANEURALNETWORKS_FUSED_RELU;
|
||||
}
|
||||
} else {
|
||||
// if there is any other non-relu node using the output
|
||||
// will add relu separately
|
||||
if (&output == dst_input)
|
||||
return ANEURALNETWORKS_FUSED_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
// if output is a graph output, will add relu separately
|
||||
if (fuse_code != ANEURALNETWORKS_FUSED_NONE) {
|
||||
for (const auto& _node : model_proto_.graph().node()) {
|
||||
if (&_node == activationNode)
|
||||
continue;
|
||||
|
||||
// if there is any other node using the output
|
||||
// will add relu separately
|
||||
for (const auto& node_input : _node.input()) {
|
||||
if (output == node_input)
|
||||
return ANEURALNETWORKS_FUSED_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
// if output is a graph output
|
||||
// will add relu separately
|
||||
for (const auto& model_output : model_proto_.graph().output()) {
|
||||
if (model_output.name() == output)
|
||||
for (const auto* graph_output : graph_view_.GetOutputs()) {
|
||||
if (&output == graph_output)
|
||||
return ANEURALNETWORKS_FUSED_NONE;
|
||||
}
|
||||
|
||||
fused_activations_.insert(activationNode->name());
|
||||
LOGS_DEFAULT(VERBOSE) << "Node [" << node.Name() << "] type [" << node.OpType()
|
||||
<< "], fused the output [" << output.Name() << "]";
|
||||
|
||||
fused_activations_.insert(output.Name());
|
||||
}
|
||||
|
||||
return fuse_code;
|
||||
}
|
||||
|
||||
IOpBuilder* ModelBuilder::GetOpBuilder(const ONNX_NAMESPACE::NodeProto& node) {
|
||||
if (!Contains(op_builders_, node.op_type()))
|
||||
IOpBuilder* ModelBuilder::GetOpBuilder(const Node& node) {
|
||||
if (!Contains(op_builders_, node.OpType()))
|
||||
return nullptr;
|
||||
|
||||
return op_builders_[node.op_type()].get();
|
||||
return op_builders_[node.OpType()].get();
|
||||
}
|
||||
|
||||
std::string ModelBuilder::GetUniqueName(const std::string& base_name) {
|
||||
|
|
|
|||
|
|
@ -5,14 +5,16 @@
|
|||
#include <onnx/onnx_pb.h>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <core/graph/graph_viewer.h>
|
||||
#include "core/providers/nnapi/nnapi_builtin/model.h"
|
||||
#include "core/providers/nnapi/nnapi_builtin/nnapi_lib/NeuralNetworksWrapper.h"
|
||||
#include "op_builder.h"
|
||||
#include "shaper.h"
|
||||
|
||||
namespace onnxruntime {
|
||||
namespace nnapi {
|
||||
|
||||
class IOpBuilder;
|
||||
|
||||
class ModelBuilder {
|
||||
public:
|
||||
using Shape = Shaper::Shape;
|
||||
|
|
@ -26,7 +28,7 @@ class ModelBuilder {
|
|||
CPU_ONLY, // use CPU only
|
||||
};
|
||||
|
||||
ModelBuilder(ONNX_NAMESPACE::ModelProto& model_proto);
|
||||
ModelBuilder(const GraphViewer& graph_view);
|
||||
~ModelBuilder() = default;
|
||||
|
||||
std::vector<std::vector<int>> GetSupportedNodes();
|
||||
|
|
@ -42,7 +44,7 @@ class ModelBuilder {
|
|||
const std::vector<bool>& is_nhwc_vec);
|
||||
|
||||
// Find if an output has a fuseable activation (Relu)
|
||||
int32_t FindActivation(const std::string& output);
|
||||
int32_t FindActivation(const Node& node, const NodeArg& output);
|
||||
|
||||
// Add an NNAPI scalar operand
|
||||
uint32_t AddOperandFromScalar(bool value);
|
||||
|
|
@ -89,11 +91,10 @@ class ModelBuilder {
|
|||
const std::unordered_set<std::string>&
|
||||
GetFusedActivations() const { return fused_activations_; }
|
||||
|
||||
const std::unordered_map<std::string,
|
||||
const ONNX_NAMESPACE::TensorProto&>&
|
||||
const std::unordered_map<std::string, const ONNX_NAMESPACE::TensorProto&>&
|
||||
GetInitializerTensors() const { return initializers_; }
|
||||
|
||||
const ONNX_NAMESPACE::ModelProto& GetOnnxModel() const { return model_proto_; }
|
||||
const Graph& GetOnnxGraph() const { return graph_view_.GetGraph(); }
|
||||
|
||||
void RegisterNHWCOperand(const std::string& name);
|
||||
bool IsOperandNHWC(const std::string& name);
|
||||
|
|
@ -109,7 +110,7 @@ class ModelBuilder {
|
|||
|
||||
private:
|
||||
const NnApi* nnapi_{nullptr};
|
||||
ONNX_NAMESPACE::ModelProto& model_proto_;
|
||||
const GraphViewer& graph_view_;
|
||||
std::unique_ptr<Model> nnapi_model_;
|
||||
|
||||
uint32_t name_token_{0};
|
||||
|
|
@ -149,7 +150,7 @@ class ModelBuilder {
|
|||
|
||||
uint32_t next_index_ = 0;
|
||||
|
||||
bool IsNodeSupported(const ONNX_NAMESPACE::NodeProto& node);
|
||||
bool IsNodeSupported(const Node& node);
|
||||
|
||||
// Convert the onnx model to ANeuralNetworksModel
|
||||
void Prepare();
|
||||
|
|
@ -171,7 +172,7 @@ class ModelBuilder {
|
|||
const android::nn::wrapper::OperandType& operand_type,
|
||||
bool is_nhwc);
|
||||
|
||||
IOpBuilder* GetOpBuilder(const ONNX_NAMESPACE::NodeProto& node);
|
||||
IOpBuilder* GetOpBuilder(const Node& node);
|
||||
};
|
||||
|
||||
} // namespace nnapi
|
||||
|
|
|
|||
|
|
@ -1,104 +0,0 @@
|
|||
//
|
||||
// Created by daquexian on 8/3/18.
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "core/common/safeint.h"
|
||||
#include "node_attr_helper.h"
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
NodeAttrHelper::NodeAttrHelper(const ONNX_NAMESPACE::NodeProto& proto) : node_(proto) {
|
||||
}
|
||||
|
||||
float NodeAttrHelper::Get(const std::string& key, float def_val) {
|
||||
for (int i = 0; i < node_.attribute_size(); i++) {
|
||||
const ONNX_NAMESPACE::AttributeProto& attr = node_.attribute(i);
|
||||
if (attr.name() == key) {
|
||||
return attr.f();
|
||||
}
|
||||
}
|
||||
|
||||
return def_val;
|
||||
}
|
||||
|
||||
int32_t NodeAttrHelper::Get(const std::string& key, int32_t def_val) {
|
||||
for (int i = 0; i < node_.attribute_size(); i++) {
|
||||
const ONNX_NAMESPACE::AttributeProto& attr = node_.attribute(i);
|
||||
if (attr.name() == key) {
|
||||
int64_t val = attr.i();
|
||||
return SafeInt<int32_t>(val);
|
||||
}
|
||||
}
|
||||
|
||||
return def_val;
|
||||
}
|
||||
|
||||
string NodeAttrHelper::Get(const std::string& key, const string& def_val) {
|
||||
for (int i = 0; i < node_.attribute_size(); i++) {
|
||||
const ONNX_NAMESPACE::AttributeProto& attr = node_.attribute(i);
|
||||
if (attr.name() == key) {
|
||||
return attr.s();
|
||||
}
|
||||
}
|
||||
|
||||
return def_val;
|
||||
}
|
||||
|
||||
vector<int32_t> NodeAttrHelper::Get(const std::string& key, const vector<int32_t>& def_val) {
|
||||
if (!HasAttr(key)) {
|
||||
return def_val;
|
||||
}
|
||||
|
||||
for (int i = 0; i < node_.attribute_size(); i++) {
|
||||
const ONNX_NAMESPACE::AttributeProto& attr = node_.attribute(i);
|
||||
if (attr.name() == key) {
|
||||
std::vector<int32_t> v;
|
||||
v.reserve(static_cast<size_t>(attr.ints_size()));
|
||||
for (int j = 0; j < attr.ints_size(); j++) {
|
||||
int64_t val = attr.ints(j);
|
||||
v.push_back(SafeInt<int32_t>(val));
|
||||
}
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
return def_val;
|
||||
}
|
||||
|
||||
vector<float> NodeAttrHelper::Get(const std::string& key,
|
||||
const vector<float>& def_val) {
|
||||
if (!HasAttr(key)) {
|
||||
return def_val;
|
||||
}
|
||||
|
||||
for (int i = 0; i < node_.attribute_size(); i++) {
|
||||
const ONNX_NAMESPACE::AttributeProto& attr = node_.attribute(i);
|
||||
if (attr.name() == key) {
|
||||
std::vector<float> v;
|
||||
v.reserve(static_cast<size_t>(attr.floats_size()));
|
||||
for (int j = 0; j < attr.floats_size(); j++) {
|
||||
v.push_back(attr.floats(j));
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
return def_val;
|
||||
}
|
||||
|
||||
bool NodeAttrHelper::HasAttr(const std::string& key) {
|
||||
for (int i = 0; i < node_.attribute_size(); i++) {
|
||||
const ONNX_NAMESPACE::AttributeProto& attr = node_.attribute(i);
|
||||
if (attr.name() == key) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
//
|
||||
// Created by daquexian on 8/3/18.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <onnx/onnx_pb.h>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* Wrapping onnx::NodeProto for retrieving attribute values
|
||||
*/
|
||||
class NodeAttrHelper {
|
||||
public:
|
||||
NodeAttrHelper(const ONNX_NAMESPACE::NodeProto& proto);
|
||||
|
||||
float Get(const std::string& key, float def_val);
|
||||
int32_t Get(const std::string& key, int32_t def_val);
|
||||
std::vector<float> Get(const std::string& key, const std::vector<float>& def_val);
|
||||
std::vector<int32_t> Get(const std::string& key, const std::vector<int32_t>& def_val);
|
||||
std::string Get(const std::string& key, const std::string& def_val);
|
||||
|
||||
bool HasAttr(const std::string& key);
|
||||
|
||||
private:
|
||||
const ONNX_NAMESPACE::NodeProto& node_;
|
||||
};
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -13,27 +13,22 @@ class IOpBuilder {
|
|||
virtual ~IOpBuilder() = default;
|
||||
|
||||
// Check if an operator is supported
|
||||
virtual bool IsOpSupported(ModelBuilder& model_builder,
|
||||
const ONNX_NAMESPACE::NodeProto& node) = 0;
|
||||
virtual bool IsOpSupported(ModelBuilder& model_builder, const Node& node) = 0;
|
||||
|
||||
// Check if the initializers of this operator need preprocess
|
||||
// which will not be copied
|
||||
virtual void AddInitializersToSkip(ModelBuilder& model_builder,
|
||||
const ONNX_NAMESPACE::NodeProto& node) = 0;
|
||||
virtual void AddInitializersToSkip(ModelBuilder& model_builder, const Node& node) = 0;
|
||||
|
||||
// Add the operator to NNAPI model
|
||||
virtual void AddToModelBuilder(ModelBuilder& model_builder,
|
||||
const ONNX_NAMESPACE::NodeProto& node) = 0;
|
||||
virtual void AddToModelBuilder(ModelBuilder& model_builder, const Node& node) = 0;
|
||||
};
|
||||
|
||||
// Generate a lookup table with IOpBuilder delegates
|
||||
// for different onnx operators
|
||||
std::unordered_map<std::string, std::shared_ptr<IOpBuilder>>
|
||||
CreateOpBuilders();
|
||||
std::unordered_map<std::string, std::shared_ptr<IOpBuilder>> CreateOpBuilders();
|
||||
|
||||
void TransposeNHWCToNCHW(ModelBuilder& model_builder,
|
||||
const std::string& input,
|
||||
const std::string& output);
|
||||
// Transpose the NHWCinput to NCHW output
|
||||
void TransposeNHWCToNCHW(ModelBuilder& model_builder, const std::string& input, const std::string& output);
|
||||
|
||||
} // namespace nnapi
|
||||
} // namespace onnxruntime
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "core/providers/nnapi/nnapi_builtin/nnapi_lib/NeuralNetworksWrapper.h"
|
||||
|
||||
#include "helper.h"
|
||||
#include "shaper.h"
|
||||
|
||||
|
|
@ -378,6 +379,41 @@ void Shaper::Concat(const std::vector<std::string>& input_names,
|
|||
}
|
||||
}
|
||||
|
||||
void Shaper::Squeeze(const std::string& input_name,
|
||||
const std::vector<int32_t>& axes,
|
||||
const std::string& output_name) {
|
||||
std::vector<uint32_t> input_dimen = shape_map_.at(input_name);
|
||||
int32_t input_size = input_dimen.size();
|
||||
size_t axes_size = axes.size();
|
||||
std::unordered_set<int32_t> axes_to_be_squeezed;
|
||||
if (axes_size == 0) {
|
||||
for (int32_t idx = 0; idx < input_size; ++idx) {
|
||||
if (input_dimen[idx] == 1)
|
||||
axes_to_be_squeezed.insert(idx);
|
||||
}
|
||||
} else {
|
||||
for (const auto& axis : axes)
|
||||
axes_to_be_squeezed.insert(axis);
|
||||
}
|
||||
|
||||
// Make output dimensions
|
||||
std::vector<uint32_t> output_dimen;
|
||||
output_dimen.reserve(input_size - axes_to_be_squeezed.size());
|
||||
for (int32_t i = 0; i < input_size; i++) {
|
||||
if (!Contains(axes_to_be_squeezed, i))
|
||||
output_dimen.push_back(input_dimen[i]);
|
||||
}
|
||||
|
||||
shape_map_[output_name] = output_dimen;
|
||||
|
||||
if (!shaper_finalized_) {
|
||||
shape_ops_.push_back(
|
||||
[input_name, axes, output_name](Shaper& shaper) {
|
||||
shaper.Squeeze(input_name, axes, output_name);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Shaper::AddShape(const std::string& name, const Shape& shape) {
|
||||
shape_map_[name] = shape;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ class Shaper {
|
|||
using Shape = std::vector<uint32_t>;
|
||||
|
||||
void AddShape(const std::string& name, const Shape& shape);
|
||||
inline const Shape& operator[](const std::string& key) const {
|
||||
return shape_map_.at(key);
|
||||
}
|
||||
|
||||
void Conv(const std::string& input_name,
|
||||
const std::string& weight_name,
|
||||
|
|
@ -33,23 +36,19 @@ class Shaper {
|
|||
bool nchw,
|
||||
const std::string& output_name);
|
||||
|
||||
void Reshape(const std::string& input_name,
|
||||
const std::vector<int32_t>& shape,
|
||||
const std::string& output_name);
|
||||
void Transpose(const std::string& input_name,
|
||||
const std::vector<int32_t>& perm,
|
||||
const std::string& output_name);
|
||||
void Eltwise(const std::string& input1_name, const std::string& input2_name,
|
||||
const std::string& output_name);
|
||||
void Identity(const std::string& input_name,
|
||||
const std::string& output_name);
|
||||
void FC(const std::string& input1_name,
|
||||
const std::string& input2_name,
|
||||
const std::string& output_name);
|
||||
void Reshape(const std::string& input_name, const std::vector<int32_t>& shape, const std::string& output_name);
|
||||
|
||||
void Concat(const std::vector<std::string>& input_names,
|
||||
const int32_t axis,
|
||||
const std::string& output_name);
|
||||
void Transpose(const std::string& input_name, const std::vector<int32_t>& perm, const std::string& output_name);
|
||||
|
||||
void Eltwise(const std::string& input1_name, const std::string& input2_name, const std::string& output_name);
|
||||
|
||||
void Identity(const std::string& input_name, const std::string& output_name);
|
||||
|
||||
void FC(const std::string& input1_name, const std::string& input2_name, const std::string& output_name);
|
||||
|
||||
void Concat(const std::vector<std::string>& input_names, const int32_t axis, const std::string& output_name);
|
||||
|
||||
void Squeeze(const std::string& input, const std::vector<int32_t>& axes, const std::string& output);
|
||||
|
||||
// If the shape of certain input is dynamic
|
||||
// Use the following 2 functions to update the particular shape
|
||||
|
|
@ -61,10 +60,6 @@ class Shaper {
|
|||
// is converted to NNAPI
|
||||
void Finalize() { shaper_finalized_ = true; }
|
||||
|
||||
inline const Shape& operator[](const std::string& key) const {
|
||||
return shape_map_.at(key);
|
||||
}
|
||||
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -43,51 +43,14 @@ NnapiExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_view
|
|||
const std::vector<const KernelRegistry*>& /*kernel_registries*/) const {
|
||||
std::vector<std::unique_ptr<ComputeCapability>> result;
|
||||
|
||||
// Need access to model_path_
|
||||
for (const auto& tensor : graph_view.GetAllInitializedTensors()) {
|
||||
if (tensor.second->has_data_location() &&
|
||||
tensor.second->data_location() == ONNX_NAMESPACE::TensorProto_DataLocation_EXTERNAL) {
|
||||
LOGS_DEFAULT(WARNING) << "NNAPI: Initializers with external data"
|
||||
" location are not currently supported";
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO, switch to use graph instead of model
|
||||
// This method is based on that of TRT EP
|
||||
// Construct modelproto from graph
|
||||
onnxruntime::Model model(graph_view.Name(), true, ModelMetaData(),
|
||||
PathString(),
|
||||
IOnnxRuntimeOpSchemaRegistryList(),
|
||||
graph_view.DomainToVersionMap(),
|
||||
std::vector<ONNX_NAMESPACE::FunctionProto>(),
|
||||
*GetLogger());
|
||||
std::unordered_set<std::string> all_node_inputs;
|
||||
onnxruntime::Graph& graph_build = model.MainGraph();
|
||||
for (const auto& node : graph_view.Nodes()) {
|
||||
std::vector<onnxruntime::NodeArg*> inputs, outputs;
|
||||
for (auto* input : node.InputDefs()) {
|
||||
auto& n_input = graph_build.GetOrCreateNodeArg(input->Name(), input->TypeAsProto());
|
||||
inputs.push_back(&n_input);
|
||||
all_node_inputs.insert(input->Name());
|
||||
}
|
||||
for (auto* output : node.OutputDefs()) {
|
||||
auto& n_output = graph_build.GetOrCreateNodeArg(output->Name(), output->TypeAsProto());
|
||||
outputs.push_back(&n_output);
|
||||
}
|
||||
graph_build.AddNode(node.Name(), node.OpType(), node.Description(), inputs, outputs, &node.GetAttributes(), node.Domain());
|
||||
}
|
||||
//Add initializer to graph
|
||||
const auto& init_tensors = graph_view.GetAllInitializedTensors();
|
||||
for (const auto& tensor : init_tensors) {
|
||||
graph_build.AddInitializedTensor(*(tensor.second));
|
||||
}
|
||||
|
||||
ORT_ENFORCE(graph_build.Resolve().IsOK());
|
||||
ONNX_NAMESPACE::ModelProto model_proto = model.ToProto();
|
||||
model_proto.set_ir_version(ONNX_NAMESPACE::Version::IR_VERSION);
|
||||
|
||||
nnapi::ModelBuilder builder(model_proto);
|
||||
nnapi::ModelBuilder builder(graph_view);
|
||||
const auto supported_nodes_vector = builder.GetSupportedNodes();
|
||||
|
||||
// Find inputs, initializers and outputs for each supported subgraph
|
||||
|
|
@ -179,9 +142,7 @@ NnapiExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_view
|
|||
for (auto it = fused_outputs.begin(), end = fused_outputs.end(); it != end; ++it) {
|
||||
if (all_node_inputs.find(it->first->Name()) != all_node_inputs.end()) {
|
||||
outputs.insert(std::pair<int, const NodeArg*>(it->second, it->first));
|
||||
}
|
||||
|
||||
if (std::find(graph_outputs.begin(), graph_outputs.end(), it->first) != graph_outputs.end()) {
|
||||
} else if (std::find(graph_outputs.begin(), graph_outputs.end(), it->first) != graph_outputs.end()) {
|
||||
outputs.insert(std::pair<int, const NodeArg*>(it->second, it->first));
|
||||
}
|
||||
}
|
||||
|
|
@ -218,16 +179,11 @@ common::Status NnapiExecutionProvider::Compile(const std::vector<onnxruntime::No
|
|||
if (!func_body) {
|
||||
return common::Status(common::ONNXRUNTIME, common::INVALID_ARGUMENT, "Function body is empty");
|
||||
}
|
||||
const Graph& graph_body = func_body->Body();
|
||||
onnxruntime::Model model(graph_body.Name(), true, ModelMetaData(), PathString(),
|
||||
IOnnxRuntimeOpSchemaRegistryList(), graph_body.DomainToVersionMap(),
|
||||
std::vector<ONNX_NAMESPACE::FunctionProto>(), *GetLogger());
|
||||
ONNX_NAMESPACE::ModelProto model_proto = model.ToProto();
|
||||
*(model_proto.mutable_graph()) = graph_body.ToGraphProto();
|
||||
model_proto.set_ir_version(ONNX_NAMESPACE::Version::IR_VERSION);
|
||||
|
||||
const Graph& graph_body = func_body->Body();
|
||||
{
|
||||
nnapi::ModelBuilder builder(model_proto);
|
||||
onnxruntime::GraphViewer graph_viewer(graph_body);
|
||||
nnapi::ModelBuilder builder(graph_viewer);
|
||||
builder.SetUseNCHW(false);
|
||||
builder.SetUseFp16(false);
|
||||
std::unique_ptr<nnapi::Model> nnapi_model = builder.Compile();
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@
|
|||
#ifndef ANDROID_ML_NN_RUNTIME_NEURAL_NETWORKS_WRAPPER_H
|
||||
#define ANDROID_ML_NN_RUNTIME_NEURAL_NETWORKS_WRAPPER_H
|
||||
#include "nnapi_implementation.h"
|
||||
#include <math.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <numeric>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue