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