最近在梳理一些关于Transformer的知识点,看了挺多问题的,罗列在这里,这是一个系列
在对答案前,先温习下Transformer模型的整体架构

带着架构图来看答案,不容易迷路:
答:
核心优势是并行计算效率高和长距离依赖捕捉能力强,解决了RNN串行瓶颈和CNN局部感受野局限,成为NLP主流架构。
解释:
替代RNN的关键:RNN需逐token串行计算(前一时刻输出依赖后一时刻),无法利用GPU并行,训练速度慢;且受限于 “短期记忆”,长序列中远距离依赖易丢失。而 Transformer 自注意力层无序列依赖,所有 token 可同时计算注意力,并行效率提升一个量级。
替代 CNN 的关键:CNN 依赖卷积核的局部感受野,需多层堆叠才能捕捉长依赖,且感受野固定;Transformer 自注意力可直接计算任意两个 token 的关联,天然捕捉全局长依赖,无需多层传递。
落地优势:结合残差连接和层归一化,深层模型训练更稳定,表达能力远超RNN/CNN。
答:
Transformer整体为 “N层Encoder栈 + N层Decoder栈+输出层” 结构;Encoder 负责编码输入序列的全局上下文,Decoder负责自回归生成目标序列,核心模块通过协同实现端到端序列建模。
解释:
整体架构:原论文中N=6,即 6个Encoder层堆叠、6个Decoder层堆叠,输入经词嵌入+位置编码后进入Encoder,Decoder接收 Encoder输出和自身已生成序列,最终通过输出层生成结果。
Encoder层组成及作用:
多头自注意力层:捕捉输入序列内部的全局依赖(如单词间语义/语法关联);
Add&Norm(残差连接 + 层归一化):残差连接解决梯度消失,层归一化稳定训练;
前馈神经网络(FFN):对注意力输出做非线性变换,增强特征表达能力。
Decoder层组成及作用:
掩码多头自注意力层:防止关注未来未生成token,保证自回归特性;
编码器-解码器注意力层(跨注意力):建立目标序列与输入序列的关联(如翻译任务中目标词对齐源词);
Add & Norm(两处,分别对应两个注意力层后):同Encoder功能;
前馈神经网络(FFN):同Encoder功能。
输出层:线性变换(映射到词汇表维度)+Softmax,输出token生成概率。
答:
自注意力核心是 “通过Q/K/V计算token间关联权重,融合全局上下文”,全流程分5步,公式推导清晰反映 “查询-匹配-加权” 逻辑。
假设输入序列向量为X,计算步骤如下:
3.1 生成 Q/K/V矩阵
通过3个可学习权重矩阵WQ,WK,WV做线性变换:Q=X*WQ,K=X*WK,V=X*WV,其中WQ, Wk, Wv 是模型通过训练学习到的核心参数,它们将输入投影到适合计算注意力的空间,使模型能动态地聚焦于相关信息。思考一下,下面那个场景这三个权重矩阵是不变的:训练场景?推理场景?留言区交流。
3.2 计算原始注意力分数
Q与K^T点积,量化token间的相似度。想一下为什么Q与K^T点积运算可以度量两个token之间的相似度?留言区交流。
3.3 缩放处理
除以根号dk,避免dk过大导致分数方差激增,Softmax 梯度消失。
(dk:Q/K/V的维度,论文中dk=64)
3.4 掩码处理(可选,仅 Decoder 用)
对未来位置或 PAD 位置置−∞,过滤无效信息
3.5 权重归一化 + 加权求和
Softmax 归一化权重(和为1),再与V加权求和得到输出。
答:
设计目的是捕捉多子空间的上下文依赖,计算步骤为 “拆分 - 多头并行计算 - 拼接 - 线性变换”,拆分与拼接是为了平衡 “多维度特征” 和 “计算效率”。
设计目的:单头自注意力仅能学习单一子空间的依赖模式(如仅关注语法),多头通过拆分维度,让不同头学习不同子空间特征(如一个头关注语法、一个关注语义),提升模型表达能力。
计算步骤:
4.1 拆分维度:将 Q/K/V 按头数 h 拆分
4.2 多头并行计算:每个头独立计算自注意力
4.3 拼接结果:将 h 个头的输出沿特征维度拼接
4.4 线性变换:通过权重矩阵 WO融合多头特征,输出最终结果
拆分与拼接的必要性:
拆分:若不拆分,多头计算量会随h线性增长,拆分后总计算量与单头相当;
拼接:将不同子空间的特征融合,实现 “分治 + 整合”,既保留多维度信息,又保证输出维度与模型一致。
答:
缩放因子的核心作用是归一化注意力分数的方差,避免dk过大时 Softmax进入饱和区导致梯度消失;不缩放会严重影响模型训练稳定性和收敛效果。
答:
Masked Multi-Head Attention 的核心作用是保证自回归生成的合理性(不泄露未来信息);两种掩码分工不同:Padding Mask 过滤无效 PAD token,Look-Ahead Mask 屏蔽未来未生成 token,共同确保注意力计算仅基于有效上下文。
具体来讲,Decoder 是自回归模型(逐token生成),若不掩码,模型会提前看到未来未生成的 token(如生成 “我” 时看到 “爱”“中国”),导致训练与测试不一致(测试时无未来token),生成序列逻辑混乱。
两种掩码的区别与应用:
Padding Mask(填充掩码):
作用:过滤批量训练中“不等长序列”的PAD token(用于补齐序列长度),避免无效信息干扰;
实现:构建与Scaled_Score同维度的矩阵,PAD位置置−∞,其余位置置 0;
应用场景:Encoder和Decoder的所有注意力层(只要存在PAD token 就需要)。
Look-Ahead Mask(未来掩码/序列掩码):
作用:屏蔽当前token之后的所有未来token,确保仅关注已生成的上下文;
实现:构建上三角矩阵,上三角部分(未来位置)置−∞,下三角及对角线(当前及历史位置)置 0;
应用场景:仅Decoder的掩码多头自注意力层(Encoder无需,因输入序列完整且无生成顺序限制)。
答:
位置编码的核心必要性是补充序列的顺序信息(自注意力本身无序)。
具体来讲,Transformer的自注意力机制是排列等变的(permutation equivariant),输入序列顺序打乱后,输出仅相应重排,不改变语义表示。
序列任务需求:自然语言等序列数据中,顺序是核心语义(如“我爱你”≠“你爱我”),必须显式注入位置信息。
主要实现方式及优缺点
7.1. 正弦/余弦编码(原始Transformer)
实现:预定义三角函数公式
优点:无需训练,固定编码可外推到训练未见长度蕴含相对位置关系(线性可表示)
缺点:非自适应,可能不是最优长距离衰减不明确
7.2. 可学习编码(BERT等)
实现:将位置编码作为可训练参数,随机初始化后学习
优点:灵活适应数据分布实现简单,性能通常不错
缺点:无法外推超长序列可能过拟合训练长度
7.3. 相对位置编码(T5、DeBERTa)
实现:编码token间相对距离,如添加可学习的相对位置偏置
优点:更好建模相对位置关系适合翻译等对位置敏感任务
缺点:实现较复杂计算开销稍大
7.4. 旋转位置编码(RoPE,LLaMA、ChatGLM等)
实现:通过旋转矩阵将位置信息融入Q、K向量
优点:优秀的外推能力,处理远超训练长度的序列相对位置的自然编码被大语言模型广泛验证有效缺点:实现稍复杂需要特定优化
7.5. 线性偏置(ALiBi,BLOOM等)
实现:给注意力分数添加与距离成比例的负偏置
优点:极强的外推能力无需位置嵌入参数,计算高效简单有效
缺点:偏置斜率需人工设定灵活性较低。
选型建议:
短序列任务:可学习编码足够,简单有效;
需要长度外推:优先RoPE或ALiBi;
翻译/生成任务:相对位置编码效果显著;
资源受限:ALiBi无额外参数,计算高效。
小结一下,现代大语言模型普遍采用RoPE(如LLaMA系列)或ALiBi,核心原因是:
优秀的外推能力:能处理远超训练长度的序列;
相对位置的自然建模:更符合语言理解需求;
计算效率:与注意力机制高效集成。
位置编码从“必要补充”发展为Transformer的核心组件,直接影响模型的长序列处理能力和泛化性能。
答:
FFN 核心结构是 “升维全连接 + ReLU 激活 + 降维全连接”;两层全连接实现维度映射,ReLU 引入非线性,共同增强模型对复杂特征的拟合能力。
答:
Layer Norm的核心作用是稳定训练过程,加速收敛;与Batch Norm的核心区别是归一化维度不同(样本内 vs 批次内);Transformer选择 Layer Norm是因为其适配NLP序列的动态特性,不受批次和序列长度影响。
解释:
Layer Norm 在 Transformer 中的作用:
9.1缓解内部协变量偏移(Internal Covariate Shift):模型训练时,各层输入分布随参数更新波动,归一化后分布稳定,降低学习难度;
9.2稳定梯度:归一化后的数值范围集中(均值 0、方差 1),避免梯度过大或过小,缓解梯度消失/爆炸;
9.3配合残差连接:归一化后的特征与残差路径的原始特征融合更平滑,提升模型表达能力。
与 Batch Norm 的核心区别:
维度 | Layer Norm(LN) | Batch Norm(BN) |
|---|---|---|
归一化维度 | 样本内(对单个样本的所有特征维度归一化) | 批次内(对批次中所有样本的同一特征维度归一化) |
依赖项 | 不依赖批次大小、序列长度 | 依赖批次大小(小批次下均值 / 方差估计不准) |
NLP 适配性 | 适配动态序列长度(如不同样本长度不同) | 不适配(固定特征维度对应序列位置,语义不合理) |
推理时处理 | 无需保存训练数据的均值 / 方差 | 需保存训练时的移动均值 / 方差 |
Transformer 选择 Layer Norm 的原因:
9.1 NLP 序列长度动态(如句子长度从10到500不等),BN 要求固定特征维度对应序列位置(如第1个维度对应所有句子的第1个词),语义上不合理(“我” 和 “今天” 无相同特征含义);
9.2 LN 对单个样本的所有词特征归一化(认为一句话的所有词属于 “语义特征” 集合),符合NLP语义逻辑;
9.3 小批次训练时 LN 效果稳定(BN小批次下均值/方差估计偏差大),适配Transformer 大模型的训练场景。
答:
残差连接的核心作用是缓解深层网络的梯度消失,保留原始特征;通过构建 “直接梯度路径”,让梯度能跨越多层网络回传,确保深层 Transformer可训练。
具体办法是为深度网络中的信息流动(包括前向的信号和反向的梯度)创建一条“直达通道”或“高速公路”。其基本形式为:输出 = F(x) + x,其中 x 是输入,F(x) 是网络层(如自注意力或前馈网络)的非线性变换。
它的核心价值体现在:
确保网络退化问题:在极深的网络中,传统的堆叠方式可能导致性能饱和甚至下降。残差连接确保了至少能保留原始输入 x 的信息,使得增加层数不会使模型比浅层模型更差。
保持信息完整性:允许模型轻松地学习恒等映射(即让 F(x) ≈ 0),这有助于在非常深的网络中,底层的特征信息也能无损或低损地传递到高层,便于进行复杂的特征整合。
促进梯度直接回传:这是解决梯度消失问题的关键。
2. 如何解决深层网络的梯度消失问题
梯度消失问题在传统深度网络中源于链式求导法则。反向传播时,梯度需要逐层连乘,如果每层的梯度值通常小于1,多次连乘后,传递到底层的梯度会指数级衰减至近乎零,导致底层参数无法更新。
答:
自注意力的核心问题是长序列下计算/空间复杂度高(O(seq_len^2)),优化方案围绕 “降低复杂度” 展开,核心思路是 “稀疏化注意力”、“线性化注意力” 或 “序列分段”,在保证效果的同时降低成本。
核心问题:当序列长度seq_len增大(如1024→4096),时间/空间复杂度平方级增长(4096²=16×1024²),显存占用和训练时间急剧增加,无法处理超长序列(如文档级文本)。
主流优化方案:
11.1 稀疏注意力(Sparse Attention):
核心思路:让每个 token 仅关注序列中部分关键 token,而非所有 token,复杂度降为 O(seq_len⋅k)(k 为关注的 token 数);
代表模型:
Longformer:采用 “局部窗口 + 全局 token”(如 CLS token 关注所有位置),窗口大小可设为 512,复杂度 O(seq_len⋅k);
BigBird:结合局部窗口、随机采样、全局 token 三种稀疏模式,在长序列任务(如 QA)中效果接近全注意力。
11.2 线性化注意力(Linear Attention):
核心思路:重写注意力公式,将 O(seq_len^2) 的点积替换为线性操作,复杂度降为 O(seq_len⋅dmodel);
代表模型:
Linformer、Performer
11.3 序列分段处理(Segment-wise Attention):
核心思路:将长序列拆分为多个固定长度的片段(Segment/Chunk),仅在片段内计算注意力,跨片段通过特殊 token 传递信息;
代表模型:Transformer-XL(引入循环机制,复用前一片段的隐状态)、GPT-4(分片注意力,支持 128k 长序列)。
11.4 其他优化:
Reformer:采用 “可逆层”(Reversible Layers)减少显存占用,结合局部敏感哈希(LSH)实现稀疏注意力,支持 16k 长序列;
轴向注意力(Axial Attention):将序列按维度拆分(如文本按行 / 列),分别计算注意力,降低单次计算的序列长度。
答:
Transformer 采用 “Xavier/Glorot 初始化” 为主、“偏置项零初始化” 为辅的策略;对 Q/K 矩阵做特定初始化是为了避免注意力分数方差异常,保证模型训练稳定启动。
答:
Label Smoothing的核心作用是缓解过拟合,提高模型泛化能力;通过软化 “one-hot” 标签的置信度,避免模型对正确标签过度自信,降低对抗噪声的敏感性。
为什么需要 Label Smoothing?
Transformer 训练时,目标标签通常是 “one-hot” 向量(正确token为1,其余为0),模型会倾向于最大化正确标签的概率,导致:
13.1 过拟合:模型对训练数据的噪声过度拟合,测试时对未见过的输入鲁棒性差;
13.2 过度自信:正确标签的预测概率趋近于1,其余趋近于0,梯度更新剧烈,训练不稳定;
13.3 对抗脆弱性:对输入的微小扰动(如拼写错误)敏感,容易预测错误。
对模型效果的影响:
优点:
泛化能力提升:模型不再只关注正确标签,对相似标签也会学习到一定概率,测试时对未见过的输入更鲁棒;
训练更稳定:软化标签后,梯度更新更平缓,避免因过度自信导致的梯度爆炸;
缓解过拟合:减少模型对训练数据噪声的依赖,降低过拟合风险。
缺点:
训练速度略降:标签软化后,模型需要更多迭代才能收敛到最优状态;
极端场景精度损失:在标签完全无噪声的任务(如小词汇表翻译)中,可能导致轻微的精度下降。
答:
Beam Search 的核心作用是在自回归生成中平衡 “生成质量” 和 “搜索效率”;相比贪心搜索,能找到更优的生成序列,束宽选择需 trade-off 效果与速度,通常设为 5-10。
解释:
核心作用:Decoder 解码时需从词汇表中选择下一个 token,贪心搜索每次选择概率最高的 token,但可能陷入局部最优(如当前概率最高的 token 会导致后续序列语义不通);Beam Search 通过保留前k个概率最高的候选序列(束宽k),逐步扩展并筛选,最终选择全局最优序列。
与贪心搜索的对比:
维度 | Beam Search(束搜索) | 贪心搜索(Greedy Search) |
|---|---|---|
搜索逻辑 | 保留前 k 个候选序列,迭代扩展 | 每次选择概率最高的 token,单路径扩展 |
生成质量 | 更高(避免局部最优,找到全局更优序列) | 较低(易陷入局部最优,序列连贯性差) |
时间复杂度 | O(k⋅seq_len⋅C)(k 为束宽,C 为词汇表大小) | O(seq_len⋅C)(最快) |
空间复杂度 | 较高(需存储 k 个候选序列的状态) | 较低(仅存储当前序列状态) |
适用场景 | 对生成质量要求高的任务(翻译、摘要) | 对速度要求高的场景(实时对话) |
答:
Transformer 训练技巧围绕 “稳定训练、缓解过拟合、加速收敛” 展开,核心技巧包括 Warmup 学习率(解决初始训练不稳定)、权重衰减(缓解过拟合)、梯度裁剪(解决梯度爆炸),是大模型训练成功的关键。
Warmup 学习率:
解决问题:训练初期模型参数随机初始化,若学习率过大,参数更新剧烈,导致训练震荡(Loss 骤升骤降);若学习率过小,收敛过慢;
核心逻辑:初期小学习率稳定参数,中期峰值学习率加速收敛,后期衰减学习率精细调整。
权重衰减(Weight Decay):
解决问题:Transformer 参数量大(Base 版约 1.1 亿),易过拟合(模型记住训练数据噪声);
实现方式:在损失函数中加入 L2 正则项,惩罚过大的权重参数
核心逻辑:通过限制权重参数的幅值,让模型学习更泛化的特征(而非噪声),提升测试集效果。
梯度裁剪(Gradient Clipping):
解决问题:深层 Transformer 中,注意力层或 FFN 的梯度可能因数值不稳定导致梯度爆炸(梯度 norm 过大),参数更新溢出;
核心逻辑:限制梯度的最大幅值,避免参数更新时因梯度过大导致的训练崩溃。
其他辅助技巧:
混合精度训练(Mixed Precision):用 FP16 存储参数和计算,FP32 保存梯度,减少显存占用,加速训练;
梯度累积(Gradient Accumulation):小批次训练时,累积多步梯度后再更新参数,模拟大批次效果;
早停(Early Stopping):监控验证集 Loss,若连续多轮无下降则停止训练,避免过拟合。
16. 多头注意力中「头数」的选择依据?头数过多/过少会带来什么问题?
结论:
头数选择的核心依据是 “任务复杂度 + 计算资源”,需平衡 “多子空间特征捕捉能力” 和 “计算效率”;主流选择为 8 头(论文原版),头数过多会导致过拟合和计算成本激增,过少会导致特征表达不足。
17. Transformer 的梯度回传过程?哪些模块易梯度爆炸/消失?如何解决?
Transformer 的梯度回传是从损失函数出发,沿正向计算的逆路径反向传递误差、更新参数的过程,核心遵循链式法则,可分为以下阶段:
17.1.1 . 损失计算与初始梯度
模型输出层(logits)与真实标签计算交叉熵损失L,损失对输出 logits 的偏导数是梯度回传的起点。
17.1.2 . Decoder 模块反向传播
梯度从输出层依次反向经过:输出层 → Decoder最后一个Add&Norm → FFN层 → Decoder前一个Add&Norm → 多头自注意力层(掩码自注意力+Encoder-Decoder注意力) → Decoder首个Add&Norm → 词嵌入/位置编码层
注意力层:反向计算注意力权重的梯度,以及对 Query/Key/Value 投影矩阵的梯度。
FFN 层:反向计算两层线性层的梯度,激活函数(如 ReLU/GELU)的梯度会影响误差传递。
17.1.3 . Encoder 模块反向传播
梯度从 Decoder 的 Encoder-Decoder 注意力层传递至 Encoder,依次反向经过:Encoder-Decoder注意力层 → Encoder最后一个Add&Norm → FFN层 → Encoder前一个Add&Norm → 多头自注意力层 → 词嵌入/位置编码层
易发生梯度爆炸 / 消失的模块
17.2.1. 多头自注意力层
梯度消失:Scaled Dot-Product Attention 中的 softmax 会导致梯度集中在少数 token 上,其他 token 梯度接近 0;长序列下注意力依赖的链式传递也会因累积衰减导致梯度消失。
梯度爆炸:注意力矩阵的链式求导可能因权重累积导致梯度范数过大;若未做 √d_k 缩放,Dot-Product 结果过大易引发梯度爆炸。
17.2.2. FFN(前馈神经网络)层
梯度消失:ReLU 激活函数的负区间梯度为 0,会导致部分神经元梯度消失;GELU 虽缓解此问题,但深层堆叠仍可能因链式衰减导致梯度消失。
梯度爆炸:线性层权重初始化不当(如方差过大),或多层累积导致梯度范数激增。
17.2.3. 无残差连接的堆叠层
若没有残差连接,纯堆叠的自注意力 / FFN 层会因多层链式求导的累积衰减,导致梯度快速消失(类似深度 MLP 的梯度消失问题)。
17.2.4. 可学习位置编码层
可学习位置编码的权重若初始化过大,易引发梯度爆炸;若词汇量 / 嵌入维度过高,嵌入层梯度也可能不稳定。
梯度问题的解决方案
17.3.1. 残差连接(Residual Connection)
每个自注意力层、FFN 层后都加入残差连接(Add 操作),为梯度提供 “捷径”,避免多层堆叠导致的梯度消失。
核心作用:让梯度可以直接通过残差路径传递,减少链式衰减。
17.3.2. 层归一化(Layer Normalization)
对每个样本的特征维度做归一化,稳定层输入分布,减少梯度波动,同时缓解梯度爆炸与消失。
核心作用:归一化后的梯度更平滑,避免因输入分布偏移导致的梯度不稳定。
17.3.3. Scaled Dot-Product Attention
自注意力计算时将点积结果除以 dk(d_k 为 Query/Key 维度),避免因 d_k 过大导致点积结果进入 softmax 饱和区,缓解梯度消失。
17.3.4. 合理初始化与激活函数
初始化:线性层用 Xavier/He 初始化,嵌入层用小范围均匀初始化,确保权重方差稳定,避免梯度爆炸。
激活函数:用 GELU(平滑 ReLU 变体)替代 ReLU,让梯度更连续;避免用 sigmoid/tanh(饱和区梯度易消失)。
17.3.5. 梯度裁剪(Gradient Clipping)
反向传播后对全局梯度的范数进行裁剪(如设置最大范数),若梯度范数超过阈值则缩放梯度,直接解决梯度爆炸问题。
17.3.6. 权重衰减(Weight Decay)
对权重添加 L2 正则化,防止权重过大导致梯度爆炸,同时缓解过拟合。
18. 自注意力的「注意力可视化」能反映什么?如何分析分布合理性?
自注意力可视化(多以热力图形式呈现,横轴为Key token、纵轴为Query token,颜色深浅表征注意力权重大小)是解析模型行为、验证语义建模能力的关键工具,其核心价值可归纳为以下五点:
判断注意力分布是否合理,需结合语言逻辑、任务特性与模型设计预期,从以下五个核心维度展开分析:
19.PyTorch 手动实现基础版 Transformer
以下是可直接运行的核心代码,聚焦 Self-Attention、Encoder/Decoder 层,注释清晰,适配新手理解:
19.1 基础依赖与缩放点积注意力
import torch
import torch.nn as nn
import torch.nn.functional as F
# 缩放点积注意力(Self-Attention核心)
class ScaledDotProductAttention(nn.Module):
def __init__(self):
super().__init__()
def forward(self, q, k, v, mask=None):
"""
参数:
q: [batch_size, n_heads, seq_len_q, d_k]
k: [batch_size, n_heads, seq_len_k, d_k]
v: [batch_size, n_heads, seq_len_v, d_v](seq_len_k=seq_len_v)
mask: [batch_size, 1, seq_len_q, seq_len_k](可选,掩码为0的位置置为-1e9)
返回:
output: [batch_size, n_heads, seq_len_q, d_v]
attn_weights: [batch_size, n_heads, seq_len_q, seq_len_k]
"""
d_k = q.size(-1)
# 1. 计算QK^T / sqrt(d_k)
scores = torch.matmul(q, k.transpose(-2, -1)) / torch.sqrt(torch.tensor(d_k, dtype=torch.float32))
# 2. 应用掩码(屏蔽PAD/未来token)
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
# 3. Softmax计算注意力权重 + 加权求和V
attn_weights = F.softmax(scores, dim=-1)
output = torch.matmul(attn_weights, v)
return output, attn_weights19.2 多头注意力模块
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, n_heads):
super().__init__()
assert d_model % n_heads == 0, "d_model必须能被n_heads整除"
self.d_model = d_model
self.n_heads = n_heads
self.d_k = d_model // n_heads # 每个头的维度
# Q/K/V线性变换层
self.w_q = nn.Linear(d_model, d_model)
self.w_k = nn.Linear(d_model, d_model)
self.w_v = nn.Linear(d_model, d_model)
# 输出线性变换层
self.w_o = nn.Linear(d_model, d_model)
def forward(self, q, k, v, mask=None):
batch_size = q.size(0)
# 1. 线性变换 + 多头拆分([batch, seq_len, d_model] → [batch, n_heads, seq_len, d_k])
q = self.w_q(q).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
k = self.w_k(k).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
v = self.w_v(v).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
# 2. 缩放点积注意力
attn_output, attn_weights = ScaledDotProductAttention()(q, k, v, mask)
# 3. 多头拼接([batch, n_heads, seq_len, d_k] → [batch, seq_len, d_model])
attn_output = attn_output.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model)
# 4. 输出线性变换
output = self.w_o(attn_output)
return output, attn_weights19.3 前馈网络(FFN)
class FeedForwardNetwork(nn.Module):
def __init__(self, d_model, d_ff=2048):
super().__init__()
self.fc1 = nn.Linear(d_model, d_ff) # 升维
self.fc2 = nn.Linear(d_ff, d_model) # 降维
self.relu = nn.ReLU() # 可替换为GELU提升效果
def forward(self, x):
# x: [batch_size, seq_len, d_model]
return self.fc2(self.relu(self.fc1(x)))19.4 Encoder层
class EncoderLayer(nn.Module):
def __init__(self, d_model, n_heads, d_ff=2048, dropout=0.1):
super().__init__()
self.self_attn = MultiHeadAttention(d_model, n_heads) # 自注意力
self.ffn = FeedForwardNetwork(d_model, d_ff) # 前馈网络
# 层归一化 + Dropout
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.dropout1 = nn.Dropout(dropout)
self.dropout2 = nn.Dropout(dropout)
def forward(self, x, src_mask):
# 1. 自注意力 + 残差连接 + 归一化(Pre-LN结构,更稳定)
x_norm = self.norm1(x)
attn_output, attn_weights = self.self_attn(x_norm, x_norm, x_norm, src_mask)
x = x + self.dropout1(attn_output)
# 2. FFN + 残差连接 + 归一化
x_norm = self.norm2(x)
ffn_output = self.ffn(x_norm)
x = x + self.dropout2(ffn_output)
return x, attn_weights19.5 Decoder层
class DecoderLayer(nn.Module):
def __init__(self, d_model, n_heads, d_ff=2048, dropout=0.1):
super().__init__()
self.masked_self_attn = MultiHeadAttention(d_model, n_heads) # 掩码自注意力
self.cross_attn = MultiHeadAttention(d_model, n_heads) # 交叉注意力
self.ffn = FeedForwardNetwork(d_model, d_ff)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.norm3 = nn.LayerNorm(d_model)
self.dropout1 = nn.Dropout(dropout)
self.dropout2 = nn.Dropout(dropout)
self.dropout3 = nn.Dropout(dropout)
def forward(self, x, enc_output, src_mask, tgt_mask):
# 1. 掩码自注意力(屏蔽未来token)
x_norm = self.norm1(x)
masked_attn_out, masked_attn_weights = self.masked_self_attn(x_norm, x_norm, x_norm, tgt_mask)
x = x + self.dropout1(masked_attn_out)
# 2. 交叉注意力(Q=Decoder,K/V=Encoder)
x_norm = self.norm2(x)
cross_attn_out, cross_attn_weights = self.cross_attn(x_norm, enc_output, enc_output, src_mask)
x = x + self.dropout2(cross_attn_out)
# 3. FFN
x_norm = self.norm3(x)
ffn_out = self.ffn(x_norm)
x = x + self.dropout3(ffn_out)
return x, (masked_attn_weights, cross_attn_weights)19.6 位置编码 + 完整Transformer(简化版)
# 位置编码(正弦余弦版,无需训练)
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_seq_len=5000):
super().__init__()
pos_enc = torch.zeros(max_seq_len, d_model)
pos = torch.arange(0, max_seq_len, dtype=torch.float32).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-torch.log(torch.tensor(10000.0)) / d_model))
pos_enc[:, 0::2] = torch.sin(pos * div_term)
pos_enc[:, 1::2] = torch.cos(pos * div_term)
self.register_buffer('pos_enc', pos_enc.unsqueeze(0)) # 不参与训练
def forward(self, x):
return x + self.pos_enc[:, :x.size(1), :]
# 完整Transformer
class Transformer(nn.Module):
def __init__(self, src_vocab_size, tgt_vocab_size, d_model=512, n_heads=8, n_layers=6, d_ff=2048, dropout=0.1):
super().__init__()
# Encoder部分
self.enc_emb = nn.Embedding(src_vocab_size, d_model)
self.pos_enc = PositionalEncoding(d_model)
self.encoder = nn.ModuleList([EncoderLayer(d_model, n_heads, d_ff, dropout) for _ in range(n_layers)])
# Decoder部分
self.dec_emb = nn.Embedding(tgt_vocab_size, d_model)
self.decoder = nn.ModuleList([DecoderLayer(d_model, n_heads, d_ff, dropout) for _ in range(n_layers)])
# 输出层
self.fc_out = nn.Linear(d_model, tgt_vocab_size)
self.dropout = nn.Dropout(dropout)
def forward(self, src, tgt, src_mask, tgt_mask):
# Encoder前向
src_emb = self.dropout(self.pos_enc(self.enc_emb(src)))
enc_output = src_emb
for enc_layer in self.encoder:
enc_output, _ = enc_layer(enc_output, src_mask)
# Decoder前向
tgt_emb = self.dropout(self.pos_enc(self.dec_emb(tgt)))
dec_output = tgt_emb
for dec_layer in self.decoder:
dec_output, _ = dec_layer(dec_output, enc_output, src_mask, tgt_mask)
# 输出预测
output = self.fc_out(dec_output)
return output
# 测试代码(验证模型可运行)
if __name__ == "__main__":
src_vocab_size = 1000
tgt_vocab_size = 1000
model = Transformer(src_vocab_size, tgt_vocab_size)
# 模拟输入:[batch_size, seq_len]
src = torch.randint(0, src_vocab_size, (2, 10)) # batch=2, src_seq_len=10
tgt = torch.randint(0, tgt_vocab_size, (2, 8)) # batch=2, tgt_seq_len=8
# 模拟掩码(全1,无屏蔽)
src_mask = torch.ones(2, 1, 10, 10)
tgt_mask = torch.ones(2, 1, 8, 8)
output = model(src, tgt, src_mask, tgt_mask)
print("输出形状:", output.shape) # 预期:[2, 8, 1000]20. Transformer 训练时的硬件瓶颈?如何通过混合精度/梯度累积/分布式训练优化?
核心硬件瓶颈有下面4个方面:
内存瓶颈(最核心):显存被模型参数、激活值、梯度、优化器状态占用,长序列下激活值随序列长度平方增长,大模型单卡显存直接不足。
计算瓶颈:注意力点积、FFN 矩阵运算算力需求高,参数量 / 序列长度提升后,GPU 算力满载,训练速度触顶。
IO 瓶颈:海量训练数据读取速度跟不上 GPU 计算速度,导致 GPU 闲置。
通信瓶颈(分布式场景):多卡 / 多机间梯度、参数传输耗时长,网络带宽不足时,分布式加速比大幅下降。
三大优化方法
20.1. 混合精度训练
原理:用 FP16(半精度)存储参数 / 计算,关键步骤(如梯度累加)用 FP32(单精度)避免数值溢出,显存占用减少 50%,计算速度提升 2-4 倍。
作用:显存占用减半,计算速度提升 2~4 倍,支持更大模型 / 更大 batch size。
关键:启用 AMP 自动管理精度切换。
20.2. 梯度累积。示例见文末项目源码
原理:大batch拆分为多个小batch,累加梯度后再更新参数,有效 batch = 单步batch×累积步数。
作用:不增加单步显存,变相扩大batch size,保证训练稳定性。
关键:累加后清零梯度,学习率随有效batch线性缩放。
20.3. 分布式训练

