ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

昇腾推理引擎实战:从模型转换到高性能部署的完整链路与避坑指南

昇腾推理引擎实战:从模型转换到高性能部署的完整链路与避坑指南 1. 昇腾推理引擎到底在解决什么问题第一次接触昇腾推理引擎的人最容易犯的一个错误就是把它当成又一个跑大模型的工具。这个理解不能说错但太浅了。要真正搞明白它为什么值得单独拿出来聊得先回到一个更朴素的问题模型训练完之后怎么让它在一台或者一堆机器上又快又稳又省地跑起来。训练和推理是两件完全不同的事。训练追求的是把参数调好算力可以堆时间可以等显存不够就上并行。推理追求的东西完全变了——延迟要低、吞吐要高、成本要可控、并发要扛得住、精度还不能掉。这就导致训练框架里那套东西直接搬到推理场景往往水土不服。昇腾开源推理引擎本质上就是华为围绕自家昇腾AI处理器专门为推理这个阶段做的一整套软件栈它要解决的核心矛盾是如何让模型在昇腾硬件上以尽可能低的资源开销跑出尽可能高的有效吞吐。这里的关键词是有效吞吐。很多人看推理性能只看单次前向耗时这个指标在真实业务里意义有限。真实场景是几十上百个请求同时打进来引擎要做批处理调度、要做显存复用、要做算子融合、要做量化压缩。昇腾推理引擎的价值恰恰体现在这些脏活累活上。它不是一个单点工具而是一套从模型转换、图优化、算子调度到运行时执行的完整链路。那它适合谁我梳理下来大概是三类人。第一类是手里有昇腾硬件、需要把训练好的模型部署上线的算法工程师他们最关心的是我的模型能不能跑、跑多快、精度掉多少。第二类是做AI基础设施的平台工程师他们关心的是怎么把推理服务封装成稳定可靠的在线服务怎么管理多模型、多版本、多卡。第三类是做端侧和边缘侧部署的开发者昇腾在边缘场景也有对应的硬件和推理方案这类场景对功耗、体积、启动速度的要求和云端完全不同。需要说明的是下面涉及的一些具体操作细节和参数是基于我在实际部署中积累的常见实践总结出来的不同版本的工具链在命令和接口上会有差异具体以你手上那套工具链的官方文档为准。但底层的思路和踩坑点是相通的这也是我觉得值得写下来的原因。2. 从训练产物到推理服务昇腾推理链路的完整拆解2.1 模型转换这一步为什么最容易出问题训练出来的模型通常是以PyTorch的.pt或者.pth格式存在的。昇腾推理引擎不能直接吃这个格式中间必须经过一次模型转换把训练框架的图转成昇腾能识别的中间表示。这一步是整个链路里最容易翻车的地方我见过太多人卡在这里。转换出问题的根因往往不是工具本身有bug而是训练图里包含了推理不需要、但转换器无法处理的东西。比如训练时为了调试加的打印节点、动态控制流、自定义的Python算子、还有一些框架特有的算子实现。转换器面对这些节点时要么直接报错要么静默地转成一个性能极差的fallback实现。我的经验是在转换之前先做一次图清理。具体做法是把模型切到eval模式用torch.jit.trace或者torch.jit.script先导出一次看看有没有警告。如果trace过程中出现大量warning说明模型里有动态行为这时候要么改成静态图要么把动态部分拆出来单独处理。另外一个实用技巧是先用一个极小的输入尺寸比如1x3x32x32跑一遍转换确认链路通了再换成真实输入尺寸。这样能把转换器不支持某算子和输入尺寸不匹配这两类问题分开定位。转换完成后通常会得到一个.om格式的离线模型文件。这个文件是高度硬件相关的换一个昇腾型号可能就要重新转。所以工程上一般会把转换脚本纳入版本管理而不是把.om文件本身当成交付物。这一点在多人协作时特别重要否则你永远不知道线上那个.om到底是用哪版代码转出来的。2.2 图优化在转换阶段做了什么很多人以为模型转换只是格式翻译其实转换阶段同时完成了一次相当激进的图优化。这部分优化对最终性能的影响可能比你在运行时调参还大。昇腾的图优化大致包含几个层次。最基础的是算子融合把连续的、可以合并的算子合成一个。比如卷积后面跟BN再跟激活训练时是三个独立算子推理时BN的参数可以折叠进卷积权重激活可以融进卷积的epilogue最后只剩一个算子。这一下就省掉了两次显存读写而显存带宽在推理场景里往往才是真正的瓶颈。再往上是常量折叠和死代码消除。训练图里有些计算只依赖常量推理时结果固定直接算好写进模型。还有一些分支在推理时永远不会走到直接删掉。这些优化听起来简单但在大模型上能省下的计算量相当可观。还有一层是内存复用规划。推理时显存是有限的引擎会分析整个计算图里每个张量的生命周期把生命周期不重叠的张量安排到同一块显存上。这个规划做得好不好直接决定了你能跑多大的batch。我实测过一个视觉模型优化前后能跑的最大batch差了将近一倍原因就是中间激活值的内存复用没做好。这里有个坑要提醒图优化是黑盒的你很难精确控制它做了什么。如果优化后精度掉了排查起来会很痛苦。我的做法是保留一个关闭大部分优化的转换配置作为对照一旦精度异常先用这个配置转一版确认是优化引入的问题还是模型本身的问题再逐步打开优化定位。2.3 运行时调度批处理才是吞吐的关键模型转好了接下来是运行时。昇腾推理引擎的运行时核心能力之一是动态批处理。这个概念值得展开讲因为它是理解推理引擎价值的关键。假设你的服务每秒收到100个请求每个请求的输入大小不一。如果来一个算一个GPU或者NPU的利用率会非常低因为单次前向的计算量太小大部分时间花在启动kernel和读写显存上。动态批处理的做法是把短时间内到达的多个请求攒成一个batch一次性送进模型计算算完再拆开返回。这样单次计算的有效算力占比大幅提升整体吞吐能翻好几倍。但批处理不是越大越好。batch越大单个请求的等待时间越长延迟就上去了。所以引擎需要在一个攒批窗口内做权衡。这个窗口通常以毫秒计窗口越大吞吐越高但延迟越大。实际调参时我一般会先测出不同batch下的单次耗时曲线找到那个边际收益开始明显下降的拐点把窗口设成能稳定攒到那个batch的大小。还有一个容易被忽略的点是padding浪费。如果batch里各个请求的序列长度差异很大为了对齐就得paddingpadding的部分也在参与计算纯属浪费。好的推理引擎会做分桶bucketing把长度相近的请求分到一组减少padding比例。这个机制在NLP类模型上效果尤其明显我见过分桶前后吞吐差出百分之三四十的情况。2.4 量化精度和性能之间的那根线推理场景绕不开量化。简单说量化就是把模型权重和激活值从高精度浮点比如FP16压到低精度整数比如INT8好处是显存占用减半、计算速度提升、带宽压力下降。代价是精度可能掉。昇腾推理引擎支持训练后量化PTQ和量化感知训练QAT两条路。PTQ不需要重新训练拿现成模型校准一下就能转工程上最省事。QAT需要在训练阶段就模拟量化误差精度更有保障但要有训练资源和数据。我个人的经验是先上PTQ精度不够再考虑QAT。PTQ的关键在校准数据的选取校准集不需要很大几百张有代表性的样本就够但一定要覆盖真实业务里的各种输入分布。我踩过一次坑校准集全用的白天场景图片结果模型上线后夜间场景精度崩了。后来把校准集按场景分层采样问题就解决了。量化还有一个隐蔽的坑是逐层敏感度差异。同一个模型里有些层对量化极其敏感一量化精度就掉有些层则完全无所谓。进阶做法是做逐层敏感度分析把敏感层保持高精度其余层量化这样能在精度和性能之间找到更好的平衡点。这个分析过程有点繁琐但对精度要求高的业务值得做。3. 实际部署中那些文档不会告诉你的坑3.1 显存不是唯一瓶颈带宽才是隐形杀手新手调推理性能眼睛往往只盯着显存占用。显存确实重要但真正卡住性能的很多时候是显存带宽。打个比方显存容量像仓库面积带宽像仓库门口的通道宽度。你仓库再大如果通道窄货物进出速度就上不去。推理时每个算子都要把输入读进来、结果写出去这些读写全走带宽。算子融合之所以有效本质就是减少了读写次数让数据在片上多待一会儿。怎么判断是不是带宽瓶颈一个简单的办法是看算力利用率。如果NPU的计算单元利用率很低但显存带宽跑满了那基本就是带宽瓶颈。这时候优化方向不是换更大显存的卡而是减少数据搬运——做算子融合、提高数据复用、用更低精度的数据类型。我在一个视频理解模型上遇到过这个情况。模型本身不大显存绰绰有余但吞吐死活上不去。后来分析发现是中间特征图太大每个算子都在搬运大量数据。把几个连续算子融合、并且把中间特征用INT8存储后吞吐直接翻倍。这个教训让我明白推理优化要先看数据流再看计算量。3.2 多卡并行的通信开销可能吃掉全部收益单卡跑不动就上多卡这是本能反应。但推理场景的多卡并行和训练场景完全不是一回事坑特别多。训练时多卡并行是为了把大模型塞进去通信可以重叠在计算里慢一点无所谓。推理时多卡是为了提高吞吐如果通信开销太大多卡带来的收益可能被完全抵消甚至出现卡越多越慢的诡异现象。昇腾推理引擎支持多种并行策略数据并行、模型并行、流水线并行都有。选择哪种取决于你的瓶颈在哪。如果是吞吐不够但单卡能装下模型用数据并行每张卡跑完整模型请求分发到不同卡上。如果是模型太大单卡装不下才考虑模型并行把模型切开放到多卡上但这样每层计算都要跨卡通信延迟会明显上升。我的建议是能用数据并行就别用模型并行。数据并行的通信只发生在请求分发和结果汇总量很小。模型并行的通信发生在每一层量大且频繁。除非模型实在装不下否则数据并行是更稳的选择。另外多卡部署时一定要实测端到端吞吐不要只看单卡性能乘以卡数那个数字往往过于乐观。3.3 模型热更新别让服务重启变成常态线上服务最怕的就是重启。模型迭代频繁的业务如果每次更新模型都要重启服务那可用性根本没法保证。昇腾推理引擎在这方面提供了一些机制但用好需要设计。核心思路是模型加载和请求处理解耦。服务启动时先加载一个模型处理请求的同时后台可以加载新模型。新模型加载完成后通过一个原子操作切换指针新请求走新模型老请求继续用老模型直到处理完。这样切换过程对用户完全无感。实现上要注意两点。一是显存管理新旧模型同时存在时显存占用会翻倍要确保显存够用或者设计成先卸载老模型再加载新模型但这样会有短暂的服务空窗。二是状态一致性如果模型带状态比如KV cache切换时要确保状态不会串。我一般会在切换时给新模型打一个版本号请求里带上版本号方便排查问题。3.4 精度对齐转换前后到底差多少才算正常模型转换和量化之后精度必然会变。问题是变多少算正常这个没有统一标准取决于业务对精度的容忍度。我的做法是建立一个精度基线对比流程。用同一批测试数据分别在原始训练框架和昇腾推理引擎上跑一遍逐样本对比输出差异。对于分类模型看top-1和top-5的一致率对于检测模型看mAP的变化对于生成模型看BLEU或者人工评估。关键是要区分系统性偏差和随机波动。如果所有样本的输出都朝一个方向偏那可能是转换或量化引入了系统性误差需要排查。如果只是个别样本有差异且差异在合理范围内那通常是正常的数值精度损失。我一般会设一个阈值比如分类一致率低于99%就要警惕低于95%就必须排查。但这个阈值因业务而异医疗、金融这类场景要求会高得多。重要的是把这个对比流程固化下来每次模型更新都跑一遍而不是等线上出问题了才回头查。4. 把推理引擎用好的几个进阶思路4.1 算子定制当内置算子不够用时昇腾推理引擎内置了大量常用算子覆盖了主流模型结构。但总有一些场景内置算子满足不了——可能是某个新出的算子还没支持可能是某个算子的实现性能不理想也可能是业务里有特殊的计算逻辑。这时候就需要自定义算子。昇腾提供了算子开发框架可以用类C的语言写算子实现编译成昇腾能执行的二进制。自定义算子的门槛不低需要理解昇腾的硬件架构、内存层次、并行模型。但一旦写好性能往往能比fallback实现好一个数量级。我的建议是先穷尽内置算子的组合可能性再考虑自定义。很多看似需要自定义算子的需求其实用几个内置算子拼一下就能实现虽然多了一两次数据搬运但开发成本低得多。只有当这个算子确实是性能热点且内置方案怎么都优化不上去时才值得投入做自定义。4.2 服务化封装从能跑到好用之间的距离模型能在昇腾上跑起来只是第一步。要变成线上服务中间还有大量工程工作。这部分往往被算法工程师忽略但恰恰是决定项目成败的关键。服务化要考虑的东西很多请求的接入协议、并发模型、超时处理、限流降级、监控告警、日志追踪。昇腾推理引擎本身聚焦在推理执行这些服务治理能力需要你自己搭或者用现成的框架。我比较推荐的做法是把推理引擎封装成一个独立的推理服务进程前面用成熟的服务框架比如各种RPC框架或者HTTP框架做接入和治理。推理进程只负责给我一批输入我还你一批输出不掺和业务逻辑。这样职责清晰推理进程可以独立扩缩容也方便做性能压测。监控这块特别重要。至少要监控几个指标请求量、延迟分布P50/P95/P99、批处理大小分布、显存占用、NPU利用率。延迟的P99比平均值重要得多因为用户体验由最慢的那部分请求决定。批处理大小分布能帮你判断攒批窗口设得合不合理。4.3 性能压测怎么测才能反映真实情况压测是推理优化的眼睛但很多人的压测方法是错的测出来的数字没有参考价值。最常见的错误是用固定输入压测。真实业务的输入长度、内容分布是变化的固定输入会让批处理和缓存机制失去意义测出来的吞吐虚高。正确的做法是准备一个能反映真实分布的测试集压测时随机采样。第二个错误是忽略预热。NPU和推理引擎都有预热过程第一次执行某个算子会慢很多。压测必须包含预热阶段等性能稳定后再开始统计。第三个错误是只看吞吐不看延迟。高吞吐和低延迟往往矛盾压测要同时记录两个维度的数据画出吞吐-延迟曲线才能找到适合业务的平衡点。我一般会做三组压测低并发看延迟、中并发看吞吐、高并发看稳定性。高并发下如果延迟急剧上升或者出现错误说明系统有瓶颈或者保护机制没做好这比单纯看峰值吞吐更有价值。4.4 版本管理工具链升级是把双刃剑昇腾的工具链迭代很快新版本通常带来性能提升和新特性但也可能引入不兼容的变化。我经历过一次工具链升级后原本跑得好好的模型精度掉了排查了两天才发现是新版本的某个图优化策略变了。所以我的原则是生产环境不追新测试环境充分验证。新版本先在测试环境跑完整的精度对比和性能压测确认没有回归再上生产。同时工具链版本要和模型文件、转换脚本一起做版本管理确保任何一个时间点的产物都能复现。另外升级前一定要看官方的release note重点关注不兼容变更和已知问题两部分。很多坑官方其实写了只是没人看。我现在的习惯是升级前把release note打印出来逐条过一遍标出可能影响我的点升级后重点验证这些点。5. 关于开源这件事的一些实际体会昇腾推理引擎选择开源这个决定本身值得聊几句。对使用者来说开源最直接的好处是可查、可改、可反馈。遇到问题能看源码定位而不是对着黑盒干瞪眼。有些性能问题看源码里算子的实现方式比看文档猜半天有用得多。但开源也意味着你要自己承担更多。商业软件出问题可以提工单等支持开源项目更多靠社区和自己。我的经验是用开源推理引擎团队里最好有一两个人愿意深入源码不一定要能改但至少能看懂关键路径。这样遇到问题时排查效率完全不一样。参与社区也是有回报的。我提交过几个算子性能相关的issue维护者的响应还挺及时。有时候你遇到的问题别人也遇到了社区里搜一下能省很多时间。提交issue时把复现步骤、环境信息、日志写清楚能大大提高被处理的概率。还有一点开源项目的文档和实际代码之间可能有滞后。文档说支持的特性代码里可能还没实现代码里有的参数文档可能没写。所以我的习惯是文档看个大概具体以代码为准。关键接口直接翻源码确认比信文档靠谱。6. 我在实际项目里踩过的几个具体坑说几个印象深刻的。有一次部署一个OCR模型转换后精度怎么都对不齐排查了很久发现是图像预处理的问题。训练时用的是某种归一化方式推理时我按常规做法处理结果输入分布对不上。这个坑的教训是预处理和后处理必须和训练时严格一致不能想当然。还有一次是批处理导致的精度问题。开了动态批处理后某些请求的结果和单条跑不一致。查下来是模型里有依赖batch内统计量的操作类似BN批处理时统计量变了。解决办法是把这类操作在推理时固定成训练时的统计量或者干脆在转换时把它们折叠掉。再有一次是显存碎片问题。服务跑了一段时间后显存占用越来越高最后OOM。原因是不同batch大小的请求交替到来显存分配器产生了碎片。解决办法是限制batch大小的种类做分桶让显存分配模式更规整。这个问题很隐蔽因为刚启动时完全正常跑久了才暴露。最后一个坑是关于超时的。推理服务设了超时但超时后请求被丢弃NPU上的计算还在继续白白浪费算力。后来改成超时后不立即丢弃而是等当前batch算完再统一处理虽然延迟数字上去了但整体吞吐反而更稳。这个取舍要看业务对延迟极度敏感的场景可能不适用。这些坑的共同点是它们都不在文档里都是实际跑起来才会遇到的。所以我一直觉得推理引擎这东西看十篇文档不如自己完整部署一个模型走一遍。走一遍下来该踩的坑基本都踩到了后面就顺了。
返回列表