ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

PyG导入报错Symbol not found:PyTorch版本错配排查与修复

PyG导入报错Symbol not found:PyTorch版本错配排查与修复 你有没有遇到过这种报错从环境里import torch_geometric的时候屏幕上直接甩出一行天书Symbol not found: __ZN2at8internal13_parallel_runExxxRKNSt3__18functionIFvxxmEE我第一次看到的时候整个人是懵的。后来查了一圈资料才明白这不是PyG的Bug踩到我头上了而是典型的环境版本错配问题。这个错误在macOS上尤其多几乎每个玩图神经网络的朋友都有可能会撞上。如果你也卡在这别慌这篇文章会带你从报错文本的每一个字符出发搞懂它到底在说什么然后一步步把环境修好。我也踩过好几次坑有些解法是文档里压根不会写的这次一并唠叨给你听。1. 先搞清楚这个报错到底在说什么1.1 报错文本拆解一段被撕碎的C签名这串乱码不是PyG故意来恶心你的它是一段C符号mangled symbol在动态链接时找不到的报错。我们把这串东西拆开看__ZN2at8internal13_parallel_runExxxRKNSt3__18functionIFvxxmEE它其实对应的是at::internal::parallel_run(long, long, long, std::__1::functionvoid(long, long, unsigned long) const)如果你了解C的命名规则就知道__ZN后面是名字RKNSt3__18function...是引用类型和参数。这个符号是PyTorch内部的一个并行执行函数。PyG的C扩展比如torch-scatter、torch-sparse在编译的时候会调用这个函数。如果编译PyG时链接的PyTorch库和你运行时实际加载的PyTorch库不是同一个版本或者ABI层面发生了改变那么这个符号就不存在或者签名变了动态链接器在加载.so/.dylib的时候就会直接抛出“Symbol not found”。再直白一点PyG的扩展是一栋预制好的房子它内部留了一个接口这个接口需要接在特定版本的“地基”上。你房子的接口型号是给旧地基用的但你现在的地基已经升级换代了接口对不上自然就装不起来。1.2 我遇到这个报错的实景当时我是在macOS上跑一个GAT模型环境大概是Python 3.9用pip装好了torch 1.13.0然后又直接执行了pip install torch-geometricpip顺利装完还自动带上了scatter、sparse这些依赖。结果进入Python解释器执行import torch import torch_geometric前一句很正常后一句直接抛出类似OSError: dlopen(...): Symbol not found: __ZN2at8internal...的错误。我马上怀疑是pip把PyG的某个包升级成了不兼容的版本。查了一下发现pip安装的torch-sparse是针对PyTorch 2.0编译的而当前环境还是1.13.0。这就是典型的版本错配。其实这个错误在Linux上也会出现但macOS上因为动态库链接机制更不容忍加上很多同学习惯pip和conda混用问题爆发的频率就更高。2. 为什么会出现Symbol not found版本错配是万恶之源2.1 PyG的二进制编译与PyTorch的C接口绑定关系图神经网络里最常用的几个扩展库——torch-scatter、torch-sparse、torch-cluster、torch-spline-conv它们都是用C写的PyTorch扩展。PyTorch为了支持自定义算子对外暴露了一套C接口这些接口里的很多函数属于PyTorch内部实现PyG的代码会直接调用它们。但随着PyTorch版本迭代比如1.12到1.13内部函数的参数数量或者类型都有可能调整。PyG的扩展包如果针对1.13编译就调用1.13的parallel_run版本但如果你的torch是1.12动态库里的parallel_run签名可能还是旧的于是找不到新版符号。这里有个关键概念C不是C重载函数的符号是不带参数类型的话根本无法区分。所以PyTorch内部函数的签名一变编译出来的mangled名字就变了。你在屏幕上看到的__ZN2at8internal13_parallel_runExxxRKNSt3__18functionIFvxxmEE就是带有完整参数类型的符号。如果签名不匹配系统连找都找不到对应名字。这就像你去机场接一位朋友你手里拿的牌子写的是“张三身高180”可那位朋友已经换成“张三身高175”的牌子你自然找不到人。2.2 为什么在macOS上格外常见macOS的动态库机制和Linux不一样。Linux有symbol versioning一些符号可以带版本号兼容性稍微好一点。macOS的dylib没有这么强的版本控制能力链接时对符号参数的匹配更严格。另外绝大多数同学在macOS上装Python环境时喜欢用conda然后发现conda里没有某个包又去用pip装。而conda的PyTorch和pip的PyTorch在安装路径、链接库结构上都可能不一致导致PyG扩展链接到了conda的torch库但运行时加载的是pip的torch库或者反过来。还有PyG官方虽然提供whl但macOS的wheel有时候编译时选择的libtorch路径来自某一个特定的PyTorch版本如果你用conda渠道的PyTorch就很容易出问题。我见过一个极端案例conda里装了pytorch 1.12pip里又装了pytorch 2.0两个环境的Python路径一模一样因为conda环境的site-packages里被pip硬塞了东西然后PyG的扩展也不知道自己该链接哪个导弹直接失控。这种情况没法靠修改代码解决只能把环境彻底重建。2.3 版本错配的三种常见场景根据我自己的经验和在GitHub Issues里翻到的帖子这个错误最常见的出现方式有三种场景典型操作结果纯pip安装但未指定PyG依赖版本pip install torch-geometricpip会拉取最新版本的torch-scatter等可能与已装torch不匹配conda和pip混装conda install pytorchpip install torch-scatterPyG扩展可能链接到不同torch库运行时符号冲突从源码编译CMAKE_PREFIX_PATH指向错误自己构建PyG时未指定正确的libtorch路径编译链接用了旧版本符号实际运行却是新版本torch这三种场景里第一种最普遍。我身边很多人都是直接pip install torch-geometric装完就以为万事大吉。其实PyG官方对版本匹配的要求非常苛刻它提供的预编译包必须和PyTorch的精确版本对应。你装的是1.13.0它给你的这个包可能对应1.13.1看似相同但内部符号已经有了细微差别。3. 排查与定位一步步把问题揪出来3.1 先确认你现在的PyTorch和PyG版本不要凭记忆判断直接在终端里跑python -c import torch; print(torch.__version__) python -c import torch_geometric; print(torch_geometric.__version__)如果你还能看到torch_geometric版本号说明Python层面的包还是能import的报错只是在import torch_geometric内部触发到C扩展时才出现。如果连版本号都打印不出来那就是连Python包都坏了。同时我强烈建议你把当前环境的完整pip包列表导出留档pip debug --verbose pip list pip_list_before_fix.txt这样你后面改完环境还能对比看到底动了哪些包。3.2 用otool/nm检查动态库依赖下一步找出PyG扩展包的文件位置python -c import torch_geometric, pathlib; print(pathlib.Path(torch_geometric.__file__).parent)在torch_geometric目录下你会看到很多_*.so或者_*.dylib文件比如_scatter.so、_sparse.so。用otool查看它依赖哪些动态库otool -L /your_path/torch_geometric/_scatter.so这里会列出这个扩展链接的所有libtorch相关库。你需要重点看libtorch_python.dylib和libtorch.dylib的路径到底指向哪里。正常情况下它们应该指向当前torch包内部的lib目录。比如/usr/local/lib/python3.9/site-packages/torch/lib/libtorch.dylib。如果指向了/opt/anaconda3/pkgs/里某个旧版本那问题就找到了。另外在Linux上用ldd命令ldd /your_path/torch_geometric/_scatter.so | grep torch3.3 检查是否真的存在符号接下来验证一下当前torch库中是否有PyG需要的那个符号。先找到torch的安装路径python -c import torch, pathlib; print(pathlib.Path(torch.__file__).parent / lib)然后执行nm命令nm -U /you/lib/libtorch.dylib | grep parallel_run你也可以用nm -g查看所有动态符号。如果发现libtorch.dylib里面有类似__ZN2at8internal13_parallel_run...的符号说明这个符号存在于torch库那问题多半是PyG扩展链接到了别的库而不是当前加载的这个。这个方法虽然有点粗糙但能帮你快速判断方向。3.4 使用DYLD_PRINT_LIBRARIES抓取运行时加载过程macOS上有一些动态链接器调试环境变量很有用DYLD_PRINT_LIBRARIES1 python -c import torch_geometric这样运行时所有被加载的动态库都会打印出来。你会看到PyG扩展在加载之前系统到底去哪些路径找libtorch.dylib。注意macOS的DYLD_PRINT_LIBRARIES在较新系统上可能需要先关闭SIP才能生效如果没输出也别纠结我们有更轻量级的方法import torch; print(torch.__file__)检查torch文件路径和otool里显示的路径是否一致。我见过最奇葩的情况是PyG扩展在otool里看到的是/Library/Developer/CommandLineTools/...下某个遗留的libtorch而当前Python导入的torch在conda的site-packages里二者根本不是一个世界。4. 解决方案直接有效的方法与命令4.1 方案一使用官方推荐的安装方式PyG官方文档里给出了一个标准流程很多人跳过了我在这里强烈建议别走捷径。先确认你的torch版本比如1.13.0并且确认你的CUDA版本macOS上当然是CPU版。然后按照PyG官网的轮子方式安装配套扩展包。执行pip install torch-scatter torch-sparse torch-cluster torch-spline-conv -f https://data.pyg.org/whl/torch-1.13.0cpu.html注意这里URL里的torch-1.13.0cpu要和你的torch版本完全一致CPU还是CUDA也要对应。macOS上通常只有cpu版本。如果你用的是2.0那就是torch-2.0.0cpu。装完这些再装torch-geometricpip install torch-geometric很多人的问题就是跳过了这个步骤让pip自动解析依赖结果pip可能拉了一个基于不同torch版本编译的旧wheel包。4.2 方案二升级PyG或者让所有扩展包从源码编译如果你需要的PyG版本较新但还是报错可以考虑使用最新的PyG版本因为新版本对ABI兼容性容忍度高一些。比如PyG 2.3及以上对于不同的小版本号适配会好一点但并不能从根本上解决。还有一种更彻底的思路让PyG的全部C扩展都从源码编译确保它们基于你当前的torch环境生成。pip install --no-binary :all: torch-scatter torch-sparse torch-cluster torch-spline-conv源码编译会花几分钟时间但能保证链接到当前环境下的libtorch。不过源码编译需要你系统里有C编译器macOS上需要安装Xcode Command Line Toolsxcode-select --install另外还需要gcc和g可用。编译过程中如果报C14语法的错那是正常的因为PyG的扩展包需要C14标准新版本Xcode默认支持。4.3 方案三统一环境重建conda环境我的首选这个方案是我最常用的十次里有九次能解决。既然已经乱成麻不如直接推倒重来。创建一个全新的conda环境conda create -n pyg python3.9 -y conda activate pyg在conda环境里先安装pytorch这里我建议用conda安装因为它会帮你装好兼容的依赖conda install pytorch1.13.0 -c pytorch确认一下torch版本和路径。然后再用pip安装PyG相关包并且一定要带上官方wheel urlpip install torch-scatter torch-sparse torch-cluster torch-spline-conv -f https://data.pyg.org/whl/torch-1.13.0cpu.html pip install torch-geometric注意在这个新环境里你绝对不要再运行conda install pytorch之外的任何conda包乱装了。尤其是不要通过conda install pyg如果有的话去装那样会破坏统一性。我试过很多次只要严格不混用conda和pip的torch基本都能跑通。我还习惯在装完PyG后再跑一个环境检查脚本把torch和PyG的版本以及每个扩展包对应的编译torch版本都打出来python -c import torch; print(torch.__version__) python -c import torch_geometric; print(torch_geometric.__version__)4.4 方案四从源码编译PyG的C扩展如果上面这些都不满足你或者你正在开发PyG想改动它的源码那我们就直接从源码编译。先拉取PyG的源码git clone https://github.com/pyg-team/pytorch_geometric.git cd pytorch_geometric pip install -e .然后单独编译scatter等扩展包。这些包是独立的需要先克隆各自仓库git clone https://github.com/rusty1s/pytorch_scatter.git cd pytorch_scatter python setup.py install如果你之前用--no-binary都没成功可能是CMAKE_PREFIX_PATH没设置好。在编译前你需要告诉构建系统在哪找libtorch。最直接的办法是设置环境变量export CMAKE_PREFIX_PATH$(python -c import torch; print(torch.utils.cmake_prefix_path))然后重新运行setup.py。这个变量会告诉CMake你需要的Torch库就在这里别去系统里乱找。我有个血的教训有一次我没设CMAKE_PREFIX_PATH结果CMake找到了我之前用Homebrew安装的某个旧libtorch编译出的扩展包完全不能用运行时立刻报Symbol not found查了半天才找到原因。所以不管是用源码编译还是重装都务必先执行这句。4.5 终极方案绕开这些C扩展PyG的很多功能其实并不是强依赖这些C扩展。torch-scatter、torch-sparse这些扩展主要提供一些高性能算子。如果你只是跑一些基础的GCN、GAT模型PyG内部有纯Python的fallback实现。在某些情况下你可以直接卸载这些扩展包或者说让PyG不要导入它们。例如你可以把不需要的扩展包直接pip uninstallpip uninstall torch-scatter torch-sparse torch-cluster torch-spline-conv然后再试试import torch_geometric。如果PyG版本较新它可能会提示你缺少某些依赖但有些核心功能依然能跑。这是个很不优雅的权宜之计但当你只是临时用一下PyG的图形数据结构、MessagePassing骨架时效果还是不错的。我自己用这种方式处理过几个客户的服务器他们不想重建生产环境我就用这个“缩水版”PyG续了几天命。5. 常见问题速查与避坑清单5.1 常见问题场景表这里整理了我实际遇到或者帮别人排查过的几种类似场景按症状、原因、解法分列报错内容可能原因快速解法Symbol not found: __ZN2at8internal13_parallel_run...PyG扩展编译用的torch版本与当前torch不一致按4.1重装扩展包undefined symbol: _ZNK2at...conda与pip混合安装导致的libtorch路径错乱重建conda环境统一用pip或condaImportError: dlopen(... no suitable image found)扩展包的dylib架构不对比如arm64装成x86_64检查file命令确认架构重新安装匹配版本运行时有多个libtorch被加载系统里存在其他torch残留路径使用DYLD_PRINT_LIBRARIES定位删除残余这个表格是给你应急的别按图索骥看到报错就对号入座。建议每个case都按第3章的方法快速确认一下。5.2 避坑心得版本锁定的正确姿势经过这么多次折腾我总结出一个习惯——永远不要把PyG的版本号写成大范围。比如在requirements.txt里不要写torch-geometric2.0而是写具体版本并且最好连同它的扩展包版本一起锁定torch1.13.0 torch-scatter2.1.0 torch-sparse0.6.16 torch-cluster1.6.0 torch-spline-conv1.2.1 torch-geometric2.3.0这些扩展包的版本号要和torch版本匹配。怎么看匹配去PyG的官方wheel列表页找或者直接先用第4.1节的URL安装然后记录下实际装好的版本。以后部署到服务器或者换机器时就按记录下来的版本装。另外如果你用conda环境建议在环境建好后执行conda env export environment.yaml这样你就能随时复现一模一样的环境。别再自己手动一个个pip安装了那是在给自己埋雷。5.3 我的调试小技巧调试这种符号链接问题我最常用的一套流程是先打印torch.__version__和torch.__file__确保只有唯一一个torch在site-packages中。用find / -name libtorch.dylibmacOS把所有libtorch找出来看看是不是有多个。用otool -L检查PyG扩展链接的libtorch路径确认和当前torch的lib路径一致。如果有多个路径最省事的是重建环境不要去删那些残留文件因为删错了可能把系统搞坏。如果你用的是Anaconda还经常出现一个情况pip安装的torch会被装到conda环境的site-packages里但libtorch所在的目录还是在conda的libs里。这时候PyG扩展链接到的libtorch其实是conda包的而python导入的torch是pip的二者版本不同。你需要在创建环境时就用python -m pip install torch而不是conda install torch让pip把torch完整装在同一个site-packages里。我自己在macOS上更倾向于完全用pip装torch和PyG因为pip打包的torch内部自带了全套libtorch路径清晰不容易出岔子。最后再说一个很多人不知道的小细节在改完环境后一定要重启你的Python进程或Jupyter Kernel。不要在一个已经启动的会话里重装包然后立刻import。动态链接缓存有时候会保留旧状态导致你看着版本貌似对了但依然报错。遇到过好几次明明重装好了一import还是老错误把kernel重启一下直接解决。这个坑值得你记在笔记里。
返回列表