PyTorch's autograd system is the engine that powers automatic differentiation—it tracks operations on tensors and computes gradients automatically. This eliminates manual derivative calculations during backpropagation, making deep learning research fast and error-free.
By default, calling .backward()accumulates gradients into .grad:
tensor.grad←tensor.grad+∂tensor∂L
Why accumulation? In mini-batch training, you sum gradients across multiple samples before updating weights. But this means you must zero gradients between iterations:
x.grad.zero_() # Clear previous gradients
Derivation of why accumulation is needed:
In batch gradient descent, the loss is:
Lbatch=N1∑i=1NLi
The gradient is:
∂w∂Lbatch=N1∑i=1N∂w∂Li
If we compute ∂w∂Li for each sample separately, we must sum them. Accumulation implements this sum automatically.
During inference, you don't need gradients. Disabling autograd saves memory and speeds up computation:
with torch.no_grad(): output = model(x) # No graph built
Why this works: PyTorch skips grad_fn assignment, eliminating overhead.
Mathematical insight: In inference, we only need the forward pass:
y^=f(x;θ)
No need to compute ∂θ∂L, so no graph required.
Inference mode (newer, faster):
with torch.inference_mode(): output = model(x)
Difference: inference_mode() is more aggressive—tensors created inside cannot be used in gradient computation later (even outside the context). Use for pure inference; use no_grad() if you might need gradients later.
Gradient checkpointing: Recompute intermediate activations during backward pass (trade compute for memory)
Delete graphs after use: PyTorch's default (graphs freed after .backward())
Detach unnecessary tensors: Stop gradients where not needed
Formula for memory cost:Memory≈O(N⋅S)
where N = number of layers, S = activation size. Gradient checkpointing reduces this to O(N⋅S) at cost of 2× compute.
Imagine you're building a really tall tower with LEGO blocks. Every time you place a block, you write down on a piece of paper: "I put block #5 on top of block #3." That list of notes is your computational graph—it's a record of how you built the tower.
Now, at the end, someone tells you, "The tower is 2 inches too tall." You need to figure out which blocks to remove. You start at the top and work backward: "If I remove this block, the tower gets 1 inch shorter. If I remove the one below, it gets another inch shorter." That's backpropagation—going backward through your notes to see how each piece contributed to the final height.
PyTorch's autograd is like having a robot that automatically writes those notes for you and calculates how changing each block affects the tower's height. You just build (forward pass), and the robot figures out the changes (backward pass) for you!
5.3.01-Transfer-learning - Fine-tuning requires controlling which layers have requires_grad
6.4.02-Gradient-checkpointing - Advanced memory optimization technique for deep networks
#flashcards/ai-ml
What is a computational graph in PyTorch? :: A directed acyclic graph (DAG) where nodes are tensors/operations and edges are data flow. PyTorch builds it dynamically during the forward pass and uses it during backward pass to compute gradients via the chain rule.
What does requires_grad=True do?
Marks a tensor for gradient tracking. PyTorch records all operations on such tensors, building the computational graph that enables automatic differentiation during backpropagation.
Why must you call optimizer.zero_grad() before each training iteration?
PyTorch accumulates gradients by default (adds new gradients to existing .grad values). Without zeroing, gradients from previous iterations add up incorrectly, causing wrong parameter updates.
What's the difference between torch.no_grad() and torch.inference_mode()?
Both disable gradient tracking. no_grad() allows tensors to later be used in gradient computation (after exiting the context). inference_mode() is more aggressive—tensors created inside cannot participate in autograd later, but it's faster for pure inference.
Why does .backward() require a scalar input?
Backpropagation computes ∂x∂L where L is scalar. For vector outputs, the gradient would be a Jacobian matrix. You must reduce to scalar (via sum, mean, or indexing) or provide a gradient argument.
What does .detach() do?
Creates a new tensor that shares data with the original but has requires_grad=False and grad_fn=None. Gradients do not flow through detached tensors—they're treated as constants in the computational graph.
What is grad_fn?
A reference to the function that created a tensor (e.g., <MulBackward>, <AddBackward>). It forms the computational graph—following grad_fn pointers backward reconstructs the sequence of operations needed for backpropagation.
Why do in-place operations (like +=) sometimes break autograd?
Autograd needs original values to compute correct gradients. In-place ops overwrite history. If you modify a tensor that's needed later in the backward pass, the gradient computation fails. Use out-of-place operations (y = y + 1 instead of y += 1) during training.
What's the chain rule formula for a computational graph path x→a→b→z?
∂x∂z=∂b∂z⋅∂a∂b⋅∂x∂a. Autograd computes this by traversing the graph backward, multiplying local gradients at each node.
When would you use retain_graph=True?
When you need to call .backward() multiple times on the same graph (e.g., computing multiple losses from one forward pass in multi-task learning or adversarial training). By default, PyTorch frees the graph after first .backward() to save memory.
What's a leaf tensor?
A tensor created directly by the user (not as result of an operation). It has is_leaf=True and grad_fn=None. Only leaf tensors store gradients by default; intermediate tensors need retain_grad() to keep their gradients.
Derive dxd(x2) from the limit definition :: dxd(x2)=limh→0h(x+h)2−x2=limh→0hx2+2xh+h2−x2=limh→0h2xh+h2=limh→0(2x+h)=2x
Why does gradient accumulation happen by default in PyTorch?
To support mini-batch training where you sum gradients across multiple samples: ∂w∂Lbatch=N1∑i=1N∂w∂Li. Accumulation implements this sum. You must manually zero between training iterations.
What's the memory complexity of storing a computational graph?
O(N⋅S) where N is the number of operations/layers and S is the size of activations. Each operation stores inputs and metadata for the backward pass. Gradient checkpointing reduces this to O(N⋅S) by recomputing activations.
Dekho, autograd basically PyTorch ka woh magical engine hai jo automatically derivatives (gradients) nikaal deta hai — tumhe khud haath se calculus karne ki zarurat nahi padti. Jab tum neural network train karte ho, tumhe loss function ka gradient nikaalna padta hai lakhon parameters ke respect me. Ye kaam manually karna impossible hai aur galtiyaan hone ki full guarantee. Isliye PyTorch ek "tape recorder" ki tarah kaam karta hai — jab tum forward pass me tensors pe operations karte ho (jaise multiply, add, power), to woh chupchaap ek computational graph banata jaata hai jisme har operation record hota hai. Jaise hi tum .backward() call karte ho, woh is graph ko ulta traverse karke, chain rule laga ke saare gradients ek hi baar me nikaal deta hai.
Ismein main trick ye hai ki tum tensor pe requires_grad=True set karte ho — matlab "iss tensor ko track karo". Fir har naya tensor jo operations se banta hai, uske paas ek grad_fn hota hai (jaise <MulBackward>, <AddBackward>) jo batata hai ki woh kis operation se aaya. Yehi grad_fn saare tensors ko jodkar poora graph bana deta hai. Backward pass me PyTorch bas chain rule apply karta hai — agar z=xy+x2 hai, to dz/dx=y+2x automatically nikal aata hai, aur tumhe answer .grad attribute me mil jaata hai. Yaani jo math tum kaagaz pe limit definition se derive karte, woh PyTorch milliseconds me kar deta hai.
Ek important baat jo dhyaan me rakhni hai — .backward() by default gradients ko accumulate karta hai, matlab purane gradient me naya add hota jaata hai. Ye mini-batch training ke liye kaam ka hai kyunki tum multiple samples ke gradients sum karte ho, par iska matlab hai ki har iteration ke baad tumhe x.grad.zero_() se gradients clear karne padte hain, warna galat purane values add hote rahenge aur tumhara training kharab ho jaayega. Ye samajhna kyun zaroori hai? Kyunki deep learning ka poora foundation isi automatic differentiation pe khada hai — bina iske tum bade networks efficiently train hi nahi kar sakte, aur ye concept tumhe har PyTorch project me kaam aayega.