Нам нужно было сопоставлять произвольный вопрос пользователя с одним из примерно 1000 имён таблиц базы данных — быстро и на собственном железе. Мы испробовали Laya, open-source «модель решений System-1», от и до: zero-shot, с гибридным поиском и, наконец, дообученную на синтетических данных. Вот что сработало, что незаметно провалилось и что мы бы сделали иначе.

Проблема

Наша платформа данных предоставляет около 1000 логических таблиц. Пользователи задают вопросы вроде «какие были суммарные дневные продажи по магазинам в январе, по дням?», и где-то до генерации SQL что-то должно решить, что этот вопрос про XQR7_DTV_AGG, а не про сорок других таблиц, в которых тоже упоминаются магазины, дни или суммы. Большая LLM справляется с этим хорошо, но нам хотелось, чтобы это был дешёвый локальный примитив: вызываемый на каждой реплике пользователя, добавляющий десятки миллисекунд, а не секунды, и никогда не отправляющий метаданные схемы во внешний API.

Эта постановка — классификация по большому закрытому набору меток — это ровно то, что рекламирует Laya. Laya — это неавторегрессионная модель решений с 421 млн параметров, построенная на энкодере в стиле ModernBERT, выпущенная под Apache 2.0. Ей дают состояние (текст пользователя) и типизированный вопрос с вариантами ответа; она возвращает выбор с откалиброванными вероятностями за один forward pass, примерно за 33 мс на скромных GPU. Набор меток задаётся в момент запроса, так что при добавлении таблиц переобучение не требуется. На бумаге — идеальное совпадение.

Первая стена: это не модель с длинным контекстом

Наивный план — «скормить модели всё про все таблицы как контекст» — умирает мгновенно. Документация по каждой таблице (имя, человекочитаемая метка, описание, список колонок, типичные формулировки использования, заметки для различения) в сжатом виде занимает около 8,5 МБ текста, это почти два миллиона токенов. Бюджет ввода у Laya — 512 токенов (1024 для некоторых чекпоинтов), из которых примерно 200 зарезервированы под отрисовку вариантов ответа, а сама документация отмечает, что качество падает после примерно 20 вариантов на вопрос.

Поэтому архитектура вынужденно состоит из двух этапов:

  1. Поиск сужает около 1000 таблиц до топ-15 кандидатов для запроса.
  2. Решение: Laya выбирает одного кандидата, получив описание каждого примерно на 140 символов.

Это стандартный паттерн «сначала поиск, потом переранжирование»; интересная часть — что на самом деле дал каждый из этапов.

Для начала — хоть что-то измерить

Мы собрали оценочный набор из существующей библиотеки сценарных диалогов поддержки: 115 реплик, где текст ожидаемого ответа называет реально использованную таблицу (или таблицы). Две детали оказались важнее любого выбора модели:

  • Эталонные метки зашумлены. «Ожидаемая» таблица — это та, которую случайно использовал эталонный агент; промах иногда оказывается вполне обоснованной альтернативой. Абсолютные цифры ниже — пессимистичны.
  • На уточняющие реплики без истории ответить невозможно. «Покажи объёмы за тот месяц» сама по себе ничего не находит. Склейка двух предыдущих реплик пользователя с запросом (q_ctx) примерно удвоила все метрики в исследовании. Если ваши пользователи задают уточняющие вопросы, контекст диалога — это не оптимизация, а обязательное условие входа.

Этап первый: поиск — это потолок

Что бы ни делал этап решения, он не может выбрать таблицу, которую не нашёл поиск, так что hit@15 — это жёсткий потолок сквозной точности. Мы сравнили три метода поиска, все на одном и том же сжатом TSV (одна строка на таблицу, взвешенные поля):

Метод поиска hit@15, голый запрос hit@15, с контекстом
BM25, со взвешенными полями 0.24 0.45
BM25 + вручную написанные алиасы сокращений 0.24 0.46
Гибрид: BM25 + маленький эмбеддер, reciprocal rank fusion 0.39 0.65

Доля оценочных реплик, где правильная таблица попадает в топ-15 кандидатов. Эмбеддер: энкодер предложений с 33 млн параметров, один вектор на таблицу.

Здесь два вывода:

  • Разрыв в словаре — главная причина провалов. Пользователь говорит «дневные продажи по магазинам»; таблица называется XQR7_DTV_AGG, а в её описании написано «агрегированные объёмы транзакций». Лексический поиск не может перекрыть соглашения об именовании, построенные на сокращениях. Маленький плотный эмбеддер закрыл большую часть этого разрыва ценой матрицы 1000×384.
  • Вручную составленные списки алиасов — ловушка. Мы написали около 30 разворачиваний (сокращение для «магазина» → магазин и так далее) и получили один балл. Длинный хвост соглашений об именовании длиннее, чем чьё-либо терпение; эмбеддинги выучивают его бесплатно.

Этап второй: что на самом деле добавляет модель решений

С гибридным поиском и контекстом правильная таблица оказывается в списке кандидатов в 65% случаев. Далее zero-shot Laya выбирает её верно в 20% всех реплик:

Конфигурация (с контекстом) Точность Top-1
Только гибридный поиск, берём 1-е место 0.17
Только плотный эмбеддер, берём 1-е место 0.22
Топ-15 гибридного поиска → выбор Laya 0.20
Топ-50 гибридного поиска → турнир Laya (группы по 13, победители проходят дальше) 0.19

Сквозная точность на 115 репликах реальных диалогов; таблица считается верной, если предсказание совпадает с любой таблицей, названной в эталонном ответе.

Если смотреть снисходительно: внутри своего списка кандидатов Laya конвертирует около 31% — более чем в четыре раза больше, чем дали бы 6,7% от случайного выбора среди 15 вариантов, причём по 140-символьным описаниям, которых она никогда не видела, за один forward pass. Механизм работает.

Если смотреть честно: весь пайплайн проигрывает просто первому результату плотного поиска. А турнирная схема — скормить 50 кандидатов группами, продвигать победителей — сделала только хуже, потому что победитель группы лучший лишь локально, и хорошие кандидаты вылетают рано из-за сильных дистракторов.

Одно по-настоящему полезное свойство уцелело: уверенность разделяет случаи. Медианная заявленная уверенность составила 0,88–0,90 на верных выборах против 0,68–0,73 на неверных (с оговоркой, что поставляемые температуры калибровки не покрывают корзину с 11+ вариантами, так что значения стоит считать относительными). Это делает Laya пригодной в роли дешёвого шлюза эскалации: отвечать локально при высокой уверенности, иначе передавать большой модели.

Попытка дообучения, или как выиграть не тот бенчмарк

Веса Laya открыты, и авторы публикуют полный рецепт дообучения (обучение методом градиента политики против наград по корректному правилу оценивания, с этапом калибровки в конце). Их собственный специализированный чекпоинт подскакивает с уровня почти случайного угадывания до 0,77 на их бенчмарке. Идея как раз в том, что модель можно специализировать под своё решение. Так мы и сделали.

Обучающие данные — бесплатно: наша документация по таблицам содержит «типичные формулировки использования» — короткие строки вроде «любимые маршруты доставки, сохранённые комбинации маршрутов, предпочитаемые пути перевозчика». Разбив по запятым, получаем около 25 000 пар (запрос, таблица) по всем 1000 таблицам. Для каждой пары мы построили вопрос с 15 вариантами ответа, чьи дистракторы были реальными топ-кандидатами поиска для этого запроса — hard negatives, соответствующие распределению продакшена. Мы полностью отложили 5% таблиц под валидацию, оставили 115 реплик реальных диалогов как тестовый набор, которого обучение никогда не касается, и убрали формулировки использования из описаний вариантов, чтобы модель не могла выучить простое сопоставление строк.

Сам прогон: по причинам, связанным с железом, обучение шло на CPU — 56-поточный Xeon с тренером, пропатченным с NCCL/CUDA на Gloo, bf16 autocast, минимальный приоритет процесса. Пропускная способность — около 20 секунд на микробатч, примерно 17 часов на эпоху, чуть меньше трёх суток на четыре эпохи. Не рекомендуется, но это доказывает, что рецепту не нужно ничего экзотического.

Результат — самая поучительная таблица в этой статье:

Чекпоинт Синтетическая валидация (запросы-формулировки) Реальные диалоги (115 реплик)
Базовая Laya, zero-shot 0.244 0.200
После 1 эпохи 0.340 0.165
После 4 эпох 0.380 0.139

Дообучение на синтетических запросах: точность на валидации выросла на 56%, точность на реальных данных упала на 30%. Монотонно, в обе стороны.

