
我们考虑的嵌入和模型大小类似于小型 GPT-2 模型。
我们将具体实现最小的 GPT-2 模型(1.24 亿参数)的架构,参考 Radford 等人发表的 Language Models are Unsupervised Multitask Learners(注意,最初报告中列出该模型参数量为 1.17 亿,但模型权重库后来更正为 1.24 亿)。
后续部分将展示如何将预训练权重加载到我们的实现中,以支持 3.45 亿、7.62 亿和 15.42 亿参数的模型大小。
1.24亿参数GPT-2型号的配置细节包括:
GPT_CONFIG_124M = {
"vocab_size": 50257, # Vocabulary size
"context_length": 1024, # Context length
"emb_dim": 768, # Embedding dimension
"n_heads": 12, # Number of attention heads
"n_layers": 12, # Number of layers
"drop_rate": 0.1, # Dropout rate
"qkv_bias": False # Query-Key-Value bias
}
"vocab_size" 表示词汇表大小为 50,257,由 BPE 分词器支持。"context_length" 表示模型的最大输入词元数量,由位置嵌入实现。"emb_dim" 是输入词元的嵌入维度,将每个输入词元转换为 768 维向量。"n_heads" 是多头注意力机制中的注意力头数。"n_layers" 是模型中的 Transformer 块数量。"drop_rate" 是 dropout 机制的强度,在第 3 章中讨论过;0.1 表示在训练过程中丢弃 10% 的隐藏单元,以减轻过拟合。"qkv_bias" 决定多头注意力机制中的 Linear 层在计算查询(Q)、键(K)和值(V)张量时是否包含偏置向量;我们将禁用此选项,这是现代 LLM 的标准做法。
# create 2 training examples with 5 dimensions (features) each
batch_example = torch.randn(2, 5)
layer = nn.Sequential(nn.Linear(5, 6), nn.ReLU())
out = layer(batch_example)
print(out)
输出
tensor([[0.0000, 0.0000, 0.1504, 0.2049, 0.0694, 0.0000],
[0.0000, 0.0000, 0.1146, 0.3098, 0.0939, 0.5742]],
grad_fn=<ReluBackward0>)
mean = out.mean(dim=-1, keepdim=True)
var = out.var(dim=-1, keepdim=True)
print("Mean:\n", mean)
print("Variance:\n", var)
Mean:
tensor([[0.3448],
[0.2182]], grad_fn=<MeanBackward1>)
Variance:
tensor([[0.0791],
[0.2072]], grad_fn=<VarBackward0>)
dim=-1 会在最后一个维度(此处为特征维度)上执行计算,而不是在行维度上执行。
out_norm = (out - mean) / torch.sqrt(var)
print("Normalized layer outputs:\n", out_norm)
mean = out_norm.mean(dim=-1, keepdim=True)
var = out_norm.var(dim=-1, keepdim=True)
print("Mean:\n", mean)
print("Variance:\n", var)
输出
Normalized layer outputs:
tensor([[ 1.9920, -0.1307, -0.3069, -0.7573, -0.2769, -0.5201],
[-0.4793, -0.4793, -0.4793, -0.1003, 2.0176, -0.4793]],
grad_fn=<DivBackward0>)
Mean:
tensor([[-9.9341e-09],
[ 4.5945e-08]], grad_fn=<MeanBackward1>)
Variance:
tensor([[1.0000],
[1.0000]], grad_fn=<VarBackward0>)
torch.set_printoptions(sci_mode=False)
print("Mean:\n", mean)
print("Variance:\n", var)
输出
Mean:
tensor([[ -0.0000],
[ 0.0000]], grad_fn=<MeanBackward1>)
Variance:
tensor([[1.0000],
[1.0000]], grad_fn=<VarBackward0>)
LayerNorm 类:class LayerNorm(nn.Module):
def __init__(self, emb_dim):
super().__init__()
self.eps = 1e-5
self.scale = nn.Parameter(torch.ones(emb_dim))
self.shift = nn.Parameter(torch.zeros(emb_dim))
def forward(self, x):
"""
args:
x: torch.Tensor
The input tensor
returns:
norm_x: torch.Tensor
The normalized tensor
Step:
1. Compute the mean and variance separately
2. Normalize the tensor
3. Scale and shift the tensor
4. Return the normalized tensor
"""
# complete this section (3/10)
# 1. 计算每个特征的均值和方差
mean = x.mean(dim=-1,keepdim=True)
variance = x.var(dim=-1,keepdim=True,unbiased=False)
# 2. 对张量进行归一化处理
x_normalized = (x - mean) / torch.sqrt(variance + self.eps)
# 3. 缩放并平移张量
norm_x = self.scale * x_normalized + self.shift
# 4. 返回归一化后的张量
return norm_x
scale 和 shift。scale(乘以 1)和 shift(加 0)值不会产生任何效果;但是,scale 和 shift 是可训练的参数,LLM 会在训练期间自动调整它们,以提高模型在训练任务中的表现。eps),以避免方差为 0 时的除零错误。有偏方差
在上述方差计算中,设置 unbiased=False 意味着使用公式
∑
i
(
x
i
−
x
ˉ
)
2
n
\cfrac{\sum_i (x_i - \bar{x})^2}{n}
n∑i(xi−xˉ)2 计算方差,其中 n 为样本大小(在这里为特征或列数);此公式不包含贝塞尔校正(其分母为 n-1),因此提供了方差的有偏估计。
对于嵌入维度 n 很大的 LLM,使用 n 和 n-1 之间的差异可以忽略不计。
然而,GPT-2 的归一化层是在有偏方差下训练的,因此为了与我们将在后续章节加载的预训练权重兼容,我们也采用了这种设置。
现在让我们实际尝试 LayerNorm:
ln = LayerNorm(emb_dim=5)
out_ln = ln(batch_example)
mean = out_ln.mean(dim=-1, keepdim=True)
var = out_ln.var(dim=-1, unbiased=False, keepdim=True)
print("Mean:\n", mean)
print("Variance:\n", var)
输出
Mean:
tensor([[ -0.0000],
[ -0.0000]], grad_fn=<MeanBackward1>)
Variance:
tensor([[0.9999],
[1.0000]], grad_fn=<VarBackward0>)
class GELU(nn.Module):
def __init__(self):
super().__init__()
def forward(self, x):
"""
args:
x: torch.Tensor
The input tensor
returns:
torch.Tensor
The output tensor
"""
# Complete this section (4/10)
# Approximate GELU using the tanh-based formula
return 0.5 * x * (1 + torch.tanh((torch.sqrt(torch.tensor(2 / 3.1415)) * (x + 0.044715 * torch.pow(x, 3)))))
import matplotlib.pyplot as plt
gelu, relu = GELU(), nn.ReLU()
# Some sample data
x = torch.linspace(-3, 3, 100)
y_gelu, y_relu = gelu(x), relu(x)
plt.figure(figsize=(8, 3))
for i, (y, label) in enumerate(zip([y_gelu, y_relu], ["GELU", "ReLU"]), 1):
plt.subplot(1, 2, i)
plt.plot(x, y)
plt.title(f"{label} activation function")
plt.xlabel("x")
plt.ylabel(f"{label}(x)")
plt.grid(True)
plt.tight_layout()
plt.show()
输出
FeedForward,稍后将在 LLM 的 Transformer 块中使用:class FeedForward(nn.Module):
def __init__(self, cfg):
super().__init__()
"""
implement self.layers as a Sequential model with:
1. Linear layer with input dimension cfg["emb_dim"] and output dimension 4*cfg["emb_dim"]
2. GELU activation function
3. Linear layer with input dimension 4*cfg["emb_dim"] and output dimension cfg["emb_dim"]
"""
# complete this section (5/10)
self.layers = nn.Sequential(
nn.Linear(cfg["emb_dim"], 4 * cfg["emb_dim"]), # 1. 线性层,输入维度 cfg["emb_dim"],输出 4*cfg["emb_dim"]
GELU(), # 2. 使用 GELU 激活函数
nn.Linear(4 * cfg["emb_dim"], cfg["emb_dim"]) # 3. 线性层,输入维度 4*cfg["emb_dim"],输出 cfg["emb_dim"]
)
def forward(self, x):
return self.layers(x)
print(GPT_CONFIG_124M["emb_dim"])
输出
768

ffn = FeedForward(GPT_CONFIG_124M)
# input shape: [batch_size, num_token, emb_size]
x = torch.rand(2, 3, 768)
out = ffn(x)
print(out.shape)
输出
torch.Size([2, 3, 768])
