第 10 章 第二部分:中训练数据

中训练的灾难性遗忘与稳定性

灾难性遗忘的防控、训练策略与遗忘检测评估

灾难性遗忘 Replay 稳定性 NTK AccessibilityCollapse TFGN LoRA持续学习

“学习新事物最困难的部分不是记住新东西,而是不忘掉旧东西。”

这句话对大模型同样成立——甚至更为残酷:模型不会告诉你它忘了什么。


10.1 灾难性遗忘(Catastrophic Forgetting)

什么是遗忘?

在中训练的语境下,灾难性遗忘指的是:模型在新数据上训练后,在之前已经掌握的任务或知识上表现显著退化。

这不是一个理论风险——它是每个做过中训练的团队都会遇到的实际问题。

一个典型的场景:
  - 你有一个通用 7B 模型,MMLU 分数 55%,C-Eval 50%
  - 你在 200B 的代码+数学数据上做中训练
  - 中训练后:HumanEval 从 25% 飙升到 50% ✓
  - 但是:MMLU 从 55% 跌到 45%,C-Eval 从 50% 跌到 38% ✗
  
  你增强了代码能力,但付出了丢失通用知识的代价。

遗忘的机制

从参数更新的角度理解遗忘:

  1. 参数覆盖:模型的权重是有限的。当在新分布上训练时,参数会被更新以适应新分布,但这些更新可能覆盖了之前学到的模式

  2. 表示漂移:中间层的表示会向新数据的分布漂移。如果新数据和旧数据的分布差异大,漂移就大,遗忘就严重

  3. 注意力重分配:注意力头可能从关注”通用模式”转向关注”领域特定模式”,导致通用能力下降

遗忘的函数空间理论:为什么参数空间正则化器会失效

上节描述的”参数覆盖/表示漂移/注意力重分配”是参数空间视角的解释。但 2026 年 6 月 Tel Aviv University 的 Hidekel & Raviv(ICML 2026 Workshop)从函数空间(NTK 框架)给出了更根本的解释,并直接挑战了 EWC/SI/MAS/RWalk 等参数空间正则化器的基础假设。

NTK 视角的核心洞察:

参数空间视角(传统):
  遗忘 = 参数 θ 偏离 θ_old → 用 Fisher 矩阵 / SI / MAS 惩罚 θ 漂移
  └── 问题:参数空间是高维的,惩罚"哪些方向"难以精确瞄准输出空间的脆弱点

函数空间视角(NTK):
  遗忘 = 输出 f(x) 偏离 f_old(x) → 跨任务核函数 K_AB 决定漂移方向
  └── 优势:遗忘集中在旧任务 NTK 特征模态的少数方向上

闭式遗忘向量预测器:

Hidekel & Raviv 的核心定理是:在 NTK 体制下,新任务训练尚未执行任何梯度步之前,就能通过跨任务核函数精确预测遗忘向量的方向和大小:

Forgetting Vector≈∇θfold(x)⋅Δθnew\text{Forgetting Vector} \approx \nabla_\theta f_{\text{old}}(x) \cdot \Delta\theta_{\text{new}}

其中关键观察是:

  • 在冻结骨干+线性头 PEFT-CL 设定下,预测器在 float32 数值精度内精确
  • 在非线性适配器/全模型微调下,作为局部 NTK 近似(cosine similarity > 0.994)
  • 遗忘能量集中在旧任务 NTK 矩阵 KAAK_{AA} 的少数特征模态上
    • Split-MNIST/CIFAR-10 实验:1-6 个模态承载 50-90% 的遗忘能量

Kronecker 缩放规则(冻结线性头下的解析结论):

在冻结骨干+任务共享可训练线性头的设定下,KAAK_{AA} 可分解为 Kronecker 积:

k⋆≈C⋅kGk^{\star} \approx C \cdot k_G

其中 CC 是输出维度,kGk_G 是特征 Gram 矩阵的有效秩。这意味着:

  • 脆弱秩 = 输出维度 × 特征秩——这是冻结线性头下可计算的精确公式
  • 保护所有参数既无必要(大部分方向不脆弱),也徒增成本

为什么参数空间正则化器会失效:

EWC / SI / MAS / RWalk 的共同盲区:

L2 参数正则化(Fisher 加权):
  └── 惩罚方向:参数空间高维
  └── 实际脆弱方向:NTK 特征模态空间低秩(k=1~6)
  └── 后果:在参数空间广撒胡椒面 → 在脆弱方向上可能漏防

共享头基准(shared-head benchmarks)上的失败模式:
  └── L2 正则化假设"所有参数同等重要"
  └── 实际:仅 NTK 前 k 个模态占脆弱性的 50-90%
  └── 结果:在共享头设定下,参数空间正则化器效果远低于函数空间方法

靶向谱正则化器(Targeted Spectral Regularizer):

基于上述理论,Hidekel & Raviv 提出了只在 NTK 脆弱子空间上加正则化的方案:

"""
靶向谱正则化器(伪代码)
"""
import torch

def targeted_spectral_regularizer(
    model,
    old_task_ntk_eigvecs: torch.Tensor,  # 旧任务 NTK 前 k 个特征向量
    lambda_spec: float = 1.0,
) -> torch.Tensor:
    """
    只在 NTK 脆弱子空间(old_task_ntk_eigvecs 张成)上惩罚参数漂移
    其他方向(占参数空间绝大多数)不加惩罚
    """
    penalty = 0.0
    for name, param in model.named_parameters():
        if name in old_task_ntk_eigvecs:
            # 将参数漂移投影到脆弱子空间
            drift = param - old_task_ntk_eigvecs[name]['anchor']
            projection = old_task_ntk_eigvecs[name]['eigvecs'] @ \
                         old_task_ntk_eigvecs[name]['eigvecs'].T @ \
                         drift.flatten()
            penalty += projection.norm() ** 2
    return lambda_spec * penalty

对章节理论的补充意义:

章节方法参数空间视角下的解释函数空间视角下的局限性
EWCFisher 对角加权 L2惩罚方向 ≠ 脆弱方向,参数空间高维浪费
SI路径积分 ω 累积同上,ω 高维但脆弱方向低秩
MAS输出敏感度 |∂f/∂θ|接近正确方向,但仍分散在参数空间
OPLoRA奇异方向正交投影部分对齐——仅在 SVD 主导方向上对齐,未必在 NTK 模态上对齐

对工程实践的指导:

  1. 诊断优先:在设计正则化器之前,先计算旧任务的 NTK 矩阵并定位前 k 个脆弱模态(k=1~6 通常足够)
  2. 保护什么:基于脆弱模态设计保护机制,而不是基于参数幅值
  3. 避免冗余保护:参数空间大部分方向对遗忘贡献微弱,不需要保护

遗忘的模式

2025 年 ACL 的论文《Exploring Forgetting in LLM Pre-Training》首次系统性地研究了预训练/中训练过程中的遗忘模式,发现遗忘不是均匀的:

选择性遗忘:

  • 低频知识比高频知识更容易被遗忘
  • 与新训练数据分布差异大的知识更容易被遗忘
  • 跨语言知识比同语言知识更脆弱

遗忘的阶段性:

  • 遗忘在中训练初期最严重(前 10% 的训练步)
  • 之后遗忘速率逐渐放缓
  • 但总遗忘量随训练量增加而持续增长

模型规模的影响:

  • 令人意外的是,更大的模型遗忘更严重
  • 假说:大模型的参数有更多”冗余”用于适应新分布,因此更容易发生参数覆盖
  • 但大模型的绝对能力基数也更高,所以遗忘后的绝对水平可能仍然高于小模型

虚假遗忘:性能下降 ≠ 知识丢失

上面讨论的遗忘机制默认了一个假设:评估指标下降就意味着模型丢失了知识。 但这个假设并不总是成立。

2025 年 ICLR 上发表的 Spurious Forgetting 研究首次揭示了一种被长期忽视的现象——虚假遗忘(Spurious Forgetting):模型在持续学习后,虽然在旧任务上的评估分数下降了,但知识并未真正丢失。真正发生的是模型丧失了对旧任务格式的任务对齐(task alignment)。

真实遗忘 vs 虚假遗忘:

真实遗忘(Genuine Forgetting):
  └── 知识确实从参数中被覆盖/丢失
  └── 无论怎么提示,模型都无法恢复

虚假遗忘(Spurious Forgetting):
  └── 知识仍存在于参数中
  └── 但模型"忘记了"如何按旧格式输出
  └── 通过少量 re-alignment(如 few-shot 提示或轻量微调)可以恢复

