捷徑

torch.package

torch.package 新增了對建立包含成品和任意 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 如何依賴於 foo,則 PackageExporter.all_paths() 方法將返回一個 DOT 格式的圖,顯示 srcfoo 之間的所有相依路徑。

如果您只想查看 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 的正常 pickle 過程定義 __reduce__

步驟

  1. 在目標類別上定義方法 __reduce_package__(self, exporter: PackageExporter)。 此方法應完成將類別實例儲存在套件內的工作,並且應返回一個元組,其中包含相應的解封裝函式以及調用解封裝函式所需的參數。 當 PackageExporter 遇到目標類別的實例時,會呼叫此方法。

  2. 為類別定義一個解封裝 (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_pickleload_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 yimport zfrom 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 擴充模組和位元組碼模組,則會引發錯誤。這些模組需要被 mockextern

extern

如果一個模組被 extern-ed,它將不會被封裝。相反,它會被添加到此套件的外部相依性清單中。您可以在 package_exporter.extern_modules 上找到此清單。

在套件匯入時,當封裝的程式碼嘗試匯入一個 extern-ed 模組時,PackageImporter 將使用預設的 Python importer 尋找該模組,就像您執行了 importlib.import_module("my_externed_module") 一樣。如果找不到該模組,則會引發錯誤。

透過這種方式,您可以從您的套件中相依於像 numpyscipy 這樣的第三方函式庫,而無需也封裝它們。

警告:如果任何外部函式庫以向後不相容的方式發生變更,您的套件可能無法載入。如果您需要套件的長期可重現性,請盡量限制您使用 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 為其內容建立一個獨立的環境。 這很好,因為這意味著我們可以載入多個套件,並確保它們彼此隔離,但是當模組以假設共享可變全域狀態的方式編寫時,此行為可能會產生難以除錯的錯誤。

類型在套件和載入環境之間不共享

您從 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!

在此範例中,`MyClass` 和 `imported_MyClass` *不是同一種類型*。 在這個具體的例子中,`MyClass` 和 `imported_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 環境,來滿足未來的匯入請求。

名稱混淆 (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]

建立匯出器。

參數
  • f (Union[str, Path, BinaryIO]) – 要匯出的位置。可以是包含檔案名稱的 string/ Path 物件或二進制 I/O 物件。

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

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

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

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

all_paths(src, dst)[source][source]
傳回具有從 src 到 dst 的所有路徑的子圖的點表示。

具有從 src 到 dst 的所有路徑的子圖的點表示。

傳回

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

傳回類型

str

close()[source][source]

將封裝寫入到檔案系統。 在呼叫 close() 之後的任何呼叫現在都無效。 最好改用資源保護語法

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

傳回目前拒絕的所有模組。

傳回

包含將在此封裝中拒絕的模組名稱的清單。

傳回類型

List[str]

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

將名稱與給定的 glob 模式匹配的模組從封裝可以匯入的模組清單中加入黑名單。 如果找到對任何匹配封裝的依賴關係,則會引發 PackagingError

參數
  • include (Union[List[str], str]) – 一個字串,例如 "my_package.my_subpackage",或是要設為外部模組的模組名稱字串清單。這也可以是 glob 樣式 (glob-style) 的模式 (pattern),如 mock() 中所述。

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

dependency_graph_string()[原始碼][原始碼]

傳回封裝中相依性的有向圖字串表示形式。

傳回

封裝中相依性的字串表示形式。

傳回類型

str

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,則不會擲回此類例外。

externed_modules()[原始碼][原始碼]

傳回目前設定為外部模組的所有模組。

傳回

包含將在此封裝中設為外部模組的模組名稱清單。

傳回類型

List[str]

get_rdeps(module_name)[原始碼][原始碼]

傳回依賴於模組 module_name 的所有模組的清單。

傳回

包含依賴於 module_name 的模組名稱清單。

傳回類型

List[str]

get_unique_id()[原始碼][原始碼]

取得一個 ID。保證此 ID 僅針對此封裝發放一次。

傳回類型

str

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,則不會擲回此類例外。

interned_modules()[原始碼][原始碼]

傳回所有目前被內部化的模組。

傳回

一個包含模組名稱的列表,這些模組將在此封裝中被內部化。

傳回類型

List[str]

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,則不會拋出此類例外。

mocked_modules()[原始碼][原始碼]

傳回所有目前被 mock 的模組。

傳回

一個包含模組名稱的列表,這些模組將在此封裝中被 mock。

傳回類型

List[str]

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_binary(package, resource, binary)[原始碼][原始碼]

將原始位元組儲存到套件中。

參數
  • package (str) – 此資源應放入的模組套件名稱(例如, "my_package.my_subpackage")。

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

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

save_module(module_name, dependencies=True)[原始碼][原始碼]

module 的程式碼儲存到套件中。 模組的程式碼是使用 importers 路徑來尋找模組物件來解析的,然後使用其 __file__ 屬性來尋找原始碼。

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

  • dependencies (bool, optional) – 如果 True,我們會掃描原始碼以尋找相依性。

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 列表中,這樣才能正常運作。

參數
  • 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)[原始碼][原始碼]

將本機檔案系統 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)[原始碼][原始碼]

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][source]

將文字資料儲存到套件中。

參數
  • package (str) – 此資源應放入的模組套件名稱(例如, "my_package.my_subpackage")。

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

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

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 允許的模組。

參數
  • file_or_buffer (Union[str, PyTorchFileReader, PathLike, BinaryIO]) – 類似檔案的物件 (必須實作 read(), readline(), tell(), 和 seek()), 字串,或包含檔案名稱的 os.PathLike 物件。

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

引發

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

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

傳回套件 zip 檔案的檔案結構表示。

參數
  • include (Union[List[str], str]) – 可選的字串,例如 "my_package.my_subpackage",或要包含在 zip 檔案表示中的檔案名稱的可選字串列表。這也可以是 glob 樣式的模式,如 PackageExporter.mock() 中所述。

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

傳回

目錄

傳回類型

目錄

id()[原始碼][原始碼]

傳回內部識別碼,torch.package 用於區分 PackageImporter 實例。看起來像

<torch_package_0>
import_module(name, package=None)[原始碼][原始碼]

如果模組尚未載入,則從套件載入模組,然後傳回模組。 模組會在本機載入到 importer,並會出現在 self.modules 而非 sys.modules 中。

參數
  • name ( str) – 要載入模組的完整名稱。

  • package ([type], optional) – 未使用,但存在以符合 importlib.import_module 的簽章。 預設值為 None

傳回

(可能已載入的)模組。

傳回類型

types.ModuleType

load_binary(package, resource)[原始碼][原始碼]

載入原始位元組。

參數
  • package ( str) – 模組套件的名稱(例如 "my_package.my_subpackage")。

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

傳回

載入的資料。

傳回類型

bytes

load_pickle(package, resource, map_location=None)[原始碼][原始碼]

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

參數
  • package ( str) – 模組套件的名稱(例如 "my_package.my_subpackage")。

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

  • map_location – 傳遞至 torch.load 以決定如何將張量對應到裝置。 預設值為 None

傳回

反序列化的物件。

傳回類型

Any

load_text(package, resource, encoding='utf-8', errors='strict')[原始碼][原始碼]

載入字串。

參數
  • package ( str) – 模組套件的名稱(例如 "my_package.my_subpackage")。

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

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

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

傳回

載入的文字。

傳回類型

str

python_version()[原始碼][原始碼]

傳回用於建立此套件的 Python 版本。

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

傳回

Optional[str] 一個 Python 版本,例如 3.8.9,如果沒有任何版本與此套件一起儲存,則為 None

class torch.package.Directory(name, is_dir)[原始碼][原始碼]

檔案結構表示。 組織為具有其 Directory 子清單的 Directory 節點。 套件的目錄是透過呼叫 PackageImporter.file_structure() 來建立。

has_file(filename)[原始碼][原始碼]

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

參數

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

傳回

如果 Directory 包含指定的檔案。

傳回類型

bool

文件

取得 PyTorch 的完整開發者文件

檢視文件

教學課程

取得針對初學者和進階開發者的深入教學課程

檢視教學課程

資源

尋找開發資源並取得您的問題解答

檢視資源