In Silico

frontier AI

LLMのトークナイザ高速化は、どのモデルと入力で効くのか

2026/10/3

  • トークナイザ
  • LLM
  • Hugging Face
  • tokenizers
  • gigatoken
  • BPE
  • tiktoken
  • SentencePiece
  • 前トークン
  • キャッシュ
  • 専用コード
  • 分割規則
  • GPT-2
  • Rust
  • SIMD
  • Python
  • CPU
  • GPU
  • 互換モード
  • 正規表現
  • v1
目次
背景・問い・要点
背景

LLMは文章をそのまま計算に使わない。トークナイザという部品が、文章を整数の列(トークン番号)に変えてからモデルへ渡す。この変換は主にCPUで動き、GPUはトークン番号の列がそろうまで計算を始められない。大量の文章でモデルを学習させる場合や、多くの利用者からの要求を同時に処理する場合には、CPUの変換が間に合わず、高価なGPUがデータを待つ時間が生じうる1。

トークナイザの処理のうち、本稿が扱うのは二つの段である。一段目の分割では、正規表現で書かれた規則(分割規則)に従って、文章を単語や記号のまとまりに切り分ける。このまとまりを前トークン(pre-token)と呼ぶ。

二段目のBPE(byte pair encoding)では、前トークンを1個ずつ処理する。まず前トークンを小さな記号の並びに分け、次に、隣り合う記号を学習時に決めた順位に従って結合していく。この結合の繰り返しをマージ処理と呼ぶ。マージ処理を終えると、トークナイザは残った記号を、語彙(トークンと番号の対応表)に従って番号へ変える。

トークナイザを速くする実装は、この二つの段で処理の量を減らす。分割の段では、汎用の正規表現エンジンに毎回規則を解釈させる代わりに、特定の分割規則だけを処理するコードを手で書いて使う。本稿はこのコードを専用コードと呼ぶ。BPEの段では、一度処理した前トークンの結果を保存し、同じ前トークンが再び現れたら保存した結果を返す。この仕組みをキャッシュと呼ぶ1。

作り手は、従来の道具の何倍速いかを倍率で示す。倍率は、作り手が選んだモデルと入力で測った値である。自分のモデルと入力で同じだけ速くなるかは、倍率だけでは分からない。

本稿が根拠にするのは、二つの道具の作り手が書いた資料である。tokenizersはHugging Faceのトークナイザのライブラリで、v1はその新しい版(リリース候補)である1。速さの比較の相手はv0.23である。gigatokenは、Marcel Rødという個人の作者が作ったトークナイザで、作者はGitHubの自己紹介でスタンフォード大学の博士課程の学生と名乗る2。Hugging Faceはv1の記事で、gigatokenをtiktoken、kitoken、tokie、fastokensなどとともに速いトークナイザを押し進めてきたライブラリとして挙げ、v1の着想のいくつかはそうした別のプロジェクトから届いたと書く1。どちらの資料も、何が速くなり、何が速くならないかを書いている。

問い

トークナイザの高速化は、どのモデルとどの入力で効くのか。

要点

トークナイザの高速化のうち、分割の段の専用コードはモデルの分割規則が対象に入っているときに効き、BPEの段のキャッシュは同じ前トークンが繰り返し現れる入力ほどよく効く3。 v1の専用コードは、多くのモデルが共有する少数の規則だけを対象にし、対象外の規則は従来の正規表現エンジンで処理されて速くならない3。キャッシュは、同じ前トークンが再び現れたときだけマージ処理を省く3。v1の速さには、このほかに、マージ処理のメモリ確保と呼び出しの回数を減らす二つの変更も加わる1。作り手は、専用コードでもキャッシュでも出力のトークン番号が変わらないことを設計の条件に置いている14。

モデル・例示

前トークン10個の入力で、減る処理の回数を数える

※ この節の数値は説明のための仮定で、測定値ではありません。

トークナイザの分割の段は文章を前トークン(単語や記号のまとまり)に切り分け、BPEの段は前トークンを1個ずつマージ処理にかけてトークン番号の列に変える。ここでは前トークンの切れ目を実際の分割規則より単純にして、空白で区切った単語1個を前トークン1個と数える。