这一发现的实践意义很大:

  1. 评估遗忘前先做 re-alignment 验证:在判定模型”遗忘”之前,先尝试用少量 in-context examples 或轻量提示工程恢复表现。如果能恢复,就是虚假遗忘,不需要加大回放比例
  2. 避免对虚假遗忘过度反应:如果把虚假遗忘当真实遗忘处理,可能会不必要地增加回放数据量,浪费训练容量
  3. 监控体系需要升级:仅看 benchmark 分数变化不够,需要区分两类遗忘。实操上可以在评估 pipeline 中增加一个”re-alignment 验证”步骤

可访问性失败:根叙事的重大更新(Accessibility Collapse)

虚假遗忘揭示了”性能下降 ≠ 知识丢失”的一个边界,但 2026 年 6 月 Trivedi & Melwani 提出的 Accessibility Collapse(三层框架) 给出了一个更具颠覆性的视角:灾难性遗忘应被描述为”可访问性失败”而非”完全表征擦除”。

知识的三层分离(Accessibility Collapse 框架):

Layer 1: 知识存储(Storage)
  └── 任务解决方案在 checkpoint 中是否被物理保留?
  └── 实证:所有任务 checkpoint 都能保持其原始准确率
  └── 结论:✅ 存储完整,未被擦除

Layer 2: 知识表征(Representation)
  └── 任务信息是否在网络特征空间中被编码?
  └── 实证:行为性塌缩到 0%,但线性探针保留 76% 信息
  └── 结论:✅ 表征仍在,但可访问性丧失

Layer 3: 知识可访问性(Accessibility)
  └── 模型能否在标准推理路径下访问到这些表征?
  └── 实证:54.8% → 0% 的行为性塌缩
  └── 结论:❌ 访问失败,是真正的失效点

关键实验证据(基于 CIFAR-100 sequential + ResNet-18):

实验测量目标结果含义
行为性任务准确率输出是否正确54.8% → 0%表面看完全遗忘
线性探针准确率末层特征是否含信息0.468(= checkpoint 的 76%)表征基本保留
重训最终分类器仅恢复最后一层恢复 75.7% 原始任务性能骨干未被破坏
分层保留率哪一层丢失Layer 1-3 保持 100-104%;Layer 4 和分类器层严重退化遗忘高度局部化

对章节根叙事的颠覆:

之前的章节叙事隐含一个假设——灾难性遗忘 = 知识擦除。Accessibility Collapse 框架将这一假设拆解为三个独立维度,并实证表明:

  • 知识本身没有被擦除
  • 知识表征大部分被保留
  • 真正的失败发生在输出层——即”可访问性”维度

对实践的指导意义:

之前认知:模型"忘了"旧任务 → 需要完整回放保护
现在认知:模型"够不到"旧任务 → 仅重训最后一层即可恢复 75.7%

新的轻量级恢复方案:
  1. 保留预训练/前一阶段模型的特征提取器(frozen)
  2. 仅重训输出层分类器/输出头
  3. 在保留的表征上 + 少量标注 → 性能快速回升

与虚假遗忘的关系:Accessibility Collapse 是”虚假遗忘”的根本性解释——虚假遗忘本质上就是可访问性失败的体现。两者在诊断上互补:虚假遗忘是”可恢复 vs 不可恢复”的二分,Accessibility Collapse 则是”知识还在但访问失败”的连续谱。

哪些能力最容易被遗忘?

基于多个研究和实践经验的总结:

能力维度遗忘风险原因保护策略
世界知识(MMLU 类)高知识点分散,中训练不覆盖就会退化回放通用数据
语言理解(阅读理解)中基础语言能力较鲁棒保留自然语言数据
数学推理中-高依赖特定的推理模式保留数学数据
代码生成中依赖语法和模式记忆保留代码数据
多语言能力极高非目标语言的数据减少后快速退化多语言回放
创意写作高格式化数据训练压制创造性保留文学数据
安全对齐中中训练可能冲淡安全训练重新做安全对齐

10.2 数据层面的遗忘防控

回放策略(Experience Replay)

回放是对抗遗忘最有效且最直接的方法。 核心思路:在中训练数据中混入一定比例的预训练数据(或其高质量子集),让模型在学习新知识的同时”复习”旧知识。

"""
回放数据的配置框架
"""

from dataclasses import dataclass
from typing import Dict

@dataclass
class ReplayConfig:
    """回放策略配置"""

    # 回放数据占中训练总数据的比例
    replay_ratio: float = 0.3

    # 回放数据的来源分布(应该近似预训练的分布)
    replay_distribution: Dict[str, float] = None

    # 是否按能力维度精选回放数据
    targeted_replay: bool = True

    def __post_init__(self):
        if self.replay_distribution is None:
            self.replay_distribution = {
                "high_quality_web": 0.40,
                "code": 0.15,
                "math": 0.10,
                "academic": 0.10,
                "books": 0.10,
                "multilingual": 0.10,
                "creative_writing": 0.05,
            }

def design_replay_strategy(
    midtraining_objective: str,
    midtraining_tokens: float,
    forgetting_tolerance: str = "low"
) -> ReplayConfig:
    """
    根据中训练目标设计回放策略

    forgetting_tolerance:
    - "low": 不能接受任何能力退化 → 回放比例高
    - "medium": 可以接受 1-2% 的退化 → 回放比例中等
    - "high": 可以接受 3-5% 的退化 → 回放比例低
    """
    # 回放比例
    ratio_map = {
        "low": 0.40,
        "medium": 0.25,
        "high": 0.15,
    }
    replay_ratio = ratio_map.get(forgetting_tolerance, 0.25)

    # 根据中训练目标调整回放分布
    if midtraining_objective == "code":
        # 代码中训练时,回放重点保留数学和自然语言
        replay_dist = {
            "high_quality_web": 0.30,
            "math": 0.20,     # 保护数学能力
            "academic": 0.15,
            "books": 0.15,
            "multilingual": 0.15,
            "creative_writing": 0.05,
        }
    elif midtraining_objective == "domain_medical":
        # 医学领域中训练,回放重点保留通用能力
        replay_dist = {
            "high_quality_web": 0.35,
            "code": 0.15,
            "math": 0.10,
            "academic": 0.15,
            "books": 0.10,
            "multilingual": 0.10,
            "creative_writing": 0.05,
        }
    elif midtraining_objective == "long_context":
        # 长上下文扩展,回放保持原有能力
        replay_dist = {
            "high_quality_web": 0.30,
            "code": 0.20,
            "math": 0.15,
            "academic": 0.15,
            "books": 0.10,
            "multilingual": 0.10,
        }
    else:
        replay_dist = None  # 使用默认分布

    return ReplayConfig(
        replay_ratio=replay_ratio,
        replay_distribution=replay_dist,
        targeted_replay=True,
    )

# 示例
config = design_replay_strategy("code", 500e9, "medium")
print(f"回放比例: {config.replay_ratio*100}%")
print(f"回放数据量: {config.replay_ratio * 500e9 / 1e9:.0f}B tokens")
for domain, ratio in config.replay_distribution.items():
    print(f"  {domain}: {ratio*100:.0f}%")

回放比例的选择

这是中训练中最关键的超参数之一。以下是经验性的指导:

中训练场景经验回放比例理由
长上下文扩展30-40%数据分布变化小,但能力要求高
代码/数学增强25-35%数据分布变化中等
单一领域注入35-50%数据分布变化大,遗忘风险高
多语言增强30-40%需要保护原语言能力
综合中训练20-30%目标数据本身已较多样

⚠️ 注意:上表是基于 2023-2024 年工程经验的经验性指导,适合作为快速参考的起点。但 2025-2026 年的研究表明,回放比例可以通过 Scaling Laws 更精确地计算。

从经验值到 Scaling Law 预测

两项研究从根本上改变了回放比例的设定方式:

Apple Forgetting Scaling Laws(ICLR 2026)推导出遗忘量与回放比例之间存在幂律关系,并量化了目标域、可用目标数据量、模型规模三个维度的交互。关键发现:在特定条件下,仅需混入 1% 的预训练数据即可有效防止遗忘——远低于传统经验的 20-40%。

CMR Scaling Law(EMNLP 2024)提出了 Critical Mixture Ratio 的概念:给定目标域和模型规模,可以用小规模实验预测”恰好不遗忘”的最小回放比例。这使得回放比例可以从”经验猜测”走向”数学预测”。

CPT Scaling Law(Wang et al., 2025)则将视角从”终点状态”扩展到”训练过程动态”——不再只关心训练结束时的遗忘量,而是试图预测持续预训练(Continual Pre-Training, CPT)过程中每一步的损失轨迹。其核心贡献在于将 CPT 的损失变化解耦为两个独立驱动因素:

  1. 学习率退火效应:随训练推进、学习率逐步衰减,模型在任意数据上的损失自然下降——这与数据内容无关,纯粹是优化动力学的结果
  2. 数据分布偏移效应:从预训练数据切换到新领域数据,模型需要适应新的分布,这一过程导致的损失变化取决于新旧分布的差异程度

