跳转至

不同精度下的显存换算

📊 FP32、TF32、FP16、BF16、INT8、INT4、NF4 各占多少字节?

不同的数值精度在内存中占据的字节数直接决定了模型权重和激活值的显存占用。以下是常见的几种精度格式及其字节数:

  • FP32 (单精度浮点):4 字节。这是深度学习训练的传统精度,提供极高的数值范围和精度,但显存和带宽开销巨大。

  • TF32 (TensorFloat-32):4 字节。NVIDIA Ampere 架构引入的特殊格式,在 Tensor Core 内部使用。它的存储格式与 FP32 完全相同(占 4 字节),但计算时采用与 FP16 相同的指数位和 BF16 相同的尾数位,是速度和精度的折中。

  • FP16 (半精度浮点):2 字节。广泛应用于混合精度训练和推理。它拥有较高的计算吞吐,但数值范围较小(最大 65504),容易出现溢出或下溢,需要配合损失缩放(Loss Scaling)使用。

  • BF16 (脑浮点16):2 字节。与 FP16 占用相同的 2 字节,但指数位更多(8 位,与 FP32 相同),动态范围极大,几乎不需要损失缩放,训练更稳定,已成为大模型训练的主流。

  • INT8 (8位整数):1 字节。广泛用于推理加速和部分训练组件(如 8-bit Adam 优化器)。占用显存是 FP16 的一半,但需要量化校准来保持精度。

  • INT4 (4位整数):0.5 字节。极致压缩的代表,用于权重量化推理(如 GPTQ、AWQ),可将模型体积压缩至原来的 1/4,但对量化算法要求很高。

  • NF4 (4位 NormalFloat):0.5 字节。QLoRA 引入的非均匀量化格式。基于正态分布的分位数设计,专门针对零中心正态分布的神经网络权重优化,信息保真度优于均匀 INT4。

⚡ 为什么混合精度训练能节省显存?省了哪些?

混合精度训练指的是在训练过程中同时使用低精度(如 FP16/BF16)和高精度(FP32)。它能大幅节省显存,主要省在了以下几个方面:

  1. 模型权重的低精度副本:在混合精度训练中,前向和反向传播使用的是低精度(FP16/BF16)的模型权重,这比 FP32 主权重副本节省了一半的存储空间。同时,FP32 的主权重通常存储在优化器状态中,不单独占用一份显存。

  2. 梯度的低精度存储:反向传播计算出的梯度同样以低精度(FP16/BF16)存储。相比于 FP32 梯度,这部分显存需求也减半。

  3. 激活值的低精度存储:前向传播时产生的中间激活值,如果以 FP16 存储而不是 FP32,其占用的显存也直接减半。这是显存节省的一个重要来源,尤其在 batch size 大或序列长时效果显著。

需要注意的是,优化器状态(如 Adam 的动量和方差)通常保留为 FP32 以保证更新精度,这部分并未被压缩。因此,混合精度训练主要节省的是模型权重、梯度和激活值的显存,而不是优化器状态。以 7B 模型为例,这部分可节省约 30-40 GB 的显存。

🤔 FP16 和 BF16 在显存占用上相同,为什么 BF16 训练更稳定?

FP16 和 BF16 都占用 2 字节,显存节省效果完全一致。但 BF16 训练更稳定,根本原因在于两者的内部结构和动态范围存在巨大差异:

image.png

结论:BF16 通过牺牲一部分尾数精度,换来了与 FP32 同等的动态范围,从根本上解决了梯度溢出问题,使得训练过程更加平滑、鲁棒,对超参数不敏感。这就是它更稳定的原因。

🔍 什么是“伪量化”?它和真量化在显存上有什么区别?

伪量化(Fake Quantization) 是指在训练过程中模拟量化误差,但并不真正将权重和激活值转换为低位整数存储的技术。它通常在量化感知训练(QAT, Quantization-Aware Training)中使用。

  • 工作机制:在前向传播时,插入伪量化节点。这些节点将浮点值按设定的量化参数(scale, zero-point)进行舍入和反量化,产生量化误差,并让模型在训练中学会适应这种误差。但在实际存储和反向传播时,权重和激活值仍然保留为全精度浮点数(FP32/FP16)。因此,伪量化不节省任何显存。

  • 真量化(True Quantization):指将模型权重和激活值真正转换为低位整数(如 INT8/INT4)进行存储和计算。例如,INT8 真量化的权重只占 1 字节,在推理或训练时直接使用这些整数进行计算,从而极大地节省显存和带宽。

