第 9 章 第二部分:预训练数据工程

课程化与退火策略

课程学习(Curriculum Learning)原理(从易到难)、退火(Annealing)策略设计(LR线性衰减/高质量精选)、退火数据组成、工业级实践案例、训练阶段切换时序

课程学习 退火策略 Annealing 工业实践 阶段切换

预训练的最后 5% 数据,决定了模型 20% 的最终能力。

退火是预训练的”压轴戏”——低学习率 + 高质量精选 = 模型能力的最后跃升。


9.1 课程学习原理

9.1.1 什么是课程学习

课程学习(Curriculum Learning, Bengio et al., 2009)的核心思想:模型像人一样,应该从易到难学习。

直觉上:

  • 学数学先学加减,再学乘除,最后学代数
  • 学语言先学日常对话,再学复杂论证
  • 训练大模型也应该先学简单模式,再学复杂推理

9.1.2 为什么课程学习有效

  1. 梯度方向更稳定:早期数据简单,模型梯度方向稳定,收敛快
  2. 避免被难数据”震飞”:如果一开始就喂复杂数学,模型容易陷入局部最优
  3. 逐步建立抽象:简单模式是复杂模式的基础,先建立基础再上层建筑

9.1.3 难度评估

如何判断数据的”难度”?三种主流方法:

class DifficultyEstimator:
    """数据难度评估"""
    
    def __init__(self):
        # 用一个 reference 模型计算 PPL
        self.ref_model = load_reference_model()
    
    def estimate_by_ppl(self, text):
        """
        方法1: 用参考模型计算 PPL
        PPL 高 = 难,PPL 低 = 易
        """
        ppl = compute_perplexity(self.ref_model, text)
        # 归一化到 1-5 分
        if ppl < 3.0:
            return 1
        elif ppl < 4.0:
            return 2
        elif ppl < 5.0:
            return 3
        elif ppl < 6.0:
            return 4
        else:
            return 5
    
    def estimate_by_features(self, text):
        """
        方法2: 基于文本特征
        - 句子长度
        - 词汇复杂度(长词比例)
        - 数学公式数量
        - 代码行数
        """
        sentences = text.split('.')
        avg_sentence_len = np.mean([len(s.split()) for s in sentences])
        
        words = text.split()
        long_words = sum(1 for w in words if len(w) > 8)
        long_word_ratio = long_words / max(len(words), 1)
        
        formula_count = text.count('$')
        code_lines = text.count('```')
        
        # 综合评分
        score = 0
        if avg_sentence_len > 25: score += 1
        if long_word_ratio > 0.15: score += 1
        if formula_count > 10: score += 1
        if code_lines > 3: score += 1
        if len(text) > 5000: score += 1
        
        return min(score + 1, 5)
    
    def estimate_by_fineWeb_edu(self, text):
        """
        方法3: 用 FineWeb-Edu 教育价值评分
        教育价值 1-2 = 容易(基础内容)
        教育价值 3 = 中等
        教育价值 4-5 = 难(高密度知识)
        """
        return fineweb_edu_classify(text)

EACL 2026 发现的最有效难度信号(Zhang et al., 2026, EACL 2026 Main):系统研究 200+ 模型在 100B tokens 上的课程学习效果,识别出三种比 FineWeb-Edu 更有效的难度信号:

  1. 压缩比(Compression Ratio):文本经 gzip 压缩后的比率,压缩比越低=冗余度越高=越容易
  2. 词汇多样性 MTLD(Measure of Textual Lexical Diversity):衡量文本词汇丰富度,MTLD 越高=词汇越多样=越难
  3. 可读性 Flesch Reading Ease:基于句子长度和音节数计算,得分越低=文本越难

EACL 2026 还发现课程学习可加速收敛 18-45%,作为 warmup 后切换随机采样可获得持续 +3.5% 提升——暗示”课程学习 + 随机混合”的组合策略可能优于纯课程学习。

