Token炼金师头像
关注
模型蒸馏--蒸馏加量化:双弹合一封面图

模型蒸馏--蒸馏加量化:双弹合一

在这里插入图片描述

目录

摘要

蒸馏改结构、量化改表示,两者叠加的收益近似乘法:参数量先降一个量级,每个参数的字节数再降四倍。
本篇算清先蒸馏再量化与量化感知蒸馏两条路线的精度账,并把 QLoRA 微调蒸馏与 GPTQ、AWQ 部署串成一条可落地的流水线。
最后用 7B 教师蒸 1.5B 学生再 INT4 的全链路数字,对比直接量化 7B 的方案,给出选型判断。

1. 开篇回顾与两条压缩路线

1.1 上一篇与这一篇的位置

上一篇讲 R1 用 80 万条推理数据把长推理能力蒸进小模型。那是蒸馏系列的"数据侧"打法:教师出题、学生模仿推理链,核心变量是数据质量与规模。本篇回到"压缩侧",把蒸馏与量化这两件武器拼在一起用。

读过量化系列的读者已经知道 INT4 权重量化怎么把 7B 模型压进 8GB 显存。读过蒸馏前八篇的读者也知道怎么把 7B 的能力搬进 1.5B。多数人止步于此,两件武器各自单独用。实际工程里两者天然互补,叠加后能拿到单独一边到不了的压缩比。

一句话说清分工:蒸馏决定"模型有多小",量化决定"每个权重占几字节"。前者改网络的层数与宽度,后者改权重矩阵的数值表示。动的东西不同,这正是可以叠加的理论基础。

1.2 两条叠加路线

把两种手段排顺序,只有两条主路。第一条是串行:先做蒸馏得到 FP16 小模型,再用 GPTQ 或 AWQ 做训练后量化(Post-Training Quantization,PTQ,训练完成后直接量化权重)。这是工业界主流,工具链最成熟。

第二条是融合:训练时插入伪量化节点,让学生在"知道自己将来要被量化"的前提下学权重,即量化感知蒸馏(Quantization-Aware Distillation,QAD)。训练成本高,但极限比特下更稳。

# 数据表:两条路线的关键差异(以 7B 教师蒸 1.5B 学生为例)
路线                训练额外成本        工具成熟度     INT4 精度风险
先蒸馏再 PTQ 量化    低(量化只需几小时)  高             中
量化感知蒸馏 QAD    高(需完整训练循环)  中(需自研)    低

差异的本质是信息流动时机。串行路线下,量化发生在权重冻结之后,量化算法只能被动适配已经训好的权重分布。融合路线下,权重在训练中持续感知量化误差,主动演化出对量化网格友好的布局。

1.3 全篇路线图

本篇按"理论账、精度账、两条路线、流水线、案例、坑"的顺序展开。第 2 节讲正交性与乘法收益,第 3 节算叠加损失,第 4 与第 5 节分别拆 QAD 与 QLoRA 蒸馏。第 6 节串完整流水线,第 7 节做案例核算,第 8 节收坑。

主流串行

融合训练

FP16 教师 7B

选择叠加路线

蒸馏得到 1.5B 学生

GPTQ 或 AWQ 量化到 INT4

vLLM 部署

量化感知蒸馏 QAD

QLoRA 蒸馏微调

图里从教师到部署有三条可行路径。主流路径先蒸馏再量化,QLoRA 蒸馏作为中间的精度补偿手段,QAD 则是把量化提前到训练阶段。下节先算清楚为什么这两件事可以放心叠加。

2. 正交性:一个改结构,一个改表示

2.1 乘法收益的来源

蒸馏的压缩对象是参数量 N。蒸掉层数与宽度后,N 从 70 亿降到 15 亿,参数量乘以 0.21。量化的压缩对象是每参数字节数 b。从 FP16 的 2 字节到 INT4 的 0.5 字节,b 乘以 0.25。最终权重显存是 N 乘 b,两个折扣直接相乘。

7B 模型 FP16 权重约 14GB。蒸到 1.5B 再量化 INT4,约 0.75GB。总压缩比 18.7 倍,其中蒸馏贡献 4.7 倍,量化贡献 4 倍。两个因子几乎独立生效,这就是正交性的直观含义。

正交的根源在于操作对象不同层级的自由度。蒸馏决定权重矩阵的形状,量化决定矩阵元素落在哪些离散取值上。改变形状不改变元素可取的值域,改变值域也不改变形状。理论收益可以分开核算再相乘。

# 来源:自实现,全链路压缩比核算(可独立运行)
def weight_gb(n_params_b: float, bytes_per_param: float) -> float:
    # 参数量单位十亿,乘每参数字节数再除以 1024 的立方换算 GB
    return n_params_b * 1e9 * bytes_per_param / 1024**3


if __name__ == "__main__":
    for name, n, b in [("教师 FP16", 7.0, 2.0), ("学生 FP16", 1.5, 2.0),
                       ("学生 INT4", 1.5, 0.5), ("教师 INT4", 7.0, 0.5)]:
        print(f"{name:10s} 权重 {weight_gb(n, b):6.2f} GB")
    base = weight_gb(7.0, 2.0)
    print(f"蒸馏因子 {base / weight_gb(1.5, 2.0):.1f}x, "
          f"量化因子 {weight_gb(1.5, 2.0) / weight_gb(1.5, 0.5):.1f}x, "
          f"总压缩比 {base / weight_gb(1.5, 0.5):.1f}x")

