☰ inferserver 的数学基础

12. 采样:温度、top-k、top-p、逆变换

模型给出 logits,采样决定“下一个 token 怎么选”。仓库里的顺序是:温度 → top-k → softmax → top-p → 抽一个。

12.1 温度:把 logits 除以 T

定义

\[ p_i(T)=\frac{e^{z_i/T}}{\sum_j e^{z_j/T}},\qquad T>0 . \]

两个极限:

温度越小越“尖”,越大越“平”。注意 \(T\) 不改变排序,只改变概率的集中程度;所以 top-k 的集合与 \(T\) 无关(但 top-p 的截断集合会变)。

12.2 top-k:只留最大的 k 个

把第 \(k+1\) 名及以后的 logits 设为 \(-\infty\),再 softmax。因为 \(e^{-\infty}=0\),这些位置概率精确为 0;剩下 \(k\) 个的分子不变,分母只剩这 \(k\) 项,等价于在子集上重新归一化:

\[ p_i=\frac{e^{z_i}}{\sum_{j\in\text{top-}k}e^{z_j}},\qquad i\in\text{top-}k . \]

它固定了候选数量,避免从长尾里抽到离谱 token。缺点是 \(k\) 不随分布形状变化:分布很尖时 \(k\) 太大,很平时 \(k\) 太小。

12.3 top-p(nucleus):按累积概率截断

把概率从大到小排序 \(p_{(1)}\ge p_{(2)}\ge\cdots\)。找到最小的 \(m\) 使得

\[ \sum_{j=1}^{m}p_{(j)}\ge p, \]

只保留前 \(m\) 个,再除以它们的和 \(S_m=\sum_{j=1}^m p_{(j)}\):

\[ \tilde p_{(j)}=\frac{p_{(j)}}{S_m},\quad j\le m;\qquad \tilde p_{(j)}=0,\quad j>m . \]

因为 \(S_m\ge p\),保留的集合至少覆盖 \(p\) 的概率质量;分布尖时 \(m\) 很小,分布平时 \(m\) 自动变大。这就是“核采样”的自适应之处。

代码里的循环是“累加、把当前项放进去、如果累计 \(\ge p\) 就停”,所以 \(m\) 是第一个让累计跨过 p 的位置,包含跨过门槛的那一项。

12.4 逆变换采样:为什么按累积概率就能抽对

设 \(u\sim U[0,1)\),累积分布 \(C_i=\sum_{j=1}^{i}p_j\),定义 \(X=\min\{i:u<C_i\}\)。那么

\[ \mathbb{P}(X=i)=\mathbb{P}(C_{i-1}\le u<C_i)=C_i-C_{i-1}=p_i, \]

因为 \(u\) 落在长度 \(p_i\) 的区间里的概率就是区间长度。实现上就是从前往后累加,第一次超过 \(u\) 就返回。这是“按概率抽一个”的标准做法,不需要额外的随机数分布。

12.5 本项目的实现细节

对应代码:model/sample.go 的 SampleLogits / Sample / topKFloat64 / topPFloat64 / sampleOneFloat64。