Concepts / Computing Gradients via Autograd

Computing Gradients via Autograd

The grad function computes one gradient on request and returns it, while the backward method fills the grad attribute of every tracked tensor on the path to the loss.

  • Programming

Predicting the Sign of a Gradient

Gradient computation is easiest to understand when you have a guess about the answer before the library hands you a number. This article follows one short PyTorch program. The program takes an input, multiplies it by a weight, adds a bias, passes the result through a sigmoid, and measures the error with a binary cross-entropy loss. PyTorch's autograd system then produces gradients for the parameters. The purpose of the program is to show two different ways of asking autograd for those gradients, and how the order of the calls affects what you can read back.

Start with the setup. The bias b begins at 0.0 and is tracked by autograd. The input x1 is 1.1, the weight w1 is 2.2, and the target y is 1.0. Before reading further, decide whether the gradient of the loss with respect to b should be positive or negative. The target is 1.0 and the sigmoid output for this starting point is below 1, so the prediction is too low. Commit to a sign before you read the answer.

What do you think happens?

For the bias b at 0.0, with x1 = 1.1, w1 = 2.2 and y = 1.0, is the gradient of the loss with respect to b positive or negative?

  • Positive

  • Negative

Reveal answer
Answer:

Negative

The weighted sum is z = 2.2 × 1.1 + 0.0 = 2.42, and the sigmoid output a is about 0.92, which is below the target 1.0. For binary cross-entropy on a sigmoid output, the derivative of the loss with respect to the pre-sigmoid value is a − y, which here is about −0.08. Increasing b would push a toward 1.0 and lower the loss, so the gradient is negative. Gradient descent would therefore move b upward.

Tracing the Forward Computation

Every value in the program is a link in a chain. The weighted sum z is built from w1, x1 and b. The sigmoid output a is built from z. The loss is built from a and the target y. Autograd records these links as a computation graph while the forward values are computed, so that a later request for a gradient can walk back along the same links from the loss toward the parameters.

lossbinary cross-entropyasigmoid outputzweighted sumw1requires_grad Truebrequires_grad Truex1requires_grad Falsedepends onsigmoid oftimesplustimes
Starting at the loss, the gradient passes back through the sigmoid output and the weighted sum, and reaches the two tensors that have requires_grad set to True.

The tree shows the path a backward pass follows. The loss depends on a, a depends on z, and z depends on w1, x1 and b. The leaf tensors are the ones at the bottom of the tree that autograd can deliver a gradient to. Because x1 was created without requires_grad, it is on the path but does not collect a gradient of its own.

Writing the Program

The code below reconstructs the program described above. It has two phases. The first phase builds the loss and calls grad once to get the gradient of the loss with respect to b. The second phase calls backward on the same loss and reads the grad attributes of the parameters.

python
Notes on the code
  • x1 has no requires_grad, so autograd does not track it as a parameter
  • w1 and b have requires_grad=True, so they are the tensors that can receive gradients
  • retain_graph=True keeps the graph alive for the backward call that follows
  • grad returns a tuple, so [0] selects the gradient for b

Notice that the code never passes w1 or b to backward. The loss already knows which tensors fed into it through the recorded graph. The grad call is different: it answers one question about one chosen tensor and returns the answer to you. The backward call answers the question for every tracked tensor at once and stores each answer in that tensor's grad attribute.

Return Value Versus Stored Attribute

The grad function computes one gradient on request and returns it. It takes the loss and the parameter whose gradient you want. The backward method is called on the loss tensor and computes gradients for every tracked tensor that contributed to it, storing each result in that tensor's grad attribute. The two differ in where the answer goes and how many answers come back.

Propertygrad(loss, tensor)loss.backward()
Called onThe loss, with the chosen tensor passed as an argumentThe loss tensor only
ScopeOne gradient for one chosen tensorEvery tracked tensor on the path to the loss
Where the result goesReturned as the value of the callStored in each tensor's grad attribute
How you read itAssign the return value, as with grad_L_bRead tensor.grad, as with w1.grad and b.grad

How the two gradient calls differ in what they return and where the results are stored

What retain_graph=True Protects

Both phases of the program depend on the same computation graph. The grad call uses the graph to produce grad_L_b. The argument retain_graph=True tells autograd to keep that graph so the later loss.backward() call can still use it to fill in w1.grad and b.grad. This is why the order of the calls and the retain_graph argument matter together.

