Temporal Breakdown 计算分析
概述
temporal_breakdown 是 GPU trace 分析中的核心指标,用于将 GPU 执行时间分解为三个主要类别:
- compute_time: 计算时间(COMPUTATION 类型的 kernel)
- non_compute_time: 非计算时间(COMMUNICATION、MEMORY 等类型的 kernel)
- idle_time: 空闲时间(GPU 没有执行任何 kernel 的时间)
核心要点:
- 多 stream 的 kernel 通过贪心区间合并算法处理,将所有重叠区间合并为不重叠的区间集合
- idle_time = 总时间跨度 - 合并后的实际运行时间
- non_compute_time = 总时间跨度 - compute_time - idle_time
提示
总时间跨度不是多个 stream 时间加起来,而是从第一个 kernel 开始到最后一个 kernel 结束的墙钟时间(wall clock time)。所以 idle_time 反映的是真实的 GPU 空闲间隙,即所有 stream 都没有 kernel 在执行的时间段。
整体计算流程
flowchart TD
A[加载 Trace 文件] --> B[提取 GPU Kernels]
B --> C[按 Stream 分组]
C --> D[合并重叠区间]
D --> E[计算总时间跨度]
E --> F[计算各类型时间]
F --> G[输出 Temporal Breakdown]
subgraph "Kernel 分类"
H[COMPUTATION]
I[COMMUNICATION]
J[MEMORY]
K[OTHER]
end
B --> H
B --> I
B --> J
B --> K
核心代码分析
1. 入口函数 get_temporal_breakdown
// src/analyzer.rs
pub fn get_temporal_breakdown(&mut self) -> Result<TemporalBreakdown> {
let gpu_kernels = self.get_gpu_kernels()?;
if gpu_kernels.is_empty() {
return Ok(TemporalBreakdown {
compute_time: 0.0, non_compute_time: 0.0, idle_time: 0.0,
compute_percent: 0.0, non_compute_percent: 0.0, idle_percent: 0.0,
});
}
// 1. 合并所有 kernel 的时间区间
let merged_intervals = merge_kernel_intervals(&gpu_kernels);
// 2. 计算总时间跨度 (从第一个 kernel 开始到最后一个 kernel 结束)
let kernel_time = if !merged_intervals.is_empty() {
merged_intervals.last().unwrap().end - merged_intervals.first().unwrap().start
} else { 0.0 };
// 3. 计算实际运行时间 (合并后的区间总和)
let kernel_run_time: f64 = merged_intervals.iter().map(|i| i.end - i.start).sum();
// 4. 空闲时间 = 总跨度 - 实际运行时间
let idle_time = (kernel_time - kernel_run_time).max(0.0);
// 5. 筛选计算类型的 kernel 并合并
let compute_kernels: Vec<GPUKernel> = gpu_kernels.iter()
.filter(|k| k.kernel_type == KernelType::Computation)
.cloned().collect();
let compute_intervals = merge_kernel_intervals(&compute_kernels);
let compute_time: f64 = compute_intervals.iter().map(|i| i.end - i.start).sum();
// 6. 非计算时间 = 总跨度 - 计算时间 - 空闲时间
let non_compute_time = (kernel_time - compute_time - idle_time).max(0.0);
// 7. 计算百分比
let (idle_percent, compute_percent, non_compute_percent) = if kernel_time > 0.0 {
((idle_time / kernel_time) * 100.0,
(compute_time / kernel_time) * 100.0,
(non_compute_time / kernel_time) * 100.0)
} else { (0.0, 0.0, 0.0) };
Ok(TemporalBreakdown {
compute_time, non_compute_time, idle_time,
compute_percent, non_compute_percent, idle_percent,
})
}
2. 区间合并算法 merge_kernel_intervals
这是处理多 stream 重叠的核心算法:
// src/utils.rs
pub fn merge_kernel_intervals(kernels: &[GPUKernel]) -> Vec<TimeInterval> {
if kernels.is_empty() { return Vec::new(); }
// 1. 提取所有 kernel 的 (start, end) 时间对
let mut times: Vec<(f64, f64)> = Vec::with_capacity(kernels.len());
for k in kernels {
times.push((k.start_time, k.end_time));
}
// 2. 按开始时间排序
times.sort_unstable_by(|a, b|
a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
// 3. 贪心合并重叠区间
let mut merged = Vec::with_capacity(times.len() / 4 + 1);
let mut current = TimeInterval { start: times[0].0, end: times[0].1 };
for &(start, end) in times.iter().skip(1) {
if start <= current.end {
// 区间重叠,扩展当前区间
current.end = current.end.max(end);
} else {
// 区间不重叠,保存当前区间,开始新区间
merged.push(current);
current = TimeInterval { start, end };
}
}
merged.push(current);
merged
}
多 Stream 处理机制
问题背景
GPU 可以有多个 CUDA Stream 并行执行 kernel。不同 stream 上的 kernel 可能在时间上重叠:

合并策略
算法将所有 stream 的 kernel 视为一个整体,合并重叠区间:
flowchart LR
subgraph "原始区间"
A["[0,100]"]
B["[50,180]"]
C["[80,120]"]
D["[150,250]"]
E["[200,300]"]
end
subgraph "排序后"
A1["[0,100]"]
B1["[50,180]"]
C1["[80,120]"]
D1["[150,250]"]
E1["[200,300]"]
end
subgraph "合并结果"
M1["[0,180]"]
M2["[200,300]"]
end
A --> A1
B --> B1
C --> C1
D --> D1
E --> E1
A1 --> M1
B1 --> M1
C1 --> M1
D1 --> M1
E1 --> M2
合并过程详解
sequenceDiagram
participant Input as 输入区间
participant Sort as 排序
participant Merge as 合并
participant Output as 输出
Input->>Sort: [0,100], [50,180], [80,120], [150,250], [200,300]
Sort->>Merge: 按 start 排序
Note over Merge: current = [0,100]
Merge->>Merge: [50,180] 重叠 → current = [0,180]
Merge->>Merge: [80,120] 重叠 → current = [0,180]
Merge->>Merge: [150,250] 重叠 → current = [0,250]
Merge->>Merge: [200,300] 重叠 → current = [0,300]
Merge->>Output: [[0,300]]
时间计算公式
flowchart TD
subgraph "时间定义"
A[kernel_time<br/>总时间跨度]
B[kernel_run_time<br/>实际运行时间]
C[idle_time<br/>空闲时间]
D[compute_time<br/>计算时间]
E[non_compute_time<br/>非计算时间]
end
subgraph "计算公式"
F["kernel_time = last.end - first.start"]
G["kernel_run_time = Σ(merged_interval.duration)"]
H["idle_time = kernel_time - kernel_run_time"]
I["compute_time = Σ(compute_merged.duration)"]
J["non_compute_time = kernel_time - compute_time - idle_time"]
end
A --> F
B --> G
C --> H
D --> I
E --> J
Kernel 类型分类
// src/analyzer.rs - extract_kernels 函数中的分类逻辑
when(col("cat").eq(lit("kernel")).or(col("cat").eq(lit("Kernel"))))
.then(
// Communication kernels: 包含 nccl, deep_ep, 或 rccl (不区分大小写)
when(col("name").str().contains(lit("(?i)nccl"), false)
.or(col("name").str().contains(lit("(?i)deep_ep"), false))
.or(col("name").str().contains(lit("(?i)rccl"), false)))
.then(lit("COMMUNICATION"))
// Memory kernels: 以 Memcpy, Memset, dma 开头
.when(col("name").str().starts_with(lit("Memcpy"))
.or(col("name").str().starts_with(lit("Memset")))
.or(col("name").str().starts_with(lit("dma"))))
.then(lit("MEMORY"))
// 其他都是 COMPUTATION
.otherwise(lit("COMPUTATION"))
)
flowchart TD
A[GPU Kernel] --> B{Category?}
B -->|kernel/Kernel| C{Name 包含?}
B -->|gpu_memcpy/gpu_memset| D[MEMORY]
B -->|cuda_runtime/cuda_driver| E[RUNTIME]
B -->|synchronization| F[SYNCHRONIZATION]
C -->|nccl/deep_ep/rccl| G[COMMUNICATION]
C -->|Memcpy/Memset/dma| D
C -->|其他| H[COMPUTATION]
数据结构
// src/models.rs
#[derive(Debug, Clone, Serialize)]
pub struct TemporalBreakdown {
pub compute_time: f64, // 计算时间 (微秒)
pub non_compute_time: f64, // 非计算时间 (微秒)
pub idle_time: f64, // 空闲时间 (微秒)
pub compute_percent: f64, // 计算时间百分比
pub non_compute_percent: f64, // 非计算时间百分比
pub idle_percent: f64, // 空闲时间百分比
}
#[derive(Debug, Clone)]
pub struct GPUKernel {
pub name: String,
pub start_time: f64,
pub duration: f64,
pub end_time: f64,
pub stream: i32,
pub kernel_type: KernelType,
// ... 其他字段
}
pub struct TimeInterval {
pub start: f64,
pub end: f64,
}
输出示例
{
"temporal_breakdown": {
"compute_time_us": 25402605.0,
"non_compute_time_us": 1251970.0,
"idle_time_us": 29603154.0,
"compute_percent": 45.15,
"non_compute_percent": 2.23,
"idle_percent": 52.62
}
}
多 Stream 场景的完整示例
注意: 同一个 CUDA Stream 内的 kernel 是串行执行的,不能同时运行。 不同 Stream 之间可以并行执行。
举例来说:
Stream 0: [====] [====]
0 100 200 300
Stream 1: [====]
50 150
合并后: [=========] [====]
0 150 200 300
总时间跨度 = 300 - 0 = 300 (不是 100+100+100=300)
实际运行时间 = (150-0) + (300-200) = 150 + 100 = 250
idle_time = 300 - 250 = 50 (就是 150~200 这段空隙)
性能优化
- 避免完整克隆:
merge_kernel_intervals只提取(start, end)元组,不克隆整个GPUKernel结构 - 不稳定排序: 使用
sort_unstable_by减少内存分配 - 预分配容量:
Vec::with_capacity减少动态扩容 - 并行处理: 使用
rayon进行并行统计计算
总结
temporal_breakdown 的计算核心是区间合并算法,它将所有 stream 的 kernel 执行时间合并为不重叠的区间,从而准确计算:
- GPU 实际工作时间(不重复计算重叠部分)
- GPU 空闲时间(没有任何 kernel 执行的间隙)
- 各类型 kernel 的时间占比
修改历史2 次提交
- docs: add temporal breakdown profiling documentationxiaocheng··
9235240