CUDA Event:GPU 时间线上的事件、同步原理与实战

CUDA kernel launch 通常是异步的:CPU 把 kernel、内存拷贝等操作提交到 stream 后便继续执行。由此产生两个常见问题:如何准确测量 GPU 工作耗时,以及如何让不同 stream 在不阻塞 CPU 的情况下建立依赖? CUDA Event 就是解决这类问题的基础设施。它可以理解为插入 GPU stream 时间线中的一个标记:当 event 之前的工作全部完成,event 才进入完成状态。借助这个状态,我们可以做 GPU 计时、CPU 等待、跨 stream 同步和异步资源回收。 1. Event 不是 CPU 事件,而是 GPU 时间线标记 一个 CUDA stream 是按序执行的 GPU 工作队列: CPU 提交: Kernel A → memcpy → Event E → Kernel B 异步提交 GPU 执行: Kernel A ── memcpy ── E 完成 ── Kernel B 调用 cudaEventRecord(event, stream) 时,CPU 通常只是向 stream 提交一个 event 记录操作,并不会等待 GPU 执行到该位置。只有当这个 stream 中排在 event 前面的操作完成后,event 才会被标记为完成。 ...

2026年8月31日 · 6 分钟 · Hellokitty

SGLang 双 Stream 重叠调度:如何把 CPU 后处理藏到 GPU 计算背后

大模型自回归推理看起来是纯 GPU 工作:每一步做一次模型前向,采样一个 token,再把 token 喂给下一步。然而在高性能推理系统里,GPU 计算只是流水线的一部分。采样结果回传、EOS 判断、请求回收、KV Cache 管理、下一批组装和元数据准备,都可能在两次前向之间制造空洞。 SGLang 的 Overlap Scheduling 解决的正是这个问题。它不是简单地“多开一条 CUDA stream”,而是同时完成两件事: 让当前步产生的 token 留在 GPU,直接作为下一步输入,解除下一次 forward 对 CPU 读值的依赖; 用 scheduler stream 和 engine stream 构造软件流水线,让 GPU 计算当前批次时,调度器处理上一批次的结果。 本文以 Mini-SGLang 的 python/minisgl/scheduler/scheduler.py 为线索,解释 overlap_loop、_forward 和 _process_last_data 背后的依赖重排。 先看结论:优化的不是“提交速度”,而是依赖关系 CUDA kernel 的提交本来就是异步的。CPU 把工作放进 stream 后,不必等待 GPU 完成。因此一个自然的问题是:只用一条 stream,只要 CPU 提交得足够快,不也能让 GPU 连续工作吗? 如果系统只有计算任务,这个判断基本成立。但自回归推理存在一个控制依赖:调度器通常要知道采样出的 token,才能判断请求是否命中 EOS、是否应该释放资源,以及下一轮有哪些请求仍应留在 batch 中。 串行执行会形成如下链条: GPU: forward(B1) ── idle ── forward(B2) ── idle ── forward(B3) CPU: 处理 B1 处理 B2 读 token 读 token 判 EOS 判 EOS 组 B2 组 B3 GPU 完成 B1 后,CPU 才读取结果并决定 B2。此时 GPU 没有可执行的新工作,只能等待。这不是 CPU 调用 CUDA API 太慢,而是 B2 在逻辑上依赖 B1 的结果。 ...

2026年8月30日 · 4 分钟 · Hellokitty

从朴素循环到硬件极限:GEMM / GEMV 高性能算子优化指南

GEMM 与 GEMV 看起来都只是乘加: GEMM: C = alpha * A * B + beta * C GEMV: y = alpha * A * x + beta * y 但二者的最佳实现完全不同。GEMM 可以反复复用矩阵块,通常有机会逼近计算峰值;GEMV 中矩阵元素通常只读一次,往往受内存带宽限制。高性能算子的第一步不是写 SIMD 或 CUDA,而是先判断瓶颈究竟在哪里。 本文给出一条从正确基线走向高性能内核的完整路线。重点不是某段固定代码,而是每一步为什么有效、如何验证,以及何时应该停止优化。 一、先建立性能上限 1. 计算量 对于矩阵尺寸: A: M × K B: K × N C: M × N GEMM 约执行: FLOPs = 2 * M * N * K GEMV 是 N = 1 的特殊形态: FLOPs = 2 * M * K 乘法和加法各算一次浮点操作。 ...

2026年8月26日 · 6 分钟 · Hellokitty

Phoenix 源码解读:把 GPU 显存变成 Linux I/O 可直达的内存

深入解析 xPU-IO/Phoenix:它如何通过内核模块、用户态库和应用适配器,将 SSD 数据直接送入 GPU 显存;并比较 full BAR、staging、batch、stream 等数据路径的实现与取舍。

2026年8月24日 · 7 分钟 · Hellokitty

uGDS 原理解析:在用户态打通 NVMe SSD 与 GPU 显存

