以下是基于你列出的框架整理的大模型算法面试八股完整答案清单。
一、基础
1. KL散度、交叉熵、两者关系
KL散度(Kullback-Leibler Divergence) 衡量两个概率分布P和Q之间的差异:\(D_{KL}(P||Q) = \sum_x P(x) \log \frac{P(x)}{Q(x)}\)。非对称,\(D_{KL}(P||Q) \neq D_{KL}(Q||P)\)。
交叉熵(Cross Entropy) :\(H(P,Q) = -\sum_x P(x) \log Q(x)\)。
关系:\(H(P,Q) = H(P) + D_{KL}(P||Q)\)。当P固定时,最小化交叉熵等价于最小化KL散度。
2. 大模型中的幻觉、复读机等现象的成因与解决方法
幻觉(Hallucination)成因:
- 训练数据中的知识冲突或错误信息
- 模型对不确定内容的过度自信生成
- 解码策略(如过高的temperature)导致随机性过大
复读机(Repetition)成因:
- 注意力机制对已生成内容的过度关注
- 缺乏多样性惩罚机制
- 训练数据中存在重复模式
解决方法:
- 引入重复惩罚(repetition penalty)
- 调整解码参数(temperature、top_p、top_k)
- 使用对比解码(Contrastive Decoding)
- RLHF阶段通过奖励模型惩罚重复/幻觉内容
- 检索增强生成(RAG)提供事实依据
3. 为什么使用decoder-only架构
- GPT系列验证:decoder-only在语言建模任务上表现优异,Scaling Law得到充分验证
- 因果掩码:天然适配自回归生成任务
- 训练效率:相比encoder-decoder,参数量更少,训练更高效
- 统一范式:预训练和下游任务(生成)一致,无需额外适配
- 主流模型(LLaMA、Qwen、GPT等)均采用decoder-only
4. 梯度爆炸、梯度消失、梯度饱和现象、成因和解决办法
| 现象 | 成因 | 解决办法 |
|---|---|---|
| 梯度消失 | 深层网络中梯度逐层相乘趋于0;激活函数(sigmoid/tanh)饱和区导数接近0 | 使用ReLU/GELU/SwiGLU;残差连接;Pre-Norm;梯度裁剪 |
| 梯度爆炸 | 梯度逐层相乘大于1;初始化不当 | 梯度裁剪(gradient clipping);合适的权重初始化(Xavier/He);梯度累计 |
| 梯度饱和 | 激活函数进入饱和区(如sigmoid输出接近0或1) | 使用非饱和激活函数(ReLU/GELU);BatchNorm/LayerNorm |
5. GPT、BERT、CLIP、Llama
| 模型 | 架构 | 训练任务 | 特点 |
|---|---|---|---|
| GPT | Decoder-only | 自回归语言建模 | 因果掩码,生成能力强 |
| BERT | Encoder-only | MLM + NSP | 双向注意力,理解能力强 |
| CLIP | Dual Encoder | 图文对比学习 | 多模态对齐,zero-shot能力强 |
| Llama | Decoder-only | 自回归语言建模 | Pre-Norm + RMSNorm + SwiGLU + RoPE,开源标杆 |
6. Python进程/线程/协程、GIL锁、异步计数器、async
- 进程:操作系统资源分配的基本单位,拥有独立内存空间,多进程可绕过GIL
- 线程:CPU调度的基本单位,同一进程内线程共享内存,受GIL限制
- 协程:用户态轻量级线程,通过
async/await实现,单线程内并发 - GIL(全局解释器锁) :CPython中限制同一时刻只有一个线程执行Python字节码,CPU密集任务需用多进程,IO密集可用多线程/协程
- 异步计数器:
asyncio.Semaphore或asyncio.Queue用于控制并发数
7. SGD优化器、Momentum、Adam、AdamW、Muon
| 优化器 | 核心思想 | 特点 |
|---|---|---|
| SGD | \(θ = θ - η∇L\) | 简单,但收敛慢,易陷入局部最优 |
| Momentum | 引入动量项,累积历史梯度 | 加速收敛,减少震荡 |
| Adam | Momentum + RMSProp自适应学习率 | 自适应,收敛快,但可能泛化差 |
| AdamW | Adam + 解耦的权重衰减 | 修正Adam的L2正则化问题,大模型标配 |
| Muon | 矩阵级优化,而非逐元素 | 显存占用比AdamW降低约48%,DeepSeek等采用 |
8. 深拷贝浅拷贝
- 浅拷贝:拷贝对象本身,但内部元素仍指向原对象的引用(
copy.copy()) - 深拷贝:递归拷贝对象及其所有子对象,完全独立(
copy.deepcopy())
9. 智能指针(C++)
- unique_ptr:独占所有权,不可拷贝
- shared_ptr:共享所有权,引用计数
- weak_ptr:弱引用,不增加引用计数,解决循环引用
10. 为什么大模型选用交叉熵损失
- 语言建模本质是分类问题(预测下一个token),交叉熵是分类任务的标准损失
- 最大化似然估计等价于最小化交叉熵
- 梯度形式良好,易于优化
- 与KL散度直接关联,便于与RLHF中的KL惩罚对接
11. 为什么回归用MSE,分类用交叉熵
- MSE:假设误差服从高斯分布,适合连续值预测;梯度与残差成正比
- 交叉熵:适合概率分布预测;梯度形式为\((p-y)\),避免了MSE在饱和区的梯度消失问题
二、训练推理加速
1. 推理优化
vLLM:PagedAttention、Continuous Batching
PagedAttention:将KV Cache分页管理,类似操作系统的虚拟内存:
- 将KV缓存划分为固定大小的"页"(如16个token/块)
- 非连续物理显存 + 逻辑页表映射
- 内存利用率从50%提升至96%以上
- 吞吐量提升2-4倍
Continuous Batching:动态批处理,请求到达后立即加入批次,无需等待固定batch填满,显著降低延迟。
FlashAttention vs PagedAttention:
- FlashAttention聚焦单条序列的注意力计算,解决内存访问效率低的问题
- PagedAttention聚焦多请求并发的KV Cache管理
- 两者正交,可配合使用
KV Cache
自回归生成时,每个token的K、V向量在后续生成中会被重复使用。KV Cache将已生成token的K、V缓存下来,避免重复计算。
显存占用:\(2 \times L \times H \times \text{seq\_len} \times \text{precision\_bytes}\)(L为层数,H为隐藏维度)。
Prefill & Decode
- Prefill阶段:处理输入提示词(prompt),并行计算所有输入token的注意力,生成第一个输出token
- Decode阶段:自回归逐token生成,每步只计算新token的注意力,利用KV Cache
梯度检查点 & 梯度累计
- 梯度检查点(Gradient Checkpointing) :前向时丢弃部分中间激活值,反向时重新计算,以时间换显存
- 梯度累计(Gradient Accumulation) :将大batch拆分为多个micro-batch,累加梯度后再更新,以时间换显存
2. 训练/模型优化
Megatron-LM:三种并行
| 并行方式 | 切分对象 | 通信模式 | 适用场景 |
|---|---|---|---|
| 数据并行(DP/DDP) | 数据分片 | AllReduce梯度 | 模型能放入单卡,需要大batch |
| 张量并行(TP) | 层内参数(按head/维度切分) | AllReduce(高频) | 单层参数量过大,单卡放不下 |
| 流水线并行(PP) | 按层切分 | Send/Recv(低频) | 模型层数过多,设备间通信带宽有限 |
嵌套逻辑:通常TP在单机内(高速NVLink),PP跨节点(较少通信),DP跨所有设备(梯度同步)。
DeepSpeed ZeRO
ZeRO(Zero Redundancy Optimizer)将模型状态(优化器状态、梯度、参数)在数据并行进程间分片:
| 阶段 | 分片内容 | 显存优化 |
|---|---|---|
| ZeRO-1 | 优化器状态分片 | 降至 \(4\Psi + \frac{12\Psi}{N}\) |
| ZeRO-2 | 优化器状态 + 梯度分片 | 进一步降低 |
| ZeRO-3 | 优化器状态 + 梯度 + 参数全分片 | 降至原来的1/8 |
并行节点通信方式
| 通信原语 | 描述 | 应用场景 |
|---|---|---|
| Broadcast | 一对多广播 | 参数初始化 |
| Reduce | 多对一归约 | 梯度汇总到主节点 |
| AllReduce | 多对多归约+广播 | 数据并行梯度同步 |
| AllGather | 多对多收集 | 收集所有节点的数据 |
| Reduce-Scatter | 归约+分散 | 模型并行中的梯度分发 |
FlashAttention
核心思想:通过分块(tiling) 和重计算(recomputation) 减少全局内存IO:
- 将Q、K、V分块加载到SRAM(片上高速缓存)
- 局部计算注意力
- 避免存储中间矩阵 \(QK^T\)(\(O(N^2)\))
效果:训练速度提升15-30%,显存占用降低5-20倍。
MQA、GQA、MLA
| 方法 | 核心思想 | KV Cache | 效果 |
|---|---|---|---|
| MQA(Multi-Query Attention) | 所有head共享一组K、V | 极大减少 | 推理快,但质量有损 |
| GQA(Grouped-Query Attention) | 分组共享K、V | 显著减少 | 质量与效率的折中 |
| MLA(Multi-head Latent Attention) | 低秩压缩KV | 极致减少 | DeepSeek采用 |
3. 模型训练和推理显存需求分析
推理显存 = 模型权重 + KV Cache + 激活值 + 系统开销
训练显存 = 模型权重 + 梯度 + 优化器状态 + 激活值
估算公式:
- 模型大小 = 参数量 × 精度(FP32:4B, FP16/BF16:2B, INT8:1B)
- 训练显存约为推理的2-4倍
示例:Llama 3 70B在FP16下推理约需140GB显存(仅权重),训练需额外存储梯度和优化器状态(Adam需2倍动量+方差)。
三、Transformer内部
1. Norm归一化
Pre-norm、Post-norm、Deep-norm
| 位置 | 说明 | 特点 |
|---|---|---|
| Post-norm | 残差连接后做Norm(原始Transformer) | 正则化强,但深层梯度可能爆炸 |
| Pre-norm | 残差连接前做Norm | 训练稳定,梯度范数近似相等 |
| Deep-norm | 残差连接放大 + 参数缩小 | 兼具Pre-norm稳定和Post-norm性能 |
BatchNorm、LayerNorm、RMSNorm
| 方法 | 归一化维度 | 特点 |
|---|---|---|
| BatchNorm | batch维度 | 适合CV,不适合序列(变长) |
| LayerNorm | feature维度 | 适合序列数据,有均值和方差 |
| RMSNorm | 仅用均方根 | 去除均值中心化,计算更快 |
可学习参数作用:\(\gamma\)(缩放)和\(\beta\)(平移)让网络恢复表达能力。
训练/推理行为差异:BatchNorm训练时用batch统计量,推理时用全局统计量;LayerNorm/RMSNorm训练推理行为一致。
为什么大模型用RMSNorm:
- 去除均值计算,减少计算量
- 研究表明LayerNorm成功的关键是缩放不变性而非平移不变性
- 经过softmax后,偏移信息已隐含在输入中
2. 激活函数
| 激活函数 | 公式 | 特点 |
|---|---|---|
| Sigmoid | \(\sigma(x)=1/(1+e^{-x})\) | 易饱和,梯度消失 |
| Tanh | \(\tanh(x)=(e^x-e^{-x})/(e^x+e^{-x})\) | 零中心,但仍易饱和 |
| ReLU | \(\max(0,x)\) | 非饱和,但负区间死神经元 |
| LeakyReLU | \(\max(\alpha x, x)\) | 解决死神经元 |
| GELU | \(x \cdot \Phi(x)\) | 平滑,Transformer标配 |
| Swish | \(x \cdot \sigma(x)\) | 平滑非单调 |
| SwiGLU | \(Swish(xW_1) \otimes (xW_2)\) | GLU变体,LLaMA等采用 |
SwiGLU为何取代GELU:门控机制提供更强的表达能力,在相同参数量下效果更好。
3. 位置编码
| 类型 | 方法 | 特点 |
|---|---|---|
| 绝对位置编码 | 可学习 / Sinusoidal | 简单,但外推性差 |
| RoPE(旋转位置编码) | 旋转矩阵编码相对位置 | 支持任意长度,相对位置感知 |
| ALiBi | 注意力分数加线性偏置 | 外推能力强 |
RoPE长文本外推失效的根本原因:训练时的旋转角度范围有限,超出训练长度后,位置编码超出训练分布,模型无法正确感知相对位置。
4. 其他
为什么除以√d_k
防止点积结果过大导致softmax进入饱和区(梯度极小)。\(QK^T\)的方差为\(d_k\),除以\(\sqrt{d_k}\)使方差归一化为1。
Encoder vs Decoder
- Encoder:双向自注意力(无掩码),看到完整序列
- Decoder:因果掩码自注意力(只能看到过去)+ Cross-Attention(Encoder提供K、V,Decoder提供Q)
为什么要分为QKV三个矩阵
- Q(Query) :当前token要查询的内容
- K(Key) :被查询token的标识
- V(Value) :被查询token的实际内容
- 分离QKV让模型学习不同的投影空间,增强表达能力
Dropout训练和推理时的行为区别
- 训练时:以概率p随机丢弃神经元(置零),并缩放保留值(乘以\(1/(1-p)\))
- 推理时:不丢弃任何神经元,直接使用完整网络
四、强化学习
1. Value-based方法
| 方法 | 核心思想 |
|---|---|
| 蒙特卡洛法 | 完整episode后更新,无偏但方差大 |
| SARSA | On-policy TD,用当前策略的动作更新 |
| 时序差分法(TD) | 一步更新,有偏但方差小 |
| Q-learning | Off-policy TD,用最优动作更新 |
| DQN | 神经网络近似Q函数,经验回放+目标网络 |
2. Policy-based方法
| 方法 | 核心思想 |
|---|---|
| REINFORCE | 蒙特卡洛策略梯度 |
| Actor-Critic | Actor选动作,Critic评估 |
| TRPO | 信任域策略优化,约束KL散度 |
| PPO | TRPO简化版,clip机制 |
| GRPO | PPO改进,移除Critic |
| DAPO | 数据约束型优势加权 |
| GSPO | 序列级优化,Qwen3采用 |
3. PPO vs GRPO vs DPO
| 维度 | PPO | GRPO | DPO |
|---|---|---|---|
| 所需模型 | Actor + Ref + Reward + Critic(4个) | Actor + Ref + Reward(3个) | Actor + Ref(2个) |
| 优势估计 | GAE + Critic | 组内相对奖励 | 直接偏好对 |
| 训练复杂度 | 最高 | 中等 | 最低 |
| 显存占用 | 最高 | 中等 | 最低 |
| 适用场景 | 追求极致效果 | 大规模训练稳定性优先 | 资源受限、偏好数据充足 |
| 缺点 | 资源消耗巨大 | 需一次生成多个答案 | 探索能力弱 |
PPO的clip解决问题:限制新旧策略差异,避免一步更新太大导致训练崩溃。不clip会导致策略崩溃、模型坍塌。
4. GRPO vs GSPO
核心区别:
- GRPO:token级重要性采样 + token级优化,噪声大、效率低
- GSPO:序列级重要性采样 + 序列级优化,更稳定、更高效
GSPO为何更优:
- 从理论上修正了GRPO中重要性采样的误用
- 序列级奖励与序列级优化对齐
- 显著稳定MoE模型的RL训练
- 训练效率和性能均超越GRPO
显存占用:两者相近,都无需Critic网络。
5. On-policy vs Off-policy
- On-policy:用当前策略采样的数据更新当前策略(PPO、GRPO、SARSA)
- Off-policy:用历史策略采样的数据更新当前策略(Q-learning、DPO)
6. KL估计器 K1、K2、K3
用于估计策略分布与参考分布的KL散度:
- K1:\(D_{KL}(\pi_\theta || \pi_{ref}) = \mathbb{E}[\log(\pi_\theta/\pi_{ref})]\)
- K2:\(D_{KL}(\pi_{ref} || \pi_\theta)\)(反向KL)
- K3:\(\mathbb{E}[(\log(\pi_\theta/\pi_{ref}))^2]\)
7. 正向KL vs 反向KL
- 正向KL \(D_{KL}(P||Q)\):追求覆盖所有P的模式(mean-seeking)
- 反向KL \(D_{KL}(Q||P)\):追求集中在P的高概率区域(mode-seeking)
大模型RL用反向KL:避免模型探索低质量区域,保持生成质量。
8. GRPO的奖励来源
GRPO不需要单独训练奖励模型(RM),奖励来自规则验证器(如数学题的答案正确性)或可验证的奖励信号(如代码执行结果)。
9. KL系数β的影响
- β太大:KL惩罚过重,模型无法充分优化奖励,效果提升有限
- β太小:KL惩罚不足,模型偏离SFT分布过远,可能产生退化输出
10. PPO的Loss组成
\(L = L_{policy} + c_1 \cdot L_{value} - c_2 \cdot L_{entropy}\)
- Policy loss:clip后的策略梯度损失
- Value loss:价值网络预测与回报的MSE
- Entropy bonus:鼓励探索,防止熵坍塌
11. GAE(Generalized Advantage Estimation)
\(\hat{A}_t = \sum_{l=0}^{\infty} (\gamma\lambda)^l \delta_{t+l}\)
λ=0.95的原因:
- λ=0:单步TD,方差小但bias大
- λ=1:蒙特卡洛,方差大但bias小
- λ=0.95:bias-variance trade-off的最佳实践值
12. 其他关键概念
模态/熵坍塌问题:
- Clip-Cov:对协方差高的token做梯度裁剪
- KL-Cov:对优势高且概率大的token做KL限制
- Clip-Higher:提升概率小但分数高的回答的概率
Reward Hacking:模型利用奖励函数的漏洞获得高奖励但实际质量下降。解决:KL惩罚、奖励模型校准、多样性约束。
DPO损失下降但性能不提升:DPO直接优化偏好对,可能过度拟合偏好数据中的噪声,或模型坍缩到简单模式。
Reference Free Alignment(KTO、IPO):无需参考模型的对齐方法,进一步降低显存占用。
补充:其他高频问题
softmax数值上溢/下溢:指数运算导致大数上溢、小数下溢。解决:Safe Softmax - 减去最大值 \(\text{softmax}(x_i) = \frac{e^{x_i - \max(x)}}{\sum_j e^{x_j - \max(x)}}\)。
RL训练让seq_len变长:RL鼓励模型进行更深层次的推理(如CoT),模型学会生成更长的思考过程来获得更高奖励。
Policy entropy的重要性:熵度量策略的随机性,熵过低表示策略坍塌(总是输出相同内容),丧失探索能力。
Clamped entropy:对熵设置上下界,防止熵过高(随机输出)或过低(坍塌)。
GQA vs MHA手撕代码:GQA将Q头分组,每组共享一组K、V头,减少KV Cache。面试常要求实现MHA/GQA的forward。
verl的hybrid engine:结合Megatron的TP/PP和DeepSpeed的ZeRO,实现高效的大规模RL训练。
以下是深度学习/大模型面试中高频出现的手撕代码题目完整清单与答案。
手撕代码高频题完整清单
一、注意力机制
1. Multi-Head Attention (MHA)
最常考,没有之一
import torch
import torch.nn as nn
import torch.nn.functional as F
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads, dropout=0.1):
super().__init__()
assert d_model % num_heads == 0
self.d_model = d_model
self.num_heads = num_heads
self.head_dim = d_model // num_heads
self.q_proj = nn.Linear(d_model, d_model)
self.k_proj = nn.Linear(d_model, d_model)
self.v_proj = nn.Linear(d_model, d_model)
self.o_proj = nn.Linear(d_model, d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, query, key, value, mask=None):
batch_size = query.size(0)
# [batch, seq_len, d_model] -> [batch, seq_len, num_heads, head_dim]
Q = self.q_proj(query).view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
K = self.k_proj(key).view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
V = self.v_proj(value).view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
# Scaled Dot-Product Attention
scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.head_dim ** 0.5) # 除以√d_k
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
attn = F.softmax(scores, dim=-1)
attn = self.dropout(attn)
out = torch.matmul(attn, V) # [batch, num_heads, seq_len, head_dim]
out = out.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model)
return self.o_proj(out)
时间复杂度:\(O(n^2 \cdot d)\),n为序列长度,d为维度。
2. Grouped-Query Attention (GQA)
面试高频,重点考察K/V的分组共享逻辑
class GroupedQueryAttention(nn.Module):
def __init__(self, d_model, num_heads, num_kv_heads, dropout=0.1):
super().__init__()
assert num_heads % num_kv_heads == 0
self.num_heads = num_heads
self.num_kv_heads = num_kv_heads
self.head_dim = d_model // num_heads
self.num_groups = num_heads // num_kv_heads
self.q_proj = nn.Linear(d_model, d_model)
self.k_proj = nn.Linear(d_model, num_kv_heads * self.head_dim)
self.v_proj = nn.Linear(d_model, num_kv_heads * self.head_dim)
self.o_proj = nn.Linear(d_model, d_model)
def forward(self, query, key, value, mask=None):
batch_size = query.size(0)
Q = self.q_proj(query).view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
K = self.k_proj(key).view(batch_size, -1, self.num_kv_heads, self.head_dim).transpose(1, 2)
V = self.v_proj(value).view(batch_size, -1, self.num_kv_heads, self.head_dim).transpose(1, 2)
# 核心:将K/V按组重复扩展到num_heads
K = K.repeat_interleave(self.num_groups, dim=1) # [batch, num_heads, seq_len, head_dim]
V = V.repeat_interleave(self.num_groups, dim=1)
scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.head_dim ** 0.5)
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
attn = F.softmax(scores, dim=-1)
out = torch.matmul(attn, V)
out = out.transpose(1, 2).contiguous().view(batch_size, -1, self.num_heads * self.head_dim)
return self.o_proj(out)
3. Multi-Query Attention (MQA)
所有head共享同一组K、V
class MultiQueryAttention(nn.Module):
def __init__(self, d_model, num_heads, dropout=0.1):
super().__init__()
self.num_heads = num_heads
self.head_dim = d_model // num_heads
self.q_proj = nn.Linear(d_model, d_model)
self.k_proj = nn.Linear(d_model, self.head_dim) # 只有1组K
self.v_proj = nn.Linear(d_model, self.head_dim) # 只有1组V
self.o_proj = nn.Linear(d_model, d_model)
def forward(self, query, key, value, mask=None):
batch_size = query.size(0)
Q = self.q_proj(query).view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
K = self.k_proj(key).view(batch_size, -1, 1, self.head_dim).transpose(1, 2) # 1个head
V = self.v_proj(value).view(batch_size, -1, 1, self.head_dim).transpose(1, 2)
# K/V扩展到所有head
K = K.expand(-1, self.num_heads, -1, -1)
V = V.expand(-1, self.num_heads, -1, -1)
scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.head_dim ** 0.5)
# ... 后续同MHA
三者对比: | | MHA | MQA | GQA | |---|---|---|---| | K/V组数 | = num_heads | 1 | num_kv_heads (< num_heads) | | KV Cache | 最大 | 最小 | 中等 | | 质量 | 最高 | 有损 | 折中 |
二、归一化
4. LayerNorm
class LayerNorm(nn.Module):
def __init__(self, hidden_size, eps=1e-12):
super().__init__()
self.gamma = nn.Parameter(torch.ones(hidden_size)) # 可学习缩放
self.beta = nn.Parameter(torch.zeros(hidden_size)) # 可学习偏移
self.eps = eps
def forward(self, x):
# 在最后一个维度(feature维度)上归一化
mean = x.mean(-1, keepdim=True)
var = x.var(-1, keepdim=True, unbiased=False)
x_norm = (x - mean) / torch.sqrt(var + self.eps)
return self.gamma * x_norm + self.beta
5. RMSNorm
大模型标配,比LayerNorm少计算均值
class RMSNorm(nn.Module):
def __init__(self, hidden_size, eps=1e-6):
super().__init__()
self.weight = nn.Parameter(torch.ones(hidden_size)) # 只有缩放参数
self.eps = eps
def forward(self, x):
# RMS = root mean square
rms = torch.sqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
return self.weight * x / rms
三、前馈网络
6. FFN (ReLU/GELU)
class PositionwiseFFN(nn.Module):
def __init__(self, d_model, d_ff, dropout=0.1):
super().__init__()
self.linear1 = nn.Linear(d_model, d_ff)
self.linear2 = nn.Linear(d_ff, d_model)
self.activation = nn.GELU() # 或 ReLU
self.dropout = nn.Dropout(dropout)
def forward(self, x):
return self.linear2(self.dropout(self.activation(self.linear1(x))))
7. SwiGLU
class SwiGLU(nn.Module):
def __init__(self, d_model, d_ff, dropout=0.1):
super().__init__()
self.w1 = nn.Linear(d_model, d_ff) # 门控
self.w2 = nn.Linear(d_model, d_ff) # 值
self.w3 = nn.Linear(d_ff, d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
# Swish(xW1) ⊗ (xW2)
gate = F.silu(self.w1(x)) # Swish = x * sigmoid(x)
value = self.w2(x)
return self.w3(self.dropout(gate * value))
四、损失函数
8. 交叉熵损失
def cross_entropy_loss(logits, targets, ignore_index=-100):
"""
logits: [batch_size, seq_len, vocab_size] 或 [batch_size, vocab_size]
targets: [batch_size, seq_len] 或 [batch_size]
"""
# 对于语言模型,需要shift
if logits.dim() == 3:
shift_logits = logits[:, :-1, :].contiguous()
shift_labels = targets[:, 1:].contiguous()
logits = shift_logits.view(-1, shift_logits.size(-1))
targets = shift_labels.view(-1)
log_probs = F.log_softmax(logits, dim=-1)
# 手动实现(等效于nn.CrossEntropyLoss)
loss = -log_probs.gather(dim=-1, index=targets.unsqueeze(-1)).squeeze(-1)
if ignore_index is not None:
mask = targets != ignore_index
loss = loss[mask]
return loss.mean()
9. Binary Cross Entropy (BCE)
def binary_cross_entropy(pred, target):
# pred: [batch_size, ...] 经过sigmoid后的概率
eps = 1e-12
pred = torch.clamp(pred, eps, 1 - eps)
return -(target * torch.log(pred) + (1 - target) * torch.log(1 - pred)).mean()
10. InfoNCE 对比损失
def info_nce_loss(query, key, temperature=0.07):
"""
query: [batch_size, dim]
key: [batch_size, dim]
正样本:同索引的query-key对
负样本:batch内其他key
"""
logits = torch.matmul(query, key.T) / temperature # [batch, batch]
labels = torch.arange(query.size(0)).to(query.device)
return F.cross_entropy(logits, labels)
五、位置编码
11. 绝对位置编码(Sinusoidal)
def sinusoidal_position_encoding(seq_len, d_model):
pe = torch.zeros(seq_len, d_model)
position = torch.arange(0, seq_len, dtype=torch.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 # [seq_len, d_model]
12. 可学习位置编码
class LearnablePositionalEncoding(nn.Module):
def __init__(self, max_seq_len, d_model):
super().__init__()
self.pe = nn.Parameter(torch.randn(max_seq_len, d_model))
def forward(self, x):
# x: [batch, seq_len, d_model]
return x + self.pe[:x.size(1), :]
13. RoPE 旋转位置编码
def rotate_half(x):
x1, x2 = x.chunk(2, dim=-1)
return torch.cat([-x2, x1], dim=-1)
def apply_rotary_pos_emb(q, k, cos, sin):
# cos, sin: [seq_len, head_dim]
q_embed = (q * cos) + (rotate_half(q) * sin)
k_embed = (k * cos) + (rotate_half(k) * sin)
return q_embed, k_embed
六、采样与生成
14. Top-K 采样
def top_k_sampling(logits, k, temperature=1.0):
"""
logits: [vocab_size] 模型输出logits
"""
logits = logits / temperature
# 保留概率最大的k个
top_k_values, top_k_indices = torch.topk(logits, k)
# 其余设为-inf
mask = torch.ones_like(logits) * float('-inf')
mask.scatter_(0, top_k_indices, top_k_values)
probs = F.softmax(mask, dim=-1)
return torch.multinomial(probs, 1).item()
15. Top-P (Nucleus) 采样
def top_p_sampling(logits, p, temperature=1.0):
"""
logits: [vocab_size]
p: 累积概率阈值,如0.9
"""
logits = logits / temperature
sorted_logits, sorted_indices = torch.sort(logits, descending=True)
sorted_probs = F.softmax(sorted_logits, dim=-1)
# 累积概率超过p的截断
cum_probs = torch.cumsum(sorted_probs, dim=-1)
mask = cum_probs > p
mask = torch.cat([torch.zeros_like(mask[:1]), mask[:-1]], dim=-1) # 保留第一个超过p的
sorted_logits[mask] = float('-inf')
logits = sorted_logits.scatter(0, sorted_indices, sorted_logits)
probs = F.softmax(logits, dim=-1)
return torch.multinomial(probs, 1).item()
七、强化学习相关
16. PPO Loss
def ppo_loss(log_probs, old_log_probs, advantages, clip_epsilon=0.2):
"""
log_probs: 当前策略下动作的对数概率
old_log_probs: 旧策略下动作的对数概率
advantages: GAE计算的优势
"""
ratio = torch.exp(log_probs - old_log_probs) # 重要性采样比率
# Clipped surrogate objective
surr1 = ratio * advantages
surr2 = torch.clamp(ratio, 1 - clip_epsilon, 1 + clip_epsilon) * advantages
policy_loss = -torch.min(surr1, surr2).mean()
return policy_loss
17. GAE (Generalized Advantage Estimation)
def compute_gae(rewards, values, gamma=0.99, lam=0.95):
"""
rewards: [T] 每一步奖励
values: [T+1] 每一步价值估计
"""
advantages = torch.zeros_like(rewards)
gae = 0
for t in reversed(range(len(rewards))):
delta = rewards[t] + gamma * values[t+1] - values[t]
gae = delta + gamma * lam * gae
advantages[t] = gae
returns = advantages + values[:-1]
return advantages, returns
18. GRPO Loss
def grpo_loss(log_probs, old_log_probs, rewards, group_size=4):
"""
GRPO: 组内相对优势,无需Critic网络
"""
# 将同一prompt的多个response分组
# rewards: [batch_size] 每个response的奖励
advantages = (rewards - rewards.mean()) / (rewards.std() + 1e-8) # 组内标准化
ratio = torch.exp(log_probs - old_log_probs)
# 同样使用clip
policy_loss = -torch.min(ratio * advantages,
torch.clamp(ratio, 0.8, 1.2) * advantages).mean()
return policy_loss
八、LeetCode高频题
面试通常要求Hot 100的Medium难度,以下为最高频:
| 类别 | 高频题 |
|---|---|
| 链表 | 反转链表、环形链表、合并K个升序链表 |
| 二叉树 | 层序遍历、二叉树右视图、最大深度 |
| 动态规划 | 最长公共子序列、最大子数组和、跳跃游戏 |
| 双指针 | 三数之和、接雨水 |
| 滑动窗口 | 无重复字符的最长子串 |
| DFS/BFS | 岛屿数量、课程表 |
九、其他高频手撕
19. Softmax(Safe Softmax)
def safe_softmax(x, dim=-1):
# 减去最大值防止上溢
x_max = x.max(dim=dim, keepdim=True)[0]
x_exp = torch.exp(x - x_max)
return x_exp / x_exp.sum(dim=dim, keepdim=True)
20. Sigmoid
def sigmoid(x):
return 1 / (1 + np.exp(-x))
# 或 torch.sigmoid(x)
21. Dropout
class Dropout(nn.Module):
def __init__(self, p=0.5):
super().__init__()
self.p = p
def forward(self, x):
if not self.training: # 推理时不做任何操作
return x
mask = torch.rand_like(x) > self.p
# 训练时缩放,保持期望不变
return x * mask / (1 - self.p)
22. NMS(非极大值抑制)
def nms(boxes, scores, iou_threshold=0.5):
# boxes: [N, 4] (x1, y1, x2, y2)
# 按分数降序排列
order = scores.argsort(descending=True)
keep = []
while order.numel() > 0:
i = order[0]
keep.append(i)
# 计算与其他框的IoU
ious = compute_iou(boxes[i], boxes[order[1:]])
order = order[1:][ious <= iou_threshold]
return keep
备考建议
- MHA是必考,一定要能无辅助默写
- GQA是重点,理解分组逻辑和repeat_interleave
- LayerNorm/RMSNorm要能快速写出
- Top-K/Top-P采样代码要熟悉
- LeetCode Hot 100 Medium是底线
- RL相关:PPO loss + GAE是最高频