第 18 环 · 采样(温度与 Top-K)
同一个圆心:一台开机只有一个 AI 的手机。上一环芯片已经能从残差流吐出下一个 token,并且一轮轮往下写、写到停止符才停——「说话」这件事在芯片上闭合了。但每一轮它都是 argmax:只取分数最大的那个字。问题在于,这等于把分数的分布抹平了——第二高分的候选永远没机会,同一段开头永远写出同一句话,那不是在说话,是在复读。真模型是按分数的分布抽签:温度高时谁都可能被抽到(话说得活),温度低时几乎只取最大(话说得准);Top-K 则先砍掉长尾里那些几乎不可能的候选,只在最有希望的 K 个里抽,免得抽到荒谬的字。这一环把「抽签」做成片上的采样器——同一个开头能写出不一样的句子。
延续「端上紧凑、全定点、每步硬件自洽」的主线,新增一个引擎,接在上一环那一页里:
· 采样器(sampler)——输入 LM 头的 logits,输出采样出的 token:温度把 logits 右移 temp_shift 位(移得越多分布越平,候选越容易被翻盘);Top-K 让硬件同时找出最高分与次高分两个候选,只有两者差距 margin ≤ 1 才允许抽签,否则稳取 top-1(分布太尖,没得选);抽签用片上 LFSR(8bit xorshift),每采样一次走一步;
· 软硬协同分工——「分数怎么算」(LM 头投影)是引擎的活;「在分数上怎么选」(温度 + Top-K + 抽签)也是硬件的活;核只负责把 LM 头的结果送进采样器、把采样出的字登记进生成循环;
· 只扩不推翻——寄存器接在第十六圈页内(0x464 起,同 bit10 守卫),前 17 圈的地址映射一个字不动,解码循环的其余部分完全不变。
新增 rtl/sampler.sv:片上采样器(温度 + Top-2 候选 + LFSR 抽签)。
scaled[t] = logits[t] >>> temp_shift # 温度:右移压平分布(有符号)
top1/top2 = 最高分 / 次高分候选(并列取小下标,保证确定性)
margin = top1 - top2
flip_en = (top_k >= 2) && (margin <= 1) # 分布太尖就不抽,稳取 top-1
coin = lfsr[0] # 抽签:看随机位
token = flip_en ? (coin ? top2 : top1) : top1
lfsr_next = {lfsr[0]^lfsr[2]^lfsr[3]^lfsr[4], lfsr[7:1]} # 每采样一次走一步
min_bus.sv 挂载并新映射(仍在 LM 头 / 词嵌入页内,0x464 起):
0x464 → SMP_TEMP 温度(logits 右移位数,0=原始温度) 0x468 → SMP_TOPK Top-K(1=只取最大;2=最高+次高,可抽签) 0x46C → SMP_SEED 写 LFSR 种子(单拍装载,复位 0xA5) 0x470 → SMP_START 写 1 → 采样一次(锁结果 + LFSR 前进一步) 0x474 → SMP_SRC logits 源:0=核写入的 SMP_L0..3;1=直接取 LM 头 0x478 → SMP_TOKEN 读采样出的 token(不只 argmax) 0x47C → SMP_T1 读最高分候选号 0x480 → SMP_T2 次高分候选号 0x484 → SMP_MARGIN 读 top1-top2 分差 0x488 → SMP_FLIP 本次是否翻到 top-2 0x48C → SMP_FLIPS 读累计翻盘次数 0x490 → SMP_LFSR 当前 LFSR 状态 0x494 → SMP_BUSY 读采样器忙闲(含刚下发那拍,无空窗) 0x498~0x4A4 → SMP_L0..L3 核写入的 logits(备选源)
最小 OS 只改「吐 token」这一步,其余解码循环原样不动:
SMP_TEMP=2、SMP_TOPK=2、SMP_SEED=0xA5、SMP_SRC=1(直接取 LM 头 logits)
L_gen_body: ... 两层块 → LM_X → LM_START → 读 LM logits
→ SMP_START(温度右移 + Top-2 + LFSR 抽签)
→ 读 SMP_TOKEN = 采样出的字(不再读 LM_TOKEN)
→ GEN_STEP_W 登记 → 未 done 就 j L_gen_loop
这一环最直观的是「同样的开头,采样和贪心给出的字不一样」:
· 温度 shift = 2、Top-K = 2、种子 0xA5、停止符 3、最大步数 4、prompt = token 0;
· 第 1 步:残差流 → LM 头 logits [3,-2,4,-1]。温度右移 2 位 → [0,-1,1,-1],top1 = 2(分 1)、top2 = 0(分 0),差距只有 1 → 允许抽签;LFSR 首步最低位 = 1 → 抽到 0。上一环的 argmax 会给 2——这就是「不只取最大」的硬证据;
· 第 2 步:LFSR 走一步后最低位 = 0 → 落回 top1 = 2;
· 第 3 步:logits [7,-3,-12,8],argmax 明明是 3,但温度把它压成 [1,-1,-3,2],top1 = 3(分 2)、top2 = 0(分 1),差距又是 1 → 抽签再次翻盘到 0。温度真的改变了结果;
· 第 4 步:回到 2;4 步走完到长度上限而停(这段没吐停止符),SCREEN2 里 flips=2(第 1、3 步各翻一次盘);
· 屏幕 SCREEN = 0xFA03FB07 = 4194564871(最后一步残差流),SCREEN2 = 0x02040200,与软件参考模型逐位一致;单元仿真还覆盖了「Top-K=1 关掉抽签永远取最大」「分数拉开就不抽」「换种子改变抽签结果」「翻盘计数」四种情形。
本环走完一整圈:目标 → 一个开机只有一个对话窗口的手机,它说话得像人——同一句话开头能给出不同的说法,而不是复读机 → 硬件在 LM 头与生成控制器之间插入采样器(温度右移 + Top-K 两候选 + LFSR 抽签),OS 把「吐 token」从 argmax 改成采样,解码循环原样保留 → 采样序列与软件参考模型逐位一致、与贪心结果明显不同,单元仿真 + 协同仿真 + 回归 22 项全绿 → 回到目标。这块芯片第一次不是「只挑最大的字」,而是按温度与 Top-K 抽签说话。离一个会说话、不像复读机的 AI 手机,又近一圈。下一环接上更完整的一步:现在 KV 只放得下 4 个 token(长度一到就停),下一步把 KV 做成环形缓冲——写满了就绕回去覆盖最老的,长序列不再溢出,才能真的聊下去。
第 19 环:KV 环形缓冲 · 长序列不溢出——把 KV bank 的写指针做成环形,写满绕回覆盖最老的 K/V(滑窗注意力),让生成不再被 4 个 token 的容量卡住,才聊得下去。