AdaLoRA: Adaptive Rank Allocation for Efficient Fine-Tuning

Michael BrenndoerferDecember 5, 202561 min read

Explains how AdaLoRA dynamically allocates rank budgets across weight matrices using SVD parameterization and importance scoring for efficient model adaptation.

Language AI HandbookAll chapters
Part 35 of 67Parameter-Efficient Fine-tuning
Chapter 260AdaLoRA › AdaLoRA: Adaptive Low-Rank Adaptation
Reading the full chapter · 11 sections · 61 min11 sections · 61 min

All 11 sections · 61 min read

AdaLoRA › AdaLoRA: Adaptive Low-Rank AdaptationFull chapter · Sections in this chapter1 / 11 ↓
Track your reading progress

Sign in to mark sections as read and track your learning journey

Sign in →
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

AdaLoRA: Adaptive Low-Rank Adaptation

Standard LoRA applies the same rank rr to every adapted weight matrix in a model. A query projection receives the same adaptation capacity as a value projection, and attention layers receive the same budget as feed-forward layers. This uniform allocation ignores a basic insight: different weight matrices contribute differently to task performance. Some weight matrices require substantial adaptation while others barely need any adjustment from their pretrained values. Allocating the same rank to every matrix is a bit like giving every employee on a team an equal budget regardless of how central their role is to the project outcome. The result is systematic waste: too many parameters where they aren't needed, not enough where they are.

Think of a transformer model as a large organization with many specialized departments. The pretrained model has already learned general-purpose skills in every department. When you fine-tune for a specific task, say sentiment analysis, some departments will need extensive retraining (perhaps the attention heads that focus on emotionally charged words), while others can largely coast on what they already know (perhaps the feed-forward layers that handle syntactic structure, which changes little between general language and sentiment tasks). Giving every department the same retraining budget wastes resources on departments that need little change and starves departments that need the most.

AdaLoRA (Adaptive Low-Rank Adaptation) addresses this limitation by dynamically allocating rank budgets across weight matrices during training. Rather than fixing ranks beforehand, AdaLoRA starts with a high rank for all matrices, measures which components contribute most to performance, and progressively prunes the less important ones. The result is an intelligent distribution of adaptation capacity: more rank where it matters, less where it doesn't, all while staying within a fixed total parameter budget. The algorithm in effect performs a form of neural architecture search, discovering the optimal rank allocation automatically rather than requiring expensive manual experiments.

The key insight is that SVD (Singular Value Decomposition) gives a natural measure of component importance. When you decompose a weight update into singular value triplets, each triplet occupies an independent direction in the weight space. The singular value associated with each triplet directly measures how much that component contributes to the overall update. By parameterizing weight adaptations in this SVD-inspired form, AdaLoRA can prune individual components cleanly, without the entanglement that makes pruning difficult in standard matrix factorizations.

AdaLoRA was introduced by Zhang et al. in 2023 as part of the growing ecosystem of parameter-efficient fine-tuning methods. It builds on the theoretical foundations of LoRA, which we covered in the previous chapter, but extends the approach with a principled mechanism for heterogeneous rank allocation. The technique has proven particularly effective in resource-constrained settings where you need the best possible performance from a strictly limited parameter budget, such as deploying multiple fine-tuned variants of a large model simultaneously on shared hardware.

This chapter covers the technical foundations of AdaLoRA in depth: how its SVD-based parameterization allows fine-grained pruning, how importance scores identify which components to keep, and how the training procedure orchestrates dynamic rank allocation from beginning to end. Building on the LoRA fundamentals from the previous chapter, you will see how a carefully chosen parameterization makes adaptation substantially more efficient. We will also work through a concrete numerical example to develop intuition for how the pruning decisions unfold, and explore the practical trade-offs that determine when AdaLoRA is worth the extra complexity.

Historical Context

AdaLoRA was introduced by Qingru Zhang, Minshuo Chen, Alexander Backurs, Nikos Papailiopoulos, Dimitris Papailiopoulos, Tuo Zhao, and others in the 2023 paper "Adaptive Budget Allocation for Parameter-Efficient Fine-Tuning." The work appeared at ICLR 2023 and addressed a clear gap in the LoRA literature: while LoRA had shown that low-rank adapters could match full fine-tuning performance, practitioners had observed that uniform rank allocation was often suboptimal. The authors were motivated by empirical observations showing that different weight matrices in BERT and GPT-2 have sharply different sensitivity to fine-tuning, with query and value projections generally being much more important than feed-forward projections for many NLP tasks. This observation suggested that the fixed-rank constraint in standard LoRA was leaving significant performance on the table. AdaLoRA provided a principled solution grounded in the mathematics of singular value decomposition, and its ideas about importance-weighted pruning have since influenced a broader family of adaptive PEFT methods.

SVD-Based Parameterization

The foundation of AdaLoRA is a change in how weight updates are represented. To understand why this change matters, we need to first understand what goes wrong with standard LoRA when you try to prune individual components.

Recall from our LoRA discussion that standard LoRA is weight updates as:

ΔW=BA\Delta W = BA

where:

  • BB: the downstream projection matrix of dimension d×rd \times r
  • AA: the upstream projection matrix of dimension r×dinr \times d_{\text{in}}

This product produces a rank-rr matrix, but the individual columns of BB and rows of AA are entangled: you cannot remove one column-row pair without affecting the entire product's behavior unpredictably. To understand why this entanglement poses a problem, consider what happens when you try to prune a component. If you remove the third column of BB and the third row of AA, the remaining columns and rows still interact in complex ways during the matrix multiplication. The contribution of each column-row pair depends on all the others, which makes it impossible to cleanly isolate and evaluate individual components.

Think of standard LoRA's BABA product as a team project where everyone's contributions are mixed together in the final report. If you want to remove one team member's contribution, you cannot simply cross out their paragraphs because their ideas are woven throughout everyone else's writing. You would need to rewrite the entire document to fairly assess what would be lost. SVD-based parameterization solves this by making each contributor's work self-contained in its own dedicated section, so that removing one section has no effect on the others.

AdaLoRA adopts an SVD-inspired parameterization that decouples these components and resolves the entanglement problem:

ΔW=PΛQ\Delta W = P \Lambda Q

where:

  • P∈Rd×rP \in \mathbb{R}^{d \times r} contains orthonormal columns (left singular vectors)
  • Λ=diag(λ1,λ2,…,λr)\Lambda = \text{diag}(\lambda_1, \lambda_2, \ldots, \lambda_r) is a diagonal matrix of singular values
  • Q∈Rr×dinQ \in \mathbb{R}^{r \times d_{\text{in}}} contains orthonormal rows (right singular vectors)

This parameterization resembles a truncated SVD, though PP, Λ\Lambda, and QQ are learned rather than computed from an existing matrix. The key advantage is separability: each triplet (P:,i,λi,Qi,:)(P_{:,i}, \lambda_i, Q_{i,:}) contributes independently to the weight update. This independence arises from the orthonormality constraints on PP and QQ. When the columns of PP are orthogonal to each other, and the rows of QQ are orthogonal to each other, each singular value triplet occupies its own distinct "direction" in the weight update space. Removing one triplet leaves the others completely unchanged, letting precise surgical pruning of adaptation components.

SVD Decomposition

Singular Value Decomposition expresses any matrix MM as UΣVTU \Sigma V^T, where UU and VV have orthonormal columns and Σ\Sigma is diagonal with non-negative singular values. The singular values appear in decreasing order: σ1≥σ2≥⋯≥σr≥0\sigma_1 \geq \sigma_2 \geq \cdots \geq \sigma_r \geq 0. The largest singular values correspond to the most "important" directions in the transformation, in the sense that they capture the most variance. A truncated SVD keeping only the top kk singular values gives the best rank-kk approximation to the original matrix in the Frobenius norm sense. AdaLoRA uses a similar structure but learns the components directly through gradient descent, letting negative singular values and soft rather than exact orthonormality. This makes the parameterization more flexible than a true SVD while retaining the key separability property.

The contribution of the ii-th component to the weight update can be written as an outer product scaled by the singular value:

ΔWi=λi⋅P:,i⋅Qi,:\Delta W_i = \lambda_i \cdot P_{:,i} \cdot Q_{i,:}

where:

  • ΔWi∈Rd×din\Delta W_i \in \mathbb{R}^{d \times d_{\text{in}}}: the rank-1 matrix contributed by the ii-th triplet
  • λi∈R\lambda_i \in \mathbb{R}: the scalar singular value controlling this component's magnitude
  • P:,i∈RdP_{:,i} \in \mathbb{R}^{d}: the ii-th column of PP, a unit vector in the output space
  • Qi,:∈RdinQ_{i,:} \in \mathbb{R}^{d_{\text{in}}}: the ii-th row of QQ, a unit vector in the input space

The outer product P:,i⋅Qi,:P_{:,i} \cdot Q_{i,:} creates a rank-1 matrix where each entry (a,b)(a, b) equals Pa,i⋅Qi,bP_{a,i} \cdot Q_{i,b}. This matrix captures one particular "pattern" of influence: how the input direction captured by Qi,:Q_{i,:} maps to the output direction captured by P:,iP_{:,i}. Multiplying by the scalar λi\lambda_i scales the entire pattern's magnitude. This formulation makes pruning straightforward: setting λi=0\lambda_i = 0 completely removes this component from the update without affecting the others. The total weight update is the sum of all rank-1 contributions:

ΔW=∑i=1rΔWi=∑i=1rλi⋅P:,i⋅Qi,:\Delta W = \sum_{i=1}^{r} \Delta W_i = \sum_{i=1}^{r} \lambda_i \cdot P_{:,i} \cdot Q_{i,:}

This is exactly how SVD works conceptually: the full matrix is reconstructed as a weighted sum of rank-1 outer products, with the singular values serving as the weights. Components with larger ∣λi∣|\lambda_i| contribute more to the final update; components with λi=0\lambda_i = 0 contribute nothing at all. This property is what makes importance-based pruning tractable: we can evaluate and remove individual components rather than analyzing the entire matrix structure.

VisualizationsOut [3]
CollapseExpand
Heatmap of the first rank-1 matrix contribution scaled by lambda=0.8, showing the dominant pattern in the weight update decomposition.
Rank-1 component from the first singular value triplet $(\lambda_1=0.8, P_{:,1}, Q_{1,:})$, the dominant contribution to the weight update.
Heatmap of the second rank-1 matrix contribution scaled by lambda=0.4, showing a moderate pattern in the weight update decomposition.
Rank-1 component from the second singular value triplet $(\lambda_2=0.4, P_{:,2}, Q_{2,:})$, a moderate contribution to the weight update.
Heatmap of the third rank-1 matrix contribution scaled by lambda=0.1, showing the smallest pattern in the weight update decomposition.
Rank-1 component from the third singular value triplet $(\lambda_3=0.1, P_{:,3}, Q_{3,:})$, the smallest contribution to the weight update.
Heatmap of the aggregated weight update matrix delta W, formed by summing all three rank-1 component matrices.
Aggregated weight update $\Delta W = \sum_i \lambda_i P_{:,i} Q_{i,:}$, the sum of all three independent rank-1 components.

The visualization above illustrates why this decomposition is so powerful. Each of the first three panels shows a single rank-1 pattern scaled by its singular value. The patterns are orthogonal to each other (they do not overlap in any systematic way), which is what the orthonormality constraints enforce. The fourth panel shows their sum. Because the components are orthogonal, their combined effect is simply additive: removing any one component produces a predictable, well-defined change in the final matrix. This is not the case in standard LoRA's BABA factorization, where the individual columns of BB are not constrained to be orthogonal and can develop complex correlations during training.

Orthonormality Constraints

For the decomposition to maintain its SVD-like properties, PP and QQ should have orthonormal columns and rows respectively. Orthonormality means two things simultaneously: each column of PP should have unit length (normality), and different columns should be perpendicular to each other (orthogonality). The same applies to the rows of QQ. These constraints ensure that the singular value triplets remain independent and that the singular values λi\lambda_i directly stand for the magnitude of each component's contribution.

If the orthonormality constraints were violated, the interpretation of the singular values would break down. A column of PP that is twice as long as intended would make the corresponding λi\lambda_i appear twice as important as it is. Two correlated columns of PP would make their corresponding components appear redundant when they might be capturing complementary directions. The orthonormality constraints give the singular values a direct interpretive role as a measure of each component's importance.

During training, AdaLoRA enforces orthonormality approximately through a regularization term added to the training objective. Specifically, the optimizer minimizes the task loss plus a penalty for deviating from orthonormality:

R(P,Q)=∥PTP−I∥F2+∥QQT−I∥F2R(P, Q) = \|P^T P - I\|_F^2 + \|QQ^T - I\|_F^2

where:

  • R(P,Q)R(P, Q): the orthonormality regularization term
  • ∥⋅∥F\|\cdot\|_F: the Frobenius norm, which sums the squares of all matrix entries
  • II: the identity matrix
  • PTP−IP^T P - I: the deviation of PP's column Gram matrix from the identity
  • QQT−IQQ^T - I: the deviation of QQ's row Gram matrix from the identity

To see why these expressions capture orthonormality, consider the matrix PTPP^T P. Its (i,j)(i,j) entry is P:,iTP:,jP_{:,i}^T P_{:,j}, the dot product of column ii and column jj. If PTP=IP^T P = I, then diagonal entries equal 1 (unit length) and off-diagonal entries equal 0 (mutual orthogonality). The penalty ∥PTP−I∥F2\|P^T P - I\|_F^2 is zero only when PP is perfectly column-orthonormal, and grows larger the more PP deviates from this ideal. Similarly, QQTQQ^T captures the dot products between different rows of QQ, and the penalty drives QQ toward row-orthonormality.

The total training objective combines the task loss with the orthogonality regularization:

Ltotal=Ltask+γ⋅R(P,Q)\mathcal{L}_{\text{total}} = \mathcal{L}_{\text{task}} + \gamma \cdot R(P, Q)

where γ\gamma is the orthogonality regularization weight (orth_reg_weight in the PEFT implementation, typically around 0.5). The hyperparameter γ\gamma controls the trade-off: higher values enforce stricter orthonormality at the cost of potentially interfering with task loss minimization, while lower values allow more deviation in exchange for greater flexibility in fitting the task.

This soft constraint offers a practical trade-off between mathematical purity and computational efficiency. Hard orthonormality (using Gram-Schmidt or similar projection methods) would require expensive projections after each gradient step, fundamentally changing the optimization problem and adding significant compute overhead. The regularization approach allows the standard optimizer (typically AdamW) to handle orthonormality as just another objective, balancing it against the task loss naturally. The optimizer can temporarily violate orthonormality if doing so helps reduce the task loss, then gradually restore the constraint as training progresses. This flexibility often leads to better final solutions than rigid enforcement would allow, and it means the implementation requires no specialized optimization machinery beyond a standard gradient-based optimizer.

VisualizationsOut [4]
CollapseExpand
Heatmap of P-transpose times P without regularization, with bright off-diagonal entries showing correlated columns and diagonal values deviating from one.
$P^T P$ without regularization, showing non-unit diagonal entries and non-zero off-diagonal correlations between columns.
Heatmap of P-transpose times P with regularization, showing near-identity structure with values close to one on the diagonal and near-zero off-diagonal.
$P^T P$ with orthonormality regularization applied, showing columns pushed toward unit length and mutual orthogonality.
Heatmap of the identity matrix showing the ideal target for orthonormal columns: ones on the diagonal and zeros elsewhere.
Target identity matrix $I$ representing perfect column orthonormality, with unit diagonals and zero off-diagonals.

The visualization above shows the effect of the orthonormality regularization in practice. Without regularization, the Gram matrix PTPP^T P has off-diagonal entries that differ substantially from zero, showing that different columns of PP are correlated and therefore not truly independent. The diagonal entries also deviate from one, meaning the columns do not have unit length. With regularization, the Gram matrix approaches the identity: diagonal entries are close to 1 and off-diagonal entries are close to 0. The result is that the singular value triplets become independent, and the singular values acquire a direct importance interpretation.

Importance Scoring

The heart of AdaLoRA is deciding which singular value triplets to keep and which to prune. This requires an importance score that shows each component's real contribution to model performance. Designing a good importance score turns out to be more subtle than it might first appear.

Think of importance scoring as trying to identify which employees on a team are truly needed. You have two types of information available: how much work each employee is currently creating (magnitude), and how much the team's output would suffer if they were suddenly absent (sensitivity). An employee who produces a lot but would be easily replaced by others contributes less than their output suggests. An employee who produces less but is an irreplaceable coordinator, whose absence would cascade into major disruptions, is more important than their direct output implies. The best measure of importance combines both signals.

In the context of singular value triplets, the magnitude corresponds to ∣λi∣|\lambda_i|: larger singular values contribute more to the weight update's norm. The sensitivity corresponds to how much the training loss would increase if that triplet were zeroed out. Together, these signals identify components that are both currently contributing and would be missed if removed.

Magnitude and Sensitivity

A naive approach would rank components by ∣λi∣|\lambda_i| alone: larger singular values contribute more to the weight update's magnitude. This makes intuitive sense because the singular value directly scales the rank-1 contribution. However, magnitude alone misses important information about gradient flow. A large singular value with near-zero gradients is stable and well-fitted: it has found its optimal value and further gradient-driven changes would not improve performance. A smaller singular value with large gradients is actively being adjusted and may be necessary for continued learning, representing a component that the optimization process is still working to refine.

Consider two extremes. A singular value of 1.0 with gradient magnitude 0.001 has high magnitude but low sensitivity: removing it would change the weight update substantially, but the gradient signal suggests the loss is not currently sensitive to this component's value. A singular value of 0.1 with gradient magnitude 0.5 has low magnitude but high sensitivity: removing it would cause a small direct change to the weight update, but the large gradient indicates the loss is highly responsive to this component and pruning it would cause significant performance degradation.

AdaLoRA combines both signals into an importance score that captures current contribution and learning dynamics simultaneously:

Si=sˉi⋅∣λi∣S_i = \bar{s}_i \cdot |\lambda_i|

where:

  • SiS_i: the importance score for the ii-th singular value triplet
  • sˉi\bar{s}_i: the smoothed sensitivity estimate derived from gradient information
  • ∣λi∣|\lambda_i|: the absolute magnitude of the singular value

The product structure ensures that both factors must be substantial for a component to receive a high importance score. A component is considered important only if it both currently contributes to the weight update (non-trivial magnitude) and the model would suffer if it were removed (non-trivial sensitivity). Components that are large but insensitive, or sensitive but small, receive lower scores than components that are both large and sensitive. This combination balances two complementary views of importance and guards against the failure modes of either metric alone.

