Cohere の Command R/R+ において 128k input tokens は短いのか長いのか

## Hello Cohere Command R/R+ at Amazon Bedrock
なれない英語でタイトル書きました。

さておき、Amazon Bedrock で Cohere の Command R と Command R+ が来ましたね。

https://aws.amazon.com/about-aws/whats-new/2024/04/cohere-command-r-r-plus-amazon-bedrock/

https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere-command-r-plus.html

RAG やらマルチステップのことが得意らしいです。
RAG を使うには長文コンテキスト(大きい input tokens)を扱えるほうがよいです。Command R/R+ では 128k tokens が使用できます。

128k は一般的に大きいほうですが、Claude が 200k 使えることを考えるとすごく大きいというわけではありません。

一方で Claude は日本語に対してトークンの消費量が大きかったりします（1 文字につき 3 tokens 使うことなどもざら)。

Cohere はトークナイザーが日本語にそこそこ最適化されているそうで、トークン数を節約できるとのことです。

https://twitter.com/Ishihara_Naok1/status/1785102407556595896

というわけで試してみました。

## じっけん！

以下のようなコードを用意しました。

cohere/r.py
```python
import json
import boto3

br = boto3.client(service_name='bedrock-runtime')

AVAILABLE_MODEL_IDS = [
    'cohere.command-r-v1:0', # R
    'cohere.command-r-plus-v1:0', # R+
]

modelId = AVAILABLE_MODEL_IDS[0] # R

with open('cohere/momotaro.txt','rt') as f:
    momotaro = f.read()

chat_history = [
    {"role": "USER", "message": "あなたは文章を理解と回答が得意な AI です。"},
    {"role": "CHATBOT", "message": "そのとおりです。"},
    {"role": "USER", "message": "文章を渡すので理解してください。"},
    {"role": "CHATBOT", "message": "わかりました。"},
    {"role": "USER", "message": momotaro},
    {"role": "CHATBOT", "message": "理解しました。"},
    {"role": "USER", "message": "渡した文章について質問するので、渡した文章から端的に答えてください。"},
    {"role": "CHATBOT", "message": "わかりました。"},
]

body = {
    "message": '鬼を退治したのは誰ですか？',
    "chat_history": chat_history,
    'temperature': 0,
}

response = br.invoke_model(body=json.dumps(body), modelId=modelId)

for event in response.get("body"):
    bytes = json.loads(event["chunk"]["bytes"])
    chat_history = bytes['chat_history']
    for chat in chat_history:
        print(f'{chat["role"]}「{chat["message"]}」')
print(f'finish reason : {bytes["finish_reason"]}')
print(f'input tokens : {bytes["amazon-bedrock-invocationMetrics"]["inputTokenCount"]}')
print(f'output tokens : {bytes["amazon-bedrock-invocationMetrics"]["outputTokenCount"]}')
```

claude/messages_t2t.py
```python
# docs: https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages.html

import boto3
import json

br = boto3.client(service_name='bedrock-runtime')

ANTHROPIC_VERSION = 'bedrock-2023-05-31'

AVAILABLE_MODEL_IDS = [
    'anthropic.claude-instant-v1', # v1
    'anthropic.claude-v2:1','anthropic.claude-v2', # v2
    'anthropic.claude-3-haiku-20240307-v1:0','anthropic.claude-3-sonnet-20240229-v1:0', # v3
]
model_id = AVAILABLE_MODEL_IDS[-2] # claude-3-haiku

with open('cohere/momotaro.txt','rt') as f:
    momotaro = f.read()

system_prompt = 'あなたは文章を理解と回答が得意な AI です。'

messages = [
    {"role": "user", "content": "あなたは文章を理解と回答が得意な AI です。"},
    {"role": "assistant", "content": "そのとおりです。"},
    {"role": "user", "content": "文章を渡すので理解してください。"},
    {"role": "assistant", "content": "わかりました。"},
    {"role": "user", "content": momotaro},
    {"role": "assistant", "content": "理解しました。"},
    {"role": "user", "content": "渡した文章について質問するので、渡した文章から端的に答えてください。"},
    {"role": "assistant", "content": "わかりました。"},
    {"role": "user", "content": "鬼を退治したのは誰ですか？"},
]



body=json.dumps({
    'anthropic_version': ANTHROPIC_VERSION,
    'max_tokens': 4096,
    'system': system_prompt,
    'messages': messages,
    'temperature': 0,
    'top_p': 0,
    'top_k': 0,
    'stop_sequences': ['</output>']
})

response = br.invoke_model_with_response_stream(body=body, modelId=model_id)
for event in response.get("body"):
    chunk = json.loads(event["chunk"]["bytes"])
    if chunk['type'] == 'message_start':
        input_tokens = chunk['message']['usage']['input_tokens']
    if chunk['type'] == 'message_delta':
        print(f"\nStop reason: {chunk['delta']['stop_reason']}")
        print(f"Stop sequence: {chunk['delta']['stop_sequence']}")
        print(f"Input tokens: {input_tokens}")
        print(f"Output tokens: {chunk['usage']['output_tokens']}")
    if chunk['type'] == 'content_block_delta':
        if chunk['delta']['type'] == 'text_delta':
            print(chunk['delta']['text'], end="")
```

