Merge changes from topic "nnapi-reusable-execution-hal"

* changes:
  Reusable execution at HAL level -- HAL.
  Define AIDL reusable execution interface.
This commit is contained in:
Xusong Wang
2022-01-18 21:44:51 +00:00
committed by Gerrit Code Review
27 changed files with 1163 additions and 172 deletions

View File

@@ -411,7 +411,7 @@
</hal> </hal>
<hal format="aidl" optional="true"> <hal format="aidl" optional="true">
<name>android.hardware.neuralnetworks</name> <name>android.hardware.neuralnetworks</name>
<version>1-3</version> <version>1-4</version>
<interface> <interface>
<name>IDevice</name> <name>IDevice</name>
<regex-instance>.*</regex-instance> <regex-instance>.*</regex-instance>

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2021 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.
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.neuralnetworks;
@VintfStability
interface IExecution {
android.hardware.neuralnetworks.ExecutionResult executeSynchronously(in long deadlineNs);
android.hardware.neuralnetworks.FencedExecutionResult executeFenced(in ParcelFileDescriptor[] waitFor, in long deadlineNs, in long durationNs);
}

View File

@@ -37,6 +37,7 @@ interface IPreparedModel {
android.hardware.neuralnetworks.ExecutionResult executeSynchronously(in android.hardware.neuralnetworks.Request request, in boolean measureTiming, in long deadlineNs, in long loopTimeoutDurationNs); android.hardware.neuralnetworks.ExecutionResult executeSynchronously(in android.hardware.neuralnetworks.Request request, in boolean measureTiming, in long deadlineNs, in long loopTimeoutDurationNs);
android.hardware.neuralnetworks.FencedExecutionResult executeFenced(in android.hardware.neuralnetworks.Request request, in ParcelFileDescriptor[] waitFor, in boolean measureTiming, in long deadlineNs, in long loopTimeoutDurationNs, in long durationNs); android.hardware.neuralnetworks.FencedExecutionResult executeFenced(in android.hardware.neuralnetworks.Request request, in ParcelFileDescriptor[] waitFor, in boolean measureTiming, in long deadlineNs, in long loopTimeoutDurationNs, in long durationNs);
android.hardware.neuralnetworks.IBurst configureExecutionBurst(); android.hardware.neuralnetworks.IBurst configureExecutionBurst();
android.hardware.neuralnetworks.IExecution createReusableExecution(in android.hardware.neuralnetworks.Request request, in boolean measureTiming, in long loopTimeoutDurationNs);
const long DEFAULT_LOOP_TIMEOUT_DURATION_NS = 2000000000; const long DEFAULT_LOOP_TIMEOUT_DURATION_NS = 2000000000;
const long MAXIMUM_LOOP_TIMEOUT_DURATION_NS = 15000000000; const long MAXIMUM_LOOP_TIMEOUT_DURATION_NS = 15000000000;
} }

View File

@@ -78,10 +78,10 @@ interface IBurst {
* runs from the time the driver sees the call to the executeSynchronously * runs from the time the driver sees the call to the executeSynchronously
* function to the time the driver returns from the function. * function to the time the driver returns from the function.
* @param deadlineNs The time by which the execution is expected to complete. The time is * @param deadlineNs The time by which the execution is expected to complete. The time is
* measured in nanoseconds since epoch of the steady clock (as from * measured in nanoseconds since boot (as from clock_gettime(CLOCK_BOOTTIME,
* std::chrono::steady_clock). If the execution cannot be finished by the * &ts) or ::android::base::boot_clock). If the execution cannot be finished
* deadline, the execution may be aborted. Passing -1 means the deadline is * by the deadline, the execution may be aborted. Passing -1 means the
* omitted. Other negative values are invalid. * deadline is omitted. Other negative values are invalid.
* @param loopTimeoutDurationNs The maximum amount of time in nanoseconds that should be spent * @param loopTimeoutDurationNs The maximum amount of time in nanoseconds that should be spent
* executing a {@link OperationType::WHILE} operation. If a loop * executing a {@link OperationType::WHILE} operation. If a loop
* condition model does not output false within this duration, the * condition model does not output false within this duration, the

View File

@@ -0,0 +1,153 @@
/*
* Copyright (C) 2021 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.
*/
package android.hardware.neuralnetworks;
import android.hardware.neuralnetworks.ExecutionResult;
import android.hardware.neuralnetworks.FencedExecutionResult;
/**
* IExecution represents a reusable execution object with request and most other execution
* properties fixed. It is used to launch executions.
*
* At most one execution may occur on a reusable execution object at any given time, either by
* means of executeSynchronously or executeFenced.
*
* An IExecution object is used to control a set of executions on the same prepared model with
* the same request and properties. IExecution objects enable some optimizations:
* (1) An IExecution object can preserve resources between executions. For example, a driver can
* map a memory object when the IExecution object is created and cache the mapping for reuse in
* subsequent executions. Any cached resource can be released when the IExecution object is
* destroyed.
* (2) Because an IExecution object may be used for at most one execution at a time, any transient
* execution resources such as intermediate tensors can be allocated once when the IExecution
* object is created and freed when the IExecution object is destroyed.
* (3) An IExecution object is created for a fixed request. This enables the implementation to apply
* request-specific optimizations. For example, an implementation can avoid request validation
* and conversions when the IExecution object is reused. An implementation may also choose to
* specialize the dynamic tensor shapes in the IExecution object according to the request.
*/
@VintfStability
interface IExecution {
/**
* Performs a synchronous execution on the reusable execution object.
*
* The execution is performed synchronously with respect to the caller. executeSynchronously
* must verify the inputs to the function are correct, and the usages of memory pools allocated
* by IDevice::allocate are valid. If there is an error, executeSynchronously must immediately
* return a service specific exception with the appropriate ErrorStatus value. If the inputs to
* the function are valid and there is no error, executeSynchronously 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 the 'request'
* provided in {@link IPreparedModel::createReusableExecution} (described by the
* {@link DataLocation} of a {@link RequestArgument}) until executeSynchronously returns.
* executeSynchronously must not change the content of any of the data objects corresponding to
* 'request' inputs.
*
* If the execution object was configured from a prepared 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.
*
* If the execution object was created with measureTiming being true and the execution is
* successful, the driver may report the timing information in the returning
* {@link ExecutionResult}. The duration runs from the time the driver sees the call to the time
* the driver returns from the function.
*
* executeSynchronously 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. The error due to an abort must be
* sent the same way as other errors, described above.
*
* @param deadlineNs The time by which the execution is expected to complete. The time is
* measured in nanoseconds since boot (as from clock_gettime(CLOCK_BOOTTIME,
* &ts) or ::android::base::boot_clock). If the execution cannot be finished
* by the deadline, the execution may be aborted. Passing -1 means the
* deadline is omitted. Other negative values are invalid.
* @return ExecutionResult parcelable, containing the status of the execution, output shapes
* and timing information.
* @throws ServiceSpecificException with one of the following ErrorStatus values:
* - DEVICE_UNAVAILABLE if driver is offline or busy
* - GENERAL_FAILURE if there is an unspecified error
* - INVALID_ARGUMENT if one of the input arguments is invalid
* - MISSED_DEADLINE_* if the execution is aborted because it cannot be completed by the
* deadline
* - RESOURCE_EXHAUSTED_* if the task was aborted by the driver
*/
ExecutionResult executeSynchronously(in long deadlineNs);
/**
* Launch a fenced asynchronous execution on the reusable execution object.
*
* The execution is performed asynchronously with respect to the caller. executeFenced must
* verify the inputs to the function are correct, and the usages of memory pools allocated by
* IDevice::allocate are valid. If there is an error, executeFenced must immediately return a
* service specific exception with the corresponding ErrorStatus. If the inputs to the function
* are valid and there is no error, executeFenced must dispatch an asynchronous task to perform
* the execution in the background, and immediately return a {@link FencedExecutionResult}
* containing two fields: a callback (which can be used by the client to query the duration and
* runtime error status) and a sync fence (which will be signaled once the execution is
* completed). If the task has finished before the call returns, syncFence file descriptor may
* be set to -1. 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 executeFenced call. After the syncFence is signaled, the task
* must not modify the content of any data object referenced by the 'request' provided in
* IPreparedModel::createReusableExecution (described by the {@link DataLocation} of a
* {@link RequestArgument}).
*
* 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. 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 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 waitFor A vector of sync fence file descriptors. Execution must not start until all
* sync fences have been signaled.
* @param deadlineNs The time by which the execution is expected to complete. The time is
* measured in nanoseconds since boot (as from clock_gettime(CLOCK_BOOTTIME,
* &ts) or ::android::base::boot_clock). If the execution cannot be finished
* by the deadline, the execution may be aborted. Passing -1 means the
* deadline is omitted. Other negative values are invalid.
* @param durationNs The length of time in nanoseconds within which the execution is expected
* to complete after all sync fences in waitFor are signaled. If the
* execution cannot be finished within the duration, the execution may be
* aborted. Passing -1 means the duration is omitted. Other negative values
* are invalid.
* @return The FencedExecutionResult parcelable, containing IFencedExecutionCallback and the
* sync fence.
* @throws ServiceSpecificException with one of the following ErrorStatus values:
* - DEVICE_UNAVAILABLE if driver is offline or busy
* - GENERAL_FAILURE if there is an unspecified error
* - INVALID_ARGUMENT if one of the input arguments is invalid, including fences in error
* states.
* - MISSED_DEADLINE_* if the execution is aborted because it cannot be completed by the
* deadline
* - RESOURCE_EXHAUSTED_* if the task was aborted by the driver
*/
FencedExecutionResult executeFenced(
in ParcelFileDescriptor[] waitFor, in long deadlineNs, in long durationNs);
}

View File

@@ -21,6 +21,7 @@ import android.hardware.neuralnetworks.ErrorStatus;
import android.hardware.neuralnetworks.ExecutionResult; import android.hardware.neuralnetworks.ExecutionResult;
import android.hardware.neuralnetworks.FencedExecutionResult; import android.hardware.neuralnetworks.FencedExecutionResult;
import android.hardware.neuralnetworks.IBurst; import android.hardware.neuralnetworks.IBurst;
import android.hardware.neuralnetworks.IExecution;
import android.hardware.neuralnetworks.Request; import android.hardware.neuralnetworks.Request;
/** /**
@@ -105,11 +106,12 @@ interface IPreparedModel {
* IDevice::allocate are valid. If there is an error, executeFenced must immediately return a * IDevice::allocate are valid. If there is an error, executeFenced must immediately return a
* service specific exception with the corresponding ErrorStatus. If the inputs to the function * service specific exception with the corresponding ErrorStatus. If the inputs to the function
* are valid and there is no error, executeFenced must dispatch an asynchronous task to perform * are valid and there is no error, executeFenced must dispatch an asynchronous task to perform
* the execution in the background, assign a sync fence that will be signaled once the execution * the execution in the background, and immediately return a {@link FencedExecutionResult}
* is completed and immediately return a callback that can be used by the client to query the * containing two fields: a callback (which can be used by the client to query the duration and
* duration and runtime error status. If the task has finished before the call returns, * runtime error status) and a sync fence (which will be signaled once the execution is
* syncFence file descriptor may be set to -1. The execution must wait for all the sync fences * completed). If the task has finished before the call returns, syncFence file descriptor may
* (if any) in waitFor to be signaled before starting the actual execution. * be set to -1. 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 * When the asynchronous task has finished its execution, it must immediately signal the
* syncFence returned from the executeFenced call. After the syncFence is signaled, the task * syncFence returned from the executeFenced call. After the syncFence is signaled, the task
@@ -186,4 +188,36 @@ interface IPreparedModel {
* - RESOURCE_EXHAUSTED_* if the task was aborted by the driver * - RESOURCE_EXHAUSTED_* if the task was aborted by the driver
*/ */
IBurst configureExecutionBurst(); IBurst configureExecutionBurst();
/**
* Create a reusable execution object to launch multiple executions with the same request and
* properties.
*
* createReusableExecution must verify the inputs to the function are correct, and the usages of
* memory pools allocated by IDevice::allocate are valid. If there is an error,
* createReusableExecution must immediately return a service specific exception with the
* appropriate ErrorStatus value. If the inputs to the function are valid and there is no error,
* createReusableExecution must construct a reusable execution.
*
* @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 loopTimeoutDurationNs The maximum amount of time in nanoseconds that should be spent
* executing a {@link OperationType::WHILE} operation. If a loop
* condition model does not output false within this duration, the
* computation performed on the returned reusable execution object
* must be aborted. If -1 is provided, the maximum amount
* of time is {@link DEFAULT_LOOP_TIMEOUT_DURATION_NS}. Other
* negative values are invalid. When provided, the duration must
* not exceed {@link MAXIMUM_LOOP_TIMEOUT_DURATION_NS}.
* @return execution An IExecution object representing a reusable execution that has been
* specialized for a fixed request.
* @throws ServiceSpecificException with one of the following ErrorStatus values:
* - DEVICE_UNAVAILABLE if driver is offline or busy
* - GENERAL_FAILURE if there is an unspecified error
* - INVALID_ARGUMENT if one of the input arguments is invalid
* - RESOURCE_EXHAUSTED_* if the task was aborted by the driver
*/
IExecution createReusableExecution(
in Request request, in boolean measureTiming, in long loopTimeoutDurationNs);
} }

