今天出去散步,主要是想了,和她交流的时候不知怎么的话题转向了我的工作,在输出 tokens 的同时莫名有种把 RFT 的自我训练的感觉。

感觉自己的显式思维链(CoT)又能够充分地深入了未知的领域,交杂着我所有的 19 年来的枯燥噪声的历史而蒸馏(Distill)出来了少量但是深刻的思维链路。

有感而作此篇。

核心(Core)只有一个:天下没有免费的午餐,任何事情都具有代价(Everything Comes with a Price)

这个代价可能是显式的,也可能是隐式的;可能是未来的,也可能是透支过去历史的;夹杂着情感的,也可能是物质的。

但是,你所付出的所有的成本都会被记录,而且最重要的是——

开弓没有回头箭。永远是一个向前的矢量光锥,锥内无限因果交杂,但是锥外就是平行线。每一个十字路口的抉择,即使客观环境给你提供了无数次 trials & errors 的机会,最终命中的只有一次。

第一次终身注定的因果链接:前缀和——每个 OIer 的入门课

让我们从小的时候开始吧,这是每一个 OIer 都必须学会的一个入门算法。

引用自 OI Wiki - 前缀和 & 差分:

前缀和与差分是算法竞赛中常用的技巧,前者用于快速求区间和,后者用于高效进行区间修改。

为方便讨论,默认数组 {a_i} 下标从 1 开始,并补充定义 a_0 = 0。

前缀和可以简单理解为「数列的前 n 项的和」,是一种重要的预处理方式。对于长度为 n 的序列 {a_i},如果要多次查询区间 [l,r] 中数字的和,就可以使用前缀和:
Si=∑j=1iajS_i = \sum_{j=1}^{i} a_j
前缀和定义 · 摘自 OI Wiki
它可以通过递推关系式 S_0 = 0, S_i = S_{i-1} + a_i 逐项计算得到。要询问区间 [l,r] 内的和,只需要计算差值:
S([l,r])=Sr−Sl−1S([l,r]) = S_r - S_{l-1}
区间和 = 两个前缀之差 · 摘自 OI Wiki
就这样,通过 O(n) 时间预处理,能够将单次查询区间和的复杂度降低到 O(1)。

哎呀!按理说咱们这个前缀和能显著降低区间求和的时间复杂度,但是代价就是你的写入/删改的时间复杂度被显著提高——每改一个元素,后面所有的 S_i 全部作废,O(n) 重建。这是向我们用户层面的范式转移:查询侧的免费午餐,是写入侧替你买的单。

来自数据结构的回声:从前缀和到树状数组——一棵被压扁的二叉树(非战斗人员慎入)

既然聊到代价,就把这个层面扩展到数据结构那里,其实在这里代价才是更加显式的。先看前缀和的参考实现(同样出自 OI Wiki):

text
int n;                // 数组大小
std::vector<int> a;   // 原数组(下标从 1 开始)
std::vector<int> ps;  // 前缀和数组

void prefix_sum() {
  ps = a;
  for (int i = 1; i <= n; ++i) {
    ps[i] += ps[i - 1];   // S_i = S_{i-1} + a_i
  }
}

int query(int l, int r) { return ps[r] - ps[l - 1]; }  // O(1) 查询
前缀和参考实现 · 摘自 OI Wiki

两个操作,复杂度极不对称:

text
操作     | 前缀和               | 朴素数组
---------+----------------------+---------
区间查询 | O(1)                 | O(n)    
单点修改 | O(n)(后缀全部重建) | O(1)    
账单一览 · 前缀和 vs 朴素数组

复杂度没有消失,它只是从查询账本挪到了写入账本。总成本守恒,区别只在于谁来付。

二叉查找树:平衡的代价是旋转

那能不能两边都便宜?第一个想法是二叉查找树(BST):把区间和信息挂在树节点上,查询和修改都走树高 O(h)。理想情况下 h = log n,两边各付 O(log n)——看起来很公平。

