blob: 4eb704b6e03978726f559ead5224a183513be639 [file] [log] [blame]
Lev Proleevc185e882020-12-15 19:25:32 +00001/*
2 * Copyright (C) 2021 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 <aidl/android/hardware/neuralnetworks/ErrorStatus.h>
20#include <android-base/logging.h>
21#include <android/binder_auto_utils.h>
22#include <android/sync.h>
23#include <gtest/gtest.h>
24
25#include <algorithm>
26#include <chrono>
27#include <iostream>
28#include <iterator>
29#include <numeric>
30#include <vector>
31
32#include <MemoryUtils.h>
33#include <android/binder_status.h>
34#include <nnapi/Result.h>
35#include <nnapi/SharedMemory.h>
36#include <nnapi/Types.h>
37#include <nnapi/hal/aidl/Conversions.h>
38#include <nnapi/hal/aidl/Utils.h>
39
40#include "Callbacks.h"
41#include "TestHarness.h"
42#include "Utils.h"
43#include "VtsHalNeuralnetworks.h"
44
45namespace aidl::android::hardware::neuralnetworks::vts::functional {
46
47namespace nn = ::android::nn;
48using namespace test_helper;
49using implementation::PreparedModelCallback;
50
51namespace {
52
53enum class OutputType { FULLY_SPECIFIED, UNSPECIFIED, INSUFFICIENT, MISSED_DEADLINE };
54
55struct TestConfig {
56 Executor executor;
57 bool measureTiming;
58 OutputType outputType;
59 MemoryType memoryType;
60 // `reportSkipping` indicates if a test should print an info message in case
61 // it is skipped. The field is set to true by default and is set to false in
62 // quantization coupling tests to suppress skipping a test
63 bool reportSkipping;
64 TestConfig(Executor executor, bool measureTiming, OutputType outputType, MemoryType memoryType)
65 : executor(executor),
66 measureTiming(measureTiming),
67 outputType(outputType),
68 memoryType(memoryType),
69 reportSkipping(true) {}
70 TestConfig(Executor executor, bool measureTiming, OutputType outputType, MemoryType memoryType,
71 bool reportSkipping)
72 : executor(executor),
73 measureTiming(measureTiming),
74 outputType(outputType),
75 memoryType(memoryType),
76 reportSkipping(reportSkipping) {}
77};
78
79enum class IOType { INPUT, OUTPUT };
80
81class DeviceMemoryAllocator {
82 public:
83 DeviceMemoryAllocator(const std::shared_ptr<IDevice>& device,
84 const std::shared_ptr<IPreparedModel>& preparedModel,
85 const TestModel& testModel)
86 : kDevice(device), kPreparedModel(preparedModel), kTestModel(testModel) {}
87
88 // Allocate device memory for a target input/output operand.
89 // Return {IBuffer object, token} if successful.
90 // Return {nullptr, 0} if device memory is not supported.
91 template <IOType ioType>
92 std::pair<std::shared_ptr<IBuffer>, int32_t> allocate(uint32_t index) {
93 std::pair<std::shared_ptr<IBuffer>, int32_t> buffer;
94 allocateInternal<ioType>(index, &buffer);
95 return buffer;
96 }
97
98 private:
99 template <IOType ioType>
100 void allocateInternal(int32_t index, std::pair<std::shared_ptr<IBuffer>, int32_t>* result) {
101 ASSERT_NE(result, nullptr);
102
103 // Prepare arguments.
104 BufferRole role = {.modelIndex = 0, .ioIndex = index, .frequency = 1.0f};
105 std::vector<BufferRole> inputRoles, outputRoles;
106 if constexpr (ioType == IOType::INPUT) {
107 inputRoles = {role};
108 } else {
109 outputRoles = {role};
110 }
111
112 // Allocate device memory.
113 DeviceBuffer buffer;
114 IPreparedModelParcel parcel;
115 parcel.preparedModel = kPreparedModel;
116 const auto ret = kDevice->allocate({}, {parcel}, inputRoles, outputRoles, &buffer);
117
118 // Check allocation results.
119 if (ret.isOk()) {
120 ASSERT_NE(buffer.buffer, nullptr);
121 ASSERT_GT(buffer.token, 0);
122 } else {
123 ASSERT_EQ(ret.getExceptionCode(), EX_SERVICE_SPECIFIC);
124 ASSERT_EQ(static_cast<ErrorStatus>(ret.getServiceSpecificError()),
125 ErrorStatus::GENERAL_FAILURE);
126 buffer.buffer = nullptr;
127 buffer.token = 0;
128 }
129
130 // Initialize input data from TestBuffer.
131 if constexpr (ioType == IOType::INPUT) {
132 if (buffer.buffer != nullptr) {
133 // TestBuffer -> Shared memory.
134 const auto& testBuffer =
135 kTestModel.main.operands[kTestModel.main.inputIndexes[index]].data;
136 ASSERT_GT(testBuffer.size(), 0);
137 const auto sharedMemory = nn::createSharedMemory(testBuffer.size()).value();
138 const auto memory = utils::convert(sharedMemory).value();
139 const auto mapping = nn::map(sharedMemory).value();
140 uint8_t* inputPtr = static_cast<uint8_t*>(std::get<void*>(mapping.pointer));
141 ASSERT_NE(inputPtr, nullptr);
142 const uint8_t* begin = testBuffer.get<uint8_t>();
143 const uint8_t* end = begin + testBuffer.size();
144 std::copy(begin, end, inputPtr);
145
146 // Shared memory -> IBuffer.
147 auto ret = buffer.buffer->copyFrom(memory, {});
148 ASSERT_TRUE(ret.isOk());
149 }
150 }
151 *result = {std::move(buffer.buffer), buffer.token};
152 }
153
154 const std::shared_ptr<IDevice> kDevice;
155 const std::shared_ptr<IPreparedModel> kPreparedModel;
156 const TestModel& kTestModel;
157};
158
159Subgraph createSubgraph(const TestSubgraph& testSubgraph, uint32_t* constCopySize,
160 std::vector<const TestBuffer*>* constCopies, uint32_t* constRefSize,
161 std::vector<const TestBuffer*>* constReferences) {
162 CHECK(constCopySize != nullptr);
163 CHECK(constCopies != nullptr);
164 CHECK(constRefSize != nullptr);
165 CHECK(constReferences != nullptr);
166
167 // Operands.
168 std::vector<Operand> operands(testSubgraph.operands.size());
169 for (uint32_t i = 0; i < testSubgraph.operands.size(); i++) {
170 const auto& op = testSubgraph.operands[i];
171
172 DataLocation loc = {};
173 if (op.lifetime == TestOperandLifeTime::CONSTANT_COPY) {
174 loc = {
175 .poolIndex = 0,
176 .offset = *constCopySize,
177 .length = static_cast<int64_t>(op.data.size()),
178 };
179 constCopies->push_back(&op.data);
180 *constCopySize += op.data.alignedSize();
181 } else if (op.lifetime == TestOperandLifeTime::CONSTANT_REFERENCE) {
182 loc = {
183 .poolIndex = 0,
184 .offset = *constRefSize,
185 .length = static_cast<int64_t>(op.data.size()),
186 };
187 constReferences->push_back(&op.data);
188 *constRefSize += op.data.alignedSize();
189 } else if (op.lifetime == TestOperandLifeTime::SUBGRAPH) {
190 loc = {
191 .poolIndex = 0,
192 .offset = *op.data.get<uint32_t>(),
193 .length = 0,
194 };
195 }
196
197 std::optional<OperandExtraParams> extraParams;
198 if (op.type == TestOperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL) {
199 using Tag = OperandExtraParams::Tag;
200 extraParams = OperandExtraParams::make<Tag::channelQuant>(SymmPerChannelQuantParams{
201 .scales = op.channelQuant.scales,
202 .channelDim = static_cast<int32_t>(op.channelQuant.channelDim)});
203 }
204
205 operands[i] = {.type = static_cast<OperandType>(op.type),
206 .dimensions = utils::toSigned(op.dimensions).value(),
207 .scale = op.scale,
208 .zeroPoint = op.zeroPoint,
209 .lifetime = static_cast<OperandLifeTime>(op.lifetime),
210 .location = loc,
211 .extraParams = std::move(extraParams)};
212 }
213
214 // Operations.
215 std::vector<Operation> operations(testSubgraph.operations.size());
216 std::transform(testSubgraph.operations.begin(), testSubgraph.operations.end(),
217 operations.begin(), [](const TestOperation& op) -> Operation {
218 return {.type = static_cast<OperationType>(op.type),
219 .inputs = utils::toSigned(op.inputs).value(),
220 .outputs = utils::toSigned(op.outputs).value()};
221 });
222
223 return {.operands = std::move(operands),
224 .operations = std::move(operations),
225 .inputIndexes = utils::toSigned(testSubgraph.inputIndexes).value(),
226 .outputIndexes = utils::toSigned(testSubgraph.outputIndexes).value()};
227}
228
229void copyTestBuffers(const std::vector<const TestBuffer*>& buffers, uint8_t* output) {
230 uint32_t offset = 0;
231 for (const TestBuffer* buffer : buffers) {
232 const uint8_t* begin = buffer->get<uint8_t>();
233 const uint8_t* end = begin + buffer->size();
234 std::copy(begin, end, output + offset);
235 offset += buffer->alignedSize();
236 }
237}
238
239} // namespace
240
241void waitForSyncFence(int syncFd) {
242 constexpr int kInfiniteTimeout = -1;
243 ASSERT_GT(syncFd, 0);
244 int r = sync_wait(syncFd, kInfiniteTimeout);
245 ASSERT_GE(r, 0);
246}
247
248Model createModel(const TestModel& testModel) {
249 uint32_t constCopySize = 0;
250 uint32_t constRefSize = 0;
251 std::vector<const TestBuffer*> constCopies;
252 std::vector<const TestBuffer*> constReferences;
253
254 Subgraph mainSubgraph = createSubgraph(testModel.main, &constCopySize, &constCopies,
255 &constRefSize, &constReferences);
256 std::vector<Subgraph> refSubgraphs(testModel.referenced.size());
257 std::transform(testModel.referenced.begin(), testModel.referenced.end(), refSubgraphs.begin(),
258 [&constCopySize, &constCopies, &constRefSize,
259 &constReferences](const TestSubgraph& testSubgraph) {
260 return createSubgraph(testSubgraph, &constCopySize, &constCopies,
261 &constRefSize, &constReferences);
262 });
263
264 // Constant copies.
265 std::vector<uint8_t> operandValues(constCopySize);
266 copyTestBuffers(constCopies, operandValues.data());
267
268 // Shared memory.
Michael Butlerfadeb8a2021-02-07 00:11:13 -0800269 std::vector<nn::SharedMemory> pools = {};
Lev Proleevc185e882020-12-15 19:25:32 +0000270 if (constRefSize > 0) {
271 const auto pool = nn::createSharedMemory(constRefSize).value();
272 pools.push_back(pool);
273
274 // load data
275 const auto mappedMemory = nn::map(pool).value();
276 uint8_t* mappedPtr = static_cast<uint8_t*>(std::get<void*>(mappedMemory.pointer));
277 CHECK(mappedPtr != nullptr);
278
279 copyTestBuffers(constReferences, mappedPtr);
280 }
281
282 std::vector<Memory> aidlPools;
283 aidlPools.reserve(pools.size());
284 for (auto& pool : pools) {
285 auto aidlPool = utils::convert(pool).value();
286 aidlPools.push_back(std::move(aidlPool));
287 }
288
289 return {.main = std::move(mainSubgraph),
290 .referenced = std::move(refSubgraphs),
291 .operandValues = std::move(operandValues),
292 .pools = std::move(aidlPools),
293 .relaxComputationFloat32toFloat16 = testModel.isRelaxed};
294}
295
296static bool isOutputSizeGreaterThanOne(const TestModel& testModel, uint32_t index) {
297 const auto byteSize = testModel.main.operands[testModel.main.outputIndexes[index]].data.size();
298 return byteSize > 1u;
299}
300
301static void makeOutputInsufficientSize(uint32_t outputIndex, Request* request) {
302 auto& length = request->outputs[outputIndex].location.length;
303 ASSERT_GT(length, 1u);
304 length -= 1u;
305}
306
307static void makeOutputDimensionsUnspecified(Model* model) {
308 for (auto i : model->main.outputIndexes) {
309 auto& dims = model->main.operands[i].dimensions;
310 std::fill(dims.begin(), dims.end(), 0);
311 }
312}
313
314// Manages the lifetime of memory resources used in an execution.
315class ExecutionContext {
316 public:
317 ExecutionContext(std::shared_ptr<IDevice> device, std::shared_ptr<IPreparedModel> preparedModel)
318 : kDevice(std::move(device)), kPreparedModel(std::move(preparedModel)) {}
319
320 std::optional<Request> createRequest(const TestModel& testModel, MemoryType memoryType);
321 std::vector<TestBuffer> getOutputBuffers(const TestModel& testModel,
322 const Request& request) const;
323
324 private:
325 // Get a TestBuffer with data copied from an IBuffer object.
326 void getBuffer(const std::shared_ptr<IBuffer>& buffer, size_t size,
327 TestBuffer* testBuffer) const;
328
329 static constexpr uint32_t kInputPoolIndex = 0;
330 static constexpr uint32_t kOutputPoolIndex = 1;
331 static constexpr uint32_t kDeviceMemoryBeginIndex = 2;
332
333 const std::shared_ptr<IDevice> kDevice;
334 const std::shared_ptr<IPreparedModel> kPreparedModel;
335 std::unique_ptr<TestMemoryBase> mInputMemory, mOutputMemory;
336 std::vector<std::shared_ptr<IBuffer>> mBuffers;
337};
338
339std::optional<Request> ExecutionContext::createRequest(const TestModel& testModel,
340 MemoryType memoryType) {
341 // Memory pools are organized as:
342 // - 0: Input shared memory pool
343 // - 1: Output shared memory pool
344 // - [2, 2+i): Input device memories
345 // - [2+i, 2+i+o): Output device memories
346 DeviceMemoryAllocator allocator(kDevice, kPreparedModel, testModel);
347 std::vector<int32_t> tokens;
348 mBuffers.clear();
349
350 // Model inputs.
351 std::vector<RequestArgument> inputs(testModel.main.inputIndexes.size());
352 size_t inputSize = 0;
353 for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
354 const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]];
355 if (op.data.size() == 0) {
356 // Omitted input.
357 inputs[i] = {.hasNoValue = true};
358 continue;
359 } else if (memoryType == MemoryType::DEVICE) {
360 SCOPED_TRACE("Input index = " + std::to_string(i));
361 auto [buffer, token] = allocator.allocate<IOType::INPUT>(i);
362 if (buffer != nullptr) {
363 DataLocation loc = {.poolIndex = static_cast<int32_t>(mBuffers.size() +
364 kDeviceMemoryBeginIndex)};
365 mBuffers.push_back(std::move(buffer));
366 tokens.push_back(token);
367 inputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
368 continue;
369 }
370 }
371
372 // Reserve shared memory for input.
373 DataLocation loc = {.poolIndex = kInputPoolIndex,
374 .offset = static_cast<int64_t>(inputSize),
375 .length = static_cast<int64_t>(op.data.size())};
376 inputSize += op.data.alignedSize();
377 inputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
378 }
379
380 // Model outputs.
381 std::vector<RequestArgument> outputs(testModel.main.outputIndexes.size());
382 size_t outputSize = 0;
383 for (uint32_t i = 0; i < testModel.main.outputIndexes.size(); i++) {
384 const auto& op = testModel.main.operands[testModel.main.outputIndexes[i]];
385 if (memoryType == MemoryType::DEVICE) {
386 SCOPED_TRACE("Output index = " + std::to_string(i));
387 auto [buffer, token] = allocator.allocate<IOType::OUTPUT>(i);
388 if (buffer != nullptr) {
389 DataLocation loc = {.poolIndex = static_cast<int32_t>(mBuffers.size() +
390 kDeviceMemoryBeginIndex)};
391 mBuffers.push_back(std::move(buffer));
392 tokens.push_back(token);
393 outputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
394 continue;
395 }
396 }
397
398 // In the case of zero-sized output, we should at least provide a one-byte buffer.
399 // This is because zero-sized tensors are only supported internally to the driver, or
400 // reported in output shapes. It is illegal for the client to pre-specify a zero-sized
401 // tensor as model output. Otherwise, we will have two semantic conflicts:
402 // - "Zero dimension" conflicts with "unspecified dimension".
403 // - "Omitted operand buffer" conflicts with "zero-sized operand buffer".
404 size_t bufferSize = std::max<size_t>(op.data.size(), 1);
405
406 // Reserve shared memory for output.
407 DataLocation loc = {.poolIndex = kOutputPoolIndex,
408 .offset = static_cast<int64_t>(outputSize),
409 .length = static_cast<int64_t>(bufferSize)};
410 outputSize += op.data.size() == 0 ? TestBuffer::kAlignment : op.data.alignedSize();
411 outputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
412 }
413
414 if (memoryType == MemoryType::DEVICE && mBuffers.empty()) {
415 return std::nullopt;
416 }
417
418 // Memory pools.
419 if (memoryType == MemoryType::BLOB_AHWB) {
420 mInputMemory = TestBlobAHWB::create(std::max<size_t>(inputSize, 1));
421 mOutputMemory = TestBlobAHWB::create(std::max<size_t>(outputSize, 1));
422 } else {
423 mInputMemory = TestAshmem::create(std::max<size_t>(inputSize, 1));
424 mOutputMemory = TestAshmem::create(std::max<size_t>(outputSize, 1));
425 }
426 CHECK_NE(mInputMemory, nullptr);
427 CHECK_NE(mOutputMemory, nullptr);
428 std::vector<RequestMemoryPool> pools;
429 pools.reserve(kDeviceMemoryBeginIndex + mBuffers.size());
430
431 auto copiedInputMemory = utils::clone(*mInputMemory->getAidlMemory());
432 CHECK(copiedInputMemory.has_value()) << copiedInputMemory.error().message;
433 auto copiedOutputMemory = utils::clone(*mOutputMemory->getAidlMemory());
434 CHECK(copiedOutputMemory.has_value()) << copiedOutputMemory.error().message;
435
436 pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::pool>(
437 std::move(copiedInputMemory).value()));
438 pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::pool>(
439 std::move(copiedOutputMemory).value()));
440 for (const auto& token : tokens) {
441 pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::token>(token));
442 }
443
444 // Copy input data to the input shared memory pool.
445 uint8_t* inputPtr = mInputMemory->getPointer();
446 for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
447 if (!inputs[i].hasNoValue && inputs[i].location.poolIndex == kInputPoolIndex) {
448 const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]];
449 const uint8_t* begin = op.data.get<uint8_t>();
450 const uint8_t* end = begin + op.data.size();
451 std::copy(begin, end, inputPtr + inputs[i].location.offset);
452 }
453 }
454 return Request{
455 .inputs = std::move(inputs), .outputs = std::move(outputs), .pools = std::move(pools)};
456}
457
458std::vector<TestBuffer> ExecutionContext::getOutputBuffers(const TestModel& testModel,
459 const Request& request) const {
460 // Copy out output results.
461 uint8_t* outputPtr = mOutputMemory->getPointer();
462 std::vector<TestBuffer> outputBuffers;
463 for (uint32_t i = 0; i < request.outputs.size(); i++) {
464 const auto& outputLoc = request.outputs[i].location;
465 if (outputLoc.poolIndex == kOutputPoolIndex) {
466 outputBuffers.emplace_back(outputLoc.length, outputPtr + outputLoc.offset);
467 } else {
468 const auto& op = testModel.main.operands[testModel.main.outputIndexes[i]];
469 if (op.data.size() == 0) {
470 outputBuffers.emplace_back(0, nullptr);
471 } else {
472 SCOPED_TRACE("Output index = " + std::to_string(i));
473 const uint32_t bufferIndex = outputLoc.poolIndex - kDeviceMemoryBeginIndex;
474 TestBuffer buffer;
475 getBuffer(mBuffers[bufferIndex], op.data.size(), &buffer);
476 outputBuffers.push_back(std::move(buffer));
477 }
478 }
479 }
480 return outputBuffers;
481}
482
483// Get a TestBuffer with data copied from an IBuffer object.
484void ExecutionContext::getBuffer(const std::shared_ptr<IBuffer>& buffer, size_t size,
485 TestBuffer* testBuffer) const {
486 // IBuffer -> Shared memory.
487 auto sharedMemory = nn::createSharedMemory(size).value();
488 auto aidlMemory = utils::convert(sharedMemory).value();
489 const auto ret = buffer->copyTo(aidlMemory);
490 ASSERT_TRUE(ret.isOk());
491
492 // Shared memory -> TestBuffer.
493 const auto outputMemory = nn::map(sharedMemory).value();
494 const uint8_t* outputPtr = std::visit(
495 [](auto* ptr) { return static_cast<const uint8_t*>(ptr); }, outputMemory.pointer);
496 ASSERT_NE(outputPtr, nullptr);
497 ASSERT_NE(testBuffer, nullptr);
498 *testBuffer = TestBuffer(size, outputPtr);
499}
500
501static bool hasZeroSizedOutput(const TestModel& testModel) {
502 return std::any_of(testModel.main.outputIndexes.begin(), testModel.main.outputIndexes.end(),
503 [&testModel](uint32_t index) {
504 return testModel.main.operands[index].data.size() == 0;
505 });
506}
507
508void EvaluatePreparedModel(const std::shared_ptr<IDevice>& device,
509 const std::shared_ptr<IPreparedModel>& preparedModel,
510 const TestModel& testModel, const TestConfig& testConfig,
511 bool* skipped = nullptr) {
512 if (skipped != nullptr) {
513 *skipped = false;
514 }
515 // If output0 does not have size larger than one byte, we can not test with insufficient buffer.
516 if (testConfig.outputType == OutputType::INSUFFICIENT &&
517 !isOutputSizeGreaterThanOne(testModel, 0)) {
518 return;
519 }
520
521 ExecutionContext context(device, preparedModel);
522 auto maybeRequest = context.createRequest(testModel, testConfig.memoryType);
523 // Skip if testing memory domain but no device memory has been allocated.
524 if (!maybeRequest.has_value()) {
525 return;
526 }
527
528 Request request = std::move(maybeRequest).value();
529
530 constexpr uint32_t kInsufficientOutputIndex = 0;
531 if (testConfig.outputType == OutputType::INSUFFICIENT) {
532 makeOutputInsufficientSize(kInsufficientOutputIndex, &request);
533 }
534
535 int64_t loopTimeoutDuration = kOmittedTimeoutDuration;
536 // OutputType::MISSED_DEADLINE is only used by
537 // TestKind::INTINITE_LOOP_TIMEOUT tests to verify that an infinite loop is
538 // aborted after a timeout.
539 if (testConfig.outputType == OutputType::MISSED_DEADLINE) {
540 // Override the default loop timeout duration with a small value to
541 // speed up test execution.
542 constexpr int64_t kMillisecond = 1'000'000;
543 loopTimeoutDuration = 1 * kMillisecond;
544 }
545
546 ErrorStatus executionStatus;
547 std::vector<OutputShape> outputShapes;
548 Timing timing = kNoTiming;
549 switch (testConfig.executor) {
550 case Executor::SYNC: {
551 SCOPED_TRACE("synchronous");
552
553 ExecutionResult executionResult;
554 // execute
555 const auto ret = preparedModel->executeSynchronously(request, testConfig.measureTiming,
556 kNoDeadline, loopTimeoutDuration,
557 &executionResult);
558 ASSERT_TRUE(ret.isOk() || ret.getExceptionCode() == EX_SERVICE_SPECIFIC)
559 << ret.getDescription();
560 if (ret.isOk()) {
561 executionStatus = executionResult.outputSufficientSize
562 ? ErrorStatus::NONE
563 : ErrorStatus::OUTPUT_INSUFFICIENT_SIZE;
564 outputShapes = std::move(executionResult.outputShapes);
565 timing = executionResult.timing;
566 } else {
567 executionStatus = static_cast<ErrorStatus>(ret.getServiceSpecificError());
568 }
569 break;
570 }
571 case Executor::FENCED: {
572 SCOPED_TRACE("fenced");
573 ErrorStatus result = ErrorStatus::NONE;
Jooyung Hand33893f2021-02-26 17:09:23 +0900574 FencedExecutionResult executionResult;
Lev Proleevc185e882020-12-15 19:25:32 +0000575 auto ret = preparedModel->executeFenced(request, {}, testConfig.measureTiming,
576 kNoDeadline, loopTimeoutDuration, kNoDuration,
Jooyung Hand33893f2021-02-26 17:09:23 +0900577 &executionResult);
Lev Proleevc185e882020-12-15 19:25:32 +0000578 ASSERT_TRUE(ret.isOk() || ret.getExceptionCode() == EX_SERVICE_SPECIFIC)
579 << ret.getDescription();
580 if (!ret.isOk()) {
581 result = static_cast<ErrorStatus>(ret.getServiceSpecificError());
582 executionStatus = result;
Jooyung Hand33893f2021-02-26 17:09:23 +0900583 } else if (executionResult.syncFence.get() != -1) {
Lev Proleevc185e882020-12-15 19:25:32 +0000584 std::vector<ndk::ScopedFileDescriptor> waitFor;
Jooyung Hand33893f2021-02-26 17:09:23 +0900585 auto dupFd = dup(executionResult.syncFence.get());
Lev Proleevc185e882020-12-15 19:25:32 +0000586 ASSERT_NE(dupFd, -1);
587 waitFor.emplace_back(dupFd);
588 // If a sync fence is returned, try start another run waiting for the sync fence.
589 ret = preparedModel->executeFenced(request, waitFor, testConfig.measureTiming,
590 kNoDeadline, loopTimeoutDuration, kNoDuration,
Jooyung Hand33893f2021-02-26 17:09:23 +0900591 &executionResult);
Lev Proleevc185e882020-12-15 19:25:32 +0000592 ASSERT_TRUE(ret.isOk());
Jooyung Hand33893f2021-02-26 17:09:23 +0900593 waitForSyncFence(executionResult.syncFence.get());
Lev Proleevc185e882020-12-15 19:25:32 +0000594 }
595 if (result == ErrorStatus::NONE) {
Jooyung Hand33893f2021-02-26 17:09:23 +0900596 ASSERT_NE(executionResult.callback, nullptr);
Lev Proleevc185e882020-12-15 19:25:32 +0000597 Timing timingFenced;
Jooyung Hand33893f2021-02-26 17:09:23 +0900598 auto ret = executionResult.callback->getExecutionInfo(&timing, &timingFenced,
599 &executionStatus);
Lev Proleevc185e882020-12-15 19:25:32 +0000600 ASSERT_TRUE(ret.isOk());
601 }
602 break;
603 }
604 default: {
605 FAIL() << "Unsupported execution mode for AIDL interface.";
606 }
607 }
608
609 if (testConfig.outputType != OutputType::FULLY_SPECIFIED &&
610 executionStatus == ErrorStatus::GENERAL_FAILURE) {
611 if (skipped != nullptr) {
612 *skipped = true;
613 }
614 if (!testConfig.reportSkipping) {
615 return;
616 }
617 LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
618 "execute model that it does not support.";
619 std::cout << "[ ] Early termination of test because vendor service cannot "
620 "execute model that it does not support."
621 << std::endl;
622 GTEST_SKIP();
623 }
624 if (!testConfig.measureTiming) {
625 EXPECT_EQ(timing, kNoTiming);
626 } else {
627 if (timing.timeOnDevice != -1 && timing.timeInDriver != -1) {
628 EXPECT_LE(timing.timeOnDevice, timing.timeInDriver);
629 }
630 }
631
632 switch (testConfig.outputType) {
633 case OutputType::FULLY_SPECIFIED:
634 if (testConfig.executor == Executor::FENCED && hasZeroSizedOutput(testModel)) {
635 // Executor::FENCED does not support zero-sized output.
636 ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionStatus);
637 return;
638 }
639 // If the model output operands are fully specified, outputShapes must be either
640 // either empty, or have the same number of elements as the number of outputs.
641 ASSERT_EQ(ErrorStatus::NONE, executionStatus);
642 ASSERT_TRUE(outputShapes.size() == 0 ||
643 outputShapes.size() == testModel.main.outputIndexes.size());
644 break;
645 case OutputType::UNSPECIFIED:
646 if (testConfig.executor == Executor::FENCED) {
647 // For Executor::FENCED, the output shape must be fully specified.
648 ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionStatus);
649 return;
650 }
651 // If the model output operands are not fully specified, outputShapes must have
652 // the same number of elements as the number of outputs.
653 ASSERT_EQ(ErrorStatus::NONE, executionStatus);
654 ASSERT_EQ(outputShapes.size(), testModel.main.outputIndexes.size());
655 break;
656 case OutputType::INSUFFICIENT:
657 if (testConfig.executor == Executor::FENCED) {
658 // For Executor::FENCED, the output shape must be fully specified.
659 ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionStatus);
660 return;
661 }
662 ASSERT_EQ(ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, executionStatus);
663 ASSERT_EQ(outputShapes.size(), testModel.main.outputIndexes.size());
664 // Check that all returned output dimensions are at least as fully specified as the
665 // union of the information about the corresponding operand in the model and in the
666 // request. In this test, all model outputs have known rank with all dimensions
667 // unspecified, and no dimensional information is provided in the request.
668 for (uint32_t i = 0; i < outputShapes.size(); i++) {
669 ASSERT_EQ(outputShapes[i].isSufficient, i != kInsufficientOutputIndex);
670 const auto& actual = outputShapes[i].dimensions;
671 const auto& golden =
672 testModel.main.operands[testModel.main.outputIndexes[i]].dimensions;
673 ASSERT_EQ(actual.size(), golden.size());
674 for (uint32_t j = 0; j < actual.size(); j++) {
675 if (actual[j] == 0) continue;
676 EXPECT_EQ(actual[j], golden[j]) << "index: " << j;
677 }
678 }
679 return;
680 case OutputType::MISSED_DEADLINE:
681 ASSERT_TRUE(executionStatus == ErrorStatus::MISSED_DEADLINE_TRANSIENT ||
682 executionStatus == ErrorStatus::MISSED_DEADLINE_PERSISTENT)
683 << "executionStatus = " << executionStatus;
684 return;
685 }
686
687 // Go through all outputs, check returned output shapes.
688 for (uint32_t i = 0; i < outputShapes.size(); i++) {
689 EXPECT_TRUE(outputShapes[i].isSufficient);
690 const auto& expect = testModel.main.operands[testModel.main.outputIndexes[i]].dimensions;
691 const auto unsignedActual = nn::toUnsigned(outputShapes[i].dimensions);
692 ASSERT_TRUE(unsignedActual.has_value());
693 const std::vector<uint32_t>& actual = unsignedActual.value();
694 EXPECT_EQ(expect, actual);
695 }
696
697 // Retrieve execution results.
698 const std::vector<TestBuffer> outputs = context.getOutputBuffers(testModel, request);
699
700 // We want "close-enough" results.
701 checkResults(testModel, outputs);
702}
703
704void EvaluatePreparedModel(const std::shared_ptr<IDevice>& device,
705 const std::shared_ptr<IPreparedModel>& preparedModel,
706 const TestModel& testModel, TestKind testKind) {
707 std::vector<OutputType> outputTypesList;
708 std::vector<bool> measureTimingList;
709 std::vector<Executor> executorList;
710 std::vector<MemoryType> memoryTypeList;
711
712 switch (testKind) {
713 case TestKind::GENERAL: {
714 outputTypesList = {OutputType::FULLY_SPECIFIED};
715 measureTimingList = {false, true};
716 executorList = {Executor::SYNC};
717 memoryTypeList = {MemoryType::ASHMEM};
718 } break;
719 case TestKind::DYNAMIC_SHAPE: {
720 outputTypesList = {OutputType::UNSPECIFIED, OutputType::INSUFFICIENT};
721 measureTimingList = {false, true};
722 executorList = {Executor::SYNC, Executor::FENCED};
723 memoryTypeList = {MemoryType::ASHMEM};
724 } break;
725 case TestKind::MEMORY_DOMAIN: {
726 outputTypesList = {OutputType::FULLY_SPECIFIED};
727 measureTimingList = {false};
728 executorList = {Executor::SYNC, Executor::FENCED};
729 memoryTypeList = {MemoryType::BLOB_AHWB, MemoryType::DEVICE};
730 } break;
731 case TestKind::FENCED_COMPUTE: {
732 outputTypesList = {OutputType::FULLY_SPECIFIED};
733 measureTimingList = {false, true};
734 executorList = {Executor::FENCED};
735 memoryTypeList = {MemoryType::ASHMEM};
736 } break;
737 case TestKind::QUANTIZATION_COUPLING: {
738 LOG(FATAL) << "Wrong TestKind for EvaluatePreparedModel";
739 return;
740 } break;
741 case TestKind::INTINITE_LOOP_TIMEOUT: {
742 outputTypesList = {OutputType::MISSED_DEADLINE};
743 measureTimingList = {false, true};
744 executorList = {Executor::SYNC, Executor::FENCED};
745 memoryTypeList = {MemoryType::ASHMEM};
746 } break;
747 }
748
749 for (const OutputType outputType : outputTypesList) {
750 for (const bool measureTiming : measureTimingList) {
751 for (const Executor executor : executorList) {
752 for (const MemoryType memoryType : memoryTypeList) {
753 const TestConfig testConfig(executor, measureTiming, outputType, memoryType);
754 EvaluatePreparedModel(device, preparedModel, testModel, testConfig);
755 }
756 }
757 }
758 }
759}
760
761void EvaluatePreparedCoupledModels(const std::shared_ptr<IDevice>& device,
762 const std::shared_ptr<IPreparedModel>& preparedModel,
763 const TestModel& testModel,
764 const std::shared_ptr<IPreparedModel>& preparedCoupledModel,
765 const TestModel& coupledModel) {
766 const std::vector<OutputType> outputTypesList = {OutputType::FULLY_SPECIFIED};
767 const std::vector<bool> measureTimingList = {false, true};
768 const std::vector<Executor> executorList = {Executor::SYNC, Executor::FENCED};
769
770 for (const OutputType outputType : outputTypesList) {
771 for (const bool measureTiming : measureTimingList) {
772 for (const Executor executor : executorList) {
773 const TestConfig testConfig(executor, measureTiming, outputType, MemoryType::ASHMEM,
774 /*reportSkipping=*/false);
775 bool baseSkipped = false;
776 EvaluatePreparedModel(device, preparedModel, testModel, testConfig, &baseSkipped);
777 bool coupledSkipped = false;
778 EvaluatePreparedModel(device, preparedCoupledModel, coupledModel, testConfig,
779 &coupledSkipped);
780 ASSERT_EQ(baseSkipped, coupledSkipped);
781 if (baseSkipped) {
782 LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
783 "execute model that it does not support.";
784 std::cout << "[ ] Early termination of test because vendor service "
785 "cannot "
786 "execute model that it does not support."
787 << std::endl;
788 GTEST_SKIP();
789 }
790 }
791 }
792 }
793}
794
795void Execute(const std::shared_ptr<IDevice>& device, const TestModel& testModel,
796 TestKind testKind) {
797 Model model = createModel(testModel);
798 if (testKind == TestKind::DYNAMIC_SHAPE) {
799 makeOutputDimensionsUnspecified(&model);
800 }
801
802 std::shared_ptr<IPreparedModel> preparedModel;
803 switch (testKind) {
804 case TestKind::GENERAL:
805 case TestKind::DYNAMIC_SHAPE:
806 case TestKind::MEMORY_DOMAIN:
807 case TestKind::FENCED_COMPUTE:
808 case TestKind::INTINITE_LOOP_TIMEOUT: {
809 createPreparedModel(device, model, &preparedModel);
810 if (preparedModel == nullptr) return;
811 EvaluatePreparedModel(device, preparedModel, testModel, testKind);
812 } break;
813 case TestKind::QUANTIZATION_COUPLING: {
814 ASSERT_TRUE(testModel.hasQuant8CoupledOperands());
815 createPreparedModel(device, model, &preparedModel,
816 /*reportSkipping*/ false);
817 TestModel signedQuantizedModel = convertQuant8AsymmOperandsToSigned(testModel);
818 std::shared_ptr<IPreparedModel> preparedCoupledModel;
819 createPreparedModel(device, createModel(signedQuantizedModel), &preparedCoupledModel,
820 /*reportSkipping*/ false);
821 // If we couldn't prepare a model with unsigned quantization, we must
822 // fail to prepare a model with signed quantization as well.
823 if (preparedModel == nullptr) {
824 ASSERT_EQ(preparedCoupledModel, nullptr);
825 // If we failed to prepare both of the models, we can safely skip
826 // the test.
827 LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
828 "prepare model that it does not support.";
829 std::cout
830 << "[ ] Early termination of test because vendor service cannot "
831 "prepare model that it does not support."
832 << std::endl;
833 GTEST_SKIP();
834 }
835 ASSERT_NE(preparedCoupledModel, nullptr);
836 EvaluatePreparedCoupledModels(device, preparedModel, testModel, preparedCoupledModel,
837 signedQuantizedModel);
838 } break;
839 }
840}
841
842void GeneratedTestBase::SetUp() {
843 testing::TestWithParam<GeneratedTestParam>::SetUp();
844 ASSERT_NE(kDevice, nullptr);
845}
846
847std::vector<NamedModel> getNamedModels(const FilterFn& filter) {
848 return TestModelManager::get().getTestModels(filter);
849}
850
851std::vector<NamedModel> getNamedModels(const FilterNameFn& filter) {
852 return TestModelManager::get().getTestModels(filter);
853}
854
855std::string printGeneratedTest(const testing::TestParamInfo<GeneratedTestParam>& info) {
856 const auto& [namedDevice, namedModel] = info.param;
857 return gtestCompliantName(getName(namedDevice) + "_" + getName(namedModel));
858}
859
860// Tag for the generated tests
861class GeneratedTest : public GeneratedTestBase {};
862
863// Tag for the dynamic output shape tests
864class DynamicOutputShapeTest : public GeneratedTest {};
865
866// Tag for the memory domain tests
867class MemoryDomainTest : public GeneratedTest {};
868
869// Tag for the fenced compute tests
870class FencedComputeTest : public GeneratedTest {};
871
872// Tag for the dynamic output shape tests
873class QuantizationCouplingTest : public GeneratedTest {};
874
875// Tag for the loop timeout tests
876class InfiniteLoopTimeoutTest : public GeneratedTest {};
877
878TEST_P(GeneratedTest, Test) {
879 Execute(kDevice, kTestModel, TestKind::GENERAL);
880}
881
882TEST_P(DynamicOutputShapeTest, Test) {
883 Execute(kDevice, kTestModel, TestKind::DYNAMIC_SHAPE);
884}
885
886TEST_P(MemoryDomainTest, Test) {
887 Execute(kDevice, kTestModel, TestKind::MEMORY_DOMAIN);
888}
889
890TEST_P(FencedComputeTest, Test) {
891 Execute(kDevice, kTestModel, TestKind::FENCED_COMPUTE);
892}
893
894TEST_P(QuantizationCouplingTest, Test) {
895 Execute(kDevice, kTestModel, TestKind::QUANTIZATION_COUPLING);
896}
897
898TEST_P(InfiniteLoopTimeoutTest, Test) {
899 Execute(kDevice, kTestModel, TestKind::INTINITE_LOOP_TIMEOUT);
900}
901
902INSTANTIATE_GENERATED_TEST(GeneratedTest,
903 [](const TestModel& testModel) { return !testModel.expectFailure; });
904
905INSTANTIATE_GENERATED_TEST(DynamicOutputShapeTest, [](const TestModel& testModel) {
906 return !testModel.expectFailure && !testModel.hasScalarOutputs();
907});
908
909INSTANTIATE_GENERATED_TEST(MemoryDomainTest,
910 [](const TestModel& testModel) { return !testModel.expectFailure; });
911
912INSTANTIATE_GENERATED_TEST(FencedComputeTest,
913 [](const TestModel& testModel) { return !testModel.expectFailure; });
914
915INSTANTIATE_GENERATED_TEST(QuantizationCouplingTest, [](const TestModel& testModel) {
916 return !testModel.expectFailure && testModel.hasQuant8CoupledOperands() &&
917 testModel.main.operations.size() == 1;
918});
919
920INSTANTIATE_GENERATED_TEST(InfiniteLoopTimeoutTest, [](const TestModel& testModel) {
921 return testModel.isInfiniteLoopTimeoutTest();
922});
923
924} // namespace aidl::android::hardware::neuralnetworks::vts::functional