diff --git a/engine/commands/cortex_upd_cmd.cc b/engine/commands/cortex_upd_cmd.cc index c6526df66..b8edede53 100644 --- a/engine/commands/cortex_upd_cmd.cc +++ b/engine/commands/cortex_upd_cmd.cc @@ -49,7 +49,7 @@ void CortexUpdCmd::Exec(std::string v) { bool CortexUpdCmd::GetStable(const std::string& v) { auto system_info = system_info_utils::GetSystemInfo(); - CTL_INF("OS: " << system_info.os << ", Arch: " << system_info.arch); + CTL_INF("OS: " << system_info->os << ", Arch: " << system_info->arch); // Download file auto github_host = GetHostName(); @@ -67,7 +67,7 @@ bool CortexUpdCmd::GetStable(const std::string& v) { } if (!HandleGithubRelease(json_data["assets"], - {system_info.os + "-" + system_info.arch})) { + {system_info->os + "-" + system_info->arch})) { return false; } } catch (const nlohmann::json::parse_error& e) { @@ -103,7 +103,7 @@ bool CortexUpdCmd::GetStable(const std::string& v) { bool CortexUpdCmd::GetBeta(const std::string& v) { auto system_info = system_info_utils::GetSystemInfo(); - CTL_INF("OS: " << system_info.os << ", Arch: " << system_info.arch); + CTL_INF("OS: " << system_info->os << ", Arch: " << system_info->arch); // Download file auto github_host = GetHostName(); @@ -133,7 +133,7 @@ bool CortexUpdCmd::GetBeta(const std::string& v) { } if (!HandleGithubRelease(json_data["assets"], - {system_info.os + "-" + system_info.arch})) { + {system_info->os + "-" + system_info->arch})) { return false; } } catch (const nlohmann::json::parse_error& e) { @@ -234,11 +234,11 @@ bool CortexUpdCmd::HandleGithubRelease(const nlohmann::json& assets, bool CortexUpdCmd::GetNightly(const std::string& v) { auto system_info = system_info_utils::GetSystemInfo(); - CTL_INF("OS: " << system_info.os << ", Arch: " << system_info.arch); + CTL_INF("OS: " << system_info->os << ", Arch: " << system_info->arch); // Download file std::string version = v.empty() ? "latest" : std::move(v); - std::string os_arch{system_info.os + "-" + system_info.arch}; + std::string os_arch{system_info->os + "-" + system_info->arch}; const char* paths[] = { "cortex", version.c_str(), diff --git a/engine/commands/engine_install_cmd.cc b/engine/commands/engine_install_cmd.cc index 36f7a040b..59d51bfd1 100644 --- a/engine/commands/engine_install_cmd.cc +++ b/engine/commands/engine_install_cmd.cc @@ -10,8 +10,9 @@ namespace commands { void EngineInstallCmd::Exec(const std::string& engine, - const std::string& version) { - engine_service_.InstallEngine(engine, version); + const std::string& version, + const std::string& src) { + engine_service_.InstallEngine(engine, version, src); CLI_LOG("Engine " << engine << " installed successfully!"); } }; // namespace commands diff --git a/engine/commands/engine_install_cmd.h b/engine/commands/engine_install_cmd.h index c6ba6f135..32b7079cc 100644 --- a/engine/commands/engine_install_cmd.h +++ b/engine/commands/engine_install_cmd.h @@ -9,7 +9,8 @@ class EngineInstallCmd { public: explicit EngineInstallCmd() : engine_service_{EngineService()} {}; - void Exec(const std::string& engine, const std::string& version = "latest"); + void Exec(const std::string& engine, const std::string& version = "latest", + const std::string& src = ""); private: EngineService engine_service_; diff --git a/engine/controllers/command_line_parser.cc b/engine/controllers/command_line_parser.cc index f57efb7a2..31ace9ffd 100644 --- a/engine/controllers/command_line_parser.cc +++ b/engine/controllers/command_line_parser.cc @@ -331,7 +331,8 @@ void CommandLineParser::SetupEngineCommands() { }); for (auto& engine : engine_service_.kSupportEngines) { std::string engine_name{engine}; - EngineInstall(install_cmd, engine_name, cml_data_.engine_version); + EngineInstall(install_cmd, engine_name, cml_data_.engine_version, + cml_data_.engine_src); } auto uninstall_cmd = @@ -395,7 +396,7 @@ void CommandLineParser::SetupSystemCommands() { void CommandLineParser::EngineInstall(CLI::App* parent, const std::string& engine_name, - std::string& version) { + std::string& version, std::string& src) { auto install_engine_cmd = parent->add_subcommand(engine_name, ""); install_engine_cmd->usage("Usage:\n" + commands::GetCortexBinary() + " engines install " + engine_name + " [options]"); @@ -404,9 +405,12 @@ void CommandLineParser::EngineInstall(CLI::App* parent, install_engine_cmd->add_option("-v, --version", version, "Engine version to download"); - install_engine_cmd->callback([engine_name, &version] { + install_engine_cmd->add_option("-s, --source", src, + "Install engine by local path"); + + install_engine_cmd->callback([engine_name, &version, &src] { try { - commands::EngineInstallCmd().Exec(engine_name, version); + commands::EngineInstallCmd().Exec(engine_name, version, src); } catch (const std::exception& e) { CTL_ERR(e.what()); } diff --git a/engine/controllers/command_line_parser.h b/engine/controllers/command_line_parser.h index 98f437098..87a8063fd 100644 --- a/engine/controllers/command_line_parser.h +++ b/engine/controllers/command_line_parser.h @@ -21,7 +21,7 @@ class CommandLineParser { void SetupSystemCommands(); void EngineInstall(CLI::App* parent, const std::string& engine_name, - std::string& version); + std::string& version, std::string& src); void EngineUninstall(CLI::App* parent, const std::string& engine_name); @@ -35,6 +35,7 @@ class CommandLineParser { std::string model_alias; std::string model_path; std::string engine_version = "latest"; + std::string engine_src; std::string cortex_version; bool check_upd = true; int port; diff --git a/engine/controllers/engines.cc b/engine/controllers/engines.cc index e35002e1f..1c1466e5e 100644 --- a/engine/controllers/engines.cc +++ b/engine/controllers/engines.cc @@ -38,7 +38,7 @@ void Engines::InstallEngine( auto jsonResponse = json::parse(res->body); auto assets = jsonResponse["assets"]; - auto os_arch{system_info.os + "-" + system_info.arch}; + auto os_arch{system_info->os + "-" + system_info->arch}; for (auto& asset : assets) { auto assetName = asset["name"].get(); if (assetName.find(os_arch) != std::string::npos) { diff --git a/engine/e2e-test/test_cli_engine_install.py b/engine/e2e-test/test_cli_engine_install.py index dfb4e9599..b63fa6f0f 100644 --- a/engine/e2e-test/test_cli_engine_install.py +++ b/engine/e2e-test/test_cli_engine_install.py @@ -1,4 +1,5 @@ import platform +import tempfile import pytest from test_runner import run @@ -36,3 +37,16 @@ def test_engines_install_pre_release_llamacpp(self): assert "Start downloading" in output, "Should display downloading message" assert exit_code == 0, f"Install engine failed with error: {error}" + def test_engines_should_fallback_to_download_llamacpp_engine_if_not_exists(self): + exit_code, output, error = run( + "Install Engine", ["engines", "install", "cortex.llamacpp", "-s", tempfile.gettempdir()], timeout=None + ) + assert "Start downloading" in output, "Should display downloading message" + assert exit_code == 0, f"Install engine failed with error: {error}" + + def test_engines_should_not_perform_with_dummy_path(self): + exit_code, output, error = run( + "Install Engine", ["engines", "install", "cortex.llamacpp", "-s", "abcpod"], timeout=None + ) + assert "Folder does not exist" in output, "Should display error" + assert exit_code == 0, f"Install engine failed with error: {error}" diff --git a/engine/main.cc b/engine/main.cc index bdac8148c..e7fe9bd22 100644 --- a/engine/main.cc +++ b/engine/main.cc @@ -88,10 +88,10 @@ void RunServer() { int main(int argc, char* argv[]) { // Stop the program if the system is not supported auto system_info = system_info_utils::GetSystemInfo(); - if (system_info.arch == system_info_utils::kUnsupported || - system_info.os == system_info_utils::kUnsupported) { - CTL_ERR("Unsupported OS or architecture: " << system_info.os << ", " - << system_info.arch); + if (system_info->arch == system_info_utils::kUnsupported || + system_info->os == system_info_utils::kUnsupported) { + CTL_ERR("Unsupported OS or architecture: " << system_info->os << ", " + << system_info->arch); return 1; } diff --git a/engine/services/engine_service.cc b/engine/services/engine_service.cc index 0d5cf9ac8..1b1f1d278 100644 --- a/engine/services/engine_service.cc +++ b/engine/services/engine_service.cc @@ -12,6 +12,32 @@ using json = nlohmann::json; +namespace { +std::string GetSuitableCudaVersion(const std::string& engine, + const std::string& cuda_driver_version) { + auto suitable_toolkit_version = ""; + if (engine == "cortex.tensorrt-llm") { + // for tensorrt-llm, we need to download cuda toolkit v12.4 + suitable_toolkit_version = "12.4"; + } else { + // llamacpp + auto cuda_driver_semver = + semantic_version_utils::SplitVersion(cuda_driver_version); + if (cuda_driver_semver.major == 11) { + suitable_toolkit_version = "11.7"; + } else if (cuda_driver_semver.major == 12) { + suitable_toolkit_version = "12.0"; + } + } + return suitable_toolkit_version; +} +} // namespace + +EngineService::EngineService() + : hw_inf_{.sys_inf = system_info_utils::GetSystemInfo(), + .cuda_driver_version = system_info_utils::GetCudaVersion()} {} +EngineService::~EngineService() {} + std::optional EngineService::GetEngineInfo( const std::string& engine) const { // if engine is not found in kSupportEngine, throw runtime error @@ -74,8 +100,101 @@ std::vector EngineService::GetEngineInfoList() const { } void EngineService::InstallEngine(const std::string& engine, - const std::string& version) { - auto system_info = system_info_utils::GetSystemInfo(); + const std::string& version, + const std::string& src) { + + if (!src.empty()) { + UnzipEngine(engine, version, src); + } else { + DownloadEngine(engine, version); + DownloadCuda(engine); + } +} + +void EngineService::UnzipEngine(const std::string& engine, + const std::string& version, + const std::string& path) { + bool found_cuda = false; + + CTL_INF("engine: " << engine); + CTL_INF("CUDA version: " << hw_inf_.cuda_driver_version); + std::string cuda_variant = "cuda-"; + cuda_variant += GetSuitableCudaVersion(engine, hw_inf_.cuda_driver_version) + + "-" + hw_inf_.sys_inf->os + "-" + hw_inf_.sys_inf->arch + + ".tar.gz"; + CTL_INF("cuda_variant: " << cuda_variant); + + std::vector variants; + // Loop through all files in the directory + // 1. Push all engine variants to a list + // 2. If cuda version is matched, extract it + if (std::filesystem::exists(path) && std::filesystem::is_directory(path)) { + for (const auto& entry : std::filesystem::directory_iterator(path)) { + CTL_INF("file path: " << entry.path().string()); + if (entry.is_regular_file() && (entry.path().extension() == ".tar.gz" || + entry.path().extension() == ".gz")) { + CTL_INF("file name: " << entry.path().filename().string()); + variants.push_back(entry.path().filename().string()); + if (std::string cf = entry.path().filename().string(); + cf == cuda_variant) { + CTL_INF("Found cuda variant, extract it"); + found_cuda = true; + // extract binary + auto engine_path = + file_manager_utils::GetEnginesContainerPath() / engine; + archive_utils::ExtractArchive(path + "/" + cf, engine_path.string()); + } + } + } + } else { + // Folder does not exist, throw exception + CTL_ERR("Folder does not exist: " << path); + return; + } + + auto matched_variant = GetMatchedVariant(engine, variants); + CTL_INF("Matched variant: " << matched_variant); + if (matched_variant.empty()) { + CTL_INF("No variant found for " << hw_inf_.sys_inf->os << "-" + << hw_inf_.sys_inf->arch + << ", will get engine from remote"); + // Go with the remote flow + DownloadEngine(engine, version); + } else { + auto engine_path = file_manager_utils::GetEnginesContainerPath(); + archive_utils::ExtractArchive(path + "/" + matched_variant, + engine_path.string()); + } + + // Not match any cuda binary, download from remote + if (!found_cuda) { + DownloadCuda(engine); + } +} + +void EngineService::UninstallEngine(const std::string& engine) { + // TODO: Unload the model which is currently running on engine_ + + // TODO: Unload engine if is loaded + + auto ecp = file_manager_utils::GetEnginesContainerPath(); + auto engine_path = ecp / engine; + + if (!std::filesystem::exists(engine_path)) { + throw std::runtime_error("Engine " + engine + " is not installed!"); + } + + try { + std::filesystem::remove_all(engine_path); + CTL_INF("Engine " << engine << " uninstalled successfully!"); + } catch (const std::exception& e) { + CTL_ERR("Failed to uninstall engine " << engine << ": " << e.what()); + throw; + } +} + +void EngineService::DownloadEngine(const std::string& engine, + const std::string& version) { auto get_params = [&engine, &version]() -> std::vector { if (version == "latest") { return {"repos", "janhq", engine, "releases", version}; @@ -109,11 +228,11 @@ void EngineService::InstallEngine(const std::string& engine, body = get_data(body); } if (body.empty()) { - throw std::runtime_error("No release found for " + version); + throw std::runtime_error("No release found for " + version); } auto assets = body["assets"]; - auto os_arch{system_info.os + "-" + system_info.arch}; + auto os_arch{hw_inf_.sys_inf->os + "-" + hw_inf_.sys_inf->arch}; std::vector variants; for (auto& asset : assets) { @@ -121,24 +240,9 @@ void EngineService::InstallEngine(const std::string& engine, variants.push_back(asset_name); } - auto cuda_driver_version = system_info_utils::GetCudaVersion(); CTL_INF("engine: " << engine); - CTL_INF("CUDA version: " << cuda_driver_version); - std::string matched_variant = ""; - - if (engine == "cortex.tensorrt-llm") { - matched_variant = engine_matcher_utils::ValidateTensorrtLlm( - variants, system_info.os, cuda_driver_version); - } else if (engine == "cortex.onnx") { - matched_variant = engine_matcher_utils::ValidateOnnx( - variants, system_info.os, system_info.arch); - } else if (engine == "cortex.llamacpp") { - cortex::cpuid::CpuInfo cpu_info; - auto suitable_avx = engine_matcher_utils::GetSuitableAvxVariant(cpu_info); - matched_variant = engine_matcher_utils::Validate( - variants, system_info.os, system_info.arch, suitable_avx, - cuda_driver_version); - } + CTL_INF("CUDA version: " << hw_inf_.cuda_driver_version); + auto matched_variant = GetMatchedVariant(engine, variants); CTL_INF("Matched variant: " << matched_variant); if (matched_variant.empty()) { CTL_ERR("No variant found for " << os_arch); @@ -195,85 +299,6 @@ void EngineService::InstallEngine(const std::string& engine, } CTL_INF("Finished!"); }); - if (system_info.os == "mac" || engine == "cortex.onnx") { - // mac and onnx engine does not require cuda toolkit - return; - } - - if (cuda_driver_version.empty()) { - CTL_WRN("No cuda driver, continue with CPU"); - return; - } - - // download cuda toolkit - const std::string jan_host = "https://catalog.jan.ai"; - const std::string cuda_toolkit_file_name = "cuda.tar.gz"; - const std::string download_id = "cuda"; - - // TODO: we don't have API to retrieve list of cuda toolkit dependencies atm because we hosting it at jan - // will have better logic after https://github.com/janhq/cortex/issues/1046 finished - // for now, assume that we have only 11.7 and 12.4 - auto suitable_toolkit_version = ""; - if (engine == "cortex.tensorrt-llm") { - // for tensorrt-llm, we need to download cuda toolkit v12.4 - suitable_toolkit_version = "12.4"; - } else { - // llamacpp - auto cuda_driver_semver = - semantic_version_utils::SplitVersion(cuda_driver_version); - if (cuda_driver_semver.major == 11) { - suitable_toolkit_version = "11.7"; - } else if (cuda_driver_semver.major == 12) { - suitable_toolkit_version = "12.0"; - } - } - - // compare cuda driver version with cuda toolkit version - // cuda driver version should be greater than toolkit version to ensure compatibility - if (semantic_version_utils::CompareSemanticVersion( - cuda_driver_version, suitable_toolkit_version) < 0) { - CTL_ERR("Your Cuda driver version " - << cuda_driver_version - << " is not compatible with cuda toolkit version " - << suitable_toolkit_version); - throw std::runtime_error( - "Cuda driver is not compatible with cuda toolkit"); - } - - std::ostringstream cuda_toolkit_url; - cuda_toolkit_url << jan_host << "/" << "dist/cuda-dependencies/" - << cuda_driver_version << "/" << system_info.os << "/" - << cuda_toolkit_file_name; - - LOG_DEBUG << "Cuda toolkit download url: " << cuda_toolkit_url.str(); - auto cuda_toolkit_local_path = - file_manager_utils::GetContainerFolderPath( - file_manager_utils::DownloadTypeToString( - DownloadType::CudaToolkit)) / - cuda_toolkit_file_name; - LOG_DEBUG << "Download to: " << cuda_toolkit_local_path.string(); - auto downloadCudaToolkitTask{DownloadTask{ - .id = download_id, - .type = DownloadType::CudaToolkit, - .items = {DownloadItem{.id = download_id, - .downloadUrl = cuda_toolkit_url.str(), - .localPath = cuda_toolkit_local_path}}, - }}; - - download_service.AddDownloadTask( - downloadCudaToolkitTask, [&](const DownloadTask& finishedTask) { - auto engine_path = - file_manager_utils::GetEnginesContainerPath() / engine; - archive_utils::ExtractArchive( - finishedTask.items[0].localPath.string(), - engine_path.string()); - - try { - std::filesystem::remove(finishedTask.items[0].localPath); - } catch (std::exception& e) { - CTL_ERR("Error removing downloaded file: " << e.what()); - } - }); return; } } @@ -282,23 +307,89 @@ void EngineService::InstallEngine(const std::string& engine, } } -void EngineService::UninstallEngine(const std::string& engine) { - // TODO: Unload the model which is currently running on engine_ +void EngineService::DownloadCuda(const std::string& engine) { + if (hw_inf_.sys_inf->os == "mac" || engine == "cortex.onnx") { + // mac and onnx engine does not require cuda toolkit + return; + } - // TODO: Unload engine if is loaded + if (hw_inf_.cuda_driver_version.empty()) { + CTL_WRN("No cuda driver, continue with CPU"); + return; + } + // download cuda toolkit + const std::string jan_host = "catalog.jan.ai"; + const std::string cuda_toolkit_file_name = "cuda.tar.gz"; + const std::string download_id = "cuda"; + + auto suitable_toolkit_version = + GetSuitableCudaVersion(engine, hw_inf_.cuda_driver_version); + + // compare cuda driver version with cuda toolkit version + // cuda driver version should be greater than toolkit version to ensure compatibility + if (semantic_version_utils::CompareSemanticVersion( + hw_inf_.cuda_driver_version, suitable_toolkit_version) < 0) { + CTL_ERR("Your Cuda driver version " + << hw_inf_.cuda_driver_version + << " is not compatible with cuda toolkit version " + << suitable_toolkit_version); + throw std::runtime_error("Cuda driver is not compatible with cuda toolkit"); + } - auto ecp = file_manager_utils::GetEnginesContainerPath(); - auto engine_path = ecp / engine; + auto url_obj = url_parser::Url{ + .protocol = "https", + .host = jan_host, + .pathParams = {"dist", "cuda-dependencies", suitable_toolkit_version, + hw_inf_.sys_inf->os, cuda_toolkit_file_name}, + }; - if (!std::filesystem::exists(engine_path)) { - throw std::runtime_error("Engine " + engine + " is not installed!"); - } + auto cuda_toolkit_url = url_parser::FromUrl(url_obj); + + LOG_DEBUG << "Cuda toolkit download url: " << cuda_toolkit_url; + auto cuda_toolkit_local_path = + file_manager_utils::GetContainerFolderPath( + file_manager_utils::DownloadTypeToString(DownloadType::CudaToolkit)) / + cuda_toolkit_file_name; + LOG_DEBUG << "Download to: " << cuda_toolkit_local_path.string(); + auto downloadCudaToolkitTask{DownloadTask{ + .id = download_id, + .type = DownloadType::CudaToolkit, + .items = {DownloadItem{.id = download_id, + .downloadUrl = cuda_toolkit_url, + .localPath = cuda_toolkit_local_path}}, + }}; + + DownloadService download_service; + download_service.AddDownloadTask( + downloadCudaToolkitTask, [&](const DownloadTask& finishedTask) { + auto engine_path = + file_manager_utils::GetEnginesContainerPath() / engine; + archive_utils::ExtractArchive(finishedTask.items[0].localPath.string(), + engine_path.string()); + + try { + std::filesystem::remove(finishedTask.items[0].localPath); + } catch (std::exception& e) { + CTL_ERR("Error removing downloaded file: " << e.what()); + } + }); +} - try { - std::filesystem::remove_all(engine_path); - CTL_INF("Engine " << engine << " uninstalled successfully!"); - } catch (const std::exception& e) { - CTL_ERR("Failed to uninstall engine " << engine << ": " << e.what()); - throw; +std::string EngineService::GetMatchedVariant( + const std::string& engine, const std::vector& variants) { + std::string matched_variant; + if (engine == "cortex.tensorrt-llm") { + matched_variant = engine_matcher_utils::ValidateTensorrtLlm( + variants, hw_inf_.sys_inf->os, hw_inf_.cuda_driver_version); + } else if (engine == "cortex.onnx") { + matched_variant = engine_matcher_utils::ValidateOnnx( + variants, hw_inf_.sys_inf->os, hw_inf_.sys_inf->arch); + } else if (engine == "cortex.llamacpp") { + auto suitable_avx = + engine_matcher_utils::GetSuitableAvxVariant(hw_inf_.cpu_inf); + matched_variant = engine_matcher_utils::Validate( + variants, hw_inf_.sys_inf->os, hw_inf_.sys_inf->arch, suitable_avx, + hw_inf_.cuda_driver_version); } -} + return matched_variant; +} \ No newline at end of file diff --git a/engine/services/engine_service.h b/engine/services/engine_service.h index 442923356..5e434bf24 100644 --- a/engine/services/engine_service.h +++ b/engine/services/engine_service.h @@ -1,9 +1,11 @@ #pragma once +#include #include #include #include #include +#include "utils/cpuid/cpu_info.h" struct EngineInfo { std::string name; @@ -14,6 +16,9 @@ struct EngineInfo { std::string status; }; +namespace system_info_utils { +struct SystemInfo; +} class EngineService { public: constexpr static auto kIncompatible = "Incompatible"; @@ -23,12 +28,35 @@ class EngineService { const std::vector kSupportEngines = { "cortex.llamacpp", "cortex.onnx", "cortex.tensorrt-llm"}; + EngineService(); + ~EngineService(); + std::optional GetEngineInfo(const std::string& engine) const; std::vector GetEngineInfoList() const; void InstallEngine(const std::string& engine, - const std::string& version = "latest"); + const std::string& version = "latest", + const std::string& src = ""); + + void UnzipEngine(const std::string& engine, const std::string& version, + const std::string& path); void UninstallEngine(const std::string& engine); + + private: + void DownloadEngine(const std::string& engine, + const std::string& version = "latest"); + void DownloadCuda(const std::string& engine); + + std::string GetMatchedVariant(const std::string& engine, + const std::vector& variants); + + private: + struct HardwareInfo { + std::unique_ptr sys_inf; + cortex::cpuid::CpuInfo cpu_inf; + std::string cuda_driver_version; + }; + HardwareInfo hw_inf_; }; diff --git a/engine/utils/system_info_utils.h b/engine/utils/system_info_utils.h index 9cdcc8f05..9dbfcc7c9 100644 --- a/engine/utils/system_info_utils.h +++ b/engine/utils/system_info_utils.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -22,6 +23,8 @@ constexpr static auto kGpuInfoRegex{ R"((\d+),\s*(\d+),\s*([^,]+),\s*([\d\.]+))"}; struct SystemInfo { + explicit SystemInfo(std::string os, std::string arch) + : os(std::move(os)), arch(std::move(arch)) {} std::string os; std::string arch; }; @@ -51,7 +54,7 @@ inline std::string GetGpuArch(const std::string& gpuName) { } } -inline SystemInfo GetSystemInfo() { +inline std::unique_ptr GetSystemInfo() { std::ostringstream arch; std::ostringstream os; @@ -76,7 +79,7 @@ inline SystemInfo GetSystemInfo() { #else os << kUnsupported; #endif - return SystemInfo{os.str(), arch.str()}; + return std::make_unique(os.str(), arch.str()); } inline bool IsNvidiaSmiAvailable() {