DEBUG.WIKI
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国

PyTorch 调了 model.eval(),推理为什么仍占大量显存

eval 控制部分层的训练行为,no_grad 和 inference_mode 控制求导记录。说明纯推理、后续训练和输出留存的不同边界。

模型加载后已经调用 model.eval(),但把验证数据整批送进去仍报 CUDA out of memory。缩小输入能运行,显存却比预期高。这时应检查前向计算是否仍在记录自动求导,而不是再次调用 eval()。

以下按 PyTorch 2.8 说明普通 nn.Module 的推理。模型、输入设备与数据类型应已匹配;自动设备映射、分布式推理和框架托管的评估循环还需查看各自的调用边界。

eval 不会关闭自动求导

model.eval() 相当于 model.train(False),通知模块切换到评估行为。典型变化包括 Dropout 停止随机丢弃,以及使用运行统计量的 BatchNorm 改变统计方式;具体效果取决于模块实现与配置。

这个开关与梯度模式独立。即使输入本身不需要梯度,只要参与运算的参数需要梯度,默认模式下的前向仍可以建立计算图,并保存反向计算可能用到的中间量。没有调用 backward() 只说明还没有计算梯度,不代表从未记录求导所需的信息。

下面的片段用来观察实际输出。model 和 inputs 表示已经加载、可以运行的对象:

model.eval()
output = model(inputs)
print(output.requires_grad)
print(output.grad_fn)

不要要求所有业务输出一律有 grad_fn;冻结参数、不可微操作、输出容器或模型自带的装饰器都会影响结果。对容器输出,应检查实际需要分析的 Tensor。

普通验证同时设置层模式和梯度范围

若这段前向只用于验证结果,不参与反向传播,可以写成:

model.eval()
with torch.no_grad():
    output = model(inputs)

no_grad() 控制块内的新运算,不会把进入这个块之前已经构建的图全部释放。它也不会让已经保存的 GPU Tensor 消失。若验证后继续训练,应在训练前恢复 model.train();退出 no_grad() 会恢复原梯度模式,但不会自动切回模块训练模式。

完全独立的推理还可以采用:

model.eval()
with torch.inference_mode():
    output = model(inputs)

inference_mode() 除不记录反传图外,还省去更多自动求导开销,但其中新建的张量不能随意进入后续需要自动求导的计算。若特征还要交给可训练的后半段模型,优先按设计使用 no_grad(),并确认冻结部分与可训练部分的梯度边界;不要把 inference_mode() 当成所有场景下可互换的缩写。

评估输入梯度、显著性图、对抗样本等任务仍需要求导。它们可以使用 eval() 保持层行为稳定,同时保留所需的梯度记录,不能套用纯预测的禁梯度方案。

禁梯度后仍然可能超出显存

no_grad() 不会缩小模型参数、输入或输出。把整个验证集一次送入模型,注意力中间结果或大输出仍可能超出容量。将数据按可接受的批次处理,并及时消费结果;不要无限追加 GPU 输出到历史列表。

以下是只保留 CPU 预测的局部示例,loader 已按正确设备组织输入,model(inputs) 返回 Tensor:

model.eval()
with torch.no_grad():
    for inputs in loader:
        prediction = model(inputs)
        consume_cpu_prediction(prediction.cpu())

consume_cpu_prediction 是项目已有的结果处理函数。CPU 输出也要有容量和生命周期约束;把完整数据集都收进一个列表,会把容量问题转到主内存。需要长期独立快照时还要考虑复制与共享存储。

若进入评估前仍保存着训练损失、激活或旧优化器状态,应先结束这些对象不再需要的生命周期。这里的修正解决“前向本不需要求导却在记录”的问题;用于统计的张量累加则需要另外检查统计对象跨批次的留存。

对照同样输入和批次大小,确认实际推理范围内不再建立预期外的图、输出数值与验证定义一致,再观察固定阶段的显存。占用下降不是模型正确性的证明,错误的层模式或漏掉一部分样本同样可能让内存变小。