blob: 82cc73db95f89992dc0ed818423112b81a2994e8 [file] [log] [blame]
Slava Shklyaev73ee79d2019-05-14 14:15:14 +01001/*
2 * Copyright (C) 2019 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 "GeneratedTestHarness.h"
18
19#include <android-base/logging.h>
20#include <android/hardware/neuralnetworks/1.0/IDevice.h>
21#include <android/hardware/neuralnetworks/1.0/IExecutionCallback.h>
22#include <android/hardware/neuralnetworks/1.0/IPreparedModel.h>
23#include <android/hardware/neuralnetworks/1.0/IPreparedModelCallback.h>
24#include <android/hardware/neuralnetworks/1.0/types.h>
25#include <android/hardware/neuralnetworks/1.1/IDevice.h>
26#include <android/hardware/neuralnetworks/1.2/IDevice.h>
27#include <android/hardware/neuralnetworks/1.2/IExecutionCallback.h>
28#include <android/hardware/neuralnetworks/1.2/IPreparedModel.h>
29#include <android/hardware/neuralnetworks/1.2/IPreparedModelCallback.h>
30#include <android/hidl/allocator/1.0/IAllocator.h>
31#include <android/hidl/memory/1.0/IMemory.h>
32#include <hidlmemory/mapping.h>
33
34#include <iostream>
35
36#include "1.0/Utils.h"
37#include "1.2/Callbacks.h"
38#include "ExecutionBurstController.h"
39#include "MemoryUtils.h"
40#include "TestHarness.h"
41#include "Utils.h"
42
43namespace android {
44namespace hardware {
45namespace neuralnetworks {
Slava Shklyaeve8b24462019-07-17 15:50:57 +010046namespace V1_2 {
Slava Shklyaev73ee79d2019-05-14 14:15:14 +010047namespace generated_tests {
48
49using ::android::hardware::neuralnetworks::V1_0::ErrorStatus;
50using ::android::hardware::neuralnetworks::V1_0::Request;
51using ::android::hardware::neuralnetworks::V1_0::RequestArgument;
52using ::android::hardware::neuralnetworks::V1_1::ExecutionPreference;
Michael Butler3835f612019-07-11 15:43:22 -070053using ::android::hardware::neuralnetworks::V1_2::Constant;
Slava Shklyaev73ee79d2019-05-14 14:15:14 +010054using ::android::hardware::neuralnetworks::V1_2::IDevice;
55using ::android::hardware::neuralnetworks::V1_2::IPreparedModel;
Michael Butler3835f612019-07-11 15:43:22 -070056using ::android::hardware::neuralnetworks::V1_2::MeasureTiming;
Slava Shklyaev73ee79d2019-05-14 14:15:14 +010057using ::android::hardware::neuralnetworks::V1_2::Model;
Michael Butler3835f612019-07-11 15:43:22 -070058using ::android::hardware::neuralnetworks::V1_2::OutputShape;
59using ::android::hardware::neuralnetworks::V1_2::Timing;
Slava Shklyaev73ee79d2019-05-14 14:15:14 +010060using ::android::hardware::neuralnetworks::V1_2::implementation::ExecutionCallback;
61using ::android::hardware::neuralnetworks::V1_2::implementation::PreparedModelCallback;
62using ::android::hidl::memory::V1_0::IMemory;
63using ::test_helper::compare;
64using ::test_helper::expectMultinomialDistributionWithinTolerance;
65using ::test_helper::filter;
66using ::test_helper::for_all;
67using ::test_helper::for_each;
68using ::test_helper::MixedTyped;
69using ::test_helper::MixedTypedExample;
70using ::test_helper::resize_accordingly;
71using HidlToken = hidl_array<uint8_t, static_cast<uint32_t>(Constant::BYTE_SIZE_OF_CACHE_TOKEN)>;
72
73static bool isZeroSized(const MixedTyped& example, uint32_t index) {
74 for (auto i : example.operandDimensions.at(index)) {
75 if (i == 0) return true;
76 }
77 return false;
78}
79
80static Return<ErrorStatus> ExecutePreparedModel(sp<IPreparedModel>& preparedModel,
81 const Request& request, MeasureTiming measure,
82 sp<ExecutionCallback>& callback) {
83 return preparedModel->execute_1_2(request, measure, callback);
84}
85static Return<ErrorStatus> ExecutePreparedModel(sp<IPreparedModel>& preparedModel,
86 const Request& request, MeasureTiming measure,
87 hidl_vec<OutputShape>* outputShapes,
88 Timing* timing) {
89 ErrorStatus result;
90 Return<void> ret = preparedModel->executeSynchronously(
91 request, measure,
92 [&result, outputShapes, timing](ErrorStatus error, const hidl_vec<OutputShape>& shapes,
93 const Timing& time) {
94 result = error;
95 *outputShapes = shapes;
96 *timing = time;
97 });
98 if (!ret.isOk()) {
99 return ErrorStatus::GENERAL_FAILURE;
100 }
101 return result;
102}
103static std::shared_ptr<::android::nn::ExecutionBurstController> CreateBurst(
104 const sp<IPreparedModel>& preparedModel) {
105 return ::android::nn::ExecutionBurstController::create(preparedModel, /*blocking=*/true);
106}
107enum class Executor { ASYNC, SYNC, BURST };
108enum class OutputType { FULLY_SPECIFIED, UNSPECIFIED, INSUFFICIENT };
109const float kDefaultAtol = 1e-5f;
110const float kDefaultRtol = 1e-5f;
111void EvaluatePreparedModel(sp<IPreparedModel>& preparedModel, std::function<bool(int)> is_ignored,
112 const std::vector<MixedTypedExample>& examples,
113 bool hasRelaxedFloat32Model, float fpAtol, float fpRtol,
114 Executor executor, MeasureTiming measure, OutputType outputType) {
115 const uint32_t INPUT = 0;
116 const uint32_t OUTPUT = 1;
117
118 int example_no = 1;
119 for (auto& example : examples) {
120 SCOPED_TRACE(example_no++);
121 const MixedTyped& inputs = example.operands.first;
122 const MixedTyped& golden = example.operands.second;
123
124 const bool hasFloat16Inputs = !inputs.float16Operands.empty();
125 if (hasRelaxedFloat32Model || hasFloat16Inputs) {
126 // TODO: Adjust the error limit based on testing.
127 // If in relaxed mode, set the absolute tolerance to be 5ULP of FP16.
128 fpAtol = 5.0f * 0.0009765625f;
129 // Set the relative tolerance to be 5ULP of the corresponding FP precision.
130 fpRtol = 5.0f * 0.0009765625f;
131 }
132
133 std::vector<RequestArgument> inputs_info, outputs_info;
134 uint32_t inputSize = 0, outputSize = 0;
135 // This function only partially specifies the metadata (vector of RequestArguments).
136 // The contents are copied over below.
137 for_all(inputs, [&inputs_info, &inputSize](int index, auto, auto s) {
138 if (inputs_info.size() <= static_cast<size_t>(index)) inputs_info.resize(index + 1);
139 RequestArgument arg = {
140 .location = {.poolIndex = INPUT,
141 .offset = 0,
142 .length = static_cast<uint32_t>(s)},
143 .dimensions = {},
144 };
145 RequestArgument arg_empty = {
146 .hasNoValue = true,
147 };
148 inputs_info[index] = s ? arg : arg_empty;
149 inputSize += s;
150 });
151 // Compute offset for inputs 1 and so on
152 {
153 size_t offset = 0;
154 for (auto& i : inputs_info) {
155 if (!i.hasNoValue) i.location.offset = offset;
156 offset += i.location.length;
157 }
158 }
159
160 MixedTyped test; // holding test results
161
162 // Go through all outputs, initialize RequestArgument descriptors
163 resize_accordingly(golden, test);
164 bool sizeLargerThanOne = true;
165 for_all(golden, [&golden, &outputs_info, &outputSize, &outputType, &sizeLargerThanOne](
166 int index, auto, auto s) {
167 if (outputs_info.size() <= static_cast<size_t>(index)) outputs_info.resize(index + 1);
168 if (index == 0) {
169 // On OutputType::INSUFFICIENT, set the output operand with index 0 with
170 // buffer size one byte less than needed.
171 if (outputType == OutputType::INSUFFICIENT) {
172 if (s > 1 && !isZeroSized(golden, index)) {
173 s -= 1;
174 } else {
175 sizeLargerThanOne = false;
176 }
177 }
178 }
179 RequestArgument arg = {
180 .location = {.poolIndex = OUTPUT,
181 .offset = 0,
182 .length = static_cast<uint32_t>(s)},
183 .dimensions = {},
184 };
185 outputs_info[index] = arg;
186 outputSize += s;
187 });
188 // If output0 does not have size larger than one byte,
189 // we can not provide an insufficient buffer
190 if (!sizeLargerThanOne && outputType == OutputType::INSUFFICIENT) return;
191 // Compute offset for outputs 1 and so on
192 {
193 size_t offset = 0;
194 for (auto& i : outputs_info) {
195 i.location.offset = offset;
196 offset += i.location.length;
197 }
198 }
199 std::vector<hidl_memory> pools = {nn::allocateSharedMemory(inputSize),
200 nn::allocateSharedMemory(outputSize)};
201 ASSERT_NE(0ull, pools[INPUT].size());
202 ASSERT_NE(0ull, pools[OUTPUT].size());
203
204 // load data
205 sp<IMemory> inputMemory = mapMemory(pools[INPUT]);
206 sp<IMemory> outputMemory = mapMemory(pools[OUTPUT]);
207 ASSERT_NE(nullptr, inputMemory.get());
208 ASSERT_NE(nullptr, outputMemory.get());
209 char* inputPtr = reinterpret_cast<char*>(static_cast<void*>(inputMemory->getPointer()));
210 char* outputPtr = reinterpret_cast<char*>(static_cast<void*>(outputMemory->getPointer()));
211 ASSERT_NE(nullptr, inputPtr);
212 ASSERT_NE(nullptr, outputPtr);
213 inputMemory->update();
214 outputMemory->update();
215
216 // Go through all inputs, copy the values
217 for_all(inputs, [&inputs_info, inputPtr](int index, auto p, auto s) {
218 char* begin = (char*)p;
219 char* end = begin + s;
220 // TODO: handle more than one input
221 std::copy(begin, end, inputPtr + inputs_info[index].location.offset);
222 });
223
224 inputMemory->commit();
225 outputMemory->commit();
226
227 const Request request = {.inputs = inputs_info, .outputs = outputs_info, .pools = pools};
228
229 ErrorStatus executionStatus;
230 hidl_vec<OutputShape> outputShapes;
231 Timing timing;
232 switch (executor) {
233 case Executor::ASYNC: {
234 SCOPED_TRACE("asynchronous");
235
236 // launch execution
237 sp<ExecutionCallback> executionCallback = new ExecutionCallback();
238 ASSERT_NE(nullptr, executionCallback.get());
239 Return<ErrorStatus> executionLaunchStatus =
240 ExecutePreparedModel(preparedModel, request, measure, executionCallback);
241 ASSERT_TRUE(executionLaunchStatus.isOk());
242 EXPECT_EQ(ErrorStatus::NONE, static_cast<ErrorStatus>(executionLaunchStatus));
243
244 // retrieve execution status
245 executionCallback->wait();
246 executionStatus = executionCallback->getStatus();
247 outputShapes = executionCallback->getOutputShapes();
248 timing = executionCallback->getTiming();
249
250 break;
251 }
252 case Executor::SYNC: {
253 SCOPED_TRACE("synchronous");
254
255 // execute
256 Return<ErrorStatus> executionReturnStatus = ExecutePreparedModel(
257 preparedModel, request, measure, &outputShapes, &timing);
258 ASSERT_TRUE(executionReturnStatus.isOk());
259 executionStatus = static_cast<ErrorStatus>(executionReturnStatus);
260
261 break;
262 }
263 case Executor::BURST: {
264 SCOPED_TRACE("burst");
265
266 // create burst
267 const std::shared_ptr<::android::nn::ExecutionBurstController> controller =
268 CreateBurst(preparedModel);
269 ASSERT_NE(nullptr, controller.get());
270
271 // create memory keys
272 std::vector<intptr_t> keys(request.pools.size());
273 for (size_t i = 0; i < keys.size(); ++i) {
274 keys[i] = reinterpret_cast<intptr_t>(&request.pools[i]);
275 }
276
277 // execute burst
278 std::tie(executionStatus, outputShapes, timing) =
279 controller->compute(request, measure, keys);
280
281 break;
282 }
283 }
284
285 if (outputType != OutputType::FULLY_SPECIFIED &&
286 executionStatus == ErrorStatus::GENERAL_FAILURE) {
287 LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
288 "execute model that it does not support.";
289 std::cout << "[ ] Early termination of test because vendor service cannot "
290 "execute model that it does not support."
291 << std::endl;
292 GTEST_SKIP();
293 }
294 if (measure == MeasureTiming::NO) {
295 EXPECT_EQ(UINT64_MAX, timing.timeOnDevice);
296 EXPECT_EQ(UINT64_MAX, timing.timeInDriver);
297 } else {
298 if (timing.timeOnDevice != UINT64_MAX && timing.timeInDriver != UINT64_MAX) {
299 EXPECT_LE(timing.timeOnDevice, timing.timeInDriver);
300 }
301 }
302
303 switch (outputType) {
304 case OutputType::FULLY_SPECIFIED:
305 // If the model output operands are fully specified, outputShapes must be either
306 // either empty, or have the same number of elements as the number of outputs.
307 ASSERT_EQ(ErrorStatus::NONE, executionStatus);
308 ASSERT_TRUE(outputShapes.size() == 0 ||
309 outputShapes.size() == test.operandDimensions.size());
310 break;
311 case OutputType::UNSPECIFIED:
312 // If the model output operands are not fully specified, outputShapes must have
313 // the same number of elements as the number of outputs.
314 ASSERT_EQ(ErrorStatus::NONE, executionStatus);
315 ASSERT_EQ(outputShapes.size(), test.operandDimensions.size());
316 break;
317 case OutputType::INSUFFICIENT:
318 ASSERT_EQ(ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, executionStatus);
319 ASSERT_EQ(outputShapes.size(), test.operandDimensions.size());
320 ASSERT_FALSE(outputShapes[0].isSufficient);
321 return;
322 }
323 // Go through all outputs, overwrite output dimensions with returned output shapes
324 if (outputShapes.size() > 0) {
325 for_each<uint32_t>(test.operandDimensions,
326 [&outputShapes](int idx, std::vector<uint32_t>& dim) {
327 dim = outputShapes[idx].dimensions;
328 });
329 }
330
331 // validate results
332 outputMemory->read();
333 copy_back(&test, outputs_info, outputPtr);
334 outputMemory->commit();
335 // Filter out don't cares
336 MixedTyped filtered_golden = filter(golden, is_ignored);
337 MixedTyped filtered_test = filter(test, is_ignored);
338
339 // We want "close-enough" results for float
340 compare(filtered_golden, filtered_test, fpAtol, fpRtol);
341
342 if (example.expectedMultinomialDistributionTolerance > 0) {
343 expectMultinomialDistributionWithinTolerance(test, example);
344 }
345 }
346}
347void EvaluatePreparedModel(sp<IPreparedModel>& preparedModel, std::function<bool(int)> is_ignored,
348 const std::vector<MixedTypedExample>& examples,
349 bool hasRelaxedFloat32Model, Executor executor, MeasureTiming measure,
350 OutputType outputType) {
351 EvaluatePreparedModel(preparedModel, is_ignored, examples, hasRelaxedFloat32Model, kDefaultAtol,
352 kDefaultRtol, executor, measure, outputType);
353}
354
355void EvaluatePreparedModel(sp<IPreparedModel>& preparedModel, std::function<bool(int)> is_ignored,
356 const std::vector<MixedTypedExample>& examples,
357 bool hasRelaxedFloat32Model, bool testDynamicOutputShape) {
358 if (testDynamicOutputShape) {
359 EvaluatePreparedModel(preparedModel, is_ignored, examples, hasRelaxedFloat32Model,
360 Executor::ASYNC, MeasureTiming::NO, OutputType::UNSPECIFIED);
361 EvaluatePreparedModel(preparedModel, is_ignored, examples, hasRelaxedFloat32Model,
362 Executor::SYNC, MeasureTiming::NO, OutputType::UNSPECIFIED);
363 EvaluatePreparedModel(preparedModel, is_ignored, examples, hasRelaxedFloat32Model,
364 Executor::BURST, MeasureTiming::NO, OutputType::UNSPECIFIED);
365 EvaluatePreparedModel(preparedModel, is_ignored, examples, hasRelaxedFloat32Model,
366 Executor::ASYNC, MeasureTiming::YES, OutputType::UNSPECIFIED);
367 EvaluatePreparedModel(preparedModel, is_ignored, examples, hasRelaxedFloat32Model,
368 Executor::SYNC, MeasureTiming::YES, OutputType::UNSPECIFIED);
369 EvaluatePreparedModel(preparedModel, is_ignored, examples, hasRelaxedFloat32Model,
370 Executor::BURST, MeasureTiming::YES, OutputType::UNSPECIFIED);
371 EvaluatePreparedModel(preparedModel, is_ignored, examples, hasRelaxedFloat32Model,
372 Executor::ASYNC, MeasureTiming::NO, OutputType::INSUFFICIENT);
373 EvaluatePreparedModel(preparedModel, is_ignored, examples, hasRelaxedFloat32Model,
374 Executor::SYNC, MeasureTiming::NO, OutputType::INSUFFICIENT);
375 EvaluatePreparedModel(preparedModel, is_ignored, examples, hasRelaxedFloat32Model,
376 Executor::BURST, MeasureTiming::NO, OutputType::INSUFFICIENT);
377 EvaluatePreparedModel(preparedModel, is_ignored, examples, hasRelaxedFloat32Model,
378 Executor::ASYNC, MeasureTiming::YES, OutputType::INSUFFICIENT);
379 EvaluatePreparedModel(preparedModel, is_ignored, examples, hasRelaxedFloat32Model,
380 Executor::SYNC, MeasureTiming::YES, OutputType::INSUFFICIENT);
381 EvaluatePreparedModel(preparedModel, is_ignored, examples, hasRelaxedFloat32Model,
382 Executor::BURST, MeasureTiming::YES, OutputType::INSUFFICIENT);
383 } else {
384 EvaluatePreparedModel(preparedModel, is_ignored, examples, hasRelaxedFloat32Model,
385 Executor::ASYNC, MeasureTiming::NO, OutputType::FULLY_SPECIFIED);
386 EvaluatePreparedModel(preparedModel, is_ignored, examples, hasRelaxedFloat32Model,
387 Executor::SYNC, MeasureTiming::NO, OutputType::FULLY_SPECIFIED);
388 EvaluatePreparedModel(preparedModel, is_ignored, examples, hasRelaxedFloat32Model,
389 Executor::BURST, MeasureTiming::NO, OutputType::FULLY_SPECIFIED);
390 EvaluatePreparedModel(preparedModel, is_ignored, examples, hasRelaxedFloat32Model,
391 Executor::ASYNC, MeasureTiming::YES, OutputType::FULLY_SPECIFIED);
392 EvaluatePreparedModel(preparedModel, is_ignored, examples, hasRelaxedFloat32Model,
393 Executor::SYNC, MeasureTiming::YES, OutputType::FULLY_SPECIFIED);
394 EvaluatePreparedModel(preparedModel, is_ignored, examples, hasRelaxedFloat32Model,
395 Executor::BURST, MeasureTiming::YES, OutputType::FULLY_SPECIFIED);
396 }
397}
398
399void PrepareModel(const sp<IDevice>& device, const Model& model,
400 sp<IPreparedModel>* preparedModel) {
401 // see if service can handle model
402 bool fullySupportsModel = false;
403 Return<void> supportedCall = device->getSupportedOperations_1_2(
404 model, [&fullySupportsModel](ErrorStatus status, const hidl_vec<bool>& supported) {
405 ASSERT_EQ(ErrorStatus::NONE, status);
406 ASSERT_NE(0ul, supported.size());
407 fullySupportsModel = std::all_of(supported.begin(), supported.end(),
408 [](bool valid) { return valid; });
409 });
410 ASSERT_TRUE(supportedCall.isOk());
411
412 // launch prepare model
413 sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback();
414 ASSERT_NE(nullptr, preparedModelCallback.get());
415 Return<ErrorStatus> prepareLaunchStatus = device->prepareModel_1_2(
416 model, ExecutionPreference::FAST_SINGLE_ANSWER, hidl_vec<hidl_handle>(),
417 hidl_vec<hidl_handle>(), HidlToken(), preparedModelCallback);
418 ASSERT_TRUE(prepareLaunchStatus.isOk());
419 ASSERT_EQ(ErrorStatus::NONE, static_cast<ErrorStatus>(prepareLaunchStatus));
420
421 // retrieve prepared model
422 preparedModelCallback->wait();
423 ErrorStatus prepareReturnStatus = preparedModelCallback->getStatus();
424 sp<V1_0::IPreparedModel> preparedModelV1_0 = preparedModelCallback->getPreparedModel();
425 *preparedModel = IPreparedModel::castFrom(preparedModelV1_0).withDefault(nullptr);
426
427 // early termination if vendor service cannot fully prepare model
428 if (!fullySupportsModel && prepareReturnStatus != ErrorStatus::NONE) {
429 ASSERT_EQ(nullptr, preparedModel->get());
430 LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
431 "prepare model that it does not support.";
432 std::cout << "[ ] Early termination of test because vendor service cannot "
433 "prepare model that it does not support."
434 << std::endl;
435 return;
436 }
437 EXPECT_EQ(ErrorStatus::NONE, prepareReturnStatus);
438 ASSERT_NE(nullptr, preparedModel->get());
439}
440
441void Execute(const sp<IDevice>& device, std::function<Model(void)> create_model,
442 std::function<bool(int)> is_ignored, const std::vector<MixedTypedExample>& examples,
443 bool testDynamicOutputShape) {
444 Model model = create_model();
445 sp<IPreparedModel> preparedModel = nullptr;
446 PrepareModel(device, model, &preparedModel);
447 if (preparedModel == nullptr) {
448 GTEST_SKIP();
449 }
450 EvaluatePreparedModel(preparedModel, is_ignored, examples,
451 model.relaxComputationFloat32toFloat16, testDynamicOutputShape);
452}
453
454} // namespace generated_tests
Slava Shklyaeve8b24462019-07-17 15:50:57 +0100455} // namespace V1_2
Slava Shklyaev73ee79d2019-05-14 14:15:14 +0100456} // namespace neuralnetworks
457} // namespace hardware
458} // namespace android