diff --git a/README.md b/README.md index 4f0661a..6db8696 100644 --- a/README.md +++ b/README.md @@ -1,75 +1,81 @@ # gpu-rent -Прерываемый GPU в [Selectel](https://selectel.ru) + SwarmUI на `http://127.0.0.1:17801`. -Модели и ComfyUI живут на сетевом диске — платишь за GPU только пока он поднят. +Прерываемый GPU в [Selectel](https://selectel.ru) + [SwarmUI](https://github.com/mcmonkeyprojects/SwarmUI) на `http://127.0.0.1:17801`. -**Сначала ключи и квота GPU, не `up`.** → [`docs/setup.md`](docs/setup.md) +Модели и ComfyUI живут на сетевом диске. **GPU платится только пока жив compute** (`up`…`stop` или idle-killer). Диск — **всегда**, даже после `stop`. --- -## Быстрый старт +## С нуля → первая картинка -```powershell -.\gpu-rent.ps1 doctor # preflight -.\gpu-rent.ps1 flavors # что выберет фоллбек -.\gpu-rent.ps1 dry-run # план без create -.\gpu-rent.ps1 up --yes # GPU + SwarmUI + seed + туннель :17801 -.\gpu-rent.ps1 up --yes --no-tunnel # только облако -.\gpu-rent.ps1 tunnel --open # снова открыть UI, если туннель закрыли -.\gpu-rent.ps1 hold # отложить idle-killer -.\gpu-rent.ps1 stop # гасить compute, диски оставить -``` +Делай по порядку. Подробности и скрины панели — в [docs/setup.md](docs/setup.md). + +| # | Шаг | Команда / действие | +| --- | --- | --- | +| 1 | Python 3.11+, клон репо | — | +| 2 | Квота GPU в Selectel (> 0) или тикет | [setup §2](docs/setup.md#2-selectel-проект-и-квота-gpu) | +| 3 | Сервисный пользователь + `.env` из RC | [setup §3](docs/setup.md#3-ключи-openstack-это-и-есть-api-key-для-gpu-rent) | +| 4 | (Опц.) Civitai token + `models.yaml` | [setup §4](docs/setup.md#4-civitai-api-token-модели) | +| 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`). -Лаунчер сам создаёт `.venv` и ставит пакет. Конфиг — **в корне репо**: - -| Файл | Что | -| --- | --- | -| `.env` | из `env.example` — `OS_*`, Civitai | -| `models.yaml` | из `models.example.yaml` — seed с Civitai | -| `extensions.yaml` | из `extensions.example.yaml` | -| `.gpu-rent/` | state, SSH-ключ (gitignore) | +**Не начинай с `up`**, пока `doctor` не зелёный. Ключ панели `X-Token` для этого CLI **не подходит**. --- -## Команды +## Обычный день + +```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, манифесты | -| `flavors` | Живой список по `FLAVOR_PREFERENCE` | -| `up` / `up --yes` | Поднять GPU + туннель :17801 (браузер). Ctrl+C = туннель off | -| `up --no-tunnel` | Только облако | -| `up --no-spot` / `up --flavor ID` | Обычный тариф / явный flavor | -| `tunnel` / `tunnel --open` | Повторный проброс; watchdog на EXPIRED | -| `open` | Браузер на 17801 | -| `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` | Диск только вверх | +| `setup` | Wizard: `.env`, LLM, watchdog | +| `doctor` | Preflight без create | +| `flavors` / `dry-run` | Что выберет / план без денег | +| `up` / `up --yes` | GPU + seed + туннель | +| `up --ollama` | + Ollama (см. [docs/llm.md](docs/llm.md)) | +| `tunnel` / `open` | Снова UI / браузер | +| `hold` / `status` | Пауза killer / состояние | | `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. -- Preemptible по умолчанию (~24 ч) → `EXPIRED`; `tunnel` сам unshelve, иначе `up`. -- Idle-killer: льгота после boot, потом N мин пустой очереди → delete. `hold` откладывает. -- `Ctrl+C` на tunnel **не** гасит GPU. +| Файл | Что | +| --- | --- | +| `.env` | из `env.example` — `OS_*`, Civitai (не в git) | +| `gpu-rent.vars` | из example — несекретные дефолты, лаунчер | +| `models.yaml` / `extensions.yaml` | манифесты (из `*.example.yaml`) | +| `Models/` … | локальный push на `up` | +| `.gpu-rent/` | state, SSH-ключ (gitignore) | ---- - -## Модели - -1. Civitai-seed по `models.yaml` (нужен `CIVITAI_API_TOKEN`, хост `civitai.red`). -2. Локальные `Models/`, `Wildcards/`, `CustomWorkflows/` — непустые едут на каждый `up`. - -Пример в репо уже с Krea 2 + LoRA — скопируй в `models.yaml`. +Лаунчер сам создаёт `.venv` и ставит пакет. --- @@ -77,10 +83,11 @@ Unix: `./gpu-rent.sh …` (один раз `chmod +x gpu-rent.sh`). | Док | Зачем | | --- | --- | -| [docs/setup.md](docs/setup.md) | Ключи, квота, RC | -| [docs/spike-notes.md](docs/spike-notes.md) | Чеклист живого spike | -| [docs/cli.md](docs/cli.md) | Все команды и `.env` | -| [docs/decisions.md](docs/decisions.md) | Продуктовые решения | -| [docs/roadmap.md](docs/roadmap.md) | Что готово / что нет | +| [docs/setup.md](docs/setup.md) | **Пошаговая подготовка** до первого `up` | +| [docs/cli.md](docs/cli.md) | Все команды и переменные | +| [docs/llm.md](docs/llm.md) | Ollama / llama.cpp | +| [docs/models.md](docs/models.md) | Civitai + папка `Models/` | +| [docs/spike-notes.md](docs/spike-notes.md) | Чеклист первого живого прогона | +| [docs/README.md](docs/README.md) | Оглавление всего `docs/` | MIT. Секреты и веса в git не коммитить. diff --git a/docs/README.md b/docs/README.md index a4cc13c..6182fbf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,39 +1,45 @@ # Документация 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 | -| [spike-notes.md](spike-notes.md) | Чеклист первого живого прогона | -| [concept.md](concept.md) | Задача, границы, модель стоимости | -| [decisions.md](decisions.md) | Зафиксированные решения | -| [architecture.md](architecture.md) | Компоненты, диски, стейт-машина, idle-killer | -| [selectel.md](selectel.md) | Контракт с облаком: auth, API, GPU, preemptible, сеть | -| [models.md](models.md) | Первый seed с Civitai, манифест, пропуск дефолта SwarmUI | -| [extensions.md](extensions.md) | Git-репы SwarmUI-расширений и ComfyUI nodes | -| [autocomplete.md](autocomplete.md) | Word-list промптов, Settings.fds, проверка версии | -| [local-folders.md](local-folders.md) | `Models/` / `Wildcards/` / `CustomWorkflows/` push, optional `Output/` pull | -| [swarmui.md](swarmui.md) | Нативный install, порт 7801, API, MCP | -| [llm.md](llm.md) | Opt-in Ollama / llama.cpp, `ollama-models.yaml`, порты 17811/17812 | -| [cli.md](cli.md) | Команды, конфиг, локальный state | -| [roadmap.md](roadmap.md) | Порядок реализации | -| [open-questions.md](open-questions.md) | Ещё не закрыто | +| Понять зачем и сколько стоит | [concept.md](concept.md) | +| Все команды и `.env` | [cli.md](cli.md) | +| Модели с Civitai / `Models/` | [models.md](models.md) | +| Git-расширения SwarmUI/Comfy | [extensions.md](extensions.md) | +| Word-list промптов | [autocomplete.md](autocomplete.md) | +| Push/pull папок | [local-folders.md](local-folders.md) | +| Ollama / llama.cpp | [llm.md](llm.md) | +| Как устроены диски и killer | [architecture.md](architecture.md) | +| Контракт Selectel | [selectel.md](selectel.md) | +| Нативный SwarmUI на VM | [swarmui.md](swarmui.md) | +| Зафиксированные решения | [decisions.md](decisions.md) | +| Статус реализации | [roadmap.md](roadmap.md) | +| Что ещё проверить на spike | [open-questions.md](open-questions.md) | -## Источники +Ревью кода: `docs/reviews/` (для разработки, не для первого запуска). + +## Источники (Selectel / SwarmUI / Civitai) Проверено 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 images / drivers](https://docs.selectel.ru/en/cloud-servers/manage/install-gpu-drivers/) -- [Selectel: IAM / API auth](https://docs.selectel.ru/en/api/authorization/) +- [Selectel: GPU drivers](https://docs.selectel.ru/en/cloud-servers/manage/install-gpu-drivers/) +- [Selectel: API auth](https://docs.selectel.ru/en/api/authorization/) - [Selectel: restore preemptible](https://docs.selectel.ru/en/cloud-servers/manage/restore-preemptible-server/) -- [SwarmUI](https://github.com/mcmonkeyprojects/SwarmUI) (нативный Linux, порт 7801) -- [Civitai Site API](https://developer.civitai.com/) (download + metadata; API есть и на `.red`) -- [Civitai: два входа .com / .red](https://civitai.com/articles/28369/two-front-doors-civitaicom-civitaired-and-whats-next) +- [SwarmUI](https://github.com/mcmonkeyprojects/SwarmUI) +- [Civitai Site API](https://developer.civitai.com/) +- [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) diff --git a/docs/architecture.md b/docs/architecture.md index f865ff4..9ff3dec 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -17,8 +17,9 @@ ┌─ Selectel, сегмент пула (например ru-7a) ────────────────────┐ │ GPU VM (tag preemptible + gpu-rent) │ │ SwarmUI : 127.0.0.1:7801 │ -│ idle-killer: очередь / hold / качалка в UI → delete self │ -│ application credential (compute delete/shelve only) │ +│ idle-killer: очередь / hold / LLM busy → delete this server │ +│ app cred: DELETE/GET только этот server_id (fail closed) │ +│ optional: Ollama :11434 / llama.cpp :8080 (туннель 17811/12)│ │ │ │ boot volume (network) ОС + NVIDIA + SwarmUI нативно + snapshot │ │ data volume (network) Models, Output, Data, workflows │ diff --git a/docs/autocomplete.md b/docs/autocomplete.md index b021bf5..fa935cc 100644 --- a/docs/autocomplete.md +++ b/docs/autocomplete.md @@ -2,7 +2,9 @@ Официальная инструкция: [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 diff --git a/docs/cli.md b/docs/cli.md index 23ea4b3..1600290 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -1,144 +1,153 @@ # 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 doctor` | Preflight **без** create: Keystone, квота GPU, flavor в AZ, диски, Civitai token+`.red`, манифесты, SSH-ключ. Код выхода ≠ 0, если сессию нельзя начать | -| `gpu-rent setup` | Wizard: `.env`/манифесты, `LLM_RUNTIME`, пресет ollama-models, опционально local-watchdog | -| `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 ` | 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 setup` | Wizard: манифесты, `LLM_RUNTIME`, пресет ollama-models, опционально local-watchdog | +| `gpu-rent doctor` | Preflight **без** create. Exit ≠ 0 → сессию начинать нельзя | +| `gpu-rent flavors` | Скан `SCAN_POOLS` × `FLAVOR_PREFERENCE`, список в текущем регионе | | `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` -Не создаёт сервер, не тратит GPU. Нужен до первого `up` и когда «вчера работало». +Не создаёт сервер и не тратит GPU. Нужен до первого `up` и когда «вчера работало». -Проверки по порядку, все печатаются (не падать на первой, собрать отчёт): +Проверки (все печатаются в отчёт): -1. Читается `.env`, обязательные `OS_*`. -2. Keystone: токен выдаётся. -3. Квота GPU > 0; иначе текст «напишите в поддержку Selectel». -4. В сегменте есть хотя бы один flavor из `FLAVOR_PREFERENCE` (не `disabled`). -5. Volume type сегмента существует; data 100 GB влезает в квоту дисков. -6. SSH-ключ: есть `/.gpu-rent/id_ed25519` или CLI сможет его создать. -7. Если задан `CIVITAI_API_TOKEN` — HEAD/лёгкий запрос к `CIVITAI_API_HOST` (дефолт `.red`); манифест парсится. -8. `extensions.yaml` парсится, если файл есть. -9. Локальные папки: предупреждение, если `Models/` огромный относительно свободного места на будущем диске. +1. Читается `.env`, обязательные `OS_*` +2. Keystone выдаёт токен +3. Квота GPU > 0 (иначе текст про поддержку Selectel) +4. В сегменте есть flavor из `FLAVOR_PREFERENCE` +5. Volume type / место под data ~100 GB +6. SSH-ключ есть или будет создан в `.gpu-rent/` +7. Если есть `CIVITAI_API_TOKEN` — доступ к API-хосту; манифест парсится +8. `extensions.yaml` парсится, если есть +9. Предупреждения по огромным локальным `Models/` -Exit 0 — можно `up`. Exit 1 — нельзя, причина в отчёте. +Exit 0 → можно `up`. Exit 1 → причина в таблице / кратком списке на `up`. -## `status` +--- -Без туннеля. Если SSH есть — ещё диск и killer; если нет — только OpenStack. +## `status` и `hold` -| Поле | Откуда | -| --- | --- | -| Фаза 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` | +**status** — без туннеля. При SSH: ещё `df` диска и состояние killer; иначе только OpenStack + локальный state. -## `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`, можно вызвать его внутри). -2. Если квота 0 — стоп с текстом «напишите в поддержку Selectel», не traceback. -3. Выбранный flavor (или фоллбек) + оценка: GPU ₽/час, диск ₽/мес, preemptible 24 ч, idle-killer через N мин. -4. Фазы: provisioning → bootstrap → clone extensions → autocomplete → seed Civitai → push локальных папок → waiting UI → backend Idle. -5. Если `NOTIFY_READY` — Windows toast + звук (и строка в лог). -6. Готово: +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 сам -```text -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 -``` +Без `install` поведение прежнее. При `up --no-tunnel` lease не вооружается. -Сниппет MCP для Cursor — в stdout, `mcp.json` не редактировать. +--- ## Конфигурация | Файл | Назначение | | --- | --- | | `.env` | Секреты и OpenStack (`OS_*`, токены). Не в git | -| `gpu-rent.vars` | Параметры запуска / несекретные дефолты. Читают `gpu-rent.ps1` / `.sh` / `.bat` и CLI. Пример: `gpu-rent.vars.example` | -| `models.yaml` / `extensions.yaml` | Манифесты | +| `gpu-rent.vars` | Несекретные дефолты; читают лаунчеры и CLI. Пример: `gpu-rent.vars.example` | +| `models.yaml` / `extensions.yaml` / `ollama-models.yaml` | Манифесты | -`.env` в корне репозитория (рядом с `env.example`). - -В `gpu-rent.vars` (создаётся из example при первом запуске): +### Лаунчер (`gpu-rent.vars`) ```env -# Двойной клик / запуск лаунчера без аргументов (CLI сам по себе показывает help): +# Двойной клик / запуск без аргументов у .ps1/.bat/.sh: GPU_RENT_DEFAULT_ARGS=up --yes # Дописать ко всем вызовам: # GPU_RENT_EXTRA_ARGS=--no-update UPDATE_GIT=true +LLM_RUNTIME=none ``` +### Основные переменные (`.env` / vars) + ```env OS_AUTH_URL=https://cloud.api.selcloud.ru/identity/v3 OS_USER_DOMAIN_NAME= @@ -150,6 +159,8 @@ GPU_RENT_AZ=ru-7a SSH_PRIVATE_KEY_PATH= SSH_USER=ubuntu +# Пусто = авто /32 твоего IP. Для spike/WARP иногда 0.0.0.0/0: +GPU_RENT_SSH_CIDR= BOOT_VOLUME_ID= DATA_VOLUME_ID= @@ -167,46 +178,57 @@ EXTENSIONS_MANIFEST= GIT_TOKEN= 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 +LLM_RUNTIME=none +OLLAMA_LOCAL_PORT=17811 +LLAMACPP_LOCAL_PORT=17812 +UPDATE_GIT=true DEFAULT_FLAVOR_ID= FLAVOR_PREFERENCE=4090-24,4090-48,a5000,a100-40 FLAVOR_FALLBACK=true +SCAN_POOLS=ru-6,ru-7 DEFAULT_SPOT=true KEEP_FLOATING_IP=false IDLE_MINUTES=30 IDLE_GRACE_MINUTES=45 +# LOCAL_WATCHDOG_GRACE_MINUTES=10 PULL_OUTPUT=false 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 -`/.gpu-rent/state.json` — ids, фаза (`idle` / `ready_cloud` / `ready_tunneled` / …), flavor, timestamps create/unshelve (для окна 24 ч). Без паролей. Lockfile там же, чтобы два `up` не создали два сервера. +`/.gpu-rent/state.json` — ids, фаза, timestamps (окно preempt 24 ч). Без паролей. Lockfile рядом — два `up` не создадут два сервера. + +--- ## Ожидание готовности -Потолок ~20–40 мин на первый bootstrap, ~5–10 мин на unshelve: +Ориентир: **~20–40 мин** первый bootstrap, **~5–10 мин** unshelve. + +Порядок готовности: 1. Nova `ACTIVE` -2. TCP 22 -3. SSH: cloud-init или `systemctl is-active swarmui` -4. HTTP `http://127.0.0.1:7801` **на VM** (через SSH), не путать с локальным 7801 -5. Для MCP/API / toast — дождаться Idle backend. +2. TCP 22 / SSH +3. HTTP `http://127.0.0.1:7801` **на VM** (через SSH) +4. Backend Idle → toast (если `NOTIFY_READY`) + access-card -## Windows +Локальный порт UI: **17801** (на VM по-прежнему 7801 на loopback). -Лаунчеры в корне: `gpu-rent.bat` (cmd / двойной клик), `.\gpu-rent.ps1` (PowerShell). На Unix — `./gpu-rent.sh`. Они создают `.venv` в репозитории и вызывают `python -m gpu_rent`. Ключ генерирует CLI: `/.gpu-rent/id_ed25519`. +--- -`NOTIFY_READY`: toast через WinRT / `win10toast` (что заработает на 10/11 без админ-прав) + системный звук. Если toast недоступен — только звук и лог, не падать. +## Windows / notify + +`NOTIFY_READY`: toast + системный звук, когда backend Idle. Если toast недоступен — только звук и лог, без падения CLI. diff --git a/docs/concept.md b/docs/concept.md index 424ba5b..9df1069 100644 --- a/docs/concept.md +++ b/docs/concept.md @@ -24,6 +24,7 @@ GPU в облаке дорогой. Веса для генерации карт - Первый clone git-реп расширений SwarmUI и ComfyUI nodes из `extensions.yaml`. - Autocomplete: word-list в `Data/Autocompletions` до старта UI, на каждом `up` проверка новой версии. - `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. - Snapshot boot-диска после первого удачного bootstrap. diff --git a/docs/decisions.md b/docs/decisions.md index 446a3fe..0e796b5 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -32,11 +32,13 @@ | Wildcards / workflows | Как Models: пустые папки не трогаем; появился файл — push на `up` | | Idle hold | `gpu-rent hold [--minutes N]` сдвигает дедлайн killer. Качалка моделей в UI SwarmUI тоже считается занятостью (сигнал уточнить на spike) | | 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` берёт первый доступный из списка | -| Готово | Windows toast + звук (и лог), когда backend Idle | -| Status | Диск used/free, ₽/час, сколько до 24 ч preempt, состояние killer / hold | +| Готово | Windows toast + звук (и лог), когда backend Idle; access-card с URL/MCP | +| Status | Диск used/free, ₽/час, сколько до 24 ч preempt, состояние killer / hold / LLM | | 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). 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, диски оставить. -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-сессию, а не произвольные машины проекта. diff --git a/docs/extensions.md b/docs/extensions.md index d35f177..e417b18 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -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`): @@ -23,15 +33,28 @@ ```yaml 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 - ref: main # ветка, тег или commit SHA; по умолчанию HEAD default branch - dir: SomeExt # имя папки; по умолчанию последний сегмент URL без .git + ref: main + dir: SomeExt comfy: - url: https://github.com/example/ComfyUI-Something.git 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 сверх примера. ## Когда в жизненном цикле diff --git a/docs/llm.md b/docs/llm.md index 519a187..561a564 100644 --- a/docs/llm.md +++ b/docs/llm.md @@ -1,79 +1,122 @@ # LLM рядом со SwarmUI (opt-in) -По умолчанию поднимается только SwarmUI. Ollama или llama.cpp — по флагу, `LLM_RUNTIME` в `gpu-rent.vars` / `.env`, или через wizard. +По умолчанию поднимается **только SwarmUI**. Ollama или llama.cpp — отдельно, когда нужны (помощь с промптами и т.п.). -## Включение +--- + +## Быстрый путь + +### Вариант A — wizard ```text -gpu-rent setup # спросит none/ollama/llamacpp + пресет моделей -gpu-rent up --ollama # разово -gpu-rent up --llm llamacpp -# или в gpu-rent.vars: +gpu-rent setup +``` + +Выбери `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 ``` -Без параметров `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 | -| Ollama | 11434 | 17811 | -| llama.cpp | 8080 | 17812 | +| SwarmUI | 7801 | **17801** | +| Ollama | 11434 | **17811** | +| llama.cpp | 8080 | **17812** | ```text gpu-rent tunnel -gpu-rent open --llm # http://127.0.0.1:17811 (Ollama) -# клиент: -set OLLAMA_HOST=http://127.0.0.1:17811 +gpu-rent open --llm ``` -## Ollama models +Клиент Ollama: -Как Civitai `models.yaml`: +```text +# Windows PowerShell +$env:OLLAMA_HOST = "http://127.0.0.1:17811" +``` -- `ollama-models.example.yaml` — в git -- `ollama-models.yaml` — локальный (gitignore) +После успешного `up`/`tunnel` CLI печатает access-card со всеми URL и MCP-сниппетом. -На `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 | зачем | | --- | --- | --- | -| **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` | быстрее, слабее | -| stock | `qwen2.5:7b` | официальный, больше цензуры | +| stock | `qwen2.5:7b` | официальный | | 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-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 -# Ollama — GitHub release + checksum OLLAMA_VERSION=0.6.5 OLLAMA_SHA256= -# llama.cpp — tag или прямой URL + checksum LLAMACPP_TAG=b4690 -# или: -LLAMACPP_ASSET_URL=https://github.com/ggerganov/llama.cpp/releases/download/... -LLAMACPP_SHA256= +# или LLAMACPP_ASSET_URL=... + LLAMACPP_SHA256=... ``` - -Без этих переменных в логе будет `WARN` про отсутствие pin/checksum. diff --git a/docs/local-folders.md b/docs/local-folders.md index 1feedf6..fab4ff0 100644 --- a/docs/local-folders.md +++ b/docs/local-folders.md @@ -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 | Направление | Обязательно | | --- | --- | --- | --- | diff --git a/docs/models.md b/docs/models.md index fa26c01..5d9cf88 100644 --- a/docs/models.md +++ b/docs/models.md @@ -2,10 +2,21 @@ Два источника на data volume, они **складываются**, не заменяют друг друга: -1. **Civitai-seed** (ниже) — один раз на пустой диск, если есть API-токен и манифест. -2. **Папка `Models/` в корне gpu-rent** — на каждом `up` выгрузить на VM только то, что изменилось. `Wildcards/` и `CustomWorkflows/` — то же правило, см. [local-folders.md](local-folders.md). +1. **Civitai-seed** (ниже) — на пустой диск при bootstrap, если есть API-токен и манифест. +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` приложения. ## Локальная папка → сервер diff --git a/docs/open-questions.md b/docs/open-questions.md index 84b28ee..8d68b66 100644 --- a/docs/open-questions.md +++ b/docs/open-questions.md @@ -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`. -- Реальная цена 100 GB сетевого диска в выбранном сегменте и цена 1×4090 preemptible ₽/час (в OpenStack API нет). -- Имя GPU-образа (без Docker) и flavor id в твоём пуле (в git не класть); какие из списка фоллбека реально есть. -- Хватает ли application credential с access_rules (delete/GET server) на Selectel — при отказе CLI падает назад на unrestricted cred и пишет в лог. +- Качалка модели в UI: отдельного poll-API нет → v1 считает busy через очередь / loading / backend≠idle; иначе пользователь жмёт `hold`. +- Реальная цена 100 GB сетевого диска и 1×4090 preemptible ₽/час в твоём сегменте (в OpenStack API нет — только панель). +- Имя GPU-образа (без Docker) и flavor id в твоём пуле (в git не класть); какие из `FLAVOR_PREFERENCE` реально есть. +- 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. -Если spike покажет, что 45 минут льготы мало на первую установку ComfyUI — поднять `IDLE_GRACE_MINUTES`, не отключать killer. +Если spike покажет, что 45 минут льготы мало на первую установку ComfyUI — подними `IDLE_GRACE_MINUTES`, не отключай killer. diff --git a/docs/roadmap.md b/docs/roadmap.md index b7b166b..fe31f4e 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,16 +1,17 @@ # 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 -- [x] Квота GPU в аккаунте в порядке (подтверждено) -- [ ] Тикет в поддержку Selectel — не нужен, если лимит уже > 0 -- [ ] Сервисный пользователь с правом `member` на проект + `.env` заполнен +- [ ] Квота GPU в панели ≥ 1 (иначе тикет — текст в setup §2.3) +- [ ] Тикет закрыт / не нужен +- [ ] Сервисный пользователь `member` + `.env` заполнен +- [ ] `gpu-rent doctor` exit 0 -`doctor` / `flavors` / `dry-run` — дальше; `up` после зелёного doctor. +`flavors` / `dry-run` — по желанию перед первым `up`. ## 0. Ручной spike в панели diff --git a/docs/setup.md b/docs/setup.md index a362758..ee48f51 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -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». -В корне репозитория достаточно лаунчера — venv и `pip install` он сделает сам: +В корне репозитория: ```powershell .\gpu-rent.ps1 --help -.\gpu-rent.ps1 doctor ``` ```bat @@ -26,11 +44,24 @@ chmod +x gpu-rent.sh ./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%`: только `/.env` и `/.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): +Секреты только в `/.env` и runtime в `/.gpu-rent/` (оба в `.gitignore`). + +Опционально wizard (после того как `.env` хотя бы частично заполнен): + +```powershell +.\gpu-rent.ps1 setup +``` + +Спросит LLM (none/ollama/llamacpp) и local-watchdog. Можно пропустить и настроить позже. + +Для разработки / pytest: ```powershell python -m venv .venv @@ -44,171 +75,194 @@ python -m gpu_rent --help ## 2. Selectel: проект и квота GPU -На новых аккаунтах лимит GPU почти всегда **0**. Без тикета в поддержку `up` создать карту не сможет — это норма, не баг CLI. +На новых аккаунтах лимит GPU часто **0**. Без поднятия лимита `up` не создаст сервер — это нормально, не баг CLI. ### 2.1. Проект -1. Панель → сверху **IAM** → **Projects** (Проекты). -2. Отдельный проект, например `gpu-rent`. Не клади GPU в общий «мусорный» проект. -3. Скопируй **ID проекта** (uuid). Он же `OS_PROJECT_ID`. -4. **Номер аккаунта** — в правом верхнем углу панели. Он же `OS_USER_DOMAIN_NAME` (domain в Keystone). +1. Панель → **IAM** → **Projects** (Проекты). +2. Отдельный проект, например `gpu-rent` (не общий «мусорный»). +3. Скопируй **ID проекта** (uuid) → это `OS_PROJECT_ID`. +4. **Номер аккаунта** (правый верхний угол) → это `OS_USER_DOMAIN_NAME`. ### 2.2. Есть ли GPU в квоте -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`. -3. Строка **GPU** (и при необходимости vCPU/RAM/network volumes). Если лимит 0 — дальше тикет. +1. **IAM** → **Projects** → твой проект → **Quotas and limits** / **Квоты** → **Cloud platform**. +2. Пул и сегмент с GPU: матрица [GPU availability](https://docs.selectel.ru/en/cloud-servers/create/gpus/). Часто смотрят мультизональный `ru-6` и `ru-7`. +3. Строка **GPU**. Если лимит 0 — тикет (ниже). -Квоту внутри уже выданного лимита можно крутить в панели. **Сам лимит GPU поднимает только поддержка.** +Лимит GPU поднимает **только поддержка**. Внутри уже выданного лимита квоту можно крутить в панели. + +Подсказка по пулу после появления ключей: `.\gpu-rent.ps1 flavors` (скан `SCAN_POOLS`). ### 2.3. Тикет в поддержку -**Тикеты** в панели (не email вслепую). Лимит увеличивают **на один конкретный проект**. - -Текст можно почти копировать: +**Тикеты** в панели. Лимит увеличивают **на один конкретный проект**. ```text Прошу увеличить лимит GPU в облачной платформе. Проект: <имя> (ID: ) Пул / сегмент: ru-7 / ru-7a ← подставь свой из матрицы GPU -Нужно: 1× NVIDIA RTX 4090 24 GB (если нет — ближайший аналог в этом сегменте: 4090 48 GB или A5000). -Цель: один прерываемый (preemptible) облачный сервер для персональных сессий генерации, диски сетевые. +Нужно: 1× NVIDIA RTX 4090 24 GB (если нет — ближайший аналог: 4090 48 GB или A5000). +Цель: один прерываемый (preemptible) облачный сервер для персональных сессий, диски сетевые. Сейчас квота/лимит 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`. Это статический ключ панели, OpenStack (серверы/диски/сети) он **не** двигает. +Нужен **сервисный пользователь** + пароль. Не используй: Профиль → Access → API Keys → `X-Token`. ### 3.1. Сервисный пользователь -Только владелец аккаунта или роль `iam.admin`. На балансе для роли `member` должно быть хотя бы **100 ₽**. +Только владелец аккаунта или `iam.admin`. На балансе для роли `member` обычно нужно хотя бы **~100 ₽**. -1. Сверху **IAM** → **Service users** / **Сервисные пользователи**. -2. **Add service user**. -3. Имя, например `gpu-rent-api`. -4. Пароль: **минимум 20 символов**, сохрани в менеджер паролей. После создания пароль **больше не показывают** — только сброс. -5. Права: - - **Scope: Projects** (не весь аккаунт); - - проект `gpu-rent`; - - роль **`member`** (создание серверов/дисков/сетей). Роль `reader` для `up` не хватит. -6. **Add user**. +1. **IAM** → **Service users** / **Сервисные пользователи** → **Add**. +2. Имя, например `gpu-rent-api`. +3. Пароль: **≥ 20 символов**, сохрани сразу — потом только сброс. +4. Scope: **Projects** → проект `gpu-rent` → роль **`member`** (`reader` для `up` мало). +5. **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**. -2. Блок **RC files**: - - проект `gpu-rent`; - - локация = **пул**, например `ru-7` (это `OS_REGION_NAME`, не сегмент `ru-7a`); - - **Download**. -3. Открой файл (`rc.sh`). Из него в `.env` переносятся: +1. **IAM** → **Service users** → пользователь → **Access**. +2. **RC files**: проект `gpu-rent`, локация = **пул** (например `ru-7`, не сегмент `ru-7a`) → **Download**. +3. Из `rc.sh` перенеси в `.env`: | Переменная | Откуда | | --- | --- | | `OS_AUTH_URL` | обычно `https://cloud.api.selcloud.ru/identity/v3` | | `OS_USER_DOMAIN_NAME` | номер аккаунта | -| `OS_PROJECT_DOMAIN_NAME` | тот же номер (можно не дублировать в нашем `.env`) | | `OS_PROJECT_ID` | uuid проекта | | `OS_USERNAME` | имя сервисного пользователя | -| `OS_PASSWORD` | пароль, который ты сохранил (в RC его часто нет — дописываешь сам) | -| `OS_REGION_NAME` | пул, `ru-7` | -| `GPU_RENT_AZ` | **сегмент** пула, `ru-7a` — в RC его может не быть, смотри матрицу GPU | +| `OS_PASSWORD` | пароль (в RC часто нет — допиши сам) | +| `OS_REGION_NAME` | пул, например `ru-7` | +| `GPU_RENT_AZ` | **сегмент**, например `ru-7a` (в RC может не быть) | Официально: [Configure OpenStack CLI](https://docs.selectel.ru/en/cloud-servers/tools/openstack-cli/configure-openstack-cli/). -### 3.3. Куда класть - -Создай каталог и файл **вне git**: +### 3.3. Заполнить `.env` ```powershell copy env.example .env notepad .env ``` -Вставь значения из RC + пароль + `GPU_RENT_AZ`. Никогда не коммить `.env`. +Вставь значения из RC + пароль + `GPU_RENT_AZ`. **Не коммить** `.env`. -Проверка без нашего CLI (необязательно): - -```powershell -# после pip install python-openstackclient, если хочешь -openstack token issue -openstack flavor list -``` - -Наш способ: `gpu-rent doctor`. +Проверка: `.\gpu-rent.ps1 doctor` (не обязательно ставить `openstack` CLI). --- ## 4. Civitai API token (модели) -Нужен, если хочешь seed с Civitai по `models.yaml`. Без токена SwarmUI поставит свою дефолтную модель — это допустимо. +Нужен, если хочешь seed по `models.yaml`. Без токена SwarmUI поставит свою дефолтную модель — это нормально. -1. Войди на [civitai.com](https://civitai.com) (тот же аккаунт, что и для `.red`). -2. [Account settings](https://civitai.com/user/account) → блок **API Keys** → **Add API key**. -3. Имя, например `gpu-rent`. Токен показывают **один раз**. -4. В `.env`: `CIVITAI_API_TOKEN=...` -5. Хост API по умолчанию **`civitai.red`** (полный каталог). `.com` — SFW-витрина, NSFW с неё часто 404. Один токен на оба домена. - -Не клади токен в query-string в скриптах «на память» — в логах светится. CLI шлёт `Authorization: Bearer …` только на `civitai.com` / `civitai.red` / `civitai.green`, не на CDN. - -Манифест (не в git со своими id, если не хочешь светить вкусы): +1. Войди на [civitai.com](https://civitai.com) (тот же аккаунт, что для `.red`). +2. [Account settings](https://civitai.com/user/account) → **API Keys** → **Add**. +3. Токен показывают **один раз** → в `.env`: `CIVITAI_API_TOKEN=...` +4. Хост по умолчанию **`civitai.red`** (полный каталог). С `.com` NSFW часто 404. +5. Манифест: ```powershell 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` клонируются без токена. -Если репа приватная: - -1. GitHub → Settings → Developer settings → Personal access tokens. -2. Fine-grained: доступ только к нужным репам, **Contents: Read**. -3. `GIT_TOKEN` в `.env`. - -Скопируй шаблон: `extensions.example.yaml` → `extensions.yaml` в корне репо. Пустой файл = стоковый SwarmUI. +Приватные: GitHub fine-grained PAT, **Contents: Read** → `GIT_TOKEN` в `.env`. +Шаблон: `extensions.example.yaml` → `extensions.yaml`. Пустой файл = стоковый SwarmUI. См. [extensions.md](extensions.md). --- -## 6. SSH +## 6. Чеклист перед `doctor` -Ключ **не надо** делать руками. CLI создаст `\.gpu-rent\id_ed25519` без passphrase и зарегистрирует keypair в OpenStack при первом `up`. `doctor` только проверяет, что это получится. - ---- - -## 7. Чеклист перед `doctor` - -- [ ] Python 3.11+, `pip install -e .` -- [ ] Проект Selectel, скопирован uuid -- [ ] Тикет на лимит **1× GPU** в нужном сегменте (или квота уже > 0) -- [ ] Сервисный пользователь `member` на этот проект, пароль сохранён -- [ ] RC скачан на **тот же пул**, где GPU -- [ ] `.env` в корне репо заполнен (`OS_*` + `GPU_RENT_AZ`) -- [ ] Нет `X-Token` вместо пароля сервисного пользователя -- [ ] (опционально) Civitai token + `models.yaml` -- [ ] На балансе хватает на диск 100 GB **даже когда GPU выключен** - -Дальше: +- [ ] Python 3.11+, лаунчер отвечает на `--help` +- [ ] Проект Selectel, uuid скопирован +- [ ] Лимит GPU ≥ 1 (или тикет в работе — тогда `doctor` честно скажет «0») +- [ ] Сервисный пользователь `member`, пароль сохранён +- [ ] RC с того же **пула**, где GPU +- [ ] `.env`: `OS_*` + `GPU_RENT_AZ`, не `X-Token` +- [ ] (опц.) Civitai + `models.yaml` +- [ ] На балансе хватает на **диск ~100 GB 24/7**, даже когда GPU выключен ```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): ориентир **20–40 минут**. Не закрывай терминал посередине 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). diff --git a/docs/spike-notes.md b/docs/spike-notes.md index a72e597..12703d3 100644 --- a/docs/spike-notes.md +++ b/docs/spike-notes.md @@ -1,21 +1,27 @@ -# Spike notes (заполнять руками) +# Spike notes — первый живой прогон -Без секретов, без личных имён файлов моделей. Цель — один раз пройти панель + CLI на живом GPU и зафиксировать факты для v1. +Заполняй **руками** после зелёного `doctor`. Без секретов и без личных имён чекпоинтов. -## 0.0 Квота +Подготовка ключей: [setup.md](setup.md). Цель — один раз пройти панель + CLI на реальном GPU и зафиксировать факты (времена, цены, имя образа). + +--- + +## 0. Перед прогоном | Шаг | Дата | Результат | | --- | --- | --- | | Квота GPU в панели (лимит) | | 0 / N | -| Тикет в поддержку (номер) | | | -| Лимит поднят | | | -| Сервисный пользователь `member` на проект | | | +| Тикет в поддержку (номер), если был | | | +| Сервисный пользователь `member` + `.env` | | да / нет | +| `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 flavors .\gpu-rent.ps1 dry-run @@ -25,17 +31,21 @@ | --- | --- | --- | | Keystone token | | | | GPU quota > 0 | | | -| Flavor из FLAVOR_PREFERENCE | | id / имя | +| Flavor из `FLAVOR_PREFERENCE` | | id / имя | | Image Driver 580 (без Docker) | | имя образа | | Volume type в AZ | | | -| Civitai `.red` + token | | | +| Civitai `.red` + token (если используешь) | | | -## Первый `up` +--- -```text +## 2. Первый `up` + +```powershell .\gpu-rent.ps1 up --yes ``` +Первый bootstrap долгий (ориентир 20–40 мин). Не рви терминал посередине. + | Метрика | Значение | | --- | --- | | Flavor фактически | | @@ -44,13 +54,18 @@ | SSH через (мин) | | | Backend Idle через (мин) | | | Seed Civitai (мин / GB) | | -| Boot snapshot создался | да / нет / ошибка | +| Boot snapshot `gpu-rent-boot-ok` | да / нет / ошибка | | ₽/час GPU (панель) | | -| ₽/мес data disk | | +| ₽/мес data disk (панель) | | +| Access-card / URL :17801 | да / нет | -## Туннель и API +--- -```text +## 3. Туннель и API + +Если закрыл туннель (Ctrl+C): + +```powershell .\gpu-rent.ps1 tunnel --open ``` @@ -59,31 +74,53 @@ | UI http://127.0.0.1:17801 | | | `/API/GetNewSession` | | | MCP `/mcp` (если нужно) | | -| `nvidia-smi` по `gpu-rent ssh` | | +| `nvidia-smi` через `gpu-rent ssh` | | -## Idle-killer / hold +--- + +## 4. Idle-killer / hold | Шаг | OK? | Заметка | | --- | --- | --- | -| `systemctl status gpu-rent-idle-killer.timer` | | | +| `systemctl status gpu-rent-idle-killer.timer` (по SSH) | | | | `gpu-rent hold` пишет hold-until | | | -| Качалка модели в UI → нужен hold? | | нет сигнала API — только hold | +| Качалка модели в UI | | hold, если killer не видит busy | -## Preempt / диски +--- + +## 5. Preempt / диски / stop | Шаг | OK? | | --- | --- | -| EXPIRED → tunnel unshelve или `up` | | +| EXPIRED → `tunnel` unshelve или `up` | | | `stop` — диски живы | | | Второй `up` — те же модели на data | | | 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-образа в пуле: - Реальные flavor id (не в git): - Хватает ли `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. diff --git a/extensions.example.yaml b/extensions.example.yaml index 6b6a2e4..1083cef 100644 --- a/extensions.example.yaml +++ b/extensions.example.yaml @@ -2,8 +2,14 @@ # Empty/missing file → no extra extensions, stock SwarmUI. # swarmui = C# repos cloned to src/Extensions # 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 # ref: main # dir: MyExt diff --git a/src/gpu_rent/manifests.py b/src/gpu_rent/manifests.py index 73e2ce6..581a4ab 100644 --- a/src/gpu_rent/manifests.py +++ b/src/gpu_rent/manifests.py @@ -45,12 +45,16 @@ class ModelEntry: url: str | None +VALID_REQUIRES = frozenset({"none", "ollama", "llamacpp", "any-llm"}) + + @dataclass class GitRepo: kind: str url: str ref: str directory: str | None + requires: str = "none" def _load_yaml(path: Path) -> Any: @@ -96,6 +100,28 @@ def parse_models(path: Path) -> list[ModelEntry]: 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]: data = _load_yaml(path) if not data: @@ -112,12 +138,17 @@ def parse_extensions(path: Path) -> list[GitRepo]: for item in items: if not isinstance(item, dict) or not item.get("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( GitRepo( kind=kind, url=str(item["url"]).strip(), ref=str(item.get("ref") or "main"), directory=str(item["dir"]) if item.get("dir") else None, + requires=requires, ) ) return repos diff --git a/src/gpu_rent/provision.py b/src/gpu_rent/provision.py index 17b9d99..44b1705 100644 --- a/src/gpu_rent/provision.py +++ b/src/gpu_rent/provision.py @@ -20,6 +20,8 @@ from gpu_rent.manifests import ( parse_extensions, parse_models, 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.sync_files import pull_tree, push_tree @@ -34,9 +36,22 @@ def _pkg_text(name: str) -> str: 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: - log("extensions.yaml пуст — стоковый SwarmUI") + if all_repos: + log("extensions: все строки отфильтрованы по requires — стоковый SwarmUI") + else: + log("extensions.yaml пуст — стоковый SwarmUI") return False jobs = [] for repo in repos: diff --git a/tests/test_manifests.py b/tests/test_manifests.py index d2dfc95..6f6821e 100644 --- a/tests/test_manifests.py +++ b/tests/test_manifests.py @@ -1,6 +1,9 @@ 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): @@ -30,3 +33,49 @@ def test_extensions_repo(tmp_path: Path): repos = parse_extensions(path) assert repos[0].kind == "swarmui" 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)