我第一次装好 PyTorch 的时候信心满满——毕竟照着官网 pip install torch 一行命令就搞定了。但真正开始写训练代码之后才发现,会装和会用之间差了整整一周的踩坑。

坑一:Tensor 和 NumPy 互转的 device 地狱

这是我第一个报错,也是最困惑的一个。代码逻辑很简单:从 NumPy 加载数据,转成 Tensor,送进 GPU 训练。结果一到 torch.from_numpy() 就报 TypeError: expected CPU tensor。查了半天才明白:NumPy 数组永远在 CPU 上,转成 Tensor 后默认也在 CPU。如果你之前把模型 .to('cuda') 了,直接拿 CPU tensor 去算就会报 device mismatch。正确做法是 torch.from_numpy(arr).to(device),或者干脆在 DataLoader 的 collate_fn 里统一处理——我后来踩了三四次才养成这个习惯。

坑二:Windows 下 DataLoader 的 num_workers

Tutorial 里写着 num_workers=4 能加速数据加载,我就照抄了。结果 Windows 上报了一串 RuntimeError: DataLoader worker exited unexpectedly,完全看不懂。搜了 GitHub Issues 才知道:Windows 没有 fork(),多进程数据加载需要用 if __name__ == '__main__' 包裹整个训练脚本,否则子进程会递归创建新进程。最简单的方案是设 num_workers=0,虽然慢一点,但至少不会崩。这个坑浪费了我整整一个下午。

坑三:autograd 的 retain_graph

我想在一个 batch 上算两次 loss——一次分类 loss,一次重构 loss——然后分别 backward。结果第二次 backward 时报 Trying to backward through the graph a second time。原来 PyTorch 默认 backward 后释放计算图,想多次反向传播必须设 retain_graph=True。但这也意味着显存不释放,batch 大了容易 OOM。后来学会了用 loss = loss1 + loss2; loss.backward() 合并,比 retain_graph 优雅得多。

坑四:model.train() vs model.eval() 的诡异 bug

训练 loss 一路下降,accuracy 也在涨,一切看起来完美——直到我开始做验证集评估。每次 eval 的准确率都比训练时低一截,而且波动巨大。排查了两天,突然意识到:忘记切 model.eval() 了。BatchNorm 在 train 模式下用的是当前 batch 的统计量,eval 时应该用全局 running mean/var。忘记切换导致的后果不是报错,而是悄无声息的性能下降——这种 bug 最难找。

坑五:CUDA out of memory 排查

batch_size 设了 128,跑了两个 epoch 就 OOM。第一反应是减小 batch_size,但改成 32 还是爆。后来用 nvidia-smi 发现显存占用一直在涨——原来我在循环里不停创建新 tensor 但没有 detach,计算图越积越大。解决方法是每个 batch 结束时 loss.item() 取标量值、中间结果加 .detach(),必要时清缓存 torch.cuda.empty_cache()

回头看,这五个坑每个都花了不少时间,但踩过之后对 PyTorch 的底层机制理解深了很多。框架不是黑箱——报错信息虽然吓人,但仔细读总能找到线索。

--- 约 700 字