Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← 人工智能 / Artificial Intelligence

AI 编译器 / AI Compilers

1. AI 编译器全景——为什么模型需要编译器 / The AI Compiler Landscape and Why Models Need Compilers

2. 编译原理速通——面向 ML 工程师的核心概念 / Compiler Fundamentals for Machine Learning Engineers

3. 中间表示基础——理解 IR 层级与 lowering 链路 / Intermediate Representation Levels and Lowering Pipelines

4. 计算图的构建与表示 / Building and Representing Computational Graphs

5. MLIR 架构、方言与渐进式降级 / MLIR Architecture, Dialects, and Progressive Lowering

6. 算子语义、广播、归约与形状推导 / Operator Semantics, Broadcasting, Reduction, and Shape Inference

7. 模型前端格式:ONNX、TFLite、HLO 与 SavedModel / Model Frontend Formats: ONNX, TFLite, HLO, and SavedModel

8. 图优化 Pass——经典优化在 ML 中的应用 / Graph Optimization Passes for Machine Learning

9. 算子融合——编译器最重要的性能优化 / Operator Fusion as a Core Compiler Optimization

10. 内存规划——Buffer 分配与生命周期管理 / Memory Planning, Buffer Allocation, and Lifetime Management

11. Layout 优化——数据排布转换与内存效率 / Layout Optimization for Data Movement and Memory Efficiency

12. 动态 Shape——符号分析与形状处理 / Dynamic Shapes, Symbolic Analysis, and Shape Processing

13. 硬件约束下的操作调度 / Operation Scheduling Under Hardware Constraints

14. 从模板、DSL 到 IR 降级的代码生成架构 / Code Generation Architectures from Templates and DSLs to IR Lowering

15. CPU 后端:SIMD、分块与多线程 / CPU Backends with SIMD, Tiling, and Multithreading

16. CUDA 后端:合并访存与 Tensor Core / CUDA Backends, Memory Coalescing, and Tensor Cores

17. NPU 后端:脉动阵列与端侧 AI 生态 / NPU Backends, Systolic Arrays, and Edge AI Ecosystems

18. Kernel 性能基础:Roofline 与 Occupancy / Kernel Performance Fundamentals with Roofline and Occupancy

19. CUTLASS 与分层 GEMM 模板 / CUTLASS and Hierarchical GEMM Templates

20. TVM Tensor Expression 与计算调度分离 / TVM Tensor Expressions and Compute-Schedule Separation

21. 使用 Triton 编写高性能 GPU Kernel / Triton for High-Performance GPU Kernels in Python

22. 基于成本模型与实测搜索的自动调度 / Automatic Scheduling with Cost Models and Measurement-Based Search

23. XLA 内部机制:HLO、融合与 SPMD / XLA Internals, HLO, Fusion, and SPMD

24. Torch-MLIR:从 PyTorch 算子到 MLIR 方言 / Torch-MLIR from PyTorch Operators to MLIR Dialects

25. torch.compile:Dynamo、AOTAutograd、Inductor 与 Triton / Torch Compile with Dynamo, AOTAutograd, Inductor, and Triton

26. 从 MLIR 经 LLVM 降级到机器码 / Lowering from MLIR Through LLVM to Machine Code

27. 量化——低精度推理的工程实践 / Engineering Low-Precision Inference with Quantization

28. 分布式编译与训练——多设备编排的编译器支持 / Compiler Support for Distributed Training and Multi-Device Orchestration

29. 生产调试——真实问题的编译器视角排查 / Production Debugging from the Compiler Perspective

30. 未来方向——AI 编译器的新挑战与机遇 / Future Challenges and Opportunities for AI Compilers

本页目录

生产调试——真实问题的编译器视角排查 / Production Debugging from the Compiler Perspective ​

