机器学习平台技术栈之 RDMA Device Plugin

在大语言模型(LLM)狂飙突进的时代,训练一个千亿参数的模型动辄需要上千张甚至上万张 GPU。在这个规模下,单节点的算力早已不是唯一的瓶颈,节点间的网络通信能力往往决定了整个集群的计算效率上限

在分布式训练(如基于 NCCL 的 AllReduce 集合通信)中,GPU 之间需要极其频繁地交换几十甚至上百 GB 的梯度数据。传统的 TCP/IP 协议栈由于其内核态到用户态的内存拷贝(Copy)、上下文切换(Context Switch)开销,导致了极高的延迟和严重的 CPU 负担。为了突破这道网络墙,RDMA(Remote Direct Memory Access,远程直接内存访问) 成为了现代 AI 集群的标配。

而在云原生(Kubernetes)体系下,如何将物理机上的 RDMA 高性能网卡无损地、安全地透传给容器(Pod)使用,是构建 AI 基础设施的关键挑战。这正是 RDMA Device Plugin 的核心使命。

本文将剥茧抽丝,从 TCP/IP 的痛点出发,详细解读 RDMA 的核心概念、K8s 设备插件机制,以及 RDMA Device Plugin 的架构设计与最底层的全链路技术细节,带你真正吃透这块构建高性能机器学习平台不可或缺的基石。

1. 核心概念解析

在深入 RDMA Device Plugin 之前,我们必须先理清底层的硬件与网络概念。

1.1 为什么需要 RDMA?(TCP/IP 的困境)

在传统的 TCP/IP 网络交互中,当应用进程发送数据时,数据流向是:
应用缓冲区 (User Space) -> Socket 缓冲区 (Kernel Space) -> 网卡驱动 -> 物理网卡 (NIC)
这个过程经历了多次内存数据拷贝(Data Copy),并且需要 CPU 介入进行协议栈的封装/解封装,带来大量的 CPU 中断和上下文切换。在 400Gbps 乃至 800Gbps 的网络时代,CPU 即便 100% 满载也无法跑满网卡带宽。

RDMA (Remote Direct Memory Access) 的出现彻底改变了这一现状。它允许一台计算机直接读写另一台计算机的内存,**完全绕过操作系统内核 (Kernel Bypass),实现了真正的零拷贝 (Zero-Copy)**。

  • Kernel Bypass: 应用直接通过用户态的驱动(User-level verbs API)控制网卡。
  • Zero-Copy: 数据直接从应用的内存写入对端物理内存,没有内核缓冲区的参与。
  • CPU Offload: 数据传输由网卡(HCA, Host Channel Adapter)直接完成,释放 CPU 资源给核心计算。

1.2 RDMA 的三大网络实现

RDMA 是一种技术思想,在物理链路上主要有三种实现:

  1. InfiniBand (IB): 专为 RDMA 设计的无损网络架构,性能最好,但需要专用的 IB 交换机和线缆,成本极高。
  2. RoCE (RDMA over Converged Ethernet): 将 RDMA 报文封装在传统的以太网上。RoCEv2 封装在 UDP/IP 之上,具备良好的路由能力,是目前各大云厂商 AI 计算集群中最主流的方案。
  3. iWARP: 基于 TCP 实现的 RDMA,虽然兼容性好,但因 TCP 本身的开销,性能逊于前两者,在 AI 领域极少使用。

1.3 SR-IOV (Single Root I/O Virtualization)

当物理机接入了一块高速 RDMA 网卡(如 Mellanox ConnectX-6/7),如何在多台容器或虚拟机之间共享这块网卡呢?
SR-IOV 技术应运而生。它能够将一个物理功能(PF, Physical Function)虚拟出多个相互隔离的虚拟功能(VF, Virtual Function)
每一个 VF 都可以像独立的物理网卡一样被分配给一个 Pod。由于支持硬件层面的隔离,VF 既享受了原生的 RDMA 性能,又实现了容器间的安全隔离。

1.4 Kubernetes Device Plugin Framework

Kubernetes 默认只管理 CPU 和 Memory。对于 GPU、FPGA、RDMA 等异构计算和通信硬件,K8s 提供了一套被称为 **Device Plugin (设备插件)**的机制。
各个硬件厂商部署自己编写的 Device Plugin DaemonSet 运行在 K8s Node 上,通过 gRPC 协议与本机的 Kubelet 通信,告诉 Kubelet 本机有多少块专属硬件可供分配。

2. 核心概念之间的关系网络

在云原生 AI 训练任务中,RDMA 的使用链路涉及 K8s、CNI 网络插件和设备插件。下图描述了它们之间的复杂协作关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
graph TD
subgraph K8s Control Plane
API[API Server]
Scheduler[K8s Scheduler]
end

subgraph Node
Kubelet[Kubelet]
RDP[RDMA Device Plugin]
Multus[Multus CNI Plugin]
SRIOV_CNI[SR-IOV / Macvlan CNI]

