根本原因是GPU→CPU默认使用非pinned内存,需经临时buffer和内核拷贝,而CPU→GPU可直接DMA;实测前者吞吐仅1.96GB/s,后者达12.48GB/s。

PyTorch 张量在 CPU 和 GPU 之间转换慢,根本原因不是 .to(device) 本身“慢”,而是你调用它的方式、时机和上下文触发了高开销路径——尤其是频繁、小批量、未预处理的传输,会把 PCIe 带宽和内存管理拖垮。
为什么 .to("cpu") 比 .to("cuda") 慢 6 倍?
这不是 PyTorch 的 bug,而是底层 CUDA 内存模型决定的:GPU → CPU 默认走非 pinned memory(普通系统内存),导致每次拷贝都要多绕两步——先搬进临时 pinned buffer,再由 CPU 内核 memcpy 到目标张量。而 CPU → GPU 可直接 DMA,带宽接近 PCIe 理论上限。
-
.to("cuda")实际调用cudaMemcpyAsync,走 DMA 通道,吞吐常达 10+ GB/s -
.to("cpu")默认不 pin 内存,触发内核态拷贝 + 临时 buffer 分配,实测常低于 2 GB/s - 这个差距在小张量(
哪些 .to(device) 调用是“隐形性能杀手”?
这些写法看着合法,但会在训练循环里反复制造同步瓶颈和冗余拷贝:
- 在每个 batch 开头把
inputs和targets单独.to(device),却不复用 pinned tensor - 前向后立刻
loss.item()或output.cpu().numpy(),强制同步并触发低效回传 - 模型输出后做可视化/日志时,对中间特征图逐层
.to("cpu"),没做 batch 合并 - 使用
Dataloader但没设pin_memory=True,导致每个 batch 的 CPU→GPU 传输无法异步化
怎么让 .to(device) 真正快起来?
关键不是少调用,而是让每次调用都落在高效路径上:
仅使用 Python 将 Claw Code 虚拟化框架与 OpenClaw 集成,提供 184 种工具和 200 多条命令,用于安全分析和代码质量...
立即学习“Python免费学习笔记(深入)”;
- 给
Dataloader加pin_memory=True,这是开启异步传输的前提 - CPU 端创建张量时,直接用
torch.empty(..., pin_memory=True)或后续调用.pin_memory() - GPU→CPU 传输必须加
non_blocking=True,且前面紧跟torch.cuda.synchronize()(仅首次或关键点) - 避免在训练循环内做
.cpu().numpy();改用.detach()+.to("cpu", non_blocking=True)+ 后续统一处理 - 小规模调试时,干脆禁用
.to("cpu")—— 用print(loss)替代print(loss.item()),后者强制同步
最容易被忽略的细节:设备一致性检查只在运算时触发
PyTorch 不会在 .to(device) 时校验所有张量是否同设备,而是在真正执行算子(如 torch.mm、model.forward())那一刻才报错。这意味着你可能写了几十行 .to(),却因漏掉一个 mask.to(device),导致整个 batch 在 GPU 上卡住等待 CPU 数据——任务管理器里 GPU 利用率是 0%,CPU 却在满载等同步。
这种错误不会抛异常,只会静默变慢。最稳的做法是:所有输入、label、loss weight、mask、甚至 scheduler 的 step 参数,都在进入 forward 前统一过一遍 .to(device),别信“它应该已经在 GPU 上了”。

