📅 创建时间:2026-06-03 🏷️ 标签:#调试 #Profiling #NVTX #Nsight #PyTorchProfiler #TensorBoard #torch.compile调试 #dot图 #MLIR调试 📚 前置知识:[[../training-infra/02-distributed-training]](../training-infra/02-distributed-training) [[24-torch-compile]](torch.compile) 📚 相关知识:[[12-scheduling]](调度) [[08-operator-fusion]](算子融合)


┌──────────────────────────────────────────────────────────────────────────────┐
│  📌 场景:推理 P99 延迟周期性抖动                                              │
├──────────────────────────────────────────────────────────────────────────────┤
│  你的推理服务 P99 延迟从 20ms 突然涨到 200ms,持续了 30 分钟,                 │
│  然后恢复正常:                                                                │
│  • 外部监控:请求量没有显著变化                                                │
│  • 服务端日志:没有明显错误                                                    │
│  • GPU 利用率:从 70% 掉到 30%                                                 │
│  • 你开始怀疑 JIT 编译缓存失效了,但不知道如何确认                              │
└──────────────────────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10

第1节:性能分析工具全家桶 ​

1.1 PyTorch Profiler ​

PyTorch Profiler 是 PyTorch 官方的性能分析工具,可以分析 CPU 和 GPU 上的执行时间。

python
import torch
from torch.profiler import profile, ProfilerActivity, schedule, tensorboard_trace_handler

# ============================================================
# 基础用法
# ============================================================

with profile(
    activities=[
        ProfilerActivity.CPU,           # 分析 CPU 时间
        ProfilerActivity.CUDA,          # 分析 GPU 时间
    ],
    schedule=schedule(
        wait=1,      # 跳过前 1 个 step
        warmup=1,    # 预热 1 个 step
        active=3,    # 分析 3 个 step
        repeat=2     # 重复 2 次
    ),
    on_trace_ready=tensorboard_trace_handler('./profiler_logs'),  # 输出到 TensorBoard
    record_shapes=True,        # 记录 tensor shape
    profile_memory=True,       # 分析显存使用
    with_stack=True,           # 记录调用栈
) as prof:
    
    for step, batch in enumerate(dataloader):
        # 训练步骤
        output = model(batch)
        loss = criterion(output, target)
        loss.backward()
        optimizer.step()
        optimizer.zero_grad()
        
        prof.step()  # 进入下一个 step

# ============================================================
# 分析输出
# ============================================================

# 打印 CPU 时间最长的操作
print(prof.key_averages().table(
    sort_by="cpu_time_total",
    row_limit=20
))

# 打印 GPU 时间最长的操作
print(prof.key_averages().table(
    sort_by="cuda_time_total",
    row_limit=20
))

# 打印显存使用
print(prof.key_averages().table(
    sort_by="self_cuda_memory_usage",
    row_limit=20
))

# ============================================================
# 导出到 TensorBoard
# ============================================================

# 在终端运行:
# tensorboard --logdir=./profiler_logs
# 然后打开 http://localhost:6006
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

1.2 NVTX(NVIDIA Tools Extensions) ​

NVTX 允许你在 CUDA 代码中插入标记,用于标识代码的不同区域。

python
import torch.cuda.nvtx as nvtx

# ============================================================
# 基本标记
# ============================================================

def train_step(batch):
    nvtx.range_push("data_transfer")  # 开始一个区间
    batch = batch.to('cuda')
    nvtx.range_pop()
    
    nvtx.range_push("forward")
    output = model(batch)
    nvtx.range_pop()
    
    nvtx.range_push("backward")
    loss = criterion(output, target)
    loss.backward()
    nvtx.range_pop()
    
    nvtx.range_push("optimizer")
    optimizer.step()
    optimizer.zero_grad()
    nvtx.range_pop()

# ============================================================
# 带颜色的命名区间
# ============================================================

# nvtx.range_push 支持颜色字符串
nvtx.range_push("forward", "green")
nvtx.range_push("backward", "red")

# ============================================================
# 在 torch.compile 中使用 NVTX
# ============================================================

