BERT 模型结构与参数:多头注意力、位置编码、层归一化详解
说起 BERT,很多人第一反应就是“Transformer 编码器堆叠”。没错,它本质上就是个多层双向 Transformer 编码器。但要真正吃透测试和链路均衡,咱们得把它的三个核心部件拆开揉碎了讲——多头注意力、位置编码、层归一化。这几个模块,我在调试分布式推理链路时吃了不少苦头,也攒了些经验。
1. 多头注意力:让模型同时看多个“角度”
注意力机制的核心就是“加权求和”。但为什么需要多头的?说白了,一个头只能关注一种关系模式。比如在处理“苹果”这个词时,一个头可能关注它的颜色属性,另一个头关注它跟“水果”的类别联系。多头就是并行跑多组注意力,然后把结果拼起来。
参数与计算量
我们以 BERTbase 为例,隐藏层维度 \(d_{model}=768\),12 个头,每个头维度 \(d_k=64\)。每组 Q、K、V 矩阵都是 \(768×64\),12 个头就是 12 组。加上最后的投影矩阵 \(768×768\),光注意力模块的参数大约就是:
- Q/K/V 各 12 组:\(12 × 3 × (768×64) ≈ 1.77M\)
- 输出投影:\(768×768 ≈ 0.59M\)
- 合计约 2.36M 参数。
我在做模型裁剪时发现,12 个头并不都“干活”。有些头学习到的注意力模式几乎均匀分布,属于冗余头。我个人习惯先做一层头重要性评估,再去掉得分低的那几个。
2. 位置编码:给序列标上“坐标”
Transformer 的结构没有循环,也没有卷积。它天生对词序不敏感。你想想看,如果不加位置信息,“我打你”和“你打我”在模型眼里就是相同的词袋。所以必须有位置编码。
BERT 用的是可学习的位置编码。它会初始化一个矩阵 \(P \in \mathbb{R}^{seq\_len \times d_{model}}\),和 的 embedding 直接相加。对于 BERTbase,最大序列长度一般是 512,那这个位置编码矩阵就有 \(512×768 ≈ 0.39M\) 个可训练参数。
相比 Transformer 原作的正弦编码,BERT 的做法有啥区别?
| 对比维度 | 正弦位置编码 | BERT 可学习编码 |
|---|---|---|
| 参数数量 | 0(固定值) | seq_len × d_model |
| 外推能力 | 支持更长序列 | 不超最大长度会崩 |
| 灵活性 | 固定模式 | 随任务自适应 |
| 收敛速度 | 一般 | 通常更快 |
3. 层归一化:稳定训练的神器
层归一化(LayerNorm)的作用,就是让每一层的输出均值为 0、方差为 1,再缩放平移。相比 BatchNorm,它对 batch 大小不敏感,特别适合 NLP 中的变长序列。
BERT 里两个特殊要点
- Pre-LN vs Post-LN: 早期的 BERT 用的是 Post-LN(先残差连接再归一化)。但后来发现 Pre-LN(先归一化再子层)更容易训练。我自己在复现时更倾向于 Pre-LN,梯度更稳。
- 参数更新: LayerNorm 有 2 个可学习参数 \(\gamma\)(缩放)和 \(\beta\)(偏移),每个都是 \(d_{model}\) 维度。所以 BERTbase 12 层编码器,每层又含 Attention LayerNorm 和 FFN LayerNorm,总共 \(12×2×2=48\) 组 \(\gamma,\beta\),参数量约 \(48×2×768 ≈ 73K\)。
4. 模型结构总览与参数汇总
整张图就是把上面三个部件串起来的流程。输入 做 embedding,加上位置编码,扔进 12 层 Transformer 编码器。每层内部:先 Attention,再残差连接 + LayerNorm;然后 FFN,再残差连接 + LayerNorm。最后输出给下游任务。
嗯,框架图把整个流程理清了。从 Embedding 加位置编码,进注意力,再做归一化,接着 FFN 处理,又一次归一化。这就是 BERT 模型的“一次前向”。我们在做链路均衡分析时,注意力部分容易遇到矩阵乘的计算瓶颈,FFN 的 3072 宽中间层会吃掉大量显存,LayerNorm 则是精度敏感的重灾区。
5. 性能测试与链路均衡建议
基于上面的结构,我个人习惯在测试中重点关注这几项指标:
- 注意力矩阵稀疏度: 如果某些头输出几乎恒定,那可以裁剪掉。我在项目中用 SVD 分解发现接近 20% 的头贡献度低于 5%。
- 位置编码的周期性: 可视化位置编码向量的余弦相似度矩阵,如果相邻位置的编码差异很小,说明冗余度偏高,可以考虑降低编码维度。
- LayerNorm 的统计漂移: 在模型量化或混合精度训练时,LayerNorm 的均值/方差统计会漂移。建议在验证集上监控余弦相似度,设定告警阈值。