总结:伪量化是“训练技巧”,目的是让模型提前适应量化噪声,为后续的真量化做准备;真量化是“部署技术”,目的是实际压缩模型体积和加速计算。伪量化节省了0%的显存,真量化可节省50%-75%的显存。

📉 模型量化到 INT4 后,权重显存变为原来的几分之一?

理论上是 1/4。因为每个 FP16 参数占 2 字节,而 INT4 参数占 0.5 字节。

但在实际工程中,由于需要存储量化常数(如 scale 和 zero-point),最终权重部分的总体积大约是原来的 25%–30%。例如,一个 7B 的 FP16 模型权重约 14GB,量化为 INT4 后,实际权重文件大小约为 3.5–4.5 GB。

🧬 QLoRA 中的 NF4 量化有什么特点?为什么用 NF4 而不是 INT4?

NF4(4-bit NormalFloat) 是 QLoRA 中专门为神经网络权重设计的一种非均匀量化数据类型。它的核心特点是:量化台阶是根据正态分布的累积分布函数来设定的,而非均匀分布。

为什么用 NF4 而不是 INT4?

  • 标准 INT4 是均匀量化:它将取值范围均等分割成 16 个区间。对于近似服从零均值正态分布的预训练模型权重来说,这种均匀分配非常浪费——绝大多数量化等级被浪费在了权重极少出现的尾部,而在权重集中的零附近,表示精度却严重不足。

  • NF4 是非均匀量化:它“因地制宜”地在权重密集的零附近分配了更多的量化台阶(更精细的刻度),而在稀疏的尾部分配了更少的台阶(更粗的刻度)。这种设计从信息论上最大化了量化后权重的信息保留,因此在 4-bit 极低位宽下,精度损失远小于均匀 INT4,从而为 QLoRA 的微调提供了一个高质量的基座。

总结:NF4 利用了权重分布的先验知识(正态分布假设),实现了信息论上最优的 4-bit 量化。而 INT4 忽略了这一先验,导致对权重核心区域的表示能力浪费。

💡 双重量化(Double Quantization)进一步节省了什么?

双重量化是对量化过程本身产生的“额外开销”进行二次压缩的技术。

当我们对模型权重进行分组量化(如 QLoRA 中每 64 个权重共享一个 32 位浮点缩放因子 scale)时,这些量化常数本身会占据可观的显存。对于一个 65B 模型,这部分开销可能高达数 GB。

双重量化就是对这些量化常数再进行一次更低精度的量化(例如,将它们从 FP32 压缩到 INT8)。这样一来,量化常数占用的显存就从数 GB 压缩到了数百 MB,几乎可以忽略不计。这一步进一步“榨干”了显存节约的空间,为模型参数、梯度和优化器状态腾出了宝贵空间。

📉 量化到 4-bit 后,推理精度通常如何?用哪些指标评估?

将模型权重量化到 4-bit(INT4或NF4)后,推理精度通常会轻微下降,但现代量化算法(如 GPTQ、AWQ、QLoRA 的 NF4)已能将这种损失控制在极小的范围内。对于大多数任务,困惑度(PPL)的上升幅度通常在 0.5-2.0 个点之间,下游任务的准确率下降也通常在 1% 以内。但在某些需要高精度推理的任务(如复杂数学、代码生成)上,4-bit量化的影响可能更明显。

评估 4-bit 量化模型的推理精度,通常需要从多个维度进行综合判断,单一指标往往具有欺骗性。

