Release 18.02

Change-Id: I41a89c149534a7c354a58e2c66a32cba572fc0c1
diff --git a/ArmnnDriver.cpp b/ArmnnDriver.cpp
new file mode 100644
index 0000000..914d656
--- /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;
+}
+
+}