CPT Scaling Law 将持续预训练概念化为一条从”初始预训练轨迹”到”新领域特定轨迹”的过渡:模型在训练初期仍沿预训练轨迹运行,随着训练推进逐渐过渡到新轨迹。两个因素各自贡献可分解、可预测的损失分量,使得研究者能够在训练开始前就绘制出完整的 CPT 损失曲线——而不仅仅是终点的单一数值。

遗忘缩放定律的三代演进:

Apple Forgetting Scaling Laws:
  └── 终点度量:训练结束后,遗忘量 vs 回放比例的幂律关系
  └── 回答:"最终会忘多少?"

CMR Scaling Law:
  └── 临界比例:恰好不遗忘的最小回放比例
  └── 回答:"至少要回放多少?"

CPT Scaling Law:
  └── 训练动力学:解耦 LR 退火与分布偏移,预测全程损失轨迹
  └── 回答:"训练过程中每一步表现如何?"

这意味着遗忘不仅可以从终点状态衡量,还可以从训练过程中的动力学理解——例如,在训练早期出现的大幅损失上升,究竟是因为学习率尚未充分退火,还是因为分布偏移过于剧烈?CPT Scaling Law 的分解框架为这类诊断提供了定量工具。

三项研究的实践指导可以归纳为:

回放比例的设定路线:

经验时代(2023-2024):
  └── 查表 → 设定 20-40% → 观察效果 → 手动调整

Scaling Law 时代(2025-):
  └── 小规模实验 → 拟合幂律关系 → 计算 CMR → 精确设定
  └── 关键变量:目标域分布差异越大 → 需要回放越多
                模型规模越大 → 需要回放越少
                回放数据质量越高 → 需要回放越少

需要注意的是,1% 是理想条件下的下界。实际工程中通常需要更高比例来覆盖多个能力维度(语言、推理、知识等)。但核心变化是:不再盲目设定 30%,而是根据具体场景计算最优比例。

💡 反直觉发现

回放数据不需要和预训练数据一模一样。实际上,用预训练数据的高质量子集做回放效果更好——因为回放的目标不是”重现预训练”,而是”保持关键能力”。高质量子集在更少的 token 中包含了更密集的知识信号。这也解释了为什么 Scaling Law 预测的比例远低于经验值——质量上去了,数量可以下来。

智能回放选择:从静态到动态

上面讨论的回放配比假设回放数据是预先选好的静态子集。但回放的效果还取决于另一个维度:选什么样本来回放。

SuRe(Surprise-prioritised Replay)提出将回放从”静态选择”升级为”动态优先级排序”,核心思想是将回放效果拆解为两个独立维度:

  1. 选择策略(What to replay):不是随机选,而是用模型当前参数对候选回放数据计算 NLL(负对数似然),优先回放模型最”惊讶”的样本——这些正是模型正在遗忘的内容
  2. 整合方式(How to consolidate):使用 EMA(指数移动平均)维护一个慢权重(slow weights),快学习器适应新数据,慢学习器保持旧知识的表示

实践定位:静态均匀回放仍然是合理且简单的默认策略。惊奇度驱动的回放更适用于回放预算受限、需要最大化每个回放样本效率的场景。这种方法目前主要在持续学习研究中得到验证,尚未在大规模中训练(>100B token)中被广泛采用。

配比策略:新域数据 vs 旧域数据

"""
中训练数据配比的决策框架
"""

def midtraining_mix_calculator(
    target_abilities: list,           # 要增强的能力列表
    ability_priorities: dict,          # 每个能力的优先级 (1-10)
    total_tokens: float,              # 总训练 token 数
    available_data: dict,             # 各类数据的可用量
    forgetting_tolerance: str = "low" # 遗忘容忍度
) -> dict:
    """
    计算中训练的数据配比

    算法:
    1. 先分配回放数据(保底)
    2. 按优先级分配目标能力数据
    3. 用高质量通用数据填充剩余
    """
    # Step 1: 回放分配
    replay_ratios = {"low": 0.35, "medium": 0.25, "high": 0.15}
    replay_ratio = replay_ratios[forgetting_tolerance]
    replay_tokens = total_tokens * replay_ratio

    # Step 2: 目标能力数据分配
    remaining = total_tokens - replay_tokens
    total_priority = sum(ability_priorities.values())
    target_allocation = {}
    for ability in target_abilities:
        priority = ability_priorities.get(ability, 5)
        allocation = remaining * (priority / total_priority)
        # 不能超过可用量
        available = available_data.get(ability, float('inf'))
        allocation = min(allocation, available)
        target_allocation[ability] = allocation

    # Step 3: 剩余用通用高质量数据填充
    allocated = sum(target_allocation.values()) + replay_tokens
    filler = total_tokens - allocated

    result = {
        "total_tokens": f"{total_tokens/1e9:.0f}B",
        "replay": {
            "tokens": f"{replay_tokens/1e9:.0f}B",
            "ratio": f"{replay_ratio*100:.0f}%",
        },
        "target_abilities": {
            ability: {
                "tokens": f"{tokens/1e9:.0f}B",
                "ratio": f"{tokens/total_tokens*100:.1f}%",
            }
            for ability, tokens in target_allocation.items()
        },
        "filler_high_quality": {
            "tokens": f"{filler/1e9:.0f}B",
            "ratio": f"{filler/total_tokens*100:.1f}%",
        }
    }

    return result

数据排列顺序对遗忘的影响

中训练数据的排列顺序不是无关紧要的。

三种策略:

  1. 均匀混合(Uniform Mixing)

    • 每个 batch 中均匀混合新旧数据
    • 优点:简单,遗忘最少
    • 缺点:新知识的学习效率可能不是最优
    • 推荐作为默认策略
  2. 渐进式过渡(Progressive Transition)

    • 开始时回放比例高,逐渐降低;新数据比例逐渐升高
    • 优点:平滑过渡,减少初期遗忘
    • 缺点:实现稍复杂
  3. 先新后旧(New First, Then Review)

    • 先大量训练新数据,然后用旧数据”恢复”遗忘
    • 优点:新知识学习效率高
    • 缺点:遗忘恢复可能不完全;如果旧数据不够,恢复效果差
    • 不推荐
"""
中训练数据排列策略
"""

import numpy as np

def generate_mixing_schedule(
    total_steps: int,
    strategy: str = "uniform",
    new_data_final_ratio: float = 0.6,  # 最终新数据占比
) -> list:
    """
    生成每个 step 的数据混合比例
    返回每个 step 的 new_data_ratio
    """
    if strategy == "uniform":
        # 均匀混合
        return [new_data_final_ratio] * total_steps

    elif strategy == "progressive":
        # 渐进式:从 0.3 线性增长到 final_ratio
        start_ratio = 0.3
        return [start_ratio + (new_data_final_ratio - start_ratio) * i / total_steps
                for i in range(total_steps)]

    elif strategy == "warmup_stable":
        # warmup 后稳定
        warmup_steps = int(total_steps * 0.1)
        schedule = []
        for i in range(total_steps):
            if i < warmup_steps:
                ratio = 0.2 + (new_data_final_ratio - 0.2) * i / warmup_steps
            else:
                ratio = new_data_final_ratio
            schedule.append(ratio)
        return schedule

    else:
        raise ValueError(f"Unknown strategy: {strategy}")

无 Replay 无 Task-ID 的架构新范式:TFGN

上面讨论的回放策略、正则化方法(EWC/SI/MAS)、参数隔离方法(O-LoRA/InfLoRA/SD-LoRA/OPLoRA)都有共同的依赖假设:要么需要 replay buffer 保留旧域数据,要么需要 task 标签来识别任务身份,要么需要 Fisher 矩阵等额外结构。这意味着在真实的开放持续预训练场景(无 task 边界、无数据保留、无任务元信息)下,这些方法都无法直接应用。

2026 年 5 月 Anurup Ganguli 提出的 TFGN(Task-Free, Replay-Free Gradient Network) 是第一个在 LLM 规模上同时满足”无 replay + 无 task-ID + 无 Fisher 惩罚”的架构突破。

核心思想:Read/Write 分解

传统方法的隐含假设:
  稳定性 = 不写既往参数(参数空间加约束)
  └── 代价:需要 task ID / replay buffer / Fisher 信息

TFGN 的洞察:
  稳定性是"写"问题,不是"读"问题
  读路径(前向)保持密集
  写路径(参数更新)结构化地"避开既往子空间"
  └── 代价:仅在写路径上加架构约束

架构设计要点:

  • TFGN 是一个架构叠加层(architectural overlay),嵌入在 Transformer 每个 block 内部
  • 不改变前向传播路径——前向完全密集(dense),无 MoE 风格的稀疏路由
  • 仅结构化”写”路径:跨域参数更新被强制构造为”不写既往子空间”的形式
  • 可训练参数仅占总参数 1-14%(parameter-efficient)

关键实验结果(6 个异构文本域 × 3 模型规模 × 2 范式):

