You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

259 lines
9.6 KiB

/*
* 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 "CanonicalDevice.h"
#include <Tracing.h>
#include <android-base/logging.h>
#include <nnapi/IBuffer.h>
#include <nnapi/IDevice.h>
#include <nnapi/IPreparedModel.h>
#include <nnapi/OperandTypes.h>
#include <nnapi/Result.h>
#include <nnapi/Types.h>
#include <nnapi/Validation.h>
#include <algorithm>
#include <any>
#include <functional>
#include <iterator>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "CanonicalBuffer.h"
#include "CanonicalPreparedModel.h"
namespace android::nn::sample {
namespace {
Capabilities makeCapabilities() {
constexpr float kPerf = 1.0f;
const Capabilities::PerformanceInfo kPerfInfo = {.execTime = kPerf, .powerUsage = kPerf};
constexpr OperandType kOperandsTypes[] = {
OperandType::FLOAT32,
OperandType::INT32,
OperandType::UINT32,
OperandType::TENSOR_FLOAT32,
OperandType::TENSOR_INT32,
OperandType::TENSOR_QUANT8_ASYMM,
OperandType::BOOL,
OperandType::TENSOR_QUANT16_SYMM,
OperandType::TENSOR_FLOAT16,
OperandType::TENSOR_BOOL8,
OperandType::FLOAT16,
OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL,
OperandType::TENSOR_QUANT16_ASYMM,
OperandType::TENSOR_QUANT8_SYMM,
OperandType::TENSOR_QUANT8_ASYMM_SIGNED,
};
std::vector<Capabilities::OperandPerformance> operandPerformance;
operandPerformance.reserve(std::size(kOperandsTypes));
std::transform(std::begin(kOperandsTypes), std::end(kOperandsTypes),
std::back_inserter(operandPerformance), [kPerfInfo](OperandType op) {
return Capabilities::OperandPerformance{.type = op, .info = kPerfInfo};
});
auto table =
Capabilities::OperandPerformanceTable::create(std::move(operandPerformance)).value();
return {.relaxedFloat32toFloat16PerformanceScalar = kPerfInfo,
.relaxedFloat32toFloat16PerformanceTensor = kPerfInfo,
.operandPerformance = std::move(table),
.ifPerformance = kPerfInfo,
.whilePerformance = kPerfInfo};
}
std::string toString(const Dimensions& dimensions) {
std::ostringstream oss;
oss << "[";
for (size_t i = 0; i < dimensions.size(); ++i) {
if (i != 0) oss << ", ";
oss << dimensions[i];
}
oss << "]";
return oss.str();
}
} // namespace
Device::Device(std::string name, const IOperationResolver* operationResolver)
: kName(std::move(name)), kOperationResolver(*operationResolver) {
CHECK(operationResolver != nullptr);
initVLogMask();
}
const std::string& Device::getName() const {
return kName;
}
const std::string& Device::getVersionString() const {
static const std::string kVersionString = "JUST_AN_EXAMPLE";
return kVersionString;
}
Version Device::getFeatureLevel() const {
return Version::ANDROID_S;
}
DeviceType Device::getType() const {
return DeviceType::CPU;
}
const std::vector<Extension>& Device::getSupportedExtensions() const {
static const std::vector<Extension> kExtensions = {/* No extensions. */};
return kExtensions;
}
const Capabilities& Device::getCapabilities() const {
static const Capabilities kCapabilities = makeCapabilities();
return kCapabilities;
}
std::pair<uint32_t, uint32_t> Device::getNumberOfCacheFilesNeeded() const {
return std::make_pair(/*numModelCache=*/0, /*numDataCache=*/0);
}
GeneralResult<void> Device::wait() const {
return {};
}
GeneralResult<std::vector<bool>> Device::getSupportedOperations(const Model& model) const {
VLOG(DRIVER) << "sample::Device::getSupportedOperations";
// Validate arguments.
if (const auto result = validate(model); !result.ok()) {
return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << result.error();
}
// Mark all operations except extension operations as supported.
std::vector<bool> supported;
supported.reserve(model.main.operations.size());
std::transform(model.main.operations.begin(), model.main.operations.end(),
std::back_inserter(supported), [](const Operation& operation) {
return !isExtensionOperationType(operation.type) &&
operation.type != OperationType::OEM_OPERATION;
});
return supported;
}
GeneralResult<SharedPreparedModel> Device::prepareModel(
const Model& model, ExecutionPreference preference, Priority priority,
OptionalTimePoint deadline, const std::vector<SharedHandle>& /*modelCache*/,
const std::vector<SharedHandle>& /*dataCache*/, const CacheToken& /*token*/) const {
if (VLOG_IS_ON(DRIVER)) {
VLOG(DRIVER) << "sample::Device::prepareModel";
logModelToInfo(model);
}
// Validate arguments.
if (const auto result = validate(model); !result.ok()) {
return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << "Invalid Model: " << result.error();
}
if (const auto result = validate(preference); !result.ok()) {
return NN_ERROR(ErrorStatus::INVALID_ARGUMENT)
<< "Invalid ExecutionPreference: " << result.error();
}
if (const auto result = validate(priority); !result.ok()) {
return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << "Invalid Priority: " << result.error();
}
// Check if deadline has passed.
if (hasDeadlinePassed(deadline)) {
return NN_ERROR(ErrorStatus::MISSED_DEADLINE_PERSISTENT);
}
std::vector<RunTimePoolInfo> poolInfos;
if (!setRunTimePoolInfosFromCanonicalMemories(&poolInfos, model.pools)) {
return NN_ERROR() << "setRunTimePoolInfosFromCanonicalMemories failed";
}
// Create the prepared model.
return std::make_shared<const PreparedModel>(model, preference, priority, &kOperationResolver,
kBufferTracker, std::move(poolInfos));
}
GeneralResult<SharedPreparedModel> Device::prepareModelFromCache(
OptionalTimePoint /*deadline*/, const std::vector<SharedHandle>& /*modelCache*/,
const std::vector<SharedHandle>& /*dataCache*/, const CacheToken& /*token*/) const {
NNTRACE_FULL(NNTRACE_LAYER_DRIVER, NNTRACE_PHASE_COMPILATION,
"sample::Device::prepareModelFromCache");
return NN_ERROR(ErrorStatus::GENERAL_FAILURE)
<< "prepareModelFromCache not supported on sample::Device::prepareModelFromCache("
<< kName << ")";
}
GeneralResult<SharedBuffer> Device::allocate(const BufferDesc& desc,
const std::vector<SharedPreparedModel>& preparedModels,
const std::vector<BufferRole>& inputRoles,
const std::vector<BufferRole>& outputRoles) const {
VLOG(DRIVER) << "sample::Device::allocate";
std::set<PreparedModelRole> roles;
Operand operand;
auto getModel = [](const SharedPreparedModel& preparedModel) -> const Model* {
std::any resource = preparedModel->getUnderlyingResource();
const Model** maybeModel = std::any_cast<const Model*>(&resource);
if (maybeModel == nullptr) {
LOG(ERROR) << "sample::Device::allocate -- unknown remote IPreparedModel.";
return nullptr;
}
return *maybeModel;
};
if (const auto result = validateMemoryDesc(desc, preparedModels, inputRoles, outputRoles,
getModel, &roles, &operand);
!result.ok()) {
return NN_ERROR(ErrorStatus::INVALID_ARGUMENT)
<< "sample::Device::allocate -- validation failed: " << result.error();
}
if (isExtensionOperandType(operand.type)) {
return NN_ERROR(ErrorStatus::GENERAL_FAILURE)
<< "sample::Device::allocate -- does not support extension type.";
}
// TODO(xusongw): Support allocating buffers with unknown dimensions or rank.
uint32_t size = nonExtensionOperandSizeOfData(operand.type, operand.dimensions);
VLOG(DRIVER) << "sample::Device::allocate -- type = " << operand.type
<< ", dimensions = " << toString(operand.dimensions) << ", size = " << size;
if (size == 0) {
return NN_ERROR(ErrorStatus::GENERAL_FAILURE)
<< "sample::Device::allocate -- does not support dynamic output shape.";
}
auto bufferWrapper = ManagedBuffer::create(size, std::move(roles), operand);
if (bufferWrapper == nullptr) {
return NN_ERROR(ErrorStatus::GENERAL_FAILURE)
<< "sample::Device::allocate -- not enough memory.";
}
auto token = kBufferTracker->add(bufferWrapper);
if (token == nullptr) {
return NN_ERROR(ErrorStatus::GENERAL_FAILURE)
<< "sample::Device::allocate -- BufferTracker returned invalid token.";
}
auto sampleBuffer = std::make_shared<const Buffer>(std::move(bufferWrapper), std::move(token));
VLOG(DRIVER) << "sample::Device::allocate -- successfully allocates the requested memory";
return sampleBuffer;
}
} // namespace android::nn::sample