self-llm: Qwen3-8B — развёртывание и вызов через vLLM
Полный перевод рецепта self-llm «02-Qwen3-8B vLLM: развёртывание и вызов»: установка vLLM, скачивание Qwen3-8B, офлайн-генерация в режимах с «размышлением» и без, OpenAI-совместимый сервер, запросы через curl и Python — с примечаниями для локальной машины с 4 GPU.
Знакомство с vLLM
Заголовок раздела «Знакомство с vLLM»Фреймворк vLLM — эффективная система инференса и развёртывания больших языковых моделей. Его особенности:
- Эффективное управление памятью: алгоритм
PagedAttentionпозволяетvLLMэффективно управлятьKV-кэшем, меньше тратить память впустую и быстрее работать. - Высокая пропускная способность:
vLLMподдерживает асинхронную обработку и непрерывное пакетирование (continuous batching) запросов — пропускная способность инференса заметно выше, генерация и обработка текста быстрее. - Простота:
vLLMбесшовно работает с моделямиHuggingFace, поддерживает множество популярных больших языковых моделей и упрощает их развёртывание и инференс. Содержит API-сервер, совместимый сOpenAI. - Распределённый инференс: фреймворк поддерживает распределённый инференс на нескольких
GPU; стратегии параллелизма модели и эффективный обмен данными позволяют работать с большими моделями. - Открытость: благодаря открытому коду у
vLLMактивное сообщество, разработчикам легко вносить вклад и улучшения, вместе развивая технологию.
Подготовка окружения
Заголовок раздела «Подготовка окружения»Базовое окружение статьи:
----------------ubuntu 22.04python 3.12cuda 12.4pytorch 2.5.1----------------Предполагается, что у вас уже настроено окружение
Pytorch (cuda)выше; если нет — сначала установите его сами.
Сначала переключаем pip на зеркало для ускорения и ставим зависимости:
python -m pip install --upgrade pippip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
pip install modelscopepip 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на свой путь для моделей!
Подготовка кода
Заголовок раздела «Подготовка кода»Скрипт на Python
Заголовок раздела «Скрипт на Python»Создайте файл 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, SamplingParamsfrom transformers import AutoTokenizerimport osimport json
# При автоматическом скачивании модели использовать modelscope; иначе модель скачается с HuggingFaceos.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, SamplingParamsfrom transformers import AutoTokenizerimport osimport json
# При автоматическом скачивании модели использовать modelscope; иначе модель скачается с HuggingFaceos.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. Я могу отвечать на самые разные вопросы, писать тексты — истории, письма, сценарии, — а также рассуждать логически, понимать много языков, писать код и т. д. Я стремлюсь быть вашим самым надёжным помощником. Чем могу помочь?'Сервер с API, совместимым с OpenAI
Заголовок раздела «Сервер с API, совместимым с OpenAI»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:
from openai import OpenAIclient = 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:
from openai import OpenAIopenai_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-кэша).
От переводчика: как запустить на своей машине
Заголовок раздела «От переводчика: как запустить на своей машине»Что сделать руками:
- Поднимите сервер и подключите к нему агента или RAG из ступени 8 через
base_url="http://localhost:8000/v1"— код из урока 8.1.1 заработает без изменений. - Задайте один и тот же вопрос с
enable_thinking=TrueиFalseи сравнитеcompletion_tokensи качество ответа. - Измерьте пропускную способность: отправьте 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 |