← 返回首页
基础理论2026-09-26 发布 · 基础篇 v1.1(持续更新)

通用大模型架构详解:一次前向的形状流转

拿一条真实请求(6 个 token)走完一个通用 decoder-only 大模型的全流程,每一步都标出具体形状

一句话概括:一个通用大模型 = Embedding 查表 + 32 层 decoder block(注意力 + FFN + 残差)+ 最终归一化 + LM head。整个前向传播里,主数据流的形状始终是 [1, 6, 4096]——进多少层都不变;只有入口(分词)和出口(LM head)改形状。
本篇的讲法:像 Kev 决策流程那篇一样,全程用一个具体例子走到底、全程写具体数字:输入是一条 6 个 token 的真实请求,隐藏维 4096、32 层、32 个注意力头(每头 128 维)、8 组 KV 头、FFN 中间层 11008、词表 152000——这些数字取自 Qwen / Llama 系主流开源模型的典型量级。B(batch)、S(序列长)、H(隐藏维)这些字母只在解释维度含义时出现。
[1, 6, 4096]
主数据流形状:1 条请求 × 6 token × 4096 维
×32 层
decoder block 堆叠,形状进出不变
[1, 152000]
最后位置的 logits,152000 为词表大小

一、例子与参照配置

全篇用同一条请求做例子:

原始文本:「订单有问题,需要退款吗」

经 tokenizer 分词后是 6 个 token。模型用的参照配置(Qwen / Llama 系典型量级,不同模型数值有差异,结构完全一致):

配置项具体值含义
词表大小 V152000模型认识多少个 token
隐藏维 H4096每个 token 被表示成 4096 维向量(模型的"宽度")
层数 N32decoder block 堆叠 32 层(模型的"深度")
注意力头32 头 × 128 维32×128 = 4096 = H
KV 头(GQA)8 组8 组 KV 头服务 32 个 Q 头,KV Cache 省 4 倍
FFN 中间维11008先升到 11008 再降回 4096(SwiGLU 门控)
本例输入S = 6「订单有问题,需要退款吗」分词后 6 个 token,batch = 1

维度字母对照:B = batch(本例 1)、S = 序列长度(本例 6)、H = 隐藏维(本例 4096)、V = 词表(本例 152000)。

二、总流程:九个阶段(带具体数字,阶段四展开到 QKV / Attention)

flowchart TD classDef io fill:#f6f8fa,stroke:#8b949e,color:#1f2937,stroke-width:1.5px classDef stage fill:#eff6ff,stroke:#3b82f6,color:#1f2937,stroke-width:1.5px classDef attn fill:#e8f0fe,stroke:#2f6feb,color:#1a4ea8,stroke-width:1.5px classDef ffn fill:#e6f5ed,stroke:#1a7f4b,color:#155c38,stroke-width:1.5px classDef out fill:#e6f5ed,stroke:#1a7f4b,color:#155c38,stroke-width:1.5px T0["原始文本
「订单有问题,需要退款吗」"]:::io S1["阶段一 · 分词(tokenizer)
按词表切分 → 6 个 token
[订单] [有问题] [,] [需要] [退款] [吗]"]:::stage S2["阶段二 · 查词表换成整数 ID
[1533, 100157, 3493, 11, 2762, 104120](示意)
形状 [6] → 加 batch 维 [1, 6]"]:::stage S3["阶段三 · Embedding 查表
词表矩阵 [152000, 4096] 按 ID 取 6 行
形状 [1, 6, 4096]"]:::stage S4["阶段四 · 进入 32 层 decoder block
以第 1 层为例(32 层结构完全相同)"]:::stage S4a["④-a RMSNorm
[1, 6, 4096]"]:::attn S4b["④-b Q / K / V 投影
Q [1, 6, 4096] @ [4096, 4096] → 拆 32 头 [1, 32, 6, 128]
K, V @ [4096, 1024] → 8 组头 [1, 8, 6, 128](GQA)"]:::attn S4c["④-c Attention 注意力
scores = Q @ Kᵀ ÷ √128 → [1, 32, 6, 6]
因果掩码(第 3 个 token 看不到第 5 个)→ softmax
probs @ V → [1, 32, 6, 128]"]:::attn S4d["④-d 合并 32 头 → [1, 6, 4096]
输出投影 + 残差"]:::attn S4e["④-e FFN(SwiGLU)
[1, 6, 4096] → 升维 [1, 6, 11008] × 2 → 降回 [1, 6, 4096]
+ 残差"]:::ffn S4f["④-f 第 2 … 32 层:重复同样流程
形状始终 [1, 6, 4096]"]:::stage S5["阶段五 · 最终 RMSNorm
[1, 6, 4096]"]:::stage S6["阶段六 · LM head 投影
[1, 6, 4096] @ [4096, 152000]
logits 形状 [1, 6, 152000]"]:::stage S7["阶段七 · 取最后位置
只要第 6 个位置的打分
[1, 152000]"]:::stage S8["阶段八 · 采样
温度 / top-p → 下一个 token「是」"]:::out S9["阶段九 · 自回归
「是」拼回输入(现在 7 个 token)
解码阶段每步只进 1 个新 token [1, 1, 4096]
历史 KV 已缓存,无需重算"]:::stage T0 --> S1 --> S2 --> S3 --> S4 S4 --> S4a --> S4b --> S4c --> S4d --> S4e --> S4f S4f --> S5 --> S6 --> S7 --> S8 S8 -.->|"循环直到结束符"| S4
图 1 · 一条 6 token 请求的完整前向(九个阶段):形状从 [6] 出发,[1, 6, 4096] 穿过 32 层,LM head 投到 [1, 6, 152000],采样出第 7 个 token;阶段四展开到 QKV 与 Attention