难度信号的”偏好侧”扩展(PDPC):上述难度信号(PPL/文本特征/FineWeb-Edu)都来自”文本侧”,Preference Curriculum(PDPC, arXiv:2501.13126) 提出用偏好数据中的”预测难度(prediction difficulty, PD)“作为课程难度信号,配合 S 形(sigmoid)调度,在 3B 规模平均提升 +4.1%。这把”难度信号来源”从文本统计扩展到偏好信号。

课程学习的组合协调(CGLS, ICML 2026):课程学习通常与其它训练加速策略(如渐进层堆叠 progressive layer stacking)各自独立使用,CGLS(arXiv:2506.11389)将二者在同一训练流程中联合调度,探索”课程 × 层堆叠”的协调范式——这代表课程学习从”单点策略”走向”组合策略”的延伸方向。

9.1.4 课程学习的实验对比

Lee et al. (2024) 的实验(1B 模型):

训练策略数据顺序MMLU
随机全程随机混合24.5
从难到易5→4→3→2→122.1(崩塌)
从易到难1→2→3→4→526.8

从难到易反而比随机差——印证了”先易后难”的合理性。

9.2 退火(Annealing)策略设计

9.2.1 什么是退火

退火源自金属学——缓慢冷却使晶体结构更稳定。在大模型训练中:

Pretrain 主训练:  LR=3e-4, 数据=混合, tokens=10T
         ↓
退火阶段:         LR=3e-5→0, 数据=精选, tokens=200B

9.2.2 退火的核心要素

# 退火的核心配置
ANNEALING_CONFIG = {
    # 学习率配置
    'lr_start': 3e-5,           # 起始学习率(比主训练低10倍)
    'lr_end': 1e-7,              # ⚠️ 传统推荐=接近0,但 ICLR 2026 发现这会浪费高质量数据→见 §9.2.3 策略4 CMA
    'schedule': 'linear_decay',  # 或 'cosine_decay'
    'warmup_steps': 100,        # 短warmup
    
    # 数据配置
    'data_quality': 'high',      # 高质量精选
    'data_size': 200_000_000_000,# 200B tokens
    'mix': {
        'fineweb_edu_4plus': 0.50,
        'arxiv_math': 0.20,
        'github_clean': 0.15,
        'stackexchange_top': 0.10,
        'synthetic_reasoning': 0.05,
    },
    
    # 训练配置
    'batch_size': 'same_as_pretrain',  # 保持batch size
    'gradient_accumulation': 'same',
    'weight_decay': 0.1,                # 略微提高正则化
}

9.2.3 四种退火策略

⚠️ LR 衰减的隐患(ICLR 2026, Luo et al., arXiv:2511.18903):课程学习在恒定 LR 下显著优于随机打乱,但在标准 LR 衰减(WSD/Cosine)下优势几乎消失。原因是 LR 衰减到低值时模型更新步长过小,无法充分学习最后阶段的高质量数据——“LR 衰减浪费了最好的数据”。解决方案见下文策略 4(CMA)和 CDMA。

策略1:纯线性衰减

def linear_decay_lr(step, total_steps, lr_start, lr_end):
    """线性衰减"""
    return lr_start - (lr_start - lr_end) * (step / total_steps)

最简单,效果中等。适合算力紧张。

策略2:余弦衰减

import math

def cosine_decay_lr(step, total_steps, lr_start, lr_end):
    """余弦衰减"""
    progress = step / total_steps
    cosine_factor = 0.5 * (1 + math.cos(math.pi * progress))
    return lr_end + (lr_start - lr_end) * cosine_factor

更平滑,效果略好于线性。

策略3:阶梯式衰减

def step_decay_lr(step, total_steps, stages):
    """
    阶梯式衰减
    stages: [(end_step1, lr1), (end_step2, lr2), ...]
    """
    for end_step, lr in stages:
        if step < end_step:
            return lr
    return stages[-1][1]  # 最后一个stage的lr

