Merge remote-tracking branch 'upstream/master' into DmlDev

This commit is contained in:
ISS Build Account 2020-10-23 12:34:04 +00:00
commit 49ec73e939
24 changed files with 591 additions and 178 deletions

View file

@ -510,6 +510,11 @@ cmake ../onnxruntime-arm-upstream/cmake -DONNX_CUSTOM_PROTOC_EXECUTABLE=/usr/bin
The ```-Donnxruntime_USE_ACL=ON``` option will use, by default, the 19.05 version of the Arm Compute Library. To set the right version you can use:
```-Donnxruntime_USE_ACL_1902=ON```, ```-Donnxruntime_USE_ACL_1905=ON```, ```-Donnxruntime_USE_ACL_1908=ON``` or ```-Donnxruntime_USE_ACL_2002=ON```;
To use a library outside the normal environment you can set a custom path by using ```-Donnxruntime_ACL_HOME``` and ```-Donnxruntime_ACL_LIBS``` tags that defines the path to the ComputeLibrary directory and the build directory respectively.
```-Donnxruntime_ACL_HOME=/path/to/ComputeLibrary```, ```-Donnxruntime_ACL_LIBS=/path/to/build```
2. Build ONNX Runtime library, test and performance application:
```
make -j 6
@ -542,6 +547,10 @@ export LD_LIBRARY_PATH=~/ComputeLibrary/build/
```
./build.sh --use_acl
```
To use a library outside the normal environment you can set a custom path by using --acl_home and --acl_libs tags that defines the path to the ComputeLibrary directory and the build directory respectively.
```
./build.sh --use_acl --acl_home /path/to/ComputeLibrary --acl_libs /path/to/build
```
---
@ -572,6 +581,11 @@ The Batch Normalization operator is set by default to use the CPU execution prov
./build.sh --use_armnn --armnn_bn
```
To use a library outside the normal environment you can set a custom path by using --armnn_home and --armnn_libs tags that defines the path to the ArmNN home directory and the build directory respectively.
```
./build.sh --use_armnn --armnn_home /path/to/ComputeLibrary --armnn_libs /path/to/build
```
---
### RKNPU

View file

