☰
PyTorch torch.load的weights_only警告:pickle安全与模型加载迁移
2026/10/14 7:03:27 网站建设 项目流程

1. 从升级后的第一屏警告说起

如果你负责的项目最近从 PyTorch 1.x 升到了 2.x,或者只是日常同步了一下依赖版本,大概率会在控制台里看到这样一类警告:

FutureWarning: You are using `torch.load` with `weights_only=False` (we currently default to False), which uses the default pickle module implicitely. This can lead to dangerous code execution in untrusted environments. In a future PyTorch release, the default value for `weights_only` will be flipped to True.

我群里已经不止一个人问过:不是刚升完版本吗,模型加载怎么就开始“FutureWarning”了?这个警告是要我做什么?不处理会不会出事?最怕的就是那种“警告刷屏但代码照常跑”的状态,拖到新版真把默认值改成 True 再集体翻车。

先说结论:这不是普通的警告,是 PyTorch 官方针对 pickle 反序列化安全做的一次渐进式收口。你需要做的不是忽略它,也不是无脑把weights_only=False写在每行torch.load里,而是先搞清楚这个参数到底管什么、你的模型文件里到底存了什么、以及未来默认值切换后你的代码还能不能跑。

这篇文章,我就按实际踩坑顺序来讲:警告从哪来、里面是什么机制、现在该怎么迁移、以后怎么避免。

1.1 警告内容的完整解读

我们先把这个警告拆开看。

You are using torch.load with weights_only=False这句很明确:你当前的加载行为是非 weights-only 模式。(we currently default to False)说的是 PyTorch 2.x 现阶段仍以 False 为默认值。真正需要留意的是后半句:In a future PyTorch release, the default value for weights_only will be flipped to True,意思是未来某个版本会直接把默认值换成 True。

这句话之所以用 FutureWarning 而不用 DeprecationWarning,是因为官方知道改默认值会破坏一批老脚本,所以提前 N 个版本开始提醒,让开发者有时间主动适配。换句话说,现在就动手,等大版本切换那天你就不用熬夜改代码了。

1.2 为什么整个社区都在讨论这个参数

简单回顾一下时间线:PyTorch 2.0 引入了weights_only参数,但默认保持 False,照顾老代码。到 2.4 前后,部分调用路径的默认值开始悄悄变成 True。再到 2.6 这个版本,警告信息开始高频出现在各种训练、推理脚本里,社区讨论也密集起来。

从工程角度看,这就是一次“默认值翻转”(default flip)。风险最高的不是改代码,而是大量历史 checkpoint 文件还躺在各处的磁盘上,里面存的可能不只是张量,还有各种自定义类。你永远不知道老代码在torch.save时往文件里塞了什么。

1.3 先搞清楚你的代码在哪里触发了加载

排查这类问题,第一步永远不是改代码,而是定位触发点。把torch.load的调用位置都找出来,常见的有这么几类:

  • 模型训练中的断点续训,加载model.state_dict()
  • 模型推理前的权重读取
  • 微调脚本里加载预训练权重
  • 评估脚本里读取历史实验结果(很多项目爱把指标、配置、模型捆在一起存)

每一处都要单独判断。我后面会给出一个判断算法,这里先埋个伏笔:加载方式取决于“你保存的是什么”,而不是“你想加载什么”。

2. 为什么 torch.load 要动 weights_only 这根弦

要真正理解这个参数的意义,得先聊聊 PyTorch 背后的 pickle 机制,这是整个事件的核心。

2.1 问题根源:pickle 模块的“开箱即拆”

torch.load底层依赖 Python 标准库的 pickle 做反序列化。pickle 是什么?你可以把它理解成一套“把 Python 对象变成字节流,再从字节流变回对象”的协议。它在本地开发时非常方便——任何对象都能序列化,读回来几乎无损。问题就出在“任何对象”四个字上。

pickle 在设计时就允许在反序列化过程中执行任意的函数调用。举个例子,如果你加载一个 pickled 文件,这个文件里包含了一段恶意的字节码,它可以在你torch.load的那一瞬间执行系统命令。不是“可能执行”,是协议层面就支持这么做。

