评价此页

torch.package#

创建于: 2025 年 6 月 10 日 | 最后更新于: 2025 年 6 月 10 日

torch.package 增加了对包含 Artifacts 和任意 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")
    # edit file_bytes in some way
    myzip.writestr("torchvision/models/resnet.py", new_file_bytes)
  • vim 能够原生读取 ZIP 存档。您甚至可以编辑文件并使用 :write 将它们写回存档!

# add this to your .vimrc to treat `*.pt` files as zip files
au BufReadCmd *.pt call zip#Browse(expand("<amatch>"))

~ vi my_package.pt

使用 file_structure() API#

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

Directory 对象本身是可直接打印的,并将打印出文件树表示。要过滤返回的内容,请使用 glob 样式的 includeexclude 过滤参数。

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

importer = PackageImporter('my_package.pt')
# can limit printed items with include/exclude args
print(importer.file_structure(include=["**/utils.py", "**/*.pkl"], exclude="**/*.storage"))
print(importer.file_structure()) # will print out all files

输出

# filtered with glob pattern:
#    include=["**/utils.py", "**/*.pkl"], exclude="**/*.storage"
─── my_package.pt
    ├── models
    │   └── model_1.pkl
    └── torchvision
        └── models
            └── utils.py

# all files
─── 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,而您想知道为什么您的 PackageExporter 会将 foo 作为依赖项。

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

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

如果您只想查看您的 :class:PackageExporter 的整个依赖图,您可以使用 PackageExporter.dependency_graph_string()

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

PackageExporter 暴露了三个方法:save_picklesave_textsave_binary,它们允许您将 Python 对象、文本和二进制数据保存到包中。

with torch.PackageExporter("package.pt") as exporter:
    # Pickles the object and saves to `my_resources/tensor.pkl` in the archive.
    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 [Example of customizing how class Foo is packaged]
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):
        """
        Called by ``torch.package.PackageExporter``'s Pickler's ``persistent_id`` when
        saving an instance of this object. This method should do the work to save this
        object inside of the ``torch.package`` archive.

        Returns function w/ arguments to load the object from a
        ``torch.package.PackageImporter``'s Pickler's ``persistent_load`` function.
        """

        # use this pattern to ensure no naming conflicts with normal dependencies,
        # anything saved under this module name shouldn't conflict with other
        # items in the package
        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)

        # returns de-packaging function w/ arguments to invoke with
        return (unpackage_foo, (generated_module_name, time_exported,))


def unpackage_foo(
    importer: PackageImporter, generated_module_name: str, time_exported: float
) -> Foo:
    """
    Called by ``torch.package.PackageImporter``'s Pickler's ``persistent_load`` function
    when depickling a Foo object.
    Performs work of loading and returning a Foo instance from a ``torch.package`` archive.
    """
    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

# example of saving instances of class 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:
    # save as normal, no extra work necessary
    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}")
# output of running above script
─── foo_package
    ├── foo-generated
    │   ├── _0
    │   │   └── foo.txt
    │   └── _1
    │       └── foo.txt
    ├── foo_collection
    │   ├── foo1.pkl
    │   └── foo2.pkl
    └── foo.py

foo_1 string: 'foo_1 initial string, with reduction modification!'
foo_1 export time: 9857706.650140837
foo_1 import time: 9857706.652698385

在我的源代码中测试它是在包内执行还是在包外执行?#

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

# In foo/bar.py:

if "__torch_package__" in dir():  # true if the code is being loaded from a package
    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())  # False

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

警告:通常情况下,让代码根据是否打包而表现出不同的行为是不好的做法。这可能导致难以调试的问题,这些问题对您导入代码的方式很敏感。如果您的包旨在被广泛使用,请考虑重构您的代码,使其无论加载方式如何都能表现出相同的行为。

将代码修补到包中?#

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

with PackageExporter(f) as exporter:
    # Save the my_module.foo available in your current Python environment.
    exporter.save_module("my_module.foo")

    # This saves the provided string to my_module/foo.py in the package archive.
    # It will override the my_module.foo that was previously saved.
    exporter.save_source_string("my_module.foo", textwrap.dedent(
        """\
        def my_function():
            print('hello world')
        """
    ))

    # If you want to treat my_module.bar as a package
    # (e.g. save to `my_module/bar/__init__.py` instead of `my_module/bar.py)
    # pass 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()  # prints 'hello world'

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

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

with PackageExporter(f) as exporter:
    # saves text to my_resource/a.txt in the archive
    exporter.save_text("my_resource", "a.txt", "hello world!")
    # saves the tensor to my_pickle/obj.pkl
    exporter.save_pickle("my_pickle", "obj.pkl", torch.ones(2, 2))

    # see below for module contents
    exporter.save_module("foo")
    exporter.save_module("bar")

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

