Баг кэширования Qwen 3.5: исправление для llama.cpp и oMLX.ai | AiManual
AiManual Logo Ai / Manual.
08 Апр 2026 Гайд

Разбор и исправление бага кэширования в Qwen 3.5: как проблема в chat template влияет на производительность в llama.cpp и oMLX.ai

Глубокий разбор бага кэширования в Qwen 3.5 из-за ошибки в chat template. Диагностика, исправление и оптимизация производительности для llama.cpp и oMLX.ai на 0

Ваша модель Qwen 3.5 вдруг стала тупить? Возможно, это не железо, а баг

Запускаете Qwen 3.5 (например, актуальную на апрель 2026 года версию Qwen3.5-72B-2026-03) через llama.cpp. Первые запросы летят быстро. Потом - паузы. Генерация замедляется в два, а то и в три раза. В oMLX.ai та же история: контекстный кеш будто сходит с ума, съедая лишние гигабайты памяти.

Вы проверяете температуру GPU, обновляете драйверы, ругаетесь на производителей видеокарт. А проблема - в одной строчке кода. Вернее, в её отсутствии.

На 08.04.2026 этот баг актуален для всех версий Qwen 3.5, конвертированных в GGUF формат. Особенно критично для long-context моделей (128K+) и агентских сценариев с tool calling.

Что ломается и как это заметить

Симптомы кажутся размытыми, пока не узнаешь, куда смотреть.

  • Деградация производительности во времени: первый промпт - 50 токенов в секунду, десятый - 20. Без изменения длины контекста.
  • Скачки потребления памяти: llama.cpp внезапно запрашивает на 15-20% больше VRAM после нескольких итераций диалога.
  • Артефакты в логах: при детальном логировании (--log-disable в llama.cpp) видите повторяющиеся служебные токены <|im_start|> там, где их быть не должно.
  • Сломанный кеш в oMLX.ai: система сообщает о cache hit, но latency не уменьшается. Иногда вообще падает с ошибкой переполнения буфера кеша.

В чём связь между этими симптомами? Все они указывают на один корень: неправильную работу ключевого механизма кеширования контекста (KV-cache).

Диагностика: ищем не там, где потеряли, а где светло

Первое, что делают 90% разработчиков - начинают ковырять настройки quantization или параметры инференса. Это тупик.

Правильный путь - смотреть на сырой промпт, который уходит в модель. Запустите llama.cpp с флагом --prompt-cache и --log-disable, затем сохраните промпты до и после нескольких итераций.

./main -m qwen3.5-72b.Q4_K_M.gguf -p "Тестовый запрос" --interactive --log-disable --prompt-cache cache.bin --prompt-cache-all
💡
В oMLX.ai аналогичную диагностику можно провести через веб-интерфейс разработчика (Developer Tools), включив логирование raw tokens. Ищите повторяющиеся sequence IDs.

Что вы увидите? В идеале, для каждого нового пользовательского сообщения в диалоге, модель должна получать только новые токены. Старые - должны браться из кеша.

А в реальности - после второго сообщения система начинает подмешивать в промпт закрывающие теги предыдущих ответов или дублировать служебные разделители. Для модели это как читать книгу, где каждую главу начинают с абзаца из предыдущей. Она сбивается, кеш становится невалидным, и inference начинается почти с нуля.

1Корень зла: сломанная логика в Jinja-шаблоне

Откройте стандартный chat template для Qwen 3.5. В llama.cpp он лежит в chat_templates/qwen.jinja. В oMLX.ai - встроен в рантайм. Нас интересует блок обработки истории диалога.

Оригинальный код (упрощённо) выглядит так:

{{ bos_token }}{% for message in messages %}{% if message['role'] == 'user' %}<|im_start|>user
{{ message['content'] }}<|im_end|>
<|im_start|>assistant
{% endif %}{% endfor %}

Кажется, всё нормально? Нет. Проблема в том, как этот шаблон применяется при инкрементальной генерации (когда у нас уже есть часть ответа от ассистента).

Когда система пытается добавить новое сообщение пользователя к существующему кешу, она должна вставить только токены нового пользовательского промпта. Но из-за ошибки в условных операторах шаблон пересобирает всю историю с начала, включая старые теги <|im_end|>.

Для модели токен <|im_end|> - это маркер конца сообщения. Если он появляется в середине контекста, ломается вся логика attention mask. Кеш становится непригодным для reuse.

Особенно критично это для режима "reasoning" (think step) в Qwen 3.5 Next. Модель начинает "думать" внутри тегов, а шаблон их дублирует, создавая бесконечные циклы. Мы писали об этом в статье о критических багах парсера.