但 BST 的隐式代价藏在「平衡」两个字里:一旦插入序列有序,树退化成链,h = n,两本账同时爆炸。为了维持平衡,AVL 树和红黑树引入了旋转操作——每次插入删除最多付出若干次指针翻转和颜色翻转。你看到的 O(log n) 查询,是背后无数次旋转预付的会员费。

树状数组:把二叉树蒸馏进一个数组

然后就是很显然的树状数组(Fenwick Tree / Binary Indexed Tree)这种特定类型的算法实现。它堪称「代价转移」这门艺术里的杰作:它是一棵二叉树,但被蒸馏成了一个纯数组下标的游戏。 参考 OI Wiki - 树状数组:

这个算法的思想大致是任何前缀 [1, x] 都可以拆成不超过 log n 段预先算好的区间。比如想求 a[1…7] 的和,不必加 7 个数——已知 A = a[1…4]、B = a[5…6]、C = a[7…7],三个数一加就完事。

数组 c 就存这些「预制区间」的和。c[x] 管辖的区间是 [x - lowbit(x) + 1, x],其中 lowbit(x) 是 x 二进制最低位的 1 及其后面所有 0 组成的数(非战斗人员慎入):

text
int lowbit(int x) {
  // lowbit(0b01011000) == 0b00001000
  return x & -x;   // 补码性质:-x 是 x 取反加 1,相与后只剩最低位的 1
}
lowbit:取二进制最低位的 1

查询前缀和:从 c[x] 开始往前跳,每次 x ← x - lowbit(x),跳到 0 为止:

text
int sum(int x) {
  int res = 0;
  for (int i = x; i >= 1; i -= lowbit(i)) res += c[i];  // 最多 log n 段
  return res;
}

int query(int l, int r) { return sum(r) - sum(l - 1); }  // 区间化为两个前缀之差
前缀和查询:最多 log n 段

单点修改:a[x] 变了,所有管辖区间包含 x 的 c 都要更新——往上跳,每次 i ← i + lowbit(i):

text
void add(int x, int y) {
  for (int i = x; i <= n; i += lowbit(i)) c[i] += y;    // 最多 log n 个上级
}
单点修改:向上更新所有管辖区间

账单一览:

text
操作     | 前缀和 | 树状数组 | 平衡 BST           
---------+--------+----------+--------------------
区间查询 | O(1)   | O(log n) | O(log n)           
单点修改 | O(n)   | O(log n) | O(log n) + 旋转常数
代码量   | 5 行   | 10 行    | 100+ 行            
账单一览 · 前缀和 vs 树状数组 vs 平衡 BST

注意树状数组付出了什么:查询从 O(1) 涨回 O(log n),而且信息必须「可差分」(有逆运算)——它能维护加法、异或,却维护不了 max、gcd(这些没有逆运算,区间无法靠相减还原)。它甚至连「任意区间」都不直接支持,必须拆成两个前缀之差。

这就是隐式代价的标准形态:能力范围被悄悄砍了一刀,换取复杂度账面好看。 天下没有免费的数据结构。

第二次相遇:Quora 上的 ML 老资历

第二次与这个概念相遇是在 Quora 上,有老资历发帖子(一位自称在 ML 领域活跃发表了 35 年的研究者):

Sadly, yes, and therein lies one of the deepest problems with the field right now. By my conservative estimate, more than 10,000 papers are published in ML every year (roughly say 30 per day), and almost every paper without exception introduces a new algorithm. Heck, I'm as guilty of this sin as the next ML researcher...

Let's try to understand why this is a problem. Warning: the below discussion may cause you deep anxiety as an ML researcher or practitioner! ... I've spent 40 years thinking about ML, and lately been having a "have I wasted my life?" kind of crisis.

I heard a fascinating talk a few years ago by a Harvard professor of political science Gary King... being the thorough scholar that he is, asked his graduate students to implement every clustering algorithm in the literature... they discovered more than 250 clustering methods. So, they write an R package to compare all of them. What did they find? Was there a "best" algorithm? Of course not! Each algorithm behaved in a different quirky way.

