@@ -101,6 +101,91 @@ def test_strip_grad_suffix(self):
101101 self .assertEqual (backward ._strip_grad_suffix_ (input_ ), desired )
102102
103103
104+ class TestBackwardCreateGraph (unittest .TestCase ):
105+ """Test backward with create_graph parameter for higher-order gradients."""
106+
107+ def test_backward_create_graph_second_order (self ):
108+ """Test computing second-order gradients using create_graph=True."""
109+ paddle .disable_static ()
110+ x = paddle .to_tensor ([1.0 , 2.0 ], dtype = 'float32' , stop_gradient = False )
111+ y = x ** 2
112+ loss = y .sum ()
113+ # First backward with create_graph=True
114+ # x.grad should be [2.0, 4.0]
115+ paddle .autograd .backward (loss , create_graph = True )
116+ grad_sum = x .grad .sum ()
117+ grad_sum .backward ()
118+ # Check second-order gradients
119+ # sum up to [4.0, 6.0]
120+ self .assertIsNotNone (x .grad )
121+ np .testing .assert_allclose (x .grad .numpy (), [4.0 , 6.0 ], rtol = 1e-5 )
122+
123+ def test_backward_create_graph_with_multiple_tensors (self ):
124+ """Test backward with create_graph on multiple output tensors."""
125+ paddle .disable_static ()
126+ x = paddle .to_tensor (
127+ [[1.0 , 2.0 ], [3.0 , 4.0 ]], dtype = 'float32' , stop_gradient = False
128+ )
129+ z1 = x ** 2
130+ z2 = x * 3
131+ # Backward on z1
132+ paddle .autograd .backward (z1 , create_graph = True )
133+ self .assertIsNotNone (x .grad )
134+ self .assertFalse (x .grad .stop_gradient )
135+ x .clear_grad ()
136+ # Backward on z2
137+ paddle .autograd .backward (z2 , create_graph = True )
138+ self .assertIsNotNone (x .grad )
139+ self .assertFalse (x .grad .stop_gradient )
140+
141+ def test_backward_create_graph_with_grad_tensors (self ):
142+ """Test backward with create_graph and custom grad_tensors."""
143+ paddle .disable_static ()
144+ x = paddle .to_tensor ([1.0 , 2.0 ], dtype = 'float32' , stop_gradient = False )
145+ y = x ** 2
146+ z = y .sum ()
147+ grad_tensor = paddle .to_tensor ([1.0 , 2.0 ], dtype = 'float32' )
148+ paddle .autograd .backward (y , grad_tensors = grad_tensor , create_graph = True )
149+ # Check gradients with custom weights
150+ self .assertIsNotNone (x .grad )
151+ expected = [2.0 * 1.0 , 4.0 * 2.0 ] # dy/dx * weights
152+ np .testing .assert_allclose (x .grad .numpy (), expected , rtol = 1e-5 )
153+ self .assertFalse (x .grad .stop_gradient )
154+
155+ def test_backward_create_graph_retain_graph (self ):
156+ """Test backward with create_graph=True and retain_graph=True."""
157+ paddle .disable_static ()
158+ x = paddle .to_tensor ([2.0 ], dtype = 'float32' , stop_gradient = False )
159+ y = x ** 3
160+ loss = y .sum ()
161+ # First backward
162+ paddle .autograd .backward (loss , create_graph = True , retain_graph = True )
163+ grad1 = x .grad .clone ()
164+ x .clear_grad ()
165+ # Second backward with same graph
166+ paddle .autograd .backward (loss , create_graph = True , retain_graph = False )
167+ grad2 = x .grad
168+ # Gradients should be the same
169+ np .testing .assert_allclose (grad1 .numpy (), grad2 .numpy (), rtol = 1e-5 )
170+
171+ def test_backward_create_graph_chain_rule (self ):
172+ """Test chain rule with higher-order gradients."""
173+ paddle .disable_static ()
174+ x = paddle .to_tensor ([1.0 ], dtype = 'float32' , stop_gradient = False )
175+ y = x ** 3
176+ loss = y ** 2
177+ # First backward
178+ paddle .autograd .backward (loss , create_graph = True , retain_graph = True )
179+ # At x=1: x.grad should be 6.0
180+ np .testing .assert_allclose (x .grad .numpy (), [6.0 ], rtol = 1e-5 )
181+ # Second backward
182+ grad_sum = paddle .sum (x .grad )
183+ paddle .autograd .backward (grad_sum )
184+ # At x=1: x.grad should be 30
185+ # Sum up to 36
186+ np .testing .assert_allclose (x .grad .numpy (), [36.0 ], rtol = 1e-5 )
187+
188+
104189if __name__ == '__main__' :
105190 paddle .enable_static ()
106191 unittest .main ()
0 commit comments