GDS、GIDS 与 uGDS 有什么区别:从数据直达、GPU 发起到用户态 NVMe

GDS、GIDS 和 uGDS 的名字非常接近,也都在讨论 GPU 与存储之间的数据通路,因此很容易被理解成同一项技术的三个版本。 实际上,它们解决的是三个不同层面的问题: GDS 关注数据路径:怎样让存储数据不经 CPU 内存中转,直接进入 GPU 显存; GIDS 关注控制路径:怎样让 GPU Kernel 自己决定并发起存储请求,减少 GPU 与 CPU 往返; uGDS 关注软件栈:怎样让 CPU 在用户态直接管理 NVMe 队列,绕过内核 NVMe 驱动与文件系统。 可以先用一句话概括: GDS 是“数据直达 GPU”,GIDS 是“GPU 主动要数据”,uGDS 是“用户态 CPU 直接驱动 NVMe 把数据送到 GPU”。 本文从请求由谁发起、数据经过哪里、NVMe 命令由谁构造、是否保留文件系统语义等维度,分析三者的区别与联系。更深入的原理可以继续阅读本系列的三篇文章: NVIDIA GPUDirect Storage(GDS)详解:让存储数据绕过 CPU 直达 GPU NVIDIA GPU-Initiated Data Storage(GIDS)详解:让 GPU Kernel 主动访问存储 uGDS 原理解析:在用户态打通 NVMe SSD 与 GPU 显存 一、先区分数据路径与控制路径 理解这三项技术的关键,是不要把“数据经过哪里”和“谁发起 I/O”混为一谈。 数据路径 数据路径描述有效载荷如何移动。例如: 传统路径:SSD → CPU 内存 → GPU 显存 直接路径:SSD ──────────→ GPU 显存 GDS、GIDS 和 uGDS 都希望建立 SSD 与 GPU 显存之间的 DMA 路径,减少 CPU bounce buffer。但“数据不经过 CPU 内存”并不代表“CPU 没有参与”。 ...

2026年8月20日 · 5 分钟 · 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

数据库 MVCC 原理与实现:版本链、事务快照和可见性判断

MVCC(Multi-Version Concurrency Control,多版本并发控制)是现代数据库实现高并发事务的重要机制。它的核心思想并不复杂:数据被修改时,不立即丢弃旧值,而是保留多个版本;事务读取数据时,根据自己的快照选择可见版本。 因此,读事务可以继续访问旧版本,写事务则创建新版本。普通读取通常不需要等待并发写入完成,写入也不必等待读事务释放共享锁。 本文从 MVCC 要解决的问题出发,依次说明版本存储、事务快照、可见性判断、写冲突控制和旧版本回收,并比较 InnoDB 与 PostgreSQL 的典型实现。 一、为什么需要 MVCC 假设数据库中存在一条记录: 账户 A 的余额 = 100 事务 T1 正在读取这条记录,与此同时,事务 T2 希望把余额修改为 80。 如果所有读取都依赖共享锁,那么 T2 可能必须等待 T1 结束;如果 T2 已经持有排他锁,T1 又可能必须等待 T2。锁能够保证正确性,但大量读写互相等待会降低系统并发能力,并增加死锁概率。 MVCC 使用另一种思路处理普通读取: 新版本:balance = 80,由 T2 创建 ↓ 旧版本:balance = 100 如果 T1 的事务快照不允许它看到 T2 的修改,T1 就读取旧版本 100;在 T2 提交后启动的新事务,则可以读取新版本 80。 于是,同一时刻可以出现: 旧事务看到 100 新事务看到 80 这不是数据不一致,而是两个事务观察到了数据库在不同逻辑时刻的一致状态。 二、MVCC 的五个组成部分 一个完整的 MVCC 实现通常包含五个部分: MVCC = 多版本存储 + 事务快照 + 可见性判断 + 写冲突控制 + 旧版本回收 只保存历史版本还不够。数据库还必须知道版本由谁创建、事务能够看到哪些版本、两个写事务冲突时如何处理,以及历史版本何时可以安全删除。 ...

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

