Dual-Blade: Dual-Path NVMe-Direct KV-Cache Offloading for Edge LLM Inference

发表时间: 2026-04 · arXiv:2604.26557 (ICDCS 2026)

原文: https://arxiv.org/abs/2604.26557

作者/机构:Bodon Jeong, Hongsu Byun, Youngjae Kim, Weikuan Yu, Kyungkeun Lee, Jihoon Yang, Sungyong Park (Sogang University, Florida State University, Samsung Electronics Co.)

速读

一句话结论 本文提出了一种名为 DUAL-BLADE 的双路径 NVMe 直通 KV Cache 卸载框架,通过动态将张量分配至页缓存或绕过内核的直通路径,并结合自适应流水线并行,在边缘设备的严格内存限制下显著降低了大语言模型推理的预填充和解码延迟。

要解决什么问题 在显存受限的边缘 AI 系统中,大语言模型推理产生的 KV Cache 极易撑爆显存并溢出到主机内存,进而被迫卸载到 NVMe SSD 上。现有的卸载方案(如 FlexLLMGen)主要依赖操作系统的页缓存(Page Cache)和内存映射(mmap)来进行 DRAM 与 SSD 之间的数据流转,这在机制上存在三个致命卡点。首先是页缓存颠簸(Thrashing),在解码阶段,注意力机制需要自回归地反复读取不断增长的 KV Cache,当工作集大小超过可用页缓存时,基于 LRU 的替换策略会导致刚被缓存的页在复用前就被驱逐,使得缓存命中率从 42% 瞬间断崖式跌落至不足 1%,引发极高的延迟。其次是内核存储栈的软件开销,数据穿过虚拟文件系统、文件系统、块层和设备驱动会带来巨大的单次请求延迟,导致预填充的大量写入和解码的读取被切块后,设备在请求间隙处于空闲状态,无法跑满 NVMe 带宽。最后是物理访问的顺序性被破坏,KV Cache 的读写在逻辑上是高度顺序的,但 Linux 的多队列块层(blk-mq)会将缓存未命中的并发读取分散到多个队列中,导致到达 SSD 控制器的请求变成碎片化、交错的非顺序逻辑块地址(LBA)流,增加了固件层的处理开销。

怎么做的 DUAL-BLADE 的核心思路是摒弃单一的页缓存依赖,根据实时内存预算将 KV 张量分流,并为落盘数据构建一条绕过内核的直通高速公路。该方法由三个关键部件构成。第一是双路径 KV 驻留机制(Dual-Path KV Residency),系统会实时评估可用的页缓存预算,将能够放入该预算的张量(Group 1)分配给传统的页缓存路径以维持高命中率,而将超出预算的张量(Group 2)分配给 NVMe 直通路径。这种物理隔离确保了页缓存不会被超量数据撑爆,从根本上绕开了缓存颠簸机制。第二是带有顺序 LBA 放置的 NVMe 直通设计(NVMe-direct with Sequential-LBA Placement),对于 Group 2 的张量,系统利用 io_uring_cmd 绕过文件系统等内核栈,直接向设备发送原生 NVMe 指令。为了消除张量与底层存储的映射错位,该部件强制将张量映射到连续的 LBA 区域,其关键不变量定义为下一个张量的起始地址紧接上一个张量的末尾:

$$lba_{start}(n+1) = lba_{start}(n) + \text{bytes}(\text{tensor } n) / lba_{size}$$

同时,它采用 Co-DMA 机制,让 GPU 和 NVMe 共享同一块锁页内存(Pinned Memory),实现了数据在 GPU 和 NVMe 之间的对称两跳传输,彻底消除了 CPU 内存拷贝。第三是自适应流水线并行(Adaptive Pipeline Parallelism),在多线程环境下,单纯并发 NVMe 读取会打满存储带宽引发争用。该部件在解码初期会动态探测两种策略的吞吐量:一种是同路径重叠(并发读取),另一种是跨路径重叠(让后继线程的存储 I/O 延迟启动,与前置线程的 GPU DMA 传输重叠)。系统会锁定吞吐量更高的策略,从而有效隐藏残余的 I/O 延迟。

