登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

AI ONNX Runtime 复用会话 怎样处理

来源:17golang原创

时间:2026-10-22 06:57:27 349浏览 收藏

我在把 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删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>