# PyTorch 2.0+ 支持自动插入 NVTX 标记
with profile(
    activities=[ProfilerActivity.CUDA],
    with_stack=True,
) as prof:
    # 运行推理
    output = torch.compile(model)(batch)
    prof.step()

# 在 Nsight Systems 中可以看到编译后的 kernel
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

1.3 Nsight Compute 与 Nsight Systems ​

Nsight Compute:CUDA kernel 级分析,精确到每个 SM 的执行情况。

bash
# ============================================================
# Nsight Compute 命令行使用
# ============================================================

# 基本 kernel 分析
ncu --set full \
    --target-processes all \
    --output ./kernel_report \
    python inference.py

# 分析特定的 kernel
ncu --kernel-name "gemm" \
    --output ./gemm_report \
    python inference.py

# 分析 GPU 利用率
ncu --metrics sm__throughput.avg.pct_of_peak_sustained \
    python inference.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Nsight Systems:系统级分析,包括 CPU、GPU、内存、I/O 等。

bash
# ============================================================
# Nsight Systems 命令行使用
# ============================================================

# 基本分析
nsys profile --output ./profile \
    --trace=cuda,nvtx,osrt,pytorch \
    python training.py

# 导出为 JSON 格式(便于程序分析)
nsys profile --output ./profile \
    --type cuda \
    --format json \
    python inference.py

# 分析特定时间段
nsys profile --output ./profile \
    --cuda-memory-usage true \
    --duration 10 \  # 10 秒
    python inference.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

1.4 TensorBoard Profiler 插件 ​

bash
# ============================================================
# TensorBoard Profiler 使用
# ============================================================

# 1. 安装
pip install tensorboard tensorboard_plugin_profile

# 2. 启动 TensorBoard
tensorboard --logdir=./logs --port=6006

# 3. 在浏览器打开
# http://localhost:6006/#profile
1
2
3
4
5
6
7
8
9
10
11
12
python
# ============================================================
# TensorBoard 日志格式
# ============================================================

from torch.profiler import tensorboard_trace_handler

# 输出到 TensorBoard 兼容格式
with profile(
    activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
    on_trace_ready=tensorboard_trace_handler('./tb_logs'),
    ...
) as prof:
    for step in range(100):
        train_step(batch)
        prof.step()

# TensorBoard 会生成以下分析视图:
# - Overview Page: 性能概览和优化建议
# - Trace View: 时间线视图
# - GPU Kernel Stats: GPU kernel 统计
# - Memory View: 显存使用分析
# - CPU GPU Ops: CPU 和 GPU 操作对应关系
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

1.5 工具对比表 ​

工具粒度主要用途使用场景学习曲线
PyTorch ProfilerOp 级别PyTorch 训练/推理分析日常性能分析低
NVTX区间标记代码区域标记自定义代码分析低
Nsight ComputeKernel 级别CUDA kernel 细节GPU kernel 优化高
Nsight Systems系统级全系统分析定位瓶颈来源中
TensorBoardOp 级别可视化分析结果展示低

第2节:torch.compile 调试 ​

2.1 开启 debug 模式 ​

python
import torch

# ============================================================
# 基本 debug 配置
# ============================================================

# 开启 dynamo debug(查看 graph break)
torch._dynamo.config.debug = True
torch._dynamo.config.verbose = True

# 编译模型
compiled_model = torch.compile(
    model,
    backend="inductor",
    mode="reduce-overhead",
)

# ============================================================
# 捕获 Graph Break
# ============================================================

# 运行后,Dynamo 会输出每个 graph break 的原因
# 例如:
# Graph break: call_function max_pool2d ... not supported
# Graph break: call_method items() ... not a tensor

# ============================================================
# 导出编译后的代码
# ============================================================

torch._inductor.config.debug = True
torch._inductor.config.trace.enabled = True

compiled_model = torch.compile(
    model,
    backend="inductor",
)

# 运行一次以生成代码
output = compiled_model(sample_input)

# 导出的代码位置:
# torch._inductor.config.trace.output = "./inductor_output"
# 或在环境变量设置:
# TORCHINDUCTOR_TRACE_OUTPUT=./logs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
python
# ============================================================
# 查看生成的 Inductor 代码
# ============================================================