# foo.py:
import importlib.resources
import my_resource

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

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

# bar.py:
import torch_package_importer # this is the PackageImporter that imported this module.

# Prints "hello world!", equivalent to importlib.resources.read_text
def get_my_resource():
    return torch_package_importer.load_text("my_resource", "a.txt")

# You also do things that the importlib.resources API does not support, like loading
# a pickled object from the package.
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) # str is from stdlib, so this will return False

重新导出导入的对象?#

要重新导出先前由 PackageImporter 导入的对象,您必须使新的 PackageExporter 了解原始的 PackageImporter,以便它能够找到您对象的依赖项的源代码。

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

# re-export obj in a new package
with PackageExporter(f2, importer=(importer, sys_importer)) as exporter:
    exporter.save_pickle("model", "model.pkl", obj)

打包 TorchScript 模块?#

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

# save TorchScript just like any other object
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)
# load as normal
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  # All framework-specific data is stored here.
│   │      # It's named to avoid conflicts with user-serialized code.
│   ├── 94286146172688.storage  # tensor data
│   ├── 94286146172784.storage
│   ├── extern_modules  # text file with names of extern modules (e.g. 'torch')
│   ├── version         # version metadata
│   ├── ...
├── model  # the pickled model
│   └── model.pkl
└── torchvision  # all code dependencies are captured as source files
    └── models
        ├── resnet.py
        └── utils.py

框架文件#

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

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

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

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

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

.data
├── 94286146172688.storage
├── 94286146172784.storage
├── extern_modules
├── version
├── ...

用户文件#

