目录

torch.package

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

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

警告

该模块依赖于 pickle 模块,而该模块不安全。仅解包你信任的数据。

有可能构造恶意的pickle数据,这些数据在反序列化期间会执行任意代码。 请勿解包可能来自不可信来源或被篡改的数据。

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

教程

打包您的第一个模型

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

如何做……

查看包内有什么?

将该软件包视为ZIP存档

该容器的格式为torch.package,因此任何处理标准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 and PackageExporter 提供了一个 file_structure() 方法,该方法将返回一个可打印 且可查询的 Folder 对象。 Folder 对象是一个简单的目录结构,你可以使用它来探索 当前 torch.package 的内容。

The Folder object itself is directly printable and will print out a file tree representation. To filter what is returned, use the glob-style include and exclude filtering arguments.

with PackageExporter('my_package.pt') as pe:
    pe.save_pickle('models', 'model_1.pkl', mod)
    # can limit printed items with include/exclude args
    print(pe.file_structure(include=["**/utils.py", "**/*.pkl"], exclude="**/*.storages"))

importer = PackageImporter('my_package.pt')
print(importer.file_structure()) # will print out all files

Output:

# filtered with glob pattern:
#    include=["**/utils.py", "**/*.pkl"], exclude="**/*.storages"
─── 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

You can also query Folder objects with the has_file() method.

exporter_file_structure = exporter.file_structure()
found: bool = exporter_file_structure.has_file("package_a/subpackage.py")

想知道某个模块为何被列为依赖项吗?

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

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

如果您想查看给定模块 src 依赖于 foo 的情况,可以使用 PackageExporter.all_paths() 方法,它将返回一个以 DOT 格式表示的图,展示 srcfoo 之间的所有依赖路径。

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

包含任意资源并与我的包一起使用,并稍后访问它们?

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

with torch.PackageExporter("package.pt") as exporter:
    # Pickles the object and saves to `my_resources/tens.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__

Steps:

  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)
    print(pe.file_structure())

pi = PackageImporter('foo_package.pt')
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

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

A 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_pacakge).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 one/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")

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

# 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!", equivalient 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 文件是一个通常使用 .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:class:`PackageImporter`. ``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字节码。

在紧急情况下,对象会与一个GLOBAL操作码一起保存,该操作码描述了如何找到对象类型的实现,例如:

GLOBAL 'torchvision.models.resnet Resnet`

依赖解析器将收集所有 GLOBAL 操作并将它们标记为已序列化对象的依赖项。 有关序列化和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.**")。您使用PackageImporter的方法将模式与操作关联,例如

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

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

intern

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

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

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

注意:只有Python源模块可以被intern-化。其他类型的模块,比如C扩展模块和字节码模块,在尝试对其进行intern时会引发错误。这些类型的模块需要被mock-化或extern-化。

extern

如果一个模块被 extern-ed,它将不会被打包。相反,它将被添加到此包的外部依赖列表中。您可以在 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*)。通配符匹配任何字符串,包括空字符串。

  • 两个通配符 (**)。这可以匹配零个或多个完整段落。

Examples:

  • 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!

在此示例中,MyClassimport_MyClass 不是同一种类型。在这个具体示例中,MyClassimport_MyClass 具有完全相同的实现,因此你可能会认为可以将它们视为同一类。但请考虑这种情况:import_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 导入器相同的 core 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

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

API 参考

class torch.package.PackagingError(dependency_graph)[source]

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

class torch.package.EmptyMatchError[source]

此异常在模拟或外部被标记为 allow_empty=False,但在打包时未与任何模块匹配时抛出。

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

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

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

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

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

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

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

创建一个导出器。

Parameters
  • f – 导出的目标位置。可以是包含文件名的 string/Path 对象 或一个二进制 I/O 对象。

  • 导入器 – 如果传递了一个单独的导入器,则使用该导入器来搜索模块。 如果传递了一组导入器,将从中构造一个 OrderedImporter

add_dependency(module_name, dependencies=True)[source]

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

all_paths(src, dst)[source]
Return a dot representation of the subgraph

具有从 src 到 dst 的所有路径。

Returns

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