指标数值含义
反向迁移(BWT, LLaMA-3.1 8B Retrofit)-0.007几乎完美保留旧域
HellaSwag 保留(3 阶段)0.506 / 0.504 / 0.510跨阶段漂移仅 0.006
跨域梯度 L2-orthogonal 分离≥99.59%写路径结构化分离
跨域正向迁移(Python→JavaScript PPL 下降)26.8%(LLaMA-8B)/ 62.0%(GPT-2 Medium)旧域训练帮助新域
Extension A(元控制层)额外减少遗忘81%闭环元控制器
Extension B(算子级计划向量)cosine fidelity99.96%(30 对)算子级潜在规划器

为什么是根本性突破:

传统持续学习方法谱系(章节前面讨论的):

  ├── Replay 类:需要保留旧域数据
  │     ├── 经验回放(10.2.1)
  │     └── SuRe 惊奇度回放
  ├── 正则化类:需要 Fisher / SI / MAS
  │     ├── EWC
  │     ├── SI / MAS / RWalk
  │     └── Targeted Spectral Regularizer(NTK 视角)
  ├── 参数隔离类:需要 task 标签路由
  │     ├── O-LoRA / InfLoRA / SD-LoRA
  │     ├── OPLoRA(正交投影)
  │     ├── KeepLoRA(SVD 子空间)
  │     └── Merge before Forget(正交初始化)
  └── 架构覆盖类:TFGN
        ├── 无 replay buffer ✅
        ├── 无 task IDs ✅
        ├── 无 Fisher 惩罚 ✅
        └── 在 LLM 规模上闭解 ✅

与 NTK 理论的关系:TFGN 的写路径约束在经验上产生了 ≥99.59% 的梯度正交性,这与 10.1 节讨论的 NTK 函数空间理论形成呼应——TFGN 实际上是在架构层实现了 NTK 视角推荐的”靶向谱保护”。

对工程实践的影响:

  1. 真正的”持续预训练”成为可能:不需要保留任何历史数据,可应用于隐私敏感或合规要求严格的场景
  2. 多域预训练成本降低:不再需要精心设计的数据回放配比
  3. 元控制器与潜在规划器的结合:Extension A + B 提供了”自主学习”和”算子级规划”的 LLM 规模实现
  4. 代码/模型已开源:https://arxiv.org/abs/2605.15053v2(独立研究者 Anurup Ganguli)

适用边界:

  • TFGN 在6 个异构文本域上验证(Prose/Python/Math/Biomedical/Chinese/JavaScript),但未在视觉或多模态上验证
  • Extension A/B 是”工作演示”级别,工程化程度(训练稳定性、收敛速度)尚需大规模实践检验
  • 与现有 EWC/O-LoRA/OPLoRA 等的对比基准尚未广泛建立

章节影响:

  • 10.2 的”数据层面防控”和 10.3 的”训练策略层面”分类需要补充”架构层面”作为第三类
  • 章节开头”灾难性遗忘 = 参数覆盖”叙事需要增加”架构约束可结构性闭解”的补充
  • 章节不应再将”需要 replay”作为持续学习的硬性前提

10.3 训练策略层面的稳定性

学习率的选择

学习率是影响遗忘程度的最重要训练超参数。

经验法则:

  • 中训练的起始学习率通常是预训练峰值学习率的 1/5 到 1/3
  • 学习率越高,学习新知识越快,但遗忘也越严重
  • 学习率越低,遗忘越少,但学习效率也越低
预训练峰值 lr = 3e-4
  ├── 激进中训练: lr = 1e-4   → 学得快,忘得多
  ├── 平衡中训练: lr = 5e-5   → 推荐默认值
  └── 保守中训练: lr = 2e-5   → 忘得少,学得慢

三种学习率策略的选择:

策略学习率方案适用场景遗忘程度
直接退火从预训练末期 lr 开始余弦退火目标与预训练相近最低
短暂 warmup 后退火先 warmup 到中等 lr,然后退火通用中训练(推荐)低-中
重新 warmupwarmup 到较高 lr,然后退火大幅领域转换中-高

权重平均(Weight Averaging / Model Merging)

一种优雅的遗忘缓解方案是权重平均:将中训练后的模型权重与中训练前的模型权重做加权平均。

"""
权重平均缓解遗忘
"""

import torch

def weight_averaging(
    pretrained_state_dict: dict,
    midtrained_state_dict: dict,
    alpha: float = 0.7,  # 中训练权重的比例
) -> dict:
    """
    简单的线性权重平均

    alpha=1.0: 完全使用中训练权重(无回退)
    alpha=0.5: 等权平均
    alpha=0.7: 偏向中训练(推荐起点)
    """
    merged = {}
    for key in pretrained_state_dict:
        if key in midtrained_state_dict:
            merged[key] = (
                alpha * midtrained_state_dict[key] +
                (1 - alpha) * pretrained_state_dict[key]
            )
        else:
            merged[key] = midtrained_state_dict[key]
    return merged

def find_optimal_alpha(
    pretrained_state_dict: dict,
    midtrained_state_dict: dict,
    eval_fn,  # 评估函数,输入 state_dict,返回 (new_task_score, old_task_score)
    alpha_range: list = None,
) -> dict:
    """
    搜索最优的 alpha 值
    通过在多个 alpha 上评估,找到新旧能力的最佳平衡点
    """
    if alpha_range is None:
        alpha_range = [0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]

    results = []
    for alpha in alpha_range:
        merged = weight_averaging(pretrained_state_dict, midtrained_state_dict, alpha)
        new_score, old_score = eval_fn(merged)
        results.append({
            "alpha": alpha,
            "new_task_score": new_score,
            "old_task_score": old_score,
            "harmonic_mean": 2 * new_score * old_score / max(new_score + old_score, 1e-8),
        })

    # 选择调和平均最高的 alpha
    best = max(results, key=lambda x: x["harmonic_mean"])
    return {
        "optimal_alpha": best["alpha"],
        "all_results": results,
        "recommendation": f"alpha={best['alpha']}: new={best['new_task_score']:.1f}%, "
                         f"old={best['old_task_score']:.1f}%"
    }

权重平均的优势:

  • 零额外训练成本——只需做一次权重合并
  • 可以在不同 alpha 上快速实验
  • 效果出奇地好:通常能恢复 50-80% 的遗忘

权重平均的限制:

  • 线性平均假设参数空间是凸的,但实际并非如此
  • 对于差异很大的模型(中训练很长时间),效果会下降
  • 不能完全替代数据回放——两者配合使用效果最好

更高级的合并方法

除了简单的线性平均,还有多种更高级的模型合并方法:

  1. SLERP(球面线性插值):在球面上进行插值,理论上更适合高维参数空间
  2. TIES-Merging:只合并”重要”的参数变化,消除冲突
  3. DARE:随机删除小的参数变化,然后重新缩放
  4. Task Arithmetic:用”任务向量”(中训练后减去中训练前的参数差)进行加减运算
  5. RECALL(表示对齐合并):利用隐层表示(hidden representations)的相似度指导逐层合并(EMNLP 2025)

前四种方法的合并系数都是全局统一的——所有层使用同一个 alpha。RECALL 的关键升级在于引入了逐层自适应:

全局统一合并 vs 逐层自适应合并:

全局统一(方法 1-4):
  所有层统一 alpha=0.7
  └── 简单,但忽略了不同层的漂移差异

逐层自适应(RECALL):
  Layer 0: alpha=0.9  ← 表示漂移大,保留更多旧权重
  Layer 5: alpha=0.6  ← 表示漂移小,可以用更多新权重
  Layer 10: alpha=0.8 ← 中等漂移
  ...
  └── 只需在少量聚类样本上计算表示相似度,成本很低

RECALL 不需要访问历史训练数据,可以和 TIES/DARE 组合使用——先用 TIES 消除参数冲突,再用 RECALL 决定逐层合并比例。

EWC 与参数高效方案

弹性权重巩固(Elastic Weight Consolidation, EWC)是经典的遗忘防控方法:

"""
EWC 的核心思想(简化版)
"""

def ewc_loss(
    model_params: dict,
    pretrained_params: dict,
    fisher_information: dict,  # 预训练参数的 Fisher 信息矩阵
    lambda_ewc: float = 1.0,   # EWC 正则化强度
    standard_loss: float = 0.0, # 正常训练损失
) -> float:
    """
    EWC 损失 = 标准损失 + λ * Σ F_i * (θ_i - θ*_i)²

    核心思想:
    - Fisher 信息矩阵的对角元素 F_i 衡量参数 θ_i 对旧任务的重要性
    - 重要的参数(F_i 大)变化被惩罚得更重
    - 不重要的参数(F_i 小)可以自由变化去适应新任务
    """
    ewc_penalty = 0.0
    for name in model_params:
        if name in fisher_information:
            ewc_penalty += (
                fisher_information[name] *
                (model_params[name] - pretrained_params[name]) ** 2
            ).sum()

    total_loss = standard_loss + lambda_ewc * ewc_penalty
    return total_loss

