nl-sql / docs /ceiling_anatomy.md
liovina's picture
Deploy NL_SQL HEAD to HF Space (tracked files only)
4e1037f verified
|
Raw
History Blame Contribute Delete
21.2 kB
# Анатомия потолка
Этот документ отвечает на один вопрос: **почему пайплайн стоит там, где стоит, и что именно
мешает ему подняться.** Он состоит в основном из отрицательных результатов — это и есть его
содержание. Все числа здесь пересчитаны из сырых отчётов (`eval/reports/`), а не перенесены
из прошлых редакций; команды воспроизведения — в конце.
Короткий ответ: **из всего, что мы пробовали, работает ровно одна ось — сила генератора.**
Всё остальное — вмешательства в промпт, контекст, отбор примеров, ансамбли, дообучение своей
модели — на этом стеке либо не двигает метрику, либо вредит. А сама метрика гораздо шумнее,
чем выглядит.
---
## 1. Сначала о метрике: половину пути мы мерили внутри шума
Execution Accuracy на BIRD выглядит как объективное число. Это не так.
Jin et al. ([arXiv:2601.08778](https://arxiv.org/abs/2601.08778)) проверили разметку BIRD
Mini-Dev и обнаружили ошибки аннотации примерно у половины вопросов. Их исправленный gold
(вариант `sql_only` — переписан только SQL, вопрос тот же) даёт возможность измерить
собственный шум бенчмарка: прогнать одни и те же предсказания против двух разметок,
отвечающих на **один и тот же вопрос**, и посмотреть, где вердикт меняется.
Для продуктового прогона (codestral, n=200, 199 сопоставимых вопросов):
| | Вопросов | Доля |
|---|---:|---:|
| Верно при **обеих** разметках | 105 | 52.8% |
| Неверно при **обеих** | 61 | 30.7% |
| **Вердикт зависит от разметки** | **33** | **16.6%** |
То есть «61.5%» — одна точка в коридоре **[52.8%, 69.3%]**, ширину которого задаёт
разногласие аннотаторов, а не качество пайплайна.
Практическое следствие оказалось неприятным. Порог «живого рычага», по которому проект
полгода принимал решения, был ±1.5 п.п. (три вопроса) — **в одиннадцать раз тоньше
собственного шума бенчмарка**. Восемь измеренных до этого «паритетов» не были опровергнуты;
они просто **неинформативны**: измерение шло внутри шума.
### Метрика, которой можно пользоваться
Вместо голого EA мы судим по «бесспорной земле» — по вопросам, где разметка не решает исход:
- **чинит** — сколько из **61** вопроса, который бейслайн проваливает при обеих разметках,
претендент решает **при обеих**;
- **ломает** — сколько из **105** вопросов, которые бейслайн берёт при обеих, претендент
теряет **при обеих**;
- **net = чинит − ломает.**
Требование «при обеих» существенно и применяется к претенденту тоже. Более мягкий вариант —
засчитывать починку по одной лишь оригинальной разметке — возвращает в счёт ровно ту
лотерею аннотаций, ради исключения которой метрика и заводилась, и завышает «чинит»
на 3–4 вопроса у большинства моделей.
---
## 2. Единственный живой рычаг — генератор
Все прогоны: одна конфигурация (`config E`, `n=200`, `seed=0`), один продуктовый промпт,
меняется только модель-генератор.
| Генератор | Доступ | EA (оригинальный gold) | чинит /61 | ломает /105 | net |
|---|---|---:|---:|---:|---:|
| `claude-opus-4-8`, effort=max | подписка | **79.5%** | **31** | **1** | **+30** |
| `claude-sonnet-5` | подписка | 71.5% | 22 | 6 | +16 |
| `grok-composer-2.5-fast` | подписка | 70.0% | 23 | 3 | +20 |
| **`mimo-v2.5-free`** | **бесплатно** | **68.5%** | 22 | 5 | **+17** |
| `big-pickle` (тот же MiMo) | бесплатно | 66.0% | 19 | 6 | +13 |
| `nemotron-3-ultra-free` | бесплатно | 63.5% | 20 | 9 | +11 |
| `grok-build` | подписка | 62.5% | — | — | — |
| `deepseek-v4-flash-free` | бесплатно | 62.5% | — | — | — |
| **codestral (бейслайн)** | free API | **61.5%** | — | — | — |
| `north-mini-code-free` | бесплатно | 60.5% | — | — | — |
Три наблюдения важнее самих чисел.
**Разрыв реален и крупнее шума.** Лучший бесплатный генератор чинит 22 из 61 бесспорного
промаха бейслайна, ломая 5 из 105. Это не переливание одного класса ошибок в другой —
это движение.
**Сильному генератору не нужны костыли пайплайна.** Доля вопросов, где сработал repair:
codestral **16/200**, mimo-free 3/200, Sonnet 1/200, Opus **0/200**. Механизм починки
невалидного SQL, на который потрачено заметное время, у сильной модели просто не
вызывается.
**Рост идёт туда, где пайплайн был слабее всего.** По тирам сложности:
codestral 76.1 / 58.6 / **41.2**, mimo-free 77.6 / 66.7 / **55.9**,
Opus 88.1 / 77.8 / **67.6** (simple / moderate / challenging).
---
## 3. На исправленном gold порядок моделей сжимается
Тот же набор предсказаний, судимый по исправленной разметке Arcwise `sql_only` (n=199):
| Генератор | Оригинальный gold | Исправленный gold | Δ |
|---|---:|---:|---:|
| `claude-opus-4-8` max | 79.4% | **73.9%** | **−5.5** |
| `grok-composer-2.5-fast` | 69.8% | 72.9% | +3.0 |
| `mimo-v2.5-free` | 68.3% | 71.9% | +3.5 |
| `big-pickle` | 65.8% | 69.8% | +4.0 |
| `nemotron-3-ultra-free` | 63.8% | 69.8% | +6.0 |
| `claude-sonnet-5` | 71.4% | 68.8% | −2.5 |
| codestral | 61.8% | 60.3% | −1.5 |
(Проценты в этой таблице считаются по 199 сопоставимым вопросам, поэтому на десятые
расходятся с таблицей §2, где знаменатель 200.)
**Модели расходятся в разные стороны, и это не случайность.** Бесплатные модели на
исправленной разметке растут, а те, что лучше всех на оригинальной, — падают. Падение
означает, что модель хорошо ловит конвенции конкретной аннотации: few-shot-примеры взяты
из того же корпуса, и модель, которая их лучше усваивает, лучше отвечает «как размечено»,
а не «как правильно».
Практический вывод: **на исправленном gold вся верхушка помещается в 68.8–73.9%.** Разрыв
между лучшей платной моделью и лучшей бесплатной — **2.0 п.п.** вместо 11.0 п.п., которые
показывает оригинальная разметка. Ось «сильнее модель» жива, но её масштаб зависит от того,
какой разметке верить, и честная оценка масштаба заметно скромнее.
При этом на бесспорной земле (§1) Opus всё равно первый: 31 починенных против 1 сломанного.
Оба факта верны одновременно — он действительно лучший, и он же сильнее всех
подстраивается под оригинальные конвенции.
---
## 4. Что не сработало: шесть литературных рычагов подряд
Все шесть реализованы по опубликованным работам, каждый измерен отдельным прогоном n=200
на лучшем бесплатном генераторе (чистый `mimo-v2.5-free` = 68.5%), каждый спрятан за флагом
и по итогу **выключен**.
| Рычаг | Источник идеи | EA | Δ |
|---|---|---:|---:|
| Value retrieval (поиск значений в БД) | CHESS | 68.0% | −0.5 |
| Микропромпты классов ошибок | — | 67.5% | −1.0 |
| DAIL-отбор few-shot по маскированному вопросу | DAIL-SQL / MCS | 67.0% | −1.5 |
| Целевые описания колонок | — | 65.5% | −3.0 |
| Обогащение вопроса в явную спецификацию | E-SQL | 63.0% | −5.5 |
| Синтетические few-shot под целевую схему | CHASE-SQL | 59.0% | −9.5 |
Ни один не дал плюса. Разброс — от «в пределах шума, но со знаком минус» до −9.5 п.п.
Отдельно показателен последний: техника, у авторов дающая +9.3 п.п., на нашем стеке
обрушила сложный тир до уровня бейслайна.
Раньше тем же способом умерли: декомпозиция запроса (**−6.5**), M-Schema (**−2.0**),
компактный промпт на бейслайне (**−3.5**). Без пересчёта в этой редакции, поэтому без чисел:
self-consistency, сужение схемы, межмодельное голосование двух и трёх моделей,
судья-селектор поверх кандидатов, LLM-верификатор покрытия условий — все дали ноль или
шум (детали — в [`docs/03_eval_methodology.md`](03_eval_methodology.md) и
[`docs/BACKLOG.md`](BACKLOG.md)).
**Обобщение, ради которого этот раздел написан:** чистая конфигурация оказалась локальным
оптимумом. Любое добавление контекста или структуры в промпт на нём вредит, а литературные
приёмы не переносятся систематически, а не разово. Мы получили шесть независимых
подтверждений подряд.
---
## 5. Стена: 32 вопроса, которых не берёт никто
Возьмём пять моделей разных семейств (Anthropic ×2, xAI, Xiaomi, NVIDIA) и посмотрим на
исправленном gold, что они решают (n=199):
| | Вопросов | Доля |
|---|---:|---:|
| Решают все пять | 112 | 56.3% |
| Решает хотя бы одна (оракул) | 167 | **83.9%** |
| **Не решает ни одна** | **32** | **16.1%** |
Интересна не величина стены, а её устройство. Из 32 вопросов **15 — это случаи, где все
модели дают ОДИН И ТОТ ЖЕ ответ, и он расходится с gold.** Пять независимых семейств не
галлюцинируют одинаково. Когда они согласны между собой и не согласны с разметкой,
подозреваемый — не модель.
Разбор формы ответа, на котором модели сошлись: **12 — семантика**, 2 — верная форма при
неверной кардинальности, 1 — недостающая колонка.
По сложности стена растёт, но присутствует везде: она забирает **11.9%** простых вопросов
(8 из 67), **16.3%** средних (16 из 98) и **23.5%** сложных (8 из 34). То есть это не просто
«самые трудные вопросы» — четверть стены стоит на простом тире, где генератор в среднем
даёт 76–88%.
Отдельный класс, найденный при разборе руками, стоит назвать прямо: **бенчмарк местами
противоречит собственной подсказке.** В поле `evidence` лежит готовая формула, а gold-SQL
считает по другой — например, `evidence` предписывает делить сумму значений, а gold делит
количество строк. Инвариант нашего промпта (вопрос и `evidence` — в начале) прямо учит
модель доверять подсказке, и на таких вопросах послушание гарантирует промах. Ни один
генератор их не возьмёт, пока следует инструкции. Исправленный gold чинит SQL, но `evidence`
не трогает — противоречие уцелело и там.
Вывод, который стоил разбора: **дешёвых рычагов в стене нет.** Класс «недостающая колонка»
лечится правилом в промпте и стоит порядка одного вопроса. Всё остальное — либо семантика,
которую чинит только более сильная модель, либо вопросы, у которых нет единственного
правильного прочтения.
---
## 6. Своя дообученная модель бейслайн не догоняет
Отдельная проверка гипотезы «дообучим свою маленькую модель под эти данные». Qwen2.5-Coder-7B
в 4 битах, QLoRA на train-части BIRD (8468 примеров; шаблон промпта — продуктовый, но
блок схемы при обучении собран из сырого DDL, а на измерении приходит retrieval-карточками
с примерами значений — известный зазор этой версии датасета), измерение тем же харнессом.
| | EA (n=200) | чинит /61 | ломает /105 | net |
|---|---:|---:|---:|---:|
| База (без дообучения) | 50.0% | 12 | 30 | −18 |
| После дообучения | 53.0% | 12 | 28 | −16 |
Парное сравнение на 199 вопросах, отвеченных обеими версиями: **50.3% → 53.3%, +3.0 п.п.**,
починено 25 / сломано 19, точный критерий Макнемара **p = 0.451**. То есть дообучение
не дало статистически значимого выигрыша даже над собственной базой, а до продуктовых
61.5% не дотянулось на 8.5 п.п. По тирам выигрыш есть на простом и среднем и **отрицателен
на сложном** (−2.9 п.п.).
Методическая деталь, которая тут важнее результата: промежуточная оценка на 153 из 200
вопросов давала «+7.2 п.п., p = 0.099». После добивки оставшихся 46 эффект **ужался вдвое,
а значимость исчезла** — пропущенные вопросы систематически играли за базовую модель.
Знак устоял, но вывод «эффект крепнет с выборкой» оказался артефактом дыр в прогоне.
---
## 7. Что из этого следует
1. **Метрику нельзя читать одним числом.** У EA на оригинальной разметке BIRD коридор
±16.6 п.п. Сравнения тоньше этого — не результаты. Судить надо по бесспорной земле.
2. **Работает только смена генератора.** Все прочие оси — промпт, контекст, отбор примеров,
ансамбли, отбор кандидатов без gold, собственное дообучение — на этом стеке мертвы,
и это проверено, а не предположено.
3. **Масштаб этого рычага зависит от разметки.** На оригинальном gold он выглядит как
+18.0 п.п., на исправленном — как +11.6 п.п. до бесплатной модели и +2.0 п.п. дальше.
4. **Потолок структурный, а не вычислительный.** Верхняя граница отбора из пяти моделей —
83.9%, но все механизмы выбора лучшего кандидата без доступа к gold в этом проекте
умирали шесть раз. А часть остатка недостижима в принципе: там бенчмарк противоречит
сам себе.
5. **Литературные приёмы не переносятся.** Шесть подряд отрицательных результатов на
техниках из статей — достаточное основание проверять их у себя прежде, чем закладывать
в план.
---
## Как воспроизвести
```bash
# прогон генератора (config E, n=200, seed=0)
python scripts/eval_baseline.py --config E --n 200 \
--provider zen --sql-model mimo-v2.5-free --explain-provider mistral \
--report-suffix zen-mimo
# пересчёт того же прогона против исправленного gold
python scripts/rescore_arcwise.py \
--report eval/reports/<дата>/<отчёт>.json \
--sql-only data/arcwise_plat_sql_only.json \
--full data/arcwise_plat_full.json \
--out eval/reports/arcwise_rescored_<имя>.json
```
Коридор разметки и разбиение 105/61/33 считаются из выхода `rescore_arcwise.py`
по полям `original_match` и `sql_only_match`.
**Важно при чтении отчётов:** имя файла отчёта не содержит времени, поэтому два прогона
с одним суффиксом пишут в один файл. Прежде чем назвать число — убедиться, что процесс
прогона завершён, и сверить время изменения отчёта; а пересчёт против исправленного gold
имеет смысл только если он новее самого отчёта.
## Источники
- Jin et al. Аудит разметки BIRD Mini-Dev — [arXiv:2601.08778](https://arxiv.org/abs/2601.08778).
Исправленные артефакты: `data/arcwise_plat_sql_only.json`, `data/arcwise_plat_full.json`.
- Разбор техник и обзор литературы — [`docs/quality_levers_research_2026-07.md`](quality_levers_research_2026-07.md).
- Методология измерения — [`docs/03_eval_methodology.md`](03_eval_methodology.md).
- Сравнение на исправленном gold — [`docs/corrected_gold_evaluation.md`](corrected_gold_evaluation.md).