Last active 1 month ago

changes.md Raw

Ниже единый план: твой разбор репозитория + roadmap коллеги. Главное изменение приоритетов: human-CV больше не гейт. Коллега проверил тремя сабмитами: LB ≈ LLM-прокси − 0.19, а human 0.74 не ранжирует кандидатов. Поэтому fold 4 / hard-soup / категорийные веса на human-OOF уходят вниз.

Цифры коллеги, от которых пляшем: LB 0.43059 (E5) → 0.43169 (soup+V2+категории). Их бленд твоего E5 с их CatBoost — 0.43456.


Правило отбора экспериментов

Каждый кандидат меряется так, в этом порядке:

  1. LLM-прокси (фиксированный сэмпл, не в обучении) — основное.
  2. Human disjoint folds — вторично, «не уехала ли модель с ручных меток».
  3. Сабмит на LB — только если прокси вырос и архив/время влезают.

Не принимать решение по human-OOF, если прокси не вырос. Коллега: прирост на прокси переносится в LB с коэффициентом ~0.4; калибровка LB ≈ прокси − 0.19.

Не делать (уже проверено у них): bi-encoder косинусы, AutoGluon, hard-mine по human-OOF, категорийные веса по human-CV, масштабирование таблички 2M→8M LLM.


Фаза 0. Заморозить якоря

Пока это не сделано — не двигать файлы и не переобучать.

  1. Зафиксировать текущий боевой набор:

    • E5 чекпоинт (model.safetensors)
    • compact *.joblib + версия sklearn
    • ensemble-candidate.json
    • SHA zip (21767a7d… в README) — сверить Get-FileHash
  2. Снять два референса, не один:

# A. Human (регрессия runtime, не оценка качества)
python -u run_ensemble_candidate.py \
  --items_path data/items_human.parquet \
  --matches_path data/matches.parquet \
  --output_path submit-reference.csv \
  --model-path <E5_DIR> \
  --tabular-path <COMPACT.joblib> \
  --config-path ensemble-candidate.json

python -u verify_candidate_output.py \
  --matches-path data/matches.parquet \
  --prediction-path submit-reference.csv \
  --items-path data/items_human.parquet
  1. Зафиксировать LLM-прокси (действие №1 коллеги). У тебя уже есть зачатки: prepare_proxy_data.py, new_ideas/X_proxy.npy, new_ideas/proxy_meta.parquet. Канон:
  • один неизменяемый сэмпл (~300k LLM-пар), random_state=42
  • бинаризация target >= 0.5
  • macro PR-AUC по категориям
  • эти пары исключены из любого дальнейшего обучения
  • скрипт оценки: взять/адаптировать их scripts/eval_llm_proxy.py

Записать в таблицу три точки коллеги как sanity калибровки:

Модель Прокси LB Прокси − LB
(их слабая) 0.502 0.3097 0.192
твой E5 0.613 0.4306 0.182
E5 + их CatBoost 0.624 0.4346 0.189

Готово, когда: есть submit-reference.csv и одно число прокси для текущего zip. Дальше любой эксперимент пишет строку в эту таблицу.


Фаза 1. Один runtime (закрывает дыру у обоих разборов)

Коллега, §5: в боевом run.py сейчас чистый CE, ансамбль только в run_ensemble_candidate.py.

1.1. run.py = ансамбль

  • Логику run_ensemble_candidate.py перенести в run.py.
  • Алиасы на 3 строки: from run import main.
  • Падать, если в конфиге нет neural_weight (иначе снова уедет чистый CE).

Проверка: run.py даёт byte-identical submit-reference.csv.

1.2. Один ensemble.json

  • Содержимое ensemble-candidate.json → ensemble.json.
  • Старый файл удалить после смены ссылок.

1.3. Один упаковщик

  • Канон: make_ensemble_submission.py.
  • make_submission.py убрать или в experiments/ с пометкой DEAD (пакует model.joblib + GPU run.py).
  • make_gpu_submission.py — только E5-only fallback.
