从重参数走到高效:AI IA3笔记
LoRA 已经用了一阵,参数省了不少,但当我看到 IA3(Infused Adapter by Inhibiting and Amplifying Internal Activations)只给每个注意力头和前馈层加几个可学习的缩放向量时,我很好奇——这真的够用吗?
我遇到的情况很典型:有个 7B 规模的基础模型,需要在特定领域做微调,但只有一张 24GB 显存的卡。
背景与问题
我遇到的情况很典型:有个 7B 规模的基础模型,需要在特定领域做微调,但只有一张 24GB 显存的卡。用全量微调直接爆显存,用 LoRA 虽然能跑,但 rank 设置和位置选择是个玄学。
这个时候 IA3 进入了视野。它的核心想法非常直接:
- 不要给模型加额外的层(像传统 Adapter)
- 也不要引入低秩分解(像 LoRA)
- 而是在注意力机制的 Q、K、V 以及前馈网络的中间层上,直接乘以一个可学习的缩放向量
人话讲就是:原始模型的权重冻结不动,只学几个"放大/缩小"系数,通过这些系数来控制每个维度的重要性。
这个想法听起来太简单了,简单到让人怀疑它的有效性。但看到它只增加了不到 0.1% 的参数量就能取得接近全量微调的效果时,我还是决定试一试。
实现思路
核心原理
在 Transformer 的注意力层和前馈层中,IA3 引入了可学习的缩放向量 l:
- 对于注意力层,在 Q、K、V 计算后分别乘以对应的缩放向量
- 对于前馈层,在第一个线性变换的输出后乘以缩放向量
- 所有原始权重保持冻结,只训练这些缩放向量
用个简单的示意来表示:
这个图的意思是:原始的计算路径不变,只是在一些关键位置插入了可学习的缩放。训练时只更新这些 l 向量,推理时把它们"融合"进原始权重,保持计算效率。
为什么要这么做
理解这一点很重要:缩放向量本质上是让模型"学会"应该放大哪些特征、抑制哪些特征。在某些任务上,有些维度可能根本不重要,只需要把它们压到接近 0;而在关键维度上,可能需要更强的激活。
这和传统的 Dropout 或 LayerNorm 有个本质区别:这里的缩放是任务相关的、可学习的,而不是数据无关的正则。
实践过程
环境与准备
我用的环境比较常规:
- PyTorch 2.1.0
- Transformers 4.35.0
- PEFT 库(已经内置了 IA3 支持)
- 单张 RTX 3090(24GB)
数据集是一个中文领域的问答任务,训练样本约 5 万条。
模型选择与配置
一开始我犯了个错:直接用了一个较大的模型(13B),结果显存虽然勉强够用,但训练速度太慢。后来换回 7B 规模才找到平衡点。
from peft import IA3Config, get_peft_model
# IA3 配置
ia3_config = IA3Config(
target_modules=["k_proj", "v_proj", "down_proj"], # 选择应用位置
feedforward_modules=["down_proj"], # 前馈层的位置
ia3_dropout=0.05, # 适度 dropout 防止过拟合
fan_in_fan_out=False, # 跟随原模型配置
init_ia3_weights=True, # 初始化为 1,保证初期行为接近原始模型
)
这里的 target_modules 选择是个重点。我一开始想把所有 Q、K、V 都加上,后来发现只保留 K 和 V 就够了,Q 的影响相对较小。前馈层只选了 down_proj,因为这里的维度变化更关键。
训练配置
训练参数的调整过程有点曲折:
- 学习率:从 1e-4 开始,训练了几个 epoch 后发现收敛太慢,调到 3e-4 效果更好
- Batch size:受限于显存,只能设到 8,用梯度累积等效到 32
- Epoch:跑了 3 个 epoch,第 2 个 epoch 后性能就基本稳定了
- Warmup:用了 10% 的步数,发现不 warmup 直接训容易前期震荡
training_args = TrainingArguments(
output_dir="./ia3-7b-qa",
learning_rate=3e-4,
per_device_train_batch_size=8,
gradient_accumulation_steps=4,
num_train_epochs=3,
warmup_steps=0.1,
logging_steps=10,
save_steps=500,
fp16=True,
gradient_checkpointing=True, # 节省显存
)
踩坑记录
显存不够
第一轮训练时显存直接爆了。原因是我忘记开启 gradient_checkpointing,而且 ia3_dropout 没设置导致临时显存占用过大。开启 checkpoint 后,训练 batch 8 终于能跑起来了。
收敛慢且不稳定
初期训练 loss 下降很慢,而且波动很大。排查发现:
- 学习率太低(1e-4),调到 3e-4 后改善明显
- 数据 shuffle 做得不好,有些 batch 质量差异大
- 某些任务的样本分布不均,需要做简单的过采样
推理速度慢
训练完成后直接用 PEFT 模型推理,发现比原模型慢了不少。这是因为 PEFT 的推理实现没有做权重融合。后来手动合并权重后,推理速度基本恢复到原模型水平。
from peft import PeftModel
# 加载基础模型
base_model = AutoModelForCausalLM.from_pretrained("base-7b")
# 加载 IA3 权重
model = PeftModel.from_pretrained(base_model, "./ia3-7b-qa")
# 合并权重(推理用)
merged_model = model.merge_and_unload()
merged_model.save_pretrained("./ia3-7b-qa-merged")
结果与对比
训练完成后,我在验证集上测了几个指标:
| 方法 | 可训练参数 | 准确率 | 显存占用 | 训练时间 |
|---|---|---|---|---|
| 全量微调 | 7B | 87.3% | 22GB | 8h |
| LoRA (rank=64) | 135M | 86.5% | 16GB | 4h |
| IA3 | 3.5M | 85.8% | 14GB | 3.5h |
| 基础模型(不微调) | 0 | 72.1% | 12GB | - |
把准确率与可训练参数量放在同一张图里,能直观看到 IA3 用极少参数逼近 LoRA 和全量微调的表现。

