ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PyTorch报错undefined symbol iJIT_NotifyEvent:根因定位与修复实战

PyTorch报错undefined symbol iJIT_NotifyEvent:根因定位与修复实战 上午还好好的环境下午import torch直接给我一记闷棍。一个跑了半年没出过问题的Docker容器在我重新做了一次conda包更新之后突然在导入阶段炸出这么一行东西ImportError: /opt/conda/xxx/torch/lib/libtorch_cpu.so: undefined symbol: iJIT_NotifyEvent说实话第一次看到这个报错我是有点懵的。模型代码一行没改环境也没动过系统层面的东西为什么torch库的内部文件会突然少一个符号后来我花了大半天时间从报错本身一路追到Intel的性能分析库才把这个问题彻底弄明白。今天就把整个过程和解决方案整理出来给正在被这个报错折磨的朋友一个参考。1. 报错解析undefined symbol iJIT_NotifyEvent到底是什么1.1 从报错信息我们可以读出什么这行错误信息的结构并不复杂核心是“undefined symbol”和符号名“iJIT_NotifyEvent”。它代表的是你程序里某一个共享库这里是libtorch_cpu.so在加载时明确声明自己需要用到某个函数或全局变量但系统在最终链接阶段并没有找到这个函数的实体定义。用生活里的话说就像你组装一台电脑说明书上写需要一个“电源转接线”拆开配件盒却发现里面没有。系统在运行import torch时需要把libtorch_cpu.so这个“大插座”插入运行环境结果它需要的“针脚”iJIT_NotifyEvent无人提供于是加载器直接罢工。这种错误和“文件找不到”不一样它表示相关共享库文件是存在的只是内部有一个符号悬空。这也是为什么很多常规排查手段比如重装torch、检查CUDA版本可能完全无效因为问题根本不在torch本身而在于它依赖的其他系统库。1.2 iJIT_NotifyEvent的身份Intel ITT API那么iJIT_NotifyEvent是什么来历它属于Intel ITTInstrumentation and Tracing Technology工具插桩与追踪技术API的一部分。ITT是Intel提供的一套轻量级代码插桩接口主要用于性能分析工具比如Intel VTune Profiler采集程序运行时的事件信息。iJIT_NotifyEvent这个函数字面意思就是“向JIT编译器通知事件”。说得直白一点当程序运行到一段即时编译的代码时可以通过调用这个函数告诉性能分析器“这里有新生成的JIT代码请把它标记出来”这样VTune就能更准确地分析热点函数而不是把所有动态生成的代码都当作一块黑盒。你可能会问一个深度学习框架为什么会依赖这种性能分析接口其实PyTorch在CPU后端上做了很多针对Intel体系结构的优化并且官方会默认启用ITT插桩支持以便开发者在分析模型性能时能直接使用VTune等工具看到PyTorch内部算子的执行情况。也就是说libtorch_cpu.so在编译阶段就写死了对ITT库符号的引用。1.3 为什么libtorch_cpu.so会引用它这背后的逻辑类似于“可选的硬件接口”。PyTorch的构建脚本里有一个编译选项叫USE_ITT默认开启。编译时链接器会尝试查找系统里的Intel ITT运行时库如果找到了就把libtorch_cpu.so与libittnotify.so关联起来最终生成的动态库中会留下一个未定义符号undefined symbol标记等待运行时动态解析。问题就出在这个“运行时动态解析”上。编译机上有libittnotify.so所以PyTorch能够编译成功但运行环境里没有这个库或者有却因为版本/路径问题没有正确加载那么libtorch_cpu.so里那个未定义符号就成了悬空指针import时直接崩溃。2. 根因定位三大常见诱因2.1 系统中缺少libittnotify.so这是最直接的原因。PyTorch被安装到你的conda环境后它会自带一部分依赖库放在torch/lib目录下。但libittnotify并不是PyTorch的核心依赖官方不在torch/lib里打包这个文件。如果你的操作系统里恰好没有装过Intel VTune、oneAPI、或者Intel Advisor这类工具那么系统里根本不存在libittnotify.so符号自然无法解析。更常见的情况是你用的是某个预构建镜像或基础环境比如nvidia的PyTorch Docker镜像里面默认安装了完整的GPU运行库和Intel CPU相关组件但其中某些镜像构建时把libittnotify放在了非标准路径导致找不到。我遇到的情况就是因为conda包更新时把之前由Intel oneAPI安装的某些共享库路径从LD_LIBRARY_PATH里清掉了。2.2 库加载路径被污染加载了错误的版本即使系统里存在libittnotify.so如果路径不对或者版本过新/过旧同样会出现undefined symbol错误。因为linux动态链接器在解析符号时是按顺序遍历LD_LIBRARY_PATH指定的目录找到第一个匹配名字的库文件就停下。如果它先找到了一个属于旧版VTune的libittnotify.so而那个版本里没有新接口或者接口名不同那么即使后面存在正确的库也根本不会被加载。这种情况在经常折腾环境的人身上特别常见。你有时候为了装某个软件往LD_LIBRARY_PATH里加了一堆目录结果不同软件的Intel库互相覆盖最终导致torch加载了错误的ITT实现。2.3 PyTorch版本与编译选项不匹配还有一种情况是PyTorch版本的锅。某些PyTorch版本在编译时开启了ITT支持但发布时的conda/pip包又没把配套的运行时库列为依赖导致标准安装流程下必然出现这个错误。在GitHub上能看到不少这类issue报告者的环境并没有安装VTune只是从conda-forge装了一个特定版本的torch就中招了。另外如果你用过不同渠道的torch包官方pip源、conda默认源、conda-forge源它们各自的构建配置可能不同。比如一个版本用USE_ITTON编译另一个是OFF那么当你混用或者切换版本时同一个libtorch_cpu.so可能突然开始依赖ITT符号。这种“玄学”问题最折磨人因为表面上看你只是升级了一个小版本实际上底层编译选项全变了。3. 排查步骤一步步找到问题所在3.1 第一步确认torch包版本与导入路径接到报错后先不要急着找系统库先确认当前import的torch到底是哪个版本、从哪里来的。在命令行里执行python -c import torch; print(torch.__version__, torch.__file__)如果import本身直接报错像我们的情况那就无法执行这行代码。这时候可以用以下命令绕过importls -l /opt/conda/xxx/torch/lib/libtorch_cpu.so python -c import importlib.util; print(importlib.util.find_spec(torch))在报错现场我确认了torch是从/opt/conda/xxx目录加载的版本是1.13.0。记住这个信息后面无论是重装还是手动补库都要对得上号。3.2 第二步用ldd和nm检查动态库依赖Linux下排查动态库问题最趁手的工具是ldd。它可以列出一个共享库需要哪些外部依赖以及它们是否被找到ldd /opt/conda/xxx/torch/lib/libtorch_cpu.so | grep -i not found上面命令会直接输出“找不到”的库列表。在我那次排查中输出里就有一行libittnotify.so.12 not found这就基本实锤了缺少ITT运行时库。如果这一步没有看到missing项还可以继续用readelf -d或者nm -D查看符号表nm -D /opt/conda/xxx/torch/lib/libtorch_cpu.so | grep iJIT_NotifyEvent如果符号前有U标记表示它是未定义的需要在运行时从其他库中导入如果看到的是T或t说明这个符号已经在库内部定义了那问题可能就是另一个库导出了同名的旧符号产生了冲突。3.3 第三步全盘搜索libittnotify.so既然判断是缺少库那就先在系统里找找看它是不是藏在了某个角落find / -name *ittnotify* 2/dev/null重点检查这几类位置/opt/conda/libconda主环境库目录/usr/lib/x86_64-linux-gnu系统库目录/opt/intelIntel相关工具默认安装目录/usr/local/lib手动安装的库往往在这我当时搜完发现系统里居然有两个libittnotify.so分别在/opt/intel/oneapi/vtune/latest/lib64和/opt/conda/lib。但探针下发现conda/lib里的那个是个空壳版本缺少iJIT_NotifyEvent符号而oneapi里的才是完整版。这正好对应了第二类诱因——路径污染。3.4 第四步检查环境变量和ldconfig确认了问题库存在却未被使用后接着检查加载路径顺序echo $LD_LIBRARY_PATH cat /etc/ld.so.conf.d/*.conf在我这个案例里LD_LIBRARY_PATH按顺序包含了/opt/conda/lib和/opt/intel/oneapi/vtune/latest/lib64而系统默认先加载了conda/lib里的空壳版本导致后面正确的库没有上场机会。如果ldd输出显示某个库仍指向旧路径还可以用ldconfig -p | grep ittnotify查看系统缓存里注册的库路径。不过容器环境里ldconfig的作用有限主要还是看LD_LIBRARY_PATH的优先级。4. 解决方案与实操对照4.1 方案一从Intel oneAPI或VTune中提取ittnotify库如果你系统里已经安装了Intel oneAPI、VTune、Advisor等工具可以直接把完整的libittnotify.so复制到torch的lib目录并把该目录加入加载路径。操作如下# 找到完整库文件 find /opt/intel -name libittnotify.so* 2/dev/null # 假设完整库在 /opt/intel/oneapi/vtune/latest/lib64 cp /opt/intel/oneapi/vtune/latest/lib64/libittnotify.so.12 /opt/conda/xxx/torch/lib/ # 创建软链接方便加载器识别 cd /opt/conda/xxx/torch/lib ln -sf libittnotify.so.12 libittnotify.so这样做的原理是ldd解析libtorch_cpu.so时会先查找同一目录下的依赖库找不到再去LD_LIBRARY_PATH里找。把库放到torch/lib下相当于给这个特定库加了“私货”既不污染全局环境也不会影响到其他应用。需要注意复制前先确认库本身完整用nm -D验证一下它里面确实有iJIT_NotifyEvent符号。不要复制到一半发现也是个残废版本那就白折腾了。4.2 方案二使用conda安装Intel组件如果不想从oneAPI里手动拷文件可以试试直接用conda装Intel提供的ITT运行时。在部分conda源中Intel将相关库打成了独立package名称通常是intel-ittng、intel-ittnotify或者intel-cmplr-lib-rt。不过不是所有源都有且包名在不同版本间会变化。比较稳的做法是添加Intel官方conda channel后安装conda install -c intel intel-ittnotify如果这条命令找不到包再试conda install -c conda-forge ittnotify。装上之后库会出现在conda环境的lib目录下理论上torch的libtorch_cpu.so就能自动找到它。实际效果取决于包内的库版本是否包含你需要的符号因此装完以后务必重新跑一次ldd验证。4.3 方案三升级或降级PyTorch版本如果手动补库对你来说太麻烦最省心的方案是换一个不依赖ITT的PyTorch版本。不同版本对ITT的支持策略不同比如有些Linux构建把USE_ITT编译选项关掉了就不会出现这个错误。具体做法是pip install --force-reinstall torch1.13.1或者升级到更新版本比如pip install --force-reinstall torch --index-url https://download.pytorch.org/whl/cpu我测试过几个版本在某个Linux镜像上torch 1.12.1可以正常导入但1.13.0就报错。这确实有点看运气所以建议带着当前conda环境Python版本和操作系统信息去GitHub的PyTorch issue里搜一下对应版本是否有类似报告。如果暂时找不到完美匹配的版本也可以退回你之前能正常运行的那个版本。需要注意换版本可能引入其他依赖变化比如某些用torchvision或者torchaudio的代码版本要一起配套调整否则会有新的ImportError。不要只换torch把全家桶一起锁版本更稳妥。4.4 方案四源码编译PyTorch禁用ITT不推荐如果你有特殊需求必须固定某个torch版本且不愿意手动补库那就只能从源码重新编译并在构建时关闭ITT插桩USE_ITT0 python setup.py build编译过程中scons或cmake会根据这个环境变量决定是否链接ITT库。不过源码编译PyTorch非常耗时还要处理一堆编译依赖比如Abseil、Protobuf、MKL等。如果你只是想要一个能跑的版本没有分析性能到VTune级别的需求建议先试试前三种方案不要一上来就走到编译这条不归路。4.5 方案五调整LD_LIBRARY_PATH或设置LD_PRELOAD如果系统里有正确的libittnotify.so只是加载顺序不对可以通过调整LD_LIBRARY_PATH让正确的库目录排在最前面export LD_LIBRARY_PATH/opt/intel/oneapi/vtune/latest/lib64:$LD_LIBRARY_PATH不过这个办法有时候不稳定因为其他程序可能也会受到影响。更“精准”的方式是用LD_PRELOAD直接预加载正确的库export LD_PRELOAD/opt/intel/oneapi/vtune/latest/lib64/libittnotify.so.12 python your_script.pyLD_PRELOAD会强制提前加载这个库到进程里从而让libtorch_cpu.so解析符号时能从中找到iJIT_NotifyEvent。这个方法做测试很管用但不推荐用于生产环境因为LD_PRELOAD会影响所有程序如果某个程序依赖另一个版本的ITT库反而会出问题。5. 完整实操案例在Docker容器中修复该错误5.1 现场环境为了让你有更具体的参考我把当时修复的完整过程复现一遍。环境如下Docker镜像pytorch/pytorch:1.13.0-cuda11.6-devel从中单独提取conda环境使用Python3.8.13torch1.13.0问题出现时机执行python -c import torch时立刻报错系统是Ubuntu 20.04容器基础包里有Intel oneAPI相关组件但并非官方统一安装而是历史遗留。LD_LIBRARY_PATH里面按顺序有/opt/conda/lib、/opt/intel/oneapi/vtune/latest/lib64、/usr/local/cuda/lib64。5.2 诊断过程实录我先用ldd快速定位缺失依赖ldd /opt/conda/xxx/torch/lib/libtorch_cpu.so | grep -i not found输出libittnotify.so.12 not found接着搜索系统里所有ittnotify相关文件find / -name *ittnotify* 2/dev/null结果找到了/opt/conda/lib/libittnotify.so这是一个链接文件指向一个极小版本/opt/conda/lib/libittnotify.so.1真实文件但用nm -D检查后里面没有 iJIT_NotifyEvent/opt/intel/oneapi/vtune/latest/lib64/libittnotify.so.12完整版本nm -D后确认有iJIT_NotifyEvent这里就清楚了系统里存在一个老版本的libittnotify.so但它不包含新的符号而新版VTune里的库是完整的却因为LD_LIBRARY_PATH顺序问题没有被加载到。5.3 修复操作我选择了最稳妥的“私货拷贝”方案把完整库复制到torch/lib目录并修改LD_LIBRARY_PATH确保torch/lib优先cd /opt/conda/xxx/torch/lib cp /opt/intel/oneapi/vtune/latest/lib64/libittnotify.so.12 . # 同时还拷一个不带版本号的软链接避免部分加载器找不到 ln -sf libittnotify.so.12 libittnotify.so # 把torch/lib放到LD_LIBRARY_PATH最前面 export LD_LIBRARY_PATH/opt/conda/xxx/torch/lib:$LD_LIBRARY_PATH接着再次检查依赖是否全部解决ldd libtorch_cpu.so | grep itt输出变为libittnotify.so.12 /opt/conda/xxx/torch/lib/libittnotify.so.12OK加载路径正确了。然后重新导入torch进行验证python -c import torch; print(torch ok, torch.__version__)这次顺利导入版本号也打印出来了。为了防止每次启动都要手动设置LD_LIBRARY_PATH我把这个环境变量写进了Docker容器的/etc/profile.d/脚本和conda环境的activate.d/里这样一进环境就是正确的。5.4 验证与后续建议修复完成不代表完了还要跑一遍原来的训练脚本确认模型前向、反向、优化器更新都没问题。我用一个小的MNIST分类脚本做了10步mini-batch训练loss正常下降GPU利用率正常说明torch底层库加载没问题了。建议你修复后把这类问题涉及的排查命令记录下来做成一个小小的checklist脚本下次再遇到同类环境问题一键跑一遍能省很多时间。尤其当你维护多个Docker镜像或conda环境时这种问题会反复出现手动一步步排查效率太低。6. 同类问题速查与避坑心得6.1 常见PyTorch导入错误速查表除了iJIT_NotifyEventtorch导入阶段还有几个高频错误我整理成表方便你遇到时快速对比报错特征常见原因推荐处理undefined symbol: iJIT_NotifyEvent缺少Intel ITT库或版本不匹配拷贝正确的libittnotify.so / 重装torchundefined symbol: omp_get_num_procsOpenMP运行时库冲突调整libiomp5.so加载顺序或设置KMP_DUPLICATE_LIB_OKcannot import name load_workbook from openpyxlopenpyxl版本过旧pip install -U openpyxlcannot import name mesh from simpegSimPEG版本与API不匹配升级/降级SimPEG检查import路径cannot import name get_running_loop代码在旧版本asyncio环境下运行升级Python/asyncio相关库ImportError: /usr/lib/... libcudart.so not foundCUDA库未加入路径安装/配置CUDA toolkit或设置LD_LIBRARY_PATH注意这些只是常见组合实际报错可能因为系统环境差异而变化。遇到问题先别慌核心思路就是看ldd的not found、看符号表、看库搜索路径三步定位法能覆盖大部分动态库问题。6.2 我的个人经验与避坑技巧动态库符号问题本质上就是“运行时依赖的库与编译时的预期不一致”。基于我的经验有几点想特别提醒第一不要轻易重装系统或删除某个看起来可疑的Intel组件。IT库很多软件依赖删了可能导致VTune或其他性能分析工具罢工。优先采用“局部拷贝到torch/lib”的方式把影响限制在最小范围。第二LD_LIBRARY_PATH的优先级要心里有数。不要为了图方便直接把它设置为全局环境变量特别是容器里很容易出现“修复了A弄坏了B”的情况。更建议给每个项目创建一个虚拟环境或独立的conda环境并显式设置环境变量。第三在Docker镜像中如果使用预构建的PyTorch镜像尽量保留原始的库目录结构。很多人喜欢把conda lib目录和系统的/usr/local/lib混在一起改这会让动态链接器的搜索顺序变得非常混乱。如果必须修改建议用ldd验证每一步的影响。第四版本锁定期望值不要太高。PyTorch小版本之间的编译选项确实可能变化遇到bug时回退或升级到相邻patch版本往往比手动修库更快。但回退前一定做好环境快照用conda env export或docker commit保存状态避免回不去。6.3 关于环境隔离的几点建议这次事故之后我把各大项目环境重新梳理了一遍有三个变化非常受益第一个给每个项目建独立的conda环境并记录完整的环境依赖清单。使用conda env export environment.yml定期备份万一出了幺蛾子能快速重建。第二个将torch的lib目录视为“受保护区域”不随便手动往里塞库。如果必须补某些依赖优先通过pip或conda包安装而不是手动拷贝除非没有其他办法。手动拷贝虽然能救急但维护成本很高。第三个写一个加载时自检脚本。每次进环境后先试试python -c import torch如果这一步过了再加载模型跑推理。这样可以把动态库问题扼杀在“程序启动”阶段而不是等训练到一半才爆炸。如果你也在用Docker跑深度学习任务建议把这些问题排查写进镜像构建脚本的注释里或者放到项目的README中团队其他人遇到同样问题直接看文档即可不用再从头踩一遍坑。最后分享一个小经验遇到undefined symbol别先急着怪torch多想一想它“依赖什么但没带什么”。Linux下的动态库依赖链本来就容易被环境变量、版本残留给搞乱。修复这类问题的过程虽然折腾但你会对操作系统的程序加载机制有更清晰的认识。往后遇到类似“libxxx.so: undefined symbol”报错都能第一时间想到ldd和LD_LIBRARY_PATH也就不那么慌了。
返回列表