import torch._inductor

# 设置输出目录
torch._inductor.config.trace.output = "./inductor_logs"

# 编译
compiled = torch.compile(model)

# 运行(会自动生成代码)
output = compiled(input)

# 在 ./inductor_logs 目录下会有:
# - *.py: 生成的 CUDA kernel 代码
# - *.cpp: 生成的 cpp wrapper 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

2.2 XLA HLO 可视化 ​

python
# ============================================================
# XLA HLO 调试
# ============================================================

import torch_xla.core.xla_model as xm
import torch_xla

# 设置 XLA 调试选项
xla_env = os.environ
xla_env["XLA_IR_DEBUG"] = "1"           # 输出 IR
xla_env["XLA_HLO_DEBUG"] = "1"          # 输出 HLO
xla_env["XLA_SAVE_TENSORS_FILE"] = "./xla_debug"  # 保存 tensor

# 运行 XLA 编译
model_xla = torch_xla.compile(model)

# 生成的 HLO 文件可以用 XLA inspector 查看
# https://github.com/jreiffers/XLAInspector
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

2.3 编译调试代码示例 ​

python
# ============================================================
# torch.compile 调试完整示例
# ============================================================

import torch
import torch._dynamo as dynamo
import torch._inductor as inductor

def debug_torch_compile():
    # 1. 设置 debug 配置
    dynamo.config.debug = True
    dynamo.config.verbose = True
    dynamo.config.replay = True  # 允许重放
    
    inductor.config.debug = True
    inductor.config.trace.enabled = True
    inductor.config.trace.output = "./debug_output"
    
    # 2. 编译模型
    print("=" * 60)
    print("开始编译")
    print("=" * 60)
    
    compiled = torch.compile(
        model,
        backend="inductor",
        options={
            "trace.enabled": True,
            "trace.output": "./debug_output",
        }
    )
    
    # 3. 运行编译
    print("\n" + "=" * 60)
    print("运行第一次推理(编译阶段)")
    print("=" * 60)
    
    # Warmup
    torch.cuda.synchronize()
    output = compiled(sample_input)
    torch.cuda.synchronize()
    
    # 4. 分析生成的代码
    print("\n" + "=" * 60)
    print("分析生成的代码")
    print("=" * 60)
    
    # 读取生成的 kernel
    import os
    for f in os.listdir("./debug_output"):
        if f.endswith(".py"):
            print(f"\n--- {f} ---")
            with open(os.path.join("./debug_output", f)) as fp:
                print(fp.read()[:2000])  # 打印前 2000 字符
    
    # 5. 检查是否有 graph break
    print("\n" + "=" * 60)
    print("检查 Graph Break")
    print("=" * 60)
    
    # dynamo 会输出类似:
    # TorchDynamo skipped 1 graphs with 2 graph breaks
    
    return compiled

# 运行调试
compiled_model = debug_torch_compile()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

第3节:MLIR 调试 ​

3.1 MLIR Pass 调试 ​

bash
# ============================================================
# MLIR 调试选项
# ============================================================

# 打印每个 pass 后的 IR
mlir-opt --mlir-print-ir-after-all input.mlir

# 打印每个 pass 前的 IR
mlir-opt --mlir-print-ir-before-all input.mlir

# 只打印感兴趣的 pass
mlir-opt \
    --mlir-print-ir-after-change \
    --mlir-pass-pipeline="builtin.module(cse,canonicalize)" \
    input.mlir

# 打印展平的 SSA 形式
mlir-opt --mlir-print-flat-root-scope input.mlir

# 打印 operation 的类型
mlir-opt --mlir-print-op-generic input.mlir
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

3.2 MLIR 工具链 ​

python
# ============================================================
# 在 Python 中使用 MLIR 工具链
# ============================================================

import subprocess
import os

