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
1 change: 1 addition & 0 deletions src/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,7 @@ ovms_cc_library(
"//src/kfserving_api:kfserving_api_cpp",
"capimodule",
"//src/pull_module:hf_pull_model_module",
"//src/graph_export:graph_export",
"//src/servables_config_manager_module:servablesconfigmanagermodule",
"predict_request_validation_utils", # to be removed when capi has its own lib and added there @atobisze
"kfs_backend_impl",
Expand Down
16 changes: 13 additions & 3 deletions src/cli_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,9 @@ void CLIParser::prepareGraph(ServerSettingsImpl& serverSettings, HFSettingsImpl&
}
if (result->count("source_model")) {
hfSettings.sourceModel = result->operator[]("source_model").as<std::string>();
} else if (result->count("model_name")) {
} else if (result->count("model_name") && !result->count("model_path")) {
// Only use model_name as source_model when model_path is not set
// (when model_path is set, user wants to use local model without HF pull)
hfSettings.sourceModel = result->operator[]("model_name").as<std::string>();
}
if ((result->count("weight-format") || result->count("extra_quantization_params")) && isOptimumCliDownload(hfSettings.sourceModel, hfSettings.ggufFilename)) {
Expand All @@ -732,6 +734,11 @@ void CLIParser::prepareGraph(ServerSettingsImpl& serverSettings, HFSettingsImpl&
if (result->count("vocoder"))
hfSettings.exportSettings.vocoder = result->operator[]("vocoder").as<std::string>();
hfSettings.downloadPath = result->operator[]("model_repository_path").as<std::string>();
// 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>();
}
if (result->count("task")) {
hfSettings.task = stringToEnum(result->operator[]("task").as<std::string>());
switch (hfSettings.task) {
Expand Down Expand Up @@ -840,11 +847,14 @@ void CLIParser::prepareGraphStart(HFSettingsImpl& hfSettings, ModelsSettingsImpl
// Model settings
if (result->count("model_name")) {
modelsSettings.modelName = result->operator[]("model_name").as<std::string>();
} else {
} else if (!hfSettings.sourceModel.empty()) {
modelsSettings.modelName = hfSettings.sourceModel;
}

modelsSettings.modelPath = FileSystem::joinPath({hfSettings.downloadPath, hfSettings.sourceModel});
// Only override modelPath if it wasn't already set via --model_path
if (!result->count("model_path")) {
modelsSettings.modelPath = FileSystem::joinPath({hfSettings.downloadPath, hfSettings.sourceModel});
}
}

void CLIParser::prepare(ServerSettingsImpl* serverSettings, ModelsSettingsImpl* modelsSettings) {
Expand Down
18 changes: 11 additions & 7 deletions src/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,17 @@ bool Config::validateUserSettingsInConfigAddRemoveModel(const ModelsSettingsImpl

bool Config::validate() {
if (this->serverSettings.serverMode == HF_PULL_MODE || this->serverSettings.serverMode == HF_PULL_AND_START_MODE) {
if (!serverSettings.hfSettings.sourceModel.size()) {
std::cerr << "source_model parameter is required for pull mode";
return false;
}
if (!serverSettings.hfSettings.downloadPath.size()) {
std::cerr << "model_repository_path parameter is required for pull mode";
return false;
// When --task is used with --model_path (no HF pulling), sourceModel and downloadPath are not required
bool taskWithModelPath = this->serverSettings.serverMode == HF_PULL_AND_START_MODE && !this->modelsSettings.modelPath.empty();
if (!taskWithModelPath) {
if (!serverSettings.hfSettings.sourceModel.size()) {
std::cerr << "source_model parameter is required for pull mode";
return false;
}
if (!serverSettings.hfSettings.downloadPath.size()) {
std::cerr << "model_repository_path parameter is required for pull mode";
return false;
}
}
if (this->serverSettings.hfSettings.task == UNKNOWN_GRAPH) {
std::cerr << "Error: --task parameter not set." << std::endl;
Expand Down
62 changes: 44 additions & 18 deletions src/graph_export/graph_export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,20 @@
#endif
namespace ovms {

static std::string s_inMemoryGraphContent;

bool GraphExport::hasInMemoryGraphContent() {
return !s_inMemoryGraphContent.empty();
}

const std::string& GraphExport::getInMemoryGraphContent() {
return s_inMemoryGraphContent;
}

void GraphExport::clearInMemoryGraphContent() {
s_inMemoryGraphContent.clear();
}

static const std::string OVMS_VERSION_GRAPH_LINE = std::string("# File created with: ") + PROJECT_NAME + std::string(" ") + PROJECT_VERSION + std::string("\n");

static std::string constructModelsPath(const std::string& modelPath, const std::optional<std::string>& ggufFilenameOpt) {
Expand Down Expand Up @@ -91,7 +105,7 @@ std::string GraphExport::getDraftModelDirectoryPath(const std::string& directory
} \
auto pluginConfigOpt = std::get<std::optional<std::string>>(pluginConfigOrStatus)

static Status createPbtxtFile(const std::string& directoryPath, const std::string& pbtxtContent) {
static Status createPbtxtFile(const std::string& directoryPath, const std::string& pbtxtContent, bool writeToFile) {
#if (MEDIAPIPE_DISABLE == 0)
::mediapipe::CalculatorGraphConfig config;
SPDLOG_TRACE("Generated pbtxt: {}", pbtxtContent);
Expand All @@ -101,12 +115,16 @@ static Status createPbtxtFile(const std::string& directoryPath, const std::strin
return StatusCode::MEDIAPIPE_GRAPH_CONFIG_FILE_INVALID;
}
#endif
if (!writeToFile) {
s_inMemoryGraphContent = pbtxtContent;
return StatusCode::OK;
}
// clang-format on
std::string fullPath = FileSystem::joinPath({directoryPath, "graph.pbtxt"});
return FileSystem::createFileOverwrite(fullPath, pbtxtContent);
Comment on lines +118 to 124
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

When writeToFile==false, s_inMemoryGraphContent is populated, but there is no corresponding clearing when writeToFile==true (or before generating a new graph). This means a previous in-memory graph can persist and later be picked up by GraphExport::hasInMemoryGraphContent(), causing unrelated runs/tests to load stale graph content instead of reading graph.pbtxt from disk. Clear the in-memory content whenever you generate/write a file-based graph (and/or at the start of each server startup) so the in-memory fallback cannot leak across runs.

Copilot uses AI. Check for mistakes.
}

static Status createTextGenerationGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings) {
static Status createTextGenerationGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings, bool writeToFile) {
if (!std::holds_alternative<TextGenGraphSettingsImpl>(hfSettings.graphSettings)) {
SPDLOG_ERROR("Graph options not initialized for text generation.");
return StatusCode::INTERNAL_ERROR;
Expand Down Expand Up @@ -198,10 +216,10 @@ static Status createTextGenerationGraphTemplate(const std::string& directoryPath
}
}
})";
return createPbtxtFile(directoryPath, oss.str());
return createPbtxtFile(directoryPath, oss.str(), writeToFile);
}

static Status createRerankGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings) {
static Status createRerankGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings, bool writeToFile) {
if (!std::holds_alternative<RerankGraphSettingsImpl>(hfSettings.graphSettings)) {
SPDLOG_ERROR("Graph options not initialized for reranking.");
return StatusCode::INTERNAL_ERROR;
Expand Down Expand Up @@ -242,10 +260,10 @@ node {
}
}
})";
return createPbtxtFile(directoryPath, oss.str());
return createPbtxtFile(directoryPath, oss.str(), writeToFile);
}

static Status createEmbeddingsGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings) {
static Status createEmbeddingsGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings, bool writeToFile) {
if (!std::holds_alternative<EmbeddingsGraphSettingsImpl>(hfSettings.graphSettings)) {
SPDLOG_ERROR("Graph options not initialized for embeddings.");
return StatusCode::INTERNAL_ERROR;
Expand Down Expand Up @@ -289,10 +307,10 @@ node {
oss << R"(}
}
})";
return createPbtxtFile(directoryPath, oss.str());
return createPbtxtFile(directoryPath, oss.str(), writeToFile);
}

static Status createTextToSpeechGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings) {
static Status createTextToSpeechGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings, bool writeToFile) {
if (!std::holds_alternative<TextToSpeechGraphSettingsImpl>(hfSettings.graphSettings)) {
SPDLOG_ERROR("Graph options not initialized for speech generation.");
return StatusCode::INTERNAL_ERROR;
Expand Down Expand Up @@ -339,11 +357,15 @@ node {
}
#endif
// clang-format on
if (!writeToFile) {
s_inMemoryGraphContent = oss.str();
return StatusCode::OK;
}
std::string fullPath = FileSystem::joinPath({directoryPath, "graph.pbtxt"});
return FileSystem::createFileOverwrite(fullPath, oss.str());
}

static Status createSpeechToTextGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings) {
static Status createSpeechToTextGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings, bool writeToFile) {
if (!std::holds_alternative<SpeechToTextGraphSettingsImpl>(hfSettings.graphSettings)) {
SPDLOG_ERROR("Graph options not initialized for speech to text.");
return StatusCode::INTERNAL_ERROR;
Expand Down Expand Up @@ -389,11 +411,15 @@ node {
}
#endif
// clang-format on
if (!writeToFile) {
s_inMemoryGraphContent = oss.str();
return StatusCode::OK;
}
std::string fullPath = FileSystem::joinPath({directoryPath, "graph.pbtxt"});
return FileSystem::createFileOverwrite(fullPath, oss.str());
}

static Status createImageGenerationGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings) {
static Status createImageGenerationGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings, bool writeToFile) {
if (!std::holds_alternative<ImageGenerationGraphSettingsImpl>(hfSettings.graphSettings)) {
SPDLOG_ERROR("Graph options not initialized for image generation.");
return StatusCode::INTERNAL_ERROR;
Expand Down Expand Up @@ -473,13 +499,13 @@ node: {
}
)";
// clang-format on
return createPbtxtFile(directoryPath, oss.str());
return createPbtxtFile(directoryPath, oss.str(), writeToFile);
}

GraphExport::GraphExport() {
}

Status GraphExport::createServableConfig(const std::string& directoryPath, const HFSettingsImpl& hfSettings) {
Status GraphExport::createServableConfig(const std::string& directoryPath, const HFSettingsImpl& hfSettings, bool writeToFile) {
if (directoryPath.empty()) {
SPDLOG_ERROR("Directory path empty: {}", directoryPath);
return StatusCode::PATH_INVALID;
Expand All @@ -502,17 +528,17 @@ Status GraphExport::createServableConfig(const std::string& directoryPath, const
}
}
if (hfSettings.task == TEXT_GENERATION_GRAPH) {
return createTextGenerationGraphTemplate(directoryPath, hfSettings);
return createTextGenerationGraphTemplate(directoryPath, hfSettings, writeToFile);
} else if (hfSettings.task == EMBEDDINGS_GRAPH) {
return createEmbeddingsGraphTemplate(directoryPath, hfSettings);
return createEmbeddingsGraphTemplate(directoryPath, hfSettings, writeToFile);
} else if (hfSettings.task == RERANK_GRAPH) {
return createRerankGraphTemplate(directoryPath, hfSettings);
return createRerankGraphTemplate(directoryPath, hfSettings, writeToFile);
} else if (hfSettings.task == IMAGE_GENERATION_GRAPH) {
return createImageGenerationGraphTemplate(directoryPath, hfSettings);
return createImageGenerationGraphTemplate(directoryPath, hfSettings, writeToFile);
} else if (hfSettings.task == TEXT_TO_SPEECH_GRAPH) {
return createTextToSpeechGraphTemplate(directoryPath, hfSettings);
return createTextToSpeechGraphTemplate(directoryPath, hfSettings, writeToFile);
} else if (hfSettings.task == SPEECH_TO_TEXT_GRAPH) {
return createSpeechToTextGraphTemplate(directoryPath, hfSettings);
return createSpeechToTextGraphTemplate(directoryPath, hfSettings, writeToFile);
} else if (hfSettings.task == UNKNOWN_GRAPH) {
SPDLOG_ERROR("Graph options not initialized.");
return StatusCode::INTERNAL_ERROR;
Expand Down
6 changes: 5 additions & 1 deletion src/graph_export/graph_export.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,13 @@ class Status;
class GraphExport {
public:
GraphExport();
Status createServableConfig(const std::string& directoryPath, const HFSettingsImpl& graphSettings);
Status createServableConfig(const std::string& directoryPath, const HFSettingsImpl& graphSettings, bool writeToFile = true);
static std::variant<std::optional<std::string>, Status> createPluginString(const ExportSettings& exportSettings);
static std::string getDraftModelDirectoryName(std::string draftModel);
static std::string getDraftModelDirectoryPath(const std::string& directoryPath, const std::string& draftModel);

static bool hasInMemoryGraphContent();
static const std::string& getInMemoryGraphContent();
static void clearInMemoryGraphContent();
};
} // namespace ovms
1 change: 1 addition & 0 deletions src/mediapipe_internal/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ ovms_cc_library(
"//src:libovms_servable_name_checker",
"//src/metrics:libovms_metric_provider",
"//src/filesystem:libovmsfilesystem",
"//src/graph_export:graph_export",
"//src:libovms_version",
"//src:libovms_execution_context",
"//src:libovmstimer",
Expand Down
5 changes: 5 additions & 0 deletions src/mediapipe_internal/mediapipegraphconfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <spdlog/spdlog.h>

#include "src/filesystem/filesystem.hpp"
#include "src/graph_export/graph_export.hpp"
#include "../status.hpp"

namespace ovms {
Expand Down Expand Up @@ -129,6 +130,10 @@ Status MediapipeGraphConfig::parseNode(const rapidjson::Value& v) {
}

void MediapipeGraphConfig::logGraphConfigContent() const {
if (GraphExport::hasInMemoryGraphContent()) {
SPDLOG_DEBUG("Content of in-memory graph config:\n{}", GraphExport::getInMemoryGraphContent());
return;
}
std::ifstream fileStream(this->graphPath);
if (!fileStream.is_open()) {
SPDLOG_ERROR("Failed to open file: {}", this->graphPath);
Expand Down
8 changes: 8 additions & 0 deletions src/mediapipe_internal/mediapipegraphdefinition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

#include "../execution_context.hpp"
#include "src/filesystem/filesystem.hpp"
#include "src/graph_export/graph_export.hpp"
#include "src/metrics/metric.hpp"
#include "../model_metric_reporter.hpp"
#include "../ov_utils.hpp"
Expand Down Expand Up @@ -60,6 +61,13 @@ const tensor_map_t MediapipeGraphDefinition::getOutputsInfo() const {
}

Status MediapipeGraphDefinition::validateForConfigFileExistence() {
if (GraphExport::hasInMemoryGraphContent()) {
const std::string& content = GraphExport::getInMemoryGraphContent();
this->chosenConfig = content;
this->mgconfig.setCurrentGraphPbTxtMD5(ovms::FileSystem::getStringMD5(content));
SPDLOG_LOGGER_DEBUG(modelmanager_logger, "Using in-memory graph content for mediapipe graph definition: {}", this->getName());
return StatusCode::OK;
}
Comment on lines 63 to +70
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

validateForConfigFileExistence() unconditionally prefers GraphExport in-memory content whenever it is set. Since that content is global and not scoped to a particular graph/model, this can cause the wrong graph to be loaded (e.g., if a previous startup/test left in-memory content set) and will also bypass filesystem-based reload semantics. Consider scoping the in-memory graph to the specific startup flow (or at least clearing it after it has been consumed) so normal file-based graphs aren’t shadowed.

Copilot uses AI. Check for mistakes.
std::ifstream ifs(this->mgconfig.getGraphPath());
if (!ifs.is_open()) {
SPDLOG_LOGGER_ERROR(modelmanager_logger, "Failed to open mediapipe graph definition: {}, file: {}\n", this->getName(), this->mgconfig.getGraphPath());
Expand Down
13 changes: 9 additions & 4 deletions src/modelmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
#include "dags/pipelinedefinition.hpp"
#include "filesystem/filesystem.hpp"
#include "filesystem/filesystemfactory.hpp"
#include "graph_export/graph_export.hpp"
#include "logging.hpp"
#if (MEDIAPIPE_DISABLE == 0)
#include "mediapipe_internal/mediapipefactory.hpp"
Expand Down Expand Up @@ -229,7 +230,8 @@ Status ModelManager::startFromConfig() {

std::vector<MediapipeGraphConfig> mediapipesInConfigFile;
std::ifstream ifs(mpConfig.getGraphPath());
if (ifs.is_open()) {
bool graphAvailable = ifs.is_open() || GraphExport::hasInMemoryGraphContent();
if (graphAvailable) {
// Single model with graph.pbtxt, check if user passed model unsupported model parameters in cmd arguments
Comment on lines 232 to 235
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

ModelManager::startFromConfig() treats GraphExport::hasInMemoryGraphContent() as equivalent to having a graph.pbtxt on disk. Because the in-memory graph is process-global and not tied to mpConfig/modelPath, a stale value could make graphAvailable true and skip the non-graph startup path incorrectly. To avoid incorrect graph detection, gate this on the current server mode/CLI path (only use in-memory graph in the specific HF/task+model_path flow) and/or ensure the in-memory content is cleared deterministically after use.

Copilot uses AI. Check for mistakes.
status = ModelManager::validateUserSettingsInSingleModelCliGraphStart(config.getModelSettings());
if (!status.ok())
Expand Down Expand Up @@ -473,10 +475,13 @@ bool ModelManager::CheckStartFromGraph(std::string inputPath, MediapipeGraphConf
if (ifs.is_open()) {
SPDLOG_LOGGER_DEBUG(modelmanager_logger, "Graph: {} path: {} exists", mpConfig.getGraphName(), mpConfig.getGraphPath());
return true;
} else {
SPDLOG_LOGGER_DEBUG(modelmanager_logger, "Graph: {} path: {} does not exist", mpConfig.getGraphName(), mpConfig.getGraphPath());
return false;
}
if (GraphExport::hasInMemoryGraphContent()) {
SPDLOG_LOGGER_DEBUG(modelmanager_logger, "Graph: {} using in-memory graph content", mpConfig.getGraphName());
return true;
}
SPDLOG_LOGGER_DEBUG(modelmanager_logger, "Graph: {} path: {} does not exist", mpConfig.getGraphName(), mpConfig.getGraphPath());
return false;
}

Status ModelManager::validateUserSettingsInSingleModelCliGraphStart(const ModelsSettingsImpl& modelsSettings) {
Expand Down
9 changes: 7 additions & 2 deletions src/pull_module/hf_pull_model_module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,16 @@ Status HfPullModelModule::clone() const {
}

GraphExport graphExporter;
status = graphExporter.createServableConfig(graphDirectory, this->hfSettings);
bool writeToFile = (ovms::Config::instance().getServerSettings().serverMode == HF_PULL_MODE);
status = graphExporter.createServableConfig(graphDirectory, this->hfSettings, writeToFile);
if (!status.ok()) {
return status;
}
std::cout << "Graph: graph.pbtxt created in: " << graphDirectory << std::endl;
if (writeToFile) {
std::cout << "Graph: graph.pbtxt created in: " << graphDirectory << std::endl;
} else {
std::cout << "Graph: graph.pbtxt content stored in memory" << std::endl;
}

return StatusCode::OK;
}
Expand Down
32 changes: 23 additions & 9 deletions src/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
#include "capi_frontend/server_settings.hpp"
#include "cli_parser.hpp"
#include "config.hpp"
#include "graph_export/graph_export.hpp"
#include "grpcservermodule.hpp"
#include "http_server.hpp"
#include "httpservermodule.hpp"
Expand Down Expand Up @@ -378,16 +379,29 @@ Status Server::startModules(ovms::Config& config) {
return status;
}
if (config.getServerSettings().serverMode == HF_PULL_MODE || config.getServerSettings().serverMode == HF_PULL_AND_START_MODE) {
INSERT_MODULE(HF_MODEL_PULL_MODULE_NAME, it);
START_MODULE(it);
if (!status.ok()) {
return status;
bool needsHfPull = !config.getServerSettings().hfSettings.sourceModel.empty();
if (needsHfPull) {
INSERT_MODULE(HF_MODEL_PULL_MODULE_NAME, it);
START_MODULE(it);
if (!status.ok()) {
return status;
}
auto hfModule = dynamic_cast<const HfPullModelModule*>(it->second.get());
status = hfModule->clone();
// Return from modules only in --pull mode or error, otherwise start the rest of modules
if (config.getServerSettings().serverMode == HF_PULL_MODE || !status.ok())
return status;
} else {
// --task with --model_path: create graph in memory without HF download
GraphExport graphExporter;
const auto& hfSettings = config.getServerSettings().hfSettings;
status = graphExporter.createServableConfig(config.modelPath(), hfSettings, false);
if (!status.ok()) {
SPDLOG_ERROR("Failed to create in-memory graph config: {}", status.string());
return status;
}
SPDLOG_INFO("Graph config created in memory from model_path: {}", config.modelPath());
}
Comment on lines 381 to 404
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

startModules() can enter HF_PULL(_AND_START)_MODE with an existing GraphExport in-memory graph still set from a previous run (GraphExport is process-global). Because this function doesn’t clear that state, later graph detection paths may incorrectly treat a graph as available even when graph.pbtxt isn’t present on disk. Consider explicitly calling GraphExport::clearInMemoryGraphContent() at the beginning of the HF_PULL/HF_PULL_AND_START handling (and/or after successful disk write) to prevent stale in-memory graphs from affecting subsequent startups.

Copilot uses AI. Check for mistakes.
auto hfModule = dynamic_cast<const HfPullModelModule*>(it->second.get());
status = hfModule->clone();
// Return from modules only in --pull mode or error, otherwise start the rest of modules
if (config.getServerSettings().serverMode == HF_PULL_MODE || !status.ok())
return status;
}

#if (PYTHON_DISABLE == 0)
Expand Down
Loading