效果如何 实验在配备 Intel Core Ultra 7 处理器、16 GB 主机内存和单张 NVIDIA RTX 5060 Ti(16 GB 显存)的系统上搭建,使用 OPT-6.7B 模型,输入 512 个 token 并生成 32 个 token,批处理大小为 32。测试对比了三个基线:Baseline(原生的 FlexLLMGen,代表纯页缓存路线)、CachePolicy-Only(仅使用页缓存但主动驱逐数据以防颠簸的路线)以及 NVMe-direct-Only(完全禁用页缓存,全部走直通路径的路线)。量化结果显示,在 2 GB 到 11 GB 的动态主机内存限制下,DUAL-BLADE 始终保持最优性能。在 PCIe Gen5 SSD 上,与 Baseline 相比,DUAL-BLADE 将预填充延迟降低了最高 33.1%,解码延迟降低了最高 42.4%,同时将 SSD 的利用率提升了最高 2.2 倍,并且页缓存命中率呈线性平稳增长,彻底消除了断崖式颠簸。在真实的数据清洗任务(长上下文、短输出)中,该方法同样能在 4 GB 的严苛内存限制下显著优于基线。作者也承认了该方法的代价:为了实现直通路径的 Co-DMA,每个拷贝线程都需要独占保留一块与张量大小相等的锁页内存,这构成了独立于页缓存之外的固定 DRAM 开销;此外,自适应流水线在解码阶段需要消耗一次迭代来进行策略探测。

主要贡献

随着隐私要求的提高和对低延迟本地推理的需求,大语言模型(LLM)推理正越来越多地从云端迁移到边缘AI系统。这些边缘系统通常是单GPU平台,具有严格的内存预算(8–32 GB DRAM)。在此限制下,7–8B参数模型成为主流。然而,边缘AI系统面临一个核心挑战:在加载模型参数后,GPU设备内存通常已接近饱和。当KV Cache(用于存储每个Token的Key/Value向量以避免注意力机制重复计算)超出GPU内存时,它会被推入主机DRAM。由于KV Cache的大小随上下文长度和批处理大小动态变化,它极易耗尽GPU内存和剩余的主机DRAM,从而形成结构性瓶颈。

现有的解决方案通常利用NVMe SSD作为扩展内存层,通过标准文件系统(如基于mmap的OS Page Cache)来映射SSD。然而,这种设计存在三个根本性缺陷:
1. Page Cache抖动:在Decode阶段,自回归模式导致反复读取积累的KV Cache,触发严重的页面缓存抖动(缓存页在重用前被驱逐),使得缓存命中率崩溃。
2. 高昂的软件栈开销:遍历虚拟文件系统(VFS)、文件系统、块层和设备驱动程序的软件开销引入了严重的延迟,阻碍了对NVMe SSD带宽的充分利用。
3. 设备级顺序局部性丢失:Linux多队列块层(blk-mq)将请求分布到多个队列,导致原本连续的KV Cache流在到达SSD控制器时变得碎片化和交错。

针对上述问题,本文提出了DUAL-BLADE,一个为边缘LLM推理设计的双路径NVMe-direct KV Cache卸载架构。其主要创新点包括:
* 双路径KV驻留(Dual-Path KV Residency):根据运行时可用的Page Cache预算,动态地将KV张量组分配到Page Cache路径或NVMe-direct路径,从而避免缓存抖动并维持高命中率。
* 带有顺序LBA放置的NVMe-direct(NVMe-direct with Sequential-LBA Placement):在NVMe-direct路径上绕过内核存储栈,并将KV张量映射到连续的逻辑块地址(LBA)区域,以实现低开销的直接存储访问并保留设备级顺序性。
* 自适应流水线并行(Adaptive Pipeline Parallelism):动态协调线程间的I/O和GPU DMA计算,在带宽争用下隐藏残余I/O延迟,最大化端到端推理吞吐量。

实验表明,DUAL-BLADE在各种内存预算下大幅缓解了I/O瓶颈,将Prefill和Decode延迟分别降低了最高$33.1\%$和$42.4\%$,同时将SSD利用率提高了$2.2\times$。