View File

@@ -26,7 +26,14 @@ package {
cc_defaults { cc_defaults {
name: "neuralnetworks_utils_hal_aidl_defaults", name: "neuralnetworks_utils_hal_aidl_defaults",
defaults: ["neuralnetworks_utils_defaults"], defaults: ["neuralnetworks_utils_defaults"],
srcs: ["src/*"], srcs: [
// AIDL utils that a driver may depend on.
"src/BufferTracker.cpp",
"src/Conversions.cpp",
"src/HalUtils.cpp",
"src/Utils.cpp",
"src/ValidateHal.cpp",
],
local_include_dirs: ["include/nnapi/hal/aidl/"], local_include_dirs: ["include/nnapi/hal/aidl/"],
export_include_dirs: ["include"], export_include_dirs: ["include"],
cflags: ["-Wthread-safety"], cflags: ["-Wthread-safety"],
@@ -47,6 +54,7 @@ cc_defaults {
}, },
} }
// Deprecated. Remove once all modules depending on this are migrated away.
cc_library_static { cc_library_static {
name: "neuralnetworks_utils_hal_aidl_v1", name: "neuralnetworks_utils_hal_aidl_v1",
defaults: ["neuralnetworks_utils_hal_aidl_defaults"], defaults: ["neuralnetworks_utils_hal_aidl_defaults"],
@@ -55,20 +63,26 @@ cc_library_static {
], ],
} }
cc_library_static {
name: "neuralnetworks_utils_hal_aidl_v2",
defaults: ["neuralnetworks_utils_hal_aidl_defaults"],
shared_libs: [
"android.hardware.neuralnetworks-V2-ndk",
],
}
cc_library_static { cc_library_static {
name: "neuralnetworks_utils_hal_aidl", name: "neuralnetworks_utils_hal_aidl",
defaults: ["neuralnetworks_utils_hal_aidl_defaults"], defaults: ["neuralnetworks_utils_hal_aidl_defaults"],
shared_libs: [ srcs: [
"android.hardware.neuralnetworks-V3-ndk", // Additional AIDL utils for the runtime.
"src/Assertions.cpp",
"src/Buffer.cpp",
"src/Burst.cpp",
"src/Callbacks.cpp",
"src/Device.cpp",
"src/Execution.cpp",
"src/InvalidDevice.cpp",
"src/PreparedModel.cpp",
"src/ProtectCallback.cpp",
"src/Service.cpp",
], ],
shared_libs: [
"android.hardware.neuralnetworks-V4-ndk",
],
cflags: ["-DNN_AIDL_V4_OR_ABOVE"],
} }
// A cc_defaults that includes the latest non-experimental AIDL utilities and other AIDL libraries // A cc_defaults that includes the latest non-experimental AIDL utilities and other AIDL libraries
@@ -79,9 +93,10 @@ cc_defaults {
static_libs: [ static_libs: [
"android.hardware.common-V2-ndk", "android.hardware.common-V2-ndk",
"android.hardware.graphics.common-V2-ndk", "android.hardware.graphics.common-V2-ndk",
"android.hardware.neuralnetworks-V3-ndk", "android.hardware.neuralnetworks-V4-ndk",
"neuralnetworks_utils_hal_aidl", "neuralnetworks_utils_hal_aidl",
], ],
cflags: ["-DNN_AIDL_V4_OR_ABOVE"],
} }
cc_test { cc_test {

View File

@@ -36,6 +36,8 @@ class PreparedModelCallback final : public BnPreparedModelCallback, public IProt
public: public:
using Data = nn::GeneralResult<nn::SharedPreparedModel>; using Data = nn::GeneralResult<nn::SharedPreparedModel>;
PreparedModelCallback(nn::Version featureLevel) : kFeatureLevel(featureLevel) {}
ndk::ScopedAStatus notify(ErrorStatus status, ndk::ScopedAStatus notify(ErrorStatus status,
const std::shared_ptr<IPreparedModel>& preparedModel) override; const std::shared_ptr<IPreparedModel>& preparedModel) override;
@@ -44,6 +46,7 @@ class PreparedModelCallback final : public BnPreparedModelCallback, public IProt
Data get(); Data get();
private: private:
const nn::Version kFeatureLevel;
hal::utils::TransferValue<Data> mData; hal::utils::TransferValue<Data> mData;
}; };

View File

