960个参数跑通Transformer:从零理解大模型

如果有人问你"大模型有几千亿参数,参数到底是什么",大多数回答要么是"就是权重矩阵"(等于没说),要么丢一堆公式把你吓跑。

这篇从零开始,用一个 960 个参数的迷你 Transformer 把这件事讲透。能跑,有真实输出,不灌水。

参数是什么:不是旋钮,是"话语权"

想象模型在回答一个问题:猫喜欢吃____?

模型内部有几百亿个"小评委",每个评委只管一件具体的事:

  • 评委 A:这个词是不是食物?
  • 评委 B:这个词是不是动物?
  • 评委 C:上下文有没有提到猫?

每个评委给出一个 0 到 1 之间的分数。但每个评委的"话语权"不一样——有的说话算数(权重高),有的说了等于没说(权重低)。

这个"话语权"的数值,就是参数。

python
# 评委A检测出"鱼"的食物属性是0.9
# 但评委A的话语权(参数)是0.8
# 最终贡献:0.9 × 0.8 = 0.72

# 评委B检测出"鱼"的动物属性是0.5
# 但评委B的话语权只有0.1
# 最终贡献:0.5 × 0.1 = 0.05

几百亿个这种数字合在一起,构成了模型的全部"经验"。

训练前,这些数字全是随机数,模型乱说("猫喜欢吃桌子")。训练就是给模型喂海量文本,每次答错就微调这些数值。几万亿字读下来,几百亿个数值慢慢稳定。训练结束,数值固定——这就是你下载的模型文件。

从3个参数开始

python
def predict(word):
    judge_food   = check_food(word)     # 0.9
    judge_animal = check_animal(word)   # 0.5
    judge_context = check_context(word) # 0.8

    score = (judge_food   * weight_A)   # 0.9 × 0.8 = 0.72
          + (judge_animal * weight_B)   # 0.5 × 0.3 = 0.15
          + (judge_context * weight_C)  # 0.8 × 0.7 = 0.56
    return score
    # → 1.43

3 个权重 = 3 个参数。weight_Aweight_Bweight_C 就是参数。训练调的就是这三个数字。

正确答案哪来的?不需要人工标注。从互联网文本里挖掉一个词让模型猜:

text
原文:我家的小猫特别喜欢吃鱼,每天都吃一罐
挖空:我家的小猫特别喜欢吃____,每天都吃一罐
答案:鱼

模型对每个候选词都算一遍分数,选最高的。猜错了就调参数。循环几万亿遍。这叫自监督学习。

参数量怎么爆炸的:乘法不是指数

一个容易踩的坑:100 万参数/层 × 100 层,为什么是 1 亿而不是 100 的 100 次方?

每层的参数是独立的,不累乘,只累加。

text
第1层→第2层:100万个权重(自己的)
第2层→第3层:100万个权重(全新的,跟上一层没关系)
第3层→第4层:100万个权重(又是全新的)
...
100万 × 99层连接 ≈ 1亿

打比方:100 层楼,每层 1000 个房间,每间放 1 张床。总床数 = 100 × 1000 = 10 万,不是 1000 的 100 次方。

那万亿参数怎么来的?把每层做宽 + 层数做多:

text
我的例子:  3个检测器, 1层     → 3个参数
小模型:    几百个检测器, 12层  → ~1亿
GPT-3:    上万个检测器, 96层  → 1750亿

结构一样,维度和层数大了几个数量级,参数自然到几千亿。不需要指数增长,乘法就够了。

三个关键零件

光有加权求和不够,还有三个固定规则让模型能工作。

ReLU:非线性开关

python
def ReLU(x):
    return max(0, x)

负的归零,正的留着。没有任何参数,不需要训练,就这一条折线。

为什么必须有它? 如果每层只做乘法加法(线性变换),不管叠多少层,最后还是一个乘法加一个加法:

text
第1层:y = 2x + 1
第2层:z = 3y - 2 = 3(2x+1) - 2 = 6x + 1

不管叠 100 层还是 1000 层,最后还是一条直线。但语言不是直线的——"猫喜欢吃鱼"合理,"猫喜欢吃桌子"不合理,这是有门槛、有条件的。

ReLU 在每一层做了一个"开关":信号够大才放行,不够大就切断。几十亿个开关叠起来,组合出极其复杂的条件判断逻辑。

一句话:ReLU 让层数有意义。没有它,100 层等于 1 层。

