blob: 3c7f5f797d9627e43a1b72ec28097e3fb2eaa4eb [file] [log] [blame]
Lev Proleevb38bb4f2020-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 "Utils.h"
18
19#include <aidl/android/hardware/neuralnetworks/IPreparedModelParcel.h>
20#include <aidl/android/hardware/neuralnetworks/Operand.h>
21#include <aidl/android/hardware/neuralnetworks/OperandType.h>
22#include <android-base/logging.h>
23#include <android/binder_status.h>
24#include <android/hardware_buffer.h>
25
26#include <iostream>
27#include <limits>
28#include <numeric>
29
30#include <MemoryUtils.h>
31#include <nnapi/SharedMemory.h>
32#include <nnapi/hal/aidl/Conversions.h>
33#include <nnapi/hal/aidl/Utils.h>
34
35namespace aidl::android::hardware::neuralnetworks {
36
37using test_helper::TestBuffer;
38using test_helper::TestModel;
39
40uint32_t sizeOfData(OperandType type) {
41 switch (type) {
42 case OperandType::FLOAT32:
43 case OperandType::INT32:
44 case OperandType::UINT32:
45 case OperandType::TENSOR_FLOAT32:
46 case OperandType::TENSOR_INT32:
47 return 4;
48 case OperandType::TENSOR_QUANT16_SYMM:
49 case OperandType::TENSOR_FLOAT16:
50 case OperandType::FLOAT16:
51 case OperandType::TENSOR_QUANT16_ASYMM:
52 return 2;
53 case OperandType::TENSOR_QUANT8_ASYMM:
54 case OperandType::BOOL:
55 case OperandType::TENSOR_BOOL8:
56 case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
57 case OperandType::TENSOR_QUANT8_SYMM:
58 case OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
59 return 1;
60 case OperandType::SUBGRAPH:
61 return 0;
62 default:
63 CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
64 return 0;
65 }
66}
67
68static bool isTensor(OperandType type) {
69 switch (type) {
70 case OperandType::FLOAT32:
71 case OperandType::INT32:
72 case OperandType::UINT32:
73 case OperandType::FLOAT16:
74 case OperandType::BOOL:
75 case OperandType::SUBGRAPH:
76 return false;
77 case OperandType::TENSOR_FLOAT32:
78 case OperandType::TENSOR_INT32:
79 case OperandType::TENSOR_QUANT16_SYMM:
80 case OperandType::TENSOR_FLOAT16:
81 case OperandType::TENSOR_QUANT16_ASYMM:
82 case OperandType::TENSOR_QUANT8_ASYMM:
83 case OperandType::TENSOR_BOOL8:
84 case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
85 case OperandType::TENSOR_QUANT8_SYMM:
86 case OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
87 return true;
88 default:
89 CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
90 return false;
91 }
92}
93
94uint32_t sizeOfData(const Operand& operand) {
95 const uint32_t dataSize = sizeOfData(operand.type);
96 if (isTensor(operand.type) && operand.dimensions.size() == 0) return 0;
97 return std::accumulate(operand.dimensions.begin(), operand.dimensions.end(), dataSize,
98 std::multiplies<>{});
99}
100
101std::unique_ptr<TestAshmem> TestAshmem::create(uint32_t size) {
102 auto ashmem = std::make_unique<TestAshmem>(size);
103 return ashmem->mIsValid ? std::move(ashmem) : nullptr;
104}
105
106void TestAshmem::initialize(uint32_t size) {
107 mIsValid = false;
108 ASSERT_GT(size, 0);
109 const auto sharedMemory = nn::createSharedMemory(size).value();
110 mMappedMemory = nn::map(sharedMemory).value();
111 mPtr = static_cast<uint8_t*>(std::get<void*>(mMappedMemory.pointer));
112 CHECK_NE(mPtr, nullptr);
113 mAidlMemory = utils::convert(sharedMemory).value();
114 mIsValid = true;
115}
116
117std::unique_ptr<TestBlobAHWB> TestBlobAHWB::create(uint32_t size) {
118 auto ahwb = std::make_unique<TestBlobAHWB>(size);
119 return ahwb->mIsValid ? std::move(ahwb) : nullptr;
120}
121
122void TestBlobAHWB::initialize(uint32_t size) {
123 mIsValid = false;
124 ASSERT_GT(size, 0);
125 const auto usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN;
126 const AHardwareBuffer_Desc desc = {
127 .width = size,
128 .height = 1,
129 .layers = 1,
130 .format = AHARDWAREBUFFER_FORMAT_BLOB,
131 .usage = usage,
132 .stride = size,
133 };
134
135 ASSERT_EQ(AHardwareBuffer_allocate(&desc, &mAhwb), 0);
136 ASSERT_NE(mAhwb, nullptr);
137
Michael Butlerbbe43d92021-02-08 00:05:07 -0800138 const auto sharedMemory =
139 nn::createSharedMemoryFromAHWB(mAhwb, /*takeOwnership=*/false).value();
Lev Proleevb38bb4f2020-12-15 19:25:32 +0000140 mMapping = nn::map(sharedMemory).value();
141 mPtr = static_cast<uint8_t*>(std::get<void*>(mMapping.pointer));
142 CHECK_NE(mPtr, nullptr);
143 mAidlMemory = utils::convert(sharedMemory).value();
144
145 mIsValid = true;
146}
147
148TestBlobAHWB::~TestBlobAHWB() {
149 if (mAhwb) {
150 AHardwareBuffer_unlock(mAhwb, nullptr);
151 AHardwareBuffer_release(mAhwb);
152 }
153}
154
155std::string gtestCompliantName(std::string name) {
156 // gtest test names must only contain alphanumeric characters
157 std::replace_if(
158 name.begin(), name.end(), [](char c) { return !std::isalnum(c); }, '_');
159 return name;
160}
161
162::std::ostream& operator<<(::std::ostream& os, ErrorStatus errorStatus) {
163 return os << toString(errorStatus);
164}
165
166Request ExecutionContext::createRequest(const TestModel& testModel, MemoryType memoryType) {
167 CHECK(memoryType == MemoryType::ASHMEM || memoryType == MemoryType::BLOB_AHWB);
168
169 // Model inputs.
170 std::vector<RequestArgument> inputs(testModel.main.inputIndexes.size());
171 size_t inputSize = 0;
172 for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
173 const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]];
174 if (op.data.size() == 0) {
175 // Omitted input.
176 inputs[i] = {.hasNoValue = true};
177 } else {
178 DataLocation loc = {.poolIndex = kInputPoolIndex,
179 .offset = static_cast<int64_t>(inputSize),
180 .length = static_cast<int64_t>(op.data.size())};
181 inputSize += op.data.alignedSize();
182 inputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
183 }
184 }
185
186 // Model outputs.
187 std::vector<RequestArgument> outputs(testModel.main.outputIndexes.size());
188 size_t outputSize = 0;
189 for (uint32_t i = 0; i < testModel.main.outputIndexes.size(); i++) {
190 const auto& op = testModel.main.operands[testModel.main.outputIndexes[i]];
191
192 // In the case of zero-sized output, we should at least provide a one-byte buffer.
193 // This is because zero-sized tensors are only supported internally to the driver, or
194 // reported in output shapes. It is illegal for the client to pre-specify a zero-sized
195 // tensor as model output. Otherwise, we will have two semantic conflicts:
196 // - "Zero dimension" conflicts with "unspecified dimension".
197 // - "Omitted operand buffer" conflicts with "zero-sized operand buffer".
198 size_t bufferSize = std::max<size_t>(op.data.size(), 1);
199
200 DataLocation loc = {.poolIndex = kOutputPoolIndex,
201 .offset = static_cast<int64_t>(outputSize),
202 .length = static_cast<int64_t>(bufferSize)};
203 outputSize += op.data.size() == 0 ? TestBuffer::kAlignment : op.data.alignedSize();
204 outputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
205 }
206
207 // Allocate memory pools.
208 if (memoryType == MemoryType::ASHMEM) {
209 mInputMemory = TestAshmem::create(inputSize);
210 mOutputMemory = TestAshmem::create(outputSize);
211 } else {
212 mInputMemory = TestBlobAHWB::create(inputSize);
213 mOutputMemory = TestBlobAHWB::create(outputSize);
214 }
215 CHECK_NE(mInputMemory, nullptr);
216 CHECK_NE(mOutputMemory, nullptr);
217
218 auto copiedInputMemory = utils::clone(*mInputMemory->getAidlMemory());
219 CHECK(copiedInputMemory.has_value()) << copiedInputMemory.error().message;
220 auto copiedOutputMemory = utils::clone(*mOutputMemory->getAidlMemory());
221 CHECK(copiedOutputMemory.has_value()) << copiedOutputMemory.error().message;
222
223 std::vector<RequestMemoryPool> pools;
224 pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::pool>(
225 std::move(copiedInputMemory).value()));
226 pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::pool>(
227 std::move(copiedOutputMemory).value()));
228
229 // Copy input data to the memory pool.
230 uint8_t* inputPtr = mInputMemory->getPointer();
231 for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
232 const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]];
233 if (op.data.size() > 0) {
234 const uint8_t* begin = op.data.get<uint8_t>();
235 const uint8_t* end = begin + op.data.size();
236 std::copy(begin, end, inputPtr + inputs[i].location.offset);
237 }
238 }
239
240 return {.inputs = std::move(inputs), .outputs = std::move(outputs), .pools = std::move(pools)};
241}
242
243std::vector<TestBuffer> ExecutionContext::getOutputBuffers(const Request& request) const {
244 // Copy out output results.
245 uint8_t* outputPtr = mOutputMemory->getPointer();
246 std::vector<TestBuffer> outputBuffers;
247 for (const auto& output : request.outputs) {
248 outputBuffers.emplace_back(output.location.length, outputPtr + output.location.offset);
249 }
250 return outputBuffers;
251}
252
253} // namespace aidl::android::hardware::neuralnetworks