@@ -17,6 +17,8 @@
#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_EXECUTION_H #ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_EXECUTION_H
#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_EXECUTION_H #define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_EXECUTION_H
#include <aidl/android/hardware/neuralnetworks/IExecution.h>
#include <nnapi/IExecution.h> #include <nnapi/IExecution.h>
#include <nnapi/Result.h> #include <nnapi/Result.h>
#include <nnapi/Types.h> #include <nnapi/Types.h>
@@ -33,17 +35,22 @@
namespace aidl::android::hardware::neuralnetworks::utils { namespace aidl::android::hardware::neuralnetworks::utils {
class Execution final : public nn::IExecution, public std::enable_shared_from_this<Execution> { // A reusable execution implementation with a cached Request, internally it is still passing the
// request to the driver in every computation.
class ExecutionWithCachedRequest final
: public nn::IExecution,
public std::enable_shared_from_this<ExecutionWithCachedRequest> {
struct PrivateConstructorTag {}; struct PrivateConstructorTag {};
public: public:
static nn::GeneralResult<std::shared_ptr<const Execution>> create( static nn::GeneralResult<std::shared_ptr<const ExecutionWithCachedRequest>> create(
std::shared_ptr<const PreparedModel> preparedModel, Request request, std::shared_ptr<const PreparedModel> preparedModel, Request request,
hal::utils::RequestRelocation relocation, bool measure, int64_t loopTimeoutDuration); hal::utils::RequestRelocation relocation, bool measure, int64_t loopTimeoutDuration);
Execution(PrivateConstructorTag tag, std::shared_ptr<const PreparedModel> preparedModel, ExecutionWithCachedRequest(PrivateConstructorTag tag,
Request request, hal::utils::RequestRelocation relocation, bool measure, std::shared_ptr<const PreparedModel> preparedModel, Request request,
int64_t loopTimeoutDuration); hal::utils::RequestRelocation relocation, bool measure,
int64_t loopTimeoutDuration);
nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> compute( nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> compute(
const nn::OptionalTimePoint& deadline) const override; const nn::OptionalTimePoint& deadline) const override;
@@ -60,6 +67,30 @@ class Execution final : public nn::IExecution, public std::enable_shared_from_th
const int64_t kLoopTimeoutDuration; const int64_t kLoopTimeoutDuration;
}; };
// A reusable execution implementation that is backed by an actual AIDL IExecution object.
class Execution final : public nn::IExecution, public std::enable_shared_from_this<Execution> {
struct PrivateConstructorTag {};
public:
static nn::GeneralResult<std::shared_ptr<const Execution>> create(
std::shared_ptr<aidl_hal::IExecution> execution,
hal::utils::RequestRelocation relocation);
Execution(PrivateConstructorTag tag, std::shared_ptr<aidl_hal::IExecution> execution,
hal::utils::RequestRelocation relocation);
nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> compute(
const nn::OptionalTimePoint& deadline) const override;
nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>> computeFenced(
const std::vector<nn::SyncFence>& waitFor, const nn::OptionalTimePoint& deadline,
const nn::OptionalDuration& timeoutDurationAfterFence) const override;
private:
const std::shared_ptr<aidl_hal::IExecution> kExecution;
const hal::utils::RequestRelocation kRelocation;
};
} // namespace aidl::android::hardware::neuralnetworks::utils } // namespace aidl::android::hardware::neuralnetworks::utils
#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_EXECUTION_H #endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_EXECUTION_H

View File

@@ -61,6 +61,11 @@
#include <aidl/android/hardware/neuralnetworks/SymmPerChannelQuantParams.h> #include <aidl/android/hardware/neuralnetworks/SymmPerChannelQuantParams.h>
#include <aidl/android/hardware/neuralnetworks/Timing.h> #include <aidl/android/hardware/neuralnetworks/Timing.h>
#ifdef NN_AIDL_V4_OR_ABOVE
#include <aidl/android/hardware/neuralnetworks/BnExecution.h>
#include <aidl/android/hardware/neuralnetworks/IExecution.h>
#endif // NN_AIDL_V4_OR_ABOVE
namespace android::nn { namespace android::nn {
namespace aidl_hal = ::aidl::android::hardware::neuralnetworks; namespace aidl_hal = ::aidl::android::hardware::neuralnetworks;

View File

@@ -41,10 +41,11 @@ class PreparedModel final : public nn::IPreparedModel,
public: public:
static nn::GeneralResult<std::shared_ptr<const PreparedModel>> create( static nn::GeneralResult<std::shared_ptr<const PreparedModel>> create(
std::shared_ptr<aidl_hal::IPreparedModel> preparedModel); std::shared_ptr<aidl_hal::IPreparedModel> preparedModel, nn::Version featureLevel);
PreparedModel(PrivateConstructorTag tag, PreparedModel(PrivateConstructorTag tag,
std::shared_ptr<aidl_hal::IPreparedModel> preparedModel); std::shared_ptr<aidl_hal::IPreparedModel> preparedModel,
nn::Version featureLevel);
nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> execute( nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> execute(
const nn::Request& request, nn::MeasureTiming measure, const nn::Request& request, nn::MeasureTiming measure,
@@ -78,6 +79,7 @@ class PreparedModel final : public nn::IPreparedModel,
private: private:
const std::shared_ptr<aidl_hal::IPreparedModel> kPreparedModel; const std::shared_ptr<aidl_hal::IPreparedModel> kPreparedModel;
const nn::Version kFeatureLevel;
}; };
} // namespace aidl::android::hardware::neuralnetworks::utils } // namespace aidl::android::hardware::neuralnetworks::utils

View File

@@ -38,6 +38,8 @@ constexpr std::optional<nn::Version> aidlVersionToCanonicalVersion(int aidlVersi
return nn::kVersionFeatureLevel6; return nn::kVersionFeatureLevel6;
case 3: case 3:
return nn::kVersionFeatureLevel7; return nn::kVersionFeatureLevel7;
case 4:
return nn::kVersionFeatureLevel8;
default: default:
return std::nullopt; return std::nullopt;
} }

View File

@@ -38,16 +38,17 @@ namespace {
// nn::kVersionFeatureLevel5. On failure, this function returns with the appropriate // nn::kVersionFeatureLevel5. On failure, this function returns with the appropriate
// nn::GeneralError. // nn::GeneralError.
nn::GeneralResult<nn::SharedPreparedModel> prepareModelCallback( nn::GeneralResult<nn::SharedPreparedModel> prepareModelCallback(
ErrorStatus status, const std::shared_ptr<IPreparedModel>& preparedModel) { ErrorStatus status, const std::shared_ptr<IPreparedModel>& preparedModel,
nn::Version featureLevel) {
HANDLE_STATUS_AIDL(status) << "model preparation failed with " << toString(status); HANDLE_STATUS_AIDL(status) << "model preparation failed with " << toString(status);
return NN_TRY(PreparedModel::create(preparedModel)); return NN_TRY(PreparedModel::create(preparedModel, featureLevel));
} }
} // namespace } // namespace
ndk::ScopedAStatus PreparedModelCallback::notify( ndk::ScopedAStatus PreparedModelCallback::notify(
ErrorStatus status, const std::shared_ptr<IPreparedModel>& preparedModel) { ErrorStatus status, const std::shared_ptr<IPreparedModel>& preparedModel) {
mData.put(prepareModelCallback(status, preparedModel)); mData.put(prepareModelCallback(status, preparedModel, kFeatureLevel));
return ndk::ScopedAStatus::ok(); return ndk::ScopedAStatus::ok();
} }

View File

@@ -229,7 +229,7 @@ nn::GeneralResult<nn::SharedPreparedModel> Device::prepareModel(
const auto aidlDataCache = NN_TRY(convert(dataCache)); const auto aidlDataCache = NN_TRY(convert(dataCache));
const auto aidlToken = NN_TRY(convert(token)); const auto aidlToken = NN_TRY(convert(token));
const auto cb = ndk::SharedRefBase::make<PreparedModelCallback>(); const auto cb = ndk::SharedRefBase::make<PreparedModelCallback>(kFeatureLevel);
const auto scoped = kDeathHandler.protectCallback(cb.get()); const auto scoped = kDeathHandler.protectCallback(cb.get());
const auto ret = kDevice->prepareModel(aidlModel, aidlPreference, aidlPriority, aidlDeadline, const auto ret = kDevice->prepareModel(aidlModel, aidlPreference, aidlPriority, aidlDeadline,
@@ -247,7 +247,7 @@ nn::GeneralResult<nn::SharedPreparedModel> Device::prepareModelFromCache(
const auto aidlDataCache = NN_TRY(convert(dataCache)); const auto aidlDataCache = NN_TRY(convert(dataCache));
const auto aidlToken = NN_TRY(convert(token)); const auto aidlToken = NN_TRY(convert(token));
const auto cb = ndk::SharedRefBase::make<PreparedModelCallback>(); const auto cb = ndk::SharedRefBase::make<PreparedModelCallback>(kFeatureLevel);
const auto scoped = kDeathHandler.protectCallback(cb.get()); const auto scoped = kDeathHandler.protectCallback(cb.get());
const auto ret = kDevice->prepareModelFromCache(aidlDeadline, aidlModelCache, aidlDataCache, const auto ret = kDevice->prepareModelFromCache(aidlDeadline, aidlModelCache, aidlDataCache,

View File

@@ -35,36 +35,39 @@
namespace aidl::android::hardware::neuralnetworks::utils { namespace aidl::android::hardware::neuralnetworks::utils {
nn::GeneralResult<std::shared_ptr<const Execution>> Execution::create( nn::GeneralResult<std::shared_ptr<const ExecutionWithCachedRequest>>
std::shared_ptr<const PreparedModel> preparedModel, Request request, ExecutionWithCachedRequest::create(std::shared_ptr<const PreparedModel> preparedModel,
hal::utils::RequestRelocation relocation, bool measure, int64_t loopTimeoutDuration) { Request request, hal::utils::RequestRelocation relocation,
bool measure, int64_t loopTimeoutDuration) {
if (preparedModel == nullptr) { if (preparedModel == nullptr) {
return NN_ERROR() << "aidl::utils::Execution::create must have non-null preparedModel"; return NN_ERROR() << "aidl::utils::ExecutionWithCachedRequest::create must have non-null "
"preparedModel";
} }
return std::make_shared<const Execution>(PrivateConstructorTag{}, std::move(preparedModel), return std::make_shared<const ExecutionWithCachedRequest>(
std::move(request), std::move(relocation), measure, PrivateConstructorTag{}, std::move(preparedModel), std::move(request),
loopTimeoutDuration); std::move(relocation), measure, loopTimeoutDuration);
} }
Execution::Execution(PrivateConstructorTag /*tag*/, ExecutionWithCachedRequest::ExecutionWithCachedRequest(
std::shared_ptr<const PreparedModel> preparedModel, Request request, PrivateConstructorTag /*tag*/, std::shared_ptr<const PreparedModel> preparedModel,
hal::utils::RequestRelocation relocation, bool measure, Request request, hal::utils::RequestRelocation relocation, bool measure,
int64_t loopTimeoutDuration) int64_t loopTimeoutDuration)
: kPreparedModel(std::move(preparedModel)), : kPreparedModel(std::move(preparedModel)),
kRequest(std::move(request)), kRequest(std::move(request)),
kRelocation(std::move(relocation)), kRelocation(std::move(relocation)),
kMeasure(measure), kMeasure(measure),
kLoopTimeoutDuration(loopTimeoutDuration) {} kLoopTimeoutDuration(loopTimeoutDuration) {}
nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> Execution::compute( nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
const nn::OptionalTimePoint& deadline) const { ExecutionWithCachedRequest::compute(const nn::OptionalTimePoint& deadline) const {
const auto aidlDeadline = NN_TRY(convert(deadline)); const auto aidlDeadline = NN_TRY(convert(deadline));
return kPreparedModel->executeInternal(kRequest, kMeasure, aidlDeadline, kLoopTimeoutDuration, return kPreparedModel->executeInternal(kRequest, kMeasure, aidlDeadline, kLoopTimeoutDuration,
kRelocation); kRelocation);
} }
nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>> Execution::computeFenced( nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>>
ExecutionWithCachedRequest::computeFenced(
const std::vector<nn::SyncFence>& waitFor, const nn::OptionalTimePoint& deadline, const std::vector<nn::SyncFence>& waitFor, const nn::OptionalTimePoint& deadline,
const nn::OptionalDuration& timeoutDurationAfterFence) const { const nn::OptionalDuration& timeoutDurationAfterFence) const {
const auto aidlWaitFor = NN_TRY(convert(waitFor)); const auto aidlWaitFor = NN_TRY(convert(waitFor));
@@ -75,4 +78,18 @@ nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>> Execu
aidlTimeoutDurationAfterFence, kRelocation); aidlTimeoutDurationAfterFence, kRelocation);
} }
nn::GeneralResult<std::shared_ptr<const Execution>> Execution::create(
std::shared_ptr<aidl_hal::IExecution> execution, hal::utils::RequestRelocation relocation) {
if (execution == nullptr) {
return NN_ERROR() << "aidl::utils::Execution::create must have non-null execution";
}
return std::make_shared<const Execution>(PrivateConstructorTag{}, std::move(execution),
std::move(relocation));
}
Execution::Execution(PrivateConstructorTag /*tag*/, std::shared_ptr<aidl_hal::IExecution> execution,
hal::utils::RequestRelocation relocation)
: kExecution(std::move(execution)), kRelocation(std::move(relocation)) {}
} // namespace aidl::android::hardware::neuralnetworks::utils } // namespace aidl::android::hardware::neuralnetworks::utils

View File

@@ -54,21 +54,77 @@ nn::GeneralResult<std::pair<nn::Timing, nn::Timing>> convertFencedExecutionResul
return std::make_pair(NN_TRY(nn::convert(timingLaunched)), NN_TRY(nn::convert(timingFenced))); return std::make_pair(NN_TRY(nn::convert(timingLaunched)), NN_TRY(nn::convert(timingFenced)));
} }
nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> handleExecutionResult(
const ExecutionResult& result, const hal::utils::RequestRelocation& relocation) {
if (!result.outputSufficientSize) {
auto canonicalOutputShapes =
nn::convert(result.outputShapes).value_or(std::vector<nn::OutputShape>{});
return NN_ERROR(nn::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, std::move(canonicalOutputShapes))
<< "execution failed with " << nn::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE;
}
auto [outputShapes, timing] =
NN_TRY(convertExecutionResults(result.outputShapes, result.timing));
if (relocation.output) {
relocation.output->flush();
}
return std::make_pair(std::move(outputShapes), timing);
}
nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>>
handleFencedExecutionResult(const FencedExecutionResult& result,
const hal::utils::RequestRelocation& relocation) {
auto resultSyncFence = nn::SyncFence::createAsSignaled();
if (result.syncFence.get() != -1) {
resultSyncFence = nn::SyncFence::create(NN_TRY(nn::convert(result.syncFence))).value();
}
auto callback = result.callback;
if (callback == nullptr) {
return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "callback is null";
}
// If computeFenced required the request memory to be moved into shared memory, block here until
// the fenced execution has completed and flush the memory back.
if (relocation.output) {
const auto state = resultSyncFence.syncWait({});
if (state != nn::SyncFence::FenceState::SIGNALED) {
return NN_ERROR() << "syncWait failed with " << state;
}
relocation.output->flush();
}
// Create callback which can be used to retrieve the execution error status and timings.
nn::ExecuteFencedInfoCallback resultCallback =
[callback]() -> nn::GeneralResult<std::pair<nn::Timing, nn::Timing>> {
ErrorStatus errorStatus;
Timing timingLaunched;
Timing timingFenced;
const auto ret = callback->getExecutionInfo(&timingLaunched, &timingFenced, &errorStatus);
HANDLE_ASTATUS(ret) << "fenced execution callback getExecutionInfo failed";
return convertFencedExecutionResults(errorStatus, timingLaunched, timingFenced);
};
return std::make_pair(std::move(resultSyncFence), std::move(resultCallback));
}
} // namespace } // namespace
nn::GeneralResult<std::shared_ptr<const PreparedModel>> PreparedModel::create( nn::GeneralResult<std::shared_ptr<const PreparedModel>> PreparedModel::create(
std::shared_ptr<aidl_hal::IPreparedModel> preparedModel) { std::shared_ptr<aidl_hal::IPreparedModel> preparedModel, nn::Version featureLevel) {
if (preparedModel == nullptr) { if (preparedModel == nullptr) {
return NN_ERROR() return NN_ERROR()
<< "aidl_hal::utils::PreparedModel::create must have non-null preparedModel"; << "aidl_hal::utils::PreparedModel::create must have non-null preparedModel";
} }
return std::make_shared<const PreparedModel>(PrivateConstructorTag{}, std::move(preparedModel)); return std::make_shared<const PreparedModel>(PrivateConstructorTag{}, std::move(preparedModel),
featureLevel);
} }
PreparedModel::PreparedModel(PrivateConstructorTag /*tag*/, PreparedModel::PreparedModel(PrivateConstructorTag /*tag*/,
std::shared_ptr<aidl_hal::IPreparedModel> preparedModel) std::shared_ptr<aidl_hal::IPreparedModel> preparedModel,
: kPreparedModel(std::move(preparedModel)) {} nn::Version featureLevel)
: kPreparedModel(std::move(preparedModel)), kFeatureLevel(featureLevel) {}
nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> PreparedModel::execute( nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> PreparedModel::execute(
const nn::Request& request, nn::MeasureTiming measure, const nn::Request& request, nn::MeasureTiming measure,
@@ -101,19 +157,7 @@ PreparedModel::executeInternal(const Request& request, bool measure, int64_t dea
const auto ret = kPreparedModel->executeSynchronously(request, measure, deadline, const auto ret = kPreparedModel->executeSynchronously(request, measure, deadline,
loopTimeoutDuration, &executionResult); loopTimeoutDuration, &executionResult);
HANDLE_ASTATUS(ret) << "executeSynchronously failed"; HANDLE_ASTATUS(ret) << "executeSynchronously failed";
if (!executionResult.outputSufficientSize) { return handleExecutionResult(executionResult, relocation);
auto canonicalOutputShapes =
nn::convert(executionResult.outputShapes).value_or(std::vector<nn::OutputShape>{});
return NN_ERROR(nn::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, std::move(canonicalOutputShapes))
<< "execution failed with " << nn::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE;
}
auto [outputShapes, timing] =
NN_TRY(convertExecutionResults(executionResult.outputShapes, executionResult.timing));
if (relocation.output) {
relocation.output->flush();
}
return std::make_pair(std::move(outputShapes), timing);
} }
nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>> nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>>
@@ -154,39 +198,7 @@ PreparedModel::executeFencedInternal(const Request& request,
kPreparedModel->executeFenced(request, waitFor, measure, deadline, loopTimeoutDuration, kPreparedModel->executeFenced(request, waitFor, measure, deadline, loopTimeoutDuration,
timeoutDurationAfterFence, &result); timeoutDurationAfterFence, &result);
HANDLE_ASTATUS(ret) << "executeFenced failed"; HANDLE_ASTATUS(ret) << "executeFenced failed";
return handleFencedExecutionResult(result, relocation);
auto resultSyncFence = nn::SyncFence::createAsSignaled();
if (result.syncFence.get() != -1) {
resultSyncFence = nn::SyncFence::create(NN_TRY(nn::convert(result.syncFence))).value();
}
auto callback = result.callback;
if (callback == nullptr) {
return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "callback is null";
}
// If executeFenced required the request memory to be moved into shared memory, block here until
// the fenced execution has completed and flush the memory back.
if (relocation.output) {
const auto state = resultSyncFence.syncWait({});
if (state != nn::SyncFence::FenceState::SIGNALED) {
return NN_ERROR() << "syncWait failed with " << state;
}
relocation.output->flush();
}
// Create callback which can be used to retrieve the execution error status and timings.
nn::ExecuteFencedInfoCallback resultCallback =
[callback]() -> nn::GeneralResult<std::pair<nn::Timing, nn::Timing>> {
ErrorStatus errorStatus;
Timing timingLaunched;
Timing timingFenced;
const auto ret = callback->getExecutionInfo(&timingLaunched, &timingFenced, &errorStatus);
HANDLE_ASTATUS(ret) << "fenced execution callback getExecutionInfo failed";
return convertFencedExecutionResults(errorStatus, timingLaunched, timingFenced);
};
return std::make_pair(std::move(resultSyncFence), std::move(resultCallback));
} }
nn::GeneralResult<nn::SharedExecution> PreparedModel::createReusableExecution( nn::GeneralResult<nn::SharedExecution> PreparedModel::createReusableExecution(
@@ -202,8 +214,18 @@ nn::GeneralResult<nn::SharedExecution> PreparedModel::createReusableExecution(
auto aidlRequest = NN_TRY(convert(requestInShared)); auto aidlRequest = NN_TRY(convert(requestInShared));
auto aidlMeasure = NN_TRY(convert(measure)); auto aidlMeasure = NN_TRY(convert(measure));
auto aidlLoopTimeoutDuration = NN_TRY(convert(loopTimeoutDuration)); auto aidlLoopTimeoutDuration = NN_TRY(convert(loopTimeoutDuration));
return Execution::create(shared_from_this(), std::move(aidlRequest), std::move(relocation),
aidlMeasure, aidlLoopTimeoutDuration); if (kFeatureLevel.level >= nn::Version::Level::FEATURE_LEVEL_8) {
std::shared_ptr<IExecution> execution;
const auto ret = kPreparedModel->createReusableExecution(
aidlRequest, aidlMeasure, aidlLoopTimeoutDuration, &execution);
HANDLE_ASTATUS(ret) << "createReusableExecution failed";
return Execution::create(std::move(execution), std::move(relocation));
}
return ExecutionWithCachedRequest::create(shared_from_this(), std::move(aidlRequest),
std::move(relocation), aidlMeasure,
aidlLoopTimeoutDuration);
} }
nn::GeneralResult<nn::SharedBurst> PreparedModel::configureExecutionBurst() const { nn::GeneralResult<nn::SharedBurst> PreparedModel::configureExecutionBurst() const {
@@ -218,4 +240,36 @@ std::any PreparedModel::getUnderlyingResource() const {
return resource; return resource;
} }
nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> Execution::compute(
const nn::OptionalTimePoint& deadline) const {
const auto aidlDeadline = NN_TRY(convert(deadline));
if (kRelocation.input) {
kRelocation.input->flush();
}
ExecutionResult executionResult;
auto ret = kExecution->executeSynchronously(aidlDeadline, &executionResult);
HANDLE_ASTATUS(ret) << "executeSynchronously failed";
return handleExecutionResult(executionResult, kRelocation);
}
nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>> Execution::computeFenced(
const std::vector<nn::SyncFence>& waitFor, const nn::OptionalTimePoint& deadline,
const nn::OptionalDuration& timeoutDurationAfterFence) const {
const auto aidlWaitFor = NN_TRY(convert(waitFor));
const auto aidlDeadline = NN_TRY(convert(deadline));
const auto aidlTimeoutDurationAfterFence = NN_TRY(convert(timeoutDurationAfterFence));
if (kRelocation.input) {
kRelocation.input->flush();
}
FencedExecutionResult result;
const auto ret = kExecution->executeFenced(aidlWaitFor, aidlDeadline,
aidlTimeoutDurationAfterFence, &result);
HANDLE_ASTATUS(ret) << "executeFenced failed";
return handleFencedExecutionResult(result, kRelocation);
}
} // namespace aidl::android::hardware::neuralnetworks::utils } // namespace aidl::android::hardware::neuralnetworks::utils

View File

@@ -17,6 +17,7 @@
#include "MockBuffer.h" #include "MockBuffer.h"
#include "MockDevice.h" #include "MockDevice.h"
#include "MockPreparedModel.h" #include "MockPreparedModel.h"
#include "TestUtils.h"
#include <aidl/android/hardware/neuralnetworks/BnDevice.h> #include <aidl/android/hardware/neuralnetworks/BnDevice.h>
#include <android/binder_auto_utils.h> #include <android/binder_auto_utils.h>
@@ -146,26 +147,7 @@ constexpr auto makeDeadObjectFailure = [] {
return ndk::ScopedAStatus::fromStatus(STATUS_DEAD_OBJECT); return ndk::ScopedAStatus::fromStatus(STATUS_DEAD_OBJECT);
}; };
class DeviceTest : public ::testing::TestWithParam<nn::Version> { class DeviceTest : public VersionedAidlUtilsTestBase {};
protected:
const nn::Version kVersion = GetParam();
};
std::string printDeviceTest(const testing::TestParamInfo<nn::Version>& info) {
const nn::Version version = info.param;
CHECK(!version.runtimeOnlyFeatures);
switch (version.level) {
case nn::Version::Level::FEATURE_LEVEL_5:
return "v1";
case nn::Version::Level::FEATURE_LEVEL_6:
return "v2";
case nn::Version::Level::FEATURE_LEVEL_7:
return "v3";
default:
LOG(FATAL) << "Invalid AIDL version: " << version;
return "invalid";
}
}
} // namespace } // namespace
@@ -894,9 +876,6 @@ TEST_P(DeviceTest, allocateDeadObject) {
EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
} }
INSTANTIATE_TEST_SUITE_P(TestDevice, DeviceTest, INSTANTIATE_VERSIONED_AIDL_UTILS_TEST(DeviceTest, kAllAidlVersions);
::testing::Values(nn::kVersionFeatureLevel5, nn::kVersionFeatureLevel6,
nn::kVersionFeatureLevel7),
printDeviceTest);
} // namespace aidl::android::hardware::neuralnetworks::utils } // namespace aidl::android::hardware::neuralnetworks::utils