...The biggest payoff comes to the pioneers. Every variant of a previous method gets even less credit. Research impact is a submodular function, meaning the law of diminishing returns applies. Ian Goodfellow rightly got a lot of kudos for inventing generative adversarial networks... There are easily a hundred or more variants of GANs. People get attracted to GANs the way moths get attracted to light. Sadly, few if any of these variants will get much long term recognition.

There's a beautiful set of theorems in optimization and machine learning called the "No Free Lunch Theorems" (really, I kid you not). Quoting from Wikipedia, essentially this theorem says there's never going to be a "best" machine learning algorithm:

In computational complexity and optimization, the no free lunch theorem is a result that states that for certain types of mathematical problems, the computational cost of finding a solution, averaged over all problems in the class, is the same for any solution method. No solution therefore offers a "short cut".

NFL 定理本身:所有算法平均一样笨

这就是 Wolpert & Macready 1997 年证明的东西。用公式说,对任意两个优化算法 a_1, a_2,迭代 m 步后:

∑fP(dmy∣f,m,a1)=∑fP(dmy∣f,m,a2)\sum_f P(d_m^y \mid f, m, a_1) = \sum_f P(d_m^y \mid f, m, a_2)
No Free Lunch 定理 · Wolpert & Macready, 1997

在所有可能问题上平均,任何两个算法——包括随机搜索和精心设计的梯度下降——表现完全一样。(参见 no-free-lunch.org 与 Wikipedia。)

这里藏着第一个平常人难以察觉的隐式影响:NFL 定理不是说「学习是不可能的」,而是说无偏置的学习是徒劳的。这个洞察可以追溯到 1739 年的休谟——「即使观察到事物频繁或恒常的联结,我们也没有理由对经验之外的对象做出任何推断」。Mitchell (1980)、Schaffer (1994)、Wolpert (1996) 陆续用越来越严格的数学证明:bias-free learning is futile。

所以 CNN 之所以在图像上碾压一切,是因为它预付了代价——把「空间局部性」这个假设烧进了架构;Transformer 预付了「序列注意」的假设。你在这个问题类上赚的每一分,都是从互补问题类上亏的。 专精的隐形成本是:对违反你假设的任务,你的鲁棒性更差。

还有一层更隐的:NFL 对交叉验证和 AutoML 同样成立——在均匀先验下,用交叉验证在一组固定算法里做选择,平均而言并不比不用强。「避免过拟合」「偏爱简单模型」这些口耳相传的工程直觉,在所有可能问题上平均起来也是对错各半。AutoML 没有消灭代价,它只是把「人选算法」的代价换成了「机器搜超参」的电费。

三源搜索:各研究领域账本上的隐式午餐费

为了把「隐式影响」挖到底,我用三个独立来源(通用搜索、学术语义搜索、深度研究搜索)交叉检索了这个概念在各领域的变体。以下每个领域,重点都不是那个显而易见的 trade-off,而是平常人察觉不到的那本暗账。

经济学:零价格的幻觉——你付的不是钱,是你不知道自己付了