我用一个生活化的类比:pickle 的加载过程就好比收到一个快递,你打开箱子的一瞬间,箱子里的弹簧玩偶会直接弹出来打你脸。你没法提前知道里面是书还是弹簧玩偶,因为封装协议允许发送方在拆箱时触发动作。weights_only=True就是给拆箱加了一道安检,只允许特定类型的“物品”通过。

2.2 weights_only=True 到底限制了什么

weights_only=True模式下,PyTorch 会启用一个受限的反序列化入口(内部对应_weights_only_unpickler之类的机制),只允许重建白名单内的数据类型。大致包括这些:

类型示例是否常用
基础张量torch.Tensor、torch.nn.Parameter是,权重绝对在里面
基础 Python 类型str、int、float、bool、complex是,配置项常用
容器类型list、dict、tuple、set是
常用映射collections.OrderedDict、defaultdict是,state_dict本身就是有序字典
PyTorch 尺寸torch.Size是,模型结构信息
部分张量相关对象torch.dtype、torch.device等是

反过来说,如果 checkpoint 里存了自定义的类实例、某个外部库的复杂对象、或者包含代码引用关系的对象,weights_only=True直接加载就会失败。

2.3 版本演进:为什么官方选择“渐进式翻转”

你可能会问:既然torch.load加载不可信文件这么危险,为什么不一个版本直接改掉默认值?答案是工程现实。

PyTorch 生态里存在海量历史 checkpoint。很多模型库、实验脚本、在线服务都是直接torch.save就完事,有些甚至把预处理函数、回调对象一起存进去。强行一刀切会让大量脚本无法运行,社区会炸锅。

所以官方走了渐进式策略:

  1. 2.0 引入weights_only参数让大家先认识它
  2. 2.x 中间版本在部分路径上把默认值改为 True 试水
  3. 现在用 FutureWarning 通知所有开发者
  4. 未来版本彻底翻转默认值

这种策略其实值得学习,它给足了你迁移窗口期。我们作为使用者,应该在这个窗口期内把代码全部适配好,而不是等到最后一天。

3. weights_only 参数拆解与实际加载判断

接下来的内容是这篇文章的核心操作部分:到底怎么判断你的模型文件能不能用weights_only=True加载,以及各种场景下应该选哪个参数值。

3.1 参数行为逐项拆解

torch.load的完整签名很长,关键参数就那几个,我逐个说明:

torch.load(f, map_location=None, pickel_module=None, *, weights_only=None, mmap=None)
  • f:文件路径,也可以是文件对象
  • map_location:张量加载到哪个设备,比如map_location='cpu'
  • weights_only:核心开关。None表示沿用默认策略;True表示受限安全加载;False表示兼容旧行为,走完整 pickle
  • mmap:是否用内存映射方式加载,大模型场景常用

重点说一下weights_only=None和默认值的关系。在新版本里,如果你什么都不传,官方会根据调用方式、未来默认策略做处理。但我的建议是,显式写出这个参数,让代码行为清晰可读。

3.2 五分钟判断你的 checkpoint 里到底存了什么

判断方法很简单:先拿weights_only=True试加载,看它报什么错。

import torch try: state_dict = torch.load("checkpoint.pt", map_location="cpu", weights_only=True) print("加载成功,文件内容是安全的张量/基础类型") except Exception as e: print("加载失败,请查看异常信息判断具体原因") print(type(e).__name__, e)

如果成功,恭喜,你的模型权重文件是干净状态。以后可以放心用weights_only=True。

如果失败,异常信息通常会告诉你具体是哪个类型不能被重建。比如:

UnpicklingError: Weights only load failed, can not reconstruct module: MyCustomClass

看到这类信息后,你就知道文件里混入了自定义类或者非常规对象。这时候有两个选择:修改保存端、或者接受weights_only=False。

3.3 state_dict 和完整 model 要分开处理

很多人问:我加载的是model.state_dict(),为什么weights_only=True还是失败?

