Open the workspace
Algorithms 101

IA3 (infused adapters by inhibiting and amplifying activations), explained

How IA3 fine-tunes LLMs with vectors that rescale keys, values and MLP activations: the math, T-Few losses, cost, limits versus LoRA and real-weight tests.

IA3, written (IA) and short for "Infused Adapter by Inhibiting and Amplifying Inner Activations", is a parameter-efficient fine-tuning method that learns three vectors per transformer block and uses them to rescale activations element by element: one for the attention keys, one for the attention values, and one for the hidden layer of the feed-forward network. Every entry starts at 1, so the model is unchanged at initialisation, and training learns which channels to amplify (values above 1) and which to inhibit (values below 1). For a Llama-style 8B model that is about half a million parameters, roughly 80 times fewer than a rank-16 LoRA on all linear layers. Because each scaling sits next to a matrix multiplication, it can be folded into the weights after training for zero inference overhead, or kept separate so that different requests in one batch use different tasks. IA3 is a parameterisation, not an objective; it is trained with SFT cross-entropy or any other loss, and it belongs to the same family as LoRA and prompt tuning.

The problem and where it came from#

IA3 was introduced by Liu, Tam, Muqeeth, Mohta, Huang, Bansal and Raffel (2022) in "Few-Shot Parameter-Efficient Fine-Tuning is Better and Cheaper than In-Context Learning". The paper's question was whether few-shot in-context learning, which reprocesses every demonstration at every prediction, could be beaten on both accuracy and cost by fine-tuning a tiny number of parameters on the same few examples. They wanted a method that adds very few parameters, reaches strong accuracy from a few dozen examples, and, like in-context learning, allows mixed-task batches. Prompt tuning met the first and third requirements, but they "were unable to attain reasonable accuracy with prompt tuning" on T0-3B and observed wild swings in validation accuracy. LoRA and adapters were accurate but modify weights, which complicates mixed-task batches.

IA3 was their answer. Combined with two extra loss terms and a recipe built on T0 (called T-Few), it reached 72.4% average accuracy on T0's held-out tasks, against 66.9% for zero-shot T0 and 66.6% for few-shot in-context learning with GPT-3 175B, at about 1,000 times fewer inference FLOPs than the latter (1.1e12 against 1.4e15 per example). On the RAFT benchmark T-Few scored 75.8%, the first result above the 73.5% human baseline. Among the PEFT methods they compared on T0-3B (including BitFit, adapters, Compacter, prompt tuning, prefix tuning, FISH mask, intrinsic SAID and LoRA), IA3 was the only one to beat full-model fine-tuning.

The parameterisation, with every symbol defined#

In each transformer block, with input activations , attention uses , , for each head, and the feed-forward network computes with nonlinearity (for gated MLPs, the gated hidden activation). IA3 introduces three learned vectors , and and multiplies them in by broadcasting over positions:

All three are initialised to ones. Only they are trained; every pretrained weight is frozen. Each block gets its own vectors, so the parameter count is for an -block decoder-only model (encoder-decoder models add a second set for cross-attention). Here and are the total key and value widths across heads, which in grouped-query attention is the key-value width.

IA3 as a constrained weight update

Since , each IA3 vector is exactly a rescaling of the output channels of and , and of the input channels of :

In the update form used for LoRA, . This is a full-rank update in general, but it lives in a space of only dimensions: it can make any channel louder or quieter, and nothing else. It cannot rotate a channel or mix two channels. LoRA's update is the mirror image: it can point anywhere, but only in directions. The two are complementary, which the toy run below measures on real weights.

The gradient of each scale is a sum over positions of the activation times its backpropagated error. For the key scale, with the -th key channel at position and :

so a channel is amplified when its activations correlate positively with the direction the loss wants that channel to move. Scaling keys changes how sharply each head attends (the logits for that channel are multiplied by ), scaling values changes what each head writes, and scaling the MLP hidden layer turns individual neurons up or down. That is why the authors describe it as inhibiting and amplifying.

The losses that came with it

T-Few's accuracy came from IA3 plus two loss terms for multiple-choice and classification tasks scored by rank classification. With the correct answer and incorrect choices , they add an unlikelihood loss that pushes down every token of every wrong choice, and a length-normalised softmax over choices:

