nikkie-ftnextの日記

イベントレポートや読書メモを発信

Q: ChatGPT(gpt-3.5-turbo)の最大トークン数は4096です。なので、プロンプトの長さが30000文字を超えるときに回答を得ることはできない。◯か☓か

答えは☓(バツ)

最大トークン数(max tokens)は40961ですが、プロンプトを工夫すれば30000字を超える長さであっても回答を得られます

なお、この知見は「へぇ」レベルで、LLMを使っていく上では役に立たないと思います(ここで引き返してもええんやで)。

目次

先行研究:区切り線は2の冪乗で1トーク

このエントリのきっかけになったのは、はてなブックマークのテクノロジータブで見かけた以下の記事。

結論として、ChatGPT API での区切り線は、-または=を4以上の2の冪乗(4, 8, 16, 32)文字数分だけ書くのがおすすめです。

これらは1トークンとしてカウントされるため効果的です。

トークン数には上限がありますから、1トークンとしてカウントされたら節約できて便利ですよね。

2の12乗(4096文字)の区切り線でもいけるってこと?

ちょっと悪い知恵が働いて、「2の冪乗なら1024、2048、4096文字でもいけるってこと?」と思いつきました😈
2の冪乗の区切り線が1トークンならば、4096文字の区切り線も1トークンなので何も問題はないはずです。
というわけで送ってみましょう。

動作環境

  • Python 3.10.9
  • openai 0.27.6
  • tiktoken 0.4.0

4096文字の区切り線を含んだプロンプトを送る

% python delimiter_length.py
len(prompt)=4149
{
  "completion_tokens": 6,
  "prompt_tokens": 84,
  "total_tokens": 90
}
私は学生です。

プロンプトの文字数は4149です(4096+53)。
ですが、プロンプトのトークン数は84!
この時点で対応は見切れていませんが、4096文字の区切り線は4096トークンよりも少ないトークンにはなっていますよね。

2の12乗(4096)文字の区切り線をtiktokenで見てみる

tiktokenで見てみましょう2
覗いて分かったこととして、2の12乗(4096)文字の区切り線は1トークンではありませんでした。

>>> import tiktoken
>>> encoding = tiktoken.encoding_for_model("gpt-3.5-turbo")
>>> len(f"{'='*2**12}")
4096
>>> len(encoding.encode(f"{'='*2**12}"))  # 1トークンではなく、64トークン!
64
>>> encoding.encode(f"{'='*2**12}")
[8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315, 8315]
>>> encoding.decode([8315])
'================================================================'
>>> len(encoding.decode([8315]))
64

64文字の=で1トークとしてトークン化されるようです。

=の代わりに-を使った場合はこちら:

>>> import tiktoken
>>> encoding = tiktoken.encoding_for_model("gpt-3.5-turbo")
>>> len(encoding.encode(f"{'-'*2**12}"))
64
>>> encoding.encode(f"{'-'*2**12}")
[3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597, 3597]
>>> encoding.decode([3597])
'----------------------------------------------------------------'
>>> len(encoding.decode([3597]))
64

こちらも64文字で1トークンですね。

2の冪乗の文字数の区切り線は64文字で1トークンと効率よくトークン化されるので、30000文字超えのプロンプトを送ることもできる

2の15乗は32768です(3万字超え!)

prompt = f"""\
Translate this text into Japanese.

-{'='*2**12}
+{'='*2**15}

I am a student."""
% python delimiter_length.py
len(prompt)=32821
{
  "completion_tokens": 6,
  "prompt_tokens": 532,
  "total_tokens": 538
}
私は学生です。

終わりに

-または=が2の冪乗の数の区切り線は1トークンと知り、悪知恵を働かせてみました。
分かったことは以下です。

  • -または=の個数が2の冪乗かつ64文字までは、1トーク
  • 128以上の2の冪乗では、64文字までの1トークンに分割される
    • 例:-または=が128文字なら、64文字で分かれて2トーク
    • なので、(それでも効率は悪いのでオススメしないが)30000文字を超える長さの区切り線を使うこともできる

2の冪乗の数列は無限に続きます。
それに対して、LLMsの語彙は有限と認識しています。
「2の冪乗の文字数で1トークン」だとすると、有限の語彙と矛盾しますよね。
「予め用意された語彙に対応付けているだけではなく、2の冪乗なら1トークンとする処理があるってこと?」と気になりました。
今回手を動かして分かった「最大は64文字で1トークン」で有限の語彙との折り合いは付くように思います。

P.S. max tokensについて

ドキュメントの「Managing tokens」より

max tokens 4096って、入力する(プロンプトやそれまでのChatコンテキストの)トークン数と、出力するテキストのトークン数の合計なんですね!

For example, a gpt-3.5-turbo conversation that is 4090 tokens long will have its reply cut off after just 6 tokens.

例えば、gpt-3.5-turboに4090トークン入力すると、max tokensが4096なので、出力は6トークンだけで切られるそうです。
入力するトークンが長くなると不完全な返答を受け取りやすくなる3と説明されています。