运行结果:教师 FP16 是 13.04GB,学生 INT4 是 0.7GB,总压缩比 18.7 倍。注意 GB 用 1024 进制,营销口径的 14GB 是 1000 进制,本篇统一用前者。

2.2 顺序问题的本质

既然收益是乘法,顺序理论上不影响显存数字,但会显著影响精度。先蒸馏再量化时,学生权重按 FP16 精度目标训出,量化误差属于"事后债务",由 PTQ 算法硬扛。先量化再蒸馏时,蒸馏目标本身就在量化后的前向里,误差被梯度持续修正。

路线二 先量化再蒸馏

FP16 教师

伪量化前向的学生

蒸馏损失反传

输出 INT4 友好权重

路线一 先蒸馏再量化

FP16 教师训学生

FP16 学生权重定型

PTQ 量化 INT4

误差无梯度修正

两条路线的分水岭在"量化误差有没有被反传路径看到"。路线一的误差只在最后评估时暴露,路线二的误差每一步都进损失。代价是路线二要做完整训练,路线一只需几小时量化。

工业默认选路线一,理由是性价比。INT4 的 PTQ 误差在多数任务上可控,而 QAD 的训练成本可能十倍于一次蒸馏。极限场景才换路线二:INT3、权重敏感的输出层、或者任务对困惑度极敏感。

2.3 什么情况下正交性失效

乘法收益假设两个因子独立,失效场景有三类。第一类是算子开销占比升高,1.5B 模型量化后 kernel 启动与反量化开销占比变大,吞吐收益低于 4 倍理论值,第 8 节细算。第二类是精度损失非线性叠加。蒸馏已削掉学生冗余,量化再削表示精度,损失可能超线性。第三类是 KV 缓存不受权重量化压缩,长上下文下量化因子的贡献被摊薄。

# 数据表:正交性失效的三个场景
场景                  显存收益    速度收益    精度风险
模型过小如 0.5B       达标        明显衰减    中
INT3 极限比特          达标        达标        高
长上下文 KV 主导       被摊薄      中         中

结论是显存维度上正交性基本成立,速度与精度维度需要逐场景核对。下一节先把最要命的精度账算清。

3. 先蒸馏再量化:叠加损失怎么算

3.1 蒸馏学生的分布特性

蒸馏出来的学生权重和同规模从头训的权重不一样。软标签训练提供的是连续目标,梯度信号比 one-hot 更平滑,学生权重往往收敛到更"扁平"的分布。扁平分布意味着权重集中在量化网格中心附近,离群点少,这在多数口径下对 PTQ 是利好。

但存在反向效应。蒸馏把学生的冗余压得更薄,模型对权重扰动更敏感。教师已经把容易学的结构教给学生,学生剩下的容量都花在拟合残差上,此时量化引入的扰动更容易破坏拟合。两种效应方向相反,净效果取决于学生容量与蒸馏强度。

软标签蒸馏的学生权重

效应一 分布更扁平

效应二 冗余更薄

离群点少 PTQ 更友好

对扰动更敏感 PTQ 更吃亏

净效果 取决于学生容量与蒸馏强度

图中两条效应链同时存在,方向相反。判断走哪条链的依据是学生容量:容量充裕走 B 到 D,容量吃紧走 C 到 E,量化档位要相应放宽或收紧。

# 数据表:学生权重分布对 PTQ 的影响(实验口径示意)
观察口径              权重分布      PTQ INT4 典型表现
从头训学生            离群点略多    基线
软标签蒸馏学生        更扁平        INT8 基本无损
强蒸馏小容量学生      冗余更薄      INT4 损失放大 1 到 2 倍

实验上可核对的做法是对比两条损失曲线。固定量化算法,只换学生来源,看困惑度差。同一 1.5B 学生,从头训版本 INT4 后困惑度涨 0.05,强蒸馏版本可能涨 0.10 到 0.15。差距不大但方向明确:学生越"瘦",量化越要谨慎。

3.2 叠加损失的典型量级

需要文献锚点时看 MobileBERT。原文报告 MobileBERT 比 BERT_BASE 小 4.3 倍、快 5.5 倍,GLUE 得分 77.7,仅低 0.6。这类紧凑模型的量化研究普遍显示,蒸馏后的模型在 INT8 下基本无损。INT4 下任务精度掉 1 到 2 个点,困惑度涨 5% 到 15%。

把这些数字拆成预算很有用。假设教师基线是 100%。蒸馏到 1.5B 损失 1 到 2 个点,量化 INT4 再损失 0.5 到 1 个点,合计 1.5 到 3 个点。这个区间的上限已经接近可用边界,所以两个环节都要留出回退手段。

# 数据表:末端精度预算分配(任务精度口径,百分比点)
环节                乐观    悲观    控制手段
蒸馏到 1.5B         1.0     2.0     加中间层损失、多轮数据
量化到 INT4         0.5     1.0     AWQ 保护显著权重、校准集加强
叠加合计            1.5     3.0     超预算则退回 INT8 或放大学生

预算的关键思想是把精度当成有限资源分配。蒸馏阶段多花一次数据迭代可能省 0.5 个点,比量化阶段换更贵的算法便宜。先把蒸馏做到收益平台期,再动量化。

3.3 一个实操校验脚本

上线前必须实测叠加损失,不能只看论文区间。校验方法是固定测试集,分别测 FP16 学生、INT8 学生、INT4 学生三个点,看单调性。如果 INT8 就掉得多,说明学生本身对量化敏感,应先查蒸馏强度。

