Files
Notes/机器学习/深度学习模型/07-计算架构/3-推理优化/1-KV-Cache与Continuous-Batching.md
2026-05-16 17:16:51 +08:00

259 lines
6.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# KV Cache与Continuous Batching
## KV Cache原理
### 自回归生成的挑战
在自回归Autoregressive语言模型生成中每个token的预测需要依赖之前所有token
```
生成第t个token: P(token_t | token_1, token_2, ..., token_{t-1})
```
朴素实现的问题每步生成都需要重新计算所有历史token的注意力计算量随序列长度平方增长。
### KV Cache解决方案
KV Cache通过缓存已计算的Key和Value向量避免重复计算
```
Layer i 的 KV Cache:
- K_cache[i]: (batch, heads, seq_len, head_dim)
- V_cache[i]: (batch, heads, seq_len, head_dim)
```
**前向传播修改**
```python
def forward_with_kvcache(layer, x, past_key_value=None):
# x: (batch, seq_len, hidden)
if past_key_value is None:
# 首次生成,初始化缓存
k, v = compute_kv(x) # 完整计算
past_kv = (k, v)
else:
# 后续生成只计算新token
k_new, v_new = compute_kv(x[:, -1:]) # 只处理最后一个token
past_kv = update_kvcache(past_key_value, k_new, v_new)
k, v = past_kv # 完整K, V用于注意力计算
# 注意力计算
attn_output = attention(q, k, v)
return attn_output, past_kv
```
### 计算量对比
| 方法 | 注意力计算复杂度 | 序列长度S处计算量 |
|------|----------------|-----------------|
| 朴素无Cache | O(S²) | 每步重新计算历史 |
| KV Cache | O(S) per step | 只计算新token |
| 节省比例 | - | S/(S + 1) → 接近1长序列 |
## KV Cache内存占用
### 内存计算公式
```
KV Cache显存 = 2 × batch_size × num_layers × num_heads × seq_len × head_dim × bytes_per_param
FP16时 bytes_per_param = 2
```
### 示例计算
| 模型配置 | 参数 | 批量=1, 序列长度=2048 |
|---------|------|----------------------|
| LLaMA-7B (32层, 32头, 128维) | 2×32×32×2048×128×2 | ~512MB |
| LLaMA-13B (40层, 40头, 128维) | 2×40×40×2048×128×2 | ~1GB |
| LLaMA-65B (80层, 80头, 128维) | 2×80×80×2048×128×2 | ~4GB |
批量大于1时显存占用线性增加成为推理瓶颈。
### 内存优化技术
1. **序列并行**将KV cache分片到多个GPU
2. **页式管理**vLLM的Paged Attention见专题
3. **MQA/GQA**减少KV头数量见下文
## Multi-Query Attention (MQA) 与 Grouped-Query Attention (GQA)
### 标准Multi-Head Attention (MHA)
每个注意力头都有独立的K、V矩阵
```
K_heads: (batch, heads, seq, head_dim)
V_heads: (batch, heads, seq, head_dim)
计算量: heads × seq²
```
### Multi-Query Attention (MQA)
所有注意力头共享同一份K、V
```
K: (batch, 1, seq, head_dim) # 只有一个头
V: (batch, 1, seq, head_dim)
Query: (batch, heads, seq, head_dim)
```
**优点**KV Cache显存降至 1/heads
**缺点**:可能降低模型表达能力
### Grouped-Query Attention (GQA)
MHA和MQA的折中方案
```
num_kv_heads = num_query_heads / ratio (ratio通常为4-8)
LLaMA 2使用: 32 query heads, 8 kv heads
```
### 显存对比
| 注意力类型 | KV Cache大小相对MHA |
|-----------|----------------------|
| MHA (32 heads) | 1x |
| GQA (8 kv heads) | 0.25x |
| MQA (1 kv head) | 1/32 x |
## Continuous Batching动态批处理
### Static Batching静态批处理的问题
传统方法将多个请求组成固定大小的batch一起处理
```
问题1: 不同请求序列长度差异大
问题2: 短序列需等待长序列完成
问题3: GPU资源利用率低
```
```
Static Batch Example:
Batch = [req1(len=100), req2(len=500), req3(len=200)]
所有请求需等待最长的req2完成 → 大量空闲等待
```
### Continuous Batching原理
Continuous Batching在iteration级别进行调度新请求可随时加入完成的请求立即退出
```python
def continuous_batching_scheduler():
running_requests = []
while True:
# 1. 检查完成的请求
finished = [r for r in running_requests if r.is_done()]
for r in finished:
yield r.output
free_slot(r)
# 2. 尝试加入新请求
if has_pending_requests() and has_free_slot():
new_req = get_next_request()
running_requests.append(new_req)
# 3. 执行一步迭代
for req in running_requests:
req.step() # 生成一个token
# 4. 同步(如果有依赖)
synchronize_if_needed()
```
### 对比分析
| 特性 | Static Batching | Continuous Batching |
|------|----------------|---------------------|
| 调度时机 | batch开始时 | 每个iteration |
| 序列结束处理 | 等待batch内全部完成 | 立即释放 |
| GPU利用率 | 较低(有空闲) | 较高 |
| 延迟 | 取决于最长请求 | 取决于平均长度 |
| 吞吐 | 受限于单个长序列 | 充分利用GPU |
### 实现框架
```python
# vLLM Continuous Batching示例
from vllm import LLM, SamplingParams
llm = LLM(model="meta-llama/Llama-2-7b-hf")
sampling_params = SamplingParams(temperature=0.8, top_p=0.95)
# 提交多个请求
prompts = ["Hello", "Hi", "How are you"]
outputs = llm.generate(prompts, sampling_params)
# vLLM自动处理Continuous Batching
for output in outputs:
print(output.outputs[0].text)
```
### Iteration-level调度详解
```
时间步 t:
Batch = [req1, req2, req3, req4]
生成: [tok1, tok2, tok3, tok4]
时间步 t+1:
req3 完成 → 移除
req5 新加入
Batch = [req1, req2, req4, req5]
生成: [tok1, tok2, tok4, tok5]
```
### 调度策略
1. **First-Come-First-Served (FCFS)**:按请求到达顺序处理
2. **Priority Scheduling**:高优先级请求优先
3. **Preemption**:当显存不足时,暂停低优先级请求
### 与Prefix Caching的结合
Continuous Batching可与Prefix Caching配合
```python
# 共享系统提示前缀
system_prompt = "You are a helpful assistant."
# 多用户共享相同前缀 → KV Cache可复用
req1 = system_prompt + "User1 question"
req2 = system_prompt + "User2 question"
# Prefix KV Cache只需计算一次
```
## 系统设计考量
### 显存管理策略
| 策略 | 描述 | 适用场景 |
|------|------|---------|
| 静态分配 | 预分配固定KV Cache大小 | 简单实现 |
| 动态分配 | 按需分配/释放 | 通用场景 |
| Paged Attention | 分页管理(见专题) | 高吞吐 |
### Batch大小选择
```
最大batch_size = GPU显存 / (单请求KV Cache + 模型参数)
对于7B模型FP16约20GB显存用于模型
剩余约20GB → 最大batch_size受KV Cache限制
```
### 延迟-吞吐权衡
```
高延迟 + 高吞吐: 大量请求排队大batch处理
低延迟 + 低吞吐: 小batch及时处理短等待队列
```
Continuous Batching通过动态调整实现两者平衡。