LLM推理基础:采样(Sampling)常见知识概览 - 指南
本文作者:kaiyuan
LLM推理基础:采样(Sampling)常见知识概览
本文主要介绍LLM推理采样(Sampling)相关的基本概念,包括随机采样、确定性采样,涉及概念包括:Temperature/Topk/TopP/MinP/Penalty和Beam Search/Greedy Search等。结合原理、公式和示例,用通俗的语言帮助读者快速了解相关内容。
1.推理采样的概念
什么是采样?目前大语言模型(LLM)的文字生成,主要采用的机制是词语接龙,比如输入“我爱”,后面可以接“你”、“她”、“他”等词。这个“我爱”的输入称之为提示词(prompt),后续的输出文字是依次迭代产生的,迭代过程称之为自回归。
推理运算的基本过程:输入的prompt先经过模型的主体(Backbone),再经过解码层(LM Head)和Softmax层后能够得到序列(seq)中每个字符的输出概率数组(logits),这个数组的大小与字典大小(vocab_size)相等,字典中的每个词都有可能是输出结果。采样(Sampling)则是根据概率分布从字典中选取一个最合适的词。

为什么需要调整采样过程?对于LLM采样最直接的方式就是选取概率最大值的字符作为输出,但这会让模型的输出没有多样性,比如上例中输入“我爱”后面只会接“你”,不会是其它词。所以,得让模型能够“创造”出更多的结果。
常见的采样方式有哪些?
- 随机:根据一定条件筛选符合条件的高概率值,然后从中随机挑选一个作为输出结果;
- 贪心:每轮都挑选概率最大值作为本次的输出结果;
- 累积概率最大:每一个步输出多个结果,进行多步后挑选概率乘积最大的一组,如BeamSearch。
- 结构化:按照一定格式限定输出形式。比如提示词为:“你最喜欢一周中的哪天?”。一种结构化输形式为“周__”最后一个词的选项只有7个。
2 .随机采样
随机采样并非完全随机,而是有条件的随机。因为随机可能会降低输出质量,过量的随机性会导致输出混乱,一个极端的例子是对字典范围内进行简单随机抽样,这显然不对。比较常见随机采样是权重采样,把LLM的输出的logits分数作为采样的权重(参考torch.multinomial)。为了获得合理且丰富的输出结果,在随机采样中还引入了一些调整logits值的操作。
2.1 温度(Temperature)
温度(Temperature)操作用于调整logits的概率分布整体情况,能让概率分布变得尖锐或者平坦。利用了吉布斯分布的特点。标准的softmax计算:
P
(
i
)
=
exp
(
z
i
)
∑
j
=
1
V
exp
(
z
j
)
(1)
P(i) = \frac{\exp(z_i)}{\sum_{j=1}^{V} \exp(z_j)} \tag {1}
P(i)=∑j=1Vexp(zj)exp(zi)(1)增加了温度T的softmax计算:
P
(
i
)
=
exp
(
z
i
T
)
∑
j
=
1
V
exp
(
z
j
T
)
(2)
P(i) = \frac{\exp(\frac{z_i}{T})}{\sum_{j=1}^{V} \exp(\frac{z_j}{T})} \tag {2}
P(i)=∑j=1Vexp(Tzj)exp(Tzi)(2)
改变T值的大小,能够影响输出的概率:
- 0<T< 1,称之为低温。logits概率数组将会变得锐化,值与值之间的差距拉大。
- T>1,称之为高温。logits概率数组变得更加平缓,值与值之间的差距缩小。
举个例子,看一下经过不同T后,概率的变化。
- 字典的词组为:[‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’]
- 输出logits结果(LM head之后,softmax计算之前):[2.0, 1.0, 0.5, 0.1, -1.0, -2.0]


