当前位置:首页 > 文章列表 > 科技周边 > 人工智能 > AI ONNX Runtime 复用会话 怎样处理

AI ONNX Runtime 复用会话 怎样处理

来源:17golang原创 2026-10-22 06:57:27 0浏览 收藏

我在把 ONNX 模型接进服务时,最先遇到的不是模型输出错误,而是每个请求都重新创建 InferenceSession:首请求很慢,进程内存也随着流量变得难以判断。更稳的默认模式是按模型和会话配置创建一次,后续请求复用它调用 session.run()。会话可以共享,并不意味着并发数可以无限放大;真正的上限还要看执行提供程序、输入缓冲区和显存预算。

官方地址:https://onnxruntime.ai/

把 InferenceSession 当成服务级依赖,把输入和输出当成请求级状态;先复用会话,再用信号量或队列控制同时运行的请求。

把会话提升为长期依赖

ONNX Runtime 的 Python API 将 InferenceSession 定义为加载并运行模型的主类。模型路径、SessionOptions 和 providers 都属于创建会话时的契约,不应该在每个 HTTP 请求里重复决定。我的经验是把它收进一个小型 runner:初始化阶段加载模型,请求阶段只接收本次输入。

import threading
import onnxruntime as ort

class ModelRunner:
    def __init__(self, model_path: str, max_inflight: int = 2):
        options = ort.SessionOptions()
        # 图优化和线程配置在会话创建时确定,避免请求之间互相改配置。
        options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
        options.intra_op_num_threads = 2
        self.session = ort.InferenceSession(
            model_path,
            sess_options=options,
            providers=["CPUExecutionProvider"],
        )
        # 槽位只限制同时运行数,不复制模型,也不共享请求输入。
        self.slots = threading.BoundedSemaphore(max_inflight)

    def infer(self, input_name: str, input_value):
        # 每次调用使用调用方准备好的独立输入,避免并发改写同一数组。
        with self.slots:
            return self.session.run(None, {input_name: input_value})

这里的重点不是把 ModelRunner 做成全局变量,而是让它的生命周期至少覆盖一批请求。实际服务可以通过依赖注入持有它。max_inflight 是资源预算,不是 ONNX Runtime 的“正确答案”:CPU 模型可以从较小值开始,GPU 模型则要把临时激活内存、复制开销和尾延迟一起测量。

ModelRunner、InferenceSession、请求输入和执行提供程序的静态关系
图1:会话生命周期结构说明图,查看长期复用的 InferenceSession 与每次请求输入之间的边界;这是静态说明图,不是运行截图。

模式成立的压力:初始化贵,运行要稳定

创建会话通常包含模型读取、图转换、算子准备和执行提供程序选择。把这些工作放进请求路径,会让首请求和常规请求混在一起,也容易让每个会话各自创建线程池。官方线程管理文档提供了 intra_op_num_threads、inter_op_num_threads、执行模式和图优化级别等选项;它们应围绕部署机器和模型形状做一次配置,而不是在高峰期临时修改。

执行提供程序也属于这个模式的边界。CPU、CUDA、TensorRT 或其他 provider 的优先级决定哪些节点由哪一侧执行;同一个模型换 provider,内存和并发行为可能随之变化。若要切换 provider,应在初始化或明确的重建流程里做,不能把 set_providers() 当成每个请求的开关,因为官方文档说明这个调用会重建底层会话。

我会把以下三项放在 runner 配置旁边记录:模型输入形状是否固定、provider 列表及其顺序、并发槽位的初始值。这样出现延迟变化时,至少能分辨是模型输入、执行后端还是请求排队造成的。

用信号量控制并发,而不是盲目复制会话

ONNX Runtime 的核心设计允许多个线程调用同一会话的 Run(),但这不是“每个 provider 都保证相同的并发体验”。共享会话可以减少重复加载和重复缓存,复制多个会话却可能增加线程池、权重或设备内存压力。更可控的做法是先共享一个会话,用信号量限制同时进入推理的请求数,再根据吞吐、P95 延迟和内存曲线决定是否扩容。