View File

@@ -0,0 +1,254 @@
/*
* Copyright (C) 2021 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 "MockExecution.h"
#include "MockFencedExecutionCallback.h"
#include <aidl/android/hardware/neuralnetworks/IFencedExecutionCallback.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <nnapi/IExecution.h>
#include <nnapi/TypeUtils.h>
#include <nnapi/Types.h>
#include <nnapi/hal/aidl/Execution.h>
#include <functional>
#include <memory>
namespace aidl::android::hardware::neuralnetworks::utils {
namespace {
using ::testing::_;
using ::testing::DoAll;
using ::testing::Invoke;
using ::testing::InvokeWithoutArgs;
using ::testing::SetArgPointee;
const std::shared_ptr<IExecution> kInvalidExecution;
constexpr auto kNoTiming = Timing{.timeOnDeviceNs = -1, .timeInDriverNs = -1};
constexpr auto makeStatusOk = [] { return ndk::ScopedAStatus::ok(); };
constexpr auto makeGeneralFailure = [] {
return ndk::ScopedAStatus::fromServiceSpecificError(
static_cast<int32_t>(ErrorStatus::GENERAL_FAILURE));
};
constexpr auto makeGeneralTransportFailure = [] {
return ndk::ScopedAStatus::fromStatus(STATUS_NO_MEMORY);
};
constexpr auto makeDeadObjectFailure = [] {
return ndk::ScopedAStatus::fromStatus(STATUS_DEAD_OBJECT);
};
auto makeFencedExecutionResult(const std::shared_ptr<MockFencedExecutionCallback>& callback) {
return [callback](const std::vector<ndk::ScopedFileDescriptor>& /*waitFor*/,
int64_t /*deadline*/, int64_t /*duration*/,
FencedExecutionResult* fencedExecutionResult) {
*fencedExecutionResult = FencedExecutionResult{.callback = callback,
.syncFence = ndk::ScopedFileDescriptor(-1)};
return ndk::ScopedAStatus::ok();
};
}
} // namespace
TEST(ExecutionTest, invalidExecution) {
// run test
const auto result = Execution::create(kInvalidExecution, {});
// verify result
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
}
TEST(ExecutionTest, executeSync) {
// setup call
const auto mockExecution = MockExecution::create();
const auto execution = Execution::create(mockExecution, {}).value();
const auto mockExecutionResult = ExecutionResult{
.outputSufficientSize = true,
.outputShapes = {},
.timing = kNoTiming,
};
EXPECT_CALL(*mockExecution, executeSynchronously(_, _))
.Times(1)
.WillOnce(
DoAll(SetArgPointee<1>(mockExecutionResult), InvokeWithoutArgs(makeStatusOk)));
// run test
const auto result = execution->compute({});
// verify result
EXPECT_TRUE(result.has_value())
<< "Failed with " << result.error().code << ": " << result.error().message;
}
TEST(ExecutionTest, executeSyncError) {
// setup test
const auto mockExecution = MockExecution::create();
const auto execution = Execution::create(mockExecution, {}).value();
EXPECT_CALL(*mockExecution, executeSynchronously(_, _))
.Times(1)
.WillOnce(Invoke(makeGeneralFailure));
// run test
const auto result = execution->compute({});
// verify result
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
}
TEST(ExecutionTest, executeSyncTransportFailure) {
// setup test
const auto mockExecution = MockExecution::create();
const auto execution = Execution::create(mockExecution, {}).value();
EXPECT_CALL(*mockExecution, executeSynchronously(_, _))
.Times(1)
.WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
// run test
const auto result = execution->compute({});
// verify result
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
}
TEST(ExecutionTest, executeSyncDeadObject) {
// setup test
const auto mockExecution = MockExecution::create();
const auto execution = Execution::create(mockExecution, {}).value();
EXPECT_CALL(*mockExecution, executeSynchronously(_, _))
.Times(1)
.WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
// run test
const auto result = execution->compute({});
// verify result
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
}
TEST(ExecutionTest, executeFenced) {
// setup call
const auto mockExecution = MockExecution::create();
const auto execution = Execution::create(mockExecution, {}).value();
const auto mockCallback = MockFencedExecutionCallback::create();
EXPECT_CALL(*mockCallback, getExecutionInfo(_, _, _))
.Times(1)
.WillOnce(DoAll(SetArgPointee<0>(kNoTiming), SetArgPointee<1>(kNoTiming),
SetArgPointee<2>(ErrorStatus::NONE), Invoke(makeStatusOk)));
EXPECT_CALL(*mockExecution, executeFenced(_, _, _, _))
.Times(1)
.WillOnce(Invoke(makeFencedExecutionResult(mockCallback)));
// run test
const auto result = execution->computeFenced({}, {}, {});
// verify result
ASSERT_TRUE(result.has_value())
<< "Failed with " << result.error().code << ": " << result.error().message;
const auto& [syncFence, callback] = result.value();
EXPECT_EQ(syncFence.syncWait({}), nn::SyncFence::FenceState::SIGNALED);
ASSERT_NE(callback, nullptr);
// get results from callback
const auto callbackResult = callback();
ASSERT_TRUE(callbackResult.has_value()) << "Failed with " << callbackResult.error().code << ": "
<< callbackResult.error().message;
}
TEST(ExecutionTest, executeFencedCallbackError) {
// setup call
const auto mockExecution = MockExecution::create();
const auto execution = Execution::create(mockExecution, {}).value();
const auto mockCallback = MockFencedExecutionCallback::create();
EXPECT_CALL(*mockCallback, getExecutionInfo(_, _, _))
.Times(1)
.WillOnce(Invoke(DoAll(SetArgPointee<0>(kNoTiming), SetArgPointee<1>(kNoTiming),
SetArgPointee<2>(ErrorStatus::GENERAL_FAILURE),
Invoke(makeStatusOk))));
EXPECT_CALL(*mockExecution, executeFenced(_, _, _, _))
.Times(1)
.WillOnce(Invoke(makeFencedExecutionResult(mockCallback)));
// run test
const auto result = execution->computeFenced({}, {}, {});
// verify result
ASSERT_TRUE(result.has_value())
<< "Failed with " << result.error().code << ": " << result.error().message;
const auto& [syncFence, callback] = result.value();
EXPECT_NE(syncFence.syncWait({}), nn::SyncFence::FenceState::ACTIVE);
ASSERT_NE(callback, nullptr);
// verify callback failure
const auto callbackResult = callback();
ASSERT_FALSE(callbackResult.has_value());
EXPECT_EQ(callbackResult.error().code, nn::ErrorStatus::GENERAL_FAILURE);
}
TEST(ExecutionTest, executeFencedError) {
// setup test
const auto mockExecution = MockExecution::create();
const auto execution = Execution::create(mockExecution, {}).value();
EXPECT_CALL(*mockExecution, executeFenced(_, _, _, _))
.Times(1)
.WillOnce(InvokeWithoutArgs(makeGeneralFailure));
// run test
const auto result = execution->computeFenced({}, {}, {});
// verify result
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
}
TEST(ExecutionTest, executeFencedTransportFailure) {
// setup test
const auto mockExecution = MockExecution::create();
const auto execution = Execution::create(mockExecution, {}).value();
EXPECT_CALL(*mockExecution, executeFenced(_, _, _, _))
.Times(1)
.WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
// run test
const auto result = execution->computeFenced({}, {}, {});
// verify result
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
}
TEST(ExecutionTest, executeFencedDeadObject) {
// setup test
const auto mockExecution = MockExecution::create();
const auto execution = Execution::create(mockExecution, {}).value();
EXPECT_CALL(*mockExecution, executeFenced(_, _, _, _))
.Times(1)
.WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
// run test
const auto result = execution->computeFenced({}, {}, {});
// verify result
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
}
} // namespace aidl::android::hardware::neuralnetworks::utils

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2021 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_AIDL_UTILS_TEST_MOCK_EXECUTION_H
#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_EXECUTION_H
#include <aidl/android/hardware/neuralnetworks/BnExecution.h>
#include <android/binder_interface_utils.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <hidl/HidlSupport.h>
#include <hidl/Status.h>
namespace aidl::android::hardware::neuralnetworks::utils {
class MockExecution final : public BnExecution {
public:
static std::shared_ptr<MockExecution> create();
MOCK_METHOD(ndk::ScopedAStatus, executeSynchronously,
(int64_t deadline, ExecutionResult* executionResult), (override));
MOCK_METHOD(ndk::ScopedAStatus, executeFenced,
(const std::vector<ndk::ScopedFileDescriptor>& waitFor, int64_t deadline,
int64_t duration, FencedExecutionResult* fencedExecutionResult),
(override));
};
inline std::shared_ptr<MockExecution> MockExecution::create() {
return ndk::SharedRefBase::make<MockExecution>();
}
} // namespace aidl::android::hardware::neuralnetworks::utils
#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_EXECUTION_H

