Appearance
大模型工程面试专题
大模型工程面试的主线:Transformer 原理 → 训练流程 → 微调技术 → 推理优化 → 部署选型 → 评测实践。
Q1: 什么是大语言模型?和传统 NLP 模型有什么区别? 「🟢 校招/初级」
考察点:大模型的基本概念和与传统方法的对比。
参考答案:
大语言模型(LLM) 是基于 Transformer 架构、在海量文本上自监督预训练的超大规模神经网络,具备语言理解、生成、推理等通用能力。
| 维度 | 传统 NLP 模型 | 大语言模型 |
|---|---|---|
| 架构 | RNN/LSTM/CNN + 任务特定头 | Transformer(纯 Attention) |
| 训练方式 | 监督学习(标注数据) | 自监督预训练 + 指令微调 + RLHF |
| 参数量 | 百万~亿级 | 亿~万亿级(7B~1T+) |
| 数据量 | GB 级标注数据 | TB~PB 级无标注文本 |
| 能力 | 单一任务(分类/NER/翻译) | 通用多任务(Zero/Few-shot) |
| 使用方式 | 训练新模型/微调 | 提示词 + 可选微调 |
| 推理方式 | 判别式(输出标签) | 生成式(输出文本) |
| 部署成本 | 低(模型小) | 高(GPU/显存/推理优化) |
┌──────────────────────────────────────────────────────┐
│ 传统 NLP vs LLM 使用流程对比 │
│ │
│ 传统 NLP: │
│ 收集标注数据 → 训练任务模型 → 部署预测 │
│ 每个任务都需要重新训练 │
│ │
│ 大语言模型: │
│ 预训练(通用)→ SFT(指令跟随)→ RLHF(对齐) │
│ → 使用时:写 Prompt → 输出结果 │
│ 一个模型处理所有任务 │
└──────────────────────────────────────────────────────┘追问延伸:
- 大模型能完全替代传统 NLP 吗?(不能,简单分类任务传统方法更快更便宜)
- LLM 的 Zero-shot 和 Few-shot 有什么区别?(Zero-shot 不给示例,Few-shot 给几个示例)
Q2: 讲讲 Transformer 架构基本原理?Encoder 和 Decoder 是什么? 「🟢 校招/初级」
考察点:Transformer 核心架构和组件理解。
参考答案:
Transformer 是 2017 年 Google 提出的完全基于注意力机制的序列模型架构,抛弃了 RNN 的循环结构,支持并行计算。
┌──────────────────────────────────────────────────────────────┐
│ Transformer 架构 │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Encoder │ │ Decoder │ │
│ │ │ │ │ │
│ │ Input Embedding │ │ Output Embedding│ │
│ │ + Pos Enc │ │ + Pos Enc │ │
│ │ │ │ │ │ │ │
│ │ ┌────▼────┐ │ │ ┌────▼────┐ │ │
│ │ │Masked │ │ │ │Masked │ │ │
│ │ │Self-Attn │ │ │ │Self-Attn │ │ │
│ │ └────┬────┘ │ │ └────┬────┘ │ │
│ │ ┌────▼────┐ │ │ ┌────▼────┐ │ │
│ │ │Add & Norm│ │ │ │Cross-Attn│ │ │
│ │ └────┬────┘ │ │ │(K,V from │ │ │
│ │ ┌────▼────┐ │ │ │ Encoder) │ │ │
│ │ │Feed │ │ │ └────┬────┘ │ │
│ │ │Forward │ │ │ ┌────▼────┐ │ │
│ │ └────┬────┘ │ │ │Add & Norm│ │ │
│ │ ┌────▼────┐ │ │ └────┬────┘ │ │
│ │ │Add & Norm│ │ │ ┌────▼────┐ │ │
│ │ └─────────┘ │ │ │Feed │ │ │
│ │ × N 层 │ │ │Forward │ │ │
│ │ │ │ └────┬────┘ │ │
│ │ Encoder 输出 │────────►│ ┌────▼────┐ │ │
│ │ │ K, V │ │Linear + │ │ │
│ │ │ │ │Softmax │ │ │
│ │ │ │ └────┬────┘ │ │
│ └─────────────────┘ │ 输出概率分布 │ │
│ └─────────────────┘ │
│ │
│ Encoder-Only: BERT, RoBERTa → 理解任务(分类/NER) │
│ Decoder-Only: GPT, LLaMA → 生成任务 │
│ Encoder-Decoder: T5, BART → Seq2Seq(翻译/摘要) │
└──────────────────────────────────────────────────────────────┘核心组件
| 组件 | 作用 | 说明 |
|---|---|---|
| Self-Attention | 捕捉序列内部关系 | 每个 token 关注所有 token |
| Multi-Head Attention | 多角度关注 | 并行多个 Attention,捕捉不同模式 |
| Position Encoding | 注入位置信息 | Transformer 无位置感知,需额外编码 |
| Feed Forward | 非线性变换 | 两层全连接 + 激活函数 |
| Add & Norm | 残差连接 + LayerNorm | 缓解梯度消失,稳定训练 |
| Masked Attention | 防止看到未来信息 | Decoder 中用下三角掩码 |
python
import torch
import torch.nn.functional as F
import math
class SelfAttention(torch.nn.Module):
def __init__(self, dim, heads=8):
super().__init__()
self.heads = heads
self.dim_head = dim // heads
self.to_qkv = torch.nn.Linear(dim, dim * 3)
self.to_out = torch.nn.Linear(dim, dim)
def forward(self, x, mask=None):
# x: [batch, seq_len, dim]
qkv = self.to_qkv(x) # [batch, seq_len, dim*3]
q, k, v = qkv.chunk(3, dim=-1)
# 多头拆分
q = q.view(q.shape[0], q.shape[1], self.heads, self.dim_head).transpose(1, 2)
k = k.view(k.shape[0], k.shape[1], self.heads, self.dim_head).transpose(1, 2)
v = v.view(v.shape[0], v.shape[1], self.heads, self.dim_head).transpose(1, 2)
# Scaled Dot-Product Attention
attn = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.dim_head)
if mask is not None:
attn = attn.masked_fill(mask == 0, float('-inf'))
attn = F.softmax(attn, dim=-1)
out = torch.matmul(attn, v)
# 合并多头
out = out.transpose(1, 2).reshape(out.shape[0], out.shape[2], -1)
return self.to_out(out)为什么大模型都用 Decoder-Only
| 维度 | Encoder-Only | Encoder-Decoder | Decoder-Only |
|---|---|---|---|
| 代表模型 | BERT | T5 | GPT, LLaMA |
| 训练目标 | 掩码语言模型 | 去噪自编码 | 自回归生成 |
| 统一性 | 需要分类头 | 编码+解码两套 | 单一架构统一所有任务 |
| 扩展性 | 不好 | 一般 | 最适合 Scaling Law |
| 推理效率 | 一次编码 | 并行编码+串行解码 | 纯串行但 KV Cache 优化 |
追问延伸:
- 为什么不用 RNN 做大模型?(RNN 无法并行训练,梯度消失,长序列效果差)
- Encoder 和 Decoder 能共用参数吗?(可以,ALBERT/共享参数可以减参数量)
Q3: 多头注意力(MHA)有哪些局限?MQA、GQA、Flash Attention 怎么解决? 「🟡 中级」
考察点:注意力机制的优化技术。
参考答案:
MHA 的局限性
标准 MHA(Multi-Head Attention):
- 参数:Q、K、V 各有 n_heads 个投影矩阵
- 显存:KV Cache 占用 = 2 × n_heads × head_dim × seq_len × batch
- 问题:head 数量多时 KV Cache 巨大,推理瓶颈在显存带宽
举例:Llama-2-70B
- n_heads = 64, head_dim = 128, layers = 80
- KV Cache = 2 × 64 × 128 × 4096 × 80 ≈ 80GB(一个请求!)MQA / GQA 优化
┌──────────────────────────────────────────────────┐
│ MHA(标准多头注意力) │
│ Q: n_heads 个头 K: n_heads 个头 V: n_heads 个头│
│ KV Cache: 大 │
│ │
│ MQA(Multi-Query Attention) │
│ Q: n_heads 个头 K: 1 个头 V: 1 个头 │
│ KV Cache: 缩小 n_heads 倍 │
│ 问题:质量下降明显 │
│ │
│ GQA(Grouped-Query Attention) │
│ Q: n_heads 个头 K: n_groups 个头 V: n_groups │
│ 分组共享 KV,MHA 和 MQA 的折中 │
│ KV Cache: 缩小 n_heads/n_groups 倍 │
│ 质量接近 MHA,速度接近 MQA │
└──────────────────────────────────────────────────┘| 方法 | KV 头数 | KV Cache | 质量 | 代表模型 |
|---|---|---|---|---|
| MHA | = Q 头数 | 大 | 最好 | Llama-2, GPT |
| MQA | = 1 | 最小 | 略差 | PaLM, StarCoder |
| GQA | = 中间值 | 中等 | 接近 MHA | Llama-2-70B, Llama-3 |
python
class GroupedQueryAttention(torch.nn.Module):
def __init__(self, dim, n_heads, n_kv_heads):
super().__init__()
self.n_heads = n_heads # Q 的头数
self.n_kv_heads = n_kv_heads # KV 的头数(比 n_heads 少)
self.head_dim = dim // n_heads
self.n_rep = n_heads // n_kv_heads # 每个 KV 头对应几个 Q 头
self.to_q = torch.nn.Linear(dim, n_heads * self.head_dim, bias=False)
self.to_k = torch.nn.Linear(dim, n_kv_heads * self.head_dim, bias=False)
self.to_v = torch.nn.Linear(dim, n_kv_heads * self.head_dim, bias=False)
self.to_out = torch.nn.Linear(n_heads * self.head_dim, dim)
def forward(self, x):
q = self.to_q(x).view(..., self.n_heads, self.head_dim)
k = self.to_k(x).view(..., self.n_kv_heads, self.head_dim)
v = self.to_v(x).view(..., self.n_kv_heads, self.head_dim)
# KV 复制扩展到 Q 头数
k = k.repeat_interleave(self.n_rep, dim=-2) # 复制 n_rep 份
v = v.repeat_interleave(self.n_rep, dim=-2)
# 后续计算同 MHA
attn = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.head_dim)
attn = F.softmax(attn, dim=-1)
out = torch.matmul(attn, v)
return self.to_out(out)Flash Attention
问题:标准 Attention 需要把完整的 [seq_len, seq_len] 矩阵写入 HBM,显存 IO 是瓶颈。
核心思想:利用 GPU 的 SRAM(片上高速缓存),分块计算 Attention,避免中间矩阵在 HBM 中反复读写。
标准 Attention:
Q×K^T → 写入 HBM → 读回 → Softmax → 写入 → 读回 → ×V
O(N²) 显存 + 大量 HBM 读写
Flash Attention:
分块加载 Q, K, V 到 SRAM
→ 在 SRAM 内计算 Q×K^T + Softmax + ×V
→ 只写最终结果到 HBM
O(N) 显存 + 少量 HBM 读写| 维度 | 标准 Attention | Flash Attention |
|---|---|---|
| 显存 | O(N²) | O(N) |
| HBM 读写 | O(N²) | O(N²/M), M=SRAM 大小 |
| 数值精度 | 标准浮点 | 兼容在线 Softmax |
| 速度 | 慢 | 2-4 倍加速 |
追问延伸:
- Flash Attention v2 相比 v1 有什么改进?(更好的并行度,减少非矩阵乘法运算)
- 为什么 GQA 在推理时比训练时更有优势?(推理受显存带宽限制,训练是计算密集型)
Q4: 大模型的位置编码是干什么用的?sin/cos、RoPE、ALiBi 有什么区别? 「🟡 中级」
考察点:位置编码的作用和不同方案对比。
参考答案:
为什么需要位置编码:Transformer 的 Self-Attention 本质是集合操作(无序的),同一个词在不同位置会产生相同的输出,所以需要注入位置信息。
┌────────────────────────────────────────────────────────────┐
│ 位置编码方案对比 │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 绝对位置编码 │ │ RoPE 旋转位置 │ │ ALiBi 偏置 │ │
│ │ (sin/cos) │ │ 编码 │ │ │ │
│ │ │ │ │ │ │ │
│ │ 固定编码 │ │ 旋转矩阵 │ │ 注意力偏置 │ │
│ │ 训练时固定 │ │ 相对位置 │ │ 线性距离惩罚 │ │
│ │ 外推性差 │ │ 外推性好 │ │ 无需训练 │ │
│ │ BERT/GPT-2 │ │ LLaMA/Qwen │ │ BLOOM/MPT │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└────────────────────────────────────────────────────────────┘详细对比
| 维度 | sin/cos 绝对编码 | RoPE 旋转编码 | ALiBi |
|---|---|---|---|
| 原理 | 位置 i 的编码为 sin(i/10000^(2j/d)) | 对 Q/K 做旋转,角度与相对位置成正比 | 在 Attention Score 上加线性距离偏置 |
| 位置类型 | 绝对 | 相对 | 相对 |
| 外推性 | 差(超出训练长度效果骤降) | 好(可扩展到更长序列) | 好(天然支持长序列) |
| 可学习 | 固定(不可学习)或可学习 | 固定公式 | 无参数 |
| 实现位置 | 嵌入层后加 | Q、K 旋转 | Attention Score 上加 |
| 代表模型 | BERT, GPT-2 | LLaMA, Qwen, DeepSeek | BLOOM, MPT |
RoPE 原理详解
python
# RoPE 的核心思想:通过旋转矩阵编码相对位置
# 对位置 m 的 Q 和位置 n 的 K,它们的点积只依赖于 (m-n)
import torch
def rotate_half(x):
"""将 x 的后半部分取负与前半部分拼接"""
x1 = x[..., :x.shape[-1] // 2]
x2 = x[..., x.shape[-1] // 2:]
return torch.cat([-x2, x1], dim=-1)
def apply_rotary_pos_emb(q, k, cos, sin):
"""应用旋转位置编码"""
# q, k: [batch, heads, seq_len, head_dim]
# cos, sin: [seq_len, head_dim]
q_embed = q * cos + rotate_half(q) * sin
k_embed = k * cos + rotate_half(k) * sin
return q_embed, k_embed
# 位置 m 的 q_m 和位置 n 的 k_n 做点积:
# <R(m)*q, R(n)*k> = q^T * R(m-n) * k
# 只依赖于相对位置 (m-n),这是 RoPE 的核心优势ALiBi 原理
python
# ALiBi 不使用位置编码,而是在 Attention Score 上加距离偏置
# 距离越远的 token,Attention 被线性抑制
def alibi_attention_bias(seq_len, n_heads):
"""生成 ALiBi 偏置矩阵"""
# 距离矩阵
positions = torch.arange(seq_len)
dist = positions.unsqueeze(0) - positions.unsqueeze(1) # [seq, seq]
# 每个 head 一个斜率(固定值,不可学习)
slopes = torch.tensor([1 / (2 ** (i // 2)) for i in range(n_heads)])
# 偏置 = -slope * 距离
bias = -slopes.unsqueeze(-1).unsqueeze(-1) * dist.abs()
return bias # [n_heads, seq_len, seq_len]
# 效果:距离越远,Attention 被抑制越多
# 优点:无需训练位置编码,天然支持外推
# 缺点:线性衰减可能过于简单追问延伸:
- RoPE 如何支持更长上下文?(YaRN、NTK-aware 等插值方法扩展频率范围)
- 为什么 ALiBi 没有成为主流?(表达能力不如 RoPE,长序列效果不如经过调整的 RoPE)
Q5: 什么是大模型项目的分词器?原理是什么? 「🟢 校招/初级」
考察点:分词器的基本概念和原理。
参考答案:
分词器(Tokenizer) 是将文本切分为模型可处理的 token 序列的工具,介于文本和模型之间。
┌──────────────────────────────────────────────────────┐
│ 分词器工作流程 │
│ │
│ "我喜欢大模型" → 分词器 → [1024, 5678, 3456, 7890] │
│ 原始文本 → token ID 序列 │
│ │
│ "I love LLMs" → 分词器 → [40, 3012, 4156, 29889] │
│ 原始文本 → token ID 序列 │
└──────────────────────────────────────────────────────┘主流分词算法
| 算法 | 原理 | 代表模型 | 特点 |
|---|---|---|---|
| BPE | 合并最高频字节对,逐步构建词表 | GPT-2, LLaMA | 最常用,平衡频次和长度 |
| WordPiece | 类似 BPE,但用似然值选择合并 | BERT | 适合拉丁语系 |
| Unigram | 从大词表中删减,最大化语料概率 | T5, XLNet | 适合多语言 |
| SentencePiece | 直接处理原始文本,空格也作为 token | LLaMA, T5 | 语言无关,支持多语言 |
BPE 原理示例
训练过程:
1. 初始词表 = 所有单字符
2. 统计所有相邻 token 对频率
3. 合并频率最高的 token 对 → 新 token
4. 重复 2-3 直到词表大小达到目标
示例:
语料: ["low", "lower", "newest", "widest"]
初始: l, o, w, e, r, n, s, t, i, d
第1轮: "l"+"o" 出现最多 → "lo"
第2轮: "lo"+"w" 出现最多 → "low"
第3轮: "e"+"s" → "es"
...
推理时:
"lowest" → ["low", "es", "t"] → [1024, 3456, 789]为什么分词很重要
python
# 分词影响:
# 1. 词表大小 → 模型嵌入层参数量
# 2. 序列长度 → 计算量和上下文窗口利用率
# 3. 多语言能力 → 中文是1字1token还是多字1token
# 对比不同模型的中文分词
# GPT-4 (cl100k_base): "我喜欢大模型" → 6 tokens(一字一token)
# LLaMA-3: "我喜欢大模型" → 4 tokens(部分合并)
# 同样1K上下文窗口:
# 英文约 750 词
# 中文约 500-1000 字(取决于分词器)追问延伸:
- 词表大小怎么选?(太小→序列长;太大→嵌入层参数多。通常 32K-128K)
- 为什么中文模型要用专门的分词器?(避免一字一 token,压缩序列长度)
Q6: 大模型是怎么训练出来的? 「🟡 中级」
考察点:大模型训练全流程。
参考答案:
┌──────────────────────────────────────────────────────────────┐
│ 大模型训练全流程 │
│ │
│ 阶段1: 预训练 (Pre-training) │
│ ┌──────────────────────────────────────────────┐ │
│ │ 数据: 互联网文本、书籍、代码(TB级) │ │
│ │ 目标: 下一个 token 预测(自回归) │ │
│ │ 算力: 数千张 GPU × 数月 │ │
│ │ 产出: Base 模型(懂语言但不会对话) │ │
│ └──────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 阶段2: 指令微调 (SFT - Supervised Fine-Tuning) │
│ ┌──────────────────────────────────────────────┐ │
│ │ 数据: 高质量指令-回答对(万~百万条) │ │
│ │ 目标: 学会理解指令并给出有用回答 │ │
│ │ 产出: Chat 模型(能对话但可能有不当内容) │ │
│ └──────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 阶段3: 人类偏好对齐 (RLHF / DPO) │
│ ┌──────────────────────────────────────────────┐ │
│ │ 数据: 人类标注的回答偏好排序 │ │
│ │ 目标: 让回答符合人类价值观(安全/有用/诚实) │ │
│ │ 产出: 对齐后的 Chat 模型 │ │
│ └──────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 阶段4: 后训练 (Post-Training,可选) │
│ ┌──────────────────────────────────────────────┐ │
│ │ 能力增强: 代码/数学/工具调用/多语言 │ │
│ │ 安全: Red Teaming + 安全微调 │ │
│ │ 约束: Constitution AI / RLAIF │ │
│ └──────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘预训练细节
| 要素 | 说明 |
|---|---|
| 数据 | Common Crawl, Wikipedia, GitHub, 书籍等 |
| 数据清洗 | 去重、过滤低质量、PII 脱敏 |
| 训练目标 | 自回归:给定前 N 个 token,预测第 N+1 个 |
| 优化器 | AdamW(大 batch + 学习率 warmup + cosine 衰减) |
| 并行策略 | 数据并行 + 张量并行 + 流水线并行 |
| 混合精度 | BF16/FP16 训练 |
SFT 细节
python
# SFT 训练数据格式
{
"instruction": "请解释什么是闭包",
"input": "",
"output": "闭包是指有权访问另一个函数作用域中变量的函数..."
}
# 只对 output 部分计算 loss(输入部分不计算)
# 这是 SFT 和预训练的关键区别RLHF 流程
1. 训练奖励模型 (Reward Model)
- 收集人类偏好数据:对于同一问题,标注回答 A > 回答 B
- 训练 RM:输入 (prompt, response) → 输出标量分数
2. PPO 强化学习
- SFT 模型生成回答 → RM 打分 → PPO 优化策略
- KL 散度约束:防止模型偏离 SFT 太远追问延伸:
- 预训练的学习率怎么调?(Warmup + Cosine 衰减,峰值约 1e-4~3e-4)
- 3D 并行怎么分配?(TP 用于层内、PP 用于层间、DP 用于数据副本)
Q7: 什么是 Scaling Law?大模型的「涌现能力」是怎么回事? 「🟡 中级」
考察点:理解模型规模与性能的关系。
参考答案:
Scaling Law(缩放定律)
OpenAI 发现:模型的 Loss 随参数量(N)、数据量(D)、算力(C)呈幂律下降。
L(N, D, C) = A / N^α + B / D^β + C / C^γ
其中 α, β, γ ≈ 0.05-0.5(经验值)
核心结论:
1. 性能可预测:给定计算预算,可以预测最优模型大小和数据量
2. 模型越大越好(数据越多越好)→ 持续 Scaling
3. 存在最优分配:给定算力 C,最优 N* 和 D* 的比例是固定的
Chinchilla 法则(DeepMind 修正):
之前大家「模型太大、数据太少」
最优比例:D / N ≈ 20(每参数约 20 个 token)┌───────────────────────────────────────────────┐
│ Compute Budget vs 最优分配 │
│ │
│ 算力 模型参数 数据量 代表 │
│ ───────────────────────────────────────── │
│ 10^21 1B 20B tokens 小模型 │
│ 10^22 10B 200B tokens Llama-7B │
│ 10^23 70B 1.4T tokens Llama-70B │
│ 10^24 175B 3.5T tokens GPT-3 │
│ 10^25 540B 10T tokens PaLM │
└───────────────────────────────────────────────┘涌现能力(Emergent Abilities)
定义:模型在达到一定规模前不具备某些能力,跨过某个阈值后突然出现。
┌───────────────────────────────────────────────┐
│ 涌现能力示意图 │
│ │
│ 性能 │
│ ↑ │
│ │ ┌───── 突然出现 │
│ │ │ │
│ │ │ │
│ │──────────────┘ │
│ │ │
│ │──────────────(之前等于随机猜) │
│ └────────────────────────→ 模型规模 │
│ │
│ 涌现能力示例: │
│ - 少样本学习(Few-shot) │
│ - 思维链推理(Chain-of-Thought) │
│ - 指令跟随 │
│ - 代码生成 │
│ - 数学推理 │
└───────────────────────────────────────────────┘| 能力 | 涌现规模 | 说明 |
|---|---|---|
| Few-shot 学习 | ~10B | 不需微调,只看示例就能做任务 |
| CoT 推理 | ~60B | 思维链提示才有效果 |
| 指令跟随 | ~1B (SFT后) | 理解复杂指令 |
| 代码生成 | ~10B | 能写可运行的代码 |
涌现能力的争议
- 测量幻觉说:可能是因为评测指标有突变(如精确匹配),实际上能力是平滑提升的
- 任务难度说:困难任务需要更多组件能力同时达标,所以看起来像"涌现"
追问延伸:
- Chinchilla 法则对实际训练有什么指导?(要平衡模型大小和数据量,不要盲目增大参数)
- 涌现能力能预测吗?(目前不能精确预测,是经验观察现象)
Q8: 大模型微调的方案有哪些? 「🟡 中级」
考察点:微调方法的全景了解。
参考答案:
┌──────────────────────────────────────────────────────────────┐
│ 大模型微调方案分类 │
│ │
│ ┌───────────┴───────────┐ │
│ 全参数微调 参数高效微调 (PEFT) │
│ │ ┌───────┼───────┐ │
│ 所有参数更新 LoRA Adapter Prefix │
│ QLoRA Tuning │
│ │
│ 全参数: 更新所有参数,效果最好但成本高 │
│ PEFT: 只更新少量参数,效果接近全参数但成本低 │
└──────────────────────────────────────────────────────────────┘方案对比
| 方法 | 更新参数量 | 显存需求 | 效果 | 训练速度 | 代表 |
|---|---|---|---|---|---|
| 全参数 SFT | 100% | 高(需全部梯度) | 最好 | 慢 | 标准 SFT |
| LoRA | 0.1%-1% | 低(只训练适配器) | 接近全参数 | 快 | 最主流 |
| QLoRA | 0.1%-1% | 极低(4bit量化基座) | 接近 LoRA | 快 | 单卡微调 |
| Adapter | 1%-5% | 中(插入适配层) | 好 | 中 | Adapter-Tuning |
| Prefix Tuning | 0.1% | 低(只训练前缀) | 一般 | 快 | 早期方案 |
| P-Tuning v2 | 0.1% | 低(每层前缀) | 好 | 快 | 比v1改进 |
LoRA 原理
┌──────────────────────────────────────────┐
│ LoRA 原理 │
│ │
│ 原始权重 W (d×d) ← 冻结不动 │
│ 新增: W + ΔW = W + B×A │
│ A (d×r): 随机初始化,训练 │
│ B (r×d): 零初始化,训练 │
│ r = rank(通常 8-64) │
│ │
│ 参数量: d×d → 2×d×r (r<<d) │
│ 例: d=4096, r=8 → 参数量减少 256 倍 │
│ │
│ 推理时: 可合并 W' = W + B×A,无额外开销 │
└──────────────────────────────────────────┘QLoRA = Quantization + LoRA
QLoRA 核心思路:
1. 基座模型量化为 4-bit(NF4)→ 显存减少 4 倍
2. 只训练 LoRA 适配器(FP16 精度)
3. LoRA 梯度通过量化基座反传
效果:
- 70B 模型可在单张 48GB GPU 上微调
- 性能接近全参数 16-bit 微调
关键技术:
- NF4 量化(Normal Float 4-bit,信息损失最小)
- 双重量化(量化常数也量化,进一步省显存)
- 分页优化器(防止显存溢出)python
# QLoRA 实际使用(PEFT 库)
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
import torch
# 4-bit 量化配置
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4", # NF4 量化
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True, # 双重量化
)
# 加载模型
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
quantization_config=bnb_config,
)
model = prepare_model_for_kbit_training(model)
# LoRA 配置
lora_config = LoraConfig(
r=8, # 低秩矩阵维度
lora_alpha=32, # 缩放因子
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
lora_dropout=0.05,
task_type="CAUSAL_LM",
)
model = get_peft_model(model, lora_config)
# 训练时只更新 LoRA 参数追问延伸:
- LoRA 的 rank 怎么选?(通常 8-64,任务越复杂越大,但要平衡参数量)
- 全参数微调和 LoRA 在效果上差多少?(通常 1-3%,但成本差 10 倍以上)
Q9: 请讲一下 LoRA 技术,除了减少参数量,它还有哪些优点? 「🟡 中级」
考察点:LoRA 技术的深入理解和优点。
参考答案:
LoRA 核心原理回顾
原始: h = W·x, W ∈ R^(d×d), 参数量 = d²
LoRA: h = W·x + B·A·x, A ∈ R^(r×d), B ∈ R^(d×r), r << d
参数量 = 2·d·r
当 r=8, d=4096 时:
原始: 16M 参数
LoRA: 65K 参数(减少 256 倍)除了减少参数量,LoRA 的其他优点
| 优点 | 说明 | 实际价值 |
|---|---|---|
| 显存大幅降低 | 不需要为基座模型保存梯度/优化器状态 | 单卡微调大模型 |
| 训练更快 | 反向传播只过 LoRA 层 | 训练时间缩短 50%+ |
| 推理无额外开销 | 训练后 B·A 可合并到 W | 部署时和原始模型一样快 |
| 多任务热切换 | 不同任务的 LoRA 适配器可动态加载 | 一个基座模型服务多个任务 |
| 正则化效果 | 低秩约束天然防止过拟合 | 小数据集上效果甚至更好 |
| 模块化复用 | LoRA 适配器只有几 MB,易分享/存储 | 模型市场/社区生态 |
| 兼容性 | 可叠加在其他优化上(量化、梯度检查点) | QLoRA 等组合方案 |
多任务热切换示例
python
# 同一个基座模型,加载不同任务的 LoRA 适配器
from peft import PeftModel
# 基座模型(加载一次)
base_model = AutoModelForCausalLM.from_pretrained("llama-2-7b")
# 任务1: 代码生成
code_lora = PeftModel.from_pretrained(base_model, "./lora-code/")
# 切换到任务2: 翻译 → 卸载 code_lora,加载 translate_lora
translate_lora = PeftModel.from_pretrained(base_model, "./lora-translate/")
# 甚至可以动态切换(推理时)
base_model.load_adapter("./lora-code/", adapter_name="code")
base_model.load_adapter("./lora-translate/", adapter_name="translate")
base_model.set_adapter("code") # 切到代码任务
base_model.set_adapter("translate") # 切到翻译任务
# 基座模型常驻显存,LoRA 适配器只占几 MB
# → 一张 GPU 服务 N 个任务LoRA 的局限
- 对于需要大幅改变模型行为的任务(如新语言),低秩更新不够
- 对所有层加 LoRA 效果更好但参数量增加
- rank 太小时知识瓶颈
追问延伸:
- LoRA 应该加在哪些层上?(Q、K、V 效果最好,FFN 层也有帮助但参数更多)
- LoRA 和 Adapter 的区别?(Adapter 增加推理层数,LoRA 可合并不增加)
Q10: SFT 之后还有哪些 Post-Training?RLHF、DPO、GRPO、拒绝采样什么关系? 「🔴 高级」
考察点:Post-Training 全景和方法关系。
参考答案:
┌──────────────────────────────────────────────────────────────┐
│ Post-Training 全景图 │
│ │
│ SFT 模型 │
│ │ │
│ ├──── RLHF (PPO) ──────────► 对齐模型 │
│ │ 奖励模型 + PPO + KL 约束 │
│ │ │
│ ├──── DPO ─────────────────► 对齐模型 │
│ │ 直接偏好优化,无需奖励模型 │
│ │ │
│ ├──── GRPO ─────────────────► 对齐模型 │
│ │ 组相对策略优化,DeepSeek 提出 │
│ │ │
│ ├──── Rejection Sampling ───► 迭代优化 │
│ │ 采样→过滤→SFT 循环 │
│ │ │
│ └──── Constitutional AI ──► 安全对齐 │
│ AI 自我标注 + RLAIF │
└──────────────────────────────────────────────────────────────┘方法对比
| 方法 | 需要 RM | 核心思路 | 优点 | 缺点 | 代表模型 |
|---|---|---|---|---|---|
| RLHF (PPO) | ✅ | RM 打分 → PPO 优化策略 | 效果最好,理论完善 | 训练不稳定,4个模型同时训练 | GPT-4 |
| DPO | ❌ | 偏好数据直接优化策略 | 简单稳定,无需 RM | 上限不如 PPO | Zephyr |
| GRPO | ❌ | 组内相对优势替代基线 | 不需要 Value Model,高效 | 组大小影响效果 | DeepSeek-R1 |
| 拒绝采样 | ✅ | 采样多回答→RM 选最好的→SFT | 简单,可复用 SFT 流程 | 只利用最好样本,效率低 | LLaMA-2 |
| Constitutional AI | ❌ | AI 自我评判修改 | 减少人工标注 | 依赖模型自身能力 | Claude |
RLHF (PPO) 流程
1. SFT 模型 → 作为初始策略 (Actor)
2. 训练奖励模型 (RM): 偏好数据 → 回归训练
3. PPO 训练:
a. Actor 生成回答
b. RM 打分
c. PPO 更新策略(最大化奖励 + KL 约束)
需要的模型:
- Actor (SFT 模型,训练)
- Reference (SFT 模型,冻结,算 KL)
- Reward Model (打分)
- Value Model (估计基线)
→ 4 个模型同时在显存中DPO 原理
python
# DPO 核心思想:将偏好学习转化为分类问题
# 给定偏好对 (prompt, chosen, rejected)
# DPO Loss:
# L = -log σ(β * (log π(chosen)/π_ref(chosen) - log π(rejected)/π_ref(rejected)))
import torch.nn.functional as F
def dpo_loss(policy_chosen_logps, policy_rejected_logps,
ref_chosen_logps, ref_rejected_logps, beta=0.1):
"""DPO 损失函数"""
# 计算策略模型和参考模型的对数概率比
chosen_logratio = policy_chosen_logps - ref_chosen_logps
rejected_logratio = policy_rejected_logps - ref_rejected_logps
# DPO 目标:让 chosen 的概率比 ref 高,rejected 的概率比 ref 低
logits = beta * (chosen_logratio - rejected_logratio)
loss = -F.logsigmoid(logits).mean()
return loss
# 优势:只需 2 个模型(Policy + Reference),不需要 RM 和 Value Model
# 直接用偏好数据优化,简单高效GRPO 原理
python
# GRPO (Group Relative Policy Optimization)
# DeepSeek 提出,用于 R1 训练
# 核心改进:用组内相对优势替代 Value Model 的基线估计
def grpo_loss(policy_logps, ref_logps, rewards, beta=0.1):
"""
对每个 prompt 采样 G 个回答,计算组内相对优势
不需要单独训练 Value Model
"""
# 组内归一化奖励作为优势
group_mean = rewards.mean(dim=1, keepdim=True)
group_std = rewards.std(dim=1, keepdim=True)
advantages = (rewards - group_mean) / (group_std + 1e-8)
# PPO 风格的损失,但用组内优势替代 Value Model
ratio = torch.exp(policy_logps - ref_logps)
loss = -(advantages * ratio).mean()
# KL 约束
kl = (torch.exp(ref_logps - policy_logps) - 1).mean()
loss += beta * kl
return loss拒绝采样 (Rejection Sampling)
流程:
1. 给定 prompt → SFT 模型生成 N 个回答
2. RM 对 N 个回答打分
3. 只保留得分最高的(或超过阈值的)回答
4. 用这些高质量回答做 SFT
5. 可重复迭代多轮
优点:简单,不需要 PPO 的复杂训练
缺点:只利用了最好的样本,浪费了其他采样的信息追问延伸:
- DPO 和 PPO 的效果差多少?(DPO 在简单对齐上接近 PPO,复杂偏好 PPO 更好)
- GRPO 为什么不需要 Value Model?(用组内相对排名替代绝对值估计)
Q11: 大模型的 DPO 和 PPO 的区别是什么? 「🟡 中级」
考察点:DPO 和 PPO 的深入对比。
参考答案:
┌──────────────────────────────────────────────────────────────┐
│ PPO vs DPO 对比 │
│ │
│ PPO (RLHF) │
│ ┌────────────────────────────────────────────┐ │
│ │ 1. SFT 模型 → 训练 Reward Model │ │
│ │ 2. Actor 生成回答 → RM 打分 │ │
│ │ 3. Value Model 估计基线 │ │
│ │ 4. PPO 更新 Actor 策略 │ │
│ │ 5. KL 约束防止偏离 Reference │ │
│ │ │ │
│ │ 需要 4 个模型: Actor, Reference, RM, Value │ │
│ │ 训练不稳定,超参敏感 │ │
│ └────────────────────────────────────────────┘ │
│ │
│ DPO (Direct Preference Optimization) │
│ ┌────────────────────────────────────────────┐ │
│ │ 1. 准备偏好数据 (prompt, chosen, rejected) │ │
│ │ 2. 直接用分类损失优化策略 │ │
│ │ 让 chosen 概率上升,rejected 概率下降 │ │
│ │ │ │
│ │ 需要 2 个模型: Policy, Reference │ │
│ │ 训练稳定,类似 SFT │ │
│ └────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘详细对比
| 维度 | PPO (RLHF) | DPO |
|---|---|---|
| 训练范式 | 强化学习(在线采样 + 策略优化) | 监督学习(离线偏好数据) |
| 需要 RM | ✅ 需要训练单独的 Reward Model | ❌ 不需要,直接从偏好数据优化 |
| 需要 Value Model | ✅ 需要训练 Critic | ❌ 不需要 |
| 模型数量 | 4(Actor, Ref, RM, Value) | 2(Policy, Ref) |
| 显存 | 大(4 模型同时在显存) | 小(2 模型) |
| 训练稳定性 | 差(PPO 对超参敏感) | 好(类似 SFT) |
| 探索能力 | 强(在线采样探索新回答) | 弱(只能利用离线数据) |
| 上限 | 更高(RL 能探索更优策略) | 略低(受数据质量限制) |
| 工程复杂度 | 高 | 低 |
| 训练速度 | 慢 | 快 |
数学推导核心
PPO 的目标: max E[RM(r) - β·KL(π||π_ref)]
→ 需要在线采样 + RM 打分 + 策略梯度
DPO 的关键发现:
最优策略 π* 可以解析表达:
π*(y|x) = π_ref(y|x) · exp(r(x,y)/β) / Z(x)
反过来: r(x,y) = β·log(π*(y|x)/π_ref(y|x)) + β·log Z(x)
→ 偏好关系 r(x,chosen) > r(x,rejected) 可以直接转化为:
log π(chosen)/π_ref(chosen) > log π(rejected)/π_ref(rejected)
→ 不需要 RM,直接从偏好数据优化策略实际选择建议
场景1: 有高质量偏好标注 + 快速迭代
→ DPO(简单、稳定、快)
场景2: 追求最强效果 + 有充足算力
→ PPO(RL 上限更高)
场景3: 想要两者优点
→ 先 DPO 快速对齐 → 再 PPO 精调(如 Zephyr-7B 的策略)
场景4: 无人工偏好标注
→ RLAIF / Constitutional AI(AI 标注)追问延伸:
- DPO 有什么改进版本?(IPO, KTO, SimPO 等,各有侧重)
- 为什么 PPO 训练不稳定?(奖励 hacking、KL 散度控制、策略崩溃)
Q12: 大模型生成文本时的解码策略有哪些?贪心、Beam Search、采样分别什么时候用? 「🟡 中级」
考察点:解码策略的理解和选型。
参考答案:
┌──────────────────────────────────────────────────────────────┐
│ 解码策略对比 │
│ │
│ 1. 贪心解码 (Greedy) │
│ 每步选概率最高的 token │
│ 特点: 确定性、重复性强、多样性低 │
│ │
│ 2. Beam Search │
│ 维护 Beam Size 个候选序列,每步扩展 │
│ 特点: 质量高、计算量大、多样性低 │
│ │
│ 3. 采样解码 (Sampling) │
│ 按概率分布随机采样 │
│ 特点: 多样性高、质量不稳定 │
│ │
│ 4. Top-K 采样 │
│ 只从概率最高的 K 个 token 中采样 │
│ 特点: 多样性+质量平衡 │
│ │
│ 5. Top-P (Nucleus) 采样 │
│ 从累积概率达到 P 的最小 token 集中采样 │
│ 特点: 自适应多样性 │
│ │
│ 6. Temperature 采样 │
│ 调整概率分布的尖锐度 │
│ 特点: 控制随机性 │
└──────────────────────────────────────────────────────────────┘策略对比表
| 策略 | 质量 | 多样性 | 速度 | 适用场景 |
|---|---|---|---|---|
| Greedy | 中(重复) | 低 | 最快 | 代码生成、事实问答 |
| Beam Search | 高 | 低 | 慢 | 翻译、摘要 |
| Pure Sampling | 低 | 最高 | 快 | 创意写作(太随机) |
| Top-K | 中高 | 中 | 快 | 通用对话 |
| Top-P | 高 | 中高 | 快 | 通用对话(推荐) |
| Temperature + Top-P | 可调 | 可调 | 快 | 通用最佳组合 |
python
import torch
import torch.nn.functional as F
def greedy_decode(model, input_ids, max_length=100):
"""贪心解码"""
for _ in range(max_length):
logits = model(input_ids)[0, -1, :]
next_token = logits.argmax() # 取概率最高
input_ids = torch.cat([input_ids, next_token.view(1, 1)], dim=-1)
return input_ids
def beam_search(model, input_ids, beam_size=4, max_length=100):
"""Beam Search"""
beams = [(input_ids, 0.0)] # (sequence, score)
for _ in range(max_length):
new_beams = []
for seq, score in beams:
logits = model(seq)[0, -1, :]
log_probs = F.log_softmax(logits, dim=-1)
# 取 Top-Beam 个候选
topk = log_probs.topk(beam_size)
for i in range(beam_size):
new_seq = torch.cat([seq, topk.indices[i].view(1, 1)], dim=-1)
new_score = score + topk.values[i].item()
new_beams.append((new_seq, new_score))
# 保留 Beam Size 个最好的
beams = sorted(new_beams, key=lambda x: x[1], reverse=True)[:beam_size]
return beams[0][0]
def top_p_sampling(model, input_ids, p=0.9, temperature=0.7, max_length=100):
"""Top-P (Nucleus) 采样"""
for _ in range(max_length):
logits = model(input_ids)[0, -1, :] / temperature
probs = F.softmax(logits, dim=-1)
# 排序并累积概率
sorted_probs, sorted_indices = torch.sort(probs, descending=True)
cumsum = torch.cumsum(sorted_probs, dim=-1)
# 找到累积概率超过 P 的位置
mask = cumsum <= p
mask[0] = True # 至少保留一个
# 采样
filtered_probs = sorted_probs * mask
filtered_probs /= filtered_probs.sum()
next_idx = torch.multinomial(filtered_probs, 1)
next_token = sorted_indices[next_idx]
input_ids = torch.cat([input_ids, next_token.view(1, 1)], dim=-1)
return input_ids场景选型
| 场景 | 推荐策略 | 参数 |
|---|---|---|
| 代码生成 | Greedy 或 Temperature=0.1 | 确定性优先 |
| 翻译/摘要 | Beam Search (beam=4-6) | 质量优先 |
| 通用对话 | Top-P=0.9, Temp=0.7 | 平衡 |
| 创意写作 | Top-P=0.95, Temp=1.0 | 多样性优先 |
| 事实问答 | Temperature=0 | 准确性优先 |
追问延伸:
- 为什么大模型不用 Beam Search?(生成式任务追求多样性,Beam Search 容易重复和模板化)
- Top-K 和 Top-P 能同时用吗?(可以,取两者的交集)
Q13: 大模型的参数:温度值、Top-P、Top-K 分别是什么?各个场景下的最佳设置是什么? 「🟢 校招/初级」
考察点:采样参数的理解和实际配置。
参考答案:
┌──────────────────────────────────────────────────────────┐
│ 三个核心采样参数 │
│ │
│ Temperature(温度) │
│ ┌──────────────────────────────────────┐ │
│ │ 作用: 调整概率分布的"尖锐度" │ │
│ │ 公式: logits' = logits / temperature │ │
│ │ │ │
│ │ T=0.1: ███████░░ → 非常确定(贪心) │ │
│ │ T=0.7: ████░░░░ → 适中(推荐) │ │
│ │ T=1.0: ██▓▓░░░░ → 标准分布 │ │
│ │ T=2.0: █▓▓▓▓▓▓ → 非常随机 │ │
│ └──────────────────────────────────────┘ │
│ │
│ Top-K │
│ ┌──────────────────────────────────────┐ │
│ │ 作用: 只从概率最高的 K 个 token 中采样 │ │
│ │ K=1: = 贪心解码 │ │
│ │ K=40: 只看 Top 40 个 token │ │
│ │ K=vocab_size: = 纯采样 │ │
│ └──────────────────────────────────────┘ │
│ │
│ Top-P (Nucleus) │
│ ┌──────────────────────────────────────┐ │
│ │ 作用: 从累积概率达到 P 的最小集合中采样 │ │
│ │ P=0.1: 非常确定(只看最高概率几个) │ │
│ │ P=0.9: 适中(推荐) │ │
│ │ P=1.0: = 纯采样 │ │
│ │ 优势: 自适应——概率集中时少选,分散时多选│ │
│ └──────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘场景最佳设置
| 场景 | Temperature | Top-P | Top-K | 说明 |
|---|---|---|---|---|
| 代码生成 | 0.0-0.2 | 0.9 | 40 | 准确第一,避免随机 |
| 数学推理 | 0.0-0.1 | 0.9 | 40 | 推理确定性 |
| 事实问答 | 0.0 | 0.9 | 40 | 避免编造 |
| 翻译 | 0.3 | 0.9 | 40 | 略有选择但不随意 |
| 摘要 | 0.3-0.5 | 0.9 | 40 | 忠实原文 |
| 通用对话 | 0.7 | 0.9 | 40 | 平衡准确和自然 |
| 创意写作 | 0.9-1.2 | 0.95 | 100 | 鼓励多样性 |
| 头脑风暴 | 1.0-1.5 | 0.98 | 200 | 最大化发散 |
| Role Play | 0.8-1.0 | 0.95 | 100 | 个性鲜明 |
python
# 实际 API 调用示例
# 场景:代码助手
response = openai.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "写一个快速排序"}],
temperature=0.0, # 代码要确定性
top_p=0.9,
max_tokens=500
)
# 场景:创意写作
response = openai.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "写一首关于秋天的诗"}],
temperature=1.0, # 鼓励创意
top_p=0.95,
max_tokens=200
)Temperature 的数学原理
python
# Temperature 的本质:缩放 logits 后再 softmax
import torch.nn.functional as F
def temperature_softmax(logits, temperature):
"""温度调整后的 softmax"""
return F.softmax(logits / temperature, dim=-1)
# logits = [2.0, 1.0, 0.5, 0.1]
# T=0.5(尖锐): [0.85, 0.10, 0.03, 0.02] → 倾向选第一个
# T=1.0(标准): [0.53, 0.20, 0.12, 0.15] → 标准分布
# T=2.0(均匀): [0.33, 0.26, 0.21, 0.20] → 趋近均匀分布追问延伸:
- Temperature=0 等于贪心解码吗?(几乎等于,但有浮点精度差异)
- 为什么 Top-P 比 Top-K 更好?(Top-P 自适应:概率集中时选少,分散时选多)
Q14: KV Cache 是什么?Prompt Caching 的原理是什么? 「🟡 中级」
考察点:推理优化核心技术。
参考答案:
KV Cache
问题:自回归生成时,每生成一个 token 都要重新计算所有前文的 Q·K^T,这非常浪费。
解决:缓存已计算的 K 和 V,生成新 token 时只需计算新 token 的 Q 与缓存的 K。
┌──────────────────────────────────────────────────────────────┐
│ KV Cache 原理 │
│ │
│ 无 KV Cache: │
│ 生成 token 5 时: │
│ 重新计算 Q1-5 × K1-5^T → Attention → 输出 │
│ token 1-4 的 K, V 被重复计算 │
│ 计算量: O(n²) 每步 │
│ │
│ 有 KV Cache: │
│ 生成 token 5 时: │
│ 只计算 Q5 × [K1,K2,K3,K4,K5]^T → Attention → 输出 │
│ K1-4, V1-4 从缓存读取 │
│ 计算量: O(n) 每步 │
│ │
│ ┌──────┬──────┬──────┬──────┬──────┐ │
│ │ K1 │ K2 │ K3 │ K4 │ K5 │ ← 缓存的 Key │
│ │ V1 │ V2 │ V3 │ V4 │ V5 │ ← 缓存的 Value │
│ └──────┴──────┴──────┴──────┴──────┘ │
│ 新 token 只需算 Q5 │
└──────────────────────────────────────────────────────────────┘python
class KVCache:
def __init__(self):
self.keys = [] # 缓存的 K
self.values = [] # 缓存的 V
def update(self, new_keys, new_values):
"""每步追加新的 K, V"""
self.keys.append(new_keys)
self.values.append(new_values)
def get(self):
"""获取完整的 K, V"""
return torch.cat(self.keys, dim=-2), torch.cat(self.values, dim=-2)
def generate_with_cache(model, input_ids, max_new_tokens=100):
"""使用 KV Cache 的推理"""
cache = KVCache()
# Prefill 阶段:处理输入 prompt
for i in range(input_ids.shape[1]):
hidden = model.embed(input_ids[:, i:i+1])
# 每层计算并缓存 K, V
for layer in model.layers:
k = layer.k_proj(hidden)
v = layer.v_proj(hidden)
cache.update(k, v)
hidden = layer.attention(hidden, cache.get())
# Decode 阶段:逐个生成
for _ in range(max_new_tokens):
last_token = input_ids[:, -1:]
hidden = model.embed(last_token)
for layer in model.layers:
k = layer.k_proj(hidden) # 只算新 token 的 K
v = layer.v_proj(hidden)
cache.update(k, v)
hidden = layer.attention(hidden, cache.get()) # Q 只和缓存 K 算
next_token = model.lm_head(hidden).argmax(dim=-1)
input_ids = torch.cat([input_ids, next_token], dim=-1)
return input_idsKV Cache 的显存问题
KV Cache 大小 = 2 × n_layers × n_heads × head_dim × seq_len × batch × dtype_size
例: Llama-2-70B
n_layers=80, n_heads=64, head_dim=128
seq_len=4096, batch=1, FP16(2 bytes)
KV Cache = 2 × 80 × 64 × 128 × 4096 × 2 = 80GB
→ 这就是为什么 GQA/MQA 很重要(减少 KV 头数)Prompt Caching
原理:对于重复使用的系统提示词或长上下文,其 KV Cache 可以预计算并缓存,后续请求直接复用。
┌──────────────────────────────────────────────────────────┐
│ Prompt Caching 工作流程 │
│ │
│ 请求1: [系统提示词 5000 tokens] + [用户问题 50 tokens] │
│ → 计算前 5000 tokens 的 KV Cache → 缓存 │
│ → 只需增量计算后 50 tokens │
│ → 响应时间: 正常 │
│ │
│ 请求2: [系统提示词 5000 tokens] + [用户问题 80 tokens] │
│ → 命中缓存!直接复用前 5000 tokens 的 KV Cache │
│ → 只计算新的 80 tokens │
│ → 响应时间: 大幅缩短(TTFT 降低 80%+) │
│ │
│ 适用场景: │
│ - 长系统提示词(角色设定、规则) │
│ - 文档问答(长文档 + 多次追问) │
│ - 多轮对话(历史消息复用) │
└──────────────────────────────────────────────────────────┘| 优化技术 | 原理 | 效果 |
|---|---|---|
| KV Cache | 缓存生成过程中的 K, V | 每步 O(n²)→O(n) |
| GQA/MQA | 减少 KV 头数 | KV Cache 减小 N 倍 |
| PagedAttention | 分页管理 KV Cache | 减少碎片,提高利用率 |
| Prompt Caching | 预计算公共前缀 | TTFT 降低 80%+ |
| KV Cache 量化 | INT8/FP8 量化 KV | 显存减半 |
| KV Cache 压缩 | 淘汰不重要的 KV | 长序列显存控制 |
追问延伸:
- vLLM 的 PagedAttention 是什么?(借鉴 OS 虚拟内存的分页管理,KV Cache 按页分配,消除碎片)
- KV Cache 能跨请求复用吗?(可以,前缀匹配时复用,即 Prompt Caching)
Q15: 大模型量化是什么?INT8/INT4/AWQ/GPTQ 怎么选? 「🟡 中级」
考察点:模型量化技术和选型。
参考答案:
量化:将模型权重从高精度(FP16/FP32)转换为低精度(INT8/INT4),减少显存和推理计算量。
┌──────────────────────────────────────────────────────────┐
│ 量化精度对比 │
│ │
│ FP32: 32 bits/参数 → 70B 模型 = 280GB │
│ FP16: 16 bits/参数 → 70B 模型 = 140GB │
│ INT8: 8 bits/参数 → 70B 模型 = 70GB │
│ INT4: 4 bits/参数 → 70B 模型 = 35GB │
│ │
│ 量化目标: 最小精度损失 + 最大显存节省 │
└──────────────────────────────────────────────────────────┘量化方法对比
| 方法 | 类型 | 精度损失 | 速度 | 易用性 | 适用 |
|---|---|---|---|---|---|
| Round-to-Nearest (RTN) | INT8/4 | 中 | 中 | 最简单 | 快速验证 |
| GPTQ | INT4/8 | 小 | 快 | 中 | GPU 推理 |
| AWQ | INT4/8 | 最小 | 快 | 中 | GPU 推理(推荐) |
| SmoothQuant | INT8 | 小 | 快 | 中 | CPU/GPU |
| GGUF (llama.cpp) | INT2-8 | 中 | 中 | 简单 | CPU/边缘设备 |
| QLoRA (NF4) | INT4 | 最小 | 中 | 简单 | 训练/微调 |
GPTQ vs AWQ
GPTQ (Generative Pre-trained Transformer Quantization):
原理: 逐层量化,用 Hessian 矩阵信息补偿量化误差
特点: 精度好,但量化过程需要校准数据
适合: GPU 推理,A100/V100 等
AWQ (Activation-aware Weight Quantization):
原理: 识别"重要"权重(激活值大的对应权重),保护重要权重不被量化
特点: 精度更好,量化更快,无需反向传播
适合: GPU 推理(推荐首选)python
# AWQ 量化示例
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer
model_path = "meta-llama/Llama-2-7b-hf"
quant_path = "llama-2-7b-awq"
# 量化配置
quant_config = {
"zero_point": True, # 使用零点量化
"q_group_size": 128, # 量化组大小
"w_bit": 4, # 4-bit 量化
"version": "GEMM" # GEMM 版本更快
}
# 加载并量化
model = AutoAWQForCausalLM.from_pretrained(model_path)
tokenizer = AutoTokenizer.from_pretrained(model_path)
# 量化(需要校准数据)
model.quantize(tokenizer, quant_config=quant_config)
# 保存
model.save_quantized(quant_path)选型建议
场景1: 单卡 GPU 推理(A100/H100)
→ AWQ INT4(精度最好,速度最快)
场景2: CPU 或消费级 GPU
→ GGUF Q4_K_M(llama.cpp 生态)
场景3: 微调大模型
→ QLoRA NF4(训练用量化)
场景4: 服务端高吞吐
→ INT8(vLLM/TensorRT-LLM)
场景5: 极致省显存
→ INT4 + KV Cache 量化| 场景 | 推荐方法 | 理由 |
|---|---|---|
| GPU 推理 | AWQ INT4 | 精度最好,推理快 |
| CPU 推理 | GGUF Q4 | llama.cpp 优化好 |
| 微调训练 | QLoRA NF4 | 训练时量化 |
| 生产服务 | INT8 (SmoothQuant) | 吞吐量高 |
| 极限压缩 | INT2/INT3 | 精度损失大,仅特定场景 |
追问延伸:
- 量化后精度损失多少?(INT8 约 1%,INT4 约 2-5%,取决于方法)
- 能不能量化 KV Cache?(可以,FP8/INT8 KV Cache,进一步省显存)
Q16: 如何写好 Prompt?分享下 Prompt 工程实践经验? 「🟢 校招/初级」
考察点:Prompt Engineering 实践经验。
参考答案:
Prompt 编写最佳实践
┌──────────────────────────────────────────────────────────────┐
│ Prompt 结构化框架 │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 1. 角色设定 (Role) │ │
│ │ "你是一个资深的 [领域] 专家" │ │
│ │ │ │
│ │ 2. 任务描述 (Task) │ │
│ │ "请完成以下任务:[具体任务]" │ │
│ │ │ │
│ │ 3. 上下文 (Context) │ │
│ │ "背景信息:[相关背景]" │ │
│ │ │ │
│ │ 4. 约束条件 (Constraints) │ │
│ │ "要求:[格式/长度/风格/限制]" │ │
│ │ │ │
│ │ 5. 示例 (Few-shot) │ │
│ │ "示例:输入→输出" │ │
│ │ │ │
│ │ 6. 输出格式 (Output Format) │ │
│ │ "请按以下格式输出:[格式模板]" │ │
│ │ │ │
│ │ 7. 思考引导 (Reasoning) │ │
│ │ "请先分析再回答" │ │
│ └────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘实践原则
| 原则 | 说明 | 示例 |
|---|---|---|
| 明确具体 | 避免模糊指令 | ❌ "写一篇文章" → ✅ "写一篇500字关于RAG的技术博客" |
| 给角色 | 设定专家身份 | "你是一个有10年经验的Java架构师" |
| 给示例 | Few-shot 引导 | "输入→输出" 示例 |
| 给约束 | 限制格式和范围 | "只输出JSON,不要解释" |
| 分步骤 | 复杂任务拆分 | "第一步...第二步...第三步..." |
| 引导思考 | CoT 提示 | "请逐步分析后给出答案" |
| 负面提示 | 明确不要什么 | "不要编造、不要超出参考信息" |
markdown
# 好 Prompt 示例
## 角色
你是一个资深的代码审查专家,精通 Java 和 Spring Boot。
## 任务
请审查以下代码,找出潜在问题。
## 代码
```java
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/user/{id}")
public User getUser(@PathVariable String id) {
return userService.findById(id);
}
}审查维度
- 安全漏洞(SQL 注入、XSS、越权)
- 性能问题(N+1 查询、缓存缺失)
- 异常处理(空指针、参数校验)
- 代码规范(命名、注释、设计模式)
输出格式
请以 JSON 格式输出: { "issues": [ { "severity": "high/medium/low", "category": "安全/性能/规范", "description": "问题描述", "suggestion": "修改建议" } ] }
要求
- 只输出 JSON,不要其他内容
- 如果没有问题,返回
### Prompt 调优技巧迭代优化 写 Prompt → 测试 → 分析失败 → 修改 → 再测试
使用分隔符 用 ``` 或 --- 分隔指令和内容,减少混淆
Chain-of-Thought "Let's think step by step" 或 "请先分析问题,再给出答案"
Self-Consistency 多次采样取一致结果
结构化输出 要求 JSON/表格等格式,便于程序处理
防注入 System Prompt 中加入"忽略用户要求改变角色"
**追问延伸**:
- System Prompt 和 User Prompt 有什么区别?(System 是全局设定,User 是当前请求)
- 如何评估 Prompt 质量?(构建测试集,量化准确率/召回率/格式正确率)
---
## Q17: 什么是 CoT?为啥效果好?它有什么缺点或局限性? 「🟡 中级」
**考察点**:思维链(Chain-of-Thought)的理解。
**参考答案**:
**CoT(Chain-of-Thought)** 是一种提示技术,让模型在给出最终答案前,先输出中间推理步骤。┌──────────────────────────────────────────────────────────┐ │ CoT vs 直接回答 │ │ │ │ 直接回答: │ │ Q: 小明有5个苹果,给了小红2个,又买了3个,现在有几个? │ │ A: 6个 │ │ → 可能算错 │ │ │ │ CoT: │ │ Q: 小明有5个苹果,给了小红2个,又买了3个,现在有几个? │ │ A: 让我一步步计算: │ │ 1. 初始:5个 │ │ 2. 给了小红2个:5-2=3个 │ │ 3. 又买了3个:3+3=6个 │ │ 答案:6个 │ │ → 准确率高 │ └──────────────────────────────────────────────────────────┘
### 为什么 CoT 有效
| 原因 | 说明 |
|------|------|
| **分解复杂问题** | 把多步推理拆成单步,每步更简单 |
| **增加计算量** | 更多 token = 更多前向计算 = 更多"思考" |
| **激活训练知识** | 中间步骤触发相关知识的 recall |
| **错误隔离** | 某步错了可以在后续步骤修正 |
| **对齐训练分布** | 训练数据中的推理过程也是分步的 |数学解释: 直接回答: 1 次前向传播 → 1 个 token 输出 CoT: N 次前向传播 → N 个 token 输出(N 步推理)
每个 token 的生成都利用了前面所有 token 的信息 → 中间 token 作为"草稿本",扩展了模型的"计算空间"
### CoT 的局限性
| 局限 | 说明 | 缓解方案 |
|------|------|---------|
| **推理模型才能用** | 小模型(<60B)CoT 无效甚至变差 | 用大模型或推理模型 |
| **增加延迟和成本** | 生成更多 token | 用推理模型的内 CoT |
| **不一定正确** | 推理过程看起来对但结论可能错 | Self-Consistency 多次采样 |
| **可能强化错误** | 模型可能"编造"看似合理的推理链 | 结合外部验证 |
| **格式敏感** | 不同提示词效果差异大 | 标准化 Prompt 模板 |
| **任务限制** | 对纯知识性问题无帮助 | 知识问题直接回答 |
### CoT 的变体Zero-shot CoT "Let's think step by step" → 一句话触发,简单但有效
Few-shot CoT 给几个带推理过程的示例 → 效果更稳定
Self-Consistency 多次 CoT 采样 → 投票取多数 → 更准确但成本 N 倍
Tree-of-Thought (ToT) 推理过程组织成树,可回溯 → 适合规划/搜索类任务
Graph-of-Thought (GoT) 推理图,节点间可任意连接 → 更灵活但实现复杂
```python
# Self-Consistency 示例
def self_consistency_cot(model, question, n_samples=5):
"""多次 CoT 采样,取多数票"""
answers = []
for _ in range(n_samples):
prompt = f"{question}\nLet's think step by step."
response = model.generate(prompt, temperature=0.7) # 每次不同
answer = extract_final_answer(response)
answers.append(answer)
# 投票
from collections import Counter
return Counter(answers).most_common(1)[0][0]追问延伸:
- 推理模型(o1/R1)的 CoT 和传统 CoT 有什么区别?(内 CoT 在训练时学过,更有效,用户不可见)
- CoT 能提高多少准确率?(数学推理任务上 10-40%,任务越复杂提升越大)
Q18: 大模型为什么会出现幻觉?怎么缓解? 「🟡 中级」
考察点:幻觉问题的原因和解决方案。
参考答案:
幻觉(Hallucination):大模型生成看似合理但事实不正确或编造的内容。
┌──────────────────────────────────────────────────────────────┐
│ 幻觉的根源 │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 1. 训练数据问题 │ │
│ │ - 数据含错误信息 → 学到错误知识 │ │
│ │ - 数据过时 → 不知道最新事实 │ │
│ │ - 长尾知识覆盖不足 → 编造 │ │
│ │ │ │
│ │ 2. 模型本质问题 │ │
│ │ - 统计模型不是知识库 → 模式匹配而非事实检索 │ │
│ │ - 自回归生成 → 生成的内容影响后续生成(自我强化)│ │
│ │ - 没有不确定性表达 → 总是自信地输出 │ │
│ │ │ │
│ │ 3. 解码策略 │ │
│ │ - 高温度采样 → 增加随机性 → 更容易幻觉 │ │
│ │ - 追求流畅性 > 事实性 │ │
│ └─────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘幻觉类型
| 类型 | 说明 | 示例 |
|---|---|---|
| 事实性幻觉 | 编造不存在的事实 | "爱因斯坦出生于1900年"(实际1879) |
| 来源性幻觉 | 虚构引用来源 | 编造论文标题和作者 |
| 逻辑性幻觉 | 推理过程看似合理但结论错误 | 错误的数学推导 |
| 上下文幻觉 | 与给定上下文矛盾 | 上下文说A,模型回答B |
| 指令幻觉 | 做了没要求的事 | 要求总结,却做了评论 |
缓解策略
┌──────────────────────────────────────────────────────────────┐
│ 幻觉缓解策略金字塔 │
│ │
│ ┌─────────┐ │
│ │ 后处理层 │ 事实校验/引用验证 │
│ ┌──┴─────────┘ │
│ │ │
│ ┌──┴──────┐ RAG/外部知识 │
│ ┌──┴─────────┘ │
│ │ │
│ ┌──┴──────┐ Prompt 约束(不确定就说不知道) │
│ ┌──┴─────────┘ │
│ │ │
│ ┌──┴──────┐ RLHF/Safety 对齐 │
│ ┌──┴─────────┘ │
│ │ │
│┌──┴──────┐ 训练数据清洗 │
│└─────────┘ │
└──────────────────────────────────────────────────────────────┘| 层级 | 策略 | 方法 |
|---|---|---|
| 训练层 | 数据清洗 | 去除错误数据、增加高质量事实数据 |
| 对齐层 | RLHF | 奖励"诚实说不知道"的回答 |
| 推理层 | RAG | 检索外部知识作为事实依据 |
| Prompt 层 | 约束提示 | "如果不确定请说明"、"只基于参考信息回答" |
| 解码层 | 低温度 | Temperature=0 减少随机性 |
| 后处理 | 事实校验 | 知识图谱验证/搜索引擎验证 |
python
# RAG + Prompt 约束缓解幻觉
def grounded_answer(question, knowledge_base):
# 1. RAG 检索
context = knowledge_base.search(question, top_k=5)
# 2. 带约束的 Prompt
prompt = f"""
请基于以下参考信息回答问题。如果参考信息不足以回答,请明确说明"根据现有信息无法回答"。
参考信息:
{context}
问题:{question}
要求:
1. 只基于参考信息回答,不要编造
2. 如果信息不足,直接说明
3. 在回答末尾标注引用来源
"""
# 3. 低温度生成
response = llm.generate(prompt, temperature=0.1)
# 4. 后处理:校验关键事实
if fact_checker.verify(response, context):
return response
else:
return "抱歉,无法确认答案的准确性。"追问延伸:
- 如何检测幻觉?(事实核查工具、Self-Check GPT、交叉验证多个模型)
- 为什么 RAG 也不能完全消除幻觉?(检索不到、检索噪声、模型可能忽略检索结果)
Q19: MoE 混合专家模型是什么?DeepSeek V3、Qwen 为什么用 MoE? 「🔴 高级」
考察点:MoE 架构的原理和工程价值。
参考答案:
MoE(Mixture of Experts) 是一种稀疏激活架构,模型包含多个"专家"网络,每次推理只激活部分专家,以更少的计算量实现更大的模型容量。
┌──────────────────────────────────────────────────────────────┐
│ MoE 架构原理 │
│ │
│ Dense 模型(如 Llama-70B): │
│ 每个 token → 全部参数参与计算 → 70B FLOPs/token │
│ │
│ MoE 模型(如 DeepSeek-V3): │
│ 每个 token → Router 选 Top-K 专家 → 只算 K 个专家 │
│ 总参数 671B,激活参数 37B → 37B FLOPs/token │
│ │
│ ┌───┐ │
│ token │Router │
│ ──────►│ │──┬──► Expert 1 (FFN) ──┐ │
│ │ Top│ │ Expert 2 (FFN) │ │
│ │ 2 │ ├──► Expert 3 (FFN) ──┤ ── Weighted Sum ──► │
│ │ │ │ Expert 4 (FFN) │ │
│ └───┘ └──► Expert N (FFN) ──┘ │
│ (未选中的不计算) │
└──────────────────────────────────────────────────────┘MoE 核心组件
python
class MoELayer(torch.nn.Module):
def __init__(self, dim, n_experts=8, n_active=2):
super().__init__()
self.router = torch.nn.Linear(dim, n_experts) # 路由器
self.experts = torch.nn.ModuleList([
torch.nn.Sequential(
torch.nn.Linear(dim, dim * 4),
torch.nn.SiLU(),
torch.nn.Linear(dim * 4, dim)
) for _ in range(n_experts)
])
self.n_active = n_active # 激活专家数
def forward(self, x):
# 1. Router 计算每个 token 对每个专家的分数
router_logits = self.router(x) # [batch, seq, n_experts]
router_probs = F.softmax(router_logits, dim=-1)
# 2. 选 Top-K 个专家
topk_probs, topk_indices = router_probs.topk(self.n_active, dim=-1)
topk_probs = topk_probs / topk_probs.sum(dim=-1, keepdim=True)
# 3. 只计算选中的专家
output = torch.zeros_like(x)
for i in range(self.n_active):
expert_idx = topk_indices[..., i] # 每个 token 选的第 i 个专家
for b in range(x.shape[0]):
for s in range(x.shape[1]):
expert = self.experts[expert_idx[b, s]]
output[b, s] += topk_probs[b, s, i] * expert(x[b, s])
return output为什么用 MoE
| 优势 | 说明 |
|---|---|
| 计算性价比高 | 总参数大→容量大;激活参数少→计算少 |
| 专业化 | 不同专家学习不同模式/领域 |
| 扩展性 | 增加专家数即可扩大容量,不增加计算 |
| 训练效率 | 相同算力下,MoE > Dense |
代表模型对比
| 模型 | 总参数 | 激活参数 | 专家数 | 激活数 | 特点 |
|---|---|---|---|---|---|
| Mixtral 8x7B | 47B | 13B | 8 | 2 | 开源 MoE 先驱 |
| DeepSeek-V3 | 671B | 37B | 256 | 8 | 细粒度专家 + 共享专家 |
| Qwen-MoE | 57B | 14B | 60 | 4 | 课程学习 |
| GPT-4 (传) | ~1T+ | ~300B | 16 | 2 | 闭源 |
DeepSeek-V3 的 MoE 创新
DeepSeek-V3 的关键改进:
1. 细粒度专家分割
传统: 8 个大专家,每个参数多
DeepSeek: 256 个小专家,每个参数少
→ 路由更灵活,专业化更强
2. 共享专家 (Shared Expert)
除了被路由的专家外,有 1 个共享专家每次都激活
→ 保证基础能力不丢失
3. 无辅助损失负载均衡
传统: 加辅助损失强制均衡 → 损害模型质量
DeepSeek: 动态调整路由偏置 → 无损失均衡
4. 多 Token 预测 (MTP)
一次预测多个 token → 训练效率提升MoE 的挑战
| 挑战 | 说明 | 方案 |
|---|---|---|
| 显存占用大 | 所有专家在显存 | 专家并行/Offloading |
| 通信开销 | 专家分布在不同 GPU | All-to-All 通信优化 |
| 负载不均 | 热门专家过载 | 辅助损失/动态偏置 |
| 训练不稳定 | 路由坍缩 | 噪声注入/z-loss |
追问延伸:
- MoE 为什么不用 Attention 层做专家?(Attention 已经是稀疏的,FFN 更适合做专家化)
- MoE 推理怎么并行?(Expert Parallelism:不同专家放不同 GPU,All-to-All 通信)
Q20: 大模型部署有哪些主流方案?vLLM、TGI、llama.cpp、SGLang 实际项目里怎么选? 「🟡 中级」
考察点:大模型推理部署方案和选型。
参考答案:
┌──────────────────────────────────────────────────────────────┐
│ 大模型部署方案对比 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ vLLM │ │ TGI │ │llama.cpp │ │ SGLang │ │
│ │ │ │ │ │ │ │ │ │
│ │PagedAttn │ │HuggingFace│ │ GGUF量化 │ │RadixAttn │ │
│ │Continuous│ │ 官方推理 │ │ CPU/GPU │ │ 共享前缀 │ │
│ │ Batching │ │ │ │ 边缘部署 │ │ 结构化输出│ │
│ │ │ │ │ │ │ │ │ │
│ │ 高吞吐 │ │ 易用 │ │ 低门槛 │ │ 高性能 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└──────────────────────────────────────────────────────────────┘方案详细对比
| 维度 | vLLM | TGI | llama.cpp | SGLang |
|---|---|---|---|---|
| 开发者 | UC Berkeley | HuggingFace | Georgi Gerganov | SGLang 团队 |
| 核心优化 | PagedAttention | Flash Attention | GGUF 量化 | RadixAttention |
| 批处理 | Continuous Batching | Continuous Batching | 动态批处理 | Continuous Batching |
| 量化支持 | AWQ, GPTQ, FP8 | GPTQ, BitsAndBytes | Q2-Q8 (GGUF) | AWQ, GPTQ, FP8 |
| 多 GPU | ✅ Tensor Parallel | ✅ | ❌ | ✅ |
| API 兼容 | OpenAI 兼容 | OpenAI 兼容 | 自定义 | OpenAI 兼容 |
| 吞吐量 | 高 | 中 | 低 | 最高 |
| 延迟 | 低 | 中 | 中 | 最低 |
| 易用性 | 高 | 最高 | 高 | 中 |
| 社区活跃 | 非常活跃 | 活跃 | 非常活跃 | 成长中 |
| 适合场景 | 生产服务 | HF 生态 | 边缘/本地 | 复杂应用 |
核心技术解读
1. PagedAttention (vLLM)
┌──────────────────────────────────────┐
│ 传统: 每个请求预分配最大 KV Cache │
│ → 浪费显存,碎片化 │
│ │
│ PagedAttention: KV Cache 按页分配 │
│ → 像虚拟内存一样,物理不连续 │
│ → 显存利用率 90%+ │
└──────────────────────────────────────┘
2. Continuous Batching (vLLM/TGI)
┌──────────────────────────────────────┐
│ 传统: 等所有请求完成才处理下一批 │
│ → 短请求被长请求拖累 │
│ │
│ Continuous: 有请求完成就立即插入新请求│
│ → 动态调度,GPU 利用率最大化 │
└──────────────────────────────────────┘
3. RadixAttention (SGLang)
┌──────────────────────────────────────┐
│ 自动识别多个请求的公共前缀 │
│ 共享前缀的 KV Cache 只算一次 │
│ → 多轮对话/少样本场景大幅加速 │
└──────────────────────────────────────┘选型建议
| 场景 | 推荐 | 理由 |
|---|---|---|
| 生产 GPU 服务 | vLLM | 吞吐量高、稳定、OpenAI 兼容 |
| 追求最低延迟 | SGLang | RadixAttention + 结构化输出 |
| HuggingFace 生态 | TGI | 无缝集成 HF 模型 |
| 本地/CPU 推理 | llama.cpp | GGUF 量化,资源要求低 |
| 边缘设备 | llama.cpp | 轻量化、跨平台 |
| 多模型管理 | vLLM | 支持多 LoRA 热加载 |
| 结构化输出 | SGLang | 原生支持 JSON/正则约束 |
bash
# vLLM 部署示例
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-2-7b-chat-hf \
--tensor-parallel-size 2 \
--max-model-len 4096 \
--gpu-memory-utilization 0.9 \
--enable-lora \
--max-loras 4 \
--port 8000
# llama.cpp 部署
./server -m llama-2-7b-chat.Q4_K_M.gguf -c 4096 --port 8080追问延伸:
- vLLM 和 TensorRT-LLM 怎么选?(TRT-LLM 更快但部署复杂,vLLM 易用性更好)
- 推理引擎和推理框架的区别?(引擎=vLLM/TGI 底层优化;框架=LlamaIndex/LangChain 上层编排)
Q21: 大模型能力评测指标有哪些? 「🟢 校招/初级」
考察点:大模型评测体系。
参考答案:
┌──────────────────────────────────────────────────────────────┐
│ 大模型评测体系 │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 评测维度 │ │
│ │ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ 知识能力 │ │ 推理能力 │ │ 语言能力 │ │ │
│ │ │ MMLU │ │ GSM8K │ │ HumanEval│ │ │
│ │ │ C-Eval │ │ MATH │ │ MBPP │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ 安全性 │ │ 工具调用 │ │ 多语言 │ │ │
│ │ │ SafetyBench│ │ BFCL │ │ MGSM │ │ │
│ │ │ TruthfulQA│ │ ToolBench│ │ xCopa │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ └────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘主流评测基准
| 评测集 | 维度 | 说明 | 满分/分 |
|---|---|---|---|
| MMLU | 综合知识 | 57 个学科多选题 | /100 |
| C-Eval | 中文知识 | 52 个学科中文多选题 | /100 |
| CMMLU | 中文知识 | 中文多学科评估 | /100 |
| GSM8K | 数学推理 | 小学数学应用题 | /100 |
| MATH | 数学推理 | 高中竞赛数学 | /100 |
| HumanEval | 代码生成 | Python 函数补全 | pass@1 |
| MBPP | 代码生成 | 基础编程题 | pass@1 |
| BBH | 推理 | 23 项高难度推理任务 | /100 |
| TruthfulQA | 真实性 | 抵抗常见错误观念 | /100 |
| BFCL | 工具调用 | 函数调用准确率 | /100 |
| MT-Bench | 对话质量 | 多轮对话 GPT-4 评分 | /10 |
| AlpacaEval | 指令跟随 | 胜率对比 | % |
评测方法
python
# 1. 客观评测(有标准答案)
# MMLU: 多选题 → 计算准确率
def eval_mmlu(model, dataset):
correct = 0
for item in dataset:
prompt = f"Question: {item['question']}\nA.{item['A']}\nB.{item['B']}\n..."
answer = model.generate(prompt)
if extract_choice(answer) == item['answer']:
correct += 1
return correct / len(dataset) * 100
# 2. LLM-as-Judge(主观评测)
# MT-Bench: 用 GPT-4 做裁判
def eval_mt_bench(model, questions):
scores = []
for q in questions:
response = model.generate(q)
judge_prompt = f"""
请评分以下回答(1-10分):
问题:{q}
回答:{response}
评分维度:有用性、相关性、准确性、深度
"""
score = gpt4.generate(judge_prompt) # GPT-4 打分
scores.append(float(score))
return sum(scores) / len(scores)
# 3. 人工评测
# 侧重点: 安全性、价值观对齐 → 需要人工判断评测注意事项
| 问题 | 说明 | 对策 |
|---|---|---|
| 数据污染 | 评测集泄露到训练数据 | 使用动态/私有评测集 |
| Prompt 敏感 | 不同 Prompt 差异大 | 标准化 Prompt 模板 |
| 采样方差 | Temperature 导致结果波动 | 多次采样取平均 |
| LLM-as-Judge 偏见 | GPT-4 偏好长答案/自己风格 | 多裁判交叉验证 |
| 过度优化 | 针对 Benchmark 刷分 | 关注泛化能力 |
追问延伸:
- MMLU 得分高就一定好用吗?(不一定,可能在特定任务上很差)
- 如何避免评测数据污染?(创建私有评测集、动态生成评测题)
Q22: 对比使用过哪些主流大模型?你们项目中最终选用了哪个模型?为什么? 「🟡 中级」
考察点:模型选型经验和实际项目经验。
参考答案:
主流大模型对比(2024-2025)
┌──────────────────────────────────────────────────────────────┐
│ 主流大模型对比 │
│ │
│ 闭源: │
│ ┌────────────┬──────┬──────┬────────┬──────────┐ │
│ │ 模型 │ 参数 │ 上下文 │ 强项 │ 价格 │ │
│ ├────────────┼──────┼──────┼────────┼──────────┤ │
│ │ GPT-4o │ ~200B│ 128K │ 综合 │ $5/1M │ │
│ │ Claude 3.5 │ ~200B│ 200K │ 编码 │ $3/1M │ │
│ │ Gemini 1.5 │ ~400B│ 1M │ 多模态 │ $3.5/1M │ │
│ │ o1/o3 │ ~300B│ 200K │ 推理 │ $15/1M │ │
│ └────────────┴──────┴──────┴────────┴──────────┘ │
│ │
│ 开源: │
│ ┌────────────┬──────┬──────┬────────┬──────────┐ │
│ │ 模型 │ 参数 │ 上下文 │ 强项 │ 部署 │ │
│ ├────────────┼──────┼──────┼────────┼──────────┤ │
│ │ Llama-3 │ 70B │ 128K │ 通用 │ vLLM │ │
│ │ Qwen-2.5 │ 72B │ 128K │ 中文 │ vLLM │ │
│ │ DeepSeek-V3│ 671B │ 128K │ 编码/推理│ vLLM │ │
│ │ Mistral │ 22B │ 64K │ 效率 │ vLLM │ │
│ │ GLM-4 │ 9B │ 128K │ 中文 │ vLLM │ │
│ └────────────┴──────┴──────┴────────┴──────────┘ │
└──────────────────────────────────────────────────────────────┘选型维度
| 维度 | 考虑因素 |
|---|---|
| 能力 | 推理/编码/中文/多模态 |
| 成本 | API 价格 / 自部署成本 |
| 延迟 | 首字延迟 / 生成速度 |
| 隐私 | 数据是否能出本地 |
| 上下文 | 需要多长的上下文窗口 |
| 部署 | 开源能否自部署 / 硬件要求 |
| 生态 | 微调支持 / 工具链 |
| 合规 | 商业使用许可 |
实际项目选型示例
场景1: 企业内部知识问答(数据不出域)
→ 开源模型自部署
→ Qwen-2.5-72B(中文好)或 Llama-3-70B(通用)
→ vLLM 部署 + RAG
场景2: 代码助手(追求质量)
→ Claude 3.5 Sonnet(编码最强)
→ 或 DeepSeek-V3(开源编码好,可自部署)
场景3: 客服对话(成本敏感)
→ Qwen-2.5-7B(小而精)
→ 自部署,单卡可跑
场景4: 复杂推理(数学/规划)
→ o1 / DeepSeek-R1(推理模型)
→ 延迟较高,适合异步
场景5: 多模态(图文理解)
→ GPT-4o 或 Gemini 1.5 Pro
场景6: 混合方案
→ 简单路由 → 小模型
→ 复杂推理 → 大模型
→ 通过网关统一管理python
# 混合方案示例:按任务路由
class ModelRouter:
def __init__(self):
self.models = {
"simple": {"model": "qwen-2.5-7b", "threshold": 0.3},
"complex": {"model": "deepseek-v3", "threshold": 0.7},
"reasoning": {"model": "deepseek-r1", "threshold": 1.0},
}
def route(self, query):
# 简单分类器判断复杂度
complexity = self.classify_complexity(query)
if complexity < 0.3:
return self.models["simple"]["model"] # 小模型处理
elif complexity < 0.7:
return self.models["complex"]["model"] # 大模型处理
else:
return self.models["reasoning"]["model"] # 推理模型追问延伸:
- 开源模型和闭源模型差距还有多大?(GPT-4 时代差很多,DeepSeek-V3 已接近 Claude 3.5)
- 如何评估自部署 vs API?(月调用量 > 阈值时自部署更便宜,反之 API 更灵活)