diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index b01a925503..65ac0b791b 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -71,6 +71,7 @@ option(onnxruntime_ENABLE_LTO "Enable link time optimization, which is not stabl option(onnxruntime_CROSS_COMPILING "Cross compiling onnx runtime" OFF) option(onnxruntime_USE_FULL_PROTOBUF "Use full protobuf" OFF) option(onnxruntime_DISABLE_CONTRIB_OPS "Disable contrib ops" OFF) +option(tensorflow_C_PACKAGE_PATH "Path to tensorflow C package installation dir") set(protobuf_BUILD_TESTS OFF CACHE BOOL "Build protobuf tests" FORCE) #nsync tests failed on Mac Build diff --git a/cmake/onnxruntime_unittests.cmake b/cmake/onnxruntime_unittests.cmake index 0fa7419482..7a0e8a9f38 100644 --- a/cmake/onnxruntime_unittests.cmake +++ b/cmake/onnxruntime_unittests.cmake @@ -460,18 +460,28 @@ else () endif() file(GLOB onnxruntime_perf_test_src ${onnxruntime_perf_test_src_patterns}) -add_executable(onnxruntime_perf_test ${onnxruntime_perf_test_src}) +add_executable(onnxruntime_perf_test ${onnxruntime_perf_test_src} ${ONNXRUNTIME_ROOT}/core/framework/path_lib.cc) target_include_directories(onnxruntime_perf_test PRIVATE ${onnx_test_runner_src_dir} ${ONNXRUNTIME_ROOT} ${eigen_INCLUDE_DIRS} ${extra_includes} ${onnxruntime_graph_header} ${onnxruntime_exec_src_dir} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/onnx) if (WIN32) target_compile_options(onnxruntime_perf_test PRIVATE ${disabled_warnings}) + SET(SYS_PATH_LIB shlwapi) endif() onnxruntime_add_include_to_target(onnxruntime_perf_test gsl) -target_link_libraries(onnxruntime_perf_test PRIVATE onnx_test_runner_common ${GETOPT_LIB_WIDE} ${onnx_test_libs}) +target_link_libraries(onnxruntime_perf_test PRIVATE onnxruntime_test_utils onnx_test_runner_common onnxruntime_common + onnx_test_data_proto onnx_proto libprotobuf ${GETOPT_LIB_WIDE} onnxruntime + ${SYS_PATH_LIB} ${CMAKE_DL_LIBS} Threads::Threads) set_target_properties(onnxruntime_perf_test PROPERTIES FOLDER "ONNXRuntimeTest") +if(tensorflow_C_PACKAGE_PATH) + target_include_directories(onnxruntime_perf_test PRIVATE ${tensorflow_C_PACKAGE_PATH}/include) + target_link_directories(onnxruntime_perf_test PRIVATE ${tensorflow_C_PACKAGE_PATH}/lib) + target_link_libraries(onnxruntime_perf_test PRIVATE tensorflow) + target_compile_definitions(onnxruntime_perf_test PRIVATE HAVE_TENSORFLOW) +endif() + # shared lib if (onnxruntime_BUILD_SHARED_LIB) add_library(onnxruntime_mocked_allocator ${ONNXRUNTIME_ROOT}/test/util/test_allocator.cc) diff --git a/onnxruntime/core/platform/posix/env.cc b/onnxruntime/core/platform/posix/env.cc index 78ff664e1e..4a40471a2c 100644 --- a/onnxruntime/core/platform/posix/env.cc +++ b/onnxruntime/core/platform/posix/env.cc @@ -145,16 +145,6 @@ class PosixEnv : public Env { return Status::OK(); } - static bool GetFileSizeIfUnknown(int fd, size_t& len) { - if(len > 0) return true; - struct stat stbuf; - if ((fstat(fd, &stbuf) != 0) || (!S_ISREG(stbuf.st_mode))) { - return false; - } - len = static_cast(stbuf.st_size); - return true; - } - common::Status ReadFileAsString(const char* fname, off_t offset, void*& p, size_t& len, OrtCallback& deleter) const override { if (!fname) { @@ -169,13 +159,21 @@ class PosixEnv : public Env { deleter.param = nullptr; int fd = open(fname, O_RDONLY); if (fd < 0) { - int err = errno; - return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "open file ", fname, " fail, errcode =", err); + return ReportSystemError("open", fname); } - if (!GetFileSizeIfUnknown(fd, len)) { - (void)close(fd); - return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Get file '", fname, "' size fail"); + if (len <= 0) { + struct stat stbuf; + if (fstat(fd, &stbuf) != 0) { + return ReportSystemError("fstat", fname); + } + + if (!S_ISREG(stbuf.st_mode)) { + return common::Status(common::ONNXRUNTIME, common::INVALID_ARGUMENT, + "ReadFileAsString: input is not a regular file"); + } + len = static_cast(stbuf.st_size); } + if (len == 0) { p = nullptr; } else { @@ -199,10 +197,30 @@ class PosixEnv : public Env { return common::Status::OK(); } + static common::Status ReportSystemError(const char* operation_name, const std::string& path) { + auto e = errno; + char buf[1024]; + const char* msg = ""; + if (e > 0) { +#if defined(_GNU_SOURCE) && !defined(__APPLE__) + msg = strerror_r(e, buf, sizeof(buf)); +#else + // for Mac OS X + if (strerror_r(e, buf, sizeof(buf)) != 0) { + buf[0] = '\0'; + } + msg = buf; +#endif + } + std::ostringstream oss; + oss << operation_name << " file \"" << path << "\" failed: " << msg; + return common::Status(common::SYSTEM, e, oss.str()); + } + common::Status FileOpenRd(const std::string& path, /*out*/ int& fd) const override { fd = open(path.c_str(), O_RDONLY); if (0 > fd) { - return common::Status(common::SYSTEM, errno); + return ReportSystemError("open", path); } return Status::OK(); } @@ -210,7 +228,7 @@ class PosixEnv : public Env { common::Status FileOpenWr(const std::string& path, /*out*/ int& fd) const override { fd = open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); if (0 > fd) { - return common::Status(common::SYSTEM, errno); + return ReportSystemError("open", path); } return Status::OK(); } @@ -218,7 +236,7 @@ class PosixEnv : public Env { common::Status FileClose(int fd) const override { int ret = close(fd); if (0 != ret) { - return common::Status(common::SYSTEM, errno); + return ReportSystemError("close", ""); } return Status::OK(); } diff --git a/onnxruntime/test/onnx/TestCase.cc b/onnxruntime/test/onnx/TestCase.cc index 460e1cc6b0..bdf040aed9 100644 --- a/onnxruntime/test/onnx/TestCase.cc +++ b/onnxruntime/test/onnx/TestCase.cc @@ -222,14 +222,6 @@ class OnnxModelInfo : public TestModelInfo { const PATH_CHAR_TYPE* GetModelUrl() const override { return model_url_.c_str(); } - std::basic_string GetDir() const override { - std::basic_string test_case_dir; - auto st = GetDirNameFromFilePath(model_url_, test_case_dir); - if (!st.IsOK()) { - ORT_THROW("GetDirNameFromFilePath failed"); - } - return test_case_dir; - } const std::string& GetNodeName() const override { return node_name_; } const ONNX_NAMESPACE::ValueInfoProto* GetOutputInfoFromModel(size_t i) const override { return &output_value_info_[i]; diff --git a/onnxruntime/test/onnx/TestCase.h b/onnxruntime/test/onnx/TestCase.h index 9f632882c6..5d7fe59453 100644 --- a/onnxruntime/test/onnx/TestCase.h +++ b/onnxruntime/test/onnx/TestCase.h @@ -37,7 +37,14 @@ class ITestCase { class TestModelInfo { public: virtual const PATH_CHAR_TYPE* GetModelUrl() const = 0; - virtual std::basic_string GetDir() const = 0; + virtual std::basic_string GetDir() const { + std::basic_string test_case_dir; + auto st = onnxruntime::GetDirNameFromFilePath(GetModelUrl(), test_case_dir); + if (!st.IsOK()) { + ORT_THROW("GetDirNameFromFilePath failed"); + } + return test_case_dir; + } virtual const std::string& GetNodeName() const = 0; virtual const ONNX_NAMESPACE::ValueInfoProto* GetOutputInfoFromModel(size_t i) const = 0; virtual int GetInputCount() const = 0; diff --git a/onnxruntime/test/onnx/runner.cc b/onnxruntime/test/onnx/runner.cc index 4c532d8b82..b80f01793c 100644 --- a/onnxruntime/test/onnx/runner.cc +++ b/onnxruntime/test/onnx/runner.cc @@ -317,7 +317,7 @@ EXECUTE_RESULT DataRunner::RunTaskImpl(size_t task_id) { c_->LoadTestData(task_id, holder, feeds, true); // Create output feed - size_t output_count; + size_t output_count = 0; ORT_THROW_ON_ERROR(OrtSessionGetOutputCount(session, &output_count)); std::vector output_names(output_count); for (size_t i = 0; i != output_count; ++i) { diff --git a/onnxruntime/test/perftest/TFModelInfo.cc b/onnxruntime/test/perftest/TFModelInfo.cc new file mode 100644 index 0000000000..21503846bc --- /dev/null +++ b/onnxruntime/test/perftest/TFModelInfo.cc @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "TFModelInfo.h" +#include + +TestModelInfo* TFModelInfo::Create(_In_ const PATH_CHAR_TYPE* model_url) { + TFModelInfo* ret = new TFModelInfo(); + ret->model_url_ = model_url; + std::basic_string meta_file_path = model_url; + meta_file_path.append(ORT_TSTR(".meta")); + void* p = nullptr; + size_t len = 0; + OrtCallback b; + auto st = onnxruntime::Env::Default().ReadFileAsString(meta_file_path.c_str(), 0, p, len, b); + if (!st.IsOK()) { + ORT_THROW(st.ErrorMessage()); + } + // this string is not null terminated + std::string filecontent(reinterpret_cast(p), len); + std::istringstream is(filecontent); + + std::string line; + while (std::getline(is, line)) { + size_t line_len = 0; + if (!line.empty() && line.back() == '\n') { + line_len = line.length() - 1; + if (line_len > 0 && line[line_len - 1] == '\r') { + --line_len; + } + line.resize(line_len); + } + if (line.empty()) continue; + if (line.compare(0, 6, "input=") == 0) { + ret->input_names_.push_back(line.substr(6)); + } else if (line.compare(0, 7, "output=") == 0) { + ret->output_names_.push_back(line.substr(7)); + } else { + ORT_THROW("unknow line:", line.size()); + } + } + + if (b.f) b.f(b.param); + + return ret; +} + +int TFModelInfo::GetInputCount() const { return static_cast(input_names_.size()); } +int TFModelInfo::GetOutputCount() const { return static_cast(output_names_.size()); } +const std::string& TFModelInfo::GetInputName(size_t i) const { return input_names_[i]; } +const std::string& TFModelInfo::GetOutputName(size_t i) const { return output_names_[i]; } diff --git a/onnxruntime/test/perftest/TFModelInfo.h b/onnxruntime/test/perftest/TFModelInfo.h new file mode 100644 index 0000000000..2d90c3bd25 --- /dev/null +++ b/onnxruntime/test/perftest/TFModelInfo.h @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#include "TestCase.h" +#include +#include + +class TFModelInfo : public TestModelInfo { + public: + const PATH_CHAR_TYPE* GetModelUrl() const override { return model_url_.c_str(); } + + const std::string& GetNodeName() const override { return node_name_; } + const ONNX_NAMESPACE::ValueInfoProto* GetOutputInfoFromModel(size_t) const override { return nullptr; } + + int GetInputCount() const override; + int GetOutputCount() const override; + const std::string& GetInputName(size_t i) const override; + const std::string& GetOutputName(size_t i) const override; + ~TFModelInfo() override = default; + + static TestModelInfo* Create(_In_ const PATH_CHAR_TYPE* model_url); + + private: + TFModelInfo() = default; + std::basic_string model_url_; + std::vector input_names_; + std::vector output_names_; + std::string node_name_; +}; diff --git a/onnxruntime/test/perftest/command_args_parser.cc b/onnxruntime/test/perftest/command_args_parser.cc index 863179bd61..7fd60da7ab 100644 --- a/onnxruntime/test/perftest/command_args_parser.cc +++ b/onnxruntime/test/perftest/command_args_parser.cc @@ -27,8 +27,10 @@ namespace perftest { "perf_test [options...] model_path result_file\n" "Options:\n" "\t-m [test_mode]: Specifies the test mode. Value coulde be 'duration' or 'times'.\n" - "\t\tProvide 'duration' to run the test for a fix duration, and 'times' to repeated for a certain times. Default:'duration'.\n" + "\t\tProvide 'duration' to run the test for a fix duration, and 'times' to repeated for a certain times. " + "Default:'duration'.\n" "\t-e [cpu|cuda|mkldnn|tensorrt]: Specifies the provider 'cpu','cuda','mkldnn' or 'tensorrt'. Default:'cpu'.\n" + "\t-b [tf|ort]: backend to use. Default:ort\n" "\t-r [repeated_times]: Specifies the repeated times if running in 'times' test mode.Default:1000.\n" "\t-t [seconds_to_run]: Specifies the seconds to run for 'duration' mode. Default:600.\n" "\t-p [profile_file]: Specifies the profile name to enable profiling and dump the profile data to the file.\n" @@ -41,7 +43,7 @@ namespace perftest { /*static*/ bool CommandLineParser::ParseArguments(PerformanceTestConfig& test_config, int argc, ORTCHAR_T* argv[]) { int ch; - while ((ch = getopt(argc, argv, ORT_TSTR("m:e:r:t:p:x:o:vhs"))) != -1) { + while ((ch = getopt(argc, argv, ORT_TSTR("b:m:e:r:t:p:x:o:vhs"))) != -1) { switch (ch) { case 'm': if (!CompareCString(optarg, ORT_TSTR("duration"))) { @@ -52,6 +54,9 @@ namespace perftest { return false; } break; + case 'b': + test_config.backend = optarg; + break; case 'p': test_config.run_config.profile_file = optarg; break; @@ -71,14 +76,14 @@ namespace perftest { } break; case 'r': - test_config.run_config.repeated_times = static_cast(OrtStrtol(optarg, nullptr)); + test_config.run_config.repeated_times = static_cast(OrtStrtol(optarg, nullptr)); if (test_config.run_config.repeated_times <= 0) { return false; } test_config.run_config.test_mode = TestMode::KFixRepeatedTimesMode; break; case 't': - test_config.run_config.duration_in_seconds = static_cast(OrtStrtol(optarg, nullptr)); + test_config.run_config.duration_in_seconds = static_cast(OrtStrtol(optarg, nullptr)); if (test_config.run_config.repeated_times <= 0) { return false; } @@ -100,8 +105,8 @@ namespace perftest { case 'o': test_config.run_config.optimization_level = static_cast(OrtStrtol(optarg, nullptr)); // Valid values are: 0, 1, 2. - if (test_config.run_config.optimization_level > 2 ) { - return false; + if (test_config.run_config.optimization_level > 2) { + return false; } break; case '?': diff --git a/onnxruntime/test/perftest/main.cc b/onnxruntime/test/perftest/main.cc index 43c0446f3d..8a5cbfe6fa 100644 --- a/onnxruntime/test/perftest/main.cc +++ b/onnxruntime/test/perftest/main.cc @@ -2,10 +2,7 @@ // Licensed under the MIT License. // onnxruntime dependencies -#include -#include -#include -#include +#include #include "command_args_parser.h" #include "performance_runner.h" @@ -36,7 +33,7 @@ int real_main(int argc, char* argv[], OrtEnv** p_env) { perftest::PerformanceRunner perf_runner(env, test_config); auto status = perf_runner.Run(); if (!status.IsOK()) { - LOGF_DEFAULT(ERROR, "Run failed:%s", status.ErrorMessage().c_str()); + printf("Run failed:%s\n", status.ErrorMessage().c_str()); return -1; } @@ -60,8 +57,6 @@ int main(int argc, char* argv[]) { } if (env) { OrtReleaseEnv(env); - } else { - ::google::protobuf::ShutdownProtobufLibrary(); } return retval; } diff --git a/onnxruntime/test/perftest/ort_test_session.cc b/onnxruntime/test/perftest/ort_test_session.cc new file mode 100644 index 0000000000..69f24efd9a --- /dev/null +++ b/onnxruntime/test/perftest/ort_test_session.cc @@ -0,0 +1,105 @@ +#include "ort_test_session.h" +#include +#include +#include "providers.h" +#include "TestCase.h" + +#ifdef _WIN32 +#define strdup _strdup +#endif + +namespace onnxruntime { +namespace perftest { + +std::chrono::duration OnnxRuntimeTestSession::Run(const OrtValue* const* input) { + auto start = std::chrono::high_resolution_clock::now(); + + ORT_THROW_ON_ERROR(OrtRun(session_object_, nullptr, input_names_.data(), input, input_names_.size(), + output_names_raw_ptr.data(), output_names_raw_ptr.size(), output_values_.data())); + auto end = std::chrono::high_resolution_clock::now(); + std::chrono::duration duration_seconds = end - start; + for (size_t i = 0; i != output_values_.size(); ++i) { + OrtReleaseValue(output_values_[i]); + output_values_[i] = nullptr; + } + return duration_seconds; +} + +OnnxRuntimeTestSession::OnnxRuntimeTestSession(OrtEnv* env, PerformanceTestConfig& performance_test_config, + const TestModelInfo* m) + : input_names_(m->GetInputCount()) { + SessionOptionsWrapper sf(env); + const bool enable_cpu_mem_arena = true; + const std::string& provider_name = performance_test_config.machine_config.provider_type_name; + if (provider_name == onnxruntime::kMklDnnExecutionProvider) { +#ifdef USE_MKLDNN + ORT_THROW_ON_ERROR(OrtSessionOptionsAppendExecutionProvider_Mkldnn(sf, enable_cpu_mem_arena ? 1 : 0)); +#else + ORT_THROW("MKL-DNN is not supported in this build\n"); +#endif + } else if (provider_name == onnxruntime::kCudaExecutionProvider) { +#ifdef USE_CUDA + ORT_THROW_ON_ERROR(OrtSessionOptionsAppendExecutionProvider_CUDA(sf, 0)); +#else + ORT_THROW("CUDA is not supported in this build\n"); +#endif + } else if (provider_name == onnxruntime::kNupharExecutionProvider) { +#ifdef USE_NUPHAR + ORT_THROW_ON_ERROR(OrtSessionOptionsAppendExecutionProvider_Nuphar(sf, 0, "")); +#else + ORT_THROW("Nuphar is not supported in this build\n"); +#endif + } else if (provider_name == onnxruntime::kTensorrtExecutionProvider) { +#ifdef USE_TENSORRT + ORT_THROW_ON_ERROR(OrtSessionOptionsAppendExecutionProvider_Tensorrt(sf)); + ORT_THROW_ON_ERROR(OrtSessionOptionsAppendExecutionProvider_CUDA(sf, 0)); +#else + ORT_THROW("TensorRT is not supported in this build\n"); +#endif + } else if (!provider_name.empty() && provider_name != onnxruntime::kCpuExecutionProvider) { + ORT_THROW("This backend is not included in perf test runner.\n"); + } + + if (enable_cpu_mem_arena) + sf.EnableCpuMemArena(); + else + sf.DisableCpuMemArena(); + if (performance_test_config.run_config.enable_sequential_execution) + sf.EnableSequentialExecution(); + else + sf.DisableSequentialExecution(); + fprintf(stdout, "Setting thread pool size to %d\n", performance_test_config.run_config.session_thread_pool_size); + sf.SetSessionThreadPoolSize(performance_test_config.run_config.session_thread_pool_size); + // Set optimization level. + sf.SetSessionGraphOptimizationLevel(performance_test_config.run_config.optimization_level); + if (!performance_test_config.run_config.profile_file.empty()) + sf.EnableProfiling(performance_test_config.run_config.profile_file.c_str()); + session_object_ = sf.OrtCreateSession(performance_test_config.model_info.model_file_path.c_str()); + + size_t output_count; + ORT_THROW_ON_ERROR(OrtSessionGetOutputCount(session_object_, &output_count)); + output_names_.resize(output_count); + OrtAllocator* a; + ORT_THROW_ON_ERROR(OrtCreateDefaultAllocator(&a)); + for (size_t i = 0; i != output_count; ++i) { + char* output_name = nullptr; + ORT_THROW_ON_ERROR(OrtSessionGetOutputName(session_object_, i, a, &output_name)); + assert(output_name != nullptr); + output_names_[i] = output_name; + a->Free(a, output_name); + } + output_names_raw_ptr.resize(output_count); + for (size_t i = 0; i != output_count; ++i) { + output_names_raw_ptr[i] = output_names_[i].c_str(); + } + OrtReleaseAllocator(a); + output_values_.resize(output_count); + + size_t input_count = static_cast(m->GetInputCount()); + for (size_t i = 0; i != input_count; ++i) { + input_names_[i] = strdup(m->GetInputName(i).c_str()); + } +} + +} // namespace perftest +} // namespace onnxruntime \ No newline at end of file diff --git a/onnxruntime/test/perftest/ort_test_session.h b/onnxruntime/test/perftest/ort_test_session.h new file mode 100644 index 0000000000..01d461e893 --- /dev/null +++ b/onnxruntime/test/perftest/ort_test_session.h @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once +#include +#include "test_configuration.h" +#include "test_session.h" +class TestModelInfo; +namespace onnxruntime { +namespace perftest { +class OnnxRuntimeTestSession : public TestSession { + public: + OnnxRuntimeTestSession(OrtEnv* env, PerformanceTestConfig& performance_test_config, const TestModelInfo* m); + + ~OnnxRuntimeTestSession() override { + if (session_object_ != nullptr) OrtReleaseSession(session_object_); + for (char* p : input_names_) { + free(p); + } + } + std::chrono::duration Run(const OrtValue* const* input) override; + + ORT_DISALLOW_COPY_ASSIGNMENT_AND_MOVE(OnnxRuntimeTestSession); + + private: + OrtSession* session_object_ = nullptr; + std::vector output_names_; + // The same size with output_names_. + // TODO: implement a customized allocator, then we can remove output_names_ to simplify this code + std::vector output_names_raw_ptr; + std::vector output_values_; + std::vector input_names_; +}; + +} // namespace perftest +} // namespace onnxruntime \ No newline at end of file diff --git a/onnxruntime/test/perftest/performance_runner.cc b/onnxruntime/test/perftest/performance_runner.cc index c9defc54a7..8fa23843f7 100644 --- a/onnxruntime/test/perftest/performance_runner.cc +++ b/onnxruntime/test/perftest/performance_runner.cc @@ -2,13 +2,15 @@ // Licensed under the MIT License. #include "performance_runner.h" -#include "TestCase.h" -#include "core/graph/graph_viewer.h" //for onnxruntime::NodeArg -#include "core/session/inference_session.h" -#include "utils.h" -#include "testenv.h" -#include "providers.h" +#include +#include "TestCase.h" +#include "TFModelInfo.h" +#include "utils.h" +#include "ort_test_session.h" +#ifdef HAVE_TENSORFLOW +#include "tf_test_session.h" +#endif using onnxruntime::Status; namespace onnxruntime { @@ -20,10 +22,9 @@ Status PerformanceRunner::Run() { // warm up RunOneIteration(true /*isWarmup*/); - InferenceSession* session_object = (InferenceSession*)session_object_; - if (!performance_test_config_.run_config.profile_file.empty()) - session_object->StartProfiling(performance_test_config_.run_config.profile_file); + // TODO: start profiling + // if (!performance_test_config_.run_config.profile_file.empty()) std::unique_ptr p_ICPUUsage = utils::CreateICPUUsage(); switch (performance_test_config_.run_config.test_mode) { @@ -39,7 +40,8 @@ Status PerformanceRunner::Run() { performance_result_.average_CPU_usage = p_ICPUUsage->GetUsage(); performance_result_.peak_workingset_size = utils::GetPeakWorkingSetSize(); - if (!performance_test_config_.run_config.profile_file.empty()) session_object->EndProfiling(); + // TODO: end profiling + // if (!performance_test_config_.run_config.profile_file.empty()) session_object->EndProfiling(); std::cout << "Total time cost:" << performance_result_.total_time_cost << std::endl << "Total iterations:" << performance_result_.time_costs.size() << std::endl @@ -48,18 +50,8 @@ Status PerformanceRunner::Run() { } Status PerformanceRunner::RunOneIteration(bool isWarmup) { - auto start = std::chrono::high_resolution_clock::now(); - OrtRunOptions run_options; - - ORT_THROW_ON_ERROR(OrtRun(session_object_, nullptr, input_names_.data(), input_values_.data(), input_names_.size(), - output_names_raw_ptr.data(), output_names_raw_ptr.size(), output_values_.data())); - auto end = std::chrono::high_resolution_clock::now(); - for (size_t i = 0; i != output_values_.size(); ++i) { - OrtReleaseValue(output_values_[i]); - output_values_[i] = nullptr; - } + std::chrono::duration duration_seconds = session_->Run(input_values_.data()); if (!isWarmup) { - std::chrono::duration duration_seconds = end - start; performance_result_.time_costs.emplace_back(duration_seconds.count()); performance_result_.total_time_cost += duration_seconds.count(); if (performance_test_config_.run_config.f_verbose) { @@ -70,16 +62,16 @@ Status PerformanceRunner::RunOneIteration(bool isWarmup) { return Status::OK(); } +PerformanceRunner::~PerformanceRunner() = default; + +PerformanceRunner::PerformanceRunner(OrtEnv* env, const PerformanceTestConfig& test_config) + : env_(env), performance_test_config_(test_config) {} + bool PerformanceRunner::Initialize() { - bool has_valid_extension = HasExtensionOf(performance_test_config_.model_info.model_file_path, ORT_TSTR("onnx")); - if (!has_valid_extension) { - LOGF_DEFAULT(ERROR, "input path is not a valid model"); - return false; - } std::basic_string test_case_dir; auto st = GetDirNameFromFilePath(performance_test_config_.model_info.model_file_path, test_case_dir); if (!st.IsOK()) { - LOGF_DEFAULT(ERROR, "input path is not a valid model"); + printf("input path is not a valid model\n"); return false; } std::basic_string model_name = GetLastComponent(test_case_dir); @@ -90,102 +82,40 @@ bool PerformanceRunner::Initialize() { std::string narrow_model_name = ToMBString(model_name); performance_result_.model_name = narrow_model_name; - auto p_model = TestModelInfo::LoadOnnxModel(performance_test_config_.model_info.model_file_path.c_str()); - std::unique_ptr test_case(CreateOnnxTestCase(narrow_model_name, p_model, 0.0, 0.0)); + TestModelInfo* p_model; + if (CompareCString(performance_test_config_.backend.c_str(), ORT_TSTR("ort")) == 0) { + p_model = TestModelInfo::LoadOnnxModel(performance_test_config_.model_info.model_file_path.c_str()); + } else if (CompareCString(performance_test_config_.backend.c_str(), ORT_TSTR("tf")) == 0) { + p_model = TFModelInfo::Create(performance_test_config_.model_info.model_file_path.c_str()); + } else { + ORT_NOT_IMPLEMENTED(ToMBString(performance_test_config_.backend), " is not supported"); + } + test_case_.reset(CreateOnnxTestCase(narrow_model_name, p_model, 0.0, 0.0)); - SessionOptionsWrapper sf(env_); - const bool enable_cpu_mem_arena = true; - const std::string& provider_name = performance_test_config_.machine_config.provider_type_name; - if (provider_name == onnxruntime::kMklDnnExecutionProvider) { -#ifdef USE_MKLDNN - ORT_THROW_ON_ERROR(OrtSessionOptionsAppendExecutionProvider_Mkldnn(sf, enable_cpu_mem_arena ? 1 : 0)); -#else - fprintf(stderr, "MKL-DNN is not supported in this build"); - return false; -#endif - } else if (provider_name == onnxruntime::kCudaExecutionProvider) { -#ifdef USE_CUDA - ORT_THROW_ON_ERROR(OrtSessionOptionsAppendExecutionProvider_CUDA(sf, 0)); -#else - fprintf(stderr, "CUDA is not supported in this build"); - return false; -#endif - } else if (provider_name == onnxruntime::kNupharExecutionProvider) { -#ifdef USE_NUPHAR - ORT_THROW_ON_ERROR(OrtSessionOptionsAppendExecutionProvider_Nuphar(sf, 0, "")); -#else - fprintf(stderr, "Nuphar is not supported in this build"); - return false; -#endif - } else if (provider_name == onnxruntime::kTensorrtExecutionProvider) { -#ifdef USE_TENSORRT - ORT_THROW_ON_ERROR(OrtSessionOptionsAppendExecutionProvider_Tensorrt(sf)); - ORT_THROW_ON_ERROR(OrtSessionOptionsAppendExecutionProvider_CUDA(sf, 0)); -#else - fprintf(stderr, "TensorRT is not supported in this build"); - return false; -#endif - } else if (!provider_name.empty() && provider_name != onnxruntime::kCpuExecutionProvider) { - fprintf(stderr, "This backend is not included in perf test runner."); + // TODO: Place input tensor on cpu memory if mkldnn provider type to avoid CopyTensor logic in CopyInputAcrossDevices + if (test_case_->GetDataCount() <= 0) { + std::cout << "there is no test data for model " << test_case_->GetTestCaseName() << std::endl; return false; } - if (enable_cpu_mem_arena) - sf.EnableCpuMemArena(); - else - sf.DisableCpuMemArena(); - if (performance_test_config_.run_config.enable_sequential_execution) - sf.EnableSequentialExecution(); - else - sf.DisableSequentialExecution(); - fprintf(stdout, "Setting thread pool size to %d\n", performance_test_config_.run_config.session_thread_pool_size); - sf.SetSessionThreadPoolSize(performance_test_config_.run_config.session_thread_pool_size); - - // Set optimization level. - sf.SetSessionGraphOptimizationLevel(performance_test_config_.run_config.optimization_level); - - session_object_ = sf.OrtCreateSession(test_case->GetModelUrl()); - - auto provider_type = performance_test_config_.machine_config.provider_type_name; - // Place input tensor on cpu memory if mkldnn provider type to avoid CopyTensor logic in CopyInputAcrossDevices - // TODO: find a better way to do this. - if (provider_type == onnxruntime::kMklDnnExecutionProvider) { - provider_type = onnxruntime::kCpuExecutionProvider; - } - - if (test_case->GetDataCount() <= 0) { - LOGS_DEFAULT(ERROR) << "there is no test data for model " << test_case->GetTestCaseName(); - return false; - } - - test_case->LoadTestData(0 /* id */, b_, feeds_, true); - - input_names_.resize(feeds_.size()); + test_case_->LoadTestData(0 /* id */, b_, feeds_, true); input_values_.resize(feeds_.size()); size_t input_index = 0; for (auto& kvp : feeds_) { - input_names_[input_index] = kvp.first.c_str(); input_values_[input_index] = kvp.second; ++input_index; } - size_t output_count; - ORT_THROW_ON_ERROR(OrtSessionGetOutputCount(session_object_, &output_count)); - output_names_.resize(output_count); - OrtAllocator* a; - ORT_THROW_ON_ERROR(OrtCreateDefaultAllocator(&a)); - for (size_t i = 0; i != output_count; ++i) { - char* output_name = nullptr; - ORT_THROW_ON_ERROR(OrtSessionGetOutputName(session_object_, i, a, &output_name)); - assert(output_name != nullptr); - output_names_[i] = output_name; - a->Free(a, output_name); + + if (CompareCString(performance_test_config_.backend.c_str(), ORT_TSTR("ort")) == 0) { + session_ = new OnnxRuntimeTestSession(env_, performance_test_config_, p_model); +#ifdef HAVE_TENSORFLOW + } else if (CompareCString(performance_test_config_.backend.c_str(), ORT_TSTR("tf")) == 0) { + session_ = new TensorflowTestSession(performance_test_config_, p_model); +#endif + } else { + ORT_NOT_IMPLEMENTED(ToMBString(performance_test_config_.backend), " is not supported"); } - output_names_raw_ptr.resize(output_count); - for (size_t i = 0; i != output_count; ++i) { - output_names_raw_ptr[i] = output_names_[i].c_str(); - } - OrtReleaseAllocator(a); - output_values_.resize(output_count); + return true; } diff --git a/onnxruntime/test/perftest/performance_runner.h b/onnxruntime/test/perftest/performance_runner.h index ec4e97b2f7..f558a4f93d 100644 --- a/onnxruntime/test/perftest/performance_runner.h +++ b/onnxruntime/test/perftest/performance_runner.h @@ -10,17 +10,14 @@ // onnxruntime dependencies #include -#include -#include #include -#include -#include -#include #include -#include #include #include "test_configuration.h" #include "heap_buffer.h" +#include "test_session.h" + +class ITestCase; namespace onnxruntime { namespace perftest { @@ -36,7 +33,7 @@ struct PerformanceResult { std::ofstream outfile; outfile.open(path, std::ofstream::out | std::ofstream::app); if (!outfile.good()) { - LOGF_DEFAULT(ERROR, "failed to open result file"); + printf("failed to open result file"); return; } @@ -44,7 +41,7 @@ struct PerformanceResult { outfile << model_name << "," << time_costs[runs] << "," << peak_workingset_size << "," << average_CPU_usage << "," << runs << std::endl; } - if (time_costs.size() > 0 && f_include_statistics) { + if (!time_costs.empty() && f_include_statistics) { std::vector sorted_time = time_costs; size_t total = sorted_time.size(); @@ -70,9 +67,9 @@ struct PerformanceResult { class PerformanceRunner { public: - PerformanceRunner(OrtEnv* env, const PerformanceTestConfig& test_config) - : env_(env), performance_test_config_(test_config) {} + PerformanceRunner(OrtEnv* env, const PerformanceTestConfig& test_config); + ~PerformanceRunner(); Status Run(); inline const PerformanceResult& GetResult() const { return performance_result_; } @@ -81,9 +78,6 @@ class PerformanceRunner { performance_result_.DumpToFile(performance_test_config_.model_info.result_file_path, performance_test_config_.run_config.f_dump_statistics); } - ~PerformanceRunner() { - if (session_object_ != nullptr) OrtReleaseSession(session_object_); - } ORT_DISALLOW_COPY_ASSIGNMENT_AND_MOVE(PerformanceRunner); private: @@ -108,17 +102,12 @@ class PerformanceRunner { OrtEnv* env_; PerformanceResult performance_result_; PerformanceTestConfig performance_test_config_; - // not owned - OrtSession* session_object_ = nullptr; - std::vector input_names_; + std::unordered_map feeds_; std::vector input_values_; HeapBuffer b_; - std::vector output_names_; - // The same size with output_names_. - // TODO: implement a customized allocator, then we can remove output_names_ to simplify this code - std::vector output_names_raw_ptr; - std::vector output_values_; + std::unique_ptr test_case_; + TestSession* session_; }; } // namespace perftest } // namespace onnxruntime diff --git a/onnxruntime/test/perftest/test_configuration.h b/onnxruntime/test/perftest/test_configuration.h index 182b120789..59c3ce9000 100644 --- a/onnxruntime/test/perftest/test_configuration.h +++ b/onnxruntime/test/perftest/test_configuration.h @@ -49,6 +49,7 @@ struct PerformanceTestConfig { ModelInfo model_info; MachineConfig machine_config; RunConfig run_config; + std::basic_string backend = ORT_TSTR("ort"); }; } // namespace perftest diff --git a/onnxruntime/test/perftest/test_session.h b/onnxruntime/test/perftest/test_session.h new file mode 100644 index 0000000000..11cf1f308e --- /dev/null +++ b/onnxruntime/test/perftest/test_session.h @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once +namespace onnxruntime { +namespace perftest { +class TestSession { + public: + virtual std::chrono::duration Run(const OrtValue* const* input) = 0; + virtual ~TestSession() = default; +}; +} // namespace perftest +} // namespace onnxruntime \ No newline at end of file diff --git a/onnxruntime/test/perftest/tf_test_session.h b/onnxruntime/test/perftest/tf_test_session.h new file mode 100644 index 0000000000..2af1e847ca --- /dev/null +++ b/onnxruntime/test/perftest/tf_test_session.h @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once +#include +#include +#include "test_configuration.h" +#include "tensorflow/c/c_api.h" +#include "test_session.h" + +namespace onnxruntime { +namespace perftest { +class TensorflowTestSession : public TestSession { + private: + OrtCallback model_deleter; + std::vector feed_; + std::vector fetches_; + TF_Session* sess_; + TF_Graph* tf_graph_; + // This function is for both graph inputs and outputs + static TF_Output GetOutputFromGraph(const char* tensor_name, TF_Graph* tf_graph) { + TF_Output ret; + const char* start = tensor_name; + const char* sep = strchr(start, ':'); + if (sep == nullptr) { + ORT_THROW("invalid name:", tensor_name); + } + size_t name_len = sep - start; + std::string name(name_len, '\0'); + memcpy(const_cast(name.data()), start, name_len); + ret.oper = TF_GraphOperationByName(tf_graph, name.c_str()); + if (ret.oper == nullptr) ORT_THROW("input name: \"", name, "\" can not be find in the graph"); + start = sep + 1; + char* end; + ret.index = static_cast(strtol(start, &end, 10)); + if (start == end) { + ORT_THROW("invalid name:", tensor_name); + } + return ret; + } + + public: + TensorflowTestSession(PerformanceTestConfig& performance_test_config, const TestModelInfo* m) { + TF_Status* s = TF_NewStatus(); + tf_graph_ = TF_NewGraph(); + TF_ImportGraphDefOptions* opts = TF_NewImportGraphDefOptions(); + TF_ImportGraphDefOptionsSetPrefix(opts, ""); + TF_Buffer* graph_def = TF_NewBuffer(); + void* model_data; + auto st = Env::Default().ReadFileAsString(performance_test_config.model_info.model_file_path.c_str(), 0, model_data, + graph_def->length, model_deleter); + if (!st.IsOK()) + ORT_THROW("read file ", performance_test_config.model_info.model_file_path, " failed:", st.ErrorMessage()); + graph_def->data = model_data; + TF_GraphImportGraphDef(tf_graph_, graph_def, opts, s); + if (TF_GetCode(s) != TF_OK) ORT_THROW("load TF model failed:", TF_Message(s)); + TF_SessionOptions* session_opts = TF_NewSessionOptions(); + sess_ = TF_NewSession(tf_graph_, session_opts, s); + if (TF_GetCode(s) != TF_OK) ORT_THROW("load TF model failed:", TF_Message(s)); + feed_.resize(static_cast(m->GetInputCount())); + for (size_t i = 0; i != feed_.size(); ++i) { + feed_[i] = GetOutputFromGraph(m->GetInputName(i).c_str(), tf_graph_); + } + fetches_.resize(static_cast(m->GetOutputCount())); + for (size_t i = 0; i != fetches_.size(); ++i) { + fetches_[i] = GetOutputFromGraph(m->GetOutputName(i).c_str(), tf_graph_); + } + } + ORT_DISALLOW_COPY_ASSIGNMENT_AND_MOVE(TensorflowTestSession); + std::chrono::duration Run(const OrtValue* const* input) override { + size_t input_len = feed_.size(); + std::vector feed_tensors(input_len); + for (size_t i = 0; i != input_len; ++i) { + void* input_buffer = nullptr; + ORT_THROW_ON_ERROR(OrtGetTensorMutableData(const_cast(input[i]), &input_buffer)); + assert(input_buffer != nullptr); + OrtTensorTypeAndShapeInfo* shape; + ORT_THROW_ON_ERROR(OrtGetTensorShapeAndType(input[i], &shape)); + size_t dim_count = OrtGetNumOfDimensions(shape); + std::vector dims(dim_count); + OrtGetDimensions(shape, dims.data(), dim_count); + int64_t ele_count = OrtGetTensorShapeElementCount(shape); + size_t buffer_length = ele_count * sizeof(float); + TF_Tensor* t = TF_AllocateTensor(TF_FLOAT, dims.data(), static_cast(dims.size()), buffer_length); + assert(t != nullptr); + feed_tensors[i] = t; + assert(TF_TensorByteSize(t) == buffer_length); + memcpy(TF_TensorData(t), input_buffer, buffer_length); + } + std::vector output_tensors(fetches_.size()); + TF_Status* s = TF_NewStatus(); + auto start = std::chrono::high_resolution_clock::now(); + TF_SessionRun(sess_, nullptr, feed_.data(), feed_tensors.data(), static_cast(feed_.size()), fetches_.data(), + output_tensors.data(), static_cast(fetches_.size()), nullptr, 0, nullptr, s); + auto end = std::chrono::high_resolution_clock::now(); + if (TF_GetCode(s) != TF_OK) ORT_THROW("run TF model failed:", TF_Message(s)); + TF_DeleteStatus(s); + return end - start; + } + ~TensorflowTestSession() override { + if (model_deleter.f != nullptr) { + model_deleter.f(model_deleter.param); + } + TF_Status* s = TF_NewStatus(); + TF_DeleteSession(sess_, s); + TF_DeleteStatus(s); + } +}; + +} // namespace perftest +} // namespace onnxruntime \ No newline at end of file