# SPDX-License-Identifier: Apache-2.0
设备-DAX (/dev/dax)#
概述#
DAX 存储插件通过 mmap(MAP_SHARED) 映射 /dev/dax 设备,并将映射区域用作 KV Cache 块的固定大小区域。典型的 /dev/dax 设备包括持久内存、CXL 附加内存和其他字节可寻址内存设备。
存储在 DAX 设备上的数据可能在进程重启后仍然存在,但不保证其持久性。
KV Cache 数据作为后端存储流程的一部分存储在 DAX 区域。读取操作会将数据复制回 CPU 内存对象。
配置#
local_cpu: true
max_local_cpu_size: 80
storage_plugins: ["dax"]
extra_config:
storage_plugin.dax.module_path: lmcache.v1.storage_backend.plugins.dax_backend
storage_plugin.dax.class_name: DaxBackend
dax.device_path: "/dev/dax1.0"
dax.max_dax_size: 100
dax.restore_workers: 8
dax.restore_max_regions: 8
dax.retrieve_staging_slab_bytes: 268435456
多进程模式#
在 LMCache 多进程模式下,Device-DAX 被配置为名为 dax 的内置 L2 适配器。MP 适配器使用正常的 L2 适配器 submit -> event fd -> query 合同;不需要 vLLM 连接器协议的更改。
lmcache server \
--l1-size-gb 80 \
--eviction-policy LRU \
--l2-adapter '{
"type": "dax",
"device_path": "/dev/dax1.0",
"max_dax_size_gb": 100,
"slot_bytes": 268435456,
"num_store_workers": 1,
"num_lookup_workers": 1,
"num_load_workers": 4
}'
旧版的单设备 --l2-adapter JSON 接受以下字段:
device_path: 必须可读写的 DAX 设备路径。max_dax_size_gb: 必需的映射大小,以 GiB 为单位。当可以通过fstat确定容量时,值必须适合设备容量。slot_bytes: 所需的固定槽大小(以字节为单位)。它必须足够大,以容纳一个完整的 LMCache 块。num_store_workers: 可选的存储工作线程数量,默认值为1。num_lookup_workers: 可选查找工作线程数量,默认为1。num_load_workers: 可选的加载工作线程数量,默认min(4, os.cpu_count())。persist_enabled: 被通用 MP L2 解析接受,但在此版本中被dax忽略。
运行时热插拔使用多设备形式。当 hotplug_enabled 为 true 时,devices 列表也可以为空。
lmcache server \
--l1-size-gb 80 \
--eviction-policy LRU \
--l2-adapter '{
"type": "dax",
"devices": [
{"device_path": "/dev/daxX.X", "max_dax_size_gb": 100},
{"device_path": "/dev/daxY.Y", "max_dax_size_gb": 100}
],
"slot_bytes": 268435456,
"hotplug_enabled": true,
"num_store_workers": 1,
"num_lookup_workers": 1,
"num_load_workers": 4
}'
MP DAX 在内存中存储不透明的 ObjectKey 值,并且在此版本中仅为易失性。关闭并重新打开同一路径上的服务器时,索引将为空,因此之前写入的字节在重启后无法被发现。
MP DAX 每个 LMCache 服务器使用一个稳定的适配器外观。该外观拥有稳定的事件文件描述符和工作池,运行时的添加/删除/调整大小仅更改该外观后面的映射 DAX 核心。它不添加内核级 CXL 或 DAX 重新配置、每个 TP 的 DAX 分区、设备上的元数据或重启恢复。容量核算和逐出基于插槽:即使存储对象的有效负载小于 slot_bytes,它也占用一个插槽。
运行时热插拔 API#
运行时热插拔在 hotplug_enabled 为 true 时才会启用。该 API 仅更改 LMCache 的运行时映射和元数据;/dev/dax* 设备必须已经存在,并且 LMCache 服务器进程必须具有可读写权限。运行时端点通过 StorageManager 的通用 L2 适配器重新配置接口实现,该接口将后端、操作名称和适配器特定有效负载路由到所选适配器。DAX 拥有路径、模式、迁移和调整大小的语义;通用接口可被其他适配器(如 P2P)重用。使用 JSON 主体,因为 DAX 路径包含斜杠:
curl http://127.0.0.1:9000/reconfigure/dax/status
curl -X POST http://127.0.0.1:9000/reconfigure/dax/add \
-H 'Content-Type: application/json' \
-d '{"device_path": "/dev/daxX.X", "size": "100GiB"}'
curl -X POST http://127.0.0.1:9000/reconfigure/dax/remove \
-H 'Content-Type: application/json' \
-d '{"device_path": "/dev/daxX.X", "mode": "migrate"}'
curl -X POST http://127.0.0.1:9000/reconfigure/dax/resize \
-H 'Content-Type: application/json' \
-d '{"device_path": "/dev/daxX.X", "size": "200GiB"}'
size 是添加和调整大小所必需的。使用整数字节数或字符串,例如 "100GiB"。remove 支持以下模式:
migrate: 在关闭源设备之前,将 DAX 驻留的 KV 移动到其他活动的 DAX 设备。evict: 删除源设备上的 DAX 驻留 KV。这对 DAX 层是破坏性的。drain: 停止对源设备的新写入,并保持现有的 KV 可读,直到被逐出或服务器关闭。
resize 支持 migrate 和 evict 模式。它不支持 drain,因为 resize 是同步完成的。
热插拔操作默认是安全的。删除或缩小会删除外部锁定或借用的槽位时,除非设置了 force,否则返回 409 Conflict。没有活动目标容量的迁移返回 507 Insufficient Storage。扩展的调整大小保留内存中的键索引,并且不移动 KV 负载。缩小的调整大小绝不会静默丢弃键;新槽范围外的条目必须先迁移,否则请求将失败。
硬件验证流程#
在运行时容量更改之前和之后使用相同的 Qwen 8B 或 14B 长上下文工作负载。如果没有热插拔支持,/reconfigure/dax/status 和 /reconfigure/dax/add 将不可用;更改 DAX 设备集需要使用新的 --l2-adapter 值重新启动 LMCache,这将丢弃易失性的 DAX 密钥索引。
export MODEL=Qwen/Qwen3-8B # or a local Qwen 8B/14B checkpoint
curl http://127.0.0.1:9000/reconfigure/dax/status
python benchmarks/long_doc_qa/long_doc_qa.py \
--model "$MODEL" --num-documents 1 --document-length 1024 \
--output-len 16 --repeat-count 2 --repeat-mode tile \
--completions --host 127.0.0.1 --port 8000 --json-output
curl -X POST http://127.0.0.1:9000/reconfigure/dax/add \
-H 'Content-Type: application/json' \
-d '{"device_path": "/dev/daxX.X", "size": "100GiB"}'
curl http://127.0.0.1:9000/reconfigure/dax/status
记录这些字段以进行比较:
total_capacity_bytes在/reconfigure/dax/add之前和之后。total_used_bytes在 Qwen 工作负载运行时。是否需要重启 LMCache。
在容量变化后,是否仍然可以检索到相同的缓存提示。
使用批量恢复路径#
当前的 DAX 优化是一个分阶段的批量恢复路径,用于检索。只要配置了 DAX 后端,它会自动启用。无需额外的功能标志。
检索流程是:
保留一组可读的 DAX 块。
从
LocalCPUBackend分配 CPU 恢复缓冲区。将 DAX 数据复制到后端拥有的固定暂存块中的合并区域。
从暂存块复制到最终的 CPU
MemoryObj输出。通过正常的 GPU 连接器路径上传这些 CPU 输出。
存储流程保持不变:KV 数据仍然通过 CPU 内存进行暂存,然后写入 DAX 区域。
新的 DAX 调优控制批量恢复路径的旋钮:
dax.restore_workers: 用于并行执行恢复区域的持久工作线程数量。dax.restore_max_regions: 一次波次中最大恢复区域的数量。较大的值会增加并行性,但也会增加 slab 空间的需求。dax.retrieve_staging_slab_bytes: 可重用的固定检索块的总大小(以字节为单位)。这必须足够大,以容纳每个配置的恢复区域的一个完整块。
对于第一次尝试,从以下开始:
dax.restore_workers等于您希望用于 DAX 恢复的 CPU 工作线程数量dax.restore_max_regions等于dax.restore_workersdax.retrieve_staging_slab_bytes至少为dax.restore_max_regions * full_chunk_size,如果批量恢复较大,则向上调整。
如果提取吞吐量较低,首先增加块大小,然后一起增加工作线程和区域数量。如果 CPU 压力较高,减少 dax.restore_workers 和 dax.restore_max_regions。
运行时要求#
extra_config['dax.device_path']是必需的,并且必须指向一个可读写的 DAX 设备。该进程必须对 DAX 设备具有读写访问权限(例如,通过适当的权限或组成员资格)。
LocalCPUBackend必须启用,因为 DAX 读取返回的是 CPU 支持的内存对象。
验证和当前限制#
当前张量并行性仅限于 TP=1 (
metadata.world_size == 1)。仅支持单张量块布局。多张量放置请求将被拒绝。
批量恢复使用后端拥有的检索暂存块和持久恢复执行器。可以通过
dax.restore_workers、dax.restore_max_regions和dax.retrieve_staging_slab_bytes调整暂存块和区域数量。阻塞批量恢复保留了位置输出语义,而异步批量恢复仅返回连续的命中前缀。