Spec-Zone.ru › PyTorch 2.14

Устранение неполадок torch.compile

Создано: 28 нояб. 2022 г. | Последнее обновление: 03 дек. 2025 г.

Вы пытаетесь использовать torch.compile для своей модели PyTorch, чтобы повысить её производительность, но всё работает не так, как ожидалось. Возможно, производительность не улучшается, возникают сбои или компиляция занимает слишком много времени. В этой статье приведены советы, обходные решения и инструменты отладки, которые помогут вам преодолеть эти трудности.

Содержание

  • Формирование ожиданий

    • Время компиляции
  • Терминология

    • Разрыв графа
    • Защитные проверки
    • Повторная компиляция
    • Динамические формы
  • Инструменты журналирования

    • tlparse / TORCH_TRACE
    • TORCH_LOGS
    • tlparse и TORCH_LOGS
  • Простые обходные решения

    • Где применять torch.compile?
    • Отключение и подавление ошибок
    • Устранение разрывов графа

      • Операции, зависящие от данных
      • Пользовательские операции
      • Вывод
      • Некорректный код
    • Работа с повторными компиляциями

      • Включены ли динамические формы?
      • Изменение ограничения размера кэша
      • Обёртывание констант в тензоры
  • Сообщение о проблемах

    • Абляция
    • Двоичный поиск
    • Создание воспроизводимого примера
    • Минимизатор
  • Углублённая отладка

    • TorchDynamo

      • Журналирование трассируемого Dynamo кода
      • Установка точек останова при трассировке Dynamo
      • Ошибки генерации байт-кода
    • AOTAutograd
    • Сводка параметров TORCH_LOGS
  • Связанные статьи

Формирование ожиданий

torch.compile — это компилятор PyTorch общего назначения. В отличие от предыдущего решения для компиляции TorchScript, для torch.compile требуется меньше изменений кода, поэтому модели обычно не нужно переписывать с нуля. Кроме того, неподдерживаемый код обрабатывается более корректно: он приводит к потере возможности оптимизации, а не к сбою.

В идеальном случае достаточно применить torch.compile к любой модели PyTorch, чтобы автоматически ускорить её. Однако на практике сложности кода могут привести к одному из трёх сценариев:

  1. torch.compile работает без проблем и обеспечивает ускорение.
  2. Необходимы некоторые изменения кода. torch.compile не вызывает сбой и не занимает слишком много времени, но заметного повышения производительности может не быть.
  3. Требуются существенные изменения кода.

Мы ожидаем, что большинство фрагментов кода попадёт в сценарии (1) и (2). В этом документе приведены советы, упорядоченные по степени необходимых усилий, которые помогут устранить проблемы кода в сценарии (2).

Время компиляции

torch.compile работает как JIT-компилятор, поэтому ожидается, что первый или два запуска скомпилированной функции будут значительно медленнее. Повторные компиляции, которые могут происходить при определённых условиях (описанных ниже), также замедляют выполнение. Различные компоненты torch.compile кэшируют результаты, чтобы сократить время компиляции при последующих вызовах, в том числе в других процессах. Компиляция при холодном запуске (без кэша) обычно занимает от нескольких секунд до нескольких минут для распространённых моделей и моделей, используемых в тестах производительности. Для более крупных моделей она может занимать от 30 минут до нескольких часов.

Терминология

Следующие термины важны для устранения проблем с torch.compile.

Разрыв графа

torch.compile трассирует ваш код и пытается преобразовать код PyTorch в единый граф вычислений, состоящий из операторов PyTorch (граф FX). Однако это не всегда возможно. При обнаружении кода, который нельзя трассировать, происходит «разрыв графа». При разрыве графа компилируется уже построенный граф FX, выполняется неподдерживаемый код, а затем трассировка возобновляется после него с построением нового графа FX. Поскольку граф вычислений разбивается на части, мы теряем возможности оптимизации, поэтому по возможности следует избегать разрывов графа в коде модели. Разрывы графа возникают, например, при использовании:

  • Условных операторов if, зависящих от данных
  • Многих встроенных функций Python
  • Функций C

Ниже приведён пример разрыва графа, вызванного функцией copy.deepcopy из встроенной библиотеки Python (точный вывод может отличаться).

import torch

@torch.compile
def fn(x):
    x = x + 1
    with open("test.txt", "r") as f:
        return x + len(f.read())

fn(torch.ones(3, 3))
$TORCH_LOGS="graph_breaks" python playground.py
Graph break in user code at /data/users/williamwen/pytorch/playground.py:7
Reason: Unsupported: builtin: open [<class 'torch._dynamo.variables.constant.ConstantVariable'>, <class 'torch._dynamo.variables.constant.ConstantVariable'>] False
User code traceback:
File "/data/users/williamwen/pytorch/playground.py", line 7, in fn
    with open("test.txt", "r") as f:
Traceback (most recent call last):
File "/data/users/williamwen/pytorch/torch/_dynamo/symbolic_convert.py", line 635, in wrapper
    return inner_fn(self, inst)
        ^^^^^^^^^^^^^^^^^^^^
File "/data/users/williamwen/pytorch/torch/_dynamo/symbolic_convert.py", line 2414, in CALL
    self._call(inst)
File "/data/users/williamwen/pytorch/torch/_dynamo/symbolic_convert.py", line 2408, in _call
    self.call_function(fn, args, kwargs)
File "/data/users/williamwen/pytorch/torch/_dynamo/symbolic_convert.py", line 962, in call_function
    self.push(fn.call_function(self, args, kwargs))  # type: ignore[arg-type]
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/data/users/williamwen/pytorch/torch/_dynamo/variables/builtin.py", line 997, in call_function
    return handler(tx, args, kwargs)
        ^^^^^^^^^^^^^^^^^^^^^^^^^
File "/data/users/williamwen/pytorch/torch/_dynamo/variables/builtin.py", line 831, in <lambda>
    return lambda *args: unimplemented(error_msg)
                        ^^^^^^^^^^^^^^^^^^^^^^^^