graph builtforward passgrad callretain_graph=Truegraph retainedstill availableloss.backward()fills .gradw1.grad and b.gradgraph releasedwithout retain_graphfirst callkeeps graphreuses graphstoresno retain_graph
With retain_graph=True on the grad call, the graph stays available for the backward call. Without it, the graph is released after the first gradient request.

Which Tensors Receive Gradients

Only tensors created with requires_grad=True receive gradients. In the program, w1 and b have that setting, so they are the tensors whose grad attributes are filled in. The tensor x1 does not, so it takes part in the forward computation but never gets a gradient. This means the requires_grad choice decides what you can read back after the calls.

requires_grad?x1Falseno grad attributew1Truew1.gradbTrueb.gradFalseTrueTruenot trackedreceivesreceives
Each tensor in the program is checked against its requires_grad flag. Tensors with the flag set receive a grad value, and the others do not.

Tracing an Unexpected Gradient

When a printed gradient looks wrong, do not start with the final number. Start with the program's structure. First check which tensors have requires_grad=True, since only those receive grad attributes. Then check which call produced the value you are reading: grad returns a value, while backward fills grad attributes. Finally, trace the forward values through the same operations to find the first place where your expected value and the computed value disagree.

Worked example

Tracing the bias gradient by hand

The printed value for b.grad does not match what you expected. Find where the value originates, using the program from this article with w1 = 2.2, x1 = 1.1, b = 0.0 and y = 1.0.

  1. 1
    Check the flags

    b was created with requires_grad=True, so it is a leaf that can receive a gradient. If it had been created without the flag, no value would appear in b.grad at all.

  2. 2
    Recompute the forward value of z

    z = w1 × x1 + b = 2.2 × 1.1 + 0.0 = 2.42. If your expected z differs, the error is in the forward line, not in autograd.

  3. 3
    Recompute the sigmoid output

    a = sigmoid(2.42), which is about 0.92. This is the link between z and the loss.

  4. 4
    Apply the chain rule back to b

    For binary cross-entropy on a sigmoid output, the derivative of the loss with respect to b equals a − y, which is about −0.08. The sign is negative, matching the prediction made at the start.

  5. 5
    Compare against the printed value

    The first step where your hand value and the printed value disagree is the link to inspect. Look at which call produced the printed value and whether that tensor was the one you intended.

Result

The gradient for b comes from the link between the sigmoid output and b. A mismatch points to the specific tensor and operation where the two values diverge.

This hand check is tracing, not a replacement for autograd. In practice you let backward compute the value. You trace by hand only to confirm which tensor and which operation a number came from when something looks wrong.

Mistakes That Produce Missing or Stale Gradients

  • Common mistake

    Expecting a gradient on a tensor that was created without requires_grad=True

    What it looks like
    x1 = torch.tensor(1.1) followed by reading x1.grad after backward
    Why it fails

    Only tensors created with requires_grad=True receive gradients, so x1 never gets a grad value.

    Do this instead

    Create the tensor with requires_grad=True if you need its gradient, or read the gradient only from tensors that are tracked.

  • Common mistake

    Calling backward without retain_graph=True after an earlier grad call on the same loss

    What it looks like
    grad(loss, b) followed by loss.backward(), with no retain_graph argument
    Why it fails

    The program's second phase depends on the same graph the grad call used, so the graph must be kept.

    Do this instead

    Pass retain_graph=True to the earlier grad call, as the program does.

  • Common mistake

    Reading a return value where the answer is stored, or the reverse

    What it looks like

    Expecting loss.backward() to return the gradient for b

    Why it fails

    grad returns the gradient as its result, while backward stores gradients in the grad attributes of tracked tensors.

    Do this instead

    Assign the return value of grad, and read tensor.grad after backward.

  • Common mistake

    Assuming the printed gradient is correct without tracing the forward values that produced it

Practice Tracing the Program

MEDIUM

Using the program in this article, suppose you change b to be created without requires_grad=True and keep the rest of the code the same. Which tensors would receive a grad value after loss.backward(), and what would happen to the grad call for b? Explain your answer by naming the tensors and the call responsible for each result.

Hints
  • Look at which tensors have requires_grad=True after the change.
  • Think about whether grad can be asked for a tensor that is not tracked.

Checking Your Understanding

Key Takeaways

  • The grad function computes one chosen gradient and returns it, while backward fills the grad attribute of every tracked tensor on the path to the loss.
  • Only tensors created with requires_grad=True receive gradients, so that flag determines what you can read back.
  • retain_graph=True keeps the computation graph available so that a backward call after grad can reuse it.
  • When a gradient looks wrong, trace the forward values through the same chain of operations to find the first point of disagreement.

Related concepts

More concepts starting with C