Summary
Adds ObjectSpec and extends lookup_spec_from_dynamo_source so the spec system can address tensors reached via Python attribute access (obj.weight, nn.Module params/buffers, self.x inside an instance method).
import torch
from torch.fx.experimental.dynamic_spec import (
ObjectSpec, ParamsSpec, ShapesSpec, ShapeVar, TensorSpec,
)
class Model(torch.nn.Module):
def __init__(self):
super().__init__()
self.weight = torch.nn.Parameter(torch.randn(4, 3))
def forward(self):
return self.weight + 1
torch.compile(
Model(),
shapes_spec=ShapesSpec(
params=ParamsSpec(
{"self": ObjectSpec({"weight": TensorSpec([ShapeVar("h"), None])})}
)
),
)What changed
torch/fx/experimental/dynamic_spec.py
ObjectSpec— attribute-keyed container.
- Constructor:
ObjectSpec({name: IntermediateSpec, ...}). Values may be leaves (TensorSpec/IntVar/int/None) or anotherObjectSpecfor recursion. - Implements
__getitem__/__contains__/__iter__/__len__/items()for dict-like access. __repr__matches the module's existing style (object_spec:\n .name: ...); nested children indent recursively.to_jsonable()mirrors the pattern used by other spec types — recurses into spec children, passes raw leaves through.
- Constructor:
IntermediateSpectype alias updated toLeafSpec | ObjectSpecso containers can hold mixed leaf and nested-spec values.
torch/_dynamo/variables/builder.py
lookup_spec_from_dynamo_source— extended to walkSourcechains:
LocalSource(name, is_input=True)— the root of any walk.AttrSource(base, member)— descendsObjectSpec._fields[member].NNModuleSource(and subclasses) — transparently unwrapped (guard-semantics marker, not an access step).DictGetItemSource(UnspecializedParamBufferSource(_, '_parameters' | '_buffers'), key)— dynamo internally rewritesself.weightasself._parameters["weight"]; the walk collapses that pair into a single("attr", key)step so the user-facing attribute name matches the spec.- Other source kinds return
None— later container PRs (DictSpec / ListSpec) extend this dispatch.
wrap_module— the pre-marking loop that callsmark_static_inputon eachnamed_parameter/named_buffernow skips entries with a spec. Without this, the static stamp would be applied beforewrap_tensor's spec-aware bypasses get a chance to fire, and the Parameter would still be lifted as a graph attribute.
Combined effect: nn.Parameter attributes participate in spec-driven dynamism end-to-end.
Test plan — python test/dynamo/test_dynamic_spec.py
TestObjectSpec(data class) — empty / dict-construction / iter+items / recursive nesting / repr (none-leaf / nested-tensor / nested-objectspec) /to_jsonable.TestObjectSpecLookup(lookup walk, ordered simplest → most complex):
test_local_source_root_returns_top_level_spec— bareLocalSourcereturns theObjectSpecitself.test_attr_descends_into_objectspec—AttrSource(LocalSource(...), "weight").test_nested_objectspec_walk— three-levelmodel.inner.weight.test_missing_attr_returns_none— attr not present inObjectSpec.test_attr_against_non_objectspec_returns_none— type mismatch (top-level isTensorSpec, source asks for an attr).test_nn_module_source_is_unwrapped—AttrSource(NNModuleSource(LocalSource(...)), "weight").
TestObjectSpecCompile(e2e):
test_attr_tensor_dim_dynamic— plain Python container,obj.w + 1, assertsSymIntat dim 0 and no recompile across dim-0 changes.test_nn_module_parameter_dim_dynamic— realistic case:nn.Parameterreached viaself.weight. ExercisesNNModuleSourceunwrap,UnspecializedParamBufferSourcecollapse, module-wrapping pre-mark skip, andwrap_tensorspec-aware bypasses.
Pull Request resolved: #182764
Approved by: https://github.com/laithsakka
SOCIAL SHARE CARD GENERATOR