转载 

https://mp.weixin.qq.com/s/TM9lv6b-9AH8O9ZiApgTBA

下载一个开源模型时,经常会看到一种奇怪现象:模型代码只有几十 KB,权重文件却有几个 GB,甚至几十 GB。

为什么差距这么大?模型的知识到底存放在哪里?当我们输入一句话时,代码和权重又是如何配合,最终生成结果的?

要理解这些问题,不需要先研究 Transformer 或注意力机制。我们从一个只有 3 个参数的小模型开始,一步步走完整个推理过程。

先搭一个最小模型

假设我要做一个判断天气的小模型。

输入两个数:

  • 温度

  • 湿度

输出一个数:

  • 适合出门的分数

分数越高,越适合出门。

模型结构写出来只有几行:

class WeatherModel:    def __init__(self):        self.w1 = None        self.w2 = None        self.b  = None
    def forward(self, temp, humidity):        return temp * self.w1 + humidity * self.w2 + self.b

代码定义了这样一个公式:

分数 = 温度 × w1 + 湿度 × w2 + b

这里有一个很重要的事实:

代码只规定了:

这里需要三个数字。

但它并没有规定:

这三个数字具体是多少。

此时:

w1 = ?w2 = ?b  = ?

都还是空的。

所以模型结构本质上是一张蓝图。

它描述了:

  • 有哪些变量

  • 怎么连接

  • 应该执行什么计算

但它本身并不包含知识。

训练之后,权重来了

假设我们收集了很多历史天气数据:​​​​​​​

温度  湿度  是否适合出门25    80    否23    40    是30    35    是18    95    否...

训练的过程,就是不断调整 w1、w2、b,让模型输出越来越接近真实结果。

经过大量迭代后,得到:​​​​​​​

w1 = 0.7w2 = -0.3b  = 5.0

这三个数字就是模型权重。

很多人会觉得:

不就是三个数字吗?

但这里其实发生了一件更重要的事情。

训练结束后,原始天气数据并不会跟着模型一起保存。

保存下来的只有:​​​​​​​

0.7-0.35.0

这些数字其实是历史经验的压缩结果。

换句话说:​​​​​​​

权重 ≠ 普通数字
权重 = 从训练数据中提炼出来的经验

模型学到的知识,被编码进了这些数字里。

对于 GPT 来说,道理完全一样。

区别只是:​​​​​​​

天气模型3 个参数
GPT-31750 亿个参数

参数数量扩大了几十亿倍,但本质没有变化。

跑一遍:数字的旅程

假设今天:​​​​​​​

温度 = 25湿度 = 80

输入模型。

首先把权重代入公式:

temp × 0.7 + humidity × (-0.3) + 5.0

再把输入代进去:

25 × 0.7 + 80 × (-0.3) + 5.0

开始计算:​​​​​​​

25 × 0.7 = 17.5
80 × (-0.3) = -24.0

最后:​​​​​​​

17.5 + (-24.0) + 5.0
= -1.5

输出:

-1.5

模型认为今天不太适合出门。

这就是一次完整推理。

整个过程中:​​​​​​​

代码决定怎么算
权重决定用什么数字算

两者缺一不可。

加载权重,本质就是填空

很多人每天都会写:

model.load_state_dict(torch.load("weights.pth"))

但很少去想这句话到底干了什么。

在我们的例子里:

weights.pth

里面存的其实就是:​​​​​​​

w1 = 0.7w2 = -0.3b  = 5.0

加载时做的事情,本质等价于:​​​​​​​

model.w1 = 0.7model.w2 = -0.3model.b  = 5.0

就这么简单。

没有神秘计算。

没有自动推理。

没有重新训练。

只是把数字按照名字对应起来:​​​​​​​

w1 → w1
w2 → w2
b → b

然后复制进去。

加载完成后:​​​​​​​

空蓝图+具体数字=完整模型

结构和权重,到底谁更重要?

一个容易被忽视的事实是:

同一个模型结构,可以搭配很多不同权重。

例如:​​​​​​​

同一个 Transformer 架构↓预训练权重 A→ 英文模型
预训练权重 B→ 中文模型
预训练权重 C→ 代码模型

结构没变。

变化的是权重。

可以把它理解成:​​​​​​​

