..

盘古之白

漢學家稱這個空白字元為「盤古之白」,因為它劈開了全形字和半形字之間的混沌。另有研究顯示,打字的時候不喜歡在中文和英文之間加空格的人,感情路都走得很辛苦,有七成的比例會在 34 歲的時候跟自己不愛的人結婚,而其餘三成的人最後只能把遺產留給自己的貓。畢竟愛情跟書寫都需要適時地留白。1

以上是「盘古之白」的项目介绍。最早接触排版,看了《中文文案排版指北》,然后知道了「盘古之白」这个项目。看这份材料的那台笔记本的硬盘的原子估计都和秦始皇呼出的原子结合成新的分子了。

要提这个,是因为 Qwen 把问题重新摆到面前。在实验固定框架测试模型效果时,接入 Qwen 后整个 trajectory 急剧变长,错误率奇高。查了一下,发现是 Qwen 在生成路径时会在 Latin 字符和非 Latin 字符之间插入空格。比如 /home/user/Downloads/你的路径your-path 被生成成了 /home/user/Downloads/你的路径 your-path。此事在 qwen-code issues #1897 亦有记载。虽然,写非 ASCII 字符路径是一件让人讨厌的事,但是用户真要这么做,你也不能打他一顿。

看到这个现象,就闪现应该是训练数据的问题,而数据根源很有可能要追溯到那份《中文文案排版指北》和盘古之白了,先简单说下技术层面的事。

为什么发生

第一层:Tokenizer 的结构性偏置

BPE 中,句中位置的英文单词 token 大多以 Ġ(词首空格)为前缀;词内子词、句首单词等形态的 token 不带此前缀。这是 Byte-level BPE 的通用特性,并非 Qwen 独有。当模型在中文 token 后面生成英文时:

  • 商品 → 下一个 token?
    • ĠSKU(概率高,这是「SKU」在词表中的主要形态)
    • SK(概率低,这通常只在英文词中间出现)

结果:商品 SKU(自动带空格),而期望是 商品SKU(无空格)。

模型要生成无空格的 商品SKU,就必须选择不带 Ġ 前缀的 token,但这些 token 在训练时几乎只出现在英文接英文的 subword continuation 场景中。所以 tokenizer 本身就在结构上倾向于在 CJK-Latin 边界插入空格。

第二层:训练数据的偏置

这里就轮到「盘古之白」登场了。

  • 盘古之白风格:商品 SKU 的 GMV 增长了 10%
  • 无空格风格:商品SKU的GMV增长了10%

中英混排加空格,其实是中文出版行业的通用规范。CY/T 154—2017《中文出版物夹用英文的编辑规范》对此有明确规定,苹果、微软、谷歌的中文本地化文档也均遵循此标准。「盘古之白」是这一规范在开源程序员社区的标志性推广项目,把规范做成了工具,让 GitHub 中文 README、技术博客大量自动合规。

Qwen 的训练数据有大量的网页、代码、文档,这些来源中带空格的写法占压倒性多数。模型在 SFT/RLHF 阶段也大概率使用了遵循此规范的标注数据。

两层叠加后导致了这种效果。

Tokenizer 层:英文 token 自带 Ġ 前缀 → 结构上倾向加空格
      ↓
数据分布层:训练语料中「中文 English」远多于「中文English」
      ↓
SFT/RLHF 层:标注数据进一步强化盘古之白风格
      ↓
模型输出:中文和英文之间大概率带空格

为什么 GPT 和 Claude 很少有这个问题

GPT 和 Claude 也有,只是程度轻得多。差异主要来自两个层面:

1. Tokenizer 词表的能力不同(分水岭在 BPE 阶段,不在正则)

一个常见的误解需要先纠正:GPT-4 cl100k_base 和 Qwen 的 pre-tokenization 正则,核心分支都使用了 \p{L}+,Unicode 标准把 CJK 汉字和拉丁字母统一归为「字母」(Letter)类。对于无空格的连续中英混排文本,两者预分词都会将其作为单个 chunk 处理,不会强制切开中英边界。两套完整正则在其他细节上存在差异,但对中英边界的处理结果影响有限。2

我爱Qwen模型(中英之间无空格)为例:

正则核心分支:[^\r\n\p{L}\p{N}]?\p{L}+

我、爱、Q、w、e、n、模、型 均属于 \p{L} 字符类别
→ 预分词输出:["我爱Qwen模型"](一个整体 chunk)

两者在这一步得到了相同的 chunk。分水岭在第二阶段,BPE 词表。