# 示例
stages = [
    (50_000_000_000, 3e-5),   # 前50B: 3e-5
    (120_000_000_000, 1e-5),  # 50-120B: 1e-5
    (170_000_000_000, 3e-6),  # 120-170B: 3e-6
    (200_000_000_000, 1e-6),  # 170-200B: 1e-6
]

最复杂但效果最好。Llama3 用的是这种。

策略4:CMA(Controlled Moderate Annealing)

def cma_lr(step, total_steps, lr_peak, final_ratio=0.2):
    """
    CMA (Controlled Moderate Annealing)
    最终 LR 保持在峰值 LR 的 1/5~1/3,而非衰减到 0
    """
    lr_end = lr_peak * final_ratio
    progress = step / total_steps
    # Cosine 衰减到 final_ratio * lr_peak,而非 0
    cosine_factor = 0.5 * (1 + math.cos(math.pi * progress))
    return lr_end + (lr_peak - lr_end) * cosine_factor

# 示例
# lr_peak=3e-5, final_ratio=0.2 → lr_end=6e-6
# 而非传统的 lr_end=1e-7(几乎为 0)

关键思想:将”步长控制”与”噪声降低”解耦。CMA 保持中等 LR 以确保高质量数据阶段有足够的学习步长,噪声降低则通过 CDMA(Checkpoint-Decoupled Model Averaging) 实现——对最后 N 个 checkpoint 做加权平均(如 EMA),在不牺牲步长的情况下降低噪声。

ICLR 2026 在 1.5B/30B tokens 实验上,CMA+CDMA 组合比传统 LR 衰减到 0 的方案提升 1.64%。这挑战了章节之前推荐的 lr_end=1e-7 策略——“LR 逼近 0”的衰减恰恰浪费了退火阶段精心准备的高质量数据。

9.3 退火数据组成

退火数据是预训练的”压轴戏”,每条 token 都要”值得”。

9.3.1 推荐组成

# 退火阶段数据组成(500B tokens 为例)
annealing_data:
  
  # 主力1: 高质量教育内容(50%)
  fineweb_edu_4plus:
    weight: 0.50
    tokens: 250B
    source: 'FineWeb-Edu ≥4 分精选'
    description: '高中-大学级深度内容,结构化、信息密集'
  
  # 主力2: 学术论文与数学(20%)
  arxiv_math:
    weight: 0.15
    tokens: 75B
    source: 'arXiv 数学/物理/CS 论文'
    description: 'LaTeX 源码,公式完整还原'
  
  openwebmath:
    weight: 0.05
    tokens: 25B
    source: 'OpenWebMath'
    description: '数学网页精选'
  
  # 主力3: 代码(15%)
  github_clean:
    weight: 0.10
    tokens: 50B
    source: '高 star GitHub 仓库'
    description: '至少 10 star,过滤测试和文档'
  
  the_stack:
    weight: 0.05
    tokens: 25B
    source: 'The Stack v2'
    description: 'BigCode 项目代码精选'
  
  # 主力4: 高质量问答(10%)
  stackexchange_top:
    weight: 0.07
    tokens: 35B
    source: 'StackExchange 高赞回答'
    description: '投票 ≥10 的高质量问答'
  
  # 主力5: 合成推理链(5%)
  synthetic_reasoning:
    weight: 0.03
    tokens: 15B
    source: 'GSM8K/MATH 合成推理链'
    description: '用 GPT-4 生成的 step-by-step 推理'
  
  orca_reasoning:
    weight: 0.02
    tokens: 10B
    source: 'Orca 风格推理数据'
    description: '复杂推理任务+解释'
  
  # 主力6: 少量 SFT 风味(5%)
  sft_like_data:
    weight: 0.03
    tokens: 15B
    source: '指令风格数据'
    description: '为后训练"破冰",让模型熟悉指令格式'

9.3.2 为什么需要 SFT 风味

退火阶段加少量 SFT 风味数据(5%)的原因:

  1. 为后训练破冰:让模型提前”感受”指令格式,减少 SFT 时的分布偏移
  2. 提升指令遵循能力:少量指令数据让模型学会”听指挥”
  3. 不破坏生成能力:5% 比例足够小,不会让模型只学会指令跟随