三个关键观察:

三、单层 decoder block 放大:还是 6 个 token 的例子

总流程的阶段三是主体。把其中一层放大,看 6 个 token 是怎么被加工的:

flowchart TD classDef io fill:#f6f8fa,stroke:#8b949e,color:#1f2937,stroke-width:1.5px classDef norm fill:#f0ebff,stroke:#6b46c1,color:#4a2f9c,stroke-width:1.5px classDef attn fill:#e8f0fe,stroke:#2f6feb,color:#1a4ea8,stroke-width:1.5px classDef ffn fill:#e6f5ed,stroke:#1a7f4b,color:#155c38,stroke-width:1.5px X["输入 [1, 6, 4096]"]:::io N1["RMSNorm(Pre-Norm)
[1, 6, 4096]"]:::norm QKV["Q / K / V 线性投影
Q [1, 6, 4096] @ [4096, 4096] → 拆 32 头 [1, 32, 6, 128]
K, V @ [4096, 1024] → 8 组头 [1, 8, 6, 128](GQA)"]:::attn SC["注意力分数
scores = Q @ Kᵀ ÷ √128 → [1, 32, 6, 6]
因果掩码(第 3 个 token 看不到第 5 个)→ softmax
probs @ V → [1, 32, 6, 128]"]:::attn MG["合并 32 头 → [1, 6, 4096]
输出投影 [4096, 4096]
残差:加回输入 X"]:::attn N2["第二个 RMSNorm
[1, 6, 4096]"]:::norm FFN["FFN(SwiGLU 门控)
[1, 6, 4096] 升维到 [1, 6, 11008] × 2(gate / up)
SiLU(gate) × up → 降维回 [1, 6, 4096]
残差:加回子层输入"]:::ffn O["输出 [1, 6, 4096]
交给下一层,共重复 32 次"]:::io X --> N1 --> QKV --> SC --> MG --> N2 --> FFN --> O
图 2 · 单层 decoder block:紫色归一化、蓝色注意力、绿色 FFN;两个残差把子层输出加回主干,主干形状始终 [1, 6, 4096]

1. 注意力部分:6 个 token 怎么互相看