View File

@@ -17,6 +17,7 @@
#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_PREPARED_MODEL_H #ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_PREPARED_MODEL_H
#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_PREPARED_MODEL_H #define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_MOCK_PREPARED_MODEL_H
#include <aidl/android/hardware/neuralnetworks/BnExecution.h>
#include <aidl/android/hardware/neuralnetworks/BnPreparedModel.h> #include <aidl/android/hardware/neuralnetworks/BnPreparedModel.h>
#include <android/binder_interface_utils.h> #include <android/binder_interface_utils.h>
#include <gmock/gmock.h> #include <gmock/gmock.h>
@@ -41,6 +42,10 @@ class MockPreparedModel final : public BnPreparedModel {
(override)); (override));
MOCK_METHOD(ndk::ScopedAStatus, configureExecutionBurst, (std::shared_ptr<IBurst> * burst), MOCK_METHOD(ndk::ScopedAStatus, configureExecutionBurst, (std::shared_ptr<IBurst> * burst),
(override)); (override));
MOCK_METHOD(ndk::ScopedAStatus, createReusableExecution,
(const Request& request, bool measureTiming, int64_t loopTimeoutDuration,
std::shared_ptr<IExecution>* execution),
(override));
}; };
inline std::shared_ptr<MockPreparedModel> MockPreparedModel::create() { inline std::shared_ptr<MockPreparedModel> MockPreparedModel::create() {

View File

@@ -15,8 +15,10 @@
*/ */
#include "MockBurst.h" #include "MockBurst.h"
#include "MockExecution.h"
#include "MockFencedExecutionCallback.h" #include "MockFencedExecutionCallback.h"
#include "MockPreparedModel.h" #include "MockPreparedModel.h"
#include "TestUtils.h"
#include <aidl/android/hardware/neuralnetworks/IFencedExecutionCallback.h> #include <aidl/android/hardware/neuralnetworks/IFencedExecutionCallback.h>
#include <gmock/gmock.h> #include <gmock/gmock.h>
@@ -66,21 +68,23 @@ auto makeFencedExecutionResult(const std::shared_ptr<MockFencedExecutionCallback
}; };
} }
class PreparedModelTest : public VersionedAidlUtilsTestBase {};
} // namespace } // namespace
TEST(PreparedModelTest, invalidPreparedModel) { TEST_P(PreparedModelTest, invalidPreparedModel) {
// run test // run test
const auto result = PreparedModel::create(kInvalidPreparedModel); const auto result = PreparedModel::create(kInvalidPreparedModel, kVersion);
// verify result // verify result
ASSERT_FALSE(result.has_value()); ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
} }
TEST(PreparedModelTest, executeSync) { TEST_P(PreparedModelTest, executeSync) {
// setup call // setup call
const auto mockPreparedModel = MockPreparedModel::create(); const auto mockPreparedModel = MockPreparedModel::create();
const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
const auto mockExecutionResult = ExecutionResult{ const auto mockExecutionResult = ExecutionResult{
.outputSufficientSize = true, .outputSufficientSize = true,
.outputShapes = {}, .outputShapes = {},
@@ -99,10 +103,10 @@ TEST(PreparedModelTest, executeSync) {
<< "Failed with " << result.error().code << ": " << result.error().message; << "Failed with " << result.error().code << ": " << result.error().message;
} }
TEST(PreparedModelTest, executeSyncError) { TEST_P(PreparedModelTest, executeSyncError) {
// setup test // setup test
const auto mockPreparedModel = MockPreparedModel::create(); const auto mockPreparedModel = MockPreparedModel::create();
const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _, _, _)) EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _, _, _))
.Times(1) .Times(1)
.WillOnce(Invoke(makeGeneralFailure)); .WillOnce(Invoke(makeGeneralFailure));
@@ -115,10 +119,10 @@ TEST(PreparedModelTest, executeSyncError) {
EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
} }
TEST(PreparedModelTest, executeSyncTransportFailure) { TEST_P(PreparedModelTest, executeSyncTransportFailure) {
// setup test // setup test
const auto mockPreparedModel = MockPreparedModel::create(); const auto mockPreparedModel = MockPreparedModel::create();
const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _, _, _)) EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _, _, _))
.Times(1) .Times(1)
.WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
@@ -131,10 +135,10 @@ TEST(PreparedModelTest, executeSyncTransportFailure) {
EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
} }
TEST(PreparedModelTest, executeSyncDeadObject) { TEST_P(PreparedModelTest, executeSyncDeadObject) {
// setup test // setup test
const auto mockPreparedModel = MockPreparedModel::create(); const auto mockPreparedModel = MockPreparedModel::create();
const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _, _, _)) EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _, _, _))
.Times(1) .Times(1)
.WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
@@ -147,10 +151,10 @@ TEST(PreparedModelTest, executeSyncDeadObject) {
EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
} }
TEST(PreparedModelTest, executeFenced) { TEST_P(PreparedModelTest, executeFenced) {
// setup call // setup call
const auto mockPreparedModel = MockPreparedModel::create(); const auto mockPreparedModel = MockPreparedModel::create();
const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
const auto mockCallback = MockFencedExecutionCallback::create(); const auto mockCallback = MockFencedExecutionCallback::create();
EXPECT_CALL(*mockCallback, getExecutionInfo(_, _, _)) EXPECT_CALL(*mockCallback, getExecutionInfo(_, _, _))
.Times(1) .Times(1)
@@ -176,10 +180,10 @@ TEST(PreparedModelTest, executeFenced) {
<< callbackResult.error().message; << callbackResult.error().message;
} }
TEST(PreparedModelTest, executeFencedCallbackError) { TEST_P(PreparedModelTest, executeFencedCallbackError) {
// setup call // setup call
const auto mockPreparedModel = MockPreparedModel::create(); const auto mockPreparedModel = MockPreparedModel::create();
const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
const auto mockCallback = MockFencedExecutionCallback::create(); const auto mockCallback = MockFencedExecutionCallback::create();
EXPECT_CALL(*mockCallback, getExecutionInfo(_, _, _)) EXPECT_CALL(*mockCallback, getExecutionInfo(_, _, _))
.Times(1) .Times(1)
@@ -206,10 +210,10 @@ TEST(PreparedModelTest, executeFencedCallbackError) {
EXPECT_EQ(callbackResult.error().code, nn::ErrorStatus::GENERAL_FAILURE); EXPECT_EQ(callbackResult.error().code, nn::ErrorStatus::GENERAL_FAILURE);
} }
TEST(PreparedModelTest, executeFencedError) { TEST_P(PreparedModelTest, executeFencedError) {
// setup test // setup test
const auto mockPreparedModel = MockPreparedModel::create(); const auto mockPreparedModel = MockPreparedModel::create();
const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _)) EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _))
.Times(1) .Times(1)
.WillOnce(InvokeWithoutArgs(makeGeneralFailure)); .WillOnce(InvokeWithoutArgs(makeGeneralFailure));
@@ -222,10 +226,10 @@ TEST(PreparedModelTest, executeFencedError) {
EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
} }
TEST(PreparedModelTest, executeFencedTransportFailure) { TEST_P(PreparedModelTest, executeFencedTransportFailure) {
// setup test // setup test
const auto mockPreparedModel = MockPreparedModel::create(); const auto mockPreparedModel = MockPreparedModel::create();
const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _)) EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _))
.Times(1) .Times(1)
.WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
@@ -238,10 +242,10 @@ TEST(PreparedModelTest, executeFencedTransportFailure) {
EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
} }
TEST(PreparedModelTest, executeFencedDeadObject) { TEST_P(PreparedModelTest, executeFencedDeadObject) {
// setup test // setup test
const auto mockPreparedModel = MockPreparedModel::create(); const auto mockPreparedModel = MockPreparedModel::create();
const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _)) EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _))
.Times(1) .Times(1)
.WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
@@ -254,11 +258,13 @@ TEST(PreparedModelTest, executeFencedDeadObject) {
EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
} }
TEST(PreparedModelTest, reusableExecuteSync) { TEST_P(PreparedModelTest, reusableExecuteSync) {
if (kVersion.level >= nn::Version::Level::FEATURE_LEVEL_8) return;
// setup call // setup call
const uint32_t kNumberOfComputations = 2; const uint32_t kNumberOfComputations = 2;
const auto mockPreparedModel = MockPreparedModel::create(); const auto mockPreparedModel = MockPreparedModel::create();
const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
const auto mockExecutionResult = ExecutionResult{ const auto mockExecutionResult = ExecutionResult{
.outputSufficientSize = true, .outputSufficientSize = true,
.outputShapes = {}, .outputShapes = {},
@@ -283,10 +289,12 @@ TEST(PreparedModelTest, reusableExecuteSync) {
} }
} }
TEST(PreparedModelTest, reusableExecuteSyncError) { TEST_P(PreparedModelTest, reusableExecuteSyncError) {
if (kVersion.level >= nn::Version::Level::FEATURE_LEVEL_8) return;
// setup test // setup test
const auto mockPreparedModel = MockPreparedModel::create(); const auto mockPreparedModel = MockPreparedModel::create();
const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _, _, _)) EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _, _, _))
.Times(1) .Times(1)
.WillOnce(Invoke(makeGeneralFailure)); .WillOnce(Invoke(makeGeneralFailure));
@@ -303,10 +311,12 @@ TEST(PreparedModelTest, reusableExecuteSyncError) {
EXPECT_EQ(computeResult.error().code, nn::ErrorStatus::GENERAL_FAILURE); EXPECT_EQ(computeResult.error().code, nn::ErrorStatus::GENERAL_FAILURE);
} }
TEST(PreparedModelTest, reusableExecuteSyncTransportFailure) { TEST_P(PreparedModelTest, reusableExecuteSyncTransportFailure) {
if (kVersion.level >= nn::Version::Level::FEATURE_LEVEL_8) return;
// setup test // setup test
const auto mockPreparedModel = MockPreparedModel::create(); const auto mockPreparedModel = MockPreparedModel::create();
const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _, _, _)) EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _, _, _))
.Times(1) .Times(1)
.WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
@@ -323,10 +333,12 @@ TEST(PreparedModelTest, reusableExecuteSyncTransportFailure) {
EXPECT_EQ(computeResult.error().code, nn::ErrorStatus::GENERAL_FAILURE); EXPECT_EQ(computeResult.error().code, nn::ErrorStatus::GENERAL_FAILURE);
} }
TEST(PreparedModelTest, reusableExecuteSyncDeadObject) { TEST_P(PreparedModelTest, reusableExecuteSyncDeadObject) {
if (kVersion.level >= nn::Version::Level::FEATURE_LEVEL_8) return;
// setup test // setup test
const auto mockPreparedModel = MockPreparedModel::create(); const auto mockPreparedModel = MockPreparedModel::create();
const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _, _, _)) EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _, _, _))
.Times(1) .Times(1)
.WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
@@ -343,11 +355,13 @@ TEST(PreparedModelTest, reusableExecuteSyncDeadObject) {
EXPECT_EQ(computeResult.error().code, nn::ErrorStatus::DEAD_OBJECT); EXPECT_EQ(computeResult.error().code, nn::ErrorStatus::DEAD_OBJECT);
} }
TEST(PreparedModelTest, reusableExecuteFenced) { TEST_P(PreparedModelTest, reusableExecuteFenced) {
if (kVersion.level >= nn::Version::Level::FEATURE_LEVEL_8) return;
// setup call // setup call
const uint32_t kNumberOfComputations = 2; const uint32_t kNumberOfComputations = 2;
const auto mockPreparedModel = MockPreparedModel::create(); const auto mockPreparedModel = MockPreparedModel::create();
const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
const auto mockCallback = MockFencedExecutionCallback::create(); const auto mockCallback = MockFencedExecutionCallback::create();
EXPECT_CALL(*mockCallback, getExecutionInfo(_, _, _)) EXPECT_CALL(*mockCallback, getExecutionInfo(_, _, _))
.Times(kNumberOfComputations) .Times(kNumberOfComputations)
@@ -379,10 +393,12 @@ TEST(PreparedModelTest, reusableExecuteFenced) {
} }
} }
TEST(PreparedModelTest, reusableExecuteFencedCallbackError) { TEST_P(PreparedModelTest, reusableExecuteFencedCallbackError) {
if (kVersion.level >= nn::Version::Level::FEATURE_LEVEL_8) return;
// setup call // setup call
const auto mockPreparedModel = MockPreparedModel::create(); const auto mockPreparedModel = MockPreparedModel::create();
const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
const auto mockCallback = MockFencedExecutionCallback::create(); const auto mockCallback = MockFencedExecutionCallback::create();
EXPECT_CALL(*mockCallback, getExecutionInfo(_, _, _)) EXPECT_CALL(*mockCallback, getExecutionInfo(_, _, _))
.Times(1) .Times(1)
@@ -413,10 +429,12 @@ TEST(PreparedModelTest, reusableExecuteFencedCallbackError) {
EXPECT_EQ(callbackResult.error().code, nn::ErrorStatus::GENERAL_FAILURE); EXPECT_EQ(callbackResult.error().code, nn::ErrorStatus::GENERAL_FAILURE);
} }
TEST(PreparedModelTest, reusableExecuteFencedError) { TEST_P(PreparedModelTest, reusableExecuteFencedError) {
if (kVersion.level >= nn::Version::Level::FEATURE_LEVEL_8) return;
// setup test // setup test
const auto mockPreparedModel = MockPreparedModel::create(); const auto mockPreparedModel = MockPreparedModel::create();
const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _)) EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _))
.Times(1) .Times(1)
.WillOnce(InvokeWithoutArgs(makeGeneralFailure)); .WillOnce(InvokeWithoutArgs(makeGeneralFailure));
@@ -433,10 +451,12 @@ TEST(PreparedModelTest, reusableExecuteFencedError) {
EXPECT_EQ(computeResult.error().code, nn::ErrorStatus::GENERAL_FAILURE); EXPECT_EQ(computeResult.error().code, nn::ErrorStatus::GENERAL_FAILURE);
} }
TEST(PreparedModelTest, reusableExecuteFencedTransportFailure) { TEST_P(PreparedModelTest, reusableExecuteFencedTransportFailure) {
if (kVersion.level >= nn::Version::Level::FEATURE_LEVEL_8) return;
// setup test // setup test
const auto mockPreparedModel = MockPreparedModel::create(); const auto mockPreparedModel = MockPreparedModel::create();
const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _)) EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _))
.Times(1) .Times(1)
.WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
@@ -453,10 +473,12 @@ TEST(PreparedModelTest, reusableExecuteFencedTransportFailure) {
EXPECT_EQ(computeResult.error().code, nn::ErrorStatus::GENERAL_FAILURE); EXPECT_EQ(computeResult.error().code, nn::ErrorStatus::GENERAL_FAILURE);
} }
TEST(PreparedModelTest, reusableExecuteFencedDeadObject) { TEST_P(PreparedModelTest, reusableExecuteFencedDeadObject) {
if (kVersion.level >= nn::Version::Level::FEATURE_LEVEL_8) return;
// setup test // setup test
const auto mockPreparedModel = MockPreparedModel::create(); const auto mockPreparedModel = MockPreparedModel::create();
const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _)) EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _))
.Times(1) .Times(1)
.WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
@@ -473,14 +495,14 @@ TEST(PreparedModelTest, reusableExecuteFencedDeadObject) {
EXPECT_EQ(computeResult.error().code, nn::ErrorStatus::DEAD_OBJECT); EXPECT_EQ(computeResult.error().code, nn::ErrorStatus::DEAD_OBJECT);
} }
TEST(PreparedModelTest, configureExecutionBurst) { TEST_P(PreparedModelTest, configureExecutionBurst) {
// setup test // setup test
const auto mockPreparedModel = MockPreparedModel::create(); const auto mockPreparedModel = MockPreparedModel::create();
const auto mockBurst = ndk::SharedRefBase::make<MockBurst>(); const auto mockBurst = ndk::SharedRefBase::make<MockBurst>();
EXPECT_CALL(*mockPreparedModel, configureExecutionBurst(_)) EXPECT_CALL(*mockPreparedModel, configureExecutionBurst(_))
.Times(1) .Times(1)
.WillOnce(DoAll(SetArgPointee<0>(mockBurst), Invoke(makeStatusOk))); .WillOnce(DoAll(SetArgPointee<0>(mockBurst), Invoke(makeStatusOk)));
const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
// run test // run test
const auto result = preparedModel->configureExecutionBurst(); const auto result = preparedModel->configureExecutionBurst();
@@ -491,13 +513,13 @@ TEST(PreparedModelTest, configureExecutionBurst) {
EXPECT_NE(result.value(), nullptr); EXPECT_NE(result.value(), nullptr);
} }
TEST(PreparedModelTest, configureExecutionBurstError) { TEST_P(PreparedModelTest, configureExecutionBurstError) {
// setup test // setup test
const auto mockPreparedModel = MockPreparedModel::create(); const auto mockPreparedModel = MockPreparedModel::create();
EXPECT_CALL(*mockPreparedModel, configureExecutionBurst(_)) EXPECT_CALL(*mockPreparedModel, configureExecutionBurst(_))
.Times(1) .Times(1)
.WillOnce(InvokeWithoutArgs(makeGeneralFailure)); .WillOnce(InvokeWithoutArgs(makeGeneralFailure));
const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
// run test // run test
const auto result = preparedModel->configureExecutionBurst(); const auto result = preparedModel->configureExecutionBurst();
@@ -507,13 +529,13 @@ TEST(PreparedModelTest, configureExecutionBurstError) {
EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
} }
TEST(PreparedModelTest, configureExecutionBurstTransportFailure) { TEST_P(PreparedModelTest, configureExecutionBurstTransportFailure) {
// setup test // setup test
const auto mockPreparedModel = MockPreparedModel::create(); const auto mockPreparedModel = MockPreparedModel::create();
EXPECT_CALL(*mockPreparedModel, configureExecutionBurst(_)) EXPECT_CALL(*mockPreparedModel, configureExecutionBurst(_))
.Times(1) .Times(1)
.WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure)); .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
// run test // run test
const auto result = preparedModel->configureExecutionBurst(); const auto result = preparedModel->configureExecutionBurst();
@@ -523,13 +545,13 @@ TEST(PreparedModelTest, configureExecutionBurstTransportFailure) {
EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE); EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
} }
TEST(PreparedModelTest, configureExecutionBurstDeadObject) { TEST_P(PreparedModelTest, configureExecutionBurstDeadObject) {
// setup test // setup test
const auto mockPreparedModel = MockPreparedModel::create(); const auto mockPreparedModel = MockPreparedModel::create();
EXPECT_CALL(*mockPreparedModel, configureExecutionBurst(_)) EXPECT_CALL(*mockPreparedModel, configureExecutionBurst(_))
.Times(1) .Times(1)
.WillOnce(InvokeWithoutArgs(makeDeadObjectFailure)); .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
// run test // run test
const auto result = preparedModel->configureExecutionBurst(); const auto result = preparedModel->configureExecutionBurst();
@@ -539,10 +561,84 @@ TEST(PreparedModelTest, configureExecutionBurstDeadObject) {
EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT); EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
} }
TEST(PreparedModelTest, getUnderlyingResource) { TEST_P(PreparedModelTest, createReusableExecution) {
if (kVersion.level < nn::Version::Level::FEATURE_LEVEL_8) return;
// setup test // setup test
const auto mockPreparedModel = MockPreparedModel::create(); const auto mockPreparedModel = MockPreparedModel::create();
const auto preparedModel = PreparedModel::create(mockPreparedModel).value(); const auto mockExecution = ndk::SharedRefBase::make<MockExecution>();
EXPECT_CALL(*mockPreparedModel, createReusableExecution(_, _, _, _))
.Times(1)
.WillOnce(DoAll(SetArgPointee<3>(mockExecution), Invoke(makeStatusOk)));
const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
// run test
const auto result = preparedModel->createReusableExecution({}, {}, {});
// verify result
ASSERT_TRUE(result.has_value())
<< "Failed with " << result.error().code << ": " << result.error().message;
EXPECT_NE(result.value(), nullptr);
}
TEST_P(PreparedModelTest, createReusableExecutionError) {
if (kVersion.level < nn::Version::Level::FEATURE_LEVEL_8) return;
// setup test
const auto mockPreparedModel = MockPreparedModel::create();
EXPECT_CALL(*mockPreparedModel, createReusableExecution(_, _, _, _))
.Times(1)
.WillOnce(InvokeWithoutArgs(makeGeneralFailure));
const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
// run test
const auto result = preparedModel->createReusableExecution({}, {}, {});
// verify result
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
}
TEST_P(PreparedModelTest, createReusableExecutionTransportFailure) {
if (kVersion.level < nn::Version::Level::FEATURE_LEVEL_8) return;
// setup test
const auto mockPreparedModel = MockPreparedModel::create();
EXPECT_CALL(*mockPreparedModel, createReusableExecution(_, _, _, _))
.Times(1)
.WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
// run test
const auto result = preparedModel->createReusableExecution({}, {}, {});
// verify result
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
}
TEST_P(PreparedModelTest, createReusableExecutionDeadObject) {
if (kVersion.level < nn::Version::Level::FEATURE_LEVEL_8) return;
// setup test
const auto mockPreparedModel = MockPreparedModel::create();
EXPECT_CALL(*mockPreparedModel, createReusableExecution(_, _, _, _))
.Times(1)
.WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
// run test
const auto result = preparedModel->createReusableExecution({}, {}, {});
// verify result
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
}
TEST_P(PreparedModelTest, getUnderlyingResource) {
// setup test
const auto mockPreparedModel = MockPreparedModel::create();
const auto preparedModel = PreparedModel::create(mockPreparedModel, kVersion).value();
// run test // run test
const auto resource = preparedModel->getUnderlyingResource(); const auto resource = preparedModel->getUnderlyingResource();
@@ -554,4 +650,6 @@ TEST(PreparedModelTest, getUnderlyingResource) {
EXPECT_EQ(maybeMock->get(), mockPreparedModel.get()); EXPECT_EQ(maybeMock->get(), mockPreparedModel.get());
} }
INSTANTIATE_VERSIONED_AIDL_UTILS_TEST(PreparedModelTest, kAllAidlVersions);
} // namespace aidl::android::hardware::neuralnetworks::utils } // namespace aidl::android::hardware::neuralnetworks::utils

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2021 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 "TestUtils.h"
#include <android-base/logging.h>
#include <gtest/gtest.h>
#include <nnapi/TypeUtils.h>
#include <nnapi/Types.h>
#include <nnapi/hal/CommonUtils.h>
#include <string>
namespace aidl::android::hardware::neuralnetworks::utils {
std::string printTestVersion(const testing::TestParamInfo<nn::Version>& info) {
switch (info.param.level) {
case nn::Version::Level::FEATURE_LEVEL_5:
return "v1";
case nn::Version::Level::FEATURE_LEVEL_6:
return "v2";
case nn::Version::Level::FEATURE_LEVEL_7:
return "v3";
case nn::Version::Level::FEATURE_LEVEL_8:
return "v4";
default:
LOG(FATAL) << "Invalid AIDL version: " << info.param;
return "invalid";
}
}
} // namespace aidl::android::hardware::neuralnetworks::utils

