claude_refactor_v3: Added plan.md, new conf dirictories

This commit is contained in:
pikaliov
2026-04-29 15:50:36 +03:00
parent bf5e417094
commit ce9e3dc94b
89 changed files with 1030 additions and 0 deletions

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

942
refactor_v3_plan.md Normal file
View File

@@ -0,0 +1,942 @@
# Шаг 2 — План разделения с диффами
> Решения, на которых строится этот план:
> 1. **Плоские `.gin`** без `include` — каждый эксперимент = самодостаточный набор файлов
> 2. **`TrainConfigGTAUAV` разделяем сразу**
> 3. **Отдельные `ModelsConfig`-классы на каждый бэкбон** (DINOv3, StripNet, SOFIAv1, SOFIAv71)
> 4. **Скрипты переводим на gin**
>
> Этот документ — **план**, не финальный полный набор диффов. Он отвечает на вопрос «какие конфиг-классы будут, какие гин-файлы, какая иерархия пресетов». Для каждого нового файла — содержимое. Для каждого правимого файла — diff. Это ответ на вопрос «что делать с конфигом», без переписывания `Trainer` (это будет следующим шагом).
---
## Часть A — Раскладка `TrainConfigGTAUAV` по 4 «универсальным» конфигам
`TrainConfigGTAUAV` содержит 50+ полей. Разделяю их по принципу из «Рекомендуемые_gin-config_категории.md» (одна ось изменчивости = один конфиг):
| Поле текущего `TrainConfigGTAUAV` | Куда едет |
|---|---|
| `train_json`, `test_json`, `rgb_root`, `caption_root`, `filter_meta`, `output_dir`, `resume_from`, `epochs`, `eval_every`, `warmup_epochs`, `seed` | **`PipelineConfig`** |
| `batch_size`, `num_workers`, `grad_accum_steps`, `use_amp`, `gradient_checkpointing`, `device` | **`HardwareConfig`** |
| `loss_type`, `tau_init`, `label_smoothing`, `learnable_temperature`, `weight_q2g`, `weight_g2q`, `neg_bank_size`, `learning_rate`, `text_lr_factor`, `weight_decay`, `grad_clip`, `sampler_type`, `dss_*`, `use_mutex_sampler` | **`TrainingConfig`** |
| `use_wandb`, `use_tb`, `wandb_project`, `wandb_run_name`, `wandb_entity`, `log_grad_norms`, `use_gradcam`, `gradcam_every`, `gradcam_samples`, `use_profiler`, `profiler_warmup`, `profiler_active` | **`TrackingConfig`** |
| `dino_web_path`, `dino_sat_path`, `lrsclip_path`, `init_gate`, `baseline_mode`, `shared_encoder`, `mona_bottleneck`, `mona_last_n_blocks`, `backbone`, `stripnet_path`, `stripnet_mona_last_n_stages`, `stripnet_freeze`, `stripnet_backbone_lr_factor` | **`Models*Config`** (см. Часть B) |
**Итого 4 «универсальных» конфига** (Pipeline / Hardware / Training / Tracking) + плюс семейство Models-классов из Части B.
---
## Часть B — Семейство `Models*Config` (по бэкбону)
Каждый бэкбон → собственный gin-configurable класс. **Один на эксперимент** — какой именно загружается, определяется тем, какой `models_*.gin` положен в директорию пресета.
### `ModelsCommonConfig` — общие поля
Поля, нужные **всем** бэкбонам:
```python
@gin.configurable
class ModelsCommonConfig:
"""Common architecture switches shared by all backbones."""
def __init__(
self,
backbone: str = "dinov3", # 'dinov3' | 'stripnet' | 'sofia_v1' | 'sofia_v71'
baseline_mode: bool = False, # text disabled, gate forced 1.0
init_gate: float = 0.7,
lrsclip_path: str = "nn_models/LRSCLIP/DGTRS-CLIP-ViT-L-14.pt",
) -> None:
self.backbone = backbone
self.baseline_mode = baseline_mode
self.init_gate = init_gate
self.lrsclip_path = lrsclip_path
```
### `DINOv3ModelsConfig`
```python
@gin.configurable
class DINOv3ModelsConfig:
def __init__(
self,
dino_web_path: str = "nn_models/DINO_WEB/dinov3-vitl16-pretrain-lvd1689m.pth",
dino_sat_path: str = "nn_models/DINO_SAT/model.safetensors",
shared_encoder: bool = True,
mona_bottleneck: int = 64,
mona_last_n_blocks: int = 12,
) -> None:
self.dino_web_path = dino_web_path
self.dino_sat_path = dino_sat_path
self.shared_encoder = shared_encoder
self.mona_bottleneck = mona_bottleneck
self.mona_last_n_blocks = mona_last_n_blocks
```
### `StripNetModelsConfig`
```python
@gin.configurable
class StripNetModelsConfig:
def __init__(
self,
stripnet_path: str = "nn_models/STRIPNET/stripnet_s.pth",
stripnet_freeze: bool = True,
stripnet_mona_last_n_stages: int = 2,
stripnet_backbone_lr_factor: float = 0.1,
) -> None:
self.stripnet_path = stripnet_path
self.stripnet_freeze = stripnet_freeze
self.stripnet_mona_last_n_stages = stripnet_mona_last_n_stages
self.stripnet_backbone_lr_factor = stripnet_backbone_lr_factor
```
### `SOFIAv1ModelsConfig`
Покрывает всё, что сейчас в `src/models/sofia_v1/config.py::SOFIAv1Config`:
```python
@gin.configurable
class SOFIAv1ModelsConfig:
def __init__(
self,
# Backbone.
variant: str = "small", # 'tiny_tiny' | 'tiny' | 'small' | 'small_v2'
in_channels: int = 3,
input_size: int = 256,
dcn_variant: str = "v2", # 'v2' (stable) | 'v4' (faster, leaks)
# Heads.
d_descriptor: int = 1024,
return_normalized: bool = False,
# Altitude-FiLM.
use_film_altitude: bool = True,
altitude_norm: float = 500.0,
# Text-FiLM.
use_text_film_uav: bool = True,
use_text_film_sat: bool = True,
text_film_dim: int = 1024,
text_film_hidden: int = 256,
) -> None:
self.variant = variant
self.in_channels = in_channels
self.input_size = input_size
self.dcn_variant = dcn_variant
self.d_descriptor = d_descriptor
self.return_normalized = return_normalized
self.use_film_altitude = use_film_altitude
self.altitude_norm = altitude_norm
self.use_text_film_uav = use_text_film_uav
self.use_text_film_sat = use_text_film_sat
self.text_film_dim = text_film_dim
self.text_film_hidden = text_film_hidden
```
> **`SOFIAv1Config` (`@dataclass`) в `src/models/sofia_v1/config.py` остаётся** — это внутренняя структура для модели. В `Trainer._build_model` создаём `SOFIAv1Config(...)` из полей `models_cfg` (где `models_cfg: SOFIAv1ModelsConfig`). Один источник правды — gin, а dataclass это просто адаптер на границе модельного слоя.
### `SOFIAv71ModelsConfig`
Покрывает поля из `src/models/sofia_v71/config.py`. По README вижу:
```python
@gin.configurable
class SOFIAv71ModelsConfig:
def __init__(
self,
# Preset.
preset: str = "M", # 'M' | 'L' | 'Tiny'
# Mamba backend.
mamba_variant: str = "mamba2", # 'mamba1' | 'mamba2' | 'efficient_vmamba'
mamba_backend: str = "auto", # 'auto' | 'torch'
# Heads.
d_descriptor: int = 512,
# Altitude-FiLM.
use_film_altitude: bool = True,
altitude_norm: float = 500.0,
# KD taps.
return_features: bool = False,
# Quantization (PTQ/QAT — for production deploy, not training).
# Not adding here unless an experiment toggles them; can be added later.
) -> None:
self.preset = preset
self.mamba_variant = mamba_variant
self.mamba_backend = mamba_backend
self.d_descriptor = d_descriptor
self.use_film_altitude = use_film_altitude
self.altitude_norm = altitude_norm
self.return_features = return_features
```
> ⚠️ Точные поля `SOFIAv71` нужно сверить с `src/models/sofia_v71/config.py` (я его не открыл целиком). Это **открытый пункт** — заполняется при создании файла.
### Как загружать «правильный» Models config?
Ровно один `models.gin` лежит в директории пресета и определяет один из 4 классов. `config_loader` ветвится:
```python
def load_all_configs(path2cfg: str) -> dict[str, Any]:
cfg_dir = Path(path2cfg)
gin.clear_config()
gin.parse_config_files_and_bindings(
config_files=[str(f) for f in sorted(cfg_dir.glob("*.gin"))],
bindings=[],
)
# Build common first to learn which backbone to use.
common = ModelsCommonConfig()
backbone_to_cls = {
"dinov3": DINOv3ModelsConfig,
"stripnet": StripNetModelsConfig,
"sofia_v1": SOFIAv1ModelsConfig,
"sofia_v71": SOFIAv71ModelsConfig,
}
if common.backbone not in backbone_to_cls:
raise ValueError(
f"Unknown backbone={common.backbone!r}; expected one of {list(backbone_to_cls)}",
)
models_specific = backbone_to_cls[common.backbone]() # gin fills it
return {
"pipeline": PipelineConfig(),
"hardware": HardwareConfig(),
"models_common": common,
"models": models_specific, # one of 4 classes
"training": TrainingConfig(),
"tracking": TrackingConfig(),
}
```
**В `models.gin` пресета** прописаны биндинги **только** для активного бэкбона + `ModelsCommonConfig`. Биндинги для других бэкбонов в этот файл не попадают (плоский стиль).
---
## Часть C — Конфиг для скриптов препроцессинга (Часть 4 ваших ответов)
Скрипты `make_split.py` и `filter_segmentation.py` переводятся на gin. Возникает **5-й универсальный конфиг**:
### `PreprocessConfig`
```python
@gin.configurable
class PreprocessConfig:
"""Preprocessing utilities: train/test split + segmentation filter.
Used only by scripts/make_split.py and scripts/filter_segmentation.py.
Not consumed by the training pipeline directly.
"""
def __init__(
self,
# Inputs (read from PipelineConfig if you want consistency, but having
# them here lets preprocess run independently).
rgb_root: str = "/home/servml/Документы/datasets/GTA-UAV-LR",
segm_root: str = "/home/servml/Документы/datasets/GTA-UAV-LR-aug/segm",
# make_split params.
split_ratio: float = 0.8,
split_seed: int = 42,
split_input_train: str = "cross-area-drone2sate-train.json",
split_input_test: str = "cross-area-drone2sate-test.json",
split_output_dir: str = "meta",
split_output_train: str = "train_80.json",
split_output_test: str = "test_20.json",
# filter_segmentation params.
seg_threshold: float = 0.90,
seg_exclude_classes: list[int] | None = None, # default [0, 4]
seg_filter_output: str = "meta/seg_filter.json",
) -> None:
self.rgb_root = rgb_root
self.segm_root = segm_root
self.split_ratio = split_ratio
self.split_seed = split_seed
self.split_input_train = split_input_train
self.split_input_test = split_input_test
self.split_output_dir = split_output_dir
self.split_output_train = split_output_train
self.split_output_test = split_output_test
self.seg_threshold = seg_threshold
self.seg_exclude_classes = seg_exclude_classes or [0, 4]
self.seg_filter_output = seg_filter_output
```
> **Вопрос дизайна:** держать `rgb_root` отдельно в `PreprocessConfig` (как выше) или брать из `PipelineConfig.rgb_root`?
>
> **Решение:** держать **отдельно**, потому что `PreprocessConfig` живёт в **другом** наборе .gin (отдельный пресет `preprocess/`). Это согласуется с принципом плоских конфигов без ссылок между файлами. Дублирование одного пути на 2 файла — приемлемая цена за изоляцию.
---
## Часть D — Что и где будет лежать (структура каталогов)
```
caption-test/
├── in/
│ └── config_files/ # АКТИВНЫЙ пресет, копируется из presets/
│ ├── pipeline.gin
│ ├── hardware.gin
│ ├── models.gin # биндинги ModelsCommonConfig + одного из Models*Config
│ ├── training.gin
│ └── tracking.gin
├── presets/
│ ├── gtauav_balanced/ # все 5 файлов, БЕЗ include
│ │ ├── pipeline.gin
│ │ ├── hardware.gin
│ │ ├── models.gin # backbone='dinov3', shared, mona_12
│ │ ├── training.gin
│ │ └── tracking.gin
│ ├── gtauav_baseline/ # 5 файлов, baseline_mode=True
│ ├── gtauav_balanced_asym/ # shared_encoder=False, mona_24
│ ├── gtauav_baseline_asym/
│ ├── gtauav_balanced_stripnet/ # backbone='stripnet'
│ ├── gtauav_balanced_stripnet_unfrozen/
│ ├── gtauav_baseline_stripnet/
│ ├── gtauav_baseline_stripnet_unfrozen/
│ ├── gtauav_text_heavy/ # init_gate=0.3
│ ├── gtauav_image_heavy/ # init_gate=0.9
│ ├── gtauav_balanced_sofia/ # backbone='sofia_v71'
│ ├── gtauav_balanced_sofia_v1/ # backbone='sofia_v1'
│ ├── gtauav_baseline_sofia/
│ ├── gtauav_baseline_sofia_v1/
│ └── preprocess/ # отдельный пресет для скриптов
│ └── preprocess.gin # PreprocessConfig.* — одиночный файл достаточен
├── src/
│ └── conf/
│ ├── __init__.py
│ ├── pipeline_conf.py # PipelineConfig + get_pipeline_cfg
│ ├── hardware_conf.py # HardwareConfig + get_hardware_cfg
│ ├── training_conf.py # TrainingConfig + get_training_cfg
│ ├── tracking_conf.py # TrackingConfig + get_tracking_cfg
│ ├── models_common_conf.py # ModelsCommonConfig + get_models_common_cfg
│ ├── models_dinov3_conf.py # DINOv3ModelsConfig + get_models_dinov3_cfg
│ ├── models_stripnet_conf.py # StripNetModelsConfig + get_models_stripnet_cfg
│ ├── models_sofia_v1_conf.py # SOFIAv1ModelsConfig + get_models_sofia_v1_cfg
│ ├── models_sofia_v71_conf.py # SOFIAv71ModelsConfig + get_models_sofia_v71_cfg
│ ├── preprocess_conf.py # PreprocessConfig + get_preprocess_cfg
│ └── config_loader.py # load_all_configs() с разводкой по backbone
```
---
## Часть E — Содержимое `presets/gtauav_balanced/` (точно)
Это эталонный пресет — остальные диффятся от него.
### `presets/gtauav_balanced/pipeline.gin`
```gin
# What to train on, where to save, schedule.
PipelineConfig.train_json = 'meta/train_80.json'
PipelineConfig.test_json = 'meta/test_20.json'
PipelineConfig.rgb_root = '/home/servml/Документы/datasets/GTA-UAV-LR'
PipelineConfig.caption_root = '/home/servml/Документы/datasets/GTA-UAV-LR-captions'
PipelineConfig.filter_meta = None
PipelineConfig.output_dir = 'out/gtauav/with_text'
PipelineConfig.resume_from = None
PipelineConfig.epochs = 10
PipelineConfig.warmup_epochs = 2
PipelineConfig.eval_every = 1
PipelineConfig.seed = 42
```
### `presets/gtauav_balanced/hardware.gin`
```gin
# RTX 4090 profile.
HardwareConfig.device = 'cuda'
HardwareConfig.batch_size = 8
HardwareConfig.grad_accum_steps = 8
HardwareConfig.num_workers = 4
HardwareConfig.use_amp = True
HardwareConfig.gradient_checkpointing = True
HardwareConfig.reserve_gb = 2.0
```
### `presets/gtauav_balanced/models.gin`
```gin
# DINOv3 shared encoder + MONA in last 12 of 24 blocks + DGTRS-CLIP text.
ModelsCommonConfig.backbone = 'dinov3'
ModelsCommonConfig.baseline_mode = False
ModelsCommonConfig.init_gate = 0.7
ModelsCommonConfig.lrsclip_path = 'nn_models/LRSCLIP/DGTRS-CLIP-ViT-L-14.pt'
DINOv3ModelsConfig.dino_web_path = 'nn_models/DINO_WEB/dinov3-vitl16-pretrain-lvd1689m.pth'
DINOv3ModelsConfig.dino_sat_path = 'nn_models/DINO_SAT/model.safetensors'
DINOv3ModelsConfig.shared_encoder = True
DINOv3ModelsConfig.mona_bottleneck = 64
DINOv3ModelsConfig.mona_last_n_blocks = 12
```
### `presets/gtauav_balanced/training.gin`
```gin
# Loss + optimizer + sampler.
TrainingConfig.loss_type = 'symmetric'
TrainingConfig.tau_init = 0.07
TrainingConfig.label_smoothing = 0.1
TrainingConfig.learnable_temperature = True
TrainingConfig.weight_q2g = 0.6
TrainingConfig.weight_g2q = 0.4
TrainingConfig.neg_bank_size = 0
TrainingConfig.learning_rate = 1e-4
TrainingConfig.text_lr_factor = 0.1
TrainingConfig.weight_decay = 1e-4
TrainingConfig.grad_clip = 1.0
TrainingConfig.sampler_type = 'mutex'
TrainingConfig.dss_warmup_epochs = 1
TrainingConfig.dss_reembed_every = 1
TrainingConfig.dss_knn_device = 'cuda'
TrainingConfig.dss_use_lsh = False
TrainingConfig.dss_lsh_num_tables = 8
TrainingConfig.dss_lsh_num_bits = 14
TrainingConfig.dss_cache_dir = None
TrainingConfig.use_mutex_sampler = True
```
### `presets/gtauav_balanced/tracking.gin`
```gin
TrackingConfig.use_wandb = False
TrackingConfig.use_tb = True
TrackingConfig.wandb_project = 'caption-test-gtauav'
TrackingConfig.wandb_run_name = None
TrackingConfig.wandb_entity = None
TrackingConfig.log_grad_norms = True
TrackingConfig.use_gradcam = False
TrackingConfig.gradcam_every = 5
TrackingConfig.gradcam_samples = 8
TrackingConfig.use_profiler = False
TrackingConfig.profiler_warmup = 3
TrackingConfig.profiler_active = 5
```
---
## Часть F — Дельты остальных пресетов от `gtauav_balanced/`
Каждый пресет — **полная копия** `gtauav_balanced/` с точечными изменениями в указанных файлах. Никакого `include`.
### `gtauav_baseline/`
**Дельта от `gtauav_balanced/`:**
`pipeline.gin`:
```gin
PipelineConfig.output_dir = 'out/gtauav/baseline_inbatch'
```
`models.gin`:
```gin
ModelsCommonConfig.baseline_mode = True
```
`training.gin`:
```gin
TrainingConfig.sampler_type = 'mutex' # was already, kept explicit per diagnostic notes in old conf/gtauav_baseline.gin
TrainingConfig.neg_bank_size = 0 # explicitly disabled (no MoCo queue)
```
(Остальные 3 файла — побайтовая копия из `gtauav_balanced/`.)
### `gtauav_balanced_asym/`
`pipeline.gin`:
```gin
PipelineConfig.output_dir = 'out/gtauav/balanced_asym'
```
`models.gin`:
```gin
DINOv3ModelsConfig.shared_encoder = False
DINOv3ModelsConfig.mona_last_n_blocks = 24
```
### `gtauav_baseline_asym/`
Объединяет дельту `gtauav_baseline/` и `gtauav_balanced_asym/`:
`pipeline.gin`: `output_dir = 'out/gtauav/baseline_asym'`
`models.gin`: `baseline_mode = True`, `shared_encoder = False`, `mona_last_n_blocks = 24`
### `gtauav_balanced_stripnet/`
`pipeline.gin`: `output_dir = 'out/gtauav/balanced_stripnet'`
`models.gin` (полностью):
```gin
ModelsCommonConfig.backbone = 'stripnet'
ModelsCommonConfig.baseline_mode = False
ModelsCommonConfig.init_gate = 0.7
ModelsCommonConfig.lrsclip_path = 'nn_models/LRSCLIP/DGTRS-CLIP-ViT-L-14.pt'
StripNetModelsConfig.stripnet_path = 'nn_models/STRIPNET/stripnet_s.pth'
StripNetModelsConfig.stripnet_freeze = True
StripNetModelsConfig.stripnet_mona_last_n_stages = 2
StripNetModelsConfig.stripnet_backbone_lr_factor = 0.1
```
(Биндинги `DINOv3ModelsConfig.*` НЕ попадают в этот файл — другой бэкбон.)
### `gtauav_balanced_stripnet_unfrozen/`
Дельта от `gtauav_balanced_stripnet/`:
`pipeline.gin`: `output_dir = 'out/gtauav/balanced_stripnet_unfrozen'`
`models.gin`: `StripNetModelsConfig.stripnet_freeze = False`
### `gtauav_baseline_stripnet/`, `gtauav_baseline_stripnet_unfrozen/`
Аналогично — `baseline_mode = True` поверх stripnet-вариантов.
### `gtauav_text_heavy/`, `gtauav_image_heavy/`
`pipeline.gin`: соответствующие `output_dir`
`models.gin`: `ModelsCommonConfig.init_gate = 0.3` (text-heavy) или `0.9` (image-heavy)
### `gtauav_balanced_sofia_v1/`
`pipeline.gin`: `output_dir = 'out/gtauav/balanced_sofia_v1'`
`models.gin` (полностью):
```gin
ModelsCommonConfig.backbone = 'sofia_v1'
ModelsCommonConfig.baseline_mode = False
ModelsCommonConfig.init_gate = 0.7
ModelsCommonConfig.lrsclip_path = 'nn_models/LRSCLIP/DGTRS-CLIP-ViT-L-14.pt'
SOFIAv1ModelsConfig.variant = 'small'
SOFIAv1ModelsConfig.in_channels = 3
SOFIAv1ModelsConfig.input_size = 256
SOFIAv1ModelsConfig.dcn_variant = 'v2'
SOFIAv1ModelsConfig.d_descriptor = 1024
SOFIAv1ModelsConfig.return_normalized = False
SOFIAv1ModelsConfig.use_film_altitude = True
SOFIAv1ModelsConfig.altitude_norm = 500.0
SOFIAv1ModelsConfig.use_text_film_uav = True
SOFIAv1ModelsConfig.use_text_film_sat = True
SOFIAv1ModelsConfig.text_film_dim = 1024
SOFIAv1ModelsConfig.text_film_hidden = 256
```
### `gtauav_balanced_sofia/` (= sofia_v71)
`pipeline.gin`: `output_dir = 'out/gtauav/balanced_sofia'`
`models.gin`:
```gin
ModelsCommonConfig.backbone = 'sofia_v71'
ModelsCommonConfig.baseline_mode = False
ModelsCommonConfig.init_gate = 0.7
ModelsCommonConfig.lrsclip_path = 'nn_models/LRSCLIP/DGTRS-CLIP-ViT-L-14.pt'
SOFIAv71ModelsConfig.preset = 'M'
SOFIAv71ModelsConfig.mamba_variant = 'mamba2'
SOFIAv71ModelsConfig.mamba_backend = 'auto'
SOFIAv71ModelsConfig.d_descriptor = 512
SOFIAv71ModelsConfig.use_film_altitude = True
SOFIAv71ModelsConfig.altitude_norm = 500.0
SOFIAv71ModelsConfig.return_features = False
```
> ⚠️ Точные дефолты для sofia_v71 пресета зависят от того, как сейчас выглядит `gtauav_balanced_sofia.gin` в локальной копии. **Нужны сами файлы**, чтобы воспроизвести один-в-один.
### `gtauav_baseline_sofia/`, `gtauav_baseline_sofia_v1/`
`baseline_mode = True` поверх sofia-вариантов.
---
## Часть G — `presets/preprocess/preprocess.gin`
Один файл (одиночный, потому что один класс):
```gin
PreprocessConfig.rgb_root = '/home/servml/Документы/datasets/GTA-UAV-LR'
PreprocessConfig.segm_root = '/home/servml/Документы/datasets/GTA-UAV-LR-aug/segm'
PreprocessConfig.split_ratio = 0.8
PreprocessConfig.split_seed = 42
PreprocessConfig.split_input_train = 'cross-area-drone2sate-train.json'
PreprocessConfig.split_input_test = 'cross-area-drone2sate-test.json'
PreprocessConfig.split_output_dir = 'meta'
PreprocessConfig.split_output_train = 'train_80.json'
PreprocessConfig.split_output_test = 'test_20.json'
PreprocessConfig.seg_threshold = 0.90
PreprocessConfig.seg_exclude_classes = [0, 4]
PreprocessConfig.seg_filter_output = 'meta/seg_filter.json'
```
---
## Часть H — Диффы для существующих файлов
> Здесь только то, что нужно поменять **в конфигурационной части**. Внутренности `Trainer`, `_evaluate`, `CSVLogger` — не трогаются на этом шаге.
### Файл: `src/training/train_gtauav.py`
**Полностью убрать `TrainConfigGTAUAV` и module-level path constants.** Функция `train()` получает не `cfg: TrainConfigGTAUAV`, а пять config-объектов.
```diff
from __future__ import annotations
...
- import argparse
...
- from dataclasses import dataclass, field
- from pathlib import Path
+ from pathlib import Path
- import gin
...
- # Default paths.
- _RGB_ROOT = "/home/servml/Документы/datasets/GTA-UAV-LR"
- _CAPTION_ROOT = "/home/servml/Документы/datasets/GTA-UAV-LR-captions"
- _TRAIN_JSON = "meta/train_80.json"
- _TEST_JSON = "meta/test_20.json"
-
- _DINO_WEB = "nn_models/DINO_WEB/dinov3-vitl16-pretrain-lvd1689m.pth"
- _DINO_SAT = "nn_models/DINO_SAT/model.safetensors"
- _LRSCLIP = "nn_models/LRSCLIP/DGTRS-CLIP-ViT-L-14.pt"
-
-
- @gin.configurable(module="src.training.train_gtauav")
- @dataclass
- class TrainConfigGTAUAV:
- """Training configuration for GTA-UAV experiment."""
- # Data.
- train_json: str = _TRAIN_JSON
- test_json: str = _TEST_JSON
- rgb_root: str = _RGB_ROOT
- # ... ВСЕ 50+ полей удаляются ...
- profiler_active: int = 5
-
-
- def train(cfg: TrainConfigGTAUAV) -> None:
+ def train(
+ pipeline_cfg: "PipelineConfig",
+ hardware_cfg: "HardwareConfig",
+ models_common_cfg: "ModelsCommonConfig",
+ models_cfg: "DINOv3ModelsConfig | StripNetModelsConfig | SOFIAv1ModelsConfig | SOFIAv71ModelsConfig",
+ training_cfg: "TrainingConfig",
+ tracking_cfg: "TrackingConfig",
+ ) -> None:
"""Run full training loop."""
# Inside the function body, every `cfg.<field>` reference is rewritten to
# the appropriate <kind>_cfg.<field>. Mapping:
# cfg.train_json → pipeline_cfg.train_json
# cfg.batch_size → hardware_cfg.batch_size
# cfg.tau_init → training_cfg.tau_init
# cfg.use_wandb → tracking_cfg.use_wandb
# cfg.dino_web_path → models_cfg.dino_web_path (when DINOv3)
# cfg.stripnet_path → models_cfg.stripnet_path (when StripNet)
# cfg.backbone → models_common_cfg.backbone
# cfg.baseline_mode → models_common_cfg.baseline_mode
# cfg.init_gate → models_common_cfg.init_gate
# cfg.lrsclip_path → models_common_cfg.lrsclip_path
...
- def main() -> None:
- parser = argparse.ArgumentParser(description="GTA-UAV caption test training.")
- parser.add_argument("--config", type=str, default=None, ...)
- parser.add_argument("--baseline", action="store_true", ...)
- # ... все 15 argparse флагов удаляются ...
- args = parser.parse_args()
-
- if args.config is not None:
- gin.parse_config_file(args.config)
- if args.gin_param:
- gin.parse_config(args.gin_param)
-
- cfg = TrainConfigGTAUAV()
-
- if args.baseline:
- cfg.baseline_mode = True
- # ... все CLI overrides удаляются ...
- train(cfg)
+ def main() -> None:
+ """Entry point: read configs from in/config_files/ and run training."""
+ from src.conf.config_loader import load_all_configs
+ from src.utils.path_utils import get_proj_dir
+
+ proj_dir = get_proj_dir()
+ path2cfg = f"{proj_dir}in/config_files/"
+ configs = load_all_configs(path2cfg)
+
+ train(
+ pipeline_cfg=configs["pipeline"],
+ hardware_cfg=configs["hardware"],
+ models_common_cfg=configs["models_common"],
+ models_cfg=configs["models"],
+ training_cfg=configs["training"],
+ tracking_cfg=configs["tracking"],
+ )
```
### Файл: `src/losses/multi_infonce.py`
Снять `@gin.configurable` (см. Шаг 1, Нарушение #2). Параметры будут приходить из `TrainingConfig` через явный вызов в `train()`:
```diff
from __future__ import annotations
...
import math
- import gin
import torch
...
- @gin.configurable
class InfoNCELoss(nn.Module):
"""Symmetric InfoNCE with learnable or scheduled temperature.
+
+ Note: NOT @gin.configurable. Parameters arrive explicitly from
+ train() via TrainingConfig.* — single source of truth.
...
"""
```
### Файл: `src/losses/weighted_infonce.py`
Аналогично:
```diff
...
- import gin
import torch
...
- @gin.configurable
class WeightedInfoNCELoss(nn.Module):
"""Weighted InfoNCE with adaptive per-sample label smoothing.
+
+ Note: NOT @gin.configurable. Parameters arrive explicitly from
+ train() via TrainingConfig.* — single source of truth.
...
"""
```
### Файл: `src/datasets/visloc_with_captions.py` (legacy v2)
Аналогично — снять `@gin.configurable` с `GeoLocCaptionDataset`. Если v2 удаляется как ветка — этот файл удаляется целиком, дифф не нужен.
```diff
- import gin
...
- @gin.configurable
class GeoLocCaptionDataset(Dataset):
...
```
### Файл: `src/datasets/gtauav_dataset.py`
Убрать module-level пути. `rgb_root` и `caption_root` становятся обязательными в `__init__` — они придут из `pipeline_cfg.rgb_root` / `pipeline_cfg.caption_root` в `train()`.
```diff
...
- # Default paths.
- _RGB_ROOT = Path("/home/servml/Документы/datasets/GTA-UAV-LR")
- _CAPTION_ROOT = Path("/home/servml/Документы/datasets/GTA-UAV-LR-captions")
_EMPTY_CAPTION = ""
...
class GTAUAVDataset(Dataset):
def __init__(
self,
pair_json: str,
- rgb_root: str = str(_RGB_ROOT),
- caption_root: str = str(_CAPTION_ROOT),
+ rgb_root: str,
+ caption_root: str,
drone_transform: Callable | None = None,
...
) -> None:
```
### Файл: `scripts/make_split.py`
Полностью переписывается на gin: argparse → `get_preprocess_cfg`, module-level пути → поля конфига.
```diff
from __future__ import annotations
...
- import argparse
import json
import logging
import random
from pathlib import Path
import coloredlogs
+ from src.conf.preprocess_conf import get_preprocess_cfg
+ from src.utils.path_utils import get_proj_dir
LOGGER = logging.getLogger("caption_test.make_split")
- _RGB_ROOT = Path("/home/servml/Документы/datasets/GTA-UAV-LR")
-
-
def main() -> None:
- parser = argparse.ArgumentParser(description="Create 80/20 split for GTA-UAV-LR.")
- parser.add_argument("--ratio", type=float, default=0.8, help="Train ratio (default 0.8).")
- parser.add_argument("--seed", type=int, default=42, help="Random seed.")
- parser.add_argument("--output-dir", type=str, default="meta", help="Output directory.")
- args = parser.parse_args()
-
- coloredlogs.install(level="INFO", logger=LOGGER, ...)
+ coloredlogs.install(level="INFO", logger=LOGGER, ...)
+
+ # Load config from a separate preprocess preset directory.
+ proj_dir = get_proj_dir()
+ cfg = get_preprocess_cfg(f"{proj_dir}presets/preprocess/")
+
+ rgb_root = Path(cfg.rgb_root)
+ train_path = rgb_root / cfg.split_input_train
+ test_path = rgb_root / cfg.split_input_test
- train_path = _RGB_ROOT / "cross-area-drone2sate-train.json"
- test_path = _RGB_ROOT / "cross-area-drone2sate-test.json"
-
LOGGER.info("📂 Loading %s", train_path.name)
with open(train_path) as f:
part1 = json.load(f)
...
- rng = random.Random(args.seed)
+ rng = random.Random(cfg.split_seed)
rng.shuffle(all_pairs)
- n_train = int(len(all_pairs) * args.ratio)
+ n_train = int(len(all_pairs) * cfg.split_ratio)
...
- out_dir = Path(args.output_dir)
+ out_dir = Path(cfg.split_output_dir)
out_dir.mkdir(parents=True, exist_ok=True)
- train_out = out_dir / "train_80.json"
- test_out = out_dir / "test_20.json"
+ train_out = out_dir / cfg.split_output_train
+ test_out = out_dir / cfg.split_output_test
...
```
### Файл: `scripts/filter_segmentation.py`
Аналогично:
```diff
from __future__ import annotations
...
- import argparse
import json
import logging
from pathlib import Path
import coloredlogs
import numpy as np
from PIL import Image
from tqdm import tqdm
+ from src.conf.preprocess_conf import get_preprocess_cfg
+ from src.utils.path_utils import get_proj_dir
LOGGER = logging.getLogger("caption_test.filter_seg")
- SEGM_ROOT = Path("/home/servml/Документы/datasets/GTA-UAV-LR-aug/segm")
- EXCLUDE_CLASSES = {0, 4} # background, water
- DEFAULT_THRESHOLD = 0.90
-
...
def main() -> None:
- parser = argparse.ArgumentParser(...)
- parser.add_argument("--segm-root", ...)
- parser.add_argument("--threshold", ...)
- parser.add_argument("--output", ...)
- args = parser.parse_args()
+ coloredlogs.install(level="INFO", logger=LOGGER, ...)
+
+ proj_dir = get_proj_dir()
+ cfg = get_preprocess_cfg(f"{proj_dir}presets/preprocess/")
+
+ segm_root = Path(cfg.segm_root)
+ exclude_classes = set(cfg.seg_exclude_classes)
+ threshold = cfg.seg_threshold
+ output_path = Path(cfg.seg_filter_output)
- coloredlogs.install(level="INFO", logger=LOGGER, ...)
- LOGGER.info("🚀 Starting segmentation filter (threshold=%.2f)", args.threshold)
- segm_root = Path(args.segm_root)
- results = scan_masks(segm_root, EXCLUDE_CLASSES, args.threshold)
+ LOGGER.info("🚀 Starting segmentation filter (threshold=%.2f)", threshold)
+ results = scan_masks(segm_root, exclude_classes, threshold)
...
- output_path = Path(args.output)
output_path.parent.mkdir(parents=True, exist_ok=True)
out = {
- "threshold": args.threshold,
- "exclude_classes": sorted(EXCLUDE_CLASSES),
+ "threshold": threshold,
+ "exclude_classes": sorted(exclude_classes),
...
}
...
```
### Файл: `src/training/train.py` (legacy v2)
Если v2 оставляем — снять `@gin.configurable` с `TrainConfig` и переписать на 5 конфигов аналогично `train_gtauav.py`.
Если удаляем — файл уходит вместе с веткой.
> **Я бы советовал удалить v2** — он создаёт двойную работу при каждом изменении. Но это **отдельный** разговор, не блокер для текущего шага.
### Файл: `conf/` (старые .gin) — удаляются после миграции
После того как **все 14 пресетов в `presets/`** созданы и проверены — старая директория `conf/` удаляется целиком:
```diff
- conf/balanced.gin
- conf/baseline_no_text.gin
- conf/text_heavy.gin
- conf/gtauav_balanced.gin
- conf/gtauav_baseline.gin
- conf/gtauav_balanced_asym.gin
- conf/gtauav_baseline_asym.gin
- conf/gtauav_balanced_stripnet.gin
- conf/gtauav_balanced_stripnet_unfrozen.gin
- conf/gtauav_baseline_stripnet.gin
- conf/gtauav_baseline_stripnet_unfrozen.gin
- conf/gtauav_text_heavy.gin
- conf/gtauav_image_heavy.gin
- conf/gtauav_balanced_sofia.gin # из локальной копии
- conf/gtauav_balanced_sofia_v1.gin # из локальной копии
- conf/gtauav_baseline_sofia.gin # из локальной копии
- conf/gtauav_baseline_sofia_v1.gin # из локальной копии
```
---
## Часть I — Что НЕ делаем на этом шаге
Чтобы шаг был обозримым, **не трогаем**:
- ❌ Декомпозиция `train()` (1296 строк) на `Trainer.run()` + методы — **отдельный шаг**
- ❌ Перенос `_evaluate` в `src/eval/evaluator.py`**отдельный шаг**
- ❌ Перенос `CSVLogger` в `src/training/csv_logger.py`**отдельный шаг**
- ❌ Замена `@torch.no_grad()` на `@torch.inference_mode()`**отдельный шаг (косметика)**
-`_atomic_save` cleanup на ошибке — **отдельный шаг (косметика)**
- ❌ Логика sofia_v1/v71 моделей и их `dataclass`-конфиги — **внутренний слой не трогаем**
После этого шага получаем: **гин-конфиг разделён, никаких `@gin.configurable + @dataclass`, никаких `@gin.configurable` на бизнес-классах, никаких argparse, плоские пресеты с дублированием полных биндингов вместо `include`**. Структура `train()` остаётся прежней (одна большая функция), но получает 6 объектов конфига вместо одного `cfg`.
---
## Часть J — Открытые вопросы для уточнения
1. **SOFIAv71 fields** — точный список полей `SOFIAv71Config` (`@dataclass` в `src/models/sofia_v71/config.py`) для построения `SOFIAv71ModelsConfig`. Я выписал поля по README, но в `config.py` могут быть ещё (mamba `headdim`, `d_state`, `kernel_size`, `num_bins` для квантизации). Нужно открыть файл и составить полный список.
2. **`gtauav_*_sofia*.gin` локальные пресеты** — содержимое 4 sofia-гинов из локальной копии (на скриншоте видны, в репо ещё нет). Нужны как эталон для воспроизведения дефолтов один-в-один.
3. **`use_mutex_sampler`** в `TrainingConfig` — текущий код помечает поле как «legacy alias». Сохранить ли его на этапе разделения, или сразу убрать (тогда `effective_sampler_type` берётся напрямую из `sampler_type`)?
4. **Legacy v2** (`train.py`, `visloc_with_captions.py`, `conf/balanced.gin`/`baseline_no_text.gin`/`text_heavy.gin`) — удаляем или приводим к новому стилю? Я склоняюсь к удалению. Если оставлять — добавляется ещё 3 пресета и переписывание `TrainConfig` на 5 конфигов.
5. **Расположение `presets/`** — в корне проекта или внутри `in/` (как `in/presets/`)? У вас сейчас лежит в корне (`presets/gtauav_balanced/` рядом с `src/`). Оставляем там же.
После ответов на эти 5 вопросов план становится готов к реализации без новых развилок.

1
src/conf/__init__.py Normal file
View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@