背景知识与关键观察

LLM Transformer推理与KV Cache。LLM由堆叠的Transformer块组成。每个块包含多头注意力(MHA)和前馈网络(FFN)。推理分为Prefill和Decode两个阶段。在Prefill阶段,所有Prompt Token并行处理以生成第一个Token。在Decode阶段,模型以自回归方式逐个生成Token。在每次Decode步骤中,模型从KV Cache加载所有先前的KV对以计算当前Token的注意力,然后将新生成的KV对存回KV Cache。KV Cache的大小随上下文长度和批处理大小变化,极易超出GPU内存。
LLM Transformer架构

高效KV Cache管理的内存分层。当KV Cache超出GPU内存时,推理系统会将其卸载到主机DRAM或NVMe SSD。以FlexLLMGen【14,Flexgen: High-throughput generative inference of large language models with a single gpu+2023+ICML】为例,系统为每一层创建文件用于存储$K$和$V$张量。在推理时,数据传输分两段进行:通过固定内存DMA实现GPU与CPU间的数据移动;通过内存映射I/O(mmap)实现CPU与文件系统间的数据移动。写操作进入Page Cache并异步刷新;读操作若命中Page Cache则立即返回,未命中则由内核从磁盘获取。
KV Cache磁盘卸载工作流

KV工作负载中的Page Cache抖动现象。在有限的主机内存下,Page Cache会产生严重的抖动。Prefill是写密集型的,当内存低于约6GB时,较小的Page Cache会强制在后台写回之前进行同步驱逐,导致写停顿和延迟上升。Decode是读密集型的,系统在每次生成Token时都会重新读取积累的KV Cache。当主机内存限制低于KV工作集(如8.57–9.11 GB)时,Page Cache命中率并不会逐渐下降,而是从$42\%$断崖式暴跌至$<1\%$,导致Decode延迟激增。根本原因在于自回归重用模式与基于LRU的页面缓存机制发生冲突,导致读取一个张量时会驱逐即将被再次读取的另一个张量。
不同主机内存限制下的Page Cache抖动

内核存储I/O路径作为核心推理瓶颈。在M-High(11GB,命中率$\approx 100\%$)和M-Low(2GB,命中率$\approx 0\%$)两种极端内存分配下,磁盘I/O(通过Page Cache的文件后备mmap I/O)成本存在显著差异。在Prefill阶段,计算主导了大部分时间,但磁盘I/O仍占据$24\%$至$28\%$的非微不足道的比例。在Decode阶段,磁盘I/O占据了$56\%$(M-High)和$69\%$(M-Low)的端到端推理时间,表明Decode主要受磁盘I/O主导。
Prefill和Decode阶段的总延迟分解

单层I/O请求延迟分解。对于写回或Page Cache未命中的情况,请求需要遍历完整的内核软件栈,累积了大量的单次请求延迟。Decode写(256 KB)能够达到$100\%$的NVMe繁忙率,因为内核将其作为单个I/O发出。相反,Prefill写(128 MB)和Decode读(128-135 MB)在块层被分块。每个分块都支付全栈开销,在命令之间留下设备空闲间隙,导致繁忙率低下($45\%$和$55\%$)。
单层每个张量I/O请求的延迟分解

KV Cache I/O中逻辑与物理访问的差异。KV Cache工作负载在张量视图的逻辑层面上具有近乎完美的顺序性(大顺序写和大顺序读)。然而,在设备级别的逻辑块地址(LBA)流中,这种顺序性被破坏了。根本原因在于提交队列(SQ)的分布:写操作集中在少数队列中,而读操作由于Page Cache未命中触发了应用程序和内核线程的并行提交,分散在多个队列中。经过Linux多队列块层(blk-mq)后,原本逻辑上单一的顺序读变成了一系列交错的短顺序片段。
逻辑(张量级)和物理(设备级)访问模式

