DataWhale组队学习笔记--llm-algo-leetcode(四)

📅 2026/7/18 22:53:17
DataWhale组队学习笔记--llm-algo-leetcode(四)
文章目录Decoding Strategies 解码策略1. Temperature温度重塑概率的“平坦度”2. Top-k硬性的“名次截断”3. Top-p核采样 / Nucleus Sampling弹性的“累积概率截断”⚙️ 生产环境中的协同流水线 (The Pipeline) 典型应用场景参数搭配4.面试必考难点Top-p是如何用代码实现的Decoding Strategies 解码策略大语言模型在生成文本时并不是直接吐出具体的单词而是为词表中的每一个词输出一个原始得分Logits。为了将这些得分转化为最终的文本模型需要利用解码策略Decoding Strategies在词表中进行筛选和采样。在这个过程中Temperature、Top-k和Top-p是控制生成文本质量、流畅度与创造力最核心的三大参数。它们就像是三层协同工作的滤网一步步决定了最终输出的词汇。1. Temperature温度重塑概率的“平坦度”Temperature 本质上是一个概率调节器它作用于 Softmax 函数用来拉大或缩小候选词之间的“贫富差距”。其数学公式为P ( x i ) exp ⁡ ( z i / T ) ∑ j exp ⁡ ( z j / T ) P(x_i) \frac{\exp(z_i / T)}{\sum_j \exp(z_j / T)}P(xi​)∑j​exp(zj​/T)exp(zi​/T)​其中z i z_izi​是词i ii的原始得分LogitsT TT就是温度参数。当T 1.0 T 1.0T1.0降温马太效应加剧。概率高的词被无限放大概率低的词被极度压缩。分布曲线变得非常“尖锐”。当T TT趋近于 0 时解码退化为贪婪搜索Greedy Search模型每次只选概率最高的那一个词输出绝对确定、严谨但缺乏多样性甚至容易陷入死循环。当T 1.0 T 1.0T1.0出厂默认保持模型原始计算出来的概率分布。当T 1.0 T 1.0T1.0升温劫富济贫。高概率词和低概率词之间的差距被缩小整体概率分布被“熨平”。那些原本概率极低的“冷门词”被选中的几率显著上升。模型会变得极具想象力和创意但过高如 1.5会导致语句逻辑混乱、胡言乱语幻觉。2. Top-k硬性的“名次截断”如果只靠调节温度当温度升高时一些完全不着边际的垃圾词如错别字、无关字符也可能被意外抽中。为了守住逻辑底线引入了 Top-k 机制。工作机制将所有候选词按照概率从大到小排序强制只保留前k kk个词将第k 1 k1k1名及之后的词全部剔除概率直接归零。剩下的k kk个词会重新按比例分配概率。局限性死板Top-k 是一个固定数值。词少不够用当句子很开放比如“今天我很…”后面可能有上百个合适的词强行只留k 50 k50k50会扼杀创意的火花。词多引入噪音当句子几乎只有一个正确答案比如“一加一等于…”后面只有“二”硬拉进来 50 个词凑数反而增加了犯错的概率。3. Top-p核采样 / Nucleus Sampling弹性的“累积概率截断”为了解决 Top-k 过于死板的问题Top-p 引入了动态候选池的概念。它不再关注“保留多少个词”而是关注“保留多少比例的概率”。工作机制同样将词按概率从高到低排序然后从头开始累加它们的概率。当累加概率刚好达到或超过阈值p pp通常在 0.8 到 0.95 之间时立刻停止截断。只有被包含进这个累积概率区间的词才会被保留其余的全部丢弃。自适应优势模型信心足时分布尖锐前 1、2 个词的概率加起来就达到了 90%候选池就自动收缩到只有这 1-2 个词保证绝对不出错。模型信心不足时分布平缓需要把前 100 个词加起来才够 90%候选池自动扩张到 100 个词提供丰富的选择。⚙️ 生产环境中的协同流水线 (The Pipeline)在真实的 LLM 推理框架如 vLLM、Hugging Face Transformers中这三个关键词并非孤立工作而是按严格的先后顺序形成了一条采样流水线[原始 Logits 得分] │ ▼ 1. Temperature ──► 缩放 Logits改变所有词的相对概率 │ ▼ 2. Top-k ───────► 硬截断只留下排名前 k 的词 │ ▼ 3. Top-p ───────► 在幸存词里进行累积概率筛选划定最终候选池 │ ▼ [重归一化采样] ──► 在最终候选池里抛随机骰子选出下一个 Token 典型应用场景参数搭配场景推荐配置调参核心思想 写代码 / 逻辑推理Temp 0.0 ~ 0.2Top-p 0.9Top-k 40追求精准、可复现。绝不允许模型自作聪明、胡乱发挥。 日常对话 / 事实问答Temp 0.5 ~ 0.7Top-p 0.9Top-k 50追求自然、生动。在保证事实基本准确的前提下让语气更有人情味。 创意写作 / 灵感风暴Temp 0.9 ~ 1.2Top-p 0.95Top-k 0 (禁用)追求发散、出人意料。放宽边界主动引导模型调用一些低概率但惊艳的词汇。4.面试必考难点Top-p是如何用代码实现的假设词表只有5个词排序后的概率分别是【0.5, 0.3, 0.1, 0.05, 0.05】。阈值p 0.85。1、计算累加和Cumulative Sumcumsum【0.5, 0.8, 0.9, 0.95, 1.0】2、找到那些累加和超过0.85的位置即[False, False, True, True, True]3、把这些位置对应的原始Logits强制设为-inf4、对剩下的有效Logits重新执行一次Softmax进行概率归一化。最后记住前面三步修改的是logits的筛选范围最后的Softmax和采样才决定真正输出哪个token。代码实现importtorchimporttorch.nn.functionalasFdefapply_temperature(logits:torch.Tensor,temperature:float)-torch.Tensor:#TODO1:温度下限与缩放tempmax(temperature,1e-6)returnlogits/tempdefapply_top_k(logits:torch.Tensor,top_k:int)-torch.Tensor:#Top-k截断。只保留值最大的Top_k个其余置为-inf。iftop_k0ortop_klogits.size(-1):returnlogits#TODO2:Top-K 截断filter_valuefloat(-inf)kth_values,_torch.topk(logits,top_k,dim-1,largestTrue,sortedTrue)kth_valueskth_values[...,-1:]#取最后一个第K大的值logitstorch.where(logitskth_values,torch.tensor(filter_value,devicelogits.device),logits)returnlogitsdefapply_top_p(logits:torch.Tensor,top_p:float)-torch.Tensor:#Top-p(Nucleus)核采样截断iftop_p0.0ortop_p1.0:returnlogits#1.首先需要将logits从大到小排序sorted_logits,sorted_indicestorch.sort(logits,descendingTrue)#2.对排序后的概率需要先算一遍Softmax计算累加和Cumulative Probabilitycumulative_probstorch.cumsum(F.softmax(sorted_logits,dim-1),dim-1)#TODO3Top-p核心逻辑sorted_indices_to_removecumulative_probstop_p sorted_indices_to_remove[...,1:]sorted_indices_to_remove[...,:-1].clone()sorted_indices_to_remove[...,0]0sorted_logits[sorted_indices_to_remove]float(-inf)restored_logitstorch.zeros_like(logits).scatter_(dim-1,indexsorted_indices,srcsorted_logits)returnrestored_logitsdefdecode_next_token(logits:torch.Tensor,temperature0.7,top_k50,top_p0.9):#1.调温logitsapply_temperature(logits,temperature)#2.Top-K截断通常先K后plogitsapply_top_k(logits,top_k)#3.Top-p截断logitsapply_top_p(logits,top_p)#4.概率重归一化probsF.softmax(logits,dim-1)#5.从概率分布中采样1个词next_tokentorch.multinomial(probs,num_samples1)returnnext_token# 运行此单元格以测试你的实现deftest_decoding():try:# 为了保证可重复性torch.manual_seed(42)vocab_size10# 伪造一组 Logits: [0.1, 2.3, 0.4, 1.2, -0.5, 4.0, 3.1, 0.0, 1.1, -1.0]# 最大的两个: index 5 (4.0), index 6 (3.1)logitstorch.tensor([[0.1,2.3,0.4,1.2,-0.5,4.0,3.1,0.0,1.1,-1.0]])print(原始 Logits (前10个单词):,logits.squeeze().tolist())# 1. 测试 Temperaturet_logitsapply_temperature(logits.clone(),0.5)# 温度 0.5 应该会让差异翻倍asserttorch.allclose(t_logits[0,5]-t_logits[0,6],(logits[0,5]-logits[0,6])*2),温度调节错误print(✅ Temperature 热量调节通过)# 2. 测试 Top-Kk_logitsapply_top_k(logits.clone(),3)# 只保留最大的三个5, 6, 1valid_count(k_logits!float(-inf)).sum().item()assertvalid_count3,fTop-K 截断没有正确执行保留了{valid_count}个值print(✅ Top-K 暴力截断通过)# 3. 测试 Top-p# 原始概率: [0.01, 0.10, 0.01, 0.03, 0.00, 0.54, 0.22, 0.01, 0.03, 0.00]# 降序: 0.54 (idx 5), 0.22 (idx 6), 0.10 (idx 1) ...# 累加和: 0.54, 0.76, 0.86# 所以只有 idx 5, 6, 1 会被保留p_logitsapply_top_p(logits.clone(),0.8)valid_count(p_logits!float(-inf)).sum().item()assertvalid_count3,fTop-p 核采样截断没算准保留了{valid_count}个值print(✅ Top-p (Nucleus) 核采样动态截断通过)# 4. 测试完整管线next_tokendecode_next_token(logits.clone(),temperature0.7,top_k50,top_p0.9)assertnext_token.shape(1,1),解码的词张量维度不对print(f\n✅ All Tests Passed! 解码策略实现通过测试。本次采样的下一个词汇 ID 是:{next_token.item()})exceptNotImplementedError:print(请先完成 TODO 部分的代码)raiseexcept(AttributeError,NameError,TypeError,ValueError,AssertionError,RuntimeError)ase:ifisinstance(e,AttributeError):print(代码未完成无法找到必要的属性)elifisinstance(e,NameError):print(代码可能未完成导致了变量未定义)elifisinstance(e,TypeError):print(代码可能未完成导致了操作错误)elifisinstance(e,ValueError):print(代码可能未完成导致了张量维度错误)elifisinstance(e,RuntimeError):print(代码可能未完成导致了运行时错误)else:print(代码可能未完成导致了断言失败)raiseNotImplementedError(请先完成 TODO 部分的代码)fromeexceptExceptionase:print(f❌ 测试失败:{e})raisetest_decoding()结果