推理必须调用model.eval()才能稳定,因train模式下Dropout和BatchNorm行为动态,eval模式才启用确定性逻辑,且需同步控制cuDNN、DataLoader及所有随机种子。
推理时用了 model.train(),结果必然不稳定——这不是 bug,是设计使然。
这两个层在 model.train() 下的行为是动态的:
nn.Dropout(p=0.5) 每次 forward 都会重新随机 mask 一半神经元,输出值直接缩放(乘以 1/(1-p)),相同输入得到不同结果nn.BatchNorm2d 在 train 模式下用当前 batch 的均值/方差做归一化,哪怕输入完全一样,只要 batch size 或数据顺序变,running_mean/running_var 就可能被意外更新(尤其在 eval 前没 reset)self.training 判断分支(比如条件 dropout 或动态路由),也会走训练路径model.eval() 的作用不是禁用随机,而是让特定层按确定性逻辑执行:
Dropout 层内部跳过 mask 步骤,直接返回输入(不缩放)BatchNorm 层跳过 batch 统计计算,改用已冻结的 running_mean 和 running_var
.eval()
注意:torch.no_grad() 只停梯度,不改层行为——它和 model.eval() 是正交的,必须同时用。
立即学习“Python免费学习笔记(深入)”;
本地 notebook 测试常因反复运行 cell 无意中触发了 model.eval(),但真实服务里容易遗漏:
model.eval(),每次请求都走 train 模式model.eval(),导致 BatchNorm 的 running_mean 被固化为零向量(尤其 TensorRT 部署时直接崩溃)模型层只是随机性的一环,其他链路也得同步控制:
torch.backends.cudnn.deterministic = True + torch.backends.cudnn.benchmark = False
shuffle=False、num_workers=0,或提供 worker_init_fn 固定各 worker 种子torch.manual_seed(42)、numpy.random.seed(42)、random.seed(42) 缺一不可真正稳定的推理,是模型模式、计算后端、数据流、随机源四者同时锁死——少一个,model.eval() 就只是个良好开端,不是终点。