EWC 在大模型中训练中的实际应用有限,原因包括:

  • Fisher 信息矩阵的计算对大模型来说成本很高
  • 只用对角近似效果有限
  • 在实践中,数据回放 + 学习率调整通常就够了

LoRA/QLoRA 作为遗忘缓解方案:

一种越来越流行的做法是在中训练中使用参数高效微调(PEFT)方法。2026 年的大规模实证研究表明,标准 LoRA(无任何额外正则化)本身就是持续学习中强大的遗忘缓解基线——低秩约束提供了选择性可塑性(selective plasticity):只允许模型在低秩子空间内调整,天然限制了参数空间中的干扰范围。

LoRA 的遗忘缓解技术路线:

标准 LoRA(基线):
  ├── 低秩约束 → 天然限制参数干扰 → 遗忘小
  ├── 但仍可能干扰预训练权重的主要奇异方向
  └── 适用:轻量级领域适应

OPLoRA(正交投影 LoRA):
  ├── 双边正交投影约束:更新限制在主要奇异方向的正交补空间
  ├── 理论保证:微调不会干扰已学知识的编码
  └── 适用:遗忘敏感场景(LLaMA-2 7B / Qwen2.5 7B 上已验证)

OPLoRA 揭示了标准 LoRA 的一个盲区:虽然只更新低秩矩阵、主干参数不变,但低秩更新仍然可能干扰预训练权重的主要奇异方向——这些方向编码了模型最重要的已学知识。OPLoRA 的双边正交投影将更新约束在这些方向的正交补空间内,从理论上保证微调不会覆盖已有知识。

ICLR 2026 LoRA 持续学习三大新方法

OPLoRA 之后,2026 年 ICLR 涌现了三个重要的 LoRA 持续学习方法,分别从子空间硬约束、正交基提取、双机制参数-特征分离三个角度推进。

KeepLoRA(ICLR 2026):SVD 子空间硬约束

KeepLoRA(ICLR 2026,东南大学+浙江大学)通过 SVD 分解预训练权重,发现了清晰的双子空间结构:

SVD 分解 W = U S V^T 的发现:

主子空间(W_p = U[:, 1:p],大奇异值方向):
  └── 编码通用知识(general knowledge)
  └── 置零低能量分量时,ImageNet 等通用数据集性能几乎不变
  └── 关键:必须严格保护

残差子空间(小奇异值方向):
  └── 编码任务/领域特定知识
  └── 移除后,特定数据集性能急剧下降
  └── 关键:可作为新任务更新的"领地"

方法:将新任务的 LoRA 更新投影到主子空间的正交补(残差子空间),同时用首步梯度信息初始化 LoRA 以保持可塑性。

三重平衡:

KeepLoRA 解决的三个竞争目标:

前向稳定性(Forward stability):
  └── 保护主子空间 → 不破坏预训练通用知识
后向稳定性(Backward stability):
  └── 累积提取历史任务的"主导特征方向" → 不遗忘旧任务
可塑性(Plasticity):
  └── 在残差子空间内自由更新 → 新任务学习能力强

与现有方法对比:

方法保护对象初始化策略推理成本
O-LoRA通过正交约束随机不变
InfLoRA统一神经正切核空间随机不变
SD-LoRA知识蒸馏随机不变
OPLoRA主要奇异方向随机不变
KeepLoRA主子空间 + 历史主导方向梯度投影不变

Merge before Forget(ICLR 2026):单 LoRA 持续学习

Merge before Forget / SLAO(ICLR 2026,Pennsylvania State University)解决了另一个根本问题:持续累积 LoRA 适配器导致的存储爆炸。

传统 LoRA 持续学习方法的存储复杂度:

O-LoRA / InfLoRA / SD-LoRA:
  └── 每个任务保存一个 LoRA 适配器
  └── 总内存 = T × (m+n) × r,T 为任务数
  └── 问题:长期部署成本不可接受

Merge before Forget:
  └── 维护单一统一 LoRA
  └── 总内存 = (m+n) × r,与任务数无关
  └── 优势:常数内存复杂度

核心方法:

  1. 正交基提取(Orthogonal Basis Extraction):

    • 从前一个任务的 LoRA 矩阵 Aft,i−1A_{\text{ft},i-1} 通过 QR 分解提取正交基
    • 用正交基初始化新任务 LoRA 的 Aft,i(0)A^{(0)}_{\text{ft},i}
    • 公式:Aft,i(0)=QiTA^{(0)}_{\text{ft},i} = Q_i^T,其中 QiRi=QR(Aft,i−1T)Q_i R_i = \text{QR}(A_{\text{ft},i-1}^T)
    • 保证 Aft,i(0)(Aft,i(0))T=IrA^{(0)}_{\text{ft},i} (A^{(0)}_{\text{ft},i})^T = I_r
  2. 时感知非对称合并(Time-aware Asymmetric Merging):

    • 利用 LoRA 组件 A/B 的内在不对称性——A 矩阵跨任务相似度高,B 矩阵差异大
    • A 直接覆盖:Amerge,i=Aft,iA_{\text{merge},i} = A_{\text{ft},i}
    • B 时感知缩放:Bmerge,i=Bmerge,i−1+λ(i)⋅(Bft,i−Bmerge,i−1)B_{\text{merge},i} = B_{\text{merge},i-1} + \lambda^{(i)} \cdot (B_{\text{ft},i} - B_{\text{merge},i-1}),λ(i)=1/i\lambda^{(i)} = 1/\sqrt{i}

理论支撑(NTK 体制下的分析):

论文证明在 NTK 体制下,最小化遗忘和 intransigence 错误等价于最小化 ∥ΔWt−ΔWi∥F\|\Delta W_t - \Delta W_i\|_F——这正是非对称合并所实现的目标。

Janus-LoRA:参数级正交 + 特征级分离的双机制

Janus-LoRA(2026-05-27)识别了 LoRA 持续学习 SOTA 仍未解决的”复合更新违反正交性”问题:

传统 LoRA 优化的盲区:

  优化器独立更新 A 和 B → 复合更新 ΔW = sBA
  但 ΔW 在参数空间的方向 ≠ 在历史子空间正交的方向
  └── 后果:所谓"正交 LoRA"在数学上不成立
  └── 表现:重新引入干扰,破坏稳定性

双机制设计:

机制作用层级方法解决什么问题
Gradient Rectification (GR)参数级闭式解耦 A/B 因子更新,强制对历史子空间正交(Online Estimation 识别)复合更新违反正交性
Decoupled Margin Loss (DML)特征级将新特征推离旧特征特征空间被新类”侵蚀”

关键洞察:

仅参数级正交 → 牺牲可塑性(新类无法找到”空地”) 仅特征级分离 → 牺牲稳定性(旧类表征被新类干扰) 双重机制协同 → 在多个 CL 基准上达到 SOTA

与 KeepLoRA/Merge before Forget 的对比:

KeepLoRA:     子空间硬约束(主子空间保护)        + 梯度初始化
Merge before: 单 LoRA 持续合并(常数内存)        + 正交基提取
Janus-LoRA:   复合更新正交矫正(GR)              + 特征级分离(DML)

三种方法覆盖 LoRA 持续学习的三个不同设计维度。

LoRA 适配器公共仓的审计协议

以上 LoRA 合并/持续学习方法都有一个隐含前提:输入的 LoRA 适配器是”可信的”。但 Aharon/Glazer/Aharon(OpenReview 2025)的 Audit Before You Merge 研究表明,公共 LoRA 适配器公共仓存在严重的可信度问题,直接挑战了章节当前 LoRA 合并实证的”基准不严谨”问题。

核心发现:

公共 LoRA 适配器的两类主要问题:

1. 基础模型不匹配(Base model mismatch):
   └── 上传的 LoRA 声明基于 "LLaMA-3-8B",实际基于 LLaMA-3-8B 的某个微调版本
   └── 后果:与声称的基础模型 SVD 谱差异大,合并后基线偏置

2. 近零 delta 范数(Near-zero delta norm):
   └── LoRA 权重变化 $\Delta W$ 极小(接近空操作)
   └── 后果:合并时这些 LoRA 贡献接近零,但合并方法会"虚高"干扰度量

   └── 量化影响:单纯合并造成 ~10pp 干扰度量虚高

两阶段审计协议:

"""
Audit Before You Merge 协议(伪代码)
"""

