快捷方式

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 的正常 pickling 過程定義 __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 會像往常一樣 pickle 物件。然後,它使用 pickletools 標準庫模組來解析 pickle 位元組碼。

在 pickle 中,物件與一個 GLOBAL 操作碼一起儲存,該操作碼描述了在哪裡找到物件型別的實現,例如

GLOBAL 'torchvision.models.resnet Resnet`

依賴解析器會收集所有 GLOBAL 操作碼,並將它們標記為您 pickled 物件的依賴項。更多關於 pickling 和 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:對此模組進行存根處理(mock)。

  • deny:依賴此模組將在包匯出期間引發錯誤。

最後,還有一個重要的操作,嚴格來說不屬於 torch.package

  • 重構:移除或更改程式碼中的依賴項。

請注意,操作僅定義在整個 Python 模組上。無法只打包模組中的“某個”函式或類而排除其餘部分。這是有意為之的。Python 在模組中定義的物件之間沒有清晰的界限。唯一定義的依賴組織單位是模組,因此 torch.package 使用模組作為單位。

操作使用模式應用於模組。模式可以是模組名稱("foo.bar")或 glob(例如 "foo.**")。您使用 PackageExporter 上的方法將模式與操作關聯起來,例如:

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

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

intern

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

此操作適用於您的模型程式碼或您想打包的任何相關程式碼。例如,如果您嘗試打包來自 torchvision 的 ResNet,您需要 intern 模組 torchvision.models.resnet。

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

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

extern

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

在包匯入時,當打包程式碼嘗試匯入一個被 extern 的模組時,PackageImporter 將使用預設的 Python 匯入器來查詢該模組,就像您執行了 importlib.import_module("my_externed_module") 一樣。如果找不到該模組,將引發錯誤。

透過這種方式,您可以從包內部依賴第三方庫,例如 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.**"])

如果一個模組匹配任何排除模式,它將不會與此操作匹配。在此示例中,我們 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!

在此示例中,MyClassimported_MyClass 不是同一型別。在這個特定示例中,MyClassimported_MyClass 具有完全相同的實現,因此你可能認為將它們視為同一類是可以的。但是考慮一下 imported_MyClass 來自一個具有完全不同 MyClass 實現的舊包的情況——在這種情況下,將它們視為同一類是不安全的。

在底層,每個匯入器都有一個字首,允許它唯一地標識類

print(MyClass.__name__)  # prints "foo.MyClass"
print(imported_MyClass.__name__)  # prints <torch_package_0>.foo.MyClass

這意味著當其中一個引數來自包而另一個不來自包時,你不應期望 isinstance 檢查能正常工作。如果你需要此功能,請考慮以下選項

  • 使用鴨子型別(duck typing)(直接使用類而不是顯式檢查它是否屬於給定型別)。

  • 使型別關係成為類契約的顯式部分。例如,你可以新增一個屬性標籤 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

命名修飾有助於避免不同包之間模組名稱的無意雙關(punning),並有助於透過使堆疊跟蹤和列印語句更清晰地顯示它們是否引用了打包的程式碼來幫助你進行除錯。有關面向開發者的命名修飾詳情,請查閱 torch/package/ 中的 mangling.md 檔案。

API 參考

class torch.package.PackagingError(dependency_graph, debug=False)[source][source]

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

class torch.package.EmptyMatchError[source][source]

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

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

匯出器允許你將程式碼包、pickled 的 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, PathLike[str], IO[bytes]]) – 匯出的目標位置。可以是包含檔名的 string/Path 物件,或一個二進位制 I/O 物件。

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

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

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

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

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

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

返回