cl100k_base 以英文语料为核心构建,仅包含少量中文单字和极少量高频中文词,绝大多数中文会被拆分为字节级片段,无法形成稳定的多字子词,中文编码效率远低于 Qwen。英文部分合并得很好,中文却碎成零件。

Qwen 的词表超过 15 万条,包含大量原生中文子词。同样的 chunk 进来,BPE 能直接索引到 我爱Qwen模型,在中英边界自然断开,高效合并。这是由词表结构决定的隐式切分。

因果链澄清一下:分词器词表提供了结构基础,带空格前缀的英文子词在词表中占比更高,是生成空格的物质条件;训练数据决定了行为倾向,语料中带空格的写法占压倒性多数,模型从中学到了「中英边界应加空格」的模式。分词器本身不会主动插入空格,最终生成空格是模型基于数据分布做出的选择。

2. 训练数据的中文比例不同

  Qwen GPT / Claude
中文数据占比 高(阿里系,中文优先) 低(英文优先,中文为辅)
中文来源 大量中文技术社区 更偏 Wikipedia、新闻、书籍
盘古之白覆盖率 极高(技术社区几乎全覆盖) 较低(非技术类中文文本遵循此规范较少)

Qwen 在训练前看到的中文文本中,"中文 English" 带空格的写法远多于 "中文English",模型从数据中学到了「中英交界处应该有空格」这个 pattern。GPT/Claude 的中文数据比例低,没有形成这么强的单一偏置。

所谓成也萧何,败也萧何。「盘古之白」增加了文本的可读性,但在生成代码路径、命令行指令等场景里,反而成了绊脚石。根本原因还是非 Latin 字符路径的训练数据太少。Qwen3 系列里中文路径的问题有所缓解,但 CJK 中的 JK 依然一如既往地糟糕。

缓解方案

知道原因,解法也清晰了。

推理/应用侧:对代码、路径类输出用正则后处理,移除中英边界的多余空格。成本最低,治标不治本。更加建议在应用侧解决。

微调侧:在指令微调数据中加入无空格的中英混排路径和代码样本,修正模型行为。大部分人训不动,且有灾难性遗忘风险。

Prompt 侧:在系统提示词中明确约束「路径、命令、标识符中不要在中英文之间插入空格」。用处有那么一点,但不大。

技术之外的往事

本来,不想从技术层面分析这个问题。从逻辑和已有的资料来看,大概率是以上的原因,但毕竟没有亲历数据清洗和训练,也无法完全验证这个结论。

技术层面的归因大致如此。但这件事真正让我觉得有意思的,在技术之外。

《金枝》里,弗雷泽花了大量篇幅记录各种「边界」禁忌。不同类别的东西不能触碰,不能混入,不能共处一个容器。国王和普通人不能踩同一块土地。圣物和俗物之间要留出距离。违反边界,就是亵渎。

「盘古之白」是一条现代的边界禁忌。中文和英文不能挨着,它们之间要有空格,要有留白。这条规则诞生在程序员社区,写进了一个 Chrome 插件,随后蔓延进 GitHub README、技术文档、公司规范。它本来只是一条排版建议,却慢慢有了禁忌的质感。

弗雷泽发现,禁忌有扩张的本能。原本只针对国王的规则,会慢慢被应用到酋长,再到祭司,再到任何和神圣稍有关联的人。「盘古之白」也一样。它从网页排版扩张到文档,从文档扩张进训练数据,最后被一个语言模型内化成本能。模型不再区分「这是一篇文章」还是「这是一个文件路径」,它只知道:CJK 和 Latin 之间,要有空白。

盘古开天辟地,劈开了混沌,让天地各归其位。几千年后,一个大语言模型继承了这个使命,忠实地在每个中英边界插入一道空格,哪怕那个边界是 /home/你的路径/code

这种无意识的扩张,一定程度上是人类的排版审美通过训练数据被模型内化,最终在严格格式场景中发生了「规则溢出」。通用常识与特定场景的专属规则产生冲突,是大模型对齐中一个反复出现的问题。盘古之白不过是其中一个有趣的样本,排版上的禁忌,变成了模型的本能。


  1. 為什麼你們就是不能加個空格呢? 

  2. cl100k_base 和 Qwen 的 pre-tokenization 正则核心分支均使用 \p{L}+,对于无空格的连续中英混排文本,两者预分词都会将其作为单个 chunk 处理,不会强制切开中英边界;两套完整正则在其他细节上存在差异,核心分支逻辑一致。差异在 BPE 词表:cl100k_base 以英文语料为核心构建,中文多被拆为字节级片段;Qwen 词表(15 万+)含大量中文子词,在边界处主动合并。详见 tokenization_qwen.py 

EOF 🤞