python make_ensemble_submission.py \
  --model-dir <E5_DIR> \
  --tabular-model <COMPACT.joblib> \
  --config-path ensemble.json \
  --run-path run.py \
  --output-path submission.zip

1.4. README

Один блок Current submission. Human-CV и refit 0.785 — в Historical / sanity, не как оценка LB. Добавить строку «gate = LLM-прокси».

Готово, когда: по README собирается тот же zip, что на LB 0.43169.


Фаза 2. Перемерить всё, что уже лежит на диске (H100, без нового обучения)

Это пункт 1 недели коллеги: «до +0.01–0.02, если пик не в конце файнтюна». Делать до fold 4 и до нового претрейна.

На том же прокси прогнать:

  1. Чекпоинт этапа 1: models/multilingual-e5-llm (до human-finetune).
  2. Финальный human-finetune.
  3. Hard-soup 0.10 (текущий LB).
  4. Промежуточные checkpoint-* этапа 2, если сохранились.
  5. Супы этап1 × этап2: tuned_weight ∈ {0.3, 0.5, 0.7, 1.0} через уже существующий merge_transformer_weights.py.
python merge_transformer_weights.py \
  --base-model models/multilingual-e5-llm \
  --tuned-model <HUMAN_OR_SOUP> \
  --tuned-weight 0.5 \
  --output-dir models/soup-stage1-2-w05

Потом тот же eval_llm_proxy на каждый чекпоинт.

Решение:

  • если этап 1 или ранний этап 2 выше на прокси, чем текущий soup — это новый runtime без обучения;
  • human-метрика при этом может упасть — это ожидаемо (у них CatBoost: прокси 0.513 → 0.484 после human-finetune);
  • в архив класть победителя по прокси, если влезает в 780 с.

Готово, когда: таблица чекпоинт → прокси. Выбран новый baseline. Human-fold 4 не блокирует этот выбор.


Фаза 3. Освободить бюджет инференса (разблокирует e5-base)

Коллега: из 469 с полного прогона 281 с — однопоточный V2, ~190 с — CE. Лимит ~780 с.

3.1. Распараллелить V2

В build_features_v2 (и при необходимости base build_features): чанки пар, multiprocessing, ~20 CPU. Ожидание: 281 с → 25–40 с, запас ~4 минуты.

Не менять численные значения фич (битовая идентичность на 1k пар).

3.2. Двойная буферизация CE

В predict_cross_encoder: токенизировать батч N+1, пока GPU считает N. Ожидание +20–30% к CE-стадии, без смены скоров.

3.3. Symmetric TTA — только после 3.1

Скоринг (A,B) и (B,A), усреднение. Для small это ещё ~190 с. Влезает только если V2 уже быстрый. Мерить на прокси, не на human.

3.4. max_length

Доля пар, обрезанных при 160. Если заметная — 192/224 на small (бюджет уже есть). На прокси.

Готово, когда: полный прогон в образе, V2 < 50 с, CE не медленнее, CSV совпадает с якорем (кроме TTA/max_len — те отдельные ветки).


Фаза 4. Баги фич, которые коллега и разбор оба указали

Делать вместе с 3.1, пока трогаешь features_v2.py. Меняет схему → ретрейн таблички.

  1. battery в MEASUREMENT_KINDS — сейчас mah/мач парсятся и выкидываются из per-kind match/conflict.
  2. Числовой допуск ±5% (их num_val_close): 990 г vs 1 кг у тебя conflict. Добавить в V2, не ломая нормализацию единиц.
  3. Пустые атрибуты: оба пустые → similarity 1.0, как Jaccard (твой разбор).
  4. Категория пары только по id1: на прокси померить альтернативу (id1, или id1 если равны иначе unknown). Менять runtime только если прокси вырос.
  5. TF-IDF: один check fit-on-train vs transductive на прокси. В run.py оставить transductive.

