12.3.1 基于GPU的并行计算
预热的原因:
🧠 核心原因:GPU 的初始化和缓存机制会影响首次运行速度
1. 首次执行会有额外开销
- 当你第一次在一个设备(如
cuda:0)上执行一个操作时,系统会进行一些初始化工作:- 加载内核(kernel);
- 分配内存;
- 编译 PTX(Parallel Thread Execution)代码为特定 GPU 架构的指令;
- 初始化 CUDA 上下文;
- 这些操作只会在第一次运行时发生,后续调用就会快很多。
2. 缓存效应
- 某些计算或内存访问模式会被硬件缓存(cache)优化;
- 第一次运行可能命中率低,第二次就高了;
- 如果你在测量时不考虑这些因素,第一次的“慢速”会扭曲你的平均性能数据。
🔍 原因 1:缓存未命中(Cache Miss)
- 第一次执行某个操作时,所需的数据不在缓存中;
- 系统必须从内存甚至硬盘中读取,这会花费更多时间;
- 这叫做 缓存未命中(cache miss)。
✅ 原因 2:缓存命中(Cache Hit)
- 第二次运行相同或类似的操作时,所需数据很可能已经缓存好了;
- 系统可以直接从缓存中快速读取;
- 这叫做 缓存命中(cache hit),速度快很多。
你的理解是正确的,这里的并行确实是因为 PyTorch 的 CUDA 操作是异步的,而不是因为 Python 本身的同步特性。
我来详细解释一下:
1. Python 是同步的,但 CUDA 操作是异步的
在 Python 中,默认情况下代码是同步执行的,即一行执行完才会执行下一行。
然而,当调用 PyTorch 的 CUDA 操作(如 .cuda() 上的张量计算)时,PyTorch 并不会阻塞 Python 线程,而是将计算任务提交到 GPU 的命令队列中,然后立即返回,这样 Python 就可以继续执行下一行代码。
2. GPU 有自己的并行执行能力
GPU 有多个流(streams)和多处理器,可以同时执行多个独立的计算任务。
当你在代码中连续调用两个 GPU 的计算任务时:
run(x_gpu1) # 任务1提交到 GPU1
run(x_gpu2) # 任务2提交到 GPU2
PyTorch 会:
- 将第一个任务提交到 GPU1 的队列
- 将第二个任务提交到 GPU2 的队列
- 两个 GPU 可以同时执行各自的任务
- Python 代码继续往下执行,不等待 GPU 计算完成
3. torch.cuda.synchronize() 的作用
如果你希望等待某个 GPU 完成所有任务,就需要调用:
torch.cuda.synchronize(device)
否则,你测量时间时可能会包含 GPU 计算时间 + Python 执行时间的不准确结果。
4. 为什么你的例子中删除 synchronize 能实现并行
原代码:
with d21.Benchmark('GPU1 time'):
run(x_gpu1)
torch.cuda.synchronize(devices[0]) # 等待 GPU1
with d21.Benchmark('GPU2 time'):
run(x_gpu2)
torch.cuda.synchronize(devices[1]) # 等待 GPU2
总时间 ≈ 0.46 + 0.47 ≈ 0.93 秒(串行测量)。
删除 synchronize 后:
with d21.Benchmark('GPU1 & GPU2'):
run(x_gpu1) # 提交到 GPU1
run(x_gpu2) # 立即提交到 GPU2
torch.cuda.synchronize() # 等待所有 GPU 完成
总时间 ≈ 0.458 秒(几乎等于单个 GPU 的时间),说明两个 GPU 是同时执行的。
5. 这就是“自动并行”
PyTorch 底层自动管理 GPU 任务调度,你不需要手动写多线程或多进程代码,只需按顺序提交任务到不同设备,系统会自动尝试并行执行。
✅ 总结:
- Python 代码是同步的,但 PyTorch CUDA 操作是异步的。
- GPU 任务提交后立即返回,计算在后台进行。
- 多个 GPU 可以同时执行各自的任务。
- 这种设计让用户可以用同步的写法(一行接一行)获得异步并行的效果。

浙公网安备 33010602011771号