LLM Model Basic

导言

LLM Prefill、decode、kvcache等概念

Prefill and Decode

在大型语言模型(LLM)的推理过程中,Prefill(预填充)Decode(解码)是两个核心阶段,分别承担不同的计算任务和资源需求。以下是两者的详细对比:


1. Prefill阶段

功能:处理用户输入的完整提示(prompt),生成首个输出token,并构建初始的KV Cache(Key-Value缓存)。
输入:用户输入的完整token序列(例如“天空为什么是蓝色的?”分词后的多个token)。
输出
首个生成token(如“阳光”);
KV Cache:存储所有输入token的Key和Value向量,用于后续解码阶段的注意力计算。

计算特点

  1. 并行计算:由于输入的所有token已知,模型通过矩阵乘法(GEMM)并行计算自注意力,生成每个token的Key和Value向量。
  2. 计算密集型:消耗大量计算资源(如GPU算力),尤其长prompt时耗时显著增加。
  3. 显存占用:KV Cache的显存占用与输入token数正相关(例如Llama-7B模型处理128个token需约134MB显存)。

示例:输入“天空为什么是蓝色的?”(6个token),Prefill阶段完成所有6个token的KV缓存计算,并生成首个输出token“阳光”。


2. Decode阶段

功能:基于KV Cache自回归生成后续token,直至达到终止条件(如输出结束符<eos>或最大生成长度)。
输入:每次仅输入当前生成的单个token(如“阳光”→“中”→“的”…)。
输出
下一个生成token
更新的KV Cache:将新token的Key和Value追加到缓存中。

计算特点

  1. 串行生成:每次仅处理一个token,通过矩阵向量乘法(GEMV)计算注意力,依赖历史KV Cache。
  2. 内存密集型:频繁读写显存中的KV Cache,显存带宽成为瓶颈(例如Llama-7B生成每个token需加载约14GB参数)。
  3. 延迟敏感:每个token的生成时间(TPOT)直接影响用户体验,通常要求低于50ms。

示例:生成“阳光”后,模型读取缓存中所有7个token(原6个+新1个)的KV向量,计算下一个token“中”,并更新缓存。


对比与优化挑战

维度 Prefill阶段 Decode阶段
计算类型 计算密集型(GEMM) 内存密集型(GEMV)
并行性 高(全token并行) 低(单token串行)
显存压力 初始KV缓存构建 动态KV缓存增长与频繁访问
优化方向 算子融合、张量并行 KV缓存压缩、量化、连续批处理
关键指标 首Token延迟(TTFT) 每Token延迟(TPOT)

典型问题与解决方案
显存碎片:vLLM的PagedAttention技术通过分页管理KV Cache,减少碎片。
长上下文瓶颈:Mooncake架构提出预填充-解码分离设计,利用分布式存储优化显存占用。
吞吐量提升:连续批处理(Continuous Batching)动态调度请求,提高GPU利用率。


总结

Prefill阶段是模型“理解”用户输入的过程,通过并行计算快速构建上下文;
Decode阶段是模型“生成”响应的过程,依赖高效显存管理和低延迟计算。
两者共同决定LLM推理的吞吐量和响应速度,优化需结合计算、存储和调度策略。

Prefill和Decode的差异

在大型语言模型(LLM)推理过程中,PrefillDecode阶段的网络结构虽然相同,但代码实现存在显著差异,主要体现在输入处理方式、计算优化策略和缓存管理上。以下是具体分析及典型开源仓库示例:


一、代码实现差异的核心点

1. 输入处理与计算模式

Prefill阶段
输入:一次性处理完整的用户输入序列(如整个提示词),需将所有token并行编码为嵌入向量,并计算全局自注意力。
代码实现
◦ 使用矩阵乘法(GEMM)批量处理所有token的注意力计算,例如通过torch.bmm实现全序列的QKV矩阵并行计算。
◦ 生成完整的KV缓存(Key-Value Cache),存储所有输入token的键值对,供后续Decode阶段复用。
示例代码片段

1
2
3
4
5
# Prefill阶段的注意力计算(伪代码)
q = linear(query_emb) # 全序列并行计算Query
k = linear(key_emb) # 全序列并行计算Key
v = linear(value_emb) # 全序列并行计算Value
attn_output = scaled_dot_product_attention(q, k, v) # 全局注意力

Decode阶段
输入:每次仅处理一个生成的token,基于历史KV缓存进行增量计算。
代码实现
◦ 使用矩阵向量乘法(GEMV)逐个生成token,例如通过torch.mv实现单token的Q与历史K的点积。
KV缓存的动态更新:将新token的Key和Value追加到缓存中,避免重复计算。
示例代码片段

1
2
3
4
5
6
# Decode阶段的注意力计算(伪代码)
new_q = linear(new_token_emb) # 仅处理当前token的Query
attn_weights = new_q @ cached_k.transpose() # 仅计算当前token与历史K的点积
attn_output = attn_weights @ cached_v # 基于历史V生成输出
cached_k = torch.cat([cached_k, new_k], dim=1) # 增量更新KV缓存
cached_v = torch.cat([cached_v, new_v], dim=1)

2. 缓存管理与显存优化

Prefill:需一次性为所有输入token分配显存存储KV缓存,显存占用与输入长度正相关。
Decode:通过分页缓存(如vLLM的PagedAttention)动态管理显存,支持长序列生成和并发请求的高效处理。

3. 批处理与并行策略

Prefill:支持静态批处理(Static Batching),将多个请求的输入序列合并为一个大矩阵并行处理,提升计算效率。
Decode:采用连续批处理(Continuous Batching),动态调度不同长度的生成请求,避免GPU资源空闲。


二、典型开源仓库及实现特点

以下仓库针对Prefill和Decode阶段的差异进行了针对性优化:

1. vLLM

核心优化
PagedAttention:将KV缓存分页管理,支持动态显存分配,显著提升长序列生成效率。
Decode阶段异步调度:通过连续批处理实现高吞吐量,减少GPU空闲时间。
代码差异示例
◦ Prefill阶段调用execute_model_prefill函数处理全序列,生成初始缓存。
◦ Decode阶段调用execute_model_decode函数逐个生成token,并更新分页缓存。

2. Hugging Face Transformers

实现方式
◦ Prefill通过model.generate(input_ids)一次性处理输入,Decode通过自回归循环调用model.generate()生成后续token。
◦ 提供past_key_values参数传递历史KV缓存,避免重复计算。

3. TensorRT-LLM

优化重点
◦ 为Prefill阶段生成高度优化的计算图(如融合注意力核函数),减少内存访问开销。
◦ 在Decode阶段使用Tensor Core加速GEMV运算,提升单token生成速度。


三、总结

Prefill与Decode的代码差异本质上是并行计算与串行生成的权衡,前者侧重计算密集型任务,后者依赖显存带宽和调度优化。
• 开源仓库通过分页缓存、连续批处理、计算图优化等技术,在工程层面解决了两阶段的性能瓶颈。实际开发中,需根据场景需求选择适配的框架(如高吞吐选vLLM,低延迟选TensorRT-LLM)。

Author

Shaojie Tan

Posted on

2023-12-18

Updated on

2025-12-17

Licensed under