KVCacheMemory: KV cache记忆(激活记忆)
KVCacheMemory 是MemOS中用于存储和管理KV cache的专用记忆模块,主要用于加速大语言模型(LLMs)推理并支持有效的上下文复用。它作为激活记忆有助于对于会话式和生成式人工智能系统。KV Cache记忆使用案例
在MemOS中,KV Cache最适合存储语义稳定且经常复用的背景信息,例如:
- 常见问题(FAQs)或特定领域知识
- 先前的对话历史
这些稳定的明文记忆项由MemScheduler模块自动识别和管理。一旦被选中,它们就会被提前转换成KV格式的表示(KVCacheItem)。这个预计算步骤以可复用的格式存储记忆的激活状态(键值对张量),允许它们在推理期间注入到模型的注意力缓存中
一旦进行转换,这些KV记忆就可以跨查询复用,而不需要对原始内容重新编码。这减少了处理和存储大量文本的计算开销,使其成为需要快速响应时间和高吞吐量的应用程序的理想选择。
为什么是KV Cache记忆
将MemScheduler与KV Cache记忆集成可以实现显著的性能优化,特别是在LLM推理的预填充阶段。
无KV Cache记忆
- 每个新查询都被添加到完整的提示模板中,包括背景知识。
- 模型必须在整个序列上重新计算token嵌入和注意力——即使是未更改的记忆。
有KV Cache记忆
- 背景知识以键值对张量的形式缓存一次。
- 对于每个查询,只对新用户输入(查询token)进行编码。
- 之前缓存的KV被直接注入到注意力机制中。
好处
这种分离减少了预填充阶段的冗余计算,从而导致:
- 跳过背景知识的重复编码
- 更快的查询token和缓存记忆之间的注意力计算
- 降低首次token时间(Time To First Token, TTFT) 生成过程中的延迟
这种优化在以下方面特别有价值:
- 多回合聊天机器人交互
- 检索增强生成或上下文增强生成(RAG, CAG)
- 在固定文档或FAQ风格记忆上操作的助理
KV Cache记忆加速评估
为了验证基于KV的记忆注入对性能的影响,我们进行了一组在MemOS中模拟真实记忆复用的对照实验。
实验建立
在典型的使用中,MemScheduler模块持续跟踪交互模式,并将高频、稳定的明文记忆提升为KV格式。这些KV记忆作为激活缓存加载到GPU内存中,并在推理过程中重复使用。
评估比较两种记忆策略:
- 基于提示的注入: 背景知识被作为原始文本添加
- KV Cache注入: 记忆被直接注入到模型的注意力缓存
我们对这些策略进行了测试:
- 三种文本长度: 短文本, 中等长度文本和长文本
- 三种查询类型: 短查询, 中等查询和长查询
主要指标是首次token时间(TTFT),这是响应式生成的关键延迟指标。
实验结果
下表显示了跨三个模型的结果(Qwen3-8B, Qwen3-32B, Qwen2.5-72B).KV Cache注入下的TTFT始终低于基于提示的注入,而两种策略的输出token保持一致.
Build (s)是指将记忆转换为KV格式的一次性预处理成本,分摊到多个查询中.| Model | Ctx | CtxTok | Qry | QryTok | Build (s) | KV TTFT (s) | Dir TTFT (s) | Speedup (%) |
|---|---|---|---|---|---|---|---|---|
| Qwen3-8B | long | 6064 | long | 952.7 | 0.92 | 0.50 | 2.37 | 79.1 |
| medium | 302.7 | 0.93 | 0.19 | 2.16 | 91.1 | |||
| short | 167 | 0.93 | 0.12 | 2.04 | 94.2 | |||
| medium | 2773 | long | 952.7 | 0.41 | 0.43 | 1.22 | 64.6 | |
| medium | 302.7 | 0.41 | 0.16 | 1.08 | 85.1 | |||
| short | 167 | 0.43 | 0.10 | 0.95 | 89.7 | |||
| short | 583 | long | 952.7 | 0.12 | 0.39 | 0.51 | 23.0 | |
| medium | 302.7 | 0.12 | 0.14 | 0.32 | 55.6 | |||
| short | 167 | 0.12 | 0.08 | 0.29 | 71.3 | |||
| Qwen3-32B | long | 6064 | long | 952.7 | 0.71 | 0.31 | 1.09 | 71.4 |
| medium | 302.7 | 0.71 | 0.15 | 0.98 | 84.3 | |||
| short | 167 | 0.71 | 0.11 | 0.96 | 88.8 | |||
| medium | 2773 | long | 952.7 | 0.31 | 0.24 | 0.56 | 56.9 | |
| medium | 302.7 | 0.31 | 0.12 | 0.47 | 75.1 | |||
| short | 167 | 0.31 | 0.08 | 0.44 | 81.2 | |||
| short | 583 | long | 952.7 | 0.09 | 0.20 | 0.24 | 18.6 | |
| medium | 302.7 | 0.09 | 0.09 | 0.15 | 39.6 | |||
| short | 167 | 0.09 | 0.07 | 0.14 | 53.5 | |||
| Qwen2.5-72B | long | 6064 | long | 952.7 | 1.26 | 0.48 | 2.04 | 76.4 |
| medium | 302.7 | 1.26 | 0.23 | 1.82 | 87.2 | |||
| short | 167 | 1.27 | 0.15 | 1.79 | 91.4 | |||
| medium | 2773 | long | 952.7 | 0.58 | 0.39 | 1.05 | 62.7 | |
| medium | 302.7 | 0.58 | 0.18 | 0.89 | 79.2 | |||
| short | 167 | 0.71 | 0.23 | 0.82 | 71.6 | |||
| short | 583 | long | 952.7 | 0.16 | 0.33 | 0.43 | 23.8 | |
| medium | 302.7 | 0.16 | 0.15 | 0.27 | 43.2 | |||
| short | 167 | 0.16 | 0.10 | 0.25 | 60.5 |
基于 vLLM 的性能表现
MemOS 现在支持使用 vLLM 管理激活内存。为了评估KV Cache预存不同长度的前缀文本带来的影响,我们在一个配备 8 张 H800 80GB GPU(112 vCPU,1920 GiB 内存)的系统,以及一个配备 8张 RTX4090-24G-PCIe(112 vCPU,960 GiB 内存) 的系统上分别进行了性能测试。评估覆盖了当前两种核心模型:Qwen3-32B 和 Qwen2.5-72B。
基准测试在一系列记忆和上下文长度组合下运行,以模拟各种激活内存场景:
- 记忆文本长度(tokens):500、1000、2000
- 上下文文本长度(tokens):500、1000、2000、4000
下表总结了基准测试结果。
Qwen2.5-72B
- On 4090(2 Nodes 16 GPUs)
| mem tks | prompt tks | TTFT (without cache, ms) | TTFT (With cache, ms) | TTFT Speedup (%) | Abs Dis(ms) |
|---|---|---|---|---|---|
| 0.5k | 0.5k | 1787.21 | 851.47 | 52.358% | 935.74 |
| 0.5k | 1k | 2506.26 | 1290.68 | 48.502% | 1215.58 |
| 0.5k | 2k | 3843.48 | 2897.97 | 24.600% | 945.51 |
| 0.5k | 4k | 6078.01 | 5200.86 | 14.432% | 877.15 |
| 1k | 0.5k | 2274.61 | 920.16 | 59.546% | 1354.45 |
| 1k | 1k | 2907.17 | 1407.65 | 51.580% | 1499.52 |
| 1k | 2k | 4278.53 | 2916.47 | 31.835% | 1362.06 |
| 1k | 4k | 6897.99 | 5218.94 | 24.341% | 1679.05 |
| 2k | 0.5k | 3460.12 | 782.73 | 77.379% | 2677.39 |
| 2k | 1k | 4443.34 | 1491.24 | 66.439% | 2952.10 |
| 2k | 2k | 5733.14 | 2758.48 | 51.885% | 2974.66 |
| 2k | 4k | 8152.76 | 5627.41 | 30.975% | 2525.35 |
- On H800(4 GPUs)
| mem tks | prompt tks | TTFT (without cache, ms) | TTFT (With cache, ms) | TTFT Speedup (%) | Abs Dis(ms) |
|---|---|---|---|---|---|
| 0.5k | 0.5k | 51.65 | 52.17 | -1.007% | -0.52 |
| 0.5k | 1k | 55.70 | 57.03 | -2.388% | -1.33 |
| 0.5k | 2k | 74.23 | 78.56 | -5.833% | -4.33 |
| 0.5k | 4k | 77.56 | 77.45 | 0.142% | 0.11 |
| 1k | 0.5k | 55.90 | 55.73 | 0.304% | 0.17 |
| 1k | 1k | 55.35 | 52.89 | 4.444% | 2.46 |
| 1k | 2k | 80.14 | 73.82 | 7.886% | 6.32 |
| 1k | 4k | 82.83 | 73.51 | 11.252% | 9.32 |
| 2k | 0.5k | 75.82 | 71.31 | 5.948% | 4.51 |
| 2k | 1k | 80.60 | 78.71 | 2.345% | 1.89 |
| 2k | 2k | 83.91 | 78.60 | 6.328% | 5.31 |
| 2k | 4k | 99.15 | 80.12 | 19.193% | 19.03 |
Qwen3-32B
- On 4090(1 Nodes 8 GPUs)
| mem tks | prompt tks | TTFT (without cache, ms) | TTFT (With cache, ms) | TTFT Speedup (%) | Abs Dis(ms) |
|---|---|---|---|---|---|
| 0.5k | 0.5k | 288.72 | 139.29 | 51.756% | 149.43 |
| 0.5k | 1k | 428.72 | 245.85 | 42.655% | 182.87 |
| 0.5k | 2k | 683.65 | 538.59 | 21.218% | 145.06 |
| 0.5k | 4k | 1170.48 | 986.94 | 15.681% | 183.54 |
| 1k | 0.5k | 409.83 | 137.96 | 66.337% | 271.87 |
| 1k | 1k | 507.95 | 262.21 | 48.379% | 245.74 |
| 1k | 2k | 743.48 | 539.71 | 27.408% | 203.77 |
| 1k | 4k | 1325.34 | 1038.59 | 21.636% | 286.75 |
| 2k | 0.5k | 686.01 | 147.34 | 78.522% | 538.67 |
| 2k | 1k | 762.96 | 246.22 | 67.728% | 516.74 |
| 2k | 2k | 1083.93 | 498.05 | 54.051% | 585.88 |
| 2k | 4k | 1435.39 | 1053.31 | 26.619% | 382.08 |
- On H800(2 GPUs)
| mem tks | prompt tks | TTFT (without cache, ms) | TTFT (With cache, ms) | TTFT Speedup (%) | Abs Dis(ms) |
|---|---|---|---|---|---|
| 0.5k | 0.5k | 161.18 | 97.61 | 39.440% | 63.57 |
| 0.5k | 1k | 164.00 | 121.39 | 25.982% | 42.61 |
| 0.5k | 2k | 257.34 | 215.20 | 16.375% | 42.14 |
| 0.5k | 4k | 365.14 | 317.95 | 12.924% | 47.19 |
| 1k | 0.5k | 169.45 | 100.52 | 40.679% | 68.93 |
| 1k | 1k | 180.91 | 128.25 | 29.108% | 52.66 |
| 1k | 2k | 271.69 | 210.00 | 22.706% | 61.69 |
| 1k | 4k | 389.30 | 314.64 | 19.178% | 74.66 |
| 2k | 0.5k | 251.43 | 130.92 | 47.930% | 120.51 |
| 2k | 1k | 275.81 | 159.60 | 42.134% | 116.21 |
| 2k | 2k | 331.11 | 218.17 | 34.110% | 112.94 |
| 2k | 4k | 451.06 | 334.80 | 25.775% | 116.26 |
结果清楚地表明,集成 vLLM 的 KV 缓存重用功能为 MemOS 带来了革命性的性能提升。
KV Cache的记忆结构
通过KVCacheMemory实现基于KV的记忆复用,在保持相同输出的同时,大大减少了模型大小和查询类型之间的延迟。通过将可复用记忆从明文提示转移到预先计算的KV Cache,MemOS消除了冗余的上下文编码,并实现了更快的响应时间,特别是在实时的、记忆增强的LLM应用程序中。
每个缓存被存储为一个KVCacheItem:
| 字段 | 类型 | 描述 |
|---|---|---|
kv_cache_id | str | 缓存中的唯一ID(UUID) |
kv_cache | DynamicCache | 实际的KV Cache(transformers) |
metadata | dict | 元数据 (源, 抽取时间等.) |
API总结 (KVCacheMemory)
初始化
KVCacheMemory(config: KVCacheMemoryConfig)
核心方法
| 方法 | 描述 |
|---|---|
extract(text) | 使用LLM从输入文本中提取KV Cache |
add(memories) | 添加一个或多个KVCacheItem到记忆中 |
get(memory_id) | 根据ID获取单个缓存 |
get_by_ids(ids) | 根据IDs获取多个缓存 |
get_all() | 返回所有存储的缓存 |
get_cache(cache_ids) | 从多个IDs合并并返回组合缓存 |
delete(ids) | 通过IDs删除缓存 |
delete_all() | 删除所有缓存 |
dump(dir) | 将所有缓存序列化到目录中的pickle文件 |
load(dir) | 从目录中的pickle文件加载缓存 |
from_textual_memory(mem) | 将TextualMemoryItem 转换为 KVCacheItem |
当调用dump(dir), 系统写到:
<dir>/<config.memory_filename>
该文件包含所有KV Cache的pickle字典,可以使用load(dir)重新加载。
如何使用
from memos.configs.memory import KVCacheMemoryConfig
from memos.memories.activation.kv import KVCacheMemory
config = KVCacheMemoryConfig(
extractor_llm={
"backend": "huggingface",
"config": {"model_name_or_path": "Qwen/Qwen3-1.7B"}
}
)
mem = KVCacheMemory(config)
# Extract and add a cache
cache_item = mem.extract("The capital of France is Paris.")
mem.add([cache_item])
# Retrieve and merge caches
merged_cache = mem.get_cache([cache_item.kv_cache_id])
# Save/load
mem.dump("tmp/act_mem")
mem.load("tmp/act_mem")
开发者注意事项
- 使用HuggingFace
DynamicCache高效的键值存储 - 基于pickle的序列化,用于快速加载/保存
/tests中的集成测试涵盖了所有方法。