def audit_lora_adapter(adapter, claimed_base_model) -> AuditResult:
    # Stage 1: 基础模型匹配(必须通过才能继续)
    base_spectrum = compute_top_k_singular_values(claimed_base_model)
    adapter_spectrum = compute_top_k_singular_values(adapter.base_weights)
    base_match_score = cosine_similarity(
        base_spectrum[:k], adapter_spectrum[:k]
    )
    if base_match_score < 0.95:
        return AuditResult(
            status="REJECTED",
            reason=f"Base model mismatch (cosine={base_match_score:.3f})"
        )
    
    # Stage 2: 非平凡 delta 范数
    delta_norm = compute_lora_delta_norm(adapter)
    if delta_norm < 1e-4:  # 经验阈值
        return AuditResult(
            status="REJECTED",
            reason=f"Trivial delta (norm={delta_norm:.2e})"
        )
    
    return AuditResult(status="VERIFIED", delta_norm=delta_norm)

无泄漏探测协议(Leakage-Free Probing Protocol):

作为遗忘研究的预条件——若 LoRA 适配器在测试集上已经”见过”答案,则任何”抗遗忘”评估都是不可靠的。该协议要求评估时使用 LoRA 训练集严格隔离的 holdout 集合。

关键实验结果(4 适配器审计集合):

方法GSM8KHumanEval
朴素合并(未审计)-10.5pp-18.3pp
Task Arithmetic (λ=0.5)落在未合并基线标准误差内-
负系数 math 适配器 (λ=-0.5)探测最优-

对章节当前实证的反思:

章节当前 LoRA 合并实证的潜在问题:

  ├── 缺乏基础模型匹配验证
  │     └── 不同 LoRA 可能基于不同基础模型,合并无意义
  ├── 缺乏 delta 范数过滤
  │     └── 空操作 LoRA 贡献被高估
  ├── 缺乏 holdout 隔离
  │     └── 训练集泄漏导致"抗遗忘"假象
  └── 缺乏负系数探索
        └── 章节未发现"negation-aware merging"可被利用

建议章节未来的 LoRA 合并实证都应通过该审计协议。

设备端 LoRA 持续融合:K-Merge 系列

章节前面的 LoRA 合并方法主要针对服务器端多任务离线合并。但用户级个性化场景(移动设备、边缘端)需要在线持续融合——K-Merge / K-Merge++(K-Merge: Online Continual Merging of Adapters for On-device Large Language Models,Shenaj 等,2025-10-15)给出了工业级方案。

三段式管线:

K-Merge 在线持续融合流程:

1. 相似性聚类(Similarity Clustering)
     └── 计算新 LoRA 与已存储 LoRA 的余弦相似度
     └── 匹配兼容性 → 决定是新增还是合并
2. 历史感知融合(History-aware Merging)
     └── 动态加权平衡新旧任务
     └── 保持每个任务贡献的可追溯性
3. 存储决策(Storage Decision)
     └── 在存储预算内选择新增槽或融合现有
     └── 自动化"添加"与"融合"的权衡

实验配置:Llama-3.2-1B / Qwen-2.5-1.5B × 5 任务 × 8 语言。结果显示 K-Merge 优于 Linear / TIES / DARE 合并基线,K-Merge++ 对任务到达顺序鲁棒性更强。

对工业实践的意义:

  • 设备端部署:每个用户设备可独立运行 K-Merge 持续学习
  • 隐私保护:无需回传数据到中心服务器
  • 个性化:用户级 LoRA 适配器持续累积并自动融合
  • 存储高效:自动决策减少冗余存储

易感区定位:从”层冻结”到”基于激活态子空间的精准保护”

章节前面的”权重平均 / LoRA / OPLoRA”主要处理参数空间的稳定性,但哪些层/参数最易感一直缺乏精细化定位。2026 年的 Mechanistic Analysis of Catastrophic Forgetting 通过对 20 个最先进模型(10 闭源 + 10 开源)的大规模比较分析,提供了易感区定位的方法学框架。

关键发现:最高遗忘易感区位于中段网络 MLP 和 MoE 路由投影。

20 个模型的大规模定位方法学:

1. CKA(Centered Kernel Alignment):
     └── 比较不同训练阶段的层表征相似度
2. Activation Patching:
     └── 干预特定层的激活,观察下游任务性能变化
3. Frobenius 漂移跟踪:
     └── 量化参数更新矩阵的 Frobenius 范数变化
4. MoE 路由门稳定性测量:
     └── 追踪 router gate 决策的稳定性
5. 闭源模型用 SOD(Semantic Output Drift)间接度量:
     └── 通过语义相似度估计输出空间漂移

位置精确定位:

层/模块遗忘易感度主要机制
Embedding 层低输入分布稳定
浅层 Attention(Layer 1-25%)中低主要编码浅层语法
中层 MLP(Layer 25-75%)极高编码任务特定模式
MoE 路由投影极高路由决策对分布敏感
深层 Attention中与中层 MLP 协同
末层 Norm中输出归一化
LM Head低任务无关

LRCP(Low-Rank Circuit Projection)方法:

基于上述定位,论文提出 LRCP 方法——将梯度更新限制在历史任务激活态张成的子空间的正交补:

"""
LRCP(Low-Rank Circuit Projection)伪代码
"""

def lrcp_gradient(
    grad: torch.Tensor,           # 当前任务梯度
    historical_activations: torch.Tensor,  # 历史任务激活态矩阵
    rank_threshold: int = 16,     # 历史子空间保留秩
) -> torch.Tensor:
    """
    1. 对历史激活态矩阵做 SVD 分解
    2. 取前 rank_threshold 个右奇异向量作为历史子空间基 U_hist
    3. 将当前梯度投影到 U_hist 的正交补
    """
    # 历史子空间基(来自历史任务激活态)
    U_hist = torch.svd_lowrank(historical_activations, q=rank_threshold)[0]
    
    # 投影到 U_hist 张成子空间
    projection = U_hist @ (U_hist.T @ grad)
    
    # 取正交补(保留不干扰历史子空间的部分)
    orthogonal_projection = grad - projection
    return orthogonal_projection

与已有方法的关系:

参数空间方法(章节前面讨论):
  └── 保护对象:参数本身(θ)
  └── 保护依据:Fisher / SI / MAS / OPLoRA SVD
  └── 保护粒度:参数级别

LRCP:
  └── 保护对象:激活态子空间
  └── 保护依据:历史任务激活态的 SVD 低秩近似
  └── 保护粒度:子空间级别(更精细)

两者关系:LRCP 是从"参数空间保护"到"激活态空间保护"的升级

对工程实践的指导:

  1. 诊断顺序:先用 CKA/activation patching 定位易感层/模块,再针对性保护
  2. LRCP 比层冻结更精细:层冻结粒度太粗(layer 25-75% 全部冻结会牺牲可塑性)
  3. 历史激活态采集成本:需要少量历史数据(小 buffer)计算 SVD,不要求完整 replay

置信度提示:LRCP 在 20 模型上有大规模证据,但工程化程度(如何高效采集历史激活态、如何与 LoRA 集成)尚需验证。

全参数中训练 vs LoRA 中训练

全参数中训练:
  ├── 优点:学习能力强,能做大幅度调整
  ├── 缺点:遗忘风险高,需要大量回放数据
  └── 适用:大规模中训练(>100B token)

LoRA 中训练:
  ├── 优点:低秩约束天然缓解遗忘(选择性可塑性)
  ├── 注意:学习容量受秩限制,大幅调整需要更高的秩或多阶段策略
  └── 适用:轻量级领域适应(10B-50B token)

折中方案:阶段性 LoRA → 合并 → 再 LoRA
  ├── 第 1 阶段:LoRA 训练一段(遗忘小)
  ├── 合并 LoRA 参数到主干
  ├── 第 2 阶段:在合并后的模型上再加 LoRA
  └── 优点:分步累积变化,每步遗忘都可控

10.4 遗忘检测与评估

遗忘监控指标体系

"""
中训练遗忘监控系统
"""

from typing import Dict, List, Optional
from dataclasses import dataclass

