【Bug已解决】AddExternalInitializers copies device (GPU) OrtValues per session instead of using them in …
【Bug已解决】AddExternalInitializers copies device (GPU) OrtValues per session instead of using them in place 解决方案一、现象长什么样用AddExternalInitializers把一个已经常驻 GPU 的OrtValue作为外部初始化器加到计算图里常见于把预加载的大常量权重直接挂到图上时发现每个新建的 session 都会把这份 GPU 张量完整拷贝一份而不是原地复用in-place / 共享引用// 预加载一个大权重到 GPU得到 device OrtValue Ort::Value gpu_weight loadWeightToGpu(...); // 比如 1GB 的 embedding 表 // 把它作为外部初始化器加进图 // 期望session 直接引用 gpu_weight0 拷贝 // 实际每个 session 都 deep-copy 了一份 gpu_weight1GB x N session std::vectorOrtValue* initializers {gpu_weight}; // AddExternalInitializers 内部对 device OrtValue 做了拷贝最小信号GPU 显存随 session 数线性增长每个 session 多一份权重拷贝 预期所有 session 共享同一份 GPU 权重in-place注意这不是结果错而是显存浪费 拷贝开销——大权重本可跨 session 共享却被每份 session 复制。二、背景AddExternalInitializers的作用是把一个OrtValue通常是常量权重注册成图的初始化器这样图在构造时就“拥有”这个权重不用每次run()都从外部传入。当这个OrtValue已经在GPU 设备上时理想情况是 session直接持有它的引用或共享所有权多个 session 共享同一块 GPU 显存。但 ORT 的AddExternalInitializers实现里对 deviceGPUOrtValue走了“拷一份再挂到图上”的路径而不是“原地接管/共享引用”。原因通常是图的初始化器在内部用OrtValue的拷贝语义值语义默认AddExternalInitializers的契约是“把值加进图”实现者图省事直接拷贝。deviceOrtValue的拷贝涉及一次cudaMemcpy设备内或跨流既占显存又耗时。对于大模型几十 GB 权重每个 session 拷一份直接爆显存所以必须 in-place 共享。三、根因根因是AddExternalInitializers对 deviceGPUOrtValue走了深拷贝路径而不是 in-place 共享引用移动语义 / 共享所有权导致每个 session 都复制一份 GPU 权重默认值拷贝语义AddExternalInitializers内部把传入的OrtValue按值存入图的初始化器列表对 GPU 张量触发一次设备内拷贝。未用移动/共享没有判断“如果传入的是 device OrtValue就移动所有权或共享引用不拷贝”于是多 session 各自持有一份拷贝。显存线性增长N 个 session 就有 N 份权重拷贝大模型直接 OOM。不是结果错拷贝出的权重值一样推理正确只是浪费显存和拷贝时间。所以这不是数值错而是API 对 device OrtValue 的共享语义没实现导致本可共享的 GPU 权重被重复拷贝。四、最小可运行复现下面用 C 标准库模拟“AddExternalInitializers 拷贝 vs in-place 共享”的显存差异#include iostream #include vector #include memory // 模拟 device OrtValueGPU 张量记录被拷贝次数 struct DeviceOrtValue { size_t bytes; static int copy_count; DeviceOrtValue(size_t b) : bytes(b) {} DeviceOrtValue(const DeviceOrtValue o) : bytes(o.bytes) { copy_count; } }; int DeviceOrtValue::copy_count 0; // 有 bug 的实现拷贝 device OrtValue 进图 std::vectorDeviceOrtValue addExternalInitializers_copy(const DeviceOrtValue w) { std::vectorDeviceOrtValue graph; graph.push_back(w); // 拷贝 return graph; } int main() { DeviceOrtValue weight(1000000000); // 1GB // 建 4 个 session每个都 AddExternalInitializers for (int i 0; i 4; i) addExternalInitializers_copy(weight); std::cout GPU 权重拷贝次数: DeviceOrtValue::copy_count 每个 session 拷贝一次 - 浪费 4GB\n; // 修复应移动/共享引用拷贝次数 0 }跑出来copy_count 4即 4 个 session 各拷贝一份 1GB 权重共浪费 4GB。修复后应当用移动/共享引用copy_count 0。这复现了“device OrtValue 被重复拷贝”的机制。五、解决方案第一层最小直接修复最小修复让AddExternalInitializers对 deviceOrtValue走 in-place 共享移动所有权或共享引用不拷贝。对使用者临时规避是减少 session 数量全局单例 session让拷贝只发生一次// 全局只建一个持有外部初始化器的 session所有推理复用 // 这样 device OrtValue 只被拷贝或共享一次 static Ort::Session* g_session nullptr; Ort::Session getSession(Ort::Env env, Ort::SessionOptions so, const char* path) { if (!g_session) { // 把 GPU 权重 AddExternalInitializers 后建 session仅一次拷贝 g_session new Ort::Session(env, path, so); } return *g_session; }对 ORT 仓库侧修复是改AddExternalInitializers若传入的OrtValue位于设备内存使用std::move或共享OrtValue的智能指针引用计数接管而不是值拷贝多个 session 共享同一份 deviceOrtValue。这一层立刻消除重复拷贝。六、解决方案第二层结构性改进把“外部初始化器对 device OrtValue 必须 in-place 共享”收口成唯一的配置对象OrtAddExternalInitDeviceCopyPolicyAPI 实现读它from dataclasses import dataclass, field from typing import Tuple, Literal dataclass(frozenTrue) class OrtAddExternalInitDeviceCopyPolicy: AddExternalInitializers device OrtValue 共享语义的单一事实来源。 # device OrtValue 的处理方式in-place 共享不拷贝 device_value_policy: Literal[in_place_share, move, copy] in_place_share # 是否允许多 session 共享同一 device OrtValue allow_cross_session_share: bool True # 受影响 API affected_api: Tuple[str, ...] (AddExternalInitializers,) # 拷贝必须显式声明默认禁止隐式拷贝 forbid_implicit_copy: bool True def should_copy(self, is_device_value: bool) - bool: if not is_device_value: return False return self.device_value_policy copy def describe(self) - str: return device OrtValue 进 AddExternalInitializers 必须 in-place 共享禁止隐式拷贝 POLICY OrtAddExternalInitDeviceCopyPolicy() def plan_add(ort_value_is_device: bool, policy: OrtAddExternalInitDeviceCopyPolicy POLICY) - str: return copy if policy.should_copy(ort_value_is_device) else in_place_share所有 API 实现读同一份POLICYdevice OrtValue 默认 in-place 共享杜绝隐式拷贝。七、解决方案第三层断言 / CI 守护把“device OrtValue 不被隐式拷贝”做成断言。下面用 pytest 风格守护复用第四节逻辑import pytest def test_device_value_in_place(policy): assert policy.device_value_policy in_place_share assert policy.should_copy(True) is False # device 不拷贝 def test_cross_session_share_allowed(policy): assert policy.allow_cross_session_share is True def test_forbid_implicit_copy(policy): assert policy.forbid_implicit_copy is True def test_host_value_no_copy_either(policy): # 即便 host 值按策略也不拷贝直接用 assert policy.should_copy(False) is False这四组断言锁住(1) device 值 in-place 不拷贝(2) 允许跨 session 共享(3) 禁止隐式拷贝(4) host 值也不无故拷贝。CI 跑通即代表共享语义被守护。八、排查清单遇到 AddExternalInitializers 让 GPU 显存随 session 数增长确认是不是 device OrtValue传入的权重在 GPU 上、且每个 session 都多一份 - 锁定隐式拷贝。看 API 语义AddExternalInitializers是不是值拷贝没用移动/共享。查 session 数量是不是每请求一 session应改单例。临时规避全局单例 session只拷贝/共享一次。根本修复device OrtValue 走 in-place 共享移动/引用计数不拷贝。统一策略对象用OrtAddExternalInitDeviceCopyPolicy固化。CI 守护断言 device 值不拷贝、可跨 session 共享。九、小结AddExternalInitializers copies device (GPU) OrtValues per session instead of using them in place的根因是AddExternalInitializers对位于设备内存的OrtValue走了值拷贝路径而不是 in-place 共享引用移动语义/引用计数导致每个新建的 session 都把这份 GPU 权重完整拷贝一份显存随 session 数线性增长、还有拷贝开销。最小修复是让 API 对 device OrtValue 走 in-place 共享移动/共享所有权临时规避是全局单例 session 只共享一次结构性改进是用唯一的OrtAddExternalInitDeviceCopyPolicy固化“device 值必须 in-place 共享、禁止隐式拷贝”CI 用四组断言守护“device 不拷贝、可跨 session 共享、禁止隐式拷贝”。记住常驻 GPU 的大权重进图必须共享引用隐式拷贝会直接撑爆显存。