VisualizationOut [5]
CollapseExpand
Scatter plot of singular value components plotted by magnitude on the x-axis and gradient sensitivity on the y-axis, with color showing combined importance score; lighter points in upper-right quadrant have highest importance.
Importance scoring based on magnitude and gradient sensitivity. Components with high magnitude but low sensitivity (top-left) stand for stable features, while those with low magnitude but high sensitivity (bottom-right) indicate active learning. AdaLoRA prioritizes components with high combined importance scores (lighter colors) to retain the most necessary adaptation parameters.

The dashed contour lines in the plot stand for iso-importance curves: all components along a given contour have the same importance score. A component at (magnitude=1.0, sensitivity=0.3) has the same importance score as a component at (magnitude=0.3, sensitivity=1.0). Components below the lowest contour line are prime pruning candidates regardless of which dimension they fail on. The plot also shows that the "Prune" region in the lower-left corner, where both magnitude and sensitivity are small, clearly deserves pruning, while the upper-right region contains the components worth retaining.

Sensitivity Estimation

The sensitivity sis_i for singular value λi\lambda_i measures how much the training loss would change if that component were removed. We compute it using a first-order Taylor approximation of the loss function.

To formalize this, consider the loss L\mathcal{L} as a function of the singular value λi\lambda_i. We want to estimate the loss change ΔL\Delta \mathcal{L} that would result from setting λi\lambda_i to zero (pruning the component). The first-order Taylor expansion around the current value λi(t)\lambda_i^{(t)} gives:

L(λi=0)≈L(λi(t))+∂L∂λi(t)⋅(0−λi(t))\mathcal{L}(\lambda_i = 0) \approx \mathcal{L}(\lambda_i^{(t)}) + \frac{\partial \mathcal{L}}{\partial \lambda_i^{(t)}} \cdot (0 - \lambda_i^{(t)})

The change in loss from pruning is therefore:

ΔL=L(λi=0)−L(λi(t))≈∂L∂λi(t)⋅(0−λi(t))=−λi(t)⋅∂L∂λi(t)\begin{aligned} \Delta \mathcal{L} &= \mathcal{L}(\lambda_i = 0) - \mathcal{L}(\lambda_i^{(t)}) \\ &\approx \frac{\partial \mathcal{L}}{\partial \lambda_i^{(t)}} \cdot (0 - \lambda_i^{(t)}) \\ &= -\lambda_i^{(t)} \cdot \frac{\partial \mathcal{L}}{\partial \lambda_i^{(t)}} \end{aligned}

Taking the absolute value (since we care about the magnitude of the change, not its direction) gives the instantaneous sensitivity estimate:

si(t)=∣λi(t)⋅∂L∂λi(t)∣s_i^{(t)} = \left| \lambda_i^{(t)} \cdot \frac{\partial \mathcal{L}}{\partial \lambda_i^{(t)}} \right|

where:

  • si(t)s_i^{(t)}: the instantaneous sensitivity at training step tt, estimating how much the loss would increase if component ii were pruned
  • λi(t)\lambda_i^{(t)}: the value of the singular value at step tt
  • ∂L∂λi(t)\frac{\partial \mathcal{L}}{\partial \lambda_i^{(t)}}: the gradient of the training loss with respect to λi\lambda_i at step tt

This quantity is sometimes called "saliency" in the neural network pruning literature, inspired by analogous formulas used for identifying important neurons and connections. We do not need to evaluate what would happen if we removed the component: we can estimate the effect using only quantities that are already computed during the standard backward pass. The gradient ∂L∂λi\frac{\partial \mathcal{L}}{\partial \lambda_i} is computed anyway as part of updating Λ\Lambda, so computing the sensitivity adds almost no overhead beyond a pointwise multiplication and absolute value.

To reduce noise from stochastic gradients, AdaLoRA maintains an exponential moving average of the sensitivity estimates across training steps:

sˉi(t)=β⋅sˉi(t−1)+(1−β)⋅si(t)\bar{s}_i^{(t)} = \beta \cdot \bar{s}_i^{(t-1)} + (1 - \beta) \cdot s_i^{(t)}

where:

  • sˉi(t)\bar{s}_i^{(t)}: the smoothed sensitivity estimate at step tt, averaging information over the training history
  • β\beta: the smoothing parameter, typically set around 0.85, controlling how much weight to give the historical average versus the new observation
  • si(t)s_i^{(t)}: the instantaneous sensitivity from the current step

This averaging stabilizes importance estimates across mini-batches, preventing premature pruning decisions based on noisy gradient samples. Because gradients computed on small mini-batches can vary materially from step to step, a single noisy gradient could incorrectly suggest that an useful component is unimportant, or vice versa. The exponential moving average smooths out this noise by giving weight to the entire history of gradient observations, with more recent observations weighted more heavily through the β\beta decay factor. The parameter β\beta controls the effective window: with β=0.85\beta = 0.85, roughly 11−β=6.7\frac{1}{1-\beta} = 6.7 recent steps receive large weight, while older steps fade away exponentially. Higher values of β\beta create smoother estimates that respond more slowly to real changes in importance; lower values track changes faster but are more susceptible to noise.

VisualizationOut [6]
CollapseExpand
Line plot showing raw noisy gradient sensitivity over training steps in gray alongside smoothed EMA estimates at different beta values, showing how higher beta values produce smoother importance score signals.
Smoothing of noisy sensitivity estimates using exponential moving averages at different beta values. Raw instantaneous sensitivity (gray trace) exhibits high variance due to mini-batch stochasticity. The smoothed estimates (colored lines) give stable signals for pruning decisions, with higher beta values creating greater noise reduction at the cost of slower response to real changes.

Full Importance Formula

When the orthonormality constraint is enforced only softly, the actual magnitude of each component's contribution to the weight update is not determined solely by λi\lambda_i but also depends on the norms of P:,iP_{:,i} and Qi,:Q_{i,:}. In the ideal SVD case where ∥P:,i∥2=1\|P_{:,i}\|_2 = 1 and ∥Qi,:∥2=1\|Q_{i,:}\|_2 = 1, the singular value alone captures the full magnitude. In practice, slight deviations from orthonormality mean that the vectors may have norms slightly different from one, which affect the actual magnitude of the corresponding rank-1 contribution.

To account for this, and to ensure that importance scores are fairly comparable across weight matrices with different dimensions, AdaLoRA uses a complete importance formula that incorporates the vector norms:

Si(t)=sˉi(t)⋅∣λi(t)∣⋅∥P:,i∥F⋅∥Qi,:∥Fd⋅dinS_i^{(t)} = \bar{s}_i^{(t)} \cdot |\lambda_i^{(t)}| \cdot \frac{\|P_{:,i}\|_F \cdot \|Q_{i,:}\|_F}{\sqrt{d} \cdot \sqrt{d_{\text{in}}}}

where:

  • Si(t)S_i^{(t)}: the final importance score used for global ranking at step tt
  • sˉi(t)\bar{s}_i^{(t)}: the smoothed sensitivity estimate from the exponential moving average
  • ∣λi(t)∣|\lambda_i^{(t)}|: the absolute magnitude of the current singular value
  • ∥P:,i∥F\|P_{:,i}\|_F: the Frobenius norm of the ii-th column of PP, correcting for column-length deviations from unit norm
  • ∥Qi,:∥F\|Q_{i,:}\|_F: the Frobenius norm of the ii-th row of QQ, correcting for row-length deviations from unit norm
  • d,dind, d_{\text{in}}: the output and input dimensions of the weight matrix, used for cross-layer normalization

The normalization by d\sqrt{d} and din\sqrt{d_{\text{in}}} is needed for fair comparison across layers. Without this normalization, layers with larger dimensions would naturally have larger vector norms (since the column of PP has dd entries and thus a norm that scales roughly as d\sqrt{d} for random initialization), potentially biasing the pruning toward keeping components in smaller layers regardless of their actual importance for the task.

When the orthonormality constraint is well-satisfied, ∥P:,i∥F≈1\|P_{:,i}\|_F \approx 1 and ∥Qi,:∥F≈1\|Q_{i,:}\|_F \approx 1, and the full formula simplifies to approximately sˉi(t)⋅∣λi(t)∣\bar{s}_i^{(t)} \cdot |\lambda_i^{(t)}|, recovering the intuitive product of sensitivity and magnitude. The full formula is thus a reliable version of this product that remains valid even when orthonormality is imperfectly enforced, which is always the case in practice.

Dynamic Rank Allocation

With importance scores in hand, AdaLoRA can allocate rank budgets across all weight matrices in the model. The basic challenge is deciding, at each pruning step, how many singular value triplets to keep in each layer. AdaLoRA's elegant solution is to treat this as a global competition: all triplets from all layers compete for slots in the total budget, and the highest-scoring triplets win.

Think of the rank allocation process as a talent competition across the entire organization. Each department (weight matrix) starts with the same number of "performers" (singular value triplets). As the competition progresses, performers are eliminated based on their importance scores, regardless of which department they come from. By the end, the best performers have survived, and they are naturally distributed across departments in proportion to how many truly talented individuals each department had. Departments with many important contributors end up with more slots; departments where most contributors were marginal end up with fewer.

This design has a important practical consequence: the algorithm discovers the optimal rank distribution automatically from the data and task, without requiring any domain knowledge about which layers are likely to be important. A researcher using standard LoRA must decide upfront whether to give higher ranks to attention layers, feed-forward layers, or specific projection types, typically based on intuition or expensive ablation studies. AdaLoRA makes this decision automatically and optimally given the available gradient information.

Global Ranking

