网易首页 > 网易号 > 正文 申请入驻

ONNX Runtime 推理加速:8 个降低 Python 延迟的硬核技巧

0
分享至



在深度学习落地过程中,有一个常见的误区:一旦推理速度不达标,大家的第一反应往往是拿着模型开到,比如:做剪枝、搞蒸馏、甚至牺牲精度换小模型。

实际上生产环境中的 Python 推理链路隐藏着巨大的“工程红利”。很多时候你的模型本身并不慢,慢的是低效的数据搬运、混乱的线程争用以及不合理的 Runtime 默认配置。在不改变模型精度的情况下,仅靠ONNX Runtime (ORT) 的工程特性,往往就能从现有技术栈中“抠”出惊人的性能提升。

以下是 8 个经过实战验证的低延迟优化策略,专治各种“莫名其妙的慢”。

1、 明确指定 Execution Provider 及其顺序

ORT 会严格按照你传入的 providers 列表顺序进行尝试。把最快的放在第一位,并且尽量避免它静默回退(Fallback)到 CPU。如果不显式指定,ORT 有时候会“犹豫”,这都会消耗时间。

import onnxruntime as ort
providers = [
("TensorrtExecutionProvider", {"trt_fp16_enable": True}), # if supported
"CUDAExecutionProvider",
"CPUExecutionProvider",
]
sess = ort.InferenceSession("model.onnx", providers=providers)
print(sess.get_providers()) # verify what you actually got

Fallback 是有成本的,如果环境里有 TensorRT 就优先用,没有就降级到 CUDA,最后才是 CPU。把这个路径写死。另外在边缘设备上,OpenVINO 或者 CoreML 的性能通常吊打普通 CPU 推理;如果是 Windows 平台带集显DirectML 也是个容易被忽视的加速选项。

2.、像做手术一样控制线程数(不要超配)

线程配置有两个核心参数:intra-op(算子内并行)和inter-op(算子间并行)。这两个参数的设置必须参考机器的物理核心数以及你的负载特性。

import os, multiprocessing as mp, onnxruntime as ort
cores = mp.cpu_count() // 2 or 1 # conservative default
so = ort.SessionOptions()
so.intra_op_num_threads = cores
so.inter_op_num_threads = 1 # start low for consistent latency
so.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
sess = ort.InferenceSession("model.onnx", sess_options=so, providers=["CPUExecutionProvider"])

默认的线程策略经常会跟 NumPy、BLAS 库甚至你的 Web Server 抢占资源,导致严重的线程争用和长尾延迟。建议把 inter_op 设为 1(通常能获得更稳定的延迟),然后遍历测试 intra_op(从 1 到物理核数),盯着p50p95指标找最佳平衡点,不要光看平均速度。

3、使用 IO Binding 规避内存拷贝(GPU 必选项)

如果在 GPU 上跑推理,却每次 run() 都把张量从 Device 拷回 Host再拷回 Device,利用 IO Binding 将输入/输出直接绑定在显存上,复用这块内存。

import onnxruntime as ort
import numpy as np
sess = ort.InferenceSession("model.onnx", providers=["CUDAExecutionProvider"])
io = sess.io_binding()
# Example: preallocate on device via OrtValue (CUDA)
import onnxruntime as ort
x = np.random.rand(1, 3, 224, 224).astype(np.float32)
x_ort = ort.OrtValue.ortvalue_from_numpy(x, device_type="cuda", device_id=0)
io.bind_input(name=sess.get_inputs()[0].name, device_type="cuda", device_id=0, element_type=np.float32, shape=x.shape, buffer_ptr=x_ort.data_ptr())
io.bind_output(name=sess.get_outputs()[0].name, device_type="cuda", device_id=0)
sess.run_with_iobinding(io)
y_ort = io.get_outputs()[0] # still on device

这对于高频请求特别重要,哪怕单次拷贝只耗费几毫秒,累积起来也是巨大的开销,所以让热数据留在它该在的地方。

4、锁定 Shape 或采用分桶策略

动态 Shape 看起来很灵活,但它会阻碍 ORT 进行激进的算子融合和 Kernel 优选。在导出 ONNX 时能固定 Shape 就尽量固定

如果业务场景确实需要变长输入,可以采用分桶(Bucketing)策略:

# pseudo: choose session by input shape
def get_session_for_shape(h, w):
if h <= 256 and w <= 256: return sess_256
if h <= 384 and w <= 384: return sess_384
return sess_fallback

比如在视觉任务中,把输入限定在 224、256、384 这几档,创建对应的 Session。哪怕只分两三个桶,性能表现也比完全动态 Shape 强得多。

5、开启全图优化并验证

这一步很简单但容易被忽略。开启 ORT_ENABLE_ALL,让 ORT 帮你做算子融合、常量折叠和内存规划。

import onnxruntime as ort
so = ort.SessionOptions()
so.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
# optional: serialize the optimized model for inspection
so.optimized_model_filepath = "model.optimized.onnx"
sess = ort.InferenceSession("model.onnx", sess_options=so, providers=["CPUExecutionProvider"])

