捷徑

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 的常規序列化過程定義 __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 標準。但是,也可以從打包的程式碼中存取父 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 會像平常一樣醃製物件。然後,它使用 pickletools 標準函式庫模組來解析醃製位元組碼。

在醃製檔案中,物件會與 GLOBAL 操作碼一起儲存,該操作碼描述在哪裡可以找到物件類型的實作,例如

GLOBAL 'torchvision.models.resnet Resnet`

依賴關係解析器會收集所有 GLOBAL 操作並將它們標記為醃製物件的依賴關係。有關醃製和醃製格式的更多資訊,請參閱 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")或萬用字元(例如 "foo.**")。你可以使用 PackageExporter 上的方法將模式與動作關聯,例如

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

如果模組符合模式,則會對其套用相應的動作。對於給定的模組,將按照定義模式的順序檢查模式,並採取第一個動作。

intern

如果模組是 intern-ed,它將被放入套件中。

此動作是你的模型程式碼,或任何你想打包的相關程式碼。例如,如果你嘗試從 torchvision 打包 ResNet,則需要 intern 模組 torchvision.models.resnet。

在套件導入時,當你的打包程式碼嘗試導入 intern-ed 模組時,PackageImporter 會在你的套件中尋找該模組。如果找不到該模組,則會引發錯誤。這確保每個 PackageImporter 都與載入環境隔離,即使你在套件和載入環境中都有 my_interned_modulePackageImporter 也只會使用套件中的版本。

**注意**:只有 Python 原始碼模組可以是 intern-ed。其他類型的模組,例如 C 擴充模組和位元組碼模組,如果你嘗試 intern 它們,則會引發錯誤。這種類型的模組需要 mock-ed 或 extern-ed。

extern

如果模組是 extern-ed,則不會打包它。相反,它將被添加到此套件的外部依賴關係清單中。你可以在 package_exporter.extern_modules 上找到此清單。

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

透過這種方式,你可以依賴套件中的第三方函式庫,例如 numpyscipy,而不必也打包它們。

**警告**:如果任何外部函式庫以不相容的方式變更,你的套件可能會載入失敗。如果你需要套件的長期可重複性,請嘗試限制你對 extern 的使用。

mock

如果模組是 mock-ed,則不會打包它。相反,將在其位置打包一個存根模組。存根模組將允許你從中檢索物件(因此 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),完全匹配。

  • 包含萬用字元的字串(例如 torchfoo*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]

當模擬或外部物件標記為 allow_empty=False,並且在封裝期間未與任何模組匹配時,就會引發此例外狀況。

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

匯出器允許您將程式碼套件、醃製的 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, Path, BinaryIO]) – 匯出到的位置。可以是一個包含檔名的 string/Path 物件,或者是一個二進位 I/O 物件。

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

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

add_dependency(module_name, dependencies=True)[source]

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

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

其中包含從 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 新增 intern 模組 glob 模式,並且在任何模組與該模式相符之前呼叫 close()(明確呼叫或透過 __exit__ 呼叫),則會擲回例外狀況。如果 allow_empty=True,則不會擲回此類例外狀況。

interned_modules()[原始碼]

傳回目前所有已內嵌的模組。

返回

一個清單,其中包含將在此封裝中內嵌的模組名稱。

返回類型

List[str]

mock(include, *, exclude=(), allow_empty=True)[原始碼]

使用模擬實作替換一些必要的模組。模擬的模組將針對從中存取的任何屬性傳回一個虛假物件。由於我們逐一複製檔案,因此依賴項解析有時會找到由模型檔案匯入但其功能從未使用過的檔案(例如,自訂序列化程式碼或訓練輔助程式)。使用此函式可以模擬出此功能,而無需修改原始程式碼。

參數
  • 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,則不會擲回此類例外狀況。

mocked_modules()[原始碼]

傳回目前所有已模擬的模組。

返回

一個清單,其中包含將在此封裝中模擬的模組名稱。

返回類型

List[str]

register_extern_hook(hook)[原始碼]

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

每次模組與 extern() 模式相符時,都會呼叫此鉤子。它應該具有以下簽章

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

鉤子將按照註冊順序呼叫。

返回

一個控點,可用於透過呼叫 handle.remove() 來移除已新增的鉤子。

返回類型

torch.utils.hooks.RemovableHandle

register_intern_hook(hook)[原始碼]

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

每次模組與 intern() 模式相符時,都會呼叫此鉤子。它應該具有以下簽章

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

鉤子將按照註冊順序呼叫。

返回

一個控點,可用於透過呼叫 handle.remove() 來移除已新增的鉤子。

返回類型

torch.utils.hooks.RemovableHandle

register_mock_hook(hook)[原始碼]

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

每次模組與 mock() 模式相符時,都會呼叫此鉤子。它應該具有以下簽章

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

鉤子將按照註冊順序呼叫。

返回

一個控點,可用於透過呼叫 handle.remove() 來移除已新增的鉤子。

返回類型

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) – 要儲存的物件,必須是可醃製的。

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

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

參數
  • package (str) – 此資源應歸入的模組封裝名稱(例如 "my_package.my_subpackage")。

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

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

class torch.package.PackageImporter(file_or_buffer, module_allowed=<function PackageImporter.<lambda>>)[原始碼]

匯入器允許您載入由 PackageExporter 寫入套件的程式碼。程式碼會以封閉的方式載入,使用套件中的檔案,而不是一般的 Python 匯入系統。這允許將 PyTorch 模型程式碼和資料打包,以便在伺服器上執行或在未來用於遷移學習。

套件匯入器確保模組中的程式碼只能從套件內部載入,但匯出期間明確列為外部的模組除外。zip 封存中的 extern_modules 檔案列出了套件外部依賴的所有模組。這可以防止「隱式」依賴關係,即套件在本地執行是因為它正在匯入本地安裝的套件,但在將套件複製到另一台機器時卻失敗了。

__init__(file_or_buffer, module_allowed=<function PackageImporter.<lambda>>)[原始碼]

開啟 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=())[原始碼]

返回套件 zipfile 的檔案結構表示。

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

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

返回

目錄

返回類型

目錄

id()[原始碼]

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

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

如果尚未載入,則從套件中載入模組,然後返回該模組。模組會載入到匯入器的本地,並顯示在 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, 可選的) – 傳遞給 decode。預設值為 'utf-8'

  • errors (str, 可選的) – 傳遞給 decode。預設值為 'strict'

返回

載入的文字。

返回類型

str

python_version()[原始碼]

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

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

返回

Optional[str] Python 版本,例如 3.8.9,如果此套件未儲存版本,則為 None

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

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

has_file(filename)[原始碼]

檢查 Directory 中是否存在檔案。

參數

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

返回

如果 Directory 包含指定的檔案。

返回類型

bool

文件

存取 PyTorch 的完整開發人員文件

檢視文件

教學課程

取得適合初學者和進階開發人員的深入教學課程

檢視教學課程

資源

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

檢視資源