# 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_enabledtrue 时,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_enabledtrue 时才会启用。该 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 支持 migrateevict 模式。它不支持 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 后端,它会自动启用。无需额外的功能标志。

检索流程是:

  1. 保留一组可读的 DAX 块。

  2. LocalCPUBackend 分配 CPU 恢复缓冲区。

  3. 将 DAX 数据复制到后端拥有的固定暂存块中的合并区域。

  4. 从暂存块复制到最终的 CPU MemoryObj 输出。

  5. 通过正常的 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_workers

  • dax.retrieve_staging_slab_bytes 至少为 dax.restore_max_regions * full_chunk_size,如果批量恢复较大,则向上调整。

如果提取吞吐量较低,首先增加块大小,然后一起增加工作线程和区域数量。如果 CPU 压力较高,减少 dax.restore_workersdax.restore_max_regions

运行时要求#

  • extra_config['dax.device_path'] 是必需的,并且必须指向一个可读写的 DAX 设备。

  • 该进程必须对 DAX 设备具有读写访问权限(例如,通过适当的权限或组成员资格)。

  • LocalCPUBackend 必须启用,因为 DAX 读取返回的是 CPU 支持的内存对象。

验证和当前限制#

  • 当前张量并行性仅限于 TP=1 (metadata.world_size == 1)。

  • 仅支持单张量块布局。多张量放置请求将被拒绝。

  • 批量恢复使用后端拥有的检索暂存块和持久恢复执行器。可以通过 dax.restore_workersdax.restore_max_regionsdax.retrieve_staging_slab_bytes 调整暂存块和区域数量。

  • 阻塞批量恢复保留了位置输出语义,而异步批量恢复仅返回连续的命中前缀。