class BoundedRunner:
    def __init__(self, session, max_inflight: int):
        self.session = session
        # 从1开始压测,再逐步增加;不要用无限队列掩盖设备内存压力。
        self.slots = threading.BoundedSemaphore(max_inflight)

    def run(self, feeds: dict):
        # feeds 必须属于当前请求;调用期间不要由其他线程改写其中的数组。
        with self.slots:
            return self.session.run(None, feeds)

这段代码只表达资源边界,不代表任意硬件都适合并发值2。对于 CUDA 等设备后端,多个同时运行的请求可能增加临时 workspace 和输入输出拷贝;对于低并发服务,值1反而可能带来更稳定的尾延迟。若模型需要 I/O Binding,也要把绑定对象的所有权和生命周期按请求隔离,不能为了复用会话顺手复用一份正在变化的绑定状态。

共享 InferenceSession、Semaphore 与三个执行提供程序之间的静态约束
图2:并发边界结构说明图,查看共享会话、并发槽位与执行提供程序约束;这是静态说明图,不是性能测试结果。

反例:会话能复用,不等于所有状态都能共享

最常见的反例有三个。第一,把 InferenceSession(model) 写进请求处理器;它把初始化成本乘到请求数上。第二,把一个可变的 NumPy 输入数组放在全局变量里,多个请求同时做预处理;即便会话本身可以并发运行,输入也可能在推理期间被改写。第三,只看 CPU 的经验就套到 DirectML。ONNX Runtime 的 DirectML 文档明确要求关闭内存模式、使用顺序执行,并说明同一会话不能被多个线程同时调用;此时应使用单槽位、队列或不同会话的受控模型。

因此,代码里应共享的是已初始化的模型执行器,而不是正在被请求修改的字典、数组、OrtValue 或 I/O Binding。请求结束后只返回自己的结果;如果结果还要异步交给下游,先明确复制或所有权转移策略。

我的判断清单:什么时候单会话就够了

  • 模型和 provider 固定,初始化成本明显高于一次推理:优先单会话长期复用。
  • 多个请求共享同一会话:先加有界信号量,避免把设备并发交给无限线程。
  • 输入数组、输出容器或绑定对象会被修改:按请求创建,不能只因为模型相同就共享。
  • 切换 provider、SessionOptions 或模型文件:视为重建会话,不在请求中临时切换。
  • 使用 DirectML 或遇到特定 GPU 后端异常:遵循该 provider 的限制,先把并发降到1再定位。
  • 只有当单会话已经成为瓶颈,并且内存、线程和 provider 都允许时,才比较多会话方案。

官方资料可从以下入口继续核对:https://onnxruntime.ai/docs/api/python/api_summary.html、https://onnxruntime.ai/docs/performance/tune-performance/threading.html、https://onnxruntime.ai/docs/execution-providers/DirectML-ExecutionProvider.html。这几个页面分别对应会话 API、线程配置和特殊 provider 边界。

两个容易混淆的问题

复用会话会不会让模型权重自动复制很多份? 单纯重复调用 run() 不会按请求创建新的会话;但不同会话的内存占用不能假定完全共享,尤其是设备端缓存和临时工作区。是否拆成多会话,要用目标 provider 的实际内存曲线判断。

同一会话能不能直接放进线程池? 核心层允许并发调用 Run(),但 provider 可能有更窄的限制,且请求输入仍必须独立。先用有界并发做基线;如果是 DirectML,按官方限制串行调用同一会话。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
AI Datasets 按列过滤 怎样处理AI Datasets 按列过滤 怎样处理
上一篇
AI Datasets 按列过滤 怎样处理
AI vLLM 限制显存 怎样处理
下一篇
AI vLLM 限制显存 怎样处理
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    585次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    622次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    655次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    622次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    436次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码