そして momotaro 変数にあらかじめルビを除去した[芥川龍之介版桃太郎](https://www.aozora.gr.jp/cards/000879/files/100_15253.html)を格納しました。

## 結果

Cohere Command R
```shell
python cohere/r.py
桃太郎です。
finish reason : COMPLETE
input tokens : 3239
output tokens : 4
```

Claude 3 Haiku
```shell
python claude/messages_t2t.py
桃太郎が犬、猿、雉の三匹の家来とともに鬼を退治しました。
Stop reason: end_turn
Stop sequence: None
Input tokens: 5007
Output tokens: 37
```

3239 vs 5007 と 1.5 倍以上の差が出ました。正誤はどちらも合ってますが、論文で共著だったはずが謝辞に回されてしまった Command R より Haiku のほうが好きです。

続いて走れメロスで竹馬の友も聞いてみます。
momotaro 変数の中身を走れメロスに変え、質問を「メロスの竹馬の友は誰ですか？」に変えます。

Cohere Command R
```shell
python cohere/r.py           
メロスです。
finish reason : COMPLETE
input tokens : 7073
output tokens : 4
```

Claude 3 Haiku

```shell
python claude/messages_t2t.py
セリヌンティウスです。
Stop reason: end_turn
Stop sequence: None
Input tokens: 10577
Output tokens: 13
```

おい、メロスの竹馬の友がメロスのわけがなかろう…。
さておき、こちらも同様 1.5倍程度の差が出ています。

答えが違うのは悲しいので Command R+ で名誉挽回をしてもらいましょう。

Cohere Command R+

```shell
python cohere/r.py           
セリヌンティウスです。
finish reason : COMPLETE
input tokens : 7076
output tokens : 7
```


よかった、今度は正解しました。
しかしトークン数が R と R+ で微妙に違うのでトークナイザーは別のようですね。

さて、「セリヌンティウスです。」が 7 tokens とのことですが、どこで分割されているのか気になりました。ちょっと試してみましょう。

メッセージにセリヌンティウスだけ入れて実行してみました。

```shell
python cohere/r.py           
セリヌンティウスは、古代ローマの哲学者、作家、および風刺家であるルキウス・アンナエウス・セネカの奴隷および解放奴隷であった人物です。彼はセネカの多くの書簡に登場し、セネカの哲学的思想に大きな影響を与えたと考えられています。

セネカの書簡によれば、セリヌンティウスはセネカの奴隷として働き、セネカの教育と哲学的議論に貢献しました。彼はセネカの親友となり、セネカは彼に深い敬意と愛情を抱いていました。セネカはセリヌンティウスの知性、洞察力、およびユーモアセンスを称賛し、彼との会話や議論から多くのことを学んだと述べています。

セネカはストア派の哲学者として知られており、彼の哲学はセリヌンティウスとの交流によって形作られました。セリヌンティウスはストア派の教えを実践し、簡素な生活、自制心、および精神的な強さを重視しました。彼はセネカにストア派の哲学的概念を教えたと考えられており、セネカの倫理観や人生観に大きな影響を与えた可能性があります。

セネカの書簡では、セリヌンティウスの知恵、謙虚さ、およびストア派哲学への献身がしばしば称賛されています。セネカはセリヌンティウスを「私の哲学者」や「私の教師」と呼び、彼との会話や議論が自分の人生を豊かにしたと述べています。

セリヌンティウスについてはあまり多くのことは知られていませんが、セネカの書簡における彼の存在は、古代ローマ社会における奴隷と解放奴隷の複雑な役割を垣間見せてくれます。彼はセネカの人生と哲学に永続的な影響を与え、セネカの著作を通じて後世の読者にも影響を与え続けています。
finish reason : COMPLETE
input tokens : 5
output tokens : 417
```

盛大にハルシネーションしている気がしますが、セリヌンティウスが 5 tokens でした。おそらく「です」と「。」で 1 token ずつでしょう。Claude だと 1 文字 1 token だと御の字ところを、複数の文字で 1 token で処理してくれるのは僥倖ですね。

## おわり
雑検証でしたがおわりです。