性能测试#

本小节尝试在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)秒"