机遇:NVMe-direct路径(内核旁路)。KV Cache访问模式可预测且主要为顺序,且无需标准文件系统提供的持久性、一致性和元数据持久性保证。因此,可以采用NVMe-direct机制绕过文件系统。系统使用io_uring_cmd【25,I/O passthru: Upstreaming a flexible and efficient I/O path in Linux+2024+FAST】作为内核旁路通道来发出设备原生的NVMe命令。相比于标准ext4文件系统,io_uring_cmd和SPDK【23,Storage performance development kit (SPDK)+2025+URL】均能显著降低延迟。由于io_uring_cmd通过标准内核接口实现了与SPDK相当的性能且无需忙轮询,因此被选中用于设计。

方法细节

双路径KV驻留(Dual-Path KV Residency)。双路径KV驻留机制将隐藏层的KV张量分配到两个I/O路径组,使得在Prefill和Decode阶段,每个张量都能通过其分配路径对应的I/O栈进行处理。在初始化时,每个KV对被抽象为KV放置单元(KPU)。预算器(budgeter)计算可用的Page Cache,并将每个KPU划分为两类:Group 1(Page Cache路径)和Group 2(NVMe-direct路径)。随后,每个组绑定到物理地址空间,将Group 1映射到文件系统扩展,将Group 2映射到NVMe命名空间LBA扩展。这种绑定在逻辑上保持,直到第一次访问时才具体化为实际的文件或块。
双路径KV缓存驻留的高级架构

规划阶段(Plan)。首先,预算器在系统和cgroup限制下估计可用的Page Cache预算$B_{pc}$。固定内存($M_{pin}$)被专门保留供每个复制线程进行GPU DMA使用,其大小与单个隐藏层的K或V张量(KPU)匹配。总的保留内存$N_{threads} \cdot M_{pin}$构成了推理服务的恒定DRAM开销。算法1使用旋钮$X \in [0, B_{pc}]$将每个隐藏层的KPU对$(K^{(i)}, V^{(i)})$划分到Group 1或Group 2。算法先计算能容纳的层数$n_1$,接着将第1层到第$n_1$层的KPU对分配给Group 1($x_i = 1$),其余的层分配给Group 2($x_i = 0$)。

绑定与具体化阶段(Bind & Materialize)。绑定过程将驻留类映射到存储。Group 1 KPU被分配给文件系统扩展(使用mmap),而Group 2 KPU被分配给NVMe命名空间LBA扩展。为了隔离路径,系统使用分区或不同的命名空间分离NVMe空间。在推理期间,每个请求由二进制决策$x_i$调度以具体化逻辑绑定。对于读写操作,Group 1利用文件系统控制下的Page Cache,而Group 2通过NVMe-direct直接访问保留的LBA块。

带有顺序LBA放置的NVMe-direct(NVMe-direct with Sequential-LBA Placement)。该设计旨在通过消除内核存储栈开销和张量-LBA不匹配来加速Group 2 KPU。前提条件是张量大小必须是设备LBA大小(通常为512 B或4 KiB)的整数倍。对于OPT模型,当批处理大小$B \geq 1$时,这种对齐通常成立;对于边缘情况(如OPT-13B,约10 KiB),系统选择偶数$B$以确保对齐。

LBA绑定(LBA Bind)。绑定过程通过哈希映射$\mathcal{M}$将Group 2 KPU映射到一个连续的命名空间扩展上,映射关系为从张量ID $n$到其起始LBA和块数:$\mathcal{M} : n \rightarrow \langle lba_{start}, n_{blocks} \rangle$。绑定遵循三个不变量:(i) 对齐(张量I/O单元是LBA大小的倍数);(ii) 不相交(扩展不重叠);(iii) 连续(每个下一个张量在上一个张量结束处开始)。只有第一个张量的$lba_{start}$是用户指定的,后续张量的起始LBA通过累加前一个张量的块数顺序决定。这确保了所有Group 2 KPU占据一个连续的扩展,从而在NVMe控制器处保留逻辑上的顺序流。

