已合并
test(fx): add graph_module internal API alignment test cases [v2.12.0] #43640
test(fx): add graph_module internal API alignment test cases [v2.12.0] #43640
已合并
zkx创建于 23 天前
1 个文件变更+231-0
Atest/test_fx_graph_module_npu.py+231-0
@@ -0,0 +1,231 @@
1+# Copyright (c) 2026 Huawei Technologies Co., Ltd
2+# All rights reserved.
3+#
4+# Licensed under the BSD 3-Clause License (the "License");
5+# you may not use this file except in compliance with the License.
6+# You may obtain a copy of the License at
7+#
8+# https://opensource.org/licenses/BSD-3-Clause
9+#
10+# Unless required by applicable law or agreed to in writing, software
11+# distributed under the License is distributed on an "AS IS" BASIS,
12+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+# See the License for the specific language governing permissions and
14+# limitations under the License.
15+ 
16+"""
17+Add validation cases for torch.fx.graph_module APIs on NPU:
18+1. PyTorch community lacks dedicated test cases for internal
19+ graph_module APIs, so this file is added.
20+2. This file validates _exec_with_source, _forward_from_src,
21+ _CodeOnlyModule, _copy_attr, and _WrappedCall.
22+"""
23+ 
24+import torch
25+ 
26+from torch.testing._internal.common_utils import TestCase, run_tests
27+from torch.fx.graph_module import (
28+ _exec_with_source,
29+ _forward_from_src,
30+ _CodeOnlyModule,
31+ _copy_attr,
32+ _WrappedCall,
33+ _loader,
34+)
35+ 
36+ 
37+device_type = acc.type if (acc := torch.accelerator.current_accelerator()) else "cpu"
38+ 
39+ 
40+class TestExecWithSource(TestCase):
41+ """Test _exec_with_source function."""
42+ 
43+ def test_exec_with_source_basic(self):
44+ src = "x = 42"
45+ g = {}
46+ _exec_with_source(src, g)
47+ self.assertEqual(g["x"], 42)
48+ 
49+ def test_exec_with_source_multiple(self):
50+ src = "a = 1\nb = 2\nc = a + b"
51+ g = {}
52+ _exec_with_source(src, g)
53+ self.assertEqual(g["a"], 1)
54+ self.assertEqual(g["b"], 2)
55+ self.assertEqual(g["c"], 3)
56+ 
57+ def test_exec_with_source_invalid_syntax(self):
58+ src = "x = "
59+ g = {}
60+ with self.assertRaises(SyntaxError):
61+ _exec_with_source(src, g)
62+ 
63+ def test_exec_with_source_invalid_globals(self):
64+ src = "x = 42"
65+ with self.assertRaises((AttributeError, TypeError)):
66+ _exec_with_source(src, None)
67+ 
68+ def test_exec_with_source_co_fields(self):
69+ src = "x = 42"
70+ g = {}
71+ co_fields = {"co_filename": "test_mod.py", "co_firstlineno": 10, "co_name": "test_exec"}
72+ _exec_with_source(src, g, co_fields)
73+ self.assertEqual(g["x"], 42)
74+ cache_keys = list(_loader.eval_cache.keys())
75+ self.assertTrue(any("test_mod.py:10 in test_exec" in k for k in cache_keys))
76+ 
77+ 
78+class TestForwardFromSrc(TestCase):
79+ """Test _forward_from_src function."""
80+ 
81+ def test_forward_from_src_basic(self):
82+ src = (
83+ "import torch\n"
84+ "def forward(self, x):\n"
85+ " return x + 1\n"
86+ )
87+ fn = _forward_from_src(src, {})
88+ x = torch.tensor(1.0).to(device_type)
89+ result = fn(None, x)
90+ self.assertEqual(result, torch.tensor(2.0).to(device_type))
91+ 
92+ def test_forward_from_src_with_imports(self):
93+ src = (
94+ "import torch\n"
95+ "def forward(self, x):\n"
96+ " return torch.relu(x)\n"
97+ )
98+ fn = _forward_from_src(src, {})
99+ t = torch.tensor([-1.0, 0.0, 1.0]).to(device_type)
100+ result = fn(None, t)
101+ expected = torch.tensor([0.0, 0.0, 1.0]).to(device_type)
102+ self.assertEqual(result, expected)
103+ 
104+ def test_forward_from_src_missing_forward(self):
105+ src = "x = 1"
106+ with self.assertRaises((KeyError, SyntaxError)):
107+ _forward_from_src(src, {})
108+ 
109+ def test_forward_from_src_co_fields(self):
110+ src = (
111+ "def forward(self, x):\n"
112+ " return x + 1\n"
113+ )
114+ co_fields = {"co_filename": "fwd_mod.py", "co_firstlineno": 5, "co_name": "forward_src"}
115+ fn = _forward_from_src(src, {}, co_fields)
116+ x = torch.tensor(1.0).to(device_type)
117+ result = fn(None, x)
118+ self.assertEqual(result, torch.tensor(2.0).to(device_type))
119+ cache_keys = list(_loader.eval_cache.keys())
120+ self.assertTrue(any("fwd_mod.py:5 in forward_src" in k for k in cache_keys))
121+ 
122+ 
123+class TestCodeOnlyModule(TestCase):
124+ """Test _CodeOnlyModule class."""
125+ 
126+ def test_code_only_module_basic(self):
127+ body = {"a": 1, "b": "test"}
128+ m = _CodeOnlyModule(body)
129+ self.assertEqual(m.a, 1)
130+ self.assertEqual(m.b, "test")
131+ 
132+ def test_code_only_module_empty(self):
133+ m = _CodeOnlyModule({})
134+ self.assertIsInstance(m, torch.nn.Module)
135+ 
136+ 
137+class TestCopyAttr(TestCase):
138+ """Test _copy_attr function."""
139+ 
140+ def test_copy_attr_tensor(self):
141+ src_mod = torch.nn.Module()
142+ dst_mod = torch.nn.Module()
143+ src_mod.register_buffer("weight", torch.ones(3, 4).to(device_type))
144+ _copy_attr(src_mod, dst_mod, "weight")
145+ self.assertTrue(hasattr(dst_mod, "weight"))
146+ self.assertEqual(dst_mod.weight, torch.ones(3, 4).to(device_type))
147+ 
148+ def test_copy_attr_parameter(self):
149+ src_mod = torch.nn.Module()
150+ dst_mod = torch.nn.Module()
151+ src_mod.register_parameter(
152+ "param", torch.nn.Parameter(torch.zeros(2, 2).to(device_type)))
153+ _copy_attr(src_mod, dst_mod, "param")
154+ self.assertTrue(hasattr(dst_mod, "param"))
155+ self.assertEqual(dst_mod.param, torch.zeros(2, 2).to(device_type))
156+ 
157+ def test_copy_attr_nested(self):
158+ src_mod = torch.nn.Module()
159+ dst_mod = torch.nn.Module()
160+ child = torch.nn.Module()
161+ child.register_buffer("buf", torch.ones(2).to(device_type))
162+ src_mod.add_module("child", child)
163+ _copy_attr(src_mod, dst_mod, "child.buf")
164+ self.assertTrue(hasattr(dst_mod.child, "buf"))
165+ self.assertEqual(dst_mod.child.buf, torch.ones(2).to(device_type))
166+ 
167+ def test_copy_attr_npu_tensor(self):
168+ src_mod = torch.nn.Module()
169+ dst_mod = torch.nn.Module()
170+ src_mod.register_buffer("npu_buf", torch.ones(3).to(device_type))
171+ _copy_attr(src_mod, dst_mod, "npu_buf")
172+ self.assertTrue(hasattr(dst_mod, "npu_buf"))
173+ self.assertEqual(dst_mod.npu_buf.device.type, device_type)
174+ 
175+ def test_copy_attr_missing_attribute(self):
176+ src_mod = torch.nn.Module()
177+ dst_mod = torch.nn.Module()
178+ with self.assertRaises(AttributeError):
179+ _copy_attr(src_mod, dst_mod, "nonexistent")
180+ 
181+ def test_copy_attr_existing_parent(self):
182+ src_mod = torch.nn.Module()
183+ child = torch.nn.Module()
184+ child.register_buffer("buf", torch.ones(2).to(device_type))
185+ src_mod.add_module("child", child)
186+ dst_mod = torch.nn.Module()
187+ dst_mod.add_module("child", torch.nn.Module())
188+ _copy_attr(src_mod, dst_mod, "child.buf")
189+ self.assertTrue(hasattr(dst_mod.child, "buf"))
190+ self.assertEqual(dst_mod.child.buf, torch.ones(2).to(device_type))
191+ 
192+ 
193+class TestWrappedCall(TestCase):
194+ """Test _WrappedCall class."""
195+ 
196+ def test_wrapped_call_basic(self):
197+ class SimpleMod(torch.nn.Module):
198+ def forward(self, x):
199+ return x * 2
200+ 
201+ mod = SimpleMod()
202+ wrapped = _WrappedCall(SimpleMod, None)
203+ t = torch.tensor(3.0).to(device_type)
204+ result = wrapped(mod, t)
205+ self.assertEqual(result, torch.tensor(6.0).to(device_type))
206+ 
207+ def test_wrapped_call_with_cls_call(self):
208+ class SimpleMod(torch.nn.Module):
209+ def forward(self, x):
210+ return x + 1
211+ 
212+ mod = SimpleMod()
213+ wrapped = _WrappedCall(SimpleMod, SimpleMod.forward)
214+ t = torch.tensor(5.0).to(device_type)
215+ result = wrapped(mod, t)
216+ self.assertEqual(result, torch.tensor(6.0).to(device_type))
217+ 
218+ def test_wrapped_call_error_path(self):
219+ class BadMod(torch.nn.Module):
220+ def forward(self, x):
221+ return x.undefined_attr
222+ 
223+ mod = BadMod()
224+ wrapped = _WrappedCall(BadMod, None)
225+ t = torch.tensor(1.0).to(device_type)
226+ with self.assertRaises(AttributeError):
227+ wrapped(mod, t)
228+ 
229+ 
230+if __name__ == "__main__":
231+ run_tests()