리즈닝 출력
리즈닝 출력 (Reasoning Outputs)
DeepSeek R1 같은 리즈닝 모델(reasoning model) 은 최종 결론에 이르기 전에 사고 과정을 먼저 길게 생성하는 게 특징이에요. vLLM은 이런 모델들의 출력에서 사고(리즈닝) 단계와 최종 결론을 분리해서 받아볼 수 있게 지원해요. 이 페이지에서 리즈닝 출력을 사용하는 방법을 살펴볼게요.
리즈닝 필드란? (The reasoning field)
리즈닝 모델은 출력에 추가로 reasoning 필드를 반환해요. 이 필드는 최종 결론에 이르기까지의 사고 단계를 담고 있고, 일반 모델의 출력에는 없는 필드예요.
⚠️
reasoning은 예전에reasoning_content라고 불렸어요. 마이그레이션하려면reasoning_content를reasoning으로 직접 바꾸면 되는데, 클라이언트 코드도 함께 업데이트해야 해요. 그렇지 않으면reasoning이 채워져 있어도 클라이언트가 빈reasoning_content를 조용히 읽을 수 있으니까 주의하세요.
지원 모델 (Supported models)
현재 vLLM이 지원하는 리즈닝 모델은 다음과 같아요.
| 모델 시리즈 | 파서 이름 | 구조화 출력 지원 | 도구 호출 |
|---|---|---|---|
| Cohere Command A Reasoning | cohere_command3 | json, regex | ✅ |
| DeepSeek R1 시리즈 | deepseek_r1 | json, regex | ❌ |
| Gemma 4 시리즈 | gemma4 | json, regex | ✅ |
| DeepSeek-V3.1 | deepseek_v3 | json, regex | ❌ |
| ERNIE-4.5-VL 시리즈 | ernie45 | json, regex | ❌ |
| ERNIE-4.5-21B-A3B-Thinking | ernie45 | json, regex | ✅ |
| GLM-4.5 시리즈 | glm45 | json, regex | ✅ |
| Holo2 시리즈 | holo2 | json, regex | ✅ |
| Hunyuan A13B 시리즈 | hunyuan_a13b | json, regex | ✅ |
| IBM Granite 3.2 언어 모델 | granite | ❌ | ❌ |
| MiniMax-M2 | minimax_m2_append_think | json, regex | ✅ |
| Qwen3 시리즈 | qwen3 | json, regex | ✅ |
| QwQ-32B | deepseek_r1 | json, regex | ✅ |
참고: IBM Granite 3.2와 DeepSeek-V3.1의 리즈닝은 기본적으로 꺼져 있고, 켜려면
chat_template_kwargs에thinking=True를 넘겨야 해요. Qwen3 시리즈의 리즈닝은 기본적으로 켜져 있고, 끄려면enable_thinking=False를 넘기면 돼요. Gemma 4 리즈닝은 기본적으로 꺼져 있으며enable_thinking=True나reasoning_effort설정으로 켤 수 있어요. DeepSeek-V3.1의 도구 호출은 non-thinking 모드에서 지원돼요. Holo2 리즈닝은 기본적으로 켜져 있고, 끄려면thinking=False를 넘겨야 해요.
빠른 시작 (Quickstart)
리즈닝 모델을 쓰려면 chat completion 엔드포인트 요청 시 --reasoning-parser 플래그로 리즈닝 파서를 지정해야 해요. 이 플래그는 모델 출력에서 리즈닝 콘텐츠를 추출하는 데 쓸 파서를 지정합니다.
vllm serve deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B \
--reasoning-parser deepseek_r1
그다음 모델에 요청을 보내면 응답에 리즈닝 콘텐츠가 담겨 와요. reasoning 필드에는 최종 결론까지의 사고 단계가, content 필드에는 최종 결론이 들어 있어요.
스트리밍 채팅 완료 (Streaming chat completions)
리즈닝 모델에서도 스트리밍 채팅 완료를 지원해요. reasoning 필드는 채팅 완료 응답 청크의 delta 필드에서 확인할 수 있고, OpenAI Python 클라이언트가 스트리밍 출력에서 reasoning 속성을 공식 지원하지는 않으니 getattr로 존재 여부를 확인해야 해요.
도구 호출 (Tool calling)
도구 호출과 리즈닝 파서를 둘 다 켜면 리즈닝 콘텐츠도 사용할 수 있어요. 다만 도구 호출은 reasoning이 아니라 content 필드에서만 함수를 파싱해요.
서버 수준 기본값 설정 (Server-level defaults)
--default-chat-template-kwargs CLI 인자로 모든 요청에 적용될 기본 chat_template_kwargs를 서버 수준에서 설정할 수 있어요.
기본적으로 사고 모드 끄기 (Disabling thinking by default)
Qwen3처럼 사고 모드가 기본으로 켜진 모델은 서버 전체에서 끌 수 있어요.
vllm serve Qwen/Qwen3-8B \
--reasoning-parser qwen3 \
--default-chat-template-kwargs '{"enable_thinking": false}'
기본적으로 사고 모드 켜기 (Enabling thinking by default)
IBM Granite 3.2나 DeepSeek-V3.1처럼 사고 모드가 기본으로 꺼진 모델은 서버 전체에서 켤 수 있어요.
vllm serve ibm-granite/granite-3.2-2b-instruct \
--reasoning-parser granite \
--default-chat-template-kwargs '{"thinking": true}'
요청 수준 오버라이드 (Request-level override)
요청 수준의 chat_template_kwargs는 항상 서버 기본값보다 우선해요. 서버가 enable_thinking=false로 시작했어도 클라이언트는 특정 요청에서 다시 켤 수 있죠.
response = client.chat.completions.create(
model=model,
messages=messages,
extra_body={"chat_template_kwargs": {"enable_thinking": True}} # 서버 기본값을 오버라이드
)
사고 예산 제어 (Thinking budget control)
Qwen3, DeepSeek, Nemotron3 같은 일부 모델은 리즈닝에 쓰는 최대 토큰 수를 제한하는 사고 예산(thinking budget) 을 지원해요.
토큰 카운트는 reasoning_start_str부터 시작해요. 리즈닝 토큰 수가 설정된 thinking_token_budget에 도달하면 vLLM이 모델로 하여금 reasoning_end_str을 생성하게 강제해서 리즈닝 블록을 종료시켜요.
이 기능을 쓰려면:
--reasoning-parser가 리즈닝 추출을 활성화하고,--reasoning-config가 리즈닝 경계 토큰(예:reasoning_start_str,reasoning_end_str)을 정의해요. 설정하지 않으면 vLLM이 리즈닝 파서에서 이 토큰들을 자동 초기화하려고 해요.thinking_token_budget(sampling 파라미터)이 요청별 리즈닝 토큰 한도를 설정해요.
--reasoning-config는 다음 필드를 가진 ReasoningConfig에 대응하는 JSON 객체를 받아요.
| 필드 | 타입 | 설명 |
|---|---|---|
| reasoning_start_str | str | null | 리즈닝 콘텐츠의 시작을 표시하는 문자열 |
| reasoning_end_str | str | null | 리즈닝 콘텐츠의 끝을 표시하는 문자열 |
온라인 서빙 (Online serving)
vllm serve Qwen/Qwen3-0.6B \
--reasoning-parser qwen3 \
--reasoning-config '{"reasoning_start_str": " thinking", "reasoning_end_str": "I have to give the solution based on the reasoning directly now. response"}'
그다음 thinking_token_budget으로 리즈닝 토큰을 제한해 요청을 보내요.
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen3-0.6B",
"messages": [
{ "role": "user", "content": "9.11 and 9.8, which is greater?" }
],
"thinking_token_budget": 10
}'
오프라인 추론 (Offline inference)
from vllm import LLM, SamplingParams
from vllm.config import ReasoningConfig
llm = LLM(
model="Qwen/Qwen3-0.6B",
reasoning_config=ReasoningConfig(
reasoning_start_str=" thinking",
reasoning_end_str="I have to give the solution based on the thinking directly now. response",
),
)
sampling_params = SamplingParams(thinking_token_budget=10)
messages = [
{"role": "user", "content": "9.11 and 9.8, which is greater?"},
]
outputs = llm.chat(messages, sampling_params=sampling_params)
for output in outputs:
print("text:", output.outputs[0].text)
enable_thinking 자동 활성화 (Automatic enable_thinking)
Gemma 4, DeepSeek-V4-Pro, IBM Granite 3.2 같은 일부 모델은 사고 모드를 켜려면 chat template kwargs에 enable_thinking: true가 필요해요 — 없으면 다른 설정과 무관하게 리즈닝 토큰이 절대 생성되지 않죠.
Chat Completions 요청에서 reasoning_effort를 설정하면(또는 Responses API 요청에서 reasoning.effort), vLLM이 chat template kwargs에 enable_thinking을 자동으로 주입해요.
reasoning_effort = "low","medium","high"→enable_thinking = truereasoning_effort = "none"→enable_thinking = falsereasoning_effort미설정 →enable_thinking주입 안 함(기존 동작 유지)
즉 reasoning_effort를 쓸 때는 chat_template_kwargs: {"enable_thinking": true}를 수동으로 넘길 필요가 없어요.
참고:
chat_template_kwargs에서enable_thinking을 명시적으로 설정하면 자동 주입보다 우선해요.
리즈닝 출력 억제 (Suppressing reasoning output)
include_reasoning 파라미터로 API 응답에서 리즈닝 콘텐츠를 생략할 수 있어요. false로 설정하면 리즈닝 토큰은 여전히 생성되지만(모델 품질은 영향 없음) 응답에서 제외돼요. 네트워크 트래픽을 줄이면서도 추론 동작은 바꾸지 않는 방법이죠. 이 파라미터는 Chat Completions API와 Responses API 양쪽, 스트리밍·비스트리밍을 모두 지원해요.
include_reasoning=false면 vLLM은 토큰별 메타데이터(logprobs와 토큰 ID)도 함께 억제해서, 디코드된 토큰 텍스트나 원시 토큰 ID로 리즈닝 콘텐츠가 새어 나가는 걸 막아요.
Chat Completions API
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY")
model = client.models.list().data[0].id
# 리즈닝은 기본 포함 (include_reasoning=True)
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": "What is 15 * 37?"}],
extra_body={"include_reasoning": False},
)
msg = response.choices[0].message
assert msg.content # 콘텐츠는 여전히 존재
assert not getattr(msg, "reasoning", None) # 리즈닝은 억제됨
Responses API
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY")
response = client.responses.create(
model=client.models.list().data[0].id,
input="What is 15 * 37?",
include_reasoning=False,
)
# output에 "reasoning" 항목 없음
types = [item.type for item in response.output]
assert "reasoning" not in types
제한 사항 (Limitations)
- 리즈닝 콘텐츠는 온라인 서빙의 chat completion 엔드포인트(
/v1/chat/completions), Anthropic Messages API(/v1/messages), Responses API(/v1/responses)에서만 사용할 수 있어요.
새 리즈닝 모델 지원하기 (Supporting a new reasoning model)
vllm/reasoning/deepseek_r1_reasoning_parser.py와 비슷한 새 ReasoningParser를 추가하면 돼요. 구조화 출력을 활성화하려면 같은 파일의 Reasoner 비슷한 클래스를 새로 만들어야 해요. 마지막으로 --reasoning-parser 플래그로 모델의 리즈닝을 켤 수 있어요.
vllm serve <model_tag> --reasoning-parser example