| /* Copyright 2020 The TensorFlow Authors. All Rights Reserved. |
| |
| 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 <errno.h> |
| #include <jni.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include <fstream> |
| #include <sstream> |
| #include <string> |
| #include <unordered_map> |
| #include <utility> |
| #include <vector> |
| |
| #include "tensorflow/lite/tools/benchmark/benchmark_tflite_model.h" |
| |
| #ifdef __ANDROID__ |
| #include <android/log.h> |
| #endif |
| |
| namespace tflite { |
| namespace benchmark { |
| namespace { |
| |
| const char kOutputDir[] = "/sdcard/benchmark_output"; |
| |
| class FirebaseReportingListener : public BenchmarkListener { |
| public: |
| explicit FirebaseReportingListener(std::string tag, int report_fd) |
| : tag_(tag), report_fd_(report_fd) { |
| if (report_fd < 0) { |
| #ifdef __ANDROID__ |
| __android_log_print( |
| ANDROID_LOG_ERROR, "tflite", |
| "Report would be streamed only to local log not to Firebase " |
| "since the Firebase log file is not opened."); |
| #else |
| fprintf(stderr, |
| "Report would be streamed only to local log not to Firebase " |
| "since the Firebase log file is not opened."); |
| #endif |
| } |
| } |
| |
| void OnBenchmarkEnd(const BenchmarkResults& results) override { |
| ReportResult(results); |
| } |
| |
| void ReportFailure(TfLiteStatus status) { |
| std::string status_msg = |
| status == kTfLiteError |
| ? "TFLite error" |
| : (status == kTfLiteDelegateError ? "TFLite delegate error" |
| : "Unknown error code"); |
| Report(status_msg, std::vector<std::pair<std::string, std::string>>()); |
| } |
| |
| private: |
| void Report( |
| const std::string& status, |
| const std::vector<std::pair<std::string, std::string>>& contents) { |
| // The output format of Firebase Game Loop test is json. |
| // https://firebase.google.com/docs/test-lab/android/game-loop#output-example |
| std::stringstream report; |
| report << "{\n" |
| << " \"name\": \"TFLite benchmark\",\n" |
| << " \"benchmark config\": \"" << tag_ << "\",\n" |
| << " \"status\": \"" << status << "\""; |
| for (const auto& content : contents) { |
| report << ",\n" |
| << " \"" << content.first << "\": \"" << content.second << "\""; |
| } |
| report << "\n}\n"; |
| |
| auto report_str = report.str(); |
| if (report_fd_ >= 0) { |
| write(report_fd_, report_str.c_str(), report_str.size()); |
| } |
| |
| #ifdef __ANDROID__ |
| __android_log_print(ANDROID_LOG_ERROR, "tflite", "%s", report_str.c_str()); |
| #else |
| fprintf(stderr, "%s", report_str.c_str()); |
| #endif |
| } |
| |
| void ReportResult(const BenchmarkResults& results) { |
| std::vector<std::pair<std::string, std::string>> contents; |
| std::stringstream avg_time; |
| avg_time << "init: " << results.startup_latency_us() << ", " |
| << "warmup: " << results.warmup_time_us().avg() << ", " |
| << "inference: " << results.inference_time_us().avg(); |
| contents.emplace_back("average time in us", avg_time.str()); |
| std::stringstream overall_mem_usage; |
| overall_mem_usage << results.overall_mem_usage(); |
| contents.emplace_back("overall memory usage", overall_mem_usage.str()); |
| |
| Report("OK", contents); |
| } |
| |
| std::string tag_; |
| int report_fd_; |
| }; |
| |
| class CsvExportingListener : public BenchmarkListener { |
| public: |
| explicit CsvExportingListener(std::string tag) : tag_(tag) {} |
| |
| void OnBenchmarkEnd(const BenchmarkResults& results) override { |
| if (!CreateOutputDir()) { |
| #ifdef __ANDROID__ |
| __android_log_print(ANDROID_LOG_ERROR, "tflite", |
| "Failed to create output directory %s.", kOutputDir); |
| #else |
| fprintf(stderr, "Failed to create output directory %s.", kOutputDir); |
| #endif |
| return; |
| } |
| WriteBenchmarkResultCsv(results); |
| } |
| |
| private: |
| bool CreateOutputDir() { |
| struct stat st; |
| if (stat(kOutputDir, &st) != 0) { |
| if (mkdir(kOutputDir, 0777) != 0 && errno != EEXIST) { |
| return false; |
| } |
| } else if (!S_ISDIR(st.st_mode)) { |
| errno = ENOTDIR; |
| return false; |
| } |
| return true; |
| } |
| |
| void WriteBenchmarkResultCsv(const BenchmarkResults& results) { |
| auto init_us = results.startup_latency_us(); |
| auto warmup_us = results.warmup_time_us(); |
| auto inference_us = results.inference_time_us(); |
| auto init_mem_usage = results.init_mem_usage(); |
| auto overall_mem_usage = results.overall_mem_usage(); |
| |
| std::stringstream file_name; |
| file_name << kOutputDir << "/benchmark_result_" << tag_; |
| |
| std::ofstream file; |
| file.open(file_name.str().c_str()); |
| file << "config_key,model_size,init_time," |
| << "warmup_avg,warmup_min,warmup_max,warmup_stddev," |
| << "inference_avg,inference_min,inference_max,inference_stddev," |
| << "init_max_rss,init_total_alloc,init_in_use_alloc," |
| << "overall_max_rss,overall_total_alloc,overall_in_use_alloc\n"; |
| file << tag_ << "," << results.model_size_mb() << "," << init_us << "," |
| << warmup_us.avg() << "," << warmup_us.min() << "," << warmup_us.max() |
| << "," << warmup_us.std_deviation() << "," << inference_us.avg() << "," |
| << inference_us.min() << "," << inference_us.max() << "," |
| << inference_us.std_deviation() << "," |
| << (init_mem_usage.max_rss_kb / 1024.0) << "," |
| << (init_mem_usage.total_allocated_bytes / 1024.0 / 1024.0) << "," |
| << (init_mem_usage.in_use_allocated_bytes / 1024.0 / 1024.0) << "," |
| << (overall_mem_usage.max_rss_kb / 1024.0) << "," |
| << (overall_mem_usage.total_allocated_bytes / 1024.0 / 1024.0) << "," |
| << (overall_mem_usage.in_use_allocated_bytes / 1024.0 / 1024.0) |
| << "\n"; |
| file.close(); |
| } |
| |
| std::string tag_; |
| }; |
| |
| std::string GetScenarioConfig(const string& library_dir, int scenario, |
| std::vector<std::string>& args) { |
| // The number of scenarios should equal to the value specified in |
| // AndroidManifest.xml file. |
| std::unordered_map<int, std::pair<std::string, std::vector<std::string>>> |
| all_scenarios = { |
| {1, {"cpu_1thread", {"--num_threads=1"}}}, |
| {2, {"cpu_2threads", {"--num_threads=2"}}}, |
| {3, {"cpu_4threads", {"--num_threads=4"}}}, |
| {4, {"xnnpack_1thread", {"--use_xnnpack=true", "--num_threads=1"}}}, |
| {5, {"xnnpack_2threads", {"--use_xnnpack=true", "--num_threads=2"}}}, |
| {6, {"xnnpack_4threads", {"--use_xnnpack=true", "--num_threads=4"}}}, |
| {7, |
| {"gpu_default", |
| {"--use_gpu=true", "--gpu_precision_loss_allowed=false"}}}, |
| {8, |
| {"gpu_fp16", |
| {"--use_gpu=true", "--gpu_precision_loss_allowed=true"}}}, |
| {9, {"dsp_hexagon", {"--use_hexagon=true"}}}, |
| {10, {"nnapi", {"--use_nnapi=true"}}}, |
| }; |
| |
| std::string tag; |
| args.emplace_back("(BenchmarkModelAndroid)"); |
| args.emplace_back("--graph=/data/local/tmp/graph"); |
| |
| auto it = all_scenarios.find(scenario); |
| if (it != all_scenarios.end()) { |
| const auto& scenario_info = it->second; |
| tag = scenario_info.first; |
| for (const auto& arg : scenario_info.second) { |
| args.push_back(arg); |
| } |
| } |
| if (scenario == 9) { |
| std::stringstream hexagon_lib_path; |
| hexagon_lib_path << "--hexagon_lib_path=" << library_dir; |
| args.push_back(hexagon_lib_path.str()); |
| } |
| return tag; |
| } |
| |
| void RunScenario(const string& library_dir, int scenario, int report_fd) { |
| std::vector<std::string> args; |
| std::string tag = GetScenarioConfig(library_dir, scenario, args); |
| std::vector<char*> argv; |
| argv.reserve(args.size()); |
| for (auto& arg : args) { |
| argv.push_back(const_cast<char*>(arg.data())); |
| } |
| |
| BenchmarkTfLiteModel benchmark; |
| FirebaseReportingListener firebaseReporting(tag, report_fd); |
| benchmark.AddListener(&firebaseReporting); |
| CsvExportingListener csvExporting(tag); |
| benchmark.AddListener(&csvExporting); |
| auto status = benchmark.Run(static_cast<int>(argv.size()), argv.data()); |
| if (status != kTfLiteOk) { |
| firebaseReporting.ReportFailure(status); |
| } |
| } |
| |
| } // namespace |
| } // namespace benchmark |
| } // namespace tflite |
| |
| extern "C" { |
| |
| JNIEXPORT void JNICALL |
| Java_org_tensorflow_lite_benchmark_firebase_BenchmarkModel_nativeRun( |
| JNIEnv* env, jclass clazz, jstring library_dir, jint scenario, |
| jint report_fd) { |
| const char* lib_dir = env->GetStringUTFChars(library_dir, nullptr); |
| |
| tflite::benchmark::RunScenario(lib_dir, static_cast<int>(scenario), |
| static_cast<int>(report_fd)); |
| |
| env->ReleaseStringUTFChars(library_dir, lib_dir); |
| } |
| |
| } // extern "C" |