Adapter Layers: Bottleneck Modules for Efficient Fine-Tuning

Michael BrenndoerferDecember 13, 202569 min read

Part of Language AI Handbook

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

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

Adapter Layers

While LoRA modifies attention weights through low-rank decomposition and prefix tuning prepends learnable tokens to the input, adapter layers take a different architectural approach: they insert small, trainable neural network modules directly into the transformer's forward pass. These compact bottleneck modules learn task-specific transformations while leaving the original model weights completely frozen. The result is a family of techniques that achieves something remarkable: a single pretrained model can support dozens of distinct tasks simultaneously, with only a tiny fraction of the parameters needing to change per task.

Adapters emerged from a simple observation: if we want to add new capabilities to a pretrained model without disrupting what it already knows, why not add new components rather than modify existing ones? This additive approach separates pretrained knowledge from task-specific adaptations. The pretrained model becomes a static foundation, like a well-tuned musical instrument, and adapters are the small adjustments a performer makes to match a particular venue or style without fundamentally changing the instrument. The base model stays the same; only the thin adaptive layer on top of it changes per task.

Think of adapter layers as the software equivalent of a plugin system. A plugin does not rewrite the host application; it inserts a small module at a specific hook point, and that module intercepts the data flowing through that point and modifies it for a specific purpose. Adapters work exactly this way. They intercept the representations flowing through the transformer's layers, apply learned task-specific transformations, and pass modified representations downstream. The host application, the pretrained model, is untouched.

To understand why this matters in practice, consider the economics of serving language models. A company might need a customer service assistant, a legal document summarizer, a code review tool, and a medical note generator. With full fine-tuning, maintaining these capabilities requires storing and serving four entirely separate copies of a multi-billion parameter model. With adapters, the company stores one frozen base model and four tiny adapter sets. Switching between tasks is as simple as swapping which adapter is active. The storage savings can be three or four orders of magnitude.

In this chapter, we'll explore the adapter architecture in detail, examining how bottleneck modules compress and expand hidden representations, how the residual structure enables near-identity initialization and stable training, and where to place adapters within transformer blocks for maximum effectiveness. We'll also examine how to choose the right bottleneck dimensionality for different tasks, and we'll cover adapter fusion, a technique for combining multiple task-specific adapters to enable flexible multi-task learning. Throughout, we'll see how adapters proved several concepts that shaped the entire parameter-efficient fine-tuning field.

Historical Context

Adapter layers for neural networks appeared in the computer vision literature before their adoption in NLP. The foundational NLP work came from Houlsby et al. in their 2019 paper "Parameter-Efficient Transfer Learning for NLP," which demonstrated that adapters with as few as 3.6% of BERT's parameters could match full fine-tuning performance on GLUE benchmarks. This result was initially surprising: the intuition at the time was that fine-tuning all model parameters was necessary for high performance. Houlsby et al. showed that the pretrained model already contained most of the knowledge needed for downstream tasks, and only a small amount of task-specific adaptation was required. Their work catalyzed a burst of research into parameter-efficient methods, directly inspiring Pfeiffer et al.'s efficient placement strategy (2021), the AdapterFusion work (2021), and eventually influencing the design of LoRA (2022) and subsequent PEFT methods. The concept of the bottleneck adapter module has proven so durable that it remains a component in many modern PEFT approaches.

Adapter Architecture

The core idea behind adapters is straightforward: insert small bottleneck neural networks into each transformer layer that can learn task-specific transformations. To understand why this approach works, consider what happens when you fine-tune a pretrained model on a new task. The model needs to adjust how it processes information, but most of what it learned during pretraining remains relevant. Rather than modifying the carefully tuned weights that encode this valuable knowledge, adapters provide dedicated pathways for learning new behaviors. The pretrained weights stay frozen, preventing any degradation of the rich representations built up over billions of training tokens.

These modules are called "bottleneck" architectures because they first compress the hidden representation to a much smaller dimension, apply a nonlinearity, and then project back to the original dimension. This compression serves a dual purpose: it dramatically reduces the number of parameters needed for adaptation, and it acts as a regularizer that forces the adapter to capture only the most needed task-specific information. An adapter with a bottleneck of dimension 64 operating on a 768-dimensional hidden space must encode everything it wants to change in just 64 numbers. This constraint sounds limiting, but in practice it prevents overfitting and forces the adapter to learn generalizable patterns.

Think of the bottleneck as a narrow channel in a pipe. When water passes through a narrow section, it accelerates and the flow becomes more organized, with turbulence and noise pushed out. When information passes through a bottleneck layer, the low-capacity channel forces the adapter to prioritize: only the most important task-relevant features can make it through. The input hidden state, which encodes all the context the transformer has computed so far, gets compressed into a compact code that captures only the dimensions of variation most useful for the task.

The key insight is that most of the variation in a pretrained model's hidden representations is generic and task-independent. The adapter's bottleneck discards this generic variation during compression and reconstructs only what is needed during expansion. This is why adapters can adapt effectively with so few parameters: they do not need to encode the full richness of the pretrained representations, only the task-specific corrections to those representations.

Bottleneck Architecture

A bottleneck architecture compresses data through a narrow intermediate representation before expanding it back. This forces the network to learn a compact encoding of the relevant information, acting as an information bottleneck. The concept draws from information theory, where limiting the bandwidth of a channel forces the sender to prioritize the most important signals. In autoencoders, bottlenecks are used to learn compressed representations for reconstruction or generation. In adapters, the goal is different: we are not trying to reconstruct the input but to learn a compact transformation of it. The bottleneck's value lies in the regularization it provides, encouraging the adapter to identify and exploit the low-dimensional subspace of the pretrained representations that is most relevant for the downstream task.

The Adapter Module

An adapter module consists of three components arranged sequentially, each serving a specific purpose in the transformation pipeline. The architecture mirrors the structure of an autoencoder, where information is first compressed and then reconstructed, but here the goal is not reconstruction fidelity but rather learning a useful transformation of the input representation. Unlike an autoencoder, the adapter's output lives in the same space as its input, letting the necessary residual connection that we will discuss shortly.

The three components work together as follows:

  1. Down-projection: A linear layer that compresses the hidden dimension dd to a smaller bottleneck dimension rr. This projection acts as a learned feature selector, identifying which aspects of the high-dimensional hidden state are most relevant for the task at hand. Each row of the down-projection matrix can be thought of as a learned detector for a particular pattern in the input space.
  2. Nonlinearity: An activation function, typically ReLU or GELU, that enables nonlinear transformations. Without this component, the composition of two linear layers would simply be another linear transformation, severely limiting the adapter's expressive power. The nonlinearity allows the adapter to model interactions between features in the compressed representation that no single linear projection could capture.
  3. Up-projection: A linear layer that expands from dimension rr back to dd. This projection reconstructs the full hidden dimension, translating the compressed task-specific representation back into a form compatible with subsequent transformer layers. The columns of the up-projection matrix define how each compressed dimension spreads its influence across the full hidden state.

Given an input hidden state h∈Rd\mathbf{h} \in \mathbb{R}^d, the adapter computes its output through the sequential application of these three components. Before we write the formula, note that we are computing a function that takes a dd-dimensional vector, maps it to an rr-dimensional space, applies a nonlinearity, and maps it back:

Adapter(h)=Wup⋅f(Wdown⋅h+bdown)+bup\text{Adapter}(\mathbf{h}) = \mathbf{W}_{up} \cdot f(\mathbf{W}_{down} \cdot \mathbf{h} + \mathbf{b}_{down}) + \mathbf{b}_{up}

where:

  • Adapter(h)\text{Adapter}(\mathbf{h}): the output of the adapter module, a vector in Rd\mathbb{R}^d representing the task-specific transformation that will be added to the original hidden state
  • h\mathbf{h}: the input hidden state, a vector of dimension dd that encodes the current representation at this point in the transformer
  • Wdown∈Rr×d\mathbf{W}_{down} \in \mathbb{R}^{r \times d}: the down-projection weight matrix, which compresses the input from dimension dd to rr. Each row of this matrix defines one learned feature detector in the input space
  • bdown∈Rr\mathbf{b}_{down} \in \mathbb{R}^r: the bias vector for the down-projection layer. This allows the adapter to shift the compressed representation independent of the input values
  • f(⋅)f(\cdot): the nonlinear activation function, such as ReLU or GELU, that enables the network to learn complex patterns. The choice of activation function affects both the expressiveness of the adapter and its training dynamics
  • Wup∈Rd×r\mathbf{W}_{up} \in \mathbb{R}^{d \times r}: the up-projection weight matrix, which expands the representation back from rr to dd. Each column of this matrix defines how one compressed dimension contributes to the full hidden state
  • bup∈Rd\mathbf{b}_{up} \in \mathbb{R}^d: the bias vector for the up-projection layer, often initialized to zero to ensure near-identity behavior at the start of training

Information flows through this formula from the inside out. First, the input hidden state is multiplied by the down-projection matrix and shifted by the bias, yielding a compressed representation in Rr\mathbb{R}^r. Next, the activation function introduces nonlinearity, letting the adapter to model complex relationships in the compressed space. Finally, the up-projection expands this compressed, transformed representation back to Rd\mathbb{R}^d, creating a vector that represents the adapter's desired modification to the hidden state.

An important subtlety: the adapter output is a modification to the hidden state, not a replacement. Its magnitude is typically small, especially early in training. The adapter learns to make surgical adjustments to the representations flowing through the transformer, nudging them in directions that improve performance on the target task without disrupting the rich structure encoded in those representations by pretraining.

Residual Connection

A necessary design choice is wrapping the adapter in a residual connection. This architectural decision fundamentally shapes how the adapter integrates with the transformer and has measurable implications for both training dynamics and the nature of the learned transformation. Without the residual connection, the adapter would need to learn to reproduce all of the input's useful content in addition to the task-specific modifications. With it, the adapter can focus exclusively on what needs to change.

