04 · 模型显存与训练的四本账:别再只用“几 B 配几 G”选硬件

定位:原理研究与算式复核。 本文整理
llm-memory与llm-training教程。仓库没有因此完成一套模型训练或显存基准测试;文中数值是明确假设下的计算,不是设备实测。
“七十亿参数,半精度每个参数两字节,所以准备十四 GB”只算了一笔账:权重。模型真正运行时,还可能需要注意力缓存、中间激活、梯度、优化器状态和框架工作区。
显存问题最有效的解法不是背硬件口诀,而是把资源拆成可以分别解释、分别测量的账本。
一、第一本账:到底有多少权重需要常驻
最基本的估算是:
权重字节数 ≈ 常驻参数数量 × 每个参数的存储字节数
这里每一个词都重要。参数数量与架构有关;常驻参数不等于每个 token 激活的参数;量化后的平均位宽也不包含所有元数据、缩放因子和未量化层。
估算 Transformer 参数时,不能只看层数乘隐藏维度。词表嵌入、注意力投影、前馈层、归一化和输出头都有贡献。输入嵌入与输出头是否共享,可能带来很大的差别:以词表 128256、隐藏宽度 4096 为例,一张额外矩阵就约有 5.25 亿参数,不能当成舍入误差。
原始参数教程提供了拆解方法,但具体模型应以配置和实际张量为准。遇到 GQA、MoE、共享权重或不同前馈结构,更不能直接套用另一种架构的系数。
二、第二本账:KV Cache 随任务规模增长
对普通、完整缓存的解码器注意力,采用统一层配置时,可以估算:
KV 字节数 = 2 × layer_count × kv_head_count × head_dimension
× sequence_length × batch_size × bytes_per_element
系数 2 对应 Key 与 Value。GQA 改变的是 KV 头数,不能把查询头数直接当作 KV 头数。sequence_length 是缓存中需要保留的 token 数,服务中的并发请求会使总量进一步增加。
以下算例可直接复算,无需下载模型:
layer_count = 16
kv_head_count = 8
head_dimension = 64
sequence_length = 1024
batch_size = 1
bytes_per_element = 2
kv_bytes = (
2 * layer_count * kv_head_count * head_dimension
* sequence_length * batch_size * bytes_per_element
)
print(kv_bytes / 2**20)
结果为 32 MiB。原始推理显存表的一行按其给出的配置应得到这个结果,而不是其中写出的约 16 MB。本系列采用重新计算的结果,不继续传播原表数字。
这仍然是缓存张量估算。分页分配、共享前缀、滑动窗口、混合层结构、缓存量化和碎片都会改变实际占用。GB 是十进制,GiB 是二进制;比较硬件、框架报告和公式时也要统一单位。
三、第三本账:中间激活与临时工作区
激活不是另一个固定参数包。它与序列长度、批量、训练或推理模式,以及注意力实现相关。长输入的 prefill 和逐 token decode 的资源形态也不相同,不能用一次短回答的峰值推断所有请求。
训练时,为反向传播保存的激活往往占据显著空间。梯度检查点通过少存中间结果、在反向阶段重算来换取显存;它降低的是部分激活开销,不会让主权重或全部优化器状态凭空消失。
框架还可能预留内存池、通信缓冲区和算子工作区。所以“理论张量之和低于显卡容量”只是必要检查,不是不会 OOM 的保证。
四、第四本账:训练状态取决于实现布局
原始资料列举了一种混合精度 Adam 布局:
| 每个参数关联状态 | 字节数 |
|---|---|
| FP32 主权重 | 4 |
| BF16 计算副本 | 2 |
| BF16 梯度 | 2 |
| FP32 一阶矩 | 4 |
| FP32 二阶矩 | 4 |
| 合计 | 16 |
在这一特定布局下,70 亿参数的静态状态约为 112 GB,也就是 104.3 GiB,还没有加入激活和工作区。它不是所有框架都必须遵守的常数,但足以说明:直接宣称某张小于这一静态账本的卡可以全参训练,必须补充状态精度、分片、卸载或优化器实现等条件。
LoRA 主要减少需要训练的参数及其梯度和优化器状态;QLoRA 还涉及基础权重的量化。两者不等于免除基础模型的前向计算与激活开销。ZeRO 或其他分片策略改变状态在设备间的分布,也带来通信和调度成本。

图 04:推理缓存示例为 32 MiB。训练条带仅对应文中指定的混合精度 Adam 布局:每参数 16 字节,70 亿参数约 112 GB ≈ 104.3 GiB,未含激活与工作区。
五、训练阶段不同,资源与评估目标也不同
预训练、监督微调、偏好优化和带奖励的训练,不应该只按“训练一次模型”来估算。它们的数据结构、参考模型、奖励或验证过程可能不同。
后训练资料里有一处适合用来说明“公式也需要代码审查”的例子:DPO 应使用策略与参考策略的对数概率之差,不是将两个对数概率相除。用简化记号表示:
relative_logprob(answer) = log policy(answer | prompt)
- log reference(answer | prompt)
loss = -log sigmoid(
beta * (relative_logprob(preferred) - relative_logprob(rejected))
)
这只是单个偏好样本的标准形式说明,不是完整训练脚本。实际实现还需处理序列概率、掩码、批量聚合、数值稳定性及所用算法变体。
同理,教程中的 TRL 配置片段、GRPO 伪代码和未定义辅助函数,应当作为理解思路的材料,而不是直接承诺可执行的工程交付。
六、从估算走向真实验证
建议采用以下顺序,避免一次运行混入太多未知量:
- 固定模型配置、运行时版本、存储精度和缓存策略。
- 分别列出权重、缓存、激活和训练状态的估算,统一字节单位。
- 从小批量、短序列开始,分别记录加载、prefill、decode 或训练步的峰值。
- 一次只增加一个维度:输入长度、输出长度、并发或批量。
- 对比理论值与实测差额,解释内存池、工作区、分片和碎片,而不是用一个任意“安全系数”掩盖差异。
结语
显存估算的价值不是算出一个看似精确的数字,而是知道每一项为什么存在、随什么增长、由什么策略改变。只有账本清楚,硬件选型、量化、微调和服务容量才能进入可验证的工程讨论。

浙公网安备 33010602011771号