diff options
Diffstat (limited to 'ArmnnDriver.cpp')
-rw-r--r-- | ArmnnDriver.cpp | 429 |
1 files changed, 429 insertions, 0 deletions
diff --git a/ArmnnDriver.cpp b/ArmnnDriver.cpp new file mode 100644 index 00000000..914d6560 --- /dev/null +++ b/ArmnnDriver.cpp @@ -0,0 +1,429 @@ +// +// Copyright © 2017 Arm Ltd. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +#define LOG_TAG "ArmnnDriver" + +#include "ArmnnDriver.hpp" +#include "ArmnnPreparedModel.hpp" +#include "ModelToINetworkConverter.hpp" +#include "Utils.hpp" + +#include <log/log.h> +#include "SystemPropertiesUtils.hpp" + +#include "OperationsUtils.h" + +#include <boost/algorithm/string/predicate.hpp> +#include <boost/program_options.hpp> + +#include <cassert> +#include <functional> +#include <string> +#include <sstream> + +using namespace android; +using namespace std; + +namespace +{ + +const char *g_Float32PerformanceExecTimeName = "ArmNN.float32Performance.execTime"; +const char *g_Float32PerformancePowerUsageName = "ArmNN.float32Performance.powerUsage"; +const char *g_Quantized8PerformanceExecTimeName = "ArmNN.quantized8Performance.execTime"; +const char *g_Quantized8PerformancePowerUsageName = "ArmNN.quantized8Performance.powerUsage"; + +}; //namespace + +namespace armnn_driver +{ + +DriverOptions::DriverOptions(armnn::Compute computeDevice) +: m_ComputeDevice(computeDevice) +, m_VerboseLogging(false) +, m_UseAndroidNnCpuExecutor(false) +{ +} + +DriverOptions::DriverOptions(int argc, char** argv) +: m_ComputeDevice(armnn::Compute::GpuAcc) +, m_VerboseLogging(false) +, m_UseAndroidNnCpuExecutor(false) +, m_ClTunedParametersMode(armnn::IClTunedParameters::Mode::UseTunedParameters) +{ + namespace po = boost::program_options; + + std::string computeDeviceAsString; + std::string unsupportedOperationsAsString; + std::string clTunedParametersModeAsString; + + po::options_description optionsDesc("Options"); + optionsDesc.add_options() + ("compute,c", + po::value<std::string>(&computeDeviceAsString)->default_value("GpuAcc"), + "Which device to run layers on by default. Possible values are: CpuRef, CpuAcc, GpuAcc") + + ("verbose-logging,v", + po::bool_switch(&m_VerboseLogging), + "Turns verbose logging on") + + ("use-androidnn-cpu-executor,e", + po::bool_switch(&m_UseAndroidNnCpuExecutor), + "Forces the driver to satisfy requests via the Android-provided CpuExecutor") + + ("request-inputs-and-outputs-dump-dir,d", + po::value<std::string>(&m_RequestInputsAndOutputsDumpDir)->default_value(""), + "If non-empty, the directory where request inputs and outputs should be dumped") + + ("unsupported-operations,u", + po::value<std::string>(&unsupportedOperationsAsString)->default_value(""), + "If non-empty, a comma-separated list of operation indices which the driver will forcibly " + "consider unsupported") + + ("cl-tuned-parameters-file,t", + po::value<std::string>(&m_ClTunedParametersFile)->default_value(""), + "If non-empty, the given file will be used to load/save CL tuned parameters. " + "See also --cl-tuned-parameters-mode") + + ("cl-tuned-parameters-mode,m", + po::value<std::string>(&clTunedParametersModeAsString)->default_value("UseTunedParameters"), + "If 'UseTunedParameters' (the default), will read CL tuned parameters from the file specified by " + "--cl-tuned-parameters-file. " + "If 'UpdateTunedParameters', will also find the optimum parameters when preparing new networks and update " + "the file accordingly."); + + + po::variables_map variablesMap; + try + { + po::store(po::parse_command_line(argc, argv, optionsDesc), variablesMap); + po::notify(variablesMap); + } + catch (const po::error& e) + { + ALOGW("An error occurred attempting to parse program options: %s", e.what()); + } + + if (computeDeviceAsString == "CpuRef") + { + m_ComputeDevice = armnn::Compute::CpuRef; + } + else if (computeDeviceAsString == "GpuAcc") + { + m_ComputeDevice = armnn::Compute::GpuAcc; + } + else if (computeDeviceAsString == "CpuAcc") + { + m_ComputeDevice = armnn::Compute::CpuAcc; + } + else + { + ALOGW("Requested unknown compute device %s. Defaulting to compute id %s", + computeDeviceAsString.c_str(), GetComputeDeviceAsCString(m_ComputeDevice)); + } + + if (!unsupportedOperationsAsString.empty()) + { + std::istringstream argStream(unsupportedOperationsAsString); + + std::string s; + while (!argStream.eof()) + { + std::getline(argStream, s, ','); + try + { + unsigned int operationIdx = std::stoi(s); + m_ForcedUnsupportedOperations.insert(operationIdx); + } + catch (const std::invalid_argument&) + { + ALOGW("Ignoring invalid integer argument in -u/--unsupported-operations value: %s", s.c_str()); + } + } + } + + if (!m_ClTunedParametersFile.empty()) + { + // The mode is only relevant if the file path has been provided + if (clTunedParametersModeAsString == "UseTunedParameters") + { + m_ClTunedParametersMode = armnn::IClTunedParameters::Mode::UseTunedParameters; + } + else if (clTunedParametersModeAsString == "UpdateTunedParameters") + { + m_ClTunedParametersMode = armnn::IClTunedParameters::Mode::UpdateTunedParameters; + } + else + { + ALOGW("Requested unknown cl-tuned-parameters-mode '%s'. Defaulting to UseTunedParameters", + clTunedParametersModeAsString.c_str()); + } + } +} + +ArmnnDriver::ArmnnDriver(DriverOptions options) + : m_Runtime(nullptr, nullptr) + , m_ClTunedParameters(nullptr, nullptr) + , m_Options(std::move(options)) +{ + ALOGV("ArmnnDriver::ArmnnDriver()"); + + armnn::ConfigureLogging(false, m_Options.IsVerboseLoggingEnabled(), armnn::LogSeverity::Trace); + if (m_Options.IsVerboseLoggingEnabled()) + { + SetMinimumLogSeverity(base::VERBOSE); + } + else + { + SetMinimumLogSeverity(base::INFO); + } + + try + { + armnn::IRuntime::CreationOptions options(m_Options.GetComputeDevice()); + options.m_UseCpuRefAsFallback = false; + if (!m_Options.GetClTunedParametersFile().empty()) + { + m_ClTunedParameters = armnn::IClTunedParameters::Create(m_Options.GetClTunedParametersMode()); + try + { + m_ClTunedParameters->Load(m_Options.GetClTunedParametersFile().c_str()); + } + catch (const armnn::Exception& error) + { + // This is only a warning because the file won't exist the first time you are generating it. + ALOGW("ArmnnDriver: Failed to load CL tuned parameters file '%s': %s", + m_Options.GetClTunedParametersFile().c_str(), error.what()); + } + options.m_ClTunedParameters = m_ClTunedParameters.get(); + } + m_Runtime = armnn::IRuntime::Create(options); + } + catch (const armnn::ClRuntimeUnavailableException& error) + { + ALOGE("ArmnnDriver: Failed to setup CL runtime: %s. Device will be unavailable.", error.what()); + } +} + +Return<void> ArmnnDriver::getCapabilities(getCapabilities_cb cb) +{ + ALOGV("ArmnnDriver::getCapabilities()"); + + Capabilities capabilities; + if (m_Runtime) + { + capabilities.float32Performance.execTime = + ParseSystemProperty(g_Float32PerformanceExecTimeName, 1.0f); + + capabilities.float32Performance.powerUsage = + ParseSystemProperty(g_Float32PerformancePowerUsageName, 1.0f); + + capabilities.quantized8Performance.execTime = + ParseSystemProperty(g_Quantized8PerformanceExecTimeName, 1.0f); + + capabilities.quantized8Performance.powerUsage = + ParseSystemProperty(g_Quantized8PerformancePowerUsageName, 1.0f); + + cb(ErrorStatus::NONE, capabilities); + } + else + { + capabilities.float32Performance.execTime = 0; + capabilities.float32Performance.powerUsage = 0; + capabilities.quantized8Performance.execTime = 0; + capabilities.quantized8Performance.powerUsage = 0; + + cb(ErrorStatus::DEVICE_UNAVAILABLE, capabilities); + } + + return Void(); +} + +Return<void> ArmnnDriver::getSupportedOperations(const Model& model, getSupportedOperations_cb cb) +{ + ALOGV("ArmnnDriver::getSupportedOperations()"); + + std::vector<bool> result; + + if (!m_Runtime) + { + cb(ErrorStatus::DEVICE_UNAVAILABLE, result); + return Void(); + } + + // Run general model validation, if this doesn't pass we shouldn't analyse the model anyway + if (!android::nn::validateModel(model)) + { + cb(ErrorStatus::INVALID_ARGUMENT, result); + return Void(); + } + + // Attempt to convert the model to an ArmNN input network (INetwork). + ModelToINetworkConverter modelConverter(m_Runtime->GetDeviceSpec().DefaultComputeDevice, model, + m_Options.GetForcedUnsupportedOperations()); + + if (modelConverter.GetConversionResult() != ConversionResult::Success + && modelConverter.GetConversionResult() != ConversionResult::UnsupportedFeature) + { + cb(ErrorStatus::GENERAL_FAILURE, result); + return Void(); + } + + // Check each operation if it was converted successfully and copy the flags + // into the result (vector<bool>) that we need to return to Android + result.reserve(model.operations.size()); + for (uint32_t operationIdx = 0; operationIdx < model.operations.size(); operationIdx++) + { + bool operationSupported = modelConverter.IsOperationSupported(operationIdx); + result.push_back(operationSupported); + } + + cb(ErrorStatus::NONE, result); + return Void(); +} + +namespace +{ + +void NotifyCallbackAndCheck(const sp<IPreparedModelCallback>& callback, ErrorStatus errorStatus, + const ::android::sp<IPreparedModel>& preparedModelPtr) +{ + Return<void> returned = callback->notify(errorStatus, preparedModelPtr); + // This check is required, if the callback fails and it isn't checked it will bring down the service + if (!returned.isOk()) + { + ALOGE("ArmnnDriver::prepareModel: hidl callback failed to return properly: %s ", + returned.description().c_str()); + } +} + +Return<ErrorStatus> FailPrepareModel(ErrorStatus error, + const std::string& message, + const sp<IPreparedModelCallback>& callback) +{ + ALOGW("ArmnnDriver::prepareModel: %s", message.c_str()); + NotifyCallbackAndCheck(callback, error, nullptr); + return error; +} + +} + +Return<ErrorStatus> ArmnnDriver::prepareModel(const Model& model, + const sp<IPreparedModelCallback>& cb) +{ + ALOGV("ArmnnDriver::prepareModel()"); + + if (cb.get() == nullptr) + { + ALOGW("ArmnnDriver::prepareModel: Invalid callback passed to prepareModel"); + return ErrorStatus::INVALID_ARGUMENT; + } + + if (!m_Runtime) + { + return FailPrepareModel(ErrorStatus::DEVICE_UNAVAILABLE, "ArmnnDriver::prepareModel: Device unavailable", cb); + } + + if (!android::nn::validateModel(model)) + { + return FailPrepareModel(ErrorStatus::INVALID_ARGUMENT, + "ArmnnDriver::prepareModel: Invalid model passed as input", cb); + } + + if (m_Options.UseAndroidNnCpuExecutor()) + { + sp<AndroidNnCpuExecutorPreparedModel> preparedModel = new AndroidNnCpuExecutorPreparedModel(model, + m_Options.GetRequestInputsAndOutputsDumpDir()); + if (preparedModel->Initialize()) + { + NotifyCallbackAndCheck(cb, ErrorStatus::NONE, preparedModel); + return ErrorStatus::NONE; + } + else + { + NotifyCallbackAndCheck(cb, ErrorStatus::INVALID_ARGUMENT, preparedModel); + return ErrorStatus::INVALID_ARGUMENT; + } + } + + // Deliberately ignore any unsupported operations requested by the options - + // at this point we're being asked to prepare a model that we've already declared support for + // and the operation indices may be different to those in getSupportedOperations anyway. + std::set<unsigned int> unsupportedOperations; + ModelToINetworkConverter modelConverter(m_Runtime->GetDeviceSpec().DefaultComputeDevice, model, + unsupportedOperations); + + if (modelConverter.GetConversionResult() != ConversionResult::Success) + { + return FailPrepareModel(ErrorStatus::GENERAL_FAILURE, "ModelToINetworkConverter failed", cb); + } + + // optimize the network + armnn::IOptimizedNetworkPtr optNet(nullptr, nullptr); + try + { + optNet = armnn::Optimize(*modelConverter.GetINetwork(), m_Runtime->GetDeviceSpec()); + } + catch (armnn::Exception& e) + { + std::stringstream message; + message << "armnn::Exception ("<<e.what()<<") caught from optimize."; + return FailPrepareModel(ErrorStatus::GENERAL_FAILURE, message.str(), cb); + } + + // load it into the runtime + armnn::NetworkId netId = 0; + try + { + if (m_Runtime->LoadNetwork(netId, std::move(optNet)) != armnn::Status::Success) + { + return FailPrepareModel(ErrorStatus::GENERAL_FAILURE, + "ArmnnDriver::prepareModel: Network could not be loaded", cb); + } + } + catch (armnn::Exception& e) + { + std::stringstream message; + message << "armnn::Exception (" << e.what()<< ") caught from LoadNetwork."; + return FailPrepareModel(ErrorStatus::GENERAL_FAILURE, message.str(), cb); + } + + std::unique_ptr<ArmnnPreparedModel> preparedModel(new ArmnnPreparedModel( + netId, + m_Runtime.get(), + model, + m_Options.GetRequestInputsAndOutputsDumpDir() + )); + + // Run a single 'dummy' inference of the model. This means that CL kernels will get compiled (and tuned if + // this is enabled) before the first 'real' inference which removes the overhead of the first inference. + preparedModel->ExecuteWithDummyInputs(); + + if (m_ClTunedParameters && + m_Options.GetClTunedParametersMode() == armnn::IClTunedParameters::Mode::UpdateTunedParameters) + { + // Now that we've done one inference the CL kernel parameters will have been tuned, so save the updated file. + try + { + m_ClTunedParameters->Save(m_Options.GetClTunedParametersFile().c_str()); + } + catch (const armnn::Exception& error) + { + ALOGE("ArmnnDriver: Failed to save CL tuned parameters file '%s': %s", + m_Options.GetClTunedParametersFile().c_str(), error.what()); + } + } + + NotifyCallbackAndCheck(cb, ErrorStatus::NONE, preparedModel.release()); + + return ErrorStatus::NONE; +} + +Return<DeviceStatus> ArmnnDriver::getStatus() +{ + ALOGV("ArmnnDriver::getStatus()"); + return DeviceStatus::AVAILABLE; +} + +} |