📘今日LLM学习笔记总结
一、大模型解码策略
1.1 自回归解码
- 定义:逐词生成下一个词,基于已生成内容继续生成。
- 流程:
- 输入词序列 (
u
) - 重复:
- 模型输出下一个词的概率分布 (
P
) - 采样或选择下一个词 (
u'
) - 将 (
u'
) 拼接到 (u
)
- 直到生成结束词或达到最大长度
1.2 贪心搜索
- 每步选择概率最高的词
- 缺点:容易陷入局部最优,生成内容单调
1.3 束搜索(Beam Search)
- 每步保留前 ( n ) 个最优句子(路径)
- 缓解贪心搜索的局部最优问题
1.4 随机采样
- 基于概率分布采样下一个词
- 优点:生成多样性高
1.5 温度采样
- 温度采样:调整 softmax 函数的温度系数:
\[P(u_j \mid \mathbf{u}_{<i}) = \frac{\exp(l_j/t)}{\sum_{j'} \exp(l_{j'}/t)}
\]
- ( t > 1 ):平滑分布,增加多样性
- ( t < 1 ):锐化分布,更确定
1.6 Top-k 采样
- 仅从概率最高的 ( k ) 个词中采样
- 兼顾质量与多样性
1.7 Top-p 采样(核采样)
- 从累积概率达到 ( p ) 的最小词集中采样
- 适应不同分布形状
1.8 重复惩罚
- n-元惩罚:避免重复连续 n 个词
- 出现惩罚:降低已出现词的概率
- 频率惩罚:降低高频词的概率
1.9 对比解码
- 使用大模型与小模型的概率差进行采样
- 突出重要词汇,提升生成质量
二、解码效率分析与加速算法
2.1 键值缓存(KV Cache)
- 缓存已生成词的 Key 和 Value 向量
- 避免重复计算,提升自回归生成效率
2.2 效率评估指标
- 算力:FLOP/s
- 带宽:byte/s
- 计算强度:FLOP/byte
- 瓶颈判断:
- 计算强度 < GPU 上限 → 带宽瓶颈
- 计算强度 > GPU 上限 → 计算瓶颈
2.3 运算量与访存量估计
- 矩阵乘法:矩阵 \(A \in \mathbb{R}^{n \times m}\) 和矩阵 \(B \in \mathbb{R}^{m \times p}\) 相乘所需的运算量为 2𝑛𝑚p
- 矩阵乘法:矩阵 \(A \in \mathbb{R}^{n \times m}\) 和矩阵 \(B \in \mathbb{R}^{m \times p}\) 相乘访存量 ( O(mn + mp + np) )
- 注意力计算:运算量比访存量为 \(O(\frac{1}{\frac{1}{n}+\frac{1}{m}+\frac{1}{p}})\)
2.4 系统级优化
- FlashAttention:分块计算 + 算子融合,减少中间结果读写
- PagedAttention:显存分页管理,提升缓存效率
- 连续批处理:动态分割请求,提升 GPU 利用率
2.5 解码策略优化
- 推测解码:小模型生成候选,大模型验证,加速 2 倍
- 级联解码:多个模型依次生成,小模型优先
- 非/半自回归解码:一次性或分组生成,减少步数
- Medusa:多预测头 + 推测解码,加速 2.2 倍
- 早退机制:熵值低时提前退出,减少层数计算
- 混合深度:动态路由,最多减少 50% 计算
三、模型压缩
3.1 量化基础
- 量化公式(将映射浮点数到整数的过程):\(\mathbf{X}_{q} = R(\mathbf{X}/S)-Z\)
- 反量化:从量化值中恢复原始值:\(\widetilde{\boldsymbol{X}}=S\cdot(\boldsymbol{X}_{q}+Z)\)
- 量化误差:原始值 \(\mathbf{X}\) 和恢复值 \(\widetilde{\boldsymbol{X}}\) 之间的数值差异:\(\Delta = ||\mathbf{x} - \mathbf{\tilde{x}}||_2^2\)
- 对称量化:( Z = 0 )
- 非对称量化:( Z \neq 0 )
- 粒度:
- 张量量化:整个矩阵一组参数
- 通道量化:每列一组参数
3.2 训练后量化(PTQ)
- 权重量化:最小化重构损失
- GPTQ:逐组量化,支持 3/4 比特
- 激活值量化:处理异常值,混合精度计算
3.3 模型蒸馏
- 基于反馈的蒸馏:使用教师模型的输出概率作为软标签
\[\mathcal{L}(\mathbf{l}_{t}, \mathbf{l}_{s}) = \mathcal{L}_{R}(P_{t}(\cdot), P_{s}(\cdot))
\]
![[image-20251020213704493.png]]
- 基于特征的蒸馏:使用教师模型中间层的输出特征作为监督信息训练学生模型
\[\mathcal{L}(f_{t}(x), f_{s}(x)) = \mathcal{L}_{F}(\Phi(f_{t}(x)), \Phi(f_{s}(x)))
\]
![[image-20251020213735232.png]]
代码学习笔记
1. RMSNorm (5.1_RMSNorm.py
)
核心功能
- 替代传统的LayerNorm,使用均方根进行归一化
- 计算更简单,训练更稳定
代码分析
class LlamaRMSNorm(nn.Module):
def __init__(self, hidden_size, eps=1e-6):
super().__init__()
self.weight = nn.Parameter(torch.ones(hidden_size)) # 可学习的缩放参数
self.variance_epsilon = eps # 数值稳定性常数def forward(self, hidden_states):
input_dtype = hidden_states.dtype
hidden_states = hidden_states.to(torch.float32) # 转为float32保证精度# 核心计算:均方根归一化
variance = hidden_states.pow(2).mean(-1, keepdim=True) # 计算方差
hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)return self.weight * hidden_states.to(input_dtype) # 缩放并恢复原数据类型
关键特点
- 无偏置项:相比LayerNorm只有weight没有bias
- 混合精度支持:计算时用float32,输入输出保持原类型
- 计算高效:仅需计算二阶矩,不需要一阶矩
2. RoPE旋转位置编码 (5.2_RoPE.py
)
核心功能
- 为Query和Key注入绝对位置信息
- 通过旋转矩阵实现位置编码
代码分析
def rotate_half(x):
"""将向量的后半部分与前半部分交换并取反"""
x1 = x[..., : x.shape[-1] // 2] # 前半部分
x2 = x[..., x.shape[-1] // 2 :] # 后半部分
return torch.cat((-x2, x1), dim=-1) # 交换并取反def apply_rotary_pos_emb(q, k, cos, sin, position_ids):
# 根据位置ID获取对应的cos/sin值
cos = cos[position_ids].unsqueeze(1) # [batch, 1, seq_len, dim]
sin = sin[position_ids].unsqueeze(1)# RoPE公式: q_embed = q * cos + rotate_half(q) * sin
q_embed = (q * cos) + (rotate_half(q) * sin)
k_embed = (k * cos) + (rotate_half(k) * sin)return q_embed, k_embed
数学原理
- 将d维向量分为d/2个二维子空间
- 每个子空间应用旋转矩阵:
[cosθ -sinθ] [x_i]
[sinθ cosθ] [x_{i+d/2}]
优势
- 相对位置感知:内积只依赖于相对位置差
- 长度外推:支持比训练更长的序列
3. ALiBi注意力偏置 (5.3_ALiBi.py
)
核心功能
- 通过偏置矩阵引入相对位置信息
- 替代传统的位置编码方法
代码分析
def build_alibi_tensor(attention_mask: torch.Tensor, num_heads: int, dtype: torch.dtype) -> torch.Tensor:
batch_size, seq_length = attention_mask.shape# 计算每个注意力头的斜率
closest_power_of_2 = 2 ** math.floor(math.log2(num_heads))
base = torch.tensor(2 ** (-(2 ** -(math.log2(closest_power_of_2) - 3))), device=attention_mask.device)
powers = torch.arange(1, 1 + closest_power_of_2, device=attention_mask.device, dtype=torch.int32)
slopes = torch.pow(base, powers)# 处理头数不是2的幂的情况
if closest_power_of_2 != num_heads:
extra_base = torch.tensor(2 ** (-(2 ** -(math.log2(2 * closest_power_of_2) - 3))), device=attention_mask.device)
num_remaining_heads = min(closest_power_of_2, num_heads - closest_power_of_2)
extra_powers = torch.arange(1, 1 + 2 * num_remaining_heads, 2, device=attention_mask.device, dtype=torch.int32)
slopes = torch.cat([slopes, torch.pow(extra_base, extra_powers)], dim=0)# 构建相对距离矩阵
arange_tensor = ((attention_mask.cumsum(dim=-1) - 1) * attention_mask)[:, None, :]# 计算ALiBi偏置: m * (i - j), 其中i>j
alibi = slopes[..., None] * arange_tensorreturn alibi.reshape(batch_size * num_heads, 1, seq_length).to(dtype)
关键特点
- 每个头不同斜率:不同注意力头有不同的惩罚强度
- 相对位置惩罚:距离越远,惩罚越大
- 无需学习参数:偏置是预先计算好的
4. MoE混合专家层 (5.4_MoE.py
)
核心功能
- 使用多个专家网络,每个token只由部分专家处理
- 在保持计算效率的同时增加模型容量
代码分析
class MoeLayer(nn.Module):
def __init__(self, experts: List[nn.Module], gate: nn.Module, num_experts_per_tok: int):
super().__init__()
self.experts = nn.ModuleList(experts) # 专家列表
self.gate = gate # 路由网络
self.num_experts_per_tok = num_experts_per_tok # 每个token使用的专家数def forward(self, inputs: torch.Tensor):
# 1. 路由计算
gate_logits = self.gate(inputs) # [batch, seq_len, num_experts]
weights, selected_experts = torch.topk(gate_logits, self.num_experts_per_tok)
weights = F.softmax(weights, dim=1, dtype=torch.float).to(inputs.dtype)# 2. 初始化输出
results = torch.zeros_like(inputs)# 3. 专家计算与聚合
for i, expert in enumerate(self.experts):
# 找出需要当前专家的token位置
batch_idx, nth_expert = torch.where(selected_experts == i)
if len(batch_idx) > 0:
# 加权求和
results[batch_idx] += weights[batch_idx, nth_expert, None] * expert(inputs[batch_idx])return results
工作流程
- 路由:通过gate网络计算每个专家对每个token的权重
- 选择:每个token选择top-k个专家
- 计算:每个专家处理分配给它的token
- 聚合:加权求和所有专家的输出
优势
- 模型容量大:专家数量可以很多
- 计算高效:每个token只使用部分专家
- 灵活可扩展:易于增加专家数量
5. LLaMA主模型 (5.5_LLaMA.py
)
核心结构
class LlamaModel(LlamaPreTrainedModel):
def __init__(self, config: LlamaConfig):
super().__init__(config)
self.vocab_size = config.vocab_size
self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx)# 构建解码器层
self.layers = nn.ModuleList([
LlamaDecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)
])self.norm = LlamaRMSNorm(config.hidden_size, eps=config.rms_norm_eps)# 因果注意力掩码
causal_mask = torch.full(
(config.max_position_embeddings, config.max_position_embeddings), fill_value=True, dtype=torch.bool
)
前向传播
def forward(self, input_ids, attention_mask=None, position_ids=None, **kwargs):
# 1. 输入嵌入
inputs_embeds = self.embed_tokens(input_ids)# 2. 构建注意力掩码
causal_mask = self._update_causal_mask(attention_mask, inputs_embeds)# 3. 逐层处理
hidden_states = inputs_embeds
for decoder_layer in self.layers:
hidden_states = decoder_layer(
hidden_states,
attention_mask=causal_mask,
position_ids=position_ids,
)[0]# 4. 输出归一化
hidden_states = self.norm(hidden_states)return BaseModelOutputWithPast(last_hidden_state=hidden_states)
6. LLaMA解码层 (5.6_LLaMALayer.py)
层结构
class LlamaDecoderLayer(nn.Module):
def __init__(self, config: LlamaConfig, layer_idx: int):
super().__init__()
self.hidden_size = config.hidden_size
self.self_attn = LlamaAttention(config=config, layer_idx=layer_idx) # 注意力层
self.mlp = LlamaMLP(config) # 前馈网络
self.input_layernorm = LlamaRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
self.post_attention_layernorm = LlamaRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
前向传播
def forward(self, hidden_states, attention_mask=None, position_ids=None, **kwargs):
# 1. 注意力子层(Pre-Norm结构)
residual = hidden_states
hidden_states = self.input_layernorm(hidden_states) # 前置归一化
hidden_states, self_attn_weights, present_key_value = self.self_attn(
hidden_states=hidden_states,
attention_mask=attention_mask,
position_ids=position_ids,
**kwargs,
)
hidden_states = residual + hidden_states # 残差连接# 2. MLP子层(Pre-Norm结构)
residual = hidden_states
hidden_states = self.post_attention_layernorm(hidden_states) # 前置归一化
hidden_states = self.mlp(hidden_states)
hidden_states = residual + hidden_states # 残差连接return (hidden_states,)
7.核心设计思想总结
1. 归一化策略
- RMSNorm替代LayerNorm:计算更简单,效果相当
- Pre-Norm结构:在子层之前进行归一化,训练更稳定
2. 位置编码
- RoPE:通过旋转注入绝对位置信息,支持长度外推
- ALiBi:通过偏置引入相对位置信息,无需学习参数
3. 注意力机制
- 分组查询注意力:平衡计算效率和表达能力
- 因果掩码:保证自回归生成特性
4. 模型架构
- Decoder-Only:适合生成任务
- 残差连接:促进梯度流动,支持深层网络
- 模块化设计:各组件解耦,易于扩展
5. 扩展性设计
- MoE架构:通过专家混合增加模型容量而不显著增加计算量
- 可配置性:通过Config类灵活调整模型结构