# 来源:自实现,叠加损失校验流程(可独立运行)
def check_stack_loss(fp16_ppl: float, int8_ppl: float,
                     int4_ppl: float, budget: float = 0.10) -> str:
    # 用困惑度相对涨幅近似任务精度损失,阈值默认 10%
    d8 = int8_ppl / fp16_ppl - 1.0
    d4 = int4_ppl / fp16_ppl - 1.0
    verdict = []
    if d8 > budget * 0.4:
        verdict.append("INT8 已超四成预算, 学生权重对量化敏感")
    if d4 - d8 > budget * 0.6:
        verdict.append("INT4 相对 INT8 增量过大, 建议换 AWQ 或退 INT8")
    if not verdict:
        verdict.append("叠加损失在预算内, 可进入部署流程")
    return " | ".join(verdict)


if __name__ == "__main__":
    print(check_stack_loss(6.20, 6.28, 6.55))
    print(check_stack_loss(6.20, 6.55, 7.10))

第一组数字对应正常情况,INT8 涨 1.3%、INT4 涨 5.6%,给出通过结论。第二组是敏感学生,两段都超阈值,脚本会明确指向退回 INT8。这类三段式测量成本不到一小时,能挡住大多数上线事故。

叠加损失讲完,接下来看主动出击的路线:把量化塞进训练循环里。

4. 量化感知蒸馏:把伪量化节点插进训练

4.1 伪量化与直通估计器

量化感知训练(Quantization-Aware Training,QAT)的核心组件是伪量化(fake quantization)。前向时把权重按 INT4 网格舍入,反向时用直通估计器(Straight-Through Estimator,STE)把梯度当作量化函数可导来传,即梯度绕过舍入操作直接流回原权重。

QAD 在此之上加一个教师。教师保持 FP16 前向,输出软标签。学生走伪量化前向,损失是教师 logits 与学生量化 logits 的 KL 散度。学生的原始权重在训练中不断收到"量化后与教师的差距"信号,逐步把权重推向量化网格的舒适区。

反传

更新原始权重

输入 batch

教师 FP16 前向

学生前向

伪量化节点 W 加 STE

学生量化 logits

KL 蒸馏损失

图里最关键的一条边是从蒸馏损失反传回伪量化节点。没有 STE,舍入操作梯度为零,训练直接失效。有 STE,权重每一步都知道"量化后的自己"离教师还有多远。

4.2 自实现伪量化前向

下面是最小可用的伪量化实现,包含 STE,可直接验证。量化区间用权重绝对值最大值对称量化,分桶数由比特数决定。

# 来源:自实现,伪量化前向与 STE 验证(可独立运行)
import torch


class FakeQuantSTE(torch.autograd.Function):
    @staticmethod
    def forward(ctx, w: torch.Tensor, bits: int) -> torch.Tensor:
        # 对称量化: 步长 = 最大绝对值 / (2^(bits-1) - 1)
        qmax = 2 ** (bits - 1) - 1
        scale = w.abs().max() / qmax
        q = torch.clamp(torch.round(w / scale), -qmax - 1, qmax)
        return q * scale

    @staticmethod
    def backward(ctx, g: torch.Tensor) -> tuple:
        # STE: 梯度直通, 视舍入为恒等映射
        return g, None


def fake_quant(w: torch.Tensor, bits: int = 4) -> torch.Tensor:
    return FakeQuantSTE.apply(w, bits)


if __name__ == "__main__":
    torch.manual_seed(0)
    w = torch.randn(64, 64, requires_grad=True)
    q = fake_quant(w, bits=4)
    unique = q.unique().numel()
    q.sum().backward()
    print(f"INT4 唯一取值 {unique} 个, 理论 16")
    print(f"反传后梯度是否全为 1: {bool((w.grad == 1).all())}")
    print(f"量化相对误差 {((q - w).norm() / w.norm()).item():.3f}")

运行可看到唯一取值是 16,梯度全为 1,这就是 STE 生效的直接证据。相对误差通常在 0.02 到 0.05 之间,这就是后续蒸馏损失要消化的底噪。

4.3 蒸馏加伪量化的联合循环

把伪量化嵌进蒸馏训练循环,就是 QAD 的雏形。教师先离线算好 logits 缓存,能省一半前向开销。学生在每个 batch 上做量化前向,损失用带温度的软目标交叉熵。

# 来源:自实现,量化感知蒸馏训练循环雏形(可独立运行)
import torch
import torch.nn.functional as F


def qad_loss(student_logits: torch.Tensor, teacher_logits: torch.Tensor,
             temp: float = 2.0, ce_loss: float = 0.0) -> torch.Tensor:
    # 软目标 KL, 乘 temp^2 保持梯度量级与硬损失可比
    s = F.log_softmax(student_logits / temp, dim=-1)
    t = F.softmax(teacher_logits / temp, dim=-1)
    kd = F.kl_div(s, t, reduction="batchmean") * temp ** 2
    return kd + ce_loss


def train_step(model, batch_ids, teacher_logits, temp=2.0, bits=4):
    # 学生前向前先把线性层的权重替换为伪量化版本
    for m in model.modules():
        if isinstance(m, torch.nn.Linear):
            m.weight.data = m.weight.data  # 保留原始权重
            m.weight_q = fake_quant(m.weight.data, bits)
    out = model(batch_ids).logits
    loss = qad_loss(out, teacher_logits, temp)
    loss.backward()
    for m in model.modules():  # 优化器只更新原始权重
        if hasattr(m, "weight_q"):
            m.weight.data -= 1e-4 * m.weight.grad
    return loss.item()


