Table of Contents
Metaが2024年12月13日に公開した論文 "Byte Latent Transformer: Patches Scale Better Than Tokens" [Pagnoni, 2024] を読んだのでその内容をまとめます。
ここで掲載された図は特に説明がない限り全て[Pagnoni, 2024]からの引用で、著作権は著者らに所属します。
これは何?
提案手法は主に言語モデルに文字を入力する前の処理方式、つまりtokenizerに相当する部分の新方式を提案したものです。 既存のtransformerは入力文字列のバイト列をtokenizerによってtokenという単位に分割します。 tokenizerの処理方式は様々ですが、共通して内部でバイト列とtokenの対応を記した辞書を持っていて、この辞書を使って入力テキストをtoken列に変換します。 「辞書」と言う通り、この語彙のmappingは学習時に決定し、推論時にはこの固定のmappingを使って一意的に処理されます。
これに対し、提案手法はbyte列の「予測しやすさ」に基づいて動的に処理単位を決定します。 すなわち、直前のコンテキストから予測しやすい一連の文字列をすべて1まとまりとして扱います。 このようにすることで「予測しやすい(=情報が少ない)」パターンを1つの処理単位に押し込めます。 こうして動的に決められる処理単位を論文中ではpatchとよび、tokenとは区別します。
具体的にはbyte列の「予測しやすさ」の評価にentropyという特徴量を使います。 entropyには次に来るパターンの確率が必要なので、これ予測する100Mパラメータ規模の比較的小さなtransfomerを事前学習用のデータを使って学習します。
byte列をpatchに区切る具体例
以下は論文のfigure 3,4を転載したもので、“Daenerys Targeryen is in Game of Thrones, a fantasy epic by George R.R. Martin.” という文字列を提案手法を使ってpatchに区切る様子を可視化しています。
Figure 4からわかるように、最初の"Dae"という文字列まで処理するところでは次にくるパターンが何か予測するのが難しいため、"D", "a", "e"はそれぞれ個別のパッチに分割されます。 これに対し、"Daen"まで来ると、次に来るのが"Daenerys Targeryen"と言う文字列だと推測できるので、残りの"nerys Targeryen"という文字列はすべて1つのパッチにまとめられます。 このように、過去の系列から予測しやすい(=情報量が少ない)文字は長いpatchにまとめられやすく、予測しにくい(=情報量が多い)文字は短いpatchにまとめられやすくなります。
なお、tokenベースの手法も頻出する長い系列がtokenにまとめられるので結果的にはこれと似た振る舞いをしているのですが、tokenベースの手法との違いは、 学習時に存在しなかった新たな系列に対しても対応できると言う点です。厳密には未知のパターンに汎化するかどうかはentropy予測モデルの汎化性能に依存しますが、tokenベースの手法のように全く対応できないわけではないため、既知パターンに対する性能がtokenベースの手法と同等であれば少なくとも幾らかの性能向上が見込める可能性があります。

図は[Pagnoni, 2024]より引用
提案手法のメリット
さて、このような新たな仕組みを導入することで得られるメリットは何でしょうか? 論文で著者らが主張しているメリット(を意訳したもの)が以下です。
- 学習効率の向上(8Bまでのモデルで学習時の計算量の削減を実証)
- 1つの処理単位に押し込める情報量を学習時のハイパーパラメータでコントロールできる
- tokenベースのLLMでは拾えないサブトークンの特徴を利用できる
以下これらの観点を順番に説明します。
学習効率の向上
事前学習時の効率をtokenベースのモデルであるLlama3と提案手法で比較した結果が論文中のFigure 1です。 ここではperplextyではなくBits per bytes(BPB)という評価指標を用いています(理由は後述)。 この図を一見すると、提案手法は既存手法よりも同一の学習コストでより良い性能(低いBPB)を実現しているように見えます。