# SFT 风味数据示例
SFT_LIKE_TEMPLATES = [
    # 数学推理
    {
        'prompt': 'What is 15 * 24? Think step by step.',
        'response': 'Let me calculate 15 * 24 step by step.\n15 * 20 = 300\n15 * 4 = 60\n300 + 60 = 360\nSo 15 * 24 = 360.'
    },
    
    # 知识问答
    {
        'prompt': 'Explain how photosynthesis works.',
        'response': 'Photosynthesis is the process by which plants convert light energy into chemical energy...'
    },
    
    # 代码解释
    {
        'prompt': 'What does this code do?\n```python\ndef fib(n): return n if n<2 else fib(n-1)+fib(n-2)```',
        'response': 'This is a recursive implementation of the Fibonacci sequence...'
    }
]

9.3.3 合成推理链

退火阶段推荐加 5% 合成推理链,特别提升数学/推理能力:

# 合成推理链示例(用 GPT-4 生成)
SYNTHETIC_REASONING_EXAMPLES = [
    {
        'task': 'Solve: A train travels 60 km/h for 2 hours, then 80 km/h for 1 hour. Total distance?',
        'reasoning': '''
Step 1: Calculate distance for first part
60 km/h × 2 hours = 120 km

Step 2: Calculate distance for second part  
80 km/h × 1 hour = 80 km

Step 3: Add distances
120 + 80 = 200 km

Answer: 200 km
''',
        'answer': '200 km'
    }
]

生成流程:

def generate_synthetic_reasoning(num_samples=10000):
    """用 GPT-4 生成合成推理链"""
    from openai import OpenAI
    client = OpenAI()
    
    examples = []
    
    # 用 GSM8K 题目作为种子
    gsm8k_problems = load_gsm8k_train()
    
    for problem in gsm8k_problems[:num_samples]:
        response = client.chat.completions.create(
            model='gpt-4',
            messages=[
                {'role': 'system', 'content': 'You are a math tutor. Solve the problem step by step with clear reasoning.'},
                {'role': 'user', 'content': problem['question']}
            ],
            temperature=0.7  # 适度多样性
        )
        
        examples.append({
            'question': problem['question'],
            'reasoning': response.choices[0].message.content,
            'answer': problem['answer']
        })
    
    return examples

9.4 工业级实践案例

9.4.1 Llama3 退火实践

Llama3(Meta, 2024)的退火实践:

阶段tokensLR数据
Pretrain 主训练15T3e-4 → 3e-5混合
退火阶段200B3e-5 → 0精选(同主训练但质量更高)

Llama3 的关键发现:退火数据用”主训练数据中质量最高的子集”,而不是全新数据。这避免了分布偏移。

9.4.2 DeepSeek-V3 退火实践

DeepSeek-V3 的退火更激进:

阶段tokensLR数据
Pretrain 主训练14T3e-4 → 3e-5数学/代码占比高
退火阶段500B3e-5 → 1e-6数学 30%+代码 30%+推理 20%
长文训练100B1e-5长文档拼接

DeepSeek-V3 的关键发现:退火阶段数学/代码配比拉满,效果显著——MATH 从 35 跃升到 61.6。

9.4.3 Qwen2.5 退火实践

Qwen2.5(阿里, 2024)的退火策略:

# Qwen2.5 退火配方
qwen25_annealing:
  total_tokens: 300B
  
  # 学习率:用阶梯式衰减
  lr_schedule:
    - {tokens: 100B, lr: 3e-5}
    - {tokens: 100B, lr: 1e-5}
    - {tokens: 70B,  lr: 3e-6}
    - {tokens: 30B,  lr: 1e-6}
  
  # 数据:精选+合成
  mix:
    fineweb_edu_4plus: 0.40
    arxiv_math: 0.20
    synthetic_reasoning: 0.15  # 比例较高
    github_clean: 0.15
    wiki: 0.05
    sft_like: 0.05