subgraph Pod [AI Training Pod]
App[PyTorch/NCCL Process]
NetDev[eth1 / net1]
DevFS[/dev/infiniband/uverbs0]
end

Hardware[Mellanox ConnectX-6 NIC \n PF / VF]
end

App -.->|Bypass Kernel \n direct write| Hardware
App -.->|Bind IP| NetDev
API <-->|List/Watch Resources| Kubelet
Kubelet <-->|gRPC: Discover / Allocate| RDP
RDP -->|Maps Char Devices| DevFS
Kubelet -->|Trigger network setup| Multus
Multus -->|Call low-level CNI| SRIOV_CNI
SRIOV_CNI -->|Moves VF into Pod netns| NetDev
Hardware -->|Generates VFs| NetDev
  • Multus CNI: Kubernetes 默认一个 Pod 只有一个网卡(eth0,用于控制流)。如果又要提供业务控制面,又要使用高达 400G 的 RDMA 数据面网络,就需要 Multus CNI 让 Pod 拥有多张网卡(Multiple Network Interfaces)。
  • SR-IOV CNI: 负责将宿主机上虚拟出的 VF 接口,通过 Linux namespace 隔离移动到容器的 network namespace 中,并配置 IP。
  • RDMA Device Plugin: 并不配置 IP 和以太网链路,它专职负责向容器挂载能执行 RDMA Kernel Bypass 所必须的字符设备文件(即 /dev/infiniband/* 目录下的硬件抽象映射)。
    这些组件完美配合,让 Pod 既拥有网卡(配置 NCCL IP binding)又拥有底层字符设备读写权限(开启 verbs direct access)。

3. RDMA Device Plugin 架构设计

Kubernetes 社区广泛使用的是由 Mellanox 维护的 k8s-rdma-device-plugin 以及后续更复杂的 sriov-network-device-plugin
我们以 RDMA Device Plugin 为例探究其架构。

它主要由以下三个模块构成:

  1. **Sysfs Watcher (设备发现层)**:监听宿主机的 sysfs 文件系统树(如 /sys/class/infiniband//sys/class/net/),发现可用的 RDMA 设备(PF 或 VF),收集设备的拓扑、PciID、NUMA node 节点等信息。
  2. **Plugin Server (gRPC 接口层)**:实现了 K8s 标准定义的 v1beta1.DevicePlugin gRPC 服务接口(包含 ListAndWatchAllocate 两个最核心的 API)。
  3. **Config Manager (策略与配置层)**:根据 K8s configmap 中定义的策略(使用 SR-IOV 还是 MACVLAN 共享,支持什么类型的网络),将底层物理资源重新抽象聚合给上层。

gRPC 交互时序 (架构细节)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sequenceDiagram
participant Kubelet
participant RDP as RDMA Device Plugin
participant Sysfs as Host OS (Sysfs)

Sysfs->>RDP: Hardware Event (Load Kernel Module `ib_core`)
RDP->>Sysfs: Scan /sys/class/infiniband/
RDP->>RDP: Create device list: [rdma1a, rdma1b]
RDP->>Kubelet: Register via gRPC Socket (/var/lib/kubelet/device-plugins/rdma.sock)
Kubelet->>RDP: call ListAndWatch()
RDP-->>Kubelet: Stream Stream of Devices (Capacity: 2, ResourceName: rdma/cx6)

Note over Kubelet: User creates Pod requesting resources: rdma/cx6: 1

Kubelet->>RDP: call Allocate(DeviceIDs: ["rdma1a"])
RDP-->>Kubelet: AllocateResponse { HostPaths: ["/dev/infiniband/uverbs0", "/dev/infiniband/rdma_cm"] }
Kubelet->>Kubelet: Create Container with volume mounts for /dev/infiniband

4. 关键技术细节剖析

作为构建集群的技术深度好文,我们还需要将显微镜对准最核心的几个技术细节:到底是什么支撑着这个看似简单的 Device Plugin 走完了最后一公里?

4.1 ListAndWatch:资源注册与 NUMA 感知

在 AI 集群中,GPU 网卡通常会绑定在特定的 PCIe Switch 下,这涉及到 NUMA 架构。跨 NUMA 高速通信会带来巨大的带宽高消耗和延迟的急剧攀升(因为必须走 CPU QPI/UPI 总线)。

RDMA Device Plugin 在向 Kubelet 汇报(ListAndWatch 接口)可以分配的设备列表时,除了返回 DeviceID 之外,更重要的是会上报 Topology 信息(包含了 NUMA Node ID)

1
2
3
4
5
6
// K8s Device Plugin API definition fragment
type Device struct {
ID string
Health string
Topology *TopologyInfo
}

结合 K8s 1.18+ 引入的 **Topology Manager (拓扑管理器)**,Kubelet 会在本地做出对齐策略:如果 Pod 同时申请了 GPU 和 RDMA,Topology Manager 可以保证分配到的 GPU 和 RDMA 网卡必定处在同一个 NUMA Node 上。这正是诸如 GPUDirect RDMA(让 RDMA 芯片直接越过 CPU 读写 GPU 显存)技术得以施展的基石。

4.2 Allocate:注入神秘的字符设备

当一个 Pod 真的被调度到节点上时,RDMA Device Plugin 的 Allocate 的方法会被触发。
RDMA Kernel Bypass 的核心在于 K8s 容器需要直接和内核的 InfiniBand 栈打交道。为了让用户态应用能操控网卡,Linux 内核抽象出了以下几个重要设备文件:

  • /dev/infiniband/uverbsX:(重要)负责建立 Queue Pairs (QP, 发送/接收队列簇),并进行控制面命令的下发。
  • /dev/infiniband/rdma_cm: RDMA Connection Manager,用于建立类似 TCP/IP 的套接字连接通信(常被 NCCL Socket 模式调用)。
  • /dev/infiniband/issmX: 用于 InfiniBand 网络的子网管理。

AllocateResponse 中,Plugin 明确指定这些设备文件不仅要被 Volume Mounts 到容器系统的相同路径下,还要设置 **Linux Device Permissions (cgroups 设备控制)**,使得容器内的进程获得读写的最高权限。

4.3 Lock Memory: CAP_IPC_LOCK 权限

这里隐藏着坑了无数初学者的深水区。

我们在第一卷中提到 RDMA 是 Zero-Copy 的,即网卡直接去内存取数据。但由于操作系统的虚拟内存页面置换机制(Swap/Paging),如果此时应用态的内存页面恰巧被换出了物理内存,RDMA 网卡的 DMA 时钟将产生灾难性的缺页中断,这在硬件传输层面是无法挽回的。

为了防止被 DMA 访问的内存页面遭到内核置换,用户的 AI 训练等程序在使用 RDMA 时必须**锁住内存页面,即 Pin Memory (通过系统调用 mlock)**。然而普通的进程并没有大面积锁定物理内存的权限,它需要 ulimit -l 配置的支持。

在云原生环境中,RDMA 相关的部署往往需要给容器赋予特殊的 Linux Capabilities:

1
2
3
4
securityContext:
capabilities:
add:
- IPC_LOCK

CAP_IPC_LOCK (Capability Inter-Process Communication Lock) 是进行大页内存操作或调用 verbs 接口在宿主锁定任意大小内存的必要凭证。缺乏这个 Capability,即便 RDMA Device Plugin 注入了 /dev/infiniband 文件,NCCL 在尝试向驱动申请注册 MR (Memory Region) 操作时也会立刻无情报错崩溃。

4.4 Shared Mode (共享模式) vs SR-IOV 模式

在 K8s 中部署带有 RDMA 的业务,Plugin 在配置和配合上提供两种典型的网工模式:

  1. MacVlan / Ipvlan Shared 模式
    在物理网卡只有一块,且没有开启 SR-IOV 功能时。RDMA Device Plugin 支持通过 Macvlan 将相同的物理 RoCE 块抽象后塞进多个容器。缺点是所有容器共享同一个 PF(物理设备的硬件队列),隔离性极其糟糕,某个用户的容器发起的超高频 RDMA 请求很容易挤爆网卡的限流,导致其他容器连坐。
  2. SR-IOV 单占模式(生产标配):
    通过底层开启 SR-IOV,生成 ens1f0v0, ens1f0v1…这些独立的 VF。并且利用 sriov-network-device-plugin 或 RDMA 增强 plugin,每次 Allocate 会为 Pod 分配独占的硬件级队列和网卡指针。这提供了与虚拟机一致的安全性和质量保证(QoS),目前无论是阿里云、腾讯云等公有云还是各厂私有超算几乎全部采用这条技术栈。

5. 总结:AI Infra 皇冠上的明珠

在这场愈演愈烈的 AI 算力军备竞赛中,算力的载体不只是高耸入云的 NVIDIA H100 或者是计算集群。由数十个高速交换层搭建成的无阻塞网络拓扑,才是串联起这张人类智慧网络的神经系统。

RDMA Device Plugin 以及其背后的 SR-IOV / Multus 生态体系,便是连接高维玄幻的算法世界(PyTorch, Megatron)与冰冷深邃的物理网络协议栈(InfiniBand 交换机)之间的唯一桥梁。

掌握 RDMA 原理和 Kubernetes 异构设备扩展能力,不再是网络或者运维部门的专职,它目前已成为任何一位立志深耕大规模分布式深度学习平台的 AI 系统工程师不可跨越的必修课。借助 Kubernetes 开放的 CNI 和设备发现抽象,我们在云原生的浩瀚宇宙中,终于能够用代码优雅、高效、安全地编排属于我们自己的算力巨兽。