We want to compute a new hidden state h′\mathbf{h}' that incorporates both the original pretrained information and the task-specific modification. The residual formulation achieves this cleanly:

h′=h+Adapter(h)\mathbf{h}' = \mathbf{h} + \text{Adapter}(\mathbf{h})

where:

  • h′\mathbf{h}': the output hidden state after applying the adapter, which will be passed to subsequent operations in the transformer
  • h\mathbf{h}: the input hidden state from the previous layer or sublayer, preserved unchanged and added to the adapter output. This is the "skip connection" that ensures information from before the adapter can flow directly forward
  • Adapter(h)\text{Adapter}(\mathbf{h}): the task-specific transformation computed by the bottleneck module, representing the delta, the modification, to apply to the representation

This residual structure serves multiple purposes that together make adapters both effective and practical. First, it enables the adapter to learn only the delta needed for the new task, rather than having to reproduce the entire transformation from scratch. This is conceptually powerful because the adapter need not learn to pass through information that should remain unchanged; the original representation flows through the skip connection automatically, and the adapter simply adds whatever modifications are necessary. A well-trained adapter learns to produce deltas close to zero for input positions that require no task-specific adjustment, and meaningful modifications only where the task requires it.

Second, the residual structure allows for near-identity initialization, where adapters initialized to output near-zero values will pass through the original representations essentially unchanged. This property is important for stable training because it means the model starts from a state equivalent to the pretrained model. If we simply inserted random linear layers into the transformer, the initial forward pass would produce garbage outputs and gradients would be difficult to work with. With near-zero initialization of the adapter's weights, the initial behavior of the adapted model is indistinguishable from the frozen pretrained model, and training starts from a point where all the pretrained knowledge is fully intact. From there, gradients guide the adapter toward task-specific modifications.

Third, the residual structure provides stable gradient flow during training, as we discussed in the residual connections chapter from Part XV. Gradients can flow directly through the skip connection without passing through the adapter's nonlinearity. This keeps learning signals reach all parts of the network even when the adapter produces small outputs. This is especially important early in training when the adapter weights are near zero: even though the adapter itself contributes very little signal, gradients can still propagate efficiently through the residual path.

Parameter Count

Understanding the parameter count helps you appreciate adapter efficiency. For a single adapter module, the total number of trainable parameters is the sum of all weights and biases in the two projection layers. We can count each component systematically:

The down-projection matrix Wdown\mathbf{W}_{down} has shape r×dr \times d, contributing r⋅dr \cdot d parameters. Its bias bdown\mathbf{b}_{down} adds rr more. The up-projection matrix Wup\mathbf{W}_{up} has shape d×rd \times r, contributing another d⋅rd \cdot r parameters. Its bias bup\mathbf{b}_{up} adds dd more. Summing these:

Parameters=r×d⏟Wdown+r⏟bdown+d×r⏟Wup+d⏟bup=2rd+r+d\text{Parameters} = \underbrace{r \times d}_{\mathbf{W}_{down}} + \underbrace{r}_{\mathbf{b}_{down}} + \underbrace{d \times r}_{\mathbf{W}_{up}} + \underbrace{d}_{\mathbf{b}_{up}} = 2rd + r + d

where:

  • rr: the bottleneck dimension, typically chosen to be much smaller than the hidden dimension (e.g., 64 for BERT-base)
  • dd: the model's hidden dimension, determined by the base model architecture (e.g., 768 for BERT-base, 1024 for BERT-large, 4096 for LLaMA-7B)
  • r×dr \times d: the number of parameters in the down-projection weight matrix, one parameter for each pair of input and output dimensions
  • rr: the bias parameters for down-projection, one per output dimension
  • d×rd \times r: the parameters in the up-projection weight matrix
  • dd: the bias parameters for up-projection
  • 2rd+r+d2rd + r + d: the total, which simplifies to approximately 2rd2rd when r≪dr \ll d, since the bias terms are negligible compared to the weight matrices

When r≪dr \ll d, the formula simplifies to approximately 2rd2rd parameters per adapter. For a model with hidden dimension d=768d = 768 and bottleneck dimension r=64r = 64, each adapter contains roughly 2×64×768=98,3042 \times 64 \times 768 = 98{,}304 parameters. Compare this to a single self-attention layer in BERT-base, which has approximately 4×7682=2,359,2964 \times 768^2 = 2{,}359{,}296 parameters (for the Q, K, V, and output projections combined). The adapter adds less than 5% overhead relative to the attention layer alone, and less than 0.09% of BERT-base's 110 million total parameters.

This efficiency scales favorably. For a 12-layer BERT-base model with dual adapters (one after attention, one after FFN in each layer), the total adapter count is 24. At 98K parameters each, the full adapter set adds about 2.4 million parameters, or roughly 2.2% of the base model. You could store more than 45 completely different task adapters before accumulating as many parameters as the base model itself. For an organization deploying models at scale, this means storing 45 task-specific models for roughly the cost of storing two full models.

Out[3]:
Visualization
Line chart showing adapter parameter count in millions versus bottleneck dimension on a log scale for BERT-base, BERT-large, GPT-2 Medium, and LLaMA-7B.
Adapter parameter count scaling with bottleneck dimension for different model sizes. The parameter count grows linearly with both the bottleneck dimension r and the hidden dimension d, following the 2rd formula. Even at larger bottleneck sizes, adapters remain a small fraction of the base model's parameters.
Line chart showing percentage of BERT-base parameters added by adapters versus bottleneck dimension for FFN-only and dual placement strategies.
Parameter overhead as a percentage of BERT-base for single (FFN only) and dual (Houlsby) adapter placements across 12 transformer layers, showing how overhead scales with bottleneck dimension. The dashed line marks the 10 percent threshold where adapter cost becomes substantial.

Adapter Placement

Where you insert adapters within the transformer block significantly impacts both performance and parameter efficiency. The transformer layer consists of multiple sublayers, each performing a distinct function: the self-attention sublayer captures contextual relationships between tokens, and the feed-forward network sublayer applies position-wise nonlinear transformations. The choice of where to place adapters determines which computations the adapter can influence and how the resulting gradients flow during training. Research has explored several placement strategies that trade expressiveness against efficiency while affecting training stability.

Before diving into specific strategies, it is worth considering what each sublayer contributes to the representation. The attention sublayer reads information from across the entire sequence and writes a context-dependent mixture of values back to each token position. The FFN sublayer applies the same two-layer network independently to each token, serving as a kind of key-value memory that looks up and retrieves learned associations. When we adapt a model for a new task, we might need to adjust either or both of these computations: perhaps the task requires attending to different aspects of the context, or perhaps it requires different feature transformations at each token. The placement strategy determines where in this pipeline the adapter's influence is felt.

Original Placement (Houlsby et al.)

The original adapter paper by Houlsby et al. (2019) proposed inserting two adapter modules per transformer layer. This complete approach ensures that adapters can modify representations both after contextual mixing occurs in the attention sublayer and after feature transformation in the feed-forward sublayer. The rationale was conservative: without knowing in advance which sublayer would require adaptation for a given task, inserting adapters after both provides the greatest flexibility. The two insertion points are:

  1. After the multi-head attention sublayer: between the attention output and the first layer normalization. This position allows the adapter to modify how attended information is integrated before it enters the residual stream.
  2. After the feed-forward network sublayer: between the FFN output and the second layer normalization. This position enables the adapter to adjust the feature transformations applied by the FFN before they are added to the residual.

This placement follows the existing residual connection pattern established in the transformer architecture. To understand how adapters integrate with the standard transformer computation, recall that the original transformer layer computation proceeds as:

h1=LayerNorm(x+MHA(x))h2=LayerNorm(h1+FFN(h1))\begin{aligned} \mathbf{h}_1 &= \text{LayerNorm}(\mathbf{x} + \text{MHA}(\mathbf{x})) \\ \mathbf{h}_2 &= \text{LayerNorm}(\mathbf{h}_1 + \text{FFN}(\mathbf{h}_1)) \end{aligned}

where:

  • h1\mathbf{h}_1: the intermediate representation after the attention sublayer, capturing contextually mixed information from across the sequence
  • LayerNorm\text{LayerNorm}: the layer normalization operation that stabilizes training by normalizing activations across the feature dimension
  • x\mathbf{x}: the input to the transformer layer, which may come from the embedding layer or a previous transformer layer
  • MHA\text{MHA}: the Multi-Head Attention mechanism that computes contextual relationships between all pairs of tokens in the sequence
  • h2\mathbf{h}_2: the final output of the transformer layer, ready to pass to the next layer
  • FFN\text{FFN}: the Feed-Forward Network sublayer that applies position-wise transformations to each token independently

With adapters inserted according to the Houlsby placement strategy, the adapted layer computation becomes:

h1=LayerNorm(x+Adapter1(MHA(x)))h2=LayerNorm(h1+Adapter2(FFN(h1)))\begin{aligned} \mathbf{h}_1 &= \text{LayerNorm}(\mathbf{x} + \text{Adapter}_1(\text{MHA}(\mathbf{x}))) \\ \mathbf{h}_2 &= \text{LayerNorm}(\mathbf{h}_1 + \text{Adapter}_2(\text{FFN}(\mathbf{h}_1))) \end{aligned}

where each component plays the following role:

  • h1\mathbf{h}_1: the intermediate representation after the adapted attention sublayer, now incorporating task-specific modifications to the attention output before it joins the residual stream
  • Adapter1\text{Adapter}_1: the first adapter module inserted after attention, which learns to modify how attention information should be adjusted for the specific task
  • MHA(x)\text{MHA}(\mathbf{x}): the Multi-Head Attention output, computed with frozen weights that are never updated
  • h2\mathbf{h}_2: the final output of the adapted transformer layer
  • Adapter2\text{Adapter}_2: the second adapter module inserted after the feed-forward network, which learns task-specific adjustments to the feature transformations
  • FFN(h1)\text{FFN}(\mathbf{h}_1): the Feed-Forward Network output, computed with frozen weights

