【AI专栏】图解深度学习-AI infra工程师必知必会 - 第 05 章:数值精度与显存账本
博主介绍:程序喵大人
- 35 - 资深C/C++/Rust/Android/iOS客户端开发
- 10年大厂工作经验
- 嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手
- 《C++20高级编程》《C++23高级编程》等多本书籍著译者
- 更多原创精品文章,首发gzh,见文末
- 👇👇记得订阅专栏,以防走丢👇👇
😉C++基础系列专栏
😃C语言基础系列专栏
🤣C++大佬养成攻略专栏
🤓C++训练营
👉🏻个人网站

经过上一章,咱们已经知道训练和推理在显存开销上完全是两套逻辑了。但之前我们在算账的时候,其实一直隐藏了一个大前提:也就是每个数字到底占多大地方?
你看白板上这个精密的“显存计算器”,左边那一排长短不一的色块,代表的就是深度学习里的“精度”。精度这事儿说白了,就是计算机用几个字节(Byte)来记住一个数字。给的字节越多,数字就记得越准、范围越大,但同时显存也被吃得越狠。
作为 AI Infra 工程师,你每天的工作其实就是在天平的两端做取舍:怎么在保证模型变笨得不太明显的前提下,尽量用更少的字节把它塞进有限的显卡里。把下面这几种精度的账算明白了,你就能对任何模型做到心里有数。
一、一个数字占几个字节:FP32 到 INT4 的阶梯

咱们先来看看这几种最常见的精度规格。
最上面那条深蓝色的长条是 FP32(单精度浮点),足足占了 32 个格子(也就是 4 个字节)。这是以前深度学习的标配,特别准。往下看,蓝色的 FP16 和浅蓝的 BF16 都被砍掉了一半,只占 16 个格子(2 个字节)。
再往下,为了极致的省空间,大家开始用整数:绿色的 INT8 只占 1 个字节,而最极端的橙色 INT4,一个数字只分配半个字节。
你看右边算出来的显存差距:如果存 1 亿个参数,用 FP32 需要 400MB,但用 INT4 只要 50MB。在实际工程里,把这些倍数关系(4B、2B、1B、0.5B)当乘法口诀背下来,算显存的时候就能做到秒出答案。
二、FP16 vs BF16:范围 vs 精度的取舍

这里得专门聊聊 FP16 和 BF16,这两个家伙虽然都占 2 个字节,但内部的构造可大不一样,这是面试的绝对高频考点。
你看左边的 FP16,它把更多的格子留给了“尾数”(也就是小数部分),所以它记得很准,但代价是“指数”范围很小,算出来的数稍微大一点就溢出了(爆掉)。如果你用它来做训练,经常得配合 Loss Scaling 这种打补丁的技巧。
再看右边的 BF16,它非常聪明地把“指数”拉宽到了和 FP32 一模一样的程度!这就意味着,凡是 FP32 能装下的天文数字,BF16 也绝对装得下,训练起来极其稳定,根本不担心溢出。虽然它的小数部分被砍掉了很多(精度变低了),但大语言模型(LLM)其实对这种微小的精度丢失并不敏感。所以现在在大模型训练里,BF16 已经是绝对的标配了。
三、一个参数 × 字节数 = 权重显存

认识了精度,咱们终于可以拿出一个宇宙级的速算公式了。
算模型的权重显存,其实就是一个最简单的小学乘法:参数量 × 每个参数的字节数。比如你有一个 1B(10亿)参数的模型,如果你非要用 FP32(4字节)来存,那就是 10 9 × 4 10^9 \times 4 109×4 字节,算下来大概就是 4GB。如果你换成 FP16(2字节),就立刻砍半变成 2GB。
你看,精度降一半,显存需求就精准地减去一半。以后只要别人报给你模型规模和精度,你闭着眼睛也能算出它静态占多大地方。
四、7B 模型为什么是 14GB:FP16 下的显存账本

咱们拿大家最熟悉的 7B 模型(比如 Llama-2-7B)来实操一下。
70 亿个参数,现在业界默认的发布格式基本都是 FP16。套用刚才的公式: 70 亿 × 2 字节 = 140 亿字 70亿 \times 2字节 = 140亿字 70亿×2字节=140亿字,约等于 14GB。
你看右边那张 A100 的对比图,14GB 的权重放进 80GB 的显存条里,也就是占了底下的一小截。这就是为什么大家说推理一个 7B 模型,单张卡轻轻松松。当然,如果是真实跑推理,这 14GB 只是个“起步价”,你还得再留出几个 GB 来放之前讲过的 Activations 以及 KV Cache。
五、训练时为什么显存翻好几倍