更少的算子意味着更少的 Kernel Launch 开销和内存带宽压力。建议导出一个 optimized_model_filepath,用 Netron 打开看看,确认 Conv+BN+ReLU 这种经典组合是不是真的被融合成一个节点了,如果没融那就是优化链路上有问题。

6、CPU 推理?直接上量化

如果只能用 CPU,INT8 量化或者动态量化是提速神器。配合 CPU 的向量指令集能极大减少矩阵乘法的开销。

from onnxruntime.quantization import quantize_dynamic, QuantType
quantize_dynamic(
model_input="model.onnx",
model_output="model.int8.onnx",
weight_type=QuantType.QInt8, # try QInt8 or QUInt8
extra_options={"MatMulConstBOnly": True}
)

然后加载量化后的模型:

import onnxruntime as ort
sess = ort.InferenceSession("model.int8.onnx", providers=["CPUExecutionProvider"])

对于 Transformer 类模型,动态量化通常能带来 1.5 到 3 倍的加速且精度损失很小。不过需要先在真实数据上验证,如果精度掉得厉害尝试 Per-channel 量化或者只量化计算最密集的算子。

7、预热、复用与 Micro-Batching

InferenceSession 的初始化开销很大,属于重资源对象。务必全局只创建一次,并且需要启动后先跑几次 Dummy Data 做预热,把 Kernel Cache 和内存池填好。

# app startup
sess = ort.InferenceSession("model.onnx", providers=["CUDAExecutionProvider"])
dummy = {sess.get_inputs()[0].name: np.zeros((1, 3, 224, 224), np.float32)}
for _ in range(3):
sess.run(None, dummy) # warms kernels, caches, memory arenas

如果是高并发场景不要一个个请求单独跑,攒一个 Micro-batch(比如 2 到 8 个样本)一起送进去,能显著提高 GPU 利用率(Occupancy)。

def infer_batch(batch):
inputs = np.stack(batch, axis=0).astype(np.float32, copy=False)
return sess.run(None, {sess.get_inputs()[0].name: inputs})[0]

调整 Batch Size 的时候,盯着p95 延迟吞吐量看,找到那个甜点。

8、优化前后处理:拒绝 Python 循环

很多时候大家抱怨模型慢,其实瓶颈在预处理和后处理。Python 的 for 循环处理像素或 logits 是绝对的性能杀手。所以保持数组内存连续,避免不必要的 astype 转换尽量全部向量化。

import numpy as np
# Bad: repeated copies/conversions
# x = np.array(img).astype(np.float32) # realloc every time
# Better: reuse buffers and normalize in-place
buf = np.empty((1, 3, 224, 224), dtype=np.float32)
def preprocess(img, out=buf):
# assume img is already CHW float32 normalized upstream
np.copyto(out, img, casting="no") # no implicit cast
return out
# Post-process with NumPy ops, not Python loops
def topk(logits, k=5):
idx = np.argpartition(logits, -k, axis=1)[:, -k:]
vals = np.take_along_axis(logits, idx, axis=1)
order = np.argsort(-vals, axis=1)
return np.take_along_axis(idx, order, axis=1), np.take_along_axis(vals, order, axis=1)

几个多余的 .astype() 就能吃掉好几毫秒,这点在低延迟场景下非常致命。

基准测试模板

这是一个简单的 Benchmarking 脚本,改改就能用,别靠感觉优化要用数据来进行对比:

import time, statistics as stats
import numpy as np, onnxruntime as ort
def bench(sess, x, iters=100, warmup=5):
name = sess.get_inputs()[0].name
for _ in range(warmup):
sess.run(None, {name: x})
times = []
for _ in range(iters):
t0 = time.perf_counter()
sess.run(None, {name: x})
times.append((time.perf_counter() - t0) * 1e3)
return {
"p50_ms": stats.median(times),
"p95_ms": sorted(times)[int(0.95 * len(times)) - 1],
"min_ms": min(times),
"max_ms": max(times)
}
# Example usage
providers = ["CUDAExecutionProvider", "CPUExecutionProvider"]
so = ort.SessionOptions(); so.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
sess = ort.InferenceSession("model.onnx", sess_options=so, providers=providers)
x = np.random.rand(1, 3, 224, 224).astype(np.float32)
print(bench(sess, x))

总结

做低延迟推理没有什么黑科技,全是细节。选对 Provider,别乱开线程,减少内存拷贝,固定 Shape,激进地做图融合,最后把 Python 代码洗干净。哪怕只落实其中两三点,性能提升也是肉眼可见的。

https://avoid.overfit.cn/post/aa489c6b429641b9b1a1a3e4a3e4ce1d

作者:Modexa

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
不摆烂了?连杀雷湖勇狼!西部搅局者出现,一群弃将打成西部绿军

不摆烂了?连杀雷湖勇狼!西部搅局者出现,一群弃将打成西部绿军

你的篮球频道
2026-01-05 13:02:36
20岁高中生上学途中被邻居恶意撞亡案一审宣判,被告人王某桂被判处死刑

20岁高中生上学途中被邻居恶意撞亡案一审宣判,被告人王某桂被判处死刑