View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2021 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_AIDL_UTILS_TEST_TEST_UTILS_H
#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_TEST_UTILS_H
#include <gtest/gtest.h>
#include <nnapi/Types.h>
#include <nnapi/hal/CommonUtils.h>
#include <string>
namespace aidl::android::hardware::neuralnetworks::utils {
class VersionedAidlUtilsTestBase : public ::testing::TestWithParam<nn::Version> {
protected:
const nn::Version kVersion = GetParam();
};
std::string printTestVersion(const testing::TestParamInfo<nn::Version>& info);
inline const auto kAllAidlVersions =
::testing::Values(nn::kVersionFeatureLevel5, nn::kVersionFeatureLevel6,
nn::kVersionFeatureLevel7, nn::kVersionFeatureLevel8);
#define INSTANTIATE_VERSIONED_AIDL_UTILS_TEST(TestSuite, versions) \
INSTANTIATE_TEST_SUITE_P(Versioned, TestSuite, versions, printTestVersion)
} // namespace aidl::android::hardware::neuralnetworks::utils
#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_TEST_TEST_UTILS_H

View File

@@ -208,6 +208,11 @@ class InvalidPreparedModel : public BnPreparedModel {
return ndk::ScopedAStatus::fromServiceSpecificError( return ndk::ScopedAStatus::fromServiceSpecificError(
static_cast<int32_t>(ErrorStatus::GENERAL_FAILURE)); static_cast<int32_t>(ErrorStatus::GENERAL_FAILURE));
} }
ndk::ScopedAStatus createReusableExecution(const aidl_hal::Request&, bool, int64_t,
std::shared_ptr<aidl_hal::IExecution>*) override {
return ndk::ScopedAStatus::fromServiceSpecificError(
static_cast<int32_t>(ErrorStatus::GENERAL_FAILURE));
}
}; };
template <typename... Args> template <typename... Args>

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2022 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_ADAPTER_AIDL_EXECUTION_H
#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_ADAPTER_AIDL_EXECUTION_H
#include "nnapi/hal/aidl/Adapter.h"
#include <aidl/android/hardware/neuralnetworks/BnExecution.h>
#include <aidl/android/hardware/neuralnetworks/ExecutionResult.h>
#include <aidl/android/hardware/neuralnetworks/FencedExecutionResult.h>
#include <aidl/android/hardware/neuralnetworks/IExecution.h>
#include <aidl/android/hardware/neuralnetworks/Request.h>
#include <android/binder_auto_utils.h>
#include <nnapi/IExecution.h>
#include <nnapi/Types.h>
#include <memory>
#include <vector>
// See hardware/interfaces/neuralnetworks/utils/README.md for more information on AIDL interface
// lifetimes across processes and for protecting asynchronous calls across AIDL.
namespace aidl::android::hardware::neuralnetworks::adapter {
// Class that adapts nn::IExecution to BnExecution.
class Execution : public BnExecution {
public:
explicit Execution(::android::nn::SharedExecution execution);
ndk::ScopedAStatus executeSynchronously(int64_t deadlineNs,
ExecutionResult* executionResult) override;
ndk::ScopedAStatus executeFenced(const std::vector<ndk::ScopedFileDescriptor>& waitFor,
int64_t deadlineNs, int64_t durationNs,
FencedExecutionResult* fencedExecutionResult) override;
protected:
const ::android::nn::SharedExecution kExecution;
};
} // namespace aidl::android::hardware::neuralnetworks::adapter
#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_ADAPTER_AIDL_EXECUTION_H

