/* * Copyright (C) 2018 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 <android-base/scopeguard.h> #include <gtest/gtest.h> #include <nnapi/SharedMemory.h> #include <nnapi/Types.h> #include <nnapi/Validation.h> #include "GeneratedTestUtils.h" #include "Memory.h" #include "ModelBuilder.h" #include "TestNeuralNetworksWrapper.h" #ifdef __ANDROID__ #include <android/hardware_buffer.h> #endif // __ANDROID__ namespace android::nn::compliance_test { using namespace test_helper; using WrapperModel = test_wrapper::Model; using WrapperOperandType = test_wrapper::OperandType; using WrapperType = test_wrapper::Type; // Tag for the compilance tests class ComplianceTest : public ::testing::Test {}; // Verifies the earliest supported version for the model. static void testAvailableSinceVersion(const WrapperModel& wrapperModel, const Version testVersion) { // Creates a canonical model from a wrapper model. auto modelBuilder = reinterpret_cast<const ModelBuilder*>(wrapperModel.getHandle()); EXPECT_TRUE(modelBuilder->isFinished()); EXPECT_TRUE(modelBuilder->isValid()); Model model = modelBuilder->makeModel(); const auto modelVersion = validate(model); ASSERT_TRUE(modelVersion.ok()) << modelVersion.error(); ASSERT_EQ(testVersion, modelVersion.value()); } // Verifies the earliest supported version for the request. static void testAvailableSinceVersion(const Request& request, const Version testVersion) { const auto requestVersion = validate(request); ASSERT_TRUE(requestVersion.ok()) << requestVersion.error(); ASSERT_EQ(testVersion, requestVersion.value()); } static const WrapperOperandType kTypeTensorFloat(WrapperType::TENSOR_FLOAT32, {1}); static const WrapperOperandType kTypeTensorFloatRank0(WrapperType::TENSOR_FLOAT32, {}); static const WrapperOperandType kTypeInt32(WrapperType::INT32, {}); const int32_t kNoActivation = ANEURALNETWORKS_FUSED_NONE; TEST_F(ComplianceTest, Rank0TensorModelInput) { // A simple ADD operation: op1 ADD op2 = op3, with op1 and op2 of rank 0. WrapperModel model; auto op1 = model.addOperand(&kTypeTensorFloatRank0); auto op2 = model.addOperand(&kTypeTensorFloatRank0); auto op3 = model.addOperand(&kTypeTensorFloat); auto act = model.addConstantOperand(&kTypeInt32, kNoActivation); model.addOperation(ANEURALNETWORKS_ADD, {op1, op2, act}, {op3}); model.identifyInputsAndOutputs({op1, op2}, {op3}); ASSERT_TRUE(model.isValid()); model.finish(); testAvailableSinceVersion(model, kVersionFeatureLevel3); } TEST_F(ComplianceTest, Rank0TensorModelOutput) { // A simple ADD operation: op1 ADD op2 = op3, with op3 of rank 0. WrapperModel model; auto op1 = model.addOperand(&kTypeTensorFloat); auto op2 = model.addOperand(&kTypeTensorFloat); auto op3 = model.addOperand(&kTypeTensorFloatRank0); auto act = model.addConstantOperand(&kTypeInt32, kNoActivation); model.addOperation(ANEURALNETWORKS_ADD, {op1, op2, act}, {op3}); model.identifyInputsAndOutputs({op1, op2}, {op3}); ASSERT_TRUE(model.isValid()); model.finish(); testAvailableSinceVersion(model, kVersionFeatureLevel3); } TEST_F(ComplianceTest, Rank0TensorTemporaryVariable) { // Two ADD operations: op1 ADD op2 = op3, op3 ADD op4 = op5, with op3 of rank 0. WrapperModel model; auto op1 = model.addOperand(&kTypeTensorFloat); auto op2 = model.addOperand(&kTypeTensorFloat); auto op3 = model.addOperand(&kTypeTensorFloatRank0); auto op4 = model.addOperand(&kTypeTensorFloat); auto op5 = model.addOperand(&kTypeTensorFloat); auto act = model.addConstantOperand(&kTypeInt32, kNoActivation); model.addOperation(ANEURALNETWORKS_ADD, {op1, op2, act}, {op3}); model.addOperation(ANEURALNETWORKS_ADD, {op3, op4, act}, {op5}); model.identifyInputsAndOutputs({op1, op2, op4}, {op5}); ASSERT_TRUE(model.isValid()); model.finish(); testAvailableSinceVersion(model, kVersionFeatureLevel3); } // Hardware buffers are an Android concept, which aren't necessarily // available on other platforms such as ChromeOS, which also build NNAPI. #if defined(__ANDROID__) TEST_F(ComplianceTest, HardwareBufferModel) { const size_t memorySize = 20; AHardwareBuffer_Desc desc{ .width = memorySize, .height = 1, .layers = 1, .format = AHARDWAREBUFFER_FORMAT_BLOB, .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, }; AHardwareBuffer* buffer = nullptr; ASSERT_EQ(AHardwareBuffer_allocate(&desc, &buffer), 0); auto allocateGuard = android::base::make_scope_guard([buffer]() { AHardwareBuffer_release(buffer); }); test_wrapper::Memory memory(buffer); ASSERT_TRUE(memory.isValid()); // A simple ADD operation: op1 ADD op2 = op3, with op2 using a const hardware buffer. WrapperModel model; auto op1 = model.addOperand(&kTypeTensorFloat); auto op2 = model.addOperand(&kTypeTensorFloat); auto op3 = model.addOperand(&kTypeTensorFloat); auto act = model.addConstantOperand(&kTypeInt32, kNoActivation); model.setOperandValueFromMemory(op2, &memory, 0, sizeof(float)); model.addOperation(ANEURALNETWORKS_ADD, {op1, op2, act}, {op3}); model.identifyInputsAndOutputs({op1}, {op3}); ASSERT_TRUE(model.isValid()); model.finish(); testAvailableSinceVersion(model, kVersionFeatureLevel3); } TEST_F(ComplianceTest, HardwareBufferRequest) { constexpr size_t kAhwbMemorySize = 1024; const auto [n, ahwb] = MemoryRuntimeAHWB::create(kAhwbMemorySize); ASSERT_EQ(n, ANEURALNETWORKS_NO_ERROR); const Request::MemoryPool ahwbMemoryPool = ahwb->getMemoryPool(); constexpr size_t kSharedMemorySize = 1024; auto maybeSharedMemoryPool = createSharedMemory(kSharedMemorySize); ASSERT_TRUE(maybeSharedMemoryPool.ok()) << maybeSharedMemoryPool.error().message; const Request::MemoryPool sharedMemoryPool = std::move(maybeSharedMemoryPool).value(); // AHardwareBuffer as input. testAvailableSinceVersion( Request{ .inputs = {{.lifetime = Request::Argument::LifeTime::POOL, .location = {.poolIndex = 0, .length = kAhwbMemorySize}, .dimensions = {}}}, .outputs = {{.lifetime = Request::Argument::LifeTime::POOL, .location = {.poolIndex = 1, .length = kSharedMemorySize}, .dimensions = {}}}, .pools = {ahwbMemoryPool, sharedMemoryPool}, }, kVersionFeatureLevel3); // AHardwareBuffer as output. testAvailableSinceVersion( Request{ .inputs = {{.lifetime = Request::Argument::LifeTime::POOL, .location = {.poolIndex = 0, .length = kSharedMemorySize}, .dimensions = {}}}, .outputs = {{.lifetime = Request::Argument::LifeTime::POOL, .location = {.poolIndex = 1, .length = kAhwbMemorySize}, .dimensions = {}}}, .pools = {sharedMemoryPool, ahwbMemoryPool}, }, kVersionFeatureLevel3); } #endif TEST_F(ComplianceTest, DeviceMemory) { constexpr size_t kSharedMemorySize = 1024; auto maybeSharedMemoryPool = createSharedMemory(kSharedMemorySize); ASSERT_TRUE(maybeSharedMemoryPool.ok()) << maybeSharedMemoryPool.error().message; const Request::MemoryPool sharedMemoryPool = std::move(maybeSharedMemoryPool).value(); const Request::MemoryPool deviceMemoryPool = Request::MemoryDomainToken(1); // Device memory as input. testAvailableSinceVersion( Request{ .inputs = {{.lifetime = Request::Argument::LifeTime::POOL, .location = {.poolIndex = 0}, .dimensions = {}}}, .outputs = {{.lifetime = Request::Argument::LifeTime::POOL, .location = {.poolIndex = 1, .length = kSharedMemorySize}, .dimensions = {}}}, .pools = {deviceMemoryPool, sharedMemoryPool}, }, kVersionFeatureLevel4); // Device memory as output. testAvailableSinceVersion( Request{ .inputs = {{.lifetime = Request::Argument::LifeTime::POOL, .location = {.poolIndex = 0, .length = kSharedMemorySize}, .dimensions = {}}}, .outputs = {{.lifetime = Request::Argument::LifeTime::POOL, .location = {.poolIndex = 1}, .dimensions = {}}}, .pools = {sharedMemoryPool, deviceMemoryPool}, }, kVersionFeatureLevel4); } class GeneratedComplianceTest : public generated_tests::GeneratedTestBase {}; TEST_P(GeneratedComplianceTest, Test) { generated_tests::GeneratedModel model; generated_tests::createModel(testModel, &model); ASSERT_TRUE(model.isValid()); model.finish(); switch (testModel.minSupportedVersion) { // TODO(b/209797313): Unify HalVersion and Version. case TestHalVersion::V1_0: testAvailableSinceVersion(model, kVersionFeatureLevel1); break; case TestHalVersion::V1_1: testAvailableSinceVersion(model, kVersionFeatureLevel2); break; case TestHalVersion::V1_2: testAvailableSinceVersion(model, kVersionFeatureLevel3); break; case TestHalVersion::V1_3: testAvailableSinceVersion(model, kVersionFeatureLevel4); break; case TestHalVersion::AIDL_V1: testAvailableSinceVersion(model, kVersionFeatureLevel5); break; case TestHalVersion::AIDL_V2: testAvailableSinceVersion(model, kVersionFeatureLevel6); break; case TestHalVersion::AIDL_V3: testAvailableSinceVersion(model, kVersionFeatureLevel7); break; case TestHalVersion::UNKNOWN: FAIL(); } } INSTANTIATE_GENERATED_TEST(GeneratedComplianceTest, [](const TestModel& testModel) { return !testModel.expectFailure && testModel.minSupportedVersion != TestHalVersion::UNKNOWN; }); } // namespace android::nn::compliance_test