入力は二つ用意する。

  • 入力A:「to be or not to be or not to be」。前トークンは10個で、種類はto、be、or、notの4種類である。
  • 入力B:10個の単語がすべて異なる文。前トークンは10個で、種類も10種類である。

トークナイザは、まず分割の段で入力を10個の前トークンに切り分け、次にBPEの段で前トークンを1個ずつトークン番号の列に変える。

分割の段で専用コードと正規表現エンジンのどちらが使われるかは、モデルの分割規則と、使う道具が専用コードを書いた規則とで決まる。規則が専用コードの対象に入っていれば、入力Aも入力Bも専用コードが切り分ける。対象外なら、どちらの入力も正規表現エンジンが切り分ける。切り分けた結果は、専用コードでも正規表現エンジンでも同じ10個である。

BPEの段では、キャッシュの有無で処理の回数が変わる。キャッシュが無ければ、トークナイザは前トークン1個につき1回、マージ処理を行う。キャッシュが有れば、前トークン1個につき1回、保存した結果を検索する。結果が見つかればそれを返し、見つからなければマージ処理を行って結果を保存する。キャッシュは10個の結果をすべて保存できるとする。

入力とキャッシュの有無BPEの段で行う処理
入力A・キャッシュ無しマージ処理10回
入力A・キャッシュ有り検索10回、マージ処理4回
入力B・キャッシュ無しマージ処理10回
入力B・キャッシュ有り検索10回、マージ処理10回

入力Aでは、マージ処理が10回から4回に減る。減った6回は、前トークンの総数10から種類の数4を引いた数である。入力Bでは、マージ処理は10回のまま減らず、検索の10回が加わる。表のどの行でも、入力が同じなら出力されるトークン番号の列は同じである。

この例から、次の三つが言える。

  1. 専用コードとキャッシュは、出力のトークン番号を変えない。変えるのは処理の量である。
  2. 分割の段で専用コードが使われるかどうかは、モデルの分割規則と、道具が専用コードを用意した規則の一覧とで決まる。入力の文章を替えても変わらない。
  3. キャッシュが省けるマージ処理の回数は、前トークンの総数から種類の数を引いた数を超えない。繰り返しの無い入力では0回で、検索の回数だけが増える。

二つの道具は、出力の番号を変えないことを設計の条件に置いた

専用コードやキャッシュを使っても出力のトークン番号を変えないことを、二つの道具の作り手は設計の条件として書いている。Hugging Faceは2026年9月21日の記事で、v1がv0.23と同じトークン番号を出すと書く1。目標は、出力、API、語彙、マージの順位を保ったまま、改善できるところをすべて改善することだった1。作り手は利用者から見た変化を「encodingは変わらない。同じ呼び出し、同じ番号」とまとめる1。

キャッシュを使っても番号が変わらない理由も、同じ記事にある。BPEは、同じ前トークンに対して常に同じ番号を出す1。同じ前トークンが2回目に現れたときに保存した結果を返しても、マージ処理をやり直した場合と同じ番号になる。

gigatokenも、既存の道具と同じ番号を出す設定を持つ。READMEは、HF tokenizersより約1000倍速く、呼び出し側のコードを変えずに差し替えられると主張する(作り手の主張)2。gigatokenは独自のAPI(Gigatoken API)に加えて、HF tokenizersやOpenAIのtiktoken5と同じ呼び出し方で使える互換モードを持つ2。互換モードについて作者は、HF tokenizersと出力が完全に一致するよう多くの労力を払ったと書く4。

gigatokenでは、出力の完全一致に速さの費用が伴う。作者は、互換モードで出力をHF tokenizersと完全に一致させたことが、速さに無視できない費用を伴うと書く。互換モードでも全般に速くなるが、Gigatoken APIほどではない4。作者はこの費用を互換モードという設定に限って書いており、完全一致を求める実装すべてに残るとまでは原典から言えない。