模型结构 = 大脑
模型权重 = 记忆

或者:​​​​​​​

模型结构 = 神经系统
模型权重 = 学到的知识

因此真正决定模型能力的,往往不是结构文件,而是权重文件。

参数为什么会突然爆炸?

在天气模型里:​​​​​​​

输入数量 = 2
参数数量 = 2

很容易理解。

但神经网络规模一大,情况立刻变化。

假设:​​​​​​​

1000 个输入↓1000 个输出

如果每个输出都需要看到所有输入:

1000 × 1000 = 1,000,000

就需要 100 万个权重。

再进一步:​​​​​​​

8192 个输入↓8192 个输出

对应:

8192 × 8192 = 67,108,864

单个矩阵就有 6700 多万个参数。

而 GPT 的一层里不止一个矩阵。

几十层甚至上百层叠起来之后,参数数量很快达到:​​​​​​​

70 亿700 亿1750 亿

级别。

所以权重文件越来越大,不是因为代码变复杂了,而是因为需要保存的数字越来越多。

为什么代码只有几百行,权重却有几十 GB?

看一个真实例子。

假设代码里写着:

nn.Linear(768, 768)

这一行代码只有十几个字符。

但它对应的权重矩阵大小是:

768 × 768 = 589,824

接近 59 万个浮点数。

代码描述的是:

这里有个 768×768 的矩阵

权重保存的是:​​​​​​​

矩阵第 1 个格子是多少矩阵第 2 个格子是多少...矩阵第 589824 个格子是多少

所以:​​​​​​​

代码负责描述规格
权重负责填满内容

规格不会变得特别大。

内容可以无限增长。

这就是为什么:​​​​​​​

模型结构几 KB ~ 几 MB
模型权重几 GB ~ 几百 GB

进入 GPU 之后发生了什么?

加载完成后,权重会被放进显存。

然后开始推理。

CPU 和 GPU 的分工可以简单理解为:​​​​​​​

CPU = 指挥
GPU = 干活

CPU 负责告诉 GPU:​​​​​​​

执行第 1 层执行第 2 层执行第 3 层...

GPU 负责真正计算矩阵乘法。

例如:​​​​​​​

768×768 矩阵乘768×1 向量

最终得到:

768 个输出

这些输出之间互不依赖。

于是 GPU 可以把任务拆给大量计算单元同时执行。

GPU 之所以快,不是因为单个计算特别强,而是因为能同时做大量相同计算。

为什么推理前必须先把权重搬进显存?

很多人会问:

权重不是放在 SSD 里吗?为什么还要占显存?

原因很简单:

推理时会频繁访问权重。

假设一个模型有:

70B 参数

使用 FP16 存储:​​​​​​​

70B × 2 Byte
≈ 140GB

生成一个词时,几乎所有层都会访问自己的权重。

如果每次都去 SSD 读取:​​​​​​​

SSD 带宽≈ 7GB/s

那么读完整个模型就需要十几到二十秒。

而用户希望看到的是:

每秒几十个 Token

显然做不到。

所以实际流程是:​​​​​​​

SSD↓内存↓显存↓GPU计算

推理时,权重常驻显存。

输入数据不断流过这些权重矩阵。

这样 GPU 才能高速完成计算。

最后,把整个过程串起来

从头到尾,其实只有四步:​​​​​​​

1. 定义模型结构
2. 加载训练好的权重
3. 把权重放进显存
4. 输入数据逐层经过计算

最终得到结果。

整个链路如下:​​​​​​​

模型结构(蓝图)
        +
模型权重(经验和知识)
        ↓
加载到模型
        ↓
搬到GPU显存
        ↓
输入逐层流过
        ↓
输出结果

很多人第一次看到 GPT-3 有 1750 亿参数时,以为里面藏着某种全新的数学。

实际上并没有。

模型结构描述的事情非常简单:​​​​​​​

乘法加法结果传给下一层

真正庞大的,不是算法本身,而是那些训练过程中学出来的数字。

大模型之所以“大”,不是因为代码有多复杂,而是因为它记住了太多东西。

代码负责定义大脑的结构。

权重负责保存大脑的记忆。

而推理,本质上就是让输入数据穿过这些记忆,最终得到答案。

更多推荐