已合并
[UT] adapt 4 official testcase files for dynamo module(test_optimizers.py test_hooks.py test_debug_utils.py test_bytecode_hook.py) #8820
AtomGit-Bot创建于 2024年1月8日
[UT] adapt 4 official testcase files for dynamo module(test_optimizers.py test_hooks.py test_debug_utils.py test_bytecode_hook.py) #8820
已合并
AtomGit-Bot创建于 2024年1月8日
4 个文件变更+898-0
@@ -0,0 +1,29 @@
1+# Owner(s): ["module: dynamo"]
2+ 
3+import torch
4+import torch_npu
5+import torch._dynamo.test_case
M
Mmisty-rain-typhoid2024年1月8日

import库的顺序换一下

likedislike
6+ 
7+ 
8+class BytecodeHookTests(torch._dynamo.test_case.TestCase):
9+ def test_bytecode_hook(self):
10+ def fn(a, b):
11+ return a - b * 10
12+ 
13+ def hook(code, out_code):
14+ return code
M
Mmisty-rain-typhoid2024年1月8日

这个print是为什么存在?

likedislike
15+ 
16+ torch._dynamo.reset()
17+ handle = torch._dynamo.convert_frame.register_bytecode_hook(hook)
18+ try:
19+ opt_fn = torch.compile(fn)
20+ for i in range(2, 12):
21+ opt_fn(torch.randn(i), torch.randn(i))
22+ finally:
23+ handle.remove()
24+ 
25+ 
26+if __name__ == "__main__":
27+ from torch._dynamo.test_case import run_tests
28+ 
29+ run_tests()
@@ -0,0 +1,57 @@
1+# Owner(s): ["module: dynamo"]
2+ 
3+import torch
4+import torch_npu
5+from functorch import make_fx
6+from torch._dynamo import debug_utils
7+from torch._dynamo.test_case import TestCase
8+ 
9+ 
10+class TestDebugUtils(TestCase):
11+ def test_cast_model_to_fp64_dtype_args(self):
12+ # Test that dtype arguments are converted to fp64
13+ 
14+ def fn(x):
15+ return (
16+ torch.ops.prims.convert_element_type(x, torch.float16),
17+ x.to(torch.float16),
18+ torch.full(x.shape, 2, dtype=torch.float32, device=x.device),
19+ x.new_empty(x.shape),
20+ )
21+ 
22+ x = torch.randn(32, device="cpu")
23+ decomps = torch._decomp.core_aten_decompositions()
24+ fx = make_fx(fn, decomposition_table=decomps)(x)
25+ 
26+ self.assertExpectedInline(
27+ fx.code.lstrip(),
28+ """\
29+def forward(self, x_1):
30+ convert_element_type = torch.ops.prims.convert_element_type.default(x_1, torch.float16)
31+ _to_copy = torch.ops.aten._to_copy.default(x_1, dtype = torch.float16); x_1 = None
32+ full = torch.ops.aten.full.default([32], 2, dtype = torch.float32, device = device(type='cpu'), pin_memory = False)
33+ empty = torch.ops.aten.empty.memory_format([32], dtype = torch.float32, layout = torch.strided, device = device(type='cpu'), pin_memory = False)
34+ return (convert_element_type, _to_copy, full, empty)
35+ """, # NOQA: B950
36+ )
37+ 
38+ fp64_model, fp64_examples = debug_utils.cast_to_fp64(fx, (x,))
39+ self.assertEqual(fp64_examples, (x.to(torch.float64),))
40+ 
41+ self.assertExpectedInline(
42+ fx.code.lstrip(),
43+ """\
44+def forward(self, x_1):
45+ convert_element_type = torch.ops.prims.convert_element_type.default(x_1, torch.float64)
46+ _to_copy = torch.ops.aten._to_copy.default(x_1, dtype = torch.float64); x_1 = None
47+ full = torch.ops.aten.full.default([32], 2, dtype = torch.float64, device = device(type='cpu'), pin_memory = False)
48+ empty = torch.ops.aten.empty.memory_format([32], dtype = torch.float64, layout = torch.strided, device = device(type='cpu'), pin_memory = False)
49+ return (convert_element_type, _to_copy, full, empty)
50+ """, # NOQA: B950
51+ )
52+ 
53+ 
54+if __name__ == "__main__":
55+ from torch._dynamo.test_case import run_tests
56+ 
57+ run_tests()
@@ -0,0 +1,612 @@
1+# Owner(s): ["module: dynamo"]
2+ 
3+import contextlib
4+import functools
5+ 
6+import torch
7+import torch_npu
8+import torch._dynamo
9+import torch._dynamo.test_case
10+import torch._dynamo.testing
11+from functorch.compile import nop
12+from torch._dynamo import compiled_autograd
13+from torch._functorch.aot_autograd import aot_module_simplified
14+ 
15+ 
16+def compiler_fn(gm):
17+ return torch._dynamo.optimize("inductor", nopython=True, dynamic=True)(gm)
18+ 
19+ 
20+def global_hook_0(grad):
21+ return grad * 4
22+ 
23+ 
24+def global_hook_1(grad):
25+ return grad / 2
26+ 
27+ 
28+def global_hook_2(grad):
29+ return grad * 3
30+ 
31+ 
32+h0 = None
33+ 
34+ 
35+class HooksTests(torch._dynamo.test_case.TestCase):
36+ def test_tensor_only_register_hook_in_graph_lambda(self):
37+ def fn(x):
38+ x.register_hook(lambda grad: grad * 2)
39+ return x
40+ 
41+ cnts = torch._dynamo.testing.CompileCounter()
42+ fn = torch._dynamo.optimize(cnts)(fn)
43+ v = torch.tensor([0.0, 0.0, 0.0], requires_grad=True)
44+ v = fn(v)
45+ v.backward(torch.tensor([1.0, 2.0, 3.0]))
46+ self.assertEqual(v.grad, torch.tensor([2.0, 4.0, 6.0]))
47+ self.assertEqual(cnts.frame_count, 0)
48+ 
49+ def test_tensor_register_hook_in_graph_lambda(self):
50+ def fn(x, y, z):
51+ x.register_hook(lambda grad: grad * 2)
52+ return x, y * y, z * z
53+ 
54+ cnts = torch._dynamo.testing.CompileCounter()
55+ fn = torch._dynamo.optimize(cnts)(fn)
56+ v = torch.tensor([0.0, 0.0, 0.0], requires_grad=True)
57+ v = fn(v, torch.randn([2, 2]), torch.randn([2, 2]))[0]
58+ v.backward(torch.tensor([1.0, 2.0, 3.0]))
59+ self.assertEqual(v.grad, torch.tensor([2.0, 4.0, 6.0]))
60+ self.assertEqual(cnts.frame_count, 1)
61+ 
62+ def test_tensor_register_hook_in_graph_break_handle_lambda(self):
63+ def fn(x, y, z):
64+ handle = x.register_hook(lambda grad: grad * 2)
65+ z = z * z
66+ handle.remove()
67+ x.register_hook(lambda grad: grad * 3)
68+ return x, y * y, z
69+ 
70+ cnts = torch._dynamo.testing.CompileCounter()
71+ fn = torch._dynamo.optimize(cnts)(fn)
72+ v = torch.tensor([0.0, 0.0, 0.0], requires_grad=True)
73+ v = fn(v, torch.randn([2, 2]), torch.randn([2, 2]))[0]
74+ v.backward(torch.tensor([1.0, 2.0, 3.0]))
75+ self.assertEqual(v.grad, torch.tensor([3.0, 6.0, 9.0]))
76+ self.assertEqual(cnts.frame_count, 2)
77+ 
78+ def test_tensor_register_hook_multi_handle_return(self):
79+ def fn(x, y, z):
80+ handle = x.register_hook(lambda grad: grad * 2)
81+ h2 = handle
82+ z = z * z
83+ return x, y * y, z, handle, h2
84+ 
85+ cnts = torch._dynamo.testing.CompileCounter()
86+ fn = torch._dynamo.optimize(cnts)(fn)
87+ v = torch.tensor([0.0, 0.0, 0.0], requires_grad=True)
88+ v, y, z, h, h2 = fn(v, torch.randn([2, 2]), torch.randn([2, 2]))
89+ v.backward(torch.tensor([1.0, 2.0, 3.0]))
90+ self.assertEqual(v.grad, torch.tensor([2.0, 4.0, 6.0]))
91+ self.assertEqual(cnts.frame_count, 1)
92+ self.assertNotEqual(h, None)
93+ self.assertNotEqual(h2, None)
94+ self.assertEqual(h2, h)
95+ 
96+ def test_tensor_register_hook_repeated_handle_return(self):
97+ def fn(x, y, z):
98+ handle = x.register_hook(lambda grad: grad * 2)
99+ h2 = handle
100+ z = z * z
101+ return x, y * y, z, handle, handle
102+ 
103+ cnts = torch._dynamo.testing.CompileCounter()
104+ fn = torch._dynamo.optimize(cnts)(fn)
105+ v = torch.tensor([0.0, 0.0, 0.0], requires_grad=True)
106+ v, y, z, h, h2 = fn(v, torch.randn([2, 2]), torch.randn([2, 2]))
107+ v.backward(torch.tensor([1.0, 2.0, 3.0]))
108+ self.assertEqual(v.grad, torch.tensor([2.0, 4.0, 6.0]))
109+ self.assertEqual(cnts.frame_count, 1)
110+ self.assertNotEqual(h, None)
111+ self.assertNotEqual(h2, None)
112+ self.assertEqual(h2, h)
113+ 
114+ def test_tensor_register_hook_repeated_handle_not_local(self):
115+ def fn(x, y, z, mod):
116+ mod.handle = x.register_hook(lambda grad: grad * 2)
117+ z = z * z
118+ return x, y * y, z
119+ 
120+ cnts = torch._dynamo.testing.CompileCounter()
121+ fn = torch._dynamo.optimize(cnts)(fn)
122+ v = torch.tensor([0.0, 0.0, 0.0], requires_grad=True)
123+ 
124+ mod = torch.nn.Module()
125+ mod.handle = None
126+ 
127+ v, y, z = fn(v, torch.randn([2, 2]), torch.randn([2, 2]), mod)
128+ v.backward(torch.tensor([1.0, 2.0, 3.0]))
129+ 
130+ self.assertEqual(v.grad, torch.tensor([2.0, 4.0, 6.0]))
131+ self.assertEqual(cnts.frame_count, 1)
132+ 
133+ self.assertNotEqual(mod.handle, None)
134+ 
135+ def test_tensor_only_register_hook_in_graph_local(self):
136+ def local_hook(grad):
137+ return grad * 2
138+ 
139+ def fn(x):
140+ x.register_hook(local_hook)
141+ return x
142+ 
143+ cnts = torch._dynamo.testing.CompileCounter()
144+ fn = torch._dynamo.optimize(cnts)(fn)
145+ v = torch.tensor([0.0, 0.0, 0.0], requires_grad=True)
146+ v = fn(v)
147+ v.backward(torch.tensor([1.0, 2.0, 3.0]))
148+ self.assertEqual(v.grad, torch.tensor([2.0, 4.0, 6.0]))
149+ self.assertEqual(cnts.frame_count, 0)
150+ 
151+ def test_tensor_only_register_hook_in_graph_local_inner(self):
152+ def fn(x):
153+ def local_hook(grad):
154+ return grad * 2
155+ 
156+ z = x * x
157+ x.register_hook(local_hook)
158+ z.register_hook(local_hook)
159+ return x, z
160+ 
161+ cnts = torch._dynamo.testing.CompileCounter()
162+ fn = torch._dynamo.optimize(cnts)(fn)
163+ v = torch.tensor([0.0, 0.0, 0.0], requires_grad=True)
164+ v = fn(v)
165+ v[0].backward(torch.tensor([1.0, 2.0, 3.0]))
166+ self.assertEqual(v[0].grad, torch.tensor([2.0, 4.0, 6.0]))
167+ self.assertEqual(cnts.frame_count, 1)
168+ 
169+ def test_tensor_register_hook_in_graph_local(self):
170+ def local_hook(grad):
171+ return grad * 2
172+ 
173+ def fn(x, y, z):
174+ x.register_hook(local_hook)
175+ return x, y * y, z * z
176+ 
177+ cnts = torch._dynamo.testing.CompileCounter()
178+ fn = torch._dynamo.optimize(cnts)(fn)
179+ v = torch.tensor([0.0, 0.0, 0.0], requires_grad=True)
180+ v = fn(v, torch.randn([2, 2]), torch.randn([2, 2]))[0]
181+ v.backward(torch.tensor([1.0, 2.0, 3.0]))
182+ self.assertEqual(v.grad, torch.tensor([2.0, 4.0, 6.0]))
183+ self.assertEqual(cnts.frame_count, 1)
184+ 
185+ def test_tensor_register_hook_in_graph_break_handle_local(self):
186+ def local_hook(grad):
187+ return grad * 2
188+ 
189+ def local_hook2(grad):
190+ return grad * 3
191+ 
192+ def fn(x, y, z):
193+ handle = x.register_hook(local_hook)
194+ z = z * z
195+ handle.remove()
196+ x.register_hook(local_hook2)
197+ return x, y * y, z
198+ 
199+ cnts = torch._dynamo.testing.CompileCounter()
200+ fn = torch._dynamo.optimize(cnts)(fn)
201+ v = torch.tensor([0.0, 0.0, 0.0], requires_grad=True)
202+ v = fn(v, torch.randn([2, 2]), torch.randn([2, 2]))[0]
203+ v.backward(torch.tensor([1.0, 2.0, 3.0]))
204+ 
205+ self.assertEqual(v.grad, torch.tensor([3.0, 6.0, 9.0]))
206+ 
207+ def test_tensor_register_global_hook(self):
208+ def fn(x):
209+ x.register_hook(global_hook_0)
210+ return x, x * x
211+ 
212+ cnts = torch._dynamo.testing.CompileCounter()
213+ fn = torch._dynamo.optimize(cnts)(fn)
214+ v = torch.tensor([0.0, 0.0, 0.0], requires_grad=True)
215+ v = fn(v)[0]
216+ v.backward(torch.tensor([1.0, 2.0, 3.0]))
217+ self.assertEqual(v.grad, torch.tensor([4.0, 8.0, 12.0]))
218+ self.assertEqual(cnts.frame_count, 1)
219+ 
220+ def test_tensor_register_multiple_hooks(self):
221+ def fn(x):
222+ x.register_hook(global_hook_0) # * 4
223+ x.register_hook(global_hook_1) # / 2
224+ x.register_hook(global_hook_2) # * 3
225+ return x, x * x
226+ 
227+ cnts = torch._dynamo.testing.CompileCounter()
228+ fn = torch._dynamo.optimize(cnts)(fn)
229+ v = torch.tensor([0.0, 0.0, 0.0], requires_grad=True)
230+ v = fn(v)[0]
231+ v.backward(torch.tensor([1.0, 2.0, 3.0]))
232+ self.assertEqual(v.grad, torch.tensor([6.0, 12.0, 18.0]))
233+ self.assertEqual(cnts.frame_count, 1)
234+ 
235+ def test_tensor_register_multiple_hooks_handles_in_list(self):
236+ def fn(x):
237+ h_0 = x.register_hook(global_hook_0) # * 4
238+ h_1 = x.register_hook(global_hook_1) # / 2
239+ h_2 = x.register_hook(global_hook_2) # * 3
240+ return x, x * x, h_0, h_1, h_2
241+ 
242+ cnts = torch._dynamo.testing.CompileCounter()
243+ fn = torch._dynamo.optimize(cnts)(fn)
244+ v = torch.tensor([0.0, 0.0, 0.0], requires_grad=True)
245+ v, r, handle_0, handle_1, handle_2 = fn(v)
246+ v.backward(torch.tensor([1.0, 2.0, 3.0]))
247+ self.assertEqual(v.grad, torch.tensor([6.0, 12.0, 18.0]))
248+ handle_0.remove()
249+ handle_1.remove()
250+ handle_2.remove()
251+ 
252+ v.backward(torch.tensor([1.0, 2.0, 3.0]))
253+ # Handles gone, grad is just applied as is
254+ self.assertEqual(v.grad, torch.tensor([7.0, 14.0, 21.0]))
255+ 
256+ self.assertEqual(cnts.frame_count, 1)
257+ 
258+ def test_tensor_register_global_hooks_handles_in_list(self):
259+ def fn(x):
260+ global h0
261+ h0 = x.register_hook(global_hook_0) # * 4
262+ return x, x * x
263+ 
264+ cnts = torch._dynamo.testing.CompileCounter()
265+ fn = torch._dynamo.optimize(cnts)(fn)
266+ v = torch.tensor([0.0, 0.0, 0.0], requires_grad=True)
267+ v, r = fn(v)
268+ 
269+ self.assertIsNotNone(h0)
270+ v.backward(torch.tensor([1.0, 2.0, 3.0]))
271+ self.assertEqual(v.grad, torch.tensor([4.0, 8.0, 12.0]))
272+ h0.remove()
273+ 
274+ v.backward(torch.tensor([1.0, 2.0, 3.0]))
275+ # Handles gone, grad is just applied as is
276+ self.assertEqual(v.grad, torch.tensor([5.0, 10.0, 15.0]))
277+ 
278+ # NYI!
279+ self.assertEqual(cnts.frame_count, 0)
280+ 
281+ def test_intermediary_hooks(self):
282+ # Graph breaks because compiled_autograd is not set
283+ def simple_hook(g):
284+ return g * 2
285+ 
286+ def f(x):
287+ y = x + 1
288+ y.register_hook(simple_hook)
289+ z = y + 1
290+ return z
291+ 
292+ out = torch.randn(1, requires_grad=True)
293+ cnts = torch._dynamo.testing.CompileCounter()
294+ fn = torch._dynamo.optimize(cnts, nopython=False)(f)
295+ res = fn(out)
296+ res.backward()
297+ self.assertEqual(res, f(out))
298+ self.assertEqual(cnts.frame_count, 2)
299+ self.assertEqual(out.grad, torch.Tensor([2.0]))
300+ 
301+ def test_intermediary_hooks_same_on_aot_eager(self):
302+ def my_hook(grad, *, k=0):
303+ return grad + k
304+ 
305+ class MyMod(torch.nn.Module):
306+ def forward(self, x):
307+ y = x.mul(2)
308+ hook1 = functools.partial(my_hook, k=3)
309+ hook2 = functools.partial(my_hook, k=4)
310+ y.register_hook(hook1)
311+ y.register_hook(hook2)
312+ z = y.mul(3)
313+ return (z,)
314+ 
315+ mod = MyMod()
316+ x0 = torch.ones(4, requires_grad=True)
317+ eager_out = mod(x0)
318+ eager_out[0].backward(torch.ones(4))
319+ 
320+ x1 = torch.ones(4, requires_grad=True)
321+ mod_compiled = aot_module_simplified(mod, (x1,), nop)
322+ aot_out = mod_compiled(x1)
323+ aot_out[0].backward(torch.ones(4))
324+ 
325+ x2 = torch.ones(4, requires_grad=True)
326+ with compiled_autograd.enable(compiler_fn):
327+ dynamo_out = torch._dynamo.optimize("aot_eager", nopython=True)(mod)(x2)
328+ dynamo_out[0].backward(torch.ones(4))
329+ 
330+ self.assertEqual(dynamo_out, aot_out)
331+ self.assertEqual(dynamo_out, eager_out)
332+ 
333+ self.assertEqual(x0.grad, x1.grad)
334+ self.assertEqual(x0.grad, x2.grad)
335+ 
336+ def test_input_hooks_same(self):
337+ backends = ["eager", "aot_eager", "inductor"]
338+ for backend in backends:
339+ 
340+ def my_hook(grad, *, k=0):
341+ return grad + k
342+ 
343+ hook = functools.partial(my_hook, k=3)
344+ 
345+ class MyMod(torch.nn.Module):
346+ def forward(self, x):
347+ x.register_hook(hook)
348+ y = x.mul(2)
349+ z = y.mul(3)
350+ return (z,)
351+ 
352+ mod = MyMod()
353+ x0 = torch.ones(4, requires_grad=True)
354+ eager_out = mod(x0)
355+ eager_out[0].backward(torch.ones(4))
356+ 
357+ x1 = torch.ones(4, requires_grad=True)
358+ mod_compiled = aot_module_simplified(mod, (x1,), nop)
359+ aot_out = mod_compiled(x1)
360+ aot_out[0].backward(torch.ones(4))
361+ 
362+ x2 = torch.ones(4, requires_grad=True)
363+ dynamo_out = torch._dynamo.optimize(backend, nopython=True)(mod)(x2)
364+ with compiled_autograd.enable(compiler_fn):
365+ dynamo_out[0].backward(torch.ones(4))
366+ 
367+ self.assertEqual(dynamo_out, aot_out)
368+ self.assertEqual(dynamo_out, eager_out)
369+ 
370+ self.assertEqual(x0.grad, x1.grad)
371+ self.assertEqual(x0.grad, x2.grad)
372+ 
373+ def test_intermediary_hooks_same_on_inductor(self):
374+ def my_hook(grad, *, k=0):
375+ return grad + k
376+ 
377+ class MyMod(torch.nn.Module):
378+ def forward(self, x):
379+ y = x.mul(2)
380+ hook1 = functools.partial(my_hook, k=3)
381+ hook2 = functools.partial(my_hook, k=4)
382+ y.register_hook(hook1)
383+ y.register_hook(hook2)
384+ z = y.mul(3)
385+ return (z,)
386+ 
387+ mod = MyMod()
388+ x0 = torch.ones(4, requires_grad=True)
389+ eager_out = mod(x0)
390+ eager_out[0].backward(torch.ones(4))
391+ 
392+ x1 = torch.ones(4, requires_grad=True)
393+ mod_compiled = aot_module_simplified(mod, (x1,), nop)
394+ aot_out = mod_compiled(x1)
395+ aot_out[0].backward(torch.ones(4))
396+ 
397+ x2 = torch.ones(4, requires_grad=True)
398+ with compiled_autograd.enable(compiler_fn):
399+ dynamo_out = torch._dynamo.optimize("inductor", nopython=True)(mod)(x2)
400+ dynamo_out[0].backward(torch.ones(4))
401+ 
402+ self.assertEqual(dynamo_out, aot_out)
403+ self.assertEqual(dynamo_out, eager_out)
404+ 
405+ self.assertEqual(x0.grad, x1.grad)
406+ self.assertEqual(x0.grad, x2.grad)
407+ 
408+ def test_complex_state_mutation_in_intermediary_hooks_same_on_inductor(self):
409+ class SomePyClass:
410+ count = 0
411+ 
412+ def do_stuff(self, grad):
413+ if self.count % 2 == 0:
414+ r = grad * grad
415+ else:
416+ r = grad + grad
417+ self.count += 1
418+ return r
419+ 
420+ def complex_state_touching_hook(grad, *, obj):
421+ return obj.do_stuff(grad)
422+ 
423+ class MyMod(torch.nn.Module):
424+ def forward(self, x, obj):
425+ y = x.mul(2)
426+ hook1 = functools.partial(complex_state_touching_hook, obj=obj)
427+ hook2 = functools.partial(complex_state_touching_hook, obj=obj)
428+ y.register_hook(hook1)
429+ y.register_hook(hook2)
430+ z = y.mul(3)
431+ return (z,)
432+ 
433+ mod = MyMod()
434+ obj = SomePyClass()
435+ x0 = torch.ones(4, requires_grad=True)
436+ eager_out = mod(x0, obj)
437+ eager_out[0].backward(torch.ones(4))
438+ 
439+ # Eager 2
440+ self.assertEqual(obj.count, 2)
441+ x2 = torch.ones(4, requires_grad=True)
442+ with compiled_autograd.enable(compiler_fn):
443+ dynamo_out = torch._dynamo.optimize("inductor", nopython=True)(mod)(x2, obj)
444+ dynamo_out[0].backward(torch.ones(4))
445+ 
446+ self.assertEqual(dynamo_out, eager_out)
447+ 
448+ # Eager 2 + compiled 2
449+ self.assertEqual(obj.count, 4)
450+ self.assertEqual(x0.grad, x2.grad)
451+ 
452+ def test_complex_state_mutation_in_intermediary_hooks_same_on_inductor_with_graph_break(
453+ self,
454+ ):
455+ class SomePyClass:
456+ grad_as_str = "None"
457+ count = 0
458+ 
459+ def write_grad_as_str_and_do_stuff(self, grad):
460+ self.grad_as_str = str(grad)
461+ if self.count % 2 == 0:
462+ r = grad * grad
463+ else:
464+ r = grad + grad
465+ print("Break!")
466+ self.count += 1
467+ return r
468+ 
469+ def complex_state_touching_hook(grad, *, obj):
470+ return obj.write_grad_as_str_and_do_stuff(grad)
471+ 
472+ class MyMod(torch.nn.Module):
473+ def forward(self, x, obj):
474+ y = x.mul(2)
475+ hook1 = functools.partial(complex_state_touching_hook, obj=obj)
476+ hook2 = functools.partial(complex_state_touching_hook, obj=obj)
477+ y.register_hook(hook1)
478+ y.register_hook(hook2)
479+ z = y.mul(3)
480+ return (z,)
481+ 
482+ mod = MyMod()
483+ obj = SomePyClass()
484+ x0 = torch.ones(4, requires_grad=True)
485+ eager_out = mod(x0, obj)
486+ eager_out[0].backward(torch.ones(4))
487+ 
488+ x2 = torch.ones(4, requires_grad=True)
489+ with compiled_autograd.enable(compiler_fn):
490+ dynamo_out = torch._dynamo.optimize("inductor", nopython=True)(mod)(x2, obj)
491+ with self.assertRaisesRegex(
492+ torch._dynamo.exc.Unsupported, ".*BuiltinVariable\\(str\\).*"
493+ ):
494+ dynamo_out[0].backward(torch.ones(4))
495+ 
496+ self.assertEqual(obj.count, 2)
497+ 
498+ def test_no_recompile_on_hook_identity_change(self):
499+ def my_hook(grad, k=0):
500+ return grad + k
501+ 
502+ def my_hook2(grad):
503+ return grad * 2
504+ 
505+ class MyMod(torch.nn.Module):
506+ def forward(self, x):
507+ y = x.mul(2)
508+ y.register_hook(my_hook)
509+ y.register_hook(my_hook)
510+ z = y.mul(3)
511+ return (z,)
512+ 
513+ mod = MyMod()
514+ x0 = torch.ones(4, requires_grad=True)
515+ eager_out = mod(x0)
516+ eager_out[0].backward(torch.ones(4))
517+ 
518+ x1 = torch.ones(4, requires_grad=True)
519+ with compiled_autograd.enable(compiler_fn):
520+ cnts = torch._dynamo.testing.CompileCounterWithBackend("aot_eager")
521+ comp_mod = torch._dynamo.optimize(cnts, nopython=True)(mod)
522+ comp_out = comp_mod(x1)
523+ comp_out[0].backward(torch.ones(4))
524+ 
525+ self.assertEqual(cnts.frame_count, 1)
526+ my_hook = my_hook2 # noqa: F811
527+ self.assertEqual(x0.grad, x1.grad)
528+ 
529+ eager_out = mod(x0)
530+ eager_out[0].backward(torch.ones(4))
531+ 
532+ comp_out = comp_mod(x1)
533+ 
534+ self.assertEqual(cnts.frame_count, 2)
535+ comp_out[0].backward(torch.ones(4))
536+ self.assertEqual(x0.grad, x1.grad)
537+ 
538+ def test_functools_arg_vary(self):
539+ def pre_hook(grad, *, k):
540+ return grad * k
541+ 
542+ hook = functools.partial(pre_hook, k=1)
543+ 
544+ @torch.compile(backend="eager", fullgraph=True)
545+ def h(x):
546+ y = x.mul(2)
547+ y.register_hook(hook)
548+ return y.mul(3)
549+ 
550+ with compiled_autograd.enable(torch.compile(backend="eager", fullgraph=True)):
551+ x = torch.randn(2, requires_grad=True)
552+ h(x).sum().backward()
553+ orig_grad = x.grad
554+ x.grad = None
555+ 
556+ hook = functools.partial(pre_hook, k=2)
557+ h(x).sum().backward()
558+ self.assertEqual(orig_grad * 2, x.grad)
559+ 
560+ def test_post_acc_grad_hook(self):
561+ def hook(input_t):
562+ input_t.mul_(input_t.grad)
563+ input_t.grad.mul_(5)
564+ 
565+ def reg_and_mul(x, y):
566+ x.register_post_accumulate_grad_hook(hook)
567+ return x * y
568+ 
569+ cnts = None
570+ 
571+ def test_fn(fn):
572+ fn(x, y)
573+ b = torch.tensor([2.0, 2.0, 2.0], requires_grad=True)
574+ x.backward(b)
575+ if cnts:
576+ self.assertEqual(cnts.frame_count, 1)
577+ # These same exact assertions run on both eager and compiled
578+ # X goes to x*2 becaue of mul_
579+ self.assertEqual(x, torch.tensor([0.5, 0.5, 0.5]) * 2)
580+ # This test proves grad aliasing works -
581+ self.assertEqual(x.grad, b * 5)
582+ 
583+ # Eager values
584+ x = torch.tensor([0.5, 0.5, 0.5], requires_grad=True)
585+ y = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
586+ test_fn(reg_and_mul)
587+ 
588+ # Compiled
589+ for backend in ["eager", "aot_eager", "inductor"]:
590+ for compiled_bwd in [False, True]:
591+ torch._dynamo.reset()
592+ x = torch.tensor([0.5, 0.5, 0.5], requires_grad=True)
593+ y = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
594+ 
595+ cnts = torch._dynamo.testing.CompileCounterWithBackend(backend)
596+ compiled_fn = torch._dynamo.optimize(cnts, nopython=True)(reg_and_mul)
597+ 
598+ compiled_bwd_ctx = (
599+ compiled_autograd.enable(
600+ torch.compile(backend=backend, fullgraph=True)
601+ )
602+ if compiled_bwd
603+ else contextlib.nullcontext()
604+ )
605+ with compiled_bwd_ctx:
606+ test_fn(compiled_fn)
607+ 
608+ 
609+if __name__ == "__main__":
610+ from torch._dynamo.test_case import run_tests
611+ 
612+ run_tests()
@@ -0,0 +1,200 @@
1+"""
2+PYTEST_DONT_REWRITE (prevents pytest from rewriting assertions, which interferes
3+with test_adam in OptimizerTests)
4+"""
5+import functools
6+ 
7+# Owner(s): ["module: dynamo"]
8+ 
9+import inspect
10+ 
11+import torch
12+import torch_npu
13+import torch._dynamo
14+import torch._dynamo.test_case
15+import torch._dynamo.testing
16+from torch.nn import Parameter
17+ 
18+input1 = torch.ones([10, 10])
19+model = torch.nn.Sequential(*[torch.nn.Linear(10, 10) for _ in range(2)])
20+model(input1).sum().backward()
21+ 
22+ 
23+def get_optimizer_step(opt_arg, closure=None):
24+ # run the patcher so that step has the expected structure
25+ torch._dynamo.eval_frame.TorchPatcher.patch()
26+ 
27+ # unwrap step to avoid a deliberate graph break due to
28+ # a limitation of functionalization/no_grad detection
29+ # see the [Note on graph break] in optimizer.py
30+ # This ignores the outer _use_grad_if_differentiable wrapper, which is fine for now
31+ # as dynamo does not support differentiable optimizers anyway
32+ step_fn = opt_arg.step.__wrapped__
33+ if closure is not None:
34+ 
35+ def fn():
36+ step_fn(opt_arg, closure)
37+ 
38+ else:
39+ 
40+ def fn():
41+ step_fn(opt_arg)
42+ 
43+ return fn
44+ 
45+ 
46+def make_test(optim_cls, closure=None, **kwargs):
47+ opt = optim_cls(model.parameters(), **kwargs)
48+ 
49+ def test_fn(self):
50+ nonlocal opt
51+ 
52+ fn = get_optimizer_step(opt, closure=closure)
53+ 
54+ with torch.set_grad_enabled(False):
55+ torch.compile(fn, backend="eager", fullgraph=True)()
56+ 
57+ return test_fn
58+ 
59+ 
60+class OptimizerTests(torch._dynamo.test_case.TestCase):
61+ test_sgd = make_test(torch.optim.SGD, lr=0.01)
62+ # lgbfs has data-dependent control and internally iterates
63+ # calling the closure
64+ # do for later mlazos: re-enable once we have latest pytorch with FakeTensor fix #497
65+ # test_lbfgs = make_test(
66+ # torch.optim.LBFGS, exp_frame_cnt=3, closure=lambda: model(input).sum()
67+ # )
68+ 
69+ # Has data dependent control for rectification (needs symint)
70+ # RAdam has data-dependent control which breaks the graph;
71+ # furthermore, the break is inside a for loop, so we bail on the frame
72+ # entirely. This is basically an xfail; if the frame count goes up
73+ # you done good
74+ # test_radam = unittest.skipIf(IS_FBCODE, "TypeError: _use_grad() missing")(
75+ # make_test(torch.optim.RAdam, exp_graph_count=0)
76+ # )
77+ 
78+ 
79+# exclude SparseAdam because other areas of the stack don't support it yet
80+# the others are handled specially above
81+exclude = {
82+ "SGD", # Handled above
83+ "Optimizer",
84+ "SparseAdam", # Unsupported
85+ "LBFGS", # Unsupported
86+ "RAdam", # Has data dependent control for rectification (needs symint)
87+}
88+ 
89+ 
90+def check_opt(opt_ipt):
91+ if inspect.isclass(opt_ipt) and issubclass(opt_ipt, torch.optim.Optimizer) and opt_ipt.__name__ not in exclude:
92+ return True
93+ return False
94+ 
95+ 
96+optimizers = [
97+ opt
98+ for opt in torch.optim.__dict__.values()
99+ if check_opt(opt)
100+]
101+ 
102+ 
103+for opt in optimizers:
104+ setattr(OptimizerTests, "test_" + opt.__name__.lower(), make_test(opt))
105+ 
106+ 
107+class MyOptimizer(torch.optim.Optimizer):
108+ def __init__(self, params):
109+ super().__init__(params, {})
110+ 
111+ def _init_group(self, params, group):
112+ any_complex = False
113+ for p in group["params"]:
114+ params.append(p)
115+ any_complex |= p.is_complex()
116+ return any_complex
117+ 
118+ def step(self):
119+ for group in self.param_groups:
120+ params = []
121+ any_complex = self._init_group(params, group)
122+ if any_complex:
123+ params[0] -= 1
124+ else:
125+ params[0] += 1
126+ 
127+ 
128+class End2EndTests(torch._dynamo.test_case.TestCase):
129+ # see torchdynamo issues 1604
130+ def test_optimizing_over_tensor_with_requires_grad(self):
131+ class Net(torch.nn.Module):
132+ def forward(self, x, y):
133+ z = torch.bmm(x, y)
134+ z = torch.flatten(z, 1)
135+ return z
136+ 
137+ def training_iter_fn(batch, model, optimizer):
138+ optimizer.zero_grad()
139+ out = model(**batch)
140+ target = torch.tensor([0, 7])
141+ loss = torch.nn.CrossEntropyLoss()(out, target)
142+ loss.backward()
143+ optimizer.step()
144+ return loss
145+ 
146+ net = Net()
147+ input_1 = torch.randn(2, 1, 4)
148+ input_2 = torch.randn(2, 4, 8, requires_grad=True)
149+ optimizer = torch.optim.Adam([input_2], lr=0.1)
150+ 
151+ cnts = torch._dynamo.testing.CompileCounter()
152+ opt_training_iter_fn = torch._dynamo.optimize(cnts)(training_iter_fn)
153+ batch = {"x": input_1, "y": input_2}
154+ for _ in range(2):
155+ opt_training_iter_fn(batch, net, optimizer)
156+ self.assertEqual(cnts.frame_count, 2)
157+ 
158+ def test_state_dict(self):
159+ @torch.compile(backend="eager")
160+ def _test_state_dict(weight, bias, ipt):
161+ def fn_base(optimizer, weight, bias):
162+ optimizer.zero_grad()
163+ i = ipt
164+ loss = (weight.mv(i) + bias).pow(2).sum()
165+ loss.backward()
166+ return loss
167+ 
168+ optimizer = torch.optim.Adagrad([weight, bias])
169+ fn = functools.partial(fn_base, optimizer, weight, bias)
170+ return optimizer, fn
171+ 
172+ optimizer, fn = _test_state_dict(
173+ Parameter(torch.randn(10, 5)),
174+ Parameter(torch.randn(10)),
175+ torch.randn(5, requires_grad=True),
176+ )
177+ optimizer.step(fn)
178+ 
179+ def test_init_group(self):
180+ for dtype in [torch.float32, torch.cfloat]:
181+ tensor = torch.randn(5, 5, dtype=dtype)
182+ params = Parameter(tensor.detach().clone(), requires_grad=False)
183+ opt_params = Parameter(tensor.detach().clone(), requires_grad=False)
184+ print(params, opt_params)
185+ 
186+ optim = MyOptimizer([params])
187+ optim.step()
188+ 
189+ opt_optim = MyOptimizer([opt_params])
190+ opt_step = torch.compile(backend="eager", fullgraph=True)(opt_optim.step)
191+ opt_step()
192+ print(params, opt_params)
193+ 
194+ self.assertEqual(params, opt_params)
195+ 
196+ 
197+if __name__ == "__main__":
198+ from torch._dynamo.test_case import run_tests
199+ 
200+ run_tests()