K8s 上跑 LLM 推理:RDMA 设备权限自动注入与 GPU 拓扑亲和

K8s 上跑 LLM 推理:RDMA 设备权限自动注入与 GPU 拓扑亲和

10 分钟阅读

在 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 后:

  1. 检测容器的 NVIDIA_VISIBLE_DEVICES 环境变量,确定分配了哪些 GPU
  2. /sys/bus/pci/drivers/nvidia/ 枚举 GPU 的 PCI BDF 地址
  3. /sys/class/infiniband/ 枚举 RDMA 设备的 PCI BDF 地址
  4. 通过 PCIe 拓扑匹配 GPU 和 RDMA 设备:
    • PCIe Root Complex 匹配(最高优先级)——同一个 PCIe Switch 下,延迟最低
    • NUMA Node 匹配(降级)——至少在同一个内存域
  5. 把匹配到的 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 推理/训练场景下是合理的,因为通常一个节点只跑一个或少数几个推理/训练任务。

项目地址

Issues 和 PR 欢迎提交。如果对你有帮助,给个 ⭐ 吧。

Zoe

Written by

Zoe

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

评论