已合并
patch: fixed pickle serialization for precompile cache #40165
Jingming Liu创建于 7月6日
patch: fixed pickle serialization for precompile cache #40165
已合并
Jingming Liu创建于 7月6日
3 个文件变更+120-0
@@ -588,5 +588,47 @@ class TestTorchNpuBootstrap(TestCase):
588 """588 """
589 )589 )
590 590 
591+ def test_13_patch_dataclass_before_dynamo_source_import(self):
592+ self._run_python(
593+ """
594+ import torch
595+ import torch_npu
596+ 
597+ patched_decorator = torch._guards.dataclass_with_cached_hash
598+ assert patched_decorator.__module__ == "torch_npu.utils._guards"
599+ 
600+ from torch._dynamo import source as source_module
601+ from torch._dynamo.source import DefaultsSource, LocalSource
602+ 
603+ assert source_module.dataclass_with_cached_hash is patched_decorator
604+ 
605+ source = DefaultsSource(LocalSource("fn"), 0)
606+ reduce_callable, reduce_args = source.__reduce__()
607+ assert reduce_callable is DefaultsSource
608+ assert reduce_args == (LocalSource("fn"), 0, False)
609+ """
610+ )
611+ 
612+ def test_14_patch_dataclass_after_dynamo_source_import(self):
613+ self._run_python(
614+ """
615+ import torch
616+ from torch._dynamo import source as source_module
617+ from torch._dynamo.source import DefaultsSource, LocalSource
618+ 
619+ source = DefaultsSource(LocalSource("fn"), 0)
620+ assert len(source.__reduce__()[1]) == 5
621+ 
622+ import torch_npu
623+ 
624+ patched_decorator = torch._guards.dataclass_with_cached_hash
625+ assert source_module.dataclass_with_cached_hash is patched_decorator
626+ 
627+ reduce_callable, reduce_args = source.__reduce__()
628+ assert reduce_callable is DefaultsSource
629+ assert reduce_args == (LocalSource("fn"), 0, False)
630+ """
631+ )
632+ 
591if __name__ == "__main__":633if __name__ == "__main__":
592 run_tests()634 run_tests()
@@ -13,3 +13,11 @@ def apply_npugraph_tree_patch():
13 from torch_npu.utils._graph_tree import _apply_npugraph_tree_methods13 from torch_npu.utils._graph_tree import _apply_npugraph_tree_methods
14 14 
15 _apply_npugraph_tree_methods()15 _apply_npugraph_tree_methods()
16+ 
17+ 
18+# Source subclasses bind and execute this decorator while torch._dynamo.source is imported.
19+@PatchManager.register_patch("dynamo")
20+def apply_dataclass_with_cached_hash_patch():
21+ from torch_npu.utils._guards import patch_dataclass_with_cached_hash
22+ 
23+ patch_dataclass_with_cached_hash()
atomgit-bot
atomgit-botatomgit-bot7月6日

🟡 Medium Priority

dynamo_patches.py 中,新增的第 20 行定义了与第 12 行同名的函数 apply_npugraph_tree_patch。Python 中后定义的同名函数会覆盖先前的定义,导致原有的 graph tree patch 函数(第 12-15 行)在模块命名空间中不可访问。

虽然两个函数都通过 @PatchManager.register_patch("dynamo") 装饰器在定义时注册到了 _patch_groups["dynamo"] 列表中(_add_patch 使用身份比较,两个不同函数对象都会被添加),两者在补丁执行时都会运行,但存在以下风险:

  1. 新函数名为 apply_npugraph_tree_patch,但其实际功能是 patch dataclass_with_cached_hash 用于 pickle 序列化修复,与 "npugraph tree" 完全无关——名称极具误导性。
  2. 原有 graph tree patch 函数无法再通过 from torch_npu._init.patches.dynamo_patches import apply_npugraph_tree_patch 导入,增加了维护风险。

建议:将新增的补丁函数重命名为能准确描述其功能的名称,例如 apply_dataclass_pickle_patchapply_guards_patch。同时建议将注释中的 "npugraph tree" 相关内容更新为对 guards patch 的描述。

likedislike
不准确?
Jingming Liu
Jingming Liu
7月7日 评论:
@@ -0,0 +1,70 @@
1+import dataclasses
2+import sys
3+from typing import Any, Callable, overload, TypeVar
4+ 
5+import torch
6+ 
7+ 
8+T = TypeVar("T")
9+ 
10+ 
11+def _reduce_without_cached_hash(self):
12+ fields = dataclasses.fields(self)
13+ field_values = tuple(getattr(self, field.name) for field in fields if field.init)
14+ return (self.__class__, field_values)
15+ 
16+ 
17+def _patch_existing_source_classes():
18+ source_class = torch._guards.Source
19+ pending = [source_class]
20+ while pending:
21+ cls = pending.pop()
22+ reduce_method = cls.__dict__.get("__reduce__")
23+ if (
24+ reduce_method is not None
25+ and reduce_method.__module__ == "torch._guards"
26+ ):
27+ cls.__reduce__ = _reduce_without_cached_hash
28+ pending.extend(cls.__subclasses__())
29+ 
30+ # torch._dynamo.source binds the decorator during import. Keep that binding
31+ # consistent when the module was imported before torch_npu.
32+ source_module = sys.modules.get("torch._dynamo.source")
33+ if source_module is not None:
34+ source_module.dataclass_with_cached_hash = (
35+ torch._guards.dataclass_with_cached_hash
36+ )
37+ 
38+ 
39+def patch_dataclass_with_cached_hash():
40+ @overload
41+ def dataclass_with_cached_hash(cls: type[T], **kwargs: Any) -> type[T]: ...
42+ 
43+ @overload
44+ def dataclass_with_cached_hash(
45+ cls: None = None, **kwargs: Any
46+ ) -> Callable[[type[T]], type[T]]: ...
47+ 
48+ def dataclass_with_cached_hash(
49+ cls: type[T] | None = None, **kwargs: Any
50+ ) -> type[T] | Callable[[type[T]], type[T]]:
51+ def wrap(cls_inner: type[T]) -> type[T]:
52+ new_cls = dataclasses.dataclass(cls_inner, **kwargs)
53+ old_hash = cls_inner.__hash__
54+ 
55+ def __hash__(self) -> int:
56+ if not hasattr(self, "_hash"):
57+ object.__setattr__(self, "_hash", old_hash(self))
58+ return self._hash
59+ 
60+ new_cls.__hash__ = __hash__
61+ new_cls.__reduce__ = _reduce_without_cached_hash
62+ return new_cls # type: ignore[return-value]
63+ 
64+ if cls is None:
65+ return wrap
66+ 
67+ return wrap(cls)
68+ 
69+ torch._guards.dataclass_with_cached_hash = dataclass_with_cached_hash
70+ _patch_existing_source_classes()