二级 KV 存储#
LMCache MP 模式(多进程)支持两级存储架构:
L1 (快速层) -- 默认情况下为 CPU 内存,或通过 GPUDirect Storage (cuFile) 的 NVMe 块,当设置
--gds-l1-path时,由 L1 管理器管理。所有 KV Cache 块在活跃使用期间都存放在这里。(在 GDS L1 层下,不支持字节数组 L2 适配器,因为它不暴露任何 L1 内存缓冲区。)L2 (持久性) -- 持久存储后端(基于 NIXL 或普通文件系统/原始块)。StoreController 异步将数据从 L1 推送到 L2,而 PrefetchController 在缓存未命中时将数据从 L2 加载回 L1。
数据流#
写入路径 (L1 -> L2):
vLLM 通过
STORERPC 将 KV Cache 块存储到 L1。StoreController通过 eventfd 检测新对象,并异步将存储任务提交到每个已配置的 L2 适配器。L2 适配器将数据写入其后端(例如,通过 GDS 的本地 SSD)。
读取路径 (L2 -> L1):
LOOKUPRPC 检查 L1 中的前缀缓存命中情况。对于在 L1 中未找到的键,
PrefetchController向 L2 适配器提交查找请求。如果在 L2 中找到,数据将被加载回 L1,并为待处理的
RETRIEVERPC 加读锁。
适配器类型#
LMCache 提供了几种 L2 存储后端,按介质分组在 支持的后端 下。使用 --l2-adapter 标志选择一个或多个。
多个适配器(级联)#
您可以通过重复 --l2-adapter 参数来配置多个 L2 适配器。适配器按指定的顺序使用。StoreController 将数据推送到所有配置的适配器,而 PrefetchController 在查找期间按顺序查询适配器。
# SSD (fast, smaller) + NVMe GDS (larger capacity)
--l2-adapter '{"type": "nixl_store", "backend": "POSIX", "backend_params": {"file_path": "/data/ssd/l2", "use_direct_io": "false"}, "pool_size": 64}' \
--l2-adapter '{"type": "nixl_store", "backend": "GDS", "backend_params": {"file_path": "/data/nvme/l2", "use_direct_io": "true"}, "pool_size": 128}'
存储和预取策略#
存储策略 控制键从 L1 流向 L2 的方式:哪些适配器接收每个键,以及在成功存储到 L2 后是否从 L1 删除键。 预取策略 控制键从 L2 流回 L1 的方式:当多个适配器具有相同的键时,策略决定哪个适配器加载它。
通过 CLI 选择策略:
--l2-store-policy default \
--l2-prefetch-policy default
内置策略:
标志 |
名称 |
行为 |
|---|---|---|
|
|
将所有键存储到所有适配器中。永不从 L1 中删除。 |
|
|
仅缓冲区模式。将所有键存储到所有适配器,然后立即 从 L1 中删除它们。与 |
|
|
对于每个键,选择第一个(索引最低的)拥有该键的适配器。预取的键是临时的(在读取完成后删除)。 |
|
|
与 |
预取并发性#
--l2-prefetch-max-in-flight 标志限制了 PrefetchController 在任意时刻可以并发进行的预取请求数量。较大的值会提升 L2 到 L1 的吞吐量,但也会增加在途数据对 L1 内存的压力。
标志 |
默认 |
描述 |
|---|---|---|
|
|
最大并发预取请求数。 |
仅缓冲区模式#
当 L1 仅作为写缓冲区使用(所有数据存储在 L2 中)时,使用 --l2-store-policy skip_l1 以及 --eviction-policy noop。此组合会在数据存储到 L2 后立即从 L1 中删除键,并完全禁用 LRU 逐出跟踪器,从而减少内存和 CPU 开销。
--eviction-policy noop \
--l2-store-policy skip_l1 \
--l2-prefetch-policy default
策略是可扩展的——可以通过在 storage_controllers/ 中创建文件并在导入时调用 register_store_policy() 或 register_prefetch_policy() 来添加新策略。有关详细信息,请参阅设计文档 l2_adapters/design_docs/overall.md。
序列化与反序列化(压缩 / 量化)#
每个适配器可以选择性地运行一个 serde(序列化器/反序列化器),在数据进出 L2 时进行转换——例如,针对磁盘后端的 fp8 量化,或针对远程适配器的加密。有关详细信息和配置,请参见 KV Cache Compression。
逐出#
LMCache 支持在两个存储层次上进行逐出,以便每个层次都可以在固定的容量预算内运行。
L1 逐出#
L1 逐出运行一个后台线程,监控整体 L1 内存使用情况。当使用量超过 trigger_watermark 时,逐出策略会逐出一部分最近最少使用的键。
命令行标志:
标志 |
默认 |
描述 |
|---|---|---|
|
(必需) |
策略名称: |
|
|
触发逐出的 L1 使用比例 [0, 1]。 |
|
|
每个周期逐出的当前分配的 L1 内存的比例。 |
示例:
--eviction-policy LRU \
--eviction-trigger-watermark 0.8 \
--eviction-ratio 0.2
L2 逐出#
L2 逐出是按适配器独立配置且可选启用的。每个适配器可以通过在其 --l2-adapter JSON 规范中添加 "eviction" 子对象来独立声明逐出策略。没有 "eviction" 键的适配器不启用逐出控制器。
当为适配器启用 L2 逐出时,一个专用的后台线程会监控该适配器的 get_usage() 值。一旦使用量超过 trigger_watermark,该策略将逐出键,直到使用量降低 eviction_ratio。
``"eviction"`` 子对象字段:
字段 |
默认 |
描述 |
|---|---|---|
|
(必需) |
策略名称: |
|
|
触发逐出的适配器使用比例 [0, 1]。 |
|
|
每个周期逐出的已用容量的比例。 |
示例 — 使用 LRU 逐出的 nixl_store:
--l2-adapter '{
"type": "nixl_store",
"backend": "POSIX",
"backend_params": {"file_path": "/data/lmcache/l2", "use_direct_io": "false"},
"pool_size": 128,
"eviction": {
"eviction_policy": "LRU",
"trigger_watermark": 0.8,
"eviction_ratio": 0.2
}
}'
适配器支持:
适配器 |
L2 逐出支持 |
|---|---|
|
完全支持。 |
|
完全支持。 |
|
完全支持。适用于在无真实存储硬件的情况下测试逐出行为。 |
|
完全支持共享/全局逐出。 |
|
|
|
|
|
完全支持。 |
|
不支持逐出(原生连接器适配器)。 |
|
不支持逐出( |
原生连接器 |
不支持逐出。 |
备注
每个 L2 适配器实例都有自己独立的逐出控制器和策略。两个相同类型的适配器可以有不同的水位线或策略。
L1 + L2 逐出示例#
--l1-size-gb 100 \
--eviction-policy LRU \
--eviction-trigger-watermark 0.8 \
--eviction-ratio 0.2 \
--l2-adapter '{
"type": "nixl_store",
"backend": "GDS",
"backend_params": {"file_path": "/data/nvme/l2", "use_direct_io": "true"},
"pool_size": 256,
"eviction": {
"eviction_policy": "LRU",
"trigger_watermark": 0.9,
"eviction_ratio": 0.1
}
}'
在此设置中:
当 L1 的内存使用达到 80% 时,它会逐出内存,每个周期回收 20% 的分配内存。
L2 (NIXL/GDS) 在存储池中当 90% 的池槽被占用时进行逐出,每个周期回收 10%。
两个层级使用独立的 LRU 策略,因此每个层级逐出其自身的最近最少使用的键。
验证 L2 存储#
设置 LMCACHE_LOG_LEVEL=DEBUG 以在服务器日志中查看 L2 活动:
LMCACHE_LOG_LEVEL=DEBUG lmcache server \
--l1-size-gb 100 --eviction-policy LRU \
--l2-adapter '{"type": "nixl_store", "backend": "POSIX", "backend_params": {"file_path": "/data/lmcache/l2", "use_direct_io": "false"}, "pool_size": 64}'
L2 运行时的预期日志消息:
LMCache DEBUG: Submitted store task ...
LMCache DEBUG: L2 store task N completed ...
LMCache DEBUG: Prefetch request submitted: X total keys, Y L1 prefix hits, Z remaining for L2