NNAPI Burst -- HAL VTS tests
FastMessageQueue is a Treble-compliant data structure that enables fast
communication between two processes. The FMQ object itself is an atomic
circular buffer that is optionally synchronized with a futex. However,
FMQ has no notion of ownership or lifetime across processes, so it must
be paired with higher-level constructs to manage the lifetime and
ownership.
The NNAPI is introducing the notion of an "Execution Burst" object (or
more simply a "Burst" object), which is similar to an
ANeuralNetworksExecution, but is intended to be reused across multiple
executions and has lower IPC overheads. It achieves this low IPC
overhead by replacing HIDL HwBinder calls with FMQ messages.
Specifically, it replaces IPreparedModel::executeSynchronously's call
from the client into the service with fmq_sync<FmqRequestDatum> (an FMQ
channel used to pass a serialized Request object) and it replaces
the return from the service into the client with
fmq_sync<FmqResultDatum> (an FMQ channel used to return serialized
result status and OutputShapes information).
Each channel is a unidirectional flow of information with exactly one
producer and exactly one consumer. The channels are created by the NN
runtime and passed to the service via
IPreparedModel::configureExecutionBurst.
This CL tests the Burst in both the execution path and validation path
in the Vendor Test Suite (VTS) in neuralnetworks/1.*/vts/functional/.
The VTS binary--VtsHalNeuralnetworksV1_2TargetTest--can be built and run
as any previous version could.
Bug: 119570067
Test: mma
Test: VtsHalNeuralnetworksV1_2TargetTest
Change-Id: I3a36484eff9565c2d028c07c099804a0289f294a
Merged-In: I3a36484eff9565c2d028c07c099804a0289f294a
(cherry picked from commit 814d8372f3
)
This commit is contained in:
parent
685fcc0098
commit
29471a8935
3 changed files with 126 additions and 43 deletions
|
@ -23,6 +23,7 @@ cc_library_static {
|
|||
defaults: ["VtsHalTargetTestDefaults"],
|
||||
export_include_dirs: ["."],
|
||||
shared_libs: [
|
||||
"libfmq",
|
||||
"libnativewindow",
|
||||
],
|
||||
static_libs: [
|
||||
|
@ -51,6 +52,7 @@ cc_defaults {
|
|||
"VtsHalNeuralnetworks.cpp",
|
||||
],
|
||||
shared_libs: [
|
||||
"libfmq",
|
||||
"libnativewindow",
|
||||
],
|
||||
static_libs: [
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
|
||||
#include "Callbacks.h"
|
||||
#include "ExecutionBurstController.h"
|
||||
#include "TestHarness.h"
|
||||
#include "Utils.h"
|
||||
|
||||
|
@ -109,14 +110,22 @@ static Return<ErrorStatus> ExecutePreparedModel(sp<V1_2::IPreparedModel>& prepar
|
|||
}
|
||||
return result;
|
||||
}
|
||||
enum class Synchronously { NO, YES };
|
||||
static std::unique_ptr<::android::nn::ExecutionBurstController> CreateBurst(
|
||||
const sp<V1_0::IPreparedModel>&) {
|
||||
ADD_FAILURE() << "asking for burst execution at V1_0";
|
||||
return nullptr;
|
||||
}
|
||||
static std::unique_ptr<::android::nn::ExecutionBurstController> CreateBurst(
|
||||
const sp<V1_2::IPreparedModel>& preparedModel) {
|
||||
return ::android::nn::createExecutionBurstController(preparedModel, /*blocking=*/true);
|
||||
}
|
||||
enum class Executor { ASYNC, SYNC, BURST };
|
||||
const float kDefaultAtol = 1e-5f;
|
||||
const float kDefaultRtol = 1e-5f;
|
||||
template <typename T_IPreparedModel>
|
||||
void EvaluatePreparedModel(sp<T_IPreparedModel>& preparedModel, std::function<bool(int)> is_ignored,
|
||||
const std::vector<MixedTypedExample>& examples,
|
||||
bool hasRelaxedFloat32Model, float fpAtol, float fpRtol,
|
||||
Synchronously sync, MeasureTiming measure, bool testDynamicOutputShape) {
|
||||
const std::vector<MixedTypedExample>& examples, bool hasRelaxedFloat32Model, float fpAtol,
|
||||
float fpRtol, Executor executor, MeasureTiming measure, bool testDynamicOutputShape) {
|
||||
const uint32_t INPUT = 0;
|
||||
const uint32_t OUTPUT = 1;
|
||||
|
||||
|
@ -209,35 +218,62 @@ void EvaluatePreparedModel(sp<T_IPreparedModel>& preparedModel, std::function<bo
|
|||
inputMemory->commit();
|
||||
outputMemory->commit();
|
||||
|
||||
const Request request = {.inputs = inputs_info, .outputs = outputs_info, .pools = pools};
|
||||
|
||||
ErrorStatus executionStatus;
|
||||
hidl_vec<OutputShape> outputShapes;
|
||||
Timing timing;
|
||||
if (sync == Synchronously::NO) {
|
||||
SCOPED_TRACE("asynchronous");
|
||||
switch (executor) {
|
||||
case Executor::ASYNC: {
|
||||
SCOPED_TRACE("asynchronous");
|
||||
|
||||
// launch execution
|
||||
sp<ExecutionCallback> executionCallback = new ExecutionCallback();
|
||||
ASSERT_NE(nullptr, executionCallback.get());
|
||||
Return<ErrorStatus> executionLaunchStatus = ExecutePreparedModel(
|
||||
preparedModel, {.inputs = inputs_info, .outputs = outputs_info, .pools = pools},
|
||||
measure, executionCallback);
|
||||
ASSERT_TRUE(executionLaunchStatus.isOk());
|
||||
EXPECT_EQ(ErrorStatus::NONE, static_cast<ErrorStatus>(executionLaunchStatus));
|
||||
// launch execution
|
||||
sp<ExecutionCallback> executionCallback = new ExecutionCallback();
|
||||
ASSERT_NE(nullptr, executionCallback.get());
|
||||
Return<ErrorStatus> executionLaunchStatus =
|
||||
ExecutePreparedModel(preparedModel, request, measure, executionCallback);
|
||||
ASSERT_TRUE(executionLaunchStatus.isOk());
|
||||
EXPECT_EQ(ErrorStatus::NONE, static_cast<ErrorStatus>(executionLaunchStatus));
|
||||
|
||||
// retrieve execution status
|
||||
executionCallback->wait();
|
||||
executionStatus = executionCallback->getStatus();
|
||||
outputShapes = executionCallback->getOutputShapes();
|
||||
timing = executionCallback->getTiming();
|
||||
} else {
|
||||
SCOPED_TRACE("synchronous");
|
||||
// retrieve execution status
|
||||
executionCallback->wait();
|
||||
executionStatus = executionCallback->getStatus();
|
||||
outputShapes = executionCallback->getOutputShapes();
|
||||
timing = executionCallback->getTiming();
|
||||
|
||||
// execute
|
||||
Return<ErrorStatus> executionReturnStatus = ExecutePreparedModel(
|
||||
preparedModel, {.inputs = inputs_info, .outputs = outputs_info, .pools = pools},
|
||||
measure, &outputShapes, &timing);
|
||||
ASSERT_TRUE(executionReturnStatus.isOk());
|
||||
executionStatus = static_cast<ErrorStatus>(executionReturnStatus);
|
||||
break;
|
||||
}
|
||||
case Executor::SYNC: {
|
||||
SCOPED_TRACE("synchronous");
|
||||
|
||||
// execute
|
||||
Return<ErrorStatus> executionReturnStatus = ExecutePreparedModel(
|
||||
preparedModel, request, measure, &outputShapes, &timing);
|
||||
ASSERT_TRUE(executionReturnStatus.isOk());
|
||||
executionStatus = static_cast<ErrorStatus>(executionReturnStatus);
|
||||
|
||||
break;
|
||||
}
|
||||
case Executor::BURST: {
|
||||
SCOPED_TRACE("burst");
|
||||
|
||||
// create burst
|
||||
const std::unique_ptr<::android::nn::ExecutionBurstController> controller =
|
||||
CreateBurst(preparedModel);
|
||||
ASSERT_NE(nullptr, controller.get());
|
||||
|
||||
// create memory keys
|
||||
std::vector<intptr_t> keys(request.pools.size());
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
keys[i] = reinterpret_cast<intptr_t>(&request.pools[i]);
|
||||
}
|
||||
|
||||
// execute burst
|
||||
std::tie(executionStatus, outputShapes, timing) =
|
||||
controller->compute(request, measure, keys);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (testDynamicOutputShape && executionStatus != ErrorStatus::NONE) {
|
||||
|
@ -285,11 +321,10 @@ void EvaluatePreparedModel(sp<T_IPreparedModel>& preparedModel, std::function<bo
|
|||
}
|
||||
template <typename T_IPreparedModel>
|
||||
void EvaluatePreparedModel(sp<T_IPreparedModel>& preparedModel, std::function<bool(int)> is_ignored,
|
||||
const std::vector<MixedTypedExample>& examples,
|
||||
bool hasRelaxedFloat32Model, Synchronously sync, MeasureTiming measure,
|
||||
bool testDynamicOutputShape) {
|
||||
const std::vector<MixedTypedExample>& examples, bool hasRelaxedFloat32Model,
|
||||
Executor executor, MeasureTiming measure, bool testDynamicOutputShape) {
|
||||
EvaluatePreparedModel(preparedModel, is_ignored, examples, hasRelaxedFloat32Model, kDefaultAtol,
|
||||
kDefaultRtol, sync, measure, testDynamicOutputShape);
|
||||
kDefaultRtol, executor, measure, testDynamicOutputShape);
|
||||
}
|
||||
|
||||
static void getPreparedModel(sp<PreparedModelCallback> callback,
|
||||
|
@ -345,8 +380,8 @@ void Execute(const sp<V1_0::IDevice>& device, std::function<V1_0::Model(void)> c
|
|||
|
||||
float fpAtol = 1e-5f, fpRtol = 5.0f * 1.1920928955078125e-7f;
|
||||
EvaluatePreparedModel(preparedModel, is_ignored, examples,
|
||||
/*hasRelaxedFloat32Model=*/false, fpAtol, fpRtol, Synchronously::NO,
|
||||
MeasureTiming::NO, /*testDynamicOutputShape=*/false);
|
||||
/*hasRelaxedFloat32Model=*/false, fpAtol, fpRtol, Executor::ASYNC, MeasureTiming::NO,
|
||||
/*testDynamicOutputShape=*/false);
|
||||
}
|
||||
|
||||
void Execute(const sp<V1_1::IDevice>& device, std::function<V1_1::Model(void)> create_model,
|
||||
|
@ -392,8 +427,8 @@ void Execute(const sp<V1_1::IDevice>& device, std::function<V1_1::Model(void)> c
|
|||
ASSERT_NE(nullptr, preparedModel.get());
|
||||
|
||||
EvaluatePreparedModel(preparedModel, is_ignored, examples,
|
||||
model.relaxComputationFloat32toFloat16, 1e-5f, 1e-5f, Synchronously::NO,
|
||||
MeasureTiming::NO, /*testDynamicOutputShape=*/false);
|
||||
model.relaxComputationFloat32toFloat16, 1e-5f, 1e-5f, Executor::ASYNC,
|
||||
MeasureTiming::NO, /*testDynamicOutputShape=*/false);
|
||||
}
|
||||
|
||||
// TODO: Reduce code duplication.
|
||||
|
@ -441,17 +476,23 @@ void Execute(const sp<V1_2::IDevice>& device, std::function<V1_2::Model(void)> c
|
|||
ASSERT_NE(nullptr, preparedModel.get());
|
||||
|
||||
EvaluatePreparedModel(preparedModel, is_ignored, examples,
|
||||
model.relaxComputationFloat32toFloat16, Synchronously::NO,
|
||||
MeasureTiming::NO, testDynamicOutputShape);
|
||||
model.relaxComputationFloat32toFloat16, Executor::ASYNC, MeasureTiming::NO,
|
||||
testDynamicOutputShape);
|
||||
EvaluatePreparedModel(preparedModel, is_ignored, examples,
|
||||
model.relaxComputationFloat32toFloat16, Synchronously::YES,
|
||||
MeasureTiming::NO, testDynamicOutputShape);
|
||||
model.relaxComputationFloat32toFloat16, Executor::SYNC, MeasureTiming::NO,
|
||||
testDynamicOutputShape);
|
||||
EvaluatePreparedModel(preparedModel, is_ignored, examples,
|
||||
model.relaxComputationFloat32toFloat16, Synchronously::NO,
|
||||
MeasureTiming::YES, testDynamicOutputShape);
|
||||
model.relaxComputationFloat32toFloat16, Executor::BURST, MeasureTiming::NO,
|
||||
testDynamicOutputShape);
|
||||
EvaluatePreparedModel(preparedModel, is_ignored, examples,
|
||||
model.relaxComputationFloat32toFloat16, Synchronously::YES,
|
||||
MeasureTiming::YES, testDynamicOutputShape);
|
||||
model.relaxComputationFloat32toFloat16, Executor::ASYNC, MeasureTiming::YES,
|
||||
testDynamicOutputShape);
|
||||
EvaluatePreparedModel(preparedModel, is_ignored, examples,
|
||||
model.relaxComputationFloat32toFloat16, Executor::SYNC, MeasureTiming::YES,
|
||||
testDynamicOutputShape);
|
||||
EvaluatePreparedModel(preparedModel, is_ignored, examples,
|
||||
model.relaxComputationFloat32toFloat16, Executor::BURST, MeasureTiming::YES,
|
||||
testDynamicOutputShape);
|
||||
}
|
||||
|
||||
} // namespace generated_tests
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "VtsHalNeuralnetworks.h"
|
||||
|
||||
#include "Callbacks.h"
|
||||
#include "ExecutionBurstController.h"
|
||||
#include "TestHarness.h"
|
||||
#include "Utils.h"
|
||||
|
||||
|
@ -112,6 +113,7 @@ static void validate(const sp<IPreparedModel>& preparedModel, const std::string&
|
|||
};
|
||||
MeasureTiming measure = (hash & 1) ? MeasureTiming::YES : MeasureTiming::NO;
|
||||
|
||||
// asynchronous
|
||||
{
|
||||
SCOPED_TRACE(message + " [execute_1_2]");
|
||||
|
||||
|
@ -131,6 +133,7 @@ static void validate(const sp<IPreparedModel>& preparedModel, const std::string&
|
|||
ASSERT_TRUE(badTiming(timing));
|
||||
}
|
||||
|
||||
// synchronous
|
||||
{
|
||||
SCOPED_TRACE(message + " [executeSynchronously]");
|
||||
|
||||
|
@ -144,6 +147,43 @@ static void validate(const sp<IPreparedModel>& preparedModel, const std::string&
|
|||
});
|
||||
ASSERT_TRUE(executeStatus.isOk());
|
||||
}
|
||||
|
||||
// burst
|
||||
{
|
||||
SCOPED_TRACE(message + " [burst]");
|
||||
|
||||
// create burst
|
||||
std::unique_ptr<::android::nn::ExecutionBurstController> burst =
|
||||
::android::nn::createExecutionBurstController(preparedModel, /*blocking=*/true);
|
||||
ASSERT_NE(nullptr, burst.get());
|
||||
|
||||
// create memory keys
|
||||
std::vector<intptr_t> keys(request.pools.size());
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
keys[i] = reinterpret_cast<intptr_t>(&request.pools[i]);
|
||||
}
|
||||
|
||||
// execute and verify
|
||||
ErrorStatus error;
|
||||
std::vector<OutputShape> outputShapes;
|
||||
Timing timing;
|
||||
std::tie(error, outputShapes, timing) = burst->compute(request, measure, keys);
|
||||
EXPECT_EQ(ErrorStatus::INVALID_ARGUMENT, error);
|
||||
EXPECT_EQ(outputShapes.size(), 0);
|
||||
EXPECT_TRUE(badTiming(timing));
|
||||
|
||||
// additional burst testing
|
||||
if (request.pools.size() > 0) {
|
||||
// valid free
|
||||
burst->freeMemory(keys.front());
|
||||
|
||||
// negative test: invalid free of unknown (blank) memory
|
||||
burst->freeMemory(intptr_t{});
|
||||
|
||||
// negative test: double free of memory
|
||||
burst->freeMemory(keys.front());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete element from hidl_vec. hidl_vec doesn't support a "remove" operation,
|
||||
|
|
Loading…
Reference in a new issue