答案很可能出在保存端。可能有这么几种情况:

  • 你在保存时用了torch.save(model, ...),整个模型对象都进去了
  • 你在 state_dict 里存了额外信息,比如torch.save({'epoch': 10, 'model': model.state_dict()}, ...)
  • 某个自定义模块的前向处理在重建时需要外部引用

如果只是纯 state_dict,weights_only=True就是为它设计的,闭眼用。如果是完整模型或混合信息,那就要做一次拆分或改造。

4. 实操迁移方案:从压制警告到安全的加载方式

下面从最“脏”的方案到最“净”的方案依次讲,你可以按需选择。

4.1 方案一:临时屏蔽警告(只适合急救)

如果你的代码一时改不动,又不想看着警告心烦,可以先在调用处临时忽略:

import warnings import torch with warnings.catch_warnings(): warnings.simplefilter("ignore", FutureWarning) state_dict = torch.load("checkpoint.pt", map_location="cpu")

这样写会让代码安静下来,但它只是治标。你等于提前把隐患埋在了未来某个版本的升级里。别让这种代码上线,线上环境一旦因为默认值翻转而挂掉,排查成本远超你现在的改造成本。

4.2 方案二:显式声明 weights_only=False

警告的结尾其实给了你两个选择,其中一个就是“如果你确实需要非安全加载,显式传 False 可消除此警告”。

state_dict = torch.load("checkpoint.pt", map_location="cpu", weights_only=False)

这个写法的价值在于:你把“我知道这个文件可能包含非白名单对象,且我接受风险”这句话写进了代码。代码审查时能一眼看出安全性考量。

但要注意,它不代表你被豁免了。如果文件来源不可信,比如从网上下载、从同事的网盘链接获取、或者经过第三方中转,还是应该尽量避免weights_only=False。

4.3 方案三:适配 weights_only=True

再看核心方案。尽可能让你的加载路径走安全模式。具体分两步做。

第一步,检查保存端代码。把torch.save(model, ...)改成torch.save(model.state_dict(), ...),这是最彻底的改造。同时,避免在保存的 dict 里塞自定义类对象。如果一定要保存额外信息,尽量用基础类型表达:

torch.save({ "epoch": epoch, "best_acc": best_acc, "model_state_dict": model.state_dict(), "optimizer_state_dict": optimizer.state_dict(), }, "checkpoint.pt")

第二步,在加载端统一使用安全模式:

checkpoint = torch.load("checkpoint.pt", map_location="cpu", weights_only=True) model.load_state_dict(checkpoint["model_state_dict"])

这样改完,警告自然消失,行为也符合未来默认值。如果你的老 checkpoint 确实包含非白名单类型,也可以考虑用“白名单扩展机制”处理,新版 PyTorch 的序列化接口里提供了相关的注册能力,不过我对这块的建议是多用多踩后再用,不要一上来就开放。

4.4 方案四:换一种序列化格式

如果你的项目还在早期设计阶段,或者你可以完全控制保存与加载两端,更推荐直接把权重用社区最近非常流行的 safetensors 格式存储。这个格式天然不支持 pickle 式的任意代码执行,加载安全性高不少,而且对大文件做了内存映射优化。

示例:

from safetensors.torch import save_file, load_file # 保存 save_file(model.state_dict(), "model.safetensors") # 加载 state_dict = load_file("model.safetensors")

safetensors 有一个限制:它主要面向纯张量存储,非张量元数据需要自己做旁路管理。如果你要在 checkpoint 里保存 epoch、超参数之类,可以额外写一个 JSON 或者在另一个文件里存。这样做的代价是“单文件一体保存”的便利性会丢一点,但换来的是加载阶段的确定性安全性,我认为很值。

4.5 不同业务场景的最终建议

场景建议
加载自己训练的纯 state_dictweights_only=True,直接安全加载
加载老项目里的混合 checkpoint先拆解内容,再决定是否改写保存端
从网上下载的预训练权重优先 safetensors 格式;若是.pt先试weights_only=True
服务内部流转的文件显式指定weights_only=False并做好来源管理
断点续训且含优化器状态优化器状态通常包含自定义类,评估后选 True 或 False