极目新闻
2026-01-05 10:25:17
委内瑞拉代总统只扛了一天,决定对美投降?这将是全国人民的耻辱

委内瑞拉代总统只扛了一天,决定对美投降?这将是全国人民的耻辱

瑛派儿老黄
2026-01-05 15:12:00
留给大清的时间,真的不多了

留给大清的时间,真的不多了

我是历史其实挺有趣
2026-01-03 08:50:37
被拐30年儿子认亲14小时就走,全程冷脸,网友:穷家标签太刺眼

被拐30年儿子认亲14小时就走,全程冷脸,网友:穷家标签太刺眼

老特有话说
2025-12-06 17:31:27
没想到竟然这么多工作需要保密的!网友:不让看非看被一枪毙了

没想到竟然这么多工作需要保密的!网友:不让看非看被一枪毙了

另子维爱读史
2025-12-08 20:58:22
11分16板!辽宁队弃将打出顶级外援水平 球迷:杨鸣后悔吗?

11分16板!辽宁队弃将打出顶级外援水平 球迷:杨鸣后悔吗?

体育哲人
2026-01-04 20:46:54
当不成总统了?特朗普突然出手,俄英法选边站,美本土或陷入大乱

当不成总统了?特朗普突然出手,俄英法选边站,美本土或陷入大乱

绝对军评
2026-01-05 15:52:07
马琳王皓职位曝光!王励勤妥协了,教练组将官宣,秦志戬任总教练

马琳王皓职位曝光!王励勤妥协了,教练组将官宣,秦志戬任总教练

体育就你秀
2026-01-05 06:05:03
“没见过这么离谱的”!深夜零下20℃,数百游客滞留!两知名景区双双被挤爆,最新致歉→

“没见过这么离谱的”!深夜零下20℃,数百游客滞留!两知名景区双双被挤爆,最新致歉→

新民晚报
2026-01-04 14:29:18
为什么还要掳走马杜罗夫人?

为什么还要掳走马杜罗夫人?

扬子晚报
2026-01-04 22:11:21
1982年河南200枪支失窃,多年未破,一退休干警查出真相,遭灭口

1982年河南200枪支失窃,多年未破,一退休干警查出真相,遭灭口

阿胡
2024-08-31 11:55:02
心脏支架一放,人生倒计时就开始了?医生说出实话:这4点要注意

心脏支架一放,人生倒计时就开始了?医生说出实话:这4点要注意

医学原创故事会
2026-01-05 12:18:04
最新通报:河北省20人死亡重大事故···

最新通报:河北省20人死亡重大事故···

新牛城
2026-01-05 09:58:17
美女老板初心并不坏!曾送永州队10万救命钱 现花10万救自己脸面

美女老板初心并不坏!曾送永州队10万救命钱 现花10万救自己脸面

风过乡
2026-01-05 10:14:29
朱婷没想到,和76岁老公国外养老的郎平,已走上另一条“上坡路”

朱婷没想到,和76岁老公国外养老的郎平,已走上另一条“上坡路”

比利
2026-01-04 18:03:48
《人民日报》:父母经常说这些话,孩子往往内心强大、情绪稳定

《人民日报》:父母经常说这些话,孩子往往内心强大、情绪稳定

育儿读书乐
2026-01-03 13:24:01
-4℃!雨夹雪、雪又来了!浙江天气就像坐过山车,这一天升温……

-4℃!雨夹雪、雪又来了!浙江天气就像坐过山车,这一天升温……

鲁中晨报
2026-01-05 08:15:09
1000亿!比亚迪全面发力!

1000亿!比亚迪全面发力!

电动知家
2026-01-05 12:24:48
2026刚开年,人民日报再次点名张艺谋,释放2大信号,巩俐没说错

2026刚开年,人民日报再次点名张艺谋,释放2大信号,巩俐没说错

做一个合格的吃瓜群众
2026-01-05 05:56:55
2026-01-05 17:39:00
deephub incentive-icons
deephub
CV NLP和数据挖掘知识
1880文章数 1440关注度
往期回顾 全部

科技要闻

4100家科技企业集结赌城,CES揭开AI新战场

头条要闻

美对委动手致欧盟立场分裂 两元首先后发文内容南辕北辙

头条要闻

美对委动手致欧盟立场分裂 两元首先后发文内容南辕北辙

体育要闻

41岁詹皇26+10+6又迎里程碑 湖媒赞GOAT

娱乐要闻

黄宗泽夺双料视帝,泪洒颁奖台忆往昔

财经要闻

李迅雷:扩内需要把重心从"投"转向"消"

汽车要闻

海狮06EV冬季续航挑战 "电"这事比亚迪绝对玩明白了

态度原创

旅游
艺术
本地
游戏
公开课

旅游要闻

从现在至元宵节 德州推出7大主题132项文旅活动

艺术要闻

19幅 列宾美院学生优秀毕业作品

本地新闻

云游内蒙|初见呼和浩特,古今交融的北疆都会

《英雄联盟》服务器大规模中断 原因为安全证书到期?

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版