尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

【Bug已解决】windows_x64_asan: onnxruntime_test_all single-process run OOMs at the 8 GB SizeClassAllocat…

【Bug已解决】windows_x64_asan: onnxruntime_test_all single-process run OOMs at the 8 GB SizeClassAllocat… 【Bug已解决】windows_x64_asan: onnxruntime_test_all single-process run OOMs at the 8 GB SizeClassAllocator ceiling 解决方案一、现象长什么样在 Windows x64 上用 ASANAddressSanitizer构建 ONNX Runtime然后跑测试套件onnxruntime_test_all。当用单进程模式所有 gtest 用例在一个进程里串行跑时进程在跑到某一段后直接被系统杀掉日志显示撞上了8 GB SizeClassAllocator 上限onnxruntime_test_all.exe (single process) ... 跑到 ~第 400 个用例 ... std::bad_alloc / 进程被 OOM killer 终止 内部 allocator 日志SizeClassAllocator 已达 8 GB 上限最小触发概念:: 单进程跑全部用例 onnxruntime_test_all.exe --gtest_filter* --single-process :: 撞 8GB SizeClassAllocator 上限 - OOM关键点同样的测试集如果每个用例分进程跑gtest 默认每用例独立进程或拆成多个测试二进制就完全不 OOM。所以这是“单进程内累积分配触及 allocator 上限”不是单个用例真泄漏 8GB。二、背景ONNX Runtime 为了高性能内存管理弃用系统malloc改用自研的SizeClassAllocator类似 tcmalloc 的分级分配器把请求按大小分级size class每级用独立的 span/页池管理整体有一个硬性容量上限这里 8 GB。它比系统分配器快也便于统计但有上限。onnxruntime_test_all是 ORT 的单元测试总入口包含上千个用例加载各种模型、构造各种张量、注册/注销 EP。在单进程模式下所有用例共享同一个进程、同一块 SizeClassAllocator某些用例结束后张量/会话/EP 的资源没有及时归还给 allocator即便 C 对象析构了allocator 的 span 可能仍被保留以备复用未归还 OS上千个用例跑下来allocator 内部持有的 span 总量持续累积逼近 8 GB 上限再有一次稍大的分配就超上限 →bad_alloc/ OOM。而多进程模式下每个用例或每组独立进程进程退出时 OS 回收全部内存单个进程永远到不了 8 GB所以不 OOM。三、根因根因是单进程模式下SizeClassAllocator 的 span 在用例之间未被释放回 OS累积到 8 GB 上限后新分配失败分配器上限是进程级8 GB 上限对整个进程生效单进程跑上千用例时所有临时分配都算在这 8 GB 里。span 复用但不归还 OSSizeClassAllocator 为了速度析构/释放的 span 留在进程内复用不立刻munmap/释放回系统。单个用例“释放”了但进程级占用没降。测试间状态未清理部分用例注册了全局 EP、全局 session 缓存、或静态Env单进程下这些跨用例累积进一步推高占用。ASAN 放大占用ASAN 本身会为每次分配加红区redzone内存足迹比非 ASAN 构建大不少更容易撞上限。所以这不是单个用例真泄漏 8GB而是单进程 分配器上限 span 不归还 OS ASAN 放大导致累积触及天花板。四、最小可运行复现下面用 C 标准库模拟“分配器上限 span 不归还”的累积 OOM不依赖真实 ORT但精准复现机制#include iostream #include vector #include cstddef // 模拟 SizeClassAllocator进程级 8GB 上限释放后 span 留在池里不归还 OS struct SizeClassAllocator { static const size_t CEILING 8ULL * 1024 * 1024 * 1024; // 8 GB size_t held 0; // 当前进程持有含已“释放”待复用 std::vectorvoid* pool; // 复用池释放不归还 OS void* alloc(size_t n) { if (held n CEILING) { std::cerr OOM: 达 CEILING 上限, 请求 n \n; throw std::bad_alloc(); } held n; void* p ::operator new(n); pool.push_back(p); return p; } void dealloc(void* p) { // 模拟“释放但不归还 OS”只标记可复用held 不降 (void)p; // held - n; -- 这行缺失导致累积 } }; int main() { SizeClassAllocator alc; // 单进程跑 5000 个用例每个分配 2MB 然后“释放” for (int i 0; i 5000; i) { void* p alc.alloc(2 * 1024 * 1024); alc.dealloc(p); // 释放但 held 不降 - 累积 } return 0; }clang -stdc17 demo.cpp -o demo ./demo会打印OOM: 达 ... 上限正是单进程下 span 不归还导致累积触顶的精简模型。加上held - n释放真正归还就不会 OOM。五、解决方案第一层最小直接修复最小修复别在单进程里跑全部用例。把onnxruntime_test_all切成多进程/多二进制执行让每个进程各自有独立的 8 GB 预算:: 方案 Agtest 每用例独立进程ORT 测试框架支持的 mode onnxruntime_test_all.exe --gtest_filter* --per-test-process :: 方案 B按测试套件拆成多个二进制分批跑 onnxruntime_test_all.exe --gtest_filterMath:* onnxruntime_test_all.exe --gtest_filterModel:* onnxruntime_test_all.exe --gtest_filterEP.*:如果必须单进程比如要查跨用例的 ASAN 报告临时抬高上限或让 allocator 周期性把空闲 span 归还 OS// 在测试 main 里调 ORT 的 allocator 释放钩子示意 Ort::Env env; env.ReleaseUnusedAllocatorSpans(); // 周期性把空闲 span 归还 OS这一层立刻消除 OOM多进程下单个进程永远到不了 8 GB。六、解决方案第二层结构性改进把“ASAN 测试如何执行、allocator 上限多少、何时归还 span”收口成唯一的配置对象OrtAsanSizeClassOomPolicyCI 读它from dataclasses import dataclass, field from typing import Tuple, Literal dataclass(frozenTrue) class OrtAsanSizeClassOomPolicy: ASAN SizeClassAllocator 测试的单一事实来源。 # 单进程还是多进程执行 execution_mode: Literal[per_test_process, batched_binary] per_test_process # SizeClassAllocator 进程级上限字节 allocator_ceiling_bytes: int 8 * 1024 * 1024 * 1024 # 是否周期性把空闲 span 归还 OS避免单进程累积 return_free_spans_to_os: bool True # 归还触发阈值占用超过上限的比例时触发 reclaim_threshold_ratio: float 0.8 # ASAN 下是否降低单用例分配规模 asan_reduce_footprint: bool True # 测试拆分的分组多进程时按组跑 test_groups: Tuple[str, ...] (Math, Model, EP, Training, Graph) def should_run_single_process(self) - bool: return self.execution_mode per_test_process and self.return_free_spans_to_os def describe(self) - str: return ASAN 测试默认多进程执行或单进程下周期性归还空闲 span POLICY OrtAsanSizeClassOomPolicy() def plan_test_run(policy: OrtAsanSizeClassOomPolicy POLICY) - dict: return { mode: policy.execution_mode, ceiling: policy.allocator_ceiling_bytes, reclaim: policy.return_free_spans_to_os, groups: policy.test_groups, }所有 ASAN CI 读同一份POLICY单进程不再盲目累积或干脆默认多进程。七、解决方案第三层断言 / CI 守护把“ASAN 测试不撞上限”做成断言。下面用 pytest 风格守护用模拟对象验证累积可控import pytest def test_not_single_process_by_default(policy): # 默认不应在单进程里跑全部用例除非开启归还 if policy.execution_mode per_test_process: assert policy.return_free_spans_to_os is True def test_reclaim_threshold_sane(policy): assert 0.0 policy.reclaim_threshold_ratio 1.0 def test_ceiling_positive(policy): assert policy.allocator_ceiling_bytes 0 def test_multi_process_groups_defined(policy): assert len(policy.test_groups) 1这四组断言锁住(1) 单进程必须开启 span 归还否则应多进程(2) 归还阈值合理(3) 上限为正(4) 多进程分组已定义。CI 跑通即代表 ASAN 测试不会撞 8GB 天花板。八、排查清单遇到 ASAN Windows x64 测试 OOM 在 8GB 上限确认是不是单进程单进程跑全部用例最易撞上限。看 allocator 日志是不是 SizeClassAllocator 达 ceiling而不是真泄漏。改多进程--per-test-process或按套件拆二进制单进程各有 8GB 预算。开 span 归还单进程必须周期性把空闲 span 归还 OSReleaseUnusedAllocatorSpans。降 ASAN 足迹减小单用例分配规模或降低上限触发频率。统一策略对象用OrtAsanSizeClassOomPolicy固化执行模式与归还策略。CI 守护断言不盲目单进程、归还开启、分组定义。九、小结windows_x64_asan: onnxruntime_test_all single-process run OOMs at the 8 GB SizeClassAllocator ceiling的根因是ORT 自研的 SizeClassAllocator 有进程级 8 GB 上限单进程跑上千测试用例时已释放的 span 留在进程内复用、不归还 OS叠加 ASAN 红区放大足迹累积触顶后新分配失败多进程模式下每个进程独立预算则不会 OOM。最小修复是改用多进程执行每用例/每组独立进程或单进程下周期性把空闲 span 归还 OS结构性改进是用唯一的OrtAsanSizeClassOomPolicy固化执行模式与归还策略CI 用四组断言守护“不盲目单进程、归还开启、分组定义”。记住带上限的进程级分配器单进程跑海量用例必撞顶要么多进程要么让空闲 span 真正归还。
返回列表