ARTICLE DETAIL

资讯详情

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

vLLM-Ascend 启动失败排查:动态库、EngineCore 与环境变量

vLLM-Ascend 启动失败排查:动态库、EngineCore 与环境变量 vLLM-Ascend 启动失败一般不会只给一条干净报错最常见的是三类libatb.so 动态库加载失败、EngineCore 进程起不来、et_env.sh 没真正生效或顺序不对。我最近在昇腾 NPU 环境里连续踩了这几类坑最麻烦的不是修而是日志里父进程只喊一句 EngineCore died子进程真正的 .so 加载错误被藏在后面。这套排查思路适合刚接触 vLLM-Ascend 的部署同学也适合已经在跑单卡、想上多卡推理的老手。你不需要先精通 CANN、ATB、HCCL只要按启动链路把“环境变量有没有进到进程”“动态库能不能被找到”“EngineCore 子进程为什么退出”这三件事拆开就能把大部分启动失败定位到具体文件、具体变量或具体卡号。下面我按故障类型讲遇到哪类就跳到哪节最后再给一份速查表和自己的避坑记录。1. 先分清三类故障vLLM-Ascend 启动失败到底卡在哪1.1 从拉起命令到 EngineCore 子进程的启动链路一条常见的 vLLM-Ascend 启动命令表面看只是执行了一个 Python 入口背后其实分成了好几段。第一段是 shell 环境准备也就是 source CANN 的 set_env.sh、ATB 的 set_env.sh、你项目里的 et_env.sh或者用 conda、venv、容器 entrypoint 把这些变量注进去。第二段是 Python 主进程导入 torch、torch_npu、vllm、vllm_ascend这一步会加载大量动态库libatb.so 通常就在这里被牵出来。第三段是 vLLM 初始化引擎V1 架构下常见的是主进程拉起 EngineCore 子进程由子进程负责调度、加载模型、创建 NPU 执行环境。第四段才是真正跑模型绑定卡、初始化 HCCL、分配显存、启动 API 服务。这三类故障之所以经常混在一起是因为报错位置和根因位置经常不在同一层。比如 libatb.so 找不到可能在父进程导入时直接报 ImportError也可能在 EngineCore 子进程重新导入时只留下一个非零退出码。et_env.sh 写错又更隐蔽因为当前 shell 里变量看着全对但子进程如果用了 spawn、systemd、docker exec环境没继承过去等价于没 source。我的建议是不要把“vLLM 起不来”当成一个整体问题而是按链路切成四段每段只回答一个是非题shell 变量对不对Python 主进程导入过不过EngineCore 子进程为什么退NPU 设备和通信能不能初始化。1.2 三类症状与日志关键词对照下面这张表是我平时用来做第一轮分流的看到报错先对照关键词不要急着改代码。只要能把故障落到某一类后面的排查范围会小很多。故障类别典型日志关键词高概率原因优先检查libatb.so 加载失败libatb.so: cannot open shared object file、undefined symbol、ImportErrorLD_LIBRARY_PATH 没包含 ATB 库目录ABI 选错CANN 与 ATB 版本不配套ldd、LD_DEBUGlibs、ATB 安装路径、PyTorch CXX ABIEngineCore 启动失败Engine core died、EngineCore initialization failed、RuntimeError子进程导入失败、端口占用、卡号不可见、共享内存不足、HCCL 初始化失败子进程完整日志、npu-smi info、ss -lntp、df -h /dev/shmet_env.sh 未生效父进程正常、子进程变量缺失、$\r: command not found、source后仍找不到库用./et_env.sh在子 shell 执行source 顺序错误非交互 shell 不读 .bashrcCRLF 换行env这张表的使用方法很直接如果第一条报错里出现 libatb.so就先跳到第 2 节如果只看到 EngineCore died但前面没有明显 .so 报错就先跳到第 3 节同时回头看 et_env.sh如果当前 shell 手动导入没问题一放到 systemd 或 docker exec 就失败那基本就是第 4 节。不要忽略第一行报错很多时候最后一条 traceback 只是父进程在喊“子进程死了”真正的线索在更早的 stderr 里。1.3 排查前的固定动作和最小复现我每次排查前会先做一个最小复现不在完整的 API 服务命令上瞎试而是用 Python 导入和小模型加载把链路拆开。第一步固定检查版本python -c import sys; print(sys.executable) python -c import torch; print(torch.__version__); print(torch._C._GLIBCXX_USE_CXX11_ABI) python -c import torch_npu; print(torch_npu.__version__) python -c import vllm; print(vllm.__version__) python -c import vllm_ascend; print(vllm_ascend.__file__)这一步能区分 Python 环境错、torch_npu 没装好、vLLM 和 vLLM-Ascend 版本不匹配。第二步检查 NPU 是否可见npu-smi info python - PY import torch import torch_npu print(device_count:, torch.npu.device_count()) print(current_device:, torch.npu.current_device()) print(device_name_0:, torch.npu.get_device_name(0)) PY第三步检查环境变量是否进了当前进程env | sort | grep -E ASCEND|ATB|HCCL|LD_LIBRARY_PATH|PYTHONPATH|VLLM第四步再启动最小服务VLLM_LOGGING_LEVELDEBUG python -m vllm.entrypoints.openai.api_server \ --model /data/models/Qwen2.5-0.5B-Instruct \ --device npu \ --port 8000小模型能过再换大模型和多卡。这个顺序能帮你把 libatb.so、EngineCore、et_env.sh 的问题分离开而不是一上来就用完整参数把水搅浑。 注意所有环境变量修改都要在启动 vLLM 的那个 shell 或那个父进程里完成Python 进程启动后再改os.environ对已经加载的动态库没有意义。2. libatb.so 加载失败动态库、CANN 版本和 LD_LIBRARY_PATH 的排查2.1 libatb.so 在 vLLM-Ascend 链路里干什么libatb.so 是昇腾 ATB 加速库的核心动态库之一很多算子融合、图优化、Transformer 相关加速能力会依赖它。vLLM-Ascend 在导入阶段或运行阶段会通过 torch_npu、vllm_ascend 的扩展模块间接加载它。它不是一个纯 Python 包pip 装完 vllm-ascend 并不代表运行时一定能找到 libatb.so因为动态库搜索走的是系统加载器那一套RPATH、RUNPATH、LD_LIBRARY_PATH、ldconfig 缓存。你可以把 Python 包想成说明书libatb.so 想成工具本身说明书到了工具没放到工具箱里启动时一样抓瞎。最常见的报错有两种。一种是libatb.so: cannot open shared object file: No such file or directory说明加载器根本没找到文件。另一种是文件找到了但报undefined symbol或版本相关错误说明找到的 libatb.so 和当前 torch、torch_npu、CANN 不是一套或者 CXX ABI 选错了。很多人只盯着第一种忽略了第二种其实更难查因为它不是路径问题而是版本和编译选项问题。我的经验是先把“找不找得到”和“找到对不对”分开路径问题用 ldd、LD_DEBUG 查符号问题用 readelf、nm 和 ABI 检查查。2.2 用 ldd、readelf、LD_DEBUG 定位是找不到还是找错先找系统里到底有哪些 libatb.sofind /usr/local/Ascend -name libatb.so* 2/dev/null ldconfig -p | grep -i atb然后找到 vllm_ascend 的扩展模块逐个用 ldd 看依赖python - PY import os, vllm_ascend root os.path.dirname(vllm_ascend.__file__) for dirpath, _, filenames in os.walk(root): for name in filenames: if name.endswith(.so): print(os.path.join(dirpath, name)) PY把输出的 .so 路径逐个执行ldd /path/to/vllm_ascend/_C.so | grep -i atb如果显示not found就继续看这个 .so 自己声明的搜索路径readelf -d /path/to/vllm_ascend/_C.so | grep -E RPATH|RUNPATH|NEEDED更直接的是让加载器自己说话LD_DEBUGlibs python -c import vllm_ascend 21 | grep -i atb这个输出会列出加载器搜索过的目录。你重点看三件事它有没有去 ATB 的 lib 目录找找到的是哪个路径这个路径是不是你期望的版本。 注意ldd只反映当前 shell 的搜索环境如果你在 A shell 里 source 了 et_env.sh却在 B shell 里跑 ldd结果没有参考价值。2.3 CXX ABI 选错是 libatb.so 最容易被忽略的坑昇腾 ATB 安装目录下经常能看到cxx_abi_0和cxx_abi_1两种库目录对应不同的 C ABI 编译选项。PyTorch 自己是用_GLIBCXX_USE_CXX11_ABI控制的。如果 PyTorch 是 ABI1而你加载了 cxx_abi_0 的 libatb.so就可能出现符号找不到、段错误、初始化失败。先查 PyTorchpython - PY import torch print(torch._C._GLIBCXX_USE_CXX11_ABI) PY如果输出True优先选cxx_abi_1对应的 lib 目录如果输出False选cxx_abi_0。具体目录名以现场安装为准常见形态是ls /usr/local/Ascend/nnal/atb/latest/atb/ # 看到 cxx_abi_0 cxx_abi_1 之类目录然后把对应目录放到 LD_LIBRARY_PATH 前面export ATB_LIB_DIR/usr/local/Ascend/nnal/atb/latest/atb/cxx_abi_1/lib export LD_LIBRARY_PATH${ATB_LIB_DIR}:${LD_LIBRARY_PATH:-}这里为什么放在前面因为动态库搜索按顺序匹配如果你系统里还有旧版 ATB放在后面可能被旧版抢先加载。放前面能保证当前进程优先用你指定的版本。改完后必须新开 Python 进程验证不能在当前进程里重试。2.4 常见坑conda 污染、pip 覆盖和子进程丢变量我遇到过最迷惑的一次是当前 shell 里ldd明明能找到 libatb.so但 vLLM 一启动就报 EngineCore died。后来发现主进程和子进程用的是不同 Python。主进程在 conda 环境里EngineCore 子进程因为 spawn 重新执行 Python 时PATH 被 conda 的 shim 改过落到了另一个解释器。查这类问题不要只看which python要看type -a python type -a python3 python -c import sys; print(sys.executable)还有一种坑是 pip 安装的 torch_npu 和系统 CANN 版本不配套。pip 包版本号好看不代表和 libatb.so 的接口对得上。遇到undefined symbol不要急着重装 vLLM-Ascend先把 torch、torch_npu、CANN、ATB 四个版本列出来再对照安装说明。最后子进程丢 LD_LIBRARY_PATH 非常常见尤其是 systemd、docker exec、spawn 多进程。验证方法是在启动命令前临时插一句打印python -c import os; print(os.environ.get(LD_LIBRARY_PATH))如果父进程有、子进程没有就要检查 et_env.sh 到底是在哪个 shell 里 source 的以及子进程是不是重新登录了环境。3. EngineCore 启动失败子进程、端口、设备与初始化顺序3.1 EngineCore 为什么单独起进程vLLM 的 V1 架构里EngineCore 通常被拆成独立进程这样做的好处是把调度、模型执行和 API 服务解耦父进程负责 HTTP、参数校验、请求分发EngineCore 负责真正的引擎循环。好处是资源隔离和并发处理更清晰代价是环境变量、动态库、NPU 设备状态都要跨进程传递。父进程能导入 torch_npu不代表子进程也能导入父进程能看到卡不代表子进程也能看到同一批卡。很多Engine core died的根因其实是子进程重新初始化时缺了某个变量或者多进程启动方式不兼容。先记住一个原则父进程日志只负责告诉你“子进程没了”不负责告诉你“为什么没”。你要做的第一件事不是改父进程代码而是把子进程的 stderr 找出来。用前台启动、提高日志级别、关闭静默重定向通常能看到完整 traceback。如果日志被重定向到文件就按EngineCore、Traceback、ImportError、libatb、HCCL、ACL_ERROR这些关键字 grep。找不到子进程日志时不要猜先把启动方式改成前台单进程或者临时减少并行度把问题暴露出来。3.2 从日志定位先把子进程堆栈捞出来我常用的一套命令是export VLLM_LOGGING_LEVELDEBUG export VLLM_WORKER_MULTIPROC_METHODspawn python -m vllm.entrypoints.openai.api_server \ --model /data/models/Qwen2.5-0.5B-Instruct \ --device npu \ --tensor-parallel-size 1 \ --port 8000 21 | tee /tmp/vllm_start.log启动失败后grep -nE EngineCore|Traceback|Error|ImportError|libatb|HCCL|ACL|NPU|npu /tmp/vllm_start.log | tail -n 200如果看到Engine core died前后没有任何子进程堆栈可以试试关闭 vLLM 的多进程守护或者直接用 Python 脚本手动初始化 EngineCore。另一个技巧是看子进程退出码echo $?退出码 134 常见于 abort139 常见于段错误1 一般是 Python 异常。退出码只能辅助判断不能替代日志。多卡场景下子进程可能因为某张卡被占用、HCCL 端口冲突、网卡选择错误而退出这些通常在HCCL关键字附近出现。我的建议是先把 tensor parallel 设为 1确认单卡能过再逐张增加卡号最后再开多卡并行。3.3 NPU 设备、HCCL、内存和端口排查EngineCore 子进程要拿到卡依赖ASCEND_RT_VISIBLE_DEVICES。这个变量必须在 Python 进程启动前设置而且父进程和子进程要一致。常见错误是父进程设置了0,1子进程因为 spawn 重新登录环境变量丢了默认拿所有卡结果卡数对不上或者卡被占用。检查命令npu-smi info echo $ASCEND_RT_VISIBLE_DEVICES python - PY import torch import torch_npu print(torch.npu.device_count()) PY如果tensor-parallel-size2至少需要两张可见卡。端口方面vLLM 自身端口和分布式 master port 都可能冲突ss -lntp | grep -E 8000|29600 export MASTER_ADDR127.0.0.1 export MASTER_PORT29600 export VLLM_HOST_IP127.0.0.1共享内存也常被忽略。多进程推理会用到/dev/shm如果容器里默认只有 64MBEngineCore 可能起不来df -h /dev/shmDocker 启动时加--shm-size16g是我比较常用的做法。还有文件句柄和锁内存限制ulimit -n ulimit -l如果报显存不足先看是不是其他进程占着卡不要直接改 batch size。npu-smi info能看到显存占用和进程号确认没有残留进程再启动。3.4 多进程启动方式与 et_env.sh 的传递关系VLLM_WORKER_MULTIPROC_METHOD可以影响子进程如何启动。fork 方式能继承父进程内存和环境但在 NPU、CUDA、线程池场景下容易出诡异问题spawn 方式会重新执行 Python环境更干净但要求所有变量都能从环境里重新读到。我的经验是昇腾多卡优先试 spawn然后确保 et_env.sh 在父进程启动前已经 source并且变量通过export导出。只在 shell 里写VARvalue不加 export子进程读不到。验证方法export MY_TEST_VARhello python -c import os; print(os.environ.get(MY_TEST_VAR))如果这个都为 None说明你的子进程环境传递有问题。容器里还要注意docker exec -it进入的 shell 不一定会读/etc/profile或.bashrc更不一定会读你项目里的 et_env.sh。启动命令写成bash -lc source /workspace/et_env.sh python -m vllm...往往比在容器里手动 source 更可靠。4. et_env.sh 的坑source 顺序、变量覆盖与脚本可重复执行4.1 et_env.sh 通常要设置哪些变量et_env.sh 不是 vLLM 官方固定文件更多是项目里为了统一环境写的一层封装。它一般要负责四件事加载 CANN 环境、加载 ATB 环境、设置 Python 包路径、设置 vLLM 和 NPU 相关运行变量。典型变量包括ASCEND_TOOLKIT_HOME、ASCEND_HOME_PATH、ATB_HOME_PATH、LD_LIBRARY_PATH、PYTHONPATH、ASCEND_RT_VISIBLE_DEVICES、VLLM_USE_V1、VLLM_WORKER_MULTIPROC_METHOD。有些环境还会设置HCCL_IF_IP、HCCL_SOCKET_IFNAME、MASTER_ADDR、MASTER_PORT。你要清楚每一项的作用不要从别人那里复制一整坨变量里面很可能有旧路径和过期卡号。我建议把 et_env.sh 分成“必须项”和“可选项”。必须项是 CANN、ATB、Python 路径、卡可见性。可选项是日志级别、分布式端口、调试开关。必须项写错了启动直接失败可选项写错了可能只是性能或日志不好看。最忌讳的是把绝对路径写死到某个用户目录比如/home/old_user/Ascend换机器必然崩。能用${ASCEND_TOOLKIT_HOME}拼出来的路径就不要手写死。4.2 source 顺序为什么比内容更致命很多人只检查 et_env.sh 内容却忽略 source 顺序。常见顺序应该是先 source CANN 的 set_env.sh再 source ATB 的 set_env.sh再 source 自定义 et_env.sh最后激活 Python 虚拟环境或 conda。原因很简单CANN 和 ATB 的脚本会往LD_LIBRARY_PATH、PATH、PYTHONPATH里追加路径自定义脚本要在它们之后做覆盖和补丁。如果你先 source et_env.sh再 source CANNCANN 可能把旧路径插到前面导致加载器优先找到旧版本。如果你先激活 conda再 source CANNconda 的库路径可能被挤到后面Python 扩展模块又可能找不到自己的依赖。还有一个经常被忽略的点source et_env.sh和./et_env.sh完全不同。source在当前 shell 执行变量会留下来./et_env.sh会开子 shell脚本执行完变量就没了。如果你执行./et_env.sh后觉得“明明跑了脚本怎么还找不到库”就是这个原因。检查方式source ./et_env.sh echo $LD_LIBRARY_PATH | tr : \n | grep -i atb如果输出为空要么脚本没设置要么你用了子 shell要么脚本中途报错退出。4.3 写一个可打印、可校验、可回滚的 et_env.sh下面是我常用的 et_env.sh 骨架变量名和路径要按你的现场安装改。重点是每一步都有判断加载失败能看出来拼接 LD_LIBRARY_PATH 时用了${LD_LIBRARY_PATH:-}避免未定义变量导致脚本异常退出。#!/usr/bin/env bash # et_env.sh按实际路径修改不要直接照抄生产路径 export ASCEND_TOOLKIT_HOME/usr/local/Ascend/ascend-toolkit/latest if [ -f ${ASCEND_TOOLKIT_HOME}/set_env.sh ]; then source ${ASCEND_TOOLKIT_HOME}/set_env.sh else echo [et_env] CANN set_env.sh not found: ${ASCEND_TOOLKIT_HOME} 2 fi export ATB_HOME_PATH/usr/local/Ascend/nnal/atb/latest if [ -f ${ATB_HOME_PATH}/set_env.sh ]; then source ${ATB_HOME_PATH}/set_env.sh else echo [et_env] ATB set_env.sh not found: ${ATB_HOME_PATH} 2 fi # 根据 PyTorch CXX ABI 选择True 用 cxx_abi_1False 用 cxx_abi_0 export ATB_CXX_ABI_DIR${ATB_HOME_PATH}/atb/cxx_abi_1/lib if [ -d ${ATB_CXX_ABI_DIR} ]; then export LD_LIBRARY_PATH${ATB_CXX_ABI_DIR}:${LD_LIBRARY_PATH:-} else echo [et_env] ATB ABI dir not found: ${ATB_CXX_ABI_DIR} 2 fi export PYTHONPATH/workspace/vllm-ascend:${PYTHONPATH:-} export ASCEND_RT_VISIBLE_DEVICES${ASCEND_RT_VISIBLE_DEVICES:-0} export VLLM_USE_V11 export VLLM_WORKER_MULTIPROC_METHODspawn export VLLM_LOGGING_LEVEL${VLLM_LOGGING_LEVEL:-INFO} echo [et_env] ASCEND_TOOLKIT_HOME${ASCEND_TOOLKIT_HOME} echo [et_env] ATB_HOME_PATH${ATB_HOME_PATH} echo [et_env] ASCEND_RT_VISIBLE_DEVICES${ASCEND_RT_VISIBLE_DEVICES} echo [et_env] LD_LIBRARY_PATH${LD_LIBRARY_PATH}这个脚本每次 source 都会打印关键变量排查时非常有用。 注意如果 et_env.sh 会被 systemd、supervisor、docker entrypoint 反复执行要保证可重复执行不要每次都无条件在 PATH、LD_LIBRARY_PATH 前面重复追加否则变量会越来越长。可以在追加前先判断是否已包含或者接受少量重复但保证优先顺序正确。4.4 容器、systemd、conda 场景下的加载差异容器里最常见的问题是非交互 shell 不加载 .bashrc。你手动docker exec -it进去source 一下 et_env.sh服务能起但容器 entrypoint 里执行 Python它不会自动 source。解决办法有三类一是把 et_env.sh 写进 entrypoint由 entrypoint 先 source 再exec $二是用BASH_ENV/workspace/et_env.sh让非交互 bash 也加载三是直接把启动命令写成bash -lc source /workspace/et_env.sh python -m vllm...。systemd 场景同理EnvironmentFile只能读 KEYVALUE不能执行source所以要把变量拆成纯环境文件或者在 ExecStart 里调用 bash 包装脚本。conda 场景要注意激活顺序。conda activate 会改 PATH、PYTHONPATH有时还会改 LD_LIBRARY_PATH。我的习惯是先 source CANN、ATB再 conda activate最后 source 自定义 et_env.sh 做覆盖。这样 et_env.sh 里的 Python 路径、LD_LIBRARY_PATH 优先级最高。验证时可以打印type -a python echo $LD_LIBRARY_PATH | tr : \n | head -n 20 python -c import torch_npu; print(torch_npu ok)如果type -a python第一个不是你期望的解释器后面所有包检查都不要信。还有一个小坑是 Windows 上编辑 et_env.sh 后带 CRLFLinux 下 source 会报$\r: command not found。用下面命令检查和处理file et_env.sh sed -n 1p et_env.sh | cat -A sed -i s/\r$// et_env.sh5. 一次完整复现与排查实录从报错到跑通5.1 故障现场三份日志叠在一起现场环境是单机 2 张昇腾卡CANN 和 ATB 已安装vLLM-Ascend 从源码安装。启动命令如下source /usr/local/Ascend/ascend-toolkit/set_env.sh source /usr/local/Ascend/nnal/atb/set_env.sh source ./et_env.sh python -m vllm.entrypoints.openai.api_server \ --model /data/models/Qwen2.5-7B-Instruct \ --device npu \ --tensor-parallel-size 2 \ --port 8000第一份日志是父进程报ImportError: libatb.so: cannot open shared object file。手动ldd发现 vllm_ascend 扩展模块没找到 libatb.so但系统里其实有 ATB。第二份日志是 source 完 et_env.sh 后父进程能导入了但启动到一半报Engine core died前面只有torch_npu初始化信息没有子进程堆栈。第三份日志是把日志级别开到 DEBUG 后看到子进程里ASCEND_RT_VISIBLE_DEVICES为空默认拿了两张卡但MASTER_PORT和另一个服务冲突HCCL 初始化失败后退出。三份日志其实对应三类故障库路径、子进程环境、et_env.sh 和端口变量。5.2 排查步骤和修复动作第一步先修 libatb.so。查 PyTorch ABIpython -c import torch; print(torch._C._GLIBCXX_USE_CXX11_ABI) # True确认应该用 cxx_abi_1。把 ATB 对应 lib 目录插到 LD_LIBRARY_PATH 最前面export ATB_LIB_DIR/usr/local/Ascend/nnal/atb/latest/atb/cxx_abi_1/lib export LD_LIBRARY_PATH${ATB_LIB_DIR}:${LD_LIBRARY_PATH:-} python -c import vllm_ascend; print(import ok)第二步修 et_env.sh。原来的脚本里写的是export ASCEND_RT_VISIBLE_DEVICES0,1但容器启动时被外层环境覆盖成空。改成默认值写法并在脚本末尾打印变量export ASCEND_RT_VISIBLE_DEVICES${ASCEND_RT_VISIBLE_DEVICES:-0,1} echo [et_env] visible${ASCEND_RT_VISIBLE_DEVICES}第三步修 EngineCore。设置固定的 master port并确认端口没被占用export MASTER_ADDR127.0.0.1 export MASTER_PORT29600 export VLLM_HOST_IP127.0.0.1 ss -lntp | grep 29600 || echo port free export VLLM_WORKER_MULTIPROC_METHODspawn export VLLM_LOGGING_LEVELDEBUG第四步重新启动先单卡python -m vllm.entrypoints.openai.api_server \ --model /data/models/Qwen2.5-0.5B-Instruct \ --device npu \ --tensor-parallel-size 1 \ --port 8000单卡通了再双卡python -m vllm.entrypoints.openai.api_server \ --model /data/models/Qwen2.5-7B-Instruct \ --device npu \ --tensor-parallel-size 2 \ --port 8000这次 EngineCore 正常起来API 能响应。回头看真正需要改的就三处LD_LIBRARY_PATH 的 ATB 路径、et_env.sh 的可见卡变量默认值、MASTER_PORT 冲突。其他都是排查过程中验证过的假设。5.3 修复后的验证清单我一般会留下一个固定验证清单避免下次换机器又从头猜。第一项Python 解释器是否正确python -c import sys; print(sys.executable)第二项核心包是否可导入python - PY import torch import torch_npu import vllm import vllm_ascend print(torch, torch.__version__) print(torch_npu, torch_npu.__version__) print(vllm, vllm.__version__) print(vllm_ascend, vllm_ascend.__file__) PY第三项libatb.so 是否可解析ldd $(python -c import vllm_ascend, os; print(os.path.join(os.path.dirname(vllm_ascend.__file__), _C.so))) | grep -i atb如果_C.so名字不同先 find 出所有 .so 再逐个查。第四项NPU 可见npu-smi info python -c import torch, torch_npu; print(torch.npu.device_count())第五项环境变量是否在启动 shell 里env | grep -E ASCEND_RT_VISIBLE_DEVICES|LD_LIBRARY_PATH|MASTER_PORT|VLLM第六项端口和共享内存ss -lntp | grep -E 8000|29600 df -h /dev/shm这六项都过了再启动完整服务。这样做的好处是一旦某个环节失败你能立刻知道是环境、库、设备还是端口而不是等到 EngineCore 退出后才开始翻日志。6. 常见问题速查与避坑经验6.1 vLLM-Ascend 启动失败速查表现象可能原因快速验证处理动作libatb.so: cannot open shared object fileLD_LIBRARY_PATH 缺失或子进程未继承ldd、LD_DEBUGlibs把 ATB lib 目录放到 LD_LIBRARY_PATH 前部确认 exportundefined symbolCXX ABI 错、ATB 与 torch_npu 版本不配查torch._C._GLIBCXX_USE_CXX11_ABI切换 cxx_abi_0 或 cxx_abi_1核对版本Engine core died无堆栈子进程日志被藏环境未传递VLLM_LOGGING_LEVELDEBUG前台启动找子进程 traceback检查 spawn 和 et_env.shAddress already in use8000、MASTER_PORT、VLLM_PORT 冲突ss -lntp换端口或杀掉占用进程ACL_ERROR_RT_DEVICE卡号错、卡被占用、驱动异常npu-smi info修正ASCEND_RT_VISIBLE_DEVICES清理残留进程HCCL初始化失败多卡通信变量、网卡、端口问题看 HCCL 日志单卡先试固定 MASTER_ADDR/PORT确认网卡和卡数ImportError: torch_npuPython 环境不对et_env.sh 未生效type -a pythonsource 正确环境确认解释器路径$\r: command not foundet_env.sh 是 Windows 换行file et_env.shsed -i s/\r$// et_env.shsource 后变量为空用了./et_env.sh子 shell 执行echo $LD_LIBRARY_PATH改用source et_env.sh或. et_env.sh单卡正常双卡失败卡数、端口、HCCL 或显存问题单卡验证再逐卡加检查 TP 与可见卡数是否一致这张表建议打印出来贴在工位上遇到启动失败先按关键词定位再按快速验证确认最后执行处理动作。不要一上来就重装整个环境重装成本高而且容易把本来能用的 CANN、驱动弄乱。先把范围缩小到某个变量或某个库修复效率会高很多。6.2 我个人踩过的几个坑第一个坑是太相信当前 shell。我在终端里 source 了 et_env.shecho $LD_LIBRARY_PATH看着没问题就以为服务也一定能读到。结果 systemd 启动时用的是另一套环境变量根本没进去。后来我养成习惯任何服务化启动都用包装脚本脚本里显式 source再把env输出到日志开头。第二个坑是只改 LD_LIBRARY_PATH 不检查顺序。系统里有两份 ATB一份在/usr/local/Ascend/nnal/atb/latest一份在旧的 toolkit 目录。我追加到后面加载器先找到旧版报undefined symbol查了半天。后来统一把目标版本插到最前面。第三个坑是忽略子进程日志。父进程报 EngineCore died 时我一开始只盯着父进程 traceback后来发现子进程的ImportError被写到了另一个文件描述符。改用前台启动并21 | tee后才看到。第四个坑是端口。MASTER_PORT没显式设置时vLLM 或底层通信库可能随机选机器上同时跑多个任务就容易撞。现在我会固定一段端口范围启动前用ss -lntp检查。第五个坑是卡号。ASCEND_RT_VISIBLE_DEVICES写错成0,1,2但机器只有两张卡单卡测试时没暴露双卡 TP 时才报错。现在每次启动前先npu-smi info再核对变量。6.3 一个自动检查脚本减少重复排查我后来写了一个启动前检查脚本放在 et_env.sh 同目录每次启动服务前先跑一遍。它不解决所有问题但能提前拦住大部分低级错误。#!/usr/bin/env bash set -u echo python type -a python python -c import sys; print(sys.executable) echo env env | grep -E ASCEND|ATB|HCCL|LD_LIBRARY_PATH|PYTHONPATH|VLLM|MASTER | sort echo npu npu-smi info || true echo torch_npu python - PY || true import torch import torch_npu print(torch, torch.__version__) print(abi, torch._C._GLIBCXX_USE_CXX11_ABI) print(device_count, torch.npu.device_count()) PY echo vllm_ascend import python -c import vllm_ascend; print(vllm_ascend ok) || true echo ports ss -lntp | grep -E 8000|29600 || echo ports free echo shm df -h /dev/shm这个脚本的核心不是自动化修复而是把环境、设备、库、端口四类信息一次性摊开。你可以在启动命令前加上source ./et_env.sh bash ./check_vllm_ascend.sh python -m vllm.entrypoints.openai.api_server ...如果检查脚本里某一项已经失败就不要继续启动大模型服务先修那一项。我自己用下来最常拦住的是 Python 解释器不对、LD_LIBRARY_PATH 没包含 ATB、ASCEND_RT_VISIBLE_DEVICES 为空、MASTER_PORT 被占用这四种情况。修完再启动日志会干净很多。我个人现在排查 vLLM-Ascend 启动失败的顺序基本固定先bash -lc source et_env.sh env看变量再ldd看 libatb.so再VLLM_LOGGING_LEVELDEBUG拉 EngineCore最后核对卡号和端口。这三步做完绝大多数问题都会落到某个具体文件、某个具体变量或某个具体卡号上而不是停在“vLLM 起不来”这句模糊结论里。
返回列表