Note that each adapter is applied to the sublayer output before the residual connection with the input is formed. The adapter receives the raw sublayer output, transforms it, and then that transformed output participates in the residual addition. This ordering is deliberate: it allows the adapter to transform the sublayer's contribution while still preserving the original skip connection's ability to pass information through unchanged. The base model's residual structure continues to function exactly as designed; adapters simply intercept and modify the sublayer outputs before they enter that structure.

Efficient Placement (Pfeiffer et al.)

Later work by Pfeiffer et al. (2021) made a surprising discovery: using a single adapter per layer, placed only after the feed-forward network, achieves comparable performance with half the parameters. This finding challenged the assumption that both insertion points were necessary and led to more efficient adapter configurations becoming standard practice in the field.

The intuition behind this finding connects to what we learned about transformer components in Part XV. Feed-forward networks act as key-value memories that store factual and associative knowledge, while attention layers handle contextual mixing of token information. Task adaptation often requires modifying how the model processes and retrieves information in response to specific inputs, which aligns more closely with the FFN's role as a learned lookup table. When adapting a model for sentiment analysis, for example, the model primarily needs to adjust how features are transformed and emphasized, rather than fundamentally changing which tokens attend to which other tokens.

The attention mechanism is already quite flexible due to the query-key-value decomposition. Different attention heads specialize in capturing different types of relationships, and the linear combination of their outputs provides a rich contextual mixture without the need for additional adaptation. The FFN, in contrast, applies fixed transformation functions that may need task-specific tuning to capture relevant linguistic patterns for the new task. By placing adapters exclusively after the FFN, Pfeiffer et al. directly targeted the component most likely to require modification while eliminating the half of the adapter parameters that produced diminishing returns.

This result has a practical implication that extends beyond parameter counting: training with fewer adapters is faster, uses less memory, and converges more reliably. The reduced complexity of the FFN-only placement makes it the recommended default for most applications, reserving the dual placement for cases where careful ablation shows it is needed.

Parallel Adapters

An alternative to sequential placement is parallel adapters, where the adapter computation runs alongside the original sublayer rather than after it. This design represents a departure from the standard "intercept and modify" approach, instead treating the adapter as an independent contribution to the layer output.

Rather than sequentially transforming the sublayer output, we want the adapter and the original sublayer to operate in parallel on the same input, and then combine their contributions. The formulation for parallel placement following a multi-head attention sublayer is:

h′=x+MHA(x)+s⋅Adapter(x)\mathbf{h}' = \mathbf{x} + \text{MHA}(\mathbf{x}) + s \cdot \text{Adapter}(\mathbf{x})

where:

  • h′\mathbf{h}': the output of the layer, combining contributions from the input (via the residual skip), the attention mechanism, and the adapter
  • x\mathbf{x}: the input to the layer, which is the common source for all three pathways. The adapter operates on x\mathbf{x} directly, not on the attention output
  • MHA(x)\text{MHA}(\mathbf{x}): the attention sublayer output, computed from the input as in the standard transformer
  • ss: a scaling factor that controls the adapter's contribution to the final representation. This hyperparameter allows fine-grained control over how much the adapter influences the output, and can be tuned per task
  • Adapter(x)\text{Adapter}(\mathbf{x}): the adapter computation running in parallel with the attention mechanism, operating directly on the layer input rather than on the attention output

Parallel placement can offer computational advantages on hardware that supports concurrent operations, as the adapter and original sublayer can be computed simultaneously. On modern GPUs and TPUs, this parallelism can translate to reduced wall-clock time even though the total computation in floating-point operations is the same. Additionally, parallel placement changes the gradient flow: the adapter receives gradients directly from the layer output without having to backpropagate through the attention computation, which can lead to different optimization dynamics. Some researchers have found that parallel adapters train more stably on certain tasks, though the evidence is mixed.

Parallel adapters can also be viewed as implicitly learning an additive correction to the layer's function. If we write the full layer computation as h′=Layer(x)+s⋅Adapter(x)\mathbf{h}' = \text{Layer}(\mathbf{x}) + s \cdot \text{Adapter}(\mathbf{x}), we see that the adapter is learning to produce a vector that, when added to the standard layer output, improves task performance. This is similar in spirit to how LoRA modifies weight matrices: instead of changing the weights, we add a learned correction to the outputs.

Placement Comparison

The choice of adapter placement involves trade-offs between several competing factors. Understanding these trade-offs helps you make informed decisions for your specific use case:

  • Parameter efficiency: Single FFN-only adapters use half the parameters of dual placement. This translates directly to storage savings when maintaining many task-specific adapters, and also reduces memory requirements during training.
  • Performance: Dual placement often achieves slightly better results on diverse tasks, particularly tasks that require modifying both contextual mixing and feature transformation. The advantage is typically 0.5 to 1.5 percentage points on standard benchmarks, which may or may not be worth the additional parameter cost depending on your application.
  • Training stability: The original dual placement provides more gradient pathways, potentially making optimization easier for difficult tasks with little training data. More adapters mean more places where task-specific gradients can be absorbed, reducing the adjustment each individual adapter needs to make.
  • Inference overhead: More adapters mean more sequential computations, increasing latency during inference. For latency-sensitive applications, minimizing the number of adapter forward passes per layer is important.

For most practical applications, the single adapter placement after the FFN provides an excellent balance of efficiency and performance. The savings in parameters and inference time typically outweigh the marginal performance improvements of dual placement, especially when deploying multiple adapters for different tasks.

Out[4]:
Visualization
Bar chart showing total adapter parameter counts for four placement strategies, with Houlsby requiring roughly twice the parameters of single-adapter approaches.
Total adapter parameter counts in millions for Houlsby (both sublayers), Pfeiffer (FFN only), attention-only, and parallel placement strategies applied to a 12-layer BERT-base model with bottleneck dimension 64. Houlsby requires twice the parameters of single-adapter approaches.
Grouped bar chart comparing accuracy on SST-2, MNLI, QQP, and QNLI benchmarks for full fine-tuning, Houlsby adapters, and Pfeiffer adapters.
Simulated accuracy comparison across four GLUE tasks for full fine-tuning, Houlsby dual-adapter, and Pfeiffer single-adapter placements. Both adapter strategies achieve near-comparable performance to full fine-tuning while using far fewer trainable parameters.

Adapter Dimensionality

The bottleneck dimension rr is the primary hyperparameter controlling the trade-off between adapter capacity and parameter efficiency. Choosing the right value requires understanding how information flows through the bottleneck and how the degree of compression affects the adapter's ability to learn useful transformations. Too small a bottleneck and the adapter cannot encode enough task-specific information; too large and we lose the regularization benefits and approach the cost of full fine-tuning.

Think of the bottleneck dimension as the bandwidth of a communication channel between the input and output of the adapter. With a narrow channel (small rr), the adapter must ruthlessly prioritize: only the most consistently useful task-relevant patterns can make it through, and idiosyncratic or noisy features are filtered out. With a wider channel (large rr), the adapter has more freedom to encode complex transformations, but also more opportunity to overfit to training-set peculiarities. The right channel width depends on how much information needs to flow through it for the task at hand.

The Information Bottleneck Perspective

When we compress a hidden representation from dimension dd to dimension rr, we are forcing the adapter to identify and preserve the most task-relevant information. This compression acts as a form of regularization, preventing the adapter from memorizing noise in the training data. The principle at work connects to information theory: by limiting the capacity of the channel through which task information must flow, we force the adapter to be selective about what it encodes.

Consider a hidden representation h∈R768\mathbf{h} \in \mathbb{R}^{768} compressed to r=64r = 64 dimensions. The adapter must encode whatever task-specific transformation it needs to make using only 64 degrees of freedom. This constraint encourages the adapter to learn generalizable patterns rather than fitting to idiosyncrasies in the training set. If a particular feature of the training data is not consistently useful for the task, the limited bottleneck capacity will prevent the adapter from dedicating parameters to capturing it.

The compression also has implications for what kinds of transformations adapters can represent. With a 12:1 compression ratio, the adapter cannot simply learn an arbitrary linear transformation of the input. Instead, it must identify a low-dimensional subspace that captures the needed variation needed for task adaptation. This constraint is often beneficial, as it biases the adapter toward learning transformations that align with the structure already present in the pretrained representations. If the pretrained model has organized its representations so that semantically similar inputs are nearby in representation space, the adapter's bottleneck will naturally pick up on this structure and use it, rather than learning a transformation that ignores the pretrained model's organization.

The compression ratio also interacts with the dimensionality of the task itself. A simple binary classification task, such as positive or negative sentiment, might require only a handful of dimensions to adapt; the model needs to learn which features to attend to and how to weight them for the binary decision. A complex structured prediction task, like named entity recognition across dozens of entity types, requires more fine-grained adjustments and may benefit from a larger bottleneck. The bottleneck dimension should roughly match the intrinsic complexity of the adaptation, not the dimensionality of the representation space.

Dimensionality Guidelines