特点:在LLM中,低温处理能够让概率大值更加突出,输出变得可预测、连贯,但也可能变得重复和缺乏创造力;高温处理输出会更加多样、有创意,但会变得不连贯或出现事实性错误。
2.2 Topk/TopP/MinP
目前一个LLM字典大小能到151K,随机采样时范围并不需要这么大,为了降低随机的范围,可去掉一些低概率值。常见的方式是用过滤来缩小范围。
- TopK:概率排序后,保留概率最大的K个值。取值范围是1~vocab_size(-1表示禁用)。
- TopP:概率排序后,取累积概率到P的值,先排序 p ( 1 ) ≥ p ( 2 ) ≥ ⋯ ≥ p ( V ) p_{(1)} \geq p_{(2)} \geq \cdots \geq p_{(V)} p(1)≥p(2)≥⋯≥p(V),然后算累加值: C k = ∑ i = 1 k p ( i ) C_k = \sum_{i=1}^k p_{(i)} Ck=∑i=1kp(i), 保留 C k ≥ P C_k \geq P Ck≥P的前K个值。topP取值范围0~1。
- MinP:保留所有概率至少为最高概率的P倍的候选词,保留所有满足 p i ≥ min-p × p max p_i\geq \text{min-p} \times p{\text{max}} pi≥min-p×pmax的候选词,min_p的取值范围>0。
对于没保留的值,一般设置概率为0,举个例子:
若字典是[‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’],logits为[0.6, 0.3, 0.05, 0.03, 0.015, 0.005]
- 用TopK=4,则logits变为:[0.6, 0.3, 0.05, 0.03, 0, 0];
- 用TopP=0.95,则logits变为:[0.6, 0.3, 0.05, 0, 0, 0];
- 用MinP=0.5,则logits变为:[0.6, 0.3, 0, 0, 0, 0];
特点:TopK保留前K个最大值;TopP能够根据累积概率筛选掉低概率值。MinP能够保证每个值达到一定要求。
通过示例,了解这几个处理方式的问题:
- TopK会选取到一些概率低的样本。例:logtis为[0.6, 0.3, 0.05, 0.03, 0.015,0.005],TopK=3,0.05概率被采样进去了。
- TopP有采集的数量过多的问题。例:logtis为[0.2, 0.2, 0.2, 0.2, 0.1,0.1],TopP=0.95,全部值都有效。
- MinP有采样过少的问题。例:logtis为[0.8, 0.1, 0.05, 0.03, 0.015,0.005],MinP=0.5时,仅保留了一个值。
所以通常的LLM推理过程中,会混合使用这些策略。
2.3 惩罚(Penalty)
惩罚(Penalty)是根据历史的输出字符情况增加一些超参来调整局部的logits值,是一个局部缩放操作。
为什么需要惩罚这种机制?一个理想LLM像人类一样输出合适的结果,但目前的LLM还不能完全达到人类的水平。在输出采样实践中,如果纯靠概率模型可能会犯一些错误,比如:循环、重复、冗余。

惩罚作为对logits的一种处理手段,抑制一些基本的错误。常见的惩罚手段有:
- 频率惩罚(frequency penalty):对出现过的词,根据其出现频率降低logits值,频率越高衰减越严重。
- 存在惩罚(presence penalty):对出现过的词,在logits中减去一个相应惩罚值,每个词至多惩罚一次。
- 重复惩罚(repetition penalty):对重复出现的词进行衰减,类似频率处理。
一般计算方法:
- logits[i] -= frequency_penalty × count
- logits[i] -= presence_penalty
- logits[i] /= (repetition _penalty ** (count - 1)) ,当logits[i] >0;
- logits[i] *= (repetition _penalty ** (count - 1)) ,当logits[i] < 0;
其中frequency_penalty 和 presence_penalty 默认值0,取值范围[-2, 2];repetition _penalty 默认值1,取值范围[0, 2]。
2.4 操作顺序
除了温度、惩罚外,还有其它能修改logits数值的方式,比如:
- 偏置(bias):给指定的词添加一个偏置项,计算方式:logits[i] += bias[i]);
- 掩膜(mask):将掩膜外的值置为0;
在对每一轮的logits的调节上,操作的顺序会影响最后的结果。有些操作要需要固定,但有些操作之间的顺序又可以调节,比如温度操作在softmax之前,而top操作之间可以调整。对于采样顺序调整的问题参考:TopK/MinP/P/T/Softmax等的顺序对采样有何影响?

