第 17 环 · 连续生成(解码循环与停止符)
同一个圆心:一台开机只有一个 AI 的手机。上一环芯片已经能「吐出一个 token」——LM 头把最终残差流投到词表、argmax 出下一个字,再把这个字变回下一轮输入。但那只是一步,而且那两步是手写死在 OS 里的展开代码。真模型的生成是一段:吐一个字 → 喂回去 → 再吐 → 再喂回去……一直写到遇到结束符(EOS)或写满长度上限为止。一句话不是一个字,是一串字。这一环把「写到什么时候停」从核的软件循环里拎出来,做成片上的硬件,并让 OS 真的跑一个解码循环——连续生成在芯片上闭合。
延续「端上紧凑、全定点、权重 INT4、激活 INT8、每步硬件自洽」的主线,新增一个引擎,接在上一环那一页里:
· 生成控制器(gen_ctrl)——核每完成一步解码就把刚生成的 token 交给它「登记」;硬件自己记步数(count)、自己比对停止符(stop_hit):命中停止符、或到达最大步数,立刻拉高 done。核只要看着 done 决定要不要跳回循环开头,这就是一个真正的解码循环;
· 软硬协同分工——「往前走一步」(投影、注意力、LM 头)是引擎的活;「走到什么时候停」(记步数、比停止符)也是硬件的活;核只负责「把上一轮的输出喂回下一轮的输入」这条数据通路;
· 只扩不推翻——寄存器接在第十六圈页内(0x440 起,同 bit10 守卫),前面 16 圈的地址映射一个字不动。
新增 rtl/gen_ctrl.sv:片上生成控制器(步数 + 停止符 + 长度上限)。
start 单拍 → count=0、stop_hit=0、done=0、running=1
commit 单拍 → tok_out ← tok_in、count ← count+1
若 tok_in == stop_tok → stop_hit=1、done=1、running=0
否则若 count+1 >= max_steps → done=1、running=0
done 一旦拉高保持到下次 start(running=0 屏蔽之后的 commit)
min_bus.sv 挂载并新映射(仍在 LM 头 / 词嵌入页内,0x440 起):
0x440 → GEN_START 写 1 → 重新开始一段连续生成(计数清零、running=1) 0x444 → GEN_STOP_TOK 写停止符(EOS)token 号 0x448 → GEN_MAX_STEPS 写最大生成步数(长度上限) 0x44C → GEN_STEP_W 写一个刚生成的 token → 登记(计数+1、判停止符/上限) 0x450 → GEN_COUNT 读已生成个数 0x454 → GEN_TOK_OUT 读最后登记的 token 0x458 → GEN_STOP_HIT 读「是否因命中停止符而停」 0x45C → GEN_DONE 读「这段生成是否已结束」 0x460 → GEN_RUNNING 读「是否仍在生成」
最小 OS 从「手写死的两步展开」改成真解码循环:
GEN_STOP_TOK = 3、GEN_MAX_STEPS = 4、GEN_START
第 1 轮输入 = prompt(0) 的嵌入 → j L_gen_body
L_gen_loop: EMB_TOK ← 上一轮生成的 token → x = E[token]
(把刚吐出的字喂回输入,这才叫自回归)
L_gen_body: 两层块(每层:自己投影 Q/K/V → K 进 K bank / V 进 V bank
→ QK^T → Softmax → 加权 V → 残差LN → FFN → 残差LN → XSR)
→ LM_X = 本轮最终残差流 → LM_START → 读 LM_TOKEN = 本轮的字
→ GEN_STEP_W 登记(硬件 count++、比停止符)
→ 读 GEN_DONE:done → 跳出;未 done → j L_gen_loop(继续写下一个字)
L_gen_done: SCREEN = 最后一步的最终残差流;SCREEN2 = (count, tok_out, stop_hit)
这一环最直观的是「OS 不再手写两步,而是真的在绕圈」,而且能一眼看出是被停止符叫停的(不是写满长度):
· 停止符设 3、最大步数设 4、prompt = token 0,词表与词嵌入表沿用上一环;
· 第 1 圈:x = E[0] = [6,-4,3,-5];两层块跑完得残差流 [7,-5,3,-6],LM 头 logits [3,-2,4,-1] → 吐出 2;登记后 count=1、2 ≠ 3、1 < 4 → 未停,j L_gen_loop;
· 第 2 圈:查 E[2] = [-3,1,4,-2] 喂回输入;每层的 K bank 里已躺着第 1 圈的 K(长度由 1 累积到 2),注意力真的在「两圈的 K/V」上算分;残差流 [-5,1,7,-3],LM 头 logits [7,-3,-12,8] → 吐出 3;登记后 count=2、3 == 停止符 → stop_hit=1、done=1 → 跳出循环;
· 注意:如果不设停止符,循环会一直写到长度上限 4(KV bank 容量);这里第 2 圈就被停止符叫停,正是「遇到停止符才停」;
· 屏幕 SCREEN = 0xFD0701FB = 4245094907(最后一步残差流),SCREEN2 = 0x020301(count=2、tok_out=3、stop_hit=1),与软件参考模型逐位一致;单元仿真还覆盖了「长度上限停」「重新 start 清零」「done 之后 commit 无效」「第一步就命中停止符」四种情形。
本环走完一整圈:目标 → 一个开机只有一个对话窗口的手机,它得能「说一整句话」,而说话就是一轮轮往下写、写到该停才停 → 硬件加上生成控制器(记步数 + 比停止符 + 长度上限),OS 把手写死的两步改成真解码循环,每写一个字就追加进 KV bank → 解码循环端到端跑通、命中停止符而停,单元仿真 + 协同仿真 + 回归 20 项全绿 → 回到目标。这块芯片第一次能自己绕圈往下写,而且知道什么时候该停。离一台本地连续生成、只有一个对话窗口的手机,又近一圈。下一环接上更聪明的一步:现在每个字都取分数最大的那个(greedy),下一步加采样——温度与 Top-K,让同一个开头能写出不一样的句子,AI 才像在说话,而不是在复读。
第 18 环:采样 · 温度与 Top-K(不只取最大)——把「永远取 logits 最大的那个字」换成「按温度缩放后再从 Top-K 里采样」,让同一段开头能写出不同的句子;芯片上补一条采样通路,才像在说话。