vLLM으로 Qwen 배포하기
vLLM으로 Qwen 배포하기
Qwen 배포에는 vLLM을 사용하는 것을 권장해요. vLLM은 사용하기 간단하면서도 최첨단 서빙 처리량, PagedAttention으로 어텐션 키·밸류 메모리를 효율적으로 관리하고, 입력 요청 연속 배칭(batching), 최적화된 CUDA 커널 등을 제공해 빠르답니다.
출처: 문서
본문
vLLM에 대해 더 알아보려면 논문과 문서를 참고하세요.
환경 설정
기본적으로 깨끗한 환경에서 pip으로 vllm을 설치할 수 있어요:
pip install "vllm>=0.8.5"
사전 빌드된 vllm은 torch와 그 CUDA 버전에 엄격한 의존성이 있다는 점을 유의하세요. 자세한 도움이 필요하면 공식 문서의 설치 안내(링크)를 참고하세요.
API 서비스
vLLM으로 OpenAI 호환 API 서비스를 만드는 것은 쉬워요. OpenAI API 프로토콜을 구현하는 서버로 배포할 수 있거든요. 기본적으로 서버는 http://localhost:8000에서 시작돼요. --host와 --port 인자로 주소를 지정할 수 있어요. 아래처럼 명령을 실행하세요:
vllm serve Qwen/Qwen3-8B
기본적으로 모델이 유효한 로컬 디렉터리를 가리키지 않으면 Hugging Face Hub에서 모델 파일을 다운로드해요. ModelScope에서 모델을 다운로드하려면 위 명령을 실행하기 전에 다음을 설정하세요:
export VLLM_USE_MODELSCOPE=true
텐서 병렬 분산 추론은 아래처럼 간단해요:
vllm serve Qwen/Qwen3-8B --tensor-parallel-size 4
위 명령은 4개의 GPU에서 텐서 병렬을 사용해요. 필요에 따라 GPU 수를 변경하면 돼요.
기본 사용법
그 다음 create chat 인터페이스로 Qwen과 통신할 수 있어요.
curl:
curl http://localhost:8000/v1/chat/completions -H "Content-Type: application/json" -d '{
"model": "Qwen/Qwen3-8B",
"messages": [
{"role": "user", "content": "Give me a short introduction to large language models."}
],
"temperature": 0.6,
"top_p": 0.95,
"top_k": 20,
"max_tokens": 32768
}'
Python:
아래처럼 openai Python SDK로 API 클라이언트를 사용할 수 있어요:
from openai import OpenAI
# Set OpenAI's API key and API base to use vLLM's API server.
openai_api_key = "EMPTY"
openai_api_base = "http://localhost:8000/v1"
client = OpenAI(
api_key=openai_api_key,
base_url=openai_api_base,
)
chat_response = client.chat.completions.create(
model="Qwen/Qwen3-8B",
messages=[
{"role": "user", "content": "Give me a short introduction to large language models."},
],
max_tokens=32768,
temperature=0.6,
top_p=0.95,
extra_body={
"top_k": 20,
},
)
print("Chat response:", chat_response)
💡 팁:
vllm은 모델 파일의generation_config.json에 있는 샘플링 파라미터를 사용해요. 기본 샘플링 파라미터가 thinking 모드에서 대부분 잘 동작하지만, 애플리케이션에 맞게 샘플링 파라미터를 조정하고 항상 API에 샘플링 파라미터를 전달하는 것을 권장해요.
Thinking & Non-Thinking 모드
Qwen3 모델은 응답하기 전에 먼저 생각해요. 이 동작은 thinking을 완전히 끄는 하드 스위치(hard switch)로 제어하거나, 모델이 사용자가 생각해야 하는지에 대한 지시를 따르는 소프트 스위치(soft switch)로 제어할 수 있어요.
하드 스위치는 vLLM에서 API 호출에 다음 설정으로 사용할 수 있어요. thinking을 끄려면:
curl:
curl http://localhost:8000/v1/chat/completions -H "Content-Type: application/json" -d '{
"model": "Qwen/Qwen3-8B",
"messages": [
{"role": "user", "content": "Give me a short introduction to large language models."}
],
"temperature": 0.7,
"top_p": 0.8,
"top_k": 20,
"max_tokens": 8192,
"presence_penalty": 1.5,
"chat_template_kwargs": {"enable_thinking": false}
}'
Python:
아래처럼 openai Python SDK로 API 클라이언트를 사용할 수 있어요 (enable_thinking=False):
from openai import OpenAI
# Set OpenAI's API key and API base to use vLLM's API server.
openai_api_key = "EMPTY"
openai_api_base = "http://localhost:8000/v1"
client = OpenAI(
api_key=openai_api_key,
base_url=openai_api_base,
)
chat_response = client.chat.completions.create(
model="Qwen/Qwen3-8B",
messages=[
{"role": "user", "content": "Give me a short introduction to large language models."},
],
max_tokens=8192,
temperature=0.7,
top_p=0.8,
presence_penalty=1.5,
extra_body={
"top_k": 20,
"chat_template_kwargs": {"enable_thinking": False},
},
)
print("Chat response:", chat_response)
📝 참고:
enable_thinking을 전달하는 것은 OpenAI API 호환 방식이 아니에요. 정확한 방법은 프레임워크마다 다를 수 있어요.
💡 팁: thinking을 완전히 끄려면 모델을 시작할 때 커스텀 채팅 템플릿을 사용하면 돼요:
vllm serve Qwen/Qwen3-8B --chat-template ./qwen3_nonthinking.jinja
이 채팅 템플릿은 사용자가 /think로 지시하더라도 모델이 thinking 콘텐츠를 생성하는 것을 막아요.
💡 팁: thinking 모드와 non-thinking 모드에 샘플링 파라미터를 다르게 설정하는 것을 권장해요.
Thinking 콘텐츠 파싱
vLLM은 모델 생성에서 thinking 콘텐츠를 구조화된 메시지로 파싱하는 것을 지원해요:
vllm serve Qwen/Qwen3-8B --enable-reasoning --reasoning-parser deepseek_r1
vLLM 0.9.0부터는 다음도 사용할 수 있어요:
vllm serve Qwen/Qwen3-8B --reasoning-parser qwen3
응답 메시지에는 content 외에 reasoning_content라는 필드가 생기며, 여기에 모델이 생성한 thinking 콘텐츠가 담겨요.
📝 참고: 이 기능은 OpenAI API 호환 방식이 아니에요.
⚠️ 중요: vLLM 0.8.5 기준으로
enable_thinking=False는 이 기능과 호환되지 않아요. API에enable_thinking=False를 전달해야 한다면 thinking 콘텐츠 파싱을 꺼야 해요. 이 문제는 vLLM 0.9.0의qwen3reasoning 파서에서 해결되었어요.
도구 호출 파싱
vLLM은 모델 생성에서 도구 호출 콘텐츠를 구조화된 메시지로 파싱하는 것을 지원해요:
vllm serve Qwen/Qwen3-8B --enable-auto-tool-choice --tool-call-parser hermes
자세한 내용은 우리의 함수 호출 가이드를 참고하세요.
구조화/JSON 출력
vLLM은 구조화/JSON 출력을 지원해요. guided_json 파라미터는 vLLM 문서를 참고하세요. 또한 시스템 메시지나 프롬프트에서 모델에 특정 형식을 생성하도록 지시하는 것도 권장해요.
양자화 모델 서빙
Qwen3에는 FP8과 AWQ 두 가지 유형의 사전 양자화 모델이 제공돼요. 이 모델들을 서빙하는 명령은 이름만 바뀌고 원본 모델과 동일해요:
# For FP8 quantized model
vllm serve Qwen/Qwen3-8B-FP8
# For AWQ quantized model
vllm serve Qwen/Qwen3-8B-AWQ
📝 참고: Qwen3의 FP8 모델은 블록 단위 양자화(block-wise quant)이며, 컴퓨트 능력이 8.9보다 큰 NVIDIA GPU, 즉 Ada Lovelace, Hopper 이후 GPU에서 지원되고 w8a8로 실행돼요. vLLM v0.9.0부터 FP8 Marlin이 블록 단위 양자화(w8a16으로 실행)를 지원하며 Ampere 카드에서도 Qwen3 FP8 모델을 실행할 수 있어요.
📝 참고: FP8 모델을 배포할 때 다음 오류를 만난다면, 텐서 병렬 크기가 모델 가중치와 일치하지 않는다는 뜻이에요:
File ".../vllm/vllm/model_executor/layers/quantization/fp8.py", line 477, in create_weights
raise ValueError(
ValueError: The output_size of gate's and up's weight = 192 is not divisible by weight quantization block_n = 128.
텐서 병렬의 정도를 낮추거나(예: --tensor-parallel-size 4), 전문가 병렬을 활성화하는 것(예: --tensor-parallel-size 8 --enable-expert-parallel)을 권장해요.
컨텍스트 길이
Qwen3 모델의 사전 훈련 컨텍스트 길이는 최대 32,768 토큰이에요. 32,768 토큰을 크게 초과하는 컨텍스트를 처리하려면 RoPE 스케일링 기법을 적용해야 해요. 모델 길이 외삽을 향상시키는 기법인 YaRN의 성능을 검증해서 긴 텍스트에서 최적의 성능을 보장했어요.
vLLM은 YaRN을 지원하며 다음처럼 설정할 수 있어요:
vllm serve Qwen/Qwen3-8B --rope-scaling '{"rope_type":"yarn","factor":4.0,"original_max_position_embeddings":32768}' --max-model-len 131072
📝 참고: vLLM은 정적 YaRN을 구현하므로 스케일링 인자가 입력 길이와 무관하게 일정하며, 이는 짧은 텍스트의 성능에 영향을 줄 수 있어요. 긴 컨텍스트 처리가 필요한 경우에만
rope_scaling설정을 추가하는 것을 권장해요. 필요에 따라factor를 수정하는 것도 권장해요. 예를 들어 애플리케이션의 일반적인 컨텍스트 길이가 65,536 토큰이라면factor를 2.0으로 설정하는 것이 좋아요.
📝 참고:
config.json의 기본max_position_embeddings는 40,960으로 설정되어 있으며,--max-model-len을 지정하지 않으면 vLLM이 이를 사용해요. 이 할당에는 출력용 32,768 토큰과 일반적인 프롬프트용 8,192 토큰이 포함되며, 대부분의 짧은 텍스트 처리 시나리오에 충분하고 모델 thinking에 충분한 여유를 남겨줘요. 평균 컨텍스트 길이가 32,768 토큰을 초과하지 않는다면, 이 시나리오에서는 YaRN을 활성화하지 않는 것을 권장해요. 모델 성능이 저하될 수 있기 때문이에요.
Python 라이브러리
vLLM은 Python 라이브러리로도 직접 사용할 수 있어요. 오프라인 배치 추론에 편리하지만, 모델 생성의 구조화 메시지 파싱 같은 일부 API 전용 기능은 부족해요.
다음은 vLLM을 라이브러리로 사용하는 기본 예시예요:
from transformers import AutoTokenizer
from vllm import LLM, SamplingParams
# Initialize the tokenizer
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-8B")
# Configurae the sampling parameters (for thinking mode)
sampling_params = SamplingParams(temperature=0.6, top_p=0.95, top_k=20, max_tokens=32768)
# Initialize the vLLM engine
llm = LLM(model="Qwen/Qwen3-8B")
# Prepare the input to the model
prompt = "Give me a short introduction to large language models."
messages = [
{"role": "user", "content": prompt}
]
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
enable_thinking=True, # Set to False to strictly disable thinking
)
# Generate outputs
outputs = llm.generate([text], sampling_params)
# Print the outputs.
for output in outputs:
prompt = output.prompt
generated_text = output.outputs[0].text
print(f"Prompt: {prompt!r}, Generated text: {generated_text!r}")
vLLM v0.9.0부터 chat_template_kwargs를 지원하는 LLM.chat 인터페이스도 사용할 수 있어요:
from vllm import LLM, SamplingParams
# Configurae the sampling parameters (for thinking mode)
sampling_params = SamplingParams(temperature=0.6, top_p=0.95, top_k=20, max_tokens=32768)
# Initialize the vLLM engine
llm = LLM(model="Qwen/Qwen3-8B")
# Prepare the input to the model
prompt = "Give me a short introduction to large language models."
messages = [
{"role": "user", "content": prompt}
]
# Generate outputs
outputs = llm.chat(
[messages],
sampling_params,
chat_template_kwargs={"enable_thinking": True}, # Set to False to strictly disable thinking
)
# Print the outputs.
for output in outputs:
prompt = output.prompt
generated_text = output.outputs[0].text
print(f"Prompt: {prompt!r}, Generated text: {generated_text!r}")
FAQ
꽤 짜증나는 OOM(메모리 부족) 문제를 만날 수 있어요. 두 가지 인자를 권장해요.
첫 번째는 --max-model-len이에요. 기본 max_position_embedding이 40960이라 서빙 최대 길이도 이 값이 되어 메모리 요구량이 높아져요. 여러분 자신에게 적절한 길이로 줄이면 OOM 문제에 자주 도움이 돼요.
또 하나 주목할 인자는 --gpu-memory-utilization이에요. vLLM은 이만큼의 GPU 메모리를 사전 할당해요. 기본값은 0.9예요. 그래서 vLLM 서비스는 항상 메모리를 많이 차지하는 거예요. eager 모드(기본값은 아님)라면 이를 높여 OOM 문제를 해결할 수 있어요. 그렇지 않으면 CUDA Graphs가 사용되며, 이는 vLLM이 제어하지 않는 GPU 메모리를 사용하므로 값을 낮춰야 해요. 그래도 안 되면 --enforce-eager(추론이 느려질 수 있음)를 시도하거나 --max-model-len을 줄여 보세요.
vLLM 사용법에 대한 더 자세한 안내는 vLLM의 Qwen3 사용 가이드를 참고하세요.