Перейти к содержимому
ПереводыDatawhale и китайские курсы

self-llm: Qwen3-8B — развёртывание и вызов через vLLM

Полный перевод рецепта self-llm «02-Qwen3-8B vLLM: развёртывание и вызов»: установка vLLM, скачивание Qwen3-8B, офлайн-генерация в режимах с «размышлением» и без, OpenAI-совместимый сервер, запросы через curl и Python — с примечаниями для локальной машины с 4 GPU.

Фреймворк vLLM — эффективная система инференса и развёртывания больших языковых моделей. Его особенности:

  • Эффективное управление памятью: алгоритм PagedAttention позволяет vLLM эффективно управлять KV-кэшем, меньше тратить память впустую и быстрее работать.
  • Высокая пропускная способность: vLLM поддерживает асинхронную обработку и непрерывное пакетирование (continuous batching) запросов — пропускная способность инференса заметно выше, генерация и обработка текста быстрее.
  • Простота: vLLM бесшовно работает с моделями HuggingFace, поддерживает множество популярных больших языковых моделей и упрощает их развёртывание и инференс. Содержит API-сервер, совместимый с OpenAI.
  • Распределённый инференс: фреймворк поддерживает распределённый инференс на нескольких GPU; стратегии параллелизма модели и эффективный обмен данными позволяют работать с большими моделями.
  • Открытость: благодаря открытому коду у vLLM активное сообщество, разработчикам легко вносить вклад и улучшения, вместе развивая технологию.

Базовое окружение статьи:

----------------
ubuntu 22.04
python 3.12
cuda 12.4
pytorch 2.5.1
----------------

Предполагается, что у вас уже настроено окружение Pytorch (cuda) выше; если нет — сначала установите его сами.

Сначала переключаем pip на зеркало для ускорения и ставим зависимости:

Окно терминала
python -m pip install --upgrade pip
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
pip install modelscope
pip install vllm

Поскольку у некоторых при настройке окружения бывают проблемы, мы подготовили на платформе AutoDL образ окружения для Qwen3: перейдите по ссылке ниже и сразу создайте экземпляр AutoDL. https://www.codewithgpu.com/i/datawhalechina/self-llm/Qwen3

Скачиваем модель функцией snapshot_download из modelscope: первый параметр — имя модели, параметр cache_dir — путь для скачивания.

Создайте файл model_download.py, вставьте в него код ниже и не забудьте сохранить.

from modelscope import snapshot_download
model_dir = snapshot_download('Qwen/Qwen3-8B', cache_dir='/root/autodl-tmp', revision='master')

Затем выполните в терминале python model_download.py и терпеливо подождите, пока модель скачается.

Внимание: не забудьте заменить cache_dir на свой путь для моделей!

Создайте файл vllm_model.py, вставьте в него код ниже и сразу сохраните. В коде подробные комментарии; если что-то непонятно — открывайте issue.

Сначала импортируем из библиотеки vLLM классы LLM и SamplingParams. LLM — главный класс для офлайн-инференса движком vLLM. SamplingParams задаёт параметры выборки — они управляют случайностью и разнообразием сгенерированного текста.

vLLM очень удобно всё оборачивает: достаточно передать имя модели или путь к ней, инициализировать модель и токенизатор вручную не нужно.

Затем функцией токенизатора apply_chat_template превращаем нашу подсказку (prompt) во входной формат, который нужен модели.

По умолчанию у Qwen3 включён режим размышления (thinking), как у QwQ-32B. Это значит, что модель будет использовать свои способности к рассуждению, чтобы повысить качество ответа. Например, если в tokenizer.apply_chat_template явно задать enable_thinking=True или оставить значение по умолчанию, модель войдёт в режим размышления.

На этом примере можно познакомиться с работой движка vLLM. Закомментированные части расширяют возможности модели, но не обязательны — выбирайте по необходимости и пробуйте сами ~