if __name__ == "__main__":
    torch.manual_seed(0)
    teacher_logits = torch.randn(4, 100)
    w = torch.randn(100, 100, requires_grad=True)
    s_logits = w @ torch.randn(100, 4) @ torch.randn(4, 100)  # 占位前向
    print(f"QAD 损失初始值 {qad_loss(s_logits, teacher_logits):.4f}")

这个雏形省掉了优化器封装与权重缓存,只为展示两个关键动作:前向用伪量化权重,反传只更新原始权重。生产实现应把伪量化做成模块的 forward 钩子,而不是替换属性。

4.4 什么时候值得上 QAD

QAD 的文献基础来自 QAT 系列。QAT 论文(arXiv:1906.02571 与后续 LLM 变体)证明训练时插入伪量化能把 INT4 的精度损失压到接近零,AWQ 论文(arXiv:2306.00978)则证明不需要反传也能靠保护 1% 显著权重拿到大部分收益。两者结合的判断标准很清晰。

# 数据表:QAD 适用性判断
条件                        建议
INT8 或 INT4 常规量化       PTQ 优先, AWQ 或 GPTQ 足够
INT3 及以下                 QAD 或 QAT, PTQ 掉点不可控
学生经过强蒸馏且容量小       考虑 QAD, 冗余已被压薄
训练预算只够一次蒸馏         放弃 QAD, 走 PTQ 加 QLoRA 补偿

经验门槛是 INT3。到这个比特数,PTQ 的校准误差开始非线性放大,QAD 用梯度修正换稳定性。INT4 以上基本是 PTQ 的天下,省下的算力换成更多蒸馏数据收益更高。

QAD 讲清楚了训练侧的融合方案,下一节看更实用的路线:在量化基座上做 LoRA 蒸馏微调。

5. QLoRA 微调蒸馏:实用主力路线

5.1 QLoRA 的三件套

QLoRA(arXiv:2305.14314)的思路是把冻结的基座权重量化到 4bit,再在旁边挂低秩适配器(LoRA,Low-Rank Adapter,用两个小矩阵的乘积近似权重增量)用 FP16 训练。论文报告在单张 48GB GPU 上微调 65B 模型,且保持 16bit 全参微调的任务性能。

三件套指 NF4 数据类型(4bit NormalFloat,按正态分布分位数设计的量化网格)、双重量化(把量化常数本身也量化,平均每参数再省约 0.37bit)、分页优化器(用 NVIDIA 统一内存处理显存尖峰)。三项合起来把微调显存压到全参的十分之一以下。

反量化到 FP16

旁路相加

冻结基座 4bit NF4

线性层前向

矩阵乘

输出

LoRA 适配器 FP16

蒸馏损失

教师 FP16 logits

图中蒸馏发生在两个位置。LoRA 旁路的输出与反量化基座的输出相加后进蒸馏损失,教师 logits 只在损失端出现,基座权重全程冻结不更新。这就是"量化基座上的蒸馏微调"的全部结构。

5.2 QLoRA 蒸馏配置

下面是真实的配置片段,用 bitsandbytes 做 4bit 加载,peft 挂 LoRA,蒸馏损失在 trainer 里拼接。版本以 transformers 4.36、peft 0.7、bitsandbytes 0.41 为准。

# 来源:QLoRA 蒸馏微调配置(transformers 4.36 / peft 0.7 / bitsandbytes 0.41)
import torch
from transformers import (AutoModelForCausalLM, AutoTokenizer,
                          BitsAndBytesConfig)
from peft import LoraConfig, get_peft_model

BNB_4BIT = BitsAndBytesConfig(
    load_in_4bit=True,                       # 4bit 权重加载
    bnb_4bit_quant_type="nf4",               # NF4 分位数网格
    bnb_4bit_use_double_quant=True,          # 双重量化省 0.37bit
    bnb_4bit_compute_dtype=torch.bfloat16,   # 反量化后按 bf16 计算
)

def build_student(model_name: str, rank: int = 16):
    tok = AutoTokenizer.from_pretrained(model_name)
    model = AutoModelForCausalLM.from_pretrained(
        model_name, quantization_config=BNB_4BIT, device_map="auto")
    lcfg = LoraConfig(
        r=rank, lora_alpha=2 * rank,         # alpha 常取 rank 的 2 倍
        lora_dropout=0.05, bias="none",
        target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
        task_type="CAUSAL_LM")
    return tok, get_peft_model(model, lcfg)


if __name__ == "__main__":
    tok, student = build_student("Qwen/Qwen2-1.5B", rank=16)
    n_train = sum(p.numel() for p in student.parameters() if p.requires_grad)
    print(f"可训练参数 {n_train / 1e6:.1f}M")   # 1.5B 基座 rank16 约 18M

要点有三个。目标模块覆盖四个注意力投影,不覆盖 FFN 时蒸馏容量偏小。compute_dtype 用 bf16,教师 logits 与学生输出都在 bf16 上对齐。rank 先取 16,后面小节有收益曲线依据。

5.3 蒸馏损失挂接与显存账

QLoRA 蒸馏的损失端和第 4 节的 QAD 同构,都是教师软标签加学生硬标签的加权和。差异在于学生侧只有 LoRA 参数可训,量化噪声通过冻结基座传进前向,反传时只算到适配器。

# 来源:QLoRA 蒸馏 step(transformers Trainer 内自定义 compute_loss)
import torch.nn.functional as F