相关新闻

【信息科学与工程学】【通信工程】第一百五十九篇 SDN网络中的核心算法01

【信息科学与工程学】【通信工程】第一百五十九篇 SDN网络中的核心算法01

编号2 完整表格 字段 内容 编号​ 2 类型​ 算法 领域​ SD-WAN(软件定义广域网) 模块​ 流量工程(Traffic Engineering) – 带宽分配 依赖软件环境​ 编译器:GCC 11.2.0 (C++20) 操作系统:Linux Kernel 5.15 (x86_64) 虚拟化:KVM + Docker 运行库:GLPK (GN…

2026/11/10 3:09:23 阅读更多 →
【Bug已解决】[Web] Expose WebGPU EP buffer cache mode options in JS 解决方案

【Bug已解决】[Web] Expose WebGPU EP buffer cache mode options in JS 解决方案

【Bug已解决】[Web] Expose WebGPU EP buffer cache mode options in JS 解决方案 一、现象长什么样 在 Web 端(ONNX Runtime Web)用 WebGPU EP,想调“缓冲区缓存模式”相关的 session 选项(比如让中间张量在不同 run() 之间复用 …

2026/11/10 3:09:23 阅读更多 →
Spring Boot 3.x升级遇UnsupportedClassVersionError:Java版本不匹配的排查与修复

Spring Boot 3.x升级遇UnsupportedClassVersionError:Java版本不匹配的排查与修复

1. 问题现象与本质剖析 最近在升级一个老项目到 Spring Boot 3.x 时,编译过程一切顺利,但在启动应用时,控制台直接抛出了一个令人困惑的错误: java.lang.UnsupportedClassVersionError: class file has wrong version 61.0, shou…

2026/11/8 2:06:30 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/9 14:01:10 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/9 14:56:39 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/10 2:03:25 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/9 14:01:10 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/9 7:45:06 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/9 14:01:11 阅读更多 →

日新闻

周新闻

月新闻