刚才算的是推理,那如果是训练这个 7B 模型呢?咱们把上一章的“四笔账”重新翻出来,套上字节精度再算一遍。
在 Adam 优化器全精度的标准配置下,这笔账是极其恐怖的:
- Weights(权重):14GB。
- Activations(激活值):大概 20GB(随 batch 变)。
- Gradients(梯度):和权重一样,也是 14GB。
- Adam 状态(m和v):这是个大坑。为了保证更新够准,Adam 的这两个矩阵通常被强制要求用 FP32(4字节)来存。算一下: 70 亿 × 4 字节 × 2 个矩阵 = 56 G B 70亿 \times 4字节 \times 2个矩阵 = 56GB 70亿×4字节×2个矩阵=56GB
这四笔加起来直接超过了 100GB,单张 80GB 的 A100 当场宕机。所以你看,在 Infra 工程师眼里,参数量从来不等于显存量,它身后还拖着一个巨大的杠杆。
六、量化是什么:精度换显存和速度

既然显存这么贵,大家自然就打起了压缩的主意,这就引出了“量化(Quantization)”。
你看中间这台压缩机干的事儿:它把原本宽松地躺在 16 个格子里的浮点数,硬生生地按比例挤压到了只有 8 个格子的 INT8 里,甚至 4 个格子的 INT4 里。虽然小数部分被无情地抹掉了(带来了精度损失),但在推理的时候,显存瞬间少了一半甚至四分之三,计算速度也因为整数处理更简单而跟着起飞。
现在大家在消费级显卡(比如 24G 的 4090)上跑大模型,基本全靠 INT4 量化。这是在工程端用极其微小的智商代价,换取极其庞大的物理资源的经典操作。
七、混合精度训练:低精度算,高精度存

推理可以用量化,但训练的时候如果全降成低精度,模型可就彻底学废了。因为训练更新的步子(梯度)通常特别小,用低精度一存直接就变成 0 了。于是,大佬们发明了“混合精度训练”。
你看这套极其巧妙的流水线:中间做大头计算(Forward 和 Backward)的时候,为了追求极致的速度和算力利用率,咱们放心地用 BF16 去算;但到了最后一步,要把梯度加回到参数身上时,咱们再把它提拔回高精度的 FP32。
Forward 的时候你在做大量矩阵乘法,每个数从 FP32 变成 BF16,大概损失了小数点后第3、4位的精度。但你想,这些中间激活值本身就是要继续往下传的,后面还要过激活函数、做 normalization,这些操作本身就会重新缩放数值。你在第 5 位小数上差了一点,经过后面那么多层运算,对最终 loss 的影响几乎测不出来。
Backward 也类似,你算出来的梯度本来就是用一个 mini-batch 估算的,本身就带噪声。BF16 引入的那点精度误差,跟 mini-batch 采样带来的噪声比起来,小得多。就像你已经在用一把精度只到厘米的尺子在量东西了,你把尺子的精度从毫米降到厘米也没啥区别,反正你量的东西本身就是模糊的。Backward 这一步算出来的梯度值本身并不小,比如某个参数的梯度可能是 0.05、0.1 这种量级,BF16 完全能表示,没问题。
但到了参数更新这一步,学习率一乘,0.05 × 0.0001 = 0.000005,这个更新量就极其小了。你要把 0.000005 加到一个值为 1.0 的权重上,BF16 分辨不出 1.0 和 1.000005 的区别,更新就被吞掉了。
所以需要把 BF16 的梯度先 cast 回 FP32,然后在 FP32 精度下对 FP32 的 Master Weights 做更新,这样 1.0 + 0.000005 = 1.000005 就能被正确保存下来。更新完之后,再把新的 FP32 权重 cast 成 BF16,送去做下一轮 forward。
这就相当于,干苦力活的时候穿轻便的衣服(BF16),到了核心记账的时候掏出最高级的账本(FP32)。这样既享受了快,又保住了准,现在所有的大模型训练几乎都在跑这套流程。
八、看到模型规模,能立刻算出显存

最后看这张速算表。你以后去面试,如果面试官问“跑个 XX 模型要多少显存”,你脑子里的第一反应就是这张卡片:
首先,掏出底座公式:参数量 × 精度字节数,算出权重静态显存。 接着,问清楚对方是做推理还是训练。
- 如果是推理:在此基础上加点 KV Cache 预留(量级通常和权重差不多或者更少)。如果是量化,直接除以对应的倍数。
- 如果是训练:直接在权重的基础上乘以一个放大系数。如果用全精度 Adam,这个系数大概是 12 到 20 之间(视 batch 和激活用量而定)。
这个公式框架是焊死的,无论新模型怎么变,只要把具体的数字套进去,你就算得出一本明白账。
码字不易,欢迎大家点赞,关注,评论,谢谢!
更多推荐

所有评论(0)