Transformer六阶段学习路径:从自注意力到多卡训练与LoRA微调
秋招和社招的大模型算法岗面试翻来覆去其实都在问 Transformer 这条主线。很多同学准备面试时习惯把注意力放在最新的模型结构、SOTA 指标上结果被问到“自注意力的 Q、K、V 到底怎么算”“多卡训练时梯度怎么同步”反而答不清楚。实际上从自注意力机制到多卡训练是一条完整的知识链路也是大模型算法岗高频考点的核心覆盖范围。本文按六个阶段整理了一套学习路径。前三个阶段聚焦 Transformer 本身的原理与手写实现后三个阶段进入大模型工程化分布式训练、微调、推理优化、面试高频追问。每一阶段都配有代码示例、面试考察角度和避坑建议适合正在准备大模型算法岗的同学系统性复习也适合刚接触 Transformer 的开发者作为入门路线图。1. 阶段一自注意力机制——从公式到代码自注意力是整个 Transformer 的基石。面试时考官很少直接让你背公式而是通过追问检验你是否真正理解“为什么需要自注意力”“Q、K、V 是从哪来的”“计算复杂度为什么是 O(n²)”。1.1 自注意力的核心思想自注意力的目标是让序列中每个位置都能直接建模与其他所有位置的关系。以文本序列为例当模型处理“苹果”这个词时需要根据上下文判断它是水果还是品牌这就需要“苹果”与句子中其他词建立关联。具体实现上每个输入向量通过三个不同的线性变换得到 Query、Key、Value。Query 表示“我在找什么”Key 表示“我是什么”Value 表示“我携带的信息”。通过 Query 与所有 Key 做点积得到注意力分数再用 softmax 归一化后对 Value 加权求和就得到了当前 token 融合全序列信息的输出向量。专业一点的表述给定输入序列 X首先计算 Q XW_QK XW_KV XW_V然后计算Attention(Q, K, V) softmax(QK^T / √d_k) V其中 d_k 是 Key 的维度除以 √d_k 是为了防止点积结果过大导致 softmax 梯度消失。1.2 最小可运行的自注意力代码下面用 PyTorch 写一个单头自注意力模块不依赖任何高级封装方便理解每一步的计算过程。import torch import torch.nn as nn import math class SelfAttention(nn.Module): def __init__(self, d_model: int, d_k: int None, d_v: int None): d_model: 输入向量维度 d_k: Query/Key 投影维度默认取 d_model d_v: Value 投影维度默认取 d_model super().__init__() d_k d_k if d_k is not None else d_model d_v d_v if d_v is not None else d_model self.d_k d_k self.d_v d_v self.w_q nn.Linear(d_model, d_k, biasFalse) self.w_k nn.Linear(d_model, d_k, biasFalse) self.w_v nn.Linear(d_model, d_v, biasFalse) def forward(self, x, maskNone): x: [batch_size, seq_len, d_model] mask: [batch_size, seq_len, seq_len] 或 None 返回: [batch_size, seq_len, d_v] batch_size, seq_len, _ x.shape Q self.w_q(x) # [B, T, d_k] K self.w_k(x) # [B, T, d_k] V self.w_v(x) # [B, T, d_v] scores torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k) # scores: [B, T, T] if mask is not None: scores scores.masked_fill(mask 0, float(-inf)) attn_weights torch.softmax(scores, dim-1) output torch.matmul(attn_weights, V) # output: [B, T, d_v] return output, attn_weights这个模块去掉了一切花哨设计保留自注意力最核心的计算链路。面试时能默写这份代码并能逐行解释每个张量的 shape 变化基本就过了第一关。1.3 面试高频追问与误区面试官围绕自注意力最常见的追问是为什么点积之后要除以 √d_k当 d_k 较大时点积结果的方差也会变大softmax 会进入梯度极小区域。除以 √d_k 后点积结果的方法被拉回 1 附近梯度更稳定。mask 在什么时候用编码器里主要用 padding mask把无效位置的注意力分数置为负无穷。解码器里除了 padding mask 还要用因果 mask保证当前位置只能看到之前的位置。自注意力和 RNN 的本质区别RNN 的隐状态是逐步传递的长距离依赖会有信息衰减和梯度问题自注意力直接计算两两之间的关联路径长度为 1但代价是 O(n²) 的计算和显存开销。一个典型误区是很多初学者把注意力分数和注意力权重混为一谈。注意力分数是 softmax 之前的点积结果注意力权重是 softmax 之后的归一化结果两者用途完全不同。2. 阶段二Transformer 整体架构——从编码器到解码器自注意力只是 Transformer 的一个组件。大模型算法岗面试不会只问一个组件而是会逐层展开位置编码的作用、残差连接与 LayerNorm 的顺序、Feed-Forward Network 为什么是两层的、编码器与解码器到底哪里不同。2.1 宏观架构拆解Transformer 宏观上分为编码器和解码器两部分。编码器由 N 个相同层堆叠每层包含两个子层多头自注意力子层和前馈网络子层。解码器则由 N 个相同层堆叠每层包含三个子层带因果 mask 的多头自注意力子层、交叉注意力子层、前馈网络子层。每个子层后面都跟着残差连接和 LayerNorm。这个设计很关键残差连接让深层网络的信息流动更顺畅LayerNorm 则让每层输入分布更稳定。以 6 层编码器、6 层解码器的经典配置为例整体数据流向是输入序列 - Embedding 位置编码 - N层编码器 - 编码器输出 解码输入 - Embedding 位置编码 - N层解码器 - 线性层 - Softmax - 输出概率其中解码器的交叉注意力子层Query 来自解码器上一层输出Key 和 Value 来自编码器输出。这也是“交叉”二字的含义在两种不同序列之间做注意力。2.2 位置编码原理与代码实现自注意力本身不具备序列顺序信息。对模型来说“我打你”和“你打我”的 token 组合完全相同只是顺序不同。如果不加位置信息模型无法区分这两个句子的含义。经典 Transformer 使用正弦位置编码。它的设计巧妙之处在于位置 p 的编码向量可以通过线性变换表示为位置 p k 的编码向量这有助于模型学习相对位置关系。import torch import math def sinusoidal_position_encoding(seq_len: int, d_model: int) - torch.Tensor: 生成正弦位置编码 返回: [seq_len, d_model] pe torch.zeros(seq_len, d_model) position torch.arange(0, seq_len, dtypetorch.float).unsqueeze(1) div_term torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)) pe[:, 0::2] torch.sin(position * div_term) pe[:, 1::2] torch.cos(position * div_term) return pe # 示例生成 50 个位置、512 维的位置编码 pos_enc sinusoidal_position_encoding(50, 512) print(pos_enc.shape) # torch.Size([50, 512])大模型时代位置编码演进出很多变体。RoPE旋转位置编码是目前主流大模型最常用的方案它的做法是把位置信息编码进 Query 和 Key 的旋转矩阵中能更好地处理长序列外推。面试时至少要能说清三类位置编码类型代表方法核心思路优点绝对位置编码正弦编码、可学习位置编码给每个位置一个独立的编码向量简单直接相对位置编码T5 的相对位置偏置建模两两位置之间的相对距离外推能力更好旋转位置编码RoPE通过旋转矩阵注入位置信息长文本外推能力强主流大模型常用2.3 从经典架构到大模型架构的差异面试时还会遇到一个高频对比题经典 Transformer 和现代大模型架构有什么区别。现代大模型普遍采用解码器架构并且去掉了编码器与交叉注意力。GPT 系列就是典型的因果解码器架构只保留带因果 mask 的自注意力和前馈网络。这种设计更符合自回归生成的需求根据前文预测下一个 token。另一个差异是归一化层的升级。经典 Transformer 使用 Post-LN即子层计算完后再加 LayerNorm现代大模型多使用 Pre-LN先归一化再进入子层。Pre-LN 训练更稳定更容易扩展到深层网络。激活函数也发生了变化。经典 Transformer 使用 ReLU现代大模型普遍使用 SwiGLU、GeGLU 等门控激活函数。门控激活函数给网络引入了更多非线性表达能力但会增加参数量。3. 阶段三多头注意力机制——为什么要拆成多个头多头注意力是 Transformer 中“多头”的意义所在。单头注意力只能建模一种关联模式多头注意力则允许模型在不同子空间里分别学习不同类型的关联关系。3.1 多头注意力的运行过程多头注意力的实现思路是将 Query、Key、Value 分别投影到 h 个低维子空间在每个子空间独立计算注意力最后把 h 个头的输出拼接起来再通过一个线性层融合。设头数为 h每个头的维度为 d_head d_model / h则总参数量与单头注意力的投影维度为 d_model 时基本一致。这是多头注意力的一个优势在不显著增加参数量的情况下增强了模型的表达能力。面试官经常追问为什么多头注意力有效可以从两个角度回答从表征角度看多头让模型在不同表示子空间捕获不同类型的依赖关系比如一个头关注句法关系另一个头关注语义关系从优化角度看多头注意力中的每个头可以看作多个注意力机制的平均集成降低了单一头过拟合的风险。3.2 多头注意力完整实现下面是一个标准的多头注意力实现代码中加上了 padding mask 和因果 mask 的支持。import torch import torch.nn as nn import math class MultiHeadAttention(nn.Module): def __init__(self, d_model: int, n_heads: int): super().__init__() assert d_model % n_heads 0, d_model 必须能被 n_heads 整除 self.d_model d_model self.n_heads n_heads self.d_head d_model // n_heads self.w_q nn.Linear(d_model, d_model, biasFalse) self.w_k nn.Linear(d_model, d_model, biasFalse) self.w_v nn.Linear(d_model, d_model, biasFalse) self.out_proj nn.Linear(d_model, d_model, biasFalse) def forward(self, x, maskNone, use_causal_maskFalse): batch_size, seq_len, _ x.shape Q self.w_q(x) K self.w_k(x) V self.w_v(x) # 拆分为多个头: [B, n_heads, T, d_head] Q Q.view(batch_size, seq_len, self.n_heads, self.d_head).transpose(1, 2) K K.view(batch_size, seq_len, self.n_heads, self.d_head).transpose(1, 2) V V.view(batch_size, seq_len, self.n_heads, self.d_head).transpose(1, 2) # 注意力分数 scores torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_head) if use_causal_mask: # 因果 mask: 上三角置为 -inf causal_mask torch.triu(torch.ones(seq_len, seq_len, devicex.device), diagonal1).bool() scores scores.masked_fill(causal_mask, float(-inf)) if mask is not None: # mask: [B, 1, 1, seq_len] 或 [B, 1, seq_len, seq_len] scores scores.masked_fill(mask 0, float(-inf)) attn_weights torch.softmax(scores, dim-1) context torch.matmul(attn_weights, V) # context: [B, n_heads, T, d_head] context context.transpose(1, 2).contiguous().view(batch_size, seq_len, self.d_model) output self.out_proj(context) return output, attn_weights这段代码在面试中非常有价值。它用不到 40 行完成了一个标准多头注意力模块面试官如果要求“手写一个多头注意力”这段代码可以直接作为答案框架。3.3 多头注意力的现代变体MQA 与 GQA大模型推理阶段KV Cache 会占用大量显存。为了降低推理开销业界设计了 MQAMulti-Query Attention和 GQAGrouped-Query Attention。MQA 的做法是让所有 Query 头共享同一组 Key 和 Value。这样 KV Cache 的大小从 n_heads 份降为 1 份显存占用大幅降低代价是模型质量可能略有下降。GQA 是 MQA 与 MHA 的折中。它把 Query 头分成若干组每组共享一组 Key 和 Value。比如 32 个 Query 头分成 8 组那么 Key 和 Value 只有 8 份KV Cache 减少到原来的 1/4模型效果损失较小。方法KV 头数量KV Cache 显存推理速度模型质量MHAn_heads基准基准基准GQAn_groups约 n_groups / n_heads更快接近 MHAMQA1约 1 / n_heads最快可能略降很多主流大模型都采用 GQA面试被问到时除了说明概念最好能补充一句GQA 是在推理效率和模型质量之间做 trade-off具体分组数需要根据模型规模和硬件资源确定。4. 阶段四从单卡到多卡训练——大模型训练的关键工程能力大模型算法岗面试中多卡训练是区分“会调参”和“懂工程”的重要考察点。很多候选人能背出数据并行、模型并行的概念但被问到“梯度怎么同步”“通信开销怎么评估”“ZeRO 三个阶段分别优化了哪些维度”时就会卡壳。4.1 数据并行与梯度同步数据并行是最基础的多卡训练方式。每张卡保存一份完整的模型副本处理不同的 batch 数据。前向和反向传播各自独立完成之后进行梯度同步将所有卡上的梯度做 AllReduce 求平均再用平均梯度更新每一张卡上的模型参数。PyTorch 中使用 DistributedDataParallel 可以很方便地实现数据并行。import torch import torch.distributed as dist import torch.nn as nn from torch.nn.parallel import DistributedDataParallel as DDP def init_process_group(world_size: int, rank: int): dist.init_process_group( backendnccl, init_methodenv://, world_sizeworld_size, rankrank, ) torch.cuda.set_device(rank) class SimpleModel(nn.Module): def __init__(self, vocab_size1000, d_model512, n_layers4): super().__init__() self.embedding nn.Embedding(vocab_size, d_model) self.layers nn.ModuleList([nn.TransformerEncoderLayer(d_model, 8) for _ in range(n_layers)]) self.lm_head nn.Linear(d_model, vocab_size) def forward(self, x): x self.embedding(x) for layer in self.layers: x layer(x) return self.lm_head(x) def train_with_ddp(rank: int, world_size: int): init_process_group(world_size, rank) model SimpleModel().to(rank) ddp_model DDP(model, device_ids[rank]) optimizer torch.optim.AdamW(ddp_model.parameters(), lr1e-4) dataloader build_dataloader() # 每个进程加载不同数据切片 for step, batch in enumerate(dataloader): input_ids batch[input_ids].to(rank) labels batch[labels].to(rank) logits ddp_model(input_ids) loss torch.nn.functional.cross_entropy(logits.view(-1, logits.size(-1)), labels.view(-1)) optimizer.zero_grad() loss.backward() optimizer.step() if rank 0 and step % 100 0: print(fstep {step}, loss {loss.item()}) dist.destroy_process_group()这份代码的核心在于 DDP 封装后梯度 AllReduce 的细节被隐藏了。但面试时一定要能说出背后的流程DDP 在反向传播过程中每个参数梯度计算完成后会触发 AllReduce 操作把所有 rank 上的梯度进行求和平均。4.2 显存不够时的并行策略当单卡显存放不下模型时数据并行就失效了这时需要引入模型并行策略。面试中主要关注三类张量并行把单个 Transformer 层的参数切分到多张卡上。比如把注意力层的 Q、K、V 权重矩阵按列切分每张卡只计算一部分最后通过 AllReduce 合并结果。Megatron-LM 是张量并行的典型实现。流水线并行把模型的不同层放到不同卡上。第 1 层在第 0 号卡第 2 到第 4 层在第 1 号卡依此类推。数据像流水线一样在卡间传递。朴素的流水线并行会有 GPU 空闲的问题所以有了 micro-batch 切分、1F1B 调度等优化方法。ZeROZero Redundancy Optimizer是 DeepSpeed 提出的显存优化方案它不把模型切到不同卡上而是把优化器状态、梯度、参数分区存储。ZeRO-1 把优化器状态分区ZeRO-2 进一步把梯度分区ZeRO-3 再把模型参数分区。分区后需要用到某个参数时通过通信临时聚合。很多面试者分不清张量并行和流水线并行的区别。记住一个口诀张量并行是“一层拆成多张卡”流水线并行是“多层分配多张卡”。4.3 多卡训练的常见坑多卡训练调试起来比单卡复杂得多面试官常从下列问题中挑一个做深挖。通信开销成为瓶颈怎么办数据并行中每步训练都要做梯度 AllReduce当模型参数量很大、卡数很多时通信时间可能超过计算时间。解决思路是梯度压缩、梯度累积、通信与计算重叠。nvidia-smi 显示显存占用不均匀先检查是否所有进程都加载了正确的数据切片再检查是否有进程提前退出导致其他进程卡在集合通信上。最常见的原因是 DataLoader 没有按 rank 正确切分数据或者某个进程异常退出后没有调用 destroy_process_group。loss 比单卡训练时波动更大多卡训练时全局 batch size 变大学习率一般需要相应调整。常见的经验法则是在 batch size 翻倍时学习率也翻倍但这只是一个经验起点更稳妥的做法是使用学习率预热和衰减策略。5. 阶段五大模型微调与推理优化——看懂 LoRA 和模型部署大模型算法岗日常工作很大一部分是微调和推理优化。面试重点在于你是否理解全参数微调与参数高效微调的区别、LoRA 的原理与实现、推理阶段的显存优化。5.1 全参数微调与 LoRA全参数微调会更新模型所有参数。以 7B 模型为例参数总量约 70 亿如果用 float16 存储需要约 14GB 显存放参数再加上梯度、优化器状态和激活值单卡根本放不下。全参数微调通常需要多卡并行。LoRALow-Rank Adaptation的核心思想是冻结原始模型参数在原始权重旁边增加低秩矩阵来模拟权重更新。对于权重矩阵 W原始更新量 ΔW 是高秩矩阵LoRA 将其分解为两个低秩矩阵 A 和 B满足 ΔW ≈ BA其中 B 的维度为 d_out × rA 的维度为 r × d_in。这样可训练的参数量大幅减少。比如一个 512×512 的权重矩阵原始更新量参数量是 262144使用 LoRA 且 r8 时参数量为 512×88×5128192只有原来的约 3%。5.2 LoRA 微调示例使用 Hugging Face 的 PEFT 库可以很轻松地应用 LoRA。代码思路如下from transformers import AutoModelForCausalLM, AutoTokenizer from peft import LoraConfig, get_peft_model, TaskType model AutoModelForCausalLM.from_pretrained(your-base-model-path, torch_dtypeauto) tokenizer AutoTokenizer.from_pretrained(your-base-model-path) lora_config LoraConfig( task_typeTaskType.CAUSAL_LM, r8, # 低秩矩阵的秩 lora_alpha32, # 缩放因子 target_modules[q_proj, v_proj, k_proj, o_proj], # 对哪些模块做 LoRA lora_dropout0.05, # dropout 比例 biasnone, # 不训练偏置项 ) peft_model get_peft_model(model, lora_config) # 打印可训练参数量 trainable_params sum(p.numel() for p in peft_model.parameters() if p.requires_grad) total_params sum(p.numel() for p in peft_model.parameters()) print(f可训练参数量: {trainable_params / 1e6:.2f}M / 总参数量: {total_params / 1e9:.2f}B)target_modules 的选择会影响微调效果。如果只对 query 和 value 投影做 LoRA显存开销较小但效果可能不如对全部注意力线性层都做 LoRA。实际项目中需要根据任务类型和资源情况调整。面试官会追问的一个点是LoRA 的 r 怎么选r 过小会导致表达能力不足r 过大又失去参数高效的优势。经验上r 在 8 到 64 之间比较多见具体需要做实验验证。此外LoRA 训练完可以单独保存低秩矩阵权重推理时把 BA 合并回原始 W 中这样不增加任何推理延迟。5.3 推理阶段的显存与速度优化部署大模型时显存占用主要来自三部分模型权重、KV Cache 和中间激活值。其中 KV Cache 随着序列长度线性增长是长文本生成场景下最大的显存压力来源。KV Cache 的优化思路与前面提到的 GQA 是一脉相承的。GQA 通过减少 KV 头数量来压缩 KV Cache 体积另一类思路是 PagedAttention它像操作系统管理内存一样管理 KV Cache把显存切分成固定大小的块按需分配避免碎片化这也是 vLLM 的核心思想。推理速度优化方面最常用的是算子融合。把多个小算子合并成一个大算子减少 kernel 启动开销和显存读写次数。FlashAttention 就是典型代表它通过分块计算和在线 softmax 技术避免了完整注意力矩阵的显存占用同时大幅加速计算。技术优化目标原理FlashAttention显存 速度分块计算注意力避免中间矩阵落显存PagedAttention显存KV Cache 分页管理减少碎片算子融合速度合并 kernel减少启动开销量化INT8/INT4显存 速度降低权重和激活值精度面试中还被常问到“量化”。量化是将模型权重从 FP16 压缩到 INT8 或 INT4以减小显存占用和加速推理。W4A16权重 4bit激活 16bit是当前大模型推理中常见的量化方案之一。量化的难点在于精度损失控制代表性的方法有 GPTQ、AWQ 等它们通过校准数据和重参数化来减少量化误差。6. 阶段六高频面试题与工程最佳实践最后一个阶段把前面涉及的面试高频考点和工程实践建议串起来形成可复用的复习框架。6.1 高频面试题速查表下面整理了不同主题的典型面试题按考察频度排列。准备时可以先对着表格自查答不上来的点就回到对应章节复习。主题高频题目回答要点自注意力为什么除以 √d_k防梯度消失控制方差自注意力复杂度和优化O(n²)可用稀疏注意力/线性注意力优化多头注意力多头的作用不同子空间捕捉不同关系参数量基本不变多头注意力MQA/GQA 区别KV 头数量不同显存与效果 trade-off位置编码RoPE 为什么流行相对位置建模、外推能力好模型架构Pre-LN 和 Post-LNPre-LN 更稳定适合深层模型分布式训练梯度同步方式DDP 反向传播后 AllReduce 梯度求平均分布式训练ZeRO-1/2/3 区别分别分区优化器状态、梯度、参数微调LoRA 为什么有效冻结原参数低秩模拟更新量推理KV Cache 是什么缓存历史 Key、Value避免重复计算推理vLLM 为什么快PagedAttention 管理 KV Cache连续批处理6.2 工程最佳实践清单大模型算法岗的日常工作中调模型的工程能力往往比模型结构设计能力更常用。下面这些实践能帮你减少很多无用功。分布式训练时先在小规模数据上验证代码正确性。直接用全量数据跑多卡训练一旦报错排查成本很高。正确做法是先在一个小数据集上用单卡跑通再切到多卡最后再上全量数据。日志记录要结构化。每步记录 loss、学习率、当前 batch 的 token 数、吞吐量tokens/s、显存占用。这些指标能帮你快速定位是数据问题、模型问题还是通信问题。定期做 checkpoint 保存和验证。大模型训练动辄数天中途失败是常态。建议每隔一定步数保存一份完整 checkpoint包含模型权重、优化器状态、学习率调度器状态和当前步数。恢复训练时从最近的 checkpoint 加载。微调任务要区分数据质量与模型能力。如果微调后效果不好先检查数据质量和数据分布再调整超参数。不要一上来就改 LoRA rank 或学习率那是低效的调参方式。安全与合规方面涉及私有数据的微调和模型部署要在授权范围内进行遵循最小权限原则。生产环境中的模型服务要做好访问控制、推理日志审计和输入输出的内容安全过滤。6.3 学习路线建议如果你是从零开始准备大模型算法岗建议按照下面路径推进第一步吃透自注意力与多头注意力的实现细节。做到不参考任何资料能在白板上画出计算流程并写出核心代码。第二步阅读经典 Transformer 论文再看一两篇大模型基础结构的解读文章理清结构演进脉络。第三步动手实现一个小型 GPT。用几千万参数的规模在开源数据集上训练一个文本生成模型。这个过程中的数据预处理、loss 设计、生成采样策略都是面试常考的实操点。第四步学习分布式训练。先理解 DDP 的原理再用多卡跑一个简单模型观察不同并行策略下的显存和速度差异。第五步掌握 LoRA 微调和主流推理加速工具的用法。选一个开源基座模型用 LoRA 微调成一个垂直领域助手然后用 vLLM 或类似工具部署成服务。这一步完整做下来对模型全生命周期会有整体认识。六阶段复习路径的核心逻辑是从原理到实现从单卡到多卡从训练到部署。每一步都建立在代码和实验之上而不是只背概念。面试前把每个阶段的代码重新手写一遍把每个高频追问的答案用自己的话整理一遍准备效果会比只刷面试题扎实得多。Transformer 这条线是大模型算法岗的骨架自注意力、多头注意力、分布式训练、参数高效微调、推理优化是骨架上最重要的几块骨头。把它们逐个啃透再去关注最新的模型结构和训练技巧就有了稳固的基础。接下来就是动手写代码、跑实验、整理自己的 FAQ把这些考点真正变成自己的工程能力。