3FS 如何使用 FoundationDB:元数据模型、事务与并发控制

3FS 是面向 AI 训练和推理负载设计的分布式文件系统。它没有把 FoundationDB 当成普通的持久化 KV 使用,而是围绕 FoundationDB 的有序键空间、乐观事务、冲突检测和 Versionstamp,构建了一套强一致的文件系统元数据服务。 本文从源码出发,梳理 3FS 如何接入 FoundationDB,如何编码 inode 和目录项,以及 create、rename、remove、list 等文件系统操作怎样映射为 FoundationDB 事务。 本文分析的源码位于 3FS 仓库中的以下目录: src/fdb/ FoundationDB C API 封装 src/common/kv/ 通用 KV 和事务接口 src/meta/store/ inode、目录项和元数据操作 src/meta/components/ ID 分配、服务分布和 GC 等组件 src/meta/service/ Meta Service RPC 入口 一、3FS 中 FoundationDB 的定位 3FS 将数据面和元数据面分开: 文件的 chunk 数据由 Storage Service 保存。 文件系统元数据由 Meta Service 管理,并持久化到 FoundationDB。 FoundationDB 中保存的主要内容包括: inode; directory entry,也就是目录项; 文件打开会话; RPC 幂等记录; Meta Server 分布信息; 用户、配置和其他全局状态。 整体调用链可以简化为: ...

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

FoundationDB 事务如何实现?从源码看 GRV、Resolver、TLog 与 Storage Server

FoundationDB 支持跨任意 key range 的 ACID 事务,并向应用提供严格可串行化语义。但它既没有让每个 Storage Server 参与经典的两阶段提交,也不是传统单机数据库中“共享缓冲池 + Undo/Redo”的事务实现。 它的核心方案可以概括为: 全局版本号 + MVCC 快照读 + 客户端暂存修改 + Resolver 乐观冲突检测 + 多副本 TLog 持久化。 本文结合 FoundationDB 官方源码,沿着一笔事务的完整路径,解释它如何从读取、提交、冲突检测,一直走到日志持久化和 Storage Server 应用。 本文分析的源码版本为: commit: eab21ffe07c20575d5bac9ab0745375b8f7f8357 date: 2026-08-12 源码中的具体行号会随版本变化,阅读时应以函数和类型名为主要定位依据。 一、整体架构 FoundationDB 的事务涉及以下核心角色: 角色 主要职责 Client 保存事务 mutations、读冲突范围和写冲突范围 GRV Proxy 分配 Read Version Commit Proxy 批量接收事务、分配 Commit Version、组织提交流水线 Resolver 检测乐观事务冲突 TLog 复制并持久化已提交 mutations Storage Server 提供版本化读取,异步应用 TLog mutations Sequencer 为事务系统提供版本推进基础 完整流程可以简化为: Client │ ├── 获取 Read Version │ ├── 从 Storage Server 读取该版本 │ ├── 在客户端积累 mutations 和 conflict ranges │ └── 提交 │ ▼ Commit Proxy │ ├── 组成 Commit Batch ├── 分配 Commit Version └── 按 key range 发给 Resolver │ ▼ Resolver │ ├── 检查 read conflict ranges ├── 登记 write conflict ranges └── 返回 committed / conflict / too old │ ▼ Commit Proxy │ ├── 丢弃冲突事务 ├── 为 mutation 添加 Storage Server tag └── 将成功事务写入 TLog │ ▼ TLog │ ├── 按冗余策略复制和持久化 └── 返回 durable │ ▼ Commit Proxy │ └── 向 Client 返回 Commit Version │ ▼ Storage Server ├── 异步读取自己的 tagged mutations ├── 应用到内存中的版本化数据 └── 后台持久化到 Storage Engine 最关键的解耦是: ...

2026年8月14日 · 9 分钟 · Hellokitty

FoundationDB 中有 Undo 和 Redo 吗?从 TLog、MVCC 到故障恢复

