Spec-Zone.ru › PyTorch 2

torch.package

torch.package добавляет поддержку создания пакетов, содержащих как артефакты, так и произвольный код PyTorch. Эти пакеты можно сохранять, делиться ими, использовать для загрузки и выполнения моделей в будущем или на другом компьютере, а также развертывать в производство с помощью torch::deploy.

Этот документ содержит руководства, пошаговые инструкции, объяснения и справочник API, которые помогут вам узнать больше о torch.package и о том, как его использовать.

Предупреждение

Этот модуль зависит от модуля pickle , который не является безопасным. Разбирайте только данные, которым доверяете.

Возможна конструкция вредоночных данных pickle, которые будут выполнять произвольный код во время распаковки. Никогда не распаковывайте данные, которые могли поступить из недоверенного источника или быть изменёнными.

Для получения дополнительной информации ознакомьтесь с документацией для модуля pickle.

  • Руководства

    • Создание первого пакета модели
  • Как мне…

    • Посмотреть, что находится внутри пакета?
    • Узнать, почему данный модуль был включён в качестве зависимости?
    • Включить произвольные ресурсы в свой пакет и получить к ним доступ позже?
    • Настроить способ упаковки класса?
    • Проверить в исходном коде, выполняется ли он внутри пакета?
    • Заменить код в пакете?
    • Получить доступ к содержимому пакета из упакованного кода?
    • Отличить упакованный код от неупакованного?
    • Повторно экспортировать импортированный объект?
    • Упаковать модуль TorchScript?
  • Объяснение

    • torch.package Обзор формата
    • Как torch.package находит зависимости вашего кода
    • Управление зависимостями
    • torch.package острые углы
    • Как torch.package изолирует пакеты друг от друга
  • Справочник API

Руководства

Создание первого пакета модели

Руководство, которое поможет вам упаковать и распаковать простую модель, доступно в Colab. После выполнения этого упражнения вы будете знакомы с базовым API для создания и использования пакетов Torch.

Как мне…

Посмотреть, что находится внутри пакета?

Рассматривать пакет как 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

Использовать API структуры файлов

PackageImporter предоставляет метод file_structure(), который вернёт печатаемый и запрошиваемый объект Directory. Объект Directory — это простая структура каталога, которую вы можете использовать для изучения текущего содержимого torch.package.

Сам объект Directory непосредственно печатается и выводит представление древовидной структуры файлов. Чтобы отфильтровать возвращаемое значение, используйте шаблоны поиска include и exclude аргументы фильтрации.

with PackageExporter('my_package.pt') as pe:
    pe.save_pickle('models', 'model_1.pkl', mod)

importer = PackageImporter('my_package.pt')
# can limit printed items with include/exclude args
print(importer.file_structure(include=["**/utils.py", "**/*.pkl"], exclude="**/*.storage"))
print(importer.file_structure()) # will print out all files

Вывод:

# filtered with glob pattern:
#    include=["**/utils.py", "**/*.pkl"], exclude="**/*.storage"
─── my_package.pt
    ├── models
    │   └── model_1.pkl
    └── torchvision
        └── models
            └── utils.py

# all files
─── my_package.pt
    ├── .data
    │   ├── 94304870911616.storage
    │   ├── 94304900784016.storage
    │   ├── extern_modules
    │   └── version
    ├── models
    │   └── model_1.pkl
    └── torchvision
        └── models
            ├── resnet.py
            └── utils.py

Вы также можете запросить объекты Directory с помощью метода has_file().

importer_file_structure = importer.file_structure()
found: bool = importer_file_structure.has_file("package_a/subpackage.py")

Узнать, почему данный модуль был включён в качестве зависимости?

Допустим, есть модуль foo, и вы хотите узнать, почему ваш PackageExporter включает foo в качестве зависимости.

PackageExporter.get_rdeps() вернёт все модули, которые напрямую зависят от foo.

