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 的正常 pickle 過程定義 __reduce__
。
步驟
在目標類別上定義方法
__reduce_package__(self, exporter: PackageExporter)
。 此方法應完成將類別實例儲存在套件內的工作,並且應返回一個元組,其中包含相應的解封裝函式以及調用解封裝函式所需的參數。 當PackageExporter
遇到目標類別的實例時,會呼叫此方法。為類別定義一個解封裝 (de-packaging) 函式。這個解封裝函式應該執行重建並傳回該類別實例的工作。該函式簽名的第一個參數應該是一個
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
操作碼一起儲存,該操作碼描述了在哪裡可以找到物件類型(例如 class)的實作,如下所示:
GLOBAL 'torchvision.models.resnet Resnet`
相依性解析器會收集所有 GLOBAL
操作碼,並將它們標記為您 pickle 物件的相依性。有關 pickling 和 pickle 格式的更多資訊,請參閱 Python 文件。
分析模組的相依性¶
當 Python 模組被識別為相依性時,torch.package
會遍歷該模組的 Python AST 表示形式,並尋找 import 語句,並完全支援標準形式:from x import y
、import z
、from w import v as u
等。當遇到這些 import 語句之一時,torch.package
會將匯入的模組註冊為相依性,然後以相同的方式進行 AST 遍歷剖析。
注意:AST 剖析對 __import__(...)
語法的支援有限,並且不支援 importlib.import_module
呼叫。一般來說,您不應期望 torch.package
會偵測到動態匯入。
相依性管理¶
torch.package
會自動尋找您的程式碼和物件所相依的 Python 模組。此過程稱為相依性解析。對於相依性解析器找到的每個模組,您必須指定要採取的動作。
允許的動作如下:
intern
:將此模組放入套件中。extern
:將此模組宣告為套件的外部相依性。mock
:stub 掉此模組。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
-ed 模組時,PackageImporter 將在您的套件中尋找該模組。如果找不到該模組,則會引發錯誤。這可確保每個 PackageImporter
與加載環境隔離——即使您的套件和加載環境中都有 my_interned_module
可用,PackageImporter
也只會使用您套件中的版本。
注意:只有 Python 原始碼模組可以被 intern
。如果您嘗試 intern
其他類型的模組,例如 C 擴充模組和位元組碼模組,則會引發錯誤。這些模組需要被 mock
或 extern
。
extern
¶
如果一個模組被 extern
-ed,它將不會被封裝。相反,它會被添加到此套件的外部相依性清單中。您可以在 package_exporter.extern_modules
上找到此清單。
在套件匯入時,當封裝的程式碼嘗試匯入一個 extern
-ed 模組時,PackageImporter
將使用預設的 Python importer 尋找該模組,就像您執行了 importlib.import_module("my_externed_module")
一樣。如果找不到該模組,則會引發錯誤。
透過這種方式,您可以從您的套件中相依於像 numpy
和 scipy
這樣的第三方函式庫,而無需也封裝它們。
警告:如果任何外部函式庫以向後不相容的方式發生變更,您的套件可能無法載入。如果您需要套件的長期可重現性,請盡量限制您使用 extern
。
mock
¶
如果一個模組被 mock
-ed,它將不會被封裝。相反,一個 stub 模組將被封裝到它的位置。 stub 模組將允許您從中檢索物件(因此 from my_mocked_module import foo
不會出錯),但是對該物件的任何使用都會引發 NotImplementedError
。
mock
應該用於那些您「知道」在已載入的套件中不需要,但您仍然希望在非套件內容中可用的程式碼。例如,初始化/配置程式碼,或僅用於除錯/訓練的程式碼。
警告:一般來說,mock
應該作為最後的手段。它會在套件化的程式碼和非套件化的程式碼之間引入行為差異,這可能會導致後續的混淆。建議改為重構您的程式碼以移除不需要的依賴關係。
重構¶
管理依賴關係最好的方法是完全沒有依賴關係!通常,程式碼可以被重構以移除不必要的依賴關係。以下是一些編寫具有乾淨依賴關係的程式碼的指南(這些通常也是好的實踐!)
只包含您使用的東西。不要在您的程式碼中留下未使用的 import。依賴關係解析器不夠聰明,無法判斷它們是否真的未使用,並會嘗試處理它們。
限定您的 import。例如,不要寫 `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 環境,來滿足未來的匯入請求。
名稱混淆 (Mangling)¶
為了避免混淆(“這個 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]¶
當 mock 或 extern 被標記為
allow_empty=False
,並且在打包期間沒有與任何模組匹配時,會拋出此例外。
- 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
)。它會尋找 import 語句、解析對限定模組名稱的相對引用,並執行使用者指定的操作 (請參閱:extern()
、mock()
和intern()
)。- __init__(f, importer=<torch.package.importer._SysImporter object>, debug=False)[source][source]¶
建立匯出器。
- all_paths(src, dst)[source][source]¶
- 傳回具有從 src 到 dst 的所有路徑的子圖的點表示。
具有從 src 到 dst 的所有路徑的子圖的點表示。
- 傳回
包含從 src 到 dst 的所有路徑的點表示。 (https://graphviz.dev.org.tw/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)[原始碼][原始碼]¶
將
module
包含在封裝可以匯入的外部模組清單中。這將防止相依性探索將其儲存在封裝中。匯入器將直接從標準匯入系統載入外部模組。外部模組的程式碼也必須存在於載入封裝的程序中。- 參數
include (Union[List[str], str]) – 一個字串,例如
"my_package.my_subpackage"
,或是要設為外部模組的模組名稱字串清單。這也可以是 glob 樣式 (glob-style) 的模式 (pattern),如mock()
中所述。exclude (Union[List[str], str]) – 一個可選的模式 (pattern),用於排除符合 include 字串的某些模式。
allow_empty (bool) – 一個可選的旗標,用於指定呼叫
extern
方法所指定的外部模組是否必須在封裝期間與某些模組相符。如果使用allow_empty=False
新增了外部模組的 glob 模式,並且在任何模組符合該模式之前呼叫了close()
(明確地或透過__exit__
),則會擲回例外。如果allow_empty=True
,則不會擲回此類例外。
- intern(include, *, exclude=(), allow_empty=True)[原始碼][原始碼]¶
指定應封裝的模組。模組必須符合某些
intern
模式才能包含在封裝中,並使其相依性以遞迴方式處理。- 參數
include (Union[List[str], str]) – 一個字串,例如 “my_package.my_subpackage”,或是要設為外部模組的模組名稱字串清單。這也可以是 glob 樣式 (glob-style) 的模式 (pattern),如
mock()
中所述。exclude (Union[List[str], str]) – 一個可選的模式 (pattern),用於排除符合 include 字串的某些模式。
allow_empty (bool) – 一個可選的旗標,用於指定呼叫
intern
方法所指定的內部模組是否必須在封裝期間與某些模組相符。如果使用allow_empty=False
新增了intern
模組的 glob 模式,並且在任何模組符合該模式之前呼叫了close()
(明確地或透過__exit__
),則會擲回例外。如果allow_empty=True
,則不會擲回此類例外。
- mock(include, *, exclude=(), allow_empty=True)[原始碼][原始碼]¶
使用 mock 實作替換一些必要的模組。 Mock 的模組將針對從中存取的任何屬性傳回一個假的物件。 因為我們是逐個複製檔案,所以依賴性解析有時會找到由模型檔案匯入但其功能從未使用過的檔案 (例如,自定義序列化程式碼或訓練輔助程式)。 使用此函數來 mock 掉此功能,而無需修改原始程式碼。
- 參數
include (Union[List[str], str]) –
一個字串,例如
"my_package.my_subpackage"
,或用於要 mock 掉的模組名稱的字串列表。 字串也可以是 glob 樣式的模式字串,它可以匹配多個模組。 與此模式字串匹配的任何必要依賴項將會自動被 mock 掉。- 範例
'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'
將 mock 掉除了'torch.foo'
之外的所有 torch 套件,預設值為[]
。allow_empty (bool) – 一個可選的標誌,用於指定在封裝期間,對
mock()
方法的此呼叫所指定的 mock 實作是否必須與某些模組匹配。 如果新增了一個 mock 且allow_empty=False
,並且呼叫了close()
(顯式呼叫或透過__exit__
),並且該 mock 尚未與正在匯出的封裝所使用的模組匹配,則會拋出例外。 如果allow_empty=True
,則不會拋出此類例外。
- register_extern_hook(hook)[原始碼][原始碼]¶
在 exporter 上註冊一個 extern hook。
每次模組與
extern()
模式匹配時,都會呼叫該 hook。 它應具有以下簽名hook(exporter: PackageExporter, module_name: str) -> None
Hooks 將按照註冊順序被呼叫。
- 傳回
可以使用此句柄透過呼叫
handle.remove()
來刪除新增的 hook。- 傳回類型
torch.utils.hooks.RemovableHandle
- register_intern_hook(hook)[原始碼][原始碼]¶
在 exporter 上註冊一個 intern hook。
每次模組與
intern()
模式匹配時,都會呼叫該 hook。 它應具有以下簽名hook(exporter: PackageExporter, module_name: str) -> None
Hooks 將按照註冊順序被呼叫。
- 傳回
可以使用此句柄透過呼叫
handle.remove()
來刪除新增的 hook。- 傳回類型
torch.utils.hooks.RemovableHandle
- register_mock_hook(hook)[原始碼][原始碼]¶
在 exporter 上註冊一個 mock hook。
每次模組與
mock()
模式匹配時,都會呼叫該 hook。 它應具有以下簽名hook(exporter: PackageExporter, module_name: str) -> None
Hooks 將按照註冊順序被呼叫。
- 傳回
可以使用此句柄透過呼叫
handle.remove()
來刪除新增的 hook。- 傳回類型
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,則此方法還會掃描 pickled 物件,以了解需要哪些模組才能重建它們,並儲存相關的程式碼。為了能夠儲存
type(obj).__name__
為my_module.MyObject
的物件,根據importer
的順序,my_module.MyObject
必須解析為物件的類別。 儲存先前已封裝的物件時,匯入器的import_module
方法需要出現在importer
列表中,這樣才能正常運作。
- save_source_file(module_name, file_or_directory, dependencies=True)[原始碼][原始碼]¶
將本機檔案系統
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()[原始碼][原始碼]¶
傳回內部識別碼,torch.package 用於區分
PackageImporter
實例。看起來像<torch_package_0>
- import_module(name, package=None)[原始碼][原始碼]¶
如果模組尚未載入,則從套件載入模組,然後傳回模組。 模組會在本機載入到 importer,並會出現在
self.modules
而非sys.modules
中。- 參數
- 傳回
(可能已載入的)模組。
- 傳回類型
- load_pickle(package, resource, map_location=None)[原始碼][原始碼]¶
從套件中反序列化資源,使用
import_module()
載入建構物件所需的任何模組。