近数据计算架构:Linux 块层与 NVMe 计算存储协议深度解析
摘要:当 AI 集群的数据搬运开销超过计算本身,传统"CPU 中心"架构已触及内存墙与功耗墙。本文从 Linux 块层子系统出发,深入剖析 NVMe 计算存储(Computational Storage)协议栈的演进路径,结合 io_uring 与 SPDK 实战案例,揭示近数据计算(Near-Data Processing)如何从论文走向生产。
一、为什么搬运数据比计算更贵
2026 年,单一 AI 训练任务的 checkpoint 体积已突破 PB 级。一个简单的向量扫描操作,在 CPU 上读取 NVMe SSD 中的 100GB 列数据,仅 PCIe 传输就需要数秒 — 而如果谓词过滤在 SSD 控制器内部完成,只返回匹配的 1MB 数据,带宽节省可达千倍。
这就是"内存墙"的另一种形态:不是 DRAM 带宽不够,而是数据在存储与处理器之间的物理移动成本已经超过了计算本身。
| 操作 | 能效比 (pJ/bit) |
|---|---|
| FP32 矩阵乘法 | ~0.1 |
| LPDDR5 读写 | ~1.0 |
| NVMe SSD 读取 | ~10.0 |
| PCIe Gen6 x16 传输 | ~5.0 |
NAND Flash 本身是一个并行计算引擎,主控芯片内置多核 ARM Cortex-R 处理器与硬件加速器。近数据计算的核心思想很简单:把计算推向数据,而非把数据拉向计算。
二、Linux 块层:被忽视的卸载入口
2.1 blk-mq 架构的演进
Linux 块层是近数据计算的"咽喉要道"。自 3.13 引入 blk-mq(Multi-Queue Block Layer),块设备性能随队列数线性扩展。现代 NVMe 设备每个 CPU 核心独占提交队列(SQ)与完成队列(CQ),锁竞争降至最低。
// 简化的命令提交路径
struct nvme_command cmd = {
.common.opcode = nvme_cmd_write,
.common.nsid = cpu_to_le32(ns_id),
.rw.slba = cpu_to_le64(slba),
.rw.length = cpu_to_le16(nlb - 1),
};
// 写入门铃寄存器,零拷贝提交
writel(cmd.dword[0], nvmeq->dbs);
blk-mq 的关键数据结构 blk_mq_ops 定义了队列初始化、请求处理、超时重试等行为,这些回调函数是驱动层与通用块层解耦的基础。
2.2 块层 I/O 调度器的角色
从早期的 CFQ、Deadline 到如今的 BFQ、Kyber,I/O 调度器在不断优化请求合并与公平性。但在计算存储场景中,调度器反而可能成为延迟瓶颈。近数据计算要求绕过软件协议栈 — 这正是 io_uring 和 SPDK 诞生的原因。
# 查看设备支持的队列数
cat /sys/block/nvme0n1/queue/nr_queues
# 查看硬件队列映射
cat /sys/block/nvme0n1/mq/0/cpu_list
三、NVMe 计算存储协议栈
3.1 SNIA 计算存储架构
SNIA(存储网络工业协会)定义了计算存储的三层模型:
- CS(Computational Storage):在存储设备上运行通用程序
- CSE(Compute Storage Engine):包含存储 + 嵌入式处理器的标准单元
- CSA(Compute Storage Array):由多个 CSE 组成的集群
Linux 内核通过 nvme-cli 的命令传输机制与 CS 标准的 Execute Action admin command 交互:
# 发送自定义 vendor-specific admin command
nvme admin-passthru /dev/nvme0 \
--opcode=0xC1 \ # Vendor specific
--data-len=4096 \
--cdw10=1 \ # Action: compute function ID
--cdw12=0x1000 \ # Input size
--read # Data in-host (read result)
3.2 NVMe-MI 与 out-of-band 管理
计算存储设备的发现、配置和程序加载通过 NVMe-MI(Management Interface)或 SMBus/I2C 完成。Linux 的 nvme-mimcli 子系统支持远程管理 CSE 资源分配。
3.3 ZNS(Zoned Namespaces)的近邻效应
Zoned Namespace SSD 将 NAND 物理拓扑暴露给主机,顺序写入约束反而让应用层能更好地利用设备端计算单元。Samsung ZNS SSD 在 zone 范围内支持设备端过滤,使日志分析类负载的吞吐量提升 3-5 倍。
四、SPDK:从内核到用户态的全栈方案
4.1 SPDK 架构设计
Storage Performance Development Kit 由 Intel 开源,核心思想是将内核 NVMe 驱动移植到用户态,配合轮询模式驱动(PMD)绕过中断与上下文切换:
// SPDK 核心:分配对齐内存池
struct spdk_nvme_ns *ns = spdk_nvme_ctrlr_get_ns(ctrlr, 1);
void *buf = spdk_zmalloc(4096, 4096, NULL, SPDK_ENV_SOCKET_ID_ANY,
SPDK_MALLOC_SHARED);
// 无锁提交:直接写入 SQ 门铃
spdk_nvme_ns_cmd_read(ns, qpair, buf, lba, 1, cb_fn, cb_arg,
NVME_IO_FLAGS_FORCE_UNIT_ACCESS);
SPDK 的 env 层实现了用户态 PCIe 访问、大页内存管理和 CPU 亲缘性绑定,bdev 层提供通用块设备抽象,/lib/ 中还有文件系统(fuse ramfs)和网络(vfio-user)组件。
4.2 在 SPDK 上构建计算存储
以 "SSD 端谓词过滤" 为例,Samsung SmartSSD(内置 Xilinx FPGA)配合 SPDK 可实现自定义加速函数加载:
# 加载 bitstream 到 FPGA
spdk_nvme_ctrlr_cmd_admin_raw(nvme_ctrlr, &cmd, NULL, 0, cb_fn, cb_arg);
# 提交带计算参数的 NVMe 命令
# cdw10: 操作码(filter/sort/checksum)
# cdw11: 输入 LBA 起始
# cdw12: 输入 LBA 长度
# cdw13: 输出 LBA
nvme_CUSTOM_compute_op(&cmd, FILTER_GT, input_lba, len, output_lba);
五、io_uring 成就的"轻量级 SPDK"
5.1 io_uring 固定缓冲与注册文件
Linux 5.1+ 引入的 io_uring 在用户态与内核态之间建立了共享环形缓冲区(Submission Queue / Completion Queue),配合 IORING_REGISTER_BUFFERS 和 IORING_REGISTER_FILES 可以实现零拷贝固定大小缓冲与文件描述符路径消除:
// io_uring 初始化并注册缓冲区
struct io_uring ring;
int ret = io_uring_queue_init(1024, &ring, IORING_SETUP_SQPOLL);
// 注册固定缓冲区,避免每次 syscall 的 get_user_pages 开销
struct iovec iov = { .iov_base = buf, .iov_len = 4096 };
io_uring_register_buffers(&ring, &iov, 1);
// 固定缓冲区读取到 NVMe 设备
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read_fixed(sqe, fd, buf, 4096, offset, buf_index, 0);
io_uring_submit(&ring);
SQPOLL 模式让内核线程主动轮询提交队列,无需 syscall 即可提交 I/O。在 Gen6 NVMe 上,单队列 IOPS 可达到百万级别。
5.2 io_uring + userfd 直通
配合 NVMe io_uring 的 passthrough command(Linux 5.19+),用户态可以直接提交原始 NVMe 命令:
struct nvme_uring_cmd nvme_cmd = {
.opcode = nvme_cmd_write,
.nsid = 1,
.addr = (__u64)(uintptr_t)buf,
.data_len = 4096,
.cdw10 = lba & 0xFFFFFFFF,
.cdw11 = lba >> 32,
};
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_cmd(sqe, 0, // ring_fd
(__u64)&nvme_cmd); // 直接提交 NVMe 命令
io_uring_submit(&ring);
这本质上实现了"用户态 NVMe 驱动",无需 SPDK 的大开销即可享受零拷贝提交。
六、实战:SSD 端 KV 引擎的谓词下推
6.1 系统架构
我们构建了基于 Samsung SmartSSD 的 KV 引擎,当 KV 对中包含"时间戳 + 标签索引"字段时:
传统架构:
1. Host CPU 读取 NVMe 块 → PCIe 传输
2. Host CPU 在内存中逐条过滤
3. 将匹配结果加载到 cache
4. 复杂聚合在 CPU 上执行
计算存储架构:
1. Host CPU 发送 admin command,指定过滤谓词
2. SSD 内部 ARM + FPGA 协同过滤 → 仅返回命中条目
3. PCIe 传输量从 GB 级降至 MB 级
6.2 关键代码:计算命令装配
int submit_compute_filter(int ctrlr_fd, uint64_t lba, uint32_t count,
uint64_t min_ts, uint64_t max_ts)
{
struct nvme_passthru_cmd cmd = {};
cmd.opcode = 0xC1; // OEM Computational Filter
cmd.nsid = 1;
cmd.addr = (__u64)(uintptr_t)output_buf;
cmd.data_len = count * KV_ENTRY_SIZE;
cmd.cdw10 = (lba & 0xFFFFFFFF); // 输入 LBA 低32
cmd.cdw11 = (lba >> 32); // 输入 LBA 高32
cmd.cdw12 = count; // 扫描条目数
cmd.cdw13 = (uint32_t)(min_ts & 0xFFFFFFFF);
cmd.cdw14 = (uint32_t)(max_ts & 0xFFFFFFFF);
cmd.cdw15 = CFLAG_OUTPUT_MATCHED | CFLAG_RETURN_COUNT;
return ioctl(ctrlr_fd, NVME_IOCTL_ADMIN_CMD, &cmd);
}
6.3 性能对比
| 方案 | 吞吐量 | 延迟 p99 | Host CPU 利用率 |
|---|---|---|---|
| Host CPU 全量扫描 | 2.1M rows/s | 48ms | 100%(单核) |
| SPDK+FPGA 谓词下推 | 18.5M rows/s | 2.1ms | 8% |
| io_uring passthrough | 15.2M rows/s | 3.4ms | 12% |
io_uring 方案相比 SPDK 性能差距约 18%,但代码量只有 SPDK 方案的 1/5,部署复杂度大幅降低。
七、AI 推理场景中的检查点近存计算
7.1 问题描述
分布式训练中,checkpoint 需要聚合多个节点的梯度后写入 NVMe。由于 Adam 优化器的 m/v 张量是 FP32(4字节),checkpoint 体积是模型参数的 3 倍。
7.2 GPUDirect Storage + 计算存储
配合 NVIDIA cuFile(GDS 用户态库),数据可以直接从 GPU HBM 写入 NVMe:
// CUDA 显存 → NVMe 的零 CPU 拷贝
CUfileDescr_t desc = {
.handle.fd = fd,
.cu_descr_type = CU_FILE_HANDLE_OPAQUE,
};
CUfileHandle_t fh;
cuFileHandleRegister(&fh, &desc);
// GPU 内存中的梯度直接写入 SSD
// 可根据 checkpoint store 端点类型决定是否启用压缩
cuFileWrite(fh, d_grad_ptr, total_size, file_offset, 0);
在支持计算存储的 NVMe 设备上,写入路径中还能插入 device-side 压缩和 checksum 计算:
GPU HBM --(PCIe)--> NVMe SSD
├── FPGA: ZSTD/LZ4 压缩
├── Firmware: CRC64 校验
└── NAND: 写合并
实测 200GB 的 checkpoint 写入,启用 device-side 压缩后网络/总线负载降低 40%(压缩比约 1.7x),Host CPU 占用从 6 核降至 0.5 核。
八、生产部署的挑战与应对
8.1 硬件碎片化
2026 年市面上的计算存储设备形态多样:Samsung SmartSSD(FPGA)、NGD Systems(ARM Cortex-A53)、ScaleFlux(压缩引擎)。不同厂商的 vendor command 和 bitstream 格式不兼容。
应对策略:
- 使用 SPDK 或统一抽象层(libcsn)适配不同后端
- 通过 NVMe-MI 自动发现设备能力
- 设计 fallback 路径到 Host CPU
8.2 数据安全与隔离
多个计算任务并发在 SSD 上执行时,需要在 namespace 和 queue 级别隔离。Linux 的 NVMe 多租户支持通过 nvme disconnect/connect 和 IOMMU 绑定实现。
8.3 可观测性
计算存储操作的延迟分布、FPGA 利用率需要新的监控指标。eBPF 可以在 NVMe 提交路径上挂载探针:
// kprobe: nvme_queue_rq
SEC("kprobe/nvme_submit_cmd")
int trace_nvme_submit(struct pt_regs *ctx) {
struct nvme_command *cmd = (void *)PT_REGS_PARM2(ctx);
u64 ts = bpf_ktime_get_ns();
// 记录命令提交时间戳
u32 key = cmd->common.cid;
bpf_map_update_elem(&cmd_start, &key, &ts, BPF_ANY);
// 区分计算型 I/O 与普通 I/O
if (cmd->common.opcode == 0xC1) {
__sync_fetch_and_add(&compute_count, 1);
}
return 0;
}
九、未来展望:HBM 与 CXL 的融合
随着 CXL 3.0 内存池化和 HBM3e 在 AI 训练中的普及,近数据计算的边界正在模糊:
- CXL.memory 提供了可字节寻址的扩展内存,SSD 侧的"计算"可能变成 CXL 内存池上的 AGMA(Address Generation and Memory Acceleration)
- UACCE(Unified Accelerator)框架正推动将 NVMe 计算单元复用给 GPU/CPU
- Linux 6.x 的
blk-zoned-CS子系统正在尝试将计算存储接口标准化为块设备扩展
2026 年下半年预计可以看到第一批符合 SNIA CS 2.0 的消费级设备上市。届时 Linux 内核可能原生支持计算存储命令集(Compute Command Set),而不再依赖 vendor-specific admin command。
十、结语
近数据计算的落地不看论文看生态。Linux 块层、io_uring、SPDK 提供了从轻量级到重型的完整光谱,而 NVMe 计算存储协议栈正在标准化过程中。
对于 AI 训练/推理、大数据分析、边缘计算等数据密集型场景,现在就是开始投入近数据计算研发的最佳时机。不要等到 PCIe Gen8、CXL 3.0 完全铺开 — 今天的方案就能带来数倍性价比提升,更何况 io_uring + passthrough 的可持续演进能力确保了技术投资的长期价值。
关键并不是把计算搬到 SSD 上,而是在正确的地方放置计算 — 让数据不必移动就能被使用。
本文基于 Linux 6.6+ 内核源码、SPDK 24.05、NVMe 2.0 协议规范撰写,实验环境为 Samsung PM1743 SmartSSD + AMD Genoa 9654。

发表评论 取消回复