評價此頁

torch.package#

建立時間:2025 年 6 月 10 日 | 最後更新時間:2025 年 7 月 15 日

torch.package 增加了對建立包含 PyTorch 工件和任意 PyTorch 程式碼的包的支援。這些包可以被儲存、共享、用於稍後或在不同機器上載入和執行模型,甚至可以使用 torch::deploy 部署到生產環境。

本文件包含教程、操作指南、解釋和 API 參考,將幫助您更多地瞭解 torch.package 以及如何使用它。

警告

此模組依賴於 pickle 模組,該模組不安全。請僅解包您信任的資料。

有可能構造惡意的 pickle 資料,這些資料會在**反序列化過程中執行任意程式碼**。切勿反序列化可能來自不可信來源或可能被篡改過的資料。

有關更多資訊,請參閱 pickle 模組的 文件

教程#

打包您的第一個模型#

一個關於打包和解包簡單模型的教程可在 Colab 上找到。完成此練習後,您將熟悉用於建立和使用 Torch 包的基本 API。

如何…#

檢視包的內容?#

將包視為 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 的 includeexclude 過濾引數。

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 如何依賴於 fooPackageExporter.all_paths() 方法將返回一個 DOT 格式的圖,顯示 srcfoo 之間的所有依賴路徑。

如果您只想檢視 :class:PackageExporter 的整個依賴圖,您可以使用 PackageExporter.dependency_graph_string()

在我的包中包含任意資源並在以後訪問它們?#

PackageExporter 提供了三個方法:save_picklesave_textsave_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_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__

步驟

  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)

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 標準。但是,也可以從打包程式碼中訪問父 :class: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() 函式。注意:如果一個物件來自包,但其定義來自標記為 externstdlib 的模組,此檢查將返回 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)

解釋#

torch.package 格式概述#

torch.package 檔案是一個 ZIP 存檔,通常使用 .pt 副檔名。在 ZIP 存檔內,有兩種檔案:

  • 框架檔案,放在 .data/ 中。

  • 使用者檔案,即其他所有檔案。

例如,這是完全打包的 ResNet 模型來自 torchvision 的樣子:

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:一個模組列表,這些模組被視為 externextern 模組將使用載入環境的系統匯入器匯入。

  • *.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 位元組碼。

在 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")或 glob(如 "foo.**")。您可以使用 PackageExporter 上的方法將模式與操作關聯,例如:

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

如果模組匹配模式,則對其應用相應操作。對於給定的模組,將按定義的順序檢查模式,並採取第一個匹配的操作。

intern#

如果一個模組被 intern,它將被放入包中。

此操作是您的模型程式碼,或您想要打包的任何相關程式碼。例如,如果您試圖打包 torchvision 中的 ResNet,您將需要 intern 模組 torchvision.models.resnet。

在包匯入時,當您的打包程式碼嘗試匯入一個 intern 模組時,PackageImporter 將在您的包中查詢該模組。如果找不到該模組,則會引發錯誤。這確保了每個 PackageImporter 都與載入環境隔離——即使您的包和載入環境中都有 my_interned_modulePackageImporter 也只會使用您包中的版本。

注意:只有 Python 原始碼模組可以被 intern。其他型別的模組,如 C 擴充套件模組和位元組碼模組,如果您嘗試 intern 它們,將引發錯誤。這些型別的模組需要被 mockextern

extern#

如果一個模組被 extern,它將不會被打包。相反,它將被新增到此包的外部依賴項列表中。您可以在 package_exporter.extern_modules 上找到此列表。

在包匯入時,當打包的程式碼嘗試匯入一個 extern 模組時,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*)。萬用字元匹配任何字串,包括空字串。

  • 雙萬用字元(**)。這匹配零個或多個完整段。

示例

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

在此示例中,MyClassimported_MyClass **不是同一型別**。在此特定示例中,MyClassimported_MyClass 具有完全相同的實現,因此您可能認為它們是同一類。但請考慮 imported_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 匯入器相同的核心 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]#

當匯出包時出現問題時,將引發此異常。 PackageExporter 將嘗試收集所有錯誤並將它們一次性呈現給您。

class torch.package.EmptyMatchError[source]#

當 mock 或 extern 被標記為 allow_empty=False 並且在打包過程中沒有與任何模組匹配時,會丟擲此異常。

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

Exporter 允許您將程式碼包、序列化的 Python 資料以及任意二進位制和文字資源寫入一個獨立的包。

匯入可以以密封的方式載入此程式碼,使得程式碼從包中載入而不是從普通的 Python 匯入系統中載入。這使得 PyTorch 模型程式碼和資料可以被打包,以便在伺服器上執行或將來用於遷移學習。

