Используйте fullgraph=True для выявления и устранения разрывов графа
Создано: 28 июля 2025 г. | Последнее обновление: 31 марта 2026 г.
Использование torch.compile(fullgraph=False) (значение по умолчанию) — хороший способ начать работу с torch.compile: благодаря возможности прерывать граф, он изначально поддерживает все программы на Python и обеспечивает высокую производительность в распространённых случаях.
Однако если вы хотите повысить производительность своей модели, следует явно определить, какие участки кода нужно компилировать:
- Рекомендуем использовать
torch.compile(fullgraph=True)для поиска и устранения разрывов графа в коде. - Если вы разрабатываете библиотеку (или проверяете, «работает» ли ваш код с
torch.compile), рекомендуем проводить тестирование с помощьюtorch.compile(fullgraph=True).
torch.compile(fullgraph=True) обеспечивает более строгие гарантии, чем fullgraph=False: мы всегда захватываем один граф FX для компиляции (или выдаём ошибку, если это невозможно из-за разрыва графа). В частности, необходимо устранить каждый обнаруженный разрыв графа.
Существует несколько способов устранить разрыв графа.
Стратегия 1: перепишите неподдерживаемый код, используя возможности, поддерживаемые Dynamo
Во многих сообщениях об ошибках, связанных с разрывом графа, приводятся рекомендации по переписыванию кода, чтобы избежать разрыва. Если устранить разрыв графа по-прежнему сложно, перейдите к следующей стратегии или создайте задачу в репозитории PyTorch на GitHub.
Дополнительные примеры разрывов графа и способы их устранения приведены в разделе Распространённые разрывы графа.
Пример: Dynamo не поддерживает вызов next для объекта list_iterator, который был входным аргументом функции, проходящей компиляцию.
@torch.compile(fullgraph=True)
def f(xs):
a = next(xs)
b = next(xs)
return a + b
xs = [torch.tensor(1.), torch.tensor(2.)]
try:
out = f(iter(xs))
except Exception as e:
print(e)
Вместо этого перепишите компилируемую функцию так, чтобы она принимала список.
@torch.compile(fullgraph=True)
def f_rewritten(xs):
it = iter(xs)
a = next(it)
b = next(it)
return a + b
f_rewritten(xs)
Стратегия 2: чистые функции всегда можно компилировать с помощью обходного механизма.
Краткое описание: пространство всех функций Python огромно, поэтому Dynamo нецелесообразно трассировать каждую функцию Python без разрывов графа. Для функций Python, которые считаются «чистыми» и которые Dynamo не может трассировать без разрывов графа, предусмотрены обходные механизмы, позволяющие попытаться трассировать такие функции:
- Используйте
custom_opилиtriton_opдля чистых ядер Triton. - Используйте
nonstrict_traceдля чистых функций, использующих только операции с тензорами PyTorch. - Используйте
custom_opдля всех остальных чистых функций.
«Чистая функция» обладает следующими свойствами:
- Детерминированность. При одинаковых входных данных чистая функция всегда возвращает одинаковый результат.
- Отсутствие внешних побочных эффектов. Чистая функция не имеет побочных эффектов, видимых извне, таких как изменение внешнего состояния или выполнение операций ввода-вывода. Допускаются побочные эффекты, остающиеся внутри функции (например, изменение промежуточных тензоров). Одно примечательное исключение: обычно допускаются операции
torch.*, изменяющие входные тензоры функции. - Явные входные и выходные данные. Все входные данные должны передаваться через параметры функции, а все результаты должны возвращаться функцией.
Примеры см. в разделе Чистые функции.
Теоретически Dynamo способен обрабатывать широкий спектр нечистых функций, но для некоторых возможностей языка Python поддержка может быть недостаточной. Однако чистые функции всегда можно компилировать с помощью обходного механизма.
Если у вас возник разрыв графа, возможно, код вокруг него удастся переработать в чистую функцию и использовать обходной механизм, позволяющий обойти трассировку Dynamo:
- Используйте
torch._dynamo.nonstrict_trace, если хотите, чтобы операции с тензорами в функции отображались в выходном графе Dynamo (и, следовательно, могли оптимизироваться).nonstrict_traceуказывает Dynamo использовать нестрогую трассировку. - Используйте пользовательские операторы, если хотите, чтобы функция оставалась непрозрачной для
torch.compile(как для Dynamo во фронтенде, так и для бэкенда).
Обратите внимание: ничто не мешает применять эти обходные механизмы к нечистым функциям, но мы не предоставляем никаких гарантий корректности.
Пример: если Dynamo не поддерживает некоторую возможность или API Python, допускающие нестрогую трассировку (например, использующие операции PyTorch), используйте torch._dynamo.nonstrict_trace для их захвата.
# this is a function that Dynamo doesn't support (due to the graph_break() call).
def g(x):
y = x.sin()
torch._dynamo.graph_break()
z = y.sin()
return z
@torch.compile(fullgraph=True)
def f(x):
w = x.sin()
return g(w)
x = torch.randn(3)
try:
f(x) # Graph Break: there was a call to torch._dynamo.graph_break()
except Exception as e:
print(e)
@torch.compile(fullgraph=True)
def f_rewritten(x):
w = x.sin()
return torch._dynamo.nonstrict_trace(g)(w)
f_rewritten(x) # works
Пример: используйте пользовательские операторы, чтобы создавать функции, непрозрачные для torch.compile
from torch.utils.cpp_extension import load_inline
# C++ source code for the square operation
cpp_source = """
torch::Tensor square_cpu(torch::Tensor input) {
// Check that input is a CPU tensor
TORCH_CHECK(input.device().is_cpu(), "Input must be a CPU tensor");
// Create output tensor with same shape and dtype as input
torch::Tensor output = torch::empty_like(input);
// Get data pointers
float* input_data = input.data_ptr<float>();
float* output_data = output.data_ptr<float>();
// Get total number of elements
int64_t numel = input.numel();
// For loop to compute square of each element
for (int64_t i = 0; i < numel; i++) {
output_data[i] = input_data[i] * input_data[i];
}
return output;
}
"""
# Load the extension inline
square_module = load_inline(
name="square_cpu_kernel",
cpp_sources=cpp_source,
functions=["square_cpu"],
verbose=True
)
def square(x):
return square_module.square_cpu(x)
@torch.compile(fullgraph=True)
def f(x):
return square(x)
try:
f(torch.randn(3, 3)) # graph break
except Exception as e:
print(e)
# Use torch.library.custom_op to define a new custom operator.
# Custom operators are opaque with respect to torch.compile:
# that is, torch.compile does not peek into them.
@torch.library.custom_op("mylib::square", mutates_args=())
def square(x: torch.Tensor) -> torch.Tensor:
return square_module.square_cpu(x)
# Use register_fake to add a ``FakeTensor`` kernel for the operator
@square.register_fake
def _(x):
return x.new_empty(x.size())
print(f(torch.randn(3, 3))) # no graph break
Дополнительные сведения о triton_op для пользовательских ядер Triton см. в руководстве по пользовательским ядрам Triton.
Стратегия 3: не компилируйте код
Не весь код подходит для компиляции. torch.compile — это компилятор вычислений с тензорами; он не сможет оптимизировать такие операции, как ввод-вывод с диска. Попробуйте переработать код так, чтобы неподдерживаемый код не вызывался в компилируемой области.
@torch.compile(fullgraph=True)
def f(x):
y = x ** 2 / 2
torch.save(y, "foo.pt")
z = y ** 3 / 6
return z
x = torch.randn(3)
try:
f(x) # Graph Break: torch.save not supported
except Exception as e:
print(e)
def f_rewritten(x):
y = g(x)
torch.save(y, "foo.pt")
z = h(y)
return z
@torch.compile(fullgraph=True)
def g(x):
y = x ** 2 / 2
return y
@torch.compile(fullgraph=True)
def h(y):
z = y ** 3 / 6
return z
f_rewritten(x)
Если проблемную функцию не нужно выполнять во время компиляции, рассмотрите возможность использовать torch.compiler.is_compiling(), чтобы пропустить её.
@torch.compile(fullgraph=True)
def f(x):
y = x ** 2 / 2
if not torch.compiler.is_compiling():
torch.save(y, "foo.pt")
z = y ** 3 / 6
return z
x = torch.randn(3)
f(x) # torch.save is not called
Если функция вызывается во многих местах и вы не возражаете против того, чтобы torch.compile всегда пропускал её, добавьте её в torch._dynamo.config.ignore_logging_functions.
def bad_fn(y):
torch.save(y, "foo.pt")
torch._dynamo.config.ignore_logging_functions.add(bad_fn)
@torch.compile(fullgraph=True)
def f(x):
y = x ** 2 / 2
bad_fn()
z = y ** 3 / 6
return z
x = torch.randn(3)
f(x) # torch.save is not called
Обратите внимание, что существуют ограничения на типы функций, которые можно добавлять в ignore_logging_functions. В частности:
- Функция может принимать любые аргументы, но ДОЛЖНА возвращать
None. - Функции должны быть функциями на уровне модуля,
logging.Logger.<method>(игнорирует этот метод для всех экземпляровlogging.Logger) илиlogger_obj.<method>(игнорирует этот метод только для конкретного экземпляраlogger_obj).
Другие функции могут игнорироваться или не игнорироваться в зависимости от деталей реализации. Если вы хотите игнорировать функцию, но ignore_logging_functions не удаётся её игнорировать, создайте задачу.
© 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/compile/programming_model.fullgraph_true.html