📊 核心评估指标:

  1. 困惑度(Perplexity, PPL):这是最基础、最常用的指标,衡量模型对语言建模的确定度。PPL越低越好。在通用文本数据集(如 WikiText-2、C4)上评估,观察量化后PPL的相对增加率。如果PPL从8.5上升到8.7,说明量化造成的语言建模能力损失极小。但如果从8.5上升到12.0,就说明量化出现了严重问题。PPL的优势在于计算简单、快速,适合作为第一道筛子。缺点是它并不直接反映模型在真实任务上的表现,有时PPL几乎没有变化,但模型在特定任务上的表现已经大打折扣。

  2. 下游任务性能基准:这是最接近实际应用的评估方式。根据模型的最终用途选择相应的基准测试:

  3. 知识理解:MMLU(大规模多任务语言理解)、ARC(AI2推理挑战)、HellaSwag(常识推理)。这些测试衡量模型的知识广度和推理能力。
  4. 代码生成:HumanEval、MBPP(大多数基础Python问题)。通过 pass@k 指标评估模型生成正确代码的能力。
  5. 数学推理:GSM8K(小学数学)、MATH(竞赛级数学)。量化对多步推理能力的影响在这些测试上往往更明显。
  6. 对话质量:AlpacaEval、MT-Bench。使用GPT-4等强模型作为裁判,比较量化模型与原始模型的回答质量。

  7. 长文本与多轮对话能力:量化可能微妙地"抹掉"模型对长上下文信息的记忆。需要在 LongBench、L-Eval 等长文本基准上测试,或者自定义多轮对话场景,检查模型是否出现上下文遗忘。

  8. 生成文本的多样性与连贯性:量化可能使模型的输出分布变得更"尖锐"(低熵),导致生成文本重复、模板化、丧失创造力。这需要通过人工评估或自动指标(如 distinct n-grams、Repetition Ratio)来衡量。

  9. 安全对齐与事实性:权重的微小变化可能影响模型的安全对齐效果,使其更容易被越狱或产生有害内容。需要用红队提示集(如 HarmBench)和事实性基准(如 TruthfulQA)进行专项测试。

🏗️ 综合评估流程:

  1. 先跑 PPL,如果上升超过 5%,直接排查量化配置。

  2. 如果 PPL 正常,跑核心下游基准(如 MMLU、HumanEval)。

  3. 对于生成任务,进行人工评估或 GPT-4 评判,重点检查流畅性、多样性和事实准确性。

  4. 如果业务场景涉及长文本或安全敏感,进行专项补充测试。


🧩 模型训练时,哪些部分适合保持 FP32,哪些可以降为 FP16?

在混合精度训练中,并非所有张量都适合降为 FP16。一个核心原则是:对数值范围敏感、需要高精度累积的操作保留 FP32;而对计算吞吐和存储空间需求大、数值范围相对稳定的操作降为 FP16/BF16。

🔴 适合保持 FP32 的部分:

  1. 优化器状态:Adam/AdamW 的一阶动量(m)和二阶动量(v)必须保持 FP32。这些状态需要累积大量微小梯度更新,如果使用 FP16,动量值很容易因精度不足而丢失,导致优化器无法有效工作。这也是为什么 8-bit Adam 仅对动量进行量化,而并非使用 FP16。

image.png

  1. 批次归一化(BatchNorm):BN 层计算均值和方差时需要高精度,FP16 下的舍入误差可能导致统计量不稳定,因此通常强制在 FP32 下执行 BN 的前向传播。

  2. 损失计算与 softmax:损失函数(如交叉熵)和 softmax 操作容易因为数值范围问题产生溢出。特别是 softmax 中的指数运算,可能因为一个大的输入值而溢出。因此通常保持 FP32。

🟢 适合降为 FP16/BF16 的部分:

  1. 前向和反向传播中的权重和激活值:这是混合精度训练的主要优化对象。矩阵乘法(GEMM)和卷积等计算密集型操作可以在 FP16 Tensor Core 上获得数倍加速。激活值的显存占用也减半。

  2. 梯度的存储:反向传播计算出的梯度以 FP16 存储,大幅减少显存。在更新前再转换为 FP32 应用到主权重上。

  3. 大部分逐元素操作:如 ReLU、Dropout、残差连接等,对精度相对不敏感,可用 FP16。

📊 训练精度分配表:

查看内嵌表格


💾 训练中的 master weights 为什么用 FP32 存储?

master weights(主权重副本)是混合精度训练中维护的一份 FP32 精度的模型参数。它的存在是为了解决 FP16 精度不足导致的参数更新丢失问题。

image.png

  1. 从 FP32 主副本出发,生成 FP16 权重副本。

  2. 用 FP16 权重进行前向和反向计算,得到 FP16 梯度。

  3. 将 FP16 梯度转换为 FP32,更新 FP32 主权重副本。

  4. 下一轮迭代,再从更新后的 FP32 主副本生成新的 FP16 权重。

