🦙 第7章 多语言大模型技术介绍及其工业应用
📄 一句话概括

前面六章默认了一件事:模型说英语和中文。这一章换个前提——一本模型要服务几十种语言。 难点不在「多学几门语言」,在词表就那么大,多给一种语言就要从别人碗里拿。 这一节先把这笔零和账算清楚。

📝 本章和前面几章的关系

这是全书最后一章。 前六章讲「怎么把一个模型做出来、用好」,这一章讲「它凭什么只对一部分人好用」。 42 课 图解人工智能 讲过语言模型的基本原理; 这一节关注的是语言之间的不平等怎么被技术放大。

一、为什么要做多语言:三个动机

第一个动机最实在:用户就在那里。世界上说英语的人不到十分之一,而绝大多数模型的第一语言是英语。 不做多语言,等于放弃九成市场。 第二个是数据杠杆。一种语言里的知识,往往在另一种语言里没有对应表达。 让模型会多种语言,等于让它能读到更多种写法的同一个事实。 第三个是迁移收益,也是最容易被高估的。 学过英语的模型去学法语,比从零学快得多——因为它们共享字母表和大量词根。

但这条对中文、日文、阿拉伯文不成立。 共享字母表才有迁移红利,不共享的时候,多语言训练基本是各学各的,还要抢同一份词表。

二、社区的三步走

第一步是「一个语言一个模型」。 2018 年之前基本如此,每种语言单独训一份,效果最好,成本也最高,小语种根本没人做。 第二步是「共享词表的多语模型」。多语 BERT 把上百种语言塞进同一套参数,一个模型干所有语言的活。 代价是小语种被大语种压着,效果比单语模型差一截。 第三步是「大规模覆盖加专门优化」。 多语翻译模型把语言数推到两百种,同时在采样、词表、数据配比上做专门设计。

这三步的共同趋势是:语言数越多,每种语言分到的资源越少。这不是工程没做好,是结构决定的。 Llama 2 站的位置有点特殊:它以英语为主,官方文档明确说非英语能力有限。 所以社区拿到它之后,第一件事往往是扩词表、补语料,而不是直接拿来用。

三、三条技术路线,各有各的账

第一条,从零训一个多语模型。 效果上限最高,成本也最高,而且要求每种目标语言都得有足够语料——小语种根本凑不出来。 第二条,拿现成模型继续预训练。补上目标语言的语料,再训一段。 成本低得多,代价是原始能力可能退化,得靠混一部分原语料兜住。 第三条,扩词表。把目标语言的常用字组塞进词表,然后重训嵌入层。 它解决的是 7.1 节这张表里的问题:token 膨胀。

三条可以叠加。 实践里最常见的是「扩词表 + 继续预训练」,因为扩词表能立刻降低 token 数, 继续预训练能让新词表真正被用起来。 要注意扩词表有个坑: 只扩词表不继续训练,新词是随机初始化的,模型不会用。表现是 token 数降下来了,但效果反而变差。

四、词表是零和的:一笔账算到底

词表大小是个超参,一般是 32000 到 128000 之间。 它不会随语言数一起涨——涨了嵌入矩阵就大,输出层就大,显存和算力都跟着涨。 所以词表是一个固定大小的名额池。英语占得多,别的语言就占得少。 而谁占得多,取决于预训练语料里谁的字组重复得多。 演示里把这件事放大了:同一段技术规范的五种语言版本,用「每种语言分五分之一」的词表, 各语言每 token 能装 1.6 到 2.0 个字符。

换成「英语占八成」的词表,中文的 token 数涨到 1.64 倍,英语自己缩到 0.66 倍。 一边省下来的,正是另一边多花的。 这个倍数直接换成钱和上下文:同样的窗口,中文能装的内容少了 39%; 按 token 计费的接口,同一段内容对中文用户贵 64%。 而这一切跟「模型聪不聪明」没关系。它只是词表里没有那些字组,只能逐字切。

五、一个常被忽略的区分:多语言不等于多文化

