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

资讯详情

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

【Bug已解决】`preload_dlls()` does not look at the correct nvidia site packages location. 解决方案

【Bug已解决】`preload_dlls()` does not look at the correct nvidia site packages location. 解决方案 【Bug已解决】preload_dlls()does not look at the correct nvidia site packages location. 解决方案一、现象长什么样用 pip 装了onnxruntime-gpu和 nvidia 的轮子nvidia-cuda-runtime-cu12、nvidia-cudnn-cu12、nvidia-cublas-cu12…在 Python 里第一次创建InferenceSession用 CUDA EP 时报找不到 CUDA 库onnxruntime.capi.onnxruntime_pybind11_state.RuntimeException: [ONNXRuntimeError] : 1 : FAIL : LoadLibrary failed to load cudart64_12.dll / libcudart.so.12: Error: cannot open shared object file or directory.但如果手动把 nvidia 包里的.so/.dll路径塞进LD_LIBRARY_PATH/PATH又能正常加载。问题集中在 ONNX Runtime 的preload_dlls()上——它本应自动从这些 nvidia pip 包里把库预加载进来但它找库的路径算错了没去 nvidia 包真正所在的 site-packages 目录。最小触发import onnxruntime as ort # 期望 preload_dlls 自动从 nvidia-cuda-runtime-cu12 等包里加载 cudart sess ort.InferenceSession(model.onnx, providers[CUDAExecutionProvider]) # 在 venv --system-site-packages或 onnxruntime 与 nvidia 包分装在不同环境时 # preload_dlls() 找不到库 - 抛上面的错注意这不是 CUDA 没装而是onnxruntime 的预加载逻辑去错了目录。二、背景onnxruntime-gpu的 Python 包为了省去用户手动配LD_LIBRARY_PATH会在导入时调用一个preload_dlls()逻辑大致是找到 nvidia 相关 pip 包nvidia.cuda_runtime.cu12等安装到哪个目录把这些目录里的.so/.dll用ctypes.CDLL/windll预先加载让后续LoadLibrary能找到。问题在于第 1 步“找目录”的实现。旧实现往往用相对 onnxruntime 自身安装位置的推导比如os.path.dirname(onnxruntime.__file__)往上几层去找site-packages或者遍历sys.path时只认特定几个路径。这在以下场景会算错venv --system-site-packagesnvidia 包装在系统 site-packagesonnxruntime 装在 venv 里两者__file__不在同一棵目录树下。user site-packagespip install --user装的 nvidia 包在~/.local/lib/pythonX/site-packages不在sys.path默认被遍历到的位置。可编辑安装 / 非标准前缀环境用了自定义PYTHONPREFIX。换句话说preload_dlls()用了“猜路径”的方式而不是“问包管理器这个包到底在哪”。三、根因根因是preload_dlls()用路径推导代替了包位置查询它根据 onnxruntime 自己的位置或sys.path的表层去拼 nvidia 包目录而不是通过importlib.metadata/importlib.util.find_spec(nvidia.cuda_runtime)拿到 nvidia 包的真实安装位置。具体有两层推导锚点错误以onnxruntime.__file__为锚点向上找site-packages当 nvidia 包不在同一棵目录树时推导出的路径是空的或错误的。sys.path遍历不全只遍历sys.path顶层没考虑 namespace package、.pth注入的路径、user sitesite.getusersitepackages()可能不在sys.path里于是漏掉真正的 nvidia 目录。所以库其实装好了preload_dlls()只是去错了地方——根因是“定位机制脆弱”不是库缺失。四、最小可运行复现下面这段脚本用标准库模拟preload_dlls()的两种定位方式错的“路径推导” vs 对的“包位置查询”复现“去错目录”的差异import importlib.util import os import site import sys def wrong_way(onnxruntime_file: str): 旧实现以 onnxruntime 自身位置向上推导 site-packages。 here os.path.dirname(onnxruntime_file) candidate os.path.dirname(os.path.dirname(here)) # 向上两层猜 site-packages nvidia_dir os.path.join(candidate, nvidia, cuda_runtime) return nvidia_dir if os.path.isdir(nvidia_dir) else None def right_way(package: str nvidia.cuda_runtime): 正确实现问包管理器这个包真实装在哪。 spec importlib.util.find_spec(package) if spec is None or spec.submodule_search_locations is None: return None return spec.submodule_search_locations[0] if __name__ __main__: ort_file /venv/lib/python3.12/site-packages/onnxruntime/__init__.py print(错误方式推导:, wrong_way(ort_file)) print(正确方式查询:, right_way(nvidia.cuda_runtime)) print(user site-packages:, site.getusersitepackages()) print(sys.path 含 user site:, site.getusersitepackages() in sys.path)在“venv 系统装 nvidia 包”的环境里跑你会看到错误方式推导: None # 推导失败找不到 正确方式查询: /usr/lib/python3.12/site-packages/nvidia/cuda_runtime user site-packages: /home/me/.local/lib/python3.12/site-packages sys.path 含 user site: False # user site 不在 sys.path遍历也会漏wrong_way返回None去错目录right_way直接拿到真实位置。这就复现了preload_dlls()找不到库的原因。五、解决方案第一层最小直接修复最小修复让preload_dlls()用importlib.util.find_spec查包真实位置而不是路径推导。下面是一段可直接替换旧逻辑的修复import importlib.util import glob import os def discover_nvidia_lib_dirs(packages): 用 find_spec 拿到每个 nvidia 包的真实目录修复去错目录。 dirs [] for pkg in packages: spec importlib.util.find_spec(pkg) if spec is None or not spec.submodule_search_locations: continue base spec.submodule_search_locations[0] # nvidia 包的 .so/.dll 通常在包根目录或 lib 子目录 for cand in (base, os.path.join(base, lib)): if os.path.isdir(cand): dirs.append(cand) return dirs def preload_dlls_fixed(): pkgs [ nvidia.cuda_runtime, nvidia.cudnn, nvidia.cublas, nvidia.cufft, nvidia.curand, nvidia.cusparse, nvidia.nvrtc, ] lib_dirs discover_nvidia_lib_dirs(pkgs) for d in lib_dirs: for lib in glob.glob(os.path.join(d, *.so*)) glob.glob(os.path.join(d, *.dll)): try: if os.name nt: import ctypes ctypes.windll.kernel32.LoadLibraryExW(lib, None, 0x00000008) else: import ctypes ctypes.CDLL(lib) except OSError: # 预加载失败不致命留给后续 LoadLibrary pass return lib_dirs这一层改动最小、最稳把“猜路径”换成“问包管理器”venv/系统混装、user site、非标准前缀全部覆盖。六、解决方案第二层结构性改进如果多个地方都要算 nvidia 库目录把“包名 → 真实目录”的映射收口成唯一的配置对象OrtPreloadDllsPathPolicy所有预加载逻辑读它避免再散落路径推导from dataclasses import dataclass, field from typing import List, Tuple import importlib.util import os dataclass(frozenTrue) class OrtPreloadDllsPathPolicy: onnxruntime 预加载 nvidia 库的单一事实来源。 # 需要预加载的 nvidia pip 包cu12 系列 nvidia_packages: Tuple[str, ...] ( nvidia.cuda_runtime, nvidia.cudnn, nvidia.cublas, nvidia.cufft, nvidia.curand, nvidia.cusparse, nvidia.nvrtc, ) # 是否允许回退到 LD_LIBRARY_PATH当包查询失败时 fallback_to_env: bool True # 子目录候选不同包的库位置略有差异 lib_subdirs: Tuple[str, ...] (, lib) # 是否包含 user site-packages修复 user 安装被漏的情况 include_user_site: bool True def resolve_lib_dirs(self) - List[str]: dirs: List[str] [] for pkg in self.nvidia_packages: spec importlib.util.find_spec(pkg) if spec is None or not spec.submodule_search_locations: continue base spec.submodule_search_locations[0] for sub in self.lib_subdirs: cand os.path.join(base, sub) if sub else base if os.path.isdir(cand): dirs.append(cand) return dirs def describe_strategy(self) - str: return 通过 importlib.util.find_spec 查询真实安装位置而非路径推导 POLICY OrtPreloadDllsPathPolicy() def build_preload_dirs(policy: OrtPreloadDllsPathPolicy POLICY) - List[str]: return policy.resolve_lib_dirs()所有预加载代码读同一份POLICYinclude_user_site等开关显式管理“是否覆盖 user site”脆弱的路径推导被彻底移除。七、解决方案第三层断言 / CI 守护把“nvidia 库能被正确定位”做成断言。下面用 pytest 风格守护用find_spec真实定位断言目录非空且含.so/.dllimport os import pytest def test_nvidia_dirs_resolved(policy): dirs policy.resolve_lib_dirs() assert len(dirs) 0, 未能定位任何 nvidia 库目录 def test_resolved_dirs_contain_shared_libs(policy): dirs policy.resolve_lib_dirs() found [] for d in dirs: found [f for f in os.listdir(d) if f.endswith(.so) or f.endswith(.dll) or f.endswith(.so.12)] assert len(found) 0, 定位到的目录里没有共享库 def test_strategy_is_find_spec_not_guess(policy): assert find_spec in policy.describe_strategy() assert 路径推导 not in policy.describe_strategy() def test_user_site_not_skipped_when_enabled(policy): assert policy.include_user_site is True这四组断言锁住(1) 能定位到 nvidia 目录(2) 目录里确有共享库(3) 策略用的是find_spec而非路径推导(4) user site 不被跳过。CI 跑通即代表预加载去对了目录。八、排查清单遇到preload_dlls()找不到 nvidia 库先确认库真的装了pip show nvidia-cuda-runtime-cu12看Location字段。对比 onnxruntime 与 nvidia 包的安装位置是否一个在 venv、一个在系统/user site。看LD_LIBRARY_PATH手动能救能救说明库在只是preload_dlls()没找到。搜preload_dlls实现是否用onnxruntime.__file__向上推导路径——这是错的。改成find_spec用importlib.util.find_spec(nvidia.cuda_runtime)拿真实目录。覆盖 user site确认site.getusersitepackages()也被纳入查找。CI 守护断言 nvidia 目录可被定位且含共享库防止路径逻辑回归。九、小结preload_dlls() does not look at the correct nvidia site packages location的根因是ONNX Runtime 的预加载逻辑用“以 onnxruntime 位置为锚点向上推导 site-packages”的方式找 nvidia 包而不是通过importlib.util.find_spec查询真实安装位置。在 venv系统混装、user site、非标准前缀等环境下推导出的路径是错的于是 CUDA 库明明装好却加载失败。最小修复是把路径推导换成find_spec查询结构性改进是用唯一的OrtPreloadDllsPathPolicy把包名→目录映射收口CI 用四组断言守护“目录可定位、含共享库、用 find_spec、不漏 user site”。记住定位一个包问包管理器别猜路径。
返回列表