使用 LMCache CacheBlend 进行 Multi-Doc QA 基准测试:完整指南与原理剖析
【免费下载链接】LMCacheLMCache: Supercharge Your LLM with the Fastest KV Cache Layer项目地址: https://gitcode.com/GitHub_Trending/lm/LMCache
本指南围绕 LMCache 仓库中的benchmarks/multi_doc_qa基准测试套件展开,系统讲解如何在多文档问答(Multi-Doc QA)场景下,通过 CacheBlend 技术实现非前缀位置的 KV Cache 复用,从而显著降低首 token 延迟(TTFT)。读完本文,你将掌握该基准测试的两轮请求设计原理、三种基线(纯 vLLM、vLLM + 原生 LMCache、vLLM + LMCache blending)的完整启动方式、全部命令行参数含义,以及 blending 在源码层面的实现机制。
一、基准测试定位:为什么要做 Multi-Doc QA
传统的 KV Cache 复用依赖前缀匹配——只有当两个请求的开头 token 完全相同时才能复用缓存。但在多文档问答这类真实场景中,用户请求通常是把若干文档按不同顺序拼接进 prompt,每个请求的前缀各不相同,前缀缓存几乎完全失效。
LMCache 的CacheBlend技术正是为了解决这一问题:它允许 KV Cache 在非前缀位置被复用,通过重算拼接处及关键位置的一小部分 token,把多个文档各自独立的预计算 KV Cache 组合起来使用。benchmarks/multi_doc_qa就是用于量化验证这一收益的基准测试套件,其核心目标与 blending 设计文档 中描述的机制一一对应。
二、基准测试设计:两轮请求机制
根据 benchmarks/multi_doc_qa/README.md 的 Overview,该基准包含两个请求轮次:
- Warmup 轮(预热轮):把每个文档作为单独 prompt 发送。这一轮的作用是让 LMCache 把每个文档的 KV Cache 分别计算并缓存下来(按分隔符切分成独立块)。
- Query 轮(查询轮):为每个请求随机采样若干文档,将它们与系统提示词、查询提示词拼接后作为一个长 prompt 发送。由于各文档的 KV 已在 warmup 轮缓存,启用 blending 后可以按任意顺序复用它们。
两轮均通过 OpenAI 兼容接口(AsyncOpenAI 客户端)发送请求,warmup 轮的每个文档都使用相同的系统提示词"You are a helpful assistant."和查询提示词"What's up? how are you recently?",从而保证缓存内容在 query 轮可以被稳定命中。
三、配置文件解读
基准目录下提供了两个 LMCache 配置文件,均以LMCACHE_CONFIG_FILE环境变量方式加载:
1. vanilla LMCache 配置:lmcache.yaml
max_local_cpu_size: 60仅设置了一个配置项:CPU 本地缓存上限为 60 GB(对应环境变量LMCACHE_MAX_LOCAL_CPU_SIZE,默认 5.0 GB)。文档总数 100、每文档 3000 token 时,总 prompt 量约 30 万 token,60 GB 足以容纳全部文档的 KV Cache,避免查询轮因缓存被逐出而失效。此配置下 LMCache 只提供前缀缓存复用能力。
2. blending 配置:lmcache_blend.yaml
max_local_cpu_size: 60 enable_blending: True blend_special_str: " # # " use_layerwise: True| YAML 配置项 | 环境变量 | 含义 | 默认值 |
|---|---|---|---|
enable_blending | LMCACHE_ENABLE_BLENDING | 是否启用 blending | false |
blend_special_str | LMCACHE_BLEND_SPECIAL_STR | 文档块之间的分隔字符串,LMCache 据此切分与识别各文档 KV 块 | " # # " |
use_layerwise | LMCACHE_USE_LAYERWISE | 是否启用逐层(layerwise)流水线。启用 blending 时必须开启 | false |
max_local_cpu_size的完整配置含义可参见 配置参考文档,其中还有两个与 blending 强相关的进阶参数:
| YAML 配置项 | 环境变量 | 含义 | 默认值 |
|---|---|---|---|
blend_recompute_ratios | LMCACHE_BLEND_RECOMPUTE_RATIOS | 需要重算的 token 比例 | 0.15 |
blend_check_layers | LMCACHE_BLEND_CHECK_LAYERS | 在哪一层决定哪些 token 需要重算 | 1 |
四、Step 1:启动 Serving Engine(三种基线)
基准通过对比三条命令的结果来体现 blending 的收益,模型以mistralai/Mistral-7B-Instruct-v0.2为例,可按需替换为其他 vLLM 支持的模型。
基线 1:纯 vLLM(无 KV Cache 复用)
vllm serve mistralai/Mistral-7B-Instruct-v0.2 --gpu-memory-utilization 0.8 --port 8000没有任何缓存层,query 轮的每个长 prompt 都必须完整重新计算。
基线 2:vLLM + vanilla LMCache(仅前缀缓存)
LMCACHE_CONFIG_FILE=lmcache.yaml vllm serve mistralai/Mistral-7B-Instruct-v0.2 --gpu-memory-utilization 0.8 --port 8000 --kv-transfer-config '{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}'--kv-transfer-config指定通过LMCacheConnectorV1连接器接入 LMCache,kv_role: kv_both表示该实例同时承担 KV 的存储与读取角色。由于 query 轮中文档顺序随机,前缀缓存命中率趋近于零,理论上这一基线的收益很小。
基线 3:vLLM + LMCache with blending
LMCACHE_CONFIG_FILE=lmcache_blend.yaml vllm serve mistralai/Mistral-7B-Instruct-v0.2 --gpu-memory-utilization 0.8 --port 8000 --no-enable-prefix-caching --kv-transfer-config '{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}'与基线 2 相比增加了--no-enable-prefix-caching开关。这是为了保证对比公平:vLLM 自带的前缀缓存会干扰实验,关闭后所有前缀级命中都只能来自 LMCache 的 blending 机制,从而让测量结果真实反映 blending 的贡献。
五、Step 2:发送请求并理解全部参数
启动服务后运行:
python multi_doc_qa.py --num-total-documents 100 --document-length 3000 --output-len 1 --num-requests 100 --num-docs-per-request 5 --model mistralai/Mistral-7B-Instruct-v0.2 --port 8000 --max-inflight-requests 1multi_doc_qa.py 基于 vLLM 官方benchmark_long_document_qa_throughput.py改编而来,完整的命令行参数及默认值如下表:
| 参数 | 默认值 | 说明 |
|---|---|---|
--num-total-documents | 100 | 生成多少个文档用于采样 |
--document-length | 3000 | 每个文档的 token 长度(约等于一篇不含图的系统论文体量) |
--output-len | 10 | 每个 prompt 生成的最大 token 数 |
--num-requests | 100 | 发送的请求总数 |
--num-docs-per-request | 5 | 每个请求拼接的文档数量 |
--sampling-strategy | "random" | 文档采样策略(当前仅支持 random) |
--random-seed | 0 | 随机种子,保证实验可复现 |
--blend-special-str | " # # " | 文档间的分隔字符串,必须与 LMCache 配置中的blend_special_str一致 |
--port | 8000 | 查询 vLLM 服务的端口 |
--model | "meta-llama/Llama-3.1-8B-Instruct" | 模型名 |
--max-inflight-requests | 20 | 最大并发在途请求数(README 示例中设为 1,即串行发送) |
--sleep-time-after-warmup | 0.0 | warmup 轮结束后、query 轮开始前的休眠秒数 |
--output | 无(stdout) | 所有响应写入的文件名 |
--expected-ttft-gain | 无 | 期望的最小 TTFT 加速比(warmup/query),低于则脚本以错误退出 |
--expected-latency-gain | 无 | 期望的最小整体延迟加速比(每 prompt 耗时比),低于则脚本以错误退出 |
需要特别指出:脚本用AutoTokenizer.from_pretrained(args.model)在本地完成 tokenization,把sys_prompt + 分隔符 + 文档 + 分隔符 + 查询提示词拼成 token 序列后再发给服务端(请求体为{"prompt": prompt_ids}形式的 token id 列表)。这是 blending 正确工作的前提,详见下文第六节。
六、Blending 原理纵深:从配置到源码
6.1 为什么必须预分词
从 blending 设计文档 可以看到,直接 tokenize 一个拼接后的长字符串,与把各段文本分别 tokenize 再拼接结果,产生的 token 可能完全不同。因此必须先在本地用tokenizer.encode(...)分别处理系统提示词、每个文档、查询提示词,再用分隔符 token 连接。
multi_doc_qa.py 中的generate_warmup_prompt_ids和generate_prompt_ids正是这一逻辑的实现:两者都先编码blend_special_ids = tokenizer.encode(blend_special_str)[offset:],再按sys_prompt_ids + blend_special_ids + doc_ids + ... + blend_special_ids + query_prompt_ids的次序组装。offset=1用于去掉编码后可能引入的前导空格 token,保证与缓存时一致。
6.2 分隔符与块级缓存
LMCache 依据blend_special_str识别 prompt 中的文档边界,把各文档的 KV Cache 切分成独立块分别存储(文档正文是唯一的,与拼接顺序无关)。这样 query 轮无论文档以何种顺序出现,都能从缓存中按块取出复用。
6.3 层内重算机制(源码级)
为什么 blend 配置要求use_layerwise: True?因为 blending 需要在模型推理逐层进行的过程中,把缓存 KV 与当前实际输入做比对,并对差异显著的位置做重算——这要求 KV 存取与模型执行逐层交错,正是 layerwise 流水线的职责。
核心实现在 lmcache/v1/compute/blend/blender.py 的process_qkv方法中。当layer_id落在blend_check_layers(默认第 1 层)指定的层时,算法执行以下步骤:
- 从 GPU 连接器取出该层缓存的 KV(
self.gpu_connector.get_kv(layer_id)); - 计算当前层新计算出的 K 与缓存 K 的逐位置平方差
diff_k = sum((k - old_k)^2, dim=1); - 按
blend_recompute_ratios(默认 0.15)取差值最大的topk_num = int(total_len * ratio)个位置(至少 1 个),即当前输入与缓存差异最大的 token; - 只对这些位置的 Q/K/V 和 residual 进行重算,并通过
attn_metadata.update_from_top_indices(top_indices)更新注意力掩码; - 其余位置直接沿用缓存的 KV(
old_k[imp_indices] = k),从而实现"只重算少量 token"的拼接复用。
blend_check_layers、blend_recompute_ratios等参数正是在LMCBlendCommonMetadata中从配置文件解析后传入的(见 blender.py 构造函数)。源码中的 TODO 注释("support threshold-based blending"、"support different ratios for different layers")表明当前实现采用的是固定比例的 top-k 选择策略。
七、结果指标与自动化校验
脚本结束时会输出以下指标:
- Warmup 轮平均 TTFT(秒)、总耗时、prompt 数;
- Query 轮平均 TTFT(秒)、总耗时、prompt 数;
- 若指定
--expected-ttft-gain:实际 TTFT 加速比 = warmup 平均 TTFT / query 平均 TTFT; - 若指定
--expected-latency-gain:实际每 prompt 延迟加速比(warmup 每 prompt 耗时 / query 每 prompt 耗时)。
两个expected参数的作用是把基准测试变成可断言的自动化检查:例如传--expected-ttft-gain 4.3表示期望 blending 带来至少 4.3 倍的 TTFT 提升,若实测低于该值脚本会打印错误并以非零码退出(sys.exit),非常适合接入 CI 流水线。TTFT 在process_single_prompt中通过记录"首个非空响应 chunk 到达时间 - 请求发送时间"测得。
注意:warmup 轮因为要逐文档计算并写缓存,总耗时通常显著高于 query 轮;query 轮若 blending 生效,每个请求只需处理文档边界处约 15% 的 token,TTFT 将大幅下降。本文不预设任何具体加速比数值,实际收益取决于硬件、模型与文档长度,请以本机实测为准。
八、进阶工具:shuffle_doc_qa.py 的错位排列测试
目录下还提供了 shuffle_doc_qa.py,用于更严格地验证顺序无关的缓存复用能力。该脚本会为 n 个文档生成 n−1 个"错位排列(derangement)"请求:每个排列保证任意文档都不出现在与基线(恒等排列)相同的序位上(perm[i] != i)。如果 blending 实现正确,这些完全打乱顺序的请求依然应能命中缓存。
其特点包括:
--num-documents(必填)、--document-length(必填)、--output-len(必填)、--port(默认取环境变量SERVICE_PORT或 10001)、--random-seed;- 文档构造方式与
multi_doc_qa.py完全一致(str(i) + " " + " ".join(["hi"] * document_length)); - n ≤ 8 时枚举全部错位排列并随机挑选,n 较大时用洗牌-拒绝法随机采样,保证
pick_derangements的可行性检查(不足时抛出错误); - 请求采用 chat 消息格式(system + 若干对 (user 标签, user 文档正文) + 汇总请求),流式输出并逐请求打印 TTFT。
该脚本尤其适合验证"同一个文档出现在不同位置都能被复用",是 blending 正确性测试的补充手段。
九、运行注意事项
- 参数一致性:
multi_doc_qa.py的--blend-special-str必须与 lmcache_blend.yaml 中的blend_special_str保持完全一致(默认均为" # # "),否则 LMCache 无法按分隔符正确切分文档块。 - 层间依赖:
enable_blending: True时必须同时开启use_layerwise: True,且 vLLM 端需使用支持 layerwise 的执行路径。 - 关闭前缀缓存:对比 blending 收益时务必保留
--no-enable-prefix-caching,否则 vLLM 自带前缀缓存会污染测量结果。 - 内存规划:warmup 轮会把全部文档的 KV Cache 写入 CPU 内存,
max_local_cpu_size需根据文档总数与单文档长度估算(示例配置为 60 GB)。 - 模型选择:README 示例使用
mistralai/Mistral-7B-Instruct-v0.2,脚本默认模型为meta-llama/Llama-3.1-8B-Instruct,两者都需保证 vLLM 服务端与本机 tokenizer 加载的是同一模型。 - 环境要求:脚本依赖
openai与transformers库,可通过 requirements/bench.txt 等依赖清单安装;服务端需按 vLLM 集成文档 正确安装 LMCache 连接器。
十、总结
benchmarks/multi_doc_qa用一套简洁的两轮请求设计,把"多文档随机拼接导致前缀缓存失效"这一真实痛点量化成可对比的 TTFT 指标:warmup 轮负责建立文档级 KV Cache,query 轮验证随机顺序下的复用效果。配合lmcache_blend.yaml中的enable_blending、blend_special_str、use_layerwise三项配置,以及 blender.py 中"第 1 层按 K 差异 top-k 挑选重算 token"的核心算法,你可以在自己的模型与硬件上完整复现并验证 CacheBlend 在非前缀场景下的缓存复用能力。
【免费下载链接】LMCacheLMCache: Supercharge Your LLM with the Fastest KV Cache Layer项目地址: https://gitcode.com/GitHub_Trending/lm/LMCache
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考