语言模型能在 Linux eBPF 里运行吗?
Linux eBPF 真能计算一个语言模型吗?用 Qwen3-0.6B 的实测结果,解释哪些工作在内核、速度如何,以及 INT4 与 arena 还有哪些取舍。
我们通常用 eBPF 观察或影响别的程序:跟踪系统调用、过滤网络包,或者调整调度策略。qwen3-ebpf 提了一个不同的问题:通过内核验证的 BPF 程序,能否亲自完成真实语言模型的数值计算,而不只是观察模型服务?
这个实验让 Qwen3-0.6B 的 28 层 decoder 在 Linux eBPF 中运行,计算 151,936 个候选 token 的分数,并在内核里选出下一个 token。后续生成还会复用 KV cache。它实现了完整前向计算,但还不能替代成熟的 CPU 或 GPU 推理引擎:测试主机上生成一个 token 约需 1.2 秒,模型加载、文本处理和 BPF 算子调度仍由 C 程序负责。
先看一个 token 怎样生成
语言模型并不直接接收一段文本。分词器先把文本转换成整数 ID;模型查出对应的向量,让它逐层经过 decoder,最后为每个可能的下一个 ID 算一个分数,也就是 logit。选最高分得到 token ID,再把 ID 解码成人能读的文本。
本项目把这条路径分成两部分:
flowchart TD
A["C:文本分词、加载 BF16 权重"] --> B["C:取得 embedding、调度 BPF 程序"]
B --> C
subgraph L["一层 eBPF decoder,由 C 重复调度 28 次"]
C["RMSNorm · qwen3_norm"] --> D["Q/K/V 矩阵 · qwen3_batch"]
D --> E["Q/K 归一化与 RoPE · qwen3_norm + qwen3_rope"]
E --> F["因果注意力 · qwen3_attention ↔ KV map"]
F --> G["输出投影与残差 · qwen3_batch + qwen3_vector"]
G --> H["MLP · qwen3_norm + qwen3_batch + qwen3_silu + qwen3_vector"]
end
H -- "下一层" --> C
H -- "完成第 28 层" --> I["最终归一化、词表投影与 argmax · qwen3_norm + qwen3_batch"]
I --> J["C:将 token ID 解码成文本"]
图中每个方框是有界算子,并非一个巨大的 BPF 程序。C 按顺序调用它们; 注意力程序负责读写 BPF KV map。矩阵程序每次调用最多处理 128 行, 因此一个方框仍可能对应多次进入内核。
C 驱动通过 bpf_prog_test_run_opts 调用 socket-filter BPF 程序。这里使用的是测试运行入口;程序没有挂到网卡上,也不是常驻的模型服务。矩阵向量乘、RMSNorm、SiLU、向量运算、RoPE 旋转、因果注意力和最终 argmax 都在 BPF 中执行。C 提供 embedding、权重和 RoPE 所需的三角函数值,再按模型顺序调用算子。较长的提示词要逐个位置经过 28 层;生成新 token 时,由 BPF 写入的历史 K/V 向量可以被注意力重新使用。
所以这里说的“内核内推理”,具体指前向算术和选 token 在通过验证的 BPF 程序中完成,并不是把文本送进内核,一次调用就得到整句话。
为什么拆成这么多次调用?
eBPF 程序必须先通过 Linux verifier。它会检查执行路径和内存访问,因此计算需要有明确界限,状态也要通过 BPF map 传递。原模型采用 BF16 浮点数;这套 BPF 算子改用定点整数:激活值为 Q16,矩阵权重为 Q24,归一化系数为 Q20。SiLU 和注意力 softmax 使用整数近似。RoPE 的三角函数值由 C 计算,向量旋转由 BPF 完成。
减少跨越用户态和内核态次数的关键是 bpf_loop。一次矩阵调用最多处理 128 行;其他调用可以覆盖全部 24 个 Q/K 归一化头、一个有界历史片段中的全部 16 个 query 头,或最多 256 个历史位置。新的 K/V 对由注意力 BPF 程序写进 KV map。这些改动减少了反复调用,却没有把 28 层假装成一个巨大的 BPF 程序。现在一次单 token 推理约有 4,169 次 bpf 调用,早期较小批次的实现则有数万次;调用数是实现层面的测量,不等于性能结论。
两次失败说明了实际边界。早期把 RMSNorm 写成一个复杂程序,在测试内核上超过 verifier 的指令处理预算;改为有界回调后才成功加载。另一版试图在 3,072 列的 BPF 循环中直接把 BF16 转成 Q24,遇到 verifier 复杂度错误。这些是 Linux 6.17 arm64 上具体实现的结果,并不证明更大的 BPF 设计一概不可行。
权重究竟放在哪里?
官方模型是约 1.5 GB 的 BF16 Safetensors 文件,由 C 以只读方式映射。默认推理路径把当前需要的矩阵行转换成 Q24,再复制到可映射的 BPF 工作 map。我们试过预先把所有矩阵转换为四字节 Q24:文件增至约 3.0 GB,在当时可用内存约 4.6 GiB 的主机上,探索性测试反而更慢。扩大权重体积没有解决瓶颈。
现在还有一条可选的完整推理路径使用 BPF arena。C 每次只把当前 128 行 BF16 权重复制到约 2 MiB 的 arena;arena 内的 65,536 项查表,把 BF16 位模式映射为默认路径使用的同一个 Q24 整数,然后由 BPF 读取权重并计算点积。这样,当前批次的权重转换移到了 BPF,但并未强行让整个模型常驻内核可访问内存。输入 token ID 0,以及对 Hello, world! 连续生成两个 token 时,两条路径给出相同 ID,最终全词表 logits 也逐字节一致。独立算子测试还核对了 128 行真实 Q 投影权重。
arena 并没有显示稳定提速。六次交错的单 token 测试中,默认路径分别用时 1.227/1.190/1.251 秒,arena 路径为 1.220/1.228/1.234 秒。计时从模型和 BPF 初始化之后开始;同一主机上的这几个样本不足以证明吞吐或尾延迟优势。arena 改变了活跃权重的访问方式,既不会自动压缩权重,也不会消除其余算子调用。
换成四位权重会更好吗?
INT4 的吸引力很直观:一个字节能存两个权重,而一个 BF16 权重需要两个字节。测试过的一版分组缩放 INT4,把本模型使用的矩阵连同缩放系数打包到 316,616,704 字节;BPF 算子通过了 verifier 和真实权重行测试。在一次单 token 比较中,排除预打包的 1.928 秒后,INT4 前向计算用时 0.956 秒,Q24 为 1.243 秒。这说明它值得研究,但输出先出了问题:输入 ID 0 时,默认路径选择 token 9,INT4 版本选择 284,全词表 logit 平均绝对误差为 1.837。其他测试提示词也改变了下一个 token。缩小量化分组,或单独保留高精度的词表投影,都没有修复这个例子。
因此 INT4 还不是默认模型。经过校准、对敏感层保留更高精度的方案也许能用,但必须跨多种提示词核对完整模型的 logits 和生成 token,并同时测量内存、预处理开销与延迟分布。文件变小,不能自动证明模型仍给出预期结果。
哪些部分还在用户态?
准确分词需要读取大型 tokenizer.json、用正则切分文本、执行字节级 BPE,并在输出时还原字节。C 负责这些文本与输入输出工作。把它们搬进 BPF,需要另写一套可验证的有界实现,却不会减少最昂贵的矩阵计算。C 还负责决定下一步运行哪个算子、提供下一批矩阵权重。按照现在只让活跃权重批次进入 BPF 的设计,单个长时间运行的内核调用无法自行穿过所有模型层。
KV cache 也有明确的内存边界:当前布局每个位置预留约 224 KiB;若按配置上限一次分配 40,960 个位置,仅 KV 就需约 8.75 GiB。注意力可以分段扫描历史,但任意长上下文下的内存使用和生成质量尚未验证。
现有证据支持一个具体而有限的结论:在测试内核上,通过 verifier 的 BPF 程序确实运行了全部 28 层,并为真实模型选出了 token。Hello, world! 首次生成的 ID 是 1096,解码为 This;最高的五个词表 ID 与官方 BF16 参考结果一致。不过参考比较只覆盖少量输入,定点计算的全词表误差也不是零。接下来真正要回答的是:更合适的权重格式、更少的调用边界和可控的内存占用,能否让这套实现适用于某种确实需要在内核里运行的负载。源码、运行方法和测量记录 都可以供下一轮验证使用。
继续阅读
返回索引
Blog
Technical articles on eBPF, bpftime, AI agent observability, GPU tracing, userspace runtimes, and systems research from Eunomia.
上一篇 / 上一页
When Does CPU Scheduling Help AI Inference?
CPU scheduling can become part of the AI inference critical path when host-side vLLM, GPU driver, IRQ, or NCCL work competes for CPU time. Experiments with Gthulhu, schedext, and DGX Spark show when it helps and when it does not.
下一篇 / 下一页
为什么 eBPF verifier 报错难修:诊断鸿沟
对 235 个可复现 eBPF verifier 拒绝案例的研究表明,最后一条报错只标出验证停止的位置,根因和修复层次藏在此前的证明生命周期里。bpfix 从 verifier 日志重建这段生命周期,弥合诊断鸿沟。
- 最后更新
- 2026年9月26日
- 首次发布
- 2026年9月24日
- 贡献者
- Littlefisher619
这个页面有帮助吗?