• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "NeuralNetworksWrapper.h"
18 #include "TestHarness.h"
19 
20 #include <gtest/gtest.h>
21 
22 #include <tuple>
23 #include <vector>
24 
25 using namespace android::nn::wrapper;
26 using namespace test_helper;
27 
28 namespace {
29 
30 const uint32_t INTENDED_SIZE = 3;
31 const uint32_t OTHER_SIZE    = 2;
32 const uint32_t UNKNOWN_SIZE  = 0;
33 typedef uint8_t IntendedMatrix[INTENDED_SIZE][INTENDED_SIZE];
34 
35 // TODO: add a float version of this test for use against drivers that don't
36 // support quantized add. b/72448000
37 
38 // We test three basic scenarios for each tensor dimension:
39 //     INTENDED_AT_COMPILE_AND_EXECUTE: set the dimension at compile
40 //     (addOperand) time to INTENDED_SIZE, use same size at execution
41 //     (setInput/setOutput) time. This should always work.
42 //
43 //     INTENDED_AT_COMPILE_NOT_SET_AT_EXECUTE: set the dimension at compile
44 //     (addOperand) time to INTENDED_SIZE, give no size at execution time.
45 //     This should always work.
46 //
47 //     UNKNOWN_AT_COMPILE_INTENDED_AT_EXECUTE: don't set the dimension at
48 //     compile (addOperand) time, use INTENDED_SIZE at execute
49 //     (setInput/setOutput) time. Note for constants, this just means using an
50 //     unknown dimension at addOperand as there is no type parameter to
51 //     setOperandValue. This should work for inputs and outputs and give an
52 //     error for constants at compile time.
53 //
54 //     UNKNOWN_AT_COMPILE_OTHER_AT_EXECUTE: don't set the dimension at compile
55 //     (addOperand) time, use OTHER_SIZE at execute (setInput/setOutput) time.
56 //     This should give an error at execute time (as the constant value will
57 //     have a different size).
58 enum class DimensionKind { INTENDED_AT_COMPILE_AND_EXECUTE,
59                            INTENDED_AT_COMPILE_NOT_SET_AT_EXECUTE,
60                            UNKNOWN_AT_COMPILE_INTENDED_AT_EXECUTE,
61                            UNKNOWN_AT_COMPILE_OTHER_AT_EXECUTE };
62 typedef std::tuple<DimensionKind, DimensionKind> OperandParams;
63 typedef std::tuple<OperandParams,  // first input
64                    OperandParams,  // second input
65                    OperandParams,  // constant
66                    OperandParams   // output
67                   > TestParams;
68 // All relevant combinations of the basic scenarios are then created with TEST_P
69 auto ioDimensionValues = testing::Values(DimensionKind::INTENDED_AT_COMPILE_AND_EXECUTE,
70                                          DimensionKind::INTENDED_AT_COMPILE_NOT_SET_AT_EXECUTE,
71                                          DimensionKind::UNKNOWN_AT_COMPILE_INTENDED_AT_EXECUTE,
72                                          DimensionKind::UNKNOWN_AT_COMPILE_OTHER_AT_EXECUTE);
73 auto constantDimensionValues = testing::Values(
74         DimensionKind::INTENDED_AT_COMPILE_NOT_SET_AT_EXECUTE,
75         DimensionKind::UNKNOWN_AT_COMPILE_INTENDED_AT_EXECUTE);
76 auto ioValues = testing::Combine(ioDimensionValues, ioDimensionValues);
77 auto constantValues = testing::Combine(constantDimensionValues, constantDimensionValues);
78 auto combinedValues = testing::Combine(ioValues, ioValues, constantValues, ioValues);
79 
80 class UnknownDimensionsTest : public ::testing::TestWithParam<TestParams> {
81 protected:
82     const IntendedMatrix ones = { { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 } };
83     const IntendedMatrix twos = { { 2, 2, 2 }, { 2, 2, 2 }, { 2, 2, 2 } };
84     const IntendedMatrix fives = { { 5, 5, 5 }, { 5, 5, 5 }, { 5, 5, 5 } };
85 };
86 
TEST_P(UnknownDimensionsTest,UnknownDimensions)87 TEST_P(UnknownDimensionsTest, UnknownDimensions) {
88     TestParams params = GetParam();
89     auto paramsForInput0 = std::get<0>(params),
90          paramsForInput1 = std::get<1>(params),
91          paramsForConst  = std::get<2>(params),
92          paramsForOutput = std::get<3>(params);
93 
94     Model model;
95     std::string input0Scope("Input 0:"), input1Scope("Input 1:"),
96                 constantScope("Constant:"), outputScope("Output:");
97 
98     auto getDimForCompile = [](DimensionKind kind, std::string* scope) {
99         switch (kind) {
100             case DimensionKind::INTENDED_AT_COMPILE_AND_EXECUTE:
101                 if (scope) scope->append(" INTENDED_AT_COMPILE_AND_EXECUTE");
102                 return INTENDED_SIZE;
103             case DimensionKind::INTENDED_AT_COMPILE_NOT_SET_AT_EXECUTE:
104                 if (scope) scope->append(" INTENDED_AT_COMPILE_NOT_SET_AT_EXECUTE");
105                 return INTENDED_SIZE;
106             case DimensionKind::UNKNOWN_AT_COMPILE_INTENDED_AT_EXECUTE:
107                 if (scope) scope->append(" UNKNOWN_AT_COMPILE_INTENDED_AT_EXECUTE");
108                 return UNKNOWN_SIZE;
109             case DimensionKind::UNKNOWN_AT_COMPILE_OTHER_AT_EXECUTE:
110                 if (scope) scope->append(" UNKNOWN_AT_COMPILE_OTHER_AT_EXECUTE");
111                 return UNKNOWN_SIZE;
112         }
113     };
114     auto addOperand = [&model, &getDimForCompile](OperandParams params,
115                                                   std::string* scope = nullptr) {
116         OperandType matrixTypeWithPotentiallyUnknownDims(
117                 Type::TENSOR_QUANT8_ASYMM,
118                 { getDimForCompile(std::get<0>(params), scope),
119                   getDimForCompile(std::get<1>(params), scope) },
120                 1.0f);
121         return model.addOperand(&matrixTypeWithPotentiallyUnknownDims);
122     };
123     auto inputOpd0 = addOperand(paramsForInput0, &input0Scope);
124     auto inputOpd1 = addOperand(paramsForInput1, &input1Scope);
125     auto intermediateOpd0 = addOperand(OperandParams{
126             // Dimensions for intermediate operand actually deduced at execution time
127             DimensionKind::UNKNOWN_AT_COMPILE_INTENDED_AT_EXECUTE,
128             DimensionKind::UNKNOWN_AT_COMPILE_INTENDED_AT_EXECUTE});
129     auto constantOpd0 = addOperand(paramsForConst, &constantScope);
130     auto outputOpd0 = addOperand(paramsForOutput, &outputScope);
131 
132     // Make the gtest failure easier to read, TEST_P just outputs a list of
133     // numbers
134     SCOPED_TRACE(input0Scope);
135     SCOPED_TRACE(input1Scope);
136     SCOPED_TRACE(constantScope);
137     SCOPED_TRACE(outputScope);
138 
139     OperandType scalarType(Type::INT32, {});
140     int32_t activation(ANEURALNETWORKS_FUSED_NONE);
141     auto activationOpd0 = model.addOperand(&scalarType);
142 
143     model.setOperandValue(activationOpd0, &activation, sizeof(activation));
144     model.setOperandValue(constantOpd0, twos, sizeof(twos));
145     model.addOperation(ANEURALNETWORKS_ADD,
146                        {inputOpd0, inputOpd1, activationOpd0},
147                        {intermediateOpd0});
148     model.addOperation(ANEURALNETWORKS_ADD,
149                        {intermediateOpd0, constantOpd0, activationOpd0},
150                        {outputOpd0});
151     model.identifyInputsAndOutputs({inputOpd0, inputOpd1}, {outputOpd0});
152     if (std::get<0>(paramsForConst) == DimensionKind::INTENDED_AT_COMPILE_NOT_SET_AT_EXECUTE &&
153         std::get<1>(paramsForConst) == DimensionKind::INTENDED_AT_COMPILE_NOT_SET_AT_EXECUTE) {
154         ASSERT_TRUE(model.isValid());
155         ASSERT_EQ(model.finish(), Result::NO_ERROR);
156     } else {
157         ASSERT_FALSE(model.isValid());
158         // There is no contract (yet) for specific errors in NeuralNetworks.h,
159         // so we just assert on not being successful.
160         ASSERT_NE(model.finish(), Result::NO_ERROR);
161         return;
162     }
163 
164     Compilation compilation(&model);
165     ASSERT_EQ(compilation.finish(), Result::NO_ERROR);
166 
167     IntendedMatrix actual = { { 10, 10, 10 }, { 10, 10, 10 }, { 10, 10, 10 } };
168     Execution execution(&compilation);
169 
170     OperandType matrixTypeIntended(Type::TENSOR_QUANT8_ASYMM, {INTENDED_SIZE, INTENDED_SIZE}, 1.0f);
171     OperandType matrixTypeFirstOther(Type::TENSOR_QUANT8_ASYMM, {OTHER_SIZE, INTENDED_SIZE}, 1.0f);
172     OperandType matrixTypeSecondOther(Type::TENSOR_QUANT8_ASYMM, {INTENDED_SIZE, OTHER_SIZE}, 1.0f);
173     OperandType matrixTypeBothOther(Type::TENSOR_QUANT8_ASYMM, {OTHER_SIZE, OTHER_SIZE}, 1.0f);
174     bool allAreIntendedSizeAtExecution = true;
175 
176     // Helper to return appropriate "type" parameter to setInput/setOutput based
177     // on OperandParams
178     auto typeAtSet = [&](OperandParams params) {
179         auto first = std::get<0>(params), second = std::get<1>(params);
180         if (first == DimensionKind::UNKNOWN_AT_COMPILE_OTHER_AT_EXECUTE &&
181             second == DimensionKind::UNKNOWN_AT_COMPILE_OTHER_AT_EXECUTE) {
182             allAreIntendedSizeAtExecution = false;
183             return &matrixTypeBothOther.operandType;
184         } else if (first == DimensionKind::UNKNOWN_AT_COMPILE_OTHER_AT_EXECUTE) {
185             allAreIntendedSizeAtExecution = false;
186             return &matrixTypeFirstOther.operandType;
187         } else if (second == DimensionKind::UNKNOWN_AT_COMPILE_OTHER_AT_EXECUTE) {
188             allAreIntendedSizeAtExecution = false;
189             return &matrixTypeSecondOther.operandType;
190         } else if (first == DimensionKind::INTENDED_AT_COMPILE_AND_EXECUTE &&
191                    second == DimensionKind::INTENDED_AT_COMPILE_AND_EXECUTE) {
192             return &matrixTypeIntended.operandType;
193         } else if (first == DimensionKind::INTENDED_AT_COMPILE_NOT_SET_AT_EXECUTE &&
194                    second == DimensionKind::INTENDED_AT_COMPILE_NOT_SET_AT_EXECUTE) {
195             return static_cast<ANeuralNetworksOperandType*>(nullptr);
196         } else {
197             return &matrixTypeIntended.operandType;
198         }
199     };
200     // Helper to return appropriate "size" parameter to setInput/setOutput based
201     // on OperandParams
202     auto sizeAtSet = [](OperandParams params) {
203         auto first = std::get<0>(params), second = std::get<1>(params);
204         size_t firstDim = (first == DimensionKind::UNKNOWN_AT_COMPILE_OTHER_AT_EXECUTE) ?
205             OTHER_SIZE : INTENDED_SIZE;
206         size_t secondDim = (second == DimensionKind::UNKNOWN_AT_COMPILE_OTHER_AT_EXECUTE) ?
207             OTHER_SIZE : INTENDED_SIZE;
208         return firstDim * secondDim * sizeof(fives[0][0]);
209     };
210     ASSERT_EQ(execution.setInput(0, ones, sizeAtSet(paramsForInput0), typeAtSet(paramsForInput0)),
211               Result::NO_ERROR);
212     ASSERT_EQ(execution.setInput(1, twos, sizeAtSet(paramsForInput1), typeAtSet(paramsForInput1)),
213               Result::NO_ERROR);
214     ASSERT_EQ(execution.setOutput(0, actual, sizeAtSet(paramsForOutput),
215                                   typeAtSet(paramsForOutput)),
216               Result::NO_ERROR);
217 
218     if (allAreIntendedSizeAtExecution) {
219         ASSERT_EQ(execution.compute(), Result::NO_ERROR);
220     } else {
221         // There is no contract (yet) for specific errors in NeuralNetworks.h,
222         // so we just assert on not being successful.
223         ASSERT_NE(execution.compute(), Result::NO_ERROR);
224         return;
225     }
226 
227     using qvec = std::vector<uint8_t>;
228     constexpr size_t count = sizeof(fives) / sizeof(fives[0][0]);
229     Quant8Operands expected_opds{{0, qvec{&fives[0][0], &fives[0][0] + count}}};
230     Quant8Operands actual_opds{{0, qvec{&actual[0][0], &actual[0][0] + count}}};
231     compare(MixedTyped{ {}, {}, expected_opds }, MixedTyped{ {}, {}, actual_opds });
232 }
233 
234 INSTANTIATE_TEST_CASE_P(UnknownCombinationsTest, UnknownDimensionsTest,
235                         combinedValues);
236 
237 }  // end namespace
238