From c45573c49fba98ff8e8a16fc95b3fb5be9e38445 Mon Sep 17 00:00:00 2001 From: exzile Date: Fri, 26 Jun 2026 14:15:38 -0400 Subject: [PATCH 1/3] Propagate global --cache_dir to continuous batching pipeline The continuous batching servable initializer constructs the GenAI ContinuousBatchingPipeline directly and never applied the server-level --cache_dir (ServerSettings.cacheDir). Unlike the non-CB path, which applies it via ModelInstance::setCacheOptions, the CB path left model compilation caching disabled unless the user duplicated the value into the node's plugin_config as CACHE_DIR. As a result, .blob/.cl_cache artifacts were never persisted and every restart fully recompiled the model. Inject the global cache_dir into the pipeline plugin config before constructing the pipeline. An explicit CACHE_DIR in the node's plugin_config remains authoritative. Adds a regression test (LLMNodeOptionsCacheDirPropagation) covering both propagation of the global value and precedence of an explicit node value. Fixes #4230 Co-Authored-By: Claude Opus 4.8 --- docs/model_cache.md | 2 + .../servable_initializer.cpp | 15 +++ src/test/llm/llmnode_test.cpp | 120 ++++++++++++++++++ 3 files changed, 137 insertions(+) diff --git a/docs/model_cache.md b/docs/model_cache.md index 46373ae113..2174ca7f26 100644 --- a/docs/model_cache.md +++ b/docs/model_cache.md @@ -23,6 +23,8 @@ Alternatively the location of the cache storage can be set using the parameter ` The model server security context must have read-write access to the cache storage path. +`--cache_dir` also applies to LLM text-generation servables using the continuous batching pipeline (GPU). With it set, the compiled-model/blob cache is persisted across restarts, so a model that has already been compiled (or idle-unloaded) reloads from the cache instead of recompiling. An explicit `CACHE_DIR` in a node's `plugin_config` takes precedence over the global `--cache_dir`. + When using Model Server with configuration file, it is possible to serve more than one model. In such case, model cache is applied to all the models, with an exception to: - Models with custom loader (for security reasons explained earlier) - Models configured to shape `auto` or batch_size `auto` diff --git a/src/llm/language_model/continuous_batching/servable_initializer.cpp b/src/llm/language_model/continuous_batching/servable_initializer.cpp index 1aaff99844..c59c94bf71 100644 --- a/src/llm/language_model/continuous_batching/servable_initializer.cpp +++ b/src/llm/language_model/continuous_batching/servable_initializer.cpp @@ -227,6 +227,21 @@ Status ContinuousBatchingServableInitializer::initialize(std::shared_ptrpluginConfig.find(ov::cache_dir.name()) == properties->pluginConfig.end()) { + properties->pluginConfig[ov::cache_dir.name()] = globalCacheDir; + SPDLOG_DEBUG("Applying global cache_dir to continuous batching pipeline: {}", globalCacheDir); + } else { + SPDLOG_DEBUG("CACHE_DIR set explicitly in node plugin_config; keeping user value over global cache_dir"); + } + } + if (properties->device == "CPU") { status = applyDefaultCpuProperties(properties->pluginConfig); if (!status.ok()) { diff --git a/src/test/llm/llmnode_test.cpp b/src/test/llm/llmnode_test.cpp index e13cf29919..5f8b764e84 100644 --- a/src/test/llm/llmnode_test.cpp +++ b/src/test/llm/llmnode_test.cpp @@ -37,6 +37,7 @@ #endif #include "../../http_rest_api_handler.hpp" +#include "../../config.hpp" #include "../../http_status_code.hpp" #include "../../json_parser.hpp" #include "../../llm/apis/openai_completions.hpp" @@ -4425,6 +4426,125 @@ TEST_F(LLMVLMOptionsHttpTest, LLMVLMNodeOptionsCheckPluginConfig) { LLMNodeOptionsCheckPluginConfig(modelsPath); } +// Verifies that the global --cache_dir (ServerSettings) is propagated into the +// continuous batching pipeline plugin config, and that an explicit CACHE_DIR in +// the node's plugin_config takes precedence over the global value. +// Regression test for openvinotoolkit/model_server#4230. +void LLMNodeOptionsCacheDirPropagation(std::string& modelsPath) { + // Seed the global cache_dir via the CLI parser (same path used in production). + char* n_argv[] = {(char*)"ovms", (char*)"--model_path", (char*)"/path/to/model", (char*)"--model_name", (char*)"some_name", (char*)"--rest_port", (char*)"8080", (char*)"--cache_dir", (char*)"/tmp/ovms_global_cache"}; + int arg_count = 9; + ovms::Config::instance().parse(arg_count, n_argv); + ASSERT_EQ(ovms::Config::instance().cacheDir(), "/tmp/ovms_global_cache"); + + // Case 1: no CACHE_DIR in node plugin_config -> global value is applied. + { + std::string testPbtxt = R"( + input_stream: "HTTP_REQUEST_PAYLOAD:input" + output_stream: "HTTP_RESPONSE_PAYLOAD:output" + + node: { + name: "llmNode" + calculator: "HttpLLMCalculator" + input_stream: "LOOPBACK:loopback" + input_stream: "HTTP_REQUEST_PAYLOAD:input" + input_side_packet: "LLM_NODE_RESOURCES:llm" + output_stream: "LOOPBACK:loopback" + output_stream: "HTTP_RESPONSE_PAYLOAD:output" + input_stream_info: { + tag_index: 'LOOPBACK:0', + back_edge: true + } + node_options: { + [type.googleapis.com / mediapipe.LLMCalculatorOptions]: { + models_path: ")" + + modelsPath + R"(" + } + } + input_stream_handler { + input_stream_handler: "SyncSetInputStreamHandler", + options { + [mediapipe.SyncSetInputStreamHandlerOptions.ext] { + sync_set { + tag_index: "LOOPBACK:0" + } + } + } + } + } + )"; + adjustConfigForTargetPlatform(testPbtxt); + ::mediapipe::CalculatorGraphConfig config; + ASSERT_TRUE(::google::protobuf::TextFormat::ParseFromString(testPbtxt, &config)); + std::shared_ptr servable; + ASSERT_EQ(initializeGenAiServable(servable, config.node(0), ""), StatusCode::OK); + auto properties = std::static_pointer_cast(servable->getProperties()); + ASSERT_EQ(properties->pluginConfig.count("CACHE_DIR"), 1); + ASSERT_EQ(properties->pluginConfig["CACHE_DIR"].as(), "/tmp/ovms_global_cache"); + } + + // Case 2: explicit CACHE_DIR in node plugin_config wins over the global value. + { + std::string testPbtxt = R"( + input_stream: "HTTP_REQUEST_PAYLOAD:input" + output_stream: "HTTP_RESPONSE_PAYLOAD:output" + + node: { + name: "llmNode" + calculator: "HttpLLMCalculator" + input_stream: "LOOPBACK:loopback" + input_stream: "HTTP_REQUEST_PAYLOAD:input" + input_side_packet: "LLM_NODE_RESOURCES:llm" + output_stream: "LOOPBACK:loopback" + output_stream: "HTTP_RESPONSE_PAYLOAD:output" + input_stream_info: { + tag_index: 'LOOPBACK:0', + back_edge: true + } + node_options: { + [type.googleapis.com / mediapipe.LLMCalculatorOptions]: { + models_path: ")" + + modelsPath + R"(" + plugin_config: '{"CACHE_DIR": "/tmp/ovms_node_cache"}' + } + } + input_stream_handler { + input_stream_handler: "SyncSetInputStreamHandler", + options { + [mediapipe.SyncSetInputStreamHandlerOptions.ext] { + sync_set { + tag_index: "LOOPBACK:0" + } + } + } + } + } + )"; + adjustConfigForTargetPlatform(testPbtxt); + ::mediapipe::CalculatorGraphConfig config; + ASSERT_TRUE(::google::protobuf::TextFormat::ParseFromString(testPbtxt, &config)); + std::shared_ptr servable; + ASSERT_EQ(initializeGenAiServable(servable, config.node(0), ""), StatusCode::OK); + auto properties = std::static_pointer_cast(servable->getProperties()); + ASSERT_EQ(properties->pluginConfig.count("CACHE_DIR"), 1); + // The test harness may rewrite the path for the target platform, so match + // on substrings: the explicit node value must win over the global one. + std::string nodeCacheDir = properties->pluginConfig["CACHE_DIR"].as(); + ASSERT_NE(nodeCacheDir.find("ovms_node_cache"), std::string::npos) << "Explicit node CACHE_DIR should be used, got: " << nodeCacheDir; + ASSERT_EQ(nodeCacheDir.find("ovms_global_cache"), std::string::npos) << "Global cache_dir must not override explicit node CACHE_DIR, got: " << nodeCacheDir; + } + + // Restore the global cache_dir so the singleton does not leak into other tests. + char* reset_argv[] = {(char*)"ovms", (char*)"--model_path", (char*)"/path/to/model", (char*)"--model_name", (char*)"some_name", (char*)"--rest_port", (char*)"8080"}; + ovms::Config::instance().parse(7, reset_argv); +} +TEST_F(LLMOptionsHttpTest, LLMNodeOptionsCacheDirPropagation) { + LLMNodeOptionsCacheDirPropagation(modelsPath); +} +TEST_F(LLMVLMOptionsHttpTest, LLMVLMNodeOptionsCacheDirPropagation) { + LLMNodeOptionsCacheDirPropagation(modelsPath); +} + void LLMNodeOptionsCheckNonDefault(std::string& modelsPath) { std::string testPbtxt = R"( input_stream: "HTTP_REQUEST_PAYLOAD:input" From 70299c03a8e02045f28bcbdd13cec1a941dc4399 Mon Sep 17 00:00:00 2001 From: exzile Date: Fri, 3 Jul 2026 12:45:02 -0400 Subject: [PATCH 2/3] Add end-to-end cache_dir regression test verifying real cache artifacts LLMNodeOptionsCacheDirPropagation only asserts that --cache_dir lands in properties->pluginConfig, which doesn't prove OpenVINO Core actually persists compiled-model cache artifacts -- the actual symptom in #4230 (log said "cache enabled", nothing was ever written to disk). Addresses feedback on the issue: https://github.com/openvinotoolkit/model_server/issues/4230#issuecomment-4874816871 LLMNodeOptionsCacheDirWritesCacheArtifacts constructs a real ContinuousBatchingPipeline against a temp --cache_dir and asserts at least one cache file actually lands there. Verified locally on Windows (MSVC) against facebook/opt-125m: [ OK ] LLMOptionsHttpTest.LLMNodeOptionsCacheDirWritesCacheArtifacts (707 ms) --- src/test/llm/llmnode_test.cpp | 76 +++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/src/test/llm/llmnode_test.cpp b/src/test/llm/llmnode_test.cpp index 5f8b764e84..4fdaae1edf 100644 --- a/src/test/llm/llmnode_test.cpp +++ b/src/test/llm/llmnode_test.cpp @@ -4545,6 +4545,82 @@ TEST_F(LLMVLMOptionsHttpTest, LLMVLMNodeOptionsCacheDirPropagation) { LLMNodeOptionsCacheDirPropagation(modelsPath); } +// End-to-end regression test for #4230: LLMNodeOptionsCacheDirPropagation above only +// verifies that --cache_dir lands in properties->pluginConfig; it does not prove that +// OpenVINO Core actually persists compiled-model cache artifacts, which was the crux of +// the original bug report (log said "cache enabled", nothing was ever written on disk). +// This test constructs a real ContinuousBatchingPipeline against --cache_dir and asserts +// that compiled-model cache artifacts actually land under it. +TEST_F(LLMOptionsHttpTest, LLMNodeOptionsCacheDirWritesCacheArtifacts) { + std::string cacheDir = std::filesystem::temp_directory_path().string() + + "/LLMNodeOptionsCacheDirWritesCacheArtifacts_" + + ::testing::UnitTest::GetInstance()->current_test_info()->name(); + std::filesystem::remove_all(cacheDir); + std::filesystem::create_directories(cacheDir); + + // Seed the global cache_dir via the CLI parser (same path used in production). + char* n_argv[] = {(char*)"ovms", (char*)"--model_path", (char*)"/path/to/model", (char*)"--model_name", (char*)"some_name", (char*)"--rest_port", (char*)"8080", (char*)"--cache_dir", (char*)cacheDir.c_str()}; + int arg_count = 9; + ovms::Config::instance().parse(arg_count, n_argv); + ASSERT_EQ(ovms::Config::instance().cacheDir(), cacheDir); + + std::string testPbtxt = R"( + input_stream: "HTTP_REQUEST_PAYLOAD:input" + output_stream: "HTTP_RESPONSE_PAYLOAD:output" + + node: { + name: "llmNode" + calculator: "HttpLLMCalculator" + input_stream: "LOOPBACK:loopback" + input_stream: "HTTP_REQUEST_PAYLOAD:input" + input_side_packet: "LLM_NODE_RESOURCES:llm" + output_stream: "LOOPBACK:loopback" + output_stream: "HTTP_RESPONSE_PAYLOAD:output" + input_stream_info: { + tag_index: 'LOOPBACK:0', + back_edge: true + } + node_options: { + [type.googleapis.com / mediapipe.LLMCalculatorOptions]: { + models_path: ")" + + modelsPath + R"(" + } + } + input_stream_handler { + input_stream_handler: "SyncSetInputStreamHandler", + options { + [mediapipe.SyncSetInputStreamHandlerOptions.ext] { + sync_set { + tag_index: "LOOPBACK:0" + } + } + } + } + } + )"; + adjustConfigForTargetPlatform(testPbtxt); + ::mediapipe::CalculatorGraphConfig config; + ASSERT_TRUE(::google::protobuf::TextFormat::ParseFromString(testPbtxt, &config)); + std::shared_ptr servable; + ASSERT_EQ(initializeGenAiServable(servable, config.node(0), ""), StatusCode::OK); + + bool foundCacheArtifact = false; + for (const auto& entry : std::filesystem::recursive_directory_iterator(cacheDir)) { + if (entry.is_regular_file()) { + foundCacheArtifact = true; + break; + } + } + EXPECT_TRUE(foundCacheArtifact) + << "Expected compiled-model cache artifacts under --cache_dir after constructing the " + << "continuous batching pipeline, found none in: " << cacheDir; + + // Restore the global cache_dir so the singleton does not leak into other tests. + char* reset_argv[] = {(char*)"ovms", (char*)"--model_path", (char*)"/path/to/model", (char*)"--model_name", (char*)"some_name", (char*)"--rest_port", (char*)"8080"}; + ovms::Config::instance().parse(7, reset_argv); + std::filesystem::remove_all(cacheDir); +} + void LLMNodeOptionsCheckNonDefault(std::string& modelsPath) { std::string testPbtxt = R"( input_stream: "HTTP_REQUEST_PAYLOAD:input" From a067dcffba3f1804cf2ea764fb8c2c14112d43db Mon Sep 17 00:00:00 2001 From: exzile Date: Thu, 9 Jul 2026 10:51:42 -0400 Subject: [PATCH 3/3] Address review: cover legacy pipelines and guard test env restore Extract the global --cache_dir propagation into a shared GenAiServableInitializer::applyGlobalCacheDir helper and call it from all GenAI initializers. The continuous batching path already applied it (and VLM_CB shares that same initializer), but the legacy LM and legacy VLM paths construct their pipelines directly and never applied the server-level cache_dir. Routing every path through one helper covers all four pipeline types (LM/VLM x CB/legacy) and removes the duplicated inline block from the CB initializer. In the cache_dir tests, replace the manual end-of-test Config restore with a GlobalCacheDirGuard RAII helper so a failed ASSERT mid-test can no longer leak the modified --cache_dir singleton into subsequent tests in the suite. The guard also removes the temporary cache directory on scope exit. Co-Authored-By: Claude Opus 4.8 --- .../servable_initializer.cpp | 15 +------- .../legacy/servable_initializer.cpp | 2 ++ src/llm/servable_initializer.cpp | 20 +++++++++++ src/llm/servable_initializer.hpp | 5 +++ .../legacy/servable_initializer.cpp | 2 ++ src/test/llm/llmnode_test.cpp | 34 ++++++++++++++----- 6 files changed, 55 insertions(+), 23 deletions(-) diff --git a/src/llm/language_model/continuous_batching/servable_initializer.cpp b/src/llm/language_model/continuous_batching/servable_initializer.cpp index c59c94bf71..1ec08e46f4 100644 --- a/src/llm/language_model/continuous_batching/servable_initializer.cpp +++ b/src/llm/language_model/continuous_batching/servable_initializer.cpp @@ -227,20 +227,7 @@ Status ContinuousBatchingServableInitializer::initialize(std::shared_ptrpluginConfig.find(ov::cache_dir.name()) == properties->pluginConfig.end()) { - properties->pluginConfig[ov::cache_dir.name()] = globalCacheDir; - SPDLOG_DEBUG("Applying global cache_dir to continuous batching pipeline: {}", globalCacheDir); - } else { - SPDLOG_DEBUG("CACHE_DIR set explicitly in node plugin_config; keeping user value over global cache_dir"); - } - } + applyGlobalCacheDir(properties); if (properties->device == "CPU") { status = applyDefaultCpuProperties(properties->pluginConfig); diff --git a/src/llm/language_model/legacy/servable_initializer.cpp b/src/llm/language_model/legacy/servable_initializer.cpp index 52d041f74d..89947a6fbf 100644 --- a/src/llm/language_model/legacy/servable_initializer.cpp +++ b/src/llm/language_model/legacy/servable_initializer.cpp @@ -98,6 +98,8 @@ Status LegacyServableInitializer::initialize(std::shared_ptr& ser return status; } + applyGlobalCacheDir(properties); + // Max prompt len is NPU specific property if (properties->device == "NPU") { auto it = properties->pluginConfig.find("MAX_PROMPT_LEN"); diff --git a/src/llm/servable_initializer.cpp b/src/llm/servable_initializer.cpp index e2bcac9134..51217a0846 100644 --- a/src/llm/servable_initializer.cpp +++ b/src/llm/servable_initializer.cpp @@ -25,6 +25,7 @@ #include +#include #include #include @@ -36,6 +37,7 @@ #pragma GCC diagnostic pop #pragma warning(pop) +#include "../config.hpp" #include "../logging.hpp" #include "../mediapipe_internal/mediapipe_utils.hpp" #include "../status.hpp" @@ -169,6 +171,24 @@ void GenAiServableInitializer::loadChatTemplate(std::shared_ptr properties) { + // Propagate the global --cache_dir (ServerSettings) into the pipeline plugin config. + // Unlike the non-CB ModelInstance path (ModelInstance::setCacheOptions), these GenAI + // initializers construct the pipeline directly, so the server-level cache_dir is + // otherwise never applied and compiled-model cache artifacts are never persisted. + // An explicit CACHE_DIR in the node's plugin_config remains authoritative. + const std::string& globalCacheDir = Config::instance().cacheDir(); + if (globalCacheDir.empty()) { + return; + } + if (properties->pluginConfig.find(ov::cache_dir.name()) == properties->pluginConfig.end()) { + properties->pluginConfig[ov::cache_dir.name()] = globalCacheDir; + SPDLOG_DEBUG("Applying global cache_dir to GenAI pipeline: {}", globalCacheDir); + } else { + SPDLOG_DEBUG("CACHE_DIR set explicitly in node plugin_config; keeping user value over global cache_dir"); + } +} + #if (PYTHON_DISABLE == 0) // Helper function for case-insensitive comparison of file extensions static bool hasGGUFExtension(const std::filesystem::path& path) { diff --git a/src/llm/servable_initializer.hpp b/src/llm/servable_initializer.hpp index d742db9c3e..2951d1e470 100644 --- a/src/llm/servable_initializer.hpp +++ b/src/llm/servable_initializer.hpp @@ -48,6 +48,11 @@ class GenAiServableInitializer { public: virtual ~GenAiServableInitializer() = default; static void loadChatTemplate(std::shared_ptr properties, const std::string& chatTemplateDirectory); + // Propagates the global --cache_dir (ServerSettings) into the pipeline plugin config + // when the node did not set an explicit CACHE_DIR. Shared by every GenAI initializer + // (continuous batching and legacy, LM and VLM) since they all construct GenAI pipelines + // directly and would otherwise never apply the server-level cache_dir. + static void applyGlobalCacheDir(std::shared_ptr properties); #if (PYTHON_DISABLE == 0) // Use Python Jinja module for template processing static void loadPyTemplateProcessor(std::shared_ptr properties, const ExtraGenerationInfo& extraGenInfo); diff --git a/src/llm/visual_language_model/legacy/servable_initializer.cpp b/src/llm/visual_language_model/legacy/servable_initializer.cpp index 3b3cb61923..fb7a80282d 100644 --- a/src/llm/visual_language_model/legacy/servable_initializer.cpp +++ b/src/llm/visual_language_model/legacy/servable_initializer.cpp @@ -97,6 +97,8 @@ Status VisualLanguageModelLegacyServableInitializer::initialize(std::shared_ptr< return status; } + applyGlobalCacheDir(properties); + try { properties->pipeline = std::make_shared(parsedModelsPath, properties->device, properties->pluginConfig); properties->tokenizer = properties->pipeline->get_tokenizer(); diff --git a/src/test/llm/llmnode_test.cpp b/src/test/llm/llmnode_test.cpp index 122bcec957..5f5348539f 100644 --- a/src/test/llm/llmnode_test.cpp +++ b/src/test/llm/llmnode_test.cpp @@ -4500,11 +4500,31 @@ TEST_F(LLMVLMOptionsHttpTest, LLMVLMNodeOptionsCheckPluginConfig) { LLMNodeOptionsCheckPluginConfig(modelsPath); } +// RAII guard that restores the global Config singleton (and optionally removes a temporary +// cache directory) on scope exit. The cache_dir tests below mutate the process-wide Config +// singleton; without this guard a failed ASSERT_* mid-test (which returns early) would leak +// the modified --cache_dir into subsequent tests in this suite. +struct GlobalCacheDirGuard { + std::string cacheDirToRemove; + explicit GlobalCacheDirGuard(std::string cacheDirToRemove = "") : + cacheDirToRemove(std::move(cacheDirToRemove)) {} + ~GlobalCacheDirGuard() { + char* reset_argv[] = {(char*)"ovms", (char*)"--model_path", (char*)"/path/to/model", (char*)"--model_name", (char*)"some_name", (char*)"--rest_port", (char*)"8080"}; + ovms::Config::instance().parse(7, reset_argv); + if (!cacheDirToRemove.empty()) { + std::error_code ec; + std::filesystem::remove_all(cacheDirToRemove, ec); + } + } +}; + // Verifies that the global --cache_dir (ServerSettings) is propagated into the // continuous batching pipeline plugin config, and that an explicit CACHE_DIR in // the node's plugin_config takes precedence over the global value. // Regression test for openvinotoolkit/model_server#4230. void LLMNodeOptionsCacheDirPropagation(std::string& modelsPath) { + // Restore the global cache_dir on scope exit even if an ASSERT below fails early. + GlobalCacheDirGuard cacheDirGuard; // Seed the global cache_dir via the CLI parser (same path used in production). char* n_argv[] = {(char*)"ovms", (char*)"--model_path", (char*)"/path/to/model", (char*)"--model_name", (char*)"some_name", (char*)"--rest_port", (char*)"8080", (char*)"--cache_dir", (char*)"/tmp/ovms_global_cache"}; int arg_count = 9; @@ -4607,10 +4627,7 @@ void LLMNodeOptionsCacheDirPropagation(std::string& modelsPath) { ASSERT_NE(nodeCacheDir.find("ovms_node_cache"), std::string::npos) << "Explicit node CACHE_DIR should be used, got: " << nodeCacheDir; ASSERT_EQ(nodeCacheDir.find("ovms_global_cache"), std::string::npos) << "Global cache_dir must not override explicit node CACHE_DIR, got: " << nodeCacheDir; } - - // Restore the global cache_dir so the singleton does not leak into other tests. - char* reset_argv[] = {(char*)"ovms", (char*)"--model_path", (char*)"/path/to/model", (char*)"--model_name", (char*)"some_name", (char*)"--rest_port", (char*)"8080"}; - ovms::Config::instance().parse(7, reset_argv); + // GlobalCacheDirGuard restores the global cache_dir on scope exit. } TEST_F(LLMOptionsHttpTest, LLMNodeOptionsCacheDirPropagation) { LLMNodeOptionsCacheDirPropagation(modelsPath); @@ -4631,6 +4648,9 @@ TEST_F(LLMOptionsHttpTest, LLMNodeOptionsCacheDirWritesCacheArtifacts) { ::testing::UnitTest::GetInstance()->current_test_info()->name(); std::filesystem::remove_all(cacheDir); std::filesystem::create_directories(cacheDir); + // Restore the global cache_dir and remove the temp cache dir on scope exit, even if an + // ASSERT below fails early. + GlobalCacheDirGuard cacheDirGuard(cacheDir); // Seed the global cache_dir via the CLI parser (same path used in production). char* n_argv[] = {(char*)"ovms", (char*)"--model_path", (char*)"/path/to/model", (char*)"--model_name", (char*)"some_name", (char*)"--rest_port", (char*)"8080", (char*)"--cache_dir", (char*)cacheDir.c_str()}; @@ -4688,11 +4708,7 @@ TEST_F(LLMOptionsHttpTest, LLMNodeOptionsCacheDirWritesCacheArtifacts) { EXPECT_TRUE(foundCacheArtifact) << "Expected compiled-model cache artifacts under --cache_dir after constructing the " << "continuous batching pipeline, found none in: " << cacheDir; - - // Restore the global cache_dir so the singleton does not leak into other tests. - char* reset_argv[] = {(char*)"ovms", (char*)"--model_path", (char*)"/path/to/model", (char*)"--model_name", (char*)"some_name", (char*)"--rest_port", (char*)"8080"}; - ovms::Config::instance().parse(7, reset_argv); - std::filesystem::remove_all(cacheDir); + // GlobalCacheDirGuard restores the global cache_dir and removes cacheDir on scope exit. } void LLMNodeOptionsCheckNonDefault(std::string& modelsPath) {