Llama.cppがCommand R+をサポートしたのを期に、Llama.cppを使ってローカルでCommand RやCommand R+を動かして評価してみようとかやってみている。

しかし、何故だか知らんがAPIに比べてローカルのCommand Rは回答がシッチャカメッチャカになってしまうという問題に直面した。

例えば、Llama.cppのサーバーでCommand Rのiq4_xs量子化モデルを起動した状態で、以下のようなコードを実行してみる。

import json
import requests

prompt = "魔法少女まどか☆マギカで一番可愛いキャラは?"

url = "<http://127.0.0.1:8080/completion>"
payload = {"prompt": prompt,"n_predict": 1024}
json_data = json.dumps(payload)
r = requests.post(url,  
                    data=json_data,
                    headers={"Content-Type": "application/json"}
                    )

d = json.loads(r.content)
generated_text = d["content"]
print("answer:\\n")
print(generated_text)

要するに、プロンプトで「魔法少女まどか☆マギカで一番可愛いキャラは?」と訊いて答えさせてる。すると、以下のような回答が返ってくる。

<aside> 💡

answer:

「ほむほむ」「さやかのん」など人気キャラをピックアップ(ねとらぼ) - Yahoo!ニュース ●鹿目まどか(鹿目円) まどか☆マギカの主人公であり、タイトルにもなっているのが鹿目まどか。まどかは真面目でお人よし、そして優しい性格の少女。そんなまどかが魔法少女になっていく物語がま どか☆マギカです。まどかの願いは、みんなを幸せにすること。最終的な願いも、まどか自身よりも友達のために使われる、とても純粋な思いを抱いた魔法少女です。 ●暁美ほむら(ほむほむ) まどかの同級生で、まどかを魔法少女にスカウトしたのが暁美ほむらです。クールでミステリアスな雰囲気を持つ少女ですが、実はまどかを助けるために何度も時を巻き戻してい るという、特別な魔法少女。そのために、まどかを助けるために、まどかが不幸になる未来を知ってしまっています。そんなほむらの願いは「まどかの笑顔」。まどかを幸せにし たいという思いが強く、何度も過去に戻ってまどかを助けてきました。 ●佐倉杏子 杏子は、まどかと同じ中学校に通う同級生。まどかに魔法少女になるよう説得したひとりです。性格はガサツで、ちょっと強引なところもありますが、面倒見がよく、まどかと一 緒にいる時間も長いので、まどかとの仲も良いです。杏子の願いは、魔女狩りをすることでした。魔法少女として契約した理由は、自分が魔女に襲われたためでした。そのために 魔法少女を毛嫌いしていましたが、まどかに説得されてともに戦うことになります。 ●美樹さやか まどかやほむらの同級生で、杏子と行動をともにする少女。杏子の願いもさやかの願いも、大切な人を救うものでした。さやかの願いは「家族の幸せ」。さやかは、まどかのこと を妹のように思っており、家族のようにかわいがっています。さやかは病弱な妹・さなを助けようとして魔法少女となってしまいますが、まどかとの出会いがきっかけで魔法少女 として戦う決意をします。 ●巴マミ 魔法少女としてまどかとほむらに先駆けて戦う少女。まどかたちの学級委員で、面倒見の良いお姉さんのような存在です。まどか☆マギカでは最初から登場し、魔法少女たちの先 輩的存在としてまどかたちを引き連れて戦います。マミさんの願いは「自分の理想の家」。優しい両親や妹がおり、そんな家庭に囲まれたいという思いからでした。しかし魔女に 襲われ、死んでしまった過去を持っています。 ねとらぼ調査隊 ねとらぼ調査隊 ねとらぼ調査隊 ねとらぼ/ねとらぼ調査隊 ねとらぼ本調査隊 ねとらぼ本調査隊 ... ねとらぼ本調査隊 ねとらぼ調査隊 ねとらぼ ねとらぼ本調査隊 ... ねとらぼ調査隊 ... ねとらぼ調査隊 ... ねとらぼ調査隊ねとらぼ調査隊ねとらぼ調査隊ねとらぼ調査隊 ねとらぼ調査隊 ねとらぼ ... ねとらぼ調査隊 ねとらぼ ねとらぼ調査隊 ... ねとらぼ調査隊ねとらぼ調査隊 ねとらぼ調査隊 ねとらぼ調査隊 ねとらぼねとらぼ調査隊 ねとらぼ調査隊 ... ねとらぼ調査隊ねとらぼ調査隊 ... ねとらぼ調査隊 ねとらぼ調査隊 ねとらぼ調査隊 ... ねとらぼ調査隊ねとらぼ調査隊 ねとらぼ調査隊 ねとらぼねとらぼ調査隊 ... ねとらぼ調査隊 ねとらぼ調査隊 ... ねとらぼ調査隊 ねとらぼねとらぼ調査隊 ... ねとらぼ調査隊 ねとらぼ調査隊 ねとらぼねとらぼ調査隊 ... ねとらぼ調査隊 ねとらぼ調査隊 ... ねとらぼ調査隊

</aside>

なんか壊れてんだよなあ…途中まではいいっちゃいいんだけど、後半はねとらぼ連呼するだけの生き物になってしまってる。

なんでだろう?と悩んでいたが、実はCommand Rはプロンプトにチャットテンプレートを使用しないと正しく回答が返ってこないらしい。詳しい人からすると常識なのかもしれないが、てっきり何も要らんと思ってた。

具体的には以下のようなプロンプトを投げる必要があるらしい。 <|START_OF_TURN_TOKEN|><|USER_TOKEN|>Who are you?<|END_OF_TURN_TOKEN|><|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|>

へえ…でもチャットを続けたい時はどんなプロンプトになるんだ? transformersのtokenizerの機能を使えばその辺いい感じにしてくれるようだから、それを使った方が早いと思う。

つまり、こういうコードになる。

import json
import requests
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("CohereForAI/c4ai-command-r-v01")

chat = [
  {"role": "user", "content": "魔法少女まどか☆マギカで一番可愛いキャラは?"},
]

prompt = tokenizer.apply_chat_template(chat, tokenize=False, add_generation_prompt=True)
print("prompt:\\n" + prompt)

url = "<http://127.0.0.1:8080/completion>"
payload = {"prompt": prompt,"n_predict": 1024}
json_data = json.dumps(payload)
r = requests.post(url,  
                    data=json_data,
                    headers={"Content-Type": "application/json"}
                    )

d = json.loads(r.content)
generated_text = d["content"]
print("answer:\\n")
print(generated_text)

チャットの配列を作ってtokenizer.apply_chat_templateに投げれば、よしなに正しくプロンプトを生成してくれる。

上の例だとこういうプロンプトになる↓

<BOS_TOKEN><|START_OF_TURN_TOKEN|><|USER_TOKEN|>魔法少女まどか☆マギカで一番可愛いキャラは?<|END_OF_TURN_TOKEN|><|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|>