LLMのトークンIDは、意味の近さを表す番号ではない
結論
LLMの入力を追うときは、トークンIDと、モデルが計算に使うベクトルを分けて見ます。トークンIDの大小や差を意味の近さとして読むと、語彙表を引くための番号と、意味や文脈を表す数値を混同してしまいます。
このスレッドでは、LLMの数値処理を「IDへの変換」「各層での更新」「生成時の再利用」「次のIDの選択」の順に追います。モデルの出力ログや生成設定を読むときも、いま見ている数値がどの段階のものかを切り分けられます。
LLMに文章を渡すと、まずモデルに対応したトークナイザーが、文字列をトークンの列へ変換します。トークンは必ずしも単語一つではなく、単語の一部や記号、バイト列、特殊トークンになることもあります。
各トークンには、モデルの語彙表に対応する整数のIDが割り当てられます。このIDは、語彙表の項目を指定するための番号です。たとえばID 120と121が隣り合っていても、二つのトークンの意味が近いとは限りません。
- 文字列
- トークン列
- ID列
- トークンごとの埋め込み
モデルは各IDを使って埋め込み層を参照し、各トークンの最初のベクトルを得ます。一方、意味検索などで使う文章埋め込みは、文章や段落などのテキスト全体を一つのベクトルとして比較するための表現です。どちらも「埋め込み」と呼ばれますが、表している単位と役割が違います。
参考:Tokenizer(Hugging Face Transformers)、Semantic Textual Similarity(Sentence Transformers)
IDから得たトークンのベクトルは、まだ周囲のトークンの情報を反映していません。次は、Transformerの層がこの表現をどう更新するかを見ます。
トークンIDから取り出した最初のベクトルは、Transformerのブロックを通るたびに更新されます。
中心になる処理の一つがSelf-Attentionです。各位置の表現に、その位置自身を含む、その時点で参照できる位置から情報を集めます。ただし、TransformerはAttentionだけでできているわけではありません。
- Self-Attention:位置どうしの関係を使って情報を集める
- Feed Forward Network:各位置の表現を個別に変換する
- 残差接続と正規化:入力と計算結果を組み合わせ、層を重ねて処理しやすくする
これらを組み合わせ、層を重ねながら各位置の表現を更新します。正規化を置く順序や細かなブロック構成はモデルによって異なるため、特定の図をすべてのLLMへそのまま当てはめることはできません。
ではSelf-Attentionは、どの位置から、どれだけ情報を集めるかをどう決めるのでしょうか。
Self-Attentionは、トークンIDの数値を直接比較していません。各位置の現在の表現から、Query(Q)、Key(K)、Value(V)を作って計算します。
QとKの内積をスケーリングした値が、位置どうしの関連度を求める基になります。未来の位置など参照できない場所をマスクしたうえで、softmaxという処理でスコアを重みへ変換し、その重み付きでVを混ぜます。
- QとK
- 参照先ごとのスコア
- マスクとsoftmax
- Vの重み付き和
QとKは「どの位置をどれだけ参照するか」を決める側、Vは「参照先から何を取り込むか」を担う側です。ここで比べているのはIDそのものではなく、層の途中にある表現から作ったQとKです。
文章を一トークンずつ生成すると、この計算を次のトークンごとに繰り返します。そのとき、過去の位置で計算したKとVはどう扱われるのでしょうか。
KVキャッシュを使う自己回帰型Transformerの推論では、入力プロンプトを含む、すでに処理した位置のKとVを保存します。次のトークンを生成するときは、それらを再利用します。
キャッシュがなければ、新しいトークンを一つ加えるたびに、すでに処理した位置のKとVまで繰り返し計算することになります。KVキャッシュを使う場合は、新しい位置に必要なQ・K・Vを計算し、保存済みのK・Vと合わせてAttentionを求めます。
- 処理済み:保存済みのK・Vを再利用
- 新しい位置:新しいQ・K・Vを計算
KVキャッシュが保存するのは文章そのものではなく、すでに処理した各位置について、各層でAttentionに使うKとVです。その分だけメモリを使いますが、処理済みのK・Vを毎回計算し直す処理を減らせます。実際のキャッシュ方式や保持範囲は、モデルのAttention方式によって異なります。
参考:Caching(Hugging Face Transformers)
こうして過去の情報を参照したモデルは、次に来る一トークンの候補ごとにスコアを出します。では、そのスコアから次のIDをどう選ぶのでしょうか。
モデルが出すのは、語彙に含まれる各トークン候補のlogitという変換前のスコアです。確率に応じて候補をランダムに選ぶサンプリングでは、このスコアを確率へ変換する前にtemperatureで調整します。
これにより、確率が一部の候補へ集中する度合いが変わります。
- temperatureを低くする:高いスコアの候補へ確率が集まりやすい
- temperatureを高くする:確率が複数の候補へ広がりやすい
temperatureは、候補を選ぶ方式そのものではありません。候補の選び方や絞り方には、次のような違いがあります。
- greedy decoding:最もスコアの高い候補を選ぶ
- top-k:スコア上位のk個へ候補を絞る
- top-p:累積確率が指定値へ達するまでの候補へ絞る
サンプリングを有効にした場合は、調整や絞り込みを行ったあとの分布から、確率に応じて次のトークンを選びます。
temperatureを下げても、モデルの知識が増えたり、回答が必ず正確になったりするわけではありません。変わるのは、与えられたスコアから次の候補を選ぶときの分布です。
参考:Generation(Hugging Face Transformers)
LLMの生成では、まず文字列をID列へ変え、トークンごとの表現を層で更新します。必要に応じて過去のK・Vを再利用し、そのあとで次のIDを選びます。
モデルの説明や実行ログを読むときも、この順に分けると「トークン」「埋め込み」「Attention」「生成設定」を同じ数値として扱わずに済みます。