Модель выучила ровно то, чему мы её учили: классифицировать короткие, насыщенные ключевыми словами именные группы, взятые из документации. Реальные пользователи задают длинные, контекстные, разговорные вопросы. Это разные распределения входных данных, и с каждой эпохой обучение всё сильнее тянуло модель к синтетическому. Валидационный набор с отложенными таблицами — который мы построили специально для отлова переобучения — не поймал ничего, потому что был взят из того же синтетического распределения, что и обучающий набор. Он уверенно валидировал не то.

Урок, который мы бы повесили на плакат: валидационный набор доказывает обобщение только по той оси, по которой он отличается от обучающего. Наш отличался по таблице и совпадал по стилю запроса, так что измерить он мог только первое. Если ваш реальный трафик отличается от обучающих данных по стилю, ваш валидационный набор должен тоже — даже если это значит пятьдесят написанных вручную примеров вместо тысячи сгенерированных.

Собранные оговорки

  1. Laya не контекстная модель. 512–1024 входных токена, примерно 15–20 пригодных вариантов. Любая задача с большим набором меток превращается в «сначала поиск, потом решение», и потолок задаёт качество поиска, а не модель решений.
  2. Лексический поиск проваливается на словаре схемы. Сокращённые имена таблиц против естественных формулировок — главный источник промахов. Маленький плотный эмбеддер — самый дешёвый большой выигрыш из доступных.
  3. Контекст диалога удваивает точность. Разрешайте уточняющие вопросы, склеивая недавние реплики пользователя перед поиском.
  4. Уверенность откалибрована только для малого числа вариантов. Поставляемая таблица температур заканчивается на отметке 11+ вариантов; выше неё используйте уверенность как относительный сигнал для эскалации, а не как вероятность.
  5. Турниры среди множества кандидатов снижают точность. Победители групп лучшие лишь локально; расширяйте поиск вместо каскадирования решений.
  6. Дообучение на синтетических запросах, выведенных из метаданных, явно вредно, если реальные запросы выглядят иначе — а синтетический валидационный набор вас не предупредит.
  7. Эталонные метки из референсных ответов зашумлены; считайте абсолютную точность нижней границей и следите за разницей между конфигурациями.

Всё ещё стоит ли это улучшать?

Мы думаем, что да, по трём направлениям, в порядке ожидаемой отдачи:

1. Чинить распределение обучающих данных, а не модель. Механизм дообучения явно работает — он сдвинул точность на валидации на 14 баллов за три дня на CPU. Он оптимизировал не ту цель, потому что мы скормили ему не те запросы. Решение — генерировать обучающие вопросы, похожие на настоящие: пусть способная LLM перепишет каждую формулировку использования в полноценный разговорный вопрос, добавит правдоподобный диалоговый контекст к части из них, и подмешать любые реальные запросы из логов, какие есть. Тот же рецепт, те же hard negatives, та же дисциплина отложенной выборки — плюс срез валидации, взятый из реального трафика.

2. Вкладываться в поиск раньше этапа решения. Более крупный эмбеддер, эмбеддинг запроса, переписанного с учётом диалога (а не просто склеенного), и индексирование нескольких представлений на таблицу (отдельно развёрнутое имя, описание, список колонок) — всё это напрямую атакует потолок в 0,65. Каждый балл hit@15 стоит больше, чем балл конверсии на этапе решения.

3. Использовать модель решений там, где она сильна: как шлюз. Даже в режиме zero-shot уверенность Laya разделяет верные ответы от неверных. Прагматичная продакшен-схема такая: плотный поиск предлагает кандидатов, Laya выносит вердикт по топ-кандидатам, при высокой уверенности отвечает локально за ~50 мс, при низкой — эскалирует к большой модели. Тогда маленькой модели не нужно обыгрывать LLM — ей нужно просто поглотить лёгкое большинство трафика, а это гораздо более низкая планка.


Настройка для воспроизводимости: около 1000 таблиц; документация по каждой таблице сжата до одной строки TSV; BM25 с весами по полям; эмбеддер предложений на 33 млн параметров с reciprocal rank fusion (k=60); английский чекпоинт Laya 421M через его Python SDK; дообучение по опубликованному рецепту градиента политики, 24,6 тыс. вариантов выбора, 4 эпохи, эффективный батч 32, LR 2,5e-5 косинус; оценка на 115 репликах диалогов с засчитыванием любого совпадения с эталоном. Все цифры получены из одиночных прогонов; небольшие отличия стоит читать соответственно.