図は[Pagnoni, 2024]より引用
BPB評価指標について
上記の結果を正しく評価するにはBPB評価指標について知る必要があるので、以下で詳しく説明します。 まず、事前学習時のモデルの性能評価の指標として最もよく使われるのはPerplexityで、以下のように定義されます。
$$ \mathrm{PPL} = \left( \prod _ {i=1} ^ N p(x _ i \vert x _ {<i}) \right)^{-1/N} \tag{1} $$
Perplextyは意味的にはモデルが特定の文字列を出力する尤度(=実現しやすさ)をトークンあたりで平均し、逆数をとったものです。 したがって、Perplextyが小さいほど尤度が高いことを表し、モデルの予測性能が高いことを表します。
なお、PPLの対数を取ったものはnext token予測タスクにおけるcross entropy lossと一致します。
$$ \mathcal{L} _ \mathrm{CrossEntropy} = - \frac{1}{N} \sum _ {i=1} ^ N \log(p(x _ i \vert x _ {<i})) \tag{2} $$
ところで、perplextyを使って異なるtokenizerを使った言語モデルを比較することは注意が必要です。なぜなら、perplextyは入力系列の区切り方に大きく依存する評価指標だからです。
このことを直感的に理解するには、"I have a dog."と言う文字列を以下の2通りに区切る場合を考えます。
- ["I", "_h", "a", "v", "e", "_a", "d", "o", "g", "."]
- ["I", "_have", "_a", "_dog", "."]
1のように1文字ずつ区切る場合は9つの予測が必要なのに対し、2のように単語ごとに区切る場合は4つのトークンを予測するだけで済みます。 したがって、データにも依存しますが、一般的に前者の方が後者よりも難しいタスクになります。
したがって、このようなトークンの分割方法によらない評価指標として、Bits per bytes(BPB)という評価指標が提案されました[Choe, 2019]。
$$ \mathrm{BPB} = \frac{\vert \mathrm{patches} \vert}{\log 2 \vert \mathrm{bytes} \vert} \mathcal{L} _ \mathrm{CrossEntropy}\tag{3} $$
BPBはPPLを正規化した指標で、PPLにおけるトークンの区切りをbyte単位に統一したものとみなすことができます。
なお、計算する時の最小単位はtokenベースのモデルはトークン単位、byteベースのモデルはbyte単位になります。 上の例で言うと、2の単語単位で区切ったモデルはp("I"), p("_have"), p("_a"), ...などを計算します。 これはPPLの定義より、 $p(\text{have}) = p(\text{h}) p(\text{a}) p(\text{v}) p(\text{e})$ などが成り立つためです。
個人的にBPBを用いても厳密な意味で異なる処理単位で区切ったモデル同士を比較できると考えるのは懐疑的に思えます(モデルの予測確率は厳密に経験的な確率にcalibrateされているわけではないので)。 BPBでの比較は「PPLで比較するよりはマシな指標」くらいの感覚で捉えておくのが無難だと思いました。 より信頼できる比較としては、著者らが論文の後半で行なっているようにダウンストリームタスクの性能で比較するのが良いと思います。
ダウンストリームタスクでの比較
以下の図は論文のTable 1で、1T tokenの追加データで追加学習を施したモデルでのzero shotの性能を比較したものです。 左列が既存手法で、右列が提案手法です。ここでは8Bパラメータモデルで既存手法と提案手法を比較しています。 残念ながら既存手法を上回るほどではないものの、概ね同程度の性能が出ていることがわかります。 著者らによると、「過去にいくつかbyte単位で処理するモデルは提案されてきたが、tokenベースのモデルと同程度の性能を報告したのは提案手法が初めて」だそうです。

