未充分训练的代码模型行为解析 | AI生成和翻译

Home 2026.06

问题: 现在我用了一个真实的57个token的代码前缀作为提示,输出不再是纯空白——而是类似代码的token(casedata_typestaticreturn 1;p_count = 0、C/C++片段),但仍然不连贯。该如何解释这种情况?

回答:

这个输出讲述了一个与之前完全不同的、且令人安心得多的故事。看看发生了什么变化:

模型现在能够根据上下文进行条件生成了。 它不再坍缩到unigram先验。因此,你之前的垃圾输出主要是短上下文/采样问题导致的,而不是模型权重失效。模型确实学到了真实的东西。三个具体的证据:

  1. 它识别出了编程语言。 提示是Python(for _ in range(...)),但续写却偏向C/C++staticreturn 1;case<<流操作符、0x十六进制字面量)。这并不是随机的——这表明模型识别出了底层/语法性的代码token,并生成了看似合理的C代码。github-code数据集中C/C++/Java占比很大,因此模型最强的先验知识就在这些语言上。它正在生成训练最多的语言,这正是训练不足的代码模型会做的事情。

  2. 局部语法正确,全局结构错误。 return 1; }static ... (p_count = 0, p_len)case ...:——这些单独看都是格式良好的C语言片段。模型学会了token级别和短跨度的语法return后面跟什么、case后面需要一个:<<在C++流中可以链式调用)。它没有学会的是长程连贯性——跨行匹配花括号、保持函数语义一致、坚持使用同一种语言。这正是124M参数、PPL约为32的模型预期的能力画像:具备良好的局部统计能力,但缺乏全局规划能力。

  3. _和混淆标识符的泛滥static_p__p_INT_id_R_REp_dp___p_p)是你的分词器在作祟。GPT-2 BPE会把像 p_countdata_type__init__ 这样的标识符以下划线为界切分成碎片,所以模型会输出大量下划线,因为语料在token空间里看起来就是这个样子。这是切换到StarCoder分词器的最有力论据——它能正确处理代码标识符和连续的空白符,并能立即清理掉这一大堆视觉垃圾。

那么,现在的实际诊断结果是什么?

你可以废弃之前”问题2:空格主导”和”问题3:无法学习模式”的结论了。两者都不成立。修正后的图景如下:

之前的说法 修正后的判断
空格token主导是病理性的 不——这是3个token上下文的产物。真正原因是生成循环的问题,现已修复。
模型无法学习模式(欠拟合) 错误。它学会了局部的C/C++语法。是训练不足,而非能力不足
数据过于杂乱 仍然正确,并且现在是主要的剩余杠杆。
(新增)分词器在切割标识符 现在可以明显看出这是第二重要的杠杆。

你现在看到的是一个正常训练不足的小型代码模型,配了一个对代码不友好的分词器。这是nanoGPT-124M在此损失水平下对原始github-code数据的预期输出。没有任何东西是坏的。只是目前还不够好。

为什么在PPL为32时会有如此程度的不连贯性

困惑度(Perplexity)为32意味着:平均而言,在每个位置上,模型的确定性等同于从大约32个token中均匀随机选择。对于局部语法(return后面、case后面),真实分布是尖锐的,所以模型能正确生成。但对于”函数内部40个token之后的有意义的标识符是什么”,分布实际上是宽泛的,模型毫无头绪——所以它会采样出看起来合理但毫无意义的token。结果就是局部合乎语法、全局毫无意义的代码。对于这个损失水平来说,这是教科书式的现象。PPL约为20的CodeParrot-small仍然会产生半残的函数;你的模型在困惑度上要差1.6倍,这就是可见的差距所在。

现在哪些措施能起到最显著的效果,按优先级排序

  1. 分词器 → 切换到StarCoder/StarCoder2分词器。 消除 __p__p 这种标识符混乱,将空白符序列作为单个token处理(释放上下文,降低损失)。单位工作量下最显著的视觉质量提升。仅此一项就能让输出看起来更像真正的代码。
  2. 数据 → 使用 the-stack-dedup,并限制在2–3种语言(比如Python + C)。在124M模型下使用更少的语言 = 更密集的信号 = 模型不会在函数中途切换语言。去重加许可证过滤能降低噪声基底。
  3. 然后训练更长时间。 损失仍在下降;使用更干净的数据和更好的分词器,渐近线会更低,因此更多的训练步骤现在真正能带来回报。

不要加入自然语言混合——目前阶段和针对这个症状,这仍然无关紧要。

一个值得快速验证的确认方法

相同的57个token提示进行贪心解码(argmax,温度→0)。如果贪心解码给你一段比上述采样版本更连贯的C代码,那就证实了权重没问题,剩余的不连贯性仅仅是采样熵在训练不足模型上的体现——此时,分词器和数据无疑就是你整个路线图的关键了。

# argmax解码,无采样噪声
logits = model(idx_cond)[0][:, -1, :]
next_id = logits.argmax(dim=-1, keepdim=True)
idx = torch.cat([idx, next_id], dim=1)

结论: 第一次的输出看起来像一个死掉的模型;这次的输出证明它是一个活着的、训练不足的、分词器糟糕的模型。这是一个好得多的处境——修复方法是分词器和数据,这两点你都已经识别出来了,而且都不需要比你现在拥有的更多的GPU资源。


Back Donate