428 lines
17 KiB
Markdown
428 lines
17 KiB
Markdown
# 方向3: 加速器协同调度 (CPU + NPU/GPU)
|
||
|
||
## 1. 问题陈述
|
||
|
||
现代嵌入式AI SoC都包含专用加速器(NPU/GPU/DSP),LLM推理需要CPU和加速器协同工作。核心问题:
|
||
|
||
1. **速度不匹配**: CPU和加速器的速度比通常是1:5~1:10,容易产生stall
|
||
2. **状态不可见**: NPU驱动通常封闭,无法精确感知NPU状态
|
||
3. **数据传输开销**: CPU↔NPU的数据传输是瓶颈
|
||
4. **同步开销**: barrier/semaphore的实时性分析复杂
|
||
|
||
## 2. 加速器架构分析
|
||
|
||
### 2.1 常见加速器类型
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ Accelerator Types │
|
||
├──────────────────┬───────────────────────────────────┤
|
||
│ NPU │ Neural Processing Unit │
|
||
│ │ • 专为矩阵乘设计 │
|
||
│ │ • 低精度(INT8/FP16) │
|
||
│ │ • 固定function, 可编程interface │
|
||
│ │ 例: 骁龙Hexagon, RK3588 NPU │
|
||
├──────────────────┼───────────────────────────────────┤
|
||
│ GPU │ Graphics Processing Unit │
|
||
│ │ • 通用并行计算 │
|
||
│ │ • 高带宽, 高功耗 │
|
||
│ │ • 成熟软件栈(CUDA/OpenCL) │
|
||
│ │ 例: Adreno, Mali, PowerVR │
|
||
├──────────────────┼───────────────────────────────────┤
|
||
│ DSP │ Digital Signal Processor │
|
||
│ │ • 向量/标量并行 │
|
||
│ │ • 低功耗, 适合小模型 │
|
||
│ │ 例: Hexagon DSP, Kryo │
|
||
├──────────────────┼───────────────────────────────────┤
|
||
│ DPU │ Deep Learning Processing Unit │
|
||
│ │ • 固定功能, 低延迟 │
|
||
│ │ • 适合known architecture │
|
||
│ │ 例: Xilinx DPU, 平头哥玄铁 │
|
||
└──────────────────┴───────────────────────────────────┘
|
||
```
|
||
|
||
### 2.2 CPU-加速器通信路径
|
||
|
||
```
|
||
CPU Accelerator
|
||
┌─────────────┐ ┌──────────────┐
|
||
│ │ DMA/Bus │ │
|
||
│ Task Queue │─────────────→│ Command Q │
|
||
│ │ │ │
|
||
│ Memory │←──DMA/Bus───│ Memory │
|
||
│ Pool │ │ Pool │
|
||
│ │ │ │
|
||
│ Event Q │←────────────│ Interrupt │
|
||
└─────────────┘ └──────────────┘
|
||
|
||
通信机制:
|
||
1. Shared Memory: 零拷贝,通过DMA传递数据
|
||
2. Command Queue: CPU写入命令,加速器执行后完成
|
||
3. Interrupt: 加速器完成后的异步通知
|
||
4. Memory Barrier: 保证CPU和加速器看到的内存一致性
|
||
```
|
||
|
||
## 3. 协同调度模型
|
||
|
||
### 3.1 典型LLM推理的CPU-加速器交互
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Phase: Prefill (Batch Inference) │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ CPU: Accelerator: │
|
||
│ ┌──────────┐ ┌──────────────────┐ │
|
||
│ │Tokenizer │ DMA→ │ │ │
|
||
│ └──────────┘ │ Embedding │ │
|
||
│ ↓ └────────┬─────────┘ │
|
||
│ ┌──────────┐ ┌────────┴─────────┐ │
|
||
│ │Control │──Cmd──→ │ Attention L1..N │ │
|
||
│ │ (plan) │ └────────┬─────────┘ │
|
||
│ └──────────┘ ┌────────┴─────────┐ │
|
||
│ ↓ │ FFN L1..N │ │
|
||
│ ┌──────────┐ └────────┬─────────┘ │
|
||
│ │Monitor │←──IRQ──│ │ │
|
||
│ │ (wait) │ ┌────────────┐│
|
||
│ └──────────┘ ┌───────────────────┐│ Output ││
|
||
│ │ KV Cache Write │└─────┬──────┘│
|
||
│ └───────────────────┘ │ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
|
||
Timeline:
|
||
CPU: | Tokenize | Plan | Wait | Monitor | Process Output |
|
||
NPU: |-------- Embedding + Layers + Output --------|
|
||
Time: 5ms 20ms (NPU compute) 5ms
|
||
Total E2E: ~30ms (mostly dominated by NPU)
|
||
```
|
||
|
||
### 3.2 解码阶段的流水线
|
||
|
||
```
|
||
Decode Phase (逐token生成):
|
||
|
||
Step i: Step i+1:
|
||
───────── ─────────
|
||
CPU: Send Cmd NPU: Execute Layer 1..N
|
||
Wait ←───────────────→ CPU: Send Cmd
|
||
NPU: Execute Layer 1..N
|
||
|
||
问题: CPU SendCmd 和 NPU Execute 之间有gap
|
||
原因: NPU完成前CPU需等待
|
||
|
||
优化: Double Buffering
|
||
──────────────────────────────
|
||
CPU: Send(i) Send(i+1) Send(i+2)
|
||
NPU: Exec(i) Exec(i+1) Exec(i+2)
|
||
|
||
实现:
|
||
- 两个buffer: buf_A, buf_B
|
||
- CPU写buf_A时,NPU读buf_A
|
||
- 完成后swap: CPU写buf_B,NPU读buf_B
|
||
- 需要RTOS semaphore管理buffer swap
|
||
```
|
||
|
||
## 4. 同步与通信机制
|
||
|
||
### 4.1 同步原语
|
||
|
||
```c
|
||
// NPU协同同步原语
|
||
typedef struct {
|
||
// 命令同步
|
||
SemaphoreHandle_t cmd_complete; // 命令完成信号
|
||
SemaphoreHandle_t data_ready; // 数据就绪信号
|
||
|
||
// 双缓冲管理
|
||
uint8_t *buffers[2]; // 双buffer
|
||
uint8_t active_buf; // 当前active buffer
|
||
SemaphoreHandle_t buf_swap; // buffer交换信号
|
||
|
||
// 事件通知
|
||
EventGroupHandle_t events; // 事件组
|
||
} npu_sync_t;
|
||
|
||
// 同步流程
|
||
void npu_sync_wait(npu_sync_t *sync) {
|
||
// 等待NPU完成当前命令
|
||
xSemaphoreTake(sync->cmd_complete, portMAX_DELAY);
|
||
// 交换buffer
|
||
xSemaphoreTake(sync->buf_swap, portMAX_DELAY);
|
||
}
|
||
|
||
void npu_sync_signal(npu_sync_t *sync) {
|
||
// NPU完成中断回调
|
||
xSemaphoreGiveFromISR(sync->cmd_complete, NULL);
|
||
xSemaphoreGiveFromISR(sync->buf_swap, NULL);
|
||
}
|
||
```
|
||
|
||
### 4.2 中断处理策略
|
||
|
||
```
|
||
┌─────────────────────────────────────────────┐
|
||
│ Interrupt Hierarchy │
|
||
├─────────────────────────────────────────────┤
|
||
│ Level 0: Hard IRQ (NPU完成中断) │
|
||
│ 动作: 提升task优先级, 触发barrier │
|
||
│ 时间: <10μs │
|
||
├─────────────────────────────────────────────┤
|
||
│ Level 1: SW IRQ (NPU驱动层) │
|
||
│ 动作: 释放semaphore, 唤醒task │
|
||
│ 时间: <50μs │
|
||
├─────────────────────────────────────────────┤
|
||
│ Level 2: Soft IRQ (任务调度) │
|
||
│ 动作: 调度下一个task │
|
||
│ 时间: <100μs │
|
||
└─────────────────────────────────────────────┘
|
||
|
||
关键设计:
|
||
- NPU完成中断 → 直接唤醒attention task (不经过软中断)
|
||
- 使用Interrupt-to-Semaphore模式, 避免context switch开销
|
||
- 中断处理函数最小化, 尽量defer到task层
|
||
```
|
||
|
||
## 5. 调度策略
|
||
|
||
### 5.1 Pipeline并行调度
|
||
|
||
```
|
||
Layer Pipeline (各层在加速器上流水执行):
|
||
|
||
Layer: L1 L2 L3 L4
|
||
[Attn][FFN][Attn][FFN][Attn][FFN][Attn][FFN]
|
||
CPU: [Plan] [Plan] [Plan] [Plan] [Collect]
|
||
Time: ↓ ↓ ↓ ↓
|
||
|
||
Scheduling:
|
||
1. CPU先plan Layer 1的Attention
|
||
2. L1 Attn执行时, CPU plan L2 Attn
|
||
3. L1 Attn完成 → L1 FFN启动
|
||
4. CPU plan L3 Attn
|
||
...
|
||
|
||
RTOS实现:
|
||
- 每个Layer的Attn/FFn是一个task
|
||
- CPU上跑"planner" task
|
||
- 加速器上跑"compute" task
|
||
- 通过barrier同步相邻Layer
|
||
```
|
||
|
||
### 5.2 Cross-device Scheduling (多加速器)
|
||
|
||
```
|
||
Example: SoC with both NPU + GPU
|
||
|
||
Request A: Large matrix → NPU (矩阵乘优化)
|
||
Request B: Conv/Embed → GPU (并行度高)
|
||
Request C: Small ops → DSP (低功耗)
|
||
|
||
调度问题:
|
||
- 哪个加速器处理哪个请求?
|
||
- 资源冲突时如何仲裁?
|
||
- 数据在设备间传输的开销?
|
||
|
||
调度策略:
|
||
1. Capability-based: 根据加速器能力分配
|
||
2. Load-based: 根据当前负载分配
|
||
3. Hybrid: capability + load
|
||
|
||
RTOS实现:
|
||
typedef struct {
|
||
enum accel_type { ACCEL_NPU, ACCEL_GPU, ACCEL_DSP } type;
|
||
uint32_t utilization; // 当前利用率
|
||
uint32_t queue_depth; // 排队深度
|
||
uint64_t avg_latency_us; // 平均延迟
|
||
SemaphoreHandle_t lock; // 资源锁
|
||
QueueHandle_t cmd_queue; // 命令队列
|
||
} accel_resource_t;
|
||
|
||
accel_resource_t* select_accelerate(task_t *task) {
|
||
// 1. Filter by capability
|
||
if (task->compute_type == MATRIX_MUL) return npu;
|
||
if (task->compute_type == CONV) return gpu;
|
||
|
||
// 2. Pick least loaded
|
||
return min_utilization(all_accelerators);
|
||
}
|
||
```
|
||
|
||
### 5.3 Async Pipeline Scheduling
|
||
|
||
```
|
||
CPU side (RTOS task):
|
||
┌────────────────────────────────────────────┐
|
||
│ Task: NPU Dispatcher (priority: HIGH) │
|
||
│ │
|
||
│ while (running) { │
|
||
│ cmd = dequeue_command(); │
|
||
│ send_to_npu(cmd); │
|
||
│ wait_for_irq(); │ // 阻塞等待
|
||
│ if (complete) { │
|
||
│ process_result(); │
|
||
│ dispatch_next(); │
|
||
│ } │
|
||
│ } │
|
||
└────────────────────────────────────────────┘
|
||
|
||
NPU side (hardware):
|
||
┌────────────────────────────────────────────┐
|
||
│ Command Queue (FIFO): │
|
||
│ ├─ Cmd 1: Attention Layer 1 │
|
||
│ ├─ Cmd 2: FFN Layer 1 │
|
||
│ ├─ Cmd 3: Attention Layer 2 │
|
||
│ └─ ... │
|
||
│ │
|
||
│ Execution: Sequential (FIFO) or │
|
||
│ Out-of-order (with dependencies)│
|
||
└────────────────────────────────────────────┘
|
||
```
|
||
|
||
## 6. 数据传输优化
|
||
|
||
### 6.1 DMA策略
|
||
|
||
```
|
||
传输类型 | 策略 | 优化手段
|
||
---------------------|------------------------|------------------
|
||
CPU→NPU (weights) | 一次性批量DMA | PCIe burst
|
||
CPU→NPU (input) | 流水线DMA | 预读
|
||
NPU→CPU (output) | 完成中断+DMA pull | 零拷贝
|
||
NPU→NPU (cross) | 共享内存 + cache sync | invalidate+clean
|
||
|
||
DMA Descriptor设计:
|
||
typedef struct {
|
||
uint32_t src_addr; // 源地址
|
||
uint32_t dst_addr; // 目的地址
|
||
uint32_t size; // 传输大小
|
||
uint32_t flags; // 同步/异步/中断
|
||
uint32_t completion_irq; // 完成后是否触发中断
|
||
uint32_t next_desc; // 链式DMA描述符
|
||
} dma_desc_t;
|
||
|
||
// 链式DMA: 多个描述符连成链, 一次性提交
|
||
// 减少RTOS调用次数, 提高DMA效率
|
||
void dma_chain_submit(dma_desc_t *head) {
|
||
// Submit chain to DMA controller
|
||
// No RTOS calls needed during transfer
|
||
// Only interrupt on last descriptor
|
||
}
|
||
```
|
||
|
||
### 6.2 零拷贝技术
|
||
|
||
```
|
||
Before:
|
||
CPU alloc: malloc(input) // 分配
|
||
memcpy: copy(input) // 拷贝到临时buffer
|
||
DMA: transfer(temp) // DMA从temp传输
|
||
Total: 2 copies + 1 DMA
|
||
|
||
After (zero-copy):
|
||
CPU alloc: mmap(shared_mem) // 共享内存
|
||
Direct: write(shared) // CPU直接写
|
||
DMA: transfer(shared) // DMA直接从shared传输
|
||
Total: 0 copies + 1 DMA
|
||
|
||
RTOS实现:
|
||
- 使用mmap或共享内存驱动
|
||
- CPU和NPU使用同一块物理内存
|
||
- 通过memory barrier保证一致性
|
||
- 使用RTOS semaphore管理访问权限
|
||
```
|
||
|
||
## 7. 各平台的加速器协同差异
|
||
|
||
### 7.1 MCU级
|
||
|
||
```
|
||
典型: STM32H7 + Cortex-M7 (CPU) + DSP (协处理器)
|
||
- 无NPU, 纯CPU/DSP矩阵运算
|
||
- 共享SRAM, 无DMA或简单DMA
|
||
- 协同简单: CPU调用DSP函数, 等待完成
|
||
|
||
关键优化:
|
||
- DSP的并行化调度
|
||
- 内存bank切换减少bank冲突
|
||
- 无共享内存, 需手动memcpy
|
||
```
|
||
|
||
### 7.2 SoC级
|
||
|
||
```
|
||
典型: 骁龙8 Gen + Kryo CPU + Hexagon NPU
|
||
- NPU驱动封闭, 使用QMI接口通信
|
||
- 共享内存通过RPMsg (Remote Processor Messaging)
|
||
- 中断: ARM GIC → Hexagon
|
||
|
||
关键挑战:
|
||
- NPU状态不完全可见
|
||
- QMI通信有固定开销(~100μs)
|
||
- 需估算NPU延迟, 不能精确测量
|
||
|
||
RTOS策略:
|
||
- 使用QMI异步API
|
||
- 通过Event FD等待NPU完成
|
||
- 双Buffer管理QMI传输
|
||
```
|
||
|
||
### 7.3 Edge盒子
|
||
|
||
```
|
||
典型: Jetson Orin + ARM CPU + NVIDIA GPU
|
||
- GPU驱动开放, CUDA API可用
|
||
- 成熟的stream/executor模型
|
||
- PCIe x16连接
|
||
|
||
关键优势:
|
||
- 可精确控制GPU调度
|
||
- CUDA Stream可映射为RTOS task
|
||
- NVLink多GPU调度成熟
|
||
|
||
RTOS集成:
|
||
- CUDA Runtime作为RTOS task的一部分
|
||
- GPU completion → RTOS event
|
||
- DMA between CPU↔GPU via PCIe
|
||
```
|
||
|
||
### 7.4 Server级
|
||
|
||
```
|
||
典型: x86 + 多GPU + NVLink
|
||
- 成熟的vLLM/TGI框架
|
||
- PCIe/NVLink互联
|
||
- NUMA拓扑
|
||
|
||
RTOS角色:
|
||
- 管理GPU进程
|
||
- 处理NVLink中断
|
||
- 提供实时性保证给GPU inference
|
||
|
||
调度:
|
||
- vLLM already does PagedAttention scheduling
|
||
- RTOS主要保障中断响应
|
||
```
|
||
|
||
## 8. 关键设计决策
|
||
|
||
| 决策点 | 选项 | 推荐 | 理由 |
|
||
|-------|------|-----|------|
|
||
| 同步模式 | Sync / Async / Event | **Async + Event** | 最大化并行度 |
|
||
| 缓冲策略 | Single / Double / Triple | **Triple** | 最大化流水线效率 |
|
||
| 数据传输 | Copy / DMA / Shared | **DMA + Shared** | 零拷贝+低CPU占用 |
|
||
| 加速决策 | Static / Dynamic | **Dynamic** | 根据负载自动选择 |
|
||
| 中断模式 | Polling / Interrupt / Event | **Interrupt** | 实时性最好 |
|
||
| 错误处理 | Retry / Skip / Report | **Retry + Report** | 保证正确性 |
|
||
|
||
## 9. 开放研究问题
|
||
|
||
1. **Black-box NPU Scheduling**: 加速状态不可知时的调度策略?
|
||
2. **Cross-accelerator Load Balancing**: 多加速器间的动态负载均衡?
|
||
3. **Accelerator Fault Recovery**: 加速器故障时的降级调度?
|
||
4. **Predictive Pipeline**: 预测NPU延迟, 优化pipeline stall?
|
||
5. **Heterogeneous Memory**: CPU/NPU共享内存的一致性管理?
|
||
|
||
---
|
||
|
||
*最后更新: 2026-09-17*
|