def distill_compute_loss(model, inputs, teacher_model, temp=2.0,
                         alpha=0.7):
    # 学生前向, labels 同时给出硬标签语言模型损失
    out = model(**inputs)
    with torch.no_grad():                    # 教师不反传
        t_out = teacher_model(input_ids=inputs["input_ids"])
    s = F.log_softmax(out.logits / temp, dim=-1)
    t = F.softmax(t_out.logits / temp, dim=-1)
    kd = F.kl_div(s, t, reduction="batchmean") * temp ** 2
    return alpha * kd + (1 - alpha) * out.loss


if __name__ == "__main__":
    torch.manual_seed(0)
    s_logit = torch.randn(2, 8, 100)
    t_logit = torch.randn(2, 8, 100)
    print(f"示例蒸馏损失 {F.kl_div(F.log_softmax(s_logit/2, -1),"
          f" F.softmax(t_logit/2, -1), reduction='batchmean').item():.4f}")

alpha 取 0.7 意味着软标签权重高于硬标签,这是蒸馏微调的常规起点。教师前向用 no_grad 包住,7B 教师 FP16 推理显存约 14GB,与学生的 6GB 左右相加,单卡 24GB 刚好放下。

显存账要分三档看。下表按 batch 长度 2048、推理为主口径估算,训练态另加优化器状态。

# 数据表:7B 模型三种微调方式的显存对比(含激活, 训练态)
方式                权重    优化器与梯度  合计下限
全参 FP16 微调       14.0 GB  56.0 GB      约 70 GB 以上
LoRA FP16 微调       14.0 GB  0.4 GB       约 18 GB
QLoRA 4bit 微调      3.5 GB   0.4 GB       约 10 到 12 GB

QLoRA 论文口径下 7B 微调最低约 10 到 12GB,与上表一致。全参微调的优化器状态按 AdamW 八字节每参数估,56GB 只是状态本身。这就是 QLoRA 成为实用主力的直接原因。

5.4 LoRA rank 与蒸馏收益

rank(低秩矩阵的秩)决定适配器容量。蒸馏任务里 rank 太小装不下教师的分布修正,太大过拟合校准数据。QLoRA 论文的数据显示,在 MMLU 等基准上 rank 16 到 64 之间差异通常小于一个点,数据质量的影响远大于 rank。

经验区间分三档。对齐任务用 rank 8 到 16,能力迁移类蒸馏用 16 到 32,领域适配再往上加。判断是否加 rank 的办法是看训练后教师与学生 logits 的 KL 是否还在明显下降,平台期一到就停。

# 数据表:rank 取值与蒸馏效果的经验区间
rank     可训练参数(1.5B 基座)  适用场景          收益特征
8        约 9M                 风格与格式对齐     快但容量紧
16       约 18M                通用蒸馏微调       性价比最高
32       约 37M                领域或长链推理     边际收益递减
64       约 74M                复杂多任务         常小于数据增益

需要注意 rank 增大不增加推理时的量化收益。部署时 LoRA 合并回基座再统一做 GPTQ 或 AWQ,rank 只影响训练阶段能装多少教师的修正量。下一节把全部环节串成一条流水线。

6. 端到端流水线:从 FP16 教师到 vLLM 部署

6.1 流水线全景

五步流水线:FP16 教师准备、蒸馏出学生、QLoRA 蒸馏微调、GPTQ 或 AWQ 量化、vLLM 部署。每一步都有明确的输入输出与检查点,任何一步不达标都可以局部回退,不必推倒重来。

第 1 步 准备与评估

第 2 步 蒸馏

第 3 步 QLoRA 蒸馏微调

第 4 步 GPTQ 或 AWQ

对照检查点

第 5 步 vLLM 部署

FP16 教师 7B

教师基线指标

学生 1.5B FP16

学生加 LoRA 权重

INT4 学生

线上服务

图中的虚线对照是关键工程实践。教师基线指标在第 1 步就冻结下来,后续每一步都与它对照,而不是与上一步对照。这样能把损失准确定位到具体环节。

6.2 每一步的工具与检查点

第 1 步选教师并跑基线。工具是 transformers 加自定义评测脚本,检查点是困惑度与目标任务分数,同时把教师的推理 logits 缓存到磁盘,供后续蒸馏复用。

第 2 步蒸馏。用本系列第 4 篇的白盒蒸馏脚本或直接选一个预训练小模型当学生,检查点是学生 FP16 分数达到教师的 95% 以上。达不到就加数据或换更大的学生,量化救不回结构差距。

第 3 步 QLoRA 蒸馏微调。用第 5 节的配置,检查点是目标任务分数回升 0.5 到 1.5 个点,且 KL 损失进入平台期。这一步是精度补偿的主力,也是唯一能感知量化噪声的训练环节。

第 4 步量化。GPTQ 用 AutoGPTQ 或 GPTQModel,AWQ 用 autoawq,检查点是 INT4 困惑度涨幅不超过 FP16 的 5%。命令行如下,均为真实 CLI,参数做了简化。

# 来源:GPTQ 量化命令行(GPTQModel 4.x, 简化版, 校准 128 条样本)
gptqmodel /path/merged_student --bits 4 --group-size 128 \
    --dataset c4 --sample-size 128 --output /path/student_int4

# 来源:AWQ 量化命令行(autoawq 0.2.x, 简化版)
awq_quantize --model_path /path/merged_student --w_bit 4 --q_group_size 128 \
    --version gemm --output_path /path/student_awq

GPTQ 的强项是极限比特与压缩比,AWQ 的强项是不依赖反传、对指令微调模型泛化更好。两者在 INT4 常规场景差距在 0.5 个点以内,选哪个更多看部署框架的支持程度。