与显存的关系:master weights 是混合精度训练中不可避免的显存开销。它使得模型权重的显存占用实际上并未减少——FP16 权重和 FP32 主副本同时存在。所以,混合精度训练主要节省的是激活值和梯度的显存,而不是模型权重的存储空间。


⚠️ 为什么 FP16 训练需要 Loss Scaling?与显存有关吗?

Loss Scaling(损失缩放)是 FP16 混合精度训练中特有的技术,其本质是为了解决 FP16 动态范围不足导致的梯度下溢问题。

image.png

动态 Loss Scaling 进一步自动化了这一过程:训练过程中持续监控梯度,如果没有出现 Inf/NaN,就逐步增大缩放因子,以更好地利用 FP16 的表示范围;如果检测到溢出,就跳过本次更新并缩小缩放因子。

与显存的关系:Loss Scaling 不直接节省显存。它是一个数值稳定性技术,目的是让 FP16 训练能够正常进行,而不是为了减少显存占用。显存的节省来自于使用 FP16 存储权重、激活值和梯度本身。Loss Scaling 是让这种节省得以实现的前提。

💡 BF16 的优势:BF16 拥有 8 位指数,动态范围与 FP32 相同,因此不需要 Loss Scaling。这也是为什么大模型训练越来越多地转向 BF16。


🧮 TF32 是 NVIDIA 的默认精度,它占用几个字节?有什么好处?

TF32(TensorFloat-32)是 NVIDIA 在 Ampere 架构(A100、RTX 30 系列)中引入的一种特殊数据格式,专门用于 Tensor Core 的矩阵乘法运算。

📏 存储大小:TF32 在显存中占用 4 字节(与 FP32 相同)。它不是一个独立的存储格式,而是一种计算格式。数据以 FP32 形式存储,但在进入 Tensor Core 进行计算时,被转换为 TF32 进行处理,计算完成后再将结果以 FP32 形式写回。

🔬 内部结构:TF32 的设计是 FP16 和 BF16 的巧妙结合:

  • 指数位:8 位(与 FP32 和 BF16 相同),保证了与 FP32 一样大的动态范围,无需 Loss Scaling。

  • 尾数位:10 位(与 FP16 相同),精度介于 FP32 和 FP16 之间。

🚀 好处:

  1. 零代码成本:在 NVIDIA Ampere GPU 上,PyTorch 的 torch.matmul 默认就使用 TF32,无需修改任何代码或训练配置。

  2. 速度显著提升:TF32 充分利用了 Tensor Core 的硬件加速,矩阵乘法速度可达 FP32 的 8-10 倍。

  3. 精度几乎无损失:由于保持了 FP32 的动态范围和适中的尾数精度,TF32 训练的模型精度与全 FP32 几乎一致,无需复杂的混合精度管理。

  4. 训练稳定:不需要 Loss Scaling,避免了 FP16 训练中的下溢和溢出问题。

💡 一句话总结:TF32 用 FP32 的存储成本,换取了接近 FP16 的计算速度,并且保持了 FP32 的训练稳定性,是 Ampere 架构上的"隐形福利"。


🔍 在 PyTorch 中,如何查看当前 tensor 的显存占用?

PyTorch 提供了多种查看张量显存占用的方法:

  1. 精确计算单个张量的字节数
import torch

# 创建一个 FP16 张量
x = torch.randn(1000, 1000, dtype=torch.float16, device='cuda')

# 方法1:通过 numel() 和 element_size() 计算
bytes_1 = x.numel() * x.element_size()
print(f"显存占用: {bytes_1} 字节 = {bytes_1/1024**2:.2f} MB")

# 方法2:使用 untyped_storage().nbytes()
bytes_2 = x.untyped_storage().nbytes()
print(f"显存占用: {bytes_2} 字节 = {bytes_2/1024**2:.2f} MB")
  • numel() 返回张量中元素的总数。

  • element_size() 返回每个元素的字节数(FP32=4,FP16/BF16=2,INT8=1)。

  • untyped_storage().nbytes() 直接返回底层存储分配的总字节数,对于视图张量(view)来说,它会返回原张量的存储大小,可能大于实际"可见"的元素总和。

  • 监控整个 GPU 的显存使用情况

