欢迎光临
我们一直在努力

从零手搓大模型(七)GPT 转 Llama:从教学版 GPT 走向现代 LLM 架构

GPT 转 Llama:从教学版 GPT 走向现代 LLM 架构

前面几章的主线是:

第 1 章从零手搓大模型(一)文本如何变成 LLM 的输入:文本 -> token ID -> embedding 第 2 章从零手搓大模型(二)Attention 机制到底在算什么:attention 让 token 读取上下文 第 3 章从零手搓大模型(三)从零实现一个 GPT 模型:把 embedding、attention、FFN、LayerNorm 组装成 GPT 第 4 章从零手搓大模型(四)预训练、损失函数与文本生成:真正训练 GPT,让它学会预测下一个 token 第 5 章从零手搓大模型(五)把 GPT 微调成垃圾信息分类器:把 GPT 微调用于分类任务 第 6 章从零手搓大模型(六)让 GPT 学会按指令回答:怎样让它从只会“续写文本”变得学会“按照用户指令完成任务”

第7章这个模块教你把 GPT 骨架逐步改造成 Llama:RMSNorm、RoPE、SwiGLU、GQA、现代 tokenizer、权重加载。

1. GPT 和 Llama 的关系

GPT 和 Llama 都是 decoder-only Transformer。

它们的主干非常像:

token embedding
-> 多层 Transformer block
-> final norm
-> output head

差异主要在 block 内部细节:

GPT: LayerNorm + learned positional embedding + GELU FFN + MHA
Llama: RMSNorm + RoPE + SwiGLU FFN + GQA

这些差异就是现代 LLM 架构演化的重点。

2. 第一个变化:LayerNorm -> RMSNorm

GPT 主线里用的是 LayerNorm。

Llama 用的是 RMSNorm。

LayerNorm 会使用均值和方差:

(x – mean) / sqrt(var + eps)

RMSNorm 只用均方根:

x / sqrt(mean(x^2) + eps)

公式可以理解成:

RMSNorm 不减均值,只按整体大小缩放。

好处是:

计算更简单
训练效果通常也很好
现代 LLM 大量使用

3. 第二个变化:GELU FFN -> SwiGLU FFN

GPT 的 feed forward 通常是:

Linear -> GELU -> Linear

Llama 使用 SwiGLU:

class FeedForward(nn.Module):
def __init__(self, cfg):
super().__init__()
self.fc1 = nn.Linear(cfg["emb_dim"], cfg["hidden_dim"], bias=False)
self.fc2 = nn.Linear(cfg["emb_dim"], cfg["hidden_dim"], bias=False)
self.fc3 = nn.Linear(cfg["hidden_dim"], cfg["emb_dim"], bias=False)

def forward(self, x):
x_fc1 = self.fc1(x)
x_fc2 = self.fc2(x)
x = nn.functional.silu(x_fc1) * x_fc2
return self.fc3(x)

关键是这行:

nn.functional.silu(x_fc1) * x_fc2

它是门控结构:

一路生成内容
一路控制哪些内容通过

SwiGLU 是现代 LLM feed forward 的常见标配。

4. 第三个变化:绝对位置 embedding -> RoPE

GPT 主线里用了 learned positional embedding:

token embedding + position embedding

Llama 使用 RoPE,Rotary Position Embedding。

RoPE 不再把位置向量直接加到 token embedding 上,而是在 attention 里旋转 query 和 key。

代码路线是:

cos, sin = compute_rope_params(...)
queries = apply_rope(queries, cos, sin)
keys = apply_rope(keys, cos, sin)

直觉理解:

RoPE 把位置信息编码进 Q/K 的角度关系里。

这样 attention score 里天然带有相对位置信息。

5. RoPE 的核心代码

先预计算 cos/sin:

def compute_rope_params(head_dim, theta_base=10_000, context_length=4096, freq_config=None):
inv_freq = 1.0 / (
theta_base ** (torch.arange(0, head_dim, 2).float() / head_dim)
)
positions = torch.arange(context_length)
angles = positions.unsqueeze(1) * inv_freq.unsqueeze(0)
angles = torch.cat([angles, angles], dim=1)
cos = torch.cos(angles)
sin = torch.sin(angles)
return cos, sin