def run_mlir_pass(mlir_file, passes, output_file=None):
    """
    运行 MLIR pass
    """
    cmd = ["mlir-opt"]
    
    # 添加 pass
    cmd.extend(["--pass-pipeline", passes])
    
    # 输入文件
    cmd.append(mlir_file)
    
    # 输出文件
    if output_file:
        cmd.extend(["-o", output_file])
    
    result = subprocess.run(cmd, capture_output=True, text=True)
    
    return result.stdout, result.stderr


def visualize_mlir(mlir_code, title="MLIR"):
    """
    可视化 MLIR(需要在支持的环境中运行)
    """
    # 打印 IR
    print(f"\n{'=' * 60}")
    print(f"{title}")
    print(f"{'=' * 60}")
    print(mlir_code)
    
    # 可以导出为 dot 图
    # 使用 mlir-tblgen 生成 .td 定义,然后用 mlir-opt --mlir-print-dot
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

第4节:常见性能问题根源分析 ​

4.1 GPU 利用率低 ​

python
# ============================================================
# GPU 利用率分析
# ============================================================

# 问题分类
class GPUUtilizationAnalyzer:
    """
    分析 GPU 利用率低的根本原因
    """
    
    def analyze_low_utilization(self, profiler_result):
        """
        分析低利用率的根本原因
        """
        cpu_time = self.get_cpu_time(profiler_result)
        gpu_time = self.get_gpu_time(profiler_result)
        
        if cpu_time > gpu_time * 1.5:
            return "CPU_BOUND"
        elif gpu_time < self.theoretical_min_time:
            return "MEMORY_BOUND"
        else:
            return "COMPUTE_BOUND"
    
    def diagnose_kernel_launch_overhead(self, profiler_result):
        """
        诊断 kernel launch 开销
        """
        # 太多小的 kernel 会导致 launch overhead
        small_kernels = []
        for kernel in profiler_result.kernels():
            if kernel.duration < 1:  # 小于 1 微秒
                small_kernels.append(kernel)
        
        if len(small_kernels) > 100:
            return "TOO_MANY_SMALL_KERNELS"
        
        return "OK"
    
    def diagnose_memory_bound(self, profiler_result):
        """
        诊断是否是显存带宽瓶颈
        """
        memory_ops = self.get_memory_ops(profiler_result)
        compute_ops = self.get_compute_ops(profiler_result)
        
        memory_time = sum(op.duration for op in memory_ops)
        compute_time = sum(op.duration for op in compute_ops)
        
        if memory_time > compute_time * 2:
            return "MEMORY_BOUND"
        
        return "COMPUTE_BOUND"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

GPU 利用率低的原因和解决方案:

原因诊断方法解决方案
CPU 瓶颈CPU time >> GPU time优化数据加载、减少 Python 开销
Kernel 太小大量 < 1μs 的 kernel使用 torch.compile 融合 kernel
显存带宽瓶颈memory_time > compute_time使用更高效的数据格式、融合操作
内存不足导致 swapGIL + CPU wait减小 batch size、使用梯度检查点
通信瓶颈训练时 GPU 空闲使用 NCCL、优化通信 schedule

4.2 P99 延迟抖动 ​

python
# ============================================================
# P99 抖动分析
# ============================================================