传统数据库通过 Undo Log 和 Redo Log 保证事务的原子性与持久性:Undo 撤销未提交的修改,Redo 恢复已经提交但尚未写入数据文件的修改。那么,在采用分布式事务架构的 FoundationDB 中,是否也存在这两类日志? 简短回答是: FoundationDB 有承担类似 Redo 职责的 TLog,但通常没有传统意义上的 Undo Log。 这并不意味着 FoundationDB 不支持事务回滚或 MVCC,而是因为它对未提交数据、提交日志和历史版本的组织方式与传统数据库不同。 一、先回顾传统数据库为什么需要 Undo 和 Redo 传统数据库通常允许事务直接修改缓冲池中的共享数据页。数据页何时写入磁盘,与事务何时提交并不完全同步。 因此会出现两种状态。 1. 事务未提交,数据页却已经落盘 例如事务把账户余额从 100 改成 80,但随后事务失败: BEGIN; UPDATE account SET balance = 80 WHERE id = 'A'; ROLLBACK; 如果包含 80 的脏页已经写入磁盘,数据库就必须知道原值是 100,才能撤销这次修改。这是 Undo Log 的主要职责。 2. 事务已经提交,数据页却尚未落盘 另一个事务已经执行 COMMIT,但修改可能仍然只存在于内存中的脏页里。如果服务器此时断电,已提交的数据就会丢失。 因此数据库先持久化 Redo Log,再返回提交成功。重启后,即使数据页没有及时落盘,也可以通过 Redo 重新应用修改。 可以把二者概括为: Undo:事务不应该生效,但修改可能已经进入数据文件。 Redo:事务应该生效,但修改可能还没有进入数据文件。 二、FoundationDB 的事务提交流程 FoundationDB 并不是把客户端事务中的每次 set 或 clear 立即写入 Storage Server。事务提交前,这些操作首先保存在客户端的事务对象中。 ...

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

哈希碰撞概率与生日界:从公式到 SHA-256 / XXH128 实测

选哈希算法时常有两个问题绑在一起:碰撞概率有多小、算得有多快。这篇把碰撞概率背后的数学(生日界)讲清楚,再用它算一算 256-bit 的 SHA-256/BLAKE3 与 128-bit 的 XXH128 各自的碰撞概率,最后附上一组本机实测速度数据。 本文所有实测数据均来自随机生成的字节串,不含任何业务或私有数据。 一、背景:碰撞概率的两种含义 对固定长度输出的哈希,“碰撞概率"必须分两种场景谈,否则会得出互相矛盾的结论: 随机碰撞:没有攻击者,数据是正常/随机的。这时只要输出在取值空间里均匀分布,碰撞概率就纯粹由输出位数决定,与具体算法无关。 抗恶意碰撞:有攻击者知道算法、故意构造两个哈希相同的输入。这里才真正区分加密哈希(SHA-256、BLAKE3、BLAKE2)和非加密哈希(xxHash、CityHash、Murmur)。 第一种是数学问题,用生日界就能算。第二种是密码学性质,非加密哈希直接不提供保证。 二、生日问题:直觉的陷阱 一个房间里要多少人,才有超过 50% 的概率存在两人同一天生日?答案是 23 人——远比直觉小。原因在于碰撞看的不是"某人和我同天”,而是"任意两人之间"的配对数:n 个人有 n(n-1)/2 ≈ n²/2 对,概率随 n² 增长。 哈希碰撞是同一个问题:把"人"换成"哈希输入",把"365 天"换成"哈希空间大小 N"。对 128-bit 哈希,N = 2^128。 三、生日界公式的推导 设哈希空间大小为 N,独立均匀地放入 n 个值。直接算"至少一次碰撞"要用容斥,很麻烦,所以反过来算"全部不同"的概率: 第 1 个:随便放 → N/N 第 2 个:不能撞前 1 个 → (N-1)/N ... 第 n 个:不能撞前 n-1 个 → (N-n+1)/N 全部不同的概率是连乘: P(无碰撞) = ∏_{k=0}^{n-1} (1 - k/N) 于是至少一次碰撞: P(碰撞) = 1 - ∏_{k=0}^{n-1} (1 - k/N) 这是精确解,但连乘不好用。用近似 1 - x ≈ e^(-x)(x 很小时成立)把每一项换掉: ...

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