View File

@@ -23,6 +23,7 @@
#include <aidl/android/hardware/neuralnetworks/ExecutionResult.h> #include <aidl/android/hardware/neuralnetworks/ExecutionResult.h>
#include <aidl/android/hardware/neuralnetworks/FencedExecutionResult.h> #include <aidl/android/hardware/neuralnetworks/FencedExecutionResult.h>
#include <aidl/android/hardware/neuralnetworks/IBurst.h> #include <aidl/android/hardware/neuralnetworks/IBurst.h>
#include <aidl/android/hardware/neuralnetworks/IExecution.h>
#include <aidl/android/hardware/neuralnetworks/Request.h> #include <aidl/android/hardware/neuralnetworks/Request.h>
#include <android/binder_auto_utils.h> #include <android/binder_auto_utils.h>
#include <nnapi/IPreparedModel.h> #include <nnapi/IPreparedModel.h>
@@ -50,6 +51,9 @@ class PreparedModel : public BnPreparedModel {
int64_t loopTimeoutDurationNs, int64_t durationNs, int64_t loopTimeoutDurationNs, int64_t durationNs,
FencedExecutionResult* executionResult) override; FencedExecutionResult* executionResult) override;
ndk::ScopedAStatus configureExecutionBurst(std::shared_ptr<IBurst>* burst) override; ndk::ScopedAStatus configureExecutionBurst(std::shared_ptr<IBurst>* burst) override;
ndk::ScopedAStatus createReusableExecution(const Request& request, bool measureTiming,
int64_t loopTimeoutDurationNs,
std::shared_ptr<IExecution>* execution) override;
::android::nn::SharedPreparedModel getUnderlyingPreparedModel() const; ::android::nn::SharedPreparedModel getUnderlyingPreparedModel() const;

