android13/hardware/interfaces/neuralnetworks/1.3/vts/functional/MemoryDomainTests.cpp

1212 lines
52 KiB
C++

/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "neuralnetworks_hidl_hal_test"
#include <android-base/logging.h>
#include <gtest/gtest.h>
#include "1.3/Callbacks.h"
#include "1.3/Utils.h"
#include "GeneratedTestHarness.h"
#include "MemoryUtils.h"
#include "TestHarness.h"
#include "Utils.h"
#include "VtsHalNeuralnetworks.h"
namespace android::hardware::neuralnetworks::V1_3::vts::functional {
using namespace test_helper;
using implementation::ExecutionCallback;
using implementation::PreparedModelCallback;
using V1_0::RequestArgument;
using V1_1::ExecutionPreference;
using V1_2::Constant;
using V1_2::MeasureTiming;
using V1_2::OutputShape;
using V1_2::Timing;
namespace {
const auto kNamedDeviceChoices = testing::ValuesIn(getNamedDevices());
// A 1.3 driver is likely to support at least one of the following operand types.
const std::vector<TestOperandType> kTestOperandTypeChoicesVector = {
TestOperandType::TENSOR_FLOAT32,
TestOperandType::TENSOR_FLOAT16,
TestOperandType::TENSOR_QUANT8_ASYMM,
TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED,
};
const auto kTestOperandTypeChoices = testing::ValuesIn(kTestOperandTypeChoicesVector);
bool isInChoices(TestOperandType type) {
return std::count(kTestOperandTypeChoicesVector.begin(), kTestOperandTypeChoicesVector.end(),
type) > 0;
}
bool isFloat(TestOperandType type) {
CHECK(isInChoices(type));
return type == TestOperandType::TENSOR_FLOAT32 || type == TestOperandType::TENSOR_FLOAT16;
}
// Create dummy buffers for model constants as well as inputs and outputs.
// We only care about the size here because we will not check accuracy in validation tests.
void createDummyData(TestModel* testModel) {
for (auto& operand : testModel->main.operands) {
if (operand.data != nullptr) continue;
switch (operand.lifetime) {
case TestOperandLifeTime::SUBGRAPH_INPUT:
case TestOperandLifeTime::SUBGRAPH_OUTPUT:
case TestOperandLifeTime::CONSTANT_COPY:
case TestOperandLifeTime::CONSTANT_REFERENCE: {
const uint32_t size = nn::nonExtensionOperandSizeOfData(
static_cast<OperandType>(operand.type), operand.dimensions);
operand.data = TestBuffer(size);
} break;
default:
break;
}
}
}
TestOperand createInt32Scalar(int32_t value) {
return {
.type = TestOperandType::INT32,
.dimensions = {},
.numberOfConsumers = 1,
.scale = 0.0f,
.zeroPoint = 0,
.lifetime = TestOperandLifeTime::CONSTANT_COPY,
.data = TestBuffer::createFromVector<int32_t>({value}),
};
}
// Construct a test model with multiple CONV_2D operations with the given operand as inputs.
// The dimensions of the filters are chosen to ensure outputs has the same dimensions as inputs.
// We choose CONV_2D operation because it is commonly supported by most drivers.
TestModel createConvModel(const TestOperand& operand, uint32_t numOperations) {
CHECK(isInChoices(operand.type));
TestOperand weight = {.type = operand.type,
.dimensions = {operand.dimensions[3], 3, 3, operand.dimensions[3]},
.numberOfConsumers = 1,
.scale = isFloat(operand.type) ? 0.0f : 1.0f,
.zeroPoint = 0,
.lifetime = TestOperandLifeTime::CONSTANT_COPY};
TestOperand bias = {
.type = isFloat(operand.type) ? operand.type : TestOperandType::TENSOR_INT32,
.dimensions = {operand.dimensions[3]},
.numberOfConsumers = 1,
.scale = operand.scale * weight.scale,
.zeroPoint = 0,
.lifetime = TestOperandLifeTime::CONSTANT_COPY};
TestOperand output = operand;
output.numberOfConsumers = 0;
output.lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT;
const std::vector<TestOperand> operands = {
operand,
std::move(weight),
std::move(bias),
createInt32Scalar(1), // same padding
createInt32Scalar(1), // width stride
createInt32Scalar(1), // height stride
createInt32Scalar(0), // activation = NONE
std::move(output),
};
TestModel model;
for (uint32_t i = 0; i < numOperations; i++) {
model.main.operands.insert(model.main.operands.end(), operands.begin(), operands.end());
const uint32_t inputIndex = operands.size() * i;
const uint32_t outputIndex = inputIndex + operands.size() - 1;
std::vector<uint32_t> inputs(operands.size() - 1);
std::iota(inputs.begin(), inputs.end(), inputIndex);
model.main.operations.push_back({.type = TestOperationType::CONV_2D,
.inputs = std::move(inputs),
.outputs = {outputIndex}});
model.main.inputIndexes.push_back(inputIndex);
model.main.outputIndexes.push_back(outputIndex);
}
createDummyData(&model);
return model;
}
// Construct a test model with a single ADD operation with the given operand as input0 and input1.
// This is to cover additional cases that the CONV_2D model does not support, e.g. arbitrary input
// operand rank, scalar input operand. We choose ADD operation because it is commonly supported by
// most drivers.
TestModel createSingleAddModel(const TestOperand& operand) {
CHECK(isInChoices(operand.type));
TestOperand act = {
.type = TestOperandType::INT32,
.dimensions = {},
.numberOfConsumers = 1,
.scale = 0.0f,
.zeroPoint = 0,
.lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
};
TestOperand output = operand;
output.numberOfConsumers = 0;
output.lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT;
TestModel model = {
.main =
{
.operands =
{
operand,
operand,
std::move(act),
output,
},
.operations = {{.type = TestOperationType::ADD,
.inputs = {0, 1, 2},
.outputs = {3}}},
.inputIndexes = {0, 1, 2},
.outputIndexes = {3},
},
};
createDummyData(&model);
return model;
}
// A dummy invalid IPreparedModel class for MemoryDomainAllocateTest.InvalidPreparedModel
class InvalidPreparedModel : public IPreparedModel {
public:
Return<V1_0::ErrorStatus> execute(const V1_0::Request&,
const sp<V1_0::IExecutionCallback>&) override {
return V1_0::ErrorStatus::GENERAL_FAILURE;
}
Return<V1_0::ErrorStatus> execute_1_2(const V1_0::Request&, V1_2::MeasureTiming,
const sp<V1_2::IExecutionCallback>&) override {
return V1_0::ErrorStatus::GENERAL_FAILURE;
}
Return<V1_3::ErrorStatus> execute_1_3(const V1_3::Request&, V1_2::MeasureTiming,
const V1_3::OptionalTimePoint&,
const V1_3::OptionalTimeoutDuration&,
const sp<V1_3::IExecutionCallback>&) override {
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
Return<void> executeSynchronously(const V1_0::Request&, V1_2::MeasureTiming,
executeSynchronously_cb) override {
return Void();
}
Return<void> executeSynchronously_1_3(const V1_3::Request&, V1_2::MeasureTiming,
const V1_3::OptionalTimePoint&,
const V1_3::OptionalTimeoutDuration&,
executeSynchronously_1_3_cb) override {
return Void();
}
Return<void> configureExecutionBurst(const sp<V1_2::IBurstCallback>&,
const MQDescriptorSync<V1_2::FmqRequestDatum>&,
const MQDescriptorSync<V1_2::FmqResultDatum>&,
configureExecutionBurst_cb) override {
return Void();
}
Return<void> executeFenced(const V1_3::Request&, const hidl_vec<hidl_handle>&,
V1_2::MeasureTiming, const V1_3::OptionalTimePoint&,
const V1_3::OptionalTimeoutDuration&,
const V1_3::OptionalTimeoutDuration&, executeFenced_cb) override {
return Void();
}
};
} // namespace
class MemoryDomainTestBase : public testing::Test {
protected:
MemoryDomainTestBase(sp<IDevice> device, TestOperandType type)
: kDevice(std::move(device)),
kTestOperandType(type),
kTestOperand(kTestOperandMap.at(type)),
kTestOperandDataSize(nn::nonExtensionOperandSizeOfData(static_cast<OperandType>(type),
kTestOperand.dimensions)) {}
void SetUp() override {
testing::Test::SetUp();
ASSERT_NE(kDevice, nullptr);
const bool deviceIsResponsive = kDevice->ping().isOk();
ASSERT_TRUE(deviceIsResponsive);
}
sp<IPreparedModel> createConvPreparedModel(const TestOperand& testOperand,
uint32_t numOperations = 1) {
const TestModel testModel = createConvModel(testOperand, numOperations);
const Model model = createModel(testModel);
sp<IPreparedModel> preparedModel;
createPreparedModel(kDevice, model, &preparedModel, /*reportSkipping=*/false);
return preparedModel;
}
sp<IPreparedModel> createAddPreparedModel(const TestOperand& testOperand) {
const TestModel testModel = createSingleAddModel(testOperand);
const Model model = createModel(testModel);
sp<IPreparedModel> preparedModel;
createPreparedModel(kDevice, model, &preparedModel, /*reportSkipping=*/false);
return preparedModel;
}
static const std::map<TestOperandType, TestOperand> kTestOperandMap;
const sp<IDevice> kDevice;
const TestOperandType kTestOperandType;
const TestOperand& kTestOperand;
const uint32_t kTestOperandDataSize;
};
const std::map<TestOperandType, TestOperand> MemoryDomainTestBase::kTestOperandMap = {
{TestOperandType::TENSOR_FLOAT32,
{
.type = TestOperandType::TENSOR_FLOAT32,
.dimensions = {1, 32, 32, 8},
.numberOfConsumers = 1,
.scale = 0.0f,
.zeroPoint = 0,
.lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
}},
{TestOperandType::TENSOR_FLOAT16,
{
.type = TestOperandType::TENSOR_FLOAT16,
.dimensions = {1, 32, 32, 8},
.numberOfConsumers = 1,
.scale = 0.0f,
.zeroPoint = 0,
.lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
}},
{TestOperandType::TENSOR_QUANT8_ASYMM,
{
.type = TestOperandType::TENSOR_QUANT8_ASYMM,
.dimensions = {1, 32, 32, 8},
.numberOfConsumers = 1,
.scale = 0.5f,
.zeroPoint = 0,
.lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
}},
{TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED,
{
.type = TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED,
.dimensions = {1, 32, 32, 8},
.numberOfConsumers = 1,
.scale = 0.5f,
.zeroPoint = 0,
.lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
}},
};
using MemoryDomainAllocateTestParam = std::tuple<NamedDevice, TestOperandType>;
class MemoryDomainAllocateTest : public MemoryDomainTestBase,
public testing::WithParamInterface<MemoryDomainAllocateTestParam> {
protected:
MemoryDomainAllocateTest()
: MemoryDomainTestBase(getData(std::get<NamedDevice>(GetParam())),
std::get<TestOperandType>(GetParam())) {}
struct AllocateTestArgs {
hidl_vec<uint32_t> dimensions;
hidl_vec<sp<IPreparedModel>> preparedModels;
hidl_vec<BufferRole> inputRoles;
hidl_vec<BufferRole> outputRoles;
};
// Validation test for IDevice::allocate. The driver is expected to fail with INVALID_ARGUMENT,
// or GENERAL_FAILURE if memory domain is not supported.
void validateAllocate(AllocateTestArgs args) {
const auto ret = kDevice->allocate(
{.dimensions = std::move(args.dimensions)}, std::move(args.preparedModels),
std::move(args.inputRoles), std::move(args.outputRoles),
[](ErrorStatus status, const sp<IBuffer>& buffer, uint32_t token) {
EXPECT_TRUE(status == ErrorStatus::INVALID_ARGUMENT ||
status == ErrorStatus::GENERAL_FAILURE);
EXPECT_EQ(buffer, nullptr);
EXPECT_EQ(token, 0);
});
ASSERT_TRUE(ret.isOk());
}
void testConflictOperands(const sp<IPreparedModel>& model1, const sp<IPreparedModel>& model2) {
validateAllocate({
.preparedModels = {model1, model2},
.inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f},
{.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}},
});
validateAllocate({
.preparedModels = {model1, model2},
.inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
.outputRoles = {{.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}},
});
validateAllocate({
.preparedModels = {model1, model2},
.outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f},
{.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}},
});
}
};
TEST_P(MemoryDomainAllocateTest, EmptyRole) {
// Test with empty prepared models and roles.
validateAllocate({});
auto preparedModel = createConvPreparedModel(kTestOperand);
if (preparedModel == nullptr) return;
// Test again with non-empty prepared models but empty roles.
validateAllocate({
.preparedModels = {preparedModel},
});
}
TEST_P(MemoryDomainAllocateTest, NullptrPreparedModel) {
// Test with nullptr prepared model as input role.
validateAllocate({
.preparedModels = {nullptr},
.inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
});
// Test with nullptr prepared model as output role.
validateAllocate({
.preparedModels = {nullptr},
.outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
});
}
TEST_P(MemoryDomainAllocateTest, InvalidPreparedModel) {
sp<InvalidPreparedModel> invalidPreparedModel = new InvalidPreparedModel();
// Test with invalid prepared model as input role.
validateAllocate({
.preparedModels = {invalidPreparedModel},
.inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
});
// Test with invalid prepared model as output role.
validateAllocate({
.preparedModels = {invalidPreparedModel},
.outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
});
}
TEST_P(MemoryDomainAllocateTest, InvalidModelIndex) {
auto preparedModel = createConvPreparedModel(kTestOperand);
if (preparedModel == nullptr) return;
// This should fail, because the model index is out of bound.
validateAllocate({
.preparedModels = {preparedModel},
.inputRoles = {{.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}},
});
// This should fail, because the model index is out of bound.
validateAllocate({
.preparedModels = {preparedModel},
.outputRoles = {{.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}},
});
}
TEST_P(MemoryDomainAllocateTest, InvalidIOIndex) {
auto preparedModel = createConvPreparedModel(kTestOperand);
if (preparedModel == nullptr) return;
// This should fail, because the model only has one input.
validateAllocate({
.preparedModels = {preparedModel},
.inputRoles = {{.modelIndex = 0, .ioIndex = 1, .frequency = 1.0f}},
});
// This should fail, because the model only has one output.
validateAllocate({
.preparedModels = {preparedModel},
.outputRoles = {{.modelIndex = 0, .ioIndex = 1, .frequency = 1.0f}},
});
}
TEST_P(MemoryDomainAllocateTest, InvalidFrequency) {
auto preparedModel = createConvPreparedModel(kTestOperand);
if (preparedModel == nullptr) return;
for (float invalidFreq : {10.0f, 0.0f, -0.5f}) {
// Test with invalid frequency for input roles.
validateAllocate({
.preparedModels = {preparedModel},
.inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = invalidFreq}},
});
// Test with invalid frequency for output roles.
validateAllocate({
.preparedModels = {preparedModel},
.outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = invalidFreq}},
});
}
}
TEST_P(MemoryDomainAllocateTest, SameRoleSpecifiedTwice) {
auto preparedModel = createConvPreparedModel(kTestOperand);
if (preparedModel == nullptr) return;
// Same role with same model index.
validateAllocate({
.preparedModels = {preparedModel},
.inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f},
{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
});
validateAllocate({
.preparedModels = {preparedModel},
.outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f},
{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
});
// Different model indexes, but logically referring to the same role.
validateAllocate({
.preparedModels = {preparedModel, preparedModel},
.inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f},
{.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}},
});
validateAllocate({
.preparedModels = {preparedModel, preparedModel},
.outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f},
{.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}},
});
}
TEST_P(MemoryDomainAllocateTest, ConflictOperandType) {
const std::map<TestOperandType, TestOperandType> conflictTypeMap = {
{TestOperandType::TENSOR_FLOAT32, TestOperandType::TENSOR_FLOAT16},
{TestOperandType::TENSOR_FLOAT16, TestOperandType::TENSOR_FLOAT32},
{TestOperandType::TENSOR_QUANT8_ASYMM, TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED},
{TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED, TestOperandType::TENSOR_QUANT8_ASYMM},
};
TestOperand conflictTestOperand = kTestOperand;
const auto it = conflictTypeMap.find(kTestOperandType);
ASSERT_FALSE(it == conflictTypeMap.end());
conflictTestOperand.type = it->second;
auto preparedModel = createConvPreparedModel(kTestOperand);
auto conflictPreparedModel = createConvPreparedModel(conflictTestOperand);
if (preparedModel == nullptr || conflictPreparedModel == nullptr) return;
testConflictOperands(preparedModel, conflictPreparedModel);
}
TEST_P(MemoryDomainAllocateTest, ConflictScale) {
if (isFloat(kTestOperandType)) return;
TestOperand conflictTestOperand = kTestOperand;
ASSERT_NE(conflictTestOperand.scale, 1.0f);
conflictTestOperand.scale = 1.0f;
auto preparedModel = createConvPreparedModel(kTestOperand);
auto conflictPreparedModel = createConvPreparedModel(conflictTestOperand);
if (preparedModel == nullptr || conflictPreparedModel == nullptr) return;
testConflictOperands(preparedModel, conflictPreparedModel);
}
TEST_P(MemoryDomainAllocateTest, ConflictZeroPoint) {
if (isFloat(kTestOperandType)) return;
TestOperand conflictTestOperand = kTestOperand;
ASSERT_NE(conflictTestOperand.zeroPoint, 10);
conflictTestOperand.zeroPoint = 10;
auto preparedModel = createConvPreparedModel(kTestOperand);
auto conflictPreparedModel = createConvPreparedModel(conflictTestOperand);
if (preparedModel == nullptr || conflictPreparedModel == nullptr) return;
testConflictOperands(preparedModel, conflictPreparedModel);
}
TEST_P(MemoryDomainAllocateTest, ConflictRankBetweenRoles) {
TestOperand conflictTestOperand = kTestOperand;
conflictTestOperand.dimensions.pop_back();
auto preparedModel = createAddPreparedModel(kTestOperand);
auto conflictPreparedModel = createAddPreparedModel(conflictTestOperand);
if (preparedModel == nullptr || conflictPreparedModel == nullptr) return;
testConflictOperands(preparedModel, conflictPreparedModel);
}
TEST_P(MemoryDomainAllocateTest, ConflictDimensionsBetweenRoles) {
TestOperand conflictTestOperand = kTestOperand;
conflictTestOperand.dimensions[0] = 4;
auto preparedModel = createConvPreparedModel(kTestOperand);
auto conflictPreparedModel = createConvPreparedModel(conflictTestOperand);
if (preparedModel == nullptr || conflictPreparedModel == nullptr) return;
testConflictOperands(preparedModel, conflictPreparedModel);
}
TEST_P(MemoryDomainAllocateTest, ConflictRankBetweenRoleAndDesc) {
auto preparedModel = createConvPreparedModel(kTestOperand);
if (preparedModel == nullptr) return;
auto badDimensions = kTestOperand.dimensions;
badDimensions.pop_back();
validateAllocate({
.dimensions = badDimensions,
.preparedModels = {preparedModel},
.inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
});
validateAllocate({
.dimensions = badDimensions,
.preparedModels = {preparedModel},
.outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
});
}
TEST_P(MemoryDomainAllocateTest, ConflictDimensionsBetweenRoleAndDesc) {
auto preparedModel = createConvPreparedModel(kTestOperand);
if (preparedModel == nullptr) return;
auto badDimensions = kTestOperand.dimensions;
badDimensions[0] = 4;
validateAllocate({
.dimensions = badDimensions,
.preparedModels = {preparedModel},
.inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
});
validateAllocate({
.dimensions = badDimensions,
.preparedModels = {preparedModel},
.outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
});
}
TEST_P(MemoryDomainAllocateTest, ConflictRankWithScalarRole) {
auto preparedModel = createAddPreparedModel(kTestOperand);
if (preparedModel == nullptr) return;
// This should fail, because the target operand is a scalar but a non-empty dimension is
// specified.
validateAllocate({
.dimensions = {1},
.preparedModels = {preparedModel},
.inputRoles = {{.modelIndex = 0, .ioIndex = 2, .frequency = 1.0f}},
});
}
std::string printMemoryDomainAllocateTest(
const testing::TestParamInfo<MemoryDomainAllocateTestParam>& info) {
const auto& [namedDevice, operandType] = info.param;
const std::string type = toString(static_cast<OperandType>(operandType));
return gtestCompliantName(getName(namedDevice) + "_" + type);
}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MemoryDomainAllocateTest);
INSTANTIATE_TEST_SUITE_P(TestMemoryDomain, MemoryDomainAllocateTest,
testing::Combine(kNamedDeviceChoices, kTestOperandTypeChoices),
printMemoryDomainAllocateTest);
class MemoryDomainCopyTestBase : public MemoryDomainTestBase {
protected:
MemoryDomainCopyTestBase(sp<IDevice> device, TestOperandType type)
: MemoryDomainTestBase(std::move(device), type) {}
// Allocates device memory for roles of a single prepared model.
// Returns {IBuffer, token} if success; returns {nullptr, 0} if not supported.
std::pair<sp<IBuffer>, uint32_t> allocateBuffer(const sp<IPreparedModel>& preparedModel,
const std::vector<uint32_t>& inputIndexes,
const std::vector<uint32_t>& outputIndexes,
const std::vector<uint32_t>& dimensions) {
if (preparedModel == nullptr) {
return {nullptr, 0};
}
hidl_vec<BufferRole> inputRoles(inputIndexes.size()), outputRoles(outputIndexes.size());
auto trans = [](uint32_t ind) -> BufferRole {
return {.modelIndex = 0, .ioIndex = ind, .frequency = 1.0f};
};
std::transform(inputIndexes.begin(), inputIndexes.end(), inputRoles.begin(), trans);
std::transform(outputIndexes.begin(), outputIndexes.end(), outputRoles.begin(), trans);
sp<IBuffer> buffer;
uint32_t token = 0;
const auto ret = kDevice->allocate(
{.dimensions = dimensions}, {preparedModel}, std::move(inputRoles),
std::move(outputRoles),
[&buffer, &token](ErrorStatus err, const sp<IBuffer>& buf, uint32_t tok) {
if (err == ErrorStatus::NONE) {
EXPECT_NE(buf, nullptr);
EXPECT_GT(tok, 0);
buffer = buf;
token = tok;
} else {
EXPECT_EQ(err, ErrorStatus::GENERAL_FAILURE);
EXPECT_EQ(buf, nullptr);
EXPECT_EQ(tok, 0);
}
});
EXPECT_TRUE(ret.isOk());
return {std::move(buffer), token};
}
std::pair<sp<IBuffer>, uint32_t> allocateBuffer(const sp<IPreparedModel>& preparedModel,
const std::vector<uint32_t>& inputIndexes,
const std::vector<uint32_t>& outputIndexes) {
return allocateBuffer(preparedModel, inputIndexes, outputIndexes, {});
}
hidl_memory allocateSharedMemory(uint32_t size) {
hidl_memory memory = nn::allocateSharedMemory(size);
EXPECT_EQ(memory.size(), size);
return memory;
}
void testCopyFrom(const sp<IBuffer>& buffer, const hidl_memory& memory,
const std::vector<uint32_t>& dimensions, ErrorStatus expectedStatus) {
const auto ret = buffer->copyFrom(memory, dimensions);
ASSERT_TRUE(ret.isOk());
ASSERT_EQ(static_cast<ErrorStatus>(ret), expectedStatus);
}
void testCopyTo(const sp<IBuffer>& buffer, const hidl_memory& memory,
ErrorStatus expectedStatus) {
const auto ret = buffer->copyTo(memory);
ASSERT_TRUE(ret.isOk());
ASSERT_EQ(static_cast<ErrorStatus>(ret), expectedStatus);
}
void initializeDeviceMemory(const sp<IBuffer>& buffer) {
hidl_memory memory = nn::allocateSharedMemory(kTestOperandDataSize);
ASSERT_EQ(memory.size(), kTestOperandDataSize);
testCopyFrom(buffer, memory, kTestOperand.dimensions, ErrorStatus::NONE);
}
};
using MemoryDomainCopyTestParam = std::tuple<NamedDevice, TestOperandType>;
class MemoryDomainCopyTest : public MemoryDomainCopyTestBase,
public testing::WithParamInterface<MemoryDomainCopyTestParam> {
protected:
MemoryDomainCopyTest()
: MemoryDomainCopyTestBase(getData(std::get<NamedDevice>(GetParam())),
std::get<TestOperandType>(GetParam())) {}
};
TEST_P(MemoryDomainCopyTest, CopyFrom_InvalidMemorySize) {
auto preparedModel = createConvPreparedModel(kTestOperand);
auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
if (buffer == nullptr) return;
uint32_t badMemorySize1 = kTestOperandDataSize / 2, badMemorySize2 = kTestOperandDataSize * 2;
hidl_memory badMemory1 = allocateSharedMemory(badMemorySize1);
hidl_memory badMemory2 = allocateSharedMemory(badMemorySize2);
testCopyFrom(buffer, badMemory1, {}, ErrorStatus::INVALID_ARGUMENT);
testCopyFrom(buffer, badMemory2, {}, ErrorStatus::INVALID_ARGUMENT);
}
TEST_P(MemoryDomainCopyTest, CopyFrom_InvalidMemorySize_DynamicShape) {
TestOperand testOperand = kTestOperand;
testOperand.dimensions[0] = 0;
auto preparedModel = createConvPreparedModel(testOperand);
auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
if (buffer == nullptr) return;
uint32_t badMemorySize1 = kTestOperandDataSize / 2, badMemorySize2 = kTestOperandDataSize * 2;
hidl_memory badMemory1 = allocateSharedMemory(badMemorySize1);
hidl_memory badMemory2 = allocateSharedMemory(badMemorySize2);
hidl_memory goodMemory = allocateSharedMemory(kTestOperandDataSize);
auto badDimensions = kTestOperand.dimensions;
badDimensions[0] = 2;
testCopyFrom(buffer, badMemory1, kTestOperand.dimensions, ErrorStatus::INVALID_ARGUMENT);
testCopyFrom(buffer, badMemory2, kTestOperand.dimensions, ErrorStatus::INVALID_ARGUMENT);
testCopyFrom(buffer, goodMemory, kTestOperand.dimensions, ErrorStatus::NONE);
testCopyFrom(buffer, goodMemory, badDimensions, ErrorStatus::INVALID_ARGUMENT);
}
TEST_P(MemoryDomainCopyTest, CopyFrom_InvalidDimensions) {
auto preparedModel = createConvPreparedModel(kTestOperand);
auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
if (buffer == nullptr) return;
hidl_memory memory = allocateSharedMemory(kTestOperandDataSize);
std::vector<uint32_t> badDimensions;
badDimensions = kTestOperand.dimensions;
badDimensions.pop_back();
testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT);
badDimensions = kTestOperand.dimensions;
badDimensions[0] = 2;
testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT);
badDimensions = kTestOperand.dimensions;
badDimensions[0] = 0;
testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT);
testCopyFrom(buffer, memory, {}, ErrorStatus::NONE);
testCopyFrom(buffer, memory, kTestOperand.dimensions, ErrorStatus::NONE);
}
TEST_P(MemoryDomainCopyTest, CopyFrom_InvalidDimensions_DynamicShape) {
TestOperand testOperand = kTestOperand;
testOperand.dimensions[0] = 0;
auto preparedModel = createConvPreparedModel(testOperand);
auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
if (buffer == nullptr) return;
hidl_memory memory = allocateSharedMemory(kTestOperandDataSize);
std::vector<uint32_t> badDimensions;
badDimensions = kTestOperand.dimensions;
badDimensions.pop_back();
testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT);
badDimensions = kTestOperand.dimensions;
badDimensions[0] = 2;
badDimensions[3] = 4;
testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT);
badDimensions = kTestOperand.dimensions;
badDimensions[0] = 1;
badDimensions[3] = 0;
testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT);
testCopyFrom(buffer, memory, {}, ErrorStatus::INVALID_ARGUMENT);
testCopyFrom(buffer, memory, kTestOperand.dimensions, ErrorStatus::NONE);
}
TEST_P(MemoryDomainCopyTest, CopyTo_UninitializedMemory) {
auto preparedModel = createConvPreparedModel(kTestOperand);
auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
if (buffer == nullptr) return;
hidl_memory memory = allocateSharedMemory(kTestOperandDataSize);
testCopyTo(buffer, memory, ErrorStatus::GENERAL_FAILURE);
}
TEST_P(MemoryDomainCopyTest, CopyTo_InvalidMemorySize) {
auto preparedModel = createConvPreparedModel(kTestOperand);
auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
if (buffer == nullptr) return;
uint32_t badMemorySize1 = kTestOperandDataSize / 2, badMemorySize2 = kTestOperandDataSize * 2;
hidl_memory badMemory1 = allocateSharedMemory(badMemorySize1);
hidl_memory badMemory2 = allocateSharedMemory(badMemorySize2);
hidl_memory goodMemory = allocateSharedMemory(kTestOperandDataSize);
initializeDeviceMemory(buffer);
testCopyTo(buffer, badMemory1, ErrorStatus::INVALID_ARGUMENT);
testCopyTo(buffer, badMemory2, ErrorStatus::INVALID_ARGUMENT);
testCopyTo(buffer, goodMemory, ErrorStatus::NONE);
}
TEST_P(MemoryDomainCopyTest, CopyTo_InvalidMemorySize_DynamicShape) {
TestOperand testOperand = kTestOperand;
testOperand.dimensions[0] = 0;
auto preparedModel = createConvPreparedModel(testOperand);
auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
if (buffer == nullptr) return;
uint32_t badMemorySize1 = kTestOperandDataSize / 2, badMemorySize2 = kTestOperandDataSize * 2;
hidl_memory badMemory1 = allocateSharedMemory(badMemorySize1);
hidl_memory badMemory2 = allocateSharedMemory(badMemorySize2);
hidl_memory goodMemory = allocateSharedMemory(kTestOperandDataSize);
initializeDeviceMemory(buffer);
testCopyTo(buffer, badMemory1, ErrorStatus::INVALID_ARGUMENT);
testCopyTo(buffer, badMemory2, ErrorStatus::INVALID_ARGUMENT);
testCopyTo(buffer, goodMemory, ErrorStatus::NONE);
}
std::string printMemoryDomainCopyTest(
const testing::TestParamInfo<MemoryDomainCopyTestParam>& info) {
const auto& [namedDevice, operandType] = info.param;
const std::string type = toString(static_cast<OperandType>(operandType));
return gtestCompliantName(getName(namedDevice) + "_" + type);
}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MemoryDomainCopyTest);
INSTANTIATE_TEST_SUITE_P(TestMemoryDomain, MemoryDomainCopyTest,
testing::Combine(kNamedDeviceChoices, kTestOperandTypeChoices),
printMemoryDomainCopyTest);
using MemoryDomainExecutionTestParam = std::tuple<NamedDevice, TestOperandType, Executor>;
class MemoryDomainExecutionTest
: public MemoryDomainCopyTestBase,
public testing::WithParamInterface<MemoryDomainExecutionTestParam> {
protected:
MemoryDomainExecutionTest()
: MemoryDomainCopyTestBase(getData(std::get<NamedDevice>(GetParam())),
std::get<TestOperandType>(GetParam())) {}
Request::MemoryPool createSharedMemoryPool(uint32_t size) {
hidl_memory memory = allocateSharedMemory(size);
Request::MemoryPool pool;
pool.hidlMemory(memory);
return pool;
}
Request::MemoryPool createDeviceMemoryPool(uint32_t token) {
Request::MemoryPool pool;
pool.token(token);
return pool;
}
void testExecution(const sp<IPreparedModel>& preparedModel, const Request& request,
ErrorStatus expectedStatus) {
switch (kExecutor) {
case Executor::ASYNC:
EXPECT_EQ(executeAsync(preparedModel, request), expectedStatus);
break;
case Executor::SYNC:
EXPECT_EQ(executeSync(preparedModel, request), expectedStatus);
break;
case Executor::FENCED:
EXPECT_EQ(executeFenced(preparedModel, request), expectedStatus);
break;
default:
ASSERT_TRUE(false);
}
}
ErrorStatus executeAsync(const sp<IPreparedModel>& preparedModel, const Request& request) {
ErrorStatus executionStatus;
// launch execution
sp<ExecutionCallback> executionCallback = new ExecutionCallback();
const auto ret =
preparedModel->execute_1_3(request, MeasureTiming::NO, {}, {}, executionCallback);
EXPECT_TRUE(ret.isOk());
executionStatus = static_cast<ErrorStatus>(ret);
// retrieve execution status
executionCallback->wait();
if (executionStatus == ErrorStatus::NONE) {
executionStatus = executionCallback->getStatus();
} else {
EXPECT_EQ(executionStatus, executionCallback->getStatus());
}
const auto timing = executionCallback->getTiming();
EXPECT_EQ(UINT64_MAX, timing.timeOnDevice);
EXPECT_EQ(UINT64_MAX, timing.timeInDriver);
if (executionStatus != ErrorStatus::NONE) {
EXPECT_EQ(executionCallback->getOutputShapes().size(), 0);
}
return executionStatus;
}
ErrorStatus executeSync(const sp<IPreparedModel>& preparedModel, const Request& request) {
ErrorStatus executionStatus;
const auto ret = preparedModel->executeSynchronously_1_3(
request, MeasureTiming::NO, {}, {},
[&executionStatus](ErrorStatus error, const hidl_vec<OutputShape>& shapes,
const Timing& time) {
executionStatus = error;
EXPECT_EQ(UINT64_MAX, time.timeOnDevice);
EXPECT_EQ(UINT64_MAX, time.timeInDriver);
if (executionStatus != ErrorStatus::NONE) {
EXPECT_EQ(shapes.size(), 0);
}
});
EXPECT_TRUE(ret.isOk());
return executionStatus;
}
ErrorStatus executeFenced(const sp<IPreparedModel>& preparedModel, const Request& request) {
ErrorStatus executionStatus;
hidl_handle syncFenceHandle;
sp<IFencedExecutionCallback> fencedCallback;
const auto callbackFunc = [&executionStatus, &syncFenceHandle, &fencedCallback](
ErrorStatus error, const hidl_handle& handle,
const sp<IFencedExecutionCallback>& callback) {
executionStatus = error;
syncFenceHandle = handle;
fencedCallback = callback;
};
Return<void> ret = preparedModel->executeFenced(request, {}, MeasureTiming::NO, {}, {}, {},
callbackFunc);
EXPECT_TRUE(ret.isOk());
if (executionStatus != ErrorStatus::NONE) {
EXPECT_EQ(syncFenceHandle.getNativeHandle(), nullptr);
EXPECT_EQ(fencedCallback, nullptr);
return executionStatus;
}
if (syncFenceHandle.getNativeHandle()) {
waitForSyncFence(syncFenceHandle.getNativeHandle()->data[0]);
}
EXPECT_NE(fencedCallback, nullptr);
ret = fencedCallback->getExecutionInfo(
[&executionStatus](ErrorStatus error, Timing t, Timing) {
executionStatus = error;
EXPECT_EQ(UINT64_MAX, t.timeOnDevice);
EXPECT_EQ(UINT64_MAX, t.timeInDriver);
});
EXPECT_TRUE(ret.isOk());
return executionStatus;
}
const Executor kExecutor = std::get<Executor>(GetParam());
};
TEST_P(MemoryDomainExecutionTest, InvalidToken) {
auto preparedModel = createConvPreparedModel(kTestOperand);
if (preparedModel == nullptr) return;
Request::MemoryPool sharedMemory = createSharedMemoryPool(kTestOperandDataSize);
Request::MemoryPool badDeviceMemory1 = createDeviceMemoryPool(0); // Invalid token.
Request::MemoryPool badDeviceMemory2 = createDeviceMemoryPool(100); // Unknown token.
RequestArgument sharedMemoryArg = {
.location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}};
RequestArgument deviceMemoryArg = {.location = {.poolIndex = 1}};
testExecution(preparedModel,
{.inputs = {deviceMemoryArg},
.outputs = {sharedMemoryArg},
.pools = {sharedMemory, badDeviceMemory1}},
ErrorStatus::INVALID_ARGUMENT);
testExecution(preparedModel,
{.inputs = {deviceMemoryArg},
.outputs = {sharedMemoryArg},
.pools = {sharedMemory, badDeviceMemory2}},
ErrorStatus::INVALID_ARGUMENT);
testExecution(preparedModel,
{.inputs = {sharedMemoryArg},
.outputs = {deviceMemoryArg},
.pools = {sharedMemory, badDeviceMemory1}},
ErrorStatus::INVALID_ARGUMENT);
testExecution(preparedModel,
{.inputs = {sharedMemoryArg},
.outputs = {deviceMemoryArg},
.pools = {sharedMemory, badDeviceMemory2}},
ErrorStatus::INVALID_ARGUMENT);
}
TEST_P(MemoryDomainExecutionTest, InvalidPreparedModel) {
auto preparedModel = createConvPreparedModel(kTestOperand);
auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
if (buffer == nullptr) return;
auto badPreparedModel = createConvPreparedModel(kTestOperand);
if (badPreparedModel == nullptr) return;
Request::MemoryPool sharedMemory = createSharedMemoryPool(kTestOperandDataSize);
Request::MemoryPool deviceMemory = createDeviceMemoryPool(token);
RequestArgument sharedMemoryArg = {
.location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}};
RequestArgument deviceMemoryArg = {.location = {.poolIndex = 1}};
// This should fail, because the buffer is not allocated for badPreparedModel.
initializeDeviceMemory(buffer);
testExecution(badPreparedModel,
{.inputs = {deviceMemoryArg},
.outputs = {sharedMemoryArg},
.pools = {sharedMemory, deviceMemory}},
ErrorStatus::INVALID_ARGUMENT);
testExecution(badPreparedModel,
{.inputs = {sharedMemoryArg},
.outputs = {deviceMemoryArg},
.pools = {sharedMemory, deviceMemory}},
ErrorStatus::INVALID_ARGUMENT);
}
TEST_P(MemoryDomainExecutionTest, InvalidIOIndex) {
auto preparedModel = createConvPreparedModel(kTestOperand, 2);
auto [buffer, token] = allocateBuffer(preparedModel, {0}, {});
if (buffer == nullptr) return;
Request::MemoryPool sharedMemory1 = createSharedMemoryPool(kTestOperandDataSize);
Request::MemoryPool sharedMemory2 = createSharedMemoryPool(kTestOperandDataSize);
Request::MemoryPool sharedMemory3 = createSharedMemoryPool(kTestOperandDataSize);
Request::MemoryPool deviceMemory = createDeviceMemoryPool(token);
RequestArgument sharedMemoryArg1 = {
.location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}};
RequestArgument sharedMemoryArg2 = {
.location = {.poolIndex = 1, .offset = 0, .length = kTestOperandDataSize}};
RequestArgument sharedMemoryArg3 = {
.location = {.poolIndex = 2, .offset = 0, .length = kTestOperandDataSize}};
RequestArgument deviceMemoryArg = {.location = {.poolIndex = 3}};
// This should fail, because the device memory is not allocated for input 1.
initializeDeviceMemory(buffer);
testExecution(preparedModel,
{.inputs = {sharedMemoryArg1, deviceMemoryArg},
.outputs = {sharedMemoryArg2, sharedMemoryArg3},
.pools = {sharedMemory1, sharedMemory2, sharedMemory3, deviceMemory}},
ErrorStatus::INVALID_ARGUMENT);
// This should fail, because the device memory is not allocated for output 1.
testExecution(preparedModel,
{.inputs = {sharedMemoryArg1, sharedMemoryArg2},
.outputs = {sharedMemoryArg3, deviceMemoryArg},
.pools = {sharedMemory1, sharedMemory2, sharedMemory3, deviceMemory}},
ErrorStatus::INVALID_ARGUMENT);
}
TEST_P(MemoryDomainExecutionTest, InvalidIOType) {
auto preparedModel = createConvPreparedModel(kTestOperand);
auto [inputBuffer, inputToken] = allocateBuffer(preparedModel, {0}, {});
auto [outputBuffer, outputToken] = allocateBuffer(preparedModel, {}, {0});
if (inputBuffer == nullptr || outputBuffer == nullptr) return;
Request::MemoryPool sharedMemory = createSharedMemoryPool(kTestOperandDataSize);
Request::MemoryPool deviceMemory = createDeviceMemoryPool(inputToken);
RequestArgument sharedMemoryArg = {
.location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}};
RequestArgument deviceMemoryArg = {.location = {.poolIndex = 1}};
// This should fail, because the device memory is allocated for input but used as output.
testExecution(preparedModel,
{.inputs = {sharedMemoryArg},
.outputs = {deviceMemoryArg},
.pools = {sharedMemory, deviceMemory}},
ErrorStatus::INVALID_ARGUMENT);
// This should fail, because the device memory is allocated for output but used as input.
deviceMemory.token(outputToken);
initializeDeviceMemory(outputBuffer);
testExecution(preparedModel,
{.inputs = {deviceMemoryArg},
.outputs = {sharedMemoryArg},
.pools = {sharedMemory, deviceMemory}},
ErrorStatus::INVALID_ARGUMENT);
}
TEST_P(MemoryDomainExecutionTest, UninitializedMemory) {
auto preparedModel = createConvPreparedModel(kTestOperand);
auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
if (buffer == nullptr) return;
Request::MemoryPool sharedMemory = createSharedMemoryPool(kTestOperandDataSize);
Request::MemoryPool deviceMemory = createDeviceMemoryPool(token);
RequestArgument sharedMemoryArg = {
.location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}};
RequestArgument deviceMemoryArg = {.location = {.poolIndex = 1}};
// This should fail, because the device memory is not initialized.
testExecution(preparedModel,
{.inputs = {deviceMemoryArg},
.outputs = {sharedMemoryArg},
.pools = {sharedMemory, deviceMemory}},
ErrorStatus::GENERAL_FAILURE);
// This should initialize the device memory.
testExecution(preparedModel,
{.inputs = {sharedMemoryArg},
.outputs = {deviceMemoryArg},
.pools = {sharedMemory, deviceMemory}},
ErrorStatus::NONE);
// Test again with initialized device memory.
testExecution(preparedModel,
{.inputs = {deviceMemoryArg},
.outputs = {sharedMemoryArg},
.pools = {sharedMemory, deviceMemory}},
ErrorStatus::NONE);
}
TEST_P(MemoryDomainExecutionTest, SameRequestMultipleRoles) {
auto preparedModel = createConvPreparedModel(kTestOperand, 2);
auto [buffer, token] = allocateBuffer(preparedModel, {0, 1}, {0, 1});
if (buffer == nullptr) return;
Request::MemoryPool sharedMemory1 = createSharedMemoryPool(kTestOperandDataSize);
Request::MemoryPool sharedMemory2 = createSharedMemoryPool(kTestOperandDataSize);
Request::MemoryPool deviceMemory = createDeviceMemoryPool(token);
RequestArgument sharedMemoryArg1 = {
.location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}};
RequestArgument sharedMemoryArg2 = {
.location = {.poolIndex = 1, .offset = 0, .length = kTestOperandDataSize}};
RequestArgument deviceMemoryArg = {.location = {.poolIndex = 2}};
// This should fail, because the same device memory cannot be used for both input and output.
initializeDeviceMemory(buffer);
testExecution(preparedModel,
{.inputs = {deviceMemoryArg, sharedMemoryArg1},
.outputs = {deviceMemoryArg, sharedMemoryArg2},
.pools = {sharedMemory1, sharedMemory2, deviceMemory}},
ErrorStatus::INVALID_ARGUMENT);
// This should fail, because the same device memory cannot be used for multiple outputs.
testExecution(preparedModel,
{.inputs = {sharedMemoryArg1, sharedMemoryArg2},
.outputs = {deviceMemoryArg, deviceMemoryArg},
.pools = {sharedMemory1, sharedMemory2, deviceMemory}},
ErrorStatus::INVALID_ARGUMENT);
// The same device memory can be used for multiple inputs.
initializeDeviceMemory(buffer);
testExecution(preparedModel,
{.inputs = {deviceMemoryArg, deviceMemoryArg},
.outputs = {sharedMemoryArg1, sharedMemoryArg2},
.pools = {sharedMemory1, sharedMemory2, deviceMemory}},
ErrorStatus::NONE);
}
TEST_P(MemoryDomainExecutionTest, InvalidDimensions) {
// FENCED execution does not support dynamic shape.
if (kExecutor == Executor::FENCED) return;
TestOperand testOperand = kTestOperand;
testOperand.dimensions[0] = 0;
auto preparedModel = createConvPreparedModel(testOperand);
auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0}, kTestOperand.dimensions);
if (buffer == nullptr) return;
// Use an incompatible dimension and make sure the length matches with the bad dimension.
auto badDimensions = kTestOperand.dimensions;
badDimensions[0] = 2;
const uint32_t badTestOperandDataSize = kTestOperandDataSize * 2;
Request::MemoryPool sharedMemory = createSharedMemoryPool(badTestOperandDataSize);
Request::MemoryPool deviceMemory = createDeviceMemoryPool(token);
RequestArgument sharedMemoryArg = {
.location = {.poolIndex = 0, .offset = 0, .length = badTestOperandDataSize},
.dimensions = badDimensions};
RequestArgument deviceMemoryArg = {.location = {.poolIndex = 1}};
RequestArgument deviceMemoryArgWithBadDimensions = {.location = {.poolIndex = 1},
.dimensions = badDimensions};
initializeDeviceMemory(buffer);
testExecution(preparedModel,
{.inputs = {deviceMemoryArgWithBadDimensions},
.outputs = {sharedMemoryArg},
.pools = {sharedMemory, deviceMemory}},
ErrorStatus::INVALID_ARGUMENT);
testExecution(preparedModel,
{.inputs = {sharedMemoryArg},
.outputs = {deviceMemoryArgWithBadDimensions},
.pools = {sharedMemory, deviceMemory}},
ErrorStatus::INVALID_ARGUMENT);
testExecution(preparedModel,
{.inputs = {sharedMemoryArg},
.outputs = {deviceMemoryArg},
.pools = {sharedMemory, deviceMemory}},
ErrorStatus::GENERAL_FAILURE);
}
const auto kExecutorChoices = testing::Values(Executor::ASYNC, Executor::SYNC, Executor::FENCED);
std::string printMemoryDomainExecutionTest(
const testing::TestParamInfo<MemoryDomainExecutionTestParam>& info) {
const auto& [namedDevice, operandType, executor] = info.param;
const std::string type = toString(static_cast<OperandType>(operandType));
const std::string executorStr = toString(executor);
return gtestCompliantName(getName(namedDevice) + "_" + type + "_" + executorStr);
}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MemoryDomainExecutionTest);
INSTANTIATE_TEST_SUITE_P(TestMemoryDomain, MemoryDomainExecutionTest,
testing::Combine(kNamedDeviceChoices, kTestOperandTypeChoices,
kExecutorChoices),
printMemoryDomainExecutionTest);
} // namespace android::hardware::neuralnetworks::V1_3::vts::functional