feat: add explanation

pull/478/head
Zhenyu Wang 2025-08-15 14:45:05 +08:00
parent 561979c7e3
commit fd6448ce61
2 changed files with 17 additions and 2 deletions

View File

@ -133,6 +133,7 @@ class Attention(nn.Module):
self.n_local_kv_heads = self.num_key_value_heads self.n_local_kv_heads = self.num_key_value_heads
self.n_rep = self.n_local_heads // self.n_local_kv_heads self.n_rep = self.n_local_heads // self.n_local_kv_heads
self.head_dim = args.hidden_size // args.num_attention_heads self.head_dim = args.hidden_size // args.num_attention_heads
# 把所有头权重都映射到同一个维度,方便并行运算
self.q_proj = nn.Linear(args.hidden_size, args.num_attention_heads * self.head_dim, bias=False) self.q_proj = nn.Linear(args.hidden_size, args.num_attention_heads * self.head_dim, bias=False)
self.k_proj = nn.Linear(args.hidden_size, self.num_key_value_heads * self.head_dim, bias=False) self.k_proj = nn.Linear(args.hidden_size, self.num_key_value_heads * self.head_dim, bias=False)
self.v_proj = nn.Linear(args.hidden_size, self.num_key_value_heads * self.head_dim, bias=False) self.v_proj = nn.Linear(args.hidden_size, self.num_key_value_heads * self.head_dim, bias=False)
@ -151,6 +152,7 @@ class Attention(nn.Module):
attention_mask: Optional[torch.Tensor] = None): attention_mask: Optional[torch.Tensor] = None):
bsz, seq_len, _ = x.shape bsz, seq_len, _ = x.shape
xq, xk, xv = self.q_proj(x), self.k_proj(x), self.v_proj(x) xq, xk, xv = self.q_proj(x), self.k_proj(x), self.v_proj(x)
# 分割并行计算后的结果,还原至各个注意力头
xq = xq.view(bsz, seq_len, self.n_local_heads, self.head_dim) xq = xq.view(bsz, seq_len, self.n_local_heads, self.head_dim)
xk = xk.view(bsz, seq_len, self.n_local_kv_heads, self.head_dim) xk = xk.view(bsz, seq_len, self.n_local_kv_heads, self.head_dim)
xv = xv.view(bsz, seq_len, self.n_local_kv_heads, self.head_dim) xv = xv.view(bsz, seq_len, self.n_local_kv_heads, self.head_dim)
@ -164,6 +166,7 @@ class Attention(nn.Module):
xv = torch.cat([past_key_value[1], xv], dim=1) xv = torch.cat([past_key_value[1], xv], dim=1)
past_kv = (xk, xv) if use_cache else None past_kv = (xk, xv) if use_cache else None
# GQA时可能需要多个Q 共用 KV 矩阵
xq, xk, xv = ( xq, xk, xv = (
xq.transpose(1, 2), xq.transpose(1, 2),
repeat_kv(xk, self.n_rep).transpose(1, 2), repeat_kv(xk, self.n_rep).transpose(1, 2),
@ -179,13 +182,18 @@ class Attention(nn.Module):
output = F.scaled_dot_product_attention(xq, xk, xv, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=True) output = F.scaled_dot_product_attention(xq, xk, xv, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=True)
else: else:
# 计算注意力分数
scores = (xq @ xk.transpose(-2, -1)) / math.sqrt(self.head_dim) scores = (xq @ xk.transpose(-2, -1)) / math.sqrt(self.head_dim)
# 添加三角掩码,防止模型看到未来的信息(在训练和推理阶段都 mask)。这是实现 kv cache 的关键
scores = scores + torch.triu( scores = scores + torch.triu(
torch.full((seq_len, seq_len), float("-inf"), device=scores.device), torch.full((seq_len, seq_len), float("-inf"), device=scores.device),
diagonal=1 diagonal=1
).unsqueeze(0).unsqueeze(0) # scores+mask ).unsqueeze(0).unsqueeze(0) # scores+mask
if attention_mask is not None: if attention_mask is not None:
# attention mask 标记了输入token,并把有意义的 token 值设置为 1
# 掩盖 pad 部分的注意力分数,防止模型关注空白token
# 掩盖的结果就是把 pad 部分的分数设置为极小值,从而在 softmax 时忽略这些 token
extended_attention_mask = attention_mask.unsqueeze(1).unsqueeze(2) extended_attention_mask = attention_mask.unsqueeze(1).unsqueeze(2)
extended_attention_mask = (1.0 - extended_attention_mask) * -1e9 extended_attention_mask = (1.0 - extended_attention_mask) * -1e9
scores = scores + extended_attention_mask scores = scores + extended_attention_mask
@ -384,7 +392,7 @@ class MiniMindModel(nn.Module):
start_pos = past_key_values[0][0].shape[1] if past_key_values[0] is not None else 0 start_pos = past_key_values[0][0].shape[1] if past_key_values[0] is not None else 0
hidden_states = self.dropout(self.embed_tokens(input_ids)) hidden_states = self.dropout(self.embed_tokens(input_ids))
# 在模型顶部计算一次位置编码,之后传给每个 Attention 层
position_embeddings = ( position_embeddings = (
self.freqs_cos[start_pos:start_pos + seq_length], self.freqs_cos[start_pos:start_pos + seq_length],
self.freqs_sin[start_pos:start_pos + seq_length] self.freqs_sin[start_pos:start_pos + seq_length]

View File

@ -47,17 +47,24 @@ def train_epoch(epoch, wandb):
res.logits.view(-1, res.logits.size(-1)), res.logits.view(-1, res.logits.size(-1)),
Y.view(-1) Y.view(-1)
).view(Y.size()) ).view(Y.size())
# 实际句子长度通常小于max_length,pad部分的loss_mask是0,不需要计算损失
loss = (loss * loss_mask).sum() / loss_mask.sum() loss = (loss * loss_mask).sum() / loss_mask.sum()
# 添加辅助损失,用于训练专家选择
loss += res.aux_loss loss += res.aux_loss
# 梯度累积(/累计步数)
loss = loss / args.accumulation_steps loss = loss / args.accumulation_steps
# 放大 Loss,因为 loss 高精度而梯度低精度,防止极小的梯度丢失。
# 相当于链式法则 ∂L_scaled/∂W₁ = ∂(k×L)/∂W₁ = k × ∂L/∂W₁,k是缩放因子
scaler.scale(loss).backward() scaler.scale(loss).backward()
if (step + 1) % args.accumulation_steps == 0: if (step + 1) % args.accumulation_steps == 0:
# 缩小梯度
scaler.unscale_(optimizer) scaler.unscale_(optimizer)
# 梯度裁剪,防止梯度爆炸
torch.nn.utils.clip_grad_norm_(model.parameters(), args.grad_clip) torch.nn.utils.clip_grad_norm_(model.parameters(), args.grad_clip)
scaler.step(optimizer) scaler.step(optimizer)
# 更新缩放因子
scaler.update() scaler.update()
optimizer.zero_grad(set_to_none=True) optimizer.zero_grad(set_to_none=True)