Введение: зачем мы вообще тестировали локальные LLM для SQL
Ручное написание SQL для биллинга в WMS отнимает часы. Разработчики логистической компании столкнулись с задачей: счета за услуги склада формируются через сложные запросы с десятком JOIN и агрегаций. Каждый новый тип услуги требовал нового запроса. Решение - Text-to-SQL: оператор пишет на естественном языке, что нужно посчитать, модель генерирует SQL.
Облачные API сразу отпали. Данные счетов содержат персональную информацию клиентов и финансовые показатели - выносить это за периметр компании нельзя. Добавьте сюда задержки сети и предсказуемость затрат: при тысячах запросов в день счёт от провайдера становится непрогнозируемым. Оставался один путь: локальный инференс на собственном железе.
Цель этого теста - проверить, способны ли современные открытые модели генерировать корректный SQL на реальной бизнес-задаче без GPU, на сервере с 30 GB RAM. Спойлер: справилась только одна.
Методология тестирования: железо, модели и задача
Конфигурация сервера и ограничения
Сервер, на котором проводился тест, не был спроектирован под инференс. Это стандартная машина для внутренних задач: Intel Xeon E5-2660 v3 (10 ядер, 20 потоков, частота 2.6 ГГц), 30 GB DDR4 RAM, без дискретного GPU. Операционная система - Ubuntu 22.04 LTS.
Запуск моделей - через llama.cpp с квантованием Q4_K_M. Контекстное окно - 4096 токенов, температура - 0.1 (минимальная вариативность для детерминированной генерации кода). Все модели загружались в оперативную память целиком, без оффлоада на диск - это критично для скорости инференса на CPU.
Тестируемые модели и их особенности
В тест попали три семейства моделей, каждое в двух вариантах: универсальная версия и coder-специализация. Размеры - от 9B до 30B параметров. Выбор обусловлен доступностью: все модели свободно распространяются и имеют поддержку в llama.cpp.
- gemma4:26b - флагманская модель Google с архитектурой смеси экспертов (MoE). 26B общих параметров, но активны только 4B на токен. Это позволяет запускать её на CPU с умеренным потреблением памяти и приемлемой скоростью.
- qwen2.5:14b и qwen2.5-coder:14b - универсальная и кодовая версии от Alibaba. Coder-вариант дообучался на репозиториях кода и SQL, что должно давать преимущество в генерации запросов.
- deepseek-coder-v2:16b и deepseek-llm:16b - модель от DeepSeek, известная сильными результатами на бенчмарках кода. Coder-версия позиционируется как инструмент для программистов.
Ожидание было очевидным: coder-версии должны показывать лучшие результаты на Text-to-SQL. Реальность оказалась сложнее.
Тестовая SQL-задача: выставление счета в WMS
Задача взята из продуктивной базы данных склада. Нужно сформировать счёт за услуги хранения и обработки грузов за апрель 2026 года для конкретного клиента. База содержит семь таблиц: clients, contracts, storage_log (ежедневные записи о занятых палето-местах), operations (погрузка, разгрузка, переупаковка), tariffs, invoices, invoice_items.
Промпт, который подавался каждой модели:
Схема базы данных:
- clients (id, name, inn, contract_id)
- contracts (id, client_id, start_date, end_date, tariff_group)
- storage_log (id, contract_id, date, pallets_count, zone)
- operations (id, contract_id, date, op_type, quantity, unit)
- tariffs (id, tariff_group, op_type, zone, price_per_unit)
- invoices (id, client_id, period_start, period_end, total)
- invoice_items (id, invoice_id, description, quantity, unit_price, amount)
Сформируй SQL-запрос для создания счёта клиенту ООО "Логист-Трейд" за апрель 2026.
Нужно посчитать:
1. Стоимость хранения по каждой зоне склада (сумма палето-мест за месяц * тариф зоны)
2. Стоимость операций по каждому типу (количество * тариф операции)
3. Итоговую сумму
Запрос должен быть одним SQL-выражением, без временных таблиц.
Ожидаемый результат - запрос с корректными JOIN по цепочке clients → contracts → storage_log/operations → tariffs, группировкой по зонам и типам операций, фильтрацией по дате и названию клиента, и итоговой суммой через подзапрос или CTE.
Результаты теста: кто справился, а кто - нет
Gemma4:26b - единственный верный результат
Gemma4:26b сгенерировала корректный SQL с первого раза. Запрос использовал CTE для хранения и операций, корректно соединил все семь таблиц, правильно применил фильтр по названию клиента и диапазону дат, сгруппировал результаты по зонам и типам операций, и вывел итоговую сумму.
Сгенерированный запрос (упрощённо):
WITH storage_cost AS (
SELECT
sl.zone,
SUM(sl.pallets_count * t.price_per_unit) as cost
FROM clients c
JOIN contracts ct ON c.contract_id = ct.id
JOIN storage_log sl ON ct.id = sl.contract_id
JOIN tariffs t ON ct.tariff_group = t.tariff_group
AND t.zone = sl.zone
WHERE c.name = 'ООО "Логист-Трейд"'
AND sl.date BETWEEN '2026-04-01' AND '2026-04-30'
GROUP BY sl.zone
),
operations_cost AS (
SELECT
o.op_type,
SUM(o.quantity * t.price_per_unit) as cost
FROM clients c
JOIN contracts ct ON c.contract_id = ct.id
JOIN operations o ON ct.id = o.contract_id
JOIN tariffs t ON ct.tariff_group = t.tariff_group
AND t.op_type = o.op_type
WHERE c.name = 'ООО "Логист-Трейд"'
AND o.date BETWEEN '2026-04-01' AND '2026-04-30'
GROUP BY o.op_type
)
SELECT 'storage' as item_type, zone as detail, cost
FROM storage_cost
UNION ALL
SELECT 'operation', op_type, cost
FROM operations_cost
UNION ALL
SELECT 'total', 'Итого', (SELECT SUM(cost) FROM storage_cost) +
(SELECT SUM(cost) FROM operations_cost);
Запрос оптимален: один проход по таблицам, никаких лишних подзапросов, читаемая структура. Скорость инференса - 4.2 токена в секунду. Вся генерация заняла 47 секунд. Потребление RAM на пике - 18.4 GB из доступных 30 GB.
Qwen и Deepseek: где они ошиблись
Остальные модели справились хуже. Coder-специализация не дала ожидаемого преимущества.
qwen2.5-coder:14b сгенерировала синтаксически верный запрос, но перепутала связи таблиц: соединила storage_log напрямую с clients, минуя contracts. Результат - подсчёт палето-мест по всем клиентам, а не по одному. Модель поняла структуру задачи, но не справилась с нормализацией схемы.
qwen2.5:14b (базовая) выдала запрос с халлюцинированным столбцом clients.legal_name вместо name. Запрос упал бы с ошибкой на проде.
deepseek-coder-v2:16b - самая неожиданная неудача. Модель проигнорировала условие «одним SQL-выражением» и сгенерировала три отдельных запроса с комментариями «выполните первый, затем второй, затем третий». Для автоматизированной системы это неприемлемо.
deepseek-llm:16b (базовая) создала корректный JOIN, но потеряла фильтр по дате. Запрос посчитал бы стоимость за весь период действия контракта.
Сводная таблица результатов:
| Модель | Корректность SQL | Время инференса | RAM (пик) |
|---|---|---|---|
| gemma4:26b | Полная, оптимальный запрос | 47 сек | 18.4 GB |
| qwen2.5-coder:14b | Ошибка в JOIN (пропущена таблица contracts) | 22 сек | 12.1 GB |
| qwen2.5:14b | Халлюцинация столбца | 19 сек | 11.8 GB |
| deepseek-coder-v2:16b | Нарушено требование «один запрос» | 31 сек | 13.5 GB |
| deepseek-llm:16b | Потерян фильтр по дате | 28 сек | 13.2 GB |
Практические метрики: скорость инференса на CPU
Цифры говорят сами за себя. На CPU без GPU generation speed составил от 3.1 до 5.8 токенов в секунду в зависимости от модели. Gemma4:26b показала 4.2 t/s - средний результат по скорости, но единственный приемлемый по качеству.
47 секунд на генерацию - это много или мало? Для оператора WMS, который формирует счёт вручную 15-20 минут, это мгновенно. Система не обязана отвечать за миллисекунды: оператор отправляет запрос, через минуту получает готовый SQL, проверяет и выполняет. Ручное написание такого запроса заняло бы от 10 до 30 минут в зависимости от сложности тарифной сетки.
Сравнение с облачными API: GPT-4o сгенерировал бы аналогичный запрос за 3-5 секунд, но ценой отправки конфиденциальных данных во внешний контур. Локальный инференс на gemma4:26b даёт полный контроль над данными при десятикратном увеличении времени ответа - для сценария биллинга это приемлемый компромисс.
Если скорость критична, можно рассмотреть qwen2.5-coder:14b с её 22 секундами на ответ и ценой периодических ошибок в JOIN. Такой подход потребует слоя валидации SQL перед выполнением - тема для отдельного теста.
Выводы: какую модель выбрать для Text-to-SQL в условиях ограниченного железа
Прямой ответ: gemma4:26b с квантованием Q4_K_M. Это единственная модель из протестированных, которая выдала корректный и оптимальный SQL на реальной задаче. 18.4 GB RAM на пике оставляют запас на 30 GB сервере для операционной системы и других процессов.
Критерии выбора модели для Text-to-SQL
Чек-лист для тех, кто выбирает модель под свою задачу:
- Размер модели vs доступная RAM. Модель должна помещаться в память целиком. Gemma4:26b с MoE-архитектурой требует 18-20 GB в Q4_KM - вдвое меньше, чем можно было ожидать от 26B модели.
- Архитектура. MoE (смесь экспертов) даёт преимущество на CPU: активны только 4B из 26B параметров на каждом токене. Это снижает требования к пропускной способности памяти.
- Наличие coder-специализации. Тест показал, что coder-версии не гарантируют лучшего качества. Deepseek-coder нарушила требования к формату вывода, qwen-coder ошиблась в связях таблиц. Универсальная gemma4 обошла специализированные модели.
- Поддержка квантования. Без квантования gemma4:26b заняла бы все 30 GB и не оставила бы места для системы. Q4_K_M - минимально необходимый уровень для запуска.
- Сообщество и совместимость. Все протестированные модели поддерживаются llama.cpp, что упрощает развёртывание. Gemma4 имеет активное сообщество и регулярные обновления.
Промпт-инжиниринг решает многое. В тесте использовалась полная схема БД в промпте - без неё даже gemma4 не справилась бы. Для сложных схем с десятками таблиц стоит рассмотреть RAG: динамическую подгрузку релевантных описаний таблиц в промпт. Это тема для следующего теста.
Если 47 секунд ожидания неприемлемы, а бюджет позволяет добавить GPU, обратите внимание на тесты производительности серверов - например, 24-часовые краш-тесты YADRO VEGMAN показывают, какие конфигурации выдерживают непрерывный инференс без сбоев.
Ограничения теста и дальнейшие шаги
Тест проведён на одном запросе. Это не гарантирует, что gemma4:26b справится с любой задачей Text-to-SQL в WMS. Один запрос покрывает базовый сценарий с JOIN и агрегациями, но не проверяет оконные функции, рекурсивные CTE или сложную фильтрацию с подзапросами.
Направления для дальнейших исследований:
- Тестирование на наборе из 50+ запросов разной сложности. Это даст статистически значимую оценку точности и позволит выявить систематические ошибки моделей. Методология такого теста разобрана в кейсе миграции с Claude Sonnet на Qwen - там использовался эвал на 50 примерах со слепым судьёй.
- Файнтюнинг на доменных данных. Дообучение gemma4 на схеме конкретной WMS и исторических запросах может поднять точность и снизить время инференса за счёт более коротких промптов.
- RAG для схемы БД. Когда таблиц больше 20-30, полная схема не влезает в контекст. RAG-подход с векторизацией описаний таблиц и динамической подгрузкой релевантных фрагментов - логичный следующий шаг.
- Слой валидации SQL. Даже gemma4 может ошибиться на следующем запросе. Автоматическая проверка синтаксиса и запуск EXPLAIN перед выполнением защитят продовую базу от некорректных запросов.
Выбор провайдера модели тоже влияет на результат. В тесте использовались квантованные версии из официальных репозиториев llama.cpp, но разные провайдеры (bartowski, unsloth, lm-studio) могут давать разное качество на одних и тех же моделях. Детальное сравнение провайдеров для Qwen и Gemma с конкретными цифрами pass@1 и связности ответов - в прямом тесте провайдеров для кодинга и общения.
Результат теста однозначен: локальный Text-to-SQL на CPU возможен уже сейчас. Gemma4:26b справляется с реальной задачей биллинга в WMS на сервере за 47 секунд. Остальные модели пока не дотягивают до уровня надёжности, необходимого для автоматизации без постоянного контроля человека.