Rather than allocating ranks layer-by-layer, AdaLoRA performs global ranking across all singular values in all adapted weight matrices simultaneously. If the model adapts LL weight matrices, each initialized with rank r0r_0, there are L⋅r0L \cdot r_0 total triplets. These are all ranked by their importance scores Si(t)S_i^{(t)}, and the top bb are retained, where bb is the current budget determined by the pruning schedule.

This global approach naturally handles heterogeneity across layers. If attention layers consistently show higher importance scores than feed-forward layers, they automatically receive higher effective ranks. No manual configuration is needed because the data determines the allocation. The algorithm also adapts to heterogeneity within a single matrix: if a weight matrix has five very important directions and five unimportant ones, it will end up with rank five, regardless of what rank was initially allocated to it.

An important subtlety is that the ranking is competitive across layer types and across specific triplets within the same layer. Within a single weight matrix, AdaLoRA can prune the less important singular value triplets while keeping the more important ones, effectively performing a matrix-specific rank reduction that is tailored to what the optimization has found. This within-layer adaptivity adds another dimension of flexibility that approaches like manually setting different ranks for different layer types cannot reach.

Pruning Schedule

Pruning doesn't happen all at once. Removing many components simultaneously could destabilize training, particularly early on when importance estimates are unreliable. Instead, AdaLoRA follows a cubic schedule that gradually reduces the total budget from the initial L⋅r0L \cdot r_0 to the final target bb:

b(t)=b+(L⋅r0−b)⋅(1−t−twT−tw)3b^{(t)} = b + (L \cdot r_0 - b) \cdot \left(1 - \frac{t - t_w}{T - t_w}\right)^3

where:

  • b(t)b^{(t)}: the effective rank budget available at training step tt (the number of triplets that should remain active)
  • bb: the final target budget, the total number of triplets that survive to the end of training
  • L⋅r0L \cdot r_0: the initial total budget, the number of triplets available at the start
  • tt: the current training step
  • twt_w: the warmup period steps, during which no pruning occurs
  • TT: the total number of training steps

For t<twt < t_w (during warmup), the budget remains at L⋅r0L \cdot r_0. For t≥twt \geq t_w, the budget follows the cubic decay toward bb. Let τ=t−twT−tw∈[0,1]\tau = \frac{t - t_w}{T - t_w} \in [0, 1] denote the fractional progress through the post-warmup phase. Then the budget is b+(L⋅r0−b)(1−τ)3b + (L \cdot r_0 - b)(1 - \tau)^3. At τ=0\tau = 0 (start of pruning), the budget is L⋅r0L \cdot r_0. At τ=1\tau = 1 (end of training), the budget is bb.

The cubic function (1−τ)3(1 - \tau)^3 is key to the schedule's shape. It starts with a large initial slope (when many obvious low-importance candidates exist and aggressive pruning is appropriate) and flattens as training progresses (when the remaining decisions are more consequential and should be made gradually). Compare this to a linear schedule (1−τ)(1 - \tau), which would prune at a constant rate, or a linear-then-stop approach. The cubic schedule is more natural because it mirrors the typical behavior of importance scores: early in training, there is a long tail of clearly unimportant components that can be safely removed in bulk; later in training, the surviving components are more competitive and should be pruned more carefully.

VisualizationOut [7]
CollapseExpand
Line plot of effective rank budget over training steps, with a shaded warmup region followed by a cubic decay curve from initial rank down to the target rank shown as a dashed horizontal line.
Cubic pruning schedule for rank budget reduction. The process begins with a warmup period (shaded region) where no pruning occurs, letting importance scores to stabilize. Subsequently, the budget follows a cubic decay trajectory, removing components aggressively in early stages and slowing down as the target budget is approached to preserve necessary parameters.

The warmup period twt_w is important for the reliability of pruning decisions. During warmup, no pruning occurs, letting importance estimates to stabilize. Pruning too early risks removing components that are important but haven't yet accumulated sufficient gradient statistics to show their value. In the first few steps of training, gradient estimates are particularly noisy because the model is simultaneously adjusting to the new task, and the individual λi\lambda_i values are starting from zero, meaning early gradients reflect initialization artifacts rather than true task requirements. The warmup period gives every component a fair chance to reveal its importance before any elimination decisions are made. A typical warmup period of 10-15% of total training steps is usually sufficient, though this depends on the task and dataset size.

Masking Mechanism

When a singular value triplet is pruned, AdaLoRA sets λi=0\lambda_i = 0 via a binary mask Mi∈{0,1}M_i \in \{0, 1\} rather than removing the parameters from memory. The actual weight update becomes:

ΔW=P⋅diag(M⊙λ)⋅Q\Delta W = P \cdot \text{diag}(M \odot \lambda) \cdot Q

where M⊙λM \odot \lambda denotes elementwise multiplication of the mask vector with the singular values vector. When Mi=0M_i = 0, the corresponding λi\lambda_i is zeroed out in the forward pass, effectively removing that component's contribution.

This masking approach serves two important purposes. First, it avoids dynamic memory reallocation during training, which would require complex bookkeeping to track which parameters are currently active. With fixed-size tensors and a masking layer, the computation graph remains static throughout training, making automatic differentiation straightforward. Second, it leaves open the possibility of reactivating pruned components: if a component's importance score rises substantially after being masked, a more advanced algorithm could unmask it. The standard AdaLoRA implementation does not implement this "revitalization" but the masking architecture makes it mechanistically possible.

The masking mechanism also has implications for understanding AdaLoRA's parameter efficiency. During training, all L⋅r0L \cdot r_0 triplets occupy memory regardless of how many are active. The memory cost during training is therefore proportional to the initial rank r0r_0, not the final target rank b/Lb/L. At inference time (after training), only the active triplets (those with non-zero singular values) matter for computation. The inactive triplets can be removed entirely, yielding a final model whose inference cost depends on the effective rank rather than the initial rank. This distinction between training memory cost and inference computation cost is worth keeping in mind when planning your hardware requirements.

Worked Example: Step-by-Step AdaLoRA Pruning

To build concrete intuition for how AdaLoRA's pruning decisions unfold, let's work through a small numerical example from first principles. We will consider a toy model with just two adapted weight matrices, each initialized at rank 3, with a target budget of 3 total triplets (meaning we need to prune half the components).

Setup: Two weight matrices W1W_1 and W2W_2, each with initial rank r0=3r_0 = 3. Total initial budget: 2×3=62 \times 3 = 6 triplets. Target budget: b=3b = 3 triplets. We are at some training step after the warmup period.

Step 1: Read current singular values and gradients.

For matrix W1W_1, suppose the current singular values and their gradients are:

  • Triplet (1,1)(1,1): λ1=0.80\lambda_1 = 0.80, ∂L∂λ1=−0.20\frac{\partial \mathcal{L}}{\partial \lambda_1} = -0.20
  • Triplet (1,2)(1,2): λ2=0.45\lambda_2 = 0.45, ∂L∂λ2=−0.60\frac{\partial \mathcal{L}}{\partial \lambda_2} = -0.60
  • Triplet (1,3)(1,3): λ3=0.10\lambda_3 = 0.10, ∂L∂λ3=−0.05\frac{\partial \mathcal{L}}{\partial \lambda_3} = -0.05

For matrix W2W_2:

  • Triplet (2,1)(2,1): λ4=0.70\lambda_4 = 0.70, ∂L∂λ4=−0.15\frac{\partial \mathcal{L}}{\partial \lambda_4} = -0.15
  • Triplet (2,2)(2,2): λ5=0.35\lambda_5 = 0.35, ∂L∂λ5=−0.40\frac{\partial \mathcal{L}}{\partial \lambda_5} = -0.40
  • Triplet (2,3)(2,3): λ6=0.05\lambda_6 = 0.05, ∂L∂λ6=−0.02\frac{\partial \mathcal{L}}{\partial \lambda_6} = -0.02

Step 2: Compute instantaneous sensitivities.

si(t)=∣λi⋅∂L∂λi∣s_i^{(t)} = \left| \lambda_i \cdot \frac{\partial \mathcal{L}}{\partial \lambda_i} \right|

For W1W_1:

  • s1=∣0.80×(−0.20)∣=∣−0.160∣=0.160s_1 = |0.80 \times (-0.20)| = |-0.160| = 0.160
  • s2=∣0.45×(−0.60)∣=∣−0.270∣=0.270s_2 = |0.45 \times (-0.60)| = |-0.270| = 0.270
  • s3=∣0.10×(−0.05)∣=∣−0.005∣=0.005s_3 = |0.10 \times (-0.05)| = |-0.005| = 0.005

For W2W_2:

  • s4=∣0.70×(−0.15)∣=∣−0.105∣=0.105s_4 = |0.70 \times (-0.15)| = |-0.105| = 0.105
  • s5=∣0.35×(−0.40)∣=∣−0.140∣=0.140s_5 = |0.35 \times (-0.40)| = |-0.140| = 0.140
  • s6=∣0.05×(−0.02)∣=∣−0.001∣=0.001s_6 = |0.05 \times (-0.02)| = |-0.001| = 0.001

Notice that triplet (1,2)(1,2) (the second component of W1W_1) has the highest sensitivity despite not having the largest magnitude. This is because its gradient is large in magnitude (−0.60-0.60), showing the loss is sensitive to this component's value. Triplet (1,1)(1,1) has a larger magnitude but smaller gradient, suggesting it has already settled near a good value.

Step 3: Update exponential moving averages (assuming previous smoothed estimates).

