引言

大语言模型(LLM)的持续学习是一个新兴且重要的研究领域。与传统深度学习模型不同,LLM 具有:

  1. 海量参数:GPT-3 有 175B 参数,微调成本高昂
  2. 通用能力:预训练阶段已获得广泛知识和能力
  3. 知识时效性:世界知识需要不断更新

如何在持续微调 LLM 时保持通用能力同时适应新任务,是当前研究的核心问题。


1. LLM 持续学习的独特挑战

1.1 三大核心挑战

挑战描述影响
灾难性遗忘学习新知识时丢失预训练能力模型泛化能力下降
知识过时世界知识随时间变化事实性错误增加
分布偏移新数据分布与预训练数据不同性能不稳定

1.2 能力vs知识

LLM 有两种需要保护的内容:

┌────────────────────────────────────────────────────────────────┐
│                    LLM 知识结构                                 │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│  ┌─────────────────┐        ┌─────────────────┐               │
│  │   预训练能力     │        │   微调知识       │               │
│  │ (泛化、推理...) │        │ (特定任务...)   │               │
│  └─────────────────┘        └─────────────────┘               │
│         ↓                          ↓                            │
│    知识世界知识              指令遵循能力                        │
│    语言能力                  领域知识                            │
│    推理能力                  任务技能                            │
│                                                                │
│  挑战: 增强一方可能损害另一方                                  │
└────────────────────────────────────────────────────────────────┘

1.3 与传统CL的区别

特性传统持续学习LLM 持续学习
参数规模1M - 100M1B - 100B+
预训练基础无/ImageNet大量通用预训练
能力保留单一任务能力多维能力
评估指标任务准确率多维度能力评估

2. PEFT 方法在持续学习中的应用

2.1 方法分类

参数高效微调(PEFT)方法天然适合持续学习,因为它们只更新少量参数:

方法更新参数比例代表工作
LoRA0.1% - 1%Hu et al., ICLR 2022
Adapter1% - 5%Houlsby et al., 2019
Prefix Tuning0.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_weights

2.3 LoRA vs Prompt Tuning 对比

研究表明,LoRA 在持续学习场景下优于 Prompt Tuning2

特性LoRAPrompt 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_loss

3. 知识编辑方法

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 += delta

MEMIT: 多证据知识编辑

MEMIT(Mass-Editing Memory with In-context Knowledge)扩展了 ROME,支持批量编辑多个知识。4

3.3 与持续学习的结合

知识编辑可以作为持续学习的补充:

  1. 快速适应:无需完整训练,快速更新特定知识
  2. 精确修改:只改变需要的知识,不影响其他能力
  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_batch

4.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 rank8 - 16任务复杂度高时增大
LoRA alpha2 × rank标准设置
学习率1e-4 - 5e-4PEFT 通常更高
批量大小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 results

7. 未来研究方向

  1. 高效知识管理:如何在海量知识中高效更新和检索
  2. 能力量化:如何量化评估 LLM 的各项能力
  3. 自适应 PEFT:根据任务难度动态选择 PEFT 方法
  4. 多模态持续学习:视觉-语言模型的持续学习
  5. 终身学习:从无限数据流中持续学习

参考资料


相关阅读

Footnotes

  1. Hu, E. J., et al. (2022). LoRA: Low-rank adaptation of large language models. ICLR.

  2. arXiv:2406.03216. Choice of PEFT Technique in Continual Learning: Prompt Tuning is Not All You Need. 2 3

  3. Meng, K., et al. (2022). Locating and editing factual associations in GPT. NeurIPS.

  4. Meng, K., et al. (2023). Mass-editing memory in a transformer. ICLR.