train_utility/.agents/PROJECT_CONTEXT.md
2026-07-24 09:31:39 +04:00

30 KiB
Executable file
Raw Blame History

Контекст проекта YOLO Train WebUI

Дата актуализации: 2026-07-18

Этот файл — основной технический контекст проекта для разработчиков и агентов. История найденных и исправленных дефектов находится в PROJECT_ISSUES.md.

1. Назначение и границы проекта

YOLO Train WebUI — локальное веб-приложение для настройки и запуска обучения Ultralytics YOLO. Оно предоставляет форму конфигурации, профили запусков, live-логи, прогресс по эпохам, графики метрик, мягкую остановку и интеграцию с MLflow.

Поддерживаемые задачи:

  • detect — детекция объектов;
  • segment — сегментация;
  • classify — классификация;
  • pose — оценка поз;
  • obb — ориентированные bounding boxes.

Приложение рассчитано на локального доверенного пользователя и один активный запуск обучения. Это не многопользовательская платформа, не планировщик задач и не сервис хранения датасетов. В нём нет встроенных учётных записей, ролей или аутентификации.

2. Технологии

Область Технология
Backend/API Python 3.11+, FastAPI, Uvicorn
Обучение Ultralytics YOLO, PyTorch
Эксперименты MLflow
Frontend HTML, CSS, vanilla JavaScript
Графики Chart.js из CDN
Real-time WebSocket
Зависимости uv, frozen-набор в uv.lock
Упаковка Hatchling
Тесты pytest, FastAPI TestClient/httpx, Node.js smoke-test
Контейнер Docker, Docker Compose

Основные зависимости объявлены в pyproject.toml: fastapi, uvicorn, websockets, ultralytics, mlflow. Dev-группа содержит pytest и httpx. Python package называется yolo-train-webui, текущая версия — 0.1.0; wheel собирается Hatchling только из src/yolo_webui.

yolo_webui.__init__ публично экспортирует TrainingConfig, TrainingEvent и TrainingRunner.

3. Структура репозитория

.
├── .agents/
│   ├── PROJECT_CONTEXT.md       # этот технический контекст
│   └── PROJECT_ISSUES.md        # аудит и история исправлений
├── src/yolo_webui/
│   ├── __init__.py              # публичные Python-экспорты
│   ├── __main__.py              # запуск `python -m yolo_webui`
│   ├── app.py                   # FastAPI, TrainingManager, REST и WebSocket
│   ├── config.py                # dataclass-конфигурации и валидация
│   ├── dataset_splitter.py      # detection-style train/val splitter
│   ├── subprocess_runner.py     # дочерний процесс обучения и stdout-протокол
│   ├── trainer.py               # Ultralytics callbacks, MLflow, cancellation
│   └── static/
│       ├── index.html           # форма и панель мониторинга
│       ├── app.js               # browser state, API, WebSocket, Chart.js
│       └── style.css            # всё визуальное оформление
├── tests/
│   ├── test_app.py              # API и TrainingManager
│   ├── test_config.py           # конфигурация, безопасность, MLflow env
│   ├── test_splitter.py         # классы и разбиение датасета
│   ├── test_subprocess_runner.py
│   ├── test_trainer.py          # callbacks и остановка
│   ├── test_frontend.py         # запуск Node-проверок из pytest
│   └── frontend_smoke.js        # browser stubs, форма и графики
├── Dockerfile
├── docker-compose.yml
├── pyproject.toml
├── uv.lock
└── README.md

Рабочие каталоги не входят в Git:

  • datasets/ — локальные датасеты;
  • models/ — локальные веса и YAML моделей;
  • runs/ — результаты Ultralytics и JSON-профили;
  • .yolo-webui/ — сгенерированные split-файлы внутри датасетов;
  • mlflow.db, mlruns/, mlflow/ — локальные данные MLflow;
  • .venv/, кэши Python и pytest.

4. Архитектура во время выполнения

Browser
  ├── HTTP JSON ───────────────┐
  └── WebSocket /api/ws ───────┤
                               v
FastAPI / TrainingManager (основной процесс Uvicorn)
  ├── хранит LiveState и WebSocket-клиентов
  ├── сохраняет профили в runs/sessions
  └── запускает background thread
           |
           v
Python subprocess: yolo_webui.subprocess_runner
  ├── читает временный JSON config
  ├── ставит SIGTERM/SIGINT handlers
  ├── создаёт TrainingRunner
  ├── запускает Ultralytics YOLO.train()
  └── пишет события, логи и результат в stdout
           |
           ├── dataset / generated split
           ├── models / official model download
           ├── runs / training artifacts
           └── MLflow storage