9.4.4 YuLan-Mini:小规模开源模型的完整课程+退火配方

上述三个案例都是大厂大模型,YuLan-Mini(人大高瓴,arXiv:2412.17743)则提供了一个小规模开源模型的完整落地配方,作为”课程 + 退火”在小体量模型上的对照样本:

  • 27 阶段课程学习:将预训练切分为 27 个难度递进的课程阶段(远多于前述大厂的 3-8 阶段),在较小算力下精细调度数据难度
  • 80B tokens 退火:退火阶段规模与模型体量匹配,而非简单套用大厂的 200-500B
  • RoPE 基频提升:在课程后期配合 RoPE 基频调整,扩展长程位置建模能力

YuLan-Mini 的价值在于说明”课程 + 退火”并非大厂专属——小体量模型同样可以用更细粒度的课程切分 + 匹配体量的退火规模 + 位置编码调整,拼出完整的能力曲线。

9.4.5 三家对比

维度Llama3DeepSeek-V3Qwen2.5
退火 tokens200B500B300B
学习率衰减阶梯线性阶梯
数学配比5%30%20%
合成推理0%5%15%
SFT 风味0%0%5%

核心规律:

  1. 退火 tokens 占总训练的 1-5%
  2. 学习率降到主训练的 1/10 到 0
  3. 数据全部用高质量精选
  4. 数学/代码配比拉满(推理能力提升关键)

9.5 训练阶段切换时序

9.5.1 完整预训练时序

时间轴 →

Pretrain 主训练 (10T tokens, 3-6 个月)
   ↓
Midtrain 二阶段 (1-3T tokens, 2-4 周)
   ↓
退火阶段 (200-500B tokens, 3-7 天)
   ↓
长文训练 (50-200B tokens, 2-5 天)
   ↓
进入后训练 (SFT/DPO/RL)

9.5.2 阶段切换的”平滑过渡”

class PhaseTransitionScheduler:
    """阶段切换调度器"""
    
    def __init__(self):
        self.transitions = []
    
    def add_transition(self, from_phase, to_phase, transition_tokens=10_000_000_000):
        """
        添加一个阶段切换
        transition_tokens: 过渡期 token 数(默认 10B)
        """
        self.transitions.append({
            'from': from_phase,
            'to': to_phase,
            'transition_tokens': transition_tokens
        })
    
    def get_lr_during_transition(self, step, transition_steps):
        """
        过渡期学习率:线性插值
        """
        from_lr = self.transitions[0]['from']['lr']
        to_lr = self.transitions[0]['to']['lr']
        
        # 线性插值
        progress = step / transition_steps
        return from_lr + (to_lr - from_lr) * progress
    
    def get_mix_during_transition(self, step, transition_steps):
        """
        过渡期数据混合:渐进切换
        """
        from_mix = self.transitions[0]['from']['mix']
        to_mix = self.transitions[0]['to']['mix']
        
        progress = step / transition_steps
        # 渐进切换:从 from_mix 渐变到 to_mix
        blended_mix = {}
        for key in set(from_mix.keys()) | set(to_mix.keys()):
            from_w = from_mix.get(key, 0)
            to_w = to_mix.get(key, 0)
            blended_mix[key] = from_w * (1 - progress) + to_w * progress
        
        # 归一化
        total = sum(blended_mix.values())
        return {k: v/total for k, v in blended_mix.items()}

9.5.3 切换的踩坑

坑1:学习率突然降10倍

某团队从 Pretrain(LR=3e-4)切到退火(LR=3e-5)时直接切换,loss 突然跳变 0.3,模型陷入次优解。

解决:用 10B tokens 做线性插值过渡。

坑2:数据突然切换

某团队从混合数据切到精选数据时直接切换,模型在切换后出现”知识遗忘”——之前学的能力丢失了。

解决:渐进切换混合比例(保留 20% 主训练数据 + 80% 退火数据)。

坑3:阶段切换太频繁

