Как я собрал на DGX Spark приватный AI-сервер, и теперь рассказываю что туда вошло
AGmind: 30 контейнеров, локальная 26B-модель, GPU-парсинг документов, drag-n-drop конструктор агентов в Dify. История сборки на DGX Spark GB10 с Dify + RAGFlow + vLLM, грабли драйверов и месяц работы с Claude Code.
Оригинал статьи опубликован на habr.com . Эта версия — копия в нашем блоге.
DGX Spark — компактный AI-десктоп NVIDIA на чипе GB10 (128 ГБ unified memory, Blackwell GPU) размером с Mac mini, стартовая цена от $4 699. Помещает Llama 3.3 70B в FP8 без шардирования и подходит командам до 150 человек — тем, кому нужен self-hosted AI без datacenter-инфраструктуры. Кто не вписывается: крупные компании с 200+ concurrent-запросами.
Проект создавался не как увлечение, а для работы с корпоративными документами, договорами и регламентами, которые нельзя отправлять в облачные сервисы. Решение названо AGmind и выложено на GitHub под Apache 2.0.
Зачем мне понадобился свой AI-сервер
Облачные ассистенты (OpenAI, Anthropic, Gemini) хорошо работают с публичной информацией. Когда речь идёт о корпоративных документах, юридических договорах или внутренних регламентах, compliance-отделы справедливо отказывают SaaS-решениям. Дело не в TLS, а в том, что данные физически уходят на чужие серверы.
Доступные self-hosted альтернативы имели ограничения:
- Open WebUI — приличный чат, но слабо работает с документами
- Dify — мощный workflow-конструктор, но встроенный парсер PDF неудачный
- RAGFlow — сильный парсер, но UI уступает Dify
- Прочие инструменты — каждый имеет свои пробелы
Требование было простое: положить 200 PDF-документов в папку, задавать вопросы через нормальный чат, собирать агентов drag-n-drop, и чтобы всё поднималось одной командой.
Железо: что такое DGX Spark в двух словах
DGX Spark — компактный AI-десктоп от NVIDIA на чипе GB10 Grace Blackwell, анонсированный в марте 2025, продажи начались 15 октября 2025. Стартовая цена $3999, в феврале 2026 поднялась до $4699 из-за дефицита LPDDR5x.
Внутри один SoC:
- 20-ядерный ARM-процессор (10 Cortex-X925 + 10 Cortex-A725)
- Blackwell GPU: 48 SM, 6144 CUDA-ядра, 192 тензорных ядра пятого поколения
- 128 ГБ памяти LPDDR5x (пропускная способность 273 ГБ/с)
- Два QSFP-порта на 200 Гбит (возможность кластеризации)
По GPU-производительности это промежуточный вариант между RTX 5070 и 5070 Ti. Главное преимущество — 128 ГБ unified memory для инференса 70B-моделей в FP4 без шардирования. Среди альтернативного компактного железа с большой unified memory выделяется Apple Silicon — сравнение в статье «Mac Studio M3 Ultra для AI».
Проблема: драйверы. Spark стабилен на ветке 580.x. На 590+ обнаружены три регрессии:
- vLLM зависает на CUDAGraph при первом инференсе
- Утечка 80 ГБ памяти после завершения CUDA-процесса (требует перезагрузки)
- TMA-баг в 595.58.03 ломает NVFP4-квантизацию
В install.sh поэтому зафиксирован драйвер 580: apt-mark hold nvidia-driver-580-open.
Архитектура: компоненты стека
Стек состоит из тридцати контейнеров.
Dify — фронтенд и оркестратор
Dify (langgenius/dify-api:1.13.3) — основной интерфейс, аналог LangChain Studio + ChatGPT-чат + drag-n-drop конструктор агентов. Workflow-редактор позволяет за пять минут собрать цепочку типа «получи документ → распарси → суммаризируй → отправь в Telegram» без кода.
Dify используется как primary frontend (agmind-dify.local). Состоит из пяти контейнеров: api, worker, web, sandbox, plugin_daemon.
RAGFlow — пересобранный под Spark
Upstream-RAGFlow не работает на DGX Spark: есть x86-only зависимости, устаревшие ONNX-рантаймы, неподдержка arm64 + sm_121 + CUDA 13.
Собран образ ar2r223/ragflow-spark:v0.24.1-spark на базе патчей HendrikSchoettle/ragflow-dgx-spark с собственными cherry-pick’ами:
Включено:
- Cascade-OCR на Latin/Cyrillic/Chinese
- File metadata в ES-чанках
- Поддержка AVIF
- ONNX Runtime GPU 1.25.0 под aarch64 + CUDA 13 + sm_121
- TokenChunker для атомарных таблиц
- TitleChunker с 5-уровневой иерархией
- 7 ingestion-шаблонов (book, laws, manual, paper, resume и др.)
- Русский prompt для vision-модели при language=Russian
- Патчи бага Pipeline.globals
Размер образа ~13.3 ГБ.
vLLM — три инстанса
vLLM (LLM) — vllm/vllm-openai:gemma4-cu130 с google/gemma-4-26B-A4B-it. NVIDIA playbook-сборка под arm64 и sm_121. gpu_memory_utilization=0.60 для запаса docling-serve.
vLLM (embeddings) — nvcr.io/nvidia/vllm:26.02-py3 с deepvk/USER-bge-m3 (финтюненный под русский).
vLLM (reranker) — тот же базовый NGC-образ с BAAI/bge-reranker-v2-m3.
Выбор версии 26.02-py3 (а не 26.03) обусловлен требованием драйвера 595.45+, а система работает на 580.142.
Внимание-бэкенд зафиксирован на TRITON_ATTN: FlashInfer FP8 на sm_121 падает с «kernel only supports sm120» (известный баг).
Docling — GPU-парсер документов
Docling-serve (docling-serve-cu130:v1.16.1) — GPU-ускоренный конвертер от IBM. Принимает PDF/DOCX/PPTX/XLSX, выдаёт Markdown со структурой таблиц и описаниями картинок.
Три режима:
- FAST — отключает OCR, ~4 сек на 5-страничной статье
- BALANCED (default) — полный pipeline с автоматическим OCR
- SCAN — для отсканированных страниц, easyocr + vision-описание через vLLM concurrency=8
Базы данных и хранение
- PostgreSQL 16-alpine — метаданные Dify
- Redis 7.4 — task queue, кэш, pub/sub
- Weaviate 1.37 — векторное хранилище эмбеддингов
- MinIO — S3-совместимое хранилище документов
- Elasticsearch 9.x — чанки RAGFlow и полнотекстовый индекс
- MySQL 8.0 — RAGFlow (захардкожена в схеме)
Мониторинг и операционка
- Prometheus + Grafana 3001 с десятью дашбордами
- На GB10
dcgm-exporterне работает; написан свой textfile collector для nvidia-smi - Loki + Grafana Alloy — логи всех контейнеров
- Alertmanager — уведомления в Telegram
- Portainer 2.39 — визуальное управление контейнерами и автодеплой на peer
- fail2ban + UFW — firewall LAN-only по умолчанию
Безопасность
- Секреты в
/opt/agmind/credentials.txt(права 600, root-only) - 30+ Linux-capabilities сдропаны в каждом контейнере
- Dify Sandbox изолирован в отдельной Docker-сети через Squid-прокси (SSRF-защита)
- Опциональная Authelia с TOTP/WebAuthn
agmind rotate-secretsперегенерирует все пароли одной командой
Кластер из двух Spark’ов
Две машины соединяются QSFP 200G DAC в unified cluster:
┌─────────────────────┐ ┌─────────────────────┐
│ spark-master │ QSFP 200G DAC │ spark-peer │
│ (frontend + БД + │ ◄──── direct link ────►│ (только vLLM, │
│ Dify + RAGFlow + │ 192.168.100.0/24 │ выделенный GPU, │
│ monitoring) │ │ 128K context) │
│ WAN: ethernet │ │ WAN: NAT через │
│ iptables MASQUERADE│ ─────── default gw ───►│ master по QSFP │
└─────────────────────┘ └─────────────────────┘
Ключевые моменты:
- На single-Spark vLLM делит GPU с docling (контекст ограничен 32K/64K/128K)
- На dual-Spark peer крутит только vLLM, GPU выделен полностью, 128K без компромиссов
- NAT-on-demand: peer выходит в WAN только при необходимости, потом отрезается
- Симметричная установка определяет роль через LLDP, passwordless SSH настраивается автоматически
Что я делаю с этим в реальной жизни
Чат с локальной 26B-моделью:
- TTFT 183 мс
- 23–24 токена/сек на одиночном запросе
- До 50 токенов агрегированно на трёх параллельных
- Контекст 65K с FP8 KV-кэшем
- До 45 параллельных запросов
Загрузка и обработка документов:
RAGFlow парсит PDF, вытягивает таблицы и картинки, прогоняет изображения через vision-модель (gemma-4 описывает их на русском), нарезает на чанки, загружает эмбеддинги в Weaviate. При вопросе bge-m3 ищет похожие чанки, bge-reranker сортирует, gemma-26B отвечает со ссылками на источник. Полная архитектура RAG для корпоративной базы знаний разобрана в «RAG для корпоративной базы знаний».
Конструктор агентов:
Собирается drag-n-drop в Dify. Пример: агент забирает письма из Gmail, классифицирует, важные — в Notion, остальные суммаризирует в дайджест и отправляет в Telegram. Собирается за десять минут без кода.
Бэкап: sudo agmind backup снимает Postgres, Redis, volumes и конфиги. Опционально с шифрованием age и upload на peer.
Производительность на бенчмарках
| Метрика | Результат |
|---|---|
| TTFT (streaming) | 183 мс |
| TPS (1 запрос) | 23–24 токенов/сек |
| TPS (3 параллельных) | ~50 токенов/сек агрегированно |
| Контекст | 65K (FP8 KV-cache) |
| Max concurrency @ 65K | 45 запросов |
| Память: веса | 48.5 GiB (bfloat16) |
| Память: KV-cache | 41.7 GiB (FP8) |
| Полный footprint | ~95 GiB |
Docling (5-page arxiv PDF):
- Cold start: 6.04 сек
- Warm: 1.6 сек
- Per-page (warm): 0.32 сек
284-page PDF без VLM: 88 сек Тот же PDF с VLM concurrency=8: 191 сек
Полный install.sh на чистой системе: ~30 минут (включая 52 ГБ весов)
Установка
git clone https://github.com/botAGI/AGmind.git
cd AGmind
sudo bash install.sh
Визард задаёт 10–15 вопросов: single/dual-Spark, выбор LLM, контекст, опциональные сервисы, место для бэкапов. Через ~25 минут стек готов, credentials в /opt/agmind/credentials.txt (права 600).
Day-2 операции:
agmind status # сервисы, GPU, модели, эндпоинты
agmind doctor # диагностика
agmind logs <service> # тейл логов
agmind ragflow status # RAGFlow контейнеры + ES health
agmind docling bench <pdf> # холодный/тёплый старт и per-page timing
agmind upgrade --diff # что устарело
agmind backup # Postgres + Redis + volumes
agmind rotate-secrets # перегенерировать пароли и ключи
Пять грабель GB10
1. 502 на каждый запрос после force-recreate (потерял два дня)
Nginx-конфиг с upstream agmind_api { server api:5001; } кэшировал IP при старте. После docker compose up -d --force-recreate api новый контейнер получал новый IP, upstream-блок не обновлялся, nginx отвечал 502.
resolver влияет только на proxy_pass $variable, а не на upstream {}. Решение: переписать на variable form:
location /api {
set $u_dify_api http://api:5001;
proxy_pass $u_dify_api;
}
Убрать все upstream {}. Регрессионный тест проверяет, что после force-recreate сервис отвечает 200 без ручного перезапуска nginx.
2. Зомби-задачи в Redis после force-recreate (потерял два часа)
Большой PDF на 280 страниц запущен на docling — висит в processing десять минут. После docker compose up -d --force-recreate worker задача всё ещё processing.
Реальная причина: остатки Redis-state (ключи generate_task_belong:<task_id> с TTL 1800, pub/sub-каналы привязаны к старому hostname celery@OLDID). Recreate = новый контейнер с новым hostname, Redis об этом не знает.
Лечение:
redis-cli -a $PW -n 0 --scan --pattern 'generate_task_belong:*' | \
xargs redis-cli -a $PW -n 0 DEL
redis-cli -a $PW -n 1 --scan --pattern 'celery-task-meta-*' | \
xargs redis-cli -a $PW -n 1 DEL
FLUSHDB блокируется ACL. Правильный путь: менять env через docker restart, а не recreate.
3. mDNS self-collision (потерял час)
Попытка добавить три алиаса (agmind-dify.local, agmind-rag.local, agmind-storage.local) в /etc/avahi/hosts на один IP. Avahi пишет «Local name collision», алиасы не резолвятся.
Avahi регистрирует $(hostname).local → IP как primary record. Добавление алиаса на тот же IP видится как конфликт — probe в сеть не отправляется.
Фикс: использовать avahi-publish-address -R name.local IP через systemd-юнит вместо /etc/avahi/hosts.
Бонус-баг: второй mDNS-respondent (NoMachine, iTunes) на UDP/5353 ломает avahi молча. Проверка: sudo ss -ulnp | grep 5353.
4. Distroless-контейнеры без shell (потерял полчаса)
Loki, redis_exporter, nginx-prometheus-exporter — distroless-образы без /bin/sh. Healthcheck через CMD-SHELL wget ... выдаёт ошибку, контейнер висит unhealthy, хотя метрики отдаются.
Правило: для distroless-образов здоровье проверяется через Prometheus up{job=...}, а не через docker healthcheck.
5. APT и truncated downloads (потерял вечер)
apt-get install -y linux-firmware (603 МБ) падает с «неожиданный конец файла». SHA256 не совпадает, reinstall не помогает. Файл обрезается ~2 МБ до конца.
Curl по HTTP принимает truncated body как успех (HTTP 200, Connection closed до Content-Length). Где-то сидит прозрачный прокси/CDN, режущий соединение.
Решение: везде HTTPS. TLS требует close_notify alert, клиент знает, что body неполный. После замены http://ports.ubuntu.com на https:// всё качается чисто.
Месяц с Claude Code: что это на самом деле
Код не писался руками — все 88 КБ install.sh, 16 lib-модулей, nginx-шаблоны, Python-скрипты сгенерировал Claude Code (CLI-агент Anthropic).
Что работало хорошо:
- Bash-скрипты с edge-case’ами (30 состояний хоста, частичная установка, существующие credentials, сломанный peer, занятый порт)
- Регрессионные тесты под mock’и
- Документация
Что работало плохо:
LLM врут, и это не лечится. Claude предложил minio/minio:RELEASE.2026-02-01T00-00-00Z — выглядит правдоподобно (timestamp правильного формата), но manifest unknown. После этого в CLAUDE.md написано: «Никаких выглядит правдоподобно. Только docker manifest inspect». Добавлен автотест проверки тэгов в registry.
Контекст забывается между сессиями. Без CLAUDE.md (200+ строк правил) ничего не работает. Каждое правило — выстраданное факапом.
Иногда не понимает приоритеты: просишь починить баг в OCR — попутно отрефакторит три соседних модуля.
Что я вынес про AI-разработку
AI-агент не делает тебя умнее. Он делает тебя быстрее в том, в чём ты уже разбираешься.
Без понимания, почему proxy_pass http://name ломается, а proxy_pass $variable — нет, Claude сгенерирует код, ты его поставишь, оно сломается, и ты не поймёшь почему.
Claude — не замена программисту. Это ускоренный младший разработчик для рутины, при условии, что архитектор есть. Где это реально меняет уравнение: в задачах с видением, опытом и предметной областью, но без команды и времени.
Раньше один человек мог сделать MVP за пару месяцев. Сейчас — production-grade продукт за 30 дней. В репозитории 86 коммитов, 32 тысячи строк, 30 контейнеров.
Дисциплина:
- Чёткое видение продукта
CLAUDE.mdс правилами, пополняется после каждого факапа- Живое железо для проверки
Итоговые ссылки
- GitHub: github.com/botAGI/AGmind
- Внедрение под ключ: prem.agmind.dev
Вторая часть — про мониторинг unified memory без NVML и supply-chain hardening для Dify-плагинов в air-gap-сценариях — будет следующей.