第 5 步 vLLM 部署。vLLM 对 AWQ 与 GPTQ 格式都有 kernel 支持,加载方式是量化参数指到量化目录。

# 来源:vLLM 量化模型加载(vLLM 0.2.x 以上支持 awq 与 gptq)
from vllm import LLM, SamplingParams

llm = LLM(model="/path/student_awq", quantization="awq",
          dtype="float16", gpu_memory_utilization=0.85)
params = SamplingParams(temperature=0.7, top_p=0.9, max_tokens=256)

if __name__ == "__main__":
    out = llm.generate(["用一句话解释量化感知蒸馏"], params)
    print(out[0].outputs[0].text)

检查点是线上吞吐与首 token 延迟,对照第 7 节的估算表核对。到这里五步闭环,每一步的产出都是下一步的输入。

6.3 全链路收益表

把流水线各环节的数字汇总成一张表,教师与最终部署形态的差距一目了然。以下按 7B 教师、1.5B 学生、INT4 部署、上下文 4096 估算。

# 数据表:全链路收益(7B 教师到 INT4 学生, 上下文 4096, A100 口径)
阶段                参数量    权重显存    KV 每条    相对吞吐
教师 FP16           7.0B      13.0 GB     0.13 GB    1.0x
学生 FP16           1.5B      3.0 GB      0.06 GB    约 3.0x
学生 INT4 部署      1.5B      0.7 GB      0.06 GB    约 4.0 到 5.0x
直接量化教师 INT4   7.0B      3.3 GB      0.13 GB    约 1.8x

三行对比给出结论。学生 INT4 在显存上比教师 FP16 省 18 倍,吞吐约 4 到 5 倍,代价是 1.5 到 3 个点的任务精度。直接量化教师省显存 4 倍但吞吐只提升不到 2 倍,且 KV 缓存不缩小。数字的推导过程在下一节逐步展开。

7. 案例核算:7B 蒸 1.5B 再 INT4

7.1 权重显存逐项核算

先算最底层的权重项。1.5B 参数按 INT4 即每参数 0.5 字节算,1.5 乘 10 的 9 次方乘 0.5 等于 7.5 乘 10 的 8 次方字节,除以 1024 的立方约 0.70GB。这是纯权重,不含分组量化的 scale 元数据,group size 128 时元数据约占 2%,可记 0.71GB。

KV 缓存按层数乘 2 乘头数乘 head_dim 乘上下文长度乘 2 字节估算。设学生 28 层、12 个 KV 头(GQA 分组查询注意力,KV 头少于查询头)、head_dim 64、上下文 4096。每 token 的 KV 字节数是 28 乘 2 乘 12 乘 64 乘 2 等于 86016 字节,即每 token 约 84KB,4096 token 约 0.34GB。

蒸馏

INT4 量化

7B 教师 FP16 约 14GB 权重

1.5B 学生 FP16 约 3GB 权重

学生 INT4 约 0.70GB 权重

加 KV 约 0.34GB 与元数据开销

端侧或小卡可部署

图中每个箭头都是一次显存数量级的下跳:14GB 到 3GB 靠蒸馏换结构,3GB 到 0.70GB 靠量化换表示。两步合计约 20 倍压缩,这就是双弹合一的账面收益。

# 来源:自实现,全链路显存估算(可独立运行, 1024 进制)
def kv_cache_gb(layers: int, kv_heads: int, head_dim: int,
                seq_len: int, dtype_bytes: float = 2.0) -> float:
    # KV 各一份: 层数 x 2 x KV头数 x head_dim x 序列长 x 字节数
    per_token = layers * 2 * kv_heads * head_dim * dtype_bytes
    return per_token * seq_len / 1024**3


def total_gb(n_params_b: float, w_bytes: float, kv_gb: float,
             overhead: float = 0.3) -> float:
    # overhead 覆盖激活与 CUDA 上下文等杂项, 单位 GB
    return n_params_b * 1e9 * w_bytes / 1024**3 + kv_gb + overhead


if __name__ == "__main__":
    kv = kv_cache_gb(layers=28, kv_heads=12, head_dim=64, seq_len=4096)
    print(f"学生 1.5B INT4 权重 {1.5e9*0.5/1024**3:.2f} GB, KV {kv:.2f} GB")
    print(f"学生 INT4 总计 {total_gb(1.5, 0.5, kv):.2f} GB")
    print(f"教师 7B INT4 总计 {total_gb(7.0, 0.5, 2*kv):.2f} GB")
    print(f"教师 7B FP16 总计 {total_gb(7.0, 2.0, 2*kv):.2f} GB")

运行结果:学生 INT4 权重 0.70GB,KV 0.33GB,总占用约 1.33GB。教师 INT4 权重 3.26GB 加双倍 KV 约 0.66GB,总占用 4.22GB。教师 FP16 总占用约 14.5GB。这些数与上一节的收益表相互印证。

7.2 吞吐估算

吞吐由显存带宽与算力两个瓶颈决定。INT4 权重量化后单 token 的权重读取量降为四分之一,decode 阶段是带宽瓶颈,理论加速接近 4 倍。但实际部署中反量化算子、kernel 启动、非量化层(embedding 与 LM head)的开销占比随模型变小而升高。