View File

@@ -17,6 +17,7 @@
#include "PreparedModel.h" #include "PreparedModel.h"
#include "Burst.h" #include "Burst.h"
#include "Execution.h"
#include <aidl/android/hardware/neuralnetworks/BnFencedExecutionCallback.h> #include <aidl/android/hardware/neuralnetworks/BnFencedExecutionCallback.h>
#include <aidl/android/hardware/neuralnetworks/BnPreparedModel.h> #include <aidl/android/hardware/neuralnetworks/BnPreparedModel.h>
@@ -26,6 +27,7 @@
#include <aidl/android/hardware/neuralnetworks/Request.h> #include <aidl/android/hardware/neuralnetworks/Request.h>
#include <android-base/logging.h> #include <android-base/logging.h>
#include <android/binder_auto_utils.h> #include <android/binder_auto_utils.h>
#include <nnapi/IExecution.h>
#include <nnapi/IPreparedModel.h> #include <nnapi/IPreparedModel.h>
#include <nnapi/Result.h> #include <nnapi/Result.h>
#include <nnapi/SharedMemory.h> #include <nnapi/SharedMemory.h>
@@ -167,6 +169,56 @@ nn::GeneralResult<FencedExecutionResult> executeFenced(
.syncFence = std::move(fileDescriptor)}; .syncFence = std::move(fileDescriptor)};
} }
nn::GeneralResult<nn::SharedExecution> createReusableExecution(
const nn::IPreparedModel& preparedModel, const Request& request, bool measureTiming,
int64_t loopTimeoutDurationNs) {
const auto nnRequest = NN_TRY(convertInput(request));
const auto nnMeasureTiming = measureTiming ? nn::MeasureTiming::YES : nn::MeasureTiming::NO;
const auto nnLoopTimeoutDuration = NN_TRY(makeOptionalDuration(loopTimeoutDurationNs));
return preparedModel.createReusableExecution(nnRequest, nnMeasureTiming, nnLoopTimeoutDuration);
}
nn::ExecutionResult<ExecutionResult> executeSynchronously(const nn::IExecution& execution,
int64_t deadlineNs) {
const auto nnDeadline = NN_TRY(makeOptionalTimePoint(deadlineNs));
const auto result = execution.compute(nnDeadline);
if (!result.ok() && result.error().code == nn::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE) {
const auto& [message, code, outputShapes] = result.error();
LOG(ERROR) << "executeSynchronously failed with " << code << ": " << message;
return ExecutionResult{.outputSufficientSize = false,
.outputShapes = utils::convert(outputShapes).value(),
.timing = {.timeInDriverNs = -1, .timeOnDeviceNs = -1}};
}
const auto& [outputShapes, timing] = NN_TRY(result);
return ExecutionResult{.outputSufficientSize = true,
.outputShapes = utils::convert(outputShapes).value(),
.timing = utils::convert(timing).value()};
}
nn::GeneralResult<FencedExecutionResult> executeFenced(
const nn::IExecution& execution, const std::vector<ndk::ScopedFileDescriptor>& waitFor,
int64_t deadlineNs, int64_t durationNs) {
const auto nnWaitFor = NN_TRY(convertSyncFences(waitFor));
const auto nnDeadline = NN_TRY(makeOptionalTimePoint(deadlineNs));
const auto nnDuration = NN_TRY(makeOptionalDuration(durationNs));
auto [syncFence, executeFencedInfoCallback] =
NN_TRY(execution.computeFenced(nnWaitFor, nnDeadline, nnDuration));
ndk::ScopedFileDescriptor fileDescriptor;
if (syncFence.hasFd()) {
auto uniqueFd = NN_TRY(nn::dupFd(syncFence.getFd()));
fileDescriptor = ndk::ScopedFileDescriptor(uniqueFd.release());
}
return FencedExecutionResult{.callback = ndk::SharedRefBase::make<FencedExecutionCallback>(
std::move(executeFencedInfoCallback)),
.syncFence = std::move(fileDescriptor)};
}
} // namespace } // namespace
PreparedModel::PreparedModel(nn::SharedPreparedModel preparedModel) PreparedModel::PreparedModel(nn::SharedPreparedModel preparedModel)
@@ -222,4 +274,51 @@ nn::SharedPreparedModel PreparedModel::getUnderlyingPreparedModel() const {
return kPreparedModel; return kPreparedModel;
} }
ndk::ScopedAStatus PreparedModel::createReusableExecution(const Request& request,
bool measureTiming,
int64_t loopTimeoutDurationNs,
std::shared_ptr<IExecution>* execution) {
auto result = adapter::createReusableExecution(*kPreparedModel, request, measureTiming,
loopTimeoutDurationNs);
if (!result.has_value()) {
const auto& [message, code] = result.error();
const auto aidlCode = utils::convert(code).value_or(ErrorStatus::GENERAL_FAILURE);
return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
static_cast<int32_t>(aidlCode), message.c_str());
}
*execution = ndk::SharedRefBase::make<Execution>(std::move(result).value());
return ndk::ScopedAStatus::ok();
}
Execution::Execution(nn::SharedExecution execution) : kExecution(std::move(execution)) {
CHECK(kExecution != nullptr);
}
ndk::ScopedAStatus Execution::executeSynchronously(int64_t deadlineNs,
ExecutionResult* executionResult) {
auto result = adapter::executeSynchronously(*kExecution, deadlineNs);
if (!result.has_value()) {
const auto& [message, code, _] = result.error();
const auto aidlCode = utils::convert(code).value_or(ErrorStatus::GENERAL_FAILURE);
return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
static_cast<int32_t>(aidlCode), message.c_str());
}
*executionResult = std::move(result).value();
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Execution::executeFenced(const std::vector<ndk::ScopedFileDescriptor>& waitFor,
int64_t deadlineNs, int64_t durationNs,
FencedExecutionResult* executionResult) {
auto result = adapter::executeFenced(*kExecution, waitFor, deadlineNs, durationNs);
if (!result.has_value()) {
const auto& [message, code] = result.error();
const auto aidlCode = utils::convert(code).value_or(ErrorStatus::GENERAL_FAILURE);
return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
static_cast<int32_t>(aidlCode), message.c_str());
}
*executionResult = std::move(result).value();
return ndk::ScopedAStatus::ok();
}
} // namespace aidl::android::hardware::neuralnetworks::adapter } // namespace aidl::android::hardware::neuralnetworks::adapter