步骤运算形状
Q 投影[1, 6, 4096] @ [4096, 4096][1, 6, 4096] → 拆头 [1, 32, 6, 128]
K / V 投影(GQA)[1, 6, 4096] @ [4096, 1024][1, 8, 6, 128](只有 8 组头)
分数Q @ Kᵀ ÷ √128[1, 32, 6, 6]
掩码 + softmax因果掩码逐行归一化[1, 32, 6, 6](6×6 的"谁能看谁"表)
加权求和probs @ V[1, 32, 6, 128]
合并多头32 头 × 128 → 4096[1, 6, 4096]

两个易混点:① [1, 32, 6, 128] 里的 32 是"头的维度"——6 个 token 各自被 32 个头分别表示成 128 维,和序列长度无关;② [1, 32, 6, 6] 的注意力分数是 6×6 的小方阵:第 i 行第 j 列表示"第 i 个 token 对第 j 个 token 的关注程度",因果掩码把 j>i 的格子置 −∞(未来的内容不许看)。GQA 下 K/V 只有 8 组头,Q 的 32 个头每 4 个一组共用,分数矩阵依然是 [1, 32, 6, 6],只是 K/V 存储省了 4 倍。

2. FFN 部分:升维、门控、降维

FFN 不做 token 之间的交互,只对每个位置独立"加深加工":[1, 6, 4096] 先升到 [1, 6, 11008](SwiGLU 有 gate / up 两份),SiLU(gate) × up 逐元素相乘后再降回 [1, 6, 4096]。参数量上 FFN 约占整层的三分之二,是大模型参数的主要存放地。

3. 残差连接:形状不变的秘密

每个子层的输出都会加回它的输入:output = x + sublayer(norm(x))。这就是主干形状恒为 [1, 6, 4096] 的原因——子层随便加工,主干始终保留原始信息通道。没有残差,32 层堆叠梯度根本传不回去。

四、解码阶段与 KV Cache:为什么长上下文贵

第 6 个位置采样出「是」之后开始自回归。每步只进 1 个新 token,但注意力需要"回看"全部历史,所以每层把历史 K/V 缓存下来:

KV Cache 随序列长度线性增长、随 batch 倍增。站内另外两篇文章讲的正是压它的一套方案——DeepSeek-V4.1-Flash 推理加速解读(CED 层切分 + CSA2 跨层共享)与CED + CSA2 复现代码检视。

五、完整形状流转总表(本例 S=6,B=1)

阶段张量形状维度含义(字母对照)
① 分词token 序列[6]S = 6 个 token
② 查词表 + 加 batchtoken IDs[1, 6]B = 1 条请求
③ embedding初始 hidden[1, 6, 4096]H = 4096 维向量
④ ×32 层 block层输出[1, 6, 4096]形状不变,语义逐层加深(内部 ④-a ~ ④-f)
④-a RMSNorm归一化[1, 6, 4096]Pre-Norm
④-b Q/K/V 投影query / key / value[1, 32, 6, 128] / [1, 8, 6, 128]32 个 Q 头、8 组 KV 头
④-c Attentionscores → probs @ V[1, 32, 6, 6] → [1, 32, 6, 128]6×6 的"谁看谁"方阵
④-e FFN 中间query[1, 32, 6, 128]32 头 × 6 位置 × 128 头维
④-b K/V(GQA)key / value[1, 8, 6, 128]8 组 KV 头
④-d 合并 + 残差层中间输出[1, 6, 4096]加回主干
④-e FFN 中间gate / up[1, 6, 11008]升维后的门控表示
⑥ 最终 norm最终 hidden[1, 6, 4096]深层语义向量
⑦ LM headlogits[1, 6, 152000]6 个位置各 152000 个打分
⑧ 取最后位logits[1, 152000]只看第 6 个位置
⑨ 采样next token标量采出「是」,拼回输入进下一轮

六、常见易混点

七、本篇版本与更新计划

基础篇 v1.1(2026-09-26):本篇是基础参考知识的第一版,先搭通"具体数字形状流转"的主线,后续计划补充:注意力变体(MQA / MLA / NSA)的形状对比、MoE 层的形状与路由、位置编码(RoPE)在哪个维度起作用、多模态输入的形状拼接、不同序列长度下的显存速查表等。有想看的主题欢迎指出。
华杉的技术博客 · 苏ICP备2026063680号-1
首页