vLLM Translation Performance Optimization Continuous Batching Machine Learning

Part 2:扩展翻译推理:吞吐量 +82%

Ashar Mirza - VoicePing 3 分钟阅读
Part 2:扩展翻译推理:吞吐量 +82%

通过 AsyncLLMEngine 和合理配置 Continuous Batching,将 vLLM 推理吞吐量提升 82%

回顾:问题所在

Part 1 中,我们找到了瓶颈:FastAPI 服务使用多进程工作器和 IPC 队列来分发翻译任务,导致了以下问题:

  • 队列序列化开销
  • 工作进程间的 GPU 计算资源争用
  • 尖峰状 GPU 使用率

基线:25 个并发请求时 2.2 RPS

改进方向:去掉多进程,利用 vLLM 的批量推理能力。


尝试 2:静态批处理

在现有工作进程中实现了静态批处理。

实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 工作进程内
MAX_BATCH_SIZE = 16
BATCH_TIMEOUT = 0.05  # 50ms

while True:
    batch_keys = []
    batch_tasks = []

    # 获取第一个任务(阻塞)
    first_key = queue.get()
    batch_keys.append(first_key)
    batch_tasks.append(tasks[first_key])

    # 尝试收集更多任务(带超时的非阻塞)
    batch_start = time.time()
    while len(batch_keys) < MAX_BATCH_SIZE:
        time_remaining = BATCH_TIMEOUT - (time.time() - batch_start)
        if time_remaining <= 0:
            break
        try:
            key = queue.get(timeout=time_remaining)
            batch_keys.append(key)
            batch_tasks.append(tasks[key])
        except Empty:
            break

    # 使用 vLLM 批量处理
    results = translation_provider.translate_batch(
        texts=[t.text for t in batch_tasks],
        source_langs=[t.source_lang for t in batch_tasks],
        target_langs=[t.target_lang for t in batch_tasks]
    )

要点:

  • 批大小:16 个请求
  • 超时:50ms(不会无限等待批次填满)
  • vLLM 同时处理多个序列
  • 仍然使用多进程工作器

结果

图 1:静态批处理带来显著的吞吐量和响应时间改善

吞吐量提升近 3 倍。 每请求推理时间:452ms → 171ms。

权衡

优点:

  • 大幅提升吞吐量
  • GPU 利用率提高
  • 实现简单

缺点:

  • 队头阻塞(Head-of-Line Blocking):所有请求都要等最慢的那个完成
  • 输入长度不一时,短翻译需要等待长翻译
  • 示例:[50 token, 50 token, 200 token] → 前两个要等 200 token 的翻译完成

这是不错的进展,但我们想消除队头阻塞问题。

尝试 3:Continuous Batching

解决方案:使用 vLLM 的 AsyncLLMEngine 实现 Continuous Batching。

什么是 Continuous Batching?

与静态批处理不同,Continuous Batching 动态组建批次:

  • 新请求可以在生成过程中加入批次
  • 完成的请求立即退出(不等待其他请求)
  • 每个 token 都会更新批次组成
  • vLLM 的 AsyncLLMEngine 自动处理

无队头阻塞。 短翻译完成后立即返回。

实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from vllm import AsyncLLMEngine, EngineArgs

engine_args = EngineArgs(
    model=model_id,
    max_num_seqs=64,  # 初始值
    max_num_batched_tokens=16384,
    gpu_memory_utilization=0.3,
    enable_chunked_prefill=True,
)

engine = AsyncLLMEngine.from_engine_args(engine_args)

@app.post("/translate")
async def translate(request: TranslateRequest):
    result_generator = engine.generate(
        request.text,
        sampling_params,
        request_id=generate_id()
    )

    async for output in result_generator:
        final_output = output
    return TranslateResponse(translation=final_output.text)

架构变更:

  • 在 FastAPI 中直接使用 AsyncLLMEngine
  • vLLM 通过 Continuous Batching 引擎内部管理批处理
  • 全程纯 async/await

测试现实

初始结果(均匀输入)

使用标准的均匀长度输入(相似长度)进行测试:

图 2:均匀输入下的 Continuous Batching,达到 15 RPS 的高吞吐量

15 RPS vs 基线 2.2 — 约 7 倍提升。看起来非常出色。

可变长度输入(真实场景)

使用真实的可变长度输入(10~200 token,长短混合)进行测试:

可变输入下的基线重测:

  • 极高负载:1.1 RPS(对比均匀输入的 2.2 RPS)
  • 即使基线在真实数据下也表现更差

可变输入下的 Continuous Batching(max_num_seqs=64):

  • 极高负载:3.5 RPS(max_num_seqs=16 调优后)
  • 与均匀输入下达到 15 RPS 的同一配置

