HiCache 系统设计与优化#

本文档全面概述了 SGLang HiCache,涵盖了其系统架构、工作流程和关键组件。它还详细介绍了配置参数、优化技术以及与各种 L3 存储后端的集成,旨在为用户和开发人员理解和调优 HiCache 以实现高效的 LLM 推理提供完整参考。

为什么需要 HiCache?它是什么?#

在大语言模型推理中,Prefill(预填充)阶段往往非常耗时:输入序列需要先转换为 Key-Value 缓存(KV cache)以供后续解码使用。当多个请求共享相同的前缀时,该前缀的 KV 缓存是完全相同的。通过缓存并复用这些共享的 KV 缓存,可以避免重复计算。为了解决这一问题,SGLang 引入了 RadixAttention(利用空闲 GPU 显存缓存和复用前缀 KV 缓存)以及 HiCache(将这一理念扩展到主机内存和分布式存储)。

受现代 CPU 经典三级缓存设计的启发,HiCache 将 GPU 显存组织为 L1,主机内存组织为 L2,分布式存储组织为 L3。这种层次结构使 HiCache 能够充分利用 GPU 和 CPU 的“空闲”存储空间,同时集成了 Mooncake、3FS、NIXL 和 AIBrix KVCache 等分布式缓存系统,用于全局 KV 缓存的存储和调度。因此,HiCache 在保持强劲读取性能的同时,显著扩展了 KV 缓存容量——特别是在多轮问答和长文本推理等 KV 缓存复用频繁的场景中。有关详细的基准测试结果,请参阅此博客

系统设计#

整体架构#

在许多现代 CPU 架构中,容量小但速度快的 L1 和 L2 缓存是每个核心私有的,以便快速访问最热的数据,而容量更大的 L3 缓存则由所有核心共享,从而显著减少缓存内的冗余。类似地,在 HiCache 中,L1 和 L2 KV 缓存对每个推理实例是私有的,而 L3 KV 缓存则由集群内的所有推理实例共享。

HiRadixTree:HiCache 中的元数据组织#

在 KV 缓存数据组织方面,HiCache 基于 RadixAttention 中引入的 RadixTree 结构,提出了 HiRadixTree。在 RadixAttention 中,RadixTree 的每个节点对应 GPU 显存中一段连续 Token 的 KV 缓存。从根节点到叶节点的路径代表请求的前缀,多个请求之间的共享前缀可以复用相同的节点,从而避免重复存储。

HiRadixTree 扩展了这一理念:每个节点对应一段连续 Token 的 KV 缓存,并记录该 KV 缓存储存在何处——无论是本地 GPU 显存、CPU 内存、L3 存储,还是其中的多个层级。如果是本地存储,HiRadixTree 会维护精确的元数据,包括具体的存储地址。然而,为了减少开销,HiRadixTree 不会存储或持续同步 L3 KV 缓存的元数据。相反,在访问 L3 数据时,它会实时查询后端以获取必要的元数据,例如数据是否存在以及驻留在哪台服务器和位置。

整体工作流#

HiCache 的工作流程主要涉及三个关键操作:本地匹配 (local match)预取 (prefetch)写回 (write-back)。当系统接收到新请求时,首先在本地 L1 和 L2 缓存中搜索匹配的 KV 缓存。对于本地未找到的部分,它会尝试从 L3 预取。预取完成后,所有需要的 KV 缓存都会加载到 GPU 中进行计算。一旦 Prefill 计算完成,系统会考虑将新生成的数据存储到 L2 或 L3 中。

HiCache Workflow

本地匹配#

本地匹配是 HiCache 工作流的第一步,传入的请求 Token 会与 HiRadixTree 进行匹配,以定位本地存储层级(L1 GPU 显存和 L2 主机内存)中缓存的 KV 数据。

匹配算法从根节点开始遍历 HiRadixTree,沿着与 Token 序列前缀匹配的子节点进行。在每个节点,传入的 Token 序列会与节点存储的 Token 序列进行比较。当 page_size > 1 时,匹配以页面(page)粒度执行,以优化内存访问模式。如果匹配在节点存储的序列内部终止,节点会自动分裂以创建一个精确的边界,从而提高未来匹配的效率。

该算法返回请求的一个连续前缀,其中第一部分位于 L1,后一部分位于 L2。

由于该过程仅需遍历本地 HiRadixTree,不涉及任何实际的数据拷贝,因此本地匹配速度极快。

从 L3 预取#

数据预取是 HiCache 的核心优化技术之一,旨在主动将 KV 缓存从 L3 存储加载到本地 L2 内存,从而降低后续操作期间的访问延迟。

预取触发条件:本地匹配后,对于在 L1 或 L2 中未找到的部分,系统会查询 L3 以获取后续连续匹配 KV 缓存的元数据。如果 L3 中命中缓存的长度超过阈值(默认:256 Token,可配置),则触发预取操作。

预取策略:HiCache 提供了三种不同的预取终止策略,以满足不同场景的需求:

  • best_effort:当 GPU 可以执行 Prefill 计算时立即终止,无需等待,适用于对延迟极其敏感的场景。

  • wait_complete:必须等待所有预取操作完成,适用于需要高缓存命中率的场景。

  • timeout:在指定时间后或完成时终止,平衡延迟和缓存命中率的需求。

预取停止后,已获取的数据将与本地数据一起用于 Prefill 计算。

对于 timeout 策略,HiCache 引入了两个配置参数,以支持对预取超时条件的精细控制:

  • prefetch_timeout_base:基础超时时间,代表与 Token 数量无关的开销(如调度和同步)。

  • prefetch_timeout_per_ki_token:每千个 Token 的增量超时时间。

