
在实际搭建 GPU 推理集群时很多人都会遇到这样一个场景算法同事说“我着急用卡跑一批推理”你打开nvidia-smi一看八张卡全被占满但谁在跑、什么时候结束、还剩多少显存没有人能说清楚。更麻烦的是当推理任务从几个 batch 变成几百个 batch 时手动排队、手动挑卡、手动回收输出文件这套流程很快就会失控。NVIDIA 在推理编排方向上推进的开源项目 srt-slurm核心就是解决“GPU 推理服务如何像高性能计算作业一样被可靠调度和部署”的问题。本文会围绕 srt-slurm 编排推理部署这条主线展开先从背景概念讲清楚它到底解决什么问题再带你在 Ubuntu 环境下把 NVIDIA 驱动、容器运行时、SLURM 调度器全部装好最后通过一个可运行的推理批量调度示例把“提交任务 → 排队分配 GPU → 执行推理 → 回收结果”这条链路完整跑通。适合正在维护多卡服务器、准备搭建推理平台、或者刚接触 SLURM 的运维和算法工程师阅读。看完之后你可以直接在自己的小集群上复现这套方案并对常见的 GPU 调度报错形成一套排查思路。1. 背景与核心概念1.1 为什么推理部署也需要编排先把“编排”这个词说清楚。日常开发中我们会在很多地方看到编排比如 Agent 框架里有任务编排Java 规则引擎里有 LiteFlow 这类流程编排。这些编排本质上都是把多个步骤或资源组织起来让它们按照一定策略协同工作。srt-slurm 里的编排也是一样的思想只不过它编排的对象是“GPU 推理任务”和“集群计算资源”。在单机单卡的环境里推理部署很简单装好 GPU 驱动写好模型加载脚本启动服务即可。但一旦环境变成多台服务器、多张显卡、多人共用事情就复杂了。首先会遇到资源冲突问题两个任务同时申请第一张卡显存不够就直接 OOM其次是环境隔离问题有人用 PyTorch 2.0有人用 TensorFlow还有人要跑旧版本 CUDA手动切换环境非常痛苦最后是管理问题任务是谁提交的、运行在哪个节点、日志在哪里、失败了怎么重试如果没有一套调度系统这些问题只能靠人肉维护群聊记录。NVIDIA 开源 srt-slurm 的目的就是把这些分散的能力整合起来借助 SLURM 这类成熟的资源调度器把推理任务变成一个个可排队、可监控、可重试的作业。你可以把这套方案理解成推理请求走服务化入口底层由 SLURM 统一管卡、管队列、管生命周期服务层只关心模型逻辑和结果回收。1.2 SLURM 是什么和 Kubernetes 有什么区别SLURMSimple Linux Utility for Resource Management是高性能计算领域非常老牌的开源资源管理器很多超算中心和科研机构都用它来管理计算节点。它负责维护一份全网节点和资源清单用户通过sbatch、srun等命令提交作业SLURM 根据队列策略、优先级、资源可用情况把作业分配到具体节点上执行。很多做互联网后端的人会问既然有了 Kubernetes为什么还要用 SLURM这里需要区分场景。Kubernetes 擅长的是在线微服务的弹性伸缩它面向的是常驻进程、容器编排、负载均衡和滚动更新而 SLURM 擅长的是离线批处理作业它面向的是“申请独占资源、跑完就结束”的科学计算和训练任务。GPU 推理任务刚好介于两者之间它既要像批处理一样排队拿卡又希望对外提供一定的服务能力。srt-slurm 这类编排方案想做的就是在这两种模式之间找到一条兼顾的思路任务仍以作业形式提交到 SLURM推理结果和服务的接入则由上层平台封装。需要提醒的是这里的 srt 是 NVIDIA 推理编排项目使用的代号千万不要和视频传输领域里的 SRT 协议搞混。在阅读资料时如果看到 SRT 和 Slurm 同时出现要结合上下文判断是视频传输链路配合推理服务还是任务调度层面的项目名称。1.3 srt-slurm 编排推理部署要解决的核心问题把问题简化一下srt-slurm 编排推理部署主要解决四件事第一是队列化把一个时间点的大量推理请求转成有序的作业队列避免大家同时抢卡第二是资源显式化每个作业明确申请“要几张卡、多少 CPU、多少内存”SLURM 负责分配并保证隔离第三是可观测调度器会记录作业从提交到结束的完整状态用户能通过squeue、sacct跟踪任务进度第四是失败处理作业失败后可以设置重试次数输出日志也能统一落盘方便定位问题。为什么这件事值得自己做一遍因为直接让用户nvidia-smi挑卡跑脚本短期看省事长期看必然埋坑。人数一多手动抢卡必然产生隐性等待和故障出问题时也很难追溯。而基于 SLURM 的编排方案虽然前期需要一些配置成本但它能保证多卡推理集群的公平性和可维护性。2. 环境准备与版本说明2.1 硬件与系统基线本文的实操示例以常见的 Linux GPU 服务器为环境参照。具体硬件上需要至少一台管理节点和一台计算节点计算节点配有 NVIDIA GPU例如 V100、A100、H20 等。如果你的环境只有一台带 GPU 的机器也可以把管理节点和计算节点部署在同一台服务器上先跑通流程再扩展。系统版本方面本文以 Ubuntu 22.04 LTS 为主要示例Ubuntu 20.04 的操作步骤基本一致个别软件源地址和包名可能需要微调。需要说明的是NVIDIA 驱动、CUDA、NVIDIA Container Toolkit 和 SLURM 的版本变化都很快具体版本号不应成为硬编码标准。建议你在安装前先确认当前的内核版本、GPU 型号和发行版再选择对应的驱动版本。下面所有命令都围绕“先验证、再安装、最后重启”的思路展开避免一上来就盲目执行。2.2 Ubuntu 下 NVIDIA 驱动安装与 nouveau 处理在 Ubuntu 上安装 NVIDIA 驱动最容易踩的坑就是 nouveau 驱动没有禁用。nouveau 是 Linux 内核自带的开源 NVIDIA 驱动框架性能很弱而且会和官方闭源驱动抢设备。如果不先禁用它装完官方驱动后可能会遇到黑屏、循环登录、nvidia-smi找不到设备等问题。先看一下当前系统是否能识别到 NVIDIA GPUlspci | grep -i nvidia如果能列出显卡型号说明硬件层面正常。接着检查系统当前加载的显卡驱动lsmod | grep nouveau如果有输出就需要把 nouveau 加入黑名单。在 Ubuntu 22.04 上可以创建如下配置文件sudo tee /etc/modprobe.d/blacklist-nouveau.conf EOF blacklist nouveau options nouveau modeset0 EOF sudo update-initramfs -u执行完update-initramfs -u后必须重启系统否则 nouveau 仍然会加载。重启后再执行lsmod | grep nouveau如果没有任何输出说明黑名单生效了。接下来安装驱动依赖。内核对驱动模块的编译非常敏感建议先安装内核头文件和编译工具sudo apt update sudo apt install -y build-essential linux-headers-$(uname -r)然后通过ubuntu-drivers查看推荐的驱动版本sudo apt install -y ubuntu-drivers-common ubuntu-drivers devices输出里一般会列出多个nvidia-driver-xxx的候选版本其中标注 recommended 的版本通常是当前系统最合适的。选择推荐版本安装即可sudo apt install -y nvidia-driver-535当然驱动版本要根据你的 GPU 型号和 CUDA 需求来定。如果你的项目需要 CUDA 12.x可能就要选择更新的驱动分支。安装完成后再次重启然后执行nvidia-smi如果能看到显卡型号、驱动版本、显存大小说明驱动装好了。这里还有一个常见的理解误区Ubuntu 并没有强制要求你手动装 CUDA Toolkit 才能用 PyTorch。大多数深度学习框架都自带 CUDA 运行时依赖你只需要安装匹配的显卡驱动。只有在做底层 CUDA 开发、编译扩展库时才需要单独安装 CUDA Toolkit。至于热词里提到的 Windows 安装报错0xe6000000那是 Windows 侧 NVIDIA Installer 的问题和 Linux 环境不是同一套排查路径大家在搜索解决方案时要注意区分平台。2.3 安装 NVIDIA Container Toolkit推理集群中容器化是最常用的隔离手段。NVIDIA Container Toolkit 的作用就是让 Docker/Singularity 容器能够访问宿主机的 GPU。如果不安装它即使你docker run进去了在容器里执行nvidia-smi也会提示找不到设备。按照 NVIDIA 官方文档Ubuntu 的安装步骤大致如下。先把 NVIDIA 容器工具的软件源配置到系统里curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \ sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \ sed s#deb https://#deb [signed-by/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g | \ sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list然后更新软件源并安装sudo apt update sudo apt install -y nvidia-container-toolkit安装完成后需要把 nvidia-container-runtime 注册到 Docker 的运行时配置里sudo nvidia-ctk runtime configure --runtimedocker sudo systemctl restart docker验证是否配置成功可以运行一个带 CUDA 的基础镜像docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi如果能在容器内看到与宿主机一致的 GPU 信息说明 GPU 容器能力已经通了。需要注意国内网络拉取 Docker 镜像可能比较慢你可以提前把需要的镜像下载到本地导出为 tar 后在内网环境导入或者使用靠谱的镜像加速配置。另外不要在生产机器上盲目执行来路不明的curl | bash脚本所有软件都应该走官方 apt 源或可信渠道。2.4 安装与初始化 SLURMUbuntu 下安装 SLURM 最简单的方式是直接使用系统自带的slurm-wlm软件包sudo apt update sudo apt install -y slurm-wlm mungemunge 是 SLURM 依赖的认证服务集群内所有节点必须使用同一个munge.key。安装后需要初始化并启动 mungesudo /usr/sbin/create-munge-key sudo systemctl enable --now munge如果是多节点集群需要把生成的/etc/munge/munge.key安全地复制到所有节点保证所有节点一致否则节点之间无法通信认证。接下来是 SLURM 的核心配置/etc/slurm-llnl/slurm.conf。不同发行版路径可能不同有的在/etc/slurm/slurm.conf你安装后可以用systemctl status slurmctld查看实际使用的配置文件路径。下面是一个单集群、四台计算节点的最小配置片段ClusterNameinfer-cluster SlurmctldHostmaster NodeNamecompute[01-04] CPUs64 RealMemory252000 Gresgpu:8 StateUNKNOWN PartitionNamegpu Nodescompute[01-04] DefaultYES MaxTimeINFINITE StateUP这个配置的含义是集群里有一个名为gpu的分区包含四台计算节点每台节点有 64 个 CPU 核心、252GB 内存并且通过Gresgpu:8声明了 8 张 GPU 资源。Gres是 SLURM 的通用资源声明机制只有在这里声明了 GPUSLURM 才知道这台节点有哪些 GPU 可以分配。配置写好后将同一份slurm.conf复制到所有节点然后启动服务sudo systemctl enable --now slurmctld sudo systemctl enable --now slurmd启动后可以用sinfo查看节点和分区状态用scontrol show node检查节点是否注册成功。如果sinfo里节点状态是drain或down多半是slurmd没起来或者 munge 认证失败需要结合日志排查。由于版本差异SLURM 在 Ubuntu 20.04 和 22.04 上的包名、服务名可能略有不同请以实际安装环境为准。3. 核心概念如何把推理任务变成 SLURM 作业3.1 作业、分区与 GPU 资源模型SLURM 的核心抽象是“作业”job。一次推理任务的运行本质上就是提交一个作业。作业由多种资源需求来描述要多少节点、每个节点多少 CPU、多少内存、多少 GPU、运行多长时间。SLURM 在收到作业后会参照分区配置去寻找满足条件的节点然后把作业放到队列中等待。分区可以理解成一组节点的集合。生产环境里通常会把不同规格的节点分成不同分区比如gpu-a100分区跑大模型gpu-rtx分区跑小规模推理。这样用户在提交任务时只需要指定分区名SLURM 就会在对应节点池里调度。好处是资源管理粒度更清晰也便于对不同业务设置不同的优先级和限额。在推理场景里最常见的需求是“跑一个单卡推理任务”。对应的 SLURM 参数是--gresgpu:1。如果申请多卡可以写--gresgpu:4表示这个作业需要独占 4 张 GPU。3.2 GRES 与 CUDA_VISIBLE_DEVICES 的关系很多人会困惑SLURM 到底是怎么把“哪张卡”告诉程序的答案是通过环境变量。当节点配置了Gresgpu并且作业申请了--gres后SLURM 会在作业进程环境中设置CUDA_VISIBLE_DEVICES告诉 CUDA 运行时哪些 GPU 对当前进程可见。举个例子如果节点上有 8 张 GPU编号是 0 到 7某个作业被分配到第 3 和第 5 张卡SLURM 会把CUDA_VISIBLE_DEVICES设置为类似3,5的值。程序内部用torch.device(cuda)时看到的 GPU 序号就从 4 和 6 变成了 0 和 1。这就是为什么在作业脚本里千万不要自己写死CUDA_VISIBLE_DEVICES0否则会覆盖 SLURM 的分配结果造成多作业抢卡的严重问题。正确的做法是让 SLURM 自动注入环境变量并在脚本开头打印出来确认。作业系统分配的 GPU 信息也会记录在作业的节点列表和环境属性中可以通过srun env | grep CUDA查看。3.3 srt-slurm 编排层的任务流转设计在 srt-slurm 的编排思路里一条完整的推理任务流转链路可以概括为外部把一批推理请求写入任务清单提交入口根据资源情况生成 SLURM 作业并提交SLURM 在计算节点上拉起容器或 Python 进程执行推理推理结果统一写入输出目录。下面用一个简单的 ASCII 图表示关键流程外部请求 / 批量任务 | v submit.py 生成任务清单 tasks.json | v sbatch --array 提交到 SLURM | v 计算节点 GPU 上执行 task_worker.py | v 结果写入 outputs/ 目录这个流程里提交入口是“编排层”的核心它把推理任务列表翻译成 SLURM 可以理解的多作业请求并且在任务失败时负责重试或告警。任务执行层只需要关心“读取任务、执行模型、写结果”这三件事。4. 实战搭建一套 srt-slurm 推理编排示例4.1 项目结构为了让示例看起来更像真实项目我们先规划一个最小工程结构infer-cluster/ ├── submit.py ├── task_worker.py ├── tasks.json ├── sbatch_templates/ │ └── infer.sbatch.sh ├── logs/ └── outputs/其中submit.py是提交入口负责生成任务清单并调用sbatchtask_worker.py是在计算节点上执行推理的工作脚本sbatch_templates/infer.sbatch.sh是 SLURM 作业模板tasks.json存放推理任务清单logs和outputs分别存放日志和结果。4.2 准备推理任务清单为了演示我们先用一个小脚本生成 10 条模拟推理任务。每条任务包含模型名和模拟耗时字段。实际项目中这个任务清单可能来自 API 请求、消息队列或业务表这里只保留核心逻辑。#!/usr/bin/env python3 # submit.py 的部分逻辑用于生成 tasks.json import json from pathlib import Path def build_tasks(): items [] for i in range(10): items.append({ model: fdemo-model-{i % 3}, sleep_seconds: 5, }) return items def main(): task_file Path(tasks.json) task_file.write_text(json.dumps(build_tasks(), ensure_asciiFalse, indent2), encodingutf-8) print(fgenerated {task_file} with {len(build_tasks())} tasks) if __name__ __main__: main()这段脚本会生成 10 条任务每个任务有一个模型名和一个模拟耗时。真实推理项目里这里还需要考虑模型版本、输入文件路径、超时时间等字段。4.3 编写 SLURM 作业模板作业模板是所有推理任务执行的公共入口里面通过#SBATCH指令声明资源需求。这里以单卡、每个任务 8 个 CPU 核心、16GB 内存为例#!/bin/bash #SBATCH --job-namesrt-infer #SBATCH --partitiongpu #SBATCH --gresgpu:1 #SBATCH --cpus-per-task8 #SBATCH --mem16G #SBATCH --time01:00:00 #SBATCH --outputlogs/%x-%A_%a.log #SBATCH --errorlogs/%x-%A_%a.err set -euo pipefail task_file${1:?usage: sbatch infer.sbatch.sh task.json} echo SLURM_JOB_ID${SLURM_JOB_ID} echo SLURM_ARRAY_TASK_ID${SLURM_ARRAY_TASK_ID:-0} echo allocated GPU: ${CUDA_VISIBLE_DEVICES:-none} nvidia-smi --query-gpuindex,name,memory.used,memory.total --formatcsv export SRT_TASK_FILE${task_file} export SRT_OUTPUT_DIRoutputs python3 task_worker.py脚本里几个关键点需要说明--gresgpu:1表示每个作业申请一张卡--output里使用了%x、%A、%a这些占位符分别表示作业名、作业 ID、数组任务 ID方便在批量任务时日志按任务分开CUDA_VISIBLE_DEVICES会由 SLURM 自动注入脚本里打印出来用于确认set -euo pipefail可以让脚本在出错时立刻退出避免静默失败。4.4 编写任务提交调度入口下面编写完整的提交入口。它负责生成任务清单、读取任务数量、调用sbatch提交一个 Job Array并限制同时运行的任务数量防止一次性把所有 GPU 打满#!/usr/bin/env python3 # submit.py import json import subprocess import sys from pathlib import Path def build_tasks(): items [] for i in range(10): items.append({ model: fdemo-model-{i % 3}, sleep_seconds: 5, }) return items def main() - int: task_file Path(tasks.json) task_file.write_text(json.dumps(build_tasks(), ensure_asciiFalse, indent2), encodingutf-8) tasks json.loads(task_file.read_text(encodingutf-8)) n len(tasks) cmd [ sbatch, --export, fALL,SRT_TASK_FILE{task_file.resolve()},SRT_OUTPUT_DIRoutputs, --array, f0-{n-1}%4, # 最多同时运行 4 个任务 sbatch_templates/infer.sbatch.sh, str(task_file.resolve()), ] print(run:, .join(cmd)) proc subprocess.run(cmd, capture_outputTrue, textTrue) print(proc.stdout) if proc.returncode ! 0: print(proc.stderr, filesys.stderr) return proc.returncode return 0 if __name__ __main__: raise SystemExit(main())这段代码用 Job Array 方式提交任务--array 0-9%4的含义是创建编号从 0 到 9 的数组作业同时最多运行 4 个。这样在 GPU 数量有限的情况下多余任务会留在 SLURM 队列中排队不会把节点资源瞬间打满。4.5 编写推理工作脚本任务执行脚本task_worker.py负责读取任务、模拟推理、写入结果。真实项目中你可以在这里加载 PyTorch 模型对输入数据执行model(input_tensor)这里用time.sleep模拟计算消耗#!/usr/bin/env python3 # task_worker.py import json import os import sys import time from pathlib import Path def main() - int: task_file os.environ.get(SRT_TASK_FILE) output_dir os.environ.get(SRT_OUTPUT_DIR, outputs) if not task_file: print(missing SRT_TASK_FILE, filesys.stderr) return 1 tasks json.loads(Path(task_file).read_text(encodingutf-8)) index int(os.environ.get(SLURM_ARRAY_TASK_ID, 0)) if index len(tasks): print(ftask index {index} out of range, filesys.stderr) return 1 task tasks[index] gpu_id os.environ.get(CUDA_VISIBLE_DEVICES, unknown) print(f[worker] index{index} gpu{gpu_id} model{task[model]}) # 模拟推理过程真实场景在这里加载模型并执行 batch time.sleep(task.get(sleep_seconds, 2)) Path(output_dir).mkdir(parentsTrue, exist_okTrue) result { index: index, model: task[model], gpu_id: gpu_id, status: ok, finished_at: time.strftime(%Y-%m-%d %H:%M:%S), } out_file Path(output_dir) / ftask-{index}.json out_file.write_text(json.dumps(result, ensure_asciiFalse, indent2), encodingutf-8) print(f[worker] write {out_file}) return 0 if __name__ __main__: raise SystemExit(main())看到这里你可能会问怎么没有看到torch.cuda.is_available()这是因为在模拟流程里我们要让示例在无 GPU 环境下也能跑通。如果你想验证 GPU 真的被用上了可以在脚本中插入一小段 CUDA 检测代码try: import torch if torch.cuda.is_available(): print(f[worker] CUDA device count{torch.cuda.device_count()}) print(f[worker] device name{torch.cuda.get_device_name(0)}) else: print([worker] CUDA is not available, running on CPU mode) except ImportError: print([worker] torch not installed, skip CUDA check)需要注意如果计算节点上的 Python 环境没有安装 PyTorch这句会打印跳过提示不影响演示流程。4.6 运行与验证先创建日志和结果目录然后执行提交脚本cd infer-cluster mkdir -p logs outputs python3 submit.py正常输出类似generated tasks.json with 10 tasks run: sbatch --export ALL,SRT_TASK_FILE/root/infer-cluster/tasks.json,SRT_OUTPUT_DIRoutputs --array 0-9%4 sbatch_templates/infer.sbatch.sh /root/infer-cluster/tasks.json Submitted batch job 42这意味着数组作业 42 已经提交成功。用squeue查看排队和运行情况squeue -u $(whoami)输出会显示作业 ID、分区、作业名、任务状态、节点等信息。如果节点上只有 4 张 GPU你会看到部分任务处于RUNNING其余处于PENDING。等待片刻后检查输出目录ls outputs/ cat outputs/task-0.json每个任务都会生成一个 JSON 结果文件里面包含任务索引、模型名、分配到的 GPU 编号和完成时间。如果某个数组任务报错可以通过作业 ID 查看对应的日志cat logs/srt-infer-42_5.log42_5表示作业 42 的第 6 个数组任务日志文件里的 GPU 分配信息能帮你快速判断是不是卡资源或显存问题。5. 进阶批量推理、多机并行与容器化推理服务5.1 用 Job Array 提高批量推理吞吐上面的示例已经展示了 Job Array 的基本用法。它是批量推理里性价比最高的方式因为一个提交命令就能产生大量作业每个作业独立申请资源、独立记录日志。批量任务时建议根据 GPU 数量合理设置%限制避免积压太多任务导致排队时间不可控。在任务清单比较大时还可以做“任务分片”也就是每个数组任务处理一批数据而不是一条数据。比如 10 万条推理请求可以切成 100 个片段每个片段 1000 条这样能把调度开销降到最低也能减少小任务过多带来的日志碎片问题。5.2 多节点推理与并行框架如果你需要运行的是一个大模型推理任务单张卡放不下就可能要用到张量并行或多节点并行。这类任务在 SLURM 中的资源请求是另一套写法#!/bin/bash #SBATCH --job-namemulti-node-infer #SBATCH --partitiongpu #SBATCH --nodes2 #SBATCH --ntasks-per-node8 #SBATCH --gresgpu:8然后通过srun在多个节点上启动分布式进程srun python3 -m my_model_worker \ --tensor-parallel-size 16 \ --node-rank ${SLURM_NODEID}多节点并行推理的复杂度远高于单卡这里不展开详细代码。需要明确的是--nodes、--ntasks-per-node、--gres的配置必须和模型并行框架的要求一一对应比如 vLLM 的张量并行度、Megatron-LM 的模型并行度。如果两者不匹配轻则性能极差重则直接启动失败。5.3 与 vLLM / NIM 等推理服务结合srt-slurm 编排的不一定只是离线批处理也可以和 vLLM、NVIDIA NIM 这类在线推理服务结合。通常做法是把推理服务封装成容器镜像通过作业模板启动一个长期运行的推理服务作业然后由上层网关把请求转发到该作业所在的节点端口。以 vLLM 为例在装有 NVIDIA Container Toolkit 的节点上可以直接运行容器启动 OpenAI 兼容服务docker run --rm --gpus all \ -v $PWD/models:/models \ -p 127.0.0.1:8000:8000 \ vllm/vllm-openai:latest \ --model /models/Qwen2.5-7B-Instruct \ --tensor-parallel-size 2这条命令要求本地已有模型权重并且镜像版本与驱动、CUDA 版本匹配。如果你所在团队的模型服务走的是 NVIDIA NIM 这类企业级推理微服务也可以在容器中预置 NIM 的推理环境再通过 SLURM 拉起。把在线服务也纳入 SLURM 管理好处在于可以复用已有的 GPU 队列策略。比如低优先级推理服务在忙时可以被抢占训练作业在空闲时段自动补位资源利用率会明显提高。6. 常见问题与排查思路推理编排链路里问题通常集中在驱动、容器、调度器三个层面。下面把高频问题整理成一张速查表。问题现象常见原因解决思路执行nvidia-smi提示找不到设备nouveau 未禁用、内核头文件缺失、驱动未安装先确认黑名单生效再安装内核头文件重装驱动后重启容器内执行nvidia-smi报无设备NVIDIA Container Toolkit 未安装或未配置 Docker 运行时执行nvidia-ctk runtime configure --runtimedocker重启 Dockersbatch提交后一直 PENDING分区无可用 GPU、墙钟时间限制、QOS 限制用scontrol show job id查看等待原因调整资源请求作业进程无法访问 GPU作业模板里没有--gresgpu或节点未配置Gres检查 slurm.conf 中NodeName的Gres字段并在提交脚本里加上--gresgpu:1GPU 显存被占满但进程消失存在残留进程或僵尸进程用nvidia-smi定位 PID确认归属后正常终止进程多作业之间互相抢卡作业脚本内手动设置了CUDA_VISIBLE_DEVICES删除手动设置让 SLURM 自动注入 GPU 可见性环境变量srun: error: Unable to contact slurm controllerslurmctld 未启动或 munge 认证失败检查控制节点服务状态确认所有节点 munge.key 一致其中“PENDING 很久”是最常被问到的问题。遇到这种情况不要急着改代码先执行scontrol show job job_id输出中的Reason字段会明确告诉你等待原因。常见的有Resources资源不足、Priority还在等优先级调度、DependencyNeverSatisfied依赖任务没结束、BadConstraints约束条件无法满足。看清楚原因后再针对性调整效率会高很多。另外如果使用了新款的 GPU 型号比如 H20要特别注意驱动、固件和容器运行时版本的配套关系。有些新卡需要较新的驱动分支和固件才能被正确识别否则可能出现nvidia-smi能看得到但 CUDA 程序无法初始化的情况。解决方案是先升级到官方支持该型号的驱动版本再检查容器运行时的 GPU 支持列表。7. 最佳实践与工程建议7.1 环境一致性优先推理集群最怕“机器漂移”。同一套代码在这台节点上正常换一台节点就报错十有八九是驱动版本、CUDA 版本、Python 依赖不一致。建议在项目里固定一份环境清单包括驱动版本、容器镜像 tag、Python 依赖 lock 文件并在作业脚本里启动时打印这些信息方便队列里任何任务都能追溯到环境来源。GPU 集群的环境一致性可以借助容器镜像来保证。把模型依赖、运行库、推理框架全部打进镜像SLURM 作业运行时直接拉取镜像启动宿主机只需要保证驱动和容器运行时兼容即可这样管理成本会大幅下降。如果你恰好使用 Kubernetes 集群来跑 GPU 推理可以关注 NVIDIA GPU Operator它把驱动、容器运行时、监控组件都做成了 Operator 自动部署是另一种降低环境维护成本的思路。7.2 资源请求必须显式化在任何 SLURM 作业里都不要省略资源请求。即使你的节点 CPU 和内存非常富裕也建议显式声明--cpus-per-task、--mem、--gres。原因有两个第一显式声明能保证作业运行时获得稳定的资源配额避免和其他作业竞争第二SLURM 调度器依赖资源声明做全局规划如果所有作业都不声明调度器只能按默认值乱分最终系统资源视图会失真。对于 GPU 资源还有一个容易被忽略的细节默认情况下 SLURM 按整卡分配 GPU不会自动做显存细分。如果一个作业只需要 4GB 显存而卡上有 80GB整卡分配会非常浪费。这时可以考虑使用 CUDA MIG 或多实例 GPU 技术把物理卡切成多个实例再在 SLURM 里通过Gres配置来分配。当然MIG 的配置也依赖驱动和硬件支持需要提前验证。7.3 作业超时与失败重试推理作业要设置合理的超时时间。--time01:00:00