Top-k 与 Top-p 采样:控制文本生成多样性的关键技术解析

你有没有遇到过这种情况——让大模型写一段故事,结果开头永远是“这是一个关于……的故事”,结尾必接“总之,我们要珍惜当下”?😅 这就是典型的 贪心搜索陷阱 :模型总选概率最高的词,输出虽然流畅,但千篇一律,像被复制粘贴过的模板。

可如果我们换一种方式,完全随机地从所有可能的词里抽一个呢?那更可怕——“蓝色的思想在跳舞,因为昨天吃了星期五。”🤪 完全不知所云。

所以问题来了: 我们能不能既保留语言的合理性,又让模型有点创意和个性?

答案就是今天要聊的两位“幕后操盘手”: Top-k 采样 Top-p 采样(也叫核采样) 。它们不是什么神秘黑科技,而是现代大模型生成文本时最常用的“筛选器”,悄悄帮你过滤掉太离谱的选项,又不至于让回答变得死板。


咱们先来打个比方 🍕:

想象你在披萨店点配料,菜单上有100种选择。
- 贪心搜索:每次都只选“最受欢迎”的奶酪 + 火腿,吃十次都一样。
- 完全随机:可能选到牙膏 + 橡皮泥,直接送医。
- Top-k:你告诉店员:“我只要前20个热门配料。”然后在这20个里随机挑几个。稳妥又有新意。
- Top-p:你说:“给我凑够90%顾客都会选的那些配料组合。”如果前5个就占了85%,再加一个到92%,那就只考虑这6个——动态调整范围,聪明!

看到区别了吗?这就是 Top-k 和 Top-p 的核心思想。


先看 Top-k:固定名额的“优等生班”

Top-k 很简单粗暴:每轮预测时,模型会给出每个词的概率,我们只留下 概率最高的前 k 个词 ,其他统统不要,然后在这k个词里按比例抽一个作为下一个词。

举个例子:

当前上下文是:“春天来了,花儿______”

模型输出可能是:
- “开了” → 40%
- “绽放” → 30%
- “盛开” → 15%
- “凋谢” → 5%
- “跑了” → 0.001%

如果你设置 k=3 ,那就只保留“开了”“绽放”“盛开”,重新归一化后变成约 47%、35%、18%,然后从中采样。不会选到“跑了”这种荒谬词,也不会死盯着“开了”不放。

优点
- 实现超简单,排序+截断就行。
- 控制力度强,适合对输出稳定性要求高的场景。
- 计算快,移动端也能跑。

⚠️ 缺点
有时候太机械了。比如某个句子里,前3个词已经占了99%的概率,其实没必要留那么多候选;而另一个句子里,前10个才凑够60%,这时候 k=5 可能反而漏了好选择。

这就引出了更聪明的做法——


再看 Top-p:按“群众基础”划线的“民主选举”

Top-p 不关心有多少人进候选池,它只关心:“这些人的支持率加起来能不能覆盖大多数民意?” 😂

具体来说,它先把所有词按概率从高到低排队,然后不断往里加,直到累计概率首次超过设定的阈值 p 。比如 p=0.9 ,那就一直加,直到总和 ≥ 90%,这一组就是所谓的“核”(nucleus),只有它们有资格参与投票。

还是上面的例子:
- “开了” → 40%
- “绽放” → 30% → 累计 70%
- “盛开” → 15% → 累计 85%
- “怒放” → 8% → 累计 93% ✅ 停!前面四个进入候选池

注意,这里实际用了4个词,但如果下一句话模型特别自信,可能第一个词就占了95%,那 p=0.9 时就只留它自己,相当于自动切换成贪心模式;反之如果分布很平,可能会拉进来几十个词。

🧠 这才是真正的智能适应 :模型自己决定这一步该不该“冒险”。


代码长什么样?真没你想得复杂!

下面这两个函数可以直接塞进你的生成循环里用👇

import torch
import torch.nn.functional as F

def top_k_sampling(logits, k, temperature=1.0):
    logits = logits / temperature
    probs = F.softmax(logits, dim=-1)

    # 取 top-k
    top_k_probs, top_k_indices = torch.topk(probs, k)

    # 构造掩码
    masked_probs = torch.zeros_like(probs).scatter_(-1, top_k_indices, top_k_probs)
    masked_probs = masked_probs / masked_probs.sum()  # 归一化

    return torch.multinomial(masked_probs, num_samples=1).item()
