Я дал qwen3.5-9B (8-bit) и qwen3-coder-30B (iq2_xxs) одну задачу — исправить падающие тесты в Python-проекте. 9B справился за 3 шага. 30B сделал 24 шага, потерял нить, повторил одни и те же вызовы инструментов и вернул уверенный неправильный ответ.
У 30B больше параметров. Он проиграл.
Причина не в модели — в harness’е. Три месяца я строил агентный CLI для локальных LLM и разбирался, почему маленькая модель с правильным окружением стабильно бьёт большую без него. Вот что нашёл.
Типичный сценарий провала
Задача: исправить падающие тесты в репозитории.
Модель читает тест. Читает исходник. Придумывает фикс — и возвращает ответ. Уверенно. С объяснением. Тесты при этом всё ещё красные.
Модель не запустила тесты. Она не могла — у неё не было механизма проверить себя. Она просто сгенерировала правдоподобный ответ и остановилась.
Это не глупость. Это архитектура. Без цикла обратной связи модель работает вслепую — как повар, которому не дают пробовать еду. Он может быть хорошим поваром. Просто блюдо с вероятностью 50% окажется пересолённым.
Что такое harness и почему он важнее модели
Когда запускаешь агент — Claude Code, Cursor, любой другой — большая часть работы происходит не внутри модели. Harness решает: какие файлы показать, запустить ли тесты, что помнить между сессиями, как не потерять нить на длинной задаче.
Умная модель в плохом harness’е работает хуже, чем средняя модель в хорошем. Это контринтуитивно — мы привыкли думать что качество = размер. Но на практике разрыв между “9B не справляется” и “9B справился за 3 шага” — это не веса, это инфраструктура вокруг них.
Я написал lema — опенсорсный агентный CLI для локальных LLM. Расскажу что внутри и какие решения оказались нетривиальными.
Цикл верификации: модель не должна верить себе на слово
Главная идея простая: когда модель говорит “я сделал” — harness не верит ей. Он запускает тесты сам.
Если тесты красные — вывод об ошибках уходит обратно в контекст, и модель продолжает работу. Если зелёные — принимаем результат. Модель физически не может сказать “готово” пока тесты не пройдут.
Это меняет всё. Модель перестаёт гадать и начинает знать. Вместо “кажется правильно” — “проверил, работает”. Одна эта штука убрала большую часть ошибок в тестах.
Небольшая деталь из практики: если модель сама запускает тесты через bash — harness это видит и учитывает при своей проверке. Двойного запуска не происходит. Но и ложного “зелёного” тоже.
Память: чтобы не начинать с нуля каждый раз
Когда задача завершается циклом провал → успех (тесты были красными, потом стали зелёными), harness сохраняет урок. Не весь транскрипт — короткую выжимку: что за задача, какая команда упала, что помогло.
На следующей похожей задаче уроки достаются через поиск по эмбеддингам и ложатся в контекст перед началом работы. Модель читает их как подсказки от себя прошлой.
DRY-принцип, но на уровне агентских сессий. Модель не изобретает одно и то же решение дважды.
Есть и ручной режим: /remember в TUI позволяет сохранить что угодно — соглашения проекта, особенности API, что-то что модель должна помнить всегда.
Контекст: маскировка дешевле суммаризации
Длинные задачи переполняют контекстное окно. Большинство агентов в этот момент либо крашатся, либо начинают галлюцинировать.
Классический ответ — попросить модель написать резюме разговора. Проблема в двух частях: это дополнительный LLM-вызов (медленно), и маленькая модель пишет плохие резюме — дропает детали, которые понадобятся на следующем шаге.
Исследование JetBrains Research (arXiv 2508.21433) показало: маскировка на 52% дешевле суммаризации и при этом точнее. Идея: не удалять старые сообщения, а заменять тяжёлые выводы инструментов коротким плейсхолдером — “вывод скрыт, файл такой-то, 487 строк”. Рассуждения модели и сами вызовы инструментов остаются нетронутыми. Агент знает что делал и может перечитать нужный файл если нужно.
lema маскирует первым. Суммаризация включается только если маскировки не хватило — и тогда это делает дешёвая вспомогательная модель, не основная.
Инструменты: называй как модель ожидает
Самая незаметная причина провалов SLM — schema misalignment. Модель галлюцинирует правдоподобное название инструмента вместо реального, потому что видела похожее в pretraining.
Исследование PA-Tool (arXiv 2510.07248) измерило это: одно только переименование инструментов под pretraining-конвенции даёт +17% точности и минус 80% ошибок несоответствия. Без изменения модели, без тюнинга.
Поэтому в lema инструменты называются максимально скучно и предсказуемо: read_file, write_file, edit_file, grep, glob, bash. Никаких умных имён — только те, которые модель видела тысячи раз.
Второй принцип — не больше 7±2 инструментов в контексте. Точность выбора деградирует после десяти. Когда нужно больше — dynamic retrieval, показываем только релевантные задаче.
Третий — минимальный вывод. read_file отдаёт запрошенный диапазон, не весь файл. grep — строки с контекстом, не весь файл. Меньше шум = больше места для рассуждений.
Effort dial: для SLM overthinking опаснее, чем underthinking
Когда модель плохо справляется — инстинкт говорит “дай ей больше думать”. Больше токенов на рассуждение, больше шагов. Это ошибка.
Research 2026 (arXiv 2604.10739, 2507.14417) документирует inverse scaling: при росте thinking-бюджета точность SLM сначала растёт, потом падает. Модель начинает сомневаться в правильных ответах, переусложнять простые задачи, топтаться на одном месте.
Поэтому effort в lema — это не “думай больше”. Это пресет конкретных параметров: сколько шагов, сколько токенов, какой тон инструкции. Четыре уровня: low (быстро, кратко), medium (дефолт), high (планируй, проверяй инструментами), ultra (максимум шагов для цикла верификации).
Ключевой нюанс в ultra: он даёт в три раза больше шагов, но не в три раза больше токенов на мышление. Больше tool actions, не больше thinking. Для SLM запустить тесты ещё раз эффективнее, чем думать о них дольше.
auto выбирает уровень по задаче без LLM-вызова: длинный фикс или рефакторинг → high, короткий вопрос → low, иначе medium.
AGENTS.md: правила, которые не забываются
Стандартная проблема: кладёшь правила в system prompt, модель соблюдает их первые несколько шагов — потом забывает.
Это не баг модели. LLM хорошо помнит начало и конец контекста, слепнет к середине. На длинной задаче правила уходят именно туда.
lema решает это re-injection’ом: правила инжектируются дважды — в начало контекста и в конец перед каждым вызовом модели. В конце — конденсированная версия, только ключевые строки. Конденсация детерминированная, без LLM-вызова.
Поддерживаются AGENTS.md (open standard, принят OpenAI, Google, Cursor, Aider, Gemini CLI), CLAUDE.md и .lema/rules.md. Кладёшь файл в корень проекта — lema читает автоматически.
Баг, который было приятно найти
При тестировании qwen3.5-9b модель возвращала пустой ответ при явно ненулевом числе completion-токенов. Выглядело как зависание.
Прямой curl к LM Studio показал: с thinking-моделями сервер возвращает content: "" и кладёт всё мышление в отдельное поле reasoning_content. Наш тип сообщения это поле не знал — оно дропалось при разборе ответа.
Фикс несложный: добавить поле в тип, а если content пустой при непустом reasoning_content — запросить финальный ответ явно. И важная деталь: reasoning_content не должен попадать в историю контекста — иначе thinking-токены начнут накапливаться и жрать окно на каждом следующем шаге.
Классический случай когда баг объясняет поведение, которое иначе выглядит как “модель просто плохая”.
Сравнение: 9B vs 30B на одних задачах
Тестировал на pomodoro-таймере на Python (~4 файла, реальные тесты):
Модель |
Квант |
Шагов на задачу |
Результат |
|---|---|---|---|
qwen3-coder-30B |
iq2_xxs |
20-26 |
средний, часто false success |
qwen3.5-9B |
8-bit |
2-4 |
высокий, верифицированный |
30B технически мощнее. Но агрессивный квант (iq2_xxs — это очень низко) убивает качество рассуждений до такой степени, что модель теряет нить, повторяет вызовы инструментов и не замечает противоречий в своих действиях.
9B с нормальным квантом и harness’ом: читает файл → делает правку → верификация зелёная → готово.
Вывод простой: качество кванта важнее размера модели, когда harness делает свою работу.
Итог
Три месяца экспериментов свелись к одному выводу: локальные модели не дурнее, чем кажутся. Дурнее окружение.
Разница между “9B не справляется” и “9B справился за 3 шага” — это не размер весов. Это наличие цикла обратной связи, правильных имён у инструментов и памяти между сессиями.
Что удивило больше всего:
Переименование инструментов дало заметный прирост без изменения промптов
Маскировка работает лучше суммаризации — и это контринтуитивно
Inverse scaling реален: “думай дольше” иногда ухудшает результат
9B с 8-bit стабильно бьёт 30B с iq2_xxs при правильном harness’е
Честные ограничения: большие кодовые базы (500+ файлов), задачи без явного verifier, всё что требует глобального понимания архитектуры — пока не его история.
lema — опенсорсный проект, MIT, TypeScript, zero runtime deps. Нужен LM Studio с загруженной моделью:
npm install -g @iivgll4/lema lema ping lema "fix the failing tests"
Исходники: github.com/iivgll/lema
Если строили что-то похожее или пробовали другие подходы к верификации — интересно сравнить в комментариях.
Комментарии (3)

al-chemist
01.07.2026 06:07LLM хорошо помнит начало и конец контекста, слепнет к середине. На длинной задаче правила уходят именно туда.
Этому есть какое-то подтверждение, или это «я так вижу»?
OlegGavrilov
Кликбейтный заголовок сделал только хуже, когда оказалось что вы сравнивали модели с настолько разным квантованием. Сравните ваш харнесс с тем же pi на одной и той же локальной модели, вот это будет действительно интересно.
vaashaforostov
оно быть может и да, но мне, как относительному новичку, который только начинает познавать локальные ИИ и столкнулся с первыми проблемами их использования - было интересно почитать все равно, и статья навела меня на вектор поиска того, что мне нужно, чтобы уйти от дорогого уже теперь codex, хоть и неглупого