做模型工程化的朋友,最近应该普遍有同一个感受:大模型越来越大,训练时还能顶着几十张卡硬扛,真到上线推理那一步,算力、显存、延迟、功耗全都成了紧箍咒。一个几十B参数量的模型,FP16权重就得占几十GB显存,生成一个token的算力开销更是成倍上升,生产环境往往连一个像样的并发都顶不住。于是,把模型“瘦身”——在尽量不影响效果的前提下把体积、计算量和内存占用压下来——成了大模型能不能真正落地的一道分水岭。
这篇文章就把当前最主流的几套压缩加速手段从头到尾理一遍:知识蒸馏、量化、剪枝、低秩分解,以及它们在工程上怎么组合使用。不堆理论名词,重点是讲清楚它们各自解决什么问题、背后的原理是什么、实操时有哪些关键细节和坑。适合正在做模型部署、推理优化,或者刚开始接触大模型工程化的同学参考。
1. 先算一笔账:为什么要给大模型瘦身
很多人觉得“压缩模型”就是把参数变少一点,但实际工程里的驱动力远不止“硬盘装不下”这么简单。先搞清楚成本和瓶颈在哪,后面选择用哪种压缩手段才会有的放矢。
1.1 大模型上线的三座大山:显存、算力、带宽
先说显存。一个70亿参数的模型,如果用FP16存储,权重本身就要占14GB显存;如果参数量到700亿,那就是140GB,单卡根本放不下。这还只是权重,推理的时候还有激活值、KV Cache、临时buffer,每一层前向计算都会产生中间结果。实际部署时,显存占用往往比“参数体积”高出不少。KV Cache尤其夸张,它随序列长度线性增长,上下文越长占用越大,长文本场景下经常把显存吃穿。
再说算力。Transformer每生成一个token,计算量大约正比于模型参数量,7B模型单token前向大概要跑上百亿次浮点运算。看起来单个token不多,但线上服务一秒可能要生成几十上百个token,还要同时服务多个用户,算力开销瞬间就上去了。
还有一个容易被忽略的瓶颈是内存带宽。权重是存在显存里的,计算单元要把权重搬到片上才能做矩阵乘法。模型越大,每一次前向要搬的数据越多,带宽反而比算力更容易成为瓶颈。很多量化方案之所以能提速,本质就是把“搬运的数据量”减下来了。
1.2 压缩效果怎么看:四个关键指标
评估压缩方案,不能只盯着“参数量下降了多少”。我一般会从四个方面来看:
| 指标 | 说明 |
|---|---|
| 模型体积 | 权重文件占用空间,直接决定能不能装进目标设备 |
| 推理延迟 | 首Token延迟和后续每Token延迟,关系到用户体验 |
| 吞吐量 | 单位时间处理多少请求、生成多少Token,关系到服务成本 |
| 精度效果 | 在业务任务上的指标变化,关系到模型能不能用 |
这四个指标经常互相拉扯。比如剪枝很猛,体积下降了,但精度崩了;量化做得很激进,速度上去了,但业务指标不合格。所以压缩优化的本质是在这四个维度之间找平衡点。一个模型压缩方案好不好,不是看单点指标多漂亮,而是看整体成本收益划不划算。
2. 知识蒸馏:让大模型当“师傅”带“徒弟”
知识蒸馏是压缩里最“温柔”的一种方式,它不直接删参数,而是用一个大而强的模型当老师,训练一个小而快的模型当学生,让学生去模仿老师的输出行为,从而把小模型的效果“拔”到尽量接近大模型。
2.1 蒸馏的核心:软标签里的暗知识
如果只是拿真实标签训练小模型,小模型学到的是“猫是猫,狗是狗”这样的硬分类。但在真实任务里,模型对类别的判断从来不是非黑即白。比如一张图里,猫和狗同时出现,大模型可能给“猫”打0.7,给“狗”打0.2,给“狼”打0.05。这个0.05的微小概率,本身就是一种知识——它告诉小模型,猫和狗在特征空间里比猫和狼更接近。
这种信息藏在输出概率分布里,被称为“暗知识”。真实标签只有一个“正确项”,完全没有这种相似度信息。为了让学生模型能接收到这部分知识,蒸馏时要把老师的输出概率分布“软化”,这里就引入了温度T。
把老师模型的logit除以T后再做softmax,分布会更平滑:
p_i = exp(z_i / T) / Σ_j exp(z_j / T)T越高,分布越平滑,类别间的相似关系越明显;T=1就是普通的softmax。常见做法是T取3到10之间,温度太高会把硬标签信息冲淡,太低则和直接学one-hot区别不大。蒸馏损失一般由两部分组成:模仿老师软标签的KL散度,加上学习真实标签的交叉熵,两者按权重混合。
2.2 离线蒸馏、在线蒸馏和自蒸馏怎么选
按训练流程不同,蒸馏可以分为几类,实际工程里要根据资源情况来选择。
离线蒸馏是最经典的玩法:先用大规模数据训好一个老师,固定住,再用老师的输出去喂学生。好处是老师只需推理一次,学生可以单独训练,流程清晰。坏处是老师模型很大,离线生成软标签需要大量计算和存储空间。如果目标场景是“把一个大模型压缩成小模型”,离线蒸馏通常是最稳的选择。
在线蒸馏是指老师和学生同时训练,老师也在不断更新。这种方案不用预先准备大量软标签,显存占用却很高,因为要同时维护两个模型。有一种变体叫“自蒸馏”,同一个模型自己教自己——用深层输出的特征指导浅层学习。这种方式主要用于加快收敛,压缩力度不大,但实现简单。
还有一种场景,老师的权重完全拿不到,只有API调用能力,只能通过调用接口拿输出标签来构造蒸馏数据。这种“黑盒蒸馏”在现在大模型应用里越来越常见,适合用超大模型生成高质量数据来微调小模型,虽然拿不到中间层,但胜在可操作。
2.3 实操时最容易翻车的三个点
第一是学生模型选得“太小”。如果学生容量跟任务复杂度完全不匹配,蒸馏再强也救不回来。我见过不少例子,蒸馏出来的小模型在训练集上就欠拟合,这时候应该考虑换稍大一点的学生结构,而不是继续调蒸馏参数。
第二是软标签权重和温度的配合。固定温度T=4时,如果发现hard label部分loss一直在涨,多半是soft label权重太高,把真实标签的信息压过头了。反过来,如果学生输出和老师输出差异很大,先别急着加权重,大概率是温度太低,软标签太接近one-hot,暗知识根本没传过去。
第三是数据和词表对齐问题。老师模型和学生模型的tokenizer、输出空间如果不一致,蒸馏loss会变得很诡异。要么统一词表,要么在映射层做对齐,否则学生学到的软标签本身就是错位的。
3. 量化压缩:把精度打折,换性能翻倍
量化是目前大模型工程化里应用最广、见效最快的一招。它的思路很直接:把连续的高精度浮点数,映射成离散的低精度整数,用更少的bit表达权重和激活值。
3.1 量化的本质与两种路线:PTQ和QAT
先说原理。一个FP32的权重值,如果转成INT8,那就只能用256个离散等级来表示它。量化的核心公式是:
q = clamp(round(x / s) + z, q_min, q_max)其中s是缩放因子,z是零点偏移。对称量化里z=0,实现简单;非对称量化z不为0,能更好覆盖分布不对称的数值。
量化分两种路线。PTQ是训练后量化,不需要重新训练,只需拿一批校准数据统计激活值的分布范围,然后计算缩放因子。成本低、速度快,大多数场景都优先试PTQ。QAT是量化感知训练,在训练过程中模拟量化误差,让模型自己适应低精度表达。效果通常更稳,但要重新训练,成本高,适合对精度损失极度敏感的任务。
很多人不理解为什么量化能提速,这里有个关键点:模型推理时,权重要从内存搬到计算单元,访存带宽经常是真正的瓶颈。权重从FP16降到INT8,搬运量直接减半,哪怕计算单元算得快,整体延迟也被拖下来了。尤其在小batch、单请求场景,访存占比更高,量化带来的收益更明显。
3.2 校准集怎么选,直接影响模型生死
PTQ虽然不用训练,但校准集的选择极其重要,这是新手最容易忽略的地方。校准集的作用是统计每一层激活值的min/max范围,如果统计出来的范围和真实部署时的输入分布差太多,量化时就会出现大量截断或过度压缩,精度直接崩盘。
实操上有几点经验:
校准集规模不用大,几百到几千条足够,但覆盖面要广。必须包含真实业务场景里会出现的长尾输入。如果线上是对话脚本,就别拿一堆论文摘要来校准。
分布统计要小心离群值。一小撮异常大的激活值会把max推得很高,导致正常数值被压缩到很粗的区间里,精度白白损失。遇到这种情况,可以在统计时去掉极端的异常值,或者改用更稳健的校准方法。
逐层校准比整体一刀切更稳。敏感层识别也可以做,常见的分类头、LayerNorm、Softmax前的logits这些层,宁可保留FP16也不要强行量化。有些推理框架支持混合精度量化,本质上就是把“容易崩”的层留在高精度,其余层量化。
3.3 量化参数粒度:per-tensor还是per-channel
量化粒度决定了每个量级有独立缩放因子,还是整层共用一个缩放因子。per-tensor实现简单,算子兼容性好,但不同通道的数值范围差异大时误差会很大。per-channel每个输出通道单独算缩放因子,精度更好,但需要底层算子支持。
在GPU上做INT8推理时,很多常见的算子库只支持per-tensor卷积,这时候强行用per-channel可能需要改模型结构,反而不划算。我的建议是:先默认用per-tensor跑一版,看精度能不能接受;如果掉点明显,再尝试per-channel或者把敏感层提出来用高精度。量化不是越细越好,还要考虑部署框架能不能跑起来。
4. 剪枝与低秩分解:从结构上动刀子
蒸馏是“大模型教小模型”,量化是“数值打折”,剪枝和低秩分解则是直接对模型结构下手,切掉冗余的参数和计算路径。
4.1 剪枝:结构化与非结构化差在哪
剪枝的思路是把不重要的参数删掉。但“怎么删”很有讲究。
非结构化剪枝是把权重矩阵里绝对值低于阈值的元素直接置零,模型变成稀疏矩阵。好处是压缩率可以很高,理论计算量也下来了。坏处是稀疏矩阵的存储和计算极不平衡,普通硬件和底层库对稀疏矩阵的加速支持很差。我见过不少人拿非结构化剪枝试了一遍,推理时间不但没降,反而因为稀疏格式转换开销更慢了。如果在GPU上,即使稀疏度到50%,如果没跑稀疏内核,收益约等于零。
结构化剪枝就完全不一样了,它删掉的是整行、整列、整个通道甚至整个注意力头。这样剪完之后,矩阵还是规整的稠密结构,可以直接用现有底层库加速。代价是删掉的信息更集中,精度损失更明显,通常需要剪完后配合微调恢复。
那怎么判断哪些通道不重要?常见做法是看权重L1范数,范数小的通道被认为贡献低。也有更精细的方法,逐个候选通道剪掉后看loss变化多少,变化最小就优先剪。不过这种方法计算量很大,大模型上一般不实用。实操里先用L1范数排序来剪,配合微调,已经能应付大部分场景了。
4.2 低秩分解:用数学办法给矩阵“降维”
低秩分解的思路和剪枝完全不同:剪枝是删掉冗余元素,低秩分解是用一个更小的结构近似原来的大矩阵。一个权重矩阵W,如果写成两个小矩阵的乘积 W ≈ A × B,A和B的秩远小于W本身,那参数量和计算量就都下来了。
数学上最经典的工具是奇异值分解:把矩阵W分解成 U Σ V^T,只保留前r个最大的奇异值,用 U_r Σ_r V_r^T 来近似 W。这个r就是秩,r越小压缩越狠,但近似误差也越大。选r的时候可以看奇异值衰减曲线,保留能量到95%以上通常效果可控。
低秩分解对全连接层、Embedding投影层这类大矩阵效果较好,因为这些层经常有明显的低秩结构。但要注意,低秩分解后的矩阵乘法未必能在现有硬件上自动提速。底层库对稠密矩阵乘法的优化已经很极致,一个小矩阵的乘积可能反而不如原矩阵高效,关键要看有没有配套的算子融合和内存布局优化。
4.3 结构压缩实操时的节奏控制
剪枝和低秩分解都不建议一步到位。我的做法是分阶段执行,每次压缩后马上做少量微调,再进入下一轮。比如第一轮剪掉20%,微调恢复;第二轮再剪15%,再微调;第三轮看情况决定是否继续。这样每步的精度回落都可控,出了问题也能明确回退到上一版。
还有一个细节是剪枝和低秩分解的“侧重”不同。剪枝更适合移除明显的死单元和冗余头,低秩分解更适合压缩大矩阵的投影层。两者组合使用时,一般先剪枝,再对剩下的稠密大矩阵做低秩分解。反过来,如果先把矩阵压低了再做结构化剪枝,可剪的空间就变小了,收益有限。
5. 组合拳实战:一条可落地的压缩流水线
单看每种方法都有局限,实际项目里我很少只用一种手段,而是组合使用。关键是组合顺序,顺序错了误差会叠加,顺序对了每步都能为下一步打基础。
5.1 推荐顺序:蒸馏 → 剪枝 → 量化
我个人的默认顺序是:先蒸馏,再剪枝,最后量化。
为什么蒸馏要放在最前面?因为蒸馏的过程本质上是把“知识”从大模型迁移到小模型。如果先把大模型剪得乱七八糟再拿去当老师,教出来的学生质量必然受影响。先蒸馏能得到一个体积大幅缩小、效果仍然不错的“中等身材”模型,之后才能在这个模型上放心动结构。
剪枝放在量化之前,是因为剪枝会改变模型内部的数值分布。如果先量化再做结构化剪枝,量化时统计的激活值范围和剪枝后的实际分布会对不上,精度损失更不可控。反过来,先剪枝,再通过少量微调让分布稳定下来,最后量化时就有一个相对干净的统计基础。
量化放在最后,是因为它带来的误差是数值层面的“最后一公里”。如果前面的蒸馏和剪枝已经让小模型接近目标效果,量化即使掉一点精度,也还能通过校准和混合精度策略补回来。如果一开始就量化,后面再剪枝,等于同时叠加了两层误差,排查问题都无从下手。
5.2 每走一步,都要有精度恢复预案
组合流水线里最容易犯的错,是“一把梭”。把模型从头到尾压缩一遍,再丢进微调流程想一步恢复,结果发现各项指标都回不去了,但又不知道是哪一步出了问题。
正确做法是每完成一步就评估一次,并保留该阶段的checkpoint。具体流程可以参考下面这个表:
| 阶段 | 操作 | 评估指标 | 精度恢复手段 |
|---|---|---|---|
| 第一步 | 知识蒸馏 | 学生模型参数量、业务指标 | 调整T、soft权重、增大蒸馏数据 |
| 第二步 | 结构化剪枝 | 稀疏度、延迟、指标降幅 | 小学习率微调,观察loss变化 |
| 第三步 | 低秩分解 | 压缩率、单层kernel时延 | 调整秩r,重训或微调该层附近参数 |
| 第四步 | PTQ量化 | 量化前后指标对比 | 换per-channel、敏感层高精度、QAT |
每一阶段的“精度恢复”都要一小步一小步来,学习率通常取主训练学习率的0.1倍左右,训练轮数不用太长,但要让模型充分适应新结构。如果微调后指标依旧明显下降,优先回退到上一版模型,而不是硬着头皮继续往下走。
5.3 部署层的工程优化同样重要
模型压缩只是第一步,最后真正跑起来还离不开部署层面的配合。我的经验是,压缩完模型后不要急着上线,先做一轮profile,看延迟到底花在哪个环节。
常见的工程优化包括:把相邻算子融合成一个kernel,减少反复启动和中间内存搬运;KV Cache复用,避免每次请求都重新申请显存;动态batch,把多个请求打包成不同batch维度,提高GPU利用率;异步流水线,让数据加载和计算重叠起来。
这些优化和模型压缩是相互成全的关系。压缩后的模型体积变小了,省下的显存可以开更大的batch;量化后的算子变轻了,融合后的kernel能进一步压掉访存开销。模型层面的“瘦身”加上系统层面的“调度优化”,才能真正把推理成本打下来。
6. 常见翻车现场与排查手册
压缩优化做得多了,哪些地方容易翻车基本心里有数。这一节把最常见的几类问题整理成速查表,并附上排查思路,遇到类似的情况可以直接按图索骥。
6.1 量化后精度雪崩
量化后指标跌得没法看,通常不是“量化本身错”,而是校准环节出了问题。先检查校准集和线上输入分布是否一致,再用逐层量化对比定位敏感层:每次只量化一层,其他层保持高精度,找出掉点最集中的那几层,把它们列入量化例外名单。
如果校准集没问题,敏感层也排除了,剩下的可能是激活值范围统计被离群值带偏。这时候可以试一下去掉极端值的校准方式,或者改用动态量化——每次推理时现场统计激活值范围,虽然慢一点,但精度通常更稳。
还有一个容易被忽略的点:有些部署框架的INT8算子本身实现质量一般,速度提升可能有限,精度表现也不理想。遇到这种情况,不要硬上INT8,考虑FP8或者混合精度方案,反而更省心。
6.2 蒸馏后学生模型效果迟迟上不去
学生模型loss不降,甚至比直接训练还差,先别急着怀疑蒸馏方法,按顺序排查:
- 先做一轮无蒸馏的普通训练,看学生模型是不是本身结构有问题。如果普通训练也学不好,说明容量不够,换结构比调参数更重要。
- 检查温度。T太高,软标签太平滑,学生学到的全是模糊信息;T太低,软标签接近one-hot,暗知识丢了。可以以T=4为基线,上下对比一轮。
- 检查数据。蒸馏数据不是越多越好,数据如果偏离任务分布,学生学到的是老师的“偏见”。要保证蒸馏数据来自真实业务场景,并覆盖边界情况。
6.3 剪枝后速度没变,低秩分解反而变慢
这两个问题通常是“理论收益”和“实际收益”脱节导致的。剪枝要检查剪的到底是结构化还是非结构化:如果是非结构化稀疏,当前硬件没有做稀疏内核优化,速度不增反降很正常。低秩分解要检查底层算子是否真的跑通了新的小矩阵乘法,如果框架直接把分解后的矩阵还原成普通稠密矩阵来算,那当然不会快。
面对这类问题,最直接的办法是用性能分析工具看每一层的耗时。把压缩前后的kernel时间拉出来对比,找出“理论应该快但实际没快”的层,再针对性地调整压缩策略。不要只看模型整体指标,分层细看才能找到真正的瓶颈。
6.4 一个通用的收益对照参考
下面这张表是我在多个场景里实践下来的大致经验值,注意不同模型、不同任务差异很大,只能当参考不能当标准:
| 方法 | 典型压缩效果 | 精度影响 | 部署复杂度 | 落地周期 |
|---|---|---|---|---|
| 知识蒸馏 | 参数减少数倍到数十倍 | 通常可控 | 低,模型结构和原模型一致 | 较长,需要重新训练 |
| INT8量化 | 显存减少约一半(相对FP16) | 小,校准得当几乎无损 | 中,依赖算子支持 | 短,几小时到几天 |
| 结构化剪枝 | 参数量和FLOPs下降20%-50% | 较大,需要微调恢复 | 中,需要适配剪后结构 | 中,剪枝+微调 |
| 低秩分解 | 参数量下降明显 | 取决于秩的选择 | 高,需要算子融合优化 | 长,改造工作量较大 |
| 组合拳 | 压缩数倍且加速明显 | 每步控制可接受 | 高,链路复杂 | 长 |
实操中最稳的策略不是“把每种方法都用到极致”,而是给每一步设定一个止损线。比如“剪枝后指标下降不超过2%”“量化后得到的效果差异不超过1%”,到达止损线就停下来。毕竟压缩的最终目的不是把模型变得多小,而是让它在成本、延迟和效果之间找到最优解。
我个人在实际项目里养成的习惯是:拿到一个需要上线的模型,先不急着上全套压缩方案,先花半天时间做profile,把显存、延迟、吞吐的基准数据记录下来。然后从量化开始试水,如果收益不够再考虑蒸馏,最后才动用剪枝和低秩分解这些结构性手段。每走一步都记录收益,保留可回退的checkpoint。这个“先测后压、逐步推进”的节奏,比一上来就叠满所有技巧靠谱得多。