通过主机固定内存的GPU-NVMe协同DMA(GPU-NVMe Co-DMA over Host Pinned Memory)。对于Page Cache路径(Group 1),写操作遵循3跳序列:GPU到固定内存(DMA)、固定内存到Page Cache(memcpy)、Page Cache到NVMe(DMA)。相反,NVMe-direct路径(Group 2)通过绕过Page Cache去除了memcpy,形成对称的2跳流。写操作遵循GPU缓冲区到NVMe WRITE,读操作遵循NVMe READ到GPU缓冲区。系统使用Co-DMA方案,GPU和NVMe共享同一个已经为GPU DMA分配的每线程固定缓冲区。这得益于DMA缓冲区约束是对齐的(页锁定且4 KiB对齐),并且独占所有权确保复制线程使用不相交的固定区域并单向操作。
NVMe-direct路径架构:Bind、Data Path和Data Flow

数据流:固定内存与NVMe之间的转换(Data Flow: Pinned Memory ↔ NVMe)。为了通过io_uring_cmd在NVMe上执行直接读写,复制线程需要将张量信息转换为目标位置(LBA)和请求大小。算法2在哈希映射$\mathcal{M}$中查找$lba_{start}$,根据目标形状和索引计算行主序字节偏移,然后推导出起始LBA($slba^*$)和请求字节数($req_{bytes}$)。接着,复制线程将$req_{bytes}$分块以适应NVMe控制器的最大数据传输大小(MDTS)。分块大小被设置为MDTS内容纳的最大LBA大小的倍数。对于每个分块,复制线程构建一个NVMe原生命令,包含操作码(READ或WRITE)、命名空间ID、起始LBA、逻辑块数以及指向固定内存地址的主机数据缓冲区。这些分块命令被异步提交到提交队列(SQ),并在完成队列(CQ)上收集完成事件。当飞行中的请求达到队列深度(QD)或排空剩余分块时,复制线程会收获CQE,直到所有分块收集完毕即完成传输。
算法2图片

数据集管理与解除分配(Dataset Management–Deallocate (TRIM))。在上下文销毁时,分配的LBA空间通过NVMe数据集管理(DSM)解除分配来进行回收。对于每个张量,系统查询$\mathcal{M}$获取位置信息,并通过io_uring_cmd提交TRIM命令,显式提示NVMe控制器指定的LBA范围不再包含有效数据。由于系统有效消除了LBA碎片,设备维持了接近1的理想写入放大因子(WAF),从而使得垃圾回收(GC)开销可以忽略不计。

GPU DMA与存储I/O之间的流水线并行(Pipeline Parallelism between GPU DMA and Storage I/O)。在多复制线程环境中,系统引入了流水线并行以重叠GPU DMA和存储I/O,动态地在Overlap-Intra和Overlap-Cross两种策略中选择。Overlap-Intra在同一硬件路径内重叠I/O(如并行NVMe读),能提高带宽利用率,但在存储饱和时会引发争用。Overlap-Cross跨不同硬件路径重叠I/O(如NVMe读和GPU DMA),通过利用独立的硬件资源实现有效的延迟隐藏。
适应性流水线并行与两种重叠策略

自适应流水线策略选择(Adaptive Pipeline Strategy Selection)。在Decode阶段,系统动态执行策略选择。首先,省略第一次Decode迭代以排除冷Page Cache效应。接着在第2次迭代(Overlap-Intra)中,并行化两个复制线程的存储读取,而GPU DMA串行执行。然后在第3次迭代(Overlap-Cross)中,系统强制延迟线程T2的存储读取(交错启动),使其与线程T1发出的GPU DMA重叠。最后,系统比较这两次迭代中各组的吞吐量,并将性能更高的策略固定用于后续所有的Decode迭代。

实验环境

实验结果

端到端推理性能(Prefill和Decode)。在2–11 GB主机内存限制范围内测量了各配置的延迟。CachePolicy-Only有效避免了Baseline在Decode阶段的Page Cache抖动,延迟随内存增加呈线性下降。NVMe-direct-Only因绕过Page Cache表现出恒定延迟,但在内存充足时无法利用DRAM,导致性能垫底。DUAL-BLADE在所有内存限制下均表现最优,它在2-7GB范围内缓解了Prefill写停顿,并消除了Decode时的缓存抖动。在SSD A上,DUAL-BLADE将Prefill延迟降低最多$33.1\%$,Decode延迟降低$8.2\% - 42.4\%$;在SSD B上,Prefill降低最多$25.4\%$,Decode降低$11.7\% - 57.8\%$。
端到端推理延迟

