blob: 7a042ed37eed8ba2ad793d6692d2990aeaec71f8 [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) {
Xusong Wang16858a62021-02-17 21:59:39 -0800302 auto& loc = request->outputs[outputIndex].location;
303 ASSERT_GT(loc.length, 1u);
304 loc.length -= 1u;
305 // Test that the padding is not used for output data.
306 loc.padding += 1u;
Lev Proleevc185e882020-12-15 19:25:32 +0000307}
308
309static void makeOutputDimensionsUnspecified(Model* model) {
310 for (auto i : model->main.outputIndexes) {
311 auto& dims = model->main.operands[i].dimensions;
312 std::fill(dims.begin(), dims.end(), 0);
313 }
314}
315
316// Manages the lifetime of memory resources used in an execution.
317class ExecutionContext {
318 public:
319 ExecutionContext(std::shared_ptr<IDevice> device, std::shared_ptr<IPreparedModel> preparedModel)
320 : kDevice(std::move(device)), kPreparedModel(std::move(preparedModel)) {}
321
322 std::optional<Request> createRequest(const TestModel& testModel, MemoryType memoryType);
323 std::vector<TestBuffer> getOutputBuffers(const TestModel& testModel,
324 const Request& request) const;
325
326 private:
327 // Get a TestBuffer with data copied from an IBuffer object.
328 void getBuffer(const std::shared_ptr<IBuffer>& buffer, size_t size,
329 TestBuffer* testBuffer) const;
330
331 static constexpr uint32_t kInputPoolIndex = 0;
332 static constexpr uint32_t kOutputPoolIndex = 1;
333 static constexpr uint32_t kDeviceMemoryBeginIndex = 2;
334
335 const std::shared_ptr<IDevice> kDevice;
336 const std::shared_ptr<IPreparedModel> kPreparedModel;
337 std::unique_ptr<TestMemoryBase> mInputMemory, mOutputMemory;
338 std::vector<std::shared_ptr<IBuffer>> mBuffers;
339};
340
Xusong Wang16858a62021-02-17 21:59:39 -0800341// Returns the number of bytes needed to round up "size" to the nearest multiple of "multiple".
342static uint32_t roundUpBytesNeeded(uint32_t size, uint32_t multiple) {
343 CHECK(multiple != 0);
344 return ((size + multiple - 1) / multiple) * multiple - size;
345}
346
Lev Proleevc185e882020-12-15 19:25:32 +0000347std::optional<Request> ExecutionContext::createRequest(const TestModel& testModel,
348 MemoryType memoryType) {
349 // Memory pools are organized as:
350 // - 0: Input shared memory pool
351 // - 1: Output shared memory pool
352 // - [2, 2+i): Input device memories
353 // - [2+i, 2+i+o): Output device memories
354 DeviceMemoryAllocator allocator(kDevice, kPreparedModel, testModel);
355 std::vector<int32_t> tokens;
356 mBuffers.clear();
357
358 // Model inputs.
359 std::vector<RequestArgument> inputs(testModel.main.inputIndexes.size());
360 size_t inputSize = 0;
361 for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
362 const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]];
363 if (op.data.size() == 0) {
364 // Omitted input.
365 inputs[i] = {.hasNoValue = true};
366 continue;
367 } else if (memoryType == MemoryType::DEVICE) {
368 SCOPED_TRACE("Input index = " + std::to_string(i));
369 auto [buffer, token] = allocator.allocate<IOType::INPUT>(i);
370 if (buffer != nullptr) {
371 DataLocation loc = {.poolIndex = static_cast<int32_t>(mBuffers.size() +
372 kDeviceMemoryBeginIndex)};
373 mBuffers.push_back(std::move(buffer));
374 tokens.push_back(token);
375 inputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
376 continue;
377 }
378 }
379
380 // Reserve shared memory for input.
Xusong Wang16858a62021-02-17 21:59:39 -0800381 inputSize += roundUpBytesNeeded(inputSize, nn::kDefaultRequestMemoryAlignment);
382 const auto padding = roundUpBytesNeeded(op.data.size(), nn::kDefaultRequestMemoryPadding);
Lev Proleevc185e882020-12-15 19:25:32 +0000383 DataLocation loc = {.poolIndex = kInputPoolIndex,
384 .offset = static_cast<int64_t>(inputSize),
Xusong Wang16858a62021-02-17 21:59:39 -0800385 .length = static_cast<int64_t>(op.data.size()),
386 .padding = static_cast<int64_t>(padding)};
387 inputSize += (op.data.size() + padding);
Lev Proleevc185e882020-12-15 19:25:32 +0000388 inputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
389 }
390
391 // Model outputs.
392 std::vector<RequestArgument> outputs(testModel.main.outputIndexes.size());
393 size_t outputSize = 0;
394 for (uint32_t i = 0; i < testModel.main.outputIndexes.size(); i++) {
395 const auto& op = testModel.main.operands[testModel.main.outputIndexes[i]];
396 if (memoryType == MemoryType::DEVICE) {
397 SCOPED_TRACE("Output index = " + std::to_string(i));
398 auto [buffer, token] = allocator.allocate<IOType::OUTPUT>(i);
399 if (buffer != nullptr) {
400 DataLocation loc = {.poolIndex = static_cast<int32_t>(mBuffers.size() +
401 kDeviceMemoryBeginIndex)};
402 mBuffers.push_back(std::move(buffer));
403 tokens.push_back(token);
404 outputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
405 continue;
406 }
407 }
408
409 // In the case of zero-sized output, we should at least provide a one-byte buffer.
410 // This is because zero-sized tensors are only supported internally to the driver, or
411 // reported in output shapes. It is illegal for the client to pre-specify a zero-sized
412 // tensor as model output. Otherwise, we will have two semantic conflicts:
413 // - "Zero dimension" conflicts with "unspecified dimension".
414 // - "Omitted operand buffer" conflicts with "zero-sized operand buffer".
415 size_t bufferSize = std::max<size_t>(op.data.size(), 1);
416
417 // Reserve shared memory for output.
Xusong Wang16858a62021-02-17 21:59:39 -0800418 outputSize += roundUpBytesNeeded(outputSize, nn::kDefaultRequestMemoryAlignment);
419 const auto padding = roundUpBytesNeeded(bufferSize, nn::kDefaultRequestMemoryPadding);
Lev Proleevc185e882020-12-15 19:25:32 +0000420 DataLocation loc = {.poolIndex = kOutputPoolIndex,
421 .offset = static_cast<int64_t>(outputSize),
Xusong Wang16858a62021-02-17 21:59:39 -0800422 .length = static_cast<int64_t>(bufferSize),
423 .padding = static_cast<int64_t>(padding)};
424 outputSize += (bufferSize + padding);
Lev Proleevc185e882020-12-15 19:25:32 +0000425 outputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
426 }
427
428 if (memoryType == MemoryType::DEVICE && mBuffers.empty()) {
429 return std::nullopt;
430 }
431
432 // Memory pools.
433 if (memoryType == MemoryType::BLOB_AHWB) {
434 mInputMemory = TestBlobAHWB::create(std::max<size_t>(inputSize, 1));
435 mOutputMemory = TestBlobAHWB::create(std::max<size_t>(outputSize, 1));
436 } else {
437 mInputMemory = TestAshmem::create(std::max<size_t>(inputSize, 1));
438 mOutputMemory = TestAshmem::create(std::max<size_t>(outputSize, 1));
439 }
440 CHECK_NE(mInputMemory, nullptr);
441 CHECK_NE(mOutputMemory, nullptr);
442 std::vector<RequestMemoryPool> pools;
443 pools.reserve(kDeviceMemoryBeginIndex + mBuffers.size());
444
445 auto copiedInputMemory = utils::clone(*mInputMemory->getAidlMemory());
446 CHECK(copiedInputMemory.has_value()) << copiedInputMemory.error().message;
447 auto copiedOutputMemory = utils::clone(*mOutputMemory->getAidlMemory());
448 CHECK(copiedOutputMemory.has_value()) << copiedOutputMemory.error().message;
449
450 pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::pool>(
451 std::move(copiedInputMemory).value()));
452 pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::pool>(
453 std::move(copiedOutputMemory).value()));
454 for (const auto& token : tokens) {
455 pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::token>(token));
456 }
457
458 // Copy input data to the input shared memory pool.
459 uint8_t* inputPtr = mInputMemory->getPointer();
460 for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
461 if (!inputs[i].hasNoValue && inputs[i].location.poolIndex == kInputPoolIndex) {
462 const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]];
463 const uint8_t* begin = op.data.get<uint8_t>();
464 const uint8_t* end = begin + op.data.size();
465 std::copy(begin, end, inputPtr + inputs[i].location.offset);
466 }
467 }
468 return Request{
469 .inputs = std::move(inputs), .outputs = std::move(outputs), .pools = std::move(pools)};
470}
471
472std::vector<TestBuffer> ExecutionContext::getOutputBuffers(const TestModel& testModel,
473 const Request& request) const {
474 // Copy out output results.
475 uint8_t* outputPtr = mOutputMemory->getPointer();
476 std::vector<TestBuffer> outputBuffers;
477 for (uint32_t i = 0; i < request.outputs.size(); i++) {
478 const auto& outputLoc = request.outputs[i].location;
479 if (outputLoc.poolIndex == kOutputPoolIndex) {
480 outputBuffers.emplace_back(outputLoc.length, outputPtr + outputLoc.offset);
481 } else {
482 const auto& op = testModel.main.operands[testModel.main.outputIndexes[i]];
483 if (op.data.size() == 0) {
484 outputBuffers.emplace_back(0, nullptr);
485 } else {
486 SCOPED_TRACE("Output index = " + std::to_string(i));
487 const uint32_t bufferIndex = outputLoc.poolIndex - kDeviceMemoryBeginIndex;
488 TestBuffer buffer;
489 getBuffer(mBuffers[bufferIndex], op.data.size(), &buffer);
490 outputBuffers.push_back(std::move(buffer));
491 }
492 }
493 }
494 return outputBuffers;
495}
496
497// Get a TestBuffer with data copied from an IBuffer object.
498void ExecutionContext::getBuffer(const std::shared_ptr<IBuffer>& buffer, size_t size,
499 TestBuffer* testBuffer) const {
500 // IBuffer -> Shared memory.
501 auto sharedMemory = nn::createSharedMemory(size).value();
502 auto aidlMemory = utils::convert(sharedMemory).value();
503 const auto ret = buffer->copyTo(aidlMemory);
504 ASSERT_TRUE(ret.isOk());
505
506 // Shared memory -> TestBuffer.
507 const auto outputMemory = nn::map(sharedMemory).value();
508 const uint8_t* outputPtr = std::visit(
509 [](auto* ptr) { return static_cast<const uint8_t*>(ptr); }, outputMemory.pointer);
510 ASSERT_NE(outputPtr, nullptr);
511 ASSERT_NE(testBuffer, nullptr);
512 *testBuffer = TestBuffer(size, outputPtr);
513}
514
515static bool hasZeroSizedOutput(const TestModel& testModel) {
516 return std::any_of(testModel.main.outputIndexes.begin(), testModel.main.outputIndexes.end(),
517 [&testModel](uint32_t index) {
518 return testModel.main.operands[index].data.size() == 0;
519 });
520}
521
522void EvaluatePreparedModel(const std::shared_ptr<IDevice>& device,
523 const std::shared_ptr<IPreparedModel>& preparedModel,
524 const TestModel& testModel, const TestConfig& testConfig,
525 bool* skipped = nullptr) {
526 if (skipped != nullptr) {
527 *skipped = false;
528 }
529 // If output0 does not have size larger than one byte, we can not test with insufficient buffer.
530 if (testConfig.outputType == OutputType::INSUFFICIENT &&
531 !isOutputSizeGreaterThanOne(testModel, 0)) {
532 return;
533 }
534
535 ExecutionContext context(device, preparedModel);
536 auto maybeRequest = context.createRequest(testModel, testConfig.memoryType);
537 // Skip if testing memory domain but no device memory has been allocated.
538 if (!maybeRequest.has_value()) {
539 return;
540 }
541
542 Request request = std::move(maybeRequest).value();
543
544 constexpr uint32_t kInsufficientOutputIndex = 0;
545 if (testConfig.outputType == OutputType::INSUFFICIENT) {
546 makeOutputInsufficientSize(kInsufficientOutputIndex, &request);
547 }
548
549 int64_t loopTimeoutDuration = kOmittedTimeoutDuration;
550 // OutputType::MISSED_DEADLINE is only used by
551 // TestKind::INTINITE_LOOP_TIMEOUT tests to verify that an infinite loop is
552 // aborted after a timeout.
553 if (testConfig.outputType == OutputType::MISSED_DEADLINE) {
554 // Override the default loop timeout duration with a small value to
555 // speed up test execution.
556 constexpr int64_t kMillisecond = 1'000'000;
557 loopTimeoutDuration = 1 * kMillisecond;
558 }
559
560 ErrorStatus executionStatus;
561 std::vector<OutputShape> outputShapes;
562 Timing timing = kNoTiming;
563 switch (testConfig.executor) {
564 case Executor::SYNC: {
565 SCOPED_TRACE("synchronous");
566
567 ExecutionResult executionResult;
568 // execute
569 const auto ret = preparedModel->executeSynchronously(request, testConfig.measureTiming,
570 kNoDeadline, loopTimeoutDuration,
571 &executionResult);
572 ASSERT_TRUE(ret.isOk() || ret.getExceptionCode() == EX_SERVICE_SPECIFIC)
573 << ret.getDescription();
574 if (ret.isOk()) {
575 executionStatus = executionResult.outputSufficientSize
576 ? ErrorStatus::NONE
577 : ErrorStatus::OUTPUT_INSUFFICIENT_SIZE;
578 outputShapes = std::move(executionResult.outputShapes);
579 timing = executionResult.timing;
580 } else {
581 executionStatus = static_cast<ErrorStatus>(ret.getServiceSpecificError());
582 }
583 break;
584 }
585 case Executor::FENCED: {
586 SCOPED_TRACE("fenced");
587 ErrorStatus result = ErrorStatus::NONE;
Jooyung Hand33893f2021-02-26 17:09:23 +0900588 FencedExecutionResult executionResult;
Lev Proleevc185e882020-12-15 19:25:32 +0000589 auto ret = preparedModel->executeFenced(request, {}, 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() || ret.getExceptionCode() == EX_SERVICE_SPECIFIC)
593 << ret.getDescription();
594 if (!ret.isOk()) {
595 result = static_cast<ErrorStatus>(ret.getServiceSpecificError());
596 executionStatus = result;
Jooyung Hand33893f2021-02-26 17:09:23 +0900597 } else if (executionResult.syncFence.get() != -1) {
Lev Proleevc185e882020-12-15 19:25:32 +0000598 std::vector<ndk::ScopedFileDescriptor> waitFor;
Jooyung Hand33893f2021-02-26 17:09:23 +0900599 auto dupFd = dup(executionResult.syncFence.get());
Lev Proleevc185e882020-12-15 19:25:32 +0000600 ASSERT_NE(dupFd, -1);
601 waitFor.emplace_back(dupFd);
602 // If a sync fence is returned, try start another run waiting for the sync fence.
603 ret = preparedModel->executeFenced(request, waitFor, testConfig.measureTiming,
604 kNoDeadline, loopTimeoutDuration, kNoDuration,
Jooyung Hand33893f2021-02-26 17:09:23 +0900605 &executionResult);
Lev Proleevc185e882020-12-15 19:25:32 +0000606 ASSERT_TRUE(ret.isOk());
Jooyung Hand33893f2021-02-26 17:09:23 +0900607 waitForSyncFence(executionResult.syncFence.get());
Lev Proleevc185e882020-12-15 19:25:32 +0000608 }
609 if (result == ErrorStatus::NONE) {
Jooyung Hand33893f2021-02-26 17:09:23 +0900610 ASSERT_NE(executionResult.callback, nullptr);
Lev Proleevc185e882020-12-15 19:25:32 +0000611 Timing timingFenced;
Jooyung Hand33893f2021-02-26 17:09:23 +0900612 auto ret = executionResult.callback->getExecutionInfo(&timing, &timingFenced,
613 &executionStatus);
Lev Proleevc185e882020-12-15 19:25:32 +0000614 ASSERT_TRUE(ret.isOk());
615 }
616 break;
617 }
618 default: {
619 FAIL() << "Unsupported execution mode for AIDL interface.";
620 }
621 }
622
623 if (testConfig.outputType != OutputType::FULLY_SPECIFIED &&
624 executionStatus == ErrorStatus::GENERAL_FAILURE) {
625 if (skipped != nullptr) {
626 *skipped = true;
627 }
628 if (!testConfig.reportSkipping) {
629 return;
630 }
631 LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
632 "execute model that it does not support.";
633 std::cout << "[ ] Early termination of test because vendor service cannot "
634 "execute model that it does not support."
635 << std::endl;
636 GTEST_SKIP();
637 }
638 if (!testConfig.measureTiming) {
639 EXPECT_EQ(timing, kNoTiming);
640 } else {
641 if (timing.timeOnDevice != -1 && timing.timeInDriver != -1) {
642 EXPECT_LE(timing.timeOnDevice, timing.timeInDriver);
643 }
644 }
645
646 switch (testConfig.outputType) {
647 case OutputType::FULLY_SPECIFIED:
648 if (testConfig.executor == Executor::FENCED && hasZeroSizedOutput(testModel)) {
649 // Executor::FENCED does not support zero-sized output.
650 ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionStatus);
651 return;
652 }
653 // If the model output operands are fully specified, outputShapes must be either
654 // either empty, or have the same number of elements as the number of outputs.
655 ASSERT_EQ(ErrorStatus::NONE, executionStatus);
656 ASSERT_TRUE(outputShapes.size() == 0 ||
657 outputShapes.size() == testModel.main.outputIndexes.size());
658 break;
659 case OutputType::UNSPECIFIED:
660 if (testConfig.executor == Executor::FENCED) {
661 // For Executor::FENCED, the output shape must be fully specified.
662 ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionStatus);
663 return;
664 }
665 // If the model output operands are not fully specified, outputShapes must have
666 // the same number of elements as the number of outputs.
667 ASSERT_EQ(ErrorStatus::NONE, executionStatus);
668 ASSERT_EQ(outputShapes.size(), testModel.main.outputIndexes.size());
669 break;
670 case OutputType::INSUFFICIENT:
671 if (testConfig.executor == Executor::FENCED) {
672 // For Executor::FENCED, the output shape must be fully specified.
673 ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionStatus);
674 return;
675 }
676 ASSERT_EQ(ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, executionStatus);
677 ASSERT_EQ(outputShapes.size(), testModel.main.outputIndexes.size());
678 // Check that all returned output dimensions are at least as fully specified as the
679 // union of the information about the corresponding operand in the model and in the
680 // request. In this test, all model outputs have known rank with all dimensions
681 // unspecified, and no dimensional information is provided in the request.
682 for (uint32_t i = 0; i < outputShapes.size(); i++) {
683 ASSERT_EQ(outputShapes[i].isSufficient, i != kInsufficientOutputIndex);
684 const auto& actual = outputShapes[i].dimensions;
685 const auto& golden =
686 testModel.main.operands[testModel.main.outputIndexes[i]].dimensions;
687 ASSERT_EQ(actual.size(), golden.size());
688 for (uint32_t j = 0; j < actual.size(); j++) {
689 if (actual[j] == 0) continue;
690 EXPECT_EQ(actual[j], golden[j]) << "index: " << j;
691 }
692 }
693 return;
694 case OutputType::MISSED_DEADLINE:
695 ASSERT_TRUE(executionStatus == ErrorStatus::MISSED_DEADLINE_TRANSIENT ||
696 executionStatus == ErrorStatus::MISSED_DEADLINE_PERSISTENT)
697 << "executionStatus = " << executionStatus;
698 return;
699 }
700
701 // Go through all outputs, check returned output shapes.
702 for (uint32_t i = 0; i < outputShapes.size(); i++) {
703 EXPECT_TRUE(outputShapes[i].isSufficient);
704 const auto& expect = testModel.main.operands[testModel.main.outputIndexes[i]].dimensions;
705 const auto unsignedActual = nn::toUnsigned(outputShapes[i].dimensions);
706 ASSERT_TRUE(unsignedActual.has_value());
707 const std::vector<uint32_t>& actual = unsignedActual.value();
708 EXPECT_EQ(expect, actual);
709 }
710
711 // Retrieve execution results.
712 const std::vector<TestBuffer> outputs = context.getOutputBuffers(testModel, request);
713
714 // We want "close-enough" results.
715 checkResults(testModel, outputs);
716}
717
718void EvaluatePreparedModel(const std::shared_ptr<IDevice>& device,
719 const std::shared_ptr<IPreparedModel>& preparedModel,
720 const TestModel& testModel, TestKind testKind) {
721 std::vector<OutputType> outputTypesList;
722 std::vector<bool> measureTimingList;
723 std::vector<Executor> executorList;
724 std::vector<MemoryType> memoryTypeList;
725
726 switch (testKind) {
727 case TestKind::GENERAL: {
728 outputTypesList = {OutputType::FULLY_SPECIFIED};
729 measureTimingList = {false, true};
730 executorList = {Executor::SYNC};
731 memoryTypeList = {MemoryType::ASHMEM};
732 } break;
733 case TestKind::DYNAMIC_SHAPE: {
734 outputTypesList = {OutputType::UNSPECIFIED, OutputType::INSUFFICIENT};
735 measureTimingList = {false, true};
736 executorList = {Executor::SYNC, Executor::FENCED};
737 memoryTypeList = {MemoryType::ASHMEM};
738 } break;
739 case TestKind::MEMORY_DOMAIN: {
740 outputTypesList = {OutputType::FULLY_SPECIFIED};
741 measureTimingList = {false};
742 executorList = {Executor::SYNC, Executor::FENCED};
743 memoryTypeList = {MemoryType::BLOB_AHWB, MemoryType::DEVICE};
744 } break;
745 case TestKind::FENCED_COMPUTE: {
746 outputTypesList = {OutputType::FULLY_SPECIFIED};
747 measureTimingList = {false, true};
748 executorList = {Executor::FENCED};
749 memoryTypeList = {MemoryType::ASHMEM};
750 } break;
751 case TestKind::QUANTIZATION_COUPLING: {
752 LOG(FATAL) << "Wrong TestKind for EvaluatePreparedModel";
753 return;
754 } break;
755 case TestKind::INTINITE_LOOP_TIMEOUT: {
756 outputTypesList = {OutputType::MISSED_DEADLINE};
757 measureTimingList = {false, true};
758 executorList = {Executor::SYNC, Executor::FENCED};
759 memoryTypeList = {MemoryType::ASHMEM};
760 } break;
761 }
762
763 for (const OutputType outputType : outputTypesList) {
764 for (const bool measureTiming : measureTimingList) {
765 for (const Executor executor : executorList) {
766 for (const MemoryType memoryType : memoryTypeList) {
767 const TestConfig testConfig(executor, measureTiming, outputType, memoryType);
768 EvaluatePreparedModel(device, preparedModel, testModel, testConfig);
769 }
770 }
771 }
772 }
773}
774
775void EvaluatePreparedCoupledModels(const std::shared_ptr<IDevice>& device,
776 const std::shared_ptr<IPreparedModel>& preparedModel,
777 const TestModel& testModel,
778 const std::shared_ptr<IPreparedModel>& preparedCoupledModel,
779 const TestModel& coupledModel) {
780 const std::vector<OutputType> outputTypesList = {OutputType::FULLY_SPECIFIED};
781 const std::vector<bool> measureTimingList = {false, true};
782 const std::vector<Executor> executorList = {Executor::SYNC, Executor::FENCED};
783
784 for (const OutputType outputType : outputTypesList) {
785 for (const bool measureTiming : measureTimingList) {
786 for (const Executor executor : executorList) {
787 const TestConfig testConfig(executor, measureTiming, outputType, MemoryType::ASHMEM,
788 /*reportSkipping=*/false);
789 bool baseSkipped = false;
790 EvaluatePreparedModel(device, preparedModel, testModel, testConfig, &baseSkipped);
791 bool coupledSkipped = false;
792 EvaluatePreparedModel(device, preparedCoupledModel, coupledModel, testConfig,
793 &coupledSkipped);
794 ASSERT_EQ(baseSkipped, coupledSkipped);
795 if (baseSkipped) {
796 LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
797 "execute model that it does not support.";
798 std::cout << "[ ] Early termination of test because vendor service "
799 "cannot "
800 "execute model that it does not support."
801 << std::endl;
802 GTEST_SKIP();
803 }
804 }
805 }
806 }
807}
808
809void Execute(const std::shared_ptr<IDevice>& device, const TestModel& testModel,
810 TestKind testKind) {
811 Model model = createModel(testModel);
812 if (testKind == TestKind::DYNAMIC_SHAPE) {
813 makeOutputDimensionsUnspecified(&model);
814 }
815
816 std::shared_ptr<IPreparedModel> preparedModel;
817 switch (testKind) {
818 case TestKind::GENERAL:
819 case TestKind::DYNAMIC_SHAPE:
820 case TestKind::MEMORY_DOMAIN:
821 case TestKind::FENCED_COMPUTE:
822 case TestKind::INTINITE_LOOP_TIMEOUT: {
823 createPreparedModel(device, model, &preparedModel);
824 if (preparedModel == nullptr) return;
825 EvaluatePreparedModel(device, preparedModel, testModel, testKind);
826 } break;
827 case TestKind::QUANTIZATION_COUPLING: {
828 ASSERT_TRUE(testModel.hasQuant8CoupledOperands());
829 createPreparedModel(device, model, &preparedModel,
830 /*reportSkipping*/ false);
831 TestModel signedQuantizedModel = convertQuant8AsymmOperandsToSigned(testModel);
832 std::shared_ptr<IPreparedModel> preparedCoupledModel;
833 createPreparedModel(device, createModel(signedQuantizedModel), &preparedCoupledModel,
834 /*reportSkipping*/ false);
835 // If we couldn't prepare a model with unsigned quantization, we must
836 // fail to prepare a model with signed quantization as well.
837 if (preparedModel == nullptr) {
838 ASSERT_EQ(preparedCoupledModel, nullptr);
839 // If we failed to prepare both of the models, we can safely skip
840 // the test.
841 LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
842 "prepare model that it does not support.";
843 std::cout
844 << "[ ] Early termination of test because vendor service cannot "
845 "prepare model that it does not support."
846 << std::endl;
847 GTEST_SKIP();
848 }
849 ASSERT_NE(preparedCoupledModel, nullptr);
850 EvaluatePreparedCoupledModels(device, preparedModel, testModel, preparedCoupledModel,
851 signedQuantizedModel);
852 } break;
853 }
854}
855
856void GeneratedTestBase::SetUp() {
857 testing::TestWithParam<GeneratedTestParam>::SetUp();
858 ASSERT_NE(kDevice, nullptr);
859}
860
861std::vector<NamedModel> getNamedModels(const FilterFn& filter) {
862 return TestModelManager::get().getTestModels(filter);
863}
864
865std::vector<NamedModel> getNamedModels(const FilterNameFn& filter) {
866 return TestModelManager::get().getTestModels(filter);
867}
868
869std::string printGeneratedTest(const testing::TestParamInfo<GeneratedTestParam>& info) {
870 const auto& [namedDevice, namedModel] = info.param;
871 return gtestCompliantName(getName(namedDevice) + "_" + getName(namedModel));
872}
873
874// Tag for the generated tests
875class GeneratedTest : public GeneratedTestBase {};
876
877// Tag for the dynamic output shape tests
878class DynamicOutputShapeTest : public GeneratedTest {};
879
880// Tag for the memory domain tests
881class MemoryDomainTest : public GeneratedTest {};
882
883// Tag for the fenced compute tests
884class FencedComputeTest : public GeneratedTest {};
885
886// Tag for the dynamic output shape tests
887class QuantizationCouplingTest : public GeneratedTest {};
888
889// Tag for the loop timeout tests
890class InfiniteLoopTimeoutTest : public GeneratedTest {};
891
892TEST_P(GeneratedTest, Test) {
893 Execute(kDevice, kTestModel, TestKind::GENERAL);
894}
895
896TEST_P(DynamicOutputShapeTest, Test) {
897 Execute(kDevice, kTestModel, TestKind::DYNAMIC_SHAPE);
898}
899
900TEST_P(MemoryDomainTest, Test) {
901 Execute(kDevice, kTestModel, TestKind::MEMORY_DOMAIN);
902}
903
904TEST_P(FencedComputeTest, Test) {
905 Execute(kDevice, kTestModel, TestKind::FENCED_COMPUTE);
906}
907
908TEST_P(QuantizationCouplingTest, Test) {
909 Execute(kDevice, kTestModel, TestKind::QUANTIZATION_COUPLING);
910}
911
912TEST_P(InfiniteLoopTimeoutTest, Test) {
913 Execute(kDevice, kTestModel, TestKind::INTINITE_LOOP_TIMEOUT);
914}
915
916INSTANTIATE_GENERATED_TEST(GeneratedTest,
917 [](const TestModel& testModel) { return !testModel.expectFailure; });
918
919INSTANTIATE_GENERATED_TEST(DynamicOutputShapeTest, [](const TestModel& testModel) {
920 return !testModel.expectFailure && !testModel.hasScalarOutputs();
921});
922
923INSTANTIATE_GENERATED_TEST(MemoryDomainTest,
924 [](const TestModel& testModel) { return !testModel.expectFailure; });
925
926INSTANTIATE_GENERATED_TEST(FencedComputeTest,
927 [](const TestModel& testModel) { return !testModel.expectFailure; });
928
929INSTANTIATE_GENERATED_TEST(QuantizationCouplingTest, [](const TestModel& testModel) {
930 return !testModel.expectFailure && testModel.hasQuant8CoupledOperands() &&
931 testModel.main.operations.size() == 1;
932});
933
934INSTANTIATE_GENERATED_TEST(InfiniteLoopTimeoutTest, [](const TestModel& testModel) {
935 return testModel.isInfiniteLoopTimeoutTest();
936});
937
938} // namespace aidl::android::hardware::neuralnetworks::vts::functional