Shortcuts

torch.package

torch.package 增加了对创建包含工件和任意 PyTorch 代码的包的支持。这些包可以保存、共享,用于在以后的日期或在不同的机器上加载和执行模型,甚至可以使用 torch::deploy 部署到生产环境中。

本文档包含教程、操作指南、解释和API参考,将帮助您更深入地了解torch.package及其使用方法。

警告

此模块依赖于不安全的 pickle 模块。仅解包您信任的数据。

可以构造恶意的pickle数据,这些数据在反序列化过程中会执行任意代码。 切勿解包可能来自不受信任来源或可能被篡改的数据。

更多信息,请查看文档中关于pickle模块的内容。

教程

打包你的第一个模型

一个指导您完成打包和解包简单模型的教程可以在 Colab上找到。 完成此练习后,您将熟悉用于创建和使用 Torch包的基本API。

我如何…

查看包内的内容是什么?

将包视为ZIP存档

用于 torch.package 的容器格式是 ZIP,因此任何适用于标准 ZIP 文件的工具都应适用于浏览其内容。一些常见的与 ZIP 文件交互的方式:

  • unzip my_package.pt 将把 torch.package 存档解压缩到磁盘上,您可以在那里自由检查其内容。

$ unzip my_package.pt && tree my_package
my_package
├── .data
│   ├── 94304870911616.storage
│   ├── 94304900784016.storage
│   ├── extern_modules
│   └── version
├── models
│   └── model_1.pkl
└── torchvision
    └── models
        ├── resnet.py
        └── utils.py
~ cd my_package && cat torchvision/models/resnet.py
...
  • Python 的 zipfile 模块提供了一种标准的方式来读取和写入 ZIP 存档内容。

from zipfile import ZipFile
with ZipFile("my_package.pt") as myzip:
    file_bytes = myzip.read("torchvision/models/resnet.py")
    # 以某种方式编辑 file_bytes
    myzip.writestr("torchvision/models/resnet.py", new_file_bytes)
  • vim 具有原生读取 ZIP 档案的能力。你甚至可以编辑文件并将其 write 回档案中!

# 将此添加到您的 .vimrc 中,以将 `*.pt` 文件视为 zip 文件
au BufReadCmd *.pt call zip#Browse(expand(""))

~ vi my_package.pt

使用 file_structure() API

PackageImporter 提供了一个 file_structure() 方法,该方法将返回一个可打印和可查询的 Directory 对象。Directory 对象是一个简单的目录结构,您可以使用它来探索 torch.package 的当前内容。

对象本身可以直接打印,并会输出一个文件树表示。要过滤返回的内容,请使用glob风格的includeexclude过滤参数。

with PackageExporter('my_package.pt') as pe:
    pe.save_pickle('models', 'model_1.pkl', mod)

importer = PackageImporter('my_package.pt')
# 可以使用include/exclude参数限制打印的项目
print(importer.file_structure(include=["**/utils.py", "**/*.pkl"], exclude="**/*.storage"))
print(importer.file_structure()) # 将打印出所有文件

输出:

# 使用glob模式过滤:
#    include=["**/utils.py", "**/*.pkl"], exclude="**/*.storage"
─── my_package.pt
    ├── models
    │   └── model_1.pkl
    └── torchvision
        └── models
            └── utils.py

# 所有文件
─── my_package.pt
    ├── .data
    │   ├── 94304870911616.storage
    │   ├── 94304900784016.storage
    │   ├── extern_modules
    │   └── version
    ├── models
    │   └── model_1.pkl
    └── torchvision
        └── models
            ├── resnet.py
            └── utils.py

您还可以使用 has_file() 方法查询 Directory 对象。

importer_file_structure = importer.file_structure()
found: bool = importer_file_structure.has_file("package_a/subpackage.py")

查看为什么某个模块被包含为依赖项?

假设有一个给定的模块 foo,而你想知道为什么你的 PackageExporterfoo 作为依赖项引入。

PackageExporter.get_rdeps() 将返回所有直接依赖于 foo 的模块。

如果您想查看某个模块 src 如何依赖于 fooPackageExporter.all_paths() 方法将返回一个DOT格式的图,显示 srcfoo 之间的所有依赖路径。

如果你只想查看整个依赖图,可以使用 PackageExporterPackageExporter.dependency_graph_string()

在我的包中包含任意资源并在以后访问它们?

PackageExporter 提供了三种方法,save_picklesave_textsave_binary,允许您将 Python 对象、文本和二进制数据保存到包中。

with torch.PackageExporter("package.pt") as exporter:
    # 将对象序列化并保存到归档文件中的 `my_resources/tensor.pkl`。
    exporter.save_pickle("my_resources", "tensor.pkl", torch.randn(4))
    exporter.save_text("config_stuff", "words.txt", "a sample string")
    exporter.save_binary("raw_data", "binary", my_bytes)

PackageImporter 提供了名为 load_pickleload_textload_binary 的补充方法,允许您从包中加载Python对象、文本和二进制数据。

importer = torch.PackageImporter("package.pt")
my_tensor = importer.load_pickle("my_resources", "tensor.pkl")
text = importer.load_text("config_stuff", "words.txt")
binary = importer.load_binary("raw_data", "binary")

如何自定义类的打包方式?

torch.package 允许自定义类的打包方式。这种行为通过在类上定义方法 __reduce_package__ 并通过定义相应的解包函数来实现。这与为 Python 的常规序列化过程定义 __reduce__ 类似。

