# Диагностика: коллапс recall на эпохе 1 — DSS + MoCo queue **Дата:** 2026-04-25 **Конфиг:** `conf/gtauav_balanced.gin` (gate=0.7, with text) **Запуск:** `python -m src.training.train_gtauav --config conf/gtauav_balanced.gin --filter-meta meta/seg_filter.json` --- ## 1. Симптомы из лога ### Метрики по эпохам | Эпоха | Режим сэмплера | LR | train loss | train R@1 | val R@1 | val AP | eval loss | |---|---|---|---|---|---|---|---| | **ep0** | mutex-only (warmup) | 5.00e-05 | 4.2239 | **0.0742** | 0.0758 | 0.1511 | 2.0783 | | **ep1** | DSS активирован, MoCo queue полная | 1.00e-04 | 4.4218 ↑ | **0.0120** ↓6× | 0.0107 | 0.0328 | 2.0786 | ### Ключевые наблюдения - **Recall обвалился ровно на эпохе включения DSS** (warmup=1, поэтому DSS стартует на ep1) - **Train loss выросла** (4.22 → 4.42), хотя должна падать - **Eval loss практически стоит** (2.0783 → 2.0786) — модель не учится на чистом батч-сигнале - **Разрыв train/eval loss > 2×** — большая часть train loss идёт от MoCo+hard-mining компонент, не от in-batch contrast - Gate stable: `gate_q=0.70`, `gate_g=0.70` (текст не вытаскивает дополнительный градиент) - `tau=0.07` (clamped, learnable, но почти не двигается → loss не учит температуру) ### Сбой при сохранении чекпойнта ``` RuntimeError: [enforce fail at inline_container.cc:668] . unexpected pos 289048128 vs 289048016 ``` **Причина:** диск переполнен (ENOSPC). Подтверждено независимо: `mkdir` в session-env тоже падает с ENOSPC. Это инфраструктурный баг, не алгоритмический — но он не даёт сохранить состояние и продолжить. --- ## 2. Почему DSS «не работает» на этом этапе ### 2.1. Mutex-constraint спасает от false negatives только формально Mutex исключает дронов с пересекающимися `sat_candidates`. Но DSS специально пакует **визуально похожих** дронов — у которых sat-кандидаты могут быть *разными tiles*, но при этом сцены практически идентичные: - один и тот же район Лос-Сантоса - та же высота полёта - та же time-of-day, та же погода (синтетика GTA-V) Для холодного энкодера такие «negatives» неотличимы от позитива → softmax пытается развести то, что развести нечем → градиенты шумные и противоречивые. ### 2.2. Re-embed раз в эпоху — слишком редко в начале обучения - На ep1 LR удваивается (5e-5 → 1e-4 после warmup) - Encoder за эпоху смещается сильно (MONA-адаптеры учатся быстро) - Батчи внутри эпохи собираются по embeddings, посчитанным **до** этих обновлений - То есть «similarity» в DSS — это similarity **вчерашнего** энкодера - Re-embed на ep1 занял 313s, но между двумя re-embed encoder успевает измениться существенно ### 2.3. Эпохи 0 на mutex-only недостаточно как warmup - К моменту запуска DSS R@1=0.074 на полной gallery (~2684 уникальных тайлов) - Random baseline ≈ 1/2684 ≈ 0.0004 - 0.074 — это «модель чуть-чуть оторвалась от случая», но качество embeddings ещё недостаточно - DSS усиливает шум, а не сигнал: «визуально похожих» определяет *шумный* энкодер --- ## 3. Почему MoCo queue делает хуже ### 3.1. Representation drift в queue - Queue хранит **4096 embeddings**, посчитанных в разные моменты времени разными версиями encoder - На ep0–1, когда MONA-адаптеры учатся быстро, разница между «свежим» и «3 шага назад» embedding'ом — заметная - Эти устаревшие негативы дают сигнал в направлении, в котором энкодер уже не находится - Без momentum encoder (как в оригинальном MoCo) drift ничем не сглажен ### 3.2. `hard_mining_k=512` амплифицирует ошибку queue - Из 4096 берутся 512 «самых трудных» - «Трудность» меряется в **текущем** feature space - Сами вектора лежали там в **старом** feature space - На холодном энкодере это эквивалентно «учиться отличать себя-позитива от себя-вчерашнего» - Loss растёт, recall падает ### 3.3. Тройная hard-negative композиция → collapse-режим Все три механизма независимо подсовывают «трудные» негативы: | Механизм | Источник трудности | |---|---| | DSS | визуально близкие в батче | | Mutex sampler | in-batch contrast после фильтра | | `hard_mining_k=512` | top-K из MoCo queue | На warm encoder это polish; на cold encoder это **too-hard negatives problem** (известная failure mode у contrastive learning, Robinson et al., 2021): - Модель не получает «лёгких» примеров, на которых формируется базовое embedding-пространство - Градиенты толкают её в произвольных направлениях - Embedding space коллапсирует или размывается --- ## 4. Подтверждения из истории коммитов ``` 8f8cbb1 Diagnostic baseline v2: also disable MoCo queue c25bd64 Diagnostic baseline: disable DSS + hard mining, fresh output dir 9a7fbff Fix plot_combined: fallback from 'total' to 'train_loss' 70f1617 Fix autograd in-place error: move memory-bank enqueue after backward 8197ab2 Fix training loop: only pass positive_weights to WeightedInfoNCELoss ``` Подозрение на DSS+MoCo уже было — текущие числа подтверждают его эмпирически. --- ## 5. Рекомендации ### 5.1. Cold-start curriculum (приоритет) 1. **Несколько эпох mutex-only без MoCo и без hard_mining** до R@1 хотя бы ~0.2 на train. - Конкретно: `sampler_type="mutex"`, `loss.use_memory_bank=False`, `loss.hard_mining_k=0`. 2. Затем **включить MoCo queue с warm-up**: либо momentum encoder, либо queue pre-fill 1 эпоху со `stop_grad` на queue updates. 3. **DSS включать только после** того, как embeddings стали discriminative (R@1 на train > 0.2). Иначе «similar» = «random». 4. **`hard_mining_k` стартует с 0** и поднимается curriculum-схемой (например, 0 → 64 → 256 → 512 по эпохам). ### 5.2. Изменения по DSS - Сократить интервал re-embed (раз в N шагов, не раз в эпоху) — минимум первые 2-3 эпохи - Или временно фиксировать кеш embeddings из чистого baseline (без DSS) и использовать его как «референсный» для сэмплинга ### 5.3. Изменения по MoCo - Добавить momentum encoder (EMA на ключи, как в оригинальном MoCo) — это решает drift - Либо очищать queue на каждой эпохе (теряем эффект, но избегаем drift) - Размер queue 4096 при batch 64 → 64 батча в очереди = слишком долгая история для холодного энкодера ### 5.4. Инфраструктура - **Освободить диск** (ENOSPC блокирует чекпойнты и часть утилит) - Возможно, перенести `out/gtauav/` на другой диск или почистить старые runs - Добавить pre-flight disk check перед `torch.save` --- ## 6. План эксперимента для подтверждения | Run | Sampler | MoCo queue | hard_mining_k | Цель | |---|---|---|---|---| | **A** (clean baseline) | mutex | off | 0 | Подтвердить, что без DSS/MoCo recall растёт нормально | | **B** (only MoCo) | mutex | on | 0 | Изолировать вклад queue | | **C** (only hard_mining) | mutex | on | 512 | Изолировать вклад top-K mining | | **D** (only DSS) | dss | off | 0 | Изолировать вклад DSS | | **E** (full, current) | dss | on | 512 | Reproduce coллапс | Decision rule: - Если A учится нормально (R@1 растёт монотонно), а E коллапсирует — это decisive ablation - Если B/C/D по отдельности тоже коллапсируют — проблема в каждом компоненте - Если только E коллапсирует — проблема в композиции --- ## 7. Альтернативная гипотеза (менее вероятная) LR=1e-4 на проекциях после warmup может быть слишком большим для shared DINOv3 + MONA с малым trainable %. Симптомы похожи (резкий обвал на эпохе с полным LR), но не объясняют, почему mutex-only baseline (commit `c25bd64`) учится нормально без изменения LR. Так что это вторичный фактор. --- ## 8. Резюме **Главный диагноз:** связка `DSS + MoCo queue + hard_mining_k=512` создаёт **too-hard negatives problem** на холодном энкодере. Каждый компонент по отдельности рассчитан на warm encoder; их композиция на ep1 (когда R@1 ещё ~0.07) делает задачу нерешаемой и приводит к коллапсу embedding space. **Решение:** curriculum — сначала mutex+CE без queue/mining до R@1≈0.2, потом постепенно включать остальные механизмы. **Блокер:** диск переполнен (ENOSPC), без освобождения места дальнейшее обучение и сохранение чекпойнтов невозможно.