File "/data/users/williamwen/pytorch/torch/_dynamo/exc.py", line 313, in unimplemented
    raise Unsupported(msg, case_name=case_name)
torch._dynamo.exc.Unsupported: builtin: open [<class 'torch._dynamo.variables.constant.ConstantVariable'>, <class 'torch._dynamo.variables.constant.ConstantVariable'>] False

Защитные проверки

При трассировке кода torch.compile делает некоторые предположения о значениях во время выполнения. Во время трассировки создаются «защитные проверки» — проверки во время выполнения, подтверждающие эти предположения. При последующих вызовах скомпилированной функции выполняются проверки, чтобы определить, можно ли повторно использовать ранее скомпилированный код. Например, проверяться могут постоянные значения, типы и идентификаторы объектов.

Ниже приведён пример сгенерированных защитных проверок. Проверка TENSOR_MATCH проверяет тип, устройство, тип данных, форму и другие характеристики входных данных.

import torch

@torch.compile
def fn(x):
    return x + 1

fn(torch.ones(3, 3))
$ TORCH_LOGS="guards" python playground.py
GUARDS:

TREE_GUARD_MANAGER:
+- RootGuardManager
| +- DEFAULT_DEVICE: utils_device.CURRENT_DEVICE == None                           # _dynamo/output_graph.py:471 in init_ambient_guards
| +- GLOBAL_STATE: ___check_global_state()
| +- TORCH_FUNCTION_MODE_STACK: ___check_torch_function_mode_stack()
| +- GuardManager: source=L['x'], accessed_by=DictGetItemGuardAccessor(x)
| | +- TENSOR_MATCH: check_tensor(L['x'], Tensor, DispatchKeySet(CPU, BackendSelect, ADInplaceOrView, AutogradCPU), torch.float32, device=None, requires_grad=False, size=[3, 3], stride=[3, 1])  # return x + 1  # playground.py:6 in fn
| | +- NO_HASATTR: hasattr(L['x'], '_dynamo_dynamic_indices') == False           # return x + 1  # playground.py:6 in fn

Повторная компиляция

Если проверки не проходят для всех экземпляров ранее скомпилированного кода, torch.compile необходимо «повторно скомпилировать» функцию, заново трассировав исходный код.

В примере ниже требуется повторная компиляция, поскольку проверка формы аргумента-тензора не прошла.

import torch

@torch.compile
def fn(x):
    return x + 1

fn(torch.ones(3, 3))
fn(torch.ones(4, 4))
$ TORCH_LOGS="recompiles" python playground.py
Recompiling function fn in /data/users/williamwen/pytorch/playground.py:3
    triggered by the following guard failure(s):
    - 0/0: tensor 'L['x']' size mismatch at index 0. expected 3, actual 4

Динамические формы

Изначально torch.compile считает формы тензоров статическими/постоянными и устанавливает защитные проверки на основе этих предположений. Используя «динамические формы», можно добиться того, чтобы torch.compile создавал скомпилированный код, принимающий тензорные входные данные разных форм, — это позволяет избежать повторной компиляции при каждом изменении формы. По умолчанию автоматические динамические формы включены torch.compile(dynamic=None): если компиляция завершается неудачей из-за несовпадения форм, выполняется повторная попытка с динамическими формами. Динамические формы также можно включить полностью dynamic=True или отключить dynamic=False.

Ниже мы включаем динамические формы и видим, что повторная компиляция больше не требуется.

import torch

@torch.compile(dynamic=True)
def fn(x):
    return x + 1

fn(torch.ones(3, 3))
fn(torch.ones(4, 4))
$ TORCH_LOGS="dynamic,recompiles" python playground.py
create_symbol s0 = 3 for L['x'].size()[0] [2, int_oo] at playground.py:5 in fn (_dynamo/variables/builder.py:2718 in <lambda>), for more info run with TORCHDYNAMO_EXTENDED_DEBUG_CREATE_SYMBOL="s0"
produce_guards
produce_guards

Дополнительные сведения о динамических формах см. в руководстве по динамическим формам.

Инструменты журналирования

tlparse / TORCH_TRACE

tlparse / TORCH_TRACE — это пара инструментов, создающих отчёты о компиляции, например: https://web.mit.edu/~ezyang/Public/bhack-20240609-tlparse/index.html.

Собирать трассировки очень просто. Чтобы собрать трассировку, запустите команду воспроизведения проблемы с помощью

TORCH_TRACE="/tmp/tracedir" python foo.py
pip install tlparse
tlparse /tmp/tracedir

Этот подход работает даже при запуске распределённой задачи и предоставляет трассировку для каждого ранга. В браузере откроется HTML-отчёт, похожий на приведённый выше. Если вы сообщаете о сложной проблеме, для которой у вас нет автономного примера воспроизведения, вы всё равно можете существенно помочь разработчикам PyTorch, приложив журнал трассировки, созданный в /tmp/tracedir.

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

Журнал трассировки содержит весь код вашей модели. Не делитесь им, если модель, над которой вы работаете, содержит конфиденциальные данные. Журнал трассировки НЕ содержит веса.

