397 lines
13 KiB
Markdown
397 lines
13 KiB
Markdown
# 方向2: KV Cache 与内存管理
|
||
|
||
## 1. 问题陈述
|
||
|
||
KV Cache是LLM推理中**最大的内存消费者**,也是RTOS内存管理的核心痛点:
|
||
|
||
```
|
||
KV Cache Size ≈ 2 × N_layers × batch_size × seq_len × hidden_dim × 2 bytes
|
||
|
||
Example (Qwen2.5-7B, batch=32, seq_len=4096):
|
||
= 2 × 32 × 32 × 4096 × 4096 × 2 bytes
|
||
≈ 6.8 GB
|
||
|
||
Example (Qwen2.5-1.5B, batch=8, seq_len=2048):
|
||
= 2 × 32 × 8 × 2048 × 2048 × 2 bytes
|
||
≈ 2.2 GB
|
||
```
|
||
|
||
RTOS场景下KV Cache管理的关键问题:
|
||
1. **内存碎片** — 频繁分配/释放导致碎片化
|
||
2. **带宽竞争** — KV读写与计算同时竞争DDR
|
||
3. **内存带宽瓶颈** — Attention是memory-bound而非compute-bound
|
||
4. **多请求KV Cache隔离** — 多流场景下的内存隔离与复用
|
||
|
||
### 1.1 本方向在总课题中的角色
|
||
|
||
本方向聚焦:
|
||
|
||
- 如何让人工智能目标负载在受限容量下保持可持续服务;
|
||
- 如何避免 KV Cache 动态增长破坏关键保障负载的实时边界;
|
||
- 如何把内存确定性、带宽隔离和准入控制纳入 SylixOS 的实时保障机制。
|
||
|
||
因此,这一方向服务的是总课题中的“容量边界与内存确定性”主线。
|
||
|
||
### 1.2 与验证矩阵的对应关系
|
||
|
||
KV Cache 与内存管理在 `T5~T1` 中的约束差异很大,因此需要按部署形态看重点:
|
||
|
||
| 部署形态 | 内存管理侧重点 |
|
||
|---|---|
|
||
| `T5` 控制端 | 极低内存预算下的模型裁剪与静态预分配 |
|
||
| `T4` 终端设备 | 小模型多会话下的碎片与带宽竞争 |
|
||
| `T3` 边缘节点 | 多请求并发下的 KV 隔离与带宽准入 |
|
||
| `T2` 单机工作站 | 大上下文与高吞吐下的容量边界 |
|
||
| `T1` 服务器/集群 | NUMA、多设备与跨节点缓存协同 |
|
||
|
||
## 2. KV Cache结构分析
|
||
|
||
### 2.1 KV Cache布局
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ KV Cache Layout │
|
||
│ │
|
||
│ Layer 0: [K_0 | V_0] → Shape: [batch, head, seq, hd] |
|
||
│ Layer 1: [K_1 | V_1] → Shape: [batch, head, seq, hd] |
|
||
│ ... │
|
||
│ Layer N: [K_N | V_N] → Shape: [batch, head, seq, hd] |
|
||
│ │
|
||
│ Total = 2 × N_layers × batch × seq × hidden × dtype |
|
||
└─────────────────────────────────────────────────────┘
|
||
|
||
Attention计算公式:
|
||
Output = Softmax(QK^T / √d_k) × V
|
||
|
||
Q, K, V均为实时维护的Tensor。推理过程中:
|
||
- Prefill阶段: K, V一次性填充
|
||
- Decode阶段: K, V逐tokenappend
|
||
```
|
||
|
||
### 2.2 KV Cache的内存访问特征
|
||
|
||
```
|
||
Access Pattern | Read | Write | Latency | Bandwidth
|
||
------------------------|-------|-------|---------|----------
|
||
Prefill K/V init | Low | High | Low | Very High
|
||
Decode K append | High | Medium| Medium | Medium
|
||
Attention QK^T | High | Low | Medium | High
|
||
Attention SV | High | High | Medium | High
|
||
```
|
||
|
||
**关键洞察**:Attention是 **memory-bound** 的,内存带宽比计算算力更容易成为瓶颈。
|
||
|
||
## 3. 内存管理策略
|
||
|
||
### 3.1 Memory Pool 设计
|
||
|
||
**核心思路**:为KV Cache预分配固定大小的内存池,避免运行时malloc/free。
|
||
|
||
```c
|
||
// KV Cache Memory Pool
|
||
typedef struct {
|
||
uint8_t *base; // 内存池基地址
|
||
size_t total_size; // 总大小
|
||
size_t used_size; // 已使用
|
||
size_t max_block; // 最大block大小
|
||
uint32_t n_blocks; // block数量
|
||
uint32_t *free_map; // 空闲块位图
|
||
uint32_t alloc_count; // 当前分配次数
|
||
} kv_cache_pool_t;
|
||
|
||
// Block结构 — 每个layer一个block
|
||
typedef struct {
|
||
struct kv_cache_pool_t *pool;
|
||
uint8_t *data; // 数据指针
|
||
size_t size; // 大小
|
||
uint32_t layer_id; // 所属layer
|
||
uint32_t batch_id; // 所属batch
|
||
bool locked; // 是否锁定(防释放)
|
||
} kv_block_t;
|
||
```
|
||
|
||
**Pool设计决策**:
|
||
|
||
| 策略 | 描述 | 优点 | 缺点 |
|
||
|-----|------|-----|-----|
|
||
| **Fixed-size pool** | 预分配固定大小,不动态扩容 | 零碎片,O(1)分配 | 可能浪费或不够用 |
|
||
| **Buddy system** | powers-of-2分块 | 低碎片 | 内碎片最多50% |
|
||
| **Slab allocator** | 预分配固定类型cache | 高效同类分配 | 不同size需多个slab |
|
||
| **Region pool** | 按请求分配region | 便于多请求隔离 | region间可能碎片 |
|
||
|
||
**推荐**: 在嵌入式场景用Fixed-size pool,在边缘场景用Slab + Region组合。
|
||
|
||
### 3.2 分层KV Cache管理
|
||
|
||
```
|
||
┌──────────────────────────────────────────────┐
|
||
│ L1: SRAM Cache (芯片内, ~100KB) │
|
||
│ 最近访问的KV block, 超低延迟(<10ns) │
|
||
│ 策略: LRU, hardware-managed │
|
||
├──────────────────────────────────────────────┤
|
||
│ L2: DDR Memory Pool (RAM, 2-8GB) │
|
||
│ 所有KV Cache, 中延迟(~100ns) │
|
||
│ 策略: Slab allocator + region per request │
|
||
├──────────────────────────────────────────────┤
|
||
│ L3: Flash/SSD (持久化, 10-100GB) │
|
||
│ 冷KV Cache, 高延迟(~100μs) │
|
||
│ 策略: 按需swap, 预读 │
|
||
└──────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 3.3 多请求KV Cache隔离
|
||
|
||
```
|
||
Request A: [Layer 0 Block | Layer 1 Block | ...] → Pool Region A
|
||
Request B: [Layer 0 Block | Layer 1 Block | ...] → Pool Region B
|
||
Request C: [Layer 0 Block | Layer 1 Block | ...] → Pool Region C
|
||
|
||
Region管理:
|
||
┌──────────┬──────────┬──────────┬──────────┐
|
||
│ Region A │ Region B │ Region C │ Free │
|
||
│ 256MB │ 128MB │ 256MB │ 512MB │
|
||
└──────────┴──────────┴──────────┴──────────┘
|
||
|
||
每个Region包含:
|
||
- Header: 大小、引用计数、是否锁定
|
||
- Data: KV Cache数据
|
||
- Metadata: 序列长度、是否有效、引用task
|
||
```
|
||
|
||
## 4. 内存带宽优化
|
||
|
||
### 4.1 带宽竞争模型
|
||
|
||
```
|
||
总带宽 = DDR Bandwidth (e.g., LPDDR5: 6400 Mbps × 4 = 25.6 GB/s)
|
||
|
||
带宽分配:
|
||
KV Cache Read (Attention): █████████████████████ 40%
|
||
KV Cache Write (Append): ████████████ 25%
|
||
Weight Read (FFN/Attn): ████████████████████ 30%
|
||
Input/Output: ████████ 5%
|
||
|
||
问题: KV Cache读+写 = 65% 带宽
|
||
加上Weight Read = 95% 带宽
|
||
仅剩5% 给其他任务
|
||
```
|
||
|
||
### 4.2 带宽调度策略
|
||
|
||
**策略1: 错峰访问**
|
||
|
||
```
|
||
Time →
|
||
KV Cache Write: [████████][ ][████████]
|
||
Weight Read: [ ][████████████][ ]
|
||
Input/Output: [███████][ ][ ]
|
||
t0 t1 t2
|
||
```
|
||
|
||
**策略2: DMA Batching**
|
||
|
||
```
|
||
Before (no batching):
|
||
KV Write 1 → DMA → DDR (small chunk)
|
||
KV Write 2 → DMA → DDR (small chunk)
|
||
KV Write 3 → DMA → DDR (small chunk)
|
||
Total DMA overhead: 3 × overhead
|
||
|
||
After (batching):
|
||
KV Write 1,2,3 → DMA burst → DDR (large chunk)
|
||
Total DMA overhead: 1 × overhead
|
||
|
||
RTOS实现:
|
||
1. KV Write task将多个小DMA请求放入队列
|
||
2. DMA Controller task批量处理
|
||
3. 使用RTOS queue传递DMA描述符
|
||
```
|
||
|
||
**策略3: Bandwidth-aware Scheduling**
|
||
|
||
```c
|
||
typedef struct {
|
||
uint64_t bandwidth_allocated; // 已分配带宽
|
||
uint64_t bandwidth_used; // 已使用带宽
|
||
uint64_t peak_bandwidth; // 峰值带宽
|
||
uint64_t window_ms; // 滑动窗口大小
|
||
} bandwidth_tracker_t;
|
||
|
||
// 任务请求带宽时的检查
|
||
bool can_allocate_bandwidth(task_t *task, uint64_t required_bw) {
|
||
if (bandwidth_tracker.used + required_bw <= BANDWIDTH_LIMIT) {
|
||
bandwidth_tracker.used += required_bw;
|
||
return true;
|
||
}
|
||
// 等待或排队
|
||
vTaskSuspend(task);
|
||
return false;
|
||
}
|
||
```
|
||
|
||
### 4.3 In-Place KV Update
|
||
|
||
减少KV Cache的write bandwidth:
|
||
|
||
```
|
||
Before (old):
|
||
for each new token:
|
||
allocate new KV block // 分配+写
|
||
write new KV values // 写
|
||
update pointer // 写
|
||
|
||
After (in-place):
|
||
for each new token:
|
||
KV_buffer += step_size // 指针移动(O(1))
|
||
write KV values in-place // 只写一次
|
||
```
|
||
|
||
**实现要点**:
|
||
- 预分配连续的KV内存(避免碎片)
|
||
- 使用环形buffer管理seq_len(自然复用)
|
||
- 维护valid长度指针,不实际移动数据
|
||
|
||
## 5. KV Cache替换策略
|
||
|
||
### 5.1 缓存淘汰算法
|
||
|
||
```
|
||
当KV Cache满时,需要选择替换策略:
|
||
|
||
策略 | 复杂度 | 命中率 | 适用场景
|
||
----------------------|--------|--------|---------
|
||
LRU | O(1) | Good | 通用
|
||
LFU | O(log n)| Better | 长对话
|
||
Sliding Window | O(1) | Good | 固定上下文
|
||
Hybrid (Window+LFU) | O(1) | Best | 生产环境
|
||
```
|
||
|
||
**RTOS友好实现** (LRU with doubly-linked list):
|
||
|
||
```c
|
||
typedef struct kv_cache_node {
|
||
uint32_t layer;
|
||
uint32_t token_pos;
|
||
struct kv_cache_node *prev;
|
||
struct kv_cache_node *next;
|
||
uint8_t *data;
|
||
uint64_t last_access;
|
||
} kv_cache_node_t;
|
||
|
||
// LRU: 访问时移动到链表头部
|
||
void kv_cache_access(uint32_t layer, uint32_t pos) {
|
||
kv_cache_node_t *node = find_node(layer, pos);
|
||
if (node) {
|
||
node->last_access = get_tick_ms();
|
||
move_to_head(&lru_list, node); // O(1)
|
||
}
|
||
}
|
||
|
||
// Evict: 从链表尾部淘汰
|
||
void kv_cache_evict(void) {
|
||
kv_cache_node_t *victim = lru_list.tail;
|
||
remove_from_list(victim);
|
||
free_block(victim);
|
||
}
|
||
```
|
||
|
||
### 5.2 压缩策略
|
||
|
||
```
|
||
策略 | 压缩比 | 精度损失 | 带宽节省 | 计算开销
|
||
--------------|--------|----------|----------|---------
|
||
Float16 | 1.0x | 0% | 基准 | 无
|
||
Int8 | 2.0x | ~2% | 50% | 低
|
||
Int4 | 4.0x | ~5% | 75% | 中
|
||
Sparsification| 2-8x | 1-10% | 50-87% | 中-高
|
||
Paged KV | variable| 0% | variable | 低
|
||
```
|
||
|
||
## 6. 各硬件平台的内存管理差异
|
||
|
||
### 6.1 MCU级 (256KB~2MB)
|
||
|
||
```
|
||
约束:
|
||
- 内存极小,无法容纳完整KV Cache
|
||
- 通常只支持一个请求
|
||
- 模型参数也需压缩
|
||
|
||
策略:
|
||
- KV Cache固定大小(如256 token)
|
||
- 使用连续内存池,零碎片
|
||
- KV Cache满后触发OOM处理(丢弃 oldest)
|
||
- 可能需要在Flash上swap
|
||
```
|
||
|
||
### 6.2 SoC级 (2-8GB)
|
||
|
||
```
|
||
约束:
|
||
- 内存有限但可容纳小模型KV Cache
|
||
- 多请求时内存竞争严重
|
||
- NPU有独立的memory pool
|
||
|
||
策略:
|
||
- 分区管理: CPU pool + NPU pool
|
||
- 使用Paged Memory (类似vLLP)
|
||
- NPU-Shared Memory通过DMA同步
|
||
```
|
||
|
||
### 6.3 Edge盒子 (8-16GB)
|
||
|
||
```
|
||
约束:
|
||
- 内存充足但带宽可能受限
|
||
- 多请求+大模型场景
|
||
|
||
策略:
|
||
- 分层缓存: SRAM(热点) + DDR(主流) + SSD(冷数据)
|
||
- 预读策略: 预测下一个token的KV位置
|
||
- 多流隔离: CGroup级别内存限制
|
||
```
|
||
|
||
### 6.4 Server级
|
||
|
||
```
|
||
约束:
|
||
- 内存充足(NVMe/DDR)
|
||
- 主要问题是带宽和NUMA
|
||
|
||
策略:
|
||
- NUMA-aware KV placement
|
||
- NVMe作为KV Cache扩展
|
||
- 多GPU间KV Cache同步
|
||
```
|
||
|
||
## 7. 本方向的验证关注点
|
||
|
||
为了让本方向与总课题的双目标评价框架对齐,KV Cache 与内存管理至少要回答下面三个问题:
|
||
|
||
1. 人工智能目标负载在不同上下文长度和并发度下是否仍保持可接受的 `TTFT`、`TPOT` 与成功率;
|
||
2. 关键保障负载是否因内存碎片、带宽争用或回收抖动而出现 `deadline miss` 或尾部抖动放大;
|
||
3. 内存池、分页、压缩和准入机制是否建立了可预测的容量边界与准入边界。
|
||
|
||
## 8. 关键设计决策
|
||
|
||
| 决策点 | 选项 | 推荐 | 理由 |
|
||
|-------|------|-----|------|
|
||
| 内存分配器 | malloc/free / Pool / Slab | **Slab + Region** | 零碎片+多请求隔离 |
|
||
| KV Cache布局 | 连续 / Paged / Sparse | **Paged** | 灵活+低碎片 |
|
||
| 替换策略 | LRU / LFU / Sliding | **Sliding Window** | 计算复杂度O(1) |
|
||
| 压缩策略 | FP16 / Int8 / Int4 | **Int8** | 精度-带宽权衡最优 |
|
||
| 带宽管理 | 固定分配 / 动态 | **Bandwidth-aware** | 避免带宽饱和 |
|
||
| 多流隔离 | 共享池 / Region | **Region** | 公平+可分析 |
|
||
|
||
## 9. 开放研究问题
|
||
|
||
1. **Dynamic KV Cache Sizing**: 运行时根据负载自动调整KV Cache大小?
|
||
2. **Cross-device KV**: 在CPU-NPU间共享/分发KV Cache?
|
||
3. **KV Cache Compression**: 有损压缩的RTOS友好实现?
|
||
4. **Predictive Allocation**: 预测最大seq_len,预分配最优大小?
|
||
5. **KV Cache Migration**: 请求迁移时的KV Cache热迁移?
|
||
|
||
---
|
||
|
||
*最后更新: 2026-09-17*
|