步骤:

  1. 在目标类上定义方法 __reduce_package__(self, exporter: PackageExporter)。此方法应完成将类实例保存到包中的工作,并应返回一个元组,其中包含相应的解包函数及其所需的参数,以便调用解包函数。当 PackageExporter 遇到目标类的实例时,会调用此方法。

  2. 为类定义一个解包函数。这个解包函数应该完成重建并返回类实例的工作。函数签名的第一个参数应该是一个 PackageImporter 实例,其余的参数由用户定义。

# foo.py [自定义类Foo打包方式的示例]
from torch.package import PackageExporter, PackageImporter
import time


class Foo:
    def __init__(self, my_string: str):
        super().__init__()
        self.my_string = my_string
        self.time_imported = 0
        self.time_exported = 0

    def __reduce_package__(self, exporter: PackageExporter):
        """
        当保存此对象的实例时,由``torch.package.PackageExporter``的Pickler的``persistent_id``调用。
        此方法应完成在``torch.package``存档中保存此对象的工作。

        返回带有参数的函数,以从``torch.package.PackageImporter``的Pickler的``persistent_load``函数加载对象。
        """

        # 使用此模式以确保不会与正常依赖项发生命名冲突,
        # 保存在此模块名称下的任何内容都不应与其他包中的项目冲突
        generated_module_name = f"foo-generated._{exporter.get_unique_id()}"
        exporter.save_text(
            generated_module_name,
            "foo.txt",
            self.my_string + ", with exporter modification!",
        )
        time_exported = time.clock_gettime(1)

        # 返回解包函数及参数,以调用
        return (unpackage_foo, (generated_module_name, time_exported,))


def unpackage_foo(
    importer: PackageImporter, generated_module_name: str, time_exported: float
) -> Foo:
    """
    当解包Foo对象时,由``torch.package.PackageImporter``的Pickler的``persistent_load``函数调用。
    执行从``torch.package``存档加载并返回Foo实例的工作。
    """
    time_imported = time.clock_gettime(1)
    foo = Foo(importer.load_text(generated_module_name, "foo.txt"))
    foo.time_imported = time_imported
    foo.time_exported = time_exported
    return foo
# 保存类 Foo 实例的示例

import torch
from torch.package import PackageImporter, PackageExporter
import foo

foo_1 = foo.Foo("foo_1 initial string")
foo_2 = foo.Foo("foo_2 initial string")
with PackageExporter('foo_package.pt') as pe:
    # 正常保存,无需额外工作
    pe.save_pickle('foo_collection', 'foo1.pkl', foo_1)
    pe.save_pickle('foo_collection', 'foo2.pkl', foo_2)

pi = PackageImporter('foo_package.pt')
print(pi.file_structure())
imported_foo = pi.load_pickle('foo_collection', 'foo1.pkl')
print(f"foo_1 string: '{imported_foo.my_string}'")
print(f"foo_1 export time: {imported_foo.time_exported}")
print(f"foo_1 import time: {imported_foo.time_imported}")
# 运行上述脚本的输出
─── foo_package
    ├── foo-generated
    │   ├── _0
    │   │   └── foo.txt
    │   └── _1
    │       └── foo.txt
    ├── foo_collection
    │   ├── foo1.pkl
    │   └── foo2.pkl
    └── foo.py

foo_1 字符串: 'foo_1 初始字符串, 包含减少修改!'
foo_1 导出时间: 9857706.650140837
foo_1 导入时间: 9857706.652698385

在我的源代码中测试是否在包内执行?

一个 PackageImporter 会将属性 __torch_package__ 添加到它初始化的每个模块中。您的代码可以检查此属性的存在,以确定它是否在打包的上下文中执行。

# 在 foo/bar.py 中:

if "__torch_package__" in dir():  # 如果代码是从包中加载的,则为真
    def is_in_package():
        return True

    UserException = Exception
else:
    def is_in_package():
        return False

    UserException = UnpackageableException

现在,代码的行为将根据它是通过您的 Python 环境正常导入还是从 torch.package 导入而有所不同。

from foo.bar import is_in_package

print(is_in_package())  # 假

loaded_module = PackageImporter(my_package).import_module("foo.bar")
loaded_module.is_in_package()  # 真

警告:通常情况下,代码的行为会根据其是否被打包而有所不同,这是一种不好的做法。这可能导致难以调试的问题,这些问题对代码的导入方式非常敏感。如果你的包打算被广泛使用,考虑重构你的代码,使其无论以何种方式加载,行为都保持一致。

将代码补丁集成到包中?

PackageExporter 提供了一个 save_source_string() 方法,允许您将任意 Python 源代码保存到您选择的模块中。

with PackageExporter(f) as exporter:
    # 保存当前Python环境中可用的my_module.foo。
    exporter.save_module("my_module.foo")

    # 这将提供的字符串保存到包归档文件中的my_module/foo.py。
    # 它将覆盖之前保存的my_module.foo。
    exporter.save_source_string("my_module.foo", textwrap.dedent(
        """\
        def my_function():
            print('hello world')
        """
    ))

    # 如果你想将my_module.bar视为一个包
    # (例如,保存到`my_module/bar/__init__.py`而不是`my_module/bar.py`)
    # 传递is_package=True,
    exporter.save_source_string("my_module.bar",
                                "def foo(): print('hello')\n",
                                is_package=True)