Вывод tlparse предназначен в первую очередь для разработчиков PyTorch, а формат журнала позволяет легко загружать его на GitHub и делиться им. Однако даже если вы не разрабатываете PyTorch, из него можно извлечь полезную информацию. Рекомендуем начать со встроенной справки в отчёте, где объясняется его содержимое. Вот какую информацию можно получить из tlparse:

  • Какой код модели был скомпилирован? Это можно определить по дереву стека. Особенно полезно, если вы не знакомы с компилируемой кодовой базой!
  • Сколько разрывов графа / отдельных областей компиляции было обнаружено? (Каждая отдельная компиляция обозначается блоком своего цвета, например [0/0]). Кадры, в которых возможен разрыв графа, выделены светло-зелёным цветом: [2/4]. Большое количество таких кадров вызывает подозрения и говорит о том, что произошли серьёзные разрывы графа или ваш код плохо подходит для torch.compile.
  • Сколько раз выполнялась повторная компиляция определённого кадра? Если компиляция повторялась много раз, это будет выглядеть так: [10/0] [10/1] [10/2] — если что-то часто компилируется повторно, это вызывает подозрения и заслуживает внимания, даже если не является первопричиной проблемы.
  • Возникла ли ошибка компиляции? Кадры с ошибками будут выглядеть так: [0/1].
  • Какие промежуточные результаты компилятора были созданы для данного кадра? Например, можно просмотреть сгенерированный граф FX высокого уровня или код Triton.
  • Есть ли относящаяся к делу информация для определённого кадра? Её можно найти в compilation_metrics.

TORCH_LOGS

С помощью переменной среды TORCH_LOGS можно выборочно включать журналирование отдельных частей стека torch.compile. Фактически TORCH_LOGS является источником журналов для tlparse. Формат переменной среды TORCH_LOGS выглядит так:

TORCH_LOGS="<option1>,<option2>,..." python foo.py

К полезным параметрам высокого уровня относятся:

  • graph_breaks: записывает в журнал расположение разрывов графа в пользовательском коде и их причины
  • guards: записывает в журнал созданные защитные проверки
  • recompiles: записывает в журнал, какая функция была скомпилирована повторно и какие проверки не прошли, что привело к повторной компиляции
  • dynamic: записывает в журнал сведения о динамических формах

Кроме того, параметры журналирования можно задать программно с помощью torch._logging.set_logs:

import logging
torch._logging.set_logs(graph_breaks=True)
...

Другие параметры TORCH_LOGS перечислены в разделе Сводка параметров TORCH_LOGS. Полный список параметров см. в документации torch._logging и torch._logging.set_logs.

tlparse и TORCH_LOGS

Как правило, при возникновении проблем мы рекомендуем сначала использовать tlparse. tlparse идеально подходит для отладки больших моделей и получения общего представления о том, как была скомпилирована модель. С другой стороны, TORCH_LOGS предпочтительнее для небольших примеров и детальной отладки, когда уже известно, какой компонент torch.compile вызывает проблему.

Простые обходные решения

Здесь мы описываем некоторые обходные решения проблем torch.compile, связанные с небольшими изменениями кода или изменением некоторых параметров torch.compile.

Где применять torch.compile?

Рекомендуем применять torch.compile к функции самого высокого уровня, которая не вызывает чрезмерных проблем. Как правило, это шаг обучения или оценки с оптимизатором, но без цикла, ваша nn.Module верхнего уровня или какая-либо под-nn.Module``s. ``torch.compile специально не очень хорошо работает с модулями-обёртками для распределённых вычислений, такими как DDP или FSDP, поэтому рассмотрите возможность применения torch.compile к внутреннему модулю, передаваемому обёртке.

# inference
model = ...
opt_model = torch.compile(model)

for _ in range(N_ITERS):
    inp = ...
    out = opt_model(inp)
# training
model = ...
opt = torch.optim.Adam(model.parameters())

@torch.compile
def train(mod, data):
    opt.zero_grad(True)
    pred = mod(data[0])
    loss = torch.nn.CrossEntropyLoss()(pred, data[1])
    loss.backward()
    opt.step()

for _ in range(N_ITERS):
    inp = ...
    train(model, inp)
# DistributedDataParallel
model = ...
opt_model = torch.compile(model)
model_ddp = DistributedDataParallel(opt_model, ...)

for _ in range(N_ITERS):
    inp = ...
    out = model_ddp(inp)

Отключение и подавление ошибок

В некоторых архитектурах моделей есть части, которые особенно сложно скомпилировать: либо в них происходит много разрывов графа, либо возникают сбои. Возможно, вы захотите явно отключить проблемные части модели, чтобы применить torch.compile к тем частям, которые работают. Для этого можно использовать декоратор @torch.compiler.disable. Когда torch.compile пытается вызвать отключённую функцию, происходит разрыв графа, трассировка отключённой функции пропускается и возобновляется после вызова. По умолчанию все рекурсивные вызовы из отключённой функции также отключены. Используйте параметр recursive=False, чтобы разрешить компиляцию рекурсивных вызовов.

def bad1_inner(...):
    # skipped

@torch.compiler.disable
def bad1_outer(...):
    # skipped
    bad1_inner(...)

def bad2_inner(...)
    # traced

@torch.compiler.disable(recursive=False)
def bad2_outer(...):
    # skipped
    bad2_inner(...)

@torch.compile
def fn(...):
    # graph break
    bad1_outer(...)
        ...
    # graph break
    bad2_outer(...)

Например, мы используем torch.compiler.disable, чтобы отключить torch.compile для разреженной архитектуры в рекомендательных моделях, поскольку разреженную архитектуру сложно скомпилировать. Функции предварительной обработки и ведения журналов — другие примеры функций, которые обычно вызывают множество разрывов графа и не получают преимуществ от компиляции.

Если возникают сбои компилятора и вы хотите продолжить работу несмотря на них, можно задать torch._dynamo.config.suppress_errors = True. При сбое компилятора мы просто пропустим трассировку функции и попробуем снова позже. Это не лучший подход — со временем лучше вручную добавлять аннотации отключения там, где это необходимо.

Устранение разрывов графа

Чтобы максимально использовать возможности оптимизации, важно сократить количество разрывов графа. Напомним, что узнать о происходящих разрывах графа можно с помощью tlparse или TORCH_LOGS="graph_breaks". В целом разрывы графа возникают по одной из следующих причин:

  1. Вы пытаетесь выполнить действие, которое принципиально невозможно трассировать, например управление потоком, зависящее от данных.
  2. Вы пытаетесь выполнить действие, которое пока не поддерживается. Например, в настоящее время поддержка трассировки кода, использующего встроенный модуль Python inspect, ограничена.
  3. В вашем коде есть ошибка. Например, возможно, вы вызвали функцию с неправильным количеством аргументов.

