大模型量化到底在做什么?——从浮点数原理到 Qwen FP8 落地的完整拆解
如果你部署过大模型,第一个拦路虎通常不是”算不算得动”,而是放不放得下、搬不搬得快。一个 27B 参数的模型,权重以 BF16 存储,每个参数占 2 Byte,光是权重本体就要 27B × 2 ≈ 54 GB 显存——这还没算 KV Cache、激活值、运行时碎片。一张 80GB 的卡,一个模型就吃掉大半,batch 稍大一点、上下文稍长一点,直接 OOM。
量化的本质,就是在这个残酷算术面前,给大模型找一条活路。
一、量化的核心矛盾:用精度换生存空间
量化解决的是一个非常工程化的矛盾:
- 降低权重常驻显存:用更少的 bit 存参数;
- 减少显存带宽压力:搬运 8-bit 数据天生比搬运 16-bit 快;
- 在精度可接受的前提下,让同一张卡扛更大的 batch、更长的上下文、更大的模型。
它不是为了把模型文件”压缩小一点”给人看,而是大模型部署里绕不开的工程手段——把一部分数值精度让出来,换取真实的显存预算和推理吞吐。
二、一切的起点:浮点数在计算机里怎么存的
要理解量化,得先理解浮点数的三段结构。任何浮点数本质上就是二进制版的科学记数法,由三部分组成:
value = (-1)^sign × 2^(exponent − bias) × 1.fraction
- 符号位(sign):正负而已,1 bit 搞定。
- 指数位(exponent):决定动态范围——这个数能表示多大、多小的量级。
- 尾数位(fraction / mantissa):决定有效精度——小数点后面能保留几位有效数字。
指数之所以要加一个偏移量(bias,如 8 位指数的 bias=127),是因为真实指数可正可负,而硬件更喜欢把指数当无符号整数来存和比较。整体向右平移 bias,负指数、零、正指数就都能塞进同一个无符号字段里了。
尾数的巧妙之处在于hidden bit(隐藏位):规格化后的二进制浮点数总是 1.xxxxx × 2^n 的形式,那个前面的 1. 几乎是固定的,所以硬件干脆不存,只存 xxxxx 部分,读的时候再补回来——省了 1 bit,却是最值钱的 1 bit。
这一点至关重要: 当你砍指数位,主要损失的是动态范围(更容易溢出/下溢);当你砍尾数位,主要损失的是精度(数值变”糙”)。两类损失性质不同,这也是 BF16 和 FP16 虽然同为 16-bit,工程含义却天差地别的原因。
三、FP32 / FP16 / BF16 / FP8 到底差在哪
把它们摆在一起,差异一目了然:
| 格式 | 位宽 | 符号/指数/尾数 | 1B 参数占用 | 核心特点 |
|---|---|---|---|---|
| FP32 | 32 bit | 1 / 8 / 23 | ~4 GB | 范围大、精度高、最稳——训练基准 |
| FP16 | 16 bit | 1 / 5 / 10 | ~2 GB | 精度还行,但指数位少,容易溢出 |
| BF16 | 16 bit | 1 / 8 / 7 | ~2 GB | 指数位与 FP32 同款,范围稳,精度粗——训练默认 |
| FP8-E4M3 | 8 bit | 1 / 4 / 3 | ~1 GB | 精度略好、范围较小,适合前向权重/激活 |
| FP8-E5M2 | 8 bit | 1 / 5 / 2 | ~1 GB | 范围更大、精度更低,适合梯度等”范围优先”场景 |
一句话总结 BF16 为什么赢下训练时代:它用 16 bit 偷到了接近 FP32 的动态范围。 对大模型而言,数值”不崩掉、不下溢”往往比小数点后很多位更重要。
FP8 则是把压缩再推一层: 单个数压到 8 bit,权重存储和带宽压力直接再减半。代价也同样明确——指数和尾数都更少了,动态范围和精度更脆弱,所以 FP8 高度依赖缩放因子(scale)、量化粒度和混合精度设计,绝不可能粗暴地”把所有东西一刀切到 8 bit”。
四、量化到底在做什么:不是改 dtype,是重建数值表达
量化准确的描述不是”把 BF16 改成 INT8/FP8”,而是三步:
- 决定压谁——权重(最常规、可离线)、激活值(动态、需运行时 scale)、KV Cache(长上下文大户);
- 选格式 + 算 scale——把原始数值范围映射到低位格式能表达的空间;
- 推理时用低精度存储、高精度累加完成计算。
4.1 scale 与量化粒度是灵魂
对 INT 量化,核心公式是 x_q = round(x / s),还原时 x ≈ x_q × s,s 就是缩放因子。
粒度决定了误差的去向:
- per-tensor:整个张量一个 scale,最简单但误差最大(分布不均匀时吃亏);
- per-channel / per-group / per-block:分块各自算 scale,更精细,误差更小,但元数据更多、内核更复杂。
现代 FP8 实践普遍采用 block-wise 量化(如 128×128 分块),因为它介于”太粗”和”太碎”之间,既稳住误差,又能对齐高性能 kernel。
4.2 PTQ vs. QAT:你要的是部署还是极致
- PTQ(Post-Training Quantization):训练完再量化,不需要重训底座模型,先把高精度权重扫描一遍、算 scale、存出量化检查点——部署工程师的日常。
- QAT(Quantization-Aware Training):训练时就模拟量化误差,让模型提前适应低精度,效果更好更稳,但成本更高,更像训练工程。
大多数团队的正确顺序是:先跑高精度基线 → 试官方量化版/在线量化 → 只有在确有需要时,才做离线 PTQ 或 QAT。
五、Qwen3.5-27B-FP8:一个 FP8 落地范本
Qwen/Qwen3.5-27B-FP8 不是一个”全程 FP8 训练”的模型,而是训练完成后通过 PTQ 产出的推理检查点,官方写明采用 fine-grained FP8 quantization,block size = 128。
关键信息解读:
- 它不是粗粒度量化:整个权重矩阵不共用一个 scale,而是按 128×128 分 block,每个 block 配自己的 scale。以一个
[5120, 17408]的 Linear 层为例,block 数约为40 × 136,远比”一个 scale 管全场”精细。 - weight_scale_inv 等元数据就是反量化线索:权重本体以 FP8 存(省显存、省带宽),推理时 kernel 用每块配套的 scale 把数值映射回合适空间,累加仍在更高精度(FP16/BF16/FP32)里做——所以 FP8 推理的真实图景是”低精度存储搬运 + 高精度关键计算”。
- 混合精度保质量:像
embed_tokens、lm_head、LayerNorm 这类对输出分布敏感的层,参数占比未必大,但影响直接,通常保留 BF16;真正被压的是那些参数量大、矩阵乘法密集、对轻微误差相对不敏感的 Linear 层——FP8 的收益主要来自这里。
六、实操路线:你该从哪条路入手
| 路线 | 何时选 | 要点 |
|---|---|---|
| 直接用官方量化版 | 官方有 FP8/GPTQ/AWQ/GGUF 时 | 最省心,先用 vLLM 起基线:vllm serve Qwen/Qwen3.5-27B-FP8 |
| 在线量化 | 只有 BF16 模型,想快速试 | vllm serve ... --quantization fp8_per_block,部署时临时压,方便但非最优终态 |
| 离线 PTQ | 长期部署、要可控可复现 | 用 llmcompressor 等工具离线跑一遍,产出量化检查点再上线;关键选择是 targets / scheme / ignore / 校准数据 |
文章给出的 llmcompressor 最小脚本也讲清了核心决策点:量化作用到 "Linear"(大头在这),scheme 选 FP8_BLOCK,ignore 放过 lm_head 和 embed_tokens,再跑一次 sanity generation 验证没崩——整个流程几十行代码,但背后每一行都对应一个工程判断。
七、总结
量化不是玄学,也不是”把精度滑块往左拉一拉”——它是一次对数值表达方式的重新设计:选什么格式(FP8-E4M3 还是 E5M2?INT8 还是 INT4?)、scale 怎么算(per-tensor 还是 block-wise?)、哪些层保留高精度(混合精度策略)、推理 kernel 怎么对齐(低精度搬运 + 高精度累加)。
对绝大多数团队而言,最稳的策略始终是同一句话:先跑高精度基线建立参照,再用官方量化版或在线量化验证收益,最后只在必要时做离线 PTQ——不要迷信最低 bit,能在质量、显存、吞吐和硬件兼容之间拿到平衡,才是好的量化方案。