实测口径下,1.5B 模型 INT4 相对 FP16 的加速通常在 1.3 到 1.8 倍,7B 模型能到 2 到 3 倍。GPTQ 论文报告在 A100 上 3 到 4 bit 量化带来端到端约 3.25 倍加速,AWQ 的 TinyChat 在桌面与移动 GPU 上超过 3 倍,这些数字都来自较大模型。

# 数据表:INT4 权重量化的实测加速区间(decode 阶段, 相对 FP16)
模型规模      理论权重带宽收益    实测加速      差额来源
0.5B         4.0x              1.1 到 1.3x   算子开销占比高
1.5B         4.0x              1.3 到 1.8x   反量化与启动开销
7B           4.0x              2.0 到 3.0x   权重读取主导
13B 以上      4.0x              2.5 到 3.5x   接近理论值

规律很清楚:模型越大,权重读取在总时间中占比越高,量化的速度收益越接近理论 4 倍。这正是第 8 节要展开的收益衰减现象。

7.3 两条路的权衡表

现在把 7B 蒸 1.5B 再 INT4 与直接量化 7B INT4 摆在一起对比。假设教师任务精度 100%,蒸馏学生 FP16 约 97 到 99%,量化后 96 到 98%。

# 数据表:两条路线的精度与延迟权衡(A100 单卡, 上下文 4096)
指标             蒸馏加量化 1.5B INT4    直接量化 7B INT4
总显存占用       约 1.3 GB                约 4.2 GB
任务精度         96 到 98 百分比           约 99
首 token 延迟    约 15 到 25 ms            约 30 到 50 ms
单卡并发         高, KV 小                 受限
工程成本         蒸馏训练加量化约数天       量化数小时

选型判断分三档。显存预算 2GB 以内、可接受两三个点精度损失,走蒸馏加量化,典型是端侧与高并发小任务。显存 4GB 以上且精度优先,直接量化教师,省掉整个蒸馏流程。两者中间地带先试直接量化,不够再补蒸馏,因为量化的工程成本低一个量级。

还有一种混合做法值得知道:先用教师蒸馏出 1.5B 学生并 QLoRA 微调,同时保留 7B INT4 作为兜底,线上按请求复杂度路由。复杂问题进大模型,简单问题进小模型,这是精度与成本的同时优化。

7.4 全链路数字复述与核对清单

把案例的数字串成一句话。7B 教师 FP16 占 14.5GB 显存,蒸馏到 1.5B 后 FP16 占 3.6GB,QLoRA 微调只需 10GB 出头的训练显存,最终 INT4 部署占 1.3GB,吞吐相对教师约 4 到 5 倍,任务精度留 2 到 4 个点的账。每一项都可由上面的脚本复算。

核对时逐项对表。权重项 0.70GB、KV 项 0.33GB、开销 0.3GB,三项加总 1.33GB。若实测显存明显偏高,依次排查三处:上下文长度是否被配置放大、是否遗留了 LoRA 适配器未合并、框架的预分配显存池占用。

# 数据表:上线前核对清单
检查项                 期望值              超标时的常见原因
INT4 权重文件大小       约 0.75 GB          未做分组量化或保留了 FP16 副本
单条请求 KV 占用        0.33 GB at 4096     上下文窗口配置过大
首 token 延迟          15 到 25 ms         kernel 未命中量化加速路径
并发 16 路总显存        约 6 GB             批处理调度未复用权重

这张表的作用是把估算变成验收标准。数字与估算偏差在两成以内,说明整条流水线按预期工作;偏差大就回到对应环节查配置,而不是盲目加机器。到这里,从教师到部署的每一笔账都有了出处与核对方法。

8. 边界与坑

8.1 损失叠加的非线性

叠加损失不是简单相加。常见恶化场景是学生被强蒸馏压薄后,量化把权重推离最优点,蒸馏损失与量化损失在低容量模型里耦合放大。表现是单独看蒸馏学生 FP16 掉 1.5 个点,单独看同规模从头训模型 INT4 掉 0.8 个点,两者叠加却掉 3 个点以上,超过 2.3 的线性预期。

识别办法是做 2 乘 2 的对照实验。四种组合:从头训 FP16、从头训 INT4、蒸馏 FP16、蒸馏 INT4,各自测分。若蒸馏 INT4 一格明显劣于另三格的线性组合,就确认了非线性叠加,此时应降低蒸馏强度或放宽学生容量。

# 数据表:2 乘 2 对照实验的典型读数(任务精度, 百分比)
组合              分数     判断
从头训 FP16        92.0     基线
从头训 INT4        91.2     量化掉 0.8
蒸馏 FP16          94.5     蒸馏赚 2.5
蒸馏 INT4          91.4     叠加掉 3.1, 超线性

上表是典型的超线性案例,蒸馏 INT4 比线性预期低 0.2 个点。差距不大但要警惕,容量再小一档往往放大到 1 个点以上。

8.2 温度与量化敏感度

蒸馏温度影响软标签的平滑程度,温度越高分布越平滑。平滑目标下的梯度更均匀,学生权重倾向于收敛到更平的极小值区域,这类区域对权重扰动更鲁棒,理论上对量化更友好。Müller 等人的标签平滑研究(arXiv:1906.02699)证实平滑标签能压缩 logits 的取值范围并提升校准,这间接支持了高温度与量化鲁棒性的关联。

但温度过高会抹掉类间差异,蒸馏本身失效。可操作的折中是温度 2 到 4 区间内做两次量化后评测,取量化掉点最小的那次。不要凭直觉固定温度,它同时影响蒸馏收益与量化敏感度两个目标。

T 小 分布尖锐

T 大 分布平滑

蒸馏温度 T

