• 文档 >
  • torch.package
快捷键

torch.package

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

本文档包含教程、如何操作指南、解释和 API 参考,这些内容将帮助您了解 torch.package 并学习如何使用它。

警告

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

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

如需更多信息,请查阅 pickle 模块的文档。

教程 §

打包您的第一个模型 §

在 Colab 上有一个教程,指导您如何打包和解包一个简单的模型。完成此练习后,您将熟悉创建和使用 Torch 软件包的基本 API。

我该如何……

看看包裹里面有什么?

将包裹当作 ZIP 存档来处理

torch.package 的容器格式是 ZIP,因此任何可以处理标准 ZIP 文件的工具都可以用来探索其内容。与 ZIP 文件交互的一些常见方法:

  • 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() 方法将返回一个显示 srcfoo 之间所有依赖路径的 DOT 格式图形。

如果你只想查看你的 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")

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

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

步骤:

  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() 函数。注意:如果一个对象来自包,但其定义来自标记为 externstdlib 的模块,则此检查将返回 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 格式概述 ¶

A torch.package 文件是一个使用 .pt 扩展名的 ZIP 归档。在 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

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

分析对象的依赖项 ¶

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

在 pickle 中,一个对象会与其描述对象类型实现位置的 GLOBAL 操作码一起保存,如下所示:

GLOBAL 'torchvision.models.resnet Resnet`

依赖解析器会收集所有 GLOBAL 操作并将它们标记为已序列化对象的依赖项。有关 pickling 和 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 -ed,它将被放入包中。

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

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

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

extern

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

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

这样,您可以在您的软件包内部依赖第三方库,如 numpyscipy ,而无需将它们打包。

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

mock

如果一个模块被 mock -ed,它将不会被打包。相反,将打包一个存根模块来替代它。存根模块将允许您从中检索对象(这样 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 及其子模块之外的所有模块。

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

锋利的边缘

在你的模块中避免全局状态

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 环境中搜索。

Mangling

为了避免混淆(“这个 foo.bar 对象是我的包中的,还是我的 Python 环境中的?”), PackageImporter 对所有导入的模块的 __name____file__ 进行修改,通过给它们添加一个 mangle 前缀。

对于 __name__ ,一个像 torchvision.models.resnet18 这样的名字变成 <torch_package_0>.torchvision.models.resnet18

对于 __file__ ,一个像 torchvision/models/resnet18.py 这样的名字变成 <torch_package_0>.torchvision/modules/resnet18.py

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

API 参考 ¶

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

当导出包存在问题时会引发此异常。 PackageExporter 将尝试收集所有错误并一次性向您展示。

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

这是一个在打包过程中,当模拟或外部标记为 allow_empty=False 且未与任何模块匹配时抛出的异常。

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

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

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

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

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

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

__init__(f, importer=, debug=False)[源代码][源代码] ¶

创建导出器。

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

  • importer (Union[Importer, Sequence[Importer]]) – 如果传递单个 Importer,则使用该 Importer 搜索模块。如果传递一系列导入器,则将它们组合成一个 OrderedImporter

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

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

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

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

包含从源到目的地的所有路径。

返回:

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

返回类型:

str

close()[来源][来源] ¶

将包写入文件系统。在 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])- 一个可选模式,用于排除与 include 字符串匹配的一些模式。

dependency_graph_string()[source][source]

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

返回:

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

返回类型:

str

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

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

参数:
  • 包含(联合[List[str], str])- 字符串,例如 "my_package.my_subpackage" ,或要外部化的模块名称的字符串列表。这也可以是 glob 风格的模式,如 mock() 中所述。

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

  • allow_empty(布尔值)- 一个可选的标志,指定通过此 extern 方法调用的外部化模块是否必须在打包期间匹配到某些模块。如果在添加 allow_empty=False 的外部模块 glob 模式后,并且在此调用 close() (无论是显式调用还是通过 __exit__ )之前没有任何模块匹配该模式,将抛出异常。如果 allow_empty=True ,则不会抛出此类异常。

