Проблема: две модели в памяти, один результат
Представьте ситуацию: вы развертываете Qwen 3.5 в продакшене. Пользователи хотят два режима - обычный (Non-Thinking) и "размышляющий" (Thinking), где модель ведет внутренний диалог перед ответом. Вы запускаете два экземпляра vLLM с разными chat templates. И тут понимаете, что каждый экземпляр загружает свою копию весов модели. Для Qwen 3.5 32B это около 64 ГБ VRAM в FP16. Две копии - 128 ГБ. А если у вас только 80 ГБ на карте? Вы встали.
Эта проблема не теоретическая. На февраль 2026 года, когда модели становятся больше, а VRAM дорожает, каждая гигабайта на счету.
Почему так происходит? Потому что vLLM по умолчанию загружает модель один раз на процесс. Если вы запускаете два отдельных сервера - это два процесса, две загрузки. Но есть способ лучше.
Что такое Thinking режим в Qwen 3.5 и почему он съедает память
Qwen 3.5 (особенно версии 32B и выше) поддерживает режим "Thinking" - это не отдельная модель, а специальный формат промпта. Модель получает инструкцию "Think step by step" внутри запроса, и её chat template добавляет метки для внутреннего диалога.
Например, в Non-Thinking режиме промпт выглядит так:
system: You are a helpful assistant.
user: What is the capital of France?
А в Thinking режиме так:
system: You are a helpful assistant. Think step by step.
user: What is the capital of France?
assistant: Let me think... The capital of France is Paris.
Но ключевое отличие - в chat template. Для Thinking режима используется другой шаблон, который добавляет служебные токены. И если в vLLM вы задаете chat template при запуске, он фиксирован для всего сервера.
Отсюда и проблема: два разных шаблона - два сервера.
Решение: один vLLM, два режима через chat_template_kwargs
Начиная с vLLM 0.5.7 (актуально на 28.02.2026), появилась возможность передавать дополнительные аргументы в chat template через параметр --chat-template-kwargs. Это позволяет динамически менять поведение шаблона без перезагрузки модели.
Идея: создать универсальный chat template, который в зависимости от переданного параметра решает, использовать Thinking или Non-Thinking формат.
Таким образом, мы запускаем один экземпляр vLLM, а при запросе через API передаем параметр, например, thinking_mode=True. Шаблон интерпретирует это и форматирует промпт соответственно.
1 Шаг 1: Создание кастомного chat template
Сначала нужно создать файл chat template для Qwen 3.5, который поддерживает оба режима. Сохраним его как qwen_thinking.jinja:
{% if thinking_mode %}
{{- bos_token -}}
{%- for message in messages %}
{%- if message['role'] == 'system' %}
{{- 'system: ' + message['content'] + ' Think step by step.' + eos_token -}}
{%- elif message['role'] == 'user' %}
{{- 'user: ' + message['content'] + eos_token -}}
{%- elif message['role'] == 'assistant' %}
{{- 'assistant: ' + message['content'] + eos_token -}}
{%- endif %}
{%- endfor %}
{{- 'assistant: Let me think...' -}}
{% else %}
{{- bos_token -}}
{%- for message in messages %}
{%- if message['role'] == 'system' %}
{{- 'system: ' + message['content'] + eos_token -}}
{%- elif message['role'] == 'user' %}
{{- 'user: ' + message['content'] + eos_token -}}
{%- elif message['role'] == 'assistant' %}
{{- 'assistant: ' + message['content'] + eos_token -}}
{%- endif %}
{%- endfor %}
{{- 'assistant:' -}}
{% endif %}
Этот шаблон проверяет переменную thinking_mode. Если True, добавляет "Think step by step." к системному сообщению и инициирует ответ с "Let me think...". Если False, работает как обычный шаблон.
2 Шаг 2: Запуск vLLM с кастомным шаблоном и параметрами
Запускаем vLLM сервер с указанием нашего шаблона и передаем аргументы для шаблона:
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen3.5-32B-Instruct \
--chat-template ./qwen_thinking.jinja \
--chat-template-kwargs thinking_mode=false \
--tensor-parallel-size 2 \
--gpu-memory-utilization 0.9
Обратите внимание на --chat-template-kwargs thinking_mode=false. Это значение по умолчанию. Но мы можем переопределить его при запросе через API.
3 Шаг 3: Отправка запросов с разными режимами
При отправке запроса к API, мы можем передать дополнительные параметры в поле chat_template_kwargs (если используем OpenAI-совместимый API) или через другие методы.
Пример запроса для Non-Thinking режима (значение по умолчанию):
curl http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen3.5-32B-Instruct",
"prompt": "What is the capital of France?",
"max_tokens": 50
}'
Для Thinking режима нужно передать chat_template_kwargs:
curl http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen3.5-32B-Instruct",
"prompt": "What is the capital of France?",
"max_tokens": 100,
"chat_template_kwargs": {"thinking_mode": true}
}'
В ответе модель будет использовать Thinking шаблон.
Важно: vLLM передает chat_template_kwargs непосредственно в шаблон, так что вам не нужно менять код модели. Это чисто конфигурационное решение.
Альтернатива: модификация модели под два шаблона
Если по какой-то причине chat_template_kwargs не работает (например, в более старых версиях vLLM), можно модифицировать код модели, чтобы она принимала параметр thinking в запросе. Но это требует изменений в коде и перезагрузки модели.
Однако, на 28.02.2026, vLLM 0.5.7 и выше поддерживает chat_template_kwargs, так что этот способ предпочтительнее.
Нюансы, которые взорвут ваш сервер, если не знать
- Кэширование промптов: vLLM кэширует промпты для ускорения. Если вы отправляете одинаковые промпты с разными thinking_mode, они будут закэшированы отдельно. Это хорошо, но может занять память. Учитывайте это при настройке
--block-sizeи--enable-prefix-caching. - Производительность: Thinking режим генерирует более длинные ответы (из-за внутреннего диалога), что может снизить throughput. Настройте
--max-num-seqsи--max-model-lenсоответственно. - Ошибки памяти: Если вы используете Thinking режим для очень длинных контекстов, следите за памятью. Включите
--gpu-memory-utilizationи мониторьте использование VRAM. В крайнем случае, используйте CPU Offloading.
Частые ошибки и как их избежать
| Ошибка | Причина | Решение |
|---|---|---|
| Шаблон не применяется, всегда один режим | chat_template_kwargs не передаются в шаблон | Убедитесь, что в запросе передается chat_template_kwargs, а в шаблоне используется правильная переменная. |
| Утечка памяти при переключении режимов | Кэш промптов растет без ограничений | Настройте --enable-prefix-caching и ограничьте --block-size. |
| Низкая скорость обработки в Thinking режиме | Модель генерирует больше токенов | Увеличьте --max-num-batched-tokens и используйте более быстрые GPU, например, через облачные сервисы вроде Lambda Labs. |
FAQ: коротко о главном
Вопрос: Этот метод работает только для Qwen 3.5?
Ответ: Нет, он работает для любой модели, где chat template можно параметризовать. Например, для Llama 3.1 или Mistral. Но шаблон нужно адаптировать.
Вопрос: Сколько VRAM я экономлю?
Ответ: Вместо двух загрузок модели - одна. Для Qwen 3.5 32B в FP16 это экономия около 64 ГБ VRAM. Если использовать квантование, как в статье "Можно ли запустить локальную LLM на 10 ГБ видеопамяти?", то экономия еще значительнее.
Вопрос: Что если я хочу больше двух режимов?
Ответ: Добавьте больше параметров в chat_template_kwargs. Например, {"thinking_mode": true, "detailed": false}. Шаблон Jinja2 может обрабатывать несколько переменных.
Вопрос: А если vLLM не поддерживает chat_template_kwargs?
Ответ: Обновитесь до версии 0.5.7 или выше. Если не можете, придется модифицировать код модели или запускать два экземпляра с общими весами через техники оптимизации vLLM, например, используя общую память для весов.
Итог: больше не нужно жертвовать режимами из-за нехватки VRAM. Один vLLM, два режима - и все довольны.