分割の段が速くなるのは、規則が専用コードの対象に入っているモデルである

分割規則は、トークナイザとともに出荷され、実行中には変わらない。そうであれば、どんな規則でも解釈できる汎用の正規表現エンジンを毎回動かす必要はない。Hugging Faceは、モデルが実際に使う規則に対して、同じ結果を出すコードを一度だけ手で書けると述べる1。このコードが専用コードである。専用コードが使われるかどうかが入力の文章によらず決まるのは、どちらを使うかを決める規則がモデルの側で固定されているからである。

CPUには、1つの命令で複数のデータをまとめて処理する命令(SIMD命令)がある。手で書いたコードは、この命令を使える。v1の専用コードは、入力を1文字ずつ進んで調べる代わりに、1回の演算で64バイトをまとめて判定する1。

専用コードが書かれているのは、一部の規則だけである。Hugging Faceによれば、専用コードを使えるかどうかは、規則を認識できるかどうかで決まる。少数の規則がバイト単位のBPEを使うモデルの大半を覆うが、規則がその中に無いトークナイザは正規表現エンジンでの処理に残り、分割の段の高速化を受けない3。リリース候補の時点で、v1の専用コードの対象はGPT-2、cl100k、o200k、Tekken、DeepSeekの規則である1。

GPT-2の規則を使うモデルでは、どんな文章を入れても専用コードが切り分ける。五つの規則のどれも使わないモデルでは、どんな文章も正規表現エンジンが切り分け、分割の段は速くならない。

作り手は、計測結果の伸びの幅が大きく違う理由を、規則が対象に入っているかどうかに置いている3。同じ記事の結論節は、1スレッド・Apple M4 Maxで、v1が対応する10のモデル族においてv0.23の3〜30倍とする。下端はt5-base、上端はgpt2というモデルである1。

対象外という状態は、対応が広がれば変わる。新しい規則は、専用コードが書かれるまで従来の正規表現エンジンで処理される。作り手が次の優先に挙げるのは対応するモデル族の拡大で、1.0.0までの作業として具体的に書くのは、ほかのモデルを新しいマージ処理の実装へ移すことである1。

専用コードの対象の一覧は、道具ごとに違う。gigatokenのREADMEは、よく使われるトークナイザのほぼすべてに対応すると書く2。そのgigatokenにも、最適化が及んでいない方式がある。READMEは既知の問題として、WordPieceという別の方式にはまだ対応しておらず、SentencePieceというライブラリで作られたトークナイザは一般的なBPEほど最適化されていないと書く2。READMEの表では、GPT-2の規則の倍率が機械によって106倍から1,268倍であるのに対し、SentencePiece系の行の倍率はどの機械でも7〜22倍にとどまる2。

キャッシュが省くのは、同じ前トークンが繰り返した分だけである

v1は、一度処理した前トークンの結果を保存し、同じ前トークンが再び現れたときにマージ処理を省く1。省ける回数の上限は、前トークンの総数から種類の数を引いた数である。どの種類も、初めて現れたときには1回マージ処理を要するからである。

入力が大きくなると、繰り返しの割合は上がりうる。Hugging Faceは、入力が大きくなるにつれ、単語の種類の数は単語の総数よりゆっくり増えうると書く。そうなれば、繰り返す単語が入力に占める割合が増える。ただし新しい単語も現れ続ける1。新しく現れる単語は種類の数を増やし、省ける回数が総数に占める割合を押し下げる。割合が上がるか下がるかは、新しく現れる単語がどれだけ混ざるかで決まる。

繰り返しの少ない入力では、キャッシュの費用だけが残ることがある。作り手は、キャッシュは繰り返す前トークンが多い入力で最もよく効き、繰り返しの少ない入力は検索の費用を払っても当たりが少ないことがあると書く3。前トークンがすべて異なる入力では、前トークンの数だけ検索が加わり、マージ処理は1回も減らない。gigatokenの作者も、速さの主な出所として、分割の段を正規表現エンジンの代わりにSIMD命令で処理することと、一度見た前トークンの結果を引くキャッシュを挙げる2。作者は、キャッシュはすぐ大きくなり、前トークンの分布は裾が長いので、キャッシュは難しいとも書く2。READMEは既知の問題として、CJK(中国語・日本語・韓国語)の多いデータはトークン化がずっと遅く、多くの分割規則がこの場面でキャッシュを難しくすると書き、改善に取り組んでいるとも添える2。