class LatencyJitterAnalyzer:
    """
    分析 P99 延迟抖动的原因
    """
    
    def check_gc_pauses(self):
        """
        检查 GC 暂停
        """
        import gc
        import time
        
        gc_waits = []
        
        # 在关键路径上插入 GC 检查
        for _ in range(1000):
            start = time.perf_counter()
            gc.collect()  # 手动触发检查
            elapsed = time.perf_counter() - start
            gc_waits.append(elapsed)
        
        max_gc_time = max(gc_waits)
        if max_gc_time > 0.01:  # 超过 10ms
            return f"GC_PAUSE: {max_gc_time * 1000:.1f}ms"
        
        return "OK"
    
    def check_jit_warmup(self, model):
        """
        检查 JIT 热身延迟
        """
        import time
        
        # 检查是否是第一次编译
        start = time.perf_counter()
        output = model(sample_input)
        torch.cuda.synchronize()
        first_time = time.perf_counter() - start
        
        # 后续调用应该更快
        warm_times = []
        for _ in range(10):
            start = time.perf_counter()
            output = model(sample_input)
            torch.cuda.synchronize()
            warm_times.append(time.perf_counter() - start)
        
        avg_warm = sum(warm_times) / len(warm_times)
        
        if first_time > avg_warm * 10:
            return f"JIT_WARMUP: first={first_time*1000:.1f}ms, warm={avg_warm*1000:.1f}ms"
        
        return "OK"
    
    def check_memory_fragmentation(self):
        """
        检查内存碎片化
        """
        # 反复分配和释放不同大小的 tensor
        import torch
        
        allocations = []
        for size in range(100, 10000, 100):
            t = torch.randn(size)
            allocations.append(t)
        
        # 释放部分
        for i in range(0, len(allocations), 2):
            del allocations[i]
        
        # 再分配
        start = time.perf_counter()
        torch.randn(5000)
        allocate_time = time.perf_counter() - start
        
        if allocate_time > 0.001:  # 超过 1ms 说明有碎片
            return f"MEMORY_FRAGMENTATION: {allocate_time*1000:.1f}ms"
        
        return "OK"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

P99 抖动常见原因:

原因典型延迟诊断方法解决方案
GC 暂停10-100msgc.collect() 耗时减少对象创建、使用对象池
JIT 编译1-10s首次调用慢预热、缓存编译结果
内存碎片1-10ms分配延迟使用内存池、避免频繁分配释放
锁竞争变化大多线程 profiling减少锁、使用无锁数据结构
系统调度0.1-1ms系统工具绑核、优先级设置

4.3 CPU Bound 诊断 ​

python
# ============================================================
# CPU Bound 分析
# ============================================================

class CPUBoundAnalyzer:
    """
    分析 CPU bound 的原因
    """
    
    def diagnose_gil_contention(self):
        """
        诊断 GIL 争用
        """
        import threading
        import time
        
        results = []
        
        def cpu_work():
            start = time.perf_counter()
            for _ in range(1000000):
                pass
            results.append(time.perf_counter() - start)
        
        # 单线程
        t1 = threading.Thread(target=cpu_work)
        t1.start()
        t1.join()
        single_thread_time = results[0]
        
        # 双线程
        results.clear()
        t1 = threading.Thread(target=cpu_work)
        t2 = threading.Thread(target=cpu_work)
        t1.start(); t2.start()
        t1.join(); t2.join()
        dual_thread_time = max(results)
        
        # 如果双线程没有加速,说明是 GIL 瓶颈
        speedup = single_thread_time / dual_thread_time
        if speedup < 1.3:  # 理论应该接近 2
            return f"GIL_CONTENTION: speedup={speedup:.2f}x"
        
        return f"OK: speedup={speedup:.2f}x"
    
    def diagnose_data_loading(self):
        """
        诊断数据加载瓶颈
        """
        import time
        import torch.utils.data as data
        
        # 测量 DataLoader 的 throughput
        loader = data.DataLoader(dataset, batch_size=32, num_workers=4)
        
        times = []
        for batch in loader:
            start = time.perf_counter()
            # 模拟处理
            _ = batch.to('cuda')
            torch.cuda.synchronize()
            times.append(time.perf_counter() - start)
        
        avg_time = sum(times) / len(times)
        p99_time = sorted(times)[int(len(times) * 0.99)]
        
        # 如果 p99 >> avg,说明有长尾
        if p99_time > avg_time * 3:
            return f"DATA_LOADING_SLOW: avg={avg_time*1000:.1f}ms, p99={p99_time*1000:.1f}ms"
        
        return f"OK: avg={avg_time*1000:.1f}ms"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

第5节:JIT 编译缓存 ​

5.1 torch.compile 缓存机制 ​