図は[Pagnoni, 2024]より引用
1つの処理単位に押し込める情報量をコントロールする
提案手法のメリットの2つ目は、patchサイズのハイパーパラメータによって学習時・推論時の計算コストと性能のトレードオフをコントロールできるようになった点です。
論文のfigure 4で見たように、patchをどこまでまとめるかについては、entropy予測モデルの閾値を設定することでデータセットに合わせて調整することができます。 閾値を下げればpatchの区切れ目はより細かくなるので、1文字単位の区切りに近づきます。一方で、閾値を上げれば単語単位、フレーズ単位とより分割単位が大きくなります。 このようにpatchあたりに詰め込めるbyte数がハイパーパラメータとして設定できるので、同じテキストを入力しても、patchの数が大きくなったり小さくなったりできます。 patch size(patchあたりの平均byte数)をコントロールすることでLLMの計算量と性能のトレードオフが可能になります。 著者らはこのことを「新たなモデルのスケール軸を提供する」と表現しています。
実際にこの効果は論文のFigure 1でも確認することができて、patch sizeを8->6と小さくすることでよりBPBを早く下げることができる傾向があることがわかります。 この傾向は550M, 760Mと比較的パラメータ数が小さいモデルどうしの比較(Figure 1左)では顕著ですが、5.2B, 6.4Bとパラメータ数が大きいモデル同士の比較(Figure 1右)ではパッチサイズを変えたことによる差がほとんど見られなくなっています。
サブトークンの特徴の利用
提案手法のメリットの3つ目は、サブトークンの特徴にモデルがアクセスできることです。 ChatGPTが「9.11と9.9のうち大きいのはどちらか?」という問いに正く答えることができないというのが少し前にSNSで話題になりましたが、 この原因の一つとして、GPTのtokenizerでは"9.11"を"9", ".", "11"の3つのトークンに分割し、"11"を1つのトークンとして扱うためではないか?という説がありました。 GPTは"11"が"1"と"1"の2つのサブトークンからなることを理解できないというわけです。
一方、提案手法は後述するようにbyteごとの表現や直近のn-gramの表現を内部で持つため、「"11"は"1"と"1"からなる」という情報を予測に活用できる可能性があります。
また、提案手法はスペルミスなど、学習時のデータにはなかったbyte単位のノイズに対して頑健である可能性があります。
Byte単位の情報の利用が有効なタスク
論文のtable 3はByte単位の情報の活用が有効に働くと考えられる各種タスクについて、既存手法と提案手法の性能を比較したものです。 この結果によると、ほとんど全てのタスクで提案手法が既存手法の性能を上回っています。 特に、spellingなどでは提案手法が99.9%と大幅な性能改善を実現しているものもあります。 この結果を見ると、提案手法が実際にbyte単位の特徴を有効に活用できているであろうことがわかります。

図は[Pagnoni, 2024]より引用
具体的にどういうタスクかをサンプルで示したのがFigure 7です。 ここでは特定の単語を別の単語で置換したり、単語中の文字の一部を入れ替えたりするタスクについて、既存手法と提案手法の処理結果がそれぞれ示されています。 この結果を見ると、実際に既存手法が苦戦しているタスクに提案手法がうまく回答できていることがわかります。

図は[Pagnoni, 2024]より引用
低頻度言語の翻訳タスク
学習データにわずかしか含まれない言語(以後「低頻度言語」と表記)は、n-gramパターンの出現頻度が少ないためtokenizerベースの手法では辞書に登録されないケースが増えるため、 下位タスクで性能を伸ばすのが課題になっています。
これに対し、提案手法は固定の辞書は持たず、入力言語を「byte列パターン」として扱うため、低頻度言語の場合でも高い汎化能力を示す可能性があります。
Table 4は6つの高頻度言語と20の低頻度言語を英語に翻訳するタスクに対するう既存手法と提案手法の性能比較です(マーカーはbilzard)。 これによると、確かにアルメニア語などの低頻度言語のいくつかで顕著な改善が見られています。
一方で、英語からアラビア語とタイ語(高頻度言語)へと翻訳するタスクでは2-3ポイントほど性能低下しています。 この結果は言語特有の事情を反映しているのでしょうか。
何にせよ提案手法をそのままtokenizerから置換するのにはまだ課題がありそうです。

図は[Pagnoni, 2024]より引用(マーカーはbilzard)
どのように実現したか?
以下では提案手法の具体的な実現方法について簡単に記載します。
byte列の表現の粒度
最初の方でbyte列の区切りを動的に判定する方法について説明しましたが、既存手法との違いはもう一つあります。 それはbyte列の表現の粒度です。
既存手法は最初にbyte列をいくつかのまとまりでまとめてtoken化したあとは、全てtokenの表現(embedding)を元に処理を行うので、 サブトークンの表現については明示的に持っていません。 このため、前に示した「"11"というトークンが"1"と"1"の2つのトークンからなる」という情報を(少なくとも低レイヤの表現から)直接知ることができないわけです。
これに対し、提案手法は byte単位の表現、および直近のn-gramの表現を内部で明示的に持っています。 これによって、上記の結果で見たようなbyte単位のタスクに強いモデルを実現しています。
具体的な内部表現の持ち方についてはAppendix Aに記載します。
提案手法における「語彙数」の定義
tokenizerの設計において語彙数は重要なパラメータなので軽く触れておきます。
まず、提案手法はn-gramの表現をハッシュ関数で圧縮します(Appendix A 参照)。 したがって提案手法における「語彙数」とは、ハッシュ関数のcardinality(ユニークな値の数)で定義されます。
また、提案手法は(3~8)-gramの表現の和として表される(Appendix A)ので、 最終的な語彙数は「n-gramあたりの語彙数 x n-gramの数」で表されます。
以上をまとめると既存手法、提案手法の語彙数は以下のようになります。
- 既存手法(Llama 3): 128k
- 提案手法: 300k (=50k x 6)
提案手法の方が語彙数が明らかに多くてフェアじゃないようにも思えますが、 計算量としては無視できる範囲なので目を瞑ったということでしょうか(にしても既存手法は提案手法と語彙数を同じくらいに増やしたバージョンで比較するのが妥当に思えますが)。 embeddingの次元数にもよりますが、提案手法の方がVRAMの消費が増加しているかもしれません。
言語モデルのアーキテクチャ
以下で提案手法の言語モデルのアーキテクチャについて説明します。論文のFigure 2で示されたような3つのコンポーネントからなる構造をしています。
- Local Encoder: byte表現をpatchの表現に変換する Transformer
- Latent Transformer: patch列から次のpatch列を予測するLLM
- Local Decoder: patch表現をbyte表現に戻す Transformer
中央にあるLatent TransformerがこれまでのLLMに相当する部分でpatch列からpatch列を予測します。 これに対し、入力と出力はともにbyte列なので、これらとpatch列の表現を相互変換するアダプタとしてLocal Encoder/Decoderと呼ばれるモジュールが追加されています。 特徴として、Latent Transformerに対して、Encoder/Decoderのレイヤ数は極めて浅いと言う点です。 したがって、計算量の大部分はLatent Transformerにおける処理に割かれます。

