大型语言模型(LLM)的推理过程通常分为两个截然不同的计算阶段:预填充(Pre-fill)和生成(Decode)。这两个阶段的计算和资源需求特性存在巨大差异,如果在同一块GPU上混合执行,往往会导致资源利用率低下,尤其是在高并发的服务环境中。
PD分离架构(Pre-fill/Decode Separation)旨在将这两个阶段解耦,并将其调度到最适合的硬件资源上运行,特别是在多显卡环境中,可以分别部署在不同的GPU上,以实现资源匹配与性能优化。
1. Pre-fill(预填充)阶段的特性与需求
特性:
* 处理用户的输入提示(Prompt)。
* 计算量与输入序列长度成正比,通常是带宽密集型(Bandwidth-bound)。
* 需要一次性计算所有输入Token的KV Cache。
* 通常以较大的“逻辑批次”(即输入序列长度)运行。
理想硬件: 拥有高显存带宽(HBM)的GPU,能够快速处理大批量数据写入KV Cache。
2. Decode(生成)阶段的特性与需求
特性:
* 迭代地生成单个或少数几个新Token。
* 计算量与模型参数量成正比,通常是计算密集型(Compute-bound),且延迟敏感。
* 批次大小通常较小(高并发下除外,但单次迭代的Token数通常为1)。
理想硬件: 拥有高核心频率、为低延迟计算优化的GPU。
3. 不同显卡分离运行的收益分析
当我们将Pre-fill任务分配给GPU A,将Decode任务分配给GPU B时,可以获得以下核心收益:
3.1 提高资源利用率与吞吐量
在传统的单GPU架构中,一个请求必须等待其Pre-fill完成后,才能进行Decode。而当Pre-fill和Decode分离后,GPU A(Pre-fill)可以并行处理多个新进入系统的长Prompt,快速完成KV Cache的构建;与此同时,GPU B(Decode)可以持续地处理已完成Pre-fill请求的Token生成任务。
这种并行化消除了阶段间的等待时间,使得两块GPU都能达到更高的平均利用率,从而显著提升系统的总吞吐量(Tokens/s)。
3.2 优化用户感知延迟
LLM推理的用户体验主要取决于首个Token生成时间(TTFT, Time-To-First-Token)和后续Token生成时间(TPOT, Time-Per-Output-Token)。
- TTFT 主要受限于Pre-fill阶段。通过专用GPU A快速处理Pre-fill,可以缩短TTFT。
- TPOT 主要受限于Decode阶段。通过专用GPU B运行Decode,可以确保该阶段不受GPU A上Pre-fill的突发高负载干扰,保证低且稳定的TPOT,从而提升用户等待体验。
4. 架构实现:调度器逻辑示例
实现PD分离的关键在于一个智能的调度器,如vLLM等现代推理框架所采用的机制。调度器需要维护两个独立的任务队列,并将任务动态地分配给Pre-fill GPU集群和Decode GPU集群。
下面是一个简化的Python伪代码,展示了任务如何在两个分离的“资源”间流转:
import time
import threading
from queue import Queue
# 定义任务队列
prefill_queue = Queue() # 待处理的Prompt
decode_queue = Queue() # 已完成Pre-fill,待生成的请求
class Request:
def __init__(self, prompt_len, req_id):
self.prompt_len = prompt_len
self.req_id = req_id
self.k_v_cache = None
# 模拟 Pre-fill GPU (GPU A) Worker
def prefill_worker(gpu_id):
while True:
request = prefill_queue.get()
if request is None: break
# 模拟 Pre-fill 过程:耗时取决于Prompt长度 (带宽密集)
process_time = request.prompt_len * 0.005
time.sleep(process_time)
# 假设生成了KV Cache
request.k_v_cache = f"Cache_{request.req_id}"
print(f"[GPU {gpu_id} P] Request {request.req_id}: Prefill completed in {process_time:.3f}s")
# 任务转移到 Decode 队列
decode_queue.put(request)
prefill_queue.task_done()
# 模拟 Decode GPU (GPU B) Worker
def decode_worker(gpu_id):
while True:
request = decode_queue.get()
if request is None: break
# 模拟 Decode 过程:低延迟,连续生成10个Token (计算密集)
for token_idx in range(1, 11):
# 假设每个Token耗时固定,且稳定
token_time = 0.02
time.sleep(token_time)
print(f"[GPU {gpu_id} D] Request {request.req_id}: Generated Token {token_idx}")
decode_queue.task_done()
# --- 运行模拟 ---
if __name__ == '__main__':
# 启动 Pre-fill Worker (可以启动多个,体现并行处理长Prompt)
p_thread = threading.Thread(target=prefill_worker, args=(0,))
# 启动 Decode Worker (可以启动多个,保证低延迟生成)
d_thread = threading.Thread(target=decode_worker, args=(1,))
p_thread.start()
d_thread.start()
# 提交任务 (长Prompt任务)
requests = [
Request(prompt_len=200, req_id=101),
Request(prompt_len=500, req_id=102),
Request(prompt_len=300, req_id=103),
]
for req in requests:
prefill_queue.put(req)
# 等待所有任务完成
prefill_queue.join()
decode_queue.join()
# 停止Worker
prefill_queue.put(None)
decode_queue.put(None)
p_thread.join()
d_thread.join()
总结
PD分离架构是提高LLM推理服务效率的关键手段之一。通过将带宽敏感的Pre-fill阶段与延迟敏感的Decode阶段分配给不同的、优化的硬件资源(不同显卡),我们可以实现更精细化的资源调度,最终达到更高的系统吞吐量和更稳定的低延迟用户体验。这种架构对于构建高并发、低延迟的LLM生产环境至关重要。
汤不热吧