很多人觉得大语言模型(LLM)是一个深不可测的黑箱,动辄千亿参数、万卡集群。但剥开工程化的外壳,其核心推理架构用几十行代码就能讲清楚。今天,我们就手写一个“微缩版”大模型——它具备GPT系列最根本的因果自注意力与前馈网络,能真正运行并生成文本。代码极简,但灵魂俱全。
大模型之所以能看懂上下文,全靠注意力机制。而“因果”(Causal)意味着模型在预测下一个词时,只能看到当前位置之前的词,不能“偷看”未来。这是生成式任务的根本原则。
下面我们用PyTorch手写一个纯正的因果注意力模块:
import torch
import torch.nn as nn
import torch.nn.functional as F
class CausalAttention(nn.Module):
def __init__(self, d_model, n_head):
super().__init__()
assert d_model % n_head == 0
self.n_head = n_head
self.head_dim = d_model // n_head
# 一次性计算 Q、K、V,提升效率
self.qkv = nn.Linear(d_model, 3 * d_model)
self.proj = nn.Linear(d_model, d_model)
def forward(self, x):
B, T, C = x.shape # 批次、序列长度、特征维度
qkv = self.qkv(x).reshape(B, T, 3, self.n_head, self.head_dim)
qkv = qkv.permute(2, 0, 3, 1, 4) # 拆出 Q、K、V
q, k, v = qkv[0], qkv[1], qkv[2]
# 缩放点积注意力
att = (q @ k.transpose(-2, -1)) * (self.head_dim ** -0.5)
# 生成下三角掩码,屏蔽未来位置(核心!)
mask = torch.tril(torch.ones(T, T, device=x.device)).view(1, 1, T, T)
att = att.masked_fill(mask == 0, float('-inf'))
att = F.softmax(att, dim=-1)
y = (att @ v).transpose(1, 2).reshape(B, T, C)
return self.proj(y)重点解释:torch.tril生成的下三角矩阵,就是大模型“自回归”性质的物理体现。一旦去掉这个掩码,它就变成BERT那样的双向编码器,无法做续写生成。
仅靠注意力还不够,大模型还需要前馈网络(FFN)来做非线性变换,以及残差连接和层归一化来稳定训练。一个标准的Decoder Block长这样:
class FeedForward(nn.Module):
def __init__(self, d_model):
super().__init__()
# 经典扩缩结构:4倍维度膨胀
self.net = nn.Sequential(
nn.Linear(d_model, 4 * d_model),
nn.GELU(), # 比ReLU更平滑的激活
nn.Linear(4 * d_model, d_model)
)
def forward(self, x): return self.net(x)
class DecoderBlock(nn.Module):
def __init__(self, d_model, n_head):
super().__init__()
self.ln1 = nn.LayerNorm(d_model)
self.attn = CausalAttention(d_model, n_head)
self.ln2 = nn.LayerNorm(d_model)
self.ffwd = FeedForward(d_model)
def forward(self, x):
# 残差连接(Pre-Norm结构,现今主流)
x = x + self.attn(self.ln1(x))
x = x + self.ffwd(self.ln2(x))
return x把上述DecoderBlock重复堆叠 N 层(如12层、24层),再加上最底层的Embedding和最顶层的Linear输出头,就是一个完整的GPT骨架。
模型搭建好后,生成文本的核心在于迭代采样。大模型每次只预测下一个词的概率分布,我们将这个分布除以温度(Temperature)软化后,按概率抽样选出下一个词,再拼回输入,如此循环往复。
以下是绝不依赖高级框架的原生生成函数:
@torch.no_grad()
def generate(model, idx, max_new_tokens, temperature=1.0, top_k=None):
"""
idx: 初始输入索引,形状 (1, T)
model: 我们手写的Decoder堆叠实例
"""
model.eval()
for _ in range(max_new_tokens):
# 若上下文过长,可截断(此处只取最后1024位,模拟滑动窗口)
idx_cond = idx[:, -1024:]
logits = model(idx_cond) # 前向传播,得到 (B, T, vocab_size)
logits = logits[:, -1, :] # 只取最后一个时间步
logits = logits / temperature # 温度控制随机性
# 可选:Top-K采样,只保留概率最高的K个
if top_k is not None:
v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
logits[logits < v[:, [-1]]] = float('-inf')
probs = F.softmax(logits, dim=-1)
idx_next = torch.multinomial(probs, num_samples=1) # 按概率抽样
idx = torch.cat((idx, idx_next), dim=1) # 拼回序列
return idx仅此三块代码,一个可运行的生成式大模型内核已经完整。如果你把d_model设为768,n_head设为12,堆叠12层,参数量就达到了1亿级别——这已经接近初代GPT-2的规模。
你可能会问:没有反向传播、没有海量数据训练,这能算大模型吗?
请注意,前向推理架构与训练方法是正交的。上面手写的CausalAttention + FeedForward + 残差连接,正是当今所有主流大模型(GPT-4、Llama、Claude)的基础数学操作。它们之间的区别仅在于:
d_model和深度n_layers的放大)但意识的核心,那套让机器理解上下文的注意力公式,已经被我们用20行代码牢牢握在手中。
手写这段代码的意义,并非让你用它去训练ChatGPT(那需要数千张GPU和数月时间),而是帮你祛魅。当你看到masked_fill那行代码时,你就明白了“因果”不是玄学,只是一张下三角矩阵;当你看到multinomial时,你就懂了“创造性”不过是概率抽样。
若你想让这套代码真正跑起来生成一句英文,只需额外补上:
Tokenizer(如字符级编码器)nn.Embedding和nn.Linear输出头DecoderBlock循环堆叠成一个nn.Sequential然后输入[ [1, 2, 3] ],它就能像模像样地吐出[4, 5, 6, ...]——尽管由于未经训练,内容是乱码,但数学逻辑完全正确。
大模型不是魔法,而是一套被极致放大的、优雅的数学公式。今天我们手写了它的最小完备子集:因果注意力保证自回归,前馈网络提供非线性,残差连接保障梯度流动,温度采样赋予生成多样性。
当你下次与AI对话时,可以回想这几行代码——所有深刻的智慧,都始于对q @ k和tril的透彻理解。手写一遍,胜过阅读百篇论文。现在,你不只在使用大模型,你已经握住了它的灵魂骨架。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。