- AI ⚙️
トークナイザの仕組み|BPE・Unigramを自作し日本語のトークン数を実測
結論
トークナイザは、文字列を「語彙にある断片(サブワード)」の列に分けて番号(ID)に変える仕組みです。BPE は「よく隣り合う2つを結合する」を繰り返して語彙を作り、Unigram は「大きな候補から確率の低い断片を削る」ことで語彙を作ります。RJC の技術記事で学習した手元のトークナイザでは、同じ日本語が語彙1,000では1トークンあたり1.15文字、語彙32,000では2.51文字になり、学習データに無い分野(料理・医療・法律)の文章では語彙8,000でも1トークンあたり1文字前後まで落ちました。
この記事でわかること
- BPE の学習(ペアの結合)を Python で自作し、結合の順番と語彙が育つ過程を表で見る方法
- SentencePiece の BPE と Unigram を語彙1k〜32kで学習し、日本語・英語・コード・絵文字・分野外の文章のトークン数を実測した表
- バイトフォールバック・未知語・NFKC 正規化の落とし穴と、LLM の料金とコンテキスト長への響き方の計算
この記事で実際に動かしたもの:Python だけで書いた BPE の学習・分割と Unigram の Viterbi 分割、PyPI の sentencepiece 0.2.2 で学習した BPE・Unigram のモデル23個(語彙1,000〜32,000、オプション違いを含む)。RJC の技術記事227本を学習用、別の20本を評価用に分け、トークン数・1トークンあたりの文字数・未知語・バイトに分解された数・元の文字列に戻るかを測りました。
LLM は文字をそのまま読むのではなく、トークナイザが切り分けた「トークン」の番号の列を読みます。料金もコンテキスト長もトークンで数えるので、同じ文章が何トークンになるかは、そのまま費用と入る量を決めます。この記事では、トークナイザの中身を自分で作って動かし、日本語でトークンが増えたり減ったりする理由を数字で確かめます。
flowchart TD
A[入力の文字列] --> B[正規化<br>NFKC・空白の整理]
B --> C[事前分割<br>空白や文字種で区切る]
C --> D[サブワード分割<br>BPE または Unigram]
D --> E[ID に変換<br>語彙表を引く]
E --> F[後処理<br>特殊トークンを足す]
D -.語彙に無い文字.-> G[バイトに分解<br>または unk]
よく調べられることの答えを先にまとめます。
| 知りたいこと | 答え | 詳しい節 |
|---|---|---|
| トークナイザの仕組み | 正規化→事前分割→サブワード分割→ID 化→特殊トークン付与の順に処理する | トークナイザとは |
| BPE とは | 隣り合う2つのうち最も多い組を1つに結合する、を語彙数に達するまで繰り返す方式 | BPEとは |
| Unigram とは | 断片ごとの確率を持ち、確率の積が最大になる分け方を選ぶ方式。学習は削っていく | Unigramとは |
| SentencePiece の使い方 | SentencePieceTrainer.train で学習し、SentencePieceProcessor の encode・decode で使う | SentencePieceの使い方 |
| 日本語のトークン数 | 手元の語彙8,000(BPE)で1トークンあたり2.06文字。語彙と学習データで2倍以上変わる | 語彙数とトークン数の実測 |
| 英語・コード・絵文字との差 | 英語2.83、コード2.17、絵文字を含む文1.02文字/トークン(語彙8,000の BPE) | 種類別のトークン数 |
| 語彙に無い文字はどうなるか | バイトフォールバック有りなら UTF-8 の1バイトずつ、無しなら unk になり元に戻らない | バイトフォールバックと未知語 |
| 料金とコンテキスト長への影響 | トークン数=文字数÷(文字/トークン)。128Kトークンに入る日本語は手元の条件で12万〜32万字 | 料金とコンテキスト長 |
確認環境:Ubuntu 24.04.5 LTS(Intel Xeon 2.10GHz・2コア、ほかの作業と同時に動いている共有のマシン)で、Python 3.13.18 と PyPI の sentencepiece 0.2.2 を作業場所の venv に入れて動かしました。コーパスは RJC の技術記事(本サイトの技術ブログの本文247本)から HTML タグとコードブロックを外したもので、乱数で20本を評価用(368,255文字・9,088行)に取り分け、残り227本を学習に使いました。sentencepiece の学習には学習用の4行に1行(1,084,449文字・27,115行)、自作 BPE には9行に1行(473,600文字)を使っています。コードの評価には評価用20本のコードブロック(35,858文字)、英語の評価には Ubuntu に入っている Apache License 2.0 の本文1万文字、分野外の評価には筆者が書いた料理・医療・法律の文章(各約800文字)と絵文字を含む文を使いました。tiktoken 0.14.0 も入れましたが、語彙ファイルの取得先に接続できなかったため(エラーの原文は後の節)、OpenAI のエンコーディングでは数えていません。学習済みの LLM や LLM の API には接続していないため、各社のモデルの実際のトークン数は測っていません。各社のトークナイザの情報は、tiktoken・Llama 3 の公式リポジトリ、Meta の公式ブログ、Qwen 公式の config.json、Anthropic の公式ドキュメントで確かめた範囲だけを書いています。図は mermaid 11.16.0 で描画を確かめました(確認日 2026年11月4日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「トークナイザの仕組み|BPE・Unigramを自作し日本語のトークン数を実測」
トークナイザとは(文字列をIDの列に変える4つの段階)
この節では、トークナイザが文字列をトークン ID に変えるまでの段階と、なぜサブワードという単位が使われるのかが分かります。
トークナイザは、文字列を「語彙表にある断片」の列に分け、それぞれを整数の ID に変える部品です。LLM の入力層は「ID → ベクトル」の表(埋め込み行列)なので、モデルが読めるのは語彙表にある断片だけです。語彙表と埋め込み行列は1対1で結び付いているため、学習済みのモデルのトークナイザを後から差し替えることはできません。
1正規化
全角英数を半角に、互換文字(①や半角カナ)を標準の形に、連続する空白を1つに、などをそろえる。SentencePiece の既定は NFKC を元にした nmt_nfkc。
2事前分割
サブワードが越えてはいけない境界を決める。英語は空白、GPT 系は正規表現、日本語では文字種の切れ目を使うこともある。ここで「何が1トークンになりうるか」の上限が決まる。
3サブワード分割
BPE なら学習した結合の規則を順に当て、Unigram なら確率が最大の分け方を探す。語彙に無い文字はバイトに分けるか unk にする。
4ID 化と後処理
断片を語彙表で引いて ID にし、文頭・文末やチャット形式の特殊トークンを足す。特殊トークンも料金とコンテキストのトークンに数えられる。
単位の選び方は、単語・文字・サブワード・バイトの4通りです。単語単位は語彙が数十万を超え、学習データに無い単語を表せません。文字単位は語彙が小さく済みますが、日本語でも英語でも系列が長くなり、計算量(注意は系列長の2乗)とコンテキストを食います。サブワードは「よく出る並びは長い1トークン、まれな並びは短い断片の組み合わせ」にする中間の方式で、BPE と Unigram が代表です。バイト単位は語彙256で何でも表せますが、日本語の1文字が UTF-8 で3バイトなので、そのままでは系列が文字単位の3倍になります。
BPE(Byte Pair Encoding)
小さな語彙(文字やバイト)から始め、結合を足して語彙を増やす
分割は「学習した結合の順番」で決まり、1通りに定まる
GPT 系(tiktoken)・Llama 3 はバイト単位の BPE
Unigram(言語モデル)
大きな候補の集合から始め、尤度への寄与が小さい断片を削っていく
分割は「断片の確率の積が最大」の経路を Viterbi で探す
確率を持つので、分割を確率的に揺らす学習(サブワード正則化)ができる
トークンの数え方の目安や、各サービスでの数え方の道具は、関連記事「トークンとは|生成AIの数え方・日本語の文字数目安と節約方法」で扱っています。この記事は、その数字がどう決まるのかを、中身を作って確かめる側に役割を分けています。
BPEとは(ペアの結合を自作して学習の過程を見る)
この節では、BPE の学習が「最頻のペアを結合する」だけでできていることと、日本語の文章で語彙がどの順番で育つかが分かります。
BPE の学習は、①全部を1文字ずつに分ける ②隣り合う2つの組を数える ③最も多い組を1つの新しい断片として語彙に足し、文章中のその組を置き換える、の②③を決めた回数だけ繰り返すものです。結合した順番の一覧(merges)が、そのままトークナイザの中身になります。Python で書くと、学習の中心はこれだけです(差分だけ数え直す工夫を省いた形)。
import re, collections
# 事前分割:漢字・ひらがな・カタカナ・英数字の連続、それ以外は1文字ずつ
PAT = re.compile(r'[一-鿿々〆]+|[ぁ-ゟ]+|[゠-ヿー]+|[A-Za-z0-9_]+|[^\s]')
def train_bpe(lines, n_merges):
words = collections.Counter(tuple(w) for l in lines for w in PAT.findall(l))
merges = []
for _ in range(n_merges):
pairs = collections.Counter()
for w, c in words.items():
for a, b in zip(w, w[1:]):
pairs[(a, b)] += c
if not pairs:
break
(a, b), c = max(pairs.items(), key=lambda x: (x[1], x[0]))
merges.append((a, b, c))
new = collections.Counter()
for w, cnt in words.items():
out, i = [], 0
while i < len(w):
if i < len(w) - 1 and w[i] == a and w[i + 1] == b:
out.append(a + b); i += 2
else:
out.append(w[i]); i += 1
new[tuple(out)] += cnt
words = new
return merges
def encode(text, merges):
rank = {(a, b): i for i, (a, b, _) in enumerate(merges)}
out = []
for w in PAT.findall(text):
s = list(w)
while len(s) > 1:
r, k = min((rank.get(p, 1 << 60), k) for k, p in enumerate(zip(s, s[1:])))
if r == 1 << 60:
break
s[k:k + 2] = [s[k] + s[k + 1]]
out += s
return out
RJC の技術記事(473,600文字)で4,000回結合させたところ、学習は33.4秒でした(変わった単語だけ数え直す版。上の素朴な版は300回で12.1秒かかり、回数に比例して遅くなります)。最初の結合は次のとおりです。「回数」はその時点で隣り合っていた回数です。
| 順番 | 結合した組 | できた断片 | 回数 |
|---|---|---|---|
| 1 | ま+す | ます | 2,633 |
| 2 | i+n | in | 1,774 |
| 3 | e+r | er | 1,592 |
| 5 | し+た | した | 1,417 |
| 6 | て+い | てい | 1,286 |
| 9 | で+は | では | 1,135 |
| 10 | で+す | です | 1,122 |
| 21 | り+ます | ります | 807 |
| 37 | てい+ます | ています | 534 |
です・ます調の語尾と英語の綴りの断片が先に結合し、結合済みの断片同士がさらに結合して(「てい」+「ます」)長くなっていくのが分かります。技術用語は後から育ちます。同じ学習で「トークン」は次の順でできました。
158回目:ー+ク → ーク
カタカナ語に共通する並びとして、222回の出現で結合。
1,252回目:ーク+ン → ークン
この時点で「ークン」が隣り合っていたのは34回。
1,331回目:ト+ークン → トークン
32回の出現で1つの断片になった。以後「トークン」は1トークンで表される。
結合の回数を増やすと、評価用の20本(学習に使っていない記事)のトークン数は次のように減りました。0回は1文字ずつに分けた状態です。
| 結合の回数 | 語彙数 | 評価用のトークン数 | 文字/トークン | 分割の時間 |
|---|---|---|---|---|
| 0 | 1,492 | 339,447 | 1.00 | 0.7秒 |
| 500 | 1,992 | 251,242 | 1.35 | 2.9秒 |
| 1,000 | 2,492 | 230,953 | 1.47 | 4.7秒 |
| 2,000 | 3,492 | 210,959 | 1.61 | 9.9秒 |
| 4,000 | 5,492 | 195,647 | 1.74 | 20.5秒 |
最初の500回で26%減り、そこから4,000回まで足してもさらに22%しか減りません。よく出る組から順に結合するので、語彙を増やすほど1回の結合で減るトークンは小さくなります。なお自作の分割は、毎回すべての組の順位を見る素朴な書き方のため、同じ文章を sentencepiece(C++)は0.18秒で分けました。仕組みを確かめるには自作、使うにはライブラリ、と分けるのが確実です。
結合の回数ごとに、同じ文がどう分かれるかも見ておきます。
0回 ト|ー|ク|ナ|イ|ザ|の|仕|組|み|を|自|作|し|て|日|本|語|の|ト|ー|ク|ン|数|を|実|測|す|る (29)
500回 ト|ーク|ナ|イ|ザ|の|仕|組|み|を|自|作|して|日本語|の|ト|ーク|ン|数|を|実測|する (22)
2000回 ト|ーク|ナ|イ|ザ|の|仕組|みを|自作|して|日本語|の|トークン|数|を|実測|する (17)
事前分割で「仕組み」が1つにならない理由
2,000回結合しても「仕組み」は「仕組」と「みを」に分かれたままです。原因は事前分割です。自作の BPE は、漢字の連続とひらがなの連続を別の「単語」として扱うので、「仕組」(漢字)と「み」(ひらがな)の間は結合の候補にすら上がりません。代わりに「みを」がひらがなの連続の中で結合しています。BPE の結合は事前分割の境界を越えないため、どこで区切るかが語彙の形を決めます。GPT 系や Llama 3 の BPE も、正規表現で事前分割してから結合します(Llama 3 の公式リポジトリの tokenizer.py にその正規表現があります)。後で見る sentencepiece は空白だけで区切るので、「の仕組み」が1トークンになりました。文字種の判定に使う正規表現は、関連記事「正規表現でひらがな・カタカナ・漢字を判定する方法【言語別】」で扱っています。
Unigramとは(Viterbiを自作してSentencePieceと照合)
この節では、Unigram の分割が「確率の積が最大の分け方」であることと、学習が候補を削っていく形で進むことが、自作の分割器と sentencepiece の学習ログで分かります。
Unigram は、語彙の各断片に確率(sentencepiece では対数確率のスコア)を持たせ、文をどう分けたらスコアの合計が最大になるかを動的計画法(Viterbi)で探します。BPE のような「結合の順番」は持っていません。sentencepiece で学習した語彙4,000の Unigram のスコア表を読み出し、自分で書いた Viterbi で分けて、sentencepiece の encode の結果と比べました。
def viterbi(s, score, maxlen, unk):
n = len(s)
best = [-1e18] * (n + 1); back = [0] * (n + 1); best[0] = 0.0
for i in range(n):
if best[i] <= -1e17:
continue
hit = False
for j in range(i + 1, min(n, i + maxlen) + 1):
p = s[i:j]
if p in score: # 語彙にある断片だけ候補にする
hit = True
if best[i] + score[p] > best[j]:
best[j] = best[i] + score[p]; back[j] = i
if not hit and best[i] + unk > best[i + 1]: # どの断片にも当たらない文字は unk
best[i + 1] = best[i] + unk; back[i + 1] = i
out, j = [], n
while j > 0:
out.append(s[back[j]:j]); j = back[j]
return out[::-1]
# 入力は sp.normalize(line)(正規化と先頭の ▁ を付けた形)を渡す
lines 2000 same 1887 rate 0.9435 sec 0.6
不一致113行の内訳 Counter({'byte': 113})
['▁', 'ト', 'ー', 'ク', 'ナ', 'イ', 'ザ', 'の仕組み', 'を', '自作', 'する'] 合計 -80.976
スコア [-1.947, -7.983, -6.88, -7.401, -9.226, -7.958, -10.519, -9.161, -4.309, -9.469, -6.122]
評価用の2,000行のうち1,887行(94.35%)が完全に一致し、残る113行はすべて語彙に無い文字をバイトに分解した行でした(自作の側はバイトフォールバックを実装していないため)。語彙にある文字だけの行では100%一致したことになり、Unigram の分割が「スコアの合計が最大の経路」そのものであることを確かめられます。
確率を持つので、「2番目・3番目に良い分け方」も出せます。sentencepiece の nbest_encode_as_pieces で上位4つを出すと次のようになりました。
['▁', 'ト', 'ー', 'ク', 'ナ', 'イ', 'ザ', 'の仕組み', 'を', '自作', 'する']
['▁', 'ト', 'ー', 'ク', 'ナ', 'イ', 'ザ', 'の', '仕組み', 'を', '自作', 'する']
['▁', 'ト', 'ー', 'ク', 'ナ', 'イ', 'ザ', 'の仕組み', 'を', '自作', 'す', 'る']
['▁', 'ト', 'ー', 'ク', 'ナ', 'イ', 'ザ', 'の仕組み', 'を', '自', '作', 'する']
学習のときに、この中から確率的に分け方を選んでモデルに見せるのがサブワード正則化(enable_sampling=True, alpha=0.1)です。同じ文を4回分けると「の仕組み」「の|仕組み」「の|仕|組み」が混ざって出ました。BPE にも BPE-Dropout(結合を確率的に飛ばす)という同じ目的の仕組みがあり、sentencepiece では BPE のモデルでも enable_sampling で使えます。
Unigram の学習は「削っていく」
Unigram の学習は BPE と逆向きです。sentencepiece の学習ログ(語彙8,000)を見ると、最初に140,606個の候補(接尾辞配列で集めた頻出の部分文字列)を作り、EM で各断片の確率を推定しては、尤度への寄与が小さい断片を約25%ずつ削っていきます。
trainer_interface.cc:578] Alphabet size=1283
trainer_interface.cc:579] Final character coverage=0.9995
unigram_model_trainer.cc:354] Initialized 140606 seed sentencepieces
unigram_model_trainer.cc:644] EM sub_iter=1 size=54037 obj=35.4147 num_tokens=338305 num_tokens/piece=6.26062
unigram_model_trainer.cc:644] EM sub_iter=1 size=40257 obj=35.8897 num_tokens=355136 num_tokens/piece=8.82172
unigram_model_trainer.cc:644] EM sub_iter=1 size=30133 obj=36.9912 num_tokens=375449 num_tokens/piece=12.4597
unigram_model_trainer.cc:644] EM sub_iter=1 size=22590 obj=38.1458 num_tokens=397242 num_tokens/piece=17.5849
unigram_model_trainer.cc:644] EM sub_iter=1 size=16940 obj=39.6457 num_tokens=420953 num_tokens/piece=24.8496
unigram_model_trainer.cc:644] EM sub_iter=1 size=12704 obj=41.2808 num_tokens=446061 num_tokens/piece=35.1119
unigram_model_trainer.cc:644] EM sub_iter=1 size=9528 obj=42.9548 num_tokens=472777 num_tokens/piece=49.6198
unigram_model_trainer.cc:644] EM sub_iter=1 size=8800 obj=43.4791 num_tokens=484355 num_tokens/piece=55.0403
size(語彙の大きさ)が減るたびに num_tokens(学習データを分けたときのトークン数)が増えています。語彙を小さくするほど文が細かく分かれる、という関係が学習の途中にもそのまま出ています。最後は目標の1.1倍(8,800)から、スコアの高い順に8,000まで絞って終わります。
SentencePieceの使い方(学習・分割・復元とオプション)
この節では、PyPI の sentencepiece でトークナイザを学習し、分割と復元をする最小の手順と、日本語で効くオプションが分かります。
sentencepiece は、空白で区切られていない日本語のような言語でも、生の文章からそのままトークナイザを学習できるライブラリです。空白は「▁」(U+2581)に置き換えて断片の一部として扱うので、分割した断片をつなげるだけで元の文に戻せる設計になっています(正規化で変わった部分を除く)。
python3 -m venv .venv
.venv/bin/pip install sentencepiece # 手元では sentencepiece 0.2.2 が入った
import sentencepiece as spm
# 1行1文のテキストファイルから学習する
spm.SentencePieceTrainer.train(
input='train_sp.txt', model_prefix='sp_bpe_8000',
model_type='bpe', # 'unigram'(既定)/ 'bpe' / 'char' / 'word'
vocab_size=8000, # 特殊トークンとバイトの256個を含めた総数
character_coverage=0.9995, # 文字の出現の何割を語彙の文字で覆うか
byte_fallback=True, # 語彙に無い文字を UTF-8 のバイトに分ける
num_threads=1)
sp = spm.SentencePieceProcessor(model_file='sp_bpe_8000.model')
print(sp.encode('トークナイザの仕組みを自作する', out_type=str))
print(sp.encode('トークナイザの仕組みを自作する'))
print(sp.decode(sp.encode('トークナイザの仕組みを自作する')))
['▁', 'ト', 'ーク', 'ナ', 'イ', 'ザ', 'の仕組み', 'を', '自作', 'する']
[6717, 6761, 460, 7057, 6778, 6954, 2780, 6733, 1545, 275]
トークナイザの仕組みを自作する
学習で作られる .model ファイルには、正規化の規則・語彙・スコアがすべて入っています(公式の README の説明)。語彙8,000の BPE で365KB、語彙32,000の Unigram で906KB でした。主なオプションは次のとおりです(既定値は公式リポジトリの doc/options.md で確認)。
| オプション | 既定値 | 意味 | 日本語での注意 |
|---|---|---|---|
| model_type | unigram | unigram・bpe・char・word | 手元では BPE の方がトークン数が3〜7%少なかった |
| vocab_size | 8000 | 語彙の総数(特殊記号を含む) | 文字だけで1,000を超えるので、1,000では学習が止まる |
| character_coverage | 0.9995 | 語彙に入れる文字の範囲(出現の割合) | まれな漢字は語彙から外れる。1.0にするとすべて入る |
| byte_fallback | false | 語彙に無い文字をバイトに分ける | false だと unk になり、元の文字が失われる |
| normalization_rule_name | nmt_nfkc | 正規化の規則 | 全角の括弧やコロンが半角に変わり、元に戻らない |
| split_digits | false | 数字を1桁ずつに分ける | 手元では日本語の記事のトークンが5%増えた |
| max_sentencepiece_length | 16 | 1つの断片の最大文字数 | 空白の無い日本語では長い定型文が1断片になりうる |
| max_sentence_length | 4192 | 学習で読む1行の最大バイト数 | 日本語は1文字3バイトなので約1,400文字を超える行は読まれない |
学習の時間は、BPE が語彙1,000〜32,000で1.3〜4.5秒、Unigram が18.2〜54.8秒でした(共有のマシンで負荷が高い時間帯の値)。Unigram は語彙が小さいほど削る回数が増えるので遅くなり、語彙32,000が最も速く終わりました。
自分で確かめる(ターミナルで1行):手元の文章ファイル(1行1文)で、語彙8,000の BPE を学習して1文あたりの平均トークン数を出すには次のとおりです。input のファイル名だけ差し替えて動きます。
python3 -c "import sentencepiece as s; s.SentencePieceTrainer.train(input='my.txt',model_prefix='m',model_type='bpe',vocab_size=8000,byte_fallback=True,minloglevel=2); p=s.SentencePieceProcessor(model_file='m.model'); L=[l for l in open('my.txt') if l.strip()]; print(sum(len(l.strip()) for l in L)/sum(len(p.encode(l.strip())) for l in L),'文字/トークン')"
語彙数1k〜32kで日本語のトークン数を実測
この節では、語彙数を変えると同じ日本語の文章が何トークンになるかが、BPE と Unigram の両方の実測で分かります。
学習用の記事で語彙1,000〜32,000の BPE と Unigram を作り、学習に使っていない評価用の20本(368,255文字)を分けました。語彙1,000だけは文字の数が足りないため character_coverage を0.98に下げています(理由は次の節のエラー原文)。
| 語彙数 | BPE のトークン数 | BPE 文字/トークン | Unigram のトークン数 | Unigram 文字/トークン |
|---|---|---|---|---|
| 1,000 | 313,299 | 1.15 | 322,401 | 1.11 |
| 2,000 | 266,754 | 1.35 | 278,617 | 1.29 |
| 4,000 | 203,426 | 1.77 | 216,636 | 1.66 |
| 8,000 | 174,212 | 2.06 | 188,107 | 1.91 |
| 16,000 | 155,885 | 2.30 | 168,512 | 2.13 |
| 32,000 | 143,024 | 2.51 | 154,485 | 2.33 |
語彙を2倍にするごとのトークンの減り方は、1k→2kで15%、2k→4kで24%、4k→8kで14%、8k→16kで11%、16k→32kで8%でした。日本語は文字の種類だけで1,300近くあるため、語彙2,000までは「文字を覚えるだけ」で枠を使い切り、4,000前後から語尾や頻出語の結合が効き始めます。そこから先は、語彙を倍にしても1割前後しか減りません。
同じ文の分かれ方を語彙ごとに並べると、何が1トークンになっていくかが見えます。
BPE 1000 ▁|ト|ー|ク|ナ|イ|ザ|の|仕|組|み|を|自|作|して|日|本|語|の|ト|ー|ク|ン|数|を|実|測|する (28)
BPE 8000 ▁|ト|ーク|ナ|イ|ザ|の仕組み|を|自作|して|日本語の|トークン|数を|実測|する (15)
BPE 32000 ▁|トーク|ナイ|ザ|の仕組み|を自作|して|日本語の|トークン|数を|実測|する (12)
Uni 32000 ▁|ト|ー|ク|ナイ|ザ|の仕組み|を自作|して|日本語の|トークン数|を実測する (12)
BPE 8000 ▁do|cker|▁com|po|se|▁up|▁-|d|▁で|▁PostgreSQL|▁16|▁を|起動|します (14)
BPE 32000 ▁docker|▁com|pose|▁up|▁-|d|▁で|▁PostgreSQL|▁16|▁を|起動します (11)
BPE と Unigram の差は、学習の目的の違いから来ます。BPE は「よく隣り合う組を結合する」ので結果としてトークン数を減らす方向に働き、Unigram は「学習データの尤度を上げる」ことを目的にしているので、確率が高ければ短い断片の組み合わせも選びます(上の Unigram 32000 は「ト|ー|ク」と分けたまま「トークン数」を1つにしています)。トークン数だけで比べれば BPE が少なめでしたが、どちらが LLM の性能に良いかは、この実験では測っていません。
語彙32,000の語彙表で最も長い断片を見ると、「で確かめた範囲だけを書いています」「は一緒に成長を楽しめる会社です」という、RJC の記事に何度も出る定型の文が1トークンになっていました。学習データに同じ文が繰り返し入ると、その文に語彙の枠が使われます。実際の LLM の学習で重複の除去が重視されるのは、モデルの側だけでなく、トークナイザの側にもこうした偏りが出るためです。
英語・日本語・コード・絵文字でのトークン数の差
この節では、同じトークナイザで英語・日本語・コード・絵文字を分けたとき、1トークンあたりの文字数がどれだけ違うかが分かります。
| 文章の種類 | 文字数 | BPE 8k 文字/トークン | BPE 32k 文字/トークン | Unigram 8k 文字/トークン |
|---|---|---|---|---|
| 日本語(評価用の記事) | 368,255 | 2.06 | 2.51 | 1.91 |
| 英語(Apache License 2.0) | 10,000 | 2.83 | 3.56 | 2.29 |
| コード(評価用の記事のコードブロック) | 35,858 | 2.17 | 2.73 | 1.99 |
| 絵文字を含む日本語の文 | 318 | 1.02 | 1.14 | 0.95 |
英語の評価文は学習データ(日本語の記事)にほとんど出ない法律文の英語ですが、記事中の英単語とコードで覚えた綴りの断片が効き、1トークンあたりの文字数は日本語より多くなりました。ただし英語は1単語が平均5文字前後なので、「1単語あたり」で見ると英語は2トークン近く使っています。文字数で比べると日本語の方がトークンが少なく見えても、同じ内容を伝えるのに要る文字数が言語で違うので、言語の比較は「同じ内容の文章」で行う必要があります。
大文字・小文字でも数が変わります。語彙8,000の BPE では「hello world」が4トークン、「Hello World」が5トークン、「HELLO WORLD」が8トークンでした。学習データで大文字だけの綴りが少ないためです。
絵文字の文が1トークン1文字前後まで落ちたのは、技術記事に絵文字がほとんど無く、絵文字がすべて語彙の外になったためです。この扱いは次の節で詳しく見ます。
バイトフォールバックと未知語(unk)の扱い
この節では、語彙に無い文字がバイトに分けられる様子と、バイトフォールバックを切ったときに文字が失われる様子が、実際の出力で分かります。
バイトフォールバックは、語彙に無い文字を UTF-8 の1バイトずつ(<0x00>〜<0xFF> の256個の特殊な断片)に分ける仕組みです。どんな文字でも表せて元に戻せますが、日本語の漢字は3バイト、絵文字は4バイトなので、その分トークンが増えます。
byte_fallback=True(語彙8,000の Unigram)
'デプロイ成功🎉'
['▁', 'デプロイ', '成', '功', '<0xF0>', '<0x9F>', '<0x8E>', '<0x89>']
'👨💻'(3文字・11バイト)→ 12トークン
['▁', '<0xF0>', '<0x9F>', '<0x91>', '<0xA8>', '<0xE2>', '<0x80>', '<0x8D>', '<0xF0>', '<0x9F>', '<0x92>', '<0xBB>']
byte_fallback=False(同じ条件で切ったもの)
['▁', 'デプロイ', '成', '功', '🎉'] → ID [3, 2942, 584, 7813, 0]
decode → 'デプロイ成功 ⁇ '
'魑魅魍魎と齟齬' → ['▁', '魑魅魍魎', 'と', '齟齬'] → decode ' ⁇ と ⁇ '
語彙に無い文字の扱い
手元では「魑魅魍魎」がまとめて1つの unk になり、4文字の情報がすべて消えた
バイトフォールバックを切るとトークン数は減ります(絵文字の文が106→59トークン)。しかしそれは情報を捨てた結果なので、トークン数の比較では「unk の数」と並べて見る必要があります。絵文字の🎉は UTF-8 で4バイト、👨💻は「👨+ゼロ幅接合子+💻」の3文字で11バイトです。文字数とバイト数の違いは、関連記事「文字化けの原因8選|濁点分離・utf8mb4・絵文字の文字数」で扱っています。
character_coverage も効きます。既定の0.9995では、学習データの1,717種類の文字のうち1,283種類だけが語彙に入り、残りはまれな文字としてバイト扱いになります。1.0にすると評価用の記事でバイトに分けられた数が2,294→1,224に減りましたが、その分、語彙の枠を文字に使うため、全体のトークン数は188,107→189,373と少し増えました。
学習データに無い分野の文章でトークン数が増える
この節では、トークナイザの学習データと違う分野の文章を入れたときに、トークン数がどれだけ増えるかが分かります。
IT の記事だけで学習した語彙8,000の BPE に、料理・医療・法律の文章(筆者が書いた各約800文字)を入れました。
| 文章の分野 | 文字/トークン | バイトに分けられた数 | 語彙に無かった文字の例 |
|---|---|---|---|
| IT(評価用の記事) | 2.06 | 368,255文字中 2,294 | (まれな記号・漢字) |
| 法律 | 1.13 | 808文字中 45 | 甲 乙 債 裁 賠 轄 争 審 |
| 医療 | 1.09 | 772文字中 66 | 薬 熱 痛 胃 菌 腰 咳 麻 鏡 |
| 料理 | 0.94 | 796文字中 111 | 肉 鶏 塩 油 火 煮 茹 醤 鰹 |
料理の文章では、65文字の1行が69トークンになり、文字数よりトークン数が多くなりました。「肉」「鶏」「火」「油」は、学習データ(1,084,449文字)に0〜1回しか出ないため語彙に入らず、3バイトずつに分かれています。
鶏もも肉は皮目から焼き、(65文字 → 69トークン)
▁|<0xE9>|<0xB6>|<0x8F>|も|も|<0xE8>|<0x82>|<0x89>|は|<0xE7>|<0x9A>|<0xAE>|目|から|焼|き|、|...
文字が語彙にあっても、その分野の言い回しが結合されていなければ1文字ずつに近くなります。法律の文章の「履行」「遅滞」「損害」は、どれも1文字ずつに分かれました。分野外の文章は短い(各約800文字)ので数字の幅は大きめですが、IT の記事の半分程度の効率になる傾向ははっきり出ています。
反対に、学習データに分野の文章を足すと効率が上がります。コードは評価用でも学習データ(文章だけ)には入っていなかったので、学習用の記事のコードブロックを3行に1行足して学習し直しました。
| 学習データ(BPE 8k) | コードのトークン数 | 日本語のトークン数 | 英語のトークン数 |
|---|---|---|---|
| 記事の文章だけ | 16,091 | 174,212 | 3,469 |
| 文章+コード | 13,804(−14.2%) | 175,503(+0.7%) | 3,354(−3.3%) |
語彙の総数は同じなので、コードに枠を回すと、日本語の効率がわずかに下がります。多言語・多分野の LLM のトークナイザの語彙が10万を超える大きさになっているのは、この取り合いを語彙の大きさで吸収するためと考えられます。
正規化と先頭の「▁」で起きること
この節では、sentencepiece の既定の正規化(nmt_nfkc)で元の文字列に戻らなくなる例と、先頭に付く「▁」がトークン数に与える影響が分かります。
既定の正規化は、全角英数字や全角の記号を半角に変え、連続する空白を1つにまとめます。そのため decode しても元の文字列に戻らないことがあります。評価用のデータで encode→decode が元と一致した行の割合を測りました。
| 設定(BPE 8k) | 日本語の記事 | コード | 英語 | コードのトークン数 |
|---|---|---|---|---|
| 既定(nmt_nfkc) | 71.7% | 49.0% | 0.7% | 16,091 |
| identity+空白をまとめない | 100% | 100% | 100% | 18,488 |
| 既定の正規化+空白をまとめない+空白だけの断片を許す(コードも学習) | 71.7% | 96.9% | 100% | 14,282 |
日本語の行が戻らなかった原因の上位は「(」「)」(1,887行)、「:」(472行)、「?」(217行)で、全角の括弧が半角の「(」「)」に変わっていました。コードと英語が戻らなかったのは、行頭の字下げ(連続する空白)が1つにまとめられたためです。
'①②' → ['▁12'] decode '12'
'ABC123' → ['▁A', 'B', 'C', '123'] decode 'ABC123'
'カタカナ' → ['▁', 'カ', 'タ', 'カ', 'ナ'] decode 'カタカナ'
' return x'(既定) → ['▁ret', 'urn', '▁x'] decode 'return x'
' return x'(identity)→ ['▁', '▁', '▁', '▁', '▁ret', 'urn', '▁x'] decode ' return x'
Python のように字下げに意味があるコードでは、既定の正規化はコードを壊します。identity にすると戻るようになりますが、空白1つずつが1トークンになり、コードのトークン数は15%増えました。コードを扱うなら、空白の連続を断片として覚えさせる(allow_whitespace_only_pieces)うえで、コードを学習データに入れるのが、往復とトークン数の両方で良い結果でした。
もう1つは、文頭に付く「▁」です。sentencepiece は既定で各文の先頭に空白を1つ足す(add_dummy_prefix)ので、日本語の文頭では「▁」が単独の1トークンになりがちです。評価用の9,088行のうち2,010行で、先頭が単独の「▁」でした。add_dummy_prefix=false で学習すると、日本語のトークン数は174,212→170,900(−1.9%)になりました。英語は単語の前の空白が意味を持つので、こちらは既定のままの方がトークンが少なくなります(3,469→3,518)。
LLMの料金とコンテキスト長への響き方(計算例)
この節では、1トークンあたりの文字数の違いが、料金とコンテキストに入る量にどう効くかを、手元の実測値で計算した結果が分かります。
トークン数=文字数÷(1トークンあたりの文字数)です。料金はトークン数に単価を掛け、コンテキスト長に入る文字数はトークン数の上限に1トークンあたりの文字数を掛けます。下の表は、手元のトークナイザの実測値を当てはめたもので、各社のモデルの値ではありません。単価は仮に「100万トークンあたり3ドル」としています。
| 条件(実測の文字/トークン) | 1万字のトークン数 | 100万字の費用(仮の単価) | 128Kトークンに入る文字数 |
|---|---|---|---|
| BPE 1k・IT の記事(1.15) | 8,726 | 2.62ドル | 約14.7万字 |
| BPE 8k・IT の記事(2.06) | 4,850 | 1.45ドル | 約26.4万字 |
| BPE 32k・IT の記事(2.51) | 3,982 | 1.19ドル | 約32.1万字 |
| BPE 8k・法律の文章(1.13) | 8,842 | 2.65ドル | 約14.5万字 |
| BPE 8k・料理の文章(0.94) | 10,604 | 3.18ドル | 約12.1万字 |
同じ語彙8,000でも、得意な分野と苦手な分野で費用は2倍以上、入る文字数は半分以下になります。トークン数の見積もりは「日本語1文字=何トークン」という1つの係数ではなく、実際に送る文章で、使うモデルのトークナイザで数える必要があります。Anthropic の公式ドキュメントも、Claude 4.7 以降のモデルは新しいトークナイザを使い、同じ文章が以前のモデルより約30%多いトークンになるとして、以前のモデルで数えた値を使い回さず数え直すよう書いています。
語彙を大きくすると系列は短くなりますが、別の費用が増えます。入力の埋め込み行列は「語彙数×隠れ次元」なので、隠れ次元4,096なら語彙32,000で約1.31億、語彙128,000で約5.24億のパラメータになります(出力層を別に持てばその2倍)。出力のたびに語彙数の大きさの確率分布を計算するのも同じ比率で重くなります。小さなモデルほど、語彙の大きさが全体に占める割合が大きくなります。
自分で確かめる(Python で1行):自分の文章が1トークンあたり何文字かが分かれば、上の式に入れるだけで見積もれます。sentencepiece のモデルが手元にあれば次の1行で出ます(モデル名と文章を差し替え)。
python3 -c "import sentencepiece as s; p=s.SentencePieceProcessor(model_file='m.model'); t='ここに見積もりたい文章'; n=len(p.encode(t)); print(n,'トークン',round(len(t)/n,2),'文字/トークン')"
各社のトークナイザの違い(公式で確かめた範囲)
この節では、主要な LLM のトークナイザの方式と語彙の大きさについて、公式のリポジトリ・ドキュメントで確かめられた事実が分かります。
| 提供元 | 公式で確かめた内容 | 出典 |
|---|---|---|
| OpenAI(tiktoken) | gpt-4o・gpt-4.1・gpt-5・o1・o3 は o200k_base、gpt-4・gpt-3.5-turbo は cl100k_base。終端トークンの ID は o200k_base が199999、cl100k_base が100257。gpt-oss は o200k_harmony | tiktoken の model.py と openai_public.py |
| Meta(Llama 3) | 語彙128Kトークン。Llama 2 より最大15%少ないトークンになると公表。tiktoken 形式の BPE に、予約を含む特殊トークン256個を足す | Meta 公式ブログ、llama3 リポジトリの tokenizer.py |
| Qwen(Qwen3-8B) | config.json の vocab_size は151936 | Qwen 公式の config.json |
| Anthropic(Claude) | 語彙は公開されていない。数えるには POST /v1/messages/count_tokens(無料・レート制限あり・推定値)。Claude 4.7 以降は新しいトークナイザで、同じ文章が約30%多くなる | Anthropic 公式ドキュメント「Token counting」 |
tiktoken のエンコーディング(cl100k_base・o200k_base)は、手元で最初の使用時に語彙ファイルを取得しにいきます。この環境では取得先への接続が許可されておらず、次のエラーで止まりました。そのため、この記事では OpenAI のエンコーディングでの実測はしていません。
cl100k_base ERR ProxyError HTTPSConnectionPool(host='openaipublic.blob.core.windows.net', port=443): Max retries exceeded with url: /encodings/cl100k_base.tiktoken (Caused by ProxyError('Unable to connect to proxy', OSError('Tunnel connection failed: 403 Forbidden')))
社内のネットワークで同じエラーが出る場合は、語彙ファイルを許可された経路で用意して、環境変数 TIKTOKEN_CACHE_DIR が指す場所に置く運用が考えられます(キャッシュの仕組みは tiktoken のソースの load.py にあります)。
各社のトークナイザは10万〜20万規模の語彙を多言語・コードを含むデータで学習しており、手元のもの(語彙32,000まで・IT の日本語記事だけ)とは条件が違います。ここでの文字/トークンの値は、各社のモデルにそのまま当てはめられません。
よくあるエラー・つまずき(原文つき)
この節では、トークナイザを自作・学習するときに実際に出たエラーと、数え方のつまずきの原因と直し方が分かります。
| 症状・原文 | 原因 | 直し方 |
|---|---|---|
| Vocabulary size is smaller than required_chars. 1000 vs 1542. | 日本語は文字だけで1,283種類+バイト256個+特殊3個が要り、語彙1,000に入らない | vocab_size を上げるか、character_coverage を下げる(0.98で1,000が通った) |
| decode した文が元と違う(全角の括弧が半角になる) | 既定の nmt_nfkc 正規化 | normalization_rule_name='identity'。元の文字列の位置が要るなら encode_as_offset_mapping 系も検討 |
| コードの字下げが消える | 連続する空白を1つにまとめる既定の動き | remove_extra_whitespaces=False と allow_whitespace_only_pieces=True で学習 |
| decode すると「⁇」が出る | byte_fallback=False で語彙外の文字が unk(ID 0)になった | byte_fallback=True で学習し直す。学習済みなら元の文字は戻せない |
| 学習に時間がかかりすぎる(Unigram) | 候補の数(seed)と EM の回数が多い。語彙が小さいほど削る回数が増える | input_sentence_size で学習文を間引く、num_threads を増やす |
| 長い行が学習に入っていない | max_sentence_length(既定4192バイト)を超える行は読まれない。日本語は約1,400文字で超える | 文や段落で改行してから学習する |
| ProxyError … 403 Forbidden(tiktoken) | 語彙ファイルの取得先に接続できない | 許可された経路で取得し TIKTOKEN_CACHE_DIR に置く |
| 自作 BPE で「仕組み」が1つにならない | 事前分割で漢字とひらがなを別の単語にしている | 事前分割の規則を変える。境界を越えて結合しないのは BPE の仕様 |
1つ目のエラーは、語彙1,000で学習しようとしたときに BPE・Unigram の両方で実際に出たものです。エラーの全文は次のとおりでした。
RuntimeError: INTERNAL: src/trainer_interface.cc(600) [(static_cast<int>(required_chars_.size() + meta_pieces_.size())) <= (trainer_spec_.vocab_size())] Vocabulary size is smaller than required_chars. 1000 vs 1542. Increase vocab_size or decrease character_coverage with --character_coverage option.
数え方のつまずきとしては、特殊トークンの数え漏れもあります。チャット形式のモデルでは、役割の区切りや文頭・文末の特殊トークンが入力に足されます。Llama 3 の tokenizer.py にも begin_of_text・start_header_id・eot_id などが定義されています。本文だけを数えると、実際の請求やコンテキストの消費より少なく見積もることになります。
判断表:方式と語彙数の選び方
この節では、自分でトークナイザを作る・選ぶときに、どの方式と語彙数を選ぶかの判断の目安が分かります。
| やりたいこと | 選ぶもの | 根拠(この記事の実測) |
|---|---|---|
| 学習済みの LLM を使う | そのモデルのトークナイザ(変えられない) | 語彙と埋め込み行列が1対1 |
| 日本語中心の小さなモデルを一から作る | BPE か Unigram で語彙8k〜32k、byte_fallback=True | 8kで2.06、32kで2.51文字/トークン。8kから先は倍で1割減 |
| トークン数をできるだけ減らしたい | BPE・語彙を大きく・分野の文章を学習データに入れる | BPE は Unigram より7%少ない。コードを足すとコードが14%減 |
| 分割の揺らぎで頑健にしたい | Unigram+サブワード正則化 | n-best とサンプリングで複数の分け方が出せる |
| コードを扱う | 空白をまとめない設定+空白だけの断片+コードを学習 | 往復96.9%、トークン数は既定より11%少ない |
| 元の文字列に必ず戻したい | identity 正規化・空白をまとめない・byte_fallback=True | 往復100%。代わりにコードのトークンが15%増 |
| 費用・コンテキストを見積もる | 実際の文章を、使うモデルの公式の数え方で数える | 分野が違うだけで同じ字数のトークンが2.2倍 |
学習の前に確かめておくことを並べます。
よくある質問
この節では、トークナイザの仕組みと日本語のトークン数について、よく聞かれる疑問への答えが分かります。
Q. BPE と Unigram の違いを一言で言うと何ですか。
BPE は小さな語彙から結合を足して育て、分割は結合の順番で決まります。Unigram は大きな候補から削って作り、分割は確率の積が最大の経路で決まります。手元では同じ語彙数で BPE の方がトークン数が3〜7%少なくなりました。
Q. SentencePiece と BPE は別のものですか。
SentencePiece はライブラリ(と、空白を ▁ で扱う設計)の名前で、その中で BPE と Unigram の両方を選べます。既定は Unigram です。
Q. 日本語は1文字何トークンですか。
トークナイザと文章で大きく変わります。手元では、IT の記事で学習した語彙8,000の BPE で1トークンあたり2.06文字(1文字約0.49トークン)、学習データに無い料理の文章では0.94文字(1文字約1.06トークン)でした。使うモデルの公式の数え方で、実際の文章を数えるのが確実です。
Q. 語彙数はいくつにすればよいですか。
日本語では文字だけで1,300前後の枠を使うので、数千以上が目安です。手元では語彙8,000から先は、語彙を倍にしてもトークン数は1割前後しか減りませんでした。モデルの大きさ(埋め込み行列の大きさ)とのつり合いで決めます。
Q. バイトフォールバックとは何ですか。
語彙に無い文字を UTF-8 の1バイトずつの断片に分ける仕組みです。どんな文字も表せて元に戻せますが、漢字は3トークン、絵文字は4トークン以上になります。
Q. 未知語(unk)が出ると何が困りますか。
モデルには「何か分からない文字」としか伝わらず、decode しても「⁇」になって元に戻りません。手元では「魑魅魍魎」がまとめて1つの unk になりました。
Q. 形態素解析(MeCab など)とトークナイザは何が違いますか。
形態素解析は辞書と文法に基づいて「意味のある単語」に分けます。サブワードのトークナイザは、頻度や確率に基づいて「語彙に収まる断片」に分けます。形態素解析で事前分割してからサブワードに分ける組み合わせもあります。
Q. 学習済みの LLM のトークナイザに語彙を足せますか。
語彙を足すことはできますが、足した ID の埋め込みは学習されていないので、追加の学習が要ります。語彙と埋め込み行列は1対1なので、トークナイザだけを替えることはできません。
Q. OpenAI や Claude のトークン数はどう数えますか。
OpenAI のモデルは tiktoken(gpt-4o 以降の多くは o200k_base)、Claude は公式のトークン数を数える API(POST /v1/messages/count_tokens)で数えます。数え方の詳しい手順は、関連記事「トークンとは|生成AIの数え方・日本語の文字数目安と節約方法」で扱っています。
Q. トークンの後、LLM の中では何が起きていますか。
ID が埋め込みベクトルに変わり、Transformer が次のトークンの確率を出します。その流れは関連記事「生成AI(LLM)の仕組み8選|ハルシネーション・トークン」で、確率から1つを選ぶときの設定は関連記事「temperatureとは?top_p・max_tokensの意味と設定目安表」で扱っています。
まとめ
この節では、記事全体の要点が分かります。
- トークナイザは正規化→事前分割→サブワード分割→ID 化→特殊トークンの順に動く。語彙と埋め込み行列は1対1なので、学習済みのモデルのトークナイザは替えられない。
- BPE は最頻のペアの結合を繰り返し、Unigram は確率の積が最大の分割を選ぶ。自作の BPE で結合の過程(「ます」が1番目、「トークン」は1,331番目)を、自作の Viterbi で sentencepiece と同じ分割になること(バイト分解の行を除き100%)を確かめた。
- 語彙1,000→32,000で日本語のトークン数は2.19分の1。ただし語彙8,000から先は倍にしても1割前後しか減らず、BPE は Unigram より3〜7%少なかった。
- 学習データに無い分野ではトークンが増える。IT の記事で学習した語彙8,000では、料理の文章が1トークン0.94文字で、同じ字数の IT の記事の2.2倍のトークンになった。語彙外の文字はバイトフォールバックで3バイト=3トークンになる。
- 既定の正規化は全角記号と字下げを変える。往復が要るなら identity、コードなら空白を断片として学習する。費用とコンテキストは、実際の文章を使うモデルの公式の方法で数えて見積もる。
参考・出典
この節では、この記事で根拠にした論文・公式リポジトリ・公式ドキュメントが分かります(確認日 2026年11月4日)。
- google/sentencepiece(公式リポジトリ・README):https://github.com/google/sentencepiece
- sentencepiece 学習オプション一覧 doc/options.md:https://github.com/google/sentencepiece/blob/master/doc/options.md
- Taku Kudo, John Richardson「SentencePiece: A simple and language independent subword tokenizer and detokenizer for Neural Text Processing」EMNLP 2018:https://arxiv.org/abs/1808.06226
- Rico Sennrich ほか「Neural Machine Translation of Rare Words with Subword Units」ACL 2016(BPE):https://arxiv.org/abs/1508.07909
- Taku Kudo「Subword Regularization: Improving Neural Network Translation Models with Multiple Subword Candidates」ACL 2018(Unigram):https://arxiv.org/abs/1804.10959
- Ivan Provilkov ほか「BPE-Dropout: Simple and Effective Subword Regularization」ACL 2020:https://arxiv.org/abs/1910.13267
- openai/tiktoken(model.py・tiktoken_ext/openai_public.py):https://github.com/openai/tiktoken
- meta-llama/llama3(llama/tokenizer.py):https://github.com/meta-llama/llama3
- Meta「Introducing Meta Llama 3」:https://ai.meta.com/blog/meta-llama-3/
- Qwen3-8B config.json(Qwen 公式):https://huggingface.co/Qwen/Qwen3-8B
- Anthropic 公式ドキュメント「Token counting」:https://platform.claude.com/docs/en/build-with-claude/token-counting
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「トークナイザの仕組み|BPE・Unigramを自作し日本語のトークン数を実測」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。