externed_modules()[源][源] ¶

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

返回:

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

返回类型:

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])- 一个可选模式,用于排除与 include 字符串匹配的一些模式。

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

interned_modules()[source][source]

返回所有当前已内嵌的模块。

返回:

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

返回类型:

list[str]

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

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

参数:
  • 包含(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 包,除了 'torch.foo' ,默认为 []

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

模拟的模块()[源][源] ¶

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

返回:

包含将要在此包中模拟的模块名称的列表。

返回类型:

list[str]

register_extern_hook(hook)[源][源] ¶

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

每当模块与 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)[源代码][源代码] ¶

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

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

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

插件将按照注册顺序被调用。

返回:

可以通过调用 handle.remove() 来移除已添加的插件。

返回类型:

torch.utils.hooks.RemovableHandle

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

将原始字节保存到包中。

参数:
  • 包(字符串)- 应将此资源放入的模块包名称(例如 "my_package.my_subpackage" )。

  • 资源(字符串)- 资源的唯一名称,用于标识以加载它。

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

save_module(module_name, dependencies=True)[源][源] ¶

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

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

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

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

使用 pickle 将 Python 对象存档。相当于 torch.save() ,但将数据存入存档而不是独立的文件。标准的 pickle 不保存代码,只保存对象。如果 dependencies 为真,此方法还将扫描需要重建对象的模块,并将相关代码保存。

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

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

  • resource (str) – 资源的唯一名称,用于在加载时识别它。

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

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

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

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

参数:
  • 模块名称(字符串)- 例如 "my_package.my_subpackage" ,代码将被保存以提供此包的代码。

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

  • 依赖项(布尔值,可选)- 如果 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, 可选) – 如果 True ,则此模块被视为包。包可以有子模块(例如 my_package.my_subpackage.my_subsubpackage ),并且可以在其中保存资源。默认为 False

  • 依赖项(布尔值,可选)- 如果 True ,我们将扫描源文件以查找依赖项。

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

将文本数据保存到包中。

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

  • 资源(字符串)- 资源的唯一名称,用于标识以加载它。

  • 要保存的内容。

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, PathLike[str], IO[bytes], PyTorchFileReader]) – 一个文件对象(必须实现 read() , readline() , tell() , 和 seek() ),一个字符串,或包含文件名的 os.PathLike 对象。

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

引发:

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

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

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

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

  • 排除(联合[List[str], str])- 一个可选的模式,用于排除与该模式匹配的文件名。

返回:

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)[源代码][源代码] ¶

加载原始字节。

参数:
  • 包(字符串)- 模块包的名称(例如 "my_package.my_subpackage" )。

  • 资源(字符串)- 资源的唯一名称。

返回:

加载的数据。

返回类型:

字节

加载 pickle 包的资源,加载任何需要的模块来构造对象使用@0# 。

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

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

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

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

返回:

反序列化的对象。

返回类型:

任何

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

加载字符串。

参数:
  • 包(字符串)- 模块包的名称(例如 "my_package.my_subpackage" )。

  • 资源(字符串)- 资源的唯一名称。

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

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

返回:

加载的文本。

返回类型:

str

python_version()[source][source]

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

注意:此函数是实验性的,并且不向前兼容。计划稍后将其移动到锁定文件中。

返回:

Python 版本,例如 3.8.9,或者如果没有与该软件包一起存储版本则为 None

class torch.package.Directory(name, is_dir)[source][source]

文件结构表示。以目录节点组织,每个目录节点包含其子目录列表。通过调用 PackageImporter.file_structure() 创建软件包的目录。

has_file(filename)[source][source]

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

参数:

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

返回:

如果 Directory 包含指定的文件。

返回类型:

布尔型


© 版权所有 PyTorch 贡献者。

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

文档

PyTorch 开发者文档全面访问

查看文档

教程

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

查看教程

资源

查找开发资源并获得您的疑问解答

查看资源