/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "Utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace aidl::android::hardware::neuralnetworks { using test_helper::TestBuffer; using test_helper::TestModel; uint32_t sizeOfData(OperandType type) { switch (type) { case OperandType::FLOAT32: case OperandType::INT32: case OperandType::UINT32: case OperandType::TENSOR_FLOAT32: case OperandType::TENSOR_INT32: return 4; case OperandType::TENSOR_QUANT16_SYMM: case OperandType::TENSOR_FLOAT16: case OperandType::FLOAT16: case OperandType::TENSOR_QUANT16_ASYMM: return 2; case OperandType::TENSOR_QUANT8_ASYMM: case OperandType::BOOL: case OperandType::TENSOR_BOOL8: case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL: case OperandType::TENSOR_QUANT8_SYMM: case OperandType::TENSOR_QUANT8_ASYMM_SIGNED: return 1; case OperandType::SUBGRAPH: return 0; default: CHECK(false) << "Invalid OperandType " << static_cast(type); return 0; } } static bool isTensor(OperandType type) { switch (type) { case OperandType::FLOAT32: case OperandType::INT32: case OperandType::UINT32: case OperandType::FLOAT16: case OperandType::BOOL: case OperandType::SUBGRAPH: return false; case OperandType::TENSOR_FLOAT32: case OperandType::TENSOR_INT32: case OperandType::TENSOR_QUANT16_SYMM: case OperandType::TENSOR_FLOAT16: case OperandType::TENSOR_QUANT16_ASYMM: case OperandType::TENSOR_QUANT8_ASYMM: case OperandType::TENSOR_BOOL8: case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL: case OperandType::TENSOR_QUANT8_SYMM: case OperandType::TENSOR_QUANT8_ASYMM_SIGNED: return true; default: CHECK(false) << "Invalid OperandType " << static_cast(type); return false; } } uint32_t sizeOfData(const Operand& operand) { const uint32_t dataSize = sizeOfData(operand.type); if (isTensor(operand.type) && operand.dimensions.size() == 0) return 0; return std::accumulate(operand.dimensions.begin(), operand.dimensions.end(), dataSize, std::multiplies<>{}); } std::unique_ptr TestAshmem::create(uint32_t size, bool aidlReadonly) { auto ashmem = std::make_unique(size, aidlReadonly); return ashmem->mIsValid ? std::move(ashmem) : nullptr; } // This function will create a readonly shared memory with PROT_READ only. // The input shared memory must be either Ashmem or mapped-FD. static nn::SharedMemory convertSharedMemoryToReadonly(const nn::SharedMemory& sharedMemory) { if (std::holds_alternative(sharedMemory->handle)) { const auto& memory = std::get(sharedMemory->handle); return nn::createSharedMemoryFromFd(memory.size, PROT_READ, memory.fd.get(), /*offset=*/0) .value(); } else if (std::holds_alternative(sharedMemory->handle)) { const auto& memory = std::get(sharedMemory->handle); return nn::createSharedMemoryFromFd(memory.size, PROT_READ, memory.fd.get(), memory.offset) .value(); } CHECK(false) << "Unexpected shared memory type"; return sharedMemory; } void TestAshmem::initialize(uint32_t size, bool aidlReadonly) { mIsValid = false; ASSERT_GT(size, 0); const auto sharedMemory = nn::createSharedMemory(size).value(); mMappedMemory = nn::map(sharedMemory).value(); mPtr = static_cast(std::get(mMappedMemory.pointer)); CHECK_NE(mPtr, nullptr); if (aidlReadonly) { mAidlMemory = utils::convert(convertSharedMemoryToReadonly(sharedMemory)).value(); } else { mAidlMemory = utils::convert(sharedMemory).value(); } mIsValid = true; } std::unique_ptr TestBlobAHWB::create(uint32_t size) { auto ahwb = std::make_unique(size); return ahwb->mIsValid ? std::move(ahwb) : nullptr; } void TestBlobAHWB::initialize(uint32_t size) { mIsValid = false; ASSERT_GT(size, 0); const auto usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN; const AHardwareBuffer_Desc desc = { .width = size, .height = 1, .layers = 1, .format = AHARDWAREBUFFER_FORMAT_BLOB, .usage = usage, .stride = size, }; ASSERT_EQ(AHardwareBuffer_allocate(&desc, &mAhwb), 0); ASSERT_NE(mAhwb, nullptr); const auto sharedMemory = nn::createSharedMemoryFromAHWB(mAhwb, /*takeOwnership=*/false).value(); mMapping = nn::map(sharedMemory).value(); mPtr = static_cast(std::get(mMapping.pointer)); CHECK_NE(mPtr, nullptr); mAidlMemory = utils::convert(sharedMemory).value(); mIsValid = true; } TestBlobAHWB::~TestBlobAHWB() { if (mAhwb) { AHardwareBuffer_unlock(mAhwb, nullptr); AHardwareBuffer_release(mAhwb); } } std::string gtestCompliantName(std::string name) { // gtest test names must only contain alphanumeric characters std::replace_if( name.begin(), name.end(), [](char c) { return !std::isalnum(c); }, '_'); return name; } ::std::ostream& operator<<(::std::ostream& os, ErrorStatus errorStatus) { return os << toString(errorStatus); } Request ExecutionContext::createRequest(const TestModel& testModel, MemoryType memoryType) { CHECK(memoryType == MemoryType::ASHMEM || memoryType == MemoryType::BLOB_AHWB); // Model inputs. std::vector inputs(testModel.main.inputIndexes.size()); size_t inputSize = 0; for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) { const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]]; if (op.data.size() == 0) { // Omitted input. inputs[i] = {.hasNoValue = true}; } else { DataLocation loc = {.poolIndex = kInputPoolIndex, .offset = static_cast(inputSize), .length = static_cast(op.data.size())}; inputSize += op.data.alignedSize(); inputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}}; } } // Model outputs. std::vector outputs(testModel.main.outputIndexes.size()); size_t outputSize = 0; for (uint32_t i = 0; i < testModel.main.outputIndexes.size(); i++) { const auto& op = testModel.main.operands[testModel.main.outputIndexes[i]]; // In the case of zero-sized output, we should at least provide a one-byte buffer. // This is because zero-sized tensors are only supported internally to the driver, or // reported in output shapes. It is illegal for the client to pre-specify a zero-sized // tensor as model output. Otherwise, we will have two semantic conflicts: // - "Zero dimension" conflicts with "unspecified dimension". // - "Omitted operand buffer" conflicts with "zero-sized operand buffer". size_t bufferSize = std::max(op.data.size(), 1); DataLocation loc = {.poolIndex = kOutputPoolIndex, .offset = static_cast(outputSize), .length = static_cast(bufferSize)}; outputSize += op.data.size() == 0 ? TestBuffer::kAlignment : op.data.alignedSize(); outputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}}; } // Allocate memory pools. if (memoryType == MemoryType::ASHMEM) { mInputMemory = TestAshmem::create(inputSize); mOutputMemory = TestAshmem::create(outputSize); } else { mInputMemory = TestBlobAHWB::create(inputSize); mOutputMemory = TestBlobAHWB::create(outputSize); } CHECK_NE(mInputMemory, nullptr); CHECK_NE(mOutputMemory, nullptr); auto copiedInputMemory = utils::clone(*mInputMemory->getAidlMemory()); CHECK(copiedInputMemory.has_value()) << copiedInputMemory.error().message; auto copiedOutputMemory = utils::clone(*mOutputMemory->getAidlMemory()); CHECK(copiedOutputMemory.has_value()) << copiedOutputMemory.error().message; std::vector pools; pools.push_back(RequestMemoryPool::make( std::move(copiedInputMemory).value())); pools.push_back(RequestMemoryPool::make( std::move(copiedOutputMemory).value())); // Copy input data to the memory pool. uint8_t* inputPtr = mInputMemory->getPointer(); for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) { const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]]; if (op.data.size() > 0) { const uint8_t* begin = op.data.get(); const uint8_t* end = begin + op.data.size(); std::copy(begin, end, inputPtr + inputs[i].location.offset); } } return {.inputs = std::move(inputs), .outputs = std::move(outputs), .pools = std::move(pools)}; } std::vector ExecutionContext::getOutputBuffers(const Request& request) const { // Copy out output results. uint8_t* outputPtr = mOutputMemory->getPointer(); std::vector outputBuffers; for (const auto& output : request.outputs) { outputBuffers.emplace_back(output.location.length, outputPtr + output.location.offset); } return outputBuffers; } } // namespace aidl::android::hardware::neuralnetworks