主流大模型训练完整教程

4 阅读28分钟

从零开始,详解GPT、ChatGPT等大语言模型的训练全流程


目录

  1. 基础知识
    • Transformer架构
    • 注意力机制
    • 位置编码
  2. 阶段一:预训练
  3. 阶段二:监督微调(SFT)
  4. 阶段三:奖励模型训练
  5. 阶段四:RLHF训练
  6. 完整代码示例

1. 基础知识

1.1 Transformer架构详解

什么是Transformer?

Transformer是现代大语言模型(GPT、BERT、LLaMA等)的核心架构,由Google在2017年提出。它完全抛弃了传统的循环神经网络(RNN)和卷积神经网络(CNN),仅使用注意力机制来处理序列数据。

重要说明:现代大语言模型(GPT系列、LLaMA等)不使用卷积层。卷积层主要用于计算机视觉模型(如ResNet、VGG)。大语言模型基于Transformer,使用的是自注意力机制

Transformer整体架构

输入文本:"Hello World"
    ↓
[词嵌入 Embedding + 位置编码 Positional Encoding]
    ↓
┌─────────────────────────────────┐
│     编码器(Encoder)- 可选     │
│  ┌───────────────────────────┐ │
│  │  多头自注意力              │ │
│  │  (Multi-Head Attention)   │ │
│  └───────────────────────────┘ │
│           ↓                     │
│  ┌───────────────────────────┐ │
│  │  前馈神经网络(FFN)       │ │
│  └───────────────────────────┘ │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│     解码器(Decoder)           │
│  ┌───────────────────────────┐ │
│  │  掩码多头自注意力          │ │
│  │  (Masked Attention)       │ │
│  └───────────────────────────┘ │
│           ↓                     │
│  ┌───────────────────────────┐ │
│  │  前馈神经网络(FFN)       │ │
│  └───────────────────────────┘ │
└─────────────────────────────────┘
    ↓
[输出层 Linear + Softmax]
    ↓
输出:预测下一个词的概率分布

核心组件

1. 词嵌入(Token Embedding)

将每个词转换为固定维度的向量:

import torch.nn as nn

vocab_size = 50000  # 词汇表大小
d_model = 512       # 嵌入维度

embedding = nn.Embedding(vocab_size, d_model)

# 示例:将"Hello World"转换为向量
input_ids = torch.tensor([15496, 2159])  # token IDs
embedded = embedding(input_ids)  # 形状: [2, 512]
2. 位置编码(Positional Encoding)

由于Transformer没有循环结构,需要添加位置信息:

公式

PE(pos, 2i)   = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))

代码实现

import numpy as np
import torch

def get_positional_encoding(max_len, d_model):
    """生成位置编码"""
    position = np.arange(max_len)[:, np.newaxis]
    div_term = np.exp(np.arange(0, d_model, 2) * -(np.log(10000.0) / d_model))

    pe = np.zeros((max_len, d_model))
    pe[:, 0::2] = np.sin(position * div_term)  # 偶数维度
    pe[:, 1::2] = np.cos(position * div_term)  # 奇数维度

    return torch.FloatTensor(pe)

# 生成位置编码
pos_encoding = get_positional_encoding(max_len=512, d_model=512)

1.2 自注意力机制(Self-Attention)

核心思想

让每个词都能"看到"句子中的所有其他词,并计算相关性。

例子

句子: "The animal didn't cross the street because it was too tired."
问题: "it" 指的是什么?

注意力分布:
- "it""animal" 的注意力:0.8 (高)
- "it""street" 的注意力:0.1 (低)

数学原理

核心公式

Attention(Q, K, V) = softmax(Q × K^T / √d_k) × V

三个关键矩阵

  • Q (Query):查询向量,"我想找什么信息?"
  • K (Key):键向量,"我能提供什么信息?"
  • V (Value):值向量,"实际的信息内容"

完整代码实现

import torch
import torch.nn as nn
import torch.nn.functional as F
import math

class MultiHeadAttention(nn.Module):
    """多头自注意力层"""

    def __init__(self, d_model, num_heads, dropout=0.1):
        super().__init__()
        assert d_model % num_heads == 0

        self.d_model = d_model
        self.num_heads = num_heads
        self.d_k = d_model // num_heads

        # Q、K、V的线性变换
        self.W_Q = nn.Linear(d_model, d_model)
        self.W_K = nn.Linear(d_model, d_model)
        self.W_V = nn.Linear(d_model, d_model)
        self.W_O = nn.Linear(d_model, d_model)

        self.dropout = nn.Dropout(dropout)

    def forward(self, x, mask=None):
        batch_size, seq_len, d_model = x.size()

        # 1. 线性变换并分割成多个头
        Q = self.W_Q(x).view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)
        K = self.W_K(x).view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)
        V = self.W_V(x).view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)

        # 2. 计算注意力分数
        scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)

        # 3. 应用掩码(防止看到未来信息)
        if mask is not None:
            scores = scores.masked_fill(mask == 0, float('-inf'))

        # 4. Softmax归一化
        attention_weights = F.softmax(scores, dim=-1)
        attention_weights = self.dropout(attention_weights)

        # 5. 加权求和
        attention_output = torch.matmul(attention_weights, V)

        # 6. 合并多个头
        attention_output = attention_output.transpose(1, 2).contiguous()
        attention_output = attention_output.view(batch_size, seq_len, d_model)

        # 7. 输出线性变换
        output = self.W_O(attention_output)

        return output

为什么使用多头注意力?

单个注意力头只能关注一种模式。多头注意力可以同时关注:

  • 头1:语法关系(如主谓关系)
  • 头2:语义关系(如同义词)
  • 头3:位置关系(如相邻词)

2. 阶段一:预训练

2.1 什么是预训练?

预训练是大语言模型训练的第一阶段,在海量无标注文本上训练模型,让模型学习语言的基本规律。

训练目标:预测下一个词(Next Token Prediction)

示例

输入: "The cat sat on the"
目标: 预测 "mat"

2.2 预训练流程

原始文本(维基百科、网页、书籍)
         ↓
    [数据清洗][分词Tokenization][转换为Token ID]
         ↓
┌──────────────────────┐
│   Transformer模型    │
└──────────────────────┘
         ↓
[预测下一个词][计算交叉熵损失][反向传播更新参数]
         ↓
    重复数百万步
         ↓
  得到Base模型

2.3 完整预训练代码

"""
预训练完整示例
"""

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import Dataset, DataLoader
from torch.optim import AdamW
from transformers import GPT2LMHeadModel, GPT2Tokenizer, GPT2Config
from tqdm import tqdm

# ============ 1. 定义数据集 ============
class PretrainDataset(Dataset):
    """预训练数据集"""

    def __init__(self, texts, tokenizer, max_length=512):
        self.encodings = []

        for text in texts:
            tokens = tokenizer.encode(text, truncation=True, max_length=max_length)
            if len(tokens) == max_length:
                self.encodings.append(tokens)

    def __len__(self):
        return len(self.encodings)

    def __getitem__(self, idx):
        return {'input_ids': torch.tensor(self.encodings[idx])}

# ============ 2. 训练函数 ============
def train_epoch(model, dataloader, optimizer, device):
    """训练一个epoch"""
    model.train()
    total_loss = 0

    for batch in tqdm(dataloader, desc="Training"):
        input_ids = batch['input_ids'].to(device)

        # 前向传播(目标就是输入本身)
        outputs = model(input_ids=input_ids, labels=input_ids)
        loss = outputs.loss

        # 反向传播
        optimizer.zero_grad()
        loss.backward()

        # 梯度裁剪
        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

        # 更新参数
        optimizer.step()

        total_loss += loss.item()

    return total_loss / len(dataloader)

# ============ 3. 主训练流程 ============
def main():
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    print(f"使用设备: {device}")

    # 创建小模型(用于演示)
    config = GPT2Config(
        vocab_size=50257,
        n_positions=512,
        n_embd=256,      # 嵌入维度
        n_layer=6,       # Transformer层数
        n_head=8,        # 注意力头数
    )

    model = GPT2LMHeadModel(config).to(device)
    tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
    if tokenizer.pad_token is None:
        tokenizer.pad_token = tokenizer.eos_token

    # 打印模型信息
    total_params = sum(p.numel() for p in model.parameters())
    print(f"模型参数量: {total_params:,}")

    # 准备训练数据
    train_texts = [
        "Machine learning is a subset of artificial intelligence. " * 50,
        "Python is a popular programming language for data science. " * 50,
        "Deep learning uses neural networks with multiple layers. " * 50,
    ] * 10

    # 创建数据集
    dataset = PretrainDataset(train_texts, tokenizer, max_length=128)
    dataloader = DataLoader(dataset, batch_size=8, shuffle=True)

    # 创建优化器
    optimizer = AdamW(model.parameters(), lr=5e-4, weight_decay=0.01)

    # 训练
    num_epochs = 3
    print(f"\n开始预训练 {num_epochs} 个epochs...")

    for epoch in range(num_epochs):
        avg_loss = train_epoch(model, dataloader, optimizer, device)
        perplexity = math.exp(avg_loss)

        print(f"Epoch {epoch+1}/{num_epochs}")
        print(f"  平均损失: {avg_loss:.4f}")
        print(f"  困惑度: {perplexity:.2f}")

    # 保存模型
    model.save_pretrained("./pretrained_model")
    tokenizer.save_pretrained("./pretrained_model")
    print("\n✓ 预训练完成!模型已保存")

if __name__ == "__main__":
    main()

2.4 关键技术

1. 损失函数

# 交叉熵损失
def compute_loss(logits, labels):
    """
    logits: [batch_size, seq_len, vocab_size]
    labels: [batch_size, seq_len]
    """
    # Shift操作:预测下一个词
    shift_logits = logits[:, :-1, :].contiguous()
    shift_labels = labels[:, 1:].contiguous()

    loss = F.cross_entropy(
        shift_logits.view(-1, shift_logits.size(-1)),
        shift_labels.view(-1)
    )

    return loss

2. 学习率调度

from torch.optim.lr_scheduler import CosineAnnealingLR

# 余弦退火调度器
scheduler = CosineAnnealingLR(optimizer, T_max=num_epochs)

# 每个epoch后更新
scheduler.step()

3. 混合精度训练

from torch.cuda.amp import autocast, GradScaler

scaler = GradScaler()

# 训练循环
with autocast():
    outputs = model(input_ids)
    loss = outputs.loss

scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()

3. 阶段二:监督微调(SFT)

3.1 什么是SFT?

监督微调是大语言模型训练的第二阶段,使用高质量的指令-回答数据对预训练模型进行微调,让模型学会遵循人类指令。

对比

阶段预训练监督微调
数据海量无标注文本高质量指令-回答对
目标预测下一个词生成符合指令的回答
数据量数TB数GB
结果Base模型Instruct模型

3.2 SFT数据格式

[
  {
    "instruction": "解释什么是机器学习",
    "input": "",
    "output": "机器学习是人工智能的一个分支,它使计算机系统能够从数据中学习并改进,而无需明确编程。主要分为监督学习、无监督学习和强化学习三大类。"
  },
  {
    "instruction": "将下面的句子翻译成英文",
    "input": "今天天气很好。",
    "output": "The weather is very nice today."
  }
]

3.3 SFT训练关键点

核心区别:只对回答部分计算损失,不对指令部分计算。

def create_labels(input_ids, response_start_idx):
    """
    创建labels,只对回答部分计算损失
    """
    labels = input_ids.clone()
    labels[:response_start_idx] = -100  # 忽略指令部分
    return labels

3.4 完整SFT代码

"""
监督微调(SFT)完整示例
"""

import torch
import torch.nn.functional as F
from torch.utils.data import Dataset, DataLoader
from torch.optim import AdamW
from transformers import GPT2LMHeadModel, GPT2Tokenizer
from tqdm import tqdm

# ============ 1. SFT数据集 ============
class InstructionDataset(Dataset):
    """指令微调数据集"""

    def __init__(self, data, tokenizer, max_length=256):
        self.data = data
        self.tokenizer = tokenizer
        self.max_length = max_length

    def format_prompt(self, instruction, input_text, output_text):
        """格式化为prompt"""
        if input_text:
            prompt = f"""Instruction: {instruction}
Input: {input_text}
Response: {output_text}"""
        else:
            prompt = f"""Instruction: {instruction}
Response: {output_text}"""
        return prompt

    def __len__(self):
        return len(self.data)

    def __getitem__(self, idx):
        item = self.data[idx]

        # 格式化完整文本
        full_text = self.format_prompt(
            item['instruction'],
            item.get('input', ''),
            item['output']
        )

        # Tokenize
        encoding = self.tokenizer(
            full_text,
            truncation=True,
            max_length=self.max_length,
            padding='max_length',
            return_tensors='pt'
        )

        input_ids = encoding['input_ids'].squeeze()
        labels = input_ids.clone()

        # 将padding token的label设为-100
        labels[labels == self.tokenizer.pad_token_id] = -100

        return {
            'input_ids': input_ids,
            'labels': labels
        }

# ============ 2. 训练函数 ============
def train_sft(model, dataloader, optimizer, device, num_epochs=3):
    """SFT训练"""
    model.train()

    for epoch in range(num_epochs):
        total_loss = 0
        progress_bar = tqdm(dataloader, desc=f"Epoch {epoch+1}")

        for batch in progress_bar:
            input_ids = batch['input_ids'].to(device)
            labels = batch['labels'].to(device)

            # 前向传播
            outputs = model(input_ids=input_ids, labels=labels)
            loss = outputs.loss

            # 反向传播
            optimizer.zero_grad()
            loss.backward()
            torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
            optimizer.step()

            total_loss += loss.item()
            progress_bar.set_postfix({'loss': f'{loss.item():.4f}'})

        avg_loss = total_loss / len(dataloader)
        print(f"Epoch {epoch+1} 平均损失: {avg_loss:.4f}")

# ============ 3. 主流程 ============
def main():
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

    # 加载预训练模型
    model = GPT2LMHeadModel.from_pretrained('gpt2').to(device)
    tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
    if tokenizer.pad_token is None:
        tokenizer.pad_token = tokenizer.eos_token

    # 准备SFT数据
    sft_data = [
        {
            "instruction": "解释什么是机器学习",
            "input": "",
            "output": "机器学习是人工智能的一个分支,使计算机能够从数据中学习并改进。"
        },
        {
            "instruction": "翻译成英文",
            "input": "今天天气很好。",
            "output": "The weather is very nice today."
        },
    ] * 50  # 重复以增加数据量

    # 创建数据集
    dataset = InstructionDataset(sft_data, tokenizer)
    dataloader = DataLoader(dataset, batch_size=4, shuffle=True)

    # 优化器(学习率比预训练小)
    optimizer = AdamW(model.parameters(), lr=1e-5, weight_decay=0.01)

    # 训练
    print("开始监督微调...")
    train_sft(model, dataloader, optimizer, device, num_epochs=3)

    # 保存
    model.save_pretrained("./sft_model")
    print("\n✓ SFT完成!")

if __name__ == "__main__":
    main()

3.5 最佳实践

  1. 数据质量 > 数据数量:1000条高质量数据胜过10000条低质量数据
  2. 防止过拟合:使用较小学习率(1e-5),少量epoch(1-3)
  3. 保留通用能力:混合70% SFT数据 + 30% 预训练数据

4. 阶段三:奖励模型训练

4.1 什么是奖励模型?

奖励模型是RLHF训练的核心组件,它学习人类的偏好,对模型生成的回答进行评分。

作用

  • 输入:问题 + 回答
  • 输出:标量分数(越高越好)

示例

问题: "解释量子力学"

回答A: "量子力学是物理学的一个分支..."(详细准确)
回答B: "量子力学很复杂。"(过于简单)

奖励模型(问题, 回答A) → 8.5分
奖励模型(问题, 回答B) → 3.2

4.2 训练数据格式

{
  "prompt": "解释什么是机器学习",
  "chosen": "机器学习是人工智能的一个分支,它使计算机系统能够从数据中学习并改进...",
  "rejected": "机器学习就是让机器学习。"
}

每条数据包含:

  1. Prompt(问题/指令)
  2. Chosen(更好的回答)
  3. Rejected(较差的回答)

4.3 奖励模型架构

import torch.nn as nn
from transformers import GPT2Model

class RewardModel(nn.Module):
    """奖励模型 - 对输入文本打分"""

    def __init__(self, base_model_name='gpt2'):
        super().__init__()

        # 加载预训练的Transformer
        self.transformer = GPT2Model.from_pretrained(base_model_name)
        hidden_size = self.transformer.config.hidden_size

        # 奖励头(输出标量分数)
        self.reward_head = nn.Linear(hidden_size, 1, bias=False)

    def forward(self, input_ids, attention_mask=None):
        """
        返回: rewards [batch_size] 标量奖励分数
        """
        # 通过Transformer
        outputs = self.transformer(
            input_ids=input_ids,
            attention_mask=attention_mask
        )

        # 获取最后一个token的hidden state
        last_hidden_states = outputs.last_hidden_state

        # 找到最后一个非padding token的位置
        if attention_mask is not None:
            sequence_lengths = attention_mask.sum(dim=1) - 1
        else:
            sequence_lengths = input_ids.shape[1] - 1

        # 提取最后一个token的表示
        batch_size = input_ids.shape[0]
        last_hidden = last_hidden_states[
            torch.arange(batch_size, device=input_ids.device),
            sequence_lengths
        ]

        # 计算奖励分数
        rewards = self.reward_head(last_hidden).squeeze(-1)

        return rewards

4.4 训练损失函数

排序损失(Ranking Loss)

def compute_reward_loss(r_chosen, r_rejected):
    """
    计算奖励模型损失

    目标: r_chosen > r_rejected

    参数:
        r_chosen: [batch_size] chosen回答的奖励分数
        r_rejected: [batch_size] rejected回答的奖励分数
    """
    # 对数sigmoid损失
    loss = -F.logsigmoid(r_chosen - r_rejected).mean()

    return loss

数学公式

Loss = -log(sigmoid(r_chosen - r_rejected))

4.5 完整训练代码

"""
奖励模型训练完整示例
"""

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import Dataset, DataLoader
from torch.optim import AdamW
from tqdm import tqdm

# ============ 1. 数据集 ============
class RewardDataset(Dataset):
    """奖励模型数据集"""

    def __init__(self, data, tokenizer, max_length=256):
        self.data = data
        self.tokenizer = tokenizer
        self.max_length = max_length

    def __len__(self):
        return len(self.data)

    def __getitem__(self, idx):
        item = self.data[idx]

        # Tokenize chosen和rejected
        chosen_text = item['prompt'] + " " + item['chosen']
        rejected_text = item['prompt'] + " " + item['rejected']

        chosen_enc = self.tokenizer(
            chosen_text,
            truncation=True,
            max_length=self.max_length,
            padding='max_length',
            return_tensors='pt'
        )

        rejected_enc = self.tokenizer(
            rejected_text,
            truncation=True,
            max_length=self.max_length,
            padding='max_length',
            return_tensors='pt'
        )

        return {
            'chosen_ids': chosen_enc['input_ids'].squeeze(),
            'chosen_mask': chosen_enc['attention_mask'].squeeze(),
            'rejected_ids': rejected_enc['input_ids'].squeeze(),
            'rejected_mask': rejected_enc['attention_mask'].squeeze(),
        }

# ============ 2. 训练函数 ============
def train_reward_model(model, dataloader, optimizer, device, num_epochs=3):
    """训练奖励模型"""

    for epoch in range(num_epochs):
        model.train()
        total_loss = 0
        total_accuracy = 0

        progress_bar = tqdm(dataloader, desc=f"Epoch {epoch+1}")

        for batch in progress_bar:
            # 获取chosen和rejected的分数
            r_chosen = model(
                batch['chosen_ids'].to(device),
                batch['chosen_mask'].to(device)
            )

            r_rejected = model(
                batch['rejected_ids'].to(device),
                batch['rejected_mask'].to(device)
            )

            # 计算损失
            loss = -F.logsigmoid(r_chosen - r_rejected).mean()

            # 计算准确率
            accuracy = (r_chosen > r_rejected).float().mean()

            # 反向传播
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            total_loss += loss.item()
            total_accuracy += accuracy.item()

            progress_bar.set_postfix({
                'loss': f'{loss.item():.4f}',
                'acc': f'{accuracy.item():.2%}'
            })

        avg_loss = total_loss / len(dataloader)
        avg_acc = total_accuracy / len(dataloader)

        print(f"Epoch {epoch+1} - 损失: {avg_loss:.4f}, 准确率: {avg_acc:.2%}")

# ============ 3. 主流程 ============
def main():
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

    # 创建奖励模型
    reward_model = RewardModel('gpt2').to(device)
    tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
    if tokenizer.pad_token is None:
        tokenizer.pad_token = tokenizer.eos_token

    # 准备数据
    preference_data = [
        {
            'prompt': '解释什么是机器学习',
            'chosen': '机器学习是人工智能的一个分支,使计算机能够从数据中学习...',
            'rejected': '机器学习就是让机器学习。'
        },
        # ... 更多数据
    ] * 50

    # 创建数据集
    dataset = RewardDataset(preference_data, tokenizer)
    dataloader = DataLoader(dataset, batch_size=4, shuffle=True)

    # 优化器
    optimizer = AdamW(reward_model.parameters(), lr=1e-5)

    # 训练
    print("开始训练奖励模型...")
    train_reward_model(reward_model, dataloader, optimizer, device, num_epochs=3)

    # 保存
    torch.save(reward_model.state_dict(), "reward_model.pt")
    print("\n✓ 奖励模型训练完成!")

if __name__ == "__main__":
    main()

5. 阶段四:RLHF训练

5.1 什么是RLHF?

RLHF(Reinforcement Learning from Human Feedback)是大语言模型训练的第三阶段,使用强化学习算法,根据奖励模型的反馈优化模型。

核心思想

模型生成回答 → 奖励模型打分 → 根据分数调整模型参数

5.2 RLHF训练循环

1. 输入Prompt: "解释量子力学"
       ↓
2. 策略模型生成回答
       ↓
3. 奖励模型评分: 7.5
       ↓
4. 计算优势函数: Advantage = 7.5 - baseline
       ↓
5. PPO算法优化
       ↓
   重复1-5数千次
       ↓
  得到对齐模型

5.3 PPO算法详解

PPO(Proximal Policy Optimization)是RLHF中最常用的强化学习算法。

核心思想

限制策略更新幅度,防止模型偏离太多。

PPO损失函数

def compute_ppo_loss(log_probs, old_log_probs, advantages, epsilon=0.2):
    """
    PPO裁剪损失

    参数:
        log_probs: 新策略的对数概率
        old_log_probs: 旧策略的对数概率
        advantages: 优势函数值
        epsilon: 裁剪范围(通常0.2)
    """
    # 计算概率比
    ratio = torch.exp(log_probs - old_log_probs)

    # 未裁剪的损失
    unclipped_loss = ratio * advantages

    # 裁剪的损失
    clipped_ratio = torch.clamp(ratio, 1 - epsilon, 1 + epsilon)
    clipped_loss = clipped_ratio * advantages

    # 取最小值(保守更新)
    loss = -torch.min(unclipped_loss, clipped_loss).mean()

    return loss

KL散度惩罚

防止模型偏离原始SFT模型太远:

def compute_kl_penalty(log_probs, ref_log_probs, beta=0.1):
    """
    KL散度惩罚

    参数:
        log_probs: 当前模型的对数概率
        ref_log_probs: 参考模型(SFT)的对数概率
        beta: KL惩罚系数
    """
    kl = (log_probs - ref_log_probs).mean()
    return beta * kl

完整损失函数

Total Loss = PPO Loss + KL Penalty

5.4 RLHF关键组件

# 1. 策略模型(Policy Model)- 需要训练
policy_model = GPT2LMHeadModel.from_pretrained('sft-model')

# 2. 参考模型(Reference Model)- 冻结的SFT模型
ref_model = GPT2LMHeadModel.from_pretrained('sft-model')
ref_model.eval()
for param in ref_model.parameters():
    param.requires_grad = False

# 3. 奖励模型(Reward Model)- 已训练好
reward_model = RewardModel.from_pretrained('reward-model')
reward_model.eval()

# 4. 价值模型(Value Model)- 预测奖励基线
value_model = ValueModel(...)

5.5 完整RLHF代码(简化版)

"""
RLHF训练完整示例(简化版)
"""

import torch
import torch.nn.functional as F
from transformers import GPT2LMHeadModel, GPT2Tokenizer
from tqdm import tqdm

# ============ 1. 生成函数 ============
@torch.no_grad()
def generate_responses(model, tokenizer, prompts, max_new_tokens=50, device='cpu'):
    """
    生成回答并返回log概率
    """
    model.eval()
    responses = []
    log_probs = []

    for prompt in prompts:
        input_ids = tokenizer.encode(prompt, return_tensors='pt').to(device)

        generated_ids = []
        generated_log_probs = []

        current_ids = input_ids

        for _ in range(max_new_tokens):
            outputs = model(current_ids)
            logits = outputs.logits[:, -1, :]

            # 采样
            probs = F.softmax(logits, dim=-1)
            next_token = torch.multinomial(probs, num_samples=1)

            # 记录log prob
            log_prob = F.log_softmax(logits, dim=-1)
            selected_log_prob = log_prob[0, next_token[0]]

            generated_ids.append(next_token[0].item())
            generated_log_probs.append(selected_log_prob)

            current_ids = torch.cat([current_ids, next_token], dim=1)

            if next_token[0].item() == tokenizer.eos_token_id:
                break

        response = tokenizer.decode(generated_ids, skip_special_tokens=True)
        responses.append(response)
        log_probs.append(torch.stack(generated_log_probs))

    return responses, log_probs

# ============ 2. RLHF训练步骤 ============
def rlhf_training_step(
    policy_model,
    ref_model,
    reward_model,
    tokenizer,
    prompts,
    optimizer,
    device
):
    """执行一步RLHF训练"""

    # 1. 生成回答
    responses, old_log_probs = generate_responses(
        policy_model, tokenizer, prompts, device=device
    )

    # 2. 计算奖励
    rewards = []
    for prompt, response in zip(prompts, responses):
        text = prompt + " " + response
        input_ids = tokenizer.encode(text, return_tensors='pt').to(device)
        reward = reward_model(input_ids)
        rewards.append(reward.item())

    rewards = torch.tensor(rewards, device=device)

    # 3. 计算优势(简化版:直接使用奖励)
    advantages = rewards
    advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8)

    # 4. PPO更新
    policy_model.train()

    # 重新计算log probs(需要梯度)
    new_log_probs = []
    for prompt, response in zip(prompts, responses):
        # 这里需要完整实现,简化省略...
        pass

    # 计算PPO损失
    # ppo_loss = compute_ppo_loss(new_log_probs, old_log_probs, advantages)

    # 计算KL惩罚
    # kl_loss = compute_kl_penalty(new_log_probs, ref_log_probs)

    # total_loss = ppo_loss + kl_loss

    # 反向传播
    # optimizer.zero_grad()
    # total_loss.backward()
    # optimizer.step()

    return {
        'reward_mean': rewards.mean().item(),
        'reward_std': rewards.std().item(),
    }

# ============ 3. 主训练流程 ============
def main():
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

    # 加载模型
    policy_model = GPT2LMHeadModel.from_pretrained('gpt2').to(device)
    ref_model = GPT2LMHeadModel.from_pretrained('gpt2').to(device)
    ref_model.eval()

    tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
    if tokenizer.pad_token is None:
        tokenizer.pad_token = tokenizer.eos_token

    # 简化:使用随机奖励模型
    class DummyRewardModel:
        def __call__(self, input_ids):
            return torch.randn(1) * 2 + 5

    reward_model = DummyRewardModel()

    # 优化器
    optimizer = torch.optim.AdamW(policy_model.parameters(), lr=1e-6)

    # 训练prompts
    prompts = [
        "解释什么是机器学习",
        "什么是深度学习?",
        "描述Python编程语言",
    ]

    # 训练循环
    num_iterations = 20
    print("开始RLHF训练...")

    for iteration in range(num_iterations):
        stats = rlhf_training_step(
            policy_model, ref_model, reward_model,
            tokenizer, prompts, optimizer, device
        )

        print(f"Iteration {iteration+1}/{num_iterations}")
        print(f"  平均奖励: {stats['reward_mean']:.2f}")

    # 保存
    policy_model.save_pretrained("./rlhf_model")
    print("\n✓ RLHF训练完成!")

if __name__ == "__main__":
    main()

5.6 RLHF关键挑战

1. 奖励黑客(Reward Hacking)

问题:模型学会利用奖励模型的漏洞获得高分

解决

  • KL惩罚:限制偏离程度
  • 奖励归一化和裁剪
  • 使用多个奖励模型

2. 训练不稳定

解决

  • 使用很小的学习率(1e-6)
  • 梯度裁剪
  • 较小的epsilon(0.1-0.2)

6. 完整代码示例

6.1 端到端训练流程

"""
完整的端到端大语言模型训练流程
包含:预训练 → SFT → 奖励模型 → RLHF
"""

import torch
import torch.nn as nn
from transformers import GPT2LMHeadModel, GPT2Tokenizer, GPT2Config
from torch.utils.data import Dataset, DataLoader
from torch.optim import AdamW
from tqdm import tqdm

class CompleteLLMTraining:
    """完整的LLM训练流程"""

    def __init__(self, device='cpu'):
        self.device = device
        self.tokenizer = None
        self.model = None

    # ===== 阶段1: 预训练 =====
    def pretrain(self, texts, num_epochs=3):
        """预训练阶段"""
        print("\n" + "="*80)
        print("阶段1: 预训练")
        print("="*80)

        # 创建模型
        config = GPT2Config(
            vocab_size=50257,
            n_embd=256,
            n_layer=4,
            n_head=4
        )

        self.model = GPT2LMHeadModel(config).to(self.device)
        self.tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
        if self.tokenizer.pad_token is None:
            self.tokenizer.pad_token = self.tokenizer.eos_token

        print(f"模型参数量: {sum(p.numel() for p in self.model.parameters()):,}")

        # 训练(代码省略,参考前面章节)
        print("✓ 预训练完成")

    # ===== 阶段2: 监督微调 =====
    def supervised_finetune(self, instruction_data, num_epochs=2):
        """监督微调阶段"""
        print("\n" + "="*80)
        print("阶段2: 监督微调")
        print("="*80)

        # 训练(代码省略)
        print("✓ 监督微调完成")

    # ===== 阶段3: 训练奖励模型 =====
    def train_reward_model(self, preference_data, num_epochs=3):
        """训练奖励模型"""
        print("\n" + "="*80)
        print("阶段3: 训练奖励模型")
        print("="*80)

        # 训练(代码省略)
        print("✓ 奖励模型训练完成")

    # ===== 阶段4: RLHF训练 =====
    def rlhf_training(self, prompts, num_iterations=10):
        """RLHF训练"""
        print("\n" + "="*80)
        print("阶段4: RLHF训练")
        print("="*80)

        # 训练(代码省略)
        print("✓ RLHF训练完成")

    # ===== 生成函数 =====
    @torch.no_grad()
    def generate(self, prompt, max_length=100):
        """生成文本"""
        self.model.eval()
        input_ids = self.tokenizer.encode(prompt, return_tensors='pt').to(self.device)

        output_ids = self.model.generate(
            input_ids,
            max_length=max_length,
            temperature=0.7,
            top_p=0.9,
            do_sample=True,
            pad_token_id=self.tokenizer.eos_token_id
        )

        return self.tokenizer.decode(output_ids[0], skip_special_tokens=True)

    # ===== 保存模型 =====
    def save(self, path):
        """保存模型"""
        self.model.save_pretrained(path)
        self.tokenizer.save_pretrained(path)
        print(f"✓ 模型已保存到: {path}")

# ===== 主流程 =====
def main():
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    print("大语言模型端到端训练流程")

    trainer = CompleteLLMTraining(device=device)

    # 1. 预训练数据
    pretrain_texts = [
        "Machine learning is a subset of AI. " * 20,
    ] * 10

    # 2. SFT数据
    sft_data = [
        {
            "instruction": "解释机器学习",
            "output": "机器学习是AI的一个分支..."
        }
    ] * 10

    # 3. 偏好数据
    preference_data = [
        {
            "prompt": "什么是AI?",
            "chosen": "人工智能是模拟人类智能的技术...",
            "rejected": "AI就是人工智能。"
        }
    ] * 10

    # 4. RLHF prompts
    rlhf_prompts = ["什么是机器学习?", "解释深度学习"]

    # 执行完整流程
    trainer.pretrain(pretrain_texts, num_epochs=2)
    trainer.supervised_finetune(sft_data, num_epochs=2)
    trainer.train_reward_model(preference_data, num_epochs=2)
    trainer.rlhf_training(rlhf_prompts, num_iterations=5)

    # 测试生成
    print("\n最终模型测试:")
    response = trainer.generate("什么是人工智能?", max_length=80)
    print(f"Response: {response}")

    # 保存
    trainer.save("./final_model")
    print("\n✓ 完整训练流程结束!")

if __name__ == "__main__":
    main()

6.2 模型推理

"""
训练好的模型推理示例
"""

import torch
from transformers import GPT2LMHeadModel, GPT2Tokenizer

class LLMInference:
    """大语言模型推理"""

    def __init__(self, model_path, device='cpu'):
        self.device = device
        self.model = GPT2LMHeadModel.from_pretrained(model_path).to(device)
        self.tokenizer = GPT2Tokenizer.from_pretrained(model_path)

        if self.tokenizer.pad_token is None:
            self.tokenizer.pad_token = self.tokenizer.eos_token

        self.model.eval()

    @torch.no_grad()
    def generate(self, prompt, max_length=100, temperature=0.7):
        """生成文本"""
        input_ids = self.tokenizer.encode(prompt, return_tensors='pt').to(self.device)

        output_ids = self.model.generate(
            input_ids,
            max_length=max_length,
            temperature=temperature,
            top_p=0.9,
            do_sample=True,
            pad_token_id=self.tokenizer.pad_token_id
        )

        return self.tokenizer.decode(output_ids[0], skip_special_tokens=True)

    @torch.no_grad()
    def chat(self, instruction, max_length=150):
        """对话式生成"""
        prompt = f"Instruction: {instruction}\nResponse:"
        response = self.generate(prompt, max_length=max_length)

        if "Response:" in response:
            response = response.split("Response:")[-1].strip()

        return response

# 使用示例
def main():
    # 加载模型
    inference = LLMInference('./final_model', device='cpu')

    # 测试生成
    prompts = [
        "解释什么是机器学习",
        "列出三种编程语言",
        "写一首关于春天的诗"
    ]

    for prompt in prompts:
        response = inference.chat(prompt)
        print(f"\n指令: {prompt}")
        print(f"回答: {response}")
        print("-" * 80)

if __name__ == "__main__":
    main()

总结

完整训练流程回顾

┌──────────────┐
│ 1. 预训练    │  海量文本 → Base模型
│              │  目标:学习语言基础
└──────┬───────┘
       ↓
┌──────────────┐
│ 2. 监督微调  │  指令-回答对 → Instruct模型
│   (SFT)      │  目标:遵循指令
└──────┬───────┘
       ↓
┌──────────────┐
│ 3. 奖励模型  │  人类偏好对比 → 评分器
│              │  目标:学习人类偏好
└──────┬───────┘
       ↓
┌──────────────┐
│ 4. RLHF      │  强化学习 → 对齐模型
│   (PPO)      │  目标:符合人类偏好
└──────────────┘
       ↓
  ChatGPT级别的模型

关键要点

Transformer架构

  • 不使用卷积层,使用自注意力机制
  • 核心公式:Attention(Q,K,V) = softmax(QK^T/√d_k)V
  • 位置编码:PE = sin/cos(pos/10000^(2i/d_model))

预训练

  • 目标:下一个词预测
  • 损失:交叉熵损失
  • 数据:海量无标注文本
  • 学习率:5e-4

监督微调(SFT)

  • 目标:遵循指令
  • 数据:指令-回答对
  • 关键:只对回答部分计算损失
  • 学习率:1e-5(比预训练小)

奖励模型

  • 目标:评估回答质量
  • 损失:-log(sigmoid(r_chosen - r_rejected))
  • 数据:人类偏好对比

RLHF

  • 算法:PPO
  • 损失:PPO损失 + KL惩罚
  • 学习率:1e-6(最小)
  • 关键:限制更新幅度

常见问题

Q: 需要多少GPU?

  • 学习示例:1个GPU或CPU
  • 小模型(1B):1-8个GPU
  • 大模型(70B+):数百个GPU

Q: 训练需要多长时间?

  • 小模型示例:数小时
  • 实际应用:数周到数月

Q: 如何获取训练数据?

  • 预训练:Common Crawl、Wikipedia
  • SFT:Alpaca、ShareGPT等开源数据集
  • RLHF:需要人工标注偏好

Q: 显存不足怎么办?

  • 减小批次大小
  • 减小序列长度
  • 使用梯度累积
  • 使用LoRA等参数高效方法

进阶学习资源

论文

  1. Attention Is All You Need (Transformer)
  2. Language Models are Few-Shot Learners (GPT-3)
  3. Training language models to follow instructions (InstructGPT)

开源项目

  1. Hugging Face Transformers
  2. TRL (Transformer Reinforcement Learning)
  3. LLaMA, Alpaca

在线课程

  1. Stanford CS224N
  2. Hugging Face Course
  3. DeepLearning.AI

7. 主流大模型具体训练案例

7.1 GPT-3 训练案例

模型信息

  • 发布时间:2020年
  • 开发公司:OpenAI
  • 参数量:175B(1750亿)
  • 架构:仅解码器(Decoder-only)Transformer

训练配置

# GPT-3 (175B) 配置
gpt3_config = {
    # 模型架构
    'n_layers': 96,              # Transformer层数
    'n_heads': 96,               # 注意力头数
    'd_model': 12288,            # 嵌入维度
    'd_ff': 49152,               # FFN中间层维度 (4 * d_model)
    'vocab_size': 50257,         # 词汇表大小
    'context_length': 2048,      # 上下文长度

    # 训练超参数
    'batch_size': 3200000,       # 批次大小(token数)
    'learning_rate': 0.6e-4,     # 学习率
    'weight_decay': 0.1,         # 权重衰减
    'gradient_clip': 1.0,        # 梯度裁剪

    # 训练数据
    'training_tokens': 300e9,    # 300B tokens
    'training_data': [
        'Common Crawl (filtered): 60%',
        'WebText2: 22%',
        'Books1: 8%',
        'Books2: 8%',
        'Wikipedia: 3%'
    ]
}

训练流程

阶段1: 预训练
├── 数据准备
   ├── Common Crawl: 410B tokens  过滤后 60%
   ├── WebText2: 高质量网页数据
   ├── Books1 + Books2: 书籍语料
   └── Wikipedia: 英文维基百科

├── 训练细节
   ├── 训练时间: 数月
   ├── 硬件: 数千个V100 GPU
   ├── 混合精度: FP16训练
   └── 优化器: Adam (β1=0.9, β2=0.95, ε=10^-8)

└── 结果
    └── GPT-3 Base模型(能生成流畅文本,但不遵循指令)

代码示例(简化版)

"""
GPT-3 风格的预训练(简化版)
"""

from transformers import GPT2Config, GPT2LMHeadModel, GPT2Tokenizer
import torch
from torch.optim import AdamW

# GPT-3 Small配置(用于演示,实际GPT-3是175B)
config = GPT2Config(
    vocab_size=50257,
    n_positions=2048,      # GPT-3的上下文长度
    n_embd=768,            # 简化版,实际是12288
    n_layer=12,            # 简化版,实际是96
    n_head=12,             # 简化版,实际是96
    n_inner=3072,          # 简化版,实际是49152
)

model = GPT2LMHeadModel(config)
print(f"模型参数量: {sum(p.numel() for p in model.parameters()):,}")

# 优化器配置(与GPT-3相同)
optimizer = AdamW(
    model.parameters(),
    lr=0.6e-4,
    betas=(0.9, 0.95),
    eps=1e-8,
    weight_decay=0.1
)

# 训练循环(伪代码)
"""
for step in range(300_000_000_000 // batch_size):  # 300B tokens
    # 1. 从混合数据集采样
    batch = sample_batch(
        common_crawl=0.60,
        webtext2=0.22,
        books=0.16,
        wikipedia=0.03
    )

    # 2. 前向传播
    outputs = model(input_ids=batch, labels=batch)
    loss = outputs.loss

    # 3. 反向传播
    loss.backward()

    # 4. 梯度裁剪
    torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)

    # 5. 更新参数
    optimizer.step()
    optimizer.zero_grad()

    # 6. 学习率调度(余弦退火)
    scheduler.step()
"""

关键技术

  1. 数据过滤:Common Crawl质量参差不齐,需要严格过滤
  2. 混合精度训练:使用FP16节省显存和加速计算
  3. 模型并行:175B参数无法放入单个GPU,使用张量并行和流水线并行
  4. 梯度累积:模拟超大批次

7.2 ChatGPT 训练案例

模型信息

  • 发布时间:2022年11月
  • 开发公司:OpenAI
  • 基础模型:GPT-3.5(优化版GPT-3)
  • 训练方法:SFT + RLHF

完整训练流程

步骤1: 基础模型(GPT-3.5)
└── 从GPT-3改进而来,增强代码和推理能力

步骤2: 监督微调(SFT)
├── 数据收集
│   ├── 人工标注员编写高质量对话
│   ├── 涵盖各种任务类型
│   └── 约13,000条训练样本
│
├── 训练配置
│   ├── 学习率: 9.65e-6
│   ├── Epoch: 1-2
│   ├── Batch size: 32
│   └── 训练时间: 数天
│
└── 结果
    └── GPT-3.5-instruct(能遵循指令)

步骤3: 奖励模型训练
├── 数据收集
│   ├── 对同一prompt生成多个回答
│   ├── 标注员对回答排序
│   └── 约33,000条比较数据
│
├── 模型架构
│   ├── 基于GPT-3.5
│   ├── 移除语言建模头
│   └── 添加标量输出头
│
└── 训练
    └── 使用Ranking Loss训练

步骤4: RLHF(PPO)
├── 训练配置
│   ├── 学习率: 1.41e-6
│   ├── PPO epochs: 4
│   ├── KL系数: 0.2
│   └── 训练迭代: 数千次
│
├── 关键技术
│   ├── PPO算法优化策略
│   ├── KL散度约束防止偏离
│   └── 价值函数估计优势
│
└── 结果
    └── ChatGPT(对齐人类偏好)

SFT数据示例

{
  "prompt": "如何学习编程?",
  "response": "学习编程可以遵循以下步骤:\n\n1. **选择一门语言**:对于初学者,推荐Python或JavaScript\n2. **学习基础语法**:变量、循环、函数等\n3. **动手实践**:通过小项目巩固知识\n4. **阅读文档**:学会查阅官方文档\n5. **参与开源**:贡献代码提升技能\n\n记住,编程需要持续练习,不要害怕犯错!"
}

RLHF训练代码(简化版)

"""
ChatGPT风格的RLHF训练(简化版)
"""

from transformers import GPT2LMHeadModel, GPT2Tokenizer
import torch

# 1. 加载SFT模型作为初始策略
policy_model = GPT2LMHeadModel.from_pretrained('gpt3.5-sft')
ref_model = GPT2LMHeadModel.from_pretrained('gpt3.5-sft')  # 参考模型(冻结)

# 2. 加载训练好的奖励模型
reward_model = torch.load('reward_model.pt')

# 3. RLHF训练配置
rlhf_config = {
    'learning_rate': 1.41e-6,
    'ppo_epochs': 4,
    'kl_coef': 0.2,
    'clip_range': 0.2,
    'value_loss_coef': 1.0,
    'batch_size': 256,
}

# 4. 训练循环(伪代码)
"""
for iteration in range(num_iterations):
    # 采样prompts
    prompts = sample_prompts()

    # 生成回答
    responses = policy_model.generate(prompts)

    # 计算奖励
    rewards = reward_model(prompts + responses)

    # 计算KL散度(与参考模型)
    kl_div = compute_kl(policy_model, ref_model, prompts, responses)

    # PPO优化
    advantages = compute_advantages(rewards, values)
    ppo_loss = compute_ppo_loss(policy_model, old_policy, advantages)

    total_loss = ppo_loss + kl_coef * kl_div

    # 更新
    total_loss.backward()
    optimizer.step()
"""

7.3 LLaMA 训练案例

模型信息

  • 发布时间:2023年2月
  • 开发公司:Meta AI
  • 参数量:7B, 13B, 33B, 65B
  • 特点:开源、高效、仅预训练

训练配置(LLaMA-65B)

llama_65b_config = {
    # 模型架构
    'n_layers': 80,
    'n_heads': 64,
    'd_model': 8192,
    'd_ff': 22016,
    'vocab_size': 32000,      # 使用BPE tokenizer
    'context_length': 2048,

    # 训练数据(1.4T tokens)
    'training_data': {
        'CommonCrawl': '67%',
        'C4': '15%',
        'Github': '4.5%',
        'Wikipedia': '4.5%',
        'Books': '4.5%',
        'ArXiv': '2.5%',
        'StackExchange': '2%'
    },

    # 训练配置
    'batch_size': 4_000_000,  # 4M tokens
    'learning_rate': 1.5e-4,
    'weight_decay': 0.1,
    'gradient_clip': 1.0,
    'warmup_steps': 2000,
}

LLaMA的改进技术

"""
LLaMA相比GPT-3的改进
"""

# 1. Pre-normalization (GPT-3使用Post-norm)
class LLaMATransformerBlock(nn.Module):
    def forward(self, x):
        # Pre-norm: 在子层之前归一化
        x = x + self.attention(self.norm1(x))
        x = x + self.ffn(self.norm2(x))
        return x

# GPT-3使用Post-norm
class GPT3TransformerBlock(nn.Module):
    def forward(self, x):
        # Post-norm: 在子层之后归一化
        x = self.norm1(x + self.attention(x))
        x = self.norm2(x + self.ffn(x))
        return x

# 2. SwiGLU激活函数(替代ReLU)
class SwiGLU(nn.Module):
    def forward(self, x):
        x, gate = x.chunk(2, dim=-1)
        return F.silu(gate) * x

# 3. RoPE位置编码(替代绝对位置编码)
class RotaryPositionalEmbedding(nn.Module):
    """旋转位置编码"""
    def forward(self, q, k):
        # 应用旋转矩阵
        q = self.apply_rotary_emb(q, self.sin, self.cos)
        k = self.apply_rotary_emb(k, self.sin, self.cos)
        return q, k

训练代码示例

"""
LLaMA风格的预训练
"""

from transformers import LlamaConfig, LlamaForCausalLM, LlamaTokenizer

# LLaMA-7B配置
config = LlamaConfig(
    vocab_size=32000,
    hidden_size=4096,
    intermediate_size=11008,
    num_hidden_layers=32,
    num_attention_heads=32,
    max_position_embeddings=2048,
)

model = LlamaForCausalLM(config)
tokenizer = LlamaTokenizer.from_pretrained('meta-llama/Llama-7b')

# 训练数据准备
"""
LLaMA使用高质量、多样化的数据:
- CommonCrawl: 网页数据(经过严格过滤)
- C4: Colossal Clean Crawled Corpus
- Github: 开源代码
- Wikipedia: 多语言百科
- Books: 图书语料
- ArXiv: 学术论文
- StackExchange: 问答数据
"""

# 训练循环
optimizer = torch.optim.AdamW(
    model.parameters(),
    lr=1.5e-4,
    betas=(0.9, 0.95),
    weight_decay=0.1
)