def top_p_sampling(logits, p, temperature=1.0):
    logits = logits / temperature
    probs = F.softmax(logits, dim=-1)

    # 降序排列
    sorted_probs, sorted_indices = torch.sort(probs, descending=True)
    cumulative_probs = torch.cumsum(sorted_probs, dim=-1)

    # 找出累积不超过 p 的部分
    nucleus = cumulative_probs <= p
    if not nucleus.any():
        nucleus[0] = True  # 至少保留一个

    # 截断并归一化
    truncated = sorted_probs.masked_fill(~nucleus, 0)
    truncated = truncated / truncated.sum()

    # 采样后再映射回原索引
    sample_idx = torch.multinomial(truncated, num_samples=1)
    return sorted_indices[sample_idx].item()

是不是比你想象中简洁多了?⚡️
而且你会发现,Hugging Face 的 transformers 库调用起来更是傻瓜式操作:

from transformers import pipeline

gen = pipeline("text-generation", model="gpt2")

output = gen(
    "夜空中最亮的星",
    max_new_tokens=50,
    do_sample=True,
    top_k=50,
    top_p=0.95,
    temperature=0.8
)

一行代码搞定整个解码策略,背后的原理却值得深挖。


实战中怎么搭配使用?有讲究!

很多人以为 Top-k 和 Top-p 是二选一,其实它们经常一起上阵,形成“双重保险”。

在 Hugging Face 中,默认逻辑是:

先做 Top-k,再在结果上做 Top-p

也就是说,你可以这样配置:

top_k=40, top_p=0.9

意思是:先取前40个高概率词,然后再从这里面挑出累计概率达到90%的核心子集进行采样。

🎯 这样做的好处是:
- 防止 Top-p 在极端情况下纳入太多低概率词(比如分布太散的时候);
- 同时避免 Top-k 把一些合理但略低频的词(如“绽裂”“吐蕊”)提前剔除。

🔧 小技巧分享:
- 写新闻/摘要: top_p=0.7~0.8 , temperature=0.7 —— 稳重靠谱
- 创作诗歌/小说: top_p=0.95 , temperature=1.0~1.2 —— 放飞灵感
- 客服机器人: top_k=20 , temperature=0.5 —— 礼貌又不出错

💡 经验之谈:别迷信默认值!不同模型对参数敏感度差异很大。Llama 系列通常需要更高的 top_p 才能激活创造力,而某些小模型在 temperature > 1.0 时就会语无伦次。


工程落地时要注意哪些坑?

别以为加个参数就万事大吉,真实部署中有不少细节容易踩雷:

🔴 排序开销不能忽视
Top-k/p 都需要排序操作,尤其是 Top-p 还要算累积和。对于词汇量动辄数万的大模型,每次生成都要排序一次,积少成多也很可观。

✅ 解决方案:
- 使用部分排序(partial sort),只排前几百个就够了;
- GPU 上可用 torch.topk 优化实现;
- 推理引擎如 vLLM 已内置高效采样器。

🟢 安全边界必须设防
即使有了 Top-p,也不能完全防止模型说出不该说的话。比如某些敏感词可能恰好落在高概率区。

✅ 建议做法:
- 结合 bad_words_ids 参数屏蔽特定 token;
- 或使用后处理规则过滤输出;
- 更高级的可以用 contrastive search 或 guided decoding 引导方向。

🟡 别忘了终止条件
开启采样后,生成路径变得不确定,可能导致无限输出或提前截断。记得设置:
- max_new_tokens
- eos_token_id
- 必要时启用 repetition_penalty


总结一下:为什么这俩技术这么重要?

我们可以把大模型看作一个才华横溢但偶尔失控的作家。

  • 没有采样 → 太保守,只会抄范文;
  • 完全随机 → 太疯癫,语无伦次;
  • Top-k → 给他划定一个“安全创作区”;
  • Top-p → 让他自己判断什么时候该收敛、什么时候该发散。

它们共同完成了一个关键任务: 在创造性和可控性之间找到黄金平衡点

如今,无论是 ChatGPT、Claude 还是国内的通义千问、讯飞星火,背后都离不开这类采样策略的支持。甚至很多“高级玩法”,比如:
- 对比搜索(Contrastive Search)
- Diverse Beam Search
- Logit Bias 引导

都是在 Top-k/p 的基础上进一步演进而来。

所以啊,下次当你惊叹于某个 AI 回答既有趣又不失分寸时,不妨想想——也许正是那个默默工作的 Top-p 采样器,在关键时刻说了一句:“这个想法不错,但我们还是别说‘狗跑月亮吃哲学’了吧。” 🐶🌕🍝


掌握 Top-k 和 Top-p,不只是学会两个参数,更是理解了 如何与不确定性共舞 。而这,正是构建真正智能系统的起点。✨

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