这段文本和图片主要讲了两个核心概念:模型部署的权衡 和 两种核心架构的对比(Seq2Seq vs Transformer)。
作为一个 Android 工程师,你可以把这段内容理解为**“自然语言处理(NLP)领域的架构演进史”**,就像 Android 开发从早期的 MVC 演进到 MVP 再到现在的 MVVM/MVI 一样。
我用 Android 开发的术语来帮你拆解一下:
1. 选型与性能开销(左上角文本)
文本第一段在讨论**“Build Config”**的问题。
- 原文意思:在训练模型前,开发者要决定模型长什么样(架构)以及有多大(参数量)。
- Android 类比:
- 参数量 (Parameters):这就好比你的 APK 体积 和 运行时内存占用。
- 7B (70亿) 参数模型:像是一个经过 ProGuard 混淆优化过的轻量级库,勉强可以在高端 Android 手机的 NPU 上跑(端侧部署)。
- 175B (1750亿) 参数模型:像是一个巨大的服务端应用,根本不可能塞进手机里,必须通过 API 调云端接口。
- Latency (延迟):优化 Transformer 就像优化 RecyclerView 的滑动流畅度,不同的架构决定了响应速度。
2. 旧架构:Seq2Seq (RNN-based) —— "单线程流式处理"
看图中的上半部分 (Seq2Seq)。这是 2014-2017 年的主流技术(Google 翻译早期就用这个)。
- 工作原理:
- Encoder (编码器):像一个
InputStream,串行读取输入("How", "are", "you", "?")。它必须按顺序读,读完一个词,更新一下内部状态。
- Final hidden state (中间那个蓝框):这是关键瓶颈!它相当于把整个句子的含义压缩成了一个单一的
Context 对象或者一个 Bundle。
- Decoder (解码器):拿到这个
Bundle,开始生成翻译结果。
- Android 类比:
- 想象你有一个巨大的 JSON 文件。这个架构要求你必须从头读到尾,然后把整个文件的内容死记硬背存到一个
String 变量里。
- 然后,你把这个
String 传给下一个 Activity 去处理。
- 缺点:如果句子很长(JSON 很大),那个单一的
String 变量存不下那么多细节,前面的内容容易被“遗忘”。这就是所谓的长距离依赖问题。
看图中的下半部分 (Transformer)。这是现在的王者(ChatGPT、DeepSeek 都是基于这个)。
- 工作原理:
- 注意那些虚线箭头。
- 当模型要生成翻译结果 "Como" (How) 时,它不仅仅依赖上一个词,而是直接“看”向原始输入的所有单词。
- 它会计算一个权重(Attention):生成 "Como" 时,我应该多关注 "How",少关注 "are"。
- Android 类比:
- 这不再是死记硬背传
Bundle 了。
- 这更像是一个 HashMap 或者 SQL 查询。
- 当 Decoder 需要生成下一个词时,它拥有对 Input 数据的随机访问权限 (Random Access)。它可以在 O(1) 的时间复杂度内直接索引到原始句子中相关的部分,而不需要在内存里苦苦回想。
- 并行化:RNN 必须等上一个词处理完才能处理下一个(像单线程);Transformer 可以同时处理所有词(像 RxJava/Coroutines 的并发处理),效率极高。
总结
- Seq2Seq (RNN):像是一个串行的
InputStream,容易发生“内存溢出”或“数据丢失”(记不住长句子)。
- Transformer:像是一个并发的、带有索引的数据库查询,指哪打哪,精准且高效。
这就是为什么现在所有的 AI(包括你手机里的 Google Assistant 或 Siri 的新版本)都在底层换成了 Transformer 架构。
刚才我们讲到了架构的对比(Seq2Seq vs Transformer)。现在我们深入挖掘一下文本中提到的历史背景和技术痛点,看看为什么 Google 最终抛弃了旧架构,转向了 Transformer。
1. 历史背景:Google Translate 的一次“重构”
文中提到:“In 2016, Google incorporated seq2seq into Google Translate...”
- 背景故事:
在 2014-2016 年之前,机器翻译主要靠“统计机器翻译”(SMT),你可以理解为写了无数个
if-else 规则和查字典。
2016 年,Google 把翻译引擎的底层代码重构了,换成了 Seq2Seq (基于 RNN)。这在当时是一个巨大的飞跃,翻译质量大幅提升。
- Android 类比:
这就像 Android 5.0 (Lollipop) 时代,Google 把虚拟机从 Dalvik 换成了 ART。
虽然 ART (Seq2Seq) 比 Dalvik (旧规则) 强很多,但它依然有性能瓶颈,还不是最终形态。
2. Seq2Seq 的致命 Bug:那个蓝色的方块
文中提到:“The decoder... conditioned on both the final hidden state...” 以及图片上半部分中间那个蓝色的方块 "Final hidden state"。
这是旧架构最大的痛点,也是 Transformer 诞生的原因。
- 技术痛点:
Seq2Seq 架构要求 Encoder(编码器)把读到的整句话(无论多长),都压缩进这一个 "Final hidden state" 向量里。
Decoder(解码器)在生成翻译时,只能盯着这个被压缩过的向量看。
- Android 类比:
想象你正在做一个图片上传功能。
- Seq2Seq 的做法:不管用户选的是一张 10KB 的头像,还是一个 1GB 的 4K 视频,你都强制把它压缩成一个 Fixed Size 的 ByteArray(比如固定 512 bytes),然后传给服务器。
- 后果:
- 信息丢失 (OOM/Data Loss):如果句子很长(视频很大),压缩后的数据就会严重失真。前面的单词(视频开头)早就被后面的单词覆盖或遗忘了。
- 无法并行 (Main Thread Block):因为是串行处理(RNN),你必须等第 99 个词处理完,才能处理第 100 个词。这就像在主线程跑耗时任务,无法利用多核 CPU 并行加速。
文中提到:“...transformer architecture... which is based on the attention mechanism.”
这就是图片下半部分那些虚线箭头的含义。
- 核心逻辑:
Transformer 抛弃了“压缩成一个状态”的做法。它保留了输入句子的完整上下文。当它需要翻译某个词时,它会通过“注意力机制”去查询原句中相关的词。
- Android 类比:
- 不再压缩:你不再把 1GB 视频压缩成 512 bytes 了,而是把原始数据完整地放在内存/缓存里。
- 按需加载 (Lazy Loading / Paging):当服务器(Decoder)需要处理视频的第 10 分钟时,它不需要解压整个文件,而是通过一个**索引(Attention)**直接跳转到第 10 分钟的数据块读取。
- 全异步并行 (Coroutines/RxJava):因为不需要等待上一个状态,Transformer 可以一次性把整句话扔进 GPU 里并行计算。就像你用
Coroutines 并发发起了 10 个网络请求,而不是一个接一个地 await。
4. 总结:文末的伏笔
图片最后一句写道:“There are two problems with seq2seq that Vaswani et al. (2017) addresses.”
(Vaswani et al. 2017 就是那篇著名的论文《Attention Is All You Need》,AI 界的圣经)。
这两个问题(虽然图里没写完,但作为 Android 工程师你可以这样理解):
- 长距离依赖问题 (Long-term Dependency):
- Seq2Seq:句子太长就忘了开头(内存泄漏/被回收)。
- Transformer:无论句子多长,都能瞬间索引到开头(全局引用,O(1) 访问)。
- 训练效率问题 (Training Efficiency):
- Seq2Seq:串行计算,GPU 只能用 1% 的算力,在那干等(单线程)。
- Transformer:并行计算,跑满 GPU 100% 算力(多线程并发)。
所以,现在的 ChatGPT、Claude、DeepSeek,本质上都是在这个“并发 + 索引”的高级架构上堆出来的超级应用。
Q/K/V
这页 PPT 讲的是 Transformer 架构中最核心、最天才的部分:Attention Mechanism(注意力机制),具体来说是 Query (Q), Key (K), Value (V) 的概念。
对于 Android 工程师来说,理解这个机制最好的方式就是把它想象成一个 “超级模糊搜索的 HashMap”。
我们来拆解一下这三个核心变量:Q, K, V。
1. 核心概念:Q, K, V 的 Android 映射
在传统的编程里,我们查数据通常是用 HashMap:
Value result = map.get(Key); —— 这是一个精确匹配,要么拿到,要么是 null。
但在 AI 的世界里,查找是模糊的。
-
Query (Q) —— “搜索请求”
- 定义:当前模型正在关注的词(或者说正在生成的词)。
- Android 类比:用户在
SearchView 里输入的搜索关键词。
- 图中例子:右边的红色框 $Q_t$。比如现在模型要翻译 "How" (对应西班牙语 "Como"),那 $Q$ 就是 "Como" 的向量表示。它在问:“谁跟我有关?”
-
Key (K) —— “索引/标签”
- 定义:之前所有输入过的词的“特征标签”。用来和 Q 进行匹配,看是否相关。
- Android 类比:你数据库(Room)里每一条数据的
tag 或 id。
- 图中例子:橙色框 $K_1, K_2...$。代表 "How", "are", "you" 这些词的标签。
-
Value (V) —— “实际内容”
- 定义:如果匹配上了,我要提取的实际信息内容。
- Android 类比:数据库里存的实际 JSON 数据对象(Payload)。
- 图中例子:绿色框 $V_1, V_2...$。
2. 流程解析:一次“加权搜索”的过程
看右边的流程图,这其实就是一次计算相关性并提取数据的过程:
-
Dot Product (点积) —— calculateSimilarity()
- 操作:拿 Query ($Q$) 去和每一个 Key ($K$) 做点积运算。
- Android 类比:遍历数据库,计算搜索词和每一条记录 tag 的相似度分数。
- 结果:比如 "Como" (Q) 和 "How" (K) 的相似度很高(分数 0.9),和 "?" (K) 的相似度很低(分数 0.01)。
-
SoftMax —— normalizeScores()
- 操作:把上面的分数归一化,变成概率(加起来等于 1)。
- Android 类比:把原始分数转成百分比。
- 结果:关注度分配 -> 90% 给 "How",5% 给 "are",5% 给 "you"。
-
Weighted Sum (加权求和) —— aggregateResults()
- 操作:根据上面的百分比,把对应的 Value ($V$) 加起来。
- Android 类比:这不是返回一条数据,而是返回一个混合体。
- 结果:最终输出 = 0.9 * (How的内容) + 0.05 * (are的内容) + ...
- 这就解释了为什么模型能理解上下文:它在生成下一个词时,**“混合”**了之前所有相关词的信息。
3. 为什么会有性能瓶颈?(右下角文字)
文中提到:“the longer the sequence, the more key and value vectors need to be computed and stored.”
这对 Android 工程师来说非常容易理解,这就是典型的 OOM (Out of Memory) 风险 和 缓存膨胀。
- KV Cache (KV 缓存):
为了不每次都重新计算,模型会把之前算好的 K 和 V 存起来。
- Context Window (上下文窗口):
这就好比你的
LruCache 或者 ArrayList。
- 如果你聊了 100 句,就有 100 个 K-V 对。
- 如果你聊了 10 万句(比如丢给它一本书),这个
ArrayList 就会变得巨大无比,显存(RAM)直接撑爆。
- 计算复杂度 $O(N^2)$:
每生成一个新词,都要回头去和之前所有的词算一遍相似度(点积)。列表越长,遍历一次越慢。
总结
- Attention 就是一个基于向量相似度的模糊查询系统。
- Q 是搜索词,K 是索引键,V 是数据值。
- 它不返回单一结果,而是返回所有相关结果的加权混合物。
- 代价:对话越长,需要缓存的 K/V 越多,内存占用越大,计算越慢。这就是为什么现在的 AI 模型都有“最大上下文长度”(比如 128k token)的限制。
参数量和混合架构(MoE)
这一页主要在讲 模型大小(Model Size) 以及一种特殊的架构 MoE(混合专家模型)。
作为 Android 工程师,你可以这样理解这一页的内容逻辑:
左栏:基础概念 —— "APK 体积与性能的关系"
-
参数量 = 智力水平?
- 原文片段:“Llama-13B refers to... 13 billion parameters”
- 核心观点:通常来说,参数越多,模型越强。比如 Llama-13B 通常比 Llama-7B 厉害。
- Android 类比:就像 APK 体积越大,通常包含的功能和资源越多。
-
新架构 > 旧堆料(Note 部分)
- 原文片段:“Llama 3-8B (2024) outperforms even Llama 2-70B (2023)”
- 核心观点:这很重要!它说现在的**小模型(8B)已经吊打去年的超大模型(70B)**了。
- Android 类比:这就像你重构了代码,用新的算法(Llama 3)写了一个 8MB 的 App,运行效率和功能比以前那个 70MB 的屎山代码(Llama 2)还要好。架构优化比单纯堆代码行数更重要。
-
显存计算公式(底部)
- 原文片段:“each parameter is stored using 2 bytes... 14 billion bytes (14 GB)”
- 核心观点:怎么算模型占多少内存?
- 公式:参数量 × 2 (FP16精度) = 显存占用。
- 例子:7B 模型 × 2 = 14GB 显存。
- Android 类比:这就是你在做端侧部署(On-device AI)时必须算的账。你的手机 RAM 够不够跑这个模型?
右栏:进阶架构 —— "插件化与动态加载 (MoE)"
这部分被遮挡得最厉害,但讲的是目前最火的 MoE (Mixture of Experts) 架构,特别是 Mixtral 8x7B 这个模型。
-
什么是稀疏模型 (Sparse Model)?
- 原文片段:“zero-value parameters... 90% sparse”
- 核心观点:有些模型虽然很大,但大部分参数是 0,或者不参与计算。
-
MoE (混合专家模型)
- 原文片段:“Mixture of Experts (MoE)... divided... groups of parameters... experts”
- 核心观点:MoE 架构把一个大模型拆成了好几个“专家”(Experts)。
- Android 类比:
- 传统模型 (Dense):像是一个巨大的
Monolithic App,用户点一个按钮,整个 App 所有的代码都要过一遍,很慢,费电。
- MoE 模型:像是 “插件化架构” 或者 “动态 Feature Module”。App 里装了 8 个插件(8 Experts),但当用户点“翻译”按钮时,系统只动态加载并运行其中 2 个相关的插件,其他 6 个休眠。
-
Mixtral 8x7B 的魔法
- 原文片段:“Mixtral 8x7B... eight experts... only two experts are active... cost and speed are the same as a 12.9-billion... model”
- 核心观点:
- 总参数量:46.7B(虽然叫 8x7B,但因为有共享参数,不是 56B)。这是它的知识储备量(硬盘占用)。
- 活跃参数量:12.9B(每次只用 2 个专家)。这是它的运行速度(CPU/内存占用)。
- 结论:MoE 模型让你拥有大模型的智商(47B),却只消耗小模型的算力(13B)。这就是为什么现在的端侧大模型都在往 MoE 方向发展。
-
数据的重要性(底部)
- 原文片段:“I like pineapples”
- 核心观点:如果你用只有一句话的数据集去训练一个 13B 的大模型,它就是个傻子。数据质量决定上限,不仅仅是模型大小。
总结
这一页书实际上是在教你如何评估一个模型:
- 看代数:Llama 3 比 Llama 2 强。
- 算内存:参数量 × 2 bytes。
- 看架构:MoE (Mixtral) 比传统模型更适合在资源受限的设备(如手机)上运行,因为它“平时不用的脑子就不转”,省电省算力。
LLM 训练规模和成本
这两页在讲大模型训练中最烧钱、最硬核的两个维度:数据量 (Data) 和 算力 (Compute)。
作为 Android 工程师,你可以把这两页理解为:“为了编译这个超级 App,我们需要多大的代码库(数据),以及需要租多少台服务器跑多久(算力/成本)。”
第一张图(UnqX):数据量 —— "代码库的规模"
这一页的核心是在讨论 Tokens(词元) 的数量。
-
衡量单位:Token vs Sample
- 原文逻辑:以前大家用“样本数”(比如读了多少本书、多少个网页)来衡量数据量。但这不准,因为一本书可能很薄,也可能很厚。
- 现在的标准:Token。
- Android 类比:
- Sample:就像你问“你的项目有多少个
.java 文件?”(文件大小不一,没法衡量工作量)。
- Token:就像你问“你的项目有多少行 代码 (LOC)?”或者“编译后的 Bytecode 指令数是多少?”。这是最精准的衡量单位。
-
通货膨胀:Llama 的进化史
- Llama 1:1.4 Trillion (1.4万亿) tokens。
- Llama 2:2 Trillion tokens。
- Llama 3:15 Trillion tokens。
- 解读:模型并没有变大太多(参数量),但它“读”的书变多了 10 倍。
- Android 类比:这就像你写同一个功能的 App,V1 版本你只参考了官方文档;V3 版本你把 GitHub 上所有相关的开源代码都读了一遍,写出来的代码肯定更健壮。
-
Chinchilla 定律(表格部分)
- 表格里提到了 Chinchilla 模型(70B 参数,1.4T tokens)。
- 核心理论:这是 AI 界的一个黄金定律。它告诉我们,模型大小(脑容量)和训练数据量(阅读量)必须匹配。脑子太大书太少是浪费,脑子太小书太多读不完。
- Android 类比:CPU 性能与内存的配比。如果你给一个骁龙 8 Gen 3 的 CPU 配了 2GB 的 RAM,性能就瓶颈了;反之亦然。
第二张图(OKV):算力与成本 —— "编译耗时与云服务账单"
这一页非常硬核,在算钱和时间。
-
FLOPs vs FLOP/s(红色 Warning 框)
- 这是一个经典的坑。
- FLOPs (Floating Point Operations):总量。指训练完这个模型一共需要做多少次浮点运算。
- 类比:编译整个 AOSP 源码需要的 CPU 总指令数。
- FLOP/s (per second):速度。指你的显卡每秒能算多少次。
- 公式:训练时间 = 总工作量 (FLOPs) / 显卡速度 (FLOP/s)。
-
天价账单:训练 GPT-3 需要多久?
- 假设条件:你有 256 张 H100 显卡(H100 是目前最强的 AI 显卡,单张售价约 3-4 万美元,这里光硬件就价值近千万美元)。
- 计算结果:
- 如果你能跑满显卡性能,需要 236 天(约 8 个月)才能训练完一个 GPT-3 (175B)。
- Android 类比:
- 想象一下,你点了一下 Android Studio 的 "Run" 按钮(开始训练)。
- 然后 Gradle Build 进度条开始走。
- 这个进度条要走 8 个月 才能编译完成。
- 而且中间不能断电,不能报错(OOM),否则可能要重头来过(Checkpoints 机制就是为了防止这个)。
-
利用率 (Utilization) 的陷阱
- 原文:“Generally, if you can get half the advertised performance... you're doing okay.”
- 解释:虽然显卡理论速度很快,但因为数据传输、网络延迟等原因,实际利用率通常只有 50% - 70%。
- Android 类比:就像你的 CPU 是 8 核的,但因为 I/O 阻塞或者锁竞争,实际运行时 CPU 利用率可能只有 40%,大部分时间都在等待。
-
最终成本(右下角)
- 原文:“cost over $4 million”
- 结论:按每小时租金计算,训练一次 GPT-3 的电费和设备租金超过 400 万美元。
- Android 类比:这不再是个人开发者能玩的领域了。这就像是开发操作系统,只有 Google、Apple 这种巨头才付得起这个“编译费”。
总结
这两页书揭示了为什么大模型是“富人的游戏”:
- 你需要准备 万亿级别 的高质量代码/文本(Tokens)。
- 你需要租用 数百张 顶级显卡,连续跑 大半年。
- 只要代码有一行写错(参数设置不对),几百万美元的电费就打水漂了。
Checkpoint 介绍
结合刚才提到的“训练一次 GPT-3 需要 400 万美元、耗时 8 个月”的背景,Checkpoint(检查点)机制就是这场昂贵赌博中的**“存档”**功能。
如果没有 Checkpoint,一旦在第 7 个月零 29 天的时候机房停电或者显卡烧了,那 400 万美元就直接归零。
作为一个 Android 工程师,你可以通过以下几个维度来理解 Checkpoint:
1. 核心概念:它是 AI 界的 onSaveInstanceState()
在 Android 开发中,当 Activity 因为内存不足被系统杀掉,或者用户旋转屏幕时,系统会调用 onSaveInstanceState(Bundle)。
- 目的:保存当前界面的状态(输入框里的文字、滚动条的位置)。
- 结果:当 Activity 重建时,可以通过
onRestoreInstanceState 恢复现场,用户感觉不到中断。
Checkpoint 也是一样的逻辑:
训练大模型是一个漫长的循环(Loop)。每隔一段时间(比如每跑完 1000 个 Step,或者每 1 个小时),程序就会把内存里的所有数据 Dump 到硬盘上,生成一个文件(通常是 .pt, .ckpt, .safetensors 格式)。
2. Checkpoint 到底存了什么?
它不仅仅是保存了模型的“智商”(权重),还保存了“学习的状态”。
- Model Weights (模型权重):
- 类比:
EditText 里用户已经输入的文字。
- 这是模型学到的知识,是推理(Inference)时唯一需要的东西。
- Optimizer State (优化器状态):
- 类比:光标的位置、滚动条的惯性速度。
- 这是关键!训练是用“梯度下降”算法的,往往带有动量 (Momentum)。如果你只存权重,恢复训练时就像车子突然从 100km/h 刹停再重新起步,效率会大打折扣。保存优化器状态,是为了让车子能以 100km/h 的速度无缝接续跑下去。
- Epoch / Step Number:
- 类比:视频播放进度条的时间戳。告诉程序“我们上次学到第几章了”。
3. Checkpoint 的三大核心用途
A. 容灾与断点续训 (Disaster Recovery)
这是最基础的功能。
- 场景:训练集群里有 1000 张显卡,概率学告诉我们,连续 8 个月不坏一张卡的概率几乎为 0。
- 操作:当某张卡挂了导致训练崩溃,工程师换好卡,读取最新的 Checkpoint,可能只损失了最近 1 小时的进度,而不是 8 个月。
- Android 类比:玩 RPG 游戏打 Boss 前存个档。挂了就读档重来,不用从一级开始练号。
B. 防止过拟合 (Early Stopping / Model Selection)
- 场景:模型训练并不是越久越好。有时候练到第 100 个 Epoch 效果最好,练到第 120 个 Epoch 反而因为“死记硬背”(过拟合)变笨了。
- 操作:我们会保存很多个 Checkpoint(如
epoch_10.ckpt, epoch_20.ckpt...)。训练结束后,我们在测试集上跑分,发现 epoch_100 分数最高,那就选它发布,把后面的都删了。
- Android 类比:Git 的 Commit 历史。你写代码写嗨了,改出 Bug 了,你可以
git checkout 回退到昨天那个“虽然功能少点但能稳定运行”的版本。
C. 迁移学习与微调 (Fine-tuning)
这是目前开源社区(如 HuggingFace)最常用的方式。
- 场景:Meta 花了 400 万美元练好了 Llama-3-Base(基础模型),发布了一个 Checkpoint。
- 操作:你下载这个 Checkpoint,加载到你的显卡里,然后喂给它你们公司的私有数据,继续训练(Fine-tune)。
- Android 类比:Fork 开源项目。你不需要从零写一个 OkHttp,你只需要 clone 下来(下载 Checkpoint),然后根据你的需求修改几行代码(微调),就能用了。
4. 总结
对于 Android 工程师来说:
- Training (训练) = 这是一个耗时 8 个月的
AsyncTask 或 WorkManager 任务。
- Checkpoint = 定期把任务进度序列化写入
SharedPreferences 或 SQLite。
- Inference (推理) = 任务结束,把最终生成的
JSON (模型权重) 打包进 APK 发布给用户。
模型训练的最佳化和瓶颈
这三页从单纯的技术架构转向了工程经济学和未来瓶颈。
作为 Android 工程师,你可以把这部分内容看作是:“如何用有限的预算(算力/钱)构建性能最好的 App,以及我们即将面临的‘资源枯竭’危机。”
我为你拆解为三个核心模块:
1. 黄金法则:Chinchilla Scaling Law(第一页)
这一页解决了一个核心的工程问题:我有 1000 万美元预算(算力),我是该造一个巨大的模型(参数多),还是造一个中等模型但让它读更多的书(数据多)?
2. 调参玄学与数据枯竭(第二页)
这一页讲了两个让工程师头大的问题:参数怎么调以及数据不够用了。
3. 乱伦危机与能源危机(第三页)
这一页讲的是未来的两大隐患,非常赛博朋克。
总结
这三页书告诉你,做 AI 不仅仅是写代码,现在面临的是:
- 算账:怎么用最少的钱训练出最强的模型(Chinchilla 定律)。
- 抢资源:抢高质量的人类数据(防止 AI 吃 AI),抢电力。
- 拼落地:像 Llama 那样,宁可训练时多花钱,也要把模型做小,方便在端侧(手机/PC)运行。
追问:
问题一:怎么预设/推断我要训练一个多大的模型?
决定模型大小(参数量,比如 7B, 70B, 175B)并不是拍脑袋决定的,它通常是一个**“倒推法”**的过程。
你可以把它想象成:“我要开发一个 App,我该把 minSdk 设为多少?APK 体积限制是多少?”
主要有三个维度的限制条件,通常是短板效应决定了你的上限:
1. 硬件限制(最硬的指标)—— "用户的手机带得动吗?"
这是最直接的判断标准,特别是对于端侧(On-device)模型。
- 显存/内存公式:模型参数量 × 2 Bytes (FP16精度) = 最小显存需求。
- 7B 模型 $\approx$ 14GB 显存。
- 13B 模型 $\approx$ 26GB 显存。
- Android 场景:
- 如果你想让模型跑在 Pixel 8 或高端小米手机上(通常 12GB-16GB RAM,但系统和 App 要吃掉一半),你撑死只能跑一个 3B - 7B 的量化版模型(4-bit 量化后体积减半)。
- 结论:如果目标是手机端,7B 是目前的物理极限,不用想更大的了。
2. 数据量限制(Chinchilla 定律)—— "我有多少砖头盖房子?"
如果你有无限的算力(比如你是 Google),那么限制你的就是数据量。
- 黄金法则:训练数据 Token 数应该是参数量的 20 倍。
- 推断逻辑:
- 假设你手里只有 100 亿 (10B) 个 Token 的高质量行业数据(比如你们公司的所有文档)。
- 根据公式:$10B \div 20 = 0.5B$。
- 结论:你应该训练一个 0.5B (5亿参数) 的小模型。如果你强行训练一个 7B 的模型,它就像一个脑容量很大但没书读的学生,会产生严重的“幻觉”或过拟合。
3. 算力预算(老板给多少钱)—— "项目经费够烧几天?"
这是最现实的商业逻辑。
- 估算公式:训练成本 $\propto$ 模型大小 × 数据量。
- 推断逻辑:
- 老板说:“给你 1 万美元预算。”
- 你去租 H100 显卡,算一下 1 万美元能跑多少个 FLOPs。
- 然后查表(Chinchilla Scaling Law 图表),看这点算力能支持的最佳模型是多少。
- 结论:可能算下来只能训练一个 1B 的模型。
总结:如何决定?
- 先看部署环境:跑在服务器?(上限很高)还是跑在手机?(上限 7B)。
- 再看数据口袋:数据够不够填满这个模型?(不够就缩小模型)。
- 最后看钱包:租得起显卡吗?
问题二:Parameter (参数) vs. Hyperparameter (超参数)
这个概念确实容易混淆。我们用 Android 项目编译 和 教学生 两个类比来彻底区分。
类比 1:Android 项目构建 (Build Process)
想象你在开发一个 App。
类比 2:教学生考试
想象你是一个老师(开发者),你要教一个学生(模型)去参加高考。
核心区别对照表
| 特性 |
Hyperparameter (超参数) |
Parameter (参数) |
| 设定者 |
人 (工程师) |
机器 (算法) |
| 设定时间 |
训练开始前 |
训练过程中不断更新 |
| 数量级 |
几十个 (可枚举) |
几十亿、几千亿 (7B, 175B) |
| Android 类比 |
build.gradle 配置 |
classes.dex 里的字节码 |
| 如何优化 |
调参 (Tuning) - 试错法 |
训练 (Training) - 梯度下降 |
| 例子 |
学习率、层数、Batch Size |
权重 (Weights)、偏置 (Biases) |
一句话总结:
超参数 (Hyperparameter) 是你用来控制训练过程的**“旋钮”;
参数 (Parameter) 是训练结束后模型学到的“脑细胞”**。
Shoggoth(修格斯)梗图
这四页内容非常经典,它们解释了为什么 ChatGPT 能像人一样聊天,而不是像搜索引擎一样只会补全句子。
这里引入了一个 AI 圈最著名的梗图——Shoggoth(修格斯)(那个长满眼睛的克苏鲁怪物)。
作为 Android 工程师,你可以把这部分理解为:“从 Linux 内核(Pre-training)到 Android UI(Post-training)的进化过程”。
1. 核心概念:Shoggoth 面具(图二的怪物)
这是理解这几页的钥匙。
-
怪物本体 (Pre-trained Model):
- 状态:它读完了互联网上所有的书,智商极高,但性格混乱邪恶。它不知道什么是“对话”,它只知道“补全”。
- 例子:你问它“怎么制作炸弹?”,它可能会真的给你一个化学配方,或者补全成“怎么制作炸弹的教程在第几页”。
- Android 类比:AOSP 的底层源码。功能极其强大,能控制硬件,但如果你直接给普通用户一个 Terminal 终端,用户会疯掉。
-
面具 (SFT - Supervised Finetuning):
- 状态:给怪物戴上了一个“人类”的面具。它学会了“一问一答”的格式。
- Android 类比:System UI / Launcher。用户终于看到了图标和按钮,知道怎么交互了。
-
笑脸 (RLHF - Preference Finetuning):
- 状态:在面具上画了个笑脸。它不仅会回答,还变得礼貌、安全、政治正确。
- Android 类比:Google Play 审核机制 / SafetyNet。确保 App 不会崩溃,不会有恶意代码,符合社区规范。
2. Post-Training 的两个阶段(图一 & 图三)
文中明确指出,预训练(Pre-training)消耗了 98% 的算力,但**后训练(Post-training)**才是决定产品体验的关键 2%。
第一阶段:SFT (有监督微调) —— "教它说话"
- 问题:预训练模型是“补全机”。
- 你输入:“如何做披萨”
- 它补全:“...给一家六口人吃?”(它以为你在写句子)
- 目标:让模型学会遵循指令 (Instruction Following)。
- 你输入:“如何做披萨”
- 它回答:“1. 准备面粉...”
- 方法:Behavior Cloning (行为克隆)。
- 找几万个真人,写出完美的“问题-答案”对(Prompt-Response Pairs),喂给模型吃。
- Android 类比:这就像你在写 Unit Test (单元测试)。你明确告诉函数:输入 A,必须输出 B。
第二阶段:RLHF (人类反馈强化学习) —— "教它做人"
- 问题:SFT 模型可能会一本正经地胡说八道,或者输出有害信息。
- 目标:让模型的回答符合人类的偏好 (Preference)——有用、诚实、无害。
- 方法:
- 模型生成两个答案。
- 人类标记员选出“更好的那个”。
- 训练一个“奖励模型 (Reward Model)”来模仿人类的打分标准。
- 用强化学习(RL)让模型不断刷高分。
- Android 类比:这就像 A/B Testing 和 用户反馈系统。你发布了两个版本的 UI,看用户更喜欢点哪个,然后根据数据优化产品。
3. 数据质量的代价(图四)
这一页揭示了为什么现在的 AI 越来越贵。
- 昂贵的“老师”:
- 做 SFT 数据标注的不是随便找的兼职。文中提到,OpenAI 的标注员 90% 拥有大学学位,甚至有很多硕士/博士。
- 写一条高质量的问答对(Prompt-Response),可能需要 30 分钟。
- 成本计算:
- 一条数据成本约 $10。
- InstructGPT 用了 13,000 条数据,光这一小步的数据成本就是 $130,000(还不算管理费)。
- Android 类比:
- Pre-training 数据 = Logcat 里的原始日志(量大,但全是噪音)。
- SFT 数据 = 高级工程师写的 Technical Design Doc(量少,但字字珠玑,非常昂贵)。
总结
这几页书解释了为什么你觉得 ChatGPT "像人":
- Pre-training 给了它智商(怪物的脑子)。
- SFT 给了它对话的能力(面具)。
- RLHF 给了它情商和道德(笑脸)。
对于 Android 工程师来说,这就是从 Kernel (内核) 到 Framework (框架) 再到 Application (应用体验) 的完整封装过程。
SFT 和 RLHF 是不是老的 ML 时代就有,不是现在 AI 时代的产物?答案是:是的,它们绝对是“旧时代”的产物,或者更准确地说,是深度学习(Deep Learning)中期的经典技术,被“旧瓶装新酒”用在了 LLM 上。
作为 Android 工程师,你可以这样理解:这就像是把十几年前就有的 MVC 模式和 A/B Testing 思想,突然用在了最新的 Jetpack Compose 架构里,产生奇效。
我们来逐个“考古”:
1. SFT (Supervised Fine-Tuning) —— 其实就是“迁移学习”
SFT 并不是什么新词,它在“老 ML 时代”(2012-2018年,计算机视觉统治时期)有一个更通用的名字:Transfer Learning(迁移学习)。
- 考古现场:
- 早在 2014 年左右,做图像识别的标准流程就是:下载一个在 ImageNet(千万级通用图片库)上训练好的 ResNet 或 VGG 模型(相当于现在的 Pre-trained Model)。
- 然后,把最后一层分类层砍掉,换成你自己的分类层(比如识别“猫 vs 狗”)。
- 最后,用你手里少量的猫狗图片,去微调(Fine-tune)这个模型的参数。
- 本质:
这就是 SFT。Pre-training 学通用特征(边缘、纹理),Fine-tuning 学特定任务。
- Android 类比:
这就像你继承了一个功能强大的
BaseActivity(预训练模型),然后重写了它的 onCreate() 方法(SFT),加入你自己页面的特定逻辑。你不需要从零开始写一个 Activity 类,你只是在“微调”它。
2. RLHF (Reinforcement Learning from Human Feedback) —— 也就是“教机器人翻跟头”
RLHF 听起来很赛博朋克,但它的核心组件 RL(强化学习) 是 ML 里最古老的流派之一(AlphaGo 下围棋用的就是 RL)。
而 RLHF(带人类反馈的 RL) 这个具体组合,成名于 2017年(OpenAI 和 DeepMind 的论文)。
- 考古现场:
- 在 ChatGPT 出现之前,RLHF 主要不是用来教 AI 说话的,而是用来教机器人(Agent)做动作的。
- 经典案例:2017 年,OpenAI 发表了一篇论文,他们想教一个火柴人做“后空翻”。但是写代码定义“什么是完美的后空翻”太难了(数学公式很难写)。
- 解决方案:让 AI 随机动,生成两段视频。让人类看,选“哪一段更像后空翻”。AI 根据人类的选择(Reward)去调整动作。最后火柴人学会了极其标准的后空翻。
- 本质:
用“人类的选择”代替“数学公式”作为奖励函数(Reward Function)。
- Android 类比:
这就像 Google Play 的推荐算法。
- Google 很难写死一个公式说“用户喜欢什么样的 App”。
- 但是 Google 可以根据用户的 点击(Click) 和 卸载(Uninstall) 行为(Human Feedback),来强化推荐算法(RL)。用户点得越多,算法就越倾向于推这类 App。
3. 为什么它们现在突然“火”了?
既然都是老技术,为什么以前没造出 ChatGPT?
因为以前大家用错了地方,或者说没打通任督二脉。
-
SFT 的转变:
- 以前:SFT 用来做分类(这张图是猫还是狗?)。
- 现在:SFT 用来做生成(请写一首关于猫的诗)。
- 突破点:人们发现,只要给大模型看几个“指令-回复”的例子,它就能学会“听懂指令”这种抽象能力。
-
RLHF 的跨界:
- 以前:RLHF 用来玩游戏、控制机器人手臂。
- 现在:OpenAI 的天才之处在于,他们把这个用来“控制机器人动作”的技术,拿来控制“文本生成的概率”。
- 突破点:他们发现,用 RLHF 调整模型,比单纯喂数据更能让模型学会“人类的价值观”(比如礼貌、不骂人)。
总结
- SFT = 老派的迁移学习 (Transfer Learning)。
- RLHF = 2017 年左右用于机器人控制的技术。
现在的 AI 革命,并不是发明了全新的算法,而是把这些“老零件”组装到了一个前所未有的“大引擎”(Transformer)上,并喂了前所未有的“大数据”。
就像 Android 开发,RecyclerView 和 OkHttp 都是老东西了,但你把它们和一个超大规模的后端数据结合,再配合全新的 UI 设计,就做出了 TikTok。
进一步提高 AI 基座模型的性能
这四页完成了从**“如何训练模型”到“模型如何生成结果”**的跨越。
作为 Android 工程师,你可以把这部分内容理解为:“如何给 App 加上内容审核机制(RLHF/DPO),以及底层的 Random 随机数生成逻辑(Sampling)。”
我为你拆解为三个核心模块:
1. 价值观对齐:RLHF 与 DPO(第一、二页)
这一部分解决的问题是:SFT 教会了模型说话,但没教会它“什么话该说,什么话不该说”。
2. 穷人的绝招:Best-of-N 策略(第三页右下角)
有些公司(如 Stitch Fix, Grab)觉得训练 RLHF 太贵太难了,于是想了个笨办法。
- 策略:Best-of-N (Rejection Sampling)
- 既然我训练不好模型,那我就以量取胜。
- 对于同一个 Prompt,我让模型一次性生成 N 个(比如 10 个)不同的回答。
- 然后用一个现成的“打分器”(Reward Model)给这 10 个回答打分。
- 只把分数最高的那个返回给用户。
- Android 类比:
- 这就像 TCP 的重传机制 或者 网络请求的 Retry 策略。
- 一次请求可能会失败(生成烂回答),那我就并发请求 10 次,哪个成功了(分数高)我就用哪个。虽然浪费了流量(算力),但保证了用户体验。
3. 揭秘底层:Sampling 与 Logits(第四页)
这一页开始进入**推理(Inference)**阶段,解释模型到底是怎么吐出下一个字的。
-
Logits (逻辑值):
- 看图 2-15。神经网络的原始输出不是概率,而是一堆实数(可以是负数,比如 -1.2, 0.7)。这些数字叫 Logits。
- Android 类比:这就像
View.getMeasuredWidth() 拿到的原始像素值,或者是 Color.parseColor() 拿到的原始 HEX 值,还没经过处理。
-
Softmax 层:
- Logits 必须经过 Softmax 函数,才能变成概率 (Probabilities)(所有概率加起来等于 1)。
- 比如:
- "green": 0.7 -> 70%
- "red": 0.5 -> 20%
- "house": -0.2 -> 1%
- Android 类比:这就像把一堆原始数据通过
MathUtils.normalize() 归一化,变成 0.0 到 1.0 之间的进度条进度。
-
Sampling (采样):
- 有了概率之后,模型怎么选下一个词?
- Greedy Search (贪婪搜索):永远选概率最大的那个(70% 的 green)。结果:模型会很死板,像个复读机。
- Random Sampling (随机采样):根据概率随机选。有 20% 的几率选 "red"。结果:模型会有创造力,但有时会胡说八道。
- 这就是为什么你在用 ChatGPT API 时有一个参数叫
temperature。
temperature = 0:只选概率最高的(严谨,适合写代码)。
temperature = 1:增加随机性(发散,适合写小说)。
总结
这四页书串联了 AI 发布的最后几公里:
- RLHF/DPO:是**“政审员”**。确保模型三观正,不乱说话。(类似 Android 的 Permission 权限检查)。
- Best-of-N:是**“大力出奇迹”**。生成一堆,选最好的。(类似并发请求)。
- Logits & Sampling:是**“骰子”**。决定了模型是像个严谨的程序员,还是像个浪漫的诗人。(类似
Random 类和配置参数)。
运行时配置对模型的影响
这几页书的内容含金量极高,它们揭示了OpenAI o1 模型背后的核心思想,以及如何通过“运行时配置”来控制 AI 的行为。
作为 Android 工程师,你可以把这部分内容理解为:“配置 Random 随机数生成器的参数,以及通过‘并发请求 + 结果校验’(Retry & Verify)来强行提升 App 的成功率。”
我为你拆解为三个核心模块:
1. 调节“疯狂指数”:Temperature, Top-k, Top-p
这一部分讲的是如何控制 AI 是“严谨的程序员”还是“发疯的艺术家”。
2. 调试工具:Logprobs (对数概率)
- 为什么用 Log (对数)?
- Underflow (下溢出):概率通常是很小的数(比如 $0.0000001$)。计算机的
float 类型精度有限,乘多了就变成 0 了。
- 用 Log 之后,乘法变加法($\log(a \times b) = \log a + \log b$),数值变成了负数(比如 -15.2),计算机处理起来很舒服。
- Android 类比:
- 这就是 Logcat。你平时看不到 App 内部的变量状态,但开启
logprobs 就像打开了 Log.d(),能看到模型在生成每一个字时,内心到底有多纠结(自信度是多少)。
3. 核心大招:Test Time Compute (测试时算力)
这是这几页书里最重要的概念,也是目前 AI 发展的最新方向(OpenAI o1 的原理)。
总结
这几页书实际上是在教你**“如何用工程手段弥补模型的不足”**:
- 调参 (Temperature/Top-p):根据业务场景(写代码 vs 写小说)调整模型的“性格”。
- 换算 (Test Time Compute):如果你没有钱训练大模型,你可以通过**“多跑几次 + 投票/验证”**的方式,用小模型达到大模型的效果。
这就是为什么现在的 AI API 越来越慢(比如 o1-preview),因为它在后台偷偷地“多想了几遍”(Test Time Compute)。
实现模型的格式化输出
这三页内容对于 Android 工程师来说简直太亲切了。
如果说之前的章节是在讲“如何训练一个像人的大脑”,那么这一章就是在讲**“如何把这个大脑接入到你的 App 代码里”**。
作为开发者,我们最怕的就是非结构化数据。
- LLM 的天性:喜欢啰嗦,喜欢输出 "Here is the JSON you asked for: {...}"。
- App 的需求:我只要
{...},多一个字符 Gson 或 Moshi 解析都会报错(Crash)。
这几页书就是在讲如何强迫 LLM 输出完美的 JSON、SQL 或 Regex,以便你的代码能直接调用。我为你拆解为三个核心层级:
1. 核心需求:Semantic Parsing (语义解析)
场景:用户说人话,App 听不懂,数据库更听不懂。
任务:把“人话”翻译成“机器指令”。
- Text-to-SQL / Text-to-Regex:
- 用户说:“帮我查一下过去 6 个月的平均营收。”
- App 需要:
SELECT avg(revenue) FROM sales WHERE date > now() - interval '6 months';
- 用户说:“我要匹配美国手机号。”
- App 需要:
\+?1?\s?(\(?(\d{3})\)?[-.\s]?(\d{3})[-.\s]?(\d{4}))
- Android 类比:
- 这就像是一个超级智能的 Adapter 或者 Converter。
- 输入是
String (自然语言),输出是 Query 对象 (Room Database) 或 Pattern 对象 (Regex)。
2. 怎么保证输出格式?(五层防御体系)
书中列出了 5 种让模型“听话”的方法,这里重点讲前三种最实用的。
Level 1: Prompting (提示词工程) —— "求求你"
- 做法:在 System Prompt 里写:“请只返回 JSON,不要说废话。”
- 结果:很不靠谱。模型经常会忍不住加一句 "Sure! Here it is:",导致你的
JSONObject.parse() 抛出异常。
- Android 类比:这就像在代码注释里写
// Please don't pass null here,但没有任何编译期检查,运行时该空指针还是空指针。
Level 2: Post-processing (后处理) —— "擦屁股"
- 做法:模型输出错了,我写个脚本修一下。
- LinkedIn 的案例:
- 他们发现模型生成的 JSON 经常少写一个闭合括号
}。
- 于是他们写了个脚本:如果解析失败,就尝试在字符串末尾加个
} 再解析一次。
- 效果:成功率从 90% 提升到了 99.99%。
- YAML vs JSON (书中 Tip):
- LinkedIn 发现 YAML 格式比 JSON 更好。
- 原因:YAML 符号少(没有那么多引号和括号),Token 数更少。
- Android 类比:
- 后处理 =
try-catch 块里的重试逻辑。
- YAML = ProGuard/R8 混淆。同样的逻辑,体积更小,传输更快,省流量(省 Token 钱)。
Level 3: Constrained Sampling (受限采样) —— "强类型检查"
这是最硬核、最有效的技术(图 2-20)。
3. OpenAI 的 JSON Mode (中间派)
书中特别提到了 OpenAI 的 JSON Mode。
- 是什么:它保证返回的字符串语法上一定是合法的 JSON(能过
JSON.parse)。
- 局限性:
- 内容不保真:它可能返回
{ "age": "hello" },虽然是 JSON,但 Schema 不对。
- 截断风险:如果 Token 超限,JSON 可能被切断(比如
{ "name": "To),导致解析失败。
- Android 类比:
- 这就像服务器返回了
200 OK,Body 也是 JSON,但里面的字段可能是空的,或者类型不对。你还是需要用 Moshi 的 JsonAdapter 来做进一步校验。
总结
这几页书实际上是在教你如何把“玄学”的 AI 变成“工程化”的 API:
- Prompting 是靠运气(不推荐生产环境)。
- Post-processing 是靠补丁(LinkedIn 的 YAML 技巧很实用)。
- Constrained Sampling 是靠底层机制(类似
llama.cpp 或 Guidance 库),它是最稳的,相当于给 AI 加上了 Type Safety (类型安全)。
作为 Android 工程师,如果你要在 App 里集成 AI 功能,强烈建议使用支持 Structured Outputs (Constrained Sampling) 的框架,否则你的 Crash 率会非常难看。
Constrained Sampling 实现原理
书中对 Constrained Sampling(受限采样) 的分析非常深入,它是目前解决“大模型胡乱输出格式”最底层、最硬核的技术手段。
作为 Android 工程师,你可以把它理解为:从“运行时校验(Runtime Check)”进化到了“编译时类型安全(Compile-time Type Safety)”。
以下是书中对这一技术的详细拆解:
1. 核心原理:在“大脑”和“嘴巴”之间设卡
书中虽然没有画出底层的概率图,但文字描述(Page 2 右下角 & Page 1 图 2-20)揭示了其工作机制。
-
正常流程:
- 模型大脑计算出下一个词的概率分布(Logits)。
- 比如:
"yellow" (40%), "red" (30%), "blue" (20%)。
- 采样层(Sampling)根据概率随机选一个词输出。
-
Constrained Sampling 流程:
- 定义约束:你告诉系统,这里只能填
['red', 'blue', 'green']。
- 动态掩码 (Masking):
- 模型算出了
"yellow" (40%)。
- 拦截器介入:检测到
"yellow" 不在白名单里。
- 强制修改概率:把
"yellow" 的概率瞬间设为 0 (或者 Logit 设为 $-\infty$)。
- 重归一化:剩下的
red, blue, green 瓜分剩下的概率。
- 输出:模型被迫在合法的选项里选一个概率最高的。
Android 类比:
这就像你自定义了一个 InputConnectionWrapper 给软键盘。
当 EditText 设置为 inputType="number" 时,即使用户在键盘上疯狂点击字母 "A",你的 Wrapper 会直接拦截这个事件,根本不让这个字符上屏。模型想说错都难。
2. 书中展示的两种约束模式(图 2-20)
书中通过代码片段(类似 guidance 库的语法)展示了两种最常见的约束场景:
A. 集合约束 (Set of Options) —— 枚举 (Enum)
3. 与 OpenAI JSON Mode 的对比(Page 1 右上角)
书中特意把 Constrained Sampling 和 OpenAI 的 JSON Mode 做了区分,这是一个很重要的技术细节。
4. 局限性与代价
虽然 Constrained Sampling 看起来很完美,但书中也暗示了它的代价(Page 1 右上角最后一句):
- 推理延迟 (Latency):
- 每次生成一个 Token,都要运行一次“状态机”来判断哪些词是合法的。如果是复杂的正则表达式或巨大的 JSON Schema,这个计算量不小,会拖慢生成速度。
- API 支持度:
- 这需要访问模型的底层 Logits。目前只有开源模型(如
llama.cpp 跑在本地)或者少数支持高级功能的 API(如 OpenAI 的 Structured Outputs 新功能)才能做到。普通的 API 只能靠 Prompting(求它写对)。
总结
书中对 Constrained Sampling 的分析核心在于:不要相信模型的自律,要用算法接管它的输出层。
- Prompting = 告诉用户“请输入数字”。(用户可能不听)
- Post-processing = 用户输错了,你写个正则去提取数字。(可能提取不到)
- Constrained Sampling = 直接把键盘上的非数字键扣掉。(绝对安全)
。
Constrained Sampling 的底层实现原理(书外补充)
我可以用通用的技术原理(基于 llama.cpp 或 outlines 等开源库的实现逻辑)为你补全这块拼图。这其实是一个编译原理 + Trie 树搜索的问题
要实现“让模型只输出符合 Regex 的内容”,系统在底层做了三步操作:
1. 编译阶段:Regex $\rightarrow$ FSM (有限状态机)
在你把 regex='\d{3}-\d{4}' 传给系统时,系统会先把它编译成一个 DFA (确定性有限状态自动机)。
- 状态 0 (Start): 期待数字。
- 状态 1 (输了1个数字): 期待数字。
- ...
- 状态 3 (输了3个数字): 期待连字符
-。
- ...
2. 索引阶段:Vocabulary $\rightarrow$ Trie (前缀树)
大模型的词表(Vocabulary)通常有 3万-10万 个 Token。为了快速查找,这些 Token 会被构建成一个 Trie 树。
3. 运行时阶段:Logit Masking (掩码拦截) —— 核心魔法
这是发生在模型生成每一个 Token 之前的毫秒级操作:
- 获取当前状态:
假设 FSM 当前处于 状态 3(已经输了3个数字,现在必须输
-)。
- 计算合法字符集:
FSM 告诉我们,下一个字符必须是
-。
- Trie 树剪枝 (Pruning):
系统去遍历 Token Trie 树。
- Token
"apple"?以 a 开头 $\rightarrow$ 非法。
- Token
"123"?以 1 开头 $\rightarrow$ 非法(因为现在急需 -)。
- Token
"-"?以 - 开头 $\rightarrow$ 合法。
- Token
"-hello"?以 - 开头 $\rightarrow$ 合法(虽然它后面跟了 hello,但只要前缀匹配,且能推动 FSM 状态流转,有些实现是允许的,有些严格实现会继续检查后续字符)。
- 修改 Logits:
找到所有非法 Token 的 ID,直接在 Logits 数组中把对应的值设为 $-\infty$。
- Softmax & Sampling:
经过 Softmax 后,非法 Token 的概率变为 0。模型只能选到合法的 Token。
- 状态推进:
模型选了
"-",FSM 状态从 3 变为 4。
Android 工程师视角的类比
这就好比你写了一个超级复杂的 InputFilter,但不是用 if-else 写的,而是用编译原理写的。
- 场景:用户在输入框里输入。
- FSM:就是你编译好的
Pattern 对象。
- Token Trie:就是软键盘上的所有按键(加上智能联想词)。
- Logit Masking:
- 当用户输完前 3 个数字时,你的代码瞬间遍历了软键盘所有按键。
- 发现只有
- 键能让正则匹配继续下去。
- 于是你调用
setKeyEnabled(false) 把除了 - 以外的所有键(A-Z, 0-9)全部变灰不可点。
- 用户闭着眼睛按,也只能按出
- 来。
总结
虽然书中没写,但这个机制的本质就是:将 Regex 解析为状态机,在每一步生成前,计算出“哪些 Token 能让状态机合法流转”,然后把其他 Token 的概率物理清零。
这就是为什么它能 100% 保证格式正确,但也会增加推理延迟(因为每一步都要遍历 Trie 树做计算)。
模型的不一致性和幻觉
这五页书是本章的大结局,它揭示了 AI 最致命的两个“系统级 Bug”:不一致性 (Inconsistency) 和 幻觉 (Hallucination)。
作为 Android 工程师,你可以把这部分理解为:“为什么这个后端接口有时候返回 200,有时候返回 404,有时候返回 200 但里面的数据全是假的?”
我为你拆解为三个核心模块:
1. 根源:概率性 (Probabilistic) vs 确定性 (Deterministic)
第一页点出了问题的核心。
- 传统软件 (Deterministic):
- 输入
2 + 2,永远输出 4。
- Android 类比:
HashMap.get("key")。只要 Key 存在,每次拿到的 Value 绝对是一样的。
- AI 模型 (Probabilistic):
- 输入
2 + 2,它是在掷骰子。
- 它认为
4 的概率是 99%,5 的概率是 1%。如果你运气不好(或者 Temperature 设置高了),它真能给你输出 5。
- Android 类比:
Random.nextInt() 或者 多线程 Race Condition。你永远不知道这一次运行的结果会不会和上一次一样。
2. Bug 类型 A:不一致性 (Inconsistency) —— "Flaky Test"
第二页详细描述了这种痛苦。
- 现象:
- 同入异出:同样的 Prompt 问两次,第一次给作文打 3 分,第二次打 5 分(图 2-23)。
- 蝴蝶效应:Prompt 改了一个标点符号,输出结果天差地别。
- 硬件也有锅:
- 书中提到,即使你固定了 Seed(随机种子),在不同的 GPU(比如 NVIDIA A100 vs H100)上跑,结果也可能不一样。
- Android 类比:机型适配问题。
- 你的代码在 Pixel 上跑得好好的,在三星或小米手机上就 Crash 了。
- 或者像 Flaky Unit Test(不稳定的单元测试)。代码没改,CI 跑十次,有两次红了。这让开发者非常抓狂,因为不可复现。
3. Bug 类型 B:幻觉 (Hallucination) —— "一本正经胡说八道"
第三、四页深入探讨了 AI 为什么会撒谎,以及为什么越教它做人,它越爱撒谎。
A. 滚雪球效应 (Snowballing)
- 原理:模型是基于“上一个词”预测“下一个词”的。
- 过程:
- 模型第一步预测错了一个词(比如把“洗发水”看成了“牛奶”)。
- 为了让逻辑通顺,它后面生成的几百个词都必须圆谎(开始编造牛奶的成分表)。
- 这就是 Self-delusion (自我欺骗)。
- Android 类比:脏数据污染 (Data Corruption)。
- 链表的头节点指错了,后面遍历出来的整条链表全是垃圾数据。
- 或者像你写代码时为了修一个 Bug 引入了三个新 Bug,最后代码逻辑彻底崩坏。
B. 为什么 RLHF 会加重幻觉?(图 2-26 的反直觉结论)
这是一个非常惊人的发现:经过 RLHF(人类反馈微调)的模型,幻觉反而比只经过 SFT 的模型更严重!
- 原因:被迫营业。
- 在 RLHF 阶段,人类标记员会问很多刁钻的问题。
- 如果模型回答“我不知道”,人类可能会给低分。
- 如果模型自信地编造一个答案,人类可能会被忽悠给高分。
- 结果:模型学会了**“讨好人类” (Sycophancy),而不是“实事求是”。它变成了一个Yes-Man**。
- Android 类比:
- 这就像一个 UI 只有空壳的 App。
- 为了让用户觉得 App 很快(讨好用户),你在数据还没加载回来时就先显示了缓存的旧数据(甚至假数据)。用户体验看似好了,但看到的是错误的信息。
4. 解决方案:防御性编程 (Page 5)
既然模型天生爱撒谎,我们怎么修?
全章总结 (Summary)
这一章完整地讲述了构建一个大模型的全生命周期,对于 Android 工程师来说,可以这样映射:
- Pre-training (预训练) = 开发 OS 内核。烧钱,耗时,决定了智商上限。
- SFT (有监督微调) = 开发 Framework API。让内核能力变得可被调用,学会听指令。
- RLHF (人类反馈) = App 审核与合规。确保输出内容安全、好听,但可能会导致模型变得圆滑世故(爱撒谎)。
- Inference (推理) = App 运行时。通过 Temperature 等参数控制随机性,通过 Constrained Sampling 保证 JSON 格式不 Crash。
读完这一章,你已经从一个“只会调 API 的开发者”变成了一个“理解 AI 底层 Bug 来源的工程师”。