python
# ============================================================
# torch.compile 缓存机制
# ============================================================

import torch._dynamo as dynamo

# 查看缓存统计
print(f"缓存统计: {dynamo.eval_frame.cached_frames_stats()}")

# 清理缓存
dynamo.reset()

# ============================================================
# 缓存配置
# ============================================================

# 设置 FX graph 缓存大小(默认 64)
torch._inductor.config.fx_graph_cache = True
torch._inductor.config.fx_graph_cache_size = 128

# 环境变量方式
# TORCHINDUCTOR_FX_CACHE_SIZE=128
# TORCHINDUCTOR_CACHE=1

# ============================================================
# 缓存失效原因
# ============================================================

"""
缓存失效的常见原因:
1. 输入 shape 变化 -> 生成新的编译结果
2. 输入 dtype 变化
3. 设备变化(cuda:0 vs cuda:1)
4. 模型参数变化(微调后)
5. 全局配置变化(精度设置等)
6. 环境变量变化
"""

def check_cache_hit_rate():
    """
    检查缓存命中率
    """
    # 方法 1:查看日志
    # torch.compile 会输出类似:
    # "torch.compile cache stats: 10 cache hits, 5 cache misses"
    
    # 方法 2:使用环境变量
    import os
    os.environ["TORCH_SHOW_CPP_STACKS"] = "1"
    
    # 方法 3:查看 counters
    from torch._inductor import compile_fx
    print(compile_fx.COUNTERS)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

5.2 缓存调试 ​

python
# ============================================================
# 缓存调试示例
# ============================================================

import torch
import torch._inductor as inductor

# 开启缓存调试
inductor.config.debug = True
inductor.config.trace.cache = True

# 第一次编译
print("=" * 60)
print("第一次推理")
print("=" * 60)

model1 = torch.nn.Linear(128, 128).cuda()
compiled1 = torch.compile(model1)
output1 = compiled1(torch.randn(32, 128).cuda())
torch.cuda.synchronize()

# 检查缓存
print(f"缓存键: {compiled1._cache_key}")
print(f"编译缓存目录: {inductor.config.trace.output}")

# 第二次推理(相同 shape)
print("\n" + "=" * 60)
print("第二次推理(相同 shape)")
print("=" * 60)

output2 = compiled1(torch.randn(32, 128).cuda())
torch.cuda.synchronize()

# 不同 shape 的推理
print("\n" + "=" * 60)
print("不同 shape 推理")
print("=" * 60)

output3 = compiled1(torch.randn(64, 128).cuda())  # batch_size 变化
torch.cuda.synchronize()
# 这会导致缓存失效,生成新的编译结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

第6节:编译正确性验证 ​

6.1 数值误差分析 ​

python
# ============================================================
# 编译正确性验证
# ============================================================

import torch
import torch.testing as testing

def verify_compiled_correctness(model, input_data):
    """
    验证编译模型与 eager 模型的数值一致性
    """
    model.eval()
    
    # Eager 模式输出
    with torch.no_grad():
        eager_output = model(input_data)
    
    # Compiled 模式输出
    compiled_model = torch.compile(model)
    with torch.no_grad():
        compiled_output = compiled_model(input_data)
    
    # 比较输出
    print("=" * 60)
    print("数值一致性检查")
    print("=" * 60)
    
    # 方法 1:使用 torch.testing.assert_close
    try:
        testing.assert_close(
            eager_output, 
            compiled_output,
            rtol=1e-3,      # 相对误差容限
            atol=1e-3,      # 绝对误差容限
        )
        print("✅ 数值一致")
    except AssertionError as e:
        print(f"❌ 数值不一致:\n{e}")
    
    # 方法 2:手动计算误差
    abs_diff = (eager_output - compiled_output).abs()
    rel_diff = abs_diff / eager_output.abs()
    
    print(f"\n绝对误差 - max: {abs_diff.max():.6f}, mean: {abs_diff.mean():.6f}")
    print(f"相对误差 - max: {rel_diff.max():.6f}, mean: {rel_diff.mean():.6f}")
    
    return eager_output, compiled_output