from vllm import LLM, SamplingParams
from transformers import AutoTokenizer
import os
import json
# При автоматическом скачивании модели использовать modelscope; иначе модель скачается с HuggingFace
os.environ['VLLM_USE_MODELSCOPE']='True'
def get_completion(prompts, model, tokenizer=None, temperature=0.6, top_p=0.95, top_k=20, min_p=0, max_tokens=4096, max_model_len=8192):
stop_token_ids = [151645, 151643]
# Параметры выборки. temperature управляет разнообразием текста, top_p — вероятностью «ядра» при nucleus-выборке,
# top_k ограничивает число кандидатов и так влияет на качество и разнообразие, min_p отсекает кандидатов по
# порогу вероятности — сохраняет качество и при этом добавляет разнообразия
sampling_params = SamplingParams(temperature=temperature, top_p=top_p, top_k=top_k, min_p=min_p, max_tokens=max_tokens, stop_token_ids=stop_token_ids) # max_tokens ограничивает максимальную длину ответа модели
# Инициализируем движок инференса vLLM
llm = LLM(model=model, tokenizer=tokenizer, max_model_len=max_model_len,trust_remote_code=True) # max_model_len ограничивает суммарную длину входа и выхода, которую модель может обработать
outputs = llm.generate(prompts, sampling_params)
return outputs
if __name__ == "__main__":
# Инициализируем движок инференса vLLM
model='/root/autodl-tmp/Qwen/Qwen3-8B' # путь к модели
tokenizer = AutoTokenizer.from_pretrained(model, use_fast=False) # загружаем токенизатор
prompt = "Дай мне краткое введение в большие модели."
messages = [
{"role": "user", "content": prompt}
]
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
enable_thinking=True # включать ли режим размышления; по умолчанию True
)
outputs = get_completion(text, model, tokenizer=None, temperature=0.6, top_p = 0.95, top_k=20, min_p=0) # для режима размышления официально рекомендованы: temperature = 0.6, TopP = 0.95, TopK = 20, MinP = 0.
# Результат — список объектов RequestOutput с подсказкой, сгенерированным текстом и другой информацией.
# Печатаем результат.
for output in outputs:
prompt = output.prompt
generated_text = output.outputs[0].text
print(f"Prompt: {prompt!r}, \nResponse: {generated_text!r}")

Запускаем код:

Окно терминала
python vllm_model.py

Результат такой (ответ модели сокращён и переведён переводчиком):

Окно терминала
Prompt: '<|im_start|>user\nДай мне краткое введение в большие модели.<|im_end|>\n<|im_start|>assistant\n',
Response: '<think>\nХорошо, пользователь просит краткое введение в большие модели. Сначала нужно понять, что имеется в виду под «большой моделью». … Так пользователь быстро ухватит главное.\n</think>\n\nБольшие модели (Large Language Models, LLMs) — передовая технология ИИ на основе глубокого обучения: обученные на огромных объёмах текста, они хорошо понимают и порождают язык. Обычно у них от нескольких миллиардов до сотен миллиардов параметров; они решают сложные задачи — генерацию текста, ответы на вопросы, перевод, программирование — и широко применяются в умных помощниках, создании контента, анализе данных, заметно повышая эффективность и «интеллектуальность» взаимодействия человека с машиной.'

Что в сокращённой части размышления: «Возможно, пользователь плохо знаком с ИИ, значит, надо объяснить базовые понятия… Нужно ли упомянуть недостатки — высокие требования к вычислениям? Пользователь просил кратко, так что, пожалуй, не нужно… Сначала определение, потом особенности, области применения, и в конце вывод о преимуществах».

Ответ модели состоит из двух частей: процесса размышления, обёрнутого в <think> и </think>, и окончательного ответа после </think>.

Если режим размышления не нужен, достаточно выставить параметр enable_thinking в False. Например:

from vllm import LLM, SamplingParams
from transformers import AutoTokenizer
import os
import json
# При автоматическом скачивании модели использовать modelscope; иначе модель скачается с HuggingFace
os.environ['VLLM_USE_MODELSCOPE']='True'
def get_completion(prompts, model, tokenizer=None, temperature=0.6, top_p=0.95, top_k=20, min_p=0, max_tokens=4096, max_model_len=8192):
stop_token_ids = [151645, 151643]
# Параметры выборки.
sampling_params = SamplingParams(temperature=temperature, top_p=top_p, top_k=top_k, min_p=min_p, max_tokens=max_tokens, stop_token_ids=stop_token_ids)
# Инициализируем движок инференса vLLM
llm = LLM(model=model, tokenizer=tokenizer, max_model_len=max_model_len,trust_remote_code=True)
outputs = llm.generate(prompts, sampling_params)
return outputs
if __name__ == "__main__":
# Инициализируем движок инференса vLLM
model='/root/autodl-tmp/Qwen/Qwen3-8B' # путь к модели
tokenizer = AutoTokenizer.from_pretrained(model, use_fast=False) # загружаем токенизатор
prompt = "Кто ты?"
messages = [
{"role": "user", "content": prompt}
]
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
enable_thinking=False # включать ли режим размышления; по умолчанию True
)
outputs = get_completion(text, model, tokenizer=None, temperature=0.7, top_p = 0.8, top_k=20, min_p=0) # для режима без размышления официально рекомендованы: temperature = 0.7, TopP = 0.8, TopK = 20, MinP = 0.
# Результат — список объектов RequestOutput с подсказкой, сгенерированным текстом и другой информацией.
# Печатаем результат.
for output in outputs:
prompt = output.prompt
generated_text = output.outputs[0].text
print(f"Prompt: {prompt!r},Response: {generated_text!r}")

Результат такой (переведён):

Окно терминала
Prompt: '<|im_start|>user\nКто ты?<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n',
Response: 'Я Тунъи Цяньвэнь (Qwen), сверхбольшая языковая модель, созданная лабораторией Tongyi. Я могу отвечать на самые разные вопросы, писать тексты — истории, письма, сценарии, — а также рассуждать логически, понимать много языков, писать код и т. д. Я стремлюсь быть вашим самым надёжным помощником. Чем могу помочь?'

Qwen3-8B совместима с протоколом OpenAI API, поэтому через vLLM можно сразу создать сервер OpenAI API. Развернуть в vLLM сервер с протоколом OpenAI API очень просто. По умолчанию сервер запускается на http://localhost:8000. Сервер обслуживает одну модель за раз и реализует эндпоинты списка моделей, completions и chat completions.

  • completions — базовая генерация текста: модель продолжает заданную подсказку. Обычно используется, чтобы писать статьи, истории, письма и т. п.
  • chat completions — диалоговая задача: модель должна понимать и вести диалог. Обычно используется для чат-ботов и диалоговых систем.

При создании сервера можно задать имя модели, путь к ней, шаблон диалога и другие параметры.

  • --host и --port задают адрес.
  • --model задаёт имя модели.
  • --chat-template задаёт шаблон диалога.
  • --served-model-name задаёт имя, под которым сервер отдаёт модель.
  • --max-model-len задаёт максимальную длину для модели.
  • --enable-reasoning включает режим размышления.
  • --reasoning-parser задаёт, как разбирать порождённые моделью рассуждения. При --enable-reasoning параметр --reasoning-parser обязателен. Модель-«рассуждатель» добавляет в выход дополнительное поле reasoning_content — шаги рассуждения, приведшие к итоговому выводу. Подходящий парсер правильно извлекает и форматирует это содержимое. Например, парсер deepseek_r1 подходит для моделей серии DeepSeek R1 и разбирает содержимое формата <think> ... </think>.

Скопируйте в терминал команду ниже — и API модели Qwen3-8B запущен:

Окно терминала
VLLM_USE_MODELSCOPE=true vllm serve /root/autodl-tmp/Qwen/Qwen3-8B --served-model-name Qwen3-8B --max_model_len 8192 --enable-reasoning --reasoning-parser deepseek_r1

Если после загрузки появилось сообщение, как на рисунке ниже, — сервис успешно запущен.

Снимок экрана в оригинале (images/02-01.png): лог vLLM, заканчивающийся строками о запуске сервера Uvicorn на порту 8000.

  • Смотрим список моделей командой curl:
Окно терминала
curl http://localhost:8000/v1/models

Получаем такой ответ:

{
"object": "list",
"data": [
{
"id": "Qwen3-8B",
"object": "model",
"created": 1745950421,
"owned_by": "vllm",
"root": "/root/autodl-tmp/Qwen/Qwen3-8B",
"parent": null,
"max_model_len": 8192,
"permission": [
{
"id": "modelperm-3f20f566536d445cbfbf5a9ddb115204",
"object": "model_permission",
"created": 1745950421,
"allow_create_engine": false,
"allow_sampling": true,
"allow_logprobs": true,
"allow_search_indices": false,
"allow_view": true,
"allow_fine_tuning": false,
"organization": "*",
"group": null,
"is_blocking": false
}
]
}
]
}
  • Проверяем OpenAI Completions API командой curl:
Окно терминала
curl http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen3-8B",
"prompt": "Хочу спросить: чему равен факториал 5?<think>\n",
"max_tokens": 1024,
"temperature": 0
}'

(В конце подсказки сразу стоит открывающий тег <think>, чтобы модель начала с размышления.)

Получаем такой ответ (текст сокращён и переведён переводчиком):

{
"id": "cmpl-fb7fa8e981164942b1e126afe43b2acf",
"object": "text_completion",
"created": 1745950506,
"model": "Qwen3-8B",
"choices": [
{
"index": 0,
"text": "Так, мне нужно посчитать, чему равен факториал 5. Сначала вспомню определение факториала. … В общем, после нескольких проверок считаю, что факториал 5 равен 120.\n</think>\n\nФакториал 5 (обозначается 5!) получается перемножением всех целых положительных чисел от 1 до 5. … Итак, **факториал 5 равен 120**.\n\n**Ответ: 120**",
"logprobs": null,
"finish_reason": "stop",
"stop_reason": null,
"prompt_logprobs": null
}
],
"usage": {
"prompt_tokens": 12,
"total_tokens": 797,
"completion_tokens": 785,
"prompt_tokens_details": null
}
}

Что в сокращённой части размышления: «…произведение чисел от 1 до самого числа… 5×4 = 20, 20×3 = 60, 60×2 = 120, 120×1 = 120… Проверю другим способом: 4! = 24, 5×24 = 120…».

  • Запрос к OpenAI Completions API скриптом на Python:
vllm_openai_completions.py
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="sk-xxx", # любое значение — нужно только чтобы пройти проверку параметров
)
completion = client.chat.completions.create(
model="Qwen3-8B",
messages=[
{"role": "user", "content": "Хочу спросить: чему равен факториал 5?<think>\n"}
]
)
print(completion.choices[0].message)
Окно терминала
python vllm_openai_completions.py

Получаем такой ответ (сокращён и переведён переводчиком):

ChatCompletionMessage(content='\n\nФакториал 5 (обозначается 5!) — результат перемножения всех целых положительных чисел от 1 до 5. Вычисление:\n\n$$\n5! = 5 \\times 4 \\times 3 \\times 2 \\times 1 = 120\n$$\n\n**Ответ:** факториал 5 равен **120**.', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=[], reasoning_content='\nПользователь спрашивает, чему равен факториал 5. Сначала уточню определение факториала. … Для размышления этого достаточно.\n')

Что в сокращённой части reasoning_content: «…Возможно, это школьник, который только начал изучать математику…».

  • Проверяем OpenAI Chat Completions API командой curl:
Окно терминала
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen3-8B",
"messages": [
{"role": "user", "content": "Хочу спросить: чему равен факториал 5?<think>\n"}
]
}'

Получаем такой ответ (сокращён и переведён переводчиком):