Их 32 фичи (src/features.py) + твои слоты/измерения — отдельный дешёвый стек, не в этом же коммите.

После изменения схемы: новый кэш, новый joblib, новый прокси-скор. Старый compact несовместим — это ок.


Фаза 5. Переобучение под тестовое распределение (H100, ночи)

Только после Фазы 2 (знаешь, какой чекпоинт вообще лучший) и Фазы 3 (есть бюджет на base).

Все варианты мерять на прокси. Human — в лог, не в решение.

5.1. Мягкий этап 2, не 4 эпохи human

Подозрение коллеги: 4 эпохи, lr 1e-5, batch 1024 утащили E5 с LLM-теста.

Порядок:

  1. Если Фаза 2 уже нашла лучший ранний чекпоинт — стартовать от него.
  2. Смешанный этап 2: human + 0.5–1M LLM-пар (не из прокси-сэмпла), human с большим весом, 1–2 эпохи, не 4.
  3. Не делать hard-mine по human-OOF (у них перенос в LB ≈ 0).

5.2. Больше претрейна этапа 1

Сейчас 1 эпоха на 11.2M. На H100 эпоха ~1.5–2 ч (batch 2048, seq 160).
2–3 эпохи, cosine → 0. На мягких метках E5-small скорее недообучен.

5.3. e5-base / USER-base

После ускорения V2:

  • intfloat/multilingual-e5-base
  • deepvk/USER-base (русский, попробовать оба)

Тот же пайплайн: претрейн LLM → мягкий этап 2.
Инференс base ~3× small → ~570 с CE + 40 с V2 ≈ 650 с < 780 с.
Претрейн base: 4–6 ч/эпоха, ночь.

Large не трогать, пока не режешь max_len или не дистиллируешь.

Ожидание коллеги: +0.02–0.04 LB на base + быстрые фичи.


Фаза 6. Ансамбль на LLM-распределении

Текущая V2-таблица учится только на human и тянет бленд к human. Их факт: CatBoost на 2M LLM (CE по soft labels) + human вес 5 → прокси 0.51 vs 0.41 human-only. Твой E5 + их CatBoost, ранг-бленд 0.8/0.2 → +0.004 LB.

6.1. Переобучить табличку на LLM-миксе

Варианты, дешевле → дороже:

  1. Забрать их artifacts/catboost_v5_llm8m.cbm + src/features.py и смешать с твоим E5 (быстрый потолок 0.43456).
  2. Обучить свой HGB/CatBoost на LLM-миксе (scripts/train_gbdt_llm.py у них) на твоих V2 (+ допуск ±5%, battery).
  3. Стек: их 32 фичи ∪ твои слоты/измерения.

Не гнать 2M→8M: у них +0.003 прокси / +0.001 LB, насыщение.

6.2. Категорийные веса — только на прокси

Твой tune_category_blend.py оставить, данные сменить на прокси. Human cross-fit не использовать как accept-gate.

6.3. Второй CE

Когда будет base или USER-base: бленд small+base (+ LLM-табличка третьим). Веса по прокси, tune_three_blend.py уже есть. Два разных CE обычно дают больше, чем ещё одна табличка.

Готово, когда: прокси ансамбля > прокси лучшего одиночного CE, полный прогон < 780 с, zip < 4.7 GB.


Фаза 7. Инженерный долг (не блокирует H100)

Делать в паузах между ночными трейнами.

7.1. Дубли

  • macro_average_precision только в validation.py
  • product_text только в solution/transformer.py (+ тест vs DuckDB)
  • set-helpers в одном модуле
  • category_ranks только в tune_blend.py
  • канон проверки CSV: verify_candidate_output.py

7.2. Тесты без GPU и без data/

  • фолды: overlap == 0
  • фичи: swap-invariance, длина схемы, battery, finite
  • verify: NaN / чужой порядок падают
  • zip: namelist + есть neural_weight

Убрать хак experiments_next из test_features_v2.py.

