Refactor LLM configuration to remove llamacpp support
- Removed references to llamacpp from configuration files, scripts, and documentation, streamlining the LLM setup process to focus solely on Ollama. - Updated environment variables and paths to eliminate llamacpp-related entries, ensuring clarity in the configuration. - Adjusted CLI commands and help messages to reflect the removal of llamacpp, enhancing user experience and reducing confusion. - Revised documentation to provide clear guidance on using Ollama exclusively, including updates to setup instructions and runtime options.
This commit is contained in:
@@ -4,7 +4,6 @@
|
|||||||
!.env.example
|
!.env.example
|
||||||
gpu-rent.vars
|
gpu-rent.vars
|
||||||
ollama-models.yaml
|
ollama-models.yaml
|
||||||
llamacpp-models.yaml
|
|
||||||
models.yaml
|
models.yaml
|
||||||
extensions.yaml
|
extensions.yaml
|
||||||
|
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ Unix: `./gpu-rent.sh …` (один раз `chmod +x gpu-rent.sh`).
|
|||||||
| `doctor` | Preflight без create |
|
| `doctor` | Preflight без create |
|
||||||
| `flavors` / `dry-run` | Что выберет / план без денег |
|
| `flavors` / `dry-run` | Что выберет / план без денег |
|
||||||
| `up` / `up --yes` | GPU + seed + туннель; без `--yes` — меню Selectel/LLM |
|
| `up` / `up --yes` | GPU + seed + туннель; без `--yes` — меню Selectel/LLM |
|
||||||
| `up --ollama` / `--llamacpp` | + LLM (см. [docs/llm.md](docs/llm.md)) |
|
| `up --ollama` | + Ollama (см. [docs/llm.md](docs/llm.md)) |
|
||||||
| `up --llm-only --llamacpp` | только LLM, без SwarmUI |
|
| `up --llm-only --ollama` | только Ollama, без SwarmUI |
|
||||||
| `tunnel` / `open` | Снова UI / браузер |
|
| `tunnel` / `open` | Снова UI / браузер |
|
||||||
| `hold` / `status` | Пауза killer / состояние |
|
| `hold` / `status` | Пауза killer / состояние |
|
||||||
| `stop` / `destroy --i-understand-data-loss` | Стоп GPU / + диски |
|
| `stop` / `destroy --i-understand-data-loss` | Стоп GPU / + диски |
|
||||||
@@ -73,7 +73,7 @@ Unix: `./gpu-rent.sh …` (один раз `chmod +x gpu-rent.sh`).
|
|||||||
| `.env` | из `env.example` — `OS_*`, Civitai (не в git) |
|
| `.env` | из `env.example` — `OS_*`, Civitai (не в git) |
|
||||||
| `gpu-rent.vars` | из example — несекретные дефолты, лаунчер |
|
| `gpu-rent.vars` | из example — несекретные дефолты, лаунчер |
|
||||||
| `models.yaml` / `extensions.yaml` | манифесты (из `*.example.yaml`) |
|
| `models.yaml` / `extensions.yaml` | манифесты (из `*.example.yaml`) |
|
||||||
| `ollama-models.yaml` / `llamacpp-models.yaml` | LLM (из example; gitignore) |
|
| `ollama-models.yaml` | Ollama-модели (из example; gitignore) |
|
||||||
| `Models/` … | локальный push на `up` |
|
| `Models/` … | локальный push на `up` |
|
||||||
| `.gpu-rent/` | state, SSH-ключ (gitignore) |
|
| `.gpu-rent/` | state, SSH-ключ (gitignore) |
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ Unix: `./gpu-rent.sh …` (один раз `chmod +x gpu-rent.sh`).
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| [docs/setup.md](docs/setup.md) | **Пошаговая подготовка** до первого `up` |
|
| [docs/setup.md](docs/setup.md) | **Пошаговая подготовка** до первого `up` |
|
||||||
| [docs/cli.md](docs/cli.md) | Все команды и переменные |
|
| [docs/cli.md](docs/cli.md) | Все команды и переменные |
|
||||||
| [docs/llm.md](docs/llm.md) | Ollama / llama.cpp |
|
| [docs/llm.md](docs/llm.md) | Ollama (opt-in LLM) |
|
||||||
| [docs/models.md](docs/models.md) | Civitai + папка `Models/` |
|
| [docs/models.md](docs/models.md) | Civitai + папка `Models/` |
|
||||||
| [docs/spike-notes.md](docs/spike-notes.md) | Чеклист первого живого прогона |
|
| [docs/spike-notes.md](docs/spike-notes.md) | Чеклист первого живого прогона |
|
||||||
| [docs/README.md](docs/README.md) | Оглавление всего `docs/` |
|
| [docs/README.md](docs/README.md) | Оглавление всего `docs/` |
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ CLI поднимает прерываемый GPU в Selectel, держит Swar
|
|||||||
| Git-расширения SwarmUI/Comfy | [extensions.md](extensions.md) |
|
| Git-расширения SwarmUI/Comfy | [extensions.md](extensions.md) |
|
||||||
| Word-list промптов | [autocomplete.md](autocomplete.md) |
|
| Word-list промптов | [autocomplete.md](autocomplete.md) |
|
||||||
| Push/pull папок | [local-folders.md](local-folders.md) |
|
| Push/pull папок | [local-folders.md](local-folders.md) |
|
||||||
| Ollama / llama.cpp | [llm.md](llm.md) |
|
| Ollama | [llm.md](llm.md) |
|
||||||
| Capture ссылок с VM | [cli.md](cli.md) (`capture`) + [models.md](models.md) |
|
| Capture ссылок с VM | [cli.md](cli.md) (`capture`) + [models.md](models.md) |
|
||||||
| Как устроены диски и killer | [architecture.md](architecture.md) |
|
| Как устроены диски и killer | [architecture.md](architecture.md) |
|
||||||
| Контракт Selectel | [selectel.md](selectel.md) |
|
| Контракт Selectel | [selectel.md](selectel.md) |
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
│ SwarmUI : 127.0.0.1:7801 │
|
│ SwarmUI : 127.0.0.1:7801 │
|
||||||
│ idle-killer: очередь / hold / LLM busy → delete this server │
|
│ idle-killer: очередь / hold / LLM busy → delete this server │
|
||||||
│ app cred: DELETE/GET только этот server_id (fail closed) │
|
│ app cred: DELETE/GET только этот server_id (fail closed) │
|
||||||
│ optional: Ollama :11434 / llama.cpp :8080 (туннель 17811/12)│
|
│ optional: Ollama :11434 (туннель 17811) │
|
||||||
│ │
|
│ │
|
||||||
│ boot volume (network) ОС + NVIDIA + SwarmUI нативно + snapshot │
|
│ boot volume (network) ОС + NVIDIA + SwarmUI нативно + snapshot │
|
||||||
│ data volume (network) Models, Output, Data, workflows │
|
│ data volume (network) Models, Output, Data, workflows │
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
| `notify` | Toast/звук при backend Idle |
|
| `notify` | Toast/звук при backend Idle |
|
||||||
| `tunnel` | sshtunnel + Nova EXPIRED watchdog |
|
| `tunnel` | sshtunnel + Nova EXPIRED watchdog |
|
||||||
| `local_watchdog` | Опциональный локальный тик → stop при unclean exit |
|
| `local_watchdog` | Опциональный локальный тик → stop при unclean exit |
|
||||||
| `llm_runtime` / `setup_wizard` | Opt-in Ollama/llama.cpp + `ollama-models.yaml` / `llamacpp-models.yaml` |
|
| `llm_runtime` / `setup_wizard` | Opt-in Ollama + `ollama-models.yaml` |
|
||||||
| `idle_killer` / `hold` | systemd на VM + hold-файл |
|
| `idle_killer` / `hold` | systemd на VM + hold-файл |
|
||||||
| `ready` / `snapshot` | Idle backend + boot snapshot |
|
| `ready` / `snapshot` | Idle backend + boot snapshot |
|
||||||
| `access_card` | URL / MCP panel после ready |
|
| `access_card` | URL / MCP panel после ready |
|
||||||
|
|||||||
+6
-7
@@ -51,7 +51,7 @@ gpu-rent up --yes --ollama
|
|||||||
|
|
||||||
| Команда | Поведение |
|
| Команда | Поведение |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `gpu-rent setup` | Wizard: манифесты (в т.ч. ollama/llamacpp-models), `LLM_RUNTIME` (нумерованное меню), пресет, опционально local-watchdog |
|
| `gpu-rent setup` | Wizard: манифесты (в т.ч. ollama-models), `LLM_RUNTIME` (нумерованное меню), пресет, опционально local-watchdog |
|
||||||
| `gpu-rent doctor` | Preflight **без** create. Exit ≠ 0 → сессию начинать нельзя |
|
| `gpu-rent doctor` | Preflight **без** create. Exit ≠ 0 → сессию начинать нельзя |
|
||||||
| `gpu-rent flavors` | Скан `SCAN_POOLS` × `FLAVOR_PREFERENCE`, список в текущем регионе |
|
| `gpu-rent flavors` | Скан `SCAN_POOLS` × `FLAVOR_PREFERENCE`, список в текущем регионе |
|
||||||
| `gpu-rent dry-run` | План без mutating-вызовов |
|
| `gpu-rent dry-run` | План без mutating-вызовов |
|
||||||
@@ -59,8 +59,8 @@ gpu-rent up --yes --ollama
|
|||||||
| `gpu-rent up --yes` | Без вопросов; flavor из `FLAVOR_PREFERENCE` / `DEFAULT_FLAVOR_ID` |
|
| `gpu-rent up --yes` | Без вопросов; flavor из `FLAVOR_PREFERENCE` / `DEFAULT_FLAVOR_ID` |
|
||||||
| `up --keep-on-fail` | Не гасить GPU при ошибке install (по умолчанию `UP_STOP_ON_FAIL=true` → `stop`) |
|
| `up --keep-on-fail` | Не гасить GPU при ошибке install (по умолчанию `UP_STOP_ON_FAIL=true` → `stop`) |
|
||||||
| `gpu-rent up -v` / `--verbose` | Полная таблица doctor на `up` (по умолчанию кратко) |
|
| `gpu-rent up -v` / `--verbose` | Полная таблица doctor на `up` (по умолчанию кратко) |
|
||||||
| `gpu-rent up --ollama` / `--llamacpp` / `--llm …` | LLM рядом со SwarmUI |
|
| `gpu-rent up --ollama` / `--llm ollama` | Ollama рядом со SwarmUI |
|
||||||
| `gpu-rent up --no-swarm` / `--llm-only` | Только LLM (нужен `--ollama`/`--llamacpp`); без clone SwarmUI |
|
| `gpu-rent up --no-swarm` / `--llm-only` | Только Ollama (нужен `--ollama` / `LLM_RUNTIME=ollama`); без clone SwarmUI |
|
||||||
| `gpu-rent up --no-update` | Без `git pull` SwarmUI/extensions (только недостающие clone) |
|
| `gpu-rent up --no-update` | Без `git pull` SwarmUI/extensions (только недостающие clone) |
|
||||||
| `gpu-rent up --no-tunnel` | Только облако |
|
| `gpu-rent up --no-tunnel` | Только облако |
|
||||||
| `gpu-rent up --no-spot` | Не preemptible |
|
| `gpu-rent up --no-spot` | Не preemptible |
|
||||||
@@ -138,7 +138,7 @@ Exit 0 → можно `up`. Exit 1 → причина в таблице / кра
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `.env` | Секреты и OpenStack (`OS_*`, токены). Не в git |
|
| `.env` | Секреты и OpenStack (`OS_*`, токены). Не в git |
|
||||||
| `gpu-rent.vars` | Несекретные дефолты; читают лаунчеры и CLI. Пример: `gpu-rent.vars.example` |
|
| `gpu-rent.vars` | Несекретные дефолты; читают лаунчеры и CLI. Пример: `gpu-rent.vars.example` |
|
||||||
| `models.yaml` / `extensions.yaml` / `ollama-models.yaml` / `llamacpp-models.yaml` | Манифесты (gitignore; из `*.example.yaml`) |
|
| `models.yaml` / `extensions.yaml` / `ollama-models.yaml` | Манифесты (gitignore; из `*.example.yaml`) |
|
||||||
|
|
||||||
### Лаунчер (`gpu-rent.vars`)
|
### Лаунчер (`gpu-rent.vars`)
|
||||||
|
|
||||||
@@ -192,7 +192,6 @@ AUTOCOMPLETE_ENABLED=true
|
|||||||
SWARMUI_LOCAL_PORT=17801
|
SWARMUI_LOCAL_PORT=17801
|
||||||
LLM_RUNTIME=none
|
LLM_RUNTIME=none
|
||||||
OLLAMA_LOCAL_PORT=17811
|
OLLAMA_LOCAL_PORT=17811
|
||||||
LLAMACPP_LOCAL_PORT=17812
|
|
||||||
UPDATE_GIT=true
|
UPDATE_GIT=true
|
||||||
|
|
||||||
DEFAULT_FLAVOR_ID=
|
DEFAULT_FLAVOR_ID=
|
||||||
@@ -233,12 +232,12 @@ Application credential для idle-killer CLI создаёт на `up` (узко
|
|||||||
1. Nova `ACTIVE`
|
1. Nova `ACTIVE`
|
||||||
2. TCP 22 / SSH
|
2. TCP 22 / SSH
|
||||||
3. Backend Idle (если SwarmUI) → toast (если `NOTIFY_READY`)
|
3. Backend Idle (если SwarmUI) → toast (если `NOTIFY_READY`)
|
||||||
4. На VM: HTTP сервисов стека (SwarmUI `:7801` / Ollama `:11434` / llama.cpp `:8080`) — `verify_stack_on_vm`
|
4. На VM: HTTP сервисов стека (SwarmUI `:7801` / Ollama `:11434`) — `verify_stack_on_vm`
|
||||||
5. На VM: **nvidia-smi / CUDA** (fail-fast) + **torch+cuda** в Comfy venv при SwarmUI (ждём) — `verify_gpu_env`
|
5. На VM: **nvidia-smi / CUDA** (fail-fast) + **torch+cuda** в Comfy venv при SwarmUI (ждём) — `verify_gpu_env`
|
||||||
6. В логе: строка **`тайминг up:`** (SSH / bootstrap / Idle / verify / …)
|
6. В логе: строка **`тайминг up:`** (SSH / bootstrap / Idle / verify / …)
|
||||||
7. Туннель + проверка **localhost** тех же сервисов → access-card (красная рамка, если killer failed)
|
7. Туннель + проверка **localhost** тех же сервисов → access-card (красная рамка, если killer failed)
|
||||||
|
|
||||||
`gpu-rent logs --unit swarm|ollama|llamacpp|killer` — фильтр journalctl.
|
`gpu-rent logs --unit swarm|ollama|killer` — фильтр journalctl.
|
||||||
`gpu-rent status` — killer/hold, последний стек/GPU-env, тайминг up.
|
`gpu-rent status` — killer/hold, последний стек/GPU-env, тайминг up.
|
||||||
|
|
||||||
Локальный порт UI: **17801** (на VM по-прежнему 7801 на loopback).
|
Локальный порт UI: **17801** (на VM по-прежнему 7801 на loopback).
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ GPU в облаке дорогой. Веса для генерации карт
|
|||||||
- Первый clone git-реп расширений SwarmUI и ComfyUI nodes из `extensions.yaml`.
|
- Первый clone git-реп расширений SwarmUI и ComfyUI nodes из `extensions.yaml`.
|
||||||
- Autocomplete: word-list в `Data/Autocompletions` до старта UI, на каждом `up` проверка новой версии.
|
- Autocomplete: word-list в `Data/Autocompletions` до старта UI, на каждом `up` проверка новой версии.
|
||||||
- `doctor` до create; фоллбек flavor; интерактивный выбор flavor/диска; `hold`; toast Idle; `open` на 17801.
|
- `doctor` до create; фоллбек flavor; интерактивный выбор flavor/диска; `hold`; toast Idle; `open` на 17801.
|
||||||
- Opt-in LLM (Ollama / llama.cpp) через `setup` / флаги / `LLM_RUNTIME`; `capture` ссылок с VM.
|
- Opt-in LLM (Ollama) через `setup` / флаги / `LLM_RUNTIME`; `capture` ссылок с VM.
|
||||||
- `status`: диск, окно preempt 24 ч, killer / LLM.
|
- `status`: диск, окно preempt 24 ч, killer / LLM.
|
||||||
- Snapshot boot после первого удачного bootstrap.
|
- Snapshot boot после первого удачного bootstrap.
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -10,7 +10,7 @@
|
|||||||
| Civitai хост | Дефолт API `civitai.red` (полный каталог). `.com` — SFW-витрина, NSFW с неё часто 404. Ссылки `.com`/`.red`/`.green` в манифесте принимаем. 404 → один retry на второй хост. Один токен на оба домена |
|
| Civitai хост | Дефолт API `civitai.red` (полный каталог). `.com` — SFW-витрина, NSFW с неё часто 404. Ссылки `.com`/`.red`/`.green` в манифесте принимаем. 404 → один retry на второй хост. Один токен на оба домена |
|
||||||
| Пул GPU | Перед `up`/`flavors` сканируем `SCAN_POOLS` (дефолт `ru-6,ru-7`). `ru-6` — мультизональный: ходим на `https://ru-6.cloud.api.selcloud.ru/compute/` тем же токеном (SDK-каталог часто знает только RC-пул). Собираем типы GPU из extra_specs и совпадения с `FLAVOR_PREFERENCE`. Автоматом `.env` не пишем — печатаем рекомендацию `OS_REGION_NAME` / `GPU_RENT_AZ` |
|
| Пул GPU | Перед `up`/`flavors` сканируем `SCAN_POOLS` (дефолт `ru-6,ru-7`). `ru-6` — мультизональный: ходим на `https://ru-6.cloud.api.selcloud.ru/compute/` тем же токеном (SDK-каталог часто знает только RC-пул). Собираем типы GPU из extra_specs и совпадения с `FLAVOR_PREFERENCE`. Автоматом `.env` не пишем — печатаем рекомендацию `OS_REGION_NAME` / `GPU_RENT_AZ` |
|
||||||
| Манифест моделей | `<repo>/models.yaml`, типы: checkpoint / lora / vae / embedding / controlnet / upscaler. В git только `models.example.yaml` |
|
| Манифест моделей | `<repo>/models.yaml`, типы: checkpoint / lora / vae / embedding / controlnet / upscaler. В git только `models.example.yaml` |
|
||||||
| Расширения | `<repo>/extensions.yaml`: git-репы `swarmui` → `src/Extensions`, `comfy` → DLNodes. Поле `requires: none\|ollama\|llamacpp\|any-llm` фильтрует по `LLM_RUNTIME`. Пример: swarm-assistent с `requires: ollama` |
|
| Расширения | `<repo>/extensions.yaml`: git-репы `swarmui` → `src/Extensions`, `comfy` → DLNodes. Поле `requires: none\|ollama\|any-llm` фильтрует по `LLM_RUNTIME`. Пример: swarm-assistent с `requires: ollama` |
|
||||||
| Autocomplete | До первого старта: скачать word-list в `Data/Autocompletions`, прописать `DefaultUser.AutoComplete.Source`. На каждом `up` сверить GitHub blob sha и обновить файл, если изменился. Дефолт: `tags/danbooru.csv` из a1111-sd-webui-tagcomplete (как в доке SwarmUI) |
|
| Autocomplete | До первого старта: скачать word-list в `Data/Autocompletions`, прописать `DefaultUser.AutoComplete.Source`. На каждом `up` сверить GitHub blob sha и обновить файл, если изменился. Дефолт: `tags/danbooru.csv` из a1111-sd-webui-tagcomplete (как в доке SwarmUI) |
|
||||||
| Доступ | Браузер на туннеле; MCP переключается на облако, пока оно живо; HTTP API SwarmUI через тот же туннель |
|
| Доступ | Браузер на туннеле; MCP переключается на облако, пока оно живо; HTTP API SwarmUI через тот же туннель |
|
||||||
| Preemptible | По умолчанию всегда. Обычный сервер — только `--no-spot` |
|
| Preemptible | По умолчанию всегда. Обычный сервер — только `--no-spot` |
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
| `up` / `tunnel` | `up` по умолчанию после ready открывает туннель `:17801`, печатает URL и ждёт. `--no-tunnel` — только облако. Ctrl+C на туннеле GPU не гасит (`stop` отдельно). Команда `tunnel` остаётся для повторного входа |
|
| `up` / `tunnel` | `up` по умолчанию после ready открывает туннель `:17801`, печатает URL и ждёт. `--no-tunnel` — только облако. Ctrl+C на туннеле GPU не гасит (`stop` отдельно). Команда `tunnel` остаётся для повторного входа |
|
||||||
| Interactive `up` | Без `--yes`: нумерованные меню (LLM, пресет, flavor #, data GB, preemptible) → confirm. Изменения можно сохранить в `gpu-rent.vars` |
|
| Interactive `up` | Без `--yes`: нумерованные меню (LLM, пресет, flavor #, data GB, preemptible) → confirm. Изменения можно сохранить в `gpu-rent.vars` |
|
||||||
| Git update | На каждом `up` по умолчанию: `git pull` SwarmUI + репы из `extensions.yaml` + уже установленные на data (`Extensions`/`DLNodes`). `--no-update` или `UPDATE_GIT=false` — не тянуть |
|
| Git update | На каждом `up` по умолчанию: `git pull` SwarmUI + репы из `extensions.yaml` + уже установленные на data (`Extensions`/`DLNodes`). `--no-update` или `UPDATE_GIT=false` — не тянуть |
|
||||||
| LLM (opt-in) | `none` по умолчанию. Флаги / `LLM_RUNTIME` / меню. Манифесты: `ollama-models.yaml`, `llamacpp-models.yaml`. Порты 17811 / 17812. Prompt-help, не замена SwarmUI |
|
| LLM (opt-in) | `none` по умолчанию. Флаги / `LLM_RUNTIME` / меню. Манифест: `ollama-models.yaml`. Порт 17811. Prompt-help, не замена SwarmUI |
|
||||||
| llm-only | `ENABLE_SWARMUI=false` / `WORKLOAD=llm` / `--no-swarm`/`--llm-only`: GPU + LLM без SwarmUI (bootstrap только data disk) |
|
| llm-only | `ENABLE_SWARMUI=false` / `WORKLOAD=llm` / `--no-swarm`/`--llm-only`: GPU + LLM без SwarmUI (bootstrap только data disk) |
|
||||||
| Capture | `gpu-rent capture` — инвентарь VM → merge **ссылок** в локальные yaml (веса не качать) |
|
| Capture | `gpu-rent capture` — инвентарь VM → merge **ссылок** в локальные yaml (веса не качать) |
|
||||||
| Perf auto-tune | На `up`: probe GPU → tier. Swarm/Comfy: sageattention ExtraArgs на Ampere+ ≥16 GiB. Ollama: flash/KV/keep-alive + `GPU_OVERHEAD` чтобы оставить VRAM под Krea |
|
| Perf auto-tune | На `up`: probe GPU → tier. Swarm/Comfy: sageattention ExtraArgs на Ampere+ ≥16 GiB. Ollama: flash/KV/keep-alive + `GPU_OVERHEAD` чтобы оставить VRAM под Krea |
|
||||||
|
|||||||
+2
-3
@@ -38,7 +38,7 @@ swarmui:
|
|||||||
- url: https://gitea.hsrv.site/mrleo1nid/swarm-assistent.git
|
- url: https://gitea.hsrv.site/mrleo1nid/swarm-assistent.git
|
||||||
ref: main
|
ref: main
|
||||||
dir: swarm-assistent
|
dir: swarm-assistent
|
||||||
requires: ollama # none | ollama | llamacpp | any-llm; default none = always
|
requires: ollama # none | ollama | any-llm; default none = always
|
||||||
- url: https://github.com/example/SwarmUI-SomeExt.git
|
- url: https://github.com/example/SwarmUI-SomeExt.git
|
||||||
ref: main
|
ref: main
|
||||||
dir: SomeExt
|
dir: SomeExt
|
||||||
@@ -51,8 +51,7 @@ comfy:
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `none` (или поле отсутствует) | всегда |
|
| `none` (или поле отсутствует) | всегда |
|
||||||
| `ollama` | только при `LLM_RUNTIME=ollama` |
|
| `ollama` | только при `LLM_RUNTIME=ollama` |
|
||||||
| `llamacpp` | только при `LLM_RUNTIME=llamacpp` |
|
| `any-llm` | при `LLM_RUNTIME=ollama` |
|
||||||
| `any-llm` | при `ollama` или `llamacpp` |
|
|
||||||
|
|
||||||
Строки с несовпавшим `requires` пропускаются (лог), остальные ставятся как обычно. В `extensions.example.yaml` по умолчанию — **swarm-assistent** с `requires: ollama` (чат/vision под Krea 2).
|
Строки с несовпавшим `requires` пропускаются (лог), остальные ставятся как обычно. В `extensions.example.yaml` по умолчанию — **swarm-assistent** с `requires: ollama` (чат/vision под Krea 2).
|
||||||
|
|
||||||
|
|||||||
+10
-63
@@ -1,6 +1,6 @@
|
|||||||
# LLM рядом со SwarmUI (opt-in)
|
# LLM рядом со SwarmUI (opt-in)
|
||||||
|
|
||||||
По умолчанию поднимается **только SwarmUI**. Ollama или llama.cpp — отдельно (помощь с промптами и т.п.).
|
По умолчанию поднимается **только SwarmUI**. Ollama — отдельно (помощь с промптами и т.п.).
|
||||||
|
|
||||||
Интерактивные вопросы — **нумерованные меню** (Enter = вариант со ←). Ключ словом (`recommended`, `keep`) тоже принимается.
|
Интерактивные вопросы — **нумерованные меню** (Enter = вариант со ←). Ключ словом (`recommended`, `keep`) тоже принимается.
|
||||||
|
|
||||||
@@ -9,10 +9,10 @@
|
|||||||
| # | стек | смысл |
|
| # | стек | смысл |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| 1 | SwarmUI | только генерация картинок |
|
| 1 | SwarmUI | только генерация картинок |
|
||||||
| 2 | SwarmUI + LLM | UI + Ollama/llama.cpp на той же карте |
|
| 2 | SwarmUI + LLM | UI + Ollama на той же карте |
|
||||||
| 3 | только LLM | **без** SwarmUI — Ollama или llama.cpp |
|
| 3 | только LLM | **без** SwarmUI — только Ollama |
|
||||||
|
|
||||||
Флаги: `up --yes --llm-only --llamacpp` / `--no-swarm --ollama`. Vars: `ENABLE_SWARMUI=false` или `WORKLOAD=llm`.
|
Флаги: `up --yes --llm-only --ollama` / `--no-swarm --ollama`. Vars: `ENABLE_SWARMUI=false` или `WORKLOAD=llm`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -30,8 +30,8 @@ gpu-rent setup
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
gpu-rent up --yes --ollama
|
gpu-rent up --yes --ollama
|
||||||
gpu-rent up --yes --llm llamacpp
|
gpu-rent up --yes --llm ollama
|
||||||
gpu-rent up --yes --llm-only --llamacpp # без SwarmUI
|
gpu-rent up --yes --llm-only --ollama # без SwarmUI
|
||||||
```
|
```
|
||||||
|
|
||||||
С `--yes` пресеты не спрашивает (берёт существующий yaml / example).
|
С `--yes` пресеты не спрашивает (берёт существующий yaml / example).
|
||||||
@@ -44,7 +44,7 @@ LLM_RUNTIME=ollama
|
|||||||
|
|
||||||
Потом `gpu-rent up --yes`.
|
Потом `gpu-rent up --yes`.
|
||||||
|
|
||||||
Выключить: `LLM_RUNTIME=none` (на `up` старые unit’ы `gpu-rent-ollama` / `gpu-rent-llamacpp` останавливаются).
|
Выключить: `LLM_RUNTIME=none` (на `up` старые LLM unit’ы на VM останавливаются).
|
||||||
|
|
||||||
Голый `gpu-rent` без args — **help**. Двойной клик: `GPU_RENT_DEFAULT_ARGS=up --yes`. Полный doctor на up: `up -v`.
|
Голый `gpu-rent` без args — **help**. Двойной клик: `GPU_RENT_DEFAULT_ARGS=up --yes`. Полный doctor на up: `up -v`.
|
||||||
|
|
||||||
@@ -56,7 +56,6 @@ LLM_RUNTIME=ollama
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| SwarmUI | 7801 | **17801** |
|
| SwarmUI | 7801 | **17801** |
|
||||||
| Ollama | 11434 | **17811** |
|
| Ollama | 11434 | **17811** |
|
||||||
| llama.cpp | 8080 | **17812** |
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
gpu-rent tunnel
|
gpu-rent tunnel
|
||||||
@@ -111,61 +110,12 @@ Unit `gpu-rent-ollama` читает `/mnt/swarm_data/.gpu-rent-gpu.json`:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## llama.cpp
|
|
||||||
|
|
||||||
| Файл | Роль |
|
|
||||||
| --- | --- |
|
|
||||||
| `llamacpp-models.example.yaml` | шаблон |
|
|
||||||
| `llamacpp-models.yaml` | HTTPS URL на `.gguf` (gitignore) |
|
|
||||||
|
|
||||||
На `up`: скачать GGUF → `/mnt/swarm_data/llamacpp/models` → `llama-server` + systemd. Уже скачанные крупные файлы не трогает.
|
|
||||||
|
|
||||||
**Бинарник:** в GitHub Releases **нет** Linux CUDA — только Windows CUDA + Ubuntu CPU/Vulkan. Драйвер/CUDA runtime на VM ≠ готовый `llama-server` с CUDA: его нужно **собрать** (`nvcc`) или взять Vulkan prebuilt.
|
|
||||||
|
|
||||||
Порядок по умолчанию (`LLAMACPP_BACKEND=auto`):
|
|
||||||
|
|
||||||
1. Если на VM уже есть `nvcc` (часто после прошлого `up`) → **CUDA-сборка** (тихо + heartbeat 5–15 мин).
|
|
||||||
2. Иначе → Ubuntu **Vulkan** prebuilt (~30 MB).
|
|
||||||
3. Fallback на другой путь при ошибке.
|
|
||||||
|
|
||||||
Был только Vulkan-stamp, а `nvcc` появился — следующий `up` сам пересоберёт CUDA.
|
|
||||||
|
|
||||||
| Var | Зачем |
|
|
||||||
| --- | --- |
|
|
||||||
| `LLAMACPP_BACKEND=auto\|cuda\|vulkan` | выбор пути (default auto) |
|
|
||||||
| `LLAMACPP_TAG` | pin release (`b10545`) |
|
|
||||||
| `LLAMACPP_ASSET_URL` + `LLAMACPP_SHA256` | свой архив |
|
|
||||||
| `LLAMACPP_BUILD_CUDA=1` | форс CUDA; `=0` — никогда не собирать |
|
|
||||||
| `LLAMACPP_FORCE_REINSTALL=1` | снести бинарь и поставить заново |
|
|
||||||
| `LLAMACPP_NGL` / `LLAMACPP_CTX` | override GPU layers / context |
|
|
||||||
| `LLAMACPP_HOST` / `LLAMACPP_PORT` | bind (default `127.0.0.1:8080`) |
|
|
||||||
| `LLAMACPP_EXTRA_ARGS` | доп. флаги `llama-server` |
|
|
||||||
| `OLLAMA_VERSION` / `OLLAMA_SHA256` | pin Ollama |
|
|
||||||
|
|
||||||
Готовые кейсы A–H — в `gpu-rent.vars.example`.
|
|
||||||
|
|
||||||
### Пресеты (меню)
|
|
||||||
|
|
||||||
| # | ключ | что |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| 1 | **recommended** | Qwen2.5-VL 7B abliterate + mmproj (~4.7+0.8 GB, картинки+RU) |
|
|
||||||
| 2 | light | Qwen2.5 3B Instruct text Q4_K_M |
|
|
||||||
| 3 | text | Qwen2.5 7B abliterate text-only |
|
|
||||||
| 4 | stock | официальный 7B Instruct Q4_K_M |
|
|
||||||
| 5 | empty | только runtime |
|
|
||||||
| — | keep | не трогать yaml |
|
|
||||||
|
|
||||||
`-ngl` / `-c` — по GPU probe. Для recommended abliterate GGUF нужен **`HF_TOKEN`** в `.env` (иначе 401). Вручную: положи GGUF в models и `systemctl restart gpu-rent-llamacpp`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Idle-killer и LLM
|
## Idle-killer и LLM
|
||||||
|
|
||||||
Busy (не гасить GPU):
|
Busy (не гасить GPU):
|
||||||
|
|
||||||
- `ollama pull` (маркер младше ~45 мин; старше сбрасывается);
|
- `ollama pull` (маркер младше ~45 мин; старше сбрасывается);
|
||||||
- загруженная модель в Ollama;
|
- загруженная модель в Ollama.
|
||||||
- llama.cpp: слоты заняты.
|
|
||||||
|
|
||||||
Ошибка установки LLM на `up` — **fail** (не тихий лог).
|
Ошибка установки LLM на `up` — **fail** (не тихий лог).
|
||||||
|
|
||||||
@@ -178,9 +128,6 @@ Busy (не гасить GPU):
|
|||||||
```bash
|
```bash
|
||||||
OLLAMA_VERSION=0.6.5
|
OLLAMA_VERSION=0.6.5
|
||||||
OLLAMA_SHA256=<sha256 of ollama-linux-amd64.tgz>
|
OLLAMA_SHA256=<sha256 of ollama-linux-amd64.tgz>
|
||||||
LLAMACPP_TAG=b10545
|
|
||||||
# или LLAMACPP_ASSET_URL=... + LLAMACPP_SHA256=...
|
|
||||||
# CUDA из исходников (медленно): LLAMACPP_BUILD_CUDA=1
|
|
||||||
# Переустановка бинаря: LLAMACPP_FORCE_REINSTALL=1
|
|
||||||
# VRAM: LLAMACPP_NGL=40 LLAMACPP_CTX=4096 LLAMACPP_EXTRA_ARGS=--flash-attn on
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Готовые кейсы — в `gpu-rent.vars.example`.
|
||||||
|
|||||||
+1
-1
@@ -196,7 +196,7 @@ LOCAL_MODELS_DIR= # пусто = <корень приложения>/Models
|
|||||||
|
|
||||||
## Hugging Face (резерв)
|
## Hugging Face (резерв)
|
||||||
|
|
||||||
- **Скачивание:** `HF_TOKEN` в `.env` → GGUF (llama.cpp) и строки `models.yaml` с `huggingface.co/…/resolve/…`.
|
- **Скачивание:** `HF_TOKEN` в `.env` → строки `models.yaml` с `huggingface.co/…/resolve/…`.
|
||||||
- **Capture:** если Civitai by-hash не нашёл файл → поиск на Hub по имени + LFS sha256 → URL в манифест.
|
- **Capture:** если Civitai by-hash не нашёл файл → поиск на Hub по имени + LFS sha256 → URL в манифест.
|
||||||
- **SwarmUI:** тот же токен прокидывается как `huggingface_api` (Model Downloader).
|
- **SwarmUI:** тот же токен прокидывается как `huggingface_api` (Model Downloader).
|
||||||
- Abliterated / gated репозитории без токена почти всегда дают **401**.
|
- Abliterated / gated репозитории без токена почти всегда дают **401**.
|
||||||
|
|||||||
+2
-2
@@ -49,7 +49,7 @@ chmod +x gpu-rent.sh
|
|||||||
- `env.example` → `.env`
|
- `env.example` → `.env`
|
||||||
- `models.example.yaml` → `models.yaml`
|
- `models.example.yaml` → `models.yaml`
|
||||||
- `extensions.example.yaml` → `extensions.yaml`
|
- `extensions.example.yaml` → `extensions.yaml`
|
||||||
- `ollama-models.example.yaml` / `llamacpp-models.example.yaml` → соответствующие yaml (лаунчер / setup)
|
- `ollama-models.example.yaml` → `ollama-models.yaml` (лаунчер / setup)
|
||||||
- `gpu-rent.vars.example` → `gpu-rent.vars`
|
- `gpu-rent.vars.example` → `gpu-rent.vars`
|
||||||
|
|
||||||
Секреты только в `<repo>/.env` и runtime в `<repo>/.gpu-rent/` (оба в `.gitignore`).
|
Секреты только в `<repo>/.env` и runtime в `<repo>/.gpu-rent/` (оба в `.gitignore`).
|
||||||
@@ -243,7 +243,7 @@ copy models.example.yaml models.yaml
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `--no-tunnel` | только облако; UI потом: `tunnel --open` |
|
| `--no-tunnel` | только облако; UI потом: `tunnel --open` |
|
||||||
| `--no-update` | не `git pull` SwarmUI/extensions |
|
| `--no-update` | не `git pull` SwarmUI/extensions |
|
||||||
| `--ollama` / `--llamacpp` | LLM рядом ([llm.md](llm.md)) |
|
| `--ollama` / `--llm ollama` | LLM рядом ([llm.md](llm.md)) |
|
||||||
| `--no-swarm` / `--llm-only` | только LLM, без SwarmUI (нужен runtime) |
|
| `--no-swarm` / `--llm-only` | только LLM, без SwarmUI (нужен runtime) |
|
||||||
| `--no-spot` | обычный (не preemptible) тариф |
|
| `--no-spot` | обычный (не preemptible) тариф |
|
||||||
| `--flavor ID` | явный flavor (пропускает меню flavor) |
|
| `--flavor ID` | явный flavor (пропускает меню flavor) |
|
||||||
|
|||||||
+1
-1
@@ -108,7 +108,7 @@
|
|||||||
|
|
||||||
| Шаг | OK? |
|
| Шаг | OK? |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Ollama :17811 или llama.cpp :17812 через туннель | |
|
| Ollama :17811 через туннель | |
|
||||||
| pull / GGUF из манифеста | |
|
| pull / GGUF из манифеста | |
|
||||||
| `LLM_RUNTIME=none` гасит unit на следующем up | |
|
| `LLM_RUNTIME=none` гасит unit на следующем up | |
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@
|
|||||||
6. Если есть Civitai-токен и манифест моделей: seed весов **до** первого старта SwarmUI; иначе — дефолтная модель установщика.
|
6. Если есть Civitai-токен и манифест моделей: seed весов **до** первого старта SwarmUI; иначе — дефолтная модель установщика.
|
||||||
7. Push непустых `Models/` / `Wildcards/` / `CustomWorkflows/`.
|
7. Push непустых `Models/` / `Wildcards/` / `CustomWorkflows/`.
|
||||||
8. systemd unit `swarmui`: `./launch-linux.sh --launch_mode none --host 127.0.0.1 --port 7801`.
|
8. systemd unit `swarmui`: `./launch-linux.sh --launch_mode none --host 127.0.0.1 --port 7801`.
|
||||||
9. **GPU probe** → `/mnt/swarm_data/.gpu-rent-gpu.json` (VRAM / compute cap / tier) — до старта UI; Ollama/llama.cpp читают его при install.
|
9. **GPU probe** → `/mnt/swarm_data/.gpu-rent-gpu.json` (VRAM / compute cap / tier) — до старта UI; Ollama читает его при install.
|
||||||
10. Старт SwarmUI → seed LLM → idle-killer → **wait backend Idle**.
|
10. Старт SwarmUI → seed LLM → idle-killer → **wait backend Idle**.
|
||||||
11. **Perf tune после Idle** — `triton`+`sageattention` в Comfy venv и `--use-sage-attention` в `Data/Backends.fds` (маркер `.gpu-rent-perf-tuned`; повтор при смене GPU).
|
11. **Perf tune после Idle** — `triton`+`sageattention` в Comfy venv и `--use-sage-attention` в `Data/Backends.fds` (маркер `.gpu-rent-perf-tuned`; повтор при смене GPU).
|
||||||
12. Авторизация SwarmUI включена, токен в `Data` на диске.
|
12. Авторизация SwarmUI включена, токен в `Data` на диске.
|
||||||
|
|||||||
+4
-15
@@ -38,29 +38,18 @@ AUTOCOMPLETE_GITHUB_REF=main
|
|||||||
AUTOCOMPLETE_FILENAME=danbooru.csv
|
AUTOCOMPLETE_FILENAME=danbooru.csv
|
||||||
|
|
||||||
SWARMUI_LOCAL_PORT=17801
|
SWARMUI_LOCAL_PORT=17801
|
||||||
# Optional LLM beside SwarmUI: none | ollama | llamacpp (setup / --ollama / --llamacpp)
|
# Optional LLM beside SwarmUI: none | ollama (setup / --ollama / --llm ollama)
|
||||||
# ENABLE_SWARMUI=true
|
# ENABLE_SWARMUI=true
|
||||||
# WORKLOAD=llm # llm-only (no SwarmUI); requires LLM_RUNTIME≠none
|
# WORKLOAD=llm # llm-only (no SwarmUI); requires LLM_RUNTIME=ollama
|
||||||
LLM_RUNTIME=none
|
LLM_RUNTIME=none
|
||||||
OLLAMA_LOCAL_PORT=17811
|
OLLAMA_LOCAL_PORT=17811
|
||||||
LLAMACPP_LOCAL_PORT=17812
|
|
||||||
# OLLAMA_MODELS_MANIFEST=
|
# OLLAMA_MODELS_MANIFEST=
|
||||||
# LLAMACPP_MODELS_MANIFEST=
|
# Pin Ollama (несecреты; удобнее в gpu-rent.vars):
|
||||||
# Pin / тонкая настройка LLM (несecреты; удобнее в gpu-rent.vars — см. кейсы A–H):
|
|
||||||
# LLAMACPP_TAG=b10545
|
|
||||||
# LLAMACPP_BACKEND=auto
|
|
||||||
# LLAMACPP_BUILD_CUDA=1
|
|
||||||
# LLAMACPP_FORCE_REINSTALL=1
|
|
||||||
# LLAMACPP_ASSET_URL=
|
|
||||||
# LLAMACPP_SHA256=
|
|
||||||
# LLAMACPP_NGL=40
|
|
||||||
# LLAMACPP_CTX=4096
|
|
||||||
# LLAMACPP_EXTRA_ARGS=--flash-attn on
|
|
||||||
# OLLAMA_VERSION=0.6.5
|
# OLLAMA_VERSION=0.6.5
|
||||||
# OLLAMA_SHA256=
|
# OLLAMA_SHA256=
|
||||||
# CIVITAI_API_TOKEN=
|
# CIVITAI_API_TOKEN=
|
||||||
# CIVITAI_API_HOST=civitai.red
|
# CIVITAI_API_HOST=civitai.red
|
||||||
# Hugging Face (GGUF / gated HF URLs / capture fallback metadata):
|
# Hugging Face (HF URLs in models.yaml / capture fallback metadata):
|
||||||
# HF_TOKEN= # or HUGGING_FACE_HUB_TOKEN — https://huggingface.co/settings/tokens
|
# HF_TOKEN= # or HUGGING_FACE_HUB_TOKEN — https://huggingface.co/settings/tokens
|
||||||
# git pull SwarmUI + extensions on each up (default true). CLI: --no-update
|
# git pull SwarmUI + extensions on each up (default true). CLI: --no-update
|
||||||
UPDATE_GIT=true
|
UPDATE_GIT=true
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
# Empty/missing file → no extra extensions, stock SwarmUI.
|
# Empty/missing file → no extra extensions, stock SwarmUI.
|
||||||
# swarmui = C# repos cloned to src/Extensions
|
# swarmui = C# repos cloned to src/Extensions
|
||||||
# comfy = Python custom nodes cloned to ComfyUI DLNodes
|
# comfy = Python custom nodes cloned to ComfyUI DLNodes
|
||||||
# requires: none (default) | ollama | llamacpp | any-llm
|
# requires: none (default) | ollama | any-llm
|
||||||
# — clone only when LLM_RUNTIME matches (ollama / llamacpp / either)
|
# — clone only when LLM_RUNTIME matches (ollama / either LLM)
|
||||||
|
|
||||||
swarmui:
|
swarmui:
|
||||||
- url: https://gitea.hsrv.site/mrleo1nid/swarm-assistent.git
|
- url: https://gitea.hsrv.site/mrleo1nid/swarm-assistent.git
|
||||||
|
|||||||
@@ -119,7 +119,6 @@ function Copy-IfMissing {
|
|||||||
Copy-IfMissing (Join-Path $Root "models.example.yaml") (Join-Path $Root "models.yaml") "models.yaml"
|
Copy-IfMissing (Join-Path $Root "models.example.yaml") (Join-Path $Root "models.yaml") "models.yaml"
|
||||||
Copy-IfMissing (Join-Path $Root "extensions.example.yaml") (Join-Path $Root "extensions.yaml") "extensions.yaml"
|
Copy-IfMissing (Join-Path $Root "extensions.example.yaml") (Join-Path $Root "extensions.yaml") "extensions.yaml"
|
||||||
Copy-IfMissing (Join-Path $Root "ollama-models.example.yaml") (Join-Path $Root "ollama-models.yaml") "ollama-models.yaml"
|
Copy-IfMissing (Join-Path $Root "ollama-models.example.yaml") (Join-Path $Root "ollama-models.yaml") "ollama-models.yaml"
|
||||||
Copy-IfMissing (Join-Path $Root "llamacpp-models.example.yaml") (Join-Path $Root "llamacpp-models.yaml") "llamacpp-models.yaml"
|
|
||||||
Copy-IfMissing (Join-Path $Root "gpu-rent.vars.example") (Join-Path $Root "gpu-rent.vars") "gpu-rent.vars"
|
Copy-IfMissing (Join-Path $Root "gpu-rent.vars.example") (Join-Path $Root "gpu-rent.vars") "gpu-rent.vars"
|
||||||
|
|
||||||
Import-GpuRentVars (Join-Path $Root "gpu-rent.vars")
|
Import-GpuRentVars (Join-Path $Root "gpu-rent.vars")
|
||||||
|
|||||||
@@ -99,10 +99,6 @@ if [[ ! -f "$ROOT/ollama-models.yaml" && -f "$ROOT/ollama-models.example.yaml" ]
|
|||||||
cp "$ROOT/ollama-models.example.yaml" "$ROOT/ollama-models.yaml"
|
cp "$ROOT/ollama-models.example.yaml" "$ROOT/ollama-models.yaml"
|
||||||
echo "gpu-rent: created ollama-models.yaml"
|
echo "gpu-rent: created ollama-models.yaml"
|
||||||
fi
|
fi
|
||||||
if [[ ! -f "$ROOT/llamacpp-models.yaml" && -f "$ROOT/llamacpp-models.example.yaml" ]]; then
|
|
||||||
cp "$ROOT/llamacpp-models.example.yaml" "$ROOT/llamacpp-models.yaml"
|
|
||||||
echo "gpu-rent: created llamacpp-models.yaml"
|
|
||||||
fi
|
|
||||||
if [[ ! -f "$ROOT/gpu-rent.vars" && -f "$ROOT/gpu-rent.vars.example" ]]; then
|
if [[ ! -f "$ROOT/gpu-rent.vars" && -f "$ROOT/gpu-rent.vars.example" ]]; then
|
||||||
cp "$ROOT/gpu-rent.vars.example" "$ROOT/gpu-rent.vars"
|
cp "$ROOT/gpu-rent.vars.example" "$ROOT/gpu-rent.vars"
|
||||||
echo "gpu-rent: created gpu-rent.vars"
|
echo "gpu-rent: created gpu-rent.vars"
|
||||||
|
|||||||
+8
-43
@@ -31,59 +31,24 @@
|
|||||||
# Подробнее: docs/llm.md
|
# Подробнее: docs/llm.md
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
# --- A) Default: SwarmUI + llama.cpp (auto: CUDA если nvcc на VM, иначе Vulkan) ---
|
# --- A) Default: SwarmUI + Ollama ---
|
||||||
# LLM_RUNTIME=llamacpp
|
# LLM_RUNTIME=ollama
|
||||||
# ENABLE_SWARMUI=true
|
# ENABLE_SWARMUI=true
|
||||||
# LLAMACPP_TAG=b10545
|
# OLLAMA_LOCAL_PORT=17811
|
||||||
# LLAMACPP_BACKEND=auto
|
|
||||||
|
|
||||||
# --- B) Только LLM, без SwarmUI ---
|
# --- B) Только LLM, без SwarmUI ---
|
||||||
# WORKLOAD=llm
|
# WORKLOAD=llm
|
||||||
# LLM_RUNTIME=llamacpp
|
# LLM_RUNTIME=ollama
|
||||||
# LLAMACPP_TAG=b10545
|
|
||||||
|
|
||||||
# --- C) Явно CUDA (и снести старый Vulkan-бинарь) ---
|
# --- C) Pin версии Ollama (supply-chain) ---
|
||||||
# LLM_RUNTIME=llamacpp
|
|
||||||
# LLAMACPP_TAG=b10545
|
|
||||||
# LLAMACPP_BACKEND=cuda
|
|
||||||
# LLAMACPP_FORCE_REINSTALL=1
|
|
||||||
|
|
||||||
# --- C2) Явно быстрый Vulkan, без compile ---
|
|
||||||
# LLM_RUNTIME=llamacpp
|
|
||||||
# LLAMACPP_BACKEND=vulkan
|
|
||||||
# LLAMACPP_BUILD_CUDA=0
|
|
||||||
|
|
||||||
# --- D) Свой бинарь / pin URL (supply-chain) ---
|
|
||||||
# LLM_RUNTIME=llamacpp
|
|
||||||
# LLAMACPP_ASSET_URL=https://github.com/ggml-org/llama.cpp/releases/download/b10545/llama-b10545-bin-ubuntu-vulkan-x64.tar.gz
|
|
||||||
# LLAMACPP_SHA256=<sha256 архива>
|
|
||||||
# LLAMACPP_FORCE_REINSTALL=1
|
|
||||||
|
|
||||||
# --- E) Делим 4090 со SwarmUI: меньше слоёв / короче контекст ---
|
|
||||||
# LLM_RUNTIME=llamacpp
|
|
||||||
# LLAMACPP_NGL=40
|
|
||||||
# LLAMACPP_CTX=4096
|
|
||||||
# LLAMACPP_EXTRA_ARGS=--flash-attn on
|
|
||||||
|
|
||||||
# --- F) Длинный контекст / почти весь VRAM под LLM (llm-only) ---
|
|
||||||
# WORKLOAD=llm
|
|
||||||
# LLM_RUNTIME=llamacpp
|
|
||||||
# LLAMACPP_NGL=99
|
|
||||||
# LLAMACPP_CTX=32768
|
|
||||||
# LLAMACPP_EXTRA_ARGS=--parallel 1
|
|
||||||
|
|
||||||
# --- G) Ollama вместо llama.cpp + pin версии ---
|
|
||||||
# LLM_RUNTIME=ollama
|
# LLM_RUNTIME=ollama
|
||||||
# OLLAMA_VERSION=0.6.5
|
# OLLAMA_VERSION=0.6.5
|
||||||
# OLLAMA_SHA256=<sha256 of ollama-linux-amd64.tgz>
|
# OLLAMA_SHA256=<sha256 of ollama-linux-amd64.tgz>
|
||||||
# OLLAMA_LOCAL_PORT=17811
|
|
||||||
|
|
||||||
# --- H) Выключить LLM (гасит unit’ы на следующем up) ---
|
# --- D) Выключить LLM (гасит unit’ы на следующем up) ---
|
||||||
# LLM_RUNTIME=none
|
# LLM_RUNTIME=none
|
||||||
|
|
||||||
# Порты туннеля (localhost):
|
# Порт туннеля (localhost):
|
||||||
# OLLAMA_LOCAL_PORT=17811
|
# OLLAMA_LOCAL_PORT=17811
|
||||||
# LLAMACPP_LOCAL_PORT=17812
|
# Свой путь к манифесту pull:
|
||||||
# Свой путь к манифесту GGUF/pull:
|
|
||||||
# LLAMACPP_MODELS_MANIFEST=
|
|
||||||
# OLLAMA_MODELS_MANIFEST=
|
# OLLAMA_MODELS_MANIFEST=
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
# Copy to llamacpp-models.yaml (gitignored). Used when LLM_RUNTIME=llamacpp.
|
|
||||||
# url = direct HTTPS link to a .gguf; mmproj = vision projector (Qwen2.5-VL).
|
|
||||||
# Empty models: [] → only llama-server, GGUF клади вручную.
|
|
||||||
# Purpose: prompt-help / vision beside SwarmUI (RU/EN, low refusal).
|
|
||||||
|
|
||||||
models:
|
|
||||||
# Recommended: Qwen2.5-VL 7B abliterate Q4_K_M + mmproj (~4.7 + 0.8 GB)
|
|
||||||
- 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
|
|
||||||
|
|
||||||
# Text-only abliterate 7B (~4.7 GB):
|
|
||||||
# - url: https://huggingface.co/RichardErkhov/huihui-ai_-_Qwen2.5-7B-Instruct-abliterated-gguf/resolve/main/Qwen2.5-7B-Instruct-abliterated.Q4_K_M.gguf
|
|
||||||
|
|
||||||
# Lighter text (~2 GB):
|
|
||||||
# - url: https://huggingface.co/bartowski/Qwen2.5-3B-Instruct-GGUF/resolve/main/Qwen2.5-3B-Instruct-Q4_K_M.gguf
|
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
# 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` (Ollama library / community).
|
||||||
# Purpose: SwarmUI prompt help + vision (RU/EN, low refusal).
|
# Empty models: [] → runtime only, no pull.
|
||||||
|
# Purpose: SwarmUI prompt help — vision + RU/EN on the same GPU as diffusion.
|
||||||
|
|
||||||
models:
|
models:
|
||||||
# Recommended: vision + Russian/English, ~6GB, abliterated
|
# Recommended (~6GB): vision + Russian/English, low refusal
|
||||||
- name: huihui_ai/qwen2.5-vl-abliterated:7b
|
- name: huihui_ai/qwen2.5-vl-abliterated:7b
|
||||||
default: true
|
default: true
|
||||||
|
|
||||||
# Lighter vision:
|
# Alternatives (uncomment / use presets on setup|up):
|
||||||
# - name: huihui_ai/qwen2.5-vl-abliterated:3b
|
# light — huihui_ai/qwen2.5-vl-abliterated:3b (~3GB)
|
||||||
|
# stock — qwen2.5vl:7b (official library)
|
||||||
# Text-only abliterate (~5GB):
|
# text — huihui_ai/qwen2.5-abliterate:7b (no vision, ~5GB)
|
||||||
# - name: huihui_ai/qwen2.5-abliterate:7b
|
# big — qwen2.5vl:32b (~21GB; llm-only)
|
||||||
|
|
||||||
# Official stock (more refusals):
|
|
||||||
# - name: qwen2.5:7b
|
|
||||||
|
|||||||
@@ -71,19 +71,6 @@ def collect_access_links(cfg: Config, *, tunneled: bool) -> list[AccessLink]:
|
|||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
elif runtime == "llamacpp":
|
|
||||||
p = cfg.llamacpp_local_port
|
|
||||||
links.extend(
|
|
||||||
[
|
|
||||||
AccessLink("llama.cpp", f"http://127.0.0.1:{p}", "OpenAI-compatible"),
|
|
||||||
AccessLink(
|
|
||||||
"OpenAI /v1",
|
|
||||||
f"http://127.0.0.1:{p}/v1/chat/completions",
|
|
||||||
"chat completions",
|
|
||||||
),
|
|
||||||
AccessLink("Models", f"http://127.0.0.1:{p}/v1/models", "list"),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
if not links:
|
if not links:
|
||||||
links.append(
|
links.append(
|
||||||
AccessLink(
|
AccessLink(
|
||||||
@@ -138,7 +125,7 @@ def render_access_panel(
|
|||||||
cmds.add_column(style="dim", no_wrap=True)
|
cmds.add_column(style="dim", no_wrap=True)
|
||||||
cmds.add_column()
|
cmds.add_column()
|
||||||
cmds.add_row("открыть UI", "gpu-rent open")
|
cmds.add_row("открыть UI", "gpu-rent open")
|
||||||
if resolve_llm_runtime(cfg) in {"ollama", "llamacpp"}:
|
if resolve_llm_runtime(cfg) == "ollama":
|
||||||
cmds.add_row("открыть LLM", "gpu-rent open --llm")
|
cmds.add_row("открыть LLM", "gpu-rent open --llm")
|
||||||
cmds.add_row("hold killer", "gpu-rent hold")
|
cmds.add_row("hold killer", "gpu-rent hold")
|
||||||
cmds.add_row("стоп GPU", "gpu-rent stop")
|
cmds.add_row("стоп GPU", "gpu-rent stop")
|
||||||
|
|||||||
+15
-95
@@ -335,7 +335,7 @@ def status() -> None:
|
|||||||
rt = resolve_llm_runtime(cfg)
|
rt = resolve_llm_runtime(cfg)
|
||||||
noted = notes.get("llm_runtime")
|
noted = notes.get("llm_runtime")
|
||||||
llm_err = notes.get("llm_error")
|
llm_err = notes.get("llm_error")
|
||||||
detail = f"{rt}; ollama :{cfg.ollama_local_port} / llamacpp :{cfg.llamacpp_local_port}"
|
detail = f"{rt}; ollama :{cfg.ollama_local_port}"
|
||||||
if noted and noted != rt:
|
if noted and noted != rt:
|
||||||
detail += f" (notes: {noted})"
|
detail += f" (notes: {noted})"
|
||||||
if llm_err:
|
if llm_err:
|
||||||
@@ -370,7 +370,7 @@ def status() -> None:
|
|||||||
def open(
|
def open(
|
||||||
llm: bool = typer.Option(False, "--llm", help="Открыть LLM API URL вместо SwarmUI"),
|
llm: bool = typer.Option(False, "--llm", help="Открыть LLM API URL вместо SwarmUI"),
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Открыть браузер на SwarmUI :17801 (или --llm / llm-only на Ollama/llama.cpp)."""
|
"""Открыть браузер на SwarmUI :17801 (или --llm / llm-only на Ollama)."""
|
||||||
cfg = load_config(require_auth=False)
|
cfg = load_config(require_auth=False)
|
||||||
use_llm = llm or not bool(getattr(cfg, "enable_swarmui", True))
|
use_llm = llm or not bool(getattr(cfg, "enable_swarmui", True))
|
||||||
if use_llm:
|
if use_llm:
|
||||||
@@ -379,8 +379,6 @@ def open(
|
|||||||
runtime = resolve_llm_runtime(cfg)
|
runtime = resolve_llm_runtime(cfg)
|
||||||
if runtime == "ollama":
|
if runtime == "ollama":
|
||||||
port = cfg.ollama_local_port
|
port = cfg.ollama_local_port
|
||||||
elif runtime == "llamacpp":
|
|
||||||
port = cfg.llamacpp_local_port
|
|
||||||
else:
|
else:
|
||||||
console.print("[red]LLM не выбран[/red] (LLM_RUNTIME / gpu-rent setup)")
|
console.print("[red]LLM не выбран[/red] (LLM_RUNTIME / gpu-rent setup)")
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
@@ -398,9 +396,9 @@ def open(
|
|||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def setup(
|
def setup(
|
||||||
llm: Optional[str] = typer.Option(None, "--llm", help="none|ollama|llamacpp"),
|
llm: Optional[str] = typer.Option(None, "--llm", help="none|ollama"),
|
||||||
ollama_preset: Optional[str] = typer.Option(
|
ollama_preset: Optional[str] = typer.Option(
|
||||||
None, "--ollama-preset", help="recommended|light|stock|alt|empty"
|
None, "--ollama-preset", help="recommended|light|stock|text|big|empty"
|
||||||
),
|
),
|
||||||
watchdog: Optional[bool] = typer.Option(
|
watchdog: Optional[bool] = typer.Option(
|
||||||
None, "--watchdog/--no-watchdog", help="Поставить local-watchdog"
|
None, "--watchdog/--no-watchdog", help="Поставить local-watchdog"
|
||||||
@@ -466,10 +464,9 @@ def up(
|
|||||||
help="Полный doctor-таблица на up (по умолчанию кратко)",
|
help="Полный doctor-таблица на up (по умолчанию кратко)",
|
||||||
),
|
),
|
||||||
llm: Optional[str] = typer.Option(
|
llm: Optional[str] = typer.Option(
|
||||||
None, "--llm", help="none|ollama|llamacpp (override LLM_RUNTIME)"
|
None, "--llm", help="none|ollama (override LLM_RUNTIME)"
|
||||||
),
|
),
|
||||||
ollama: bool = typer.Option(False, "--ollama", help="То же что --llm ollama"),
|
ollama: bool = typer.Option(False, "--ollama", help="То же что --llm ollama"),
|
||||||
llamacpp: bool = typer.Option(False, "--llamacpp", help="То же что --llm llamacpp"),
|
|
||||||
no_swarm: bool = typer.Option(
|
no_swarm: bool = typer.Option(
|
||||||
False,
|
False,
|
||||||
"--no-swarm",
|
"--no-swarm",
|
||||||
@@ -489,7 +486,7 @@ def up(
|
|||||||
write_ollama_models_preset,
|
write_ollama_models_preset,
|
||||||
)
|
)
|
||||||
from gpu_rent.paths import vars_path
|
from gpu_rent.paths import vars_path
|
||||||
from gpu_rent.prompts import MenuItem, prompt_menu
|
from gpu_rent.prompts import prompt_menu
|
||||||
from gpu_rent.timing import clock_elapsed, clock_reset, format_duration
|
from gpu_rent.timing import clock_elapsed, clock_reset, format_duration
|
||||||
from gpu_rent.varsfile import upsert_vars
|
from gpu_rent.varsfile import upsert_vars
|
||||||
|
|
||||||
@@ -506,7 +503,6 @@ def up(
|
|||||||
runtime = decide_runtime(
|
runtime = decide_runtime(
|
||||||
flag=llm,
|
flag=llm,
|
||||||
ollama_flag=ollama,
|
ollama_flag=ollama,
|
||||||
llamacpp_flag=llamacpp,
|
|
||||||
from_config=cfg.llm_runtime,
|
from_config=cfg.llm_runtime,
|
||||||
)
|
)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
@@ -514,11 +510,10 @@ def up(
|
|||||||
|
|
||||||
enable_swarm = False if no_swarm else cfg.enable_swarmui
|
enable_swarm = False if no_swarm else cfg.enable_swarmui
|
||||||
asked_model_preset = False
|
asked_model_preset = False
|
||||||
llm_flags = bool(llm or ollama or llamacpp or no_swarm)
|
llm_flags = bool(llm or ollama or no_swarm)
|
||||||
|
|
||||||
if not yes and not llm_flags:
|
if not yes and not llm_flags:
|
||||||
from gpu_rent.llm_runtime import (
|
from gpu_rent.llm_runtime import (
|
||||||
llamacpp_preset_menu,
|
|
||||||
ollama_preset_menu,
|
ollama_preset_menu,
|
||||||
workload_menu,
|
workload_menu,
|
||||||
)
|
)
|
||||||
@@ -548,47 +543,11 @@ def up(
|
|||||||
elif stack == "both":
|
elif stack == "both":
|
||||||
enable_swarm = True
|
enable_swarm = True
|
||||||
if runtime == "none":
|
if runtime == "none":
|
||||||
try:
|
runtime = "ollama"
|
||||||
choice = prompt_menu(
|
|
||||||
"LLM runtime",
|
|
||||||
[
|
|
||||||
MenuItem("ollama", "Ollama (+ pull моделей)"),
|
|
||||||
MenuItem("llamacpp", "llama.cpp server (+ GGUF)"),
|
|
||||||
],
|
|
||||||
default="ollama",
|
|
||||||
ask=_ask,
|
|
||||||
show=log,
|
|
||||||
)
|
|
||||||
runtime = decide_runtime(
|
|
||||||
flag=choice,
|
|
||||||
ollama_flag=False,
|
|
||||||
llamacpp_flag=False,
|
|
||||||
from_config="none",
|
|
||||||
)
|
|
||||||
except ValueError as exc:
|
|
||||||
raise GpuRentError(str(exc)) from exc
|
|
||||||
else:
|
else:
|
||||||
enable_swarm = False
|
enable_swarm = False
|
||||||
if runtime == "none":
|
if runtime == "none":
|
||||||
try:
|
runtime = "ollama"
|
||||||
choice = prompt_menu(
|
|
||||||
"LLM runtime",
|
|
||||||
[
|
|
||||||
MenuItem("ollama", "Ollama (+ pull моделей)"),
|
|
||||||
MenuItem("llamacpp", "llama.cpp server (+ GGUF)"),
|
|
||||||
],
|
|
||||||
default="llamacpp",
|
|
||||||
ask=_ask,
|
|
||||||
show=log,
|
|
||||||
)
|
|
||||||
runtime = decide_runtime(
|
|
||||||
flag=choice,
|
|
||||||
ollama_flag=False,
|
|
||||||
llamacpp_flag=False,
|
|
||||||
from_config="none",
|
|
||||||
)
|
|
||||||
except ValueError as exc:
|
|
||||||
raise GpuRentError(str(exc)) from exc
|
|
||||||
|
|
||||||
if typer.confirm("Запомнить стек в gpu-rent.vars?", default=True):
|
if typer.confirm("Запомнить стек в gpu-rent.vars?", default=True):
|
||||||
upsert_vars(
|
upsert_vars(
|
||||||
@@ -614,52 +573,15 @@ def up(
|
|||||||
if preset not in {"keep", "example"}:
|
if preset not in {"keep", "example"}:
|
||||||
write_ollama_models_preset(cfg.ollama_models_manifest, preset)
|
write_ollama_models_preset(cfg.ollama_models_manifest, preset)
|
||||||
asked_model_preset = True
|
asked_model_preset = True
|
||||||
elif runtime == "llamacpp":
|
|
||||||
from gpu_rent.llm_runtime import (
|
|
||||||
ensure_llamacpp_manifest_from_example,
|
|
||||||
write_llamacpp_models_preset,
|
|
||||||
)
|
|
||||||
|
|
||||||
ensure_llamacpp_manifest_from_example()
|
|
||||||
try:
|
|
||||||
preset = prompt_menu(
|
|
||||||
"llama.cpp GGUF",
|
|
||||||
llamacpp_preset_menu(include_keep=False),
|
|
||||||
default="recommended",
|
|
||||||
ask=_ask,
|
|
||||||
show=log,
|
|
||||||
)
|
|
||||||
except ValueError as exc:
|
|
||||||
raise GpuRentError(str(exc)) from exc
|
|
||||||
if preset not in {"keep", "example"}:
|
|
||||||
write_llamacpp_models_preset(cfg.llamacpp_models_manifest, preset)
|
|
||||||
asked_model_preset = True
|
|
||||||
|
|
||||||
# Runtime уже в vars — спросить пресет, default=keep.
|
# Runtime уже в vars — спросить пресет, default=keep.
|
||||||
if not yes and not asked_model_preset and runtime in {"llamacpp", "ollama"}:
|
if not yes and not asked_model_preset and runtime == "ollama":
|
||||||
from gpu_rent.llm_runtime import (
|
from gpu_rent.llm_runtime import ollama_preset_menu
|
||||||
ensure_llamacpp_manifest_from_example,
|
|
||||||
llamacpp_preset_menu,
|
|
||||||
ollama_preset_menu,
|
|
||||||
write_llamacpp_models_preset,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _ask2(msg: str, default: str = "") -> str:
|
def _ask2(msg: str, default: str = "") -> str:
|
||||||
return typer.prompt(msg, default=default)
|
return typer.prompt(msg, default=default)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if runtime == "llamacpp":
|
|
||||||
ensure_llamacpp_manifest_from_example()
|
|
||||||
key = prompt_menu(
|
|
||||||
"llama.cpp GGUF",
|
|
||||||
llamacpp_preset_menu(include_keep=True),
|
|
||||||
default="keep",
|
|
||||||
ask=_ask2,
|
|
||||||
show=log,
|
|
||||||
)
|
|
||||||
if key not in {"keep", "example", ""}:
|
|
||||||
write_llamacpp_models_preset(cfg.llamacpp_models_manifest, key)
|
|
||||||
else:
|
|
||||||
ensure_ollama_manifest_from_example()
|
ensure_ollama_manifest_from_example()
|
||||||
key = prompt_menu(
|
key = prompt_menu(
|
||||||
"Ollama preset",
|
"Ollama preset",
|
||||||
@@ -675,7 +597,7 @@ def up(
|
|||||||
|
|
||||||
if not enable_swarm and runtime == "none":
|
if not enable_swarm and runtime == "none":
|
||||||
raise GpuRentError(
|
raise GpuRentError(
|
||||||
"llm-only требует --ollama / --llamacpp / --llm … "
|
"llm-only требует --ollama / --llm ollama "
|
||||||
"(или убери --no-swarm / ENABLE_SWARMUI=true)"
|
"(или убери --no-swarm / ENABLE_SWARMUI=true)"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -796,7 +718,7 @@ def logs(
|
|||||||
None,
|
None,
|
||||||
"--unit",
|
"--unit",
|
||||||
"-u",
|
"-u",
|
||||||
help="swarm|ollama|llamacpp|killer|cloud-init (по умолчанию — всё)",
|
help="swarm|ollama|killer|cloud-init (по умолчанию — всё)",
|
||||||
),
|
),
|
||||||
lines: int = typer.Option(80, "--lines", "-n", help="Строк journalctl"),
|
lines: int = typer.Option(80, "--lines", "-n", help="Строк journalctl"),
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -812,8 +734,6 @@ def logs(
|
|||||||
"swarm": "swarmui",
|
"swarm": "swarmui",
|
||||||
"swarmui": "swarmui",
|
"swarmui": "swarmui",
|
||||||
"ollama": "ollama",
|
"ollama": "ollama",
|
||||||
"llamacpp": "llamacpp",
|
|
||||||
"llama": "llamacpp",
|
|
||||||
"killer": "gpu-rent-idle-killer",
|
"killer": "gpu-rent-idle-killer",
|
||||||
"idle-killer": "gpu-rent-idle-killer",
|
"idle-killer": "gpu-rent-idle-killer",
|
||||||
"idle": "gpu-rent-idle-killer",
|
"idle": "gpu-rent-idle-killer",
|
||||||
@@ -823,7 +743,7 @@ def logs(
|
|||||||
if key not in aliases:
|
if key not in aliases:
|
||||||
raise GpuRentError(
|
raise GpuRentError(
|
||||||
f"неизвестный --unit={unit!r}; "
|
f"неизвестный --unit={unit!r}; "
|
||||||
"ожидаю: swarm|ollama|llamacpp|killer|cloud-init|all"
|
"ожидаю: swarm|ollama|killer|cloud-init|all"
|
||||||
)
|
)
|
||||||
target = aliases[key]
|
target = aliases[key]
|
||||||
n = max(10, min(int(lines), 500))
|
n = max(10, min(int(lines), 500))
|
||||||
@@ -835,7 +755,7 @@ def logs(
|
|||||||
)
|
)
|
||||||
journal_units = []
|
journal_units = []
|
||||||
if target == "all":
|
if target == "all":
|
||||||
journal_units = ["swarmui", "ollama", "llamacpp", "gpu-rent-idle-killer"]
|
journal_units = ["swarmui", "ollama", "gpu-rent-idle-killer"]
|
||||||
elif target != "cloud-init":
|
elif target != "cloud-init":
|
||||||
journal_units = [target]
|
journal_units = [target]
|
||||||
for ju in journal_units:
|
for ju in journal_units:
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ from gpu_rent.paths import (
|
|||||||
default_ssh_key_path,
|
default_ssh_key_path,
|
||||||
env_path,
|
env_path,
|
||||||
extensions_manifest_path,
|
extensions_manifest_path,
|
||||||
llamacpp_models_manifest_path,
|
|
||||||
migrate_legacy_if_needed,
|
migrate_legacy_if_needed,
|
||||||
models_manifest_path,
|
models_manifest_path,
|
||||||
ollama_models_manifest_path,
|
ollama_models_manifest_path,
|
||||||
@@ -107,9 +106,7 @@ class Config:
|
|||||||
llm_runtime: str
|
llm_runtime: str
|
||||||
enable_swarmui: bool
|
enable_swarmui: bool
|
||||||
ollama_models_manifest: Path
|
ollama_models_manifest: Path
|
||||||
llamacpp_models_manifest: Path
|
|
||||||
ollama_local_port: int
|
ollama_local_port: int
|
||||||
llamacpp_local_port: int
|
|
||||||
|
|
||||||
default_flavor_id: str
|
default_flavor_id: str
|
||||||
flavor_preference: tuple[str, ...]
|
flavor_preference: tuple[str, ...]
|
||||||
@@ -185,10 +182,6 @@ def load_config(*, require_auth: bool = True) -> Config:
|
|||||||
(os.environ.get("OLLAMA_MODELS_MANIFEST") or "").strip()
|
(os.environ.get("OLLAMA_MODELS_MANIFEST") or "").strip()
|
||||||
or str(ollama_models_manifest_path())
|
or str(ollama_models_manifest_path())
|
||||||
).expanduser()
|
).expanduser()
|
||||||
llamacpp_manifest = Path(
|
|
||||||
(os.environ.get("LLAMACPP_MODELS_MANIFEST") or "").strip()
|
|
||||||
or str(llamacpp_models_manifest_path())
|
|
||||||
).expanduser()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
llm_runtime = normalize_runtime(os.environ.get("LLM_RUNTIME"))
|
llm_runtime = normalize_runtime(os.environ.get("LLM_RUNTIME"))
|
||||||
@@ -243,9 +236,7 @@ def load_config(*, require_auth: bool = True) -> Config:
|
|||||||
llm_runtime=llm_runtime,
|
llm_runtime=llm_runtime,
|
||||||
enable_swarmui=enable_swarmui,
|
enable_swarmui=enable_swarmui,
|
||||||
ollama_models_manifest=ollama_manifest,
|
ollama_models_manifest=ollama_manifest,
|
||||||
llamacpp_models_manifest=llamacpp_manifest,
|
|
||||||
ollama_local_port=_as_int(os.environ.get("OLLAMA_LOCAL_PORT"), 17811),
|
ollama_local_port=_as_int(os.environ.get("OLLAMA_LOCAL_PORT"), 17811),
|
||||||
llamacpp_local_port=_as_int(os.environ.get("LLAMACPP_LOCAL_PORT"), 17812),
|
|
||||||
default_flavor_id=(os.environ.get("DEFAULT_FLAVOR_ID") or "").strip(),
|
default_flavor_id=(os.environ.get("DEFAULT_FLAVOR_ID") or "").strip(),
|
||||||
flavor_preference=_csv(
|
flavor_preference=_csv(
|
||||||
os.environ.get("FLAVOR_PREFERENCE"),
|
os.environ.get("FLAVOR_PREFERENCE"),
|
||||||
|
|||||||
+1
-10
@@ -282,7 +282,6 @@ def _civitai(cfg: Config, checks: list[Check]) -> None:
|
|||||||
|
|
||||||
def _huggingface(cfg: Config, checks: list[Check]) -> None:
|
def _huggingface(cfg: Config, checks: list[Check]) -> None:
|
||||||
from gpu_rent.huggingface import is_huggingface_url, probe_whoami
|
from gpu_rent.huggingface import is_huggingface_url, probe_whoami
|
||||||
from gpu_rent.llm_runtime import normalize_runtime, parse_llamacpp_models
|
|
||||||
from gpu_rent.manifests import parse_models
|
from gpu_rent.manifests import parse_models
|
||||||
|
|
||||||
needs_hf = False
|
needs_hf = False
|
||||||
@@ -293,14 +292,6 @@ def _huggingface(cfg: Config, checks: list[Check]) -> None:
|
|||||||
break
|
break
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
try:
|
|
||||||
if normalize_runtime(cfg.llm_runtime) == "llamacpp":
|
|
||||||
for e in parse_llamacpp_models(cfg.llamacpp_models_manifest):
|
|
||||||
if e.url and is_huggingface_url(e.url):
|
|
||||||
needs_hf = True
|
|
||||||
break
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if not cfg.hf_token:
|
if not cfg.hf_token:
|
||||||
checks.append(
|
checks.append(
|
||||||
@@ -309,7 +300,7 @@ def _huggingface(cfg: Config, checks: list[Check]) -> None:
|
|||||||
True,
|
True,
|
||||||
False,
|
False,
|
||||||
(
|
(
|
||||||
"HF_TOKEN нет — gated GGUF / HF в models.yaml дадут 401. "
|
"HF_TOKEN нет — gated HF URL в models.yaml дадут 401. "
|
||||||
"https://huggingface.co/settings/tokens"
|
"https://huggingface.co/settings/tokens"
|
||||||
if needs_hf
|
if needs_hf
|
||||||
else "токена нет (опционально для HF URL / capture fallback)"
|
else "токена нет (опционально для HF URL / capture fallback)"
|
||||||
|
|||||||
+14
-232
@@ -1,39 +1,37 @@
|
|||||||
"""Optional LLM runtimes (Ollama / llama.cpp) beside SwarmUI."""
|
"""Optional LLM runtime (Ollama) beside SwarmUI."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from urllib.parse import unquote, urlparse
|
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from gpu_rent.paths import (
|
from gpu_rent.paths import (
|
||||||
llamacpp_models_example_path,
|
|
||||||
llamacpp_models_manifest_path,
|
|
||||||
ollama_models_example_path,
|
ollama_models_example_path,
|
||||||
ollama_models_manifest_path,
|
ollama_models_manifest_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
VALID_RUNTIMES = frozenset({"none", "ollama", "llamacpp"})
|
VALID_RUNTIMES = frozenset({"none", "ollama"})
|
||||||
|
|
||||||
OLLAMA_PRESETS: dict[str, list[str]] = {
|
OLLAMA_PRESETS: dict[str, list[str]] = {
|
||||||
# Vision + RU/EN + low refusal — best default for swarm-assistent / prompt help with images
|
# Ollama library / community tags only (no GGUF). Tuned for SwarmUI prompt help:
|
||||||
"recommended": ["huihui_ai/qwen2.5-vl-abliterated:7b"],
|
# vision + RU/EN, share VRAM with diffusion on typical 24 GiB.
|
||||||
"light": ["huihui_ai/qwen2.5-vl-abliterated:3b"],
|
"recommended": ["huihui_ai/qwen2.5-vl-abliterated:7b"], # ~6 GB, low refusal
|
||||||
"text": ["huihui_ai/qwen2.5-abliterate:7b"],
|
"light": ["huihui_ai/qwen2.5-vl-abliterated:3b"], # ~3 GB, tight VRAM
|
||||||
"stock": ["qwen2.5:7b"],
|
"stock": ["qwen2.5vl:7b"], # official library vision
|
||||||
"alt": ["richardyoung/qwen2.5-7b-instruct-abliterated"],
|
"text": ["huihui_ai/qwen2.5-abliterate:7b"], # ~5 GB, no vision
|
||||||
|
"big": ["qwen2.5vl:32b"], # ~21 GB — llm-only or ≥40 GiB free
|
||||||
"empty": [],
|
"empty": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
OLLAMA_PRESET_LABELS: dict[str, str] = {
|
OLLAMA_PRESET_LABELS: dict[str, str] = {
|
||||||
"recommended": "Qwen2.5-VL 7B abliterate (картинки+RU, ~6GB)",
|
"recommended": "Qwen2.5-VL 7B abliterate (картинки+RU, ~6GB)",
|
||||||
"light": "Qwen2.5-VL 3B abliterate (vision, быстрее, ~3GB)",
|
"light": "Qwen2.5-VL 3B abliterate (мало VRAM, ~3GB)",
|
||||||
|
"stock": "официальный qwen2.5vl:7b (library, больше отказов)",
|
||||||
"text": "Qwen2.5 7B abliterate text-only (~5GB)",
|
"text": "Qwen2.5 7B abliterate text-only (~5GB)",
|
||||||
"stock": "официальный qwen2.5:7b (больше цензуры)",
|
"big": "qwen2.5vl:32b (~21GB; llm-only / большой GPU)",
|
||||||
"alt": "другой text abliterate-пак 7B",
|
|
||||||
"empty": "только runtime, без pull",
|
"empty": "только runtime, без pull",
|
||||||
"keep": "не менять ollama-models.yaml",
|
"keep": "не менять ollama-models.yaml",
|
||||||
}
|
}
|
||||||
@@ -43,64 +41,9 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Each preset entry: {"url": "...gguf", "mmproj": optional vision projector url}
|
|
||||||
LLAMACPP_PRESETS: dict[str, list[dict[str, str]]] = {
|
|
||||||
"recommended": [
|
|
||||||
{
|
|
||||||
"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": [
|
|
||||||
{
|
|
||||||
"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": [
|
|
||||||
{
|
|
||||||
"url": (
|
|
||||||
"https://huggingface.co/bartowski/Qwen2.5-7B-Instruct-GGUF/"
|
|
||||||
"resolve/main/Qwen2.5-7B-Instruct-Q4_K_M.gguf"
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"empty": [],
|
|
||||||
}
|
|
||||||
|
|
||||||
LLAMACPP_PRESET_LABELS: dict[str, str] = {
|
|
||||||
"recommended": "Qwen2.5-VL 7B abliterate + mmproj (~4.7+0.8GB, картинки+RU)",
|
|
||||||
"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",
|
|
||||||
"empty": "только llama-server, GGUF вручную",
|
|
||||||
"keep": "не менять llamacpp-models.yaml",
|
|
||||||
}
|
|
||||||
|
|
||||||
LLAMACPP_PRESET_HELP = "\n".join(
|
|
||||||
f"{k} — {v}" for k, v in LLAMACPP_PRESET_LABELS.items() if k != "keep"
|
|
||||||
)
|
|
||||||
|
|
||||||
LLM_RUNTIME_LABELS: dict[str, str] = {
|
LLM_RUNTIME_LABELS: dict[str, str] = {
|
||||||
"none": "только SwarmUI",
|
"none": "только SwarmUI",
|
||||||
"ollama": "Ollama (+ pull моделей)",
|
"ollama": "Ollama (+ pull моделей)",
|
||||||
"llamacpp": "llama.cpp server (+ GGUF)",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WORKLOAD_LABELS: dict[str, str] = {
|
WORKLOAD_LABELS: dict[str, str] = {
|
||||||
@@ -113,7 +56,7 @@ WORKLOAD_LABELS: dict[str, str] = {
|
|||||||
def llm_runtime_menu() -> list:
|
def llm_runtime_menu() -> list:
|
||||||
from gpu_rent.prompts import MenuItem
|
from gpu_rent.prompts import MenuItem
|
||||||
|
|
||||||
return [MenuItem(k, f"{k} — {LLM_RUNTIME_LABELS[k]}") for k in ("none", "ollama", "llamacpp")]
|
return [MenuItem(k, f"{k} — {LLM_RUNTIME_LABELS[k]}") for k in ("none", "ollama")]
|
||||||
|
|
||||||
|
|
||||||
def workload_menu() -> list:
|
def workload_menu() -> list:
|
||||||
@@ -131,53 +74,29 @@ def ollama_preset_menu(*, include_keep: bool = False) -> list:
|
|||||||
return [MenuItem(k, OLLAMA_PRESET_LABELS.get(k, k)) for k in keys]
|
return [MenuItem(k, OLLAMA_PRESET_LABELS.get(k, k)) for k in keys]
|
||||||
|
|
||||||
|
|
||||||
def llamacpp_preset_menu(*, include_keep: bool = False) -> list:
|
|
||||||
from gpu_rent.prompts import MenuItem
|
|
||||||
|
|
||||||
keys = list(LLAMACPP_PRESETS.keys())
|
|
||||||
if include_keep:
|
|
||||||
keys.append("keep")
|
|
||||||
return [MenuItem(k, LLAMACPP_PRESET_LABELS.get(k, k)) for k in keys]
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class OllamaModelEntry:
|
class OllamaModelEntry:
|
||||||
name: str
|
name: str
|
||||||
default: bool = False
|
default: bool = False
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class LlamaCppModelEntry:
|
|
||||||
url: str
|
|
||||||
filename: str | None = None
|
|
||||||
mmproj_url: str | None = None
|
|
||||||
default: bool = False
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_runtime(value: str | None) -> str:
|
def normalize_runtime(value: str | None) -> str:
|
||||||
raw = (value or "none").strip().lower().replace("-", "").replace("_", "")
|
raw = (value or "none").strip().lower().replace("-", "").replace("_", "")
|
||||||
if raw in {"", "none", "off", "no", "0"}:
|
if raw in {"", "none", "off", "no", "0"}:
|
||||||
return "none"
|
return "none"
|
||||||
if raw in {"ollama"}:
|
if raw in {"ollama"}:
|
||||||
return "ollama"
|
return "ollama"
|
||||||
if raw in {"llamacpp", "llama", "llamacppserver"}:
|
raise ValueError(f"неизвестный LLM_RUNTIME={value!r}; жду none|ollama")
|
||||||
return "llamacpp"
|
|
||||||
raise ValueError(f"неизвестный LLM_RUNTIME={value!r}; жду none|ollama|llamacpp")
|
|
||||||
|
|
||||||
|
|
||||||
def decide_runtime(
|
def decide_runtime(
|
||||||
*,
|
*,
|
||||||
flag: str | None,
|
flag: str | None,
|
||||||
ollama_flag: bool,
|
ollama_flag: bool,
|
||||||
llamacpp_flag: bool,
|
|
||||||
from_config: str,
|
from_config: str,
|
||||||
) -> str:
|
) -> str:
|
||||||
if ollama_flag and llamacpp_flag:
|
|
||||||
raise ValueError("укажи только --ollama или --llamacpp, не оба")
|
|
||||||
if ollama_flag:
|
if ollama_flag:
|
||||||
return "ollama"
|
return "ollama"
|
||||||
if llamacpp_flag:
|
|
||||||
return "llamacpp"
|
|
||||||
if flag is not None and str(flag).strip() != "":
|
if flag is not None and str(flag).strip() != "":
|
||||||
return normalize_runtime(flag)
|
return normalize_runtime(flag)
|
||||||
return normalize_runtime(from_config)
|
return normalize_runtime(from_config)
|
||||||
@@ -210,65 +129,6 @@ def parse_ollama_models(path: Path) -> list[OllamaModelEntry]:
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def gguf_filename_from_url(url: str) -> str:
|
|
||||||
path = unquote(urlparse(url).path)
|
|
||||||
name = path.rsplit("/", 1)[-1] if path else ""
|
|
||||||
if name.lower().endswith(".gguf"):
|
|
||||||
return name
|
|
||||||
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]:
|
|
||||||
if not path.is_file():
|
|
||||||
return []
|
|
||||||
raw = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
||||||
if not isinstance(raw, dict):
|
|
||||||
return []
|
|
||||||
items = raw.get("models")
|
|
||||||
if items is None:
|
|
||||||
return []
|
|
||||||
if not isinstance(items, list):
|
|
||||||
raise ValueError(f"{path}: models должен быть списком")
|
|
||||||
out: list[LlamaCppModelEntry] = []
|
|
||||||
for item in items:
|
|
||||||
if isinstance(item, str):
|
|
||||||
url = remap_llamacpp_url(item.strip())
|
|
||||||
if url:
|
|
||||||
out.append(LlamaCppModelEntry(url=url))
|
|
||||||
continue
|
|
||||||
if not isinstance(item, dict):
|
|
||||||
continue
|
|
||||||
url = remap_llamacpp_url(str(item.get("url") or "").strip())
|
|
||||||
if not url:
|
|
||||||
continue
|
|
||||||
fname = item.get("filename")
|
|
||||||
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(
|
|
||||||
LlamaCppModelEntry(
|
|
||||||
url=url,
|
|
||||||
filename=filename or None,
|
|
||||||
mmproj_url=mmproj or None,
|
|
||||||
default=bool(item.get("default")),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
def write_ollama_models_preset(path: Path, preset: str) -> None:
|
def write_ollama_models_preset(path: Path, preset: str) -> None:
|
||||||
key = (preset or "recommended").strip().lower()
|
key = (preset or "recommended").strip().lower()
|
||||||
if key not in OLLAMA_PRESETS:
|
if key not in OLLAMA_PRESETS:
|
||||||
@@ -289,32 +149,6 @@ def write_ollama_models_preset(path: Path, preset: str) -> None:
|
|||||||
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
def write_llamacpp_models_preset(path: Path, preset: str) -> None:
|
|
||||||
key = (preset or "recommended").strip().lower()
|
|
||||||
if key not in LLAMACPP_PRESETS:
|
|
||||||
raise ValueError(f"пресет {preset!r}; варианты: {', '.join(LLAMACPP_PRESETS)}")
|
|
||||||
specs = LLAMACPP_PRESETS[key]
|
|
||||||
lines = [
|
|
||||||
"# Локальный манифест llama.cpp GGUF (не коммить). Пример: llamacpp-models.example.yaml",
|
|
||||||
"# url = прямой HTTPS на .gguf; mmproj = projector для vision (Qwen2.5-VL и т.п.).",
|
|
||||||
"models:",
|
|
||||||
]
|
|
||||||
if not specs:
|
|
||||||
lines.append(" []")
|
|
||||||
else:
|
|
||||||
for i, spec in enumerate(specs):
|
|
||||||
url = str(spec.get("url") or "").strip()
|
|
||||||
if not url:
|
|
||||||
continue
|
|
||||||
lines.append(f" - url: {url}")
|
|
||||||
mmproj = str(spec.get("mmproj") or "").strip()
|
|
||||||
if mmproj:
|
|
||||||
lines.append(f" mmproj: {mmproj}")
|
|
||||||
if i == 0:
|
|
||||||
lines.append(" default: true")
|
|
||||||
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_ollama_manifest_from_example() -> Path:
|
def ensure_ollama_manifest_from_example() -> Path:
|
||||||
dest = ollama_models_manifest_path()
|
dest = ollama_models_manifest_path()
|
||||||
if dest.is_file():
|
if dest.is_file():
|
||||||
@@ -327,24 +161,10 @@ def ensure_ollama_manifest_from_example() -> Path:
|
|||||||
return dest
|
return dest
|
||||||
|
|
||||||
|
|
||||||
def ensure_llamacpp_manifest_from_example() -> Path:
|
|
||||||
dest = llamacpp_models_manifest_path()
|
|
||||||
if dest.is_file():
|
|
||||||
return dest
|
|
||||||
example = llamacpp_models_example_path()
|
|
||||||
if example.is_file():
|
|
||||||
dest.write_text(example.read_text(encoding="utf-8"), encoding="utf-8")
|
|
||||||
else:
|
|
||||||
write_llamacpp_models_preset(dest, "recommended")
|
|
||||||
return dest
|
|
||||||
|
|
||||||
|
|
||||||
def llm_local_port(cfg: Any) -> int | None:
|
def llm_local_port(cfg: Any) -> int | None:
|
||||||
runtime = normalize_runtime(getattr(cfg, "llm_runtime", "none"))
|
runtime = normalize_runtime(getattr(cfg, "llm_runtime", "none"))
|
||||||
if runtime == "ollama":
|
if runtime == "ollama":
|
||||||
return int(getattr(cfg, "ollama_local_port", 17811))
|
return int(getattr(cfg, "ollama_local_port", 17811))
|
||||||
if runtime == "llamacpp":
|
|
||||||
return int(getattr(cfg, "llamacpp_local_port", 17812))
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -352,47 +172,9 @@ def llm_remote_port(runtime: str) -> int | None:
|
|||||||
runtime = normalize_runtime(runtime)
|
runtime = normalize_runtime(runtime)
|
||||||
if runtime == "ollama":
|
if runtime == "ollama":
|
||||||
return 11434
|
return 11434
|
||||||
if runtime == "llamacpp":
|
|
||||||
return 8080
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def pick_llamacpp_linux_asset_url(assets: list[dict[str, Any]]) -> str:
|
|
||||||
"""Choose a Linux llama.cpp release asset URL.
|
|
||||||
|
|
||||||
Upstream ships Windows CUDA zips first; never pick win/macos/cudart-only.
|
|
||||||
Prefer ubuntu+cuda → linux+cuda → ubuntu vulkan x64 → ubuntu x64 CPU.
|
|
||||||
Mirrored in remote/install_llamacpp.sh (pick_linux_asset_url).
|
|
||||||
"""
|
|
||||||
cands: list[tuple[int, str]] = []
|
|
||||||
for a in assets:
|
|
||||||
name = str(a.get("name") or "").lower()
|
|
||||||
url = str(a.get("browser_download_url") or "")
|
|
||||||
if not (url.endswith(".zip") or url.endswith(".tar.gz")):
|
|
||||||
continue
|
|
||||||
if any(x in name for x in ("win", "macos", "android", "darwin", "xcframework", "-ui.")):
|
|
||||||
continue
|
|
||||||
if "cudart" in name:
|
|
||||||
continue
|
|
||||||
score = 0
|
|
||||||
if "ubuntu" in name and "x64" in name and "cuda" in name:
|
|
||||||
score = 100
|
|
||||||
elif "linux" in name and "cuda" in name:
|
|
||||||
score = 90
|
|
||||||
elif "ubuntu" in name and "vulkan" in name and "x64" in name:
|
|
||||||
score = 50
|
|
||||||
elif "ubuntu" in name and "x64" in name and not any(
|
|
||||||
x in name for x in ("sycl", "openvino", "arm", "s390", "rocm")
|
|
||||||
):
|
|
||||||
score = 30
|
|
||||||
elif "ubuntu" in name or "linux" in name:
|
|
||||||
score = 10
|
|
||||||
if score:
|
|
||||||
cands.append((score, url))
|
|
||||||
cands.sort(key=lambda t: t[0], reverse=True)
|
|
||||||
return cands[0][1] if cands else ""
|
|
||||||
|
|
||||||
|
|
||||||
def append_vars_llm_runtime(vars_file: Path, runtime: str) -> None:
|
def append_vars_llm_runtime(vars_file: Path, runtime: str) -> None:
|
||||||
from gpu_rent.varsfile import upsert_vars
|
from gpu_rent.varsfile import upsert_vars
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class ModelEntry:
|
|||||||
url: str | None
|
url: str | None
|
||||||
|
|
||||||
|
|
||||||
VALID_REQUIRES = frozenset({"none", "ollama", "llamacpp", "any-llm"})
|
VALID_REQUIRES = frozenset({"none", "ollama", "any-llm"})
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -107,7 +107,7 @@ def normalize_requires(value: object | None) -> str:
|
|||||||
if raw in VALID_REQUIRES:
|
if raw in VALID_REQUIRES:
|
||||||
return raw
|
return raw
|
||||||
raise ConfigError(
|
raise ConfigError(
|
||||||
f"requires={value!r}: жду none|ollama|llamacpp|any-llm"
|
f"requires={value!r}: жду none|ollama|any-llm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ def repo_matches_runtime(repo: GitRepo, llm_runtime: str) -> bool:
|
|||||||
if req == "none":
|
if req == "none":
|
||||||
return True
|
return True
|
||||||
if req == "any-llm":
|
if req == "any-llm":
|
||||||
return runtime in {"ollama", "llamacpp"}
|
return runtime == "ollama"
|
||||||
return runtime == req
|
return runtime == req
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -56,14 +56,6 @@ def ollama_models_example_path() -> Path:
|
|||||||
return app_root() / "ollama-models.example.yaml"
|
return app_root() / "ollama-models.example.yaml"
|
||||||
|
|
||||||
|
|
||||||
def llamacpp_models_manifest_path() -> Path:
|
|
||||||
return app_root() / "llamacpp-models.yaml"
|
|
||||||
|
|
||||||
|
|
||||||
def llamacpp_models_example_path() -> Path:
|
|
||||||
return app_root() / "llamacpp-models.example.yaml"
|
|
||||||
|
|
||||||
|
|
||||||
def extensions_manifest_path() -> Path:
|
def extensions_manifest_path() -> Path:
|
||||||
return app_root() / "extensions.yaml"
|
return app_root() / "extensions.yaml"
|
||||||
|
|
||||||
|
|||||||
@@ -31,19 +31,6 @@ DATA = "/mnt/swarm_data"
|
|||||||
|
|
||||||
# Forwarded to remote install_*.sh (from .env / gpu-rent.vars → os.environ).
|
# Forwarded to remote install_*.sh (from .env / gpu-rent.vars → os.environ).
|
||||||
_OLLAMA_INSTALL_ENV = ("OLLAMA_VERSION", "OLLAMA_SHA256")
|
_OLLAMA_INSTALL_ENV = ("OLLAMA_VERSION", "OLLAMA_SHA256")
|
||||||
_LLAMACPP_INSTALL_ENV = (
|
|
||||||
"LLAMACPP_TAG",
|
|
||||||
"LLAMACPP_ASSET_URL",
|
|
||||||
"LLAMACPP_SHA256",
|
|
||||||
"LLAMACPP_BUILD_CUDA",
|
|
||||||
"LLAMACPP_BACKEND",
|
|
||||||
"LLAMACPP_FORCE_REINSTALL",
|
|
||||||
"LLAMACPP_NGL",
|
|
||||||
"LLAMACPP_CTX",
|
|
||||||
"LLAMACPP_HOST",
|
|
||||||
"LLAMACPP_PORT",
|
|
||||||
"LLAMACPP_EXTRA_ARGS",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _remote_llm_env(cfg: Config, *keys: str) -> dict[str, str]:
|
def _remote_llm_env(cfg: Config, *keys: str) -> dict[str, str]:
|
||||||
@@ -450,7 +437,7 @@ def provision_llm(cfg: Config, host: str, log: Log) -> None:
|
|||||||
check=False,
|
check=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Always drop the other runtime so VRAM is not held by a leftover unit.
|
# Drop LLM units that should not hold VRAM for this runtime.
|
||||||
if runtime == "none":
|
if runtime == "none":
|
||||||
log("LLM: none — останавливаю gpu-rent-ollama / gpu-rent-llamacpp если были")
|
log("LLM: none — останавливаю gpu-rent-ollama / gpu-rent-llamacpp если были")
|
||||||
_stop_units("gpu-rent-ollama", "gpu-rent-llamacpp")
|
_stop_units("gpu-rent-ollama", "gpu-rent-llamacpp")
|
||||||
@@ -490,71 +477,8 @@ def provision_llm(cfg: Config, host: str, log: Log) -> None:
|
|||||||
timeout=7200,
|
timeout=7200,
|
||||||
log=log,
|
log=log,
|
||||||
)
|
)
|
||||||
elif runtime == "llamacpp":
|
|
||||||
_stop_units("gpu-rent-ollama")
|
|
||||||
from gpu_rent.llm_runtime import (
|
|
||||||
gguf_filename_from_url,
|
|
||||||
parse_llamacpp_models,
|
|
||||||
)
|
|
||||||
|
|
||||||
entries = parse_llamacpp_models(cfg.llamacpp_models_manifest)
|
|
||||||
defaults = [e for e in entries if e.default]
|
|
||||||
if defaults:
|
|
||||||
log(
|
|
||||||
"llama.cpp preferred: "
|
|
||||||
f"{defaults[0].filename or gguf_filename_from_url(defaults[0].url)}"
|
|
||||||
)
|
|
||||||
if entries:
|
|
||||||
jobs = []
|
|
||||||
for e in entries:
|
|
||||||
jobs.append(
|
|
||||||
{
|
|
||||||
"url": e.url,
|
|
||||||
"filename": e.filename or gguf_filename_from_url(e.url),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if e.mmproj_url:
|
|
||||||
jobs.append(
|
|
||||||
{
|
|
||||||
"url": e.mmproj_url,
|
|
||||||
"filename": gguf_filename_from_url(e.mmproj_url),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
put_text(
|
|
||||||
cfg, host, "/tmp/gpu-rent-llamacpp-models.json", json.dumps(jobs, indent=2)
|
|
||||||
)
|
|
||||||
hf = (cfg.hf_token or "").strip()
|
|
||||||
if hf:
|
|
||||||
put_text(cfg, host, "/tmp/gpu-rent-hf.token", hf + "\n", mode=0o600)
|
|
||||||
else:
|
else:
|
||||||
log(
|
raise CloudError(f"неизвестный LLM_RUNTIME={runtime!r}")
|
||||||
"⚠ HF_TOKEN не задан — gated GGUF (abliterated и др.) часто дают 401. "
|
|
||||||
"Добавь HF_TOKEN=hf_… в .env → https://huggingface.co/settings/tokens"
|
|
||||||
)
|
|
||||||
log(f"llama.cpp: скачиваю {len(jobs)} GGUF из манифеста")
|
|
||||||
run_python(
|
|
||||||
cfg,
|
|
||||||
host,
|
|
||||||
_pkg_text("llamacpp_fetch.py"),
|
|
||||||
remote_path="/tmp/gpu-rent-llamacpp_fetch.py",
|
|
||||||
timeout=7200,
|
|
||||||
log=log,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
log("llamacpp-models.yaml пуст — GGUF skip (положи вручную)")
|
|
||||||
log("LLM: ставим/запускаем llama.cpp server")
|
|
||||||
import os
|
|
||||||
|
|
||||||
# Vulkan finishes in seconds; CUDA compile needs up to ~15–20 min.
|
|
||||||
run_script_sudo(
|
|
||||||
cfg,
|
|
||||||
host,
|
|
||||||
_pkg_text("install_llamacpp.sh"),
|
|
||||||
remote_path="/tmp/gpu-rent-install_llamacpp.sh",
|
|
||||||
timeout=3600,
|
|
||||||
env=_remote_llm_env(cfg, *_LLAMACPP_INSTALL_ENV),
|
|
||||||
log=log,
|
|
||||||
)
|
|
||||||
st = load_state()
|
st = load_state()
|
||||||
st.notes = dict(st.notes or {})
|
st.notes = dict(st.notes or {})
|
||||||
st.notes["llm_runtime"] = runtime
|
st.notes["llm_runtime"] = runtime
|
||||||
@@ -614,7 +538,7 @@ def provision_vm(
|
|||||||
rt = normalize_runtime(cfg.llm_runtime)
|
rt = normalize_runtime(cfg.llm_runtime)
|
||||||
if not swarm and rt == "none":
|
if not swarm and rt == "none":
|
||||||
raise CloudError(
|
raise CloudError(
|
||||||
"llm-only: нужен LLM_RUNTIME=ollama|llamacpp (или --ollama / --llamacpp)"
|
"llm-only: нужен LLM_RUNTIME=ollama (или --ollama / --llm ollama)"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Arm ASAP so mid-provision failures still leave auto-stop on the VM.
|
# Arm ASAP so mid-provision failures still leave auto-stop on the VM.
|
||||||
@@ -692,6 +616,4 @@ def provision_vm(
|
|||||||
log("SwarmUI слушает 127.0.0.1:7801 — gpu-rent tunnel")
|
log("SwarmUI слушает 127.0.0.1:7801 — gpu-rent tunnel")
|
||||||
if rt == "ollama":
|
if rt == "ollama":
|
||||||
log(f"Ollama API → localhost:{cfg.ollama_local_port} (туннель)")
|
log(f"Ollama API → localhost:{cfg.ollama_local_port} (туннель)")
|
||||||
elif rt == "llamacpp":
|
|
||||||
log(f"llama.cpp → localhost:{cfg.llamacpp_local_port} (туннель)")
|
|
||||||
log("Hold killer: gpu-rent hold | Стоп GPU: gpu-rent stop")
|
log("Hold killer: gpu-rent hold | Стоп GPU: gpu-rent stop")
|
||||||
|
|||||||
+6
-27
@@ -95,7 +95,6 @@ def unit_active(name):
|
|||||||
checks = []
|
checks = []
|
||||||
want_swarm = WANT_SWARM
|
want_swarm = WANT_SWARM
|
||||||
want_ollama = WANT_OLLAMA
|
want_ollama = WANT_OLLAMA
|
||||||
want_llama = WANT_LLAMA
|
|
||||||
|
|
||||||
if want_swarm:
|
if want_swarm:
|
||||||
ok, detail = http_ok("http://127.0.0.1:7801/")
|
ok, detail = http_ok("http://127.0.0.1:7801/")
|
||||||
@@ -131,18 +130,6 @@ if want_ollama:
|
|||||||
"unit": unit_active("gpu-rent-ollama"),
|
"unit": unit_active("gpu-rent-ollama"),
|
||||||
})
|
})
|
||||||
|
|
||||||
if want_llama:
|
|
||||||
ok, detail = http_ok("http://127.0.0.1:8080/health")
|
|
||||||
if not ok:
|
|
||||||
ok2, d2 = http_ok("http://127.0.0.1:8080/v1/models")
|
|
||||||
ok, detail = ok2, d2
|
|
||||||
checks.append({
|
|
||||||
"name": "llamacpp",
|
|
||||||
"ok": ok,
|
|
||||||
"detail": detail,
|
|
||||||
"unit": unit_active("gpu-rent-llamacpp"),
|
|
||||||
})
|
|
||||||
|
|
||||||
print(json.dumps({"checks": checks}, ensure_ascii=False))
|
print(json.dumps({"checks": checks}, ensure_ascii=False))
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -192,18 +179,17 @@ def wait_backend_idle(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _expected_services(cfg: Config) -> tuple[bool, bool, bool]:
|
def _expected_services(cfg: Config) -> tuple[bool, bool]:
|
||||||
swarm = bool(getattr(cfg, "enable_swarmui", True))
|
swarm = bool(getattr(cfg, "enable_swarmui", True))
|
||||||
rt = normalize_runtime(getattr(cfg, "llm_runtime", "none"))
|
rt = normalize_runtime(getattr(cfg, "llm_runtime", "none"))
|
||||||
return swarm, rt == "ollama", rt == "llamacpp"
|
return swarm, rt == "ollama"
|
||||||
|
|
||||||
|
|
||||||
def _probe_vm_once(cfg: Config, host: str) -> list[ServiceCheck]:
|
def _probe_vm_once(cfg: Config, host: str) -> list[ServiceCheck]:
|
||||||
want_swarm, want_ollama, want_llama = _expected_services(cfg)
|
want_swarm, want_ollama = _expected_services(cfg)
|
||||||
script = (
|
script = (
|
||||||
_REMOTE_STACK_PROBE.replace("WANT_SWARM", "True" if want_swarm else "False")
|
_REMOTE_STACK_PROBE.replace("WANT_SWARM", "True" if want_swarm else "False")
|
||||||
.replace("WANT_OLLAMA", "True" if want_ollama else "False")
|
.replace("WANT_OLLAMA", "True" if want_ollama else "False")
|
||||||
.replace("WANT_LLAMA", "True" if want_llama else "False")
|
|
||||||
)
|
)
|
||||||
out = run_ssh(
|
out = run_ssh(
|
||||||
cfg,
|
cfg,
|
||||||
@@ -251,8 +237,8 @@ def verify_stack_on_vm(
|
|||||||
raise_on_fail: bool = True,
|
raise_on_fail: bool = True,
|
||||||
) -> list[ServiceCheck]:
|
) -> list[ServiceCheck]:
|
||||||
"""Poll until every enabled service answers on the VM loopback."""
|
"""Poll until every enabled service answers on the VM loopback."""
|
||||||
want_swarm, want_ollama, want_llama = _expected_services(cfg)
|
want_swarm, want_ollama = _expected_services(cfg)
|
||||||
if not (want_swarm or want_ollama or want_llama):
|
if not (want_swarm or want_ollama):
|
||||||
log("проверка стека: нечего ждать (swarm off, LLM none)")
|
log("проверка стека: нечего ждать (swarm off, LLM none)")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -261,8 +247,6 @@ def verify_stack_on_vm(
|
|||||||
names.append("SwarmUI :7801")
|
names.append("SwarmUI :7801")
|
||||||
if want_ollama:
|
if want_ollama:
|
||||||
names.append("Ollama :11434")
|
names.append("Ollama :11434")
|
||||||
if want_llama:
|
|
||||||
names.append("llama.cpp :8080")
|
|
||||||
log(f"проверка на VM: {', '.join(names)}")
|
log(f"проверка на VM: {', '.join(names)}")
|
||||||
|
|
||||||
deadline = time.time() + timeout
|
deadline = time.time() + timeout
|
||||||
@@ -447,7 +431,7 @@ def verify_stack_local(
|
|||||||
raise_on_fail: bool = True,
|
raise_on_fail: bool = True,
|
||||||
) -> list[ServiceCheck]:
|
) -> list[ServiceCheck]:
|
||||||
"""After tunnel: local ports + light HTTP for enabled services."""
|
"""After tunnel: local ports + light HTTP for enabled services."""
|
||||||
want_swarm, want_ollama, want_llama = _expected_services(cfg)
|
want_swarm, want_ollama = _expected_services(cfg)
|
||||||
targets: list[tuple[str, int, str | None]] = []
|
targets: list[tuple[str, int, str | None]] = []
|
||||||
if want_swarm:
|
if want_swarm:
|
||||||
targets.append(("swarmui", int(cfg.swarmui_local_port), None))
|
targets.append(("swarmui", int(cfg.swarmui_local_port), None))
|
||||||
@@ -459,9 +443,6 @@ def verify_stack_local(
|
|||||||
f"http://127.0.0.1:{cfg.ollama_local_port}/api/tags",
|
f"http://127.0.0.1:{cfg.ollama_local_port}/api/tags",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if want_llama:
|
|
||||||
p = int(cfg.llamacpp_local_port)
|
|
||||||
targets.append(("llamacpp", p, f"http://127.0.0.1:{p}/health"))
|
|
||||||
|
|
||||||
if not targets:
|
if not targets:
|
||||||
return []
|
return []
|
||||||
@@ -481,8 +462,6 @@ def verify_stack_local(
|
|||||||
continue
|
continue
|
||||||
if url:
|
if url:
|
||||||
ok, detail = _http_local(url)
|
ok, detail = _http_local(url)
|
||||||
if not ok and name == "llamacpp":
|
|
||||||
ok, detail = _http_local(f"http://127.0.0.1:{port}/v1/models")
|
|
||||||
last.append(ServiceCheck(name, ok, detail, "local"))
|
last.append(ServiceCheck(name, ok, detail, "local"))
|
||||||
else:
|
else:
|
||||||
ok, detail = _http_local(f"http://127.0.0.1:{port}/")
|
ok, detail = _http_local(f"http://127.0.0.1:{port}/")
|
||||||
|
|||||||
@@ -108,8 +108,7 @@ mkdir -p \
|
|||||||
"${DATA_ROOT}/Extensions" \
|
"${DATA_ROOT}/Extensions" \
|
||||||
"${DATA_ROOT}/DLNodes" \
|
"${DATA_ROOT}/DLNodes" \
|
||||||
"${DATA_ROOT}/CustomWorkflows" \
|
"${DATA_ROOT}/CustomWorkflows" \
|
||||||
"${DATA_ROOT}/ollama" \
|
"${DATA_ROOT}/ollama"
|
||||||
"${DATA_ROOT}/llamacpp/models"
|
|
||||||
|
|
||||||
# LLM-only: data disk + tools, no SwarmUI clone / unit.
|
# LLM-only: data disk + tools, no SwarmUI clone / unit.
|
||||||
if [[ "${GPU_RENT_SKIP_SWARMUI:-0}" == "1" ]]; then
|
if [[ "${GPU_RENT_SKIP_SWARMUI:-0}" == "1" ]]; then
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ def swarm_busy(swarm_url: str, timeout: float = 8.0) -> tuple[bool, str]:
|
|||||||
|
|
||||||
|
|
||||||
def llm_busy(timeout: float = 3.0) -> tuple[bool, str]:
|
def llm_busy(timeout: float = 3.0) -> tuple[bool, str]:
|
||||||
"""Ollama pull / loaded models or llama.cpp with a model count as busy."""
|
"""Ollama pull / loaded models count as busy."""
|
||||||
pull_marker = DATA / ".gpu-rent-ollama-pulling"
|
pull_marker = DATA / ".gpu-rent-ollama-pulling"
|
||||||
if pull_marker.is_file():
|
if pull_marker.is_file():
|
||||||
try:
|
try:
|
||||||
@@ -138,24 +138,6 @@ def llm_busy(timeout: float = 3.0) -> tuple[bool, str]:
|
|||||||
return True, f"ollama running {names}"
|
return True, f"ollama running {names}"
|
||||||
except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError, json.JSONDecodeError, OSError):
|
except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError, json.JSONDecodeError, OSError):
|
||||||
pass
|
pass
|
||||||
# llama.cpp: slots in use
|
|
||||||
try:
|
|
||||||
req = urllib.request.Request("http://127.0.0.1:8080/health", method="GET")
|
|
||||||
with urllib.request.urlopen(req, timeout=timeout, context=ctx) as resp:
|
|
||||||
if getattr(resp, "status", 200) == 200:
|
|
||||||
try:
|
|
||||||
req2 = urllib.request.Request("http://127.0.0.1:8080/props", method="GET")
|
|
||||||
with urllib.request.urlopen(req2, timeout=timeout, context=ctx) as resp2:
|
|
||||||
props = json.loads(resp2.read().decode("utf-8"))
|
|
||||||
total = int(props.get("total_slots") or 0)
|
|
||||||
avail = int(props.get("available_slots") or total)
|
|
||||||
in_use = total - avail if total else 0
|
|
||||||
if in_use > 0:
|
|
||||||
return True, f"llamacpp slots_in_use={in_use}"
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError, OSError):
|
|
||||||
pass
|
|
||||||
return False, "llm idle"
|
return False, "llm idle"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,471 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Install llama-server for OpenAI-compatible API on loopback :8080.
|
|
||||||
#
|
|
||||||
# Default: official Linux release asset (Ubuntu Vulkan — GPU without compile).
|
|
||||||
# CUDA source build only as last resort (or LLAMACPP_BUILD_CUDA=1).
|
|
||||||
# Pin: LLAMACPP_TAG=b10545 LLAMACPP_ASSET_URL=... LLAMACPP_SHA256=...
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SWARM_USER="${SWARM_USER:-ubuntu}"
|
|
||||||
DATA_ROOT="/mnt/swarm_data"
|
|
||||||
LLAMA_ROOT="${DATA_ROOT}/llamacpp"
|
|
||||||
MODELS_DIR="${LLAMA_ROOT}/models"
|
|
||||||
BIN_DIR="${LLAMA_ROOT}/bin"
|
|
||||||
SRC_DIR="${LLAMA_ROOT}/src"
|
|
||||||
STAMP="${BIN_DIR}/.build-id"
|
|
||||||
UNIT="gpu-rent-llamacpp"
|
|
||||||
REPO="https://github.com/ggml-org/llama.cpp.git"
|
|
||||||
API_BASE="https://api.github.com/repos/ggml-org/llama.cpp"
|
|
||||||
|
|
||||||
log() { echo "[gpu-rent-llamacpp] $*" >&2; }
|
|
||||||
|
|
||||||
if [[ "$(id -u)" -ne 0 ]]; then
|
|
||||||
echo "нужен root" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p "$MODELS_DIR" "$BIN_DIR"
|
|
||||||
chown -R "${SWARM_USER}:${SWARM_USER}" "$LLAMA_ROOT"
|
|
||||||
|
|
||||||
SERVER_BIN="${BIN_DIR}/llama-server"
|
|
||||||
LLAMACPP_TAG="${LLAMACPP_TAG:-}"
|
|
||||||
LLAMACPP_ASSET_URL="${LLAMACPP_ASSET_URL:-}"
|
|
||||||
LLAMACPP_SHA256="${LLAMACPP_SHA256:-}"
|
|
||||||
# 1 = force CUDA compile; 0 = never compile (Vulkan/CPU prebuilt only)
|
|
||||||
LLAMACPP_BUILD_CUDA="${LLAMACPP_BUILD_CUDA:-}"
|
|
||||||
# auto | cuda | vulkan — default auto: CUDA if nvcc already on VM, else Vulkan prebuilt
|
|
||||||
LLAMACPP_BACKEND="${LLAMACPP_BACKEND:-auto}"
|
|
||||||
LLAMACPP_FORCE_REINSTALL="${LLAMACPP_FORCE_REINSTALL:-}"
|
|
||||||
LLAMACPP_NGL="${LLAMACPP_NGL:-}"
|
|
||||||
LLAMACPP_CTX="${LLAMACPP_CTX:-}"
|
|
||||||
LLAMACPP_HOST="${LLAMACPP_HOST:-127.0.0.1}"
|
|
||||||
LLAMACPP_PORT="${LLAMACPP_PORT:-8080}"
|
|
||||||
LLAMACPP_EXTRA_ARGS="${LLAMACPP_EXTRA_ARGS:-}"
|
|
||||||
|
|
||||||
have_nvcc() {
|
|
||||||
if command -v nvcc >/dev/null 2>&1; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if [[ -x /usr/local/cuda/bin/nvcc ]]; then
|
|
||||||
export PATH="/usr/local/cuda/bin:${PATH}"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Prefer CUDA when toolkit already present (GPU images / prior up). Vulkan = fast no-compile.
|
|
||||||
want_cuda_build() {
|
|
||||||
case "${LLAMACPP_BUILD_CUDA}" in
|
|
||||||
1|yes|true) return 0 ;;
|
|
||||||
0|no|false) return 1 ;;
|
|
||||||
esac
|
|
||||||
case "${LLAMACPP_BACKEND}" in
|
|
||||||
cuda) return 0 ;;
|
|
||||||
vulkan) return 1 ;;
|
|
||||||
*)
|
|
||||||
if have_nvcc; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
return 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
if [[ "${LLAMACPP_FORCE_REINSTALL}" == "1" ]]; then
|
|
||||||
log "LLAMACPP_FORCE_REINSTALL=1 — удаляю старый бинарь"
|
|
||||||
rm -f "$SERVER_BIN" "$STAMP"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Upgrade path: previous default was Vulkan prebuilt; if nvcc is here, prefer CUDA.
|
|
||||||
if [[ -x "$SERVER_BIN" && -f "$STAMP" && "${LLAMACPP_BACKEND}" != "vulkan" && "${LLAMACPP_BUILD_CUDA}" != "0" ]]; then
|
|
||||||
if grep -q '^asset:' "$STAMP" 2>/dev/null && want_cuda_build; then
|
|
||||||
log "был Vulkan/CPU prebuilt, nvcc есть — пересобираю CUDA (лучше на NVIDIA)"
|
|
||||||
rm -f "$SERVER_BIN" "$STAMP"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Prefer ubuntu CUDA (rare) → vulkan → cpu. Never Windows/macOS/cudart-only.
|
|
||||||
pick_linux_asset_url() {
|
|
||||||
python3 -c '
|
|
||||||
import json,sys
|
|
||||||
data=json.load(sys.stdin)
|
|
||||||
assets=data.get("assets") or []
|
|
||||||
cands=[]
|
|
||||||
for a in assets:
|
|
||||||
n=(a.get("name") or "").lower()
|
|
||||||
u=a.get("browser_download_url") or ""
|
|
||||||
if not (u.endswith(".zip") or u.endswith(".tar.gz")):
|
|
||||||
continue
|
|
||||||
if any(x in n for x in ("win","macos","android","darwin","xcframework","-ui.")):
|
|
||||||
continue
|
|
||||||
if "cudart" in n:
|
|
||||||
continue
|
|
||||||
score=0
|
|
||||||
if "ubuntu" in n and "x64" in n and "cuda" in n:
|
|
||||||
score=100
|
|
||||||
elif "linux" in n and "cuda" in n:
|
|
||||||
score=90
|
|
||||||
elif "ubuntu" in n and "vulkan" in n and "x64" in n:
|
|
||||||
score=50
|
|
||||||
elif "ubuntu" in n and "x64" in n and not any(
|
|
||||||
x in n for x in ("sycl","openvino","arm","s390","rocm")
|
|
||||||
):
|
|
||||||
score=30
|
|
||||||
elif "ubuntu" in n or "linux" in n:
|
|
||||||
score=10
|
|
||||||
if score:
|
|
||||||
cands.append((score, u, n))
|
|
||||||
cands.sort(reverse=True)
|
|
||||||
print(cands[0][1] if cands else "")
|
|
||||||
'
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve_release_tag() {
|
|
||||||
# stdout = tag only (no log lines — callers capture via $())
|
|
||||||
if [[ -n "$LLAMACPP_TAG" ]]; then
|
|
||||||
echo "$LLAMACPP_TAG"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
curl -fsSL "${API_BASE}/releases/latest" | python3 -c \
|
|
||||||
'import json,sys; print(json.load(sys.stdin).get("tag_name") or "")'
|
|
||||||
}
|
|
||||||
|
|
||||||
cuda_architectures() {
|
|
||||||
python3 - <<'PY'
|
|
||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
p = Path("/mnt/swarm_data/.gpu-rent-gpu.json")
|
|
||||||
cap = "8.9"
|
|
||||||
if p.is_file():
|
|
||||||
try:
|
|
||||||
cap = str(json.loads(p.read_text()).get("compute_cap") or cap)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
parts = cap.split(".")
|
|
||||||
try:
|
|
||||||
maj, mnr = int(parts[0]), int(parts[1]) if len(parts) > 1 else 0
|
|
||||||
print(f"{maj}{mnr}")
|
|
||||||
except ValueError:
|
|
||||||
print("89")
|
|
||||||
PY
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_build_deps() {
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
|
||||||
apt-get install -y -qq \
|
|
||||||
cmake build-essential git curl ca-certificates \
|
|
||||||
libcurl4-openssl-dev >/dev/null
|
|
||||||
if command -v nvcc >/dev/null 2>&1; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if [[ -x /usr/local/cuda/bin/nvcc ]]; then
|
|
||||||
export PATH="/usr/local/cuda/bin:${PATH}"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
log "ставлю nvidia-cuda-toolkit (нужен nvcc)…"
|
|
||||||
apt-get install -y -qq nvidia-cuda-toolkit >/dev/null
|
|
||||||
if command -v nvcc >/dev/null 2>&1; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if [[ -x /usr/local/cuda/bin/nvcc ]]; then
|
|
||||||
export PATH="/usr/local/cuda/bin:${PATH}"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_vulkan_runtime() {
|
|
||||||
if ldconfig -p 2>/dev/null | grep -q 'libvulkan\.so'; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
|
||||||
log "ставлю libvulkan1 (для Ubuntu Vulkan prebuilt)…"
|
|
||||||
apt-get install -y -qq libvulkan1 mesa-vulkan-drivers >/dev/null 2>&1 || \
|
|
||||||
apt-get install -y -qq libvulkan1 >/dev/null 2>&1 || true
|
|
||||||
}
|
|
||||||
|
|
||||||
install_from_archive_url() {
|
|
||||||
local url="$1"
|
|
||||||
local tmp kind
|
|
||||||
tmp="$(mktemp -d)"
|
|
||||||
(
|
|
||||||
cd "$tmp"
|
|
||||||
log "скачиваю prebuilt: $url"
|
|
||||||
curl -fL --progress-bar "$url" -o pkg.bin
|
|
||||||
if [[ -n "$LLAMACPP_SHA256" ]]; then
|
|
||||||
echo "${LLAMACPP_SHA256} pkg.bin" | sha256sum -c -
|
|
||||||
else
|
|
||||||
log "WARN: LLAMACPP_SHA256 не задан — checksum skip"
|
|
||||||
fi
|
|
||||||
mkdir -p out
|
|
||||||
# Do NOT grep -i zip — that matches "gzip" and breaks .tar.gz.
|
|
||||||
kind="$(file -b pkg.bin 2>/dev/null || true)"
|
|
||||||
case "$url" in
|
|
||||||
*.zip)
|
|
||||||
apt-get install -y -qq unzip >/dev/null 2>&1 || true
|
|
||||||
unzip -qo pkg.bin -d out
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
if [[ "$kind" == Zip\ archive* ]] || [[ "$kind" == *"Zip archive"* ]]; then
|
|
||||||
apt-get install -y -qq unzip >/dev/null 2>&1 || true
|
|
||||||
unzip -qo pkg.bin -d out
|
|
||||||
else
|
|
||||||
tar -xaf pkg.bin -C out 2>/dev/null \
|
|
||||||
|| tar -xzf pkg.bin -C out 2>/dev/null \
|
|
||||||
|| tar -xf pkg.bin -C out
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
local found
|
|
||||||
found="$(find out -type f -name 'llama-server' | head -n1 || true)"
|
|
||||||
if [[ -z "$found" ]]; then
|
|
||||||
found="$(find out -type f -name 'server' | head -n1 || true)"
|
|
||||||
fi
|
|
||||||
if [[ -z "$found" ]]; then
|
|
||||||
log "в архиве нет llama-server (file says: ${kind:-unknown})"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
install -m 755 "$found" "$SERVER_BIN"
|
|
||||||
# Shared libs next to binary (release tarballs ship .so alongside).
|
|
||||||
find out -type f \( -name '*.so' -o -name '*.so.*' \) -print0 2>/dev/null \
|
|
||||||
| while IFS= read -r -d '' so; do
|
|
||||||
install -m 755 "$so" "${BIN_DIR}/$(basename "$so")"
|
|
||||||
done
|
|
||||||
chown -R "${SWARM_USER}:${SWARM_USER}" "$BIN_DIR"
|
|
||||||
)
|
|
||||||
local rc=$?
|
|
||||||
rm -rf "$tmp"
|
|
||||||
return "$rc"
|
|
||||||
}
|
|
||||||
|
|
||||||
install_linux_release() {
|
|
||||||
local tag="$1"
|
|
||||||
local api url
|
|
||||||
api="${API_BASE}/releases/tags/${tag}"
|
|
||||||
url="$(curl -fsSL "$api" | pick_linux_asset_url)"
|
|
||||||
if [[ -z "$url" ]]; then
|
|
||||||
log "в release ${tag} нет Linux-ассета"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if [[ "$url" == *vulkan* ]]; then
|
|
||||||
log "беру Ubuntu Vulkan prebuilt (GPU без compile; CUDA-сборка — LLAMACPP_BUILD_CUDA=1)"
|
|
||||||
ensure_vulkan_runtime
|
|
||||||
elif [[ "$url" == *cuda* ]]; then
|
|
||||||
log "беру Linux CUDA prebuilt"
|
|
||||||
else
|
|
||||||
log "WARN: Linux prebuilt без GPU backend (CPU) — ${url##*/}"
|
|
||||||
fi
|
|
||||||
if ! install_from_archive_url "$url"; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
echo "asset:${tag}" >"$STAMP"
|
|
||||||
chown "${SWARM_USER}:${SWARM_USER}" "$STAMP"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Quiet CUDA build: no cmake spam; heartbeat every 30s with last %.
|
|
||||||
build_cuda_from_source() {
|
|
||||||
local tag="$1"
|
|
||||||
local arch build_log pid pct line
|
|
||||||
arch="$(cuda_architectures)"
|
|
||||||
build_log="${LLAMA_ROOT}/build-cuda.log"
|
|
||||||
log "крайний случай: сборка CUDA из исходников (tag=${tag}, arch=${arch}, 5–15 мин)…"
|
|
||||||
log "полный лог: ${build_log}"
|
|
||||||
if ! ensure_build_deps; then
|
|
||||||
log "нет nvcc — CUDA-сборку пропускаем"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
log "nvcc $(nvcc --version 2>/dev/null | tail -n1 || echo '?')"
|
|
||||||
|
|
||||||
mkdir -p "$SRC_DIR"
|
|
||||||
export GIT_TERMINAL_PROMPT=0
|
|
||||||
if [[ -d "${SRC_DIR}/.git" ]]; then
|
|
||||||
git -C "$SRC_DIR" -c advice.detachedHead=false fetch --depth 1 origin tag "$tag" 2>>"$build_log" || true
|
|
||||||
if ! git -C "$SRC_DIR" -c advice.detachedHead=false checkout -f "$tag" >>"$build_log" 2>&1; then
|
|
||||||
rm -rf "$SRC_DIR"
|
|
||||||
git -c advice.detachedHead=false clone --depth 1 --branch "$tag" "$REPO" "$SRC_DIR" >>"$build_log" 2>&1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
rm -rf "$SRC_DIR"
|
|
||||||
git -c advice.detachedHead=false clone --depth 1 --branch "$tag" "$REPO" "$SRC_DIR" >>"$build_log" 2>&1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cmake -S "$SRC_DIR" -B "${SRC_DIR}/build" \
|
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
|
||||||
-DGGML_CUDA=ON \
|
|
||||||
-DCMAKE_CUDA_ARCHITECTURES="${arch}" \
|
|
||||||
-DLLAMA_BUILD_SERVER=ON \
|
|
||||||
-DLLAMA_BUILD_UI=OFF \
|
|
||||||
-DLLAMA_USE_PREBUILT_UI=OFF \
|
|
||||||
-DGGML_CCACHE=OFF \
|
|
||||||
>>"$build_log" 2>&1
|
|
||||||
|
|
||||||
# Background build + heartbeat (keeps SSH stream alive without 200 cmake lines).
|
|
||||||
cmake --build "${SRC_DIR}/build" -j"$(nproc)" --target llama-server \
|
|
||||||
>>"$build_log" 2>&1 &
|
|
||||||
pid=$!
|
|
||||||
while kill -0 "$pid" 2>/dev/null; do
|
|
||||||
pct="$(grep -oE '\[[[:space:]]*[0-9]+%\]' "$build_log" 2>/dev/null | tail -n1 || true)"
|
|
||||||
line="$(grep -E 'Building CUDA|Built target|Linking' "$build_log" 2>/dev/null | tail -n1 || true)"
|
|
||||||
if [[ -n "$pct" ]]; then
|
|
||||||
log "сборка CUDA ещё идёт… ${pct}${line:+ · ${line}}"
|
|
||||||
else
|
|
||||||
log "сборка CUDA ещё идёт… (cmake/nvcc, см. build.log)"
|
|
||||||
fi
|
|
||||||
sleep 30
|
|
||||||
done
|
|
||||||
if ! wait "$pid"; then
|
|
||||||
log "сборка упала — хвост ${build_log}:"
|
|
||||||
tail -n 40 "$build_log" >&2 || true
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local built="${SRC_DIR}/build/bin/llama-server"
|
|
||||||
if [[ ! -x "$built" ]]; then
|
|
||||||
log "сборка не дала ${built}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
install -m 755 "$built" "$SERVER_BIN"
|
|
||||||
# CUDA build may need libs from build/bin
|
|
||||||
find "${SRC_DIR}/build/bin" -maxdepth 1 -type f \( -name '*.so' -o -name '*.so.*' \) -print0 2>/dev/null \
|
|
||||||
| while IFS= read -r -d '' so; do
|
|
||||||
install -m 755 "$so" "${BIN_DIR}/$(basename "$so")"
|
|
||||||
done
|
|
||||||
chown -R "${SWARM_USER}:${SWARM_USER}" "$BIN_DIR"
|
|
||||||
echo "cuda:${tag}:${arch}" >"$STAMP"
|
|
||||||
chown "${SWARM_USER}:${SWARM_USER}" "$STAMP"
|
|
||||||
log "CUDA binary → ${SERVER_BIN}"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
normalize_tag() {
|
|
||||||
printf '%s' "$1" | tr -d '\r' | head -n1 | awk 'NF{print; exit}'
|
|
||||||
}
|
|
||||||
|
|
||||||
if [[ -x "$SERVER_BIN" ]]; then
|
|
||||||
log "llama-server уже есть: ${SERVER_BIN}"
|
|
||||||
else
|
|
||||||
if [[ -n "$LLAMACPP_ASSET_URL" ]]; then
|
|
||||||
log "скачиваю по LLAMACPP_ASSET_URL…"
|
|
||||||
install_from_archive_url "$LLAMACPP_ASSET_URL"
|
|
||||||
echo "asset-url" >"$STAMP"
|
|
||||||
chown "${SWARM_USER}:${SWARM_USER}" "$STAMP"
|
|
||||||
else
|
|
||||||
if [[ -z "$LLAMACPP_TAG" ]]; then
|
|
||||||
log "WARN: LLAMACPP_TAG не задан — latest (см. docs/llm.md)"
|
|
||||||
fi
|
|
||||||
tag="$(normalize_tag "$(resolve_release_tag)")"
|
|
||||||
if [[ -z "$tag" || "$tag" == *" "* || "$tag" == *"["* ]]; then
|
|
||||||
log "не удалось определить release tag (got: ${tag:-empty})"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
installed=0
|
|
||||||
if want_cuda_build; then
|
|
||||||
log "backend: CUDA (nvcc есть или LLAMACPP_BACKEND/BUILD_CUDA) — сборка, Vulkan только если упадёт"
|
|
||||||
if build_cuda_from_source "$tag"; then
|
|
||||||
installed=1
|
|
||||||
else
|
|
||||||
log "CUDA-сборка не вышла — fallback на Linux prebuilt (Vulkan/CPU)"
|
|
||||||
if install_linux_release "$tag"; then
|
|
||||||
installed=1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
log "backend: Linux prebuilt (нет nvcc / LLAMACPP_BACKEND=vulkan) — без compile"
|
|
||||||
if install_linux_release "$tag"; then
|
|
||||||
installed=1
|
|
||||||
else
|
|
||||||
log "prebuilt не вышел — крайний случай: CUDA из исходников"
|
|
||||||
if build_cuda_from_source "$tag"; then
|
|
||||||
installed=1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
if [[ "$installed" != "1" ]]; then
|
|
||||||
log "не удалось поставить llama-server"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! -x "$SERVER_BIN" ]]; then
|
|
||||||
log "нет исполняемого ${SERVER_BIN}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Prefer a weights GGUF (skip mmproj), then attach --mmproj if present.
|
|
||||||
MODEL_ARG=""
|
|
||||||
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
|
|
||||||
MODEL_ARG="-m ${FIRST_GGUF}"
|
|
||||||
log "модель ${FIRST_GGUF}"
|
|
||||||
else
|
|
||||||
log "нет GGUF в ${MODELS_DIR} — положи файл вручную и systemctl restart ${UNIT}"
|
|
||||||
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.
|
|
||||||
NGL=99
|
|
||||||
CTX=8192
|
|
||||||
if [[ -f "${DATA_ROOT}/.gpu-rent-gpu.json" ]]; then
|
|
||||||
eval "$(python3 - <<'PY'
|
|
||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
gpu=json.loads(Path("/mnt/swarm_data/.gpu-rent-gpu.json").read_text())
|
|
||||||
vram=int(gpu.get("vram_mib") or 0)
|
|
||||||
gib=vram/1024.0
|
|
||||||
if gib < 16:
|
|
||||||
print("NGL=40"); print("CTX=4096")
|
|
||||||
elif gib < 24:
|
|
||||||
print("NGL=99"); print("CTX=8192")
|
|
||||||
elif gib < 48:
|
|
||||||
print("NGL=99"); print("CTX=16384")
|
|
||||||
else:
|
|
||||||
print("NGL=99"); print("CTX=32768")
|
|
||||||
PY
|
|
||||||
)" || true
|
|
||||||
fi
|
|
||||||
# Explicit overrides from gpu-rent.vars / .env (forwarded by provision).
|
|
||||||
if [[ -n "$LLAMACPP_NGL" ]]; then
|
|
||||||
NGL="$LLAMACPP_NGL"
|
|
||||||
fi
|
|
||||||
if [[ -n "$LLAMACPP_CTX" ]]; then
|
|
||||||
CTX="$LLAMACPP_CTX"
|
|
||||||
fi
|
|
||||||
log "llama.cpp -ngl ${NGL} -c ${CTX} host=${LLAMACPP_HOST} port=${LLAMACPP_PORT}${LLAMACPP_EXTRA_ARGS:+ extra=${LLAMACPP_EXTRA_ARGS}}"
|
|
||||||
|
|
||||||
cat >/etc/systemd/system/${UNIT}.service <<EOF
|
|
||||||
[Unit]
|
|
||||||
Description=gpu-rent llama.cpp server (loopback, GPU-tuned)
|
|
||||||
After=network-online.target local-fs.target
|
|
||||||
Wants=network-online.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
User=${SWARM_USER}
|
|
||||||
Group=${SWARM_USER}
|
|
||||||
WorkingDirectory=${LLAMA_ROOT}
|
|
||||||
Environment=LD_LIBRARY_PATH=${BIN_DIR}
|
|
||||||
ExecStart=${SERVER_BIN} ${MODEL_ARG} ${MMPROJ_ARG} --host ${LLAMACPP_HOST} --port ${LLAMACPP_PORT} -ngl ${NGL} -c ${CTX} ${LLAMACPP_EXTRA_ARGS}
|
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=8
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOF
|
|
||||||
|
|
||||||
systemctl daemon-reload
|
|
||||||
systemctl enable "$UNIT"
|
|
||||||
systemctl restart "$UNIT" || log "unit стартовал с ошибкой (часто нет GGUF) — проверь journalctl -u ${UNIT}"
|
|
||||||
log "ok — http://127.0.0.1:8080 models=${MODELS_DIR}"
|
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Download GGUF files for llama.cpp from a JSON job list. Stdlib only."""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import urllib.error
|
|
||||||
import urllib.parse
|
|
||||||
import urllib.request
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
JOBS = Path("/tmp/gpu-rent-llamacpp-models.json")
|
|
||||||
MODELS_DIR = Path("/mnt/swarm_data/llamacpp/models")
|
|
||||||
TOKEN_FILE = Path("/tmp/gpu-rent-hf.token")
|
|
||||||
|
|
||||||
|
|
||||||
def fmt_bytes(n: float) -> str:
|
|
||||||
n = float(n)
|
|
||||||
for unit, div in (("GB", 1024**3), ("MB", 1024**2), ("KB", 1024), ("B", 1)):
|
|
||||||
if n >= div or unit == "B":
|
|
||||||
if unit == "B":
|
|
||||||
return f"{int(n)}B"
|
|
||||||
return f"{n / div:.1f}{unit}"
|
|
||||||
return f"{n:.0f}B"
|
|
||||||
|
|
||||||
|
|
||||||
def progress_line(
|
|
||||||
label: str,
|
|
||||||
done: int,
|
|
||||||
total: int | None,
|
|
||||||
speed: float,
|
|
||||||
*,
|
|
||||||
width: int = 22,
|
|
||||||
) -> str:
|
|
||||||
if total and total > 0:
|
|
||||||
pct = min(100.0, 100.0 * done / total)
|
|
||||||
filled = int(width * done / total)
|
|
||||||
filled = min(width, max(0, filled))
|
|
||||||
bar = "#" * filled + "-" * (width - filled)
|
|
||||||
return (
|
|
||||||
f"{label} [{bar}] {pct:5.1f}% "
|
|
||||||
f"{fmt_bytes(done)}/{fmt_bytes(total)} {fmt_bytes(speed)}/s"
|
|
||||||
)
|
|
||||||
return f"{label} {fmt_bytes(done)} {fmt_bytes(speed)}/s"
|
|
||||||
|
|
||||||
|
|
||||||
class DownloadProgress:
|
|
||||||
def __init__(self, label: str, total: int | None) -> None:
|
|
||||||
self.label = label
|
|
||||||
self.total = total if total and total > 0 else None
|
|
||||||
self.done = 0
|
|
||||||
self.t0 = time.monotonic()
|
|
||||||
self.last_print = 0.0
|
|
||||||
|
|
||||||
def add(self, n: int) -> None:
|
|
||||||
self.done += n
|
|
||||||
now = time.monotonic()
|
|
||||||
if now - self.last_print < 1.0 and not (
|
|
||||||
self.total is not None and self.done >= self.total
|
|
||||||
):
|
|
||||||
return
|
|
||||||
self.last_print = now
|
|
||||||
self._emit()
|
|
||||||
|
|
||||||
def finish(self) -> None:
|
|
||||||
self._emit(final=True)
|
|
||||||
|
|
||||||
def _emit(self, *, final: bool = False) -> None:
|
|
||||||
elapsed = max(time.monotonic() - self.t0, 0.001)
|
|
||||||
line = progress_line(self.label, self.done, self.total, self.done / elapsed)
|
|
||||||
if final:
|
|
||||||
print(line, flush=True)
|
|
||||||
else:
|
|
||||||
print(line, end="\r", flush=True)
|
|
||||||
|
|
||||||
|
|
||||||
def download(url: str, dest: Path, headers: dict[str, str], *, label: str) -> None:
|
|
||||||
partial = dest.with_suffix(dest.suffix + ".partial")
|
|
||||||
|
|
||||||
class StripAuthRedirect(urllib.request.HTTPRedirectHandler):
|
|
||||||
def redirect_request(self, req, fp, code, msg, headers_resp, newurl):
|
|
||||||
new = urllib.request.HTTPRedirectHandler.redirect_request(
|
|
||||||
self, req, fp, code, msg, headers_resp, newurl
|
|
||||||
)
|
|
||||||
if new is None:
|
|
||||||
return None
|
|
||||||
host = (urllib.parse.urlparse(new.full_url).hostname or "").lower()
|
|
||||||
# Hub needs Bearer; CDN (cdn-lfs.*) is pre-signed — drop Authorization.
|
|
||||||
if host in {"huggingface.co", "hf.co"}:
|
|
||||||
return new
|
|
||||||
return urllib.request.Request(
|
|
||||||
new.full_url, headers={"User-Agent": headers.get("User-Agent", "gpu-rent/1")}
|
|
||||||
)
|
|
||||||
|
|
||||||
opener = urllib.request.build_opener(StripAuthRedirect)
|
|
||||||
req = urllib.request.Request(url, headers=headers)
|
|
||||||
with opener.open(req, timeout=600) as resp, partial.open("wb") as out:
|
|
||||||
cl = resp.headers.get("Content-Length")
|
|
||||||
try:
|
|
||||||
total_n = int(cl) if cl else None
|
|
||||||
except ValueError:
|
|
||||||
total_n = None
|
|
||||||
prog = DownloadProgress(label, total_n)
|
|
||||||
while True:
|
|
||||||
chunk = resp.read(1024 * 1024)
|
|
||||||
if not chunk:
|
|
||||||
break
|
|
||||||
out.write(chunk)
|
|
||||||
prog.add(len(chunk))
|
|
||||||
prog.finish()
|
|
||||||
partial.replace(dest)
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
|
||||||
if TOKEN_FILE.is_file():
|
|
||||||
try:
|
|
||||||
os.environ["HF_TOKEN"] = TOKEN_FILE.read_text(encoding="utf-8").strip()
|
|
||||||
finally:
|
|
||||||
try:
|
|
||||||
TOKEN_FILE.unlink(missing_ok=True)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
if not JOBS.is_file():
|
|
||||||
print("no jobs file")
|
|
||||||
return 1
|
|
||||||
jobs = json.loads(JOBS.read_text(encoding="utf-8"))
|
|
||||||
if not isinstance(jobs, list) or not jobs:
|
|
||||||
print("llamacpp fetch: пустой список — skip")
|
|
||||||
return 0
|
|
||||||
MODELS_DIR.mkdir(parents=True, exist_ok=True)
|
|
||||||
token = (os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN") or "").strip()
|
|
||||||
failed = 0
|
|
||||||
for i, job in enumerate(jobs, 1):
|
|
||||||
if not isinstance(job, dict):
|
|
||||||
continue
|
|
||||||
url = str(job.get("url") or "").strip()
|
|
||||||
name = str(job.get("filename") or "").strip()
|
|
||||||
if not url:
|
|
||||||
continue
|
|
||||||
if not name:
|
|
||||||
name = url.rstrip("/").rsplit("/", 1)[-1] or "model.gguf"
|
|
||||||
dest = MODELS_DIR / name
|
|
||||||
prefix = f"[{i}/{len(jobs)}]"
|
|
||||||
if dest.is_file() and dest.stat().st_size > 1_000_000:
|
|
||||||
print(f"{prefix} уже есть {name} ({fmt_bytes(dest.stat().st_size)})")
|
|
||||||
continue
|
|
||||||
print(f"{prefix} качаю {name}", flush=True)
|
|
||||||
headers = {"User-Agent": "gpu-rent/1"}
|
|
||||||
if token:
|
|
||||||
headers["Authorization"] = f"Bearer {token}"
|
|
||||||
try:
|
|
||||||
download(url, dest, headers, label=f"{prefix} {name}")
|
|
||||||
print(f"{prefix} ok {name} ({fmt_bytes(dest.stat().st_size)})")
|
|
||||||
except (urllib.error.URLError, urllib.error.HTTPError, OSError, TimeoutError) as exc:
|
|
||||||
failed += 1
|
|
||||||
msg = str(exc)
|
|
||||||
if "401" in msg or "403" in msg:
|
|
||||||
if not token:
|
|
||||||
msg += (
|
|
||||||
" — нет HF_TOKEN: добавь в .env "
|
|
||||||
"(https://huggingface.co/settings/tokens) и прими условия репо"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
msg += (
|
|
||||||
" — токен есть, но отказано: проверь scopes / "
|
|
||||||
"Accept license на странице модели"
|
|
||||||
)
|
|
||||||
print(f"FAIL {name}: {msg}")
|
|
||||||
try:
|
|
||||||
dest.with_suffix(dest.suffix + ".partial").unlink(missing_ok=True)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
if failed:
|
|
||||||
return 1
|
|
||||||
print("llamacpp fetch ok")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
@@ -8,21 +8,16 @@ from pathlib import Path
|
|||||||
|
|
||||||
from gpu_rent.llm_runtime import (
|
from gpu_rent.llm_runtime import (
|
||||||
append_vars_llm_runtime,
|
append_vars_llm_runtime,
|
||||||
ensure_llamacpp_manifest_from_example,
|
|
||||||
ensure_ollama_manifest_from_example,
|
ensure_ollama_manifest_from_example,
|
||||||
llamacpp_preset_menu,
|
|
||||||
llm_runtime_menu,
|
llm_runtime_menu,
|
||||||
normalize_runtime,
|
normalize_runtime,
|
||||||
ollama_preset_menu,
|
ollama_preset_menu,
|
||||||
write_llamacpp_models_preset,
|
|
||||||
write_ollama_models_preset,
|
write_ollama_models_preset,
|
||||||
)
|
)
|
||||||
from gpu_rent.paths import (
|
from gpu_rent.paths import (
|
||||||
app_root,
|
app_root,
|
||||||
env_path,
|
env_path,
|
||||||
extensions_manifest_path,
|
extensions_manifest_path,
|
||||||
llamacpp_models_example_path,
|
|
||||||
llamacpp_models_manifest_path,
|
|
||||||
models_manifest_path,
|
models_manifest_path,
|
||||||
ollama_models_example_path,
|
ollama_models_example_path,
|
||||||
ollama_models_manifest_path,
|
ollama_models_manifest_path,
|
||||||
@@ -66,12 +61,6 @@ def run_setup(
|
|||||||
_copy_if_missing(
|
_copy_if_missing(
|
||||||
ollama_models_example_path(), ollama_models_manifest_path(), "ollama-models.yaml", log
|
ollama_models_example_path(), ollama_models_manifest_path(), "ollama-models.yaml", log
|
||||||
)
|
)
|
||||||
_copy_if_missing(
|
|
||||||
llamacpp_models_example_path(),
|
|
||||||
llamacpp_models_manifest_path(),
|
|
||||||
"llamacpp-models.yaml",
|
|
||||||
log,
|
|
||||||
)
|
|
||||||
|
|
||||||
runtime = llm
|
runtime = llm
|
||||||
if runtime is None:
|
if runtime is None:
|
||||||
@@ -107,24 +96,6 @@ def run_setup(
|
|||||||
else:
|
else:
|
||||||
write_ollama_models_preset(ollama_models_manifest_path(), preset)
|
write_ollama_models_preset(ollama_models_manifest_path(), preset)
|
||||||
log(f"ollama-models.yaml пресет={preset}")
|
log(f"ollama-models.yaml пресет={preset}")
|
||||||
elif runtime == "llamacpp":
|
|
||||||
preset = ollama_preset
|
|
||||||
if preset is None and ask:
|
|
||||||
preset = prompt_menu(
|
|
||||||
"llama.cpp GGUF",
|
|
||||||
llamacpp_preset_menu(include_keep=False),
|
|
||||||
default="recommended",
|
|
||||||
ask=ask,
|
|
||||||
show=log,
|
|
||||||
)
|
|
||||||
if preset is None:
|
|
||||||
preset = "recommended"
|
|
||||||
if preset.strip().lower() in {"keep", "example", ""}:
|
|
||||||
ensure_llamacpp_manifest_from_example()
|
|
||||||
log("llamacpp-models.yaml из example")
|
|
||||||
else:
|
|
||||||
write_llamacpp_models_preset(llamacpp_models_manifest_path(), preset)
|
|
||||||
log(f"llamacpp-models.yaml пресет={preset}")
|
|
||||||
|
|
||||||
do_wd = install_watchdog
|
do_wd = install_watchdog
|
||||||
if do_wd is None and confirm:
|
if do_wd is None and confirm:
|
||||||
|
|||||||
@@ -79,8 +79,6 @@ def tunnel_forwards(cfg: Config) -> list[tuple[int, int]]:
|
|||||||
runtime = normalize_runtime(cfg.llm_runtime)
|
runtime = normalize_runtime(cfg.llm_runtime)
|
||||||
if runtime == "ollama":
|
if runtime == "ollama":
|
||||||
pairs.append((cfg.ollama_local_port, 11434))
|
pairs.append((cfg.ollama_local_port, 11434))
|
||||||
elif runtime == "llamacpp":
|
|
||||||
pairs.append((cfg.llamacpp_local_port, 8080))
|
|
||||||
if not pairs:
|
if not pairs:
|
||||||
# Failsafe: at least SwarmUI port so tunnel isn't empty.
|
# Failsafe: at least SwarmUI port so tunnel isn't empty.
|
||||||
pairs.append((cfg.swarmui_local_port, 7801))
|
pairs.append((cfg.swarmui_local_port, 7801))
|
||||||
@@ -189,8 +187,6 @@ def run_tunnel(
|
|||||||
open_url = f"http://127.0.0.1:{cfg.swarmui_local_port}"
|
open_url = f"http://127.0.0.1:{cfg.swarmui_local_port}"
|
||||||
elif runtime == "ollama":
|
elif runtime == "ollama":
|
||||||
open_url = f"http://127.0.0.1:{cfg.ollama_local_port}"
|
open_url = f"http://127.0.0.1:{cfg.ollama_local_port}"
|
||||||
elif runtime == "llamacpp":
|
|
||||||
open_url = f"http://127.0.0.1:{cfg.llamacpp_local_port}"
|
|
||||||
else:
|
else:
|
||||||
open_url = f"http://127.0.0.1:{cfg.swarmui_local_port}"
|
open_url = f"http://127.0.0.1:{cfg.swarmui_local_port}"
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ class _Cfg:
|
|||||||
swarmui_local_port = 17801
|
swarmui_local_port = 17801
|
||||||
llm_runtime = "ollama"
|
llm_runtime = "ollama"
|
||||||
ollama_local_port = 17811
|
ollama_local_port = 17811
|
||||||
llamacpp_local_port = 17812
|
|
||||||
enable_swarmui = True
|
enable_swarmui = True
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from gpu_rent.llm_runtime import (
|
|
||||||
gguf_filename_from_url,
|
|
||||||
parse_llamacpp_models,
|
|
||||||
write_llamacpp_models_preset,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_gguf_filename_from_url():
|
|
||||||
url = (
|
|
||||||
"https://huggingface.co/org/repo/resolve/main/"
|
|
||||||
"Qwen2.5-3B-Instruct-Q4_K_M.gguf"
|
|
||||||
)
|
|
||||||
assert gguf_filename_from_url(url) == "Qwen2.5-3B-Instruct-Q4_K_M.gguf"
|
|
||||||
|
|
||||||
|
|
||||||
def test_write_and_parse_llamacpp_preset(tmp_path: Path):
|
|
||||||
path = tmp_path / "llamacpp-models.yaml"
|
|
||||||
write_llamacpp_models_preset(path, "light")
|
|
||||||
entries = parse_llamacpp_models(path)
|
|
||||||
assert len(entries) == 1
|
|
||||||
assert entries[0].default is True
|
|
||||||
assert "Qwen2.5-3B" in entries[0].url
|
|
||||||
assert entries[0].url.startswith("https://")
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_llamacpp_empty(tmp_path: Path):
|
|
||||||
path = tmp_path / "llamacpp-models.yaml"
|
|
||||||
write_llamacpp_models_preset(path, "empty")
|
|
||||||
assert parse_llamacpp_models(path) == []
|
|
||||||
@@ -15,13 +15,12 @@ def test_parse_enable_swarmui_workload(monkeypatch):
|
|||||||
def test_tunnel_forwards_llm_only(monkeypatch):
|
def test_tunnel_forwards_llm_only(monkeypatch):
|
||||||
class Cfg:
|
class Cfg:
|
||||||
swarmui_local_port = 17801
|
swarmui_local_port = 17801
|
||||||
llm_runtime = "llamacpp"
|
llm_runtime = "ollama"
|
||||||
enable_swarmui = False
|
enable_swarmui = False
|
||||||
ollama_local_port = 17811
|
ollama_local_port = 17811
|
||||||
llamacpp_local_port = 17812
|
|
||||||
|
|
||||||
monkeypatch.setattr("gpu_rent.tunnel.load_state", lambda: type("S", (), {"notes": {}})())
|
monkeypatch.setattr("gpu_rent.tunnel.load_state", lambda: type("S", (), {"notes": {}})())
|
||||||
assert tunnel_forwards(Cfg()) == [(17812, 8080)]
|
assert tunnel_forwards(Cfg()) == [(17811, 11434)]
|
||||||
|
|
||||||
|
|
||||||
def test_access_links_llm_only(monkeypatch):
|
def test_access_links_llm_only(monkeypatch):
|
||||||
@@ -29,10 +28,9 @@ def test_access_links_llm_only(monkeypatch):
|
|||||||
|
|
||||||
class Cfg:
|
class Cfg:
|
||||||
swarmui_local_port = 17801
|
swarmui_local_port = 17801
|
||||||
llm_runtime = "llamacpp"
|
llm_runtime = "ollama"
|
||||||
enable_swarmui = False
|
enable_swarmui = False
|
||||||
ollama_local_port = 17811
|
ollama_local_port = 17811
|
||||||
llamacpp_local_port = 17812
|
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"gpu_rent.access_card.load_state",
|
"gpu_rent.access_card.load_state",
|
||||||
@@ -40,5 +38,5 @@ def test_access_links_llm_only(monkeypatch):
|
|||||||
)
|
)
|
||||||
labels = [x.label for x in collect_access_links(Cfg(), tunneled=True)]
|
labels = [x.label for x in collect_access_links(Cfg(), tunneled=True)]
|
||||||
assert "SwarmUI UI" not in labels
|
assert "SwarmUI UI" not in labels
|
||||||
assert "llama.cpp" in labels
|
assert "Ollama API" in labels
|
||||||
assert mcp_snippet_lines(Cfg())[0].startswith("#")
|
assert mcp_snippet_lines(Cfg())[0].startswith("#")
|
||||||
|
|||||||
@@ -5,10 +5,7 @@ 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,
|
||||||
pick_llamacpp_linux_asset_url,
|
|
||||||
remap_llamacpp_url,
|
|
||||||
write_ollama_models_preset,
|
write_ollama_models_preset,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,22 +13,20 @@ from gpu_rent.llm_runtime import (
|
|||||||
def test_normalize_runtime():
|
def test_normalize_runtime():
|
||||||
assert normalize_runtime(None) == "none"
|
assert normalize_runtime(None) == "none"
|
||||||
assert normalize_runtime("OLLAMA") == "ollama"
|
assert normalize_runtime("OLLAMA") == "ollama"
|
||||||
assert normalize_runtime("llama-cpp") == "llamacpp"
|
with pytest.raises(ValueError):
|
||||||
|
normalize_runtime("llamacpp")
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
normalize_runtime("foo")
|
normalize_runtime("foo")
|
||||||
|
|
||||||
|
|
||||||
def test_decide_runtime_flags_win():
|
def test_decide_runtime_flags_win():
|
||||||
assert (
|
assert (
|
||||||
decide_runtime(flag=None, ollama_flag=True, llamacpp_flag=False, from_config="none")
|
decide_runtime(flag=None, ollama_flag=True, from_config="none") == "ollama"
|
||||||
== "ollama"
|
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
decide_runtime(flag="llamacpp", ollama_flag=False, llamacpp_flag=False, from_config="ollama")
|
decide_runtime(flag="ollama", ollama_flag=False, from_config="none") == "ollama"
|
||||||
== "llamacpp"
|
|
||||||
)
|
)
|
||||||
with pytest.raises(ValueError):
|
assert decide_runtime(flag=None, ollama_flag=False, from_config="ollama") == "ollama"
|
||||||
decide_runtime(flag=None, ollama_flag=True, llamacpp_flag=True, from_config="none")
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_ollama_models(tmp_path: Path):
|
def test_parse_ollama_models(tmp_path: Path):
|
||||||
@@ -60,66 +55,3 @@ def test_write_preset(tmp_path: Path):
|
|||||||
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-vl-abliterated: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
|
|
||||||
|
|
||||||
|
|
||||||
def test_pick_llamacpp_linux_asset_skips_windows_cuda():
|
|
||||||
assets = [
|
|
||||||
{
|
|
||||||
"name": "cudart-llama-bin-win-cuda-12.4-x64.zip",
|
|
||||||
"browser_download_url": "https://example/cudart-win.zip",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "llama-b10545-bin-win-cuda-12.4-x64.zip",
|
|
||||||
"browser_download_url": "https://example/win-cuda.zip",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "llama-b10545-bin-ubuntu-x64.tar.gz",
|
|
||||||
"browser_download_url": "https://example/ubuntu-cpu.tar.gz",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "llama-b10545-bin-ubuntu-vulkan-x64.tar.gz",
|
|
||||||
"browser_download_url": "https://example/ubuntu-vulkan.tar.gz",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
assert pick_llamacpp_linux_asset_url(assets) == "https://example/ubuntu-vulkan.tar.gz"
|
|
||||||
|
|
||||||
|
|
||||||
def test_pick_llamacpp_linux_asset_prefers_ubuntu_cuda():
|
|
||||||
assets = [
|
|
||||||
{
|
|
||||||
"name": "llama-b1-bin-ubuntu-vulkan-x64.tar.gz",
|
|
||||||
"browser_download_url": "https://example/vulkan.tar.gz",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "llama-b1-bin-ubuntu-cuda-12.4-x64.tar.gz",
|
|
||||||
"browser_download_url": "https://example/cuda.tar.gz",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
assert pick_llamacpp_linux_asset_url(assets) == "https://example/cuda.tar.gz"
|
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ def test_extensions_requires_ollama(tmp_path: Path):
|
|||||||
assert repos[1].requires == "none"
|
assert repos[1].requires == "none"
|
||||||
assert repo_matches_runtime(repos[0], "ollama")
|
assert repo_matches_runtime(repos[0], "ollama")
|
||||||
assert not repo_matches_runtime(repos[0], "none")
|
assert not repo_matches_runtime(repos[0], "none")
|
||||||
assert not repo_matches_runtime(repos[0], "llamacpp")
|
|
||||||
assert repo_matches_runtime(repos[1], "none")
|
assert repo_matches_runtime(repos[1], "none")
|
||||||
assert repo_matches_runtime(repos[1], "ollama")
|
assert repo_matches_runtime(repos[1], "ollama")
|
||||||
|
|
||||||
@@ -67,7 +66,6 @@ def test_extensions_requires_any_llm(tmp_path: Path):
|
|||||||
repo = parse_extensions(path)[0]
|
repo = parse_extensions(path)[0]
|
||||||
assert repo.requires == "any-llm"
|
assert repo.requires == "any-llm"
|
||||||
assert repo_matches_runtime(repo, "ollama")
|
assert repo_matches_runtime(repo, "ollama")
|
||||||
assert repo_matches_runtime(repo, "llamacpp")
|
|
||||||
assert not repo_matches_runtime(repo, "none")
|
assert not repo_matches_runtime(repo, "none")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ class _Cfg:
|
|||||||
notify_ready = True
|
notify_ready = True
|
||||||
llm_runtime = "none"
|
llm_runtime = "none"
|
||||||
ollama_local_port = 17811
|
ollama_local_port = 17811
|
||||||
llamacpp_local_port = 17812
|
|
||||||
|
|
||||||
|
|
||||||
def test_ensure_boot_snapshot_skips_existing():
|
def test_ensure_boot_snapshot_skips_existing():
|
||||||
|
|||||||
@@ -1,27 +1,21 @@
|
|||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
from gpu_rent.provision import _LLAMACPP_INSTALL_ENV, _remote_llm_env
|
from gpu_rent.provision import _OLLAMA_INSTALL_ENV, _remote_llm_env
|
||||||
|
|
||||||
|
|
||||||
def test_remote_llm_env_forwards_llamacpp_vars(monkeypatch):
|
def test_remote_llm_env_forwards_ollama_vars(monkeypatch):
|
||||||
monkeypatch.setenv("LLAMACPP_TAG", "b10545")
|
monkeypatch.setenv("OLLAMA_VERSION", "0.6.5")
|
||||||
monkeypatch.setenv("LLAMACPP_BUILD_CUDA", "1")
|
monkeypatch.setenv("OLLAMA_SHA256", "abc123")
|
||||||
monkeypatch.setenv("LLAMACPP_NGL", "40")
|
|
||||||
monkeypatch.setenv("LLAMACPP_ASSET_URL", "https://example/a.tar.gz")
|
|
||||||
monkeypatch.delenv("LLAMACPP_SHA256", raising=False)
|
|
||||||
cfg = SimpleNamespace(ssh_user="ubuntu")
|
cfg = SimpleNamespace(ssh_user="ubuntu")
|
||||||
env = _remote_llm_env(cfg, *_LLAMACPP_INSTALL_ENV)
|
env = _remote_llm_env(cfg, *_OLLAMA_INSTALL_ENV)
|
||||||
assert env["SWARM_USER"] == "ubuntu"
|
assert env["SWARM_USER"] == "ubuntu"
|
||||||
assert env["LLAMACPP_TAG"] == "b10545"
|
assert env["OLLAMA_VERSION"] == "0.6.5"
|
||||||
assert env["LLAMACPP_BUILD_CUDA"] == "1"
|
assert env["OLLAMA_SHA256"] == "abc123"
|
||||||
assert env["LLAMACPP_NGL"] == "40"
|
|
||||||
assert env["LLAMACPP_ASSET_URL"] == "https://example/a.tar.gz"
|
|
||||||
assert "LLAMACPP_SHA256" not in env
|
|
||||||
|
|
||||||
|
|
||||||
def test_remote_llm_env_skips_empty(monkeypatch):
|
def test_remote_llm_env_skips_empty(monkeypatch):
|
||||||
for key in _LLAMACPP_INSTALL_ENV:
|
for key in _OLLAMA_INSTALL_ENV:
|
||||||
monkeypatch.delenv(key, raising=False)
|
monkeypatch.delenv(key, raising=False)
|
||||||
cfg = SimpleNamespace(ssh_user="ubuntu")
|
cfg = SimpleNamespace(ssh_user="ubuntu")
|
||||||
env = _remote_llm_env(cfg, *_LLAMACPP_INSTALL_ENV)
|
env = _remote_llm_env(cfg, *_OLLAMA_INSTALL_ENV)
|
||||||
assert env == {"SWARM_USER": "ubuntu"}
|
assert env == {"SWARM_USER": "ubuntu"}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ def test_prompt_server_plan_with_ranked(monkeypatch, tmp_path):
|
|||||||
def test_upsert_vars(tmp_path):
|
def test_upsert_vars(tmp_path):
|
||||||
path = tmp_path / "gpu-rent.vars"
|
path = tmp_path / "gpu-rent.vars"
|
||||||
path.write_text("# c\nLLM_RUNTIME=none\n", encoding="utf-8")
|
path.write_text("# c\nLLM_RUNTIME=none\n", encoding="utf-8")
|
||||||
upsert_vars(path, {"LLM_RUNTIME": "llamacpp", "DATA_VOLUME_SIZE_GB": "200"})
|
upsert_vars(path, {"LLM_RUNTIME": "ollama", "DATA_VOLUME_SIZE_GB": "200"})
|
||||||
data = parse_vars_file(path)
|
data = parse_vars_file(path)
|
||||||
assert data["LLM_RUNTIME"] == "llamacpp"
|
assert data["LLM_RUNTIME"] == "ollama"
|
||||||
assert data["DATA_VOLUME_SIZE_GB"] == "200"
|
assert data["DATA_VOLUME_SIZE_GB"] == "200"
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ def test_tunnel_forwards_swarm_only(monkeypatch):
|
|||||||
swarmui_local_port = 17801
|
swarmui_local_port = 17801
|
||||||
llm_runtime = "none"
|
llm_runtime = "none"
|
||||||
ollama_local_port = 17811
|
ollama_local_port = 17811
|
||||||
llamacpp_local_port = 17812
|
|
||||||
|
|
||||||
monkeypatch.setattr("gpu_rent.tunnel.load_state", lambda: type("S", (), {"notes": {}})())
|
monkeypatch.setattr("gpu_rent.tunnel.load_state", lambda: type("S", (), {"notes": {}})())
|
||||||
assert tunnel_forwards(Cfg()) == [(17801, 7801)]
|
assert tunnel_forwards(Cfg()) == [(17801, 7801)]
|
||||||
@@ -43,7 +42,6 @@ def test_tunnel_forwards_prefers_cfg_over_stale_notes(monkeypatch):
|
|||||||
swarmui_local_port = 17801
|
swarmui_local_port = 17801
|
||||||
llm_runtime = "none"
|
llm_runtime = "none"
|
||||||
ollama_local_port = 17811
|
ollama_local_port = 17811
|
||||||
llamacpp_local_port = 17812
|
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"gpu_rent.tunnel.load_state",
|
"gpu_rent.tunnel.load_state",
|
||||||
@@ -65,10 +63,10 @@ def test_resolve_llm_notes_only_when_cfg_none(monkeypatch):
|
|||||||
assert resolve_llm_runtime(Cfg()) == "ollama"
|
assert resolve_llm_runtime(Cfg()) == "ollama"
|
||||||
|
|
||||||
class Cfg2:
|
class Cfg2:
|
||||||
llm_runtime = "llamacpp"
|
llm_runtime = "ollama"
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"gpu_rent.access_card.load_state",
|
"gpu_rent.access_card.load_state",
|
||||||
lambda: type("S", (), {"notes": {"llm_runtime": "ollama"}})(),
|
lambda: type("S", (), {"notes": {"llm_runtime": "none"}})(),
|
||||||
)
|
)
|
||||||
assert resolve_llm_runtime(Cfg2()) == "llamacpp"
|
assert resolve_llm_runtime(Cfg2()) == "ollama"
|
||||||
|
|||||||
@@ -7,19 +7,18 @@ class _Cfg:
|
|||||||
llm_runtime = "none"
|
llm_runtime = "none"
|
||||||
swarmui_local_port = 17801
|
swarmui_local_port = 17801
|
||||||
ollama_local_port = 17811
|
ollama_local_port = 17811
|
||||||
llamacpp_local_port = 17812
|
|
||||||
|
|
||||||
|
|
||||||
def test_expected_services_swarm_only():
|
def test_expected_services_swarm_only():
|
||||||
assert _expected_services(_Cfg()) == (True, False, False)
|
assert _expected_services(_Cfg()) == (True, False)
|
||||||
|
|
||||||
|
|
||||||
def test_expected_services_llm_only():
|
def test_expected_services_llm_only():
|
||||||
class C:
|
class C:
|
||||||
enable_swarmui = False
|
enable_swarmui = False
|
||||||
llm_runtime = "llamacpp"
|
llm_runtime = "ollama"
|
||||||
|
|
||||||
assert _expected_services(C()) == (False, False, True)
|
assert _expected_services(C()) == (False, True)
|
||||||
|
|
||||||
|
|
||||||
def test_verify_stack_local_empty_when_nothing():
|
def test_verify_stack_local_empty_when_nothing():
|
||||||
@@ -28,7 +27,6 @@ def test_verify_stack_local_empty_when_nothing():
|
|||||||
llm_runtime = "none"
|
llm_runtime = "none"
|
||||||
swarmui_local_port = 17801
|
swarmui_local_port = 17801
|
||||||
ollama_local_port = 17811
|
ollama_local_port = 17811
|
||||||
llamacpp_local_port = 17812
|
|
||||||
|
|
||||||
logs: list[str] = []
|
logs: list[str] = []
|
||||||
assert verify_stack_local(C(), logs.append, timeout=0.1) == []
|
assert verify_stack_local(C(), logs.append, timeout=0.1) == []
|
||||||
@@ -40,7 +38,6 @@ def test_verify_stack_local_fails_closed_port(monkeypatch):
|
|||||||
llm_runtime = "ollama"
|
llm_runtime = "ollama"
|
||||||
swarmui_local_port = 17801
|
swarmui_local_port = 17801
|
||||||
ollama_local_port = 17999
|
ollama_local_port = 17999
|
||||||
llamacpp_local_port = 17812
|
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"gpu_rent.ready._tcp_ok", lambda port, host="127.0.0.1", timeout=0.8: False
|
"gpu_rent.ready._tcp_ok", lambda port, host="127.0.0.1", timeout=0.8: False
|
||||||
@@ -157,7 +154,7 @@ def test_verify_gpu_env_llm_only_skips_torch_requirement(monkeypatch):
|
|||||||
|
|
||||||
class C:
|
class C:
|
||||||
enable_swarmui = False
|
enable_swarmui = False
|
||||||
llm_runtime = "llamacpp"
|
llm_runtime = "ollama"
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"ok": True,
|
"ok": True,
|
||||||
|
|||||||
Reference in New Issue
Block a user