importer = PackageImporter(f)
importer.import_module("my_module.foo").my_function()  # 打印 'hello world'

从打包的代码中访问包内容?

PackageImporter 实现了 importlib.resources API,用于访问包内的资源。

with PackageExporter(f) as exporter:
    # 将文本保存到归档文件中的 my_resource/a.txt
    exporter.save_text("my_resource", "a.txt", "hello world!")
    # 将张量保存到 my_pickle/obj.pkl
    exporter.save_pickle("my_pickle", "obj.pkl", torch.ones(2, 2))

    # 请参见下面的模块内容
    exporter.save_module("foo")
    exporter.save_module("bar")

The importlib.resources API 允许从打包的代码中访问资源。

# foo.py:
import importlib.resources
import my_resource

# 返回 "hello world!"
def get_my_resource():
    return importlib.resources.read_text(my_resource, "a.txt")

使用 importlib.resources 是从打包代码内部访问包内容的推荐方式,因为它符合 Python 标准。然而,也可以从打包代码内部访问父 PackageImporter 实例本身。

# bar.py:
import torch_package_importer # 这是导入此模块的PackageImporter。

# 打印 "hello world!",相当于 importlib.resources.read_text
def get_my_resource():
    return torch_package_importer.load_text("my_resource", "a.txt")

# 你还可以做 importlib.resources API 不支持的事情,比如从包中加载一个pickled对象。
def get_my_pickle():
    return torch_package_importer.load_pickle("my_pickle", "obj.pkl")

区分打包代码和非打包代码?

要判断一个对象的代码是否来自torch.package,可以使用torch.package.is_from_package()函数。 注意:如果一个对象来自一个包,但其定义来自标记为extern的模块或来自stdlib, 此检查将返回False

importer = PackageImporter(f)
mod = importer.import_module('foo')
obj = importer.load_pickle('model', 'model.pkl')
txt = importer.load_text('text', 'my_test.txt')

assert is_from_package(mod)
assert is_from_package(obj)
assert not is_from_package(txt) # 字符串来自标准库,所以这将返回False

重新导出一个已导入的对象?

要重新导出一个之前由PackageImporter导入的对象,您必须让新的PackageExporter 知道原始的PackageImporter,以便它能够找到对象依赖项的源代码。

importer = PackageImporter(f)
obj = importer.load_pickle("model", "model.pkl")

# 在新的包中重新导出 obj
with PackageExporter(f2, importer=(importer, sys_importer)) as exporter:
    exporter.save_pickle("model", "model.pkl", obj)

如何打包一个TorchScript模块?

要打包一个 TorchScript 模型,请使用与任何其他对象相同的 save_pickleload_pickle API。 保存作为属性或子模块的 TorchScript 对象也得到支持,无需额外工作。

# 保存 TorchScript 就像保存其他对象一样
with PackageExporter(file_name) as e:
    e.save_pickle("res", "script_model.pkl", scripted_model)
    e.save_pickle("res", "mixed_model.pkl", python_model_with_scripted_submodule)
