📦 Артефакты моделей: хранение, бэкап и деплой

О чём это

Обученные модели — приватная половина open-core, поэтому они не лежат ни в одном публичном репозитории и не вшиты в публичный образ. Здесь: инвентарь (где что реально находится), разбор вариантов бэкапа с ценой каждого, и runbook деплоя модели в analytics.

Связанные: ml-lab-journal · roadmap-value-model · dicechess-analytics-on-aurora-deploy-and-sizing · northflank-onnx-bots-runbook · dexus-kcp-one-ply-bot-runbook · win-probability-for-sparse-positions

1. Инвентарь на 09.08.2026 — честная картина

АртефактГде физическиПродублировано?
models/*.onnx, *.txt в dicechess-evтолько MacBook (+ iCloud с 09.08)было нигде
веса задеплоенных ботоввнутри приватного GHCR-образа house-bots и приватного GAR-образа Northflank ONNX-ботовда, образы в реестрах
production opening_book.tsv для Northflankвнутри приватного GAR-образа, 147 строкда, образ в реестре; в публичном repo только 5-строчный sample
датасеты training_data_*.csv.gz (0,7 ГБ)только MacBookнет
исходная БД (из которой всё выводится)aurora + ночные бэкапыда

Обнаружено при подготовке деплоя

models/ стоит в .gitignore репозитория ev (строка 10), поэтому в git не лежит ни одна модель — включая июльскую df_rich.onnx, от которой зависит задеплоенный oracle-3. До 09.08 весь набор существовал в единственном экземпляре на ноутбуке. Копия в iCloud закрывает самый острый риск (потеря железа), но не решает две другие проблемы — см. ниже.

2. Почему копии файла недостаточно

Файл — наименее ценная часть артефакта. Модель без происхождения нельзя ни проверить, ни воспроизвести, ни осмысленно сравнить. Реальный пример из этой же работы: чтобы понять, чем является df_rich.onnx, пришлось восстанавливать это по датам файлов и старым логам — какой корпус, какие флаги, какой learning rate. Ответ «дистиллированная rich-9 на ~1,3 млн строк» был выведен, а не прочитан.

Переобучить «то же самое» нельзя. Кажется, что модель всегда можно получить заново: БД бэкапится, команды записаны. Но экспорт зависит от момента — БД растёт непрерывно, и завтрашний экспорт даст другой датасет, а значит другую модель. Рецепт воспроизводит похожую модель, а не ту же. Поэтому артефакт нужно хранить, а не только рецепт.

Отсюда требование: рядом с каждым файлом должен лежать манифест — датасет (имя + число строк), команда обучения целиком, метрики на холдауте, дата, SHA коммита ev. Тогда через полгода вопрос «а это что за модель и можно ли ей верить» имеет ответ, а не расследование.

3. Варианты хранения

ВариантВерсионированиеПровенансДостать на серверЦена/минусы
iCloud / Dropbox (сейчас)нетнетруками через ноутбукнулевая цена, но это backstop, а не система учёта
Коммит в приватный ev (снять models/ из .gitignore)git-историякоммит + манифест рядомgit clone / ghбинарники в истории; при 1–2 МБ и нескольких моделях в месяц — годы до проблемы
GitHub Releases приватного evтеги релизовтело релизаgh release downloadне раздувает историю; чуть больше ручных шагов
Приватный GHCR-пакеттеги образалейблы образаdocker pullпрецедент есть (house-bots); тяжеловесно для итераций
Объектное хранилище (R2/S3)вручнуювручнуюcurl с ключомещё одни креды и ещё один сервис

4. Рекомендация

Система учёта — приватный ev; iCloud остаётся дополнительным слоем.

  1. Снять models/ из .gitignore в ev (репозиторий приватный, файлы 1–2 МБ). Модель попадает в git вместе с манифестом — версионирование, провенанс и бэкап получаются одним действием.
  2. Рядом с каждой моделью — <model>.json: датасет и число строк, полная команда обучения, метрики на общем холдауте, дата, SHA ev. Заполняется в момент обучения, а не потом по памяти.
  3. Датасеты не хранить — они выводятся из БД, а БД бэкапится. Хранить только команду экспорта и число строк, чтобы отличать корпуса друг от друга.
  4. Для доставки на сервер, где git clone неуместен, — gh release download из того же приватного репо.

Чего сознательно не делаем: не кладём веса в публичный analytics и не вшиваем их в публичный образ — это прямо нарушило бы open-core (см. win-probability-for-sparse-positions).

5. Runbook: деплой модели в analytics

Порядок обязателен

Модель кладётся после релиза образа: до него в образе просто нет кода, который читает EV_MODEL_PATH. Релиз и прод-деплой — операции владельца.

Предусловия: analytics#273 смёржен и выпущен релизом (образ с нужным тегом опубликован).

Шаг 1 — положить модель на хост. API живёт на aurora в ~/dicechess-analytics (compose + .env, порт 8020):

mkdir -p ~/dicechess-analytics/models
scp models/kcp_nodice_serving_20260809.onnx aurora:~/dicechess-analytics/models/

Шаг 2 — смонтировать в контейнер. В docker-compose.yaml, сервис api:

volumes:
  - ./models:/models:ro

Шаг 3 — указать путь. В .env:

EV_MODEL_PATH=/models/kcp_nodice_serving_20260809.onnx

Шаг 4 — поднять и проверить:

docker compose up -d api
curl -s 'http://localhost:8020/api/positions/win-probability?fen=rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR%20w%20KQkq%20-'

Ожидаемо: win_probability около 0.5375 для стартовой позиции. Ориентир для сверки — эмпирика по 1,6 млн партий даёт 53.41%, то есть расхождение около 0,3 пп. Если пришло 503 — переменная не подхватилась; если контейнер не стартует — см. следующий пункт.

Брать модель на 13 фич, а не на 19

kcp_nodice_serving_*.onnx — 13 входов, это pre-roll модель, которую обслуживает эндпоинт. kcpdice_serving_*.onnx — 19 входов, roll-aware, для будущего per-roll эндпоинта. Перепутать не страшно: загрузчик сверяет ширину входа и отказывается стартовать, объясняя, какая раскладка ожидалась. Сделано намеренно — раскладки различаются только шириной, и скоринг «не своей» моделью не падает, а тихо возвращает правдоподобные неверные числа.

Откат: убрать EV_MODEL_PATH из .env и перезапустить. Эндпоинт вернётся к 503, остальное API не затронуто — это штатное состояние публичного образа без приватных весов.

6. Что осталось решить

  • Снять models/ из .gitignore в ev и завести манифесты (решение владельца — игнор стоял осознанно).
  • Добавить монтирование ./models:/models:ro в docker-compose.yaml analytics отдельным PR. Безопасно для всех: без EV_MODEL_PATH эндпоинт отвечает 503, пустая папка ничему не мешает.
  • Задним числом восстановить провенанс июльских моделей (df_rich, kcp_nodice) — пока он выведен из дат и логов, а не записан.