• 文档 >
  • 模块代码 >
  • torch >
  • torch 序列化
快捷键

torch.serialization 的源代码

# mypy: 允许未类型化定义
导入 copyreg
导入 difflib
导入 functools
导入 输入/输出
导入 操作系统
导入 酸菜
导入 正则表达式
导入 shutil
导入 结构
导入 系统
导入 tar 文件
导入 tempfile
导入 线程
导入 警告
来自 contextlib 导入 关闭, contextmanager
来自 枚举 导入 枚举
来自 打字 导入 任何, 可调用, 角色, 通用, IO, 可选, 类型变量, 联合
来自 typing_extensions 导入 类型别名, 类型是

导入 火炬
导入 torch._weights_only_unpickler as _weights_only_unpickler
来自 torch._源 导入 获取源代码行和文件
来自 torch._utils 导入 _导入点分名称
来自 torch.storage 导入 _从 pickle 存储类型获取数据类型
来自 torch 的类型 导入 文件类, 存储


全部 = [
    源更改警告,
    mkdtemp,
    注册包,
    "检查模块版本是否大于等于",
    "验证 CUDA 设备",
    "验证 HPU 设备",
    "位置标签",
    "默认恢复位置",
    "标准化存储类型",
    "存储到张量类型",
    保存,
    加载,
    "存储类型",
    "加载字节序",
    "获取 crc32 选项",
    "设置 crc32 选项",
    "获取默认加载字节序",
    设置默认加载字节序,
    获取默认的 mmap 选项,
    设置默认的 mmap 选项,
    清除安全全局变量,
    "获取安全全局变量",
    "添加安全全局变量",
    "安全全局变量",
    "在检查点中获取不安全全局变量",
    跳过数据,
]

默认协议 = 2

长度 = 结构体.结构(“=l”).大小
INT_SIZE = 结构体.结构(“=i”).大小
短尺寸 = 结构体.结构("=h").大小

魔法数字 = 1.195470371460388e+23
协议版本 = 1001
存储键分隔符 = 逗号

地图位置: 类型别名 = 可选[
    联合[可调用[[存储, str] 存储] PyTorch.设备, str, 字典[str, str]]
]
存储: 类型别名 = 联合[存储, PyTorch.存储.类型化存储, PyTorch.未类型化存储]

是否为 Windows = 系统模块.平台 == win32

不安全信息 = (
    在 PyTorch 2.6 版本中,我们更改了`torch.load`函数中`weights_only`参数的默认值
    从`False`更改为`True`。将`weights_only`设置为`False`重新运行`torch.load`可能会成功
    但可能导致任意代码执行。只有当你从
    "可信来源。"
)

如果  IS_WINDOWS:
    来自 mmap 导入 MAP_PRIVATE, MAP_SHARED
否则:
    MAP_SHARED, MAP_PRIVATE = ,   # 类型:忽略[赋值]


def _default_to_weights_only(pickle 模块):
    is_fbcode =  有属性(PyTorch.版本, git 版本)
    返回 pickle 模块     is_fbcode


# _serialization_tls 用于存储特定于序列化的线程局部状态
# 需要传播到其他文件中,特别是我们用它来
# (1) map_location(对于包装子类/第三方设备到 torch._utils 所需)
# (2) 跳过数据(用于 torch.Tensor.__reduce_ex__的 skip_data 上下文)
# (3) 材料化伪造张量(用于 torch.Tensor.__reduce_ex__的 skip_data 上下文)
 _序列化本地(线程.local):
    def __init__(self):
        超级().__init__()
        self.地图位置: 可选[地图位置] = 
        self.跳过数据: 布尔值 = 
        self.材料化伪造张量: 布尔值 = 


_序列化 TLS = _序列化本地()


 源变更警告(警告):
    通过


@contextmanager
def mkdtemp():
    路径 = 临时文件.mkdtemp()
    尝试:
        产生 路径
    最后:
        shutil.rmtree(路径)


软件包注册库: 列表[
    元组[
        int,
        可调用[[存储] 可选[str]],
        可调用[[存储, str] 可选[存储]],
    ]
] = 输入文本为空,请提供需要翻译的文本


 载入端序(枚举):
    原生 = 1
     = 2
     = 3