用pytorch实现的采样处理的示意代码:
# 伪代码,供参考:
# 惩罚处理
penalizer(logits)
# 添加bias值
logits.add_(self.logit_bias)
# 温度处理
logits.div_(temperatures)
# softmax 计算
logits[:] = torch.softmax(logits, dim=-1)
probs = logits
probs_sort, probs_idx = probs.sort(dim=-1, descending=True)
probs_sum = torch.cumsum(probs_sort, dim=-1)
# top-k 采样
probs_sort[
torch.arange(0, probs.shape[-1]).view(1, -1)
>= top_ks.view(-1, 1)
] = 0.0
# top-p 采样
probs_sort[(probs_sum - probs_sort) > top_ps.view(-1, 1)] = 0.0
# min-p 采样
min_p_thresholds = probs_sort[:, 0] * min_ps
probs_sort[probs_sort < min_p_thresholds.view(-1, 1)] = 0.0
# 权重采样
sampled_index = torch.multinomial_with_seed(probs_sort, sampling_seed, positions)
3 确定性采样
与随机采样对应的还有一类是确定性采样,主要是解决比如机器翻译、概要生成这类场景,其不需要多样性,而是期望结果更准确。确定性采样的目标就是找概率最大的输出结果。
3.1 贪婪采样
常见的确定性采样:贪婪搜索(greedy search),其思路是每一步取概率最大的logits作为输出,但贪婪搜索保证的是单步最优,没有考虑多步之间的联系,不能保证累积概率最大。
举个例子,如下图所示,采用贪婪采样,在step1选择“吃”(概率0.6),step2选择“你”。两步的累积概率为0.6*0.3=0.18;与之对比,若step1选“爱”、step2选“吃”,最后能够得到累积概率0.3x0.7=0.21,数值比贪婪算法的结果更大。

在LLM中前缀的变化会影响下一步的输出值,单个输出的累积概率是否最大,要穷举所有的可能进行对比。
3.2 问题的模型
计算累积概率最大是一个典型的多叉树找最优解的问题,可以用广度/深度搜索(BFS/DFS)来解问题。假设推导k步,字典大小(vocab_size)为n,不考虑推理提前结束(遇到stop、eos等字符),可能的计算路径数量:
n
k
n^k
nk。假设单步推理的计算量为
f
(
L
)
f(L)
f(L),L表示序列长度,总的计算量为:
F
(
k
)
=
Σ
i
=
0
k
−
1
n
i
∗
f
(
L
0
+
i
)
(3)
F(k)=\Sigma_{i=0}^{k-1} n^{i}*f(L_{0}+i)\tag{3}
F(k)=Σi=0k−1ni∗f(L0+i)(3)但单步推理的计算成本是非常高的,且常见的字典大小会超过100K,所以这种暴力搜索的方式很显然不可取。要对公式3中的
n
i
n^i
ni项进行压缩,常见处理方式:
- n i n^i ni替换为1:搜索路径只有一条,即是之前提到的的贪婪搜索;
- n i n^i ni替换为定值k:搜索路径有k条,得到一种叫束搜索(beam searh)的方式。
3.3 Beam Search
beam search是一种结合topK和剪枝的搜索算法,每次保留束宽(beam width)k个结果。beam search的基本步骤:
- 对首次输出进行topK排序,选取前k个值;
- 根据上一轮的k个值,分别获得k个输出,每个分支进行topK排序,计算累积概率;
- 对全局k * k个累积概率排序,选取前k个值;
- 重复步骤2、3,直到k个分支遇到结束符,最后以累积概率最大的分支作为结果。

beam search是一种介于贪婪搜索和暴力搜索之间的方式,通过控制beam width在计算总量与结果质量中找到一个平衡位置。
beam search有什么不足?
- 显存开销大。所以实现中一般会结合共享前缀的方式来降低存储、访存开销,参看paged attention[1]中相关介绍。
![在这里插入图片描述]()
- 输出“短视”。在计算累积概率时可知,因为概率范围是(0~1),对于同一个分支计算次数越少结果值越高。这样使得beam search趋向于选择短输出。
为了解决这一问题,引入了一个长度惩罚(length penalty),对于beam search的每个计算结束的分支累积概率乘以一个惩罚值。常用的计算方式: P c u m = P c u m L p (4) P_{cum} = \frac{P_{cum}}{L^{p} } \tag {4} Pcum=LpPcum(4)
logprob = cumulative_logprob / (seq_len**length_penalty)
参数length_penalty< 1 时鼓励长输出,即长度越短分数衰减越大。
还可以通过min_new_tokens或者max_new_tokens这种参数,控制长度的范围。实现的方式比较简单:当长度小于min_new_tokens让LLM不能输出终止符号。
总结
本文介绍了常见的采样方式的概念,涉及到的关键问题有:
- 什么是采样?
- 为什么需要调整采样过程?
- 常见的采样方式有哪些?
- Topk/TopP/MinP各自有什么缺点?
- 为什么需要惩罚这种机制?
- beam search有什么不足?如何解决?
通过对采样有了基本认识后,接下来还需要了解如何设置采样参数,本文中未对调参内容做介绍,后续再补充。
参考
[1]Efficient Memory Management for Large Language Model Serving with PagedAttention [https://arxiv.org/pdf/2309.06180]
InfraTech申明:未经允许不得转载!

浙公网安备 33010602011771号