Repository navigation
perf(tree): avoid id() and dict identity comparisons in torchtree_impl for torch.compile - #23647
Conversation
There was a problem hiding this comment.
Code Review
This pull request refactors torchtree_impl.py to make traverse compatible with torch.compile(..., fullgraph=True). It replaces id() comparisons in _traverse with a stateful closure _make_root_is_leaf to prevent graph breaks in PyTorch Dynamo, and optimizes _dict_to_ordered_dict to avoid rebuilding dictionaries if they are already ordered. A corresponding unit test is added to verify full-graph compilation. The feedback suggests merging the duplicate sorting and traversal logic for dict and defaultdict to reduce code duplication and improve maintainability.
8c1e901 to
7714c89
Compare
โฆl for torch.compile
7714c89 to
f33cb85
Compare
Codecov Reportโ
All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #23647 +/- ##
==========================================
- Coverage 86.95% 84.67% -2.28%
==========================================
Files 492 492
Lines 71781 78225 +6444
Branches 11838 13595 +1757
==========================================
+ Hits 62416 66236 +3820
- Misses 6243 8897 +2654
+ Partials 3122 3092 -30
Flags with carried forward coverage won't be shown. Click here to find out more. โ View full report in Codecov by Harness. ๐ New features to boost your workflow:
|
Description
Fixes #23648
Split out of #23201 at @hertschuh's request. On that PR the
torch.compilehunk was reviewedalongside the dict-ordering fix, and the ask was:
#23201 (the nested dict key-order fix, #23281) has since merged without it. This PR is that
carve-out, rebased on current master. No behavior from #23201 is changed here.
The problem
keras/src/tree/torchtree_impl.pyhas two identity checks that Dynamo refuses to trace, sokeras.tree.traverseโ and anything else routed through_dict_to_ordered_dict(
flatten_with_path,pack_sequence_as,map_shape_structure,lists_to_tuples) โ alwaysgraph-breaks:
_traverseflattens one level withis_leaf=lambda x: id(x) != structure_id. Dynamo modelsid()as a compile-time-only value and breaks on the comparison._dict_to_ordered_dictdecides whether to rebuild a node withif ordered_child is not child:,which on dicts raises
Unsupported: ... builtin `is_not` with argument types ['dict', 'dict'].Neither is data-dependent, so the break fires on every traced call over a structure containing a
dict or nested container.
The fix
Both checks only answer "is this the root?" and "did anything below me change?", which do not
need identity comparisons:
_traversenow walks one level throughtorch_tree.SUPPORTED_NODES[...]'sflatten_fn/unflatten_fndirectly. This is the pattern_tree_is_leafand_dict_to_ordered_dictalready use in this same module, and it removes thetree_flattenround trip along with the
TreeSpecthat was built and discarded immediately._dict_to_ordered_dict_with_changed, which returns anexplicit
(ordered_structure, changed)pair._dict_to_ordered_dictstays as the thinpublic-facing wrapper, so its callers are untouched.
Note on (1): an earlier revision of this patch kept
tree_flattenand passed a statefulis_leafclosure that returned
Falsefor the first node it saw andTrueafterwards. That works, butonly because
tree_flattenhappens to callis_leafexactly once per node in pre-order with theroot first โ an undocumented implementation detail, and one that would silently produce a
full flatten rather than an error if it ever changed. Going through the node type directly has
no such dependency.
Benchmarks
torch 2.11.0.torch._dynamo.explainovertree.traverseon[tensor, {"b": tensor, "a": tensor}]:fullgraph=TrueUnsupportedDropping the
tree_flatten/tree_unflattenround trip also helps eager, median of 10k calls over[{"b": 1, "a": [2, 3]}, (4, 5), 6]:tree.traverseTests
test_traverse_torch_compile_fullgraphinkeras/src/tree/tree_test.pycompiles atree.traverseover a nested list/dict with
fullgraph=Trueand checks both the values and the canonical keyorder. It raises
torch._dynamo.exc.Unsupportedon master and passes here. It is skipped for thenon-torch tree implementations.
The rest of
tree_test.pyis unchanged and passes (29 passed, 21 skipped), which is what pins theequivalence of the new one-level walk. I also diffed the old and new
_traverseacross 112combinations (14 structure shapes โ leaf,
None, empty containers,OrderedDict,defaultdict,deque, namedtuple, nested dicts โ x 4 callbacks xtop_downboth ways) with identical results.Contributor Agreement
Fixes #xxxabove).