@dataclass
class ForgettingMetrics:
    """遗忘监控指标"""
    # 绝对分数
    current_scores: Dict[str, float]    # 当前各 benchmark 分数
    baseline_scores: Dict[str, float]   # 中训练前的分数

    # 遗忘量
    forgetting: Dict[str, float] = None  # 各维度的遗忘量

    # 汇总
    max_forgetting: float = 0.0         # 最大遗忘量
    avg_forgetting: float = 0.0         # 平均遗忘量
    num_degraded: int = 0               # 退化的维度数量

    def __post_init__(self):
        self.forgetting = {}
        for key in self.baseline_scores:
            if key in self.current_scores:
                delta = self.current_scores[key] - self.baseline_scores[key]
                self.forgetting[key] = round(delta, 2)

        degraded = [v for v in self.forgetting.values() if v < -0.5]
        self.max_forgetting = min(self.forgetting.values()) if self.forgetting else 0
        self.avg_forgetting = sum(degraded) / max(len(degraded), 1) if degraded else 0
        self.num_degraded = len(degraded)

    def report(self) -> str:
        lines = ["遗忘监控报告", "=" * 50]

        # 汇总
        status = "🟢 正常" if self.max_forgetting > -2 else (
            "🟡 轻微遗忘" if self.max_forgetting > -5 else "🔴 严重遗忘"
        )
        lines.append(f"状态: {status}")
        lines.append(f"退化维度数: {self.num_degraded}/{len(self.forgetting)}")
        lines.append(f"最大遗忘: {self.max_forgetting:+.2f}%")
        lines.append(f"平均遗忘: {self.avg_forgetting:+.2f}%")
        lines.append("")

        # 详细
        lines.append(f"{'维度':<20} {'基线':>8} {'当前':>8} {'变化':>8} {'状态':>4}")
        lines.append("-" * 52)
        for key in sorted(self.forgetting.keys()):
            baseline = self.baseline_scores[key]
            current = self.current_scores[key]
            delta = self.forgetting[key]
            status = "✅" if delta > -1 else ("⚠️" if delta > -3 else "❌")
            lines.append(f"{key:<20} {baseline:>7.1f}% {current:>7.1f}% {delta:>+7.1f}% {status}")

        return "\n".join(lines)

# 使用示例
metrics = ForgettingMetrics(
    baseline_scores={
        "MMLU": 55.0, "C-Eval": 50.0, "HellaSwag": 72.0,
        "ARC": 48.0, "HumanEval": 25.0, "GSM8K": 40.0,
    },
    current_scores={
        "MMLU": 52.0, "C-Eval": 46.0, "HellaSwag": 71.5,
        "ARC": 47.0, "HumanEval": 50.0, "GSM8K": 58.0,
    },
)
print(metrics.report())

实时评估 vs 离线评估

评估方式频率成本覆盖面适用场景
实时评估每 1-5B token低2-3 个 benchmark日常监控
定期评估每 20-50B token中6-10 个 benchmark里程碑检查
离线评估完成后高全部 benchmark + 人工最终验证

实时评估的关键:选择对遗忘最敏感的 benchmark 作为”金丝雀”指标。

推荐的”金丝雀” benchmark:

  • MMLU:对世界知识遗忘最敏感
  • HellaSwag:对常识推理遗忘敏感
  • C-Eval/CMMLU:对中文能力遗忘敏感(如果是中文模型)

快速遗忘检测的 Proxy 方法

完整的 benchmark 评估耗时较长。以下是几种快速检测遗忘的方法:

方法 1:训练数据子集的 Perplexity

def quick_forgetting_check(
    model,
    pretrain_sample: list,  # 预训练数据的小样本(1000-5000 条)
    midtrain_sample: list,  # 中训练数据的小样本
) -> dict:
    """
    快速遗忘检测
    
    如果模型在预训练数据上的 PPL 显著升高,说明遗忘正在发生
    """
    pretrain_ppl = compute_perplexity(model, pretrain_sample)
    midtrain_ppl = compute_perplexity(model, midtrain_sample)

    return {
        "pretrain_ppl": pretrain_ppl,
        "midtrain_ppl": midtrain_ppl,
        "ppl_ratio": pretrain_ppl / max(midtrain_ppl, 1),
        "warning": pretrain_ppl > midtrain_ppl * 1.5,
        # 如果预训练数据的 PPL 比中训练数据高 50% 以上,可能有遗忘
    }

方法 2:隐层表示的漂移检测

监控模型中间层输出的变化,如果特定层的表示发生大幅漂移,可能预示着遗忘。

方法 3:生成样本的定性检查

给模型一组固定的 prompt,在中训练过程中定期生成回复,人工检查是否出现退化。这是最直观但也最难自动化的方法。

两类隐性灾难性遗忘:超越准确率指标的监测

上面所有遗忘监控方法都基于准确率/PPL/benchmark 分数等表层指标。但 SITS2026 综述 揭示了两类被准确率指标漏掉的隐性遗忘模式——这些模式在分数上看似正常,但模型实际能力已发生退化。

隐性遗忘 1:语义锚点漂移(Semantic Anchor Drift)

现象定义:
  词向量空间中稳定的高频概念锚点("patient"、"consent"、"liability")
  在持续训练过程中 cosine 相似度系统性下降

关键数字:
  └── 10K 步内 cosine 相似度下降 18.7%
  └── 同期分类 loss 仅微增 0.02(表面上"没遗忘")
  └── 但下游概念检索 / 关系推理任务性能下降

为什么准确率指标漏掉了:

准确率 = 输入匹配到正确标签的概率
  └── 表面看:分类 loss 微增 0.02 = 模型还能正确分类
  └── 实际:词向量的几何结构已偏移
        └── "patient" 在向量空间里和"医生"距离变远
        └── 但分类层可能已经过拟合到"表面模式"
        └── 后果:OOD 检索 / 关系推理 / 抽象语义任务表现差

诊断方法:
  └── 锚点校准损失 L_anchor = Σ ||emb_t(token_i) − emb_0(token_i)||²
  └── 监控 token_i 在 t 时刻的 embedding 与初始 embedding 的 L2 距离

隐性遗忘 2:分布感知钝化(Distribution Awareness Dulling)

现象定义:
  即使输入 token 分布熵提升 35%(OOD 输入增加)
  各层 attention entropy 变化幅度衰减 >62%
  └── 模型对"陌生输入"不再敏感
  └── OOD 鲁棒性系统性下降

关键数字:
  └── 输入分布熵提升 35%(训练数据多样性增加)
  └── attention entropy 变化幅度衰减 >62%
  └── 分类 / 推理准确率可能不变或微增
  └── 但 OOD 检测 / 抗对抗样本能力下降

为什么准确率指标漏掉了:

准确率在 in-distribution 上稳定 = 模型"还行"
  └── 但 attention entropy 衰减 = 模型对"意外"的反应能力下降
  └── 后果:
        ├── OOD 检测阈值失准(高置信度错误预测)
        ├── 对抗样本防御能力下降
        └── 鲁棒性指标(certified accuracy)下降

诊断方法:
  └── 动态分布感知正则(DDAR):KL 散度约束 attention entropy
  └── 监控"输入分布熵 - attention 熵响应"的传递函数

缓解方案

"""
两类隐性遗忘的缓解(伪代码)
"""
import torch
import torch.nn.functional as F


def anchor_calibration_loss(
    model, anchor_tokens: list, initial_embeddings: dict
) -> torch.Tensor:
    """
    锚点校准损失:维持高频概念锚点的 embedding 稳定
    """
    loss = 0.0
    current_embeddings = model.get_input_embeddings()
    for token in anchor_tokens:
        token_id = model.tokenizer.convert_tokens_to_ids(token)
        cur_emb = current_embeddings(token_id)
        init_emb = initial_embeddings[token]
        loss += (cur_emb - init_emb).pow(2).sum()
    return loss / len(anchor_tokens)


def dynamic_distribution_aware_regularizer(
    model, 
    input_entropy: torch.Tensor,  
    target_attention_entropy: torch.Tensor,
    decay: float = 0.999,
) -> torch.Tensor:
    """
    动态分布感知正则(DDAR)
    通过 KL 散度约束 attention entropy 跟踪输入分布变化
    """
    # 使用 batch-wise moving average(不更新全局 LayerNorm EMA)
    # decay=0.999 控制历史平均的窗口
    predicted_entropy = target_attention_entropy.mean(dim=-1)
    kl_div = F.kl_div(
        F.log_softmax(input_entropy, dim=-1),
        F.softmax(predicted_entropy, dim=-1),
        reduction='batchmean',
    )
    return kl_div

关键工程决策:

训练配置的隐性遗忘防护选项:

  1. 启用锚点校准损失 L_anchor(权重 0.01-0.1)
  2. 启用 DDAR(KL 散度约束,权重 0.001-0.01)
  3. 禁用全局 LayerNorm EMA 更新
     └── 改用 batch-wise moving average with decay=0.999
  4. 增加 OOD 鲁棒性指标到监控 dashboard
     └── 不仅看 benchmark,还要看 certified accuracy

对章节评估体系的扩展:

评估维度传统指标隐性遗忘新指标
任务准确率MMLU/C-Eval/HumanEval—
困惑度预训练数据 PPL—
词向量几何—锚点校准 L_anchor
注意力响应—DDAR / 熵传递函数
OOD 鲁棒性—certified accuracy

置信度提示:SITS2026 的两类隐性遗忘提供了新视角,但原始论文主要来自 CSDN 二手报告,学术来源溯源(待补充 arXiv 链接)和大规模实验验证尚在进行中。


10.5 🔬 显微镜案例:一次遗忘问题的发现与修复

背景

某团队在一个 13B 中英双语模型上做中训练,目标是增强代码和数学能力。

配置

