Base Model Freezing Semantics and Gradient Path Isolation
Agent-Assisted Engineering Check
Prerequisites
Day 64 LoRA forward equation and PyTorch autograd mechanics.
Learning Objective
Understand autograd graph construction during LoRA training: trace why base parameters require no gradients while adapter parameters receive valid backpropagation updates.
Instructions
1. Write the forward pass with its two paths: y = W0 @ x + (alpha/r) * B @ A @ x. The first path uses only the frozen W0; the second uses the trainable A (r x d_in) and B (d_out x r).
2. Freeze W0 with requires_grad_(False) before the forward pass. Autograd then records no gradient edge into W0 and never allocates W0.grad.
3. Derive the adapter gradients: dL/dB = (alpha/r) * (dL/dy) @ (A @ x).T and dL/dA = (alpha/r) * B.T @ (dL/dy) @ x.T. Note the shapes.
4. Predict what happens at step 0, when B is initialized to zero: which of the two gradients is exactly zero?
5. Run one backward pass and assert the three gradient facts from the prompt, then check your prediction from step 4.
6. Count the gradient memory you did not allocate: frozen parameters times 2 bytes in bf16 (or 4 bytes in fp32).
Time plan (120 min): reading 25 + exercise 65 + check 30. Reading: Hu et al. Section 4.1 (the LoRA update and its initialization) and the PEFT LoRA conceptual guide. Exercise: steps 1-6 with the reference model. Check: explain without notes why lora_A.grad is zero at step 0.
Practice & Prediction Log
Instantiate a model with a frozen linear layer and a trainable LoRA adapter. Run a backward pass. Assert that model.linear.weight.grad is None while lora_A.grad and lora_B.grad are non-None tensors.
Use for name, param in model.named_parameters(): check param.grad.
import torch
import torch.nn as nn
class TinyLoRA(nn.Module):
def __init__(self, d_in=8, d_out=4, r=2, alpha=4):
super().__init__()
self.linear = nn.Linear(d_in, d_out, bias=False)
self.linear.weight.requires_grad_(False) # freeze W0
self.lora_A = nn.Parameter(torch.randn(r, d_in) * 0.01)
self.lora_B = nn.Parameter(torch.zeros(d_out, r)) # B = 0, so the adapter starts as a no-op
self.scale = alpha / r
def forward(self, x):
return self.linear(x) + self.scale * (x @ self.lora_A.T @ self.lora_B.T)
torch.manual_seed(0)
model = TinyLoRA()
x, target = torch.randn(5, 8), torch.randn(5, 4)
loss = nn.functional.mse_loss(model(x), target)
loss.backward()
assert model.linear.weight.grad is None # no .grad buffer was ever allocated
assert model.lora_A.grad is not None
assert model.lora_B.grad is not None
# Surprise worth recording: at step 0, dL/dA = scale * B.T @ dL/dy @ x is exactly zero because
# B = 0, while dL/dB = scale * dL/dy.T @ (x @ A.T) is not. A receives signal only after B moves.
assert torch.count_nonzero(model.lora_A.grad) == 0
assert torch.count_nonzero(model.lora_B.grad) > 0
trainable = [n for n, p in model.named_parameters() if p.requires_grad]
frozen = sum(p.numel() for p in model.parameters() if not p.requires_grad)
print(trainable, frozen) # ['lora_A', 'lora_B'] 32
# Saving: 32 frozen weights x 4 bytes = 128 bytes of fp32 gradient that is never allocated;
# for a 7B base in bf16 the same rule saves 7e9 x 2 bytes = 14 GB.
Confirm gradient isolation between frozen base weights and trainable adapter matrices.
Authoritative Source Resources & Provenance
LoRA: Low-Rank Adaptation of Large Language Models
Relevant Section / Range: Section 4.2 Applying LoRA to Transformer (frozen W0, VRAM and checkpoint-size reduction)
Verified Version & Date: ICLR 2022 / arXiv:2106.09685v2 (Verified: 2026-10-30)
Primary literature introducing Low-Rank Adaptation, establishing parameter decomposition W = W0 + (alpha/r)*BA, initialization strategies, and zero inference latency merging.
Matrix factorization, rank, linear layers, and transformer attention.
Complete matrix derivations, tensor dimensions, and pure PyTorch LoRALinear modules are implemented directly in lesson code.
Compared Candidate Sources & Selection Analysis (2)
Introduces bottleneck adapter layers in series, which introduces unavoidable inference latency overhead; LoRA's parallel formulation merges with zero latency.
Prepends virtual prompt tokens; reduces available context length, unlike LoRA which preserves full sequence capacity.
PEFT: State-of-the-art Parameter-Efficient Fine-Tuning Methods
Relevant Section / Range: README 'Quickstart' (LoraConfig, get_peft_model, print_trainable_parameters)
Verified Version & Date: PEFT 0.10.x / GitHub (Verified: 2026-10-30)
Widely adopted open-source library implementing LoRA configuration classes (LoraConfig), get_peft_model, and adapter serialization.
PyTorch nn.Module, Hugging Face transformers, and LoRA equations.
Standalone, zero-dependency PyTorch implementations of LoRALinear layers and save/reload logic are provided in curriculum scripts.
Compared Candidate Sources & Selection Analysis (2)
Original reference code by Hu et al.; PEFT expands this to broader multi-model ecosystem integration.
High-performance clean implementation; compared alongside PEFT for modular code architecture.