RESP (原生 Redis/Valkey)#

概述#

RESP 后端是一个高性能的原生 C++ 存储连接器,适用于 Redis 和 Valkey 服务器,使用 TCP 上的 RESP2 传输协议。它旨在实现 KV Cache 存储和检索操作的最大吞吐量,在最佳配置下读取速度可达到 6+ GB/s

相较于标准 Redis 连接器的主要优势:

  • 多线程 C++ I/O:工作线程并行操作,采用零拷贝缓冲区传递和完全释放全局解释器锁(GIL)

  • 批处理切片:大型批处理操作会自动在工作线程之间拆分,以实现最大并行性

  • 基于 eventfd 的完成:内核在完成时唤醒 Python——没有轮询开销

  • 双模式支持:相同的 C++ 连接器在非 MP 模式(通过 ConnectorClientBase)和 MP 模式(通过 NativeConnectorL2Adapter 作为 L2 适配器)下均可工作。

本地 C++ 源代码位于 csrc/storage_backends/redis/。有关完整架构,请参见 添加本地连接器

先决条件#

  • 从源代码安装的 LMCache (pip install -e .) 以编译 C++ 扩展

  • 支持 IO 线程的 Redis 8.2+ 或 Valkey 服务器(推荐使用 Redis 8.2)

  • 一台至少配备一个 GPU 的机器用于 vLLM 推理

Redis 服务器设置#

重要

Redis 版本和服务器配置对吞吐量有 重大 影响。使用 IO 线程的 Redis 8.2 读取速度约为 6 GB/s,而使用 Redis 6.0 默认配置时约为 1.5 GB/s。

从源代码构建 Redis 8.2(推荐):

git clone https://github.com/redis/redis.git
cd redis
git checkout 8.2
make -j

启用 IO 线程启动服务器:

./src/redis-server \
    --protected-mode no \
    --save '' \
    --appendonly no \
    --io-threads 4 \
    --port 6379
推荐的服务器标志#

标志

为什么

--protected-mode no

允许来自其他主机的连接(在生产环境中使用认证)

--save '' --appendonly no

禁用持久性 -- KV Cache 是短暂的,持久性会浪费带宽

--io-threads 4

启用多线程 I/O 以实现并行读/写处理

--port 6379

默认端口(如果运行多个实例,请调整)

小技巧

--io-threads 的数量应大致与 Redis 进程可用的物理核心数量相匹配。4 是一个不错的起点;请根据您的硬件进行基准测试以找到最佳值。

块大小选择与吞吐量调优#

块大小(以令牌为单位)决定了每个 Redis 键值对所占用的字节数。这是 吞吐量最重要的参数

最佳块大小约为 4 MB。 较小或较大的块都会降低吞吐量:

块大小与吞吐量(Redis 8.2,8 个工作线程)#

块大小

总数据

设置吞吐量

获取吞吐量

1 MB (500 个键)

500 MB

约 3.5 GB/s

~5.2 GB/s

4 MB (500 个键)

2 GB

~4.4 GB/s

~5.9 GB/s

8 MB (200 个键)

1.6 GB

约 4.2 GB/s

~1.4 GB/s

为什么是 4 MB?

  • 在 ~2 MB 以下,每个键的开销(RESP 帧、TCP 往返时间)占主导地位

  • 超过 ~4 MB 时,Redis 服务器端内存分配和 TCP 窗口大小成为瓶颈。

  • 在 4 MB 时,摊销开销与内存压力之间的平衡是最佳的。

计算块大小(以 token 为单位):

块大小(以字节为单位)取决于模型的隐藏维度、KV 头的数量、层数和数据类型:

bytes_per_token = 2 * num_kv_heads * head_dim * num_layers * dtype_bytes

对于 meta-llama/Llama-3.1-8B-Instruct 使用 BFloat16:

bytes_per_token = 2 * 8 * 128 * 32 * 2 = 131,072 bytes (~128 KB)
chunk_size_tokens = 4 MB / 128 KB = 32 tokens

# But typically chunk_size is set as token count in config:
chunk_size: 16   # ~2 MB per chunk (conservative)
chunk_size: 32   # ~4 MB per chunk (optimal for throughput)

备注

每个令牌的字节计算因模型架构而异。较大的模型(例如,70B)具有更多的层和更大的隐藏维度,因此每个块所需的令牌数量较少,以达到 4 MB 的最佳点。

吞吐量扫描#

要找到适合您硬件的最佳配置,请使用随附的基准测试:

cd examples/kv_cache_reuse/remote_backends/resp