预训练模型:13B, 在 5T token 上预训练
中训练数据:300B token
  - 代码数据: 40% (120B)
  - 数学数据: 15% (45B)
  - 通用回放: 20% (60B)
  - 高质量网页: 15% (45B)
  - 中文数据: 10% (30B)
学习率:6e-5, 余弦退火到 1e-6

发现问题

中训练进行到 150B token(一半)时,实时监控报告了异常:

监控告警:
  MMLU: 55.0 → 51.2 (-3.8%)  ⚠️ 超过阈值
  C-Eval: 50.0 → 43.5 (-6.5%) ❌ 严重退化
  HellaSwag: 72.0 → 70.8 (-1.2%)  ✅ 可接受
  
  HumanEval: 25.0 → 42.0 (+17.0%)  ✅ 增长如期
  GSM8K: 40.0 → 55.0 (+15.0%)  ✅ 增长如期

代码和数学能力的增长符合预期,但中文知识(C-Eval)出现了严重退化。

分析原因

团队进行了详细的排查:

  1. 查看中文数据的实际配比

    • 发现中文数据只有 10%,但预训练中中文数据占 25%
    • 这意味着中训练中中文的”浓度”大幅降低
  2. 检查回放数据的语言分布

    • 20% 的通用回放数据中,中文只占 15%(约 3% 的总训练量)
    • 总的中文数据比例:10% + 20%×15% = 13%,远低于预训练的 25%
  3. 对比不同能力的遗忘速率

    • 英文知识(MMLU)遗忘较慢(-3.8%)
    • 中文知识(C-Eval)遗忘较快(-6.5%)
    • 验证了”非目标语言遗忘更快”的规律

修复方案

方案:调整后半段训练的数据配比

后 150B token 的数据配比(修正后):
  - 代码数据: 35% (52.5B)   ← 略降
  - 数学数据: 12% (18B)     ← 略降
  - 通用回放: 18% (27B)     ← 略降
  - 高质量网页: 10% (15B)    ← 降
  - 中文数据: 25% (37.5B)    ← 大幅增加
  
中文数据细分:
  - 中文百科+知乎: 10B
  - 中文学术论文: 5B
  - 中文书籍: 5B
  - 中文代码相关: 7.5B   ← 中文代码注释+文档
  - 中文数学: 5B         ← 中文数学题+教材
  - 中文通用网页: 5B

结果

中训练完成后的最终评估:

Benchmark中训练前修复前(150B)修复后(300B)变化
MMLU55.051.253.8-1.2% ✅
C-Eval50.043.548.2-1.8% ✅
HellaSwag72.070.871.0-1.0% ✅
HumanEval25.042.048.0+23.0% ✅
GSM8K40.055.060.0+20.0% ✅

通过及时发现和修复,C-Eval 的退化从 -6.5% 恢复到了 -1.8%,在可接受范围内。

教训

  1. 中文数据(或目标语言数据)在回放中的比例应该至少等于预训练中的比例
  2. 实时监控是关键——如果到训练结束才发现遗忘,已经来不及了
  3. 数据配比的调整可以在训练中途进行——这比重新训练划算得多
  4. 不同语言的遗忘速率不同——需要分别监控

10.6 中训练稳定性的其他考量

📌 三阶段视角

2026 年最新综述将 LLM 持续学习方法按训练阶段分为三类,不同阶段的遗忘模式和最优缓解策略截然不同:

  1. 持续预训练/中训练(本章主题):数据回放 + 学习率控制为核心
  2. 持续微调:参数高效方法(LoRA/正交约束)+ 正则化为核心
  3. 持续对齐:偏好保持 + 安全性维护为核心

本章聚焦第一阶段。持续微调和持续对齐的数据策略分别在后续的 SFT(第 11-13 章)和对齐(第 14-16 章)中讨论。

Loss Spike 的预防

中训练中的 loss spike(训练损失突然飙升)是另一个常见问题,通常由以下原因引起:

  1. 数据分布突变:从一个 epoch 切换到另一个 epoch 时,数据分布可能突然变化
  2. 上下文长度切换:从 8K 突然变到 32K
  3. 异常数据批次:少量极端样本导致梯度爆炸

预防措施:

"""
中训练稳定性的关键配置
"""

stability_config = {
    # 梯度裁剪(必须设置)
    "gradient_clip_norm": 1.0,

    # 学习率 warmup(中训练开始时)
    "warmup_steps": 200,

    # 数据混合的平滑性
    "batch_mixing": "uniform",  # 每个 batch 都均匀混合各类数据

    # 异常检测
    "loss_spike_threshold": 2.0,  # loss 突然升高超过 2 倍时告警
    "auto_rollback": True,        # loss spike 时自动回退到上一个检查点

    # 检查点频率
    "save_every_steps": 500,      # 频繁保存,方便回退
}

训练过程中的数据配比动态调整

更高级的方案是根据模型的实时评估结果动态调整数据配比:

训练过程中的反馈循环:

  每 N 步 → 快速评估(金丝雀 benchmark)
      ↓
  检测到某维度退化?
      ↓ 是                     ↓ 否
  增加该维度的回放数据比例     保持当前配比
      ↓
  调整后继续训练

这种方法在理论上最优,但在实践中有挑战:

  • 评估有延迟,可能不够及时
  • 频繁调整配比可能引入训练不稳定
  • 需要足够灵活的数据加载基础设施

10.7 认知演变时间线

时间认知阶段代表性发现
2019”遗忘是微调的问题”主要在任务级微调中讨论
2022”继续预训练也会遗忘”Code Llama 等实践暴露问题
2023”回放数据是关键”经验性地发现 20-30% 回放效果好
2024”系统化的遗忘研究”ACL/EMNLP 上多篇论文量化遗忘;CMR Scaling Law 首次将回放比例形式化
2025 前半”虚假遗忘被识别”ICLR 2025 Spurious Forgetting——部分遗忘是评测假象而非真实知识丢失
2025 后半”遗忘可数学建模”Apple Forgetting Scaling Laws(ICLR 2026)——回放比例可精确计算;OPLoRA 正交投影 LoRA 提供理论级遗忘防护
2026 上半”根叙事被颠覆”NTK 函数空间理论解释参数空间正则化器失效;Accessibility Collapse 证明遗忘是可访问性失败而非知识擦除
2026”架构层面闭解”TFGN 首个无 replay 无 task-ID 闭解 LLM 规模灾难性遗忘;KeepLoRA/Merge before Forget/Janus-LoRA(ICLR 2026)三路径 LoRA 持续学习
2026”易感区精准定位”Mechanistic Analysis + LRCP——中层 MLP/MoE 路由投影是最高易感区,基于激活态子空间的精准保护
2026”隐性遗忘被识别”SITS2026 揭示语义锚点漂移(cosine -18.7%)和分布感知钝化(attn entropy -62%)两类表层指标漏掉的失效模式
2026”遗忘管理全栈成熟”三层防线:Scaling Law 预测回放比例 + 智能回放选择 + 表示对齐逐层合并;方法按训练阶段(预训练/微调/对齐)分化

本章要点回顾

  1. 灾难性遗忘在中训练中是真实且严重的风险——不是理论问题
  2. 区分虚假遗忘和真实遗忘:性能下降不一定意味着知识丢失,先尝试 re-alignment 验证
  3. 遗忘是选择性的:低频知识和非目标语言最脆弱
  4. 回放是最有效的防控手段:经验值 20-40%,但 Scaling Laws 可以针对具体场景精确计算最优比例
  5. 学习率控制是第二道防线:中训练 lr 应为预训练峰值的 1/5 到 1/3
  6. 权重平均是零成本的补救方案:能恢复 50-80% 的遗忘;逐层自适应合并(RECALL)效果更优
  7. LoRA 的低秩约束天然缓解遗忘:正交投影(OPLoRA)提供更严格的理论保证
  8. 实时监控不可或缺:发现越早,修复成本越低
  9. 数据配比可以在训练中途调整——不需要完全重来
  10. 分别监控不同语言和不同能力维度的遗忘

🛠 动手环节

练习 1:遗忘监控 Dashboard

设计一个遗忘监控 dashboard 的 schema:

  • 定义需要监控的 5-8 个维度
  • 设定每个维度的告警阈值
  • 设计监控频率和上报策略

练习 2:权重平均实验

如果你有一个微调前和微调后的模型检查点:

  • 在不同的 alpha 值(0.3, 0.5, 0.7, 0.9)上做权重平均
  • 在 2-3 个 benchmark 上评估每个 alpha 值的效果
  • 画出”alpha vs 新能力分数”和”alpha vs 旧能力分数”的帕累托前沿

练习 3:回放策略设计

假设你要在一个通用模型上做法律领域中训练:

  • 法律数据:50B token
  • 总训练预算:150B token
  • 回放数据来源:预训练数据的高质量子集(可用 500B token)

设计完整的数据配比方案,包括:回放比例、各领域分布、中文/英文比例、评估方案。