2 // Copyright © 2017 Arm Ltd. All rights reserved.
3 // SPDX-License-Identifier: MIT
5 #include "InferenceTest.hpp"
7 #include "../src/armnn/Profiling.hpp"
8 #include <boost/algorithm/string.hpp>
9 #include <boost/numeric/conversion/cast.hpp>
10 #include <boost/log/trivial.hpp>
11 #include <boost/filesystem/path.hpp>
12 #include <boost/assert.hpp>
13 #include <boost/format.hpp>
14 #include <boost/program_options.hpp>
15 #include <boost/filesystem/operations.hpp>
23 using namespace std::chrono;
24 using namespace armnn::test;
30 /// Parse the command line of an ArmNN (or referencetests) inference test program.
31 /// \return false if any error occurred during options processing, otherwise true
32 bool ParseCommandLine(int argc, char** argv, IInferenceTestCaseProvider& testCaseProvider,
33 InferenceTestOptions& outParams)
35 namespace po = boost::program_options;
37 po::options_description desc("Options");
41 // Adds generic options needed for all inference tests.
43 ("help", "Display help messages")
44 ("iterations,i", po::value<unsigned int>(&outParams.m_IterationCount)->default_value(0),
45 "Sets the number number of inferences to perform. If unset, a default number will be ran.")
46 ("inference-times-file", po::value<std::string>(&outParams.m_InferenceTimesFile)->default_value(""),
47 "If non-empty, each individual inference time will be recorded and output to this file")
48 ("event-based-profiling,e", po::value<bool>(&outParams.m_EnableProfiling)->default_value(0),
49 "Enables built in profiler. If unset, defaults to off.");
51 // Adds options specific to the ITestCaseProvider.
52 testCaseProvider.AddCommandLineOptions(desc);
54 catch (const std::exception& e)
56 // Coverity points out that default_value(...) can throw a bad_lexical_cast,
57 // and that desc.add_options() can throw boost::io::too_few_args.
58 // They really won't in any of these cases.
59 BOOST_ASSERT_MSG(false, "Caught unexpected exception");
60 std::cerr << "Fatal internal error: " << e.what() << std::endl;
68 po::store(po::parse_command_line(argc, argv, desc), vm);
72 std::cout << desc << std::endl;
80 std::cerr << e.what() << std::endl << std::endl;
81 std::cerr << desc << std::endl;
85 if (!testCaseProvider.ProcessCommandLineOptions())
93 bool ValidateDirectory(std::string& dir)
95 if (dir[dir.length() - 1] != '/')
100 if (!boost::filesystem::exists(dir))
102 std::cerr << "Given directory " << dir << " does not exist" << std::endl;
109 bool InferenceTest(const InferenceTestOptions& params,
110 const std::vector<unsigned int>& defaultTestCaseIds,
111 IInferenceTestCaseProvider& testCaseProvider)
113 #if !defined (NDEBUG)
114 if (params.m_IterationCount > 0) // If just running a few select images then don't bother to warn.
116 BOOST_LOG_TRIVIAL(warning) << "Performance test running in DEBUG build - results may be inaccurate.";
120 double totalTime = 0;
121 unsigned int nbProcessed = 0;
124 // Opens the file to write inference times too, if needed.
125 ofstream inferenceTimesFile;
126 const bool recordInferenceTimes = !params.m_InferenceTimesFile.empty();
127 if (recordInferenceTimes)
129 inferenceTimesFile.open(params.m_InferenceTimesFile.c_str(), ios_base::trunc | ios_base::out);
130 if (!inferenceTimesFile.good())
132 BOOST_LOG_TRIVIAL(error) << "Failed to open inference times file for writing: "
133 << params.m_InferenceTimesFile;
138 // Create a profiler and register it for the current thread.
139 std::unique_ptr<Profiler> profiler = std::make_unique<Profiler>();
140 ProfilerManager::GetInstance().RegisterProfiler(profiler.get());
142 // Enable profiling if requested.
143 profiler->EnableProfiling(params.m_EnableProfiling);
145 // Run a single test case to 'warm-up' the model. The first one can sometimes take up to 10x longer
146 std::unique_ptr<IInferenceTestCase> warmupTestCase = testCaseProvider.GetTestCase(0);
147 if (warmupTestCase == nullptr)
149 BOOST_LOG_TRIVIAL(error) << "Failed to load test case";
155 warmupTestCase->Run();
157 catch (const TestFrameworkException& testError)
159 BOOST_LOG_TRIVIAL(error) << testError.what();
163 const unsigned int nbTotalToProcess = params.m_IterationCount > 0 ? params.m_IterationCount
164 : static_cast<unsigned int>(defaultTestCaseIds.size());
166 for (; nbProcessed < nbTotalToProcess; nbProcessed++)
168 const unsigned int testCaseId = params.m_IterationCount > 0 ? nbProcessed : defaultTestCaseIds[nbProcessed];
169 std::unique_ptr<IInferenceTestCase> testCase = testCaseProvider.GetTestCase(testCaseId);
171 if (testCase == nullptr)
173 BOOST_LOG_TRIVIAL(error) << "Failed to load test case";
177 time_point<high_resolution_clock> predictStart;
178 time_point<high_resolution_clock> predictEnd;
180 TestCaseResult result = TestCaseResult::Ok;
184 predictStart = high_resolution_clock::now();
188 predictEnd = high_resolution_clock::now();
190 // duration<double> will convert the time difference into seconds as a double by default.
191 double timeTakenS = duration<double>(predictEnd - predictStart).count();
192 totalTime += timeTakenS;
194 // Outputss inference times, if needed.
195 if (recordInferenceTimes)
197 inferenceTimesFile << testCaseId << " " << (timeTakenS * 1000.0) << std::endl;
200 result = testCase->ProcessResult(params);
203 catch (const TestFrameworkException& testError)
205 BOOST_LOG_TRIVIAL(error) << testError.what();
206 result = TestCaseResult::Abort;
211 case TestCaseResult::Ok:
213 case TestCaseResult::Abort:
215 case TestCaseResult::Failed:
216 // This test failed so we will fail the entire program eventually, but keep going for now.
220 BOOST_ASSERT_MSG(false, "Unexpected TestCaseResult");
225 const double averageTimePerTestCaseMs = totalTime / nbProcessed * 1000.0f;
227 BOOST_LOG_TRIVIAL(info) << std::fixed << std::setprecision(3) <<
228 "Total time for " << nbProcessed << " test cases: " << totalTime << " seconds";
229 BOOST_LOG_TRIVIAL(info) << std::fixed << std::setprecision(3) <<
230 "Average time per test case: " << averageTimePerTestCaseMs << " ms";
232 // if profiling is enabled print out the results
233 if (profiler && profiler->IsProfilingEnabled())
235 profiler->Print(std::cout);
240 BOOST_LOG_TRIVIAL(error) << "One or more test cases failed";
244 return testCaseProvider.OnInferenceTestFinished();