Update model configurations and enhance LLM runtime logic

- Revised model URLs and descriptions in `llamacpp-models.example.yaml` and `ollama-models.example.yaml` to reflect new recommendations and vision capabilities.
- Updated the LLM runtime logic to support vision projectors and improved model resolution handling.
- Enhanced the installation script to conditionally include vision projectors when available.
- Added tests to validate the inclusion of vision projectors in model presets and ensure proper URL remapping for deprecated models.
- Improved documentation to clarify model usage and configuration options.
This commit is contained in:
Leonid Pershin
2026-08-21 07:40:47 +03:00
parent 618e6e4806
commit 15b95f04c7
7 changed files with 161 additions and 54 deletions
+11 -9
View File
@@ -84,11 +84,12 @@ $env:OLLAMA_HOST = "http://127.0.0.1:17811"
| # | ключ | tag / смысл | | # | ключ | tag / смысл |
| --- | --- | --- | | --- | --- | --- |
| 1 | **recommended** | `huihui_ai/qwen2.5-abliterate:7b`RU/EN, ~5 GB | | 1 | **recommended** | `huihui_ai/qwen2.5-vl-abliterated:7b`vision+RU, ~6 GB |
| 2 | light | `qwen2.5:3b` | | 2 | light | VL 3B abliterate (~3 GB) |
| 3 | stock | `qwen2.5:7b` | | 3 | text | text-only `qwen2.5-abliterate:7b` |
| 4 | alt | другой abliterate 7B | | 4 | stock | `qwen2.5:7b` |
| 5 | empty | только runtime | | 5 | alt | другой text abliterate 7B |
| 6 | empty | только runtime |
| — | keep | не трогать yaml (если уже спросили повторно) | | — | keep | не трогать yaml (если уже спросили повторно) |
`default: true` в yaml — preferred в логе; pull идёт по всему списку. `default: true` в yaml — preferred в логе; pull идёт по всему списку.
@@ -123,10 +124,11 @@ Unit `gpu-rent-ollama` читает `/mnt/swarm_data/.gpu-rent-gpu.json`:
| # | ключ | что | | # | ключ | что |
| --- | --- | --- | | --- | --- | --- |
| 1 | **recommended** | Qwen2.5 7B abliterate Q4_K_M (~4.7 GB) | | 1 | **recommended** | Qwen2.5-VL 7B abliterate + mmproj (~4.7+0.8 GB, картинки+RU) |
| 2 | light | Qwen2.5 3B Instruct Q4_K_M | | 2 | light | Qwen2.5 3B Instruct text Q4_K_M |
| 3 | stock | официальный 7B Instruct Q4_K_M | | 3 | text | Qwen2.5 7B abliterate text-only |
| 4 | empty | только runtime | | 4 | stock | официальный 7B Instruct Q4_K_M |
| 5 | empty | только runtime |
| — | keep | не трогать yaml | | — | keep | не трогать yaml |
`-ngl` / `-c` — по GPU probe. Для recommended abliterate GGUF нужен **`HF_TOKEN`** в `.env` (иначе 401). Вручную: положи GGUF в models и `systemctl restart gpu-rent-llamacpp`. `-ngl` / `-c` — по GPU probe. Для recommended abliterate GGUF нужен **`HF_TOKEN`** в `.env` (иначе 401). Вручную: положи GGUF в models и `systemctl restart gpu-rent-llamacpp`.
+10 -9
View File
@@ -1,15 +1,16 @@
# Copy to llamacpp-models.yaml (gitignored). Used when LLM_RUNTIME=llamacpp. # Copy to llamacpp-models.yaml (gitignored). Used when LLM_RUNTIME=llamacpp.
# url = direct HTTPS link to a .gguf (Hugging Face resolve/main/…). # url = direct HTTPS link to a .gguf; mmproj = vision projector (Qwen2.5-VL).
# Empty models: [] → only llama-server, GGUF клади вручную на VM. # Empty models: [] → only llama-server, GGUF клади вручную.
# Purpose: prompt-help beside SwarmUI (RU/EN). # Purpose: prompt-help / vision beside SwarmUI (RU/EN, low refusal).
models: models:
# Recommended: Qwen2.5 7B abliterate Q4_K_M (~4.7 GB) # Recommended: Qwen2.5-VL 7B abliterate Q4_K_M + mmproj (~4.7 + 0.8 GB)
- url: https://huggingface.co/bartowski/huihui-ai_Qwen2.5-7B-Instruct-abliterated-GGUF/resolve/main/huihui-ai_Qwen2.5-7B-Instruct-abliterated-Q4_K_M.gguf - url: https://huggingface.co/mradermacher/Qwen2.5-VL-7B-Instruct-abliterated-GGUF/resolve/main/Qwen2.5-VL-7B-Instruct-abliterated.Q4_K_M.gguf
mmproj: https://huggingface.co/mradermacher/Qwen2.5-VL-7B-Instruct-abliterated-GGUF/resolve/main/Qwen2.5-VL-7B-Instruct-abliterated.mmproj-Q8_0.gguf
default: true default: true
# Lighter (~2 GB): # Text-only abliterate 7B (~4.7 GB):
# - url: https://huggingface.co/bartowski/Qwen2.5-3B-Instruct-GGUF/resolve/main/Qwen2.5-3B-Instruct-Q4_K_M.gguf # - url: https://huggingface.co/RichardErkhov/huihui-ai_-_Qwen2.5-7B-Instruct-abliterated-gguf/resolve/main/Qwen2.5-7B-Instruct-abliterated.Q4_K_M.gguf
# Official stock 7B (more refusals): # Lighter text (~2 GB):
# - url: https://huggingface.co/bartowski/Qwen2.5-7B-Instruct-GGUF/resolve/main/Qwen2.5-7B-Instruct-Q4_K_M.gguf # - url: https://huggingface.co/bartowski/Qwen2.5-3B-Instruct-GGUF/resolve/main/Qwen2.5-3B-Instruct-Q4_K_M.gguf
+8 -8
View File
@@ -1,17 +1,17 @@
# Copy to ollama-models.yaml (gitignored). Used when LLM_RUNTIME=ollama. # Copy to ollama-models.yaml (gitignored). Used when LLM_RUNTIME=ollama.
# name = exact tag for `ollama pull`. Empty models: [] → runtime only, no pull. # name = exact tag for `ollama pull`. Empty models: [] → runtime only, no pull.
# Purpose: help craft SwarmUI prompts (RU/EN, low refusal). # Purpose: SwarmUI prompt help + vision (RU/EN, low refusal).
models: models:
# Recommended: Russian+English, ~5GB, low refusal (community abliterate). # Recommended: vision + Russian/English, ~6GB, abliterated
- name: huihui_ai/qwen2.5-abliterate:7b - name: huihui_ai/qwen2.5-vl-abliterated:7b
default: true default: true
# Lighter / faster (weaker prompts): # Lighter vision:
# - name: qwen2.5:3b # - name: huihui_ai/qwen2.5-vl-abliterated:3b
# Text-only abliterate (~5GB):
# - name: huihui_ai/qwen2.5-abliterate:7b
# Official stock (more refusals): # Official stock (more refusals):
# - name: qwen2.5:7b # - name: qwen2.5:7b
# Alternate abliterate pack:
# - name: richardyoung/qwen2.5-7b-instruct-abliterated
+72 -17
View File
@@ -19,18 +19,21 @@ from gpu_rent.paths import (
VALID_RUNTIMES = frozenset({"none", "ollama", "llamacpp"}) VALID_RUNTIMES = frozenset({"none", "ollama", "llamacpp"})
OLLAMA_PRESETS: dict[str, list[str]] = { OLLAMA_PRESETS: dict[str, list[str]] = {
"recommended": ["huihui_ai/qwen2.5-abliterate:7b"], # Vision + RU/EN + low refusal — best default for swarm-assistent / prompt help with images
"light": ["qwen2.5:3b"], "recommended": ["huihui_ai/qwen2.5-vl-abliterated:7b"],
"light": ["huihui_ai/qwen2.5-vl-abliterated:3b"],
"text": ["huihui_ai/qwen2.5-abliterate:7b"],
"stock": ["qwen2.5:7b"], "stock": ["qwen2.5:7b"],
"alt": ["richardyoung/qwen2.5-7b-instruct-abliterated"], "alt": ["richardyoung/qwen2.5-7b-instruct-abliterated"],
"empty": [], "empty": [],
} }
OLLAMA_PRESET_LABELS: dict[str, str] = { OLLAMA_PRESET_LABELS: dict[str, str] = {
"recommended": "Qwen2.5 7B abliterate (RU/EN, мало отказов, ~5GB)", "recommended": "Qwen2.5-VL 7B abliterate (картинки+RU, ~6GB)",
"light": "qwen2.5:3b (быстрее, слабее)", "light": "Qwen2.5-VL 3B abliterate (vision, быстрее, ~3GB)",
"text": "Qwen2.5 7B abliterate text-only (~5GB)",
"stock": "официальный qwen2.5:7b (больше цензуры)", "stock": "официальный qwen2.5:7b (больше цензуры)",
"alt": "другой abliterate-пак 7B", "alt": "другой text abliterate-пак 7B",
"empty": "только runtime, без pull", "empty": "только runtime, без pull",
"keep": "не менять ollama-models.yaml", "keep": "не менять ollama-models.yaml",
} }
@@ -40,22 +43,51 @@ PRESET_HELP = "\n".join(
f"{k}{v}" for k, v in OLLAMA_PRESET_LABELS.items() if k != "keep" f"{k}{v}" for k, v in OLLAMA_PRESET_LABELS.items() if k != "keep"
) )
LLAMACPP_PRESETS: dict[str, list[str]] = { # Each preset entry: {"url": "...gguf", "mmproj": optional vision projector url}
LLAMACPP_PRESETS: dict[str, list[dict[str, str]]] = {
"recommended": [ "recommended": [
"https://huggingface.co/bartowski/huihui-ai_Qwen2.5-7B-Instruct-abliterated-GGUF/resolve/main/huihui-ai_Qwen2.5-7B-Instruct-abliterated-Q4_K_M.gguf", {
"url": (
"https://huggingface.co/mradermacher/Qwen2.5-VL-7B-Instruct-abliterated-GGUF/"
"resolve/main/Qwen2.5-VL-7B-Instruct-abliterated.Q4_K_M.gguf"
),
"mmproj": (
"https://huggingface.co/mradermacher/Qwen2.5-VL-7B-Instruct-abliterated-GGUF/"
"resolve/main/Qwen2.5-VL-7B-Instruct-abliterated.mmproj-Q8_0.gguf"
),
},
], ],
"light": [ "light": [
"https://huggingface.co/bartowski/Qwen2.5-3B-Instruct-GGUF/resolve/main/Qwen2.5-3B-Instruct-Q4_K_M.gguf", {
"url": (
"https://huggingface.co/bartowski/Qwen2.5-3B-Instruct-GGUF/"
"resolve/main/Qwen2.5-3B-Instruct-Q4_K_M.gguf"
),
},
],
"text": [
{
"url": (
"https://huggingface.co/RichardErkhov/huihui-ai_-_Qwen2.5-7B-Instruct-abliterated-gguf/"
"resolve/main/Qwen2.5-7B-Instruct-abliterated.Q4_K_M.gguf"
),
},
], ],
"stock": [ "stock": [
"https://huggingface.co/bartowski/Qwen2.5-7B-Instruct-GGUF/resolve/main/Qwen2.5-7B-Instruct-Q4_K_M.gguf", {
"url": (
"https://huggingface.co/bartowski/Qwen2.5-7B-Instruct-GGUF/"
"resolve/main/Qwen2.5-7B-Instruct-Q4_K_M.gguf"
),
},
], ],
"empty": [], "empty": [],
} }
LLAMACPP_PRESET_LABELS: dict[str, str] = { LLAMACPP_PRESET_LABELS: dict[str, str] = {
"recommended": "Qwen2.5 7B abliterate GGUF Q4_K_M (~4.7GB, мало отказов)", "recommended": "Qwen2.5-VL 7B abliterate + mmproj (~4.7+0.8GB, картинки+RU)",
"light": "Qwen2.5 3B Instruct Q4_K_M (~2GB)", "light": "Qwen2.5 3B Instruct Q4_K_M text (~2GB)",
"text": "Qwen2.5 7B abliterate text-only Q4_K_M (~4.7GB)",
"stock": "официальный Qwen2.5 7B Instruct Q4_K_M", "stock": "официальный Qwen2.5 7B Instruct Q4_K_M",
"empty": "только llama-server, GGUF вручную", "empty": "только llama-server, GGUF вручную",
"keep": "не менять llamacpp-models.yaml", "keep": "не менять llamacpp-models.yaml",
@@ -118,6 +150,7 @@ class OllamaModelEntry:
class LlamaCppModelEntry: class LlamaCppModelEntry:
url: str url: str
filename: str | None = None filename: str | None = None
mmproj_url: str | None = None
default: bool = False default: bool = False
@@ -185,6 +218,20 @@ def gguf_filename_from_url(url: str) -> str:
return "model.gguf" return "model.gguf"
# Dead / moved HF mirrors → current resolve URL (same Q4_K_M abliterate weights).
_LLAMACPP_URL_ALIASES: dict[str, str] = {
"https://huggingface.co/bartowski/huihui-ai_Qwen2.5-7B-Instruct-abliterated-GGUF/resolve/main/huihui-ai_Qwen2.5-7B-Instruct-abliterated-Q4_K_M.gguf": (
"https://huggingface.co/RichardErkhov/huihui-ai_-_Qwen2.5-7B-Instruct-abliterated-gguf/resolve/main/Qwen2.5-7B-Instruct-abliterated.Q4_K_M.gguf"
),
}
def remap_llamacpp_url(url: str) -> str:
"""Rewrite known-dead GGUF mirrors so old llamacpp-models.yaml still works."""
key = (url or "").strip()
return _LLAMACPP_URL_ALIASES.get(key, key)
def parse_llamacpp_models(path: Path) -> list[LlamaCppModelEntry]: def parse_llamacpp_models(path: Path) -> list[LlamaCppModelEntry]:
if not path.is_file(): if not path.is_file():
return [] return []
@@ -199,21 +246,23 @@ def parse_llamacpp_models(path: Path) -> list[LlamaCppModelEntry]:
out: list[LlamaCppModelEntry] = [] out: list[LlamaCppModelEntry] = []
for item in items: for item in items:
if isinstance(item, str): if isinstance(item, str):
url = item.strip() url = remap_llamacpp_url(item.strip())
if url: if url:
out.append(LlamaCppModelEntry(url=url)) out.append(LlamaCppModelEntry(url=url))
continue continue
if not isinstance(item, dict): if not isinstance(item, dict):
continue continue
url = str(item.get("url") or "").strip() url = remap_llamacpp_url(str(item.get("url") or "").strip())
if not url: if not url:
continue continue
fname = item.get("filename") fname = item.get("filename")
filename = str(fname).strip() if fname else None filename = str(fname).strip() if fname else None
mmproj = remap_llamacpp_url(str(item.get("mmproj") or item.get("mmproj_url") or "").strip())
out.append( out.append(
LlamaCppModelEntry( LlamaCppModelEntry(
url=url, url=url,
filename=filename or None, filename=filename or None,
mmproj_url=mmproj or None,
default=bool(item.get("default")), default=bool(item.get("default")),
) )
) )
@@ -244,17 +293,23 @@ def write_llamacpp_models_preset(path: Path, preset: str) -> None:
key = (preset or "recommended").strip().lower() key = (preset or "recommended").strip().lower()
if key not in LLAMACPP_PRESETS: if key not in LLAMACPP_PRESETS:
raise ValueError(f"пресет {preset!r}; варианты: {', '.join(LLAMACPP_PRESETS)}") raise ValueError(f"пресет {preset!r}; варианты: {', '.join(LLAMACPP_PRESETS)}")
urls = LLAMACPP_PRESETS[key] specs = LLAMACPP_PRESETS[key]
lines = [ lines = [
"# Локальный манифест llama.cpp GGUF (не коммить). Пример: llamacpp-models.example.yaml", "# Локальный манифест llama.cpp GGUF (не коммить). Пример: llamacpp-models.example.yaml",
"# url = прямой HTTPS на .gguf. Пустой models: [] — без скачивания.", "# url = прямой HTTPS на .gguf; mmproj = projector для vision (Qwen2.5-VL и т.п.).",
"models:", "models:",
] ]
if not urls: if not specs:
lines.append(" []") lines.append(" []")
else: else:
for i, url in enumerate(urls): for i, spec in enumerate(specs):
url = str(spec.get("url") or "").strip()
if not url:
continue
lines.append(f" - url: {url}") lines.append(f" - url: {url}")
mmproj = str(spec.get("mmproj") or "").strip()
if mmproj:
lines.append(f" mmproj: {mmproj}")
if i == 0: if i == 0:
lines.append(" default: true") lines.append(" default: true")
path.write_text("\n".join(lines) + "\n", encoding="utf-8") path.write_text("\n".join(lines) + "\n", encoding="utf-8")
+11 -3
View File
@@ -478,13 +478,21 @@ def provision_llm(cfg: Config, host: str, log: Log) -> None:
f"{defaults[0].filename or gguf_filename_from_url(defaults[0].url)}" f"{defaults[0].filename or gguf_filename_from_url(defaults[0].url)}"
) )
if entries: if entries:
jobs = [ jobs = []
for e in entries:
jobs.append(
{ {
"url": e.url, "url": e.url,
"filename": e.filename or gguf_filename_from_url(e.url), "filename": e.filename or gguf_filename_from_url(e.url),
} }
for e in entries )
] if e.mmproj_url:
jobs.append(
{
"url": e.mmproj_url,
"filename": gguf_filename_from_url(e.mmproj_url),
}
)
put_text( put_text(
cfg, host, "/tmp/gpu-rent-llamacpp-models.json", json.dumps(jobs, indent=2) cfg, host, "/tmp/gpu-rent-llamacpp-models.json", json.dumps(jobs, indent=2)
) )
+15 -3
View File
@@ -100,15 +100,27 @@ print(prefer[0] if prefer else "")
rm -rf "$TMP" rm -rf "$TMP"
fi fi
# Pick first GGUF if present; otherwise unit starts but API may idle without model. # Prefer a weights GGUF (skip mmproj), then attach --mmproj if present.
MODEL_ARG="" MODEL_ARG=""
FIRST_GGUF="$(find "$MODELS_DIR" -type f \( -name '*.gguf' -o -name '*.GGUF' \) | head -n1 || true)" MMPROJ_ARG=""
FIRST_GGUF="$(
find "$MODELS_DIR" -type f \( -name '*.gguf' -o -name '*.GGUF' \) \
! -iname '*mmproj*' 2>/dev/null | head -n1 || true
)"
MMPROJ_GGUF="$(
find "$MODELS_DIR" -type f \( -iname '*mmproj*.gguf' -o -iname '*mmproj*.GGUF' \) \
2>/dev/null | head -n1 || true
)"
if [[ -n "$FIRST_GGUF" ]]; then if [[ -n "$FIRST_GGUF" ]]; then
MODEL_ARG="-m ${FIRST_GGUF}" MODEL_ARG="-m ${FIRST_GGUF}"
log "модель ${FIRST_GGUF}" log "модель ${FIRST_GGUF}"
else else
log "нет GGUF в ${MODELS_DIR} — положи файл вручную и systemctl restart ${UNIT}" log "нет GGUF в ${MODELS_DIR} — положи файл вручную и systemctl restart ${UNIT}"
fi fi
if [[ -n "$MMPROJ_GGUF" ]]; then
MMPROJ_ARG="--mmproj ${MMPROJ_GGUF}"
log "mmproj ${MMPROJ_GGUF}"
fi
# GPU layers: share card with Swarm — full offload on mid+, leave headroom on low. # GPU layers: share card with Swarm — full offload on mid+, leave headroom on low.
NGL=99 NGL=99
@@ -144,7 +156,7 @@ Type=simple
User=${SWARM_USER} User=${SWARM_USER}
Group=${SWARM_USER} Group=${SWARM_USER}
WorkingDirectory=${LLAMA_ROOT} WorkingDirectory=${LLAMA_ROOT}
ExecStart=${SERVER_BIN} ${MODEL_ARG} --host 127.0.0.1 --port 8080 -ngl ${NGL} -c ${CTX} ExecStart=${SERVER_BIN} ${MODEL_ARG} ${MMPROJ_ARG} --host 127.0.0.1 --port 8080 -ngl ${NGL} -c ${CTX}
Restart=on-failure Restart=on-failure
RestartSec=8 RestartSec=8
+30 -1
View File
@@ -5,7 +5,9 @@ import pytest
from gpu_rent.llm_runtime import ( from gpu_rent.llm_runtime import (
decide_runtime, decide_runtime,
normalize_runtime, normalize_runtime,
parse_llamacpp_models,
parse_ollama_models, parse_ollama_models,
remap_llamacpp_url,
write_ollama_models_preset, write_ollama_models_preset,
) )
@@ -56,4 +58,31 @@ def test_write_preset(tmp_path: Path):
path = tmp_path / "out.yaml" path = tmp_path / "out.yaml"
write_ollama_models_preset(path, "recommended") write_ollama_models_preset(path, "recommended")
entries = parse_ollama_models(path) entries = parse_ollama_models(path)
assert entries[0].name == "huihui_ai/qwen2.5-abliterate:7b" assert entries[0].name == "huihui_ai/qwen2.5-vl-abliterated:7b"
def test_write_llamacpp_preset_includes_mmproj(tmp_path: Path):
from gpu_rent.llm_runtime import write_llamacpp_models_preset
path = tmp_path / "lc.yaml"
write_llamacpp_models_preset(path, "recommended")
entries = parse_llamacpp_models(path)
assert len(entries) == 1
assert "VL" in entries[0].url or "vl" in entries[0].url.lower()
assert entries[0].mmproj_url
assert "mmproj" in entries[0].mmproj_url
def test_remap_dead_bartowski_abliterate_url(tmp_path: Path):
dead = (
"https://huggingface.co/bartowski/huihui-ai_Qwen2.5-7B-Instruct-abliterated-GGUF/"
"resolve/main/huihui-ai_Qwen2.5-7B-Instruct-abliterated-Q4_K_M.gguf"
)
fixed = remap_llamacpp_url(dead)
assert "RichardErkhov" in fixed
assert "Q4_K_M.gguf" in fixed
path = tmp_path / "lc.yaml"
path.write_text(f"models:\n - url: {dead}\n default: true\n", encoding="utf-8")
entries = parse_llamacpp_models(path)
assert len(entries) == 1
assert entries[0].url == fixed