You need to enable JavaScript to run this app. Python多线程运行ONNX模型并行推理比串行慢的原因与解决方法
优惠活动
大模型
产品
解决方案
定价
更多

Python多线程运行ONNX模型并行推理比串行慢的原因与解决方法

线程并行比串行更慢是完全正常的现象

这是Python生态下运行ONNX模型的典型场景,并非代码逻辑错误,核心原因有三点:

  • Python全局解释器锁(GIL)的限制:纯Python实现的逻辑无法跨线程实现真并行,同一时刻仅一个线程能持有GIL执行Python字节码,锁的来回切换本身会带来额外开销。
  • ONNX Runtime的默认配置和并行预期不匹配:默认初始化的每个InferenceSession会自动占用所有可用CPU核心,通过内部多线程加速单模型推理。两个模型同时触发推理时,内部的工作线程会争抢CPU核心、内存带宽,操作系统的线程调度成本会盖过并行的收益,最终导致总耗时比串行更长。
  • 原有并行逻辑的设计问题:把纯Python实现的tokenizer预处理放到子线程里并行,这部分逻辑必须抢GIL才能运行,根本没法真正并行,反而加剧了锁竞争的成本。
适配你需求的改造方案

你需要保留“类初始化时加载两个模型、输入触发后并行推理”的逻辑,可按以下步骤调整:

1. 调整ONNX Runtime会话配置,消除资源争抢

初始化推理会话时,通过SessionOptions给每个模型分配固定的、不重叠的CPU核心数,避免两个会话无限制抢占所有核心:

import onnxruntime as ort

# 自定义会话运行配置
sess_options = ort.SessionOptions()
# 根据机器CPU总核心数分配算力,例如4核CPU就给每个模型分配2核
sess_options.intra_op_num_threads = 2
sess_options.inter_op_num_threads = 1
sess_options.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL

# 初始化两个模型会话时传入自定义配置
self.hate_sess = ort.InferenceSession(
    os.path.join(curr_dir, "model", "hatev5.onnx"),
    sess_options=sess_options
)
self.corona_sess = ort.InferenceSession(
    os.path.join(curr_dir, "model", "corona.onnx"),
    sess_options=sess_options
)

2. 优化并行逻辑顺序,最大化真并行收益

纯Python实现的tokenizer、序列填充这类预处理逻辑运行速度极快,完全没有必要拆分到子线程并行。正确的并行逻辑顺序是:

  • 先在主线程串行完成两个模型的全部输入预处理(这部分通常耗时不到1ms,对总延迟的影响可以忽略)
  • 再并行触发两个模型的推理:ONNX Runtime的C++推理后端在执行计算时会主动释放GIL,此时两个推理任务可以真正利用独立的CPU核心并行计算,不会产生锁竞争的额外开销。

参考改造后的推理逻辑:

import concurrent.futures

# 单独抽离两个模型的推理流程
def _run_hate_inference(self, hate_input):
    hate_pred = self.hate_sess.run(None, {self.hate_input_name: hate_input})
    return hate_pred[0][0][1]

def _run_corona_inference(self, corona_input):
    corona_pred = self.corona_sess.run(None, {self.corona_input_name: corona_input})
    return corona_pred[0][0][1]

def Eval(self, data):
    # 保留原有参数校验、query提取的全部逻辑
    # 第一步:串行完成两个模型的全流程预处理
    hate_line = ' '.join(trim(self.wordpiece_tokenizer.tokenize(query.strip()), self.hate_seq_len))
    hate_seq = self.hate_tokenizer.texts_to_sequences([hate_line])
    hate_input = sequence.pad_sequences(hate_seq, padding='post', maxlen=self.hate_seq_len)
    
    corona_line = ' '.join(trim(self.wordpiece_tokenizer.tokenize(query.strip()), self.corona_seq_len))
    corona_seq = self.corona_tokenizer.texts_to_sequences([corona_line])
    corona_input = sequence.pad_sequences(corona_seq, padding='post', maxlen=self.corona_seq_len)

    # 第二步:并行触发两个模型的推理计算
    with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
        hate_task = executor.submit(self._run_hate_inference, hate_input)
        corona_task = executor.submit(self._run_corona_inference, corona_input)
        hate_prob = hate_task.result()
        corona_prob = corona_task.result()
    
    # 保留原有结果计算、JSON序列化的全部逻辑

3. GPU场景的特殊校验

如果你的ONNX模型是运行在GPU上的,开启并行前需要先确认两项配置:

  • 默认配置下两个会话共用同一个CUDA流,推理会串行执行在GPU上,此时并行操作只会带来额外的调度开销,完全没有收益
  • 需要给每个会话配置独立的CUDA执行选项,同时确认GPU的剩余算力足够同时承载两个模型的推理任务,再开启并行逻辑。

按照上述方案改造后,通常两个模型的并行推理耗时可以接近单个模型的推理耗时,相比串行运行可减少接近50%的端到端延迟。

内容的提问来源于stack exchange,提问作者Abhishek Gangwar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.02 22:54:04