Use llama-server -hf mode, add GGUF variant selector, fix vision detection

Replace Python-side GGUF download with llama-server's native -hf flag for
HuggingFace repos. Add frontend variant picker so users can choose
quantization (Q4_K_M, Q8_0, BF16, etc.) with file sizes. Fix vision
detection via mmproj files instead of hardcoding is_vision=False.
This commit is contained in:
Roland Tannous 2026-02-24 19:03:06 +04:00
commit ef1cd3ac98
13 changed files with 489 additions and 82 deletions

View file

@ -36,6 +36,9 @@ class LlamaCppBackend:
self._port: Optional[int] = None
self._model_identifier: Optional[str] = None
self._gguf_path: Optional[str] = None
self._hf_repo: Optional[str] = None
self._hf_variant: Optional[str] = None
self._is_vision: bool = False
self._healthy = False
self._lock = threading.Lock()
self._chat_template: Optional[str] = None
@ -56,6 +59,10 @@ class LlamaCppBackend:
def model_identifier(self) -> Optional[str]:
return self._model_identifier
@property
def is_vision(self) -> bool:
return self._is_vision
# ── Binary discovery ──────────────────────────────────────────
@staticmethod
@ -109,27 +116,33 @@ class LlamaCppBackend:
def load_model(
self,
gguf_path: str,
*,
# Local mode: pass a path to a .gguf file
gguf_path: Optional[str] = None,
# HF mode: let llama-server download via -hf "repo:quant"
hf_repo: Optional[str] = None,
hf_variant: Optional[str] = None,
hf_token: Optional[str] = None,
# Common
model_identifier: str,
is_vision: bool = False,
n_ctx: int = 4096,
n_gpu_layers: int = -1,
n_threads: Optional[int] = None,
) -> bool:
"""
Start llama-server with the given GGUF file.
Start llama-server with a GGUF model.
Args:
gguf_path: Path to the .gguf file
model_identifier: Display identifier for the model
n_ctx: Context window size
n_gpu_layers: Number of layers to offload to GPU (-1 = all)
n_threads: Number of CPU threads (None = auto)
Two modes:
- Local: ``gguf_path="/path/to/model.gguf"`` uses ``-m``
- HF: ``hf_repo="unsloth/gemma-3-4b-it-GGUF", hf_variant="Q4_K_M"`` uses ``-hf``
Returns:
True if server started and health check passed.
In HF mode, llama-server handles downloading, caching, and
auto-loading mmproj files for vision models.
Returns True if server started and health check passed.
"""
with self._lock:
# Kill existing process if any
self._kill_process()
binary = self._find_llama_server_binary()
@ -140,17 +153,33 @@ class LlamaCppBackend:
"or set LLAMA_SERVER_PATH environment variable."
)
if not Path(gguf_path).is_file():
raise FileNotFoundError(f"GGUF file not found: {gguf_path}")
self._port = self._find_free_port()
cmd = [
binary,
"-m", gguf_path,
"--port", str(self._port),
"-c", str(n_ctx),
"-ngl", str(n_gpu_layers),
]
# Build command based on mode
if hf_repo:
hf_spec = f"{hf_repo}:{hf_variant}" if hf_variant else hf_repo
cmd = [
binary,
"-hf", hf_spec,
"--port", str(self._port),
"-c", str(n_ctx),
"-ngl", str(n_gpu_layers),
]
if hf_token:
cmd.extend(["--hf-token", hf_token])
elif gguf_path:
if not Path(gguf_path).is_file():
raise FileNotFoundError(f"GGUF file not found: {gguf_path}")
cmd = [
binary,
"-m", gguf_path,
"--port", str(self._port),
"-c", str(n_ctx),
"-ngl", str(n_gpu_layers),
]
else:
raise ValueError("Either gguf_path or hf_repo must be provided")
if n_threads is not None:
cmd.extend(["--threads", str(n_threads)])
@ -173,10 +202,14 @@ class LlamaCppBackend:
)
self._gguf_path = gguf_path
self._hf_repo = hf_repo
self._hf_variant = hf_variant
self._is_vision = is_vision
self._model_identifier = model_identifier
# Wait for health
if not self._wait_for_health(timeout=120.0):
# HF mode: llama-server downloads before becoming healthy — need longer timeout
timeout = 600.0 if hf_repo else 120.0
if not self._wait_for_health(timeout=timeout):
self._kill_process()
raise RuntimeError(
"llama-server failed to start. "
@ -185,8 +218,12 @@ class LlamaCppBackend:
self._healthy = True
# Try to read chat template from GGUF metadata
self._chat_template = self._read_gguf_chat_template(gguf_path)
# Read chat template from local GGUF metadata (skip in HF mode —
# llama-server handles template application internally)
if gguf_path:
self._chat_template = self._read_gguf_chat_template(gguf_path)
else:
self._chat_template = None
logger.info(
f"llama-server ready on port {self._port} "
@ -201,6 +238,9 @@ class LlamaCppBackend:
logger.info(f"Unloaded GGUF model: {self._model_identifier}")
self._model_identifier = None
self._gguf_path = None
self._hf_repo = None
self._hf_variant = None
self._is_vision = False
self._port = None
self._healthy = False
self._chat_template = None