torch.package¶
torch.package 增加了对创建包含工件和任意 PyTorch 代码的包的支持。这些包可以保存、共享,用于在以后的日期或在不同的机器上加载和执行模型,甚至可以使用 torch::deploy 部署到生产环境中。
本文档包含教程、操作指南、解释和API参考,将帮助您更深入地了解torch.package及其使用方法。
警告
此模块依赖于不安全的 pickle 模块。仅解包您信任的数据。
可以构造恶意的pickle数据,这些数据在反序列化过程中会执行任意代码。 切勿解包可能来自不受信任来源或可能被篡改的数据。
更多信息,请查看文档中关于pickle模块的内容。
我如何…¶
查看包内的内容是什么?¶
将包视为ZIP存档¶
用于 torch.package 的容器格式是 ZIP,因此任何适用于标准 ZIP 文件的工具都应适用于浏览其内容。一些常见的与 ZIP 文件交互的方式:
unzip my_package.pt将把torch.package存档解压缩到磁盘上,您可以在那里自由检查其内容。
$ unzip my_package.pt && tree my_package
my_package
├── .data
│ ├── 94304870911616.storage
│ ├── 94304900784016.storage
│ ├── extern_modules
│ └── version
├── models
│ └── model_1.pkl
└── torchvision
└── models
├── resnet.py
└── utils.py
~ cd my_package && cat torchvision/models/resnet.py
...
Python 的
zipfile模块提供了一种标准的方式来读取和写入 ZIP 存档内容。
from zipfile import ZipFile
with ZipFile("my_package.pt") as myzip:
file_bytes = myzip.read("torchvision/models/resnet.py")
# 以某种方式编辑 file_bytes
myzip.writestr("torchvision/models/resnet.py", new_file_bytes)
vim 具有原生读取 ZIP 档案的能力。你甚至可以编辑文件并将其
write回档案中!
# 将此添加到您的 .vimrc 中,以将 `*.pt` 文件视为 zip 文件
au BufReadCmd *.pt call zip#Browse(expand(""))
~ vi my_package.pt
使用 file_structure() API¶
PackageImporter 提供了一个 file_structure() 方法,该方法将返回一个可打印和可查询的 Directory 对象。Directory 对象是一个简单的目录结构,您可以使用它来探索 torch.package 的当前内容。
对象本身可以直接打印,并会输出一个文件树表示。要过滤返回的内容,请使用glob风格的include和exclude过滤参数。
with PackageExporter('my_package.pt') as pe:
pe.save_pickle('models', 'model_1.pkl', mod)
importer = PackageImporter('my_package.pt')
# 可以使用include/exclude参数限制打印的项目
print(importer.file_structure(include=["**/utils.py", "**/*.pkl"], exclude="**/*.storage"))
print(importer.file_structure()) # 将打印出所有文件
输出:
# 使用glob模式过滤:
# include=["**/utils.py", "**/*.pkl"], exclude="**/*.storage"
─── my_package.pt
├── models
│ └── model_1.pkl
└── torchvision
└── models
└── utils.py
# 所有文件
─── my_package.pt
├── .data
│ ├── 94304870911616.storage
│ ├── 94304900784016.storage
│ ├── extern_modules
│ └── version
├── models
│ └── model_1.pkl
└── torchvision
└── models
├── resnet.py
└── utils.py
您还可以使用 has_file() 方法查询 Directory 对象。
importer_file_structure = importer.file_structure()
found: bool = importer_file_structure.has_file("package_a/subpackage.py")
查看为什么某个模块被包含为依赖项?¶
假设有一个给定的模块 foo,而你想知道为什么你的 PackageExporter 将 foo 作为依赖项引入。
PackageExporter.get_rdeps() 将返回所有直接依赖于 foo 的模块。
如果您想查看某个模块 src 如何依赖于 foo,PackageExporter.all_paths() 方法将返回一个DOT格式的图,显示 src 和 foo 之间的所有依赖路径。
如果你只想查看整个依赖图,可以使用 PackageExporter 的 PackageExporter.dependency_graph_string()。
在我的包中包含任意资源并在以后访问它们?¶
PackageExporter 提供了三种方法,save_pickle、save_text 和 save_binary,允许您将 Python 对象、文本和二进制数据保存到包中。
with torch.PackageExporter("package.pt") as exporter:
# 将对象序列化并保存到归档文件中的 `my_resources/tensor.pkl`。
exporter.save_pickle("my_resources", "tensor.pkl", torch.randn(4))
exporter.save_text("config_stuff", "words.txt", "a sample string")
exporter.save_binary("raw_data", "binary", my_bytes)
PackageImporter 提供了名为 load_pickle、load_text 和 load_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__ 类似。
步骤:
在目标类上定义方法
__reduce_package__(self, exporter: PackageExporter)。此方法应完成将类实例保存到包中的工作,并应返回一个元组,其中包含相应的解包函数及其所需的参数,以便调用解包函数。当PackageExporter遇到目标类的实例时,会调用此方法。为类定义一个解包函数。这个解包函数应该完成重建并返回类实例的工作。函数签名的第一个参数应该是一个
PackageImporter实例,其余的参数由用户定义。
# foo.py [自定义类Foo打包方式的示例]
from torch.package import PackageExporter, PackageImporter
import time
class Foo:
def __init__(self, my_string: str):
super().__init__()
self.my_string = my_string
self.time_imported = 0
self.time_exported = 0
def __reduce_package__(self, exporter: PackageExporter):
"""
当保存此对象的实例时,由``torch.package.PackageExporter``的Pickler的``persistent_id``调用。
此方法应完成在``torch.package``存档中保存此对象的工作。
返回带有参数的函数,以从``torch.package.PackageImporter``的Pickler的``persistent_load``函数加载对象。
"""
# 使用此模式以确保不会与正常依赖项发生命名冲突,
# 保存在此模块名称下的任何内容都不应与其他包中的项目冲突
generated_module_name = f"foo-generated._{exporter.get_unique_id()}"
exporter.save_text(
generated_module_name,
"foo.txt",
self.my_string + ", with exporter modification!",
)
time_exported = time.clock_gettime(1)
# 返回解包函数及参数,以调用
return (unpackage_foo, (generated_module_name, time_exported,))
def unpackage_foo(
importer: PackageImporter, generated_module_name: str, time_exported: float
) -> Foo:
"""
当解包Foo对象时,由``torch.package.PackageImporter``的Pickler的``persistent_load``函数调用。
执行从``torch.package``存档加载并返回Foo实例的工作。
"""
time_imported = time.clock_gettime(1)
foo = Foo(importer.load_text(generated_module_name, "foo.txt"))
foo.time_imported = time_imported
foo.time_exported = time_exported
return foo
# 保存类 Foo 实例的示例
import torch
from torch.package import PackageImporter, PackageExporter
import foo
foo_1 = foo.Foo("foo_1 initial string")
foo_2 = foo.Foo("foo_2 initial string")
with PackageExporter('foo_package.pt') as pe:
# 正常保存,无需额外工作
pe.save_pickle('foo_collection', 'foo1.pkl', foo_1)
pe.save_pickle('foo_collection', 'foo2.pkl', foo_2)
pi = PackageImporter('foo_package.pt')
print(pi.file_structure())
imported_foo = pi.load_pickle('foo_collection', 'foo1.pkl')
print(f"foo_1 string: '{imported_foo.my_string}'")
print(f"foo_1 export time: {imported_foo.time_exported}")
print(f"foo_1 import time: {imported_foo.time_imported}")
# 运行上述脚本的输出
─── foo_package
├── foo-generated
│ ├── _0
│ │ └── foo.txt
│ └── _1
│ └── foo.txt
├── foo_collection
│ ├── foo1.pkl
│ └── foo2.pkl
└── foo.py
foo_1 字符串: 'foo_1 初始字符串, 包含减少修改!'
foo_1 导出时间: 9857706.650140837
foo_1 导入时间: 9857706.652698385
在我的源代码中测试是否在包内执行?¶
一个 PackageImporter 会将属性 __torch_package__ 添加到它初始化的每个模块中。您的代码可以检查此属性的存在,以确定它是否在打包的上下文中执行。
# 在 foo/bar.py 中:
if "__torch_package__" in dir(): # 如果代码是从包中加载的,则为真
def is_in_package():
return True
UserException = Exception
else:
def is_in_package():
return False
UserException = UnpackageableException
现在,代码的行为将根据它是通过您的 Python 环境正常导入还是从 torch.package 导入而有所不同。
from foo.bar import is_in_package
print(is_in_package()) # 假
loaded_module = PackageImporter(my_package).import_module("foo.bar")
loaded_module.is_in_package() # 真
警告:通常情况下,代码的行为会根据其是否被打包而有所不同,这是一种不好的做法。这可能导致难以调试的问题,这些问题对代码的导入方式非常敏感。如果你的包打算被广泛使用,考虑重构你的代码,使其无论以何种方式加载,行为都保持一致。
将代码补丁集成到包中?¶
PackageExporter 提供了一个 save_source_string() 方法,允许您将任意 Python 源代码保存到您选择的模块中。
with PackageExporter(f) as exporter:
# 保存当前Python环境中可用的my_module.foo。
exporter.save_module("my_module.foo")
# 这将提供的字符串保存到包归档文件中的my_module/foo.py。
# 它将覆盖之前保存的my_module.foo。
exporter.save_source_string("my_module.foo", textwrap.dedent(
"""\
def my_function():
print('hello world')
"""
))
# 如果你想将my_module.bar视为一个包
# (例如,保存到`my_module/bar/__init__.py`而不是`my_module/bar.py`)
# 传递is_package=True,
exporter.save_source_string("my_module.bar",
"def foo(): print('hello')\n",
is_package=True)
importer = PackageImporter(f)
importer.import_module("my_module.foo").my_function() # 打印 'hello world'
从打包的代码中访问包内容?¶
PackageImporter 实现了
importlib.resources
API,用于访问包内的资源。
with PackageExporter(f) as exporter:
# 将文本保存到归档文件中的 my_resource/a.txt
exporter.save_text("my_resource", "a.txt", "hello world!")
# 将张量保存到 my_pickle/obj.pkl
exporter.save_pickle("my_pickle", "obj.pkl", torch.ones(2, 2))
# 请参见下面的模块内容
exporter.save_module("foo")
exporter.save_module("bar")
The importlib.resources API 允许从打包的代码中访问资源。
# foo.py:
import importlib.resources
import my_resource
# 返回 "hello world!"
def get_my_resource():
return importlib.resources.read_text(my_resource, "a.txt")
使用 importlib.resources 是从打包代码内部访问包内容的推荐方式,因为它符合 Python 标准。然而,也可以从打包代码内部访问父 PackageImporter 实例本身。
# bar.py:
import torch_package_importer # 这是导入此模块的PackageImporter。
# 打印 "hello world!",相当于 importlib.resources.read_text
def get_my_resource():
return torch_package_importer.load_text("my_resource", "a.txt")
# 你还可以做 importlib.resources API 不支持的事情,比如从包中加载一个pickled对象。
def get_my_pickle():
return torch_package_importer.load_pickle("my_pickle", "obj.pkl")
区分打包代码和非打包代码?¶
要判断一个对象的代码是否来自torch.package,可以使用torch.package.is_from_package()函数。
注意:如果一个对象来自一个包,但其定义来自标记为extern的模块或来自stdlib,
此检查将返回False。
importer = PackageImporter(f)
mod = importer.import_module('foo')
obj = importer.load_pickle('model', 'model.pkl')
txt = importer.load_text('text', 'my_test.txt')
assert is_from_package(mod)
assert is_from_package(obj)
assert not is_from_package(txt) # 字符串来自标准库,所以这将返回False
重新导出一个已导入的对象?¶
要重新导出一个之前由PackageImporter导入的对象,您必须让新的PackageExporter
知道原始的PackageImporter,以便它能够找到对象依赖项的源代码。
importer = PackageImporter(f)
obj = importer.load_pickle("model", "model.pkl")
# 在新的包中重新导出 obj
with PackageExporter(f2, importer=(importer, sys_importer)) as exporter:
exporter.save_pickle("model", "model.pkl", obj)
如何打包一个TorchScript模块?¶
要打包一个 TorchScript 模型,请使用与任何其他对象相同的 save_pickle 和 load_pickle API。
保存作为属性或子模块的 TorchScript 对象也得到支持,无需额外工作。
# 保存 TorchScript 就像保存其他对象一样
with PackageExporter(file_name) as e:
e.save_pickle("res", "script_model.pkl", scripted_model)
e.save_pickle("res", "mixed_model.pkl", python_model_with_scripted_submodule)
# 正常加载
importer = PackageImporter(file_name)
loaded_script = importer.load_pickle("res", "script_model.pkl")
loaded_mixed = importer.load_pickle("res", "mixed_model.pkl"
解释¶
torch.package 格式概述¶
一个 torch.package 文件是一个ZIP归档文件,通常使用 .pt 扩展名。在ZIP归档文件中,有两种类型的文件:
框架文件,放置在
.data/目录中。用户文件,即其他所有内容。
作为一个例子,这是一个来自 torchvision 的完全打包的 ResNet 模型:
resnet
├── .data # 所有特定框架的数据都存储在这里。
│ │ # 命名为避免与用户序列化的代码冲突。
│ ├── 94286146172688.storage # 张量数据
│ ├── 94286146172784.storage
│ ├── extern_modules # 包含外部模块名称的文本文件(例如 'torch')
│ ├── version # 版本元数据
│ ├── ...
├── model # 序列化的模型
│ └── model.pkl
└── torchvision # 所有代码依赖项都作为源文件捕获
└── models
├── resnet.py
└── utils.py
框架文件¶
.data/ 目录由 torch.package 拥有,其内容被视为私有实现细节。
torch.package 格式对 .data/ 的内容不做任何保证,但任何更改都将向后兼容
(即,较新版本的 PyTorch 始终能够加载较旧的 torch.packages)。
目前,.data/ 目录包含以下项目:
version: 序列化格式的版本号,以便torch.package导入基础设施知道如何加载此包。extern_modules: 被视为extern的模块列表。extern模块将使用加载环境的系统导入器进行导入。*.storage: 序列化的张量数据。
.数据
├── 94286146172688.存储
├── 94286146172784.存储
├── 外部模块
├── 版本
├── ...
用户文件¶
存档中的所有其他文件都是由用户放置的。布局与Python的常规包相同。如需深入了解Python打包的工作原理,请参阅这篇文章(它有些过时,因此请与Python参考文档中的实现细节进行双重检查)。
├── model # 序列化的模型
│ └── model.pkl
├── another_package
│ ├── __init__.py
│ ├── foo.txt # 一个资源文件 , 参见 importlib.resources
│ └── ...
└── torchvision
└── models
├── resnet.py # torchvision.models.resnet
└── utils.py # torchvision.models.utils
如何torch.package找到你的代码的依赖项¶
分析对象的依赖关系¶
当你调用 save_pickle(obj, ...) 时,PackageExporter 会正常地序列化对象。然后,它使用 pickletools 标准库模块来解析序列化后的字节码。
在 pickle 中,对象与一个 GLOBAL 操作码一起保存,该操作码描述了在哪里可以找到对象类型的实现,例如:
GLOBAL 'torchvision.models.resnet ResNet'
依赖解析器将收集所有GLOBAL操作并将它们标记为您的pickle对象的依赖项。
有关pickle和pickle格式的更多信息,请参阅Python文档。
分析模块的依赖关系¶
当识别出一个Python模块作为依赖项时,torch.package会遍历该模块的Python AST表示,并查找具有完整支持的标准形式的导入语句:from x import y、import z、from w import v as u等。当遇到这些导入语句之一时,torch.package会将导入的模块注册为依赖项,然后以相同的AST遍历方式解析这些依赖项。
注意:AST 解析对 __import__(...) 语法的支持有限,并且不支持 importlib.import_module 调用。通常情况下,您不应期望 torch.package 能够检测到动态导入。
依赖管理¶
torch.package 会自动找到您的代码和对象所依赖的Python模块。这个过程称为依赖解析。
对于依赖解析器找到的每个模块,您必须指定一个操作来执行。
允许的操作包括:
intern: 将此模块放入包中。extern: 将此模块声明为包的外部依赖项。mock: 模拟此模块。deny: 依赖此模块将在包导出期间引发错误。
最后,还有一个重要的操作,虽然从技术上讲它并不属于torch.package:
重构:移除或更改代码中的依赖关系。
请注意,操作仅在完整的 Python 模块上定义。无法仅打包模块中的一个函数或类,而将其他部分排除在外。
这是设计使然。Python 不提供模块中定义的对象之间的清晰边界。唯一定义的依赖组织单元是模块,因此这就是 torch.package 所使用的。
操作通过模式应用于模块。模式可以是模块名称("foo.bar")或通配符(如 "foo.**")。您可以使用 PackageExporter 上的方法将模式与操作关联,例如。
my_exporter.intern("torchvision.**")
my_exporter.extern("numpy")
如果一个模块匹配某个模式,相应的操作将被应用到该模块上。对于给定的模块,模式将按照它们定义的顺序进行检查,并采取第一个匹配的操作。
intern¶
如果一个模块被intern化,它将被放入包中。
此操作是您的模型代码,或任何您想要打包的相关代码。例如,如果您正在尝试打包来自 torchvision 的 ResNet,
您需要 intern 模块 torchvision.models.resnet。
在包导入时,当您的打包代码尝试导入一个intern模块时,PackageImporter 会在您的包中查找该模块。如果找不到该模块,将引发错误。这确保了每个PackageImporter与加载环境隔离——即使您在包和加载环境中都有my_interned_module可用,PackageImporter也只会使用包中的版本。
注意:只有 Python 源模块可以被 intern。其他类型的模块,如 C 扩展模块和字节码模块,如果你尝试 intern 它们,将会引发错误。这些类型的模块需要被 mock 或 extern。
extern¶
如果一个模块被extern声明,它将不会被打包。相反,它将被添加到此包的外部依赖列表中。你可以在package_exporter.extern_modules中找到这个列表。
在包导入时,当打包的代码尝试导入一个extern-ed模块时,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或torchtorch*.**: 匹配torch,torchvision及其所有子模块
在指定操作时,您可以传递多个模式,例如。
exporter.intern(["torchvision.models.**", "torchvision.utils.**"])
如果模块匹配任何模式,它将匹配此操作。
您还可以指定要排除的模式,例如。
exporter.mock("**", exclude=["torchvision.**"])
如果一个模块匹配了任何排除模式,它将不会匹配此操作。在这个例子中,我们模拟了除torchvision及其子模块之外的所有模块。
当一个模块可能匹配多个操作时,将采用第一个定义的操作。
torch.package 尖锐边缘¶
避免在模块中使用全局状态¶
Python 使得在模块级别范围内绑定对象和运行代码变得非常容易。这通常是没问题的——毕竟,函数和类也是通过这种方式绑定到名称上的。然而,当你在模块范围内定义一个对象并打算对其进行修改时,事情就会变得更加复杂,因为这会引入可变的全局状态。
可变的全局状态非常有用——它可以减少样板代码,允许开放注册到表格等。但是,除非非常小心地使用,否则在与 torch.package 一起使用时可能会导致复杂问题。
每个PackageImporter都会为其内容创建一个独立的环境。这样做很好,因为它意味着我们可以加载多个包并确保它们彼此隔离,但当模块以假设共享可变全局状态的方式编写时,这种行为可能会导致难以调试的错误。
如何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的名称会变成。
对于 __file__,一个类似 torchvision/models/resnet18.py 的名称变为 。
名称混淆有助于避免不同包之间的模块名称无意中发生冲突,并通过使堆栈跟踪和打印语句更清晰地显示它们是否引用打包代码来帮助您调试。有关混淆的面向开发者的详细信息,请参阅torch/package/中的mangling.md。
API参考¶
- class torch.package.PackagingError(dependency_graph, debug=False)[源代码]¶
当导出包时出现问题时,会引发此异常。
PackageExporter将尝试收集所有错误并一次性呈现给您。
- class torch.package.EmptyMatchError[源代码]¶
这是一个异常,当一个mock或extern被标记为
allow_empty=False,并且在打包过程中没有与任何模块匹配时,会抛出此异常。
- class torch.package.PackageExporter(f, importer=<torch.package.importer._SysImporter object>, debug=False)[源代码]¶
导出器允许您将代码包、经过序列化的Python数据以及任意二进制和文本资源写入一个自包含的包中。
导入可以以一种密封的方式加载此代码,使得代码从包中加载,而不是从正常的Python导入系统加载。这允许打包PyTorch模型代码和数据,以便它可以在服务器上运行或在将来用于迁移学习。
包中包含的代码在创建时会从原始源文件逐个文件复制,并且文件格式是经过特殊组织的zip文件。包的未来用户可以解压缩包,并编辑代码以对其进行自定义修改。
包的导入器确保模块中的代码只能从包内部加载,除非使用
extern()明确列出为外部的模块。 压缩包中的文件extern_modules列出了包外部依赖的所有模块。 这可以防止“隐式”依赖,即包在本地运行是因为它导入了本地安装的包,但在将包复制到另一台机器时失败。当源代码被添加到包中时,导出器可以选择性地扫描它以查找更多的代码依赖项(
dependencies=True)。它会查找导入语句,解析相对引用为合格的模块名称,并执行用户指定的操作(参见:extern()、mock()和intern())。- all_paths(src, dst)[源代码]¶
- Return a dot representation of the subgraph
包含从 src 到 dst 的所有路径。
- Returns
包含从 src 到 dst 的所有路径的点表示。 (https://graphviz.org/doc/info/lang.html)
- Return type
- close()[源代码]¶
将包写入文件系统。在调用
close()之后进行的任何调用现在都无效。 建议使用资源保护语法代替:with PackageExporter("file.zip") as e: ...
- deny(include, *, exclude=())[源代码]¶
从包可以导入的模块列表中阻止名称与给定全局模式匹配的模块。如果发现任何匹配包的依赖关系,则会引发
PackagingError。
- extern(include, *, exclude=(), allow_empty=True)[源代码]¶
在包可以导入的外部模块列表中包含
module。 这将防止依赖发现将其保存在包中。导入器将直接从标准导入系统加载外部模块。 外部模块的代码也必须存在于加载包的过程中。- Parameters
包含 (联合[列表[字符串], 字符串]) – 一个字符串,例如
"my_package.my_subpackage",或模块名称的字符串列表。这也可以是一个全局样式模式,如在mock()中所述。exclude (Union[List[str], str]) – 一个可选的模式,用于排除与包含字符串匹配的一些模式。
allow_empty (bool) – 一个可选标志,指定此调用中指定的外部模块是否必须在打包期间匹配某些模块。如果添加了外部模块全局模式且
allow_empty=False,并且在任何模块匹配该模式之前调用了close()(无论是显式调用还是通过__exit__),则会抛出异常。如果allow_empty=True,则不会抛出此类异常。
- intern(include, *, exclude=(), allow_empty=True)[源代码]¶
指定应打包的模块。模块必须匹配某些
intern模式,才能被包含在包中并递归处理其依赖项。- Parameters
包含 (联合[列表[字符串], 字符串]) – 一个字符串,例如“my_package.my_subpackage”,或模块名称的字符串列表。这也可以是一个全局样式模式,如
mock()中所述。exclude (Union[List[str], str]) – 一个可选的模式,用于排除与包含字符串匹配的一些模式。
allow_empty (bool) – 一个可选标志,指定此调用中指定的内部模块是否必须在打包期间匹配某些模块。如果添加了
allow_empty=False的intern模块全局模式,并且在任何模块匹配该模式之前调用了close()(无论是显式调用还是通过__exit__),则会抛出异常。如果allow_empty=True,则不会抛出此类异常。
- mock(include, *, exclude=(), allow_empty=True)[源代码]¶
将一些必需的模块替换为模拟实现。模拟模块将为从其访问的任何属性返回一个假对象。因为我们逐文件复制,依赖解析有时会找到由模型文件导入但从未使用的文件(例如,自定义序列化代码或训练助手)。使用此函数在不修改原始代码的情况下模拟此功能。
- Parameters
include (Union[List[str], str]) –
一个字符串,例如
"my_package.my_subpackage",或模块名称的字符串列表,用于被模拟的模块。字符串也可以是glob风格的模式字符串,可能匹配多个模块。任何与此模式字符串匹配的必需依赖项都将被自动模拟。- 示例 :
'torch.**'– 匹配torch及其所有子模块,例如'torch.nn'和'torch.nn.functional''torch.*'– 匹配'torch.nn'或'torch.functional',但不匹配'torch.nn.functional'
exclude (Union[List[str], str]) – 一个可选的模式,排除与包含字符串匹配的一些模式。 例如:
include='torch.**', exclude='torch.foo'将模拟所有 torch 包,除了'torch.foo', 默认值是[]。allow_empty (bool) – 一个可选标志,指定此调用中指定的模拟实现是否必须在打包期间匹配到某个模块。如果添加了一个模拟,并且
allow_empty=False,并且在调用close()(无论是显式调用还是通过__exit__)时,模拟未匹配到正在导出的包中使用的模块,则会抛出异常。如果allow_empty=True,则不会抛出此类异常。
- register_extern_hook(hook)[源代码]¶
在导出器上注册一个外部钩子。
每次模块匹配到
extern()模式时,钩子将被调用。 它应具有以下签名:hook(exporter: PackageExporter, module_name: str) -> None
钩子将按照注册顺序被调用。
- Returns
一个可以用于通过调用
handle.remove()来移除添加的钩子的句柄。- Return type
torch.utils.hooks.RemovableHandle
- register_intern_hook(hook)[源代码]¶
在导出器上注册一个内部钩子。
每次模块与
intern()模式匹配时,钩子将被调用。 它应具有以下签名:hook(exporter: PackageExporter, module_name: str) -> None
钩子将按照注册顺序被调用。
- Returns
一个可以用于通过调用
handle.remove()来移除添加的钩子的句柄。- Return type
torch.utils.hooks.RemovableHandle
- register_mock_hook(hook)[源代码]¶
在导出器上注册一个模拟钩子。
每次模块与
mock()模式匹配时,钩子将被调用。 它应具有以下签名:hook(exporter: PackageExporter, module_name: str) -> None
钩子将按照注册顺序被调用。
- Returns
一个可以用于通过调用
handle.remove()来移除添加的钩子的句柄。- Return type
torch.utils.hooks.RemovableHandle
- save_module(module_name, dependencies=True)[源代码]¶
将
module的代码保存到包中。使用importers路径解析模块对象,然后使用其__file__属性查找源代码。
- save_pickle(package, resource, obj, dependencies=True, pickle_protocol=3)[源代码]¶
使用 pickle 将 Python 对象保存到归档文件中。等同于
torch.save(),但保存到归档文件中而不是独立的文件中。标准的 pickle 不会保存代码,只会保存对象。如果dependencies为 true,此方法还将扫描需要哪些模块来重建 pickle 对象,并保存相关代码。为了能够保存一个对象,其中
type(obj).__name__是my_module.MyObject,my_module.MyObject必须根据importer顺序解析为对象的类。当保存之前已经打包的对象时, 导入器的import_module方法需要存在于importer列表中,以便实现此功能。
- save_source_file(module_name, file_or_directory, dependencies=True)[源代码]¶
将本地文件系统中的
file_or_directory添加到源包中,以提供module_name的代码。- Parameters
module_name (str) – 例如
"my_package.my_subpackage",代码将被保存以提供此包的代码。文件或目录 (字符串) – 代码文件或目录的路径。当为目录时,目录中的所有python文件将使用
save_source_file()递归复制。如果文件名为"/__init__.py",则该代码将被视为包。依赖项 (bool, 可选) – 如果
True,我们扫描源代码以查找依赖项。
- class torch.package.PackageImporter(file_or_buffer, module_allowed=<function PackageImporter.<lambda>>)[源代码]¶
导入器允许您加载由
PackageExporter编写的代码。代码以一种密封的方式加载,使用包中的文件而不是正常的Python导入系统。这使得可以打包PyTorch模型代码和数据,以便在服务器上运行或在将来用于迁移学习。包的导入器确保模块中的代码只能从包内部加载,除非在导出时明确列出为外部的模块。压缩包中的文件
extern_modules列出了包外部依赖的所有模块。这可以防止“隐式”依赖,即包在本地运行是因为它导入了本地安装的包,但在将包复制到另一台机器时失败。- __init__(file_or_buffer, module_allowed=<function PackageImporter.<lambda>>)[源代码]¶
打开
file_or_buffer以进行导入。这将检查导入的包是否仅需要module_allowed允许的模块
- id()[源代码]¶
返回内部标识符,torch.package 使用该标识符来区分
PackageImporter实例。 看起来像:<torch_package_0>
- import_module(name, package=None)[源代码]¶
如果模块尚未加载,则从包中加载模块,然后返回该模块。模块是本地加载的,并且会出现在
self.modules而不是sys.modules中。- Parameters
- Returns
已加载(可能已经)的模块。
- Return type
- load_pickle(package, resource, map_location=None)[源代码]¶
从包中解封资源,加载构建对象所需的任何模块,使用
import_module()。