在现代AI系统中,并行化不再是一种优化手段,而是达到实用性能的必要条件。无论是自动驾驶汽车需要在毫秒级响应时间内处理多路传感器数据,还是具身智能机器人需要同时执行感知、规划和控制任务,高效的并行化策略都是系统成功的关键。本章将深入探讨AI编译器如何实现各种并行化策略,以及如何在满足实时约束的同时最大化硬件利用率。
在深入具体技术之前,我们需要理解AI系统中并行化的多个层次。现代AI系统的并行化不是单一维度的优化问题,而是涉及从硬件到应用的多层次协同设计。每一层都有其独特的并行化机会,同时也面临特定的约束和挑战。
应用层并行(多任务/多模型)
↓
模型层并行 ←→ 批处理并行
↓ ↓
算子层并行 ←→ 数据流并行
↓ ↓
硬件层并行(SIMD/多核/多卡/集群)
这个层次结构反映了并行化的复杂性。应用层可能同时运行目标检测、语义分割和轨迹预测等多个任务;模型层需要将大模型拆分到多个设备;算子层要考虑矩阵运算的分块和向量化;而硬件层则直接映射到物理计算资源。AI编译器的核心任务是在这些层次间协调优化,找到全局最优的并行策略。
更重要的是,这些层次之间存在复杂的相互作用。例如,批处理并行可以提高硬件利用率,但会影响应用层的延迟;模型并行减少了单设备内存需求,但增加了设备间通信。理解这些相互作用是设计高效并行系统的关键。
并行化设计中存在几个基本的权衡,这些权衡决定了系统的性能边界:
通信vs计算:这是并行计算的根本权衡。增加并行度通常会增加通信开销,而通信往往成为性能瓶颈。理想的并行化策略应该使得: \(\text{加速比} = \frac{T_{\text{串行}}}{T_{\text{并行}} + T_{\text{通信}}} \approx P\) 其中$P$是并行处理单元数。但实际上,由于Amdahl定律的限制,存在串行部分$s$: \(\text{实际加速比} = \frac{1}{s + \frac{1-s}{P}}\)
当$P \to \infty$时,加速比的上限是$1/s$。这意味着即使有无限的并行资源,串行部分也会限制整体性能。
延迟vs吞吐量:这是系统设计的经典权衡。批处理可以显著提高吞吐量,因为它能更好地利用硬件的并行能力,分摊固定开销。但批处理需要等待足够的请求,这会增加单个请求的延迟。对于自动驾驶系统,这个权衡尤其关键——我们需要在保证实时响应的前提下最大化处理能力。
数学上,如果单个请求的处理时间是$t_1$,批处理$B$个请求的时间是$t_B$,那么:
理想情况下,$t_B < B \cdot t_1$,这就是批处理的效率来源。
负载均衡vs调度开销:细粒度的任务划分有助于负载均衡,但会增加调度开销。假设有$N$个任务分配给$P$个处理器,如果任务粒度太粗($N \approx P$),容易出现负载不均;如果太细($N » P$),调度开销可能超过并行收益。
最优的任务粒度通常满足: \(\text{任务粒度} \approx \frac{\text{总工作量}}{P \times \text{调度开销系数}}\)
空间vs时间:这涉及内存使用和计算时间的权衡。例如,保存中间结果可以避免重复计算(空间换时间),而重新计算可以减少内存占用(时间换空间)。在GPU等内存受限的设备上,这个权衡尤为重要。
精度vs性能:混合精度计算可以提升性能,但可能影响数值稳定性。量化可以减少内存带宽需求,但需要careful calibration to maintain accuracy。
并行系统的可扩展性(scalability)是衡量其设计质量的关键指标。可扩展性分为两个维度:
强扩展性(Strong Scaling):固定问题规模,增加处理器数量。理想情况下,执行时间应该线性减少。但实际上,由于通信开销和串行部分的存在,强扩展性通常会在某个点后饱和。
弱扩展性(Weak Scaling):按比例增加问题规模和处理器数量。理想情况下,执行时间应该保持恒定。弱扩展性通常比强扩展性更容易实现,因为增大的问题规模可以分摊通信开销。
对于AI工作负载,我们需要考虑特殊的扩展性特征:
为了定量分析并行策略,我们需要建立性能模型。一个实用的性能模型应该包含:
计算时间模型: \(T_{\text{comp}} = \frac{\text{FLOPs}}{\text{峰值性能} \times \text{利用率}}\)
通信时间模型: \(T_{\text{comm}} = \alpha + \beta \times \text{消息大小}\) 其中$\alpha$是延迟,$\beta$是带宽的倒数。
内存访问模型: \(T_{\text{mem}} = \frac{\text{数据量}}{\text{带宽}} + \text{缓存未命中代价}\)
总执行时间: \(T_{\text{total}} = \max(T_{\text{comp}}, T_{\text{mem}}) + T_{\text{comm}}\)
这个模型告诉我们,当计算密集度(compute intensity)$\text{FLOPs}/\text{数据量}$较低时,系统是内存带宽受限的;当通信量大时,系统是通信受限的。AI编译器需要根据这些特征选择合适的并行策略。
数据并行是最直观也是应用最广泛的并行化策略。其核心思想简单而优雅:将输入数据分片,每个处理单元运行相同的模型副本,然后聚合结果。这种策略的美妙之处在于它保持了模型的完整性,不需要修改模型架构,因此实现相对简单,且能够很好地扩展到大规模系统。
基本流程与数学原理:
数据并行的数学基础是梯度下降的线性可加性。对于损失函数$L$和参数$\theta$,有: \(\nabla_\theta L(B) = \frac{1}{|B|} \sum_{b \in B} \nabla_\theta L(b)\)
这意味着我们可以在不同设备上独立计算子批次的梯度,然后求平均:
输入批次 B = [b₁, b₂, ..., bₙ]
↓ 分片
GPU₁: b₁...bₖ → 计算梯度g₁
GPU₂: bₖ₊₁...b₂ₖ → 计算梯度g₂
...
GPUₚ: b₍ₚ₋₁₎ₖ₊₁...bₙ → 计算梯度gₚ
↓ 梯度聚合
g_avg = (g₁ + g₂ + ... + gₚ) / P
↓ 参数更新
θ = θ - η × g_avg (所有设备同步更新)
通信模式的深入分析:
数据并行的性能瓶颈通常在于梯度聚合的通信开销。让我们详细分析不同的通信模式:
参数服务器模式(Parameter Server):
这是最早期的设计,有专门的参数服务器节点负责梯度聚合和参数更新:
参数服务器可以通过分片(sharding)来缓解瓶颈:
PS₁: 负责参数[0, M/3)
PS₂: 负责参数[M/3, 2M/3)
PS₃: 负责参数[2M/3, M)
Ring-AllReduce模式:
这是目前主流的通信模式,将设备组织成环形拓扑:
Ring-AllReduce的带宽效率分析: \(\text{有效带宽} = \frac{M}{\text{通信时间}} = \frac{M}{2M(P-1)/(P \times B)} = \frac{P \times B}{2(P-1)} \approx \frac{B}{2}\) 其中$B$是单链路带宽。这说明Ring-AllReduce能达到理论带宽的50%。
树形AllReduce:
适用于高延迟网络,通过构建平衡树来减少通信轮数:
分层AllReduce(Hierarchical AllReduce):
针对多级网络拓扑优化(如机器内vs机器间):
第一层:机器内GPU间AllReduce(NVLink,600GB/s)
第二层:机器间代表GPU的AllReduce(InfiniBand,200Gbps)
第三层:结果广播回各机器内GPU
自动驾驶场景的实际应用:
让我们以一个真实的自动驾驶感知系统为例,深入分析数据并行的应用:
系统配置:
硬件配置:
- 4个NVIDIA Drive AGX Orin(每个512 CUDA cores)
- 8个摄像头:3前视 + 2侧视 + 1后视 + 2环视
- 1个128线激光雷达
- 处理频率:30Hz(33ms周期)
模型架构:
- 骨干网络:ResNet-101 (44M参数)
- 检测头:FPN + YOLO (20M参数)
- 分割头:DeepLab (15M参数)
- 融合网络:Transformer (30M参数)
总计:约109M参数 ≈ 436MB (FP32)
数据并行策略设计:
Orin-1: 前视广角 + 前视标准 → 主要负责前方障碍物
Orin-2: 前视长焦 + 右侧 → 负责远距离和右侧盲区
Orin-3: 左侧 + 后视 → 负责左侧和后方
Orin-4: 环视×2 + 激光雷达 → 负责360°感知和点云处理
这种分组考虑了:
时刻t: 数据采集(5ms) → Orin1-4并行处理(20ms) → 融合(5ms) → 输出(3ms)
时刻t+33ms: 新一轮处理开始
关键优化:
- 异步数据传输:在t时刻处理时,预取t+33ms的数据
- 增量式融合:只传输和融合变化的特征
- 优先级调度:紧急制动相关的检测优先处理
通信优化实践:
梯度压缩策略:
# 伪代码展示梯度压缩逻辑
def compress_gradient(grad, compression_ratio=0.01):
# Top-K稀疏化
threshold = torch.quantile(grad.abs(), 1-compression_ratio)
mask = grad.abs() > threshold
compressed = grad * mask
# 误差反馈
error_feedback[param] += grad - compressed
# 量化到INT8
scale = compressed.abs().max() / 127
quantized = (compressed / scale).round().to(torch.int8)
return quantized, scale, mask
通过这种压缩,436MB的梯度可以压缩到:
通信时间从436MB/(10Gbps) ≈ 350ms降低到2MB/(10Gbps) ≈ 1.6ms。
容错与动态负载均衡:
当检测到某个摄像头故障时的动态调整:
故障检测(通过心跳和校验)
↓
负载重分配决策
↓
迁移相关计算到邻近Orin
↓
调整融合权重补偿缺失视角
这个过程需要在100ms内完成,以保持系统的实时性。
当模型规模超过单个设备的内存容量时,模型并行成为必然选择。随着大语言模型参数量从数十亿增长到数千亿甚至万亿,模型并行技术变得愈发重要。与数据并行不同,模型并行需要将模型本身拆分到多个设备上,这带来了全新的挑战:如何划分模型以最小化通信开销?如何保持设备间的负载均衡?如何处理复杂的数据依赖关系?
层间并行(Pipeline Parallelism)的深入分析:
层间并行是最直观的模型并行方式,将模型的不同层分配到不同设备:
设备分配示意:
设备1: [Embedding, Layer1-10] → 2GB参数
设备2: [Layer11-20] → 2GB参数
设备3: [Layer21-30] → 2GB参数
设备4: [Layer31-40, Output] → 2GB参数
前向传播流程:
时刻1: 设备1处理输入 → 产生激活A₁
时刻2: 设备2处理A₁ → 产生激活A₂ (设备1空闲)
时刻3: 设备3处理A₂ → 产生激活A₃ (设备1,2空闲)
时刻4: 设备4处理A₃ → 产生输出 (设备1,2,3空闲)
这种朴素的实现存在严重的效率问题:
数学分析:假设每个阶段的计算时间为$t$,批大小为$B$,流水线深度为$P$:
张量并行(Tensor Parallelism)的实现细节:
张量并行在单个算子级别进行并行化,特别适合大型矩阵运算:
输入X: [B, H]
权重W: [H, O] → 切分为 W = [W₁|W₂|...|Wₚ], 每个Wᵢ: [H, O/P]
设备i计算:
Yᵢ = XWᵢ # 结果shape: [B, O/P]
最终结果:
Y = Concat(Y₁, Y₂, ..., Yₚ, dim=1) # shape: [B, O]
通信分析:
输入X: [B, H] → 切分为 X = [X₁; X₂; ...; Xₚ], 每个Xᵢ: [B, H/P]
权重W: [H, O] → 切分为 W = [W₁; W₂; ...; Wₚ], 每个Wᵢ: [H/P, O]
设备i计算:
Yᵢ = XᵢWᵢ # 部分结果
最终结果:
Y = AllReduce(Y₁ + Y₂ + ... + Yₚ)
通信分析:
Transformer的高效张量并行:
对于Multi-Head Attention,自然的并行维度是注意力头: \(\text{MHA}(Q, K, V) = \text{Concat}(\text{head}_1, ..., \text{head}_h)W^O\)
每个设备处理$h/P$个注意力头:
设备i负责head_{(i-1)h/P+1}到head_{ih/P}
计算流程:
1. Q, K, V投影(列并行)
2. 注意力计算(完全并行,无通信)
3. 输出投影(行并行)
对于FFN层:
FFN(x) = GELU(xW₁)W₂
设备分配:
- W₁: [d_model, 4*d_model] 列切分
- W₂: [4*d_model, d_model] 行切分
通信模式:
前向:x → 广播 → xW₁(并行) → GELU → W₂(并行) → AllReduce
反向:相反方向传播梯度
2D和3D张量并行:
对于超大模型,可以在多个维度上并行:
2D并行(Summa算法):
将P个设备组织成√P × √P网格
权重W按行列同时切分
通信复杂度:O(√P)而非O(P)
混合并行策略的设计原则:
现代大模型训练通常结合多种并行策略,以GPT-3(175B参数)为例:
模型配置:
- 96层Transformer
- 隐藏维度:12,288
- 注意力头:96
- FFN维度:49,152
并行策略(使用1024个GPU):
- 数据并行度:64
- 张量并行度:8
- 流水线并行度:2
设备分组:
- 8个GPU为一组做张量并行(同一节点,NVLink连接)
- 2组做流水线并行(跨节点)
- 64路数据并行(跨机架)
内存分析:
- 每个张量并行组:175B/8 = 22B参数
- 每个流水线阶段:22B/2 = 11B参数
- 加上优化器状态和激活:约60GB/GPU
选择并行策略的决策树:
if 模型fits单GPU:
使用纯数据并行
elif 模型fits单节点:
节点内张量并行 + 跨节点数据并行
else:
节点内张量并行 + 跨节点流水线并行 + 数据并行
通信是分布式训练的主要瓶颈,优化通信对提升整体性能至关重要。
梯度压缩的理论与实践:
量化压缩:
理论基础:神经网络训练对梯度精度的要求相对较低,可以容忍一定的量化误差。
量化函数:
Q(x) = s × round(clip(x/s, -2^(b-1), 2^(b-1)-1))
其中s是缩放因子,b是位宽
动态量化:
s = max(|x|) / (2^(b-1) - 1)
随机量化(保证无偏):
Q(x) = s × (floor(x/s) + Bernoulli((x/s - floor(x/s))))
实验数据:
稀疏化压缩:
Top-K稀疏化:只传输最大的K%梯度
def top_k_sparsification(grad, sparsity=0.99):
k = int(grad.numel() * (1 - sparsity))
values, indices = torch.topk(grad.abs().view(-1), k)
mask = torch.zeros_like(grad).view(-1)
mask[indices] = 1
return grad * mask.view(grad.shape)
理论分析(收敛性保证): 设原始梯度为$g$,稀疏化后为$\tilde{g}$,如果满足: \(E[\tilde{g}] = g \text{ 且 } E[\|\tilde{g} - g\|^2] \leq \sigma^2\) 则SGD依然收敛,只是收敛速度变慢。
误差反馈机制:
累积压缩误差,避免信息永久丢失:
error_feedback = {}
def compress_with_ef(param_name, grad):
# 加上历史误差
grad_with_ef = grad + error_feedback.get(param_name, 0)
# 压缩
compressed = compress(grad_with_ef)
# 记录新误差
error_feedback[param_name] = grad_with_ef - compressed
return compressed
通信与计算重叠(Communication-Computation Overlap):
关键思想:在计算第$i$层的同时,通信第$i-1$层的梯度。
优化的反向传播流程:
for i in reversed(range(num_layers)):
# 计算第i层梯度
grad[i] = compute_gradient(layer[i])
# 立即启动异步通信
handle[i] = all_reduce_async(grad[i])
# 如果i+1层通信完成,更新参数
if i < num_layers - 1:
wait(handle[i+1])
update_params(layer[i+1], grad[i+1])
时间节省分析:
NUMA感知的通信优化深入:
NUMA(Non-Uniform Memory Access)架构在现代服务器中普遍存在,理解其特性对优化至关重要:
典型的2-socket服务器NUMA拓扑:
Socket 0 Socket 1
┌──────────────────┐ ┌──────────────────┐
│ CPU: 32 cores │ │ CPU: 32 cores │
│ Mem: 256GB │ QPI │ Mem: 256GB │
│ PCIe: GPU0, GPU1 │←─────→│ PCIe: GPU2, GPU3 │
└──────────────────┘ 25GB/s└──────────────────┘
↓↑ ↓↑
本地内存 本地内存
200GB/s 200GB/s
NUMA的性能影响:
优化策略实施:
# 使用libnuma进行NUMA感知分配
numa_node = get_gpu_numa_node(gpu_id)
memory = numa_alloc_onnode(size, numa_node)
# 将进程绑定到对应NUMA节点
numactl --cpunodebind=0 --membind=0 python train.py --gpu=0,1
# 构建NUMA感知的通信环
def build_numa_aware_ring(gpus):
# 同NUMA节点的GPU相邻
numa_groups = group_by_numa(gpus)
ring = []
for group in numa_groups:
ring.extend(group)
return ring
# 数据分片考虑NUMA亲和性
for gpu_id, data_shard in enumerate(data_shards):
numa_node = gpu_to_numa[gpu_id]
# 确保数据在对应NUMA节点
place_data_on_numa(data_shard, numa_node)
实际性能提升数据:
流水线并行通过将模型分成多个阶段(stage),让不同的微批次(micro-batch)在不同阶段并行执行,从而提高设备利用率。
朴素流水线的问题:
时间 →
设备1: F₁(mb₁) → F₁(mb₂) → F₁(mb₃) → F₁(mb₄) → 空闲...
设备2: 等待 → F₂(mb₁) → F₂(mb₂) → F₂(mb₃) → F₂(mb₄) → 空闲...
设备3: 等待 → 等待 → F₃(mb₁) → F₃(mb₂) → F₃(mb₃) → F₃(mb₄)
设备4: 等待 → 等待 → 等待 → F₄(mb₁) → F₄(mb₂) → F₄(mb₃) → F₄(mb₄)
问题:流水线气泡(bubble)导致设备利用率低
GPipe通过增加微批次数量来减少气泡比例:
设K = 微批次数,P = 流水线深度
气泡比例 = (P-1) / (K + P - 1)
当K >> P时,气泡比例 → 0
前向和反向传播的调度:
时间步: 1 2 3 4 5 6 7 8 9 10 11 12
设备1: F₁ F₂ F₃ F₄ B₄ B₃ B₂ B₁
设备2: F₁ F₂ F₃ F₄ B₄ B₃ B₂ B₁
设备3: F₁ F₂ F₃ F₄ B₄ B₃ B₂ B₁
设备4: F₁ F₂ F₃ F₄ B₄ B₃ B₂ B₁
F = 前向传播,B = 反向传播,数字 = 微批次编号
内存需求分析:
GPipe需要保存所有微批次的激活值直到反向传播:
PipeDream采用1F1B(One Forward One Backward)调度减少内存需求:
设备1: F₁¹ F₁² F₁³ F₁⁴ B₁¹ F₁⁵ B₁² F₁⁶ B₁³ F₁⁷ B₁⁴ ...
设备2: F₂¹ F₂² F₂³ F₂⁴ B₂¹ F₂⁵ B₂² F₂⁶ B₂³ F₂⁷ ...
设备3: F₃¹ F₃² F₃³ F₃⁴ B₃¹ F₃⁵ B₃² F₃⁶ B₃³ ...
设备4: F₄¹ F₄² F₄³ F₄⁴ B₄¹ F₄⁵ B₄² F₄⁶ ...
上标表示微批次编号
优势:
挑战:
在具身智能系统中,流水线不仅存在于模型内部,更体现在感知-规划-控制的系统级流水线:
传感器数据流:
摄像头 ──┐
激光雷达 ─┼→ 感知模块 → 场景理解 → 路径规划 → 运动控制 → 执行器
IMU ─────┘ (20ms) (15ms) (10ms) (5ms)
流水线时序(50Hz主循环):
时刻t: 感知(t) 理解(t-1) 规划(t-2) 控制(t-3)
时刻t+1: 感知(t+1) 理解(t) 规划(t-1) 控制(t-2)
关键设计要点:
动态批处理是在保证延迟约束的前提下,动态调整批大小以提高吞吐量的技术。
批处理之所以高效,主要因为:
效率模型: \(\text{效率}(B) = \frac{B \times \text{计算量}}{T_{\text{固定}} + B \times T_{\text{变动}}}\)
其中$B$是批大小,存在最优批大小$B^*$使效率最大化。
问题描述:
在自然语言处理或可变目标数量的检测任务中,输入长度不一:
批次内序列长度:[128, 45, 89, 256, 34, 198, 67, 145]
朴素Padding方案:
所有序列填充到256 → 计算浪费 = (256×8 - 实际总长度) / (256×8) ≈ 60%
优化策略:
将批次按长度排序并分组:
组1: [34, 45, 67] → Pad到67
组2: [89, 128, 145] → Pad到145
组3: [198, 256] → Pad到256
计算浪费降至 ~20%
原始: [seq1_____] [seq2________] [seq3___]
打包: [seq1|seq2|seq3|seq4|seq5|...]
需要额外维护位置信息和掩码
注意力掩码优化:
对于Transformer模型,可以使用块稀疏注意力: \(\text{BlockSparseAttention} = \text{Softmax}(QK^T \odot M)V\) 其中$M$是块掩码矩阵。
机会批处理(Opportunistic Batching)在请求到达时动态形成批次:
等待策略:
if (队列中请求数 >= 最小批大小) {
立即处理
} else if (最早请求等待时间 > 延迟阈值) {
处理当前所有请求
} else {
继续等待
}
自适应批大小调整:
基于Little’s Law:$L = \lambda W$
动态调整策略:
if (当前延迟 < 目标延迟 × 0.8) {
增大批大小上限
} else if (当前延迟 > 目标延迟 × 0.95) {
减小批大小上限
}
在自动驾驶等实时系统中,批处理策略需要特别谨慎:
分级批处理策略:
关键路径(10ms期限):
- 紧急制动检测:批大小=1,无等待
- 碰撞预警:批大小≤2,最大等待2ms
常规路径(50ms期限):
- 车道线检测:批大小≤8,最大等待10ms
- 交通标志识别:批大小≤16,最大等待20ms
后台任务(无严格期限):
- 地图更新:批大小32-64,机会批处理
硬实时(Hard Real-Time):
软实时(Soft Real-Time):
Firm实时:
速率单调调度(Rate Monotonic Scheduling, RMS):
任务按周期排序,周期越短优先级越高:
任务集合:
任务A: 周期=10ms, 执行时间=2ms
任务B: 周期=20ms, 执行时间=5ms
任务C: 周期=50ms, 执行时间=10ms
优先级: A > B > C
可调度性测试(Liu & Layland界限):
U = Σ(Cᵢ/Tᵢ) ≤ n(2^(1/n) - 1)
其中Cᵢ是执行时间,Tᵢ是周期,n是任务数
最早期限优先(Earliest Deadline First, EDF):
动态优先级,总是执行期限最近的任务:
时刻t的任务队列:
任务1: 期限 = t+5ms
任务2: 期限 = t+3ms ← 优先执行
任务3: 期限 = t+8ms
EDF是最优的:如果任务集可调度,EDF一定能调度
利用率界限:U ≤ 1(100%利用率)
最坏情况执行时间分析对于硬实时系统至关重要:
静态分析方法:
基本块执行时间:
BB1: 10 cycles (内存访问)
BB2: 5 cycles (计算)
BB3: 15 cycles (分支预测失败)
控制流路径:
Path1: BB1 → BB2 → BB3 = 30 cycles
Path2: BB1 → BB3 = 25 cycles
WCET = max(所有路径) = 30 cycles
缓存和内存的影响:
缓存分析:
- 必中(Must Hit):100% 在缓存中
- 必失(Must Miss):100% 不在缓存中
- 可能(May):不确定
WCET计算:
WCET = WCET_base + Σ(可能失效 × 失效代价)
GPU上的WCET挑战:
GPU的WCET分析特别困难,因为:
缓解策略:
安全关键路径设计:
┌─────────────┐
传感器 → 预处理 → │ 主感知模型 │ → 决策
↓ └─────────────┘ ↓
┌─────────────┐ ┌─────────┐
│ 简化备份 │ ─────────→ │ 仲裁器 │ → 执行
└─────────────┘ └─────────┘
(低延迟路径) (安全检查)
时间预算分配(100ms总预算):
- 传感器采集:10ms
- 预处理:10ms
- 感知:40ms(主路径)/ 20ms(备份路径)
- 决策:20ms
- 执行验证:10ms
- 余量:10ms
优先级反转问题及解决:
问题场景:
高优先级任务(H) 等待低优先级任务(L)持有的资源
中优先级任务(M) 抢占L,导致H间接等待M
解决方案:
1. 优先级继承:L临时继承H的优先级
2. 优先级天花板:资源关联最高可能优先级
3. 无锁数据结构:避免锁竞争
在具身智能设备上,能量效率至关重要:
DVFS(动态电压频率调节):
能量模型:E = C × V² × f × t
功率模型:P = C × V² × f
策略:
- 空闲时降频:f_idle = 0.4 × f_max
- 批处理时升频:f_batch = f_max
- deadline宽松时降频运行更长时间
大小核调度(big.LITTLE):
任务分类:
- 延迟敏感 → 大核(高性能)
- 吞吐量导向 → 小核集群(高效率)
- 后台任务 → 小核(低功耗)
迁移策略:
if (任务负载 > 阈值 && 大核可用) {
迁移到大核
} else if (任务负载 < 阈值 && 在大核上) {
迁移到小核
}
在多租户或多模型场景下,协同调度可以提高整体效率:
GPU时分复用:
时间片分配(10ms周期):
模型A(高优先级): 6ms
模型B(中优先级): 3ms
模型C(低优先级): 1ms
上下文切换开销:~0.5ms
有效利用率:95%
空间复用(MPS/MIG):
选择策略:
if (模型间无干扰 && 总显存 < GPU容量) {
使用MPS共享
} else if (需要性能隔离) {
使用MIG分区
} else {
时分复用
}
本章深入探讨了AI编译器中的并行化与调度策略,涵盖了从基础的数据/模型并行到复杂的实时调度约束。核心要点包括:
关键概念:
关键公式:
实践指导:
练习6.1:Ring-AllReduce通信分析
在4个GPU的数据并行训练中,模型参数量为100MB,网络带宽为10GB/s。计算Ring-AllReduce的通信时间。
💡 提示:Ring-AllReduce分为scatter-reduce和all-gather两个阶段,每个阶段传输$(P-1)/P$的数据量。
练习6.2:流水线气泡计算
一个模型被分成4个阶段进行流水线并行,使用8个微批次。计算流水线的气泡比例和设备利用率。
💡 提示:使用公式:气泡比例 = $(P-1)/(K+P-1)$
练习6.3:RMS可调度性判断
三个周期性任务:
判断这个任务集在单核处理器上是否可以用RMS调度。
💡 提示:计算CPU利用率并与Liu-Layland界限比较。
练习6.4:动态批处理优化
一个在线推理服务,请求到达率服从泊松分布($\lambda = 100$ req/s),单个请求处理时间1ms,批处理8个请求需要3ms。设计一个动态批处理策略,使得99%的请求延迟小于10ms。
💡 提示:考虑等待时间阈值和批大小的关系,使用排队论分析。
练习6.5:NUMA感知的并行策略
在2个NUMA节点的系统上,每个节点有4个GPU。设计一个混合并行策略,模型大小16GB,每个GPU显存32GB,批大小64。节点内带宽200GB/s,跨节点50GB/s。
💡 提示:考虑数据并行和模型并行的通信模式差异。
练习6.6:端到端延迟优化
设计一个自动驾驶感知系统的并行调度方案。系统包含:
💡 提示:考虑传感器融合时序、模型依赖关系、GPU负载均衡。
练习6.7:投机执行支持
为支持大语言模型的投机解码(speculative decoding),设计编译器的调度支持。小模型在GPU上投机生成k个token,大模型批量验证。
💡 提示:考虑投机成功率、批处理效率、内存管理。
错误:只考虑计算时间,忽略通信开销
// 错误假设
加速比 = 并行度P
// 实际情况
加速比 = P / (1 + 通信时间/计算时间)
正确做法:通信和计算重叠,使用异步通信
错误:简单均分任务,忽略任务异构性
// 问题场景
GPU0: 处理简单图像 → 10ms完成 → 等待
GPU1: 处理复杂图像 → 30ms完成
正确做法:动态负载均衡,任务窃取
错误:微批次数量过少
// 4阶段流水线,4个微批次
气泡率 = 3/7 = 43% (太高)
正确做法:增加微批次数,但需权衡内存开销
错误:随机分配内存和线程
// 性能可能相差2-3倍
跨NUMA访问延迟:~300ns
本地NUMA访问:~100ns
正确做法:NUMA感知的内存分配和线程绑定
错误:盲目增大批次追求吞吐量
批大小256 → 延迟500ms → 用户体验差
正确做法:根据SLA要求动态调整批大小
错误:频繁的全局同步
for epoch in epochs:
for batch in batches:
forward()
backward()
sync() # 每个batch都同步
正确做法:延迟同步,累积多个batch的梯度
错误:平均性能好但最坏情况差
平均延迟:20ms ✓
P99延迟:200ms ✗ (超过100ms要求)
正确做法:关注尾延迟,预留安全余量
错误:过度细粒度的任务划分
任务粒度1ms + 切换开销0.5ms = 33%开销
正确做法:合并小任务,减少调度开销