# 正常加载
importer = PackageImporter(file_name)
loaded_script = importer.load_pickle("res", "script_model.pkl")
loaded_mixed = importer.load_pickle("res", "mixed_model.pkl"

解释

torch.package 格式概述

一个 torch.package 文件是一个ZIP归档文件,通常使用 .pt 扩展名。在ZIP归档文件中,有两种类型的文件:

  • 框架文件,放置在 .data/ 目录中。

  • 用户文件,即其他所有内容。

作为一个例子,这是一个来自 torchvision 的完全打包的 ResNet 模型:

resnet
├── .data  # 所有特定框架的数据都存储在这里。
│   │      # 命名为避免与用户序列化的代码冲突。
│   ├── 94286146172688.storage  # 张量数据
│   ├── 94286146172784.storage
│   ├── extern_modules  # 包含外部模块名称的文本文件(例如 'torch')
│   ├── version         # 版本元数据
│   ├── ...
├── model  # 序列化的模型
│   └── model.pkl
└── torchvision  # 所有代码依赖项都作为源文件捕获
    └── models
        ├── resnet.py
        └── utils.py

框架文件

.data/ 目录由 torch.package 拥有,其内容被视为私有实现细节。 torch.package 格式对 .data/ 的内容不做任何保证,但任何更改都将向后兼容 (即,较新版本的 PyTorch 始终能够加载较旧的 torch.packages)。

目前,.data/ 目录包含以下项目:

  • version: 序列化格式的版本号,以便 torch.package 导入基础设施知道如何加载此包。

  • extern_modules: 被视为 extern 的模块列表。extern 模块将使用加载环境的系统导入器进行导入。

  • *.storage: 序列化的张量数据。

.数据
├── 94286146172688.存储
├── 94286146172784.存储
├── 外部模块
├── 版本
├── ...

用户文件

存档中的所有其他文件都是由用户放置的。布局与Python的常规包相同。如需深入了解Python打包的工作原理,请参阅这篇文章(它有些过时,因此请与Python参考文档中的实现细节进行双重检查)。


├── model  # 序列化的模型
│   └── model.pkl
├── another_package
│   ├── __init__.py
│   ├── foo.txt         # 一个资源文件 , 参见 importlib.resources
│   └── ...
└── torchvision
    └── models
        ├── resnet.py   # torchvision.models.resnet
        └── utils.py    # torchvision.models.utils

如何torch.package找到你的代码的依赖项

分析对象的依赖关系

当你调用 save_pickle(obj, ...) 时,PackageExporter 会正常地序列化对象。然后,它使用 pickletools 标准库模块来解析序列化后的字节码。

在 pickle 中,对象与一个 GLOBAL 操作码一起保存,该操作码描述了在哪里可以找到对象类型的实现,例如:

GLOBAL 'torchvision.models.resnet ResNet'

依赖解析器将收集所有GLOBAL操作并将它们标记为您的pickle对象的依赖项。 有关pickle和pickle格式的更多信息,请参阅Python文档

分析模块的依赖关系

当识别出一个Python模块作为依赖项时,torch.package会遍历该模块的Python AST表示,并查找具有完整支持的标准形式的导入语句:from x import yimport zfrom w import v as u等。当遇到这些导入语句之一时,torch.package会将导入的模块注册为依赖项,然后以相同的AST遍历方式解析这些依赖项。

注意:AST 解析对 __import__(...) 语法的支持有限,并且不支持 importlib.import_module 调用。通常情况下,您不应期望 torch.package 能够检测到动态导入。

依赖管理

torch.package 会自动找到您的代码和对象所依赖的Python模块。这个过程称为依赖解析。 对于依赖解析器找到的每个模块,您必须指定一个操作来执行。

允许的操作包括:

  • intern: 将此模块放入包中。

  • extern: 将此模块声明为包的外部依赖项。

  • mock: 模拟此模块。

  • deny: 依赖此模块将在包导出期间引发错误。

最后,还有一个重要的操作,虽然从技术上讲它并不属于torch.package

  • 重构:移除或更改代码中的依赖关系。

请注意,操作仅在完整的 Python 模块上定义。无法仅打包模块中的一个函数或类,而将其他部分排除在外。 这是设计使然。Python 不提供模块中定义的对象之间的清晰边界。唯一定义的依赖组织单元是模块,因此这就是 torch.package 所使用的。

操作通过模式应用于模块。模式可以是模块名称("foo.bar")或通配符(如 "foo.**")。您可以使用 PackageExporter 上的方法将模式与操作关联,例如。

my_exporter.intern("torchvision.**")
my_exporter.extern("numpy")

如果一个模块匹配某个模式,相应的操作将被应用到该模块上。对于给定的模块,模式将按照它们定义的顺序进行检查,并采取第一个匹配的操作。

intern

如果一个模块被intern化,它将被放入包中。

此操作是您的模型代码,或任何您想要打包的相关代码。例如,如果您正在尝试打包来自 torchvision 的 ResNet, 您需要 intern 模块 torchvision.models.resnet。

在包导入时,当您的打包代码尝试导入一个intern模块时,PackageImporter 会在您的包中查找该模块。如果找不到该模块,将引发错误。这确保了每个PackageImporter与加载环境隔离——即使您在包和加载环境中都有my_interned_module可用,PackageImporter也只会使用包中的版本。

注意:只有 Python 源模块可以被 intern。其他类型的模块,如 C 扩展模块和字节码模块,如果你尝试 intern 它们,将会引发错误。这些类型的模块需要被 mockextern

extern

如果一个模块被extern声明,它将不会被打包。相反,它将被添加到此包的外部依赖列表中。你可以在package_exporter.extern_modules中找到这个列表。

在包导入时,当打包的代码尝试导入一个extern-ed模块时,PackageImporter将使用默认的Python导入器来查找该模块,就像你执行了importlib.import_module("my_externed_module")一样。如果找不到该模块,将会引发一个错误。

通过这种方式,您可以在您的包中依赖第三方库,如 numpyscipy,而不必也将它们打包。

警告:如果任何外部库以向后不兼容的方式更改,您的包可能无法加载。如果您需要长期可重复性来确保您的包正常运行,请尽量限制使用extern

mock

如果一个模块被mock了,它将不会被打包。取而代之的是一个存根模块将被打包。该存根模块将允许你从中检索对象(因此from my_mocked_module import foo不会出错),但任何对该对象的使用都会引发NotImplementedError

mock 应该用于你“知道”在加载的包中不需要的代码,但你仍然希望在非打包内容中可用的代码。 例如,初始化/配置代码,或仅用于调试/训练的代码。

警告:通常情况下,mock 应作为最后的手段使用。它在打包代码和非打包代码之间引入了行为差异,这可能会导致以后的混淆。建议优先重构代码以消除不必要的依赖。

重构

管理依赖关系的最佳方式是完全不依赖!通常,可以通过重构代码来消除不必要的依赖。以下是编写具有清晰依赖关系的代码的一些指导原则(这些原则通常也是良好的实践!):

只包含你使用的部分。不要在你的代码中留下未使用的导入。依赖解析器不够智能,无法判断它们确实未被使用,并且会尝试处理它们。

限定你的导入。例如,与其写 import foo 然后在后面使用 foo.bar.baz,不如写 from foo.bar import baz。这样更精确地指定了你的实际依赖项(foo.bar),并让依赖解析器知道你不需要整个 foo

将包含不相关功能的大文件拆分成较小的文件。如果你的 utils 模块包含了一堆不相关的功能,那么任何依赖于 utils 的模块都需要引入许多不相关的依赖项,即使你只需要其中的一小部分。相反,建议定义单一用途的模块,这些模块可以彼此独立打包。

模式

模式允许您使用便捷的语法指定模块组。模式的语法和行为遵循 Bazel/Buck 的 glob()

我们试图与模式匹配的模块称为候选模块。候选模块由一系列由分隔符字符串分隔的段组成,例如 foo.bar.baz

模式包含一个或多个段。段可以是:

  • 一个字面字符串(例如 foo),完全匹配。

  • 包含通配符的字符串(例如 torch,或 foo*baz*)。通配符匹配任何字符串,包括空字符串。

  • 双通配符 (**)。这匹配零个或多个完整段。

示例:

  • torch.**: 匹配 torch 及其所有子模块,例如 torch.nntorch.nn.functional

  • torch.*: 匹配 torch.nntorch.functional,但不匹配 torch.nn.functionaltorch

  • torch*.**: 匹配 torch, torchvision 及其所有子模块

在指定操作时,您可以传递多个模式,例如。

exporter.intern(["torchvision.models.**", "torchvision.utils.**"])

如果模块匹配任何模式,它将匹配此操作。

您还可以指定要排除的模式,例如。

exporter.mock("**", exclude=["torchvision.**"])

如果一个模块匹配了任何排除模式,它将不会匹配此操作。在这个例子中,我们模拟了除torchvision及其子模块之外的所有模块。

当一个模块可能匹配多个操作时,将采用第一个定义的操作。

torch.package 尖锐边缘

避免在模块中使用全局状态

Python 使得在模块级别范围内绑定对象和运行代码变得非常容易。这通常是没问题的——毕竟,函数和类也是通过这种方式绑定到名称上的。然而,当你在模块范围内定义一个对象并打算对其进行修改时,事情就会变得更加复杂,因为这会引入可变的全局状态。

可变的全局状态非常有用——它可以减少样板代码,允许开放注册到表格等。但是,除非非常小心地使用,否则在与 torch.package 一起使用时可能会导致复杂问题。

每个PackageImporter都会为其内容创建一个独立的环境。这样做很好,因为它意味着我们可以加载多个包并确保它们彼此隔离,但当模块以假设共享可变全局状态的方式编写时,这种行为可能会导致难以调试的错误。

类型在包和加载环境之间不共享

您从 PackageImporter 导入的任何类都将是特定于该导入器的类的版本。例如:

from foo import MyClass

my_class_instance = MyClass()

with PackageExporter(f) as exporter:
    exporter.save_module("foo")

importer = PackageImporter(f)
imported_MyClass = importer.import_module("foo").MyClass

assert isinstance(my_class_instance, MyClass)  # 有效
assert isinstance(my_class_instance, imported_MyClass)  # 错误!

在这个例子中,MyClassimported_MyClass不同类型。在这个特定的例子中,MyClassimported_MyClass 具有完全相同的实现,所以你可能会认为将它们视为同一个类是可以的。但考虑一下 imported_MyClass 来自一个具有完全不同实现的旧包的情况——在这种情况下,将它们视为同一个类是不安全的。

在内部,每个导入器都有一个前缀,用于唯一标识类:

print(MyClass.__name__)  # 打印 "foo.MyClass"
print(imported_MyClass.__name__)  # 打印 .foo.MyClass

这意味着当其中一个参数来自一个包而另一个参数不是时,你不应该期望 isinstance 检查能够正常工作。如果你需要这个功能,可以考虑以下选项:

  • 进行鸭子类型(只是使用类,而不是显式检查它是否属于给定类型)。

  • 将打字关系明确作为类契约的一部分。例如,您可以添加一个属性标签 self.handler = "handle_me_this_way" 并让客户端代码检查 handler 的值,而不是直接检查类型。

如何torch.package保持包之间的隔离

每个PackageImporter实例都会为其模块和对象创建一个独立的、隔离的环境。包中的模块只能导入其他打包的模块,或标记为extern的模块。如果你使用多个PackageImporter实例来加载一个包,你将获得多个独立的环境,这些环境不会相互交互。

这是通过使用自定义导入器扩展Python的导入基础架构来实现的。PackageImporter提供了与importlib导入器相同的核心API;即,它实现了import_module__import__方法。

当你调用 PackageImporter.import_module()PackageImporter 将构造并返回一个新模块,就像系统导入器所做的那样。 然而,PackageImporter 会修补返回的模块,以使用 self(即那个 PackageImporter 实例)来满足未来的导入请求,通过在包中查找而不是搜索用户的 Python 环境。

名称修饰

为了避免混淆(“这是来自我的包的foo.bar对象,还是来自我的Python环境的对象?”),PackageImporter会对所有导入模块的__name____file__添加一个混淆前缀

对于__name__,一个类似torchvision.models.resnet18的名称会变成.torchvision.models.resnet18

对于 __file__,一个类似 torchvision/models/resnet18.py 的名称变为 .torchvision/modules/resnet18.py

名称混淆有助于避免不同包之间的模块名称无意中发生冲突,并通过使堆栈跟踪和打印语句更清晰地显示它们是否引用打包代码来帮助您调试。有关混淆的面向开发者的详细信息,请参阅torch/package/中的mangling.md

API参考

class torch.package.PackagingError(dependency_graph, debug=False)[源代码]

当导出包时出现问题时,会引发此异常。 PackageExporter 将尝试收集所有错误并一次性呈现给您。

class torch.package.EmptyMatchError[源代码]

这是一个异常,当一个mock或extern被标记为allow_empty=False,并且在打包过程中没有与任何模块匹配时,会抛出此异常。

class torch.package.PackageExporter(f, importer=<torch.package.importer._SysImporter object>, debug=False)[源代码]

导出器允许您将代码包、经过序列化的Python数据以及任意二进制和文本资源写入一个自包含的包中。

导入可以以一种密封的方式加载此代码,使得代码从包中加载,而不是从正常的Python导入系统加载。这允许打包PyTorch模型代码和数据,以便它可以在服务器上运行或在将来用于迁移学习。

包中包含的代码在创建时会从原始源文件逐个文件复制,并且文件格式是经过特殊组织的zip文件。包的未来用户可以解压缩包,并编辑代码以对其进行自定义修改。

包的导入器确保模块中的代码只能从包内部加载,除非使用 extern() 明确列出为外部的模块。 压缩包中的文件 extern_modules 列出了包外部依赖的所有模块。 这可以防止“隐式”依赖,即包在本地运行是因为它导入了本地安装的包,但在将包复制到另一台机器时失败。

当源代码被添加到包中时,导出器可以选择性地扫描它以查找更多的代码依赖项(dependencies=True)。它会查找导入语句,解析相对引用为合格的模块名称,并执行用户指定的操作(参见:extern()mock()intern())。

__init__(f, importer=<torch.package.importer._SysImporter object>, debug=False)[源代码]

创建一个导出器。

Parameters
  • f (联合[字符串, 路径, 二进制IO]) – 导出的位置。可以是一个包含文件名的 字符串/路径 对象,或者是一个二进制I/O对象。

  • 导入器 (联合[导入器, 序列[导入器]]) – 如果传递了一个导入器,则使用它来搜索模块。 如果传递了导入器的序列,将从中构造一个有序导入器

  • 调试 (布尔值) – 如果设置为True,将损坏模块的路径添加到PackagingErrors中。

add_dependency(module_name, dependencies=True)[源代码]

给定一个模块,根据用户指定的模式将其添加到依赖关系图中。

all_paths(src, dst)[源代码]
Return a dot representation of the subgraph

包含从 src 到 dst 的所有路径。

Returns

包含从 src 到 dst 的所有路径的点表示。 (https://graphviz.org/doc/info/lang.html)

Return type

str

close()[源代码]

将包写入文件系统。在调用 close() 之后进行的任何调用现在都无效。 建议使用资源保护语法代替:

with PackageExporter("file.zip") as e:
    ...
denied_modules()[源代码]

返回当前所有被拒绝的模块。

Returns

包含在此包中将被拒绝的模块名称的列表。

Return type

列表[字符串]

deny(include, *, exclude=())[源代码]

从包可以导入的模块列表中阻止名称与给定全局模式匹配的模块。如果发现任何匹配包的依赖关系,则会引发PackagingError

Parameters
  • 包含 (联合[列表[字符串], 字符串]) – 一个字符串,例如 "my_package.my_subpackage",或模块名称的字符串列表。这也可以是一个全局样式模式,如 mock() 中所述。

  • exclude (Union[List[str], str]) – 一个可选的模式,用于排除与包含字符串匹配的一些模式。

dependency_graph_string()[源代码]

返回包中依赖关系的有向图字符串表示。

Returns

包中依赖项的字符串表示形式。

Return type

str

extern(include, *, exclude=(), allow_empty=True)[源代码]

在包可以导入的外部模块列表中包含 module。 这将防止依赖发现将其保存在包中。导入器将直接从标准导入系统加载外部模块。 外部模块的代码也必须存在于加载包的过程中。

Parameters
  • 包含 (联合[列表[字符串], 字符串]) – 一个字符串,例如 "my_package.my_subpackage",或模块名称的字符串列表。这也可以是一个全局样式模式,如在 mock() 中所述。

  • exclude (Union[List[str], str]) – 一个可选的模式,用于排除与包含字符串匹配的一些模式。

  • allow_empty (bool) – 一个可选标志,指定此调用中指定的外部模块是否必须在打包期间匹配某些模块。如果添加了外部模块全局模式且allow_empty=False,并且在任何模块匹配该模式之前调用了close()(无论是显式调用还是通过__exit__),则会抛出异常。如果allow_empty=True,则不会抛出此类异常。

externed_modules()[源代码]

返回所有当前被外部化的模块。

Returns

包含将在本包中外部化的模块名称的列表。

Return type

列表[字符串]

get_rdeps(module_name)[源代码]

返回所有依赖于模块 module_name 的模块列表。

Returns

包含依赖于 module_name 的模块名称的列表。

Return type

列表[字符串]

get_unique_id()[源代码]

获取一个ID。此ID保证仅为此包发放一次。

Return type

str

intern(include, *, exclude=(), allow_empty=True)[源代码]

指定应打包的模块。模块必须匹配某些 intern 模式,才能被包含在包中并递归处理其依赖项。

Parameters
  • 包含 (联合[列表[字符串], 字符串]) – 一个字符串,例如“my_package.my_subpackage”,或模块名称的字符串列表。这也可以是一个全局样式模式,如mock()中所述。

  • exclude (Union[List[str], str]) – 一个可选的模式,用于排除与包含字符串匹配的一些模式。

  • allow_empty (bool) – 一个可选标志,指定此调用中指定的内部模块是否必须在打包期间匹配某些模块。如果添加了allow_empty=Falseintern模块全局模式,并且在任何模块匹配该模式之前调用了close()(无论是显式调用还是通过__exit__),则会抛出异常。如果allow_empty=True,则不会抛出此类异常。

interned_modules()[源代码]

返回当前所有被导入的模块。

Returns

包含将在此包中内部化的模块名称的列表。

Return type

列表[字符串]

mock(include, *, exclude=(), allow_empty=True)[源代码]

将一些必需的模块替换为模拟实现。模拟模块将为从其访问的任何属性返回一个假对象。因为我们逐文件复制,依赖解析有时会找到由模型文件导入但从未使用的文件(例如,自定义序列化代码或训练助手)。使用此函数在不修改原始代码的情况下模拟此功能。

Parameters
  • include (Union[List[str], str]) –

    一个字符串,例如 "my_package.my_subpackage",或模块名称的字符串列表,用于被模拟的模块。字符串也可以是glob风格的模式字符串,可能匹配多个模块。任何与此模式字符串匹配的必需依赖项都将被自动模拟。

    示例 :

    'torch.**' – 匹配 torch 及其所有子模块,例如 'torch.nn''torch.nn.functional'

    'torch.*' – 匹配 'torch.nn''torch.functional',但不匹配 'torch.nn.functional'

  • exclude (Union[List[str], str]) – 一个可选的模式,排除与包含字符串匹配的一些模式。 例如:include='torch.**', exclude='torch.foo' 将模拟所有 torch 包,除了 'torch.foo', 默认值是 []

  • allow_empty (bool) – 一个可选标志,指定此调用中指定的模拟实现是否必须在打包期间匹配到某个模块。如果添加了一个模拟,并且allow_empty=False,并且在调用close()(无论是显式调用还是通过__exit__)时,模拟未匹配到正在导出的包中使用的模块,则会抛出异常。如果allow_empty=True,则不会抛出此类异常。

mocked_modules()[源代码]

返回所有当前被模拟的模块。

Returns

包含将在本包中被模拟的模块名称的列表。

Return type

列表[字符串]

register_extern_hook(hook)[源代码]

在导出器上注册一个外部钩子。

每次模块匹配到 extern() 模式时,钩子将被调用。 它应具有以下签名:

hook(exporter: PackageExporter, module_name: str) -> None

钩子将按照注册顺序被调用。

Returns

一个可以用于通过调用handle.remove()来移除添加的钩子的句柄。

Return type

torch.utils.hooks.RemovableHandle

register_intern_hook(hook)[源代码]

在导出器上注册一个内部钩子。

每次模块与 intern() 模式匹配时,钩子将被调用。 它应具有以下签名:

hook(exporter: PackageExporter, module_name: str) -> None

钩子将按照注册顺序被调用。

Returns

一个可以用于通过调用handle.remove()来移除添加的钩子的句柄。

Return type

torch.utils.hooks.RemovableHandle

register_mock_hook(hook)[源代码]

在导出器上注册一个模拟钩子。

每次模块与 mock() 模式匹配时,钩子将被调用。 它应具有以下签名:

hook(exporter: PackageExporter, module_name: str) -> None

钩子将按照注册顺序被调用。

Returns

一个可以用于通过调用handle.remove()来移除添加的钩子的句柄。

Return type

torch.utils.hooks.RemovableHandle

save_binary(package, resource, binary)[源代码]

将原始字节保存到包中。

Parameters
  • (字符串) – 此资源应放置的模块包的名称(例如 "my_package.my_subpackage")。

  • 资源 (str) – 资源的唯一名称,用于标识和加载。

  • 二进制 (字符串) – 要保存的数据。

save_module(module_name, dependencies=True)[源代码]

module的代码保存到包中。使用importers路径解析模块对象,然后使用其__file__属性查找源代码。

Parameters
  • module_name (str) – 例如 my_package.my_subpackage,代码将被保存以提供此包的代码。

  • 依赖项 (布尔值, 可选) – 如果 True,我们扫描源代码以查找依赖项。

save_pickle(package, resource, obj, dependencies=True, pickle_protocol=3)[源代码]

使用 pickle 将 Python 对象保存到归档文件中。等同于 torch.save(),但保存到归档文件中而不是独立的文件中。标准的 pickle 不会保存代码,只会保存对象。如果 dependencies 为 true,此方法还将扫描需要哪些模块来重建 pickle 对象,并保存相关代码。

为了能够保存一个对象,其中 type(obj).__name__my_module.MyObjectmy_module.MyObject 必须根据 importer 顺序解析为对象的类。当保存之前已经打包的对象时, 导入器的 import_module 方法需要存在于 importer 列表中,以便实现此功能。

Parameters
  • (字符串) – 此资源应放入的模块包的名称(例如 "my_package.my_subpackage")。

  • 资源 (str) – 资源的唯一名称,用于标识和加载。

  • obj (任意) – 要保存的对象,必须是可序列化的。

  • 依赖项 (bool, 可选) – 如果 True,我们扫描源代码以查找依赖项。

save_source_file(module_name, file_or_directory, dependencies=True)[源代码]

将本地文件系统中的 file_or_directory 添加到源包中,以提供 module_name 的代码。

Parameters
  • module_name (str) – 例如 "my_package.my_subpackage",代码将被保存以提供此包的代码。

  • 文件或目录 (字符串) – 代码文件或目录的路径。当为目录时,目录中的所有python文件将使用save_source_file()递归复制。如果文件名为"/__init__.py",则该代码将被视为包。

  • 依赖项 (bool, 可选) – 如果 True,我们扫描源代码以查找依赖项。

save_source_string(module_name, src, is_package=False, dependencies=True)[源代码]

src 作为导出包中 module_name 的源代码。

Parameters
  • module_name (str) – 例如 my_package.my_subpackage,代码将被保存以提供此包的代码。

  • src (str) – 要为此包保存的Python源代码。

  • is_package (布尔值, 可选) – 如果True,此模块被视为一个包。包允许拥有子模块(例如my_package.my_subpackage.my_subsubpackage),并且资源可以保存在其中。默认为False

  • 依赖项 (布尔值, 可选) – 如果 True,我们扫描源代码以查找依赖项。

save_text(package, resource, text)[源代码]

将文本数据保存到包中。

Parameters
  • (字符串) – 此资源应放置的模块包的名称(例如 "my_package.my_subpackage")。

  • 资源 (str) – 资源的唯一名称,用于标识和加载。

  • 文本 (str) – 要保存的内容。

class torch.package.PackageImporter(file_or_buffer, module_allowed=<function PackageImporter.<lambda>>)[源代码]

导入器允许您加载由PackageExporter编写的代码。代码以一种密封的方式加载,使用包中的文件而不是正常的Python导入系统。这使得可以打包PyTorch模型代码和数据,以便在服务器上运行或在将来用于迁移学习。

包的导入器确保模块中的代码只能从包内部加载,除非在导出时明确列出为外部的模块。压缩包中的文件 extern_modules 列出了包外部依赖的所有模块。这可以防止“隐式”依赖,即包在本地运行是因为它导入了本地安装的包,但在将包复制到另一台机器时失败。

__init__(file_or_buffer, module_allowed=<function PackageImporter.<lambda>>)[源代码]

打开 file_or_buffer 以进行导入。这将检查导入的包是否仅需要 module_allowed 允许的模块

Parameters
  • file_or_buffer (Union[str, PyTorchFileReader, PathLike, BinaryIO]) – 一个类文件对象(必须实现 read()readline()tell()seek()),一个字符串,或一个包含文件名的 os.PathLike 对象。

  • module_allowed (可调用对象[[str], bool], 可选) – 用于确定是否允许外部提供的模块的方法。可以用来确保加载的包不依赖于服务器不支持的模块。默认为允许任何内容。

Raises

ImportError – 如果包将使用不允许的模块。

file_structure(*, include='**', exclude=())[源代码]

返回包的zip文件的文件结构表示。

Parameters
  • 包含 (联合[列表[字符串], 字符串]) – 一个可选的字符串,例如 "my_package.my_subpackage",或可选的字符串列表,用于指定要包含在zip文件表示中的文件名。这也可以是一个glob风格的模式,如在PackageExporter.mock()中所述

  • exclude (Union[List[str], str]) – 一个可选的模式,排除名称与该模式匹配的文件。

Returns

目录

Return type

目录

id()[源代码]

返回内部标识符,torch.package 使用该标识符来区分 PackageImporter 实例。 看起来像:

<torch_package_0>
import_module(name, package=None)[源代码]

如果模块尚未加载,则从包中加载模块,然后返回该模块。模块是本地加载的,并且会出现在 self.modules 而不是 sys.modules 中。

Parameters
  • 名称 (str) – 要加载的模块的完全限定名称。

  • ([类型], 可选) – 未使用,但存在以匹配 importlib.import_module 的签名。默认为 None

Returns

已加载(可能已经)的模块。

Return type

types.ModuleType

load_binary(package, resource)[源代码]

加载原始字节。

Parameters
  • (字符串) – 模块包的名称(例如 "my_package.my_subpackage")。

  • 资源 (str) – 资源的唯一名称。

Returns

加载的数据。

Return type

字节

load_pickle(package, resource, map_location=None)[源代码]

从包中解封资源,加载构建对象所需的任何模块,使用import_module()

Parameters
  • (字符串) – 模块包的名称(例如 "my_package.my_subpackage")。

  • 资源 (str) – 资源的唯一名称。

  • map_location – 传递给 torch.load 以确定张量如何映射到设备。默认为 None

Returns

未解封的对象。

Return type

任意

load_text(package, resource, encoding='utf-8', errors='strict')[源代码]

加载一个字符串。

Parameters
  • (字符串) – 模块包的名称(例如 "my_package.my_subpackage")。

  • 资源 (str) – 资源的唯一名称。

  • 编码 (str, 可选) – 传递给 decode。默认为 'utf-8'

  • 错误 (字符串, 可选) – 传递给 decode。默认为 'strict'

Returns

加载的文本。

Return type

str

python_version()[源代码]

返回用于创建此包的 Python 版本。

注意:此功能是实验性的,并且不具备前向兼容性。计划是稍后将其移入锁定文件中。

Returns

Optional[str] 一个Python版本,例如3.8.9,如果此包没有存储版本,则为None

class torch.package.Directory(name, is_dir)[源代码]

文件结构的表示。组织为具有其目录子列表的目录节点。通过调用PackageImporter.file_structure()来创建包的目录。

has_file(filename)[源代码]

检查文件是否存在于目录中。

Parameters

文件名 (str) – 要搜索的文件路径。

Returns

如果一个 Directory 包含指定的文件。

Return type

bool

优云智算