NVIDIA 开源的 srt-slurm 编排推理部署,解决的不是“能不能在一张卡上跑推理”,而是“多卡、多节点、多次提交的推理任务怎么被统一调度和编排”。它把 Slurm 的资源管理能力和上层推理状态管理组合在一起,适合需要批量处理图像、文本、语音等模型的工程团队。最值得关注的点是:这套思路把调度、容器、推理服务三者解耦,部署时不容易变成一锅粥。
我建议先不要急着找完整命令,先把问题拆开:你要处理的推理任务是什么类型,需要跑多少条,数据放在哪里,输出怎么回收。等这些问题清楚后,再看 srt-slurm 的编排层能不能接住你的流程。下面按实际落地顺序拆一遍。
1. 先搞清楚 srt-slurm 到底编排了什么
很多人一听“编排推理部署”,会以为它只是把推理命令扔给集群去跑。实际上,srt-slurm 这类方案要解决的是一整条链路:输入数据怎么进、模型加载到哪个容器、GPU 资源怎么分配、任务失败怎么处理、输出结果怎么落盘、日志去哪看。Slurm 单靠自身也能提交作业,但推理场景下我们还需要额外管理任务状态和失败重试。编排层就是补上这一段的。
1.1 推理任务和训练任务不一样
训练任务通常时间长、资源占用稳定、模型权重固定,提交后可以几个小时甚至几天不用管。推理任务则完全相反:单条耗时可能只有几秒,但数量多、输入格式多样、偶发异常多,还可能依赖预热的模型进程。
如果在 Slurm 里直接提交“每次跑一条推理”的任务,会出现几个常见问题:
- 每次提交都要重新加载模型,GPU 利用率低。
- 任务数量多,但每个任务只占很小资源,调度队列被刷屏。
- 失败任务没有统一重试策略,需要人工盯。
- 输出文件命名容易冲突,任务 ID 和输入 ID 对不上。
srt-slurm 的编排层会把推理请求整理成带有状态的任务,比如 pending、running、done、failed。只有状态为 done 的任务才认为成功,failed 的任务可以按策略重试。这样 Slurm 还是做它擅长的事——分配 CPU、GPU、内存、节点,而编排层负责把业务状态管理起来。
1.2 编排器管状态,Slurm 管资源
部署时最容易犯的错误,是把业务逻辑全部塞进 sbatch 脚本。比如在脚本里写复杂的输入解析、模型下载、结果入库、失败重试。这样看起来能跑,但后面排查会很难受。
正确的思路是分层:
- Slurm 负责资源分配:用哪个分区、几张 GPU、多少内存、排队优先级。
- 容器负责环境隔离:驱动可以不同,Python 依赖不会互相污染。
- 编排器负责任务流转:从输入队列读取请求,提交给 Slurm,回收结果,记录日志。
这套分工的价值在于,每一层都能独立替换。比如今天用 Slurm,明天换成别的调度器,编排层只需要改提交适配器;今天用 Docker,明天想换 Singularity,容器层替换即可,业务代码不用重写。
2. 部署前先确认这套架构适合哪种环境
不要一上来就搭多节点集群。先确认你手里到底有什么资源,以及这套编排推理部署在你的环境里能不能跑通。原始材料里没有给出具体版本,落地时一定先确认依赖版本和官方文档。
2.1 硬件和系统条件
srt-slurm 的典型运行环境是 Linux GPU 集群。至少需要一台装有 NVIDIA GPU 的服务器,系统推荐 Ubuntu、Rocky Linux、Debian 这类主流发行版。Windows 可以开发调试,但生产环境不建议,因为 Slurm 和容器运行时的生态主要围绕 Linux。
硬件方面先看三块:
- GPU 型号和显存,决定你能跑多大的模型、能不能开多实例。
- CPU 核数,决定前处理、后处理和容错逻辑的吞吐。
- 内存大小,模型加载、批量推理、数据缓存都要吃内存。
检查命令很基础,但每次部署前我还会执行一遍:
nvidia-smi sinfo sbatch --versionnvidia-smi 用来确认驱动和 GPU 可见,sinfo 用来确认 Slurm 分区,sbatch --version 用来确认调度器主版本。这三个命令任何一个报错,都先别往后走。
2.2 容器和 GPU 运行时
推理依赖很杂,不同模型可能需要不同版本的 PyTorch、CUDA、cuDNN 或自定义算子。直接在宿主机上装,换模型就很容易冲突。所以几乎都会引入容器方案。
常见组合是:
- Docker 或 Singularity 做容器运行时。
- NVIDIA Container Toolkit 让容器能访问 GPU。
- 镜像里固定 CUDA、Python、推理框架版本。
NVIDIA Container Toolkit 的核心作用,是把宿主机驱动透传给容器。这里要注意:容器里的 CUDA 版本不需要和宿主机的 CUDA 完全一致,但宿主机驱动版本必须高于或等于容器内 CUDA 所需的最低驱动版本。否则会出现 CUDA 初始化失败、找不到 libcuda.so 之类的报错。
如果只用单机,手动装 Docker 加 Toolkit 就行。如果是 Kubernetes 集群,可以看看 NVIDIA GPU Operator,它把驱动、容器运行时、监控组件一起管理了。不过 srt-slurm 这种以 Slurm 为核心的场景,重点还是先把 Slurm 和容器运行时连通。
2.3 网络和存储需要考虑的点
推理任务往往有大量输入输出文件,存储设计比网络更早影响结果。如果你有共享存储,比如 NFS、Lustre、GPFS,那模型权重和数据集放共享路径会比较合适,每个 Slurm 计算节点都能直接读取。
共享存储注意几个判断点:
- 输入目录是否只读,避免多个任务同时写入互相覆盖。
- 输出目录是否按任务 ID 隔离,建议统一结构,比如
outputs/{job_id}/{input_id}.json。 - 模型文件是否过大,加载时间是否超过任务超时时间。
网络方面,如果只是离线批推理,节点间网络要求不高。但如果要把推理服务发布成 API,就需要考虑端口管理、负载均衡、健康检查。这个在后面章节会展开。
3. 最小可运行部署:从单机单卡到多节点队列
我始终建议先做最小验证,再谈扩展。不要一上来就提交几百个任务,也不要一开始就部署完整控制台。
3.1 准备基础环境
假设你已经有一台 Linux 服务器,GPU 驱动正常,接下来要做的事按顺序走:
- 安装 Slurm,并配置一个默认分区。
- 安装容器运行时和 NVIDIA Container Toolkit。
- 准备一个推理镜像,镜像里包含你的模型文件和推理脚本。
- 确认 Slurm 作业脚本能调用
nvidia-smi,说明 GPU 透传成功。
Slurm 的安装方式依赖你的发行版和网络环境,这里不强行给固定命令。关键是先跑通一个最简单的 hello 作业:
srun --gpus=1 nvidia-smi如果这条命令能打印出 GPU 信息,说明 Slurm 调度、GPU 资源、驱动调用都通了。如果报错,先看sinfo分区状态,再看/var/log/slurm/下的日志。这一步很容易被跳过,但跳过之后后面所有问题都会翻倍难排查。
3.2 写一个最小推理作业脚本
一个常见的 Slurm 作业脚本会包含资源申请和实际执行命令。下面是一个通用示例,实际字段要以你的集群配置为准:
#!/bin/bash #SBATCH --job-name=srt-inference #SBATCH --output=logs/%j.out #SBATCH --error=logs/%j.err #SBATCH --gpus=1 #SBATCH --cpus-per-task=4 #SBATCH --mem=16G #SBATCH --time=00:30:00 srun python inference.py \ --input data/sample.json \ --output outputs/$SLURM_JOB_ID/sample.json这里有几个字段值得解释:
--output和--error是日志文件,命名带上%j表示任务 ID,防止多任务日志互相覆盖。--gpus=1申请一张 GPU。--time=00:30:00是超时时间,推理任务一定要设,防止死等。$SLURM_JOB_ID打印的是当前任务 ID,用来拼输出目录很方便。
为什么要把输出目录按任务 ID 隔离?因为批量提交时,多任务会并发运行,如果大家都往results/result.json里写,最后只会有一份覆盖后的结果。按任务 ID 隔离后,每个任务写自己的目录,后面做结果汇总时也更容易定位失败任务。
3.3 先跑单条任务再扩大规模
第一个验证只用一条样例数据,不跑真实业务集。看三个东西:
- 任务是否从 pending 变成 running。
- 日志里有没有加载模型、GPU 初始化成功的输出。
- 输出文件是否存在且内容正确。
单条任务跑通后,再尝试数组作业。Slurm 的--array非常适合同类型批量推理:
sbatch --array=1-10 run_array.sh脚本里通过$SLURM_ARRAY_TASK_ID对应不同输入文件:
#!/bin/bash #SBATCH --job-name=srt-array #SBATCH --output=logs/%A_%a.out #SBATCH --error=logs/%A_%a.err #SBATCH --gpus=1 #SBATCH --cpus-per-task=4 #SBATCH --mem=16G #SBATCH --time=00:30:00 srun python inference.py \ --input data/input_${SLURM_ARRAY_TASK_ID}.json \ --output outputs/${SLURM_ARRAY_TASK_ID}.json数组作业的好处是不用写循环提交命令,调度器会管理每个子任务的状态。单个子任务失败不会阻塞其他子任务,适合试批量。
3.4 结果验证和日志检查
任务结束后,不要只看任务状态。Slurm 显示COMPLETED只能说明脚本退出了,不能说明推理结果是对的。真正的成功标准是:
- 退出码为 0。
- 输出文件存在,且内容不是空文件。
- 必填字段都有,没有 NaN 或者错误占位符。
- 日志里没有 CUDA error、OOM、模型加载失败等关键字。
如果任务状态是FAILED,第一个动作是看.err文件。常见问题包括路径打不开、模型文件权限不对、容器内没有 GPU 权限、输入 JSON 格式错误。排查顺序尽量固定,不要每次从零开始。
4. 推理任务编排的关键参数与扩展方式
最小环境跑通后,接下来才进入真正的编排阶段。这里的核心不是写更多脚本,而是把参数和边界定清楚。
4.1 并发、队列和优先级怎么取舍
很多人在批量推理时会直接加大并发数。这个方向没错,但要先理解并发增加后会带来什么。
并发数升高,意味着同时运行多个任务。每个任务都会申请 CPU、内存、GPU 显存和额外的模型加载开销。如果你有 8 张 GPU,想同时跑 8 个推理任务,每张卡都能分到,这没问题。但如果你申请 16 个任务,每个任务--gpus=1,那其中 8 个只能排队。排队本身不是坏事,问题是任务一多,单个任务等待时间变长,失败重试也会更复杂。
我从经验里总结一套比较稳妥的调整顺序:
- 先确认单任务需要多少显存和内存。
- 用静态资源预估可以同时跑几个任务。
- 从预估值的 50% 开始试。
- 观察平均耗时、失败率、节点上的资源剩余量。
- 再逐步增加,直到某个指标明显恶化。
Slurm 侧有几个参数会影响编排表现:
--gres=gpu:N:申请固定 GPU 数量。--qos:限制优先级、最大运行时间和最大作业数。--partition:把不同业务拆到不同分区。MaxArraySize:控制数组任务数量上限。
业务层也要做限流,Slurm 的任务队列不是无限大。如果在编排器里一股脑提交几万个任务,调度器可能不接受,也会把日志和状态目录撑得很大。更常见的做法是分批提交,比如每次 100 个子任务,等结果回收后再提交下一批。
4.2 GPU 显存隔离和多实例支持
推理任务最敏感的资源是显存。两个模型同时放到一张卡上,如果显存不够,其中一个就会 OOM。
处理思路有三种:
- 每任务独占整张 GPU:最简单,但可能浪费显存。
- 用
CUDA_VISIBLE_DEVICES指定不同任务使用不同 GPU:适合单卡多任务的场景。 - 用 MIG 或 vGPU 做物理隔离:适合新机型,但配置复杂度更高。
MIG 的全称是 Multi-Instance GPU,可以把一张物理 GPU 切成多个实例,每个实例有独立的显存和计算切片。如果模型不大,MIG 能明显提高 GPU 利用率和稳定性。但要注意 MIG 需要对驱动和容器运行时有额外支持,不是所有卡都支持,也不是所有镜像都兼容。
判断标准很简单:如果一个任务崩溃会影响其他任务,说明隔离不够;如果需要反复调整显存大小才能稳定跑,说明资源申请和实际模型占用不匹配。
4.3 把离线任务变成推理服务
离线批处理只是 srt-slurm 编排的一种形态。实际生产里,很多团队还需要把模型发布成 HTTP API,供业务系统调用。这种情况不能每个请求都提交一个 Slurm 任务,因为任务排队和容器启动时间根本扛不住。
更合理的做法是:
- 在 Slurm 集群上启动一组常驻推理服务实例。
- 每个实例占用固定 GPU 资源,运行同一个模型服务。
- 编排器负责探测实例健康状态。
- 外部请求通过负载均衡转发到健康的实例。
一个简化版的 Slurm 常驻服务脚本可以是这样:
#!/bin/bash #SBATCH --job-name=model-api #SBATCH --output=logs/%j_api.log #SBATCH --error=logs/%j_api.err #SBATCH --gpus=1 #SBATCH --cpus-per-task=4 #SBATCH --mem=16G #SBATCH --time=08:00:00 srun python serve_model.py \ --port $SLURM_JOB_ID \ --model /models/bert-base这里端口用任务 ID 起名,主要是为了测试时避免冲突。生产环境通常不会这样设计,而是由一个服务注册中心来分配端口并登记实例地址。
把离线任务变成服务后,编排层要关注的参数就变了:原来关心超时和重试次数,现在还要关心健康检查间隔、优雅退出、请求超时、并发上限。Slurm 还是负责保证实例跑在正确的 GPU 节点上,但请求路由逻辑应该由编排层或网关来处理。
4.4 多模型、多租户和配额
当团队多人共用集群时,光有 Slurm 资源限制还不够。你需要给不同业务、不同用户做配额。
比如:
- A 组是图像模型,只能使用 A 节点。
- B 组是用户在线请求,需要较高优先级。
- C 组是离线数据处理,只在夜间批量执行。
Slurm 的分区、QoS、账户配额可以做第一层隔离。编排层则要维护“哪个项目组提交了哪些任务”,并提供按项目查看任务统计和失败情况的入口。如果这一步不做,后面排查问题时会分不清某个失败的 GPU 任务是属于哪个业务方。
多模型场景下,模型文件的管理也很关键。每个容器镜像尽量只包含一个模型或同一系列模型,避免因为模型文件过大导致镜像拉取时间过长。镜像 tag 建议带版本号,比如inference/bert:v1.2.0,不要用latest,否则重复调度时可能加载到不同权重,结果不可复现。
5. 实际部署中容易踩的坑
这部分是我最想写的。因为 srt-slurm 这类编排方案,功能上看着简单,真正跑起来会遇到不少环境层面的问题。
5.1 启动失败,大概率不是模型问题
很多人在容器启动失败后,第一反应是检查推理代码。但实际最常见的问题集中在三个方面:
- 路径和权限:模型文件不存在,或当前用户没有读取权限。
- 容器 GPU 透传失败:容器里执行
nvidia-smi报错。 - CUDA 和驱动不匹配:容器内 CUDA 版本要求比宿主机驱动支持更高。
排查顺序可以先看.err日志,再手动在容器里执行一次同一命令:
docker run --rm --gpus all inference/bert:v1.2.0 nvidia-smi如果这条命令能显示 GPU,说明容器、驱动、Toolkit 都正常。如果这条命令失败,那问题根本不在模型,而是容器运行时没有把宿主机的 GPU 设备暴露进去。
这里不要急着改代码。先把容器能跑通、GPU 能识别、模型文件能读取这三件事逐项验证,再回到业务脚本。
5.2 任务卡住,先看资源占用和日志
任务卡住比报错更难查。报错至少给了线索,卡住可能只是 pending 状态,也可能已经 running 但什么都没输出。
我的排查顺序一般是:
- 看任务当前状态:
squeue。 - 看日志文件大小和最后修改时间。
- 看节点资源占用:
nvidia-smi和top。 - 看输出目录是否被创建。
如果任务一直 pending,不是它卡住了,而是没有可用资源。可以先看是不是别的大任务占了整张卡,或者 QOS 限制了最大排队数。如果任务是 running 但长时间没有输出,可能是模型正在加载,也可能是输入数据解析进入了死循环。这时再看 CPU 和显存占用:CPU 占用高说明在计算,显存占用高说明模型已加载,如果两边都没变化,大概率在等某个网络请求或锁。
在容器里跑长任务时,要特别注意临时目录空间。推理过程中如果输出文件很大,而/tmp或容器挂载路径空间不足,任务会表现为写入变慢甚至卡死,但日志不一定有明确报错。
5.3 版本兼容和升级顺序
NVIDIA 生态的版本兼容问题会反复出现,尤其是驱动、CUDA、容器运行时、Slurm 之间。升级时最容易踩的坑是只升级一个组件,其他组件没有配套。
我建议把版本记录下来,并放到镜像描述或集群文档里。至少记录这几项:
- 宿主机 NVIDIA 驱动版本。
- 容器内 CUDA 版本。
- NVIDIA Container Toolkit 版本。
- Slurm 版本。
- 推理框架版本,比如 PyTorch 或 TensorRT。
升级顺序上,先做兼容性验证,再操作生产环境。可以准备一个“冒烟测试任务”,里面包含一个最小模型、一条样例数据、一个断言语义检查。每次升级驱动、镜像或 Slurm 配置后,都先跑一次冒烟测试任务。
这个冒烟测试任务不应该等到生产任务失败时才想起来。平时也可以作为定时任务,定期验证整个编排链路是否健康。
6. 我的落地建议
最后说说我自己的判断。这套方案不是适合所有人,但如果你正好在 GPU 集群上长期跑批量推理,很值得把编排思路拿过去用。
6.1 学习阶段怎么试
学习阶段不要从多节点开始。先在一台机器上把 Slurm 装好,创建一个分区,跑一个最简单的 Python 睡眠任务:
#!/bin/bash #SBATCH --output=logs/test.out #SBATCH --error=logs/test.err srun python -c "print('hello srt')"能跑通后,再把任务改成 GPU 任务,加上容器。不要一开始就追求美观的控制界面,先把任务提交、日志、输出回收这三条链路走通。
如果你以前只用过单机脚本,第一次用 Slurm 时觉得不适应很正常。重点不是熟悉所有参数,而是理解“申请资源”和“执行命令”之间是被调度器拆开的。写脚本时把资源申请放上面,把执行逻辑放下面,后面维护会轻松很多。
6.2 生产阶段怎么收敛
如果正式业务要用,我建议提前定义好以下几套规范:
- 任务 ID 规范:统一使用任务 ID 关联输入和输出。
- 输出目录规范:比如
outputs/{任务ID}/。 - 日志目录规范:统一收集到共享目录,方便检索。
- 失败重试规范:哪些任务允许重试,重试次数上限是多少。
- 镜像版本规范:每个模型单独一个 tag,禁止无 tag 镜像。
生产阶段还要考虑一个容易被忽略的点:任务积压时的告警。如果编排器提交任务后,Slurm 队列越来越长,新任务等待时间超过预期,需要有告警。否则用户可能以为任务在跑,实际一直排队。
6.3 判断这套方案是否适合你
适合采用 srt-slurm 编排推理部署的场景,通常有几个共同特征:
- 推理任务足够多,但不是单请求实时响应。
- 有多个用户或项目组共享 GPU 资源。
- 需要保留任务执行历史,方便回溯。
- 输入输出以文件形式批量产生。
- 需要在多台 GPU 节点上统一运行同一套推理逻辑。
如果你的业务主要是在线请求,延迟要求毫秒级,那基于 Slurm 的批处理调度不是最合适的方向。在线服务更适合用 Kubernetes + 推理框架自带的服务化模块,比如 Triton Inference Server,配合自定义路由。
如果只是单机跑一个模型,也不需要编排,直接写好 Python 脚本调用就行。编排和调度是给“规模和复杂度到了一定程度”的场景准备的能力。单机场景硬上 Slurm,只会增加维护成本。
踩过几次之后我发现,很多问题不是工具能力不够,而是前置环境和输入材料没有处理干净。srt-slurm 编排推理部署,真正考验人的地方不是调度器配置,而是对业务任务的抽象能力。你能把任务边界定义清楚,这套方案才能发挥价值;定义不清楚,再好的编排工具也会变成新的麻烦来源。