From b98aa6d6bfd96df79fb139c00bcdb491f70c895e Mon Sep 17 00:00:00 2001 From: Michael Butler Date: Sat, 22 Feb 2020 22:37:59 -0800 Subject: [PATCH] Create conversions to/from NNAPI canonical types This CL creates the following primary sets of functions: * V1_X::utils::convert() -- Converts a canonical type to the corresponding HAL version type. * nn::convert() -- Converts a HAL version type to the corresponding canonical type. * neuralnetworks::utils::hasNoPointerData -- Indicates if the object contains no pointer-based data that could be relocated to shared memory. * neuralnetworks::utils::flushDataFromPointerToShared -- Relocate pointer-based data to shared memory. * neuralnetworks::utils::unflushDataFromSharedToPointer -- Undoes `flushDataFromPointerToShared` on a Request object. More specifically, `unflushDataFromSharedToPointer` copies the output shared memory data from the transformed Request object back to the output pointer-based memory in the original Request object. It also introduces some other minor utility code, including makeQuantized8PerformanceConsistentWithP, countNumberOfConsumers, validate, valid, and validatedConvertToCanonical. Bug: 160667419 Test: mma Change-Id: I0732e658c1f4ed40cd122f1ca8581fb40b056757 Merged-In: I0732e658c1f4ed40cd122f1ca8581fb40b056757 (cherry picked from commit a685c3dbf4afb35d0a80488155ce2bde30c9d6e9) --- neuralnetworks/1.0/utils/Android.bp | 33 ++ neuralnetworks/1.0/utils/OWNERS | 11 + .../utils/include/nnapi/hal/1.0/Conversions.h | 66 +++ .../1.0/utils/include/nnapi/hal/1.0/Utils.h | 63 ++ neuralnetworks/1.0/utils/src/Assertions.cpp | 122 ++++ neuralnetworks/1.0/utils/src/Conversions.cpp | 361 ++++++++++++ neuralnetworks/1.1/utils/Android.bp | 35 ++ neuralnetworks/1.1/utils/OWNERS | 11 + .../utils/include/nnapi/hal/1.1/Conversions.h | 45 ++ .../1.1/utils/include/nnapi/hal/1.1/Utils.h | 65 +++ neuralnetworks/1.1/utils/src/Assertions.cpp | 100 ++++ neuralnetworks/1.1/utils/src/Conversions.cpp | 209 +++++++ neuralnetworks/1.2/utils/Android.bp | 37 ++ neuralnetworks/1.2/utils/OWNERS | 11 + .../utils/include/nnapi/hal/1.2/Conversions.h | 86 +++ .../1.2/utils/include/nnapi/hal/1.2/Utils.h | 70 +++ neuralnetworks/1.2/utils/src/Assertions.cpp | 188 ++++++ neuralnetworks/1.2/utils/src/Conversions.cpp | 502 ++++++++++++++++ neuralnetworks/1.3/utils/Android.bp | 39 ++ neuralnetworks/1.3/utils/OWNERS | 11 + .../utils/include/nnapi/hal/1.3/Conversions.h | 79 +++ .../1.3/utils/include/nnapi/hal/1.3/Utils.h | 67 +++ neuralnetworks/1.3/utils/src/Assertions.cpp | 218 +++++++ neuralnetworks/1.3/utils/src/Conversions.cpp | 552 ++++++++++++++++++ neuralnetworks/utils/OWNERS | 11 + neuralnetworks/utils/common/Android.bp | 29 + .../common/include/nnapi/hal/CommonUtils.h | 59 ++ .../utils/common/src/CommonUtils.cpp | 224 +++++++ 28 files changed, 3304 insertions(+) create mode 100644 neuralnetworks/1.0/utils/Android.bp create mode 100644 neuralnetworks/1.0/utils/OWNERS create mode 100644 neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Conversions.h create mode 100644 neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Utils.h create mode 100644 neuralnetworks/1.0/utils/src/Assertions.cpp create mode 100644 neuralnetworks/1.0/utils/src/Conversions.cpp create mode 100644 neuralnetworks/1.1/utils/Android.bp create mode 100644 neuralnetworks/1.1/utils/OWNERS create mode 100644 neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Conversions.h create mode 100644 neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Utils.h create mode 100644 neuralnetworks/1.1/utils/src/Assertions.cpp create mode 100644 neuralnetworks/1.1/utils/src/Conversions.cpp create mode 100644 neuralnetworks/1.2/utils/Android.bp create mode 100644 neuralnetworks/1.2/utils/OWNERS create mode 100644 neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Conversions.h create mode 100644 neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h create mode 100644 neuralnetworks/1.2/utils/src/Assertions.cpp create mode 100644 neuralnetworks/1.2/utils/src/Conversions.cpp create mode 100644 neuralnetworks/1.3/utils/Android.bp create mode 100644 neuralnetworks/1.3/utils/OWNERS create mode 100644 neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Conversions.h create mode 100644 neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Utils.h create mode 100644 neuralnetworks/1.3/utils/src/Assertions.cpp create mode 100644 neuralnetworks/1.3/utils/src/Conversions.cpp create mode 100644 neuralnetworks/utils/OWNERS create mode 100644 neuralnetworks/utils/common/Android.bp create mode 100644 neuralnetworks/utils/common/include/nnapi/hal/CommonUtils.h create mode 100644 neuralnetworks/utils/common/src/CommonUtils.cpp diff --git a/neuralnetworks/1.0/utils/Android.bp b/neuralnetworks/1.0/utils/Android.bp new file mode 100644 index 0000000000..57a052f1f5 --- /dev/null +++ b/neuralnetworks/1.0/utils/Android.bp @@ -0,0 +1,33 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +cc_library_static { + name: "neuralnetworks_utils_hal_1_0", + defaults: ["neuralnetworks_utils_defaults"], + srcs: ["src/*"], + local_include_dirs: ["include/nnapi/hal/1.0/"], + export_include_dirs: ["include"], + static_libs: [ + "neuralnetworks_types", + "neuralnetworks_utils_hal_common", + ], + shared_libs: [ + "android.hardware.neuralnetworks@1.0", + ], + export_static_lib_headers: [ + "neuralnetworks_utils_hal_common", + ], +} diff --git a/neuralnetworks/1.0/utils/OWNERS b/neuralnetworks/1.0/utils/OWNERS new file mode 100644 index 0000000000..e4feee3496 --- /dev/null +++ b/neuralnetworks/1.0/utils/OWNERS @@ -0,0 +1,11 @@ +# Neuralnetworks team +butlermichael@google.com +dgross@google.com +galarragas@google.com +jeanluc@google.com +levp@google.com +miaowang@google.com +pszczepaniak@google.com +slavash@google.com +vddang@google.com +xusongw@google.com diff --git a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Conversions.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Conversions.h new file mode 100644 index 0000000000..8ad98cbafc --- /dev/null +++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Conversions.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_CONVERSIONS_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_CONVERSIONS_H + +#include +#include +#include +#include + +namespace android::nn { + +Result convert(const hal::V1_0::OperandType& operandType); +Result convert(const hal::V1_0::OperationType& operationType); +Result convert(const hal::V1_0::OperandLifeTime& lifetime); +Result convert(const hal::V1_0::DeviceStatus& deviceStatus); +Result convert(const hal::V1_0::PerformanceInfo& performanceInfo); +Result convert(const hal::V1_0::Capabilities& capabilities); +Result convert(const hal::V1_0::DataLocation& location); +Result convert(const hal::V1_0::Operand& operand); +Result convert(const hal::V1_0::Operation& operation); +Result convert(const hardware::hidl_vec& operandValues); +Result convert(const hardware::hidl_memory& memory); +Result convert(const hal::V1_0::Model& model); +Result convert(const hal::V1_0::RequestArgument& requestArgument); +Result convert(const hal::V1_0::Request& request); +Result convert(const hal::V1_0::ErrorStatus& status); + +} // namespace android::nn + +namespace android::hardware::neuralnetworks::V1_0::utils { + +nn::Result convert(const nn::OperandType& operandType); +nn::Result convert(const nn::OperationType& operationType); +nn::Result convert(const nn::Operand::LifeTime& lifetime); +nn::Result convert(const nn::DeviceStatus& deviceStatus); +nn::Result convert(const nn::Capabilities::PerformanceInfo& performanceInfo); +nn::Result convert(const nn::Capabilities& capabilities); +nn::Result convert(const nn::DataLocation& location); +nn::Result convert(const nn::Operand& operand); +nn::Result convert(const nn::Operation& operation); +nn::Result> convert(const nn::Model::OperandValues& operandValues); +nn::Result convert(const nn::Memory& memory); +nn::Result convert(const nn::Model& model); +nn::Result convert(const nn::Request::Argument& requestArgument); +nn::Result convert(const nn::Request::MemoryPool& memoryPool); +nn::Result convert(const nn::Request& request); +nn::Result convert(const nn::ErrorStatus& status); + +} // namespace android::hardware::neuralnetworks::V1_0::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_CONVERSIONS_H diff --git a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Utils.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Utils.h new file mode 100644 index 0000000000..ec8da06ca6 --- /dev/null +++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Utils.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_H + +#include "nnapi/hal/1.0/Conversions.h" + +#include +#include +#include +#include +#include + +namespace android::hardware::neuralnetworks::V1_0::utils { + +constexpr auto kVersion = nn::Version::ANDROID_OC_MR1; + +template +nn::Result validate(const Type& halObject) { + const auto canonical = NN_TRY(nn::convert(halObject)); + const auto version = NN_TRY(nn::validate(canonical)); + if (version > utils::kVersion) { + return NN_ERROR() << ""; + } + return {}; +} + +template +bool valid(const Type& halObject) { + const auto result = utils::validate(halObject); + if (!result.has_value()) { + LOG(ERROR) << result.error(); + } + return result.has_value(); +} + +template +decltype(nn::convert(std::declval())) validatedConvertToCanonical(const Type& halObject) { + auto canonical = NN_TRY(nn::convert(halObject)); + const auto version = NN_TRY(nn::validate(canonical)); + if (version > utils::kVersion) { + return NN_ERROR() << ""; + } + return canonical; +} + +} // namespace android::hardware::neuralnetworks::V1_0::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_H diff --git a/neuralnetworks/1.0/utils/src/Assertions.cpp b/neuralnetworks/1.0/utils/src/Assertions.cpp new file mode 100644 index 0000000000..0f0095196d --- /dev/null +++ b/neuralnetworks/1.0/utils/src/Assertions.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +namespace { + +#define COMPARE_ENUMS_TYPES(lhsType, rhsType) \ + static_assert( \ + std::is_same_v< \ + std::underlying_type_t<::android::hardware::neuralnetworks::V1_0::lhsType>, \ + std::underlying_type_t<::android::nn::rhsType>>, \ + "::android::hardware::neuralnetworks::V1_0::" #lhsType \ + " does not have the same underlying type as ::android::nn::" #rhsType) + +COMPARE_ENUMS_TYPES(OperandType, OperandType); +COMPARE_ENUMS_TYPES(OperationType, OperationType); +COMPARE_ENUMS_TYPES(ErrorStatus, ErrorStatus); +COMPARE_ENUMS_TYPES(OperandLifeTime, Operand::LifeTime); + +#undef COMPARE_ENUMS_TYPES + +#define COMPARE_ENUMS_FULL(lhsSymbol, rhsSymbol, lhsType, rhsType) \ + static_assert( \ + static_cast< \ + std::underlying_type_t<::android::hardware::neuralnetworks::V1_0::lhsType>>( \ + ::android::hardware::neuralnetworks::V1_0::lhsType::lhsSymbol) == \ + static_cast>( \ + ::android::nn::rhsType::rhsSymbol), \ + "::android::hardware::neuralnetworks::V1_0::" #lhsType "::" #lhsSymbol \ + " does not match ::android::nn::" #rhsType "::" #rhsSymbol) + +#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, symbol, OperandType, OperandType) + +COMPARE_ENUMS(FLOAT32); +COMPARE_ENUMS(INT32); +COMPARE_ENUMS(UINT32); +COMPARE_ENUMS(TENSOR_FLOAT32); +COMPARE_ENUMS(TENSOR_INT32); +COMPARE_ENUMS(TENSOR_QUANT8_ASYMM); +COMPARE_ENUMS(OEM); +COMPARE_ENUMS(TENSOR_OEM_BYTE); + +#undef COMPARE_ENUMS + +#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, symbol, OperationType, OperationType) + +COMPARE_ENUMS(ADD); +COMPARE_ENUMS(AVERAGE_POOL_2D); +COMPARE_ENUMS(CONCATENATION); +COMPARE_ENUMS(CONV_2D); +COMPARE_ENUMS(DEPTHWISE_CONV_2D); +COMPARE_ENUMS(DEPTH_TO_SPACE); +COMPARE_ENUMS(DEQUANTIZE); +COMPARE_ENUMS(EMBEDDING_LOOKUP); +COMPARE_ENUMS(FLOOR); +COMPARE_ENUMS(FULLY_CONNECTED); +COMPARE_ENUMS(HASHTABLE_LOOKUP); +COMPARE_ENUMS(L2_NORMALIZATION); +COMPARE_ENUMS(L2_POOL_2D); +COMPARE_ENUMS(LOCAL_RESPONSE_NORMALIZATION); +COMPARE_ENUMS(LOGISTIC); +COMPARE_ENUMS(LSH_PROJECTION); +COMPARE_ENUMS(LSTM); +COMPARE_ENUMS(MAX_POOL_2D); +COMPARE_ENUMS(MUL); +COMPARE_ENUMS(RELU); +COMPARE_ENUMS(RELU1); +COMPARE_ENUMS(RELU6); +COMPARE_ENUMS(RESHAPE); +COMPARE_ENUMS(RESIZE_BILINEAR); +COMPARE_ENUMS(RNN); +COMPARE_ENUMS(SOFTMAX); +COMPARE_ENUMS(SPACE_TO_DEPTH); +COMPARE_ENUMS(SVDF); +COMPARE_ENUMS(TANH); +COMPARE_ENUMS(OEM_OPERATION); + +#undef COMPARE_ENUMS + +#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, symbol, ErrorStatus, ErrorStatus) + +COMPARE_ENUMS(NONE); +COMPARE_ENUMS(DEVICE_UNAVAILABLE); +COMPARE_ENUMS(GENERAL_FAILURE); +COMPARE_ENUMS(OUTPUT_INSUFFICIENT_SIZE); +COMPARE_ENUMS(INVALID_ARGUMENT); + +#undef COMPARE_ENUMS + +#define COMPARE_ENUMS(lhsSymbol, rhsSymbol) \ + COMPARE_ENUMS_FULL(lhsSymbol, rhsSymbol, OperandLifeTime, Operand::LifeTime) + +COMPARE_ENUMS(TEMPORARY_VARIABLE, TEMPORARY_VARIABLE); +COMPARE_ENUMS(MODEL_INPUT, SUBGRAPH_INPUT); +COMPARE_ENUMS(MODEL_OUTPUT, SUBGRAPH_OUTPUT); +COMPARE_ENUMS(CONSTANT_COPY, CONSTANT_COPY); +COMPARE_ENUMS(CONSTANT_REFERENCE, CONSTANT_REFERENCE); +COMPARE_ENUMS(NO_VALUE, NO_VALUE); + +#undef COMPARE_ENUMS + +#undef COMPARE_ENUMS_FULL + +} // anonymous namespace diff --git a/neuralnetworks/1.0/utils/src/Conversions.cpp b/neuralnetworks/1.0/utils/src/Conversions.cpp new file mode 100644 index 0000000000..4a58f3b93c --- /dev/null +++ b/neuralnetworks/1.0/utils/src/Conversions.cpp @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Conversions.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +template +constexpr std::underlying_type_t underlyingType(Type value) { + return static_cast>(value); +} + +} // namespace + +namespace android::nn { +namespace { + +using hardware::hidl_memory; +using hardware::hidl_vec; + +template +using ConvertOutput = std::decay_t()).value())>; + +template +Result>> convert(const hidl_vec& arguments) { + std::vector> canonical; + canonical.reserve(arguments.size()); + for (const auto& argument : arguments) { + canonical.push_back(NN_TRY(nn::convert(argument))); + } + return canonical; +} + +} // anonymous namespace + +Result convert(const hal::V1_0::OperandType& operandType) { + return static_cast(operandType); +} + +Result convert(const hal::V1_0::OperationType& operationType) { + return static_cast(operationType); +} + +Result convert(const hal::V1_0::OperandLifeTime& lifetime) { + return static_cast(lifetime); +} + +Result convert(const hal::V1_0::DeviceStatus& deviceStatus) { + return static_cast(deviceStatus); +} + +Result convert(const hal::V1_0::PerformanceInfo& performanceInfo) { + return Capabilities::PerformanceInfo{ + .execTime = performanceInfo.execTime, + .powerUsage = performanceInfo.powerUsage, + }; +} + +Result convert(const hal::V1_0::Capabilities& capabilities) { + const auto quantized8Performance = NN_TRY(convert(capabilities.quantized8Performance)); + const auto float32Performance = NN_TRY(convert(capabilities.float32Performance)); + + auto table = hal::utils::makeQuantized8PerformanceConsistentWithP(float32Performance, + quantized8Performance); + + return Capabilities{ + .relaxedFloat32toFloat16PerformanceScalar = float32Performance, + .relaxedFloat32toFloat16PerformanceTensor = float32Performance, + .operandPerformance = std::move(table), + }; +} + +Result convert(const hal::V1_0::DataLocation& location) { + return DataLocation{ + .poolIndex = location.poolIndex, + .offset = location.offset, + .length = location.length, + }; +} + +Result convert(const hal::V1_0::Operand& operand) { + return Operand{ + .type = NN_TRY(convert(operand.type)), + .dimensions = operand.dimensions, + .scale = operand.scale, + .zeroPoint = operand.zeroPoint, + .lifetime = NN_TRY(convert(operand.lifetime)), + .location = NN_TRY(convert(operand.location)), + }; +} + +Result convert(const hal::V1_0::Operation& operation) { + return Operation{ + .type = NN_TRY(convert(operation.type)), + .inputs = operation.inputs, + .outputs = operation.outputs, + }; +} + +Result convert(const hidl_vec& operandValues) { + return Model::OperandValues(operandValues.data(), operandValues.size()); +} + +Result convert(const hidl_memory& memory) { + return createSharedMemoryFromHidlMemory(memory); +} + +Result convert(const hal::V1_0::Model& model) { + auto operations = NN_TRY(convert(model.operations)); + + // Verify number of consumers. + const auto numberOfConsumers = + hal::utils::countNumberOfConsumers(model.operands.size(), operations); + CHECK(model.operands.size() == numberOfConsumers.size()); + for (size_t i = 0; i < model.operands.size(); ++i) { + if (model.operands[i].numberOfConsumers != numberOfConsumers[i]) { + return NN_ERROR() << "Invalid numberOfConsumers for operand " << i << ", expected " + << numberOfConsumers[i] << " but found " + << model.operands[i].numberOfConsumers; + } + } + + auto main = Model::Subgraph{ + .operands = NN_TRY(convert(model.operands)), + .operations = std::move(operations), + .inputIndexes = model.inputIndexes, + .outputIndexes = model.outputIndexes, + }; + + return Model{ + .main = std::move(main), + .operandValues = NN_TRY(convert(model.operandValues)), + .pools = NN_TRY(convert(model.pools)), + }; +} + +Result convert(const hal::V1_0::RequestArgument& argument) { + const auto lifetime = argument.hasNoValue ? Request::Argument::LifeTime::NO_VALUE + : Request::Argument::LifeTime::POOL; + return Request::Argument{ + .lifetime = lifetime, + .location = NN_TRY(convert(argument.location)), + .dimensions = argument.dimensions, + }; +} + +Result convert(const hal::V1_0::Request& request) { + auto memories = NN_TRY(convert(request.pools)); + std::vector pools; + pools.reserve(memories.size()); + std::move(memories.begin(), memories.end(), std::back_inserter(pools)); + + return Request{ + .inputs = NN_TRY(convert(request.inputs)), + .outputs = NN_TRY(convert(request.outputs)), + .pools = std::move(pools), + }; +} + +Result convert(const hal::V1_0::ErrorStatus& status) { + switch (status) { + case hal::V1_0::ErrorStatus::NONE: + case hal::V1_0::ErrorStatus::DEVICE_UNAVAILABLE: + case hal::V1_0::ErrorStatus::GENERAL_FAILURE: + case hal::V1_0::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE: + case hal::V1_0::ErrorStatus::INVALID_ARGUMENT: + return static_cast(status); + } + return NN_ERROR() << "Invalid ErrorStatus " << underlyingType(status); +} + +} // namespace android::nn + +namespace android::hardware::neuralnetworks::V1_0::utils { +namespace { + +template +using ConvertOutput = std::decay_t()).value())>; + +template +nn::Result>> convert(const std::vector& arguments) { + hidl_vec> halObject(arguments.size()); + for (size_t i = 0; i < arguments.size(); ++i) { + halObject[i] = NN_TRY(utils::convert(arguments[i])); + } + return halObject; +} + +} // anonymous namespace + +nn::Result convert(const nn::OperandType& operandType) { + return static_cast(operandType); +} + +nn::Result convert(const nn::OperationType& operationType) { + return static_cast(operationType); +} + +nn::Result convert(const nn::Operand::LifeTime& lifetime) { + if (lifetime == nn::Operand::LifeTime::POINTER) { + return NN_ERROR() << "Model cannot be converted because it contains pointer-based memory"; + } + return static_cast(lifetime); +} + +nn::Result convert(const nn::DeviceStatus& deviceStatus) { + return static_cast(deviceStatus); +} + +nn::Result convert(const nn::Capabilities::PerformanceInfo& performanceInfo) { + return PerformanceInfo{ + .execTime = performanceInfo.execTime, + .powerUsage = performanceInfo.powerUsage, + }; +} + +nn::Result convert(const nn::Capabilities& capabilities) { + return Capabilities{ + .float32Performance = NN_TRY(convert( + capabilities.operandPerformance.lookup(nn::OperandType::TENSOR_FLOAT32))), + .quantized8Performance = NN_TRY(convert( + capabilities.operandPerformance.lookup(nn::OperandType::TENSOR_QUANT8_ASYMM))), + }; +} + +nn::Result convert(const nn::DataLocation& location) { + return DataLocation{ + .poolIndex = location.poolIndex, + .offset = location.offset, + .length = location.length, + }; +} + +nn::Result convert(const nn::Operand& operand) { + return Operand{ + .type = NN_TRY(convert(operand.type)), + .dimensions = operand.dimensions, + .numberOfConsumers = 0, + .scale = operand.scale, + .zeroPoint = operand.zeroPoint, + .lifetime = NN_TRY(convert(operand.lifetime)), + .location = NN_TRY(convert(operand.location)), + }; +} + +nn::Result convert(const nn::Operation& operation) { + return Operation{ + .type = NN_TRY(convert(operation.type)), + .inputs = operation.inputs, + .outputs = operation.outputs, + }; +} + +nn::Result> convert(const nn::Model::OperandValues& operandValues) { + return hidl_vec(operandValues.data(), operandValues.data() + operandValues.size()); +} + +nn::Result convert(const nn::Memory& memory) { + const auto hidlMemory = hidl_memory(memory.name, memory.handle->handle(), memory.size); + // Copy memory to force the native_handle_t to be copied. + auto copiedMemory = hidlMemory; + return copiedMemory; +} + +nn::Result convert(const nn::Model& model) { + if (!hal::utils::hasNoPointerData(model)) { + return NN_ERROR() << "Mdoel cannot be converted because it contains pointer-based memory"; + } + + auto operands = NN_TRY(convert(model.main.operands)); + + // Update number of consumers. + const auto numberOfConsumers = + hal::utils::countNumberOfConsumers(operands.size(), model.main.operations); + CHECK(operands.size() == numberOfConsumers.size()); + for (size_t i = 0; i < operands.size(); ++i) { + operands[i].numberOfConsumers = numberOfConsumers[i]; + } + + return Model{ + .operands = std::move(operands), + .operations = NN_TRY(convert(model.main.operations)), + .inputIndexes = model.main.inputIndexes, + .outputIndexes = model.main.outputIndexes, + .operandValues = NN_TRY(convert(model.operandValues)), + .pools = NN_TRY(convert(model.pools)), + }; +} + +nn::Result convert(const nn::Request::Argument& requestArgument) { + if (requestArgument.lifetime == nn::Request::Argument::LifeTime::POINTER) { + return NN_ERROR() << "Request cannot be converted because it contains pointer-based memory"; + } + const bool hasNoValue = requestArgument.lifetime == nn::Request::Argument::LifeTime::NO_VALUE; + return RequestArgument{ + .hasNoValue = hasNoValue, + .location = NN_TRY(convert(requestArgument.location)), + .dimensions = requestArgument.dimensions, + }; +} + +nn::Result convert(const nn::Request::MemoryPool& memoryPool) { + return convert(std::get(memoryPool)); +} + +nn::Result convert(const nn::Request& request) { + if (!hal::utils::hasNoPointerData(request)) { + return NN_ERROR() << "Request cannot be converted because it contains pointer-based memory"; + } + + return Request{ + .inputs = NN_TRY(convert(request.inputs)), + .outputs = NN_TRY(convert(request.outputs)), + .pools = NN_TRY(convert(request.pools)), + }; +} + +nn::Result convert(const nn::ErrorStatus& status) { + switch (status) { + case nn::ErrorStatus::NONE: + case nn::ErrorStatus::DEVICE_UNAVAILABLE: + case nn::ErrorStatus::GENERAL_FAILURE: + case nn::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE: + case nn::ErrorStatus::INVALID_ARGUMENT: + return static_cast(status); + default: + return ErrorStatus::GENERAL_FAILURE; + } +} + +} // namespace android::hardware::neuralnetworks::V1_0::utils diff --git a/neuralnetworks/1.1/utils/Android.bp b/neuralnetworks/1.1/utils/Android.bp new file mode 100644 index 0000000000..85a32c5834 --- /dev/null +++ b/neuralnetworks/1.1/utils/Android.bp @@ -0,0 +1,35 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +cc_library_static { + name: "neuralnetworks_utils_hal_1_1", + defaults: ["neuralnetworks_utils_defaults"], + srcs: ["src/*"], + local_include_dirs: ["include/nnapi/hal/1.1/"], + export_include_dirs: ["include"], + static_libs: [ + "neuralnetworks_types", + "neuralnetworks_utils_hal_common", + "neuralnetworks_utils_hal_1_0", + ], + shared_libs: [ + "android.hardware.neuralnetworks@1.0", + "android.hardware.neuralnetworks@1.1", + ], + export_static_lib_headers: [ + "neuralnetworks_utils_hal_common", + ], +} diff --git a/neuralnetworks/1.1/utils/OWNERS b/neuralnetworks/1.1/utils/OWNERS new file mode 100644 index 0000000000..e4feee3496 --- /dev/null +++ b/neuralnetworks/1.1/utils/OWNERS @@ -0,0 +1,11 @@ +# Neuralnetworks team +butlermichael@google.com +dgross@google.com +galarragas@google.com +jeanluc@google.com +levp@google.com +miaowang@google.com +pszczepaniak@google.com +slavash@google.com +vddang@google.com +xusongw@google.com diff --git a/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Conversions.h b/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Conversions.h new file mode 100644 index 0000000000..d0c5397faf --- /dev/null +++ b/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Conversions.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_CONVERSIONS_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_CONVERSIONS_H + +#include +#include +#include +#include + +namespace android::nn { + +Result convert(const hal::V1_1::OperationType& operationType); +Result convert(const hal::V1_1::Capabilities& capabilities); +Result convert(const hal::V1_1::Operation& operation); +Result convert(const hal::V1_1::Model& model); +Result convert(const hal::V1_1::ExecutionPreference& executionPreference); + +} // namespace android::nn + +namespace android::hardware::neuralnetworks::V1_1::utils { + +nn::Result convert(const nn::OperationType& operationType); +nn::Result convert(const nn::Capabilities& capabilities); +nn::Result convert(const nn::Operation& operation); +nn::Result convert(const nn::Model& model); +nn::Result convert(const nn::ExecutionPreference& executionPreference); + +} // namespace android::hardware::neuralnetworks::V1_1::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_CONVERSIONS_H diff --git a/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Utils.h b/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Utils.h new file mode 100644 index 0000000000..6f9aa602d8 --- /dev/null +++ b/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Utils.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_H + +#include "nnapi/hal/1.1/Conversions.h" + +#include +#include +#include +#include +#include +#include + +namespace android::hardware::neuralnetworks::V1_1::utils { + +constexpr auto kDefaultExecutionPreference = ExecutionPreference::FAST_SINGLE_ANSWER; +constexpr auto kVersion = nn::Version::ANDROID_P; + +template +nn::Result validate(const Type& halObject) { + const auto canonical = NN_TRY(nn::convert(halObject)); + const auto version = NN_TRY(nn::validate(canonical)); + if (version > utils::kVersion) { + return NN_ERROR() << ""; + } + return {}; +} + +template +bool valid(const Type& halObject) { + const auto result = utils::validate(halObject); + if (!result.has_value()) { + LOG(ERROR) << result.error(); + } + return result.has_value(); +} + +template +decltype(nn::convert(std::declval())) validatedConvertToCanonical(const Type& halObject) { + auto canonical = NN_TRY(nn::convert(halObject)); + const auto version = NN_TRY(nn::validate(canonical)); + if (version > utils::kVersion) { + return NN_ERROR() << ""; + } + return canonical; +} + +} // namespace android::hardware::neuralnetworks::V1_1::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_H diff --git a/neuralnetworks/1.1/utils/src/Assertions.cpp b/neuralnetworks/1.1/utils/src/Assertions.cpp new file mode 100644 index 0000000000..ba4a388e1c --- /dev/null +++ b/neuralnetworks/1.1/utils/src/Assertions.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +namespace { + +#define COMPARE_ENUMS_TYPES(type) \ + static_assert(std::is_same_v< \ + std::underlying_type_t<::android::hardware::neuralnetworks::V1_1::type>, \ + std::underlying_type_t<::android::nn::type>>, \ + "::android::hardware::neuralnetworks::V1_1::" #type \ + " does not have the same underlying type as ::android::nn::" #type) + +COMPARE_ENUMS_TYPES(OperationType); +COMPARE_ENUMS_TYPES(ExecutionPreference); + +#undef COMPARE_ENUMS_TYPES + +#define COMPARE_ENUMS_FULL(symbol, type) \ + static_assert( \ + static_cast>( \ + ::android::hardware::neuralnetworks::V1_1::type::symbol) == \ + static_cast>( \ + ::android::nn::type::symbol), \ + "::android::hardware::neuralnetworks::V1_1::" #type "::" #symbol \ + " does not match ::android::nn::" #type "::" #symbol) + +#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, OperationType) + +COMPARE_ENUMS(ADD); +COMPARE_ENUMS(AVERAGE_POOL_2D); +COMPARE_ENUMS(CONCATENATION); +COMPARE_ENUMS(CONV_2D); +COMPARE_ENUMS(DEPTHWISE_CONV_2D); +COMPARE_ENUMS(DEPTH_TO_SPACE); +COMPARE_ENUMS(DEQUANTIZE); +COMPARE_ENUMS(EMBEDDING_LOOKUP); +COMPARE_ENUMS(FLOOR); +COMPARE_ENUMS(FULLY_CONNECTED); +COMPARE_ENUMS(HASHTABLE_LOOKUP); +COMPARE_ENUMS(L2_NORMALIZATION); +COMPARE_ENUMS(L2_POOL_2D); +COMPARE_ENUMS(LOCAL_RESPONSE_NORMALIZATION); +COMPARE_ENUMS(LOGISTIC); +COMPARE_ENUMS(LSH_PROJECTION); +COMPARE_ENUMS(LSTM); +COMPARE_ENUMS(MAX_POOL_2D); +COMPARE_ENUMS(MUL); +COMPARE_ENUMS(RELU); +COMPARE_ENUMS(RELU1); +COMPARE_ENUMS(RELU6); +COMPARE_ENUMS(RESHAPE); +COMPARE_ENUMS(RESIZE_BILINEAR); +COMPARE_ENUMS(RNN); +COMPARE_ENUMS(SOFTMAX); +COMPARE_ENUMS(SPACE_TO_DEPTH); +COMPARE_ENUMS(SVDF); +COMPARE_ENUMS(TANH); +COMPARE_ENUMS(BATCH_TO_SPACE_ND); +COMPARE_ENUMS(DIV); +COMPARE_ENUMS(MEAN); +COMPARE_ENUMS(PAD); +COMPARE_ENUMS(SPACE_TO_BATCH_ND); +COMPARE_ENUMS(SQUEEZE); +COMPARE_ENUMS(STRIDED_SLICE); +COMPARE_ENUMS(SUB); +COMPARE_ENUMS(TRANSPOSE); +COMPARE_ENUMS(OEM_OPERATION); + +#undef COMPARE_ENUMS + +#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, ExecutionPreference) + +COMPARE_ENUMS(LOW_POWER); +COMPARE_ENUMS(FAST_SINGLE_ANSWER); +COMPARE_ENUMS(SUSTAINED_SPEED); + +#undef COMPARE_ENUMS + +#undef COMPARE_ENUMS_FULL + +} // anonymous namespace diff --git a/neuralnetworks/1.1/utils/src/Conversions.cpp b/neuralnetworks/1.1/utils/src/Conversions.cpp new file mode 100644 index 0000000000..7fee16b5f2 --- /dev/null +++ b/neuralnetworks/1.1/utils/src/Conversions.cpp @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Conversions.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace android::nn { +namespace { + +using hardware::hidl_vec; + +template +using convertOutput = std::decay_t()).value())>; + +template +Result>> convert(const hidl_vec& arguments) { + std::vector> canonical; + canonical.reserve(arguments.size()); + for (const auto& argument : arguments) { + canonical.push_back(NN_TRY(nn::convert(argument))); + } + return canonical; +} + +} // anonymous namespace + +Result convert(const hal::V1_1::OperationType& operationType) { + return static_cast(operationType); +} + +Result convert(const hal::V1_1::Capabilities& capabilities) { + const auto quantized8Performance = NN_TRY(convert(capabilities.quantized8Performance)); + const auto float32Performance = NN_TRY(convert(capabilities.float32Performance)); + const auto relaxedFloat32toFloat16Performance = + NN_TRY(convert(capabilities.relaxedFloat32toFloat16Performance)); + + auto table = hal::utils::makeQuantized8PerformanceConsistentWithP(float32Performance, + quantized8Performance); + + return Capabilities{ + .relaxedFloat32toFloat16PerformanceScalar = relaxedFloat32toFloat16Performance, + .relaxedFloat32toFloat16PerformanceTensor = relaxedFloat32toFloat16Performance, + .operandPerformance = std::move(table), + }; +} + +Result convert(const hal::V1_1::Operation& operation) { + return Operation{ + .type = NN_TRY(convert(operation.type)), + .inputs = operation.inputs, + .outputs = operation.outputs, + }; +} + +Result convert(const hal::V1_1::Model& model) { + auto operations = NN_TRY(convert(model.operations)); + + // Verify number of consumers. + const auto numberOfConsumers = + hal::utils::countNumberOfConsumers(model.operands.size(), operations); + CHECK(model.operands.size() == numberOfConsumers.size()); + for (size_t i = 0; i < model.operands.size(); ++i) { + if (model.operands[i].numberOfConsumers != numberOfConsumers[i]) { + return NN_ERROR() << "Invalid numberOfConsumers for operand " << i << ", expected " + << numberOfConsumers[i] << " but found " + << model.operands[i].numberOfConsumers; + } + } + + auto main = Model::Subgraph{ + .operands = NN_TRY(convert(model.operands)), + .operations = std::move(operations), + .inputIndexes = model.inputIndexes, + .outputIndexes = model.outputIndexes, + }; + + return Model{ + .main = std::move(main), + .operandValues = NN_TRY(convert(model.operandValues)), + .pools = NN_TRY(convert(model.pools)), + .relaxComputationFloat32toFloat16 = model.relaxComputationFloat32toFloat16, + }; +} + +Result convert(const hal::V1_1::ExecutionPreference& executionPreference) { + return static_cast(executionPreference); +} + +} // namespace android::nn + +namespace android::hardware::neuralnetworks::V1_1::utils { +namespace { + +using utils::convert; + +nn::Result convert( + const nn::Capabilities::PerformanceInfo& performanceInfo) { + return V1_0::utils::convert(performanceInfo); +} + +nn::Result convert(const nn::Operand& operand) { + return V1_0::utils::convert(operand); +} + +nn::Result> convert(const nn::Model::OperandValues& operandValues) { + return V1_0::utils::convert(operandValues); +} + +nn::Result convert(const nn::Memory& memory) { + return V1_0::utils::convert(memory); +} + +template +using convertOutput = std::decay_t()).value())>; + +template +nn::Result>> convert(const std::vector& arguments) { + hidl_vec> halObject(arguments.size()); + for (size_t i = 0; i < arguments.size(); ++i) { + halObject[i] = NN_TRY(convert(arguments[i])); + } + return halObject; +} + +} // anonymous namespace + +nn::Result convert(const nn::OperationType& operationType) { + return static_cast(operationType); +} + +nn::Result convert(const nn::Capabilities& capabilities) { + return Capabilities{ + .float32Performance = NN_TRY(convert( + capabilities.operandPerformance.lookup(nn::OperandType::TENSOR_FLOAT32))), + .quantized8Performance = NN_TRY(convert( + capabilities.operandPerformance.lookup(nn::OperandType::TENSOR_QUANT8_ASYMM))), + .relaxedFloat32toFloat16Performance = + NN_TRY(convert(capabilities.relaxedFloat32toFloat16PerformanceTensor)), + }; +} + +nn::Result convert(const nn::Operation& operation) { + return Operation{ + .type = NN_TRY(convert(operation.type)), + .inputs = operation.inputs, + .outputs = operation.outputs, + }; +} + +nn::Result convert(const nn::Model& model) { + if (!hal::utils::hasNoPointerData(model)) { + return NN_ERROR() << "Mdoel cannot be converted because it contains pointer-based memory"; + } + + auto operands = NN_TRY(convert(model.main.operands)); + + // Update number of consumers. + const auto numberOfConsumers = + hal::utils::countNumberOfConsumers(operands.size(), model.main.operations); + CHECK(operands.size() == numberOfConsumers.size()); + for (size_t i = 0; i < operands.size(); ++i) { + operands[i].numberOfConsumers = numberOfConsumers[i]; + } + + return Model{ + .operands = std::move(operands), + .operations = NN_TRY(convert(model.main.operations)), + .inputIndexes = model.main.inputIndexes, + .outputIndexes = model.main.outputIndexes, + .operandValues = NN_TRY(convert(model.operandValues)), + .pools = NN_TRY(convert(model.pools)), + .relaxComputationFloat32toFloat16 = model.relaxComputationFloat32toFloat16, + }; +} + +nn::Result convert(const nn::ExecutionPreference& executionPreference) { + return static_cast(executionPreference); +} + +} // namespace android::hardware::neuralnetworks::V1_1::utils diff --git a/neuralnetworks/1.2/utils/Android.bp b/neuralnetworks/1.2/utils/Android.bp new file mode 100644 index 0000000000..a1dd3d0b0d --- /dev/null +++ b/neuralnetworks/1.2/utils/Android.bp @@ -0,0 +1,37 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +cc_library_static { + name: "neuralnetworks_utils_hal_1_2", + defaults: ["neuralnetworks_utils_defaults"], + srcs: ["src/*"], + local_include_dirs: ["include/nnapi/hal/1.2/"], + export_include_dirs: ["include"], + static_libs: [ + "neuralnetworks_types", + "neuralnetworks_utils_hal_common", + "neuralnetworks_utils_hal_1_0", + "neuralnetworks_utils_hal_1_1", + ], + shared_libs: [ + "android.hardware.neuralnetworks@1.0", + "android.hardware.neuralnetworks@1.1", + "android.hardware.neuralnetworks@1.2", + ], + export_static_lib_headers: [ + "neuralnetworks_utils_hal_common", + ], +} diff --git a/neuralnetworks/1.2/utils/OWNERS b/neuralnetworks/1.2/utils/OWNERS new file mode 100644 index 0000000000..e4feee3496 --- /dev/null +++ b/neuralnetworks/1.2/utils/OWNERS @@ -0,0 +1,11 @@ +# Neuralnetworks team +butlermichael@google.com +dgross@google.com +galarragas@google.com +jeanluc@google.com +levp@google.com +miaowang@google.com +pszczepaniak@google.com +slavash@google.com +vddang@google.com +xusongw@google.com diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Conversions.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Conversions.h new file mode 100644 index 0000000000..81bf7928f6 --- /dev/null +++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Conversions.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_CONVERSIONS_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_CONVERSIONS_H + +#include +#include +#include +#include + +namespace android::nn { + +Result convert(const hal::V1_2::OperandType& operandType); +Result convert(const hal::V1_2::OperationType& operationType); +Result convert(const hal::V1_2::DeviceType& deviceType); +Result convert(const hal::V1_2::Capabilities& capabilities); +Result convert( + const hal::V1_2::Capabilities::OperandPerformance& operandPerformance); +Result convert(const hal::V1_2::Operation& operation); +Result convert( + const hal::V1_2::SymmPerChannelQuantParams& symmPerChannelQuantParams); +Result convert(const hal::V1_2::Operand& operand); +Result convert(const hal::V1_2::Operand::ExtraParams& extraParams); +Result convert(const hal::V1_2::Model& model); +Result convert( + const hal::V1_2::Model::ExtensionNameAndPrefix& extensionNameAndPrefix); +Result convert(const hal::V1_2::OutputShape& outputShape); +Result convert(const hal::V1_2::MeasureTiming& measureTiming); +Result convert(const hal::V1_2::Timing& timing); +Result convert(const hal::V1_2::Extension& extension); +Result convert( + const hal::V1_2::Extension::OperandTypeInformation& operandTypeInformation); +Result convert(const hardware::hidl_handle& handle); + +Result> convert(const hardware::hidl_vec& extensions); +Result> convert(const hardware::hidl_vec& handles); +Result> convert( + const hardware::hidl_vec& outputShapes); + +} // namespace android::nn + +namespace android::hardware::neuralnetworks::V1_2::utils { + +nn::Result convert(const nn::OperandType& operandType); +nn::Result convert(const nn::OperationType& operationType); +nn::Result convert(const nn::DeviceType& deviceType); +nn::Result convert(const nn::Capabilities& capabilities); +nn::Result convert( + const nn::Capabilities::OperandPerformance& operandPerformance); +nn::Result convert(const nn::Operation& operation); +nn::Result convert( + const nn::Operand::SymmPerChannelQuantParams& symmPerChannelQuantParams); +nn::Result convert(const nn::Operand& operand); +nn::Result convert(const nn::Operand::ExtraParams& extraParams); +nn::Result convert(const nn::Model& model); +nn::Result convert( + const nn::Model::ExtensionNameAndPrefix& extensionNameAndPrefix); +nn::Result convert(const nn::OutputShape& outputShape); +nn::Result convert(const nn::MeasureTiming& measureTiming); +nn::Result convert(const nn::Timing& timing); +nn::Result convert(const nn::Extension& extension); +nn::Result convert( + const nn::Extension::OperandTypeInformation& operandTypeInformation); +nn::Result convert(const nn::NativeHandle& handle); + +nn::Result> convert(const std::vector& extensions); +nn::Result> convert(const std::vector& handles); +nn::Result> convert(const std::vector& outputShapes); + +} // namespace android::hardware::neuralnetworks::V1_2::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_CONVERSIONS_H diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h new file mode 100644 index 0000000000..b1c2f1a81a --- /dev/null +++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_H + +#include "nnapi/hal/1.2/Conversions.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace android::hardware::neuralnetworks::V1_2::utils { + +constexpr auto kDefaultMesaureTiming = MeasureTiming::NO; +constexpr auto kNoTiming = Timing{.timeOnDevice = std::numeric_limits::max(), + .timeInDriver = std::numeric_limits::max()}; +constexpr auto kVersion = nn::Version::ANDROID_Q; + +template +nn::Result validate(const Type& halObject) { + const auto canonical = NN_TRY(nn::convert(halObject)); + const auto version = NN_TRY(nn::validate(canonical)); + if (version > utils::kVersion) { + return NN_ERROR() << ""; + } + return {}; +} + +template +bool valid(const Type& halObject) { + const auto result = utils::validate(halObject); + if (!result.has_value()) { + LOG(ERROR) << result.error(); + } + return result.has_value(); +} + +template +decltype(nn::convert(std::declval())) validatedConvertToCanonical(const Type& halObject) { + auto canonical = NN_TRY(nn::convert(halObject)); + const auto version = NN_TRY(nn::validate(canonical)); + if (version > utils::kVersion) { + return NN_ERROR() << ""; + } + return canonical; +} + +} // namespace android::hardware::neuralnetworks::V1_2::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_H diff --git a/neuralnetworks/1.2/utils/src/Assertions.cpp b/neuralnetworks/1.2/utils/src/Assertions.cpp new file mode 100644 index 0000000000..9d9716acdf --- /dev/null +++ b/neuralnetworks/1.2/utils/src/Assertions.cpp @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +namespace { + +#define COMPARE_ENUMS_TYPES(type) \ + static_assert(std::is_same_v< \ + std::underlying_type_t<::android::hardware::neuralnetworks::V1_2::type>, \ + std::underlying_type_t<::android::nn::type>>, \ + "::android::hardware::neuralnetworks::V1_2::" #type \ + " does not have the same underlying type as ::android::nn::" #type) + +COMPARE_ENUMS_TYPES(OperandType); +COMPARE_ENUMS_TYPES(OperationType); +COMPARE_ENUMS_TYPES(DeviceType); +COMPARE_ENUMS_TYPES(MeasureTiming); + +#undef COMPARE_ENUMS_TYPES + +#define COMPARE_ENUMS_FULL(symbol, type) \ + static_assert( \ + static_cast>( \ + ::android::hardware::neuralnetworks::V1_2::type::symbol) == \ + static_cast>( \ + ::android::nn::type::symbol), \ + "::android::hardware::neuralnetworks::V1_2::" #type "::" #symbol \ + " does not match ::android::nn::" #type "::" #symbol) + +#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, OperandType) + +COMPARE_ENUMS(FLOAT32); +COMPARE_ENUMS(INT32); +COMPARE_ENUMS(UINT32); +COMPARE_ENUMS(TENSOR_FLOAT32); +COMPARE_ENUMS(TENSOR_INT32); +COMPARE_ENUMS(TENSOR_QUANT8_ASYMM); +COMPARE_ENUMS(BOOL); +COMPARE_ENUMS(TENSOR_QUANT16_SYMM); +COMPARE_ENUMS(TENSOR_FLOAT16); +COMPARE_ENUMS(TENSOR_BOOL8); +COMPARE_ENUMS(FLOAT16); +COMPARE_ENUMS(TENSOR_QUANT8_SYMM_PER_CHANNEL); +COMPARE_ENUMS(TENSOR_QUANT16_ASYMM); +COMPARE_ENUMS(TENSOR_QUANT8_SYMM); +COMPARE_ENUMS(OEM); +COMPARE_ENUMS(TENSOR_OEM_BYTE); + +#undef COMPARE_ENUMS + +#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, OperationType) + +COMPARE_ENUMS(ADD); +COMPARE_ENUMS(AVERAGE_POOL_2D); +COMPARE_ENUMS(CONCATENATION); +COMPARE_ENUMS(CONV_2D); +COMPARE_ENUMS(DEPTHWISE_CONV_2D); +COMPARE_ENUMS(DEPTH_TO_SPACE); +COMPARE_ENUMS(DEQUANTIZE); +COMPARE_ENUMS(EMBEDDING_LOOKUP); +COMPARE_ENUMS(FLOOR); +COMPARE_ENUMS(FULLY_CONNECTED); +COMPARE_ENUMS(HASHTABLE_LOOKUP); +COMPARE_ENUMS(L2_NORMALIZATION); +COMPARE_ENUMS(L2_POOL_2D); +COMPARE_ENUMS(LOCAL_RESPONSE_NORMALIZATION); +COMPARE_ENUMS(LOGISTIC); +COMPARE_ENUMS(LSH_PROJECTION); +COMPARE_ENUMS(LSTM); +COMPARE_ENUMS(MAX_POOL_2D); +COMPARE_ENUMS(MUL); +COMPARE_ENUMS(RELU); +COMPARE_ENUMS(RELU1); +COMPARE_ENUMS(RELU6); +COMPARE_ENUMS(RESHAPE); +COMPARE_ENUMS(RESIZE_BILINEAR); +COMPARE_ENUMS(RNN); +COMPARE_ENUMS(SOFTMAX); +COMPARE_ENUMS(SPACE_TO_DEPTH); +COMPARE_ENUMS(SVDF); +COMPARE_ENUMS(TANH); +COMPARE_ENUMS(BATCH_TO_SPACE_ND); +COMPARE_ENUMS(DIV); +COMPARE_ENUMS(MEAN); +COMPARE_ENUMS(PAD); +COMPARE_ENUMS(SPACE_TO_BATCH_ND); +COMPARE_ENUMS(SQUEEZE); +COMPARE_ENUMS(STRIDED_SLICE); +COMPARE_ENUMS(SUB); +COMPARE_ENUMS(TRANSPOSE); +COMPARE_ENUMS(ABS); +COMPARE_ENUMS(ARGMAX); +COMPARE_ENUMS(ARGMIN); +COMPARE_ENUMS(AXIS_ALIGNED_BBOX_TRANSFORM); +COMPARE_ENUMS(BIDIRECTIONAL_SEQUENCE_LSTM); +COMPARE_ENUMS(BIDIRECTIONAL_SEQUENCE_RNN); +COMPARE_ENUMS(BOX_WITH_NMS_LIMIT); +COMPARE_ENUMS(CAST); +COMPARE_ENUMS(CHANNEL_SHUFFLE); +COMPARE_ENUMS(DETECTION_POSTPROCESSING); +COMPARE_ENUMS(EQUAL); +COMPARE_ENUMS(EXP); +COMPARE_ENUMS(EXPAND_DIMS); +COMPARE_ENUMS(GATHER); +COMPARE_ENUMS(GENERATE_PROPOSALS); +COMPARE_ENUMS(GREATER); +COMPARE_ENUMS(GREATER_EQUAL); +COMPARE_ENUMS(GROUPED_CONV_2D); +COMPARE_ENUMS(HEATMAP_MAX_KEYPOINT); +COMPARE_ENUMS(INSTANCE_NORMALIZATION); +COMPARE_ENUMS(LESS); +COMPARE_ENUMS(LESS_EQUAL); +COMPARE_ENUMS(LOG); +COMPARE_ENUMS(LOGICAL_AND); +COMPARE_ENUMS(LOGICAL_NOT); +COMPARE_ENUMS(LOGICAL_OR); +COMPARE_ENUMS(LOG_SOFTMAX); +COMPARE_ENUMS(MAXIMUM); +COMPARE_ENUMS(MINIMUM); +COMPARE_ENUMS(NEG); +COMPARE_ENUMS(NOT_EQUAL); +COMPARE_ENUMS(PAD_V2); +COMPARE_ENUMS(POW); +COMPARE_ENUMS(PRELU); +COMPARE_ENUMS(QUANTIZE); +COMPARE_ENUMS(QUANTIZED_16BIT_LSTM); +COMPARE_ENUMS(RANDOM_MULTINOMIAL); +COMPARE_ENUMS(REDUCE_ALL); +COMPARE_ENUMS(REDUCE_ANY); +COMPARE_ENUMS(REDUCE_MAX); +COMPARE_ENUMS(REDUCE_MIN); +COMPARE_ENUMS(REDUCE_PROD); +COMPARE_ENUMS(REDUCE_SUM); +COMPARE_ENUMS(ROI_ALIGN); +COMPARE_ENUMS(ROI_POOLING); +COMPARE_ENUMS(RSQRT); +COMPARE_ENUMS(SELECT); +COMPARE_ENUMS(SIN); +COMPARE_ENUMS(SLICE); +COMPARE_ENUMS(SPLIT); +COMPARE_ENUMS(SQRT); +COMPARE_ENUMS(TILE); +COMPARE_ENUMS(TOPK_V2); +COMPARE_ENUMS(TRANSPOSE_CONV_2D); +COMPARE_ENUMS(UNIDIRECTIONAL_SEQUENCE_LSTM); +COMPARE_ENUMS(UNIDIRECTIONAL_SEQUENCE_RNN); +COMPARE_ENUMS(RESIZE_NEAREST_NEIGHBOR); +COMPARE_ENUMS(OEM_OPERATION); + +#undef COMPARE_ENUMS + +#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, DeviceType) + +COMPARE_ENUMS(OTHER); +COMPARE_ENUMS(CPU); +COMPARE_ENUMS(GPU); +COMPARE_ENUMS(ACCELERATOR); + +#undef COMPARE_ENUMS + +#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, MeasureTiming) + +COMPARE_ENUMS(NO); +COMPARE_ENUMS(YES); + +#undef COMPARE_ENUMS + +#undef COMPARE_ENUMS_FULL + +} // anonymous namespace diff --git a/neuralnetworks/1.2/utils/src/Conversions.cpp b/neuralnetworks/1.2/utils/src/Conversions.cpp new file mode 100644 index 0000000000..fed314b7c1 --- /dev/null +++ b/neuralnetworks/1.2/utils/src/Conversions.cpp @@ -0,0 +1,502 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Conversions.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace { + +template +constexpr std::underlying_type_t underlyingType(Type value) { + return static_cast>(value); +} + +} // namespace + +namespace android::nn { +namespace { + +constexpr bool validOperandType(OperandType operandType) { + switch (operandType) { + case OperandType::FLOAT32: + case OperandType::INT32: + case OperandType::UINT32: + case OperandType::TENSOR_FLOAT32: + case OperandType::TENSOR_INT32: + case OperandType::TENSOR_QUANT8_ASYMM: + case OperandType::BOOL: + case OperandType::TENSOR_QUANT16_SYMM: + case OperandType::TENSOR_FLOAT16: + case OperandType::TENSOR_BOOL8: + case OperandType::FLOAT16: + case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL: + case OperandType::TENSOR_QUANT16_ASYMM: + case OperandType::TENSOR_QUANT8_SYMM: + case OperandType::OEM: + case OperandType::TENSOR_OEM_BYTE: + return true; + default: + break; + } + return isExtension(operandType); +} + +using hardware::hidl_handle; +using hardware::hidl_vec; + +template +using ConvertOutput = std::decay_t()).value())>; + +template +Result>> convertVec(const hidl_vec& arguments) { + std::vector> canonical; + canonical.reserve(arguments.size()); + for (const auto& argument : arguments) { + canonical.push_back(NN_TRY(nn::convert(argument))); + } + return canonical; +} + +template +Result>> convert(const hidl_vec& arguments) { + return convertVec(arguments); +} + +} // anonymous namespace + +Result convert(const hal::V1_2::OperandType& operandType) { + return static_cast(operandType); +} + +Result convert(const hal::V1_2::OperationType& operationType) { + return static_cast(operationType); +} + +Result convert(const hal::V1_2::DeviceType& deviceType) { + return static_cast(deviceType); +} + +Result convert(const hal::V1_2::Capabilities& capabilities) { + const bool validOperandTypes = std::all_of( + capabilities.operandPerformance.begin(), capabilities.operandPerformance.end(), + [](const hal::V1_2::Capabilities::OperandPerformance& operandPerformance) { + const auto maybeType = convert(operandPerformance.type); + return !maybeType.has_value() ? false : validOperandType(maybeType.value()); + }); + if (!validOperandTypes) { + return NN_ERROR() + << "Invalid OperandType when converting OperandPerformance in Capabilities"; + } + + const auto relaxedFloat32toFloat16PerformanceScalar = + NN_TRY(convert(capabilities.relaxedFloat32toFloat16PerformanceScalar)); + const auto relaxedFloat32toFloat16PerformanceTensor = + NN_TRY(convert(capabilities.relaxedFloat32toFloat16PerformanceTensor)); + auto operandPerformance = NN_TRY(convert(capabilities.operandPerformance)); + + auto table = + NN_TRY(Capabilities::OperandPerformanceTable::create(std::move(operandPerformance))); + + return Capabilities{ + .relaxedFloat32toFloat16PerformanceScalar = relaxedFloat32toFloat16PerformanceScalar, + .relaxedFloat32toFloat16PerformanceTensor = relaxedFloat32toFloat16PerformanceTensor, + .operandPerformance = std::move(table), + }; +} + +Result convert( + const hal::V1_2::Capabilities::OperandPerformance& operandPerformance) { + return Capabilities::OperandPerformance{ + .type = NN_TRY(convert(operandPerformance.type)), + .info = NN_TRY(convert(operandPerformance.info)), + }; +} + +Result convert(const hal::V1_2::Operation& operation) { + return Operation{ + .type = NN_TRY(convert(operation.type)), + .inputs = operation.inputs, + .outputs = operation.outputs, + }; +} + +Result convert( + const hal::V1_2::SymmPerChannelQuantParams& symmPerChannelQuantParams) { + return Operand::SymmPerChannelQuantParams{ + .scales = symmPerChannelQuantParams.scales, + .channelDim = symmPerChannelQuantParams.channelDim, + }; +} + +Result convert(const hal::V1_2::Operand& operand) { + return Operand{ + .type = NN_TRY(convert(operand.type)), + .dimensions = operand.dimensions, + .scale = operand.scale, + .zeroPoint = operand.zeroPoint, + .lifetime = NN_TRY(convert(operand.lifetime)), + .location = NN_TRY(convert(operand.location)), + .extraParams = NN_TRY(convert(operand.extraParams)), + }; +} + +Result convert(const hal::V1_2::Operand::ExtraParams& extraParams) { + using Discriminator = hal::V1_2::Operand::ExtraParams::hidl_discriminator; + switch (extraParams.getDiscriminator()) { + case Discriminator::none: + return Operand::NoParams{}; + case Discriminator::channelQuant: + return convert(extraParams.channelQuant()); + case Discriminator::extension: + return extraParams.extension(); + } + return NN_ERROR() << "Unrecognized Operand::ExtraParams discriminator: " + << underlyingType(extraParams.getDiscriminator()); +} + +Result convert(const hal::V1_2::Model& model) { + auto operations = NN_TRY(convert(model.operations)); + + // Verify number of consumers. + const auto numberOfConsumers = + hal::utils::countNumberOfConsumers(model.operands.size(), operations); + CHECK(model.operands.size() == numberOfConsumers.size()); + for (size_t i = 0; i < model.operands.size(); ++i) { + if (model.operands[i].numberOfConsumers != numberOfConsumers[i]) { + return NN_ERROR() << "Invalid numberOfConsumers for operand " << i << ", expected " + << numberOfConsumers[i] << " but found " + << model.operands[i].numberOfConsumers; + } + } + + auto main = Model::Subgraph{ + .operands = NN_TRY(convert(model.operands)), + .operations = std::move(operations), + .inputIndexes = model.inputIndexes, + .outputIndexes = model.outputIndexes, + }; + + return Model{ + .main = std::move(main), + .operandValues = NN_TRY(convert(model.operandValues)), + .pools = NN_TRY(convert(model.pools)), + .relaxComputationFloat32toFloat16 = model.relaxComputationFloat32toFloat16, + .extensionNameToPrefix = NN_TRY(convert(model.extensionNameToPrefix)), + }; +} + +Result convert( + const hal::V1_2::Model::ExtensionNameAndPrefix& extensionNameAndPrefix) { + return Model::ExtensionNameAndPrefix{ + .name = extensionNameAndPrefix.name, + .prefix = extensionNameAndPrefix.prefix, + }; +} + +Result convert(const hal::V1_2::OutputShape& outputShape) { + return OutputShape{ + .dimensions = outputShape.dimensions, + .isSufficient = outputShape.isSufficient, + }; +} + +Result convert(const hal::V1_2::MeasureTiming& measureTiming) { + return static_cast(measureTiming); +} + +Result convert(const hal::V1_2::Timing& timing) { + return Timing{.timeOnDevice = timing.timeOnDevice, .timeInDriver = timing.timeInDriver}; +} + +Result convert(const hal::V1_2::Extension& extension) { + return Extension{ + .name = extension.name, + .operandTypes = NN_TRY(convert(extension.operandTypes)), + }; +} + +Result convert( + const hal::V1_2::Extension::OperandTypeInformation& operandTypeInformation) { + return Extension::OperandTypeInformation{ + .type = operandTypeInformation.type, + .isTensor = operandTypeInformation.isTensor, + .byteSize = operandTypeInformation.byteSize, + }; +} + +Result convert(const hidl_handle& handle) { + auto* cloned = native_handle_clone(handle.getNativeHandle()); + return ::android::NativeHandle::create(cloned, /*ownsHandle=*/true); +} + +Result> convert(const hidl_vec& extensions) { + return convertVec(extensions); +} + +Result> convert(const hidl_vec& handles) { + return convertVec(handles); +} + +Result> convert(const hidl_vec& outputShapes) { + return convertVec(outputShapes); +} + +} // namespace android::nn + +namespace android::hardware::neuralnetworks::V1_2::utils { +namespace { + +using utils::convert; + +nn::Result convert(const nn::Operand::LifeTime& lifetime) { + return V1_0::utils::convert(lifetime); +} + +nn::Result convert( + const nn::Capabilities::PerformanceInfo& performanceInfo) { + return V1_0::utils::convert(performanceInfo); +} + +nn::Result convert(const nn::DataLocation& location) { + return V1_0::utils::convert(location); +} + +nn::Result> convert(const nn::Model::OperandValues& operandValues) { + return V1_0::utils::convert(operandValues); +} + +nn::Result convert(const nn::Memory& memory) { + return V1_0::utils::convert(memory); +} + +template +using ConvertOutput = std::decay_t()).value())>; + +template +nn::Result>> convertVec(const std::vector& arguments) { + hidl_vec> halObject(arguments.size()); + for (size_t i = 0; i < arguments.size(); ++i) { + halObject[i] = NN_TRY(convert(arguments[i])); + } + return halObject; +} + +template +nn::Result>> convert(const std::vector& arguments) { + return convertVec(arguments); +} + +nn::Result makeExtraParams(nn::Operand::NoParams /*noParams*/) { + return Operand::ExtraParams{}; +} + +nn::Result makeExtraParams( + const nn::Operand::SymmPerChannelQuantParams& channelQuant) { + Operand::ExtraParams ret; + ret.channelQuant(NN_TRY(convert(channelQuant))); + return ret; +} + +nn::Result makeExtraParams(const nn::Operand::ExtensionParams& extension) { + Operand::ExtraParams ret; + ret.extension(extension); + return ret; +} + +} // anonymous namespace + +nn::Result convert(const nn::OperandType& operandType) { + return static_cast(operandType); +} + +nn::Result convert(const nn::OperationType& operationType) { + return static_cast(operationType); +} + +nn::Result convert(const nn::DeviceType& deviceType) { + switch (deviceType) { + case nn::DeviceType::UNKNOWN: + return NN_ERROR() << "Invalid DeviceType UNKNOWN"; + case nn::DeviceType::OTHER: + case nn::DeviceType::CPU: + case nn::DeviceType::GPU: + case nn::DeviceType::ACCELERATOR: + return static_cast(deviceType); + } + return NN_ERROR() << "Invalid DeviceType " << underlyingType(deviceType); +} + +nn::Result convert(const nn::Capabilities& capabilities) { + std::vector operandPerformance; + operandPerformance.reserve(capabilities.operandPerformance.asVector().size()); + std::copy_if(capabilities.operandPerformance.asVector().begin(), + capabilities.operandPerformance.asVector().end(), + std::back_inserter(operandPerformance), + [](const nn::Capabilities::OperandPerformance& operandPerformance) { + return nn::validOperandType(operandPerformance.type); + }); + + return Capabilities{ + .relaxedFloat32toFloat16PerformanceScalar = + NN_TRY(convert(capabilities.relaxedFloat32toFloat16PerformanceScalar)), + .relaxedFloat32toFloat16PerformanceTensor = + NN_TRY(convert(capabilities.relaxedFloat32toFloat16PerformanceTensor)), + .operandPerformance = NN_TRY(convert(operandPerformance)), + }; +} + +nn::Result convert( + const nn::Capabilities::OperandPerformance& operandPerformance) { + return Capabilities::OperandPerformance{ + .type = NN_TRY(convert(operandPerformance.type)), + .info = NN_TRY(convert(operandPerformance.info)), + }; +} + +nn::Result convert(const nn::Operation& operation) { + return Operation{ + .type = NN_TRY(convert(operation.type)), + .inputs = operation.inputs, + .outputs = operation.outputs, + }; +} + +nn::Result convert( + const nn::Operand::SymmPerChannelQuantParams& symmPerChannelQuantParams) { + return SymmPerChannelQuantParams{ + .scales = symmPerChannelQuantParams.scales, + .channelDim = symmPerChannelQuantParams.channelDim, + }; +} + +nn::Result convert(const nn::Operand& operand) { + return Operand{ + .type = NN_TRY(convert(operand.type)), + .dimensions = operand.dimensions, + .numberOfConsumers = 0, + .scale = operand.scale, + .zeroPoint = operand.zeroPoint, + .lifetime = NN_TRY(convert(operand.lifetime)), + .location = NN_TRY(convert(operand.location)), + .extraParams = NN_TRY(convert(operand.extraParams)), + }; +} + +nn::Result convert(const nn::Operand::ExtraParams& extraParams) { + return std::visit([](const auto& x) { return makeExtraParams(x); }, extraParams); +} + +nn::Result convert(const nn::Model& model) { + if (!hal::utils::hasNoPointerData(model)) { + return NN_ERROR() << "Model cannot be converted because it contains pointer-based memory"; + } + + auto operands = NN_TRY(convert(model.main.operands)); + + // Update number of consumers. + const auto numberOfConsumers = + hal::utils::countNumberOfConsumers(operands.size(), model.main.operations); + CHECK(operands.size() == numberOfConsumers.size()); + for (size_t i = 0; i < operands.size(); ++i) { + operands[i].numberOfConsumers = numberOfConsumers[i]; + } + + return Model{ + .operands = std::move(operands), + .operations = NN_TRY(convert(model.main.operations)), + .inputIndexes = model.main.inputIndexes, + .outputIndexes = model.main.outputIndexes, + .operandValues = NN_TRY(convert(model.operandValues)), + .pools = NN_TRY(convert(model.pools)), + .relaxComputationFloat32toFloat16 = model.relaxComputationFloat32toFloat16, + .extensionNameToPrefix = NN_TRY(convert(model.extensionNameToPrefix)), + }; +} + +nn::Result convert( + const nn::Model::ExtensionNameAndPrefix& extensionNameAndPrefix) { + return Model::ExtensionNameAndPrefix{ + .name = extensionNameAndPrefix.name, + .prefix = extensionNameAndPrefix.prefix, + }; +} + +nn::Result convert(const nn::OutputShape& outputShape) { + return OutputShape{.dimensions = outputShape.dimensions, + .isSufficient = outputShape.isSufficient}; +} + +nn::Result convert(const nn::MeasureTiming& measureTiming) { + return static_cast(measureTiming); +} + +nn::Result convert(const nn::Timing& timing) { + return Timing{.timeOnDevice = timing.timeOnDevice, .timeInDriver = timing.timeInDriver}; +} + +nn::Result convert(const nn::Extension& extension) { + return Extension{ + .name = extension.name, + .operandTypes = NN_TRY(convert(extension.operandTypes)), + }; +} + +nn::Result convert( + const nn::Extension::OperandTypeInformation& operandTypeInformation) { + return Extension::OperandTypeInformation{ + .type = operandTypeInformation.type, + .isTensor = operandTypeInformation.isTensor, + .byteSize = operandTypeInformation.byteSize, + }; +} + +nn::Result convert(const nn::NativeHandle& handle) { + const auto hidlHandle = hidl_handle(handle->handle()); + // Copy memory to force the native_handle_t to be copied. + auto copiedHandle = hidlHandle; + return copiedHandle; +} + +nn::Result> convert(const std::vector& extensions) { + return convertVec(extensions); +} + +nn::Result> convert(const std::vector& handles) { + return convertVec(handles); +} + +nn::Result> convert(const std::vector& outputShapes) { + return convertVec(outputShapes); +} + +} // namespace android::hardware::neuralnetworks::V1_2::utils diff --git a/neuralnetworks/1.3/utils/Android.bp b/neuralnetworks/1.3/utils/Android.bp new file mode 100644 index 0000000000..279b250532 --- /dev/null +++ b/neuralnetworks/1.3/utils/Android.bp @@ -0,0 +1,39 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +cc_library_static { + name: "neuralnetworks_utils_hal_1_3", + defaults: ["neuralnetworks_utils_defaults"], + srcs: ["src/*"], + local_include_dirs: ["include/nnapi/hal/1.3/"], + export_include_dirs: ["include"], + static_libs: [ + "neuralnetworks_types", + "neuralnetworks_utils_hal_common", + "neuralnetworks_utils_hal_1_0", + "neuralnetworks_utils_hal_1_1", + "neuralnetworks_utils_hal_1_2", + ], + shared_libs: [ + "android.hardware.neuralnetworks@1.0", + "android.hardware.neuralnetworks@1.1", + "android.hardware.neuralnetworks@1.2", + "android.hardware.neuralnetworks@1.3", + ], + export_static_lib_headers: [ + "neuralnetworks_utils_hal_common", + ], +} diff --git a/neuralnetworks/1.3/utils/OWNERS b/neuralnetworks/1.3/utils/OWNERS new file mode 100644 index 0000000000..e4feee3496 --- /dev/null +++ b/neuralnetworks/1.3/utils/OWNERS @@ -0,0 +1,11 @@ +# Neuralnetworks team +butlermichael@google.com +dgross@google.com +galarragas@google.com +jeanluc@google.com +levp@google.com +miaowang@google.com +pszczepaniak@google.com +slavash@google.com +vddang@google.com +xusongw@google.com diff --git a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Conversions.h b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Conversions.h new file mode 100644 index 0000000000..43987a9727 --- /dev/null +++ b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Conversions.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_CONVERSIONS_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_CONVERSIONS_H + +#include +#include +#include +#include +#include + +namespace android::nn { + +Result convert(const hal::V1_3::OperandType& operandType); +Result convert(const hal::V1_3::OperationType& operationType); +Result convert(const hal::V1_3::Priority& priority); +Result convert(const hal::V1_3::Capabilities& capabilities); +Result convert( + const hal::V1_3::Capabilities::OperandPerformance& operandPerformance); +Result convert(const hal::V1_3::Operation& operation); +Result convert(const hal::V1_3::OperandLifeTime& operandLifeTime); +Result convert(const hal::V1_3::Operand& operand); +Result convert(const hal::V1_3::Model& model); +Result convert(const hal::V1_3::Subgraph& subgraph); +Result convert(const hal::V1_3::BufferDesc& bufferDesc); +Result convert(const hal::V1_3::BufferRole& bufferRole); +Result convert(const hal::V1_3::Request& request); +Result convert(const hal::V1_3::Request::MemoryPool& memoryPool); +Result convert(const hal::V1_3::OptionalTimePoint& optionalTimePoint); +Result convert( + const hal::V1_3::OptionalTimeoutDuration& optionalTimeoutDuration); +Result convert(const hal::V1_3::ErrorStatus& errorStatus); + +Result> convert( + const hardware::hidl_vec& bufferRoles); + +} // namespace android::nn + +namespace android::hardware::neuralnetworks::V1_3::utils { + +nn::Result convert(const nn::OperandType& operandType); +nn::Result convert(const nn::OperationType& operationType); +nn::Result convert(const nn::Priority& priority); +nn::Result convert(const nn::Capabilities& capabilities); +nn::Result convert( + const nn::Capabilities::OperandPerformance& operandPerformance); +nn::Result convert(const nn::Operation& operation); +nn::Result convert(const nn::Operand::LifeTime& operandLifeTime); +nn::Result convert(const nn::Operand& operand); +nn::Result convert(const nn::Model& model); +nn::Result convert(const nn::Model::Subgraph& subgraph); +nn::Result convert(const nn::BufferDesc& bufferDesc); +nn::Result convert(const nn::BufferRole& bufferRole); +nn::Result convert(const nn::Request& request); +nn::Result convert(const nn::Request::MemoryPool& memoryPool); +nn::Result convert(const nn::OptionalTimePoint& optionalTimePoint); +nn::Result convert( + const nn::OptionalTimeoutDuration& optionalTimeoutDuration); +nn::Result convert(const nn::ErrorStatus& errorStatus); + +nn::Result> convert(const std::vector& bufferRoles); + +} // namespace android::hardware::neuralnetworks::V1_3::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_CONVERSIONS_H diff --git a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Utils.h b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Utils.h new file mode 100644 index 0000000000..f8c975d5d7 --- /dev/null +++ b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Utils.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_H + +#include "nnapi/hal/1.3/Conversions.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace android::hardware::neuralnetworks::V1_3::utils { + +constexpr auto kDefaultPriority = Priority::MEDIUM; +constexpr auto kVersion = nn::Version::ANDROID_R; + +template +nn::Result validate(const Type& halObject) { + const auto canonical = NN_TRY(nn::convert(halObject)); + const auto version = NN_TRY(nn::validate(canonical)); + if (version > utils::kVersion) { + return NN_ERROR() << ""; + } + return {}; +} + +template +bool valid(const Type& halObject) { + const auto result = utils::validate(halObject); + if (!result.has_value()) { + LOG(ERROR) << result.error(); + } + return result.has_value(); +} + +template +decltype(nn::convert(std::declval())) validatedConvertToCanonical(const Type& halObject) { + auto canonical = NN_TRY(nn::convert(halObject)); + const auto version = NN_TRY(nn::validate(canonical)); + if (version > utils::kVersion) { + return NN_ERROR() << ""; + } + return canonical; +} + +} // namespace android::hardware::neuralnetworks::V1_3::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_H diff --git a/neuralnetworks/1.3/utils/src/Assertions.cpp b/neuralnetworks/1.3/utils/src/Assertions.cpp new file mode 100644 index 0000000000..96d647a7aa --- /dev/null +++ b/neuralnetworks/1.3/utils/src/Assertions.cpp @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +namespace { + +#define COMPARE_ENUMS_TYPES(lhsType, rhsType) \ + static_assert( \ + std::is_same_v< \ + std::underlying_type_t<::android::hardware::neuralnetworks::V1_3::lhsType>, \ + std::underlying_type_t<::android::nn::rhsType>>, \ + "::android::hardware::neuralnetworks::V1_3::" #lhsType \ + " does not have the same underlying type as ::android::nn::" #rhsType) + +COMPARE_ENUMS_TYPES(OperandType, OperandType); +COMPARE_ENUMS_TYPES(OperationType, OperationType); +COMPARE_ENUMS_TYPES(Priority, Priority); +COMPARE_ENUMS_TYPES(OperandLifeTime, Operand::LifeTime); +COMPARE_ENUMS_TYPES(ErrorStatus, ErrorStatus); + +#undef COMPARE_ENUMS_TYPES + +#define COMPARE_ENUMS_FULL(symbol, lhsType, rhsType) \ + static_assert( \ + static_cast< \ + std::underlying_type_t<::android::hardware::neuralnetworks::V1_3::lhsType>>( \ + ::android::hardware::neuralnetworks::V1_3::lhsType::symbol) == \ + static_cast>( \ + ::android::nn::rhsType::symbol), \ + "::android::hardware::neuralnetworks::V1_3::" #lhsType "::" #symbol \ + " does not match ::android::nn::" #rhsType "::" #symbol) + +#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, OperandType, OperandType) + +COMPARE_ENUMS(FLOAT32); +COMPARE_ENUMS(INT32); +COMPARE_ENUMS(UINT32); +COMPARE_ENUMS(TENSOR_FLOAT32); +COMPARE_ENUMS(TENSOR_INT32); +COMPARE_ENUMS(TENSOR_QUANT8_ASYMM); +COMPARE_ENUMS(BOOL); +COMPARE_ENUMS(TENSOR_QUANT16_SYMM); +COMPARE_ENUMS(TENSOR_FLOAT16); +COMPARE_ENUMS(TENSOR_BOOL8); +COMPARE_ENUMS(FLOAT16); +COMPARE_ENUMS(TENSOR_QUANT8_SYMM_PER_CHANNEL); +COMPARE_ENUMS(TENSOR_QUANT16_ASYMM); +COMPARE_ENUMS(TENSOR_QUANT8_SYMM); +COMPARE_ENUMS(TENSOR_QUANT8_ASYMM_SIGNED); +COMPARE_ENUMS(SUBGRAPH); +COMPARE_ENUMS(OEM); +COMPARE_ENUMS(TENSOR_OEM_BYTE); + +#undef COMPARE_ENUMS + +#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, OperationType, OperationType) + +COMPARE_ENUMS(ADD); +COMPARE_ENUMS(AVERAGE_POOL_2D); +COMPARE_ENUMS(CONCATENATION); +COMPARE_ENUMS(CONV_2D); +COMPARE_ENUMS(DEPTHWISE_CONV_2D); +COMPARE_ENUMS(DEPTH_TO_SPACE); +COMPARE_ENUMS(DEQUANTIZE); +COMPARE_ENUMS(EMBEDDING_LOOKUP); +COMPARE_ENUMS(FLOOR); +COMPARE_ENUMS(FULLY_CONNECTED); +COMPARE_ENUMS(HASHTABLE_LOOKUP); +COMPARE_ENUMS(L2_NORMALIZATION); +COMPARE_ENUMS(L2_POOL_2D); +COMPARE_ENUMS(LOCAL_RESPONSE_NORMALIZATION); +COMPARE_ENUMS(LOGISTIC); +COMPARE_ENUMS(LSH_PROJECTION); +COMPARE_ENUMS(LSTM); +COMPARE_ENUMS(MAX_POOL_2D); +COMPARE_ENUMS(MUL); +COMPARE_ENUMS(RELU); +COMPARE_ENUMS(RELU1); +COMPARE_ENUMS(RELU6); +COMPARE_ENUMS(RESHAPE); +COMPARE_ENUMS(RESIZE_BILINEAR); +COMPARE_ENUMS(RNN); +COMPARE_ENUMS(SOFTMAX); +COMPARE_ENUMS(SPACE_TO_DEPTH); +COMPARE_ENUMS(SVDF); +COMPARE_ENUMS(TANH); +COMPARE_ENUMS(BATCH_TO_SPACE_ND); +COMPARE_ENUMS(DIV); +COMPARE_ENUMS(MEAN); +COMPARE_ENUMS(PAD); +COMPARE_ENUMS(SPACE_TO_BATCH_ND); +COMPARE_ENUMS(SQUEEZE); +COMPARE_ENUMS(STRIDED_SLICE); +COMPARE_ENUMS(SUB); +COMPARE_ENUMS(TRANSPOSE); +COMPARE_ENUMS(ABS); +COMPARE_ENUMS(ARGMAX); +COMPARE_ENUMS(ARGMIN); +COMPARE_ENUMS(AXIS_ALIGNED_BBOX_TRANSFORM); +COMPARE_ENUMS(BIDIRECTIONAL_SEQUENCE_LSTM); +COMPARE_ENUMS(BIDIRECTIONAL_SEQUENCE_RNN); +COMPARE_ENUMS(BOX_WITH_NMS_LIMIT); +COMPARE_ENUMS(CAST); +COMPARE_ENUMS(CHANNEL_SHUFFLE); +COMPARE_ENUMS(DETECTION_POSTPROCESSING); +COMPARE_ENUMS(EQUAL); +COMPARE_ENUMS(EXP); +COMPARE_ENUMS(EXPAND_DIMS); +COMPARE_ENUMS(GATHER); +COMPARE_ENUMS(GENERATE_PROPOSALS); +COMPARE_ENUMS(GREATER); +COMPARE_ENUMS(GREATER_EQUAL); +COMPARE_ENUMS(GROUPED_CONV_2D); +COMPARE_ENUMS(HEATMAP_MAX_KEYPOINT); +COMPARE_ENUMS(INSTANCE_NORMALIZATION); +COMPARE_ENUMS(LESS); +COMPARE_ENUMS(LESS_EQUAL); +COMPARE_ENUMS(LOG); +COMPARE_ENUMS(LOGICAL_AND); +COMPARE_ENUMS(LOGICAL_NOT); +COMPARE_ENUMS(LOGICAL_OR); +COMPARE_ENUMS(LOG_SOFTMAX); +COMPARE_ENUMS(MAXIMUM); +COMPARE_ENUMS(MINIMUM); +COMPARE_ENUMS(NEG); +COMPARE_ENUMS(NOT_EQUAL); +COMPARE_ENUMS(PAD_V2); +COMPARE_ENUMS(POW); +COMPARE_ENUMS(PRELU); +COMPARE_ENUMS(QUANTIZE); +COMPARE_ENUMS(QUANTIZED_16BIT_LSTM); +COMPARE_ENUMS(RANDOM_MULTINOMIAL); +COMPARE_ENUMS(REDUCE_ALL); +COMPARE_ENUMS(REDUCE_ANY); +COMPARE_ENUMS(REDUCE_MAX); +COMPARE_ENUMS(REDUCE_MIN); +COMPARE_ENUMS(REDUCE_PROD); +COMPARE_ENUMS(REDUCE_SUM); +COMPARE_ENUMS(ROI_ALIGN); +COMPARE_ENUMS(ROI_POOLING); +COMPARE_ENUMS(RSQRT); +COMPARE_ENUMS(SELECT); +COMPARE_ENUMS(SIN); +COMPARE_ENUMS(SLICE); +COMPARE_ENUMS(SPLIT); +COMPARE_ENUMS(SQRT); +COMPARE_ENUMS(TILE); +COMPARE_ENUMS(TOPK_V2); +COMPARE_ENUMS(TRANSPOSE_CONV_2D); +COMPARE_ENUMS(UNIDIRECTIONAL_SEQUENCE_LSTM); +COMPARE_ENUMS(UNIDIRECTIONAL_SEQUENCE_RNN); +COMPARE_ENUMS(RESIZE_NEAREST_NEIGHBOR); +COMPARE_ENUMS(QUANTIZED_LSTM); +COMPARE_ENUMS(IF); +COMPARE_ENUMS(WHILE); +COMPARE_ENUMS(ELU); +COMPARE_ENUMS(HARD_SWISH); +COMPARE_ENUMS(FILL); +COMPARE_ENUMS(RANK); +COMPARE_ENUMS(OEM_OPERATION); + +#undef COMPARE_ENUMS + +#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, Priority, Priority) + +COMPARE_ENUMS(LOW); +COMPARE_ENUMS(MEDIUM); +COMPARE_ENUMS(HIGH); + +#undef COMPARE_ENUMS + +#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, OperandLifeTime, Operand::LifeTime) + +COMPARE_ENUMS(TEMPORARY_VARIABLE); +COMPARE_ENUMS(SUBGRAPH_INPUT); +COMPARE_ENUMS(SUBGRAPH_OUTPUT); +COMPARE_ENUMS(CONSTANT_COPY); +COMPARE_ENUMS(CONSTANT_REFERENCE); +COMPARE_ENUMS(NO_VALUE); +COMPARE_ENUMS(SUBGRAPH); + +#undef COMPARE_ENUMS + +#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, ErrorStatus, ErrorStatus) + +COMPARE_ENUMS(NONE); +COMPARE_ENUMS(DEVICE_UNAVAILABLE); +COMPARE_ENUMS(GENERAL_FAILURE); +COMPARE_ENUMS(OUTPUT_INSUFFICIENT_SIZE); +COMPARE_ENUMS(INVALID_ARGUMENT); +COMPARE_ENUMS(MISSED_DEADLINE_TRANSIENT); +COMPARE_ENUMS(MISSED_DEADLINE_PERSISTENT); +COMPARE_ENUMS(RESOURCE_EXHAUSTED_TRANSIENT); +COMPARE_ENUMS(RESOURCE_EXHAUSTED_PERSISTENT); + +#undef COMPARE_ENUMS + +#undef COMPARE_ENUMS_FULL + +} // anonymous namespace diff --git a/neuralnetworks/1.3/utils/src/Conversions.cpp b/neuralnetworks/1.3/utils/src/Conversions.cpp new file mode 100644 index 0000000000..4c54e3b12e --- /dev/null +++ b/neuralnetworks/1.3/utils/src/Conversions.cpp @@ -0,0 +1,552 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Conversions.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +template +constexpr std::underlying_type_t underlyingType(Type value) { + return static_cast>(value); +} + +} // namespace + +namespace android::nn { +namespace { + +constexpr auto validOperandType(nn::OperandType operandType) { + switch (operandType) { + case nn::OperandType::FLOAT32: + case nn::OperandType::INT32: + case nn::OperandType::UINT32: + case nn::OperandType::TENSOR_FLOAT32: + case nn::OperandType::TENSOR_INT32: + case nn::OperandType::TENSOR_QUANT8_ASYMM: + case nn::OperandType::BOOL: + case nn::OperandType::TENSOR_QUANT16_SYMM: + case nn::OperandType::TENSOR_FLOAT16: + case nn::OperandType::TENSOR_BOOL8: + case nn::OperandType::FLOAT16: + case nn::OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL: + case nn::OperandType::TENSOR_QUANT16_ASYMM: + case nn::OperandType::TENSOR_QUANT8_SYMM: + case nn::OperandType::TENSOR_QUANT8_ASYMM_SIGNED: + case nn::OperandType::SUBGRAPH: + case nn::OperandType::OEM: + case nn::OperandType::TENSOR_OEM_BYTE: + return true; + } + return nn::isExtension(operandType); +} + +using hardware::hidl_vec; + +template +using ConvertOutput = std::decay_t()).value())>; + +template +Result>> convertVec(const hidl_vec& arguments) { + std::vector> canonical; + canonical.reserve(arguments.size()); + for (const auto& argument : arguments) { + canonical.push_back(NN_TRY(nn::convert(argument))); + } + return canonical; +} + +template +Result>> convert(const hidl_vec& arguments) { + return convertVec(arguments); +} + +} // anonymous namespace + +Result convert(const hal::V1_3::OperandType& operandType) { + return static_cast(operandType); +} + +Result convert(const hal::V1_3::OperationType& operationType) { + return static_cast(operationType); +} + +Result convert(const hal::V1_3::Priority& priority) { + return static_cast(priority); +} + +Result convert(const hal::V1_3::Capabilities& capabilities) { + const bool validOperandTypes = std::all_of( + capabilities.operandPerformance.begin(), capabilities.operandPerformance.end(), + [](const hal::V1_3::Capabilities::OperandPerformance& operandPerformance) { + const auto maybeType = convert(operandPerformance.type); + return !maybeType.has_value() ? false : validOperandType(maybeType.value()); + }); + if (!validOperandTypes) { + return NN_ERROR() + << "Invalid OperandType when converting OperandPerformance in Capabilities"; + } + + auto operandPerformance = NN_TRY(convert(capabilities.operandPerformance)); + auto table = + NN_TRY(Capabilities::OperandPerformanceTable::create(std::move(operandPerformance))); + + return Capabilities{ + .relaxedFloat32toFloat16PerformanceScalar = + NN_TRY(convert(capabilities.relaxedFloat32toFloat16PerformanceScalar)), + .relaxedFloat32toFloat16PerformanceTensor = + NN_TRY(convert(capabilities.relaxedFloat32toFloat16PerformanceTensor)), + .operandPerformance = std::move(table), + .ifPerformance = NN_TRY(convert(capabilities.ifPerformance)), + .whilePerformance = NN_TRY(convert(capabilities.whilePerformance)), + }; +} + +Result convert( + const hal::V1_3::Capabilities::OperandPerformance& operandPerformance) { + return Capabilities::OperandPerformance{ + .type = NN_TRY(convert(operandPerformance.type)), + .info = NN_TRY(convert(operandPerformance.info)), + }; +} + +Result convert(const hal::V1_3::Operation& operation) { + return Operation{ + .type = NN_TRY(convert(operation.type)), + .inputs = operation.inputs, + .outputs = operation.outputs, + }; +} + +Result convert(const hal::V1_3::OperandLifeTime& operandLifeTime) { + return static_cast(operandLifeTime); +} + +Result convert(const hal::V1_3::Operand& operand) { + return Operand{ + .type = NN_TRY(convert(operand.type)), + .dimensions = operand.dimensions, + .scale = operand.scale, + .zeroPoint = operand.zeroPoint, + .lifetime = NN_TRY(convert(operand.lifetime)), + .location = NN_TRY(convert(operand.location)), + .extraParams = NN_TRY(convert(operand.extraParams)), + }; +} + +Result convert(const hal::V1_3::Model& model) { + return Model{ + .main = NN_TRY(convert(model.main)), + .referenced = NN_TRY(convert(model.referenced)), + .operandValues = NN_TRY(convert(model.operandValues)), + .pools = NN_TRY(convert(model.pools)), + .relaxComputationFloat32toFloat16 = model.relaxComputationFloat32toFloat16, + .extensionNameToPrefix = NN_TRY(convert(model.extensionNameToPrefix)), + }; +} + +Result convert(const hal::V1_3::Subgraph& subgraph) { + auto operations = NN_TRY(convert(subgraph.operations)); + + // Verify number of consumers. + const auto numberOfConsumers = + hal::utils::countNumberOfConsumers(subgraph.operands.size(), operations); + CHECK(subgraph.operands.size() == numberOfConsumers.size()); + for (size_t i = 0; i < subgraph.operands.size(); ++i) { + if (subgraph.operands[i].numberOfConsumers != numberOfConsumers[i]) { + return NN_ERROR() << "Invalid numberOfConsumers for operand " << i << ", expected " + << numberOfConsumers[i] << " but found " + << subgraph.operands[i].numberOfConsumers; + } + } + + return Model::Subgraph{ + .operands = NN_TRY(convert(subgraph.operands)), + .operations = std::move(operations), + .inputIndexes = subgraph.inputIndexes, + .outputIndexes = subgraph.outputIndexes, + }; +} + +Result convert(const hal::V1_3::BufferDesc& bufferDesc) { + return BufferDesc{.dimensions = bufferDesc.dimensions}; +} + +Result convert(const hal::V1_3::BufferRole& bufferRole) { + return BufferRole{ + .modelIndex = bufferRole.modelIndex, + .ioIndex = bufferRole.ioIndex, + .frequency = bufferRole.frequency, + }; +} + +Result convert(const hal::V1_3::Request& request) { + return Request{ + .inputs = NN_TRY(convert(request.inputs)), + .outputs = NN_TRY(convert(request.outputs)), + .pools = NN_TRY(convert(request.pools)), + }; +} + +Result convert(const hal::V1_3::Request::MemoryPool& memoryPool) { + using Discriminator = hal::V1_3::Request::MemoryPool::hidl_discriminator; + switch (memoryPool.getDiscriminator()) { + case Discriminator::hidlMemory: + return createSharedMemoryFromHidlMemory(memoryPool.hidlMemory()); + case Discriminator::token: + return static_cast(memoryPool.token()); + } + return NN_ERROR() << "Invalid Request::MemoryPool discriminator " + << underlyingType(memoryPool.getDiscriminator()); +} + +Result convert(const hal::V1_3::OptionalTimePoint& optionalTimePoint) { + constexpr auto kTimePointMaxCount = TimePoint::max().time_since_epoch().count(); + const auto makeTimePoint = [](uint64_t count) -> Result { + if (count > kTimePointMaxCount) { + return NN_ERROR() + << "Unable to convert OptionalTimePoint because the count exceeds the max"; + } + const auto nanoseconds = std::chrono::nanoseconds{count}; + return TimePoint{nanoseconds}; + }; + + using Discriminator = hal::V1_3::OptionalTimePoint::hidl_discriminator; + switch (optionalTimePoint.getDiscriminator()) { + case Discriminator::none: + return std::nullopt; + case Discriminator::nanosecondsSinceEpoch: + return makeTimePoint(optionalTimePoint.nanosecondsSinceEpoch()); + } + return NN_ERROR() << "Invalid OptionalTimePoint discriminator " + << underlyingType(optionalTimePoint.getDiscriminator()); +} + +Result convert( + const hal::V1_3::OptionalTimeoutDuration& optionalTimeoutDuration) { + constexpr auto kTimeoutDurationMaxCount = TimeoutDuration::max().count(); + const auto makeTimeoutDuration = [](uint64_t count) -> Result { + if (count > kTimeoutDurationMaxCount) { + return NN_ERROR() + << "Unable to convert OptionalTimeoutDuration because the count exceeds the max"; + } + return TimeoutDuration{count}; + }; + + using Discriminator = hal::V1_3::OptionalTimeoutDuration::hidl_discriminator; + switch (optionalTimeoutDuration.getDiscriminator()) { + case Discriminator::none: + return std::nullopt; + case Discriminator::nanoseconds: + return makeTimeoutDuration(optionalTimeoutDuration.nanoseconds()); + } + return NN_ERROR() << "Invalid OptionalTimeoutDuration discriminator " + << underlyingType(optionalTimeoutDuration.getDiscriminator()); +} + +Result convert(const hal::V1_3::ErrorStatus& status) { + switch (status) { + case hal::V1_3::ErrorStatus::NONE: + case hal::V1_3::ErrorStatus::DEVICE_UNAVAILABLE: + case hal::V1_3::ErrorStatus::GENERAL_FAILURE: + case hal::V1_3::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE: + case hal::V1_3::ErrorStatus::INVALID_ARGUMENT: + case hal::V1_3::ErrorStatus::MISSED_DEADLINE_TRANSIENT: + case hal::V1_3::ErrorStatus::MISSED_DEADLINE_PERSISTENT: + case hal::V1_3::ErrorStatus::RESOURCE_EXHAUSTED_TRANSIENT: + case hal::V1_3::ErrorStatus::RESOURCE_EXHAUSTED_PERSISTENT: + return static_cast(status); + } + return NN_ERROR() << "Invalid ErrorStatus " << underlyingType(status); +} + +Result> convert( + const hardware::hidl_vec& bufferRoles) { + return convertVec(bufferRoles); +} + +} // namespace android::nn + +namespace android::hardware::neuralnetworks::V1_3::utils { +namespace { + +using utils::convert; + +nn::Result convert( + const nn::Capabilities::PerformanceInfo& performanceInfo) { + return V1_0::utils::convert(performanceInfo); +} + +nn::Result convert(const nn::DataLocation& dataLocation) { + return V1_0::utils::convert(dataLocation); +} + +nn::Result> convert(const nn::Model::OperandValues& operandValues) { + return V1_0::utils::convert(operandValues); +} + +nn::Result convert(const nn::Memory& memory) { + return V1_0::utils::convert(memory); +} + +nn::Result convert(const nn::Request::Argument& argument) { + return V1_0::utils::convert(argument); +} + +nn::Result convert(const nn::Operand::ExtraParams& extraParams) { + return V1_2::utils::convert(extraParams); +} + +nn::Result convert( + const nn::Model::ExtensionNameAndPrefix& extensionNameAndPrefix) { + return V1_2::utils::convert(extensionNameAndPrefix); +} + +template +using ConvertOutput = std::decay_t()).value())>; + +template +nn::Result>> convertVec(const std::vector& arguments) { + hidl_vec> halObject(arguments.size()); + for (size_t i = 0; i < arguments.size(); ++i) { + halObject[i] = NN_TRY(convert(arguments[i])); + } + return halObject; +} + +template +nn::Result>> convert(const std::vector& arguments) { + return convertVec(arguments); +} + +nn::Result makeMemoryPool(const nn::Memory& memory) { + Request::MemoryPool ret; + ret.hidlMemory(NN_TRY(convert(memory))); + return ret; +} + +nn::Result makeMemoryPool(const nn::Request::MemoryDomainToken& token) { + Request::MemoryPool ret; + ret.token(underlyingType(token)); + return ret; +} + +nn::Result makeMemoryPool( + const std::shared_ptr& /*buffer*/) { + return NN_ERROR() << "Unable to make memory pool from IBuffer"; +} + +} // anonymous namespace + +nn::Result convert(const nn::OperandType& operandType) { + return static_cast(operandType); +} + +nn::Result convert(const nn::OperationType& operationType) { + return static_cast(operationType); +} + +nn::Result convert(const nn::Priority& priority) { + return static_cast(priority); +} + +nn::Result convert(const nn::Capabilities& capabilities) { + std::vector operandPerformance; + operandPerformance.reserve(capabilities.operandPerformance.asVector().size()); + std::copy_if(capabilities.operandPerformance.asVector().begin(), + capabilities.operandPerformance.asVector().end(), + std::back_inserter(operandPerformance), + [](const nn::Capabilities::OperandPerformance& operandPerformance) { + return nn::validOperandType(operandPerformance.type); + }); + + return Capabilities{ + .relaxedFloat32toFloat16PerformanceScalar = + NN_TRY(convert(capabilities.relaxedFloat32toFloat16PerformanceScalar)), + .relaxedFloat32toFloat16PerformanceTensor = + NN_TRY(convert(capabilities.relaxedFloat32toFloat16PerformanceTensor)), + .operandPerformance = NN_TRY(convert(operandPerformance)), + .ifPerformance = NN_TRY(convert(capabilities.ifPerformance)), + .whilePerformance = NN_TRY(convert(capabilities.whilePerformance)), + }; +} + +nn::Result convert( + const nn::Capabilities::OperandPerformance& operandPerformance) { + return Capabilities::OperandPerformance{ + .type = NN_TRY(convert(operandPerformance.type)), + .info = NN_TRY(convert(operandPerformance.info)), + }; +} + +nn::Result convert(const nn::Operation& operation) { + return Operation{ + .type = NN_TRY(convert(operation.type)), + .inputs = operation.inputs, + .outputs = operation.outputs, + }; +} + +nn::Result convert(const nn::Operand::LifeTime& operandLifeTime) { + if (operandLifeTime == nn::Operand::LifeTime::POINTER) { + return NN_ERROR() << "Model cannot be converted because it contains pointer-based memory"; + } + return static_cast(operandLifeTime); +} + +nn::Result convert(const nn::Operand& operand) { + return Operand{ + .type = NN_TRY(convert(operand.type)), + .dimensions = operand.dimensions, + .numberOfConsumers = 0, + .scale = operand.scale, + .zeroPoint = operand.zeroPoint, + .lifetime = NN_TRY(convert(operand.lifetime)), + .location = NN_TRY(convert(operand.location)), + .extraParams = NN_TRY(convert(operand.extraParams)), + }; +} + +nn::Result convert(const nn::Model& model) { + if (!hal::utils::hasNoPointerData(model)) { + return NN_ERROR() << "Model cannot be converted because it contains pointer-based memory"; + } + + return Model{ + .main = NN_TRY(convert(model.main)), + .referenced = NN_TRY(convert(model.referenced)), + .operandValues = NN_TRY(convert(model.operandValues)), + .pools = NN_TRY(convert(model.pools)), + .relaxComputationFloat32toFloat16 = model.relaxComputationFloat32toFloat16, + .extensionNameToPrefix = NN_TRY(convert(model.extensionNameToPrefix)), + }; +} + +nn::Result convert(const nn::Model::Subgraph& subgraph) { + auto operands = NN_TRY(convert(subgraph.operands)); + + // Update number of consumers. + const auto numberOfConsumers = + hal::utils::countNumberOfConsumers(operands.size(), subgraph.operations); + CHECK(operands.size() == numberOfConsumers.size()); + for (size_t i = 0; i < operands.size(); ++i) { + operands[i].numberOfConsumers = numberOfConsumers[i]; + } + + return Subgraph{ + .operands = std::move(operands), + .operations = NN_TRY(convert(subgraph.operations)), + .inputIndexes = subgraph.inputIndexes, + .outputIndexes = subgraph.outputIndexes, + }; +} + +nn::Result convert(const nn::BufferDesc& bufferDesc) { + return BufferDesc{.dimensions = bufferDesc.dimensions}; +} + +nn::Result convert(const nn::BufferRole& bufferRole) { + return BufferRole{ + .modelIndex = bufferRole.modelIndex, + .ioIndex = bufferRole.ioIndex, + .frequency = bufferRole.frequency, + }; +} + +nn::Result convert(const nn::Request& request) { + if (!hal::utils::hasNoPointerData(request)) { + return NN_ERROR() << "Request cannot be converted because it contains pointer-based memory"; + } + + return Request{ + .inputs = NN_TRY(convert(request.inputs)), + .outputs = NN_TRY(convert(request.outputs)), + .pools = NN_TRY(convert(request.pools)), + }; +} + +nn::Result convert(const nn::Request::MemoryPool& memoryPool) { + return std::visit([](const auto& o) { return makeMemoryPool(o); }, memoryPool); +} + +nn::Result convert(const nn::OptionalTimePoint& optionalTimePoint) { + OptionalTimePoint ret; + if (optionalTimePoint.has_value()) { + const auto count = optionalTimePoint.value().time_since_epoch().count(); + if (count < 0) { + return NN_ERROR() << "Unable to convert OptionalTimePoint because time since epoch " + "count is negative"; + } + ret.nanosecondsSinceEpoch(count); + } + return ret; +} + +nn::Result convert( + const nn::OptionalTimeoutDuration& optionalTimeoutDuration) { + OptionalTimeoutDuration ret; + if (optionalTimeoutDuration.has_value()) { + const auto count = optionalTimeoutDuration.value().count(); + if (count < 0) { + return NN_ERROR() + << "Unable to convert OptionalTimeoutDuration because count is negative"; + } + ret.nanoseconds(count); + } + return ret; +} + +nn::Result convert(const nn::ErrorStatus& errorStatus) { + switch (errorStatus) { + case nn::ErrorStatus::NONE: + case nn::ErrorStatus::DEVICE_UNAVAILABLE: + case nn::ErrorStatus::GENERAL_FAILURE: + case nn::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE: + case nn::ErrorStatus::INVALID_ARGUMENT: + case nn::ErrorStatus::MISSED_DEADLINE_TRANSIENT: + case nn::ErrorStatus::MISSED_DEADLINE_PERSISTENT: + case nn::ErrorStatus::RESOURCE_EXHAUSTED_TRANSIENT: + case nn::ErrorStatus::RESOURCE_EXHAUSTED_PERSISTENT: + return static_cast(errorStatus); + default: + return ErrorStatus::GENERAL_FAILURE; + } +} + +nn::Result> convert(const std::vector& bufferRoles) { + return convertVec(bufferRoles); +} + +} // namespace android::hardware::neuralnetworks::V1_3::utils diff --git a/neuralnetworks/utils/OWNERS b/neuralnetworks/utils/OWNERS new file mode 100644 index 0000000000..e4feee3496 --- /dev/null +++ b/neuralnetworks/utils/OWNERS @@ -0,0 +1,11 @@ +# Neuralnetworks team +butlermichael@google.com +dgross@google.com +galarragas@google.com +jeanluc@google.com +levp@google.com +miaowang@google.com +pszczepaniak@google.com +slavash@google.com +vddang@google.com +xusongw@google.com diff --git a/neuralnetworks/utils/common/Android.bp b/neuralnetworks/utils/common/Android.bp new file mode 100644 index 0000000000..b61dc970ed --- /dev/null +++ b/neuralnetworks/utils/common/Android.bp @@ -0,0 +1,29 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +cc_library_static { + name: "neuralnetworks_utils_hal_common", + defaults: ["neuralnetworks_utils_defaults"], + srcs: ["src/*"], + local_include_dirs: ["include/nnapi/hal"], + export_include_dirs: ["include"], + static_libs: [ + "neuralnetworks_types", + ], + shared_libs: [ + "libhidlbase", + ], +} diff --git a/neuralnetworks/utils/common/include/nnapi/hal/CommonUtils.h b/neuralnetworks/utils/common/include/nnapi/hal/CommonUtils.h new file mode 100644 index 0000000000..8c013682ce --- /dev/null +++ b/neuralnetworks/utils/common/include/nnapi/hal/CommonUtils.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_COMMON_UTILS_H +#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_COMMON_UTILS_H + +#include +#include +#include + +// Shorthand +namespace android::hardware::neuralnetworks { +namespace hal = ::android::hardware::neuralnetworks; +} // namespace android::hardware::neuralnetworks + +// Shorthand +namespace android::nn { +namespace hal = ::android::hardware::neuralnetworks; +} + +namespace android::hardware::neuralnetworks::utils { + +nn::Capabilities::OperandPerformanceTable makeQuantized8PerformanceConsistentWithP( + const nn::Capabilities::PerformanceInfo& float32Performance, + const nn::Capabilities::PerformanceInfo& quantized8Performance); + +// Indicates if the object contains no pointer-based data that could be relocated to shared memory. +bool hasNoPointerData(const nn::Model& model); +bool hasNoPointerData(const nn::Request& request); + +// Relocate pointer-based data to shared memory. +nn::Result flushDataFromPointerToShared(const nn::Model& model); +nn::Result flushDataFromPointerToShared(const nn::Request& request); + +// Undoes `flushDataFromPointerToShared` on a Request object. More specifically, +// `unflushDataFromSharedToPointer` copies the output shared memory data from the transformed +// Request object back to the output pointer-based memory in the original Request object. +nn::Result unflushDataFromSharedToPointer(const nn::Request& request, + const nn::Request& requestInShared); + +std::vector countNumberOfConsumers(size_t numberOfOperands, + const std::vector& operations); + +} // namespace android::hardware::neuralnetworks::utils + +#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_COMMON_UTILS_H diff --git a/neuralnetworks/utils/common/src/CommonUtils.cpp b/neuralnetworks/utils/common/src/CommonUtils.cpp new file mode 100644 index 0000000000..667189b2a0 --- /dev/null +++ b/neuralnetworks/utils/common/src/CommonUtils.cpp @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "CommonUtils.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace android::hardware::neuralnetworks::utils { +namespace { + +bool hasNoPointerData(const nn::Operand& operand); +bool hasNoPointerData(const nn::Model::Subgraph& subgraph); +bool hasNoPointerData(const nn::Request::Argument& argument); + +template +bool hasNoPointerData(const std::vector& objects) { + return std::all_of(objects.begin(), objects.end(), + [](const auto& object) { return hasNoPointerData(object); }); +} + +bool hasNoPointerData(const nn::DataLocation& location) { + return std::visit([](auto ptr) { return ptr == nullptr; }, location.pointer); +} + +bool hasNoPointerData(const nn::Operand& operand) { + return hasNoPointerData(operand.location); +} + +bool hasNoPointerData(const nn::Model::Subgraph& subgraph) { + return hasNoPointerData(subgraph.operands); +} + +bool hasNoPointerData(const nn::Request::Argument& argument) { + return hasNoPointerData(argument.location); +} + +void copyPointersToSharedMemory(nn::Operand* operand, nn::ConstantMemoryBuilder* memoryBuilder) { + CHECK(operand != nullptr); + CHECK(memoryBuilder != nullptr); + + if (operand->lifetime != nn::Operand::LifeTime::POINTER) { + return; + } + + const void* data = std::visit([](auto ptr) { return static_cast(ptr); }, + operand->location.pointer); + CHECK(data != nullptr); + operand->lifetime = nn::Operand::LifeTime::CONSTANT_REFERENCE; + operand->location = memoryBuilder->append(data, operand->location.length); +} + +void copyPointersToSharedMemory(nn::Model::Subgraph* subgraph, + nn::ConstantMemoryBuilder* memoryBuilder) { + CHECK(subgraph != nullptr); + std::for_each(subgraph->operands.begin(), subgraph->operands.end(), + [memoryBuilder](auto& operand) { + copyPointersToSharedMemory(&operand, memoryBuilder); + }); +} + +} // anonymous namespace + +nn::Capabilities::OperandPerformanceTable makeQuantized8PerformanceConsistentWithP( + const nn::Capabilities::PerformanceInfo& float32Performance, + const nn::Capabilities::PerformanceInfo& quantized8Performance) { + // In Android P, most data types are treated as having the same performance as + // TENSOR_QUANT8_ASYMM. This collection must be in sorted order. + std::vector operandPerformances = { + {.type = nn::OperandType::FLOAT32, .info = float32Performance}, + {.type = nn::OperandType::INT32, .info = quantized8Performance}, + {.type = nn::OperandType::UINT32, .info = quantized8Performance}, + {.type = nn::OperandType::TENSOR_FLOAT32, .info = float32Performance}, + {.type = nn::OperandType::TENSOR_INT32, .info = quantized8Performance}, + {.type = nn::OperandType::TENSOR_QUANT8_ASYMM, .info = quantized8Performance}, + {.type = nn::OperandType::OEM, .info = quantized8Performance}, + {.type = nn::OperandType::TENSOR_OEM_BYTE, .info = quantized8Performance}, + }; + return nn::Capabilities::OperandPerformanceTable::create(std::move(operandPerformances)) + .value(); +} + +bool hasNoPointerData(const nn::Model& model) { + return hasNoPointerData(model.main) && hasNoPointerData(model.referenced); +} + +bool hasNoPointerData(const nn::Request& request) { + return hasNoPointerData(request.inputs) && hasNoPointerData(request.outputs); +} + +nn::Result flushDataFromPointerToShared(const nn::Model& model) { + auto modelInShared = model; + + nn::ConstantMemoryBuilder memoryBuilder(modelInShared.pools.size()); + copyPointersToSharedMemory(&modelInShared.main, &memoryBuilder); + std::for_each(modelInShared.referenced.begin(), modelInShared.referenced.end(), + [&memoryBuilder](auto& subgraph) { + copyPointersToSharedMemory(&subgraph, &memoryBuilder); + }); + + if (!memoryBuilder.empty()) { + auto memory = NN_TRY(memoryBuilder.finish()); + modelInShared.pools.push_back(std::move(memory)); + } + + return modelInShared; +} + +nn::Result flushDataFromPointerToShared(const nn::Request& request) { + auto requestInShared = request; + + // Change input pointers to shared memory. + nn::ConstantMemoryBuilder inputBuilder(requestInShared.pools.size()); + for (auto& input : requestInShared.inputs) { + const auto& location = input.location; + if (input.lifetime != nn::Request::Argument::LifeTime::POINTER) { + continue; + } + + input.lifetime = nn::Request::Argument::LifeTime::POOL; + const void* data = std::visit([](auto ptr) { return static_cast(ptr); }, + location.pointer); + CHECK(data != nullptr); + input.location = inputBuilder.append(data, location.length); + } + + // Allocate input memory. + if (!inputBuilder.empty()) { + auto memory = NN_TRY(inputBuilder.finish()); + requestInShared.pools.push_back(std::move(memory)); + } + + // Change output pointers to shared memory. + nn::MutableMemoryBuilder outputBuilder(requestInShared.pools.size()); + for (auto& output : requestInShared.outputs) { + const auto& location = output.location; + if (output.lifetime != nn::Request::Argument::LifeTime::POINTER) { + continue; + } + + output.lifetime = nn::Request::Argument::LifeTime::POOL; + output.location = outputBuilder.append(location.length); + } + + // Allocate output memory. + if (!outputBuilder.empty()) { + auto memory = NN_TRY(outputBuilder.finish()); + requestInShared.pools.push_back(std::move(memory)); + } + + return requestInShared; +} + +nn::Result unflushDataFromSharedToPointer(const nn::Request& request, + const nn::Request& requestInShared) { + if (requestInShared.pools.empty() || + !std::holds_alternative(requestInShared.pools.back())) { + return {}; + } + + // Map the memory. + const auto& outputMemory = std::get(requestInShared.pools.back()); + const auto [pointer, size, context] = NN_TRY(map(outputMemory)); + const uint8_t* constantPointer = + std::visit([](const auto& o) { return static_cast(o); }, pointer); + + // Flush each output pointer. + CHECK_EQ(request.outputs.size(), requestInShared.outputs.size()); + for (size_t i = 0; i < request.outputs.size(); ++i) { + const auto& location = request.outputs[i].location; + const auto& locationInShared = requestInShared.outputs[i].location; + if (!std::holds_alternative(location.pointer)) { + continue; + } + + // Get output pointer and size. + void* data = std::get(location.pointer); + CHECK(data != nullptr); + const size_t length = location.length; + + // Get output pool location. + CHECK(requestInShared.outputs[i].lifetime == nn::Request::Argument::LifeTime::POOL); + const size_t index = locationInShared.poolIndex; + const size_t offset = locationInShared.offset; + const size_t outputPoolIndex = requestInShared.pools.size() - 1; + CHECK(locationInShared.length == length); + CHECK(index == outputPoolIndex); + + // Flush memory. + std::memcpy(data, constantPointer + offset, length); + } + + return {}; +} + +std::vector countNumberOfConsumers(size_t numberOfOperands, + const std::vector& operations) { + return nn::countNumberOfConsumers(numberOfOperands, operations); +} + +} // namespace android::hardware::neuralnetworks::utils