Локальные open-source LLM для кодинга: как построить честное сравнение. Тест-набор задач, метрики точности и скорости, повторяемость и оформление результатов.

Когда сравнивают локальные open-source LLM для кодинга, часто побеждает не самая сильная модель, а та, которая удачно ответила на пару ваших любимых вопросов. Мозг быстро достраивает картину: один раз модель попала в точку - и кажется, что она «умнее». Но на другой задаче и с другой формулировкой впечатление легко переворачивается.
Кодинг-задачи особенно коварны, потому что «похоже на правду» не означает «работает». В чате можно простить красивый текст. В разработке важнее другое: компилируется ли код, проходят ли тесты, не ломает ли он соседние части, соблюдает ли ограничения по версиям библиотек и стилю проекта. Плюс есть мелочи, которые критичны для продукта: структура файлов, миграции БД, обработка ошибок, безопасность.
Сравнение «без религии» значит одно: результаты можно проверить и повторить. Вы заранее фиксируете условия, задачи и критерии, а потом сравниваете по цифрам и понятным фактам, а не по эмоциям. Тогда спор «мне кажется» превращается в вопрос «что показывает тест-набор».
Методика нужна не ради науки, а ради решений. По итогам теста вы сможете выбрать модель для ежедневной работы (или разные модели под разные задачи), понять, какие настройки реально помогают, оценить бюджет на железо и время (сколько стоит один успешный результат), а также решить, где нужна страховка процессом (обязательные тесты и проверки), а где можно доверять генерации.
Если вы делаете продукт, где важны повторяемость и быстрый выпуск, «самая умная по ощущениям» модель может проиграть той, что чуть проще, но стабильно дает корректный код и предсказуемое время ответа.
Чтобы сравнение было честным, заранее договоритесь, что именно вы держите постоянным. Иначе вы тестируете не модели, а случайный набор настроек, железа и привычек.
Сначала фиксируют среду запуска: CPU или GPU, модель видеокарты, объем VRAM, оперативную память, тип диска и версии драйверов. На одной машине модель может быть «быстрой», а на другой упираться в память и тормозить. Для локальных моделей уровень квантования тоже часть условий: разные варианты квантования - это фактически разные режимы.
Дальше идут ограничения по контексту и длине ответа. Две модели можно сделать «равными» только если одинаково заданы: максимальное число токенов в контексте, лимит токенов на ответ и поведение при переполнении (обрезка, сводка и т.д.). Иначе одна модель просто получит больше вводных.
Настройки генерации критичны для повторяемости. Температура и top_p меняют «смелость» ответов. Seed (если поддерживается движком) помогает проверять воспроизводимость. Если seed задать нельзя, это тоже нормальная ситуация, просто тогда повторяемость проверяется серией прогонов.
Отдельно опишите режим работы с инструментами. Тест «без инструментов» и тест «с инструментами» - это два разных режима. Доступ к терминалу, запуск тестов, линтер и подготовленные фикстуры резко повышают точность, и это нормально, если именно так вы планируете работать.
И не забудьте про язык: задача может быть на русском, а код и комментарии на английском. Зафиксируйте правило: на каком языке промпт, на каком ожидается код, допустимы ли русские комментарии, и как вы формулируете требования к стилю.
Удобно завести один «паспорт прогона» и заполнять его каждый раз:
Так результаты сравниваются по одинаковым правилам, а не по настроению.
Хороший тест-набор проверяет не «умеет ли модель делать магию», а как она закрывает обычные рабочие куски. Лучше брать короткие задачи на функцию или небольшой модуль, где можно однозначно проверить результат. Задания уровня «сделай мне весь проект» почти всегда превращаются в спор о вкусе и удаче.
Нужна смесь сложности. Простые задачи показывают базовую грамотность, средние - умение держать контекст, а задачи с подвохом проверяют внимательность к требованиям (валидация, крайние случаи, безопасность, ограничения по времени).
Чтобы сравнение было честным, приводите задания к одному формату: одинаковая структура промпта, одинаковые входные данные и четкое описание ожидаемого результата. Хорошо работает шаблон: контекст (1-2 предложения), требования, примеры ввода-вывода, критерии «зачет/незачет». Тогда модели соревнуются в решении, а не в том, как угадать, что вы имели в виду.
Покройте свой стек. Даже в одном продукте модели часто ведут себя по-разному в разных типах задач: фронтенд (состояние, формы), бэкенд (HTTP-обработчики, ошибки, тесты), SQL (джойны, агрегации, индексы), мобильные экраны (простая логика и валидация) и «клей» (конфиги, миграции, интеграции).
Задайте рамки для каждой задачи: лимит времени на ответ и лимит размера (например, «не больше 120 строк»). Пример: «Напиши функцию на Go, которая принимает JSON с заказом, валидирует обязательные поля и возвращает список ошибок. Дай 3 тест-кейса. Лимит: 8 минут и 100 строк». Такие рамки помогают сравнивать качество, а не многословие.
Главная причина споров - у каждого свое «нормально работает». Поэтому до запуска тестов договоритесь о проверяемом определении «правильно» и переведите его в проверку, которую можно повторять много раз.
Самый простой слой - юнит-тесты. Они подходят для чистых функций, парсеров, преобразований данных, правил валидации и небольших сервисов. Их можно гонять десятки раз на разных настройках и быстро видеть, где результат «плывет».
Следующий слой - интеграционные проверки. Для кодинга это обычно не про «идеальную архитектуру», а про то, что проект реально собирается и запускается. Для стека вроде React на фронте и Go + PostgreSQL на бэке базовыми считаются: сборка без ошибок, линтер без критичных замечаний, успешный запуск миграций и минимальный прогон API-ручки через тестовый клиент.
Безопасность тоже можно проверять без превращения теста в аудит. Хватает простых правил: не логировать секреты, не оставлять пароли и токены в коде, не отключать проверку TLS в примерах, не собирать SQL строкой из пользовательского ввода.
Чтобы не спорить после, задайте для каждой задачи явные критерии «принято/не принято». Например: все тесты проходят, сборка проходит, миграции применяются; выходной формат совпадает с контрактом (поля, типы, коды ошибок); нет секретов в репозитории и логах; время выполнения укладывается в лимит; не меняются файлы вне области задачи.
Эталонные ответы и данные храните так, чтобы их легко обновлять и сравнивать: фиксированные fixtures и сиды, «золотые» файлы для вывода там, где это уместно, отдельный конфиг проверки (версии, команды, лимиты) и протокол запуска (модель, параметры, коммит задачи).
Чтобы методика сравнения LLM не превращалась в спор о вкусе, заранее договоритесь, что именно вы измеряете и как считаете баллы. Три оси почти всегда дают ясную картину: точность, повторяемость и скорость.
Точность проще считать не по впечатлению от ответа, а по прохождению проверок. Самый строгий и полезный показатель - доля задач, которые прошли все тесты с первого запуска, без ручных правок. Если задача включает несколько проверок (например, 5 тест-кейсов), фиксируйте и частичную успешность: процент пройденных проверок рядом с отметкой «задача полностью пройдена». Тогда «3 из 5» не теряется и видно, где модель регулярно спотыкается.
Качество кода тоже можно оценивать без философии: понятные имена, минимум лишней магии, нет явного дублирования, ошибки обрабатываются, структура читается за минуту. Это не про «идеальный стиль», а про то, насколько результат можно поддерживать.
Повторяемость - стабильность результата при одном и том же запросе. Важно смотреть не на одинаковость текста, а на одинаковость итога: проходят ли тесты, одинаково ли обработаны крайние случаи, не меняется ли подход с «рабочего» на «опасный». Для этого запускайте один и тот же кейс несколько раз и считайте разброс.
Со скоростью полезно фиксировать два числа: время до первого токена (TTFT) и время до готового решения (когда можно запускать тесты). Если одна модель отвечает быстро, но чаще требует второго запроса, итоговое время на задачу может быть хуже.
Относитесь к сравнению как к небольшому эксперименту: один протокол, одинаковые условия, понятные метрики. Тогда спор «мне показалось» быстро заканчивается.
Зафиксируйте окружение и параметры генерации. Запишите железо (CPU, GPU, RAM), версии драйверов и рантайма, способ запуска модели, контекстное окно, шаблон промпта, temperature, top_p, seed (если есть), max_tokens, stop-токены. Если меняете хотя бы один пункт, это уже другой эксперимент.
Сделайте прогрев. Первые запросы часто медленнее из-за подгрузки весов, кешей и особенностей рантайма. Выполните 2-5 коротких прогонов и отметьте в протоколе, что прогрев выполнен.
Прогоняйте задачи в одинаковом порядке. Один и тот же набор, один и тот же текст входных данных, одинаковая длина контекста. Не «помогайте» модели уточнениями по ходу, если это не прописано как часть сценария.
Запускайте проверки автоматически и собирайте логи. Обычно это сборка, линтер, юнит-тесты, статический анализ, плюс лог выполнения (время, ошибки, выходные файлы). Сохраняйте и сырой ответ модели, и результат проверок.
Повторите N прогонов, чтобы увидеть разброс. Минимум 5, лучше 10-20 на ключевых задачах. Если есть seed, используйте несколько фиксированных значений.
Сведите результаты в таблицу и добавьте короткие выводы по классам задач: генерация кода, исправление багов, написание тестов, рефакторинг. Держите не только средние значения, но и минимум-максимум и долю успешных прогонов. Это быстро показывает, где модель стабильна, а где ей просто повезло.
Если модель один раз решила задачу, это еще не значит, что она будет решать ее завтра. Для локальных моделей скачки заметны особенно: чуть другой промпт, температура или нагрузка на машину - и ответ меняется.
Для проверки базовой «способности решать» сначала включайте максимально предсказуемый режим: фиксируйте seed (если поддерживается), ставьте низкую температуру (например, 0-0.2) и одинаковые ограничения на длину ответа.
Когда базовая проверка пройдена, добавляйте вариативность: температура выше (например, 0.6-0.8) и несколько прогонов. Это показывает, насколько часто модель приходит к рабочему решению, а не случайно угадывает.
Для «домашнего» сравнения обычно хватает 5 прогонов на задачу в детерминированном режиме и 10 прогонов в вариативном. Если задачи длинные и дорогие по времени, лучше взять меньше задач, но сделать больше повторов на каждую.
По каждому прогону записывайте минимум: успех или провал (по вашему правилу), время до первого валидного результата, число попыток исправления (если вы это разрешаете), параметры генерации (температура, top_p, max_tokens, seed).
После этого посчитайте долю успехов и разброс по времени (минимум, медиана, максимум). Медиана часто честнее среднего.
Если тест «флапает» (то проходит, то нет), это важный сигнал. Обычно помогают три действия: уточнить критерий успеха, жестче зафиксировать окружение (версии, зависимости, таймауты) или разделить задачу на две (генерация и исправление).
Для моделей разного размера держите одинаковое правило остановки: например, одна попытка генерации плюс одна попытка исправления, и одинаковый бюджет токенов. Иначе «победит» не качество, а возможность дольше рассуждать.
Скорость модели - не одно число. Минимум разделяйте TTFT и время до полного ответа. В кодинге TTFT влияет на ощущение «модель живая», а полный ответ важен, когда вы ждете готовый файл или патч.
Сравнивайте скорость отдельно на коротких и длинных задачах. На коротких (например, «напиши SQL-запрос») часто решают TTFT и накладные расходы запуска. На длинных (например, «сгенерируй компонент React + тесты») важнее стабильная генерация: токенов в секунду и просадки ближе к концу, когда разрастается контекст.
Чтобы привязать скорость к «цене», не усложняйте: фиксируйте железо и считайте «секунд на задачу» вместе с загрузкой. Две модели могут давать одинаковое время, но одна делает это при 95% загрузке GPU и 22 ГБ VRAM, а другая - при 60% и 10 ГБ. Это разная стоимость эксплуатации, даже если ответы похожи.
Параллельные прогоны легко искажают картину. Если вы запускаете несколько запросов одновременно, вы измеряете не «скорость модели», а поведение очереди и конкуренцию за память. Для честного сравнения держите два режима: одиночный прогон (чистая латентность) и фиксированная параллельность (пропускная способность).
Записывайте нагрузку так, чтобы сравнение можно было повторить через неделю: конфигурацию CPU/GPU и RAM/VRAM, квантование и размер контекста, max_new_tokens, temperature и seed, TTFT и общее время, токены/сек, пиковую VRAM/RAM и режим прогона (1 запрос или N параллельных), а также факт прогрева перед замером.
Самая частая ошибка - «подкрутить» промпт под каждую модель так, чтобы она выглядела лучше. Да, модели по-разному реагируют на стиль запроса. Но если вы меняете структуру, подсказки и ограничения, вы уже сравниваете не модели, а ваш промптинг. Правильнее держать один базовый запрос и отдельно фиксировать минимальные правки, которые нужны всем.
Вторая ловушка - свести в одну цифру задачи разного типа. Генерация кода, исправление бага, объяснение чужого кода и написание тестов - разные навыки. Если смешать их в общий «средний балл», победит модель, которой чаще повезло на конкретном наборе. Разбивайте результаты по категориям и смотрите профиль.
Еще один источник самообмана - оценка «на глаз» без фиксированных тестов. Красивое объяснение не равно рабочему решению. Минимум - компиляция/линтер и запуск тестов, максимум - одинаковый пайплайн проверки для всех.
Отдельная проблема - «чери-пикинг»: выбирать только те задачи, где фаворит уже понравился. Хороший тест-набор включает и неприятные случаи: неоднозначные требования, неполные данные, регрессии.
И часто забывают сохранять полные ответы и логи. Без этого нельзя понять, что именно произошло: модель ошиблась, вы забыли ограничение, поменялась температура, или просто оборвался вывод. Простое правило: сохранять промпт, параметры, полный ответ и итог проверки.
Протокол должен позволять повторить тест через неделю и получить сопоставимые цифры.
По модели: точное имя и версия, квантование, размер контекста, параметры генерации (temperature, top_p, max_tokens, seed).
По тест-набору: список задач с метками (тип, сложность, ожидаемые артефакты вроде «функция + юнит-тесты» или «SQL миграция»).
По каждому прогону: текст промпта и входные данные, итог проверки (pass/fail и частичный процент), время, число итераций правок (если допускаются), а также контекст запуска (дата, железо, лимиты, заметки о сбоях и ручных вмешательствах).
Если вы тестируете модели внутри одного продукта, добавьте поле «окружение проекта»: зависимости и соглашения, которые считаете обязательными. Это убирает скрытые допущения.
Сценарий, похожий на реальную работу: небольшое веб-приложение. Есть форма «Заявка» на React, сервер на Go и таблица в PostgreSQL. Пользователь вводит имя, email и комментарий, отправляет, а в интерфейсе видит таблицу последних заявок.
Один и тот же набор проверок для каждой модели:
«Правильность» фиксируете не по ощущениям, а по наблюдаемым результатам: сборка и тесты, затем 2-3 негативных сценария (пустое имя, неправильный email, слишком длинный комментарий). Отдельно посмотрите обработку ошибок БД: что вернется клиенту, есть ли логирование, не течет ли внутренняя ошибка наружу.
Чтобы честно сравнить 2-3 модели, держите условия одинаковыми: одинаковые промпты, одинаковые ограничения (температура, max tokens), одинаковая структура проекта. В итоге получается не спор «какая лучше», а таблица.
Если вы делаете такие прогоны в TakProsto (takprosto.ai), удобно разделять работу на два шага: сначала получать план файлов и действий в planning mode, а потом генерировать изменения частями. При неудаче можно откатиться к snapshot и повторить прогон на той же модели или другой, не смешивая правки.
Надежнее всего опираться на проверяемые результаты: собирается ли проект, проходят ли тесты, соблюдаются ли ограничения. Ощущение «хорошо ответила в чате» часто обманывает, потому что красивый текст не гарантирует рабочий код.
Держите постоянными железо и софт, точную версию модели и квантование, размер контекста и лимиты ответа, а также параметры генерации вроде temperature и top_p. Если меняете хоть один из этих пунктов, фиксируйте это как отдельный эксперимент, иначе сравнение будет нечестным.
Обычно достаточно коротких задач на функцию, небольшой модуль или один эндпоинт, где можно однозначно проверить результат. Смешайте простые, средние и задачи с подвохом на крайние случаи, чтобы увидеть не только «умеет ли вообще», но и насколько внимательна к требованиям.
Сделайте один шаблон: краткий контекст, четкие требования, примеры входа-выхода и критерии «принято/не принято». Тогда модели будут соревноваться в решении задачи, а не в угадывании ваших ожиданий по формулировке.
Самое практичное определение — результат проходит заранее заданные проверки без ручных правок. Для большинства задач хватает юнит-тестов и проверки сборки, а для прикладного кода полезно добавить линтер и минимальный интеграционный прогон, чтобы убедиться, что проект реально запускается.
Точность считайте как долю задач, прошедших все проверки с первой попытки, и отдельно фиксируйте частичную успешность по тестам. Повторяемость измеряйте серией одинаковых прогонов и смотрите, насколько стабильно сохраняется итоговый успех. Скорость оценивайте как время до первого токена и время до готового решения, которое можно проверять.
Сначала запустите детерминированный режим: низкая температура и фиксированный seed, если он доступен. Потом добавьте вариативность и сделайте несколько прогонов на тех же задачах; если результат «плавает», это сигнал, что модель нестабильна или критерии/среда теста недостаточно зафиксированы.
Квантование меняет качество и скорость, поэтому сравнивайте модели только при одинаково указанном варианте квантования и одинаковом контекстном окне. Если вы меняете квантование ради экономии VRAM, фиксируйте это как отдельный режим и сравнивайте уже «качество за ресурс».
Если в одном случае модели разрешены терминал, тесты и линтер, а в другом нет, это разные режимы, и результаты нельзя смешивать. Сравнивайте либо «без инструментов», либо «с инструментами», и заранее опишите, что именно доступно и какие команды запускаются.
Полезно сохранять промпт, параметры генерации, полный ответ модели, измененные файлы и результаты всех проверок с временем выполнения. В TakProsto удобно сначала получать план изменений в planning mode, а затем вносить правки частями; если прогон неудачный, snapshot и rollback помогают повторить тест чисто, не смешивая попытки.