Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions src/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -357,12 +357,26 @@ ovms_cc_library(
visibility = ["//visibility:public",],
additional_copts = COPTS_DROGON,
)
ovms_cc_library(
name = "libovms_default_task",
hdrs = ["default_task.hpp"],
srcs = ["default_task.cpp"],
deps = [
"@com_github_tencent_rapidjson//:rapidjson",
"libovmsstring_utils",
"libovmsstatus",
"//src/pull_module:curl_downloader",
"//src/pull_module:hf_env_vars",
],
visibility = ["//visibility:public",],
)
ovms_cc_library(
name = "libovms_cliparser",
hdrs = ["cli_parser.hpp"],
srcs = ["cli_parser.cpp"],
deps = [
"@com_github_jarro2783_cxxopts//:cxxopts",
"libovms_default_task",
"libovms_server_settings",
"libovms_version",
"//src/filesystem:libovmsfilesystem",
Expand Down Expand Up @@ -2158,8 +2172,6 @@ cc_binary(
linkstatic = True,
)



cc_binary(
name = "optimum-cli",
srcs = [
Expand Down Expand Up @@ -2273,6 +2285,7 @@ cc_test(
"test/tensor_conversion_test.cpp",
"test/tensorinfo_test.cpp",
"test/tensorutils_test.cpp",
"test/task_determine_test.cpp",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add as separate test library target and make those new config files as data field there.

"test/test_http_utils.hpp",
"test/tfs_rest_parser_binary_inputs_test.cpp",
"test/tfs_rest_parser_column_test.cpp",
Expand Down Expand Up @@ -2468,7 +2481,7 @@ cc_test(
"//src:libcustom_node_image_transformation.so",
"//src:libcustom_node_add_one.so",
"//src:libcustom_node_horizontal_ocr.so",
],
] + glob(["test/models_config_json/**"]),
deps = [
"optimum-cli",
"//src:ovms_lib",
Expand Down
112 changes: 100 additions & 12 deletions src/cli_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@
#include "cli_parser.hpp"

#include <filesystem>
#include <fstream>
#include <iostream>
#include <optional>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include <variant>

#include "capi_frontend/server_settings.hpp"
#include "default_task.hpp"
#include "logging.hpp"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spdlog was not used in cli_parser on purpose - during cli parsing spdlog could be not initialized yet.

#include "graph_export/graph_cli_parser.hpp"
#include "graph_export/rerank_graph_cli_parser.hpp"
#include "graph_export/embeddings_graph_cli_parser.hpp"
Expand All @@ -33,13 +37,14 @@
#include "ovms_exit_codes.hpp"
#include "filesystem/filesystem.hpp"
#include "filesystem/localfilesystem.hpp"
#include "stringutils.hpp"
#include "version.hpp"

namespace ovms {

constexpr const char* CONFIG_MANAGEMENT_HELP_GROUP{"config management"};
constexpr const char* API_KEY_ENV_VAR{"API_KEY"};
constexpr const char* MODEL_CONFIG_FILENAME{"config.json"};
constexpr const char* MODEL_INDEX_FILENAME{"model_index.json"};

std::string getConfigPath(const std::string& configPath) {
bool isDir = false;
Expand All @@ -53,6 +58,19 @@ std::string getConfigPath(const std::string& configPath) {
return configPath;
}

std::string CLIParser::getEffectiveTaskParameter() const {
if (result->count("task")) {
const auto task = result->operator[]("task").as<std::string>();
SPDLOG_DEBUG("Effective task parameter specified by user: {}", task);
return task;
}
if (inferredTaskParameter.has_value()) {
SPDLOG_DEBUG("Effective task parameter using inferred default: {}", inferredTaskParameter.value());
return inferredTaskParameter.value();
}
throw std::logic_error("error parsing options - --task parameter wasn't passed");
}

std::variant<bool, std::pair<int, std::string>> CLIParser::parse(int argc, char** argv) {
std::stringstream ss;
try {
Expand Down Expand Up @@ -299,7 +317,7 @@ std::variant<bool, std::pair<int, std::string>> CLIParser::parse(int argc, char*

options->add_options("generative task (applies to: pull hf model, single model)")
("task",
"Specifies the generative task for the local model. It should be followed by task specific parameters. Supported tasks: text_generation, embeddings, rerank, image_generation, text2speech, speech2text. It creates the pipeline graph in memory based on the provided task-specific options.",
"Specifies the generative task for the local model. If not provided, default task value is inferred from model config.json architectures. It should be followed by task specific parameters. Supported tasks: text_generation, embeddings, rerank, image_generation, text2speech, speech2text. It creates the pipeline graph in memory based on the provided task-specific options.",
cxxopts::value<std::string>(),
"TASK");
configOptions->custom_help("");
Expand Down Expand Up @@ -335,12 +353,73 @@ std::variant<bool, std::pair<int, std::string>> CLIParser::parse(int argc, char*

result = std::make_unique<cxxopts::ParseResult>(options->parse(argc, argv));

const bool isConfigManagementFlow =

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be a CLIParser method

result->count("add_to_config") || result->count("remove_from_config") || result->count("list_models");
if (!result->count("task") &&
!result->count("pull") &&
!result->count("source_model") &&
result->count("model_path") &&
!isConfigManagementFlow &&
!result->count("help") &&
!result->count("version")) {
const std::optional<std::string> modelPath = std::make_optional(result->operator[]("model_path").as<std::string>());
const auto configPath = std::filesystem::path(*modelPath) / MODEL_CONFIG_FILENAME;
const auto indexPath = std::filesystem::path(*modelPath) / MODEL_INDEX_FILENAME;
if (std::filesystem::exists(configPath) || std::filesystem::exists(indexPath)) {
// Check if task-specific parameters are provided or if graph.pbtxt is missing
bool hasUnmatchedOptions = ::ovms::hasTaskSpecificParameters(result->unmatched());
bool graphExists = ::ovms::graphPbtxtExists(*modelPath);

// Infer task if:
// 1. Task-specific parameters are provided (unmatched options), OR
// 2. graph.pbtxt doesn't exist (need to create in-memory graph)
// Otherwise, if graph.pbtxt exists and no task parameters, use the filesystem graph
if (hasUnmatchedOptions || !graphExists) {
try {
inferredTaskParameter = determineDefaultTaskParameter(modelPath, std::nullopt, std::nullopt);
} catch (const std::exception& e) {
SPDLOG_DEBUG("Default task inference skipped for model_path '{}': {}", modelPath.value_or(""), e.what());
}
}
}
}

// HF pull mode or pull and start mode or starting from local folder with graph created in memory
if (isHFPullOrPullAndStart(this->result) || isInMemoryGraphMode(this->result)) {
std::vector<std::string> unmatchedOptions;
GraphExportType task;
if (result->count("task")) {
task = stringToEnum(result->operator[]("task").as<std::string>());
std::string taskValue;
if (!result->count("task") && !result->count("help") && !result->count("version")) {
const std::optional<std::string> modelPath = result->count("model_path") ? std::make_optional(result->operator[]("model_path").as<std::string>()) : std::nullopt;
const std::optional<std::string> sourceModel = result->count("source_model") ? std::make_optional(result->operator[]("source_model").as<std::string>()) : std::nullopt;
Comment thread
dtrawins marked this conversation as resolved.
const std::optional<std::string> modelRepositoryPath = result->count("model_repository_path") ? std::make_optional(result->operator[]("model_repository_path").as<std::string>()) : std::nullopt;

// For source_model (HF pull mode), always infer the task
// For model_path in in-memory graph mode, check if task should be inferred based on parameters and graph.pbtxt
bool shouldInferTask = false;
if (sourceModel.has_value() && !sourceModel->empty()) {
// Always infer task when pulling from HuggingFace
shouldInferTask = true;
} else if (modelPath.has_value() && !modelPath->empty()) {
// For local model_path, infer task if:
// 1. Unmatched options (task-specific parameters) are present, OR
// 2. graph.pbtxt doesn't exist (need to create in-memory graph)
bool hasUnmatchedOptions = ::ovms::hasTaskSpecificParameters(result->unmatched());
bool graphExists = ::ovms::graphPbtxtExists(*modelPath);
shouldInferTask = hasUnmatchedOptions || !graphExists;
}

if (shouldInferTask) {
try {
inferredTaskParameter = determineDefaultTaskParameter(modelPath, sourceModel, modelRepositoryPath);
} catch (const std::exception& e) {
SPDLOG_DEBUG("Default task inference skipped for source_model '{}': {}", sourceModel.value_or(""), e.what());
}
}
}
taskValue = getEffectiveTaskParameter();
task = stringToEnum(taskValue);
if (task != UNKNOWN_GRAPH) {
switch (task) {
case TEXT_GENERATION_GRAPH: {
GraphCLIParser cliParser;
Expand Down Expand Up @@ -379,12 +458,12 @@ std::variant<bool, std::pair<int, std::string>> CLIParser::parse(int argc, char*
break;
}
case UNKNOWN_GRAPH: {
ss << "error parsing options - --task parameter unsupported value: " + result->operator[]("task").as<std::string>();
ss << "error parsing options - --task parameter unsupported value: " + taskValue;
return std::make_pair(OVMS_EX_USAGE, ss.str());
}
}
} else {
ss << "error parsing options - --task parameter wasn't passed";
ss << "error parsing options - --task parameter unsupported value: " + taskValue;
return std::make_pair(OVMS_EX_USAGE, ss.str());
}

Expand Down Expand Up @@ -671,11 +750,14 @@ bool CLIParser::isHFPullOrPullAndStart(const std::unique_ptr<cxxopts::ParseResul
// parse-time checks that rely on this helper continue to reject combining
// task-based flows with config-management modes. More specific mode
// differentiation is handled by isInMemoryGraphMode().
return (result->count("pull") || result->count("task"));
return (result->count("pull") || result->count("task") || result->count("source_model"));
}

bool CLIParser::isInMemoryGraphMode(const std::unique_ptr<cxxopts::ParseResult>& result) {
return (result->count("task") && !result->count("source_model") && !result->count("pull"));
if (result->count("source_model") || result->count("pull")) {
return false;
}
return result->count("task") || inferredTaskParameter.has_value();
}

void CLIParser::prepareGraph(ServerSettingsImpl& serverSettings, HFSettingsImpl& hfSettings, const std::string& modelName) {
Expand Down Expand Up @@ -731,10 +813,16 @@ void CLIParser::prepareGraph(ServerSettingsImpl& serverSettings, HFSettingsImpl&
// When --task is used with --model_path but without --pull/--source_model,
// use model_path as the model location (no HF download needed)
if (!result->count("pull") && !result->count("source_model") && result->count("model_path")) {
hfSettings.exportSettings.modelPath = result->operator[]("model_path").as<std::string>();
const auto configuredModelPath = std::filesystem::path(result->operator[]("model_path").as<std::string>());
hfSettings.exportSettings.modelPath = std::filesystem::absolute(configuredModelPath).lexically_normal().string();
SPDLOG_DEBUG("Using local absolute model path for graph export: {}", hfSettings.exportSettings.modelPath);
}
const std::string taskValue = getEffectiveTaskParameter();
if (inferredTaskParameter.has_value()) {
SPDLOG_INFO("Identified default task '{}' from model config", inferredTaskParameter.value());
Comment on lines +818 to +822

@atobiszei atobiszei Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We did not use spdlog in cli_parser on purpose. Logging could not be setup yet. Use cout. We also did use cout for model pulling.

}
if (result->count("task")) {
hfSettings.task = stringToEnum(result->operator[]("task").as<std::string>());
if (!taskValue.empty()) {
hfSettings.task = stringToEnum(taskValue);
switch (hfSettings.task) {
case TEXT_GENERATION_GRAPH: {
if (std::holds_alternative<GraphCLIParser>(this->graphOptionsParser)) {
Expand Down Expand Up @@ -785,7 +873,7 @@ void CLIParser::prepareGraph(ServerSettingsImpl& serverSettings, HFSettingsImpl&
break;
}
case UNKNOWN_GRAPH: {
throw std::logic_error("Error: --task parameter unsupported value: " + result->operator[]("task").as<std::string>());
throw std::logic_error("Error: --task parameter unsupported value: " + taskValue);
break;
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/cli_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#pragma once

#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <variant>
Expand All @@ -38,13 +39,15 @@ class CLIParser {
std::unique_ptr<cxxopts::Options> options;
std::unique_ptr<cxxopts::ParseResult> result;
std::variant<GraphCLIParser, RerankGraphCLIParser, EmbeddingsGraphCLIParser, ImageGenerationGraphCLIParser, TextToSpeechGraphCLIParser, SpeechToTextGraphCLIParser> graphOptionsParser;
std::optional<std::string> inferredTaskParameter;

public:
CLIParser() = default;
std::variant<bool, std::pair<int, std::string>> parse(int argc, char** argv);
void prepare(ServerSettingsImpl*, ModelsSettingsImpl*);

protected:
std::string getEffectiveTaskParameter() const;
void prepareServer(ServerSettingsImpl& serverSettings);
void prepareModel(ModelsSettingsImpl& modelsSettings, HFSettingsImpl& hfSettings);
void prepareGraph(ServerSettingsImpl& serverSettings, HFSettingsImpl& hfSettings, const std::string& modelName);
Expand Down
Loading