第 16 环 · 自回归解码(LM 头出下一个 token)
同一个圆心:一台开机只有一个 AI 的手机。上一环把「多层块」补完整了——每一层自己投影、自己 K/V、自己注意力。但整条 Transformer 的最后一步始终缺着:真模型靠一个 LM 头(输出词表投影)把最终隐藏状态投到词表上,得到每个候选字的分数,再取分数最大的那个作为下一个 token;然后把这个 token 变成下一轮的输入,一个字一个字往下写(自回归解码)。没有这一步,芯片算完了也不知道该说哪个字。这一环把最后这一步接上,并且让「输出」真的变成「下一轮的输入」——逐字生成在芯片上闭合。
延续「端上紧凑、全定点、权重 INT4、激活 INT8」的主线,新增两个引擎,并给它们另开一页寄存器:
· LM 头(lm_head)——输入是最终残差流 x(D 个 lane,各 INT8),配一张词表投影矩阵 W_lm(V 行 × D 列,INT4),一次算出 V 个 logits(32bit 有符号累加);同一拍在硬件里做 argmax,报出 next_token(分数并列时取下标小的,保证确定性);
· 词嵌入表(token_emb)——token 号 → 一个 D 维向量(正是下一轮 Transformer 的输入)。核把表写进来,给一个 token 号,硬件组合读立刻返回对应向量(零延迟);
· 只扩不推翻——新增一页寄存器(0x400 起),用 bit10 守卫把它和前 15 圈的老映射隔开,老地址一个字不动。
新增 rtl/lm_head.sv:词表投影 + argmax;新增 rtl/token_emb.sv:片上词嵌入表。
LM 头:logits[t] = Σ_i W_lm[t][i] · x[i] (INT4 权重 × INT8 激活,32bit 累加)
next_token = argmax_t logits[t] (并列取最小下标)
start 单拍 → 锁存 logits/next_token → 下一拍 done
词嵌入:we/waddr/wdata 写表;token → x_out 组合查表(零延迟)
min_bus.sv 挂载并新映射(LM 头 / 词嵌入页,0x400 起,页选 = addr[10]):
0x400 → LM_X 输入:最终残差流(4×8bit) 0x404/0x408 → LM_W_LO/HI 词表投影矩阵(V×D INT4,行优先) 0x40C → LM_START 写 1 → 触发 LM 头投影 + argmax 0x410~0x41C → LM_LOGIT0~3 读 V 个 logits(各 32bit 有符号) 0x420 → LM_TOKEN 读 argmax 出的下一个 token 号 0x424 → LM_BUSY 读 LM 头忙闲(含刚下发那拍,无空窗) 0x428 → EMB_ADDR 写目标 token 行号 0x42C → EMB_W 写该行的嵌入向量(4×8bit) 0x430 → EMB_TOK 写要查的 token 号 0x434 → EMB_X 读该 token 的嵌入向量(组合查表) 0x43C → SCREEN2 第二观察点(token2 / token1 / K bank 长度)
最小 OS 走完整自回归闭环(prompt → 两层块 → LM 头 → token → 查嵌入 → 再跑两层块 → LM 头 → token):
解码步 1:EMB_TOK = prompt(0) → x = E[0]
两层块(每层:LST_X → 自己投影 Q/K/V → K 进 K bank / V 进 V bank
→ QK^T → Softmax → 加权 V → 残差LN → FFN → 残差LN → XSR)
→ LM_X = 最终残差流 → LM_START → 读 LM_TOKEN = token1
解码步 2:EMB_TOK = token1 → x = E[token1](输出变成下一轮输入)
两层块(第 2 步注意力覆盖两步累积在 K/V bank 的 K/V)
→ LM_START → 读 LM_TOKEN = token2
收尾:SCREEN = 最终残差流;SCREEN2 = (token2, token1, K bank 长度)
这一环最直观的是「芯片真的自己写出了两个字」,每一步都能手算对得上:
· 词表投影矩阵 W_lm = [[0,0,1,0],[0,1,-1,-1],[1,0,-1,0],[-1,0,0,-1]],词嵌入表 E[0..3] = [6,-4,3,-5] / [2,5,-1,3] / [-3,1,4,-2] / [5,-6,2,1];
· 解码步 1:prompt = token 0 → x = E[0] = [6,-4,3,-5];两层块跑完,最终残差流 [7,-5,3,-6];LM 头投影得 logits [3,-2,4,-1],argmax 出 token1 = 2;
· 解码步 2:查 E[2] = [-3,1,4,-2] 当作新一轮输入;再跑两层块——这一回每层的 K bank 里已经躺着上一步的 K(长度由 1 累积到 2),注意力真的在「两步的 K/V」上算分;最终残差流 [-5,1,7,-3],LM 头得 logits [7,-3,-12,8],argmax 出 token2 = 3;
· 屏幕 SCREEN = 0xFD0701FB = 4245094907,SCREEN2 = 0x030202(token2=3、token1=2、K bank 长度=2),与软件参考模型逐位一致。
注:LM 头的 argmax 结果真的在驱动下一轮——如果 argmax 错了,查到的嵌入就变了,最终残差流和屏幕立刻不同;SCREEN2 里的 K bank 长度也证明 KV Cache 是随解码步增长的。
本环走完一整圈:目标 → 真模型最后一步是 LM 头选下一个 token、再把它变回输入,缺了这步芯片就「会算不会说」 → 硬件加上词表投影 + argmax 的 LM 头与片上词嵌入表,并把 token 号接回下一轮输入 → 两步自回归解码端到端跑通、单元仿真 + 协同仿真 + 回归 18 项全绿 → 回到目标。这块芯片第一次自己吐出了下一个字,而且吐出的是「下一轮的输入」。离一台本地逐字生成、只有一个对话窗口的手机,又近一圈。下一环接上最后一根线:现在解码只在 OS 里手写了两步,下一步把它变成一个真正的解码循环——不停出 token、不停追加进 KV bank,直到吐出停止符,才把一整句话交给屏幕。
第 17 环:连续生成 · 解码循环与停止符——把「两步解码」写成真正的循环,让芯片一个 token 一个 token 地往下写,每写一个就追加回 KV bank,直到遇到停止符才停,形成完整「连续生成」的推理闭环。