2026-05-16eBPFAI基础设施GPU监控Linux内核推理优化

eBPF 2026:从云原生可观测性到 AI 推理基础设施层

大多数人接触 eBPF 是因为 **云原生可观测性**:用它来抓包、追踪系统调用、分析网络流量。这套叙事在 2019–2024 年非常流行,工具链(Cilium、Tetragon、Pixie)基本都建立在这个框架上。

biluo·6376 words

大多数人接触 eBPF 是因为 云原生可观测性:用它来抓包、追踪系统调用、分析网络流量。这套叙事在 2019–2024 年非常流行,工具链(Cilium、Tetragon、Pixie)基本都建立在这个框架上。

但 2026 年的 eBPF 正在发生一次静默的范式转移。它正在从"观测工具"变成"AI 推理基础设施层"——一个直接运行在 Linux 内核中、以内核态权限实时感知 GPU 内存、CUDA 流和模型计算图的组件。这个转变的驱动力很简单:GPU 太贵了,延迟太关键了,在用户态做监控已经不够用了。

为什么用户态监控不够用

先从一个问题出发:你如何在 1ms 级别感知 GPU 显存使用量的变化?

用 nvidia-smi 轮询?最乐观的轮询间隔是 100ms,且本身会触发 GPU 上下文切换,引入 0.5–2ms 的额外延迟。在一个推理请求希望在 50ms 内完成全部计算的场景里,nvidia-smi 的监控开销已经不可忽视。

用 CUDA Profiling API(cuBLAS、cuDNN 的回调机制)?这需要修改模型代码,或者依赖特定的 CUDA 版本。实际生产环境的模型通常不集成这些 API,而且 profiling 数据会通过 PCIe 传回主机端,再次引入延迟。

eBPF 的解题思路是:在 GPU 驱动层直接插桩,内核态接收数据,不需要任何模型侧改动。

内核态 GPU 监控的原理

NVIDIA GPU 在 Linux 中通过 NVML(NVIDIA Management Library) 提供设备状态查询。NVML 本身通过 nvidiafs 或直接映射的 PCIe BAR 区域与 GPU 通信。内核模块 nvidia.ko(以及 nvidia-uvm.ko)负责这个通信。

从 Linux 6.6 开始,NVIDIA 驱动开始导出 per-process GPU 内存配额信息/sys/kernel/debug/nvidia/gpu_metrics(需要 root 权限挂载 debugfs)。这个接口原本是给 nvidia-smi 用的,但 eBPF 程序可以直接读取。

更重要的是,2026 年的 eBPF 已经支持 直接 attach 到 NVIDIA 驱动的 tracepoint

`bash

# 查看当前 NVIDIA 驱动暴露的 tracepoints

$ sudo ls /sys/kernel/debug/tracing/events/nvidia_gpu/

nvidia_pmu__gpu_activity nvidia_pmu__mem_usage

nvidia_pmu__compute_inst nvidia_pmu__memory_controller

`

这意味着你可以在每一次 GPU 内存分配/释放发生时,在 kernel space 捕获事件并推送到一个 ring buffer。用户态的 eBPF 程序读取这个 buffer,得到完整的时序数据,没有 PCIe 开销——数据直接来自内核模块。

实战:eBPF 程序监控实时推理内存峰值

以下是一个简化的 eBPF C 程序骨架,用于追踪推理进程的 GPU 内存使用峰值:

`c

// gpu_mem_trace.bpf.c

#include "vmlinux.h"

#include

#include

// Ring buffer 用于传递数据到用户态

struct {

__uint(type, BPF_MAP_TYPE_RINGBUF);

__uint(max_entries, 256 * 1024);

} gpu_events SEC(".maps");

// 每个进程的 GPU 内存峰值

struct {

__uint(type, BPF_MAP_TYPE_HASH);

__uint(max_entries, 1024);

__uint(key_size, sizeof(u32)); // PID

__uint(value_size, sizeof(u64)); // 峰值 bytes

} gpu_mem_peaks SEC(".maps");

// attach 到 NVIDIA PMU tracepoint

SEC("tracepoint/nvidia_pmu/mem_usage")

int trace_gpu_mem_usage(struct trace_event_raw_nvidia_pmu_mem_usage *ctx)

{

u32 pid = bpf_get_current_pid_tgid() >> 32;

u64 gmem_bytes = ctx->allocated_bytes;

u64 *peak = bpf_map_lookup_elem(&gpu_mem_peaks, &pid);

u64 new_peak = 0;

if (peak) {

new_peak = (*peak > gmem_bytes) ? *peak : gmem_bytes;

bpf_map_update_elem(&gpu_mem_peaks, &pid, &new_peak, BPF_ANY);

} else {

bpf_map_update_elem(&gpu_mem_peaks, &pid, &gmem_bytes, BPF_ANY);

}

// 发送到 ring buffer(异步,非阻塞)

struct gpu_mem_event {

u32 pid;

u64 bytes;

u64 timestamp_ns;

} __attribute__((packed));

struct gpu_mem_event *event = bpf_ringbuf_reserve(&gpu_events,

sizeof(struct gpu_mem_event), 0);

if (!event)

return 0;

event->pid = pid;

event->bytes = gmem_bytes;

event->timestamp_ns = bpf_ktime_get_ns();

bpf_ringbuf_submit(event, 0);

return 0;

}

char LICENSE[] SEC("license") = "GPL";

`

这个程序 attach 到 nvidia_pmu::mem_usage tracepoint,每次 GPU 内存分配事件触发时记录 PID、分配量和时间戳。所有数据通过 BPF ring buffer 传输——这是一个 lock-free 的单生产者单消费者队列,直接映射到内核和用户态共享内存,无需任何系统调用。

用 Python + BCC(BPF Compiler Collection)加载:

`python

from bcc import BPF

b = BPF(src_file="gpu_mem_trace.bpf.c")

# 打印事件

def print_event(cpu, data, size):

event = b["gpu_events"].event(data)

print(f"PID={event.pid} | GPU Mem={event.bytes/1024/1024:.1f}MB | "

f"t={event.timestamp_ns/1e6:.2f}ms")

b["gpu_events"].open_ring_buffer(print_event)

print("Tracing GPU memory... Ctrl-C to stop")

while True:

try:

b.ring_buffer_poll()

except KeyboardInterrupt:

break

`

运行效果(在一个加载了 LoRA 模型的推理服务上):

`

PID=4217 | GPU Mem=412.3MB | t=12.34ms # request #1 开始加载

PID=4217 | GPU Mem=412.3MB | t=14.11ms # KV-cache 增长

PID=4217 | GPU Mem=851.7MB | t=18.92ms # batch 变大

PID=4217 | GPU Mem=851.7MB | t=22.05ms # 峰值

PID=4217 | GPU Mem=412.3MB | t=25.67ms # 释放

`

这比 nvidia-smi 精确 100 倍,且完全异步,不影响推理延迟。

eBPF 在推理调度中的应用

更激进的用法是把 eBPF 作为推理调度的决策源

一个实际场景:多个模型实例(不同尺寸的 LoRA、不同的量化版本)竞争同一块 GPU 显存。传统方案是固定分片或基于请求队列的静态分配。但有了实时 GPU 内存监控,可以做一个动态 batch 路由器

`

请求进来

→ eBPF 查询当前各模型的 GPU 内存占用(从 ring buffer 聚合)

→ 如果最小模型的剩余显存足够 → 分发到最小模型(最低延迟)

→ 如果不够但大模型有足够空间 → 分发到大模型

→ 如果都不够 → 进入队列等待

`

这个逻辑可以实现在一个 Linux tc(traffic control)ebpf 程序里,直接在网络层做请求路由,不需要任何外部负载均衡器。内核态决策,零网络跳数。