{
"id": "chatcmpl-ebe5eb4e638449dc83d628175e540365",
"object": "chat.completion",
"created": 1745950682,
"model": "Qwen3-8B",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"reasoning_content": "\nПользователь спрашивает, чему равен факториал 5. … В общем, ответ должен быть 120.\n",
"content": "\n\nФакториал 5 (обозначается 5!) — это 5×4×3×2×1, результат:\n\n**5! = 5 × 4 × 3 × 2 × 1 = 120**\n\nИтак, факториал 5 равен **120**.",
"tool_calls": []
},
"logprobs": null,
"finish_reason": "stop",
"stop_reason": null
}
],
"usage": {
"prompt_tokens": 20,
"total_tokens": 511,
"completion_tokens": 491,
"prompt_tokens_details": null
},
"prompt_logprobs": null
}
  • Запрос к OpenAI Chat Completions API скриптом на Python:
vllm_openai_chat_completions.py
from openai import OpenAI
openai_api_key = "sk-xxx" # любое значение — нужно только чтобы пройти проверку параметров
openai_api_base = "http://localhost:8000/v1"
client = OpenAI(
api_key=openai_api_key,
base_url=openai_api_base,
)
chat_outputs = client.chat.completions.create(
model="Qwen3-8B",
messages=[
{"role": "user", "content": "Что такое глубокое обучение?"},
]
)
print(chat_outputs)
Окно терминала
python vllm_openai_chat_completions.py

Получаем такой ответ (сокращён и переведён переводчиком):

ChatCompletion(id='chatcmpl-301a933793e44f1abc01483a52a076a4', choices=[Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='\n\nГлубокое обучение (Deep Learning) — подобласть **машинного обучения**, … стало одной из ключевых технологий искусственного интеллекта.', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=[], reasoning_content='\nПользователь спрашивает: «Что такое глубокое обучение?». … Так логика будет ясной, и пользователь разберётся шаг за шагом.\n'), stop_reason=None)], created=1745950745, model='Qwen3-8B', object='chat.completion', service_tier=None, system_fingerprint=None, usage=CompletionUsage(completion_tokens=1390, prompt_tokens=12, total_tokens=1402, completion_tokens_details=None, prompt_tokens_details=None), prompt_logprobs=None)

Что в сокращённых частях: ответ — развёрнутый текст с разделами «Основные понятия» (нейросеть, глубина, автоматическое извлечение признаков), «Ключевые особенности», «Типичные применения» (компьютерное зрение, NLP, речь, рекомендации, обучение с подкреплением), «Преимущества и трудности» и таблицей «Отличия от классического машинного обучения»; размышление — план ответа: «Пользователь, видимо, только знакомится с понятием… Начну с определения, затем структура, особенности, применения, плюсы и минусы, вывод».

Кроме того, во время обработки всех этих запросов бэкенд API печатает соответствующие логи и статистику:

Снимок экрана в оригинале (images/02-02.png): лог сервера vLLM — строки о принятых запросах и периодическая статистика пропускной способности (токенов в секунду на префилле и генерации, загрузка KV-кэша).

От переводчика: как запустить на своей машине

Заголовок раздела «От переводчика: как запустить на своей машине»

Что сделать руками:

  1. Поднимите сервер и подключите к нему агента или RAG из ступени 8 через base_url="http://localhost:8000/v1" — код из урока 8.1.1 заработает без изменений.
  2. Задайте один и тот же вопрос с enable_thinking=True и False и сравните completion_tokens и качество ответа.
  3. Измерьте пропускную способность: отправьте 1, 8 и 32 запроса одновременно (asyncio или vllm bench serve) и посмотрите на строку статистики в логе сервера — это наглядная демонстрация continuous batching из урока 7.1.2.
РусскийПояснение
инференс (inference)получение ответов от обученной модели
развёртывание и вызовзапустить сервис и обращаться к нему
непрерывное пакетирование (continuous batching)запросы добавляются в батч «на лету»
пропускная способность (throughput)токенов в секунду на весь сервер
параметры выборки (sampling parameters)temperature, top_p, top_k, min_p
режим размышления (thinking mode)ответ с блоком <think>
парсер рассуждений (reasoning parser)отделяет reasoning_content от content
шаблон диалога (chat template)ChatML: <|im_start|>роль … <|im_end|>
факториал5! = 120