Динамические формы
Создано: 19 мая 2023 г. | Последнее обновление: 9 января 2026 г.
В этом разделе объясняется, как работать с динамическими формами в PyTorch, в том числе как отлаживать и исправлять распространённые ошибки, реализовывать поддержку динамических форм в операторах и понимать лежащие в их основе механизмы.
Динамические формы позволяют моделям PyTorch обрабатывать входные данные с различными размерами без повторной компиляции. Благодаря этому модели становятся более гибкими и могут обрабатывать разные размеры пакетов, длины последовательностей или размеры изображений в рамках одного скомпилированного артефакта. Динамические формы работают за счёт символьного отслеживания размеров тензоров вместо использования конкретных значений и создают граф вычислений, адаптирующийся к разным формам входных данных во время выполнения. По умолчанию PyTorch считает все формы входных данных статическими.
Как правило, компиляторы глубокого обучения поддерживают только статические формы, поэтому при изменении формы входных данных требуется повторная компиляция. Такой подход охватывает множество сценариев использования, однако в некоторых случаях его недостаточно:
- Переменные размеры — размеры пакетов или длины последовательностей меняются, например при адаптивной пакетной обработке.
- Выходные данные, зависящие от данных — модели формируют выходные данные на основе входных данных, например ограничивающие рамки переменного количества в моделях обнаружения объектов.
- Разреженные представления — обработка зависит от разреженных структур, меняющихся вместе с данными, например разреженных тензоров, тензоров с переменным числом элементов и графовых нейронных сетей.
Динамические формы не поддерживают программы с динамическим рангом, в которых меняется размерность входных тензоров, поскольку такие случаи встречаются редко и излишне усложняют обработку.
Что значит, что размер или целое число являются динамическими?
Динамические формы позволяют избежать повторных компиляций, делая динамическими определённые размерности или целые числа. Например, если функция f(x) скомпилирована со статическим размером, для обработки других размеров потребуется повторная компиляция:
Примечание
Для простоты в этом примере используется @torch.compile(dynamic=True). Обратите внимание: этот параметр не рекомендуется, поскольку его использование может привести к ошибкам. Рекомендуемый способ включения динамических форм см. в разделе Включение динамического поведения.
import torch
@torch.compile(dynamic=False)
def f(x):
return x* x.size()[0]
f(torch.rand(10))
f(torch.rand(20))
f(torch.rand(30))
f(torch.rand(40))
В полученном результате видно, что было создано четыре графа. См. соответствующий результат tlparse
Если сделать размер динамическим, функция сможет обрабатывать различные размеры без повторной компиляции:
import torch
@torch.compile(dynamic=True)
def f(x):
return x* x.size()[0]
f(torch.rand(10))
f(torch.rand(20))
f(torch.rand(30))
f(torch.rand(40))
При включённых динамических формах создаётся только один граф. См. соответствующий результат tlparse.
Для этого небольшого примера разница во времени компиляции минимальна, однако в более сложных сценариях использования улучшение производительности будет значительным.
Что такое специализация?
Специализация — это оптимизация графа вычислений для конкретных форм входных данных с проверкой условий, связанных с формами, во время управления потоком. Если выбор ветви зависит от условия, связанного с формой, граф адаптируется к этому условию. Если новые входные данные ему не соответствуют, система повторно скомпилирует граф.
Специализация позволяет создавать оптимизированные графы вычислений для конкретных форм входных данных, что может значительно повысить скорость выполнения.
import torch
@torch.compile(dynamic=True)
def f(x):
if x.size()[0] == 10:
return x * 10
if x.size()[0] <= 30:
return x*200
return x*x.size()[0]
f(torch.rand(10))
f(torch.rand(20))
f(torch.rand(30))
f(torch.rand(40))
f(torch.rand(50))
В приведённом выше коде специализируется граф, которому требуется размер входных данных 10; в этом случае он возвращает x * 10. Если размер входных данных меньше 30, он возвращает x * 200. В результате видно, что создаются три графа.
См. соответствующий результат tlparse
Так выглядят графы, созданные для приведённой выше функции:
Включение динамического поведения
Сделать значения динамическими можно следующими способами:
- Автоматическое включение динамичности
- Аннотации пользователя (рекомендуется)
- torch.compile (dynamic=true) (не рекомендуется) (только для тестирования)
- Расширенные параметры управления динамическим поведением (для сложных сценариев использования)
Подробнее о каждом из этих параметров читайте ниже.
Автоматическое включение динамичности
Автоматическое включение динамичности — поведение по умолчанию, при котором torch.compile() выполняет первую компиляцию, предполагая, что формы статические, и отслеживает размеры входных данных во время этой компиляции. При повторной компиляции эта информация используется для определения изменившихся размерностей, которые помечаются как динамические при второй компиляции.
Аннотации пользователя
Несколько API позволяют пользователям явно помечать определённые входные данные как динамические по имени или в коде. Это полезно, чтобы избежать первоначальных компиляций, после которых динамичность всё равно пришлось бы включать с помощью описанных выше средств. Аннотации также применяются для пометки элементов, которые автоматически не становятся динамическими, например параметров модулей нейронной сети. Аннотации пользователя — рекомендуемый способ включения динамических форм.
mark_dynamic(tensor, dim, min=min, max=max)
⚠️ Предупреждение
torch._dynamo.mark_dynamic() нельзя вызывать внутри функции, компилируемой с помощью torch.compile() (например, метода forward() модели или любой вызываемой им функции).
Эта функция является API времени трассировки. Если вызвать её из скомпилированного кода, Dynamo выдаст ошибку, например:
AssertionError: Attempt to trace forbidden callable
Правильный способ использования — вызвать mark_dynamic для входных тензоров до вызова torch.compile, например:
torch._dynamo.mark_dynamic(x, 0) compiled_model = torch.compile(model)
Функция torch._dynamo.mark_dynamic() помечает размерность тензора как динамическую и завершится с ошибкой, если размерность будет специализирована. Она не работает с целыми числами. Используйте эту функцию, только если уверены, что все графы в кадре, использующие этот вход, сойдутся к одному динамическому графу. В противном случае может появиться вводящее в заблуждение сообщение об ошибке нарушения ограничений. В таких случаях рассмотрите возможность использования torch._dynamo.maybe_mark_dynamic(). В настоящее время torch._dynamo.mark_dynamic() не имеет приоритета над force_parameter_static_shapes = True или force_nn_module_property_static_shapes = True.
Если заранее известно, что определённая размерность будет динамической, можно избежать первоначальной повторной компиляции, используя torch._dynamo.mark_dynamic(tensor, dim)(). Кроме того, если уже известны минимальное и максимальное возможные значения этой размерности, их можно указать с помощью torch._dynamo.mark_dynamic(tensor, dim, min=min, max=max)().
Вот краткий пример:
import torch
@torch.compile
def f(x):
return x * x.size()[0]
x = torch.randn(10)
torch._dynamo.mark_dynamic(x, 0)
# first invocation we give it is a tensor marked as dynamic
f(x)
# rest of these invocations will use dynamically compiled code
f(torch.randn(20))
f(torch.randn(30))
f(torch.randn(40))
maybe_mark_dynamic(tensor, dim)
Функция torch._dynamo.maybe_mark_dynamic() обладает теми же свойствами, что и torch._dynamo.mark_dynamic(), но не завершится с ошибкой, если размер будет специализирован. Используйте её для входных данных, общих для нескольких графов, или если число графов для определённого кадра не сходится к одному. Например, в приведённом выше примере используйте torch._dynamo.maybe_mark_dynamic(), поскольку графы для размеров 0 и 1 будут специализированы. Однако с помощью torch._dynamo.mark_dynamic() можно гарантировать, что специализация никогда не произойдёт.
mark_unbacked(tensor, dim)
Функция torch._dynamo.decorators.mark_unbacked() помечает размерность тензора как неподкреплённую. Вероятно, это не тот инструмент, который вам нужен, однако он может оказаться полезным, если специализация происходит внутри условия guard_size_oblivious(x) и использование этой функции позволяет её устранить. Убедитесь, что она устраняет специализацию и не приводит к ошибке зависимости от данных, из-за которой в месте специализации, которое вы пытаетесь обойти, или перед ним происходит разрыв графа. Возможно, лучше использовать следующий вариант.
Список разрешённых динамических источников (DYNAMIC_SOURCES)
Используйте переменную окружения TORCH_COMPILE_DYNAMIC_SOURCES, чтобы передать список имён источников, которые следует пометить как динамические. Например: TORCH_COMPILE_DYNAMIC_SOURCES=L[‘x’],L[‘y’] Проще всего найти имена динамических источников в артефакте PGO, расположенном в tlparse. Имена динамических источников можно скопировать из артефакта PGO. Этот способ работает с целыми числами и размерами тензоров и имеет наивысший приоритет среди всех остальных флагов, принудительно включающих статические формы. Ошибка не возникнет, если помеченное как динамическое значение будет специализировано или если указанный вход не существует.
Вот пример:
import torch
@torch.compile()
def f(x):
return x * x.size()[0]
with torch.compiler.config.patch(dynamic_sources="L['x']"):
f(torch.rand(10))
f(torch.rand(20))
f(torch.rand(30))
f(torch.rand(40))
torch.compiler.set_stance ("eager_then_compile")
Иногда бывает сложно определить, какие именно входные данные нужно пометить как динамические. Если вы готовы пожертвовать производительностью первого пакета, можно воспользоваться режимами eager_then_compile, которые автоматически определяют динамические входные данные. Подробнее см. в описании torch.compiler.set_stance() и в руководстве Управление динамической компиляцией с помощью torch.compiler.set_stance.
torch.compile (dynamic=true) (не рекомендуется)
Этот параметр принудительно делает динамическими все размеры и целые числа, повышая вероятность ошибок, связанных с динамическими формами. Его не рекомендуется включать, поскольку это может привести к ошибкам. Он сделает динамическим размер каждого входного значения, что может снизить производительность и увеличить время компиляции.
PyTorch также предоставляет расширенные параметры управления динамическими формами. См.: Расширенные параметры управления динамическим поведением.
Что делать дальше?
Если вы столкнулись с ошибкой в коде фреймворка или проблемой специализации, создайте сообщение об ошибке, чтобы её рассмотрели и, возможно, исправили. Если проблема связана с пользовательским кодом, подумайте, готовы ли вы переписать его, чтобы избежать этой проблемы. Определите, влияет ли она на корректность или связана с избыточной проверкой. Если проблема возникает в пользовательском ядре Triton с аргументом constexpr, оцените, можно ли переписать код, чтобы её устранить.
© 2026, PyTorch Contributors
PyTorch has a BSD-style license, as found in the LICENSE file.
https://docs.pytorch.org/docs/2.14/user_guide/torch_compiler/torch.compiler_dynamic_shapes.html