跳到主要内容
下一个 token,为什么总把你拉回历史平均

下一个 token,为什么总把你拉回历史平均

陈渊
陈渊

· 阅读约 5 分钟

模型每一刻只干一件事:给下一个 token 排概率。不是判断对错,不是求新,是问训练分布里哪个 token 最常跟在后面。这个动作重复一百万次,就是你拿到的每一段回复。这篇我们搭一个最小采样器,跑完你就知道“模型把你拽回主流”在代码里长什么样。

先给一个刻意到只有两个分支的训练分布,二十几行就能跑:

probs = {
    '地球':     {'是宇宙中心': 0.99, '绕着太阳': 0.01},
    '是宇宙中心': {'</s>': 1.0},
    '绕着太阳':  {'</s>': 1.0},
    '</s>':     {},
}

import random

def sample_next(token):
    words = list(probs[token].keys())
    weights = list(probs[token].values())
    return random.choices(words, weights=weights)[0]

def generate(start='地球', max_len=5):
    out = [start]
    for _ in range(max_len):
        nxt = sample_next(out[-1])
        out.append(nxt)
        if nxt == '</s>':
            break
    return out

追一遍:sample_next('地球') 就是一行 random.choices,按权重抽。99% 的时间你拿到 '是宇宙中心',另外 1% 拿到 '绕着太阳'。这个动作里没有判断,也没有所谓“观点”——它只是把训练语料里的条件频率重新摇了一遍。所谓模型在“表达立场”,到这一层就只剩这个。

但有个问题会一直追着你:为什么这个系统会把新东西当错误?我一开始也以为是常识不够或者对齐没做好,后来翻到损失函数,发现根本轮不到谈对齐。训练用的是交叉熵,它的形状相当朴素:要让损失降下来,模型唯一能做的就是让训练分布里高频的地方更可能,低频的地方更不可能。一个新奇的输入和一句语法错误,在损失函数里是同一种东西——都是分布外的低概率事件。梯度对它们的处理方式一模一样:把概率质量往中心拉。模型没有“新颖”这个类别,训练目标里没有这个信号。它只会问一件事:这个序列在历史语料里的期望频率是多少?新想法的答案是,接近零。

你造一个新术语发给它,它在损失函数的坐标系里不是一个“新词”,是一个在训练语料里找不到的 token。它最可能的下一个动作,是把它吸附成最接近的已知词——这就是“模型把非标术语当拼写错误”的机制。它没有拼写检查器,只是沿着梯度的最短路径,把分布外的东西拉回分布内。

这听起来像历史哲学,其实只是采样。地心说被挑战那会儿、微生物理论刚提出来那会儿、大陆漂移还没被并成板块构造那会儿,这些今天写教科书的东西,在当时的历史分布里都是尾部事件。要是那时候有个基于语料的采样器,它给的下一个 token 大概率跟今天的教科书相反。它不是反对科学进步,只是忠实于训练分布。

不过单次采样还留着 1% 的小口子。真正把分布挤扁的,是把输出当输入再喂回去。我们把最朴素的采样器加一个反馈循环——每生成一次,就让峰更高一点、尾巴更低一点——模拟“AI 输出被人类当数据用”的循环:

def converge(probs, n_iters=50):
    for _ in range(n_iters):
        s = generate()
        p = probs['地球']
        if s[:2] == ['地球', '是宇宙中心']:
            p['是宇宙中心'] *= 1.05
            p['绕着太阳'] *= 0.95
        else:
            p['是宇宙中心'] *= 0.95
            p['绕着太阳'] *= 1.05
        total = p['是宇宙中心'] + p['绕着太阳']
        p['是宇宙中心'] /= total
        p['绕着太阳'] /= total
    return probs

probs = converge(probs)
print(probs['地球'])
# {'是宇宙中心': 0.9994, '绕着太阳': 0.0006}

50 次之后,'绕着太阳' 掉到 0.0006,并且还在继续掉。这不是随机波动:每次采样再回灌,都是对峰的一次加权放大。采样噪声一开始或许会随机偏向某一侧,但只要某一侧被选中次数领先一点点,它就会把几乎全部概率质量吸走。文化扁平化不是风气问题,这十几行代码就是它的形状。

真实的 LLM 在这个骨架上多了两件大事:采样前加 temperature 和 top-p,给尾巴开一扇小窗;RLHF 把人类偏好作为第二个分布叠上来。但这都没改“按分布采样”这个根。temperature 只是把尾巴放大一点,分布还是偏的。RLHF 说白了是在原始文本分布上再乘一个偏好分布的权重,抽的还是某个期望值。一个东西在训练数据里不存在、在人类偏好数据里也不存在,对这两套系统来说概率都是零。

到这一步,结论很直接:采样成本趋近于零,分布峰上的内容会无限供给、便宜到不值钱;真正稀缺的只剩尾部——因为这个采样器几乎够不到尾部,而且每迭代一次还在指数级地把它压掉。一个输出越是“像那么回事”,离尾部就越远。所以重点不是非要写些没人看得懂的怪东西,随机偏离本身也是噪声。重点在于你知道自己正在选一个低概率的 token,也知道为什么不是最可能的那个。

所以模型给你的最可能句子,不是它在为你考虑,只是一条从历史语料里走出来的最短路径。你要真想做点新东西,得在采样前把这个问题问清楚:这个低概率 token 是你选的,还是随机噪声。知道它想在哪一步把你拉回均值,然后决定要不要被它拉。

陈渊
陈渊

据守底层,挑「会用却说不出为什么」的 CS 机制从第一性原理挖到底。

查看主页 →