/ 杂谈
谈谈Reasoning
从模型训练、reasoning token、vLLM serving、PD 分离和线上压测几个角度,解释 reasoning 到底改变了什么,又没有改变什么。
我一开始理解 reasoning 模型时,有一个很自然的误解:它是不是在普通 Transformer 外面又加了一个“推理模块”?
比如用户问一个数学题,普通模型直接回答;reasoning 模型先写一段分析,再给答案。直觉上很容易把这件事想成:模型内部多了某种符号推理机、搜索树、规划器,或者一个专门负责拆解问题的黑盒。
但真正落到推理系统里看,这个想法反而会让问题变复杂。
大多数 reasoning 模型在线上服务时,本质上仍然是熟悉的自回归生成:输入 prompt,prefill 建 KV cache,然后逐 token decode。它没有突然摆脱 next-token prediction,也没有让 Transformer decoder block 变成传统逻辑引擎。
更准确的理解是:
普通推理更像:直接回答。
reasoning 推理更像:先生成或维护一段中间思考轨迹,再基于这段轨迹输出最终答案。也就是说,底层计算框架没有被颠覆;真正变化的是训练方式、输出协议、推理预算和 serving 系统要面对的 workload 形态。
这篇文章就顺着这条线,把 reasoning 拆开看:它在模型里是什么,在训练里怎么来,在推理框架里怎么落地,又为什么会给 vLLM 这类 serving 系统带来明显的新压力。
1. reasoning 没有改变自回归生成的底座#
先把最底层的过程写出来。
普通 LLM 推理大致是:
输入 tokens
↓
Transformer forward
↓
得到下一个 token 的概率分布
↓
采样 / greedy / beam 等策略选出 token
↓
把新 token 拼回上下文
↓
继续 decodereasoning 模型仍然在做这件事。
区别不是它突然拥有了一个外置逻辑模块,而是它在训练后更倾向于生成这样的轨迹:
分析问题
↓
拆解步骤
↓
中间计算
↓
自我检查
↓
必要时修正
↓
最终回答所以从模型主体结构上看,它通常仍然是:
Embedding
↓
Transformer Decoder Blocks
↓
LM Head
↓
next token logitsDeepSeek-R1 的技术报告就是一个很典型的例子:它强调 reasoning 能力可以通过大规模强化学习被激发,模型会出现自我反思、验证和动态调整策略等行为;随后也可以把强模型的 long-CoT reasoning 能力蒸馏到更小的模型里。[1]
这里的关键词是“行为”。
reasoning 更像是一种被训练出来的生成行为,而不是一次推理请求里临时外挂的一段程序。
2. reasoning token 是什么#
普通推理里,我们通常只关心两类 token:
prompt tokens + answer tokensreasoning 推理里,输出部分被拆成了两段:
prompt tokens + reasoning tokens + final answer tokens这中间的 reasoning tokens,就是模型在最终答案之前生成或内部使用的中间推理轨迹。
2.1 显式 reasoning#
有些开源模型会直接把中间思考过程写出来,比如:
<think>
这里是模型的中间推理过程。
</think>
最终答案……这类 reasoning 其实就是普通文本 token,只是通过特殊标签和最终答案做了边界区分。
对于 vLLM 这类 OpenAI-compatible server,服务端通常需要一个 reasoning parser,把模型原始输出拆成结构化字段:
reasoning_content: 中间思考内容
content: 最终回答内容vLLM 文档里也把这类能力称为 reasoning outputs:指定模型对应的 reasoning parser 后,server 会从输出中提取 reasoning 内容和最终结论。[2]
也就是说,parser 不是让模型“开始思考”的东西。parser 只是把模型已经生成出来的 thinking 格式解析出来。
2.2 隐式 reasoning#
还有一些闭源 reasoning 模型不会把完整思考过程返回给用户。
用户看到的可能只有最终答案,或者一段很短的 reasoning summary;但模型在内部仍然可能消耗 reasoning tokens。OpenAI 的 reasoning API 文档就区分了 reasoning effort、reasoning tokens 和 reasoning summary;summary 需要显式请求才会返回。[3]
Azure OpenAI 文档也把 reasoning tokens 作为 completion token details 的一部分描述,它们会计入输出 token 使用量,但不会像普通消息内容一样直接展示给用户。[4]
所以不要因为用户看不到完整思考过程,就以为 reasoning 没有成本。
从 serving 系统视角看,只要模型多生成或内部使用了这些 token,它们就会消耗:
模型 forward
attention 计算
KV cache 写入
调度资源
端到端时间3. reasoning 能力主要来自 post-training#
如果只看推理阶段,很容易得到一个过度简化的结论:
reasoning = 多生成一段思考 token这个说法在 serving 成本上大体成立,但在模型能力来源上不够准确。
更完整的说法应该是:
Pretrain 提供知识和基础模式。
SFT 教模型怎么写推理过程。
RL 教模型什么样的推理过程更容易得到正确答案。
Distillation 把强模型的推理行为迁移给小模型。3.1 Pretrain 是地基#
预训练阶段主要还是 causal language modeling:给定前面的 token,预测下一个 token。
它会让模型学到语言、代码、数学、世界知识和大量文本模式。模型可能在预训练语料里见过很多推导、证明、代码调试和论文解释,所以会自然获得一些潜在推理能力。
但 pretrain 通常不会稳定地告诉模型:
遇到复杂问题时要先拆解。
中间步骤要检查。
答案前要验证。
简单问题不要过度思考。这些更像是 post-training 要塑造的行为。
3.2 SFT 教格式和习惯#
SFT 阶段可以给模型看高质量样本:
问题
+
标准推理过程
+
最终答案如果训练数据里包含:
<think>
先分析条件 A。
再推导条件 B。
检查是否矛盾。
</think>
最终答案是……那么 loss 会作用在这些 reasoning tokens 上。模型会学到:复杂问题后面应该更可能生成 thinking block,然后再给出 answer。
所以 SFT 很擅长教模型:
推理应该长什么样。但它不一定充分教会模型:
怎样推理才能做对。3.3 RL 强化有效性#
reasoning 模型里,RL 往往非常关键。
原因是 reasoning 的难点不只是“写出一段看起来像分析的话”,而是这段分析能不能真的提高正确率。
数学、代码、逻辑题尤其适合做这件事,因为 reward 可以相对明确:
数学题:最终答案是否正确。
代码题:单元测试是否通过。
选择题:选项是否正确。
格式题:是否满足可解析约束。DeepSeek-R1-Zero 直接从 base model 出发,通过大规模 RL 激发 reasoning 能力;但它也暴露了可读性差、语言混杂等问题,所以 DeepSeek-R1 又加入了 cold-start data 和多阶段训练。[1]
这个例子很好地说明了 SFT 和 RL 的分工:
SFT 让模型学会 reasoning 的形式。
RL 让模型学会 reasoning 的有效性。3.4 Distillation 把行为压缩出去#
强 reasoning 模型生成的高质量推理轨迹,还可以反过来作为训练数据,用来蒸馏更小的模型。
这也是为什么一些小尺寸 reasoning 模型虽然参数量不大,却能表现出明显的 long-CoT 行为。它们不一定是在 pretraining 阶段从零长出了这种习惯,而是从更强的 teacher model 那里学到了 reasoning 轨迹和回答模式。
4. 推理框架到底多了什么#
普通 serving 流程可以粗略写成:
request
↓
chat template
↓
tokenizer
↓
prefill
↓
decode answer tokens
↓
stream / return contentreasoning serving 则多了一层输出结构处理:
request
↓
chat template,可能开启 thinking mode
↓
tokenizer
↓
prefill
↓
decode reasoning tokens
↓
decode final answer tokens
↓
reasoning parser 拆分 reasoning_content 和 content
↓
stream / return structured response注意,这里真正改变的不是 prefill/decode 算子的数学形式,而是服务端协议和调度目标。
对 vLLM 这类框架来说,reasoning 场景通常要处理这些问题:
1. chat template 是否正确开启或关闭 thinking
2. sampling 参数是否允许足够长的输出
3. max_tokens 是否覆盖 reasoning + final answer
4. tokenizer 是否保留模型需要的 thinking 边界
5. reasoning parser 是否和模型格式匹配
6. streaming 时 reasoning 和 content 如何分别返回如果这些地方没有闭合,就会出现很多线上看起来很玄的问题:
<think> 内容直接泄露到最终 answer。
final answer 被误解析成 reasoning。
enable_thinking=False 但模型仍然输出 thinking。
structured output 和 reasoning parser 互相干扰。
offline inference 和 OpenAI server inference 行为不一致。我后来意识到,reasoning serving 的很多 bug 不在模型 forward 里,而在:
tokenizer chat template
+
reasoning parser
+
输出后处理状态机5. reasoning 的开关不是一个 flag#
线上控制 reasoning,至少有四层:
1. 模型 checkpoint 本身是否支持 thinking / non-thinking
2. chat template 是否注入 thinking 模式
3. server 是否启用 reasoning parser
4. parser 是否正确拆分 reasoning 和 final answer这四层只要有一层不一致,就会出问题。
5.1 先区分模型类型#
不能假设所有 reasoning 模型都能自由开关。
有些模型更像:
Thinking-only:只能开,不能真正关。
Instruct-only:只能关,不能真正开。
Hybrid:可以通过 chat template 参数切换。Qwen 文档就明确区分了不同 checkpoint 的模式:例如某些 Thinking 版本只支持 thinking mode,默认 chat template 会自动包含 thinking 相关格式;而 hybrid 模型可以通过 enable_thinking 切换。[5]
如果拿 thinking-only checkpoint 去设置 enable_thinking=False,它仍然可能输出 thinking。这不是某个参数没生效,而是模型和模板设计本来就不是为了 non-thinking 服务。
5.2 chat template 控制生成倾向#
对于 Qwen3 这类 hybrid thinking 模型,真正影响模型是否进入 thinking mode 的位置通常在 tokenizer 的 chat template。
Transformers 里可能是:
tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
enable_thinking=False,
)vLLM online serving 里,对应的思路是给 server 或 request 配置 chat template kwargs。vLLM 文档也说明,server 侧可以设置默认 chat template kwargs,而 request-level 参数可以覆盖默认值。[2]
所以生产环境里最好不要让业务方自由拼这些参数,而是维护一个 model profile:
MODEL_REASONING_PROFILE = {
"qwen3-hybrid": {
"mode": "hybrid",
"parser": "qwen3",
"thinking_kwarg": "enable_thinking",
"start": "<think>",
"end": "</think>",
},
"qwen3-thinking": {
"mode": "thinking_only",
"parser": "qwen3",
"start": "<think>",
"end": "</think>",
},
"qwen3-instruct": {
"mode": "non_thinking_only",
"parser": None,
},
}然后在网关层做强校验:
def build_chat_template_kwargs(profile, requested_reasoning: bool):
if profile["mode"] == "thinking_only" and not requested_reasoning:
raise ValueError("This model is thinking-only; reasoning cannot be disabled safely.")
if profile["mode"] == "non_thinking_only" and requested_reasoning:
raise ValueError("This model is non-thinking-only; reasoning cannot be enabled.")
if profile["mode"] == "hybrid":
return {profile["thinking_kwarg"]: requested_reasoning}
return {}这比在每个调用方里散落 enable_thinking、/think、/no_think 要稳定得多。
5.3 parser 只负责解析,不负责思考#
--reasoning-parser 很容易被误解成“开启 reasoning 的开关”。
它不是。
更准确地说:
chat template 控制模型是否更可能进入 thinking mode。
reasoning parser 控制输出如何被拆成 reasoning_content 和 content。如果只开 parser,但 chat template 没让模型输出 thinking 格式,parser 可能什么都拆不到。
如果 chat template 开了 thinking,但 parser 选错,比如 Qwen3 用了不匹配的 DeepSeek parser,就可能把 answer 切错。
6. answer 为什么会被吞进 think#
reasoning parser 最怕的一类问题是:开启 reasoning 后,最终答案被 parser 当成了 reasoning 的一部分。
理想输出应该是:
<think>
reasoning content
</think>
final answerparser 应该得到:
reasoning_content = reasoning content
content = final answer但如果模型没有正常生成 </think>,或者 max_tokens 在 thinking 阶段就耗尽了,输出可能变成:
<think>
reasoning content
final answer 也被继续写在这里这时 parser 看不到 reasoning end token,就可能把后面的内容全部归到 reasoning 里,导致 content 为空或不完整。
常见原因包括:
1. reasoning_end_str 没生成
2. max_tokens 太小,截断在 think 内部
3. parser 和模型格式不匹配
4. streaming parser 状态错乱
5. structured output / tool calling 与 reasoning parser 冲突
6. 模型本身输出格式不稳定所以生产环境里不能只写一个简单的:
text.split("</think>")streaming 场景下,</think> 可能被拆成多个 chunk:
chunk 1: </
chunk 2: think
chunk 3: >更稳的做法是状态机:
INIT
↓ 看到 reasoning_start_token
IN_REASONING
↓ 看到 reasoning_end_token
IN_ANSWER
↓ 结束
DONE对于流式输出,还要同时跟踪:
previous_text
current_text
delta_text
previous_token_ids
current_token_ids
delta_token_idsvLLM 的自定义 ReasoningParser 也正是把完整输出和流式输出分成不同提取逻辑来处理。[2]
所以 reasoning 开关和 answer 拆分的准确性,主要靠:
model-specific parser
+
token-level boundary
+
streaming state machine
+
budget control而不是靠模型 forward 自己保证。
7. 真正的系统代价:decode 变长#
从 AI Infra 角度看,reasoning 最大的变化不是“模型能不能推理”,而是输出长度和计算模式变了。
普通问答可能是:
输入 1k tokens
输出 200 tokensreasoning 请求可能变成:
输入 1k tokens
reasoning 2k tokens
最终答案 200 tokens对 LLM 推理来说,decode 是逐 token 生成的。每多生成一个 token,都要跑一轮模型 forward,并且要把新的 KV 写回 cache。
所以 reasoning tokens 会直接带来:
总输出 token 数增加
Decode step 数增加
TPOT / ITL 压力增加
端到端 latency 增加
单位时间吞吐下降
KV cache 占用增加这也是为什么 reasoning workload 在高并发下会比普通 chat 更难服务。
它不是偶尔多吐几句话,而是系统性地把请求变成了更长、更不可控、更 decode-heavy 的 workload。
8. TTFT 和 Time-to-Answer 要分开看#
普通推理里,用户看到第一个 token 通常就是答案开头,所以 TTFT 很直观。
reasoning 推理里,要拆开看:
TTFT:第一个 token 出来的时间
TTRT:第一个 reasoning token 出来的时间
TTAT:第一个 final answer token 出来的时间
E2E latency:完整响应结束时间如果服务端流式输出 reasoning,那么 TTFT 可能并不差。模型很快开始输出 thinking 内容,监控系统也会记录“首 token 已返回”。
但用户真正关心的 final answer 可能要等 reasoning 结束后才出现。
所以 reasoning 压测不能只看 TTFT。只看 TTFT 会误判用户体验:
TTFT 看起来正常,
但 first answer token latency 很差。我更倾向于把 reasoning 请求的 latency 指标拆成两组:
交互启动指标:TTFT / TTRT
答案可见指标:TTAT / E2E latency前者看服务有没有开始响应,后者才更接近用户什么时候拿到答案。
9. KV cache 压力会被 reasoning 放大#
普通推理的上下文长度大致是:
prompt length + answer lengthreasoning 推理的上下文长度则变成:
prompt length + reasoning length + answer lengthreasoning tokens 也会进入上下文。后续 final answer token 会 attend 到前面的 reasoning tokens。
因此它会带来更大的 KV cache 压力:
1. KV cache 占用增加
2. block manager 压力增加
3. 更容易发生 preemption / swap / recompute
4. max_num_seqs 需要更保守
5. gpu_memory_utilization 更敏感
6. 长尾请求更明显这对 vLLM 的 continuous batching、PagedAttention 和 block 分配策略都有直接影响。
在普通 chat workload 下能撑住的并发,换成 reasoning-heavy workload 后未必还能撑住。因为瓶颈不只是每秒 token 数,还有每个请求在系统里停留的时间、占住 KV block 的时间,以及长尾请求对 batch 的拖拽。
10. prefix cache 能帮 prefill,但救不了长 decode#
reasoning 场景下,prefix cache 仍然有价值。
如果多轮对话里系统 prompt、工具说明、长上下文前缀相同,prefix cache 可以减少 prefill 成本。
但 reasoning tokens 是每个请求动态生成的,通常不能跨请求复用。
所以:
prefix cache 主要优化 prompt / prefill。
它不能直接减少每次 reasoning decode 的生成成本。如果 workload 的主要成本来自 2k、4k 甚至更长的 reasoning 输出,那么 prefix cache 只能优化前半段,不能改变整体 decode-heavy 的事实。
这也是 reasoning serving 的一个核心判断:
长 prompt 场景:先看 prefill 和 prefix cache。
长 reasoning 场景:重点看 decode tokens/s、KV cache 和 scheduler。11. PD 分离下,reasoning 会把压力推向 D 侧#
普通 LLM 推理可以拆成:
Prefill:处理输入 prompt,建立初始 KV cache。
Decode:逐 token 生成输出,持续读取和追加 KV cache。reasoning 推理里,prefill 仍然只处理用户输入和历史上下文;真正被放大的,是 decode:
Decode = reasoning tokens + final answer tokens所以在 PD 分离系统里,reasoning 往往会让 D 侧更容易成为瓶颈。
普通推理中,如果 prompt 很长,prefill 压力会很明显;但 reasoning-heavy 场景里,即使 prompt 不长,decode 也可能因为大量 reasoning tokens 变得很重。
这会影响资源规划:
1. Decode worker 可能需要更多
2. Decode KV cache 容量要更大
3. Decode 队列要按 output length / reasoning effort 做更细调度
4. Prefill-Decode KV 传输成本占比可能下降
5. P95/P99 更容易被长 decode 请求拉高如果在双机 16 卡或者多机 vLLM serving 里部署 reasoning 模型,不能只看 prefill 吞吐。更应该看:
decode tokens/s
每请求 reasoning tokens 数量
平均 output length
P95/P99 decode latency
KV cache block 使用率
preemption 次数
GPU 利用率是否被长 decode 拉低12. 一个更工程化的 serving 方案#
如果要把 reasoning 模型稳定放到线上,我会把控制链路设计成这样:
请求进入网关
↓
识别 model profile
↓
判断 thinking_only / non_thinking_only / hybrid
↓
设置 chat_template_kwargs
↓
设置 reasoning_parser
↓
设置 thinking_token_budget
↓
设置 max_tokens = thinking_budget + answer_budget
↓
streaming parser 状态机拆分 reasoning / content
↓
输出前做 validation其中几个点尤其重要。
第一,max_tokens 不能只按最终答案估算。
错误理解:max_tokens = final answer budget
正确理解:max_tokens = reasoning budget + final answer budget第二,要限制 reasoning budget。
vLLM 文档里提到,部分模型支持 thinking budget control;当 reasoning token 计数达到预算后,系统可以强制生成 reasoning end string 来结束 reasoning block。[2]
第三,普通请求和 reasoning 请求最好分池压测。
它们对系统的压力形态完全不同。混在一起看一个平均 QPS,很容易掩盖问题:普通请求很快完成,reasoning 请求占着 KV block 和 decode batch 慢慢跑,最后 P99 被长尾拖坏。
第四,输出前要做 validation。
例如:
开启 reasoning,但没有看到 reasoning_end:重试或返回可控错误。
关闭 reasoning,但 content 出现 <think>:触发过滤或降级。
content 为空但 reasoning 很长:判定为 parser / budget 异常。
streaming 和 non-streaming 行为不一致:优先排查 parser 状态机。不要默默把全部内容都丢进 content,这样可能泄露 thinking;也不要默默把全部内容都丢进 reasoning_content,这样用户拿不到最终答案。
13. 我现在怎么理解 reasoning#
现在我更愿意用一个系统视角来概括 reasoning:
reasoning = 训练出来的长 CoT 生成行为
+ 推理时更大的 output token budget
+ 服务端对 reasoning_content 的解析
+ 更重的 decode workload它不是新的推理引擎形态,也不是一个简单的 prompt trick。
从模型训练看,它通常来自 post-training:SFT 教形式,RL 强化有效性,distillation 迁移行为。
从推理框架看,它仍然是 prefill + decode,只是 decode 更长,输出协议更复杂。
从 AI Infra 看,它最大的代价是:
输出 token 数变多
decode 时间变长
吞吐下降
P99 latency 变差
KV cache 占用变大
调度长尾更明显所以如果只问“reasoning 的底层是不是还是 Transformer 自回归生成”,答案是:大多数情况下,是。
但如果问“reasoning 对线上 serving 有没有本质影响”,答案也是:有,而且非常实际。
它把原来相对可控的 chat completion,变成了一个更长、更结构化、更难调度的 decode-heavy workload。
这可能也是 reasoning 最容易被低估的地方:用户看到的是“模型更会想了”,系统看到的是“每个请求都更久地留在 batch 里,更久地占着 KV cache,更难预测什么时候结束”。
最终可以用一句话收束:
Reasoning 没有改变 Transformer 推理的底座,
但它改变了模型生成的组织方式,
也改变了 serving 系统的成本结构。参考资料#
[1] DeepSeek-AI, DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning, arXiv:2501.12948. https://arxiv.org/abs/2501.12948
[2] vLLM Documentation, Reasoning Outputs. https://docs.vllm.ai/en/latest/features/reasoning_outputs/
[3] OpenAI Developers, Reasoning models. https://developers.openai.com/api/docs/guides/reasoning
[4] Microsoft Learn, Azure OpenAI reasoning models. https://learn.microsoft.com/en-us/azure/foundry/openai/how-to/reasoning
[5] Qwen Documentation, Quickstart. https://qwen.readthedocs.io/en/latest/getting_started/quickstart.html
[6] Qwen Team, Qwen3 Technical Report, arXiv:2505.09388. https://arxiv.org/abs/2505.09388