Если вы хотите увидеть, как данный модуль src зависит от foo, метод PackageExporter.all_paths() вернёт граф в формате DOT, показывающий все пути зависимостей между src и foo.

Если вы хотите увидеть весь граф зависимостей вашего PackageExporter, вы можете использовать PackageExporter.dependency_graph_string().

Включить произвольные ресурсы в свой пакет и получить к ним доступ позже?

PackageExporter предоставляет три метода, save_pickle, save_text и save_binary , которые позволяют сохранить объекты Python, текст и двоичные данные в пакете.

with torch.PackageExporter("package.pt") as exporter:
    # Pickles the object and saves to `my_resources/tensor.pkl` in the archive.
    exporter.save_pickle("my_resources", "tensor.pkl", torch.randn(4))
    exporter.save_text("config_stuff", "words.txt", "a sample string")
    exporter.save_binary("raw_data", "binary", my_bytes)

PackageImporter предоставляет аналогичные методы load_pickle, load_text и load_binary для загрузки объектов Python, текста и двоичных данных из пакета.

importer = torch.PackageImporter("package.pt")
my_tensor = importer.load_pickle("my_resources", "tensor.pkl")
text = importer.load_text("config_stuff", "words.txt")
binary = importer.load_binary("raw_data", "binary")

Настроить способ упаковки класса?

torch.package позволяет настраивать способ упаковки классов. Этот способ достигается путём определения метода __reduce_package__ в классе и определения соответствующей функции распаковки. Это аналогично определению __reduce__ для обычного процесса сериализации Python.

Шаги:

  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 реализует API importlib.resources для доступа к ресурсам внутри пакета.

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")

API importlib.resources позволяет получить доступ к ресурсам внутри упакованного кода.

# 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 используйте те же API save_pickle и load_pickle, что и с другими объектами. Также поддерживается сохранение объектов 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/.
  • Файлы пользователя, это всё остальное.

В качестве примера, вот как выглядит полностью упакованная модель 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: список модулей, которые считаются модулями extern:class:`PackageImporter`. ``extern и будут импортированы с помощью системного импортёра окружения загрузки.
  • *.storage: сериализованные данные тензоров.
.data
├── 94286146172688.storage
├── 94286146172784.storage
├── extern_modules
├── version
├── ...

Файлы пользователя

Все остальные файлы в архиве были помещены туда пользователем. Структура аналогична структуре обычного пакета Python regular package. Для более глубокого погружения в работу с пакетами 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 проходит по представлению модуля AST Python и ищет операторы импорта со полной поддержкой стандартных форм: from x import y, import z, from 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-руется, он будет помещён в пакет.

Это действие относится к вашему коду модели или любому связанному коду, который вы хотите упаковать. Например, если вы пытаетесь упаковать ResNet из torchvision, вам нужно 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"). Если он не найдёт этот модуль, будет выдана ошибка.

Таким образом, вы можете зависеть от сторонних библиотек, таких как numpy и scipy, внутри вашего пакета, не упаковывая их тоже.

Предупреждение: Если какая-либо внешняя библиотека изменится несовместимым образом, ваш пакет может не загрузиться. Если вам нужна долгосрочная воспроизводимость для вашего пакета, постарайтесь ограничить использование 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, будет нуждаться в подтяжке множества несвязанных зависимостей, даже если вам понадобилась только небольшая часть. Вместо этого предпочитайте определять модули с единственной целью, которые можно упаковать независимо друг от друга.

Шаблоны

Шаблоны позволяют задавать группы модулей с удобным синтаксисом. Синтаксис и поведение шаблонов следуют за glob() из Bazel/Buck.

Модуль, который мы пытаемся сопоставить с шаблоном, называется кандидатом. Кандидат состоит из списка сегментов, разделенных строкой-разделителем, например, 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.**"])

Модуль не будет соответствовать этому действию, если он соответствует любому из шаблонов исключения. В этом примере мы моделируем все модули, кроме 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 предоставляет тот же основной API, что и импортер importlib; а именно, он реализует методы 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.