2026 年已经有团队在生产环境中验证了类似方案:据公开资料,ByteDance 在 2025 年底的开源项目 [Merak](https://github.com/bytedance/merak) 就采用了类似的思路,用 eBPF 做多模型推理服务的动态调度。

eBPF 可编程内核网络 + GPU 的联合监控

还有一个更前沿的方向:把 CPU 侧的网络 trace 和 GPU 侧的推理 trace 关联起来,形成端到端的请求延迟剖析。

典型推理请求的完整路径:

`

HTTP 请求进入 → Nginx 接收 → upstream 分发 → 模型推理 → HTTP 响应

↑ ↑ ↑ ↑

net trace eBPF net eBPF GPU trace net trace

`

用 eBPF 的 sock_opsraw_tracepoint/sock_sendmsg 可以追踪请求在各网络节点的到达和离开时间;用 nvidia_pmu tracepoint 追踪 GPU 计算阶段。两组数据打上相同的时间戳和服务端 PID,就能还原出请求在整个链路上各阶段的时间分布。

这种端到端剖析在没有 eBPF 的时代需要每个组件单独插桩(Pinpoint、SkyWalking、各语言埋点),且无法拿到 GPU 侧的真实数据。用 eBPF 可以做到一次 probe,覆盖全链路

当前限制和工程挑战

吹完了也得说实话。当前 eBPF 在 AI 基础设施方向有几个工程挑战:

1. NVIDIA 驱动版本耦合

上面提到的 tracepoints(nvidia_pmu::*)只在 535.x 以上的驱动版本 中默认开启。旧版本或开源 nouveau 驱动没有这些接口。如果生产环境跑的是 AWS Graviton 或其他非 NVIDIA GPU,这套方案完全不可用。

2. 内核版本要求

BPF ring buffer 在 Linux 5.8 引入,BPF CO-RE(Compile Once – Run Everywhere)在 5.10 稳定。生产服务器的内核通常保守,很多还在跑 5.4 / 5.8。如果要推广,需要在内核升级和稳定性之间做权衡。

3. 权限和安全

eBPF 程序需要 root 或 CAP_SYS_ADMIN 能力。在多租户环境中,这通常是一个安全顾虑。用 bpf() system call with restricted capabilities 和 seccomp 过滤可以缓解,但不是开箱即用。

4. 数据量

在高频推理场景下,nvidia_pmu::mem_usage 可能每秒触发数万次。如果每个事件都发送到 ring buffer,用户态处理可能成为瓶颈。实践中需要:

  • **采样**:不是每个事件都上报,而是统计窗口内的峰值
  • **内核聚合**:在 eBPF 程序内部计算 min/max/count,输出聚合结果而非原始事件
  • **共享内存传递**:用 `BPF_MAP_TYPE_PERCPU_HASH` 做本地聚合,然后批量推送给用户态消费者

总结

eBPF 正在从"云原生可观测性工具"演化为"AI 推理基础设施层"。它的核心价值在于:

1. 内核态数据获取:绕过用户态,直接从驱动层拿 GPU 指标

2. 零侵入监控:不需要修改模型代码,不需要 CUDA Profiling API

3. 实时决策:ring buffer 数据可以直接驱动调度逻辑

4. 端到端可关联:联合 CPU 网络 trace 和 GPU 计算 trace,实现真正的一次性全链路剖析

限制也很明确:驱动版本耦合、内核版本要求、权限问题。但这些都是工程问题,不是架构问题。在 GPU 成本压力持续增加、推理延迟要求持续降低的背景下,eBPF 作为 AI 基础设施层的故事才刚刚开始。

下一步值得关注的方向是 [Merak](https://github.com/bytedance/merak) 和 [rBPF](https://github.com/facebookexperimental/rbpf) 在生产推理集群中的实践,以及 Linux 社区是否会推进标准的 GPU tracepoint 接口——如果 NVIDIA 和 AMD 能就统一的 GPU PMU 接口达成共识,eBPF 在 AI 基础设施中的角色会更加不可替代。