From af7917c45a182572bb90a734542d091c71facd00 Mon Sep 17 00:00:00 2001 From: Roland Tannous Date: Wed, 27 May 2026 12:44:44 +0400 Subject: [PATCH] Studio: don't kill chat-model llama-server when spawning helper backends --- studio/backend/core/inference/llama_cpp.py | 17 +++++++++++++++-- studio/backend/core/rag/captioner.py | 7 ++++++- studio/backend/utils/datasets/llm_assist.py | 4 +++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/studio/backend/core/inference/llama_cpp.py b/studio/backend/core/inference/llama_cpp.py index 0531dd0e93..b8d168ccc5 100644 --- a/studio/backend/core/inference/llama_cpp.py +++ b/studio/backend/core/inference/llama_cpp.py @@ -608,7 +608,19 @@ class LlamaCppBackend: 3. unload_model() — terminates llama-server subprocess """ - def __init__(self): + def __init__(self, kill_orphans: bool = True): + """Construct a backend wrapper around llama-server. + + ``kill_orphans`` (default True): at construction time, reap any + llama-server processes lingering from a prior studio crash. Safe + for the global singleton (only one LlamaCppBackend exists at + startup). Pass ``False`` for short-lived secondary instances + spawned alongside an already-running chat-model server (e.g. + the RAG captioner helper, `_run_with_helper`) — otherwise the + constructor will kill the parent's healthy chat model because + it can't distinguish "another instance's healthy server" from + "a stale process". + """ self._process: Optional[subprocess.Popen] = None self._port: Optional[int] = None self._model_identifier: Optional[str] = None @@ -691,7 +703,8 @@ class LlamaCppBackend: # to decide whether to wait for the VRAM reclaim to finish. self._last_kill_monotonic: float = 0.0 - self._kill_orphaned_servers() + if kill_orphans: + self._kill_orphaned_servers() atexit.register(self._cleanup) # ── Properties ──────────────────────────────────────────────── diff --git a/studio/backend/core/rag/captioner.py b/studio/backend/core/rag/captioner.py index cc34e7c9a7..561dd5e659 100644 --- a/studio/backend/core/rag/captioner.py +++ b/studio/backend/core/rag/captioner.py @@ -74,7 +74,12 @@ def _load_helper_vlm() -> Optional[tuple[Any, str, str]]: try: from core.inference.llama_cpp import LlamaCppBackend - backend = LlamaCppBackend() + # kill_orphans=False is critical: the global singleton is + # already running the user's chat-model llama-server. Killing + # "orphans" here would reap that healthy chat process because + # the orphan-killer can't tell two LlamaCppBackend instances + # apart by PID ownership. + backend = LlamaCppBackend(kill_orphans = False) logger.info( "RAG captioner: loading helper VLM %s (%s) as fallback", _HELPER_REPO, diff --git a/studio/backend/utils/datasets/llm_assist.py b/studio/backend/utils/datasets/llm_assist.py index a36a121c5d..004dd5b354 100644 --- a/studio/backend/utils/datasets/llm_assist.py +++ b/studio/backend/utils/datasets/llm_assist.py @@ -155,7 +155,9 @@ def _run_with_helper(prompt: str, max_tokens: int = 256) -> Optional[str]: try: from core.inference.llama_cpp import LlamaCppBackend - backend = LlamaCppBackend() + # kill_orphans=False so the helper backend doesn't reap the + # parent's chat-model llama-server while loading itself. + backend = LlamaCppBackend(kill_orphans = False) logger.info(f"Loading helper model: {repo} ({variant})") ok = backend.load_model(