跳转至

以下是基于你列出的框架整理的大模型算法面试八股完整答案清单。


一、基础

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.Semaphoreasyncio.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:

  1. 将Q、K、V分块加载到SRAM(片上高速缓存)
  2. 局部计算注意力
  3. 避免存储中间矩阵 \(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

备考建议

  1. MHA是必考,一定要能无辅助默写
  2. GQA是重点,理解分组逻辑和repeat_interleave
  3. LayerNorm/RMSNorm要能快速写出
  4. Top-K/Top-P采样代码要熟悉
  5. LeetCode Hot 100 Medium是底线
  6. RL相关:PPO loss + GAE是最高频