Add 1.2 NN HAL interface for dynamic output shape.

Let notify_1_2() notify output shapes.

Document unspecified dimensions and rank.

Bug: 73506513
Bug: 77234888
Test: NeuralNetworksTest_static
Test: VtsHalNeuralnetworksV1_xTargetTest with 1.2 sample driver
Change-Id: I01108913212d9f4aa47daf2f293ea19259925865
This commit is contained in:
Xusong Wang 2018-11-07 09:33:59 -08:00
parent a885b5d982
commit 187c59715c
8 changed files with 141 additions and 34 deletions

View file

@ -139,8 +139,10 @@ Return<void> ExecutionCallback::notify(ErrorStatus errorStatus) {
return Void();
}
Return<void> ExecutionCallback::notify_1_2(ErrorStatus errorStatus) {
Return<void> ExecutionCallback::notify_1_2(ErrorStatus errorStatus,
const hidl_vec<OutputShape>& outputShapes) {
mErrorStatus = errorStatus;
mOutputShapes = outputShapes;
CallbackBase::notify();
return Void();
}
@ -150,6 +152,11 @@ ErrorStatus ExecutionCallback::getStatus() {
return mErrorStatus;
}
const std::vector<OutputShape>& ExecutionCallback::getOutputShapes() {
wait();
return mOutputShapes;
}
} // namespace implementation
} // namespace V1_2
} // namespace neuralnetworks

View file

