这篇重点学什么
本篇重点:先累计每项概率,再用二分查找选中一次采样结果;检查边界输入和调用预算,观察生成结果的随机性。
开始读正文 ↓学过相近内容?查看哪些可以跳过
两条路线有些基础相同。已经学会的部分可以跳过,另一条路线只用来补你当前需要的内容。
概率与 softmax
共同基础概率归一化与数值稳定性是共同基础;采样和求梯度是不同任务。
另一条路线补充什么?
先推导交叉熵与 p − one-hot 梯度,再比较把多条损失求和或取平均(loss reduction)会怎样改变训练。
读完这篇,你能做什么
能只改变一种生成选项进行重复比较,报告字段合格率、差异和实际用量。
学习理由、准备要求与知识点
为什么学它
调整采样可以改变回答的变化程度,却不能代替事实检查。要靠重复比较判断参数效果,而不是把某个设置当作可靠保证。
先读这些内容
知识点清单
- 候选 Token 分数
- 概率归一化与 softmax
- 贪心与采样
- Temperature
- top-k 与 top-p
- 输出长度限制
- 全任务 Token 预算
- 种子及可重复性边界
运行前先确认完整算法使用 Java 17 标准库;JDK 源码基线为 OpenJDK 17 GA。示例是内存中的机制模型,外部服务接入需要另外配置和验证。
01先弄懂它是什么
模型生成回答时,通常先给下一个 token 的候选打分,再按规则选一个。贪心解码每次选最高分,采样则按概率分布随机选。选出的 token 会加入上下文,下一步的候选分布也随之改变。所以整段回答是一步步生成的,不是从固定句子库里抽出一条。
几种常见设置控制的是选候选的方式:Temperature 通常通过缩放分数,让分布更集中或更分散;top-k 限制候选数量;top-p 保留累计概率达到阈值的高概率候选。应用顺序和支持哪些参数要看具体实现。温度低不会自动核实事实,温度高也不保证更有创意,还是要在同一任务和评估标准下比较效果。
输出长度上限、单次上下文和全任务调用预算分别控制不同资源。随机生成可能出现更长回答、额外工具轮次或不稳定格式,因此预算约束必须由程序维护。种子有助于重现实验,但远端模型版本、批处理、硬件和实现变化仍可能影响结果,不能对所有服务承诺逐字相同。
文中这几个词,先认一认
- 候选分布
- 各个候选被选择的概率安排:每项非负,归一化后全部相加为一。
- 累积分布
- 按固定候选顺序把概率逐项加起来,形成一段段区间;区间越长,抽中机会越大。
- 解码策略
- 根据当前候选分数决定下一 token 的规则,例如贪心或采样;它本身不会检查事实。
02看看它怎么工作
假设三个候选权重为二、一、一,归一概率为二分之一、四分之一、四分之一。把[0,1)切成[0,0.5)、[0.5,0.75)、[0.75,1),随机数落在哪段就选哪个候选。零权重区间长度为零,不应被选到,这决定了二分查找必须找第一个严格大于u的累计值。
构建累计表只需一遍;以后每次选择通过二分把候选范围缩半。将随机数作为函数参数,可以直接测试边界0、0.5和接近1,而不依赖“多抽几次似乎正确”的不稳定测试。随机源和确定性映射分离,也是排查采样偏差的好方法。
权重先除以最大值再求和,避免两个巨大有限数相加溢出。全部为零、负数或非有限值明确拒绝。浮点累加会有舍入误差,将最终累计值固定为一用于封闭范围;这不使浮点概率完全精确,极小权重可能因表示精度被压成零宽区间。
正在加载流程图…
查看流程图源码(Mermaid)
flowchart LR
L["候选分数"] --> F["温度及候选过滤"]
F --> W["非负权重"]
W --> C["累计概率表"]
U["随机数 u"] --> B["二分定位区间"]
C --> B
B --> T["选择 Token 并更新预算"]按顺序看,每一步做了什么
- 记录分数处理顺序、采样参数和模型版本。
- 得到经过过滤的非负权重,检查是否仍有候选。
- 构建归一累计表,固定候选顺序。
- 从[0,1)取随机数,查找第一个严格更大的累计值。
- 生成后更新调用与Token账本,达到任务上限即终止。
03对照实现代码
OpenJDK 17 GA,jdk-17+35 · Random.nextDouble
下方为原创 Java 教学片段,调用已核验的 JDK API 说明局部机制;不是该类原样源码,也不实现神经网络。完整程序见算法模块。
Random random = new Random(42L); // 固定种子便于本地实验复现。
double u = random.nextDouble(); // 取值范围包含 0,不包含 1。
int selected = draw(cdf, u); // 随机源与区间查找分开测试。
// Random 不是用于凭证或安全令牌的密码学随机源。Random.nextDouble从伪随机位构造[0,1)范围内的double,源码给出了确定的生成机制。上界不包含一,使最后一个正宽区间能够用统一规则处理。
同一种随机生成器、相同种子和相同调用顺序可复现实验序列;如果额外调用一次随机数,后续选择都会移动。因此记录种子还不够,要固定整个实验路径。
真实模型服务不一定使用java.util.Random,也不一定暴露同等种子语义。Java程序只验证累积分布的选择机制,不能据此承诺某个远端服务重复输出。
普通伪随机源满足教学采样需求,却不应生成安全令牌;安全性质与统计分布是不同契约。这里没有把采样行为赋予身份或授权含义。
代码先调用谁,再调用谁
- 配置固定权重和候选顺序
- 归一后建立累计表
Random.nextDouble提供u- 二分查找累计区间
- 返回候选索引并更新任务计量
04动手写一个例子
累积分布二分采样
输入非负有限权重建立累计表,再输入u∈[0,1)返回候选索引。空分布和全零权重拒绝。Java17 Main.java通过固定u运行自测,不依赖随机碰巧命中。
- 输入 1
权重[2,1,1],u=0.5- 输出 1
索引1
- 输入 2
权重[0,1,0],u=0- 输出 2
索引1
最大值缩放不改变相对权重。先算缩放后总量,再逐项累加归一比例得到非递减累计表,最后项设为一。
二分保持答案在[lo,hi]中:若cdf[mid]严格大于u,答案可能在左边或mid;否则mid及更左位置都不能满足严格比较,将lo移到mid+1。
零权重产生相等累计值,严格大于规则自动跨过这些平台。只找大于等于会在u为零或精确边界时选到零概率项,边界测试因此比大量随机样本更直接。
为什么这样写是对的累计表非递减且末项为一;二分过程中所有小于lo的位置累计值不大于u,答案始终是第一个累计值大于u的位置。
n为候选数量。构建表需要几次线性扫描;m次抽样共O(n+m log n)。这不包括神经网络计算分数和top-p排序成本。
保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。
public class Main {
static double[] build(double[] weights){
if(weights.length==0)throw new IllegalArgumentException("empty");
double max=0;for(double w:weights){
if(!Double.isFinite(w)||w<0)throw new IllegalArgumentException("weight");
max=Math.max(max,w);
}
if(max==0)throw new IllegalArgumentException("all zero");
double sum=0;for(double w:weights)sum+=w/max; // 先缩放再求和。
double[] cdf=new double[weights.length];double prefix=0;
for(int i=0;i<weights.length;i++){prefix+=(weights[i]/max)/sum;cdf[i]=Math.min(1,prefix);}
cdf[cdf.length-1]=1;return cdf; // 收束浮点累计的尾部误差。
}
static int draw(double[] cdf,double u){
if(!Double.isFinite(u)||u<0||u>=1)throw new IllegalArgumentException("u");
int lo=0,hi=cdf.length-1;
while(lo<hi){int mid=lo+(hi-lo)/2;
if(cdf[mid]>u)hi=mid;else lo=mid+1; // 必须严格大于,跳过零宽区间。
}
return lo;
}
static void eq(int e,int a){if(e!=a)throw new AssertionError(a);}
public static void main(String[] args){
double[] c=build(new double[]{2,1,1});
eq(0,draw(c,0));eq(1,draw(c,0.5));eq(2,draw(c,Math.nextDown(1.0)));
eq(1,draw(build(new double[]{0,1,0}),0));
eq(1,draw(build(new double[]{Double.MAX_VALUE,Double.MAX_VALUE}),0.5));
try{build(new double[]{0,0});throw new AssertionError();}catch(IllegalArgumentException expected){}
try{draw(c,1);throw new AssertionError();}catch(IllegalArgumentException expected){}
System.out.println("PASS 7 tests");
}
}这些特殊情况也要试一试
- u等于一不属于输入契约,必须拒绝。
- 零权重不能因为累计值相等而被选中。
- draw只接收build产生的合法累计表,外部表应另加验证。
进阶选读 · 后续再读:动态分布与大量抽样
场景分布固定但抽样次数很大,或者每次都有少量权重变化。
做法固定分布可研究别名表,O(n)预处理后常数时间选择;动态权重可用树状数组维护前缀和并按累计质量定位。
复杂度别名表O(n)空间、单次O(1);树状数组更新与选择O(log n),但浮点误差和非负总量仍需处理。
好处与代价复杂结构增加实现和校验成本;候选每步全部变化时,预处理仍需重做,不能只报告抽样内核速度。
05项目里怎么用
调参实验一次只改一个因素,保留提示、数据与模型配置。先用格式合规和禁止行为作为硬门槛,再比较任务质量、输出长度与失败率。更随机的输出可能增加风格多样性,也可能增加无关内容,不能只挑最好的一次展示。
一次任务用了多少预算,要把修复、重试和子任务的调用一起算进去;没有返回用量的部分标为未知。输出上限太小,JSON 可能还没写完就被截断;上限过大,也可能带来更多等待和费用。可以先观察实际输出长度,再为超限情况设计明确的错误状态和可恢复展示,避免把半份结果当成完成。
复现报告说明可控条件:本地权重、种子、代码版本和调用顺序可固定;远端服务的内部实现可能不可见。出现差异时先确认输入和配置,再分析随机变化;不要把所有差异都解释成模型升级,也不要把一次相同结果当作完全确定性的证明。
容易踩的坑
- 把低温度当作事实正确的保证。
- 忽略top-k与top-p的实现顺序及支持边界。
- 二分使用大于等于,选中零权重项。
- 只记录种子,不记录代码路径与模型配置。
几种做法,怎么选
表格有四列,窄屏可左右滑动查看代价和适用条件。
| 方案 | 优势 | 代价与限制 | 适用条件 |
|---|---|---|---|
| 贪心 | 输出选择简单,便于建立基线 | 可能缺少多样性,仍会出错 | 格式或确定任务的对照组 |
| 概率采样 | 可探索多种候选表达 | 波动更大,需要重复评价 | 开放生成与多候选方案 |
| 固定候选表采样 | 易测边界与概率机制 | 不代表真实模型随上下文变化 | 解码算法教学 |
06检验是否读懂了
先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。
先用自己的话解释
01Temperature调整的是事实来源吗?
展开参考答案与推导
不是,它改变候选分数或分布形状;资料缺失仍需检索和核验,采样参数无法补出可靠来源。
02累计表为什么允许相等值?
展开参考答案与推导
零权重对应零宽区间,相邻累计值可相等;二分必须寻找严格超过随机数的第一项。
03种子为什么不是完整复现条件?
展开参考答案与推导
生成器类型、调用顺序和输入分布也会影响序列,远端模型与计算实现还可能变化。
换个情况,还会处理吗
01重复运行格式偶尔失败,怎么办?
展开参考答案与推导
保留失败样本,固定提示和参数检查契约;比较多次运行通过率,并由应用校验输出,而非仅降低温度。
02抽样很快但整体生成仍慢,查哪里?
展开参考答案与推导
分离模型分数计算、候选过滤、抽样和网络时间;本题二分只是其中一小步,不能据此推断端到端性能。
03预算用完但还在自动修复,哪里缺约束?
展开参考答案与推导
修复流程没有共享任务级账本或停止条件;将每次调用预留和实际用量纳入统一预算。
写代码试一试
01精确边界应选哪项?
- 输入 / 条件
权重[1,1],u=0.5- 输出 / 预期
索引1
提示 区间左闭右开。
展开参考答案与推导
首区间[0,0.5),第二个区间从0.5开始,因此找第一个cdf>u,不能返回首项。
02怎样验证全零权重?
- 输入 / 条件
[0,0,0]- 输出 / 预期
拒绝
提示 先找最大值。
展开参考答案与推导
最大值为零说明不存在正质量候选,无法归一化;不要把它自动改成均匀分布而改变业务语义。
03增加平移分数不变性应在哪一层测?
- 输入 / 条件
logits全部加常数- 输出 / 预期
Softmax概率不变
提示 不是在原始权重上相加。
展开参考答案与推导
对logits做共同平移再Softmax会抵消;直接给权重加常数会改变概率比例,两个操作不能混淆。
读完之后,独立完成
课后作业:自己做一遍
本篇共 9 道作业,先完成 7 道基础与综合任务,再按前置知识挑战进阶题。其中包含 1 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。
能只改变一种生成选项进行重复比较,报告字段合格率、差异和实际用量。
本篇重点加练
候选截断与全任务预算共同影响生成策略,需要把局部设置放回完整任务衡量。
- top-k 与 top-p
- 全任务 Token 预算
重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。
先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。
基础必做
- 作业 01原理问答
给四个候选Token分数[3,1,0,-2],解释分数、归一化概率和选中结果的关系,并列出数值计算需要处理的异常输入。
- 提交内容
- 一份独立书面回答或标注图,包含自造例子、概念边界及判断依据。
- 怎样算完成
- 逐项回应题目要求,区分事实、假设与个人判断。
- 能用自己的例子解释适用条件,记录尚未弄懂的点。
练到的知识点候选 Token 分数 · 概率归一化与 softmax
- 作业 02代码推演
对同一分数向量设置不同Temperature,分别推演贪心与采样的执行步骤;记录随机数参与的位置,不预先假定实验结论。
- 提交内容
- 手工推演记录,写明输入约定、中间状态、最终判断;可运行时附程序对照记录。
- 怎样算完成
- 推演包含中间过程,数据形状、单位、顺序或状态前后一致。
- 主动检查至少一个边界或异常条件,能解释手算与运行差异。
练到的知识点贪心与采样 · Temperature
综合应用
- 作业 03面试场景
为简短分类答案与开放式总结选择候选截断和长度限制,列出top-k、top-p与输出上限的验收指标及失败样本。
- 提交内容
- 场景分析与决策记录,包含信息缺口、约束、备选方案和验证计划。
- 怎样算完成
- 方案回应给定约束,列出至少一种替代方案及代价。
- 每项关键结论说明所需证据,不能核实的部分明确标注。
练到的知识点top-k 与 top-p · 输出长度限制
- 作业 04编程实践
实现带任务总预算的假采样器,固定输入与随机种子,连续运行多轮并核算消耗;测试中途预算不足和相同种子重复运行。
- 提交内容
- 可复现的最小程序或实验材料、自写测试、环境说明与运行记录;题目允许纸面实验时提交完整案例表。
- 怎样算完成
- 提交物按说明可以重现;测试覆盖正常、边界与失败场景。
- 运行证据与断言能核验声明的行为,注明模拟与真实执行范围。
练到的知识点全任务 Token 预算 · 种子及可重复性边界
- 作业 05排错实验
构造概率边界恰好落在累计区间端点的输入,记录采样器选择规则;再改变候选顺序检查实现是否遵守你声明的契约。
- 提交内容
- 最小故障输入、复现步骤、观测记录、定位依据及回归用例。
- 怎样算完成
- 其他人可按记录复现故障,并区分现象与已验证原因。
- 定位过程有观测依据,回归用例可以再次暴露原问题。
练到的知识点概率归一化与 softmax · top-k 与 top-p
- 作业 06编程实践
自行构造三至五步合法概率表,每一步候选分布不同,声明 EOS、每步预算计量、总预算和随机源;模拟多步任务,对比两组自选 top-k
/top-p策略的实际选择、停止位置和剩余预算。- 提交内容
- 可复现的小程序或题目允许的离线实验、自写测试、输入和运行记录。
- 怎样算完成
- 输入、环境和运行过程可重现,测试覆盖声明的重点条件。
- 验证目标由合同确定,模拟结果不冒充真实服务证据。
练到的知识点top-k 与 top-p · 全任务 Token 预算
- 作业 07面试场景
一个回答阶段希望扩大候选多样性,但任务还必须留下验证步骤的预算。制定预算与候选策略的联合合同,说明质量不达标时如何记录而非无限重试。
- 提交内容
- 场景评审记录,包含约束、备选方案、未决信息和验证计划。
- 怎样算完成
- 方案回应题目约束,取舍能对应到可核验的事实。
- 给出失败或条件变化时的检查方式。
练到的知识点top-k 与 top-p · 全任务 Token 预算
进阶挑战 · 掌握前置后选做
- 作业 08设计评审
在固定小评测集上比较三种采样设置,联合报告成功率、输出长度和波动,并说明种子不能控制的外部因素。
- 提交内容
- 设计评审文档或对照实验报告,包含前置条件、方案取舍、验收证据与尚未验证的范围。
- 怎样算完成
- 比较使用一致条件与事先声明的标准,记录失败与未知结果。
- 说明方案的适用前提、未覆盖风险及后续验证方法。
练到的知识点贪心与采样 · Temperature · 全任务 Token 预算
思维拓展
先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。
- 作业 09思维拓展 · 条件变化
如果任务从单次生成改为“生成多个候选再验证”,重新定义截断、停止与质量比较标准,评审相同单次参数是否仍有相同含义。
- 提交内容
- 变更前后条件表、重新评审的方案、检验用例与新增代价。
- 怎样算完成
- 只改变明确列出的条件,比较口径保持一致。
- 修改方案说明收益、代价与仍未验证的范围。
练到的知识点top-k 与 top-p · 全任务 Token 预算
完成后,再检验一次
给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。
继续阅读
先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。
- 必读参考文档英文
Transformers:Generation 配置参考 (新标签页)↗
查阅长度、采样与生成配置参数的定义和约束,核对所接框架真正支持哪些选项。
- 实践实践指南英文
Transformers:Generation strategies (新标签页)↗
比较贪心、采样及其他生成策略,先改变一个选项观察输出,再决定是否需要复杂搜索。
- 深入教程英文
Hugging Face:Tokenizers (新标签页)↗
阅读词级、字符级与子词分词,以及编码解码过程;用来理解字符数不能直接替代 Token 数。
- 深入参考文档英文
OpenJDK 17 Random 源码 (新标签页)↗
按指定版本定位
Random.nextDouble;对照源码分析理解实际调用边界。