运行时系统是AI编译器的执行心脏,负责将优化后的计算图转化为实际的计算任务。在自动驾驶和具身智能场景中,运行时系统不仅要保证高性能,还要满足实时性、可靠性和资源受限等严苛要求。本章将深入探讨运行时系统的架构设计、资源管理策略、容错机制,以及对投机执行等高级特性的支持。
运行时系统是AI编译器技术栈中承上启下的关键层,它将编译器产生的优化计算图转换为硬件上的实际执行。不同于传统程序的运行时,AI运行时系统需要处理大规模张量运算、复杂的内存管理模式、以及异构硬件的协同调度。
运行时系统连接了编译时优化和实际硬件执行,其核心职责包括:
在自动驾驶场景中,运行时系统需要处理多传感器数据流的实时融合:
传感器输入流:
Camera ──┐
├─→ [Perception Runtime] ──→ [Fusion Runtime] ──→ [Planning Runtime]
LiDAR ──┤ ↑ ↑ ↑
│ 低延迟执行 确定性调度 资源隔离
Radar ──┘
实时性要求的分层:
在自动驾驶系统中,不同组件对实时性的要求差异巨大。运行时系统必须提供分级的执行保证:
这种分级要求运行时系统具备:
运行时系统的执行模式选择直接影响系统的性能、灵活性和可维护性。理解两种模式的权衡对于设计高效的AI运行时至关重要。
解释器模式特点:
解释器模式的典型执行流程:
while (op = next_op(graph)) {
inputs = gather_inputs(op)
kernel = lookup_kernel(op.type, op.attrs)
outputs = kernel.execute(inputs)
store_outputs(op, outputs)
}
这种模式的优势在于其简单性和灵活性。每个算子的执行都经过中央调度器,使得运行时可以轻松插入调试代码、性能监控、动态优化等功能。对于研究和开发阶段,解释器模式提供了无与伦比的透明度。
编译执行模式特点:
编译模式通过提前生成优化的机器码,消除了运行时的解释开销:
现代运行时系统通常采用混合执行策略:
执行决策树:
计算图
│
┌──────┴──────┐
│ │
热点路径 冷路径
│ │
编译执行 解释执行
│ │
┌──┴──┐ │
│ │ │
静态形状 动态形状 │
│ │ │
AOT编译 JIT编译 │
│ │ │
└──┬──┘ │
│ │
执行引擎 ←───────┘
混合策略的决策因素:
净收益 = (解释执行时间 - 编译执行时间) × 预期执行次数 - 编译时间
只有当净收益为正时,编译才是值得的。
运行时系统通常采用分层架构,每层负责不同的抽象:
高层执行器(Graph Executor):
高层执行器负责全局视角的优化和调度。它理解整个计算图的结构,可以进行跨算子的优化决策:
Graph Executor的核心数据结构:
- 节点依赖图:DAG表示的执行顺序
- 设备映射表:节点到设备的分配
- 内存规划图:张量生命周期和复用方案
- 执行队列:就绪节点的优先级队列
在处理大规模模型时,高层执行器还负责:
中层执行器(Kernel Executor):
中层执行器是算子级别的调度中心。对于每个算子,它需要:
Kernel选择决策树:
输入形状 → 数据类型 → 设备类型 → 优化标志
↓ ↓ ↓ ↓
[1,3,224] FP16 GPU CUDNN
↓
选择: cudnn_conv2d_fp16_nhwc
底层执行器(Hardware Executor):
底层执行器封装了硬件细节,提供统一的抽象接口:
硬件抽象层接口:
class HardwareExecutor {
// 内存管理
allocate(size, alignment)
deallocate(ptr)
copy(dst, src, size)
// 执行控制
launch_kernel(kernel, args, grid, block)
synchronize()
// 事件管理
create_event()
record_event(event)
wait_event(event)
}
层次间的交互机制:
各层之间通过明确定义的接口通信,确保模块化和可扩展性:
执行流程:
Graph Executor
↓ 分解为算子序列
Kernel Executor
↓ 生成硬件指令
Hardware Executor
↓ 提交到设备
Hardware Device
这种分层设计的优势:
为了最大化硬件利用率,运行时系统广泛采用异步执行:
同步执行 vs 异步执行时序图:
同步执行:
时间 →
CPU: [Op1]──等待──[Op2]──等待──[Op3]
GPU: [K1] [K2] [K3]
异步执行:
CPU: [Op1][Op2][Op3][Op4][Op5]...
GPU: [K1] [K2] [K3] [K4]...
DMA: [D1] [D2] [D3] [D4]...
利用率提升: ~2-3x
异步执行的实现机制:
命令队列结构:
Queue_GPU0: [Conv] → [ReLU] → [Pool] → [Copy] → ...
Queue_GPU1: [MatMul] → [Add] → [Softmax] → ...
Queue_DMA: [H2D] → [D2D] → [D2H] → ...
特性:
- FIFO顺序保证
- 非阻塞提交
- 批量刷新优化
Event使用模式:
GPU0.record(event1) // 在GPU0记录事件
GPU1.wait(event1) // GPU1等待event1
GPU1.launch(kernel) // 继续执行
多流并发:
Stream0: [Conv1] ────→ [Conv2] ────→ [Conv3]
Stream1: [MatMul1] ──→ [MatMul2] ──→ [MatMul3]
Stream2: [Copy1] ──→ [Copy2] ──→ [Copy3]
异步执行的关键技术:
流水线并行的设计模式:
Stage 1 Stage 2 Stage 3 Stage 4
[预处理] → [编码器] → [解码器] → [后处理]
↓ ↓ ↓ ↓
Batch N Batch N-1 Batch N-2 Batch N-3
时间步: T1 T2 T3 T4
GPU0: [L1-6] [L1-6] [L1-6] [L1-6]
B1 B2 B3 B4
GPU1: [L7-12] [L7-12] [L7-12]
B1 B2 B3
GPU2: [L13-18][L13-18]
B1 B2
GPU3: [L19-24]
B1
计算与通信并行:
GPU计算: [Compute_1] [Compute_2] [Compute_3]
通信: [Send_1] [Send_2] [Send_3]
重叠度 = 通信时间 / 计算时间
理想情况: 重叠度 = 100%(通信完全隐藏)
异步执行的挑战与解决方案:
内存是AI系统最宝贵的资源之一。运行时系统需要精心设计内存管理策略:
内存池技术: 预分配大块内存,避免频繁的系统调用:
内存池结构:
┌─────────────────────────────────────┐
│ 预分配内存池 (1GB) │
├──────┬──────┬──────┬──────┬────────┤
│ 64MB │ 64MB │128MB │256MB │ 空闲 │
│ 块1 │ 块2 │ 块3 │ 块4 │ 512MB │
└──────┴──────┴──────┴──────┴────────┘
↓
分配策略:
1. Best-fit: 找最小满足的块
2. First-fit: 找第一个满足的块
3. Buddy系统: 2的幂次分配
内存复用机制: 通过生命周期分析复用内存:
张量生命周期分析:
时间 → t1 t2 t3 t4 t5
A: [████████]
B: [████████]
C: [████████]
D: [████]
E: [████]
内存分配优化:
原始: A(100MB) + B(100MB) + C(100MB) + D(50MB) + E(50MB) = 400MB
优化: Max(A, B+D, C+E) = Max(100, 150, 150) = 150MB
节省: 62.5%
静态调度: 编译时确定执行顺序,运行时按计划执行:
优点:
缺点:
动态调度: 运行时根据资源可用性动态分配任务:
动态调度器架构:
任务队列
┌─┬─┬─┬─┬─┬─┐
│T│T│T│T│T│T│
└┬┴┬┴┬┴┬┴┬┴┬┘
│ │ │ │ │ │
┌─┴─┴─┴─┴─┴─┴─┐
│ 调度器 │←── 资源监控
└┬──┬──┬──┬──┬┘ (CPU/GPU/内存)
│ │ │ │ │
┌┴┐┌┴┐┌┴┐┌┴┐┌┴┐
│W││W││W││W││W│ Workers
└─┘└─┘└─┘└─┘└─┘
在异构计算环境中,运行时需要协调多个设备:
设备分配策略:
数据传输优化:
设备间数据流:
CPU ←──────→ GPU1
↑ ╱ ↑
│ ╱ │
↓ ╱ ↓
GPU2 ←──────→ GPU3
优化技术:
1. 异步传输: 计算与传输重叠
2. 直接传输: GPU间P2P,避免经过CPU
3. 传输合并: 批量小传输为大传输
在NUMA架构下,内存访问延迟差异显著:
NUMA拓扑示例:
┌──────────────┐ ┌──────────────┐
│ Node 0 │ │ Node 1 │
│ ┌────────┐ │ │ ┌────────┐ │
│ │ CPU0-7 │ │ │ │ CPU8-15│ │
│ └────────┘ │ │ └────────┘ │
│ ┌────────┐ │ │ ┌────────┐ │
│ │ 64GB │ │←──→│ │ 64GB │ │
│ │ Memory │ │QPI │ │ Memory │ │
│ └────────┘ │ │ └────────┘ │
└──────────────┘ └──────────────┘
访问延迟:
本地内存: ~100ns
远程内存: ~150-200ns (1.5-2x)
NUMA优化策略:
运行时系统需要处理多种错误类型:
错误分类层次:
错误类型
├── 硬件错误
│ ├── 设备故障 (GPU崩溃)
│ ├── 内存错误 (ECC错误)
│ └── 通信错误 (PCIe错误)
├── 软件错误
│ ├── 算子错误 (数值溢出)
│ ├── 内存错误 (OOM)
│ └── 同步错误 (死锁)
└── 用户错误
├── 参数错误 (形状不匹配)
├── 配置错误 (非法设备)
└── 资源错误 (超出限制)
错误检测机制:
检查点机制: 定期保存执行状态,错误时回滚:
执行时间线:
──→ [CP1] ──→ [CP2] ──→ [错误!] ──→ 回滚到CP2 ──→ 继续
检查点内容:
- 模型参数状态
- 中间计算结果
- 执行上下文
- 资源分配信息
检查点优化策略:
降级执行: 在部分资源失效时继续服务:
降级策略:
正常: 4×GPU并行 → 300 FPS
GPU1故障: 3×GPU并行 → 225 FPS (可接受)
GPU1&2故障: 2×GPU并行 → 150 FPS (降级模式)
仅CPU: 纯CPU执行 → 30 FPS (应急模式)
自动驾驶系统对容错有特殊要求:
安全关键性分级:
冗余执行架构:
三重冗余执行:
输入 ──┬→ [执行器1] ──→ 结果1 ──┐
├→ [执行器2] ──→ 结果2 ──┼→ [投票器] → 输出
└→ [执行器3] ──→ 结果3 ──┘
↓
异常检测
时间冗余技术: 对于瞬时故障,通过重复执行提高可靠性:
时间冗余执行:
第一次执行 → 结果1
↓
延迟△t
↓
第二次执行 → 结果2
↓
比较验证 → 输出
故障隔离机制: 防止错误扩散到其他组件:
隔离边界:
┌─────────────────────────┐
│ 应用层 │
├─────────────────────────┤ ← 隔离边界
│ 运行时系统 │
│ ┌──────┬──────┬──────┤
│ │模块A │模块B │模块C │ ← 模块间隔离
│ └──────┴──────┴──────┤
├─────────────────────────┤ ← 隔离边界
│ 硬件抽象层 │
└─────────────────────────┘
快速恢复技术:
投机执行允许系统在不确定的情况下提前执行可能的计算路径,是提升系统吞吐量的重要技术:
投机执行示例 - 分支预测:
条件判断
↓
┌────┴────┐
│ │
分支A(70%) 分支B(30%)
│ │
提前执行 等待
│ │
└────┬────┘
↓
选择结果
投机执行的应用场景:
运行时需要精心管理投机执行的中间结果:
投机缓冲区结构:
┌────────────────────────────┐
│ 主执行路径缓冲区 │ ← 确定的结果
├────────────────────────────┤
│ 投机路径1缓冲区 │ ← 可能的结果
├────────────────────────────┤
│ 投机路径2缓冲区 │ ← 可能的结果
├────────────────────────────┤
│ 投机路径3缓冲区 │ ← 可能的结果
└────────────────────────────┘
↓
验证后提交或丢弃
缓冲区管理策略:
原始数据 → [共享只读]
↓
投机修改时
↓
原始数据 → [只读]
投机副本 → [可写]
版本树:
V0 (主版本)
├── V1 (投机1)
│ └── V1.1 (嵌套投机)
└── V2 (投机2)
资源分配权衡:
资源分配决策树:
总计算资源: 100%
├── 主路径: 60% (保证基本性能)
├── 投机路径1: 20% (最可能分支)
├── 投机路径2: 15% (次可能分支)
└── 预留: 5% (系统开销)
动态调整因素:
- 历史成功率
- 路径概率估计
- 资源可用性
- 延迟敏感度
投机深度控制:
投机深度限制:
Level 0: [主执行]
Level 1: ├─[投机A] ├─[投机B]
Level 2: │ ├─[A.1] │ ├─[B.1]
│ └─[A.2] │ └─[B.2]
Level 3: [达到深度限制,停止]
深度选择考虑:
- 收益递减原理
- 内存开销增长
- 管理复杂度
投机解码是大语言模型加速的关键技术,运行时需要提供专门支持:
执行流程详解:
1. 草稿模型生成 (Draft Model):
输入: [Context]
输出: [T1, T2, T3, T4] (k=4个候选token)
时间: 4×10ms = 40ms
2. 目标模型验证 (Target Model):
输入: [Context, T1, T2, T3, T4]
输出: P(T1), P(T2), P(T3), P(T4)
时间: 1×100ms = 100ms (批处理)
3. 接受/拒绝决策:
for i in 1..k:
if P_draft(Ti)/P_target(Ti) > random():
接受Ti
else:
拒绝Ti及后续,break
4. 状态更新:
提交接受的tokens
更新KV cache
继续下一轮
运行时优化技术:
传统: [T1]→验证→[T2]→验证→... (串行)
优化: [T1,T2,T3,T4]→批量验证 (并行)
加速: 3-4x
KV Cache结构:
├── 主路径KV (确定的)
└── 投机KV (临时的)
├── 分支1
└── 分支2
优化: 增量更新,避免重复计算
# 伪代码:动态调整投机长度
def adaptive_k(history):
success_rate = 计算历史成功率()
if success_rate > 0.8:
return min(k + 1, max_k)
elif success_rate < 0.3:
return max(k - 1, min_k)
return k
具身智能场景的投机执行:
在机器人控制中,投机执行可用于动作预测:
机械臂轨迹投机:
当前位置: P0
目标候选: [G1(70%), G2(20%), G3(10%)]
投机执行:
├── 轨迹1: P0→P1→P2→G1 (主路径)
├── 轨迹2: P0→P1'→G2 (备选)
└── 轨迹3: P0→G3 (应急)
实时切换:
传感器更新 → 重新评估 → 选择最优轨迹
运行时系统是AI编译器技术栈的关键组成部分,直接影响系统的性能、可靠性和可扩展性。本章深入探讨了运行时系统的四个核心方面:
性能度量:
调度效率:
容错指标:
错误:频繁的小内存分配
问题: 每个算子独立分配内存
后果: 内存碎片化,分配开销大
正确做法:使用内存池预分配,复用内存块
错误:过度同步
问题: 每个操作都等待完成
后果: GPU利用率低,性能差
正确做法:使用异步执行,批量同步
错误:忽视NUMA特性
问题: 随机分配线程和内存
后果: 大量远程内存访问,性能下降50%+
正确做法:绑定线程到NUMA节点,就近分配内存
错误:简单的失败即停止
问题: 任何错误都导致系统崩溃
后果: 可用性差,用户体验糟糕
正确做法:分级错误处理,支持降级执行
错误:过度投机
问题: 投机深度过大,资源全部用于投机
后果: 主路径饥饿,整体性能反而下降
正确做法:限制投机深度,保证主路径资源
错误:忽视设备间同步开销
问题: 频繁的GPU间同步
后果: 设备空闲等待,吞吐量下降
正确做法:最小化同步点,使用异步传输
给定一个包含100个算子的计算图,其中20个算子在热点路径上执行频率很高,80个算子很少执行。设计一个混合执行策略,说明哪些算子应该编译执行,哪些应该解释执行,并解释你的决策依据。
💡 提示:考虑编译开销与执行收益的平衡,以及内存占用。
设计一个内存池系统,支持以下需求:
💡 提示:考虑使用分级内存池或Buddy系统。
在一个2-socket NUMA系统上(每个socket 32核心,128GB内存),需要处理一个200GB的点云数据集。设计一个NUMA感知的数据分区和任务调度方案。
💡 提示:考虑数据局部性、负载均衡和内存带宽。
为自动驾驶感知系统设计一个三级容错机制,要求:
💡 提示:考虑检查点、冗余和降级策略。
设计一个投机解码系统,目标模型延迟100ms/token,草稿模型延迟10ms/token。如何选择最优的投机长度k,使得端到端延迟最小?考虑投机成功率随k的变化:p(k) = 0.9^k。
💡 提示:建立延迟模型,考虑批处理效应。
有4个GPU(2个A100,2个V100)和2个CPU(每个64核),需要执行一个包含卷积、注意力和全连接层的混合模型。设计一个异构调度策略,最大化吞吐量。
已知性能数据:
💡 提示:考虑算子-设备亲和性和流水线并行。
设计一个增量检查点系统,模型大小10GB,每秒产生100MB的梯度更新。如何设计检查点策略,使得:
💡 提示:考虑增量检查点和异步I/O。
为自动驾驶系统设计一个满足实时约束的调度器:
如何保证所有任务都满足截止时间?
💡 提示:使用Rate Monotonic或EDF调度算法。