上周三晚上一道结算逻辑的题,我卡了四十分钟。中文题面扔进去,样例全过,第三个测试用例直接崩。翻成英文,逻辑一个字没改——一把过。
画外音:当时我第一反应是"行吧,又是玄学"。
后来翻到 9 月 16 号挂上 arXiv 的那篇,arXiv:2609.18311,八位作者,已经被 Empirical Software Engineering 收下,标题里明晃晃一个 language bias。读到一半我就明白,我那句"玄学"早有人量化过了。但它量化出来的结论,跟大多数人爱听的那句"以后 prompt 全写英文",压根不是一回事。
论文本身不展开,同行自己会去读原文。三个数据集:AtCoder、LeetCode、BigCodeBench。七个模型:GPT-4o、o3-mini、DeepSeek-V3.2、Llama-3、Qwen2.5-Coder 的 14B 和 0.5B 两个尺寸,外加 GitHub Copilot。同一批题,分别用英语、日语、中文三种表述喂进去。指标只有一个,Accuracy,通过全部测试用例才算对。
这个口径我多停半句。coding benchmark 里更常见的是 pass@k,同一道题采 k 次,有一次过就算过。那个口径下,语言带来的几个百分点很容易被采样运气淹掉,你分不清是信号还是骰子掷得好。这篇用全过,等于把运气那一项直接关了。
结论一句话:题面用的自然语言,确实会改变代码生成表现。这句我信,上周三亲手验过。真正让我坐直的是后面两条。
一,各数据集上,被该数据集官方支持的那种语言,中位 Accuracy 最高。AtCoder 是日语,LeetCode 和 BigCodeBench 是英语。
二,翻译整体上能提升 Accuracy,但提升幅度在数据集之间、模型之间不一致。
先说第一条,因为它最容易被读歪。
我第一次读到的时候,脑子里蹦出来的解释特别顺:训练语料里英文占大头,官方语言赢是"同源"呗。讲得通。太讲得通了。
顺手的解释往往就是没想透的解释。所以我多看了一眼。论文另一端还写着:AtCoder 里叙事风格的题面比例明显偏高,篇幅也更长;叙事特征和上下文长度,都被列为跟语言偏见、跟翻译缓解效果相关的因素。
这几句摞在一起,我那个"同源说"就站不全了。
一个把题面讲成故事的出题人,和一个把题面直接列成三行约束的出题人,喂给模型的是两种完全不同的输入。前者多铺几百 token 的氛围描写,后者没有。语言在这中间扮演的角色,没那么纯粹。
上下文长度那条还有第二层意思:啰嗦的题面不只是带噪声,它还占位置。context window 是有限的,前面铺三百 token 背景,后面留给约束和例子的地方就少一截。多轮追问的任务里这个代价更明显,越聊越挤。
这里我得承认,我到现在也没能力分清这条里的两个成分:一部分是模型对英语"见过更多"的分布优势,另一部分是英语题面本身"写得更紧"。这两样在论文的实验设计里是缠在一起的,我手头没有能拆开它们的数据。谁要是能一句话说清,我反倒不信。
第二条才是这篇最值钱的地方,也是"最佳实践派"最容易顺手丢掉的地方。
翻译整体有效,但效果不一致。听着像免责声明,其实是全篇信息量最大的一句。
我拆一下"把中文题面翻成英文"这个动作——它做了两件事,不是一件。
第一件,换语言。第二件,压缩。
压缩这件事藏得很深,深到我自己写 prompt 时都没意识到。看我那时那段中文题面长什么样:
我们现在在做一个结算相关的逻辑,用户下单之后可能会反悔,
这种情况下他们的订单金额会变成负数,我们希望最后统计的时候
把这些单子跳过,另外已经取消的也不要算进来……
我翻成英文的时候,手是这么动的:
- 我们现在在做一个结算相关的逻辑,用户下单之后可能会反悔,
- 这种情况下他们的订单金额会变成负数,我们希望最后统计的时候
- 把这些单子跳过,另外已经取消的也不要算进来……
+ 输入:orders[],每项 {user_id, amount, status}
+ 约束:amount < 0 跳过;status == cancelled 跳过
+ 输出:去重后的 user_id 列表
第二版里那些"我们""这种情况下""另外"——全没了。是我翻译时下意识觉得英文不该这么啰嗦,还是英文本身容不下这种叙述?我没结论。但可以确定的是,被蒸发的不是语言,是叙事。
所以"翻译提升 Accuracy"这个结果,混了两个变量。换语言一份功劳,砍叙事一份功劳。论文自己没拆。我觉得这不是疏漏,是这事本来就难拆——现实里没人会"只翻译不压缩"。
但这里有个别扭的地方,我得自己给自己找麻烦:如果蒸发叙事这么有效,那翻译后的版本应该普遍压过官方语言版本才对——官方语言明明也带着同样那些铺垫。结果并没有。官方语言版本的中位准确率还是最高的。
我目前的解释是这样:翻译有两笔账。压缩进账,翻译损耗出账——漏一个约束、术语漂移、"恰好"变"恰好但不",翻过 prompt 的人都遇到过。官方语言版本不用付损耗这笔账,代价是它也没压缩。所以谁赢,取决于原题面有多啰嗦、以及这段题面好不好翻。AtCoder 的题又长又叙事,压缩的收益压过损耗,翻译就划算;题面本来就写得紧,翻译只剩损耗,翻了也白翻。
这个解释我能对着现象讲一遍,但我没法证。它顶多算个能对上数据形状的猜测,真要立住得有人做消融实验。
扯远了,我原本只想吐槽一句"别急着把 prompt 全翻英文",写着写着又绕回机制上去了。拉回来。
这周我实际改的东西很简单,也很不性感:把自己写 prompt 的手感拆了一遍。以前我写 prompt 有个毛病,喜欢先讲背景——这项目是干嘛的、我为什么撞上这个问题、理想情况下我希望……现在这些砍到只剩三段。
输入:<字段与类型>
约束:<逐条列,一条一行>
输出:<结构或类型,不要描述性形容词>
难看吧。但难看的 prompt 喂进去,模型给的 diff 反而更像回事。
顺手说个还没想明白的观察:砍背景和翻成英文,效果重叠得很厉害。我拿中文砍完背景的版本去比英文翻译版,差距明显比之前小——小多少我说不准,我只试了几天,样本小得没脸叫统计,别拿这个当结论。😅
回到开头那道题。我到现在也不敢确定它到底是不是"语言"的锅——我翻的时候,手也顺带把叙事压了,两件事同时在动。严格讲,我那次"一把过"根本没验证任何东西。
留一条我打算照做的规则,就一行:
写 prompt 卡住的时候,先别问"要不要翻成英文",先问"这段是在描述问题,还是在讲背景"。背景删掉,再看还卡不卡。还卡,再考虑语言。