def diagnose_numerical_errors(eager, compiled, tolerance=1e-3):
    """
    诊断数值误差的来源
    """
    diff = (eager - compiled).abs()
    
    # 找出误差最大的位置
    flat_diff = diff.flatten()
    flat_eager = eager.flatten()
    
    # 按相对误差排序
    rel_error = flat_diff / (flat_eager.abs() + 1e-8)
    _, indices = rel_error.topk(10)
    
    print("\n误差最大的 10 个位置:")
    for idx in indices:
        print(f"  位置 {idx}: eager={flat_eager[idx]:.4f}, "
              f"compiled={compiled.flatten()[idx]:.4f}, "
              f"diff={flat_diff[idx]:.6f}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

6.2 常见数值误差来源 ​

python
# ============================================================
# 数值误差来源分析
# ============================================================

class NumericalErrorAnalyzer:
    """
    分析编译引入的数值误差
    """
    
    def check_float_precision(self, eager, compiled):
        """
        检查浮点精度问题
        """
        # 不同硬件上的浮点运算顺序可能不同
        # 这可能导致微小差异
        
        # 检查是否在合理范围内
        diff = (eager - compiled).abs()
        rel_diff = diff / eager.abs()
        
        # FP32 的精度限制约为 1e-6
        if rel_diff.max() > 1e-5:
            return "FLOAT_PRECISION_ISSUE"
        
        return "OK"
    
    def check_operation_order(self, graph):
        """
        检查算子执行顺序
        """
        # 某些情况下,编译器可能改变算子的融合顺序
        # 这可能导致数值差异
        
        # 检查是否有顺序敏感的算子
        sensitive_ops = ['sort', 'topk', 'argmax']
        for op in sensitive_ops:
            if op in graph:
                print(f"⚠️  顺序敏感算子: {op}")
        
        return
    
    def check_reduction_order(self):
        """
        检查归约操作的顺序
        """
        # AllReduce 等操作的结果顺序可能不确定
        # 这可能导致数值差异
        
        return "OK"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

升华 ​

┌──────────────────────────────────────────────────────────────────────────────┐
│                           生产调试实践原则                                    │
├──────────────────────────────────────────────────────────────────────────────┤
│  1. 先定位后优化:用 profiler 确定瓶颈,不要猜测                                                       │
│  2. 分层排查:GPU 利用率低 → 可能是 CPU bound / memory bound / compute bound   │
│  3. 抖动先查 GC 和 JIT:P99 延迟抖动最常见的原因是垃圾回收和 JIT 编译            │
│  4. 缓存状态要监控:torch.compile 的缓存命中率直接影响推理延迟                   │
│  5. 数值安全第一:优化前先验证正确性,数值误差会累积                            │
└──────────────────────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9

"AI 可查 vs 必须理解"清单 ​

必须理解(不理解就等于不会):

  • 🔴 PyTorch Profiler 的使用方法和输出解读
  • 🔴 GPU 利用率低的三个原因:CPU bound / memory bound / compute bound
  • 🔴 P99 延迟抖动的常见原因:GC / JIT warmup / memory fragmentation
  • 🔴 torch.compile 缓存失效的原因
  • 🔴 数值误差的来源和验证方法

AI 可查(知道去哪查就行):

  • ✅ Nsight Compute/Systems 的具体使用参数
  • ✅ MLIR 调试选项的具体语法
  • ✅ 特定 kernel 的性能数据
  • ✅ 各硬件平台的性能基准
  • ✅ XLA HLO 的具体格式

学习状态:🟡 开始学习

最后更新于:

Pager
上一篇28. 分布式编译与训练——多设备编排的编译器支持 / Compiler Support for Distributed Training and Multi-Device Orchestration
下一篇30. 未来方向——AI 编译器的新挑战与机遇 / Future Challenges and Opportunities for AI Compilers

持续记录,持续成长

Copyright © Tidenflow