大模型推理正在把存储重新推到系统性能的核心位置。模型权重、KV Cache、Embedding 和训练检查点都可能在 SSD、主存与 GPU 显存之间频繁搬运。传统路径通常要经过文件系统、内核块层、页缓存或用户态中转缓冲区,不但增加 CPU 开销,也让小块 I/O 延迟变得难以控制。 uGDS 是 ScaleX-IO 开源的一套用户态 GPU Direct Storage 开发库。它的核心思路很直接: 让 CPU 在用户态构造 NVMe 命令并管理提交队列与完成队列,由 SSD 通过 PCIe P2P DMA 直接读写 GPU 显存,从数据路径中绕开内核 NVMe 驱动和文件系统。 本文结合 uGDS 当前源码,解释它为什么快、一次 I/O 如何执行、内核模块与用户态库如何分工,以及使用这类“绕过内核”的存储栈时必须承担哪些工程责任。 一、uGDS 想解决什么问题 先看一条常见的数据读取路径: NVMe SSD │ ▼ 内核 NVMe 驱动 → 块层 → 文件系统 │ ▼ 主机内存缓冲区 │ ▼ GPU 显存 即使系统支持 Direct I/O,应用仍然通常要进入内核,由内核驱动准备 NVMe 命令、完成 DMA 映射并处理中断或轮询。若数据还要经过主机内存,再复制到 GPU,路径会更长。 ...

2026年8月20日 · 5 分钟 · Hellokitty

NVIDIA GPU-Initiated Data Storage(GIDS)详解:让 GPU Kernel 主动访问存储

GPUDirect Storage(GDS)已经能够让存储数据绕过 CPU 内存,直接进入 GPU 显存。但经典 GDS 仍有一个重要特征:I/O 请求由 CPU 侧应用代码提交。 当 GPU kernel 在执行过程中才知道下一份数据位于哪里时,控制权必须在 GPU 与 CPU 之间往返。对于图计算、向量检索、稀疏模型和超大规模数据分析,这种细粒度同步可能比数据传输本身更昂贵。 NVIDIA GPU-Initiated Data Storage,简称 GIDS,试图进一步消除这层控制瓶颈:让 GPU kernel 直接发起存储 I/O,使 GPU 不仅是数据的接收者,也是 I/O 请求的生产者。 本文介绍 GIDS 的动机、架构、工作流程、适用场景,以及它和 GDS 的本质区别。 一、为什么有了 GDS 还需要 GIDS GDS 优化了数据路径: CPU 发起请求 │ ▼ 存储 ─────────────► GPU 显存 数据不经过 CPU 内存 这已经消除了大量主机内存中转,但控制路径仍然经过 CPU。经典流程可能是: GPU 执行 kernel; GPU 产生下一批要访问的数据索引; kernel 结束或与 CPU 同步; CPU 读取结果并构造文件 I/O; CPU 调用 cuFile; 数据进入 GPU; CPU 再次启动 kernel。 如果每次请求都是大块、批量且可提前预测,这种方式通常没有问题。但如果 GPU 在计算过程中产生数以万计甚至更多的动态访问,频繁的 GPU—CPU 往返会带来: ...

2026年8月19日 · 4 分钟 · Hellokitty

NVIDIA GPUDirect Storage(GDS)详解:让存储数据绕过 CPU 直达 GPU

在 AI 训练、科学计算和数据分析系统中,GPU 的算力越来越强,但数据从存储设备进入 GPU 的路径却可能成为瓶颈。传统 I/O 通常要先把数据读入 CPU 内存,再复制到 GPU 显存,不仅增加内存带宽消耗,还让 CPU 承担大量数据搬运工作。 NVIDIA GPUDirect Storage,简称 GDS,解决的正是这个问题:它在存储设备与 GPU 显存之间建立更直接的数据路径,使应用能够通过 cuFile API 将文件数据读入 GPU 缓冲区,减少 CPU bounce buffer 和不必要的数据复制。 本文介绍 GDS 的工作原理、软件栈、典型使用方式、适用场景以及它与 GPU-Initiated Data Storage(GIDS)的关系。 一、传统 GPU I/O 为什么效率不高 传统文件读取到 GPU 的路径大致如下: NVMe / 文件系统 │ ▼ CPU 内存缓冲区 │ cudaMemcpy ▼ GPU 显存 应用通常先调用 read、pread 或异步 I/O 接口,把文件内容读入主机内存,然后再调用 CUDA memcpy 将数据复制到 GPU。 这条路径存在几个问题: 同一份数据先经过 CPU 内存,再进入 GPU 显存; 存储流量和 GPU 传输流量竞争 CPU 内存带宽; CPU 需要提交、管理和完成数据搬运; 大规模 GPU 系统中,CPU 和内存通道容易成为共享瓶颈; 应用需要维护主机端 staging buffer,并处理双重缓冲。 当 GPU 计算越来越快、单机挂载更多 NVMe 或更高速的并行文件系统后,这些额外开销会更加明显。 ...

2026年8月19日 · 3 分钟 · Hellokitty