再应用到 Q/K:

def apply_rope(x, cos, sin):
x1 = x[..., : head_dim // 2]
x2 = x[..., head_dim // 2 :]
rotated = torch.cat((x2, x1), dim=1)
x_rotated = (x * cos) + (rotated * sin)
return x_rotated.to(dtype=x.dtype)

你不用一开始完全推导数学,先记住:

RoPE 只作用在 attention 的 Q/K 上,不作用在 V 上。

6. 第四个变化:MHA -> GQA

Llama 3 开始更典型地使用 GQA。

GQA 的核心是:

query heads 多
key/value groups 少
多个 query heads 共享 K/V

在 GroupedQueryAttention 里:

self.W_query = nn.Linear(d_in, d_out, bias=False)
self.W_key = nn.Linear(d_in, num_kv_groups * head_dim, bias=False)
self.W_value = nn.Linear(d_in, num_kv_groups * head_dim, bias=False)

然后:

keys = keys.repeat_interleave(self.group_size, dim=1)
values = values.repeat_interleave(self.group_size, dim=1)

这让 Llama 更省 KV cache 显存,更适合长上下文推理。

7. Llama TransformerBlock 的结构

Llama block 仍然是:

norm -> attention -> residual
norm -> feed forward -> residual

示意:

shortcut = x
x = self.norm1(x)
x = self.att(x, mask, cos, sin)
x = x + shortcut

shortcut = x
x = self.norm2(x)
x = self.ff(x)
x = x + shortcut

这叫 pre-norm 结构。

和 GPT 主线相比,整体骨架没变,变的是 norm、attention、FFN 的内部实现。

8. Llama3Model 主体

standalone-llama32.ipynb 里的模型大致是:

class Llama3Model(nn.Module):
def __init__(self, cfg):
self.tok_emb = nn.Embedding(cfg["vocab_size"], cfg["emb_dim"])
self.trf_blocks = nn.ModuleList(
[TransformerBlock(cfg) for _ in range(cfg["n_layers"])]
)
self.final_norm = nn.RMSNorm(cfg["emb_dim"])
self.out_head = nn.Linear(cfg["emb_dim"], cfg["vocab_size"], bias=False)

forward 里动态创建 causal mask:

mask = torch.triu(
torch.ones(num_tokens, num_tokens, device=x.device, dtype=torch.bool),
diagonal=1
)

然后把 x, mask, cos, sin 传进每个 block。

9. 为什么不预存巨大 mask

Llama 3.1 / 3.2 支持很长上下文。

如果预先创建:

128k x 128k causal mask

内存会非常夸张。

所以 notebook 里强调:mask 可以 forward 时按实际 num_tokens 动态创建。

这是从教学模型走向真实长上下文模型时非常重要的工程意识。

10. Llama 2 -> Llama 3 / 3.1 / 3.2

这个目录里不是只讲 Llama 2。

推荐路线是:

GPT -> Llama 2
Llama 2 -> Llama 3
Llama 3 -> Llama 3.1 / 3.2

变化主要包括:

  • tokenizer 改变。
  • 词表大小改变。
  • 上下文长度改变。
  • RoPE 频率缩放策略改变。
  • 使用 GQA。
  • 模型配置不同。

你可以把它看成“现代开源模型结构演进”的压缩课。

11. 权重加载为什么重要

主线章节训练的是小模型。

这个 bonus 会加载 Meta / Hugging Face 上的预训练权重。

这一步能帮助你理解:

从零实现模型结构
然后把官方权重映射到自己的 PyTorch 类里

这是学习开源模型实现非常重要的能力。

权重加载函数常见逻辑:

检查 shape 是否匹配
把外部权重复制到自己模型的参数里
处理命名差异

12. 建议

Llama 不是推翻 GPT,而是在 GPT decoder-only 骨架上换成更现代的 norm、位置编码、FFN 和 attention 设计。

赞(0)
未经允许不得转载:171主机测评 » 从零手搓大模型(七)GPT 转 Llama:从教学版 GPT 走向现代 LLM 架构
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址