Add stub implementation of the NNAPI interface (#11288)

* Add stub implementation of the NNAPI interface so that model builder code can be unit tested on all platforms.

Needed to fix a lot of type mismatch warnings. As these don't occur on Android builds used static_cast for simplicity.
This commit is contained in:
Scott McKay 2022-04-27 15:39:09 +10:00 committed by GitHub
parent a937920e24
commit 63d4f45186
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 281 additions and 140 deletions

View file

@ -956,13 +956,14 @@ if (onnxruntime_USE_NNAPI_BUILTIN)
else()
file(GLOB
onnxruntime_providers_nnapi_cc_srcs_nested CONFIGURE_DEPENDS
"${ONNXRUNTIME_ROOT}/core/providers/nnapi/nnapi_builtin/model.*"
"${ONNXRUNTIME_ROOT}/core/providers/nnapi/nnapi_builtin/nnapi_execution_provider.h"
"${ONNXRUNTIME_ROOT}/core/providers/nnapi/nnapi_builtin/nnapi_execution_provider.cc"
"${ONNXRUNTIME_ROOT}/core/providers/nnapi/nnapi_builtin/builders/helper.h"
"${ONNXRUNTIME_ROOT}/core/providers/nnapi/nnapi_builtin/builders/helper.cc"
"${ONNXRUNTIME_ROOT}/core/providers/nnapi/nnapi_builtin/builders/op_support_checker.h"
"${ONNXRUNTIME_ROOT}/core/providers/nnapi/nnapi_builtin/builders/op_support_checker.cc"
"${ONNXRUNTIME_ROOT}/core/providers/nnapi/nnapi_builtin/builders/*.h"
"${ONNXRUNTIME_ROOT}/core/providers/nnapi/nnapi_builtin/builders/*.cc"
"${ONNXRUNTIME_ROOT}/core/providers/nnapi/nnapi_builtin/nnapi_lib/NeuralNetworksTypes.h"
"${ONNXRUNTIME_ROOT}/core/providers/nnapi/nnapi_builtin/nnapi_lib/NeuralNetworksWrapper.*"
"${ONNXRUNTIME_ROOT}/core/providers/nnapi/nnapi_builtin/nnapi_lib/nnapi_implementation.*"
)
endif()

View file

@ -26,7 +26,7 @@ ModelBuilder::ModelBuilder(const GraphViewer& graph_viewer)
: nnapi_(NnApiImplementation()), graph_viewer_(graph_viewer) {}
int32_t ModelBuilder::GetNNAPIFeatureLevel() const {
return nnapi_ ? nnapi_->nnapi_runtime_feature_level : 0;
return nnapi_ ? static_cast<int32_t>(nnapi_->nnapi_runtime_feature_level) : 0;
}
// Scalar operand is copied into the model, no need to persist
@ -530,8 +530,8 @@ Status ModelBuilder::AddOperation(int op, const std::vector<uint32_t>& input_ind
RETURN_STATUS_ON_ERROR_WITH_NOTE(
nnapi_->ANeuralNetworksModel_addOperation(
nnapi_model_->model_, op, input_indices.size(), &input_indices[0],
output_indices.size(), &output_indices[0]),
nnapi_model_->model_, op, static_cast<uint32_t>(input_indices.size()), &input_indices[0],
static_cast<uint32_t>(output_indices.size()), &output_indices[0]),
"op = " + std::to_string(op));
num_nnapi_ops_++;
@ -574,7 +574,7 @@ Status ModelBuilder::Compile(std::unique_ptr<Model>& model) {
RETURN_STATUS_ON_ERROR_WITH_NOTE(
nnapi_->ANeuralNetworksModel_getSupportedOperationsForDevices(
nnapi_model_->model_, nnapi_target_devices_.data(),
nnapi_target_devices_.size(), supported_ops),
static_cast<uint32_t>(nnapi_target_devices_.size()), supported_ops),
"on getSupportedOperationsForDevices");
bool all_ops_supported = std::all_of(supported_ops, supported_ops + num_nnapi_ops_,
@ -598,7 +598,7 @@ Status ModelBuilder::Compile(std::unique_ptr<Model>& model) {
RETURN_STATUS_ON_ERROR_WITH_NOTE(
nnapi_->ANeuralNetworksCompilation_createForDevices(
nnapi_model_->model_, nnapi_target_devices_.data(),
nnapi_target_devices_.size(), &nnapi_model_->compilation_),
static_cast<uint32_t>(nnapi_target_devices_.size()), &nnapi_model_->compilation_),
"on createForDevices");
} else {
RETURN_STATUS_ON_ERROR_WITH_NOTE(

View file

@ -118,7 +118,7 @@ static Status AddSqueezeOp(ModelBuilder& model_builder,
if (axes.empty()) { // Squeeze all
for (size_t i = 0; i < input_dims; i++) {
if (input_shape[i] == 1)
axes.push_back(i);
axes.push_back(static_cast<int32_t>(i));
}
}
@ -725,12 +725,12 @@ Status TransposeOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, co
const auto& output = node_unit.Outputs()[0].node_arg.Name();
NodeAttrHelper helper(node_unit);
std::vector<int32_t> perm = helper.Get("perm", std::vector<int32_t>());
auto input_dims = shaper[input].size();
auto input_dims = static_cast<int32_t>(shaper[input].size());
if (perm.empty()) {
for (int32_t i = input_dims - 1; i >= 0; i--)
perm.push_back(i);
} else {
ORT_RETURN_IF_NOT(perm.size() == input_dims, "Perm and input should have same dimension");
ORT_RETURN_IF_NOT(static_cast<int32_t>(perm.size()) == input_dims, "Perm and input should have same dimension");
}
// Check if the quantization scale and ZP are correct
@ -1980,7 +1980,7 @@ Status ConcatOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const
y_scale, y_zero_point));
}
int rank = shaper[input0].size();
int32_t rank = static_cast<int32_t>(shaper[input0].size());
int32_t axis = static_cast<int32_t>(HandleNegativeAxis(helper.Get("axis", 1), rank));
ADD_SCALAR_OPERAND(model_builder, input_indices, axis);

View file

@ -249,7 +249,7 @@ Status Shaper::ReshapeImpl(const std::string& input_name,
ORT_RETURN_IF_NOT(dim_i != 0, "NNAPI does not support 0 reshape dimension");
if (dim_i == -1) {
ORT_RETURN_IF_NOT(unk_dim_idx == -1, "Only one input dimension of Attr(shape) can be unknown!");
unk_dim_idx = i;
unk_dim_idx = static_cast<int>(i);
} else {
capacity *= dim_i;
output_dimen[i] = static_cast<uint32_t>(dim_i);
@ -260,7 +260,7 @@ Status Shaper::ReshapeImpl(const std::string& input_name,
if (input_size == 0)
output_dimen[unk_dim_idx] = 0;
else
output_dimen[unk_dim_idx] = input_size / capacity;
output_dimen[unk_dim_idx] = static_cast<uint32_t>(input_size / capacity);
capacity *= output_dimen[unk_dim_idx];
}
@ -372,7 +372,7 @@ Status Shaper::SqueezeImpl(const std::string& input_name,
const std::vector<int32_t>& axes,
const std::string& output_name) {
const Shape& input_dimen = shape_map_.at(input_name);
int32_t input_size = input_dimen.size();
int32_t input_size = static_cast<int32_t>(input_dimen.size());
std::unordered_set<int32_t> axes_to_be_squeezed;
// If the Op is squeezing all by not specifying axes, the axes is pre-populate
@ -403,11 +403,11 @@ Status Shaper::ResizeUsingScalesImpl(const std::string& input_name,
const std::string& output_name) {
Shape output_dimen = shape_map_.at(input_name);
if (nchw) {
output_dimen[2] *= scale_h;
output_dimen[3] *= scale_w;
output_dimen[2] = static_cast<uint32_t>(output_dimen[2] * scale_h);
output_dimen[3] = static_cast<uint32_t>(output_dimen[3] * scale_w);
} else { // nhwc
output_dimen[1] *= scale_h;
output_dimen[2] *= scale_w;
output_dimen[1] = static_cast<uint32_t>(output_dimen[1] * scale_h);
output_dimen[2] = static_cast<uint32_t>(output_dimen[2] * scale_w);
}
shape_map_[output_name] = output_dimen;
return Status::OK();
@ -458,4 +458,4 @@ void Shaper::Clear() {
}
} // namespace nnapi
} // namespace onnxruntime
} // namespace onnxruntime

View file

@ -3,6 +3,7 @@
#pragma once
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>
@ -121,4 +122,4 @@ class Shaper {
};
} // namespace nnapi
} // namespace onnxruntime
} // namespace onnxruntime

