My Blog
Agent 学习

Agent 学习第 5 周:大语言模型初探

从理解模型跨到生成模型:Causal Mask、SFT 与 Label Mask、五种采样策略、ICL 与 CoT,以及 API 断路器。

发布于 2026/9/11更新于 2026/9/11阅读约 20 分钟
只问这篇文章 →

第五周是整个课程的分水岭——从「理解模型」跨到「生成模型」。

内容很密:BERT 为什么做不了生成、Causal Mask 的本质、SFT 到底改变了什么、五种采样策略、ICL 与 CoT、Scaling Law,最后还有 API 调用的工程实践(断路器模式)。

作业是写一个单向语言模型(week5/task/单向语言模型.py),把 Causal Mask 和自回归生成完整跑一遍。


一、BERT 的三宗罪

为什么不能直接拿 BERT 做生成?三个原因,一个比一个致命:

① 预训练目标不匹配

MLM 训练的是「完形填空」——给定双向上下文,填中间的洞。 生成任务需要的是「续写」——给定前文,预测下一个词。

② 注意力是双向的

BERT 里每个位置都能看到所有位置,包括它后面的词。生成时第 t 步不该看到 t+1 以后的内容,否则等于提前看答案。

③ 输出长度固定

MLM 的预测位置数 = 输入里 [MASK] 的个数,由输入结构决定。而真正的生成要模型自己决定:生成多长、什么时候停。

一句话:BERT 的架构和训练目标是为「理解」优化的,不是「生成」。

改造需要做三件事:预训练目标换成 next-token、禁止看到未来的词、一个一个往外输出。


二、Attention Mask:换个矩阵就换个模型

这是我这周觉得最漂亮的一个点。

Mask 作用在哪一步

① scores  = Q·Kᵀ
② weights = softmax(scores/√d)     ← Mask 加在这一步之前
③ output  = weights·V

把不允许看的位置的 score 设为 −∞,过完 softmax 权重就趋近于 0,这个位置彻底看不见了。

直觉上,Mask 就是 Attention 的「权限控制」。

Mask 的本质是一个 L×L 矩阵

行 i = Query 位置,列 j = Key 位置
M[i][j] = 0  →  第 i 个 token 可以看见第 j 个
M[i][j] = 1  →  屏蔽(score = −∞)

然后是关键的一句:

同一个 Transformer 架构,换一个矩阵就能从「理解模型」变成「生成模型」。

全 0        →  完全双向     →  BERT
下三角      →  因果生成     →  GPT / Llama
自定义      →  前缀 / 局部  →  T5 / GLM

Causal Mask 的实现

mask = torch.tril(torch.ones(L, L))          # j ≤ i 处为 1
scores = scores.masked_fill(mask == 0, -inf)

第 i 行就是第 i 个 token 能看见的位置。每新生成一个 token,下面就多出一行。

自回归生成

「今天 天气」 → 前向一次 → 预测「真」
「今天 天气 真」 → 再前向 → 预测「好」
… 直到输出 <EOS> 才停

有个重要推论:Causal Mask 保证前文的 embedding 不受后续 token 影响,所以前面算过的表示是稳定可复用的——这正是 KV Cache 能成立的前提。


三、SFT:从「续写机」到「问答助手」

Base Model 到底学到了什么

它的训练目标只有一个:给定前 t 个 token 预测第 t+1 个。学到的是语料的分布规律。

所以有个很经典的现象:

输入:什么是人工智能?
输出:什么是机器学习?什么是深度学习?什么是神经网络?……

它没有回答,而是继续列问题。

原因很朴素——语料里 FAQ 标题、目录、问题集这类文本,问题后面接的经常还是问题。它在忠实地续写。

Base Model 是一个概率续写器,不是一个问答助手。

SFT 怎么做

数据:大量(指令, 回答)成对数据,只需几万到几十万条,远小于预训练的万亿 token。

用特殊 token 标注角色:

<|user|> … <|assistant|> … <|end|>

训练目标完全不变,仍然是 next-token prediction,没有引入任何新结构。

关键技巧:Label Mask

labels = [-100] * len(prompt) + input_ids[len(prompt):]

user 部分只作为输入、不计算 loss;assistant 部分才计算 loss 并反向传播。-100 的位置被 ignore_index 跳过。

为什么?我们只想让模型学会「如何回答」,而不是让它背熟用户的提问。

两个阶段的信号密度对比:

labels有效信号密度
预训练input_ids 整体左移一位100%
SFTprompt 段全部 −100≈ 50%

SFT 到底改变了什么

