XpYd#

X 预填充器,Y 解码器 (XpYd) 示例#

此示例演示了如何在单个节点上使用 NIXL 运行带有分离式 Prefill 的 LMCache,使用多个 Prefiller 和 Decoder 实例。此配置允许对计算密集型的 Prefill 操作和解码操作进行横向扩展,从而实现更好的资源利用和更高的吞吐量。

架构概述#

XpYd 设置由多个可以独立扩展的组件组成:

  1. 多个 Prefiller 服务器 - 处理推理的预填充阶段(初始提示处理)

  2. 多个解码器服务器 - 处理推理的解码阶段(令牌生成)

  3. 代理服务器 - 使用轮询负载均衡协调预填充器和解码器之间的请求

示例 2p2d 架构:

       ┌─────────────┐
       │   Client    │
       └─────┬───────┘
     ┌───────▼────────┐
     │  Proxy Server  │
     │    Port 9100   │──────────────────────|
     │  (Round-Robin) │                      |
     └───▲────────▲───┘                      |
         │        │                          |
┌────────▼───┐  ┌─▼──────────┐               |
│ Prefiller1 │  │ Prefiller2 │               |
│ Port 7100  │  │ Port 7101  │               |
│   GPU 0    │  │   GPU 1    │               |
└─────▲──────┘  └─────▲──────┘               |
      │               │                      |
      │ NIXL transfer |                      |
      │               │                      |
 ┌────▼───────┐  ┌────▼──────┐               |
 │ Decoder 1  │  │ Decoder 2 │               |
 │ Port 7200  │  │ Port 7201 │               |
 │  GPU 2     │  │  GPU 3    │               |
 └────▲───────┘  └────▲──────┘               |
      │               │                      |
      └───────────────┴──────────────────────|

先决条件#

  • LMCache: 使用 pip install lmcache 安装

  • NIXL: 从 NIXL GitHub 仓库 安装

  • 硬件:至少需要 4 个 GPU(2 个用于 Prefillers + 2 个用于 2p2d 设置中的解码器)

  • 模型访问:有效的 Hugging Face 令牌 (HF_TOKEN) 用于 Llama 3.1 8B Instruct

快速开始 (2p2d 示例)#

  1. 设置你的 Hugging Face 令牌

    export HF_TOKEN=hf_your_token_here
    
  2. 导航到示例目录

    cd examples/disagg_prefill/xpyd_experimental
    
  3. 运行示例:

    bash disagg_example_xpyd.sh
    

该脚本将自动:

  • 在端口 8200 和 8201 启动两个解码器实例(GPU 2 和 GPU 3)

  • 在端口 7100 和 7101 上启动两个 Prefill 实例(GPU 0 和 GPU 1)

  • 在9100端口启动一个代理服务器,并使用轮询负载均衡。

  • 等待所有服务器准备就绪

Ctrl+C 停止所有服务器。

配置#

重要:为了正确的 KV Cache 传输,确保所有进程使用相同的 PYTHONHASHSEED 以保持 KV Cache 在进程间的一致性:

export PYTHONHASHSEED=0

分离式 Prefill 配置#

所有 Prefiller 通过 configs/lmcache-prefiller-config.yaml 共享相同的配置:

local_cpu: False
max_local_cpu_size: 0
max_local_disk_size: 0

enable_pd: True
transfer_channel: "nixl"
pd_role: "sender"
pd_proxy_host: "localhost"
pd_proxy_port: 7500
pd_buffer_size: 1073741824 # 1GB
pd_buffer_device: "cuda"

关键设置:

  • pd_role: "sender" - 配置这些实例以发送 KV Cache 数据

  • pd_buffer_size: 1073741824 # 1GB - PD 传输缓冲区大小的上限(以字节为单位),与块大小对齐

  • pd_buffer_device: "cuda" - 使用显存进行缓冲

解码器配置#

解码器通过 configs/lmcache-decoder-x-config.yaml 进行配置:

local_cpu: False
max_local_cpu_size: 0

enable_pd: True
transfer_channel: "nixl"
pd_role: "receiver"
pd_peer_host: "localhost"
pd_peer_init_port: 730x
pd_peer_alloc_port: 740x
pd_buffer_size: 2147483648 # 2GB
pd_buffer_device: "cuda"
pd_backends: [UCX]