View file

@ -104,7 +104,7 @@ Status Model::PrepareForExecution(std::unique_ptr<Execution>& execution) {
}
int32_t Model::GetNNAPIFeatureLevel() const {
return nnapi_ ? nnapi_->nnapi_runtime_feature_level : 0;
return nnapi_ ? static_cast<int32_t>(nnapi_->nnapi_runtime_feature_level) : 0;
}
#pragma region Model::NNMemory
@ -159,7 +159,7 @@ Execution::~Execution() {
Status Execution::SetInputBuffers(const std::vector<InputBuffer>& inputs) {
for (size_t i = 0; i < inputs.size(); i++) {
const auto& input(inputs[i]);
ORT_RETURN_IF_ERROR(SetInputBuffer(i, input));
ORT_RETURN_IF_ERROR(SetInputBuffer(static_cast<int32_t>(i), input));
ORT_RETURN_IF_ERROR(shaper_.UpdateShape(input.name, input.type.dimensions));
}
@ -169,7 +169,7 @@ Status Execution::SetInputBuffers(const std::vector<InputBuffer>& inputs) {
Status Execution::SetOutputBuffers(const std::vector<OutputBuffer>& outputs) {
for (size_t i = 0; i < outputs.size(); i++) {
ORT_RETURN_IF_ERROR(SetOutputBuffer(i, outputs[i]));
ORT_RETURN_IF_ERROR(SetOutputBuffer(static_cast<int32_t>(i), outputs[i]));
}
return Status::OK();
@ -216,4 +216,4 @@ Status Execution::Predict(const std::vector<int32_t>& dynamic_outputs, std::vect
#pragma endregion
} // namespace nnapi
} // namespace onnxruntime
} // namespace onnxruntime

View file

@ -14,7 +14,9 @@ struct NnApi;
namespace onnxruntime {
namespace nnapi {
#if defined(__ANDROID__)
#define USENNAPISHAREDMEM 1
#endif
class Execution;
@ -189,4 +191,4 @@ class Execution {
};
} // namespace nnapi
} // namespace onnxruntime
} // namespace onnxruntime

View file

@ -10,17 +10,14 @@
#include "core/platform/env.h"
#include "core/providers/common.h"
#include "core/providers/nnapi/nnapi_builtin/builders/helper.h"
#include "core/providers/nnapi/nnapi_builtin/builders/model_builder.h"
#include "core/providers/nnapi/nnapi_builtin/builders/op_support_checker.h"
#include "core/providers/nnapi/nnapi_builtin/model.h"
#include "core/providers/nnapi/nnapi_builtin/nnapi_lib/nnapi_implementation.h"
#include "core/providers/partitioning_utils.h"
#include "core/providers/shared/node_unit/node_unit.h"
#include "core/session/onnxruntime_cxx_api.h"
#ifdef __ANDROID__
#include "core/providers/nnapi/nnapi_builtin/builders/model_builder.h"
#include "core/providers/nnapi/nnapi_builtin/model.h"
#endif
namespace onnxruntime {
namespace {
@ -239,6 +236,7 @@ static Status GetOutputBuffer(Ort::CustomOpApi& ort,
return Status::OK();
}
#endif // __ANDROID__
common::Status NnapiExecutionProvider::Compile(const std::vector<FusedNodeAndGraph>& fused_nodes_and_graphs,
std::vector<NodeComputeInfo>& node_compute_funcs) {
@ -355,6 +353,7 @@ common::Status NnapiExecutionProvider::Compile(const std::vector<FusedNodeAndGra
ort.ReleaseTensorTypeAndShapeInfo(tensor_info);
}
#ifdef __ANDROID__
// From this point we will need to take the exclusive lock on the model until the Predict is
// performed, to block other threads to perform Predict on the same model
// TODO, investigate concurrent runs for different executions from the same model
@ -439,29 +438,17 @@ common::Status NnapiExecutionProvider::Compile(const std::vector<FusedNodeAndGra
memcpy(onnx_output_buffer, model_output_buffer, output_buffer_byte_size);
}
}
return Status::OK();
#else
// we have a stubbed out NNAPI implementation, so at this point there's nothing else we can do.
return ORT_MAKE_STATUS(ONNXRUNTIME, NOT_IMPLEMENTED, "Model execution is not supported in this build.");
#endif
};
node_compute_funcs.push_back(compute_info);
}
return Status::OK();
}
#else
common::Status NnapiExecutionProvider::Compile(const std::vector<FusedNodeAndGraph>& fused_nodes_and_graphs,
std::vector<NodeComputeInfo>& node_compute_funcs) {
for (const auto& fused_node_and_graph : fused_nodes_and_graphs) {
ORT_UNUSED_PARAMETER(fused_node_and_graph);
NodeComputeInfo compute_info;
compute_info.create_state_func = [](ComputeContext* /*context*/, FunctionState* /*state*/) { return 0; };
compute_info.release_state_func = [](FunctionState /*state*/) {};
compute_info.compute_func = [](FunctionState /* state */, const OrtCustomOpApi* /* api */,
OrtKernelContext* /* context */) {
return ORT_MAKE_STATUS(ONNXRUNTIME, NOT_IMPLEMENTED, "Compute is not supported in this build.");
};
node_compute_funcs.push_back(compute_info);
}
return Status::OK();
}
#endif // __ANDROID__
} // namespace onnxruntime

