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")
# 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 风格的 include
和 exclude
过滤参数。
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
如何依赖于 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:
# 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_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 的正常 pickling 过程定义 __reduce__
。
步骤
在目标类上定义方法
__reduce_package__(self, exporter: PackageExporter)
。此方法应负责将类实例保存到包内,并应返回一个元组,其中包含相应的解包函数以及调用该解包函数所需的参数。当PackageExporter
遇到目标类的实例时,会调用此方法。为类定义一个解包函数。此解包函数应负责重构并返回该类的一个实例。函数签名的第一个参数应是
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_pickle
和 load_pickle
API,就像处理任何其他对象一样。保存作为属性或子模块的 TorchScript 对象也受支持,无需额外工作。
# save TorchScript just like any other object
with PackageExporter(file_name) as e:
e.save_pickle("res", "script_model.pkl", scripted_model)
e.save_pickle("res", "mixed_model.pkl", python_model_with_scripted_submodule)
# load as normal
importer = PackageImporter(file_name)
loaded_script = importer.load_pickle("res", "script_model.pkl")
loaded_mixed = importer.load_pickle("res", "mixed_model.pkl"
解释¶
torch.package
格式概述¶
一个 torch.package
文件是一个 ZIP 存档,通常使用 .pt
扩展名。在 ZIP 存档内部,有两种类型的文件:
框架文件,放置在
.data/
中。用户文件,即所有其他文件。
例如,来自 torchvision
的一个完整打包的 ResNet 模型看起来像这样:
resnet
├── .data # All framework-specific data is stored here.
│ │ # It's named to avoid conflicts with user-serialized code.
│ ├── 94286146172688.storage # tensor data
│ ├── 94286146172784.storage
│ ├── extern_modules # text file with names of extern modules (e.g. 'torch')
│ ├── version # version metadata
│ ├── ...
├── model # the pickled model
│ └── model.pkl
└── torchvision # all code dependencies are captured as source files
└── models
├── resnet.py
└── utils.py
框架文件¶
此 .data/
目录归 torch.package 所有,其内容被视为私有实现细节。torch.package
格式不保证 .data/
的内容,但进行的任何更改都将向后兼容(即,更新版本的 PyTorch 始终能够加载旧的 torch.packages
)。
目前,.data/
目录包含以下项目:
version
:序列化格式的版本号,以便torch.package
导入基础设施知道如何加载此包。extern_modules
:被视为extern
的模块列表。extern
模块将使用加载环境的系统导入器进行导入。*.storage
:序列化的张量数据。
.data
├── 94286146172688.storage
├── 94286146172784.storage
├── extern_modules
├── version
├── ...
用户文件¶
存档中的所有其他文件都是由用户放置的。布局与 Python 的 常规包 相同。要深入了解 Python 打包的工作原理,请查阅 此文章 (它略有过时,因此请通过 Python 参考文档 双重检查实现细节)。
<package root>
├── model # the pickled model
│ └── model.pkl
├── another_package
│ ├── __init__.py
│ ├── foo.txt # a resource file , see importlib.resources
│ └── ...
└── torchvision
└── models
├── resnet.py # torchvision.models.resnet
└── utils.py # torchvision.models.utils
torch.package 如何找到您的代码依赖项¶
分析对象依赖项¶
当您发出 save_pickle(obj, ...)
调用时,PackageExporter
会像往常一样 pickle 对象。然后,它使用 pickletools
标准库模块来解析 pickle 字节码。
在 pickle 中,对象与一个 GLOBAL
操作码一起保存,该操作码描述了在哪里找到对象类型的实现,例如
GLOBAL 'torchvision.models.resnet Resnet`
依赖解析器会收集所有 GLOBAL
操作码,并将它们标记为您 pickled 对象的依赖项。更多关于 pickling 和 pickle 格式的信息,请查阅 Python 文档。
分析模块依赖项¶
当一个 Python 模块被标识为依赖项时,torch.package
会遍历模块的 Python AST 表示,并查找导入语句,完全支持标准形式: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
:对此模块进行存根处理(mock)。deny
:依赖此模块将在包导出期间引发错误。
最后,还有一个重要的操作,严格来说不属于 torch.package
:
重构:移除或更改代码中的依赖项。
请注意,操作仅定义在整个 Python 模块上。无法只打包模块中的“某个”函数或类而排除其余部分。这是有意为之的。Python 在模块中定义的对象之间没有清晰的界限。唯一定义的依赖组织单位是模块,因此 torch.package
使用模块作为单位。
操作使用模式应用于模块。模式可以是模块名称("foo.bar"
)或 glob(例如 "foo.**"
)。您使用 PackageExporter
上的方法将模式与操作关联起来,例如:
my_exporter.intern("torchvision.**")
my_exporter.extern("numpy")
如果一个模块匹配某个模式,则会对其应用相应的操作。对于给定的模块,模式将按照定义的顺序进行检查,并采用第一个匹配的操作。
intern
¶
如果一个模块被 intern
,它将被放入包中。
此操作适用于您的模型代码或您想打包的任何相关代码。例如,如果您尝试打包来自 torchvision
的 ResNet,您需要 intern
模块 torchvision.models.resnet。
在包导入时,当您的打包代码尝试导入一个被 intern
的模块时,PackageImporter 将在您的包内部查找该模块。如果找不到该模块,将引发错误。这确保了每个 PackageImporter
都与加载环境隔离——即使您的加载环境中也有 my_interned_module
可用,PackageImporter
也只会使用您包中的版本。
注意:只有 Python 源代码模块可以被 intern
。其他类型的模块,如 C 扩展模块和字节码模块,如果您尝试 intern
,则会引发错误。这些类型的模块需要被 mock
或被 extern
。
extern
¶
如果一个模块被 extern
,它将不会被打包。相反,它将被添加到此包的外部依赖项列表中。您可以在 package_exporter.extern_modules
上找到此列表。
在包导入时,当打包代码尝试导入一个被 extern
的模块时,PackageImporter
将使用默认的 Python 导入器来查找该模块,就像您执行了 importlib.import_module("my_externed_module")
一样。如果找不到该模块,将引发错误。
通过这种方式,您可以从包内部依赖第三方库,例如 numpy
和 scipy
,而无需将它们也打包进去。
警告:如果任何外部库发生了向后不兼容的更改,您的包可能会加载失败。如果您的包需要长期可复现性,请尽量限制使用 extern
。
mock
¶
如果一个模块被 mock
,它将不会被打包。取而代之的是,一个存根模块将打包在它的位置。存根模块将允许你从中检索对象(以便 from my_mocked_module import foo
不会出错),但对该对象的任何使用都会引发一个 NotImplementedError
。
mock
应该用于那些你“知道”在加载的包中不需要,但你仍然希望在非打包内容中使用的代码。例如,初始化/配置代码,或仅用于调试/训练的代码。
警告:通常,应将 mock
作为最后的手段。它会引入打包代码和非打包代码之间的行为差异,这可能会导致后续的混淆。最好是重构你的代码以移除不必要的依赖项。
重构¶
管理依赖项的最好方法就是根本没有依赖项!通常,可以通过重构代码来移除不必要的依赖项。这里有一些编写具有清晰依赖关系的代码的指导方针(这些通常也是好的实践!)
只包含你使用的内容。不要在你的代码中留下未使用的导入。依赖项解析器不够智能,无法判断它们确实未使用,并会尝试处理它们。
明确你的导入。例如,不要写 import foo
然后稍后使用 foo.bar.baz
,而最好写 from foo.bar import baz
。这更精确地指定了你真正的依赖项(foo.bar
),并让依赖项解析器知道你不需要完整的 foo
。
将功能不相关的大文件拆分成小文件。如果你的 utils
模块包含各种不相关功能的大杂烩,那么任何依赖于 utils
的模块都需要引入许多不相关的依赖项,即使你只需要其中的一小部分。最好是定义可以相互独立打包的单功能模块。
模式¶
模式允许你使用便捷的语法指定模块组。模式的语法和行为遵循 Bazel/Buck 的 glob() 函数。
我们尝试与模式匹配的模块称为候选模块。候选模块由一个由分隔符字符串分隔的段列表组成,例如 foo.bar.baz
。
一个模式包含一个或多个段。段可以是
一个字面字符串(例如
foo
),它精确匹配。一个包含通配符的字符串(例如
torch
,或foo*baz*
)。通配符匹配任何字符串,包括空字符串。一个双通配符(
**
)。这匹配零个或多个完整段。
示例
torch.**
:匹配torch
及其所有子模块,例如torch.nn
和torch.nn.functional
。torch.*
:匹配torch.nn
或torch.functional
,但不匹配torch.nn.functional
或torch
torch*.**
:匹配torch
、torchvision
及其所有子模块
指定操作时,可以传递多个模式,例如:
exporter.intern(["torchvision.models.**", "torchvision.utils.**"])
如果一个模块匹配其中任何一个模式,则它将与此操作匹配。
你也可以指定排除模式,例如:
exporter.mock("**", exclude=["torchvision.**"])
如果一个模块匹配任何排除模式,它将不会与此操作匹配。在此示例中,我们 mock
所有模块,除了 torchvision
及其子模块。
当一个模块可能匹配多个操作时,将采用定义的第一个操作。
torch.package
的棘手之处¶
避免在模块中使用全局状态¶
Python 使得在模块级别作用域绑定对象和运行代码变得非常容易。这通常是没问题的——毕竟,函数和类就是以这种方式绑定到名称的。然而,当你为了修改它而在模块作用域定义一个对象,从而引入可变的全局状态时,事情就会变得更加复杂。
可变的全局状态非常有用——它可以减少样板代码,允许向表中开放注册等。但除非非常小心地使用,否则与 torch.package
一起使用时可能会导致复杂性。
每个 PackageImporter
都会为其内容创建一个独立的、隔离的环境。这很好,因为这意味着我们可以加载多个包并确保它们彼此隔离,但是当模块编写时假定共享可变的全局状态时,这种行为可能会导致难以调试的错误。
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
。
命名修饰有助于避免不同包之间模块名称的无意双关(punning),并有助于通过使堆栈跟踪和打印语句更清晰地显示它们是否引用了打包的代码来帮助你进行调试。有关面向开发者的命名修饰详情,请查阅 torch/package/
中的 mangling.md
文件。
API 参考¶
- class torch.package.PackagingError(dependency_graph, debug=False)[source][source]¶
当导出包出现问题时,会引发此异常。
PackageExporter
将尝试收集所有错误并一次性呈现给你。
- class torch.package.EmptyMatchError[source][source]¶
当一个
mock
或extern
被标记为allow_empty=False
,并且在打包过程中没有匹配到任何模块时,会抛出此异常。
- class torch.package.PackageExporter(f, importer=<torch.package.importer._SysImporter object>, debug=False)[source][source]¶
导出器允许你将代码包、pickled 的 Python 数据以及任意二进制和文本资源写入一个自包含的包中。
导入可以以独立封闭的方式加载此代码,使得代码从包中加载而不是从正常的 Python 导入系统加载。这允许打包 PyTorch 模型代码和数据,以便可以在服务器上运行或将来用于迁移学习。
包中包含的代码在创建时会逐文件从原始源复制,文件格式是经过特殊组织的 zip 文件。包的未来用户可以解压包,并编辑代码以便对其进行自定义修改。
包的导入器确保模块中的代码只能从包内部加载,除了使用
extern()
显式列为外部的模块。zip 存档中的extern_modules
文件列出了包外部依赖的所有模块。这防止了“隐式”依赖项,即包在本地运行时因为导入了本地安装的包而成功,但在复制到另一台机器时失败。将源代码添加到包中时,导出器可以可选地扫描它以查找进一步的代码依赖项(
dependencies=True
)。它查找 import 语句,将相对引用解析为合格模块名称,并执行用户指定的操作(参见:extern()
、mock()
和intern()
)。- __init__(f, importer=<torch.package.importer._SysImporter object>, debug=False)[source][source]¶
创建一个导出器。
- all_paths(src, dst)[source][source]¶
- 返回子图的 dot 表示形式
包含从 src 到 dst 的所有路径。
- 返回
一个包含从 src 到 dst 的所有路径的 dot 表示形式。(https://graphviz.cpp.org.cn/doc/info/lang.html)
- 返回类型
- close()[source][source]¶
将包写入文件系统。在调用
close()
之后,任何调用都将无效。最好使用资源守护语法代替with PackageExporter("file.zip") as e: ...
- deny(include, *, exclude=())[source][source]¶
从包可以导入的模块列表中,将名称匹配给定 glob 模式的模块列入黑名单。如果发现依赖于任何匹配的包,则会引发一个
PackagingError
。
- 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]) – 一个可选模式,用于排除匹配 include 字符串的某些模式。
allow_empty (bool) – 一个可选标志,指定在此调用
extern
方法时指定的外部模块在打包过程中是否必须匹配到某个模块。如果添加的外部模块 glob 模式设置为allow_empty=False
,并且在任何模块匹配该模式之前调用了close()
(无论是显式调用还是通过__exit__
),则会抛出异常。如果allow_empty=True
,则不会抛出此类异常。
- 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
,则不会抛出此类异常。
- mock(include, *, exclude=(), allow_empty=True)[source][source]¶
替换一些必需的模块,使用模拟(mock)实现。模拟的模块在访问其任何属性时将返回一个虚假对象。由于我们按文件进行复制,依赖解析有时会找到模型文件导入但其功能从未被使用的文件(例如自定义序列化代码或训练辅助工具)。使用此函数可以模拟掉这些功能,而无需修改原始代码。
- 参数
include (Union[List[str], str]) –
一个字符串(例如
"my_package.my_subpackage"
),或一个字符串列表,表示要模拟(mock out)的模块名称。字符串也可以是 glob 风格的模式字符串,可以匹配多个模块。任何匹配此模式字符串的必需依赖项都将被自动模拟(mock out)。- 示例
'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
,则不会抛出此类异常。
- register_extern_hook(hook)[source][source]¶
在导出器上注册一个外部(extern)钩子。
每当一个模块匹配到
extern()
模式时,都会调用此钩子。它应具有以下签名hook(exporter: PackageExporter, module_name: str) -> None
钩子将按照注册顺序被调用。
- 返回
一个句柄,可以通过调用
handle.remove()
来移除添加的钩子。- 返回类型
torch.utils.hooks.RemovableHandle
- register_intern_hook(hook)[source][source]¶
在导出器上注册一个内部(intern)钩子。
每当一个模块匹配到
intern()
模式时,都会调用此钩子。它应具有以下签名hook(exporter: PackageExporter, module_name: str) -> None
钩子将按照注册顺序被调用。
- 返回
一个句柄,可以通过调用
handle.remove()
来移除添加的钩子。- 返回类型
torch.utils.hooks.RemovableHandle
- register_mock_hook(hook)[source][source]¶
在导出器上注册一个模拟(mock)钩子。
每当一个模块匹配到
mock()
模式时,都会调用此钩子。它应具有以下签名hook(exporter: PackageExporter, module_name: str) -> None
钩子将按照注册顺序被调用。
- 返回
一个句柄,可以通过调用
handle.remove()
来移除添加的钩子。- 返回类型
torch.utils.hooks.RemovableHandle
- save_module(module_name, dependencies=True)[source][source]¶
将
module_name
的代码保存到包中。模块的代码是使用importers
路径来查找模块对象,然后使用其__file__
属性来查找源代码的。
- 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
的对象,my_module.MyObject
必须根据importer
的顺序解析到该对象的类。当保存先前已打包的对象时,导入器的import_module
方法需要存在于importer
列表中才能使其工作。
- 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
,我们将扫描源代码以查找依赖项。
- class torch.package.PackageImporter(file_or_buffer, module_allowed=<function PackageImporter.<lambda>>)[source][source]¶
导入器(Importer)允许您加载由
PackageExporter
写入包中的代码。代码以隔离(hermetic)的方式加载,使用包中的文件而不是正常的 Python 导入系统。这使得 PyTorch 模型代码和数据可以打包,以便在服务器上运行或将来用于迁移学习(transfer learning)。包导入器确保模块中的代码只能从包内部加载,除了在导出期间明确列为外部的模块。zip 归档中的
extern_modules
文件列出了包在外部依赖的所有模块。这可以防止“隐式”依赖,即包在本地因为导入了本地安装的包而可以运行,但复制到另一台机器时就会失败。- __init__(file_or_buffer, module_allowed=<function PackageImporter.<lambda>>)[source][source]¶
打开
file_or_buffer
进行导入。这会检查导入的包是否只依赖于module_allowed
允许的模块。
- id()[source][source]¶
返回
torch.package
用于区分PackageImporter
实例的内部标识符。看起来像<torch_package_0>
- import_module(name, package=None)[source][source]¶
如果尚未加载,则从包中加载一个模块,然后返回该模块。模块加载到导入器本地,将出现在
self.modules
中,而不是sys.modules
中。- 参数
- 返回
已(可能已)加载的模块。
- 返回类型
- load_pickle(package, resource, map_location=None)[source][source]¶
从包中解 pickle 资源,并使用
import_module()
加载构造对象所需的任何模块。