從2019年到現(xiàn)在,是時候重新審視Tokenization了
2019 年問世的 GPT-2,其 tokenizer 使用了 BPE 算法,這種算法至今仍很常見,但這種方式是最優(yōu)的嗎?來自 HuggingFace 的一篇文章給出了解釋。
「9.9 和 9.11 到底哪個大?」這個問題一度難壞了各家大模型。
關(guān)于模型為什么會答錯,研究人員給出了各種猜測,包括預(yù)訓(xùn)練數(shù)據(jù)的構(gòu)成和模型架構(gòu)本身。
在一篇新博客中,來自 HuggingFace 的研究者討論了可能造成這一問題的原因之一 ——tokenization,并重點(diǎn)分析了它如何影響模型的數(shù)學(xué)能力,尤其是算術(shù)能力。
回顧 Tokenization
早在 2019 年,GPT-2 論文就詳細(xì)介紹了將 BPE(byte-pair encoding)用于語言模型的 tokenization 方法。此方法的工作原理是將頻繁出現(xiàn)的子詞合并為單個單元,直到詞匯量達(dá)到目標(biāo)大小。
然而,這種做法生成的詞匯表在很大程度上取決于輸入到 tokenizer 中的訓(xùn)練數(shù)據(jù),從而導(dǎo)致了在數(shù)字編碼方式上的不一致性。例如,在訓(xùn)練數(shù)據(jù)中常見的數(shù)字(例如 1-100、1943 年這樣的表示)很可能被表示為單個 token,而較少見到的數(shù)字則被拆分成多個 token,如下所示:
四年后,Llama 系列來了!Llama 和 Llama 2 使用 SentencePiece (一個用于基于文本生成的無監(jiān)督文本 tokenizer )的 BPE 實(shí)現(xiàn),并對數(shù)字進(jìn)行了顯著的調(diào)整:它們將所有數(shù)字拆分為單個數(shù)字。這意味著只有 10 個唯一 token(0-9)來表示任何數(shù)字,從而簡化了 LLM 的數(shù)字表示。Deepseek 后來發(fā)布了一個模型 (DeepSeek-V2),它有一個類似的單位數(shù)(single-digit)的 tokenizer 。
后來,Llama 3 采用了不同的方法來處理數(shù)字,將它們 tokenizing 為三位數(shù)。因此,從 1 到 999 的數(shù)字每個數(shù)都有唯一的 token,而從 1000 開始的數(shù)字由這些 token 組成。
一個新的范式:從右到左的 Tokenization
到目前為止,我們所看到的 tokenization 方法都是從左到右處理文本的。例如,如果三位數(shù)字的分詞法遇到序列 12345,它將從開頭掃描,將其分解為 123 和 45 這樣的片段。
與從左到右(L2R)的分詞方法不同,從右到左(R2L)的分詞方法以三個字符為一組,從文本的末尾開始向開頭處理。使用 R2L 分詞,序列 12345 將通過從右側(cè)掃描進(jìn)行分詞,首先分割出 345,然后再處理 12。最近,一些前沿的閉源模型也在探索使用這種 R2L 分詞方法,這已經(jīng)被證明對某些算術(shù)運(yùn)算有益,因?yàn)?R2L 表示可以防止操作數(shù)的錯位。還有傳言稱 Claude 使用了這種 R2L 分詞方法。
為了更好地理解錯位是什么樣子的,讓我們以 3789 + 8791 為例:
如上所示,在三位數(shù)從左到右(L2R)的例子中,9 + 1 應(yīng)該映射到數(shù)字 0,但實(shí)際上卻與 8 組合在一起形成了 80,因?yàn)榍懊娴娜齻€ token(125)已經(jīng)被分在一起了。tokenization 邊界的偏移在學(xué)習(xí)過程中引入了額外的復(fù)雜性,已經(jīng)證明準(zhǔn)確性是有害的。
而在從右到左(R2L)的例子中,數(shù)字 580 和對應(yīng)的子操作數(shù) 789 和 791 很好地對齊了。
以下是用于處理數(shù)字 tokenization 的技術(shù)概述:
不同方法的比較
該研究旨在比較多個 tokenizer 以及它們處理數(shù)字的不同方式,以盡量減少模型架構(gòu)、訓(xùn)練配置和預(yù)訓(xùn)練數(shù)據(jù)等外部因素在評估結(jié)果中的影響。因此,每個模型之間唯一的區(qū)別應(yīng)該是 tokenizer。
實(shí)驗(yàn)選擇了 3 種 tokenizer,分別是 GPT-2 的 BPE tokenizer、Llama 3 的三位數(shù) tokenizer(three-digit tokenizer)和 Deepseek 的單位數(shù) tokenizer(single-digit tokenizer)。
from transformers import AutoTokenizer
from tokenizers import pre_tokenizers, Regex
# Initialize all tokenizers
tokenizer = AutoTokenizer.from_pretrained ("meta-llama/Meta-Llama-3-8B")
# Add an extra step to the existing pre-tokenizer steps
tokenizer._tokenizer.pre_tokenizer = pre_tokenizers.Sequence (
[
# Added step: split by R2L digits
pre_tokenizers.Split (pattern = Regex (r"\d {1,3}(?=(\d {3})*\b)"),
behavior="isolated", invert = False),
# Below: Existing steps from Llama 3's tokenizer
pre_tokenizers.Split (pattern=Regex (r"(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p {L}\p {N}]?\p {L}+|\p {N}{1,3}| ?[^\s\p {L}\p {N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+"),
behavior="isolated", invert=False),
pre_tokenizers.ByteLevel (add_prefix_space=False, trim_offsets=True, use_regex=False)
]
)
print (tokenizer.tokenize ("42069")) # [42, 069]
訓(xùn)練模型使用了原始的 Llama 架構(gòu),此外,該研究還調(diào)整了隱藏層的數(shù)量,以確保每個模型大致具有相同數(shù)量的參數(shù)(約 14.5 億)。
為了保持恒定的計算預(yù)算,本文減少了具有更大詞匯表模型中的隱藏層數(shù)量。
結(jié)果
算術(shù)問題
如下圖所示,單位數(shù) tokenization 優(yōu)于其他 tokenizer 方法。
結(jié)果顯示,雖然在較簡單的問題上差異不太明顯,但隨著問題復(fù)雜性的增加,表現(xiàn)最佳的 tokenizer(單位數(shù)分詞)與其他 tokenizer 之間的差距越來越大。這表明單位數(shù)分詞對于輸入數(shù)據(jù)長度的變化更為魯棒,并且能夠更好地捕捉復(fù)雜的模式,從而在其他分詞方法難以應(yīng)對的場景中提升性能。
此外,本文還發(fā)現(xiàn)浮點(diǎn)數(shù)和整數(shù)之間的性能差距在所有 tokenizer 中都是相似的。這表明在這兩個類別中選擇 tokenizer 時,并不存在固有的權(quán)衡,即對于整數(shù)最優(yōu)的 tokenizer 對于浮點(diǎn)數(shù)也是最優(yōu)的。
如下圖所示,三位數(shù) R2L tokenization 比標(biāo)準(zhǔn)三位數(shù) L2R tokenization 具有更好的性能。
本文發(fā)現(xiàn),與使用默認(rèn) L2R token 數(shù)據(jù)進(jìn)行訓(xùn)練相比,使用 R2L token 數(shù)據(jù)進(jìn)行訓(xùn)練的模型取得了顯著的改進(jìn)(乘法除外)。這表明,與典型的從左到右編碼相比,它是算術(shù)運(yùn)算的最佳設(shè)置。
當(dāng)數(shù)字被從右向左每 3 位一組進(jìn)行分塊時,Pure-BPE(Byte Pair Encoding)tokenizer 顯示出不一致的性能。
顯然,沒有任何額外數(shù)字預(yù)處理的純基于 BPE 的 tokenizer 不會從使用 R2L token 化中受益。一個可能的解釋是,這些 tokenizer 中數(shù)字分組的方式缺乏結(jié)構(gòu)。
基于單詞的問題
雖然在基于單詞的問題上,不同 tokenizer 之間的性能差距不太明顯,但本文觀察到單位數(shù) tokenizer 和三位數(shù) tokenizer 通常優(yōu)于基于 BPE 的 tokenizer。這表明,無論是單詞問題還是數(shù)字問題,這種趨勢都是一致的。
Llama 3 R2L 推理
接下來本文進(jìn)行了另一項(xiàng)測試,即現(xiàn)有的預(yù)訓(xùn)練 / 指令模型在接受與最初訓(xùn)練方案不同的 token 化方案時表現(xiàn)如何,而無需重新訓(xùn)練或微調(diào)。因此,本文基于 Llama3 8B Instruct 模型,并使用上述相同的代碼修改其 tokenizer,以在推理期間執(zhí)行 R2L tokenization,而無需重新訓(xùn)練新模型。
在三位數(shù) tokenization 方案中進(jìn)行兩個數(shù)相加需要注意的是:結(jié)果有時會產(chǎn)生比輸入數(shù)字更多的 token。例如將 999 和 111 相加時,它們單獨(dú)只需要一個 token,但是當(dāng)它們相加產(chǎn)生 1110 時,需要兩個 token(1 和 110)?;谶@個觀察,本文想探索在使用 L2R 和 R2L tokenization 對不同的 token 長度執(zhí)行加法時,會產(chǎn)生多大的差異。
接下來,本文將把導(dǎo)致額外 token 的加法稱為進(jìn)位(carry)加法,而那些沒有進(jìn)位的加法稱為無進(jìn)位(without carry)加法。
本文用 Llama3 8B Instruct 執(zhí)行了不同數(shù)字長度和進(jìn)位設(shè)置的算術(shù)任務(wù)。結(jié)果發(fā)現(xiàn),減法、乘法或除法沒有任何顯著的性能差異,因此結(jié)果只展示了加法。
對于非進(jìn)位加法,數(shù)字個數(shù)為 3 的倍數(shù)會產(chǎn)生完全相同的結(jié)果,因?yàn)橄?528、491 這樣的數(shù)字無論 token 化方向如何都具有相同的 token。
哪種 tokenization 方法適合數(shù)學(xué)
雖然 BPE 仍然是一種流行的 tokenization 方法,但如果你必須使用具有最多 3 位數(shù)的 tokenizer,請確保數(shù)據(jù) token 方向?yàn)?R2L。
如果你已經(jīng)有一個經(jīng)過訓(xùn)練的模型,數(shù)據(jù) token 方式為 L2R,那么你可以通過使用 R2L 來獲得更好的數(shù)學(xué)性能。
最重要的是,對于算術(shù)運(yùn)算,單位數(shù) tokenization 的性能明顯優(yōu)于其他方法。
總結(jié)而言,tokenization 對語言模型中的算術(shù)性能有顯著影響。通過仔細(xì)選擇,我們可以根據(jù)問題類型優(yōu)化 tokenization 策略,從而提高 LLM 在數(shù)學(xué)任務(wù)上的表現(xiàn)。
原文鏈接:https://huggingface.co/spaces/huggingface/number-tokenization-blog。