一個包含從 src 到 dst 的所有路徑的 dot 表示形式。(https://graphviz.org/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 風格的模式,如 mock() 中所述。

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

dependency_graph_string()[source][source]

返回包中依賴項的 digraph 字串表示。

返回

包中依賴項的字串表示。

返回型別

str

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

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

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

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

  • allow_empty (bool) – 一個可選標誌,指定在此呼叫 extern 方法時指定的外部模組在打包過程中是否必須匹配到某個模組。如果新增的外部模組 glob 模式設定為 allow_empty=False,並且在任何模組匹配該模式之前呼叫了 close()(無論是顯式呼叫還是透過 __exit__),則會丟擲異常。如果 allow_empty=True,則不會丟擲此類異常。

externed_modules()[source][source]

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

返回

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

返回型別

list[str]

get_rdeps(module_name)[source][source]

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

返回

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

返回型別

list[str]

get_unique_id()[source][source]

獲取一個 ID。此 ID 保證在此包中只會被分發一次。

返回型別

str

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

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

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

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

  • allow_empty (bool) – 一個可選標誌,指定在此呼叫 intern 方法時指定的內部模組在打包過程中是否必須匹配到某個模組。如果新增的 intern 模組 glob 模式設定為 allow_empty=False,並且在任何模組匹配該模式之前呼叫了 close()(無論是顯式呼叫還是透過 __exit__),則會丟擲異常。如果 allow_empty=True,則不會丟擲此類異常。

interned_modules()[source][source]

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

返回

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

返回型別

list[str]

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

替換一些必需的模組,使用模擬(mock)實現。模擬的模組在訪問其任何屬性時將返回一個虛假物件。由於我們按檔案進行復制,依賴解析有時會找到模型檔案匯入但其功能從未被使用的檔案(例如自定義序列化程式碼或訓練輔助工具)。使用此函式可以模擬掉這些功能,而無需修改原始程式碼。

引數
  • include (Union[List[str], str]) –

    一個字串(例如 "my_package.my_subpackage"),或一個字串列表,表示要模擬(mock out)的模組名稱。字串也可以是 glob 風格的模式字串,可以匹配多個模組。任何匹配此模式字串的必需依賴項都將被自動模擬(mock out)。

    示例

    '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()[source][source]

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

返回

一個列表,包含將在此包中被模擬的模組名稱。

返回型別

list[str]

register_extern_hook(hook)[source][source]

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

每當一個模組匹配到 extern() 模式時,都會呼叫此鉤子。它應具有以下簽名

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

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

返回

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

返回型別

torch.utils.hooks.RemovableHandle

register_intern_hook(hook)[source][source]

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

每當一個模組匹配到 intern() 模式時,都會呼叫此鉤子。它應具有以下簽名

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

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

返回

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

返回型別

torch.utils.hooks.RemovableHandle

register_mock_hook(hook)[source][source]

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

每當一個模組匹配到 mock() 模式時,都會呼叫此鉤子。它應具有以下簽名

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

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

返回

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

返回型別

torch.utils.hooks.RemovableHandle

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

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

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

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

  • binary (str) – 要儲存的資料(原始位元組)。

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

module_name 的程式碼儲存到包中。模組的程式碼是使用 importers 路徑來查詢模組物件,然後使用其 __file__ 屬性來查詢原始碼的。

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

  • dependencies (bool, optional) – 如果為 True,我們將掃描原始碼以查詢依賴項。

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

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

為了能夠儲存 `type(obj).__name__` 是 my_module.MyObject 的物件,my_module.MyObject 必須根據 importer 的順序解析到該物件的類。當儲存先前已打包的物件時,匯入器的 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)[source][source]

將本地檔案系統中的 file_or_directory 新增到源包中,以提供 module_name 的程式碼。

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

  • file_or_directory (str) – 程式碼檔案或目錄的路徑。如果是目錄,則該目錄中的所有 python 檔案都將使用 save_source_file() 遞迴複製。如果檔名為 "/__init__.py",則程式碼被視為一個包。

  • dependencies (bool, optional) – 如果為 True,我們將掃描原始碼以查詢依賴項。

save_source_string(module_name, src, is_package=False, dependencies=True)[source][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][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]

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

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

__init__(file_or_buffer, module_allowed=<function PackageImporter.<lambda>>)[source][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][source]

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

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

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

返回

Directory

返回型別

Directory

id()[source][source]

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

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

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

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

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

返回

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

返回型別

types.ModuleType

load_binary(package, resource)[source][source]

載入原始位元組。

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

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

返回

載入的資料。

返回型別

bytes

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

從包中解 pickle 資源,並使用 import_module() 載入構造物件所需的任何模組。

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

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

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

返回

解 pickle 的物件。

返回型別

Any

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

載入字串。

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

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

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

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

返回

載入的文字。

返回型別

str

python_version()[source][source]

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

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

返回

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

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

一個檔案結構表示。組織為具有其 Directory 子節點列表的 Directory 節點。包的 Directory 透過呼叫 PackageImporter.file_structure() 建立。

has_file(filename)[source][source]

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

引數

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

返回

如果 Directory 包含指定的檔案。

返回型別

bool

文件

訪問 PyTorch 的綜合開發者文件

檢視文件

教程

獲取針對初學者和高階開發人員的深入教程

檢視教程

資源

查詢開發資源並解答您的問題

檢視資源