LayerNorm:数值调音器

经过一层的乘法加法,数值可能飘到离谱的范围:

text
第1层输出:[0.1, 0.5, 2.3]   ← 正常
第2层输出:[0.01, 15.7, 842]  ← 飘了
第3层输出:[0.0001, 99000, 8.3e15]  ← 爆了

LayerNorm 就是每层算完后强制把数值压回标准范围——减均值、除标准差。太大就调小,太小就调大。

跟 ReLU 的区别:ReLU 让模型聪明(能学复杂模式),LayerNorm 让模型不崩(数值不爆炸)。

注意力:词与词互看

这是 Transformer 的核心创新。每个词不再只看自己,而是去看上下文里所有其他词,判断谁跟自己相关,相关的信号加强。

实现上,每个词通过三组参数生成三个向量:查询(Q)、键(K)、值(V)。

  • Q(查询):我想找什么样的信息?
  • K(键):我能提供什么样的信息?
  • V(值):我实际携带的信息内容。

当前词的 Q 跟所有词的 K 做点积算相关度,转成权重后,按权重混合所有词的 V。这就是"注意力"。

前馈网络:为什么先升维再降维

注意力让词与词之间通了气,但通气之后,每个词还需要独立消化收集来的信息。这就是前馈网络的工作。

python
def 前馈网络(向量, 层参数):
    升维 = matmul(向量, W1)     # 8维 → 16维
    激活 = [ReLU(x) for x in 升维]
    降维 = matmul(激活, W2)     # 16维 → 8维
    return 降维

为什么不是 8→8,而是绕一圈 8→16→8?

升维 = 把原始特征做组合,生成更丰富的中间判断。

输入是 8 维向量,每个维度是一个"原子特征"。升到 16 维后,多出来的 8 个维度不是凭空变出来的——每个新维度 = 原始特征的某种组合:

text
新第9维 = 0.3×特征1 + 0.7×特征3 + 0.1×特征5 = 0.62
  → 可能代表"是不是食物"(把多个原子特征组合成一个复合判断)

新第10维 = 0.9×特征2 + 0.5×特征8 = -0.33
  → 可能代表"是不是活的"(另一种组合)

打比方:你有身高、体重、年龄、臂展等 8 个原始数据。升维就是从这 8 个数据里搭配出 16 个复合判断——"体型偏大还是偏小""是否适合打篮球""是否开始衰老"——每个复合判断都是原始数据的不同组合。维度越多,能做的组合判断越多。

ReLU 在升维之后立刻砍一刀:16 个组合判断里,负值的("这条线索不相关")直接归零,只有有意义的判断才传给降维那一步。

降维 = 把有效的复合判断压缩总结回 8 维,传给下一层。 就像开会:16 个人各发表意见(升维),最后总结成 8 条结论(降维)。输出维度必须跟输入一样大,否则下一层接不上。

真实模型升得更狠:8192 维升到 28672 维(3.5 倍),从 8192 个原子特征里组合出 28672 个中间判断。

完整 Transformer:8维迷你版

把前面所有概念串起来,写一个能跑的迷你 Transformer。

配置:8 维向量,前馈网络 16 宽,2 层,词表 4 个词(鱼、猫粮、桌子、汽车)。

每层内部做 5 件事:

python
def Transformer层(向量组, 层参数):
    # ① 注意力:词与词互看,按相关度混合信息
    attn = 注意力(向量组, 层参数)  # 用 Q/K/V 三个矩阵

    # ② 残差连接 + LayerNorm(防止信号消失 + 压数值)
    残差 = 向量组 + attn
    归一化 = LayerNorm(残差)

    # ③ 前馈网络:升维→ReLU→降维
    ffn = 前馈网络(归一化, 层参数)  # W1: 8→16, ReLU, W2: 16→8

    # ④ 残差 + LayerNorm
    输出 = LayerNorm(归一化 + ffn)
    return 输出

完整的 predict 流程:

python
def predict(输入词):
    # ① 查词向量表:词 → 8维向量
    向量组 = [词向量表[词] for 词 in 输入词]

    # ② 过2层 Transformer
    for 层参数 in 模型参数:
        向量组 = Transformer层(向量组, 层参数)

    # ③ 取最后一个位置的输出向量
    最终 = 向量组[-1]

    # ④ 跟词表里每个词算分数
    分数 = [点乘(最终, 输出矩阵[i]) for i in range(词表大小)]

    # ⑤ softmax 转概率,选分最高的
    概率 = softmax(分数)
    return 概率

