🎯 Дуэль 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=48 | elo | ci95 | Пар | SPRT |
|---|---|---|---|---|---|---|
x86 (Standard_D2as_v5) | 586 | 54.52% | +31.5 | ±3.31 пп | 293 | AcceptH1, llr 3.060 |
ARM (Neoverse-N1, D2ps_v5) | 732 | 53.69% | +25.7 | ±2.88 пп | 366 | AcceptH1, llr 2.957 |
| объединённо | 1318 | 54.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):
| x86 | ARM | |
|---|---|---|
| sd пары | 0.2894 | 0.2813 |
| sd партии | 0.4979 | 0.4983 |
| снижение дисперсии парами | 1.216× | 1.253× |
| партий на 3 пп (парно / непарно) | 716 / 1059 | 676 / 1060 |
| партий на 2 пп (парно / непарно) | 1610 / 2382 | 1520 / 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=all | elo | ci95 | SPRT | Справочно: K=48 vs K=24 на том же контроле |
|---|---|---|---|---|---|---|
| 2+1 | 700 | 52.86% | +19.9 | ±2.90 пп | Continue (упёрлись в кап 350 пар) | 54.06% (1318 партий, пул, AcceptH1) |
| 5+5 | 628 | 54.46% | +31.1 | ±3.33 пп | AcceptH1 (314 пар) | 52.86%, Continue |
| 10+10 | 316 | 58.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 мс — отдельные позиции требуют почти минуты на ход. Совместимо с оценкой из пробника (p90legalTurns= 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, но без его взрывного хвоста задержки. Это усиливает, а не ослабляет кейс за обученный пре-ранкер: грубая сила платит именно тем хвостом, которого модель избежала бы.