资讯动态

推理框架选型:PyTorch/ONNX/TensorRT/OpenVINO对比

发布时间:2026/10/20 8:48:08 来源:尧图企业网站定制
搞AI模型部署第一步绕不开的就是推理框架选型。最近我把PyTorch原生推理、ONNX Runtime、TensorRT、OpenVINO这四套方案放在同一个模型上做了横向性能对比过程中踩了不少坑也整理出一套相对可复用的测试方法。这篇文章适合正在做模型部署、或者正在为上线选型犹豫的同学尤其适合刚接触推理优化的人读完之后你至少知道该从哪些维度对比框架也明白为什么不能简单说“谁谁谁最快”。1. 为什么推理框架的性能对比总是说不清1.1 训练和推理是两套完全不同的逻辑训练阶段我们看重的是模型能不能收敛、梯度传播是否灵活、网络结构是不是方便修改所以更依赖PyTorch这类动态图框架随时可以打印中间结果也可以按条件改变计算路径。但推理阶段的核心诉求完全不同它要的是低延迟、高吞吐、确定性的计算过程以及尽量小的内存占用。训练时那些方便的动态特性在推理时反而成了性能包袱。这就是为什么拿PyTorch直接做线上推理往往会发现CPU占用高、GPU利用率不稳定、延迟波动大。不是说PyTorch不好而是它本来就不是为极致推理优化设计的。推理框架做的核心事情是把模型先固化成一个相对静态的计算图然后在这个静态图上做算子融合、内存复用、常量折叠、指令集优化甚至把浮点精度从FP32压到FP16或者INT8。这些优化手段在训练框架里很难放开手脚去做因为训练还要求随时能回传梯度计算图也不能随便改动。1.2 “哪个框架最快”本身是个伪命题我经常遇到有人问“ONNX Runtime和TensorRT哪个快”这种问题我通常不好直接回答。因为“快”这个词脱离具体条件谈没有意义。同一个模型在CPU上可能OpenVINO优势明显换到GPU上又可能是TensorRT一枝独秀输入分辨率从640改成1280算子布局变了排名可能又会变化模型里如果有自定义算子某个框架可能直接不支持只能回退到很慢的默认实现。更麻烦的是框架之间做性能对比时测法不同结论也可能完全相反。用固定batch size连续跑1000次和模拟真实线上请求那种忽高忽低的输入测出来的延迟分布完全不是一回事。用单线程跑还是多线程跑开不开动态shape用不用FP16预热多少轮测出来的数据都可能有显著差异。所以与其纠结谁最快不如建立一套能复用的测试方法论把“哪个框架更适合我的场景”这个问题变成一组可以量化的数据。2. 主流推理框架的定位差异2.1 四个框架分别擅长什么PyTorch原生推理最大的价值是作为基线。它能保证模型的数值逻辑和训练时一致算子支持最全动态shape完全不受限制。缺点是性能通常不是最优而且每次加载模型都要重新构建计算图启动开销比较大。适合小流量验证、临时服务、或者在线调试时用。ONNX Runtime属于跨平台中间层方案。它吃的是ONNX格式很多训练框架都能导出到这个格式。这个框架的优势是算子覆盖广、支持CPU和GPU、部署集成简单踩坑概率相对低。性能上它在CPU上有明显优化在GPU上也能吃到一些通用优化但跟硬件针对性极强的加速库比还是有差距。TensorRT是面向特定GPU硬件的垂直优化方案。它会把模型编译成engine文件这个过程会针对具体显卡架构、显存带宽、算子执行方式做深度调优。性能极佳代价是构建engine时间较长、算子支持范围有限遇到不支持的算子就只能做等价替换或者曲线救国。OpenVINO则更侧重于CPU和边缘设备它对x86指令集的利用非常充分也支持部分GPU。部署包体积不小但推理性能确实可以尤其是在Intel平台CPU上性价比很高。如果项目主要跑在x86 CPU上它往往是最省事的提速方案。2.2 参数维度对比一表看懂差异框架输入格式计算图类型量化支持动态shape部署复杂度典型优势PyTorch原生pt/pkl动态有限完全支持低调试灵活生态完整ONNX Runtimeonnx静态动态轴FP16/INT8需配置中跨平台算子覆盖广TensorRTonnx→engine静态FP16/INT8需指定范围高GPU上性能极致OpenVINOIR(xml/bin)/onnx静态FP16/INT8部分支持中CPU与边缘设备优化好这张表只是粗略定位不能当作绝对标准。实际选型时还要看具体模型里用什么算子、目标服务器是什么架构、线上流量是否波动、是否需要动态batch这些因素往往比框架本身的基准性能更影响最终效果。2.3 框架选型的底层逻辑我的经验是先不要急着比较性能而是先确认这个框架能不能完整跑通你的模型。一个再快的框架如果模型里有几个关键算子不支持最后只能靠CPU回退那就没什么意义了。所以选型第一步是看算子兼容性第二步才是性能。如果项目要部署到多种硬件上比如有CPU服务器也有GPU服务器未来还可能迁移到其他平台那ONNX Runtime作为统一中间层更合理。如果硬件平台长期固定GPU为主且性能压力大TensorRT值得投入。如果主要跑CPUWindows或Linux都有OpenVINO通常能拿到很不错的收益。PyTorch原生则一定要保留一个版本它是最好的调试对照物任何框架跑出的异常结果最后都回到它身上验证。3. 一套可复用的推理性能测试方法3.1 测试前的三项准备我每次做框架对比都会先把这三件事搞定省得后面反复返工。第一把模型导出成稳定的中间表示。不管是torch.jit trace还是onnx导出都要保证计算图固定下来不能每次加载都重新走一遍动态图构建。这一步很重要否则测出来的启动时间和首帧延迟完全失真。第二确定输入分辨率和维度范围。固定分辨率最省事儿但如果线上会有多种输入尺寸必须提前测动态shape支持。TensorRT对动态shape的处理方式是在构建engine时指定允许的范围比如宽高从512到1288而ONNX Runtime是设置动态轴两种方式的行为不一样要分开测。第三锁定精度基线。比如把PyTorch原生FP32的输出结果存下来作为精度参照。然后在每个框架上都用相同输入跑一遍对比输出差异。只有精度差异在可接受范围内后面对比延迟和吞吐才有意义。3.2 要测哪些指标才够用只看平均延迟是远远不够的。线上服务的核心指标是P95、P99延迟因为平均延迟很容易被少数慢请求拉高但满足不了大部分用户的真实体感。P99能反映出在峰值流量下系统最差的那些体验是否还扛得住这点对实时交互类服务尤其关键。吞吐量也要分场景看。一种是一个batch只跑一条样本模拟单请求连续到达一种是固定batch size比如batch8、16、32模拟服务端把多个请求拼在一起推理。后者往往能显著提高GPU利用率但也会带来排队延迟所以要在吞吐和P99之间做一个取舍。内存占用同样不能忽略。我遇到过模型跑得好好的但显存一直在涨最后OOM的情况。每个框架对内存池的管理策略都不一样TensorRT一般会把显存一次性申请到位看着占用高但稳定有些框架则是按需申请虽然峰值低但可能出现碎片化。这些都要在测试里观察。3.3 一个简单的计时脚本示例下面这个脚本不是任何一个框架的完整调用代码而是一个通用的计时框架你可以分别往fn里塞入ONNX Runtime、TensorRT、OpenVINO的推理函数统一用同一套逻辑测延迟分布import time def measure_latency(fn, input_data, warmup20, repeat100): # 先预热避免把第一次初始化的耗时算进去 for _ in range(warmup): fn(input_data) timing [] for _ in range(repeat): t0 time.perf_counter() fn(input_data) t1 time.perf_counter() timing.append((t1 - t0) * 1000) # 转换为毫秒 timing.sort() return { avg_ms: sum(timing) / len(timing), p50_ms: timing[int(len(timing) * 0.50)], p90_ms: timing[int(len(timing) * 0.90)], p95_ms: timing[int(len(timing) * 0.95)], p99_ms: timing[int(len(timing) * 0.99)], }用time.perf_counter而不用time.time是因为前者精度更高适合微秒毫秒级别的测量。预热20次是我常用的值如果模型比较大可以提高到50次。还有一点要注意很多框架会在前几次推理时触发懒加载或者缓存分配所以预热轮数宁可多不要少。3.4 测试时的变量控制框架对比最怕的就是变量没控制好测了半天不知道差异来自框架还是环境。首先是硬件环境要固定CPU就固定使用同一个物理核集合GPU就锁定同一块卡别让其他进程抢资源。其次线程数要显式设置比如ONNX Runtime的session_options.intra_op_num_threadsOpenVINO的num_streamsTensorRT的streams这些参数对性能影响很大。还有一个隐形坑是CPU降频和温度控制。连续长时间跑高负载推理CPU温度上来之后主频会下降导致后面测试的数据越来越难看。我一般把测试轮次拆成多段跑一段休息一下或者直接观察CPU频率确认没有明显降频再记录数据。GPU也有类似问题长时间满载后Boost频率会回落所以最好在冷启动和稳定运行两个状态各测一轮取代表性数据。4. 实际对比数据与解读方法4.1 这次测试的模型和环境我用的是一个自己搭的双分支目标检测模型姑且称为模拟项目X。模型结构不算复杂主体是卷积加特征金字塔最后有分类和回归两个分支。输入固定为1x3x640x640测试CPU和GPU两种环境。CPU环境是某款8核16线程的x86服务器CPUGPU环境是一块8GB显存的CUDA显卡。软件上PyTorch用2.x版本ONNX Runtime 1.xTensorRT 8.xOpenVINO 2023版。量化对比时FP16和INT8都用了各框架的官方校准接口校准数据是从验证集里抽的200张图。这里必须强调下面这组数据只代表当时的特定环境换一张卡、换一个输入尺寸绝对数字和相对趋势都会变不建议直接拿来做你项目的选型依据。但解读方法是可以复用的。4.2 一组相对测试结果表方案CPU平均延迟(ms)GPU平均延迟(ms)说明PyTorch原生118.68.2作为基线数值未优化ONNX Runtime72.35.6CPU提升约39%GPU提升约32%OpenVINO46.8不适用CPU提升约60%未启用GPU后端TensorRT不适用2.8GPU提升约66%FP16下进一步下降另一个我有印象的数据是GPU上单流吞吐。PyTorch原生大约每秒122帧ONNX Runtime能到178帧TensorRT能到290帧左右。把batch size从1调到8之后TensorRT的吞吐能进一步翻倍但这需要模型本身支持动态batch并且后处理逻辑要跟上才行。4.3 为什么TensorRT能在GPU上做到这么低TensorRT能把858毫秒的延时做到2.8毫秒靠的不是某一个单一优化而是很多优化叠加在一起。它会做算子融合把一个卷积后面的激活函数、批归一化直接并进卷积计算里减少显存读写会选择更合适的kernel实现针对不同的卷积尺寸和显卡架构挑最快的算法甚至把某些小算子合并成自定义kernel还会在构建engine时对显存重复使用减少分配释放的损耗。这也解释了为什么TensorRT的构建时间很长。它本质上是在做编译期搜索要为每个算子选kernel、试不同tactic、做精度验证这个过程占用的时间远大于一次推理。如果你每次启动都要重新构建engine那启动延迟可能比推理本身还大生产环境一般都会把构建好的engine序列化到磁盘加载时直接反序列化。4.4 量化收益与精度损失在同一块GPU上把TensorRT的engine从FP32切到FP16后延迟通常还会再降30%到40%精度损失在我这个检测模型上几乎可以忽略mAP掉点在0.2%以内。切INT8就更敏感了虽然延迟还能再降一些但如果不做校准直接拿FP32的权重强制转INT8掉点可能超过3%甚至出现个别类别直接失效的情况。做INT8校准的关键是校准数据要覆盖真实分布。如果校准集是从训练集里随机抽的但线上输入光照、噪声、目标大小都跟训练集差异很大那量化后精度会很难看。我习惯的做法是先用验证集跑一遍统计中间特征值的分布范围再选一批覆盖极值的样本做校准。精度掉点超过1%时优先检查量化敏感层这部分可以配合TensorRT的per-layer统计报表来看。5. 部署选型建议与优化路线5.1 按场景选框架不用追求最强匹配就行如果做的是低延迟在线推理比如交互式应用、辅助驾驶、实时音视频处理GPU环境下TensorRT是优先级最高的选项。它把单帧延迟压得很低且P99相对稳定但前提是你接受较长的离线构建时间和偏高的运维复杂度。如果是高吞吐离线任务比如批量审核、批量打标、离线特征抽取那动态batch比框架本身更重要。TensorRT配合动态batch能吃到很大的显存复用收益ONNX Runtime配合外部动态batching也有不错表现。这类场景对单帧P99要求不高反而要关注单卡每秒能处理多少样本。如果是边缘侧或者纯CPU环境OpenVINO和ONNX Runtime更合适。OpenVINO对x86指令集优化明显ONNX Runtime在跨平台和算子支持上更稳妥。移动端的话还要单独看TFLite和CoreML那些生态但不在这次对比范围内。5.2 框架以外的通用优化别把顺序搞反我见过很多同学一上来就研究怎么把延迟从5毫秒压到4毫秒却忽略了输入预处理和后处理时间可能比推理本身还长。推理框架对比永远要先测端到端延迟把预处理、推理、后处理三段拆开分别计时才知道瓶颈到底在哪。预处理阶段常见的优化包括把图像解码从CPU转到GPU、用内存池复用中间tensor、减少cvtColor等重复转换。后处理阶段常见的优化包括把阈值筛选、NMS改到GPU上做或者用更轻量的一次性排序替代多重循环。这些优化对整体效果的影响经常比换一个推理框架更明显。另一个容易忽略的点是任务本身的时间预算。如果线上要求单帧处理不能超过10毫秒而模型已经占了8.5毫秒那与其纠结框架不如先考虑降低输入分辨率、减少特征金字塔层数、替换掉重算子的组合这些模型侧改动。框架只能让你在当前模型结构下跑得更快但模型结构的冗余是它消除不了的。5.3 性能瓶颈排查的标准步骤用框架自带的profiler是必须的。ONNX Runtime可以打开verbose日志按算子统计耗时TensorRT有nvidia的profiling工具OpenVINO也有benchmark_app和per-layer耗时输出。拿到这些数据后先找耗时最高的几个算子再逐个确认有没有被融合、有没有回退到CPU、有没有频繁申请内存。如果发现GPU利用率低先看数据搬运是不是瓶颈。很多模型算得快但输入图像每次用cpu.numpy()拷贝到GPU再等结果拷回来这部分显存传输会严重拉低有效吞吐。解决办法是把预处理统一放在GPU上用GPU tensor直接作为模型输入。如果发现CPU占用高且不需要GPU那就要看线程数设了多少。ONNX Runtime默认线程数可能跟物理核数不匹配OpenVINO的stream参数也要根据实际并发请求数调整。线程开太狠会导致上下文切换开销大于计算收益开太少则吃不满多核这都需要快速试验。6. 常见问题与排查技巧实录6.1 常见问题速查表现象可能原因解决思路首帧延迟特别高engine初始化、缓存未生效、懒加载预热数次再计时生产环境常驻模型TensorRT构建an出来的engine反复失效显卡型号、驱动版本、计算能力不一致在目标机器上构建engine不要跨机器复制动态shape输入时延迟剧烈波动shape变化触发重新选kernel或重构固定常见尺寸或单独测试每种尺寸ONNX Runtime精度和PyTorch不一致算子精度设置、自动混合精度对比逐层输出检查是否开启了FP16OpenVINO在部分机器上报错指令集不兼容换兼容模式或更新版本打印详细日志GPU利用率低但CPU很高数据搬运频繁、算子不支持GPU在GPU上做预处理检查算子回退这张表里的每一条我几乎都在模拟项目X的测试过程中遇到过不是凭空写的。遇到问题时第一反应应该是复现最小场景而不是对着复杂模型瞎猜。6.2 我踩过的几个真坑第一个坑是TensorRT的动态shape范围给得太宽。我一开始把输入尺寸范围设成从320到1920结果构建出来的engine体积大了一倍而且某些shape组合下延迟变得异常高。后来把线上真实使用到的尺寸统计了一遍只保留几个档位engine小了延迟也更稳定。第二个坑是量化后的精度差异不能用单张图判断。最开始我用一张图对比发现TensorRT INT8的结果和PyTorch FP32差异很小就放过了。结果在验证集上整体跑一遍发现某些小目标漏检率提升了不少。现在我对任何量化结果都至少跑完整验证集做统计对比绝对不看单张图的目测效果。第三个坑是测试时用了同一份输入tensor但框架内部会修改数据。比如ONNX Runtime如果设置了合适的配置可能会复用输入buffer导致第二次推理输入的其实是上一轮输出做出来的脏数据。我的习惯是每个框架测试时都重新生成一份输入或者每次循环深拷贝一份保证输入一致。第四个坑显存泄漏比较隐蔽。我测试那个检测模型时TensorRT用了流式处理但后处理逻辑里对GPU tensor反复做cpu()转换导致显存每次分配不释放。后来改成显存复用池问题才解决。这种问题靠延迟数据看不出来只有持续监控显存占用才会发现。6.3 有价值的几点心得就我的使用体验来说ONNX Runtime是验证模型导出正确性的利器。不管最终部署用什么样的引擎我都会先用ONNX Runtime跑一遍因为它的算子兼容性广且安装简单如果这一步出来的结果和PyTorch不一致大概率是导出过程出了问题不用急着怀疑部署框架。性能测试一定要闭环。每一次改动无论是换了框架版本、加了量化、改了batch size都要重新完整跑一遍流程不能只盯着某个延迟数字看。我常用的做法是把测试脚本固定下来输出一个标准报告包含P50、P95、P99、平均延迟、吞吐、显存峰值和精度差异然后不同方案之间只做这一个报告格式的横向对比。最后再说一点框架版本的差异也值得留意。ONNX Runtime小版本更新经常带来算子IR变化TensorRT版本不同则生成的engine不兼容升级时一定要重新验证模型输出。我一般会锁定一个稳定版本组合在项目周期内不做无谓升级除非遇到算子缺失或bug这样能少踩很多坑。多提一个实测中节省调试时间的小技巧给每个部署方案都保留一个“黄金快照”包含模型文件、配置文件、测试脚本和当时的性能报告。这样无论过了多久还是换了人来维护都能快速复现当时的结论尤其是当模型结构更新后想判断是否要升级框架时这个黄金快照能省下大量重复测试的时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