Some checks failed
Linux Build / Linux cpu (push) Has been cancelled
Linux Build / Linux cpu-clang (push) Has been cancelled
Linux Build / Linux vulkan (push) Has been cancelled
mac-build / macOS CPU (push) Has been cancelled
Nix Build / Nix (cpu) (push) Has been cancelled
Nix Build / Nix (vulkan) (push) Has been cancelled
windows-build / Windows CPU (push) Has been cancelled
598 lines
22 KiB
C++
598 lines
22 KiB
C++
#include "app/cli/request.h"
|
|
|
|
#include "engine/framework/audio/output.h"
|
|
#include "engine/framework/io/filesystem.h"
|
|
#include "engine/framework/io/json.h"
|
|
#include "engine/framework/runtime/registry.h"
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cctype>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <numeric>
|
|
#include <optional>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
#ifdef _OPENMP
|
|
#include <omp.h>
|
|
#endif
|
|
|
|
namespace {
|
|
|
|
using JsonValue = engine::io::json::Value;
|
|
using JsonObject = engine::io::json::Value::Object;
|
|
|
|
struct PerfCaseSpec {
|
|
std::string id;
|
|
std::string family;
|
|
std::string case_id;
|
|
std::string request_id;
|
|
std::optional<std::string> warmup_request_id;
|
|
};
|
|
|
|
struct MeasurementSummary {
|
|
std::vector<double> wall_ms;
|
|
double average_wall_ms = 0.0;
|
|
double rtf = 0.0;
|
|
double duration_sec = 0.0;
|
|
};
|
|
|
|
struct CaseSummary {
|
|
std::string id;
|
|
std::string family;
|
|
std::string case_id;
|
|
std::string request_id;
|
|
std::string task;
|
|
std::string mode;
|
|
MeasurementSummary cold;
|
|
MeasurementSummary warm;
|
|
};
|
|
|
|
struct RunResult {
|
|
engine::runtime::TaskResult result;
|
|
double wall_ms = 0.0;
|
|
};
|
|
|
|
std::string optional_arg(int argc, char ** argv, const std::string & name, const std::string & fallback) {
|
|
for (int i = 1; i + 1 < argc; ++i) {
|
|
if (std::string_view(argv[i]) == name) {
|
|
return argv[i + 1];
|
|
}
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
std::optional<std::string> find_arg(int argc, char ** argv, const std::string & name) {
|
|
for (int i = 1; i + 1 < argc; ++i) {
|
|
if (std::string_view(argv[i]) == name) {
|
|
return std::string(argv[i + 1]);
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
int parse_int_arg(int argc, char ** argv, const std::string & name, int fallback) {
|
|
if (const auto value = find_arg(argc, argv, name)) {
|
|
return std::stoi(*value);
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
std::string quote_json(const std::string & value) {
|
|
return engine::io::json::stringify_string(value);
|
|
}
|
|
|
|
std::string lower_ascii(std::string value) {
|
|
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char ch) {
|
|
return static_cast<char>(std::tolower(ch));
|
|
});
|
|
return value;
|
|
}
|
|
|
|
std::string make_timestamp() {
|
|
const auto now = std::chrono::system_clock::now();
|
|
const auto time = std::chrono::system_clock::to_time_t(now);
|
|
std::tm tm = {};
|
|
#if defined(_WIN32)
|
|
localtime_s(&tm, &time);
|
|
#else
|
|
localtime_r(&time, &tm);
|
|
#endif
|
|
std::ostringstream out;
|
|
out << std::put_time(&tm, "%Y%m%d-%H%M%S");
|
|
return out.str();
|
|
}
|
|
|
|
engine::core::BackendType parse_backend(const std::string & value) {
|
|
const std::string lowered = lower_ascii(value);
|
|
if (lowered == "cpu") {
|
|
return engine::core::BackendType::Cpu;
|
|
}
|
|
if (lowered == "cuda") {
|
|
return engine::core::BackendType::Cuda;
|
|
}
|
|
if (lowered == "vulkan") {
|
|
return engine::core::BackendType::Vulkan;
|
|
}
|
|
if (lowered == "metal") {
|
|
return engine::core::BackendType::Metal;
|
|
}
|
|
if (lowered == "best") {
|
|
return engine::core::BackendType::BestAvailable;
|
|
}
|
|
throw std::runtime_error("unsupported backend: " + value);
|
|
}
|
|
|
|
std::string shorten_text(std::string text, size_t limit) {
|
|
if (text.size() <= limit) {
|
|
return text;
|
|
}
|
|
size_t cut = text.rfind(' ', limit);
|
|
if (cut == std::string::npos || cut < (limit / 2)) {
|
|
cut = limit;
|
|
}
|
|
text.resize(cut);
|
|
while (!text.empty() && std::isspace(static_cast<unsigned char>(text.back())) != 0) {
|
|
text.pop_back();
|
|
}
|
|
return text;
|
|
}
|
|
|
|
std::filesystem::path write_clipped_wav(
|
|
const std::filesystem::path & source_path,
|
|
const std::filesystem::path & temp_dir,
|
|
double seconds) {
|
|
const auto audio = minitts::cli::read_audio_buffer(source_path);
|
|
const int64_t frame_limit = static_cast<int64_t>(seconds * static_cast<double>(audio.sample_rate));
|
|
const int64_t sample_limit = std::max<int64_t>(
|
|
1,
|
|
std::min<int64_t>(
|
|
static_cast<int64_t>(audio.samples.size()),
|
|
frame_limit * static_cast<int64_t>(audio.channels)));
|
|
std::vector<float> clipped(
|
|
audio.samples.begin(),
|
|
audio.samples.begin() + static_cast<std::ptrdiff_t>(sample_limit));
|
|
const auto target = temp_dir / source_path.filename();
|
|
engine::audio::WavPcm16Sink().write(
|
|
target,
|
|
engine::audio::AudioBuffer{
|
|
audio.sample_rate,
|
|
audio.channels,
|
|
std::move(clipped),
|
|
});
|
|
return target;
|
|
}
|
|
|
|
JsonValue clip_audio_path_field(
|
|
const JsonValue & value,
|
|
const std::filesystem::path & repo_root,
|
|
const std::filesystem::path & temp_dir,
|
|
double seconds) {
|
|
const auto absolute = std::filesystem::path(value.as_string()).is_absolute()
|
|
? std::filesystem::path(value.as_string())
|
|
: repo_root / value.as_string();
|
|
return JsonValue::make_string(write_clipped_wav(absolute, temp_dir, seconds).string());
|
|
}
|
|
|
|
JsonValue derive_warmup_request(
|
|
const JsonValue & request,
|
|
const std::filesystem::path & repo_root,
|
|
const std::filesystem::path & temp_dir) {
|
|
JsonObject out = request.as_object();
|
|
const auto shorten_field = [&](const char * key, size_t limit) {
|
|
const auto it = out.find(key);
|
|
if (it != out.end() && it->second.is_string()) {
|
|
it->second = JsonValue::make_string(shorten_text(it->second.as_string(), limit));
|
|
}
|
|
};
|
|
shorten_field("text", 80);
|
|
shorten_field("target_text", 80);
|
|
shorten_field("style_ref_text", 80);
|
|
shorten_field("reference_text", 80);
|
|
shorten_field("lyrics", 80);
|
|
shorten_field("instruct", 64);
|
|
|
|
const auto clip_field = [&](const char * key) {
|
|
const auto it = out.find(key);
|
|
if (it != out.end() && it->second.is_string()) {
|
|
it->second = clip_audio_path_field(it->second, repo_root, temp_dir, 2.0);
|
|
}
|
|
};
|
|
clip_field("audio");
|
|
clip_field("voice_ref");
|
|
clip_field("source_audio");
|
|
clip_field("target_voice");
|
|
clip_field("prosody_ref");
|
|
clip_field("style_ref");
|
|
|
|
const auto it_duration = out.find("duration_seconds");
|
|
if (it_duration != out.end() && it_duration->second.is_number()) {
|
|
it_duration->second = JsonValue::make_number(std::min(4.0, it_duration->second.as_number()));
|
|
}
|
|
|
|
const auto it_max_tokens = out.find("max_tokens");
|
|
if (it_max_tokens != out.end() && it_max_tokens->second.is_number()) {
|
|
it_max_tokens->second = JsonValue::make_number(std::min<int64_t>(64, it_max_tokens->second.as_i64()));
|
|
}
|
|
|
|
if (const auto options_it = out.find("options"); options_it != out.end() && options_it->second.is_object()) {
|
|
JsonObject options = options_it->second.as_object();
|
|
const auto reduce_numeric_option = [&](const char * key, double cap) {
|
|
const auto it = options.find(key);
|
|
if (it != options.end() && it->second.is_number()) {
|
|
it->second = JsonValue::make_number(std::min(cap, it->second.as_number()));
|
|
}
|
|
};
|
|
reduce_numeric_option("duration_seconds", 4.0);
|
|
reduce_numeric_option("target_duration_seconds", 4.0);
|
|
options_it->second = JsonValue::make_object(std::move(options));
|
|
}
|
|
|
|
return JsonValue::make_object(std::move(out));
|
|
}
|
|
|
|
const JsonValue & require_case(const JsonValue & root, const std::string & case_id) {
|
|
for (const auto & item : root.require("cases").as_array()) {
|
|
if (engine::io::json::require_string(item, "id") == case_id) {
|
|
return item;
|
|
}
|
|
}
|
|
throw std::runtime_error("perf harness could not find audiocpp_cli case: " + case_id);
|
|
}
|
|
|
|
const JsonValue & require_request(const JsonValue & case_value, const std::string & request_id) {
|
|
for (const auto & request : case_value.require("requests").as_array()) {
|
|
if (engine::io::json::require_string(request, "id") == request_id) {
|
|
return request;
|
|
}
|
|
}
|
|
throw std::runtime_error(
|
|
"perf harness could not find request " + request_id + " in case " +
|
|
engine::io::json::require_string(case_value, "id"));
|
|
}
|
|
|
|
std::unordered_map<std::string, std::string> json_string_map(const JsonValue * value) {
|
|
std::unordered_map<std::string, std::string> out;
|
|
if (value == nullptr || value->is_null()) {
|
|
return out;
|
|
}
|
|
for (const auto & [key, child] : value->as_object()) {
|
|
out.emplace(key, minitts::cli::json_option_string(child));
|
|
}
|
|
return out;
|
|
}
|
|
|
|
PerfCaseSpec parse_perf_case(const JsonValue & value) {
|
|
PerfCaseSpec out;
|
|
out.id = engine::io::json::require_string(value, "id");
|
|
out.family = engine::io::json::require_string(value, "family");
|
|
out.case_id = engine::io::json::require_string(value, "case_id");
|
|
out.request_id = engine::io::json::require_string(value, "request_id");
|
|
if (const auto * warmup_request_id = value.find("warmup_request_id"); warmup_request_id != nullptr && warmup_request_id->is_string()) {
|
|
out.warmup_request_id = warmup_request_id->as_string();
|
|
}
|
|
return out;
|
|
}
|
|
|
|
engine::runtime::ModelLoadRequest build_load_request(
|
|
const JsonValue & case_value,
|
|
const std::filesystem::path & repo_root) {
|
|
engine::runtime::ModelLoadRequest request;
|
|
request.model_path = repo_root / engine::io::json::require_string(case_value, "model");
|
|
request.family_hint = engine::io::json::require_string(case_value, "family");
|
|
request.options = json_string_map(case_value.find("load_options"));
|
|
return request;
|
|
}
|
|
|
|
engine::runtime::SessionOptions build_session_options(
|
|
const JsonValue & case_value,
|
|
engine::core::BackendType backend_type,
|
|
int device,
|
|
int threads) {
|
|
engine::runtime::SessionOptions options;
|
|
options.backend.type = backend_type;
|
|
options.backend.device = device;
|
|
options.backend.threads = threads;
|
|
options.options = json_string_map(case_value.find("session_options"));
|
|
return options;
|
|
}
|
|
|
|
engine::runtime::TaskSpec build_task_spec(const JsonValue & case_value) {
|
|
return engine::runtime::TaskSpec{
|
|
engine::runtime::parse_voice_task_kind(engine::io::json::require_string(case_value, "task")),
|
|
engine::runtime::parse_run_mode(engine::io::json::require_string(case_value, "mode")),
|
|
};
|
|
}
|
|
|
|
double audio_duration_sec(const engine::runtime::AudioBuffer & audio) {
|
|
if (audio.sample_rate <= 0 || audio.channels <= 0) {
|
|
return 0.0;
|
|
}
|
|
return static_cast<double>(audio.samples.size()) /
|
|
static_cast<double>(audio.sample_rate * audio.channels);
|
|
}
|
|
|
|
double result_duration_sec(
|
|
const engine::runtime::TaskRequest & request,
|
|
const engine::runtime::TaskResult & result) {
|
|
if (result.audio_output.has_value()) {
|
|
return audio_duration_sec(*result.audio_output);
|
|
}
|
|
if (!result.named_audio_outputs.empty()) {
|
|
return audio_duration_sec(result.named_audio_outputs.front().audio);
|
|
}
|
|
if (request.audio_input.has_value()) {
|
|
return audio_duration_sec(*request.audio_input);
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
RunResult run_impl(
|
|
engine::runtime::IVoiceTaskSession & session,
|
|
const engine::runtime::TaskSpec & task_spec,
|
|
const engine::runtime::TaskRequest & request,
|
|
bool prepare_first) {
|
|
const auto started = std::chrono::steady_clock::now();
|
|
if (prepare_first) {
|
|
session.prepare(engine::runtime::build_preparation_request(request));
|
|
}
|
|
if (task_spec.mode == engine::runtime::RunMode::Offline) {
|
|
auto * offline = dynamic_cast<engine::runtime::IOfflineVoiceTaskSession *>(&session);
|
|
if (offline == nullptr) {
|
|
throw std::runtime_error("selected model does not support offline mode");
|
|
}
|
|
auto result = offline->run(request);
|
|
const auto ended = std::chrono::steady_clock::now();
|
|
return {
|
|
std::move(result),
|
|
std::chrono::duration<double, std::milli>(ended - started).count(),
|
|
};
|
|
}
|
|
|
|
auto * streaming = dynamic_cast<engine::runtime::IStreamingVoiceTaskSession *>(&session);
|
|
if (streaming == nullptr) {
|
|
throw std::runtime_error("selected model does not support streaming mode");
|
|
}
|
|
if (!request.audio_input.has_value()) {
|
|
throw std::runtime_error("streaming perf request requires request.audio_input");
|
|
}
|
|
streaming->reset();
|
|
const auto & audio = *request.audio_input;
|
|
constexpr int kChunkSamples = 512;
|
|
for (size_t offset = 0; offset < audio.samples.size(); offset += static_cast<size_t>(kChunkSamples)) {
|
|
const size_t available = std::min(static_cast<size_t>(kChunkSamples), audio.samples.size() - offset);
|
|
std::vector<float> chunk(static_cast<size_t>(kChunkSamples), 0.0f);
|
|
std::copy(
|
|
audio.samples.begin() + static_cast<std::ptrdiff_t>(offset),
|
|
audio.samples.begin() + static_cast<std::ptrdiff_t>(offset + available),
|
|
chunk.begin());
|
|
streaming->process_audio_chunk(engine::runtime::AudioChunk{
|
|
audio.sample_rate,
|
|
audio.channels,
|
|
static_cast<int64_t>(offset / static_cast<size_t>(std::max(1, audio.channels))),
|
|
std::move(chunk),
|
|
});
|
|
}
|
|
auto result = streaming->finalize();
|
|
const auto ended = std::chrono::steady_clock::now();
|
|
return {
|
|
std::move(result),
|
|
std::chrono::duration<double, std::milli>(ended - started).count(),
|
|
};
|
|
}
|
|
|
|
RunResult run_once(
|
|
engine::runtime::IVoiceTaskSession & session,
|
|
const engine::runtime::TaskSpec & task_spec,
|
|
const engine::runtime::TaskRequest & request) {
|
|
return run_impl(session, task_spec, request, true);
|
|
}
|
|
|
|
RunResult run_prepared_once(
|
|
engine::runtime::IVoiceTaskSession & session,
|
|
const engine::runtime::TaskSpec & task_spec,
|
|
const engine::runtime::TaskRequest & request) {
|
|
return run_impl(session, task_spec, request, false);
|
|
}
|
|
|
|
MeasurementSummary measure_cold(
|
|
const engine::runtime::ModelRegistry & registry,
|
|
const engine::runtime::ModelLoadRequest & load_request,
|
|
const engine::runtime::TaskSpec & task_spec,
|
|
const engine::runtime::SessionOptions & session_options,
|
|
const engine::runtime::TaskRequest & request) {
|
|
MeasurementSummary summary;
|
|
for (int rep = 0; rep < 3; ++rep) {
|
|
const auto started = std::chrono::steady_clock::now();
|
|
auto model = registry.load(load_request);
|
|
auto session = model->create_task_session(task_spec, session_options);
|
|
auto run = run_once(*session, task_spec, request);
|
|
const auto ended = std::chrono::steady_clock::now();
|
|
summary.wall_ms.push_back(std::chrono::duration<double, std::milli>(ended - started).count());
|
|
summary.duration_sec = result_duration_sec(request, run.result);
|
|
}
|
|
summary.average_wall_ms =
|
|
std::accumulate(summary.wall_ms.begin(), summary.wall_ms.end(), 0.0) /
|
|
static_cast<double>(summary.wall_ms.size());
|
|
if (summary.duration_sec > 0.0) {
|
|
summary.rtf = (summary.average_wall_ms / 1000.0) / summary.duration_sec;
|
|
}
|
|
return summary;
|
|
}
|
|
|
|
MeasurementSummary measure_warm(
|
|
const engine::runtime::ModelRegistry & registry,
|
|
const engine::runtime::ModelLoadRequest & load_request,
|
|
const engine::runtime::TaskSpec & task_spec,
|
|
const engine::runtime::SessionOptions & session_options,
|
|
const engine::runtime::TaskRequest & warmup_request,
|
|
const engine::runtime::TaskRequest & request) {
|
|
auto model = registry.load(load_request);
|
|
auto session = model->create_task_session(task_spec, session_options);
|
|
(void) run_once(*session, task_spec, warmup_request);
|
|
session->prepare(engine::runtime::build_preparation_request(request));
|
|
|
|
MeasurementSummary summary;
|
|
for (int rep = 0; rep < 3; ++rep) {
|
|
auto run = run_prepared_once(*session, task_spec, request);
|
|
summary.wall_ms.push_back(run.wall_ms);
|
|
summary.duration_sec = result_duration_sec(request, run.result);
|
|
}
|
|
summary.average_wall_ms =
|
|
std::accumulate(summary.wall_ms.begin(), summary.wall_ms.end(), 0.0) /
|
|
static_cast<double>(summary.wall_ms.size());
|
|
if (summary.duration_sec > 0.0) {
|
|
summary.rtf = (summary.average_wall_ms / 1000.0) / summary.duration_sec;
|
|
}
|
|
return summary;
|
|
}
|
|
|
|
std::string measurement_json(const MeasurementSummary & measurement) {
|
|
std::ostringstream out;
|
|
out << "{"
|
|
<< "\"wall_ms\":[";
|
|
for (size_t i = 0; i < measurement.wall_ms.size(); ++i) {
|
|
if (i != 0) {
|
|
out << ",";
|
|
}
|
|
out << measurement.wall_ms[i];
|
|
}
|
|
out << "],\"average_wall_ms\":" << measurement.average_wall_ms
|
|
<< ",\"duration_sec\":" << measurement.duration_sec
|
|
<< ",\"rtf\":" << measurement.rtf
|
|
<< "}";
|
|
return out.str();
|
|
}
|
|
|
|
std::string case_summary_json(const CaseSummary & summary) {
|
|
std::ostringstream out;
|
|
out << "{"
|
|
<< "\"id\":" << quote_json(summary.id)
|
|
<< ",\"family\":" << quote_json(summary.family)
|
|
<< ",\"case_id\":" << quote_json(summary.case_id)
|
|
<< ",\"request_id\":" << quote_json(summary.request_id)
|
|
<< ",\"task\":" << quote_json(summary.task)
|
|
<< ",\"mode\":" << quote_json(summary.mode)
|
|
<< ",\"cold\":" << measurement_json(summary.cold)
|
|
<< ",\"warm\":" << measurement_json(summary.warm)
|
|
<< "}";
|
|
return out.str();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char ** argv) {
|
|
try {
|
|
std::cout.setf(std::ios::unitbuf);
|
|
const std::filesystem::path repo_root = std::filesystem::current_path();
|
|
const std::filesystem::path perf_cases_path = optional_arg(
|
|
argc,
|
|
argv,
|
|
"--perf-cases",
|
|
"tests/perf/model_perf_cases.json");
|
|
const std::filesystem::path path_cases_path = optional_arg(
|
|
argc,
|
|
argv,
|
|
"--request-cases",
|
|
"tests/perf/model_perf_request_cases.json");
|
|
const std::string backend_name = optional_arg(argc, argv, "--backend", "cuda");
|
|
const int device = parse_int_arg(argc, argv, "--device", 0);
|
|
const int threads = parse_int_arg(argc, argv, "--threads", 1);
|
|
if (threads <= 0) {
|
|
throw std::runtime_error("--threads must be positive");
|
|
}
|
|
#ifdef _OPENMP
|
|
omp_set_num_threads(threads);
|
|
#endif
|
|
const auto selected_id = find_arg(argc, argv, "--id");
|
|
const auto summary_out_arg = find_arg(argc, argv, "--summary-out");
|
|
const auto output_root = summary_out_arg.has_value()
|
|
? std::filesystem::path(*summary_out_arg).parent_path()
|
|
: repo_root / "build" / "logs" / "perf" /
|
|
("cpp_" + lower_ascii(backend_name) + "_" + make_timestamp());
|
|
std::filesystem::create_directories(output_root);
|
|
|
|
const auto perf_root = engine::io::json::parse_file(perf_cases_path);
|
|
const auto path_root = engine::io::json::parse_file(path_cases_path);
|
|
auto registry = engine::runtime::make_default_registry();
|
|
const auto backend_type = parse_backend(backend_name);
|
|
|
|
std::vector<CaseSummary> summaries;
|
|
for (const auto & perf_case_value : perf_root.require("cases").as_array()) {
|
|
const auto perf_case = parse_perf_case(perf_case_value);
|
|
if (selected_id.has_value() && *selected_id != perf_case.id) {
|
|
continue;
|
|
}
|
|
const auto & case_value = require_case(path_root, perf_case.case_id);
|
|
const auto & request_value = require_request(case_value, perf_case.request_id);
|
|
const auto load_request = build_load_request(case_value, repo_root);
|
|
const auto task_spec = build_task_spec(case_value);
|
|
const auto session_options = build_session_options(case_value, backend_type, device, threads);
|
|
const auto request = minitts::cli::build_request_from_json(request_value, repo_root);
|
|
|
|
const auto temp_dir = output_root / (perf_case.id + "_warmup");
|
|
std::filesystem::create_directories(temp_dir);
|
|
engine::runtime::TaskRequest warmup_request;
|
|
if (perf_case.warmup_request_id.has_value()) {
|
|
warmup_request = minitts::cli::build_request_from_json(
|
|
require_request(case_value, *perf_case.warmup_request_id),
|
|
repo_root);
|
|
} else {
|
|
warmup_request = minitts::cli::build_request_from_json(
|
|
derive_warmup_request(request_value, repo_root, temp_dir),
|
|
repo_root);
|
|
}
|
|
|
|
CaseSummary summary;
|
|
summary.id = perf_case.id;
|
|
summary.family = perf_case.family;
|
|
summary.case_id = perf_case.case_id;
|
|
summary.request_id = perf_case.request_id;
|
|
summary.task = engine::runtime::to_string(task_spec.task);
|
|
summary.mode = engine::runtime::to_string(task_spec.mode);
|
|
summary.cold = measure_cold(registry, load_request, task_spec, session_options, request);
|
|
summary.warm = measure_warm(registry, load_request, task_spec, session_options, warmup_request, request);
|
|
summaries.push_back(summary);
|
|
|
|
std::cout << perf_case.id
|
|
<< " cold_avg_ms=" << summary.cold.average_wall_ms
|
|
<< " cold_rtf=" << summary.cold.rtf
|
|
<< " warm_avg_ms=" << summary.warm.average_wall_ms
|
|
<< " warm_rtf=" << summary.warm.rtf
|
|
<< std::endl;
|
|
}
|
|
|
|
std::ostringstream summary_json;
|
|
summary_json << "{\"backend\":" << quote_json(backend_name)
|
|
<< ",\"device\":" << device
|
|
<< ",\"threads\":" << threads
|
|
<< ",\"cases\":[";
|
|
for (size_t i = 0; i < summaries.size(); ++i) {
|
|
if (i != 0) {
|
|
summary_json << ",";
|
|
}
|
|
summary_json << case_summary_json(summaries[i]);
|
|
}
|
|
summary_json << "]}";
|
|
|
|
const auto summary_path = summary_out_arg.has_value()
|
|
? std::filesystem::path(*summary_out_arg)
|
|
: output_root / "summary.json";
|
|
std::ofstream(summary_path) << summary_json.str() << "\n";
|
|
std::cout << "summary_json=" << summary_path.string() << std::endl;
|
|
return 0;
|
|
} catch (const std::exception & ex) {
|
|
std::cerr << "model_perf failed: " << ex.what() << "\n";
|
|
return 1;
|
|
}
|
|
}
|