扩散语言模型dLLM这两年在推理侧的讨论越来越多尤其是 SGLang 这类高性能推理框架开始把 dLLM 的采样逻辑纳入统一调度之后一个很自然的问题就冒出来了既然自回归模型加 temperature 就是 logits 除以 T 再 softmax那扩散语言模型加 temperature 是不是也差不多改一行 argmax 换成带温度的采样就完事了我一开始也是这么想的直到真正把代码翻进去、把采样循环跑起来才发现这件事远没有表面上那么简单。argmax 只是整个去噪采样链路里最末端的一个动作而 temperature 在扩散语言模型里要作用的位置、作用的方式、以及它和迭代步数之间的耦合关系跟自回归模型完全不是一回事。这篇内容就是把我自己在给 dLLM 接 temperature 时踩过的坑、想明白的原理、以及最后落地的方案完整梳理一遍适合已经在用 SGLang 跑扩散语言模型、或者正准备给自己的 dLLM 推理管线加可控随机性的朋友参考。不管你是刚接触扩散语言模型的新手还是已经调过几轮采样参数的老手应该都能从里面找到一些能直接抄作业的东西。1. 为什么改一行 argmax这个直觉会翻车1.1 argmax 在自回归和扩散里的角色根本不是一回事先说清楚一个前提在自回归语言模型里argmax 是选下一个 token的决策动作整个生成过程是逐 token 串行推进的每一步的 logits 分布只影响当前这一个位置。你在这里把 argmax 换成 temperature 采样影响范围就是这一步选哪个词逻辑非常局部改起来也确实就是一行的事。但扩散语言模型不是这样。dLLM 的生成过程是一个迭代去噪的过程一开始整段序列都是被 mask 或者噪声污染的状态模型在每一步对所有位置同时预测一个分布然后根据某种策略决定哪些位置要被确定下来、确定成什么。也就是说argmax 在 dLLM 里不是选下一个词而是在某一轮迭代里决定某个位置是否收敛到某个 token。它作用的对象是整条序列上的多个位置作用的时间点是多轮迭代中的某一轮。这个差别带来的直接后果是temperature 如果只是简单地在最后那一步 argmax 前除一下它影响的只是最终确定时选哪个 token但不影响去噪的节奏、不影响哪些位置先被确定、不影响迭代过程中分布的演化。而恰恰是这些被忽略的部分才是扩散语言模型采样质量的关键。1.2 扩散采样的温度其实分布在多个环节我把 dLLM 的采样链路拆开看至少有三个地方可以塞进温度这个概念第一个是每轮迭代输出的 logits 分布。模型在每一轮都会对每个位置输出一个词表分布这个分布本身可以带温度控制这一轮预测的尖锐程度。第二个是位置选择策略。很多 dLLM 采样不是一次性把所有位置都确定而是每轮挑一部分置信度高的位置先定下来。这个挑多少、挑哪些的策略里其实也隐含了一个类似温度的参数——挑得激进还是保守直接决定了生成的多样性和稳定性。第三个才是最终 token 的确定方式也就是大家直觉里那个 argmax 或者采样。你只改第三个等于只调了整条链路最末端的一个阀门前面两个环节的随机性完全没动。实测下来这种改法在低温度时几乎看不出效果因为前面环节的确定性太强末端再怎么调也翻不出花样而在高温度时又容易出现前面定得太死、末端乱采的割裂感生成质量反而下降。1.3 一个具体的翻车场景我拿一个 7B 级别的扩散语言模型做填空式生成任务是在一段被 mask 的文本里补全缺失部分。最开始我就是按自回归的思路在最后的 token 选择处加了个 temperatureT 从 0.7 调到 1.3。结果很典型T0.7 的时候输出和 argmax 几乎一模一样多样性没起来T1.3 的时候确实出现了不同的补全结果但很多结果是局部合理、整体崩坏——单个位置选的词看着没问题连起来语义不通。原因就是前面几轮迭代已经把大部分位置用高置信度锁死了末端的高温采样只能在这些锁死的位置之间做微调根本没有空间去探索真正不同的生成路径。这个场景让我意识到temperature 在 dLLM 里必须是一个贯穿迭代过程的概念而不是末端的一个开关。2. 扩散语言模型采样链路的完整拆解2.1 从噪声到文本一轮迭代到底发生了什么要搞清楚 temperature 该加在哪得先把一轮迭代的完整流程讲明白。以目前主流的掩码扩散语言模型为例一轮迭代大致是这样的模型接收当前序列状态一部分位置是 mask一部分位置已经确定了 token对每一个位置输出一个词表上的 logits。注意这里是对所有位置都输出包括已经确定的位置和还是 mask 的位置。然后采样策略会根据这些 logits 做两件事一是决定这一轮要把哪些 mask 位置解锁成确定 token二是决定这些位置具体填什么词。解锁之后序列状态更新进入下一轮。直到所有 mask 位置都被确定或者达到最大迭代步数生成结束。这个流程里logits 是每一轮都重新算的因为序列状态在变。这一点和自回归完全不同——自回归里每个位置的 logits 算一次就定了扩散里同一个位置会被反复预测直到它被确定下来为止。2.2 置信度、确定顺序与温度的隐藏耦合为什么说位置选择策略里藏着温度因为大多数 dLLM 采样器是用置信度来决定解锁顺序的。置信度通常来自 softmax 之后的概率分布比如取最大概率值或者取熵的负数。这里的关键在于logits 上的温度会直接改变置信度的数值。你把 logits 除以 Tsoftmax 之后的分布就变了最大概率值也跟着变。T 越小分布越尖锐最大概率越接近 1置信度看起来越高T 越大分布越平坦最大概率越低置信度看起来越低。而置信度又决定了这一轮解锁哪些位置。于是温度就通过改变置信度这条路径间接影响了整个去噪节奏。这是一个非常隐蔽的耦合你以为你只是在调末端采样实际上你把位置选择的节奏也一起改了。我在调试的时候就遇到过这个坑把 T 调高之后模型每一轮解锁的位置数量明显变少因为置信度普遍降低了达到解锁阈值的位置变少了结果迭代步数被迫拉长生成速度掉了一大截。这个现象如果只看末端 argmax是根本解释不了的。2.3 迭代步数、温度与生成质量的三角关系把上面这些串起来就得到一个三角关系温度影响置信度置信度影响解锁节奏解锁节奏影响有效迭代步数迭代步数又反过来影响生成质量。低温度下置信度高每轮解锁的位置多迭代步数少生成快但多样性差高温度下置信度低每轮解锁的位置少迭代步数多生成慢且容易在后期出现过度去噪——本来已经确定的位置被反复重新预测反而引入了噪声。所以给 dLLM 加 temperature本质上是在调一个多维耦合系统而不是调一个标量。这也是为什么改一行 argmax会翻车它假设温度只作用在一个独立环节上但实际系统里没有哪个环节是独立的。3. 温度到底该注入到哪几个位置3.1 方案一只调 logits 温度不动采样策略最保守的做法是在每一轮模型输出 logits 之后、进入采样策略之前统一除以一个温度系数 T。这样做的效果是所有位置的分布都被同一个 T 缩放置信度的相对大小关系不变因为除以正数不改变排序但绝对数值变了。这个方案的好处是实现简单只需要在 logits 出口处加一个除法。坏处是它把分布尖锐度和解锁节奏绑死了你没法单独调其中一个。如果你想要分布尖锐但解锁保守这个方案做不到。我实测下来这个方案适合那些对生成速度不敏感、只想要一点多样性的场景。T 建议控制在 0.8 到 1.1 之间超过 1.2 之后迭代步数会明显膨胀。3.2 方案二温度与置信度阈值解耦进阶一点的做法是把温度和解锁策略分开控制。具体来说logits 除以 T 得到采样分布但解锁位置时用的置信度不直接用这个分布的 max prob而是用一个独立的、不受温度影响的置信度度量比如原始 logits 的 margintop1 减 top2或者单独维护一个温度系数 T_conf 来控制置信度计算。这样做的好处是你可以让分布保持一定的随机性T 大同时解锁节奏保持稳定T_conf 小。我在做长文本补全的时候用的就是这个方案T 设 1.15 保证多样性T_conf 设 0.9 保证解锁节奏不崩效果比单一温度稳很多。代价是实现复杂度上来了需要改采样器的置信度计算逻辑不能只动 logits 那一行。3.3 方案三分阶段温度调度再进一步是让温度随迭代步数变化。早期迭代用较高温度鼓励探索不同的生成路径后期迭代用较低温度让已经确定的位置稳定下来避免过度去噪。这个思路借鉴的是扩散模型里常见的 noise schedule只不过这里调度的是温度而不是噪声。实现上就是维护一个温度序列每一步取对应的 T。我一般用余弦退火T_t T_min 0.5 * (T_max - T_min) * (1 cos(pi * t / N))其中 t 是当前步N 是总步数。这个方案在生成质量上提升最明显尤其是需要长程一致性的任务。但它对总步数 N 比较敏感N 估不准的时候调度曲线会失真。我的经验是 N 宁可估大一点让退火曲线平缓一些不要估小导致后期温度骤降。3.4 三种方案的对比与选型建议方案实现复杂度多样性提升速度影响适用场景只调 logits 温度低中中快速试验、短文本温度与置信度解耦中高小长文本补全、需要稳定节奏分阶段温度调度高高中长程一致性要求高的任务选型上我的建议是如果你只是想快速验证 temperature 有没有用先用方案一把 T 在 0.8 到 1.2 之间扫一遍看看输出多样性和质量的变化趋势。如果发现速度掉得厉害或者质量不稳再上方案二。方案三留到你已经明确知道自己的任务需要长程一致性、并且能接受调参成本的时候再用。4. 在 SGLang 里落地温度注入的实操细节4.1 找到 logits 出口的正确位置在 SGLang 的 dLLM 推理路径里logits 的出口不在你第一眼以为的那个地方。自回归模型的 logits 处理通常在 sampler 里但 dLLM 因为要处理多位置并行预测logits 的 shape 和流转路径都不一样。我踩过的坑是一开始在 sampler 的 argmax 前面加温度结果发现只影响了最终确定的那一步中间迭代的 logits 完全没经过这里。正确的做法是找到每一轮迭代的模型前向输出在它进入位置选择逻辑之前注入温度。这个位置通常在 dLLM 的 decode loop 里而不是在通用的 sampler 模块里。具体定位方法在 decode loop 里打印每一轮 logits 的 shape 和数值范围找到那个每轮都会出现的、shape 是 [batch, seq_len, vocab_size] 的张量温度就加在它后面。4.2 温度注入的代码骨架下面是我实际用的注入骨架语言是 Python框架是 SGLang 的 dLLM 路径。注意这里只是示意核心逻辑实际接入要按你用的版本调整def apply_temperature(logits, temperature, stepNone, total_stepsNone): # 分阶段调度如果传入了 step 和 total_steps用余弦退火 if step is not None and total_steps is not None: t_min, t_max 0.7, 1.2 temperature t_min 0.5 * (t_max - t_min) * ( 1 math.cos(math.pi * step / total_steps) ) # 防止温度为 0 或负数 temperature max(temperature, 1e-5) return logits / temperature调用点放在 decode loop 里模型前向之后、位置选择之前logits model_forward(input_ids, attention_mask) logits apply_temperature(logits, base_temperature, step, total_steps) # 后面才是置信度计算和位置解锁这里有个细节要注意温度除法要在 fp32 下做。如果 logits 是 fp16 或者 bf16直接除可能引入精度问题尤其是 T 比较大的时候。我的做法是先把 logits 转成 fp32除完温度再转回去多出来的开销很小但稳定性提升明显。4.3 置信度解耦的实现要点如果你走方案二需要在位置选择逻辑里单独算置信度。核心是不用带温度的分布去算 max prob而是用原始 logits# 带温度的分布用于最终采样 sampling_probs softmax(logits / temperature, dim-1) # 不带温度的置信度用于位置解锁决策 raw_probs softmax(logits, dim-1) confidence raw_probs.max(dim-1).values这样温度只影响采样不影响解锁节奏。实测下来这个改动对生成速度的稳定性帮助很大尤其是在 T 大于 1.1 的时候。4.4 和 SGLang 调度器的配合SGLang 的调度器会管理多个请求的 batchdLLM 的迭代步数在不同请求之间可能不一样。如果你用了分阶段温度调度要注意每个请求的 total_steps 可能不同不能用一个全局的 step 计数。我的处理方式是给每个请求维护独立的 step 和 total_steps在 batch 内分别计算温度。这会带来一点额外的计算开销但比用全局计数导致的调度错乱要划算得多。如果你的请求步数都差不多也可以简化成用 batch 内最大步数做统一调度精度损失可以接受。5. 调参过程中那些文档不会告诉你的坑5.1 温度不是越高越多样存在一个崩坏阈值很多人以为温度越高多样性越好在 dLLM 里这个直觉会害你。因为前面说的耦合关系温度高到一定程度之后置信度普遍低于解锁阈值模型每轮解锁的位置急剧减少迭代步数暴涨而后期又因为过度去噪把已经确定的位置反复推翻最终输出反而变得又慢又乱。我实测的崩坏阈值大概在 T1.4 到 1.5 之间具体取决于模型和任务。超过这个值之后生成质量断崖式下跌。所以调参的时候不要一路往上加要在 1.2 附近就停下来看看趋势。5.2 不同任务的温度甜点区差别很大填空补全和开放式生成对温度的需求完全不同。填空任务因为上下文约束强温度可以稍微高一点1.1 到 1.25 都还行开放式生成因为约束弱温度高了容易跑偏0.9 到 1.05 比较稳。我建议的做法是先在你的目标任务上跑一个小规模的温度扫描T 取 0.8、0.9、1.0、1.1、1.2 五个点每个点生成 20 条样本人工或者用自动指标评估一下找到质量最高的区间再细化。不要直接套用别人博客里的推荐值模型和任务不一样甜点区差很多。5.3 温度和解码步数的联动调参温度和最大迭代步数是要一起调的。温度高的时候解锁慢你需要给更多的步数预算否则模型还没解锁完就被截断了。我的经验公式是最大步数 ≈ 基础步数 * (1 0.5 * (T - 1))T 大于 1 的时候按这个放大T 小于 1 的时候可以适当缩小。这个公式不精确但能给你一个起点避免温度调上去了步数没跟上导致输出被截断。5.4 批量推理时的温度一致性批量推理的时候如果不同请求用了不同温度要注意 SGLang 的 batch 内计算是不是支持 per-request 的温度。有些实现会把温度当成全局参数batch 内所有请求共用一个值。如果你需要 per-request 温度得确认你的版本支持或者自己在 logits 处理阶段按 request id 分别缩放。我遇到过一次因为没注意这个batch 里混了不同温度的请求结果所有请求都被按第一个请求的温度处理了排查了半天才发现是 batch 内温度没做隔离。6. 怎么验证温度真的起作用了6.1 看分布熵的变化最直接的验证方式是看每一轮 logits 分布的熵。温度调高之后熵应该上升调低之后熵应该下降。如果熵没变化说明你的温度注入点没生效或者被后面的逻辑覆盖了。我在 decode loop 里加了一个简单的熵统计每轮打印平均熵一眼就能看出温度有没有起作用。这个方法比看最终输出靠谱得多因为最终输出的变化可能被其他因素掩盖。6.2 看解锁位置数量的变化第二个验证指标是每轮解锁的位置数量。温度变化应该引起解锁数量的变化如果你没做解耦的话。如果解锁数量完全不变说明温度没有影响到置信度计算可能你只改了末端采样。6.3 看迭代步数的变化第三个指标是实际迭代步数。温度调高之后如果迭代步数没有增加要么是你的解锁策略不受温度影响解耦了要么是温度没注入到正确位置。这个指标能帮你快速判断温度的作用范围。6.4 一个可复现的验证脚本def verify_temperature_effect(model, input_ids, temperatures): results [] for T in temperatures: logits model_forward(input_ids) scaled logits / T entropy -(softmax(scaled, -1) * log_softmax(scaled, -1)).sum(-1).mean() results.append({T: T, entropy: entropy.item()}) return results跑一遍这个脚本如果熵随 T 单调上升说明温度注入是有效的。如果熵不变回去检查注入点。7. 一些延伸思考温度之外的可控性把 temperature 接进 dLLM 之后我发现这套思路其实可以推广到其他采样控制参数上。比如 top-k 和 top-p在自回归里是在末端截断分布在 dLLM 里同样面临该在哪一层截断的问题。如果只在末端截断中间迭代的分布还是全量的效果有限如果在每轮迭代都截断又可能影响解锁节奏。还有一个更有意思的方向是按位置差异化温度。已经确定的位置可以用低温度稳定住还是 mask 的位置可以用高温度探索。这个思路在长文本生成里可能有价值我还在试验阶段目前看到的效果是能减少后期过度去噪但实现复杂度不低。另外温度调度和迭代步数的联合优化也值得做。现在我是手动调这两者的关系如果能用一个简单的控制器根据实时熵自动调整温度应该能省掉不少调参时间。这个我还没落地先记在这里。最后分享一个我在实际调试里养成的小习惯每次改温度相关的代码我都会先把 T 设成一个极端值比如 0.1 和 2.0跑一遍看输出有没有明显变化。如果极端值下都没变化那肯定是注入点错了不用浪费时间在中间值上慢慢试。这个习惯帮我省了很多排查时间推荐你也试试。
阅读完成 · 觉得有帮助?