В журналах разрывов графа указаны расположение пользовательского кода и причина разрыва. К сожалению, многие разрывы графа нельзя устранить без более глубокого понимания Dynamo. Даже определить, какая из трёх причин вызвала разрыв графа, может быть непросто. Мы работаем над тем, чтобы сообщения о разрывах графа содержали более полезные сведения.

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

Если сообщение о разрыве графа не подсказывает, что делать, вы подозреваете, что причина разрыва — (2), и считаете, что разрыв снижает производительность, сообщите о нём как об ошибке. Если в функции происходит много разрывов графа, рассмотрите возможность отключить компиляцию этой функции, поскольку накладные расходы на разрывы графа могут стать чрезмерными.

Ниже приведены некоторые распространённые разрывы графа и способы их устранения.

Операции, зависящие от данных

torch.compile вызывает разрывы графа при операциях, зависящих от данных, например при управлении потоком, зависящем от данных (условные операторы, циклы с тензорами), и при прямом доступе к данным тензоров (.item, .data_ptr).

import torch

@torch.compile
def fn(x):
    y = x.sum()
    if y > 0:
        return x + y.item()
    return x - y.item()

fn(torch.ones(3, 3))
$ TORCH_LOGS="graph_breaks" python playground.py
Graph break in user code at /data/users/williamwen/pytorch/playground.py:6
Reason: Data-dependent jump
User code traceback:
File "/data/users/williamwen/pytorch/playground.py", line 6, in fn
    if y > 0:

Graph break in user code at /data/users/williamwen/pytorch/playground.py:7
Reason: Unsupported: Tensor.item
User code traceback:
File "/data/users/williamwen/pytorch/playground.py", line 7, in torch_dynamo_resume_in_fn_at_6
    return x + y.item()
Traceback (most recent call last):
File "/data/users/williamwen/pytorch/torch/_dynamo/symbolic_convert.py", line 616, in wrapper
    return inner_fn(self, inst)
        ^^^^^^^^^^^^^^^^^^^^
File "/data/users/williamwen/pytorch/torch/_dynamo/symbolic_convert.py", line 2288, in CALL
    self._call(inst)
File "/data/users/williamwen/pytorch/torch/_dynamo/symbolic_convert.py", line 2282, in _call
    self.call_function(fn, args, kwargs)
File "/data/users/williamwen/pytorch/torch/_dynamo/symbolic_convert.py", line 838, in call_function
    self.push(fn.call_function(self, args, kwargs))  # type: ignore[arg-type]
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/data/users/williamwen/pytorch/torch/_dynamo/variables/misc.py", line 1038, in call_function
    return self.obj.call_method(tx, self.name, args, kwargs)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/data/users/williamwen/pytorch/torch/_dynamo/variables/tensor.py", line 527, in call_method
    result = handler_method(*args, **kwargs)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/data/users/williamwen/pytorch/torch/_dynamo/variables/tensor.py", line 773, in method_item
    unimplemented("Tensor.item")
File "/data/users/williamwen/pytorch/torch/_dynamo/exc.py", line 304, in unimplemented
    raise Unsupported(msg, case_name=case_name)
torch._dynamo.exc.Unsupported: Tensor.item

Общий способ избежать таких разрывов графа — не выполнять операции, зависящие от данных. Вот несколько конкретных способов:

  • Если управление потоком на самом деле не зависит от значений данных, измените код так, чтобы управление потоком зависело от констант.
# old
x = torch.randn(3, 3)
@torch.compile
def fn(y):
    if x.sum() > 0:
        return y + x
    else:
        return y - x