Research and practical experience suggest the following guidelines for choosing adapter dimensions. The appropriate choice depends on task complexity, data availability, and computational constraints:

  • Very small (r=8r = 8 to 1616): Suitable for tasks closely related to pretraining, such as adapting a language model for a slightly different text domain, or when labeled data is extremely limited. Provides strong regularization but limited expressiveness. These minimal adapters work best when the task primarily requires recombining existing features rather than learning fundamentally new representations. If you have fewer than 1,000 training examples, consider starting here.
  • Small (r=32r = 32 to 6464): A good default for most NLP tasks. Balances efficiency with sufficient capacity for meaningful adaptation. This range provides enough flexibility to learn task-specific transformations while maintaining strong regularization. The original Houlsby et al. paper used r=64r = 64 for most experiments and found strong performance across diverse benchmarks.
  • Medium (r=128r = 128 to 256256): Appropriate for complex tasks or when full fine-tuning achieves significantly better results and you need to close the gap. Approaches the capacity of larger modifications. Consider this range when standard adapter sizes underperform or when the task requires substantial deviation from pretrained behavior, such as adapting to a highly specialized domain with very different vocabulary and linguistic patterns.
  • Large (r≥512r \geq 512): Rarely necessary; at this point, the parameter savings over full fine-tuning diminish substantially. If this much capacity is needed, alternative methods or full fine-tuning may be more appropriate. An adapter with r=512r = 512 for BERT-base adds roughly 800K parameters per adapter, which over 12 layers with dual placement exceeds 19 million parameters, about 17% of the base model.

When choosing a bottleneck dimension, we recommend starting with r=64r = 64 and evaluating on a validation set. If validation performance plateaus well below full fine-tuning, try increasing to r=128r = 128 or r=256r = 256. If you are concerned about overfitting on a small dataset, try reducing to r=32r = 32 or r=16r = 16. The sensitivity of performance to this choice varies by task, so a quick sweep over a few values is usually worthwhile.

Scaling with Model Size

You should consider how to scale adapter dimensions as model size increases. Larger models have larger hidden dimensions, so maintaining the same absolute bottleneck size results in increasingly severe compression. A bottleneck of 64 represents a 12:1 compression ratio for BERT-base (768-dimensional hidden states) but a 64:1 ratio for LLaMA-7B (4096-dimensional hidden states). That 64:1 ratio may be too aggressive, leaving insufficient capacity for meaningful adaptation.

A useful heuristic is to maintain a consistent compression ratio d/rd/r. For example:

Recommended adapter dimensions for different model sizes maintaining a consistent 12:1 compression ratio.
Model Hidden DimAdapter DimRatio
768 (BERT-base)6412:1
1024 (BERT-large)8512:1
4096 (LLaMA-7B)34112:1

However, empirical tuning often reveals that smaller models need proportionally larger adapters than bigger models. This may relate to the observation that larger models have more redundant capacity and can express task-specific information more compactly. A 7-billion parameter model has learned richer internal representations that can more easily be combined and adjusted through a small adapter, while a smaller model may require more adapter capacity to achieve the same effect. When in doubt, treating the compression ratio as a starting point and validating empirically will serve you better than following any fixed rule.

Adapter Efficiency Analysis

Let's compare the parameter overhead of adapters at different bottleneck dimensions for a BERT-base model with 12 layers and hidden dimension 768:

Adapter parameter overhead at different bottleneck dimensions for a 12-layer BERT-base model with dual (Houlsby) placement.
Bottleneck DimParams per AdapterTotal Adapter Params% of BERT-base
8~12K~288K0.26%
32~49K~1.2M1.1%
64~98K~2.4M2.2%
256~393K~9.4M8.6%

Even at r=256r = 256, adapters add less than 10% of the base model's parameters. This efficiency enables practical storage of dozens of task-specific adapters alongside a single frozen base model. For organizations deploying models across many tasks, this reduces storage and memory use as well as model-management overhead compared to maintaining separate fully fine-tuned models for each task. A fleet of 50 task adapters at r=64r = 64 would together consume only about 120 million parameters, roughly equal to one copy of BERT-base, while supporting 50 completely different tasks.

Out[5]:
Visualization
Area chart showing compression ratio decreasing from 96 at r=8 to 1.5 at r=512, with a green shaded region showing the recommended bottleneck range of 32 to 128.
Information compression ratio (d/r) versus bottleneck dimension for a BERT-base model with hidden dimension 768. The shaded green region marks the recommended range of 32 to 128, where compression is sufficient to regularize without overly restricting capacity.
Line chart comparing accuracy versus bottleneck dimension for a simple task and a complex task, both showing rapid improvement up to dimension 64 and leveling off thereafter.
Simulated accuracy curves versus bottleneck dimension for simple and complex tasks, with dashed lines showing full fine-tuning baselines. Both tasks exhibit diminishing returns beyond dimension 128, confirming the recommended range for most applications.

Worked Example

Let's trace through an adapter computation manually to build deep intuition for how the bottleneck transformation works. Following the computation step by step reveals how each component contributes to the final output, verifies our understanding of the mathematics, and clarifies the interaction between compression, nonlinearity, and the residual connection. Working through the numbers by hand is particularly valuable for understanding what "near-zero initialization" means concretely and why the residual produces small, targeted modifications rather than wholesale replacement of the representation.

Consider a simplified scenario with hidden dimension d=4d = 4 and bottleneck dimension r=2r = 2. While real adapters operate in much higher dimensions, this small example allows us to see every number in the computation and check each step. We begin with an input hidden state representing a token in our transformer:

h=[0.5−0.30.8−0.1]\mathbf{h} = \begin{bmatrix} 0.5 \\ -0.3 \\ 0.8 \\ -0.1 \end{bmatrix}

This vector might represent, for example, the token "review" in the sentence "Write a product review." The four dimensions capture different aspects of the token's meaning and context as computed by the layers above. Our adapter has the following weights, which we treat as fixed for this example but would in practice be learned through gradient descent:

Wdown=[0.40.2−0.10.5−0.20.50.30.1],bdown=[0.1−0.1]\mathbf{W}_{down} = \begin{bmatrix} 0.4 & 0.2 & -0.1 & 0.5 \\ -0.2 & 0.5 & 0.3 & 0.1 \end{bmatrix}, \quad \mathbf{b}_{down} = \begin{bmatrix} 0.1 \\ -0.1 \end{bmatrix} Wup=[0.3−0.20.10.4−0.20.20.5−0.1],bup=[0000]\mathbf{W}_{up} = \begin{bmatrix} 0.3 & -0.2 \\ 0.1 & 0.4 \\ -0.2 & 0.2 \\ 0.5 & -0.1 \end{bmatrix}, \quad \mathbf{b}_{up} = \begin{bmatrix} 0 \\ 0 \\ 0 \\ 0 \end{bmatrix}

The zero bias in bup\mathbf{b}_{up} reflects the near-zero initialization strategy. At initialization, both weight matrices would be set to small random values close to zero. This keeps the adapter contributes nearly nothing to start.

Step 1: Down-projection

The first step compresses the 4-dimensional input to a 2-dimensional bottleneck representation. This linear transformation identifies which combinations of input features are most relevant for the task. We compute the matrix-vector product by taking the dot product of each row of Wdown\mathbf{W}_{down} with the input vector h\mathbf{h}:

Wdown⋅h=[0.4(0.5)+0.2(−0.3)+(−0.1)(0.8)+0.5(−0.1)(−0.2)(0.5)+0.5(−0.3)+0.3(0.8)+0.1(−0.1)]\mathbf{W}_{down} \cdot \mathbf{h} = \begin{bmatrix} 0.4(0.5) + 0.2(-0.3) + (-0.1)(0.8) + 0.5(-0.1) \\ (-0.2)(0.5) + 0.5(-0.3) + 0.3(0.8) + 0.1(-0.1) \end{bmatrix}

Computing each element explicitly:

z1=0.20−0.06−0.08−0.05=0.01z2=−0.10−0.15+0.24−0.01=−0.02\begin{aligned} z_1 &= 0.20 - 0.06 - 0.08 - 0.05 = 0.01 \\ z_2 &= -0.10 - 0.15 + 0.24 - 0.01 = -0.02 \end{aligned}

Adding the bias shifts the compressed representation:

z=[0.01+0.1−0.02−0.1]=[0.11−0.12]\mathbf{z} = \begin{bmatrix} 0.01 + 0.1 \\ -0.02 - 0.1 \end{bmatrix} = \begin{bmatrix} 0.11 \\ -0.12 \end{bmatrix}

The down-projection has compressed our 4-dimensional input into a 2-dimensional representation. The values are small, which is typical for a nearly-initialized adapter: the weight matrix is not yet "tuned" to emphasize large variations in the input, so the compressed representation is close to the bias.

Step 2: Nonlinearity (ReLU)

The activation function introduces nonlinearity, letting the adapter to model complex patterns that no linear transformation could capture. ReLU zeroes out negative values while passing positive values unchanged:

f(z)=ReLU(z)=[max⁡(0,0.11)max⁡(0,−0.12)]=[0.110]f(\mathbf{z}) = \text{ReLU}(\mathbf{z}) = \begin{bmatrix} \max(0, 0.11) \\ \max(0, -0.12) \end{bmatrix} = \begin{bmatrix} 0.11 \\ 0 \end{bmatrix}

Notice that the second dimension is zeroed by ReLU, because −0.12<0-0.12 < 0. Effectively, only one of the two bottleneck dimensions contributes to the output at this point in our example. This sparsity is characteristic of ReLU networks: negative activations are simply shut off, creating a selective routing of information. Depending on the input, different bottleneck dimensions will be active, letting the adapter to implement different transformations for different types of inputs. This context-dependent routing is a key reason why the nonlinearity is needed: without it, the adapter would apply exactly the same linear transformation to every input, regardless of content.

Step 3: Up-projection

The final transformation expands the activated bottleneck representation back to the original hidden dimension. Since the second dimension of f(z)f(\mathbf{z}) is zero, only the first column of Wup\mathbf{W}_{up} contributes to the output, making the computation particularly clean:

Adapter(h)=Wup⋅f(z)+bup\text{Adapter}(\mathbf{h}) = \mathbf{W}_{up} \cdot f(\mathbf{z}) + \mathbf{b}_{up} =[0.3(0.11)+(−0.2)(0)0.1(0.11)+0.4(0)(−0.2)(0.11)+0.2(0)0.5(0.11)+(−0.1)(0)]+[0000]=[0.0330.011−0.0220.055]= \begin{bmatrix} 0.3(0.11) + (-0.2)(0) \\ 0.1(0.11) + 0.4(0) \\ (-0.2)(0.11) + 0.2(0) \\ 0.5(0.11) + (-0.1)(0) \end{bmatrix} + \begin{bmatrix} 0 \\ 0 \\ 0 \\ 0 \end{bmatrix} = \begin{bmatrix} 0.033 \\ 0.011 \\ -0.022 \\ 0.055 \end{bmatrix}

The up-projection produces a 4-dimensional output. Each element is the scalar multiple of 0.11 times the corresponding entry in the first column of Wup\mathbf{W}_{up}. The result is a small modification vector: positive adjustments to dimensions 1, 2, and 4; a small negative adjustment to dimension 3. This pattern reflects the task-specific correction the adapter has learned to apply.

Step 4: Residual connection

Finally, the adapter output is added to the original input through the residual connection, combining the pretrained representation with the task-specific modification:

h′=h+Adapter(h)=[0.5−0.30.8−0.1]+[0.0330.011−0.0220.055]=[0.533−0.2890.778−0.045]\mathbf{h}' = \mathbf{h} + \text{Adapter}(\mathbf{h}) = \begin{bmatrix} 0.5 \\ -0.3 \\ 0.8 \\ -0.1 \end{bmatrix} + \begin{bmatrix} 0.033 \\ 0.011 \\ -0.022 \\ 0.055 \end{bmatrix} = \begin{bmatrix} 0.533 \\ -0.289 \\ 0.778 \\ -0.045 \end{bmatrix}

The adapter has made small, targeted adjustments to each dimension of the hidden state. Notice how the original structure is preserved: the values remain in roughly the same range as before, with only modest shifts in each dimension. The third dimension decreased slightly (0.800 to 0.778), while the fourth changed most dramatically relative to its original value (from −0.100-0.100 to −0.045-0.045, almost halving the magnitude of the negative value). The residual connection ensures these changes are additive modifications rather than complete replacements, which is the basic mechanism that allows adapters to retain pretrained knowledge while learning new behaviors.

This example uses a nearly-converged adapter (weights are small but non-trivial). For a freshly initialized adapter with weights close to zero, the modification vector would be much closer to 0\mathbf{0}, and h′≈h\mathbf{h}' \approx \mathbf{h}. The adapter gradually grows its influence through training as gradients accumulate.

Out[6]:
Visualization
Horizontal bar chart of four input values h1 through h4, with positive values in blue and negative values in red, ranging from about negative 0.3 to 0.8.
Input hidden state h with four dimensions (d=4) represented as a horizontal bar chart, showing positive and negative values before adapter transformation.
Horizontal bar chart of two bottleneck values z1 and z2 after ReLU, with z1 positive at 0.11 and z2 at zero with an arrow annotation reading ReLU zeroed.
Bottleneck representation after down-projection and ReLU activation (r=2). The second dimension is zeroed by ReLU, showing how the nonlinearity creates sparse, selective routing of information.
Horizontal bar chart of four adapter output deltas delta-1 through delta-4, showing small positive and negative modifications between negative 0.022 and 0.055.
Adapter output delta after up-projection back to d=4. The small values show how adapters make targeted modifications rather than wholesale replacements of the representation.
Stacked horizontal bar chart showing the original hidden state in gray and the adapter delta in color for each output dimension h-prime-1 through h-prime-4.
Final output h-prime showing the stacked combination of the original hidden state (gray) and the adapter delta (colored), illustrating how the residual connection preserves the original structure while incorporating task-specific corrections.

Code Implementation

Let's implement adapter layers from scratch and then integrate them into a pretrained transformer model. The goal is to understand the mechanics well enough to adapt any model architecture, not just to call a library function. We will build the adapter module, integrate it into a transformer layer, implement the weight-freezing logic, and verify that our implementation behaves correctly.

In[7]:
Code
import torch.nn as nn  # noqa: F401
import torch.nn.functional as F  # noqa: F401

Building the Adapter Module

We start by implementing a single adapter module as a PyTorch nn.Module. The implementation follows directly from the formula Adapter(h)=Wup⋅f(Wdown⋅h+bdown)+bup\text{Adapter}(\mathbf{h}) = \mathbf{W}_{up} \cdot f(\mathbf{W}_{down} \cdot \mathbf{h} + \mathbf{b}_{down}) + \mathbf{b}_{up}, with the residual connection added in forward. The _init_weights method implements the near-zero initialization strategy:

In[8]:
Code
class AdapterModule(nn.Module):
    """
    Bottleneck adapter module with residual connection.

    Args:
        hidden_dim: The transformer's hidden dimension
        bottleneck_dim: The adapter's bottleneck dimension
        activation: Activation function ('relu' or 'gelu')
    """

    def __init__(self, hidden_dim, bottleneck_dim, activation="relu"):
        super().__init__()

        # Down-projection: d -> r
        self.down_proj = nn.Linear(hidden_dim, bottleneck_dim)

        # Up-projection: r -> d
        self.up_proj = nn.Linear(bottleneck_dim, hidden_dim)

        # Activation function
        if activation == "relu":
            self.activation = nn.ReLU()
        elif activation == "gelu":
            self.activation = nn.GELU()
        else:
            raise ValueError(f"Unknown activation: {activation}")

        # Initialize for near-identity at start
        self._init_weights()

    def _init_weights(self):
        # Initialize down-projection with small random values
        nn.init.normal_(self.down_proj.weight, std=0.01)
        nn.init.zeros_(self.down_proj.bias)

        # Initialize up-projection to near-zero for identity initialization
        nn.init.normal_(self.up_proj.weight, std=0.01)
        nn.init.zeros_(self.up_proj.bias)

    def forward(self, x):
        # Bottleneck transformation
        residual = x
        x = self.down_proj(x)
        x = self.activation(x)
        x = self.up_proj(x)

        # Residual connection
        return residual + x

The _init_weights method is important for stable training. By initializing both projection matrices with a very small standard deviation (0.01), the initial adapter output is close to zero. Biases are initialized to zero. Combined with the residual connection, this means the model's initial behavior is essentially identical to the frozen pretrained model. Without this initialization, the random adapter weights could produce large, disruptive modifications to the representations from the very first forward pass, making training unstable.

Let's verify our adapter module works correctly and count its parameters:

In[9]:
Code
import torch

# Create an adapter for BERT-base dimensions
hidden_dim = 768
bottleneck_dim = 64

adapter = AdapterModule(hidden_dim, bottleneck_dim)

# Count parameters
total_params = sum(p.numel() for p in adapter.parameters())
trainable_params = sum(
    p.numel() for p in adapter.parameters() if p.requires_grad
)

# Test forward pass
batch_size, seq_len = 4, 128
x = torch.randn(batch_size, seq_len, hidden_dim)
output = adapter(x)
Out[10]:
Console
Adapter configuration:
  Hidden dimension: 768
  Bottleneck dimension: 64
  Total parameters: 99,136

Expected parameters:
  Down projection: 49,216
  Up projection: 49,920

Input shape: torch.Size([4, 128, 768])
Output shape: torch.Size([4, 128, 768])

The adapter adds approximately 99,000 parameters, which is only about 0.09% of BERT-base's 110 million parameters. The output shape matches the input shape exactly: the adapter preserves the batch size, sequence length, and hidden dimension throughout its computation. Notice that the total parameter count matches our formula: 2rd+r+d=2(64)(768)+64+768=98,304+832=99,1362rd + r + d = 2(64)(768) + 64 + 768 = 98{,}304 + 832 = 99{,}136.

Integrating Adapters into a Transformer Layer

Now let's create a modified transformer layer that includes adapters. This implementation supports the three main placement strategies we discussed, letting easy comparison between Houlsby dual placement, Pfeiffer FFN-only placement, and attention-only placement:

In[11]:
Code
class AdaptedTransformerLayer(nn.Module):
    """
    Transformer layer with adapter modules inserted after attention and FFN.

    This implements the original Houlsby et al. placement strategy.
    """

    def __init__(
        self,
        hidden_dim,
        num_heads,
        ffn_dim,
        bottleneck_dim,
        dropout=0.1,
        adapter_placement="both",
    ):
        super().__init__()

        # Standard transformer components
        self.self_attn = nn.MultiheadAttention(
            hidden_dim, num_heads, dropout=dropout, batch_first=True
        )
        self.ffn = nn.Sequential(
            nn.Linear(hidden_dim, ffn_dim),
            nn.GELU(),
            nn.Linear(ffn_dim, hidden_dim),
            nn.Dropout(dropout),
        )

        self.norm1 = nn.LayerNorm(hidden_dim)
        self.norm2 = nn.LayerNorm(hidden_dim)
        self.dropout = nn.Dropout(dropout)

        # Adapter modules
        self.adapter_placement = adapter_placement

        if adapter_placement in ["both", "attention"]:
            self.adapter_attn = AdapterModule(hidden_dim, bottleneck_dim)

        if adapter_placement in ["both", "ffn"]:
            self.adapter_ffn = AdapterModule(hidden_dim, bottleneck_dim)

    def forward(self, x, attention_mask=None):
        # Self-attention sublayer
        attn_out, _ = self.self_attn(x, x, x, key_padding_mask=attention_mask)
        attn_out = self.dropout(attn_out)

        # Apply adapter after attention (if configured)
        if self.adapter_placement in ["both", "attention"]:
            attn_out = self.adapter_attn(attn_out)

        x = self.norm1(x + attn_out)

        # Feed-forward sublayer
        ffn_out = self.ffn(x)

        # Apply adapter after FFN (if configured)
        if self.adapter_placement in ["both", "ffn"]:
            ffn_out = self.adapter_ffn(ffn_out)

        x = self.norm2(x + ffn_out)

        return x