Suppose the previous smoothed estimates were sˉi(t−1)=[0.15,0.25,0.01,0.10,0.13,0.002]\bar{s}_i^{(t-1)} = [0.15, 0.25, 0.01, 0.10, 0.13, 0.002] and β=0.85\beta = 0.85:

sˉi(t)=0.85⋅sˉi(t−1)+0.15⋅si(t)\bar{s}_i^{(t)} = 0.85 \cdot \bar{s}_i^{(t-1)} + 0.15 \cdot s_i^{(t)}

For W1W_1:

  • sˉ1=0.85×0.15+0.15×0.160=0.1275+0.024=0.1515\bar{s}_1 = 0.85 \times 0.15 + 0.15 \times 0.160 = 0.1275 + 0.024 = 0.1515
  • sˉ2=0.85×0.25+0.15×0.270=0.2125+0.0405=0.2530\bar{s}_2 = 0.85 \times 0.25 + 0.15 \times 0.270 = 0.2125 + 0.0405 = 0.2530
  • sˉ3=0.85×0.01+0.15×0.005=0.0085+0.00075=0.0093\bar{s}_3 = 0.85 \times 0.01 + 0.15 \times 0.005 = 0.0085 + 0.00075 = 0.0093

For W2W_2:

  • sˉ4=0.85×0.10+0.15×0.105=0.085+0.01575=0.1008\bar{s}_4 = 0.85 \times 0.10 + 0.15 \times 0.105 = 0.085 + 0.01575 = 0.1008
  • sˉ5=0.85×0.13+0.15×0.140=0.1105+0.021=0.1315\bar{s}_5 = 0.85 \times 0.13 + 0.15 \times 0.140 = 0.1105 + 0.021 = 0.1315
  • sˉ6=0.85×0.002+0.15×0.001=0.0017+0.00015=0.0019\bar{s}_6 = 0.85 \times 0.002 + 0.15 \times 0.001 = 0.0017 + 0.00015 = 0.0019

Step 4: Compute final importance scores. For simplicity, assume the orthonormality constraint is well-satisfied so that ∥P:,i∥F≈1\|P_{:,i}\|_F \approx 1 and ∥Qi,:∥F≈1\|Q_{i,:}\|_F \approx 1, and assume all layers have the same dimensions so the normalization factor is the same for all. The importance score simplifies to Si=sˉi⋅∣λi∣S_i = \bar{s}_i \cdot |\lambda_i|.

  • S1=0.1515×0.80=0.1212S_1 = 0.1515 \times 0.80 = 0.1212
  • S2=0.2530×0.45=0.1139S_2 = 0.2530 \times 0.45 = 0.1139
  • S3=0.0093×0.10=0.0009S_3 = 0.0093 \times 0.10 = 0.0009
  • S4=0.1008×0.70=0.0706S_4 = 0.1008 \times 0.70 = 0.0706
  • S5=0.1315×0.35=0.0460S_5 = 0.1315 \times 0.35 = 0.0460
  • S6=0.0019×0.05=0.0001S_6 = 0.0019 \times 0.05 = 0.0001

Step 5: Global ranking and pruning. Sorting all six triplets by importance score in descending order:

  1. Triplet (1,1)(1,1): S=0.1212S = 0.1212 (keep)
  2. Triplet (1,2)(1,2): S=0.1139S = 0.1139 (keep)
  3. Triplet (2,1)(2,1): S=0.0706S = 0.0706 (keep)
  4. Triplet (2,2)(2,2): S=0.0460S = 0.0460 (prune)
  5. Triplet (1,3)(1,3): S=0.0009S = 0.0009 (prune)
  6. Triplet (2,3)(2,3): S=0.0001S = 0.0001 (prune)

With a budget of 3, we keep triplets 1, 2, and 4 (the top 3 by importance) and mask the rest by setting their λ\lambda values to zero. The resulting effective rank distribution is: W1W_1 gets rank 2 (keeps triplets 1 and 2), W2W_2 gets rank 1 (keeps triplet 4 only).

Step 6: Interpretation. This allocation shows the data-driven discovery that W1W_1 is more important for this task than W2W_2: it receives twice the parameter budget. Within W1W_1, the first two triplets both have importance scores above 0.11 and are kept, while the third has an importance score near zero and is discarded. Within W2W_2, only the first triplet has a real importance score; the second and third are pruned.

Notice something interesting about the pruning decision for triplet (2,2)(2,2) versus (1,3)(1,3): triplet (2,2)(2,2) has a much larger magnitude (∣λ∣=0.35|\lambda| = 0.35 versus ∣λ∣=0.10|\lambda| = 0.10) but is still pruned, while triplet (1,3)(1,3) is also pruned. What matters is the combined importance score: even W1W_1's smallest triplet ranks 5th overall with S=0.0009S = 0.0009, just barely above triplet (2,3)(2,3). A layer-by-layer approach that independently pruned within each matrix might make a different decision, but the global competition yields an allocation where total budgeted importance is maximized.

Training Procedure

The complete AdaLoRA training procedure integrates all the elements described above into a coherent algorithm. Understanding how these pieces fit together illuminates why AdaLoRA works effectively and how its various components interact during optimization. The procedure can be divided into three phases: initialization, the main training loop with progressive pruning, and the final extraction of the rank distribution.

Each phase serves a specific purpose in the overall strategy. Initialization sets up the mathematical structure and ensures the model starts from its pretrained state. The main training loop simultaneously learns the task and discovers the importance hierarchy through gradient observations. The final rank distribution is the algorithm's learned answer to the question "which weight matrix directions matter most for this task?"

Initialization

Training begins by initializing the SVD-style decomposition for each adapted weight matrix. The initialization strategy matters materially because it affects both training stability and the fairness of early importance assessments.

For each adapted weight matrix WkW_k, we initialize:

  1. Initialize PP with random orthonormal columns, typically by applying QR decomposition to a random matrix. The QR factorization of a random matrix produces exactly orthonormal columns, giving us a perfect starting point that satisfies the orthonormality constraint exactly at initialization.
  2. Initialize Λ\Lambda as the zero matrix. This is identical to LoRA's initialization strategy for the BB matrix. With Λ=0\Lambda = 0, the initial weight update ΔW=P⋅0⋅Q=0\Delta W = P \cdot 0 \cdot Q = 0, so the adapted model starts identical to the pretrained model. This is important for fine-tuning stability: the pretrained model's outputs serve as a well-calibrated starting point, and we want to begin training from there rather than from a perturbed state.
  3. Initialize QQ with random orthonormal rows, again using QR decomposition of a random matrix (applied to the transpose).
  4. Set all importance score accumulators sˉi=0\bar{s}_i = 0. Starting from zero ensures that initial importance estimates are determined entirely by the first few gradient observations rather than by any prior assumption.

The zero initialization of Λ\Lambda has a subtle interaction with importance scoring: in the first training step, all singular values are zero, so all sensitivity estimates si=∣λi⋅∂L/∂λi∣s_i = |\lambda_i \cdot \partial \mathcal{L}/\partial \lambda_i| are also zero. This means no component has demonstrated importance at initialization, which is correct: at step zero, before any training, we do not know which components will turn out to be important. The importance hierarchy emerges from gradient observations as training progresses.

Training Loop

Each training iteration proceeds through a sequence of well-defined steps that together drive both task learning and rank allocation:

  1. Forward pass: Compute the adapted weight as W=W0+PΛQW = W_0 + P \Lambda Q and use it in the model's forward computation to produce predictions.
  2. Loss computation: Calculate the total loss Ltotal=Ltask+γ⋅R(P,Q)\mathcal{L}_{\text{total}} = \mathcal{L}_{\text{task}} + \gamma \cdot R(P, Q), which combines the downstream task loss with the orthogonality regularization penalty.
  3. Backward pass: Compute gradients ∂Ltotal∂P\frac{\partial \mathcal{L}_{\text{total}}}{\partial P}, ∂Ltotal∂Λ\frac{\partial \mathcal{L}_{\text{total}}}{\partial \Lambda}, and ∂Ltotal∂Q\frac{\partial \mathcal{L}_{\text{total}}}{\partial Q} via standard backpropagation. The gradient with respect to Λ\Lambda is particularly important because it feeds directly into the sensitivity computation.
  4. Sensitivity update: For each singular value λi\lambda_i, compute the instantaneous sensitivity si(t)=∣λi(t)⋅∂L∂λi(t)∣s_i^{(t)} = |\lambda_i^{(t)} \cdot \frac{\partial \mathcal{L}}{\partial \lambda_i^{(t)}}| and update the exponential moving average sˉi(t)\bar{s}_i^{(t)}.
  5. Parameter update: Apply the optimizer (typically AdamW) to update PP, Λ\Lambda, and QQ in the direction that reduces the total loss.
  6. Pruning step (if past warmup and at a scheduled pruning iteration): Compute full importance scores Si(t)S_i^{(t)} for all triplets across all adapted matrices, rank them globally, and apply the binary mask to zero out the singular values of the lowest-ranked triplets to meet the current budget target.

The pruning step typically occurs every 10-50 iterations rather than every step. This interval serves two purposes: it reduces the computational overhead of the global ranking operation (which requires sorting all triplets in all layers), and it gives stability by letting the model to adapt to each pruning decision before the next one occurs. Continuous pruning at every step would create a moving target that could interfere with the optimizer's ability to converge.

One important implementation detail is that the gradients with respect to masked singular values are still computed but should not update the mask-zero parameters. In practice, this is handled by zeroing out gradients for masked parameters after the backward pass. This keeps pruned components remain pruned rather than being inadvertently revived by gradient updates.