包中包含的程式碼在建立時是逐個檔案從原始源複製的,並且檔案格式是專門組織的 zip 檔案。包的未來使用者可以解壓包,並編輯程式碼以執行自定義修改。

包的匯入器確保模組中的程式碼只能從包內部載入,除了透過 extern() 明確列為外部的模組。zip 存檔中的 extern_modules 檔案列出了包外部依賴的所有模組。這可以防止“隱式”依賴,即包在本地執行,因為它匯入了本地安裝的包,但當包複製到另一臺機器時就會失敗。

當原始碼被新增到包中時,匯出器可以選擇掃描它以查詢更多程式碼依賴項(dependencies=True)。它查詢匯入語句,解析相對引用到限定模組名,並執行使用者指定的動作(請參閱:extern()mock()intern())。

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

建立匯出器。

引數
  • f (Union[str, PathLike[str], IO[bytes]]) – 匯出到的位置。可以是包含檔名或二進位制 I/O 物件的 string/Path 物件。

  • importer (Union[Importer, Sequence[Importer]]) – 如果傳遞單個 Importer,則使用它來搜尋模組。如果傳遞 Importer 序列,則將從中構造一個 OrderedImporter

  • debug (bool) – 如果設定為 True,則將損壞模組的路徑新增到 PackagingErrors。

add_dependency(module_name, dependencies=True)[source]#

給定一個模組,根據使用者指定的模式將其新增到依賴項圖中。

all_paths(src, dst)[source]#
返回子圖的 dot 表示

包含從 src 到 dst 的所有路徑。

返回

