Skip to content

Commit 849c777

Browse files
dtrawinsCopilotCopilot
authored
configuring genai model deployment with model readonly filesystem (#4139)
### 🛠 Summary CVS-186376 Allows starting generative models on readonly filesystem with option to change runtime parameters and accepts models without graph.pbtxt. It can be done with `--model_path` and `--task` along with other task specific paramters. There is also removed default --task value as text_generation. Task should be explicit if graph.pbtxt should be created. The logic is: - if --source_model is provided, OVMS will assume to path of pulling the model from HF if missing on the local target path. It requires RW filesystem. Should be used always with --task. - if --source_model is not provided and deployment is with --model_name and --model_path, task is optional. With empty --task, graph from local model folder will be used. With set task, all runtime params will be from CLI and graph will be in memory without saving to model folder. ### 🧪 Checklist - [ ] Unit tests added. - [ ] The documentation updated. - [ ] Change follows security best practices. `` --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: dtrawins <20400806+dtrawins@users.noreply.github.com>
1 parent 865379f commit 849c777

19 files changed

Lines changed: 319 additions & 53 deletions

src/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,7 @@ ovms_cc_library(
649649
"//src/dags:pipelinedefinition",
650650
"//src/filesystem:libovmsfilesystem",
651651
"//src/filesystem:libovmsfilesystemfactory",
652+
"//src/graph_export:graph_export",
652653
"//src/metrics:libovms_metric_provider",
653654
"//src/metrics:libovmsmetrics",
654655
"@com_github_tencent_rapidjson//:rapidjson",
@@ -938,6 +939,7 @@ ovms_cc_library(
938939
"//src/kfserving_api:kfserving_api_cpp",
939940
"capimodule",
940941
"//src/pull_module:hf_pull_model_module",
942+
"//src/graph_export:graph_export",
941943
"//src/servables_config_manager_module:servablesconfigmanagermodule",
942944
"predict_request_validation_utils", # to be removed when capi has its own lib and added there @atobisze
943945
"kfs_backend_impl",

src/capi_frontend/server_settings.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ enum OvmsServerMode : int {
9090
HF_PULL_AND_START_MODE,
9191
LIST_MODELS_MODE,
9292
MODIFY_CONFIG_MODE,
93+
IN_MEMORY_GRAPH_MODE,
9394
UNKNOWN_MODE
9495
};
9596

@@ -171,7 +172,7 @@ struct HFSettingsImpl {
171172
std::string downloadPath = "";
172173
bool overwriteModels = false;
173174
ModelDownlaodType downloadType = GIT_CLONE_DOWNLOAD;
174-
GraphExportType task = TEXT_GENERATION_GRAPH;
175+
GraphExportType task = UNKNOWN_GRAPH;
175176
std::variant<TextGenGraphSettingsImpl, RerankGraphSettingsImpl, EmbeddingsGraphSettingsImpl, TextToSpeechGraphSettingsImpl, SpeechToTextGraphSettingsImpl, ImageGenerationGraphSettingsImpl> graphSettings;
176177
};
177178

src/cli_parser.cpp

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -344,8 +344,8 @@ std::variant<bool, std::pair<int, std::string>> CLIParser::parse(int argc, char*
344344

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

347-
// HF pull mode or pull and start mode
348-
if (isHFPullOrPullAndStart(this->result)) {
347+
// HF pull mode or pull and start mode or starting from local folder with graph created in memory
348+
if (isHFPullOrPullAndStart(this->result) || isInMemoryGraphMode(this->result)) {
349349
std::vector<std::string> unmatchedOptions;
350350
GraphExportType task;
351351
if (result->count("task")) {
@@ -692,13 +692,27 @@ void CLIParser::prepareModel(ModelsSettingsImpl& modelsSettings, HFSettingsImpl&
692692
}
693693

694694
bool CLIParser::isHFPullOrPullAndStart(const std::unique_ptr<cxxopts::ParseResult>& result) {
695+
// Keep `--task` in the broad mutually exclusive task/pull CLI category so
696+
// parse-time checks that rely on this helper continue to reject combining
697+
// task-based flows with config-management modes. More specific mode
698+
// differentiation is handled by isInMemoryGraphMode().
695699
return (result->count("pull") || result->count("task"));
696700
}
697701

702+
bool CLIParser::isInMemoryGraphMode(const std::unique_ptr<cxxopts::ParseResult>& result) {
703+
return (result->count("task") && !result->count("source_model") && !result->count("pull"));
704+
}
705+
698706
void CLIParser::prepareGraph(ServerSettingsImpl& serverSettings, HFSettingsImpl& hfSettings, const std::string& modelName) {
707+
// Always propagate source_model so validation can detect misuse
708+
if (result->count("source_model")) {
709+
hfSettings.sourceModel = result->operator[]("source_model").as<std::string>();
710+
}
699711
// Ovms Pull models mode || pull and start models mode
700-
if (isHFPullOrPullAndStart(this->result)) {
701-
if (result->count("pull")) {
712+
if (isHFPullOrPullAndStart(this->result) || isInMemoryGraphMode(this->result)) {
713+
if (isInMemoryGraphMode(this->result)) {
714+
serverSettings.serverMode = IN_MEMORY_GRAPH_MODE;
715+
} else if (result->count("pull")) {
702716
serverSettings.serverMode = HF_PULL_MODE;
703717
} else {
704718
serverSettings.serverMode = HF_PULL_AND_START_MODE;
@@ -711,8 +725,11 @@ void CLIParser::prepareGraph(ServerSettingsImpl& serverSettings, HFSettingsImpl&
711725
hfSettings.overwriteModels = result->operator[]("overwrite_models").as<bool>();
712726
}
713727
if (result->count("source_model")) {
728+
// Already set above, but keep the original flow for downloadType logic
714729
hfSettings.sourceModel = result->operator[]("source_model").as<std::string>();
715-
} else if (result->count("model_name")) {
730+
} else if (result->count("model_name") && !result->count("model_path")) {
731+
// Only use model_name as source_model when model_path is not set
732+
// (when model_path is set, user wants to use local model without HF pull)
716733
hfSettings.sourceModel = result->operator[]("model_name").as<std::string>();
717734
}
718735
if ((result->count("weight-format") || result->count("extra_quantization_params")) && isOptimumCliDownload(hfSettings.sourceModel, hfSettings.ggufFilename)) {
@@ -732,6 +749,11 @@ void CLIParser::prepareGraph(ServerSettingsImpl& serverSettings, HFSettingsImpl&
732749
if (result->count("vocoder"))
733750
hfSettings.exportSettings.vocoder = result->operator[]("vocoder").as<std::string>();
734751
hfSettings.downloadPath = result->operator[]("model_repository_path").as<std::string>();
752+
// When --task is used with --model_path but without --pull/--source_model,
753+
// use model_path as the model location (no HF download needed)
754+
if (!result->count("pull") && !result->count("source_model") && result->count("model_path")) {
755+
hfSettings.exportSettings.modelPath = result->operator[]("model_path").as<std::string>();
756+
}
735757
if (result->count("task")) {
736758
hfSettings.task = stringToEnum(result->operator[]("task").as<std::string>());
737759
switch (hfSettings.task) {
@@ -798,7 +820,8 @@ void CLIParser::prepareGraph(ServerSettingsImpl& serverSettings, HFSettingsImpl&
798820
if (!serverSettings.cacheDir.empty()) {
799821
hfSettings.exportSettings.pluginConfig.cacheDir = serverSettings.cacheDir;
800822
}
801-
// No pull nor pull and start mode
823+
824+
// No pull nor pull and start mode and no start with local model_path
802825
} else {
803826
if (result->count("weight-format")) {
804827
throw std::logic_error("--weight-format parameter unsupported for Openvino huggingface organization models.");
@@ -840,11 +863,14 @@ void CLIParser::prepareGraphStart(HFSettingsImpl& hfSettings, ModelsSettingsImpl
840863
// Model settings
841864
if (result->count("model_name")) {
842865
modelsSettings.modelName = result->operator[]("model_name").as<std::string>();
843-
} else {
866+
} else if (!hfSettings.sourceModel.empty()) {
844867
modelsSettings.modelName = hfSettings.sourceModel;
845868
}
846869

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

850876
void CLIParser::prepare(ServerSettingsImpl* serverSettings, ModelsSettingsImpl* modelsSettings) {

src/cli_parser.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class CLIParser {
5151
void prepareGraphStart(HFSettingsImpl& hfSettings, ModelsSettingsImpl& modelsSettings);
5252
void prepareConfigExport(ModelsSettingsImpl& modelsSettings);
5353
bool isHFPullOrPullAndStart(const std::unique_ptr<cxxopts::ParseResult>& result);
54+
bool isInMemoryGraphMode(const std::unique_ptr<cxxopts::ParseResult>& result);
5455
};
5556

5657
} // namespace ovms

src/config.cpp

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -148,18 +148,22 @@ bool Config::validateUserSettingsInConfigAddRemoveModel(const ModelsSettingsImpl
148148
}
149149

150150
bool Config::validate() {
151-
if (this->serverSettings.serverMode == HF_PULL_MODE || this->serverSettings.serverMode == HF_PULL_AND_START_MODE) {
152-
if (!serverSettings.hfSettings.sourceModel.size()) {
153-
std::cerr << "source_model parameter is required for pull mode";
154-
return false;
155-
}
156-
if (!serverSettings.hfSettings.downloadPath.size()) {
157-
std::cerr << "model_repository_path parameter is required for pull mode";
158-
return false;
159-
}
160-
if (this->serverSettings.hfSettings.task == UNKNOWN_GRAPH) {
161-
std::cerr << "Error: --task parameter not set." << std::endl;
162-
return false;
151+
if (!this->serverSettings.hfSettings.sourceModel.empty() && this->serverSettings.hfSettings.task == UNKNOWN_GRAPH) {
152+
std::cerr << "--source_model should be used combined with --task" << std::endl;
153+
return false;
154+
}
155+
if (this->serverSettings.serverMode == HF_PULL_MODE || this->serverSettings.serverMode == HF_PULL_AND_START_MODE || this->serverSettings.serverMode == IN_MEMORY_GRAPH_MODE) {
156+
// When --task is used with --model_path (no HF pulling), sourceModel and downloadPath are not required
157+
bool taskWithModelPath = this->serverSettings.serverMode == IN_MEMORY_GRAPH_MODE && !this->modelsSettings.modelPath.empty();
158+
if (!taskWithModelPath) {
159+
if (!serverSettings.hfSettings.sourceModel.size()) {
160+
std::cerr << "source_model parameter is required for pull mode";
161+
return false;
162+
}
163+
if (!serverSettings.hfSettings.downloadPath.size()) {
164+
std::cerr << "model_repository_path parameter is required for pull mode";
165+
return false;
166+
}
163167
}
164168
if (this->serverSettings.hfSettings.task == TEXT_GENERATION_GRAPH) {
165169
if (!std::holds_alternative<TextGenGraphSettingsImpl>(this->serverSettings.hfSettings.graphSettings)) {

src/graph_export/graph_export.cpp

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,20 @@
5252
#endif
5353
namespace ovms {
5454

55+
static std::string inMemoryGraphContent;
56+
57+
bool GraphExport::hasInMemoryGraphContent() {
58+
return !inMemoryGraphContent.empty();
59+
}
60+
61+
const std::string& GraphExport::getInMemoryGraphContent() {
62+
return inMemoryGraphContent;
63+
}
64+
65+
void GraphExport::clearInMemoryGraphContent() {
66+
inMemoryGraphContent.clear();
67+
}
68+
5569
static const std::string OVMS_VERSION_GRAPH_LINE = std::string("# File created with: ") + PROJECT_NAME + std::string(" ") + PROJECT_VERSION + std::string("\n");
5670

5771
static std::string constructModelsPath(const std::string& modelPath, const std::optional<std::string>& ggufFilenameOpt) {
@@ -91,22 +105,26 @@ std::string GraphExport::getDraftModelDirectoryPath(const std::string& directory
91105
} \
92106
auto pluginConfigOpt = std::get<std::optional<std::string>>(pluginConfigOrStatus)
93107

94-
static Status createPbtxtFile(const std::string& directoryPath, const std::string& pbtxtContent) {
108+
static Status createPbtxtFile(const std::string& directoryPath, const std::string& pbtxtContent, bool writeToFile) {
95109
#if (MEDIAPIPE_DISABLE == 0)
96110
::mediapipe::CalculatorGraphConfig config;
97-
SPDLOG_TRACE("Generated pbtxt: {}", pbtxtContent);
111+
SPDLOG_TRACE("Created graph config file:\n{}", pbtxtContent);
98112
bool success = ::google::protobuf::TextFormat::ParseFromString(pbtxtContent, &config);
99113
if (!success) {
100114
SPDLOG_ERROR("Created graph config file couldn't be parsed - check used task parameters values.");
101115
return StatusCode::MEDIAPIPE_GRAPH_CONFIG_FILE_INVALID;
102116
}
103117
#endif
118+
if (!writeToFile) {
119+
inMemoryGraphContent = pbtxtContent;
120+
return StatusCode::OK;
121+
}
104122
// clang-format on
105123
std::string fullPath = FileSystem::joinPath({directoryPath, "graph.pbtxt"});
106124
return FileSystem::createFileOverwrite(fullPath, pbtxtContent);
107125
}
108126

109-
static Status createTextGenerationGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings) {
127+
static Status createTextGenerationGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings, bool writeToFile) {
110128
if (!std::holds_alternative<TextGenGraphSettingsImpl>(hfSettings.graphSettings)) {
111129
SPDLOG_ERROR("Graph options not initialized for text generation.");
112130
return StatusCode::INTERNAL_ERROR;
@@ -198,10 +216,10 @@ static Status createTextGenerationGraphTemplate(const std::string& directoryPath
198216
}
199217
}
200218
})";
201-
return createPbtxtFile(directoryPath, oss.str());
219+
return createPbtxtFile(directoryPath, oss.str(), writeToFile);
202220
}
203221

204-
static Status createRerankGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings) {
222+
static Status createRerankGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings, bool writeToFile) {
205223
if (!std::holds_alternative<RerankGraphSettingsImpl>(hfSettings.graphSettings)) {
206224
SPDLOG_ERROR("Graph options not initialized for reranking.");
207225
return StatusCode::INTERNAL_ERROR;
@@ -242,10 +260,10 @@ node {
242260
}
243261
}
244262
})";
245-
return createPbtxtFile(directoryPath, oss.str());
263+
return createPbtxtFile(directoryPath, oss.str(), writeToFile);
246264
}
247265

248-
static Status createEmbeddingsGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings) {
266+
static Status createEmbeddingsGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings, bool writeToFile) {
249267
if (!std::holds_alternative<EmbeddingsGraphSettingsImpl>(hfSettings.graphSettings)) {
250268
SPDLOG_ERROR("Graph options not initialized for embeddings.");
251269
return StatusCode::INTERNAL_ERROR;
@@ -289,10 +307,10 @@ node {
289307
oss << R"(}
290308
}
291309
})";
292-
return createPbtxtFile(directoryPath, oss.str());
310+
return createPbtxtFile(directoryPath, oss.str(), writeToFile);
293311
}
294312

295-
static Status createTextToSpeechGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings) {
313+
static Status createTextToSpeechGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings, bool writeToFile) {
296314
if (!std::holds_alternative<TextToSpeechGraphSettingsImpl>(hfSettings.graphSettings)) {
297315
SPDLOG_ERROR("Graph options not initialized for speech generation.");
298316
return StatusCode::INTERNAL_ERROR;
@@ -339,11 +357,15 @@ node {
339357
}
340358
#endif
341359
// clang-format on
360+
if (!writeToFile) {
361+
inMemoryGraphContent = oss.str();
362+
return StatusCode::OK;
363+
}
342364
std::string fullPath = FileSystem::joinPath({directoryPath, "graph.pbtxt"});
343365
return FileSystem::createFileOverwrite(fullPath, oss.str());
344366
}
345367

346-
static Status createSpeechToTextGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings) {
368+
static Status createSpeechToTextGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings, bool writeToFile) {
347369
if (!std::holds_alternative<SpeechToTextGraphSettingsImpl>(hfSettings.graphSettings)) {
348370
SPDLOG_ERROR("Graph options not initialized for speech to text.");
349371
return StatusCode::INTERNAL_ERROR;
@@ -405,11 +427,15 @@ node {
405427
}
406428
#endif
407429
// clang-format on
430+
if (!writeToFile) {
431+
inMemoryGraphContent = oss.str();
432+
return StatusCode::OK;
433+
}
408434
std::string fullPath = FileSystem::joinPath({directoryPath, "graph.pbtxt"});
409435
return FileSystem::createFileOverwrite(fullPath, oss.str());
410436
}
411437

412-
static Status createImageGenerationGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings) {
438+
static Status createImageGenerationGraphTemplate(const std::string& directoryPath, const HFSettingsImpl& hfSettings, bool writeToFile) {
413439
if (!std::holds_alternative<ImageGenerationGraphSettingsImpl>(hfSettings.graphSettings)) {
414440
SPDLOG_ERROR("Graph options not initialized for image generation.");
415441
return StatusCode::INTERNAL_ERROR;
@@ -489,13 +515,13 @@ node: {
489515
}
490516
)";
491517
// clang-format on
492-
return createPbtxtFile(directoryPath, oss.str());
518+
return createPbtxtFile(directoryPath, oss.str(), writeToFile);
493519
}
494520

495521
GraphExport::GraphExport() {
496522
}
497523

498-
Status GraphExport::createServableConfig(const std::string& directoryPath, const HFSettingsImpl& hfSettings) {
524+
Status GraphExport::createServableConfig(const std::string& directoryPath, const HFSettingsImpl& hfSettings, bool writeToFile) {
499525
if (directoryPath.empty()) {
500526
SPDLOG_ERROR("Directory path empty: {}", directoryPath);
501527
return StatusCode::PATH_INVALID;
@@ -518,17 +544,17 @@ Status GraphExport::createServableConfig(const std::string& directoryPath, const
518544
}
519545
}
520546
if (hfSettings.task == TEXT_GENERATION_GRAPH) {
521-
return createTextGenerationGraphTemplate(directoryPath, hfSettings);
547+
return createTextGenerationGraphTemplate(directoryPath, hfSettings, writeToFile);
522548
} else if (hfSettings.task == EMBEDDINGS_GRAPH) {
523-
return createEmbeddingsGraphTemplate(directoryPath, hfSettings);
549+
return createEmbeddingsGraphTemplate(directoryPath, hfSettings, writeToFile);
524550
} else if (hfSettings.task == RERANK_GRAPH) {
525-
return createRerankGraphTemplate(directoryPath, hfSettings);
551+
return createRerankGraphTemplate(directoryPath, hfSettings, writeToFile);
526552
} else if (hfSettings.task == IMAGE_GENERATION_GRAPH) {
527-
return createImageGenerationGraphTemplate(directoryPath, hfSettings);
553+
return createImageGenerationGraphTemplate(directoryPath, hfSettings, writeToFile);
528554
} else if (hfSettings.task == TEXT_TO_SPEECH_GRAPH) {
529-
return createTextToSpeechGraphTemplate(directoryPath, hfSettings);
555+
return createTextToSpeechGraphTemplate(directoryPath, hfSettings, writeToFile);
530556
} else if (hfSettings.task == SPEECH_TO_TEXT_GRAPH) {
531-
return createSpeechToTextGraphTemplate(directoryPath, hfSettings);
557+
return createSpeechToTextGraphTemplate(directoryPath, hfSettings, writeToFile);
532558
} else if (hfSettings.task == UNKNOWN_GRAPH) {
533559
SPDLOG_ERROR("Graph options not initialized.");
534560
return StatusCode::INTERNAL_ERROR;

src/graph_export/graph_export.hpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,13 @@ class Status;
2727
class GraphExport {
2828
public:
2929
GraphExport();
30-
Status createServableConfig(const std::string& directoryPath, const HFSettingsImpl& graphSettings);
30+
Status createServableConfig(const std::string& directoryPath, const HFSettingsImpl& graphSettings, bool writeToFile = true);
3131
static std::variant<std::optional<std::string>, Status> createPluginString(const ExportSettings& exportSettings);
3232
static std::string getDraftModelDirectoryName(std::string draftModel);
3333
static std::string getDraftModelDirectoryPath(const std::string& directoryPath, const std::string& draftModel);
34+
35+
static bool hasInMemoryGraphContent();
36+
static const std::string& getInMemoryGraphContent();
37+
static void clearInMemoryGraphContent();
3438
};
3539
} // namespace ovms

src/mediapipe_internal/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ ovms_cc_library(
7979
"//src:libovms_servable_name_checker",
8080
"//src/metrics:libovms_metric_provider",
8181
"//src/filesystem:libovmsfilesystem",
82+
"//src/graph_export:graph_export",
8283
"//src:libovms_version",
8384
"//src:libovms_execution_context",
8485
"//src:libovmstimer",

src/mediapipe_internal/mediapipegraphconfig.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <spdlog/spdlog.h>
2626

2727
#include "src/filesystem/filesystem.hpp"
28+
#include "src/graph_export/graph_export.hpp"
2829
#include "../status.hpp"
2930

3031
namespace ovms {
@@ -129,6 +130,10 @@ Status MediapipeGraphConfig::parseNode(const rapidjson::Value& v) {
129130
}
130131

131132
void MediapipeGraphConfig::logGraphConfigContent() const {
133+
if (GraphExport::hasInMemoryGraphContent()) {
134+
SPDLOG_DEBUG("Content of in-memory graph config:\n{}", GraphExport::getInMemoryGraphContent());
135+
return;
136+
}
132137
std::ifstream fileStream(this->graphPath);
133138
if (!fileStream.is_open()) {
134139
SPDLOG_ERROR("Failed to open file: {}", this->graphPath);

0 commit comments

Comments
 (0)