Prefix Tuning: Steering LLMs with Learnable Virtual Tokens

Michael BrenndoerferDecember 7, 202564 min read

Part of Language AI Handbook

Explains how prefix tuning adapts transformers by prepending learnable virtual tokens to attention keys and values. A parameter-efficient fine-tuning method.

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

Prefix Tuning

While LoRA modifies how attention weights transform inputs, prefix tuning takes a fundamentally different approach: it changes what the attention mechanism sees rather than how it computes. Instead of learning low-rank updates to weight matrices, prefix tuning prepends learnable continuous vectors to the key and value sequences at each transformer layer. These virtual tokens steer the model's behavior without modifying any original parameters. This distinction changes how we adapt pretrained language models to new tasks.

Prefix tuning is inspired by how people naturally interact with language models. Consider how discrete prompts like "Translate to French:" guide a language model toward a specific task. The presence of those words at the beginning of the input causes the model to attend to different patterns and produce different outputs than it would without them. Prefix tuning asks a natural question: what if we could learn the optimal "prompt" in continuous space, unconstrained by the vocabulary of real tokens? By removing the requirement that our steering signal map to actual words, we gain enormous flexibility. The result is a small set of task-specific vectors that guide attention without the limitations of a discrete vocabulary.

Think of prefix tuning as giving the model a pair of tinted glasses for each task. The model itself, its knowledge and capabilities, remains completely unchanged. But the tinted lenses alter how it perceives everything that passes through attention: they soften certain signals and amplify others, nudging the model toward task-appropriate behaviors at every layer. Just as differently tinted lenses can make a photograph look warm, cool, or large without changing the camera or the scene, different learned prefix vectors can make the same frozen model behave like a translator, a summarizer, or a creative writer.

Introduced by Li and Liang in 2021, prefix tuning emerged from work on controllable generation. Prompt engineering with discrete tokens was found to be fundamentally limited by the expressiveness of individual vocabulary items. Unlike prompt engineering with discrete tokens, prefix tuning learns vectors that can stand for concepts no single token could capture. These continuous vectors can encode subtle combinations of meaning and style that reflect task requirements that would be impossible to express through word selection alone. And unlike full fine-tuning, it leaves the pretrained model completely frozen, letting the same base model to serve multiple tasks simply by swapping prefixes. This modularity proves particularly useful in production environments where a single model must accommodate diverse use cases without requiring separate deployments.

The efficiency numbers are striking. For a model like GPT-2 Large with 774 million parameters, a prefix of length 100 requires only about 1.97 million trainable parameters, representing roughly 0.25% of the base model. Yet experiments show that such a tiny fraction of new parameters can steer the model's outputs as effectively as fine-tuning approaches that modify orders of magnitude more parameters. This efficiency has made prefix tuning a foundational method in the parameter-efficient fine-tuning (PEFT) toolkit, sitting alongside LoRA and adapters as well as prompt tuning as a core technique every practitioner should understand.

Historical Context

Prefix tuning appeared in the same period as other PEFT methods, all responding to the same practical problem: GPT-3 had just demonstrated that scaling language models to 175 billion parameters produced remarkable capabilities, but fine-tuning such a model was prohibitively expensive for most researchers and organizations. The 2021 paper "Prefix-Tuning: Optimizing Continuous Prompts for Generation" by Xiang Lisa Li and Percy Liang at Stanford directly addressed this gap. The paper arrived two months after GPT-3 was announced and can be read as a direct response to the question of how to harness large models without the resources of a major AI lab. It built on earlier discrete prompt tuning work by Brown et al. but identified the key limitation: discrete prompts are constrained by the vocabulary and cannot stand for arbitrary continuous directions in representation space. The continuous relaxation that prefix tuning introduced became the conceptual foundation for a wave of soft prompt methods.

The Prefix Tuning Formulation

To understand prefix tuning deeply, we must first revisit how attention works and why modifying its inputs can be so effective. The attention mechanism determines which parts of the input sequence should influence each other. Through attention, the model learns to associate words with their contexts, track references across long distances, and reason about relationships between ideas. Modifying what attention sees can therefore have a large effect. A small change to the inputs can propagate through the softmax normalization to produce large changes in the output distribution.

As discussed in previous chapters, scaled dot-product attention computes a weighted combination of value vectors, where the weights are determined by the compatibility between query and key vectors. The computation requires three matrices derived from the input: queries that stand for what each position is looking for, keys that stand for what each position offers, and values that stand for the actual information to be retrieved. The beauty of this design is that all three can be separately controlled, which is precisely what prefix tuning exploits.

The standard scaled dot-product attention formula is:

Attention(Q,K,V)=softmax(QKTdk)V\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V

where:

  • QQ: the query matrix, containing row vectors representing "what we are looking for" at each sequence position
  • KK: the key matrix, containing row vectors representing "what each position offers" for matching
  • VV: the value matrix, containing row vectors with "what we retrieve" when a match is found
  • dkd_k: the dimension of the key vectors, used as a scaling factor
  • softmax\text{softmax}: the function converting raw attention scores into a probability distribution over positions

We scale by dk\sqrt{d_k} to prevent the dot products from growing too large in high dimensions, which would cause vanishing gradients in the softmax. To see why this matters: in a dkd_k-dimensional space, if QQ and KK contain random unit vectors, the expected magnitude of their dot product is dk\sqrt{d_k}. Without the scaling factor, attention scores for a model with dk=1024d_k = 1024 would be roughly 32 times larger than for one with dk=1d_k = 1, causing the softmax to become nearly one-hot (peaking sharply at the highest score) and preventing smooth gradient flow. Dividing by dk\sqrt{d_k} keeps attention scores in a well-behaved range regardless of model dimensionality.

These matrices are derived from input representations through learned linear projections. In standard transformers, these matrices come from projecting the input sequence X∈Rn×dX \in \mathbb{R}^{n \times d}:

Q=XWQ,K=XWK,V=XWVQ = XW_Q, \quad K = XW_K, \quad V = XW_V

where:

  • XX: the input sequence matrix of shape n×dn \times d, with one row per token and dd columns per model dimension
  • WQW_Q: the learnable query projection matrix, mapping from dd-dimensional input to dkd_k-dimensional query space
  • WKW_K: the learnable key projection matrix, mapping from dd-dimensional input to dkd_k-dimensional key space
  • WVW_V: the learnable value projection matrix, mapping from dd-dimensional input to dvd_v-dimensional value space

This formulation reveals two potential ways to adapt model behavior. We could modify the projection matrices WQW_Q, WKW_K, and WVW_V, which is the approach taken by methods like LoRA. Alternatively, we could modify what these projections operate on by changing the sequences that become keys and values. Prefix tuning chooses the latter path, leaving all projection matrices completely frozen.

The key insight is that extending the key and value sequences with additional learnable vectors gives every attention head, at every layer, access to new information sources. When a query at any position computes attention scores, it now scores the nn real input positions and the mm virtual prefix positions. The softmax normalization then redistributes attention weights over this extended set. This redistribution is how the prefix "steers" the model: by giving high-quality key-value pairs that receive attention, the prefix can shape which information gets retrieved and aggregated at every position.

Prefix tuning modifies the attention computation by prepending learnable prefix vectors to both keys and values. Let PK∈Rm×dkP_K \in \mathbb{R}^{m \times d_k} and PV∈Rm×dvP_V \in \mathbb{R}^{m \times d_v} be learnable prefix matrices, where mm is the prefix length. The modified attention becomes:

K′=[PK;K],V′=[PV;V]K' = [P_K; K], \quad V' = [P_V; V]

where:

  • K′K': the extended key matrix of shape (m+n)×dk(m + n) \times d_k
  • V′V': the extended value matrix of shape (m+n)×dv(m + n) \times d_v
  • PKP_K: learnable prefix matrix of shape m×dkm \times d_k, containing the virtual key vectors
  • PVP_V: learnable prefix matrix of shape m×dvm \times d_v, containing the virtual value vectors
  • KK: original key matrix of shape n×dkn \times d_k, derived from the real input
  • VV: original value matrix of shape n×dvn \times d_v, derived from the real input
  • [;][;]: concatenation operation along the sequence dimension (vertical stacking)

The semicolon notation indicates vertical stacking of matrices: we place the prefix matrices above the original matrices, effectively creating longer sequences. This means that when we compute attention, every query position can now attend to the original nn key positions and to these mm new prefix positions. The prefix positions give additional "options" for the attention mechanism to draw information from.

The full modified attention computation then attends over the extended sequences:

Attention(Q,K′,V′)=softmax(QK′Tdk)V′\text{Attention}(Q, K', V') = \text{softmax}\left(\frac{Q{K'}^T}{\sqrt{d_k}}\right)V'

where:

  • QQ: the query matrix of shape n×dkn \times d_k (unchanged, derived only from real input tokens)
  • K′K': the extended key matrix of shape (m+n)×dk(m + n) \times d_k
  • V′V': the extended value matrix of shape (m+n)×dv(m + n) \times d_v
  • dkd_k: the key dimension, used as the scaling factor
  • softmax\text{softmax}: now normalizes over m+nm + n positions rather than the original nn

Notice that the queries remain unchanged: we do not prepend anything to them. This asymmetry is intentional and important. The queries stand for what each position in our sequence is looking for. By keeping queries tied to the actual input positions, we ensure that the output sequence maintains the same length as the input. The output of attention remains an n×dvn \times d_v matrix, one row per real input token. The prefixes influence computation by giving additional things to attend to, not additional positions that produce outputs.

Out[4]:
Visualization
Heatmap of an attention matrix where 8 query positions (rows T1-T8) attend over 13 key positions (columns P1-P5 for prefix tokens and T1-T8 for input tokens), separated by a red dashed line, with labeled annotation boxes for prefix and input key regions.
Attention matrix structure showing input tokens attending to both prefix virtual tokens and actual input positions. Rows stand for queries while columns stand for keys, with the leftmost block showing task-specific steering from virtual tokens. Darker shades reveal higher weights, showing how input tokens draw information from both sources.
Virtual Tokens

The prefix vectors PKP_K and PVP_V are called "virtual tokens" because they occupy positions in the sequence that no real input token corresponds to. Unlike token embeddings constrained to the model's vocabulary, these vectors live in continuous space and can stand for arbitrary concepts. This freedom from the discrete vocabulary is what gives prefix tuning its expressive power: the learned vectors can encode task-specific information that no combination of real tokens could express as efficiently. A virtual token can simultaneously blend the semantic content of multiple words, encode abstract stylistic preferences, and carry syntactic cues, all without being constrained to match any particular entry in the embedding table.

Layer-Specific Prefixes

A necessary detail distinguishes prefix tuning from simpler approaches: prefix tuning introduces separate prefix vectors at each transformer layer, not just at the input. This distinguishes it from methods that only modify the input embedding layer. For a model with LL layers, prefix tuning learns a complete set of layer-specific prefix matrices:

{PK(1),PV(1),PK(2),PV(2),…,PK(L),PV(L)}\{P_K^{(1)}, P_V^{(1)}, P_K^{(2)}, P_V^{(2)}, \ldots, P_K^{(L)}, P_V^{(L)}\}

where:

  • PK(l),PV(l)P_K^{(l)}, P_V^{(l)}: learnable prefix matrices for the ll-th transformer layer, each of shape m×dkm \times d_k and m×dvm \times d_v respectively
  • LL: the total number of transformer layers in the frozen pretrained model

This layer-specific design is needed for achieving strong performance, and understanding why requires appreciating how transformers process information hierarchically. Each layer of a transformer captures different levels of abstraction. Early layers (roughly the first quarter) often detect basic linguistic features like word boundaries, morphological patterns, and simple grammatical structures. Middle layers build up more complex representations involving phrases and clauses, including syntactic dependencies. Later layers capture high-level semantic relationships, discourse coherence, and task-needed features such as sentiment or logical structure. By learning prefixes at each layer, prefix tuning can guide representations at every level of the hierarchy. This gives layer-appropriate steering signals that accumulate into coherent task-specific behavior.

Think of the layer-specific design as having a different co-pilot at each altitude of flight. At low altitude, the co-pilot gives instructions about staying in the lane and avoiding obstacles (syntactic details). At cruising altitude, they give navigational guidance about heading and speed (semantic structure). At high altitude, they advise on flight plan and destination (task-level objectives). Each set of instructions is specialized for the altitude at which it operates, and their combined effect is a smooth, coherent journey to the right destination.

If we only added prefixes at the input layer, the steering signal would need to propagate through all subsequent layers without refreshing. Research on information propagation in transformers has shown that signals injected at the input layer tend to attenuate as they pass through successive attention and feed-forward operations. By injecting fresh task guidance at every layer, prefix tuning ensures that the model receives consistent direction throughout its entire processing pipeline. The prefix at layer ll does not need to survive unchanged through L−lL - l layers of transformation; instead, it gives appropriate guidance at exactly the point in processing where that guidance is needed.

Out[5]:
Visualization
Architectural diagram showing 6 stacked frozen transformer layers (gray boxes) each receiving trainable prefix matrices P_K and P_V (teal boxes) via arrows from the left, with input arrow entering at the bottom and output arrow exiting at the top, plus a legend distinguishing trainable prefix from frozen weights.
Layer-specific prefix injection within a transformer architecture. Each layer receives distinct learned prefix vectors for keys and values, letting task-specific guidance at every abstraction level while keeping base weights frozen. This hierarchy allows the model to adjust syntax in early layers and semantics in later stages.

The Reparameterization Trick

Directly optimizing the prefix matrices often leads to unstable training. The loss surface can be non-convex, and the prefixes can drift to regions that cause attention weights to saturate or vanish. This instability occurs because the prefix vectors interact with the attention mechanism in nonlinear ways. Recall that attention weights are computed via softmax over dot products of queries and keys. Small changes to key values can produce large changes in attention probabilities when the softmax operates near saturation. A key vector that becomes very large in magnitude will dominate all attention weights, causing the model to ignore all real input tokens and attend only to that virtual token. Conversely, key vectors with small magnitudes will be ignored entirely.

To address this challenge, Li and Liang proposed a reparameterization using a small feed-forward network. The reason is that instead of directly optimizing the high-dimensional prefix vectors in their raw form, we can learn a more compact representation and project it into the required space. This bottleneck structure constrains the optimization to a lower-dimensional subspace, regularizing the solution and smoothing the loss surface. Instead of optimizing layer-specific prefixes directly, we learn a single smaller matrix PθP_{\theta} and project it through an MLP to generate prefixes for all layers:

{PK(l),PV(l)}l=1L=Split(MLP(Pθ))\{P_K^{(l)}, P_V^{(l)}\}_{l=1}^L = \text{Split}(\text{MLP}(P_{\theta}))

where:

  • PθP_{\theta}: the learnable matrix of shape m×d′m \times d' (where d′≪dd' \ll d), shared across all layers as a compressed task representation
  • MLP\text{MLP}: a feed-forward network with a hidden layer, projecting the low-dimensional PθP_{\theta} to the combined dimensionality required for all layer prefixes
  • Split\text{Split}: an operation that divides the large MLP output into 2L2L individual matrices, one PK(l)P_K^{(l)} and one PV(l)P_V^{(l)} per layer
  • PK(l)P_K^{(l)}: the resulting prefix key matrix for layer ll, of shape m×dkm \times d_k
  • PV(l)P_V^{(l)}: the resulting prefix value matrix for layer ll, of shape m×dvm \times d_v

The MLP projects the shared PθP_{\theta} from the compressed representation space to the high-dimensional space required for all layers. During training, we optimize PθP_{\theta} and the MLP weights rather than the final prefix vectors directly. The total parameter count for the trainable component is determined by the MLP architecture and the compressed dimension d′d', not by the full 2Ld⋅m2Ld \cdot m product that we would need if optimizing directly.

A necessary practical benefit: after training, we can discard the MLP entirely. We simply compute the final prefix values by running PθP_{\theta} through the trained MLP once, storing the resulting PK(l)P_K^{(l)} and PV(l)P_V^{(l)} matrices. At inference time, we use these precomputed matrices directly. The MLP is only needed during training; it is a smooth parameterization that makes optimization more stable, after which it is no longer required.

The MLP typically has one hidden layer with a nonlinear activation:

MLP(x)=tanh⁡(xW1+b1)W2+b2\text{MLP}(x) = \tanh(xW_1 + b_1)W_2 + b_2

where:

  • xx: a row vector from the prefix matrix PθP_{\theta}, with dimension d′d'
  • W1W_1: weight matrix of the first linear layer, of shape d′×dhiddend' \times d_{\text{hidden}}, performing an initial transformation
  • b1b_1: bias vector of the first linear layer, of dimension dhiddend_{\text{hidden}}
  • tanh⁡\tanh: the hyperbolic tangent nonlinear activation, bounding outputs to [−1,1][-1, 1]
  • W2W_2: weight matrix of the second linear layer, of shape dhidden×doutd_{\text{hidden}} \times d_{\text{out}}, projecting to the full prefix dimension
  • b2b_2: bias vector of the second linear layer, of dimension doutd_{\text{out}}

The hyperbolic tangent activation is particularly well-suited here because it bounds outputs to the range [−1,1][-1, 1], preventing the prefix values from growing arbitrarily large during training. This bounded activation helps maintain numerical stability throughout the optimization process. In contrast, using a ReLU activation could allow prefix values to grow unboundedly during gradient descent, potentially causing the attention saturation problems we are trying to avoid.

This reparameterization gives several important benefits:

  • Training stability: The MLP constrains the prefix space, preventing extreme values that could cause attention saturation and vanishing gradients
  • Better generalization: The bottleneck dimension d′d' acts as regularization, encouraging the model to learn compact, generalizable representations rather than overfitting to training data
  • Smoother optimization: Gradients flow through the MLP with better conditioning, giving a more favorable loss surface with fewer sharp local minima and saddle points
  • Reduced effective dimensionality: The number of degrees of freedom in the optimization is m×d′m \times d' plus MLP parameters, rather than the full 2L×m×d2L \times m \times d, making the optimization problem more tractable

Parameter Count

The total number of trainable parameters in prefix tuning depends on prefix length mm, model dimension dd, number of layers LL, and the reparameterization architecture. Understanding this count helps us compare prefix tuning's efficiency against other methods and plan resource requirements.

Without reparameterization, the inference-time parameter count is the sum of all elements in the layer-specific prefix matrices:

Paramsinference=2×m×d×L\text{Params}_{\text{inference}} = 2 \times m \times d \times L

where:

  • mm: prefix length (number of virtual tokens)
  • dd: model hidden dimension
  • LL: number of transformer layers
  • 22: factor for separate key and value prefixes at each layer

The factor of 2 accounts for the fact that we learn independent prefix matrices for both keys and values at each layer. Each of these matrices has mm rows (one per prefix position) and dd columns (matching the model's hidden dimension, assuming dk=dv=dd_k = d_v = d for simplicity).

For a model like GPT-2 Medium with L=24L = 24 layers and d=1024d = 1024, a prefix length of m=10m = 10 yields:

Params=2×m×d×L=2×10×1024×24=491,520 parameters\begin{aligned} \text{Params} &= 2 \times m \times d \times L \\ &= 2 \times 10 \times 1024 \times 24 \\ &= 491{,}520 \text{ parameters} \end{aligned}

This is roughly 0.1% of GPT-2 Medium's 345 million parameters. Learning fewer than half a million parameters can substantially alter the behavior of a model with hundreds of millions of frozen parameters. This efficiency lets practical fine-tuning even in resource-constrained environments and allows a single base model to serve many different tasks simultaneously, each with its own lightweight prefix stored separately.

For training with reparameterization, we add the MLP parameters. If the compressed dimension is d′=512d' = 512 and the hidden dimension is dhidden=512d_{\text{hidden}} = 512, and the output dimension is dout=2×L×dd_{\text{out}} = 2 \times L \times d, then:

ParamsMLP=m×d′+d′×dhidden+dhidden×dout+biases\text{Params}_{\text{MLP}} = m \times d' + d' \times d_{\text{hidden}} + d_{\text{hidden}} \times d_{\text{out}} + \text{biases}

For GPT-2 Medium with m=10m = 10, d′=512d' = 512, dhidden=512d_{\text{hidden}} = 512, L=24L = 24, d=1024d = 1024, this adds roughly 512×512+512×(2×24×1024)≈25512 \times 512 + 512 \times (2 \times 24 \times 1024) \approx 25 million training-only parameters. These are discarded at inference, so the inference overhead remains the small 491,520491,520 from the prefix matrices themselves.

Prefix Length Selection

The prefix length mm is the primary hyperparameter controlling the capacity of prefix tuning. This single number determines how much task-specific information the prefixes can encode, making its selection important for achieving good performance. The relationship between prefix length and task performance is not simply "more is better"; there is a real tradeoff that requires careful navigation.

Too short a prefix, and the prefixes cannot capture sufficient task-specific information. The model lacks the steering capacity needed to adapt to the target behavior. Consider translating between languages with very different word orders: the prefix must encode the convention that output words appear in a fundamentally different sequence than input words, information that requires significant representational capacity. A single virtual token cannot encode this complex pattern reliably.

Too long a prefix, and the virtual tokens consume attention capacity that should go to actual input tokens. Every prefix token receives attention from every real token in every forward pass. This means that as the prefix grows, each attention head devotes a growing fraction of its "budget" to virtual tokens rather than to relationships between real words. For a prefix of length 200 and an input of length 100, fully one-third of each attention computation involves the prefix. This dilution of attention to real content can degrade performance on understanding-intensive tasks.

Empirical Guidelines

Research has established several practical guidelines for selecting prefix length based on extensive experimentation across diverse tasks:

  • Simple classification tasks: m=10-20m = 10\text{-}20 often suffices. The pretrained model already captures the necessary features; the prefix just needs to route these existing capabilities to the appropriate output format.
  • Complex generation tasks: m=100-200m = 100\text{-}200 may be necessary. Generation requires encoding many simultaneous constraints: output format, style, vocabulary preferences, and structural conventions.
  • Translation and summarization: m=200m = 200 has shown strong results in the original paper, as these tasks require encoding complex input-output mapping conventions that need extensive representation capacity.
  • General starting point: m=20-50m = 20\text{-}50 for most tasks. This gives a reasonable balance between capacity and efficiency.

The original paper found that performance improves with prefix length up to a point, then plateaus or slightly degrades as the prefixes begin to interfere with attention to actual content. For the E2E dataset (a table-to-text generation benchmark), performance increased from m=10m = 10 to m=200m = 200, with diminishing returns beyond m=100m = 100. This pattern, where performance rises quickly with initial prefix length increases but then flattens, appears consistently across different tasks and datasets.

Out[6]:
Visualization
Line chart comparing task performance versus prefix length (5 to 300) for a simple classification task and a complex generation task, with shaded optimal regions highlighted in green for prefix lengths 10-30 and blue for 100-200, and annotated arrows pointing to each optimal zone.
Relationship between prefix length and task performance. Performance typically improves rapidly before plateauing as capacity saturates, though excessive lengths can eventually degrade quality by consuming the attention budget. The optimal prefix length varies based on the underlying complexity of the task.

Task Complexity and Prefix Length

The relationship between task complexity and optimal prefix length follows intuitive patterns that align with how much behavioral modification the task requires. Understanding this relationship helps you choose an appropriate starting point before hyperparameter tuning.

Low-complexity tasks such as sentiment classification and topic categorization require minimal steering. The pretrained model already captures the necessary features for understanding sentiment and topics; it has read millions of examples of positive and negative language during pretraining. The prefix just needs to route these existing capabilities to the appropriate output format, perhaps nudging the model to output a single label rather than a full sentence. The adaptation is more about output formatting than deep behavioral change. Short prefixes of 10-30 tokens work well for these scenarios because the core capability already exists in the frozen model and needs only light direction.

Medium-complexity tasks such as question answering and named entity recognition require more fine-grained control. The model must learn task-specific attention patterns while using pretrained knowledge, balancing what it already knows with new patterns specific to the task format. For question answering, the prefix must encode conventions about how to identify answer spans, when to abstain from answering due to insufficient evidence, and how to express partial confidence. For named entity recognition, the prefix must encode the labeling convention and the set of entity types. Prefix lengths of 50-100 tokens typically perform best for these tasks. This gives enough capacity to encode these different conventions.

High-complexity tasks such as open-ended generation, style transfer, and cross-lingual tasks require extensive behavioral modification. The prefix must encode stylistic and formatting preferences together with content requirements simultaneously, often representing subtle aesthetic choices that resist explicit description. For style transfer, the prefix must capture the essence of a writing style: sentence rhythm, vocabulary register, structural preferences, and tonal qualities. For cross-lingual transfer, the prefix must encode conventions that differ fundamentally between source and target languages. Longer prefixes of 100-200 tokens may be necessary to encode this rich information density.

Attention Budget Consideration

Each prefix token consumes attention capacity, creating a basic tradeoff that becomes more significant as input lengths increase. With prefix length mm and input length nn, the key-value sequences grow from nn to m+nm + n positions. The prefixes receive attention from all input positions, potentially diluting attention to the actual content.

More precisely, consider that before prefix tuning, each attention head computes nn attention scores per query position, one for each real input token. After adding a prefix of length mm, each head computes m+nm + n scores. The softmax normalization ensures these all sum to 1, meaning that on average, (m/(m+n))×100%(m / (m + n)) \times 100\% of each attention computation is "used up" by virtual tokens. For a prefix of m=200m = 200 and input of n=512n = 512, that is roughly 28% of each attention head's capacity devoted to virtual steering rather than content understanding.

For tasks with long inputs, this tradeoff becomes significant and demands careful consideration. If your input already uses 2048 tokens and you add a prefix of 200, you are devoting roughly 9% of attention capacity to virtual tokens. For generation quality, this investment must yield proportional steering benefit. If the prefix is giving important task guidance that would otherwise be missing, this cost is worthwhile. However, if the task could be accomplished with a shorter prefix, the excess length only harms performance.

The practical recommendation: start with a moderate prefix length (e.g., m=50m = 50) and tune based on validation performance. If performance is poor and you suspect insufficient capacity, increase the length. If it is good but inference is slow or you observe attention dilution effects (the model seems to ignore distant context), try reducing it to find the minimal effective length.

Prefix Tuning for Generation

Prefix tuning was originally designed for text generation tasks, where its strengths become most apparent. Unlike classification, generation requires the model to produce coherent, task-appropriate output token by token over potentially long sequences. Each generated token must be consistent with the task requirements, maintaining coherence in style and format as well as content throughout the entire output. The prefix is persistent context that guides every generation step. This gives a constant reference point that keeps the generation on track even as the output grows.

This persistent guidance is a real advantage over discrete prompts. With a discrete prompt like "Translate to French:", the prompt tokens receive some attention during generation but compete for attention with the growing output sequence. As the output becomes longer, the initial prompt occupies a shrinking fraction of the total context. With prefix tuning, the virtual tokens in the key-value cache are always available at every layer regardless of output length. This gives unwavering task guidance throughout generation.

Autoregressive Generation with Prefixes

In autoregressive models like GPT, the prefix appears as additional key-value pairs at the beginning of every layer's attention computation, persisting throughout the entire generation process. Consider generating text given an input xx. The generation proceeds through several stages that build upon each other:

  1. Concatenate prefix key/value matrices with input representations at each layer, establishing the initial context. This step creates the extended sequences K′=[PK;XWK]K' = [P_K; XW_K] and V′=[PV;XWV]V' = [P_V; XW_V] that the attention mechanism operates over.

  2. Generate the first output token attending to both prefix and input. The attention over the extended sequences allows the model to consult both the real input content and the virtual task guidance simultaneously, creating a first token that shows both.

  3. For subsequent tokens, the prefix remains fixed while the cache of previously generated tokens grows. Unlike the input tokens and generated tokens, which change with each new generation, the prefix stays constant. This means the model always has access to the same strength of task guidance regardless of how many tokens have been generated.

  4. Each new token can attend to the prefix and input as well as all previously generated tokens, maintaining coherence with both task requirements and generated content. The prefix continues to exert influence on generation even as the context window fills up, because the virtual key-value pairs are present at every layer of every forward pass.

The prefix acts as a form of "soft system prompt" that persists throughout generation. Unlike discrete prompts that consume input context window space and whose influence may diminish as they become distant in the sequence, prefix vectors exist in a separate namespace, only appearing in the key-value pairs rather than competing for query positions. This separation is significant: the prefix does not reduce the model's capacity to understand real input content, letting the full context capacity to be devoted to actual content while still receiving task guidance.

Controlling Generation Style

Prefix tuning excels at controlling generation style because it can encode fine-grained preferences that no discrete token could capture. The continuous nature of the prefix vectors allows them to stand for subtle combinations of characteristics: the formality level, sentence complexity, vocabulary preferences, structural patterns, and rhetorical devices that together define a recognizable writing style. These are often properties that skilled writers develop over years and that resist explicit articulation, yet they are precisely the kinds of patterns that can emerge naturally when optimizing prefix vectors on style-specific text.

Consider training prefixes for different styles:

  • Formal prefix: Learned on academic writing and technical documentation. The prefix would encode preferences for longer sentences, passive constructions where appropriate, technical vocabulary, and careful qualification of claims.
  • Casual prefix: Learned on conversational text and social media. The prefix would encode shorter sentences, contractions, colloquial vocabulary, and a more direct tone.
  • Creative prefix: Learned on fiction and poetry. The prefix would encode preferences for figurative language, varied sentence rhythms, emotional resonance, and narrative techniques.

At inference time, swapping prefixes instantly switches the model's generation style without any parameter changes to the base model. The same frozen GPT model can produce academic prose with one prefix and casual conversation with another. This modularity makes prefix tuning attractive for multi-tenant systems where you need different behaviors from the same underlying model. A single deployment can serve diverse style requirements simply by selecting the appropriate prefix, reducing infrastructure costs and simplifying maintenance.

Table-to-Text Generation

Prefix tuning works particularly well for table-to-text generation. This task requires the model to follow precise formatting conventions while creating natural language, combining structured data interpretation with fluent generation. Consider a structured data record:

Example table-to-text input data.
NameAreaFoodRating
The EagleriversideJapanese5 out of 5

The task is to generate: "The Eagle is a Japanese restaurant near the riverside with a perfect rating."

Prefix tuning captures the conventions of this transformation: how to order attributes, which phrasing patterns to use, how to express ratings naturally, and what to include or omit. The prefix encodes these conventions implicitly, guiding generation without explicit templates. Through training on many such examples, the prefix learns that names typically come first, that locations are introduced with "near" or "in," that ratings should be expressed naturally as descriptions rather than numerically, and that certain attributes take priority over others in the generated description. These patterns emerge from training, encoded in the continuous prefix vectors in ways that would be difficult to specify through hand-crafted rules or templates.

The original Li and Liang paper specifically evaluated prefix tuning on the E2E NLG benchmark (a standard table-to-text task) and the WebNLG dataset, finding that prefix tuning matched or exceeded the performance of full fine-tuning while using only 0.1% of the trainable parameters. This result was surprising to many researchers at the time, suggesting that the task-specific information in a fine-tuned language model could be captured in a remarkably compact representation.

Worked Example: Numerical Walk-Through

To build concrete intuition for prefix tuning, let us trace through a simplified numerical example. We will work with a tiny model to keep the numbers manageable: a single transformer layer with d=4d = 4 dimensions, 1 attention head, and a prefix of length m=2m = 2.

Setup. Our input is a sequence of 3 tokens with representations:

X=[100001000010]∈R3×4X = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \end{bmatrix} \in \mathbb{R}^{3 \times 4}

For simplicity, assume the projection matrices are the identity: WQ=WK=WV=I4W_Q = W_K = W_V = I_4. This means Q=K=V=XQ = K = V = X.

Without prefix. Standard attention computes:

QKT=X⋅XT=[100010001]QK^T = X \cdot X^T = \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}

After scaling by dk=4=2\sqrt{d_k} = \sqrt{4} = 2 and applying softmax, each token attends primarily to itself (the diagonal dominates), giving a nearly diagonal attention matrix. The output is approximately:

Attention(Q,K,V)≈X\text{Attention}(Q, K, V) \approx X

The model retrieves mostly its own representation with slight spreading, capturing no task-specific behavior.

Adding prefix vectors. Suppose we have learned prefix matrices:

PK=[21000210]∈R2×4P_K = \begin{bmatrix} 2 & 1 & 0 & 0 \\ 0 & 2 & 1 & 0 \end{bmatrix} \in \mathbb{R}^{2 \times 4} PV=[00120021]∈R2×4P_V = \begin{bmatrix} 0 & 0 & 1 & 2 \\ 0 & 0 & 2 & 1 \end{bmatrix} \in \mathbb{R}^{2 \times 4}

These prefix vectors stand for learned task guidance: PKP_K encodes "key patterns to match against" while PVP_V encodes "information to retrieve when matched."

Extended attention computation. The extended key matrix is:

K′=[PK;K]=[21000210100001000010]∈R5×4K' = [P_K; K] = \begin{bmatrix} 2 & 1 & 0 & 0 \\ 0 & 2 & 1 & 0 \\ 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \end{bmatrix} \in \mathbb{R}^{5 \times 4}

Now compute Q⋅(K′)TQ \cdot (K')^T for the first query q1=[1,0,0,0]q_1 = [1, 0, 0, 0]:

q1⋅(K′)T=[2⋅1+1⋅0+0+0,  0,  1,  0,  0]=[2,0,1,0,0]q_1 \cdot (K')^T = [2 \cdot 1 + 1 \cdot 0 + 0 + 0, \; 0, \; 1, \; 0, \; 0] = [2, 0, 1, 0, 0]

After scaling by 1/4=0.51/\sqrt{4} = 0.5: [1.0,0.0,0.5,0.0,0.0][1.0, 0.0, 0.5, 0.0, 0.0].

Applying softmax: the first prefix position (score 1.0) receives the highest attention, followed by the third position (original first token, score 0.5). The learned prefix key [2,1,0,0][2, 1, 0, 0] matches strongly with query [1,0,0,0][1, 0, 0, 0] because their dot product is 2, larger than the self-attention score of 1. This shows how the prefix can "outbid" real tokens for attention: the virtual token's key vector is specifically designed to attract attention from queries that look like the first token.

Retrieving prefix values. Since the first prefix position receives the most attention weight, the output for query 1 will be a weighted combination that includes significant contribution from PV[0]=[0,0,1,2]P_V[0] = [0, 0, 1, 2]. This prefix value vector injects information that was not present in the original input: the components in positions 3 and 4 that the original tokens XX had no content in. This is how the prefix "writes" task-specific information into the model's representations, even though the base weights remain frozen.

The key takeaway. The prefix vectors act like a learned attention bias. By engineering the key vectors to attract attention from specific query patterns and the value vectors to carry task-needed information, prefix tuning can redirect information flow within the frozen model. The model's projection matrices remain untouched, but the data flowing through attention is enriched with task-specific signals at every layer.

Prefix Tuning vs LoRA

Prefix tuning and LoRA use different principles that lead to complementary strengths and weaknesses. Understanding these differences helps you choose the right method for your task and constraints. The two approaches stand for different philosophies about how to adapt a frozen model: LoRA asks "how can we change what the model computes?", while prefix tuning asks "how can we change what the model sees?"

Both methods reach parameter efficiency by putting their adaptation in a small number of learnable parameters, and both leave the base model unchanged in a way that allows multi-task serving. But their mechanisms lead to distinct characteristics that matter for practical deployment.

Where They Apply Changes

LoRA modifies the weight matrices themselves. As we covered in the LoRA chapters, it adds low-rank updates to the query, key, value, and output projection matrices:

W′=W+ΔW=W+BAW' = W + \Delta W = W + BA

where:

  • W′W': the adapted effective weight matrix used during inference
  • WW: the frozen pretrained weights, unchanged throughout training and inference
  • ΔW\Delta W: the low-rank update matrix, approximating the full fine-tuning update
  • BB: the right factor of the low-rank decomposition, of shape d×rd \times r
  • AA: the left factor of the low-rank decomposition, of shape r×dr \times d

The model computes the same operations as before, but with slightly different weights. Every forward pass through a LoRA-adapted layer produces different intermediate representations because the linear transformations themselves have changed. This means that LoRA directly modifies how the model transforms input information, which in turn modifies how the attention mechanism computes queries, keys, and values.

Prefix tuning modifies the inputs to attention rather than the weights. The projection matrices remain completely unchanged, but attention sees additional virtual tokens:

K′=[PK;XWK],V′=[PV;XWV]K' = [P_K; XW_K], \quad V' = [P_V; XW_V]

where:

  • K′K': the extended key sequence seen by the attention mechanism, of length m+nm + n
  • V′V': the extended value sequence seen by the attention mechanism, of length m+nm + n
  • PKP_K: the learnable prefix key matrix, directly encoding key-space task representations
  • PVP_V: the learnable prefix value matrix, encoding the information to be retrieved
  • XWKXW_K: the standard key projection of the input text, computed with frozen WKW_K
  • XWVXW_V: the standard value projection of the input text, computed with frozen WVW_V
  • [;][;]: concatenation of virtual and real tokens along the sequence dimension

The weights WKW_K and WVW_V are identical to the pretrained model; only the data flowing through attention changes. This distinction is subtle but important: LoRA changes the function being computed, while prefix tuning changes the inputs to an unchanged function. Both reach behavioral adaptation, but through different mechanisms with different inductive biases.

Expressivity and Task Fit

This architectural difference leads to different strengths that make each method better suited to particular task categories. The differences are not absolute but reflect systematic tendencies observed across many experiments.

LoRA excels at tasks requiring fine-grained representation changes. When a task requires the model to update its internal representation of concepts (for example, adapting a model trained on general English to a specialized medical domain where terminology carries different connotations), LoRA's direct modification of weight matrices allows every layer to produce fundamentally different representations. This is most useful for:

  • Tasks requiring fine-grained representation changes where the pretrained representations are systematically wrong for the target domain
  • Classification where features need direct adjustment to match the target label space
  • Situations where input structure varies materially across examples, requiring flexible adaptation
  • Tasks where "what the model knows" needs updating, such as domain adaptation with specialized knowledge

Prefix tuning excels at tasks requiring consistent behavioral guidance that applies uniformly across inputs. Because the prefix vectors are input-independent (the same virtual tokens are prepended regardless of the actual input), they are most effective when the desired behavioral change is consistent and does not depend on the specific content being processed. This is most useful for:

  • Generation tasks requiring consistent style or format, where the behavioral change is a constant preference
  • Tasks with fixed output conventions that apply regardless of input content
  • Multi-task scenarios benefiting from easy prefix switching without model modification
  • Situations where input structure is consistent across examples, letting fixed guidance to be needed throughout

Empirically, LoRA often performs better on discriminative tasks (classification, NER) where per-input adaptation matters more, while prefix tuning performs competitively on generative tasks (summarization, translation) where consistent behavioral steering is the primary need. However, both methods have improved substantially since their introduction through better hyperparameter choices and training procedures, and the performance gap has narrowed considerably on many benchmarks.

Computational Considerations

The methods also differ in computational cost during both training and deployment, which affects practical decisions about which to use.

AspectLoRAPrefix Tuning
Training FLOPsSlightly higher (extra matmul per layer)Standard attention cost plus prefix operations
Inference FLOPsSame as training (or merged, no overhead)Same as training (prefix always present)
Memory (activations)Similar to base modelHigher (extended sequences in all layers)
Memory (parameters)Depends on rank rrDepends on prefix length mm
Merging into base weightsYes, eliminating inference overheadNo, prefix must always be maintained separately
Multi-task servingRequires adapter swap or separate deploymentEasy prefix swap, single base model

A key distinction is the mergeability of LoRA: LoRA adapters can be absorbed into the base model weights by computing W′=W+BAW' = W + BA directly, storing the result in the original weight matrices. This eliminates any inference overhead: the merged model runs at exactly the same speed as the original base model. Prefix tuning cannot reach this: there is no way to "absorb" prefix vectors into base model weights because the prefix operates on the input dimension, not the weight matrices.

For serving scenarios with strict latency requirements, this overhead, though small in absolute terms, may be unacceptable. On the other hand, prefix tuning makes multi-task serving trivial: a single deployment of the base model can switch behaviors by selecting a different small prefix matrix, whereas LoRA requires either merging different adapters (which requires time and computation) or maintaining separate adapter matrices in memory.

Out[7]:
Visualization
Bar chart on a logarithmic y-axis comparing trainable parameter counts across full fine-tuning, LoRA r=8, LoRA r=16, prefix m=20, and prefix m=100 for GPT-2 Medium, showing full fine-tuning requires 345M parameters while prefix methods need under 5M.
Comparison of trainable parameters across fine-tuning methods (log scale). Prefix tuning with standard length m=20 requires approximately 1.0M parameters, materially fewer than LoRA (r=16) and orders of magnitude fewer than full fine-tuning.
Bar chart showing trainable parameters as a percentage of base model size for each fine-tuning method, with full fine-tuning at 100%, LoRA and prefix methods below 2%, and exact percentage labels above each bar.
Parameter efficiency expressed as a percentage of the base model size. Prefix tuning modifies less than 1% of parameters even with long prefixes (m=100), showing high efficiency compared to full fine-tuning.

Combining Methods

Prefix tuning and LoRA are not mutually exclusive. Recent work has explored combining them to capture the benefits of both approaches, and for demanding tasks, this combination can outperform either method alone.

The idea is straightforward: use prefix tuning for high-level task steering and LoRA for fine-grained representation adjustment. The prefix handles "how to respond" in terms of stylistic and formatting choices, including behavioral conventions, while LoRA handles "what to know" by modifying the model's internal representations to better reflect the target domain. The two mechanisms operate at different levels of the model's processing hierarchy and therefore do not interfere with each other in destructive ways.

This combination can be particularly effective for complex tasks requiring both stylistic control and knowledge adaptation. For example, adapting a general model to generate clinical notes requires both stylistic adaptation (formal, precise language; specific structural conventions) and knowledge adaptation (understanding medical terminology and reasoning conventions). Using prefix tuning alone might reach the style but miss knowledge updates, while LoRA alone might capture domain knowledge but lack sufficient stylistic control. The combined approach addresses both dimensions with a modest increase in total trainable parameters.

Code Implementation

Let us implement prefix tuning for a GPT-2 model. We will build the components step by step, showing how prefix vectors fit into transformer attention. The implementation mirrors the original paper's design, including the reparameterization trick for training stability.

In[8]:
Code
import torch  # noqa: F401
import torch.nn as nn  # noqa: F401
from transformers import (  # noqa: F401
    GPT2Config,
    GPT2LMHeadModel,
    GPT2Tokenizer,
)
from transformers.cache_utils import DynamicCache  # noqa: F401

Defining the Prefix Module

The core of prefix tuning is a module that generates layer-specific key and value prefixes. We implement the reparameterization trick for stable training: a small, shared matrix is projected through an MLP to produce the full set of layer-specific prefix vectors.

In[9]:
Code
class PrefixEncoder(nn.Module):
    """
    Generates prefix key-value pairs for all transformer layers.
    Uses reparameterization through an MLP for training stability.
    """

    def __init__(
        self,
        num_layers: int,
        num_heads: int,
        head_dim: int,
        prefix_length: int,
        hidden_dim: int = 512,
    ):
        super().__init__()
        self.num_layers = num_layers
        self.num_heads = num_heads
        self.head_dim = head_dim
        self.prefix_length = prefix_length

        # Total dimension for all key-value pairs across all layers
        # Factor of 2 for key and value
        self.total_dim = 2 * num_layers * num_heads * head_dim

        # Learnable prefix embeddings (to be transformed by MLP)
        self.prefix_tokens = nn.Parameter(
            torch.randn(prefix_length, hidden_dim)
        )

        # Reparameterization MLP
        self.mlp = nn.Sequential(
            nn.Linear(hidden_dim, hidden_dim),
            nn.Tanh(),
            nn.Linear(hidden_dim, self.total_dim),
        )

    def forward(self, batch_size: int) -> torch.Tensor:
        """
        Generate prefix key-value pairs for a batch.

        Returns:
            Tensor of shape (batch_size, num_layers, 2, num_heads, prefix_length, head_dim)
        """
        # Transform prefix tokens through MLP
        # Shape: (prefix_length, total_dim)
        prefix_transformed = self.mlp(self.prefix_tokens)

        # Reshape to separate layers, key/value, and heads
        # Shape: (prefix_length, num_layers, 2, num_heads, head_dim)
        prefix = prefix_transformed.view(
            self.prefix_length,
            self.num_layers,
            2,  # key and value
            self.num_heads,
            self.head_dim,
        )

        # Permute to (num_layers, 2, num_heads, prefix_length, head_dim)
        prefix = prefix.permute(1, 2, 3, 0, 4)

        # Expand for batch
        # Shape: (batch_size, num_layers, 2, num_heads, prefix_length, head_dim)
        prefix = prefix.unsqueeze(0).expand(batch_size, -1, -1, -1, -1, -1)

        return prefix

The prefix_tokens parameter is the PθP_\theta matrix from the theoretical formulation: a compact, shared representation that the MLP projects to the full set of layer-specific prefixes. The total_dim accounts for all layers, the key-value split, all attention heads, and all head dimensions, following the formula 2×L×dk2 \times L \times d_k where dk=num_heads×head_dimd_k = \text{num\_heads} \times \text{head\_dim}.

In[10]:
Code
# Test the prefix encoder
encoder = PrefixEncoder(
    num_layers=12, num_heads=12, head_dim=64, prefix_length=20, hidden_dim=512
)

# Generate prefixes for a batch of 4
prefix = encoder(batch_size=4)
Out[11]:
Console
Prefix shape: torch.Size([4, 12, 2, 12, 20, 64])
  - Batch size: 4
  - Num layers: 12
  - Key/Value: 2
  - Num heads: 12
  - Prefix length: 20
  - Head dim: 64

The prefix encoder generates key-value pairs for each layer. The shape shows the full structure: for each sample in the batch, we have prefixes for each layer, split into key (index 0) and value (index 1) components, organized by attention head, with the prefix length and head dimension as the innermost dimensions.

Computing Parameter Counts

Let us verify the parameter efficiency of prefix tuning and understand where the parameters live.

In[12]:
Code
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


def count_prefix_parameters(prefix_encoder):
    """Count parameters in the prefix encoder."""
    total = sum(p.numel() for p in prefix_encoder.parameters())
    return total
In[13]:
Code
# Compare with GPT-2 small
config = GPT2Config()

# Create a prefix encoder matching GPT-2 small
prefix_encoder = PrefixEncoder(
    num_layers=config.n_layer,
    num_heads=config.n_head,
    head_dim=config.n_embd // config.n_head,
    prefix_length=20,
    hidden_dim=512,
)

prefix_params = count_prefix_parameters(prefix_encoder)

# Create a dummy model to calculate parameters dynamically
dummy_model = GPT2LMHeadModel(config)
gpt2_params = sum(p.numel() for p in dummy_model.parameters())
Out[14]:
Console
GPT-2 small setup:
  - Layers: 12
  - Heads: 12
  - Hidden dim: 768
  - Head dim: 64

Parameter comparison:
  - Prefix encoder params: 9,728,512
  - GPT-2 small params: 124,439,808
  - Ratio: 7.818%

With a prefix length of 20 and a hidden dimension of 512, this demonstration's prefix encoder has 9,728,512 parameters, or about 7.8% as many as GPT-2 small. The bulk of these parameters are in the reparameterization MLP, particularly the second linear layer that projects to the large combined prefix dimension across all layers. Smaller bottlenecks reduce this training-time share, and the generated key-value prefix is the only task-specific state needed after reparameterization.

Prefix-Tuned GPT-2 Model

Now let us create a wrapper that integrates prefix tuning with GPT-2. The wrapper freezes all base model parameters and makes only the prefix encoder trainable. It also handles the practical detail of converting prefix representations to the past_key_values format that Hugging Face's GPT-2 expects.

In[15]:
Code
class PrefixTunedGPT2(nn.Module):
    """
    GPT-2 with prefix tuning.
    The base GPT-2 parameters are frozen; only the prefix encoder is trained.
    """

    def __init__(
        self,
        model_name: str = "gpt2",
        prefix_length: int = 20,
        prefix_hidden_dim: int = 512,
    ):
        super().__init__()

        # Load pretrained GPT-2 and freeze it
        # self.gpt2 = GPT2LMHeadModel.from_pretrained(model_name)  # Commented out to avoid large download
        self.gpt2 = GPT2LMHeadModel(
            GPT2Config()
        )  # Use random initialization for demo
        for param in self.gpt2.parameters():
            param.requires_grad = False

        # Get model setup
        config = self.gpt2.config
        self.num_layers = config.n_layer
        self.num_heads = config.n_head
        self.head_dim = config.n_embd // config.n_head
        self.prefix_length = prefix_length

        # Create trainable prefix encoder
        self.prefix_encoder = PrefixEncoder(
            num_layers=self.num_layers,
            num_heads=self.num_heads,
            head_dim=self.head_dim,
            prefix_length=prefix_length,
            hidden_dim=prefix_hidden_dim,
        )

    def get_prefix_past_key_values(self, batch_size: int):
        """
        Convert prefix to the past_key_values format expected by GPT-2.

        GPT-2 expects past_key_values as a DynamicCache where each layer's
        key and value tensors have shape (batch_size, num_heads, seq_len, head_dim).
        """
        # Get prefix: (batch_size, num_layers, 2, num_heads, prefix_length, head_dim)
        prefix = self.prefix_encoder(batch_size)

        # Build a DynamicCache from the prefix tensors
        cache = DynamicCache()
        for layer_idx in range(self.num_layers):
            # Extract key and value for this layer
            key = prefix[
                :, layer_idx, 0, :, :, :
            ]  # (batch, heads, prefix_len, head_dim)
            value = prefix[:, layer_idx, 1, :, :, :]
            cache.update(key, value, layer_idx)

        return cache

    def forward(
        self,
        input_ids: torch.Tensor,
        attention_mask: torch.Tensor = None,
        labels: torch.Tensor = None,
    ):
        batch_size = input_ids.shape[0]

        # Get prefix as past_key_values
        past_key_values = self.get_prefix_past_key_values(batch_size)

        # Create attention mask that includes prefix positions
        if attention_mask is None:
            attention_mask = torch.ones_like(input_ids)

        # Prepend ones for prefix positions
        prefix_attention = torch.ones(
            batch_size,
            self.prefix_length,
            dtype=attention_mask.dtype,
            device=attention_mask.device,
        )
        extended_attention_mask = torch.cat(
            [prefix_attention, attention_mask], dim=1
        )

        # Forward through GPT-2 with prefix as past_key_values
        outputs = self.gpt2(
            input_ids=input_ids,
            attention_mask=extended_attention_mask,
            past_key_values=past_key_values,
            labels=labels,
        )

        return outputs

The get_prefix_past_key_values method connects the formula to the implementation. Hugging Face models use a past_key_values cache mechanism to store key-value pairs from previous positions. This supports efficient autoregressive generation. We exploit this mechanism to inject our prefix: by passing the prefix as "past" key-value pairs, we instruct the model to treat the virtual tokens as if they appeared before the real input.

In[16]:
Code
# Create and test the model
model = PrefixTunedGPT2(model_name="gpt2", prefix_length=20)

# Count parameters
trainable, total = count_parameters(model)
Out[17]:
Console
Model parameters:
  - Trainable (prefix only): 9,728,512
  - Frozen (GPT-2): 124,439,808
  - Total: 134,168,320
  - Trainable ratio: 7.251%

The wrapped model contains the frozen GPT-2 parameters plus the prefix encoder. In this deliberately explicit implementation, the 9,728,512 prefix-encoder parameters are trainable, which is 7.251% of the wrapper's total parameter count. The base model remains fully frozen; production configurations can use a smaller bottleneck or discard the reparameterization network after training.

Training Loop Example

Let us show a simplified training loop for prefix tuning. Notice that the optimizer is constructed only over the prefix encoder's parameters. This keeps the base model weights receive no gradient updates.

In[18]:
Code
def train_prefix_tuning(
    model,
    train_texts: list,
    tokenizer,
    num_epochs: int = 3,
    learning_rate: float = 5e-5,
    max_length: int = 128,
):
    """
    Simple training loop for prefix tuning demonstration.
    """
    # Only optimize prefix encoder parameters
    optimizer = torch.optim.AdamW(
        model.prefix_encoder.parameters(), lr=learning_rate
    )

    model.train()
    losses = []

    for epoch in range(num_epochs):
        epoch_loss = 0
        for text in train_texts:
            # Tokenize
            encoding = tokenizer(
                text,
                return_tensors="pt",
                max_length=max_length,
                truncation=True,
                padding="max_length",
            )

            input_ids = encoding["input_ids"]
            attention_mask = encoding["attention_mask"]

            # For language modeling, labels are the input shifted
            labels = input_ids.clone()
            labels[attention_mask == 0] = -100  # Ignore padding

            # Forward pass
            optimizer.zero_grad()
            outputs = model(
                input_ids=input_ids,
                attention_mask=attention_mask,
                labels=labels,
            )

            loss = outputs.loss
            loss.backward()
            optimizer.step()

            epoch_loss += loss.item()

        avg_loss = epoch_loss / len(train_texts)
        losses.append(avg_loss)

    return losses

The optimizer only receives model.prefix_encoder.parameters(). This is the necessary line that implements prefix tuning: by constructing the optimizer over only the prefix encoder parameters, we guarantee that gradients computed during loss.backward() will only update the virtual token representations. The base model's millions of parameters accumulate gradients internally (they are part of the computation graph) but no optimizer step is taken for them.

In[19]:
Code
# Create sample training data
train_texts = [
    "The restaurant serves excellent Italian cuisine with fresh ingredients.",
    "A cozy cafe giving homemade pastries and specialty coffee drinks.",
    "This family-owned diner has been serving classic American food since 1985.",
    "An upscale steakhouse known for dry-aged beef and extensive wine selection.",
    "A vibrant food truck specializing in authentic Mexican street tacos.",
]

# Initialize tokenizer
tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
tokenizer.pad_token = tokenizer.eos_token
In[20]:
Code
# Train (just a few epochs for demonstration)
model = PrefixTunedGPT2(model_name="gpt2", prefix_length=20)
losses = train_prefix_tuning(model, train_texts, tokenizer, num_epochs=5)
Out[21]:
Console
Epoch 1/5, Loss: 10.9743
Epoch 2/5, Loss: 10.7805
Epoch 3/5, Loss: 10.6002
Epoch 4/5, Loss: 10.4346
Epoch 5/5, Loss: 10.4094

The decreasing loss indicates that the model is successfully optimizing the prefix vectors to minimize prediction error. Even with the base model frozen, the prefixes give enough steering capacity to adapt the model's output distribution toward the training domain.

Out[22]:
Visualization
Line chart with shaded area showing training loss decreasing across 5 epochs of prefix tuning on short restaurant description texts, with an annotation showing the percentage loss reduction from epoch 1 to epoch 5.
Training loss over epochs for prefix tuning. The decreasing loss shows that the prefix encoder successfully learns to steer the frozen GPT-2 model toward the target distribution while training only the 9,728,512 prefix-encoder parameters (7.251% of this demonstration wrapper).

Visualizing Prefix Effects

Let us visualize how the learned prefix affects attention patterns across layers. This visualization simulates the typical pattern of how strongly each layer attends to the virtual prefix tokens.

In[23]:
Code
# Generate attention patterns with prefix
# Switch to inference mode
model.train(False)

# Tokenize a test sentence
test_text = "The restaurant has"
encoding = tokenizer(test_text, return_tensors="pt")
input_ids = encoding["input_ids"]

# Get prefix
batch_size = 1
prefix_kv = model.get_prefix_past_key_values(batch_size)

# We'll simulate attention patterns to the prefix
# In practice, you'd extract these from model outputs with output_attentions=True

import numpy as np

layers = list(range(1, model.num_layers + 1))
attention_to_prefix = []

# Simulate attention patterns (decreasing trend in early layers,
# increasing in later layers is a common pattern)
for layer in layers:
    if layer <= 6:
        base = 0.15 - 0.01 * layer
    else:
        base = 0.09 + 0.015 * (layer - 6)
    noise = np.random.normal(0, 0.01)
    attention_to_prefix.append(max(0.05, base + noise))
Out[24]:
Visualization
Line chart with shaded area showing simulated average attention weight to prefix tokens across 12 GPT-2 layers, with higher attention in early layers that decreases then slightly rises again in later layers, illustrating layer-dependent prefix influence.
Average attention weights from input tokens to prefix positions across layers. The curve shows the mean attention weight allocated to prefix virtual tokens at each layer depth. Higher weights indicate that the prefix more strongly influences processing at that specific layer, typically with greater impact in early and late layers.

The attention pattern reveals how prefix tuning influences different layers. Early layers use the prefix to establish basic task context, setting up the right syntactic and stylistic orientation. Middle layers tend to rely less on the prefix as they process the specific content of the input. Later layers reference the prefix again for output-specific guidance. This keeps the generation aligns with task conventions at the point where output decisions are made.

Comparing Prefix Lengths

Let us compare how different prefix lengths affect parameter count and capacity, making the linear scaling relationship concrete.

In[25]:
Code
prefix_lengths = [10, 20, 50, 100, 200]
param_counts = []

config = GPT2Config()
for length in prefix_lengths:
    # Calculate effective inference parameters (2 * L * d * m)
    # The reparameterization MLP is discarded after training
    params = 2 * config.n_layer * config.n_embd * length
    param_counts.append(params / 1e6)  # Convert to millions
Out[26]:
Visualization
Bar chart showing inference-time parameter counts in millions for prefix lengths 10, 20, 50, 100, and 200 in GPT-2 small, illustrating a linear relationship where each doubling of prefix length doubles the parameter count, with labeled values above each bar.
Parameter count as a function of prefix length for GPT-2 small. The relationship is linear: each additional prefix token adds parameters for key-value pairs across all layers and heads. For GPT-2 small, each prefix token contributes approximately 18K parameters.

The parameter count grows linearly with prefix length. For most tasks, prefix lengths of 20-100 give a good balance between expressivity and efficiency. The per-token parameter cost for GPT-2 small is approximately 2×L×d=2×12×768=18,4322 \times L \times d = 2 \times 12 \times 768 = 18{,}432 parameters per prefix position, a remarkably small cost per unit of behavioral control.

Key Parameters

Understanding the key hyperparameters of prefix tuning helps you configure it effectively for your specific use case. Each parameter controls a different aspect of the capacity-efficiency tradeoff.

The prefix length (mm) is the most important hyperparameter, controlling the number of virtual tokens added to each layer's key-value sequences. Longer prefixes increase steering capacity but consume attention budget and risk overfitting on small datasets. The right value depends primarily on task complexity, as described in the prefix length selection section. A good starting range is 20-50 for most tasks.

The prefix hidden dimension (d′d') is the dimension of the compressed representation used by the reparameterization MLP. A smaller value gives more regularization and reduces training parameters, at the cost of potentially insufficient expressivity. A larger value allows the MLP more freedom to generate diverse prefix patterns but increases the risk of overfitting. Values in the range 256-1024 typically work well; setting it equal to the model's hidden dimension is a common choice.

The learning rate applied to prefix encoder parameters is often higher than what you would use for full fine-tuning, because the prefix encoder starts from a random initialization and has many fewer parameters to update. Learning rates in the range 1×10−31 \times 10^{-3} to 5×10−45 \times 10^{-4} are common starting points, compared to 1×10−51 \times 10^{-5} for full fine-tuning of a similar model.

The number of training epochs may also differ from full fine-tuning. Because only a tiny fraction of parameters are trainable, each update makes a relatively large proportional change to those parameters. Overfitting can occur after fewer epochs than with full fine-tuning, particularly on small datasets. Monitoring validation loss carefully and using early stopping is especially important.

Limitations and Practical Considerations

Prefix tuning offers practical advantages for parameter-efficient fine-tuning, but several limitations affect its applicability. Understanding these helps you decide when prefix tuning is the right choice for your task and when alternatives like LoRA, adapters, or prompt tuning may be more appropriate.

The most significant limitation is the attention budget consumed by prefix tokens. Unlike LoRA, which modifies weights without changing sequence length, prefix tuning extends every attention computation. For models with long context windows processing near-capacity inputs, the additional prefix positions compete with input tokens for attention resources. The problem scales with both prefix length and model depth: in a 96-layer model (such as GPT-3), even a short prefix of 10 tokens adds 2×96×10=19202 \times 96 \times 10 = 1920 key-value pairs that must participate in attention at every forward pass. This overhead is especially significant when batching many short examples, where the prefix may stand for a substantial fraction of the total sequence length and thus a substantial fraction of the compute.

Training stability presents another challenge, which motivated the reparameterization trick. Without the MLP bottleneck, prefix vectors can drift to extreme values during optimization, causing attention weights to saturate. The softmax function is particularly sensitive to outliers: if one key vector has a much larger magnitude than all others, all attention weight collapses to that position, effectively making the model ignore all real input. The reparameterization trick mitigates this but does not eliminate the problem entirely; careful monitoring of attention weights during training is advisable. The MLP also adds complexity to the training setup: you must tune both the prefix length and the MLP hidden dimension as separate hyperparameters, and the MLP parameters themselves must be managed carefully to avoid the MLP becoming a bottleneck.

Prefix tuning also struggles with tasks requiring input-dependent adaptation. The prefix is the same for every input in a batch. This gives consistent global guidance but no input-specific modulation. If your task requires different behaviors for different inputs, such as maintaining different writing styles for different audiences within the same fine-tuning run, prefix tuning may underperform methods like LoRA that modify representations directly. The uniformity of the prefix means that it must encode the "average" of any task-specific variation in a single set of vectors. For tasks with high within-task variation, this averaging effect can degrade performance.

A related issue arises for multi-domain adaptation. If you want to fine-tune a single model to handle both formal and casual writing styles, prefix tuning requires two separate prefixes (one per style) and the ability to select the correct prefix at inference time. You cannot train a single prefix that "interpolates" between styles based on the input; the prefix is fixed before inference begins. This limitation motivates extensions like conditional prefix tuning, where the prefix itself is generated by a small model that takes the input as context, but these extensions add complexity.

Finally, prefix tuning lacks the merge capability that makes LoRA attractive for high-throughput deployment. LoRA adapters can be absorbed into base model weights for deployment, eliminating any inference overhead and creating a model that runs at exactly the same speed as the original pretrained model. Prefix tuning requires maintaining separate prefix vectors that must be prepended at every forward pass, adding a small but constant latency and memory overhead. For serving scenarios with strict latency requirements, such as real-time applications where the model must respond in under 100 milliseconds, this overhead may be unacceptable. The trade-off is that prefix tuning makes multi-task serving trivially easy, while LoRA merging requires additional engineering effort.

Despite these limitations, prefix tuning has had lasting impact on the field. It demonstrated that modifying attention inputs could be as effective as modifying weights, opening the door to a family of soft prompt methods. The insight that continuous vectors can stand for concepts beyond the discrete vocabulary influenced subsequent work on prompt tuning and controllable generation. Prompt tuning (the topic of the next chapter) can be seen as a simplified version of prefix tuning: rather than injecting learned vectors into every layer, prompt tuning only modifies the input embedding, reducing the parameter count further at some cost in expressivity. The progression from prefix tuning to prompt tuning illustrates how PEFT methods evolved by asking whether each component was truly necessary.

Prefix tuning also informs our understanding of what information is "stored" in pretrained language models. If a tiny set of continuous vectors can redirect a model's behavior so sharply, the model may already contain most of what it needs for many tasks. The task-specific knowledge required for table-to-text generation or formal writing is latent in the pretrained weights; the prefix activates and channels it. This perspective aligns with work on probing classifiers and knowledge extraction from language models, suggesting that PEFT methods partly reflect the amount of information encoded during large-scale pretraining.

Summary

Prefix tuning gives a parameter-efficient alternative to full fine-tuning by prepending learnable continuous vectors to the key and value sequences in attention. Rather than modifying model weights like LoRA, it changes what the attention mechanism sees, steering behavior through persistent virtual context that influences every layer at every step.

The key concepts from this chapter are:

  • Formulation: Prefix tuning concatenates learnable matrices PKP_K and PVP_V to the key and value sequences at each transformer layer, extending the attention scope without changing any model weights. The query sequences remain untouched, preserving output sequence length.

  • Layer specificity: Separate prefix matrices are learned for each transformer layer, letting task-specific steering at every level of the representational hierarchy, from basic syntactic processing in early layers to high-level semantic control in later layers.

  • Reparameterization: Training stability improves materially by generating all layer-specific prefixes through a shared MLP rather than optimizing them directly. This bottleneck prevents attention saturation and smooths the optimization objective, and the MLP can be discarded after training.

  • Prefix length: The primary capacity hyperparameter, typically ranging from 10-200 depending on task complexity. Simple classification benefits from shorter prefixes while complex generation tasks may require longer ones. The cost of each additional prefix token is a small but real fraction of the attention budget.

  • Generation focus: Prefix tuning excels at controlling generation style and format. This gives consistent task guidance throughout autoregressive decoding without consuming context window space or diminishing in influence over long generations.

  • Comparison with LoRA: While LoRA modifies weights and can be merged for zero-overhead inference, prefix tuning modifies inputs and requires maintaining separate prefix vectors. LoRA tends to excel at discriminative tasks while prefix tuning is stronger for generation tasks requiring consistent behavioral guidance.

The method reaches competitive performance with a small fraction of base model parameters, which makes it practical for adapting large language models to specific tasks without the computational infrastructure required for full fine-tuning. For generation tasks requiring consistent style or format, prefix tuning offers an elegant solution that keeps the base model completely frozen while giving powerful, persistent, task-specific guidance at every layer of the transformer hierarchy.

Quiz

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

Prefix Tuning

Question 1 of 70 of 7 completed
What does prefix tuning modify to adapt a pretrained model to a new task?

Comments

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

Reference

Citation details

Cite or share this article.

BIBTEXAcademic
@misc{brenndoerfer2025prefixtuning, author = {Michael Brenndoerfer}, title = {Prefix Tuning: Steering LLMs with Learnable Virtual Tokens}, year = {2025}, url = {https://mbrenndoerfer.com/writing/prefix-tuning-virtual-tokens-efficient-fine-tuning}, organization = {mbrenndoerfer.com}, note = {Accessed: 2026-10-15} }
APAAcademic
Michael Brenndoerfer (2025). Prefix Tuning: Steering LLMs with Learnable Virtual Tokens. Retrieved from https://mbrenndoerfer.com/writing/prefix-tuning-virtual-tokens-efficient-fine-tuning
MLAAcademic
Michael Brenndoerfer. "Prefix Tuning: Steering LLMs with Learnable Virtual Tokens." 2026. Web. October 15, 2026. <https://mbrenndoerfer.com/writing/prefix-tuning-virtual-tokens-efficient-fine-tuning>.
CHICAGOAcademic
Michael Brenndoerfer. "Prefix Tuning: Steering LLMs with Learnable Virtual Tokens." Accessed October 15, 2026. https://mbrenndoerfer.com/writing/prefix-tuning-virtual-tokens-efficient-fine-tuning.
HARVARDAcademic
Michael Brenndoerfer (2025) 'Prefix Tuning: Steering LLMs with Learnable Virtual Tokens'. Available at: https://mbrenndoerfer.com/writing/prefix-tuning-virtual-tokens-efficient-fine-tuning (Accessed: October 15, 2026).
SimpleBasic
Michael Brenndoerfer (2025). Prefix Tuning: Steering LLMs with Learnable Virtual Tokens. https://mbrenndoerfer.com/writing/prefix-tuning-virtual-tokens-efficient-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.