Short name describing what triggered the graph break
Unsupported hasattr call
Values or code snippet captured at the break point
call_obj_hasattr {self} {name}
Explanation of why the graph break was triggered
Dynamo does not know how to trace the function {self.debug_repr()}
Hints on how to resolve the graph break
hasattr({self.__class__.__name__}, {name}) in your code.This graph break happens when you call hasattr(obj, name) on a value whose
type Dynamo cannot inspect for attributes. This commonly happens with lazy
builtin iterators (map, zip, filter), slice, super() objects, and
similar types. A frequent source is duck-typing helpers that branch on
hasattr(...) and get handed one of these objects.
Example code that causes the graph break is:
import torch
def collate(values):
# Duck-typing: branch on whether we can cheaply get a length.
if hasattr(values, "__len__"):
return torch.zeros(len(values))
return torch.zeros(1)
@torch.compile(fullgraph=True)
def fn(x):
# `map(...)` is a lazy iterator that Dynamo cannot inspect, so the
# `hasattr` call inside `collate` breaks the graph.
doubled = map(lambda v: v * 2, [1.0, 2.0, 3.0])
return x + collate(doubled).sum()
fn(torch.randn(3))
The cleanest workaround is to convert the lazy iterator into a type Dynamo
understands (e.g. a list) before calling hasattr on it. hasattr is
supported on list, tuple, dict, tensors, modules, and user-defined
objects:
import torch
def collate(values):
if hasattr(values, "__len__"):
return torch.zeros(len(values))
return torch.zeros(1)
@torch.compile(fullgraph=True)
def fn(x):
# A list supports `hasattr`/`len`, so `torch.compile` handles it without a break.
doubled = list(map(lambda v: v * 2, [1.0, 2.0, 3.0]))
return x + collate(doubled).sum()
fn(torch.randn(3))
Alternatively, if the hasattr check is only deciding control flow that is
fixed each time the function runs, compute it outside the compiled region and
pass the result in, so Dynamo never has to inspect the unsupported object.