# new
x = torch.randn(3, 3)
cond = (x.sum() > 0).item()
@torch.compile
def fn(y):
    if cond:
        return y + x
    else:
        return y - x
  • Используйте операции высшего порядка, например torch.cond (https://pytorch.org/docs/main/cond.html), вместо управления потоком, зависящего от данных
# old
@torch.compile
def fn(x):
    if x.sum() > 0:
        return x + 1
    return x - 1

# new
@torch.compile
def fn(x):
    return torch.cond(
        x.sum() > 0,
        lambda x: x + 1,
        lambda x: x - 1,
        (x,),
    )
  • Если у вас есть вызов .item(), попробуйте torch._dynamo.config.capture_scalar_outputs = True или TORCHDYNAMO_CAPTURE_SCALAR_OUTPUTS=1
  • Оберните проблемные части функции в пользовательскую операцию

Пользовательские операции

Если torch.compile не удаётся трассировать ваш код из-за отсутствия поддержки или принципиальной несовместимости, можно обернуть проблемный код в пользовательскую операцию.

Чтобы обеспечить совместимость пользовательских операций с torch.compile, потребуется выполнить несколько дополнительных действий. Подробнее см. в разделе https://pytorch.org/tutorials/advanced/custom_ops_landing_page.html.

Вывод

Вывод, ведение журналов и отправка предупреждений приводят к разрыву графа. Если функция часто вызывает функции ведения журналов, например записывает данные об итерации обучения, рассмотрите возможность применить к ней torch.compiler.disable.

В качестве альтернативы можно попробовать использовать torch._dynamo.config.reorderable_logging_functions. Эта настройка используется для перемещения функций ведения журналов так, чтобы они вызывались в конце трассируемой функции, что позволяет избежать разрыва графа. Однако записываемое содержимое может отличаться, например, если происходит изменение данных.

import torch

torch._dynamo.config.reorderable_logging_functions.add(print)

@torch.compile
def fn(x):
    x += 1
    print("log!")
    return torch.sin(x)

fn(torch.ones(3, 3))
$ TORCH_LOGS="graph_breaks" python playground.py
log!

Ошибочный код

Возможно, в вашем коде есть ошибка или возникла проблема, не связанная с torch.compile. В приведённом ниже коде мы допустили опечатку при вызове torch.sin, передав лишний аргумент.

import torch

@torch.compile
def fn(x):
    y = torch.sin(x, x)
    return y

fn(torch.ones(3, 3))
$ TORCH_LOGS="graph_breaks" python playground.py
Graph break in user code at /data/users/williamwen/pytorch/playground.py:5
Reason: Unsupported: TypeError <built-in method sin of type object at 0x7fd6fd764600>: sin() takes 1 positional argument but 2 were given
User code traceback:
File "/data/users/williamwen/pytorch/playground.py", line 5, in fn
    y = torch.sin(x, x)
...

По журналам бывает трудно определить, вызвана ли ошибка вашим кодом или ошибкой torch.compile. Чтобы это выяснить, рекомендуем запустить код без torch.compile и проверить, возникает ли ошибка по-прежнему.

Работа с повторной компиляцией

Сведения о повторных компиляциях и их причинах можно посмотреть с помощью tlparse или TORCH_LOGS=recompiles.

Включены ли динамические формы?

Повторные компиляции из-за несовпадения форм имеют следующий вид:

tensor 'L['x']' size mismatch at index 0. expected 3, actual 4

Убедитесь, что для параметра dynamic у torch.compile не задано значение False. Значение по умолчанию, dynamic=None, означает, что динамические формы будут использоваться только после первой компиляции. Чтобы сразу выполнить компиляцию с максимально возможным использованием динамических форм, задайте dynamic=True.

Дополнительные сведения о динамических формах см. в руководстве по динамическим формам.

Изменение ограничения размера кэша

Количество повторных компиляций функции ограничено значениями torch._dynamo.config.recompile_limit и torch._dynamo.config.accumulated_recompile_limit. Если превышено любое из этих ограничений, функция больше не будет компилироваться и вместо этого будет выполняться в обычном режиме. torch.compile также выдаст предупреждение с указанием затронутой функции и превышенного ограничения. В приведённом ниже примере каждый вызов функции приводит к попытке повторной компиляции. Когда размер кэша достигает предельного значения (8), попытки повторной компиляции прекращаются.

import torch

@torch.compile(dynamic=False)
def fn(x):
    return x + 1

for i in range(1, 10):
    fn(torch.ones(i))
$ python playground.py
torch._dynamo hit config.recompile_limit (8)
    function: 'fn' (/data/users/williamwen/pytorch/playground.py:5)
    last reason: 0/0: tensor 'L['x']' size mismatch at index 0. expected 1, actual 9

Если вы знаете, что количество повторных компиляций имеет разумную постоянную верхнюю границу, можно увеличить ограничение размера кэша. Если затраты на повторную компиляцию превышают преимущества компиляции, можно уменьшить это ограничение.

Обёртывание констант в тензоры

По умолчанию переменные int / float рассматриваются как константы, и для них устанавливаются соответствующие проверки. В приведённом ниже примере каждая функция компилируется заново при каждом вызове.

import torch

@torch.compile
def fn(x, c):
    return x + c

for i in range(1, 10):
    fn(torch.ones(i), 0.5 + i)
$ TORCH_LOGS="recompiles" python playground.py
Recompiling function fn in /data/users/williamwen/pytorch/playground.py:3
    triggered by the following guard failure(s):
    - 0/7: L['c'] == 8.5
    - 0/6: L['c'] == 7.5
    - 0/5: L['c'] == 6.5
    - 0/4: L['c'] == 5.5
    - 0/3: L['c'] == 4.5
    - 0/2: L['c'] == 3.5
    - 0/1: L['c'] == 2.5
    - 0/0: L['c'] == 1.5
torch._dynamo hit config.recompile_limit (8)
    function: 'fn' (/data/users/williamwen/pytorch/playground.py:3)
    last reason: 0/0: L['c'] == 1.5

В частности, для планировщиков скорости обучения инициализация константой может приводить к повторным компиляциям:

import torch

mod = torch.nn.Linear(3, 3)
opt = torch.optim.Adam(mod.parameters(), lr=0.01)
sched = torch.optim.lr_scheduler.ExponentialLR(opt, 0.9)

@torch.compile
def fn(inp):
    opt.zero_grad(True)
    out = mod(inp).sum()
    out.backward()
    opt.step()
    sched.step()

for i in range(1, 10):
    fn(torch.ones(3, 3))
$ TORCH_LOGS="recompiles" python playground.py
Recompiling function step in /data/users/williamwen/pytorch/torch/optim/adam.py:189
    triggered by the following guard failure(s):
    - 3/7: L['self'].param_groups[0]['lr'] == 0.004782969000000002
    - 3/6: L['self'].param_groups[0]['lr'] == 0.005314410000000002
    - 3/5: L['self'].param_groups[0]['lr'] == 0.005904900000000002
    - 3/4: L['self'].param_groups[0]['lr'] == 0.006561000000000002
    - 3/3: L['self'].param_groups[0]['lr'] == 0.007290000000000001
    - 3/2: L['self'].param_groups[0]['lr'] == 0.008100000000000001
    - 3/1: L['self'].param_groups[0]['lr'] == 0.009000000000000001
    - 3/0: L['self'].param_groups[0]['lr'] == 0.01
torch._dynamo hit config.recompile_limit (8)
    function: 'step' (/data/users/williamwen/pytorch/torch/optim/adam.py:189)
    last reason: 3/0: L['self'].param_groups[0]['lr'] == 0.01

В обоих примерах можно обернуть переменные с плавающей точкой в тензоры, чтобы избежать повторных компиляций.

# first example
for i in range(1, 10):
    fn(torch.ones(i), torch.tensor(0.5 + i))

# second example
opt = torch.optim.Adam(mod.parameters(), lr=torch.tensor(0.01))
sched = torch.optim.lr_scheduler.ExponentialLR(opt, torch.tensor(0.9))

Сообщение о проблемах

Если приведённых выше обходных решений оказалось недостаточно, чтобы torch.compile заработал, рассмотрите возможность сообщить о проблеме в PyTorch. Но есть несколько вещей, которые помогут нам значительно упростить работу.

Абляция

С помощью параметра backend= для torch.compile проверьте, какой компонент стека torch.compile вызывает проблему. В частности, попробуйте:

  • torch.compile(fn, backend="eager"), который запускает только TorchDynamo — компонент захвата графа в torch.compile.
  • torch.compile(fn, backend="aot_eager"), который запускает TorchDynamo и AOTAutograd, дополнительно создающий граф обратного прохода во время компиляции.
  • torch.compile(fn, backend="aot_eager_decomp_partition"), который запускает TorchDynamo и AOTAutograd с декомпозициями/разбиением операторов.
  • torch.compile(fn, backend="inductor"), который запускает TorchDynamo, AOTAutograd и TorchInductor — серверный ML-компилятор, генерирующий скомпилированные ядра.

Если сбой происходит только с серверной частью Inductor, можно дополнительно проверить различные режимы Inductor:

  • torch.compile(fn, backend="inductor", mode="default")
  • torch.compile(fn, backend="inductor", mode="reduce-overhead")
  • torch.compile(fn, backend="inductor", mode="max-autotune")

Также можно проверить, вызывают ли динамические формы проблемы в какой-либо серверной части:

  • torch.compile(fn, dynamic=True) (всегда использовать динамические формы)
  • torch.compile(fn, dynamic=False) (никогда не использовать динамические формы)
  • torch.compile(fn, dynamic=None) (автоматическое использование динамических форм)

Поиск регрессии методом бисекции

Пробовали ли вы последнюю ночную сборку? Раньше что-то работало, но теперь перестало? Можете ли вы выполнить бисекцию, чтобы определить первую ночную сборку, в которой возникает проблема? Бисекция особенно полезна при регрессиях производительности, точности или времени компиляции, когда источник проблемы не очевиден.

Создание воспроизводящего примера

Создание воспроизводящих примеров требует немало усилий, и ничего страшного, если у вас нет на это времени. Однако если вы заинтересованный пользователь, не знакомый с внутренним устройством torch.compile, создание автономного воспроизводящего примера может существенно помочь нам исправить ошибку. Без такого примера в отчёте об ошибке должно быть достаточно информации, чтобы мы могли определить основную причину проблемы и написать воспроизводящий пример с нуля.

Ниже приведён список полезных воспроизводящих примеров, упорядоченных от наиболее предпочтительных к наименее предпочтительным:

  1. Небольшой автономный воспроизводящий пример: скрипт без внешних зависимостей, состоящий менее чем из 100 строк кода и воспроизводящий проблему при запуске.
  2. Большой автономный воспроизводящий пример: даже если он большой, автономность — огромное преимущество!
  3. Неавтономный воспроизводящий пример с управляемыми зависимостями: например, если проблему можно воспроизвести, запустив скрипт после pip install transformers, это приемлемо. Скорее всего, мы сможем запустить его и провести расследование.
  4. Неавтономный воспроизводящий пример, требующий сложной настройки: это может включать скачивание наборов данных, несколько этапов настройки окружения или определённые версии системных библиотек, для которых требуется образ Docker. Чем сложнее настройка, тем труднее нам воссоздать окружение.

    Примечание

    Docker simplifies setup but complicates changes to the environment, so it's not a perfect solution, though we'll use it if necessary.
    

Кроме того, воспроизводящий пример, который можно запустить в одном процессе, предпочтительнее примера, требующего многопроцессного обучения (но, повторимся, если у вас есть только многопроцессный пример, мы всё равно его примем!).

Ниже приведён неполный список аспектов, которые стоит проверить в вашей проблеме и попытаться воспроизвести в примере:

  • Autograd. Были ли у вас входные тензоры с requires_grad=True? Вызывали ли вы backward() для выходных данных?
  • Динамические формы. Задавали ли вы dynamic=True? Или запускали тестовый код несколько раз с различными формами?
  • Пользовательские операторы. Используется ли в реальном рабочем процессе пользовательский оператор? Можно ли воспроизвести некоторые его важные характеристики с помощью API пользовательских операторов Python?
  • Конфигурация. Задали ли вы ту же конфигурацию? Сюда входят параметры torch._dynamo.config и torch._inductor.config, а также аргументы torch.compile, например backend / mode.
  • Менеджеры контекста. Воспроизвели ли вы все активные менеджеры контекста? Это могут быть torch.no_grad, автоматическое смешение точности, TorchFunctionMode / TorchDispatchMode, контрольные точки активаций, скомпилированный autograd и т. д.
  • Подклассы тензоров. Используется ли подкласс тензора?

Минимизатор

Минимизатор — это экспериментальный инструмент torch.compile, который получает граф FX, аварийно завершающий работу при попытке его выполнить или скомпилировать, находит подграф, который также вызывает сбой, и выводит код, выполняющий операции этого подграфа. По сути, минимизатор находит минимальный воспроизводящий пример для определённого класса сбоев, связанных с torch.compile. Для этого предполагается, что трассировка кода прошла успешно.

К сожалению, в наши дни минимизатор чаще всего работает не так, как ожидается, и могут потребоваться альтернативные методы. Вероятно, это связано с тем, что ошибки, которые можно автоматически воспроизвести таким способом, обычно проще исправить и их уже устранили, а оставшиеся более сложные проблемы трудно воспроизводятся. Однако попробовать использовать минимизатор несложно, поэтому стоит попытаться, даже если попытка может оказаться неудачной.

Инструкции по работе с минимизатором приведены здесь. Если компилятор аварийно завершает работу, можно задать TORCHDYNAMO_REPRO_AFTER="dynamo" или TORCHDYNAMO_REPRO_AFTER="aot". Параметр aot с большей вероятностью поможет, хотя он может не выявить проблемы AOTAutograd. В результате будет создан файл repro.py, который может помочь диагностировать проблему. Для проблем, связанных с точностью, рассмотрите возможность задать TORCHDYNAMO_REPRO_LEVEL=4. Обратите внимание, что таким способом не всегда удаётся успешно определить проблемный подграф.

Углублённая отладка

В этом разделе описаны инструменты и методы для самостоятельной отладки проблем torch.compile или более глубокого понимания стека torch.compile. Эти методы сложнее описанных выше и регулярно используются разработчиками PyTorch для отладки реальных проблем torch.compile.

Ниже представлен общий обзор стека:

Torch Dynamo Stack

Стек состоит из трёх основных компонентов: TorchDynamo, AOTAutograd и Inductor. Наша стратегия отладки заключается в том, чтобы сначала определить компонент, в котором возникает ошибка, а затем отлаживать его отдельно. Чтобы определить, какой компонент вызывает проблему, см. раздел Ablation выше в разделе Reporting Issues. Рекомендации по отладке конкретного компонента приведены в разделах ниже.

TorchDynamo

Журналирование трассируемого Dynamo кода

Параметр TORCH_LOGS=trace_bytecode позволяет просматривать точные инструкции байт-кода, которые трассирует Dynamo, а также символическое представление стека интерпретатора Python. При возникновении разрыва графа или сбоя рекомендуется проверить последние несколько трассированных инструкций байт-кода.

Также можно использовать TORCH_LOGS=trace_source, чтобы увидеть, какие строки исходного кода трассирует Dynamo. Это полезно в сочетании с trace_bytecode, позволяющим увидеть, какой строке исходного кода соответствует каждая трассированная инструкция байт-кода.

Наконец, можно использовать TORCH_LOGS=graph_code, чтобы увидеть код Python, представляющий граф FX, трассированный Dynamo. Просмотрите этот код, чтобы убедиться, что трассируются правильные операции.

import torch

def g(x, y):
    return x + y

@torch.compile(backend="eager")
def f(x):
    x = torch.sin(x)
    x = g(x, x)
    return x

f(torch.ones(3, 3))
$ TORCH_LOGS="trace_bytecode,trace_source,graph_code" python playground.py
TRACE starts_line /data/users/williamwen/pytorch/playground.py:6 in f ()
    @torch.compile(backend="eager")
TRACE RESUME 0 []
TRACE starts_line /data/users/williamwen/pytorch/playground.py:8 in f (f)
        x = torch.sin(x)
TRACE LOAD_GLOBAL torch []
TRACE LOAD_ATTR sin [NullVariable(), PythonModuleVariable(<module 'torch' from '/data/users/williamwen/pytorch/torch/__init__.py'>)]
TRACE LOAD_FAST x [NullVariable(), TorchInGraphFunctionVariable(<built-in method sin of type object at 0x7f00f6964600>)]
TRACE CALL 1 [NullVariable(), TorchInGraphFunctionVariable(<built-in method sin of type object at 0x7f00f6964600>), LazyVariableTracker()]
TRACE STORE_FAST x [TensorVariable()]
TRACE starts_line /data/users/williamwen/pytorch/playground.py:9 in f (f)
        x = g(x, x)
TRACE LOAD_GLOBAL g []
TRACE LOAD_FAST x [NullVariable(), UserFunctionVariable()]
TRACE LOAD_FAST x [NullVariable(), UserFunctionVariable(), TensorVariable()]
TRACE CALL 2 [NullVariable(), UserFunctionVariable(), TensorVariable(), TensorVariable()]
TRACE starts_line /data/users/williamwen/pytorch/playground.py:3 in g (g) (inline depth: 1)
    def g(x, y):
TRACE RESUME 0 []
TRACE starts_line /data/users/williamwen/pytorch/playground.py:4 in g (g) (inline depth: 1)
        return x + y
TRACE LOAD_FAST x []
TRACE LOAD_FAST y [TensorVariable()]
TRACE BINARY_OP 0 [TensorVariable(), TensorVariable()]
TRACE RETURN_VALUE None [TensorVariable()]
TRACE STORE_FAST x [TensorVariable()]
TRACE starts_line /data/users/williamwen/pytorch/playground.py:10 in f (f)
        return x
TRACE LOAD_FAST x []
TRACE RETURN_VALUE None [TensorVariable()]
TRACED GRAPH
===== __compiled_fn_1 =====
/data/users/williamwen/pytorch/torch/fx/_lazy_graph_module.py class GraphModule(torch.nn.Module):
    def forward(self, L_x_: "f32[3, 3][3, 1]cpu"):
        l_x_ = L_x_

        # File: /data/users/williamwen/pytorch/playground.py:8 in f, code: x = torch.sin(x)
        x: "f32[3, 3][3, 1]cpu" = torch.sin(l_x_);  l_x_ = None

        # File: /data/users/williamwen/pytorch/playground.py:4 in g, code: return x + y
        x_1: "f32[3, 3][3, 1]cpu" = x + x;  x = None
        return (x_1,)

Установка точек останова при трассировке Dynamo

Иногда полезно установить точку останова в коде Dynamo или пользовательском коде, чтобы увидеть состояние Dynamo во время трассировки пользовательского кода. К сожалению, установка точки останова обычным способом в Python приведёт к разрыву графа в TorchDynamo, поэтому мы не сможем просмотреть состояние Dynamo в том месте, где хотели установить точку останова.

Первый способ установить точку останова — добавить её в исходный код Dynamo. Рекомендуется использовать три следующих места:

  • В torch/_dynamo/symbolic_convert.py установите точку останова в функциях, названных в соответствии с проблемной инструкцией байт-кода, например def CALL_FUNCTION и def STORE_ATTR. Точку останова можно устанавливать условно, исходя из входных данных, например argval инструкции или имени объекта в вершине стека, поскольку некоторые опкоды байт-кода используются часто.
  • Установите точку останова в месте возникновения разрыва графа или ошибки. Обычно разрывы графа возникают при вызове unimplemented(...).
  • Установите точку останова в torch/_dynamo/variables/builder.py, function:_wrap. Вероятно, для этого потребуется установить условие на входные данные. Эта функция определяет, как представить заданное значение символически. Установите здесь точку останова, если подозреваете, что значение представлено неверно.

Второй способ установить точку останова — использовать torch._dynamo.comptime.comptime.breakpoint:

from torch._dynamo.comptime import comptime

@torch.compile
def f(...):
    ...
    comptime.breakpoint()
    ...

Точка останова comptime удобна тем, что позволяет проверять состояние Dynamo в определённом месте пользовательского кода, который трассируется. Для этого не нужно устанавливать точку останова в исходном коде Dynamo или задавать условия останова на основе переменных.

При срабатывании точки останова comptime можно выполнить следующие действия:

  • ctx.print_bt(), чтобы вывести трассировку стека пользовательского кода
  • ctx.print_locals(), чтобы вывести все текущие локальные переменные
  • ctx.print_graph(), чтобы вывести граф, который трассируется в данный момент
  • ctx.disas(), чтобы вывести байт-код трассируемой в данный момент функции
  • Используйте стандартные команды pdb, например bt/u/d/n/s/r: можно подняться вверх по стеку pdb, чтобы изучить внутреннее устройство Dynamo
import torch
from torch._dynamo.comptime import comptime

@torch.compile(backend="eager")
def f(x):
    y = x + 1
    comptime.breakpoint()
    y = y + 1
    return y

f(torch.ones(3, 3))
$ python playground.py
--Return--
> /data/users/williamwen/pytorch/torch/_dynamo/comptime.py(392)inner()->None
-> builtins.breakpoint()
(Pdb) ctx.print_bt()
File "/data/users/williamwen/pytorch/playground.py", line 7, in f
    comptime.breakpoint()

(Pdb) ctx.print_locals()
x = FakeTensor(..., size=(3, 3))
y = FakeTensor(..., size=(3, 3))
(Pdb) bt
...
/data/users/williamwen/pytorch/torch/_dynamo/symbolic_convert.py(826)call_function()
-> self.push(fn.call_function(self, args, kwargs))  # type: ignore[arg-type]
/data/users/williamwen/pytorch/torch/_dynamo/variables/misc.py(331)call_function()
-> func(ComptimeContext(tx))
> /data/users/williamwen/pytorch/torch/_dynamo/comptime.py(392)inner()->None
-> builtins.breakpoint()
(Pdb) ctx.print_graph()



def forward(self, L_x_: "f32[3, 3]"):
    l_x_ = L_x_

    # File: /data/users/williamwen/pytorch/playground.py:6 in f, code: y = x + 1
    y: "f32[3, 3]" = l_x_ + 1;  l_x_ = y = None

Ошибки генерации байт-кода

Хотя это случается редко, Dynamo может генерировать некорректный байт-код. Возможно, это происходит, если вы обнаружили следующее:

  • Абляция показывает, что ошибка возникает на уровне TorchDynamo
  • Ошибка возникает не в кадрах стека TorchDynamo
  • Ошибка больше похожа на ошибку в пользовательском коде, чем на ошибку Dynamo, либо происходит ошибка сегментации
  • Ошибка не возникает без torch.compile

Ошибки генерации байт-кода обычно трудно исправить, поэтому мы рекомендуем отправить отчёт о проблеме, а не пытаться исправить их самостоятельно. Если вы хотите посмотреть байт-код, генерируемый Dynamo, используйте TORCH_LOGS=bytecode. Общий обзор байт-кода, генерируемого Dynamo, можно посмотреть здесь.

AOTAutograd

Ошибки AOTAutograd обычно трудно отлаживать — мы рекомендуем просто отправить отчёт о проблеме. Вывод журналов AOTAutograd в основном полезен для того, чтобы увидеть входные данные Inductor.

Краткое описание параметров TORCH_LOGS

Список полезных параметров TORCH_LOGS:

Параметр

Описание

+all

Выводить отладочные журналы всех компонентов torch.compile

+dynamo

Выводить отладочные журналы TorchDynamo

+aot

Выводить отладочные журналы AOTAutograd

+inductor

Выводить отладочные журналы TorchInductor

dynamic

Выводить журналы динамических форм

graph_code

Выводить код Python для графа FX, сгенерированного Dynamo

graph_sizes

Выводить размеры тензоров графа FX, сгенерированного Dynamo

trace_bytecode

Выводить инструкции байт-кода, которые трассирует Dynamo, и символический стек интерпретатора, состояние которого отслеживает Dynamo

trace_source

Выводить строку исходного кода, которую Dynamo трассирует в данный момент

bytecode

Выводить байт-код, сгенерированный Dynamo

guards

Выводить сгенерированные проверки

recompiles

Выводить причины повторной компиляции (только первую не пройденную проверку)

recompiles_verbose

Выводить все не пройденные проверки при повторной компиляции

aot_graphs

Выводить граф, сгенерированный AOTAutograd

aot_joint_graphs

Выводить объединённый граф прямого и обратного проходов, сгенерированный AOTAutograd

output_code

Выводить код, сгенерированный Inductor

kernel_code

Выводить код, сгенерированный Inductor отдельно для каждого ядра

schedule

Выводить журналы планирования Inductor

perf_hints

Выводить журналы подсказок по производительности Inductor

fusion

Выводить журналы слияния Inductor

Полный список параметров см. в документации torch._logging и torch._logging.set_logs.

Связанные статьи

  • Руководство по torch.compile
  • Низкоуровневые API torch.compile
  • Часто задаваемые вопросы о torch.compile
  • Обзор пространства имён torch.compiler
  • Справочник по API torch.compiler
  • Профилирование torch.compile
  • Руководство по torch.compile, которого не хватает
  • Руководство по динамическим размерам
  • Руководство по кэшированию TorchInductor

© 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_troubleshooting.html

Spec-Zone.ru

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