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