[文档]def get_default_load_endianness() -> Optional[LoadEndianness]: """ 获取用于加载文件的回退字节序 如果保存的检查点中没有字节序标记, 则使用此字节序作为回退。 默认情况下,它是“本地”字节序。 返回值: default_load_endian: 可选[LoadEndianness] """ 从 torch.utils.serialization 导入 config return config.load.endianness
[文档]def set_default_load_endianness(endianness): """ 设置加载文件的默认字节序 如果保存的检查点中没有字节序标记, 则使用此字节序作为后备。 默认情况下,它是“本地”字节序。 参数: 字节序:新的回退字节顺序 """ 如果 endianess 不是 LoadEndianness 类型且 endianess 不是 None: 抛出 TypeError 异常:“在函数 set_default_load_endianness 中无效的参数类型” 从 torch.utils.serialization 导入 config config.load.endianness = endianness
[文档]定义 get_crc32_options() -> bool: """ 获取是否 :func:`torch.save` 对每个记录计算并写入 crc32。 默认为 ``True``。 """ 从 torch.utils.serialization 导入 config return config.save.compute_crc32
[文档]def set_crc32_options(compute_crc32: bool): """ 设置是否为每个记录计算并写入 crc32。 .. 注意:: 将此设置为 ``False`` 可能会使解压缩 ``torch.save`` 输出失败 由于 CRC32 损坏而失败或警告。然而 ``torch.load`` 将会 能够加载文件。 参数: compute_crc32 (布尔值): 设置 crc32 计算标志 """ 从 torch.utils.serialization 导入 config config.save.compute_crc32 = compute_crc32
[文档]def get_default_mmap_options() -> Optional[int]: """ 获取用于 :func:`torch.load` 且 ``mmap=True`` 的默认 mmap 选项。 默认为 ``mmap.MAP_PRIVATE``。 返回值: 默认的 mmap 选项:整型 """ 从 torch.utils.serialization 导入 config return config.load.mmap_flags
def _get_storage_alignment() -> int: "" 获取 torch.save 文件中存储的对齐方式 默认为 64。 返回值: storage_alginment: 整数 """ 来自
torch.utils.serialization 导入 配置 返回 配置.保存.存储对齐
[文档]类 set_default_mmap_options: """ 上下文管理器或函数,用于为 :func:`torch.load` 设置默认的 mmap 选项,当 ``mmap=True`` 时设置标志。 目前仅支持 ``mmap.MAP_PRIVATE`` 或 ``mmap.MAP_SHARED``。 如果需要添加其他选项,请提交一个 issue。 .. 注意:: 当前该功能不支持在 Windows 上使用。 参数: 标志:`mmap.MAP_PRIVATE` 或 `mmap.MAP_SHARED` """ def __init__(self, flags: int) -> None: if IS_WINDOWS: raise RuntimeError( "Changing the default mmap options is currently not supported for Windows" ) 如果 flags 不等于 MAP_PRIVATE 且不等于 MAP_SHARED: raise ValueError( "在函数 set_default_mmap_options 中无效的参数," 期望 mmap.MAP_PRIVATE 或 mmap.MAP_SHARED,但得到了 {flags} ) # 全局配置 从 torch.utils.serialization 导入 config self.prev = config.load.mmap_flags config.load.mmap_flags = flags def __enter__(self) -> None: pass def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: from torch.utils.serialization import config config.load.mmap_flags = self.prev
[文档]def clear_safe_globals() -> None: "" 清除适用于 `weights_only` 加载的全局变量列表。 "" _weights_only_unpickler._clear_safe_globals()
[文档]def get_safe_globals() -> list[Union[Callable, tuple[Callable, str]]]: """ 返回用户添加的安全全局变量列表,这些变量适用于 ``weights_only`` 加载。 """ 返回 _weights_only_unpickler._get_safe_globals()
[文档]def add_safe_globals(safe_globals: list[Union[Callable, tuple[Callable, str]]]) -> None: """ 将给定的全局变量标记为对 ``weights_only`` 加载安全。例如,函数 在此列表中添加的内容可以在反序列化时调用,类可以被实例化并设置状态。 并且可以拥有状态。 列表中的每个项目可以是函数/类,或者是一个形式为(函数/类,字符串)的元组。 其中字符串是该函数/类的完整路径。 在序列化格式中,每个函数都通过其完整路径进行标识,路径格式为 ``{__module__}.{__qualname__}``。调用此 API 时,您可以提供与检查点中匹配的完整路径。 当调用此 API 时,您可以提供与检查点中匹配的完整路径。如果不匹配,将使用默认的 ``{fn.__module__}.{fn.__qualname__}``。 如果提供的路径不匹配,将使用默认的 ``{fn.__module__}.{fn.__qualname__}``。 如果提供的路径不匹配,将使用默认的 ``{fn.__module__}.{fn.__qualname__}``。 Args: 安全全局变量(List[Union[Callable, Tuple[Callable, str]]]):标记为安全的全局变量列表 示例: >>> # xdoctest: +SKIP("无法将 t 保存为 torch.save(t, ...),因为 doctest 认为 MyTensor 在 torch.serialization 中已定义") >>> 导入临时文件 >>> class MyTensor(torch.Tensor): ... >>> t = MyTensor(torch.randn(2, 3)) >>> 使用 tempfile.NamedTemporaryFile() 创建一个临时文件对象 f: ... torch.save(t, f.name) # 使用 `torch.load(f.name, weights_only=True)` 将会失败,因为 # 不支持的全球变量:默认情况下,不允许使用 GLOBAL __main__.MyTensor。 检查代码以确保从任意检查点加载时 MyTensor 是安全的。 ... torch.serialization.add_safe_globals([MyTensor]) ... torch.load(f.name, weights_only=True) # MyTensor([[-0.5024, -1.8152, -0.5455], # [-0.8234, 2.0500, -0.3657]]) """ _weights_only_unpickler._add_safe_globals(safe_globals)
[文档]类 safe_globals(_weights_only_unpickler._safe_globals): r"""上下文管理器,将某些全局变量添加为 ``weights_only`` 加载的安全变量。 Args: safe_globals: 用于 weights_only 加载的全局变量列表。 示例: >>> # xdoctest: +SKIP("无法保存 t 到 torch.save(...),因为 doctest 认为 MyTensor 是在 torch.serialization 中定义的") >>> import tempfile >>> class MyTensor(torch.Tensor): ... >>> t = MyTensor(torch.randn(2, 3)) >>> with tempfile.NamedTemporaryFile() as f: ... torch.save(t, f.name) # Running `torch.load(f.name, weights_only=True)` will fail with # 不支持的全球变量:默认情况下,__main__.MyTensor 不是一个允许的全局变量。 # 请检查代码,确保从任意检查点加载时 MyTensor 是安全的。 # ... 使用 torch.serialization.safe_globals([MyTensor]): # ... torch.load(f.name, weights_only=True) # MyTensor([[-0.5024, -1.8152, -0.5455], # [-0.8234, 2.0500, -0.3657]]) >>> 断言 torch.serialization.get_safe_globals() == [] """
[文档]def get_unsafe_globals_in_checkpoint(f: FileLike) -> list[str]: 返回一个字符串列表,表示在 ``torch.save`` 对象中不安全的函数/类。 对于给定的函数或类 ``f``,相应的字符串形式为 ``{f.__module__}.{f.__name__}``。 这个函数将返回检查点中任何不在标记为安全的集合中的全局变量 对于 ``weights_only``(无论是通过 :func:`add_safe_globals` 还是 :class:`safe_globals` 上下文或 默认情况下由 ``torch`` 允许的)。 .. 注意:: 此函数将静态反汇编检查点中的 pickle 文件。 其含义是,在反序列化过程中动态推入栈中的任何类 将不会包含在输出中。 参数: f: 类似文件的对象或包含通过 `torch.save` 保存的检查点对象的字符串 返回: 检查点中 pickle 的全局变量字符串列表,这些变量不允许在 `weights_only` 中使用。 """ default_safe_globals_strings = set( _weights_only_unpickler._get_allowed_globals().keys() ) user_safe_global_strings = set( _weights_only_unpickler._get_user_allowed_globals().keys() ) safe_global_strings = default_safe_globals_strings.union(user_safe_global_strings) with _open_file_like(f, "rb") as opened_file: 如果不是 _is_zipfile(opened_file): 抛出 ValueError("期望输入是 torch.save 返回的检查点") with _open_zipfile_reader(opened_file) as zip_file: 如果 _is_torchscript_zip(zip_file): raise ValueError( 预期输入为 torch.save 返回的 checkpoint,但得到了一个 torchscript checkpoint ) data_file = io.BytesIO(zip_file.get_record("data.pkl")) all_globals = _weights_only_unpickler.get_globals_in_pkl(data_file) return list(all_globals.difference(safe_global_strings))
[文档]class skip_data: """ 跳过写入/读取存储字节的上下文管理器,用于`torch.save`/`torch.load`调用。 对于保存路径,存储仍然会被保存,但它们字节通常写入的空间将被跳过 将为空空间。存储字节数可以在单独的遍历中填充。 对于加载路径,张量将按照检查点加载,但它们的存储不会填充数据。 ..警告:: `skip_data`上下文管理器是一个早期原型,可能会发生变化。 Args: materialize_fake_tensors: 是否在保存期间实例化 FakeTensors。在加载路径中这是一个空操作。 Example: >>> # xdoctest: +SKIP("NamedTemporaryFile on Windows") >>> 导入临时文件模块 >>> t = torch.randn(2, 3) >>> with tempfile.NamedTemporaryFile() as f: ... with torch.serialization.skip_data(): ...torch.save(t, f.name) ...torch.load(f.name, weights_only=True) tensor([[0., 0., 0.], [0., 0., 0.]]) """ def __init__(self, 材料化伪造张量: bool = False): self.材料化伪造张量 = 材料化伪造张量 def __enter__(self): 全局_serialization_tls self._old_skip_data = _serialization_tls.skip_data self._old_materialize_fake_tensors = _serialization_tls.materialize_fake_tensors _serialization_tls.skip_data = True _serialization_tls.materialize_fake_tensors = self.materialize_fake_tensors def __exit__(self, type, value, tb): global _serialization_tls _serialization_tls.skip_data = self._old_skip_data _serialization_tls.materialize_fake_tensors = self._old_materialize_fake_tensors
def _is_zipfile 是 zip 文件(f) -> bool: 这是一种比 zipfile.is_zipfile() 更严格的实现。 如果魔数出现在任何地方,zipfile.is_zipfile() 将返回 True。 由于我们预计这里的文件是由 torch.save 或 torch.jit.save 生成的, 因此,只检查起始字节是安全的。 假设 zip 只有一个文件,并处理冲突。 请参阅 bugs.python.org/issue28494。 开始
= f.告诉() 读取前几个字节并与 ZIP 文件签名进行匹配。 本地头魔数 = bPK\x03\x04" 读取字节 = f.阅读(长度(本地头魔数)) f.搜索(开始) 返回 读取字节 == 本地头魔数
[文档]def 注册包( 优先级: int, 标记器: Callable[[STORAGE], Optional[str]], 反序列化器: Callable[[STORAGE, str], Optional[STORAGE]], ): """ 注册具有关联优先级的可调用对象进行标记和反序列化存储对象的函数。 标记在保存时将设备与存储对象关联,而反序列化则移动 在加载时将存储对象分配到合适的设备。:attr:`tagger` 和 :attr:`deserializer` 按照它们的 :attr:`priority` 顺序执行,直到 tagger/deserializer 返回一个非 `None` 的值。 要覆盖全局注册表中设备的反序列化行为,可以注册一个 在全局注册表中覆盖设备的反序列化行为,可以注册一个 标签器具有比现有标签器更高的优先级。 此功能还可以用于为新设备注册标签器和反序列化器。 Args: priority: 表示标签器和反序列化器关联的优先级,其中较低的 值表示更高的优先级。 tagger:接受存储对象作为参数并返回其标记设备字符串的可调用函数 或无。 反序列化器:接受存储对象和设备字符串的函数,并返回存储 在适当的设备上或为 None。 返回: `None` 示例: >>> def ipu_tag(obj): >>> if obj.device.type == 'ipu': >>> return 'ipu' >>> def ipu_deserialize(obj, location): >>> 如果 location.startswith('ipu'): >>> ipu = getattr(torch, "ipu", None) >>> assert ipu is not None, "IPU 设备模块未加载" >>> assert torch.ipu.is_available(), "ipu 不可用" >>> 返回 obj.ipu(location) >>> torch.serialization.register_package(11, ipu_tag, ipu_deserialize) """ queue_elem = (priority, tagger, deserializer) _package_registry.append(队列元素) _package_registry.sort()
def 检查模块版本是否大于或等于( 模块, req_version_tuple, 如果格式错误则报错=True, ): "" 检查模块的版本是否满足要求 通常,模块的版本字符串将类似于 'x.y.z',它将被表示为一个元组(x, y, z),但有时它可能是意外的格式。 如果版本字符串不符合预期格式,则可能需要特殊处理。 字符串长度与给定元组的长度不匹配时 报错并退出或发出警告。 参数: 模块:检查版本号的模块 req_version_tuple:表示所需版本的元组(通常为整数) error_if_malformed: 是否在模块版本字符串格式错误时退出 返回值: requirement_is_met: 布尔值 """ 尝试: version_strs = 模块
.__version__.分割(“。”) 将模块版本字段转换为所需版本的类型 模块版本 = 元组( 类型(req_field)(version_strs[索引]) for 索引, 请求字段 列举(req_version_tuple) ) 需求是否满足 = 模块版本 req_version_tuple 除了 异常 as e: 消息 = ( f''{模块.__name__}'模块版本字符串格式不正确'{模块.__version__}'并且无法进行比较" ' f使用元组{str(req_version_tuple)}" ) 如果 如果格式错误则报错: 抛出 运行时错误(消息) 来自 e 否则: 警告.警告(消息 + ,但假设该要求已满足而继续) 需求是否满足 = 真实 返回 需求是否满足 def _cpu 标签(对象): 如果 对象.设备.类型 == "cpu": 返回 cpu def _mps 标签(对象): 如果 对象.设备.类型 == 国会议员: 返回 mps def _元标签(对象): 如果 对象.设备.类型 == 元数据: 返回 元数据 def _后端标签(后端名称, 对象): 如果 后端名称 == privateuse1: 后端名称 = PyTorch._C._get_privateuse1_backend_name() 如果 对象.设备.类型 == 后端名称: 如果 对象.设备.索引 : 返回 后端名称 否则: 返回 后端名称 + ":" + str(对象.设备.索引) def _cpu 反序列化(对象, 位置): 如果 位置 == "cpu": 返回 对象 def _mps 反序列化(对象, 位置): 如果 位置.以...开头(国会议员): 返回 对象.会员() def _元反序列化(对象, 位置): 如果 位置 == 元数据: 返回 PyTorch.未类型化存储(对象.字节数(), 设备=元数据) def _验证设备(位置, 后端名称): "" 检查指定后端的设备索引是否有效 在使用 privateuse1 后端的情况下,您必须首先注册一个 device_module 使用 torch._register_device_module。实现以下方法 在 device_module 中定义的方法,如 cuda: device_module._utils._get_device_index(location, True), device_module.device_count()。 参数: location:设备字符串 后端名称:后端名称或私有用途 1 的名称,可重命名 返回值: 设备索引:整数 """ 如果 有属性(PyTorch, 后端名称): 抛出 运行时错误( f"The {后端名称.()}设备模块未注册。 "如果您正在使用仅 CPU 的机器," "请使用 torch.load 并指定 map_location=torch.device('cpu') " "将您的存储映射到 CPU。" ) 设备模块 = getattr(PyTorch, 后端名称) 如果 有属性(设备模块, _utils) 有属性( 设备模块._工具, "_get_device_index" ): 设备索引 = 设备模块._工具.获取设备索引(位置, True) 设备 = PyTorch.设备(后端名称, 设备索引) 否则: 设备 = PyTorch.设备(位置) 设备索引 = 设备.索引 如果 设备.索引 否则 0 如果 有属性(设备模块, "是否可用") 设备模块.是否可用(): 抛出 运行时错误( f尝试在对象上反序列化{后端名称.()} " f设备但 torch。{后端名称}.is_available()为 False。 "如果您正在使用仅 CPU 的机器," "请使用 torch.load 并指定 map_location=torch.device('cpu') " "将您的存储映射到 CPU。" ) 如果 有属性(设备模块, 设备数量): 设备数量 = 设备模块.设备数量() 如果 设备索引 设备数量: 抛出 运行时错误( f尝试反序列化对象{后端名称.()}设备 " f"{设备索引}但是 torch。{后端名称}.device_count() 是{设备数量}. 请使用 torch.load 与 map_location 将您的存储映射到 一个现有设备。 ) 返回 设备 def validate_cuda_device(位置): 返回 _验证设备(位置, cuda).索引 def 验证 HPU 设备(位置): 返回 _验证设备(位置, hpu).索引 def 反序列化(后端名称, 对象, 位置): 如果 后端名称 == privateuse1: 后端名称 = PyTorch._C._get_privateuse1_backend_name() 如果 位置.以...开头(后端名称): 设备 = _验证设备(位置, 后端名称) 返回 对象.(设备=设备) 注册包(10, _cpu 标签, _cpu 反序列化) 注册包( 20, functools.偏函数(_后端标签, cuda), functools.偏函数(反序列化, cuda), ) 注册包(21, _mps 标签, _mps 反序列化) 注册包(22, _元标签, _元反序列化) 注册包( 23, functools.偏函数(_后端标签, privateuse1), functools.偏函数(反序列化, privateuse1), ) 注册包( 24, functools.偏函数(_后端标签, hpu), functools.偏函数(反序列化, hpu), ) 注册包( 25, functools.偏函数(_后端标签, XPU), functools.偏函数(反序列化, XPU), ) def 位置标签( 存储: 联合[存储, PyTorch.存储.类型化存储, PyTorch.未类型化存储] ): for _, 标注器, _ 软件包注册库: 位置 = 标注器(存储) 如果 位置: 返回 位置 抛出 运行时错误( "不知道如何确定数据的存储位置" + PyTorch.类型名(存储) ) def 默认恢复位置(存储, 位置): "" 使用为`位置`注册的反序列化函数恢复`存储`。 此函数在注册表中查找与`location`匹配的反序列化函数。 如果找到,它将按优先级顺序尝试使用它们来恢复`storage`,直到找到一个为止 返回一个非`None`的结果。如果在注册表中找不到反序列化器,或者找到的所有反序列化器都失败 产生一个结果时,它引发一个 `RuntimeError`。 参数: 存储(STORAGE):要恢复的存储对象 位置(str):与存储对象关联的位置标签 返回值: 存储:Optional[STORAGE] 抛出异常: RuntimeError:如果在注册表中找不到匹配 `位置` 的反序列化器,或者 所有匹配项都返回 `None`。 """ for _, _, fn 软件包注册库: 结果 = 函数(存储, 位置) 如果 结果 : 返回 结果 抛出 运行时错误( "不知道如何恢复数据位置" + PyTorch.类型名(存储) + "(标记为“” + 位置 + )" ) def 标准化存储类型(存储类型): 返回 getattr(PyTorch, 存储类型.__name__) def 存储转换为张量类型(存储): 存储类型 = 类型(存储) 模块 = 导入点名称(存储类型.__模块__) 返回 getattr(模块, 存储类型.__name__.替换(存储, "张量")) def _is_path(名称或缓冲区: 对象) -> 类型是[联合[str, os.PathLike]] 返回 isinstance(名称或缓冲区, (str, os.PathLike)) T = 类型变量(T) 打开器(通用[T)] def __init__(self, 文件类: T) -> : self.文件类: T = 文件类 def __进入__(self): 返回 self.文件类 def __退出__(self, *参数): 通过 打开文件(打开器[IO[字节]]: def __init__(self, 名称: 联合[str, os.PathLike[str]], 模式: str) -> : 超级().__init__(打开(名称, 模式)) def __退出__(self, *参数): self.文件类.关闭() 打开缓冲区读取器(打开器[IO[字节]]: def __init__(self, 缓冲区: IO[字节]) -> : 超级().__init__(缓冲区) 检查可寻址(缓冲区) _open_buffer_writer(打开器[IO[字节]]: def __退出__(self, *参数): self.文件类.清空() def _open_file_like(名称或缓冲区: 文件类, 模式: str) -> 打开器[IO[字节]] 如果 _is_path(名称或缓冲区): 返回 打开文件(名称或缓冲区, 模式) 否则: 如果 "w" 模式: 返回 _open_buffer_writer(名称或缓冲区) 如果...否则 "r" 模式: 返回 打开缓冲区读取器(名称或缓冲区) 否则: 抛出 运行时错误(f预期模式中为 'r' 或 'w',但得到{模式}") _open_zipfile_reader(打开器[PyTorch._C.PyTorch 文件读取器)] def __init__(self, 名称或缓冲区: 联合[str, IO[字节]]) -> : 超级().__init__(PyTorch._C.PyTorch 文件读取器(名称或缓冲区)) 打开 zip 文件写入文件(打开器[PyTorch._C.PyTorch 文件写入器)] def __init__(self, 名称: str) -> : self.文件流 = self.名称 = 名称 尝试: self.名称.编码(ASCII) 除了 Unicode 编码错误: # PyTorch 文件写入器仅支持 ASCII 文件名。 对于包含非 ASCII 字符的文件名,我们依赖于 Python 用于写入文件。 self.文件流 = 输入/输出.文件输入输出(self.名称, 模式=w) 超级().__init__( PyTorch._C.PyTorch 文件写入器( self.文件流, 获取 crc32 选项(), _get_storage_alignment() ) ) 否则: 超级().__init__( PyTorch._C.PyTorch 文件写入器( self.名称, 获取 crc32 选项(), _get_storage_alignment() ) ) def __退出__(self, *参数) -> : self.文件类.写入文件末尾() 如果 self.文件流 : self.文件流.关闭() 打开 ZIP 文件写入缓冲区(打开器[PyTorch._C.PyTorch 文件写入器)] def __init__(self, 缓冲区: IO[字节]) -> : 如果 可调用(getattr(缓冲区, "写", )): msg = f"缓冲区"{str(类型(缓冲区)).strip()}没有可调用的属性 'write' 如果 有属性(缓冲区, "写"): 抛出 属性错误(信息) 抛出 类型错误(信息) self.缓冲区 = 缓冲区 超级().__init__( PyTorch._C.PyTorch 文件写入器( 缓冲区, 获取 crc32 选项(), _get_storage_alignment() ) ) def __退出__(self, *参数) -> : self.文件类.写入文件末尾() self.缓冲区.清空() def 打开 zip 文件写入器(名称或缓冲区: 联合[str, IO[字节]]) -> 打开器: 容器: 类型[打开器] 如果 _is_path(名称或缓冲区): 容器 = _打开 zip 文件写入文件 否则: 容器 = _打开 zip 文件写入缓冲区 返回 容器(名称或缓冲区) def _is_compressed_file(f) -> bool: 压缩模块 = ["gzip"] 尝试: 返回 f.__module__ 压缩模块 除了 属性错误: 返回 def _应直接读取(f): "" 检查 f 是否为应直接读取的文件。它应该被读取 如果它由真实文件支持(有 fileno)且不是 压缩文件(例如 gzip) """ 如果 _is_compressed_file(f): 返回 尝试: 返回 f.文件描述符() 0 除了 输入/输出.不支持的操作: 返回 除了 属性错误: 返回 def 检查可寻址(f) -> bool: def 抛出错误信息(模式, e): for p 模式: 如果 p str(e): msg = ( str(e) + ". 您只能从可寻址的文件中 torch.load。" + "请预先将数据加载到缓冲区,例如 io.BytesIO," + "然后尝试从它加载。" ) 抛出 类型(e)(信息) 抛出 e 尝试: f.搜索(f.告诉()) 返回 真实 除了 (输入/输出.不支持的操作, 属性错误) as e: 抛出错误信息[查找, 告诉] e) 返回 def _check_dill_version(pickle 模块) -> : 检查是否使用 dill 作为 pickle 模块,如果是,则检查版本是否正确。 如果 dill 版本低于 0.3.1,则抛出 ValueError 异常。 参数: pickle_module: 用于序列化元数据和对象的模块 """ 如果 pickle 模块 pickle 模块.__name__ == dill: required_dill_version = (0, 3, 1) 如果 检查模块版本是否大于或等于( pickle 模块, required_dill_version, ): 抛出 值错误( ( "'torch'支持 dill >={},但你使用的是 dill{} "请升级 dill 或切换到'pickle'" ).格式( “。”.连接[str(数字) for 数字 required_dill_version)] pickle 模块.__version__, ) ) def _检查保存文件类型(f): 如果 _is_path(f) 有属性(f, "写"): 抛出 属性错误( "期望 'f' 是字符串、路径或文件对象" "一个 'write' 属性" )
[文档]def 保存( 对象: 对象, f: 文件类, pickle 模块: 任何 = pickle, pickle 协议: 整型 = 默认协议, 使用新的 zip 文件序列化: 布尔值 = True, 禁用字节序记录: 布尔值 = False, ) -> : # 参考:https://github.com/pytorch/pytorch/issues/54354 # 该文档字符串的第一行覆盖了 Sphinx 生成的文档字符串 # 我们需要它,这样 Sphinx 就不会泄露`pickle`s 路径(例如` # 从构建环境(例如` """保存对象到磁盘文件。参数:obj - 要保存的对象,f - 文件路径,pickle_module - 用于序列化的 pickle 模块,默认为 pickle,pickle_protocol - pickle 协议版本,默认为 2,_use_new_zipfile_serialization - 是否使用新的 zip 文件序列化,默认为 True""" 将对象保存到磁盘文件。 参见::ref:`保存和加载张量` 参数: obj: 保存的对象 f: 类似文件的对象(必须实现 write 和 flush)或字符串 os.PathLike 对象,包含文件名 pickle_module: 用于序列化元数据和对象的模块 可以指定 pickle_protocol 来覆盖默认协议 .. 注意:: PyTorch 的常见约定是使用.pt 文件扩展名保存张量 .. 注意:: PyTorch 在序列化过程中保留了存储共享。详见 ref:`preserve-storage-sharing` 以获取更多详细信息。 .. 注意:: PyTorch 1.6 版本将 `torch.save` 切换到了新的 基于 zip 文件的格式。`torch.load` 仍然保留了读取该格式的功能 以旧格式加载文件。如果出于任何原因你想使用 `torch.save` 要使用旧格式,请传递关键字参数 `_use_new_zipfile_serialization=False` 示例: >>> # xdoctest: +SKIP("使当前工作目录变脏") >>> # 保存到文件 >>> x = torch.tensor([0, 1, 2, 3, 4]) >>> torch.save(x, "tensor.pt") >>> # 保存到 io.BytesIO 缓冲区 >>> 缓冲区 = io.BytesIO() >>> torch.save(x, 缓冲区) """ PyTorch._C._log_api_usage_once(torch.save) _check_dill_version(pickle 模块) _检查保存文件类型(f) 如果 isinstance(f, (str, os.PathLike)): f = os.文件系统路径(f) 如果 使用新的 zip 文件序列化: 替换为 打开 zip 文件写入器(f) as 打开 ZIP 文件: 保存( 对象, 打开 ZIP 文件, pickle 模块, pickle 协议, 禁用字节序记录, ) 返回 否则: 全局 _序列化 TLS 如果 序列化 TLS.跳过数据: 抛出 运行时错误( "不能使用 skip_data=True 与 _use_new_zipfile_serialization=False 同时" ) 替换为 _open_file_like(f, wb) as 打开的文件: _历史保存(对象, 打开的文件, pickle 模块, pickle 协议)
def _历史保存(对象, f, pickle 模块, pickle 协议) -> : 导入 torch.nn as 神经网络 序列化容器类型 = {} 序列化存储: 字典[str, 元组[PyTorch.未类型化存储, PyTorch.数据类型]] = {} 由于不支持加载具有不同数据类型的相同数据的存储,我们需要跟踪每个存储数据_ptr 关联的数据类型,如果数据类型有任何不同,则抛出错误。 # 不支持,我们需要跟踪每个存储数据_ptr 关联的数据类型,如果数据类型有任何不同,则抛出错误。 存储数据指针并在数据类型不同时抛出错误。 TODO:此功能可能在未来添加。 存储数据类型: 字典[int, PyTorch.数据类型] = {} def 持久化 ID(对象: 任何) -> 可选[元组] 文档说明说持久化 ID 应该只返回字符串 但是 torch 存储返回元组。这仅在二进制协议中有效 查看 https://docs.python.org/2/library/pickle.html#pickling-and-unpickling-external-objects # https://github.com/python/cpython/blob/master/Lib/pickle.py#L527-L537 如果 isinstance(对象, 类型) 派生类(对象, 神经网络.模块): 如果 对象 序列化容器类型: 返回 序列化容器类型[对象] = 真实 源文件 = = 尝试: 源代码行, _, 源文件 = 获取源代码行和文件(对象) = 输入文本翻译为简体中文为:"".连接(源代码行) 除了 ( 异常 ): # 保存源是可选的,因此我们可以忽略任何错误 警告.警告( "无法检索容器的源代码 " "类型 " + 对象.__name__ + "。它不会在加载时检查正确性。" "。" ) 返回 (模块, 对象, 源文件, ) 如果 isinstance(对象, PyTorch.存储.类型化存储) 或者 PyTorch.is_storage(对象): 存储: PyTorch.未类型存储 如果 isinstance(对象, PyTorch.存储.类型化存储): # TODO: 一旦我们决定打破序列化功能,这个情况 #可删除 存储 = 对象._未指定存储 存储数据类型 = 对象.dtype 存储类型字符串 = 对象._pickle 存储类型() 存储类型 = getattr(PyTorch, 存储类型字符串) dtype = 对象.dtype 存储 numel = 对象.尺寸() 如果...否则 isinstance(对象, PyTorch.未类型化存储): 存储 = 对象 存储数据类型 = PyTorch.uint8 存储类型 = 标准化存储类型(类型(对象)) dtype = PyTorch.uint8 存储 numel = 存储.字节数() 否则: 抛出 类型错误(f"类型未识别:"{类型(对象)}") 如果已分配存储,请确保指向相同数据的任何其他已保存存储都具有相同的 dtype。 如果存储指向相同数据,请确保所有这些存储都具有相同的 dtype。 # not allocated, don't perform this check 如果 存储.数据指针() != 0: 如果 存储.数据指针() 存储数据类型: 如果 存储数据类型 != 存储数据类型[存储.数据指针()]: 抛出 运行时错误( "无法保存多个张量或存储,它们" "以不同类型查看相同的数据" ) 否则: 存储数据类型[存储.数据指针()] = 存储数据类型 视图元数据: 可选[元组[str, int, int]] # 偏移量始终为 0,但我们保留它以保持向后兼容 # 使用旧序列化格式(支持存储视图) 偏移 = 0 存储键 = str(存储._cdata) 位置 = 位置标签(存储) # TODO: 这里可能存在与 FC 相关的问题。可能无法解决,但值得关注。想象一下,我们保存一个列表 `[storage, tensor]`,其中 `tensor.storage()` 与 `storage` 相同,并且 # 可以解决,但值得关注。想象一下,我们保存一个列表 `[storage, tensor]`,其中 `tensor.storage()` 与 `storage` 相同,并且 # 可以解决,但值得关注。想象一下,我们保存一个列表 `[storage, tensor]`,其中 `tensor.storage()` 与 `storage` 相同,并且 `tensor.element_size() > 1`。假设`tensor.dtype == `torch.float`。存储将以元素大小 # 1 的,因为我们选择序列化第一次出现 # 一个重复存储。因为这个遗留的序列化格式保存 # 存储的元素数量,而不是直接保存字节数,在这种情况下,我们将 # 有效地保存字节数。我们将能够加载它 # 在此和未来的版本中,将没有问题地备份张量 # 但在旧版本的 pytorch 中,这里有问题: # 存储将被加载为 UntypedStorage,然后 FloatTensor 将被加载, # 并将 UntypedStorage 分配给 由于存储数据类型与张量数据类型不匹配,这 # 将导致错误。如果我们反转列表,例如 `[tensor, # 存储]`,那么我们将保存 `tensor.storage()` 为一个假的 # `FloatStorage`,并且保存的大小将是旧版本期望的正确 # 数据类型特定的 numel 计数。`tensor` # 将能在旧版本中正确加载,指向 # 一个 FloatStorage。然而,`storage`仍然被翻译为 # 一个 UntypedStorage,并且它将尝试解析到`tensor`包含的同一 FloatStorage。这也会导致 # `tensor`包含的 FloatStorage。这也会引起 # 错误。似乎没有绕过的办法。 # 可能,如果我们不能维护旧格式的 FC,那么 # 保存的列表中既包含一个张量又包含一个指向相同数据的存储。 # 我们仍然应该能够维护列表的 FC。 # 仅张量,只要所有视图共享相同的 dtype 正在查看的 tensor 如果 存储键 序列化存储: 序列化存储[存储键] = (存储, 数据类型) 是否查看 = 存储._cdata != 存储._cdata 如果 视图: 视图元数据 = (str(存储._cdata), 偏移量, 存储.字节数()) 否则: 视图元数据 = res = ( 存储, 存储类型, 存储键, 位置, 存储数目, 视图元数据, ) 返回 res 返回 系统信息 = 字典( 协议版本=协议版本, 小端序=系统模块.字节序 == 小端, 类型大小=字典( 短整型=短尺寸, int=整数尺寸, =长尺寸, ), ) pickle 模块.导出(魔法数字, f, 协议=pickle 协议) pickle 模块.导出(协议版本, f, 协议=pickle 协议) pickle 模块.导出(系统信息, f, 协议=pickle 协议) PyTorchLegacyPickler(pickle 模块.挑选器): def 持久化 ID(self, 对象): 返回 持久化 ID(对象) 序列化工具 = PyTorchLegacyPickler(f, 协议=pickle 协议) 序列化器.导出(对象) 序列化存储键 = 排序(序列化存储.()) pickle 模块.导出(序列化存储键, f, 协议=pickle 协议) f.清空() for 序列化存储键: 存储, dtype = 序列化存储[] 存储._写入文件( f, _应直接读取(f), True, PyTorch._工具.元素大小(数据类型) ) def 保存( 对象, zip 文件, pickle 模块, pickle 协议, 禁用字节序记录, ): 序列化存储 = {} ID 映射: 字典[int, str] = {} 由于不支持加载具有不同数据类型的相同数据的存储,我们需要跟踪每个存储数据_ptr 关联的数据类型,如果数据类型有任何不同,则抛出错误。 # 不支持,我们需要跟踪每个存储数据_ptr 关联的数据类型,如果数据类型有任何不同,则抛出错误。 存储数据指针并在数据类型不同时抛出错误。 TODO:此功能可能在未来添加。 存储数据类型: 字典[int, PyTorch.数据类型] = {} def 持久化 ID(对象): 文档说明说持久化 ID 应该只返回字符串 但是 torch 存储返回元组。这仅在二进制协议中有效 查看 https://docs.python.org/2/library/pickle.html#pickling-and-unpickling-external-objects # https://github.com/python/cpython/blob/master/Lib/pickle.py#L527-L537 如果 isinstance(对象, PyTorch.存储.类型化存储) 或者 PyTorch.is_storage(对象): 如果 isinstance(对象, PyTorch.存储.类型化存储): # TODO: 一旦我们决定打破序列化功能,这个情况 #可删除 存储 = 对象._未指定存储 存储数据类型 = 对象.dtype 存储类型字符串 = 对象._pickle 存储类型() 存储类型 = getattr(PyTorch, 存储类型字符串) 存储 numel = 对象.尺寸() 否则: 存储 = 对象 存储数据类型 = PyTorch.uint8 存储类型 = 标准化存储类型(类型(对象)) 存储 numel = 存储.字节数() 如果已分配存储,请确保指向相同数据的任何其他已保存存储都具有相同的 dtype。 如果存储指向相同数据,请确保所有这些存储都具有相同的 dtype。 # not allocated, don't perform this check 如果 str(存储.设备) != 元数据 存储.数据指针() != 0: 如果 存储.数据指针() 存储数据类型: 如果 存储数据类型 != 存储数据类型[存储.数据指针()]: 抛出 运行时错误( "无法保存多个张量或存储,它们" "以不同类型查看相同的数据" ) 否则: 存储数据类型[存储.数据指针[] = 存储数据类型 存储键 = ID 映射.setdefault(存储._cdata, str(长度(ID 映射))) 如果 有属性(对象, _假设备) 对象._假设备 : 位置 = str(对象._假设备) 否则: 位置 = 位置标签(存储) 序列化存储[存储键] = 存储 返回 (存储, 存储类型, 存储键, 位置, 存储数目) 返回 # 写入`obj`的 pickle 数据 数据缓冲区 = 输入/输出.BytesIO() PyTorchPickler(pickle 模块.挑选器): 忽略名称定义 def 持久化 ID(self, 对象): 返回 持久化 ID(对象) 序列化工具 = PyTorchPickler(数据缓冲区, 协议=pickle 协议) 序列化器.导出(对象) 数据值 = 数据缓冲区.获取值() zip 文件.记录写入("data.pkl", 数据值, 长度(数据值)) # .format_version 用于跟踪 1. 版本 1 表示存储顺序变更的顺序 基于键的字典序排列,基于键的数值排序 # 2. 版本 2 表示将 storage_alignment 作为记录包含在内 在 zip 文件内 zip 文件.记录写入(".版本格式", "1", 长度("1")) 存储对齐 = str(_get_storage_alignment()) zip 文件.记录写入( ".存储对齐", 存储对齐, 长度(存储对齐) ) # 写入字节顺序标记 如果 禁用字节序记录: 如果 系统模块.字节序 [小端, "大"] 抛出 值错误("未知端序类型:" + 系统模块.字节序) zip 文件.记录写入(字节序, 系统模块.字节序, 长度(系统模块.字节序)) # 将每个张量写入名为 tensor/the_tensor_key 的文件,存入 zip 压缩包中 for 序列化存储.(): 名称 = f数据/{}" 存储 = 序列化存储[] 字节数 = 存储.字节数() 全局 _序列化 TLS 如果 序列化 TLS.跳过数据: zip 文件.记录写入元数据(名称, 字节数) 否则: # 既然我们无论如何都要复制东西,我们可能会使用 storage.cpu() 要获取序列化的张量,你需要实现 在底层存储上的 .cpu() 如果 存储.设备.类型 != "cpu": 来自 torch.utils.serialization 导入 配置 如果 ( 配置.保存.使用_pinned_memory_for_d2h ( acc := PyTorch.加速器.当前加速器( 检查可用=真实 ) ) acc.类型 == 存储.设备.类型 ): 新存储 = PyTorch.空的( 字节数, 数据类型=PyTorch.uint8, 设备="cpu", 持久化内存=真实 ).未类型化存储() 新存储.复制_(存储) PyTorch.加速器.current_stream(存储.设备.索引).同步() 存储 = 新存储 否则: 存储 = 存储.cpu() 现在它在 CPU 上,我们可以直接将其复制到 zip 文件中 zip 文件.记录写入(名称, 存储, 字节数)
[文档]def 加载( f: 文件类, 地图位置: 地图位置 = , pickle 模块: 任何 = , *, 仅权重: 可选[bool] = , 内存映射: 可选[bool] = , **pickle_load_args: 任何, ) -> 任何: # 参考:https://github.com/pytorch/pytorch/issues/54354 # 该文档字符串的第一行覆盖了 Sphinx 生成的文档字符串 # 我们需要它,这样 Sphinx 就不会泄露`pickle`s 路径(例如` # 从构建环境(例如` 加载(f, map_location=None, pickle_module=pickle, *, weights_only=True, mmap=None, **pickle_load_args) 加载使用 :func:`torch.save` 保存到文件中的对象。 `:func:`torch.load` 使用 Python 的反序列化功能,但对待存储、 底层支撑张量的内容,尤其是。它们首先被反序列化。 CPU 然后被移动到它们保存的设备上。如果这失败 (例如,因为运行时系统没有某些设备),抛出异常 异常被触发。然而,存储可以动态重新映射到替代位置 一组使用`:attr:`map_location`参数的设备。 如果 :attr:`map_location` 是一个可调用对象,它将为每个序列化的存储调用一次,带有两个参数:存储和位置。存储参数将是存储的初始反序列化,位于 CPU 上。 每个序列化的存储都有一个与其关联的位置标签。 该存储参数将是存储的初始反序列化,位于 CPU 上。 每个序列化的存储都有一个与其关联的位置标签。 识别保存设备的标识,此标签是第二个 传递给 :attr:`map_location` 的参数。内置的位置标签有 ``'cpu'`` 用于 CPU 张量,以及用于 CUDA 张量的 ``'cuda:device_id'``(例如 ``'cuda:2'``)。 attr:`map_location` 应返回 ``None`` 或一个存储。如果 attr:`map_location` 返回一个存储,它将被用作最终反序列化的对象,已移动到正确的设备。否则,:func:`torch.load` 将回退到默认行为,就像没有指定 :attr:`map_location` 一样。 如果 :attr:`map_location` 是一个 :class:`torch.device` 对象或包含字符串的字符串,则 ... 将回退到默认行为,就像没有指定 :attr:`map_location` 一样。 如果 :attr:`map_location` 是一个 :class:`torch.device` 对象或包含字符串的字符串,则 ... 设备标签,它指示所有张量应加载的位置。 否则,如果 :attr:`map_location` 是一个字典,它将被用来重新映射位置标签 出现在文件(键)中,到指定放置位置的 存储(值)。 用户扩展可以注册自己的位置标签和标记 使用 :func:`torch.serialization.register_package` 的反序列化方法。 参数: f: 一个类似文件的对象(必须实现 :meth:`read`、:meth:`readline`、:meth:`tell` 和 :meth:`seek` 方法), 或者包含文件名的字符串或 os.PathLike 对象 map_location: 一个函数、:class:`torch.device`、字符串或指定存储重映射的字典 位置 pickle_module:用于反序列化元数据和对象的模块(必须与用于序列化文件的 :attr:`pickle_module` 匹配) weights_only:表示反序列化器是否应仅限于 weights_only:表示反序列化器是否应仅限于 仅加载张量、原始类型和字典 以及通过 :func:`torch.serialization.add_safe_globals` 添加的任何类型。 更多详情请参阅 :ref:`weights-only`。 mmap:指示是否应该将文件进行内存映射,而不是将所有存储加载到内存中。 通常,文件中的张量存储首先从磁盘移动到 CPU 内存,之后 移动到保存时标记的位置,或者由`map_location`指定。如果最终位置是 CPU, 第二步将不会执行任何操作。当设置`mmap`标志时,在第一步中, 将张量存储从磁盘复制到 CPU 内存,而不是使用`f`进行 mmap。 pickle_load_args: (仅 Python 3)可选关键字参数传递给 func:`pickle_module.load` 和 :func:`pickle_module.Unpickler`,例如, attr:`errors=...`。 ..警告:: 除非将 `weights_only` 参数设置为 `True`,否则 `:func:`torch.load()` 不会使用 隐式使用 `pickle` 模块,这是已知的不安全。 有可能构造恶意的 pickle 数据,这些数据在反序列化时会执行任意代码 永远不要加载可能来自不受信任的数据 源代码处于不安全模式,或者可能已被篡改。**仅加载您信任的数据**。 .. 注意:: 当您在包含 GPU 张量的文件上调用 :func:`torch.load()` 时,这些张量将默认加载到 GPU 上。您可以调用 ``torch.load(.., map_location='cpu')`` 然后调用 :meth:`load_state_dict` 以避免在加载模型检查点时 GPU RAM 激增。 使用 ``torch.load(.., map_location='cpu')`` 可以避免在加载模型检查点时 GPU RAM 激增。 .. 注意:: 默认情况下,我们将字节字符串解码为 `utf-8`。这是为了避免一个常见错误 UnicodeDecodeError: 'ascii' 编码无法解码字节 0x... 当在 Python 3 中加载由 Python 2 保存的文件时。如果此默认 不正确,您可能需要使用额外的 :attr:`encoding` 关键字参数来指定 这些对象应该被加载,例如::attr:`encoding='latin1'` 将它们解码为字符串,使用 `latin1` 编码,而 :attr:`encoding='bytes'` 则将它们保留为字节序列,稍后可以用 `byte_array.decode(...)` 进行解码。 使用 `latin1` 编码将它们转换为字符串,而 :attr:`encoding='bytes'` 则将它们保留为字节序列,稍后可以用 `byte_array.decode(...)` 进行解码。 将它们作为字节序列保留,稍后可以用 `byte_array.decode(...)` 进行解码。 示例: >>> # xdoctest: +SKIP("未定义的文件路径") >>> torch.load("tensors.pt", weights_only=True) # 将所有张量加载到 CPU 上 >>> torch.load( ... "tensors.pt", ... map_location=torch.device("cpu"), ... weights_only=True, ... ) # 将所有张量加载到 CPU 上,使用一个函数 >>> torch.load( ... "tensors.pt", ... map_location=lambda storage, loc: storage, ... weights_only=True, ... ) # 将所有张量加载到 GPU 1 >>> torch.load( ... "tensors.pt", ... map_location=lambda storage, loc: storage.cuda(1), ... weights_only=True, ... ) # 忽略属性定义 # 将 GPU 1 的张量映射到 GPU 0 >>> torch.load( ... "tensors.pt", ... map_location={"cuda:1": "cuda:0"}, ... weights_only=True, ... ) # 从 io.BytesIO 对象加载张量 # 从缓冲区加载设置 weights_only=False,警告这可能不安全 >>> 使用 open("tensor.pt", "rb") 作为 f: ... buffer = io.BytesIO(f.read()) >>> torch.load(buffer, weights_only=False) # 使用 'ascii' 编码进行反序列化加载模块 从模块设置中加载权重,警告这可能会不安全 >>> torch.load("module.pt", 编码="ascii", weights_only=False) """ PyTorch._C._log_api_usage_once(torch.load) 文档信息 = ( "\n\n \n\n检查 torch.load 的文档以了解默认接受的类型 "仅权重 https://maskerprc.github.io/docs/stable/generated/torch.load.html。" ) def _get_wo_message(消息: str) -> str: unsafe_global_pattern = r"默认情况下,(\S+) 不是一个允许的全局变量。" has_unsafe_global = 正则表达式.搜索(unsafe_global_pattern, 消息) blocklist_pattern = r"哪个模块(\S+)被阻止" 有黑名单 = 正则表达式.搜索(黑名单模式, 消息) 导入模式 = r"(\S+) 必须是 (\S+) 才能加载" 包含导入 = 正则表达式.搜索(导入模式, 消息) 如果 包含不安全的全局变量: 更新消息 = ( "权重加载失败。此文件仍可加载,要加载它,您有两个选项," "退格键仅在您信任检查点来源的情况下执行这些步骤退格键[0m." f"\n\t(1) {不安全信息}\n\t(2) 或者,要使用 `weights_only=True` 加载,请检查“ 以下错误信息中的推荐步骤。\n\t权重反序列化错误: + 消息 ) 否则: 如果 has_import: 返回 f权重仅加载失败。{消息}输入文本翻译为简体中文为:\n {不安全信息}输入文本翻译为简体中文为:\n" 否则: 更新消息 = f权重仅加载失败。{不安全信息}输入文本翻译为简体中文为:\n" 如果 包含黑名单: 更新消息 += ( "请提交以下问题,以便我们进行修改:" "与您的使用案例兼容 `weights_only=True`:WeightsUnpickler 错误:" ) 更新消息 += 消息 返回 更新消息 + 文档信息 仅权重未设置 = 仅权重 如果 权重仅未设置: 仅权重 = _default_to_weights_only(pickle 模块) true 值 = ["1", "是", "是的", "true"] 通过环境变量强制仅允许安全或非安全重量负载 仅加载权重 = ( os.获取环境变量("仅加载权重", "0") true 值 ) 仅加载权重,不加载权重 = ( os.获取环境变量("仅加载权重,不使用权重", "0") true 值 ) 如果 仅加载权重 强制不加载权重仅加载: 抛出 运行时错误( "仅应设置 `TORCH_FORCE_WEIGHTS_ONLY_LOAD` 或 `TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD` 其中之一" "但两者都被设置了。" ) 如果...否则 force_weights_only_load: 仅权重 = 真实 如果...否则 强制不加载权重仅加载: # TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD 只能在调用点未显式设置 weights_only 时覆盖 如果 权重仅未设置: 警告.警告( 环境变量 TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD 已检测到,因为自 `weights_only` 参数未显式传递给 `torch.load`,强制使用 weights_only=False。, 用户警告, 栈级别=2, ) 仅权重 = 如果 仅权重: 如果 pickle 模块 : 抛出 运行时错误( 当指定了显式的 pickle_module 时,无法安全地加载权重 ) 否则: 如果 pickle 模块 : pickle 模块 = 酸菜 # 使翻转默认为 BC 兼容 如果 mmap : 来自 torch.utils.serialization 导入 配置 mmap = 配置.加载.mmap _check_dill_version(pickle 模块) 如果 编码 pickle_load_args.(): pickle_load_args[编码] = utf-8 替换为 _open_file_like(f, rb) as 打开的文件: 如果 _is_zipfile 是 zip 文件(打开的文件): # 压缩包读取器将前进当前文件位置。 如果我们想要真正地尾调用 torch.jit.load,我们需要 重置回原始位置。 orig_position = 打开的文件.告诉() 总体存储 = 替换为 _open_zipfile_reader(打开的文件) as 打开 ZIP 文件: 如果 _is_torchscript_zip(打开 ZIP 文件): 警告.警告( "'torch.load' 接收了一个看起来像 TorchScript 归档的 zip 文件" "调度到 'torch.jit.load' (直接调用 'torch.jit.load' 以" "忽略此警告)", 用户警告, ) 如果 仅权重: 抛出 运行时错误( "不能与 ``weights_only=True`` 一起使用通过 TorchScript 归档传递的 " "``torch.load``。" + 不安全信息 ) 打开的文件.搜索(原始位置) 返回 PyTorch.算子.加载(打开的文件, 地图位置=地图位置) 如果 内存映射: 如果 _is_path(f): 抛出 值错误( "f 必须是一个文件路径才能使用 mmap 参数" ) 大小 = os.路径.获取大小(f) 如果 IS_WINDOWS: 共享 = 获取默认的内存映射选项() == MAP_SHARED 否则: 共享 = 总体存储 = PyTorch.未类型化存储.from_file( os.文件系统路径(f), 共享, 大小 ) 如果 仅权重: 尝试: 返回 _load( 打开 ZIP 文件, 地图位置, _weights_only_unpickler, 总体存储=总体存储, **pickle_load_args, ) 除了 pickle.UnpicklingError as e: 抛出 pickle.UnpicklingError(_get_wo_message(str(e))) 来自 返回 _load( 打开 ZIP 文件, 地图位置, pickle 模块, 总体存储=总体存储, **pickle_load_args, ) 如果 内存映射: f_name = 请提供需要翻译的文本 如果 isinstance(f, str) 否则 f"{f}," 抛出 运行时错误( "mmap 只能用于保存为“" f"`torch.save("{f_name}_use_new_zipfile_serialization=True), " 请使用此选项保存您的检查点,以便使用 mmap。 ) 如果 仅权重: 尝试: 返回 _legacy_load( 打开的文件, 地图位置, _weights_only_unpickler, **pickle_load_args, ) 除了 pickle.UnpicklingError as e: 抛出 pickle.UnpicklingError(_get_wo_message(str(e))) 来自 返回 _legacy_load( 打开的文件, 地图位置, pickle 模块, **pickle_load_args )
# 注册对布局实例的序列化支持,例如 # torch.sparse_coo 等 def 获取布局(名称): “从其字符串表示形式获取布局扩展对象。” 缓存 = 获取布局.缓存 # 类型:忽略[已定义] 如果 缓存: for v PyTorch.字典.(): 如果 isinstance(v, PyTorch.布局): 缓存[str(v)] = v 返回 缓存[名称] 目前还没有好的方法来注释函数属性 https://github.com/python/mypy/issues/2087 获取布局.缓存 = {} # 类型:忽略[已定义] 复制注册.pickle(PyTorch.布局, Lambda 函数 对象: (获取布局, (str(对象),))) def _legacy_load(f, 地图位置, pickle 模块, **pickle_load_args): 反序列化对象: 字典[int, 任何] = {} 恢复位置 = _获取恢复位置(地图位置) UnpicklerWrapper(pickle 模块.反序列化器): 忽略名称定义 def 查找类(self, 模块名称, 名称): 如果 类型(名称) 字符串 存储 名称: 尝试: 返回 存储类型(名称) 除了 键错误: 通过 返回 超级().查找类(模块名称, 名称) def 检查容器源(容器类型, 源文件, 原始来源): 尝试: 当前来源 = 输入文本翻译为简体中文为:"".连接(获取源代码行和文件(容器类型)]0]) 除了 异常: # 保存源是可选的,因此我们可以忽略任何错误 警告.警告( "无法检索容器的源代码 " "类型 " + 容器类型.__name__ + "。它不会在加载时检查正确性。" "。" ) 返回 如果 原始源 != 当前源: 如果 容器类型.导出补丁: 文件名 = 容器类型.__name__ + .patch 差异 = 差分库.统一差异( 当前源.分割("输入文本翻译为简体中文为:\n"), 原始来源.分割("输入文本翻译为简体中文为:\n"), 源文件, 源文件, 行术语=输入文本翻译为简体中文为:"", ) = "输入文本翻译为简体中文为:\n".连接(差异) 尝试: 替换为 打开(文件名, "a+") as f: 文件大小 = f.搜索(0, 2) f.搜索(0) 如果 文件大小 == 0: f.() 如果...否则 文件大小 != 长度() 或者 f.阅读() != : 抛出 OSError msg = ( "保存了一个反向补丁到 " + 文件名 + ". " "运行 `patch -p0 < " + 文件名 + ` 回滚您的 ` 变更。 ) 除了 OSError: msg = ( 尝试保存补丁,但无法创建“ 可写文件 + 文件名 + 确保它 不存在,并且你的工作目录是 可写。 ) 否则: msg = ( 您可以通过以下方式获取原始源代码: "访问对象的源属性或设置" "设置 `torch.nn.Module.dump_patches = True` 并使用" "补丁工具撤销更改。" ) msg = f"类的源代码"{PyTorch.类型名(容器类型)}'已更改。'{信息}" 警告.警告(信息, 源变更警告) def 旧版加载(f): 反序列化对象: 字典[int, 任何] = {} def 持久化加载(已保存的 ID): 如果 isinstance(已保存的 ID, 元组): 忽略没有保存任何源容器的容器 如果 所有(已保存的 ID[1(]:) 检查容器源(*已保存的 ID) 返回 已保存的 ID[0] 返回 反序列化对象[int(已保存的 ID)] 替换为 ( 关闭( tarfile.打开(文件对象=f, 模式="r:", 格式=tarfile.PAX 格式) ) as 打包, mkdtemp() as tmp 目录, ): 如果 pickle 模块 _weights_only_unpickler: 抛出 运行时错误( "不能与保存为“ legacy .tar 格式的文件一起使用 ``weights_only=True`` " "的。" + 不安全信息 ) 打包.提取(存储, 路径=tmp 目录) 替换为 打开(os.路径.连接(tmp 目录, 存储), rb, 0) as f: 存储数量 = pickle 模块.加载(f, **pickle_load_args) for _ 范围(存储数量): args = pickle 模块.加载(f, **pickle_load_args) , 位置, 存储类型 = args dtype = 存储类型._dtype 对象 = 角色(存储, PyTorch.未类型化存储).新_with_file( f, PyTorch._工具.元素大小(数据类型) ) 对象 = 恢复位置(对象, 位置) 一旦我们决定中断序列化功能,我们就可以 # 停止使用 TypedStorage 进行包装 反序列化对象[] = PyTorch.存储.类型化存储( 包装存储=对象, 数据类型=数据类型, 内部=真实 ) 存储视图 = pickle 模块.加载(f, **pickle_load_args) for 目标 cdata, 根_cdata, 偏移量, numel 存储视图: = 反序列化对象[根_cdata] 元素大小 = PyTorch._工具.元素大小(.数据类型) 偏移字节数 = 偏移 * 元素大小 一旦我们决定中断序列化功能,我们就可以 # 停止使用 TypedStorage 进行包装 反序列化对象[目标 cdata] = PyTorch.存储.类型化存储( 包装存储=.未类型化存储[ 偏移字节数 : 偏移字节数 + numel * 元素大小 ] 数据类型=.数据类型, 内部=True, ) 打包.提取(张量, 路径=tmp 目录) 替换为 打开(os.路径.连接(tmp 目录, 张量), rb, 0) as f: 累计张量数量 = pickle 模块.加载(f, **pickle_load_args) for _ 范围(累计张量数量): args = pickle 模块.加载(f, **pickle_load_args) , 存储 ID, 原始张量类型 = args 存储 = 反序列化对象[存储 ID] (维数,) = 结构体.解包("<i", f.阅读(4)) # 跳过下一个 4 字节;旧编码将 ndim 视为 8 字节 f.阅读(4) numel = 结构体.解包(f"<"{维数}引号, f.阅读(8 * 维数)) 步长 = 结构体.解包(f"<"{维数}引号, f.阅读(8 * 维数)) (storage_offset,) = 结构体.解包("<q", f.阅读(8)) 张量 = PyTorch.空的((0,), 数据类型=存储.数据类型).集合( 存储.未类型化存储, storage_offset, 元素数量, 步长 ) 反序列化对象[] = 张量 Pickle 文件 = 打包.提取文件(pickle) 反序列化器 = UnpicklerWrapper(Pickle 文件, **pickle_load_args) 反序列化器.持久加载 = 持久加载 结果 = 反序列化器.加载() 返回 结果 反序列化对象 = {} def 持久化加载(已保存的 ID): 断言 isinstance(已保存的 ID, 元组) 类型名 = 可能解码 ASCII(已保存的 ID[0]) 数据 = 已保存的 ID[1] 如果 类型名 == 模块: 忽略没有保存任何源容器的容器 如果 所有(数据[1(]:) 检查容器源(*数据) 返回 数据[0] 如果...否则 类型名 == 存储: 存储类型, 根键, 位置, 元素数量, 视图元数据 = 数据 位置 = 可能解码 ASCII(位置) dtype = 存储类型.dtype 字节数 = numel * PyTorch._工具.元素大小(数据类型) 如果 根键 反序列化对象: 如果 PyTorch._守卫.激活模拟模式() : 对象 = 角色(存储, PyTorch.未类型化存储(字节数, 设备=元数据)) 如果...否则 序列化 TLS.跳过数据: 对象 = 角色(存储, PyTorch.未类型化存储(字节数)) 对象 = 恢复位置(对象, 位置) 否则: 对象 = 角色(存储, PyTorch.未类型化存储(字节数)) 对象._torch_load_uninitialized = 真实 对象 = 恢复位置(对象, 位置) 一旦我们决定中断序列化功能,我们就可以 # 停止使用 TypedStorage 进行包装 类型存储 = PyTorch.存储.类型化存储( 包装存储=对象, 数据类型=数据类型, 内部=真实 ) 反序列化对象[根键] = 类型存储 否则: 类型存储 = 反序列化对象[根键] 如果 已类型化存储._数据指针() == 0: 类型存储 = PyTorch.存储.类型化存储( 设备=已类型化存储.未类型化存储.设备, 数据类型=数据类型, 内部=True, ) 如果 视图元数据 : 视图键, 偏移量, 视图大小 = 视图元数据 偏移字节数 = 偏移 * PyTorch._工具.元素大小(数据类型) 视图大小(字节) = 视图大小 * PyTorch._工具.元素大小(数据类型) 如果 视图键 反序列化对象: 一旦我们决定中断序列化功能,我们就可以 # 停止使用 TypedStorage 进行包装 反序列化对象[视图键] = PyTorch.存储.类型化存储( 包装存储=已类型化存储.未类型化存储[ 偏移字节数 : 偏移字节数 + 视图大小(字节) ] 数据类型=数据类型, 内部=True, ) res = 反序列化对象[视图键] 否则: res = 类型存储 返回 res 否则: 抛出 运行时错误(f"未知已保存的 ID 类型:"{已保存的 ID[0]}") 检查可寻址(f) f 应该直接读取 = _应直接读取(f) 如果 f 应该直接读取 f.告诉() == 0: # legacy_load 要求 f 有 fileno() # 只有当偏移量为零时,我们才能尝试使用传统的 tar 文件加载器 尝试: 返回 旧版加载(f) 除了 tarfile.Tar 错误: 如果 _is_zipfile 是 zip 文件(f): # .zip 用于 torch.jit.save,此处将抛出反序列化错误 抛出 运行时错误( f"{f.名称}是一个 zip 存档(你是指 torch.jit.load()吗?) ) 来自 如果不是 tarfile,则重置文件偏移量并继续 f.搜索(0) 魔法数字 = pickle 模块.加载(f, **pickle_load_args) 如果 魔法数字 != 魔法数字: 抛出 运行时错误("无效的魔法数字;文件损坏?") 协议版本 = pickle 模块.加载(f, **pickle_load_args) 如果 协议版本 != 协议版本: 抛出 运行时错误(f"无效的协议版本:"{协议版本}") 系统信息 = pickle 模块.加载(f, **pickle_load_args) 反序列化器 = UnpicklerWrapper(f, **pickle_load_args) 反序列化器.持久加载 = 持久加载 结果 = 反序列化器.加载() 反序列化存储键 = pickle 模块.加载(f, **pickle_load_args) 如果 PyTorch._守卫.激活模拟模式() 序列化 TLS.跳过数据: 偏移 = f.告诉() 如果 f 应该直接读取 否则 for 反序列化存储键: 断言 反序列化对象 类型存储 = 反序列化对象[] 已类型化存储.未类型化存储.从文件设置( f, 偏移量, 应该直接读取吗, PyTorch._工具.元素大小(已类型化存储.数据类型), ) 如果 偏移 : 偏移 = f.告诉() PyTorch._工具.验证已加载的稀疏张量() 返回 结果 def 可能解码 ASCII(字节字符串: 联合[字节, str]) -> str: 当在 Py3 中使用 encoding='bytes'时,一些**内部**键以字节形式存储 # 字符串在 Py2 中作为字节加载。此函数使用 # ascii 编码,这是 Py3 默认使用的编码。 # # 注意:这仅应用于内部键(例如,`typename`和 # `location`在下面的`persistent_load`中!) 如果 isinstance(字节字符串, 字节): 返回 字节字符串.解码(ASCII) 返回 字节字符串 def _获取恢复位置(地图位置): 如果 地图位置 : 恢复位置 = 默认恢复位置 如果...否则 isinstance(地图位置, 字典): def 恢复位置(存储, 位置): 位置 = 地图位置.获取(位置, 位置) 返回 默认恢复位置(存储, 位置) 如果...否则 isinstance(地图位置, (str, 字节)): def 恢复位置(存储, 位置): 返回 默认恢复位置(存储, 地图位置) 如果...否则 isinstance(地图位置, PyTorch.设备): def 恢复位置(存储, 位置): 返回 默认恢复位置(存储, str(地图位置)) 否则: def 恢复位置(存储, 位置): 结果 = 地图位置(存储, 位置) 如果 结果 : 结果 = 默认恢复位置(存储, 位置) 返回 结果 返回 恢复位置 存储类型: def __init__(self, 名称): self._dtype = _从 pickle 存储类型获取数据类型(名称) @property def 数据类型(self): 返回 self._dtype def __str__(self): 返回 f"存储类型(dtype="{self.数据类型})" def _load( zip 文件, 地图位置, pickle 模块, Pickle 文件="data.pkl", 总体存储=, **pickle_load_args, ): 恢复位置 = _获取恢复位置(地图位置) 已加载存储 = {} 可以计算存储偏移量 = 如果 zip 文件.有记录(".版本格式"): 版本 = zip 文件.获取记录(".版本格式") 可以计算存储偏移量 = 版本 b1 检查是否需要字节交换 字节序名称 = 字节序 字节序数据 = 如果 zip 文件.有记录(字节序名称): 字节序数据 = zip 文件.获取记录(字节序名称) 如果 字节序数据 [b小端, b"大"] 抛出 值错误("未知端序类型:" + 字节序数据.解码()) 如果...否则 ( 获取默认加载端序() == 载入端序. 或者 获取默认加载端序() ): 字节序数据 = b小端 如果...否则 获取默认加载端序() == 载入端序.大端: 字节序数据 = b大端 如果...否则 获取默认加载端序() == 载入端序.本地: 通过 否则: 抛出 值错误("无效的加载端序类型") 存储对齐 = 64 如果 zip 文件.有记录(".存储对齐"): 存储对齐 = int(zip 文件.获取记录(".存储对齐")) 如果 ( zip 文件.有记录(字节序名称) 获取默认加载端序() 系统模块.字节序 == 大端 ): 默认行为已更改 # 查看 https://github.com/pytorch/pytorch/issues/101688 警告.警告( 默认没有字节序标记的检查点的默认加载端序 在大端机器上从'native'改为'little'端序, 请避免此行为,请使用 torch.serialization.set_default_load_endianness 设置“ 期望的默认加载端序, 用户警告, ) 来自 torch.utils.serialization 导入 配置 计算存储偏移量 = 配置.加载.计算存储偏移量 运行调试断言 = os.环境.获取("TORCH_SERIALIZATION_DEBUG", "0") == 1 当前偏移量 = # miniz.h/miniz.c 中的常量 数据描述符大小 64 = 24 数据描述符大小 32 = 16 mz_uint32_max = 4294967295 偏移量: 字典[str, int] = 字典() def 获取偏移量(, 名称, 元素数量): "" 返回与键名 `name` 和大小 numel 相关的存储的偏移量。 预期此存储的 zipfile 头部从当前偏移量开始。 警告:此函数依赖于 miniz.c 中 zipwriter 的行为。特别是, `mz_zip_writer_add_mem_ex_v2` 的行为。此函数的行为必须保持 与 miniz 保持同步! 在读取从 storage_offset 开始的 numel 大小存储后 如果这是第一次读取 storage,则更新非局部变量 current_offset 到下一个 zipfile 头部的起始位置,通过增加 通过 numel 和数据描述符大小来获取。 """ 非局部 当前偏移量, 偏移量 如果 名称 偏移量: 存储偏移量 = 偏移量[名称] 返回 存储偏移量 如果 当前偏移量 : 断言 == "零" 当前偏移量 = zip 文件.获取记录偏移(名称) 本地头部偏移 = zip 文件.获取记录头偏移(名称) 存储偏移量 = 当前偏移量 否则: 存储偏移量 = zip 文件.获取记录偏移量(不读取)( 当前偏移量, 名称, 元素数量, 存储对齐 ) 本地头部偏移 = 当前偏移量 仅在存储的 typed_storage._data_ptr() == 0 的情况下才需要。 读取后。否则,持久化加载永远不会“重新调用”加载张量。 对于给定的键。 偏移量[名称] = 存储偏移量 # 增加当前偏移量,偏移量指向下一个 zip 文件头开始的位置 当前偏移量 = 存储偏移量 + numel # 在有效载荷后添加数据描述符的大小 如果 numel > 0: 如果 本地头部偏移 mz_uint32_max 或者 numel mz_uint32_max: 当前偏移量 += 数据描述符大小 64 否则: 当前偏移量 += 数据描述符大小 32 返回 存储偏移量 def 加载张量(数据类型, 元素数量, , 位置): 名称 = f数据/{}" 如果 PyTorch._守卫.检测伪造模式() : 字节数 = numel * PyTorch._工具.元素大小(数据类型) 存储 = PyTorch.未类型化存储(字节数, 设备=元数据) 存储.检查点偏移 = zip 文件.获取记录偏移(名称) 如果...否则 序列化 TLS.跳过数据: 字节数 = numel * PyTorch._工具.元素大小(数据类型) 存储 = PyTorch.未类型化存储(字节数) 如果...否则 总体存储 : 如果 可以计算存储偏移量 计算存储偏移量: 存储偏移量 = 获取偏移量(, 名称, 元素数量) 如果 运行调试断言: 如果 存储偏移量 != zip 文件.获取记录偏移(名称): 抛出 运行时错误( 这是一个作为 `TORCH_SERIALIZATION_DEBUG` 环境变量运行的调试断言 f变量已设置:偏移量不正确{名称},获得{storage_offset}预期的是 " f"{zip 文件.获取记录偏移(名称)}" ) 否则: 存储偏移量 = zip 文件.获取记录偏移(名称) 存储 = 总体存储[存储偏移量 : 存储偏移量 + 元素数量] 否则: 如果 可以计算存储偏移量 运行调试断言: # 这是调试代码,我们用它来测试有效性 torch.utils.serialization.config.load.calculate_storage_offsets 在 CI 中 存储偏移量 = 获取偏移量(, 名称, 元素数量) 如果 存储偏移量 != zip 文件.获取记录偏移(名称): 抛出 运行时错误( 这是一个作为 `TORCH_SERIALIZATION_DEBUG` 环境变量运行的调试断言 f变量已设置:偏移量不正确{名称},获得{storage_offset}预期的是 " f"{zip 文件.获取记录偏移(名称)}" ) 存储 = ( zip 文件.从记录中获取存储(名称, 元素数量, PyTorch.未类型化存储) ._typed_storage() ._未指定存储 ) 如果需要字节交换,则在此处交换 如果 字节序数据 : 如果 字节序数据.解码() != 系统模块.字节序: 存储.字节交换(数据类型) 一旦我们决定中断序列化功能,我们就可以 # 停止使用 TypedStorage 进行包装 如果 PyTorch._守卫.检测伪造模式() : 包装存储 = 恢复位置(存储, 位置) 否则: 存储._假设备 = 位置 包装存储 = 存储 类型存储 = PyTorch.存储.类型化存储( 包装存储=包装存储, 数据类型=数据类型, 内部=True, ) 如果 已类型化存储._数据指针() != 0: 已加载存储[] = 类型存储 返回 类型存储 def 持久化加载(已保存的 ID): 断言 isinstance(已保存的 ID, 元组) 类型名 = 可能解码 ASCII(已保存的 ID[0]) 数据 = 已保存的 ID[1] 断言 类型名 == 存储, ( f"未知持久加载的 typename,期望 'storage' 但得到 '"{类型名} ) 存储类型, , 位置, numel = 数据 如果 存储类型 PyTorch.未类型化存储: dtype = PyTorch.uint8 否则: dtype = 存储类型.dtype 如果 已加载存储: 类型存储 = 已加载存储[] 否则: 字节数 = numel * PyTorch._工具.元素大小(数据类型) 类型存储 = 加载张量( 数据类型, 字节数, , 可能解码 ASCII(位置) ) 返回 类型存储 加载模块映射: 字典[str, str] = { # 请参阅 https://github.com/pytorch/pytorch/pull/51633 torch.tensor: "torch._tensor" } # 需要继承 Unpickler 类而不是直接修改 find_class 方法 # 因为它在 pickle 中被标记为只读。 # type: ignore 是因为 mypy 无法静态确定此类类型。 UnpicklerWrapper(pickle 模块.反序列化器): 忽略名称定义 来自 https://stackoverflow.com/questions/13398462/unpickling-python-objects-with-a-changed-module-path/13405732 允许我们覆盖 pickle 在反序列化对象时使用的导入。 这对于维护向后兼容性非常有用,如果我们更改了依赖于张量实例化的模块路径。 def 查找类(self, 模块名称, 名称): 如果 类型(名称) 字符串 存储 名称: 尝试: 返回 存储类型(名称) 除了 键错误: 通过 模块名称 = 加载模块映射.获取(模块名称, 模块名称) 返回 超级().查找类(模块名称, 名称) # 加载数据(这可能会进一步使用 `persistent_load` 来加载数据) 数据文件 = 输入/输出.BytesIO(zip 文件.获取记录(Pickle 文件)) 反序列化器 = UnpicklerWrapper(数据文件, **pickle_load_args) 反序列化器.持久加载 = 持久加载 # 需要用于存储设备和重建张量设备的情况 # 未连接(包装子类和用 NumPy 重建的张量) 全局 _序列化 TLS 序列化 TLS.地图位置 = 地图位置 结果 = 反序列化器.加载() 序列化 TLS.地图位置 = PyTorch._工具.验证已加载的稀疏张量() PyTorch._C.记录 API 使用元数据( torch.load.metadata, {序列化 ID: zip 文件.序列化 ID()} ) 返回 结果 def _is_torchscript_zip(zip 文件): 返回 constants.pkl zip 文件.获取所有记录()

© 版权所有 PyTorch 贡献者。

使用 Sphinx 构建,并使用 Read the Docs 提供的主题。

文档

查看 PyTorch 的全面开发者文档

查看文档

教程

深入了解初学者和高级开发者的教程

查看教程

资源

查找开发资源,获取您的疑问解答

查看资源