diff options
author | Michael Butler <butlermichael@google.com> | 2020-11-17 01:44:38 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2020-11-17 01:44:38 +0000 |
commit | 75ec7d8cc4e55c77e32090215c28f9ecc0d41057 (patch) | |
tree | 0b533b9ac4b356a60d063767ee70b50a98f08af9 | |
parent | 638a57c1b06b4369487c6d36ae6c7bbc245ec14d (diff) | |
parent | c9f7baa4652bafc75abac0f3ed2b388650cb2e32 (diff) | |
download | ml-75ec7d8cc4e55c77e32090215c28f9ecc0d41057.tar.gz |
Merge "Introduce NNAPI Canonical Interface types"
-rw-r--r-- | nn/common/SharedMemory.cpp | 8 | ||||
-rw-r--r-- | nn/common/SharedMemoryAndroid.cpp | 48 | ||||
-rw-r--r-- | nn/common/SharedMemoryHost.cpp | 36 | ||||
-rw-r--r-- | nn/common/TypeUtils.cpp | 14 | ||||
-rw-r--r-- | nn/common/Types.cpp | 65 | ||||
-rw-r--r-- | nn/common/include/nnapi/IBuffer.h | 78 | ||||
-rw-r--r-- | nn/common/include/nnapi/IDevice.h | 367 | ||||
-rw-r--r-- | nn/common/include/nnapi/IPreparedModel.h | 177 | ||||
-rw-r--r-- | nn/common/include/nnapi/Result.h | 21 | ||||
-rw-r--r-- | nn/common/include/nnapi/SharedMemory.h | 14 | ||||
-rw-r--r-- | nn/common/include/nnapi/TypeUtils.h | 1 | ||||
-rw-r--r-- | nn/common/include/nnapi/Types.h | 49 | ||||
-rw-r--r-- | nn/common/include/nnapi/Validation.h | 5 |
13 files changed, 822 insertions, 61 deletions
diff --git a/nn/common/SharedMemory.cpp b/nn/common/SharedMemory.cpp index 9186be20e..3c848c315 100644 --- a/nn/common/SharedMemory.cpp +++ b/nn/common/SharedMemory.cpp @@ -67,7 +67,7 @@ bool MutableMemoryBuilder::empty() const { return mSize == 0; } -Result<Memory> MutableMemoryBuilder::finish() { +GeneralResult<Memory> MutableMemoryBuilder::finish() { return createSharedMemory(mSize); } @@ -84,7 +84,7 @@ bool ConstantMemoryBuilder::empty() const { return mBuilder.empty(); } -Result<Memory> ConstantMemoryBuilder::finish() { +GeneralResult<Memory> ConstantMemoryBuilder::finish() { // Allocate the memory. auto memory = NN_TRY(mBuilder.finish()); @@ -92,10 +92,6 @@ Result<Memory> ConstantMemoryBuilder::finish() { const auto [pointer, size, context] = NN_TRY(map(memory);); // Get mutable pointer. - if (!std::holds_alternative<void*>(pointer)) { - return NN_ERROR() - << "MemoryBuilder::finish failed because the mapped pointer is not mutable"; - } uint8_t* mutablePointer = static_cast<uint8_t*>(std::get<void*>(pointer)); // Copy data to the memory pool. diff --git a/nn/common/SharedMemoryAndroid.cpp b/nn/common/SharedMemoryAndroid.cpp index caa83a6e8..9baca73c5 100644 --- a/nn/common/SharedMemoryAndroid.cpp +++ b/nn/common/SharedMemoryAndroid.cpp @@ -46,7 +46,7 @@ using ::android::hidl::allocator::V1_0::IAllocator; const char* const kAllocatorService = "ashmem"; -Result<hidl_memory> allocateSharedMemory(size_t size) { +GeneralResult<hidl_memory> allocateSharedMemory(size_t size) { static const auto allocator = IAllocator::getService(kAllocatorService); CHECK_GT(size, 0u); @@ -59,7 +59,8 @@ Result<hidl_memory> allocateSharedMemory(size_t size) { allocator->allocate(size, fn); if (!maybeMemory.valid()) { - return NN_ERROR() << "IAllocator::allocate returned an invalid (empty) memory object"; + return NN_ERROR(ErrorStatus::GENERAL_FAILURE) + << "IAllocator::allocate returned an invalid (empty) memory object"; } return maybeMemory; @@ -85,19 +86,19 @@ hidl_memory createHidlMemory(const Memory& memory) { return copiedMemory; } -Result<Mapping> mapAshmem(const Memory& memory) { +GeneralResult<Mapping> mapAshmem(const Memory& memory) { const auto hidlMemory = createHidlMemory(memory); const auto mapping = mapMemory(hidlMemory); if (mapping == nullptr) { - return NN_ERROR() << "Failed to map memory"; + return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Failed to map memory"; } auto* const pointer = mapping->getPointer().withDefault(nullptr); if (pointer == nullptr) { - return NN_ERROR() << "Failed to get the mapped pointer"; + return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Failed to get the mapped pointer"; } const auto fullSize = mapping->getSize().withDefault(0); if (fullSize == 0 || fullSize > std::numeric_limits<size_t>::max()) { - return NN_ERROR() << "Failed to get the mapped size"; + return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Failed to get the mapped size"; } const size_t size = static_cast<size_t>(fullSize); @@ -113,7 +114,7 @@ struct MmapFdMappingContext { std::any context; }; -Result<Mapping> mapMemFd(const Memory& memory) { +GeneralResult<Mapping> mapMemFd(const Memory& memory) { const size_t size = memory.size; const native_handle_t* handle = memory.handle->handle(); const int fd = handle->data[0]; @@ -122,7 +123,7 @@ Result<Mapping> mapMemFd(const Memory& memory) { std::shared_ptr<base::MappedFile> mapping = base::MappedFile::FromFd(fd, offset, size, prot); if (mapping == nullptr) { - return NN_ERROR() << "Can't mmap the file descriptor."; + return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Can't mmap the file descriptor."; } void* data = mapping->data(); @@ -130,7 +131,7 @@ Result<Mapping> mapMemFd(const Memory& memory) { return Mapping{.pointer = data, .size = size, .context = std::move(context)}; } -Result<Mapping> mapAhwbBlobMemory(const Memory& memory) { +GeneralResult<Mapping> mapAhwbBlobMemory(const Memory& memory) { const auto* handle = memory.handle->handle(); const auto size = memory.size; const auto format = AHARDWAREBUFFER_FORMAT_BLOB; @@ -153,13 +154,15 @@ Result<Mapping> mapAhwbBlobMemory(const Memory& memory) { status_t status = AHardwareBuffer_createFromHandle( &desc, handle, AHARDWAREBUFFER_CREATE_FROM_HANDLE_METHOD_CLONE, &hardwareBuffer); if (status != NO_ERROR) { - return NN_ERROR() << "Can't create AHardwareBuffer from handle. Error: " << status; + return NN_ERROR(ErrorStatus::GENERAL_FAILURE) + << "Can't create AHardwareBuffer from handle. Error: " << status; } void* data = nullptr; status = AHardwareBuffer_lock(hardwareBuffer, usage, -1, nullptr, &data); if (status != NO_ERROR) { - return NN_ERROR() << "Can't lock the AHardwareBuffer. Error: " << status; + return NN_ERROR(ErrorStatus::GENERAL_FAILURE) + << "Can't lock the AHardwareBuffer. Error: " << status; // TODO(b/169166682): do we need to call AHardwareBuffer_release? } @@ -175,27 +178,28 @@ Result<Mapping> mapAhwbBlobMemory(const Memory& memory) { return Mapping{.pointer = data, .size = size, .context = std::move(sharedScoped)}; } -Result<Mapping> mapAhwbMemory(const Memory& /*memory*/) { - return NN_ERROR() << "Unable to map non-BLOB AHardwareBuffer memory"; +GeneralResult<Mapping> mapAhwbMemory(const Memory& /*memory*/) { + return NN_ERROR(ErrorStatus::GENERAL_FAILURE) + << "Unable to map non-BLOB AHardwareBuffer memory"; } } // namespace -Result<Memory> createSharedMemory(size_t size) { +GeneralResult<Memory> createSharedMemory(size_t size) { const auto memory = NN_TRY(allocateSharedMemory(size)); return createSharedMemoryFromHidlMemory(memory); } -Result<Memory> createSharedMemoryFromFd(size_t size, int prot, int fd, size_t offset) { +GeneralResult<Memory> createSharedMemoryFromFd(size_t size, int prot, int fd, size_t offset) { if (size == 0 || fd < 0) { - return NN_ERROR() << "Invalid size or fd"; + return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << "Invalid size or fd"; } // Duplicate the file descriptor so the resultant Memory owns its own version. int dupfd = dup(fd); if (dupfd == -1) { // TODO(b/120417090): is ANEURALNETWORKS_UNEXPECTED_NULL the correct error to return here? - return NN_ERROR() << "Failed to dup the fd"; + return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << "Failed to dup the fd"; } // Create a temporary native handle to own the dupfd. @@ -203,7 +207,7 @@ Result<Memory> createSharedMemoryFromFd(size_t size, int prot, int fd, size_t of if (nativeHandle == nullptr) { close(dupfd); // TODO(b/120417090): is ANEURALNETWORKS_UNEXPECTED_NULL the correct error to return here? - return NN_ERROR() << "Failed to create native_handle"; + return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Failed to create native_handle"; } const auto [lowOffsetBits, highOffsetBits] = getIntsFromOffset(offset); @@ -219,11 +223,11 @@ Result<Memory> createSharedMemoryFromFd(size_t size, int prot, int fd, size_t of return Memory{.handle = std::move(ownedHandle), .size = size, .name = "mmap_fd"}; } -Result<Memory> createSharedMemoryFromHidlMemory(const hardware::hidl_memory& memory) { +GeneralResult<Memory> createSharedMemoryFromHidlMemory(const hardware::hidl_memory& memory) { return createMemory(memory); } -Result<Memory> createSharedMemoryFromAHWB(const AHardwareBuffer& ahwb) { +GeneralResult<Memory> createSharedMemoryFromAHWB(const AHardwareBuffer& ahwb) { AHardwareBuffer_Desc bufferDesc; AHardwareBuffer_describe(&ahwb, &bufferDesc); const native_handle_t* handle = AHardwareBuffer_getNativeHandle(&ahwb); @@ -243,7 +247,7 @@ Result<Memory> createSharedMemoryFromAHWB(const AHardwareBuffer& ahwb) { return Memory{.handle = std::move(nativeHandle), .size = 0, .name = "hardware_buffer"}; } -Result<Mapping> map(const Memory& memory) { +GeneralResult<Mapping> map(const Memory& memory) { if (memory.name == "ashmem") { return mapAshmem(memory); } @@ -256,7 +260,7 @@ Result<Mapping> map(const Memory& memory) { if (memory.name == "hardware_buffer") { return mapAhwbMemory(memory); } - return NN_ERROR() << "Cannot map unknown memory " << memory.name; + return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << "Cannot map unknown memory " << memory.name; } bool flush(const Mapping& mapping) { diff --git a/nn/common/SharedMemoryHost.cpp b/nn/common/SharedMemoryHost.cpp index bc29d1ff1..231977cf7 100644 --- a/nn/common/SharedMemoryHost.cpp +++ b/nn/common/SharedMemoryHost.cpp @@ -32,7 +32,7 @@ namespace android::nn { namespace { -Result<Mapping> mapAshmem(const Memory& memory) { +GeneralResult<Mapping> mapAshmem(const Memory& memory) { CHECK_LE(memory.size, std::numeric_limits<uint32_t>::max()); const auto size = memory.size; @@ -40,7 +40,7 @@ Result<Mapping> mapAshmem(const Memory& memory) { std::shared_ptr<base::MappedFile> mapping = base::MappedFile::FromFd(fd, /*offset=*/0, size, PROT_READ | PROT_WRITE); if (mapping == nullptr) { - return NN_ERROR() << "Can't mmap the file descriptor."; + return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Can't mmap the file descriptor."; } void* data = mapping->data(); @@ -52,7 +52,7 @@ struct MmapFdMappingContext { std::any context; }; -Result<Mapping> mapMemFd(const Memory& memory) { +GeneralResult<Mapping> mapMemFd(const Memory& memory) { const size_t size = memory.size; const native_handle_t* handle = memory.handle->handle(); const int fd = handle->data[0]; @@ -61,7 +61,7 @@ Result<Mapping> mapMemFd(const Memory& memory) { std::shared_ptr<base::MappedFile> mapping = base::MappedFile::FromFd(fd, offset, size, prot); if (mapping == nullptr) { - return NN_ERROR() << "Can't mmap the file descriptor."; + return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Can't mmap the file descriptor."; } void* data = mapping->data(); @@ -71,16 +71,17 @@ Result<Mapping> mapMemFd(const Memory& memory) { } // namespace -Result<Memory> createSharedMemory(size_t size) { +GeneralResult<Memory> createSharedMemory(size_t size) { int fd = ashmem_create_region("NnapiAshmem", size); if (fd < 0) { - return NN_ERROR() << "ashmem_create_region(" << size << ") fails with " << fd; + return NN_ERROR(ErrorStatus::GENERAL_FAILURE) + << "ashmem_create_region(" << size << ") fails with " << fd; } native_handle_t* handle = native_handle_create(1, 0); if (handle == nullptr) { // TODO(b/120417090): is ANEURALNETWORKS_UNEXPECTED_NULL the correct error to return here? - return NN_ERROR() << "Failed to create native_handle"; + return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Failed to create native_handle"; } handle->data[0] = fd; @@ -91,16 +92,16 @@ Result<Memory> createSharedMemory(size_t size) { return Memory{.handle = std::move(nativeHandle), .size = size, .name = "ashmem"}; } -Result<Memory> createSharedMemoryFromFd(size_t size, int prot, int fd, size_t offset) { +GeneralResult<Memory> createSharedMemoryFromFd(size_t size, int prot, int fd, size_t offset) { if (size == 0 || fd < 0) { - return NN_ERROR() << "Invalid size or fd"; + return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << "Invalid size or fd"; } // Duplicate the file descriptor so the resultant Memory owns its own version. int dupfd = dup(fd); if (dupfd == -1) { // TODO(b/120417090): is ANEURALNETWORKS_UNEXPECTED_NULL the correct error to return here? - return NN_ERROR() << "Failed to dup the fd"; + return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << "Failed to dup the fd"; } // Create a temporary native handle to own the dupfd. @@ -108,7 +109,7 @@ Result<Memory> createSharedMemoryFromFd(size_t size, int prot, int fd, size_t of if (nativeHandle == nullptr) { close(dupfd); // TODO(b/120417090): is ANEURALNETWORKS_UNEXPECTED_NULL the correct error to return here? - return NN_ERROR() << "Failed to create native_handle"; + return NN_ERROR(ErrorStatus::GENERAL_FAILURE) << "Failed to create native_handle"; } const auto [lowOffsetBits, highOffsetBits] = getIntsFromOffset(offset); @@ -124,22 +125,23 @@ Result<Memory> createSharedMemoryFromFd(size_t size, int prot, int fd, size_t of return Memory{.handle = std::move(ownedHandle), .size = size, .name = "mmap_fd"}; } -Result<Memory> createSharedMemoryFromHidlMemory(const hardware::hidl_memory& /*memory*/) { - return NN_ERROR() << "hidl_memory not supported on host"; +GeneralResult<Memory> createSharedMemoryFromHidlMemory(const hardware::hidl_memory& /*memory*/) { + return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << "hidl_memory not supported on host"; } -Result<Memory> createSharedMemoryFromAHWB(const AHardwareBuffer& /*ahwb*/) { - return NN_ERROR() << "AHardwareBuffer memory not supported on host"; +GeneralResult<Memory> createSharedMemoryFromAHWB(const AHardwareBuffer& /*ahwb*/) { + return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) + << "AHardwareBuffer memory not supported on host"; } -Result<Mapping> map(const Memory& memory) { +GeneralResult<Mapping> map(const Memory& memory) { if (memory.name == "ashmem") { return mapAshmem(memory); } if (memory.name == "mmap_fd") { return mapMemFd(memory); } - return NN_ERROR() << "Cannot map unknown memory " << memory.name; + return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << "Cannot map unknown memory " << memory.name; } bool flush(const Mapping& mapping) { diff --git a/nn/common/TypeUtils.cpp b/nn/common/TypeUtils.cpp index ad17d9444..6d089bd2a 100644 --- a/nn/common/TypeUtils.cpp +++ b/nn/common/TypeUtils.cpp @@ -782,6 +782,20 @@ std::ostream& operator<<(std::ostream& os, const TimePoint& timePoint) { return os << timePoint.time_since_epoch().count() << "ns since epoch"; } +std::ostream& operator<<(std::ostream& os, const SyncFence::FenceState& fenceState) { + switch (fenceState) { + case SyncFence::FenceState::ACTIVE: + return os << "ACTIVE"; + case SyncFence::FenceState::SIGNALED: + return os << "SIGNALED"; + case SyncFence::FenceState::ERROR: + return os << "ERROR"; + case SyncFence::FenceState::UNKNOWN: + return os << "UNKNOWN"; + } + return os << "SyncFence::FenceState{" << underlyingType(fenceState) << "}"; +} + std::ostream& operator<<(std::ostream& os, const OptionalTimePoint& optionalTimePoint) { if (!optionalTimePoint.has_value()) { return os << "<no time point>"; diff --git a/nn/common/Types.cpp b/nn/common/Types.cpp index 761ffa72c..17485f442 100644 --- a/nn/common/Types.cpp +++ b/nn/common/Types.cpp @@ -17,6 +17,9 @@ #include "Types.h" #include <android-base/logging.h> +#include <cutils/native_handle.h> +#include <errno.h> +#include <poll.h> #include <algorithm> #include <cstddef> @@ -121,4 +124,66 @@ Capabilities::OperandPerformanceTable::asVector() const { return mSorted; } +SyncFence SyncFence::createAsSignaled() { + return SyncFence(nullptr); +} + +Result<SyncFence> SyncFence::create(NativeHandle syncFence) { + const bool isValid = (syncFence != nullptr && syncFence->handle() != nullptr && + syncFence->handle()->numFds == 1 && syncFence->handle()->numInts == 0 && + &syncFence->handle()->data[0] != nullptr); + if (!isValid) { + return NN_ERROR() << "Invalid sync fence handle passed to SyncFence::create"; + } + return SyncFence(std::move(syncFence)); +} + +SyncFence::SyncFence(NativeHandle syncFence) : mSyncFence(std::move(syncFence)) {} + +SyncFence::FenceState SyncFence::syncWait(OptionalTimeout optionalTimeout) const { + if (mSyncFence == nullptr) { + return FenceState::SIGNALED; + } + + const int fd = mSyncFence->handle()->data[0]; + const int timeout = optionalTimeout.value_or(Timeout{-1}).count(); + + // This implementation is directly based on the ::sync_wait() implementation. + + struct pollfd fds; + int ret; + + if (fd < 0) { + errno = EINVAL; + return FenceState::UNKNOWN; + } + + fds.fd = fd; + fds.events = POLLIN; + + do { + ret = poll(&fds, 1, timeout); + if (ret > 0) { + if (fds.revents & POLLNVAL) { + errno = EINVAL; + return FenceState::UNKNOWN; + } + if (fds.revents & POLLERR) { + errno = EINVAL; + return FenceState::ERROR; + } + return FenceState::SIGNALED; + } else if (ret == 0) { + errno = ETIME; + return FenceState::ACTIVE; + } + } while (ret == -1 && (errno == EINTR || errno == EAGAIN)); + + return FenceState::UNKNOWN; +} + +NativeHandle SyncFence::getHandle() const { + return mSyncFence; +} + } // namespace android::nn diff --git a/nn/common/include/nnapi/IBuffer.h b/nn/common/include/nnapi/IBuffer.h new file mode 100644 index 000000000..295681ecd --- /dev/null +++ b/nn/common/include/nnapi/IBuffer.h @@ -0,0 +1,78 @@ +/* + * 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_FRAMEWORKS_ML_NN_COMMON_NNAPI_IBUFFER_H +#define ANDROID_FRAMEWORKS_ML_NN_COMMON_NNAPI_IBUFFER_H + +#include "nnapi/Result.h" +#include "nnapi/Types.h" + +namespace android::nn { + +/** + * This interface represents a device memory buffer. + * + * This interface is thread-safe, and any class that implements this interface must be thread-safe. + */ +class IBuffer { + public: + /** + * Retrieves the token corresponding to this buffer. + * + * @return MemoryDomainToken corresponding to this buffer. + */ + virtual Request::MemoryDomainToken getToken() const = 0; + + /** + * Retrieves the content of this buffer to a shared memory region. + * + * The IBuffer object must have been initialized before the call to IBuffer::copyTo. + * For more information on the state of the IBuffer object, refer to IDevice::allocate. + * + * @param dst The destination shared memory region. + * @return Nothing on success, otherwise GeneralError. + */ + virtual GeneralResult<void> copyTo(const Memory& dst) const = 0; + + /** + * Sets the content of this buffer from a shared memory region. + * + * @param src The source shared memory region. + * @param dimensions Updated dimensional information. If the dimensions of the IBuffer object + * are not fully specified, then the dimensions must be fully specified here. If the + * dimensions of the IBuffer object are fully specified, then the dimensions may be empty + * here. If dimensions.size() > 0, then all dimensions must be specified here, and any + * dimension that was specified in the IBuffer object must have the same value here. + * @return Nothing on success, otherwise GeneralError. + */ + virtual GeneralResult<void> copyFrom(const Memory& src, const Dimensions& dimensions) const = 0; + + // Public virtual destructor to allow objects to be stored (and destroyed) as smart pointers. + // E.g., std::unique_ptr<IBuffer>. + virtual ~IBuffer() = default; + + protected: + // Protect the non-destructor special member functions to prevent object slicing. + IBuffer() = default; + IBuffer(const IBuffer&) = default; + IBuffer(IBuffer&&) noexcept = default; + IBuffer& operator=(const IBuffer&) = default; + IBuffer& operator=(IBuffer&&) noexcept = default; +}; + +} // namespace android::nn + +#endif // ANDROID_FRAMEWORKS_ML_NN_COMMON_NNAPI_IBUFFER_H diff --git a/nn/common/include/nnapi/IDevice.h b/nn/common/include/nnapi/IDevice.h new file mode 100644 index 000000000..9a2e51680 --- /dev/null +++ b/nn/common/include/nnapi/IDevice.h @@ -0,0 +1,367 @@ +/* + * 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_FRAMEWORKS_ML_NN_COMMON_NNAPI_IDEVICE_H +#define ANDROID_FRAMEWORKS_ML_NN_COMMON_NNAPI_IDEVICE_H + +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "nnapi/Result.h" +#include "nnapi/Types.h" + +namespace android::nn { + +// Forward declarations +class IBuffer; +class IPreparedModel; + +/** + * This interface represents a device driver. + * + * This interface is thread-safe, and any class that implements this interface must be thread-safe. + */ +class IDevice { + public: + /** + * Returns the name of the driver. + * + * @return Name of the driver. + */ + virtual const std::string& getName() const = 0; + + /** + * Get the version string of the driver implementation. + * + * The version string must be a unique token among the set of version strings of drivers of a + * specific device. The token identifies the device driver's implementation. The token must not + * be confused with the feature level which is solely defined by the interface version. This API + * is opaque to the Android framework, but the Android framework may use the information for + * debugging or to pass on to NNAPI applications. + * + * Application developers sometimes have specific requirements to ensure good user experiences, + * and they need more information to make intelligent decisions when the Android framework + * cannot. For example, combined with the device name and other information, the token can help + * NNAPI applications filter devices based on their needs: + * - An application demands a certain level of performance, but a specific version of the driver + * cannot meet that requirement because of a performance regression. The application can + * disallow the driver based on the version provided. + * - An application has a minimum precision requirement, but certain versions of the driver + * cannot meet that requirement because of bugs or certain optimizations. The application can + * filter out versions of these drivers. + * + * @return version The version string of the device implementation. Must have nonzero length. + */ + virtual const std::string& getVersionString() const = 0; + + /** + * Returns the feature level of a driver. + * + * @return featureLevel The API level of the most advanced feature this driver implements. + */ + virtual Version getFeatureLevel() const = 0; + + /** + * Returns the device type of a driver. + * + * The device type can be used to help application developers to distribute Machine Learning + * workloads and other workloads such as graphical rendering. E.g., for an app which renders AR + * scenes based on real time object detection results, the developer could choose an ACCELERATOR + * type device for ML workloads, and reserve GPU for graphical rendering. + * + * @return type The DeviceType of the device. Please note, this is not a bitfield of + * DeviceTypes. Each device must only be of a single DeviceType. + */ + virtual DeviceType getType() const = 0; + + /** + * Gets information about extensions supported by the driver implementation. + * + * Extensions of category ExtensionCategory::BASE must not appear in the list. + * + * All extension operations and operands must be fully supported for the extension to appear in + * the list of supported extensions. + * + * @return extensions A list of supported extensions. + */ + virtual const std::vector<Extension>& getSupportedExtensions() const = 0; + + /** + * Gets the capabilities of a driver. + * + * @return capabilities Capabilities of the driver. + */ + virtual const Capabilities& getCapabilities() const = 0; + + /** + * Gets the caching requirements of the driver implementation. + * + * There are two types of cache file descriptors provided to the driver: model cache and data + * cache. + * + * The data cache is for caching constant data, possibly including preprocessed and transformed + * tensor buffers. Any modification to the data cache should have no worse effect than + * generating bad output values at execution time. + * + * The model cache is for caching security-sensitive data such as compiled executable machine + * code in the device's native binary format. A modification to the model cache may affect the + * driver's execution behavior, and a malicious client could make use of this to execute beyond + * the granted permission. Thus, the driver must always check whether the model cache is + * corrupted before preparing the model from cache. + * + * IDevice::getNumberOfCacheFilesNeeded returns how many of each type of cache files the driver + * implementation needs to cache a single prepared model. Returning 0 for both types indicates + * compilation caching is not supported by this driver. The driver may still choose not to cache + * certain compiled models even if it reports that caching is supported. + * + * If the device reports that caching is not supported, the user may avoid calling + * IDevice::prepareModelFromCache or providing cache file descriptors to IDevice::prepareModel. + * + * @return A pair of: + * - numModelCache An unsigned integer indicating how many files for model cache the driver + * needs to cache a single prepared model. It must be less than or equal to + * ::android::nn::kMaxNumberOfCacheFiles. + * - numDataCache An unsigned integer indicating how many files for data cache the driver + * needs to cache a single prepared model. It must be less than or equal to + * ::android::nn::kMaxNumberOfCacheFiles. + */ + virtual std::pair<uint32_t, uint32_t> getNumberOfCacheFilesNeeded() const = 0; + + /** + * Blocks until the device is not in a bad state. + * + * @return Nothing on success, otherwise GeneralError. + */ + virtual GeneralResult<void> wait() const = 0; + + /** + * Gets the supported operations in a model. + * + * IDevice::getSupportedOperations indicates which operations of the top-level subgraph are + * fully supported by the vendor driver. If an operation may not be supported for any reason, + * IDevice::getSupportedOperations must return `false` for that operation. + * + * The {@link OperationType::IF} and {@link OperationType::WHILE} operations may only be fully + * supported if the vendor driver fully supports all operations in the referenced subgraphs. + * + * @param model A Model whose operations--and their corresponding operands--are to be verified + * by the driver. + * @return supportedOperations A list of supported operations, where `true` indicates the + * operation is supported and `false` indicates the operation is not supported. The index of + * "supported" corresponds with the index of the operation it is describing. + */ + virtual GeneralResult<std::vector<bool>> getSupportedOperations(const Model& model) const = 0; + + /** + * Creates a prepared model for execution. + * + * IDevice::prepareModel is used to make any necessary transformations or alternative + * representations to a model for execution, possibly including transformations on the constant + * data, optimization on the model's graph, or compilation into the device's native binary + * format. The model itself is not changed. + * + * Optionally, caching information may be provided for the driver to save the prepared model to + * cache files for faster model compilation time when the same model preparation is requested in + * the future. There are two types of cache file handles provided to the driver: model cache and + * data cache. For more information on the two types of cache handles, refer to + * IDevice::getNumberOfCacheFilesNeeded. + * + * The file descriptors must be opened with read and write permission. A file may have any size, + * and the corresponding file descriptor may have any offset. The driver must truncate a file to + * zero size before writing to that file. The file descriptors may be closed by the client once + * the preparation has finished. The driver must dup a file descriptor if it wants to get access + * to the cache file later. + * + * IDevice::prepareModel must verify its inputs related to preparing the model (as opposed to + * saving the prepared model to cache) are correct. If there is an error, IDevice::prepareModel + * must immediately return {@link ErrorStatus::INVALID_ARGUMENT} as a GeneralError. If the + * inputs to IDevice::prepareModel are valid and there is no error, IDevice::prepareModel must + * prepare the model. + * + * The model is prepared with a priority. This priority is relative to other prepared models + * owned by the same client. Higher priority executions may use more compute resources than + * lower priority executions, and may preempt or starve lower priority executions. + * + * IDevice::prepareModel can be called with an optional deadline. If the model is not able to be + * prepared before the provided deadline, the model preparation may be aborted, and either + * {@link ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link + * ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned as a GeneralError. + * + * Optionally, the driver may save the prepared model to cache during the preparation. Any error + * that occurs when saving to cache must not affect the status of preparing the model. Even if + * the input arguments related to the cache may be invalid, or the driver may fail to save to + * cache, IDevice::prepareModel must finish preparing the model. The driver may choose not to + * save to cache even if the caching information is provided and valid. + * + * The only information that may be unknown to the model at this stage is the shape of the + * tensors, which may only be known at execution time. As such, some driver services may return + * partially prepared models, where the prepared model may only be finished when it is paired + * with a set of inputs to the model. Note that the same prepared model object may be used with + * different shapes of inputs on different (possibly concurrent) executions. + * + * @param model The model to be prepared for execution. + * @param preference Indicates the intended execution behavior of a prepared model. + * @param priority Priority of the prepared model relative to other prepared models owned by an + * application. + * @param deadline Optional time point. If provided, prepareModel is expected to complete by + * this time point. If it is not able to be completed by the deadline, the execution may be + * aborted. + * @param modelCache A vector of handles with each entry holding exactly one cache file + * descriptor for the security-sensitive cache. The length of the vector must either be 0 + * indicating that caching information is not provided, or match numModelCache returned from + * IDevice::getNumberOfCacheFilesNeeded. The cache handles will be provided in the same + * order when retrieving the preparedModel from cache files with + * IDevice::prepareModelFromCache. + * @param dataCache A vector of handles with each entry holding exactly one cache file + * descriptor for the constants' cache. The length of the vector must either be 0 indicating + * that caching information is not provided, or match numDataCache returned from + * IDevice::getNumberOfCacheFilesNeeded. The cache handles will be provided in the same + * order when retrieving the preparedModel from cache files with + * IDevice::prepareModelFromCache. + * @param token An caching token of length ::android::nn::kByteSizeOfCacheToken identifying the + * prepared model. The same token will be provided when retrieving the prepared model from + * the cache files with IDevice::prepareModelFromCache. Tokens should be chosen to have a + * low rate of collision for a particular application. The driver cannot detect a collision; + * a collision will result in a failed execution or in a successful execution that produces + * incorrect output values. If both modelCache and dataCache are empty indicating that + * caching information is not provided, this token must be ignored. + * @return preparedModel An IPreparedModel object representing a model that has been prepared + * for execution, otherwise GeneralError. + */ + virtual GeneralResult<SharedPreparedModel> prepareModel( + const Model& model, ExecutionPreference preference, Priority priority, + OptionalTimePoint deadline, const std::vector<NativeHandle>& modelCache, + const std::vector<NativeHandle>& dataCache, const CacheToken& token) const = 0; + + /** + * Creates a prepared model from cache files for execution. + * + * IDevice::prepareModelFromCache is used to retrieve a prepared model directly from cache files + * to avoid slow model compilation time. There are two types of cache file handles provided to + * the driver: model cache and data cache. For more information on the two types of cache + * handles, refer to IDevice::getNumberOfCacheFilesNeeded. + * + * The file descriptors must be opened with read and write permission. A file may have any size, + * and the corresponding file descriptor may have any offset. The driver must truncate a file to + * zero size before writing to that file. The file descriptors may be closed by the client once + * the preparation has finished. The driver must dup a file descriptor if it wants to get access + * to the cache file later. + * + * IDevice::prepareModelFromCache must verify its inputs are correct, and that the + * security-sensitive cache has not been modified since it was last written by the driver. If + * there is an error, or if compilation caching is not supported, or if the security-sensitive + * cache has been modified, IDevice::prepareModelFromCache must immediately return {@link + * ErrorStatus::INVALID_ARGUMENT} as a GeneralError. If the inputs to + * IDevice::prepareModelFromCache are valid, the security-sensitive cache is not modified, and + * there is no error, IDevice::prepareModelFromCache must prepare the model + * + * IDevice::prepareModelFromCache can be called with an optional deadline. If the model is not + * able to prepared before the provided deadline, the model preparation may be aborted, and + * either {@link ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link + * ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned as a GeneralError. + * + * The only information that may be unknown to the model at this stage is the shape of the + * tensors, which may only be known at execution time. As such, some driver services may return + * partially prepared models, where the prepared model may only be finished when it is paired + * with a set of inputs to the model. Note that the same prepared model object may be used with + * different shapes of inputs on different (possibly concurrent) executions. + * + * @param deadline Optional time point. If provided, prepareModel is expected to complete by + * this time point. If it is not able to be completed by the deadline, the execution may be + * aborted. + * @param modelCache A vector of handles with each entry holding exactly one cache file + * descriptor for the security-sensitive cache. The length of the vector must match the + * numModelCache returned from IDevice::getNumberOfCacheFilesNeeded. The cache handles will + * be provided in the same order as with IDevice::prepareModel. + * @param dataCache A vector of handles with each entry holding exactly one cache file + * descriptor for the constants' cache. The length of the vector must match the numDataCache + * returned from IDevice::getNumberOfCacheFilesNeeded. The cache handles will be provided in + * the same order as with IDevice::prepareModel. + * @param token A caching token of length ::android::nn::kByteSizeOfCacheToken identifying the + * prepared model. It is the same token provided when saving the cache files with + * IDevice::prepareModel. Tokens should be chosen to have a low rate of collision for a + * particular application. The driver cannot detect a collision; a collision will result in + * a failed execution or in a successful execution that produces incorrect output values. + * @return preparedModel An IPreparedModel object representing a model that has been prepared + * for execution, otherwise GeneralError. + */ + virtual GeneralResult<SharedPreparedModel> prepareModelFromCache( + OptionalTimePoint deadline, const std::vector<NativeHandle>& modelCache, + const std::vector<NativeHandle>& dataCache, const CacheToken& token) const = 0; + + /** + * Allocates a driver-managed buffer with the properties specified by the descriptor as well as + * the input and output roles of prepared models. + * + * IDevice::allocate must verify its inputs are correct. If there is an error, or if a certain + * role or property is not supported by the driver, IDevice::allocate must return with {@link + * ErrorStatus::INVALID_ARGUMENT} as a GeneralError. If the allocation is successful, this + * method must return the produced IBuffer. A successful allocation must accommodate all of the + * specified roles and buffer properties. + * + * The buffer is allocated as an uninitialized state. An uninitialized buffer may only be used + * in ways that are specified by outputRoles. A buffer is initialized after it is used as an + * output in a successful execution, or after a successful invocation of IBuffer::copyFrom on + * the buffer. An initialized buffer may be used according to all roles specified in inputRoles + * and outputRoles. A buffer will return to the uninitialized state if it is used as an output + * in a failed execution, or after a failed invocation of IBuffer::copyFrom on the buffer. + * + * The driver may deduce the dimensions of the buffer according to the buffer descriptor as well + * as the input and output roles. The dimensions or rank of the buffer may be unknown at this + * stage. As such, some driver services may only create a placeholder and defer the actual + * allocation until execution time. Note that the same buffer may be used for different shapes + * of outputs on different executions. When the buffer is used as an input, the input shape must + * be the same as the output shape from the last execution using this buffer as an output. + * + * The driver must apply proper validatation upon every usage of the buffer, and fail the + * execution immediately if the usage is illegal. + * + * @param desc A buffer descriptor specifying the properties of the buffer to allocate. + * @param preparedModels A vector of IPreparedModel objects. Must only contain IPreparedModel + * objects from the same IDevice as this method invoked on. + * @param inputRoles A vector of roles with each specifying an input to a prepared model. + * @param outputRoles A vector of roles with each specifying an output to a prepared model. + * Each role specified in inputRoles and outputRoles must be unique. The corresponding model + * operands of the roles must have the same OperandType, scale, zero point, and ExtraParams. + * The dimensions of the operands and the dimensions specified in the buffer descriptor must + * be compatible with each other. Two dimensions are incompatible if there is at least one + * axis that is fully specified in both but has different values. + * @return The allocated IBuffer object. If the buffer was unable to be allocated due to an + * error, a GeneralError is returned instead. + */ + virtual GeneralResult<SharedBuffer> allocate( + const BufferDesc& desc, const std::vector<SharedPreparedModel>& preparedModels, + const std::vector<BufferRole>& inputRoles, + const std::vector<BufferRole>& outputRoles) const = 0; + + // Public virtual destructor to allow objects to be stored (and destroyed) as smart pointers. + // E.g., std::unique_ptr<IDevice>. + virtual ~IDevice() = default; + + protected: + // Protect the non-destructor special member functions to prevent object slicing. + IDevice() = default; + IDevice(const IDevice&) = default; + IDevice(IDevice&&) noexcept = default; + IDevice& operator=(const IDevice&) = default; + IDevice& operator=(IDevice&&) noexcept = default; +}; + +} // namespace android::nn + +#endif // ANDROID_FRAMEWORKS_ML_NN_COMMON_NNAPI_IDEVICE_H diff --git a/nn/common/include/nnapi/IPreparedModel.h b/nn/common/include/nnapi/IPreparedModel.h new file mode 100644 index 000000000..07476e276 --- /dev/null +++ b/nn/common/include/nnapi/IPreparedModel.h @@ -0,0 +1,177 @@ +/* + * 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_FRAMEWORKS_ML_NN_COMMON_NNAPI_IPREPARED_MODEL_H +#define ANDROID_FRAMEWORKS_ML_NN_COMMON_NNAPI_IPREPARED_MODEL_H + +#include <any> +#include <functional> +#include <memory> +#include <utility> +#include <vector> + +#include "nnapi/Types.h" + +namespace android::nn { + +// Returns status, timingLaunched, timingFenced +using ExecuteFencedInfoCallback = std::function<GeneralResult<std::pair<Timing, Timing>>()>; + +/** + * IPreparedModel describes a model that has been prepared for execution and is used to launch + * executions. + * + * This interface is thread-safe, and any class that implements this interface must be thread-safe. + */ +class IPreparedModel { + public: + /** + * Performs a synchronous execution on a prepared model. + * + * The execution is performed synchronously with respect to the caller. IPreparedModel::execute + * must verify the inputs to the function are correct. If there is an error, + * IPreparedModel::execute must immediately return {@link ErrorStatus::INVALID_ARGUMENT} as a + * ExecutionError. If the inputs to the function are valid and there is no error, + * IPreparedModel::execute must perform the execution, and must not return until the execution + * is complete. + * + * The caller must not change the content of any data object referenced by request (described by + * the {@link DataLocation} of a {@link RequestArgument}) until IPreparedModel::execute returns. + * IPreparedModel::execute must not change the content of any of the data objects corresponding + * to request inputs. + * + * If the prepared model was prepared from a model wherein all tensor operands have fully + * specified dimensions, and the inputs to the function are valid, and at execution time every + * operation's input operands have legal values, then the execution should complete + * successfully. There must be no failure unless the device itself is in a bad state. + * + * IPreparedModel::execute may be called with an optional deadline. If the execution is not + * able to be completed before the provided deadline, the execution may be aborted, and either + * {@link ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link + * ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned as a ExecutionError. + * + * @param request The input and output information on which the prepared model is to be + * executed. + * @param measure Specifies whether or not to measure duration of the execution. + * @param deadline Optional time point. If provided, execute is expected to complete by this + * time point. If it is not able to be completed by the deadline, the execution may be + * aborted. + * @param loopTimeoutDuration The maximum amount of time that should be spent executing a {@link + * OperationType::WHILE} operation. If a loop condition model does not output `false` within + * this duration, the execution must be aborted. If no loop timeout duration is provided, + * the maximum amount of time is {@link LoopTimeoutDurationNs::DEFAULT}. When provided, the + * duration must not exceed {@link LoopTimeoutDurationNs::MAXIMUM}. + * @return A pair consisting of: + * - A list of shape information of model output operands. The index into "outputShapes" + * corresponds to the index of the output operand in the Request outputs vector. + * outputShapes must be empty unless the execution is successful or the ExecutionResult is + * {@link ErrorStatus::OUTPUT_INSUFFICIENT_SIZE}. outputShapes may be empty if the + * execution is successful and all model output operands are fully-specified at execution + * time. outputShapes must have the same number of elements as the number of model output + * operands if the ExecutionResult is {@link ErrorStatus::OUTPUT_INSUFFICIENT_SIZE}, or if + * the execution is successful and the model has at least one output operand that is not + * fully-specified. + * - Duration of execution. Unless measure is YES and the execution is successful, all times + * must be reported as UINT64_MAX. A driver may choose to report any time as UINT64_MAX, + * indicating that measurement is not available. + */ + virtual ExecutionResult<std::pair<std::vector<OutputShape>, Timing>> execute( + const Request& request, MeasureTiming measure, const OptionalTimePoint& deadline, + const OptionalTimeoutDuration& loopTimeoutDuration) const = 0; + + /** + * Launch a fenced asynchronous execution on a prepared model. + * + * The execution is performed asynchronously with respect to the caller. + * IPreparedModel::executeFenced must verify its inputs are correct, and the usages of memory + * pools allocated by IDevice::allocate are valid. If there is an error, + * IPreparedModel::executeFenced must immediately return {@link ErrorStatus::INVALID_ARGUMENT} + * as a GeneralError. If the inputs to the function are valid and there is no error, + * IPreparedModel::executeFenced must dispatch an asynchronous task to perform the execution in + * the background, and immediately return with a sync fence that will be signaled once the + * execution is completed and a callback that can be used by the client to query the duration + * and runtime error status. If the task has finished before the call returns, an empty handle + * may be returned for syncFence. The execution must wait for all the sync fences (if any) in + * waitFor to be signaled before starting the actual execution. + * + * When the asynchronous task has finished its execution, it must immediately signal the + * syncFence returned from the IPreparedModel::executeFenced call. After the syncFence is + * signaled, the task must not modify the content of any data object referenced by request + * (described by the {@link DataLocation} of a {@link Request::Argument}). + * + * IPreparedModel::executeFenced may be called with an optional deadline and an optional + * duration. If the execution is not able to be completed before the provided deadline or within + * the timeout duration (measured from when all sync fences in waitFor are signaled), whichever + * comes earlier, the execution may be aborted, and either {@link + * ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link ErrorStatus::MISSED_DEADLINE_PERSISTENT} + * may be returned as an GeneralError. The error due to an abort must be sent the same way as + * other errors, described above. + * + * If any of the sync fences in waitFor changes to error status after the + * IPreparedModel::executeFenced call succeeds, or the execution is aborted because it cannot + * finish before the deadline has been reached or the duration has elapsed, the driver must + * immediately set the returned syncFence to error status. + * + * @param request The input and output information on which the prepared model is to be + * executed. + * @param waitFor A vector of sync fence file descriptors. The execution must wait for all sync + * fence to be signaled before starting the task. + * @param measure Specifies whether or not to measure duration of the execution. + * @param deadline The time by which execution is expected to complete. If the execution cannot + * be finished by the deadline, the execution may be aborted. + * @param loopTimeoutDuration The maximum amount of time that should be spent executing a {@link + * OperationType::WHILE} operation. If a loop condition model does not output `false` within + * this duration, the execution must be aborted. If no loop timeout duration is provided, + * the maximum amount of time is {@link LoopTimeoutDurationNs::DEFAULT}. When provided, the + * duration must not exceed {@link LoopTimeoutDurationNs::MAXIMUM}. + * @param timeoutDurationAfterFence The timeout duration within which the execution is expected + * to complete after all sync fences in waitFor are signaled. + * @return A pair consisting of: + * - A syncFence that will be triggered when the task is completed. The syncFence will be + * set to error if critical error occurs when doing actual evaluation. + * - A callback can be used to query information like duration and detailed runtime error + * status when the task is completed. + */ + virtual GeneralResult<std::pair<SyncFence, ExecuteFencedInfoCallback>> executeFenced( + const Request& request, const std::vector<SyncFence>& waitFor, MeasureTiming measure, + const OptionalTimePoint& deadline, const OptionalTimeoutDuration& loopTimeoutDuration, + const OptionalTimeoutDuration& timeoutDurationAfterFence) const = 0; + + /** + * Return the resource that the IPreparedModel wraps, or any empty std::any. + * + * This method is used for IDevice::allocate. + * + * @return std::any containing the underlying resource. + */ + virtual std::any getUnderlyingResource() const = 0; + + // Public virtual destructor to allow objects to be stored (and destroyed) as smart pointers. + // E.g., std::unique_ptr<IPreparedModel>. + virtual ~IPreparedModel() = default; + + protected: + // Protect the non-destructor special member functions to prevent object slicing. + IPreparedModel() = default; + IPreparedModel(const IPreparedModel&) = default; + IPreparedModel(IPreparedModel&&) noexcept = default; + IPreparedModel& operator=(const IPreparedModel&) = default; + IPreparedModel& operator=(IPreparedModel&&) noexcept = default; +}; + +} // namespace android::nn + +#endif // ANDROID_FRAMEWORKS_ML_NN_COMMON_NNAPI_IPREPARED_MODEL_H diff --git a/nn/common/include/nnapi/Result.h b/nn/common/include/nnapi/Result.h index e232cef3f..157eb7845 100644 --- a/nn/common/include/nnapi/Result.h +++ b/nn/common/include/nnapi/Result.h @@ -22,6 +22,7 @@ #include <optional> #include <sstream> #include <string> +#include <tuple> #include <utility> namespace android::nn { @@ -38,11 +39,19 @@ using Result = base::expected<Type, std::string>; namespace detail { +template <typename... Ts> class ErrorBuilder { public: + template <typename... Us> + explicit ErrorBuilder(Us&&... args) : mArgs(std::forward<Us>(args)...) {} + template <typename T, typename E> - operator base::expected<T, E>() const /* NOLINT(google-explicit-constructor) */ { - return base::unexpected<E>(std::move(mStream).str()); + operator base::expected<T, E>() /* NOLINT(google-explicit-constructor) */ { + return std::apply( + [this](Ts&&... args) { + return base::unexpected<E>(E{std::move(mStream).str(), std::move(args)...}); + }, + std::move(mArgs)); } template <typename T> @@ -52,6 +61,7 @@ class ErrorBuilder { } private: + std::tuple<Ts...> mArgs; std::ostringstream mStream; }; @@ -60,8 +70,9 @@ class ErrorBuilder { /** * Creates an error builder for the case where no arguments are provided. */ -inline detail::ErrorBuilder error() { - return detail::ErrorBuilder(); +template <typename... Types> +inline detail::ErrorBuilder<std::decay_t<Types>...> error(Types&&... args) { + return detail::ErrorBuilder<std::decay_t<Types>...>(std::forward<Types>(args)...); } /** @@ -80,7 +91,7 @@ inline detail::ErrorBuilder error() { * return <regular_return_value>; */ #define NN_ERROR(...) \ - [] { \ + [&] { \ using ::android::nn::error; \ return error(__VA_ARGS__) << __FILE__ << ":" << __LINE__ << ": "; \ }() diff --git a/nn/common/include/nnapi/SharedMemory.h b/nn/common/include/nnapi/SharedMemory.h index ee8330f45..1c8156b2a 100644 --- a/nn/common/include/nnapi/SharedMemory.h +++ b/nn/common/include/nnapi/SharedMemory.h @@ -43,7 +43,7 @@ class MutableMemoryBuilder { DataLocation append(size_t length); bool empty() const; - Result<Memory> finish(); + GeneralResult<Memory> finish(); private: uint32_t mPoolIndex; @@ -57,7 +57,7 @@ class ConstantMemoryBuilder { DataLocation append(const void* data, size_t length); bool empty() const; - Result<Memory> finish(); + GeneralResult<Memory> finish(); private: struct LazyCopy { @@ -70,13 +70,13 @@ class ConstantMemoryBuilder { std::vector<LazyCopy> mSlices; }; -Result<Memory> createSharedMemory(size_t size); +GeneralResult<Memory> createSharedMemory(size_t size); -Result<Memory> createSharedMemoryFromFd(size_t size, int prot, int fd, size_t offset); +GeneralResult<Memory> createSharedMemoryFromFd(size_t size, int prot, int fd, size_t offset); -Result<Memory> createSharedMemoryFromHidlMemory(const hardware::hidl_memory& memory); +GeneralResult<Memory> createSharedMemoryFromHidlMemory(const hardware::hidl_memory& memory); -Result<Memory> createSharedMemoryFromAHWB(const AHardwareBuffer& ahwb); +GeneralResult<Memory> createSharedMemoryFromAHWB(const AHardwareBuffer& ahwb); struct Mapping { std::variant<void*, const void*> pointer; @@ -84,7 +84,7 @@ struct Mapping { std::any context; }; -Result<Mapping> map(const Memory& memory); +GeneralResult<Mapping> map(const Memory& memory); bool flush(const Mapping& mapping); diff --git a/nn/common/include/nnapi/TypeUtils.h b/nn/common/include/nnapi/TypeUtils.h index 56b62f9b2..6b2af916f 100644 --- a/nn/common/include/nnapi/TypeUtils.h +++ b/nn/common/include/nnapi/TypeUtils.h @@ -100,6 +100,7 @@ std::ostream& operator<<(std::ostream& os, const BufferRole& bufferRole); std::ostream& operator<<(std::ostream& os, const Request::Argument& requestArgument); std::ostream& operator<<(std::ostream& os, const Request::MemoryPool& memoryPool); std::ostream& operator<<(std::ostream& os, const Request& request); +std::ostream& operator<<(std::ostream& os, const SyncFence::FenceState& fenceState); std::ostream& operator<<(std::ostream& os, const TimePoint& timePoint); std::ostream& operator<<(std::ostream& os, const OptionalTimePoint& optionalTimePoint); std::ostream& operator<<(std::ostream& os, const TimeoutDuration& timeoutDuration); diff --git a/nn/common/include/nnapi/Types.h b/nn/common/include/nnapi/Types.h index 371b4a5c2..3b9725d27 100644 --- a/nn/common/include/nnapi/Types.h +++ b/nn/common/include/nnapi/Types.h @@ -17,6 +17,7 @@ #ifndef ANDROID_FRAMEWORKS_ML_NN_COMMON_NNAPI_TYPES_H #define ANDROID_FRAMEWORKS_ML_NN_COMMON_NNAPI_TYPES_H +#include <android-base/expected.h> #include <utils/NativeHandle.h> #include <utils/StrongPointer.h> @@ -57,7 +58,7 @@ constexpr uint8_t kExtensionPrefixBits = 16; using AlignedData = std::max_align_t; using SharedBuffer = std::shared_ptr<const IBuffer>; using SharedDevice = std::shared_ptr<const IDevice>; -using PreparedModel = std::shared_ptr<const IPreparedModel>; +using SharedPreparedModel = std::shared_ptr<const IPreparedModel>; // Canonical types @@ -116,6 +117,14 @@ enum class ErrorStatus { DEAD_OBJECT = 10000, }; +struct GeneralError { + std::string message; + ErrorStatus code = ErrorStatus::GENERAL_FAILURE; +}; + +template <typename Type> +using GeneralResult = base::expected<Type, GeneralError>; + enum class FusedActivationFunc : int32_t { NONE = 0, RELU = 1, @@ -133,6 +142,16 @@ struct OutputShape { bool isSufficient = false; }; +struct ExecutionError { + std::string message; + ErrorStatus code = ErrorStatus::GENERAL_FAILURE; + // OutputShapes for code == OUTPUT_INSUFFICIENT_SIZE + std::vector<OutputShape> outputShapes = {}; +}; + +template <typename Type> +using ExecutionResult = base::expected<Type, ExecutionError>; + struct Timing { uint64_t timeOnDevice = kNoTiming; uint64_t timeInDriver = kNoTiming; @@ -290,6 +309,34 @@ struct Request { std::vector<MemoryPool> pools; }; +// Representation of sync_fence. +class SyncFence { + public: + static SyncFence createAsSignaled(); + static Result<SyncFence> create(NativeHandle syncFence); + + // The function syncWait() has the same semantics as the system function + // ::sync_wait(), except that the syncWait() return value is semantically + // richer. + enum class FenceState { + ACTIVE, // fence has not been signaled + SIGNALED, // fence has been signaled + ERROR, // fence has been placed in the error state + UNKNOWN, // either bad argument passed to syncWait(), or internal error + }; + using Timeout = std::chrono::duration<int, std::milli>; + using OptionalTimeout = std::optional<Timeout>; + + FenceState syncWait(OptionalTimeout optionalTimeout) const; + + NativeHandle getHandle() const; + + private: + explicit SyncFence(NativeHandle syncFence); + + NativeHandle mSyncFence; +}; + using Clock = std::chrono::steady_clock; using TimePoint = std::chrono::time_point<Clock, std::chrono::nanoseconds>; diff --git a/nn/common/include/nnapi/Validation.h b/nn/common/include/nnapi/Validation.h index 09133957b..3eda174f6 100644 --- a/nn/common/include/nnapi/Validation.h +++ b/nn/common/include/nnapi/Validation.h @@ -69,10 +69,9 @@ using PreparedModelRole = std::tuple<const IPreparedModel*, IOType, uint32_t>; // IMPORTANT: This function cannot validate dimensions and extraParams with extension operand type. // Each driver should do their own validation of extension type dimensions and extraParams. Result<Version> validateMemoryDesc( - const BufferDesc& desc, - const std::vector<std::shared_ptr<const IPreparedModel>>& preparedModels, + const BufferDesc& desc, const std::vector<SharedPreparedModel>& preparedModels, const std::vector<BufferRole>& inputRoles, const std::vector<BufferRole>& outputRoles, - const std::function<const Model*(const std::shared_ptr<const IPreparedModel>&)>& getModel, + const std::function<const Model*(const SharedPreparedModel&)>& getModel, std::set<PreparedModelRole>* preparedModelRoles, Operand* combinedOperand); Result<void> validateOperandSymmPerChannelQuantParams( |