Изменение имён помогает избежать случайного повторения имён модулей между разными пакетами и помогает в отладке, делая трассировки стека и операторы вывода более чётко показывающими, ссылаются ли они на упакованный код или нет. Для получения деталей о изменении имён, относящихся к разработчику, см. mangling.md в torch/package/.

Справочник API

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

Это исключение возникает, когда есть проблема с экспортом пакета. PackageExporter попытается собрать все ошибки и представить их вам сразу.

class torch.package.EmptyMatchError [source]

Это исключение возникает, когда мок или экстерн помечен как allow_empty=False, и не сопоставлен ни с одним модулем во время упаковки.

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

Экспортеры позволяют вам создавать пакеты кода, закодированных данных Python, а также произвольных двоичных и текстовых ресурсов в самодостаточный пакет.

Импорты могут загружать этот код герметичным способом, при этом код загружается из пакета, а не из обычной системы импорта Python. Это позволяет упаковывать код и данные модели PyTorch таким образом, чтобы их можно было запускать на сервере или использовать в будущем для трансферного обучения.

Код, содержащийся в пакетах, копируется из исходного кода файл за файлом при его создании, и формат файла представляет собой специально организованный zip-архив. Пользователи пакета в будущем могут распаковать пакет и редактировать код, чтобы выполнить пользовательские модификации.

Импортер пакетов гарантирует, что код в модуле может загружаться только изнутри пакета, за исключением модулей, явно указанных как внешние с помощью extern(). Файл extern_modules в zip-архиве перечисляет все модули, от которых пакет внешним образом зависит. Это предотвращает «неявные» зависимости, когда пакет работает локально, потому что импортирует локально установленный пакет, но затем завершается ошибкой, когда пакет копируется на другой компьютер.

При добавлении исходного кода в пакет, экспортер может необязательно просканировать его на предмет дополнительных зависимостей кода (dependencies=True). Он ищет операторы импорта, разрешает относительные ссылки на квалифицированные имена модулей и выполняет действие, указанное пользователем (см.: extern(), mock() и intern()).

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

Создать экспортер.

Параметры
  • f (Union[str, Path, BinaryIO]) – Место назначения экспорта. Может быть string/Path объектом, содержащим имя файла, или объектом двоичного ввода/вывода.
  • importer (Union[Importer, Sequence[Importer]]) – Если передан один Importer, используйте его для поиска модулей. Если передается последовательность импортеров, будет создан OrderedImporter из них.
  • debug (bool) – Если установлено в True, добавляйте путь к поврежденным модулям в PackagingErrors.
add_dependency(module_name, dependencies=True) [source]

Для данного модуля добавьте его в граф зависимостей в соответствии с шаблонами, указанными пользователем.

all_paths(src, dst) [source]
Возвращает представление графа в формате dot

со всеми путями от src до dst.

Возвращает

Представление графа в формате dot, содержащее все пути от 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]

Возвращает строковое представление графа зависимостей в пакете в формате digraph.

Возвращает

Строковое представление графа зависимостей в пакете.

Тип возвращаемого значения

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, быть сопоставлены с каким-либо модулем во время упаковки. Если шаблон внешнего модуля glob добавлен с allow_empty=False, и 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]

Получить идентификатор. Этот идентификатор гарантированно выдаётся только один раз для этого пакета.

Тип возвращаемого значения

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, быть сопоставлены с каким-либо модулем во время упаковки. Если шаблон внутреннего модуля glob добавлен с allow_empty=False, и 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, например '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]

Сохранение объекта Python в архив с использованием pickle. Эквивалентно torch.save(), но сохраняет в архив, а не в отдельный файл. Стандартный pickle не сохраняет код, только объекты. Если dependencies имеет значение true, этот метод также будет сканировать сохранённые объекты, чтобы определить, какие модули необходимы для их восстановления, и сохранит соответствующий код.