没变变了
模型架构(同一个 Transformer)输出分布(学会在指令后输出回答)
Causal Mask对话协议(角色轮换)
next-token 目标停止行为(学会生成 `<
大部分事实知识回答风格(结构化、有条理)

一句话记忆:知识来自预训练,回答的形式来自 SFT。


四、采样策略:五种

模型每步输出的是 logits(维度 = 词表大小 5万~15万)→ softmax → 概率分布 → 采样出 1 个 token。

两个极端都不好:永远选最大(确定但死板重复)、完全按概率随机(多样但长尾累积概率大,容易胡言乱语)。工程就是在中间找平衡。

策略做法适合
Greedy每步取 argmax代码生成、数学推理
Beam Search保留 k 条最优路径机器翻译、摘要
Temperaturesoftmax(logits/T)调节整体发散程度
Top-K只从概率最高的 K 个里采切长尾
Top-P(Nucleus)取累积概率首次达 P 的最小集合通用默认

Temperature 的方向:T↓ 分布更尖锐、更保守;T↑ 分布更均匀、低概率词也可能被选中。

Top-P 比 Top-K 好在哪:K 是固定数,不适配分布形态——尖锐分布 K=50 太宽松,平坦分布 K=50 又太紧。而 Top-P 的候选数会随分布自动调整:尖锐时只留 1~2 个,平坦时自动放宽到 10+ 个。


五、ChatGPT 改变了什么

课上用了一个比喻:弓箭与导弹,不是量变是质变。

传统 NLP / BERTLLM
任务一个模型 = 一个任务一个模型 ≈ 任意任务
数据必须有标注才能学用自然语言描述即可
新任务需重新训练零样本泛化
使用者需要 ML 工程师普通人就能驱动

In-Context Learning

模型不更新任何权重,仅凭 Prompt 里的几个示例,就能学会全新任务。

传统微调:准备标注数据 → 改结构 → 反向传播 → 部署 → 换任务从头再来
ICL:     任务描述 + 几个示例写进 Prompt → 直接给出答案

三种形式:Zero-shot(不给示例)、One-shot(一个,显著提升格式遵从)、Few-shot(2~8 个,进一步提精度)。

它的革命性在于打破了「训练数据 = 能力」这条铁律。

Chain-of-Thought

在示例里带上中间推理步骤,引导模型展示完整推理过程。

为什么有效:

  1. 复杂问题分解 —— 避免一次跳跃到答案,每步只处理简单子问题
  2. 推理过程可纠错 —— 中间步骤生成出来后成为后续 token 的上下文,模型能自我校正
  3. 顺应自回归的工作方式 —— LLM 本就是逐 token 生成,写出推理链正合适

Zero-shot CoT 更神奇:只在 Prompt 末尾加一句「Let's think step by step」,模型就自动展开推理链。

有个关键限制:CoT 只在足够大的模型(>100B)上有效,小模型用 CoT 有时反而更差。

Scaling Law

L(N) = (Nc/N)^α        α ≈ 0.076

参数量 N、数据量 D、算力 C 与 Loss 之间存在稳定幂律,而且跨越多个数量级都成立。

伴随而来的是涌现能力——某些能力在小模型上几乎为零,参数量超过某个阈值后突然出现,而不是平滑提升。


六、工程实践:API 的健康检测与断路器

这一节是我最意外的收获——没想到大模型课会讲这么工程的东西,但它确实是实际项目里绕不开的。

核心矛盾

LLM API 和普通 HTTP 服务一样会出故障:限流(429)、宕机、网络抖动、Key 失效。难点在于:

  • 单次失败不能立刻切走(可能只是偶发抖动)
  • 持续失败必须快速切走(避免每次都等超时)
  • 切走后还要能自动恢复(不能永远用备用)

断路器的三态状态机

CLOSED(正常放行)
  │  连续失败 N 次
  ↓
OPEN(快速失败,不发任何网络请求)
  │  冷却 recovery_timeout 秒
  ↓
HALF_OPEN(只放行一条探针)
  ├─ 探针成功 → CLOSED
  └─ 探针失败 → 回到 OPEN,重置冷却

核心价值是「快速失败 Fail Fast」——不等 API 超时。

生产推荐参数:

failure_threshold = 3~5     太小容易误触发,太大切换太慢
recovery_timeout  = 60~120s 给对方足够的恢复时间
success_threshold = 2       防止偶发成功就过早关闭

为什么还需要主动探针

这个点很妙:断路器进入 OPEN 后,没有用户请求会到达这个 Provider,那段判断逻辑就不会被调用,它永远没机会转 HALF_OPEN。

所以要有个后台常驻任务定时探测。探针的三条设计原则:

  • 轻量 —— max_tokens=5,只验证可达性
  • 有代表性 —— 用和真实请求相同的 endpoint 和 model,而不是 /health(有些 Provider 根本没这个接口)
  • 静默 —— 成功就更新状态,不打扰用户

这周的收获

1. Mask 是一个被低估的设计。

「换一个 L×L 矩阵,同一个架构就从理解模型变成生成模型」——这句话让我重新理解了 Transformer 的通用性。架构没变,变的是信息可见性的约束。

2. SFT 没有引入任何新东西。

我原以为对话能力是训出来的某种「新结构」,实际上架构、目标、Mask 全没变,只是换了数据格式 + 加了 Label Mask。能力的形式变了,能力的来源没变。

3. 工程和算法一样重要。

断路器那一节是纯后端知识(我以前在 Java 项目里用过类似的东西),但它出现在大模型课里恰恰说明:把模型用起来,一半的工作量在模型之外。这对我这种后端转过来的人反而是优势。

下周开始进入具体任务——文本分类,做七组对照实验。

相关文章