@ -675,7 +675,28 @@ if (onnxruntime_USE_ACL OR onnxruntime_USE_ACL_1902 OR onnxruntime_USE_ACL_1905
endif()
endif()
if (NOT ${onnxruntime_ACL_LIBS} STREQUAL "")
add_library(arm_compute SHARED IMPORTED)
set_target_properties(arm_compute PROPERTIES
IMPORTED_NO_SONAME 1
IMPORTED_LOCATION "${onnxruntime_ACL_LIBS}/libarm_compute.so")
add_library(arm_compute_core SHARED IMPORTED)
set_target_properties(arm_compute_core PROPERTIES
IMPORTED_NO_SONAME 1
IMPORTED_LOCATION "${onnxruntime_ACL_LIBS}/libarm_compute_core.so")
add_library(arm_compute_graph SHARED IMPORTED)
set_target_properties(arm_compute_graph PROPERTIES
IMPORTED_NO_SONAME 1
IMPORTED_LOCATION "${onnxruntime_ACL_LIBS}/libarm_compute_graph.so")
link_libraries(arm_compute arm_compute_core arm_compute_graph)
endif()
list(APPEND onnxruntime_EXTERNAL_LIBRARIES arm_compute arm_compute_graph arm_compute_core)
endif()
# ArmNN
@ -687,6 +708,35 @@ if (onnxruntime_USE_ARMNN)
add_definitions(-DBN_ARMNN=1)
endif()
if (NOT onnxruntime_USE_ACL AND NOT ${onnxruntime_ACL_LIBS} STREQUAL "")
add_library(arm_compute SHARED IMPORTED)
set_target_properties(arm_compute PROPERTIES
IMPORTED_NO_SONAME 1
IMPORTED_LOCATION "${onnxruntime_ACL_LIBS}/libarm_compute.so")
add_library(arm_compute_core SHARED IMPORTED)
set_target_properties(arm_compute_core PROPERTIES
IMPORTED_NO_SONAME 1
IMPORTED_LOCATION "${onnxruntime_ACL_LIBS}/libarm_compute_core.so")
add_library(arm_compute_graph SHARED IMPORTED)
set_target_properties(arm_compute_graph PROPERTIES
IMPORTED_NO_SONAME 1
IMPORTED_LOCATION "${onnxruntime_ACL_LIBS}/libarm_compute_graph.so")
link_libraries(arm_compute arm_compute_core arm_compute_graph)
endif()
if (NOT ${onnxruntime_ARMNN_LIBS} STREQUAL "")
find_library(ARMNN_LIBRARY NAMES armnn
PATHS ${onnxruntime_ARMNN_LIBS}
NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
link_libraries(armnn)
endif()
list(APPEND onnxruntime_EXTERNAL_LIBRARIES armnn pthread arm_compute_core arm_compute arm_compute_graph)
endif()

View file

@ -766,7 +766,7 @@ if (onnxruntime_USE_ACL)
target_link_libraries(onnxruntime_providers_acl -L$ENV{LD_LIBRARY_PATH})
add_dependencies(onnxruntime_providers_acl ${onnxruntime_EXTERNAL_DEPENDENCIES})
set_target_properties(onnxruntime_providers_acl PROPERTIES FOLDER "ONNXRuntime")
target_include_directories(onnxruntime_providers_acl PRIVATE ${ONNXRUNTIME_ROOT} ${eigen_INCLUDE_DIRS} ${ACL_INCLUDE_DIR})
target_include_directories(onnxruntime_providers_acl PRIVATE ${ONNXRUNTIME_ROOT} ${eigen_INCLUDE_DIRS} ${onnxruntime_ACL_HOME} ${onnxruntime_ACL_HOME}/include)
install(DIRECTORY ${PROJECT_SOURCE_DIR}/../include/onnxruntime/core/providers/acl DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/onnxruntime/core/providers)
set_target_properties(onnxruntime_providers_acl PROPERTIES LINKER_LANGUAGE CXX)
endif()
@ -783,7 +783,7 @@ if (onnxruntime_USE_ARMNN)
onnxruntime_add_include_to_target(onnxruntime_providers_armnn onnxruntime_common onnxruntime_framework onnx onnx_proto protobuf::libprotobuf flatbuffers)
add_dependencies(onnxruntime_providers_armnn ${onnxruntime_EXTERNAL_DEPENDENCIES})
set_target_properties(onnxruntime_providers_armnn PROPERTIES FOLDER "ONNXRuntime")
target_include_directories(onnxruntime_providers_armnn PRIVATE ${ONNXRUNTIME_ROOT} ${eigen_INCLUDE_DIRS} ${ARMNN_INCLUDE_DIR})
target_include_directories(onnxruntime_providers_armnn PRIVATE ${ONNXRUNTIME_ROOT} ${eigen_INCLUDE_DIRS} ${onnxruntime_ARMNN_HOME} ${onnxruntime_ARMNN_HOME}/include)
install(DIRECTORY ${PROJECT_SOURCE_DIR}/../include/onnxruntime/core/providers/armnn DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/onnxruntime/core/providers)
set_target_properties(onnxruntime_providers_armnn PROPERTIES LINKER_LANGUAGE CXX)
endif()

View file

@ -302,7 +302,7 @@ Status SequentialExecutor::Execute(const SessionState& session_state, const std:
#endif
#ifdef ENABLE_NVTX_PROFILE
profile::NvtxRangeCreator node_compute_range(
MakeString(node.OpType(), ".", node.Index()), profile::Color::Blue);
MakeString(node.OpType(), ".", node.Index(), "(", node.Name(), ")"), profile::Color::Blue);
node_compute_range.Begin();
#endif
ORT_TRY {

View file

@ -137,8 +137,9 @@ std::vector<std::unique_ptr<GraphTransformer>> GenerateTransformers(TransformerL
transformers.emplace_back(onnxruntime::make_unique<DynamicQuantizeMatMulFusion>(cpu_execution_providers));
std::unordered_set<std::string> cpu_acl_execution_providers = {onnxruntime::kCpuExecutionProvider, onnxruntime::kAclExecutionProvider};
std::unordered_set<std::string> cpu_acl_armnn_execution_providers = {onnxruntime::kCpuExecutionProvider, onnxruntime::kAclExecutionProvider, onnxruntime::kArmNNExecutionProvider};
transformers.emplace_back(onnxruntime::make_unique<ConvActivationFusion>(cpu_acl_execution_providers));
transformers.emplace_back(onnxruntime::make_unique<ConvActivationFusion>(cpu_acl_armnn_execution_providers));
std::unordered_set<std::string> cpu_cuda_execution_providers = {onnxruntime::kCpuExecutionProvider, onnxruntime::kCudaExecutionProvider};
transformers.emplace_back(onnxruntime::make_unique<GeluFusion>(cpu_cuda_execution_providers));

View file

@ -59,6 +59,26 @@ arm_compute::Status ACLImportMemory(arm_compute::TensorAllocator* allocator, voi
#endif
}
template <typename T>
void importDataToTensor(arm_compute::Tensor* tensor, const T* data){
arm_compute::Window aclInpuWindow;
aclInpuWindow.use_tensor_dimensions(tensor->info()->tensor_shape());
arm_compute::Iterator aclInputIt(tensor, aclInpuWindow);
int index = 0;
// copy input tensor into the larger buffer
arm_compute::execute_window_loop(
aclInpuWindow,
[&](const arm_compute::Coordinates& co) {
*reinterpret_cast<float*>(aclInputIt.ptr()) = data[index];
index++;
},
aclInputIt);
}
template void importDataToTensor<float>(arm_compute::Tensor*, const float*);
template <typename T>
void importDataFromTensor(arm_compute::Tensor* tensor, T* data){

View file

@ -19,6 +19,8 @@ void ACLPrintTensorShape(const char*, arm_compute::Tensor& t);
std::shared_ptr<arm_compute::MemoryManagerOnDemand> ACLCreateMemoryManager();
arm_compute::Status ACLImportMemory(arm_compute::TensorAllocator* allocator, void* memory, size_t size);
template <typename T>
void importDataToTensor(arm_compute::Tensor* tensor, const T* data);
template <typename T>
void importDataFromTensor(arm_compute::Tensor* tensor, T* data);
} // namespace acl

View file

@ -40,36 +40,48 @@ class ONNX_OPERATOR_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 11, Co
class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kAclExecutionProvider, kMSDomain, 1, float, FusedConv);
static void RegisterACLKernels(KernelRegistry& kernel_registry) {
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 6, Relu)>());
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 7, 8, Gemm)>());
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 9, 10, Gemm)>());
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 11, Gemm)>());
Status RegisterACLKernels(KernelRegistry& kernel_registry) {
static const BuildKernelCreateInfoFn function_table[] = {
//BuildKernelCreateInfo<void>, //default entry to avoid the list become empty after ops-reducing
BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 6, Relu)>,
BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 7, 8, Gemm)>,
BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 9, 10, Gemm)>,
BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 11, Gemm)>,
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 1, Conv)>());
BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 1, Conv)>,
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 7, 9, float, AveragePool)>());
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 1, 7, float, MaxPool)>());
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 8, 11, float, MaxPool)>());
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 12, MaxPool)>());
BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 7, 9, float, AveragePool)>,
BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 1, 7, float, MaxPool)>,
BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 8, 11, float, MaxPool)>,
BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 12, MaxPool)>,
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 1, 8, float, GlobalAveragePool)>());
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 1, 8, float, GlobalMaxPool)>());
BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 1, 8, float, GlobalAveragePool)>,
BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 1, 8, float, GlobalMaxPool)>,
// Opset 10
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 10, 10, float, AveragePool)>());
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 11, AveragePool)>());
BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 10, 10, float, AveragePool)>,
BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 11, AveragePool)>,
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 7, 9, BatchNormalization)>());
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 4, 10, Concat)>());
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 11, Concat)>());
BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 7, 9, BatchNormalization)>,
BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 4, 10, Concat)>,
BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kAclExecutionProvider, kOnnxDomain, 11, Concat)>,
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kAclExecutionProvider, kMSDomain, 1, float, FusedConv)>());
BuildKernelCreateInfo<ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kAclExecutionProvider, kMSDomain, 1, float, FusedConv)>,
};
for (auto& function_table_entry : function_table) {
KernelCreateInfo info = function_table_entry();
if (info.kernel_def != nullptr) { // filter disabled entries where type is void
ORT_RETURN_IF_ERROR(kernel_registry.Register(std::move(info)));
}
}
return Status::OK();
}
std::shared_ptr<KernelRegistry> GetAclKernelRegistry() {
std::shared_ptr<KernelRegistry> kernel_registry = std::make_shared<KernelRegistry>();
RegisterACLKernels(*kernel_registry);
ORT_THROW_IF_ERROR(RegisterACLKernels(*kernel_registry));
return kernel_registry;
}

View file

