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:
gwang-msft 2020-07-06 18:44:04 -07:00 committed by GitHub
parent 632b2896f3
commit 7baf374939
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 665 additions and 722 deletions

View file

@ -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);
}

View file

@ -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_;
};

View file

@ -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) {

View file

@ -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

View file

@ -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;
}

View file

@ -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_;
};

View file

@ -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

View file

@ -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;
}

View file

@ -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:

View file

@ -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();

View file

@ -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>