某团队设计了 8 个阶段(每 200B tokens 切换一次),结果每次切换都损失一些能力,最终效果不如 3 阶段。

解决:阶段数控制在 3-5 个,每个阶段至少 200B tokens。

9.6 课程化的工业实现

9.6.1 难度排序的数据准备

def prepare_curriculum_data(documents):
    """
    准备课程化数据
    1. 给每条数据打难度标签
    2. 按难度分桶
    3. 准备不同阶段的数据混合
    """
    # Step 1: 难度评估
    for doc in documents:
        doc['difficulty'] = estimate_difficulty(doc['text'])
    
    # Step 2: 按难度分桶
    buckets = {1: [], 2: [], 3: [], 4: [], 5: []}
    for doc in documents:
        buckets[doc['difficulty']].append(doc)
    
    # Step 3: 准备各阶段混合
    curriculum = {
        'phase1_easy': {
            1: 0.50, 2: 0.30, 3: 0.15, 4: 0.04, 5: 0.01
        },
        'phase2_medium': {
            1: 0.20, 2: 0.30, 3: 0.30, 4: 0.15, 5: 0.05
        },
        'phase3_hard': {
            1: 0.05, 2: 0.15, 3: 0.30, 4: 0.30, 5: 0.20
        },
        'phase4_annealing': {
            1: 0.00, 2: 0.05, 3: 0.20, 4: 0.40, 5: 0.35
        }
    }
    
    return buckets, curriculum

9.6.2 课程调度器

class CurriculumScheduler:
    """课程学习调度器"""
    
    def __init__(self, curriculum, total_tokens):
        self.curriculum = curriculum
        self.total_tokens = total_tokens
        self.current_phase = 'phase1_easy'
        self.phase_progress = 0
    
    def get_phase_for_step(self, step):
        """根据当前训练步数确定阶段"""
        # 假设4个阶段,每阶段占 25%/35%/30%/10% 的训练量
        phase_allocations = [
            ('phase1_easy', 0.25),
            ('phase2_medium', 0.35),
            ('phase3_hard', 0.30),
            ('phase4_annealing', 0.10)
        ]
        
        cumulative = 0
        for phase, alloc in phase_allocations:
            cumulative += alloc
            if step < cumulative * self.total_tokens:
                return phase
        
        return 'phase4_annealing'
    
    def sample_batch(self, buckets, batch_size):
        """按当前阶段的配比采样一个 batch"""
        mix = self.curriculum[self.current_phase]
        
        batch = []
        for difficulty, weight in mix.items():
            n_samples = int(batch_size * weight)
            samples = random.sample(buckets[difficulty], min(n_samples, len(buckets[difficulty])))
            batch.extend(samples)
        
        return batch[:batch_size]

9.7 退火效果监控

9.7.1 关键指标

class AnnealingMonitor:
    """退火效果监控"""
    
    def __init__(self):
        self.metrics_history = []
    
    def log_metrics(self, step, model, eval_loader):
        """记录退火过程中的关键指标"""
        metrics = {
            'step': step,
            'train_loss': self._compute_train_loss(model),
            'eval_loss': self._compute_eval_loss(model, eval_loader),
            'mmlu': eval_mmlu(model),           # 通用能力
            'gsm8k': eval_gsm8k(model),         # 数学能力
            'human_eval': eval_human_eval(model),  # 代码能力
            'mt_bench': eval_mt_bench(model),   # 对话能力
        }
        
        self.metrics_history.append(metrics)
        
        # 检查异常
        self._check_regression(metrics)
        
        return metrics
    
    def _check_regression(self, current):
        """检查能力倒退"""
        if len(self.metrics_history) < 2:
            return
        
        prev = self.metrics_history[-1]
        
        # 如果任何能力下降超过 2 分,告警
        for metric in ['mmlu', 'gsm8k', 'human_eval', 'mt_bench']:
            if prev[metric] - current[metric] > 2:
                print(f'⚠️ {metric} regression: {prev[metric]} → {current[metric]}')

