大规模语言模型(LLM)的推理性能是部署成功的关键。在推理过程中,模型通常经历两个截然不同的阶段:Prefill(预填充/上下文处理)和Decode(解码/生成)。理解这两个阶段的资源需求和冲突,是优化吞吐量(Throughput)和首字节时延(Latency)平衡的关键。
1. Prefill与Decode阶段概述
1.1 Prefill阶段(Context Phase)
- 目标: 计算输入提示(Prompt)的所有Token的Key和Value向量,并将其存储到K/V Cache中。
- 资源瓶颈: 通常受限于显存带宽(Memory Bandwidth Bound)。因为需要对大量Token进行矩阵乘法和Attention计算。
- 优化指标: 关注每秒处理的输入Token数量(Input Tokens/sec)。
1.2 Decode阶段(Generation Phase)
- 目标: 逐个生成新的Token,每个新Token的计算依赖于K/V Cache中已有的内容。
- 资源瓶颈: 通常受限于计算(Compute Bound)或缓存访问延迟。因为Batch Size通常较小(每个请求只生成1个新Token)。
- 优化指标: 关注首字节时延(Time to First Token, TTFT)和每秒生成的输出Token数量(Output Tokens/sec)。
2. 核心冲突:长Prefill请求的阻塞效应
传统的批处理(Static Batching)方法要求批次中的所有请求要么同时完成Prefill,要么同时等待。如果一个批次中包含一个极长的Prefill请求,即使后面有许多短小的、高优先级的Decode请求,它们也必须等待Prefill请求占用大量的计算资源和时间,导致短请求的TTFT显著增加。
博弈点:
* 高吞吐量(High Throughput): 倾向于将多个长Prefill请求打包在一起,以最大化显存带宽利用率。
* 低时延(Low Latency): 倾向于优先服务单个Token的Decode请求,以快速响应用户。
3. 解决方案:连续批处理(Continuous Batching)
为了解决Prefill和Decode之间的冲突,现代推理引擎(如vLLM、TensorRT-LLM)采用了连续批处理(Continuous Batching,或称Dynamic Batching)技术。
连续批处理的工作原理:
- 动态调度: 调度器不再等待整个批次完成,而是根据GPU的实时负载和K/V Cache的分配情况,在每次迭代中动态地添加或移除请求。
- 资源共享: 允许Prefill和Decode请求在同一个GPU批次中并行执行。
- 例如,一个长Prefill请求可能只完成了一半,但调度器可以立即将两个短Decode请求加入到下一个时间步的Batch中。
- K/V Cache高效管理: 配合使用Paged Attention(类似操作系统中的分页内存管理),确保K/V Cache的物理内存块可以灵活分配和共享,避免内存碎片化,从而支持动态扩展Batch。
4. 实操:通过VLLM配置优化平衡
VLLM是目前最流行的基于Paged Attention的开源推理引擎之一。我们可以通过调整其参数来影响调度器在吞吐量和时延之间的权衡。
以下是一个使用VLLM的Python配置示例,并重点说明了影响性能的关键参数。
from vllm import LLM, SamplingParams
# 假设模型和GPU已经就绪
# 初始化LLM配置
# VLLM默认启用连续批处理和Paged Attention
llm = LLM(
model="meta-llama/Llama-2-7b-chat-hf",
# 1. GPU显存管理:K/V Cache占用的最大比例
# 增加该值可以容纳更多或更长的序列(提高吞吐量,但可能牺牲系统稳定性)
gpu_memory_utilization=0.90,
# 2. 最大批次大小的限制(对吞吐量有直接影响)
max_model_len=4096
)
# 3. 调度器配置(通过环境变量或启动参数设置,影响Prefill/Decode的配比)
# 虽然VLLM的调度器是高度自动化的,但我们可以通过限制并发序列数来调整负载。
# 限制最大序列数,如果设置过低,会优先处理Decode请求(降低TTFT,牺牲总吞吐量)。
# VLLM默认情况下会尝试平衡,但可以手动调整请求配额或超时策略来微调。
# 示例:长Prefill任务
long_prompt = "撰写一篇关于量子计算未来挑战的详细文章,至少需要1024个Token。"
long_sampling = SamplingParams(max_tokens=1024, temperature=0.0)
# 示例:短Decode任务 (低时延要求)
short_prompt = "计算 5 * 10 结果是多少?"
short_sampling = SamplingParams(max_tokens=5, temperature=0.0)
# 在连续批处理中,短请求不会被长Prefill请求完全阻塞,
# 而是会被快速调度进入下一个Decode步,从而实现低TTFT。
print("开始并行推理 (VLLM自动调度 Prefill 和 Decode)...")
outputs = llm.generate(
prompts=[long_prompt, short_prompt],
sampling_params=[long_sampling, short_sampling]
)
# 性能观察指标:
# - Total Throughput (Tokens/s): 衡量整体效率。
# - TTFT (Time to First Token) for short prompts: 衡量时延性能。
5. 总结
Prefill与Decode阶段的博弈是LLM推理优化的核心。Prefill追求带宽和批次效率,影响总体吞吐量;Decode追求低时延,影响用户体验。通过采用连续批处理和Paged Attention等现代技术,推理引擎能够智能地在单个GPU批次中交错处理不同阶段和长度的请求,从而在保证高吞吐量的同时,显著降低了关键的TTFT。
汤不热吧