一台几年前的手机,或者产线设备上那块巴掌大的板子,内存小、散热差、供电也紧。训练好的模型放进去,文件大小先卡住;勉强塞进去了,跑起来发烫、延迟忽高忽低。到这一步,讨论的重心已经从“模型有多聪明”换成“它能不能在这个壳子里活下来”。
先别急着动手,弄清卡在哪一环
同样是跑不动,原因可能完全不同。有的场景卡在存储,权重根本装不下;有的卡在内存带宽,权重读得动但读得太慢;有的卡在功耗和发热,算力名义上够,持续跑几分钟就降频;还有的卡在延迟抖动,平均值看着还行,偶发的长尾让交互变得难受。
这几种瓶颈对应的处理方式差别很大。若只是存储吃紧,把权重精度降下来往往立竿见影;若是带宽受限,减少访存次数、改变计算顺序可能更有效;若是功耗受限,就得接受算得少一点。不先分清是哪一种,做模型压缩很容易变成只在文件大小上做功,实际体验没什么变化。
几种常见做法,动的是不同的东西
- 量化:把权重和激活从高精度浮点换成低位宽表示。它压缩的是每个数的存储宽度,改动面广,对硬件也相对友好,但难点在离群值——少数数值范围特别大的通道会把整体精度拖下来,需要单独处理。
- 剪枝:去掉一部分连接、通道或整层。它压缩的是结构,听起来很划算,但稀疏矩阵在通用硬件上未必跑得快,除非稀疏形态能被硬件或算子库识别,否则省下的只是磁盘空间。
- 蒸馏:让小模型去学大模型的输出分布。它换的不是同一个模型的体积,而是换了一个更小的模型,代价是要重新训练,并接受小模型自身容量带来的上限。
- 低秩分解与结构替换:把大矩阵拆成几个小矩阵相乘,或者直接把骨干网络换成更轻的设计。这类改动往往需要重新训练,收益也依赖具体结构,不能一概而论。
这些手段并不互斥,实际工程里经常叠着用。但每叠加一层,误差的来源就多一层,后面排查问题会更费力。
文件变小,和跑得快是两件事
这是最容易踩空的地方。一个稀疏度很高的模型,理论上计算量小了很多,可如果推理框架不认识这种稀疏格式,实际执行的还是稠密计算,速度原地不动。量化也一样,某些低位宽运算需要硬件原生支持,否则要在运行时反量化回高精度,反而多出一层开销。
所以评估模型压缩的效果,不能只看体积和参数量,得在目标设备上实测端到端耗时,包括预处理、内存拷贝和后处理。只看模型文件那一栏数字,很容易得出乐观但错误的结论。
压缩之后,还得重新建立信任
压缩会改变误差的分布,而且往往不是均匀地改变。整体准确率可能只掉一点点,但某些类别、某些边界情况、某些输入条件下退化得更明显。长尾样本本来就少,压缩后更容易被牺牲掉,而它们在真实场景里恰恰是麻烦的来源。
因此压缩后的模型需要重跑一遍测试,重点看那些原本就吃力的部分,而不是只看总体指标。如果业务对误判的容忍度很低,就得为压缩留出更大的安全余量,或者在关键环节保留原始精度的路径。
一个务实的顺序
先测清楚瓶颈在存储、带宽、功耗还是延迟,再决定动哪一块;然后选一种改动最小、最容易回退的手段先试,把误差和速度都测出来;最后再考虑叠加。反过来做——先挑一个听起来激进的方法,再去解释效果——通常会在联调阶段付出更多时间。压缩的本质不是把模型变小,而是在有限资源下重新分配精度,哪些地方可以糊涂一点,哪些地方必须清楚,这个判断没法交给工具自动完成。