From e426575e23f11abc4f6b1e214fc9dc2503026769 Mon Sep 17 00:00:00 2001 From: Tai An Date: Fri, 24 Jul 2026 21:08:56 -0700 Subject: [PATCH] fix(model): match inline-load requests by resolved path so symlink aliases don't reload An inline model request identifies the loaded model by comparing the requested name against container.model_dir.name. Because the container is created from model_path.resolve(), model_dir.name is always the real directory name. When a model is addressed through a symlink alias (e.g. a 'coder' symlink to 'ArtusDev_Qwen_...-EXL3'), the names never match, so the already-loaded model is needlessly unloaded and reloaded on every request. Compare resolved paths instead of bare directory names in both the inline fast-path guard and load_model_gen, so a symlink that points at the loaded model is recognized as the same model. Fixes #379 --- common/model.py | 9 +++++++-- endpoints/OAI/utils/common_.py | 12 ++++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/common/model.py b/common/model.py index 871a52a7..91082b00 100644 --- a/common/model.py +++ b/common/model.py @@ -145,7 +145,12 @@ async def load_model_gen(model_path: pathlib.Path, **kwargs): if container and container.model: loaded_model_name = container.model_dir.name - if loaded_model_name == model_path.name and container.loaded: + # Compare resolved paths, not just directory names, so a symlink + # alias pointing at the already-loaded model is recognized as the + # same model instead of triggering a spurious unload/reload (#379). + already_loaded = container.model_dir.resolve() == model_path.resolve() + + if already_loaded and container.loaded: xlogger.info(f'Model "{loaded_model_name}" is already loaded') # Emit a terminal progress event so API clients always @@ -320,4 +325,4 @@ def check_context_length( container.validate_context_length(prompt, params, mm_embeddings) except ContextLengthExceededError as exc: error_message = handle_request_error(str(exc), exc_info=False).error.message - raise ContextLengthHTTPException(error_message) from exc + raise ContextLengthHTTPException(error_message) from exc \ No newline at end of file diff --git a/endpoints/OAI/utils/common_.py b/endpoints/OAI/utils/common_.py index 3a68ea8c..46710099 100644 --- a/endpoints/OAI/utils/common_.py +++ b/endpoints/OAI/utils/common_.py @@ -62,9 +62,13 @@ def aggregate_usage_stats(usage_stats_list: list[UsageStats]) -> UsageStats: async def load_inline_model(model_name: str, request: Request): """Load a model from the data.model parameter""" - # Return if the model container already exists and the model is fully loaded - if model.container and model.container.model_dir.name == model_name and model.container.loaded: - return + # Return if the model container already exists and the model is fully loaded. + # Compare resolved paths so a symlink alias for the loaded model is treated + # as already loaded instead of triggering a spurious reload (#379). + if model.container and model.container.loaded: + requested_dir = pathlib.Path(config.model.model_dir) / model_name + if model.container.model_dir.resolve() == requested_dir.resolve(): + return # Return if inline loading is disabled # Also warn if an admin key is used @@ -113,4 +117,4 @@ async def load_inline_model(model_name: str, request: Request): await model.load_model( model_path, draft_model=config.draft_model.model_dump(include={"draft_model_dir"}), - ) + ) \ No newline at end of file