where is the length-normalised log-probability. The total is with no weights to tune. With full fine-tuning of T0-3B, adding raised average accuracy from 60.7% to 62.71% and adding both reached 63.3%. These losses are independent of IA3 and can be used with any PEFT method; for open-ended generation they do not apply.

Side by side with its siblings

MethodUpdate to a weight Dimension of update spaceLlama-style 8B, trainableMixed-task batches
IA3 on ; per matrix524,288Yes, one vector multiply per example
LoRA, 41.9M (all linear)Yes, with batched adapter kernels
Prompt tuning, None (extra input tokens)81,920Yes
BitFitBias terms only per matrixModel-dependent (many LLMs have no biases)Yes
DoRAMagnitude vector plus LoRA directionAbout 43MHarder

The 8B count uses 32 layers, key and value widths of 1,024 (grouped-query attention) and an MLP width of 14,336: , or 0.0065% of the model. DoRA is a useful comparison: it also learns a per-channel magnitude, but adds a low-rank directional update, which is roughly "IA3 plus LoRA" on every matrix.

Worked example: scaling one head by hand#

Take one head with , no scaling, a query , and two content positions with keys , and values , . Without IA3, the logits are and , the weights are and the output is .

Now apply : the scaled keys are and , the logits become 2 and 0.5, and the softmax gives and 0.182. The output is . The head now prefers position 1. Within a single head this is a change a prefix cannot make, because a prefix leaves the relative weights of content positions untouched, while IA3 reweights the content logits channel by channel. Add , which silences value channel 2: the output becomes . Four numbers changed the head's routing and what it writes. Folding into weights is immediate: and give the same outputs with no extra operations.

What IA3 cannot do with this head: make the output when attention is on position 1, because that requires mapping value channel 1 into channel 2, a rotation. A rank-1 LoRA on could.

Where the three IA3 vectors act in one block x W_Q x W_K x W_V ⊙ l_k ⊙ l_v attention softmax(Q K̃ᵀ) Ṽ γ(x W₁) ⊙ l_ff · W₂ Each ⊙ is an element-wise product with a learned vector initialised to ones. Per block: d_k + d_v + d_ff numbers. Each can be folded into the adjacent weight (W_K, W_V, W₂) after training.
IA3 rescales keys and values inside attention and the hidden activations of the MLP. For a gated MLP the scale applies to the input of the down projection, which is where common libraries put it.

Toy runs: what channel scaling can and cannot represent#

Two experiments, one on real weights and one on a small trained transformer, both seeded PyTorch.

What the update space contains. We took the key projection of layer 12 of Qwen2.5-0.5B-Instruct, a matrix (two key-value heads of width 64), and asked how much of a target change each parameterisation can represent at best. IA3 on this matrix has 128 numbers and represents (row scaling in the layout); the best has a closed form per row, . LoRA at rank represents the best rank- approximation of . We used two targets of equal norm: a random rescaling of the rows (scales drawn from ), which is exactly IA3-shaped, and a random rank-1 matrix, which is exactly LoRA-shaped.

Share of a target update to Qwen2.5-0.5B k_proj that each form can represent 0 20 40 60 80 100 best achievable fit, % of squared norm 100 16 65 row-rescale target 0.1 100 100 random rank-1 target IA3 (128 numbers) LoRA r=1 (1,024) LoRA r=16 (16,384)
Best possible fit of two kinds of target update on a real 128 × 896 key projection, as a share of the target's squared Frobenius norm. Parameter counts in parentheses. Computed in closed form from the weights.