图 3:均匀测试数据与真实可变长度输入之间的性能差距

配置调优

max_num_seqs=64 在可变输入下表现不佳,促使我们分析 vLLM 内部指标。

发现

1
2
3
4
5
# 监控的 vLLM Prometheus 指标:
# - vllm:time_to_first_token_seconds (TTFT)
# - vllm:time_per_output_token_seconds(解码时间)
# - vllm:gpu_cache_usage_perc(KV 缓存使用率)
# - vllm:num_requests_running / waiting(队列深度)

问题所在:

  • 实际工作负载:每台服务器 2~20 个并发请求(生产峰值约每台服务器 20 个)
  • 配置:max_num_seqs=64
  • 结果:60 多个空槽位产生额外开销

配置过大时的影响:

  • 为 64 个序列预分配 KV 缓存
  • vLLM 调度器管理 64 个槽位但实际只使用 5~10 个
  • 每 token 解码时间增加
  • 未使用的序列槽位浪费显存
  • 空槽位的调度器开销

调优方法

遵循 vLLM Continuous Batching 调优指南:

  1. 测量生产环境中的实际并发请求分布
  2. 从 max_num_seqs=1 开始,逐步增加:2 → 4 → 8 → 16 → 32
  3. 每一步监控解码时间和尾部延迟
  4. 性能下降时停止
max_num_seqs结果
8延迟良好但吞吐量受限
16最佳平衡
32解码时间增加,尾部延迟恶化

最终配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from translation_lib.config import AsyncVLLMTranslationProvider

provider = AsyncVLLMTranslationProvider(
    model_name=model_id,
    revision=model_revision,
    gpu_memory_utilization=0.3,  # RTX 5090 上约 10GB
    max_num_seqs=16,  # 匹配每台服务器的实际工作负载
    huggingface_token=hf_token,
    supported_language_pairs=None,  # 多语言模型
)

await provider.initialize_engine()

配置依据

max_num_seqs=16:

  • 生产峰值:每台服务器约 20 个并发请求
  • 测试:验证到 25 个并发
  • 在不浪费资源的前提下留有余量
  • 调度器开销与实际负载匹配

max_num_batched_tokens=8192:

  • 从默认的 16384 缩减
  • 更适合实际的平均序列长度
  • 降低显存压力

gpu_memory_utilization=0.3:

  • 在 RTX 5090(32GB)上为模型 + KV 缓存分配约 10GB VRAM
  • 通过 vllm:gpu_cache_usage_perc 追踪
  • 针对配置的平衡选择

注意: 原则:根据实际工作负载配置,而非理论上限。

图 4:全部优化尝试的吞吐量变化

生产结果

将优化配置部署到 RTX 5090 GPU 生产环境。

优化前 vs 优化后

指标优化前(多进程)优化后(优化的 AsyncLLM)变化
吞吐量9.0 RPS16.4 RPS+82%
GPU 使用率尖峰状(93% → 0% → 93%)稳定 90~95%稳定

图 5:显示吞吐量 82% 提升的生产部署结果

图 6:各优化尝试的 P95 延迟改善

图 7:可变长度输入下的响应时间变化

改善效果在生产中得以保持。 在真实流量下从 9 RPS 提升到 16.4 RPS。

总结

有效的方法

vLLM 的 Continuous Batching

  • AsyncLLMEngine 自动管理批处理
  • 无需手动批次收集开销
  • 与 FastAPI 直接 async/await 集成

合理的配置选择

  • max_num_seqs=16(匹配每台服务器的实际工作负载)
  • 而非 64(产生开销的理论最大值)
  • gpu_memory_utilization=0.3 分配 10GB

用真实数据测试

  • 可变长度输入暴露了配置问题
  • 均匀测试数据给出了误导性的 15 RPS 结果

监控 vLLM 指标

  • KV 缓存使用率
  • 每 token 解码时间
  • 队列深度
  • 作为配置决策的依据

完整改进历程

方案吞吐量对比基线备注
基线(多进程)2.2 RPS-IPC 开销,GPU 争用
双工作器2.0 RPS-9%反而变差
静态批处理5.9 RPS+168%队头阻塞
Async(64,均匀)15.0 RPS+582%误导性测试数据
Async(16,可变)3.5 RPS+59%真实但需调优
最终优化10.7 RPS+386%预发验证
生产16.4 RPS+82%真实流量,RTX 5090

相关文章: Part 1:翻译推理服务器扩展的瓶颈

分享这篇文章

免费试用 VoicePing

借助 AI 翻译跨越语言障碍。立即开始使用免费计划。

免费开始