经济学的版本是最古老的:TANSTAAFL(There Ain't No Such Thing As A Free Lunch),源自 19 世纪美国酒馆——「免费午餐」配的是咸得发齁的菜,你终究会买更多的酒。午餐的成本被记进了酒水账本。

但数字时代把这个把戏升级成了隐形的。几篇关键文献:

  • BEA 工作论文《Value of Data》(Nirei & Yamana, 2019):Facebook–Cambridge Analytica 丑闻证明了数字世界没有免费午餐——平台用「免费」服务交换用户数据,而消费者缺乏对自己数据估值的知识,平台因此捕获了数据价值链上的绝大部分收益。
  • Landwehr, Borning & Wulf《The High Cost of Free Services》:监视资本主义的间接成本清单——隐私丧失、对国家和企业监视的基建支持、自动化行为操纵、对民主进程的侵蚀、消费主义附带的环境成本。这些成本不出现在任何一张账单上。
  • Newman《The Myth of Free》:最隐的一层是零价格效应(Zero-Price Effect)——人对「0」有非理性的偏好,会系统性地高估免费商品、低估自己付出的非货币成本(注意力、数据)。你不是在理性地交换,你是被「免费」两个字催眠着交换。
  • Raskovich & Yun《Zero-Price Platform Services》的纠偏最精妙:NFL 原则只说「资源必然有人付费」,但它从不告诉你付钱的是谁。把「服务有成本」直接推断为「消费者在用隐私买单」,在逻辑上是不成立的——多边平台可能让广告主侧承担全部成本。这是一场隐式影响里的隐式陷阱:连「没有免费午餐」这个箴言本身都可能被误用成免费论据。There is no free lunch in applying the No Free Lunch Principle.

统计与机器学习:归纳偏置的暗账

上面已经展开:NFL 定理 + 偏差-方差权衡。隐式影响在于——每一次你在 benchmark 上刷出新 SOTA,你都在对这个 benchmark 的分布预付专精代价,而这个代价要等到部署分布漂移时才收到账单。250 个聚类算法没有最好的一个,只有各自怪癖的一群。

演化与生态:生态位即账单

Brett Wick 在 Navigating Proof Space 里写得很好:演化是自然界对 NFL 最伟大的演示。猎豹的速度以耐力为代价;大象的体型以惊人的热量摄入为代价;北极狐的厚毛在苔原是生存保障,在沙漠是死刑。没有万能超级生物,只有为特定生态位付过费的生物。 隐式影响:你看到的每一个「适应性优势」,都是一份已经签下的环境对赌协议——环境一变,资产变负债。

热力学与信息论:连「忘记」都要付电费

最底层、最反直觉的一笔账:Landauer 原理指出,擦除一比特信息至少耗散 kT ln 2 的能量。麦克斯韦妖看似能白嫖熵减,但 20 世纪的物理学家们最终发现,妖怪「记账」(存储观察结果)和「销账」(擦除记忆)本身就要付热力学成本。信息不是抽象的免费品,它是物理的。你以为删除是免费的?宇宙在收你的散热费。

人生策略:成功学承诺的就是免费午餐

Wick 那篇文章的副标题就是「为什么不存在通用的职业成功策略」。任何「秘诀」都只是在某个特定问题分布上预付过代价的策略。在一个被 NFL 统治的宇宙里,智慧不在于找到免费午餐,而在于理解每一餐的价格,然后有意识地选择愿意付哪些账单。

解剖二:SFT 与 RL——把代价写进参数的训练循环

这部分引用李博杰的《深入理解 AI Agent》(ai-agent-book,第 8 章 · 模型后训练)。书里给出了一张后训练全景地图,每个阶段一栏「典型代价」——看,连教科书都开始明码标价了:

text
阶段         | 用什么数据            | 优化目标                         | 学到什么             | 典型代价                     
-------------+-----------------------+----------------------------------+----------------------+------------------------------
预训练       | 海量原始互联网文本    | 预测下一个词                     | 语言规律、世界知识   | 极高(数百万~数千万美元)   
Mid-training | 目标领域语料          | 预测下一个词(全部 token)       | 补齐领域知识与能力   | 中到高                       
SFT          | 几千~几万条示范对    | 预测下一个词(只在回答上算损失) | 指令遵循、格式、协议 | 低(几小时~几天)           
RL           | 任务、环境 + 奖励信号 | 最大化期望奖励                   | 可迁移的决策策略     | 高(常是 SFT 的几十~上百倍)
后训练全景地图 · 摘自《深入理解 AI Agent》第 8 章

SFT:模仿的代价是遗忘

SFT 的本质,书中一句话讲透:换了数据、只在回答上算损失的「预测下一个词」。代码解剖——整个 SFT 和预训练的唯一区别,就在 mask 那一行:

python
import torch.nn.functional as F

def sft_step(model, batch, optimizer):
    input_ids = batch["input_ids"]            # [prompt + answer] 拼接
    labels = input_ids.clone()
    labels[batch["prompt_mask"]] = -100       # ← 关键一行:prompt 不参与损失

    logits = model(input_ids).logits
    loss = F.cross_entropy(
        logits[:, :-1].reshape(-1, logits.size(-1)),
        labels[:, 1:].reshape(-1),
        ignore_index=-100,                    # 被 mask 的位置直接跳过
    )
    loss.backward()
    optimizer.step()
    return loss
SFT 与预训练的唯一区别:mask 掉 prompt 的损失

ignore_index=-100 就是 SFT 的全部秘密:老师只批改答案,不批改题目。 便宜、快、稳——但隐式代价是:几千条 QA 对只覆盖有限问法,SFT 擅长固化「如何访问与表达」,不擅长注入新知识,还会在有限示范上过拟合(书中的 SFTvsRL 实验:同预算下 SFT 在分布内记住答案,分布外泛化输给 RL)。模仿的隐形成本是忘记如何探索。

RL(GRPO):探索的代价是方差和电费

RL 不再模仿标准答案,让模型自己试,好的调高概率、差的调低。书中给的最小直觉:PPO 用概率比裁剪抑制单次更新把模型带偏;GRPO 干脆不训练价值网络,用「同一问题的多条回答互相比较」当基线。一个最小的 GRPO 循环解剖:

python
# 对同一个 prompt 采样 G 条 rollout —— 代价的第一笔:G 倍推理开销
rollouts = [sample(model, prompt) for _ in range(G)]
rewards  = [reward_fn(prompt, r) for r in rollouts]   # 环境/验证器打分

# 组内归一化当优势:同一问题内互相比较,省掉价值网络(以及它的训练成本)
mean, std  = np.mean(rewards), np.std(rewards)
advantages = [(r - mean) / (std + 1e-8) for r in rewards]

for r, A in zip(rollouts, advantages):
    logp     = model.logprob(prompt, r)
    ratio    = torch.exp(logp - logp_old)                    # 与旧策略的概率比
    obj      = torch.min(ratio * A,                          # PPO 式裁剪:
                         torch.clamp(ratio, 1-eps, 1+eps) * A)  # 抑制一步走太远
    loss = -obj.mean()
    loss.backward()
最小 GRPO 循环:组内基线 + PPO 裁剪

账单一览,SFT vs RL(数据来自书中对照表):

text
维度       | SFT                      | RL                        
-----------+--------------------------+---------------------------
优化目标   | 最大化标注答案概率       | 最大化期望奖励            
样本效率   | 高(几千条见效)         | 低(几十~上百倍成本)    
稳定性     | 高、收敛快               | 低、易震荡                
分布漂移下 | 取决于示范覆盖,易过拟合 | 迁移更好                  
隐性风险   | 忘记探索                 | 奖励 hacking:对捷径过拟合
账单一览 · SFT vs RL(书中对照表)

RL 的隐式代价是双层的:显式的采样预算(G 倍 rollout 电费),加上隐式的——奖励函数一旦与真实目标有偏,RL 会以同样的热情去强化那个偏差。书中还有一句工业界最反直觉的经验:数据和环境比算法更重要——很多场景下数据质量到位了,你根本不需要做 RL;而 pass@k 近零时直接上 PPO/GRPO,「通常只会消耗采样预算」。

连训练模型都逃不掉:SFT 用「不会探索」换「便宜稳定」,RL 用「百倍成本 + 奖励 hacking 风险」换「泛化能力」。 天下没有免费的智能。

收尾:复杂度守恒

今天回来的时候我想明白了一件事。

前缀和把成本从查询挪到写入;树状数组把成本从写入挪回一点查询、再砍掉能力范围;SFT 和 RL 在模仿与探索之间互相转账;免费搜索用注意力付账;演化用生态位付账;连擦除一个比特都要向宇宙缴纳 kT ln 2。

复杂度不会消失,代价不会消失,它们只会转移账本。所谓成长,也许就是慢慢学会在每一次签字之前,问一句:这顿饭,记在哪本账上?

开弓没有回头箭。锥内因果交杂,锥外平行永不相交。

Everything comes with a price

世界。。。毁灭在我的眼前


参考来源