IA3 captured all of the rescaling target and 0.13% of the rank-1 target. LoRA captured all of the rank-1 target at any rank, but only 16.2% of the rescaling target at rank 1, 32.2% at rank 4, 65.4% at rank 16 and 97.4% at rank 64 (65,536 parameters, 512 times IA3's count). A change that looks trivial to IA3 is high rank, because scaling the rows of a full-rank matrix produces a full-rank change. The two methods are not points on one efficiency curve; they cover different subspaces of possible updates. Which one fits a task depends on whether the task needs channels turned up and down or new directions.

A trained model, an old skill and a new one. We pretrained a 2-layer, width-64, 4-head decoder on digit sequences with a task token in front (copy, reverse or sort six digits), reaching 99.9%, 99.95% and 96.8% exact-match accuracy. Without the task token it reversed only 1.0% of inputs correctly. We then froze it and trained only adapters for 1,500 steps on two tasks with no task token: reverse (a skill the model has) and rotate-by-one (output the digits shifted left by one position, never seen in pretraining; 0.05% before adaptation). IA3 with 768 parameters (, of width 64 and of width 256 in each of 2 blocks) reached 100% on reverse at every learning rate we tried (1e-2, 3e-2, 1e-1) and 99.95% to 100% on rotate. So did rank-4 LoRA with 9,216 parameters. For comparison, a 10-token soft prompt with 640 parameters also reached 100% on reverse but at most 30.9% on rotate across a sweep of learning rates and training lengths (details in the prompt tuning article). Rescaling channels was enough to re-route attention for a new positional pattern in this small model, which a soft prompt at the input could not do. The learned scales were far from 1: the largest was 4.3 to 9.0 on rotate against 1.8 to 2.5 on reverse, a reminder that "small parameter count" does not mean "small change".

The algorithm and training loop#

freeze all base weights
for each block: l_k = ones(d_kv); l_v = ones(d_kv); l_ff = ones(d_ff)       # trainable
    patch: K = (x W_K) * l_k ; V = (x W_V) * l_v ; h = (gamma(x W1) * l_ff) W2
optional: initialise l_k, l_v, l_ff from vectors pre-trained on a multitask mixture

optimizer = Adafactor or Adam on all l vectors, lr ~ 3e-3, warmup 60 steps, linear decay
for step in range(1000):                         # T-Few: 1,000 steps, batch 8
    loss = L_LM + L_UL + L_LN                     # or plain SFT cross-entropy for generation
    loss.backward(); optimizer.step(); optimizer.zero_grad()

deploy: fold  W_K *= l_k (column-wise on outputs), W_V *= l_v, W2 *= l_ff (row-wise on inputs)
or keep vectors per task and multiply activations per example (mixed-task batches)

The T-Few recipe used T0-3B, 1,000 steps at batch size 8 (so 8,000 example passes, which for 20 to 70 training examples is 100 or more epochs), Adafactor at learning rate 3e-3 with a 60-step warmup and linear decay, and prompt templates from P3. It also pre-trained the IA3 vectors themselves for 100,000 steps on T0's multitask mixture before few-shot fine-tuning, which lifted average accuracy from 64.6% to 65.8%. That step generalises: if you will train many IA3 task vectors on one base, first train a shared vector on a broad mixture and start every task from it.

Data needs are small by design. The method was validated on few-shot datasets (tens of examples per task) and is most useful from a few dozen to a few thousand examples. Larger datasets usually justify LoRA's greater capacity.

Hyperparameters that matter#

HyperparameterStarting valueNotes
Learning rate3e-3 (Adafactor, T-Few); 1e-3 to 1e-2 with AdamMuch higher than LoRA. The vectors start at 1 and need visible movement
Target modulesKeys, values, MLP down-projection inputPEFT's default for Llama-family models is k_proj, v_proj, down_proj, with down_proj treated as feed-forward
Steps1,000 for few-shotMeasured in steps, not epochs, because datasets are tiny
LossesLM + unlikelihood + length-normalised for multiple choicePlain cross-entropy for generation tasks
Weight decay0, or decay toward 1Standard decay pulls scales toward 0, which silences channels; decay toward 1 if you regularise at all
InitialisationOnes, or multitask pre-trained vectorsPre-training helped in the paper

Tune the learning rate first. If loss falls quickly and then plateaus well above a LoRA baseline, you are capacity-limited; adding modules (queries, up-projections) helps a little, but the real fix is a method with directional capacity.

Compute and memory cost#

Memory follows the frozen-base pattern: , where is so small (0.5M for 8B) that its optimiser state is a few megabytes. The backward pass must still flow through every frozen layer, so activation memory and backward compute match LoRA and prompt tuning, roughly FLOPs for tokens, against for full fine-tuning. The forward overhead is three element-wise multiplies per block, which is negligible. After folding, inference is exactly the base model's cost. A task checkpoint for an 8B model is about 1 MB in bf16, against 84 MB for rank-16 LoRA on all linear layers.

The cost comparison that motivated the paper is with in-context learning: T-Few's inference cost on T0-3B was 1.1e12 FLOPs per example, while 175B in-context learning with demonstrations cost 1.4e15. Fine-tuning the T-Few model cost 2.7e16 FLOPs, about as much as in-context learning with GPT-3 175B on 20 examples.

Failure modes and how to detect them#

Capacity ceiling

Training loss stops falling at a level a rank-4 LoRA beats easily. The task needs channels to mix, not just change volume. The toy below shows the extreme case: on a random rank-1 target, the best IA3 fit captured 0.13% of it. Compare against a small LoRA early.

Channels driven to zero

Some entries of collapse toward 0 (or go negative), silencing heads or neurons; general capability on unrelated prompts degrades. Monitor the distribution of : mean, minimum, and the fraction outside . Remove weight decay toward 0, lower the learning rate, or clamp.

Overfitting tiny datasets

With 30 examples and 1,000 steps, the model sees each example dozens of times. Validation accuracy peaks and then declines, or varies strongly between prompt templates. Use held-out templates and early stopping, and report the median over templates as the paper did.

Wrong module mapping

Applying the feed-forward scale to the output of the down-projection instead of its input scales the residual stream rather than the MLP's neurons, a different and much weaker parameterisation. Check which side of each matrix your library scales (PEFT has an explicit feedforward_modules list for this).

When to use IA3, and what to use instead#

SituationUse
Few-shot classification or multiple choice, dozens to hundreds of examplesIA3 with the T-Few losses
Thousands of per-user adaptations that must be tinyIA3 or prompt tuning
Mixed-task batches with no adapter kernelsIA3 (one element-wise multiply per example)
Instruction following, new formats, reasoning, domain knowledgeLoRA with SFT
Preference or RL stagesLoRA with DPO or GRPO
You want per-channel magnitude control and directional changeDoRA

Minimal implementation#

IA3 for a Hugging Face Llama-family model in plain PyTorch, then the PEFT equivalent.

import torch, torch.nn as nn

class IA3Out(nn.Module):           # scales the OUTPUT channels of a linear layer (keys, values)
    def __init__(self, base: nn.Linear):
        super().__init__(); self.base = base
        self.l = nn.Parameter(torch.ones(base.out_features))
    def forward(self, x):
        return self.base(x) * self.l.to(x.dtype)
    @torch.no_grad()
    def fold(self):                 # W' = diag(l) W  (rows of the [out, in] weight)
        self.base.weight.mul_(self.l[:, None].to(self.base.weight.dtype)); return self.base

class IA3In(nn.Module):            # scales the INPUT channels (MLP hidden units before down_proj)
    def __init__(self, base: nn.Linear):
        super().__init__(); self.base = base
        self.l = nn.Parameter(torch.ones(base.in_features))
    def forward(self, x):
        return self.base(x * self.l.to(x.dtype))
    @torch.no_grad()
    def fold(self):                 # W' = W diag(l)  (columns)
        self.base.weight.mul_(self.l[None, :].to(self.base.weight.dtype)); return self.base

def add_ia3(model):
    for p in model.parameters():
        p.requires_grad_(False)
    for layer in model.model.layers:
        layer.self_attn.k_proj = IA3Out(layer.self_attn.k_proj)
        layer.self_attn.v_proj = IA3Out(layer.self_attn.v_proj)
        layer.mlp.down_proj = IA3In(layer.mlp.down_proj)
    return [m.l for m in model.modules() if isinstance(m, (IA3Out, IA3In))]

# params = add_ia3(model); opt = torch.optim.AdamW(params, lr=3e-3, weight_decay=0.0)
# with PEFT:
# from peft import IA3Config, get_peft_model
# cfg = IA3Config(task_type="CAUSAL_LM", target_modules=["k_proj", "v_proj", "down_proj"],
#                 feedforward_modules=["down_proj"])
# model = get_peft_model(model, cfg); model.print_trainable_parameters()

Keep the vectors in fp32 during training; they move by small relative amounts around 1, which bf16 represents with only about three significant digits. In POFE, IA3 is available as a PEFT option on SFT recipes for the narrow, high-cardinality case, and the recipe reports the scale distribution alongside the loss.

Further reading#

Liu et al. (2022) introduces IA3 and T-Few; Sections 3.2 and 3.3 cover the losses and the method, and Table 1 the cost comparison with in-context learning. Hu et al. (2021) is the LoRA baseline. Lester et al. (2021) and Li and Liang (2021) are the prompt and prefix methods IA3 was compared against. He et al. (2021) places these methods in one framework, and Liu et al. (2024, DoRA) is the closest modern relative with a per-channel magnitude.

Frequently asked questions#

What does IA3 train?

Three vectors per block, initialised to ones: l_k scales attention keys, l_v scales attention values, and l_ff scales the hidden activations of the MLP (the input of the down projection). All pretrained weights stay frozen. For a Llama-style 8B model that is about 524,000 parameters.

How is IA3 different from LoRA?

IA3's update to a weight is a per-channel rescaling, diag(l - 1) times W: full rank but only d-dimensional, so it can turn channels up or down but not rotate or mix them. LoRA's update BA can point anywhere but only in r directions. On a real key projection, IA3 fitted a row-rescaling target exactly and a random rank-1 target at 0.13%; rank-1 LoRA did the reverse.

Does IA3 add inference latency?

No, if you fold it into the weights after training: scaling keys and values equals scaling the columns of W_K and W_V, and scaling the MLP hidden layer equals scaling the rows of W_2. Kept separate, it costs three element-wise multiplies per block and allows different tasks in one batch.

What learning rate should I use for IA3?

Much higher than LoRA: T-Few used 3e-3 with Adafactor for 1,000 steps at batch size 8, and 1e-3 to 1e-2 with Adam is a reasonable range. Avoid weight decay toward zero, which silences channels.

What is T-Few?

The recipe from the IA3 paper: T0-3B plus IA3, an unlikelihood loss on wrong answer choices and a length-normalised loss, with IA3 vectors pre-trained on the T0 mixture. It beat few-shot in-context learning with GPT-3 175B on held-out tasks (72.4% vs 66.6%) at about 1,000 times fewer inference FLOPs.

When should I use IA3 instead of LoRA?

For few-shot classification or multiple-choice tasks, or when you need thousands of tiny per-user adaptations or mixed-task batches without adapter kernels. For instruction following, reasoning, new knowledge and preference or RL stages, LoRA's directional capacity is usually needed.

References#

  1. Haokun Liu, Derek Tam, Mohammed Muqeeth, Jay Mohta, Tenghao Huang, Mohit Bansal, Colin Raffel (2022). Few-Shot Parameter-Efficient Fine-Tuning is Better and Cheaper than In-Context Learning. arxiv.org/abs/2205.05638
  2. Edward J. Hu, Yelong Shen, Phillip Wallis, Zeyuan Allen-Zhu, Yuanzhi Li, Shean Wang, Lu Wang, Weizhu Chen (2021). LoRA: Low-Rank Adaptation of Large Language Models. arxiv.org/abs/2106.09685
  3. Brian Lester, Rami Al-Rfou, Noah Constant (2021). The Power of Scale for Parameter-Efficient Prompt Tuning. arxiv.org/abs/2104.08691
  4. Xiang Lisa Li, Percy Liang (2021). Prefix-Tuning: Optimizing Continuous Prompts for Generation. arxiv.org/abs/2101.00190
  5. Junxian He, Chunting Zhou, Xuezhe Ma, Taylor Berg-Kirkpatrick, Graham Neubig (2021). Towards a Unified View of Parameter-Efficient Transfer Learning. arxiv.org/abs/2110.04366
  6. Shih-Yang Liu, Chien-Yi Wang, Hongxu Yin, Pavlo Molchanov, Yu-Chiang Frank Wang, Kwang-Ting Cheng, Min-Hung Chen (2024). DoRA: Weight-Decomposed Low-Rank Adaptation. arxiv.org/abs/2402.09353

Bring a model and a metric.

Tell us what the model should get better at. A post-training engineer replies within one business day with a plan, a compute estimate and an eval design.