# Sweep chunk sizes
for mb in 0.5 1 2 4 8; do
    echo "=== Chunk: ${mb} MB ==="
    python benchmark_resp_client.py \
        --host 127.0.0.1 --port 6379 \
        --chunk-mb $mb --num-workers 8 --num-keys 500
done

# Sweep worker counts
for w in 1 2 4 8 16; do
    echo "=== Workers: $w ==="
    python benchmark_resp_client.py \
        --host 127.0.0.1 --port 6379 \
        --chunk-mb 4 --num-workers $w --num-keys 500
done

预期输出:

Redis RESP Client Benchmark
Server: 127.0.0.1:6379, Workers: 8
Chunk size: 4096KB, Keys: 500
------------------------------------------------------------
Batch SET:      4.36 GB/s  (1.95 GB written)
Batch GET:      5.91 GB/s  (1.95 GB read)
Batch EXISTS: 143528 ops/s  (500/500 hits)
------------------------------------------------------------
All tests passed

环境变量配置#

敏感凭据(可选的主机/端口)可以通过环境变量提供,而不是配置文件或 CLI 参数。这可以防止在启动时将秘密记录到配置日志中。

变量

描述

LMCACHE_RESP_USERNAME

Redis ACL 用户名。当 username 在 config/JSON 中未设置时使用该默认值。

LMCACHE_RESP_PASSWORD

Redis AUTH 密码。当 config/JSON 中未设置 password 时使用该密码作为默认值。

LMCACHE_RESP_HOST

Redis 主机名或 IP。当配置/JSON/URL 中未设置 host 时使用此默认值。

LMCACHE_RESP_PORT

Redis 端口。当 port 在 config/JSON/URL 中未设置时使用此端口作为默认值。

配置文件(非多租户)和 --l2-adapter JSON(多租户)优先于环境变量。环境变量作为默认值使用——当相应的配置值为空或未设置时使用。它们在适配器创建时被读取,因此**从不存储在配置对象中**,并且**从不在启动日志中打印**。

示例 — MP 模式与环境变量:

export LMCACHE_RESP_USERNAME="default"
export LMCACHE_RESP_PASSWORD="secret"

lmcache server \
    --l1-size-gb 10 \
    --eviction-policy LRU \
    --chunk-size 16 \
    --l2-adapter '{"type": "resp", "host": "localhost", "port": 6379, "num_workers": 8}' \
    --port 6555

示例 — 非 MP 模式与环境变量:

export LMCACHE_RESP_USERNAME="default"
export LMCACHE_RESP_PASSWORD="secret"

LMCACHE_CONFIG_FILE=resp-config.yaml \
vllm serve meta-llama/Llama-3.1-8B-Instruct \
    --kv-transfer-config '{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}' \
    --no-enable-prefix-caching \
    --load-format dummy

小技巧

在生产环境中,始终使用环境变量来存储凭据,而不是将其嵌入配置文件或命令行参数中。

非 MP 模式(单进程)#

在非多进程模式下,RESP 连接器通过 RESPClient asyncio 包装器直接用作远程存储后端。

配置文件 (resp-config.yaml):

chunk_size: 16
remote_url: "resp://localhost:6379"
remote_serde: "naive"

凭据可以通过环境变量(推荐)或在配置文件的 extra_config 中设置(请参见上面的 环境变量配置)。

启动 vLLM:

LMCACHE_CONFIG_FILE=resp-config.yaml \
vllm serve meta-llama/Llama-3.1-8B-Instruct \
    --kv-transfer-config '{"kv_connector":"LMCacheConnectorV1", "kv_role":"kv_both"}' \
    --no-enable-prefix-caching \
    --load-format dummy

备注

save_unfull_chunk 必须关闭(默认)并且必须禁用块元数据保存,以便在使用原生 RESP 连接器时实现最佳吞吐量。

MP 模式(多进程)#

在 MP 模式下,LMCache 作为一个独立的服务器进程运行,通过 ZMQ 与 vLLM 进行通信。RESP 连接器作为一个具有可变大小块支持的 L2 适配器。

**步骤 1:启动 Redis**(请参见上面的 Redis Server Setup

步骤 2:启动 LMCache MP 服务器:

lmcache server \
    --l1-size-gb 10 \
    --eviction-policy LRU \
    --chunk-size 16 \
    --l2-adapter '{"type": "resp", "host": "localhost", "port": 6379, "num_workers": 8}' \
    --port 6555

步骤 3:启动带有 LMCache MP 连接器的 vLLM:

PORT=8000
vllm serve meta-llama/Llama-3.1-8B-Instruct \
    --kv-transfer-config '{
        "kv_connector": "LMCacheMPConnector",
        "kv_role": "kv_both",
        "kv_connector_extra_config": {
            "lmcache.mp.host": "tcp://localhost",
            "lmcache.mp.port": 6555
        }
    }' \
    --no-enable-prefix-caching \
    --port $PORT \
    --load-format dummy