缓解Page Cache抖动。DUAL-BLADE实现了Page Cache命中率随主机内存限制的完美线性增长,有效消除了2-7 GB之间的抖动区。由于实现了完全的路径分离,其命中率优于依赖posix_fadvise产生额外开销的CachePolicy-Only。
Page Cache命中率

最大化NVMe利用率和吞吐量。在2GB内存限制下,DUAL-BLADE显著降低了平均/99百分位延迟并提高了NVMe繁忙率。对于256 KB的Decode写,尽管Baseline和DUAL-BLADE的设备繁忙率均为$100\%$,DUAL-BLADE在两款SSD上仍将延迟降低了超过$98\%$(如SSD A上从4.91 ms降至0.06 ms),证明Baseline的延迟主要由软件开销主导。在磁盘吞吐量方面,DUAL-BLADE在SSD A上的Prefill写吞吐量比Baseline高$43\%$,Decode读吞吐量高$120\%$($2.20\times$)。
磁盘吞吐量

顺序LBA访问与更紧凑的提交-完成延迟。通过bpftrace分析,DUAL-BLADE在Prefill和Decode阶段的LBA访问模式与理想的张量访问模式紧密匹配,实现了端到端的完全顺序化,消除了Baseline中的交错读流。这使得NVMe控制器处理的是干净的顺序流,从而在大多数读写队列深度(QD)区间内降低了平均$\mu s / KB$延迟,并收紧了p5-p95的延迟分布。
LBA模式
QD-bin I/O延迟

自适应流水线并行(P·P)。在不同的DRAM-SSD分层比例($\alpha$)下,启用P·P进一步一致地降低了Decode延迟。在SSD A上延迟降低了$7\% - 9\%$,在SSD B上降低了$3\% - 5\%$。吞吐量动态分析表明,Overlap-Intra中的并发读取会导致短时间内SSD峰值顺序读取带宽饱和从而引发设备资源争用,而Overlap-Cross策略成功将其缓解,使得系统最终收敛于Overlap-Cross策略。
吞吐量动态变化
毫秒级吞吐量分析

实际边缘场景评估。在严格的4 GB主机内存限制下,测试了四项数据清洗任务。DUAL-BLADE在大多数情况下优于Baseline(例如在DI:Buy任务中将延迟降至$0.85\times$)。只有在ED:Hospital任务中,由于其较小的KV Cache(1.58 GB)能完全放入Page Cache,性能才与Baseline相当。

结论

本文提出了DUAL-BLADE,这是一种双路径、NVMe-direct的KV Cache卸载架构,旨在加速内存受限的边缘AI系统中的LLM推理。DUAL-BLADE集成了双路径KV驻留、NVMe-direct I/O和自适应流水线并行,以最大化推理期间的存储I/O效率。广泛的评估表明,DUAL-BLADE在各种内存预算下,将Prefill和Decode延迟分别降低了最高$33.1\%$和$42.4\%$,同时将SSD利用率提高了最高$2.2\times$。

补充细节

相关工作与系统定位。现有研究探索了将LLM推理卸载到NVMe SSD的技术,如LLM-in-a-Flash、KVSwap等。在系统层面上,FlexLLMGen、llama.cpp和vLLM(配备LMCache)利用异构内存层来管理权重和KV张量。DUAL-BLADE与FlexLLMGen是正交的,并且其框架无关的设计原则可应用于LMCache等后端。此外,尽管GDS和BaM等硬件原语支持GPU-SSD的P2P DMA,但它们通常假设数据中心级GPU(如A100/H100)和支持特定PCIe拓扑的服务器。相比之下,DUAL-BLADE针对的是不支持GPU-SSD P2P的边缘AI平台(如本地和嵌入式部署),并且其设计可以叠加在GDS/BaM原语之上以进一步提高卸载性能。

参考文献汇总