倍率は、計測に使う入力の選び方でも変わる。キャッシュが省ける回数は、入力の中の繰り返しで決まるからである。作り手は、ベンチマークの設計の小さな違いがトークナイザの性能に大きな違いを生みうると書く。同じ文書を繰り返し処理する計測は、別々の文書を流す計測より速いことがあり、二つの計測が測っている仕事は違う3。同じ文書を2回続けて処理する計測では、文書がキャッシュに収まる大きさなら、2回目の前トークンはすべて保存済みである。作り手の見出しの数値は別々の文書を流した計測で、計測に使った文書の全体はキャッシュに収まらない大きさである3。

処理の回数に入っていない費用は、別に測る

v1の変更には、マージ処理や検索の回数を変えずに、1回あたりの費用を下げるものもある。Hugging Faceは、v1の速さが四つの変更の組み合わせから来て、それぞれが処理の流れの別の箇所で仕事を減らすと書く1。四つのうち二つは、分割の段の専用コードとBPEの段のキャッシュである。

残りの二つは、マージ処理が使うメモリと、呼び出しの回数を変える。v1は、呼び出し側が用意した作業用の領域にマージ処理の作業データを置き、マージ処理のたびにメモリを確保し直すことをやめた1。またv1は、BPEを実行する部品の呼び出しを、前トークンごとに1回から、前トークンのまとまりごとに1回へ減らした1。

Pythonから呼ぶときの負荷は、作り手の計測値に入っていない。tokenizersをPythonから呼ぶための層は、計測されたものと同じコードを包んで作られているが、呼び出しごとの負荷が加わる。Hugging Faceの記事の計測値には、その負荷が含まれていない3。専用コードとキャッシュが減らすのも、分割の段とBPEの段の中の処理だけである。

gigatokenには、ファイルをRustの実装が直接読む呼び出し方がある。READMEによれば、Gigatoken APIを使うとRustの実装がデータを直接読み、オーバーヘッドを可能な限り省きつつ並列性を最大にできる2。ただし同じAPIでも、Pythonのデータ構造を渡せば、Pythonから読む負荷はかかると作者は書く2。

出典5件
  1. Hugging Face(Arthur Zucker, Simon Brandeis, Luc Georges, Lysandre)「tokenizers v1: encode, decode and scaling, measured」2026-09-21. https://huggingface.co/blog/tokenizers-v1 — 同じ番号を保つ設計条件、四つの変更、3〜30倍の計測値。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17 ↩18 ↩19 ↩20

  2. Marcel Rød「Gigatoken」README(2026-09-27 取得). https://github.com/marcelroed/gigatoken — HF tokenizersより約1000倍速いという主張、速さの出所(SIMDの分割とキャッシュ)、ベンチマーク表と既知の問題。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11

  3. 同記事の限界の記述. https://huggingface.co/blog/tokenizers-v1#the-split-bitstreams-instead-of-a-regex — 対象外の規則は高速化を受けないこと、繰り返しの少ない入力ではキャッシュが効きにくいこと、計測の設計とPythonの負荷の扱い。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9

  4. 同 README Compatibility Mode 節(2026-09-27 取得). https://github.com/marcelroed/gigatoken#compatibility-mode-easiest — 互換モードでHF tokenizersと出力を完全に一致させたことが速さに無視できない費用を伴うこと。 ↩ ↩2 ↩3

  5. OpenAI「tiktoken README」(2026-09-27 取得). https://github.com/openai/tiktoken — 互換モードの相手であるtiktokenが、OpenAIのモデル向けの高速なBPEトークナイザであること。 ↩

この記事はAIが執筆しています。内容には誤りが含まれる可能性があります。ご注意ください。