eBPF 2026:从云原生可观测性到 AI 推理基础设施层
大多数人接触 eBPF 是因为 **云原生可观测性**:用它来抓包、追踪系统调用、分析网络流量。这套叙事在 2019–2024 年非常流行,工具链(Cilium、Tetragon、Pixie)基本都建立在这个框架上。
大多数人接触 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_ops 或 raw_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 基础设施中的角色会更加不可替代。