7.3. Гигиена train_cross_encoder.py

  • worker_init_fn для swap (коллега и разбор: глобальный numpy RNG коррелирует воркеры)
  • drop_last=False на коротком human/микс этапе; на 11M pretrain можно оставить True
  • раз в N шагов — прокси на 2–4k пар, не ждать конца
  • в train_gpu_pipeline.sh дефолт .venv-autogluon → .venv-gpu

7.4. Раскладка репо — последней

Только когда 1.x и тесты зелёные:

solution/ runtime, training/, eval/, packaging/, experiments/ (AutoGluon, Qwen-as-runtime, proxy leftovers).


Фаза 8. Тяжёлое, если 5.x выдохлись

Qwen как runtime не возвращать (0.53 на human-fold, медленный). H100 между экспериментами:

  1. Переразметить серую зону 11.2M (метка 0.3–0.7 или сильный разъезд E5 vs табличка) через Qwen3-8B/32B-Instruct (Apache 2.0). Код почти готов: build_teacher_distillation_data.py.
  2. Дистилляция: teacher-ансамбль (E5-base + CatBoost + …) → 11M мягких меток → студент small/base. В zip одна быстрая модель.

Ожидание неизвестно, 2–3 дня H100. Не начинать, пока не закрыты Фазы 2 и 5.1–5.2.

Human fold 4: посчитать как вторичную метрику, когда будет простой. Не ждать его, чтобы выбрать чекпоинт или веса бленда.

Честные baselines в README: leaky 0.59 не ставить рядом с disjoint 0.73 и не ставить их рядом с LB 0.43 без подписи protocol.


Календарь на неделю (сшивка двух планов)

Слот Что GPU Зачем
День 1 утро Фаза 0 + 1 (якоря, run.py, zip, README) нет не отправить не тот архив
День 1 день Фаза 2: прокси на этапе 1 / этапе 2 / супах 0.3–1.0 мало самый дешёвый LB
День 1–2 Фаза 3.1–3.2 + Фаза 4 (V2 parallel, battery, ±5%) нет бюджет на base
Ночь 1–2 Фаза 5.2: претрейн 2–3 эпохи small H100 недообучен на LLM
День 2 Фаза 5.1: мягкий этап 2 (микс), выбор по прокси H100 не уехать с теста
День 2–3 Фаза 6.1–6.2: LLM-табличка + веса на прокси CPU их +0.004 уже доказан
Ночь 3 Фаза 5.3: e5-base и/или USER-base H100 крупный прирост
День 4 Фаза 6.3: бленд small+base+табличка по прокси мало
Паузы Фаза 7 тесты/дубли нет
Если плато Фаза 8 переразметка H100

Не параллелить перенос папок (7.4) с Фазой 1–2.
Не менять фичи и entry point в одном коммите.
Не запускать 4 эпохи human и hard-mine по human-OOF — это уже опровергнутый путь.


Definition of Done

  1. python run.py локально = содержимое submission.zip.
  2. Один ensemble.json с весами, один упаковщик.
  3. Есть неизменяемый LLM-прокси; все решения в одной таблице прокси / (опц.) LB.
  4. Выбран чекпоинт по прокси, не по концу 4-й human-эпохи.
  5. V2 в контейнере < 50 с; полный прогон < 780 с; zip < 4.7 GB.
  6. Battery + ±5% в схеме; табличка, если есть, обучена на LLM-миксе.
  7. Категорийные веса, если есть, подобраны на прокси.
  8. Human-CV в README помечен как вторичный; leaky 0.59 не сравнивается с LB.
  9. pytest -q зелёный без данных соревнования.
  10. В бэклоге нет bi-encoder / AutoGluon / human hard-mine / Qwen-runtime.

Если переключишься в Agent mode, логичный старт — Фаза 0+1 (не ломает обучение), сразу за ней скрипт прокси-оценки существующих чекпоинтов: это единственный пункт, который коллега оценивает как возможный +0.01–0.02 без новой ночи H100.