close()[source]

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

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

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

Returns

包含将在此软件包中被禁止的模块名称的列表。

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

从可以导入的模块列表中排除与给定通配符模式匹配名称的模块。 如果发现对任何匹配包的依赖关系,则会引发一个 PackagingError 异常。

Parameters
  • 包含 (Union[List[str], str]) – 一个字符串,例如 "my_package.my_subpackage",或者字符串列表 用于指定要外部化的模块名称。这也可以是一个类似glob的模式,如mock()中所述。

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

dependency_graph_string()[source]

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

Returns

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

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

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

Parameters
  • 包含 (Union[List[str], str]) – 一个字符串,例如 "my_package.my_subpackage",或者字符串列表 用于指定要外部化的模块名称。这也可以是一个类似glob的模式,如 mock()中所述。

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

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

externed_modules()[source]

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

Returns

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

get_rdeps(module_name)[source]

返回一个模块列表,这些模块依赖于模块 module_name

Returns

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

get_unique_id()[source]

获取一个 ID。此 ID 保证在这个包中只会发放一次。

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

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

Parameters
  • 包含 (Union[List[str], str]) – 字符串,例如“my_package.my_subpackage”,或字符串列表,用于指定要外部化的模块名称。这也可以是一个类似通配符的模式,如mock()中所述。

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

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

interned_modules()[source]

返回当前所有已注册的模块。

Returns

包含将在此软件包中进行 intern 操作的模块名称列表。

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

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

Parameters
  • 包含 (联合[列表[字符串], 字符串]) –

    例如一个字符串 "my_package.my_subpackage",或者字符串列表 用于指定要模拟的模块名称。字符串也可以是一个通配符样式模式 字符串,可能匹配多个模块。任何符合此模式字符串的必需依赖项将被自动模拟。

    Examples :

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

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

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

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

mocked_modules()[source]

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

Returns

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

register_extern_hook(hook)[source]

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

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

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

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

Returns

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

Return type

torch.utils.hooks.RemovableHandle

register_intern_hook(hook)[source]

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

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

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

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

Returns

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

Return type

torch.utils.hooks.RemovableHandle

register_mock_hook(hook)[source]

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

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

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

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

Returns

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

Return type

torch.utils.hooks.RemovableHandle

save_binary(package, resource, binary)[source]

将原始字节保存到软件包中。

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

  • 资源 (字符串) – 用于标识要加载的资源的唯一名称。

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

save_module(module_name, dependencies=True)[source]

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

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

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

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

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

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

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

  • 资源 (字符串) – 用于标识要加载的资源的唯一名称。

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

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

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

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

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

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

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

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

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

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

  • 源码 (str) – 用于保存此包的Python源代码。

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

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

save_text(package, resource, text)[source]

将文本数据保存到包中。

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

  • 资源 (字符串) – 用于标识要加载的资源的唯一名称。

  • 文本 (字符串) – 要保存的内容。

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

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

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

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

打开 file_or_buffer 以导入。这会检查导入的包仅需由 module_allowed 允许的模块

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

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

Raises

导入错误 – 如果包将使用不允许的模块。

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

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

Parameters
  • include (Union[List[str], str]) – 可选字符串,例如 "my_package.my_subpackage",或用于指定包含在zip文件表示中的文件名的可选字符串列表。这也可以是如 PackageExporter.mock() 中所述的通配符模式

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

Returns

Directory

id()[source]

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

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

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

Parameters
  • 名称 (字符串) – 要加载的模块的完整限定名称。

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

Returns

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

Return type

types.ModuleType

load_binary(package, resource)[source]

加载原始字节。

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

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

Returns

加载的数据。

Return type

字节

load_pickle(package, resource, map_location=None)[source]

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

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

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

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

Returns

反序列化的对象。

Return type

任何

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

加载字符串。

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

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

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

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

Returns

加载的文本。

Return type

字符串

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

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

has_file(filename)[source]

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

Parameters

文件名 (字符串) – 要搜索的文件路径。

Returns

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

Return type

布尔

文档

访问 PyTorch 的全面开发人员文档

查看文档

教程

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

查看教程

资源

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

查看资源