SGLang 中的 EPD 解耦:视觉语言模型弹性编码器扩展

SGLang 引入 Encoder-Prefill-Decode (EPD) 解耦架构,将视觉语言模型 (VLMs) 中的视觉编码与语言处理分离,实现视觉编码容量的独立水平扩展,提升资源利用率。该方案兼容现有的 Prefill-Decode (PD) 解耦,形成三层架构,支持多种传输后端和视觉嵌入缓存。在图像密集场景下,EPD 显著降低 TTFT(首 Token 时间),负载下比同置部署低 6–8 倍;吞吐量提升约 2 倍。但图像稀疏场景可能引入额外网络延迟。基准测试基于 Qwen3-VL-235B,在 8 张 H20 GPU 上验证其在多图像请求中的优势。(128 字)

TL;DR

SGLang 推出 Encoder-Prefill-Decode (EPD) 解耦架构,将视觉语言模型 (VLMs) 中的视觉编码与语言处理分离,实现以下优势:

  • 视觉编码容量独立扩展:编码器服务器可水平扩展,而不影响语言模型部署,优化视觉密集型负载的资源利用。
  • 兼容现有 PD 解耦:EPD 与 Prefill-Decode 解耦结合,形成完整三层架构。
  • 灵活传输后端:支持 ZMQ、Mooncake 等多种传输机制,适应不同部署场景。
  • 视觉嵌入缓存:常用图像可在编码器服务器缓存,避免重复 ViT 计算,减少网络传输开销。

EPD 在图像密集场景(如多图像输入)中效果显著,视觉编码是主要瓶颈。通过 EPD,在负载下显著降低请求 TTFT,与同置方案相比延迟降低约 6–8 倍(1 QPS 时)。而在图像稀疏场景,额外网络延迟可能导致 TTFT 更高。

引言

视觉语言模型 (VLMs) 如 Qwen2.5-VL 和 Llama-Vision 融合视觉理解与语言生成,但面临独特扩展挑战:

  • 异构计算需求:视觉编码 (CNN/ViT) 与语言解码 (Transformer) 计算模式不同。
  • 资源使用不均衡:视觉处理计算密集,但仅在 prefill 阶段需要。
  • 灵活性不足:传统单体部署无法独立扩展视觉与语言组件。
  • 请求内并行性:同一请求中不同图像可独立编码。
  • 张量并行扩展差:视觉编码器参数远少于语言组件,张量并行低效且不必要。

SGLang 现有 Prefill-Decode (PD) 解耦已分离 prefill 与 decode 阶段。EPD 进一步分离视觉编码与语言 prefill,形成三层架构。

ViT 扩展问题:为何张量并行并非总是有效

反直觉发现

EPD 的关键洞察是 Vision Transformers (ViT) 不受益于增加张量并行度 (TP),甚至 TP 越高可能越慢:

H20 上 Qwen2.5-VL-72B 基准(每请求 4 张图像):

TPViT 平均时间
2492.13ms
4465.80ms
8523.80ms

原因:

  1. 通信开销主导执行时间。
  2. 视觉模型权重参数通常较小。

EPD 通过水平扩展编码器规避此问题,而非增加 TP。

架构概述

EPD 架构请求流程:

  1. 客户端请求:多模态请求抵达 prefill 服务器(经负载均衡器或直连)。
  2. 图像分发:prefill 服务器识别图像输入,分发至一个或多个编码器服务器。图像可拆分以负载均衡。
  3. 视觉编码:编码器服务器通过 ViT 处理图像,生成视觉嵌入和图像网格元数据。若启用则缓存结果。
  4. 嵌入传输:视觉嵌入经配置传输后端 (ZMQ、Mooncake 等) 传回 prefill 服务器。
  5. LLM 计算:prefill 服务器结合视觉嵌入与文本 Token,形成包含预计算张量的 mm_inputs。LLM 执行 Prefill 和 Decode。若启用 PD,则复用现有传输逻辑;否则本地解码。

关键组件

EPD Workflow

EPD Architecture

编码器服务器 (--encoder-only)
- 仅视觉(无语言权重);预处理 + ViT 前向生成视觉嵌入
- 支持前缀多模态缓存
- 水平扩展用于负载均衡和多图像并行拆分推理

Prefill 服务器 (--language-only)
- 仅语言模型
- 接收编码器嵌入
- 启用 PD:向 Decode 发送 KV;否则本地解码

Decode 服务器
- 标准仅解码实例
- 从 prefill 接收 KV 缓存

实现细节

图像分发策略

不同于张量并行拆分单一模型,EPD 使用数据并行:运行多个独立编码器实例,分发图像。

示例(7 张图像,3 个编码器):

