🎯 Дуэль candidateLimit: 24, 48 и без ограничения

О чём это

Одна модель играет против себя, отличаясь только числом корневых кандидатов, которые поиску разрешено развернуть. Первый результат отменил вывод, на котором держался выбор candidateLimit=24 у всех боевых ботов: насыщения на K=24 нет, K=48 сильнее на +28 elo при контроле 2+1 (04–05.08.2026). Второй, более широкий замер (05–07.08.2026, K=all против K=24 на 2+1/5+5/10+10) показал, что и «потолок» не константа — эффект разворачивается по контролю и растёт до +58 elo на 10+10, том самом, что уже в проде. Инструмент OnnxWidthDuelRunner (engine#562), Azure.

Связанные: Результаты усиления модели и ростер oracle-ботов · Раннбук Azure-бокса (onnx3-k48 + hunter-book) · Каталог вариантов усиления бота · 2-ply Expectimax поверх value-модели · Ранжирование против регрессии · Арена


Результат

Модель rich_distilled_l100 (веса oracle-3), фичи rich, контроль 2+1 (120 с + 1 с), сид 42, SPRT elo0=0, elo1=20, alpha=beta=0.05, кап 430 пар.

МашинаПартийСчёт K=48eloci95ПарSPRT
x86 (Standard_D2as_v5)58654.52%+31.5±3.31 пп293AcceptH1, llr 3.060
ARM (Neoverse-N1, D2ps_v5)73253.69%+25.7±2.88 пп366AcceptH1, llr 2.957
объединённо131854.06%+28.3±2.18 пп → [51.9, 56.2]659—

Флаг-фоллов 0/0 на обеих машинах. Задержки хода: p50 2452/2607 мс, p95 5502/5505, p99 6069/6050, max 8374/8369.

Ширина конвертируется в победы

Интервал не содержит 50% ни на одной машине по отдельности, ни в объединении. Расширение перебора с 24 до 48 кандидатов — реальный прирост силы, а не шум.


Что именно это исправляет

Поправка к ростеру

Там записано: «candidateLimit sweep — K=8→16→24→32 = 58.1%→62.9%→**66.7%**→66.9%, насыщение на K=24». Насыщение не установлено, и это видно из самих чисел: разница K=24 против K=32 равна 0.2 пп при прогонах на 1600 партий, которые разрешают около 2 пп. Разница лежала на порядок ниже шума.

Тот же дефект и в dicechess-house-bots/README.md: «higher = stronger up to ~24».

Дефект был не только в размере выборки, но и в схеме опыта. Свип мерил каждый K против фиксированного слабого соперника (aggressive), а win-rate против слабого соперника сжимается у потолка: на 66% каждый следующий процент требует непропорционально большего прироста силы. Поэтому свип мог показать плато там, где сила продолжала расти.

Дуэль «модель против себя, отличие в одном параметре» этой болезни не имеет: соперник ровно такой же силы, поэтому чувствительность максимальна в районе 50%. Плюс зеркальные пары дают снижение дисперсии 1.22–1.25× (см. PairVariance, engine#508).

Как выглядело противоречие до замера

Пробник PreRankRecallProbeMain (300 позиций) показал: обрезание на K=24 меняет решение поиска в 64.4% затронутых позиций, и в 44.6% скрытый ход реально лучше. Одновременно кривая по K «выполаживалась». Оба факта могли сосуществовать только если ценность не конвертируется в победы — либо если кривая измерена слишком грубо. Верным оказалось второе.


Три оговорки, без которых число врёт

Магнитуда завышена правилом останова

SPRT оборвал оба прогона на границе H1. alpha=beta=0.05 контролируют вероятность ошибочного решения, но не смещение точечной оценки: раннее прекращение систематически сдвигает наблюдённый счёт от нуля. Существование и знак эффекта надёжны; «+28 elo» — оценка сверху. Для чистой величины нужен прогон с фиксированным n без досрочного останова.

Часы связывают через усечение глубины, а не через флаг-фоллы

Флаг-фоллов ноль и при 3+2, и при 2+1. Связывание видно иначе: p95 задержки просел с 8570 мс (калибровка 3+2) до 5502 мс при 2+1, то есть бюджет режет поиск. Широкая сторона победила, платя этим усечением, — это сильнее, чем победа при незанятых часах.

Побочный вывод про калибровку: при 3+2 обе машины выдали побитово одинаковые партии (60.0%, 12/8/0), потому что бюджет 7.9 с не мешал медиане хода 3.0 с. Сеяная арена просто воспроизвела один и тот же поток костей. Если контроль не связывает, «дуэль под часами» измеряет безчасовое сравнение под чужим именем.

Зависимости от класса ядра не обнаружено — но только для этой пары

Машины разошлись на 0.83 пп при интервалах ±2.9–3.3 пп, вердикт одинаковый, профили задержек совпали почти побитово. Значит для пары «Neoverse-N1 против x86 того же класса» дважды мерить не нужно. Про Ampere A1 (где та же дуэль когда-то дала +82 elo против +28 на быстрых ядрах) это ничего не говорит — A1 в этом замере не участвовал.


Стоимость и разрешающая способность

Из блока resolution (инструментарий #508):

x86ARM
sd пары0.28940.2813
sd партии0.49790.4983
снижение дисперсии парами1.216×1.253×
партий на 3 пп (парно / непарно)716 / 1059676 / 1060
партий на 2 пп (парно / непарно)1610 / 23821520 / 2385

Пентаномиальная сетка снова почти выродилась: n1=0, n3=1 (x86) и n1=1, n3=1 (ARM) — партии в Dice Chess решительные, ничьих почти нет, поэтому пара падает в 0 / ½ / 1. Весь выигрыш парности — это корреляция через общий поток костей, а не более тонкая сетка.

Время: x86 7 ч 45 мин на 586 партий, ARM 9 ч 27 мин на 732.


Следствия (первичные, по замеру K=48 vs K=24)

1. Поднять ORACLE_CANDIDATE_LIMIT до 48 — правка одной переменной. В dicechess-house-bots/docker-compose.yaml это ${ORACLE3_CANDIDATE_LIMIT:-24}; ни нового кода, ни нового репозитория не требуется. ✅ Сделано — бот azure/onnx3-k48 развёрнут и на лестнице, см. раннбук.

Но не включать без замера на боевом хосте

Дуэль прогнана на облачных ядрах (2 vCPU, выделенные). House-боты живут на rpi4 — 4 слабых ARM-ядра. K=48 удваивает работу на ход, и на медленном железе плата временем может перевернуть знак: там, где на облаке остаётся запас до дедлайна, на rpi4 начнутся усечения глубины или флаг-фоллы. Мерить тем же инструментом на той машине, где бот будет играть.

2. Кривую по K стоит перемерить нормальным прибором. ✅ Сделано ниже — вместо промежуточных K=96/K=192 прогнали сразу K=all (без обрезания) на всех трёх контролях.


Расширение 05–07.08.2026: K=all vs K=24, и разворот эффекта по контролю

Тот же инструмент, та же модель и сид, три контроля: 2+1, 5+5, 10+10. x86-машина, 45 ч 37 мин суммарно.

КонтрольПартийСчёт K=alleloci95SPRTСправочно: K=48 vs K=24 на том же контроле
2+170052.86%+19.9±2.90 ппContinue (упёрлись в кап 350 пар)54.06% (1318 партий, пул, AcceptH1)
5+562854.46%+31.1±3.33 ппAcceptH1 (314 пар)52.86%, Continue
10+1031658.23%+57.7±4.97 ппAcceptH1 (158 пар)52.86%, Continue

Направление эффекта разворачивается — и K=48 с K=all в противофазе

У K=48 эффект был максимален на быстром контроле (2+1: 54.06%) и сжимался на медленных (52.86% на обоих). У K=all — ровно наоборот: слабее на быстром (52.86%, даже не разрешилось за 350 пар) и монотонно растёт на медленных, до +57.7 elo на 10+10.

Правдоподобный механизм: у K=all есть фиксированные накладные расходы — генерация и батч-пре-ранкинг всех легальных ходов, не только топ-K, — и они сами по себе недёшевы: p50 задержки на 2+1 составил 4885 мс против 2452–2607 мс у K=48 на том же контроле. На коротком бюджете эти накладные съедают эффективное время на сам chance-node поиск раньше, чем широта успевает отыграть. На длинном контроле накладные становятся относительно дешёвыми, и K=all наконец получает время использовать весь просмотренный набор.

Это не прямое измерение K=all против K=48

Оба прогона — отдельные дуэли против K=24, на одном сиде и контролях, не head-to-head друг с другом. Транзитивное сравнение (58.23% > 52.86% на 10+10) наводит на «K=all вероятно бьёт K=48 на медленном контроле», но это вывод, а не измерение. Прямая дуэль K=all vs K=48 — следующий логичный шаг, если понадобится точная цифра.

Хвост задержки у K=all разрывной

На 10+10 (600s+10s): p50 6720 мс, но p95 = 35346 мс, max = 58256 мс — отдельные позиции требуют почти минуты на ход. Совместимо с оценкой из пробника (p90 legalTurns = 1843 → ~96 с при ~52 мс/кандидат). Флаг-фоллов при этом 0/0 на всех 316 партиях — anytime-гарантия поиска устояла, но запас по времени явно на пределе.

Пересмотр прежних выводов

Поправка: «K=all как живой бот не годится» была неверна для того контроля, который уже в проде

Прежний вывод («полная ширина обходится десятками секунд на ход… инструмент генерации меток офлайн, не игрок на лестнице») был оценкой стоимости без часов, а не измерением под ними. 600s+10s в нотации движка — это буквально 10+10, тот самый BOT_SEEK_TIME_CONTROL, что уже стоит дефолтом у oracle-3 и у нового azure/onnx3-k48. На этом контроле K=all не потерял ни одной партии по часам за 316 попыток и обошёл K=24 на измеримую величину. Вывод не «K=all готов к продакшену», а «блокирующей причины из-за часов на 10+10 не подтвердилось» — рассмотреть отдельным экспериментом на боевом железе, с тем же предупреждением про rpi4, что и для K=48.

Потолок для пре-ранкера (ev#14) — не константа ~4 пп, а функция бюджета на ход. Прежний вывод («пре-ранкер не может дать больше ширины, ~4 пп») был верен только для контроля 2+1. На 10+10 сам потолок вырос до ~8 пп (K=all против K=24). Пре-ранкер целится не в фиксированную цифру, а в то же самое, что делает K=all — найти ценные ходы вне топ-48, но без его взрывного хвоста задержки. Это усиливает, а не ослабляет кейс за обученный пре-ранкер: грубая сила платит именно тем хвостом, которого модель избежала бы.