Files
caption-test/belka_refactor_04_05_log.md

291 lines
16 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.
# Шаг 4а — Что изменилось
---
## 1. Новая точка входа — `src/main.py`
Запуск тренировки переехал из `src/training/train_gtauav.py::main()` в отдельный модуль `src/main.py`.
**Старый запуск:**
```bash
python src/training/train_gtauav.py --config conf/gtauav_balanced.gin
```
**Новый запуск:**
```bash
python -m src.main gtauav_balanced
```
`src/main.py`:
- Читает имя пресета из `sys.argv[1]` (один позиционный аргумент)
- Резолвит корень проекта через `get_proj_dir()` (поиск по маркерам `pyproject.toml`/`.git`/`in/`)
- Формирует `path2cfg = f"{proj_dir}in/config_files/"` буквально по REQUIREMENTS_GIN_STYLE.md §5
- Вызывает `load_all_configs(path2cfg, preset_name)` — двухпроходная загрузка из `_common`-файлов и пресет-директории
- Передаёт 6 объектов конфига в `train(...)` именованными аргументами
Никакого `argparse`, никаких CLI-overrides — все параметры в `.gin`-файлах.
---
## 2. Изменённый `src/training/train_gtauav.py`
### 2.1 — Удалено
- `import argparse`, `import gin`, `from dataclasses import dataclass, field`
- Класс `TrainConfigGTAUAV` (`@dataclass + @gin.configurable`) — все его поля переехали в 6 классов в `src/conf/`
- Module-level константы `_RGB_ROOT`, `_CAPTION_ROOT`, `_TRAIN_JSON`, `_TEST_JSON`, `_DINO_WEB`, `_DINO_SAT`, `_LRSCLIP`
- Функция `main()` с argparse и CLI-overrides
### 2.2 — Изменена сигнатура `train()`
Было:
```python
def train(cfg: TrainConfigGTAUAV) -> None:
```
Стало:
```python
def train(
pipeline_cfg: PipelineConfig,
hardware_cfg: HardwareConfig,
training_cfg: TrainingConfig,
tracking_cfg: TrackingConfig,
models_common_cfg: ModelsCommonConfig,
models_cfg: DINOv3ModelsConfig | StripNetModelsConfig | SOFIAv1ModelsConfig | SOFIAv71ModelsConfig,
) -> None:
```
### 2.3 — Обращения `cfg.xxx` переписаны
По карте уникальных полей:
- `cfg.train_json`, `cfg.rgb_root`, `cfg.epochs`, `cfg.output_dir`, `cfg.seed`, ... → `pipeline_cfg.*`
- `cfg.batch_size`, `cfg.grad_accum_steps`, `cfg.use_amp`, `cfg.gradient_checkpointing`, ... → `hardware_cfg.*`
- `cfg.tau_init`, `cfg.learning_rate`, `cfg.sampler_type`, `cfg.dss_*`, ... → `training_cfg.*`
- `cfg.use_wandb`, `cfg.use_tb`, `cfg.use_gradcam`, `cfg.use_profiler`, ... → `tracking_cfg.*`
- `cfg.backbone`, `cfg.baseline_mode`, `cfg.init_gate`, `cfg.lrsclip_path``models_common_cfg.*`
- `cfg.dino_web_path`, `cfg.shared_encoder`, `cfg.mona_*` (DINOv3-only) → `models_cfg.*`
- `cfg.stripnet_*` (StripNet-only) → `models_cfg.*`
- `cfg.sofia_preset → models_cfg.variant_label`, `cfg.sofia_d_descriptor → models_cfg.d_descriptor`, `cfg.sofia_use_text_film_*`, `cfg.sofia_mamba_*``models_cfg.*`
- `cfg.sofia_v1_variant → models_cfg.variant_label`, `cfg.sofia_v1_*``models_cfg.*`
### 2.4 — Sofia-модели строятся напрямую из gin
Раньше Sofia v7.1 строился через preset-фабрику + точечные overrides:
```python
# было
preset_map = {"Tiny": sofia_tiny_config, "M": sofia_m_config, "L": sofia_l_config}
sofia_cfg = preset_map[cfg.sofia_preset]() # строит SOFIAConfig с дефолтами размера
sofia_cfg.d_descriptor = cfg.sofia_d_descriptor # потом 8 overrides
sofia_cfg.use_text_film_uav = ...
...
```
Теперь `SOFIAConfig(...)` собирается напрямую из всех 40+ полей `SOFIAv71ModelsConfig`:
```python
# стало
sofia_cfg = SOFIAConfig(
input_size=models_cfg.input_size,
embed_dims=list(models_cfg.embed_dims), # все 4 dims из gin
depths=list(models_cfg.depths),
mamba_extra_kwargs=dict(models_cfg.mamba_extra_kwargs),
... # и все остальные 35+ полей
)
```
Преимущество: каждый размер Sofia (Tiny/M/L) — это отдельный `presets/<name>/models.gin` со всеми полями явно. Не нужно знать, что кладёт `sofia_tiny_config()` в дефолтах. Один источник правды — gin.
Аналогично для Sofia v1: `SOFIAv1Config(...)` строится из полей `SOFIAv1ModelsConfig`.
### 2.5 — Direct execution убран
```python
if __name__ == "__main__":
raise SystemExit(
"Direct execution removed. Use: python -m src.main <preset_name>",
)
```
---
## 3. Проверка передаваемых путей в энкодерах и бэкбонах
После того как `TrainConfigGTAUAV` исчез, поля стали раскиданы по 4 семейным `Models*Config`-классам. Для того чтобы поведение **точно совпадало** со старым кодом, в каждой ветке сборки модели мы передаём **те же значения**, что приходили раньше из `cfg.*` — даже если поле теперь не имеет смысла для активного бэкбона.
### 3.1 — Ветка `sofia_v71`
```python
SOFIAFusionEncoder(
sofia_cfg=..., # из SOFIAv71ModelsConfig
lrsclip_path=models_common_cfg.lrsclip_path, # общий путь к DGTRS-CLIP
init_gate=models_common_cfg.init_gate,
baseline_mode=models_common_cfg.baseline_mode,
lora_rank=models_cfg.lora_rank,
device=hardware_cfg.device,
)
```
— ничего лишнего, всё из gin.
### 3.2 — Ветка `sofia_v1`
```python
SOFIAv1FusionEncoder(
sofia_cfg=SOFIAv1Config(variant=models_cfg.variant_label, ...),
lrsclip_path=models_common_cfg.lrsclip_path,
init_gate=models_common_cfg.init_gate,
baseline_mode=models_common_cfg.baseline_mode,
lora_rank=models_cfg.lora_rank,
device=hardware_cfg.device,
)
```
— симметрично с v7.1.
### 3.3 — Ветка `stripnet`
```python
AsymmetricEncoder(
dino_web_path="nn_models/DINO_WEB/dinov3-vitl16-pretrain-lvd1689m.pth", # старый _DINO_WEB
dino_sat_path="nn_models/DINO_SAT/model.safetensors", # старый _DINO_SAT
lrsclip_path=models_common_cfg.lrsclip_path,
init_gate=models_common_cfg.init_gate,
baseline_mode=models_common_cfg.baseline_mode,
shared_encoder=True, # для StripNet всегда True
mona_bottleneck=64, # старый дефолт TrainConfigGTAUAV
mona_last_n_blocks=12, # старый дефолт
device=hardware_cfg.device,
backbone=backbone,
stripnet_path=models_cfg.stripnet_path,
stripnet_mona_last_n_stages=models_cfg.stripnet_mona_last_n_stages,
stripnet_freeze=models_cfg.stripnet_freeze,
)
```
**Почему DINO-пути передаются для StripNet**: `AsymmetricEncoder.__init__` принимает все 13 параметров независимо от `backbone`. Для StripNet-режима DINO-пути **игнорируются** (модель строит `StripNetEncoder`, не `DINOv3ViT`). Старый код передавал те же `_DINO_WEB`/`_DINO_SAT` всегда — мы воспроизводим точно. Семантика одинакова.
**Почему `shared_encoder=True`**: внутри `AsymmetricEncoder.__init__` на строке `if backbone == "stripnet": self.shared_encoder = True` — значение всё равно перезаписывается. Передаём `True` для семантической чистоты.
**Почему `mona_bottleneck=64`/`mona_last_n_blocks=12`**: `mona_bottleneck=64` **используется** при `inject_conv_mona_into_stripnet(...)` для StripNet — нужно валидное значение. Старый код всегда подставлял дефолт `TrainConfigGTAUAV.mona_bottleneck=64`. Для StripNet поле `mona_bottleneck``mona_last_n_blocks`, последнее не используется для StripNet) **не вынесено** в `StripNetModelsConfig` — это технический долг, отмечен ниже. Пока хардкод `64` совпадает с прежним поведением.
### 3.4 — Ветка `dinov3`
```python
AsymmetricEncoder(
dino_web_path=models_cfg.dino_web_path,
dino_sat_path=models_cfg.dino_sat_path,
lrsclip_path=models_common_cfg.lrsclip_path,
init_gate=models_common_cfg.init_gate,
baseline_mode=models_common_cfg.baseline_mode,
shared_encoder=models_cfg.shared_encoder,
mona_bottleneck=models_cfg.mona_bottleneck,
mona_last_n_blocks=models_cfg.mona_last_n_blocks,
device=hardware_cfg.device,
backbone=backbone,
stripnet_path="nn_models/STRIPNET/stripnet_s.pth", # старый дефолт TrainConfigGTAUAV
stripnet_mona_last_n_stages=0,
stripnet_freeze=True,
)
```
**Почему `stripnet_path` передаётся для DINOv3**: симметричная ситуация. `AsymmetricEncoder.__init__` принимает параметр всегда, для DINOv3 он игнорируется. Старый код передавал `cfg.stripnet_path` (дефолт `nn_models/STRIPNET/stripnet_s.pth`) даже при DINOv3 — воспроизводим то же.
### 3.5 — Resume через `AsymmetricEncoder.load_checkpoint`
`load_checkpoint` принимает только 4 параметра (`path`, `dino_web_path`, `dino_sat_path`, `lrsclip_path`, `device`) — остальные восстанавливаются из чекпоинта. Передаём `dino_*_path` исходя из типа `models_cfg`:
- `DINOv3ModelsConfig` → значения из конфига
- `StripNetModelsConfig` → дефолтные значения `_DINO_WEB`/`_DINO_SAT`
Это **уже было** в старом коде; новый код просто аккуратнее распределил значения по типам конфига.
> **Известное ограничение** (унаследовано от старого кода): `AsymmetricEncoder.load_checkpoint` не поддерживает StripNet-чекпоинты — он не принимает `backbone='stripnet'` и потому при resume StripNet-эксперимента построит DINOv3-модель. Это **не регрессия** — старый код имел тот же баг. Чинить — отдельный шаг.
---
## 4. Переименование `"sofia"` → `"sofia_v71"` в if-ах
В исходном коде разные части использовали разные имена для одного и того же бэкбона:
- В if-ах сборки модели: `if cfg.backbone == "sofia_v71"`
- В чекпоинт-блоке: `if cfg.backbone in ("sofia", "sofia_v1")`**остаточное старое имя**
- В сообщении gradient_checkpointing: `if cfg.backbone in ("stripnet", "sofia", "sofia_v1")` ← тоже
Это был **остаточный баг** после промежуточного переименования `sofia → sofia_v71` — в одних местах сделали, в других забыли. На уровне runtime это не приводило к падению (sofia-эксперименты тогда не запускались), но при первом запуске sofia-пресета чекпоинт-блок не сохранил бы `sofia_cfg` для v7.1 (ветка просто не сработала бы — `backbone == "sofia_v71"` не in `("sofia", "sofia_v1")`).
**Что сделано**:
- В **новых** `presets/<name>/models.gin` для Sofia v7.1: `ModelsCommonConfig.backbone = 'sofia_v71'`
- В новом `train_gtauav.py` **все** if-ы используют `"sofia_v71"`:
```python
if backbone == "sofia_v71": # сборка модели + resume + enc_str
if backbone in ("sofia_v71", "sofia_v1"): # чекпоинт-блок (исправлено)
if backbone in ("stripnet", "sofia_v71", "sofia_v1"): # gradient_checkpointing (исправлено)
```
- В `config_loader.py` мапинг `_BACKBONE_TO_MODELS_CLS`: `"sofia_v71": SOFIAv71ModelsConfig`
Имена теперь **согласованы** на всех уровнях:
- `src/conf/models_common_conf.py` → `backbone: str` ('dinov3' | 'stripnet' | 'sofia_v1' | 'sofia_v71')
- `src/conf/config_loader.py` → словарь маппинга
- `presets/<name>/models.gin` → биндинги
- `src/training/train_gtauav.py` → все if-ы
- `src/models/sofia_v71/` → имя директории моделей
`"sofia"` без версии больше нигде не используется.
---
## 5. Файлы, которые добавились / изменились
### Создано
- `src/main.py` — точка входа
- `src/utils/__init__.py`
- `src/utils/path_utils.py` — `get_proj_dir()`
- `src/utils/seed_utils.py` — `set_seed()` (на 4б)
- `src/utils/io_utils.py` — `atomic_save_torch()`, `clear_vram()` (на 4б)
### Перезаписано
- `src/training/train_gtauav.py` — новый файл (см. `train_gtauav.py` в outputs)
- `src/losses/multi_infonce.py` — снято `@gin.configurable` с `InfoNCELoss` (две строки удалены)
- `src/losses/weighted_infonce.py` — снято `@gin.configurable` с `WeightedInfoNCELoss` (две строки удалены)
### Удалено
- `conf/` (директория, 17 файлов) — старые `.gin` мёртвый код, биндят несуществующий `TrainConfigGTAUAV`
### Не тронуто на 4а
- `src/datasets/gtauav_dataset.py` — `_RGB_ROOT`/`_CAPTION_ROOT` остаются на module-level, но никто их теперь не использует. Удалить можно отдельным мини-коммитом или в 4б.
- `src/datasets/visloc_with_captions.py` (legacy v2) — оставлен по решению пользователя.
---
## 6. Технический долг (на 4б)
1. **`mona_bottleneck` для StripNet** — вынести из хардкода `64` в `StripNetModelsConfig.mona_bottleneck` или в `ModelsCommonConfig`
2. **Декомпозиция `train()`** на `Trainer` + методы (1100 строк → ~50 строк за метод)
3. **`_evaluate` → `src/eval/evaluator.py`** с `@torch.inference_mode()` вместо `@torch.no_grad()`
4. **`CSVLogger` → `src/training/csv_logger.py`**
5. **`_atomic_save` → `atomic_save_torch`** из `src/utils/io_utils.py` (с cleanup `.tmp` на ошибке)
6. **`_set_seed` / `_clear_vram`** заменить на `set_seed` / `clear_vram` из `src/utils/`
7. **`AsymmetricEncoder.load_checkpoint`** для StripNet — расширить сигнатуру или сделать отдельный путь resume
8. **Удалить `_RGB_ROOT`/`_CAPTION_ROOT`** из `gtauav_dataset.py`
---
## 7. Контрольный smoke-test
После применения шага 4а:
```bash
cd <proj_dir>
# 1. Конфиги загружаются.
python -c "
from src.conf.config_loader import load_all_configs
from src.utils.path_utils import get_proj_dir
cfgs = load_all_configs(get_proj_dir() + 'in/config_files/', 'gtauav_balanced')
print('OK', sorted(cfgs.keys()), cfgs['models_common'].backbone)
"
# 2. Тренировка стартует.
python -m src.main gtauav_balanced
# 3. На 1 эпохе и 16 батчах метрики r@1_q2g/r@5_q2g/loss/tau/gate_q/gate_g
# совпадают со старым запуском до 4-го знака.
```
Если все три проверки проходят — шаг 4а закрыт, можно идти в 4б.