Final Rank Distribution

After training completes, different weight matrices will have different effective ranks. This shows the algorithm's learned assessment of their importance for the task. The patterns that typically emerge are informative:

  • Query and value projections in attention layers tend to receive higher effective ranks. These matrices directly control what the model attends to and how attended information is combined, both of which are important for most downstream NLP tasks that require the model to focus on specific parts of the input.
  • Key projections often receive moderate ranks. Keys determine which queries each position responds to, which is important but often requires less adaptation than queries and values since the general patterns learned during pretraining remain largely applicable.
  • Feed-forward layers tend to receive lower ranks, especially in deeper transformer layers. The feed-forward networks have been interpreted as storing factual knowledge about the world, but for many fine-tuning tasks (especially classification tasks), this stored knowledge needs relatively little modification.
  • The specific pattern varies substantially by task. For tasks that require understanding long-range dependencies (like multi-hop reasoning), higher-layer attention heads may receive more rank. For tasks that require specific factual knowledge (like open-domain QA), feed-forward layers may need more rank than usual.

This distribution emerges entirely from the data and task: AdaLoRA discovers it rather than requiring manual specification. The patterns often reveal interesting insights about which parts of the model the specific fine-tuning task relies on most heavily, so AdaLoRA can serve as both a practical training tool and an analytical instrument for understanding model adaptation.

Code Implementation

Let's implement AdaLoRA using the PEFT library, which gives a clean interface for this technique. We will walk through the key configuration choices, inspect the parameter structure to verify the SVD decomposition, and examine how rank allocation evolves during training.

We start by loading a base model. We use DistilBERT for this demonstration because it is small enough to train quickly while having the same architectural structure as larger BERT-family models, making the AdaLoRA mechanics clearly visible.

PythonIn [10]
CollapseExpand
from transformers import AutoModelForSequenceClassification, AutoTokenizer

# Load a base model
model_name = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(
    model_name, num_labels=2
)

We'll configure AdaLoRA with its key hyperparameters. The most important are the initial rank and target rank, which define the budget reduction during training. Setting init_r=12 and target_r=4 means we start with 12 triplets per matrix and prune down to an average of 4, a 3x reduction in effective adaptation parameters.

PythonIn [11]
CollapseExpand
from peft import AdaLoraConfig, TaskType, get_peft_model

# AdaLoRA configuration
adalora_config = AdaLoraConfig(
    # SVD decomposition settings
    init_r=12,  # Initial rank for all matrices
    target_r=4,  # Target average rank after pruning
    # Importance scoring
    beta1=0.85,  # EMA smoothing for sensitivity
    beta2=0.85,  # EMA smoothing for importance
    # Regularization
    orth_reg_weight=0.5,  # Weight for orthonormality regularization
    # Training schedule
    total_step=100,  # Total training steps (for pruning schedule)
    # Which modules to adapt
    target_modules=["q_lin", "v_lin", "k_lin", "out_lin"],
    # Task type
    task_type=TaskType.SEQ_CLS,
)

# Create AdaLoRA model
peft_model = get_peft_model(model, adalora_config)

Let's examine the parameter structure that AdaLoRA creates. The trainable parameter count shows the initial rank r0r_0 across all adapted matrices.

PythonIn [12]
CollapseExpand
def count_parameters(model):
    """Count trainable and total parameters."""
    trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
    total = sum(p.numel() for p in model.parameters())
    return trainable, total


trainable_params, total_params = count_parameters(peft_model)
ConsoleOut [13]
CollapseExpand
Trainable parameters: 1,034,786
Total parameters: 67,989,820
Trainable %: 1.52%

The trainable parameter count shows the initial rank r0r_0 across all adapted matrices. This count will effectively decrease during training as singular values are pruned (though the actual parameters remain in memory, just zeroed out through the masking mechanism described earlier).

Inspecting SVD Components

Let's look at how AdaLoRA structures its parameters compared to standard LoRA. Understanding the naming convention in the PEFT library is helpful: AdaLoRA uses the names lora_A for the QQ matrix (right singular vectors), lora_E for the Λ\Lambda vector (singular values), and lora_B for the PP matrix (left singular vectors). The naming follows LoRA's original convention for AA and BB but adds E for the singular values (from "eigenvalues," a related concept).

PythonIn [14]
CollapseExpand
# Find an AdaLoRA layer to inspect
def find_adalora_layers(model):
    """Find all AdaLoRA-adapted layers."""
    layers = []
    for name, module in model.named_modules():
        if hasattr(module, "lora_E"):  # AdaLoRA uses E for singular values
            layers.append((name, module))
    return layers


adalora_layers = find_adalora_layers(peft_model)
ConsoleOut [15]
CollapseExpand
Found 24 AdaLoRA layers:
  base_model.model.distilbert.transformer.layer.0.attention.q_lin
  base_model.model.distilbert.transformer.layer.0.attention.k_lin
  base_model.model.distilbert.transformer.layer.0.attention.v_lin
  base_model.model.distilbert.transformer.layer.0.attention.out_lin

Each AdaLoRA layer contains QQ (called lora_A in PEFT), Λ\Lambda (called lora_E), and PP (called lora_B).

PythonIn [16]
CollapseExpand
# Examine one layer's structure
layer_name, adalora_layer = adalora_layers[0]
ConsoleOut [17]
CollapseExpand
Layer: base_model.model.distilbert.transformer.layer.0.attention.q_lin
  Q (lora_A) shape: torch.Size([12, 768])
  Lambda (lora_E) shape: torch.Size([12, 1])
  P (lora_B) shape: torch.Size([768, 12])
  Effective rank: 12

The shapes confirm the SVD structure: PP has dimensions matching the output dimension, QQ matches the input dimension, and Λ\Lambda (stored as a vector EE) has length equal to the rank. Notice that Λ\Lambda is stored as a 1D vector rather than a diagonal matrix: this saves memory (storing rr values instead of r2r^2) and makes the elementwise multiplication with the mask straightforward.

Training with Dynamic Pruning

To see AdaLoRA's pruning in action, let's set up a simple training loop that tracks rank changes. We use the SST-2 sentiment analysis dataset from the GLUE benchmark as a representative fine-tuning task.

PythonIn [18]
CollapseExpand
from datasets import load_dataset
from torch.utils.data import DataLoader

# Load a small dataset
dataset = load_dataset("nyu-mll/glue", "sst2", split="train[:1000]")


def tokenize_function(examples):
    return tokenizer(
        examples["sentence"],
        padding="max_length",
        truncation=True,
        max_length=128,
    )


tokenized_dataset = dataset.map(tokenize_function, batched=True)
tokenized_dataset.set_format(
    type="torch", columns=["input_ids", "attention_mask", "label"]
)

train_dataloader = DataLoader(tokenized_dataset, batch_size=16, shuffle=True)

The RankAllocator is the key object in PEFT's AdaLoRA implementation. It encapsulates the importance scoring and global ranking logic, and exposes the update_and_allocate method that should be called after each backward pass.

PythonIn [19]
CollapseExpand
from peft.tuners.adalora import RankAllocator
from torch.optim import AdamW

# Set up optimizer and training
optimizer = AdamW(peft_model.parameters(), lr=1e-4)

# Create the rank allocator that handles pruning
rankallocator = RankAllocator(peft_model, adalora_config, "default")

# Track rank changes during training
rank_history = []
latest_rank_pattern = None
PythonIn [20]
CollapseExpand
import torch

# Training loop with rank allocation
peft_model.train()
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
peft_model.to(device)

global_step = 0
for epoch in range(2):
    for batch in train_dataloader:
        # Move batch to device
        batch = {k: v.to(device) for k, v in batch.items()}

        # Forward pass
        outputs = peft_model(
            input_ids=batch["input_ids"],
            attention_mask=batch["attention_mask"],
            labels=batch["label"],
        )
        loss = outputs.loss

        # Add orthogonality regularization (API varies by PEFT version)
        if hasattr(rankallocator, "compute_orth_regu"):
            orth_loss = rankallocator.compute_orth_regu(
                peft_model, regu_weight=adalora_config.orth_reg_weight
            )
            total_loss = loss + orth_loss
        else:
            total_loss = loss  # Skip orth reg if not available

        # Backward pass
        total_loss.backward()

        optimizer.step()

        # Mask after the optimizer step so AdamW cannot immediately move
        # pruned singular values away from zero again.
        _, current_rank_pattern = rankallocator.update_and_allocate(
            peft_model, global_step
        )
        if current_rank_pattern is not None:
            latest_rank_pattern = current_rank_pattern

        optimizer.zero_grad()

        global_step += 1

        # Record effective ranks periodically
        if global_step % 10 == 0 and latest_rank_pattern is not None:
            current_ranks = {
                name: int(sum(mask))
                for name, mask in latest_rank_pattern.items()
            }
            rank_history.append((global_step, current_ranks))

        if global_step >= adalora_config.total_step:
            break
    if global_step >= adalora_config.total_step:
        break
ConsoleOut [21]
CollapseExpand
Training completed at step 100
Final loss: 1.1802

The final loss confirms that the model has successfully adapted to the training data. The loss reduction shows that the rank allocation strategy, despite progressively pruning components, does not prevent the model from fitting the task. In fact, the adaptive allocation typically produces lower loss at the same parameter budget compared to uniform LoRA, because it concentrates parameters in the most important directions.

Examining Final Rank Distribution