9.7.2 退火效果实证

某 7B 模型的退火前后对比:

指标退火前退火后提升
MMLU48.252.8+4.6
GSM8K12.322.1+9.8
HumanEval28.535.2+6.7
MT-Bench5.86.2+0.4

退火对推理类任务(GSM8K)提升最大,对闲聊类(MT-Bench)提升最小。

9.8 退火的踩坑经验

坑1:退火数据与主训练重复

某团队退火用的是主训练数据的子集,结果退火几乎无效果。退火数据必须独立精选,质量比主训练高一个级别。

坑2:退火 tokens 过多

某团队退火用了 2T tokens(占总训练 20%),结果模型出现过拟合,泛化能力下降。退火 tokens 应该占总训练的 1-5%。

坑3:退火学习率太高

某团队退火学习率 = 主训练的 1/3,结果退火过程中 loss 波动剧烈。退火学习率应为主训练的 1/10 到 1/100。

坑4:未做能力倒退监控

某团队退火后发现数学能力提升,但代码能力下降 5 分。退火过程中必须监控所有能力维度,及时调整配比。

坑5:合成推理数据质量差

某团队用 GPT-3.5 生成合成推理链,结果推理链有错误,模型学到了错误推理。合成数据必须用 GPT-4/Claude 等顶级模型生成,且要做质量验证。

9.9 推荐退火配方模板

9.9.1 通用模型退火

# 通用模型退火(适合 Llama/Qwen 类)
general_annealing:
  total_tokens: 200B  # 主训练的 2%
  lr_schedule: linear_decay
  lr_start: 3e-5
  lr_end: 1e-7
  
  mix:
    fineweb_edu_4plus: 0.50  # 100B
    arxiv_math: 0.20         # 40B
    github_clean: 0.15       # 30B
    stackexchange_top: 0.10  # 20B
    synthetic_reasoning: 0.05 # 10B

9.9.2 数学模型退火

# 数学模型退火(适合 DeepSeek-Math 类)
math_annealing:
  total_tokens: 100B
  mix:
    arxiv_math: 0.40         # 数学论文 40B
    openwebmath: 0.20        # 数学网页 20B
    synthetic_reasoning: 0.25 # 合成推理 25B(高比例)
    fineweb_edu_4plus: 0.10
    gsm8k_augmented: 0.05    # GSM8K 增强

9.9.3 代码模型退火

# 代码模型退火(适合 Code Llama 类)
code_annealing:
  total_tokens: 100B
  mix:
    github_clean: 0.50       # 高 star 代码 50B
    the_stack_v2: 0.20       # BigCode 数据 20B
    stackexchange: 0.15       # 编程问答 15B
    fineweb_edu_4plus: 0.10
    synthetic_code: 0.05     # 合成代码(如 LeetCode 题解)

9.10 本课总结

  • 课程学习:从易到难训练,比随机混合提升 2-3 分
  • 退火:低学习率 + 高质量精选,最后 1-5% 数据带来 20% 能力提升
  • 退火数据组成:FineWeb-Edu≥4分50% + 数学20% + 代码15% + 问答10% + 合成5%
  • 三种退火策略:线性衰减(简单)/ 余弦(平滑)/ 阶梯式(最佳)
  • 阶段切换:用 10B tokens 做平滑过渡,避免学习率/数据突变
  • 工业实践:Llama3/DeepSeek-V3/Qwen2.5 都用退火,tokens 占总训练 1-5%

课后练习

  1. 实现一个难度评估器,给你的数据集打 1-5 分难度
  2. 设计一个 4 阶段课程学习方案,并配置各阶段的配比
  3. 实现退火学习率调度器(线性 + 余弦 + 阶梯三种)
  4. 思考:你的模型最需要补强的能力是什么?退火数据配比应该怎么调?

下一章我们将进入评估、归因与闭环——用数据探针、消融实验、训练数据归因(TDA)和 Scaling Law 拟合,建立数据工程的反馈闭环。