@@ -28,6 +28,41 @@ def _clear_llama_flags_cache() -> None:
2828HELP_TIMEOUT = 90
2929
3030
31+ def _help_subprocess_failure_message (
32+ returncode : int ,
33+ argv0 : str ,
34+ * ,
35+ empty_stdout : bool ,
36+ scan_engine : Optional [str ] = None ,
37+ ) -> str :
38+ """Human-readable scan failure (126/127 often mean exec/loader/shebang issues)."""
39+ exe = argv0 or "(unknown)"
40+ tail = " (no stdout from --help; output may be missing or only on stderr)" if empty_stdout else ""
41+ if returncode == 127 :
42+ head = (
43+ f"process exited with code 127{ tail } : the program could not be run (POSIX 127 — often "
44+ f"“not found” at exec or in a wrapper). Executable: { exe } . "
45+ )
46+ if scan_engine == "lmdeploy" :
47+ return head + (
48+ "For LMDeploy: `lmdeploy` is usually a script; fix the venv shebang Python or a stale `venv_path`."
49+ )
50+ if scan_engine in ("llama_cpp" , "ik_llama" ):
51+ return head + (
52+ "For llama.cpp / ik_llama: wrong arch or libc (e.g. glibc binary on musl), missing shared "
53+ "libraries (CUDA/GGML — `.so` search path), bad `binary_path`, or a wrapper with a broken "
54+ "shebang. Run `file` on the binary and the same `--help` in the API container; ensure "
55+ "`LD_LIBRARY_PATH` includes the directory with ggml/llama shared libs (often `build/bin` next to the build)."
56+ )
57+ return head + "Check shebang, dynamic linker, and PATH/LD_LIBRARY_PATH."
58+ if returncode == 126 :
59+ return (
60+ f"process exited with code 126{ tail } : cannot execute (permission denied or not a valid executable). "
61+ f"Executable: { exe } "
62+ )
63+ return f"process exited with code { returncode } { tail } "
64+
65+
3166def _abs_path (p : str ) -> str :
3267 if not p :
3368 return p
@@ -41,6 +76,7 @@ def _run_help_argv(
4176 * ,
4277 cwd : Optional [str ] = None ,
4378 extra_env : Optional [dict ] = None ,
79+ scan_engine : Optional [str ] = None ,
4480) -> Tuple [str , Optional [str ]]:
4581 env = os .environ .copy ()
4682 if extra_env :
@@ -56,11 +92,18 @@ def _run_help_argv(
5692 env = env ,
5793 )
5894 text = r .stdout or ""
95+ argv0 = argv [0 ] if argv else ""
5996 if not text .strip ():
97+ if r .returncode != 0 :
98+ return "" , _help_subprocess_failure_message (
99+ r .returncode , argv0 , empty_stdout = True , scan_engine = scan_engine
100+ )
60101 return "" , "empty help output"
61102 if r .returncode != 0 :
62103 # Caller may still parse stdout when --help printed despite non-zero exit.
63- return text , f"process exited with code { r .returncode } "
104+ return text , _help_subprocess_failure_message (
105+ r .returncode , argv0 , empty_stdout = False , scan_engine = scan_engine
106+ )
64107 return text , None
65108 except subprocess .TimeoutExpired :
66109 return "" , "timeout"
@@ -72,44 +115,45 @@ def _run_help_argv(
72115
73116def scan_llama_engine_version (engine : str , version_row : dict ) -> dict :
74117 """engine: llama_cpp | ik_llama"""
118+ from backend .llama_server_exec import (
119+ llama_help_ld_library_path ,
120+ resolve_llama_server_invocation_paths ,
121+ )
122+
75123 binary_path = version_row .get ("binary_path" )
76124 if not binary_path :
77125 return _error_entry ("" , "missing binary_path" )
78126 path = _abs_path (binary_path )
79- if not os .path .isfile (path ):
80- return _error_entry (path , f"binary not found: { path } " )
127+ exec_path , work_cwd = resolve_llama_server_invocation_paths (path )
128+ if not os .path .isfile (exec_path ):
129+ return _error_entry (exec_path , f"binary not found: { exec_path } " )
81130
82- binary_dir = os .path .dirname (path )
83- working_dir = binary_dir
84- if "/bin/" in binary_dir and "/build/bin/" not in binary_dir :
85- working_dir = binary_dir .replace ("/bin/" , "/build/bin/" )
86- env_ld = binary_dir
87- if "/bin/" in env_ld and "/build/bin/" not in env_ld :
88- env_ld = env_ld .replace ("/bin/" , "/build/bin/" )
131+ ld_path = llama_help_ld_library_path (work_cwd )
89132
90133 text , run_err = _run_help_argv (
91- [path , "--help" ],
92- cwd = working_dir if os .path .isdir (working_dir ) else None ,
93- extra_env = {"LD_LIBRARY_PATH" : env_ld },
134+ [exec_path , "--help" ],
135+ cwd = work_cwd if os .path .isdir (work_cwd ) else None ,
136+ extra_env = {"LD_LIBRARY_PATH" : ld_path },
137+ scan_engine = engine ,
94138 )
95139 if not text .strip ():
96- return _error_entry (path , run_err or "empty help output" )
140+ return _error_entry (exec_path , run_err or "empty help output" )
97141 try :
98142 sections = parse_llama_help_to_sections (text , engine )
99143 except Exception as e :
100144 logger .exception ("llama help parse failed" )
101- return _error_entry (path , f"parse error: { e } " )
145+ return _error_entry (exec_path , f"parse error: { e } " )
102146
103147 n_params = sum (len (s .get ("params" ) or []) for s in sections )
104148 if n_params == 0 :
105149 msg = run_err or (
106150 "No CLI flags parsed from --help. If you only see GPU/CUDA lines, the binary may have exited "
107151 "before usage text was printed; try running it with --help in a shell."
108152 )
109- return _error_entry (path , msg )
153+ return _error_entry (exec_path , msg )
110154
111155 return {
112- "binary_path" : path ,
156+ "binary_path" : exec_path ,
113157 "scanned_at" : iso_now (),
114158 "scan_error" : None ,
115159 "sections" : sections ,
@@ -131,6 +175,7 @@ def scan_lmdeploy_version(version_row: dict) -> dict:
131175 [lmdeploy_bin , "serve" , "api_server" , "--help" ],
132176 cwd = vdir ,
133177 extra_env = {"VIRTUAL_ENV" : vdir , "PATH" : f"{ os .path .join (vdir , 'bin' )} :{ os .environ .get ('PATH' , '' )} " },
178+ scan_engine = "lmdeploy" ,
134179 )
135180 if not text .strip ():
136181 return _error_entry (lmdeploy_bin , run_err or "empty help output" )
@@ -171,7 +216,25 @@ def scan_engine_version(store: Any, engine: str, version_row: dict) -> dict:
171216 return entry
172217
173218 if engine in ("llama_cpp" , "ik_llama" ):
174- entry = scan_llama_engine_version (engine , version_row )
219+ row = dict (version_row )
220+ active = store .get_active_engine_version (engine )
221+ if (
222+ active
223+ and active .get ("version" ) == ver
224+ and active .get ("binary_path" )
225+ ):
226+ try :
227+ from backend .llama_engine_resolve import (
228+ get_active_llama_swap_binary_path ,
229+ infer_llama_engine_for_binary ,
230+ )
231+
232+ swap_bin = get_active_llama_swap_binary_path (store )
233+ if swap_bin and infer_llama_engine_for_binary (store , swap_bin ) == engine :
234+ row ["binary_path" ] = swap_bin
235+ except Exception as e :
236+ logger .debug ("Active llama-swap binary override skipped: %s" , e )
237+ entry = scan_llama_engine_version (engine , row )
175238 elif engine == "lmdeploy" :
176239 entry = scan_lmdeploy_version (version_row )
177240 else :
0 commit comments