train_utility/.agents/PROJECT_CONTEXT.md

600 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Контекст проекта 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. Структура репозитория
```text
.
├── .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. Архитектура во время выполнения
```text
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`:
```bash
uv run yolo-train-webui
```
Альтернативный модульный запуск:
```bash
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` для классификации результата.
Состояния:
```text
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-протокол
Дочерний процесс печатает специальные маркеры:
```text
__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-параметры:
```text
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: `randaugment`, `autoaugment`, `augmix`. Если augmentation
выключена, эти kwargs вообще не передаются в Ultralytics.
## 10. Работа с датасетами
Auto split предназначен только для detection-style структуры:
```text
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 создаётся эксклюзивно:
```text
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 принудительно включён:
```text
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 монтирует:
```text
./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.
Основные команды:
```bash
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 проблем исправлены.