@ -275,8 +275,9 @@ class ExecutionCallback : public CallbackBase, public IExecutionCallback {
* Either IExecutionCallback::notify or IExecutionCallback::notify_1_2 must
* be called exactly once on a given ExecutionCallback object.
*
* @param status Error status returned from asynchronously preparing the
* model; will be:
* @param status Error status returned from launching the asynchronous task
* (if the launch fails) or from the asynchronous task itself
* (if the launch succeeds). Must be:
* - NONE if the asynchronous execution was successful
* - DEVICE_UNAVAILABLE if driver is offline or busy
* - GENERAL_FAILURE if there is an unspecified error
@ -285,27 +286,73 @@ class ExecutionCallback : public CallbackBase, public IExecutionCallback {
* - INVALID_ARGUMENT if the input request is invalid
*/
Return<void> notify(ErrorStatus status) override;
Return<void> notify_1_2(ErrorStatus status) override;
/**
* Similar to IExecutionCallback::notify, but for V1_2::IPreparedModel to
* also notify output shapes along with error status.
*
* @param status Error status returned from launching the asynchronous task
* (if the launch fails) or from the asynchronous task itself
* (if the launch succeeds). Must be:
* - NONE if the asynchronous execution was successful
* - DEVICE_UNAVAILABLE if driver is offline or busy
* - GENERAL_FAILURE if the asynchronous task resulted in an
* unspecified error
* - OUTPUT_INSUFFICIENT_SIZE if at least one output
* operand buffer is not large enough to store the
* corresponding output
* - INVALID_ARGUMENT if one of the input arguments to
* prepareModel is invalid
* @param outputShapes A list of shape information of model output operands.
* The index into "outputShapes" corresponds to the index
* of the output operand in the Request outputs vector.
* outputShapes must be empty unless the status is either
* NONE or OUTPUT_INSUFFICIENT_SIZE.
*/
Return<void> notify_1_2(ErrorStatus status, const hidl_vec<OutputShape>& outputShapes) override;
/**
* Retrieves the error status returned from the asynchronous task launched
* by IPreparedModel::execute. If IPreparedModel::execute has not finished
* by either IPreparedModel::execute or IPreparedModel::execute_1_2. If
* IPreparedModel::execute or IPreparedModel::execute_1_2 has not finished
* asynchronously executing, this call will block until the asynchronous task
* notifies the object.
*
* @return status Error status returned from asynchronously preparing the
* model; will be:
* @return status Error status returned from launching the asynchronous task
* (if the launch fails) or from the asynchronous task itself
* (if the launch succeeds). Must be:
* - NONE if the asynchronous execution was successful
* - DEVICE_UNAVAILABLE if driver is offline or busy
* - GENERAL_FAILURE if there is an unspecified error
* - OUTPUT_INSUFFICIENT_SIZE if provided output buffer is
* not large enough to store the resultant values
* - INVALID_ARGUMENT if the input request is invalid
* - GENERAL_FAILURE if the asynchronous task resulted in an
* unspecified error
* - OUTPUT_INSUFFICIENT_SIZE if at least one output
* operand buffer is not large enough to store the
* corresponding output
* - INVALID_ARGUMENT if one of the input arguments to
* prepareModel is invalid
*/
ErrorStatus getStatus();
private:
/**
* Retrieves the output shapes returned from the asynchronous task launched
* by IPreparedModel::execute_1_2. If IPreparedModel::execute_1_2 has not finished
* asynchronously executing, this call will block until the asynchronous task
* notifies the object.
*
* If the asynchronous task was launched by IPreparedModel::execute, an empty vector
* will be returned.
*
* @return outputShapes A list of shape information of model output operands.
* The index into "outputShapes" corresponds to the index
* of the output operand in the Request outputs vector.
* outputShapes must be empty unless the status is either
* NONE or OUTPUT_INSUFFICIENT_SIZE.
*/
const std::vector<OutputShape>& getOutputShapes();
private:
ErrorStatus mErrorStatus;
std::vector<OutputShape> mOutputShapes;
};

View file

@ -89,13 +89,24 @@ static Return<ErrorStatus> ExecutePreparedModel(sp<V1_2::IPreparedModel>& prepar
sp<ExecutionCallback>& callback) {
return preparedModel->execute_1_2(request, callback);
}
static Return<ErrorStatus> ExecutePreparedModel(sp<V1_0::IPreparedModel>&, const Request&) {
static Return<ErrorStatus> ExecutePreparedModel(sp<V1_0::IPreparedModel>&, const Request&,
hidl_vec<OutputShape>*) {
ADD_FAILURE() << "asking for synchronous execution at V1_0";
return ErrorStatus::GENERAL_FAILURE;
}
static Return<ErrorStatus> ExecutePreparedModel(sp<V1_2::IPreparedModel>& preparedModel,
const Request& request) {
return preparedModel->executeSynchronously(request);
const Request& request,
hidl_vec<OutputShape>* outputShapes) {
ErrorStatus result;
Return<void> ret = preparedModel->executeSynchronously(
request, [&result, &outputShapes](ErrorStatus error, const hidl_vec<OutputShape>& shapes) {
result = error;
*outputShapes = shapes;
});
if (!ret.isOk()) {
return ErrorStatus::GENERAL_FAILURE;
}
return result;
}
enum class Synchronously { NO, YES };
const float kDefaultAtol = 1e-5f;
@ -197,6 +208,8 @@ void EvaluatePreparedModel(sp<T_IPreparedModel>& preparedModel, std::function<bo
inputMemory->commit();
outputMemory->commit();
ErrorStatus executionStatus;
hidl_vec<OutputShape> outputShapes;
if (sync == Synchronously::NO) {
SCOPED_TRACE("asynchronous");
@ -211,18 +224,24 @@ void EvaluatePreparedModel(sp<T_IPreparedModel>& preparedModel, std::function<bo
// retrieve execution status
executionCallback->wait();
ErrorStatus executionReturnStatus = executionCallback->getStatus();
EXPECT_EQ(ErrorStatus::NONE, executionReturnStatus);
executionStatus = executionCallback->getStatus();
outputShapes = executionCallback->getOutputShapes();
} else {
SCOPED_TRACE("synchronous");
// execute
Return<ErrorStatus> executionStatus = ExecutePreparedModel(
preparedModel, {.inputs = inputs_info, .outputs = outputs_info, .pools = pools});
ASSERT_TRUE(executionStatus.isOk());
EXPECT_EQ(ErrorStatus::NONE, static_cast<ErrorStatus>(executionStatus));
Return<ErrorStatus> executionReturnStatus = ExecutePreparedModel(
preparedModel, {.inputs = inputs_info, .outputs = outputs_info, .pools = pools},
&outputShapes);
ASSERT_TRUE(executionReturnStatus.isOk());
executionStatus = static_cast<ErrorStatus>(executionReturnStatus);
}
ASSERT_EQ(ErrorStatus::NONE, executionStatus);
// TODO(xusongw): Check if the returned output shapes match with expectation once the
// sample driver implementation of dynamic output shape is finished.
ASSERT_EQ(outputShapes.size(), 0);
// validate results
outputMemory->read();
copy_back(&test, outputs_info, outputPtr);

View file

@ -27,6 +27,7 @@ hidl_interface {
"Operation",
"OperationType",
"OperationTypeRange",
"OutputShape",
],
gen_java: false,
}

View file

@ -18,6 +18,7 @@ package android.hardware.neuralnetworks@1.2;
import @1.0::ErrorStatus;
import @1.0::IExecutionCallback;
import OutputShape;
/**
* IExecutionCallback must be used to return the error status result from an
@ -39,10 +40,16 @@ interface IExecutionCallback extends @1.0::IExecutionCallback {
* - DEVICE_UNAVAILABLE if driver is offline or busy
* - GENERAL_FAILURE if the asynchronous task resulted in an
* unspecified error
* - OUTPUT_INSUFFICIENT_SIZE if provided output buffer is
* not large enough to store the resultant values
* - OUTPUT_INSUFFICIENT_SIZE if at least one output
* operand buffer is not large enough to store the
* corresponding output
* - INVALID_ARGUMENT if one of the input arguments to
* prepareModel is invalid
* @param outputShapes A list of shape information of model output operands.
* The index into "outputShapes" corresponds with to index
* of the output operand in the Request outputs vector.
* outputShapes must be empty unless the status is either
* NONE or OUTPUT_INSUFFICIENT_SIZE.
*/
oneway notify_1_2(ErrorStatus status);
oneway notify_1_2(ErrorStatus status, vec<OutputShape> outputShapes);
};

View file

@ -100,11 +100,17 @@ interface IPreparedModel extends @1.0::IPreparedModel {
* - NONE if execution is performed successfully
* - DEVICE_UNAVAILABLE if driver is offline or busy
* - GENERAL_FAILURE if there is an unspecified error
* - OUTPUT_INSUFFICIENT_SIZE if provided output buffer is
* not large enough to store the resultant values
* - OUTPUT_INSUFFICIENT_SIZE if at least one output
* operand buffer is not large enough to store the
* corresponding output
* - INVALID_ARGUMENT if one of the input arguments is
* invalid
* @return outputShapes A list of shape information of model output operands.
* The index into "outputShapes" corresponds to the index
* of the output operand in the Request outputs vector.
* outputShapes must be empty unless the status is either
* NONE or OUTPUT_INSUFFICIENT_SIZE.
*/
executeSynchronously(Request request)
generates (ErrorStatus status);
generates (ErrorStatus status, vec<OutputShape> outputShapes);
};

View file

@ -234,9 +234,6 @@ struct Operand {
*
* For a scalar operand, dimensions.size() must be 0.
*
* For a tensor operand, dimensions.size() must be at least 1;
* however, any of the dimensions may be unspecified.
*
* A tensor operand with all dimensions specified has "fully
* specified" dimensions. Whenever possible (i.e., whenever the
* dimensions are known at model construction time), a tensor
@ -255,17 +252,20 @@ struct Operand {
* . The operand has lifetime CONSTANT_COPY or
* CONSTANT_REFERENCE.
*
* . The operand has lifetime MODEL_INPUT or MODEL_OUTPUT. Fully
* . The operand has lifetime MODEL_INPUT. Fully
* specified dimensions must either be present in the
* Operand or they must be provided in the corresponding
* RequestArgument.
* EXCEPTION: If the input or output is optional and omitted
* EXCEPTION: If the input is optional and omitted
* (by setting the hasNoValue field of the corresponding
* RequestArgument to true) then it need not have fully
* specified dimensions.
*
* A tensor operand with some number of unspecified dimensions is
* represented by setting each unspecified dimension to 0.
*
* A tensor operand with unspecified rank is represented by providing
* an empty dimensions vector.
*/
vec<uint32_t> dimensions;
@ -397,3 +397,18 @@ struct Model {
*/
bool relaxComputationFloat32toFloat16;
};
/**
* Describes the shape information of an output operand after execution.
*/
struct OutputShape {
/**
* Dimensions of the operand.
*/
vec<uint32_t> dimensions;
/**
* Whether the provided buffer size is sufficient for the output.
*/
bool isSufficient;
};

View file

@ -110,15 +110,20 @@ static void validate(const sp<IPreparedModel>& preparedModel, const std::string&
executionCallback->wait();
ErrorStatus executionReturnStatus = executionCallback->getStatus();
const auto& outputShapes = executionCallback->getOutputShapes();
ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionReturnStatus);
ASSERT_EQ(outputShapes.size(), 0);
}
{
SCOPED_TRACE(message + " [executeSynchronously]");
Return<ErrorStatus> executeStatus = preparedModel->executeSynchronously(request);
Return<void> executeStatus = preparedModel->executeSynchronously(
request, [](ErrorStatus error, const hidl_vec<OutputShape>& outputShapes) {
ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, error);
EXPECT_EQ(outputShapes.size(), 0);
});
ASSERT_TRUE(executeStatus.isOk());
ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, static_cast<ErrorStatus>(executeStatus));
}
}