🚀

BPE 向け pretokenizer のメモ(特に qwen2)

2024/10/13に公開

LLM の BPE(Byte Pair Encoding)による tokenizer では, BPE で処理しやすくするため(?)に, まず pretokenize(?)で入力文字列を分割(split)する.
たとえば "world123" は "world", "123" などと letter, digit に分割するなど.
このとき分割のパターンは regex(正規表現)で指定される(gpt2 から? そうなっているようでだいたい後続の tokenizer はそれを継承している)

gpt2

https://www.daoplays.org/blog/gpt2_p1
https://medium.com/analytics-vidhya/understanding-the-gpt-2-source-code-part-2-4a980c36c68b

's|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+

しかし regex は基本遅いし, unicode 考慮し robust に動作する regex はめんどい(一応 RE2 あるけど, G 社製でコード品質がビミューなのでつかいたくない, absl 依存でつらい, など)ので regex 利用は避けたいときがある.

実際にはシンプルなルールで処理できる.

https://github.com/huggingface/tokenizers/pull/973

ただし unicode 文字の分類(?)の判定処理が必要となる(letter, digit, etc).

実装としては llama.cpp を参考にするとよいでしょう.

https://github.com/ggerganov/llama.cpp/blob/master/src/unicode.cpp

Qwen2

https://huggingface.co/docs/transformers/en/model_doc/qwen2#transformers.Qwen2Tokenizer.example

行頭にスペースがあるかどうかでエンコードが変わるとある.

(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?\\p{L}+|\\p{N}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n ]+|\\s+(?!\\S)|\\s+

ぱっと見ようわからんが, llama3 と似ている感じか.
https://github.com/ggerganov/llama.cpp/blob/edc265661cd707327297b6ec4d83423c43cb50a5/src/unicode.cpp#L344

ChatGPT クンに聞いたらいい感じに正規表現解説してくれるよ.

(?i:'s|'t|'re|'ve|'m|'ll|'d) : case sensitive でマッチ. (?:'[sS]|'[tT]|'[rR][eE]|'[vV][eE]|'[mM]|'[lL][lL]|'[dD]) と同等. "He's going." -> "'s"
[^\\r\\n\\p{L}\\p{N}]?\\p{L}+ : 改行, Unicode letter or number 以外にマッチ + letter が続く. スペースを含む letter を許可. example: @World -> World, 123World -> World. ‗World -> ‗World
\\p{N} : number 1 文字. つまり number は先頭にも後方にもスペースは取らない. また一文字ごと. " 123" -> "1", "2", "3"
[^\\s\\p{L}\\p{N}]+[\\r\\n]* : スペース, letter or number 以外にマッチ, それに 0 or 複数の改行がつづく. example: "Hello!!!\n\n" は !!!\n\n にマッチ
?[^\\s\\p{L}\\p{N}]+[\\r\\n]* : 先頭に 1 個以上のスペースを含み, スペース, letter or number 以外にマッチ, それに 0 or 複数の改行がつづく. 基本的にはスペース + 記号関係にマッチする. e.g. @!$ -> @!$
\\s*[\\r\\n ]+ : 前方と後方にスペースを含むのを許す改行にマッチ
\\s+(?!\\S) : スペース + non スペースか文字列の終わりまでスペースが続くもの: e.g. "World " なら最後の 2 whitespaces にマッチ.
\\s+ : 1 以上のスペース

まとめると以下となるだろうか. マッチの順もだいたいリスト順となると思われる.

  • He's など短縮形にマッチ('s)
  • 前方にスペース含んだ letter にマッチ ‗Hello World -> ‗Hello, ‗World
  • 前方にスペース含まない letter にマッチ Hello
  • 数値(1 文字)にマッチ 123 -> '1' '2' '3'
  • 前方にスペース含むのを許す記号にマッチ. 改行含む @?, @@, ' @\n'
  • スペース含む改行にマッチ
  • スペースにマッチ

llama3

qwen2 とほぼ同じ(qwen2 が llama3 をベースにしたのかもであるが)であるが,
数値だけ, 3 文字まで一つにするの違いがある. e.g. "123" -> "123"

日本語

unicode で判定しているので, たとえば全角の3でも数値 \p{N} として判定される.

Discussion