类间差异保留好

权重落点更平坦

蒸馏收益高但量化敏感

量化鲁棒但暗知识变弱

在 2 到 4 之间实测选点

图中左右两条链的末端都指向同一条结论:温度是双目标旋钮,必须用量化后的指标来选,而不是用 FP16 指标来选。这是很多团队漏掉的一步。

8.3 QLoRA 蒸馏的梯度量化噪声

QLoRA 的反传路径要穿过 4bit 冻结基座。前向时权重从 NF4 反量化成 bf16,反向时梯度对这个反量化操作求导,量化误差以噪声形式混进 LoRA 的梯度。论文的实验显示这种噪声在小数据集上无碍,但蒸馏场景下它与 KL 损利的噪声叠加,可能让小 rank 适配器训练不稳。

三个对策按优先级排。第一,compute_dtype 用 bf16 而不是 fp16,数值范围更大。第二,学习率降到常规微调的一半左右,配合更长的 warmup。第三,蒸馏温度适当调低,压缩教师 logits 的方差。三者都是低成本改动,值得默认启用。

# 数据表:QLoRA 蒸馏的稳定性对策
现象                  对策                  成本
loss 曲线抖动          bf16 计算加半学习率    零
KL 项早平台            降低温度或减小 alpha   零
教师与学生不同 tokenizer  重新对齐词表或换学生   中

第三行是新手最常踩的硬坑。教师与学生词表不一致时 logits 形状对不上,KL 无法直接计算,必须先做词表映射或选同家族的学生模型。

8.4 小模型量化的收益衰减

第 7 节已经给出数据:1.5B 的 INT4 实测加速只有 1.3 到 1.8 倍。原因是算子开销占比。模型变小时,单次矩阵乘的时间下降,但每个 kernel 的启动、反量化、以及 RMSNorm 与采样等非量化层的固定开销不变,后者占比被动升高。

推论很直接。0.5B 以下模型做 INT4 权重量化,显存仍省 4 倍,速度收益趋近于 1,此时量化的价值主要在显存而不是吞吐。想要小模型的吞吐,优先考虑算子融合、连续批处理与推测解码这些手段。

# 数据表:不同压缩手段在小模型上的价值排序
手段                显存收益    0.5B 到 1.5B 吞吐收益    主要价值
INT4 权重量化       4.0x       1.1 到 1.8x             显存与并发
蒸馏缩参数          4 到 10x    接近参数比               延迟与吞吐
vLLM 连续批处理     1.0x       2 到 4x                  并发吞吐
推测解码            1.0x       1.5 到 2.5x              单请求延迟

正确的心态是把量化当显存手段、把蒸馏当吞吐手段、把推理框架当并发手段,三者解决不同维度的问题。本系列第 10 篇讲关系蒸馏,监督信号从输出分布升级为样本之间的结构关系。

总结

蒸馏与量化作用在不同自由度上,收益按乘法叠加。7B 教师蒸到 1.5B 再 INT4,显存从 14.5GB 压到 1.3GB,总压缩比约 18 倍,其中蒸馏与量化各贡献约 4 到 5 倍。

两条叠加路线各有定位。先蒸馏再 PTQ 是工业默认,工具链成熟、成本低;量化感知蒸馏把伪量化插进训练循环,INT3 以下极限比特时才值得上。QLoRA 微调蒸馏是中间的精度补偿主力,10 到 12GB 显存就能在 4bit 基座上做教师监督微调。

精度预算要显式分配。蒸馏留 1 到 2 个点,量化留 0.5 到 1 个点,超预算时优先回退蒸馏强度或放大学生,再考虑换量化算法。温度与蒸馏强度都会影响学生的量化敏感度,选参必须看量化后的指标。

最后的选型判断可以压缩成一句话:显存极紧、可容两三个点损失,走蒸馏加量化小模型;精度优先且显存够,直接量化大模型;两者都不确定时,先用几小时做直接量化试点,再决定是否投入蒸馏。

外部引用

  • Dettmers, T., Pagnoni, A., Holtzman, A., Zettlemoyer, L. QLoRA: Efficient Finetuning of Quantized LLMs. arXiv:2305.14314. https://arxiv.org/abs/2305.14314
  • Frantar, E., Ashkboos, S., Hoefler, T., Alistarh, D. GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers. arXiv:2210.17323. https://arxiv.org/abs/2210.17323
  • Lin, J., Tang, J., Tang, H., Yang, S., Chen, W.-M., Wang, W.-C., Xiao, G., Dang, X., Gan, C., Han, S. AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration. arXiv:2306.00978. https://arxiv.org/abs/2306.00978
  • Hinton, G., Vinyals, O., Dean, J. Distilling the Knowledge in a Neural Network. arXiv:1503.02531. https://arxiv.org/abs/1503.02531
  • Sun, Z., Yu, H., Song, X., Liu, R., Yang, Y., Zhou, D. MobileBERT: a Compact Task-Agnostic BERT for Resource-Limited Devices. arXiv:2004.02984. https://arxiv.org/abs/2004.02984
  • Müller, R., Kornblith, S., Hinton, G. When Does Label Smoothing Help. arXiv:1906.02699. https://arxiv.org/abs/1906.02699
  • bitsandbytes 官方文档. 8-bit optimizers and quantization. https://huggingface.co/docs/bitsandbytes/index
  • vLLM 官方文档. Quantization support (AWQ, GPTQ, INT8). https://docs.vllm.ai/en/latest/quantization.html

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/tsh2005974tsh/article/details/166991451

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--