2Исправление: патчим шаблон на лету

Не нужно ждать фикса от разработчиков llama.cpp (хотя на 08.04.2026 он ещё не выпущен). Исправляем сами.

Создайте новый файл qwen_fixed.jinja рядом с моделью GGUF. Содержимое:

{{ bos_token }}{% for message in messages %}{% if message['role'] == 'user' %}<|im_start|>user
{{ message['content'] }}<|im_end|>
<|im_start|>assistant
{% elif message['role'] == 'assistant' %}{{ message['content'] }}{% if not loop.last %}<|im_end|>
{% endif %}{% endif %}{% endfor %}

Ключевое изменение: убрано автоматическое добавление <|im_start|>assistant для каждого сообщения пользователя. Вместо этого тег ассистента добавляется только когда действительно начинается ответ модели. Это предотвращает дублирование.

Второе: закрывающий тег <|im_end|> добавляется только если после ответа ассистента есть ещё сообщения в истории (проверка {% if not loop.last %}).

3Применяем исправление в llama.cpp

Запускайте модель с указанием кастомного шаблона:

./main -m qwen3.5-72b.Q4_K_M.gguf --chat-template ./qwen_fixed.jinja -p "Ваш запрос"

Для постоянного использования конвертируйте модель с исправленным шаблоном внутрь GGUF файла (это немного хакерский способ, но работает):

python convert.py --outfile qwen_fixed.gguf --chat-template ./qwen_fixed.jinja исходная_модель/
💡
Если вы используете наш исправленный чат-шаблон для Qwen 3.5, эта проблема уже решена. Просто убедитесь, что используете актуальную версию от апреля 2026.

4Настройка для oMLX.ai

В oMLX.ai (актуальная версия на апрель 2026 - 2.3.x) процесс сложнее, потому что система использует собственный компилятор шаблонов.

Вам нужно создать custom chat handler. Пример конфигурации в YAML:

model_config:
  name: "qwen3.5-72b"
  chat_template: |
    {{ bos_token }}{% for message in messages %}
    {% if message['role'] == 'user' %}
    <|im_start|>user
    {{ message['content'] }}<|im_end|>
    <|im_start|>assistant
    {% elif message['role'] == 'assistant' %}
    {{ message['content'] }}
    {% if not loop.last %}<|im_end|>
    {% endif %}
    {% endif %}
    {% endfor %}
  cache_config:
    reuse_context: true
    max_reuse_tokens: 16384

Важный момент: в oMLX.ai нужно явно включить reuse_context и задать max_reuse_tokens. Иначе система будет игнорировать кеш, даже с исправленным шаблоном.

Ошибки, которые вы совершите (и как их избежать)

ОшибкаПоследствиеИсправление
Пропустить проверку loop.lastМодель не завершит ответ, создавая бесконечную генерациюВсегда добавляйте условие для последнего элемента
Использовать старый шаблон с новыми моделями Qwen NextСломается reasoning и tool callingБерите шаблоны только из официального репозитория на дату 08.04.2026
Забыть про bos_token в началеМодель будет неправильно интерпретировать начало последовательностиВсегда включайте {{ bos_token }} в первый блок

Самая коварная ошибка: думать, что исправление шаблона автоматически решит все проблемы с производительностью. Нет. После патча нужно сбросить кеш и перезапустить инференс-сессию. В llama.cpp удалите файл cache.bin. В oMLX.ai перезапустите контейнер модели.

Что в итоге? Производительность возвращается

После применения исправления:

  • Скорость генерации стабилизируется. Не будет деградации на длинных диалогах.
  • Потребление памяти снизится на 15-30% для контекстов от 8K токенов.
  • Кеш начнёт работать правильно: cache hit rate в oMLX.ai поднимется с 40-50% до 85-90%.

Это не магия. Это просто исправление одной логической ошибки, которая стоила вам сотен часов процессорного времени.

Последний совет: никогда не доверяйте стандартным шаблонам вслепую. Особенно в быстро развивающейся экосистеме локальных LLM. Всегда проверяйте, что на самом деле уходит в модель. Инструменты вроде --log-disable в llama.cpp или дебаг-режим в oMLX.ai - ваши лучшие друзья.

И да, если вы столкнулись с ошибкой "Failed to parse at pos" после манипуляций с шаблонами, у нас есть отдельное руководство по её исправлению. Сохраните его в закладках.

Теперь идите и заставьте свою Qwen 3.5 летать. Она этого достойна.

Подписаться на канал