快捷方式

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")
    # 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,您想知道为什么您的 PackageExporterfoo 作为依赖项拉入。

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

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

如果您只想查看 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 标准。但是,也可以从打包的代码内部访问父 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.package)。

目前,.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

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

分析对象的依赖项

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

在 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")或 glob(如 "foo.**")。您可以使用 PackageExporter 上的方法将模式与操作关联,例如

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

如果模块匹配某个模式,则会对其应用相应的操作。对于给定的模块,将按照模式定义的顺序检查模式,并执行第一个匹配的操作。

intern

如果一个模块被 intern,它将被放置到包中。

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

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

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

extern

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

在包导入时,当打包的代码尝试导入一个 extern 的模块时,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*.**:匹配 torchtorchvision 及其所有子模块

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

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)  # works
assert isinstance(my_class_instance, imported_MyClass)  # ERROR!

在此示例中,MyClassimported_MyClass 不是相同的类型。在这个具体的例子中,MyClassimported_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 的值,而不是直接检查类型。

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 这样的名称变为 <torch_package_0>.torchvision.models.resnet18

对于 __file__,像 torchvision/models/resnet18.py 这样的名称变为 <torch_package_0>.torchvision/modules/resnet18.py

名称修改有助于避免不同包之间模块名称的意外混淆,并通过使堆栈跟踪和打印语句更清楚地显示它们是指打包代码还是非打包代码来帮助您调试。有关名称修改的面向开发人员的详细信息,请查阅 torch/package/ 中的 mangling.md

API 参考

class torch.package.PackagingError(dependency_graph, debug=False)[source][source]

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

class torch.package.EmptyMatchError[source][source]

当标记为 allow_empty=False 的 mock 或 extern 在打包期间未与任何模块匹配时,会抛出此异常。

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

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

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

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

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

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

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

创建一个导出器。

参数
  • f (Union[str, Path, BinaryIO]) – 要导出的位置。可以是包含文件名或二进制 I/O 对象的 string/Path 对象。

  • importer (Union[Importer, Sequence[Importer]]) – 如果传递单个 Importer,则使用它来搜索模块。如果传递一系列导入器,则将从中构建 OrderedImporter

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

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

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

all_paths(src, dst)[source][source]
返回子图的 dot 表示形式

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

返回

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

返回类型

str

close()[source][source]

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

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

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

返回

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

返回类型

List[str]

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

阻止名称与给定 glob 模式匹配的模块从包可以导入的模块列表中导入。如果找到对任何匹配包的依赖项,则会引发 PackagingError

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

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

dependency_graph_string()[source][source]

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

返回

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

返回类型

str

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

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

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

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

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

externed_modules()[source][source]

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

返回

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

返回类型

List[str]

get_rdeps(module_name)[source][source]

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

返回

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

返回类型

List[str]

get_unique_id()[source][source]

获取一个 ID。此 ID 保证对于此包仅分发一次。

返回类型

str

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

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

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

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

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

interned_modules()[source][source]

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

返回

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

返回类型

List[str]

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

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

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

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

    示例

    'torch.**' – 匹配 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' 将模拟除 'torch.foo' 之外的所有 torch 包,默认值: 为 []

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

mocked_modules()[source][source]

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

返回

一个列表,其中包含将在此包中模拟的模块的名称。

返回类型

List[str]

register_extern_hook(hook)[source][source]

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

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

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

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

返回

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

返回类型

torch.utils.hooks.RemovableHandle

register_intern_hook(hook)[source][source]

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

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

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

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

返回

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

返回类型

torch.utils.hooks.RemovableHandle

register_mock_hook(hook)[source][source]

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

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

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

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

返回

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

返回类型

torch.utils.hooks.RemovableHandle

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

将原始字节保存到包中。

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

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

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

save_module(module_name, dependencies=True)[source][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][source]

使用 pickle 将 Python 对象保存到归档文件中。等效于 torch.save(),但保存到归档文件中而不是独立文件中。标准 pickle 不保存代码,仅保存对象。如果 dependencies 为 true,则此方法还将扫描 pickle 序列化的对象,以查找重建它们所需的模块并保存相关代码。

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

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

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

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

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

save_source_file(module_name, file_or_directory, dependencies=True)[source][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][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][source]

将文本数据保存到包中。

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

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

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

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

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

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

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

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

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

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

Raises

ImportError – 如果包将使用不允许的模块,则引发此错误。

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

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

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

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

返回

Directory

返回类型

Directory

id()[source][source]

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

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

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

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

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

返回

(可能已加载的)模块。

返回类型

types.ModuleType

load_binary(package, resource)[source][source]

加载原始字节。

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

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

返回

加载的数据。

返回类型

bytes

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

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

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

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

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

返回

反序列化的对象。

返回类型

Any

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

加载字符串。

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

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

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

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

返回

加载的文本。

返回类型

str

python_version()[源代码][源代码]

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

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

返回

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

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

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

has_file(filename)[源代码][源代码]

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

参数

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

返回

如果 Directory 包含指定的文件。

返回类型

bool

文档

访问 PyTorch 的综合开发者文档

查看文档

教程

获取面向初学者和高级开发者的深入教程

查看教程

资源

查找开发资源并获取您的问题解答

查看资源