Yiwei Yang et al. (UC Santa Cruz, U Washington, UC Berkeley) | 2026-04 | https://arxiv.org/abs/2604.17861 Category: kernel | Tags: GPU-runtime, persistent-kernel, kernel-launch-overhead, JIT-compilation, operator-injection, PyTorch, NVRTC
GPU 上的微操作(element-wise、小 reduction、KV cache 更新等)在 micro-batch inference 中产生的 kernel launch 开销(每次 3–7 μs)可超过计算本身数量级。GPUOS 提出一套 persistent kernel + runtime operator injection 原语:进程启动时发射一个永不退出的持久 kernel(每 SM 1 个 block),通过设备可见的 lock-free ring buffer 接收 task descriptor,经 device function pointer table 分发执行——将 per-operation 调度开销从 μs 级 host 提交压缩到 ns 级 device 函数调用。新 operator 可通过 NVRTC JIT 编译为 PTX、经 CUDA Driver API 加载、写入 dual-slot aliasing 的 operator table inactive slot 后由 version counter store-release 原子切换,实现零停机热更新。系统以 PyTorch TorchDispatch 透明集成,在 H100 上实现 element-wise $15.3\times$、attention decoding $8.7\times$、mixed pipeline $23.1\times$ 加速及 20–22% 能耗节省,仅 3793 行 C++/Python。

Figure 1: GPUOS 三组件架构:host 侧通过 ring buffer 写入 task descriptor,persistent kernel executor(每 SM 1 block)以 warp 为粒度 poll-claim-dispatch,dynamic operator table 保存 device function pointer 数组并支持 runtime 更新。
现代推理工作负载已从大 batch 训练转向 latency-sensitive 的 micro-batch serving:单次 token 生成可能触发上百个 μs 级小 kernel,每个 kernel 的 launch 开销(driver 调用、user-kernel 态切换、stream queue 更新、硬件 scheduler 编程)在 3–7 μs,100 次累计 300–700 μs——常超过实际计算时间。CUDA Graphs 在 shape 稳定时有效($6.2\times$),但动态 workload(变长 prompt、条件分支、runtime 新 operator)下退化至 $2.1\times$,且 graph variant 管理本身引入复杂度和延迟。问题根源不在单次 launch 的绝对代价,而在 host-device 边界被高频穿越。
GPUOS 的核心思路朴素而直接——"don't launch, call":将调度从 host CPU 迁移到 device 侧。persistent kernel 在进程生命周期内仅 launch 一次,此后所有小操作以 device function call(ns 级)取代 kernel launch(μs 级)。Ring buffer 使用 store-release / load-acquire 语义同步,descriptor 仅 64–128 bytes(op_id + tensor pointers + dims + flags)。动态 operator injection 通过 NVRTC → PTX → CUDA Driver API → inactive slot → version counter flip 实现,全过程 ms 级完成且不中断 in-flight tasks。PyTorch TorchDispatch 层根据 operation type、tensor size、execution context、ring buffer load 四维 filter 决定是否路由到 GPUOS,保证大 GEMM 仍走传统 launch 路径。
GPU kernel launch 的固有开销:host 侧 cudaLaunchKernel 经 runtime → driver API → kernel mode → stream queue → hardware scheduler,即使 null kernel 也需 3–7 μs。当操作本身仅需 3–10 μs compute 时,launch 开销占 30–100%。
量化场景(论文 §2.1 production case study):
CUDA Graphs 的局限:
根因:传统 GPU computing model 将每个操作建模为一次独立的 host → device 提交。当操作粒度从 ms 级缩小到 μs 级,host-device 边界的穿越频率从"可忽略的固定成本"变为"主导性瓶颈"。
架构三组件(Figure 1):
1. Ring Buffer Communication Channel
Lock-free 单生产者-单消费者环形缓冲区,位于 device-mapped memory:
Host: prepare descriptor → acquire slot → write → commit (store-release)
Device: poll (load-acquire) → claim atomically → dispatch → release slot
{op_id, input_a, input_b, output, size, flags}2. Persistent Kernel Executor
operator_tabledesc.op_id dispatch → 回到 polling3. Dynamic Operator Table
Device-resident function pointer 数组,indexed by operator ID。Runtime 更新协议:
(1) NVRTC compile new operator → PTX
(2) CUDA Driver API load PTX module
(3) Resolve function symbol → device function pointer
(4) Write to inactive slot in operator table
(5) Atomic flip version counter (store-release)
→ All device threads observe version change at polling loop → switch to updated table
PyTorch 集成:
TorchDispatch hook 在 autograd engine 层拦截操作,四维 filter 决定路由:
不符合条件的操作(如大 GEMM)仍走 PyTorch 正常路径——hybrid execution。
Operator 库覆盖:

Table 2: End-to-end speedup vs. eager PyTorch baseline,跨三个硬件平台。
| Workload | H100 (96GB) | RTX 5090 (32GB) | GB10 (128GB) | Energy Saving |
|---|---|---|---|---|
| Element-wise ops | $15.3\times$ | $11.3\times$ | $2.8\times$ | 22% |
| Attention decoding | $8.7\times$ | $2.5\times$ | $2.1\times$ | 21% |
| Mixed pipeline | $23.1\times$ | $15.3\times$ | $3.1\times$ | 20% |
延迟分解(H100):
| 指标 | Baseline (Eager) | GPUOS | 改善 |
|---|---|---|---|
| Per-op latency (element-wise) | ~8 μs (5 μs launch + 3 μs compute) | ~3.1 μs (100 ns dispatch + 3 μs compute) | $2.6\times$ per-op → $15.3\times$ aggregate |
| Per-token latency (attention) | ~140 μs | ~16 μs | $8.7\times$ |
vs CUDA Graphs(§6.3):
| Scenario | CUDA Graphs | GPUOS |
|---|---|---|
| Element-wise (stable shapes) | $6.2\times$ | $15.3\times$ |
| Element-wise (variable shapes) | $2.1\times$ (recapture) | $15.3\times$ (consistent) |
| Attention decoding (dynamic seq) | $4.3\times$ (graph variants) | $8.7\times$ (no management) |
吞吐与可扩展性(§6.4):
MPS / MIG 兼容性:
能耗:20–22% 节省,来源不是降压降频,而是消除 launch-idle-launch 循环的 idle power waste。
2026 年,GPU inference 工作负载已从训练时代的大 batch、规则 shape、可预测 execution pattern 全面转向 latency-sensitive 的 micro-batch serving。Token-by-token decoding 中每个 token 需要上百个 μs 级小 kernel(attention QKV、softmax、activation、LayerNorm、KV cache update),而 GPU 的 kernel launch 机制仍然是为 ms 级大 kernel 设计的——每次穿越 user space → kernel mode → driver → hardware scheduler 的固定开销在 3–7 μs。这是一个 workload 粒度演化(ms → μs)与 system interface 粒度(ms 级 launch)之间的结构性错配。
三股趋势使这一错配变得不可忽视:(1) LLM 推理中 decode phase 的操作粒度极细——每 token 一次 forward 仅涉及少量数据但大量操作;(2) 模型和 serving 系统持续演化——新 operator(custom attention variant、novel activation)频繁引入,使 CUDA Graphs 的 static capture 模型难以维护;(3) 多租户 cloud serving 要求每个 inference session 的 tail latency 可控——launch overhead 的不确定性直接影响 P99。
为什么 CUDA Graphs 不够? Graphs 将 DAG capture-replay 替代逐次 launch,但要求 execution pattern 稳定。生产 inference 中 prompt 长度变化、control flow 分支、streaming input、runtime 新 operator 使 graph 频繁失效。维护多个 graph variant + fallback to eager 的混合策略引入管理复杂度,且 fallback 路径"悄悄"恢复了原始开销。核心约束:graphs 要求的可预测性与 production ML 的动态性根本矛盾。
为什么 Dynamic Parallelism 不够? CDP 将 launch initiator 从 host 搬到 device,但 launch cost 本身未消除——只是由 device 而非 host 支付。且引入同步和资源管理的额外复杂度。
为什么 torch.compile / TVM 不够? 编译器优化在 graph 可提取时有效,但对 input-dependent control flow、dynamic shapes、operator polymorphism 的工作负载效果递减。编译器在 graph level 工作,graph fusion 之后仍有大量小操作无法合并。
核心约束:所有现有方案的共同假设是——每个操作对应一次 host→device 提交。当操作粒度小到 μs 级,这个假设本身成为瓶颈。需要的不是"优化每次 launch",而是"消除 launch 的概念"。
将调度从 host 迁移到 device——persistent kernel + device-side function dispatch。
为什么可行?因为 persistent kernel 的 resource footprint 极小(每 SM 1 block ≈ 2–4% threads),不与大 kernel 争资源——它"填谷"而非"占满"。大 GEMM 仍走传统路径,micro-ops 走 GPUOS——hybrid execution 取每种路径各自的最优。
| 维度 | Eager PyTorch | CUDA Graphs | torch.compile | CDP (Dynamic Parallelism) | LithOS (SOSP'25) | GPUOS |
|---|---|---|---|---|---|---|
| Launch model | 每 op 一次 host launch | DAG capture → single replay | 编译时 fusion → reduced launches | Device-initiated launch | Device-side task queue | Persistent kernel + device function call |
| Per-op overhead | 3–7 μs | amortized(graph 内) | reduced(fusion 后) | 仍有 launch cost(device 侧) | device dispatch(ns 级) | <100 ns |
| Dynamic workload | 原生支持 | 退化(recapture / fallback) | partial(input-dependent 退化) | 支持 | 支持 | 原生支持 |
| Runtime operator update | 需重编译/重启 | 需 recapture | 需 recompile | 需 recompile | 未明确 | 零停机热更新 (ms 级) |
| Resource footprint | N/A | 额外 graph memory | 编译开销 | runtime stack overhead | unknown | 每 SM 1 block (2–4% threads) |
| PyTorch integration | 原生 | 需 graph capture 逻辑 | torch.compile() API | 需自定义 kernel | 需系统级集成 | TorchDispatch 透明 |
| Shape flexibility | 完全灵活 | 需 per-shape graph | 需 dynamic shape 支持 | 灵活 | 灵活 | 完全灵活 |
| Element-wise speedup | 1× | $6.2\times$ (stable) / $2.1\times$ (variable) | moderate | N/A | N/A | $15.3\times$ (consistent) |
vs Fleet (2604.15379) — persistent megakernel approach
Fleet 采用 persistent megakernel 思路将多个 operator 合并为一个长驻 kernel 执行,与 GPUOS 共享 "launch once, dispatch many" 的基本理念。关键差异在于可扩展性模型:Fleet 的 megakernel 在编译时确定 operator 集合,添加新 operator 需要重编译和重启;GPUOS 通过 NVRTC JIT + dual-slot aliasing 实现 runtime 动态注入。在 production ML 系统中,模型演化和 operator 实验的频率使得 static megakernel 的维护成本远高于动态注入方案。但 Fleet 的静态编译可能获得更好的编译器优化(如跨 operator 寄存器分配),而 GPUOS 的 JIT 编译限于 per-operator 粒度的模板实例化。
vs ConCCL (2412.14335) — GPU DMA overlap
ConCCL 关注 GPU-initiated DMA 与 compute 的 overlap,属于通信优化范畴。GPUOS 关注 CPU-GPU launch boundary 的消除,属于调度优化范畴。两者正交且互补:ConCCL 解决的是"计算与通信重叠",GPUOS 解决的是"消除计算之间的协调间隙"。在 multi-GPU serving 中,两者可组合使用——GPUOS 消除单 GPU 内的 launch overhead,ConCCL 隐藏 GPU 间的通信延迟。但 GPUOS 目前未提供 multi-GPU 支持,实际组合需要额外工程。
vs CUDA Graphs — the primary comparison target
论文的核心对比对象。CUDA Graphs 在 shape 稳定时高效($6.2\times$),但其 "capture-replay" 模型要求 execution pattern 的可预测性——这与 production inference 的动态性矛盾。GPUOS 的优势在于:(1) shape 变化时性能不退化($15.3\times$ vs $2.1\times$),(2) 不需要 graph variant 管理,(3) 支持 runtime operator 更新。但 CUDA Graphs 的优势在于:(1) 更简单的部署模型(无 persistent kernel 管理),(2) NVIDIA 官方支持和持续优化(constant-time launch in CUDA 12.3),(3) 对规则 workload 可能有更好的 end-to-end 性能(graph-internal 优化如 kernel fusion)。在实践中,两者应共存——graphs 覆盖规则段,GPUOS 覆盖动态段。
vs LithOS — GPU OS work (SOSP 2025)
LithOS 是最直接的竞争者——同样提出 device-side task scheduling 用于 ML workload 的 GPU OS 层。LithOS 发表于 SOSP 2025,具有更强的 systems 背景和 OS-level 抽象。GPUOS 的差异化在于:(1) NVRTC-based dynamic operator injection(LithOS 未明确提供等价能力),(2) 轻量 PyTorch 集成(3793 行 plugin vs OS-level 重构),(3) production 导向的安全和可观测性设计。但论文的关键缺失是未提供与 LithOS 的直接性能对比——在同类 device-side scheduling 系统中,不与最接近的 prior art 做 head-to-head 对比显著削弱了论文的说服力。
| 数值 | 含义 | 来源 |
|---|---|---|
| 3–7 μs | Null kernel launch overhead(传统 cudaLaunchKernel) | §3.1, NVIDIA Developer Forums 2023 |
| <100 ns | GPUOS per-operation submission latency | §4.2 设计分析 |
| 30–70× | Submission latency reduction (3–7 μs → <100 ns) | §4.2 |
| $15.3\times$ | Element-wise speedup (H100) | Table 2, §6.2 |
| $11.3\times$ | Element-wise speedup (RTX 5090) | Table 2, §6.2 |
| $2.8\times$ | Element-wise speedup (GB10) | Table 2, §6.2 |
| $8.7\times$ | Attention decoding speedup (H100) | Table 2, §6.2 |
| $23.1\times$ | Mixed pipeline speedup (H100) | Table 2, §6.2 |
| $15.3\times$ | Mixed pipeline speedup (RTX 5090) | Table 2, §6.2 |
| ~8 μs → ~3.1 μs | Per-operation latency reduction (element-wise, H100) | §6.2 para 2 |
| ~140 μs → ~16 μs | Per-token latency reduction (attention, H100) | §6.2 para 3 |
| $6.2\times$ | CUDA Graphs speedup (stable shapes) | §6.3 |
| $2.1\times$ | CUDA Graphs speedup (variable shapes) | §6.3 |
| $4.3\times$ | CUDA Graphs speedup (attention decoding) | §6.3 |
| 800K ops/sec | GPUOS throughput (GB10) | §6.4 |
| 67K ops/sec | Eager execution throughput (GB10) | §6.4 |
| $12\times$ | Throughput improvement (800K / 67K) | §6.4 |
| $3.4\times$ | MIG partition speedup | §6.2, Figure 4 |
| 20–22% | Energy savings | Table 2, §6.2 |
| 2–4% | Persistent kernel resource footprint (% of total threads) | §7.1 |
| 64–128 bytes | Task descriptor size | §4.1 |
| 4096 entries | Default ring buffer capacity | §6.4 |
| >64 threads | Ring buffer contention threshold | §6.4 |
| 3793 | Lines of code (C++ + Python) | §5 |
| 100 ops / token | Typical micro-operations per token (production case) | §2.1, §3.1 |
| 500 μs | Cumulative launch overhead per token (100 × 5 μs) | §2.1 |