关键设置:

  • pd_role: "receiver" - 配置这些实例以接收 KV Cache 数据

  • pd_buffer_size: 2147483648 # 2GB - PD 传输缓冲区大小的上限(以字节为单位),与块大小对齐

  • pd_buffer_device: "cuda" - 使用显存进行缓冲

组件深入分析#

代理服务器 (disagg_proxy_server.py)#

代理服务器协调多预填充器的分离式工作流程:

  1. 请求处理:在 9000 端口接收客户端请求

  2. 负载均衡:使用轮询将请求分配到多个预填充器

  3. Prefill Coordination: 将请求发送到使用 max_tokens=1 的 Prefill 处理器

  4. Prefill Response: 接收预填充器,表示 nixl 转移已完成

  5. 响应流:从解码器流式传输完整响应

  6. 性能监控: 跟踪首次令牌时间(TTFT)统计信息

主要特性:- 轮询分配:在 --num-prefillers 实例之间平衡负载 - 容错:优雅地处理预填充器故障 - 监控:为每个预填充器提供详细的 TTFT 统计信息

支持的端点: - /v1/completions - /v1/chat/completions

vLLM 服务器启动器 (disagg_vllm_launcher.sh)#

此脚本启动具有适当配置的单个 vLLM 服务器:

Prefiller1 启动命令:

UCX_TLS=cuda_ipc,cuda_copy,tcp \
   LMCACHE_CONFIG_FILE=$prefill_config_file \
   VLLM_ENABLE_V1_MULTIPROCESSING=1 \
   VLLM_WORKER_MULTIPROC_METHOD=spawn \
   CUDA_VISIBLE_DEVICES=0 \
   vllm serve $MODEL \
   --port 7100 \
   --disable-log-requests \
   --enforce-eager \
   --no-enable-prefix-caching \
   --kv-transfer-config \
   '{"kv_connector":"LMCacheConnectorV1","kv_role":"kv_producer","kv_connector_extra_config": {"discard_partial_chunks": false, "lmcache_rpc_port": "producer1"}}'

Prefiller2 启动命令:

UCX_TLS=cuda_ipc,cuda_copy,tcp \
   LMCACHE_CONFIG_FILE=$prefill_config_file \
   VLLM_ENABLE_V1_MULTIPROCESSING=1 \
   VLLM_WORKER_MULTIPROC_METHOD=spawn \
   CUDA_VISIBLE_DEVICES=1 \
   vllm serve $MODEL \
   --port 7101 \
   --disable-log-requests \
   --enforce-eager \
   --no-enable-prefix-caching \
   --kv-transfer-config \
   '{"kv_connector":"LMCacheConnectorV1","kv_role":"kv_producer","kv_connector_extra_config": {"discard_partial_chunks": false, "lmcache_rpc_port": "producer2"}}'

解码器1 启动命令:

UCX_TLS=cuda_ipc,cuda_copy,tcp \
   LMCACHE_CONFIG_FILE=$decode_config_file \
   VLLM_ENABLE_V1_MULTIPROCESSING=1 \
   VLLM_WORKER_MULTIPROC_METHOD=spawn \
   CUDA_VISIBLE_DEVICES=2 \
   vllm serve $MODEL \
   --port 7200 \
   --disable-log-requests \
   --enforce-eager \
   --no-enable-prefix-caching \
   --kv-transfer-config \
   '{"kv_connector":"LMCacheConnectorV1","kv_role":"kv_consumer","kv_connector_extra_config": {"discard_partial_chunks": false, "lmcache_rpc_port": "consumer1", "skip_last_n_tokens": 1}}'

Decoder2 启动命令:

UCX_TLS=cuda_ipc,cuda_copy,tcp \
   LMCACHE_CONFIG_FILE=$decode_config_file \
   VLLM_ENABLE_V1_MULTIPROCESSING=1 \
   VLLM_WORKER_MULTIPROC_METHOD=spawn \
   CUDA_VISIBLE_DEVICES=3 \
   vllm serve $MODEL \
   --port 7201 \
   --disable-log-requests \
   --enforce-eager \
   --no-enable-prefix-caching \
   --kv-transfer-config \
   '{"kv_connector":"LMCacheConnectorV1","kv_role":"kv_consumer","kv_connector_extra_config": {"discard_partial_chunks": false, "lmcache_rpc_port": "consumer2", "skip_last_n_tokens": 1}}'