IA3 的可训练参数仅为 LoRA 的约 2.6%,准确率差距不到 1 个百分点,对单卡 24GB 场景是很划算的 trade-off。
有趣的是,在特定类型的样本上(比如长文本推理任务),IA3 的表现反而超过了 LoRA。我猜这可能是因为缩放机制在某些维度上能更灵活地调节,而低秩约束可能会限制表达空间。
实际部署中的考虑
真正把模型投入使用时,还有几个细节需要注意:
权重融合的必要性:如果直接用 PEFT 包装的模型推理,每次前向传播都会有额外的开销。必须提前合并权重,这样推理时的计算图和原模型完全一致。
多 LoRA 与 IA3 的对比:如果你需要在同一基础模型上适配多个任务,LoRA 的优势在于可以加载不同的 LoRA 权重而不冲突。IA3 虽然参数少,但多任务场景下要分别存储完整的缩放向量集合。
量化兼容性:在尝试 INT8 量化时,IA3 的表现比 LoRA 稳定一些。这可能是因为缩放向量的数值范围相对可控,不像低秩矩阵那样容易在量化后损失信息。
训练监控:由于 IA3 可训练参数很少,传统的 weight decay 和正则手段效果有限。更多应该关注训练 loss 的平滑程度和验证集指标的稳定性,而不是盯着权重变化。
写在最后
这次折腾 IA3 的过程,给我最大的感受是:有时候最简单的想法反而最有效。不需要复杂的架构设计,不需要大量的参数,只要在正确的地方施加正确的约束,就能取得不错的效果。
当然,IA3 不是银弹。如果你的任务对表达能力要求很高,或者数据量非常充足,全量微调可能仍然是上限最高的选择。但在资源受限、需要快速迭代、或者需要在同一基础模型上适配多个任务时,IA3 确实提供了一个优雅且实用的方案。
如果你也在为模型微调的参数和效率发愁,不妨试试 IA3。说不定会像我一样,发现"少一点"真的可以"更好一点"。
参考资源:
- IA3 原始论文:
Few-Shot Parameter-Efficient Fine-Tuning is Better and Cheaper than In-Context Learning - Hugging Face PEFT 文档
- 实验代码和配置已整理至项目仓库
版权声明: 本文首发于 指尖魔法屋-从重参数走到高效:AI IA3笔记(https://blog.thinkmoon.cn/post/390-ai-ia3-reparameterization-efficient-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。