性能测试#
本小节尝试在vLLM框架下进行完全重算、显存命中、内存命中、存储命中的快速性能测试方法。 SGLang环境下的测试方法与vLLM基本一致,区别仅在于SGLang的显存 KV Cache清除接口与vLLM不同。
测试过程#
vLLM拉起#
首先,参考 安装 准备测试需要的环境,并参考 快速开始 快速拉起vLLM服务。为了看到明显的性能加速效果,我们选 择稍微大一点的模型。此外,为了避免内存侧由于容量限制发生KV Cache驱逐导致不命中,测试中尽量开辟大一点的内存池空间。为了较为清晰的看见存储 侧的收益,建议使用高性能存储进行测试,避免HDD/NAS等低性能存储系统(极大可能产生负收益)。
# 配置vLLM启用CT-HPKV
(base) hpkv@ctyunos:~$ export VLLM_ENABLE_HPKV=1
# 配置使用的内存池大小 (8G大小)
(base) hpkv@ctyunos:~$ export HPKV_KV_MEM_POOL_SIZE=16G
# 配置共享内存文件路径
(base) hpkv@ctyunos:~$ export HPKV_SHARE_MEM_FILE_PATH="/mnt/hpkv"
# 配置KV Cache文件存储路径
(base) hpkv@ctyunos:~$ export HPKV_KV_STORAGE_PATH="~/storage"
# vLLM 开启调试模式
(base) hpkv@ctyunos:~$ export VLLM_SERVER_DEV_MODE=1
# 启动vLLM
(base) hpkv@ctyunos:~$ vllm serve /mnt/data/models/Qwen/Qwen2.5-7B-Instruct \
--host 0.0.0.0 --port 8000 --trust-remote-code \
--served-model-name qwen2.5-7b --block-size 64 \
--enable-prefix-caching \
--kv-transfer-config '{"kv_connector":"HpkvConnectorV1", "kv_role":"kv_both"}' \
--disable-log-requests
Prefill计算#
参考 性能脚本 脚本构造对应的请求数据。这里我们以长度为8192的提示长度为例,首次发起请求时,vLLM中无对应词元的KV Cache,因此推理引擎会对收到的词元的Prefill 过程计算完全的计算过程(计算后,vLLM会将对应词元的KV Cache保存于显存, CT-HPKV会将对应词元的KV Cache卸载到内存空与存储空间)。其性能结果如下;
(base) [yangxianpku@ecm-e774-0003 hpkv]$ bash ttft_benchmark.sh
A: TTFT=0.726091秒 | 长度=8192 长下文测试
B: TTFT=0.698094秒 | 长度=8192 长下文测试
C: TTFT=0.703355秒 | 长度=8192 长下文测试
D: TTFT=0.693384秒 | 长度=8192 长下文测试
E: TTFT=0.698652秒 | 长度=8192 长下文测试
F: TTFT=0.701629秒 | 长度=8192 长下文测试
G: TTFT=0.698635秒 | 长度=8192 长下文测试
H: TTFT=0.697029秒 | 长度=8192 长下文测试
I: TTFT=0.696216秒 | 长度=8192 长下文测试
J: TTFT=0.699714秒 | 长度=8192 长下文测试
======= 统计结果 =======
总请求数: 10 | 平均TTFT: 0.7012秒 | 总测试时长: 7.012799秒
显存命中#
首次请求后,推理引擎的Prefix Cache功能已经缓存相应词元的KV Cache, 再次请求时会复用保存于显存侧的KV Cache。性能结果如下:
(base) [yangxianpku@ecm-e774-0003 hpkv]$ bash ttft_benchmark.sh
A: TTFT=0.047956秒 | 长度=8192 长下文测试
B: TTFT=0.046062秒 | 长度=8192 长下文测试
C: TTFT=0.047626秒 | 长度=8192 长下文测试
D: TTFT=0.048345秒 | 长度=8192 长下文测试
E: TTFT=0.047682秒 | 长度=8192 长下文测试
F: TTFT=0.046494秒 | 长度=8192 长下文测试
G: TTFT=0.045415秒 | 长度=8192 长下文测试
H: TTFT=0.045276秒 | 长度=8192 长下文测试
I: TTFT=0.044397秒 | 长度=8192 长下文测试
J: TTFT=0.044170秒 | 长度=8192 长下文测试
======= 统计结果 =======
总请求数: 10 | 平均TTFT: 0.0463秒 | 总测试时长: 0.463423秒
内存命中#
为了查看内存命中的性能,我们需要清除vLLM的显存KV Cache,并重新发起请求。内存命中的性能测试结果如下:
# vLLM 清除显存KV Cache
(base) hpkv@ctyunos:~$ curl --location --request POST 'http://localhost:8000/reset_prefix_cache'
# 重新发起请求
(base) [yangxianpku@ecm-e774-0003 hpkv]$ bash ttft_benchmark.sh
A: TTFT=0.138681秒 | 长度=8192 长下文测试
B: TTFT=0.132116秒 | 长度=8192 长下文测试
C: TTFT=0.129995秒 | 长度=8192 长下文测试
D: TTFT=0.131925秒 | 长度=8192 长下文测试
E: TTFT=0.130476秒 | 长度=8192 长下文测试
F: TTFT=0.126713秒 | 长度=8192 长下文测试
G: TTFT=0.128067秒 | 长度=8192 长下文测试
H: TTFT=0.123508秒 | 长度=8192 长下文测试
I: TTFT=0.120852秒 | 长度=8192 长下文测试
J: TTFT=0.119743秒 | 长度=8192 长下文测试
======= 统计结果 =======
总请求数: 10 | 平均TTFT: 0.1282秒 | 总测试时长: 1.282076秒
存储命中#
同理,存储命中性能测试时,我们要确保显存和内存池没有保存对应词元的KV Cache, 于是我们可以重启vLLM推理引擎。 存储完全命中性能结果如下:
重要
CT-HPKV支持混合检索命中,无需完全命中在某一层的KV Cache!!!
(base) [yangxianpku@ecm-e774-0003 hpkv]$ bash ttft_benchmark.sh
A: TTFT=0.369138秒 | 长度=8192 长下文测试
B: TTFT=0.388246秒 | 长度=8192 长下文测试
C: TTFT=0.338912秒 | 长度=8192 长下文测试
D: TTFT=0.380504秒 | 长度=8192 长下文测试
E: TTFT=0.384393秒 | 长度=8192 长下文测试
F: TTFT=0.338840秒 | 长度=8192 长下文测试
G: TTFT=0.384478秒 | 长度=8192 长下文测试
H: TTFT=0.339397秒 | 长度=8192 长下文测试
I: TTFT=0.385246秒 | 长度=8192 长下文测试
J: TTFT=0.384723秒 | 长度=8192 长下文测试
======= 统计结果 =======
总请求数: 10 | 平均TTFT: 0.3693秒 | 总测试时长: 3.693877秒
性能对比#
Prefill计算 |
显存命中 |
内存命中 |
存储命中 |
|
|---|---|---|---|---|
平均耗时(s) |
0.701 |
0.046 |
0.128 |
0.369 |
加速比 |
+1.00x |
+15.24x |
+5.47x |
+1.90x |
重要
测试环境: Intel(R) Xeon(R) Platinum 8468V, NVIDIA L40S, HPFS 40T;
测试设置
max_tokens=1忽略推理的解码过程,仅作为参考;
性能脚本#
为快速对各级KV Cache命中性能进行测试,我们人为构造了测试数据和请求样例。此外,为了直观感受缓存命中带来的性能收益,显式控制生成的最大词 元数目为1。详细性能测试脚本内容如下:
sum=0
count=0
for letter in {A..J}; do
prompt="$(printf "${letter} %.0s" {1..8192})"
ttft=$(curl -s -X POST "http://localhost:8000/v1/completions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer 123456" \
-d '{"model":"qwen2.5-7b","prompt":"'"$prompt"'","max_tokens":1,"temperature":0.0,"stream":false}' \
-w "%{time_starttransfer}" -o /dev/null)
echo "$letter: TTFT=${ttft}秒 | 长度=8192 长下文测试"
sum=$(echo "$sum + $ttft" | bc)
count=$((count + 1))
sleep 0.2
done
avg=$(echo "scale=4; $sum / $count" | bc | sed 's/^\./0./')
echo -e "\n======= 统计结果 ======="
echo "总请求数: $count | 平均TTFT: ${avg}秒 | 总测试时长: $(echo "scale=2; $sum" | bc)秒"