与 1p1d 的主要区别: - 每个 Prefiller 获取一个唯一的 ``lmcache_rpc_port``(producer1、producer2 等) - 每个 Prefiller 在不同的 GPU 上运行(CUDA_VISIBLE_DEVICES) - 每个 Prefiller 使用不同的端口(7100、7101 等) - 每个解码器使用不同的端口(7200、7201 等)

基本测试#

一旦所有服务器都在运行,您可以使用简单的 curl 命令进行测试:

curl -s -N -X POST http://127.0.0.1:9100/v1/completions   -H "Content-Type: application/json"   -d '{
   "model": "meta-llama/Llama-3.1-8B-Instruct",
   "prompt": "What date is today?",
   "max_tokens": 20,
   "temperature": 0.0
}'

性能基准测试#

要进行全面的性能测试,请使用 vLLM 的基准测试工具:

vllm bench serve --port 9100 --seed $(date +%s) \
   --model meta-llama/Llama-3.1-8B-Instruct \
   --dataset-name random --random-input-len 7500 --random-output-len 200 \
   --num-prompts 30 --burstiness 100 --request-rate 1 --ignore-eos

预期的性能提升与 2p2d 相关:- 更高的吞吐量:多个预填充器可以处理更多的并发请求 - 更好的 TTFT:负载均衡减少排队延迟 - 更好的利用率:多个设备之间更好的 GPU 利用率

示例基准测试结果:

============ Serving Benchmark Result ============
Successful requests:                     30
Benchmark duration (s):                  31.34
Total input tokens:                      224970
Total generated tokens:                  6000
Request throughput (req/s):              0.96
Output token throughput (tok/s):         191.44
Total Token throughput (tok/s):          7369.36
---------------Time to First Token----------------
Mean TTFT (ms):                          313.41
Median TTFT (ms):                        272.83
P99 TTFT (ms):                           837.32
-----Time per Output Token (excl. 1st token)------
Mean TPOT (ms):                          8.84
Median TPOT (ms):                        8.72
P99 TPOT (ms):                           11.35
---------------Inter-token Latency----------------
Mean ITL (ms):                           8.84
Median ITL (ms):                         8.61
P99 ITL (ms):                            11.43
==================================================

日志文件和监控#

该示例生成多个日志文件以进行全面监控:

  • prefiller1.log - 第一个 Prefill 服务器的日志和错误

  • prefiller2.log - 第二个 Prefill 服务器日志和错误

  • decoder1.log - 第一个解码器服务器日志和错误

  • decoder1.log - 第一个解码器服务器日志和错误

  • proxy.log - 代理服务器日志和 TTFT 统计信息

代理服务器为每个 Prefiller 提供详细的统计信息:

===============================
Num requests: 20
Prefiller 1 TTFT stats:
 - Average (ms): 42.3
 - Median (ms): 40.1
 - 99th Percentile (ms): 48.7
Prefiller 2 TTFT stats:
 - Average (ms): 43.8
 - Median (ms): 41.5
 - 99th Percentile (ms): 52.1
===============================

这有助于识别不同 Prefill 实例之间的性能差异并优化负载均衡。

故障排除#

常见问题#

  1. 显存:确保每个 GPU 具有足够的内存以支持模型

  2. NIXL 安装: 验证 NIXL 是否正确安装并可访问

  3. 端口冲突:检查所有必需的端口是否可用

  4. HF Token: 确保您的 Hugging Face 令牌可以访问 Llama 模型

  5. GPU 分配: 验证 CUDA_VISIBLE_DEVICES 的分配是否存在冲突

多实例特定问题#

  1. 不均匀负载:监控 Prefill 统计数据以确保负载均衡分配

  2. 资源争用:监控多个实例的显存压力

  3. 网络瓶颈:监控实例之间的 NIXL 传输性能

  4. 启动时机:错开 Prefill 启动以避免资源冲突