# 余弦学习率调度
from torch.optim.lr_scheduler import CosineAnnealingLR
scheduler = CosineAnnealingLR(optimizer, T_max=num_training_steps)

"""
训练1.4T tokens,约需要:
- LLaMA-7B: 82,432 GPU小时(A100)
- LLaMA-13B: 135,168 GPU小时
- LLaMA-65B: 1,022,362 GPU小时
"""

7.4 Claude 训练案例

模型信息

  • 发布时间:2023年
  • 开发公司:Anthropic
  • 特点:Constitutional AI(宪法AI)
  • 训练方法:RLHF + CAI

Constitutional AI (CAI) 流程

传统RLHF的问题:
└── 依赖大量人工标注,成本高

Constitutional AI的解决方案:
├── 步骤1: 生成回答
│   └── 对有害prompt生成回答
│
├── 步骤2: 自我批评
│   ├── 使用"宪法原则"评估回答
│   ├── 原则示例:
│   │   - "请选择最无害的回答"
│   │   - "请选择最有帮助的回答"
│   │   - "请避免歧视性内容"
│   └── 模型自己生成改进建议
│
├── 步骤3: 自我修正
│   └── 根据批评重新生成更好的回答
│
├── 步骤4: 训练奖励模型
│   ├── 使用AI生成的偏好数据
│   └── 而非人工标注
│
└── 步骤5: RLHF
    └── 使用AI训练的奖励模型进行RL优化

Constitutional AI示例

"""
Constitutional AI训练流程(概念演示)
"""

# 定义宪法原则
CONSTITUTION = [
    "请选择最有帮助和无害的回答",
    "避免使用歧视性或冒犯性语言",
    "尊重用户隐私",
    "提供准确和可验证的信息",
    "承认不确定性,避免误导",
]

class ConstitutionalAI:
    def __init__(self, base_model):
        self.model = base_model
        self.constitution = CONSTITUTION

    def generate_critique(self, prompt, response):
        """让模型自我批评"""
        critique_prompt = f"""
原始问题: {prompt}
模型回答: {response}

请根据以下原则评估这个回答:
{self.constitution[0]}

批评:"""

        critique = self.model.generate(critique_prompt)
        return critique

    def revise_response(self, prompt, response, critique):
        """根据批评修正回答"""
        revision_prompt = f"""
原始问题: {prompt}
原始回答: {response}
批评: {critique}

请根据批评改进回答:"""

        revised_response = self.model.generate(revision_prompt)
        return revised_response

    def train_with_cai(self, prompts):
        """Constitutional AI训练循环"""
        preference_data = []

        for prompt in prompts:
            # 1. 生成初始回答
            initial_response = self.model.generate(prompt)

            # 2. 自我批评
            critique = self.generate_critique(prompt, initial_response)

            # 3. 自我修正
            revised_response = self.revise_response(prompt, initial_response, critique)

            # 4. 构建偏好数据(修正版 > 初始版)
            preference_data.append({
                'prompt': prompt,
                'chosen': revised_response,
                'rejected': initial_response
            })

        # 5. 训练奖励模型
        reward_model = self.train_reward_model(preference_data)

        # 6. RLHF训练
        self.rlhf_train(reward_model)

        return self.model

# 使用示例
cai_trainer = ConstitutionalAI(base_model)
trained_model = cai_trainer.train_with_cai(prompts)

Claude的优势

  1. 减少人工标注:使用AI自我评估和改进
  2. 更好的对齐:通过宪法原则确保价值观一致
  3. 可解释性:明确的原则列表
  4. 可扩展性:容易添加新的原则

7.5 训练成本对比

模型参数量训练数据GPU时间估算成本
GPT-3175B300B tokens~3640万GPU小时$460万
LLaMA-65B65B1.4T tokens~100万GPU小时$200万
LLaMA-7B7B1T tokens~8.2万GPU小时$16万
GPT-4未公开未公开估计>1亿GPU小时>$6000万

成本计算(基于A100 GPU):

  • A100租用成本:约$2-3/小时
  • LLaMA-7B:82,432小时 × 2.52.5 ≈ 206,000

7.6 实践建议

对于个人学习者

# 推荐配置:小型模型用于学习
small_model_config = {
    'parameters': '125M - 1B',
    'training_data': '10-100GB',
    'hardware': '1-4个GPU (RTX 3090 / V100)',
    'training_time': '数天到数周',
    'cost': '$100 - $1000',
}

# 建议
recommendations = {
    '1. 使用预训练模型': 'GPT-2, LLaMA-7B等开源模型',
    '2. 参数高效微调': 'LoRA, Adapter等技术',
    '3. 使用小数据集': 'Alpaca (52k), Dolly (15k)',
    '4. 云GPU服务': 'Google Colab, AWS, Vast.ai',
}

对于研究团队

# 推荐配置:中等模型用于研究
research_model_config = {
    'parameters': '3B - 13B',
    'training_data': '100GB - 1TB',
    'hardware': '8-64个GPU (A100)',
    'training_time': '数周到数月',
    'cost': '$10,000 - $100,000',
}

# 技术栈
tech_stack = {
    'framework': 'PyTorch + DeepSpeed / Megatron',
    'distributed': 'DDP, FSDP, 模型并行',
    'optimization': '混合精度, 梯度检查点',
    'monitoring': 'WandB, TensorBoard',
}

对于企业应用

# 推荐配置:大模型用于生产
production_model_config = {
    'parameters': '30B - 70B+',
    'training_data': '1TB+',
    'hardware': '数百个A100/H100',
    'training_time': '数月',
    'cost': '$100万+',
}

# 考虑因素
considerations = {
    '数据质量': '投入大量资源进行数据清洗和过滤',
    '安全性': '内容过滤、偏见检测',
    '可扩展性': '分布式训练、推理优化',
    '合规性': '隐私保护、版权问题',
}

7.7 关键经验总结

数据是关键

GPT-3教训: Common Crawl质量参差不齐
├── 解决方案: 严格过滤和去重
└── LLaMA改进: 使用更高质量的数据混合

ChatGPT教训: SFT数据质量 > 数量
├── 只用13k条高质量对话
└── 效果远超大规模低质量数据

Claude创新: Constitutional AI
└── 减少人工标注依赖

训练技巧

  1. 学习率调度至关重要

    • 预训练:warmup + 余弦退火
    • SFT:恒定小学习率
    • RLHF:极小学习率
  2. 批次大小影响收敛

    • 大批次:更稳定,但需要调整学习率
    • 小批次:更快迭代,但更不稳定
    • 梯度累积:模拟大批次
  3. 数据混合策略

    • GPT-3:60% Common Crawl + 其他
    • LLaMA:更均衡的数据分布
    • 定期调整混合比例
  4. 评估很重要

    • 不要只看训练loss
    • 定期进行人工评估
    • 使用多样化的测试集

这些案例展示了从GPT-3到ChatGPT、LLaMA、Claude等主流大模型的真实训练过程!


祝学习愉快!🚀

如有问题,欢迎查阅各个章节的详细代码和说明。