View file

@ -42,8 +42,6 @@ class NnapiExecutionProvider : public IExecutionProvider {
const std::unordered_set<std::string> partitioning_stop_ops_;
#ifdef __ANDROID__
std::unordered_map<std::string, std::unique_ptr<onnxruntime::nnapi::Model>> nnapi_models_;
#endif
};
} // namespace onnxruntime

View file

@ -25,22 +25,22 @@ namespace wrapper {
OperandType::OperandType(Type type, const std::vector<uint32_t>& d, float scale, int32_t zeroPoint)
: type(type), dimensions(d) {
operandType = {
.type = static_cast<int32_t>(type),
.dimensionCount = static_cast<uint32_t>(dimensions.size()),
.dimensions = dimensions.size() > 0 ? dimensions.data() : nullptr,
.scale = scale,
.zeroPoint = zeroPoint,
/*.type = */ static_cast<int32_t>(type),
/*.dimensionCount = */ static_cast<uint32_t>(dimensions.size()),
/*.dimensions = */ dimensions.size() > 0 ? dimensions.data() : nullptr,
/*.scale = */ scale,
/*.zeroPoint = */ zeroPoint,
};
}
OperandType::OperandType(Type type, const std::vector<uint32_t>& d, SymmPerChannelQuantParams&& channelQuant)
: type(type), dimensions(d), channelQuant(std::move(channelQuant)) {
operandType = {
.type = static_cast<int32_t>(type),
.dimensionCount = static_cast<uint32_t>(dimensions.size()),
.dimensions = dimensions.size() > 0 ? dimensions.data() : nullptr,
.scale = 0.0f,
.zeroPoint = 0,
/*.type = */ static_cast<int32_t>(type),
/*.dimensionCount = */ static_cast<uint32_t>(dimensions.size()),
/*.dimensions = */ dimensions.size() > 0 ? dimensions.data() : nullptr,
/*.scale = */ 0.0f,
/*.zeroPoint = */ 0,
};
}
@ -99,16 +99,16 @@ size_t OperandType::GetElementByteSize() const {
size_t OperandType::GetOperandBlobByteSize() const {
// use uin64_t even dimension is uint32_t to prevent overflow
uint64_t num_elements = std::accumulate(dimensions.begin(), dimensions.end(), 1, std::multiplies<uint64_t>());
uint64_t num_elements = std::accumulate(dimensions.begin(), dimensions.end(), uint64_t(1), std::multiplies<uint64_t>());
return SafeInt<size_t>(num_elements) * GetElementByteSize();
}
void OperandType::SetDimensions(const std::vector<uint32_t>& d) {
dimensions = d;
operandType.dimensionCount = dimensions.size();
operandType.dimensionCount = static_cast<uint32_t>(dimensions.size());
operandType.dimensions = dimensions.size() > 0 ? dimensions.data() : nullptr;
}
} // namespace wrapper
} // namespace nn
} // namespace android
} // namespace android