Let's compare parameter counts for different adapter placements to verify the theoretical analysis:

In[12]:
Code
# Configuration
hidden_dim = 768
num_heads = 12
ffn_dim = 3072
bottleneck_dim = 64

# Create layers with different adapter placements
layer_both = AdaptedTransformerLayer(
    hidden_dim, num_heads, ffn_dim, bottleneck_dim, adapter_placement="both"
)
layer_ffn_only = AdaptedTransformerLayer(
    hidden_dim, num_heads, ffn_dim, bottleneck_dim, adapter_placement="ffn"
)
layer_no_adapter = AdaptedTransformerLayer(
    hidden_dim, num_heads, ffn_dim, bottleneck_dim, adapter_placement="none"
)


# Count parameters
def count_adapter_params(layer):
    adapter_params = 0
    for name, module in layer.named_modules():
        if isinstance(module, AdapterModule):
            adapter_params += sum(p.numel() for p in module.parameters())
    return adapter_params


def count_total_params(layer):
    return sum(p.numel() for p in layer.parameters())
Out[13]:
Console
Parameter comparison for different adapter placements:

Placement               Total Params  Adapter Params   Overhead
-----------------------------------------------------------------
Both (Houlsby)             7,286,144         198,272       2.8%
FFN only (Pfeiffer)        7,187,008          99,136       1.4%
No adapters                7,087,872               0       0.0%

The FFN-only placement uses half the adapter parameters while often achieving comparable performance to the original dual placement. The numbers confirm our theoretical analysis: Houlsby placement adds roughly twice the adapter parameters compared to Pfeiffer, while the base transformer layer parameters remain constant.

Freezing Pretrained Weights

A key aspect of adapter-based fine-tuning is keeping the pretrained weights frozen. The entire point of adapters is that the pretrained model's knowledge is preserved while only the small adapter modules learn task-specific behaviors. This requires explicitly marking all base model parameters as non-trainable and all adapter parameters as trainable. Let's implement a utility to prepare a model for adapter training:

In[14]:
Code
def prepare_model_for_adapter_training(model, adapter_names=["adapter"]):
    """
    Freeze all parameters except adapter modules.

    Args:
        model: The model to prepare
        adapter_names: List of substrings to identify adapter parameters
    """
    # First, freeze all parameters
    for param in model.parameters():
        param.requires_grad = False

    # Then, unfreeze adapter parameters
    unfrozen_count = 0
    for name, param in model.named_parameters():
        if any(adapter_name in name for adapter_name in adapter_names):
            param.requires_grad = True
            unfrozen_count += 1

    # Count trainable vs total parameters
    total_params = sum(p.numel() for p in model.parameters())
    trainable_params = sum(
        p.numel() for p in model.parameters() if p.requires_grad
    )

    return {
        "total_params": total_params,
        "trainable_params": trainable_params,
        "trainable_pct": trainable_params / total_params * 100,
        "unfrozen_modules": unfrozen_count,
    }
In[15]:
Code
# Demonstrate on our adapted layer
stats = prepare_model_for_adapter_training(layer_both)
Out[16]:
Console
Model preparation for adapter training:

Total parameters: 7,286,144
Trainable parameters: 198,272
Trainable percentage: 2.72%

Parameter status by component:
  self_attn.in_proj_weight                 Frozen
  self_attn.in_proj_bias                   Frozen
  self_attn.out_proj.weight                Frozen
  self_attn.out_proj.bias                  Frozen
  ffn.0.weight                             Frozen
  ffn.0.bias                               Frozen
  ffn.2.weight                             Frozen
  ffn.2.bias                               Frozen
  norm1.weight                             Frozen
  norm1.bias                               Frozen
  norm2.weight                             Frozen
  norm2.bias                               Frozen
  adapter_attn.down_proj.weight            Trainable
  adapter_attn.down_proj.bias              Trainable
  adapter_attn.up_proj.weight              Trainable
  adapter_attn.up_proj.bias                Trainable
  adapter_ffn.down_proj.weight             Trainable
  adapter_ffn.down_proj.bias               Trainable
  adapter_ffn.up_proj.weight               Trainable
  adapter_ffn.up_proj.bias                 Trainable

Only the adapter modules remain trainable, while all original transformer weights are frozen. This is the basic mechanism that enables parameter-efficient fine-tuning: the pretrained model is locked in place, and only the small adapter parameters are exposed to gradient updates from the downstream task.

Visualizing Adapter Behavior

Let's visualize how an adapter transforms hidden representations and how its output magnitude evolves during training:

Out[17]:
Visualization
Bar chart with three bars labeled Input (d=768), Bottleneck (r=64), and Output (d=768). The middle bar is much shorter, illustrating the 12:1 compression ratio with an annotation arrow.
Visualization of adapter bottleneck compression for BERT-base dimensions. The input and output both have dimension 768, while the bottleneck compresses to dimension 64, a 12:1 compression ratio that forces the adapter to learn compact, generalizable representations.
Out[18]:
Visualization
Line chart showing the average adapter output norm over 50 training epochs, starting near zero and rising in a saturation curve toward about 0.16.
Adapter output magnitude during training, showing simulated growth from near-zero initialization. The adapter starts with negligible influence on the representations, preserving the pretrained model's behavior, and gradually increases its contribution as task-specific patterns are learned. This graceful growth is a key advantage of near-zero weight initialization.

The training dynamics visualization reveals two key aspects of adapter behavior. First, the adapter starts with negligible influence on the representations: the near-zero initialization ensures the model's initial behavior is essentially identical to the frozen pretrained model. Second, the influence grows gradually and saturates: early training epochs see rapid growth as the adapter discovers the most important task-specific adjustments, and later epochs show diminishing marginal improvement as the adapter refines its learned transformation. This saturation behavior is a healthy sign; it indicates the adapter is not continuing to grow without bound, which would suggest instability.

Adapter Fusion

When you have multiple tasks and want a model that can handle all of them, you face a basic choice: train a single multi-task model and risk interference between tasks, or train separate adapters for each task and find a principled way to combine them at inference time. Adapter fusion takes the second approach. This provides a learned mechanism for combining multiple task-specific adapters. This technique enables flexible composition of specialized knowledge without the interference problems that often plague joint multi-task training, where improving performance on one task can degrade another.

The key insight behind adapter fusion is that task-specific knowledge, once learned, can be combined through attention rather than through gradient-based joint training. Instead of trying to learn a single set of parameters that works well for all tasks simultaneously (which creates competing objectives during training), we allow each adapter to specialize fully, and then learn how to mix their expertise for new inputs. This separation of the learning problem into two stages, specialization then combination, is what makes adapter fusion work where naive parameter averaging often fails.

The Multi-Adapter Challenge

Imagine you have trained separate adapters for sentiment analysis, named entity recognition, and question answering. Each adapter has learned to transform the base model's representations for its specific task, encoding knowledge about sentiment lexicons, entity boundaries, or answer extraction strategies. Now you encounter a task that requires reasoning about both sentiment and entities, such as analyzing customer reviews to identify which product features receive positive versus negative sentiment.

A simple approach like averaging the adapter outputs treats all adapters as equally relevant for every input, which is clearly wrong. A token like "Apple" in a product review might need strong NER adapter influence to recognize it as an organization entity, while a token like "amazing" should draw primarily from the sentiment adapter to capture its positive polarity. What you need is a learned, input-dependent weighting of adapter contributions: an attention mechanism that can dynamically allocate influence based on what each token requires.

Adapter fusion provides exactly this. Rather than applying a fixed combination strategy, it trains a small attention module that, given the current hidden state, asks each adapter "what do you know that's relevant here?" and weights their contributions accordingly. This learned routing enables the fused model to handle inputs that require specialized knowledge from different task adapters without the adapter outputs interfering with each other.

AdapterFusion Architecture

AdapterFusion introduces a fusion layer that combines the outputs of multiple pretrained adapters using attention. This approach treats the combination of adapters as itself a learning problem, where the model must discover how to best use each adapter's expertise for a new task.

We want to produce a combined output that is a weighted sum of adapter contributions, where the weights depend on the input context. Given NN task-specific adapters with outputs {a1,a2,…,aN}\{\mathbf{a}_1, \mathbf{a}_2, \ldots, \mathbf{a}_N\} for input h\mathbf{h}, the fusion layer computes:

AdapterFusion(h)=∑i=1Nαi⋅vi\text{AdapterFusion}(\mathbf{h}) = \sum_{i=1}^{N} \alpha_i \cdot \mathbf{v}_i

where:

  • AdapterFusion(h)\text{AdapterFusion}(\mathbf{h}): the combined output for the input, representing a learned mixture of all adapter contributions weighted by their relevance to the current context
  • h\mathbf{h}: the input hidden state, which provides context for determining how to weight the adapters
  • αi\alpha_i: the attention weight for the ii-th adapter, showing how much that adapter should contribute to this particular input. These weights are non-negative and sum to 1 due to the softmax normalization
  • vi\mathbf{v}_i: the value vector for the ii-th adapter, projected from the adapter output by the linear map vi=ai⋅WV\mathbf{v}_i = \mathbf{a}_i \cdot \mathbf{W}_V. This projection allows the fusion layer to transform adapter outputs into a form suitable for combination
  • NN: the total number of task adapters being fused together

The attention weights αi\alpha_i are computed via a query-key-value attention mechanism, following the same pattern as the multi-head attention we studied in transformer architectures. The query comes from the hidden state (encoding "what do I need?"), the keys come from adapter outputs (encoding "what do I provide?"), and the dot product between them produces scores that, after softmax normalization, become the weights:

α=softmax(Q⋅KTdk)\alpha = \text{softmax}\left(\frac{\mathbf{Q} \cdot \mathbf{K}^T}{\sqrt{d_k}}\right)

where:

  • α\alpha: the vector of attention weights across all NN adapters, summing to 1 due to the softmax normalization
  • softmax\text{softmax}: the function that converts raw scores into a probability distribution, so the weights form a valid convex combination
  • Q\mathbf{Q}: the query derived from the hidden state, calculated as h⋅WQ\mathbf{h} \cdot \mathbf{W}_Q. The query encodes "what kind of adapter output is needed for this input?"
  • K\mathbf{K}: the keys derived from adapter outputs, calculated as stacking each ai⋅WK\mathbf{a}_i \cdot \mathbf{W}_K. Each key encodes "what kind of information does this adapter provide?"
  • dk\sqrt{d_k}: the scaling factor based on key dimension dkd_k to stabilize gradients and prevent the softmax from creating overly peaked distributions that would effectively select only one adapter

The fusion weights WQ\mathbf{W}_Q, WK\mathbf{W}_K, and WV\mathbf{W}_V are the only new parameters trained during fusion. All individual adapter weights remain frozen, preserving the task-specific knowledge each adapter has learned. The fusion mechanism discovers how to combine them effectively without any risk of gradient updates corrupting the specialized knowledge that was carefully learned in stage one.

Implementation

Let's implement a basic adapter fusion layer:

In[19]:
Code
class AdapterFusion(nn.Module):
    """
    Combines multiple adapter outputs using learned attention weights.

    Args:
        hidden_dim: Transformer hidden dimension
        num_adapters: Number of adapters to fuse
    """

    def __init__(self, hidden_dim, num_adapters):
        super().__init__()

        self.hidden_dim = hidden_dim
        self.num_adapters = num_adapters

        # Attention parameters for fusion
        self.query_proj = nn.Linear(hidden_dim, hidden_dim)
        self.key_proj = nn.Linear(hidden_dim, hidden_dim)
        self.value_proj = nn.Linear(hidden_dim, hidden_dim)

        self.scale = hidden_dim**-0.5

    def forward(self, hidden_state, adapter_outputs):
        """
        Args:
            hidden_state: Current hidden state [batch, seq_len, hidden_dim]
            adapter_outputs: List of N adapter outputs, each [batch, seq_len, hidden_dim]

        Returns:
            Fused output [batch, seq_len, hidden_dim]
        """
        batch_size, seq_len, _ = hidden_state.shape

        # Stack adapter outputs: [batch, seq_len, num_adapters, hidden_dim]
        stacked = torch.stack(adapter_outputs, dim=2)

        # Query from hidden state: [batch, seq_len, 1, hidden_dim]
        query = self.query_proj(hidden_state).unsqueeze(2)

        # Keys and values from adapter outputs
        # [batch, seq_len, num_adapters, hidden_dim]
        keys = self.key_proj(stacked)
        values = self.value_proj(stacked)

        # Compute attention scores: [batch, seq_len, 1, num_adapters]
        scores = torch.matmul(query, keys.transpose(-2, -1)) * self.scale
        weights = F.softmax(scores, dim=-1)

        # Apply attention: [batch, seq_len, 1, hidden_dim]
        fused = torch.matmul(weights, values)

        # Remove singleton dimension: [batch, seq_len, hidden_dim]
        return fused.squeeze(2), weights.squeeze(2)

Let's test the fusion layer with simulated adapter outputs to verify the shapes and weight normalization:

In[20]:
Code
# Create fusion layer
hidden_dim = 768
num_adapters = 3
fusion = AdapterFusion(hidden_dim, num_adapters)

# Simulate adapter outputs for different tasks
batch_size, seq_len = 2, 16
hidden_state = torch.randn(batch_size, seq_len, hidden_dim)

# Each adapter produces different outputs
adapter_outputs = [
    torch.randn(batch_size, seq_len, hidden_dim) * 0.1,  # Sentiment adapter
    torch.randn(batch_size, seq_len, hidden_dim) * 0.1,  # NER adapter
    torch.randn(batch_size, seq_len, hidden_dim) * 0.1,  # QA adapter
]

# Compute fused output
fused_output, attention_weights = fusion(hidden_state, adapter_outputs)
Out[21]:
Console
AdapterFusion demonstration:

Input hidden state shape: torch.Size([2, 16, 768])
Number of adapters: 3
Fused output shape: torch.Size([2, 16, 768])
Attention weights shape: torch.Size([2, 16, 3])

Sample attention weights (first token):
  Adapter 1 (Sentiment): 0.324
  Adapter 2 (NER):       0.347
  Adapter 3 (QA):        0.329
  Sum: 1.000

The fusion layer produces soft attention weights that sum to 1.0, confirming that the softmax normalization is working correctly. These weights are learned during training on the target task, and different tokens will receive different weight distributions based on their content and context.

Training Adapter Fusion

Adapter fusion uses a two-stage training process that separates the specialization phase from the combination phase. This separation is basic to why fusion works: if we tried to train all components jointly from the start, the base model and the individual adapters would receive mixed gradients from multiple tasks, leading to the interference problems that the approach is designed to avoid.

Stage one trains individual adapters on their respective tasks with the base model frozen. Each adapter becomes an expert at its task, developing specialized representations that encode task-relevant patterns. Importantly, this stage can be done independently and asynchronously: the sentiment adapter training does not need to know about or coordinate with the NER adapter training. This parallelism is a practical advantage for teams working on multiple tasks.

Stage two trains the fusion layers with both the base model and individual adapters frozen. Only the fusion attention weights (WQ\mathbf{W}_Q, WK\mathbf{W}_K, WV\mathbf{W}_V) are trainable. This ensures the specialized knowledge in each adapter is preserved: the fusion mechanism cannot corrupt the adapters because they receive no gradient updates during this stage. The fusion layer learns to selectively access the right adapter expertise for each input, token by token.

In[22]:
Code
def prepare_for_fusion_training(model, adapters, fusion_layers):
    """
    Prepare model for AdapterFusion training.
    Freezes base model and adapters, leaves only fusion layers trainable.
    """
    # Freeze base model
    for param in model.parameters():
        param.requires_grad = False

    # Freeze all adapters
    for adapter in adapters:
        for param in adapter.parameters():
            param.requires_grad = False

    # Keep fusion layers trainable
    for fusion in fusion_layers:
        for param in fusion.parameters():
            param.requires_grad = True

    # Count parameters
    fusion_params = sum(
        sum(p.numel() for p in f.parameters()) for f in fusion_layers
    )

    return fusion_params
In[23]:
Code
# Demonstrate parameter counting
dummy_model = nn.Linear(768, 768)  # Simplified base model
adapters = [AdapterModule(768, 64) for _ in range(3)]
fusion_layers = [AdapterFusion(768, 3)]

fusion_param_count = prepare_for_fusion_training(
    dummy_model, adapters, fusion_layers
)
Out[24]:
Console
AdapterFusion training setup:

Fusion layer parameters: 1,771,776
(3 projection matrices x 768 x 768 = 1,769,472)

Trainable parameter status:
  Base model: Frozen
  Adapters: Frozen
  Fusion layers: Trainable

The fusion layer adds about 1.8 million parameters (three 768×768768 \times 768 projection matrices), which is still far smaller than training the full base model. For three adapters and a 12-layer model with one fusion layer per transformer layer, the total fusion parameters would be 12×1.8M≈21.6M12 \times 1.8\text{M} \approx 21.6\text{M}, about 20% of BERT-base. This is higher than a single adapter set but enables flexible combination of three specialized adapters for potentially complex tasks.

Visualizing Adapter Fusion

Let's visualize how fusion weights might vary across different input tokens in a realistic example:

Out[25]:
Visualization
Heatmap with tokens as columns and three adapters (Sentiment, NER, QA) as rows. Entity tokens show high NER weights while the word amazing shows a high sentiment weight.
Adapter fusion attention weights across tokens in a sample sentence. The model learns to emphasize different task adapters based on token content: entity mentions such as iPhone, Apple, Tim, and Cook receive high NER adapter weights, while opinion words such as amazing activate the sentiment adapter. Non-informative tokens such as punctuation receive more uniform weights across all adapters.

The visualization shows how adapter fusion can learn meaningful, input-dependent attention patterns. Entity tokens like "Apple," "Tim," and "Cook" receive high weights from the NER adapter, which has learned to detect entity boundaries and encode entity type information. The opinion word "amazing" strongly activates the sentiment adapter, which has learned to identify and characterize sentiment-bearing tokens. More neutral or structural tokens like punctuation marks tend to receive more uniform weights. This reflects that they do not strongly favor any particular adapter's expertise. This token-level routing of adapter influence is the key capability that separates adapter fusion from simpler combination strategies.

Limitations and Impact

Adapter layers represented a significant advancement in parameter-efficient fine-tuning when introduced, establishing many concepts that influenced later methods including LoRA, prefix tuning, and the broader PEFT ecosystem. However, adapters come with their own set of trade-offs and limitations that practitioners should understand before committing to this approach for a production system.

Inference Overhead

The most significant practical limitation of adapters compared to weight-space methods like LoRA is inference latency. Unlike LoRA, which can merge its low-rank modifications into the base model weights after training (creating a model with exactly the same architecture and therefore the same inference speed as the original), adapters must remain as separate modules during inference. Each forward pass through a transformer layer must also pass through the adapter modules, adding computational operations that cannot be eliminated at deployment time.