# 当前已分配的显存
allocated = torch.cuda.memory_allocated()
print(f"PyTorch 已分配: {allocated / 1024**3:.2f} GB")

# GPU 缓存(包含已释放但未归还给驱动的内存)
cached = torch.cuda.memory_reserved()
print(f"PyTorch 缓存: {cached / 1024**3:.2f} GB")

# 查看峰值显存
max_allocated = torch.cuda.max_memory_allocated()
print(f"历史峰值: {max_allocated / 1024**3:.2f} GB")

# 重置峰值统计
torch.cuda.reset_peak_memory_stats()
  1. 使用 nvidia-smi 查看全局显存 在终端中运行 nvidia-smi,可以看到当前所有 GPU 进程的显存占用。PyTorch 的缓存机制可能导致 nvidia-smi 显示的占用比 memory_allocated() 高,这是正常现象。

📌 关键区别:

  • memory_allocated():PyTorch 实际正在使用的显存(张量数据)。

  • memory_reserved():PyTorch 从 CUDA 驱动申请到的显存总量,包括已释放但被分配器缓存的空闲块。

  • nvidia-smi:整个 GPU 上所有进程的显存使用总和。


🧮 如何估算一个 int8 量化模型加载后的显存占用?

估算 INT8 量化模型的显存占用,需要考虑三个部分:量化权重、量化常数和运行时开销。

🔢 计算步骤:

  1. 量化权重的显存:
  2. 模型参数量为 P。
  3. INT8 量化,每个参数占 1 字节。
  4. 权重显存 = P×1 字节。

  5. 量化常数(Scale 和 Zero-Point):

  6. 量化通常以组为单位进行(例如每 128 个参数一组),每组需要存储一个 FP32 的缩放因子(scale)和一个 FP32 的零点(zero-point)。
  7. 假设分组大小为 G,则每个参数分摊的常数量为 8/G 字节。
  8. 量化常数总显存 ≈ P×(8/G) 字节。
  9. 例如,G=128 时,常数开销仅为参数量的 8/128=6.25%,即 0.0625P 字节。

  10. 运行时开销:

  11. KV Cache:根据推理场景的 batch size 和序列长度计算,通常是一个动态值。
  12. 临时激活值:推理时的中间计算结果,对于短文本通常较小(<1 GB),对于长文本 Prefill 可能成为瓶颈。
  13. 框架开销:PyTorch 和推理框架本身占用的显存,通常在 0.5-2 GB。

📊 快速估算示例:以 7B 模型,INT8 权重量化为例。

image.png


⚔️ 量化训练(QAT)与训练后量化(PTQ)在显存需求上有什么不同?

量化感知训练(QAT, Quantization-Aware Training) 和 训练后量化(PTQ, Post-Training Quantization) 是实现模型量化的两种主要路径,它们在显存需求上存在本质差异。

📊 显存需求对比表:

查看内嵌表格

🔍 详细分析:

QAT 的显存需求:QAT 本质上是一次完整的训练过程。它在前向传播中插入伪量化节点模拟量化误差,并在反向传播中更新全精度权重。因此,它需要同时存储:

  • FP32 主权重副本。

  • FP16 梯度和激活值(用于反向传播)。

  • FP32 优化器状态(如 Adam 的动量和方差)。

  • 伪量化节点产生的额外中间张量。

以 7B 模型为例,仅优化器状态就需要 56 GB 显存,加上梯度和激活值,总显存需求轻松超过 100 GB,必须在多卡集群上完成。

PTQ 的显存需求:PTQ 不需要任何反向传播或参数更新。它只需要加载模型,运行少量校准数据统计激活值范围,然后直接对权重进行量化并保存。整个过程在单卡上即可完成,显存占用仅略高于模型推理时的需求。这也是 PTQ 成为工业界主流量化方案的根本原因——成本极低,部署极快。

💡 核心结论:QAT 的显存开销是 PTQ 的 10 倍以上。除非 PTQ 的精度损失不可接受,且有充足的算力资源,否则 PTQ 总是更优的选择。在大模型时代,PTQ(如 GPTQ、AWQ)几乎成为唯一实用的量化路径。