超时时间计算公式为:

timeout = prefetch_timeout_base + prefetch_timeout_per_ki_token * num_token_to_fetch / 1024

数据写回#

写回机制负责将频繁访问的 KV 缓存从 L1 移动到 L2 和 L3,从而实现更大容量和更长期的存储,以及跨实例的缓存共享。

可配置的写回策略:HiCache 支持三种写回策略:

  • write_through:每次访问都立即写回到下一层级。在带宽充足时,该策略提供最强的缓存收益。

  • write_through_selective:仅在访问频率超过阈值后才写回数据。该策略仅备份热数据,减少 I/O 开销。

  • write_back:仅当数据从上一层级逐出时才写回到下一层级。该策略缓解了存储压力,适用于存储容量有限但必须最大化内存利用率的场景。

跨实例共享:当数据从 L2 写回到 L3 时,仅传输 L3 中尚不存在的数据。存储在 L3 中的 KV 缓存随后可以由集群中的所有 SGLang 实例共享(取决于 L3 后端实现),从而在相同的内存预算内显著提高缓存命中率。

多 Rank 同步#

在多 GPU 并行计算(如张量并行 TP)期间,HiCache 必须确保不同 Rank 之间状态一致。因此,关键计算步骤需要使用 all_reduce 进行状态同步。

例如,在预取期间,使用 all_reduce(op=min) 确保所有 Rank 获得相同的 L3 命中数量,防止对是否达到预取阈值的判断不一致。同样,在预取完成或终止后,再次需要 all_reduce(op=min) 以保证各 Rank 之间对成功检索到的 KV 缓存前缀长度达成共识。

数据传输优化#

零拷贝数据传输:预取和写回都涉及大量数据移动。最小化数据拷贝次数可以显著提高系统性能。HiCache 支持在将数据从 L2 内存传输到 L3 后端时直接传递内存地址和大小。

“面向批次”的数据组织:数据读写的粒度对性能有重大影响。为了解决这个问题,HiCache L3 以 pages 为粒度存储和传输 KV 缓存数据,并支持除现有 layer first 方案之外的不同数据布局,包括 page firstpage first direct。在 page firstpage first direct 布局下,属于同一页面的所有 KV 缓存数据都放置在连续内存中,允许通过零拷贝传输将其作为一个对象传递给 L3。

HiCache L2 MEM layout

然而,由于 GPU KV 计算自然地按层(layer)进行,GPU 本质上以 layer first 布局运行。当将 page first 数据从 L2 传输到 GPU 时,数据必须以“每层一个 Token”的粒度进行传输。page first direct 布局通过将页面内给定层的所有 Token 分组在一起缓解了此问题,从而允许从 L2 到 GPU 的传输在“页面-层”级别进行聚合。

CPU 到 GPU 传输优化:在 HiCache 中,将数据从 CPU 内存移动到 GPU 与从 L3 预取数据到 L2 同样关键。HiCache 为此过程采用了多种优化:

  • 计算-传输重叠:在 Prefill 阶段,当将数据从 CPU 传输到 GPU 时,HiCache 通过在计算第 N 层时并发加载第 N+1 层的 KV 缓存来实现层级重叠。这有效地隐藏了数据传输延迟。

  • GPU 辅助 I/O Kernel:在 cudaMemcpyAsync 之上,HiCache 实现了一组专门针对 CPU 和 GPU 之间 KV 缓存传输优化的 GPU 辅助 I/O Kernel。与基准方法相比,这些 Kernel 实现了高达 3 倍的传输速度。

MLA 写回优化:对于多 TP 下的 MHA(多头注意力)模型,每个 Rank 持有一个 Token KV 数据的 1/tp_size。相比之下,对于 MLA(多层注意力)模型,所有 Rank 为每个 Token 持有完整且相同的 KV 数据。HiCache 为 MLA 包含了一项专用优化:仅由一个 Rank 发起写回操作,确保数据不会在 Rank 之间冗余存储。

与 PD 分离部署模式的集成#

SGLang 通过 Mooncake TransferEngine 支持 PD(Prefill-Decode)分离部署模式(详情请参阅此文档)。在 PD 分离部署模式下,可以在 Prefill 节点和 Decode 节点上同时启用 HiCache 以优化 Prefill 性能。如果在 Decode 节点上启用,Decode 输出也将写回到 L3。

统一接口与丰富的 L3 存储后端#

HiCache 将 L3 后端的所有读、写和查询操作封装在 class HiCacheStorage(ABC) 中,暴露出一组简单且一致的接口。这种设计支持广泛的 L3 存储后端,并允许用户选择最适合其特定用例的后端。

  • Mooncake:Mooncake 是一个高性能的 LLM 推理缓存系统,利用 RDMA 和多网卡资源实现零拷贝、极速数据传输。在此处尝试 Mooncake

  • DeepSeek 3FS (HF3FS):HF3FS 是一个 Kubernetes 原生分布式存储解决方案,采用基于 Operator 的部署。在此处尝试 HF3FS

  • NIXL:NIXL 提供了一个统一 API,用于访问各种存储插件,包括但不限于 DeepSeek 的 3FS、GPU Direct Storage (GDS) 和兼容 Amazon S3 的对象存储。在此处尝试 NIXL

  • AIBrix KVCache:AIBrix KVCache 是一个生产级的 KVCache 卸载框架,支持高效的内存分层和低开销的跨引擎复用。在此处尝试 AIBrix KVCache

  • HiCacheFile:一个用于演示目的的简单基于文件的存储后端。

特别地,LMCache 作为一个面向企业级 LLM 推理的高效 KV 缓存层,提供了 HiCache 的替代方案。在此处尝试 LMCache