Изоляция обучения в subprocess нужна, чтобы тяжёлый Ultralytics/PyTorch не блокировал ASGI event loop, stdout можно было транслировать в браузер, а зависший запуск — принудительно завершить.

Важная деталь: TrainingRunner используется в двух процессах.

  • В родительском TrainingManager он хранит ссылку на subprocess и управляет сигналами остановки.
  • В дочернем процессе отдельный экземпляр владеет моделью Ultralytics и выставляет trainer.stop = True.

5. Точки входа и запуск

CLI entry point из pyproject.toml:

uv run yolo-train-webui

Альтернативный модульный запуск:

uv run -m yolo_webui

Оба варианта вызывают yolo_webui.app:main. CLI принимает:

  • --host, default 127.0.0.1;
  • --port, default 8000.

Перед запуском Uvicorn устанавливается MPLBACKEND=Agg. В Docker приложение слушает 0.0.0.0:8000 внутри контейнера, но Compose публикует его только как 127.0.0.1:8000 на host.

6. Backend и состояние обучения

LiveState

Глобальный TrainingManager хранит единственное состояние:

  • status;
  • текущую и общую эпохи;
  • список логов;
  • историю метрик;
  • output_dir;
  • stop_requested;
  • last_event_kind для классификации результата.

Состояния:

idle
  └── start -> preparing
                  ├── event started -> training
                  │                     ├── normal exit 0 -> succeeded
                  │                     ├── stop -> stopping -> cancelled
                  │                     └── error -> failed
                  ├── stop -> stopping -> cancelled/failed
                  └── setup error -> failed

finished больше не создаётся backend-ом; frontend понимает его только для совместимости со старым состоянием. Новый запуск разрешён лишь когда нет активного статуса и предыдущий background thread уже завершён.

Потоки и lock

TrainingManager._lock защищает LiveState, ссылку на thread и набор WebSocket. Нельзя выполнять broadcast() внутри with self._lock: broadcast сам читает защищённые данные, и повторный захват обычного threading.Lock вызовет deadlock.

WebSocket привязывается к event loop Uvicorn при подключении. Вызовы broadcast из фонового потока передаются через asyncio.run_coroutine_threadsafe(). Отправки сериализуются asyncio.Lock; failed-клиенты логируются и удаляются.

Запуск subprocess

TrainingManager._run_subprocess():

  1. сериализует TrainingConfig.to_dict() во временный JSON;
  2. запускает текущий interpreter с -u -m yolo_webui.subprocess_runner;
  3. объединяет stderr со stdout;
  4. читает поток посимвольно, различая \r и \n для progress-строк;
  5. обновляет состояние и транслирует события;
  6. ждёт return code, удаляет временный JSON и очищает ссылку на процесс.

Внутренний stdout-протокол

Дочерний процесс печатает специальные маркеры:

__YOLO_WEBUI_READY__
__YOLO_WEBUI_EVENT__:{"kind":"epoch","message":"...","epoch":1,"total_epochs":100}
__YOLO_WEBUI_RESULT__:/absolute/path/to/run
  • READY означает, что signal handlers уже установлены и отложенный stop можно безопасно доставить.
  • EVENT несёт kind, message, epoch, total_epochs.
  • RESULT передаёт каталог результатов.
  • Любая другая строка считается обычным логом.

События от TrainingRunner: info, started, epoch, success, cancelled, warning. Исключение выводится traceback-ом в stderr/stdout и даёт return code 1.

Классификация завершения

Backend различает:

  • succeeded — return code 0 без подтверждённой остановки;
  • cancelled — был stop и subprocess завершился с 0, прислал cancelled или был убит force-stop таймером;
  • failed — ненулевой код без подтверждённой отмены, в том числе реальная ошибка, случившаяся после нажатия Stop.

7. Остановка обучения

Остановка кооперативная и двухуровневая:

  1. POST /api/train/stop ставит stop_requested и статус stopping.
  2. Если subprocess ещё не прислал READY, запрос сохраняется.
  3. После READY родитель отправляет SIGTERM.
  4. Signal handler дочернего процесса вызывает TrainingRunner.request_stop().
  5. Runner выставляет trainer.stop = True сразу либо в ближайшем callback.
  6. Ultralytics штатно завершает callbacks и сохранение результатов.
  7. Если subprocess не завершился за 30 секунд, parent вызывает kill().

prepare_run() перед каждым новым запуском очищает stop-флаги и старый таймер.

