to('cpu')比to('cuda')慢5–6倍,因其默认使用非pinned memory,触发gpu→临时pinned buffer→普通cpu内存的三步同步拷贝,实测带宽仅1.96 gb/s,远低于to('cuda')的12.48 gb/s。

to('cpu') 为什么比 to('cuda') 还慢?
因为 to('cpu') 默认走的是非 pinned memory 路径,触发了额外的内存分配 + 同步拷贝,实测带宽只有 1.96 GB/s,而 to('cuda') 可达 12.48 GB/s。这不是“传输慢”,是底层机制导致的低效路径被默认启用。
非 pinned memory 的三步拷贝陷阱
当你调用 tensor.cuda().to('cpu') 时,PyTorch 实际执行:
- GPU → 临时 pinned buffer(DMA 直接搬)
- 内核态 copy 到你的普通 CPU 内存(非 DMA 可达)
- 释放临时 buffer(每次调用都 malloc/free)
这三步里,第二步和第三步是瓶颈,尤其在小张量高频调用时,开销远超数据本身大小。
pin_memory=True 是必须显式打开的开关
DataLoader 默认 pin_memory=False,即使你设了 num_workers>0,CPU 端张量仍是非 pinned 的。后果就是:所有从 GPU 拷回 CPU 的操作,都自动落入低效路径。
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
正确做法:
- 构建
DataLoader时必须加pin_memory=True - 确保 CPU 端预处理(如
transforms)不破坏 pinned 状态(例如避免.numpy()或.tolist()) - 如果手动创建张量用于 CPU 推理,改用
torch.empty(..., pin_memory=True)初始化
contiguous() 和 device 切换的叠加效应
很多用户在 to('cpu') 前会先调 .contiguous()——这反而加重负担。因为 contiguous() 在 GPU 上执行后生成新张量,再 to('cpu') 就多一次拷贝;若在 CPU 上调 contiguous(),又触发 CPU 内存重排,完全抵消 pinned 带来的收益。
更稳妥的做法:
- 只在必要时调
contiguous()(比如后续要view()或进 BLAS) - 把
contiguous()放在to('cpu')之后,且确认该 CPU 张量确实需要连续布局 - 避免链式调用:
x.cuda().transpose(0,1).contiguous().to('cpu')→ 改为先to('cpu'),再按需contiguous()
真正卡顿的地方,往往不是“GPU 不够快”,而是 CPU 端那一块没锁住的内存,让 PCIe 带宽始终跑不满。 pinned memory 不是可选项,是 GPU-CPU 协同工作的基础配置。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