存档中的所有其他文件都是由用户放置的。布局与 Python 的常规包相同。有关 Python 打包工作原理的更深入了解,请参阅此文(它略有过时,因此请与Python 参考文档仔细核对实现细节。

<package root>
├── model  # the pickled model
│   └── model.pkl
├── another_package
│   ├── __init__.py
│   ├── foo.txt         # a resource file , see importlib.resources
│   └── ...
└── torchvision
    └── models
        ├── resnet.py   # torchvision.models.resnet
        └── utils.py    # torchvision.models.utils

《code class="docutils literal notranslate">torch.package 如何查找您的代码依赖项#

分析对象的依赖项#

当您发出 save_pickle(obj, ...) 调用时,PackageExporter 将像往常一样对对象进行 pickle。然后,它使用 pickletools 标准库模块来解析 pickle 字节码。

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

GLOBAL 'torchvision.models.resnet Resnet`

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

分析模块的依赖项#

当 Python 模块被识别为依赖项时,`torch.package` 会遍历模块的 Python AST 表示形式,并查找具有完全支持的标准形式的 import 语句:`from x import y`、`import z`、`from w import v as u` 等等。当遇到这些 import 语句之一时,`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"`)或 glob(如 `"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 的模块时,PackageImporter 将使用默认的 Python 导入器来查找该模块,就像您执行 `importlib.import_module("my_externed_module")` 一样。如果找不到该模块,则会引发错误。

这样,您就可以在包内依赖 `numpy` 和 `scipy` 等第三方库,而无需打包它们。

警告:如果任何外部库发生向后不兼容的更改,您的包可能会加载失败。如果您需要包的长期可重现性,请尽量限制对 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.nn` 和 `torch.nn.functional`。

  • `torch.*`:匹配 `torch.nn` 或 `torch.functional`,但不匹配 `torch.nn.functional` 或 `torch`。

  • `torch*.**`:匹配 `torch`、`torchvision` 及其所有子模块。

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

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

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

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

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

如果模块匹配任何排除模式,它将不会匹配此操作。在此示例中,我们 mock 了除 `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)  # works
assert isinstance(my_class_instance, imported_MyClass)  # ERROR!

在此示例中,`MyClass` 和 `imported_MyClass` *不是同一类型*。在此特定示例中,`MyClass` 和 `imported_MyClass` 具有完全相同的实现,因此您可能认为将它们视为同一类是可以的。但请考虑 `imported_MyClass` 来自一个具有完全不同 `MyClass` 实现的旧包的情况——在这种情况下,将它们视为同一类是不安全的。

在底层,每个导入器都有一个前缀,允许它唯一地标识类:

print(MyClass.__name__)  # prints "foo.MyClass"
print(imported_MyClass.__name__)  # prints <torch_package_0>.foo.MyClass

这意味着您不应期望在参数之一来自包而另一个不来自包时 `isinstance` 检查能够工作。如果您需要此功能,请考虑以下选项:

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

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

《code class="docutils literal notranslate">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)[source]#

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

class torch.package.EmptyMatchError[source]#

当 mock 或 extern 被标记为 `allow_empty=False` 并且在打包过程中没有匹配到任何模块时,将抛出此异常。

class torch.package.PackageExporter(f, importer=<torch.package.importer._SysImporter object>, debug=False)[source]#

导出器允许您将代码包、pickled Python 数据以及任意二进制和文本资源写入一个独立的包。

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

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

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

当源代码被添加到包时,导出器可以选择扫描它以查找进一步的代码依赖项(`dependencies=True`)。它查找 import 语句,解析相对引用到限定模块名称,并执行用户指定的行为(参见:extern()mock()intern())。

__init__(f, importer=<torch.package.importer._SysImporter object>, debug=False)[source]#

创建一个导出器。

参数
  • f (Union[str, PathLike[str], IO[bytes]]) – 导出到的位置。可以是包含文件名或二进制 I/O 对象的字符串/Path 对象。

  • importer (Union[Importer, Sequence[Importer]]) – 如果传递单个 Importer,则使用该 Importer 搜索模块。如果传递 Importer 序列,将根据它们构建一个 `OrderedImporter`。

  • debug (bool) – 如果设置为 True,则将损坏模块的路径添加到 PackagingErrors 中。

add_dependency(module_name, dependencies=True)[source]#

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

all_paths(src, dst)[source]#
返回子图的点表示

该子图具有从 src 到 dst 的所有路径。

返回

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

返回类型

str

close()[source]#

将包写入文件系统。在此之后的所有调用 close() 现在无效。首选使用资源保护程序语法。

with PackageExporter("file.zip") as e:
    ...
denied_modules()[source]#

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

返回

一个列表,其中包含将在该包中被拒绝的模块的名称。

返回类型

list[str]

deny(include, *, exclude=())[source]#

阻止与模块列表中的 given glob 模式匹配的模块被包导入。如果找到对任何匹配包的依赖,将引发 PackagingError

参数
  • include (Union[List[str], str]) – 一个字符串,例如 "my_package.my_subpackage",或者一个模块名称字符串列表,这些模块将被外部化。这也可以是 glob 风格的模式,如 mock() 中所述。

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

dependency_graph_string()[source]#

返回包中依赖关系的 digraph 字符串表示。

返回

包中依赖关系的字符串表示。

返回类型

str

extern(include, *, exclude=(), allow_empty=True)[source]#

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

参数
  • include (Union[List[str], str]) – 一个字符串,例如 "my_package.my_subpackage",或者一个模块名称字符串列表,这些模块将被外部化。这也可以是 glob 风格的模式,如 mock() 中所述。

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

  • allow_empty (bool) – 一个可选标志,指定对此 extern 方法的调用所指定的外部模块是否必须在打包过程中匹配到某个模块。如果通过 allow_empty=False 添加了外部模块 glob 模式,并且在任何模块匹配该模式之前调用了 close()(显式或通过 __exit__),则会抛出异常。如果 allow_empty=True,则不会抛出此类异常。

externed_modules()[source]#

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

返回

一个列表,其中包含将在该包中外部化的模块的名称。

返回类型

list[str]

get_rdeps(module_name)[source]#

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

返回

一个列表,其中包含所有依赖于 module_name 的模块的名称。

返回类型

list[str]

get_unique_id()[source]#

获取一个 ID。此 ID 保证仅为此包分配一次。

返回类型

str

intern(include, *, exclude=(), allow_empty=True)[source]#

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

参数
  • include (Union[List[str], str]) – 一个字符串,例如 “my_package.my_subpackage”,或一个模块名称字符串列表,这些模块将被外部化。这也可以是 glob 风格的模式,如 mock() 中所述。

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

  • allow_empty (bool) – 一个可选标志,指定对此 intern 方法的调用所指定的内部模块是否必须在打包过程中匹配到某个模块。如果通过 allow_empty=False 添加了内部模块 glob 模式,并且在任何模块匹配该模式之前调用了 close()(显式或通过 __exit__),则会抛出异常。如果 allow_empty=True,则不会抛出此类异常。

interned_modules()[source]#

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

返回

一个列表,其中包含将在该包中内部化的模块的名称。

返回类型

list[str]

mock(include, *, exclude=(), allow_empty=True)[source]#

用 mock 实现替换一些必需的模块。Mocked 模块将为从中访问的任何属性返回一个假对象。因为我们是逐个文件复制,所以依赖关系解析有时会找到被模型文件导入但其功能从未使用过的文件(例如,自定义序列化代码或训练助手)。使用此函数来 mock 出此功能,而无需修改原始代码。

参数
  • include (Union[List[str], str]) –

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

    示例

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

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

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

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

mocked_modules()[source]#

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

返回

一个列表,其中包含将在该包中被 mock 的模块的名称。

返回类型

list[str]

register_extern_hook(hook)[source]#

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

每次模块匹配 extern() 模式时,都会调用该钩子。它应该具有以下签名

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

钩子将按注册顺序调用。

返回

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

返回类型

torch.utils.hooks.RemovableHandle

register_intern_hook(hook)[source]#

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

每次模块匹配 intern() 模式时,都会调用该钩子。它应该具有以下签名

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

钩子将按注册顺序调用。

返回

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

返回类型

torch.utils.hooks.RemovableHandle

register_mock_hook(hook)[source]#

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

每次模块匹配 mock() 模式时,都会调用该钩子。它应该具有以下签名

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

钩子将按注册顺序调用。

返回

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

返回类型

torch.utils.hooks.RemovableHandle

save_binary(package, resource, binary)[source]#

将原始字节保存到包中。

参数
  • package (str) – 该资源应归属的模块包的名称(例如 "my_package.my_subpackage")。

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

  • binary (str) – 要保存的数据。

save_module(module_name, dependencies=True)[source]#

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

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

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

save_pickle(package, resource, obj, dependencies=True, pickle_protocol=3)[source]#

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

为了能够保存一个 type(obj).__name__my_module.MyObject 的对象,my_module.MyObject 必须根据 importer 顺序解析到对象的类。在保存先前已打包的对象时,导入器的 import_module 方法需要存在于 importer 列表中才能工作。

参数
  • package (str) – 该资源应归属的模块包的名称(例如 "my_package.my_subpackage")。

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

  • obj (Any) – 要保存的对象,必须是可 picklable 的。

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

save_source_file(module_name, file_or_directory, dependencies=True)[source]#

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

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

  • file_or_directory (str) – 代码文件或目录的路径。当是目录时,目录中的所有 python 文件都会被递归复制,使用 save_source_file()。如果文件名为 "/__init__.py",则代码被视为一个包。

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

save_source_string(module_name, src, is_package=False, dependencies=True)[source]#

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

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

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

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

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

save_text(package, resource, text)[source]#

将文本数据保存到包中。

参数
  • package (str) – 该资源应归属的模块包的名称(例如 "my_package.my_subpackage")。

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

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

class torch.package.PackageImporter(file_or_buffer, module_allowed=<function PackageImporter.<lambda>>)[source]#

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

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

__init__(file_or_buffer, module_allowed=<function PackageImporter.<lambda>>)[source]#

打开 file_or_buffer 以进行导入。这会检查导入的包是否只使用了 module_allowed 允许的模块。

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

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

引发

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

file_structure(*, include='**', exclude=())[source]#

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

参数
  • include (Union[List[str], str]) – 一个可选字符串,例如 "my_package.my_subpackage",或者一个文件名字符串列表,这些文件将被包含在 zip 文件表示中。这也可以是 glob 风格的模式,如 PackageExporter.mock() 中所述。

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

返回

目录

返回类型

目录

id()[source]#

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

<torch_package_0>
import_module(name, package=None)[source]#

如果模块尚未加载,则从包中加载并返回该模块。模块在导入器本地加载,并将出现在 self.modules 而不是 sys.modules 中。

参数
  • name (str) – 要加载的模块的全限定名。

  • package ([type], optional) – 未使用,但存在以匹配 importlib.import_module 的签名。默认为 None

返回

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

返回类型

types.ModuleType

load_binary(package, resource)[源代码]#

加载原始字节。

参数
  • package (str) – 模块包的名称(例如,"my_package.my_subpackage")。

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

返回

加载的数据。

返回类型

字节

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

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

参数
  • package (str) – 模块包的名称(例如,"my_package.my_subpackage")。

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

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

返回

解序列化的对象。

返回类型

任何

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

加载字符串。

参数
  • package (str) – 模块包的名称(例如,"my_package.my_subpackage")。

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

  • encoding (str, optional) – 传递给 decode。默认为 'utf-8'

  • errors (str, optional) – 传递给 decode。默认为 'strict'

返回

加载的文本。

返回类型

str

python_version()[源代码]#

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

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

返回

Optional[str] Python 版本,例如 3.8.9,如果此包未存储版本,则为 None

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

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

has_file(filename)[源代码]#

检查文件中是否存在文件。

参数

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

返回

如果 Directory 包含指定的文件。

返回类型

布尔值