尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

算法与infra协同实战:从模型部署到推理性能优化的完整指南

算法与infra协同实战:从模型部署到推理性能优化的完整指南 模型在本地调得再漂亮上了线才发现GPU利用率不到30%接口P99延迟飘到几百毫秒算法同学说“我代码没问题”infra同学说“我资源也没问题”两边对着一个黑盒互相甩锅——这种场面我在不止一个团队里见过。算法和infra的协同听起来是个工程管理话题实际操作起来全是技术细节推理框架怎么选、batch size怎么定、数据管道怎么设计、profile的结果怎么解读。这篇东西不聊虚的把我自己在一线做“算法-infra协同”的踩坑经验、设计思路和排查方法整成一份可复用的参考给正在被模型部署和性能调优折磨的算法工程师、以及被各种“优化需求”打扰的infra工程师都做个参照。我前几年主导过一个推荐模型的推理服务优化项目从最开始的两边互相看不懂到后面沉淀出一套配合模式中间的过程很能说明问题。下面按我自己的实践路径把整个协同过程拆开讲。1. 为什么“算法-infra协同”成了必须面对的问题1.1 算法模型跑通只是第一步上线才是真正的门槛很多算法团队习惯的交付状态是模型在离线评测集上指标达标然后在Jupyter Notebook里跑通一个推理demo就认为“算法已经完成”。但实际情况是从Notebook到生产服务之间有一道巨大的鸿沟这道鸿沟几乎全是infra的领域。拿我自己踩过的坑来说。有一次我们训练好的CTR模型离线AUC比旧模型高了三个千分点算法同学很开心觉得这个版本稳了。但一到线上压测就傻眼了单机QPS只有预期的四分之一P99延迟超过500毫秒线上流量稍微一抖服务直接超时雪崩。那时候算法和infra互相都觉得是对方的问题。算法觉得“模型逻辑就这么简单无非就是个矩阵乘法加激活函数怎么可能这么慢”infra觉得“机器配置给够了是你模型代码写得烂”。后来profile一查问题根本不在模型计算本身特征拼接时反复做内存拷贝、Python侧循环处理请求、每个请求都重新加载一次词表这些代码层面的问题全被忽略了。模型本身可能只占整个推理链路的20%时间剩下80%都被数据处理、框架调度、IO等待吃掉了。这件事给我的教训是算法模型的“正确性”和系统的“可用性”是两回事。算法同学的交付物不应该只是模型权重还应该包含对推理链路的整体理解以及和infra一起把系统调优到可用的状态的意愿。1.2 协同断点到底卡在哪里要说算法和infra的协同难难就难在两边的心智模型不一样。我观察下来断点主要集中在这几个地方。第一个断点是语言不通。算法同学张口闭口是“AUC”、“loss收敛”、“Embedding维度”infra同学关心的是“QPS”、“P99时延”、“CPU水位”、“内存占用”。两边都在说性能但说的根本不是一回事。算法眼里的“性能”是模型效果指标infra眼里的“性能”是系统资源指标中间缺一个翻译层。第二个断点是交付物边界不清。算法觉得“我把模型文件发给你就行了”infra觉得“你给我的模型我跑不起来”。模型用什么框架导出、有没有带预处理逻辑、依赖的Python包版本、词表文件在哪、特征维度是多少这些信息如果不掉清楚infra同学就得靠猜。猜的代价就是反复试错时间全耗在沟通上。第三个断点是资源评估全靠拍脑袋。算法说“给我4张A100”问他要4张卡的理由他说“可能不够多要点保险”。infra那边资源池本来就不宽裕一看这种申请直接打回。其实完全可以做精细化评估跑一下profile看显存峰值和计算密度再用公式估算一下吞吐需求有理有据地填申请单。第四个断点是性能问题定位的方式不对。我见过有团队遇到线上延迟高第一反应是“加机器”结果加了两倍的机器延迟一点没降因为瓶颈在CPU上的数据预处理不在GPU推理。按加机器来扩容完全扩错了方向。这些断点单独看都是小事但叠在一起就会形成巨大的协作成本。我后来总结了一个观点算法和infra的协同本质上是要找到一种方式把彼此的领域知识翻译成对方能理解的语言比如用可量化的指标、标准的交付格式、清晰的流程节点把这些知识编码下来。2. 我在协同项目里的整体设计思路2.1 从“交付模型”转向“交付服务”这个转变是我做协同优化时第一个确立的原则。如果算法团队的交付物只是模型文件那infra团队的职责就成了“把模型跑起来”至于跑得好不好、快不快全是infra的事算法自然就不关心推理性能。这样分工从根上就有问题因为推理性能的很大一部分取决于模型结构、算子实现和特征处理方式这些都在算法团队的控制范围内。正确的做法是把交付边界往后推算法团队交付的不只是模型文件还包括一套推理服务的可运行版本。这套版本要有明确的性能指标要求比如目标QPS、目标延迟、吞吐上限并且要附带模型对资源需求的估算说明比如建议的batch size范围、显存占用预估、CPU内存需求。infra团队基于这套交付物做容量规划和部署调优双方的责任边界从“模型文件”移动到了“性能指标”。你说这不是算法团队得多干不少活吗确实多了但这种“多”是必要的。因为只有写模型的团队才最清楚模型的计算图、内存占用和算子特性这些信息转述给infra要么失真要么说不全。把性能要求写进交付规范双方对话就有了共同基准。2.2 接口规范化让算法和infra解耦的关键第二个原则是接口规范化。我之前出现过不少让人崩溃的协作问题算法同学说“这个实现依赖Python 3.8的一个特性”infra同学部署的生产环境是Python 3.7直接跑不起来或者模型输入的特征顺序算法和工程端理解的不一样上线之后预测结果全乱了。后来我们做了一个标准化的模型服务接口规范把模型推理的输入输出格式固定下来特征按有序字典传入模型版本用语义化版本号管理配置信息如batch size、并发数、超时时间全部外置到配置文件里。这样算法迭代模型时不需要改动服务框架infra升级基础设施时也不需要跟算法确认每个细节。模型文件、特征配置、服务框架、资源规格四层各管各的通过标准化接口衔接。这个设计很像软件工程里的依赖倒置原则高层的算法逻辑不要依赖底层的具体基础设施底层基础设施也不要反哺高层的模型代码两边只依赖抽象接口。实践下来这个解耦带来的好处是两边可以并行推进不再互相阻塞。2.3 用性能基线和透明数据建立信任第三个原则是用数据说话建立性能基线。在协同项目里最怕的不是性能差而是性能数据不透明。算法说“我这个模型很快”没有任何数据支撑infra说“服务器有瓶颈”也说不清瓶颈在哪。最后只能靠嗓门大。我们的做法是建立一套标准的性能评测流程每次模型迭代上线前都要在固定的评测环境下跑一次压测记录吞吐量、P99时延、GPU利用率、显存占用、CPU开销等核心指标把这些指标记录到一份自动生成的评测报告里和上一版本做对比。谁快谁慢、快在哪慢在哪一目了然不再有“我觉得”“我感觉”的空间。这套评测流程跑起来之后我明显感觉到两边沟通顺畅多了。infra说“你的服务GPU利用率低”直接拿出监控截图和nvidia-smi数据算法说“这个算子慢”直接拿出profiling报告指出热点。数据代替了猜测协作自然就高效了。3. 推理服务协同优化的实操全过程3.1 环境准备阶段选对工具链后面能省一半的麻烦这个项目的目标是让一个推荐模型的在线推理服务达到线上要求支撑2000 QPS的流量P99延迟不超过100毫秒。模型用的是DeepFM架构特征规模大约5000万维稀疏特征加200维稠密特征服务于用户点击率预估场景。环境选型上我们做了一轮比较。模型原本是用PyTorch训练的原计划直接用PyTorch做推理服务但压测发现Python侧的开销太高GIL让多线程并发受限后来换成了ONNX Runtime加TensorRT的后端方案。ONNX Runtime负责从PyTorch导出的模型做转换和执行TensorRT在NVIDIA GPU上做算子融合和精度校准。实测下来单卡的推理吞吐比纯PyTorch提升了接近3倍P99时延也降了一个数量级。工具链方面profile用的是PyTorch自带的torch.profiler配合NVIDIA的Nsight Systems资源监控用的Prometheus加Grafana部署走的是Docker加Kubernetes。这套组合的好处是覆盖了从模型算子级别到系统资源级别的全链路观测两边拿到的数据是同一套不会再出现“我这边看到CPU满了你那边说进程很闲”这种数据对不上的情况。3.2 从单卡推理到服务化部署这一步隐藏了最多的坑模型导出之后第一版直接拿ONNX Runtime的Python接口做了个Wrap然后用Flask启动了一个HTTP服务。简单测了一下能跑通但很快就暴露了问题Python接口的调用开销很大每次请求进来都要做Python和C之间的数据交换而且不支持真正的多进程并行推理CPU并发利用不起来。后来改成了ONNX Runtime的C部署方案配合TensorRT的engine来做推理。这一步改动带来的性能提升非常直接因为省掉了Python解释器这层开销每次推理调用的延迟直接砍掉了三分之一。同时我们用内存映射的方式预加载了模型的Embedding参数避免了每个请求都从磁盘读取参数导致的IO抖动。服务端的并发模型也做了一轮重构。最初是每个请求一个小线程线程切换开销大后来改成了线程池加有界队列的模式根据目标QPS动态调整worker数量配合OMP_NUM_THREADS环境变量控制底层并行度。这么改完单实例的QPS从原来的不到400提升到了接近1500CPU和GPU的使用率也均衡了很多。这里有个数据值得记录一下同样的模型和机器纯Python部署的P99时延是180ms换成C后端之后降到了58ms再配合TensorRT的FP16精度优化进一步降到了35ms。这三步优化每一步单独拿出来看都不算大工程但叠加起来就是数量级的差别。3.3 压测过程和瓶颈分析把一切问题摆到明面上压测工具用的是wrk加自研的流量回放脚本。wrk负责模拟高并发HTTP请求流量回放脚本则从线上Nginx日志抽取真实请求样本按时间戳顺序重放到测试服务上尽量还原线上请求的特征包括请求大小分布、特征稀疏度、以及峰值时刻的流量形态。压测分为三轮。第一轮是小流量验证用100 QPS跑10分钟确认功能正确、内存没有泄漏、日志没有报错。第二轮是加压测试从500 QPS起步每2分钟增加250 QPS观察各项指标的变化趋势找到服务崩溃的临界点。第三轮是持续稳定性测试用目标负载的80%1600 QPS连续跑8小时确认长时间运行下没有内存膨胀、句柄泄漏等问题。瓶颈分析阶段我们抓到了好几个经典问题。第一个是第一个版本在1000 QPS左右时P99延迟突然从60ms跳升到400ms查下来发现是Python侧的特征预处理线程变成了瓶颈CPU打满GPU反而在空转。解决办法是预处理部分全部向C侧下沉用ONNX Runtime的custom operator实现特征拼接把CPU和GPU的工作重新配平。第二个是压测跑到中段出现周期性延迟尖峰后来发现是日志写的太频繁磁盘IO被打满改成异步日志之后尖峰消失。第三个是Embedding查表在GPU上表现为显存访问不连续用class-wise的存储布局优化之后这块耗时又降了一些。这些瓶颈没有一个能被直觉直接发现全靠profile数据才能定位。所以我强烈建议优化性能的时候一定要先把“当前时间花在哪儿”搞清楚再谈怎么优化千万不要凭感觉。4. 协同过程中必踩的坑与解决方案实录4.1 “GPU利用率上不去”到底是谁的锅这是我在协同项目里被问过最多的问题也是算法和infra最容易吵架的导火索。很多团队拿“GPU利用率”作为模型服务效率的核心指标但GPU利用率低的原因远比想象的复杂不一定是模型的错。我见过的GPU利用率低的典型原因有这么几类数据加载和预处理太慢GPU在等数据利用率自然上不去batch size设得太小GPU的并行能力没有充分释放CPU侧的Python逻辑占了太多时间GPU一直在空转模型本身有串行依赖计算图并行度不够。每一类对应的解法完全不同。定位方法说起来很简单打开Nsight Systems或者nvidia-smi的监控看GPU到底是“计算密集”还是“空闲等待”。如果是等待就去看CPU侧在忙什么如果是计算密集但利用率依旧低那就去看模型的计算图是不是存在大量的串行节点。有一次我们排查一个BERT的服务发现GPU利用率一直只有20%多profile一看问题在CPU侧的分词器上——每次请求都要重新做一次分词一个毫秒级的操作在并发量大的时候被放大成了巨大的等待。改成预分词加缓存之后GPU利用率直接翻倍。这类问题暴露出来以后我最大的感触是千万不要把“GPU利用率低”当作某个人的责任问题它更接近一个系统问题。GPU利用率是结果指标不是原因指标单纯盯着它没什么用还是要一级一级往下拆。4.2 模型精度复现不了的隐形元凶模型从PyTorch导出到ONNX再到TensorRT之后经常有算法同学跑回来反馈“线上预测结果和离线对不上。”一开始我们以为是模型导出过程出了错后来才发现最隐蔽的问题来自“数值精度”和“算子实现差异”。PyTorch在GPU训练时默认用FP32推理转成TensorRT的FP16后部分算子比如LayerNorm、Softmax的数值精度会有微小差异在大多数场景下不影响排序但有些边界样本的预测值会变化几个百分点。算法同学如果拿着离线全精度的输出和线上半精度的输出逐条比对就会看到差异。解决的办法是分类处理对精度有严格要求的任务比如风控、金融场景保持FP32推理用算子融合和优化内存访问来提速对排序、推荐这类对绝对概率值不敏感、只看相对顺序的任务可以用FP16甚至INT8配合校准集把精度损失控制在一个很小的范围内。还有个容易被忽略的点是框架版本差异PyTorch 1.x和2.x导出的ONNX算子集不一样TensorRT版本不同也可能导致同样的模型在部署时行为不同。所以必须把“训练框架版本、导出工具版本、推理框架版本”这三项固定下来做成一个部署配置的“铁三角”任何一项升级都要重新回归。4.3 资源申请被驳回其实是可以避免的“申请4张A100被infra驳回了说我浪费资源怎么办”这种问题我隔三差五就能遇到。实际情况是不少算法同学根本不估算自己到底需要多少资源申请的时候全靠感觉往大了填。infra那边资源池是共享的肯定优先批给那些能说清楚需求的团队。资源估算其实没那么难。第一步在目标机器上跑一次真实的训练或推理任务记录峰值显存和平均显存这就是“单实例基线”。第二步根据训练数据量和目标训练周期估算需要的总算力公式大概是总算力需求FLOPs 单样本前向计算量 × 训练样本数 × epoch数。第三步结合单卡算力算出需要的显卡数量和训练天数再留出20%到30%的冗余应对波动。拿这个思路去和infra沟通交流起来完全不同。你说“我需要4张卡跑完需要3天实测单卡峰值显存是38GB考虑数据加载波动留了10%的buffer需要40GB以上的卡”这种申请没有理由被驳回。说到底资源申请不只是填个数字它本身就是一种工程能力是算法和infra之间建立信任的重要一环。5. 把协同能力沉淀成团队资产5.1 给算法同学的建议不要把“跑通了”当作交付完成我自己也是算法出身太知道“跑通”和“交付”之间的差距了。模型在Notebook里跑通了只是证明你的思路在二三十个样本上可行真正让模型产生价值的是它能稳定地服务线上流量还能在流量翻倍时不崩在资源减半时依然可控。这些要求都不是算法同学的“分内事”但如果完全不关心你的模型就永远活不过上线那一天。所以至少要做到交付模型时附带一份“模型说明卡”内容包括模型结构、输入输出的字段和类型、依赖的框架及版本、词表在线获取方式、推荐的batch size范围、估计的显存占用、冷启动时间、对CPU和内存的依赖。这份卡片不要求多规范但至少能让infra同学不用靠猜来部署你的模型。另外学会读profile是算法同学性价比最高的技能投资。一个模型服务慢90%的情况下用profile都能找到原因。我见过太多人拿着性能问题去找infra连自己程序的火焰图都没看过一眼。不要只会跑训练要学会看推理的计算热点。掌握这一项技能你在协同中的话语权会高很多。5.2 给基础设施同学的建议把“不行”变成“怎么才行”infra同学在这类协同里也有一个常见的立场问题因为长期被各种不靠谱的资源申请和不规范的服务代码折磨容易变成一个“什么都说不行”的状态。这样短期看是保护了资源池长期看会压制业务的迭代速度最后被业务方集体吐槽“基础设施跟不上业务”。我的建议是infra团队的重点不应该放在审批和拒绝上而是应该放在提供“自助能力”上。比如做一个标准的模型部署平台算法同学上传模型文件、选定推理框架和后端配置平台自动生成Docker镜像、分配测试环境、跑一轮标准压测、输出性能报告。基础设施把通用的能力沉淀成工具和流程就能把“每个项目都要人肉介入支持一次”的重复劳动省掉。这需要在思想上做一个转变infra的kpi不是“管住了多少资源”而是“业务能多快地把模型部署上线并且用尽量少的资源跑起来”。如果基础设置的同学能往这个方向做算法和infra的摩擦会小很多。5.3 把复盘和知识沉淀变成日常动作项目结束之后我们做了一次总结把整个协同过程中的经验和工具沉淀成了几样东西一份模型交付标准文档、一个自动化压测脚本、一套性能基准数据表、一段线上问题排查的SOP。这些资产的价值在后面的几次迭代里被充分证明了——新模型上线的时间从一开始的三天压缩到了半天性能问题的定位时间也从按小时计缩短到了按分钟计。复盘形式上我们没有搞那种全员参加的PPT大会就是内部拉了一个文档把从模型开发到部署调优到线上稳定的过程中所有关键决策、踩坑记录、数据截图、最终结论全部写进去想到什么记什么。每周项目例会上花15分钟过一遍。三个月后回头翻这份文档里面很多记录在当下看来已经成了团队共识但如果没有当时的记录这些共识根本建立不起来。还有一点很关键知识沉淀要落到代码和脚本里不能只活在文档里。比如“压测时必须监控GPU利用率”这个原则写在文档里可能过几周就没人看了但如果写进压测脚本里每次压测结束自动出一份指标报告这个原则就变成了流程的一部分天然被强制执行。制度化永远是比自觉更可靠的保障。6. 一个更长期的视角算法与基础设施双轮驱动的演进路径6.1 模型迭代和基础设施演进要配套走我们刚开始做这个协同项目的时候总觉得模型迭代是最重要的infra只是“陪跑”。但做到后面才发现两者其实是在互相牵引的。模型侧有个新想法比如从单模态走向多模态或者模型结构要从2D卷积换成3D卷积这对基础设施提出的要求是完全不同的反过来基础设施这边推理框架升级支持了新的算子和精度模式也会反过来影响模型侧怎么做结构设计和调优。所以现在我们在做技术规划时会把算法和infra放在同一个盘子里看。每次新模型立项的时候算法团队要写清楚预期的推理需求和资源消耗infra团队根据这个需求反馈基础设施的改造计划两边一起排期而不是等算法做完了再丢给infra“看看能不能跑”。这种平行推进的节奏看起来很慢实际算总时间反而更快因为它减少了大量返工和等待。6.2 “算法-infra协同”的三种模式根据团队的大小和业务复杂度我观察到的协同模式大概有三类你可以看看自己团队现在卡在哪一类。第一种是“工程师一肩挑”小团队里算法同学顺便把自己写的服务部署上线了这种模式最灵活但对个人能力要求极高而且随着业务变大很难维持。第二种是“接口人协作”算法和infra各派一个人负责对接互相转述需求这种模式对接口人的能力依赖极大接口人一换人就可能崩。第三种是“流程化协同”通过标准的交付物、性能指标、部署流程来协作人和人之间对具体细节的依赖降到最低这也是我在这篇文章里主要推荐的做法。三种模式之间不是互斥的团队小的时候可以用第一种起家逐渐过渡到第二种最后在业务量上来之后用第三种方式做长治久安的保障。关键是心里要有个谱现在的协同方式在小规模下够用但它不会自动适配更大体量的团队和更复杂的业务。6.3 数据驱动的协同能力评估最后一个想聊的心得是协同本身也要量化不能被“最近好像合作好多了”这种模糊感觉带过去。我们每个月会看几个关键数字平均模型上线周期、压测通过率、资源超用率、线上重大性能事件次数。这组数字能直观反映算法和infra之间的配合是否在变好而不是凭某几次印象做判断。我第一次提出这个想法时有同事开玩笑说“连协同都要上指标了是不是卷过头了”。但实际跑下来效果还挺好。因为一旦数字看得到就总有人会想办法改进数字。比如那阵子压测通过率只有60%大家分析一圈发现是模型交付的“模型说明卡”写得不完整infra拿到模型以后各种猜浪费了不少时间。后来把说明卡模板和交付工具打通压测通过率很快升了上来。如果没有数据这种问题可能永远藏在“沟通不太顺”的模糊说法里谁也看不到具体的短板在哪里。所以我的建议是如果你的团队也经常因为算法和infra的边界吵架、推诿先别急着开“加强沟通”的会不如一起定一套可量化的协同指标把问题摆到明面上。数据到位了协作自然会改善。我个人在带着团队跑完这个项目之后最大的感受就是算法和infra的协同说到底是两个知识体系之间的翻译工作。算法同学需要理解一点系统原理和工程约束infra同学也要懂一点模型推理的基本逻辑中间用标准化的接口、可量化的指标和数据化的复盘来搭桥。不要指望靠几次团建或几轮沟通就能根治协作问题真正管用的还是把这些协同点固化到流程和技术里让流程替人去记忆、去执行。这一步做到了团队的能力才算真正沉淀下来。
返回列表