跑出来的真实输出

这个迷你模型有 960 个参数(参数是随机的,未训练):

迷你Transformer运行输出

每层参数明细:

text
注意力 Q/K/V:8×8 × 3 = 192
前馈 W1/W2:8×16 + 16×8 = 256
每层小计:448
2层合计:896

词向量表:4词 × 8维 = 32
输出层:8维 × 4词 = 32
────────────────────
总计:960

输入 ["鱼", "猫粮"] 作为上下文,模型对"猫粮"给出了最高概率(45%)。当然这个结果没有意义——参数是随机的。训练就是把这 960 个数字从随机调到正确的过程。

参数怎么调:梯度下降

训练的核心就一个问题:参数有几百亿个,每个该往哪个方向调、调多少?

想象你蒙着眼睛站在一座山上,要走到最低的谷底(误差最小的地方)。你看不到全貌,只能用脚感受坡度——左脚下斜就往左走,右脚下斜就往右走。每次走一小步,走到坡度变平就停。

这就是梯度下降。坡度(梯度)告诉你该往哪走,学习率决定每步迈多大。

四个步骤循环:

text
① 前向传播:输入文本 → 模型猜一个词
② 算误差:猜的跟正确答案差多远
③ 反向传播:从输出往回算,每个参数对这个误差负多少责任
④ 更新参数:往误差变小的方向调一小步

第③步是核心,叫反向传播。用链式法则,从最后一层开始,逐层往回算:如果某个参数调大一点点,误差是变大还是变小?变大的就往反方向调,变小的就继续往这个方向调。

这个循环跑几万亿次,参数就慢慢稳定下来。

放大到真实模型

同样一套结构,把维度和层数调大:

text
迷你版:    8维, 16前馈, 2层, 4词      → 960参数
GPT-3:    12288维, 49152前馈, 96层, 10万词 → 1750亿

维度从 8 → 12288(放大 1536 倍),前馈从 16 → 49152(放大 3072 倍),乘一下就到几千亿了。原理一模一样,没有魔法。

为什么要分层

一层只能做一次简单判断(一条折线)。不管一层做得多宽,还是一次线性变换——100 万个检测器堆在一层里,也只是"初级检查",没有组长汇总做二次判断。

两层就不一样了:第二层拿第一层的判断结果当输入,在简单判断的基础上做组合判断。浅层看字面,中层看语法,深层看语义,最深层看意图。每多一层,抽象程度上一个台阶。

实际加层遵循收益递减规律:效果先涨后平。模型"学不会"训练数据时加层有用;加到一定程度后 loss 不再下降,再加层只让推理变慢,就该停了。

GPT-3 选 96 层而不是 192 层,就是因为试下来 192 层只多涨了 2% 性能,但推理慢了一倍——不值。

KV缓存:为什么长对话吃显存

模型生成文字是一个字一个字吐的。第 1 轮输入"猫"预测"喜欢",第 2 轮输入"猫喜欢"预测"吃",第 3 轮输入"猫喜欢吃"预测"鱼"……每吐一个字,所有已有词都要重新过一遍注意力。

但注意力的计算有个特点:旧词的 K 和 V 向量每一轮都一样,纯重复计算。

缓存就是把每一层每个已处理词的 K 和 V 存起来,新词来了直接读,不重算。

text
没有缓存:每轮把所有词的 K、V 全部重算
有缓存:只算新词的 K、V,追加到缓存尾部

Q 不缓存——因为每次只有新词需要发起新查询。K 和 V 缓存——因为旧词提供给别人的信息不会变。

缓存的代价是吃显存。以一个 80 层、8192 维的模型为例,对话 1000 个词,KV 缓存大约占 5 GB。对话越长缓存越大——这就是为什么长对话特别贵、特别慢。

整个逻辑链串起来:

互联网文本 → 挖空 → 模型猜 → 对答案 → 算误差 → 反向传播 → 调参数 → 循环万亿遍 → 参数定型 → 成品模型

参数本身不神奇,就是一堆数字乘来乘去。神奇的是训练把它们调到了刚好能"理解语言"的程度。


附:本文迷你 Transformer 完整可运行代码已开源,960 个参数,Python 标准库即可运行,无需安装任何框架。封面图来自 karpathy/nanoGPT,最简单的 GPT 训练库。