@ -109,20 +109,7 @@ Status BatchNorm<T>::Compute(OpKernelContext* context) const {
if(X->Shape().Size() != 0 && pBatchNorm->in->info()->has_padding() ){
arm_compute::Window aclInpuWindow;
aclInpuWindow.use_tensor_dimensions(pBatchNorm->in->info()->tensor_shape());
arm_compute::Iterator aclInputIt(pBatchNorm->in.get(), aclInpuWindow);
const unsigned int aclWidth = pBatchNorm->in->info()->dimension(0);
const unsigned int aclHeight = pBatchNorm->in->info()->dimension(1);
// copy input tensor into the larger buffer
arm_compute::execute_window_loop(
aclInpuWindow,
[&](const arm_compute::Coordinates& co) {
*reinterpret_cast<float*>(aclInputIt.ptr()) = x_data[co.z() * (aclWidth * aclHeight) + co.y() * aclHeight + co.x()];
},
aclInputIt);
importDataToTensor<T>(pBatchNorm->in.get(), x_data);
}else{
ACLImportMemory(pBatchNorm->in->allocator(), (void*)x_data, X->Shape().Size() * 4);
}

View file

@ -216,7 +216,16 @@ Status Conv<T>::Compute(OpKernelContext* context) const {
1 /* depth multiplier */,
arm_compute::Size2D(aclDilation0, dilations[0]));
#elif defined(ACL_2002)
bool optimizable = false;
bool optimizable = bool(arm_compute::NEDepthwiseConvolutionLayerOptimized::validate(tconv.in->info(),
tconv.k->info(),
(B != nullptr) ? tconv.b->info() : nullptr,
tconv.out->info(),
aclPadStride,
1 /* depth multiplier */,
acl_activ_enabled ?
arm_compute::ActivationLayerInfo(acl_activ_func, conv_attrs_.alpha) :
arm_compute::ActivationLayerInfo(),
arm_compute::Size2D(aclDilation0, dilations[0])));
#endif
if (optimizable) {
@ -286,7 +295,12 @@ Status Conv<T>::Compute(OpKernelContext* context) const {
}
const T* x_data = X->template Data<T>();
ACLImportMemory(pConv->in->allocator(), (void*)x_data, X->Shape().Size() * 4);
if(X->Shape().Size() != 0 && pConv->in->info()->has_padding() ){
pConv->in->allocator()->allocate();
importDataToTensor<T>(pConv->in.get(), x_data);
}else{
ACLImportMemory(pConv->in->allocator(), (void*)x_data, X->Shape().Size() * 4);
}
const T* k_data = W->template Data<T>();
ACLImportMemory(pConv->k->allocator(), (void*)k_data, W->Shape().Size() * 4);
@ -297,13 +311,21 @@ Status Conv<T>::Compute(OpKernelContext* context) const {
}
T* y_data = Y->template MutableData<T>();
ACLImportMemory(pConv->out->allocator(), (void*)y_data, Y->Shape().Size() * 4);
if(Y->Shape().Size() != 0 && pConv->out->info()->has_padding() ){
pConv->out->allocator()->allocate();
} else {
ACLImportMemory(pConv->out->allocator(), (void*)y_data, Y->Shape().Size() * 4);
}
arm_compute::Allocator alloc_mm{};
pConv->mm_layer->populate(alloc_mm, 1);
pConv->layer->run();
pConv->mm_layer->clear();
if(Y->Shape().Size() != 0 && pConv->out->info()->has_padding() ){
importDataFromTensor<T>(pConv->out.get(), y_data);
}
pConv->in->allocator()->free();
pConv->k->allocator()->free();
if (B != nullptr)

View file

@ -87,20 +87,7 @@ Status Concat<T>::Compute(OpKernelContext* ctx) const {
if (X->Shape().Size() != 0 && in->info()->has_padding() ){
in->allocator()->allocate();
arm_compute::Window aclInpuWindow;
aclInpuWindow.use_tensor_dimensions(in->info()->tensor_shape());
arm_compute::Iterator aclInputIt(in, aclInpuWindow);
int index = 0;
// copy input tensor into the larger buffer
arm_compute::execute_window_loop(
aclInpuWindow,
[&](const arm_compute::Coordinates& co) {
*reinterpret_cast<float*>(aclInputIt.ptr()) = x_data[index];
index++;
},
aclInputIt);
importDataToTensor<T>(in, x_data);
} else {
ACLImportMemory(in->allocator(), (void*)x_data, X->Shape().Size() * 4);
}

View file

@ -30,7 +30,6 @@ class ONNX_OPERATOR_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 11,
class ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 7, 9, float, AveragePool);
class ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 10, 10, float, AveragePool);
class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 11, float, AveragePool);
class ONNX_OPERATOR_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 11, AveragePool);
class ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 1, 7, float, MaxPool);
class ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 8, 11, float, MaxPool);
@ -45,38 +44,52 @@ class ONNX_OPERATOR_VERSIONED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDo
class ONNX_OPERATOR_VERSIONED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 4, 10, Concat);
class ONNX_OPERATOR_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 11, Concat);
static void RegisterArmNNKernels(KernelRegistry& kernel_registry) {
class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kMSDomain, 1, float, FusedConv);
Status RegisterArmNNKernels(KernelRegistry& kernel_registry) {
static const BuildKernelCreateInfoFn function_table[] = {
#ifdef RELU_ARMNN
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 6, Relu)>());
BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 6, Relu)>,
#endif
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 1, 10, Conv)>());
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 11, Conv)>());
BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 1, 10, Conv)>,
BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 11, Conv)>,
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 7, 8, Gemm)>());
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 9, 10, Gemm)>());
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 11, Gemm)>());
BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 7, 8, Gemm)>,
BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 9, 10, Gemm)>,
BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 11, Gemm)>,
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 7, 9, float, AveragePool)>());
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 10, 10, float, AveragePool)>());
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 11, float, AveragePool)>());
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 11, AveragePool)>());
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 1, 7, float, MaxPool)>());
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 8, 11, float, MaxPool)>());
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 12, MaxPool)>());
BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 7, 9, float, AveragePool)>,
BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 10, 10, float, AveragePool)>,
BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 11, AveragePool)>,
BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 1, 7, float, MaxPool)>,
BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 8, 11, float, MaxPool)>,
BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 12, MaxPool)>,
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 1, float, GlobalAveragePool)>());
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 1, float, GlobalMaxPool)>());
BuildKernelCreateInfo<ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 1, float, GlobalAveragePool)>,
BuildKernelCreateInfo<ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 1, float, GlobalMaxPool)>,
#ifdef BN_ARMNN
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 7, 9, BatchNormalization)>());
BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 7, 9, BatchNormalization)>,
#endif
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 4, 10, Concat)>());
kernel_registry.Register(BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 11, Concat)>());
BuildKernelCreateInfo<ONNX_OPERATOR_VERSIONED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 4, 10, Concat)>,
BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kOnnxDomain, 11, Concat)>,
BuildKernelCreateInfo<ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kArmNNExecutionProvider, kMSDomain, 1, float, FusedConv)>,
};
for (auto& function_table_entry : function_table) {
KernelCreateInfo info = function_table_entry();
if (info.kernel_def != nullptr) { // filter disabled entries where type is void
ORT_RETURN_IF_ERROR(kernel_registry.Register(std::move(info)));
}
}
return Status::OK();
}
std::shared_ptr<KernelRegistry> GetArmNNKernelRegistry() {
std::shared_ptr<KernelRegistry> kernel_registry = std::make_shared<KernelRegistry>();
RegisterArmNNKernels(*kernel_registry);
ORT_THROW_IF_ERROR(RegisterArmNNKernels(*kernel_registry));
return kernel_registry;
}

View file