View file

@ -50,6 +50,11 @@ enum class ExecutePreference {
PREFER_SUSTAINED_SPEED = ANEURALNETWORKS_PREFER_SUSTAINED_SPEED
};
// handle clash with existing #define on Windows
#ifdef _MSC_VER
#undef NO_ERROR
#endif
enum class Result {
NO_ERROR = ANEURALNETWORKS_NO_ERROR,
OUT_OF_MEMORY = ANEURALNETWORKS_OUT_OF_MEMORY,
@ -103,9 +108,9 @@ struct SymmPerChannelQuantParams {
SymmPerChannelQuantParams(std::vector<float> scalesVec, uint32_t channelDim)
: scales(std::move(scalesVec)) {
params = {
.channelDim = channelDim,
.scaleCount = static_cast<uint32_t>(scales.size()),
.scales = scales.size() > 0 ? scales.data() : nullptr,
/*.channelDim = */ channelDim,
/*.scaleCount =*/static_cast<uint32_t>(scales.size()),
/*.scales = */ scales.size() > 0 ? scales.data() : nullptr,
};
}
SymmPerChannelQuantParams(const SymmPerChannelQuantParams& other)

View file

@ -11,6 +11,9 @@ limitations under the License.
==============================================================================*/
#include "nnapi_implementation.h"
// use real implementation on Android.
// use stub on other platforms to allow unit testing model building.
#if defined(__ANDROID__)
#include <dlfcn.h>
#include <fcntl.h>
#include <sys/mman.h>
@ -198,14 +201,10 @@ const NnApi LoadNnApi() {
LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksModel_finish);
LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksModel_addOperand);
LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksModel_setOperandValue);
LOAD_FUNCTION_OPTIONAL(
libneuralnetworks,
ANeuralNetworksModel_setOperandSymmPerChannelQuantParams);
LOAD_FUNCTION(libneuralnetworks,
ANeuralNetworksModel_setOperandValueFromMemory);
LOAD_FUNCTION_OPTIONAL(libneuralnetworks, ANeuralNetworksModel_setOperandSymmPerChannelQuantParams);
LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksModel_setOperandValueFromMemory);
LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksModel_addOperation);
LOAD_FUNCTION(libneuralnetworks,
ANeuralNetworksModel_identifyInputsAndOutputs);
LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksModel_identifyInputsAndOutputs);
LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksCompilation_create);
LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksCompilation_free);
LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksCompilation_setPreference);
@ -215,8 +214,7 @@ const NnApi LoadNnApi() {
LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksExecution_setInput);
LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksExecution_setInputFromMemory);
LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksExecution_setOutput);
LOAD_FUNCTION(libneuralnetworks,
ANeuralNetworksExecution_setOutputFromMemory);
LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksExecution_setOutputFromMemory);
LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksExecution_startCompute);
LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksEvent_wait);
LOAD_FUNCTION(libneuralnetworks, ANeuralNetworksEvent_free);
@ -331,6 +329,173 @@ const NnApi LoadNnApi() {
} // namespace
#else
//
// Stub out the API with default implementations that do nothing.
//
#include "core/providers/nnapi/nnapi_builtin/builders/helper.h"
#include "core/providers/nnapi/nnapi_builtin/nnapi_lib/NeuralNetworksTypes.h"
const NnApi LoadNnApi() {
NnApi nnapi = {};
nnapi.android_sdk_version = ORT_NNAPI_MAX_SUPPORTED_API_LEVEL;
nnapi.nnapi_runtime_feature_level = ANEURALNETWORKS_FEATURE_LEVEL_5;
nnapi.ANeuralNetworksMemory_createFromFd =
[](size_t, int, int, size_t, ANeuralNetworksMemory**) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksMemory_free = [](ANeuralNetworksMemory*) {};
nnapi.ANeuralNetworksModel_create = [](ANeuralNetworksModel**) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksModel_free = [](ANeuralNetworksModel*) {};
nnapi.ANeuralNetworksModel_finish = [](ANeuralNetworksModel*) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksModel_addOperand = [](ANeuralNetworksModel*, const ANeuralNetworksOperandType*) {
return int(ANEURALNETWORKS_NO_ERROR);
};
nnapi.ANeuralNetworksModel_setOperandValue = [](ANeuralNetworksModel*, int32_t, const void*, size_t) {
return int(ANEURALNETWORKS_NO_ERROR);
};
nnapi.ANeuralNetworksModel_setOperandSymmPerChannelQuantParams =
[](ANeuralNetworksModel*, int32_t, const ANeuralNetworksSymmPerChannelQuantParams*) {
return int(ANEURALNETWORKS_NO_ERROR);
};
nnapi.ANeuralNetworksModel_setOperandValueFromMemory =
[](ANeuralNetworksModel*, int32_t, const ANeuralNetworksMemory*, size_t, size_t) {
return int(ANEURALNETWORKS_NO_ERROR);
};
nnapi.ANeuralNetworksModel_addOperation =
[](ANeuralNetworksModel*, ANeuralNetworksOperationType, uint32_t, const uint32_t*, uint32_t, const uint32_t*) {
return int(ANEURALNETWORKS_NO_ERROR);
};
nnapi.ANeuralNetworksModel_identifyInputsAndOutputs =
[](ANeuralNetworksModel*, uint32_t, const uint32_t*, uint32_t, const uint32_t*) {
return int(ANEURALNETWORKS_NO_ERROR);
};
nnapi.ANeuralNetworksModel_relaxComputationFloat32toFloat16 =
[](ANeuralNetworksModel*, bool) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksCompilation_create =
[](ANeuralNetworksModel*, ANeuralNetworksCompilation**) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksCompilation_free = [](ANeuralNetworksCompilation*) {};
nnapi.ANeuralNetworksCompilation_setPreference =
[](ANeuralNetworksCompilation*, int32_t) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksCompilation_finish =
[](ANeuralNetworksCompilation*) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksExecution_create =
[](ANeuralNetworksCompilation*, ANeuralNetworksExecution**) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksExecution_free = [](ANeuralNetworksExecution*) {};
nnapi.ANeuralNetworksExecution_setInput =
[](ANeuralNetworksExecution*, int32_t, const ANeuralNetworksOperandType*, const void*, size_t) {
return int(ANEURALNETWORKS_NO_ERROR);
};
nnapi.ANeuralNetworksExecution_setInputFromMemory =
[](ANeuralNetworksExecution*, int32_t, const ANeuralNetworksOperandType*, const ANeuralNetworksMemory*, size_t, size_t) {
return int(ANEURALNETWORKS_NO_ERROR);
};
nnapi.ANeuralNetworksExecution_setOutput =
[](ANeuralNetworksExecution*, int32_t, const ANeuralNetworksOperandType*, void*, size_t) {
return int(ANEURALNETWORKS_NO_ERROR);
};
nnapi.ANeuralNetworksExecution_setOutputFromMemory =
[](ANeuralNetworksExecution*, int32_t, const ANeuralNetworksOperandType*, const ANeuralNetworksMemory*, size_t, size_t) {
return int(ANEURALNETWORKS_NO_ERROR);
};
nnapi.ANeuralNetworksExecution_startCompute =
[](ANeuralNetworksExecution*, ANeuralNetworksEvent**) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksEvent_wait = [](ANeuralNetworksEvent*) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksEvent_free = [](ANeuralNetworksEvent*) {};
nnapi.ASharedMemory_create = [](const char*, size_t) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworks_getDeviceCount = [](uint32_t*) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworks_getDevice = [](uint32_t, ANeuralNetworksDevice**) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksDevice_getName =
[](const ANeuralNetworksDevice*, const char**) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksDevice_getVersion =
[](const ANeuralNetworksDevice*, const char**) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksDevice_getFeatureLevel =
[](const ANeuralNetworksDevice*, int64_t* feature_level) {
*feature_level = ANEURALNETWORKS_FEATURE_LEVEL_5;
return int(ANEURALNETWORKS_NO_ERROR);
};
nnapi.ANeuralNetworksDevice_getType =
[](const ANeuralNetworksDevice*, int32_t*) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksModel_getSupportedOperationsForDevices =
[](const ANeuralNetworksModel*, const ANeuralNetworksDevice* const*, uint32_t, bool*) {
return int(ANEURALNETWORKS_NO_ERROR);
};
nnapi.ANeuralNetworksCompilation_createForDevices =
[](ANeuralNetworksModel*, const ANeuralNetworksDevice* const*, uint32_t, ANeuralNetworksCompilation**) {
return int(ANEURALNETWORKS_NO_ERROR);
};
nnapi.ANeuralNetworksCompilation_setCaching =
[](ANeuralNetworksCompilation*, const char*, const uint8_t*) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksCompilation_setTimeout =
[](ANeuralNetworksCompilation*, uint64_t) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksCompilation_setPriority =
[](ANeuralNetworksCompilation*, int) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksExecution_compute =
[](ANeuralNetworksExecution*) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksExecution_setTimeout =
[](ANeuralNetworksExecution*, uint64_t) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksExecution_setLoopTimeout =
[](ANeuralNetworksExecution*, uint64_t) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksExecution_getOutputOperandRank =
[](ANeuralNetworksExecution*, int32_t, uint32_t*) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksExecution_getOutputOperandDimensions =
[](ANeuralNetworksExecution*, int32_t, uint32_t*) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksBurst_create =
[](ANeuralNetworksCompilation*, ANeuralNetworksBurst**) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksBurst_free = [](ANeuralNetworksBurst*) {};
nnapi.ANeuralNetworksExecution_burstCompute =
[](ANeuralNetworksExecution*, ANeuralNetworksBurst*) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksMemory_createFromAHardwareBuffer =
[](const AHardwareBuffer*, ANeuralNetworksMemory**) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksExecution_setMeasureTiming =
[](ANeuralNetworksExecution*, bool) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksExecution_getDuration =
[](const ANeuralNetworksExecution*, int32_t, uint64_t*) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksDevice_getExtensionSupport =
[](const ANeuralNetworksDevice*, const char*, bool*) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksModel_getExtensionOperandType =
[](ANeuralNetworksModel*, const char*, uint16_t, int32_t*) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksModel_getExtensionOperationType =
[](ANeuralNetworksModel*, const char*, uint16_t, ANeuralNetworksOperationType*) {
return int(ANEURALNETWORKS_NO_ERROR);
};
nnapi.ANeuralNetworksModel_setOperandExtensionData =
[](ANeuralNetworksModel*, int32_t, const void*, size_t) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksMemoryDesc_create = [](ANeuralNetworksMemoryDesc**) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksMemoryDesc_free = [](ANeuralNetworksMemoryDesc*) {};
nnapi.ANeuralNetworksMemoryDesc_addInputRole =
[](ANeuralNetworksMemoryDesc*, const ANeuralNetworksCompilation*, uint32_t, float) {
return int(ANEURALNETWORKS_NO_ERROR);
};
nnapi.ANeuralNetworksMemoryDesc_addOutputRole =
[](ANeuralNetworksMemoryDesc*, const ANeuralNetworksCompilation*, uint32_t, float) {
return int(ANEURALNETWORKS_NO_ERROR);
};
nnapi.ANeuralNetworksMemoryDesc_setDimensions =
[](ANeuralNetworksMemoryDesc*, uint32_t, const uint32_t*) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksMemoryDesc_finish =
[](ANeuralNetworksMemoryDesc*) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksMemory_createFromDesc =
[](const ANeuralNetworksMemoryDesc*, ANeuralNetworksMemory**) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksMemory_copy =
[](const ANeuralNetworksMemory*, const ANeuralNetworksMemory*) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksEvent_createFromSyncFenceFd =
[](int, ANeuralNetworksEvent**) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksEvent_getSyncFenceFd =
[](const ANeuralNetworksEvent*, int*) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksExecution_startComputeWithDependencies =
[](ANeuralNetworksExecution*, const ANeuralNetworksEvent* const*, uint32_t, uint64_t, ANeuralNetworksEvent**) {
return int(ANEURALNETWORKS_NO_ERROR);
};
nnapi.ANeuralNetworksExecution_enableInputAndOutputPadding =
[](ANeuralNetworksExecution*, bool) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworksExecution_setReusable =
[](ANeuralNetworksExecution*, bool) { return int(ANEURALNETWORKS_NO_ERROR); };
nnapi.ANeuralNetworks_getRuntimeFeatureLevel = []() { return int64_t(ANEURALNETWORKS_FEATURE_LEVEL_5); };
return nnapi;
}
#endif // defined(__ANDROID__)
const NnApi* NnApiImplementation() {
static const NnApi nnapi = LoadNnApi();
return &nnapi;

View file

@ -8,16 +8,17 @@ These files do not need to be updated frequently, unless new functionalities are
introduced in new Android OS versions, and we will integrate the new functionalities.
The modifications to these files,
NeuralNetworksTypes.h
* The enum ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES was added.
* The operation ANEURALNETWORKS_GROUPED_CONV_2D was added.
nnapi_implementation.h/cc
* CreateNnApiFromSupportLibrary was removed
[TODO, add support of CreateNnApiFromSupportLibrary for Android 12]
NeuralNetworksTypes.h
* The enum ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES was added.
* The operation ANEURALNETWORKS_GROUPED_CONV_2D was added.
nnapi_implementation.h/cc
* CreateNnApiFromSupportLibrary was removed
* A stub implementation was added to enable unit testing on non-Android platforms
* [TODO, add support of CreateNnApiFromSupportLibrary for Android 12]
2. Files: NeuralNetworksWrapper.h
NeuralNetworksWrapper.cc
This NeuralNetworksWrapper.h is copied from The Android Open Source Project
https://android.googlesource.com/platform/frameworks/ml/+/refs/heads/master/nn/runtime/include/
This file is heavily modified, and an extra NeuralNetworksWrapper.cc was added.
Please do not update these files.
This NeuralNetworksWrapper.h is copied from The Android Open Source Project
* https://android.googlesource.com/platform/frameworks/ml/+/refs/heads/master/nn/runtime/include/
This file is heavily modified, and an extra NeuralNetworksWrapper.cc was added.
Please do not update these files.

View file

@ -3,6 +3,8 @@
#if !defined(ORT_MINIMAL_BUILD) || defined(ORT_EXTENDED_MINIMAL_BUILD)
#include "core/common/logging/logging.h"
#include "core/providers/nnapi/nnapi_builtin/builders/op_builder.h"
#include "core/providers/nnapi/nnapi_builtin/builders/op_support_checker.h"
#include "core/providers/nnapi/nnapi_builtin/nnapi_execution_provider.h"
#include "core/providers/nnapi/nnapi_builtin/nnapi_lib/NeuralNetworksTypes.h"
#include "core/providers/nnapi/nnapi_builtin/nnapi_lib/nnapi_implementation.h"
@ -16,23 +18,15 @@
#include "test/util/include/test/test_environment.h"
#include "test/util/include/test_utils.h"
#if defined(__ANDROID__)
#include "core/providers/nnapi/nnapi_builtin/builders/op_builder.h"
#include "core/providers/nnapi/nnapi_builtin/builders/op_support_checker.h"
#endif
#if !defined(ORT_MINIMAL_BUILD)
// if this is a full build we need the provider test utils
#include "test/providers/provider_test_utils.h"
#include "test/optimizer/qdq_test_utils.h"
#endif
#include "gtest/gtest.h"
#include "gmock/gmock.h"
#if !defined(ORT_MINIMAL_BUILD)
#include "test/optimizer/qdq_test_utils.h"
#endif
using namespace std;
using namespace ONNX_NAMESPACE;
using namespace ::onnxruntime::logging;
@ -42,6 +36,17 @@ namespace test {
#if !defined(ORT_MINIMAL_BUILD)
namespace {
void TestModelLoad(const ORTCHAR_T* model_file_name, const std::function<void(const Graph&)>& check_graph) {
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());
check_graph(session_object.GetGraph());
}
} // namespace
// Since NNAPI EP handles Reshape and Flatten differently,
// Please see ReshapeOpBuilder::CanSkipReshape in
// <repo_root>/onnxruntime/core/providers/nnapi/nnapi_builtin/builders/op_builder.cc
@ -69,13 +74,9 @@ TEST(NnapiExecutionProviderTest, ReshapeFlattenTest) {
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_GT(CountAssignedNodes(session_object.GetGraph(), kNnapiExecutionProvider), 0)
<< "Some nodes should have been taken by the NNAPI EP";
TestModelLoad(model_file_name,
[](const Graph& graph) { ASSERT_GT(CountAssignedNodes(graph, kNnapiExecutionProvider), 0)
<< "Some nodes should have been taken by the NNAPI EP"; });
#endif
}
@ -100,14 +101,9 @@ TEST(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";
TestModelLoad(model_file_name,
[](const Graph& graph) { ASSERT_EQ(CountAssignedNodes(graph, kNnapiExecutionProvider), 1)
<< "Exactly one node (Add) should have been taken by the NNAPI EP"; });
#endif
}
@ -133,14 +129,9 @@ TEST(NnapiExecutionProviderTest, InternalUint8SupportTest) {
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_GT(CountAssignedNodes(session_object.GetGraph(), kNnapiExecutionProvider), 0)
<< "Some nodes should have been taken by the NNAPI EP";
TestModelLoad(model_file_name,
[](const Graph& graph) { ASSERT_GT(CountAssignedNodes(graph, kNnapiExecutionProvider), 0)
<< "Some nodes should have been taken by the NNAPI EP"; });
#endif
}
@ -217,14 +208,9 @@ TEST(NnapiExecutionProviderTest, FunctionTest) {
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_GT(CountAssignedNodes(session_object.GetGraph(), kNnapiExecutionProvider), 0)
<< "Some nodes should have been taken by the NNAPI EP";
TestModelLoad(model_file_name,
[](const Graph& graph) { ASSERT_GT(CountAssignedNodes(graph, kNnapiExecutionProvider), 0)
<< "Some nodes should have been taken by the NNAPI EP"; });
#endif
}
@ -267,11 +253,9 @@ TEST(NnapiExecutionProviderTest, TestNoShapeInputModel) {
// verify the entire graph will not be assigned to NNAPI EP
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), 0)
<< "No node should be taken by the NNAPI EP";
TestModelLoad(model_file_name,
[](const Graph& graph) { ASSERT_EQ(CountAssignedNodes(graph, kNnapiExecutionProvider), 0)
<< "No nodes should have been taken by the NNAPI EP"; });
}
static void RunQDQModelTest(
@ -520,13 +504,10 @@ TEST(NnapiExecutionProviderTest, TestOrtFormatModel) {
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_GT(CountAssignedNodes(session_object.GetGraph(), kNnapiExecutionProvider), 0)
<< "Some nodes should have been taken by the NNAPI EP";
TestModelLoad(model_file_name,
[](const Graph& graph) { ASSERT_GT(CountAssignedNodes(graph, kNnapiExecutionProvider), 0)
<< "Some nodes should have been taken by the NNAPI EP"; });
#endif
}

View file

@ -133,8 +133,8 @@ std::unique_ptr<IExecutionProvider> DefaultNupharExecutionProvider(bool allow_un
// }
std::unique_ptr<IExecutionProvider> DefaultNnapiExecutionProvider() {
// For any non - Android system, NNAPI will only be used for ort model converter
// Make it unavailable here, you can still manually append NNAPI EP to session for model conversion
// The NNAPI EP uses a stub implementation on non-Android platforms so cannot be used to execute a model.
// Manually append an NNAPI EP instance to the session to unit test the GetCapability and Compile implementation.
#if defined(USE_NNAPI) && defined(__ANDROID__)
return CreateExecutionProviderFactory_Nnapi(0, {})->CreateProvider();
#else