包含從 src 到 dst 的所有路徑的字串表示。(https://graphviz.org/doc/info/lang.html

返回型別

str

close()[source]#

將包寫入檔案系統。在 close() 之後的任何呼叫都將無效。更傾向於使用資源保護程式語法

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

返回當前被拒絕的所有模組。

返回

包含在此包中將被拒絕的模組名稱的列表。

返回型別

list[str]

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

阻止匹配給定 glob 模式名稱的模組從包可以匯入的模組列表中排除。如果找到任何匹配包的依賴項,將引發 PackagingError

引數
  • include (Union[List[str], str]) – 一個字串,例如 "my_package.my_subpackage",或模組名稱的字串列表。這也可以是 glob 風格的模式,如 mock() 中所述。

  • exclude (Union[List[str], str]) – 一個可選模式,用於排除匹配 include 字串的一些模式。

dependency_graph_string()[source]#

返回包中依賴項的有向圖字串表示。

返回

包中依賴項的字串表示。

返回型別

str

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

module 包含在包可以匯入的外部模組列表中。這將阻止依賴項發現將其儲存在包中。匯入器將直接從標準匯入系統載入外部模組。外部模組的程式碼也必須存在於載入該包的程序中。

引數
  • include (Union[List[str], str]) – 一個字串,例如 "my_package.my_subpackage",或模組名稱的字串列表。這也可以是 glob 風格的模式,如 mock() 中所述。

  • exclude (Union[List[str], str]) – 一個可選模式,用於排除匹配 include 字串的一些模式。

  • allow_empty (bool) – 一個可選標誌,用於指定此 extern 方法呼叫指定的外部模組是否必須在打包過程中與某些模組匹配。如果使用 allow_empty=False 添加了外部模組 glob 模式,並且在任何模組匹配該模式之前呼叫了 close()(顯式呼叫或透過 __exit__),則會引發異常。如果 allow_empty=True,則不會引發此類異常。

externed_modules()[source]#

返回當前被外部化的所有模組。

返回

包含此包中將被外部化的模組名稱的列表。

返回型別

list[str]

get_rdeps(module_name)[source]#

返回依賴於 module_name 模組的所有模組列表。

返回

包含依賴於 module_name 的模組名稱的列表。

返回型別

list[str]

get_unique_id()[source]#

獲取一個 ID。此 ID 保證對於此包僅分配一次。

返回型別

str

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

指定要打包的模組。模組必須匹配某個 intern 模式才能包含在包中並遞迴地處理其依賴項。

引數
  • include (Union[List[str], str]) – 一個字串,例如“my_package.my_subpackage”,或模組名稱的字串列表。這也可以是 glob 風格的模式,如 mock() 中所述。

  • exclude (Union[List[str], str]) – 一個可選模式,用於排除匹配 include 字串的一些模式。

  • allow_empty (bool) – 一個可選標誌,用於指定此 intern 方法呼叫指定的內部模組是否必須在打包過程中與某些模組匹配。如果使用 allow_empty=False 添加了內部模組 glob 模式,並且在任何模組匹配該模式之前呼叫了 close()(顯式呼叫或透過 __exit__),則會引發異常。如果 allow_empty=True,則不會引發此類異常。

interned_modules()[source]#

返回當前被內部化的所有模組。

返回

包含此包中將被內部化的模組名稱的列表。

返回型別

list[str]

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

用模擬實現替換一些必需的模組。模擬的模組將為從中訪問的任何屬性返回一個假物件。由於我們逐個檔案複製,依賴項解析有時會找到模型檔案匯入但其功能從未使用過的檔案(例如,自定義序列化程式碼或訓練助手)。使用此函式模擬此功能,而無需修改原始程式碼。

引數
  • 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 字串的一些模式。例如 include='torch.**', exclude='torch.foo' 將模擬所有 torch 包,除了 'torch.foo',預設:是 []

  • allow_empty (bool) – 一個可選標誌,用於指定此 mock() 方法呼叫指定的模擬實現是否必須在打包過程中與某些模組匹配。如果使用 allow_empty=False 添加了模擬,並且在匯出包所使用的模組未匹配模擬的情況下呼叫 close()(顯式呼叫或透過 __exit__),則會引發異常。如果 allow_empty=True,則不會引發此類異常。

mocked_modules()[source]#

返回當前被模擬的所有模組。

返回

包含此包中將被模擬的模組名稱的列表。

返回型別

list[str]

register_extern_hook(hook)[source]#

在匯出器上註冊一個外部鉤子。

每次模組匹配 extern() 模式時,都會呼叫該鉤子。它應該具有以下簽名:

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

鉤子將按註冊順序呼叫。

返回

可以透過呼叫 handle.remove() 來移除已新增鉤子的控制代碼。

返回型別

torch.utils.hooks.RemovableHandle

register_intern_hook(hook)[source]#

在匯出器上註冊一個內部鉤子。

每次模組匹配 intern() 模式時,都會呼叫該鉤子。它應該具有以下簽名:

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

鉤子將按註冊順序呼叫。

返回

可以透過呼叫 handle.remove() 來移除已新增鉤子的控制代碼。

返回型別

torch.utils.hooks.RemovableHandle

register_mock_hook(hook)[source]#

在匯出器上註冊一個模擬鉤子。

每次模組匹配 mock() 模式時,都會呼叫該鉤子。它應該具有以下簽名:

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

鉤子將按註冊順序呼叫。

返回

可以透過呼叫 handle.remove() 來移除已新增鉤子的控制代碼。

返回型別

torch.utils.hooks.RemovableHandle

save_binary(package, resource, binary)[source]#

將原始位元組儲存到包中。

引數
  • package (str) – 此資源應歸屬的模組包名稱(例如 "my_package.my_subpackage")。

  • resource (str) – 資源的唯一名稱,用於標識它以進行載入。

  • binary (str) – 要儲存的資料。

save_module(module_name, dependencies=True)[source]#

module 的程式碼儲存到包中。模組程式碼的解析方式是:首先使用 importers 路徑查詢模組物件,然後使用其 __file__ 屬性查詢原始檔。

引數
  • module_name (str) – 例如 my_package.my_subpackage,程式碼將儲存以提供此包的程式碼。

  • dependencies (bool, optional) – 如果為 True,則掃描原始檔以查詢依賴項。

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

使用 pickle 將 Python 物件儲存到存檔。等同於 torch.save(),但儲存到存檔而不是獨立檔案。標準 pickle 不儲存程式碼,只儲存物件。如果 dependencies 為 true,此方法還將掃描 pickled 物件以確定重構它們所需的模組,並儲存相關程式碼。

要能夠儲存一個物件,其中 type(obj).__name__my_module.MyObjectmy_module.MyObject 必須根據 importer 順序解析為物件的類。儲存先前已打包的物件時,需要 importer 列表包含 importerimport_module 方法才能正常工作。

引數
  • package (str) – 此資源應歸屬的模組包名稱(例如 "my_package.my_subpackage")。

  • resource (str) – 資源的唯一名稱,用於標識它以進行載入。

  • obj (Any) – 要儲存的物件,必須是可 pickle 的。

  • dependencies (bool, optional) – 如果為 True,則掃描原始檔以查詢依賴項。

save_source_file(module_name, file_or_directory, dependencies=True)[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,則掃描原始檔以查詢依賴項。

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

src 作為 module_name 的原始碼新增到匯出的包中。

引數
  • module_name (str) – 例如 my_package.my_subpackage,程式碼將儲存以提供此包的程式碼。

  • src (str) – 要為此包儲存的 Python 原始碼。

  • is_package (bool, optional) – 如果為 True,則此模組被視為一個包。包允許包含子模組(例如 my_package.my_subpackage.my_subsubpackage),並且資源可以儲存在其中。預設為 False

  • dependencies (bool, optional) – 如果為 True,則掃描原始檔以查詢依賴項。

save_text(package, resource, text)[source]#

將文字資料儲存到包中。

引數
  • package (str) – 此資源應歸屬的模組包名稱(例如 "my_package.my_subpackage")。

  • resource (str) – 資源的唯一名稱,用於標識它以進行載入。

  • text (str) – 要儲存的內容。

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

Importer 允許您載入由 PackageExporter 寫入包的程式碼。程式碼以隔離的方式載入,使用包中的檔案而不是常規的 Python import 系統。這允許打包 PyTorch 模型程式碼和資料,以便可以在伺服器上執行,或者將來用於遷移學習。

包的 Importer 確保模組中的程式碼只能從包內載入,除非在匯出時顯式列為外部的模組。zip 存檔中的 extern_modules 檔案列出了包外部依賴的所有模組。這可以防止“隱式”依賴,即當包在本地執行,因為它匯入了一個本地安裝的包,但在包被複制到另一臺機器上時會失敗。

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

開啟 file_or_buffer 以進行匯入。這將檢查匯入的包是否僅需要 module_allowed 允許的模組。

引數
  • file_or_buffer (Union[str, PathLike[str], IO[bytes], PyTorchFileReader]) – 一個檔案類物件(必須實現 read()readline()tell()seek()),一個字串,或一個包含檔名的 os.PathLike 物件。

  • module_allowed (Callable[[str], bool], optional) – 用於確定是否允許外部提供的模組的函式。可用於確保載入的包不依賴於伺服器不支援的模組。預設為允許任何模組。

引發

ImportError – 如果包將使用不允許的模組。

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

返回包 zipfile 的檔案結構表示。

引數
  • include (Union[List[str], str]) – 可選字串,例如 "my_package.my_subpackage",或可選字串列表,用於在 zipfile 表示中包含的檔名。這也可以是 glob 風格的模式,如 PackageExporter.mock() 中所述。

  • exclude (Union[List[str], str]) – 一個可選模式,用於排除名稱與該模式匹配的檔案。

返回

目錄

返回型別

目錄

id()[source]#

返回 torch.package 用於區分 PackageImporter 例項的內部識別符號。看起來像

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

如果模組尚未載入,則從包中載入該模組,然後返回該模組。模組在 Importer 本地載入,並且將出現在 self.modules 中,而不是 sys.modules 中。

引數
  • name (str) – 要載入的模組的完全限定名稱。

  • package ([type], optional) – 未使用,但存在以匹配 importlib.import_module 的簽名。預設為 None

返回

已(可能已)載入的模組。

返回型別

types.ModuleType

load_binary(package, resource)[source]#

載入原始位元組。

引數
  • package (str) – 模組包的名稱(例如 "my_package.my_subpackage")。

  • resource (str) – 資源的唯一名稱。

返回

載入的資料。

返回型別

位元組

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

從包中反序列化資源,使用 import_module() 載入構造物件所需的任何模組。

引數
  • package (str) – 模組包的名稱(例如 "my_package.my_subpackage")。

  • resource (str) – 資源的唯一名稱。

  • map_location – 傳遞給 torch.load 以確定張量如何對映到裝置。預設為 None

返回

反序列化的物件。

返回型別

任何

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

載入字串。

引數
  • package (str) – 模組包的名稱(例如 "my_package.my_subpackage")。

  • resource (str) – 資源的唯一名稱。

  • encoding (str, optional) – 傳遞給 decode。預設為 'utf-8'

  • errors (str, optional) – 傳遞給 decode。預設為 'strict'

返回

載入的文字。

返回型別

str

python_version()[source]#

返回用於建立此包的 Python 版本。

注意:此函式是實驗性的,不相容向前。計劃稍後將其移至鎖定檔案。

返回

Optional[str] 一個 Python 版本,例如 3.8.9,如果此包未儲存版本,則為 None。

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

檔案結構表示。組織為目錄節點,這些節點具有其目錄子列表。包的目錄是透過呼叫 PackageImporter.file_structure() 建立的。

has_file(filename)[source]#

檢查檔案是否存在於 Directory 中。

引數

filename (str) – 要搜尋的檔案路徑。

返回

如果 Directory 包含指定的檔案。

返回型別

布林值