Для сохранения объекта, где 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]

Добавляет локальный файл 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]

Импортеры позволяют загружать код, написанный для пакетов с помощью PackageExporter. Код загружается герметичным способом, используя файлы из пакета, а не обычную систему импорта Python. Это позволяет упаковывать код и данные моделей PyTorch таким образом, чтобы их можно было запускать на сервере или использовать в будущем для трансферного обучения.

Импортер пакетов гарантирует, что код в модуле может загружаться только изнутри пакета, за исключением модулей, явно указанных как внешние во время экспорта. Файл extern_modules в архиве zip перечисляет все модули, от которых пакет внешне зависит. Это предотвращает «неявные» зависимости, когда пакет работает локально, потому что он импортирует локально установленный пакет, но затем терпит неудачу, когда пакет копируется на другой компьютер.

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

Открыть file_or_buffer для импорта. Это проверяет, что импортируемый пакет требует только модулей, разрешенных module_allowed

Parameters
  • file_or_buffer (Union[str, PyTorchFileReader, Path, BinaryIO]) – объект, подобный файлу (должен реализовывать read(), readline(), tell(), и seek()), строку или объект os.PathLike содержащий имя файла.
  • module_allowed (Callable[[str], bool], optional) – Метод для определения, должен ли быть разрешен внешне предоставляемый модуль. Может использоваться для обеспечения того, что загруженные пакеты не зависят от модулей, которых сервер не поддерживает. По умолчанию разрешает всё.
Raises

ImportError – Если пакет будет использовать запрещённый модуль.

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

Возвращает представление структуры файлов архива zip пакета.

Parameters
  • include (Union[List[str], str]) – Необязательная строка, например, "my_package.my_subpackage", или необязательный список строк для имен файлов, которые должны быть включены в представление архива zip. Это также может быть шаблон в стиле glob, как описано в PackageExporter.mock()
  • exclude (Union[List[str], str]) – Необязательный шаблон, который исключает файлы, имя которых соответствует шаблону.
Returns

Directory

Return type

Directory

id() [source]

Возвращает внутренний идентификатор, который torch.package использует для различения PackageImporter экземпляров. Выглядит так:

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

Загрузить модуль из пакета, если он ещё не загружен, и затем вернуть модуль. Модули загружаются локально в импортер и будут отображаться в self.modules вместо sys.modules.

Parameters
  • name (str) – Полное квалифицированное имя модуля для загрузки.
  • package ([type], optional) – Не используется, но присутствует для соответствия сигнатуре importlib.import_module. По умолчанию None.
Returns

Загруженный (возможно, уже загруженный) модуль.

Return type

types.ModuleType

load_binary(package, resource) [source]

Загрузить сырые байты.

Parameters
  • package (str) – Имя модуля пакета (например, "my_package.my_subpackage").
  • resource (str) – Уникальное имя ресурса.
Returns

Загруженные данные.

Return type

bytes

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

Распаковывает ресурс из пакета, загружая все необходимые модули для построения объектов с помощью import_module().

Parameters
  • package (str) – Имя модуля пакета (например, "my_package.my_subpackage").
  • resource (str) – Уникальное имя ресурса.
  • map_location – Передаётся torch.load для определения того, как тензоры отображаются на устройствах. По умолчанию None.
Returns

Распакованный объект.

Return type

Any

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

Загрузка строки.

Параметры
  • package (str) – Имя пакета модуля (например, "my_package.my_subpackage").
  • resource (str) – Уникальное имя ресурса.
  • encoding (str, необязательно) – Передаётся в decode. По умолчанию 'utf-8'.
  • errors (str, необязательно) – Передаётся в 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 содержит указанный файл.

Тип возвращаемого значения

bool

© 2024, PyTorch Contributors
PyTorch has a BSD-style license, as found in the LICENSE file.
https://pytorch.org/docs/2.1/package.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API