Skip to content

Commit 5a8db56

Browse files
committed
support create_graph in paddle.Tensor.backward() and paddle.autograd.backward()
1 parent 44b28a7 commit 5a8db56

7 files changed

Lines changed: 134 additions & 6 deletions

File tree

paddle/fluid/eager/backward.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -746,14 +746,15 @@ std::vector<paddle::Tensor> RunBackward(
746746
void Backward(const std::vector<paddle::Tensor>& tensors, // outputs
747747
const std::vector<paddle::Tensor>& grad_tensors,
748748
bool retain_graph,
749+
bool create_graph,
749750
std::string dump_backward_graph_path) {
750751
VLOG(3) << "Run in Backward";
751752
phi::RecordEvent backward_record_event(
752753
"backward", phi::TracerEventType::UserDefined, 1);
753754
RunBackward(tensors,
754755
grad_tensors,
755756
retain_graph,
756-
false,
757+
create_graph,
757758
{},
758759
false,
759760
{},

paddle/fluid/eager/backward.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ namespace egr {
2626
TEST_API void Backward(const std::vector<paddle::Tensor>& tensors,
2727
const std::vector<paddle::Tensor>& grad_tensors,
2828
bool retain_graph = false,
29+
bool create_graph = false,
2930
std::string dump_backward_graph_path = "");
3031

3132
TEST_API std::vector<paddle::Tensor> Grad(

paddle/fluid/pybind/eager_functions.cc

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,9 @@ static PyObject* eager_api_run_backward(PyObject* self,
158158
auto tensors = CastPyArg2VectorOfTensor(PyTuple_GET_ITEM(args, 0), 0);
159159
auto grad_tensors = CastPyArg2VectorOfTensor(PyTuple_GET_ITEM(args, 1), 1);
160160
bool retain_graph = CastPyArg2AttrBoolean(PyTuple_GET_ITEM(args, 2), 2);
161+
bool create_graph = CastPyArg2AttrBoolean(PyTuple_GET_ITEM(args, 3), 3);
161162
std::string dump_backward_graph_path =
162-
CastPyArg2AttrString(PyTuple_GET_ITEM(args, 3), 3);
163+
CastPyArg2AttrString(PyTuple_GET_ITEM(args, 4), 4);
163164
const phi::distributed::ProcessMesh* mesh = nullptr;
164165
if (InputsContainDistTensor(&mesh, tensors, grad_tensors)) {
165166
tensors = CastPyArg2VectorOfTensor(PyTuple_GET_ITEM(args, 0), 0, mesh);
@@ -168,8 +169,11 @@ static PyObject* eager_api_run_backward(PyObject* self,
168169
{
169170
eager_gil_scoped_release guard;
170171
EagerSetDeviceId();
171-
egr::Backward(
172-
tensors, grad_tensors, retain_graph, dump_backward_graph_path);
172+
egr::Backward(tensors,
173+
grad_tensors,
174+
retain_graph,
175+
create_graph,
176+
dump_backward_graph_path);
173177
}
174178
RETURN_PY_NONE
175179
EAGER_CATCH_AND_THROW_RETURN_NULL

python/paddle/autograd/backward_mode.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def backward(
3535
tensors: Tensor | Sequence[Tensor],
3636
grad_tensors: Tensor | Sequence[Tensor | None] | None = None,
3737
retain_graph: bool = False,
38+
create_graph: bool = False,
3839
*,
3940
dump_backward_graph_path: str | None = None,
4041
) -> None:
@@ -143,5 +144,9 @@ def check_tensors(
143144
assert isinstance(retain_graph, bool), "retain_graph must be True or False"
144145
check_and_create_dir(dump_backward_graph_path)
145146
core.eager.run_backward(
146-
tensors, grad_tensors, retain_graph, dump_backward_graph_path
147+
tensors,
148+
grad_tensors,
149+
retain_graph,
150+
create_graph,
151+
dump_backward_graph_path,
147152
)

python/paddle/base/dygraph/tensor_patch_methods.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ def backward(
288288
self: Tensor,
289289
grad_tensor: Tensor | None = None,
290290
retain_graph: bool = False,
291+
create_graph: bool = False,
291292
*,
292293
dump_backward_graph_path: str | None = None,
293294
) -> None:
@@ -368,7 +369,11 @@ def backward(
368369
self = _grad_scalar.scale(self)
369370
check_and_create_dir(dump_backward_graph_path)
370371
core.eager.run_backward(
371-
[self], grad_tensor, retain_graph, dump_backward_graph_path
372+
[self],
373+
grad_tensor,
374+
retain_graph,
375+
create_graph,
376+
dump_backward_graph_path,
372377
)
373378

374379
if in_profiler_mode():

test/legacy_test/test_backward.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
104189
if __name__ == '__main__':
105190
paddle.enable_static()
106191
unittest.main()

test/legacy_test/test_imperative_partial_backward.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,33 @@ def test_partial_backward(self):
5252
linear1.clear_gradients()
5353
linear2.clear_gradients()
5454

55+
def test_backward_with_create_graph(self):
56+
"""Test backward with create_graph=True for second-order gradients"""
57+
with base.dygraph.guard():
58+
x = paddle.to_tensor(
59+
np.array([[1.0, 2.0], [3.0, 4.0]]), dtype='float32'
60+
)
61+
x.stop_gradient = False
62+
y = x * x
63+
loss = paddle.sum(y)
64+
# First backward with create_graph=True
65+
loss.backward(create_graph=True)
66+
# Verify first-order gradients
67+
self.assertIsNotNone(x.grad)
68+
first_grad = x.grad.numpy()
69+
np.testing.assert_allclose(
70+
first_grad, np.array([[2.0, 4.0], [6.0, 8.0]]), rtol=1e-7
71+
)
72+
# Compute second-order gradients
73+
grad_sum = paddle.sum(x.grad)
74+
grad_sum.backward()
75+
# Verify second-order gradients
76+
self.assertIsNotNone(x.grad)
77+
second_grad = x.grad.numpy()
78+
np.testing.assert_allclose(
79+
second_grad, np.array([[4.0, 6.0], [8.0, 10.0]]), rtol=1e-7
80+
)
81+
5582

5683
if __name__ == '__main__':
5784
unittest.main()

0 commit comments

Comments
 (0)