General enhancements/cleanups to test exes (#4109)

* General enhancements/cleanups to test exes
  - Support running onnxruntime_perf_test with no output file
    - if you're profiling the output file is often unused and can be very large
  - Allow failure to override early success if doing multiple runs of a test using running onnx_test_runner
    - e.g. if the second run fails that's more important as a final status
  - Clarify ownership semantics
  - Cleanup naming, line lengths, usage of references for required parameters etc.
This commit is contained in:
Scott McKay 2020-06-04 07:01:39 +10:00 committed by GitHub
parent 197da135eb
commit 16cef90e29
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 357 additions and 275 deletions

View file

@ -32,7 +32,7 @@ class FixedCountFinishCallbackImpl {
if (finish_event_) OrtCloseEvent(finish_event_);
}
::onnxruntime::common::Status fail(ORT_CALLBACK_INSTANCE pci) {
::onnxruntime::common::Status Fail(ORT_CALLBACK_INSTANCE pci) {
{
std::lock_guard<onnxruntime::OrtMutex> g(m_);
failed = true;
@ -41,7 +41,7 @@ class FixedCountFinishCallbackImpl {
return OnnxRuntimeSetEventWhenCallbackReturns(pci, finish_event_);
}
::onnxruntime::common::Status onFinished(size_t task_index, std::shared_ptr<T> result, ORT_CALLBACK_INSTANCE pci) {
::onnxruntime::common::Status OnFinished(size_t task_index, std::shared_ptr<T> result, ORT_CALLBACK_INSTANCE pci) {
int v;
{
std::lock_guard<onnxruntime::OrtMutex> g(m_);
@ -54,12 +54,12 @@ class FixedCountFinishCallbackImpl {
return ::onnxruntime::common::Status::OK();
}
bool shouldStop() {
bool ShouldStop() {
std::lock_guard<onnxruntime::OrtMutex> g(m_);
return failed;
}
//this function can only be invoked once
bool wait() {
bool Wait() {
ORT_ENFORCE(WaitAndCloseEvent(finish_event_).IsOK());
{
std::lock_guard<onnxruntime::OrtMutex> g(m_);

View file

@ -276,7 +276,7 @@ OrtValue* TensorToOrtValue(const ONNX_NAMESPACE::TensorProto& t, onnxruntime::te
return temp_value.release();
}
void LoopDataFile(int test_data_pb_fd, bool is_input, const TestModelInfo* modelinfo,
void LoopDataFile(int test_data_pb_fd, bool is_input, const TestModelInfo& modelinfo,
std::unordered_map<std::string, OrtValue*>& name_data_map, onnxruntime::test::HeapBuffer& b,
std::ostringstream& oss) {
google::protobuf::io::FileInputStream f(test_data_pb_fd, protobuf_block_size_in_bytes);
@ -332,7 +332,7 @@ void LoopDataFile(int test_data_pb_fd, bool is_input, const TestModelInfo* model
std::string value_name = data.name();
if (value_name.empty()) {
const size_t c = name_data_map.size();
value_name = is_input ? modelinfo->GetInputName(c) : modelinfo->GetOutputName(c);
value_name = is_input ? modelinfo.GetInputName(c) : modelinfo.GetOutputName(c);
}
auto pv = name_data_map.insert(std::make_pair(value_name, gvalue.release()));
@ -357,8 +357,8 @@ void LoopDataFile(int test_data_pb_fd, bool is_input, const TestModelInfo* model
} // namespace
TestModelInfo* TestModelInfo::LoadOnnxModel(_In_ const PATH_CHAR_TYPE* model_url) {
return new OnnxModelInfo(model_url);
std::unique_ptr<TestModelInfo> TestModelInfo::LoadOnnxModel(_In_ const PATH_CHAR_TYPE* model_url) {
return std::unique_ptr<TestModelInfo>(new OnnxModelInfo(model_url));
}
/**
@ -372,76 +372,73 @@ TestModelInfo* TestModelInfo::LoadOnnxModel(_In_ const PATH_CHAR_TYPE* model_url
class OnnxTestCase : public ITestCase {
private:
std::string test_case_name_;
std::vector<std::string> debuginfo_strings;
onnxruntime::OrtMutex m_;
mutable std::vector<std::string> debuginfo_strings_;
mutable onnxruntime::OrtMutex m_;
std::vector<std::basic_string<PATH_CHAR_TYPE>> test_data_dirs_;
std::string GetDatasetDebugInfoString(size_t dataset_id) override {
std::string GetDatasetDebugInfoString(size_t dataset_id) const override {
std::lock_guard<OrtMutex> l(m_);
if (dataset_id < debuginfo_strings.size()) {
return debuginfo_strings[dataset_id];
if (dataset_id < debuginfo_strings_.size()) {
return debuginfo_strings_[dataset_id];
}
// return empty string
return std::string();
}
void ConvertTestData(const std::vector<ONNX_NAMESPACE::TensorProto>& test_data_pbs, onnxruntime::test::HeapBuffer& b,
bool is_input,
std::unordered_map<std::string, OrtValue*>& out);
void ConvertTestData(const std::vector<ONNX_NAMESPACE::TensorProto>& test_data_pbs,
onnxruntime::test::HeapBuffer& b, bool is_input,
std::unordered_map<std::string, OrtValue*>& out) const;
std::once_flag model_parsed_;
std::once_flag config_parsed_;
double per_sample_tolerance_;
double relative_per_sample_tolerance_;
bool post_processing_;
TestModelInfo* model_info_;
std::unique_ptr<TestModelInfo> model_info_;
ORT_DISALLOW_COPY_ASSIGNMENT_AND_MOVE(OnnxTestCase);
public:
OnnxTestCase(const std::string& test_case_name, _In_ TestModelInfo* model, double default_per_sample_tolerance,
double default_relative_per_sample_tolerance);
~OnnxTestCase() override { delete model_info_; }
Status GetPerSampleTolerance(double* value) override;
Status GetRelativePerSampleTolerance(double* value) override;
Status GetPostProcessing(bool* value) override;
OnnxTestCase(const std::string& test_case_name, _In_ std::unique_ptr<TestModelInfo> model,
double default_per_sample_tolerance, double default_relative_per_sample_tolerance);
Status GetPerSampleTolerance(double* value) const override;
Status GetRelativePerSampleTolerance(double* value) const override;
Status GetPostProcessing(bool* value) const override;
const ONNX_NAMESPACE::ValueInfoProto* GetOutputInfoFromModel(size_t i) const override {
return model_info_->GetOutputInfoFromModel(i);
}
size_t GetDataCount() const override {
return test_data_dirs_.size();
}
size_t GetDataCount() const override { return test_data_dirs_.size(); }
const std::string& GetNodeName() const override { return model_info_->GetNodeName(); }
const PATH_CHAR_TYPE* GetModelUrl() const override { return model_info_->GetModelUrl(); }
const std::string& GetTestCaseName() const override {
return test_case_name_;
}
std::string GetTestCaseVersion() const override {
return model_info_->GetModelVersion();
}
const std::string& GetTestCaseName() const override { return test_case_name_; }
std::string GetTestCaseVersion() const override { return model_info_->GetModelVersion(); }
void LoadTestData(size_t id, onnxruntime::test::HeapBuffer& b, std::unordered_map<std::string, OrtValue*>&,
bool is_input) override;
bool is_input) const override;
};
ITestCase* CreateOnnxTestCase(const std::string& test_case_name, TestModelInfo* model,
double default_per_sample_tolerance, double default_relative_per_sample_tolerance) {
return new OnnxTestCase(test_case_name, model, default_per_sample_tolerance, default_relative_per_sample_tolerance);
std::unique_ptr<ITestCase> CreateOnnxTestCase(const std::string& test_case_name,
std::unique_ptr<TestModelInfo> model,
double default_per_sample_tolerance,
double default_relative_per_sample_tolerance) {
return std::unique_ptr<ITestCase>(new OnnxTestCase(test_case_name, std::move(model),
default_per_sample_tolerance,
default_relative_per_sample_tolerance));
}
Status OnnxTestCase::GetPerSampleTolerance(double* value) {
Status OnnxTestCase::GetPerSampleTolerance(double* value) const {
*value = per_sample_tolerance_;
return Status::OK();
}
Status OnnxTestCase::GetRelativePerSampleTolerance(double* value) {
Status OnnxTestCase::GetRelativePerSampleTolerance(double* value) const {
*value = relative_per_sample_tolerance_;
return Status::OK();
}
Status OnnxTestCase::GetPostProcessing(bool* value) {
Status OnnxTestCase::GetPostProcessing(bool* value) const {
*value = post_processing_;
return Status::OK();
}
@ -516,7 +513,7 @@ static void LoadTensors(const std::vector<PATH_STRING_TYPE>& pb_files,
void OnnxTestCase::LoadTestData(size_t id, onnxruntime::test::HeapBuffer& b,
std::unordered_map<std::string, OrtValue*>& name_data_map,
bool is_input) {
bool is_input) const {
if (id >= test_data_dirs_.size()) {
ORT_THROW("index out of bound");
}
@ -529,10 +526,10 @@ void OnnxTestCase::LoadTestData(size_t id, onnxruntime::test::HeapBuffer& b,
std::ostringstream oss;
{
std::lock_guard<OrtMutex> l(m_);
oss << debuginfo_strings[id];
oss << debuginfo_strings_[id];
}
try {
LoopDataFile(test_data_pb_fd, is_input, model_info_, name_data_map, b, oss);
LoopDataFile(test_data_pb_fd, is_input, *model_info_, name_data_map, b, oss);
} catch (std::exception& ex) {
std::ostringstream oss2;
oss2 << "parse data file \"" << ToMBString(test_data_pb) << "\" failed:" << ex.what();
@ -540,7 +537,7 @@ void OnnxTestCase::LoadTestData(size_t id, onnxruntime::test::HeapBuffer& b,
}
{
std::lock_guard<OrtMutex> l(m_);
debuginfo_strings[id] = oss.str();
debuginfo_strings_[id] = oss.str();
}
return;
}
@ -561,6 +558,7 @@ void OnnxTestCase::LoadTestData(size_t id, onnxruntime::test::HeapBuffer& b,
}
return true;
});
SortTensorFileNames(test_data_pb_files);
std::vector<ONNX_NAMESPACE::TensorProto> test_data_pbs;
@ -570,7 +568,7 @@ void OnnxTestCase::LoadTestData(size_t id, onnxruntime::test::HeapBuffer& b,
void OnnxTestCase::ConvertTestData(const std::vector<ONNX_NAMESPACE::TensorProto>& test_data_pbs,
onnxruntime::test::HeapBuffer& b,
bool is_input, std::unordered_map<std::string, OrtValue*>& out) {
bool is_input, std::unordered_map<std::string, OrtValue*>& out) const {
bool has_valid_names = true;
std::vector<std::string> var_names(test_data_pbs.size());
for (size_t input_index = 0; input_index != test_data_pbs.size(); ++input_index) {
@ -615,9 +613,9 @@ void OnnxTestCase::ConvertTestData(const std::vector<ONNX_NAMESPACE::TensorProto
}
}
OnnxTestCase::OnnxTestCase(const std::string& test_case_name, _In_ TestModelInfo* model,
OnnxTestCase::OnnxTestCase(const std::string& test_case_name, _In_ std::unique_ptr<TestModelInfo> model,
double default_per_sample_tolerance, double default_relative_per_sample_tolerance)
: test_case_name_(test_case_name), model_info_(model) {
: test_case_name_(test_case_name), model_info_(std::move(model)) {
std::basic_string<PATH_CHAR_TYPE> test_case_dir = model_info_->GetDir();
// parse config
@ -649,7 +647,7 @@ OnnxTestCase::OnnxTestCase(const std::string& test_case_name, _In_ TestModelInfo
if (f_type == OrtFileType::TYPE_DIR) {
std::basic_string<PATH_CHAR_TYPE> p = ConcatPathComponent<PATH_CHAR_TYPE>(test_case_dir, filename);
test_data_dirs_.push_back(p);
debuginfo_strings.push_back(ToMBString(p));
debuginfo_strings_.push_back(ToMBString(p));
}
return true;
});

View file

@ -19,8 +19,9 @@ class ValueInfoProto;
//One test case can contain multiple test data(input/output pairs)
class ITestCase {
public:
virtual void LoadTestData(size_t id, onnxruntime::test::HeapBuffer& b, std::unordered_map<std::string, OrtValue*>& name_data_map,
bool is_input) = 0;
virtual void LoadTestData(size_t id, onnxruntime::test::HeapBuffer& b,
std::unordered_map<std::string, OrtValue*>& name_data_map,
bool is_input) const = 0;
virtual const PATH_CHAR_TYPE* GetModelUrl() const = 0;
virtual const std::string& GetNodeName() const = 0;
virtual const ONNX_NAMESPACE::ValueInfoProto* GetOutputInfoFromModel(size_t i) const = 0;
@ -28,13 +29,13 @@ class ITestCase {
virtual const std::string& GetTestCaseName() const = 0;
virtual std::string GetTestCaseVersion() const = 0;
//a string to help identify the dataset
virtual std::string GetDatasetDebugInfoString(size_t dataset_id) = 0;
virtual std::string GetDatasetDebugInfoString(size_t dataset_id) const = 0;
//The number of input/output pairs
virtual size_t GetDataCount() const = 0;
virtual ~ITestCase() = default;
virtual ::onnxruntime::common::Status GetPerSampleTolerance(double* value) = 0;
virtual ::onnxruntime::common::Status GetRelativePerSampleTolerance(double* value) = 0;
virtual ::onnxruntime::common::Status GetPostProcessing(bool* value) = 0;
virtual ::onnxruntime::common::Status GetPerSampleTolerance(double* value) const = 0;
virtual ::onnxruntime::common::Status GetRelativePerSampleTolerance(double* value) const = 0;
virtual ::onnxruntime::common::Status GetPostProcessing(bool* value) const = 0;
};
class TestModelInfo {
@ -57,9 +58,11 @@ class TestModelInfo {
virtual std::string GetModelVersion() const { return ""; }
virtual ~TestModelInfo() = default;
static TestModelInfo* LoadOnnxModel(_In_ const PATH_CHAR_TYPE* model_url);
static std::unique_ptr<TestModelInfo> LoadOnnxModel(_In_ const PATH_CHAR_TYPE* model_url);
static const std::string unknown_version;
};
ITestCase* CreateOnnxTestCase(const std::string& test_case_name, TestModelInfo* model,
double default_per_sample_tolerance, double default_relative_per_sample_tolerance);
std::unique_ptr<ITestCase> CreateOnnxTestCase(const std::string& test_case_name,
std::unique_ptr<TestModelInfo> model,
double default_per_sample_tolerance,
double default_relative_per_sample_tolerance);

View file

@ -4,5 +4,11 @@
#include "TestCaseResult.h"
void TestCaseResult::SetResult(size_t task_id, EXECUTE_RESULT r) {
excution_result_[task_id] = r;
std::lock_guard<std::mutex> guard(result_mutex_);
if (execution_result_[task_id] == EXECUTE_RESULT::NOT_SET) {
execution_result_[task_id] = r;
} else if (r != EXECUTE_RESULT::SUCCESS && execution_result_[task_id] == EXECUTE_RESULT::SUCCESS) {
// store first failure
execution_result_[task_id] = r;
}
}

View file

@ -6,10 +6,12 @@
#include <vector>
#include <core/platform/env_time.h>
#include <cstring>
#include <mutex>
//result of a single test run: 1 model with 1 test dataset
enum class EXECUTE_RESULT {
SUCCESS = 0,
NOT_SET = 0,
SUCCESS = 1,
UNKNOWN_ERROR = -1,
WITH_EXCEPTION = -2,
RESULT_DIFFERS = -3,
@ -25,23 +27,24 @@ enum class EXECUTE_RESULT {
class TestCaseResult {
public:
TestCaseResult(size_t count, EXECUTE_RESULT result, const std::string& node_name1) : node_name(node_name1), excution_result_(count, result) {
::onnxruntime::SetTimeSpecToZero(&spent_time_);
TestCaseResult(size_t count, EXECUTE_RESULT result, const std::string& node_name1)
: node_name(node_name1), execution_result_(count, result) {
onnxruntime::SetTimeSpecToZero(&spent_time_);
}
void SetResult(size_t task_id, EXECUTE_RESULT r);
const std::vector<EXECUTE_RESULT>& GetExcutionResult() const {
return excution_result_;
return execution_result_;
}
//Time spent in Session::Run. It only make sense when SeqTestRunner was used
::onnxruntime::TIME_SPEC GetSpentTime() const {
onnxruntime::TIME_SPEC GetSpentTime() const {
return spent_time_;
}
//Time spent in Session::Run. It only make sense when SeqTestRunner was used
void SetSpentTime(const ::onnxruntime::TIME_SPEC& input) const {
void SetSpentTime(const onnxruntime::TIME_SPEC& input) const {
memcpy((void*)&spent_time_, &input, sizeof(input));
}
@ -49,6 +52,7 @@ class TestCaseResult {
const std::string node_name;
private:
::onnxruntime::TIME_SPEC spent_time_;
std::vector<EXECUTE_RESULT> excution_result_;
onnxruntime::TIME_SPEC spent_time_;
std::vector<EXECUTE_RESULT> execution_result_;
std::mutex result_mutex_;
};

View file

@ -85,7 +85,7 @@ int real_main(int argc, wchar_t* argv[], Ort::Env& env) {
int real_main(int argc, char* argv[], Ort::Env& env) {
#endif
// if this var is not empty, only run the tests with name in this list
std::vector<std::basic_string<PATH_CHAR_TYPE> > whitelisted_test_cases;
std::vector<std::basic_string<PATH_CHAR_TYPE>> whitelisted_test_cases;
int concurrent_session_runs = GetNumCpuCores();
bool enable_cpu_mem_arena = true;
ExecutionMode execution_mode = ExecutionMode::ORT_SEQUENTIAL;
@ -250,7 +250,7 @@ int real_main(int argc, char* argv[], Ort::Env& env) {
return -1;
}
std::vector<std::basic_string<PATH_CHAR_TYPE> > data_dirs;
std::vector<std::basic_string<PATH_CHAR_TYPE>> data_dirs;
TestResultStat stat;
for (int i = 0; i != argc; ++i) {
@ -437,7 +437,7 @@ int real_main(int argc, char* argv[], Ort::Env& env) {
ORT_TSTR("tf_mobilenet_v2_1.0_224"), ORT_TSTR("tf_mobilenet_v2_1.4_224"), ORT_TSTR("tf_nasnet_large"), ORT_TSTR("tf_pnasnet_large"), ORT_TSTR("tf_resnet_v1_50"), ORT_TSTR("tf_resnet_v1_101"), ORT_TSTR("tf_resnet_v1_101"),
ORT_TSTR("tf_resnet_v2_101"), ORT_TSTR("tf_resnet_v2_152"), ORT_TSTR("batchnorm_example_training_mode"), ORT_TSTR("batchnorm_epsilon_training_mode")};
std::unordered_set<std::basic_string<ORTCHAR_T> > all_disabled_tests(std::begin(immutable_broken_tests), std::end(immutable_broken_tests));
std::unordered_set<std::basic_string<ORTCHAR_T>> all_disabled_tests(std::begin(immutable_broken_tests), std::end(immutable_broken_tests));
if (enable_cuda) {
all_disabled_tests.insert(std::begin(cuda_flaky_tests), std::end(cuda_flaky_tests));
}
@ -455,9 +455,15 @@ int real_main(int argc, char* argv[], Ort::Env& env) {
all_disabled_tests.insert(std::begin(x86_disabled_tests), std::end(x86_disabled_tests));
#endif
std::vector<std::unique_ptr<ITestCase>> owned_tests;
std::vector<ITestCase*> tests;
LoadTests(data_dirs, whitelisted_test_cases, per_sample_tolerance, relative_per_sample_tolerance, all_disabled_tests,
[&tests](ITestCase* l) { tests.push_back(l); });
LoadTests(data_dirs, whitelisted_test_cases, per_sample_tolerance, relative_per_sample_tolerance,
all_disabled_tests,
[&owned_tests, &tests](std::unique_ptr<ITestCase> l) {
tests.push_back(l.get());
owned_tests.push_back(std::move(l));
});
TestEnv args(tests, stat, env, sf);
Status st = RunTests(args, p_models, concurrent_session_runs, static_cast<size_t>(repeat_count),
@ -466,9 +472,6 @@ int real_main(int argc, char* argv[], Ort::Env& env) {
fprintf(stderr, "%s\n", st.ErrorMessage().c_str());
return -1;
}
for (ITestCase* l : tests) {
delete l;
}
std::string res = stat.ToString();
fwrite(res.c_str(), 1, res.size(), stdout);
}

View file

@ -31,27 +31,26 @@ void ORT_CALLBACK RunTestCase(ORT_CALLBACK_INSTANCE pci, void* context, ORT_WORK
OnnxRuntimeCloseThreadpoolWork(work);
assert(context != nullptr);
TestCaseTask* task(static_cast<TestCaseTask*>(context));
ITestCase* info = task->env.tests[task->task_id];
ITestCase& info = *task->env.tests[task->task_id];
std::shared_ptr<TestCaseResult> ret;
try {
RunSingleTestCase(info, task->env.env, task->env.sf, task->concurrent_runs, task->repeat_count, task->pool, pci, [task](std::shared_ptr<TestCaseResult> result, ORT_CALLBACK_INSTANCE pci) {
return OnTestCaseFinished(pci, task, result);
});
return;
RunSingleTestCase(info, task->env.env, task->env.sf, task->concurrent_runs, task->repeat_count, task->pool, pci,
[task](std::shared_ptr<TestCaseResult> result, ORT_CALLBACK_INSTANCE pci) {
return OnTestCaseFinished(pci, task, result);
});
} catch (std::exception& ex) {
LOGF_DEFAULT(ERROR, "Test %s failed:%s", info->GetTestCaseName().c_str(), ex.what());
std::string node_name = info->GetNodeName();
ret = std::make_shared<TestCaseResult>(info->GetDataCount(), EXECUTE_RESULT::WITH_EXCEPTION, node_name);
}
auto status = OnTestCaseFinished(pci, task, ret);
if (!status.IsOK()) {
LOGF_DEFAULT(ERROR, "FATAL ERROR");
abort();
LOGF_DEFAULT(ERROR, "Test %s failed:%s", info.GetTestCaseName().c_str(), ex.what());
ret = std::make_shared<TestCaseResult>(info.GetDataCount(), EXECUTE_RESULT::WITH_EXCEPTION, info.GetNodeName());
auto status = OnTestCaseFinished(pci, task, ret);
if (!status.IsOK()) {
ORT_THROW("OnTestCaseFinished failed: ", status.ErrorMessage());
}
}
}
void PTestRunner::Start(ORT_CALLBACK_INSTANCE, size_t concurrent_runs) {
concurrent_runs = std::min<size_t>(std::max<size_t>(1, concurrent_runs), c_->GetDataCount());
concurrent_runs = std::min<size_t>(std::max<size_t>(1, concurrent_runs), c_.GetDataCount());
next_test_to_run = 0;
for (size_t i = 0; i != concurrent_runs; ++i) {
if (!ScheduleNew()) {
@ -62,7 +61,7 @@ void PTestRunner::Start(ORT_CALLBACK_INSTANCE, size_t concurrent_runs) {
bool PTestRunner::ScheduleNew() {
size_t next_test = next_test_to_run++;
if (next_test >= c_->GetDataCount()) return false;
if (next_test >= c_.GetDataCount()) return false;
DataTask* t = new DataTask{this, next_test};
Status st = CreateAndSubmitThreadpoolWork(RunSingleDataItem, t, tpool_);
if (!st.IsOK()) {
@ -76,22 +75,23 @@ bool PTestRunner::ScheduleNew() {
void PTestRunner::OnTaskFinished(size_t, EXECUTE_RESULT, ORT_CALLBACK_INSTANCE pci) noexcept {
try {
ScheduleNew();
if (++finished == c_->GetDataCount()) {
if (++finished == c_.GetDataCount()) {
//For each test case, only one DataTask can reach here
finish(pci);
Finish(pci);
}
} catch (std::exception& ex) {
LOGF_DEFAULT(ERROR, "%s:unrecoverable error:%s,exit...\n", c_->GetTestCaseName().c_str(), ex.what());
LOGF_DEFAULT(ERROR, "%s:unrecoverable error:%s,exit...\n", c_.GetTestCaseName().c_str(), ex.what());
abort();
} catch (...) {
LOGF_DEFAULT(ERROR, "%s:unrecoverable error,exit...\n", c_->GetTestCaseName().c_str());
LOGF_DEFAULT(ERROR, "%s:unrecoverable error,exit...\n", c_.GetTestCaseName().c_str());
abort();
}
}
PTestRunner::PTestRunner(OrtSession* session1,
ITestCase* c, PThreadPool tpool,
TestCaseCallBack on_finished1) : DataRunner(session1, c->GetTestCaseName(), c, on_finished1), next_test_to_run(0), finished(0), tpool_(tpool) {
const ITestCase& c, PThreadPool tpool,
TestCaseCallBack on_finished1)
: DataRunner(session1, c.GetTestCaseName(), c, on_finished1), next_test_to_run(0), finished(0), tpool_(tpool) {
}
void ORT_CALLBACK RunSingleDataItem(ORT_CALLBACK_INSTANCE instance, void* context, ORT_WORK work) {
@ -100,34 +100,35 @@ void ORT_CALLBACK RunSingleDataItem(ORT_CALLBACK_INSTANCE instance, void* contex
PTestRunner* env = task->env;
const size_t task_id = task->task_id;
delete task;
env->RunTask(task_id, instance, true);
env->RunTask(task_id, instance);
}
Status OnTestCaseFinished(ORT_CALLBACK_INSTANCE pci, TestCaseTask* task, std::shared_ptr<TestCaseResult> result) {
FixedCountFinishCallback* finished = task->env.finished;
auto task_id = task->task_id;
bool failed = false;
{
std::unique_ptr<TestCaseTask> unused(task);
TestEnv& env = task->env;
int next_test = env.next_test_to_run++;
if (static_cast<size_t>(next_test) < env.tests.size()) {
//schedule the next TestCase
std::unique_ptr<TestCaseTask> t(new TestCaseTask{env, next_test, task->concurrent_runs, task->repeat_count, task->pool});
Status st = CreateAndSubmitThreadpoolWork(RunTestCase, t.get(), task->pool);
if (st.IsOK()) {
t.release();
} else
return st;
std::unique_ptr<TestCaseTask> delete_finished_task(task);
TestEnv& env = task->env;
int next_test = env.next_test_to_run++;
if (static_cast<size_t>(next_test) < env.tests.size()) {
//schedule the next TestCase
std::unique_ptr<TestCaseTask> t(new TestCaseTask{env, next_test, task->concurrent_runs, task->repeat_count,
task->pool});
Status st = CreateAndSubmitThreadpoolWork(RunTestCase, t.get(), task->pool);
if (st.IsOK()) {
t.release();
} else {
return st;
}
}
if (failed)
return finished->fail(pci);
return finished->onFinished(task_id, result, pci);
return failed ? env.finished->Fail(pci) : env.finished->OnFinished(task_id, result, pci);
}
//Do not run this function in the thread pool passed in
static Status ParallelRunTests(TestEnv& env, int p_models, size_t current_runs, size_t repeat_count, PThreadPool pool) {
static Status ParallelRunTests(TestEnv& env, int p_models, size_t current_runs, size_t repeat_count,
PThreadPool pool) {
p_models = static_cast<int>(std::min<size_t>(p_models, env.tests.size()));
LOGF_DEFAULT(ERROR, "Running tests in parallel: at most %d models at any time", p_models);
env.next_test_to_run = p_models;
@ -144,7 +145,7 @@ static Status ParallelRunTests(TestEnv& env, int p_models, size_t current_runs,
throw;
}
}
bool ret = env.finished->wait();
bool ret = env.finished->Wait();
if (!ret) {
return Status(::onnxruntime::common::ONNXRUNTIME, ::onnxruntime::common::FAIL, "ParallelRunTests failed");
}
@ -155,10 +156,12 @@ static Status ParallelRunTests(TestEnv& env, int p_models, size_t current_runs,
Status RunTests(TestEnv& env, int p_models, int concurrent_runs, size_t repeat_count, PThreadPool tpool) {
TestResultStat& stat = env.stat;
stat.total_model_count = env.tests.size();
stat.total_test_case_count = std::accumulate(env.tests.begin(), env.tests.end(), static_cast<size_t>(0), [](size_t v, const ITestCase* info) {
return info->GetDataCount() + v;
});
stat.total_test_case_count = std::accumulate(env.tests.begin(), env.tests.end(), static_cast<size_t>(0),
[](size_t v, const ITestCase* info) {
return info->GetDataCount() + v;
});
std::vector<std::shared_ptr<TestCaseResult>> results;
if (p_models > 1 && env.tests.size() > 1) {
ORT_RETURN_IF_ERROR(ParallelRunTests(env, p_models, concurrent_runs, repeat_count, tpool));
results = env.finished->getResults();
@ -169,10 +172,12 @@ Status RunTests(TestEnv& env, int p_models, int concurrent_runs, size_t repeat_c
ORT_EVENT ev;
ORT_RETURN_IF_ERROR(CreateOnnxRuntimeEvent(&ev));
try {
RunSingleTestCase(env.tests[i], env.env, env.sf, concurrent_runs, repeat_count, tpool, nullptr, [&results, ev](std::shared_ptr<TestCaseResult> result, ORT_CALLBACK_INSTANCE pci) {
results.push_back(result);
return OnnxRuntimeSetEventWhenCallbackReturns(pci, ev);
});
RunSingleTestCase(*env.tests[i], env.env, env.sf, concurrent_runs, repeat_count, tpool, nullptr,
[&results, ev](std::shared_ptr<TestCaseResult> result, ORT_CALLBACK_INSTANCE pci) {
results.push_back(result);
return OnnxRuntimeSetEventWhenCallbackReturns(pci, ev);
});
ORT_RETURN_IF_ERROR(WaitAndCloseEvent(ev));
} catch (std::exception& ex) {
LOGF_DEFAULT(ERROR, "Test %s failed:%s", test_case_name, ex.what());
@ -185,13 +190,15 @@ Status RunTests(TestEnv& env, int p_models, int concurrent_runs, size_t repeat_c
}
for (size_t i = 0; i != env.tests.size(); ++i) {
if (!results[i]) {
stat.AddFailedTest(std::pair<std::string, std::string>(env.tests[i]->GetTestCaseName(), env.tests[i]->GetTestCaseVersion()));
stat.AddFailedTest(std::pair<std::string, std::string>(env.tests[i]->GetTestCaseName(),
env.tests[i]->GetTestCaseVersion()));
continue;
}
const TestCaseResult& r = *results[i];
for (const EXECUTE_RESULT res : r.GetExcutionResult()) {
if (res != EXECUTE_RESULT::SUCCESS && res != EXECUTE_RESULT::NOT_SUPPORT) {
stat.AddFailedTest(std::pair<std::string, std::string>(env.tests[i]->GetTestCaseName(), env.tests[i]->GetTestCaseVersion()));
stat.AddFailedTest(std::pair<std::string, std::string>(env.tests[i]->GetTestCaseName(),
env.tests[i]->GetTestCaseVersion()));
}
switch (res) {
case EXECUTE_RESULT::SUCCESS:
@ -239,7 +246,7 @@ void LoadTests(const std::vector<std::basic_string<PATH_CHAR_TYPE>>& input_paths
const std::vector<std::basic_string<PATH_CHAR_TYPE>>& whitelisted_test_cases,
double default_per_sample_tolerance, double default_relative_per_sample_tolerance,
const std::unordered_set<std::basic_string<ORTCHAR_T>>& disabled_tests,
const std::function<void(ITestCase*)>& process_function) {
const std::function<void(std::unique_ptr<ITestCase>)>& process_function) {
std::vector<std::basic_string<PATH_CHAR_TYPE>> paths(input_paths);
while (!paths.empty()) {
std::basic_string<PATH_CHAR_TYPE> node_data_root_path = paths.back();
@ -266,22 +273,31 @@ void LoadTests(const std::vector<std::basic_string<PATH_CHAR_TYPE>>& input_paths
std::basic_string<PATH_CHAR_TYPE> p = ConcatPathComponent<PATH_CHAR_TYPE>(node_data_root_path, filename_str);
ITestCase* l = CreateOnnxTestCase(ToMBString(test_case_name), TestModelInfo::LoadOnnxModel(p.c_str()),
default_per_sample_tolerance, default_relative_per_sample_tolerance);
process_function(l);
std::unique_ptr<TestModelInfo> model_info(TestModelInfo::LoadOnnxModel(p.c_str()));
std::unique_ptr<ITestCase> l = CreateOnnxTestCase(ToMBString(test_case_name), std::move(model_info),
default_per_sample_tolerance,
default_relative_per_sample_tolerance);
process_function(std::move(l));
return true;
});
}
}
SeqTestRunner::SeqTestRunner(OrtSession* session1,
ITestCase* c, size_t repeat_count,
TestCaseCallBack on_finished1) : DataRunner(session1, c->GetTestCaseName(), c, on_finished1), repeat_count_(repeat_count) {
const ITestCase& c, size_t repeat_count,
TestCaseCallBack on_finished1)
: DataRunner(session1, c.GetTestCaseName(), c, on_finished1), repeat_count_(repeat_count) {
}
DataRunner::DataRunner(OrtSession* session1, const std::string& test_case_name1, ITestCase* c, TestCaseCallBack on_finished1) : test_case_name_(test_case_name1), c_(c), session(session1), on_finished(on_finished1), default_allocator(onnxruntime::make_unique<MockedOrtAllocator>()) {
std::string s = c->GetNodeName();
result = std::make_shared<TestCaseResult>(c->GetDataCount(), EXECUTE_RESULT::UNKNOWN_ERROR, s);
DataRunner::DataRunner(OrtSession* session1, const std::string& test_case_name1, const ITestCase& c,
TestCaseCallBack on_finished1)
: test_case_name_(test_case_name1),
c_(c),
session(session1),
on_finished(on_finished1),
default_allocator(onnxruntime::make_unique<MockedOrtAllocator>()) {
std::string s = c_.GetNodeName();
result = std::make_shared<TestCaseResult>(c_.GetDataCount(), EXECUTE_RESULT::NOT_SET, s);
SetTimeSpecToZero(&spent_time_);
}
@ -289,24 +305,23 @@ DataRunner::~DataRunner() {
Ort::GetApi().ReleaseSession(session);
}
void DataRunner::RunTask(size_t task_id, ORT_CALLBACK_INSTANCE pci, bool store_result) {
void DataRunner::RunTask(size_t task_id, ORT_CALLBACK_INSTANCE pci) {
EXECUTE_RESULT res = EXECUTE_RESULT::UNKNOWN_ERROR;
try {
res = RunTaskImpl(task_id);
} catch (std::exception& ex) {
res = EXECUTE_RESULT::WITH_EXCEPTION;
LOGS_DEFAULT(ERROR) << c_->GetTestCaseName() << ":" << ex.what();
}
if (store_result) {
result->SetResult(task_id, res);
LOGS_DEFAULT(ERROR) << c_.GetTestCaseName() << ":" << ex.what();
}
result->SetResult(task_id, res);
OnTaskFinished(task_id, res, pci);
}
EXECUTE_RESULT DataRunner::RunTaskImpl(size_t task_id) {
onnxruntime::test::HeapBuffer holder;
std::unordered_map<std::string, OrtValue*> feeds;
c_->LoadTestData(task_id, holder, feeds, true);
c_.LoadTestData(task_id, holder, feeds, true);
// Create output feed
size_t output_count = 0;
@ -341,8 +356,8 @@ EXECUTE_RESULT DataRunner::RunTaskImpl(size_t task_id) {
}
GetMonotonicTimeCounter(&start_time);
Ort::ThrowOnError(Ort::GetApi().Run(session, nullptr, input_names.data(), input_values.Data(),
static_cast<size_t>(input_values.Length()), output_names_raw_ptr.data(), output_count,
output_values.Data()));
static_cast<size_t>(input_values.Length()), output_names_raw_ptr.data(),
output_count, output_values.Data()));
}
GetMonotonicTimeCounter(&end_time);
AccumulateTimeSpec(&spent_time_, &start_time, &end_time);
@ -351,22 +366,22 @@ EXECUTE_RESULT DataRunner::RunTaskImpl(size_t task_id) {
double relative_per_sample_tolerance;
bool post_procesing;
Status status;
if (!(status = c_->GetPerSampleTolerance(&per_sample_tolerance)).IsOK()) {
if (!(status = c_.GetPerSampleTolerance(&per_sample_tolerance)).IsOK()) {
LOGS_DEFAULT(ERROR) << status.ErrorMessage() << "\n";
return StatusCodeToExecuteResult(status.Code());
}
if (!(status = c_->GetRelativePerSampleTolerance(&relative_per_sample_tolerance)).IsOK()) {
if (!(status = c_.GetRelativePerSampleTolerance(&relative_per_sample_tolerance)).IsOK()) {
LOGS_DEFAULT(ERROR) << status.ErrorMessage() << "\n";
return StatusCodeToExecuteResult(status.Code());
}
if (!(status = c_->GetPostProcessing(&post_procesing)).IsOK()) {
if (!(status = c_.GetPostProcessing(&post_procesing)).IsOK()) {
LOGS_DEFAULT(ERROR) << status.ErrorMessage() << "\n";
return StatusCodeToExecuteResult(status.Code());
}
//TODO: if there are no output value files, just skip the validation
std::unordered_map<std::string, OrtValue*> expected_output_values;
c_->LoadTestData(task_id, holder, expected_output_values, false);
c_.LoadTestData(task_id, holder, expected_output_values, false);
std::unordered_map<std::string, OrtValue*> name_fetch_output_map;
std::unordered_map<std::string, const ONNX_NAMESPACE::ValueInfoProto*> name_output_value_info_proto;
@ -374,7 +389,7 @@ EXECUTE_RESULT DataRunner::RunTaskImpl(size_t task_id) {
for (auto& output_name : output_names) {
// p_fetches is filled in the order of output_names.
name_fetch_output_map[output_name] = output_values.Get(i);
const ONNX_NAMESPACE::ValueInfoProto* infoProto = c_->GetOutputInfoFromModel(i);
const ONNX_NAMESPACE::ValueInfoProto* infoProto = c_.GetOutputInfoFromModel(i);
if (infoProto != nullptr) name_output_value_info_proto.insert(std::make_pair(infoProto->name(), infoProto));
i++;
}
@ -447,44 +462,54 @@ EXECUTE_RESULT DataRunner::RunTaskImpl(size_t task_id) {
}
void SeqTestRunner::Start(ORT_CALLBACK_INSTANCE pci, size_t) {
const size_t data_count = c_->GetDataCount();
for (size_t idx_repeat = 0; idx_repeat != repeat_count_; ++idx_repeat)
const size_t data_count = c_.GetDataCount();
for (size_t idx_repeat = 0; idx_repeat != repeat_count_; ++idx_repeat) {
for (size_t idx_data = 0; idx_data != data_count; ++idx_data) {
RunTask(idx_data, nullptr, idx_repeat == 0);
RunTask(idx_data, nullptr);
}
finish(pci);
}
Finish(pci);
}
void RunSingleTestCase(ITestCase* info, Ort::Env& env, const Ort::SessionOptions& sf, size_t concurrent_runs, size_t repeat_count, PThreadPool tpool, ORT_CALLBACK_INSTANCE pci, TestCaseCallBack on_finished) {
void RunSingleTestCase(const ITestCase& info, Ort::Env& env, const Ort::SessionOptions& sf,
size_t concurrent_runs, size_t repeat_count, PThreadPool tpool,
ORT_CALLBACK_INSTANCE pci, TestCaseCallBack on_finished) {
std::shared_ptr<TestCaseResult> ret;
size_t data_count = info->GetDataCount();
size_t data_count = info.GetDataCount();
try {
DataRunner* r = nullptr;
std::string node_name = info->GetNodeName();
std::unique_ptr<DataRunner> r;
std::string node_name = info.GetNodeName();
auto sf2 = sf.Clone();
sf2.SetLogId(info->GetTestCaseName().c_str());
Ort::Session session_object{env, info->GetModelUrl(), sf2};
LOGF_DEFAULT(INFO, "testing %s\n", info->GetTestCaseName().c_str());
sf2.SetLogId(info.GetTestCaseName().c_str());
Ort::Session session_object{env, info.GetModelUrl(), sf2};
LOGF_DEFAULT(INFO, "testing %s\n", info.GetTestCaseName().c_str());
//temp hack. Because we have no resource control. We may not have enough memory to run this test in parallel
if (info->GetTestCaseName() == "coreml_FNS-Candy_ImageNet")
if (info.GetTestCaseName() == "coreml_FNS-Candy_ImageNet") {
concurrent_runs = 1;
}
if (concurrent_runs > 1 && data_count > 1) {
r = new PTestRunner(session_object.release(), info, tpool, on_finished);
r.reset(new PTestRunner(session_object.release(), info, tpool, on_finished));
} else {
r = new SeqTestRunner(session_object.release(), info, repeat_count, on_finished);
r.reset(new SeqTestRunner(session_object.release(), info, repeat_count, on_finished));
}
r->Start(pci, concurrent_runs);
// DataRunner::Finish will delete itself, so now that we know everything has started without any exceptions
// we can release it from the unique_ptr
r.release();
return;
} catch (const Ort::Exception& ex) {
if (ex.GetOrtErrorCode() != ORT_NOT_IMPLEMENTED)
if (ex.GetOrtErrorCode() != ORT_NOT_IMPLEMENTED) {
throw;
}
LOGF_DEFAULT(ERROR, "Test %s failed:%s", info->GetTestCaseName().c_str(), ex.what());
LOGF_DEFAULT(ERROR, "Test %s failed:%s", info.GetTestCaseName().c_str(), ex.what());
std::string node_name;
ret = std::make_shared<TestCaseResult>(data_count, EXECUTE_RESULT::NOT_SUPPORT, "");
} catch (onnxruntime::NotImplementedException& ex) {
LOGF_DEFAULT(ERROR, "Test %s failed:%s", info->GetTestCaseName().c_str(), ex.what());
LOGF_DEFAULT(ERROR, "Test %s failed:%s", info.GetTestCaseName().c_str(), ex.what());
std::string node_name;
ret = std::make_shared<TestCaseResult>(data_count, EXECUTE_RESULT::NOT_SUPPORT, "");
}

View file

@ -15,7 +15,8 @@
#include "testenv.h"
#include "sync_api.h"
typedef std::function<::onnxruntime::common::Status(std::shared_ptr<TestCaseResult> result, ORT_CALLBACK_INSTANCE pci)> TestCaseCallBack;
typedef std::function<onnxruntime::common::Status(std::shared_ptr<TestCaseResult> result, ORT_CALLBACK_INSTANCE pci)>
TestCaseCallBack;
struct TestCaseTask {
TestEnv& env;
@ -28,7 +29,8 @@ struct TestCaseTask {
void ORT_CALLBACK RunTestCase(ORT_CALLBACK_INSTANCE instance, void* context, ORT_WORK work);
void ORT_CALLBACK RunSingleDataItem(ORT_CALLBACK_INSTANCE instance, void* context, ORT_WORK work);
::onnxruntime::common::Status OnTestCaseFinished(ORT_CALLBACK_INSTANCE pci, TestCaseTask* task, std::shared_ptr<TestCaseResult> result);
::onnxruntime::common::Status OnTestCaseFinished(ORT_CALLBACK_INSTANCE pci, TestCaseTask* task,
std::shared_ptr<TestCaseResult> result);
struct MockedOrtAllocator;
@ -37,7 +39,7 @@ class DataRunner {
typedef TestCaseCallBack CALL_BACK;
std::shared_ptr<TestCaseResult> result;
std::string test_case_name_;
ITestCase* c_;
const ITestCase& c_;
//Time spent in Session::Run. It only make sense when SeqTestRunner was used
::onnxruntime::TIME_SPEC spent_time_;
@ -49,14 +51,15 @@ class DataRunner {
ORT_DISALLOW_COPY_AND_ASSIGNMENT(DataRunner);
public:
DataRunner(OrtSession* session1, const std::string& test_case_name1, ITestCase* c, TestCaseCallBack on_finished1);
DataRunner(OrtSession* session1, const std::string& test_case_name1, const ITestCase& c,
TestCaseCallBack on_finished1);
virtual void OnTaskFinished(size_t task_id, EXECUTE_RESULT res, ORT_CALLBACK_INSTANCE pci) noexcept = 0;
void RunTask(size_t task_id, ORT_CALLBACK_INSTANCE pci, bool store_result);
void RunTask(size_t task_id, ORT_CALLBACK_INSTANCE pci);
virtual ~DataRunner();
virtual void Start(ORT_CALLBACK_INSTANCE pci, size_t concurrent_runs) = 0;
void finish(ORT_CALLBACK_INSTANCE pci) {
void Finish(ORT_CALLBACK_INSTANCE pci) {
std::shared_ptr<TestCaseResult> res = result;
CALL_BACK callback = on_finished;
res->SetSpentTime(spent_time_);
@ -64,7 +67,7 @@ class DataRunner {
for (size_t i = 0; i != er.size(); ++i) {
EXECUTE_RESULT r = er[i];
if (r == EXECUTE_RESULT::SUCCESS) continue;
std::string s = c_->GetDatasetDebugInfoString(i);
std::string s = c_.GetDatasetDebugInfoString(i);
switch (r) {
case EXECUTE_RESULT::RESULT_DIFFERS:
LOGF_DEFAULT(ERROR, "%s: result differs. Dataset:%s\n", test_case_name_.c_str(), s.c_str());
@ -96,9 +99,7 @@ class SeqTestRunner : public DataRunner {
size_t repeat_count_;
public:
SeqTestRunner(OrtSession* session1,
ITestCase* c, size_t repeat_count,
TestCaseCallBack on_finished1);
SeqTestRunner(OrtSession* session1, const ITestCase& c, size_t repeat_count, TestCaseCallBack on_finished1);
void Start(ORT_CALLBACK_INSTANCE pci, size_t concurrent_runs) override;
void OnTaskFinished(size_t, EXECUTE_RESULT, ORT_CALLBACK_INSTANCE) noexcept override {}
@ -113,9 +114,7 @@ class PTestRunner : public DataRunner {
public:
void Start(ORT_CALLBACK_INSTANCE pci, size_t concurrent_runs) override;
PTestRunner(OrtSession* session1,
ITestCase* c, PThreadPool tpool,
TestCaseCallBack on_finished1);
PTestRunner(OrtSession* session1, const ITestCase& c, PThreadPool tpool, TestCaseCallBack on_finished1);
private:
bool ScheduleNew();
@ -131,10 +130,10 @@ void LoadTests(const std::vector<std::basic_string<PATH_CHAR_TYPE>>& input_paths
const std::vector<std::basic_string<PATH_CHAR_TYPE>>& whitelisted_test_cases,
double default_per_sample_tolerance, double default_relative_per_sample_tolerance,
const std::unordered_set<std::basic_string<ORTCHAR_T>>& disabled_tests,
const std::function<void(ITestCase*)>& process_function);
const std::function<void(std::unique_ptr<ITestCase>)>& process_function);
//Do not run this function in the thread pool passed in
::onnxruntime::common::Status RunTests(TestEnv& env, int p_models, int concurrent_runs, size_t repeat_count, PThreadPool tpool);
onnxruntime::common::Status RunTests(TestEnv& env, int p_models, int concurrent_runs, size_t repeat_count, PThreadPool tpool);
EXECUTE_RESULT StatusCodeToExecuteResult(int input);
void RunSingleTestCase(ITestCase* info, Ort::Env& env, const Ort::SessionOptions& sf, size_t concurrent_runs,
void RunSingleTestCase(const ITestCase& info, Ort::Env& env, const Ort::SessionOptions& sf, size_t concurrent_runs,
size_t repeat_count, PThreadPool tpool, ORT_CALLBACK_INSTANCE pci, TestCaseCallBack on_finished);

View file

@ -88,8 +88,8 @@ Status OnnxRuntimeSetEventWhenCallbackReturns(ORT_CALLBACK_INSTANCE pci, ORT_EVE
finish_event->finish_event_data.notify_all();
return Status::OK();
}
pci->AddEvent(finish_event);
return Status::OK();
pci->AddEvent(finish_event);
return Status::OK();
}
void OnnxRuntimeCallbackInstance::AddEvent(ORT_EVENT event) {

View file

@ -6,10 +6,16 @@
#include <core/session/onnxruntime_cxx_api.h>
using onnxruntime::Status;
TestEnv::TestEnv(const std::vector<ITestCase*>& tests1, TestResultStat& stat1, Ort::Env& env1, Ort::SessionOptions& sf1)
: tests(tests1), next_test_to_run(0), stat(stat1), finished(new FixedCountFinishCallback(static_cast<int>(tests1.size()))), env(env1), sf(sf1) {
TestEnv::TestEnv(const std::vector<ITestCase*>& tests1, TestResultStat& stat1, Ort::Env& env1,
Ort::SessionOptions& sf1)
: tests(tests1),
next_test_to_run(0),
stat(stat1),
finished(new FixedCountFinishCallback(static_cast<int>(tests1.size()))),
env(env1),
sf(sf1) {
}
TestEnv::~TestEnv() {
delete finished;
// need dtor in .cc so 'finished' can be cleaned up as TestCaseResult only has a forward declaration in the header.
}

View file

@ -19,7 +19,7 @@ class TestEnv {
std::vector<ITestCase*> tests;
std::atomic_int next_test_to_run;
TestResultStat& stat;
FixedCountFinishCallback* finished;
std::unique_ptr<FixedCountFinishCallback> finished;
Ort::Env& env;
const Ort::SessionOptions& sf;
TestEnv(const std::vector<ITestCase*>& tests, TestResultStat& stat1, Ort::Env& env, Ort::SessionOptions& sf1);

View file

@ -7,7 +7,7 @@
#include <core/platform/env.h>
TestModelInfo* TFModelInfo::Create(_In_ const PATH_CHAR_TYPE* model_url) {
std::unique_ptr<TestModelInfo> TFModelInfo::Create(_In_ const PATH_CHAR_TYPE* model_url) {
auto ret = std::unique_ptr<TFModelInfo>(new TFModelInfo{});
ret->model_url_ = model_url;
std::basic_string<PATH_CHAR_TYPE> meta_file_path = model_url;
@ -44,11 +44,11 @@ TestModelInfo* TFModelInfo::Create(_In_ const PATH_CHAR_TYPE* model_url) {
} else if (line.compare(0, 7, "output=") == 0) {
ret->output_names_.push_back(line.substr(7));
} else {
ORT_THROW("unknow line:", line.size());
ORT_THROW("unknown line:", line.size());
}
}
return ret.release();
return std::move(ret);
}
int TFModelInfo::GetInputCount() const { return static_cast<int>(input_names_.size()); }

View file

@ -20,7 +20,7 @@ class TFModelInfo : public TestModelInfo {
const std::string& GetOutputName(size_t i) const override;
~TFModelInfo() override = default;
static TestModelInfo* Create(_In_ const PATH_CHAR_TYPE* model_url);
static std::unique_ptr<TestModelInfo> Create(_In_ const PATH_CHAR_TYPE* model_url);
private:
TFModelInfo() = default;

View file

@ -25,7 +25,7 @@ namespace perftest {
/*static*/ void CommandLineParser::ShowUsage() {
printf(
"perf_test [options...] model_path result_file\n"
"perf_test [options...] model_path [result_file]\n"
"Options:\n"
"\t-m [test_mode]: Specifies the test mode. Value could be 'duration' or 'times'.\n"
"\t\tProvide 'duration' to run the test for a fix duration, and 'times' to repeated for a certain times. \n"
@ -40,7 +40,7 @@ namespace perftest {
"\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"
"\t-s: Show statistics result, like P75, P90.\n"
"\t-s: Show statistics result, like P75, P90. If no result_file provided this defaults to on.\n"
"\t-v: Show verbose information.\n"
"\t-x [intra_op_num_threads]: Sets the number of threads used to parallelize the execution within nodes, A value of 0 means ORT will pick a default. Must >=0.\n"
"\t-y [inter_op_num_threads]: Sets the number of threads used to parallelize the execution of the graph (across nodes), A value of 0 means ORT will pick a default. Must >=0.\n"
@ -187,10 +187,19 @@ namespace perftest {
// parse model_path and result_file_path
argc -= optind;
argv += optind;
if (argc != 2) return false;
switch (argc) {
case 2:
test_config.model_info.result_file_path = argv[1];
break;
case 1:
test_config.run_config.f_dump_statistics = true;
break;
default:
return false;
}
test_config.model_info.model_file_path = argv[0];
test_config.model_info.result_file_path = argv[1];
return true;
}

View file

@ -6,6 +6,7 @@
#include <random>
#include "command_args_parser.h"
#include "performance_runner.h"
#include <google/protobuf/stubs/common.h>
using namespace onnxruntime;
const OrtApi* g_ort = NULL;
@ -66,5 +67,8 @@ int main(int argc, char* argv[]) {
fprintf(stderr, "%s\n", ex.what());
retval = -1;
}
::google::protobuf::ShutdownProtobufLibrary();
return retval;
}

View file

@ -26,13 +26,15 @@ std::chrono::duration<double> OnnxRuntimeTestSession::Run() {
OnnxRuntimeTestSession::OnnxRuntimeTestSession(Ort::Env& env, std::random_device& rd,
const PerformanceTestConfig& performance_test_config,
const TestModelInfo* m)
: rand_engine_(rd()), input_names_(m->GetInputCount()), input_length_(m->GetInputCount()) {
const TestModelInfo& m)
: rand_engine_(rd()), input_names_(m.GetInputCount()), input_length_(m.GetInputCount()) {
Ort::SessionOptions session_options;
const std::string& provider_name = performance_test_config.machine_config.provider_type_name;
if (provider_name == onnxruntime::kDnnlExecutionProvider) {
#ifdef USE_DNNL
Ort::ThrowOnError(OrtSessionOptionsAppendExecutionProvider_Dnnl(session_options, performance_test_config.run_config.enable_cpu_mem_arena ? 1 : 0));
Ort::ThrowOnError(
OrtSessionOptionsAppendExecutionProvider_Dnnl(session_options,
performance_test_config.run_config.enable_cpu_mem_arena ? 1 : 0));
#else
ORT_THROW("DNNL is not supported in this build\n");
#endif
@ -81,8 +83,9 @@ OnnxRuntimeTestSession::OnnxRuntimeTestSession(Ort::Env& env, std::random_device
#endif
} else if (provider_name == onnxruntime::kAclExecutionProvider) {
#ifdef USE_ACL
Ort::ThrowOnError(OrtSessionOptionsAppendExecutionProvider_ACL(session_options,
performance_test_config.run_config.enable_cpu_mem_arena ? 1 : 0));
Ort::ThrowOnError(
OrtSessionOptionsAppendExecutionProvider_ACL(session_options,
performance_test_config.run_config.enable_cpu_mem_arena ? 1 : 0));
#else
ORT_THROW("Acl is not supported in this build\n");
#endif
@ -146,32 +149,32 @@ OnnxRuntimeTestSession::OnnxRuntimeTestSession(Ort::Env& env, std::random_device
output_names_raw_ptr[i] = output_names_[i].c_str();
}
size_t input_count = static_cast<size_t>(m->GetInputCount());
size_t input_count = static_cast<size_t>(m.GetInputCount());
for (size_t i = 0; i != input_count; ++i) {
input_names_[i] = strdup(m->GetInputName(i).c_str());
input_names_[i] = strdup(m.GetInputName(i).c_str());
}
}
bool OnnxRuntimeTestSession::PopulateGeneratedInputTestData()
{
bool OnnxRuntimeTestSession::PopulateGeneratedInputTestData() {
// iterate over all input nodes
for (size_t i = 0; i < static_cast<size_t>(input_length_); i++) {
Ort::TypeInfo type_info = session_.GetInputTypeInfo(i);
Ort::MemoryInfo memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
if (type_info.GetONNXType() == ONNX_TYPE_TENSOR) {
auto tensor_info = type_info.GetTensorTypeAndShapeInfo();
std::vector<int64_t> input_node_dim = tensor_info.GetShape();
auto tensor_info = type_info.GetTensorTypeAndShapeInfo();
std::vector<int64_t> input_node_dim = tensor_info.GetShape();
// free dimensions are treated as 1
for (int64_t& dim : input_node_dim) {
if (dim == -1) {
dim = 1;
}
// free dimensions are treated as 1
for (int64_t& dim : input_node_dim) {
if (dim == -1) {
dim = 1;
}
// default allocator doesn't have to be freed by user
auto allocator = static_cast<OrtAllocator*>(Ort::AllocatorWithDefaultOptions());
Ort::Value input_tensor = Ort::Value::CreateTensor(allocator, (const int64_t*)input_node_dim.data(), input_node_dim.size(), tensor_info.GetElementType());
PreLoadTestData(0, i, input_tensor.release());
}
// default allocator doesn't have to be freed by user
auto allocator = static_cast<OrtAllocator*>(Ort::AllocatorWithDefaultOptions());
Ort::Value input_tensor = Ort::Value::CreateTensor(allocator, (const int64_t*)input_node_dim.data(),
input_node_dim.size(), tensor_info.GetElementType());
PreLoadTestData(0, i, input_tensor.release());
}
}
return true;

View file

@ -12,7 +12,7 @@ namespace perftest {
class OnnxRuntimeTestSession : public TestSession {
public:
OnnxRuntimeTestSession(Ort::Env& env, std::random_device& rd, const PerformanceTestConfig& performance_test_config,
const TestModelInfo* m);
const TestModelInfo& m);
void PreLoadTestData(size_t test_data_id, size_t input_id, OrtValue* value) override {
if (test_inputs_.size() < test_data_id + 1) {

View file

@ -41,6 +41,64 @@ Eigen::ThreadPoolInterface* GetDefaultThreadPool(const onnxruntime::Env& env) {
namespace onnxruntime {
namespace perftest {
void PerformanceResult::DumpToFile(const std::basic_string<ORTCHAR_T>& path, bool f_include_statistics) const {
bool have_file = !path.empty();
std::ofstream outfile;
if (have_file) {
outfile.open(path, std::ofstream::out | std::ofstream::app);
if (!outfile.good()) {
// at least provide some info on the run
std::cerr << "failed to open result file '" << path.c_str() << "'. will dump stats to output.\n";
have_file = false;
f_include_statistics = true;
}
}
if (have_file) {
for (size_t runs = 0; runs < time_costs.size(); runs++) {
outfile << model_name << "," << time_costs[runs] << "," << peak_workingset_size << ","
<< average_CPU_usage << "," << runs << std::endl;
}
} else {
// match formatting of the initial output from PerformanceRunner::Run
std::cout << "Avg CPU usage:" << average_CPU_usage
<< "\nPeak working set size:" << peak_workingset_size
<< "\nRuns:" << time_costs.size() << std::endl;
}
if (!time_costs.empty() && f_include_statistics) {
std::vector<double> sorted_time = time_costs;
size_t total = sorted_time.size();
size_t n50 = static_cast<size_t>(total * 0.5);
size_t n90 = static_cast<size_t>(total * 0.9);
size_t n95 = static_cast<size_t>(total * 0.95);
size_t n99 = static_cast<size_t>(total * 0.99);
size_t n999 = static_cast<size_t>(total * 0.999);
std::sort(sorted_time.begin(), sorted_time.end());
auto output_stats = [&](std::ostream& ostream) {
ostream << "Min Latency is " << sorted_time[0] << "sec\n";
ostream << "Max Latency is " << sorted_time[total - 1] << "sec\n";
ostream << "P50 Latency is " << sorted_time[n50] << "sec\n";
ostream << "P90 Latency is " << sorted_time[n90] << "sec\n";
ostream << "P95 Latency is " << sorted_time[n95] << "sec\n";
ostream << "P99 Latency is " << sorted_time[n99] << "sec\n";
ostream << "P999 Latency is " << sorted_time[n999] << "sec" << std::endl;
};
if (have_file) {
outfile << std::endl;
output_stats(outfile);
}
output_stats(std::cout);
}
}
Status PerformanceRunner::Run() {
if (!Initialize()) {
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "failed to initialize.");
@ -51,7 +109,7 @@ Status PerformanceRunner::Run() {
// TODO: start profiling
// if (!performance_test_config_.run_config.profile_file.empty())
performance_result_.start_ = std::chrono::high_resolution_clock::now();
performance_result_.start = std::chrono::high_resolution_clock::now();
std::unique_ptr<utils::ICPUUsage> p_ICPUUsage = utils::CreateICPUUsage();
switch (performance_test_config_.run_config.test_mode) {
@ -64,7 +122,7 @@ Status PerformanceRunner::Run() {
default:
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "unknown test mode.");
}
performance_result_.end_ = std::chrono::high_resolution_clock::now();
performance_result_.end = std::chrono::high_resolution_clock::now();
performance_result_.average_CPU_usage = p_ICPUUsage->GetUsage();
performance_result_.peak_workingset_size = utils::GetPeakWorkingSetSize();
@ -72,7 +130,7 @@ Status PerformanceRunner::Run() {
std::chrono::duration<double> session_create_duration = session_create_end_ - session_create_start_;
// TODO: end profiling
// if (!performance_test_config_.run_config.profile_file.empty()) session_object->EndProfiling();
std::chrono::duration<double> inference_duration = performance_result_.end_ - performance_result_.start_;
std::chrono::duration<double> inference_duration = performance_result_.end - performance_result_.start;
std::cout << "Session creation time cost:" << session_create_duration.count() << " s" << std::endl
<< "Total inference time cost:" << performance_result_.total_time_cost << " s" << std::endl // sum of time taken by each request
@ -168,21 +226,24 @@ Status PerformanceRunner::ForkJoinRepeat() {
return Status::OK();
}
static TestModelInfo* CreateModelInfo(const PerformanceTestConfig& performance_test_config_) {
static std::unique_ptr<TestModelInfo> CreateModelInfo(const PerformanceTestConfig& performance_test_config_) {
if (CompareCString(performance_test_config_.backend.c_str(), ORT_TSTR("ort")) == 0) {
return TestModelInfo::LoadOnnxModel(performance_test_config_.model_info.model_file_path.c_str());
}
if (CompareCString(performance_test_config_.backend.c_str(), ORT_TSTR("tf")) == 0) {
return TFModelInfo::Create(performance_test_config_.model_info.model_file_path.c_str());
}
ORT_NOT_IMPLEMENTED(ToMBString(performance_test_config_.backend), " is not supported");
}
static TestSession* CreateSession(Ort::Env& env, std::random_device& rd,
const PerformanceTestConfig& performance_test_config_,
TestModelInfo* test_model_info) {
static std::unique_ptr<TestSession> CreateSession(Ort::Env& env, std::random_device& rd,
const PerformanceTestConfig& performance_test_config_,
const TestModelInfo& test_model_info) {
if (CompareCString(performance_test_config_.backend.c_str(), ORT_TSTR("ort")) == 0) {
return new OnnxRuntimeTestSession(env, rd, performance_test_config_, test_model_info);
return std::unique_ptr<TestSession>(
new OnnxRuntimeTestSession(env, rd, performance_test_config_, test_model_info));
}
#ifdef HAVE_TENSORFLOW
if (CompareCString(performance_test_config_.backend.c_str(), ORT_TSTR("tf")) == 0) {
@ -191,11 +252,12 @@ static TestSession* CreateSession(Ort::Env& env, std::random_device& rd,
#endif
ORT_NOT_IMPLEMENTED(ToMBString(performance_test_config_.backend), " is not supported");
}
PerformanceRunner::PerformanceRunner(Ort::Env& env, const PerformanceTestConfig& test_config, std::random_device& rd)
: performance_test_config_(test_config),
test_model_info_(CreateModelInfo(test_config)) {
session_create_start_ = std::chrono::high_resolution_clock::now();
session_.reset(CreateSession(env, rd, test_config, test_model_info_));
session_ = CreateSession(env, rd, test_config, *test_model_info_);
session_create_end_ = std::chrono::high_resolution_clock::now();
}
@ -216,7 +278,9 @@ bool PerformanceRunner::Initialize() {
std::string narrow_model_name = ToMBString(model_name);
performance_result_.model_name = narrow_model_name;
test_case_.reset(CreateOnnxTestCase(narrow_model_name, test_model_info_, 0.0, 0.0));
// ownership semantics are a little unexpected here as the test case takes ownership of the model info
TestModelInfo* test_model_info = test_model_info_.get();
test_case_ = CreateOnnxTestCase(narrow_model_name, std::move(test_model_info_), 0.0, 0.0);
if (performance_test_config_.run_config.generate_model_input_binding) {
return static_cast<OnnxRuntimeTestSession*>(session_.get())->PopulateGeneratedInputTestData();
@ -232,19 +296,18 @@ bool PerformanceRunner::Initialize() {
std::unordered_map<std::string, OrtValue*> feeds;
test_case_->LoadTestData(test_data_id /* id */, b_, feeds, true);
// Discard the names in feeds
int input_count = test_model_info_->GetInputCount();
int input_count = test_model_info->GetInputCount();
for (int i = 0; i != input_count; ++i) {
auto iter = feeds.find(test_model_info_->GetInputName(i));
auto iter = feeds.find(test_model_info->GetInputName(i));
if (iter == feeds.end()) {
std::cout << "there is no test input data for input " << test_model_info_->GetInputName(i) << " and model "
std::cout << "there is no test input data for input " << test_model_info->GetInputName(i) << " and model "
<< test_case_->GetTestCaseName() << std::endl;
return false;
}
session_->PreLoadTestData(test_data_id, static_cast<size_t>(i), iter->second);
}
}
test_case_.reset(nullptr);
test_model_info_ = nullptr;
return true;
}

View file

@ -28,55 +28,15 @@ namespace onnxruntime {
namespace perftest {
struct PerformanceResult {
std::chrono::time_point<std::chrono::high_resolution_clock> start_;
std::chrono::time_point<std::chrono::high_resolution_clock> end_;
std::chrono::time_point<std::chrono::high_resolution_clock> start;
std::chrono::time_point<std::chrono::high_resolution_clock> end;
size_t peak_workingset_size{0};
short average_CPU_usage{0};
double total_time_cost{0};
std::vector<double> time_costs;
std::string model_name;
void DumpToFile(const std::basic_string<ORTCHAR_T>& path, bool f_include_statistics = false) const {
std::ofstream outfile;
outfile.open(path, std::ofstream::out | std::ofstream::app);
if (!outfile.good()) {
printf("failed to open result file");
return;
}
for (size_t runs = 0; runs < time_costs.size(); runs++) {
outfile << model_name << "," << time_costs[runs] << "," << peak_workingset_size << "," << average_CPU_usage << "," << runs << std::endl;
}
if (!time_costs.empty() && f_include_statistics) {
std::vector<double> sorted_time = time_costs;
size_t total = sorted_time.size();
size_t n50 = static_cast<size_t>(total * 0.5);
size_t n90 = static_cast<size_t>(total * 0.9);
size_t n95 = static_cast<size_t>(total * 0.95);
size_t n99 = static_cast<size_t>(total * 0.99);
size_t n999 = static_cast<size_t>(total * 0.999);
std::sort(sorted_time.begin(), sorted_time.end());
outfile << std::endl;
auto output_stats = [&](std::ostream& ostream) {
ostream << "Min Latency is " << sorted_time[0] << "sec" << std::endl;
ostream << "Max Latency is " << sorted_time[total - 1] << "sec" << std::endl;
ostream << "P50 Latency is " << sorted_time[n50] << "sec" << std::endl;
ostream << "P90 Latency is " << sorted_time[n90] << "sec" << std::endl;
ostream << "P95 Latency is " << sorted_time[n95] << "sec" << std::endl;
ostream << "P99 Latency is " << sorted_time[n99] << "sec" << std::endl;
ostream << "P999 Latency is " << sorted_time[n999] << "sec" << std::endl;
};
output_stats(outfile);
output_stats(std::cout);
}
outfile.close();
}
void DumpToFile(const std::basic_string<ORTCHAR_T>& path, bool f_include_statistics = false) const;
};
class PerformanceRunner {
@ -143,7 +103,7 @@ class PerformanceRunner {
std::chrono::time_point<std::chrono::high_resolution_clock> session_create_end_;
PerformanceResult performance_result_;
PerformanceTestConfig performance_test_config_;
TestModelInfo* test_model_info_;
std::unique_ptr<TestModelInfo> test_model_info_;
std::unique_ptr<TestSession> session_;
onnxruntime::test::HeapBuffer b_;
std::unique_ptr<ITestCase> test_case_;

View file

@ -37,7 +37,6 @@
#pragma warning(pop)
#endif
#include "core/graph/onnx_protobuf.h"
#include "core/framework/tensorprotoutils.h"
#include "core/framework/utils.h"
@ -90,7 +89,7 @@ std::pair<COMPARE_RESULT, std::string> CompareFloatResult(const Tensor& outvalue
std::ostringstream oss;
oss << std::hex << "expected " << expected_output[di] << " (" << expected_int << "), got " << real_value << " ("
<< real_int << ")"
<< ", diff: " << diff << ", tol=" << tol << ".";
<< ", diff: " << diff << ", tol=" << tol << std::dec << " idx=" << di << ".";
res.second = oss.str();
max_diff = diff;
}