++
This commit is contained in:
Executable
+202
@@ -0,0 +1,202 @@
|
||||
# 阶段一 · 小点 5:大模型基础原理
|
||||
|
||||
> 所属:阶段一 大模型基础与 Prompt 工程(Agent 核心根基)
|
||||
> 定位:做 Agent 不要求你手推反向传播,但你必须理解"模型为什么会这么表现"——为什么流式输出是一块块的、为什么上下文会超限、为什么调参会影响答案。理解了这几条,你写的 Agent 才不是瞎碰。
|
||||
|
||||
## 精简大纲
|
||||
|
||||
1. Transformer 核心思想:注意力机制通俗理解、自回归生成
|
||||
2. Token 计算方式、上下文窗口、截断 / 压缩策略
|
||||
3. 采样参数:temperature / top-p / top-k / max_tokens 与生产调优
|
||||
|
||||
## 学习内容详情
|
||||
|
||||
> **新一代模型现状(2025-2026)**:闭源侧 GPT-5 家族(2025 年 8 月发布:GPT-5 / GPT-5 mini / GPT-5 nano;API 定价输入 $1.25/百万 token、输出 $10/百万 token,mini 为 $0.25/$2)、Anthropic Claude 4 / 4.5 系列;开源侧 DeepSeek-V3/R1、Qwen3 等。⚠️ 模型迭代很快(截至 2026 已有 Claude 4.5、DeepSeek-V3.1、Qwen3-Max 等更新版本),具体型号与价格以官方最新为准;但本节学的是**原理层不变量**(上下文窗口 / 采样参数 / token 计费),它稳定不变,学习重点不受影响。
|
||||
|
||||
> ⏳ **短期可不深究**:具体哪个模型叫什么名、怎么定价、看哪些型号的细节第一遍不必背——模型迭代快、随时会过时,需要时查官方最新文档即可,别把时间花在记型号/价格上。
|
||||
|
||||
### 1. Transformer 与生成原理
|
||||
|
||||
#### 1.1 注意力机制在做什么
|
||||
|
||||
一句话版本:模型让每个词去计算"我该重点看句子里的哪些词"。比如读"它跑到河边喝水",模型要知道"它"指的是谁。注意力就是让"它"关联到正确的指代词。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[每个token] --> B[计算与其他token的关联权重]
|
||||
B --> C[重点加权关注相关词]
|
||||
C --> D[携带上下文语义 继续预测]
|
||||
```
|
||||
|
||||
- 这解释了模型**理解上下文**的能力来源。应用层不用深挖数学,但要清楚它带来的约束(上下文长度受限、对长文本处理有压力、指令遵循有波动)。
|
||||
|
||||
> 🔬 **深度理解 · 注意力机制(Attention)**:
|
||||
> 1. **本质**:让每个 token 为"预测自己"而给句子里其他 token 分配"关注权重",从而把**远距离相关**的信息拉近——解决"距离越远、语义关联越弱"的天然缺陷。
|
||||
> 2. **机制**:每个 token 被编码成 Query(我要找什么)/ Key(我是谁)/ Value(我携带什么信息);它拿自己的 Query 去和所有 token 的 Key 做点积打分,经 softmax 归一化成权重,再按权重加权求和所有 Value,得到融合上下文的表示;多头注意力=同时学多套这种"关注关系"。
|
||||
> 3. **为什么重要**:它是 Transformer 能整句并行处理(而非像 RNN 逐个串行)的关键,也是理解指代、推理逻辑的能力来源;注意力开销随文本长度平方式增长,这正是上下文窗口受限的深层原因。
|
||||
> 4. **易错点**:注意力是"加权平均",本身不含绝对位置(靠额外位置编码注入);应用层不必推数学,但要理解它带来的约束——长文本开销大、窗口有限,所以才有截断/压缩策略。
|
||||
|
||||
#### 1.2 自回归生成(流式输出的根本原因)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant M as 模型
|
||||
participant T as Token流
|
||||
M->>T: 根据已生成内容 预测下一个token A
|
||||
M->>T: 根据【A】再预测下一个token B
|
||||
M->>T: 根据【A,B】再预测下一个token C
|
||||
loop 持续到结束/达到max_tokens
|
||||
M->>T: 一个接一个地生成本token
|
||||
end
|
||||
```
|
||||
|
||||
- 模型**一次只预测一个 token**,拿"已生成内容"当条件去猜下一个。这就是为什么流式输出能一块块推给前端——它本来就是一个字一个字蹦出来的。
|
||||
|
||||
> 🔬 **深度理解 · 自回归生成**:
|
||||
> 1. **本质**:把"生成任意长文本"降维成"反复执行同一个条件概率预测"——`P(下一个token | 已生成的tokens + 输入上下文)`,一次只决定一个 token。
|
||||
> 2. **机制**:每个新 token 会拼回输入做"下一轮条件",所以它是严格从左到右、逐 token 推进的自回归结构;这正是流式输出"天生一字一字蹦"的根本原因(不是网络特意分片,而是生成就是逐 token 的)。
|
||||
> 3. **为什么重要**:理解这一点就能解释三个现象——为什么流式能低延迟输出(首 token 到得快)、为什么输出够长时会撞 `max_tokens` 截断、为什么截断会得到"残句"。Agent 里组织流式/控制输出长度都源于此。
|
||||
> 4. **易错点**:模型输出长度受 `max_tokens`(输出侧上限)约束,而上下文窗口是"输入+输出"总上限;"复读/提前断"常是 max_tokens 过小或触发保护所致,不是模型"坏"了。
|
||||
|
||||
> **应用层启示**:既然是一次吐一个,那"输出质量"和"max_tokens 给多少"就直接挂钩。输出太长被截断,答案可能残缺;太短提前断,则属于复读机触发保护。
|
||||
|
||||
### 2. Token 与上下文窗口
|
||||
|
||||
#### 2.1 Token 是什么
|
||||
|
||||
- **Token**:模型理解文本的最小单位。英文 1 词约 1.3 个 token;中文 1 字约 1~2 token;标点、空格也算。
|
||||
- **分词器**(如 OpenAI 的 tiktoken):把文本切成 token 的代码工具。要精准估算用量与成本,必须先过它。
|
||||
|
||||
```python
|
||||
# 估算 token 成本 (以 OpenAI tiktoken 为例)
|
||||
import tiktoken
|
||||
|
||||
enc = tiktoken.encoding_for_model("gpt-4o")
|
||||
text = "你好,我想开发一个 AI Agent。"
|
||||
tokens = enc.encode(text)
|
||||
print("token 列表:", tokens)
|
||||
print("token 数量:", len(tokens)) # 一句话中文可能十几甚至几十个token
|
||||
```
|
||||
|
||||
> ⚠️ 不能按"字数"估算,中文尤其要命。上线前用分词器实测,比你拍脑袋准得多。
|
||||
|
||||
> ⏳ **短期可不深究**:分词器内部的具体编码算法(BPE 合并规则、词表是怎么构建的)属于底层实现细节,第一遍会用官方 `tiktoken`/`tokenizers` 做估算即可,不必背规则,用到再深入。
|
||||
|
||||
#### 2.2 上下文窗口与两种应对策略
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[输入+输出 总token > 上下文窗口?] -->|是| B{怎么办?}
|
||||
B -->|最简单| C[滑动窗口截断: 丢最旧消息,留最近N轮]
|
||||
B -->|更聪明| D[上下文压缩: 把久远历史摘要成短文本]
|
||||
C --> E[生产常用组合: 近几轮原文 + 更早摘要]
|
||||
D --> E
|
||||
```
|
||||
|
||||
- **上下文窗口**:单次请求"输入 + 输出"最大 token 数(如 128k)。超限必须主动处理,否则请求直接报错。
|
||||
- **滑动窗口截断**:把最旧消息丢掉、保留最近 N 轮。最简单,但会失去早期记忆。
|
||||
- **上下文压缩**:让 LLM 把久远的历史总结成一段短摘要,用它替代原文。**生产标配是"近几轮原文 + 更早摘要"混合**,兼顾记忆与成本。
|
||||
|
||||
```python
|
||||
# 滑动窗口截断: 保留最近 N 条消息
|
||||
def trim_history(history: list[dict], keep: int = 10) -> list[dict]:
|
||||
"""超限时丢最旧的, 保留最近 keep 条(不含 system 系统消息)。"""
|
||||
system_msgs = [m for m in history if m["role"] == "system"] # 系统消息要保留
|
||||
rest = [m for m in history if m["role"] != "system"]
|
||||
return system_msgs + rest[-keep:] # 系统 + 最近 keep 条
|
||||
|
||||
history = [
|
||||
{"role": "system", "content": "你是助手"},
|
||||
{"role": "user", "content": "第1问"}, {"role": "assistant", "content": "第1答"},
|
||||
{"role": "user", "content": "第2问"}, {"role": "assistant", "content": "第2答"},
|
||||
{"role": "user", "content": "第3问"}, {"role": "assistant", "content": "第3答"},
|
||||
]
|
||||
print(trim_history(history, keep=2))
|
||||
# 保留 system + 最近2条(第3轮), 前两轮被丢弃
|
||||
```
|
||||
|
||||
#### 2.3 上下文压缩示例
|
||||
|
||||
```python
|
||||
def compress_old(history: list[dict], llm, age: int = 6):
|
||||
"""
|
||||
把超过 age 条的早期历史让 LLM 压成一段摘要, 再拼接最新几轮原文。
|
||||
这样既保留早期记忆, 又不撑爆上下文。
|
||||
"""
|
||||
if len(history) <= age:
|
||||
return history # 不够长, 不压缩
|
||||
old = history[:-age] # 早期这段
|
||||
recent = history[-age:] # 最近这段原文
|
||||
summary = llm(f"请把这段对话压缩成三句话摘要:{old}")
|
||||
return [{"role": "system", "content": f"早期对话摘要:{summary}"}] + recent
|
||||
```
|
||||
|
||||
### 3. 采样参数(生产经验值)
|
||||
|
||||
| 参数 | 用途 | 经验值 |
|
||||
|-|-|-|
|
||||
| temperature | 随机度(越高越"发散") | 工具调用/结构化 0~0.3;创意文案 0.7+ |
|
||||
| top-p | 累计概率前 p 的候选词采样 | 与 temperature 二选一调优,别同时乱叠 |
|
||||
| top-k | 只在前 k 名里采样 | 一般不叠加调整 |
|
||||
| max_tokens | 限制输出长度 | 成本闸门 + 防模型复读 |
|
||||
|
||||
> **关键原则**:结构化输出(抽 JSON / 工具调用)要把 temperature 调低(0~0.3),保证稳定可复现;创意类内容才调高。别真以为 temperature=1 就"锦上添花"。
|
||||
|
||||
> 🔬 **深度理解 · 采样参数(temperature / top-p / top-k)**:
|
||||
> 1. **本质**:语言模型输出的其实是一个"每个候选 token 的概率分布";采样参数决定"从这份分布里怎么挑下一个 token",是控制"确定性 vs 随机性"的旋钮。
|
||||
> 2. **机制**:temperature 通过重缩放 `softmax(logits / temperature)` 实现——越低分布越尖(高概率项更突出→更确定),越高越平(低概率项出货机会↑→更发散);top-p 取累计概率刚过 p 的最小候选集合再在其中采;top-k 只在前 k 名之间采。
|
||||
> 3. **为什么重要**:工具调用/JSON 结构化必须高确定性(temperature≈0),否则抽出的动作或参数飘忽导致程序解析崩溃;创意文案则需要温度放宽以增加多样性。合理调参就是在那条"稳定可复现 ↔ 发散有创意"的轴上取平衡。
|
||||
> 4. **易错点**:temperature 与 top-p 都在影响随机性,同时乱叠会双重缩放难以预期,通常二选一调优;temperature=0 近似 greedy 贪心取最高概率,但不是"绝对唯一",极端情况下仍有平局的取舍。
|
||||
|
||||
```python
|
||||
def build_llm_kwargs(task: str) -> dict:
|
||||
"""按任务返回合理的采样参数。"""
|
||||
if task == "structured": # 工具调用 / JSON 输出
|
||||
return {"temperature": 0.1, "max_tokens": 1024}
|
||||
if task == "coding": # 代码生成
|
||||
return {"temperature": 0.2, "max_tokens": 2048}
|
||||
return {"temperature": 0.8, "max_tokens": 512} # 创意文案
|
||||
```
|
||||
|
||||
## 本节自检
|
||||
|
||||
- [ ] 能估算一段中文文本的 token 量,并知道上下文超限的三种处理策略
|
||||
- [ ] 能针对不同任务(工具调用 vs 创意)设置合理采样参数
|
||||
- [ ] 能说出流式输出为什么"天生是一个 token 一个 token 蹦出来的"
|
||||
|
||||
## 本节配套思考题(快速入门的检验)
|
||||
|
||||
1. 为什么工具调用场景要把 temperature 调到接近 0?如果调到 1 会发生什么?
|
||||
2. 中文 100 字的对话,用 tiktoken 估算它大概多少 token?能不能直接用"字数"当 token 数?
|
||||
3. "滑动窗口截断"和"上下文压缩"各自的代价是什么?什么时候该用哪种?
|
||||
|
||||
## 本节常见面试题(深度解析)
|
||||
|
||||
> 针对本节核心知识的面试高频点,配合"本质+机制"式理解,能让你答得既有深度又有广度。
|
||||
|
||||
### 面试题 1:为什么模型是"一次只预测一个 token"的?它对流式输出和 max_tokens 有什么影响?
|
||||
- **面试官想考察**:是否理解自回归生成的本质,以及由此推导出流式、截断等现象,而非背结论。
|
||||
- **专业作答(含深度)**:
|
||||
1. **自回归本质**:生成文本被降维成反复执行条件概率 `P(下一个token | 已生成内容+输入)`,一次决定一个 token,新 token 再拼回输入继续。
|
||||
2. **流式的根本原因**:正因为逐 token 生成,服务端才能"生成一块回传一块",实现首字低延迟、前端打字机效果。
|
||||
3. **对 max_tokens 的影响**:输出是逐 token 累加的,输出侧有长度上限;超上限会被截断成"残句",提前断常是保护/上限过小所致。
|
||||
4. **工程启示**:Agent 组织流式输出、控制响应长度都要顺着"逐 token"这个事实来设。
|
||||
- **加分亮点 / 深度追问**:可提 KV Cache(已算过的上下文缓存以加速后续 token)、以及"输出 token 也占上下文窗口配额"这个常见误解。
|
||||
|
||||
### 面试题 2:temperature 和 top-p 都是控制随机性的,它们有何区别?为什么工具调用要把 temperature 调低?
|
||||
- **面试官想考察**:能否说清两者的作用层级与适用场景,理解"确定性高"对结构化输出的价值。
|
||||
- **专业作答(含深度)**:
|
||||
1. **temperature 的机制**:通过重缩放 logits 改变概率分布的"尖锐度"——低则更确定、高则更发散,属"全局重塑分布"。
|
||||
2. **top-p 的机制**:截取累计概率到 p 的最小候选集再采样,属"裁掉尾部低概率项",属于局部的候选集裁剪。
|
||||
3. **区别**:temperature 改变相对概率大小,top-p 决定"从哪些候选里挑";强行双占用会导致难以预期的叠加,一般二选一。
|
||||
4. **工具调用要低温**:工具名/参数必须稳,若温度高导致动作或参数飘忽,下游程序解析会崩;JSON 结构化同理。创意文案才需放宽温度增加多样性。
|
||||
- **加分亮点 / 深度追问**:可提 top-k 与两者的关系(同时卡 top-k 更激进)、固定输出场景的服务端默认参数(如 temperature 默认值因模型而异),以及 temperature=0 与 greedy 的细微区别。
|
||||
|
||||
### 面试题 3:上下文窗口有限,你的 Agent 会如何应对超长对话?截断和压缩各自的取舍是什么?
|
||||
- **面试官想考察**:是否理解"窗口是硬约束",能对两种主流策略做成本权衡。
|
||||
- **专业作答(含深度)**:
|
||||
1. **问题本质**:单次请求"输入+输出"总 token 不能超窗口,超限要么报错要么失真。
|
||||
2. **滑动窗口截断**:丢最旧消息留最近 N 轮。实现简单、省成本,但**丢失早期记忆**,上下文越长失真越明显。
|
||||
3. **上下文压缩**:把久远历史让 LLM 压成短摘要,较完整保留语义,"更聪明"却**每次压缩有延迟与成本**,且压缩本身也有精度损失。
|
||||
4. **生产标配**:近几轮原文 + 更早摘要的混合策略,兼顾记忆完整度与成本/延迟;对超长且高度相关的早期上下文(如代码现场)可能仍需摘要或外部记忆。
|
||||
- **加分亮点 / 深度追问**:可提上下文之外的外挂记忆(向量检索/摘要存档)作为"无限上下文"的折中,以及"分段/分页"、"只回传工具结果摘要而非原文"等 token 治理手法。
|
||||
Reference in New Issue
Block a user