The problem
Two ways to change a trained weight matrix of shape cheaply: add a small learned correction with LoRA (Low-Rank Adaptation), or remove the weights that matter least (pruning).
LoRA
Fine-tuning every weight of a model is expensive. LoRA freezes and learns a low-rank update: two thin matrices of shape and of shape , with a small (the rank, such as 8), scaled by :
where is the input and (alpha) a scaling constant. The thin matrices are tiny compared with , and the layer can be used with them as they are, or merged: computed once, so the layer then costs exactly what the original cost.
Example. , , , , (the number of columns of ).
- , so .
- For : ; , then , times is : the adapted layer gives .
- The merged layer: . The same.
Why compute (x @ A) @ B and not x @ (A @ B)? has only numbers, so the whole adapted layer costs extra, not a product of two big matrices.
Magnitude pruning
Set the fraction sparsity of weights with the smallest absolute value to zero.
Example. with
sparsity = 0.5:int(0.5 * 6) = 3weights are zeroed, the ones with the smallest : , and . The result is .
What to implement
lora_forward(x, W, A, B, alpha): the adapted layer, without merging. The rank isA.shape[1].merge_lora(W, A, B, alpha): the merged matrix .magnitude_prune(W, sparsity): a copy ofWin which theint(sparsity * W.size)entries with the smallest absolute values are 0.
Requirements
merge_loramust not modifyW, andx @ merge_lora(...)must equallora_forward(...).- In
lora_forward, compute(x @ A) @ B, never the full : that is what makes LoRA training cheap. magnitude_prunezeroes exactlyint(sparsity * W.size)entries (break ties by position:np.argsort(np.abs(W), axis=None, kind="stable")).
Hint
np.argsort(..., axis=None) sorts the flattened array and returns the positions in the flat order; out.flat[positions] = 0.0 writes zeros there.