📖 第1课:Transformer 是怎么"读懂"中文的?
Qwen2.5-0.5B 的"大脑"是怎么组织的?24层 Decoder 每层做什么?Token、Embedding、Attention 这些词到底什么意思?
理解 Transformer Decoder 的核心组件:Token→Embedding→Attention→MLP→输出。知道 Qwen2.5-0.5B(24层/896维/14个注意力头)为什么长这样。
一、语言模型的本质:一个超级"完形填空"机器
大语言模型(LLM)做的事情其实很简单——猜下一个词:
输入:"请用一句话介绍" → 模型猜 → "ZCU106"(得分最高)
模型猜对的概率取决于训练时读过多少文本。Qwen2.5-0.5B 有 5 亿个参数(0.5B = 500M),这些参数记录了"在什么上下文中什么词更可能出现"。
一句话生成的全过程
'请用一句话介绍'"] -->|"Tokenizer
分词器"| B["🔢 token序列
{16, 108, 234, ...}"] B -->|"Embedding
查表"| C["📐 向量序列
每token→896维向量"] C -->|"24层 Decoder
逐层处理"| D["📐 最后一个向量
(hidden state)"] D -->|"lm_head
矩阵乘法"| E["📊 分数表
每个词一个分数"] E -->|"ArgMax"| F["💬 输出: 'ZCU106'"] style B fill:#3987e5,color:#fff style C fill:#199e70,color:#fff style D fill:#c98500,color:#000 style F fill:#0ca30c,color:#fff
二、Embedding —— 把文字变成数字
计算机不认识汉字,只认识数字。Embedding 就是"汉字→数字"的翻译官——给每个 token 分配一个 896 维的向量(一串 896 个数字),这串数字"编码"了这个词的含义。
比如:
- "请" → [0.12, -0.34, 0.89, ...](896 个数字)
- "介绍" → [0.45, 0.22, -0.67, ...](896 个数字)
同一个 Embedding 表还负责最后一步(tied lm_head)——把最终向量"翻译回"最可能的词。Qwen2.5 的 tokenizer 词表很大(中文+英文混用),字节级 BPE 编码。
三、Decoder Layer —— 一层一层"理解"文字
Qwen2.5-0.5B 有 24 个 Decoder Layer。每一层做两件事:先"注意"关键信息(Attention),再"思考"(MLP)。
一个 Decoder Layer 的内部结构
(来自上一层或Embedding)"] --> NORM1["🔧 RMSNorm
归一化: 把数值范围压到合理区间"] NORM1 --> ATT["🎯 GQA Attention
'注意'机制: 14个查询头/2个KV头
每个头看64维"] ATT --> ADD1["➕ 残差连接
输入+注意力输出
(防止信息丢失)"] ADD1 --> NORM2["🔧 RMSNorm
再次归一化"] NORM2 --> MLP["🧮 SwiGLU MLP
前馈网络:
gate(896→2384) + up(896→2384)
+ down(2384→896)"] MLP --> ADD2["➕ 残差连接
再次相加"] ADD2 --> OUTPUT["📤 输出: 896维向量
(送下一层)"] style ATT fill:#3987e5,color:#fff style MLP fill:#e66767,color:#fff
Attention(注意力)—— "这段话里,哪些词跟当前词相关?"
这是 Transformer 最核心的发明。举个例子:
"请用一句话介绍ZCU106" → 模型注意到"介绍"这个词和"ZCU106"高度相关(因为"介绍"后面往往跟着被介绍的对象)。
Qwen2.5 用的是 GQA(分组查询注意力):14 个查询头分 2 组,每组共享一个 KV 头。这样做的好处是:KV cache 节省了 7 倍存储(2 个 KV 头 vs 14 个 KV 头),推理速度更快。
残差连接 —— "别把原始信息弄丢了"
每一层的输出 = 这个层的计算结果 + 这个层的输入(原样加回去)。这就像接力赛——你把棒子传给下一棒,但自己也跟着跑。好处是:即使某一层"算错了",原始信息还在,不会被破坏。这让 24 层的网络可以稳定训练。
四、GGUF 量化 —— 把模型"压缩"进 FPGA
Qwen2.5-0.5B 的原始权重(FP32 格式)需要:
- 290 个张量 × 4 bytes ≈ 1.6 GB
- 但 ZCU106 的片外 DDR 只有约 1GB
所以必须压缩。GGUF 格式最常用的技术是 Q6_K 量化:把 32 位浮点数压缩到 6 位整数。
量化:像"四舍五入"但更聪明
0.12345678
(32位/4字节)"] -->|"Q6_K量化
超级块压缩"| Q6["Q6_K权重
约6位/0.75字节
(压缩约5.3倍)"] Q6 -->|"反量化
(用scale+zp恢复)"| BACK["近似值
约0.1235
(精度损失很小)"] style FP32 fill:#3987e5,color:#fff style Q6 fill:#199e70,color:#fff
K-quant(超级块)是 GGUF 的特色:它不是每个值独立量化,而是把 256 个值作为一个"超级块",共享一个 scale 因子。这样可以利用块内的统计分布达到更高精度。
Qwen2.5 的 GGUF 量化结果:392 MB(从 ~1.6GB 压缩到 ~392MB),压缩比约 4 倍。
五、9 种 HLS 算子 —— 把 Transformer "拆"成硬件模块
要在 FPGA 上实现 24 层 Transformer,不能一次性把所有层都塞进去(资源不够)。Qwen2.5 的策略是:把 Transformer 拆成 9 种可复用的 HLS 算子,像搭积木一样组合。
9 种 HLS 算子覆盖了 Transformer 的每个步骤
查表: token_id→向量"] end subgraph DECODER["Decoder层 (重复24次)"] D1["RMSNorm
归一化"] D2["Q/K/V投影
Q5_0矩阵乘"] D3["Attention Chain
softmax+value_mix"] D4["Output Proj+残差
Q5_0矩阵乘"] D5["Gate/Up投影
Q5_1矩阵乘"] D6["Down投影
Q6_K矩阵乘"] end subgraph OUTPUT["输出层"] O1["RMSNorm+top1
选最高分token"] end EMB --> DECODER --> OUTPUT style D3 fill:#e66767,color:#fff
其中最复杂的是 Attention Chain(DSP=12, FF=7,584, 22,590 cycles)——它要实现 Q 和 K 的点积、softmax 归一化、V 的加权求和,是 Transformer 的核心计算。
六、当前进度:哪步做了,哪步卡住了?
| 组件 | 状态 | 说明 |
|---|---|---|
| Tokenizer | ✅ 6/6 板级回归 | ZCU106 UART 中英文编解码通过 |
| HLS CSim/CSynth | ✅ 9 种算子全部通过 | 但 Cosim 只有 Q4_K 和 Attention Chain 部分通过 |
| 24层全量化 Python | ⚠️ 通过但缺reference | top1 匹配 Ollama,但 reference logits 缺失 |
| Output Proj Cosim | ❌ 失败 | AXI boundary overflow + xsim OOM |
| Full 30/24层 runtime | ⬜ 未开始 | 待所有算子 Cosim 通过后才能集成 |