@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) 2020, NXP Semiconductor, Inc. All rights reserved.
// Licensed under the MIT License.
#ifdef _WIN32
#pragma warning(disable : 4244)
#endif
#include "core/providers/armnn/armnn_execution_provider.h"
#include "core/providers/armnn/nn/conv.h"
#include "core/providers/armnn/armnn_common.h"
#include "core/providers/armnn/armnn_fwd.h"
#include "contrib_ops/cpu/fused_activation.h"
#include "armnn/ArmNN.hpp"
#include <thread>
#include <mutex>
namespace onnxruntime {
namespace armnn_ep{
class FusedConv final : public armnn_ep::Conv<float> {
public:
explicit FusedConv(const OpKernelInfo& info) : armnn_ep::Conv<float>(info) {
ORT_ENFORCE(info.GetAttr<std::string>("activation", &(this->activation_type)).IsOK());
ORT_ENFORCE(GetFusedActivationAttr(info, activation_).IsOK());
}
};
ONNX_OPERATOR_TYPED_KERNEL_EX(
FusedConv,
kMSDomain,
1,
float,
kArmNNExecutionProvider,
KernelDefBuilder().TypeConstraint("T", DataTypeImpl::GetTensorType<float>()),
FusedConv);
} // namespace armnn_ep
} // namespace onnxruntime

View file