Request with 7 images: [img0, img1, img2, img3, img4, img5, img6]
3 encoders available

Distribution (after shuffle):
├─ Encoder 0: [img0, img1, img2] (3 images)
├─ Encoder 1: [img3, img4] (2 images)
└─ Encoder 2: [img5, img6] (2 images)

传输后端

EPD 支持三种视觉嵌入传输后端:

  • zmq_to_scheduler (默认):直接 ZMQ 套接字通信,经 RDMA 传输引擎发送嵌入至调度器,无阻塞。
  • zmq_to_tokenizer:嵌入发送至 tokenizer 管理器,在分词阶段处理。
  • mooncake:多节点 RDMA 传输,在共享内存注册嵌入,高带宽低延迟。

视觉嵌入缓存

编码器支持前缀多模态缓存,避免重复 ViT 计算:

  • 消除冗余视觉编码
  • 降低重复图像延迟
  • 可配置缓存大小(默认 4GB,经 SGLANG_VLM_CACHE_SIZE_MB)

使用示例

启动编码器实例:

MODEL=Qwen/Qwen2.5-VL-7B-Instruct
PORT=30002

CUDA_VISIBLE_DEVICES=2 taskset -c $1 python -m sglang.launch_server \
    --model-path $MODEL \
    --encoder-only \
    --enable-prefix-mm-cache \
    --port $PORT

启动 prefill 实例:

MODEL=Qwen/Qwen2.5-VL-7B-Instruct
PORT=30000
TP=1
MEM_FRACTION=0.5
CHUNK_SIZE=8192

SGLANG_VLM_CACHE_SIZE_MB=0 CUDA_VISIBLE_DEVICES=0 python -m sglang.launch_server \
    --model-path $MODEL \
    --disaggregation-mode prefill \
    --disaggregation-transfer-backend nixl \
    --tp $TP \
    --mem-fraction-static $MEM_FRACTION \
    --disable-radix-cache \
    --chunked-prefill-size $CHUNK_SIZE \
    --language-only \
    --encoder-urls http://127.0.0.1:30002 http://127.0.0.1:30003 http://127.0.0.1:30004 http://127.0.0.1:30005 http://127.0.0.1:30006 http://127.0.0.1:30007 \
    --port $PORT

启动 decode 实例:

MODEL=Qwen/Qwen2.5-VL-7B-Instruct
PORT=30001
TP=1

CUDA_VISIBLE_DEVICES=1 python -m sglang.launch_server \
    --model-path $MODEL \
    --disaggregation-mode decode \
    --disaggregation-transfer-backend nixl \
    --tp $TP \
    --port $PORT

启动 minlb:

python -m sglang_router.launch_router \
  --pd-disaggregation \
  --mini-lb \
  --prefill http://127.0.0.1:30000 \
  --decode http://127.0.0.1:30001 \
  --port 8000

基准测试

EPD 针对视觉密集负载(多图像请求),通过水平扩展编码器提升 TTFT。

基准脚本:

python -m sglang.bench_serving \
    --random-image-count \
    --model ${MODEL_PATH} \
    --num-prompts 64 \
    --dataset-name image \
    --random-input-len 128 \
    --random-output-len 256 \
    --image-count 8 \
    --image-resolution 1080p \
    --host $HOST_IP \
    --port $port \
    --backend vllm-chat \
    --request-rate $request_rate

实验设置

环境:8× H20 96GB GPU

模型:Qwen3-VL-235B-A22B-Instruct-FP8

数据集:随机多模态数据集
- 文本 Token:128 / 256
- 每请求图像:1-8 张(随机,平均 ~4 张)
- 图像分辨率:1080p
- QPS 范围:0.2-1.0

部署配置
- Colocate:1 PD 实例,tensor-parallel-size=4,使用 4× H20
- 1E1P:1 编码器 (TP=1) + 1 PD (TP=4),使用 5× H20
- 2E1P:2 编码器 (各 TP=1) + 1 PD (TP=4),使用 6× H20

测试结果

平均 TTFT (EPD vs colocate):

TTFT Results

平均 TPOT (EPD vs colocate):

TPOT Results

请求吞吐量 (EPD vs colocate):

Throughput Results

关键发现(vs. colocate):

  • 负载下编码器/prefill 保持 TTFT 远低于 colocate(1 QPS 时 ≈6–8x 更低)。
  • TPOT 远低于 colocate(≈8–10x 更低),延迟更紧凑。
  • 高 QPS 下吞吐量约翻倍(0.8–1.0 QPS 时 ≈2x)。
  • 通过专用 GPU 资源分配编码器,实现 TTFT 剧减。尽管 2E1P 使用 50% 更多 GPU(6× vs 4×),但实现更高资源利用率。