会说法语不等于懂法国的办事规矩。 模型能生成通顺的阿拉伯语,不代表它知道这个地区的合同怎么写、称呼怎么用、哪些话不能提。 语料里英语占比高,模型的世界观、举例、默认设定就都偏英语世界。 换一种语言提问,它可能只是把英语的答案翻译过去。 所以做行业落地时,语言能力只是第一层。第二层是领域语料,第三层是本地常识。 三层缺一层,用户就会觉得「它说话怪」。

这一条在 7.4 节讲客服时会再出现一次:同一个问题,用户不满意的往往不是语法, 是「你没听懂我在问什么」。

六、动手 Demo:同一段话五种语言,换词表看 token 怎么涨

五段同义文本,内容是「灯具防护等级、接线盒、通电测试」。 三种词表都是同样的名额总数,只有分配比例不同。 词表用「字符加相邻字组」按频次取,切分用贪心最长匹配——BPE 的简化版。 看两个数:每 token 装几个字符,以及换词表之后 token 涨了几倍。

QUOTA = [
    (u'均衡',     {u'中文': .2, u'英语': .2, u'日语': .2}),
    (u'英语优先', {u'英语': .8, u'中文': .05, u'日语': .05}),
]

def build(quota):            # 按配额从每种语言里取候选
    v = set()
    for lang, t in TEXTS:
        v.update(cands(t)[:int(VSIZE * quota[lang])])
    return v

def tok(s, v, L=3):          # 贪心最长匹配
    while i < len(s):
        for k in range(min(L, len(s) - i), 0, -1):
            if s[i:i + k] in v:
                i += k; break

实跑输出:

--------------------------------------------------------------------
同一段话五种语言,换词表看 token 怎么涨
--------------------------------------------------------------------


  ① 五段同义文本
  --------------------------------------------------------------------
| 语言 | 字符数 |
|---|---:|
| 英语 | 287 |
| 中文 | 73 |
| 日语 | 114 |
| 阿拉伯语 | 236 |
| 印地语 | 269 |

  内容一样,都是「户外灯具防护等级不低于 IP65」。字符数差三倍——中文最短,阿拉伯语和印地语最长。
  ! 注意:字符数和 token 数是两件事。字符多的语言未必 token 多,要看词表里有没有它常用的字组。

  ② 均衡词表:每种语言都分到五分之一
  --------------------------------------------------------------------
| 语言 | token | 每 token 字符 |
|---|---:|---:|
| 英语 | 146 | 1.97 |
| 中文 | 36 | 2.03 |
| 日语 | 71 | 1.61 |
| 阿拉伯语 | 120 | 1.97 |
| 印地语 | 149 | 1.81 |

  词表上限 600 个,五种语言各分 120 个名额,并集 566 个(标点数字重复)。
  五种语言的压缩率都在 1.6 到 2.0 之间。
  ! 均衡的时候谁都不吃亏,压缩率差不多。这一格是基准线——下面换配额,看差距从哪里出来。

  ③ 换词表:英语优先 vs 自然分布
  --------------------------------------------------------------------
| 语言 | 均衡 | 英语优先 | 自然分布 |
|---|---:|---:|---:|
| 英语 | 146 | 96 | 109 |
| 中文 | 36 | 59 | 26 |
| 日语 | 71 | 92 | 78 |
| 阿拉伯语 | 120 | 177 | 161 |
| 印地语 | 149 | 222 | 209 |

  三张词表都是 600 个 token,区别只在名额怎么分。
  词表大小固定,多给一种语言,就少给另一种——这是零和。

  ④ 膨胀倍率:同一个意思要多花几倍 token
  --------------------------------------------------------------------
| 语言 | 英语优先 | 自然分布 |
|---|---:|---:|
| 英语 | 0.66 | 0.75 |
| 中文 | 1.64 | 0.72 |
| 日语 | 1.30 | 1.10 |
| 阿拉伯语 | 1.48 | 1.34 |
| 印地语 | 1.49 | 1.40 |

  ! 英语优先词表下,中文的 token 数涨到基准的 1.64 倍。
  换个说法:同样的上下文窗口,它能装的内容少了 39%;
  英语反过来缩到 0.66 倍,这是同一笔零和账的两面。
  ! 成本完全跟着走。
  按 token 计费的接口,同一段内容对该语言用户贵 64%——不是因为内容更多,是因为词表里没有它的字。

  ⑤ 真实规模下没那么夸张,但方向一样
  --------------------------------------------------------------------
  这里用 600 个 token 把效应放大了。
  真实词表 32000 个,中文大约占几百到一千多个,膨胀倍数在 1.5 上下,不会到两倍。
  ! 但方向是完全一样的:词表是按频次长的,语料里谁多谁就占名额。
  英语在预训练语料里占大头,词表自然向英语倾斜。
  ! 这也解释了为什么后来很多模型把词表从 32000 扩到 10 万以上——不是词表越大越好,
  是要让长尾语言也有名额。

