Byte Pair Encoding(BPE)の仕組みと多言語対応
1. 頻出ペアからの語彙学習
- 隣接する2トークンの組(ペア)を数え、最も頻度の高いペアを選びます。
- 2つを連結したトークンを語彙に加え、学習データ中のそのペアを左から重ならないように置き換えます。
- 結合規則と選んだ順番を保存し、置き換え後の列でペアの頻度を数え直します。
1.1. 4種類の単語からの語彙学習
lowが5回、lowerが2回、newestが6回、widestが3回現れるデータで、4回の結合を追います。文字(Unicodeコードポイント)から始め、単語の中だけで結合します。同頻度のペアは、トークンIDの組の辞書順で選びます。
初期文字を文字コード順のd, e, i, l, n, o, r, s, t, wに並べ、IDを0〜9とします。結合で作ったトークンには10以降のIDを順に割り当てます。同頻度のペアは、左側のIDが小さい方を、左側も同じなら右側のIDが小さい方を選びます。
例えばe + sはIDの組が(1, 7)、s + tは(7, 8)なので、前者を先に結合します。この例では語末記号も付けていません。Sennrichらの原著とは、語末記号の有無と同頻度の扱いが異なります[2]。
- [2] Rico Sennrich, Barry Haddow, Alexandra Birch, "Neural Machine Translation of Rare Words with Subword Units," ACL, 2016.
e + sとs + tです。どちらもnewestとwidestに1回ずつ含まれ、学習データ全体では合計9回現れます。同頻度のときの規則に従ってe + s → esを選ぶと、両単語の末尾はes | tになります。|はトークンの区切りです。数え直すとes + tが9回で最も多く、次の結合でestができます。
lowとlowerに共通するl + o、lo + wを結合します。表の順位(rank)は、規則を学習した順番を0から数えた値です。頻度は各結合の直前に数えた出現回数で、結合後のIDは新しいトークンに付けた番号を表します。
| 順位 | 結合規則 | 頻度 | 結合後のID |
|---|---|---|---|
| 0 | e + s → es | 9 | 10 |
| 1 | es + t → est | 9 | 11 |
| 2 | l + o → lo | 7 | 12 |
| 3 | lo + w → low | 7 | 13 |
lowは1トークン、lowerはlow | e | rの3トークンになります。newestとwidestでは、末尾のestが1トークンにまとまりました。語彙数は、初期文字10種類に4トークンを加えた14です。この例のestは英語の接尾辞と一致しますが、BPEの選択基準は出現頻度です。
1.2. ペアの頻度とトークン数の削減
ペアの頻度を数える際は、重なる位置も含めます。例えばaaaのa + aは2か所と数えますが、実際に結合できるのは1組です。頻度と、結合で減るトークン数には、このような違いがあります。
各トークン列\(s\)の出現回数を\(f(s)\)とすると、ペア\((a,b)\)の頻度は次のように書けます。\(|s|\)は現在の列の長さ、\(s_i\)は\(i\)番目のトークン、\(\mathbf{1}\)は条件が成り立つときに1、それ以外で0を取る指示関数です。
\[ C(a,b)=\sum_s f(s)\sum_{i=1}^{|s|-1} \mathbf{1}[(s_i,s_{i+1})=(a,b)]. \]内側の和は隣接位置を数え、外側の和は各列の出現回数を掛けて合計します。例えば§1.1のlowは5回現れるので、初回の集計ではl + oとo + wの頻度にそれぞれ5を加えます。学習では\(C(a,b)\)が最大のペアを選び、各列で左から重ならないように置き換えます。
2. 学習済みの規則による分割
lowestを、§1.1の4規則で分割してみます。初期状態のl | o | w | e | s | tには、順位0のe + sと順位2のl + oがあります。先にesを作ると、順位1のes + tが結合できるようになります。その後でlo、lowを作り、最終的な分割はlow | estです。学習時に単語全体が現れなくても、既存のトークンを組み合わせて表せます。
[13, 11]です。結合順位は規則を適用する優先度、トークンIDはモデルへ渡すトークンの識別番号を表します。実装によっては同じ数値を兼用しますが、役割は異なります。
b + c、a + b、c + dの3規則をこの順に優先するなら、abcdの分割はa | bc | dです。同じ語彙を左から最長一致で探すとab | cdなので、分割結果もトークン数も異なります。
2.1. Pythonでの実装
rulesのキーは隣接するIDの組、値は(順位, 結合後のID)です。encode_idsは毎回すべての隣接ペアを調べ、適用できる規則のうち順位が最も小さいものを1か所で使います。
def encode_ids(initial_ids, rules):
sequence = list(initial_ids)
while len(sequence) >= 2:
candidates = [
(rules[pair][0], i, rules[pair][1])
for i, pair in enumerate(zip(sequence, sequence[1:]))
if pair in rules
]
if not candidates:
break
# 順位が最小の候補を選び、同順位なら左側を優先します。
_, i, output = min(candidates)
sequence[i:i + 2] = [output]
return sequence
# 初期文字にID 0〜9を割り当てます。
alphabet = {char: i for i, char in enumerate("deilnorstw")}
rules = {
(1, 7): (0, 10), # e + s → es
(10, 8): (1, 11), # es + t → est
(3, 5): (2, 12), # l + o → lo
(12, 9): (3, 13), # lo + w → low
}
initial_ids = [alphabet[char] for char in "lowest"]
print(encode_ids(initial_ids, rules)) # [13, 11] = low | est
初期トークン数を\(n\)、結合回数を\(m\)とし、IDペアの照合を定数時間とします。このコードは、候補の走査と配列の置き換えに1回あたり最大\(O(n)\)かかるため、初回の走査を含む全体の計算量は\(O(n+mn)\)です。\(m\)は最大で\(n-1\)なので、最悪計算量は\(O(n^2)\)になります。
隣接関係を管理するデータ構造と優先度キューを組み合わせれば、結合した位置の左右だけを更新できます。同じ仮定の下で、候補の取り出しと追加は1回あたり\(O(\log n)\)、分割全体では\(O(n\log n)\)です。
短い入力では、データ構造を管理する負担が小さい配列の走査が速いこともあります。tiktoken 0.12.0の実装も配列を使い、短い入力でのキャッシュ効率を採用理由として挙げています[16]。実行速度を比べる際は、事前分割後の各断片の長さも確認します。
- [16] OpenAI, "tiktoken: BPE implementation (v0.12.0)," GitHub, 2025.
3. 文字・バイト・前処理の関係
3.1. バイト単位のBPEと未知の文字
lowestを分割できたのは、使われている文字がすべて初期語彙に含まれていたためです。文字を初期単位にする方式では、語彙にない文字への対応も必要になります。バイト単位のBPEは、テキストをUTF-8のバイト列に変換し、0〜255の256種類のバイト値から結合を始めます。全種類を初期語彙に含めれば、学習データになかった文字も、そのバイト列で表せます。
Aは1バイト、猫は3バイト、🙂は4バイトで表されます[4]。BPEはこのバイト列の隣接ペアを結合するため、1トークンが1文字の途中で終わることも、複数の文字を含むこともあります。
猫を表す3バイトも、必要な結合を学習すれば1トークンにまとまります。各言語をどれだけ短く表せるかは、どのバイトの並びの結合を学んだかによって変わります。
E7 8Cは、単独では完全な文字を表せませんが、次のABと連結すれば猫に戻ります。個々のトークンを先に文字列へ変換すると、不完全なバイト列が置換文字に変わり、原文を復元できなくなることがあります。
SentencePieceでbyte fallbackを使うには、byte_fallbackを有効にします[17]。指定した語彙数のうち256枠をバイト用に使うため、文字や文字列に使える枠はその分だけ減ります。
SentencePieceは、BPEやUnigram(§8.1)を選べるライブラリーです[18]。分割のアルゴリズムとbyte fallbackの有無は、別の設定として指定します。
- [17] Google, "SentencePiece Training Options," SentencePiece Documentation, 2026.
- [18] Taku Kudo, John Richardson, "SentencePiece: A simple and language independent subword tokenizer and detokenizer for Neural Text Processing," EMNLP System Demonstrations, 2018.
3.2. 正規化と事前分割
éには、1コードポイントの表現と、eに結合アクセントを続ける2コードポイントの表現があります。UnicodeのNFCは両者を同じコードポイント列に変換し、NFKCはさらに全角のAをAにするような変換も行います[6]。表記を揃えると頻度をまとめて数えられますが、変換前の表記の区別は失われるため、復号して戻せるのは正規化後のテキストです。
hello worldをhelloと、先頭に空白を含む worldに分ければ、その境界をまたぐ結合は学習時にも分割時にも行いません。語彙数を増やしても、この設定のままではhello world全体を1トークンにはできません。
| 実装 | 分割の構成 | 確認する設定 |
|---|---|---|
| tiktoken[16] | バイト列の順位表と、事前分割に使う正規表現 | 設定名(encoding)、特殊トークン、順位とIDの対応 |
| SentencePiece[17] | BPEやUnigramを選択し、モデルに正規化なども保存 | 文字の採用範囲、byte fallback、正規化、空白処理 |
| Hugging Face Tokenizers[5] | 正規化、事前分割、BPEなどの分割モデル、デコーダーの組み合わせ | 各部品の設定と、追加トークンの照合方法 |
SentencePieceの既定の正規化は、独自のNFKC系の規則です。identityで文字の正規化を無効にしても、空白処理には別の設定があります[19]。例えば、先頭への空白追加や連続する空白の削除も、原文との一致に影響します。
- [16] OpenAI, "tiktoken: BPE implementation (v0.12.0)," GitHub, 2025.
- [17] Google, "SentencePiece Training Options," SentencePiece Documentation, 2026.
- [5] Hugging Face, "The tokenization pipeline," Tokenizers Documentation, 2026.
- [19] Google, "Text Normalization in SentencePiece," SentencePiece Documentation, 2026.
4. 多言語で生じる分割長の差
4.1. 学習データの比率と表記の共有
言語\(\ell\)でのペア\((a,b)\)の出現回数を\(C_\ell(a,b)\)、その言語の学習時の重みを\(w_\ell\)とすると、選択に使う頻度は次の形です。
\[ C(a,b)=\sum_\ell w_\ell C_\ell(a,b). \]例えば\(w_\ell=2\)なら、その言語の各ペアを2倍の回数で数えます。これは、その言語の学習文を2回ずつ含める場合と同じ集計です。重みを変えずに学習する間も、結合が進むたびに\(C_\ell(a,b)\)は変わります。
4.2. 学習比率を変えた小規模実験
学習用には各言語16文、評価用には別の8文を使っています。文ごとにおおむね同じ内容を表す自作文で、専門家による翻訳確認を経た評価データではありません。
言語\(\ell\)の学習文の合計UTF-8バイト数を\(B_\ell\)、下表の目標比率を\(p_\ell\)として、その言語の各文に\(100000p_\ell/B_\ell\)の重みを付けます。\(p_\ell\)の合計は1で、全条件の重み付きバイト量は100,000です。PythonのFractionで厳密に計算し、無作為抽出は行いません。英語50%の条件は、80%の条件の各比率の平方根を取り、合計が1になるように正規化したものです。
初期語彙は256種類のバイト値で、256回の結合を学びます。文字の正規化はせず、文をまたぐ結合を禁じ、文内の空白をまたぐ結合は許します。評価時には重みを掛けず、各言語8文のトークン数を合計します。
配布ZIPを展開し、展開先で次のコマンドを実行すると、テストの実行後に実験結果がresults/へ出力されます。入力データはdata/toy_parallel.json、表の値はresults/multilingual_metrics.csvで確認できます。
python3 code/run_all.py
code/bpe_core.pyはバイト単位の最小実装、code/minimal_bpe.pyは文字単位にも対応する実装です。
| 学習時のバイト量の比率(%) 英/日/中/アラビア/ヒンディー | 英語 | 日本語 | 中国語 | アラビア語 | ヒンディー語 |
|---|---|---|---|---|---|
| 80/5/5/5/5 | 137 | 310 | 266 | 344 | 452 |
| 50/12.5/12.5/12.5/12.5 | 140 | 259 | 233 | 229 | 347 |
| 20/20/20/20/20 | 184 | 208 | 177 | 189 | 259 |
5. 分割長・計算コスト・モデル品質の評価
5.1. 分割長と言語間の差
5.2. 語彙数と計算コスト
増えるパラメーター数は\(50000\times4096=204800000\)です。16ビットの浮動小数点形式BF16など、1要素2バイトで保存する場合、増加量は\(409600000\)バイト、10進のMBで409.6 MBになります。入力と出力の2行列を別々に持つ場合は、合計819.2 MBの増加です。
これは重みだけの計算値です。学習時には勾配やオプティマイザーの状態なども必要になり、実際のメモリー使用量は学習方法によって変わります。
5.3. モデル品質の比較
1トークンあたりの損失は、トークナイザーによって1単位が表す量が変わるため、そのままでは比較できません。平均損失を指数関数で変換したperplexityにも、同じ問題があります。
比較には、同じ評価テキストに対する負の対数尤度をビット単位で合計し、UTF-8バイト数で割ったBits per Byte(BPB)を使えます。値が小さいほど、モデルが評価文のトークン列に高い確率を与えています。
6. PA-BPEによる言語別の語彙配分
- 開発用データの1文あたりの平均トークン数を比べ、最も多い言語を選びます。
- その言語の学習データで、最も頻出する隣接ペアを選びます。
- 選んだペアを全言語の学習・開発用データで結合します。
語彙の学習には、言語ごとの量が不均衡な30言語のFineWeb2を使っています。原著のTable 1にある通常のBPEとPA-BPEの結果を抜き出すと、次のようになります[8]。
| 方式 | 言語間の差(Gini係数) | 全体の圧縮率(文/トークン) |
|---|---|---|
| BPE | 0.064 | 0.0275 |
| PA-BPE | 0.007 | 0.0276 |
Gini係数は、言語ごとのトークン数の差を測る指標で、0に近いほど差が小さくなります。圧縮率は文数を総トークン数で割った値で、大きいほど少ないトークンで表せています。
- [8] Negar Foroutan, Clara Meister, Debjit Paul, Joel Niklaus, Sina Ahmadi, Antoine Bosselut, Rico Sennrich, "Parity-Aware Byte-Pair Encoding: Improving Cross-lingual Fairness in Tokenization," ACL, 2026.
原著のTable 2は、通常のBPE、学習の前半を通常のBPE・後半をPA-BPEにする方式(hybrid)、さらに直近の結合で同じ言語を選びすぎないように制限する方式(hybrid+window)の比較です[8]。
トークナイザーはmC4で学習し、モデルはFineWeb2の1,000億トークンで学習しています。評価は22言語を対象とする12種類のベンチマークです。Table 1のトークナイザー単体の比較とは、語彙学習用のデータとPA-BPEの方式が異なります。
- [8] Negar Foroutan, Clara Meister, Debjit Paul, Joel Niklaus, Sina Ahmadi, Antoine Bosselut, Rico Sennrich, "Parity-Aware Byte-Pair Encoding: Improving Cross-lingual Fairness in Tokenization," ACL, 2026.
7. 既存BPEの統合・拡張
7.1. 結合規則の競合と継続BPE学習
a + b → abを持ち、追加元がb + c → bcとa + bc → abcを持っているとします。共通の文字には同じIDを使い、結合規則は次の3つだけにして、元の規則を優先します。
| 順位 | 結合規則 | 規則の出所 |
|---|---|---|
| 0 | a + b → ab | 元のトークナイザー |
| 1 | b + c → bc | 追加元のトークナイザー |
| 2 | a + bc → abc | 追加元のトークナイザー |
abcの結合はa | b | c → ab | cで止まります。先にabを作ると、b + c、続くa + bcを結合できなくなり、語彙にあるabcへ到達できません。この例なら、実際の分割に合うab + c → abcを加えることで、abcまで結合できます。
ab | cなら、候補になるのはab + cです。事前分割の境界など、元のトークナイザーの制約も引き継ぎます。
Hugging Face Tokenizers 0.22.1のBPEでは、BPE-dropout(§8.3)を使わず、ignore_merges=Trueにすると、事前分割した断片全体が語彙にあれば直接そのIDを返します[20]。表の3規則と初期文字xを持つ例では、abcは直接照合で1トークンになります。語彙にないxabcを1つの断片として渡すと結合規則が使われ、x | ab | cになります。
tiktoken 0.12.0も断片全体を直接照合します。さらに、通常の結合候補はIDペアの表ではなく、隣接トークンを連結したバイト列の順位表で調べます[16]。したがって、本文のa + bcという特定の組だけを登録する例とは、規則の表し方が異なります。
配布コードを実行すると、本文の明示的なペア規則による例をresults/integration_conflicts.jsonで確認できます。
- [20] Hugging Face, "Tokenizers: BPE model (v0.22.1)," GitHub, 2025.
- [16] OpenAI, "tiktoken: BPE implementation (v0.12.0)," GitHub, 2025.
7.2. モデルの入出力との整合性
8. 他の分割手法とBPEの拡張
8.1. WordPiece・Unigramとの比較
##を付けて語彙と照合します[11]。Unigramは、各トークンに割り当てた確率の積を分割全体の確率とし、それが最大になる組み合わせを動的計画法で求めます[12]。
8.2. 国内外のLLMでの採用例
2025年以降に公開されたモデルを中心に、トークナイザーの設定と実装を確認しました。国内の例は、海外モデルを日本語で継続事前学習した系列を除いています。Hugging Face TokenizersのJSON形式ではmodel.type、SentencePiece形式ではtrainer_spec.model_typeを読み、独自実装では分割手順を確認しています。
| 開発地域 | モデル | 方式 |
|---|---|---|
| 海外 | GPT-5[21, 16] | BPE |
| 海外 | Llama 4[22] | BPE |
| 海外 | Qwen3[23] | BPE |
| 海外 | DeepSeek-V3.2[24] | BPE |
| 国内 | PLaMo 2・3[25, 26] | Unigram系 |
| 国内 | LLM-jp 4.1[27] | Unigram |
| 国内 | Sarashina2.2-3B[28] | Unigram |
| 国内 | Stockmark-2-100B-Instruct[29] | BPE |
PLaMo 2・3は、トークンごとのスコアの和を基に分割全体を動的計画法で選ぶことから、ここではUnigram系に分類しています。PLaMo 2では、整数演算を使い、同点のときは先頭側の長いトークンを優先して、分割を一定にする工夫が加えられています[25]。llama.cppのPLaMo 3対応実装もこの分割処理を使い、さらに事前分割を行います[26]。
- [21] OpenAI, "tiktoken: Model-to-encoding mappings (v0.12.0)," GitHub, 2025. v0.12.0のモデル対応表では、GPT-5に
o200k_baseを指定しています。 - [16] OpenAI, "tiktoken: BPE implementation (v0.12.0)," GitHub, 2025.
- [22] Meta, "Llama 4: Tokenizer implementation," GitHub, 2025. BPEの順位表を読み込み、tiktokenに渡す実装です。
- [23] Qwen Team, "Qwen3 Technical Report," arXiv:2505.09388, 2025. §2でバイト単位のBPEの採用を明記しています。
- [24] DeepSeek, "DeepSeek-V3.2: tokenizer.json," Hugging Face, 2025.
model.typeはBPEです。 - [25] imos, "PLaMo 2 tokenizer: Differences from the Unigram implementation," llama.cpp pull request #14560, 2025.
- [26] llama.cpp contributors, "llama.cpp: PLaMo 2 and PLaMo 3 tokenizer implementation," GitHub, 2026.
llm_tokenizer_plamo2と、PLaMo 2・3の初期化処理を参照しています。 - [27] LLM-jp, "LLM-jp-4.1-8B-Thinking Model Card," Hugging Face, 2026. Tokenizer節でUnigramとbyte fallbackの採用を明記しています。
- [28] SB Intuitions, "Sarashina2.2-3B: SentencePiece tokenizer model," Hugging Face, 2025.
trainer_spec.model_typeはUNIGRAMです。 - [29] Stockmark, "Stockmark-2-100B-Instruct: tokenizer.json," Hugging Face, 2025.
model.typeはBPEです。
8.3. モデル学習時の確率的な分割
8.4. 単語をまたぐ結合とバイトを直接扱うモデル
おわりに
参考文献
- [1] Philip Gage, "A New Algorithm for Data Compression," The C Users Journal, 12(2), 1994. バイト列の頻出ペアを未使用のバイト値で置き換える圧縮方式として提案しています。
- [2] Rico Sennrich, Barry Haddow, Alexandra Birch, "Neural Machine Translation of Rare Words with Subword Units," ACL, 2016. §3.2、Algorithm 1。文字単位のBPEをニューラル機械翻訳へ適用した原著です。
- [3] Alec Radford, Jeffrey Wu, Rewon Child, David Luan, Dario Amodei, Ilya Sutskever, "Language Models are Unsupervised Multitask Learners," OpenAI Technical Report, 2019. §2.2。GPT-2のバイト単位のBPEと事前分割を説明しています。
- [4] François Yergeau, "UTF-8, a transformation format of ISO 10646," RFC 3629, 2003.
- [5] Hugging Face, "The tokenization pipeline," Tokenizers Documentation, 2026.
- [6] Ken Whistler, "Unicode Normalization Forms (Unicode 18.0.0, Revision 58)," Unicode Standard Annex #15, 2026.
- [7] Craig W Schmidt, Varshini Reddy, Haoran Zhang, Alec Alameddine, Omri Uzan, Yuval Pinter, Chris Tanner, "Tokenization Is More Than Compression," EMNLP, 2024. 3.5億〜24億パラメーターの64モデルを使い、分割方法などとモデル品質の関係を比較しています。
- [8] Negar Foroutan, Clara Meister, Debjit Paul, Joel Niklaus, Sina Ahmadi, Antoine Bosselut, Rico Sennrich, "Parity-Aware Byte-Pair Encoding: Improving Cross-lingual Fairness in Tokenization," ACL, 2026. ACL 2026最終版。学習手順は§4、圧縮・Gini係数はTable 1、モデル品質はTable 2を参照してください。
- [9] Taido Purason, Pavel Chizhov, Ivan P. Yamshchikov, Mark Fishel, "Teaching Old Tokenizers New Words: Efficient Tokenizer Adaptation for Pre-trained Models," Findings of EACL, 2026. 継続BPE学習と、語彙削減時の依存関係を扱っています。
- [10] Yonghui Wu et al., "Google's Neural Machine Translation System: Bridging the Gap between Human and Machine Translation," arXiv:1609.08144, 2016. §4.1のWordPieceの学習方法を参照しています。
- [11] Google Research, "BERT: WordpieceTokenizer," GitHub, 2018.
- [12] Taku Kudo, "Subword Regularization: Improving Neural Network Translation Models with Multiple Subword Candidates," ACL, 2018.
- [13] Ivan Provilkov, Dmitrii Emelianenko, Elena Voita, "BPE-Dropout: Simple and Effective Subword Regularization," ACL, 2020.
- [14] Alisa Liu, Jonathan Hayase, Valentin Hofmann, Sewoong Oh, Noah A. Smith, Yejin Choi, "SuperBPE: Space Travel for Language Models," COLM, 2025.
- [15] Artidoro Pagnoni, Ramakanth Pasunuru, Pedro Rodriguez, John Nguyen, Benjamin Muller, Margaret Li, Chunting Zhou, Lili Yu, Jason E Weston, Luke Zettlemoyer, Gargi Ghosh, Mike Lewis, Ari Holtzman, Srini Iyer, "Byte Latent Transformer: Patches Scale Better Than Tokens," ACL, 2025.