| /* |
| * 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. |
| */ |
| |
| #include <HalInterfaces.h> |
| #include <SampleDriver.h> |
| #include <SampleDriverFull.h> |
| #include <gtest/gtest.h> |
| #include <nnapi/SharedMemory.h> |
| #include <nnapi/hal/1.2/Device.h> |
| |
| #include <algorithm> |
| #include <map> |
| #include <set> |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| |
| #include "HalUtils.h" |
| #include "Manager.h" |
| #include "Memory.h" |
| #include "TestNeuralNetworksWrapper.h" |
| #include "TestUtils.h" |
| |
| #ifdef __ANDROID__ |
| constexpr bool kRunningOnAndroid = true; |
| #else // __ANDROID__ |
| constexpr bool kRunningOnAndroid = false; |
| #endif // __ANDROID__ |
| |
| using namespace android::nn; |
| namespace hardware = android::hardware; |
| using WrapperResult = test_wrapper::Result; |
| using Type = test_wrapper::Type; |
| using android::sp; |
| using android::nn::isAhwbBlob; |
| |
| namespace { |
| |
| // A buffer for test that does nothing. |
| class TestBuffer : public V1_3::IBuffer { |
| public: |
| hardware::Return<V1_3::ErrorStatus> copyTo(const hardware::hidl_memory&) override { |
| return V1_3::ErrorStatus::DEVICE_UNAVAILABLE; |
| } |
| hardware::Return<V1_3::ErrorStatus> copyFrom(const hardware::hidl_memory&, |
| const hardware::hidl_vec<uint32_t>&) override { |
| return V1_3::ErrorStatus::DEVICE_UNAVAILABLE; |
| } |
| }; |
| |
| enum class AllocateReturn { OK, BAD_TOKEN, BAD_IBUFFER, BAD_STATUS, NOT_SUPPORTED }; |
| |
| // Print AllocateReturn enum for better GTEST failure messages |
| std::ostream& operator<<(std::ostream& os, AllocateReturn allocateReturn) { |
| switch (allocateReturn) { |
| case AllocateReturn::OK: |
| return os << "OK"; |
| case AllocateReturn::BAD_IBUFFER: |
| return os << "BAD_IBUFFER"; |
| case AllocateReturn::BAD_TOKEN: |
| return os << "BAD_TOKEN"; |
| case AllocateReturn::BAD_STATUS: |
| return os << "BAD_STATUS"; |
| case AllocateReturn::NOT_SUPPORTED: |
| return os << "NOT_SUPPORTED"; |
| } |
| LOG(FATAL) << "Invalid AllocateReturn code " << static_cast<int>(allocateReturn); |
| return os; |
| } |
| |
| class TestDriverLatest : public sample_driver::SampleDriver { |
| public: |
| TestDriverLatest(const char* name, std::set<V1_3::OperationType> supportedOperations, |
| AllocateReturn allocateReturn) |
| : SampleDriver(name), |
| kSupportedOperations(std::move(supportedOperations)), |
| kAllocateReturn(allocateReturn) {} |
| |
| hardware::Return<void> getCapabilities_1_3(getCapabilities_1_3_cb cb) override { |
| android::nn::initVLogMask(); |
| // Faster than cpu. |
| const V1_0::PerformanceInfo kPerf = {.execTime = 0.1, .powerUsage = 0.1}; |
| const V1_3::Capabilities capabilities = { |
| .relaxedFloat32toFloat16PerformanceScalar = kPerf, |
| .relaxedFloat32toFloat16PerformanceTensor = kPerf, |
| .operandPerformance = nonExtensionOperandPerformance<HalVersion::V1_3>(kPerf), |
| .ifPerformance = kPerf, |
| .whilePerformance = kPerf}; |
| cb(V1_3::ErrorStatus::NONE, capabilities); |
| return hardware::Void(); |
| } |
| |
| hardware::Return<void> getSupportedOperations_1_3(const V1_3::Model& model, |
| getSupportedOperations_1_3_cb cb) override { |
| // The tests will never use a referenced model. |
| CHECK(model.referenced.size() == 0); |
| std::vector<bool> supported(model.main.operations.size(), false); |
| std::transform(model.main.operations.begin(), model.main.operations.end(), |
| supported.begin(), [this](const V1_3::Operation& op) { |
| return kSupportedOperations.count(op.type) > 0; |
| }); |
| cb(V1_3::ErrorStatus::NONE, supported); |
| return hardware::Void(); |
| } |
| |
| hardware::Return<void> allocate(const V1_3::BufferDesc&, |
| const hardware::hidl_vec<sp<V1_3::IPreparedModel>>&, |
| const hardware::hidl_vec<V1_3::BufferRole>&, |
| const hardware::hidl_vec<V1_3::BufferRole>&, |
| allocate_cb cb) override { |
| switch (kAllocateReturn) { |
| case AllocateReturn::OK: |
| cb(V1_3::ErrorStatus::NONE, new TestBuffer(), mValidBufferToken++); |
| return hardware::Void(); |
| case AllocateReturn::BAD_IBUFFER: |
| cb(V1_3::ErrorStatus::NONE, nullptr, mValidBufferToken++); |
| return hardware::Void(); |
| case AllocateReturn::BAD_TOKEN: |
| cb(V1_3::ErrorStatus::NONE, new TestBuffer(), 0); |
| return hardware::Void(); |
| case AllocateReturn::BAD_STATUS: |
| cb(V1_3::ErrorStatus::GENERAL_FAILURE, new TestBuffer(), mValidBufferToken++); |
| return hardware::Void(); |
| case AllocateReturn::NOT_SUPPORTED: |
| cb(V1_3::ErrorStatus::GENERAL_FAILURE, nullptr, 0); |
| return hardware::Void(); |
| } |
| LOG(FATAL) << "Invalid AllocateReturn code " << static_cast<int>(kAllocateReturn); |
| return hardware::Void(); |
| } |
| |
| private: |
| const std::set<V1_3::OperationType> kSupportedOperations; |
| const AllocateReturn kAllocateReturn; |
| uint32_t mValidBufferToken = 1; |
| }; |
| |
| // Create the following model for test. |
| // |
| // input0 ---+ |
| // +--- ADD ---> output0 ---+ |
| // input1 ---+ +--- MUL ---> output1 (dynamic shape) |
| // +--- SUB ---> temp ---+ |
| // input2 ---+ |
| // |
| void createTestModel(test_wrapper::Model* model) { |
| test_wrapper::OperandType tensorTypeFullySpecified(Type::TENSOR_FLOAT32, {1}); |
| test_wrapper::OperandType tensorTypeDynamicShape(Type::TENSOR_FLOAT32, {0}); |
| test_wrapper::OperandType actType(Type::INT32, {}); |
| uint32_t input0 = model->addOperand(&tensorTypeFullySpecified); |
| uint32_t input1 = model->addOperand(&tensorTypeFullySpecified); |
| uint32_t input2 = model->addOperand(&tensorTypeFullySpecified); |
| uint32_t temp = model->addOperand(&tensorTypeFullySpecified); |
| uint32_t output0 = model->addOperand(&tensorTypeFullySpecified); |
| uint32_t output1 = model->addOperand(&tensorTypeDynamicShape); |
| uint32_t act = model->addOperand(&actType); |
| int32_t activation = 0; |
| model->setOperandValue(act, &activation, sizeof(int32_t)); |
| model->addOperation(ANEURALNETWORKS_ADD, {input0, input1, act}, {output0}); |
| model->addOperation(ANEURALNETWORKS_SUB, {input1, input2, act}, {temp}); |
| model->addOperation(ANEURALNETWORKS_MUL, {output0, temp, act}, {output1}); |
| model->identifyInputsAndOutputs({input0, input1, input2}, {output0, output1}); |
| EXPECT_EQ(model->finish(), WrapperResult::NO_ERROR); |
| } |
| |
| class MemoryDomainTestBase : public ::testing::Test { |
| protected: |
| void SetUp() override { |
| ::testing::Test::SetUp(); |
| if (DeviceManager::get()->getUseCpuOnly()) { |
| GTEST_SKIP(); |
| } |
| createTestModel(&mModel); |
| // Clear the device list. |
| DeviceManager::get()->forTest_setDevices({}); |
| } |
| |
| void TearDown() override { |
| DeviceManager::get()->forTest_reInitializeDeviceList(); |
| ::testing::Test::TearDown(); |
| } |
| |
| // If "deviceNames" is not empty, the compilation is created with explicit device list; |
| // otherwise, it is created normally. |
| test_wrapper::Compilation createCompilation(const std::vector<std::string>& deviceNames) { |
| test_wrapper::Compilation compilation; |
| if (!deviceNames.empty()) { |
| // Map device names to ANeuralNetworksDevice. |
| std::map<std::string, ANeuralNetworksDevice*> deviceMap; |
| uint32_t numDevices = 0; |
| EXPECT_EQ(ANeuralNetworks_getDeviceCount(&numDevices), ANEURALNETWORKS_NO_ERROR); |
| for (uint32_t i = 0; i < numDevices; i++) { |
| ANeuralNetworksDevice* device = nullptr; |
| const char* name = nullptr; |
| EXPECT_EQ(ANeuralNetworks_getDevice(i, &device), ANEURALNETWORKS_NO_ERROR); |
| EXPECT_EQ(ANeuralNetworksDevice_getName(device, &name), ANEURALNETWORKS_NO_ERROR); |
| deviceMap.emplace(name, device); |
| } |
| std::vector<const ANeuralNetworksDevice*> devices(deviceNames.size()); |
| std::transform(deviceNames.begin(), deviceNames.end(), devices.begin(), |
| [&deviceMap](const std::string& name) { return deviceMap.at(name); }); |
| WrapperResult result; |
| std::tie(result, compilation) = |
| test_wrapper::Compilation::createForDevices(&mModel, devices); |
| EXPECT_EQ(result, WrapperResult::NO_ERROR); |
| } else { |
| compilation = test_wrapper::Compilation(&mModel); |
| } |
| EXPECT_EQ(compilation.finish(), WrapperResult::NO_ERROR); |
| return compilation; |
| } |
| |
| std::pair<int, test_wrapper::Memory> allocateDeviceMemory( |
| const test_wrapper::Compilation& compilation, const std::vector<uint32_t>& inputIndexes, |
| const std::vector<uint32_t>& outputIndexes) { |
| const auto* annCompilation = compilation.getHandle(); |
| ANeuralNetworksMemoryDesc* desc = nullptr; |
| EXPECT_EQ(ANeuralNetworksMemoryDesc_create(&desc), ANEURALNETWORKS_NO_ERROR); |
| for (uint32_t index : inputIndexes) { |
| EXPECT_EQ(ANeuralNetworksMemoryDesc_addInputRole(desc, annCompilation, index, 1.0f), |
| ANEURALNETWORKS_NO_ERROR); |
| } |
| for (uint32_t index : outputIndexes) { |
| EXPECT_EQ(ANeuralNetworksMemoryDesc_addOutputRole(desc, annCompilation, index, 1.0f), |
| ANEURALNETWORKS_NO_ERROR); |
| } |
| EXPECT_EQ(ANeuralNetworksMemoryDesc_finish(desc), ANEURALNETWORKS_NO_ERROR); |
| |
| ANeuralNetworksMemory* memory; |
| int n = ANeuralNetworksMemory_createFromDesc(desc, &memory); |
| ANeuralNetworksMemoryDesc_free(desc); |
| return {n, test_wrapper::Memory(memory)}; |
| } |
| |
| test_wrapper::Model mModel; |
| }; |
| |
| // Test memory domain with the following parameters |
| // - If true, use a V1_2 driver, otherwise, use the latest version; |
| // - If true, compile with explicit device list, otherwise, compile in the default way; |
| // - The return of the allocate function. |
| using MemoryDomainTestParam = std::tuple<bool, bool, AllocateReturn>; |
| |
| class MemoryDomainTest : public MemoryDomainTestBase, |
| public ::testing::WithParamInterface<MemoryDomainTestParam> { |
| protected: |
| // If kUseV1_2Driver, allocateReturn must be AllocateReturn::NOT_SUPPORTED. |
| void createAndRegisterDriver(const char* name, |
| std::set<V1_3::OperationType> supportedOperations, |
| AllocateReturn allocateReturn) { |
| if (kUseV1_2Driver) { |
| CHECK(allocateReturn == AllocateReturn::NOT_SUPPORTED); |
| const sp<TestDriverLatest> testDriver = |
| new TestDriverLatest(name, supportedOperations, AllocateReturn::NOT_SUPPORTED); |
| DeviceManager::get()->forTest_registerDevice( |
| V1_2::utils::Device::create(name, testDriver).value()); |
| } else { |
| DeviceManager::get()->forTest_registerDevice(makeSharedDevice( |
| name, |
| new TestDriverLatest(name, std::move(supportedOperations), allocateReturn))); |
| } |
| } |
| |
| // If not kCompileWithExplicitDeviceList, the input argument "deviceNames" is ignored. |
| test_wrapper::Compilation createCompilation(const std::vector<std::string>& deviceNames) { |
| if (kCompileWithExplicitDeviceList) { |
| return MemoryDomainTestBase::createCompilation(deviceNames); |
| } else { |
| return MemoryDomainTestBase::createCompilation({}); |
| } |
| } |
| |
| const bool kUseV1_2Driver = std::get<0>(GetParam()); |
| const bool kCompileWithExplicitDeviceList = std::get<1>(GetParam()); |
| const AllocateReturn kAllocateReturn = std::get<2>(GetParam()); |
| }; |
| |
| // Test device memory allocation on a compilation with only a single partition. |
| TEST_P(MemoryDomainTest, SinglePartition) { |
| createAndRegisterDriver( |
| "test_driver", |
| {V1_3::OperationType::ADD, V1_3::OperationType::SUB, V1_3::OperationType::MUL}, |
| kAllocateReturn); |
| auto compilation = createCompilation({"test_driver"}); |
| ASSERT_NE(compilation.getHandle(), nullptr); |
| |
| auto [n, memory] = allocateDeviceMemory(compilation, {0}, {0}); |
| if (kAllocateReturn == AllocateReturn::OK) { |
| // The memory should be backed by the IBuffer returned from the driver. |
| ASSERT_EQ(n, ANEURALNETWORKS_NO_ERROR); |
| const RuntimeMemory* m = reinterpret_cast<const RuntimeMemory*>(memory.get()); |
| ASSERT_NE(m, nullptr); |
| EXPECT_NE(m->getIBuffer(), nullptr); |
| } else { |
| if (kCompileWithExplicitDeviceList) { |
| // Should not fallback when the compiled with explicit device list. |
| ASSERT_EQ(n, ANEURALNETWORKS_OP_FAILED); |
| } else { |
| // The memory should fallback to ashmem or blob ahwb based on the driver version. |
| ASSERT_EQ(n, ANEURALNETWORKS_NO_ERROR); |
| const RuntimeMemory* m = reinterpret_cast<const RuntimeMemory*>(memory.get()); |
| ASSERT_NE(m, nullptr); |
| EXPECT_EQ(m->getIBuffer(), nullptr); |
| const auto& memory = m->getMemory(); |
| EXPECT_TRUE(validate(memory).ok()); |
| if (kUseV1_2Driver) { |
| EXPECT_FALSE(isAhwbBlob(memory)); |
| } else { |
| EXPECT_EQ(isAhwbBlob(memory), kRunningOnAndroid); |
| } |
| } |
| } |
| } |
| |
| // Test device memory allocation on a compilation with multiple partitions. |
| TEST_P(MemoryDomainTest, MultiplePartitions) { |
| createAndRegisterDriver("test_driver_add", {V1_3::OperationType::ADD}, kAllocateReturn); |
| createAndRegisterDriver("test_driver_sub", {V1_3::OperationType::SUB}, kAllocateReturn); |
| createAndRegisterDriver("test_driver_mul", {V1_3::OperationType::MUL}, kAllocateReturn); |
| auto compilation = createCompilation({"test_driver_add", "test_driver_sub", "test_driver_mul"}); |
| ASSERT_NE(compilation.getHandle(), nullptr); |
| |
| { |
| // input0 is only used in one single partition. |
| auto [n, memory] = allocateDeviceMemory(compilation, {0}, {}); |
| if (kAllocateReturn == AllocateReturn::OK) { |
| // The memory should be backed by the IBuffer returned from the driver. |
| ASSERT_EQ(n, ANEURALNETWORKS_NO_ERROR); |
| const RuntimeMemory* m = reinterpret_cast<const RuntimeMemory*>(memory.get()); |
| ASSERT_NE(m, nullptr); |
| EXPECT_NE(m->getIBuffer(), nullptr); |
| } else { |
| if (kCompileWithExplicitDeviceList) { |
| // Should not fallback when the compiled with explicit device list. |
| ASSERT_EQ(n, ANEURALNETWORKS_OP_FAILED); |
| } else { |
| // The memory should fallback to ashmem or blob ahwb based on the driver version. |
| ASSERT_EQ(n, ANEURALNETWORKS_NO_ERROR); |
| const RuntimeMemory* m = reinterpret_cast<const RuntimeMemory*>(memory.get()); |
| ASSERT_NE(m, nullptr); |
| EXPECT_EQ(m->getIBuffer(), nullptr); |
| const auto& memory = m->getMemory(); |
| EXPECT_TRUE(validate(memory).ok()); |
| if (kUseV1_2Driver) { |
| EXPECT_FALSE(isAhwbBlob(memory)); |
| } else { |
| EXPECT_EQ(isAhwbBlob(memory), kRunningOnAndroid); |
| } |
| } |
| } |
| } |
| |
| { |
| // input1 is shared by two partitions with different drivers, so the runtime will not |
| // attempt to allocate on device. |
| auto [n, memory] = allocateDeviceMemory(compilation, {1}, {}); |
| if (kCompileWithExplicitDeviceList) { |
| // Should not fallback when the compiled with explicit device list. |
| ASSERT_EQ(n, ANEURALNETWORKS_OP_FAILED); |
| } else { |
| // The memory should fallback to ashmem or blob ahwb based on the driver version. |
| ASSERT_EQ(n, ANEURALNETWORKS_NO_ERROR); |
| const RuntimeMemory* m = reinterpret_cast<const RuntimeMemory*>(memory.get()); |
| ASSERT_NE(m, nullptr); |
| EXPECT_EQ(m->getIBuffer(), nullptr); |
| const auto& memory = m->getMemory(); |
| EXPECT_TRUE(validate(memory).ok()); |
| if (kUseV1_2Driver) { |
| EXPECT_FALSE(isAhwbBlob(memory)); |
| } else { |
| EXPECT_EQ(isAhwbBlob(memory), kRunningOnAndroid); |
| } |
| } |
| } |
| |
| { |
| // output0 is shared by two partitions with different drivers, so the runtime will not |
| // attempt to allocate on device. |
| auto [n, memory] = allocateDeviceMemory(compilation, {}, {0}); |
| if (kCompileWithExplicitDeviceList) { |
| // Should not fallback when the compiled with explicit device list. |
| ASSERT_EQ(n, ANEURALNETWORKS_OP_FAILED); |
| } else { |
| // The memory should fallback to ashmem or blob ahwb based on the driver version. |
| ASSERT_EQ(n, ANEURALNETWORKS_NO_ERROR); |
| const RuntimeMemory* m = reinterpret_cast<const RuntimeMemory*>(memory.get()); |
| ASSERT_NE(m, nullptr); |
| EXPECT_EQ(m->getIBuffer(), nullptr); |
| const auto& memory = m->getMemory(); |
| EXPECT_TRUE(validate(memory).ok()); |
| if (kUseV1_2Driver) { |
| EXPECT_FALSE(isAhwbBlob(memory)); |
| } else { |
| EXPECT_EQ(isAhwbBlob(memory), kRunningOnAndroid); |
| } |
| } |
| } |
| } |
| |
| // Test device memory allocation with dynamic shape. |
| TEST_P(MemoryDomainTest, DynamicShape) { |
| createAndRegisterDriver( |
| "test_driver", |
| {V1_3::OperationType::ADD, V1_3::OperationType::SUB, V1_3::OperationType::MUL}, |
| kAllocateReturn); |
| auto compilation = createCompilation({"test_driver"}); |
| ASSERT_NE(compilation.getHandle(), nullptr); |
| |
| auto [n, memory] = allocateDeviceMemory(compilation, {}, {1}); |
| if (kAllocateReturn == AllocateReturn::OK) { |
| // The memory should be backed by the IBuffer returned from the driver. |
| ASSERT_EQ(n, ANEURALNETWORKS_NO_ERROR); |
| const RuntimeMemory* m = reinterpret_cast<const RuntimeMemory*>(memory.get()); |
| ASSERT_NE(m, nullptr); |
| EXPECT_NE(m->getIBuffer(), nullptr); |
| } else { |
| // We do not fallback in the case of dynamic shape. |
| ASSERT_EQ(n, ANEURALNETWORKS_OP_FAILED); |
| } |
| } |
| |
| static const auto kAllocateReturnChoices = |
| testing::Values(AllocateReturn::OK, AllocateReturn::BAD_TOKEN, AllocateReturn::BAD_IBUFFER, |
| AllocateReturn::BAD_STATUS, AllocateReturn::NOT_SUPPORTED); |
| |
| INSTANTIATE_TEST_SUITE_P(DeviceVersionV1_2, MemoryDomainTest, |
| testing::Combine(testing::Values(true), testing::Bool(), |
| testing::Values(AllocateReturn::NOT_SUPPORTED))); |
| |
| INSTANTIATE_TEST_SUITE_P(DeviceVersionLatest, MemoryDomainTest, |
| testing::Combine(testing::Values(false), testing::Bool(), |
| kAllocateReturnChoices)); |
| |
| class MemoryDomainCopyTest : public MemoryDomainTestBase {}; |
| |
| TEST_F(MemoryDomainCopyTest, MemoryCopyTest) { |
| DeviceManager::get()->forTest_registerDevice(makeSharedDevice( |
| "test_driver", new sample_driver::SampleDriverFull( |
| "test_driver", {.execTime = 0.1f, .powerUsage = 0.1f}))); |
| auto compilation = createCompilation({"test_driver"}); |
| ASSERT_NE(compilation.getHandle(), nullptr); |
| |
| // Allocate ashmem. |
| const float initValue1 = 3.14f, initValue2 = 2.72f; |
| auto ashmem1 = TestAshmem::createFrom(&initValue1, sizeof(float)); |
| auto ashmem2 = TestAshmem::createFrom(&initValue2, sizeof(float)); |
| ASSERT_NE(ashmem1, nullptr); |
| ASSERT_NE(ashmem2, nullptr); |
| |
| // Allocate device memories. |
| auto [n1, memory1] = allocateDeviceMemory(compilation, {0}, {}); |
| auto [n2, memory2] = allocateDeviceMemory(compilation, {0}, {}); |
| ASSERT_EQ(n1, ANEURALNETWORKS_NO_ERROR); |
| ASSERT_EQ(n2, ANEURALNETWORKS_NO_ERROR); |
| |
| // Test memory copying: ashmem1 -> memory1 -> memory2 -> ashmem2 |
| ASSERT_EQ(ANeuralNetworksMemory_copy(ashmem1->get()->get(), memory1.get()), |
| ANEURALNETWORKS_NO_ERROR); |
| ASSERT_EQ(ANeuralNetworksMemory_copy(memory1.get(), memory2.get()), ANEURALNETWORKS_NO_ERROR); |
| ASSERT_EQ(ANeuralNetworksMemory_copy(memory2.get(), ashmem2->get()->get()), |
| ANEURALNETWORKS_NO_ERROR); |
| |
| EXPECT_EQ(ashmem2->dataAs<float>()[0], initValue1); |
| } |
| |
| } // namespace |