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