8. REST и WebSocket API

Метод Путь Назначение
GET / Возвращает static/index.html
GET /static/* CSS и JavaScript
GET /api/config/defaults Полный default TrainingConfig
GET /api/datasets Верхнеуровневые каталоги и YAML из datasets/
GET /api/models Верхнеуровневые .pt/.pth/.yaml/.yml из models/
GET /api/sessions Список профилей без last_run
GET /api/sessions/{name} Загрузить профиль; last_run читать можно
POST /api/sessions/{name} Сохранить произвольный JSON профиля
DELETE /api/sessions/{name} Удалить профиль
GET /api/train/status Текущее состояние, эпохи, результат, метрики, логи
POST /api/train/start Провалидировать config, сохранить last_run, запустить
POST /api/train/stop Запросить остановку
WS /api/ws Init-снимок и live-события

FastAPI также оставляет включёнными стандартные OpenAPI endpoints: /docs, /redoc, /openapi.json.

WebSocket server → browser сообщения:

  • init: полный snapshot состояния, логов и метрик при подключении;
  • status: новое состояние и опциональный output_dir;
  • log: message и level;
  • progress: эпоха, total, извлечённые метрики и сообщение.

Browser → server сообщения не используются; endpoint только читает и отбрасывает их, поддерживая соединение открытым.

Профили хранятся в runs/sessions/{name}.json. Имя: 164 символа из латинских букв, цифр, _, -. last_run зарезервирован для автосохранения при старте: его можно прочитать, но нельзя создать или удалить через profile endpoints.

9. Конфигурация обучения

TrainingConfig

Поле Default Передача в Ultralytics
dataset обязательно; API default coco8.yaml data
model обязательно; API default yolo11n.pt аргумент конструктора YOLO()
task detect аргумент конструктора YOLO()
epochs 100 epochs
image_size 640 imgsz
batch_size 16 batch
device пусто device, только если задано
workers 8 workers; 0 допустим
patience 100 patience; 0 допустим
project runs/train project
run_name пусто name, только если задано
augmentation включена набор augmentation kwargs
mlflow включён Ultralytics setting и env
split выключен preprocessing до YOLO.train()

Основная валидация:

  • epochs >= 1, image_size >= 32;
  • batch положительный или -1; 0 и значения < -1 запрещены;
  • workers >= 0, patience >= 0;
  • задача входит в фиксированный список;
  • detection-style auto split запрещён для classify;
  • dataset/model/project проходят security path validation.

Модель без / или \ считается именем и резолвится как models/{name}.

DatasetSplitConfig

  • enabled=False;
  • train_ratio=0.8, допустимо 0.1…0.95;
  • classes_path="", пустое значение включает автопоиск.

MlflowConfig

  • enabled=True;
  • tracking_uri="sqlite:///mlflow.db";
  • experiment_name="yolo-webui";
  • run_name="".

Если MLflow включён, tracking URI и experiment name не могут быть пустыми. mlflow_environment() временно выставляет:

  • MLFLOW_TRACKING_URI;
  • MLFLOW_EXPERIMENT_NAME;
  • MLFLOW_RUN;
  • MLFLOW_KEEP_RUN_ACTIVE=False.

После обучения предыдущие значения окружения восстанавливаются. В Ultralytics глобальная настройка mlflow включается/выключается через settings.update().

AugmentationConfig

Default-параметры:

hsv_h=0.015       hsv_s=0.7       hsv_v=0.4
degrees=0.0       translate=0.1   scale=0.5
shear=0.0         perspective=0.0
flipud=0.0        fliplr=0.5      bgr=0.0
mosaic=1.0        mixup=0.0       cutmix=0.0
copy_paste=0.0    erasing=0.4     close_mosaic=10
copy_paste_mode=flip
auto_augment=randaugment

Вероятности и доли валидируются в диапазоне 0…1; degrees, shear и close_mosaic не могут быть отрицательными. Режимы copy-paste: flip, mixup. Политики AutoAugment: none (отключено), randaugment, autoaugment, augmix. Если augmentation выключена, эти kwargs вообще не передаются в Ultralytics. При auto_augment="none" параметр транслируется в None для Ultralytics.

10. Работа с датасетами

Auto split предназначен только для detection-style структуры:

dataset/
├── images/
│   └── **/*.{jpg,jpeg,png,bmp,webp,tif,tiff}
└── labels/
    └── **/*.txt

Изображения и labels могут быть вложенными. Для split нужно минимум два изображения. Shuffle детерминирован seed-ом 42; train и val всегда получают минимум по одному изображению.

Порядок определения классов:

  1. явно заданный classes_path — авторитетный, без fallback при ошибке;
  2. корневой classes.txt;
  3. labels/classes.txt;
  4. первый по имени корневой .yaml/.yml с полем names;
  5. вывод диапазона 0…max_id из всех label-файлов с именами class_N.

Поддерживаются text, YAML list и YAML dict. ID должны быть целыми, неповторяющимися и последовательными от 0; пустые имена запрещены.

Каждый split создаётся эксклюзивно:

dataset/.yolo-webui/splits/{uuid}/
├── train.txt      # абсолютные пути изображений
├── val.txt        # абсолютные пути изображений
└── dataset.yaml

Итоговый YAML сохраняет дополнительные ключи исходного YAML, например kpt_shape и flip_idx, но перезаписывает path, train, val и names. Проверенный результат read_classes() всегда авторитетен.

Для classify auto split отключён: пользователь должен предоставить готовую структуру train/, val/ или test/ с подкаталогами классов.

11. TrainingRunner и метрики

Перед обучением Runner:

  1. повторно валидирует config;
  2. при необходимости создаёт split и заменяет data на generated YAML;
  3. импортирует Ultralytics;
  4. включает/выключает MLflow integration;
  5. создаёт YOLO(config.resolved_model, task=config.task);
  6. подключает callbacks on_train_start, on_train_epoch_end, on_train_end;
  7. вызывает model.train(**config.train_kwargs()).

Epoch callback берёт numeric metrics из trainer.metrics, форматирует максимум три первых значения и отправляет их в текстовом сообщении. Parent разбирает пары key=value, поэтому live chart сейчас показывает не более трёх метрик на эпоху.

Если trainer.save_dir существует, его путь передаётся parent-у как результат.

Restricted checkpoint loading принудительно включён:

ULTRALYTICS_SAFE_LOAD=1

12. Frontend

Frontend не имеет сборщика и framework: index.html, style.css и app.js отдаются FastAPI как статические файлы. Chart.js загружается с jsDelivr CDN.

Левая панель содержит профили и вкладки:

  • «Основное» — task, model, dataset, auto split;
  • «Обучение» — epochs, image size, batch, device, workers, patience, output;
  • «Аугментация» — все поля AugmentationConfig;
  • «MLflow» — enabled, tracking URI, experiment и run name.

Правая панель содержит статус, timer, progress bar, Start/Stop, live chart и журнал.

Browser state:

  • активная вкладка хранится в localStorage.active_tab;
  • выбранный профиль — localStorage.selected_profile;
  • черновик формы — localStorage.draft_config;
  • при старте загрузки приоритет: draft → last_run → API defaults;
  • список датасетов и моделей запрашивается у backend;
  • стандартные модели YOLO11 выбираются динамически по task;
  • при disconnect WebSocket переподключается через 5 секунд;
  • init восстанавливает status, логи, progress и историю графика.

Числа читаются через Number.parseInt/parseFloat и проверку Number.isNaN. Нельзя заменять это на value || default: допустимые 0 для workers, patience, close_mosaic и augmentation-параметров должны сохраняться.

Chart datasets создаются по фактически пришедшим ключам. Новая метрика может появиться на поздней эпохе; пропущенные точки заполняются null, чтобы серии не сдвигались относительно labels.

13. Безопасность и доверенная модель

Приложение не имеет аутентификации. Безопасность по умолчанию строится на локальной публикации и ограничении файловых путей.

Default доверенные корни:

Назначение Корни
Dataset и classes ./datasets
Model/checkpoint ./models, ./runs
Training output ./runs

Дополнительные корни перечисляются через системный os.pathsep:

  • YOLO_WEBUI_DATA_ROOTS;
  • YOLO_WEBUI_MODEL_ROOTS;
  • YOLO_WEBUI_RUN_ROOTS.

Проверка запрещает URL, нормализует путь через resolve(strict=False) и проверяет принадлежность корню, включая существующие symlink-компоненты. Безопасные bare identifiers разрешены для официальных имён, но существующий одноимённый файл вне доверенного root отвергается. Model-файлы ограничены расширениями .pt, .pth, .yaml, .yml.

Compose публикует только 127.0.0.1:8000:8000. Для доступа из сети обязателен аутентифицирующий reverse proxy и явная оценка риска: API может запускать тяжёлое обучение, останавливать его и управлять профилями.

14. Docker

Dockerfile:

  • основан на python:3.11-slim;
  • устанавливает системные библиотеки для OpenCV/PyTorch/Ultralytics;
  • фиксирует uv==0.10.6;
  • копирует pyproject.toml, uv.lock, README;
  • выполняет uv sync --locked --no-dev в /opt/venv;
  • включает ULTRALYTICS_SAFE_LOAD=1;
  • запускает yolo-train-webui --host 0.0.0.0 --port 8000.

Compose монтирует:

./datasets       -> /workspace/datasets
./runs           -> /workspace/runs
./models         -> /workspace/models
./models/.config -> /root/.config/Ultralytics

Порт 8000 опубликован только на loopback. Порт 5000 объявлен в image, но Compose не запускает и не публикует MLflow UI. GPU reservation оставлена как закомментированный пример для NVIDIA/Linux.

15. Тесты и проверки

Текущий regression suite содержит 49 pytest-тестов.

  • test_app.py: defaults/status, profiles, deadlock, background WebSocket loop, финальные состояния.
  • test_config.py: kwargs, validation, zero-compatible параметры, MLflow env, security roots и URL.
  • test_splitter.py: форматы классов, приоритеты, nested data, уникальные outputs, сохранение YAML metadata.
  • test_subprocess_runner.py: return codes, READY/RESULT и traceback.
  • test_trainer.py: Ultralytics callbacks, metrics, ранний stop, сигналы.
  • test_frontend.py + frontend_smoke.js: syntax, сохранение нулей, динамические chart series в fake browser environment.

Основные команды:

uv sync --locked
uv run pytest -q
uv run python -m compileall -q src tests
node --check src/yolo_webui/static/app.js
node tests/frontend_smoke.js
uv lock --check
docker compose config
git diff --check

Python-команды проекта следует выполнять через uv run, чтобы использовать зафиксированное окружение.

16. Согласованное изменение проекта

При добавлении или переименовании config-поля обычно нужно изменить вместе:

  1. dataclass, default, validation и train_kwargs() в config.py;
  2. TrainingConfig.from_dict();
  3. поле в static/index.html;
  4. чтение в getFormConfig() и восстановление в applyConfig() в app.js;
  5. backend/frontend regression tests;
  6. README и этот контекст, если меняется пользовательский контракт.

При добавлении нового состояния обучения нужно обновить:

  1. backend state machine и финальную классификацию;
  2. WebSocket status payload;
  3. updateUIStatus();
  4. CSS-селекторы status-*;
  5. тесты переходов и reconnect snapshot.

При изменении subprocess-протокола синхронно меняются subprocess_runner.py и parser в TrainingManager._handle_subprocess_line(). Префиксы протокола нельзя печатать в обычных логах.

Критические инварианты:

  • не вызывать WebSocket send из нового или чужого event loop;
  • не вызывать broadcast() под TrainingManager._lock;
  • не объединять succeeded, cancelled, failed в общий finished;
  • не использовать JS truthiness для числовых полей;
  • явно указанный classes-файл всегда авторитетен;
  • не создавать split поверх пользовательских файлов;
  • не снимать --locked с Docker/CI установки;
  • не расширять сетевую публикацию без аутентификации;
  • сохранять traceback и ошибки доставки в наблюдаемых логах.

17. Текущие ограничения

  • Только один активный training job и один глобальный in-memory LiveState.
  • После перезапуска server live state теряется; сохраняются лишь JSON-профили, last_run, training artifacts и MLflow data.
  • Нет очереди, scheduler, истории runs API, upload API и файлового браузера.
  • Нет встроенной аутентификации и multi-user isolation.
  • Discovery просматривает только верхний уровень datasets/ и models/.
  • Live chart зависит от внешнего Chart.js CDN.
  • В график попадают максимум три numeric metrics, выбранные callback-ом.
  • Реальное длительное YOLO-обучение и Docker image build не входят в быстрый test suite; unit-тесты подменяют Ultralytics и subprocess там, где это возможно.
  • В репозитории нет .dockerignore и CI-конфигурации; Docker build context зависит от содержимого рабочей копии.
  • FastAPI TestClient выдаёт deprecation warning для текущей связки Starlette/httpx; тесты при этом проходят.

Отдельного файла лицензии проекта в репозитории нет. README напоминает, что Ultralytics распространяется по AGPL-3.0 и предлагает отдельно проверить условия Enterprise-лицензии для закрытого коммерческого использования.

Перед работой с известными дефектами сверяйтесь с PROJECT_ISSUES.md: на дату этого контекста перечисленные там 10 проблем исправлены.