| /* |
| * Copyright (C) 2017 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 "TestNeuralNetworksWrapper.h" |
| |
| #ifdef __ANDROID__ |
| #include <android/hardware_buffer.h> |
| #endif // __ANDROID__ |
| |
| using namespace android::nn::test_wrapper; |
| |
| namespace { |
| |
| typedef float Matrix3x4[3][4]; |
| typedef float Matrix4[4]; |
| |
| const int32_t kNoActivation = ANEURALNETWORKS_FUSED_NONE; |
| |
| class TrivialTest : public ::testing::Test { |
| protected: |
| virtual void SetUp() {} |
| |
| #if defined(__ANDROID__) |
| void testAddTwoWithHardwareBufferInput(uint64_t additionalAhwbUsage, |
| bool allowAllocationFailure); |
| #endif |
| |
| const Matrix3x4 matrix1 = {{1.f, 2.f, 3.f, 4.f}, {5.f, 6.f, 7.f, 8.f}, {9.f, 10.f, 11.f, 12.f}}; |
| const Matrix3x4 matrix2 = {{100.f, 200.f, 300.f, 400.f}, |
| {500.f, 600.f, 700.f, 800.f}, |
| {900.f, 1000.f, 1100.f, 1200.f}}; |
| const Matrix4 matrix2b = {100.f, 200.f, 300.f, 400.f}; |
| const Matrix3x4 matrix3 = { |
| {20.f, 30.f, 40.f, 50.f}, {21.f, 22.f, 23.f, 24.f}, {31.f, 32.f, 33.f, 34.f}}; |
| const Matrix3x4 expected2 = {{101.f, 202.f, 303.f, 404.f}, |
| {505.f, 606.f, 707.f, 808.f}, |
| {909.f, 1010.f, 1111.f, 1212.f}}; |
| const Matrix3x4 expected2b = {{101.f, 202.f, 303.f, 404.f}, |
| {105.f, 206.f, 307.f, 408.f}, |
| {109.f, 210.f, 311.f, 412.f}}; |
| const Matrix3x4 expected2c = {{100.f, 400.f, 900.f, 1600.f}, |
| {500.f, 1200.f, 2100.f, 3200.f}, |
| {900.f, 2000.f, 3300.f, 4800.f}}; |
| |
| const Matrix3x4 expected3 = {{121.f, 232.f, 343.f, 454.f}, |
| {526.f, 628.f, 730.f, 832.f}, |
| {940.f, 1042.f, 1144.f, 1246.f}}; |
| const Matrix3x4 expected3b = { |
| {22.f, 34.f, 46.f, 58.f}, {31.f, 34.f, 37.f, 40.f}, {49.f, 52.f, 55.f, 58.f}}; |
| }; |
| |
| // Create a model that can add two tensors using a one node graph. |
| void CreateAddTwoTensorModel(Model* model) { |
| OperandType matrixType(Type::TENSOR_FLOAT32, {3, 4}); |
| OperandType scalarType(Type::INT32, {}); |
| auto a = model->addOperand(&matrixType); |
| auto b = model->addOperand(&matrixType); |
| auto c = model->addOperand(&matrixType); |
| auto d = model->addConstantOperand(&scalarType, kNoActivation); |
| model->addOperation(ANEURALNETWORKS_ADD, {a, b, d}, {c}); |
| model->identifyInputsAndOutputs({a, b}, {c}); |
| ASSERT_TRUE(model->isValid()); |
| model->finish(); |
| } |
| |
| // Create a model that can add three tensors using a two node graph, |
| // with one tensor set as part of the model. |
| void CreateAddThreeTensorModel(Model* model, const Matrix3x4 bias) { |
| OperandType matrixType(Type::TENSOR_FLOAT32, {3, 4}); |
| OperandType scalarType(Type::INT32, {}); |
| auto a = model->addOperand(&matrixType); |
| auto b = model->addOperand(&matrixType); |
| auto c = model->addOperand(&matrixType); |
| auto d = model->addOperand(&matrixType); |
| auto e = model->addOperand(&matrixType); |
| auto f = model->addConstantOperand(&scalarType, kNoActivation); |
| model->setOperandValue(e, bias, sizeof(Matrix3x4)); |
| model->addOperation(ANEURALNETWORKS_ADD, {a, c, f}, {b}); |
| model->addOperation(ANEURALNETWORKS_ADD, {b, e, f}, {d}); |
| model->identifyInputsAndOutputs({c, a}, {d}); |
| ASSERT_TRUE(model->isValid()); |
| model->finish(); |
| } |
| |
| // Check that the values are the same. This works only if dealing with integer |
| // value, otherwise we should accept values that are similar if not exact. |
| int CompareMatrices(const Matrix3x4& expected, const Matrix3x4& actual) { |
| int errors = 0; |
| for (int i = 0; i < 3; i++) { |
| for (int j = 0; j < 4; j++) { |
| if (expected[i][j] != actual[i][j]) { |
| printf("expected[%d][%d] != actual[%d][%d], %f != %f\n", i, j, i, j, |
| static_cast<double>(expected[i][j]), static_cast<double>(actual[i][j])); |
| errors++; |
| } |
| } |
| } |
| return errors; |
| } |
| |
| TEST_F(TrivialTest, AddTwo) { |
| Model modelAdd2; |
| CreateAddTwoTensorModel(&modelAdd2); |
| |
| // Test the one node model. |
| Matrix3x4 actual; |
| memset(&actual, 0, sizeof(actual)); |
| Compilation compilation(&modelAdd2); |
| compilation.finish(); |
| Execution execution(&compilation); |
| ASSERT_EQ(execution.setInput(0, matrix1, sizeof(Matrix3x4)), Result::NO_ERROR); |
| ASSERT_EQ(execution.setInput(1, matrix2, sizeof(Matrix3x4)), Result::NO_ERROR); |
| ASSERT_EQ(execution.setOutput(0, actual, sizeof(Matrix3x4)), Result::NO_ERROR); |
| ASSERT_EQ(execution.compute(), Result::NO_ERROR); |
| ASSERT_EQ(CompareMatrices(expected2, actual), 0); |
| } |
| |
| // Hardware buffers are an Android concept, which aren't necessarily |
| // available on other platforms such as ChromeOS, which also build NNAPI. |
| #if defined(__ANDROID__) |
| void TrivialTest::testAddTwoWithHardwareBufferInput(uint64_t additionalAhwbUsage, |
| bool allowAllocationFailure) { |
| Model modelAdd2; |
| CreateAddTwoTensorModel(&modelAdd2); |
| |
| const uint64_t cpuUsage = |
| AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN; |
| AHardwareBuffer_Desc desc{ |
| .width = sizeof(matrix1), |
| .height = 1, |
| .layers = 1, |
| .format = AHARDWAREBUFFER_FORMAT_BLOB, |
| .usage = cpuUsage | additionalAhwbUsage, |
| }; |
| AHardwareBuffer* matrix1Buffer = nullptr; |
| int err = AHardwareBuffer_allocate(&desc, &matrix1Buffer); |
| if (allowAllocationFailure && err != 0) { |
| GTEST_SKIP() << "Test skipped: AHardwareBuffer_allocate failed"; |
| } |
| ASSERT_EQ(err, 0); |
| auto allocateGuard = android::base::make_scope_guard( |
| [matrix1Buffer]() { AHardwareBuffer_release(matrix1Buffer); }); |
| |
| Memory matrix1Memory(matrix1Buffer); |
| ASSERT_TRUE(matrix1Memory.isValid()); |
| |
| // Test the one node model. |
| Matrix3x4 actual; |
| memset(&actual, 0, sizeof(actual)); |
| Compilation compilation(&modelAdd2); |
| compilation.finish(); |
| Execution execution(&compilation); |
| ASSERT_EQ(execution.setInputFromMemory(0, &matrix1Memory, 0, sizeof(Matrix3x4)), |
| Result::NO_ERROR); |
| ASSERT_EQ(execution.setInput(1, matrix2, sizeof(Matrix3x4)), Result::NO_ERROR); |
| ASSERT_EQ(execution.setOutput(0, actual, sizeof(Matrix3x4)), Result::NO_ERROR); |
| |
| // Set the value for matrix1Buffer. |
| void* bufferPtr = nullptr; |
| ASSERT_EQ(AHardwareBuffer_lock(matrix1Buffer, cpuUsage, -1, NULL, &bufferPtr), 0); |
| memcpy((uint8_t*)bufferPtr, matrix1, sizeof(matrix1)); |
| int synFenceFd = -1; |
| ASSERT_EQ(AHardwareBuffer_unlock(matrix1Buffer, &synFenceFd), 0); |
| if (synFenceFd > 0) { |
| // If valid sync fence is return by AHardwareBuffer_unlock, use |
| // ANeuralNetworksExecution_startComputeWithDependencies |
| ANeuralNetworksEvent* eventBufferUnlock; |
| ANeuralNetworksEvent* eventToSignal; |
| ASSERT_EQ(ANeuralNetworksEvent_createFromSyncFenceFd(synFenceFd, &eventBufferUnlock), |
| ANEURALNETWORKS_NO_ERROR); |
| close(synFenceFd); |
| ANeuralNetworksExecution* executionHandle = execution.getHandle(); |
| ASSERT_EQ(ANeuralNetworksExecution_startComputeWithDependencies( |
| executionHandle, &eventBufferUnlock, 1, 0, &eventToSignal), |
| ANEURALNETWORKS_NO_ERROR); |
| ASSERT_EQ(ANeuralNetworksEvent_wait(eventToSignal), ANEURALNETWORKS_NO_ERROR); |
| ANeuralNetworksEvent_free(eventBufferUnlock); |
| ANeuralNetworksEvent_free(eventToSignal); |
| } else { |
| ASSERT_EQ(execution.compute(), Result::NO_ERROR); |
| } |
| |
| ASSERT_EQ(CompareMatrices(expected2, actual), 0); |
| } |
| |
| TEST_F(TrivialTest, AddTwoWithHardwareBufferInput) { |
| testAddTwoWithHardwareBufferInput(/* no additional usage */ 0u, |
| /*allowAllocationFailure=*/false); |
| } |
| |
| TEST_F(TrivialTest, AddTwoWithHardwareBufferInputWithGPUUsage) { |
| testAddTwoWithHardwareBufferInput(AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER, |
| /*allowAllocationFailure=*/true); |
| } |
| #endif |
| |
| TEST_F(TrivialTest, AddThree) { |
| Model modelAdd3; |
| CreateAddThreeTensorModel(&modelAdd3, matrix3); |
| |
| // Test the three node model. |
| Matrix3x4 actual; |
| memset(&actual, 0, sizeof(actual)); |
| Compilation compilation2(&modelAdd3); |
| compilation2.finish(); |
| Execution execution2(&compilation2); |
| ASSERT_EQ(execution2.setInput(0, matrix1, sizeof(Matrix3x4)), Result::NO_ERROR); |
| ASSERT_EQ(execution2.setInput(1, matrix2, sizeof(Matrix3x4)), Result::NO_ERROR); |
| ASSERT_EQ(execution2.setOutput(0, actual, sizeof(Matrix3x4)), Result::NO_ERROR); |
| ASSERT_EQ(execution2.compute(), Result::NO_ERROR); |
| ASSERT_EQ(CompareMatrices(expected3, actual), 0); |
| |
| // Test it a second time to make sure the model is reusable. |
| memset(&actual, 0, sizeof(actual)); |
| Compilation compilation3(&modelAdd3); |
| compilation3.finish(); |
| Execution execution3(&compilation3); |
| ASSERT_EQ(execution3.setInput(0, matrix1, sizeof(Matrix3x4)), Result::NO_ERROR); |
| ASSERT_EQ(execution3.setInput(1, matrix1, sizeof(Matrix3x4)), Result::NO_ERROR); |
| ASSERT_EQ(execution3.setOutput(0, actual, sizeof(Matrix3x4)), Result::NO_ERROR); |
| ASSERT_EQ(execution3.compute(), Result::NO_ERROR); |
| ASSERT_EQ(CompareMatrices(expected3b, actual), 0); |
| } |
| |
| TEST_F(TrivialTest, FencedAddThree) { |
| Model modelAdd3; |
| CreateAddThreeTensorModel(&modelAdd3, matrix3); |
| Compilation compilation(&modelAdd3); |
| compilation.finish(); |
| |
| Matrix3x4 output1, output2; |
| memset(&output1, 0, sizeof(output1)); |
| memset(&output2, 0, sizeof(output2)); |
| |
| // Start the first execution |
| Execution execution1(&compilation); |
| ASSERT_EQ(execution1.setInput(0, matrix1, sizeof(Matrix3x4)), Result::NO_ERROR); |
| ASSERT_EQ(execution1.setInput(1, matrix2, sizeof(Matrix3x4)), Result::NO_ERROR); |
| ASSERT_EQ(execution1.setOutput(0, output1, sizeof(Matrix3x4)), Result::NO_ERROR); |
| ANeuralNetworksEvent* event1; |
| ANeuralNetworksExecution* execution1_handle = execution1.getHandle(); |
| ASSERT_EQ(ANeuralNetworksExecution_startComputeWithDependencies(execution1_handle, nullptr, 0, |
| 0, &event1), |
| ANEURALNETWORKS_NO_ERROR); |
| |
| // Start the second execution which will wait for the first one. |
| Execution execution2(&compilation); |
| ASSERT_EQ(execution2.setInput(0, matrix1, sizeof(Matrix3x4)), Result::NO_ERROR); |
| ASSERT_EQ(execution2.setInput(1, matrix1, sizeof(Matrix3x4)), Result::NO_ERROR); |
| ASSERT_EQ(execution2.setOutput(0, output2, sizeof(Matrix3x4)), Result::NO_ERROR); |
| ANeuralNetworksEvent* event2; |
| ANeuralNetworksExecution* execution2_handle = execution2.getHandle(); |
| ASSERT_EQ(ANeuralNetworksExecution_startComputeWithDependencies(execution2_handle, &event1, 1, |
| 0, &event2), |
| ANEURALNETWORKS_NO_ERROR); |
| // Wait for the second event. |
| ASSERT_EQ(ANeuralNetworksEvent_wait(event2), ANEURALNETWORKS_NO_ERROR); |
| |
| // Check the results for both executions. |
| ASSERT_EQ(CompareMatrices(expected3, output1), 0); |
| ASSERT_EQ(CompareMatrices(expected3b, output2), 0); |
| |
| // Free the event objects |
| ANeuralNetworksEvent_free(event1); |
| ANeuralNetworksEvent_free(event2); |
| } |
| |
| TEST_F(TrivialTest, BroadcastAddTwo) { |
| Model modelBroadcastAdd2; |
| OperandType scalarType(Type::INT32, {}); |
| auto activation = modelBroadcastAdd2.addConstantOperand(&scalarType, kNoActivation); |
| |
| OperandType matrixType(Type::TENSOR_FLOAT32, {1, 1, 3, 4}); |
| OperandType matrixType2(Type::TENSOR_FLOAT32, {4}); |
| |
| auto a = modelBroadcastAdd2.addOperand(&matrixType); |
| auto b = modelBroadcastAdd2.addOperand(&matrixType2); |
| auto c = modelBroadcastAdd2.addOperand(&matrixType); |
| modelBroadcastAdd2.addOperation(ANEURALNETWORKS_ADD, {a, b, activation}, {c}); |
| modelBroadcastAdd2.identifyInputsAndOutputs({a, b}, {c}); |
| ASSERT_TRUE(modelBroadcastAdd2.isValid()); |
| modelBroadcastAdd2.finish(); |
| |
| // Test the one node model. |
| Matrix3x4 actual; |
| memset(&actual, 0, sizeof(actual)); |
| Compilation compilation(&modelBroadcastAdd2); |
| compilation.finish(); |
| Execution execution(&compilation); |
| ASSERT_EQ(execution.setInput(0, matrix1, sizeof(Matrix3x4)), Result::NO_ERROR); |
| ASSERT_EQ(execution.setInput(1, matrix2b, sizeof(Matrix4)), Result::NO_ERROR); |
| ASSERT_EQ(execution.setOutput(0, actual, sizeof(Matrix3x4)), Result::NO_ERROR); |
| ASSERT_EQ(execution.compute(), Result::NO_ERROR); |
| ASSERT_EQ(CompareMatrices(expected2b, actual), 0); |
| } |
| |
| TEST_F(TrivialTest, BroadcastMulTwo) { |
| Model modelBroadcastMul2; |
| OperandType scalarType(Type::INT32, {}); |
| auto activation = modelBroadcastMul2.addConstantOperand(&scalarType, kNoActivation); |
| |
| OperandType matrixType(Type::TENSOR_FLOAT32, {1, 1, 3, 4}); |
| OperandType matrixType2(Type::TENSOR_FLOAT32, {4}); |
| |
| auto a = modelBroadcastMul2.addOperand(&matrixType); |
| auto b = modelBroadcastMul2.addOperand(&matrixType2); |
| auto c = modelBroadcastMul2.addOperand(&matrixType); |
| modelBroadcastMul2.addOperation(ANEURALNETWORKS_MUL, {a, b, activation}, {c}); |
| modelBroadcastMul2.identifyInputsAndOutputs({a, b}, {c}); |
| ASSERT_TRUE(modelBroadcastMul2.isValid()); |
| modelBroadcastMul2.finish(); |
| |
| // Test the one node model. |
| Matrix3x4 actual; |
| memset(&actual, 0, sizeof(actual)); |
| Compilation compilation(&modelBroadcastMul2); |
| compilation.finish(); |
| Execution execution(&compilation); |
| ASSERT_EQ(execution.setInput(0, matrix1, sizeof(Matrix3x4)), Result::NO_ERROR); |
| ASSERT_EQ(execution.setInput(1, matrix2b, sizeof(Matrix4)), Result::NO_ERROR); |
| ASSERT_EQ(execution.setOutput(0, actual, sizeof(Matrix3x4)), Result::NO_ERROR); |
| ASSERT_EQ(execution.compute(), Result::NO_ERROR); |
| ASSERT_EQ(CompareMatrices(expected2c, actual), 0); |
| } |
| |
| } // end namespace |