L2 适配器配置#

--l2-adapter JSON 接受以下字段:

字段

类型

默认

描述

type

字符串

(必需)

必须是 "resp"

host

字符串

(必需)

Redis/Valkey 主机名或 IP

port

整数

(必需)

Redis/Valkey 端口

num_workers

整数

8

用于并行 I/O 的 C++ 工作线程

username

字符串

""

Redis ACL 用户名(留空以不进行身份验证)。如果为空,将回退到 LMCACHE_RESP_USERNAME 环境变量。

密码

字符串

""

Redis AUTH 密码(留空表示不进行身份验证)。如果为空,将回退到 LMCACHE_RESP_PASSWORD 环境变量。

max_capacity_gb

浮点数

0

用于客户端使用跟踪的最大 L2 存储容量(以 GB 为单位)。这是 L2 逐出的必要条件。设置为 0(默认值)以禁用使用跟踪。

L2 逐出#

要启用在 Redis 后端填满时自动逐出最近最少使用的键,请设置 max_capacity_gb 并添加一个 "eviction" 块:

lmcache server \
    --l1-size-gb 10 \
    --eviction-policy LRU \
    --chunk-size 16 \
    --l2-adapter '{
        "type": "resp",
        "host": "localhost",
        "port": 6379,
        "num_workers": 8,
        "max_capacity_gb": 10,
        "eviction": {
            "eviction_policy": "LRU",
            "trigger_watermark": 0.8,
            "eviction_ratio": 0.2
        }
    }' \
    --port 6555

这配置了 10 GB 的容量限制。当使用量超过 80%(trigger_watermark)时,逐出控制器将使用 Redis DEL 命令删除最近最少使用的 ~20% 存储的键(eviction_ratio)。

备注

max_capacity_gb 启用 客户端 大小跟踪。它并不配置 Redis 服务器的 maxmemory 设置。您应该将 max_capacity_gb 设置为与 Redis 服务器的可用内存相匹配或略低于该值。

测试设置#

两次发送相同的提示。第一次请求将 KV Cache 存储到 Redis;第二次请求检索它。

PORT=8000
PROMPT="$(printf 'Elaborate the significance of KV cache in language models. %.0s' {1..1000})"

# First request: store
curl -s -X POST http://localhost:${PORT}/v1/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"meta-llama/Llama-3.1-8B-Instruct","prompt":"'"$PROMPT"'","max_tokens":10}'

# Second request with same prefix: retrieve from Redis
curl -s -X POST http://localhost:${PORT}/v1/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"meta-llama/Llama-3.1-8B-Instruct","prompt":"'"$PROMPT"'","max_tokens":10}'

验证数据是否已存储:

redis-cli -p 6379 DBSIZE

在运行之间清除状态:

redis-cli -p 6379 FLUSHALL

最佳实践#

服务器部署:

  • 使用 Redis 8.2+ 和 ``--io-threads 4``(或更多,匹配可用核心)

  • 禁用持久化(--save '' --appendonly no)以支持 KV Cache 工作负载

  • 在多插槽系统上将 Redis 固定到其自己的 NUMA 节点

  • 在生产环境中,使用 --requirepass 启用身份验证,并通过 LMCACHE_RESP_USERNAME / LMCACHE_RESP_PASSWORD 环境变量提供凭据,以避免将其记录到日志中。

客户端调优:

  • num_workers: 8 开始,如果服务器有多余的 CPU 并且网络没有饱和,则可以增加该值。

  • 当块大小较小时,更多的工作线程会有所帮助(每批次更多的键 = 需要更多的并行性)

  • 在 NUMA 系统上,确保 LMCache 进程与 NIC 运行在同一个 NUMA 节点上。

块大小:

  • 每个块目标约 4 MB,以实现最大吞吐量

  • 使用模型的每个 token 字节大小计算 token 数量(请参见上面的公式)

  • 如果不确定,请运行基准测试以找到适合您特定硬件的最佳值。

网络:

  • 在单机部署中使用 localhost 或回环地址

  • 对于跨机器设置,确保低延迟网络(理想情况下 <100 微秒 RTT)

  • RESP 连接器使用 TCP;目前不支持 RDMA(考虑使用 Mooncake 进行 RDMA)。

附加资源#

  • 基准测试脚本:examples/kv_cache_reuse/remote_backends/resp/benchmark_resp_client.py

  • C++ 源码: csrc/storage_backends/redis/

  • 本地连接器架构: csrc/storage_backends/README.md

  • 添加新原生连接器的开发者指南: 添加原生连接器