blob: 4beb8282532cf16c97b22ad01a8d96e107c9452f [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;
574 ndk::ScopedFileDescriptor syncFenceFd;
575 std::shared_ptr<IFencedExecutionCallback> fencedCallback;
576 auto ret = preparedModel->executeFenced(request, {}, testConfig.measureTiming,
577 kNoDeadline, loopTimeoutDuration, kNoDuration,
578 &syncFenceFd, &fencedCallback);
579 ASSERT_TRUE(ret.isOk() || ret.getExceptionCode() == EX_SERVICE_SPECIFIC)
580 << ret.getDescription();
581 if (!ret.isOk()) {
582 result = static_cast<ErrorStatus>(ret.getServiceSpecificError());
583 executionStatus = result;
584 } else if (syncFenceFd.get() != -1) {
585 std::vector<ndk::ScopedFileDescriptor> waitFor;
586 auto dupFd = dup(syncFenceFd.get());
587 ASSERT_NE(dupFd, -1);
588 waitFor.emplace_back(dupFd);
589 // If a sync fence is returned, try start another run waiting for the sync fence.
590 ret = preparedModel->executeFenced(request, waitFor, testConfig.measureTiming,
591 kNoDeadline, loopTimeoutDuration, kNoDuration,
592 &syncFenceFd, &fencedCallback);
593 ASSERT_TRUE(ret.isOk());
594 waitForSyncFence(syncFenceFd.get());
595 }
596 if (result == ErrorStatus::NONE) {
597 ASSERT_NE(fencedCallback, nullptr);
598 Timing timingFenced;
599 auto ret =
600 fencedCallback->getExecutionInfo(&timing, &timingFenced, &executionStatus);
601 ASSERT_TRUE(ret.isOk());
602 }
603 break;
604 }
605 default: {
606 FAIL() << "Unsupported execution mode for AIDL interface.";
607 }
608 }
609
610 if (testConfig.outputType != OutputType::FULLY_SPECIFIED &&
611 executionStatus == ErrorStatus::GENERAL_FAILURE) {
612 if (skipped != nullptr) {
613 *skipped = true;
614 }
615 if (!testConfig.reportSkipping) {
616 return;
617 }
618 LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
619 "execute model that it does not support.";
620 std::cout << "[ ] Early termination of test because vendor service cannot "
621 "execute model that it does not support."
622 << std::endl;
623 GTEST_SKIP();
624 }
625 if (!testConfig.measureTiming) {
626 EXPECT_EQ(timing, kNoTiming);
627 } else {
628 if (timing.timeOnDevice != -1 && timing.timeInDriver != -1) {
629 EXPECT_LE(timing.timeOnDevice, timing.timeInDriver);
630 }
631 }
632
633 switch (testConfig.outputType) {
634 case OutputType::FULLY_SPECIFIED:
635 if (testConfig.executor == Executor::FENCED && hasZeroSizedOutput(testModel)) {
636 // Executor::FENCED does not support zero-sized output.
637 ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionStatus);
638 return;
639 }
640 // If the model output operands are fully specified, outputShapes must be either
641 // either empty, or have the same number of elements as the number of outputs.
642 ASSERT_EQ(ErrorStatus::NONE, executionStatus);
643 ASSERT_TRUE(outputShapes.size() == 0 ||
644 outputShapes.size() == testModel.main.outputIndexes.size());
645 break;
646 case OutputType::UNSPECIFIED:
647 if (testConfig.executor == Executor::FENCED) {
648 // For Executor::FENCED, the output shape must be fully specified.
649 ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionStatus);
650 return;
651 }
652 // If the model output operands are not fully specified, outputShapes must have
653 // the same number of elements as the number of outputs.
654 ASSERT_EQ(ErrorStatus::NONE, executionStatus);
655 ASSERT_EQ(outputShapes.size(), testModel.main.outputIndexes.size());
656 break;
657 case OutputType::INSUFFICIENT:
658 if (testConfig.executor == Executor::FENCED) {
659 // For Executor::FENCED, the output shape must be fully specified.
660 ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionStatus);
661 return;
662 }
663 ASSERT_EQ(ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, executionStatus);
664 ASSERT_EQ(outputShapes.size(), testModel.main.outputIndexes.size());
665 // Check that all returned output dimensions are at least as fully specified as the
666 // union of the information about the corresponding operand in the model and in the
667 // request. In this test, all model outputs have known rank with all dimensions
668 // unspecified, and no dimensional information is provided in the request.
669 for (uint32_t i = 0; i < outputShapes.size(); i++) {
670 ASSERT_EQ(outputShapes[i].isSufficient, i != kInsufficientOutputIndex);
671 const auto& actual = outputShapes[i].dimensions;
672 const auto& golden =
673 testModel.main.operands[testModel.main.outputIndexes[i]].dimensions;
674 ASSERT_EQ(actual.size(), golden.size());
675 for (uint32_t j = 0; j < actual.size(); j++) {
676 if (actual[j] == 0) continue;
677 EXPECT_EQ(actual[j], golden[j]) << "index: " << j;
678 }
679 }
680 return;
681 case OutputType::MISSED_DEADLINE:
682 ASSERT_TRUE(executionStatus == ErrorStatus::MISSED_DEADLINE_TRANSIENT ||
683 executionStatus == ErrorStatus::MISSED_DEADLINE_PERSISTENT)
684 << "executionStatus = " << executionStatus;
685 return;
686 }
687
688 // Go through all outputs, check returned output shapes.
689 for (uint32_t i = 0; i < outputShapes.size(); i++) {
690 EXPECT_TRUE(outputShapes[i].isSufficient);
691 const auto& expect = testModel.main.operands[testModel.main.outputIndexes[i]].dimensions;
692 const auto unsignedActual = nn::toUnsigned(outputShapes[i].dimensions);
693 ASSERT_TRUE(unsignedActual.has_value());
694 const std::vector<uint32_t>& actual = unsignedActual.value();
695 EXPECT_EQ(expect, actual);
696 }
697
698 // Retrieve execution results.
699 const std::vector<TestBuffer> outputs = context.getOutputBuffers(testModel, request);
700
701 // We want "close-enough" results.
702 checkResults(testModel, outputs);
703}
704
705void EvaluatePreparedModel(const std::shared_ptr<IDevice>& device,
706 const std::shared_ptr<IPreparedModel>& preparedModel,
707 const TestModel& testModel, TestKind testKind) {
708 std::vector<OutputType> outputTypesList;
709 std::vector<bool> measureTimingList;
710 std::vector<Executor> executorList;
711 std::vector<MemoryType> memoryTypeList;
712
713 switch (testKind) {
714 case TestKind::GENERAL: {
715 outputTypesList = {OutputType::FULLY_SPECIFIED};
716 measureTimingList = {false, true};
717 executorList = {Executor::SYNC};
718 memoryTypeList = {MemoryType::ASHMEM};
719 } break;
720 case TestKind::DYNAMIC_SHAPE: {
721 outputTypesList = {OutputType::UNSPECIFIED, OutputType::INSUFFICIENT};
722 measureTimingList = {false, true};
723 executorList = {Executor::SYNC, Executor::FENCED};
724 memoryTypeList = {MemoryType::ASHMEM};
725 } break;
726 case TestKind::MEMORY_DOMAIN: {
727 outputTypesList = {OutputType::FULLY_SPECIFIED};
728 measureTimingList = {false};
729 executorList = {Executor::SYNC, Executor::FENCED};
730 memoryTypeList = {MemoryType::BLOB_AHWB, MemoryType::DEVICE};
731 } break;
732 case TestKind::FENCED_COMPUTE: {
733 outputTypesList = {OutputType::FULLY_SPECIFIED};
734 measureTimingList = {false, true};
735 executorList = {Executor::FENCED};
736 memoryTypeList = {MemoryType::ASHMEM};
737 } break;
738 case TestKind::QUANTIZATION_COUPLING: {
739 LOG(FATAL) << "Wrong TestKind for EvaluatePreparedModel";
740 return;
741 } break;
742 case TestKind::INTINITE_LOOP_TIMEOUT: {
743 outputTypesList = {OutputType::MISSED_DEADLINE};
744 measureTimingList = {false, true};
745 executorList = {Executor::SYNC, Executor::FENCED};
746 memoryTypeList = {MemoryType::ASHMEM};
747 } break;
748 }
749
750 for (const OutputType outputType : outputTypesList) {
751 for (const bool measureTiming : measureTimingList) {
752 for (const Executor executor : executorList) {
753 for (const MemoryType memoryType : memoryTypeList) {
754 const TestConfig testConfig(executor, measureTiming, outputType, memoryType);
755 EvaluatePreparedModel(device, preparedModel, testModel, testConfig);
756 }
757 }
758 }
759 }
760}
761
762void EvaluatePreparedCoupledModels(const std::shared_ptr<IDevice>& device,
763 const std::shared_ptr<IPreparedModel>& preparedModel,
764 const TestModel& testModel,
765 const std::shared_ptr<IPreparedModel>& preparedCoupledModel,
766 const TestModel& coupledModel) {
767 const std::vector<OutputType> outputTypesList = {OutputType::FULLY_SPECIFIED};
768 const std::vector<bool> measureTimingList = {false, true};
769 const std::vector<Executor> executorList = {Executor::SYNC, Executor::FENCED};
770
771 for (const OutputType outputType : outputTypesList) {
772 for (const bool measureTiming : measureTimingList) {
773 for (const Executor executor : executorList) {
774 const TestConfig testConfig(executor, measureTiming, outputType, MemoryType::ASHMEM,
775 /*reportSkipping=*/false);
776 bool baseSkipped = false;
777 EvaluatePreparedModel(device, preparedModel, testModel, testConfig, &baseSkipped);
778 bool coupledSkipped = false;
779 EvaluatePreparedModel(device, preparedCoupledModel, coupledModel, testConfig,
780 &coupledSkipped);
781 ASSERT_EQ(baseSkipped, coupledSkipped);
782 if (baseSkipped) {
783 LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
784 "execute model that it does not support.";
785 std::cout << "[ ] Early termination of test because vendor service "
786 "cannot "
787 "execute model that it does not support."
788 << std::endl;
789 GTEST_SKIP();
790 }
791 }
792 }
793 }
794}
795
796void Execute(const std::shared_ptr<IDevice>& device, const TestModel& testModel,
797 TestKind testKind) {
798 Model model = createModel(testModel);
799 if (testKind == TestKind::DYNAMIC_SHAPE) {
800 makeOutputDimensionsUnspecified(&model);
801 }
802
803 std::shared_ptr<IPreparedModel> preparedModel;
804 switch (testKind) {
805 case TestKind::GENERAL:
806 case TestKind::DYNAMIC_SHAPE:
807 case TestKind::MEMORY_DOMAIN:
808 case TestKind::FENCED_COMPUTE:
809 case TestKind::INTINITE_LOOP_TIMEOUT: {
810 createPreparedModel(device, model, &preparedModel);
811 if (preparedModel == nullptr) return;
812 EvaluatePreparedModel(device, preparedModel, testModel, testKind);
813 } break;
814 case TestKind::QUANTIZATION_COUPLING: {
815 ASSERT_TRUE(testModel.hasQuant8CoupledOperands());
816 createPreparedModel(device, model, &preparedModel,
817 /*reportSkipping*/ false);
818 TestModel signedQuantizedModel = convertQuant8AsymmOperandsToSigned(testModel);
819 std::shared_ptr<IPreparedModel> preparedCoupledModel;
820 createPreparedModel(device, createModel(signedQuantizedModel), &preparedCoupledModel,
821 /*reportSkipping*/ false);
822 // If we couldn't prepare a model with unsigned quantization, we must
823 // fail to prepare a model with signed quantization as well.
824 if (preparedModel == nullptr) {
825 ASSERT_EQ(preparedCoupledModel, nullptr);
826 // If we failed to prepare both of the models, we can safely skip
827 // the test.
828 LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
829 "prepare model that it does not support.";
830 std::cout
831 << "[ ] Early termination of test because vendor service cannot "
832 "prepare model that it does not support."
833 << std::endl;
834 GTEST_SKIP();
835 }
836 ASSERT_NE(preparedCoupledModel, nullptr);
837 EvaluatePreparedCoupledModels(device, preparedModel, testModel, preparedCoupledModel,
838 signedQuantizedModel);
839 } break;
840 }
841}
842
843void GeneratedTestBase::SetUp() {
844 testing::TestWithParam<GeneratedTestParam>::SetUp();
845 ASSERT_NE(kDevice, nullptr);
846}
847
848std::vector<NamedModel> getNamedModels(const FilterFn& filter) {
849 return TestModelManager::get().getTestModels(filter);
850}
851
852std::vector<NamedModel> getNamedModels(const FilterNameFn& filter) {
853 return TestModelManager::get().getTestModels(filter);
854}
855
856std::string printGeneratedTest(const testing::TestParamInfo<GeneratedTestParam>& info) {
857 const auto& [namedDevice, namedModel] = info.param;
858 return gtestCompliantName(getName(namedDevice) + "_" + getName(namedModel));
859}
860
861// Tag for the generated tests
862class GeneratedTest : public GeneratedTestBase {};
863
864// Tag for the dynamic output shape tests
865class DynamicOutputShapeTest : public GeneratedTest {};
866
867// Tag for the memory domain tests
868class MemoryDomainTest : public GeneratedTest {};
869
870// Tag for the fenced compute tests
871class FencedComputeTest : public GeneratedTest {};
872
873// Tag for the dynamic output shape tests
874class QuantizationCouplingTest : public GeneratedTest {};
875
876// Tag for the loop timeout tests
877class InfiniteLoopTimeoutTest : public GeneratedTest {};
878
879TEST_P(GeneratedTest, Test) {
880 Execute(kDevice, kTestModel, TestKind::GENERAL);
881}
882
883TEST_P(DynamicOutputShapeTest, Test) {
884 Execute(kDevice, kTestModel, TestKind::DYNAMIC_SHAPE);
885}
886
887TEST_P(MemoryDomainTest, Test) {
888 Execute(kDevice, kTestModel, TestKind::MEMORY_DOMAIN);
889}
890
891TEST_P(FencedComputeTest, Test) {
892 Execute(kDevice, kTestModel, TestKind::FENCED_COMPUTE);
893}
894
895TEST_P(QuantizationCouplingTest, Test) {
896 Execute(kDevice, kTestModel, TestKind::QUANTIZATION_COUPLING);
897}
898
899TEST_P(InfiniteLoopTimeoutTest, Test) {
900 Execute(kDevice, kTestModel, TestKind::INTINITE_LOOP_TIMEOUT);
901}
902
903INSTANTIATE_GENERATED_TEST(GeneratedTest,
904 [](const TestModel& testModel) { return !testModel.expectFailure; });
905
906INSTANTIATE_GENERATED_TEST(DynamicOutputShapeTest, [](const TestModel& testModel) {
907 return !testModel.expectFailure && !testModel.hasScalarOutputs();
908});
909
910INSTANTIATE_GENERATED_TEST(MemoryDomainTest,
911 [](const TestModel& testModel) { return !testModel.expectFailure; });
912
913INSTANTIATE_GENERATED_TEST(FencedComputeTest,
914 [](const TestModel& testModel) { return !testModel.expectFailure; });
915
916INSTANTIATE_GENERATED_TEST(QuantizationCouplingTest, [](const TestModel& testModel) {
917 return !testModel.expectFailure && testModel.hasQuant8CoupledOperands() &&
918 testModel.main.operations.size() == 1;
919});
920
921INSTANTIATE_GENERATED_TEST(InfiniteLoopTimeoutTest, [](const TestModel& testModel) {
922 return testModel.isInfiniteLoopTimeoutTest();
923});
924
925} // namespace aidl::android::hardware::neuralnetworks::vts::functional