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 #define LOG_TAG "ShimConverter"
18
19 #include "ShimConverter.h"
20
21 #include <aidlcommonsupport/NativeHandle.h>
22 #include <android-base/logging.h>
23 #include <android-base/mapped_file.h>
24 #include <android-base/scopeguard.h>
25 #include <android/hardware_buffer.h>
26 #include <cutils/native_handle.h>
27 #include <nnapi/TypeUtils.h>
28 #include <nnapi/hal/aidl/Conversions.h>
29 #include <nnapi/hal/aidl/Utils.h>
30 #include <sys/mman.h>
31 #include <vndk/hardware_buffer.h>
32
33 #include <algorithm>
34 #include <memory>
35 #include <string>
36 #include <utility>
37 #include <vector>
38
39 using namespace ::android::nn::sl_wrapper;
40
41 namespace aidl::android::hardware::neuralnetworks {
42
43 namespace {
44
45 // Assumes that isValid(model) holds
convertSubgraphFromHAL(const NnApiSupportLibrary * nnapi,const std::vector<std::unique_ptr<::android::nn::sl_wrapper::Memory>> & memoryPools,const neuralnetworks::Model & model,std::vector<std::optional<::android::nn::sl_wrapper::Model>> * allModels,size_t subgraphIndex,const std::vector<uint8_t> & copiedOperandValues,ErrorStatus * errorStatus)46 ANeuralNetworksModel* convertSubgraphFromHAL(
47 const NnApiSupportLibrary* nnapi,
48 const std::vector<std::unique_ptr<::android::nn::sl_wrapper::Memory>>& memoryPools,
49 const neuralnetworks::Model& model,
50 std::vector<std::optional<::android::nn::sl_wrapper::Model>>* allModels,
51 size_t subgraphIndex, const std::vector<uint8_t>& copiedOperandValues,
52 ErrorStatus* errorStatus) {
53 *errorStatus = ErrorStatus::NONE;
54 if (allModels == nullptr || subgraphIndex >= (*allModels).size()) {
55 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
56 return nullptr;
57 }
58 if ((*allModels)[subgraphIndex].has_value()) {
59 return (*allModels)[subgraphIndex]->getHandle();
60 }
61
62 const auto& subgraph = subgraphIndex == 0 ? model.main : model.referenced[subgraphIndex - 1];
63 ::android::nn::sl_wrapper::Model resultModel(nnapi);
64
65 resultModel.relaxComputationFloat32toFloat16(model.relaxComputationFloat32toFloat16);
66
67 auto getExtensionName = [&](uint16_t prefix) -> const std::string* {
68 for (const auto& nameToPrefix : model.extensionNameToPrefix) {
69 if (prefix == nameToPrefix.prefix) {
70 return &nameToPrefix.name;
71 }
72 }
73 return nullptr;
74 };
75
76 for (int i = 0; i < subgraph.operands.size(); ++i) {
77 const auto& operand = subgraph.operands[i];
78
79 const std::vector<uint32_t> dimensions =
80 ::android::nn::toUnsigned(operand.dimensions).value();
81
82 ::android::nn::wrapper::OperandType operandType(
83 static_cast<::android::nn::wrapper::Type>(operand.type), dimensions, operand.scale,
84 operand.zeroPoint);
85
86 if (operand.type == OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL) {
87 const auto& params = operand.extraParams->get<OperandExtraParams::Tag::channelQuant>();
88 operandType.channelQuant = ::android::nn::wrapper::SymmPerChannelQuantParams(
89 params.scales, static_cast<uint32_t>(params.channelDim));
90 }
91
92 if (::android::nn::isExtension(static_cast<::android::nn::OperandType>(operand.type))) {
93 uint16_t extensionPrefix =
94 ::android::nn::getExtensionPrefix(static_cast<uint32_t>(operand.type));
95 uint16_t typeWithinExtension =
96 ::android::nn::getTypeWithinExtension(static_cast<uint32_t>(operand.type));
97
98 auto* extensionName = getExtensionName(extensionPrefix);
99 if (extensionName == nullptr) {
100 LOG(ERROR) << "Unknown extension prefix " << extensionPrefix;
101 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
102 return nullptr;
103 }
104 resultModel.getExtensionOperandType(*extensionName, typeWithinExtension,
105 &operandType.operandType.type);
106 if (!resultModel.isValid()) {
107 LOG(ERROR) << "Failed to get extension operand with index " << i;
108 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
109 return nullptr;
110 }
111 }
112
113 uint32_t operandIndex = resultModel.addOperand(&operandType);
114 if (!resultModel.isValid()) {
115 LOG(ERROR) << "Failed to add operand with index " << i;
116 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
117 return nullptr;
118 }
119
120 if (operand.extraParams &&
121 operand.extraParams->getTag() == OperandExtraParams::Tag::extension) {
122 const auto& extensionData =
123 operand.extraParams->get<OperandExtraParams::Tag::extension>();
124 resultModel.setOperandExtensionData(operandIndex, extensionData.data(),
125 extensionData.size());
126 if (!resultModel.isValid()) {
127 LOG(ERROR) << "Failed to add extension data for operand with index " << i;
128 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
129 return nullptr;
130 }
131 }
132
133 switch (operand.lifetime) {
134 case OperandLifeTime::CONSTANT_COPY: {
135 if (operand.location.length + operand.location.offset >
136 model.operandValues.size()) {
137 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
138 return nullptr;
139 }
140
141 if (operand.location.length <=
142 ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES) {
143 resultModel.setOperandValue(
144 i, model.operandValues.data() + operand.location.offset,
145 operand.location.length);
146 } else {
147 // If length is larger than 128 bytes, we are responsible for making sure
148 // that value outlives the model. If this case exists, then we created
149 // an internal copy, that is used here:
150 resultModel.setOperandValue(
151 i, copiedOperandValues.data() + operand.location.offset,
152 operand.location.length);
153 }
154 break;
155 }
156 case OperandLifeTime::CONSTANT_POOL: {
157 if (operand.location.poolIndex >= memoryPools.size()) {
158 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
159 return nullptr;
160 }
161 resultModel.setOperandValueFromMemory(
162 i, memoryPools[operand.location.poolIndex].get(), operand.location.offset,
163 operand.location.length);
164 break;
165 }
166 case OperandLifeTime::SUBGRAPH: {
167 ErrorStatus otherErrorStatus = ErrorStatus::NONE;
168 auto subgraph = convertSubgraphFromHAL(nnapi, memoryPools, model, allModels,
169 operand.location.offset + 1,
170 copiedOperandValues, &otherErrorStatus);
171 if (subgraph) {
172 resultModel.setOperandValueFromModel(i, subgraph);
173 } else {
174 LOG(ERROR) << "Failed to set subgraph operand value";
175 *errorStatus = otherErrorStatus;
176 return nullptr;
177 }
178 break;
179 }
180 case OperandLifeTime::NO_VALUE: {
181 resultModel.setOperandValue(i, nullptr, 0);
182 break;
183 }
184 case OperandLifeTime::TEMPORARY_VARIABLE:
185 case OperandLifeTime::SUBGRAPH_OUTPUT:
186 case OperandLifeTime::SUBGRAPH_INPUT: {
187 break;
188 }
189 default:
190 LOG(ERROR) << "Invalid operand type: " << static_cast<int>(operand.lifetime);
191 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
192 return nullptr;
193 }
194
195 if (!resultModel.isValid()) {
196 LOG(ERROR) << "Failed to add operand with index " << i;
197 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
198 return nullptr;
199 }
200 }
201
202 for (int i = 0; i < subgraph.operations.size(); ++i) {
203 const auto& operation = subgraph.operations[i];
204
205 std::vector<uint32_t> inputs(operation.inputs.begin(), operation.inputs.end());
206 std::vector<uint32_t> outputs(operation.outputs.begin(), operation.outputs.end());
207
208 int operationType = static_cast<int>(operation.type);
209 if (::android::nn::isExtension(static_cast<::android::nn::OperationType>(operationType))) {
210 uint16_t extensionPrefix =
211 ::android::nn::getExtensionPrefix(static_cast<uint32_t>(operationType));
212 uint16_t typeWithinExtension =
213 ::android::nn::getTypeWithinExtension(static_cast<uint32_t>(operationType));
214 auto* extensionName = getExtensionName(extensionPrefix);
215 if (extensionName == nullptr) {
216 LOG(ERROR) << "Unknown extension prefix " << extensionPrefix;
217 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
218 return nullptr;
219 }
220 resultModel.getExtensionOperationType(*extensionName, typeWithinExtension,
221 &operationType);
222 if (!resultModel.isValid()) {
223 LOG(ERROR) << "Failed to get extension operation with index " << i;
224 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
225 return nullptr;
226 }
227 }
228
229 resultModel.addOperation(operationType, inputs, outputs);
230
231 if (!resultModel.isValid()) {
232 LOG(ERROR) << "Failed to add operation with index " << i;
233 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
234 return nullptr;
235 }
236 }
237
238 std::vector<uint32_t> inputIndexes(subgraph.inputIndexes.begin(), subgraph.inputIndexes.end());
239 std::vector<uint32_t> outputIndexes(subgraph.outputIndexes.begin(),
240 subgraph.outputIndexes.end());
241
242 resultModel.identifyInputsAndOutputs(inputIndexes, outputIndexes);
243 if (!resultModel.isValid()) {
244 LOG(ERROR) << "Model identifyInputsAndOutputs failed";
245 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
246 return nullptr;
247 }
248
249 if (resultModel.finish() != Result::NO_ERROR) {
250 LOG(ERROR) << "Model finish failed";
251 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
252 return nullptr;
253 }
254
255 if (!resultModel.isValid()) {
256 LOG(ERROR) << "Invalid model";
257 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
258 return nullptr;
259 }
260
261 (*allModels)[subgraphIndex] = std::move(resultModel);
262 return (*allModels)[subgraphIndex]->getHandle();
263 }
264
265 // This is needed for CONSTANT_COPY operands > 128 bytes, we have to
266 // store them in intenal buffer
needsCopiedOperandValues(const neuralnetworks::Model & model)267 bool needsCopiedOperandValues(const neuralnetworks::Model& model) {
268 for (int sindex = 0; sindex < model.referenced.size() + 1; ++sindex) {
269 const auto& subgraph = sindex == 0 ? model.main : model.referenced[sindex - 1];
270 for (int i = 0; i < subgraph.operands.size(); ++i) {
271 const auto& operand = subgraph.operands[i];
272
273 if (operand.lifetime == OperandLifeTime::CONSTANT_COPY) {
274 if (operand.location.length >
275 ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES) {
276 return true;
277 }
278 }
279 }
280 }
281 return false;
282 }
283
isValid(const Subgraph & subgraph)284 bool isValid(const Subgraph& subgraph) {
285 // Either the operand has a known value before model execution begins, or we've seen a writer
286 // for this operand while walking operands in execution order. Initialize to known operands.
287 std::vector<bool> operandValueKnown;
288 operandValueKnown.reserve(subgraph.operands.size());
289 std::transform(subgraph.operands.begin(), subgraph.operands.end(),
290 std::back_inserter(operandValueKnown), [](const Operand& operand) {
291 return operand.lifetime != OperandLifeTime::TEMPORARY_VARIABLE &&
292 operand.lifetime != OperandLifeTime::SUBGRAPH_OUTPUT;
293 });
294
295 // Validate that operations are sorted into execution order.
296 //
297 // If there is a cycle in the graph, the operations will not
298 // appear to be sorted into execution order: Some operation will
299 // have an input for which operandValueKnown[] is false.
300 for (size_t i = 0; i < subgraph.operations.size(); ++i) {
301 const auto& operation = subgraph.operations[i];
302
303 for (size_t j = 0; j < operation.inputs.size(); ++j) {
304 const uint32_t k = operation.inputs[j];
305 if (!operandValueKnown[k]) {
306 LOG(ERROR) << "Operation " << i << " input " << j << " (operand " << k
307 << ") is read before it is written";
308 return false;
309 }
310 }
311
312 for (size_t j = 0; j < operation.outputs.size(); ++j) {
313 const uint32_t k = operation.outputs[j];
314 // Assuming validateOperations() has not returned an error, we know that this output is
315 // TEMPORARY_VARIABLE or MODEL_OUTPUT, and so the only way operandValueKnown[k] can be
316 // true is if we've already seen a writer for this operand.
317 if (operandValueKnown[k]) {
318 LOG(ERROR) << "Operation " << i << " output " << j << " (operand " << k
319 << ") has already been written";
320 return false;
321 }
322 operandValueKnown[k] = true;
323 }
324 }
325
326 // Verify all operands are written.
327 for (size_t i = 0; i < subgraph.operands.size(); ++i) {
328 if (!operandValueKnown[i]) {
329 LOG(ERROR) << "Operand " << i << " is never written";
330 return false;
331 }
332 const auto& operand = subgraph.operands[i];
333
334 if (operand.lifetime == OperandLifeTime::SUBGRAPH_OUTPUT) {
335 if (std::find(subgraph.outputIndexes.begin(), subgraph.outputIndexes.end(), i) ==
336 subgraph.outputIndexes.end()) {
337 LOG(ERROR) << "Op with output liftime, but not on output list: " << i;
338 return false;
339 }
340 }
341 }
342
343 // Validate input and output lifetime
344 for (auto index : subgraph.inputIndexes) {
345 if (subgraph.operands[index].lifetime != OperandLifeTime::SUBGRAPH_INPUT) {
346 LOG(ERROR) << "Input with index" << index << " has invalid lifetime";
347 return false;
348 }
349 }
350 for (auto index : subgraph.outputIndexes) {
351 if (subgraph.operands[index].lifetime != OperandLifeTime::SUBGRAPH_OUTPUT) {
352 LOG(ERROR) << "Output with index" << index << " has invalid lifetime";
353 return false;
354 }
355 }
356
357 // TODO(b/77871786): verify that every operation has at least one output operand that is read?
358 return true;
359 }
360
361 } // namespace
362
isValid(const neuralnetworks::Model & model)363 bool isValid(const neuralnetworks::Model& model) {
364 return (isValid(model.main) &&
365 std::all_of(model.referenced.begin(), model.referenced.end(),
366 [](const Subgraph& subgraph) { return isValid(subgraph); }));
367 }
368
convertFromHAL(const NnApiSupportLibrary * nnapi,const neuralnetworks::Model & model,std::vector<uint8_t> * copiedOperandValues,ErrorStatus * errorStatus)369 std::optional<ShimConvertedModel> convertFromHAL(const NnApiSupportLibrary* nnapi,
370 const neuralnetworks::Model& model,
371 std::vector<uint8_t>* copiedOperandValues,
372 ErrorStatus* errorStatus) {
373 CHECK(copiedOperandValues != nullptr);
374
375 *errorStatus = ErrorStatus::NONE;
376
377 // Using this pulls in OperationResolver and huge chunk of dependencies.
378 // TODO(172925288): Replace as followup work
379 // if (!::aidl::android::hardware::neuralnetworks::utils::valid(model)) {
380 if (!isValid(model)) {
381 LOG(ERROR) << "Invalid HAL model, failed to convert into SL model";
382 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
383 return std::nullopt;
384 }
385
386 std::vector<std::unique_ptr<::android::nn::sl_wrapper::Memory>> memoryPools;
387 memoryPools.reserve(model.pools.size());
388 for (const auto& pool : model.pools) {
389 std::unique_ptr<::android::nn::sl_wrapper::Memory> memory = convertFromHAL(nnapi, pool);
390 if (!memory) {
391 LOG(ERROR) << "Failed to convert HAL memory into SL memory";
392 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
393 return std::nullopt;
394 }
395 memoryPools.push_back(std::move(memory));
396 }
397
398 std::vector<std::optional<::android::nn::sl_wrapper::Model>> allModels(model.referenced.size() +
399 1);
400
401 if (needsCopiedOperandValues(model)) {
402 *copiedOperandValues = model.operandValues;
403 }
404
405 for (size_t i = 0; i < allModels.size(); ++i) {
406 if (convertSubgraphFromHAL(nnapi, memoryPools, model, &allModels, i, *copiedOperandValues,
407 errorStatus) == nullptr) {
408 LOG(ERROR) << "Failed to convert HAL subgraphs into SL subgraphs, index: " << i;
409 // Error status already set by convertSubgraphFromHAL
410 return std::nullopt;
411 }
412 }
413
414 std::vector<::android::nn::sl_wrapper::Model> result;
415 result.reserve(allModels.size());
416 for (size_t i = 0; i < allModels.size(); ++i) {
417 if (!allModels[i].has_value()) {
418 LOG(ERROR) << "Missing SL subgraph";
419 *errorStatus = ErrorStatus::INVALID_ARGUMENT;
420 return std::nullopt;
421 }
422 result.push_back(std::move(*allModels[i]));
423 }
424
425 return ShimConvertedModel{.memory = std::move(memoryPools), .models = std::move(result)};
426 }
427
convertFromHAL(const NnApiSupportLibrary * nnapi,const neuralnetworks::Memory & pool)428 std::unique_ptr<::android::nn::sl_wrapper::Memory> convertFromHAL(
429 const NnApiSupportLibrary* nnapi, const neuralnetworks::Memory& pool) {
430 using Tag = neuralnetworks::Memory::Tag;
431 switch (pool.getTag()) {
432 case Tag::ashmem: {
433 const auto& ashmem = pool.get<Tag::ashmem>();
434 size_t size = ashmem.size;
435 int fd = ashmem.fd.get();
436
437 auto memory = std::make_unique<::android::nn::sl_wrapper::Memory>(
438 nnapi, size, PROT_READ | PROT_WRITE, fd, 0, /*ownsFd=*/false);
439 if (!memory->isValid()) {
440 return nullptr;
441 }
442 return memory;
443 }
444 case Tag::mappableFile: {
445 const auto& mappableFile = pool.get<Tag::mappableFile>();
446 size_t size = mappableFile.length;
447 int fd = mappableFile.fd.get();
448 int prot = mappableFile.prot & (PROT_READ | PROT_WRITE);
449 size_t offset = mappableFile.offset;
450
451 auto memory = std::make_unique<::android::nn::sl_wrapper::Memory>(
452 nnapi, size, prot, fd, offset, /*ownsFd=*/false);
453 if (!memory->isValid()) {
454 return nullptr;
455 }
456 return memory;
457 }
458 case Tag::hardwareBuffer: {
459 const auto& hardwareBuffer = pool.get<Tag::hardwareBuffer>();
460
461 native_handle_t* handle = ::android::dupFromAidl(hardwareBuffer.handle);
462 if (handle == nullptr) {
463 LOG(ERROR) << "Dup of the hardware_buffer_blob memory pool failed";
464 return nullptr;
465 }
466 const auto handleGuard = ::android::base::make_scope_guard([handle] {
467 native_handle_close(handle);
468 native_handle_delete(handle);
469 });
470 for (size_t i = 0; i < handle->numFds; ++i) {
471 if (handle->data[i] == -1) {
472 LOG(ERROR) << "Dup of the hardware_buffer_blob memory pool failed";
473 return nullptr;
474 }
475 }
476
477 const AHardwareBuffer_Desc desc{
478 .width = static_cast<uint32_t>(hardwareBuffer.description.width),
479 .height = static_cast<uint32_t>(hardwareBuffer.description.height),
480 .layers = static_cast<uint32_t>(hardwareBuffer.description.layers),
481 .format = static_cast<uint32_t>(hardwareBuffer.description.format),
482 .usage = static_cast<uint64_t>(hardwareBuffer.description.usage),
483 .stride = static_cast<uint32_t>(hardwareBuffer.description.stride),
484 };
485 AHardwareBuffer* ahwb = nullptr;
486 const ::android::status_t status = AHardwareBuffer_createFromHandle(
487 &desc, handle, AHARDWAREBUFFER_CREATE_FROM_HANDLE_METHOD_CLONE, &ahwb);
488 if (status != ::android::NO_ERROR) {
489 LOG(ERROR) << "createFromHandle failed";
490 return nullptr;
491 }
492
493 const bool isBlob = desc.format == AHARDWAREBUFFER_FORMAT_BLOB;
494 const size_t size = isBlob ? desc.width : 0;
495
496 // Takes ownership of hardwareBuffer, handle gets closed
497 auto memory =
498 std::make_unique<::android::nn::sl_wrapper::Memory>(nnapi, ahwb,
499 /*ownAHB=*/true, size);
500 if (!memory->isValid()) {
501 return nullptr;
502 }
503 return memory;
504 }
505 }
506 LOG(ERROR) << "Can't convert to SL Memory, unknown pool tag: "
507 << utils::underlyingType(pool.getTag());
508 return nullptr;
509 }
510
511 } // namespace aidl::android::hardware::neuralnetworks
512