Repository navigation
Storage and Eviction.zh
默认 1.5 TB 刻意多留一些:更多 prefill 历史保留在 NVMe 上, 因此重新打开对话时可节省首次加载的重算时间和能耗。
两个不同的上限不要混淆:
-
单个对话的 token 上限(约 1M) —— 受模型上下文窗口限制
(模型
config.json中的max_position_embeddings),而非本缓存。 -
缓存在磁盘上保留多少历史 —— 仅受
KVP_CACHE_BUDGET_GB限制。 这里没有 token 上限:超出 100 万 token 后,有意义的只是单个上下文的 前缀复用边界(见「架构」页),而非 kv-persistence 本身的限制。
| 限制 | 默认值 | 执行机制 |
|---|---|---|
| 硬上限 |
KVP_CACHE_BUDGET_GB=1500 GB(1.5 TB) |
L2 适配器的 max_capacity_gb —— 超出的写入被拒绝 |
| 物理上限 | 共享 VM 磁盘(同盘还有 Docker 与模型权重) 设备的容量(预检确认) | 通过 KVP_CACHE_DEVICE 检查可用空间 |
| 软阈值 | 预算的 0.9 |
trigger_watermark —— 自动淘汰从此开始 |
| 回收目标 | 一个周期后约为预算的 0.81(0.9 × 已用容量的 0.9) |
eviction_ratio 为已用容量的 0.1 |
| 命名空间配额 | 可选,quotas.json
|
kvp-admin quota 策略层 |
预算仅涵盖持久 KV 缓存数据。模型权重、系统和其他文件不在此核算, kv-persistence 的任何脚本都不会触碰它们。
-
KVP_CACHE_ROOT下每个 KV 块对象一个文件(临时文件 + 原子重命名)。适配器 nixl_store_dynamic 写入扁平的*.bin文件 —— 没有按命名空间的子目录; 身份命名空间编码在文件名前缀中(净化后的模型名)。 - 没有单独的磁盘索引 —— 确定性文件名本身就是索引,启动时惰性重建。
used_bytes是真实缓存文件大小之和(加上遗留的kvp-*目录,如有); 没有单独的“索引开销”条目。 -
写入的准入由 LMCache 适配器通过
max_capacity_gb(L2 硬上限)强制执行: 超出的写入被拒绝。仓库自带的BudgetLedger为报告和规划计算相同数字,但 未接入生产写入路径 —— 不要依赖它来拦截写入。 -
重启后的核算(NEEDS TESTING,评审修正):缓存服务重启后,内存索引按
文件被访问惰性重建 —— 启动时并不扫描磁盘,因此真实文件仍在,而
used_bytes从零开始。在硬件运行确认固定版本会把恢复的尺寸回填到get_usage()之前,不要假设重启前的文件计入max_capacity_gb。必须的 测试:填满缓存 → 重启缓存服务 → 继续写入 → 验证used_bytes增长且保持 受限。 - 崩溃残余:被强杀(SIGKILL、OOM、断电)后,适配器可能留下
<final>.bin.tmp.<uuid>—— 未能重命名的文件。它们不属于缓存索引 (绝不会作为 KV 返回),但确实占用磁盘,因此status单独报告它们 (tmp_files、tmp_files_bytes)。删除:purge --tmp --yes(保留所有真实 缓存文件)或purge --all --yes。进行中的写入由活跃写入注册表 (kvpersist.tmp_guard)保护:每次写入在 tmp 文件出现前登记标记、原子重命名后 清除。标记是否活跃由写入进程是否存活判定(按(pid, 进程启动时间)核对, PID 复用无法蒙混)—— 已登记的写入无论持续多久都不会被删除(超过一小时的写入 同样受保护;判据是存活,而非年龄)。若无法判定存活(无 /proc、标记不可读) 则保留标记:宁可多留一个 tmp 文件,也不删除活跃写入的文件。写入者确已退出的 标记在一小时后清扫。对于不用注册表的写入者,额外以 15 分钟 mtime 窗口兜底: 近期 tmp 文件一律跳过。 随时查看报告:
bash scripts/budget-report.sh status字段:used_bytes、free_bytes、usage_fraction、soft_pressure、
per_namespace_bytes、evictions、reclaimed_bytes、rejected_writes、
tmp_files、tmp_files_bytes。
- 后台线程监视
get_usage()。超过trigger_watermark后,策略淘汰直至 用量下降eviction_ratio。 - 生产环境中真正运行的策略是 LRU(适配器原生,位于 LMCache 内部), 作用于已存块键。长且高频复用的历史仅受 LRU 新近度规则保护 —— 目前没有 额外的基于价值的防护。
- 基于价值的排序(
implementation/kvpersist/retention.py)是一个独立的 规划机制,未接入生产淘汰路径:它按新鲜度(24 小时衰减)、复用次数、 前缀长度和存储成本排序,并由单元测试覆盖 —— 但今天删除块的是 LMCache 自己的 LRU。不要指望长而有价值的前缀因它而幸存。 - 存储压力告警:状态报告中
soft_pressure: true;启动器在文件系统空闲 空间低于预算时也会在启动时告警。
bash scripts/budget-report.sh status # 查看真实命名空间名称
bash scripts/budget-report.sh purge --namespace=NAME --yes
bash scripts/budget-report.sh purge --all --yes
bash scripts/budget-report.sh purge --tmp --yes # 仅崩溃残余缓存布局是扁平的:*.bin 文件直接位于 KVP_CACHE_ROOT 下,命名空间是
文件名前缀(净化后的模型名,例如 --models--MyModel--a84fadf1)。真实名称
请从 status → namespaces 获取,不要猜测。由于真实名称通常以 - 开头,
请用 --namespace=NAME 形式或位置参数(purge --namespace=NAME --yes /
purge NAME --yes),不要用 --namespace NAME。匹配是精确的(不做子串
匹配)。需要 --yes。删除范围严格限定在 KVP_CACHE_ROOT 内的缓存文件 ——
其他路径一律拒绝并返回退出码 2。启动绝不会自动清空缓存。
LMCache 以内容派生键存储块:相同块内容 ⇒ 相同键 ⇒ 一个存储对象,而非两个。 具有相同稳定前缀的两个会话在物理上共享对象:
- 同一兼容性命名空间 ⇒ 共享键;不同命名空间永不冲突。
- 淘汰一个条目只删除其键;共享对象保留到没有任何其他键指向它为止。 若共享对象最终被淘汰(LRU 认为该键久未使用),并发正确性仍然保持: 缺失的查询只是重新计算 —— 活性上的取舍,绝非正确性问题。
- 去重节省的存储可通过
used_bytes前后对比衡量:content-key 模型由tests/unit/test_dedup_model.py覆盖(模拟,无 LMCache),真实 L2 文件由tests/integration/test_l2_dedup_real.py对活跃lmcache server计数 (共享前缀块只写入一次)。
缓存布局是扁平的:KVP_CACHE_ROOT 下每个 KV 块一个 *.bin 文件,
身份命名空间是文件名前缀(净化后的模型名,例如 --models--MyModel--a84fadf1)。
版本变更会创建新命名空间(新的文件名前缀);旧命名空间绝不自动删除
(策略已记录:清理是管理员的显式决定):
bash scripts/budget-report.sh status # 列出命名空间名称("namespaces" 字段)
bash scripts/budget-report.sh purge --namespace=NAME --yes请使用 --namespace=NAME(= 形式)或位置参数 —— 真实命名空间名通常**以 -
开头,--namespace NAME 会被解析成选项。
- 在
configs/generic/cache.env.local中修改KVP_CACHE_BUDGET_GB。 -
bash scripts/stop-cache.sh && bash scripts/start-cache.sh(数据保留)。 - 用
bash scripts/budget-report.sh status确认。