torch.package
torch.package добавляет поддержку создания пакетов, содержащих как артефакты, так и произвольный код PyTorch. Эти пакеты можно сохранять, обмениваться ими, использовать для загрузки и выполнения моделей в будущем или на другом компьютере, а также развертывать в производство с помощью torch::deploy.
Этот документ содержит учебные материалы, пошаговые руководства, объяснения и справочник API, которые помогут вам узнать больше о torch.package и о том, как им пользоваться.
Предупреждение
Этот модуль зависит от модуля pickle, который не является безопасным. Разпаковывайте только данные, которым вы доверяете.
Существует возможность создания вредоносных данных pickle, которые будут выполнять произвольный код во время распаковки. Никогда не распаковывайте данные, которые могут поступать из недоверенного источника или которые могли быть изменены.
Для получения дополнительной информации ознакомьтесь с документацией по модулю pickle.
-
- Посмотреть содержимое пакета?
- Узнать, почему определённый модуль был включён как зависимость?
- Включить произвольные ресурсы в пакет и получить к ним доступ позже?
- Настроить способ упаковки класса?
- Проверить в коде, выполняется ли он внутри пакета?
- Внести изменения в код пакета?
- Получить доступ к содержимому пакета из упакованного кода?
- Отличить упакованный код от неупакованного?
- Переэкспортировать импортированный объект?
- Упаковать модуль TorchScript?
- Справочник 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 напрямую печатается и выведет представление дерева файлов. Для фильтрации возвращаемого результата используйте фильтры в формате glob 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.
Шаги:
- Определите метод
__reduce_package__(self, exporter: PackageExporter)в целевом классе. Этот метод должен выполнить работу по сохранению экземпляра класса внутри пакета и вернуть кортеж соответствующей функции распаковки с аргументами, необходимыми для вызова функции распаковки. Этот метод вызываетсяPackageExporterпри обнаружении экземпляра целевого класса. - Определите функцию распаковки для класса. Эта функция распаковки должна выполнить работу по восстановлению и возврату экземпляра класса. Первый параметр сигнатуры функции должен быть экземпляром
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 представляет собой архив 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) модуля и ищет операторы импорта со стандартной поддержкой таких форм: 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 следует использовать в крайних случаях. Он вводит различия в поведении между упакованным и неупакованным кодом, что может привести к путанице. Предпочитайте вместо этого переписать свой код, чтобы убрать ненужные зависимости.
Рефакторинг
Лучший способ управлять зависимостями — это вообще не иметь зависимостей! Часто код можно переписать, чтобы убрать ненужные зависимости. Вот некоторые рекомендации по написанию кода с чистыми зависимостями (которые также являются хорошей практикой!):
Включайте только то, что используете. Не оставляйте в коде неиспользуемые импорты. Решатель зависимостей недостаточно умён, чтобы определить, что они не используются, и попытается обработать их.
Квалифицируйте импорты. Например, вместо импорта foo и последующего использования foo.bar.baz, предпочтительнее писать from foo.bar import baz. Это более точно определяет реальную зависимость (foo.bar) и сообщает решателю зависимостей, что вам не нужен весь foo.
Разделяйте большие файлы с не связанной функциональностью на меньшие. Если ваш модуль utils содержит смесь не связанной функциональности, любой модуль, который зависит от utils, потребует подключения множества не связанных зависимостей, даже если вам нужен только небольшой фрагмент. Предпочтительнее определять модули с единственной целью, которые можно упаковывать независимо друг от друга.
Шаблоны
Шаблоны позволяют вам указывать группы модулей с удобным синтаксисом. Синтаксис и поведение шаблонов соответствует шаблонам Bazel/Buck glob().
Модуль, который мы пытаемся сопоставить с шаблоном, называется кандидатом. Кандидат состоит из списка сегментов, разделённых разделителем, например foo.bar.baz.
Шаблон содержит один или несколько сегментов. Сегменты могут быть:
- Литеральной строкой (например,
foo), которая точно соответствует. - Строкой, содержащей символ подстановки (например,
torch, илиfoo*baz*). Символ подстановки соответствует любой строке, включая пустую. - Двойным символом подстановки (
**). Он соответствует нулю или более полным сегментам.
Примеры:
-
torch.**: соответствуетtorchи всем его подмодулям, напримерtorch.nnиtorch.nn.functional. -
torch.*: соответствуетtorch.nnилиtorch.functional, но неtorch.nn.functionalилиtorch -
torch*.**: соответствуетtorch,torchvision, и всем их подмодулям
При указании действий вы можете передавать несколько шаблонов, например:
exporter.intern(["torchvision.models.**", "torchvision.utils.**"])
Модуль будет соответствовать этому действию, если он соответствует любому из шаблонов.
Вы также можете указать шаблоны для исключения, например:
exporter.mock("**", exclude=["torchvision.**"])
Модуль не будет соответствовать этому действию, если он соответствует любому из шаблонов исключения. В этом примере мы эмулируем все модули, кроме torchvision и его подмодулей.
Когда модуль может потенциально соответствовать нескольким действиям, будет выполнено первое определённое действие.
torch.package острые углы
Избегайте глобального состояния в ваших модулях
Python делает очень лёгким связывание объектов и выполнение кода на уровне модуля. Это обычно нормально — ведь функции и классы связаны с именами таким образом. Однако, ситуация усложняется, когда вы определяете объект на уровне модуля с намерением его изменения, вводя глобальное изменяемое состояние.
Изменяемое глобальное состояние довольно полезно — оно может уменьшить объём кода, позволить открытую регистрацию в таблицы и т.д. Но если им не воспользоваться очень осторожно, это может вызвать проблемы при использовании с torch.package.
Каждый PackageImporter создаёт независимую среду для своего содержимого. Это хорошо, поскольку это означает, что мы загружаем несколько пакетов и гарантируем их изоляцию друг от друга, но когда модули написаны так, что предполагают общее изменяемое глобальное состояние, это поведение может создавать трудноотлавливаемые ошибки.
Как torch.package изолирует пакеты друг от друга
Каждый экземпляр PackageImporter создаёт независимую, изолированную среду для своих модулей и объектов. Модули в пакете могут импортировать только другие модули пакета или модули, помеченные extern. Если вы используете несколько экземпляров PackageImporter для загрузки одного пакета, вы получите несколько независимых сред, которые не взаимодействуют.
Это достигается путём расширения инфраструктуры импорта Python с помощью пользовательского импортёра. PackageImporter предоставляет тот же основной API, что и импортёр importlib, а именно, он реализует методы import_module и __import__.
Когда вы вызываете PackageImporter.import_module(), PackageImporter построит и вернёт новый модуль, так же, как это делает системный импортёр. Однако, PackageImporter подменяет возвращённый модуль, чтобы использовать self (то есть тот экземпляр PackageImporter), для удовлетворения будущих запросов на импорт, обращаясь к пакету, а не к среде пользователя Python.
Замена имён (Mangling)
Чтобы избежать путаницы («является ли этот объект foo.bar из моего пакета или из моей среды Python?»), PackageImporter заменяет имена __name__ и __file__ всех импортированных модулей, добавив к ним префикс замены.
Для __name__, имя, подобное torchvision.models.resnet18, становится <torch_package_0>.torchvision.models.resnet18.
Для __file__, имя, подобное torchvision/models/resnet18.py, становится <torch_package_0>.torchvision/modules/resnet18.py.
Замена имён помогает избежать непреднамеренного совпадения имён модулей между различными пакетами и помогает отлаживать, делая трассировки стека и операторы печати более наглядными, демонстрируя, относятся ли они к упакованному коду или нет. Для получения информации о замене имён для разработчиков, обратитесь к mangling.md в torch/package/.
Ссылка на API
-
class torch.package.PackagingError(dependency_graph)[source] -
Это исключение возникает при проблемах с экспортом пакета.
PackageExporterпостарается собрать все ошибки и представить их вам сразу.
-
class torch.package.EmptyMatchError[source] -
Это исключение возникает, когда модуль-заглушка или внешний модуль помечен как
allow_empty=False, и не сопоставлен ни с одним модулем во время упаковки.
-
class torch.package.PackageExporter(f, importer=<torch.package.importer._SysImporter object>)[source] -
Экспортеры позволяют создавать пакеты кода, закодированных Python-данных и произвольных двоичных и текстовых ресурсов в автономный пакет.
Импорты могут загружать этот код герметичным способом, так что код загружается из пакета, а не из обычной системы импорта Python. Это позволяет упаковывать код и данные модели PyTorch, чтобы их можно было запускать на сервере или использовать в будущем для трансферного обучения.
Код, содержащийся в пакетах, копируется из исходного кода по файлам во время создания, и формат файла — специально организованный zip-архив. Будущие пользователи пакета могут распаковать пакет и изменить код, чтобы внести в него пользовательские изменения.
Импортер пакетов гарантирует, что код в модуле может загружаться только изнутри пакета, за исключением модулей, явно указанных как внешние с помощью
extern(). Файлextern_modulesв архиве zip перечисляет все модули, от которых пакет зависит внешне. Это предотвращает «неявные» зависимости, когда пакет работает локально, потому что он импортирует локально установленный пакет, но затем завершается ошибкой, когда пакет копируется на другой компьютер.При добавлении исходного кода в пакет экспортер может выборочно сканировать его на предмет дополнительных зависимостей кода (
dependencies=True). Он ищет операторы импорта, разрешает относительные ссылки на полные имена модулей и выполняет действие, указанное пользователем (см.:extern(),mock()иintern()).-
__init__(f, importer=<torch.package.importer._SysImporter object>)[source] -
Создает экспортер.
- Параметры:
-
-
f (Union[str, Path, BinaryIO]) – Место назначения для экспорта. Может быть объектом
string/Path, содержащим имя файла, или объектом двоичного ввода/вывода. -
importer (Union[Importer, Sequence[Importer]]) – Если передан один Importer, используйте его для поиска модулей. Если передаётся последовательность импортеров, будет создан
OrderedImporterиз них.
-
f (Union[str, Path, BinaryIO]) – Место назначения для экспорта. Может быть объектом
-
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)
- Тип возвращаемого значения:
-
close()[source] -
Записывает пакет в файловую систему. Все вызовы после
close()теперь недействительны. Рекомендуется использовать синтаксис защиты ресурсов вместо этого:with PackageExporter("file.zip") as e: ...
-
denied_modules()[source] -
Возвращает все запрещённые модули.
-
deny(include, *, exclude=())[source] -
Добавляет в список запрещённых модулей те, имена которых соответствуют заданным шаблонам glob из списка импортируемых в пакет модулей. Если найдена зависимость от какого-либо сопоставленного пакета, то генерируется исключение
PackagingError.- Параметры:
-
-
include (Union[List[str], str]) – Строка, например
"my_package.my_subpackage", или список строк для имён модулей, которые должны быть внешними. Это также может быть шаблон в стиле glob, как описано вmock(). - exclude (Union[List[str], str]) – Необязательный шаблон, исключающий некоторые шаблоны, которые соответствуют строке include.
-
include (Union[List[str], str]) – Строка, например
-
dependency_graph_string()[source] -
Возвращает строку представления графа зависимостей в пакете.
- Возвращает:
-
Строковое представление зависимостей в пакете.
- Тип возвращаемого значения:
-
-
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, такое исключение не будет выброшено.
-
include (Union[List[str], str]) – Строка, например
-
externed_modules()[source] -
Возвращает все модули, которые в данный момент являются внешними.
-
get_rdeps(module_name)[source] -
Возвращает список всех модулей, которые зависят от модуля
module_name.
-
get_unique_id()[source] -
Получить идентификатор. Этот идентификатор гарантированно выдаётся только один раз для этого пакета.
- Тип возвращаемого значения:
-
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, такое исключение не будет выброшено.
-
include (Union[List[str], str]) – Строка, например “my_package.my_subpackage”, или список строк для имён модулей, которые нужно сделать внешними. Это также может быть шаблон в стиле glob, как описано в
-
-
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] -
Возвращает все модули, которые в данный момент смоделированы.
-
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] -
Сохранить сырые байты в пакет.
-
save_module(module_name, dependencies=True)[source] -
Сохранить код для
moduleв пакет. Код модуля определяется с помощью путиimporters, для поиска объекта модуля, а затем с помощью его атрибута__file__, для поиска исходного кода.
-
-
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, мы просматриваем исходный код на наличие зависимостей.
-
package (str) – Имя модуля пакета, в котором должен храниться этот ресурс (например,
-
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, мы просматриваем исходный код на наличие зависимостей.
-
module_name (str) – например,
-
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, мы просматриваем исходный код на наличие зависимостей.
-
module_name (str) – например,
-
save_text(package, resource, text)[source] -
Сохраняет текстовые данные в пакет.
-
-
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- Параметры:
-
-
file_or_buffer (Union[str, PyTorchFileReader, Path, BinaryIO]) – объект, подобный файлу (должен реализовывать
read(),readline(),tell(), иseek()), строку или объектos.PathLikeсодержащий имя файла. - module_allowed (Callable[[str], bool], optional) – Метод для определения, следует ли разрешить внешне предоставленный модуль. Может использоваться для обеспечения того, чтобы загруженные пакеты не зависели от модулей, которые сервер не поддерживает. По умолчанию разрешает всё.
-
file_or_buffer (Union[str, PyTorchFileReader, Path, BinaryIO]) – объект, подобный файлу (должен реализовывать
- Возможные исключения:
-
ImportError – Если пакет будет использовать запрещённый модуль.
-
file_structure(*, include='**', exclude=())[source] -
Возвращает представление структуры файлов zip-архива пакета.
- Параметры:
-
-
include (Union[List[str], str]) – Необязательная строка, например
"my_package.my_subpackage", или необязательный список строк для имен файлов, которые должны быть включены в представлении zip-архива. Это также может быть шаблон в стиле glob, как описано вPackageExporter.mock() - exclude (Union[List[str], str]) – Необязательный шаблон, исключающий файлы, имена которых соответствуют шаблону.
-
include (Union[List[str], str]) – Необязательная строка, например
- Возвращает:
- Тип возвращаемого значения:
-
id()[source] -
Возвращает внутренний идентификатор, который torch.package использует для различения
PackageImporterэкземпляров. Выглядит так:<torch_package_0>
-
import_module(name, package=None)[source] -
Загрузить модуль из пакета, если он еще не загружен, и затем вернуть модуль. Модули загружаются локально в импортер и будут отображаться в
self.modulesвместоsys.modules.- Параметры:
- Возвращает:
-
Загруженный (возможно, уже загруженный) модуль.
- Тип возвращаемого значения:
-
load_binary(package, resource)[source] -
Загрузить сырые байты.
-
load_pickle(package, resource, map_location=None)[source] -
Распаковывает ресурс из пакета, загружая любые необходимые модули для построения объектов с помощью
import_module().- Параметры:
- Возвращает:
-
Распакованный объект.
- Тип возвращаемого значения:
-
Любой
-
-
load_text(package, resource, encoding='utf-8', errors='strict')[source] -
Загрузка строки.
- Параметры:
- Возвращает:
-
Загруженный текст.
- Тип возвращаемого значения:
-
python_version()[source] -
Возвращает версию Python, которая использовалась для создания этого пакета.
Примечание: эта функция экспериментальная и не гарантирует обратную совместимость. В будущем планируется переместить её в файл-замочек.
- Возвращает:
-
Optional[str]версию Python, например, 3.8.9, или None, если версия не сохранена с этим пакетом
-
-
class torch.package.Directory(name, is_dir)[source] -
Представление структуры файла. Организовано в виде узлов Directory, которые содержат списки своих дочерних Directory. Каталоги для пакета создаются вызовом
PackageImporter.file_structure().
© 2024, PyTorch Contributors
PyTorch has a BSD-style license, as found in the LICENSE file.
https://pytorch.org/docs/1.13/package.html