Update extensions and documentation for LLM integration and CLI enhancements

- Added support for a new extension, `swarm-assistent`, in `extensions.example.yaml` with a requirement for `ollama`.
- Enhanced the README.md to clarify the setup process and provide a quick start guide for using extensions.
- Updated documentation in `llm.md` to reflect the opt-in nature of LLM support and provide clearer instructions for enabling it.
- Improved the `autocomplete.md` to detail the automatic setup of word lists during the initial launch.
- Revised `cli.md` to include new commands and options related to LLM runtime handling and extension management.
- Enhanced the `spike-notes.md` to guide users through the first live run with a focus on LLM integration.
This commit is contained in:
Leonid Pershin
2026-08-21 05:49:34 +03:00
parent dc1fde9e3e
commit 71f4e4c2e3
19 changed files with 713 additions and 394 deletions
+62 -55
View File
@@ -1,75 +1,81 @@
# gpu-rent # gpu-rent
Прерываемый GPU в [Selectel](https://selectel.ru) + SwarmUI на `http://127.0.0.1:17801`. Прерываемый GPU в [Selectel](https://selectel.ru) + [SwarmUI](https://github.com/mcmonkeyprojects/SwarmUI) на `http://127.0.0.1:17801`.
Модели и ComfyUI живут на сетевом диске — платишь за GPU только пока он поднят.
**Сначала ключи и квота GPU, не `up`.** → [`docs/setup.md`](docs/setup.md) Модели и ComfyUI живут на сетевом диске. **GPU платится только пока жив compute** (`up``stop` или idle-killer). Диск — **всегда**, даже после `stop`.
--- ---
## Быстрый старт ## С нуля → первая картинка
```powershell Делай по порядку. Подробности и скрины панели — в [docs/setup.md](docs/setup.md).
.\gpu-rent.ps1 doctor # preflight
.\gpu-rent.ps1 flavors # что выберет фоллбек | # | Шаг | Команда / действие |
.\gpu-rent.ps1 dry-run # план без create | --- | --- | --- |
.\gpu-rent.ps1 up --yes # GPU + SwarmUI + seed + туннель :17801 | 1 | Python 3.11+, клон репо | — |
.\gpu-rent.ps1 up --yes --no-tunnel # только облако | 2 | Квота GPU в Selectel (> 0) или тикет | [setup §2](docs/setup.md#2-selectel-проект-и-квота-gpu) |
.\gpu-rent.ps1 tunnel --open # снова открыть UI, если туннель закрыли | 3 | Сервисный пользователь + `.env` из RC | [setup §3](docs/setup.md#3-ключи-openstack-это-и-есть-api-key-для-gpu-rent) |
.\gpu-rent.ps1 hold # отложить idle-killer | 4 | (Опц.) Civitai token + `models.yaml` | [setup §4](docs/setup.md#4-civitai-api-token-модели) |
.\gpu-rent.ps1 stop # гасить compute, диски оставить | 5 | Preflight | `.\gpu-rent.ps1 doctor` |
``` | 6 | Поднять GPU + туннель | `.\gpu-rent.ps1 up --yes` |
| 7 | Открыть UI | браузер сам / `.\gpu-rent.ps1 open` |
| 8 | Закончить сессию | `.\gpu-rent.ps1 stop` |
Unix: `./gpu-rent.sh …` (один раз `chmod +x gpu-rent.sh`). Unix: `./gpu-rent.sh …` (один раз `chmod +x gpu-rent.sh`).
Лаунчер сам создаёт `.venv` и ставит пакет. Конфиг — **в корне репо**: **Не начинай с `up`**, пока `doctor` не зелёный. Ключ панели `X-Token` для этого CLI **не подходит**.
| Файл | Что |
| --- | --- |
| `.env` | из `env.example``OS_*`, Civitai |
| `models.yaml` | из `models.example.yaml` — seed с Civitai |
| `extensions.yaml` | из `extensions.example.yaml` |
| `.gpu-rent/` | state, SSH-ключ (gitignore) |
--- ---
## Команды ## Обычный день
```powershell
.\gpu-rent.ps1 up --yes # GPU + SwarmUI + туннель :17801
# работаешь в браузере…
# Ctrl+C в терминале = только туннель off, GPU жив
.\gpu-rent.ps1 tunnel --open # снова UI
.\gpu-rent.ps1 hold # отложить idle-killer
.\gpu-rent.ps1 stop # погасить GPU, диски оставить
```
| Важно | |
| --- | --- |
| `Ctrl+C` на туннеле | **не** гасит GPU |
| Простой ~30 мин | idle-killer сам удалит compute |
| Preemptible | ~24 ч → `EXPIRED`; `tunnel` / `up` поднимут снова |
| Двойной клик лаунчера | задай `GPU_RENT_DEFAULT_ARGS=up --yes` в `gpu-rent.vars` |
---
## Команды (кратко)
| Команда | Смысл | | Команда | Смысл |
| --- | --- | | --- | --- |
| `doctor` | Keystone, квота GPU, flavor, Civitai, манифесты | | `setup` | Wizard: `.env`, LLM, watchdog |
| `flavors` | Живой список по `FLAVOR_PREFERENCE` | | `doctor` | Preflight без create |
| `up` / `up --yes` | Поднять GPU + туннель :17801 (браузер). Ctrl+C = туннель off | | `flavors` / `dry-run` | Что выберет / план без денег |
| `up --no-tunnel` | Только облако | | `up` / `up --yes` | GPU + seed + туннель |
| `up --no-spot` / `up --flavor ID` | Обычный тариф / явный flavor | | `up --ollama` | + Ollama (см. [docs/llm.md](docs/llm.md)) |
| `tunnel` / `tunnel --open` | Повторный проброс; watchdog на EXPIRED | | `tunnel` / `open` | Снова UI / браузер |
| `open` | Браузер на 17801 | | `hold` / `status` | Пауза killer / состояние |
| `hold` / `hold --minutes 90` / `hold --clear` | Пауза idle-killer |
| `status` | Nova + диск + killer |
| `seed-models` / `seed-extensions` | Докачать на живой диск |
| `push` / `push-models` / `pull-output` | Локальные папки ↔ VM |
| `resize-data --gb 400` | Диск только вверх |
| `stop` / `destroy --i-understand-data-loss` | Стоп GPU / + диски | | `stop` / `destroy --i-understand-data-loss` | Стоп GPU / + диски |
| `push` / `pull-output` / `seed-*` | Файлы ↔ VM |
Полный список: [docs/cli.md](docs/cli.md).
--- ---
## Деньги и риски ## Конфиг в корне репо
- **GPU** — пока жив compute (`up``stop` или idle-killer). | Файл | Что |
- **Диск data** — **24/7**, даже после `stop`. | --- | --- |
- Цены в OpenStack API **нет** — смотри панель Selectel. | `.env` | из `env.example``OS_*`, Civitai (не в git) |
- Preemptible по умолчанию (~24 ч) → `EXPIRED`; `tunnel` сам unshelve, иначе `up`. | `gpu-rent.vars` | из example — несекретные дефолты, лаунчер |
- Idle-killer: льгота после boot, потом N мин пустой очереди → delete. `hold` откладывает. | `models.yaml` / `extensions.yaml` | манифесты (из `*.example.yaml`) |
- `Ctrl+C` на tunnel **не** гасит GPU. | `Models/` … | локальный push на `up` |
| `.gpu-rent/` | state, SSH-ключ (gitignore) |
--- Лаунчер сам создаёт `.venv` и ставит пакет.
## Модели
1. Civitai-seed по `models.yaml` (нужен `CIVITAI_API_TOKEN`, хост `civitai.red`).
2. Локальные `Models/`, `Wildcards/`, `CustomWorkflows/` — непустые едут на каждый `up`.
Пример в репо уже с Krea 2 + LoRA — скопируй в `models.yaml`.
--- ---
@@ -77,10 +83,11 @@ Unix: `./gpu-rent.sh …` (один раз `chmod +x gpu-rent.sh`).
| Док | Зачем | | Док | Зачем |
| --- | --- | | --- | --- |
| [docs/setup.md](docs/setup.md) | Ключи, квота, RC | | [docs/setup.md](docs/setup.md) | **Пошаговая подготовка** до первого `up` |
| [docs/spike-notes.md](docs/spike-notes.md) | Чеклист живого spike | | [docs/cli.md](docs/cli.md) | Все команды и переменные |
| [docs/cli.md](docs/cli.md) | Все команды и `.env` | | [docs/llm.md](docs/llm.md) | Ollama / llama.cpp |
| [docs/decisions.md](docs/decisions.md) | Продуктовые решения | | [docs/models.md](docs/models.md) | Civitai + папка `Models/` |
| [docs/roadmap.md](docs/roadmap.md) | Что готово / что нет | | [docs/spike-notes.md](docs/spike-notes.md) | Чеклист первого живого прогона |
| [docs/README.md](docs/README.md) | Оглавление всего `docs/` |
MIT. Секреты и веса в git не коммитить. MIT. Секреты и веса в git не коммитить.
+32 -26
View File
@@ -1,39 +1,45 @@
# Документация gpu-rent # Документация gpu-rent
CLI-оркестратор: поднимает прерываемый GPU-сервер в Selectel, монтирует постоянные сетевые диски и открывает SwarmUI на `localhost` через SSH-туннель. Платим за GPU только пока идёт сессия; модели и ComfyUI живут на диске. CLI поднимает прерываемый GPU в Selectel, держит SwarmUI/модели на сетевых дисках и открывает UI на `localhost:17801` через SSH-туннель.
**Код v1 готов.** Живой `up` ждёт квоту GPU и зелёный `doctor`. Старт руками: [setup.md](setup.md), прогон: [spike-notes.md](spike-notes.md). ## С чего начать
## Как читать 1. **[setup.md](setup.md)** — квота, ключи, `.env`, первый `doctor``up``stop`
2. Корневой **[README](../README.md)** — быстрый старт и обычный день
3. При первом живом GPU заполни **[spike-notes.md](spike-notes.md)** (времена, цены из панели)
| Документ | Зачем | Дальше — по задаче.
## По задачам
| Хочу… | Документ |
| --- | --- | | --- | --- |
| [setup.md](setup.md) | **С чего начать:** квота GPU, сервисный пользователь, Civitai, установка CLI | | Понять зачем и сколько стоит | [concept.md](concept.md) |
| [spike-notes.md](spike-notes.md) | Чеклист первого живого прогона | | Все команды и `.env` | [cli.md](cli.md) |
| [concept.md](concept.md) | Задача, границы, модель стоимости | | Модели с Civitai / `Models/` | [models.md](models.md) |
| [decisions.md](decisions.md) | Зафиксированные решения | | Git-расширения SwarmUI/Comfy | [extensions.md](extensions.md) |
| [architecture.md](architecture.md) | Компоненты, диски, стейт-машина, idle-killer | | Word-list промптов | [autocomplete.md](autocomplete.md) |
| [selectel.md](selectel.md) | Контракт с облаком: auth, API, GPU, preemptible, сеть | | Push/pull папок | [local-folders.md](local-folders.md) |
| [models.md](models.md) | Первый seed с Civitai, манифест, пропуск дефолта SwarmUI | | Ollama / llama.cpp | [llm.md](llm.md) |
| [extensions.md](extensions.md) | Git-репы SwarmUI-расширений и ComfyUI nodes | | Как устроены диски и killer | [architecture.md](architecture.md) |
| [autocomplete.md](autocomplete.md) | Word-list промптов, Settings.fds, проверка версии | | Контракт Selectel | [selectel.md](selectel.md) |
| [local-folders.md](local-folders.md) | `Models/` / `Wildcards/` / `CustomWorkflows/` push, optional `Output/` pull | | Нативный SwarmUI на VM | [swarmui.md](swarmui.md) |
| [swarmui.md](swarmui.md) | Нативный install, порт 7801, API, MCP | | Зафиксированные решения | [decisions.md](decisions.md) |
| [llm.md](llm.md) | Opt-in Ollama / llama.cpp, `ollama-models.yaml`, порты 17811/17812 | | Статус реализации | [roadmap.md](roadmap.md) |
| [cli.md](cli.md) | Команды, конфиг, локальный state | | Что ещё проверить на spike | [open-questions.md](open-questions.md) |
| [roadmap.md](roadmap.md) | Порядок реализации |
| [open-questions.md](open-questions.md) | Ещё не закрыто |
## Источники Ревью кода: `docs/reviews/` (для разработки, не для первого запуска).
## Источники (Selectel / SwarmUI / Civitai)
Проверено 21 августа 2026: Проверено 21 августа 2026:
- [Selectel: preemptible servers](https://docs.selectel.ru/en/cloud-servers/about/preemptible-servers/) - [Selectel: preemptible](https://docs.selectel.ru/en/cloud-servers/about/preemptible-servers/)
- [Selectel: GPU](https://docs.selectel.ru/en/cloud-servers/create/gpus/) - [Selectel: GPU](https://docs.selectel.ru/en/cloud-servers/create/gpus/)
- [Selectel: GPU images / drivers](https://docs.selectel.ru/en/cloud-servers/manage/install-gpu-drivers/) - [Selectel: GPU drivers](https://docs.selectel.ru/en/cloud-servers/manage/install-gpu-drivers/)
- [Selectel: IAM / API auth](https://docs.selectel.ru/en/api/authorization/) - [Selectel: API auth](https://docs.selectel.ru/en/api/authorization/)
- [Selectel: restore preemptible](https://docs.selectel.ru/en/cloud-servers/manage/restore-preemptible-server/) - [Selectel: restore preemptible](https://docs.selectel.ru/en/cloud-servers/manage/restore-preemptible-server/)
- [SwarmUI](https://github.com/mcmonkeyprojects/SwarmUI) (нативный Linux, порт 7801) - [SwarmUI](https://github.com/mcmonkeyprojects/SwarmUI)
- [Civitai Site API](https://developer.civitai.com/) (download + metadata; API есть и на `.red`) - [Civitai Site API](https://developer.civitai.com/)
- [Civitai: два входа .com / .red](https://civitai.com/articles/28369/two-front-doors-civitaicom-civitaired-and-whats-next) - [Civitai .com / .red](https://civitai.com/articles/28369/two-front-doors-civitaicom-civitaired-and-whats-next)
- [SwarmUI Autocompletions](https://github.com/mcmonkeyprojects/SwarmUI/blob/master/docs/Features/Autocompletions.md) - [SwarmUI Autocompletions](https://github.com/mcmonkeyprojects/SwarmUI/blob/master/docs/Features/Autocompletions.md)
+3 -2
View File
@@ -17,8 +17,9 @@
┌─ Selectel, сегмент пула (например ru-7a) ────────────────────┐ ┌─ Selectel, сегмент пула (например ru-7a) ────────────────────┐
│ GPU VM (tag preemptible + gpu-rent) │ │ GPU VM (tag preemptible + gpu-rent) │
│ SwarmUI : 127.0.0.1:7801 │ │ SwarmUI : 127.0.0.1:7801 │
│ idle-killer: очередь / hold / качалка в UI → delete self │ idle-killer: очередь / hold / LLM busy → delete this server
│ application credential (compute delete/shelve only) │ app cred: DELETE/GET только этот server_id (fail closed)
│ optional: Ollama :11434 / llama.cpp :8080 (туннель 17811/12)│
│ │ │ │
│ 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 │
+3 -1
View File
@@ -2,7 +2,9 @@
Официальная инструкция: [docs/Features/Autocompletions.md](https://github.com/mcmonkeyprojects/SwarmUI/blob/master/docs/Features/Autocompletions.md). Официальная инструкция: [docs/Features/Autocompletions.md](https://github.com/mcmonkeyprojects/SwarmUI/blob/master/docs/Features/Autocompletions.md).
Нужен word-list (`.csv` / `.txt`) в `Data/Autocompletions` и выбранный источник в настройках пользователя. Без файла в UI нечего выбирать. **По умолчанию включено** (`AUTOCOMPLETE_ENABLED=true`): на bootstrap CLI качает `danbooru.csv` и прописывает источник в Settings **до** первого старта SwarmUI. Ничего руками делать не нужно.
Выключить: `AUTOCOMPLETE_ENABLED=false` в `.env`.
## Поведение gpu-rent ## Поведение gpu-rent
+135 -113
View File
@@ -1,144 +1,153 @@
# CLI и конфигурация # CLI и конфигурация
Имя команды: `gpu-rent`. Windows и Linux. Лицензия MIT, в репозитории нет секретов и имён личных чекпоинтов. Имя команды: `gpu-rent`. Лаунчеры: `gpu-rent.bat` / `.\gpu-rent.ps1` / `./gpu-rent.sh` (создают `.venv` и вызывают `python -m gpu_rent`).
Без аргументов CLI показывает **help**. Поднять GPU с двойного клика: в `gpu-rent.vars``GPU_RENT_DEFAULT_ARGS=up --yes`.
---
## Типичные сценарии
### Первый раз
```text
gpu-rent doctor
gpu-rent up --yes
# … работа …
gpu-rent stop
```
Подготовка ключей: [setup.md](setup.md).
### Обычная сессия
```text
gpu-rent up --yes
# Ctrl+C → туннель off, GPU жив
gpu-rent tunnel --open
gpu-rent hold
gpu-rent stop
```
### Только облако (туннель позже)
```text
gpu-rent up --yes --no-tunnel
gpu-rent tunnel --open
```
### LLM рядом со SwarmUI
```text
gpu-rent setup # или
gpu-rent up --yes --ollama
```
Подробности: [llm.md](llm.md).
---
## Команды ## Команды
| Команда | Поведение | | Команда | Поведение |
| --- | --- | | --- | --- |
| `gpu-rent flavors` | Скан `SCAN_POOLS` (ru-6 multizone…) × `FLAVOR_PREFERENCE`, затем список в текущем `OS_REGION_NAME` | | `gpu-rent setup` | Wizard: манифесты, `LLM_RUNTIME`, пресет ollama-models, опционально local-watchdog |
| `gpu-rent doctor` | Preflight **без** create: Keystone, квота GPU, flavor в AZ, диски, Civitai token+`.red`, манифесты, SSH-ключ. Код выхода ≠ 0, если сессию нельзя начать | | `gpu-rent doctor` | Preflight **без** create. Exit ≠ 0 → сессию начинать нельзя |
| `gpu-rent setup` | Wizard: `.env`/манифесты, `LLM_RUNTIME`, пресет ollama-models, опционально local-watchdog | | `gpu-rent flavors` | Скан `SCAN_POOLS` × `FLAVOR_PREFERENCE`, список в текущем регионе |
| `gpu-rent up` / `up --yes` | Preflight → create/unshelve → bootstrap → git update → optional LLM → **туннель** `localhost:17801`; Ctrl+C закрывает туннель |
| `gpu-rent up --ollama` / `--llamacpp` / `--llm …` | Поднять LLM рядом со SwarmUI (см. [llm.md](llm.md)) |
| `gpu-rent up --no-update` | Без `git pull` SwarmUI и extensions (только недостающие clone) |
| `gpu-rent up --no-tunnel` | Только облако + bootstrap, без локального проброса |
| `gpu-rent up --no-spot` | Обычный (не preemptible) сервер |
| `gpu-rent up --flavor … --yes` | Без вопросов. `--flavor` бьёт список фоллбека. `--yes` без `--flavor` берёт первый доступный из `FLAVOR_PREFERENCE` |
| `gpu-rent tunnel` | Повторный SSH-проброс, если `up --no-tunnel` или туннель уже закрыли. Ctrl+C = закрыть туннель, GPU **оставить** |
| `gpu-rent tunnel --open` | Туннель + сразу открыть браузер на 17801 |
| `gpu-rent open` | Открыть браузер на `http://127.0.0.1:17801`. Туннель уже должен слушать порт |
| `gpu-rent status` | State + Nova + диск used/free + ₽/час + сколько до 24 ч preempt + killer/hold. Туннель не обязателен |
| `gpu-rent hold` | Отложить idle-killer на `IDLE_MINUTES` от сейчас |
| `gpu-rent hold --minutes 90` | Hold до now+90 мин (заменяет предыдущий, не складывает) |
| `gpu-rent hold --until <ISO>` | Hold до абсолютного времени |
| `gpu-rent hold --clear` | Снять hold |
| `gpu-rent stop` | Optional pull Output → удалить compute и FIP, диски оставить. С любой машины с `.env` |
| `gpu-rent stop --no-pull` | Не тянуть Output, даже если `PULL_OUTPUT=true` |
| `gpu-rent destroy` | `stop` + диски, только `--i-understand-data-loss` |
| `gpu-rent logs` | journalctl swarmui / cloud-init по SSH |
| `gpu-rent ssh` | Оболочка на VM |
| `gpu-rent seed-models` | Докачать новые строки манифеста Civitai на уже существующий диск |
| `gpu-rent push` | Инкремент `Models/` + `Wildcards/` + `CustomWorkflows/` (пустые skip) |
| `gpu-rent push-models` | Только `./Models` |
| `gpu-rent pull-output` | Забрать новые файлы с VM `Output/` в `./Output` (сервер не чистим) |
| `gpu-rent seed-extensions` | Доклонировать/обновить git-репы; если VM жива — `systemctl restart swarmui` |
| `gpu-rent resize-data --gb 400` | Увеличить data volume вверх (Selectel online resize). Вниз нельзя |
| `gpu-rent watchdog install` | Локальный тик (Task Scheduler / systemd user / launchd): аварийное закрытие туннеля → `stop` после grace |
| `gpu-rent watchdog uninstall` | Снять локальный сервис |
| `gpu-rent watchdog status` | Установлен ли сервис + local lease |
| `gpu-rent watchdog tick` | Один тик (для планировщика; `--dry-run` без delete) |
| `gpu-rent dry-run` | План без mutating-вызовов | | `gpu-rent dry-run` | План без mutating-вызовов |
| `gpu-rent up` / `up --yes` | Create/unshelve → bootstrap → optional LLM → **туннель** `:17801`. Ctrl+C = туннель off |
| `gpu-rent up -v` / `--verbose` | Полная таблица doctor на `up` (по умолчанию кратко) |
| `gpu-rent up --ollama` / `--llamacpp` / `--llm …` | LLM рядом со SwarmUI |
| `gpu-rent up --no-update` | Без `git pull` SwarmUI/extensions (только недостающие clone) |
| `gpu-rent up --no-tunnel` | Только облако |
| `gpu-rent up --no-spot` | Не preemptible |
| `gpu-rent up --flavor ID --yes` | Явный flavor; без `--flavor` + `--yes` — первый доступный из `FLAVOR_PREFERENCE` |
| `gpu-rent tunnel` / `tunnel --open` | Повторный проброс; `--open` сразу браузер. Ctrl+C GPU **не** гасит |
| `gpu-rent open` / `open --llm` | Браузер на SwarmUI / LLM-порт (туннель уже должен слушать) |
| `gpu-rent status` | State, Nova, диск, killer/hold, LLM, local-watchdog |
| `gpu-rent hold` / `--minutes N` / `--until ISO` / `--clear` | Пауза idle-killer (нужны живая VM + SSH) |
| `gpu-rent stop` / `stop --no-pull` | Удалить compute (+ FIP), диски оставить; optional pull Output |
| `gpu-rent destroy --i-understand-data-loss` | `stop` + диски |
| `gpu-rent logs` | journalctl swarmui / cloud-init |
| `gpu-rent ssh` | Оболочка на VM |
| `gpu-rent seed-models` | Докачать новые строки Civitai-манифеста на живой диск |
| `gpu-rent seed-extensions` | Доклонировать/обновить git-репы; restart swarmui |
| `gpu-rent push` / `push-models` | Локальные деревья → VM |
| `gpu-rent pull-output` | VM `Output/``./Output` |
| `gpu-rent resize-data --gb 400` | Data volume **только вверх** |
| `gpu-rent watchdog install` / `uninstall` / `status` / `tick` | Локальный safety-net (см. ниже) |
Второй `up` при живой VM: не создавать второй GPU; сделать autocomplete-check и **push** локальных папок; если `PULL_OUTPUT` — подтянуть Output; напомнить про `tunnel` / `stop`. `--adopt` если нашли тег без state. Второй `up` при живой VM: не создаёт второй GPU; autocomplete-check + push локальных папок; при `PULL_OUTPUT` — подтянуть Output. `--adopt` — подхватить тег без state.
Нет команды `generate`. Нет зеркала каталога локального SwarmUI — только папки приложения, см. [local-folders.md](local-folders.md). Нет команды `generate`. Локальный SwarmUI целиком не зеркалируем — только папки приложения ([local-folders.md](local-folders.md)).
Сейчас в коде: после seed/`start swarmui` — idle-killer, Idle backend, boot snapshot, toast/MCP-сниппет, `resize-data`. `tunnel` с Nova-watchdog: EXPIRED → unshelve + reconnect. Опционально `watchdog install` — локальный safety net. UX-полировка flavors/цен ещё впереди. ---
## Local watchdog
Опционально. **Idle-killer на VM** остаётся основным: ноут можно закрыть, GPU живёт до простоя. Local watchdog — если хочешь гасить GPU при «убили окно / ребут» без `stop`.
1. `gpu-rent watchdog install` (раз на машине, в корне репо).
2. Пока крутится `up`/`tunnel`, пишется heartbeat в `.gpu-rent/local-lease.json`.
3. **Ctrl+C** → detach, GPU **не** трогаем (как раньше).
4. Процесс умер / ребут → через `LOCAL_WATCHDOG_GRACE_MINUTES` (дефолт 10) тик вызывает `stop` (диски остаются).
5. `gpu-rent stop` чистит lease сам.
Без `install` поведение прежнее. Без открытого туннеля (`up --no-tunnel`) lease не вооружается — работает только VM idle-killer.
## `doctor` ## `doctor`
Не создаёт сервер, не тратит GPU. Нужен до первого `up` и когда «вчера работало». Не создаёт сервер и не тратит GPU. Нужен до первого `up` и когда «вчера работало».
Проверки по порядку, все печатаются (не падать на первой, собрать отчёт): Проверки (все печатаются в отчёт):
1. Читается `.env`, обязательные `OS_*`. 1. Читается `.env`, обязательные `OS_*`
2. Keystone: токен выдаётся. 2. Keystone выдаёт токен
3. Квота GPU > 0; иначе текст «напишите в поддержку Selectel». 3. Квота GPU > 0 (иначе текст про поддержку Selectel)
4. В сегменте есть хотя бы один flavor из `FLAVOR_PREFERENCE` (не `disabled`). 4. В сегменте есть flavor из `FLAVOR_PREFERENCE`
5. Volume type сегмента существует; data 100 GB влезает в квоту дисков. 5. Volume type / место под data ~100 GB
6. SSH-ключ: есть `<repo>/.gpu-rent/id_ed25519` или CLI сможет его создать. 6. SSH-ключ есть или будет создан в `.gpu-rent/`
7. Если задан `CIVITAI_API_TOKEN`HEAD/лёгкий запрос к `CIVITAI_API_HOST` (дефолт `.red`); манифест парсится. 7. Если есть `CIVITAI_API_TOKEN`доступ к API-хосту; манифест парсится
8. `extensions.yaml` парсится, если файл есть. 8. `extensions.yaml` парсится, если есть
9. Локальные папки: предупреждение, если `Models/` огромный относительно свободного места на будущем диске. 9. Предупреждения по огромным локальным `Models/`
Exit 0 можно `up`. Exit 1 — нельзя, причина в отчёте. Exit 0 можно `up`. Exit 1 причина в таблице / кратком списке на `up`.
## `status` ---
Без туннеля. Если SSH есть — ещё диск и killer; если нет — только OpenStack. ## `status` и `hold`
| Поле | Откуда | **status** — без туннеля. При SSH: ещё `df` диска и состояние killer; иначе только OpenStack + локальный state.
| --- | --- |
| Фаза state / Nova status | `state.json` + compute |
| Flavor, ₽/час | extra specs / оценка с spike; нет цены в API — «см. панель», не выдумывать |
| Preempt window | create/unshelve timestamp + 24 ч; «осталось Hh Mm» или «не preemptible» |
| Data disk used/free | SSH `df` на `/mnt/swarm_data` |
| Idle-killer | armed / grace до … / hold до … / busy (очередь или качалка) / слеп (нет кредов) |
| Туннель | слушает ли локальный 17801 |
| Snapshot boot | есть ли `gpu-rent-boot-ok` |
## `hold` **hold** пишет на VM `/mnt/swarm_data/.gpu-rent-hold-until`. Killer не удаляет compute, пока `now < ts`. Без живой VM — ошибка (не «запомню на потом»). После unshelve hold на диске сохраняется.
Пишет на VM `/mnt/swarm_data/.gpu-rent-hold-until` (unix ts). Killer не удаляет compute, пока `now < ts`. Нужен SSH и живой сервер. ---
Без VM — ошибка, не «запомню на потом». После `unshelve` hold-файл на диске сохраняется. ## Local watchdog (опционально)
## Что печатать при `up` Основной авто-stop — **idle-killer на VM** (ноут можно закрыть). Local watchdog — если хочешь гасить GPU при «убили окно / ребут» без `stop`.
1. Preflight (тот же набор, что `doctor`, можно вызвать его внутри). 1. `gpu-rent watchdog install` (раз на машине, из корня репо)
2. Если квота 0 — стоп с текстом «напишите в поддержку Selectel», не traceback. 2. Пока крутится `up`/`tunnel`, пишется heartbeat `.gpu-rent/local-lease.json`
3. Выбранный flavor (или фоллбек) + оценка: GPU ₽/час, диск ₽/мес, preemptible 24 ч, idle-killer через N мин. 3. **Ctrl+C** → detach, GPU **не** трогаем
4. Фазы: provisioning → bootstrap → clone extensions → autocomplete → seed Civitai → push локальных папок → waiting UI → backend Idle. 4. Процесс умер / ребут → через `LOCAL_WATCHDOG_GRACE_MINUTES` (дефолт 10) → `stop`
5. Если `NOTIFY_READY` — Windows toast + звук (и строка в лог). 5. `gpu-rent stop` чистит lease сам
6. Готово:
```text Без `install` поведение прежнее. При `up --no-tunnel` lease не вооружается.
SwarmUI на VM: 127.0.0.1:7801 (только через туннель)
Локально: gpu-rent tunnel
Браузер: gpu-rent open → http://127.0.0.1:17801
API: http://127.0.0.1:17801/API/
MCP: http://127.0.0.1:17801/mcp
Hold killer: gpu-rent hold
Стоп GPU: gpu-rent stop
```
Сниппет MCP для Cursor — в stdout, `mcp.json` не редактировать. ---
## Конфигурация ## Конфигурация
| Файл | Назначение | | Файл | Назначение |
| --- | --- | | --- | --- |
| `.env` | Секреты и OpenStack (`OS_*`, токены). Не в git | | `.env` | Секреты и OpenStack (`OS_*`, токены). Не в git |
| `gpu-rent.vars` | Параметры запуска / несекретные дефолты. Читают `gpu-rent.ps1` / `.sh` / `.bat` и CLI. Пример: `gpu-rent.vars.example` | | `gpu-rent.vars` | Несекретные дефолты; читают лаунчеры и CLI. Пример: `gpu-rent.vars.example` |
| `models.yaml` / `extensions.yaml` | Манифесты | | `models.yaml` / `extensions.yaml` / `ollama-models.yaml` | Манифесты |
`.env` в корне репозитория (рядом с `env.example`). ### Лаунчер (`gpu-rent.vars`)
В `gpu-rent.vars` (создаётся из example при первом запуске):
```env ```env
# Двойной клик / запуск лаунчера без аргументов (CLI сам по себе показывает help): # Двойной клик / запуск без аргументов у .ps1/.bat/.sh:
GPU_RENT_DEFAULT_ARGS=up --yes GPU_RENT_DEFAULT_ARGS=up --yes
# Дописать ко всем вызовам: # Дописать ко всем вызовам:
# GPU_RENT_EXTRA_ARGS=--no-update # GPU_RENT_EXTRA_ARGS=--no-update
UPDATE_GIT=true UPDATE_GIT=true
LLM_RUNTIME=none
``` ```
### Основные переменные (`.env` / vars)
```env ```env
OS_AUTH_URL=https://cloud.api.selcloud.ru/identity/v3 OS_AUTH_URL=https://cloud.api.selcloud.ru/identity/v3
OS_USER_DOMAIN_NAME= OS_USER_DOMAIN_NAME=
@@ -150,6 +159,8 @@ GPU_RENT_AZ=ru-7a
SSH_PRIVATE_KEY_PATH= SSH_PRIVATE_KEY_PATH=
SSH_USER=ubuntu SSH_USER=ubuntu
# Пусто = авто /32 твоего IP. Для spike/WARP иногда 0.0.0.0/0:
GPU_RENT_SSH_CIDR=
BOOT_VOLUME_ID= BOOT_VOLUME_ID=
DATA_VOLUME_ID= DATA_VOLUME_ID=
@@ -167,46 +178,57 @@ EXTENSIONS_MANIFEST=
GIT_TOKEN= GIT_TOKEN=
AUTOCOMPLETE_ENABLED=true AUTOCOMPLETE_ENABLED=true
AUTOCOMPLETE_GITHUB_REPO=DominikDoom/a1111-sd-webui-tagcomplete
AUTOCOMPLETE_GITHUB_PATH=tags/danbooru.csv
AUTOCOMPLETE_GITHUB_REF=main
AUTOCOMPLETE_FILENAME=danbooru.csv
SWARMUI_LOCAL_PORT=17801 SWARMUI_LOCAL_PORT=17801
LLM_RUNTIME=none
OLLAMA_LOCAL_PORT=17811
LLAMACPP_LOCAL_PORT=17812
UPDATE_GIT=true
DEFAULT_FLAVOR_ID= DEFAULT_FLAVOR_ID=
FLAVOR_PREFERENCE=4090-24,4090-48,a5000,a100-40 FLAVOR_PREFERENCE=4090-24,4090-48,a5000,a100-40
FLAVOR_FALLBACK=true FLAVOR_FALLBACK=true
SCAN_POOLS=ru-6,ru-7
DEFAULT_SPOT=true DEFAULT_SPOT=true
KEEP_FLOATING_IP=false KEEP_FLOATING_IP=false
IDLE_MINUTES=30 IDLE_MINUTES=30
IDLE_GRACE_MINUTES=45 IDLE_GRACE_MINUTES=45
# LOCAL_WATCHDOG_GRACE_MINUTES=10
PULL_OUTPUT=false PULL_OUTPUT=false
NOTIFY_READY=true NOTIFY_READY=true
``` ```
Flavor id в git не хардкодить. `FLAVOR_PREFERENCE` — ярлыки; реальные uuid резолвятся в сегменте. `FLAVOR_FALLBACK=false` — только `DEFAULT_FLAVOR_ID` / `--flavor`, без следующего в списке. Flavor uuid в git не хардкодить. `FLAVOR_FALLBACK=false` — только `DEFAULT_FLAVOR_ID` / `--flavor`.
Application credential для idle-killer CLI создаёт при bootstrap и кладёт на VM; в `.env` ноутбука его можно не дублировать. Application credential для idle-killer CLI создаёт на `up` (узко: DELETE/GET **этого** server) и отзывает на `stop`. В `.env` ноутбука дублировать не нужно.
Пути локальных папок по умолчанию — каталоги в корне приложения: `Models/`, `Wildcards/`, `CustomWorkflows/`, `Output/`. Пути локальных папок по умолчанию: `Models/`, `Wildcards/`, `CustomWorkflows/`, `Output/` в корне репо.
Полный шаблон: [`env.example`](../env.example).
---
## State ## State
`<repo>/.gpu-rent/state.json` — ids, фаза (`idle` / `ready_cloud` / `ready_tunneled` / …), flavor, timestamps create/unshelve (для окна 24 ч). Без паролей. Lockfile там же, чтобы два `up` не создали два сервера. `<repo>/.gpu-rent/state.json` — ids, фаза, timestamps (окно preempt 24 ч). Без паролей. Lockfile рядом — два `up` не создадут два сервера.
---
## Ожидание готовности ## Ожидание готовности
Потолок ~2040 мин на первый bootstrap, ~510 мин на unshelve: Ориентир: **~2040 мин** первый bootstrap, **~510 мин** unshelve.
Порядок готовности:
1. Nova `ACTIVE` 1. Nova `ACTIVE`
2. TCP 22 2. TCP 22 / SSH
3. SSH: cloud-init или `systemctl is-active swarmui` 3. HTTP `http://127.0.0.1:7801` **на VM** (через SSH)
4. HTTP `http://127.0.0.1:7801` **на VM** (через SSH), не путать с локальным 7801 4. Backend Idle → toast (если `NOTIFY_READY`) + access-card
5. Для MCP/API / toast — дождаться Idle backend.
## Windows Локальный порт UI: **17801** (на VM по-прежнему 7801 на loopback).
Лаунчеры в корне: `gpu-rent.bat` (cmd / двойной клик), `.\gpu-rent.ps1` (PowerShell). На Unix — `./gpu-rent.sh`. Они создают `.venv` в репозитории и вызывают `python -m gpu_rent`. Ключ генерирует CLI: `<repo>/.gpu-rent/id_ed25519`. ---
`NOTIFY_READY`: toast через WinRT / `win10toast` (что заработает на 10/11 без админ-прав) + системный звук. Если toast недоступен — только звук и лог, не падать. ## Windows / notify
`NOTIFY_READY`: toast + системный звук, когда backend Idle. Если toast недоступен — только звук и лог, без падения CLI.
+1
View File
@@ -24,6 +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; `hold` для idle-killer; toast когда backend Idle; `open` на 17801. - `doctor` до create; фоллбек flavor; `hold` для idle-killer; toast когда backend Idle; `open` на 17801.
- Opt-in LLM (Ollama / llama.cpp) через `setup` / `--ollama` / `LLM_RUNTIME`.
- `status`: диск, ₽/час, окно preempt 24 ч, состояние killer. - `status`: диск, ₽/час, окно preempt 24 ч, состояние killer.
- Snapshot boot-диска после первого удачного bootstrap. - Snapshot boot-диска после первого удачного bootstrap.
+6 -4
View File
@@ -32,11 +32,13 @@
| Wildcards / workflows | Как Models: пустые папки не трогаем; появился файл — push на `up` | | Wildcards / workflows | Как Models: пустые папки не трогаем; появился файл — push на `up` |
| Idle hold | `gpu-rent hold [--minutes N]` сдвигает дедлайн killer. Качалка моделей в UI SwarmUI тоже считается занятостью (сигнал уточнить на spike) | | Idle hold | `gpu-rent hold [--minutes N]` сдвигает дедлайн killer. Качалка моделей в UI SwarmUI тоже считается занятостью (сигнал уточнить на spike) |
| Doctor | `gpu-rent doctor` без create: Keystone, квота GPU, flavor, диск, Civitai token+`.red`, манифесты, SSH-ключ | | Doctor | `gpu-rent doctor` без create: Keystone, квота GPU, flavor, диск, Civitai token+`.red`, манифесты, SSH-ключ |
| Bare CLI | Без аргументов — help. `up` только явно или через `GPU_RENT_DEFAULT_ARGS` у лаунчера |
| Flavor fallback | Список предпочтений (4090 → A5000 → …). Нет первого — предложить следующий с ценой, `--yes` берёт первый доступный из списка | | Flavor fallback | Список предпочтений (4090 → A5000 → …). Нет первого — предложить следующий с ценой, `--yes` берёт первый доступный из списка |
| Готово | Windows toast + звук (и лог), когда backend Idle | | Готово | Windows toast + звук (и лог), когда backend Idle; access-card с URL/MCP |
| Status | Диск used/free, ₽/час, сколько до 24 ч preempt, состояние killer / hold | | Status | Диск used/free, ₽/час, сколько до 24 ч preempt, состояние killer / hold / LLM |
| Boot snapshot | После первого удачного bootstrap — один snapshot boot volume `gpu-rent-boot-ok`; следующие create могут идти с него | | Boot snapshot | После первого удачного bootstrap — один snapshot boot volume `gpu-rent-boot-ok`; следующие create могут идти с него |
| Open | `gpu-rent open` / флаг после `tunnel`: браузер на 17801 | | Open | `gpu-rent open` / `open --llm` / флаг после `tunnel`: браузер на 17801 / LLM-порт |
| App cred idle-killer | Только этот `server_id`; fail closed без unrestricted; revoke на stop |
Следствия, которые из этого вытекают и тоже считаются принятыми: Следствия, которые из этого вытекают и тоже считаются принятыми:
@@ -44,4 +46,4 @@
2. **Локальный SwarmUI на 7801 не трогаем.** Туннель по умолчанию на **17801** (на VM по-прежнему 7801 на loopback). 2. **Локальный SwarmUI на 7801 не трогаем.** Туннель по умолчанию на **17801** (на VM по-прежнему 7801 на loopback).
3. **`Ctrl+C` на туннеле не удаляет VM.** Иначе «закрыл ноут» невозможно. Чтобы убить GPU — `gpu-rent stop` или простой. При установленном **local-watchdog** Ctrl+C по-прежнему detach; убийство процесса/ребут без detach → stop после grace. 3. **`Ctrl+C` на туннеле не удаляет VM.** Иначе «закрыл ноут» невозможно. Чтобы убить GPU — `gpu-rent stop` или простой. При установленном **local-watchdog** Ctrl+C по-прежнему detach; убийство процесса/ребут без detach → stop после grace.
4. Idle-killer на VM **не может** быть `shutdown -h`: у Selectel остановленная изнутри VM часто продолжает тарифицировать ресурсы. Нужен вызов OpenStack: удалить **этот** compute, диски оставить. 4. Idle-killer на VM **не может** быть `shutdown -h`: у Selectel остановленная изнутри VM часто продолжает тарифицировать ресурсы. Нужен вызов OpenStack: удалить **этот** compute, диски оставить.
5. Для этого на VM — OpenStack **application credential** с правом удалить/shelve сервер в проекте (не пароль владельца аккаунта). Компрометация SwarmUI в худшем случае сносит GPU-сессию, а не создаёт новые дорогие машины, если роль без `compute:create`. 5. Для этого на VM — OpenStack **application credential** с правом DELETE/GET **только этого** compute (не `/servers/*`, не unrestricted fallback). На `stop`/`destroy` cred отзывается. Компрометация SwarmUI в худшем случае сносит текущую GPU-сессию, а не произвольные машины проекта.
+27 -4
View File
@@ -1,4 +1,14 @@
# Расширения: git-репы на первый bootstrap # Расширения: git-репы на bootstrap
## Быстрый старт
```powershell
copy extensions.example.yaml extensions.yaml
# добавь url реп (публичные — без GIT_TOKEN)
.\gpu-rent.ps1 up --yes
# доклонировать на уже живой VM:
.\gpu-rent.ps1 seed-extensions
```
Срабатывает на каждом `up``seed-extensions`): Срабатывает на каждом `up``seed-extensions`):
@@ -23,15 +33,28 @@
```yaml ```yaml
swarmui: swarmui:
- url: https://gitea.hsrv.site/mrleo1nid/swarm-assistent.git
ref: main
dir: swarm-assistent
requires: ollama # none | ollama | llamacpp | any-llm; default none = always
- url: https://github.com/example/SwarmUI-SomeExt.git - url: https://github.com/example/SwarmUI-SomeExt.git
ref: main # ветка, тег или commit SHA; по умолчанию HEAD default branch ref: main
dir: SomeExt # имя папки; по умолчанию последний сегмент URL без .git dir: SomeExt
comfy: comfy:
- url: https://github.com/example/ComfyUI-Something.git - url: https://github.com/example/ComfyUI-Something.git
ref: v1.2.0 ref: v1.2.0
``` ```
В git репозитория gpu-rent не коммитить рабочий список с приватными URL. В примере — вымышленные репы. | `requires` | Когда клонировать |
| --- | --- |
| `none` (или поле отсутствует) | всегда |
| `ollama` | только при `LLM_RUNTIME=ollama` |
| `llamacpp` | только при `LLM_RUNTIME=llamacpp` |
| `any-llm` | при `ollama` или `llamacpp` |
Строки с несовпавшим `requires` пропускаются (лог), остальные ставятся как обычно. В `extensions.example.yaml` по умолчанию — **swarm-assistent** с `requires: ollama` (чат/vision под Krea 2).
В git репозитория gpu-rent не коммитить рабочий список с лишними приватными URL сверх примера.
## Когда в жизненном цикле ## Когда в жизненном цикле
+81 -38
View File
@@ -1,79 +1,122 @@
# LLM рядом со SwarmUI (opt-in) # LLM рядом со SwarmUI (opt-in)
По умолчанию поднимается только SwarmUI. Ollama или llama.cpp — по флагу, `LLM_RUNTIME` в `gpu-rent.vars` / `.env`, или через wizard. По умолчанию поднимается **только SwarmUI**. Ollama или llama.cpp — отдельно, когда нужны (помощь с промптами и т.п.).
## Включение ---
## Быстрый путь
### Вариант A — wizard
```text ```text
gpu-rent setup # спросит none/ollama/llamacpp + пресет моделей gpu-rent setup
gpu-rent up --ollama # разово ```
gpu-rent up --llm llamacpp
# или в gpu-rent.vars: Выбери `ollama` или `llamacpp`, при Ollama — пресет моделей. Значение пишется в `gpu-rent.vars` (`LLM_RUNTIME=…`).
### Вариант B — флаг на `up`
```text
gpu-rent up --yes --ollama
gpu-rent up --yes --llm llamacpp
```
### Вариант C — вручную в vars
```env
LLM_RUNTIME=ollama LLM_RUNTIME=ollama
``` ```
Без параметров `gpu-rent` показывает help (не `up`). Double-click лаунчеры: `GPU_RENT_DEFAULT_ARGS=up --yes`. `gpu-rent up` без `--yes` спросит про LLM, если в vars ещё `none`. Полный doctor: `gpu-rent up -v`. Потом обычный `gpu-rent up --yes`.
## Порты (только loopback + туннель) Чтобы **выключить** LLM на следующих сессиях: `LLM_RUNTIME=none` (на `up` старые unit’ы `gpu-rent-ollama` / `gpu-rent-llamacpp` останавливаются).
| Сервис | VM | localhost | Голый `gpu-rent` без args — help, не `up`. Двойной клик: `GPU_RENT_DEFAULT_ARGS=up --yes` в `gpu-rent.vars`. Полный doctor на up: `up -v`.
---
## Порты (loopback + туннель)
| Сервис | На VM | На ноутбуке |
| --- | --- | --- | | --- | --- | --- |
| SwarmUI | 7801 | 17801 | | SwarmUI | 7801 | **17801** |
| Ollama | 11434 | 17811 | | Ollama | 11434 | **17811** |
| llama.cpp | 8080 | 17812 | | llama.cpp | 8080 | **17812** |
```text ```text
gpu-rent tunnel gpu-rent tunnel
gpu-rent open --llm # http://127.0.0.1:17811 (Ollama) gpu-rent open --llm
# клиент:
set OLLAMA_HOST=http://127.0.0.1:17811
``` ```
## Ollama models Клиент Ollama:
Как Civitai `models.yaml`: ```text
# Windows PowerShell
$env:OLLAMA_HOST = "http://127.0.0.1:17811"
```
- `ollama-models.example.yaml` — в git После успешного `up`/`tunnel` CLI печатает access-card со всеми URL и MCP-сниппетом.
- `ollama-models.yaml` — локальный (gitignore)
На `up` при `LLM_RUNTIME=ollama` CLI делает `ollama pull` по списку. Уже скачанные не трогает; лишние на диске не удаляет. ---
### Пресеты setup ## Ollama: модели
| Файл | Роль |
| --- | --- |
| `ollama-models.example.yaml` | шаблон в git |
| `ollama-models.yaml` | твой список (gitignore) |
На `up` при `LLM_RUNTIME=ollama` CLI делает `ollama pull` по списку. Уже скачанные теги **точно** не трогает (`foo:3b``foo:7b`). Лишние модели на диске не удаляет.
### Пресеты `setup`
| preset | tag | зачем | | preset | tag | зачем |
| --- | --- | --- | | --- | --- | --- |
| **recommended** | `huihui_ai/qwen2.5-abliterate:7b` | RU/EN, ~5GB, мало отказов — помощь с промптами | | **recommended** | `huihui_ai/qwen2.5-abliterate:7b` | RU/EN, ~5 GB, меньше отказов |
| light | `qwen2.5:3b` | быстрее, слабее | | light | `qwen2.5:3b` | быстрее, слабее |
| stock | `qwen2.5:7b` | официальный, больше цензуры | | stock | `qwen2.5:7b` | официальный |
| alt | `richardyoung/qwen2.5-7b-instruct-abliterated` | другой abliterate | | alt | `richardyoung/qwen2.5-7b-instruct-abliterated` | другой abliterate |
| empty | `[]` | только runtime | | empty | `[]` | только runtime, pull руками |
Community abliterate-модели без гарантий безопасности — для личного prompt-help / NSFW-тегов рядом со SwarmUI. Community abliterate-модели без гарантий безопасности — для личного prompt-help рядом со SwarmUI.
Поле `default: true` в yaml — preferred модель (лог / подсказка); pull идёт по всему списку.
---
## llama.cpp ## llama.cpp
Ставит `llama-server` и systemd. GGUF клади вручную в `/mnt/swarm_data/llamacpp/models` на data-диске (или через SSH), затем `systemctl restart gpu-rent-llamacpp`. 1. `LLM_RUNTIME=llamacpp` или `up --llamacpp`
2. CLI ставит `llama-server` + systemd `gpu-rent-llamacpp`
3. Положи GGUF в `/mnt/swarm_data/llamacpp/models` (SFTP / `gpu-rent ssh`)
4. `systemctl restart gpu-rent-llamacpp` на VM
## Idle-killer Без GGUF unit может стартовать, но API бесполезен — смотри `gpu-rent logs` / `journalctl -u gpu-rent-llamacpp`.
Busy также если идёт `ollama pull` (маркер младше ~45 мин), в Ollama есть loaded model, или llama.cpp занимает слоты. Зависший маркер `.gpu-rent-ollama-pulling` старше 45 мин idle-killer сбрасывает. ---
## Supply-chain (install на VM) ## Idle-killer и LLM
Скрипты `install_ollama.sh` / `install_llamacpp.sh` по умолчанию тянут upstream **без pin** (Ollama: `curl|sh`; llama.cpp: GitHub `latest`). Это риск подмены артефакта. Busy (не гасить GPU), если:
Рекомендуется задать pin через env при bootstrap (или патч vars / future hooks): - идёт `ollama pull` (маркер младше ~45 мин; старше — сбрасывается);
- в Ollama есть загруженная модель;
- llama.cpp занимает слоты.
Ошибка установки LLM на `up`**fail** (не тихий лог): почини или поставь `LLM_RUNTIME=none`.
---
## Supply-chain (опциональный pin)
По умолчанию install-скрипты тянут upstream **без pin** (Ollama: `curl|sh`; llama.cpp: GitHub `latest`) — риск подмены. В логе будет `WARN`.
Жёстче — задай env на VM / при bootstrap:
```bash ```bash
# Ollama — GitHub release + checksum
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>
# llama.cpp — tag или прямой URL + checksum
LLAMACPP_TAG=b4690 LLAMACPP_TAG=b4690
# или: # или LLAMACPP_ASSET_URL=... + LLAMACPP_SHA256=...
LLAMACPP_ASSET_URL=https://github.com/ggerganov/llama.cpp/releases/download/...
LLAMACPP_SHA256=<sha256 of archive>
``` ```
Без этих переменных в логе будет `WARN` про отсутствие pin/checksum.
+9 -1
View File
@@ -1,6 +1,14 @@
# Локальные папки приложения # Локальные папки приложения
Все живут в корне gpu-rent. Одно правило: **пусто → ничего не синхронизируем**. Появился файл → на `up` (и по отдельной команде) едет инкремент по SHA256. С сервера лишнее не удаляем. Все живут в корне gpu-rent.
**Одно правило:** пусто (только README / `.gitkeep`) → ничего не синхронизируем. Появился файл → на `up` (или отдельной командой) едет инкремент по SHA256. С сервера лишнее **не** удаляем.
```powershell
# положил LoRA / wildcard / workflow локально
.\gpu-rent.ps1 push # или просто следующий up
.\gpu-rent.ps1 pull-output # картинки с VM → ./Output
```
| Локально | На VM | Направление | Обязательно | | Локально | На VM | Направление | Обязательно |
| --- | --- | --- | --- | | --- | --- | --- | --- |
+14 -3
View File
@@ -2,10 +2,21 @@
Два источника на data volume, они **складываются**, не заменяют друг друга: Два источника на data volume, они **складываются**, не заменяют друг друга:
1. **Civitai-seed** (ниже) — один раз на пустой диск, если есть API-токен и манифест. 1. **Civitai-seed** (ниже) — на пустой диск при bootstrap, если есть API-токен и манифест.
2. **Папка `Models/` в корне gpu-rent** — на каждом `up` выгрузить на VM только то, что изменилось. `Wildcards/` и `CustomWorkflows/` — то же правило, см. [local-folders.md](local-folders.md). 2. **Папка `Models/` в корне gpu-rent** — на каждом `up` выгрузить только изменившееся. `Wildcards/` и `CustomWorkflows/` — то же, см. [local-folders.md](local-folders.md).
Локальный SwarmUI на `7801` по-прежнему не зеркалируем. Только дерево `./Models` приложения. ## Быстрый старт моделей
```powershell
copy models.example.yaml models.yaml
# правь URL с modelVersionId=…
# в .env: CIVITAI_API_TOKEN=…
.\gpu-rent.ps1 up --yes
```
Или положи веса в `Models/Stable-Diffusion/` (и т.п.) — уедут на `up` / `gpu-rent push-models`.
Локальный SwarmUI на `7801` не зеркалируем. Только дерево `./Models` приложения.
## Локальная папка → сервер ## Локальная папка → сервер
+7 -7
View File
@@ -1,13 +1,13 @@
# Открытые вопросы # Открытые вопросы
Пока пусто по продуктовым развилкам — см. [decisions.md](decisions.md). Продуктовые развилки закрыты — см. [decisions.md](decisions.md).
Осталось подтвердить на **spike**, не в споре: Ниже — что **подтвердить на spike** (живой GPU), не в споре:
- Качалка модели в UI: отдельного poll-API нет (`DoModelDownloadWS` только WS) → v1 считает busy через `waiting_gens` / `live_gens` / `loading_models` / backend≠idle; иначе пользователь жмёт `hold`. - Качалка модели в UI: отдельного poll-API нет → v1 считает busy через очередь / loading / backend≠idle; иначе пользователь жмёт `hold`.
- Реальная цена 100 GB сетевого диска в выбранном сегменте и цена 1×4090 preemptible ₽/час (в OpenStack API нет). - Реальная цена 100 GB сетевого диска и 1×4090 preemptible ₽/час в твоём сегменте (в OpenStack API нет — только панель).
- Имя GPU-образа (без Docker) и flavor id в твоём пуле (в git не класть); какие из списка фоллбека реально есть. - Имя GPU-образа (без Docker) и flavor id в твоём пуле (в git не класть); какие из `FLAVOR_PREFERENCE` реально есть.
- Хватает ли application credential с access_rules (delete/GET server) на Selectel — при отказе CLI падает назад на unrestricted cred и пишет в лог. - Application credential с **узкими** access_rules (DELETE/GET только этого `server_id`) на Selectel: если Keystone отвергает rules — CLI **fail closed** (idle-killer не вооружается unrestricted). Нужно проверить на spike и при необходимости править права / путь.
- Snapshot attached boot volume после Idle: время и можно ли сразу create from snapshot. - Snapshot attached boot volume после Idle: время и можно ли сразу create from snapshot.
Если spike покажет, что 45 минут льготы мало на первую установку ComfyUI — поднять `IDLE_GRACE_MINUTES`, не отключать killer. Если spike покажет, что 45 минут льготы мало на первую установку ComfyUI — подними `IDLE_GRACE_MINUTES`, не отключай killer.
+7 -6
View File
@@ -1,16 +1,17 @@
# Roadmap # Roadmap
**Код v1 (§1–6) готов.** Дальше блокирует только облако: **0.0 квота GPU** и **0 spike** в панели. Без GPU в квоте `up` не создать — это ожидаемо. **Код v1 (§1–6) готов** (включая opt-in LLM, local-watchdog, access-card). Дальше — твой облачный прогон.
Чеклист прогона: [spike-notes.md](spike-notes.md). Ключи: [setup.md](setup.md). Пользовательский путь: [setup.md](setup.md) → `doctor``up`. Чеклист живого GPU: [spike-notes.md](spike-notes.md).
## 0.0 Квота GPU ## 0.0 Квота GPU
- [x] Квота GPU в аккаунте в порядке (подтверждено) - [ ] Квота GPU в панели ≥ 1 (иначе тикет — текст в setup §2.3)
- [ ] Тикет в поддержку Selectel — не нужен, если лимит уже > 0 - [ ] Тикет закрыт / не нужен
- [ ] Сервисный пользователь с правом `member` на проект + `.env` заполнен - [ ] Сервисный пользователь `member` + `.env` заполнен
- [ ] `gpu-rent doctor` exit 0
`doctor` / `flavors` / `dry-run`дальше; `up` после зелёного doctor. `flavors` / `dry-run`по желанию перед первым `up`.
## 0. Ручной spike в панели ## 0. Ручной spike в панели
+158 -104
View File
@@ -1,20 +1,38 @@
# Что сделать до первого `gpu-rent up` # Подготовка до первого `up`
Код **не создаёт GPU**, пока не пройден `gpu-rent doctor`. Сначала доступ в облако, квота GPU и ключи. Статический ключ панели **`X-Token` для этого CLI не подходит** — им нельзя управлять серверами и дисками. Цель: зелёный `doctor` → первый `up --yes` → UI на `http://127.0.0.1:17801``stop`.
Панель: [my.selectel.ru](https://my.selectel.ru). Панель Selectel: [my.selectel.ru](https://my.selectel.ru).
**Важно:** ключ панели **`X-Token` не подходит**. Нужен сервисный пользователь + пароль (OpenStack Keystone). CLI сам получает токен на ~24 ч; в `.env` токен хранить не надо.
--- ---
## 1. Python ## Карта шагов
| # | Что сделать | Готово, когда |
| --- | --- | --- |
| 1 | Python 3.11+ и лаунчер | `.\gpu-rent.ps1 --help` печатает help |
| 2 | Проект + квота GPU > 0 | в панели лимит GPU ≥ 1 |
| 3 | Сервисный пользователь + `.env` | заполнены `OS_*` и `GPU_RENT_AZ` |
| 4 | (Опц.) Civitai + `models.yaml` | токен в `.env`, манифест с `modelVersionId` |
| 5 | (Опц.) `extensions.yaml` / Git | публичные репы без токена |
| 6 | `doctor` | exit 0 |
| 7 | Первый `up --yes` | браузер / :17801 |
| 8 | `stop` | compute удалён, диски на месте |
SSH-ключ руками не нужен — CLI создаст `.gpu-rent/id_ed25519` при первом `up`.
---
## 1. Python и лаунчер
Нужен **Python 3.11+**. На Windows при установке включи «Add python.exe to PATH». Нужен **Python 3.11+**. На Windows при установке включи «Add python.exe to PATH».
В корне репозитория достаточно лаунчера — venv и `pip install` он сделает сам: В корне репозитория:
```powershell ```powershell
.\gpu-rent.ps1 --help .\gpu-rent.ps1 --help
.\gpu-rent.ps1 doctor
``` ```
```bat ```bat
@@ -26,11 +44,24 @@ chmod +x gpu-rent.sh
./gpu-rent.sh --help ./gpu-rent.sh --help
``` ```
Первый запуск копирует `env.example``.env`, `models.example.yaml``models.yaml`, `extensions.example.yaml``extensions.yaml` в **корне репозитория**, если файлов ещё нет. Заполни `OS_*` в `.env` (шаги ниже). После `git pull`, если изменился `pyproject.toml`, лаунчер переустановит пакет. Лаунчер сам создаёт `.venv` и ставит пакет. Первый запуск копирует шаблоны **в корень репо**, если файлов нет:
Секреты и runtime не уезжают в `%USERPROFILE%`: только `<repo>/.env` и `<repo>/.gpu-rent/` (state, SSH-ключ). Оба в `.gitignore`. Если раньше лежало в `~\.gpu-rent\`, CLI один раз перенесёт в проект. - `env.example``.env`
- `models.example.yaml``models.yaml`
- `extensions.example.yaml``extensions.yaml`
- `gpu-rent.vars.example``gpu-rent.vars`
Ручной венв по желанию (для разработки / pytest): Секреты только в `<repo>/.env` и runtime в `<repo>/.gpu-rent/` (оба в `.gitignore`).
Опционально wizard (после того как `.env` хотя бы частично заполнен):
```powershell
.\gpu-rent.ps1 setup
```
Спросит LLM (none/ollama/llamacpp) и local-watchdog. Можно пропустить и настроить позже.
Для разработки / pytest:
```powershell ```powershell
python -m venv .venv python -m venv .venv
@@ -44,171 +75,194 @@ python -m gpu_rent --help
## 2. Selectel: проект и квота GPU ## 2. Selectel: проект и квота GPU
На новых аккаунтах лимит GPU почти всегда **0**. Без тикета в поддержку `up` создать карту не сможет — это норма, не баг CLI. На новых аккаунтах лимит GPU часто **0**. Без поднятия лимита `up` не создаст сервер — это нормально, не баг CLI.
### 2.1. Проект ### 2.1. Проект
1. Панель → сверху **IAM****Projects** (Проекты). 1. Панель → **IAM****Projects** (Проекты).
2. Отдельный проект, например `gpu-rent`. Не клади GPU в общий «мусорный» проект. 2. Отдельный проект, например `gpu-rent` (не общий «мусорный»).
3. Скопируй **ID проекта** (uuid). Он же `OS_PROJECT_ID`. 3. Скопируй **ID проекта** (uuid) → это `OS_PROJECT_ID`.
4. **Номер аккаунта** — в правом верхнем углу панели. Он же `OS_USER_DOMAIN_NAME` (domain в Keystone). 4. **Номер аккаунта** (правый верхний угол) → это `OS_USER_DOMAIN_NAME`.
### 2.2. Есть ли GPU в квоте ### 2.2. Есть ли GPU в квоте
1. **IAM****Projects** → твой проект → вкладка **Quotas and limits** / **Квоты и лимиты****Cloud platform**. 1. **IAM****Projects** → твой проект → **Quotas and limits** / **Квоты****Cloud platform**.
2. Выбери пул и сегмент, где есть GPU (матрица: [GPU availability](https://docs.selectel.ru/en/cloud-servers/create/gpus/)). Для Москвы смотри **мультизональный `ru-6`** (`ru-6a`/`ru-6b`/`ru-6c`) и `ru-7`. CLI сам сканит `SCAN_POOLS` в `gpu-rent flavors` и подскажет `OS_REGION_NAME` / `GPU_RENT_AZ`. 2. Пул и сегмент с GPU: матрица [GPU availability](https://docs.selectel.ru/en/cloud-servers/create/gpus/). Часто смотрят мультизональный `ru-6` и `ru-7`.
3. Строка **GPU** (и при необходимости vCPU/RAM/network volumes). Если лимит 0 — дальше тикет. 3. Строка **GPU**. Если лимит 0 — тикет (ниже).
Квоту внутри уже выданного лимита можно крутить в панели. **Сам лимит GPU поднимает только поддержка.** Лимит GPU поднимает **только поддержка**. Внутри уже выданного лимита квоту можно крутить в панели.
Подсказка по пулу после появления ключей: `.\gpu-rent.ps1 flavors` (скан `SCAN_POOLS`).
### 2.3. Тикет в поддержку ### 2.3. Тикет в поддержку
**Тикеты** в панели (не email вслепую). Лимит увеличивают **на один конкретный проект**. **Тикеты** в панели. Лимит увеличивают **на один конкретный проект**.
Текст можно почти копировать:
```text ```text
Прошу увеличить лимит GPU в облачной платформе. Прошу увеличить лимит GPU в облачной платформе.
Проект: <имя> (ID: <uuid проекта>) Проект: <имя> (ID: <uuid проекта>)
Пул / сегмент: ru-7 / ru-7a ← подставь свой из матрицы GPU Пул / сегмент: ru-7 / ru-7a ← подставь свой из матрицы GPU
Нужно: 1× NVIDIA RTX 4090 24 GB (если нет — ближайший аналог в этом сегменте: 4090 48 GB или A5000). Нужно: 1× NVIDIA RTX 4090 24 GB (если нет — ближайший аналог: 4090 48 GB или A5000).
Цель: один прерываемый (preemptible) облачный сервер для персональных сессий генерации, диски сетевые. Цель: один прерываемый (preemptible) облачный сервер для персональных сессий, диски сетевые.
Сейчас квота/лимит GPU = 0, создать сервер с GPU нельзя. Сейчас квота/лимит GPU = 0, создать сервер с GPU нельзя.
``` ```
Пока тикет не закрыт, ставишь CLI и гоняешь `doctor` — он как раз покажет «квота 0, напишите в поддержку». Пока тикет открыт, можно уже заполнять `.env` и гонять `doctor` — он покажет «квота 0».
--- ---
## 3. Ключи OpenStack (это и есть «API key» для gpu-rent) ## 3. Ключи OpenStack
Нужен **сервисный пользователь** + пароль. CLI сам получает IAM-токен на 24 часа (`X-Auth-Token`). В `.env` токен хранить не надо. Нужен **сервисный пользователь** + пароль. Не используй: Профиль → Access → API Keys → `X-Token`.
**Не используй:** Профиль → Access → API Keys → `X-Token`. Это статический ключ панели, OpenStack (серверы/диски/сети) он **не** двигает.
### 3.1. Сервисный пользователь ### 3.1. Сервисный пользователь
Только владелец аккаунта или роль `iam.admin`. На балансе для роли `member` должно быть хотя бы **100 ₽**. Только владелец аккаунта или `iam.admin`. На балансе для роли `member` обычно нужно хотя бы **~100 ₽**.
1. Сверху **IAM****Service users** / **Сервисные пользователи**. 1. **IAM****Service users** / **Сервисные пользователи****Add**.
2. **Add service user**. 2. Имя, например `gpu-rent-api`.
3. Имя, например `gpu-rent-api`. 3. Пароль: **≥ 20 символов**, сохрани сразу — потом только сброс.
4. Пароль: **минимум 20 символов**, сохрани в менеджер паролей. После создания пароль **больше не показывают** — только сброс. 4. Scope: **Projects** проект `gpu-rent` → роль **`member`** (`reader` для `up` мало).
5. Права: 5. **Add user**.
- **Scope: Projects** (не весь аккаунт);
- проект `gpu-rent`;
- роль **`member`** (создание серверов/дисков/сетей). Роль `reader` для `up` не хватит.
6. **Add user**.
Официально: [Add user](https://docs.selectel.ru/en/access-control/manage/add-user/), [авторизация API](https://docs.selectel.ru/en/api/authorization/). Официально: [Add user](https://docs.selectel.ru/en/access-control/manage/add-user/), [API auth](https://docs.selectel.ru/en/api/authorization/).
### 3.2. Скачать RC-файл (готовые `OS_*`) ### 3.2. RC-файл
1. **IAM****Service users** твой пользователь → вкладка **Access**. 1. **IAM****Service users** → пользователь → **Access**.
2. Блок **RC files**: 2. **RC files**: проект `gpu-rent`, локация = **пул** (например `ru-7`, не сегмент `ru-7a`) → **Download**.
- проект `gpu-rent`; 3. Из `rc.sh` перенеси в `.env`:
- локация = **пул**, например `ru-7` (это `OS_REGION_NAME`, не сегмент `ru-7a`);
- **Download**.
3. Открой файл (`rc.sh`). Из него в `.env` переносятся:
| Переменная | Откуда | | Переменная | Откуда |
| --- | --- | | --- | --- |
| `OS_AUTH_URL` | обычно `https://cloud.api.selcloud.ru/identity/v3` | | `OS_AUTH_URL` | обычно `https://cloud.api.selcloud.ru/identity/v3` |
| `OS_USER_DOMAIN_NAME` | номер аккаунта | | `OS_USER_DOMAIN_NAME` | номер аккаунта |
| `OS_PROJECT_DOMAIN_NAME` | тот же номер (можно не дублировать в нашем `.env`) |
| `OS_PROJECT_ID` | uuid проекта | | `OS_PROJECT_ID` | uuid проекта |
| `OS_USERNAME` | имя сервисного пользователя | | `OS_USERNAME` | имя сервисного пользователя |
| `OS_PASSWORD` | пароль, который ты сохранил (в RC его часто нет — дописываешь сам) | | `OS_PASSWORD` | пароль (в RC часто нет — допиши сам) |
| `OS_REGION_NAME` | пул, `ru-7` | | `OS_REGION_NAME` | пул, например `ru-7` |
| `GPU_RENT_AZ` | **сегмент** пула, `ru-7a` в RC его может не быть, смотри матрицу GPU | | `GPU_RENT_AZ` | **сегмент**, например `ru-7a` (в RC может не быть) |
Официально: [Configure OpenStack CLI](https://docs.selectel.ru/en/cloud-servers/tools/openstack-cli/configure-openstack-cli/). Официально: [Configure OpenStack CLI](https://docs.selectel.ru/en/cloud-servers/tools/openstack-cli/configure-openstack-cli/).
### 3.3. Куда класть ### 3.3. Заполнить `.env`
Создай каталог и файл **вне git**:
```powershell ```powershell
copy env.example .env copy env.example .env
notepad .env notepad .env
``` ```
Вставь значения из RC + пароль + `GPU_RENT_AZ`. Никогда не коммить `.env`. Вставь значения из RC + пароль + `GPU_RENT_AZ`. **Не коммить** `.env`.
Проверка без нашего CLI (необязательно): Проверка: `.\gpu-rent.ps1 doctor` (не обязательно ставить `openstack` CLI).
```powershell
# после pip install python-openstackclient, если хочешь
openstack token issue
openstack flavor list
```
Наш способ: `gpu-rent doctor`.
--- ---
## 4. Civitai API token (модели) ## 4. Civitai API token (модели)
Нужен, если хочешь seed с Civitai по `models.yaml`. Без токена SwarmUI поставит свою дефолтную модель — это допустимо. Нужен, если хочешь seed по `models.yaml`. Без токена SwarmUI поставит свою дефолтную модель — это нормально.
1. Войди на [civitai.com](https://civitai.com) (тот же аккаунт, что и для `.red`). 1. Войди на [civitai.com](https://civitai.com) (тот же аккаунт, что для `.red`).
2. [Account settings](https://civitai.com/user/account) → блок **API Keys****Add API key**. 2. [Account settings](https://civitai.com/user/account) → **API Keys****Add**.
3. Имя, например `gpu-rent`. Токен показывают **один раз**. 3. Токен показывают **один раз** → в `.env`: `CIVITAI_API_TOKEN=...`
4. В `.env`: `CIVITAI_API_TOKEN=...` 4. Хост по умолчанию **`civitai.red`** (полный каталог). С `.com` NSFW часто 404.
5. Хост API по умолчанию **`civitai.red`** (полный каталог). `.com` — SFW-витрина, NSFW с неё часто 404. Один токен на оба домена. 5. Манифест:
Не клади токен в query-string в скриптах «на память» — в логах светится. CLI шлёт `Authorization: Bearer …` только на `civitai.com` / `civitai.red` / `civitai.green`, не на CDN.
Манифест (не в git со своими id, если не хочешь светить вкусы):
```powershell ```powershell
copy models.example.yaml models.yaml copy models.example.yaml models.yaml
``` ```
`version_id: 0` — заглушка, doctor её игнорирует. Нужен **modelVersionId** из URL, не id карточки модели. В URL нужен **`modelVersionId=`**, не id карточки модели. Подробности: [models.md](models.md).
--- ---
## 5. Git-токен (только приватные репы расширений) ## 5. Git-токен (только приватные репы)
Публичные GitHub-репы в `extensions.yaml` клонируются без токена. Публичные репы в `extensions.yaml` клонируются без токена.
Если репа приватная: Приватные: GitHub fine-grained PAT, **Contents: Read**`GIT_TOKEN` в `.env`.
Шаблон: `extensions.example.yaml``extensions.yaml`. Пустой файл = стоковый SwarmUI. См. [extensions.md](extensions.md).
1. GitHub → Settings → Developer settings → Personal access tokens.
2. Fine-grained: доступ только к нужным репам, **Contents: Read**.
3. `GIT_TOKEN` в `.env`.
Скопируй шаблон: `extensions.example.yaml``extensions.yaml` в корне репо. Пустой файл = стоковый SwarmUI.
--- ---
## 6. SSH ## 6. Чеклист перед `doctor`
Ключ **не надо** делать руками. CLI создаст `<repo>\.gpu-rent\id_ed25519` без passphrase и зарегистрирует keypair в OpenStack при первом `up`. `doctor` только проверяет, что это получится. - [ ] Python 3.11+, лаунчер отвечает на `--help`
- [ ] Проект Selectel, uuid скопирован
--- - [ ] Лимит GPU ≥ 1 (или тикет в работе — тогда `doctor` честно скажет «0»)
- [ ] Сервисный пользователь `member`, пароль сохранён
## 7. Чеклист перед `doctor` - [ ] RC с того же **пула**, где GPU
- [ ] `.env`: `OS_*` + `GPU_RENT_AZ`, не `X-Token`
- [ ] Python 3.11+, `pip install -e .` - [ ] (опц.) Civitai + `models.yaml`
- [ ] Проект Selectel, скопирован uuid - [ ] На балансе хватает на **диск ~100 GB 24/7**, даже когда GPU выключен
- [ ] Тикет на лимит **1× GPU** в нужном сегменте (или квота уже > 0)
- [ ] Сервисный пользователь `member` на этот проект, пароль сохранён
- [ ] RC скачан на **тот же пул**, где GPU
- [ ] `.env` в корне репо заполнен (`OS_*` + `GPU_RENT_AZ`)
- [ ] Нет `X-Token` вместо пароля сервисного пользователя
- [ ] (опционально) Civitai token + `models.yaml`
- [ ] На балансе хватает на диск 100 GB **даже когда GPU выключен**
Дальше:
```powershell ```powershell
gpu-rent doctor .\gpu-rent.ps1 doctor
.\gpu-rent.ps1 flavors
.\gpu-rent.ps1 dry-run
``` ```
Exit 0 — облако отвечает, можно идти к spike / `up`, когда команда появится. Exit 1 — в отчёте причина (часто квота GPU = 0). | Результат | Что делать |
| --- | --- |
| exit 0 | можно `up` |
| exit 1 | читай отчёт: чаще всего квота GPU = 0 или неверный пароль / пул |
`gpu-rent dry-run` печатает план без создания сервера. ---
## 7. Первый `up`
Первый прогон долгий (образ, SwarmUI, Comfy, seed): ориентир **2040 минут**. Не закрывай терминал посередине bootstrap.
```powershell
.\gpu-rent.ps1 up --yes
```
Что произойдёт:
1. Снова короткий doctor (кратко; полный — `up -v`).
2. Create/unshelve preemptible GPU + диски.
3. Bootstrap SwarmUI, extensions, autocomplete, Civitai-seed, push локальных папок.
4. Туннель на `localhost:17801`, access-card с URL / MCP.
5. Процесс ждёт: **Ctrl+C** закрывает только туннель, GPU остаётся.
Полезные флаги:
| Флаг | Зачем |
| --- | --- |
| `--no-tunnel` | только облако; UI потом: `tunnel --open` |
| `--no-update` | не `git pull` SwarmUI/extensions |
| `--ollama` | поднять Ollama рядом ([llm.md](llm.md)) |
| `--no-spot` | обычный (не preemptible) тариф |
| `--flavor ID` | явный flavor, без фоллбека |
Двойной клик без аргументов: в `gpu-rent.vars` задай `GPU_RENT_DEFAULT_ARGS=up --yes`. Сам `gpu-rent` без args показывает **help**, не поднимает GPU.
---
## 8. Закончить сессию
```powershell
.\gpu-rent.ps1 stop
```
Удаляет compute (+ FIP по умолчанию), **диски оставляет**. Модели на месте для следующего `up`.
| Команда | Эффект |
| --- | --- |
| `hold` / `hold --minutes 90` | отложить idle-killer |
| `status` | Nova, диск, killer, LLM |
| `destroy --i-understand-data-loss` | stop + удалить диски |
---
## Деньги и риски (кратко)
- **GPU** — пока жив compute.
- **Data-диск** — тарифицируется **всегда** после создания.
- Preemptible ~**24 ч** → `EXPIRED`; `tunnel` или `up` восстановят.
- Idle-killer: после льготы (~45 мин) + ~30 мин пустой очереди → delete compute.
- Цены в OpenStack API нет — смотри панель Selectel.
Дальше: чеклист живого прогона [spike-notes.md](spike-notes.md), справочник команд [cli.md](cli.md).
+63 -26
View File
@@ -1,21 +1,27 @@
# Spike notes (заполнять руками) # Spike notes — первый живой прогон
Без секретов, без личных имён файлов моделей. Цель — один раз пройти панель + CLI на живом GPU и зафиксировать факты для v1. Заполняй **руками** после зелёного `doctor`. Без секретов и без личных имён чекпоинтов.
## 0.0 Квота Подготовка ключей: [setup.md](setup.md). Цель — один раз пройти панель + CLI на реальном GPU и зафиксировать факты (времена, цены, имя образа).
---
## 0. Перед прогоном
| Шаг | Дата | Результат | | Шаг | Дата | Результат |
| --- | --- | --- | | --- | --- | --- |
| Квота GPU в панели (лимит) | | 0 / N | | Квота GPU в панели (лимит) | | 0 / N |
| Тикет в поддержку (номер) | | | | Тикет в поддержку (номер), если был | | |
| Лимит поднят | | | | Сервисный пользователь `member` + `.env` | | да / нет |
| Сервисный пользователь `member` на проект | | | | `gpu-rent doctor` exit 0 | | |
Текст тикета: [setup.md §2.3](setup.md). Текст тикета: [setup.md §2.3](setup.md#23-тикет-в-поддержку).
## Preflight CLI ---
```text ## 1. Preflight
```powershell
.\gpu-rent.ps1 doctor .\gpu-rent.ps1 doctor
.\gpu-rent.ps1 flavors .\gpu-rent.ps1 flavors
.\gpu-rent.ps1 dry-run .\gpu-rent.ps1 dry-run
@@ -25,17 +31,21 @@
| --- | --- | --- | | --- | --- | --- |
| Keystone token | | | | Keystone token | | |
| GPU quota > 0 | | | | GPU quota > 0 | | |
| Flavor из FLAVOR_PREFERENCE | | id / имя | | Flavor из `FLAVOR_PREFERENCE` | | id / имя |
| Image Driver 580 (без Docker) | | имя образа | | Image Driver 580 (без Docker) | | имя образа |
| Volume type в AZ | | | | Volume type в AZ | | |
| Civitai `.red` + token | | | | Civitai `.red` + token (если используешь) | | |
## Первый `up` ---
```text ## 2. Первый `up`
```powershell
.\gpu-rent.ps1 up --yes .\gpu-rent.ps1 up --yes
``` ```
Первый bootstrap долгий (ориентир 20–40 мин). Не рви терминал посередине.
| Метрика | Значение | | Метрика | Значение |
| --- | --- | | --- | --- |
| Flavor фактически | | | Flavor фактически | |
@@ -44,13 +54,18 @@
| SSH через (мин) | | | SSH через (мин) | |
| Backend Idle через (мин) | | | Backend Idle через (мин) | |
| Seed Civitai (мин / GB) | | | Seed Civitai (мин / GB) | |
| Boot snapshot создался | да / нет / ошибка | | Boot snapshot `gpu-rent-boot-ok` | да / нет / ошибка |
| ₽/час GPU (панель) | | | ₽/час GPU (панель) | |
| ₽/мес data disk | | | ₽/мес data disk (панель) | |
| Access-card / URL :17801 | да / нет |
## Туннель и API ---
```text ## 3. Туннель и API
Если закрыл туннель (Ctrl+C):
```powershell
.\gpu-rent.ps1 tunnel --open .\gpu-rent.ps1 tunnel --open
``` ```
@@ -59,31 +74,53 @@
| UI http://127.0.0.1:17801 | | | UI http://127.0.0.1:17801 | |
| `/API/GetNewSession` | | | `/API/GetNewSession` | |
| MCP `/mcp` (если нужно) | | | MCP `/mcp` (если нужно) | |
| `nvidia-smi` по `gpu-rent ssh` | | | `nvidia-smi` через `gpu-rent ssh` | |
## Idle-killer / hold ---
## 4. Idle-killer / hold
| Шаг | OK? | Заметка | | Шаг | OK? | Заметка |
| --- | --- | --- | | --- | --- | --- |
| `systemctl status gpu-rent-idle-killer.timer` | | | | `systemctl status gpu-rent-idle-killer.timer` (по SSH) | | |
| `gpu-rent hold` пишет hold-until | | | | `gpu-rent hold` пишет hold-until | | |
| Качалка модели в UI → нужен hold? | | нет сигнала API — только hold | | Качалка модели в UI | | hold, если killer не видит busy |
## Preempt / диски ---
## 5. Preempt / диски / stop
| Шаг | OK? | | Шаг | OK? |
| --- | --- | | --- | --- |
| EXPIRED → tunnel unshelve или `up` | | | EXPIRED → `tunnel` unshelve или `up` | |
| `stop` — диски живы | | | `stop` — диски живы | |
| Второй `up` — те же модели на data | | | Второй `up` — те же модели на data | |
| Boot из snapshot `gpu-rent-boot-ok` | | | Boot из snapshot `gpu-rent-boot-ok` | |
## Выводы для кода ---
## 6. (Опц.) LLM
```powershell
.\gpu-rent.ps1 up --yes --ollama
```
| Шаг | OK? |
| --- | --- |
| Ollama на :17811 через туннель | |
| `ollama pull` из манифеста | |
| `LLM_RUNTIME=none` гасит unit на следующем up | |
См. [llm.md](llm.md).
---
## Выводы для кода / конфига
- Имя GPU-образа в пуле: - Имя GPU-образа в пуле:
- Реальные flavor id (не в git): - Реальные flavor id (не в git):
- Хватает ли `IDLE_GRACE_MINUTES=45`: - Хватает ли `IDLE_GRACE_MINUTES=45`:
- Application credential: ок / нужны другие права: - Application credential с узкими access_rules: ок / ошибка (fail closed):
- Прочее: - Прочее:
Когда spike закрыт — отметь пункты в [roadmap.md](roadmap.md) §0 / §0.0. Когда spike закрыт — отметь пункты в [roadmap.md](roadmap.md) §0.
+7 -1
View File
@@ -2,8 +2,14 @@
# 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
# — clone only when LLM_RUNTIME matches (ollama / llamacpp / either)
swarmui: [] swarmui:
- url: https://gitea.hsrv.site/mrleo1nid/swarm-assistent.git
ref: main
dir: swarm-assistent
requires: ollama
# - url: https://github.com/org/SwarmUI-MyExt.git # - url: https://github.com/org/SwarmUI-MyExt.git
# ref: main # ref: main
# dir: MyExt # dir: MyExt
+31
View File
@@ -45,12 +45,16 @@ class ModelEntry:
url: str | None url: str | None
VALID_REQUIRES = frozenset({"none", "ollama", "llamacpp", "any-llm"})
@dataclass @dataclass
class GitRepo: class GitRepo:
kind: str kind: str
url: str url: str
ref: str ref: str
directory: str | None directory: str | None
requires: str = "none"
def _load_yaml(path: Path) -> Any: def _load_yaml(path: Path) -> Any:
@@ -96,6 +100,28 @@ def parse_models(path: Path) -> list[ModelEntry]:
return entries return entries
def normalize_requires(value: object | None) -> str:
raw = str(value or "none").strip().lower().replace("_", "-")
if raw in {"", "none", "always", "any"}:
return "none"
if raw in VALID_REQUIRES:
return raw
raise ConfigError(
f"requires={value!r}: жду none|ollama|llamacpp|any-llm"
)
def repo_matches_runtime(repo: GitRepo, llm_runtime: str) -> bool:
"""Whether this extension should be cloned for the active LLM_RUNTIME."""
req = normalize_requires(repo.requires)
runtime = (llm_runtime or "none").strip().lower()
if req == "none":
return True
if req == "any-llm":
return runtime in {"ollama", "llamacpp"}
return runtime == req
def parse_extensions(path: Path) -> list[GitRepo]: def parse_extensions(path: Path) -> list[GitRepo]:
data = _load_yaml(path) data = _load_yaml(path)
if not data: if not data:
@@ -112,12 +138,17 @@ def parse_extensions(path: Path) -> list[GitRepo]:
for item in items: for item in items:
if not isinstance(item, dict) or not item.get("url"): if not isinstance(item, dict) or not item.get("url"):
raise ConfigError(f"{path}: у {kind} нужен url") raise ConfigError(f"{path}: у {kind} нужен url")
try:
requires = normalize_requires(item.get("requires"))
except ConfigError as exc:
raise ConfigError(f"{path}: {exc}") from exc
repos.append( repos.append(
GitRepo( GitRepo(
kind=kind, kind=kind,
url=str(item["url"]).strip(), url=str(item["url"]).strip(),
ref=str(item.get("ref") or "main"), ref=str(item.get("ref") or "main"),
directory=str(item["dir"]) if item.get("dir") else None, directory=str(item["dir"]) if item.get("dir") else None,
requires=requires,
) )
) )
return repos return repos
+16 -1
View File
@@ -20,6 +20,8 @@ from gpu_rent.manifests import (
parse_extensions, parse_extensions,
parse_models, parse_models,
remote_root_for, remote_root_for,
repo_dirname,
repo_matches_runtime,
) )
from gpu_rent.ssh_ops import put_text, remote_exists, run_python, run_ssh from gpu_rent.ssh_ops import put_text, remote_exists, run_python, run_ssh
from gpu_rent.sync_files import pull_tree, push_tree from gpu_rent.sync_files import pull_tree, push_tree
@@ -34,8 +36,21 @@ def _pkg_text(name: str) -> str:
def seed_extensions(cfg: Config, host: str, log: Log, *, update: bool = True) -> bool: def seed_extensions(cfg: Config, host: str, log: Log, *, update: bool = True) -> bool:
repos = parse_extensions(cfg.extensions_manifest) from gpu_rent.llm_runtime import normalize_runtime
runtime = normalize_runtime(cfg.llm_runtime)
all_repos = parse_extensions(cfg.extensions_manifest)
repos = [r for r in all_repos if repo_matches_runtime(r, runtime)]
skipped = [r for r in all_repos if r not in repos]
for repo in skipped:
log(
f"extensions: пропуск {repo_dirname(repo)} "
f"(requires={repo.requires}, LLM_RUNTIME={runtime})"
)
if not repos and not update: if not repos and not update:
if all_repos:
log("extensions: все строки отфильтрованы по requires — стоковый SwarmUI")
else:
log("extensions.yaml пуст — стоковый SwarmUI") log("extensions.yaml пуст — стоковый SwarmUI")
return False return False
jobs = [] jobs = []
+50 -1
View File
@@ -1,6 +1,9 @@
from pathlib import Path from pathlib import Path
from gpu_rent.manifests import parse_extensions, parse_models import pytest
from gpu_rent.errors import ConfigError
from gpu_rent.manifests import parse_extensions, parse_models, repo_matches_runtime
def test_models_skips_version_zero(tmp_path: Path): def test_models_skips_version_zero(tmp_path: Path):
@@ -30,3 +33,49 @@ def test_extensions_repo(tmp_path: Path):
repos = parse_extensions(path) repos = parse_extensions(path)
assert repos[0].kind == "swarmui" assert repos[0].kind == "swarmui"
assert repos[0].directory == "Ext" assert repos[0].directory == "Ext"
assert repos[0].requires == "none"
def test_extensions_requires_ollama(tmp_path: Path):
path = tmp_path / "extensions.yaml"
path.write_text(
"swarmui:\n"
" - url: https://gitea.example/swarm-assistent.git\n"
" ref: main\n"
" dir: swarm-assistent\n"
" requires: ollama\n"
" - url: https://github.com/org/Always.git\n"
" ref: main\n",
encoding="utf-8",
)
repos = parse_extensions(path)
assert repos[0].requires == "ollama"
assert repos[1].requires == "none"
assert repo_matches_runtime(repos[0], "ollama")
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], "ollama")
def test_extensions_requires_any_llm(tmp_path: Path):
path = tmp_path / "extensions.yaml"
path.write_text(
"swarmui:\n - url: https://example/x.git\n requires: any-llm\n",
encoding="utf-8",
)
repo = parse_extensions(path)[0]
assert repo.requires == "any-llm"
assert repo_matches_runtime(repo, "ollama")
assert repo_matches_runtime(repo, "llamacpp")
assert not repo_matches_runtime(repo, "none")
def test_extensions_requires_invalid(tmp_path: Path):
path = tmp_path / "extensions.yaml"
path.write_text(
"swarmui:\n - url: https://example/x.git\n requires: docker\n",
encoding="utf-8",
)
with pytest.raises(ConfigError, match="requires"):
parse_extensions(path)