srt-slurm:GPU集群大模型推理服务的自动发现与拓扑感知编排 在 GPU 集群上跑大模型推理服务很多团队都会遇到一个尴尬问题Slurm 把资源调度好了但推理请求并不知道该往哪台机器发。NVIDIA 开源的 srt-slurm 项目正是为了解决这个问题出现的它能在 Slurm 作业与推理服务之间建立一层自动发现和编排机制。本文将从架构原理入手逐步拆解部署流程、配置细节、完整实战案例与高频问题排查思路帮助你快速上手这套方案。1. srt-slurm 是什么解决什么核心问题1.1 大型 GPU 集群推理服务的编排痛点先看一个典型场景。假设你有一个 32 节点的 GPU 集群每台节点 8 张 A100。用户通过 Slurm 提交作业来启动 TensorRT-LLM 或 NVIDIA NIM 推理服务。Slurm 负责分配 GPU、拉起容器、管理作业生命周期这一步本身没有问题。问题出在推理请求这一侧。当客户端要调用大模型时它怎么知道哪个 GPU 上正在运行哪个模型NPU、A100、H100 上部署了多个副本客户端该怎么选如果选中的实例负载已高能不能自动切换到同模型的其他副本更复杂一点多机之间通过 NVLink 互联的 GPU 组成一个域推理请求能不能优先落在同一个 NVLink 域内减少跨机通信延迟在没有编排层的情况下团队通常的做法是人工维护一份“IP 端口 模型名”的静态清单用 Nginx 做一层粗糙的反向代理或自己写服务发现脚本定时扫描作业状态。这些方案在小规模下能跑但一旦模型数量多、副本动态扩缩容、GPU 拓扑复杂维护成本就会迅速失控。静态清单里过期条目无人清理新增副本没有及时接入误调度导致推理延迟飙升这些都是常态。1.2 srt-slurm 是什么srt-slurm 的全称是 Scheduler for LLMs with SLURM是 NVIDIA 开源的一套 GPU 推理服务编排工具用于在 Slurm 集群上实现推理服务的自动发现、自动注册和智能路由。它由两个主要组件构成组件实现语言作用Slurm SPANK 插件C作为 Slurm 作业生命周期回调负责在作业启动时收集 GPU 与 NIM 信息向调度器注册作业结束时自动反注册Scheduler 调度器Go接收并保存所有注册实例的元数据接收推理请求结合 GPU 拓扑、NVLink 域、地理位置等信息选择最优实例并转发请求SPANK 是 Slurm 提供的插件机制可以在作业的 prolog、epilog 等阶段插入自定义逻辑无需修改 Slurm 主程序。srt-slurm 正是利用这一机制把“作业启动”和“服务注册”自动关联起来。1.3 为什么需要专门为推理服务设计编排层通用容器编排工具如 Kubernetes也能做服务发现与负载均衡但在 HPC 场景下Slurm 仍然是很多团队管理 GPU 资源的标准工具。问题在于 Slurm 本身只管资源分配不管应用层路由。srt-slurm 的核心价值在于自动注册无需手工维护服务清单作业启动即注册作业结束即注销拓扑感知利用 Slurm 返回的 GPU 绑定信息和 NVIDIA 驱动提供的 NVLink 域信息构建物理拓扑实例感知调度路由请求时优先选择同一 NVLink 域内的实例减少跨机通信无侵入对 Slurm 集群几乎无侵入只增加一个插件和一个独立调度器进程。简单来说srt-slurm 让“Slurm 作业”与“可调用的推理服务”之间建立了一条自动化的双向通道。2. 核心架构与工作流程详解2.1 整体架构为了更直观地理解我们先看一个整体流程的 ASCII 示意图---------------- sbatch/srun ------------------------- | 用户客户端 | --------------------- | Slurm 集群 | ---------------- | (提交 NIM 推理作业) | ------------------------ | 作业分配 GPU启动容器 v ------------------------- | SPANK 插件 (prolog) | | 收集 GPU/NVLink/端口 | ------------------------ | RegisterInstance API v ---------------- /v1/chat/completions ------------------------- | 推理客户端 | ------------------------- | Scheduler 调度器 | ---------------- | - 实例注册表 | | - 拓扑感知路由 | | - 本地/远程转发 | ------------------------- | | 根据拓扑选择最优实例 v ------------------------- | 目标 NIM/TensorRT-LLM | | 实例 (端口 8000/8001) | -------------------------整个系统分为三条链路作业提交链用户通过 Slurm 提交 NIM 作业。注册链SPANK 插件在作业启动后向 Scheduler 注册服务元数据。推理链客户端请求先到 SchedulerScheduler 根据元数据路由到具体实例。2.2 作业生命周期如何驱动自动注册srt-slurm 的巧妙之处在于复用 Slurm 作业生命周期不需要额外的守护进程去“扫描集群”。具体过程如下用户使用srun或sbatch提交一个运行 NIM 容器的作业。Slurm 为作业分配 GPU 资源并启动作业。SPANK 插件在 prolog 阶段执行读取以下信息SLURM_JOB_ID当前作业 IDSLURM_JOB_GPUS分配的 GPU 列表CUDA_VISIBLE_DEVICES容器内可见 GPU通过 NVMLNVIDIA Management Library查询每个 GPU 的 NVLink 域通过环境变量读取 NIM 端口、模型名称、API Key 等信息。插件将上述信息组装成元数据调用 Scheduler 的RegisterInstanceAPI。Scheduler 将实例信息写入内存注册表。作业结束时插件在 epilog 阶段调用DeregisterInstanceAPI将实例从注册表移除。这套设计确保了注册信息与作业生命周期严格一致不会出现“作业已结束但服务还在列表里”的脏数据。2.3 请求调度如何做到拓扑感知当推理请求到达 Scheduler 时它并不盲目轮询而是执行以下决策流程解析请求中的模型名称查询注册表中所有运行该模型的实例提取请求来源的 GPU 拓扑信息如果客户端本身也运行在 GPU 节点上如果客户端与某个实例处于同一 NVLink 域优先选择该实例如果客户端不在任何 GPU 节点上则选择网络延迟最低的实例在拓扑相近的实例中进一步根据实例已维护的负载指标做均衡将请求转发给最终选定的实例并返回响应。这里的关键点在于 NVLink 域的判断。NVLink 是 NVIDIA GPU 间的高速互联技术多卡之间通过 NVLink 组成一个高速通信域。同一 NVLink 域内多卡通信带宽高、延迟低。当客户端与推理服务处于同一域时请求与响应的传输开销会显著降低。3. 环境准备与部署前检查3.1 软硬件要求在开始部署前你需要先确认环境满足基本要求。项目要求GPUNVIDIA 数据中心级 GPUA100、H100、A800、H800 等支持 NVLink 优先GPU 驱动450.80.02 及以上版本建议使用较新的 535 或 550 系列操作系统Ubuntu 20.04 / 22.04或其他主流 Linux 发行版Slurm21.08 及以上版本需要启用 SPANK 插件支持NVIDIA Container Toolkit用于容器内访问 GPUCUDA11.8 或 12.x视 NIM 容器要求而定Go1.21 及以上编译 Scheduler 组件C 编译器g 9.4 及以上编译 SPANK 插件版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。3.2 确认 Slurm 版本与 SPANK 支持SPANK 是 Slurm 自带的插件系统不需要额外安装。但需要确认编译 Slurm 时是否启用了相关选项。通常发行版自带的 Slurm 包已包含 SPANK 支持。你可以通过以下命令查看 Slurm 版本sinfo --version slurmctld --version确认输出中的版本号不低于 21.08。还需要确认 SPANK 插件目录。不同发行版路径不同常见位置# Ubuntu/Debian /usr/lib/slurm/ /usr/lib/x86_64-linux-gnu/slurm/ # CentOS/RHEL /usr/lib64/slurm/把插件.so文件放到对应目录即可在slurm.conf中通过绝对路径引用更可靠。3.3 确认 NVIDIA 驱动与 Container Toolkit如果集群节点上还没有安装 NVIDIA 驱动可以参考官方文档安装。这里给出一个常见检查流程# 查看驱动版本 nvidia-smi # 查看 NVLink 状态如果有 NVLink nvidia-smi nvlink --status # 验证 Container Toolkit 是否安装 nvidia-container-cli --version # 验证容器内 GPU 可见性 docker run --rm --gpus all nvidia/cuda:12.2.0-base-ubuntu22.04 nvidia-smi如果nvidia-smi输出正常且容器内能看到 GPU说明驱动与容器运行时已经就绪。需要注意的是NIM 镜像通常已经包含运行时依赖但宿主机必须提供 GPU 驱动。特别要注意驱动版本与容器内 CUDA 版本的兼容关系。遇到“容器内无法识别 GPU”“驱动版本不支持”这类问题时优先排查宿主机驱动是否过旧。4. 获取源码与编译 srt-slurm4.1 获取源码srt-slurm 的源代码托管在 GitHub 上项目地址为NVIDIA/srt-slurm。你可以通过 Git 克隆git clone https://github.com/NVIDIA/srt-slurm.git cd srt-slurm源码目录结构大致如下srt-slurm/ ├── Makefile ├── README.md ├── slurm_plugin/ # SPANK 插件源码C ├── scheduler/ # 调度器源码Go ├── examples/ # 示例配置与脚本 ├── configs/ # 配置文件模板 └── docs/ # 文档具体目录可能因版本调整以实际 Clone 的仓库为准。4.2 编译 SPANK 插件SPANK 插件需要 Slurm 开发头文件。以 Ubuntu 为例安装依赖sudo apt update sudo apt install -y slurm-wlm-basic-plugins-dev如果你使用的是从源码编译的 Slurm需要确保slurm.h、spank.h等头文件在系统 include 路径中。进入插件目录编译cd slurm_plugin make编译成功后会生成libsrt_slurm_plugin.so文件。将插件复制到 Slurm 插件目录sudo cp libsrt_slurm_plugin.so /usr/lib/slurm/4.3 编译 Scheduler 调度器调度器使用 Go 编写需要先安装 Go 工具链。可以参考官方安装方式wget https://go.dev/dl/go1.21.5.linux-amd64.tar.gz sudo tar -C /usr/local -xzf go1.21.5.linux-amd64.tar.gz export PATH$PATH:/usr/local/go/bin然后编译调度器cd ../scheduler go build -o srt-scheduler .编译产物srt-scheduler是一个独立的二进制文件可以部署到集群中的任意节点通常是登录节点或独立的管理节点。更稳妥的做法是使用项目自带的 Makefile 在根目录统一编译cd .. make all编译完成后scheduler/srt-scheduler和slurm_plugin/libsrt_slurm_plugin.so都会生成。5. 配置 srt-slurm插件与调度器5.1 配置 Scheduler 调度器Scheduler 启动时需要一个配置文件通常为 YAML 格式。在项目configs/目录下可以找到模板。我们创建一个实际的配置文件/etc/srt-scheduler/config.yamlserver: # Scheduler 监听的地址0.0.0.0 表示所有网卡 address: 0.0.0.0 # Scheduler 服务端口 port: 8080 scheduler: # 实例心跳超时时间秒超过该时间未续约则实例被视为不可用 instance_ttl: 300 # 是否启用严格拓扑路由强烈建议生产环境开启 strict_topology: true # 注册表清理周期秒 cleanup_interval: 60 logging: # 日志级别debug, info, warn, error level: info # 日志输出文件留空输出到 stdout output: 具体配置项名称可能随版本演进调整请以项目文档为准。上面展示的是配置思路下面启动调度器sudo mkdir -p /etc/srt-scheduler sudo cp configs/config.yaml /etc/srt-scheduler/ ./scheduler/srt-scheduler --config /etc/srt-scheduler/config.yaml如果希望以 systemd 服务形式运行可以创建/etc/systemd/system/srt-scheduler.service[Unit] DescriptionSRT Scheduler for LLMs Afternetwork-online.target [Service] ExecStart/usr/local/bin/srt-scheduler --config /etc/srt-scheduler/config.yaml Restartalways RestartSec10 Userroot Grouproot [Install] WantedBymulti-user.target启动服务sudo cp scheduler/srt-scheduler /usr/local/bin/ sudo systemctl daemon-reload sudo systemctl enable --now srt-scheduler sudo systemctl status srt-scheduler5.2 配置 SPANK 插件SPANK 插件的配置分为两部分Slurm 全局配置和插件自身配置文件。5.2.1 修改 slurm.conf编辑 Slurm 控制节点的slurm.conf文件通常位于/etc/slurm/slurm.conf添加 SPANK 插件配置# 在 slurm.conf 中追加 PlugStackControl/usr/lib/slurm/libsrt_slurm_plugin.soPlugStackControl是 SPANK 插件在作业控制流程中的挂载点。修改后需要重启slurmctld和slurmdsudo systemctl restart slurmctld sudo systemctl restart slurmd如果是通过scontrol在线更新配置可以执行sudo scontrol reconfigure5.2.2 创建插件配置文件SPANK 插件需要知道 Scheduler 的地址这个信息通常在插件配置文件中指定。创建/etc/slurm/srt_slurm_plugin.conf# Scheduler 服务地址 scheduler_address192.168.1.100 # Scheduler 服务端口 scheduler_port8080 # 插件日志路径用于诊断问题 log_file/var/log/srt_slurm_plugin.log # 日志级别 log_levelinfo注意这里的地址必须是计算节点能访问到的地址。如果 Scheduler 部署在管理节点确保管理节点与计算节点之间网络互通。5.3 插件如何识别 NIM 实例srt-slurm 插件在作业 prolog 阶段收集信息时需要识别哪些作业是 NIM 推理作业。插件会读取一组环境变量来判断环境变量说明SRT_NIM_ENDPOINTNIM 服务的完整端点例如http://0.0.0.0:8000/v1SRT_MODEL_NAME模型名称例如llama3-8b-instructSRT_NIM_PORTNIM 服务端口例如8000SRT_NIM_API_KEY调用 NIM 需要的 API KeySRT_ENABLE_REGISTRATION是否启用自动注册默认true如果作业没有设置这些环境变量插件会默认不注册该作业。这样可以避免把普通 GPU 作业误当成 NIM 服务。在提交作业时可以通过--export或--export-file将环境变量传递给 srun/sbatchsrun --gpus1 \ --exportALL,SRT_MODEL_NAMEllama3-8b-instruct,SRT_NIM_PORT8000 \ ...6. 实战构建一个端到端推理服务编排6.1 场景描述假设我们有 2 台计算节点每台节点 4 张 A100 GPU。我们要在节点 A 上部署一个llama3-8b-instruct的 NIM 实例在节点 B 上部署另一个llama3-8b-instruct实例。客户端请求通过 Scheduler 路由到任一实例。6.2 准备 NIM 镜像NVIDIA NIM 镜像通常从 NGC 拉取。示例# 登录 NGC docker login nvcr.io # 拉取 NIM 镜像以 TensorRT-LLM 版 Llama3 为例 docker pull nvcr.io/nim/llama3-8b-instruct:latest如果拉取受限说明需要申请 NIM 访问权限请先到 NVIDIA 官网注册并获取 API Key。6.3 编写 Slurm 作业脚本创建一个作业脚本submit_nim.slurm#!/bin/bash #SBATCH --job-namenim-llama3-8b #SBATCH --nodes1 #SBATCH --gresgpu:1 #SBATCH --time02:00:00 #SBATCH --outputlogs/nim_%j.log #SBATCH --errorlogs/nim_%j_err.log # 暴露给 SPANK 插件的环境变量 export SRT_MODEL_NAMEllama3-8b-instruct export SRT_NIM_ENDPOINThttp://0.0.0.0:8000/v1 export SRT_NIM_PORT8000 # 拉取镜像也可以预置在节点上 docker pull nvcr.io/nim/llama3-8b-instruct:latest # 获取当前作业分配的 GPU 索引 GPU_INDEX$CUDA_VISIBLE_DEVICES # 启动 NIM 容器 docker run --rm \ --gpus device${GPU_INDEX} \ --shm-size16g \ -p 8000:8000 \ -e NGC_API_KEY${NGC_API_KEY} \ -e NIM_HTTP_API_PORT8000 \ nvcr.io/nim/llama3-8b-instruct:latest提交作业sbatch submit_nim.slurm在第二个节点上再提交一次得到两个 NIM 实例。6.4 验证自动注册作业启动后SPANK 插件会自动将实例注册到 Scheduler。你可以通过 Scheduler 的 API 查看当前实例列表curl -s http://scheduler-host:8080/v1/instances | jq .预期输出中会包含类似以下信息的 JSON{ instances: [ { id: job_1234, model_name: llama3-8b-instruct, endpoint: http://192.168.1.10:8000/v1, node: node-a, gpu_index: 3, nvlink_domain: 0, status: ACTIVE }, { id: job_5678, model_name: llama3-8b-instruct, endpoint: http://192.168.1.11:8000/v1, node: node-b, gpu_index: 1, nvlink_domain: 1, status: ACTIVE } ] }关键字段包括实例 ID、模型名称、访问端点、所在节点、GPU 索引、NVLink 域和状态。只有状态为ACTIVE的实例才会被路由。6.5 通过 Scheduler 发送推理请求Scheduler 对外提供 OpenAI 兼容的推理接口。客户端只需向 Scheduler 发送请求由它完成路由curl -s http://scheduler-host:8080/v1/chat/completions \ -H Content-Type: application/json \ -d { model: llama3-8b-instruct, messages: [ {role: user, content: 解释一下什么是 GPU 推理编排} ], max_tokens: 512, temperature: 0.2 } | jq .Scheduler 会根据模型名找到对应的实例选择拓扑最优的一个将请求转发到该实例的端点并把响应返回给客户端。如果两个实例分别位于不同的 NVLink 域且客户端也在 GPU 节点上Scheduler 会优先选择与客户端处于同一 NVLink 域的实例。如果客户端不在 GPU 节点上则会选择网络延迟较低的实例。7. 常见问题与排查思路7.1 高频问题排查表问题现象常见原因解决思路插件未加载作业正常但未注册slurm.conf 未配置PlugStackControl或路径错误检查slurm.conf配置与插件文件路径重启slurmctld作业启动时报找不到.so文件SPANK 插件未复制到正确目录确认插件目录使用绝对路径引用实例注册失败Scheduler 地址不可达、端口未开放在计算节点上curlScheduler 地址检查防火墙实例状态为UNHEALTHYNIM 容器未正常启动或健康检查失败查看 NIM 容器日志确认模型加载完成请求路由到错误的实例多个模型实例未正确区分模型名检查SRT_MODEL_NAME环境变量是否唯一NVLink 域信息缺失GPU 驱动版本过旧或不支持 NVLink升级驱动运行nvidia-smi nvlink --status验证Scheduler 启动失败配置文件格式错误或端口被占用检查 YAML 格式、端口占用情况用命令行参数解析排查作业结束后实例仍未注销epilog 阶段插件执行失败查看/var/log/srt_slurm_plugin.log日志7.2 定位问题的通用排查流程遇到问题时推荐按以下顺序排查先看 Scheduler 日志。Scheduler 是核心中转站注册、路由、错误信息都会记录在这里。再看插件日志。插件日志记录每个作业的注册、反注册动作。检查 Slurm 作业状态。确认作业是否正常运行容器是否在预期节点启动。手动调用 Scheduler API。确认实例列表中是否有预期条目。直接访问 NIM 端点。确认实例本身可用。7.3 典型排障示例假设你提交作业后在/var/log/srt_slurm_plugin.log中看到以下日志ERROR: Failed to register instance: Post http://192.168.1.100:8080/v1/instances: dial tcp 192.168.1.100:8080: connect: connection refused说明计算节点无法访问 Scheduler 的 8080 端口。排查步骤# 在计算节点上测试端口连通性 curl -v telnet://192.168.1.100:8080 # 确认 Scheduler 监听状态在管理节点上执行 ss -tlnp | grep 8080 # 查看防火墙规则 sudo iptables -L -n | grep 8080定位到问题后通常需要调整防火墙放行端口或修改配置中的scheduler_address为管理节点的内网 IP。8. 最佳实践与工程建议8.1 作业资源配置建议每个 Slurm 作业最好只启动一个 NIM 实例。多实例共享一个作业会增加复杂度也会让实例注销逻辑变得难以控制。GPU 分配使用--gpus1。不要在一个作业内申请多卡运行多个服务进程除非你明确知道它们在同一个 NVLink 域内有协作需求。为每种模型设置独立的SRT_MODEL_NAME避免同名模型多副本在路由时产生歧义。为 NIM 作业设置足够长的--time限制并配合自动重启策略避免服务在无人值守时退出。8.2 调度器高可用目前的 Scheduler 是无状态服务实例注册表保存在内存中。如果 Scheduler 重启所有注册信息会丢失但插件会在作业运行时周期性续约或重新注册。生产环境中建议将 Scheduler 部署在专用的管理节点上并配置 systemd 自动重启使用负载均衡器如 HAProxy、Nginx为多个 Scheduler 副本提供入口当 Scheduler 重启后检查实例注册表是否自动恢复如果插件没有自动重新注册可以考虑在 Scheduler 恢复后手动重启相关 Slurm 作业。8.3 NVLink 域的正确使用NVLink 域信息是实现拓扑感知路由的关键但它只在多卡 GPU 节点上有意义。如果你的 GPU 不支持 NVLink如部分消费级显卡srt-slurm 的拓扑感知能力会退化为普通网络延迟调度在异构集群中建议为支持 NVLink 的节点打上标签方便识别和调度使用nvidia-smi nvlink --status确认节点上的 NVLink 连接是否健康。8.4 安全与权限NIM 端点通常包含 API Key。建议在 Slurm 作业脚本中使用环境变量注入而不是硬编码在脚本中Scheduler 对外暴露 HTTP API应限制访问来源避免未授权用户调用注册接口如果集群内网不可完全信任建议为 Scheduler 启用 TLS。具体配置方式可参考官方文档本文不再展开不要在日志中输出完整的 API Key插件和 Scheduler 的日志级别建议设置为info而不是debug除非在排查问题时临时开启。8.5 多模型场景下的路由策略当集群同时部署多个模型时建议在模型名中体现版本信息例如llama3-8b-instruct-v2。这样在更新模型版本时可以保留旧版本实例用于灰度通过修改客户端请求中的模型名逐步切换流量。9. 总结与下一步学习方向本文围绕 NVIDIA 开源的 srt-slurm 项目梳理了它在 Slurm 集群中实现 GPU 推理服务自动化编排的完整链路从 SPANK 插件在作业生命周期中完成自动注册到 Scheduler 通过 NVLink 域、GPU 索引等元数据实现拓扑感知路由再到实际的 NIM 服务部署与推理请求验证。整体来看srt-slurm 最大价值在于把“Slurm 作业状态”和“推理服务可用状态”打通让 NIM 实例不再是一堆需要人工维护的静态地址而是随作业启动自动出现在可路由列表中的动态服务。实际部署时建议先在一个小规模测试环境跑通端到端流程再逐步扩展到生产集群。重点关注几个环节SPANK 插件是否正确加载、插件与调度器的网络连通性、NIM 实例的命名规范、以及 Scheduler 重启后实例注册表的恢复机制。这些都是最容易出问题的地方。如果你正在同时使用 Kubernetes也可以进一步了解 NVIDIA 官方在 Kubernetes 生态下的类似编排方案和 srt-slurm 做对比选择更适合团队基础设施的路线。关于 srt-slurm 的更多细节建议直接查阅项目仓库中的 README 与 docs 目录里面的配置示例和 API 说明会随版本持续更新。