①先看字符数。同一段内容,中文 73 个字符,英语 287 个,印地语 269 个,差了三倍多。 但字符数不等于 token 数——要看词表里有没有它常用的字组。这一句是整节的引子。 ②均衡词表下,五种语言的压缩率都在 1.6 到 2.0 之间,差距很小。 这就是配额公平的样子:谁都不占便宜,谁也不吃亏。 这一格是基准线。下面换配额,看差距从哪里冒出来。 ③三张词表的名额总数一样,只有分法不同。英语优先那张,英语拿走八成,其余四种各分 5%。 结果很直接:英语从 146 个 token 降到 96 个,中文从 36 涨到 59,阿拉伯语从 120 涨到 177。 这是零和,不是双赢。 ④这张表是把 ③ 换成倍率。英语 0.66,中文 1.64。 1.64 倍意味着什么:同样的 4096 上下文,中文能装的内容少了 39%; 按 token 买单的接口,同一句话贵 64%。 而这些差别全在词表里,跟模型的层数、参数量、训练数据质量都没关系。换个词表,账就变了。 ⑤最后要诚实一点:这里用 600 个 token 把效应放大了。 真实词表 32000 个,中文大约占几百到一千多个,膨胀在 1.5 倍上下,没有演示里这么夸张。 但方向完全一样。 词表是按频次长的,语料里谁多谁占名额——所以词表倾斜不是设计失误,是数据分布的镜子。 这也解释了后来很多模型把词表扩到十万以上:不是词表越大越好,是要给长尾语言留出名额。

30 秒实验

  1. 把「均衡」词表的名额总数从 600 改成 120,重跑第 ② 步。五种语言的压缩率还挨得这么近吗? 这说明什么?
  2. 给词表加一条规则:每种语言至少保底 30 个名额。重跑第 ③ 步,中文的膨胀倍率会降到多少?
  3. 把日语换成一种和中文共享汉字的语言(比如繁体中文),它的膨胀倍率会比中文低还是高?为什么?

常见误区

  1. 以为多语言就是「多喂几种语言的语料」。 词表、采样、评测集三样不同步改,喂进去的语料也只是被逐字切成 token,学不到东西。
  2. 拿英语效果去推小语种效果。 「英语能到 90 分,小语种大概 70 分」——这种估算没有任何依据,实测里差距经常是三十分以上。
  3. 以为扩词表就完事。 新词是随机初始化的,不继续预训练模型根本不会用,表现是 token 数降了、效果反而差了。
  4. 把「会说这门语言」当成「懂这个地方」。语言对了,常识、规矩、忌讳全不对,用户照样不买账。
自测三问(点开对答案) 1. 为什么说词表大小是个零和游戏? —— 因为词表总量被嵌入矩阵和输出层的规模卡住,不能随语言数无限涨。 名额固定,一种语言多占,另一种就少占。谁占得多,取决于语料里谁的字组重复得多。 2. 「多语言训练有迁移收益」这句话,对哪种语言不成立? —— 不共享字母表和词根的语言。 英语和法语之间迁移很明显,英语和中文之间基本没有——它们连切分单位都不一样,谈不上互相借力。 3. 扩词表之后为什么必须继续预训练? —— 新加的词嵌入是随机初始化的,模型不知道它是什么意思、该接在什么位置。 只扩不训,token 数是降下来了,但模型对这些 token 的处理全是噪声,效果反而更差。

下一节看料从哪来。多语言的难,一半难在词表,另一半难在语料根本收不到。

相关笔记