Implementing Linear Layers in PyTorch
A LoRA layer is a PyTorch module with two learnable matrices, A and B, and a scaling factor alpha, and its forward method computes alpha times x @ A @ B.
Predicting the First Output of a LoRA Layer
This article starts from a working PyTorch class rather than from a definition. You will read the class once, predict how it behaves, and then check that prediction against the explanation. The goal is a mental model you can test against the code, so that when a LoRA layer produces an unexpected result you already know which line to examine first.
A LoRA layer, in the form used here, is a PyTorch module with two learnable matrices called A and B and a scalar setting called alpha. The module takes an input, passes it through A and then through B, scales the result by alpha, and returns it. Everything else in this article explains why those few lines are arranged the way they are.
What do you think happens?
A LoRA layer has just been constructed, and no training step has run yet. What does the layer return the first time it is called?
Reveal answer
A vector of zeros, so the adaptation adds nothing to the pretrained output.
The answer depends on one line in the constructor: B is filled with zeros. Since the forward computation multiplies by B, the LoRA output is zero at creation time. The rest of this article explains why that choice was deliberate.
Reading the Constructor: A, B, and alpha
Two details of the constructor matter most. The first is the pair of shapes: A is in_dim by rank, and B is rank by out_dim. Multiplying x by A and then by B only works because the inner dimension of the first product matches the outer dimension of the second, and that shared inner size is the rank. The second is that A and B receive different initial values, which the next sections examine closely.
- 1The class inherits from nn.Module, so PyTorch tracks the parameters it registers.
- 2
self.Ais wrapped in nn.Parameter, which marks it as learnable, and its values are drawn with Kaiming uniform initialization. - 3
self.Bis also a learnable parameter, but it is created with torch.zeros, so every entry starts at zero. - 4alpha is stored as a plain setting. It scales the output but does not add entries to A or B.
- 5forward multiplies x by A, then by B, then scales the product by alpha, and returns it.
- A is a learnable parameter with shape
in_dimby rank, filled with Kaiming uniform values - B is a learnable parameter with shape rank by
out_dim, filled with zeros - alpha is a scalar setting, not a learned matrix
- forward computes alpha times x @ A @ B, so the input passes through A, then B, then is scaled
The class is a compact expression of the forward computation alpha times x @ A @ B. The matrices A and B are the learned part of the adaptation, and alpha is the dial that controls how much their result is allowed to affect the output. Because the class is short, you can follow one input through it line by line, which is exactly what the next sections do.
Why B Starts at Zero
The A matrix is filled with random values drawn using Kaiming uniform initialization, the same scheme PyTorch uses for Linear layers. Random values in A give the layer a non-trivial starting point, so gradients can flow into it during training. The B matrix, by contrast, is filled with zeros.
Combining these two choices gives a starting state in which the adaptation's output is exactly zero. Because the forward computation multiplies x by A and then by B, any zero in B makes the whole product zero, regardless of what A contains. The layer begins by adding nothing to the pretrained output and then learns how much to add. Initializing both matrices randomly would instead inject arbitrary noise into the model from the first step.
Tracing Shapes Through A and B
Follow one input through the forward method. The input x enters A, the result enters B, and that result is multiplied by alpha. The diagram below shows each step as one operation, and the nodes show the width of the value after each step.
The rank-sized intermediate value is the only place where the input is compressed. The input is first projected down to rank values and then projected back up to out_dim. Because the rank is usually much smaller than the input and output sizes, the adaptation stays small. The final shape always matches out_dim, so the LoRA output can be combined with the output of a layer of that width.
Rank Versus Alpha
Rank and alpha are the two settings that most affect a LoRA layer, and they do different jobs. The rank sets the inner dimension shared by A and B, and that single number determines how many extra parameters LoRA adds to a layer. The source describes this as a balance: a larger rank gives the adaptation more capacity to change the layer, at the cost of more parameters, while a smaller rank keeps the layer cheap but limits what it can represent.
Alpha works differently. Rank decides how many parameters exist, while alpha decides how strongly the low-rank output is allowed to influence the result. In the class, alpha multiplies the whole adaptation result. Doubling alpha doubles the contribution of the LoRA path to the output while leaving A and B exactly as they are. A small alpha keeps the adaptation quiet, and a large alpha lets it dominate. Alpha is therefore a dial on the strength of the update, not a dial on its size in parameter terms.
| Setting | What it controls | Adds parameters? | Changes the LoRA output how? |
|---|---|---|---|
| rank | Inner dimension shared by A and B | Yes, in_dim times rank plus rank times out_dim | Changes capacity, and the width of the intermediate value |
| alpha | Scale on the whole adaptation result | No | Multiplies the contribution without changing A or B |
How rank and alpha differ
Combining the LoRA Output with the Pretrained Layer
A LoRA layer is not usually used alone. The typical goal is to add it to an existing Linear layer, so that weight updates are applied to the pretrained weights rather than replacing them. The original layer computes its output from the pretrained weights W. The LoRA layer computes a low-rank approximation of the weight update, which the source writes as delta W. The final output is the sum of the two results.
Both results are computed for the same input, and they meet at the summing step. Read this way, the adapted layer is the original layer plus a correction. The pretrained weights remain intact, and the learned part lives in A and B. When B is still zero, the correction term is zero and the adapted layer returns exactly the pretrained output.
Common Misreadings of the LoRA Layer
Most LoRA bugs in this implementation come from a small number of misreadings. Each mistake below shows where the symptom appears and which part of the reasoning to check.
Common mistake Expecting a new LoRA layer to change the model's output immediately
What it looks likeA freshly created adaptation produces a different prediction from the pretrained layer before any training has run.
Why it failsB starts at zero, so the LoRA output is zero and the final output equals the pretrained output.
Do this insteadCheck whether B has been changed. If the output differs before training, look at the initialization of B first.
Common mistake Treating alpha as the number of extra parameters
Why it failsAlpha is a scaling factor applied to the whole adaptation result. It does not add any entries to A or B.
Do this insteadCount parameters from in_dim, out_dim, and rank. Use alpha to reason about how strongly the adaptation is applied.
Common mistake Thinking a larger rank only makes the update stronger
Why it failsRank sets the inner dimension shared by A and B, so it changes the parameter count and capacity. Strength is controlled by alpha.
Do this insteadIncrease rank when more capacity is needed, and adjust alpha when the size of the contribution is the concern.
Common mistake Expecting the LoRA output to have rank-sized width
What it looks likeSomeone looks for a rank-wide output and finds a mismatch when adding it to the pretrained output.
Why it failsRank is only the intermediate size. The output after B always has
out_dimvalues.Do this insteadTrace the shapes: in_dim to rank through A, then rank to out_dim through B.
Common mistake Replacing the pretrained layer instead of adding to it, which discards the original weights rather than learning a correction
Counting Parameters for a Small LoRA Layer
A LoRA layer has in_dim 10, out_dim 6, and rank 2. How many parameters does the adaptation add, and what is the width of its output? Then explain what happens to the LoRA output if B is still all zeros.
Hints
- A has
in_dimtimes rank entries, and B has rank timesout_dimentries - The output width is set by B, not by the rank
- Zeros in B make the product x @ A @ B zero, regardless of A
Key Takeaways
- A LoRA layer is a PyTorch module with learnable matrices A (
in_dimby rank) and B (rank byout_dim) and a scalar alpha, and its forward method computes alpha times x @ A @ B. - B is initialized to zeros while A is filled with Kaiming uniform values, so a new adaptation contributes nothing to the first output and then learns how much to add.
- Rank sets the inner dimension shared by A and B and therefore the number of extra parameters, while alpha scales the adaptation's output without adding parameters.
- A LoRA layer is typically added to an existing Linear layer, and the final output is the original output plus the LoRA output, which approximates the weight update delta W.