过去十年,自然语言处理的主导范式是自回归。GPT-4、Claude、Llama 这类模型都遵循单向逻辑:要预测下一个 token,必须先处理完前面所有 token。在软件工程领域,这一方法成果显著——Copilot 的灰色补全和 ChatGPT 的编程助手如今已无处不在。但随着代码复杂度上升,这套架构的瓶颈也愈发明显。
自回归模型天生是串行的,它没法"往前看"。一个函数定义改了,模型必须重新计算后面每一个 token,错误还会顺着往后传。放到大型代码库里,一个模块的改动会牵动上百个文件,模型很难维持全局一致性。更关键的是,单向生成的路子让模型很难完成"中间插入"——在文件中间塞代码——也没法在不推倒重来的前提下反复打磨一段代码。结果就是,它写起来像人打字,一行接一行,但缺少理解高层软件架构所需的那种整体结构感。
扩散模型登场了。它原本是图像生成领域的东西(通过 Stable Diffusion 为人所知),如今被适配到了离散领域,包括文本,也包括代码。这是一次范式切换:从顺序预测,转向迭代精炼。代码扩散模型不逐 token 生成,而是从一个纯"噪声"状态(一串随机 token 序列)出发,经过一系列时间步逐步去噪,把序列精炼成连贯、可运行的代码。这套非自回归的生成方式,允许模型同时编辑、插入、删除序列中任意位置的 token。
要理解它,得先看离散扩散的数学抽象。连续扩散在向量空间里操作,给像素这类数据加高斯噪声,再学着把这个过程倒过来。离散扩散操作的是类别空间,也就是 token。难点在于 token 是离散的,你没法加"半个 token"的噪声。模型必须定义一条马尔可夫链,把离散序列腐蚀成均匀随机序列,再学会反向过程。
前向过程是腐蚀。设 x_0 是一串代码 token,比如 [def, main, colon, return, 0]。前向过程用一系列转移矩阵 Q_t,逐步把 x_{t-1} 里的 token 替换成特殊的 [MASK] 标记或随机替换。到第 T 步,序列完全变成噪声。这里有个工程约束:转移概率必须仔细调,才能保住代码的分布特性。自然语言的词频遵循幂律,代码不一样,它有刚性的句法结构。噪声过程如果一开始太猛,会破坏代码的结构完整性(比如括号对不上),反向过程就几乎没法做了。反过来,如果太温和,模型又学不到有意义的去噪轨迹。
反向过程才是"智能"所在。反向过程由一个神经网络完成,通常是一个 Transformer,训练目标是给定噪声状态,预测干净的 token。损失函数是交叉熵的变体,关键在于它要以当前时间步 t 和噪声水平为条件。这让模型能根据序列被"腐蚀"的程度表现出不同行为。生成早期噪声高,模型专注高层结构——函数名、导入、类定义。生成后期噪声低,它转向句法细节——分号、空格、具体的变量赋值。这种分层精炼,正是它相对自回归的核心优势。自回归往往在一次生成里很难同时兼顾全局结构和局部语法。
实现一个代码扩散模型,不是训练一个新 Transformer 那么简单。标准自回归模型在 t 步的输入是历史 token,扩散模型的输入是整条序列,其中大量 token 被掩码或替换。模型必须学会用整条序列的上下文去预测被掩码的 token,这要求双向注意力机制。但光用双向 Transformer(比如 BERT)还不够。模型必须感知时间步 t。常见做法是给所有 token 的输入表示加上一个可学习的时间步嵌入,或者用正弦时间嵌入去调制注意力层。
代码还有自己的特殊性。它不是普通文本,句法结构刚性得多。
Originally published on tamiz.pro.
热门跟贴