通信优化:梯度压缩、通信 - 计算重叠、使用高速网络。
标配组合策略:混合精度训练 + 梯度累积 + 分布式数据并行,兼顾显存节省、计算效率与训练稳定性。
21. Transformer 推理加速的方法介绍(量化/ONNX/TensorRT/KV Cache)
use_cache=True 启用。22. 小数据集下训练 Transformer 过拟合的解决方案介绍
小数据集下 Transformer 过拟合的核心原因是 “模型参数量远大于数据量”,解决方案从 “增加数据”“减少模型复杂度”“正则化” 三个维度入手:
针对文本数据的增强方法,低成本且有效:
from transformers import BertModel
# 加载预训练模型
pretrained_model = BertModel.from_pretrained("bert-base-chinese")
# 冻结底层参数
for param in list(pretrained_model.parameters())[:-6]:
param.requires_grad = False
# 拼接下游任务头
model = nn.Sequential(pretrained_model, nn.Linear(768, num_classes))optimizer = torch.optim.Adam(model.parameters(), lr=1e-4, weight_decay=1e-5)Dropout
在注意力层 / FFN 层后添加 Dropout(概率 0.1-0.3),随机失活部分神经元:
self.dropout = nn.Dropout(0.2) # 在Encoder/Decoder层中添加早停(Early Stopping)
监控验证集损失,当损失不再下降时停止训练,避免过拟合:
best_loss = float('inf')
patience = 3
counter = 0
for epoch in range(epochs):
# 训练+验证
val_loss = validate(model, val_dataloader)
if val_loss < best_loss:
best_loss = val_loss
counter = 0
torch.save(model.state_dict(), "best_model.pth")
else:
counter += 1
if counter >= patience:
print("早停,避免过拟合")
break标签平滑(Label Smoothing)
将硬标签(0/1)转为软标签,降低模型自信度:
def label_smoothing_loss(logits, labels, eps=0.1):
n_classes = logits.size(-1)
one_hot = torch.zeros_like(logits).scatter(1, labels.view(-1, 1), 1)
one_hot = one_hot * (1 - eps) + eps / n_classes
log_prob = F.log_softmax(logits, dim=-1)
loss = -(one_hot * log_prob).sum(dim=-1).mean()
return loss小结一下解决策略: