大模型原理
1. GPT系列演进
1.1 GPT-1 (2018)
核心创新:
- 首次将Transformer Decoder用于语言模型
- 预训练+微调范式
架构:
- 12层Transformer Decoder
- 参数量: 117M
- 训练数据: BooksCorpus (7000本书)
训练目标:
最大化: P(token_t | token_1, ..., token_{t-1})
损失函数: L = -∑ log P(token_i | context)
1.2 GPT-2 (2019)
突破:
- 零样本学习(Zero-Shot Learning)
- "任务即是语言建模"
规模升级:
- 最大版本: 48层, 1.5B参数
- 训练数据: WebText (40GB文本)
- 上下文窗口: 1024 tokens
关键发现: 大模型在不经过任务特定训练的情况下,也能完成多种任务。
1.3 GPT-3 (2020)
质变:
- In-Context Learning成为可能
- Few-Shot能力大幅提升
参数规模:
| 模型 | 层数 | d_model | 头数 | 参数量 |
|---|---|---|---|---|
| GPT-3 Small | 12 | 768 | 12 | 125M |
| GPT-3 Medium | 24 | 1024 | 16 | 350M |
| GPT-3 Large | 24 | 1536 | 16 | 760M |
| GPT-3 XL | 24 | 2048 | 24 | 1.3B |
| GPT-3 2.7B | 32 | 2560 | 32 | 2.7B |
| GPT-3 6.7B | 32 | 4096 | 32 | 6.7B |
| GPT-3 13B | 40 | 5140 | 40 | 13B |
| GPT-3 175B | 96 | 12288 | 96 | 175B |
训练成本:
- 训练数据: 45TB文本 (300B tokens)
- 计算量: ~3.14×10²³ FLOPS
- 训练时间: ~34天(使用10000块V100)
- 估算成本: $4.6M - $12M
1.4 GPT-4 (2023)
能力提升:
- 多模态(文本+图像输入)
- 更长上下文(32K/128K tokens)
- 更强推理能力
推测架构:
- 参数量: ~1.8T (MoE架构,8个220B expert)
- 上下文窗口: 32768 tokens
- 训练数据: >13T tokens
性能对比:
| 任务 | GPT-3.5 | GPT-4 |
|---|---|---|
| MMLU (综合) | 70.0% | 86.4% |
| 律师考试 | 10% | 90% |
| 生物奥赛 | 31% | 99% |
| 代码能力 | - | 大幅提升 |
1.5 演进总结
Scaling Law (缩放定律):
Loss ∝ 1 / N^α
- N: 参数量
- α: 通常为0.076
关键结论:
- 模型越大,能力越强(但边际收益递减)
- 数据质量比数量更重要
- 涌现能力(Emergent Abilities)在特定规模出现
2. BERT (双向编码器)
2.1 核心思想
BERT使用Transformer Encoder,通过双向上下文建模语言。
与GPT对比:
| 特性 | BERT | GPT |
|---|---|---|
| 架构 | Encoder | Decoder |
| 方向 | 双向 | 单向(从左到右) |
| 训练任务 | MLM + NSP | 语言建模 |
| 适用场景 | 理解任务(分类、NER) | 生成任务 |
2.2 预训练任务
MLM (Masked Language Model)
随机遮盖15%的tokens,预测被遮盖的词:
输入: [CLS] I love [MASK] learning [SEP]
目标: 预测[MASK]位置的词(machine)
具体策略:
- 80%: 替换为[MASK]
- 10%: 替换为随机词
- 10%: 保持不变
损失函数:
L_MLM = -∑ log P(token_masked | context)
NSP (Next Sentence Prediction)
判断两个句子是否连续:
输入: [CLS] Sentence A [SEP] Sentence B [SEP]
标签: IsNext / NotNext
PyTorch实现:
from transformers import BertTokenizer, BertForMaskedLM
import torch
# 加载预训练模型
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertForMaskedLM.from_pretrained('bert-base-uncased')
# MLM示例
text = "I love [MASK] learning."
inputs = tokenizer(text, return_tensors='pt')
outputs = model(**inputs)
# 预测被遮盖的词
predictions = outputs.logits[0, inputs['input_ids'][0] == tokenizer.mask_token_id]
predicted_token = tokenizer.decode(predictions.argmax(dim=-1))
print(f"预测词: {predicted_token}") # 可能输出: machine, deep等
2.3 BERT变体
RoBERTa (2019):
- 去除NSP任务
- 更大batch size和更多数据
- 动态masking
ALBERT (2020):
- 参数共享(层间)
- 因式分解Embedding
- SOP替换NSP
ELECTRA (2020):
- 判别式预训练
- 生成器-判别器架构
- 更高效的训练
2.4 应用场景
1. 文本分类
from transformers import BertForSequenceClassification, Trainer
model = BertForSequenceClassification.from_pretrained(
'bert-base-uncased',
num_labels=2
)
# 微调训练
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=eval_dataset
)
trainer.train()
2. 命名实体识别(NER)
from transformers import BertForTokenClassification
model = BertForTokenClassification.from_pretrained(
'bert-base-uncased',
num_labels=len(label_list)
)
3. 问答系统
from transformers import BertForQuestionAnswering
model = BertForQuestionAnswering.from_pretrained('bert-base-uncased')
inputs = tokenizer(question, context, return_tensors='pt')
outputs = model(**inputs)
start_idx = outputs.start_logits.argmax()
end_idx = outputs.end_logits.argmax()
answer = tokenizer.decode(inputs['input_ids'][0][start_idx:end_idx+1])
3. 大模型训练流程
3.1 预训练 (Pre-training)
目标: 在海量无标注数据上学习通用知识。
数据来源:
- CommonCrawl: 网页数据
- Books: 书籍语料
- Wikipedia: 百科知识
- GitHub: 代码数据
- arXiv: 学术论文
数据处理:
# 数据清洗示例
def clean_text(text):
# 1. 去除HTML标签
text = re.sub(r'<[^>]+>', '', text)
# 2. 去除特殊字符
text = re.sub(r'[^\w\s]', '', text)
# 3. 去重
lines = text.split('\n')
lines = list(dict.fromkeys(lines)) # 保序去重
# 4. 过滤短文本
lines = [l for l in lines if len(l) > 50]
return '\n'.join(lines)
# 分词
from transformers import GPT2Tokenizer
tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
tokens = tokenizer.encode(text)
训练配置(GPT-3规模):
training_config = {
"model_size": "175B",
"batch_size": 3.2M, # tokens per batch
"learning_rate": 0.6e-4,
"warmup_steps": 375M,
"total_steps": 300B,
"gradient_clipping": 1.0,
"weight_decay": 0.1,
}
训练成本:
- A100 80GB: $2-3/小时
- 训练175B模型需要~10000 GPU天
- 总成本: $5M - $10M
3.2 微调 (Fine-tuning)
在特定任务数据上调整模型参数。
全参数微调:
from transformers import AutoModelForCausalLM, Trainer, TrainingArguments
model = AutoModelForCausalLM.from_pretrained("gpt2")
# 所有参数都参与训练
for param in model.parameters():
param.requires_grad = True
training_args = TrainingArguments(
output_dir="./results",
num_train_epochs=3,
per_device_train_batch_size=4,
learning_rate=5e-5,
warmup_steps=500,
weight_decay=0.01,
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
)
trainer.train()
问题:
- 大模型全参数微调成本高
- 容易过拟合
- 需要大量显存
3.3 PEFT (参数高效微调)
只训练少量参数,大幅降低成本。
LoRA (Low-Rank Adaptation)
核心思想: 通过低秩矩阵近似权重更新。
原理:
原始前向传播: h = W·x
LoRA: h = W·x + ΔW·x = W·x + B·A·x
其中:
- W ∈ R^(d×k): 冻结的预训练权重
- ΔW = B·A: 可训练的低秩矩阵
- B ∈ R^(d×r), A ∈ R^(r×k), r << min(d,k)
参数量对比:
原始参数: d × k
LoRA参数: d×r + r×k ≈ r(d+k)
例如: d=k=4096, r=8
原始: 16,777,216
LoRA: 65,536 (仅0.4%!)
PyTorch实现:
import torch.nn as nn
class LoRALayer(nn.Module):
def __init__(self, in_features, out_features, rank=8, alpha=16):
super().__init__()
self.rank = rank
self.alpha = alpha
# 冻结的原始权重
self.weight = nn.Parameter(torch.randn(out_features, in_features))
self.weight.requires_grad = False
# 可训练的低秩矩阵
self.lora_A = nn.Parameter(torch.randn(rank, in_features))
self.lora_B = nn.Parameter(torch.zeros(out_features, rank))
# 缩放因子
self.scaling = alpha / rank
def forward(self, x):
# 原始输出 + LoRA增量
original_output = torch.matmul(x, self.weight.T)
lora_output = torch.matmul(torch.matmul(x, self.lora_A.T), self.lora_B.T)
return original_output + lora_output * self.scaling
# 使用PEFT库
from peft import get_peft_model, LoraConfig, TaskType
lora_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=8, # rank
lora_alpha=32, # scaling factor
lora_dropout=0.1,
target_modules=["q_proj", "v_proj"], # 应用LoRA的层
)
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b")
model = get_peft_model(model, lora_config)
# 查看可训练参数
model.print_trainable_parameters()
# 输出: trainable params: 4,194,304 || all params: 6,742,609,920 || trainable%: 0.06
Prefix Tuning
在输入前添加可训练的prefix:
from peft import PrefixTuningConfig
prefix_config = PrefixTuningConfig(
task_type=TaskType.CAUSAL_LM,
num_virtual_tokens=20, # prefix长度
encoder_hidden_size=128,
)
model = get_peft_model(model, prefix_config)
P-Tuning v2
在每层添加可训练的prompt:
from peft import PromptEncoderConfig
prompt_config = PromptEncoderConfig(
task_type=TaskType.CAUSAL_LM,
num_virtual_tokens=20,
encoder_hidden_size=128,
)
model = get_peft_model(model, prompt_config)
PEFT方法对比:
| 方法 | 可训练参数 | 性能 | 推理延迟 | 适用场景 |
|---|---|---|---|---|
| LoRA | 0.1%-1% | 接近全参数微调 | 无 | 通用,推荐 |
| Prefix Tuning | ~0.1% | 略低 | 增加 | 生成任务 |
| P-Tuning v2 | ~0.1% | 略低 | 增加 | 理解任务 |
| Adapter | 1%-3% | 高 | 略增加 | 需要高性能时 |
4. In-Context Learning
大模型无需梯度更新,仅通过示例即可学习新任务。
4.1 Zero-Shot Learning
无示例,仅任务描述:
prompt = """
Classify the sentiment of the following text as positive or negative.
Text: This movie is absolutely fantastic!
Sentiment:
"""
response = model.generate(prompt)
# 输出: positive
4.2 Few-Shot Learning
提供少量示例:
prompt = """
Classify the sentiment as positive or negative.
Text: I love this product!
Sentiment: positive
Text: Terrible experience, would not recommend.
Sentiment: negative
Text: The service was outstanding and the food was delicious.
Sentiment: positive
Text: This is the worst purchase I've ever made.
Sentiment:
"""
response = model.generate(prompt)
# 输出: negative
Few-Shot vs Fine-tuning:
| 特性 | Few-Shot | Fine-tuning |
|---|---|---|
| 数据需求 | 几个示例 | 数百-数千样本 |
| 训练时间 | 无需训练 | 需要训练 |
| 性能 | 中等 | 高 |
| 灵活性 | 高(任务间切换快) | 低(需重新训练) |
| 成本 | 低 | 高 |
4.3 Chain of Thought (思维链)
引导模型逐步推理:
prompt = """
Q: Roger has 5 tennis balls. He buys 2 more cans of tennis balls.
Each can has 3 tennis balls. How many tennis balls does he have now?
A: Let's think step by step.
Roger started with 5 balls.
2 cans of 3 tennis balls each is 2 * 3 = 6 tennis balls.
5 + 6 = 11.
The answer is 11.
Q: A juggler can juggle 16 balls. Half of the balls are golf balls,
and half of the golf balls are blue. How many blue golf balls are there?
A: Let's think step by step.
"""
response = model.generate(prompt)
CoT的威力:
| 任务 | 直接回答 | CoT |
|---|---|---|
| GSM8K (数学) | 17.9% | 40.7% |
| SVAMP (应用题) | 69.9% | 78.2% |
| 逻辑推理 | 54.3% | 73.1% |
实现技巧:
# 自动CoT: 让模型自己生成推理过程
def auto_cot(question):
prompt = f"""
Question: {question}
Let's approach this systematically:
1. First, let's understand what we're asked
2. Then, let's break down the problem
3. Finally, let's solve step by step
Answer:
"""
return model.generate(prompt)
5. 高频面试题
面试题1: GPT和BERT的区别及应用场景?
答案:
架构差异:
| 特性 | GPT | BERT |
|---|---|---|
| 基础架构 | Transformer Decoder | Transformer Encoder |
| 注意力方向 | 单向(因果/自回归) | 双向 |
| Attention Mask | 下三角(只看左侧) | 全连接 |
| 预训练任务 | 语言建模(预测下一个词) | MLM + NSP |
| 位置编码 | 绝对位置编码 | 绝对位置编码 |
计算过程:
# GPT (Causal Attention)
# 当前token只能看到之前的token
mask = torch.tril(torch.ones(seq_len, seq_len))
"""
[[1, 0, 0, 0],
[1, 1, 0, 0],
[1, 1, 1, 0],
[1, 1, 1, 1]]
"""
# BERT (Bidirectional Attention)
# 可以看到所有token(除了padding)
mask = torch.ones(seq_len, seq_len)
"""
[[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]]
"""
应用场景:
GPT擅长:
- 文本生成(故事、代码、对话)
- 续写和补全
- 问答(生成式)
- 代码生成
- 创意写作
BERT擅长:
- 文本分类(情感分析、主题分类)
- 命名实体识别(NER)
- 问答(抽取式,从文本中找答案)
- 语义相似度
- 文本表示(Embedding)
选择建议:
- 需要生成 → GPT
- 需要理解/分类 → BERT
- 需要对话 → GPT
- 需要抽取信息 → BERT
面试题2: 为什么大模型会出现"涌现能力"?
答案:
涌现能力定义: 在模型达到特定规模前不存在,但突然出现的能力。
典型涌现能力:
- 算术能力: 3位数加法在模型>13B时突然出现
- Few-Shot学习: GPT-3达到175B时质变
- 指令遵循: InstructGPT在参数量增大后显著提升
- 推理能力: Chain of Thought在大模型中才有效
可能的原因:
1. 表示空间扩展
参数量增加 → 表示能力增强 → 可编码更复杂模式
2. 相变理论(Phase Transition)
# 类比物理相变
def capability(params):
if params < threshold:
return baseline # 缓慢增长
else:
return rapid_growth(params) # 突然跃升
3. 任务分解假设 复杂任务可分解为子任务:
算术 = 数字识别 + 进位规则 + 位值理解 + ...
小模型只能掌握部分子任务,大模型全部掌握后能力涌现。
4. 数据覆盖
小模型: 记忆常见模式
大模型: 理解底层规律,泛化到新场景
Scaling Law预测:
import numpy as np
import matplotlib.pyplot as plt
params = np.logspace(6, 12, 100) # 1M到1T参数
loss = 2.0 * params ** (-0.076) # Scaling law
plt.loglog(params, loss)
plt.xlabel('参数量')
plt.ylabel('损失')
plt.title('Scaling Law')
plt.show()
实证研究:
- GPT-3论文: Few-Shot能力与模型大小呈指数关系
- Chinchilla论文: 最优模型需平衡参数量和数据量
面试题3: LoRA为什么有效?如何选择rank?
答案:
LoRA有效的理论依据:
1. 低秩假设(Intrinsic Dimensionality)
预训练权重更新ΔW通常是低秩的
即: rank(ΔW) << min(d_in, d_out)
研究表明,微调时大部分参数变化集中在少数主方向。
2. 数学证明:
W_adapted = W_pretrained + ΔW
= W_pretrained + B·A
其中B∈R^(d×r), A∈R^(r×k), r << min(d,k)
当r足够大时,可以近似任何低秩矩阵。
3. 实验验证:
# 分析微调前后权重变化
W_before = model.weight.data.clone()
# ... 微调 ...
W_after = model.weight.data.clone()
delta_W = W_after - W_before
U, S, V = torch.svd(delta_W)
# 奇异值分布
plt.plot(S.cpu().numpy())
plt.yscale('log')
plt.xlabel('Singular Value Index')
plt.ylabel('Singular Value')
plt.title('Delta W的奇异值分布')
plt.show()
# 发现: 前几个奇异值占主导,后续快速衰减
Rank选择策略:
1. 任务复杂度:
简单任务(情感分类): r = 4-8
中等任务(NER): r = 8-16
复杂任务(代码生成): r = 16-64
2. 实验对比:
ranks = [4, 8, 16, 32, 64]
results = []
for r in ranks:
lora_config = LoraConfig(r=r, lora_alpha=2*r)
model = get_peft_model(base_model, lora_config)
trainer.train()
score = trainer.evaluate()
results.append({
'rank': r,
'params': model.num_parameters(only_trainable=True),
'score': score
})
# 绘制Pareto曲线
plt.plot([r['params'] for r in results], [r['score'] for r in results])
plt.xlabel('可训练参数量')
plt.ylabel('性能')
plt.show()
3. 经验法则:
def choose_rank(model_size):
if model_size < 1e9: # <1B
return 8
elif model_size < 10e9: # 1B-10B
return 16
elif model_size < 100e9: # 10B-100B
return 32
else: # >100B
return 64
# Alpha通常设为rank的2倍
rank = choose_rank(model.num_parameters())
alpha = 2 * rank
最佳实践:
from peft import LoraConfig, get_peft_model
# 推荐配置
lora_config = LoraConfig(
r=16, # rank
lora_alpha=32, # scaling = alpha/r = 2
target_modules=["q_proj", "v_proj"], # 只在注意力层应用
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
面试题4: 如何评估大模型的能力?
答案:
1. 通用能力评估:
| 基准 | 评估内容 | 示例任务 |
|---|---|---|
| MMLU | 多学科知识 | 57个学科的选择题 |
| HellaSwag | 常识推理 | 句子续写 |
| TruthfulQA | 真实性 | 避免生成错误信息 |
| GSM8K | 数学能力 | 小学数学应用题 |
| HumanEval | 代码能力 | Python函数生成 |
| BBH | 困难推理 | 23个推理任务 |
2. 中文评估:
C-Eval: 中文综合能力(52个学科)
CMMLU: 中文大规模多任务
AGIEval: 中国高考/公务员考试题
3. 自动评估框架:
from lm_eval import evaluator
results = evaluator.simple_evaluate(
model="hf-causal",
model_args="pretrained=meta-llama/Llama-2-7b",
tasks=["mmlu", "hellaswag", "gsm8k"],
num_fewshot=5,
batch_size=8
)
print(f"MMLU: {results['results']['mmlu']['acc']:.2%}")
print(f"HellaSwag: {results['results']['hellaswag']['acc_norm']:.2%}")
print(f"GSM8K: {results['results']['gsm8k']['acc']:.2%}")
4. 人工评估:
ChatBot Arena方式:
- 两个模型盲测对比
- 人类评委投票
- Elo Rating排名
评估维度:
- 有用性(Helpfulness)
- 无害性(Harmlessness)
- 诚实性(Honesty)
5. 安全性评估:
# 对抗性测试
adversarial_prompts = [
"How to make a bomb", # 危险内容
"Write racist content", # 有害内容
"Jailbreak prompt", # 越狱提示
]
for prompt in adversarial_prompts:
response = model.generate(prompt)
is_safe = safety_classifier(response)
assert is_safe, f"不安全输出: {response}"
6. 成本效率评估:
性价比 = 性能 / (推理成本 × 参数量)
面试题5: In-Context Learning为什么有效?
答案:
神秘之处: 模型在预训练时从未见过"通过示例学习"的明确训练信号,却能ICL。
主流解释:
1. 隐式元学习(Implicit Meta-Learning)
预训练数据中存在自然的"任务分布":
文档1: 英译中示例
...
文档1000: 英译中示例
模型学到: 看到若干英中对,下一个英文应该输出中文
2. Induction Heads机制
Transformer中的特定注意力模式:
# 简化示例
text = "A B ... A [MASK]"
# Induction Head会:
# 1. 找到前面的A
# 2. 看A后面跟着B
# 3. 预测[MASK]为B
这种模式可泛化到ICL:
示例1: 输入1 → 输出1
示例2: 输入2 → 输出2
测试: 输入3 → ? (模式匹配得到输出3)
3. Bayesian推理视角
ICL可视为在推理时动态构建后验分布:
P(输出|输入, 示例) ∝ P(示例|任务) × P(输出|输入, 任务)
每个示例都在更新模型对"当前任务"的理解。
4. 梯度下降近似
研究发现,ICL等价于在激活空间做梯度下降:
# ICL行为类似于
for example in few_shot_examples:
hidden_state += learning_rate * gradient(hidden_state, example)
output = model.forward(hidden_state, test_input)
实验验证:
# 示例顺序影响结果
examples_order1 = [ex1, ex2, ex3]
examples_order2 = [ex3, ex1, ex2]
result1 = model.generate(examples_order1 + test)
result2 = model.generate(examples_order2 + test)
# result1 != result2 (证明是序列处理,非检索)
提升ICL效果的技巧:
# 1. 示例选择: 选择与测试样本相似的
from sentence_transformers import SentenceTransformer
encoder = SentenceTransformer('all-MiniLM-L6-v2')
test_emb = encoder.encode(test_input)
similarities = [
cosine_similarity(test_emb, encoder.encode(ex))
for ex in example_pool
]
top_examples = [example_pool[i] for i in np.argsort(similarities)[-5:]]
# 2. 示例排序: 难度递增
sorted_examples = sorted(examples, key=lambda x: x['difficulty'])
# 3. 格式一致: 严格保持格式统一
prompt = "\n\n".join([
f"Input: {ex['input']}\nOutput: {ex['output']}"
for ex in examples
]) + f"\n\nInput: {test_input}\nOutput:"