压测下午批量推理耗时三小时,我重学模型压缩把延迟砍到1/10
发版前一天下午的压测,SageMaker 批量推理任务跑了快三小时还没见收尾。运维在群里贴出延迟曲线,把全组钉在了会议板上。我拉日志翻到模型参数文件近 800MB--那一刻我才明白,从头缺的不是算力,而是一次正经的模型压缩补课。
我当即点开那门一直收藏的机器学习入门,跟着里面专门讲解模型压缩的章节做起实验。两天后,一样的实例、一样的数据量,处理 1 万条请求的耗时从天变成了分钟。这篇文章就是那次踩坑与止血的完整复盘,也是我为什么劝每个做在线推理的同行,先把模型压缩摸透再碰批量推理。
一、灾难现场:发版压测时推理延迟彻底失控
我们当时要给一个推荐系统的排序模型切新版本,用 SageMaker Batch Transform 跑离线批量推理,目标 15 分钟内处理完 5 万条特征数据。压测脚本一跑,pipeline 直接卡死在推理阶段。
原本预估 12~15 分钟的作业,跑了 3 小时 12 分。SLA 是发版前必须完成全量打分,运维直接给我挂了红灯。
我第一反应是加实例。从 ml.m5.xlarge 换成 ml.m5.4xlarge,又调大MaxConcurrentTransforms,但延迟曲线纹丝不动。那时候我才被迫正视一个根本问题:模型压缩没做,模型文件又笨又慢,并行的边际效应几乎为零。
二、病灶定位:为什么加实例完全没用
排查过程中我画了一张简表,试着把模型压缩前后的核心指标拼在一起,才发现差距有多大。
| 指标 | 当前模型 | 目标要求 | 缺口 |
|---|---|---|---|
| 模型体积 | 792MB | <150MB | 约5倍 |
| 单次推理耗时 | 340ms | <40ms | 约8.5倍 |
| 内存占用(单worker) | 1.9GB | <600MB | 约3倍 |
当时我调过MaxConcurrentTransforms到 8、16,延迟并没有线性下降。后来才搞懂,模型压缩不到位的情况下,worker 一启动就先得把近 800MB 的模型加载进内存,上下文切换的代价反而让吞吐量恶化。
那晚我翻出机器学习基础课的部署优化章节,里面明确提到一句话让我愣住:
“没有经过推理优化的模型直接上线,等于把一辆工程车开上高速公路。”
我终于承认,自己一直疏忽了模型压缩这一整块基础能力。
三、搬救兵:从课程里重新捡回模型压缩体系
我不是第一次听说剪枝和量化,但过去总觉得那都是“大厂才要做的极致优化”。这次压测打了脸以后,我系统性地回炉了三门课。
首先是机器学习入门,它有一整章专门讲模型压缩:从权重剪枝、结构化剪枝到参数量化,每小节都带 AWS 的 SageMaker 示例。我最大的收获是搞清楚了“剪枝后微调”的必要性--之前我以为剪掉就完事,结果精度掉到没法用。这门课是给零基础 ML 工程师补底子的,学完就能动手压缩自己的训练模型。
接着我又过了一遍深度学习基础,重点看了 PyTorch 的量化工具链。课程里一步一步演示了动态量化和静态量化的区别,还给了延迟-精度的折中建议。这让我在后续选压缩策略时有了硬依据,而不是凭感觉瞎调。
最后我还重新看了AWS深度学习里的 SageMaker Neo 部分,它教你怎么把压缩后的模型编译成针对特定硬件的格式,进一步降低推理延迟。这一环我之前完全不知道,学完以后,我的模型压缩方案才算真正闭环。
四、动手实操:在 SageMaker 上把压缩后的模型塞进批量推理
我最终采用了两步走:先用 PyTorch 做动态量化,把模型体积从 792MB 压到 136MB;再用 Structured Pruning 削掉 30% 低重要度的卷积核,微调 3 个 epoch 回到原精度。
量化核心代码像这样(来自学习笔记,关键注释保留):
import torch from torch.quantization import quantize_dynamic # 加载原始排序模型 model = torch.load('ranking_model.pth', map_location='cpu') # 动态量化:只量化 LSTM 层和 Linear 层,平衡速度与精度 quantized_model = quantize_dynamic( model, {torch.nn.LSTM, torch.nn.Linear}, dtype=torch.qint8 ) # 保存压缩后的模型,用于部署到 SageMaker torch.save(quantized_model.state_dict(), 'ranking_model_quantized.pth')剪枝部分我用了torch.nn.utils.prune:
from torch.nn.utils import prune # 对第一层卷积做 L1 非结构化剪枝,保留 70% 权重 prune.l1_unstructured(module, name='weight', amount=0.3) # 固化剪枝结果并重新微调 prune.remove(module, 'weight')部署到 SageMaker 时,Batch Transform 配置也要配套改写:
{ "BatchStrategy": "MultiRecord", "MaxConcurrentTransforms": 8, "MaxPayloadInMB": 1, "ModelName": "ranking-compressed-v2" }这套组合拳下来,压测曲线终于拉回到 SLA 之内。而我之前觉得“模型压缩很难”的错觉,也在一步步动手后被打消了。
五、数据说话:压缩前后成本与延迟对比
我把模型压缩前后的压测数据汇总成了一份表格,也是后来给 CTO 汇报用的一张硬核对比图。
| 版本 | 模型体积 | 单次推理延迟 | 5万条总耗时 | 实例类型 | 单次压测成本 |
|---|---|---|---|---|---|
| 原始模型 | 792MB | 340ms | 192 分钟 | ml.m5.4xlarge | $8.36 |
| 仅量化 | 136MB | 62ms | 36 分钟 | ml.m5.xlarge | $1.18 |
| 量化+剪枝 | 104MB | 29ms | 17 分钟 | ml.m5.xlarge | $0.62 |
延迟缩减了 91.5%,成本砍掉了 92.6%。这些数字不是靠堆机器砸出来的,而是靠模型压缩和合理的并发配置换来的。在学机器学习基础之前,我看到这种优化数据会觉得“可能是特例”;但现在我知道,只要模型压缩策略得当,绝大多数场景都能拿到 5 到 10 倍的提升。
六、学习清单:如果你也遇到批量推理瓶颈
这次事故后,我给自己列了一份可复现的学习顺序,也是给相似处境的人一条最短止血路径:
- 先判断瓶颈是模型还是链路:如果加并发延迟不降,大概率是模型体积问题,赶紧补模型压缩。
- 从机器学习入门开始:别一上来就钻量化论文,先用课程里的案例把剪枝、量化、蒸馏的概念串起来,建立全局观。这门课对零基础友好,半天就能跑通第一个压缩实验。
- 用深度学习基础巩固 PyTorch 量化方案:学会动态/静态量化的适用边界,这是最稳妥的起步路线。课程里的动手实验室能让你在沙箱里直接看到延迟变化,省得自己去搭环境踩坑。
- 不要忽视 AWS 基础知识:SageMaker Batch Transform 的配置项,比如
MaxConcurrentTransforms、BatchStrategy,必须和模型压缩后的资源占用匹配。我在这门课里补了成本管理模块,才知道如何根据压缩后内存选择实例类型。 - 压缩后记得微调和重评估:剪枝后不做微调,精度下降会让你误解模型压缩没用。我跟着机器学习入门的实验脚本做了 3 个 epoch 微调,AUC 只降了 0.007,完全可以接受。
- 把模型压缩纳入 CI/CD 卡点:以后任何新模型上线,压测脚本自动检查体积是否超过 150MB,否则阻断发布。这个教训值一整周的加班。
这次经历让我彻底认清:模型压缩不是锦上添花的选修项,而是线上服务的基础出厂设置。如果你也正被批量推理延迟折磨,点开那门让你一直好奇但没打开的机器学习入门,从它的模型压缩章节开始,大概率就是你问题止血的起点。