目录1 问题现象2 问题对比测试2.1 尝试关掉DSA验证没效果2.2 关掉MTP问题正常2.3 最终验证对比结果3 问题原因排查3.1 对比法对比sglang代码4 知识补充4.1 不开 MTP 时temperature 0 和 0.64.2 关掉 MTP和开着 MTP 且一次猜 2 个 token4.3 temperature 0 开着 MTP和 temperature 0.6 开着 MTP5 问题原因1 问题现象模型的输出存在轻微的思考重复问题2 问题对比测试2.1 尝试关掉DSA验证没效果2.2 关掉MTP问题正常2.3 最终验证对比结果temperature 0.6 开着 MTP 重复temperature 0.6 关掉 MTP 的两次完整结果不重复temperature 0 开着 MTP 不重复。可以发现当开着MTP并且temperature 0.6就存在这种问题了。3 问题原因排查3.1 对比法对比sglang代码由于sglang推理这个模型没有出现类似问题因此采用对比法查找问题原因然后发现sglang在MTP那里有个下面的代码if sampling_info.is_all_greedy or _is_npu or _is_hip: target_predict torch.argmax(next_token_logits, dim-1) verify_tree_greedy_func(...)然后其实他就是当开了MTP那么就算temperature不等于0他也还是用argmax的方式去取token那么我觉得问题在这里我去看了下vllm的代码发现了问题首先顺便介绍下基础知识4 知识补充这次服务的 MTP 配置是num_speculative_tokens2。草稿用的是模型里那一层 nextn同一层跑两次先猜第 1 个 token再按这个猜测猜第 2 个。主模型是前面 78 层的目标模型。4.1 不开 MTP 时temperature 0 和 0.6不开 MTP 时主模型一次前向只得到下一字的 logits。选出一个 token写进上下文再整网算一遍才能得到再下一个字。temperature 0 是贪心。代码看到 temperature 为 0直接用原始 logits不除 temperature不计算 softmax也不抽随机数。每个位置取分数最大的那个 token。temperature 0.6 会把这一处每个 token 的 logits 除以 0.6再做 softmax得到一组加起来等于 1 的概率然后按这个分布抽一个 token。分数最高的那个最常被抽到分数不是最高的那些也有机会。除以 0.6 不会让每个字的概率都变大。正的 logits 都会变大但 softmax 之后概率总和仍是 1。本来最高的那个字概率会变得更高本来较低的字概率会变得更低。例如「好」的 logits 是 3、「的」是 2softmax 后大约是 0.73 和 0.27。除以 0.6 之后 logits 变成 5 和 3.33概率大约变成 0.84 和 0.16。不开 MTP 时不是拿最高那个字的概率和一个随机数比。softmax 得到一整组概率之后是在这组概率里抽一个 token。直观上可以看成一条 0 到 1 的线段。每个 token 占的长度等于它的概率。再抽一个 0 到 1 之间均匀分布的随机数它落在谁的那一段就选谁。沿用上面的例子除以 0.6 并做完 softmax 之后「好」是 0.84「的」是 0.16。线段上 0 到 0.84 属于「好」0.84 到 1 属于「的」。随机数是 0.30落在「好」里这一步选「好」。随机数是 0.90落在「的」里这一步选「的」。抽很多次的话大约 84% 是「好」16% 是「的」。所以分数最高的那个最常出现其他字按自己的概率也会出现。vLLM 这份代码里的一种等价写法是每个 token 各抽一个指数分布随机数q用该 token 的概率除以自己的q再取比值最大的那个 token。效果和上面落在线段上是同一种抽样。probs logits.softmax(dim-1, dtypetorch.float32) q torch.empty_like(probs) q.exponential_() token probs.div(q).argmax(dim-1)4.2 关掉 MTP和开着 MTP 且一次猜 2 个 token关掉 MTP主模型一次前向只产出 1 个 token。下一步必须等这个 token 写进上下文之后才能再算。开着 MTP、一次猜 2 个时分两段。草稿先跑。它不看请求里的 temperature两次都取自己分数最大的 token得到猜测 1 和猜测 2。这一步比主模型便宜因为走的是那一层 nextn不是 78 层再完整跑两遍。然后主模型做一次前向。输入是「已有上下文 猜测 1 猜测 2」。这一遍同时得到三处 logits上下文末尾的 logits用来核对猜测 1。猜测 1 那个位置的 logits用来核对猜测 2。猜测 2 那个位置的 logits后面没有草稿了。两个猜测都被留下时这处直接产出第 3 个 token称为 bonus。所以num_speculative_tokens2时一次主模型前向最多写出 3 个 token。两个猜测都通过才有 bonus。第 1 个就没通过只留下主模型在这个位置给出的 token猜测 2 和 bonus 都不要。第 1 个通过、第 2 个没通过留下 2 个没有 bonus。4.3 temperature 0 开着 MTP和 temperature 0.6 开着 MTP草稿阶段两边相同都是取 nextn 自己的最大 token。差别只在主模型怎么验收。temperature 0 开着 MTP主模型这一处用原始 logits取分数最大的 token和草稿的 token 比编号。编号相同这个草稿 token 留下继续看下一个猜测。编号不同草稿 token 不留下这个位置改写成主模型分数最大的那个 token后面的猜测也不再留。两个猜测都相同bonus 同样取主模型分数最大的 token。这条路径没有随机数。最后写进上下文的每个 token都是主模型在该位置上分数最大的那个。temperature 0.6 开着 MTP主模型先把这一处每个 token 的 logits 除以 0.6再做 softmax。然后只取出草稿那个 token 编号在主模型里对应的概率。草稿是贪心的验收时它的概率按 1 算所以就是这个概率大于等于 0 到 1 之间的随机数就留下。概率够高就留下。这个 token 可以不是主模型分数最高的那个。概率不够就丢掉再从剩下的 token 里另抽一个后面的猜测也不再留。两个猜测都留下时bonus 也按 0.6 从分布里抽不是取最大的那个。因此 0.6 多出来的是主模型并没有把它当成最大 token、但概率仍然大于等于随机数的那些草稿 token。temperature 0 这种 token 进不了上下文。这次排查里整句重复出现在 0.6 开着 MTP 的时候0.6 关掉 MTP 的两次完整结果没有整句重复0 开着 MTP 也没有。5 问题原因开mtp temperature 0那么草稿模型生成了token然后主模型验证时选出来得分最高的token发现这个token和草稿模型的token一样才会采纳草稿模型的token这时候每次采纳的都是主模型的最大得分的那个开mtp temperature 0.6那么草稿模型产生token然后主模型也是验证做一次前向计算然后将logits除以0.6然后做softmax然后将草稿模型对应token序号对应的softmax得分跟随机数比较如果大于等于随机数就采纳相当于这时候可能草稿模型的这个token并不是主模型最大得分的但是他的得分大于随机数了就采纳了采纳的可以不是主模型得分最高的 token。