graph TD
A["Input Tokens"] --> B["Embedding Layer<br/>(无位置编码添加)"]
B --> C["Transformer Block × N"]
C --> D["RMSNorm"]
D --> E["Linear Head<br/>(与 Embedding 共享权重)"]
E --> F["Logits → Softmax"]
subgraph "Transformer Block"
G["RMSNorm"] --> H["Multi-Head Attention<br/>+ RoPE"]
H --> I["Residual Connection"]
I --> J["RMSNorm"]
J --> K["SwiGLU FFN"]
K --> L["Residual Connection"]
end
C --> G
第23章 LLaMA 系列训推实战
第23章 LLaMA 系列训推实战
LLaMA 不是第一个开源大语言模型,但它是第一个让整个行业重新思考”开源 AI 边界”的模型。从 LLaMA 1 的”意外泄漏”到 LLaMA 3 的正式开放,Meta 用一个模型系列定义了开源 LLM 的事实标准。
23.1 章节导入
2023 年 2 月,Meta 发布 LLaMA 1,最初仅限学术审批访问。但模型权重在一周内通过 4chan 和 GitHub 泄露到全网。这个”意外”反而催生了开源 LLM 生态的爆发——Alpaca、Vicuna、WizardLM 等基于 LLaMA 的微调模型如雨后春笋般出现。
到 2024 年,LLaMA 3.1 系列已经覆盖 1B 到 405B 参数规模,405B 版本在多项基准上与 GPT-4o 正面对决。Meta 用接近 40 万张 H100 GPU 的训练集群,向世界展示了开源模型能够达到闭源模型的天花板。
本章将深入拆解 LLaMA 系列的架构设计、训练技术、推理部署和微调实践。无论你是想部署一个私有 LLM 服务,还是想在自己的数据上微调,本章都提供了从理论到代码的完整路径。
23.2 架构拆解
23.2.1 整体架构
LLaMA 本质上是一个标准的 Decoder-only Transformer,但它在三个关键位置做了非标准选择:RoPE 位置编码、SwiGLU 激活函数、RMSNorm 归一化。这三个选择后来几乎成为所有开源模型的标配。
23.2.2 RoPE:旋转位置编码
传统的可学习位置编码(如 GPT-2)或正弦位置编码(如原始 Transformer)各有局限:前者不能外推到训练长度之外,后者外推效果也不好。
RoPE(Rotary Position Embedding)的思路很优雅:通过旋转矩阵将位置信息编码到 Query 和 Key 中,使得注意力分数自然地包含了相对位置信息。
数学上,对于位置 \(m\) 的向量 \(\mathbf{q}\),RoPE 将其按维度两两分组,每组旋转角度 \(m\theta_i\):
\[ \text{RoPE}(\mathbf{q}, m) = \mathbf{q} \odot \cos(m\boldsymbol{\theta}) + \text{rotate}(\mathbf{q}) \odot \sin(m\boldsymbol{\theta}) \]
其中 \(\theta_i = 10000^{-2i/d}\)。
RoPE 的关键优势在于长度外推——通过 NTK-aware scaling 或 YaRN 等方法,可以将 4K 训练长度的模型扩展到 128K+ 上下文。
# RoPE 的简洁实现
import torch
import torch.nn as nn
class RotaryEmbedding(nn.Module):
def __init__(self, dim, max_position_embeddings=2048, base=10000):
super().__init__()
inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2).float() / dim))
self.register_buffer("inv_freq", inv_freq)
self.max_seq_len_cached = max_position_embeddings
def forward(self, x, seq_len=None):
seq_len = seq_len or x.shape[-2]
t = torch.arange(seq_len, device=x.device, dtype=torch.float32)
freqs = torch.einsum("i,j->ij", t, self.inv_freq)
# cos 和 sin 缓存
emb = torch.cat((freqs, freqs), dim=-1)
return emb.cos()[None, :, None, :], emb.sin()[None, :, None, :]
def apply_rotary_pos_emb(q, k, cos, sin):
"""将 RoPE 应用到 Query 和 Key"""
def rotate_half(x):
x1, x2 = x[..., :x.shape[-1] // 2], x[..., x.shape[-1] // 2:]
return torch.cat((-x2, x1), dim=-1)
q_embed = (q * cos) + (rotate_half(q) * sin)
k_embed = (k * cos) + (rotate_half(k) * sin)
return q_embed, k_embed23.2.3 GQA:分组查询注意力
LLaMA 2 引入了 GQA(Grouped-Query Attention),这是从标准 Multi-Head Attention(MHA)向 Multi-Query Attention(MQA)的折中。
- MHA(标准):每个 Query head 对应一个 Key/Value head → \(O(d^2 \cdot n_{heads})\)
- MQA(极端):所有 Query head 共享同一组 Key/Value → KV Cache 大幅减少,但质量下降
- GQA(折中):将 Query heads 分组,每组共享一对 Key/Value heads
graph LR
subgraph "MHA (LLaMA 1)"
Q1[Q Head 1] --- K1[K Head 1]
Q2[Q Head 2] --- K2[K Head 2]
Q3[Q Head 3] --- K3[K Head 3]
Q4[Q Head 4] --- K4[K Head 4]
end
subgraph "GQA (LLaMA 2)"
Q5[Q Head 1] --- K5[KV Group A]
Q6[Q Head 2] --- K5
Q7[Q Head 3] --- K6[KV Group B]
Q8[Q Head 4] --- K6
end
subgraph "MQA (极端)"
Q9[Q Head 1] --- K7[Single KV]
Q10[Q Head 2] --- K7
Q11[Q Head 3] --- K7
Q12[Q Head 4] --- K7
end
LLaMA 2 70B 使用了 8 个 KV heads(而 Query heads 为 64),KV Cache 大小减少了 8 倍。这对推理吞吐至关重要——KV Cache 是长序列推理的主要内存瓶颈。
23.2.4 SwiGLU 激活函数
标准 Transformer 使用 ReLU 作为 FFN 的激活函数。LLaMA 选择了 SwiGLU(Swish-Gated Linear Unit):
\[ \text{SwiGLU}(x, W, V) = \text{Swish}(xW) \otimes xV \]
其中 \(\text{Swish}(x) = x \cdot \sigma(\beta x)\)。
SwiGLU 引入了一个额外的门控机制,虽然增加了一个权重矩阵(FFN 参数从 \(2d^2\) 变为 \(3d^2\)),但实验表明同样参数预算下效果更好。这也是为什么 LLaMA 的 FFN hidden dim 通常是 \(\frac{2}{3} \times 4d\) 而非标准的 \(4d\)——为了保持总参数量不变。
23.2.5 主要模型规格对比
| 规格 | LLaMA 2 7B | LLaMA 2 70B | LLaMA 3 8B | LLaMA 3 70B | LLaMA 3.1 405B |
|---|---|---|---|---|---|
| Layers | 32 | 80 | 32 | 80 | 126 |
| Heads | 32 | 64 | 32 | 64 | 128 |
| KV Heads | 32 | 8 | 8 | 8 | 8 |
| Dim | 4096 | 8192 | 4096 | 8192 | 16384 |
| Vocab | 32000 | 32000 | 128256 | 128256 | 128256 |
| Context | 4096 | 4096 | 8192 | 8192 | 131072 |
| 训练 Tokens | 2.0T | 2.0T | 15T | 15T | 15T+ |
LLaMA 3 的词表扩展:从 32K 扩展到 128K,这意味着同样的文本需要的 token 数更少。中文编码效率提升尤其明显——LLaMA 2 编码一个中文字符平均需要 2-3 个 token,LLaMA 3 只需 1-2 个。这不仅提升了推理速度,还降低了中文场景的 API 成本。
23.3 训练技术细节
23.3.1 预训练数据策略
Meta 在 LLaMA 3 的技术报告中透露了一个关键数字:15 万亿 token。这比 LLaMA 2 的 2 万亿多了 7.5 倍。但更重要的不是量,而是数据质量。
LLaMA 3 的数据配比大致如下(Meta 没有公开精确比例,这是社区推测):
网页数据: ~50% (Common Crawl 清洗)
代码数据: ~25% (GitHub + 代码相关网页)
学术文献: ~10% (arXiv, 等)
书籍: ~5%
数学: ~5%
其他高质量文本: ~5%
数据清洗是预训练中最耗工程的部分。Meta 使用了以下管线:
- 启发式过滤:去除重复 n-gram、低质量页面
- 模型质量分类:用一个高质量数据训练的分类器给网页打分
- 安全过滤:去除有害内容
- 去重:MinHash + LSH 近似去重
# 简化的数据去重示例
from datasketch import MinHash, MinHashLSH
def create_minhash(tokens, num_perm=128):
m = MinHash(num_perm=num_perm)
for token in tokens:
m.update(token.encode('utf-8'))
return m
# 构建 LSH 索引
lsh = MinHashLSH(threshold=0.8, num_perm=128)
for doc_id, document in enumerate(documents):
tokens = tokenize(document)
mh = create_minhash(tokens)
# 查询近似重复
duplicates = lsh.query(mh)
if not duplicates:
lsh.insert(str(doc_id), mh)
keep_document(document)
else:
# 近似重复,跳过
discard_document(document)23.3.2 后训练:SFT + RLHF
LLaMA 的对齐流程遵循经典的 SFT → RLHF 范式,但 Meta 在 LLaMA 3 中做了重要改进:
第一阶段:SFT(Supervised Fine-Tuning)
使用高质量人工标注的对话数据。Meta 强调数据质量远比数量重要——他们发现用少量高质量数据(~10K 条)比大量中等质量数据效果更好。
# SFT 训练配置示例(基于 llama-recipes)
training:
model_name: "meta-llama/Meta-Llama-3-8B"
batch_size: 64
micro_batch_size: 1
gradient_accumulation_steps: 64
learning_rate: 2e-5
num_epochs: 3
warmup_ratio: 0.03
lr_scheduler: "cosine"
data:
dataset: "meta-llama/Meta-Llama-3-8B-Instruct" # 或自定义数据
train_on_input: false # 只在 response 上计算 loss
max_seq_length: 4096
precision:
bf16: true
gradient_checkpointing: true第二阶段:RLHF(Reinforcement Learning from Human Feedback)
SFT 模型 → 训练 Reward Model → PPO 优化 → 对齐模型
↑ ↓
人类偏好数据 ←───── Reject Sampling
Meta 在 LLaMA 2 中使用了 Rejection Sampling + PPO 的两轮迭代策略:先用 SFT 模型生成多个回答,人工选择最好的,用这些数据再做一轮 SFT,然后再跑 PPO。
LLaMA 3 进一步引入了 DPO(Direct Preference Optimization) 作为 PPO 的替代方案——DPO 不需要显式的 Reward Model,直接从偏好数据中优化策略。
RLHF 不是银弹:Meta 的技术报告指出,过度的 RLHF 会导致模型”过度安全”(refusing benign requests)和”模板化回答”。他们花了大量精力调整 RLHF 的强度,确保模型既安全又有用。在实践中,DPO 的温度参数 beta 需要仔细调节——太高会让模型过于保守,太低则对齐效果不足。
23.3.3 代码训练的秘密
LLaMA 系列在代码任务上的强表现不是偶然的。Meta 在预训练中混入了大量代码数据,但这背后有一个有趣的现象:代码训练提升了模型的推理能力。
这不是 Meta 的独家发现——OpenAI 在 GPT-3.5 时代就观察到了类似现象。一种解释是代码的因果结构(condition → action)天然适合训练模型的逻辑推理链路。
23.4 推理部署
23.4.1 部署方案对比
| 方案 | 适用场景 | GPU 需求 | 吞吐量 | 延迟 | 易用性 |
|---|---|---|---|---|---|
| vLLM | 生产环境高吞吐 | 必须 | ⭐⭐⭐⭐⭐ | 低 | 中 |
| TGI | Hugging Face 生态 | 必须 | ⭐⭐⭐⭐ | 低 | 高 |
| llama.cpp | CPU/Mac/边缘设备 | 可选 | ⭐⭐ | 中 | 高 |
| Ollama | 本地开发 | 可选 | ⭐⭐ | 中 | ⭐⭐⭐⭐⭐ |
| TensorRT-LLM | 极致性能 | 必须(NVIDIA) | ⭐⭐⭐⭐⭐ | 极低 | 低 |
| MLX | Apple Silicon | 不需要 | ⭐⭐⭐ | 中 | 高 |
23.4.2 vLLM 部署
vLLM 是目前最流行的开源推理引擎,核心创新是 PagedAttention(详见第29章)。
# 基础部署:LLaMA 3.1 8B
vllm serve meta-llama/Meta-Llama-3.1-8B-Instruct \
--port 8000 \
--tensor-parallel-size 1 \
--max-model-len 8192 \
--gpu-memory-utilization 0.9
# 多 GPU 部署:LLaMA 3.1 70B
vllm serve meta-llama/Meta-Llama-3.1-70B-Instruct \
--port 8000 \
--tensor-parallel-size 4 \
--max-model-len 32768 \
--gpu-memory-utilization 0.9 \
--enable-prefix-caching
# 405B 需要 8x H100
vllm serve meta-llama/Meta-Llama-3.1-405B-Instruct \
--port 8000 \
--tensor-parallel-size 8 \
--max-model-len 8192 \
--quantization fp8 # FP8 量化减少显存# Python 客户端调用
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="dummy" # vLLM 默认不校验
)
response = client.chat.completions.create(
model="meta-llama/Meta-Llama-3.1-8B-Instruct",
messages=[
{"role": "system", "content": "你是一个有帮助的助手。"},
{"role": "user", "content": "解释什么是 PagedAttention"}
],
max_tokens=512,
temperature=0.7,
stream=True # 支持流式输出
)
for chunk in response:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")23.4.3 Ollama 本地部署
对于个人开发和测试,Ollama 是最简单的方案:
# 安装并运行(一行命令)
ollama run llama3.1:8b
# 自定义 Modelfile(类似 Dockerfile)
cat > Modelfile << 'EOF'
FROM llama3.1:8b
# 系统提示
SYSTEM """你是一个专业的 AI 工程师助手,擅长回答技术问题。"""
# 调整参数
PARAMETER temperature 0.3
PARAMETER top_p 0.9
PARAMETER num_ctx 8192
EOF
# 构建自定义模型
ollama create my-llama -f Modelfile
ollama run my-llama23.4.4 llama.cpp 量化部署
llama.cpp 支持 GGUF 格式的量化模型,可以在纯 CPU 环境或 Apple Silicon 上运行:
# 下载量化模型(Q4_K_M 是推荐的平衡点)
huggingface-cli download \
bartowski/Meta-Llama-3.1-8B-Instruct-GGUF \
Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf
# 使用 llama.cpp 运行
./llama-cli \
-m Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf \
-c 8192 \
-n -1 \
--chat-format llama3
# Apple Silicon 上使用 Metal 加速
./llama-cli \
-m Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf \
-ngl 99 \ # 将所有层放到 GPU
-c 8192量化对质量的影响是可接受的——Q4_K_M(4-bit 量化)相比 FP16,在多数基准上质量下降不到 2%:
| 量化格式 | 文件大小 (8B) | 相对质量 | 推理速度 |
|---|---|---|---|
| FP16(原始) | 16 GB | 100% | 1.0x |
| Q8_0 | 8.5 GB | ~99% | 1.3x |
| Q4_K_M | 4.7 GB | ~98% | 1.6x |
| Q4_0 | 4.4 GB | ~96% | 1.8x |
| Q3_K_M | 3.9 GB | ~93% | 2.0x |
| Q2_K | 3.0 GB | ~87% | 2.2x |
23.5 微调实践
23.5.1 LoRA:低秩适配
LoRA(Low-Rank Adaptation)是目前最流行的参数高效微调方法。核心思想:不修改原始权重,而是在旁边加一个低秩矩阵。
\[ W' = W + \Delta W = W + BA \]
其中 \(B \in \mathbb{R}^{d \times r}\),\(A \in \mathbb{R}^{r \times d}\),\(r \ll d\)(通常 \(r = 8, 16, 32\))。
# 使用 PEFT 库进行 LoRA 微调
from peft import LoraConfig, get_peft_model, TaskType
from transformers import AutoModelForCausalLM, TrainingArguments
from trl import SFTTrainer
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Meta-Llama-3-8B",
torch_dtype=torch.bfloat16,
device_map="auto"
)
lora_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=16, # LoRA 秩
lora_alpha=32, # 缩放因子,通常 = 2r
lora_dropout=0.05,
target_modules=[
"q_proj", "k_proj", "v_proj", "o_proj", # Attention
"gate_proj", "up_proj", "down_proj" # FFN
],
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# Output: trainable params: 13,631,488 || all params: 8,072,204,288 || trainable%: 0.17%
trainer = SFTTrainer(
model=model,
train_dataset=dataset,
args=TrainingArguments(
output_dir="./lora-output",
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
learning_rate=2e-4,
bf16=True,
logging_steps=10,
save_strategy="epoch",
),
max_seq_length=2048,
)
trainer.train()
# 保存 LoRA adapter(只有几十 MB)
model.save_pretrained("./my-llama-lora")23.5.2 QLoRA:量化 + LoRA
QLoRA 在 LoRA 基础上进一步量化基础模型到 4-bit,使得在单张消费级 GPU(如 RTX 4090 24GB)上微调 70B 模型成为可能。
from transformers import BitsAndBytesConfig
# 4-bit 量化配置
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4", # NormalFloat 4-bit
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True, # 双重量化
)
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Meta-Llama-3-70B",
quantization_config=bnb_config,
device_map="auto"
)
# 然后照常使用 LoRA
model = get_peft_model(model, lora_config)QLoRA 的实用建议: 1. nf4 量化类型比 fp4 效果更好——它假设权重服从正态分布 2. double_quant 可以再省 0.4 bit/参数,虽然不多但白捡 3. LoRA rank 不要太低——对于领域微调,r=32 通常是性价比最高的选择 4. 学习率要比全量微调高 10 倍左右(2e-4 vs 2e-5)
23.5.3 Full Fine-tuning
当你有足够的 GPU 资源且追求最高质量时,全量微调仍然是最佳选择。
# DeepSpeed ZeRO-3 配置用于 LLaMA 70B 全量微调
{
"bf16": {
"enabled": true
},
"zero_optimization": {
"stage": 3,
"overlap_comm": true,
"contiguous_gradients": true,
"sub_group_size": 1e9,
"reduce_bucket_size": "auto",
"stage3_prefetch_bucket_size": "auto",
"stage3_param_persistence_threshold": "auto",
"stage3_max_live_parameters": 1e9,
"stage3_max_reuse_distance": 1e9,
"stage3_gather_16bit_weights_on_model_save": true
},
"gradient_accumulation_steps": "auto",
"gradient_clipping": "auto",
"train_batch_size": "auto",
"train_micro_batch_size_per_gpu": "auto",
"steps_per_print": 2000
}# 启动全量微调(8x H100)
deepspeed --num_gpus 8 \
train.py \
--model_path meta-llama/Meta-Llama-3-70B \
--dataset_path ./data/train.jsonl \
--output_dir ./output \
--deepspeed ds_config_zero3.json \
--learning_rate 2e-5 \
--num_train_epochs 3 \
--per_device_train_batch_size 2 \
--gradient_accumulation_steps 1623.5.4 微调方法选型
全量微调
↑ 质量
│ ↗ LoRA (r=64+)
│ ↗
│ ↗ ← QLoRA
│
└──────────────→ GPU 资源需求
低 高
| 方法 | GPU 需求 (7B) | GPU 需求 (70B) | 训练速度 | 推理质量 | Adapter 大小 |
|---|---|---|---|---|---|
| Full FT | 2x A100 80G | 8x H100 80G | 1.0x | 100% | 全量 |
| LoRA | 1x RTX 4090 | 4x A100 80G | 1.5x | ~98% | ~50MB |
| QLoRA | 1x RTX 3090 | 1x A100 80G | 0.8x | ~96% | ~50MB |
23.6 实践建议
部署决策树:
- 只是想本地玩玩? → Ollama(一行命令)
- Mac 上跑? → Ollama 或 MLX(Apple Silicon 原生加速)
- 生产环境、高吞吐? → vLLM(PagedAttention 是杀手锏)
- NVIDIA 集群、极致延迟? → TensorRT-LLM(需要编译,但最快)
- CPU 服务器? → llama.cpp + GGUF 量化
- 需要灵活部署(多模型切换)? → TGI
常见陷阱: 1. Tokenizer 陷阱:LLaMA 3 的 tokenizer 与 LLaMA 2 完全不同(128K vs 32K vocab),微调数据必须用对应版本的 tokenizer 重新编码 2. Chat template 陷阱:LLaMA 3 使用自定义的 <|begin_of_text|><|start_header_id|>...<|end_header_id|> 格式,推理时必须使用正确的 chat template,否则效果会大幅下降 3. RoPE 缩放陷阱:如果你想让 8B 模型支持 128K 上下文,仅靠 YaRN 缩放是不够的——模型需要在长上下文数据上继续预训练 4. 量化陷阱:QLoRA 微调的模型在推理时必须保持量化状态,不能反量化回 FP16 再推理——这会丢失微调效果
23.7 小结
LLaMA 系列的成功不在于某个单一的技术突破,而在于一系列工程选择的系统性优化:
- RoPE 提供了长度外推的灵活性
- GQA 在质量和效率之间找到了最佳平衡点
- SwiGLU 用门控机制提升了 FFN 的表达能力
- 高质量数据 + 大规模训练 是 LLaMA 3 性能飞跃的根本原因
在推理侧,vLLM + PagedAttention 已经成为事实标准,而 Ollama 让个人开发者也能轻松在本地运行大模型。微调方面,LoRA/QLoRA 让参数高效微调成为主流——你不需要一个集群来定制模型,一张消费级 GPU 就够了。
23.8 延伸阅读
- Meta (2024). The LLaMA 3 Herd of Models. arXiv:2407.21783 — 目前最详细的技术报告
- Meta (2023). LLaMA 2: Open Foundation and Fine-Tuned Chat Models. arXiv:2307.09288
- Su et al. (2021). RoFormer: Enhanced Transformer with Rotary Position Embedding. arXiv:2104.09864
- Ainslie et al. (2023). GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints. arXiv:2305.13245
- Hu et al. (2021). LoRA: Low-Rank Adaptation of Large Language Models. arXiv:2106.09685
- Dettmers et al. (2023). QLoRA: Efficient Finetuning of Quantized LLMs. arXiv:2305.14314
- vLLM 项目:https://github.com/vllm-project/vllm
- Ollama 项目:https://github.com/ollama/ollama