feat(docker): support a multimodal projector via --mmproj
Vision-capable GGUFs need a separate projector (mmproj) that maps image embeddings into the text model's space. Add `mmproj` and `mmproj_offload` as config keys and CLI overrides, and pass them through to llama-server. The projector path resolves under hf_home exactly like model_path, so it is covered by the existing read-only mount. validate_model_path() now also checks the projector, which means --change rejects a missing one *before* it removes the running container. --no-mmproj-offload is suppressed when no projector is configured, since llama.cpp rejects the flag on its own. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
489af21fd2
commit
218c3fc791
9 changed files with 199 additions and 9 deletions
|
|
@ -55,6 +55,10 @@ model_alias = alt_llm
|
|||
[model.noname]
|
||||
host_port = 9002
|
||||
|
||||
[model.vision]
|
||||
container_name = test_vision
|
||||
mmproj = qwen3/mmproj.gguf
|
||||
|
||||
[prompt.concise]
|
||||
system_prompt = Be brief.
|
||||
"""
|
||||
|
|
@ -280,6 +284,57 @@ def test_stream_and_read_timeout_from_config(tmp_path):
|
|||
assert prompt_cfg.connect_timeout == 5.0
|
||||
|
||||
|
||||
def test_mmproj_empty_by_default(tmp_path):
|
||||
cfg_path = _write_config(tmp_path)
|
||||
server_cfg, _ = resolve_effective_config(_parse(["--start", "--config", str(cfg_path)]))
|
||||
assert server_cfg.mmproj == ""
|
||||
assert server_cfg.mmproj_offload is True # llama.cpp offloads by default
|
||||
|
||||
|
||||
def test_mmproj_from_profile(tmp_path):
|
||||
cfg_path = _write_config(tmp_path)
|
||||
args = _parse(["--start", "--config", str(cfg_path), "--profile", "vision"])
|
||||
server_cfg, _ = resolve_effective_config(args)
|
||||
assert server_cfg.mmproj == "qwen3/mmproj.gguf"
|
||||
assert server_cfg.container_name == "test_vision"
|
||||
|
||||
|
||||
def test_mmproj_cli_overrides_config(tmp_path):
|
||||
cfg_path = _write_config(tmp_path)
|
||||
args = _parse(
|
||||
[
|
||||
"--start",
|
||||
"--config",
|
||||
str(cfg_path),
|
||||
"--profile",
|
||||
"vision",
|
||||
"--mmproj",
|
||||
"other/proj.gguf",
|
||||
"--no-mmproj-offload",
|
||||
]
|
||||
)
|
||||
server_cfg, _ = resolve_effective_config(args)
|
||||
assert server_cfg.mmproj == "other/proj.gguf"
|
||||
assert server_cfg.mmproj_offload is False
|
||||
|
||||
|
||||
def test_mmproj_offload_false_from_config(tmp_path):
|
||||
cfg = CONFIG_BASIC.replace(
|
||||
"[model.vision]\ncontainer_name = test_vision",
|
||||
"[model.vision]\ncontainer_name = test_vision\nmmproj_offload = false",
|
||||
)
|
||||
cfg_path = _write_config(tmp_path, cfg)
|
||||
args = _parse(["--start", "--config", str(cfg_path), "--profile", "vision"])
|
||||
server_cfg, _ = resolve_effective_config(args)
|
||||
assert server_cfg.mmproj_offload is False
|
||||
|
||||
|
||||
def test_blank_mmproj_via_cli_rejected(tmp_path):
|
||||
cfg_path = _write_config(tmp_path)
|
||||
with pytest.raises(SystemExit):
|
||||
_parse(["--start", "--config", str(cfg_path), "--mmproj", " "])
|
||||
|
||||
|
||||
def test_stream_and_read_timeout_cli_overrides_config(tmp_path):
|
||||
cfg = CONFIG_BASIC.replace("poll_interval = 2", "poll_interval = 2\nread_timeout = 900")
|
||||
cfg_path = _write_config(tmp_path, cfg)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue