ADR-023: Локальный LLM-стек — Qwen 2.5 14B на Windows + reverse SSH tunnel

Контекст

Для BR 6.3 (AI-агент через REST) нужен LLM с function-calling, который понимает русский язык. Бизнес-ограничение: не использовать платный Claude API (см. ADR-022).

Доступное железо для хостинга: домашняя машина Алексея на Windows 11 с AMD Ryzen 7 9800X3D + NVIDIA RTX 5070 (12 GB GDDR7 VRAM, Blackwell) + 32 GB RAM.

Сервисы ERP живут на VPS erp-test.nirbi.ru (Ubuntu 24.04). Им нужно обращаться к LLM из бэкенда.

Рассмотренные варианты

Модель

МодельVRAM Q5_K_MFunction callingРусскийВердикт
Qwen 2.5 14B Instruct~10 GB★★★★★★★★★★✅ выбран
Qwen 2.5 7B Instruct~6 GB★★★★★★★★Fallback если 14B медленный
Mistral Nemo 12B~9 GB★★★★★★★Альтернатива, русский слабее
Llama 3.3 70B / Mixtral 8x22B40-80 GB★★★★★★★★★Не влезают в 12 GB
Vikhr-Nemo-12B (RU)~9 GB★★★★★★★★FC слабее Qwen

Inference engine

ОпцияПлюсыМинусы
llama.cpp server (через ollama-фронт или прямо)Установлено, работает, GGUF Q5_K_MAPI не полностью OpenAI-compatible на tool_calls
vLLM в WSL2+30% скорости, FP8 на BlackwellСложнее setup, требует prebuilt wheel под CUDA 12.8
TGI HuggingFaceХорош для prodПеребор для одной машины

Транспорт VPS ↔ LLM-хост

ОпцияТестРезультат
Tailscale (mesh VPN)Установили, авторизовали⚠️ Direct path WSL→VPS работает, обратно (VPS→WSL) — handshake не пробивает Hyper-V symmetric NAT; DERP relay не помог
Reverse SSH (autossh -R)Установили, тест с inference✅ Стабильно, 22 tok/s, переживает heavy curl
Cloudflare TunnelНе пробовалиАльтернатива на будущее, нужен домен

Решение

Стек:

  • Модельqwen2.5:14b-instruct-q5_k_m (~10 GB GGUF)
  • Хост — Windows 11 → WSL2 Ubuntu 24.04 (systemd) → llama.cpp-server (через ollama-runtime), слушает 127.0.0.1:11434
  • Транспортautossh -M 0 -f -N -R 11434:localhost:11434 root@185.152.93.77 (с WSL2 на VPS, через SSH key)
  • На VPS — Ollama-compatible API доступно на http://localhost:11434 (потребители обращаются напрямую; самописный erp-llm-gateway отменён 2026-05-13, см. ADR-022 rev 2)

Параметры:

  • OLLAMA_KEEP_ALIVE=24h — модель не выгружается из VRAM
  • OLLAMA_NUM_PARALLEL=2 — две параллельные генерации
  • autossh ServerAliveInterval=20, ServerAliveCountMax=3 — авто-восстановление туннеля

Измеренные метрики (2026-05-13):

  • Cold load 14B Q5_K_M в VRAM: ~10 сек
  • Steady-state generation: 20-22 tok/s (целевой диапазон 20-35)
  • Function calling работает корректно — модель возвращает <tool_call>{json}</tool_call> в content; парсинг — на стороне потребителя (OpenClaw framework, см. ADR-022 rev 2)

Последствия

Положительные

  • ✅ 0 ₽ inference cost после железа
  • ✅ Данные не покидают периметр (для production-сценария важно)
  • ✅ 20-22 tok/s достаточно для интерактивного агента (короткий ответ 100-300 ток за 5-15 сек)
  • ✅ Минимум зависимостей — нет публичных API, нет proxy-провайдеров

Отрицательные

  • ❌ Зависимость от одной физической машины (домашняя Windows Алексея) — single point of failure
  • ❌ После закрытия машины — деградация AI-функций ERP (admin agent падает)
  • ❌ Network latency 100-200ms через autossh (домашний роутер → интернет → VPS)
  • ❌ Не масштабируется горизонтально — одна машина = один параллельный пользователь LLM

Риски

  • ⚠️ Windows reboot / отключение — autossh поднимется, но Ollama в WSL2 нет (зависит от systemd внутри WSL и автозапуска WSL). Митигация: PowerShell-task на boot, который делает wsl -d Ubuntu -- systemctl restart ollama.
  • ⚠️ Пропускная способность апстрима — RTX 5070 выдаёт 22 tok/s одного потока. 2-3 параллельных запроса просядут до 8-12 tok/s каждый. Митигация: rate-limit на стороне потребителя, очередь.
  • ⚠️ GGUF runtime version mismatch — обнаружен факт что версия 0.23.2 (по /api/version) — это llama.cpp-fork, не стабильный Ollama (где было бы 0.5.x). Tool_calls приходят в content, не в structured поле. Митигация: парсер <tool_call>...</tool_call> блоков на стороне потребителя (OpenClaw).

Производственная миграция (после демо)

После 29.05.2026, если AI-функционал решено оставить:

  1. Перенести модель на выделенный GPU-сервер (vast.ai / immers.cloud / собственный сервер с 1× RTX 4090 24 GB) — больше VRAM для 32B моделей и параллельных пользователей
  2. Перейти на Cloudflare Tunnel или прямой ingress (TLS + аутентификация по token) вместо reverse SSH
  3. Возможно — fallback на платный API (Yandex GPT, GigaChat) для пиковых нагрузок

Ссылки