blob: 1962464922f7ef20a773f7402961bb55a645f995 [file] [log] [blame]
telsoa015307bc12018-03-09 13:51:08 +00001//
2// Copyright © 2017 Arm Ltd. All rights reserved.
3// See LICENSE file in the project root for full license information.
4//
5
6#define LOG_TAG "ArmnnDriver"
7
8#include "ArmnnDriver.hpp"
9#include "ArmnnPreparedModel.hpp"
10#include "ModelToINetworkConverter.hpp"
11#include "Utils.hpp"
12
13#include <log/log.h>
14#include "SystemPropertiesUtils.hpp"
15
16#include "OperationsUtils.h"
17
18#include <boost/algorithm/string/predicate.hpp>
19#include <boost/program_options.hpp>
20
21#include <cassert>
22#include <functional>
23#include <string>
24#include <sstream>
25
26using namespace android;
27using namespace std;
28
29namespace
30{
31
32const char *g_Float32PerformanceExecTimeName = "ArmNN.float32Performance.execTime";
33const char *g_Float32PerformancePowerUsageName = "ArmNN.float32Performance.powerUsage";
34const char *g_Quantized8PerformanceExecTimeName = "ArmNN.quantized8Performance.execTime";
35const char *g_Quantized8PerformancePowerUsageName = "ArmNN.quantized8Performance.powerUsage";
36
37}; //namespace
38
39namespace armnn_driver
40{
41
42DriverOptions::DriverOptions(armnn::Compute computeDevice)
43: m_ComputeDevice(computeDevice)
44, m_VerboseLogging(false)
45, m_UseAndroidNnCpuExecutor(false)
46{
47}
48
49DriverOptions::DriverOptions(int argc, char** argv)
50: m_ComputeDevice(armnn::Compute::GpuAcc)
51, m_VerboseLogging(false)
52, m_UseAndroidNnCpuExecutor(false)
53, m_ClTunedParametersMode(armnn::IClTunedParameters::Mode::UseTunedParameters)
54{
55 namespace po = boost::program_options;
56
57 std::string computeDeviceAsString;
58 std::string unsupportedOperationsAsString;
59 std::string clTunedParametersModeAsString;
60
61 po::options_description optionsDesc("Options");
62 optionsDesc.add_options()
63 ("compute,c",
64 po::value<std::string>(&computeDeviceAsString)->default_value("GpuAcc"),
65 "Which device to run layers on by default. Possible values are: CpuRef, CpuAcc, GpuAcc")
66
67 ("verbose-logging,v",
68 po::bool_switch(&m_VerboseLogging),
69 "Turns verbose logging on")
70
71 ("use-androidnn-cpu-executor,e",
72 po::bool_switch(&m_UseAndroidNnCpuExecutor),
73 "Forces the driver to satisfy requests via the Android-provided CpuExecutor")
74
75 ("request-inputs-and-outputs-dump-dir,d",
76 po::value<std::string>(&m_RequestInputsAndOutputsDumpDir)->default_value(""),
77 "If non-empty, the directory where request inputs and outputs should be dumped")
78
79 ("unsupported-operations,u",
80 po::value<std::string>(&unsupportedOperationsAsString)->default_value(""),
81 "If non-empty, a comma-separated list of operation indices which the driver will forcibly "
82 "consider unsupported")
83
84 ("cl-tuned-parameters-file,t",
85 po::value<std::string>(&m_ClTunedParametersFile)->default_value(""),
86 "If non-empty, the given file will be used to load/save CL tuned parameters. "
87 "See also --cl-tuned-parameters-mode")
88
89 ("cl-tuned-parameters-mode,m",
90 po::value<std::string>(&clTunedParametersModeAsString)->default_value("UseTunedParameters"),
91 "If 'UseTunedParameters' (the default), will read CL tuned parameters from the file specified by "
92 "--cl-tuned-parameters-file. "
93 "If 'UpdateTunedParameters', will also find the optimum parameters when preparing new networks and update "
94 "the file accordingly.");
95
96
97 po::variables_map variablesMap;
98 try
99 {
100 po::store(po::parse_command_line(argc, argv, optionsDesc), variablesMap);
101 po::notify(variablesMap);
102 }
103 catch (const po::error& e)
104 {
105 ALOGW("An error occurred attempting to parse program options: %s", e.what());
106 }
107
108 if (computeDeviceAsString == "CpuRef")
109 {
110 m_ComputeDevice = armnn::Compute::CpuRef;
111 }
112 else if (computeDeviceAsString == "GpuAcc")
113 {
114 m_ComputeDevice = armnn::Compute::GpuAcc;
115 }
116 else if (computeDeviceAsString == "CpuAcc")
117 {
118 m_ComputeDevice = armnn::Compute::CpuAcc;
119 }
120 else
121 {
122 ALOGW("Requested unknown compute device %s. Defaulting to compute id %s",
123 computeDeviceAsString.c_str(), GetComputeDeviceAsCString(m_ComputeDevice));
124 }
125
126 if (!unsupportedOperationsAsString.empty())
127 {
128 std::istringstream argStream(unsupportedOperationsAsString);
129
130 std::string s;
131 while (!argStream.eof())
132 {
133 std::getline(argStream, s, ',');
134 try
135 {
136 unsigned int operationIdx = std::stoi(s);
137 m_ForcedUnsupportedOperations.insert(operationIdx);
138 }
139 catch (const std::invalid_argument&)
140 {
141 ALOGW("Ignoring invalid integer argument in -u/--unsupported-operations value: %s", s.c_str());
142 }
143 }
144 }
145
146 if (!m_ClTunedParametersFile.empty())
147 {
148 // The mode is only relevant if the file path has been provided
149 if (clTunedParametersModeAsString == "UseTunedParameters")
150 {
151 m_ClTunedParametersMode = armnn::IClTunedParameters::Mode::UseTunedParameters;
152 }
153 else if (clTunedParametersModeAsString == "UpdateTunedParameters")
154 {
155 m_ClTunedParametersMode = armnn::IClTunedParameters::Mode::UpdateTunedParameters;
156 }
157 else
158 {
159 ALOGW("Requested unknown cl-tuned-parameters-mode '%s'. Defaulting to UseTunedParameters",
160 clTunedParametersModeAsString.c_str());
161 }
162 }
163}
164
165ArmnnDriver::ArmnnDriver(DriverOptions options)
166 : m_Runtime(nullptr, nullptr)
167 , m_ClTunedParameters(nullptr, nullptr)
168 , m_Options(std::move(options))
169{
170 ALOGV("ArmnnDriver::ArmnnDriver()");
171
172 armnn::ConfigureLogging(false, m_Options.IsVerboseLoggingEnabled(), armnn::LogSeverity::Trace);
173 if (m_Options.IsVerboseLoggingEnabled())
174 {
175 SetMinimumLogSeverity(base::VERBOSE);
176 }
177 else
178 {
179 SetMinimumLogSeverity(base::INFO);
180 }
181
182 try
183 {
184 armnn::IRuntime::CreationOptions options(m_Options.GetComputeDevice());
185 options.m_UseCpuRefAsFallback = false;
186 if (!m_Options.GetClTunedParametersFile().empty())
187 {
188 m_ClTunedParameters = armnn::IClTunedParameters::Create(m_Options.GetClTunedParametersMode());
189 try
190 {
191 m_ClTunedParameters->Load(m_Options.GetClTunedParametersFile().c_str());
192 }
193 catch (const armnn::Exception& error)
194 {
195 // This is only a warning because the file won't exist the first time you are generating it.
196 ALOGW("ArmnnDriver: Failed to load CL tuned parameters file '%s': %s",
197 m_Options.GetClTunedParametersFile().c_str(), error.what());
198 }
199 options.m_ClTunedParameters = m_ClTunedParameters.get();
200 }
201 m_Runtime = armnn::IRuntime::Create(options);
202 }
203 catch (const armnn::ClRuntimeUnavailableException& error)
204 {
205 ALOGE("ArmnnDriver: Failed to setup CL runtime: %s. Device will be unavailable.", error.what());
206 }
207}
208
209Return<void> ArmnnDriver::getCapabilities(getCapabilities_cb cb)
210{
211 ALOGV("ArmnnDriver::getCapabilities()");
212
213 Capabilities capabilities;
214 if (m_Runtime)
215 {
216 capabilities.float32Performance.execTime =
217 ParseSystemProperty(g_Float32PerformanceExecTimeName, 1.0f);
218
219 capabilities.float32Performance.powerUsage =
220 ParseSystemProperty(g_Float32PerformancePowerUsageName, 1.0f);
221
222 capabilities.quantized8Performance.execTime =
223 ParseSystemProperty(g_Quantized8PerformanceExecTimeName, 1.0f);
224
225 capabilities.quantized8Performance.powerUsage =
226 ParseSystemProperty(g_Quantized8PerformancePowerUsageName, 1.0f);
227
228 cb(ErrorStatus::NONE, capabilities);
229 }
230 else
231 {
232 capabilities.float32Performance.execTime = 0;
233 capabilities.float32Performance.powerUsage = 0;
234 capabilities.quantized8Performance.execTime = 0;
235 capabilities.quantized8Performance.powerUsage = 0;
236
237 cb(ErrorStatus::DEVICE_UNAVAILABLE, capabilities);
238 }
239
240 return Void();
241}
242
243Return<void> ArmnnDriver::getSupportedOperations(const Model& model, getSupportedOperations_cb cb)
244{
245 ALOGV("ArmnnDriver::getSupportedOperations()");
246
247 std::vector<bool> result;
248
249 if (!m_Runtime)
250 {
251 cb(ErrorStatus::DEVICE_UNAVAILABLE, result);
252 return Void();
253 }
254
255 // Run general model validation, if this doesn't pass we shouldn't analyse the model anyway
256 if (!android::nn::validateModel(model))
257 {
258 cb(ErrorStatus::INVALID_ARGUMENT, result);
259 return Void();
260 }
261
262 // Attempt to convert the model to an ArmNN input network (INetwork).
263 ModelToINetworkConverter modelConverter(m_Runtime->GetDeviceSpec().DefaultComputeDevice, model,
264 m_Options.GetForcedUnsupportedOperations());
265
266 if (modelConverter.GetConversionResult() != ConversionResult::Success
267 && modelConverter.GetConversionResult() != ConversionResult::UnsupportedFeature)
268 {
269 cb(ErrorStatus::GENERAL_FAILURE, result);
270 return Void();
271 }
272
273 // Check each operation if it was converted successfully and copy the flags
274 // into the result (vector<bool>) that we need to return to Android
275 result.reserve(model.operations.size());
276 for (uint32_t operationIdx = 0; operationIdx < model.operations.size(); operationIdx++)
277 {
278 bool operationSupported = modelConverter.IsOperationSupported(operationIdx);
279 result.push_back(operationSupported);
280 }
281
282 cb(ErrorStatus::NONE, result);
283 return Void();
284}
285
286namespace
287{
288
289void NotifyCallbackAndCheck(const sp<IPreparedModelCallback>& callback, ErrorStatus errorStatus,
290 const ::android::sp<IPreparedModel>& preparedModelPtr)
291{
292 Return<void> returned = callback->notify(errorStatus, preparedModelPtr);
293 // This check is required, if the callback fails and it isn't checked it will bring down the service
294 if (!returned.isOk())
295 {
296 ALOGE("ArmnnDriver::prepareModel: hidl callback failed to return properly: %s ",
297 returned.description().c_str());
298 }
299}
300
301Return<ErrorStatus> FailPrepareModel(ErrorStatus error,
302 const std::string& message,
303 const sp<IPreparedModelCallback>& callback)
304{
305 ALOGW("ArmnnDriver::prepareModel: %s", message.c_str());
306 NotifyCallbackAndCheck(callback, error, nullptr);
307 return error;
308}
309
310}
311
312Return<ErrorStatus> ArmnnDriver::prepareModel(const Model& model,
313 const sp<IPreparedModelCallback>& cb)
314{
315 ALOGV("ArmnnDriver::prepareModel()");
316
317 if (cb.get() == nullptr)
318 {
319 ALOGW("ArmnnDriver::prepareModel: Invalid callback passed to prepareModel");
320 return ErrorStatus::INVALID_ARGUMENT;
321 }
322
323 if (!m_Runtime)
324 {
325 return FailPrepareModel(ErrorStatus::DEVICE_UNAVAILABLE, "ArmnnDriver::prepareModel: Device unavailable", cb);
326 }
327
328 if (!android::nn::validateModel(model))
329 {
330 return FailPrepareModel(ErrorStatus::INVALID_ARGUMENT,
331 "ArmnnDriver::prepareModel: Invalid model passed as input", cb);
332 }
333
334 if (m_Options.UseAndroidNnCpuExecutor())
335 {
336 sp<AndroidNnCpuExecutorPreparedModel> preparedModel = new AndroidNnCpuExecutorPreparedModel(model,
337 m_Options.GetRequestInputsAndOutputsDumpDir());
338 if (preparedModel->Initialize())
339 {
340 NotifyCallbackAndCheck(cb, ErrorStatus::NONE, preparedModel);
341 return ErrorStatus::NONE;
342 }
343 else
344 {
345 NotifyCallbackAndCheck(cb, ErrorStatus::INVALID_ARGUMENT, preparedModel);
346 return ErrorStatus::INVALID_ARGUMENT;
347 }
348 }
349
350 // Deliberately ignore any unsupported operations requested by the options -
351 // at this point we're being asked to prepare a model that we've already declared support for
352 // and the operation indices may be different to those in getSupportedOperations anyway.
353 std::set<unsigned int> unsupportedOperations;
354 ModelToINetworkConverter modelConverter(m_Runtime->GetDeviceSpec().DefaultComputeDevice, model,
355 unsupportedOperations);
356
357 if (modelConverter.GetConversionResult() != ConversionResult::Success)
358 {
359 return FailPrepareModel(ErrorStatus::GENERAL_FAILURE, "ModelToINetworkConverter failed", cb);
360 }
361
362 // optimize the network
363 armnn::IOptimizedNetworkPtr optNet(nullptr, nullptr);
364 try
365 {
366 optNet = armnn::Optimize(*modelConverter.GetINetwork(), m_Runtime->GetDeviceSpec());
367 }
368 catch (armnn::Exception& e)
369 {
370 std::stringstream message;
371 message << "armnn::Exception ("<<e.what()<<") caught from optimize.";
372 return FailPrepareModel(ErrorStatus::GENERAL_FAILURE, message.str(), cb);
373 }
374
surmeh0176660052018-03-29 16:33:54 +0100375 // Check that the optimized network is valid.
376 if (!optNet)
377 {
378 return FailPrepareModel(ErrorStatus::GENERAL_FAILURE,
379 "ArmnnDriver::prepareModel: Invalid optimized network", cb);
380 }
381
382 // Export the optimized network graph to a dot file if an output dump directory
383 // has been specified in the drivers' arguments.
384 ExportNetworkGraphToDotFile(*optNet,
385 m_Options.GetRequestInputsAndOutputsDumpDir(),
386 model);
387
telsoa015307bc12018-03-09 13:51:08 +0000388 // load it into the runtime
389 armnn::NetworkId netId = 0;
390 try
391 {
392 if (m_Runtime->LoadNetwork(netId, std::move(optNet)) != armnn::Status::Success)
393 {
394 return FailPrepareModel(ErrorStatus::GENERAL_FAILURE,
395 "ArmnnDriver::prepareModel: Network could not be loaded", cb);
396 }
397 }
398 catch (armnn::Exception& e)
399 {
400 std::stringstream message;
401 message << "armnn::Exception (" << e.what()<< ") caught from LoadNetwork.";
402 return FailPrepareModel(ErrorStatus::GENERAL_FAILURE, message.str(), cb);
403 }
404
405 std::unique_ptr<ArmnnPreparedModel> preparedModel(new ArmnnPreparedModel(
406 netId,
407 m_Runtime.get(),
408 model,
409 m_Options.GetRequestInputsAndOutputsDumpDir()
410 ));
411
412 // Run a single 'dummy' inference of the model. This means that CL kernels will get compiled (and tuned if
413 // this is enabled) before the first 'real' inference which removes the overhead of the first inference.
414 preparedModel->ExecuteWithDummyInputs();
415
416 if (m_ClTunedParameters &&
417 m_Options.GetClTunedParametersMode() == armnn::IClTunedParameters::Mode::UpdateTunedParameters)
418 {
419 // Now that we've done one inference the CL kernel parameters will have been tuned, so save the updated file.
420 try
421 {
422 m_ClTunedParameters->Save(m_Options.GetClTunedParametersFile().c_str());
423 }
424 catch (const armnn::Exception& error)
425 {
426 ALOGE("ArmnnDriver: Failed to save CL tuned parameters file '%s': %s",
427 m_Options.GetClTunedParametersFile().c_str(), error.what());
428 }
429 }
430
431 NotifyCallbackAndCheck(cb, ErrorStatus::NONE, preparedModel.release());
432
433 return ErrorStatus::NONE;
434}
435
436Return<DeviceStatus> ArmnnDriver::getStatus()
437{
438 ALOGV("ArmnnDriver::getStatus()");
439 return DeviceStatus::AVAILABLE;
440}
441
442}