After training, we can inspect how AdaLoRA distributed ranks across different layers. This distribution is the key output of the algorithm: a data-driven answer to the question of which weight matrices deserve more adaptation capacity.

PythonIn [22]
CollapseExpand
def get_effective_ranks(model):
    """Get the effective rank (non-zero singular values) for each layer."""
    ranks = {}
    for name, module in model.named_modules():
        if hasattr(module, "lora_E"):
            for adapter_name in module.lora_E.keys():
                E = module.lora_E[adapter_name]
                # Count non-zero singular values
                effective_rank = (E.abs() > 1e-6).sum().item()
                ranks[name] = effective_rank
    return ranks


final_ranks = get_effective_ranks(peft_model)
ConsoleOut [23]
CollapseExpand
Final rank distribution:
  base_model.model.distilbert.transformer.layer.0.attention.k_lin: rank 4
  base_model.model.distilbert.transformer.layer.0.attention.out_lin: rank 4
  base_model.model.distilbert.transformer.layer.0.attention.q_lin: rank 4
  base_model.model.distilbert.transformer.layer.0.attention.v_lin: rank 2
  base_model.model.distilbert.transformer.layer.1.attention.k_lin: rank 4
  base_model.model.distilbert.transformer.layer.1.attention.out_lin: rank 5
  base_model.model.distilbert.transformer.layer.1.attention.q_lin: rank 4
  base_model.model.distilbert.transformer.layer.1.attention.v_lin: rank 5
  base_model.model.distilbert.transformer.layer.2.attention.k_lin: rank 3
  base_model.model.distilbert.transformer.layer.2.attention.out_lin: rank 4
  base_model.model.distilbert.transformer.layer.2.attention.q_lin: rank 3
  base_model.model.distilbert.transformer.layer.2.attention.v_lin: rank 5
  base_model.model.distilbert.transformer.layer.3.attention.k_lin: rank 3
  base_model.model.distilbert.transformer.layer.3.attention.out_lin: rank 3
  base_model.model.distilbert.transformer.layer.3.attention.q_lin: rank 3
  base_model.model.distilbert.transformer.layer.3.attention.v_lin: rank 4
  base_model.model.distilbert.transformer.layer.4.attention.k_lin: rank 4
  base_model.model.distilbert.transformer.layer.4.attention.out_lin: rank 4
  base_model.model.distilbert.transformer.layer.4.attention.q_lin: rank 2
  base_model.model.distilbert.transformer.layer.4.attention.v_lin: rank 7
  base_model.model.distilbert.transformer.layer.5.attention.k_lin: rank 4
  base_model.model.distilbert.transformer.layer.5.attention.out_lin: rank 4
  base_model.model.distilbert.transformer.layer.5.attention.q_lin: rank 4
  base_model.model.distilbert.transformer.layer.5.attention.v_lin: rank 7

Total effective rank budget: 96
Average rank per layer: 4.0

The distribution reveals which layers AdaLoRA determined were most important for this task. Layers with higher retained ranks contribute more to the adaptation. Notice that the distribution is heterogeneous even within a single transformer block: different projection types receive different ranks based on their actual importance for sentiment analysis, rather than a uniform assumption about all projections being equally important.

VisualizationOut [24]
CollapseExpand
Bar chart showing the final effective rank per adapted layer after AdaLoRA training, with taller bars for higher-importance layers and a horizontal dashed line showing the target average rank.
Effective rank distribution across adapted layers after AdaLoRA training. The algorithm allocates higher ranks to layers with greater impact on task performance. Color-coding separates the four projection types: query, value, key, and output projections. The dashed line shows the target average rank, showing how the learned allocation departs from a uniform distribution.

Visualizing Rank Evolution

PythonIn [25]
CollapseExpand
import matplotlib.pyplot as plt

use_book_style()
plt.rcParams["figure.figsize"] = (6.0, 4.0)

# Extract training steps and calculated ranks
steps = [h[0] for h in rank_history]
total_ranks = [sum(h[1].values()) for h in rank_history]
target_budget = adalora_config.target_r * len(adalora_layers)

plt.figure()
plt.plot(
    steps,
    total_ranks,
    color=PALETTE["primary"],
    label="Total effective rank",
)
plt.axhline(
    y=target_budget,
    color=PALETTE["coral"],
    linestyle="--",
    label="Target budget",
)
plt.fill_between(steps, total_ranks, color=PALETTE["primary"], alpha=0.2)

plt.xlabel("Training Step")
plt.ylabel("Total Effective Rank")
plt.title("AdaLoRA Rank Pruning Schedule")
plt.legend(
    loc="upper center",
    bbox_to_anchor=(0.5, -0.16),
    ncol=2,
    borderaxespad=0,
)
plt.subplots_adjust(bottom=0.23)
plt.show()
VisualizationOut [25]
CollapseExpand
Line plot showing rank decreasing over training steps with shaded area, from initial maximum down toward the target budget dashed line.
Temporal evolution of the total effective rank during training. The shaded area is the active parameter budget, which starts at the maximum capacity and decreases following the cubic schedule until reaching the target budget (dashed red line). The rapid initial decline shows aggressive early pruning, while the flatter tail shows gentler pruning as the budget approaches its target.

The cubic pruning schedule is evident in the rank evolution: aggressive rank reduction early in training (when many clearly unimportant components exist and the budget is far from its target) followed by gentler pruning as the budget approaches its target. The shaded area is the total "active capacity" of the adaptation: the area under the curve corresponds to the total computational work performed by the adaptation components. AdaLoRA concentrates this work in the early phases of training where it has the most impact on learning.

Comparing with Standard LoRA

To appreciate AdaLoRA's benefits, let's compare the parameter efficiency at equivalent performance levels. The comparison highlights the qualitative difference in how parameters are allocated.

PythonIn [26]
CollapseExpand
from peft import LoraConfig

# Equivalent LoRA configuration
# To match AdaLoRA's average target rank, we'd use r=4
lora_config = LoraConfig(
    r=4,
    lora_alpha=16,
    target_modules=["q_lin", "v_lin", "k_lin", "out_lin"],
    task_type=TaskType.SEQ_CLS,
)

# Count parameters for comparison
lora_model = AutoModelForSequenceClassification.from_pretrained(
    model_name, num_labels=2
)
lora_peft_model = get_peft_model(lora_model, lora_config)
lora_trainable, _ = count_parameters(lora_peft_model)

# Calculate AdaLoRA effective parameters
# Each rank unit has: 1 column P (d), 1 row Q (d), 1 singular value (1)
hidden_size = model.config.dim
adalora_effective_params = sum(final_ranks.values()) * (2 * hidden_size + 1)
ConsoleOut [27]
CollapseExpand
Parameter comparison (target rank 4):
  Standard LoRA: 739,586 trainable parameters
  AdaLoRA (final): 147,552 effective parameters (approximate)

Key difference: AdaLoRA allocates these parameters adaptively
across layers based on importance, rather than uniformly.

The parameter counts are similar in total, but AdaLoRA's adaptive allocation concentrates parameters where they matter most. Standard LoRA's uniform allocation means that less important layers receive the same rank as necessary ones, effectively wasting parameters on layers that need less adaptation while leaving necessary layers underserved.

VisualizationsOut [28]
CollapseExpand
Bar chart showing uniform rank assigned to each layer in standard LoRA, with a horizontal red dashed line at the constant rank value.
Standard LoRA applying a uniform rank to every layer, with the fixed rank shown as a red dashed reference line. Every adapted layer receives identical modeling capacity regardless of its actual importance to the task.
Bar chart showing variable rank per layer in AdaLoRA after adaptive pruning, with a horizontal red dashed line at the average rank.
AdaLoRA's adaptive rank allocation across layers after training. The varying bar heights reflect the data-driven discovery of which layers need more adaptation capacity. The red dashed line shows the average rank, revealing how individual allocations depart from uniformity.

The visual contrast makes AdaLoRA's core advantage concrete. Standard LoRA's flat allocation treats every layer identically, regardless of whether the task requires large adaptation in that layer or almost none. AdaLoRA's allocation tells a differentiated story: some layers receive substantially more capacity, others receive far less. This shows the actual demands of the fine-tuning task.

Key Parameters

Configuring AdaLoRA well requires understanding how its parameters interact with each other and with the task characteristics. Poor choices can lead to either insufficient adaptation (if the target rank is too low) or wasted computation (if the initial rank is unnecessarily high).

The key parameters for AdaLoRA and their practical implications are:

  • init_r: The initial rank allocated to all matrices before pruning begins. This should be set high enough that every potentially important direction has a chance to be represented, but not so high that training becomes prohibitively slow. Values between 8 and 16 are common for medium-sized models. Setting it too low risks missing important directions; setting it too high wastes memory and computation during the warmup phase.
  • target_r: The final average rank to reach across all adapted matrices. This is the primary parameter controlling the total parameter budget of the final model. The ratio init_r / target_r determines the total amount of pruning. A ratio of 3x (e.g., init_r=12, target_r=4) is typical and generally leaves enough room for the algorithm to discover real heterogeneity.
  • beta1 and beta2: Smoothing factors for the exponential moving averages of gradient sensitivity and importance scores respectively. Values around 0.85 are standard. Higher values reduce noise but make the algorithm less responsive to real changes in importance; lower values track changes faster but may make premature pruning decisions based on noisy gradients. In most cases, the default values work well.
  • orth_reg_weight: The strength of the regularization term that enforces orthonormality of singular vectors. Values around 0.5 give a reasonable balance. Too weak and the decomposition loses its SVD-like separability, making the importance scores less reliable. Too strong and the orthonormality objective can dominate the task loss, slowing convergence or reducing final performance.
  • total_step: The total number of training steps, used to calculate the pruning schedule. This must be set accurately to ensure the cubic schedule produces the desired pruning trajectory. If you set total_step higher than the actual number of training steps, pruning will not reach the target budget; if you set it lower, the budget will reach its target before training ends, and the remaining steps use the fixed final allocation.