For a 12-layer model with dual adapters (Houlsby placement), this means 24 additional sequential operations per inference, one adapter forward pass for each attention sublayer and each FFN sublayer across all 12 transformer layers. While each adapter operation involves relatively small matrices (for a BERT-base model with r=64r = 64, the most expensive operation is a 768×64768 \times 64 matrix multiplication), the cumulative latency can become noticeable in latency-sensitive applications serving high traffic volumes. Benchmarks suggest that adapters add 5 to 15% inference latency compared to the frozen base model, depending on the hardware, batch size, and bottleneck dimension.

This is particularly relevant when compared to LoRA, which we covered in earlier chapters of this part: LoRA's low-rank modifications can be absorbed into the original weight matrices post-training for zero additional inference cost. For applications where inference speed matters, this difference can be decisive. If you are deploying a model on a mobile device, in a real-time interactive application, or at very high throughput, the non-zero inference overhead of adapters may disqualify them in favor of weight-space methods.

Sequential Dependencies

The sequential placement of adapters within the transformer forward pass limits parallelization opportunities. Modern hardware accelerators achieve peak efficiency when they can batch many operations together and execute them in parallel. The adapter's position between the attention sublayer and the residual connection introduces a serial dependency: the attention output must be computed before the adapter can run, and the adapter output must be ready before the layer normalization can proceed. This chain of dependencies limits how much the hardware can overlap operations within a layer.

Parallel adapter variants address this partially by letting the adapter computation to run concurrently with the sublayer computation. However, the basic architectural choice of inserting modules into the sequential forward pass creates constraints that approaches that modify weight matrices avoid. For very large models where transformer layers themselves take significant time, these sequential dependencies become more important relative to overall throughput.

Activation Memory

During training, adapters require storing additional activations for backpropagation through their bottleneck layers. The input to each adapter (the full hidden state of dimension dd) must be cached to compute gradients for the down-projection matrix. The bottleneck activation (of dimension rr) must also be cached to compute gradients for the up-projection matrix. For large batch sizes or long sequences, this memory overhead compounds across all adapter modules in the model.

The bottleneck architecture mitigates this somewhat since the intermediate representation is small (dimension rr rather than dd or the FFN expansion dimension). But the input to each adapter is still the full hidden state, so activation memory scales with sequence length and batch size just as it does for the base model's layers. In practice, this means that training with adapters at the same batch size as fine-tuning without them requires somewhat more GPU memory, though the increase is modest compared to the memory saved by freezing the base model weights.

Task Interference in Fusion

While adapter fusion addresses the interference problem between tasks at the deployment level, it introduces a new challenge during the fusion training stage. The fusion attention mechanism must learn to correctly route inputs to the right adapters, but with imperfect supervision. If the fusion training data does not adequately cover all the input patterns that the individual adapters have been trained on, the fusion mechanism may learn to rely too heavily on one adapter and underutilize others.

Additionally, adapter fusion increases the total number of parameters that need to be stored: instead of one adapter per task, you need one adapter per source task plus fusion layers for the combined model. For organizations managing many tasks, the bookkeeping complexity of maintaining adapter libraries, tracking which adapters are available for fusion, and so compatibility between adapters trained on different base model versions can become significant.

Historical Impact and Legacy

Despite these limitations, adapters proved several important concepts that shaped the entire parameter-efficient fine-tuning field and continue to influence how we think about model adaptation. First, they demonstrated that additive fine-tuning works: the success of adapters on competitive benchmarks showed that pretrained models contain sufficient knowledge to adapt to new tasks with minimal additional parameters. This finding overturned the prevailing assumption that fine-tuning required updating all model parameters to achieve competitive performance.

Second, the bottleneck architecture introduced a design pattern that appears repeatedly in subsequent PEFT methods. The idea of compressing representations through a low-dimensional bottleneck to enforce efficient encoding has proven remarkably durable, appearing in various forms in later approaches including intrinsic dimensionality methods and structured pruning techniques.

Third, the modular adaptation paradigm, where swappable task-specific modules can be added to and removed from a shared base model, enabled new deployment patterns and directly influenced the development of mixture-of-experts approaches. The concept of maintaining a single frozen base model with interchangeable task-specific components is now standard practice in large-scale model deployment.

Fourth, adapter fusion particularly influenced the multi-task learning literature. By showing that separately trained adaptations could be effectively combined without catastrophic interference between tasks, it opened a path to flexible multi-task systems that do not require the difficult joint training optimization that characterizes traditional multi-task learning. This influence can be seen in subsequent work on model merging, task arithmetic, and mixture-of-adapters approaches.

Finally, the need to manage multiple adapters catalyzed the development of tooling and infrastructure for PEFT methods. The Hugging Face PEFT library now supports LoRA and prefix tuning. Prompt tuning and adapters are among the many other methods available in the same framework. It owes much of its organizational philosophy to the adapter paradigm. The library lets users swap configurations, save them, and load them later, directly reflecting the modular nature of adapter layers.

Summary

This chapter explored adapter layers as a parameter-efficient approach to fine-tuning pretrained language models. We covered the foundational concepts from module design through multi-task fusion, examining both the theory and the practical implementation details.

Adapter architecture uses a bottleneck design where a down-projection matrix Wdown\mathbf{W}_{down} compresses the hidden representation from dimension dd to a much smaller bottleneck dimension rr, a nonlinear activation introduces expressive power, and an up-projection matrix Wup\mathbf{W}_{up} restores the original dimension. The residual connection h′=h+Adapter(h)\mathbf{h}' = \mathbf{h} + \text{Adapter}(\mathbf{h}) is necessary: it allows the adapter to learn only the delta needed for the new task while preserving pretrained information in the skip connection, and it enables near-zero initialization for stable training.

Parameter efficiency follows directly from the bottleneck dimension: each adapter contains approximately 2rd2rd parameters, which for r=64r = 64 and d=768d = 768 is roughly 98K parameters compared to over 2M in a single attention layer. A full 12-layer BERT-base with dual adapters adds only about 2.4M trainable parameters, roughly 2.2% of the base model.

Adapter placement can follow the original Houlsby strategy with two adapters per layer (after both attention and FFN sublayers) or the more efficient Pfeiffer approach with a single adapter after the FFN. The Pfeiffer placement often achieves comparable performance with half the parameters, which makes it the recommended default for most applications.

Adapter dimensionality controls the trade-off between capacity and efficiency. Bottleneck dimensions of 32 to 64 provide a good default for most tasks, with the compression ratio forcing adapters to learn generalizable transformations. Very small bottlenecks (8 to 16) provide strong regularization for limited-data settings, while larger bottlenecks (128 to 256) are appropriate for complex tasks requiring substantial deviation from pretrained behavior.

Adapter fusion enables combining multiple task-specific adapters through a learned attention mechanism. The two-stage process, first training individual adapters to specialize, then training small fusion layers to combine them, avoids the interference problems of joint multi-task training. The fusion weights are input-dependent, letting different adapters to contribute different amounts depending on the content of each token.

Inference overhead is the primary practical limitation: unlike LoRA, adapters cannot be merged into base model weights and therefore add latency to every inference call. For latency-sensitive applications, this overhead, typically 5 to 15%, may favor weight-space methods. For applications where modularity and interchangeability are valuable, adapters remain a practical choice.

In the upcoming chapter on PEFT comparison, we will examine how adapters stack up against the other methods we have covered, including LoRA variants, prefix tuning, and prompt tuning, across different tasks and model scales. Understanding the relative strengths of each method will help you make principled choices for your specific use cases.

Quiz

Ready to test your understanding? Take this quick quiz to reinforce what you've learned about adapter layers and their role in parameter-efficient fine-tuning.

Adapter Layers

Question 1 of 80 of 8 completed
What is the primary purpose of the bottleneck architecture in adapter modules?

Comments

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

Reference

Citation details

Cite or share this article.

BIBTEXAcademic
@misc{brenndoerfer2025adapterlayers, author = {Michael Brenndoerfer}, title = {Adapter Layers: Bottleneck Modules for Efficient Fine-Tuning}, year = {2025}, url = {https://mbrenndoerfer.com/writing/adapter-layers-bottleneck-modules-transformer-fine-tuning}, organization = {mbrenndoerfer.com}, note = {Accessed: 2026-10-13} }
APAAcademic
Michael Brenndoerfer (2025). Adapter Layers: Bottleneck Modules for Efficient Fine-Tuning. Retrieved from https://mbrenndoerfer.com/writing/adapter-layers-bottleneck-modules-transformer-fine-tuning
MLAAcademic
Michael Brenndoerfer. "Adapter Layers: Bottleneck Modules for Efficient Fine-Tuning." 2026. Web. October 13, 2026. <https://mbrenndoerfer.com/writing/adapter-layers-bottleneck-modules-transformer-fine-tuning>.
CHICAGOAcademic
Michael Brenndoerfer. "Adapter Layers: Bottleneck Modules for Efficient Fine-Tuning." Accessed October 13, 2026. https://mbrenndoerfer.com/writing/adapter-layers-bottleneck-modules-transformer-fine-tuning.
HARVARDAcademic
Michael Brenndoerfer (2025) 'Adapter Layers: Bottleneck Modules for Efficient Fine-Tuning'. Available at: https://mbrenndoerfer.com/writing/adapter-layers-bottleneck-modules-transformer-fine-tuning (Accessed: October 13, 2026).
SimpleBasic
Michael Brenndoerfer (2025). Adapter Layers: Bottleneck Modules for Efficient Fine-Tuning. https://mbrenndoerfer.com/writing/adapter-layers-bottleneck-modules-transformer-fine-tuning

About the author

Continue with the full handbook

This chapter is part of Language AI Handbook. Use the handbook page to browse the complete table of contents and continue reading in sequence.

Explore Language AI Handbook
Newsletter

Stay up to date

Get articles, book updates, and news delivered to your inbox.

No spam, unsubscribe anytime.

or

Join the community

Sign in to remove popups, track your reading progress, and join the discussion.