引言
大语言模型(LLM)的持续学习是一个新兴且重要的研究领域。与传统深度学习模型不同,LLM 具有:
- 海量参数:GPT-3 有 175B 参数,微调成本高昂
- 通用能力:预训练阶段已获得广泛知识和能力
- 知识时效性:世界知识需要不断更新
如何在持续微调 LLM 时保持通用能力同时适应新任务,是当前研究的核心问题。
1. LLM 持续学习的独特挑战
1.1 三大核心挑战
| 挑战 | 描述 | 影响 |
|---|---|---|
| 灾难性遗忘 | 学习新知识时丢失预训练能力 | 模型泛化能力下降 |
| 知识过时 | 世界知识随时间变化 | 事实性错误增加 |
| 分布偏移 | 新数据分布与预训练数据不同 | 性能不稳定 |
1.2 能力vs知识
LLM 有两种需要保护的内容:
┌────────────────────────────────────────────────────────────────┐
│ LLM 知识结构 │
├────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 预训练能力 │ │ 微调知识 │ │
│ │ (泛化、推理...) │ │ (特定任务...) │ │
│ └─────────────────┘ └─────────────────┘ │
│ ↓ ↓ │
│ 知识世界知识 指令遵循能力 │
│ 语言能力 领域知识 │
│ 推理能力 任务技能 │
│ │
│ 挑战: 增强一方可能损害另一方 │
└────────────────────────────────────────────────────────────────┘
1.3 与传统CL的区别
| 特性 | 传统持续学习 | LLM 持续学习 |
|---|---|---|
| 参数规模 | 1M - 100M | 1B - 100B+ |
| 预训练基础 | 无/ImageNet | 大量通用预训练 |
| 能力保留 | 单一任务能力 | 多维能力 |
| 评估指标 | 任务准确率 | 多维度能力评估 |
2. PEFT 方法在持续学习中的应用
2.1 方法分类
参数高效微调(PEFT)方法天然适合持续学习,因为它们只更新少量参数:
| 方法 | 更新参数比例 | 代表工作 |
|---|---|---|
| LoRA | 0.1% - 1% | Hu et al., ICLR 2022 |
| Adapter | 1% - 5% | Houlsby et al., 2019 |
| Prefix Tuning | 0.1% | Li & Liang, 2021 |
| Prompt Tuning | <0.1% | Lester et al., 2021 |
2.2 LoRA 持续微调
LoRA(Low-Rank Adaptation)通过低秩分解来更新预训练权重:1
持续学习优势:
- 预训练权重 保持不变,作为知识基座
- 每个任务的 独立存储
- 推理时可以合并 到
class LoRAContinualLearner:
"""
基于 LoRA 的 LLM 持续学习
"""
def __init__(self, model, lora_rank=8, lora_alpha=16):
self.model = model
self.lora_rank = lora_rank
self.lora_alpha = lora_alpha
# 每个任务的 LoRA 权重
self.lora_weights = {} # {task_id: {'q': (B, A), 'v': (B, A), ...}}
# 初始化 LoRA 适配器
self.lora_modules = self._init_lora_modules()
def _init_lora_modules(self):
"""为注意力层添加 LoRA 模块"""
from peft import get_peft_model, LoraConfig, TaskType
lora_config = LoraConfig(
r=self.lora_rank,
lora_alpha=self.lora_alpha,
target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],
lora_dropout=0.05,
bias="none",
task_type=TaskType.CAUSAL_LM
)
return get_peft_model(self.model, lora_config)
def train_task(self, task_id, train_data, epochs=3):
"""
训练单个任务
Args:
task_id: 任务标识
train_data: 训练数据
"""
# 保存当前 LoRA 权重作为该任务的权重
self.lora_weights[task_id] = {
name: param.clone()
for name, param in self.lora_modules.named_parameters()
if 'lora' in name
}
# 训练
trainer = SelfSupervisedNLPTrainer(
model=self.lora_modules,
train_dataset=train_data,
# ... 其他配置
)
trainer.train()
def load_task(self, task_id):
"""
加载指定任务的 LoRA 权重
"""
if task_id in self.lora_weights:
for name, param in self.lora_weights[task_id].items():
if name in self.lora_modules.state_dict():
self.lora_modules.state_dict()[name].copy_(param)
def merge_all_tasks(self):
"""
合并所有任务的 LoRA 权重(推理时)
"""
# 获取基础模型
base_model = self.model.base_model
# 平均所有任务的 LoRA 权重
merged_weights = {}
for name in self.lora_weights[0]:
params = [w[name] for w in self.lora_weights.values()]
merged_weights[name] = torch.stack(params).mean(dim=0)
return merged_weights2.3 LoRA vs Prompt Tuning 对比
研究表明,LoRA 在持续学习场景下优于 Prompt Tuning。2
| 特性 | LoRA | Prompt Tuning |
|---|---|---|
| 表达能力 | 高(秩可调) | 中(固定长度) |
| 遗忘程度 | 低 | 高 |
| 跨任务干扰 | 小 | 大 |
| 内存效率 | 中 | 高 |
2.4 CoLoR: 对比低秩
CoLoR(Contrastive LoRA for Regularization)提出结合对比学习来增强 LoRA 的持续学习能力。2
核心思想:在 LoRA 空间中构造对比学习,使不同任务的学习表示保持分离。
class CoLoR:
"""
CoLoR: 对比低秩持续学习
参考文献: arXiv:2406.03216
"""
def __init__(self, model, lora_rank=8, temperature=0.1):
self.model = model
self.lora_rank = lora_rank
self.temperature = temperature
# 任务表示缓存
self.task_representations = {}
def compute_contrastive_loss(self, task_id, features):
"""
计算对比损失
使当前任务表示与旧任务表示分离
"""
if len(self.task_representations) == 0:
return 0
# 获取之前任务的表示
old_reprs = list(self.task_representations.values())
old_reprs = torch.stack(old_reprs).to(features.device)
# 计算相似度
sim = torch.matmul(features, old_reprs.T) / self.temperature
# 对比损失:最大化与当前任务表示的相似度,最小化与旧任务的相似度
# 这里简化处理:惩罚与旧任务的过近表示
contrastive_loss = -torch.log_softmax(sim, dim=1)[:, 0].mean()
return contrastive_loss3. 知识编辑方法
3.1 知识编辑 vs 持续学习
| 特性 | 知识编辑 | 持续学习 |
|---|---|---|
| 粒度 | 单点知识 | 任务/领域 |
| 目标 | 更新特定事实 | 适应新任务 |
| 范围 | 局部 | 全局 |
| 评估 | 事实准确性 | 多维度能力 |
3.2 代表性方法
ROME: 基于因果追踪的知识编辑
ROME(RObust Knowledge Editing)通过因果追踪定位知识存储位置,然后直接修改权重。3
class ROME:
"""
ROME: 鲁棒知识编辑
参考文献: Meng et al. "Locating and editing factual associations in GPT", NeurIPS 2022
"""
def __init__(self, model):
self.model = model
self.cache = {} # 中间激活缓存
def causal_tracing(self, subject, prompt):
"""
因果追踪:确定知识存储的 MLP 块
"""
# 1. 运行一次模型,记录所有层的激活
with torch.no_grad():
_, cache = self.model.run_with_cache(prompt)
# 2. 逐层干扰,观察输出变化
corrupt_prompt = self.corrupt_subject(subject, prompt)
effects = []
for layer_idx in range(self.model.config.n_layers):
# 替换该层激活为腐败版本的激活
modified_cache = self.corrupt_layer(cache, layer_idx, corrupt_prompt)
output = self.model.run_from_cache(modified_cache)
# 计算恢复程度
effect = self.compute_effect(output, target)
effects.append(effect)
# 找出影响最大的层
key_layer = np.argmax(effects)
return key_layer, effects
def edit_knowledge(self, subject, new_object, prompt):
"""
编辑知识
"""
# 1. 因果追踪定位关键层
key_layer, _ = self.causal_tracing(subject, prompt)
# 2. 计算新知识的表示
new_repr = self.compute_representation(new_object)
# 3. 修改 MLP 权重
self.modify_mlp_weights(key_layer, subject, new_repr)
def modify_mlp_weights(self, layer_idx, subject, new_repr):
"""
修改 MLP 权重以植入新知识
"""
mlp = self.model.transformer.h[layer_idx].mlp
# 获取主题的中间表示
subject_repr = self.compute_representation(subject)
# 计算权重修改
# 这是一个简化的版本,原始 ROME 使用更复杂的优化
delta = (new_repr - subject_repr) * 0.1
# 应用修改
with torch.no_grad():
mlp.c_fc.weight += deltaMEMIT: 多证据知识编辑
MEMIT(Mass-Editing Memory with In-context Knowledge)扩展了 ROME,支持批量编辑多个知识。4
3.3 与持续学习的结合
知识编辑可以作为持续学习的补充:
- 快速适应:无需完整训练,快速更新特定知识
- 精确修改:只改变需要的知识,不影响其他能力
- 可逆性:可以撤销不当修改
class ContinualLearningWithEditing:
"""
结合知识编辑的持续学习
"""
def __init__(self, model):
self.model = model
self.lora_learner = LoRAContinualLearner(model)
self.editor = ROME(model)
# 编辑历史(用于回滚)
self.edit_history = []
def learn_task(self, task_id, train_data, task_type='skill'):
"""
学习任务,根据任务类型选择方法
"""
if task_type == 'knowledge':
# 知识密集型任务:使用知识编辑
self.editor.edit_knowledge(...)
self.edit_history.append({
'type': 'knowledge',
'task_id': task_id
})
else:
# 技能密集型任务:使用 LoRA
self.lora_learner.train_task(task_id, train_data)
self.edit_history.append({
'type': 'skill',
'task_id': task_id
})
def rollback(self, task_id):
"""
回滚到某个任务之前的状态
"""
# 找到对应的编辑/LoRA 权重
for edit in reversed(self.edit_history):
if edit['task_id'] == task_id:
if edit['type'] == 'knowledge':
self.editor.rollback(edit)
else:
self.lora_learner.remove_task(task_id)
self.edit_history.remove(edit)4. 指令漂移问题
4.1 问题定义
**指令漂移(Instruction Drift)**是指模型在持续微调过程中,原始的指令遵循能力逐渐退化。
┌────────────────────────────────────────────────────────────────┐
│ 指令漂移现象 │
├────────────────────────────────────────────────────────────────┤
│ │
│ 初始状态: │
│ 指令: "总结以下文章" → 输出: 简洁准确的摘要 ✓ │
│ │
│ 微调任务1: (电商评论分析) │
│ 指令: "总结以下文章" → 输出: 包含情感的摘要 │
│ │
│ 微调任务2: (医学文献分析) │
│ 指令: "总结以下文章" → 输出: 技术术语堆砌 │
│ │
│ 问题: 通用指令遵循能力被任务特定模式侵蚀 │
└────────────────────────────────────────────────────────────────┘
4.2 解决方案
4.2.1 指令回放
在每个微调阶段混合原始指令数据进行复习:
class InstructionReplayBuffer:
"""
指令回放缓冲区
"""
def __init__(self, max_size=1000):
self.buffer = [] # [(instruction, response), ...]
self.max_size = max_size
self.original_instructions = [] # 原始指令数据
def add_original_instructions(self, data):
"""添加原始指令数据"""
self.original_instructions.extend(data)
def sample_mixed_batch(self, task_batch, replay_ratio=0.2):
"""
采样混合批次
Args:
task_batch: 当前任务的数据批次
replay_ratio: 回放数据比例
Returns:
mixed_batch: 混合后的批次
"""
batch_size = len(task_batch)
replay_size = int(batch_size * replay_ratio)
# 采样原始指令
if self.original_instructions:
replay_batch = random.sample(
self.original_instructions,
min(replay_size, len(self.original_instructions))
)
else:
replay_batch = []
# 混合
mixed_batch = task_batch + replay_batch
random.shuffle(mixed_batch)
return mixed_batch4.2.2 多任务学习框架
保持一个固定的基座模型,所有任务通过 LoRA/Adapter 添加,不修改基座:
class MultiTaskLoraFramework:
"""
多任务 LoRA 框架
"""
def __init__(self, base_model):
self.base_model = base_model
self.task_adapters = {} # {task_id: adapter_weights}
# 冻结基座模型
for param in self.base_model.parameters():
param.requires_grad = False
def add_task(self, task_id, train_data):
"""添加新任务(创建新的 LoRA 权重)"""
# 创建任务特定的 LoRA
task_lora = LoRAAdaptor(self.base_model)
task_lora.train(train_data)
self.task_adapters[task_id] = task_lora.get_weights()
def predict(self, input_text, task_id=None):
"""预测"""
if task_id is not None:
# 使用指定任务的 adapter
adapter = self.task_adapters[task_id]
return self._predict_with_adapter(input_text, adapter)
else:
# 聚合所有 adapter(平均或选择)
return self._predict_ensemble(input_text)
def _predict_ensemble(self, input_text):
"""集成预测"""
predictions = []
for adapter in self.task_adapters.values():
pred = self._predict_with_adapter(input_text, adapter)
predictions.append(pred)
# 平均或投票
return self.aggregate(predictions)5. 2024-2025 最新进展
5.1 LLM 持续学习基准
TiC-LM: 时间持续预训练基准
ACL 2025 提出的新基准,使用 Common Crawl 的 114 个数据转储评估 LLM 的时间持续预训练能力。
关键发现:
- 不同 LLM 在持续预训练下表现差异显著
- 规模较小的模型更容易出现知识遗忘
5.2 Control LLM: 受控演化
arXiv:2501.10979 提出的方法,解决 LLM 能力增强时的灾难性遗忘问题。
核心方法:
- 使用控制向量引导知识更新方向
- 保护预训练能力的参数子空间
5.3 PEFT 方法对比研究
研究表明,不同 PEFT 方法在持续学习场景下的表现差异:2
| 方法 | 遗忘率 | 新任务适应 | 内存效率 |
|---|---|---|---|
| LoRA | 低 | 高 | 中 |
| Prefix Tuning | 高 | 中 | 高 |
| Prompt Tuning | 高 | 低 | 高 |
| Adapter | 低 | 高 | 低 |
5.4 InfLoRA: 无干扰低秩适应
CVPR 2024 提出的方法,在连续任务间注入少量参数同时避免任务间干扰。
核心创新:
- 正交约束确保不同任务的 LoRA 权重不冲突
- 达到与全量微调相当的性能
6. 实践指南
6.1 方法选择
| 场景 | 推荐方法 | 理由 |
|---|---|---|
| 快速原型 | Prompt Tuning | 最少参数,最快 |
| 生产部署 | LoRA | 平衡效率和效果 |
| 知识密集型 | 知识编辑 + LoRA | 精确更新 |
| 指令遵循重要 | 多任务 + 指令回放 | 保持通用能力 |
6.2 超参数建议
| 参数 | 建议值 | 说明 |
|---|---|---|
| LoRA rank | 8 - 16 | 任务复杂度高时增大 |
| LoRA alpha | 2 × rank | 标准设置 |
| 学习率 | 1e-4 - 5e-4 | PEFT 通常更高 |
| 批量大小 | 4 - 16 | 受 GPU 内存限制 |
| 回放比例 | 0.1 - 0.3 | 防止指令漂移 |
6.3 评估清单
def evaluate_llm_continual_learning(model, tasks, evaluation_data):
"""
LLM 持续学习评估
"""
results = {}
# 1. 新任务性能
for task_id, data in evaluation_data.items():
results[f'task_{task_id}'] = evaluate_task(model, data)
# 2. 预训练能力保留
results['pretraining_abilities'] = {
'instruction_following': evaluate_instruction(model, instructions),
'commonsense_reasoning': evaluate_mmlu(model),
'code_generation': evaluate_humaneval(model),
'mathematical_reasoning': evaluate_math(model),
}
# 3. 知识保留
results['knowledge_retention'] = evaluate_factqa(model, facts)
# 4. 指令遵循漂移
results['instruction_drift'] = evaluate_instruction_drift(
model, original_instructions
)
return results7. 未来研究方向
- 高效知识管理:如何在海量知识中高效更新和检索
- 能力量化:如何量化评估 LLM 的各项能力
- 自适应 PEFT:根据任务难度动态选择 PEFT 方法
- 多模态持续学习:视觉-语言模型的持续学习
- 终身学习:从无限数据流中持续学习
参考资料
相关阅读:
Footnotes
-
Hu, E. J., et al. (2022). LoRA: Low-rank adaptation of large language models. ICLR. ↩
-
arXiv:2406.03216. Choice of PEFT Technique in Continual Learning: Prompt Tuning is Not All You Need. ↩ ↩2 ↩3
-
Meng, K., et al. (2022). Locating and editing factual associations in GPT. NeurIPS. ↩
-
Meng, K., et al. (2023). Mass-editing memory in a transformer. ICLR. ↩