@ -65,7 +65,7 @@ enum class QLinearOpType : uint8_t {
QLinearOpType GetQLinearOpType(const onnxruntime::Node& node);
// This qlinear op is an operator takes 2 input and producce 1 output
// This qlinear op is an operator takes 2 input and produces 1 output
// Such as QLinearConv, QLinearMatMul, QLinearAdd, ...
bool IsQLinearBinaryOp(QLinearOpType qlinear_op_type);

View file

@ -34,23 +34,23 @@ bool ModelBuilder::IsNodeSupported(const Node& node) {
}
bool IsValidSupportedNodesVec(const std::vector<int>& supported_node_vec, const GraphViewer& graph_viewer) {
if (!supported_node_vec.empty()) {
if (supported_node_vec.size() == 1) {
const auto& node_indices = graph_viewer.GetNodesInTopologicalOrder();
const auto* node(graph_viewer.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
if (op == "Reshape" ||
op == "Dropout" ||
op == "Identity") {
return false;
}
if (supported_node_vec.empty())
return false;
if (supported_node_vec.size() == 1) {
const auto& node_indices = graph_viewer.GetNodesInTopologicalOrder();
const auto* node(graph_viewer.GetNode(node_indices[supported_node_vec[0]]));
const auto& op = node->OpType();
// It is not worth it to perform a single Reshape/Flatten/Identity operator
// which is only copying the data in NNAPI
// If this is the case, let it fall back
if (op == "Reshape" ||
op == "Flatten" ||
op == "Identity") {
return false;
}
return true;
}
return false;
return true;
}
std::vector<std::vector<int>> ModelBuilder::GetSupportedNodes() {

View file

@ -132,7 +132,7 @@ class ModelBuilder {
std::unordered_map<std::string, const ONNX_NAMESPACE::TensorProto&> initializers_;
std::unordered_set<std::string> skipped_initializers_;
// All activation nodes (Relu, Relu1, Relu6) as a map <NodeIndex, activeation_code>
// All activation nodes (Relu, Relu1, Relu6) as a map <NodeIndex, activation_code>
std::unordered_map<NodeIndex, int32_t> activation_nodes_;
std::unordered_map<std::string, std::shared_ptr<IOpBuilder>> op_builders_;

View file

@ -524,8 +524,8 @@ static Status GetBinaryOpQuantizationScaleAndZeroPoint(
return Status::OK();
}
// NNAPI has the qunatization scale and zero point embedded in the ANeuralNetworksOperandType
// ONNX has the qunatization scale and zero point as the inputs of the qlinear operators
// NNAPI has the quantization scale and zero point embedded in the ANeuralNetworksOperandType
// ONNX has the quantization scale and zero point as the inputs of the qlinear operators
// We want to verify the scale and zeropoint of the ONNX inputs matches the values embedded in the NNAPI inputs
static Status IsValidInputQuantizedType(const ModelBuilder& model_builder,
const std::string& input_name,
@ -886,10 +886,10 @@ Status BinaryOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const
if (input1_is_nhwc == input2_is_nhwc) {
output_is_nhwc = input1_is_nhwc;
} else if (input1_is_nhwc) {
// need transpsoe input1 back to nchw
// need transpose input1 back to nchw
ORT_RETURN_IF_ERROR(GetNCHWInput(model_builder, node, a_idx, input1));
} else { // input2_is_nhwc
// need transpsoe input2 back to nchw
// need transpose input2 back to nchw
ORT_RETURN_IF_ERROR(GetNCHWInput(model_builder, node, b_idx, input2));
}
@ -1024,17 +1024,113 @@ Status TransposeOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, co
class ReshapeOpBuilder : public BaseOpBuilder {
public:
void AddInitializersToSkip(ModelBuilder& model_builder, const Node& node) override;
static Status AddReshapeOperator(ModelBuilder& model_builder, const Node& node,
const std::string& input, const std::vector<int32_t>& shape) ORT_MUST_USE_RESULT;
private:
bool IsOpSupportedImpl(ModelBuilder& model_builder, const Node& node) override;
Status AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node) override ORT_MUST_USE_RESULT;
static bool CanSkipReshape(const Node& node, size_t input_rank, size_t output_rank);
};
void ReshapeOpBuilder::AddInitializersToSkip(ModelBuilder& model_builder, const Node& node) {
model_builder.AddInitializerToSkip(node.InputDefs()[1]->Name());
}
// We can skip the Reshape if all the output edges satisfies,
// 1. The output of the reshape/flatten is the input 0 of the GEMM/Matmul,
// and the input rank >= 2 and output_rank == 2
// This is because Gemm/Matmul will map to ANEURALNETWORKS_FULLY_CONNECTED in NNAPI,
// ANEURALNETWORKS_FULLY_CONNECTED will flatten the 2+ dim input 0 to 2d
// 2. Or the output the reshape/flatten is the output of the graph
// (no op in the graph is using the output except can be used by Gemm/Matmul satisfying condition 1 above)
// The reason we want to skip Reshape is that Reshape is not running on Hardware (NPU,...) in NNAPI for
// some CPU (e.g. Qualcomm SD for now), skipping unnecessary Reshape will prevent context switching
// between NNAPI CPU impl and Hardware Accelerator impl and will speed up the execution
// If we are going to skip the reshape, we will still add correct shape and operand type for the output in
// onnxruntime::nnapi::Model.
// If the Reshape output is also a graph output, since NNAPI output is a void* buffer, we can find the shape
// information in onnxruntime::nnapi::Model and pass the correct shape information back to ORT to be used as output shape
/* static */ bool ReshapeOpBuilder::CanSkipReshape(const Node& node, size_t input_rank, size_t output_rank) {
const auto& output = node.OutputDefs()[0]->Name();
// We will go through all the output edges
for (auto it = node.OutputEdgesBegin(), end = node.OutputEdgesEnd(); it != end; ++it) {
const auto& op_type = it->GetNode().OpType();
// TODO add quantized matmul when reshape support quantized input
if (op_type != "Gemm" && op_type != "MatMul") {
LOGS_DEFAULT(VERBOSE) << "Reshape/Flatten can only be skipped when the output is Gemm/Matmul"
<< " or no op is using the output (output is graph output)"
<< ", output name, " << output
<< " is used by " << op_type;
return false;
}
// NNAPI ANEURALNETWORKS_FULLY_CONNECTED will only flatten the input 0
if (it->GetDstArgIndex() != 0) {
LOGS_DEFAULT(VERBOSE) << "Reshape/Flatten can only be skipped when the output is input 0 of Gemm/Matmul"
<< ", output name, " << output;
return false;
}
// We only support 2d matmul/gemm here
// And NNAPI ANEURALNETWORKS_FULLY_CONNECTED will only flatten input rank >= 2
if (input_rank < 2 || output_rank != 2) {
LOGS_DEFAULT(VERBOSE) << "Reshape/Flatten can only be skipped when input_rank >= 2 and output_rank == 2"
<< ", output name, " << output
<< ", the actual input_rank, " << input_rank
<< ", the actual output_rank, " << output_rank;
return false;
}
}
// If we reach here, we have either,
// all the Reshape outputs are used by gemm/matmul, the output can also be a model output [doesn't really matter here]
// or
// Reshape has no output edge ==> the output is a graph output or a dead end [which we don't care]
// we can skip this Reshape now
LOGS_DEFAULT(VERBOSE) << "Skipping Reshape/Flatten node ["
<< node.Name() << "] with output, " << output;
return true;
}
/* static */ Status ReshapeOpBuilder::AddReshapeOperator(ModelBuilder& model_builder,
const Node& node,
const std::string& input,
const std::vector<int32_t>& shape) {
auto& shaper(model_builder.GetShaper());
const auto& operand_indices(model_builder.GetOperandIndices());
const auto& operand_types(model_builder.GetOperandTypes());
const auto& output = node.OutputDefs()[0]->Name();
ORT_RETURN_IF_ERROR(shaper.Reshape(input, shape, output));
auto input_rank = shaper[input].size();
auto output_rank = shaper[output].size();
// Since Reshape is not running using hardware in NNAPI for some CPU (e.g. Qualcomm SD for now)
// We will try to see if we the skip the Reshape to prevent context switching between
// NNAPI CPU impl and NNAPI hardware accelerator impl
if (CanSkipReshape(node, input_rank, output_rank)) {
// Since reshape can be skipped, only register the dimension and type, with same index and new name
const OperandType output_operand_type(operand_types.at(input).type, shaper[output]);
model_builder.RegisterOperand(output, operand_indices.at(input), output_operand_type, false);
} else {
// We still need to perform a reshape here
// Add input
std::vector<uint32_t> input_indices;
input_indices.push_back(operand_indices.at(input));
// Add new shape
Shape shape_dimen = {static_cast<uint32_t>(shape.size())};
std::string shape_name = model_builder.GetUniqueName(node.Name() + input + "newshape");
OperandType shape_operand_type(Type::TENSOR_INT32, shape_dimen);
ORT_RETURN_IF_ERROR(model_builder.AddOperandFromPersistMemoryBuffer(shape_name, shape.data(), shape_operand_type));
input_indices.push_back(operand_indices.at(shape_name));
const OperandType output_operand_type(operand_types.at(input).type, shaper[output]);
ORT_RETURN_IF_ERROR(model_builder.AddOperation(ANEURALNETWORKS_RESHAPE, input_indices, {output}, {output_operand_type}, {false}));
}
return Status::OK();
}
bool ReshapeOpBuilder::IsOpSupportedImpl(ModelBuilder& model_builder, const Node& node) {
const auto& initializers(model_builder.GetInitializerTensors());
const auto& perm_name = node.InputDefs()[1]->Name();
@ -1070,8 +1166,6 @@ bool ReshapeOpBuilder::IsOpSupportedImpl(ModelBuilder& model_builder, const Node
Status ReshapeOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node) {
auto& shaper(model_builder.GetShaper());
const auto& operand_indices(model_builder.GetOperandIndices());
const auto& operand_types(model_builder.GetOperandTypes());
const auto& initializers(model_builder.GetInitializerTensors());
auto input = node.InputDefs()[0]->Name();
@ -1080,10 +1174,6 @@ Status ReshapeOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, cons
ORT_RETURN_IF_ERROR(GetNCHWInput(model_builder, node, 0, input));
}
const auto& output = node.OutputDefs()[0]->Name();
std::vector<uint32_t> input_indices;
input_indices.push_back(operand_indices.at(input)); // input
const auto& shape_tensor = initializers.at(node.InputDefs()[1]->Name());
const int64_t* rawShape = GetTensorInt64Data(shape_tensor);
const auto size = SafeInt<uint32_t>(shape_tensor.dims()[0]);
@ -1096,18 +1186,7 @@ Status ReshapeOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, cons
shape[i] = dim == 0 ? input_shape[i] : dim;
}
Shape shape_dimen = {size};
std::string shape_name = model_builder.GetUniqueName(node.Name() + input + "newshape");
OperandType shape_operand_type(Type::TENSOR_INT32, shape_dimen);
ORT_RETURN_IF_ERROR(model_builder.AddOperandFromPersistMemoryBuffer(shape_name, shape.data(), shape_operand_type));
input_indices.push_back(operand_indices.at(shape_name));
ORT_RETURN_IF_ERROR(shaper.Reshape(input, shape, output));
const OperandType output_operand_type(operand_types.at(input).type, shaper[output]);
ORT_RETURN_IF_ERROR(model_builder.AddOperation(ANEURALNETWORKS_RESHAPE, input_indices,
{output}, {output_operand_type}, {false}));
return Status::OK();
return AddReshapeOperator(model_builder, node, input, shape);
}
#pragma endregion op_reshape
@ -1287,7 +1366,7 @@ bool PoolOpBuilder::IsOpSupportedImpl(ModelBuilder& /* model_builder */, const N
const auto input_size = input_shape.size();
if (input_size != 4) {
LOGS_DEFAULT(VERBOSE)
<< op_type << " only supportes rank-4 tensor, input ["
<< op_type << " only supports rank-4 tensor, input ["
<< node.InputDefs()[0]->Name() << "] has actual dim count " << input_size;
return false;
}
@ -1499,7 +1578,7 @@ bool ConvOpBuilder::IsOpSupportedImpl(ModelBuilder& model_builder, const Node& n
const auto android_sdk_ver = model_builder.GetAndroidSdkVer();
if (android_sdk_ver < 29) {
LOGS_DEFAULT(VERBOSE) << op_type << " dilations is only supported on Android API levle 29+, "
LOGS_DEFAULT(VERBOSE) << op_type << " dilations is only supported on Android API level 29+, "
<< "actual API level: " << android_sdk_ver;
return false;
}
@ -2307,7 +2386,7 @@ Status ConcatOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const
}
int rank = shaper[input0].size();
int32_t axis = SafeInt<int32_t>(HandleNegativeAxis(helper.Get("axis", 1), rank));
int32_t axis = static_cast<int32_t>(HandleNegativeAxis(helper.Get("axis", 1), rank));
if (output_is_nhwc) {
ORT_RETURN_IF_NOT(rank == 4,
@ -2372,7 +2451,7 @@ Status SqueezeOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, cons
const auto& input_shape(shaper[input]);
auto input_dims = input_shape.size();
for (auto& axis : axes) {
axis = SafeInt<int32_t>(HandleNegativeAxis(axis, input_dims));
axis = static_cast<int32_t>(HandleNegativeAxis(axis, input_dims));
}
if (axes.empty()) { // Squeeze all
@ -2918,6 +2997,76 @@ Status ResizeOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const
#pragma endregion
#pragma region op_flatten
class FlattenOpBuilder : public BaseOpBuilder {
private:
bool IsOpSupportedImpl(ModelBuilder& /* model_builder */, const Node& node) override;
static void GetFlattenShape(const Node& node, const Shape& input_shape, int32_t& dim_1, int32_t& dim_2);
Status AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node) override ORT_MUST_USE_RESULT;
};
/* static */ void FlattenOpBuilder::GetFlattenShape(const Node& node, const Shape& input_shape, int32_t& dim_1, int32_t& dim_2) {
int32_t rank = static_cast<int>(input_shape.size());
NodeAttrHelper helper(node);
int32_t axis = helper.Get("axis", 1);
// axis == rank is a valid input, but invalid for HandleNegativeAxis
// Skip non-negative axis here
if (axis < 0)
axis = static_cast<int32_t>(HandleNegativeAxis(axis, rank));
dim_1 = std::accumulate(input_shape.cbegin(), input_shape.cbegin() + axis, 1, std::multiplies<int32_t>());
dim_2 = std::accumulate(input_shape.cbegin() + axis, input_shape.cend(), 1, std::multiplies<int32_t>());
}
bool FlattenOpBuilder::IsOpSupportedImpl(ModelBuilder& /* model_builder */, const Node& node) {
Shape input_shape;
if (!GetShape(*node.InputDefs()[0], input_shape))
return false;
if (input_shape.size() > 4 || input_shape.empty()) {
LOGS_DEFAULT(VERBOSE) << "Flatten only supports up to 1-4d shape, input is "
<< input_shape.size() << "d shape";
return false;
}
int32_t dim_1 = 1;
int32_t dim_2 = 1;
GetFlattenShape(node, input_shape, dim_1, dim_2);
if (dim_1 == 0 && dim_2 == 0) {
LOGS_DEFAULT(VERBOSE) << "The dynamical input shape " << Shape2String(input_shape)
<< " is not supported";
return false;
}
return true;
}
Status FlattenOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node) {
auto input = node.InputDefs()[0]->Name();
if (model_builder.IsOperandNHWC(input)) {
// We want to transpose nhwc operand back to nchw before reshape
ORT_RETURN_IF_ERROR(GetNCHWInput(model_builder, node, 0, input));
}
// Flatten is basically a reshape to 2d tensor
// Get the shape for Reshape here
Shape input_shape;
GetShape(*node.InputDefs()[0], input_shape);
int32_t dim_1 = 1;
int32_t dim_2 = 1;
GetFlattenShape(node, input_shape, dim_1, dim_2);
// If the input is of dynamic shape, replace 0 (dynamic) dimension with -1
// We cannot have dim_1 and dim_2 both be 0 here, it was checked in IsOpSupportedImpl
dim_1 = dim_1 == 0 ? -1 : dim_1;
dim_2 = dim_2 == 0 ? -1 : dim_2;
std::vector<int32_t> shape{dim_1, dim_2};
return ReshapeOpBuilder::AddReshapeOperator(model_builder, node, input, shape);
}
#pragma endregion op_reshape
#pragma region CreateOpBuilders
std::unordered_map<std::string, std::shared_ptr<IOpBuilder>>
@ -2982,6 +3131,7 @@ CreateOpBuilders() {
op_map.emplace("LRN", std::make_shared<LRNOpBuilder>());
op_map.emplace("Clip", std::make_shared<ClipOpBuilder>());
op_map.emplace("Resize", std::make_shared<ResizeOpBuilder>());
op_map.emplace("Flatten", std::make_shared<FlattenOpBuilder>());
return op_map;
}

View file

@ -78,7 +78,7 @@ size_t Model::GetMappedOutputIdx(const std::string& name) const {
}
bool Model::SupportsDynamicOutputShape() const {
// dynamic output shape is only supported on Android API levle 29+
// dynamic output shape is only supported on Android API level 29+
return GetAndroidSdkVer() >= 29 && dynamic_output_buffer_size_ > 0;
}

View file

@ -81,11 +81,11 @@ class Model {
bool SupportsDynamicOutputShape() const;
// Set and Get the number of elements in the buffer for a dynamic output
// If the buffer is not big enough, ANEURALNETWORKS_OUTPUT_INSUFFICIENT_SIZE will be returned by exection
// If the buffer is not big enough, ANEURALNETWORKS_OUTPUT_INSUFFICIENT_SIZE will be returned by execution
// Note: this will return number of elements of the buffer not the byte size of the buffer
// and each output will have its separated buffer
// TODO:
// 1. Consider an adaptive aproach to automatically increase the buffer size if the execution reports
// 1. Consider an adaptive approach to automatically increase the buffer size if the execution reports
// insufficient size
// 2. Experiment with bigger initial buffer size (currently 1024)
size_t GetDynamicOutputBufferSize() const { return dynamic_output_buffer_size_; }

View file

@ -312,7 +312,7 @@ common::Status NnapiExecutionProvider::Compile(const std::vector<onnxruntime::No
if (input_type.dimensions != model_input_type.dimensions && model_input_type.GetOperandBlobByteSize() != 0) {
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL,
"The actual input dimanesions should match the model input "
"The actual input dimensions should match the model input "
"dimensions, or model input dimension has 0 (dynamic)");
}
@ -361,7 +361,7 @@ common::Status NnapiExecutionProvider::Compile(const std::vector<onnxruntime::No
output_buffer_byte_size = model_output_type.GetOperandBlobByteSize();
} else {
// This output is dynamic (size unknown), will need allocate a buffer for the result
// and copy the content to ORT output tensors afte the execution (will know output shape after the execution)
// and copy the content to ORT output tensors after the execution (will know output shape after the execution)
output_buffer_byte_size = model->GetDynamicOutputBufferSize() * model_output_type.GetElementByteSize();
std::unique_ptr<uint8_t[]> buffer_holder(new uint8_t[output_buffer_byte_size]);
output_buffer = buffer_holder.get();

View file

@ -2,8 +2,9 @@
#include "core/providers/nnapi/nnapi_builtin/nnapi_execution_provider.h"
#include "core/session/inference_session.h"
#include "gtest/gtest.h"
#include "test/providers/provider_test_utils.h"
#include "test/framework/test_utils.h"
#include "test/providers/provider_test_utils.h"
#include "test/util/include/inference_session_wrapper.h"
using namespace std;
using namespace ONNX_NAMESPACE;
@ -22,41 +23,99 @@ void VerifyOutputs(const std::vector<OrtValue>& fetches, const std::vector<int64
ASSERT_EQ(expected_values, found);
}
void RunAndVerifyOutputs(const std::string& model_file_name,
const char* log_id,
const NameMLValMap& feeds,
const std::vector<std::string>& output_names,
const std::vector<int64_t>& expected_dims,
const std::vector<float>& expected_values) {
SessionOptions so;
so.session_logid = log_id;
RunOptions run_options;
run_options.run_tag = so.session_logid;
InferenceSessionWrapper session_object{so, GetEnvironment()};
ASSERT_STATUS_OK(session_object.RegisterExecutionProvider(onnxruntime::make_unique<::onnxruntime::NnapiExecutionProvider>()));
ASSERT_STATUS_OK(session_object.Load(model_file_name));
ASSERT_STATUS_OK(session_object.Initialize());
// Since we already know the model is entirely supported by NNAPI, all nodes (2 Add nodes here) will be fused
// Get the graph after session is initialized, and verify the fused node (the only node in the graph) is using NNAPI EP
const auto& graph = session_object.GetGraph();
ASSERT_EQ(1, graph.NumberOfNodes()); // Make sure the graph has 1 fused node
ASSERT_EQ(onnxruntime::kNnapiExecutionProvider, graph.Nodes().cbegin()->GetExecutionProviderType());
// Now run and verify the result
std::vector<OrtValue> fetches;
ASSERT_STATUS_OK(session_object.Run(run_options, feeds, output_names, &fetches));
VerifyOutputs(fetches, expected_dims, expected_values);
}
// 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
// We have a separated test for these skip reshape scenarios
TEST(NnapiExecutionProviderTest, ReshapeFlattenTest) {
std::string model_file_name = "testdata/nnapi_reshape_flatten_test.onnx";
std::vector<int64_t> dims_mul_x = {2, 1, 2};
std::vector<float> values_mul_x = {1.0f, 2.0f, 3.0f, 4.0f};
std::vector<int64_t> dims_mul_y = {3, 2, 2};
std::vector<float> values_mul_y = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f};
OrtValue ml_value_x;
CreateMLValue<float>(TestNnapiExecutionProvider()->GetAllocator(0, OrtMemTypeDefault), dims_mul_x, values_mul_x, &ml_value_x);
OrtValue ml_value_y;
CreateMLValue<float>(TestNnapiExecutionProvider()->GetAllocator(0, OrtMemTypeDefault), dims_mul_y, values_mul_y, &ml_value_y);
NameMLValMap feeds;
feeds.insert(std::make_pair("X", ml_value_x));
feeds.insert(std::make_pair("Y", ml_value_y));
// prepare outputs
std::vector<std::string> output_names;
output_names.push_back("Z");
// prepare expected inputs and outputs
std::vector<int64_t> expected_dims_mul_z = {1, 6};
std::vector<float> expected_values_mul_z = {59.0f, 72.0f, 129.0f, 159.0f, 204.0f, 253.0f};
RunAndVerifyOutputs(model_file_name, "NnapiExecutionProviderTest.ReshapeFlattenTest", feeds, output_names, expected_dims_mul_z, expected_values_mul_z);
}
TEST(NnapiExecutionProviderTest, FunctionTest) {
onnxruntime::Model model("graph_1", false, DefaultLoggingManager().DefaultLogger());
auto& graph = model.MainGraph();
std::vector<onnxruntime::NodeArg*> inputs;
std::vector<onnxruntime::NodeArg*> outputs;
// FLOAT tensor.
ONNX_NAMESPACE::TypeProto float_tensor;
float_tensor.mutable_tensor_type()->set_elem_type(ONNX_NAMESPACE::TensorProto_DataType_FLOAT);
float_tensor.mutable_tensor_type()->mutable_shape()->add_dim()->set_dim_value(1);
float_tensor.mutable_tensor_type()->mutable_shape()->add_dim()->set_dim_value(1);
float_tensor.mutable_tensor_type()->mutable_shape()->add_dim()->set_dim_value(3);
float_tensor.mutable_tensor_type()->mutable_shape()->add_dim()->set_dim_value(2);
auto& input_arg_1 = graph.GetOrCreateNodeArg("X", &float_tensor);
auto& input_arg_2 = graph.GetOrCreateNodeArg("Y", &float_tensor);
inputs.push_back(&input_arg_1);
inputs.push_back(&input_arg_2);
auto& output_arg = graph.GetOrCreateNodeArg("node_1_out_1", &float_tensor);
outputs.push_back(&output_arg);
graph.AddNode("node_1", "Add", "node 1.", inputs, outputs);
auto& input_arg_3 = graph.GetOrCreateNodeArg("Z", &float_tensor);
inputs.clear();
inputs.push_back(&output_arg);
inputs.push_back(&input_arg_3);
auto& output_arg_2 = graph.GetOrCreateNodeArg("M", &float_tensor);
outputs.clear();
outputs.push_back(&output_arg_2);
graph.AddNode("node_2", "Add", "node 2.", inputs, outputs);
auto status = graph.Resolve();
ASSERT_TRUE(status.IsOK());
std::string model_file_name = "nnapi_execution_provider_test_graph.onnx";
status = onnxruntime::Model::Save(model, model_file_name);
{ // Create the model with 2 add nodes
onnxruntime::Model model("graph_1", false, DefaultLoggingManager().DefaultLogger());
auto& graph = model.MainGraph();
std::vector<onnxruntime::NodeArg*> inputs;
std::vector<onnxruntime::NodeArg*> outputs;
// FLOAT tensor.
ONNX_NAMESPACE::TypeProto float_tensor;
float_tensor.mutable_tensor_type()->set_elem_type(ONNX_NAMESPACE::TensorProto_DataType_FLOAT);
float_tensor.mutable_tensor_type()->mutable_shape()->add_dim()->set_dim_value(1);
float_tensor.mutable_tensor_type()->mutable_shape()->add_dim()->set_dim_value(1);
float_tensor.mutable_tensor_type()->mutable_shape()->add_dim()->set_dim_value(3);
float_tensor.mutable_tensor_type()->mutable_shape()->add_dim()->set_dim_value(2);
auto& input_arg_1 = graph.GetOrCreateNodeArg("X", &float_tensor);
auto& input_arg_2 = graph.GetOrCreateNodeArg("Y", &float_tensor);
inputs.push_back(&input_arg_1);
inputs.push_back(&input_arg_2);
auto& output_arg = graph.GetOrCreateNodeArg("node_1_out_1", &float_tensor);
outputs.push_back(&output_arg);
graph.AddNode("node_1", "Add", "node 1.", inputs, outputs);
auto& input_arg_3 = graph.GetOrCreateNodeArg("Z", &float_tensor);
inputs.clear();
inputs.push_back(&output_arg);
inputs.push_back(&input_arg_3);
auto& output_arg_2 = graph.GetOrCreateNodeArg("M", &float_tensor);
outputs.clear();
outputs.push_back(&output_arg_2);
graph.AddNode("node_2", "Add", "node 2.", inputs, outputs);
ASSERT_STATUS_OK(graph.Resolve());
ASSERT_STATUS_OK(onnxruntime::Model::Save(model, model_file_name));
}
std::vector<int64_t> dims_mul_x = {1, 1, 3, 2};
std::vector<float> values_mul_x = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f};
@ -80,23 +139,7 @@ TEST(NnapiExecutionProviderTest, FunctionTest) {
std::vector<int64_t> expected_dims_mul_m = {1, 1, 3, 2};
std::vector<float> expected_values_mul_m = {3.0f, 6.0f, 9.0f, 12.0f, 15.0f, 18.0f};
SessionOptions so;
so.session_logid = "NnapiExecutionProviderTest.FunctionTest";
RunOptions run_options;
run_options.run_tag = so.session_logid;
InferenceSession session_object{so, GetEnvironment()};
status = session_object.RegisterExecutionProvider(onnxruntime::make_unique<::onnxruntime::NnapiExecutionProvider>());
ASSERT_TRUE(status.IsOK());
status = session_object.Load(model_file_name);
ASSERT_TRUE(status.IsOK());
status = session_object.Initialize();
ASSERT_TRUE(status.IsOK());
// Now run
status = session_object.Run(run_options, feeds, output_names, &fetches);
ASSERT_TRUE(status.IsOK());
VerifyOutputs(fetches, expected_dims_mul_m, expected_values_mul_m);
RunAndVerifyOutputs(model_file_name, "NnapiExecutionProviderTest.FunctionTest", feeds, output_names, expected_dims_mul_m, expected_values_mul_m);
}
} // namespace test
} // namespace onnxruntime

Binary file not shown.

View file

@ -0,0 +1,45 @@
import onnx
from onnx import helper
from onnx import TensorProto
# 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
# We have a separated test for these skip reshape scenarios
def GenerateModel(model_name):
nodes = [
helper.make_node("Flatten", ["X"], ["Flatten_1_Y"], "flatten_1"),
helper.make_node("MatMul", ["Flatten_1_Y", "MatMul_B"], ["MatMul_Y"], "matmul"),
helper.make_node("Reshape", ["Y", "Reshape_1_shape"], ["Reshape_1_Y"], "reshape_1"),
helper.make_node("Gemm", ["Reshape_1_Y", "Gemm_B"], ["Gemm_Y"], "gemm"),
helper.make_node("Reshape", ["MatMul_Y", "Reshape_2_shape"], ["Reshape_2_Y"], "reshape_2"),
helper.make_node("Flatten", ["Gemm_Y"], ["Flatten_2_Y"], "flatten_2", axis=0),
helper.make_node("Add", ["Reshape_2_Y", "Flatten_2_Y"], ["Z"], "add"),
]
initializers = [
helper.make_tensor('Reshape_1_shape', TensorProto.INT64, [2], [3, 4]),
helper.make_tensor('Reshape_2_shape', TensorProto.INT64, [2], [1, 6]),
helper.make_tensor('Gemm_B', TensorProto.FLOAT, [4, 2], [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]),
helper.make_tensor('MatMul_B', TensorProto.FLOAT, [2, 3], [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]),
]
inputs = [
helper.make_tensor_value_info('X', TensorProto.FLOAT, [2, 1, 2]),
helper.make_tensor_value_info('Y', TensorProto.FLOAT, [3, 2, 2])
]
graph = helper.make_graph(
nodes,
"NNAPI_Reshape_Flatten_Test",
inputs,
[helper.make_tensor_value_info('Z', TensorProto.FLOAT, [1, 6])],
initializers
)
model = helper.make_model(graph)
onnx.save(model, model_name)
if __name__ == "__main__":
GenerateModel('nnapi_reshape_flatten_test.onnx')

View file

@ -347,6 +347,10 @@ def parse_arguments():
"--use_acl", nargs="?", const="ACL_1905",
choices=["ACL_1902", "ACL_1905", "ACL_1908", "ACL_2002"],
help="Build with ACL for ARM architectures.")
parser.add_argument(
"--acl_home", help="Path to ACL home dir")
parser.add_argument(
"--acl_libs", help="Path to ACL libraries")
parser.add_argument(
"--use_armnn", action='store_true',
help="Enable ArmNN Execution Provider.")
@ -356,6 +360,10 @@ def parse_arguments():
parser.add_argument(
"--armnn_bn", action='store_true',
help="Use the Batch Normalization operator implementation from the ArmNN EP.")
parser.add_argument(
"--armnn_home", help="Path to ArmNN home dir")
parser.add_argument(
"--armnn_libs", help="Path to ArmNN libraries")
parser.add_argument(
"--build_micro_benchmarks", action='store_true',
help="Build ONNXRuntime micro-benchmarks.")
@ -578,7 +586,7 @@ def use_dev_mode(args):
def generate_build_tree(cmake_path, source_dir, build_dir, cuda_home, cudnn_home,
mpi_home, nccl_home, tensorrt_home, migraphx_home,
mpi_home, nccl_home, tensorrt_home, migraphx_home, acl_home, acl_libs, armnn_home, armnn_libs,
path_to_protoc_exe, configs, cmake_extra_defines, args, cmake_extra_args):
log.info("Generating CMake build tree")
cmake_dir = os.path.join(source_dir, "cmake")
@ -687,6 +695,18 @@ def generate_build_tree(cmake_path, source_dir, build_dir, cuda_home, cudnn_home
"ON" if args.build_micro_benchmarks else "OFF")
]
if acl_home and os.path.exists(acl_home):
cmake_args += ["-Donnxruntime_ACL_HOME=" + acl_home]
if acl_libs and os.path.exists(acl_libs):
cmake_args += ["-Donnxruntime_ACL_LIBS=" + acl_libs]
if armnn_home and os.path.exists(armnn_home):
cmake_args += ["-Donnxruntime_ARMNN_HOME=" + armnn_home]
if armnn_libs and os.path.exists(armnn_libs):
cmake_args += ["-Donnxruntime_ARMNN_LIBS=" + armnn_libs]
if mpi_home and os.path.exists(mpi_home):
cmake_args += ["-Donnxruntime_MPI_HOME=" + mpi_home]
@ -1767,6 +1787,12 @@ def main():
mpi_home = args.mpi_home
nccl_home = args.nccl_home
acl_home = args.acl_home
acl_libs = args.acl_libs
armnn_home = args.armnn_home
armnn_libs = args.armnn_libs
# if using tensorrt, setup tensorrt paths
tensorrt_home = setup_tensorrt_vars(args)
@ -1863,8 +1889,8 @@ def main():
setup_test_data(build_dir, configs)
generate_build_tree(
cmake_path, source_dir, build_dir, cuda_home, cudnn_home, mpi_home, nccl_home,
tensorrt_home, migraphx_home, path_to_protoc_exe, configs, cmake_extra_defines,
args, cmake_extra_args)
tensorrt_home, migraphx_home, acl_home, acl_libs, armnn_home, armnn_libs,
path_to_protoc_exe, configs, cmake_extra_defines, args, cmake_extra_args)
if args.clean:
clean_targets(cmake_path, build_dir, configs)