Limitations and Impact

AdaLoRA introduced an important insight to parameter-efficient fine-tuning: not all weight matrices are equally important, and adaptive allocation can improve efficiency. The technique was validated on multiple GLUE benchmarks, showing consistent improvements over LoRA at equivalent parameter budgets. However, the technique comes with practical trade-offs that are important to understand before deciding whether to use it.

The primary limitation is training overhead. AdaLoRA must maintain importance score accumulators for every singular value triplet in every adapted layer, compute exponential moving averages at each training step, and periodically evaluate global rankings across all triplets for pruning decisions. The orthogonality regularization adds an additional loss term that requires computing the Frobenius norm of PTP−IP^T P - I and QQT−IQQ^T - I, each requiring a matrix multiplication. The update_and_allocate call after each backward pass includes a global sort over all importance scores. In practice, this overhead typically increases wall-clock training time by 20-40% compared to standard LoRA at the same effective rank, depending on the model size and hardware. For many production use cases, where fine-tuning happens once and inference runs many times, this training overhead is a worthwhile trade-off for better final performance. For rapid iteration and experimentation, it can be a significant friction.

The SVD-style parameterization also introduces hyperparameter sensitivity that standard LoRA avoids. The orthogonality regularization weight must be carefully balanced: too weak and the decomposition loses its independence properties, making importance scores noisy and unreliable; too strong and it interferes with task learning, potentially creating a well-regularized but poorly-adapted model. The pruning schedule parameters, particularly the warmup length and the relationship between init_r and target_r, interact with the learning rate schedule in ways that can be difficult to predict. Getting AdaLoRA to work well on a new task often requires more hyperparameter tuning than standard LoRA's simpler two-parameter setup (rank and alpha). This is an important consideration for teams that need to fine-tune quickly on many different tasks, where the time spent on hyperparameter tuning would outweigh the gains from adaptive allocation.

Memory consumption during training is another practical consideration. While AdaLoRA can produce a smaller final model in terms of effective parameters, training starts with the full initial rank and stores importance statistics for all components, including those that will eventually be pruned. The memory overhead compared to standard LoRA at the target rank can be substantial: if you initialize at rank 12 to eventually prune to rank 4, you are storing three times more adapter parameters during training than a LoRA model trained at rank 4 directly would need. For large models being fine-tuned on memory-constrained hardware, this initial overhead may be prohibitive. One mitigation is to use gradient checkpointing, which trades compute for memory, but this adds further to the training time.

Despite these limitations, AdaLoRA's core contribution goes beyond its direct performance improvements. It established that rank allocation is a design choice worth optimizing rather than a hyperparameter to set by intuition. This insight inspired subsequent work: LoRA-variant methods that explore different importance metrics, dynamic rank adjustment during inference, and mixture approaches that combine adaptive allocation with other PEFT techniques. The technique performs particularly well when there is significant heterogeneity in how different layers contribute to a task, as often occurs in cross-domain transfer scenarios where some pretrained representations align closely with the target task while others require substantial modification.

The principles from AdaLoRA also influenced thinking about model compression more broadly. The importance-weighted pruning approach, combining magnitude with gradient-based sensitivity through a product formula, has been adopted in other contexts beyond low-rank adaptation. The key insight that sensitivity to the loss (how much the loss would change if a component were removed) is a better proxy for importance than magnitude alone (how large the component currently is) has proven useful in neural network pruning, knowledge distillation, and other model compression contexts. We will see related ideas appear in subsequent PEFT chapters, each giving different trade-offs between adaptation expressivity and parameter efficiency.

Summary

AdaLoRA extends standard LoRA with adaptive rank allocation, addressing the basic limitation that uniform ranks across all weight matrices may not reflect their varying importance to task performance. The technique is built on three interacting innovations that together let principled, data-driven allocation of adaptation capacity.

The key technical innovations are:

  • SVD-based parameterization: Representing weight updates as ΔW=PΛQ\Delta W = P \Lambda Q with approximately orthonormal PP and QQ lets independent pruning of individual singular value triplets. Unlike standard LoRA's entangled BABA product, each triplet in the SVD formulation contributes independently to the weight update, making clean surgical pruning possible.
  • Importance scoring: Combining parameter magnitude ∣λi∣|\lambda_i| with gradient-based sensitivity sˉi\bar{s}_i through a product formula identifies components that are both currently contributing and would be missed if removed. The exponential moving average smoothing prevents premature pruning decisions based on noisy mini-batch gradients.
  • Global ranking across all layers: Rather than allocating ranks independently within each layer, AdaLoRA holds a global competition where every triplet in every adapted matrix competes for slots in the total budget. This allows the algorithm to automatically discover that some layers deserve more capacity than others without any domain knowledge about the task.
  • Cubic pruning schedule with warmup: Beginning with aggressive pruning when many obvious low-importance candidates exist, and slowing as the budget approaches its target, the cubic schedule gives stability while efficiently reaching the final allocation. The warmup period ensures that importance estimates have stabilized before any pruning decisions are made.

The training procedure integrates these elements: initialize all matrices at high rank with zero singular values, accumulate importance statistics during warmup, progressively prune to the target budget following the cubic schedule, and maintain approximate orthonormality throughout via regularization. The result is an intelligent distribution of adaptation parameters that typically outperforms uniform-rank LoRA at equivalent parameter budgets.

While AdaLoRA's overhead makes it less suitable for rapid experimentation where LoRA's simplicity is more useful, its adaptive allocation suits production deployments where maximizing performance from a fixed parameter budget justifies additional training cost. It treats fine-tuning as an optimization problem over the architecture as well as the parameter values.

Quiz

Ready to test your understanding? Take this quick quiz to reinforce what you've learned about AdaLoRA and adaptive rank allocation.

AdaLoRA Quiz

Question 1 of 70 of 7 completed
What is the key advantage of AdaLoRA's SVD-based parameterization (ΔW=PΛQ\Delta W=P\Lambda Q) over standard LoRA's (ΔW=BA\Delta W=BA)?
Track your reading progress

Sign in to mark sections as read and track your learning journey

Sign in →
Join the discussionAsk a question or share what you learned.

Comments

Add to the conversation

A question, a correction, or an example from your own work.

No comments yet. Be the first to share your thoughts!

A different angle
All writing
Language AI Handbook · Vol. 11

PEFT Comparison: Choosing the Right Fine-Tuning Method

Compare LoRA, QLoRA, Adapters, IA³, Prefix Tuning, and Prompt Tuning across efficiency, performance, and memory. Practical guide for choosing PEFT methods.

66 min read
Language AI Handbook · Vol. 11

Adapter Layers: Bottleneck Modules for Efficient Fine-Tuning

Explains how adapter layers insert trainable bottleneck modules into transformers for parameter-efficient fine-tuning.

69 min read
Language AI Handbook · Vol. 11

Prompt Tuning: Parameter-Efficient Fine-Tuning

Covers prompt tuning for efficient LLM adaptation. Prepend trainable soft prompts to inputs while keeping models frozen. Scales to match full fine-tuning.

61 min read

About the author

Reference

Citation details

Cite or share this article.

BIBTEXAcademic
@misc{brenndoerfer2025adaloraadaptive, author = {Michael Brenndoerfer}, title = {AdaLoRA: Adaptive Rank Allocation for Efficient Fine-Tuning}, year = {2025}, url = {https://mbrenndoerfer.com/writing/adalora-adaptive-rank-allocation-fine-tuning}, organization = {mbrenndoerfer.com}, note = {Accessed: 2026-10-24} }
APAAcademic
Michael Brenndoerfer (2025). AdaLoRA: Adaptive Rank Allocation for Efficient Fine-Tuning. Retrieved from https://mbrenndoerfer.com/writing/adalora-adaptive-rank-allocation-fine-tuning
MLAAcademic
Michael Brenndoerfer. "AdaLoRA: Adaptive Rank Allocation for Efficient Fine-Tuning." 2026. Web. October 24, 2026. <https://mbrenndoerfer.com/writing/adalora-adaptive-rank-allocation-fine-tuning>.
CHICAGOAcademic
Michael Brenndoerfer. "AdaLoRA: Adaptive Rank Allocation for Efficient Fine-Tuning." Accessed October 24, 2026. https://mbrenndoerfer.com/writing/adalora-adaptive-rank-allocation-fine-tuning.
HARVARDAcademic
Michael Brenndoerfer (2025) 'AdaLoRA: Adaptive Rank Allocation for Efficient Fine-Tuning'. Available at: https://mbrenndoerfer.com/writing/adalora-adaptive-rank-allocation-fine-tuning (Accessed: October 24, 2026).
SimpleBasic
Michael Brenndoerfer (2025). AdaLoRA: Adaptive Rank Allocation for Efficient Fine-Tuning. https://mbrenndoerfer.com/writing/adalora-adaptive-rank-allocation-fine-tuning
Newsletter

Stay up to date

Get new articles, book updates, and notes on AI and technology.

Unsubscribe at any time. Privacy policy