図は[Pagnoni, 2024]より引用
Latent Transformerは入力列と出力列がどちらもpatchの表現なので、decoder-onlyのLLMを流用できますが、 Encoder/Decoderは入力列と出力列の表現が異なるため、encoder-decoderタイプのTransformerを採用しています(Figure 5)。

図は[Pagnoni, 2024]より引用
所感
以上、[Pagnoni, 2024]の内容について解説しました。
最後に個人的な所感を述べておきます。
提案手法のアプローチはマルチバイト言語に対しても有効か?
提案手法は広い意味で「自然言語を汎用的な記号列として扱う」アプローチの一つだと言えると思います。 中でも、低頻度言語において汎化性能が高いという結果は、英語圏以外の言語を扱う人間として興味を引く内容でした。
ただ、日本語というマルチバイト言語を扱っているユーザからすると、「byte列」を基本単位とすることが最善の方法か?については引っ掛かる点もあります。 例えば、byte列で処理した場合、先頭の1バイトが同じで続くバイトのみ異なる2つの文字は「関連したもの」として処理しますが、一般的にはこれらの文字に関連性はありません。 また、英語圏の文字のようにタイピングと表記内容が一致している場合は、byte列で処理することでスペルミスに対して頑健なモデリングができることが期待できますが、一致しない言語の場合はbyte列のみ見ていても誤変換のミスに頑健になることは期待できなさそうです(例: 「苺」と「一語」など)。
このあたりは個別の言語に固有な事情を加味したモデリングが必要かもしれません。
Reference
- [Pagnoni, 2024] Pagnoni et.al, "Byte Latent Transformer: Patches Scale Better Than Tokens", 13 Dec 2024
- [Choe, 2019] Choe et. al., "Bridging the Gap for Tokenizer-Free Language Models", 27 Aug 2019:
Appendix
A. byte単位の表現とn-gram表現の持ち方
以下では具体的な内部表現の持ち方について説明します。
まず、byte単位の表現はバイトコードを線形変換することで表現します。
$$ x _ i = E^{\text{byte}}(b _ i) $$
また、n-gram中のbyte集合を $ g _{ i , n } = \lbrace b _{ i - n + 1 }, \ldots, b _{ i } \rbrace $ 表すと、n-gramの表現は以下のようなhash関数(Rolling Polinomial Hash)で表します。
$$ \text{RollPolyHash}( g _{ i , n } ) = \sum _{ j = 1 } ^{ n } b _{ i - j + 1 } a ^{ j - 1 } \tag{4}$$
最終的な表現は(6)式のようにbyte単位の表現とn-gramの表現の和で表します。
$$ e _{ i } = x _{ i } + \sum _{ n = 3 , \ldots , 8 } E _{ n } ^{ \text{hash} } ( \text{Hash}( g _{ i , n } ) ) \tag{5}$$
$$ \text{where, Hash}( g _{ i , n } ) = \text{RollPolyHash}( g _{ i , n } ) % \vert E _{ n } ^{ \text{hash} } \vert \tag{6}$$