1. GPT 的核心概念
GPT 的全称是Generative Pre-trained Transformer,核心是基于 Transformer 的 Decoder 架构,用自回归方式生成文本,关键概念拆解:
- Decoder-only 架构:只使用 Transformer 的解码器部分,不依赖编码器;
- 自回归(Autoregressive):生成文本时,先算第 1 个词的概率→用第 1 个词算第 2 个词的概率→直到生成结束符,核心是 "用过去预测未来";
- 因果掩码(Causal Mask):保证预测第 i 个词时,只能看到前 i-1 个词,看不到后面的词,符合人类阅读生成逻辑;
- Softmax 概率输出:每个位置输出词汇表中所有词的概率,选概率最高的作为生成结果。
2.GPT的数学原理
(1)输入嵌入
GPT 的输入是文本 token 的索引,先转成向量,再加上位置编码,因为 Transformer 本身没有时序信息:
- E(x):Token 嵌入矩阵,把 token 索引转成 d 维向量,d 是模型维度;
- PE(pos):位置编码,给每个位置pos分配唯一向量,GPT 用的是可学习的位置编码,区别于原始 Transformer 的正弦位置编码;
- h0:初始输入向量,嵌入 + 位置。
(2)Decoder 层的核心计算
GPT 的 Decoder 由 N 层堆叠而成,每层做两件事:
① 多头因果自注意力(Multi-Head Causal Self-Attention)
先拆分为多个 "注意力头",每个头计算局部注意力,再拼接。
② 前馈网络
注意力输出后,通过两层全连接 + 激活函数,GPT 用 GELU:
③ 层归一化 + 残差连接(稳定训练)
每层的输入和输出做残差连接,且先做层归一化(Pre-LN):
(3)最终生成概率
最后一层输出经过线性变换 + Softmax,得到每个 token 的概率:
:映射到词汇表维度的矩阵;
:在已知前 t-1 个词的情况下,第 t 个词的概率。
3.GPT的运行代码
接下来简述一个简单的GPT运行代码,只保留核心逻辑,去掉复杂的预训练、优化器等,聚焦生成流程:
模块一:核心库导入
导入 PyTorch 核心库,用于构建神经网络、张量计算,是整个代码的基础框架。
导入 PyTorch 的神经网络模块(nn),包含所有预定义的层,如 Linear、Embedding、LayerNorm和模型基类。
导入 PyTorch 的函数式接口(F),包含常用的激活函数、损失函数、Softmax 等无参数的函数,区别于 nn 中带参数的层。
python
import torch
import torch.nn as nn
import torch.nn.functional as F
# 超参数(极简版,方便理解)
VOCAB_SIZE = 10000 # 词汇表大小
EMBED_DIM = 128 # 嵌入维度
NUM_HEADS = 4 # 注意力头数
NUM_LAYERS = 2 # Decoder层数
MAX_LEN = 50 # 最大生成长度
模块二:因果多头注意力层
python
class CausalSelfAttention(nn.Module):
def __init__(self, embed_dim, num_heads):
super().__init__()
self.embed_dim = embed_dim
self.num_heads = num_heads
self.head_dim = embed_dim // num_heads # 每个头的维度
# 定义Q/K/V的线性变换层
self.q_proj = nn.Linear(embed_dim, embed_dim)
self.k_proj = nn.Linear(embed_dim, embed_dim)
self.v_proj = nn.Linear(embed_dim, embed_dim)
# 多头输出的线性变换层
self.out_proj = nn.Linear(embed_dim, embed_dim)
def forward(self, x):
# x: [batch_size, seq_len, embed_dim]
batch_size, seq_len, _ = x.shape
# 1. 生成Q/K/V:[batch_size, seq_len, embed_dim]
q = self.q_proj(x)
k = self.k_proj(x)
v = self.v_proj(x)
# 2. 拆分多头:[batch_size, num_heads, seq_len, head_dim]
q = q.view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
k = k.view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
v = v.view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
# 3. 计算注意力分数:Q*K^T / sqrt(d_k),[batch_size, num_heads, seq_len, seq_len]
attn_scores = (q @ k.transpose(-2, -1)) / torch.sqrt(torch.tensor(self.head_dim, dtype=torch.float32))
# 4. 加因果掩码:让每个位置只能看到前面的词
# 生成掩码矩阵:下三角为0,上三角为-∞
mask = torch.tril(torch.ones(seq_len, seq_len)).to(x.device)
mask = mask.masked_fill(mask == 0, float('-inf'))
attn_scores = attn_scores + mask
# 5. Softmax归一化,得到注意力权重
attn_weights = F.softmax(attn_scores, dim=-1)
# 6. 加权求和V,得到注意力输出
attn_output = attn_weights @ v # [batch_size, num_heads, seq_len, head_dim]
# 7. 拼接多头:转回[batch_size, seq_len, embed_dim]
attn_output = attn_output.transpose(1, 2).contiguous().view(batch_size, seq_len, self.embed_dim)
# 8. 输出线性变换
output = self.out_proj(attn_output)
return output
模块三:定义Decoder层
python
class DecoderLayer(nn.Module):
def __init__(self, embed_dim, num_heads):
super().__init__()
self.attn = CausalSelfAttention(embed_dim, num_heads)
self.ffn = nn.Sequential(
nn.Linear(embed_dim, 4 * embed_dim), # 前馈网络第一层扩维4倍
nn.GELU(), # GPT用的激活函数
nn.Linear(4 * embed_dim, embed_dim) # 缩维回原维度
)
# 层归一化(Pre-LN,先归一化再计算)
self.norm1 = nn.LayerNorm(embed_dim)
self.norm2 = nn.LayerNorm(embed_dim)
def forward(self, x):
# 1. 注意力层 + 残差连接 + 层归一化
x = x + self.attn(self.norm1(x))
# 2. 前馈网络 + 残差连接 + 层归一化
x = x + self.ffn(self.norm2(x))
return x
模块四:定义GPT模型
python
class MiniGPT(nn.Module):
def __init__(self, vocab_size, embed_dim, num_heads, num_layers, max_len):
super().__init__()
self.vocab_size = vocab_size
self.embed_dim = embed_dim
self.max_len = max_len
# 1. Token嵌入层:把token索引转成向量
self.token_emb = nn.Embedding(vocab_size, embed_dim)
# 2. 可学习的位置编码(GPT的核心特点之一)
self.pos_emb = nn.Embedding(max_len, embed_dim)
# 3. 堆叠Decoder层
self.decoder_layers = nn.Sequential(*[DecoderLayer(embed_dim, num_heads) for _ in range(num_layers)])
# 4. 最终输出层:映射到词汇表维度
self.final_proj = nn.Linear(embed_dim, vocab_size)
def forward(self, x):
# x: [batch_size, seq_len],输入是token索引序列
batch_size, seq_len = x.shape
# 1. Token嵌入:[batch_size, seq_len, embed_dim]
token_emb = self.token_emb(x)
# 2. 位置编码:生成0~seq_len-1的位置索引,再转成向量
pos = torch.arange(0, seq_len, device=x.device).unsqueeze(0).repeat(batch_size, 1)
pos_emb = self.pos_emb(pos)
# 3. 嵌入+位置编码(核心输入)
h = token_emb + pos_emb
# 4. 经过所有Decoder层
h = self.decoder_layers(h)
# 5. 映射到词汇表,输出每个token的概率
logits = self.final_proj(h) # [batch_size, seq_len, vocab_size]
return logits
模块五:定义生成函数
python
def generate(self, start_tokens, max_new_tokens):
# start_tokens: [batch_size, init_seq_len],初始token序列
self.eval() # 切换到评估模式
with torch.no_grad(): # 禁用梯度计算,节省资源
for _ in range(max_new_tokens):
# 截取最后max_len个token(防止位置编码越界)
input_seq = start_tokens[:, -self.max_len:]
# 前向传播,得到最后一个位置的logits
logits = self.forward(input_seq) # [batch_size, seq_len, vocab_size]
last_logits = logits[:, -1, :] # 只取最后一个位置的输出
# Softmax转概率,选概率最高的token(贪心采样)
next_token = torch.argmax(F.softmax(last_logits, dim=-1), dim=-1).unsqueeze(1)
# 把新生成的token拼接到输入序列后
start_tokens = torch.cat([start_tokens, next_token], dim=1)
return start_tokens
模块六:测试代码
python
if __name__ == "__main__":
# 1. 初始化模型
model = MiniGPT(VOCAB_SIZE, EMBED_DIM, NUM_HEADS, NUM_LAYERS, MAX_LEN)
# 2. 模拟输入:batch_size=1,初始序列长度=3(token索引为[10, 20, 30])
start_tokens = torch.tensor([[10, 20, 30]])
# 3. 生成文本:在初始序列后生成10个新token
generated_tokens = model.generate(start_tokens, max_new_tokens=10)
# 4. 打印结果
print("初始token序列:", start_tokens.numpy())
print("生成后的token序列:", generated_tokens.numpy())
运行结果
初始token序列: [[10 20 30]]
生成后的token序列: [[ 10 20 30 3519 2958 7700 3605 31 6336 4467 4838 5154 3128]]
4.结语
- 核心概念:GPT 是 Decoder-only 架构,靠因果掩码 + 自回归生成文本,关键是 "用过去的词预测下一个词";
- 核心公式:核心是因果注意力的计算,以及自回归的概率输出;
- 代码核心:
- Token 嵌入 + 可学习位置编码是输入基础;
- 因果多头注意力保证只能看前面的词;
- 自回归生成是循环拼接新 token,直到达到长度。