fix(studio): inherit llama_extra_args and honor --no-mmproj (#5902)

* fix(studio): inherit llama_extra_args and honor --no-mmproj

Reloading the same GGUF from the UI without gguf_variant no longer drops
CLI pass-through args like --no-mmproj. Skip mmproj download and launch
when --no-mmproj is present in llama_extra_args.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix(studio): tighten GGUF llama_extra_args variant inheritance guard

Reject inherited CLI args when the request changes gguf_variant or when
omitted variant resolves differently from the stored extra_args source.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Treat --no-mmproj-auto and --mmproj-auto with last-wins parsing for PR #5902

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
This commit is contained in:
James Dawdy 2026-06-12 02:27:04 -05:00 committed by GitHub
commit f22e890ab8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 64 additions and 17 deletions

View file

@ -28,6 +28,7 @@ from typing import Callable, Generator, Iterable, List, Optional
import httpx
from core.inference.llama_server_args import (
extra_args_disable_mmproj,
parse_cache_override,
parse_ctx_override,
resolve_cache_type_kv,
@ -2929,8 +2930,8 @@ class LlamaCppBackend:
hf_variant = hf_variant,
hf_token = hf_token,
)
# Auto-download mmproj for vision models
if is_vision and not mmproj_path:
# Auto-download mmproj for vision models unless opted out.
if is_vision and not mmproj_path and not extra_args_disable_mmproj(extra_args):
mmproj_path = self._download_mmproj(
hf_repo = hf_repo,
hf_token = hf_token,
@ -3191,10 +3192,12 @@ class LlamaCppBackend:
gpu_indices, use_fit = None, True
effective_ctx = requested_ctx # fall back to original
launch_mmproj_path = self._resolve_launch_mmproj_path(
model_path = model_path,
mmproj_path = mmproj_path,
)
launch_mmproj_path = None
if not extra_args_disable_mmproj(extra_args):
launch_mmproj_path = self._resolve_launch_mmproj_path(
model_path = model_path,
mmproj_path = mmproj_path,
)
# Need both a resolved mmproj AND the config vision flag; a stray
# mmproj passing the family-name heuristic must not flip a non-VLM
# GGUF into vision mode.

View file

@ -260,6 +260,28 @@ def resolve_cache_type_kv(
return override if override is not None else fallback_cache_type_kv
_MMPROJ_DISABLE_FLAGS: frozenset[str] = frozenset({"--no-mmproj", "--no-mmproj-auto"})
_MMPROJ_ENABLE_FLAGS: frozenset[str] = frozenset({"--mmproj-auto"})
def extra_args_disable_mmproj(args: Optional[Iterable[str]]) -> bool:
"""True when pass-through args opt out of vision mmproj loading.
llama-server parses --mmproj-auto / --no-mmproj / --no-mmproj-auto as one
boolean with last-wins semantics; mirror that here.
"""
if not args:
return False
disabled = False
for raw in args:
flag = _flag_name(str(raw))
if flag in _MMPROJ_DISABLE_FLAGS:
disabled = True
elif flag in _MMPROJ_ENABLE_FLAGS:
disabled = False
return disabled
def strip_shadowing_flags(
args: Iterable[str],
*,

View file

@ -1449,18 +1449,23 @@ async def load_model(
# parse against a freshly-supplied first-class field.
if request.llama_extra_args is None and llama_backend.extra_args:
source = llama_backend.extra_args_source
# Compare against the resolved variant, not the request field:
# callers commonly omit gguf_variant for local ``.gguf`` paths
# and HF auto-pick flows. ``config.gguf_variant`` is the variant
# load_model was actually invoked with (see HF / local branches
# below), so both sides key off the same string.
resolved_variant = config.gguf_variant
same_source = bool(
source
and source[0]
and source[0].lower() == model_identifier.lower()
and (source[1] or "").lower() == (resolved_variant or "").lower()
# Compare against the resolved variant, not the request
# field: callers commonly omit gguf_variant for local
# ``.gguf`` paths and HF auto-pick flows. ``config.gguf_
# variant`` is the variant load_model was actually
# invoked with (see the HF / local branches below), so
# both sides of the comparison key off the same string.
resolved_variant = (config.gguf_variant or "").lower()
request_variant = (request.gguf_variant or "").lower()
stored_variant = (source[1] or "").lower() if source else ""
same_model = bool(
source and source[0] and source[0].lower() == model_identifier.lower()
)
if request.gguf_variant:
variant_mismatch = request_variant != stored_variant
else:
variant_mismatch = bool(stored_variant and resolved_variant != stored_variant)
same_source = same_model and not variant_mismatch
if not same_source:
logger.info(
"Not inheriting llama_extra_args: stored args came from %s, loading %s",

View file

@ -27,6 +27,7 @@ parse_cache_override = _lsa.parse_cache_override
parse_ctx_override = _lsa.parse_ctx_override
resolve_cache_type_kv = _lsa.resolve_cache_type_kv
strip_shadowing_flags = _lsa.strip_shadowing_flags
extra_args_disable_mmproj = _lsa.extra_args_disable_mmproj
validate_extra_args = _lsa.validate_extra_args
@ -509,6 +510,22 @@ def test_strip_shadowing_flags_defaults_strip_everything():
assert out == []
def test_extra_args_disable_mmproj_detects_flag():
assert extra_args_disable_mmproj(["--no-mmproj"]) is True
assert extra_args_disable_mmproj(["--threads", "12", "--no-mmproj"]) is True
assert extra_args_disable_mmproj(["--no-mmproj-auto"]) is True
def test_extra_args_disable_mmproj_false_when_absent():
assert extra_args_disable_mmproj(None) is False
assert extra_args_disable_mmproj(["--threads", "12"]) is False
def test_extra_args_disable_mmproj_last_wins():
assert extra_args_disable_mmproj(["--no-mmproj", "--mmproj-auto"]) is False
assert extra_args_disable_mmproj(["--mmproj-auto", "--no-mmproj-auto"]) is True
def test_strip_shadowing_flags_drops_model_draft_with_spec():
# --model-draft (and aliases) are Studio-managed since the separate
# MTP drafter support: an inherited copy must not last-wins-override