Lev Proleev | b38bb4f | 2020-12-15 19:25:32 +0000 | [diff] [blame] | 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 | #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 | |
| 35 | namespace aidl::android::hardware::neuralnetworks { |
| 36 | |
| 37 | using test_helper::TestBuffer; |
| 38 | using test_helper::TestModel; |
| 39 | |
| 40 | uint32_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 | |
| 68 | static 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 | |
| 94 | uint32_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 | |
| 101 | std::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 | |
| 106 | void 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 | |
| 117 | std::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 | |
| 122 | void 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 Butler | bbe43d9 | 2021-02-08 00:05:07 -0800 | [diff] [blame] | 138 | const auto sharedMemory = |
| 139 | nn::createSharedMemoryFromAHWB(mAhwb, /*takeOwnership=*/false).value(); |
Lev Proleev | b38bb4f | 2020-12-15 19:25:32 +0000 | [diff] [blame] | 140 | 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 | |
| 148 | TestBlobAHWB::~TestBlobAHWB() { |
| 149 | if (mAhwb) { |
| 150 | AHardwareBuffer_unlock(mAhwb, nullptr); |
| 151 | AHardwareBuffer_release(mAhwb); |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | std::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 | |
| 166 | Request 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 | |
| 243 | std::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 |