
K8s 上跑 LLM 推理:RDMA 设备权限自动注入与 GPU 拓扑亲和
在 Kubernetes 上跑分布式 LLM 推理和训练时,RDMA 网络设备的管理一直是个容易被忽视但影响巨大的问题。本文介绍我开源的 k8s-rdma-device-plugin 项目,解决 RDMA 设备权限自动注入和 GPU-RDMA PCIe 拓扑亲和两大核心痛点。
背景:为什么 RDMA 在 AI 推理场景越来越重要
随着大模型推理走向多卡 TP(Tensor Parallelism)、多机 PP(Pipeline Parallelism)、以及 PD 分离(Prefill-Decode Disaggregation),GPU 间的通信带宽成为关键瓶颈。
- vLLM 在 Q1 2026 Roadmap 中重点推进 Large Scale Serving,涉及 GB200、Wide EP、P/D 分离
- SGLang 的 HiCache + Mooncake 通过 RDMA 传输 KV Cache 实现 PD 分离
- DeepSpeed / Megatron-LM 的分布式训练依赖 GPUDirect RDMA 做梯度同步
NCCL 在检测到 InfiniBand/RoCE 网卡时会自动使用 RDMA 传输,吞吐可以从 TCP 的 ~10Gbps 跳到 IB 的 200-400Gbps。但前提是:容器得能访问 /dev/infiniband/* 设备。
痛点:K8s 上 RDMA 设备管理的三座大山
1. 容器没有设备权限
默认情况下,K8s Pod 无法访问宿主机的 /dev/infiniband/ 设备节点。NCCL 检测不到 IB 设备,静默退化到 TCP 传输,性能暴跌但不报错——这是最阴险的,因为用户可能根本不知道自己跑在 TCP 上。
常见做法是给容器加 privileged: true 或者手动 hostPath 挂载设备:
# ❌ 做法一:privileged(太危险)
securityContext:
privileged: true
# ❌ 做法二:手动挂载(脆弱、不可移植)
volumes:
- name: infiniband
hostPath:
path: /dev/infiniband
这两种都有明显问题:privileged 给了容器对宿主机的完全控制权,hostPath 挂载在不同节点设备不同时会出问题。
2. 选错了 RDMA 网卡
一台 8 卡 GPU 机器通常有多张 RDMA 网卡(比如 4 张 ConnectX-7),每张网卡和特定 GPU 通过 PCIe Switch 直连。如果 GPU 0 的流量走了物理上离 GPU 7 最近的网卡,数据要跨 NUMA 节点甚至跨 PCIe Root Complex,延迟会显著增加。
用户通常需要手动设置 NCCL_IB_HCA=mlx5_0 来绑定网卡,但这需要了解每台机器的 PCIe 拓扑,不可能在大规模集群上手动配。
3. 没有资源管控
K8s 调度器不知道节点有没有 RDMA 网卡、有多少容量,无法做资源级别的调度和限制。在多租户场景下这意味着无法做资源隔离。
现有方案的局限
| 方案 | 资源上报 | 设备权限注入 | GPU-RDMA 亲和 | 无需 privileged |
|---|---|---|---|---|
| Mellanox k8s-rdma-shared-dev-plugin | ✅ | ❌ | ❌ | ❌ |
| SR-IOV network device plugin | ✅ (VF) | ❌ | ❌ | 部分 |
| 手动 hostPath + privileged | ❌ | ✅(粗暴) | ❌ | ❌ |
Mellanox 的 shared-plugin 是目前最接近的方案,但它只做资源计数,不注入设备权限。用户拿到了 rdma/hca_shared_devices: 1 这个资源,但容器里还是没有 /dev/infiniband/uverbs0。
SR-IOV plugin 面向网络虚拟化场景(VF 分配),需要配合 Multus CNI,对纯 RDMA 设备场景来说过重。
解决方案:k8s-rdma-device-plugin
我做了一个把三个能力整合在一起的方案:
┌──────────────────────────────────────────────────────┐
│ k8s-rdma-device-plugin (DaemonSet) │
│ │
│ ┌─────────────────┐ ┌────────────────────────────┐ │
│ │ Device Plugin │ │ NRI Plugin │ │
│ │ │ │ │ │
│ │ 向 kubelet 上报 │ │ • 自动注入 RDMA 设备权限 │ │
│ │ RDMA 虚拟资源 │ │ • 支持 annotation 精细控制│ │
│ │ (可关闭) │ │ • GPU-RDMA 拓扑自动亲和 │ │
│ └─────────────────┘ └────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
能力一:Device Plugin 资源上报
通过 K8s Device Plugin Framework 向 kubelet 注册虚拟 RDMA 资源(默认 rdma.io/hca,数量可配置),使调度器能感知 RDMA 资源。
由于单张 RDMA 卡不支持硬件级隔离,这里的虚拟资源是可替换的(fungible)——每次分配只表示「这个 Pod 需要 RDMA 访问」,而不是分配了某张具体的卡。
这个能力可以通过 --enable-device-plugin=false 关闭,只保留 NRI 注入能力。
能力二:NRI 自动注入设备权限
利用 containerd NRI(Node Resource Interface)在容器创建时 hook 进去,自动注入 RDMA 设备节点:
- 全局设备:
/dev/infiniband/rdma_cm - 每卡设备:
/dev/infiniband/uverbs*、/dev/infiniband/umad*、/dev/infiniband/issm*
NRI 是 containerd 原生支持的插件机制,不需要修改 CNI 配置,不需要 Multus,也不需要容器有 privileged 权限。
同时支持通过 annotation 做精细控制:
metadata:
annotations:
# 为 Pod 中所有容器注入
devices.nri.io/pod: |
- path: /dev/infiniband/uverbs0
type: c
major: 231
minor: 0
# 为特定容器注入
devices.nri.io/container.myapp: |
- path: /dev/infiniband/uverbs1
type: c
major: 231
minor: 1
能力三:GPU-RDMA PCIe 拓扑亲和
这是核心差异化能力。开启 gpuRdmaAutoInject 后:
- 检测容器的
NVIDIA_VISIBLE_DEVICES环境变量,确定分配了哪些 GPU - 从
/sys/bus/pci/drivers/nvidia/枚举 GPU 的 PCI BDF 地址 - 从
/sys/class/infiniband/枚举 RDMA 设备的 PCI BDF 地址 - 通过 PCIe 拓扑匹配 GPU 和 RDMA 设备:
- PCIe Root Complex 匹配(最高优先级)——同一个 PCIe Switch 下,延迟最低
- NUMA Node 匹配(降级)——至少在同一个内存域
- 把匹配到的 RDMA 设备注入容器
以一台典型的 8xH100 + 4xCX-7 机器为例:
PCIe Root 0 PCIe Root 1
├── GPU 0 ├── GPU 4
├── GPU 1 ├── GPU 5
├── mlx5_0 ◄──┐ ├── mlx5_2 ◄──┐
├── GPU 2 │ ├── GPU 6 │
├── GPU 3 │ ├── GPU 7 │
├── mlx5_1 │ └── mlx5_3 │
│ │
Pod 请求 GPU 0,1 → 注入 mlx5_0 │
Pod 请求 GPU 4,5 ─────────────────┘ 注入 mlx5_2
关键点:这个能力对所有带 NVIDIA_VISIBLE_DEVICES 的 GPU 容器生效,不需要 Pod 显式申请 rdma.io/hca 资源。纯 GPU 推理 Pod 自动获得正确的 RDMA 卡。
实际场景
vLLM 多卡推理
apiVersion: v1
kind: Pod
spec:
containers:
- name: vllm
image: vllm/vllm-openai:latest
args: ["--model", "deepseek-ai/DeepSeek-V3", "--tensor-parallel-size", "8"]
env:
- name: NVIDIA_VISIBLE_DEVICES
value: "0,1,2,3,4,5,6,7"
resources:
limits:
nvidia.com/gpu: "8"
# RDMA 设备根据 PCIe 拓扑自动注入!
SGLang PD 分离
不需要手动指定 --disaggregation-ib-device mlx5_1,正确的 RDMA 设备已经在容器中:
apiVersion: v1
kind: Pod
spec:
containers:
- name: sglang-prefill
image: lmsysorg/sglang:latest
command: ["python", "-m", "sglang.launch_server"]
args:
- "--model-path"
- "meta-llama/Llama-3-70B"
- "--tp"
- "4"
- "--disaggregation-mode"
- "prefill"
env:
- name: NVIDIA_VISIBLE_DEVICES
value: "0,1,2,3"
resources:
limits:
nvidia.com/gpu: "4"
仅注入模式(不上报资源)
如果你只需要自动设备注入,不需要 kubelet 资源管控:
helm install rdma-device-plugin ./deploy/charts \
--namespace kube-system \
--set rdma.enableDevicePlugin=false \
--set gpuRdmaAutoInject=true
部署
前置条件
- Kubernetes 1.26+
- containerd 开启 NRI(
enable_nri = true) - RDMA 网卡(Mellanox/NVIDIA ConnectX 系列)
- (GPU 亲和功能)NVIDIA GPU 驱动
Helm 部署
helm install rdma-device-plugin ./deploy/charts \
--namespace kube-system \
--set rdma.resourceName="rdma.io/hca" \
--set rdma.resourceCount=100 \
--set gpuRdmaAutoInject=true
配置方式
支持三种配置方式,优先级:CLI flags > 环境变量 > 配置文件 > 默认值
| 环境变量 | 说明 | 默认值 |
|---|---|---|
RDMA_ENABLE_DEVICE_PLUGIN |
是否启用 Device Plugin | true |
RDMA_RESOURCE_NAME |
资源名称 | rdma.io/hca |
RDMA_RESOURCE_COUNT |
虚拟资源数量 | 100 |
RDMA_GPU_AUTO_INJECT |
GPU-RDMA 自动注入 | false |
技术实现要点
为什么选 NRI 而不是 Webhook
Admission Webhook 只能修改 Pod Spec(加 volume/volumeMount),无法直接操作容器的 Linux device cgroup。而 NRI 工作在 containerd 的容器生命周期 hook 中,可以直接通过 LinuxDevice 注入设备节点,精确控制 major/minor 号和文件权限。
PCIe 拓扑发现的可靠性
通过 sysfs 读取拓扑信息,不依赖任何用户态工具:
/sys/bus/pci/drivers/nvidia/ → GPU PCI BDF 列表
/sys/class/infiniband/<dev>/device → RDMA 设备的 PCI BDF(symlink)
/sys/bus/pci/devices/<BDF>/ → numa_node, 设备路径(含 PCIe root)
GPU 枚举顺序按 PCI BDF 排序,与 NVIDIA 的 GPU index 分配一致。
单卡不隔离的设计选择
RDMA 网卡(shared mode)不支持硬件级别的容器隔离——多个容器可以共享同一张网卡的 IB 端口。因此 Device Plugin 上报的是虚拟资源 slot,NRI 注入的是同一组设备节点。这在 AI 推理/训练场景下是合理的,因为通常一个节点只跑一个或少数几个推理/训练任务。
项目地址
- GitHub: https://github.com/jiusanzhou/k8s-rdma-device-plugin
- License: Apache 2.0
Issues 和 PR 欢迎提交。如果对你有帮助,给个 ⭐ 吧。

Written by
Zoe
AI Infra Engineer · LLM Serving · GPU/RDMA · 造工具的偏执狂