| /* | |
| * 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. | |
| */ | |
| // Provides C++ classes to more easily use the Neural Networks API. | |
| // TODO(b/117845862): this should be auto generated from NeuralNetworksWrapper.h. | |
| #ifndef ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_TEST_SUPPORT_LIBRARY_TEST_WRAPPER_H | |
| #define ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_TEST_SUPPORT_LIBRARY_TEST_WRAPPER_H | |
| #include <math.h> | |
| #include <algorithm> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "NeuralNetworksWrapper.h" | |
| #include "SupportLibrary.h" | |
| using namespace ::android::nn::wrapper; | |
| namespace android { | |
| namespace nn { | |
| namespace test_wrapper { | |
| class Memory { | |
| public: | |
| // Takes ownership of a ANeuralNetworksMemory | |
| Memory(const NnApiSupportLibrary* nnapi, ANeuralNetworksMemory* memory) | |
| : mNnApi(nnapi), mMemory(memory) {} | |
| Memory(const NnApiSupportLibrary* nnapi, size_t size, int protect, int fd, size_t offset) | |
| : mNnApi(nnapi) { | |
| mValid = mNnApi->ANeuralNetworksMemory_createFromFd(size, protect, fd, offset, &mMemory) == | |
| ANEURALNETWORKS_NO_ERROR; | |
| } | |
| Memory(const NnApiSupportLibrary* nnapi, AHardwareBuffer* buffer) : mNnApi(nnapi) { | |
| mValid = mNnApi->ANeuralNetworksMemory_createFromAHardwareBuffer(buffer, &mMemory) == | |
| ANEURALNETWORKS_NO_ERROR; | |
| } | |
| virtual ~Memory() { mNnApi->ANeuralNetworksMemory_free(mMemory); } | |
| // Disallow copy semantics to ensure the runtime object can only be freed | |
| // once. Copy semantics could be enabled if some sort of reference counting | |
| // or deep-copy system for runtime objects is added later. | |
| Memory(const Memory&) = delete; | |
| Memory& operator=(const Memory&) = delete; | |
| // Move semantics to remove access to the runtime object from the wrapper | |
| // object that is being moved. This ensures the runtime object will be | |
| // freed only once. | |
| Memory(Memory&& other) { *this = std::move(other); } | |
| Memory& operator=(Memory&& other) { | |
| if (this != &other) { | |
| mNnApi->ANeuralNetworksMemory_free(mMemory); | |
| mMemory = other.mMemory; | |
| mValid = other.mValid; | |
| other.mMemory = nullptr; | |
| other.mValid = false; | |
| } | |
| return *this; | |
| } | |
| ANeuralNetworksMemory* get() const { return mMemory; } | |
| bool isValid() const { return mValid; } | |
| private: | |
| const NnApiSupportLibrary* mNnApi = nullptr; | |
| ANeuralNetworksMemory* mMemory = nullptr; | |
| bool mValid = true; | |
| }; | |
| class Model { | |
| public: | |
| Model(const NnApiSupportLibrary* nnapi) : mNnApi(nnapi) { | |
| mValid = mNnApi->ANeuralNetworksModel_create(&mModel) == ANEURALNETWORKS_NO_ERROR; | |
| } | |
| ~Model() { mNnApi->ANeuralNetworksModel_free(mModel); } | |
| // Disallow copy semantics to ensure the runtime object can only be freed | |
| // once. Copy semantics could be enabled if some sort of reference counting | |
| // or deep-copy system for runtime objects is added later. | |
| Model(const Model&) = delete; | |
| Model& operator=(const Model&) = delete; | |
| // Move semantics to remove access to the runtime object from the wrapper | |
| // object that is being moved. This ensures the runtime object will be | |
| // freed only once. | |
| Model(Model&& other) { *this = std::move(other); } | |
| Model& operator=(Model&& other) { | |
| if (this != &other) { | |
| if (mModel != nullptr) { | |
| mNnApi->ANeuralNetworksModel_free(mModel); | |
| } | |
| mNnApi = other.mNnApi; | |
| mModel = other.mModel; | |
| mNextOperandId = other.mNextOperandId; | |
| mValid = other.mValid; | |
| mRelaxed = other.mRelaxed; | |
| mFinished = other.mFinished; | |
| other.mModel = nullptr; | |
| other.mNextOperandId = 0; | |
| other.mValid = false; | |
| other.mRelaxed = false; | |
| other.mFinished = false; | |
| } | |
| return *this; | |
| } | |
| Result finish() { | |
| if (mValid) { | |
| auto result = static_cast<Result>(mNnApi->ANeuralNetworksModel_finish(mModel)); | |
| if (result != Result::NO_ERROR) { | |
| mValid = false; | |
| } | |
| mFinished = true; | |
| return result; | |
| } else { | |
| return Result::BAD_STATE; | |
| } | |
| } | |
| uint32_t addOperand(const OperandType* type) { | |
| if (mNnApi->ANeuralNetworksModel_addOperand(mModel, &type->operandType) != | |
| ANEURALNETWORKS_NO_ERROR) { | |
| mValid = false; | |
| } | |
| if (type->channelQuant) { | |
| if (mNnApi->ANeuralNetworksModel_setOperandSymmPerChannelQuantParams( | |
| mModel, mNextOperandId, &type->channelQuant.value().params) != | |
| ANEURALNETWORKS_NO_ERROR) { | |
| mValid = false; | |
| } | |
| } | |
| return mNextOperandId++; | |
| } | |
| template <typename T> | |
| uint32_t addConstantOperand(const OperandType* type, const T& value) { | |
| static_assert(sizeof(T) <= ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES, | |
| "Values larger than ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES " | |
| "not supported"); | |
| uint32_t index = addOperand(type); | |
| setOperandValue(index, &value); | |
| return index; | |
| } | |
| uint32_t addModelOperand(const Model* value) { | |
| OperandType operandType(Type::MODEL, {}); | |
| uint32_t operand = addOperand(&operandType); | |
| setOperandValueFromModel(operand, value); | |
| return operand; | |
| } | |
| void setOperandValue(uint32_t index, const void* buffer, size_t length) { | |
| if (mNnApi->ANeuralNetworksModel_setOperandValue(mModel, index, buffer, length) != | |
| ANEURALNETWORKS_NO_ERROR) { | |
| mValid = false; | |
| } | |
| } | |
| template <typename T> | |
| void setOperandValue(uint32_t index, const T* value) { | |
| static_assert(!std::is_pointer<T>(), "No operand may have a pointer as its value"); | |
| return setOperandValue(index, value, sizeof(T)); | |
| } | |
| void setOperandValueFromMemory(uint32_t index, const Memory* memory, uint32_t offset, | |
| size_t length) { | |
| if (mNnApi->ANeuralNetworksModel_setOperandValueFromMemory( | |
| mModel, index, memory->get(), offset, length) != ANEURALNETWORKS_NO_ERROR) { | |
| mValid = false; | |
| } | |
| } | |
| void setOperandValueFromModel(uint32_t index, const Model* value) { | |
| if (mNnApi->ANeuralNetworksModel_setOperandValueFromModel(mModel, index, value->mModel) != | |
| ANEURALNETWORKS_NO_ERROR) { | |
| mValid = false; | |
| } | |
| } | |
| void addOperation(ANeuralNetworksOperationType type, const std::vector<uint32_t>& inputs, | |
| const std::vector<uint32_t>& outputs) { | |
| if (mNnApi->ANeuralNetworksModel_addOperation( | |
| mModel, type, static_cast<uint32_t>(inputs.size()), inputs.data(), | |
| static_cast<uint32_t>(outputs.size()), | |
| outputs.data()) != ANEURALNETWORKS_NO_ERROR) { | |
| mValid = false; | |
| } | |
| } | |
| void identifyInputsAndOutputs(const std::vector<uint32_t>& inputs, | |
| const std::vector<uint32_t>& outputs) { | |
| if (mNnApi->ANeuralNetworksModel_identifyInputsAndOutputs( | |
| mModel, static_cast<uint32_t>(inputs.size()), inputs.data(), | |
| static_cast<uint32_t>(outputs.size()), | |
| outputs.data()) != ANEURALNETWORKS_NO_ERROR) { | |
| mValid = false; | |
| } | |
| } | |
| void relaxComputationFloat32toFloat16(bool isRelax) { | |
| if (mNnApi->ANeuralNetworksModel_relaxComputationFloat32toFloat16(mModel, isRelax) == | |
| ANEURALNETWORKS_NO_ERROR) { | |
| mRelaxed = isRelax; | |
| } | |
| } | |
| ANeuralNetworksModel* getHandle() const { return mModel; } | |
| bool isValid() const { return mValid; } | |
| bool isRelaxed() const { return mRelaxed; } | |
| bool isFinished() const { return mFinished; } | |
| protected: | |
| const NnApiSupportLibrary* mNnApi = nullptr; | |
| ANeuralNetworksModel* mModel = nullptr; | |
| // We keep track of the operand ID as a convenience to the caller. | |
| uint32_t mNextOperandId = 0; | |
| bool mValid = true; | |
| bool mRelaxed = false; | |
| bool mFinished = false; | |
| }; | |
| class Compilation { | |
| public: | |
| // On success, createForDevice(s) will return Result::NO_ERROR and the created compilation; | |
| // otherwise, it will return the error code and Compilation object wrapping a nullptr handle. | |
| static std::pair<Result, Compilation> createForDevice(const NnApiSupportLibrary* nnapi, | |
| const Model* model, | |
| const ANeuralNetworksDevice* device) { | |
| return createForDevices(nnapi, model, {device}); | |
| } | |
| static std::pair<Result, Compilation> createForDevices( | |
| const NnApiSupportLibrary* nnapi, const Model* model, | |
| const std::vector<const ANeuralNetworksDevice*>& devices) { | |
| ANeuralNetworksCompilation* compilation = nullptr; | |
| const Result result = | |
| static_cast<Result>(nnapi->ANeuralNetworksCompilation_createForDevices( | |
| model->getHandle(), devices.empty() ? nullptr : devices.data(), | |
| devices.size(), &compilation)); | |
| return {result, Compilation(nnapi, compilation)}; | |
| } | |
| ~Compilation() { mNnApi->ANeuralNetworksCompilation_free(mCompilation); } | |
| // Disallow copy semantics to ensure the runtime object can only be freed | |
| // once. Copy semantics could be enabled if some sort of reference counting | |
| // or deep-copy system for runtime objects is added later. | |
| Compilation(const Compilation&) = delete; | |
| Compilation& operator=(const Compilation&) = delete; | |
| // Move semantics to remove access to the runtime object from the wrapper | |
| // object that is being moved. This ensures the runtime object will be | |
| // freed only once. | |
| Compilation(Compilation&& other) { *this = std::move(other); } | |
| Compilation& operator=(Compilation&& other) { | |
| if (this != &other) { | |
| mNnApi = other.mNnApi; | |
| mNnApi->ANeuralNetworksCompilation_free(mCompilation); | |
| mCompilation = other.mCompilation; | |
| other.mCompilation = nullptr; | |
| } | |
| return *this; | |
| } | |
| Result setPreference(ExecutePreference preference) { | |
| return static_cast<Result>(mNnApi->ANeuralNetworksCompilation_setPreference( | |
| mCompilation, static_cast<int32_t>(preference))); | |
| } | |
| Result setPriority(ExecutePriority priority) { | |
| return static_cast<Result>(mNnApi->ANeuralNetworksCompilation_setPriority( | |
| mCompilation, static_cast<int32_t>(priority))); | |
| } | |
| Result setCaching(const std::string& cacheDir, const std::vector<uint8_t>& token) { | |
| if (token.size() != ANEURALNETWORKS_BYTE_SIZE_OF_CACHE_TOKEN) { | |
| return Result::BAD_DATA; | |
| } | |
| return static_cast<Result>(mNnApi->ANeuralNetworksCompilation_setCaching( | |
| mCompilation, cacheDir.c_str(), token.data())); | |
| } | |
| Result finish() { | |
| return static_cast<Result>(mNnApi->ANeuralNetworksCompilation_finish(mCompilation)); | |
| } | |
| ANeuralNetworksCompilation* getHandle() const { return mCompilation; } | |
| protected: | |
| // Takes the ownership of ANeuralNetworksCompilation. | |
| Compilation(const NnApiSupportLibrary* nnapi, ANeuralNetworksCompilation* compilation) | |
| : mNnApi(nnapi), mCompilation(compilation) {} | |
| const NnApiSupportLibrary* mNnApi = nullptr; | |
| ANeuralNetworksCompilation* mCompilation = nullptr; | |
| }; | |
| class Execution { | |
| public: | |
| Execution(const NnApiSupportLibrary* nnapi, const Compilation* compilation) | |
| : mNnApi(nnapi), mCompilation(compilation->getHandle()) { | |
| int result = mNnApi->ANeuralNetworksExecution_create(compilation->getHandle(), &mExecution); | |
| if (result != 0) { | |
| // TODO Handle the error | |
| } | |
| } | |
| ~Execution() { mNnApi->ANeuralNetworksExecution_free(mExecution); } | |
| // Disallow copy semantics to ensure the runtime object can only be freed | |
| // once. Copy semantics could be enabled if some sort of reference counting | |
| // or deep-copy system for runtime objects is added later. | |
| Execution(const Execution&) = delete; | |
| Execution& operator=(const Execution&) = delete; | |
| // Move semantics to remove access to the runtime object from the wrapper | |
| // object that is being moved. This ensures the runtime object will be | |
| // freed only once. | |
| Execution(Execution&& other) { *this = std::move(other); } | |
| Execution& operator=(Execution&& other) { | |
| if (this != &other) { | |
| mNnApi->ANeuralNetworksExecution_free(mExecution); | |
| mCompilation = other.mCompilation; | |
| other.mCompilation = nullptr; | |
| mExecution = other.mExecution; | |
| other.mExecution = nullptr; | |
| } | |
| return *this; | |
| } | |
| Result setInput(uint32_t index, const void* buffer, size_t length, | |
| const ANeuralNetworksOperandType* type = nullptr) { | |
| return static_cast<Result>( | |
| mNnApi->ANeuralNetworksExecution_setInput(mExecution, index, type, buffer, length)); | |
| } | |
| template <typename T> | |
| Result setInput(uint32_t index, const T* value, | |
| const ANeuralNetworksOperandType* type = nullptr) { | |
| static_assert(!std::is_pointer<T>(), "No operand may have a pointer as its value"); | |
| return setInput(index, value, sizeof(T), type); | |
| } | |
| Result setInputFromMemory(uint32_t index, const Memory* memory, uint32_t offset, | |
| uint32_t length, const ANeuralNetworksOperandType* type = nullptr) { | |
| return static_cast<Result>(mNnApi->ANeuralNetworksExecution_setInputFromMemory( | |
| mExecution, index, type, memory->get(), offset, length)); | |
| } | |
| Result setOutput(uint32_t index, void* buffer, size_t length, | |
| const ANeuralNetworksOperandType* type = nullptr) { | |
| return static_cast<Result>(mNnApi->ANeuralNetworksExecution_setOutput( | |
| mExecution, index, type, buffer, length)); | |
| } | |
| template <typename T> | |
| Result setOutput(uint32_t index, T* value, const ANeuralNetworksOperandType* type = nullptr) { | |
| static_assert(!std::is_pointer<T>(), "No operand may have a pointer as its value"); | |
| return setOutput(index, value, sizeof(T), type); | |
| } | |
| Result setOutputFromMemory(uint32_t index, const Memory* memory, uint32_t offset, | |
| uint32_t length, const ANeuralNetworksOperandType* type = nullptr) { | |
| return static_cast<Result>(mNnApi->ANeuralNetworksExecution_setOutputFromMemory( | |
| mExecution, index, type, memory->get(), offset, length)); | |
| } | |
| Result setLoopTimeout(uint64_t duration) { | |
| return static_cast<Result>( | |
| mNnApi->ANeuralNetworksExecution_setLoopTimeout(mExecution, duration)); | |
| } | |
| // By default, compute() uses the synchronous API. Either an argument or | |
| // setComputeMode() can be used to change the behavior of compute() to | |
| // use the burst API | |
| // Returns the previous ComputeMode. | |
| enum class ComputeMode { SYNC, BURST }; | |
| static ComputeMode setComputeMode(ComputeMode mode) { | |
| ComputeMode oldComputeMode = mComputeMode; | |
| mComputeMode = mode; | |
| return oldComputeMode; | |
| } | |
| static ComputeMode getComputeMode() { return mComputeMode; } | |
| Result compute(ComputeMode computeMode = mComputeMode) { | |
| switch (computeMode) { | |
| case ComputeMode::SYNC: { | |
| return static_cast<Result>(mNnApi->ANeuralNetworksExecution_compute(mExecution)); | |
| } | |
| case ComputeMode::BURST: { | |
| ANeuralNetworksBurst* burst = nullptr; | |
| Result result = static_cast<Result>( | |
| mNnApi->ANeuralNetworksBurst_create(mCompilation, &burst)); | |
| if (result != Result::NO_ERROR) { | |
| return result; | |
| } | |
| result = static_cast<Result>( | |
| mNnApi->ANeuralNetworksExecution_burstCompute(mExecution, burst)); | |
| mNnApi->ANeuralNetworksBurst_free(burst); | |
| return result; | |
| } | |
| } | |
| return Result::BAD_DATA; | |
| } | |
| Result getOutputOperandDimensions(uint32_t index, std::vector<uint32_t>* dimensions) { | |
| uint32_t rank = 0; | |
| Result result = static_cast<Result>( | |
| mNnApi->ANeuralNetworksExecution_getOutputOperandRank(mExecution, index, &rank)); | |
| dimensions->resize(rank); | |
| if ((result != Result::NO_ERROR && result != Result::OUTPUT_INSUFFICIENT_SIZE) || | |
| rank == 0) { | |
| return result; | |
| } | |
| result = static_cast<Result>(mNnApi->ANeuralNetworksExecution_getOutputOperandDimensions( | |
| mExecution, index, dimensions->data())); | |
| return result; | |
| } | |
| ANeuralNetworksExecution* getHandle() { return mExecution; }; | |
| private: | |
| const NnApiSupportLibrary* mNnApi = nullptr; | |
| ANeuralNetworksCompilation* mCompilation = nullptr; | |
| ANeuralNetworksExecution* mExecution = nullptr; | |
| // Initialized to ComputeMode::SYNC in TestNeuralNetworksWrapper.cpp. | |
| static ComputeMode mComputeMode; | |
| }; | |
| } // namespace test_wrapper | |
| } // namespace nn | |
| } // namespace android | |
| #endif // ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_TEST_SUPPORT_LIBRARY_TEST_WRAPPER_H |