5. 踩坑实录与排查速查表

说几个我实际遇到过的案例,这些坑你可能迟早也会踩。

5.1 三个实际踩过的坑

坑一:优化器状态引起的失败。我早期写训练脚本,习惯把优化器状态整个塞进 checkpoint。后来切到weights_only=True加载,直接报 UnpicklingError。排查半天才发现,优化器内部维护了一些自定义状态对象。解决方式不是丢掉优化器状态,而是要在保存端单独处理,或者干脆用 False 但确保文件可信。

坑二:老模型里存了预处理类。有的项目会在保存时把某些回调对象也塞进去,比如学习率调度器。这在单机实验里很常见,但对加载端是噩梦。因为这类对象在重建时依赖具体环境,换个环境就可能无法加载。建议以后保存 checkpoint 只保留训练状态相关的最少信息,不要“能塞就塞”。

坑三:加载端完全没有这个参数。代码是从老仓库里抄来的,升级依赖后出现警告,但仓库代码里没有weights_only这个参数。原因是这份代码是在 PyTorch 2.0 之前写的,当时torch.load只有两个参数。处理方法就是手动补齐参数。

5.2 排查加载失败的标准流程

如果你改完weights_only=True后加载失败,不要慌,按这个顺序排查:

  1. 看异常类型:UnpicklingError是无安全模式下的典型
  2. 看异常信息里提到的类名,它能直接告诉你是什么对象不兼容
  3. 回到保存端代码,搜索torch.save语句,看它到底存了什么
  4. 如果保存内容包含额外对象,评估这些对象是否有必要存
  5. 必要时写个小脚本分析 checkpoint 内容,再决定用哪种加载方式

5.3 速查表

报错或警告原因处理方法
FutureWarning 提到 weights_only未显式指定参数按文件内容显式传 True 或 False
UnpicklingError: Weights only load failed文件里包含非白名单对象改用 False 或改造保存端
ModuleNotFoundError涉及自定义类且环境不完整检查环境、类定义是否存在
加载成功但 strict 报错state_dict 键不完全匹配检查网络结构定义,或调整加载逻辑
服务重启后加载变慢mmap 未开启大模型场景可开 mmap

6. 我现在的做法与一个额外的判断建议

最后分享下最近形成的一套工作习惯,以及我认为比较稳妥的做法。

6.1 保存端约定

现在的约定是:除非是快速调试,否则不直接torch.save整个模型对象。统一用save_model这类工具函数,内部固定只存状态字典和少量基础类型字段:

def save_checkpoint(path, model, optimizer=None, epoch=None, **extra): payload = {"model": model.state_dict()} if optimizer is not None: payload["optimizer"] = optimizer.state_dict() if epoch is not None: payload["epoch"] = int(epoch) payload.update(extra) torch.save(payload, path)

这能保证后续加载时大概率可以走weights_only=True。

6.2 加载端入口函数

加载端也统一走一个入口:

def load_checkpoint(path, map_location="cpu", weights_only=True): return torch.load(path, map_location=map_location, weights_only=weights_only)

这样以后官方翻转默认值,我只要改一个地方。

6.3 最后的小建议

我个人的体感是:不要小看 FutureWarning。它不是那种“提示你用了旧 API”的普通建议,而是一个“未来行为即将改变”的明确信号。趁现在还来得及兼容,把项目里的torch.load全部过一遍,显式写明weights_only,顺便整理一下 checkpoint 的保存内容。等真正升级那天,你会发现自己是整个团队里最稳的那个。

最后补一个小技巧:如果你有大量历史老 checkpoint 要批量迁移,可以写个脚本把每个文件都用weights_only=True试加载一遍,凡是成功的文件,后续脚本里直接强制安全模式;凡是失败的,单独标记出来,集中处理。这个做法能让你在一天之内摸清整个项目家底,比等到线上报错再查要舒服得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询