# 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/l2/status
curl -X POST http://127.0.0.1:9000/reconfigure/dax/l2/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/l2/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/l2/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 是同步完成的。

An add returns 409 Conflict when L1 or another DAX L2 adapter already maps the same physical device, including through an alias.

热插拔操作默认是安全的。删除或缩小会删除外部锁定或借用的槽位时,除非设置了 force,否则返回 409 Conflict。没有活动目标容量的迁移返回 507 Insufficient Storage。扩展的调整大小保留内存中的键索引,并且不移动 KV 负载。缩小的调整大小绝不会静默丢弃键;新槽范围外的条目必须先迁移,否则请求将失败。

硬件验证流程#

Use the same Qwen 8B or 14B long-context workload before and after a runtime capacity change. Without hotplug support, /reconfigure/dax/l2/status and /reconfigure/dax/l2/add are not available; changing the DAX device set requires restarting LMCache with a new --l2-adapter value, which drops the volatile DAX key index.

export MODEL=Qwen/Qwen3-8B  # or a local Qwen 8B/14B checkpoint
curl http://127.0.0.1:9000/reconfigure/dax/l2/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/l2/add \
  -H 'Content-Type: application/json' \
  -d '{"device_path": "/dev/daxX.X", "size": "100GiB"}'
curl http://127.0.0.1:9000/reconfigure/dax/l2/status

记录这些字段以进行比较:

  • total_capacity_bytes before and after /reconfigure/dax/l2/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_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 调整暂存块和区域数量。

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