设想一台用了四年的手机,8GB 内存,你想把一个参数量不大的语言模型装进去做离线摘要。模型能加载,进度条也走完了,可每生成一段话机身就发烫,帧率往下掉。没有风扇,它只能靠降频自己扛。
这种时候,拖住速度的往往不是算力峰值。每生成一个 token,权重都要从内存搬到计算单元一次,搬运的代价常常比乘加本身更显眼。容量和带宽是那扇窄门,模型压缩要处理的,正是怎么让一个模型从那扇门里过去。
几种做法,各记各的账
常见的手段并不多,但每一种的代价都落在不同地方。
- 量化把浮点权重换成低位宽整数,内存占用和带宽压力一起下降。麻烦在离群值:少数通道的数值范围特别大,一刀切的比例因子会把它们压坏,于是需要分组缩放,或者对个别通道单独处理。
- 剪枝删掉不重要的连接或整块结构。非结构化稀疏听起来省了很多参数,落到通用硬件上却不一定变快,不规则访存会把收益吃掉;结构化剪枝对硬件友好,但删得狠了,模型表达能力下降得也直接。
- 蒸馏让小模型去学大模型的输出分布。它需要教师模型、需要数据、需要重新训练,学到的是输出层面的相似,而不是原模型内部的那套本事。
- 低秩分解把大矩阵拆成两个瘦矩阵相乘,对矩阵形状很挑剔,碰上本来就不大的层,收益有限。
实际部署里,这些手段经常叠着用:先量化压到能放进内存,再用蒸馏补回一点能力。叠加不是免费的,误差会互相放大,可调的余地也跟着变窄。
退化不均匀,评测却给平均分
压缩之后,模型不会整体上笨一点,它是有选择地坏。日常对话、常见问法可能看不出差别,先出问题的是长尾部分:生僻的专有名词、需要严格格式的输出、长上下文里前后照应的指代。量化误差会随着层数和序列长度累积,短文本测试里干净的分数,到了真实的长输入上未必守得住。
而常见的评测给的是一句平均分。平均分把那些悄悄坏掉的地方抹平了,于是模型上线,问题在具体场景里冒出来,再回头去查是哪一步压坏的。所以压缩前后的对照,不妨按场景切片看:格式遵循、实体保留、长输入一致性,各自单独测,别指望一个总分替你回答。
别等到上线前才想起它
我倾向于把模型压缩当成一个前置的设计约束,而不是训练完再补的一刀。目标硬件的内存预算、可接受的延迟、能容忍的精度损失,这三件事应该在定模型结构的时候就摆到桌面上。结构上留出可以被压缩的余地——通道数、层宽、注意力的头数,这些选择在训练之后再改,成本高得多。
还有一点常被忽略:压缩改变的不仅是精度,还有稳定性。同一个问题问两次,答案的差异可能被放大;温度稍微调高,输出就开始跑偏。这类变化很难写进指标,却直接影响使用者的信任。做这件事的人最好自己也用一用压缩后的模型,而不是只看曲线。
说到底,模型和硬件之间的这场谈判,压缩只是其中一轮。把它当成事后补救,你得到的是一个勉强能跑的模型;把它写进最初的设计里,得到的才是一个清楚自己要在哪里运行的模型。