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 的普通打包过程定义 __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.package
)。
目前,.data/
目录包含以下项
version
:序列化格式的版本号,以便torch.package
导入基础设施知道如何加载此包。extern_modules
:被认为是extern
的模块列表。extern
模块将使用加载环境的系统导入器导入。*.storage
:序列化的张量数据。
.data
├── 94286146172688.storage
├── 94286146172784.storage
├── extern_modules
├── version
├── ...
用户文件¶
存档中的所有其他文件都是用户放在那里的。布局与 Python 常规包 相同。要深入了解 Python 打包的工作原理,请查阅 本文(它略有过时,因此请使用 Python 参考文档 仔细检查实现细节)。
<package root>
├── model # the pickled model
│ └── model.pkl
├── another_package
│ ├── __init__.py
│ ├── foo.txt # a resource file , see importlib.resources
│ └── ...
└── torchvision
└── models
├── resnet.py # torchvision.models.resnet
└── utils.py # torchvision.models.utils
torch.package
如何查找代码的依赖项¶
分析对象的依赖项¶
当您发出 save_pickle(obj, ...)
调用时,PackageExporter
将正常地 pickle 对象。然后,它使用 pickletools
标准库模块来解析 pickle 字节码。
在 pickle 中,对象与其类型的实现位置的 GLOBAL
操作码一起保存,例如
GLOBAL 'torchvision.models.resnet Resnet`
依赖关系解析器将收集所有 GLOBAL
操作,并将它们标记为 pickle 对象的依赖项。有关 pickle 和 pickle 格式的更多信息,请查阅 Python 文档。
分析模块的依赖项¶
当 Python 模块被识别为依赖项时,torch.package
会遍历模块的 python AST 表示,并查找导入语句,完全支持标准形式:from x import 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"
)或 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.**"])
如果模块匹配任何排除模式,则该模块将不匹配此操作。在此示例中,我们模拟了除 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
。
名称修改有助于避免不同包之间模块名称的意外混淆,并通过使堆栈跟踪和打印语句更清楚地显示它们是指打包代码还是非打包代码来帮助您调试。有关名称修改的面向开发人员的详细信息,请查阅 torch/package/
中的 mangling.md
。
API 参考¶
- class torch.package.PackagingError(dependency_graph, debug=False)[source][source]¶
当导出包时出现问题,则会引发此异常。
PackageExporter
将尝试收集所有错误并一次性呈现给您。
- class torch.package.EmptyMatchError[source][source]¶
当标记为
allow_empty=False
的 mock 或 extern 在打包期间未与任何模块匹配时,会抛出此异常。
- class torch.package.PackageExporter(f, importer=<torch.package.importer._SysImporter object>, debug=False)[source][source]¶
导出器允许您将代码包、pickle 化的 Python 数据以及任意二进制和文本资源写入到自包含的包中。
导入可以以封闭的方式加载此代码,以便从包而不是正常的 Python 导入系统加载代码。这允许打包 PyTorch 模型代码和数据,以便它可以在服务器上运行或将来用于迁移学习。
包中包含的代码在创建时会逐文件地从原始来源复制,文件格式是经过特殊组织的 zip 文件。包的未来用户可以解压缩包,并编辑代码以对其执行自定义修改。
包的导入器确保模块中的代码只能从包内部加载,除非使用
extern()
显式列为外部模块。zip 存档中的文件extern_modules
列出了包外部依赖的所有模块。这可以防止“隐式”依赖项,即包在本地运行时可以正常工作,因为它导入了本地安装的包,但是当包复制到另一台机器时会失败。当源代码添加到包中时,导出器可以选择扫描它以查找进一步的代码依赖项 (
dependencies=True
)。它查找导入语句,解析对限定模块名称的相对引用,并执行用户指定的操作(请参阅:extern()
、mock()
和intern()
)。- __init__(f, importer=<torch.package.importer._SysImporter object>, debug=False)[source][source]¶
创建一个导出器。
- 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()
中所述。allow_empty (bool) – 一个可选标志,用于指定通过此
extern
方法调用指定的外部模块是否必须在打包期间与某些模块匹配。如果添加了allow_empty=False
的外部模块 glob 模式,并且在任何模块与该模式匹配之前调用了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()
中所述。allow_empty (bool) – 一个可选标志,用于指定通过此
intern
方法调用指定的内部模块是否必须在打包期间与某些模块匹配。如果添加了allow_empty=False
的intern
模块 glob 模式,并且在任何模块与该模式匹配之前调用了close()
(显式调用或通过__exit__
),则会抛出异常。如果allow_empty=True
,则不会抛出此类异常。
- mock(include, *, exclude=(), allow_empty=True)[source][source]¶
用模拟实现替换某些必需的模块。模拟模块将为其访问的任何属性返回一个伪造对象。由于我们是逐文件复制,因此依赖项解析有时会找到模型文件导入但其功能从未使用过的文件(例如,自定义序列化代码或训练助手)。使用此功能可以模拟此功能,而无需修改原始代码。
- 参数
include (Union[List[str], str]) –
字符串,例如
"my_package.my_subpackage"
,或要模拟的模块名称的字符串列表。字符串也可以是 glob 样式模式字符串,可以匹配多个模块。与此模式字符串匹配的任何必需依赖项都将自动模拟。- 示例
'torch.**'
– 匹配torch
和 torch 的所有子模块,例如'torch.nn'
和'torch.nn.functional'
'torch.*'
– 匹配'torch.nn'
或'torch.functional'
,但不匹配'torch.nn.functional'
exclude (Union[List[str], str]) – 一个可选模式,用于排除与 include 字符串匹配的某些模式。例如
include='torch.**', exclude='torch.foo'
将模拟除'torch.foo'
之外的所有 torch 包,默认值: 为[]
。allow_empty (bool) – 一个可选标志,用于指定通过此
mock()
方法调用指定的模拟实现是否必须在打包期间与某些模块匹配。如果添加了allow_empty=False
的模拟,并且调用了close()
(显式调用或通过__exit__
),并且该模拟尚未与正在导出的包使用的模块匹配,则会抛出异常。如果allow_empty=True
,则不会抛出此类异常。
- register_extern_hook(hook)[source][source]¶
在导出器上注册一个外部钩子。
每当模块与
extern()
模式匹配时,将调用该钩子。它应具有以下签名hook(exporter: PackageExporter, module_name: str) -> None
将按照注册顺序调用钩子。
- 返回
一个句柄,可用于通过调用
handle.remove()
来删除添加的钩子。- 返回类型
torch.utils.hooks.RemovableHandle
- register_intern_hook(hook)[source][source]¶
在导出器上注册一个内部钩子。
每当模块与
intern()
模式匹配时,将调用该钩子。它应具有以下签名hook(exporter: PackageExporter, module_name: str) -> None
将按照注册顺序调用钩子。
- 返回
一个句柄,可用于通过调用
handle.remove()
来删除添加的钩子。- 返回类型
torch.utils.hooks.RemovableHandle
- register_mock_hook(hook)[source][source]¶
在导出器上注册一个模拟钩子。
每当模块与
mock()
模式匹配时,将调用该钩子。它应具有以下签名hook(exporter: PackageExporter, module_name: str) -> None
将按照注册顺序调用钩子。
- 返回
一个句柄,可用于通过调用
handle.remove()
来删除添加的钩子。- 返回类型
torch.utils.hooks.RemovableHandle
- save_module(module_name, dependencies=True)[source][source]¶
将
module
的代码保存到包中。模块的代码使用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
的对象,根据importer
顺序,my_module.MyObject
必须解析为对象的类。当保存先前已打包的对象时,导入器的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]¶
导入器允许您加载由
PackageExporter
写入到包中的代码。代码以封闭式方式加载,使用包中的文件而不是普通的 Python 导入系统。这允许打包 PyTorch 模型代码和数据,以便可以在服务器上运行或将来用于迁移学习。包的导入器确保模块中的代码只能从包内部加载,但显式列为外部模块的模块除外(在导出期间)。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)[源代码][源代码]¶
从包中反序列化资源,加载构建对象所需的任何模块,使用
import_module()
。