Make the NDK execution object reusable.
This CL enables reusable execution object at NDK level.
- Add new API ANNExecution_setReusable
- Modify NDK specification related to execution usablility
- Change NNAPI ExecutionBuilder to allow reusable execution
- Add validation and generated tests
This CL only enables reusable execution object in the NNAPI runtime.
Optimizations will be applied in subsequent CLs.
Additionally, this CL changes a behavior of computations: Previously, we
wouldn't mark the builder as started until we get past the initial
validation, whereas now we mark the builder as started before the
validations and then mark it as completed if the validation fails.
Bug: 179692400
Test: NNT_static
Test: CtsNNAPITestCases
Change-Id: I0e2539f2adb7316af5b5364ac961f53e4ec09c59
Merged-In: I0e2539f2adb7316af5b5364ac961f53e4ec09c59
(cherry picked from commit 0b261d5651728eed74dd4c6033e25129b5e16368)
diff --git a/runtime/Event.h b/runtime/Event.h
index 90d5932..58bbb8b 100644
--- a/runtime/Event.h
+++ b/runtime/Event.h
@@ -21,6 +21,7 @@
#include <nnapi/IPreparedModel.h>
#include <memory>
+#include <mutex>
#include <utility>
#include "ExecutionCallback.h"
@@ -30,8 +31,7 @@
class IEvent {
public:
virtual ~IEvent() = default;
- virtual void wait() const = 0;
- virtual ErrorStatus getStatus() const = 0;
+ virtual ErrorStatus wait() const = 0;
virtual int getSyncFenceFd(bool shouldDup) const = 0;
};
@@ -43,8 +43,11 @@
CHECK(kExecutionCallback != nullptr);
}
- void wait() const override { kExecutionCallback->wait(); }
- ErrorStatus getStatus() const override { return kExecutionCallback->getStatus(); }
+ ErrorStatus wait() const override {
+ kExecutionCallback->wait();
+ return kExecutionCallback->getStatus();
+ }
+
// Always return -1 as this is not backed by a sync fence.
int getSyncFenceFd(bool /*should_dup*/) const override { return -1; }
@@ -54,9 +57,12 @@
// The SyncFenceEvent wraps sync fence and ExecuteFencedInfoCallback
class SyncFenceEvent : public IEvent {
+ using ExecutionFinishCallback = std::function<ErrorStatus(ErrorStatus)>;
+
public:
- SyncFenceEvent(int sync_fence_fd, const ExecuteFencedInfoCallback& callback)
- : kFencedExecutionCallback(callback) {
+ SyncFenceEvent(int sync_fence_fd, const ExecuteFencedInfoCallback& callback,
+ const ExecutionFinishCallback& finish)
+ : kFencedExecutionCallback(callback), kFinishCallback(finish) {
if (sync_fence_fd > 0) {
// Dup the provided file descriptor
mSyncFenceFd = dup(sync_fence_fd);
@@ -68,26 +74,29 @@
~SyncFenceEvent() { close(mSyncFenceFd); }
// Use syncWait to wait for the sync fence until the status change.
- void wait() const override { syncWait(mSyncFenceFd, -1); }
+ // In case of syncWait error, query the dispatch callback for detailed error status.
+ // This method maps to the NDK ANeuralNetworksEvent_wait, which must be thread-safe.
+ ErrorStatus wait() const override {
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (mFinished) return mError;
- // Get the status of the event.
- // In case of syncWait error, query the dispatch callback for detailed
- // error status.
- ErrorStatus getStatus() const override {
- auto error = ErrorStatus::NONE;
if (mSyncFenceFd > 0 && syncWait(mSyncFenceFd, -1) != FenceState::SIGNALED) {
- error = ErrorStatus::GENERAL_FAILURE;
+ mError = ErrorStatus::GENERAL_FAILURE;
// If there is a callback available, use the callback to get the error code.
if (kFencedExecutionCallback != nullptr) {
auto result = kFencedExecutionCallback();
if (!result.has_value()) {
LOG(ERROR) << "Fenced execution callback failed: " << result.error().message;
- error = result.error().code;
- CHECK_NE(error, ErrorStatus::NONE);
+ mError = result.error().code;
+ CHECK_NE(mError, ErrorStatus::NONE);
}
}
}
- return error;
+ if (kFinishCallback != nullptr) {
+ mError = kFinishCallback(mError);
+ }
+ mFinished = true;
+ return mError;
}
// Return the sync fence fd.
@@ -106,6 +115,11 @@
// TODO(b/148423931): used android::base::unique_fd instead.
int mSyncFenceFd = -1;
const ExecuteFencedInfoCallback kFencedExecutionCallback;
+ const ExecutionFinishCallback kFinishCallback;
+
+ mutable std::mutex mMutex;
+ mutable bool mFinished GUARDED_BY(mMutex) = false;
+ mutable ErrorStatus mError GUARDED_BY(mMutex) = ErrorStatus::NONE;
};
} // namespace android::nn
diff --git a/runtime/ExecutionBuilder.cpp b/runtime/ExecutionBuilder.cpp
index 2f9ecfa..2f09d87 100644
--- a/runtime/ExecutionBuilder.cpp
+++ b/runtime/ExecutionBuilder.cpp
@@ -162,33 +162,9 @@
return mPlan->getSourceModels().getModel(index);
}
-bool ExecutionBuilder::isFinished() const {
- CHECK(!(mFinishedWithoutSyncFence && hasSyncFence()));
- if (mFinishedWithoutSyncFence) {
- return true;
- }
- if (hasSyncFence()) {
- auto r = syncWait(mSyncFenceFd, 0);
- CHECK(r != FenceState::UNKNOWN);
- return r != FenceState::ACTIVE;
- }
- return false;
-}
-
-ExecutionBuilder::Completion ExecutionBuilder::completedWith() const {
- CHECK(isFinished());
- if (hasSyncFence()) {
- auto r = syncWait(mSyncFenceFd, 0);
- CHECK(r == FenceState::SIGNALED || r == FenceState::ERROR);
- return (r == FenceState::SIGNALED) ? Completion::NO_ERROR : Completion::OTHER_ERROR;
- } else {
- return mCompletionWithoutSyncFence;
- }
-}
-
int ExecutionBuilder::setInput(uint32_t index, const ANeuralNetworksOperandType* type,
const void* buffer, size_t length) {
- if (mStarted) {
+ if (computationStarted()) {
LOG(ERROR) << "ANeuralNetworksExecution_setInput called after the "
"execution has started.";
return ANEURALNETWORKS_BAD_STATE;
@@ -225,7 +201,7 @@
size_t length) {
// Should be similar to StepExecutor::setInputOrOutputFromMemory()
- if (mStarted) {
+ if (computationStarted()) {
LOG(ERROR) << "ANeuralNetworksExecution_setInputFromMemory called after the "
"execution has started.";
return ANEURALNETWORKS_BAD_STATE;
@@ -270,7 +246,7 @@
int ExecutionBuilder::setOutput(uint32_t index, const ANeuralNetworksOperandType* type,
void* buffer, size_t length) {
- if (mStarted) {
+ if (computationStarted()) {
LOG(ERROR) << "ANeuralNetworksExecution_setOutput called after the "
"execution has started.";
return ANEURALNETWORKS_BAD_STATE;
@@ -306,7 +282,7 @@
size_t length) {
// Should be similar to StepExecutor::setInputOrOutputFromMemory()
- if (mStarted) {
+ if (computationStarted()) {
LOG(ERROR) << "ANeuralNetworksExecution_setOutputFromMemory called after the "
"execution has started.";
return ANEURALNETWORKS_BAD_STATE;
@@ -356,7 +332,7 @@
<< "with numDevices = 1";
return ANEURALNETWORKS_BAD_DATA;
}
- if (mStarted) {
+ if (computationStarted()) {
LOG(ERROR) << "ANeuralNetworksExecution_setMeasureTiming called after the "
"execution has started.";
return ANEURALNETWORKS_BAD_STATE;
@@ -366,7 +342,7 @@
}
int ExecutionBuilder::getDuration(int32_t durationCode, uint64_t* duration) const {
- if (!isFinished()) {
+ if (!completed()) {
LOG(ERROR) << "ANeuralNetworksExecution_getDuration called before the "
"execution has finished.";
*duration = UINT64_MAX;
@@ -430,7 +406,7 @@
"ANeuralNetworksCompilation_createForDevices with numDevices = 1";
return ANEURALNETWORKS_BAD_DATA;
}
- if (mStarted) {
+ if (computationStarted()) {
LOG(ERROR) << "ANeuralNetworksExecution_setTimeout called after the execution has started.";
return ANEURALNETWORKS_BAD_STATE;
}
@@ -447,7 +423,7 @@
}
int ExecutionBuilder::setLoopTimeout(uint64_t duration) {
- if (mStarted) {
+ if (computationStarted()) {
LOG(ERROR) << "ANeuralNetworksExecution_setLoopTimeout called after the "
"execution has started.";
return ANEURALNETWORKS_BAD_STATE;
@@ -462,7 +438,7 @@
}
int ExecutionBuilder::enableInputAndOutputPadding(bool enable) {
- if (mStarted) {
+ if (computationStarted()) {
LOG(ERROR) << "ANeuralNetworksExecution_enableInputAndOutputPadding called after the "
"execution has started.";
return ANEURALNETWORKS_BAD_STATE;
@@ -476,8 +452,18 @@
return ANEURALNETWORKS_NO_ERROR;
}
+int ExecutionBuilder::setReusable(bool reusable) {
+ if (computationStarted()) {
+ LOG(ERROR) << "ANeuralNetworksExecution_setReusable called after the "
+ "execution has started.";
+ return ANEURALNETWORKS_BAD_STATE;
+ }
+ mReusable = reusable;
+ return ANEURALNETWORKS_NO_ERROR;
+}
+
int ExecutionBuilder::getOutputOperandDimensions(uint32_t index, uint32_t* dimensions) {
- if (!isFinished()) {
+ if (!completed()) {
LOG(ERROR) << "ANeuralNetworksExecution_getOutputOperandDimensions called before the "
"execution has finished.";
return ANEURALNETWORKS_BAD_STATE;
@@ -506,7 +492,7 @@
}
int ExecutionBuilder::getOutputOperandRank(uint32_t index, uint32_t* rank) {
- if (!isFinished()) {
+ if (!completed()) {
LOG(ERROR) << "ANeuralNetworksExecution_getOutputOperandRank called before the "
"execution has finished.";
return ANEURALNETWORKS_BAD_STATE;
@@ -527,6 +513,22 @@
: ANEURALNETWORKS_OUTPUT_INSUFFICIENT_SIZE;
}
+bool ExecutionBuilder::checkAndSetComputationState(const char* name) {
+ std::lock_guard<std::mutex> lock(mStateWriteMutex);
+ if (!mReusable && mState == State::COMPLETED) {
+ LOG(ERROR) << "ANeuralNetworksExecution_" << name
+ << " called on a non-reusable execution that has already completed";
+ return false;
+ }
+ if (mState == State::COMPUTATION) {
+ LOG(ERROR) << "ANeuralNetworksExecution_" << name
+ << " called on an execution that has already started";
+ return false;
+ }
+ mState = State::COMPUTATION;
+ return true;
+}
+
// Attempt synchronous execution of full model on CPU.
// TODO: How should we handle timing in this case?
// For Q this is irrelevant: We only support timing in conjunction
@@ -809,14 +811,6 @@
// If the code reached the end of the plan without error, then return
// with no error.
if (executor == nullptr) {
- // If the final step returns a -1 for sync fence, the execution is finished.
- // Update the output shapes.
- if (syncFence == -1) {
- // TODO(miaowang): support dynamic output shape only with memory domain.
- // For now just return the initial output shapes.
- executionBuilder->finishWithoutSyncFence(
- ErrorStatus::NONE, executionBuilder->getInitialOutputShapes());
- }
return {ANEURALNETWORKS_NO_ERROR, syncFence, executeFencedInfoCallback};
}
const bool executorIsCpu = executor->isCpu();
@@ -868,9 +862,7 @@
}
}
auto [fullN, fullOutputShapes, fullTiming] = cpuFallbackFull(executionBuilder);
- const ErrorStatus fullStatus = convertResultCodeToErrorStatus(fullN);
syncFence = -1;
- executionBuilder->finishWithoutSyncFence(fullStatus, fullOutputShapes);
executionBuilder->reportTimingWithoutFencedExecutionCallback(fullTiming);
return std::make_tuple(fullN, syncFence, nullptr);
}
@@ -878,9 +870,7 @@
int ExecutionBuilder::computeFenced(const std::vector<int>& waitFor,
uint64_t timeoutDurationAfterFence, int* syncFence) {
CHECK(syncFence != nullptr);
- if (mStarted) {
- LOG(ERROR) << "ANeuralNetworksExecution_startComputeWithDependencies"
- " called on an execution that has already started";
+ if (!checkAndSetComputationState("startComputeWithDependencies")) {
return ANEURALNETWORKS_BAD_STATE;
}
if (timeoutDurationAfterFence > 0) {
@@ -890,7 +880,7 @@
"duration on an ANeuralNetworksExecution "
"created from an ANeuralNetworksCompilation that was not created by "
"ANeuralNetworksCompilation_createForDevices with numDevices = 1";
- return ANEURALNETWORKS_BAD_DATA;
+ return finishComputation(ANEURALNETWORKS_BAD_DATA, {});
}
}
const auto deadline = makeDeadline(mTimeoutDuration);
@@ -898,14 +888,14 @@
if (p.state() == ModelArgumentInfo::UNSPECIFIED) {
LOG(ERROR) << "ANeuralNetworksExecution_startComputeWithDependencies"
" not all inputs specified";
- return ANEURALNETWORKS_BAD_DATA;
+ return finishComputation(ANEURALNETWORKS_BAD_DATA, {});
}
}
for (auto& p : mOutputs) {
if (p.state() == ModelArgumentInfo::UNSPECIFIED) {
LOG(ERROR) << "ANeuralNetworksExecution_startComputeWithDependencies"
" not all outputs specified";
- return ANEURALNETWORKS_BAD_DATA;
+ return finishComputation(ANEURALNETWORKS_BAD_DATA, {});
}
}
for (uint32_t i = 0; i < mOutputs.size(); i++) {
@@ -914,10 +904,13 @@
"ANeuralNetworksExecution_startComputeWithDependencies", false)) {
LOG(ERROR) << "ANeuralNetworksExecution_startComputeWithDependencies"
" not all outputs have fully specified dimensions";
- return ANEURALNETWORKS_BAD_DATA;
+ return finishComputation(ANEURALNETWORKS_BAD_DATA, {});
}
}
- mStarted = true;
+
+ // Unlike ExecutionBuilder::compute, we do not need to reset output dimensions here because
+ // fenced executions do not support dynamic output shape.
+
const bool allowCpuFallback = DeviceManager::partitioningAllowsFallback(mPartitioning);
std::shared_ptr<ExecutionPlan::Controller> controller = mPlan->makeController(this, nullptr);
VLOG(EXECUTION) << "ExecutionBuilder::computeFenced";
@@ -926,6 +919,13 @@
startComputeFenced(this, *mPlan, controller, waitFor, timeoutDurationAfterFence,
deadline, allowCpuFallback);
*syncFence = mSyncFenceFd;
+ // If there is an error, call finishComputation to mark the computation as completed.
+ // Otherwise, we will call finishComputation in SyncFenceEvent::wait().
+ if (result != ANEURALNETWORKS_NO_ERROR) {
+ // TODO(miaowang): support dynamic output shape only with memory domain.
+ // For now just return empty output shapes.
+ result = finishComputation(result, {});
+ }
return result;
}
@@ -944,40 +944,40 @@
// TODO validate that we have full types for all inputs and outputs,
// that the graph is not cyclic,
- auto name = [synchronous, burstBuilder] {
- return burstBuilder ? "burstCompute" : synchronous ? "compute" : "startCompute";
- };
- if (mStarted) {
- LOG(ERROR) << "ANeuralNetworksExecution_" << name()
- << " called on an execution that has already started";
+ const char* name = burstBuilder ? "burstCompute" : synchronous ? "compute" : "startCompute";
+ if (!checkAndSetComputationState(name)) {
return ANEURALNETWORKS_BAD_STATE;
}
for (auto& p : mInputs) {
if (p.state() == ModelArgumentInfo::UNSPECIFIED) {
- LOG(ERROR) << "ANeuralNetworksExecution_" << name() << " not all inputs specified";
- return ANEURALNETWORKS_BAD_DATA;
+ LOG(ERROR) << "ANeuralNetworksExecution_" << name << " not all inputs specified";
+ return finishComputation(ANEURALNETWORKS_BAD_DATA, {});
} else if (p.state() == ModelArgumentInfo::MEMORY) {
const RuntimeMemory* memory = mMemories[p.locationAndLength().poolIndex];
if (!memory->getValidator().validateInputDimensions(p.dimensions())) {
- return ANEURALNETWORKS_OP_FAILED;
+ return finishComputation(ANEURALNETWORKS_OP_FAILED, {});
}
}
}
for (auto& p : mOutputs) {
if (p.state() == ModelArgumentInfo::UNSPECIFIED) {
- LOG(ERROR) << "ANeuralNetworksExecution_" << name() << " not all outputs specified";
- return ANEURALNETWORKS_BAD_DATA;
+ LOG(ERROR) << "ANeuralNetworksExecution_" << name << " not all outputs specified";
+ return finishComputation(ANEURALNETWORKS_BAD_DATA, {});
}
}
+ // Reset output dimensions.
+ for (auto& output : mOutputs) {
+ output.reset();
+ }
+
auto wrappedFinish = [this](ErrorStatus error, const std::vector<OutputShape>& outputShapes) {
- return finishWithoutSyncFence(error, outputShapes);
+ return finishComputation(error, outputShapes);
};
// TODO: For asynchronous execution, entire plan-based-path should run in an
// asynchronous thread -- take the asynchronous thread logic out of
// CpuPreparedModel::execute() and use it to wrap the plan-based-path.
- mStarted = true;
const bool allowCpuFallback = DeviceManager::partitioningAllowsFallback(mPartitioning);
std::shared_ptr<ExecutionPlan::Controller> controller =
mPlan->makeController(this, burstBuilder);
@@ -1088,33 +1088,36 @@
return true;
}
-ErrorStatus ExecutionBuilder::finishWithoutSyncFence(ErrorStatus status,
- const std::vector<OutputShape>& outputShapes) {
- CHECK(!mFinishedWithoutSyncFence) << "ExecutionBuilder::finishWithoutSyncFence is called twice";
- CHECK(!hasSyncFence())
- << "ExecutionBuilder::finishWithoutSyncFence is called when hasSyncFence()";
+int ExecutionBuilder::finishComputation(int result, const std::vector<OutputShape>& outputShapes) {
+ const auto status = convertResultCodeToErrorStatus(result);
if (!updateOutputShapes(status, outputShapes) || !updateMemories()) {
- status = ErrorStatus::GENERAL_FAILURE;
+ result = ANEURALNETWORKS_OP_FAILED;
}
- bool success = status == ErrorStatus::NONE;
+ bool success = result == ANEURALNETWORKS_NO_ERROR;
for (const auto& output : mOutputs) {
if (output.state() != ModelArgumentInfo::MEMORY) continue;
const RuntimeMemory* memory = mMemories[output.locationAndLength().poolIndex];
memory->getValidator().setInitialized(success);
}
- switch (convertErrorStatusToResultCode(status)) {
+ switch (result) {
case ANEURALNETWORKS_NO_ERROR:
- mCompletionWithoutSyncFence = Completion::NO_ERROR;
+ mCompletion = Completion::NO_ERROR;
break;
case ANEURALNETWORKS_OUTPUT_INSUFFICIENT_SIZE:
- mCompletionWithoutSyncFence = Completion::OUTPUT_INSUFFICIENT_SIZE;
+ mCompletion = Completion::OUTPUT_INSUFFICIENT_SIZE;
break;
default:
- mCompletionWithoutSyncFence = Completion::OTHER_ERROR;
+ mCompletion = Completion::OTHER_ERROR;
break;
}
- mFinishedWithoutSyncFence = true;
- return status;
+ {
+ std::lock_guard<std::mutex> lock(mStateWriteMutex);
+ CHECK(mState != State::PREPARATION)
+ << "ExecutionBuilder::finishComputation is called in the preparation state";
+ CHECK(mState != State::COMPLETED) << "ExecutionBuilder::finishComputation is called twice";
+ mState = State::COMPLETED;
+ }
+ return result;
}
std::string toString(StepExecutor::UpdateOutputShapes updateOutputShapes) {
diff --git a/runtime/ExecutionBuilder.h b/runtime/ExecutionBuilder.h
index 7c4af9e..bea7a66 100644
--- a/runtime/ExecutionBuilder.h
+++ b/runtime/ExecutionBuilder.h
@@ -79,6 +79,8 @@
int enableInputAndOutputPadding(bool enable);
+ int setReusable(bool reusable);
+
int computeFenced(const std::vector<int>& wait_for, uint64_t timeoutDurationAfterFence,
int* sync_fence);
@@ -108,14 +110,19 @@
return getSourceModel(sourceOperandIndex.first)->getOperand(sourceOperandIndex.second);
}
- ErrorStatus finishWithoutSyncFence(ErrorStatus error,
- const std::vector<OutputShape>& outputShapes);
+ // This method will be called at the end of all computation paths to change the state
+ // of the execution object and update output shapes / memories.
+ int finishComputation(int result, const std::vector<OutputShape>& outputShapes);
+ ErrorStatus finishComputation(ErrorStatus error, const std::vector<OutputShape>& outputShapes) {
+ const int result = finishComputation(convertErrorStatusToResultCode(error), outputShapes);
+ return convertResultCodeToErrorStatus(result);
+ }
const ExecuteFencedInfoCallback& getExecuteFencedInfoCallback() {
return mFencedExecutionCallback;
}
- bool inFlight() const { return mStarted && !isFinished(); }
+ bool inFlight() const { return mState == State::COMPUTATION; }
const ModelArgumentInfo& getInputInfo(uint32_t index) const { return mInputs[index]; }
const ModelArgumentInfo& getOutputInfo(uint32_t index) const { return mOutputs[index]; }
@@ -178,25 +185,31 @@
// Amount of time to complete or abort a loop.
uint64_t mLoopTimeoutDuration = operation_while::kTimeoutNsDefault;
- // Properties cannot be set once the execution has started.
- std::atomic_bool mStarted = false;
+ // The state of the execution.
+ // Properties can only been set when the execution is in the state State::PREPARATION.
+ // Timing and output shapes can only be queried when the execution is in the state
+ // State::COMPLETED.
+ enum class State { PREPARATION, COMPUTATION, COMPLETED };
+ std::atomic<State> mState = State::PREPARATION;
+ bool computationStarted() const { return mState != State::PREPARATION; }
+ bool completed() const { return mState == State::COMPLETED; }
- // Timing and output shapes can only be queried after the execution is
- // finished. This field only becomes true if !hasSyncFence().
- // See isFinished().
- std::atomic_bool mFinishedWithoutSyncFence = false;
+ // Mutex to guard mState. Note that we only guard mState writes to reduce the number
+ // lock/unlock constructing an execution. We provide no thread-safety guarantee to the
+ // ANeuralNetworksExecution object.
+ std::mutex mStateWriteMutex;
- bool isFinished() const;
+ // Return false if the execution is in a bad state for starting computation.
+ // Otherwise, return true and set the state to State::COMPUTATION.
+ bool checkAndSetComputationState(const char* name);
- // With what error status has execution completed? This field only takes on
- // a meaningful value if !hasSyncFence().
- // See completedWith().
+ // With what error status has execution completed?
enum class Completion { NO_ERROR, OUTPUT_INSUFFICIENT_SIZE, OTHER_ERROR };
- Completion mCompletionWithoutSyncFence = Completion::OTHER_ERROR;
-
- // With what error status has execution completed? Must only be called if
- // isFinished().
- Completion completedWith() const;
+ Completion mCompletion = Completion::OTHER_ERROR;
+ Completion completedWith() const {
+ CHECK(mState == State::COMPLETED);
+ return mCompletion;
+ }
// The sync fence fd that is created in the computeFenced call, if any.
// (Sometimes no sync fence fd will be created.)
@@ -216,6 +229,9 @@
// enableInputAndOutputPadding may only be called before any call of
// set{Input,Output}[FromMemory]
bool mHasCalledSetInputOutput = false;
+
+ // Can compute APIs be invoked multiple times on the execution object?
+ bool mReusable = false;
};
// class StepExecutor is used to execute a single "step" in a
diff --git a/runtime/ModelArgumentInfo.cpp b/runtime/ModelArgumentInfo.cpp
index da7339a..40d7bf7 100644
--- a/runtime/ModelArgumentInfo.cpp
+++ b/runtime/ModelArgumentInfo.cpp
@@ -113,12 +113,13 @@
int ModelArgumentInfo::updateDimensionInfo(const Operand& operand,
const ANeuralNetworksOperandType* newType) {
if (newType == nullptr) {
- mDimensions = operand.dimensions;
+ mInitialDimensions = operand.dimensions;
} else {
const uint32_t count = newType->dimensionCount;
- mDimensions = std::vector<uint32_t>(count);
- std::copy(&newType->dimensions[0], &newType->dimensions[count], mDimensions.begin());
+ mInitialDimensions = std::vector<uint32_t>(count);
+ std::copy(&newType->dimensions[0], &newType->dimensions[count], mInitialDimensions.begin());
}
+ mDimensions = mInitialDimensions;
return ANEURALNETWORKS_NO_ERROR;
}
diff --git a/runtime/ModelArgumentInfo.h b/runtime/ModelArgumentInfo.h
index cf01ee6..00ef08f 100644
--- a/runtime/ModelArgumentInfo.h
+++ b/runtime/ModelArgumentInfo.h
@@ -93,6 +93,12 @@
return mLocationAndLength;
}
+ // Restore updatable properties to creation-time values.
+ void reset() {
+ mDimensions = mInitialDimensions;
+ mIsSufficient = true;
+ }
+
private:
int updateDimensionInfo(const Operand& operand, const ANeuralNetworksOperandType* newType);
@@ -100,16 +106,26 @@
// has no value, or has not been specified.
// If POINTER then:
// mLocationAndLength.length is valid.
- // mDimensions is valid.
// mBuffer is valid.
// If MEMORY then:
// mLocationAndLength.{poolIndex, offset, length} is valid.
+ // In both MEMORY and POINTER cases:
+ // mInitialDimensions is valid.
// mDimensions is valid.
- State mState = UNSPECIFIED; // fixed at creation
- void* mBuffer = nullptr; // fixed at creation
- DataLocation mLocationAndLength; // can be updated after creation
- std::vector<uint32_t> mDimensions; // can be updated after creation
- bool mIsSufficient = true; // can be updated after creation
+ // mIsSufficient is valid.
+
+ // Properties that are fixed at creation.
+ State mState = UNSPECIFIED;
+ void* mBuffer = nullptr;
+ std::vector<uint32_t> mInitialDimensions;
+ // TODO(b/183021356): This field is logically fixed at creation, but actually updated in
+ // StepExecutor::mapInputOrOutput when constructing StepExecutor ModelArgumentInfos from
+ // ExecutionBuilder ModelArgumentInfos. We should find a way to avoid this update.
+ DataLocation mLocationAndLength;
+
+ // Properties that are updatable after creation.
+ std::vector<uint32_t> mDimensions;
+ bool mIsSufficient = true;
};
// Convert ModelArgumentInfo to HIDL Request::Argument. For pointer arguments, use the location
diff --git a/runtime/NeuralNetworks.cpp b/runtime/NeuralNetworks.cpp
index 3df11df..c16bf39 100644
--- a/runtime/NeuralNetworks.cpp
+++ b/runtime/NeuralNetworks.cpp
@@ -1381,8 +1381,7 @@
}
IEvent* e = reinterpret_cast<IEvent*>(event);
- e->wait();
- return convertErrorStatusToResultCode(e->getStatus());
+ return convertErrorStatusToResultCode(e->wait());
}
void ANeuralNetworksEvent_free(ANeuralNetworksEvent* event) {
@@ -1481,7 +1480,8 @@
*event = nullptr;
return ANEURALNETWORKS_BAD_DATA;
}
- std::unique_ptr<SyncFenceEvent> e = std::make_unique<SyncFenceEvent>(syncFenceFd, nullptr);
+ std::unique_ptr<SyncFenceEvent> e =
+ std::make_unique<SyncFenceEvent>(syncFenceFd, nullptr, nullptr);
*event = reinterpret_cast<ANeuralNetworksEvent*>(e.release());
return ANEURALNETWORKS_NO_ERROR;
}
@@ -1559,8 +1559,11 @@
int syncFenceToSignal = -1;
int n = r->computeFenced(waitForList, duration, &syncFenceToSignal);
- std::unique_ptr<SyncFenceEvent> e =
- std::make_unique<SyncFenceEvent>(syncFenceToSignal, r->getExecuteFencedInfoCallback());
+ std::unique_ptr<SyncFenceEvent> e = std::make_unique<SyncFenceEvent>(
+ syncFenceToSignal, r->getExecuteFencedInfoCallback(),
+ // TODO(miaowang): support dynamic output shape only with memory domain.
+ // For now just return empty output shapes.
+ [r](ErrorStatus status) { return r->finishComputation(status, {}); });
if (n != ANEURALNETWORKS_NO_ERROR) {
*event = nullptr;
} else {
@@ -1635,3 +1638,13 @@
const CompilationBuilder* c = reinterpret_cast<const CompilationBuilder*>(compilation);
return c->getPreferredMemoryPaddingForOutput(index, padding);
}
+
+int ANeuralNetworksExecution_setReusable(ANeuralNetworksExecution* execution, bool reusable) {
+ NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "ANeuralNetworksExecution_setReusable");
+ if (!execution) {
+ LOG(ERROR) << "ANeuralNetworksExecution_setReusable passed a nullptr";
+ return ANEURALNETWORKS_UNEXPECTED_NULL;
+ }
+ ExecutionBuilder* r = reinterpret_cast<ExecutionBuilder*>(execution);
+ return r->setReusable(reusable);
+}
diff --git a/runtime/include/NeuralNetworks.h b/runtime/include/NeuralNetworks.h
index c7062ed..18de09f 100644
--- a/runtime/include/NeuralNetworks.h
+++ b/runtime/include/NeuralNetworks.h
@@ -559,7 +559,12 @@
* then execution will be aborted and {@link ANEURALNETWORKS_MISSED_DEADLINE_*}
* will be returned.
*
- * See {@link ANeuralNetworksExecution} for information on multithreaded usage.
+ * Before NNAPI feature level 5, this function may only be invoked when the execution is in the
+ * preparation state. Starting at NNAPI feature level 5, if the user sets the execution to be
+ * reusable by {@link ANeuralNetworksExecution_setReusable}, this function may also be invoked when
+ * the execution is in the completed state.
+ *
+ * See {@link ANeuralNetworksExecution} for information on execution states and multithreaded usage.
*
* See {@link ANeuralNetworksExecution_burstCompute} for burst synchronous execution.
* See {@link ANeuralNetworksExecution_startCompute} for regular asynchronous execution.
@@ -578,12 +583,11 @@
/**
* Get the dimensional information of the specified output operand of the model of the
- * {@link ANeuralNetworksExecution}.
+ * latest computation evaluated on {@link ANeuralNetworksExecution}.
*
- * The execution must have completed. On asynchronous execution initiated by
- * {@link ANeuralNetworksExecution_startCompute} or
- * {@link ANeuralNetworksExecution_startComputeWithDependencies},
- * {@link ANeuralNetworksEvent_wait} must be called prior to this function.
+ * This function may only be invoked when the execution is in the completed state.
+ *
+ * See {@link ANeuralNetworksExecution} for information on execution states.
*
* @param execution The execution to be queried.
* @param index The index of the output argument we are querying. It is
@@ -604,12 +608,12 @@
/**
* Get the dimensional information of the specified output operand of the model of the
- * {@link ANeuralNetworksExecution}. The target output operand cannot be a scalar.
+ * latest computation evaluated on {@link ANeuralNetworksExecution}. The target output operand
+ * cannot be a scalar.
*
- * The execution must have completed. On asynchronous execution initiated by
- * {@link ANeuralNetworksExecution_startCompute} or
- * {@link ANeuralNetworksExecution_startComputeWithDependencies},
- * {@link ANeuralNetworksEvent_wait} must be called prior to this function.
+ * This function may only be invoked when the execution is in the completed state.
+ *
+ * See {@link ANeuralNetworksExecution} for information on execution states.
*
* @param execution The execution to be queried.
* @param index The index of the output argument we are querying. It is an index into the lists
@@ -680,6 +684,13 @@
* {@link ANeuralNetworksExecution} launched before the previous has finished
* will result in ANEURALNETWORKS_BAD_STATE.</p>
*
+ * Before NNAPI feature level 5, this function may only be invoked when the execution is in the
+ * preparation state. Starting at NNAPI feature level 5, if the user sets the execution to be
+ * reusable by {@link ANeuralNetworksExecution_setReusable}, this function may also be invoked when
+ * the execution is in the completed state.
+ *
+ * See {@link ANeuralNetworksExecution} for information on execution states and multithreaded usage.
+ *
* See {@link ANeuralNetworksExecution_compute} for synchronous execution.
* See {@link ANeuralNetworksExecution_startCompute} for regular asynchronous execution.
* See {@link ANeuralNetworksExecution_startComputeWithDependencies} for
@@ -748,7 +759,9 @@
* {@link ANeuralNetworksDevice_getFeatureLevel} that is lower than
* {@link ANEURALNETWORKS_FEATURE_LEVEL_3}, then the duration will not be measured.
*
- * See {@link ANeuralNetworksExecution} for information on multithreaded usage.
+ * This function may only be invoked when the execution is in the preparation state.
+ *
+ * See {@link ANeuralNetworksExecution} for information on execution states and multithreaded usage.
*
* Available since NNAPI feature level 3.
*
@@ -761,12 +774,12 @@
__INTRODUCED_IN(29);
/**
- * Get the time spent in the specified {@link ANeuralNetworksExecution}, in nanoseconds.
+ * Get the time spent in the latest computation evaluated on the specified
+ * {@link ANeuralNetworksExecution}, in nanoseconds.
*
- * The execution must have completed. On asynchronous execution initiated by
- * {@link ANeuralNetworksExecution_startCompute} or
- * {@link ANeuralNetworksExecution_startComputeWithDependencies},
- * {@link ANeuralNetworksEvent_wait} must be called prior to this function.
+ * This function may only be invoked when the execution is in the completed state.
+ *
+ * See {@link ANeuralNetworksExecution} for information on execution states.
*
* @param execution The execution to be queried.
* @param durationCode The measurement to be queried, specified by {@link DurationCode}.
@@ -1394,7 +1407,9 @@
* by the driver to access data in chunks, for efficiency. Passing a length argument with value
* less than the raw size of the input will result in ANEURALNETWORKS_BAD_DATA.
*
- * See {@link ANeuralNetworksExecution} for information on multithreaded usage.
+ * This function may only be invoked when the execution is in the preparation state.
+ *
+ * See {@link ANeuralNetworksExecution} for information on execution states and multithreaded usage.
* See {@link ANeuralNetworksCompilation_getPreferredMemoryAlignmentForInput} and
* {@link ANeuralNetworksCompilation_getPreferredMemoryPaddingForInput} for information on getting
* preferred buffer alignment and padding, to improve performance.
@@ -1458,7 +1473,9 @@
* by the driver to access data in chunks, for efficiency. Passing a length argument with value
* less than the raw size of the input will result in ANEURALNETWORKS_BAD_DATA.
*
- * See {@link ANeuralNetworksExecution} for information on multithreaded usage.
+ * This function may only be invoked when the execution is in the preparation state.
+ *
+ * See {@link ANeuralNetworksExecution} for information on execution states and multithreaded usage.
* See {@link ANeuralNetworksMemory_createFromAHardwareBuffer} for information on
* AHardwareBuffer usage.
* See {@link ANeuralNetworksMemory_createFromDesc} for information on usage of memory objects
@@ -1520,7 +1537,9 @@
* by the driver to access data in chunks, for efficiency. Passing a length argument with value
* less than the raw size of the output will result in ANEURALNETWORKS_BAD_DATA.
*
- * See {@link ANeuralNetworksExecution} for information on multithreaded usage.
+ * This function may only be invoked when the execution is in the preparation state.
+ *
+ * See {@link ANeuralNetworksExecution} for information on execution states and multithreaded usage.
* See {@link ANeuralNetworksCompilation_getPreferredMemoryAlignmentForOutput} and
* {@link ANeuralNetworksCompilation_getPreferredMemoryPaddingForOutput} for information on getting
* preferred buffer alignment and padding, to improve performance.
@@ -1588,7 +1607,9 @@
* by the driver to access data in chunks, for efficiency. Passing a length argument with value
* less than the raw size of the output will result in ANEURALNETWORKS_BAD_DATA.
*
- * See {@link ANeuralNetworksExecution} for information on multithreaded usage.
+ * This function may only be invoked when the execution is in the preparation state.
+ *
+ * See {@link ANeuralNetworksExecution} for information on execution states and multithreaded usage.
* See {@link ANeuralNetworksMemory_createFromAHardwareBuffer} for information on
* AHardwareBuffer usage.
* See {@link ANeuralNetworksMemory_createFromDesc} for information on usage of memory objects
@@ -1664,7 +1685,12 @@
* will not complete within the timeout duration, the device may choose to skip
* the execution and instead return {@link ANEURALNETWORKS_MISSED_DEADLINE_*}.
*
- * See {@link ANeuralNetworksExecution} for information on multithreaded usage.
+ * Before NNAPI feature level 5, this function may only be invoked when the execution is in the
+ * preparation state. Starting at NNAPI feature level 5, if the user sets the execution to be
+ * reusable by {@link ANeuralNetworksExecution_setReusable}, this function may also be invoked when
+ * the execution is in the completed state.
+ *
+ * See {@link ANeuralNetworksExecution} for information on execution states and multithreaded usage.
*
* See {@link ANeuralNetworksExecution_compute} for synchronous execution.
* See {@link ANeuralNetworksExecution_burstCompute} for burst synchronous execution.
@@ -1715,7 +1741,9 @@
* {@link ANEURALNETWORKS_FEATURE_LEVEL_4}, then the timeout duration hint will
* be ignored.
*
- * See {@link ANeuralNetworksExecution} for information on multithreaded usage.
+ * This function may only be invoked when the execution is in the preparation state.
+ *
+ * See {@link ANeuralNetworksExecution} for information on execution states and multithreaded usage.
*
* @param execution The execution to be modified.
* @param duration The maximum amount of time in nanoseconds that is expected to
@@ -1741,7 +1769,9 @@
* {@link ANeuralNetworks_getMaximumLoopTimeout} for the default
* and maximum timeout values.
*
- * See {@link ANeuralNetworksExecution} for information on multithreaded usage.
+ * This function may only be invoked when the execution is in the preparation state.
+ *
+ * See {@link ANeuralNetworksExecution} for information on execution states and multithreaded usage.
*
* @param execution The execution to be modified.
* @param duration The maximum amount of time in nanoseconds that can be spent
@@ -1792,7 +1822,7 @@
* the execution will be aborted, and {@link ANEURALNETWORKS_MISSED_DEADLINE_*}
* will be returned here.
*
- * See {@link ANeuralNetworksExecution} for information on multithreaded usage.
+ * See {@link ANeuralNetworksExecution} for information on execution states and multithreaded usage.
*
* Available since NNAPI feature level 1.
*
@@ -1905,7 +1935,12 @@
* will be returned through {@link ANeuralNetworksEvent_wait} on the event
* object.
*
- * See {@link ANeuralNetworksExecution} for information on multithreaded usage.
+ * Before NNAPI feature level 5, this function may only be invoked when the execution is in the
+ * preparation state. Starting at NNAPI feature level 5, if the user sets the execution to be
+ * reusable by {@link ANeuralNetworksExecution_setReusable}, this function may also be invoked when
+ * the execution is in the completed state.
+ *
+ * See {@link ANeuralNetworksExecution} for information on execution states and multithreaded usage.
*
* See {@link ANeuralNetworksExecution_compute} for synchronous execution.
* See {@link ANeuralNetworksExecution_burstCompute} for burst synchronous execution.
@@ -2146,6 +2181,33 @@
const ANeuralNetworksCompilation* compilation, uint32_t index, uint32_t* padding)
__INTRODUCED_IN(31);
+/**
+ * Specifies whether the {@link ANeuralNetworksExecution} can be reused for multiple computations.
+ *
+ * By default, the {@link ANeuralNetworksExecution} is not reusable.
+ *
+ * Setting the execution to be reusable enables multiple computations to be scheduled and evaluated
+ * on the same execution sequentially, either by means of
+ * {@link ANeuralNetworksExecution_burstCompute}, {@link ANeuralNetworksExecution_compute},
+ * {@link ANeuralNetworksExecution_startCompute} or
+ * {@link ANeuralNetworksExecution_startComputeWithDependencies}.
+ *
+ * This function may only be invoked when the execution is in the preparation state.
+ *
+ * See {@link ANeuralNetworksExecution} for information on execution states and multithreaded usage.
+ *
+ * @param execution The execution to be modified.
+ * @param reusable 'true' if the execution is to be reusable, 'false' if not.
+ *
+ * @return ANEURALNETWORKS_NO_ERROR if successful.
+ * ANEURALNETWORKS_UNEXPECTED_NULL if execution is NULL.
+ * ANEURALNETWORKS_BAD_STATE if the execution is not in the preparation state.
+ *
+ * Available since NNAPI feature level 5.
+ */
+int ANeuralNetworksExecution_setReusable(ANeuralNetworksExecution* execution, bool reusable)
+ __INTRODUCED_IN(31);
+
__END_DECLS
#endif // ANDROID_FRAMEWORKS_ML_NN_RUNTIME_NEURAL_NETWORKS_H
diff --git a/runtime/include/NeuralNetworksTypes.h b/runtime/include/NeuralNetworksTypes.h
index 66fab9e..32e8562 100644
--- a/runtime/include/NeuralNetworksTypes.h
+++ b/runtime/include/NeuralNetworksTypes.h
@@ -6083,6 +6083,11 @@
* <li>Associate output buffers or memory regions to the model outputs with
* {@link ANeuralNetworksExecution_setOutput} or
* {@link ANeuralNetworksExecution_setOutputFromMemory}.</li>
+ * <li>Optionally, configure the execution with
+ * {@link ANeuralNetworksExecution_setLoopTimeout},
+ * {@link ANeuralNetworksExecution_setMeasureTiming},
+ * {@link ANeuralNetworksExecution_setReusable}, or
+ * {@link ANeuralNetworksExecution_setTimeout}.
* <li>Apply the model with one of the following:</li><ul>
* <li>Asynchronously with {@link ANeuralNetworksExecution_startCompute}
* or with {@link ANeuralNetworksExecution_startComputeWithDependencies},
@@ -6091,6 +6096,8 @@
* <li>Synchronously with {@link ANeuralNetworksExecution_compute}.</li>
* <li>Synchronously as part of an execution burst with
* {@link ANeuralNetworksExecution_burstCompute}.</li></ul>
+ * If the execution has been marked as reusable, then you can
+ * apply the model more than once.
* <li>Destroy the execution with
* {@link ANeuralNetworksExecution_free}.</li></ul></p>
*
@@ -6099,11 +6106,16 @@
* memory region, or with an operand value in a memory object
* ({@link ANeuralNetworksModel_setOperandValueFromMemory}).</p>
*
- * <p>An execution cannot be modified once
- * {@link ANeuralNetworksExecution_burstCompute},
- * {@link ANeuralNetworksExecution_compute},
- * {@link ANeuralNetworksExecution_startCompute} or
- * {@link ANeuralNetworksExecution_startComputeWithDependencies} has been called on it.</p>
+ * <p>An execution is in the preparation state after it is created by
+ * {@link ANeuralNetworksExecution_create}. An execution may only be modified in the preparation
+ * state. Scheduling a computation by calling {@link ANeuralNetworksExecution_burstCompute},
+ * {@link ANeuralNetworksExecution_compute}, {@link ANeuralNetworksExecution_startCompute},
+ * or {@link ANeuralNetworksExecution_startComputeWithDependencies} will change the state of
+ * the execution object to the computation state. When the computation completes, the state of
+ * the execution object will change from the computation state to the completed state.
+ * The computation is completed when {@link ANeuralNetworksExecution_compute},
+ * {@link ANeuralNetworksExecution_burstCompute}, or {@link ANeuralNetworksEvent_wait}
+ * has returned.</p>
*
* <p>An execution can be applied to a model with
* {@link ANeuralNetworksExecution_burstCompute},
@@ -6112,6 +6124,11 @@
* {@link ANeuralNetworksExecution_startComputeWithDependencies} only once. Create new
* executions to do new evaluations of the model.</p>
*
+ * <p>Starting at NNAPI feature level 5, the application may call
+ * {@link ANeuralNetworksExecution_setReusable} to set an execution to be reusable for multiple
+ * computations. The application may schedule and evaluate a computation again from the completed
+ * state of a reusable execution. The execution cannot be modified between computations.</p>
+ *
* <p>It is the application's responsibility to make sure that only one thread
* modifies an execution at a given time. It is however safe for more than one
* thread to use {@link ANeuralNetworksEvent_wait} at the same time.</p>
@@ -6126,13 +6143,15 @@
* <p>It is also the application's responsibility to ensure that there are no other
* uses of the execution after calling {@link ANeuralNetworksExecution_free}.</p>
*
- * <p>Multiple executions can be scheduled and evaluated concurrently, either by
- * means of {@link ANeuralNetworksExecution_compute} or
- * {@link ANeuralNetworksExecution_burstCompute} (which are synchronous) in
- * different threads, or by means of
+ * <p>It is the application's responsibility to ensure that there are no concurrent computations
+ * scheduled and evaluated on the same execution, either by means of
+ * {@link ANeuralNetworksExecution_compute} or
+ * {@link ANeuralNetworksExecution_burstCompute} (which are synchronous)
+ * in different threads, or by means of
* {@link ANeuralNetworksExecution_startCompute} or
* {@link ANeuralNetworksExecution_startComputeWithDependencies} (which are asynchronous).
- * (Concurrent uses of {@link ANeuralNetworksExecution_burstCompute} must be on
+ * It is however safe to schedule and evaluate multiple computations on different executions
+ * concurrently. (Concurrent uses of {@link ANeuralNetworksExecution_burstCompute} must be on
* different burst objects.) The runtime makes no guarantee on the ordering of
* completion of executions. If it's important to the application, the
* application should enforce the ordering by ensuring that one execution
diff --git a/runtime/include/NeuralNetworksWrapper.h b/runtime/include/NeuralNetworksWrapper.h
index c5af0b5..1a42fbe 100644
--- a/runtime/include/NeuralNetworksWrapper.h
+++ b/runtime/include/NeuralNetworksWrapper.h
@@ -616,6 +616,11 @@
ANeuralNetworksExecution_enableInputAndOutputPadding(mExecution, enable)));
}
+ Result setReusable(bool reusable) {
+ return static_cast<Result>(
+ NNAPI_CALL(ANeuralNetworksExecution_setReusable(mExecution, reusable)));
+ }
+
#ifndef NNTEST_SLTS
Result startCompute(Event* event) {
ANeuralNetworksEvent* ev = nullptr;
diff --git a/runtime/libneuralnetworks.map.txt b/runtime/libneuralnetworks.map.txt
index 043f670..5314634 100644
--- a/runtime/libneuralnetworks.map.txt
+++ b/runtime/libneuralnetworks.map.txt
@@ -78,6 +78,7 @@
ANeuralNetworksExecution_setMeasureTiming; # introduced=Q
ANeuralNetworksExecution_setOutput;
ANeuralNetworksExecution_setOutputFromMemory;
+ ANeuralNetworksExecution_setReusable; # introduced=31
ANeuralNetworksExecution_startCompute;
ANeuralNetworksExecution_startComputeWithDependencies; # introduced=30
ANeuralNetworksExecution_getOutputOperandDimensions; # introduced=Q
diff --git a/runtime/test/TestGenerated.cpp b/runtime/test/TestGenerated.cpp
index 4a4639e..d40dabc 100644
--- a/runtime/test/TestGenerated.cpp
+++ b/runtime/test/TestGenerated.cpp
@@ -82,6 +82,7 @@
bool mExpectFailure = false;
bool mTestQuantizationCoupling = false;
bool mTestDeviceMemory = false;
+ bool mTestReusableExecution = true;
Execution::ComputeMode mComputeMode = Execution::getComputeMode();
};
@@ -104,7 +105,13 @@
class QuantizationCouplingTest : public GeneratedTests {
protected:
- QuantizationCouplingTest() { mTestQuantizationCoupling = true; }
+ QuantizationCouplingTest() {
+ mTestQuantizationCoupling = true;
+ // QuantizationCouplingTest is intended for verifying if a driver supports ASYMM quant8, it
+ // must support SYMM quant8. All the models in QuantizationCouplingTest will also be
+ // executed in other test suites, so there is no need to test reusable execution again.
+ mTestReusableExecution = false;
+ }
};
class DeviceMemoryTest : public GeneratedTests {
@@ -139,16 +146,6 @@
}
}
-static void computeWithPtrs(const TestModel& testModel, Execution* execution,
- Execution::ComputeMode computeMode, Result* result,
- std::vector<TestBuffer>* outputs) {
- {
- NNTRACE_APP(NNTRACE_PHASE_INPUTS_AND_OUTPUTS, "computeWithPtrs example");
- createRequest(testModel, execution, outputs);
- }
- *result = execution->compute(computeMode);
-}
-
static ANeuralNetworksMemory* createDeviceMemoryForInput(const Compilation& compilation,
uint32_t index) {
ANeuralNetworksMemoryDesc* desc = nullptr;
@@ -175,52 +172,53 @@
return memory;
}
-// Set result = Result::NO_ERROR and outputs = {} if the test should be skipped.
-static void computeWithDeviceMemories(const Compilation& compilation, const TestModel& testModel,
- Execution* execution, Execution::ComputeMode computeMode,
- Result* result, std::vector<TestBuffer>* outputs) {
+static void createRequestWithDeviceMemories(const Compilation& compilation,
+ const TestModel& testModel, Execution* execution,
+ std::vector<Memory>* inputMemories,
+ std::vector<Memory>* outputMemories) {
ASSERT_NE(execution, nullptr);
- ASSERT_NE(result, nullptr);
- ASSERT_NE(outputs, nullptr);
- outputs->clear();
- std::vector<Memory> inputMemories, outputMemories;
+ ASSERT_NE(inputMemories, nullptr);
+ ASSERT_NE(outputMemories, nullptr);
- {
- NNTRACE_APP(NNTRACE_PHASE_INPUTS_AND_OUTPUTS, "computeWithDeviceMemories example");
- // Model inputs.
- for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
- SCOPED_TRACE("Input index: " + std::to_string(i));
- const auto& operand = testModel.main.operands[testModel.main.inputIndexes[i]];
- // Omitted input.
- if (operand.data.size() == 0) {
- ASSERT_EQ(Result::NO_ERROR, execution->setInput(i, nullptr, 0));
- continue;
- }
-
- // Create device memory.
- ANeuralNetworksMemory* memory = createDeviceMemoryForInput(compilation, i);
- ASSERT_NE(memory, nullptr);
- auto& wrapperMemory = inputMemories.emplace_back(memory);
-
- // Copy data from TestBuffer to device memory.
- auto ashmem = TestAshmem::createFrom(operand.data);
- ASSERT_NE(ashmem, nullptr);
- ASSERT_EQ(ANeuralNetworksMemory_copy(ashmem->get()->get(), memory),
- ANEURALNETWORKS_NO_ERROR);
- ASSERT_EQ(Result::NO_ERROR, execution->setInputFromMemory(i, &wrapperMemory, 0, 0));
+ // Model inputs.
+ for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
+ SCOPED_TRACE("Input index: " + std::to_string(i));
+ const auto& operand = testModel.main.operands[testModel.main.inputIndexes[i]];
+ // Omitted input.
+ if (operand.data.size() == 0) {
+ ASSERT_EQ(Result::NO_ERROR, execution->setInput(i, nullptr, 0));
+ continue;
}
- // Model outputs.
- for (uint32_t i = 0; i < testModel.main.outputIndexes.size(); i++) {
- SCOPED_TRACE("Output index: " + std::to_string(i));
- ANeuralNetworksMemory* memory = createDeviceMemoryForOutput(compilation, i);
- ASSERT_NE(memory, nullptr);
- auto& wrapperMemory = outputMemories.emplace_back(memory);
- ASSERT_EQ(Result::NO_ERROR, execution->setOutputFromMemory(i, &wrapperMemory, 0, 0));
- }
+ // Create device memory.
+ ANeuralNetworksMemory* memory = createDeviceMemoryForInput(compilation, i);
+ ASSERT_NE(memory, nullptr);
+ auto& wrapperMemory = inputMemories->emplace_back(memory);
+
+ // Copy data from TestBuffer to device memory.
+ auto ashmem = TestAshmem::createFrom(operand.data);
+ ASSERT_NE(ashmem, nullptr);
+ ASSERT_EQ(ANeuralNetworksMemory_copy(ashmem->get()->get(), memory),
+ ANEURALNETWORKS_NO_ERROR);
+ ASSERT_EQ(Result::NO_ERROR, execution->setInputFromMemory(i, &wrapperMemory, 0, 0));
}
- *result = execution->compute(computeMode);
+ // Model outputs.
+ for (uint32_t i = 0; i < testModel.main.outputIndexes.size(); i++) {
+ SCOPED_TRACE("Output index: " + std::to_string(i));
+ ANeuralNetworksMemory* memory = createDeviceMemoryForOutput(compilation, i);
+ ASSERT_NE(memory, nullptr);
+ auto& wrapperMemory = outputMemories->emplace_back(memory);
+ ASSERT_EQ(Result::NO_ERROR, execution->setOutputFromMemory(i, &wrapperMemory, 0, 0));
+ }
+}
+
+static void copyResultsFromDeviceMemories(const TestModel& testModel,
+ const std::vector<Memory>& outputMemories,
+ std::vector<TestBuffer>* outputs) {
+ ASSERT_NE(outputs, nullptr);
+ ASSERT_EQ(testModel.main.outputIndexes.size(), outputMemories.size());
+ outputs->clear();
// Copy out output results.
for (uint32_t i = 0; i < testModel.main.outputIndexes.size(); i++) {
@@ -243,40 +241,54 @@
NNTRACE_APP(NNTRACE_PHASE_EXECUTION, "executeWithCompilation example");
Execution execution(&compilation);
- Result result;
+ execution.setReusable(mTestReusableExecution);
std::vector<TestBuffer> outputs;
+ std::vector<Memory> inputMemories, outputMemories;
if (mTestDeviceMemory) {
- computeWithDeviceMemories(compilation, testModel, &execution, mComputeMode, &result,
- &outputs);
+ createRequestWithDeviceMemories(compilation, testModel, &execution, &inputMemories,
+ &outputMemories);
} else {
- computeWithPtrs(testModel, &execution, mComputeMode, &result, &outputs);
+ createRequest(testModel, &execution, &outputs);
}
- if (result == Result::NO_ERROR && outputs.empty()) {
- return;
- }
+ const auto computeAndCheckResults = [this, &testModel, &execution, &outputs, &outputMemories] {
+ Result result = execution.compute(mComputeMode);
+ if (mTestDeviceMemory) {
+ copyResultsFromDeviceMemories(testModel, outputMemories, &outputs);
+ }
- {
- NNTRACE_APP(NNTRACE_PHASE_RESULTS, "executeWithCompilation example");
- if (mExpectFailure) {
- ASSERT_NE(result, Result::NO_ERROR);
+ if (result == Result::NO_ERROR && outputs.empty()) {
return;
- } else {
- ASSERT_EQ(result, Result::NO_ERROR);
}
- // Check output dimensions.
- for (uint32_t i = 0; i < testModel.main.outputIndexes.size(); i++) {
- SCOPED_TRACE("Output index: " + std::to_string(i));
- const auto& output = testModel.main.operands[testModel.main.outputIndexes[i]];
- if (output.isIgnored) continue;
- std::vector<uint32_t> actualDimensions;
- ASSERT_EQ(Result::NO_ERROR, execution.getOutputOperandDimensions(i, &actualDimensions));
- ASSERT_EQ(output.dimensions, actualDimensions);
- }
+ {
+ NNTRACE_APP(NNTRACE_PHASE_RESULTS, "executeWithCompilation example");
+ if (mExpectFailure) {
+ ASSERT_NE(result, Result::NO_ERROR);
+ return;
+ } else {
+ ASSERT_EQ(result, Result::NO_ERROR);
+ }
- checkResults(testModel, outputs);
+ // Check output dimensions.
+ for (uint32_t i = 0; i < testModel.main.outputIndexes.size(); i++) {
+ SCOPED_TRACE("Output index: " + std::to_string(i));
+ const auto& output = testModel.main.operands[testModel.main.outputIndexes[i]];
+ if (output.isIgnored) continue;
+ std::vector<uint32_t> actualDimensions;
+ ASSERT_EQ(Result::NO_ERROR,
+ execution.getOutputOperandDimensions(i, &actualDimensions));
+ ASSERT_EQ(output.dimensions, actualDimensions);
+ }
+
+ checkResults(testModel, outputs);
+ }
+ };
+
+ computeAndCheckResults();
+ if (mTestReusableExecution) {
+ computeAndCheckResults();
}
}
diff --git a/runtime/test/TestNeuralNetworksWrapper.h b/runtime/test/TestNeuralNetworksWrapper.h
index e3f3527..121be41 100644
--- a/runtime/test/TestNeuralNetworksWrapper.h
+++ b/runtime/test/TestNeuralNetworksWrapper.h
@@ -415,6 +415,10 @@
ANeuralNetworksExecution_enableInputAndOutputPadding(mExecution, enable));
}
+ Result setReusable(bool reusable) {
+ return static_cast<Result>(ANeuralNetworksExecution_setReusable(mExecution, reusable));
+ }
+
Result startCompute(Event* event) {
ANeuralNetworksEvent* ev = nullptr;
Result result = static_cast<Result>(ANeuralNetworksExecution_startCompute(mExecution, &ev));
diff --git a/runtime/test/TestValidation.cpp b/runtime/test/TestValidation.cpp
index 046010c..e1153ba 100644
--- a/runtime/test/TestValidation.cpp
+++ b/runtime/test/TestValidation.cpp
@@ -1268,135 +1268,257 @@
enum class ExecutionType : uint32_t { ASYNC, SYNC, BURST, FENCED };
for (auto executionType :
{ExecutionType::ASYNC, ExecutionType::SYNC, ExecutionType::BURST, ExecutionType::FENCED}) {
- SCOPED_TRACE(static_cast<uint32_t>(executionType));
+ for (bool explicitlyDisableReusablility : {false, true}) {
+ SCOPED_TRACE(static_cast<uint32_t>(executionType));
+ SCOPED_TRACE(explicitlyDisableReusablility);
- ANeuralNetworksExecution* execution;
- ASSERT_EQ(ANeuralNetworksExecution_create(mCompilation, &execution),
- ANEURALNETWORKS_NO_ERROR);
+ ANeuralNetworksExecution* execution;
+ ASSERT_EQ(ANeuralNetworksExecution_create(mCompilation, &execution),
+ ANEURALNETWORKS_NO_ERROR);
- float in0[] = {0.0f, 0.0f}, in1[] = {1.0f, 1.0f}, out0[2];
- int in2 = 0;
- ASSERT_EQ(ANeuralNetworksExecution_setInput(execution, 0, nullptr, &in0, sizeof(in0)),
- ANEURALNETWORKS_NO_ERROR);
- ASSERT_EQ(ANeuralNetworksExecution_setInput(execution, 1, nullptr, &in1, sizeof(in1)),
- ANEURALNETWORKS_NO_ERROR);
- ASSERT_EQ(ANeuralNetworksExecution_setInput(execution, 2, nullptr, &in2, sizeof(in2)),
- ANEURALNETWORKS_NO_ERROR);
- ASSERT_EQ(ANeuralNetworksExecution_setOutput(execution, 0, nullptr, &out0, sizeof(out0)),
- ANEURALNETWORKS_NO_ERROR);
-
- const size_t memorySize = std::max(sizeof(in0), sizeof(out0));
- int memoryFd = ASharedMemory_create("nnMemory", memorySize);
- ASSERT_GT(memoryFd, 0);
- ANeuralNetworksMemory* memory;
- EXPECT_EQ(ANeuralNetworksMemory_createFromFd(memorySize, PROT_READ | PROT_WRITE, memoryFd,
- 0, &memory),
- ANEURALNETWORKS_NO_ERROR);
-
- auto testTooLate = [this, execution, &in0, &out0, memory] {
- // Try a bunch of things that are impermissible if the execution has started.
-
- // Set loop timeout.
- ASSERT_EQ(ANeuralNetworksExecution_setLoopTimeout(execution, kShortWaitInNanoseconds),
- ANEURALNETWORKS_BAD_STATE);
-
- // Enable/Disable input and output padding.
- ASSERT_EQ(ANeuralNetworksExecution_enableInputAndOutputPadding(execution, true),
- ANEURALNETWORKS_BAD_STATE);
- ASSERT_EQ(ANeuralNetworksExecution_enableInputAndOutputPadding(execution, false),
- ANEURALNETWORKS_BAD_STATE);
+ if (explicitlyDisableReusablility) {
+ ASSERT_EQ(ANeuralNetworksExecution_setReusable(execution, false),
+ ANEURALNETWORKS_NO_ERROR);
+ }
// Set inputs and outputs.
+ float in0[] = {0.0f, 0.0f}, in1[] = {1.0f, 1.0f}, out0[2];
+ int in2 = 0;
ASSERT_EQ(ANeuralNetworksExecution_setInput(execution, 0, nullptr, &in0, sizeof(in0)),
- ANEURALNETWORKS_BAD_STATE);
+ ANEURALNETWORKS_NO_ERROR);
+ ASSERT_EQ(ANeuralNetworksExecution_setInput(execution, 1, nullptr, &in1, sizeof(in1)),
+ ANEURALNETWORKS_NO_ERROR);
+ ASSERT_EQ(ANeuralNetworksExecution_setInput(execution, 2, nullptr, &in2, sizeof(in2)),
+ ANEURALNETWORKS_NO_ERROR);
ASSERT_EQ(
ANeuralNetworksExecution_setOutput(execution, 0, nullptr, &out0, sizeof(out0)),
- ANEURALNETWORKS_BAD_STATE);
- ASSERT_EQ(ANeuralNetworksExecution_setInputFromMemory(execution, 0, nullptr, memory, 0,
- sizeof(in0)),
- ANEURALNETWORKS_BAD_STATE);
- ASSERT_EQ(ANeuralNetworksExecution_setOutputFromMemory(execution, 0, nullptr, memory, 0,
- sizeof(out0)),
- ANEURALNETWORKS_BAD_STATE);
+ ANEURALNETWORKS_NO_ERROR);
- // Reuse for asynchronous execution.
- {
- ANeuralNetworksEvent* event;
- ASSERT_EQ(ANeuralNetworksExecution_startCompute(execution, &event),
+ const size_t memorySize = std::max(sizeof(in0), sizeof(out0));
+ int memoryFd = ASharedMemory_create("nnMemory", memorySize);
+ ASSERT_GT(memoryFd, 0);
+ ANeuralNetworksMemory* memory;
+ EXPECT_EQ(ANeuralNetworksMemory_createFromFd(memorySize, PROT_READ | PROT_WRITE,
+ memoryFd, 0, &memory),
+ ANEURALNETWORKS_NO_ERROR);
+
+ auto testTooLate = [this, execution, &in0, &out0, memory] {
+ // Try a bunch of things that are impermissible if the execution has started.
+
+ // Set loop timeout.
+ ASSERT_EQ(
+ ANeuralNetworksExecution_setLoopTimeout(execution, kShortWaitInNanoseconds),
+ ANEURALNETWORKS_BAD_STATE);
+
+ // Enable/Disable input and output padding.
+ ASSERT_EQ(ANeuralNetworksExecution_enableInputAndOutputPadding(execution, true),
ANEURALNETWORKS_BAD_STATE);
+ ASSERT_EQ(ANeuralNetworksExecution_enableInputAndOutputPadding(execution, false),
+ ANEURALNETWORKS_BAD_STATE);
+
+ // Set inputs and outputs.
+ ASSERT_EQ(
+ ANeuralNetworksExecution_setInput(execution, 0, nullptr, &in0, sizeof(in0)),
+ ANEURALNETWORKS_BAD_STATE);
+ ASSERT_EQ(ANeuralNetworksExecution_setOutput(execution, 0, nullptr, &out0,
+ sizeof(out0)),
+ ANEURALNETWORKS_BAD_STATE);
+ ASSERT_EQ(ANeuralNetworksExecution_setInputFromMemory(execution, 0, nullptr, memory,
+ 0, sizeof(in0)),
+ ANEURALNETWORKS_BAD_STATE);
+ ASSERT_EQ(ANeuralNetworksExecution_setOutputFromMemory(execution, 0, nullptr,
+ memory, 0, sizeof(out0)),
+ ANEURALNETWORKS_BAD_STATE);
+
+ // Set reusable.
+ ASSERT_EQ(ANeuralNetworksExecution_setReusable(execution, true),
+ ANEURALNETWORKS_BAD_STATE);
+ ASSERT_EQ(ANeuralNetworksExecution_setReusable(execution, false),
+ ANEURALNETWORKS_BAD_STATE);
+
+ // Reuse for asynchronous execution.
+ {
+ ANeuralNetworksEvent* event;
+ ASSERT_EQ(ANeuralNetworksExecution_startCompute(execution, &event),
+ ANEURALNETWORKS_BAD_STATE);
+ }
+
+ // Reuse for synchronous execution.
+ ASSERT_EQ(ANeuralNetworksExecution_compute(execution), ANEURALNETWORKS_BAD_STATE);
+
+ // Reuse for burst execution.
+ {
+ ANeuralNetworksBurst* burst;
+ ASSERT_EQ(ANeuralNetworksBurst_create(mCompilation, &burst),
+ ANEURALNETWORKS_NO_ERROR);
+ ASSERT_EQ(ANeuralNetworksExecution_burstCompute(execution, burst),
+ ANEURALNETWORKS_BAD_STATE);
+ ANeuralNetworksBurst_free(burst);
+ }
+
+ // Reuse for fenced execution.
+ {
+ ANeuralNetworksEvent* event;
+ ASSERT_EQ(ANeuralNetworksExecution_startComputeWithDependencies(
+ execution, nullptr, 0, 0, &event),
+ ANEURALNETWORKS_BAD_STATE);
+ }
+ };
+
+ // Compute.
+ switch (executionType) {
+ case ExecutionType::ASYNC: {
+ ANeuralNetworksEvent* event;
+ ASSERT_EQ(ANeuralNetworksExecution_startCompute(execution, &event),
+ ANEURALNETWORKS_NO_ERROR);
+ testTooLate();
+ ASSERT_EQ(ANeuralNetworksEvent_wait(event), ANEURALNETWORKS_NO_ERROR);
+ testTooLate();
+ ANeuralNetworksEvent_free(event);
+ break;
+ }
+ case ExecutionType::SYNC: {
+ ASSERT_EQ(ANeuralNetworksExecution_compute(execution),
+ ANEURALNETWORKS_NO_ERROR);
+ testTooLate();
+ break;
+ }
+ case ExecutionType::BURST: {
+ ANeuralNetworksBurst* burst;
+ ASSERT_EQ(ANeuralNetworksBurst_create(mCompilation, &burst),
+ ANEURALNETWORKS_NO_ERROR);
+ ASSERT_EQ(ANeuralNetworksExecution_burstCompute(execution, burst),
+ ANEURALNETWORKS_NO_ERROR);
+ testTooLate();
+ ANeuralNetworksBurst_free(burst);
+ break;
+ }
+ case ExecutionType::FENCED: {
+ ANeuralNetworksEvent* event;
+ ASSERT_EQ(ANeuralNetworksExecution_startComputeWithDependencies(
+ execution, nullptr, 0, 0, &event),
+ ANEURALNETWORKS_NO_ERROR);
+ testTooLate();
+ ASSERT_EQ(ANeuralNetworksEvent_wait(event), ANEURALNETWORKS_NO_ERROR);
+ testTooLate();
+ ANeuralNetworksEvent_free(event);
+ break;
+ }
+ default:
+ FAIL() << "Unreachable";
}
- // Reuse for synchronous execution.
- ASSERT_EQ(ANeuralNetworksExecution_compute(execution), ANEURALNETWORKS_BAD_STATE);
+ // close memory
+ ANeuralNetworksExecution_free(execution);
+ ANeuralNetworksMemory_free(memory);
+ close(memoryFd);
+ }
+ }
+}
- // Reuse for burst execution.
- {
- ANeuralNetworksBurst* burst;
- ASSERT_EQ(ANeuralNetworksBurst_create(mCompilation, &burst),
- ANEURALNETWORKS_NO_ERROR);
- ASSERT_EQ(ANeuralNetworksExecution_burstCompute(execution, burst),
- ANEURALNETWORKS_BAD_STATE);
- ANeuralNetworksBurst_free(burst);
- }
+static void testConcurrentExecution(bool reusable, ANeuralNetworksCompilation* compilation) {
+ ASSERT_EQ(ANeuralNetworksCompilation_finish(compilation), ANEURALNETWORKS_NO_ERROR);
- // Reuse for fenced execution.
- {
- ANeuralNetworksEvent* event;
- ASSERT_EQ(ANeuralNetworksExecution_startComputeWithDependencies(execution, nullptr,
- 0, 0, &event),
- ANEURALNETWORKS_BAD_STATE);
- }
- };
-
- // Compute.
+ enum class ExecutionType : uint32_t { ASYNC, SYNC, BURST, FENCED };
+ const auto compute = [compilation](ExecutionType executionType,
+ ANeuralNetworksExecution* execution) -> int {
switch (executionType) {
case ExecutionType::ASYNC: {
ANeuralNetworksEvent* event;
- ASSERT_EQ(ANeuralNetworksExecution_startCompute(execution, &event),
- ANEURALNETWORKS_NO_ERROR);
- testTooLate();
- ASSERT_EQ(ANeuralNetworksEvent_wait(event), ANEURALNETWORKS_NO_ERROR);
- testTooLate();
+ int result = ANeuralNetworksExecution_startCompute(execution, &event);
+ if (result == ANEURALNETWORKS_NO_ERROR) {
+ result = ANeuralNetworksEvent_wait(event);
+ }
ANeuralNetworksEvent_free(event);
- break;
+ return result;
}
case ExecutionType::SYNC: {
- ASSERT_EQ(ANeuralNetworksExecution_compute(execution), ANEURALNETWORKS_NO_ERROR);
- testTooLate();
- break;
+ return ANeuralNetworksExecution_compute(execution);
}
case ExecutionType::BURST: {
ANeuralNetworksBurst* burst;
- ASSERT_EQ(ANeuralNetworksBurst_create(mCompilation, &burst),
- ANEURALNETWORKS_NO_ERROR);
- ASSERT_EQ(ANeuralNetworksExecution_burstCompute(execution, burst),
- ANEURALNETWORKS_NO_ERROR);
- testTooLate();
+ int result = ANeuralNetworksBurst_create(compilation, &burst);
+ if (result == ANEURALNETWORKS_NO_ERROR) {
+ result = ANeuralNetworksExecution_burstCompute(execution, burst);
+ }
ANeuralNetworksBurst_free(burst);
- break;
+ return result;
}
case ExecutionType::FENCED: {
ANeuralNetworksEvent* event;
- ASSERT_EQ(ANeuralNetworksExecution_startComputeWithDependencies(execution, nullptr,
- 0, 0, &event),
- ANEURALNETWORKS_NO_ERROR);
- testTooLate();
- ASSERT_EQ(ANeuralNetworksEvent_wait(event), ANEURALNETWORKS_NO_ERROR);
- testTooLate();
+ int result = ANeuralNetworksExecution_startComputeWithDependencies(
+ execution, nullptr, 0, 0, &event);
+ if (result == ANEURALNETWORKS_NO_ERROR) {
+ result = ANeuralNetworksEvent_wait(event);
+ }
ANeuralNetworksEvent_free(event);
- break;
+ return result;
}
- default:
- FAIL() << "Unreachable";
}
+ };
- // close memory
- ANeuralNetworksExecution_free(execution);
- ANeuralNetworksMemory_free(memory);
- close(memoryFd);
+ const std::vector<ExecutionType> kExecutionTypes = {
+ ExecutionType::ASYNC, ExecutionType::SYNC, ExecutionType::BURST, ExecutionType::FENCED};
+ for (auto executionType1 : kExecutionTypes) {
+ for (auto executionType2 : kExecutionTypes) {
+ SCOPED_TRACE(static_cast<uint32_t>(executionType1));
+ SCOPED_TRACE(static_cast<uint32_t>(executionType2));
+
+ ANeuralNetworksExecution* execution;
+ ASSERT_EQ(ANeuralNetworksExecution_create(compilation, &execution),
+ ANEURALNETWORKS_NO_ERROR);
+
+ float in0[] = {0.0f, 0.0f}, in1[] = {1.0f, 1.0f}, out0[2];
+ int in2 = 0;
+ ASSERT_EQ(ANeuralNetworksExecution_setInput(execution, 0, nullptr, &in0, sizeof(in0)),
+ ANEURALNETWORKS_NO_ERROR);
+ ASSERT_EQ(ANeuralNetworksExecution_setInput(execution, 1, nullptr, &in1, sizeof(in1)),
+ ANEURALNETWORKS_NO_ERROR);
+ ASSERT_EQ(ANeuralNetworksExecution_setInput(execution, 2, nullptr, &in2, sizeof(in2)),
+ ANEURALNETWORKS_NO_ERROR);
+ ASSERT_EQ(
+ ANeuralNetworksExecution_setOutput(execution, 0, nullptr, &out0, sizeof(out0)),
+ ANEURALNETWORKS_NO_ERROR);
+ ASSERT_EQ(ANeuralNetworksExecution_setReusable(execution, reusable),
+ ANEURALNETWORKS_NO_ERROR);
+
+ // Compute on the same execution concurrently.
+ auto first = std::async(std::launch::async, [compute, executionType1, execution] {
+ return compute(executionType1, execution);
+ });
+ auto second = std::async(std::launch::async, [compute, executionType2, execution] {
+ return compute(executionType2, execution);
+ });
+ const int result1 = first.get();
+ const int result2 = second.get();
+
+ // At least one result must be ANEURALNETWORKS_NO_ERROR. One may return
+ // ANEURALNETWORKS_BAD_STATE if the other is already executing.
+ EXPECT_TRUE(result1 == ANEURALNETWORKS_BAD_STATE ||
+ result1 == ANEURALNETWORKS_NO_ERROR);
+ EXPECT_TRUE(result2 == ANEURALNETWORKS_BAD_STATE ||
+ result2 == ANEURALNETWORKS_NO_ERROR);
+ EXPECT_TRUE(result1 == ANEURALNETWORKS_NO_ERROR || result2 == ANEURALNETWORKS_NO_ERROR);
+
+ // If the execution is not reusable, one result must be ANEURALNETWORKS_BAD_STATE.
+ if (!reusable) {
+ EXPECT_TRUE(result1 == ANEURALNETWORKS_BAD_STATE ||
+ result2 == ANEURALNETWORKS_BAD_STATE);
+ }
+
+ ANeuralNetworksExecution_free(execution);
+ }
}
}
+// Also see TEST_F(ValidationTestBurst, BurstComputeConcurrent)
+TEST_F(ValidationTestCompilation, ReusableExecutionConcurrent) {
+ testConcurrentExecution(/*reusable=*/true, mCompilation);
+}
+TEST_F(ValidationTestCompilation, NonReusableExecutionConcurrent) {
+ testConcurrentExecution(/*reusable=*/false, mCompilation);
+}
+
TEST_F(ValidationTestExecution, SetLoopTimeout) {
EXPECT_EQ(ANeuralNetworksExecution_setLoopTimeout(nullptr, kShortWaitInNanoseconds),
ANEURALNETWORKS_UNEXPECTED_NULL);
@@ -1409,6 +1531,12 @@
ANEURALNETWORKS_UNEXPECTED_NULL);
}
+TEST_F(ValidationTestExecution, ExecutionSetReusable) {
+ EXPECT_EQ(ANeuralNetworksExecution_setReusable(nullptr, true), ANEURALNETWORKS_UNEXPECTED_NULL);
+ EXPECT_EQ(ANeuralNetworksExecution_setReusable(nullptr, false),
+ ANEURALNETWORKS_UNEXPECTED_NULL);
+}
+
TEST_F(ValidationTestExecution, SetInput) {
char buffer[20];
EXPECT_EQ(ANeuralNetworksExecution_setInput(nullptr, 0, nullptr, buffer, sizeof(float)),