最近、一部のローカルLLM勢のあいだでChatVectorで遊ぶのが流行っている。
ChatVectorとは何か?というとこちらの論文で発表された技術だ。
こちらの解説記事が分かりやすい。
Chat Vectorを使って日本語LLMをチャットモデルに改造する – Qiita
要するに、ChatVectorとは指示チューニングでチャット能力を獲得したモデルのウエイトから、ベースになったモデルのウエイトを差し引いた差分の事である。
そしてChatVectorを別のモデルのウエイトに加算すれば、そのモデルにチャット能力を付与する事ができてしまうらしい。そんな大雑把な理屈が上手くいくとは!
それどころか、英語モデルから抽出したChatVectorを日本語ベースモデルに加算すると、日本語でチャットできるようになるという!マジ!?
これが面白い話だってんで、色々と試して遊ぶ人が続々と現れている。よく使われてるのはMistral-7Bベースの派生モデルだ。というのも、優秀なベースモデルだから沢山の派生モデルが存在するし、7Bというのはローカルで遊ぶには手頃なサイズだからだろう。
続々と出現する日本語ChatVectorモデル
まず、AIXサトシさんが3月20日にSwallow-MX-8x7b-NVE-chatvector-Mixtral-instructというモデルをリリースした。
ベースになったSwallow-MX-8x7b-NVE-v0.1はスペックが高いが指示チューニングされてないのでチャットに難があるらしい。そこで、Mixtral-8x7B-Instruct-v0.1から抽出したChatVectorを加算したモデルらしい。
数日後にv2がリリースされた。これはv1とほぼ同じ構造だが、ChatVectorを0.8倍して加算してる点が違う。こうする事で出力される日本語がより自然になったとの事。
次に3月24日にかもさんがswallow-hermes-st-v1というモデルをリリースした。
このモデルでは、まずHermes-2-Pro-Mistral-7bからChatVectorを抽出し、Mistral-7B-Instruct-v0.2-Neural-Storyから物語Vectorを抽出して、Swallow-MS-7b-v0.1に加算している。
さらにSakanaAIのEvoLLMの手法を参考に、レイヤーごとに最適なVector加算割合を進化戦略で求めてるそうだが、詳しい手法は分からない。
それから、5ちゃん(PINKちゃんねる)の某スレで発表されて話題になったモデルがSdff-Ltbaさんが作成したLightChatAssistant-2x7Bだ。パラ数が小さい割に、かなり流暢な日本語の文章を出力できて賢い感じがする。
このモデルでは、まずMistral-7B-Instruct-v0.2からChatVectorを抽出して、ChatNTQ-ja-7b-v1.0とAntler-7Bにそれぞれ0.8倍で加算する。出来上がった二つのモデルをMergekitでMoEとして合体させたらしい。
ZuntanさんはLightChatAssistant(LCA)をKoboldCppから簡単に遊べるようにしたEasyLightChatAssistantを公開してくれている。
その次に、AratakoさんはLCAと同じノリで、エキスパートを4つに増やしたモデルをリリースした。
Aratako/LightChatAssistant-4x7B · Hugging Face
さらに続いてAratakoさんはLCAのChatVectorの加算割合を最適化したモデルをリリースした。元のLCAでは一律で0.8倍のChatVectorを加算していたが、このモデルではOptunaを使ってTPEというベイズ最適化ベースの手法でレイヤーごとに加算倍率を最適化したらしい(よく分かってない)
Aratako/LightChatAssistant-2x7B-optimized-experimental · Hugging Face
一方その頃、BakuさんもLCAに着目して色々と検証記事を書いている。
Mistral 7Bベースの日本語チャットモデル ChatNTQ-JA-7B を試す – ローカルLLM自由帳 (hatenablog.com)
ChatNTQ 7B と LightChatAssistant 2x7B の日本語性能を測定する – ローカルLLM自由帳 (hatenablog.com)
MergeKitによるMoEマージだけで日本語性能は上がるか? – ローカルLLM自由帳 (hatenablog.com)
【LLM論文を読む】Chat Vector:LLMに新たな言語での指示追従とアラインメントを付与するシンプルなアプローチ – ローカルLLM自由帳 (hatenablog.com)
LightChatAssistant 2x7B を再現する – ローカルLLM自由帳 (hatenablog.com)
Chat Vector の効果 vs. MoEマージ の効果 – ローカルLLM自由帳 (hatenablog.com)
どの記事も非常に重要な知見の塊だが、個人的には一連のChatVectorモデルを評価する手法を確立してくれた意義が大きい。Bakuさんの評価手法では、ElyzaTasks100というベンチマークをGPT-4に評価させるている。
LLMのベンチマークには色々あるが、旧来のベンチマークの5択クイズみたいな問題では、もはやLLMの性能をちゃんと評価できないという話が出てきている。特にChatVectorモデルで我々が求める性能は、どんだけ流暢にイケてる日本語文章を書けるかどうかであって、クイズ王なんて別に欲しくない。
そんななか出てきてるのがjmt-benchやElyzaTasks100などの新しいベンチマークだ。これらのベンチマークではLLMに質問に対して自由回答させる。自由に文章を書かせてこそLLMに求めてる性能が測れるというものである。
それはいいんだが、旧来の5択クイズとかなら回答の正解不正解を機械的に判定、評価させる事ができたのに対して、自由回答の文章がどんだけ優れてるか?なんて事は機械的に判定できないから、人間が評価するしかない。
そんな事言ったって、色んなモデルを作っては評価して…を繰り返さないといけないのに、自分でLLMの答案の採点までしないといけないのはダルすぎる。
だったらもう、評価もLLMにやらせればいいじゃんという発想が出てくる。だから、ElyzaTasks100をGPT-4に評価させるという手法なら、旧来よりちゃんとLLMの性能を評価できるし、自動で評価させる事ができるのも優れている。
まあ、問題があるとすれば評価のたびにGPT-4のAPI料金がかさんでいく点だろう。一度の評価(100問)で1ドルくらいかかるらしい。高いとは言わないが、LLMを評価するだけでチマチマ金取られるのはうっとおしい。
話を戻すが、そんなElyzaTasks100においてLCAは3.31点という高い平均スコアを叩き出している。ベースになったChatNTQ-JAが3.06点だったのでかなり性能向上してると分かる。Command R-35Bの3.42点に匹敵するスコアである。
そんで、Bakuさんは検証記事で得られた知見を元にJapanese-Starling-ChatV-7Bというモデルを作ってリリースした。
饒舌な日本語ローカルLLM【Japanese-Starling-ChatV-7B】を公開しました|Baku (note.com)
このモデルでは、Starling-LM-7B-betaという、Mistralベースの非常に優秀なモデルからChatVectorを抽出して、ChatNTQ-JA-v1.0-7bに1.0倍で加算している。
MoEにすらしていないにも関わらず、ElyzaTasks100において3.42点という、LCAを上回る高得点をたたき出してしまったという。なんとCommand R-35Bと同点だ。
はちさんもChatVectorに着目して色々な実験を行われている。
Chat VectorにならぬCode Vectorは作れるのか|はち (note.com)
Chat VectorならぬMath Vectorは作れるのか|はち (note.com)
Chat VectorとMath Vectorは併用できるのか|はち (note.com)
はちさんがリリースしたモデルがSwallow-MS-7b-v0.1-ChatSkill-LABだ。
ibm/merlinite-7bから抽出したChatVector(はちさんはスキルツリーと呼んでいる)をMistral-7B-v0.1に加算したものらしい。
というような感じで、最近はChatVectorで色々遊ぶのが界隈で流行ってきているという事だ。
今までもLLMのマージやMoE化で遊ぶのは流行っていたが、そこに新しいオモチャとしてChatVectorが加わった感じだ。
ちなみに、どうして界隈ではLLMの継続事前学習や微調整やLoRA学習はあんま流行ってないのか?それは要求する計算資源のハードルが高いからだ。LLMの継続事前学習、微調整、LoRA学習は7Bとかのモデルでさえ相当強力なGPUが必要だったりしがちだ。画像AIのLoRA学習ならちょっとしたGPUさえあれば手元で遊べていたが、そんなに強力なPC持ってる人はそうそういないので、手元で遊べる人が少ないとあんま流行らないという事になる。モデルのマージやMoE化やChatVector抽出は学習が要らないから計算資源も要らない、つまり誰でも手元で遊べるから流行ってきている。
SakanaAIが進化的マージ、EvoLLMの手法を発表したのもモデルマージなら計算資源が要らない点に着目したらしい。
モデルマージやMoE化、ChatVectorは、まあこういう組み合わせたらいいんじゃないか?という予感とかはあるものの、実際に作ってみて評価してみないとどんな性能になるか事前にあんま予測できない。ガチャみたいで面白いといえば面白いかもしれないが。
LCAやJapanese-Starling-ChatV-7Bなどではシンプルな発想でChatVector加算を行ってるが、swallow-hermes-st-v1やAratakoさんの最適化LCAでは何らかの手法で自動的に最適化が行われている。これが突き詰められるとEvoLLMのようになってくのだろう。
出回ってる全てのモデルを全組み合わせパターンでマージ、MoE化、ChatVector合成してみて、片っ端から評価していって、最強モデルを見つけてピックアップする…ゆくゆくはそんな感じで全てが自動化されてくかもしれない。
ところで、昨日またMistral-7BベースのWizardLM-2-7Bというモデルがマイクロソフトからリリースされた。(今は事情で一時的に消されてるが)
それによると、WizardLM-2-7Bはmt-benchでStarling-LM-7B-betaを上回ったらしい!
「え?だったらこのWizardLM-2-7BからChatVector抽出してChatNTQ-JA-v1.0-7bに加算すればJapanese-Starling-ChatV-7Bに勝てるんじゃね?」安易にそう考えた私は自分でもChatVector遊びに手を出してみる事にした。
先に結果を見せておくと、これが完成したJapanese-WizardLM2-ChatV-7Bである。
umiyuki/Japanese-WizardLM2-ChatV-7B-GGUF · Hugging Face
結局、ベンチでJapanese-Starling-ChatV-7Bに全然勝てんかった。残念だったけど、できちまったもんはしょうがねえからモデルは一応上げた。
というわけで、今回の記事ではこのJapanese-WizardLM2-ChatV-7Bを作って評価するまでのところを書いていく。
ChatVectorを抽出して加算する
ChatVectorを云々する具体的な方法は、LCAのリードミーに丁寧に書いてくださってる。
Sdff-Ltba/LightChatAssistant-2x7B · Hugging Face
BakuさんもLCAの再現手順をまとめてくれてるので参考になる。
LightChatAssistant 2x7B を再現する – ローカルLLM自由帳 (hatenablog.com)
とりま、condaとかで仮想環境を作って、pipで色々入れる必要があるかと思うが、すいませんが何をインスコしたか忘れました。まあtransformersとかpytorchは入れなきゃいけない気がする。
とにかく、このpythonコードを実行するだけでChatVector加算したモデルができてしまう。とても簡単だ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
from transformers import AutoModelForCausalLM import torch # ベースモデル="Mistral-7B-v0.1" base_model = AutoModelForCausalLM.from_pretrained( "mistralai/Mistral-7B-v0.1", torch_dtype=torch.bfloat16, device_map="cpu", ) # 差分を取り出すチャットモデル="WizardLM-2-7B" inst_model = AutoModelForCausalLM.from_pretrained( "amazingvince/Not-WizardLM-2-7B", torch_dtype=torch.bfloat16, device_map="cpu", ) # 差分を張り付ける日本語モデル(1)="chatntq-ja-7b-v1.0" cp_model = AutoModelForCausalLM.from_pretrained( "NTQAI/chatntq-ja-7b-v1.0", torch_dtype=torch.bfloat16, device_map="cpu", ) for k, v in cp_model.state_dict().items(): chat_vector = inst_model.state_dict()[k] - base_model.state_dict()[k] new_v = v + ( 1.0 * chat_vector.to(v.device) ) v.copy_(new_v) cp_model.save_pretrained("./japanese-wizardlm2-chatv-7b") |
ただし、最後にちょっと作業が必要になる。
まず、いくつか足りないファイルがあるので、ChatNTQ-ja-7bのリポジトリからspecial_tokens_map.jsonとtokenizer.modelとtokenizer_config.jsonファイルをDLしてきて出力されたモデルのフォルダに入れる。
NTQAI/chatntq-ja-7b-v1.0 at main (huggingface.co)
モデルのフォルダの中のconfig.jsonを開いて編集する。”max_position_embeddings”を32768に、”rope_theta”を1000000.0に、”sliding_window”をnullにそれぞれ変更する。
これでtransformers形式のモデルは完成だが、扱いにくいのでgguf形式に変換する。そのためにはLlama.cppリポジトリのconvert.pyが必要になるので、Llama.cppリポジトリをクローンしてくる。
こんな風にconvert.pyを叩けばモデルがggufに変換される。
1 |
python ./../llama.cpp/convert.py ./japanese-wizardlm2-chatv-7b-2 --outtype f16 --outfile ./japanese-wizardlm2-chatv-7b-2_f16.gguf |
次にggufを量子化するには、Llama.cppをビルドすると生成されるquantize.exeが必要になる。ビルドするのがダルければLlama.cppのリリースページからバイナリをダウンロードしてきてもいい。
で、例えばこんな風に書けばQ6_Kで量子化モデルが生成できる
1 |
./llamacpp/quantize.exe .\japanese-wizardlm2-chatv-7b-f16.gguf ./japanese-wizardlm2-chatv-7b.Q6_K.gguf Q6_K |
指定できる量子化オプションの一覧についてはソース見た方が早い
llama.cpp/examples/quantize/quantize.cpp at master · ggerganov/llama.cpp (github.com)
ついでにimatrix量子化の方法も書いておく。まずimatrixを作成するためのテキストが必要なようだが、LCAの説明ではwiki.train.rawというテキストを使ってる。よく分からんがここからDLできるようだ。↓
Index of /pub/datasets/wikitext-2-raw/ (cosmo.zip)
で、Llama.cppバイナリのimatrix.exeを使ってimatrixを作成する。こんな感じ
1 |
./llamacpp/imatrix.exe -m .\japanese-wizardlm2-chatv-7b-f16.gguf -f ./wiki.train.raw -o ./japanese-wizardlm2-chatv-7b-f16.imatrix --chunks 32 |
作ったimatrixを使ってquantize.exeでこんな感じでimatrix量子化モデルが作成できる
1 |
./llamacpp/quantize.exe -imatrix .\japanese-wizardlm2-chatv-7b-f16.imatrix .\japanese-wizardlm2-chatv-7b-f16.gguf ./japanese-wizardlm2-chatv-7b.IQ4_XS.gguf IQ4_XS |
Command R+に評価させよう
モデルはすぐできたが、問題はこれをどうやって評価するかだ。
まあ、BakuさんがやってたみたいにElyzeTasks100でベンチマークして、GPT-4に採点させるのが一番ベストだろう。
しかし、GPT-4のAPIに金払うのはなんか癪だな。
そこで、GPT-4の代わりにCohereのCommand R+のAPIを使う事にした。何故ならCommand R+なら現在のところタダでAPIが叩けるからだ。なんか正式リリースまでは無料らしい。もし有料化したとしても、そもそもGPT-4のAPIよりも大分安い。それにCommand R+はGPT-4に近い性能があるとか言う話なので、ベンチ評価だってできるだろう。
それで、こちらにElyzatasks100をGPT-4に自動評価させるためのスクリプトが公開されている。
Northern-System-Service/gpt4-autoeval: GPT-4 を用いて、言語モデルの応答を自動評価するスクリプト (github.com)
ELYZA-tasks-100 でLLM14個の日本語性能を横断評価してみた #LLM – Qiita
とりま、git cloneする。
1 |
git clone https://github.com/Northern-System-Service/gpt4-autoeval.git |
まず、どうやって作ったJapanese-WizardLM2-ChatV-7B(JWC)モデルにElyzaTasks100に回答させるかだが、リポジトリのなかのノートブックを適当に開く。
ノートブックを参考にやっていく。まず、ノートブックの最初のセルのとおりに、pipで諸々入れる。
1 |
pip install accelerate datasets jsonlines |
で、次のセルのスクリプトでElyzaTasks100データセットをDLしてきてjsonlに変換して保存する。保存するパスは適宜自分の都合のいいパスに変える。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
import json from pathlib import Path from datasets import load_dataset # Load the dataset ds = load_dataset("elyza/ELYZA-tasks-100") # Function to convert dataset to JSONL format and print def dataset_to_jsonl(dataset, filename): with open(filename, 'w', encoding='utf-8') as file: for entry in dataset: # Construct JSON object json_obj = { "input_text": entry['input'], "output_text": entry['output'], "eval_aspect": entry['eval_aspect'] } # Write JSON object to file in JSONL format json_str = json.dumps(json_obj, ensure_ascii=False) file.write(json_str + '\n') # Convert and write the dataset to a file in JSONL format path_jsonl = Path("<保存したいパス>/dataset.jsonl") if (path_jsonl.parent is not None) and (not path_jsonl.parent.exists()): path_jsonl.parent.mkdir(parents=True, exist_ok=True) dataset_to_jsonl(ds["test"], path_jsonl) |
その次のセルがいよいよ推論させるスクリプトだが、ところで、どうやってJWCモデルにElyzaTasks100の設問を回答させていくか?
今回はLlama.cppバイナリのserver.exeでモデルを動かして、そのサーバーのAPIを叩く形にする事にする。こうすれば、サーバー側でロードするモデルを切り替えれば推論側のスクリプトは使い回せて便利な気がする。
というわけでJWCモデルを読み込ませてサーバーを起動する↓
1 |
.\server.exe -m .\models\japanese-wizardlm2-chatv-7b-2_f16.gguf" -c 3000 -ngl 100 |
引数のcはコンテキスト長で、これを増やすと消費メモリも増える。nglはGPUオフロードレイヤー数を指定する。今回は7Bモデルだし全部オフロードすりゃいいので適当に100にしてる。
で、推論側のスクリプトはこんな感じだ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
import json import jsonlines from llama_cpp import Llama import requests import os # Setup model def format_prompt(input_text): prompt_template = """{input}\n""" return prompt_template.format(input=input_text) def process_dataset(input_file, output_file): with jsonlines.open(input_file) as reader, jsonlines.open(output_file, mode='w', flush=True) as writer: for i,obj in enumerate(reader): prompt = obj['input_text'] url = "http://127.0.0.1:8080/completion" payload = {"prompt": prompt,"n_predict": 1500} 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("設問" + str(i) + "--------------------------------------------------------------------------------") print(generated_text) writer.write({"pred": generated_text}) # Process the dataset input_dataset = 'L:\GitHubProjects\gpt4-autoeval\content\dataset.jsonl' base_dir = 'L:/GitHubProjects/gpt4-autoeval/content' output_dirname = 'Japanese-WizardLM2-ChatV-7B' #これをモデル毎に変える output_dir = os.path.join(base_dir, output_dirname) os.makedirs(output_dir, exist_ok=True) # ディレクトリが存在しない場合は作成 output_predictions = os.path.join(output_dir, 'preds.jsonl') process_dataset(input_dataset, output_predictions) |
server.exeのAPIを叩くように改変している。input_datasetのパスはさっき保存したElyzaTasks100データセットのjsonlのパスを指定する。base_dirも各自の環境に合わせて変える。
これを実行するとLLMが設問に対する答えを1問ずつ生成して、結果はoutput_dirにpreds.jsonlとして保存される。
LLMの回答(preds.jsonl)ができたら次はこれをCommand R+のAPIに採点させる。
自動評価リポジトリのmain.pyが採点のメインコードで、openai_judge.pyがGPT-4のAPIに採点判定させているコードだ。
そこでまず以下のようにcohere_judge.pyを作成する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
import json import cohere co = cohere.Client('APIキー') import requests import time from tenacity import retry, stop_after_attempt, wait_random_exponential with open("./assets/prompt_eval.txt", encoding='utf-8') as f: template_prompt = f.read() @retry(wait=wait_random_exponential(min=30, max=60), stop=stop_after_attempt(10)) def evaluate(pred, input_text, output_text, eval_aspect): """OpenAI API により評価を行う Args: Returns: [dict] 評価結果 {"reason": "<評価理由>", "grade": <int, 1~5の5段階評価>} """ # `pred` が空の場合は、評点を1にする if (pred == ""): return {"reason": "No response", "grade": 1} prompt = template_prompt.format( input_text=input_text, output_text=output_text, eval_aspect=eval_aspect, pred=pred, ) try: response = co.chat( message=prompt, model="command-r-plus", temperature=0.3 ) response = response.text print(response) d = json.loads(response) _validate_schema(d, response) return d except cohere.errors.too_many_requests_error.TooManyRequestsError as e: print(f"TooManyRequestsError occurred: {str(e)}") raise except ValueError as e: print(f"ValueError occurred: {str(e)}") raise except Exception as e: print(f"An unexpected error occurred: {str(e)}") raise def _validate_schema(response: dict, response_string): """response を JSON としてパースし、下記のスキーマに合致することを確かめる {"reason": "<評価理由>", "grade": <int, 1~5の5段階評価>} Raises: ValueError """ if not isinstance(response, dict): raise ValueError("Response is not a JSON object\n" + str(response_string)) required_keys = {"reason", "grade"} if not required_keys.issubset(response.keys()): raise ValueError("Missing required keys\n" + str(response_string)) if not isinstance(response['reason'], str): raise ValueError("'reason' should be a string\n" + str(response_string)) if not isinstance(response['grade'], int) or not (1 <= response['grade'] <= 5): raise ValueError("'grade' should be an integer between 1 and 5\n" + str(response_string)) |
これはGPT-4の代わりにCommand R+のAPIを呼び出して判定させるコードだ。APIキーの部分には各自のキーを入力する。
次に、以下のようにcohere_main.pyを作成する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
import json import os import jsonlines import cohere_judge import cohere # Function to read data from a JSONL file def read_jsonl(file_path): with jsonlines.open(file_path) as reader: return [obj for obj in reader] def call_evaluate_with_error_handling(pred, input_text, output_text, eval_aspect): try: result = cohere_judge.evaluate(pred, input_text, output_text, eval_aspect) return result except cohere.errors.too_many_requests_error.TooManyRequestsError as e: print(f"TooManyRequestsError occurred after all retries: {str(e)}") return {"reason": "API rate limit exceeded\n" + str(e), "grade": 1} except ValueError as e: print(f"ValueError occurred after all retries: {str(e)}") return {"reason": "Invalid API response\n" + str(e), "grade": 1} except Exception as e: print(f"An unexpected error occurred after all retries: {str(e)}") return {"reason": "Unexpected error\n" + str(e), "grade": 1} base_directory = "L:/GitHubProjects/gpt4-autoeval/content/" model_name = "LightChatAssistant-2x7B_q8" #これをモデル毎に変える dataset = read_jsonl("L:\GitHubProjects\gpt4-autoeval\content\dataset.jsonl") preds = read_jsonl(base_directory + model_name + "/preds.jsonl") with jsonlines.open(base_directory + model_name + '/result.jsonl', mode='w') as writer: # Evaluate each sample of the dataset, and write the result to the file for i, (eval_data, pred_data) in enumerate(zip(dataset, preds)): pred = pred_data["pred"] input_text = eval_data["input_text"] output_text = eval_data["output_text"] eval_aspect = eval_data["eval_aspect"] result = call_evaluate_with_error_handling(pred, input_text, output_text, eval_aspect) writer.write(result) print(f"==============================") print(f"Q. {input_text}") print(f"A. {pred}") print(f"Command R+. {result}") print(f"") |
これまた、main.pyをcohere_judgeを使うように改変したコードだ。datasetにはElyzaTasks100データセットのjsonlのパスを、base_directoryにはさっきのbase_dirのパス、model_nameにはさっきのoutput_dirnameを指定する。
それから、assets/prompt_eval.txtも編集した方がいい。これはCommand R+に投げるプロンプトのテンプレートだ。GPT-4ならjsonフォーマットで回答するように強制できる機能があるが、Command R+にはそういうのが無い。まあJsonで回答してって言えば大抵はちゃんとJsonで返してくれるが、たまに余計な文言を付け足してくる時がある。だからこんな感じで「絶対にJsonだけ返答しろ」って念押ししといた方がいい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
あなたは言語モデルの採点者です。 問題, 正解例, 採点基準, 言語モデルが生成した回答が与えられます。 「採点基準」と「正解例」を参考にして、「言語モデルの回答」を評価してください。 そして、回答理由および1,2,3,4,5の5段階評価による採点結果を「評価フォーマット」に示すようなJSON形式で返してください。 かならずJSONのみを返答してください。返答のJSONは一個だけです。それ以外の余計な文章は絶対に付け加えないでください。 # 問題 {input_text} # 正解例 {output_text} # 採点基準 基本的な採点基準 - 1点: 誤っている、 指示に従えていない - 2点: 誤っているが、方向性は合っている - 3点: 部分的に誤っている、 部分的に合っている - 4点: 合っている - 5点: 役に立つ 基本的な減点項目 - 不自然な日本語: -1点 - 部分的に事実と異なる内容を述べている: -1点 - 「倫理的に答えられません」のように過度に安全性を気にしてしまっている: 2点にする 問題固有の採点基準 {eval_aspect} # 言語モデルの回答 {pred} # 評価フォーマット {{"reason": "(採点基準に照らした評価内容)", "grade": (採点結果、1~5の5段階評価)}} # かならず評価フォーマットのJSON形式のみを返答してください。返答のJSONは一個だけです。それ以外の余計な文章は絶対に付け加えないでください。 |
ここまで言ってもダメな時はダメだから、アカン時はまあどうにかするしかない。一応、JSONのパースに失敗した時はリトライする処理が入ってるが、10回投げても10回失敗するケースも稀にある。
さて、これを実行する事で、base_directoryの中にはさっき生成したpreds.jsonlの他に、Command R+が採点したresult.jsonlが生成されてるハズだ。
採点してくれたのはいいけど、結果をスプレッドシートとかにまとめてくれないと見づらいわけだが、自動評価リポジトリには親切な事にそれをやってくれるスクリプトも同梱されてる。tools/copy_jsonl_to_google_spreadsheet.jsというファイルだ。
まず、Googleドライブのルートにllm_research/migrationというディレクトリを切る。その中に、評価結果のpreds.jsonlとresult.jsonlが入ってるフォルダをコピーする。
次にGoogleスプレッドシートを開いて、メニューの拡張機能→Apps Scriptを選択して、エディタにcopy_jsonl_to_google_spreadsheet.jsの中味を貼り付ける。
スクリプトだと開始列がCZになっており、やたら遠いところから始まってしまうので、Eとかに変えた方がいい。で、スクリプトを実行して成功すればスプレッドシートに回答、評価が記入される。
記入されるのは回答だけで、設問や正解例は記入されないので、こちらのスプレッドシートなどからコピペしてくるといいだろう。↓
https://docs.google.com/spreadsheets/d/1nOWtneRdrkxwQbAN0rWmXqiJXR9IXK9lVkyDjQTqNGc/edit?usp=sharing
スプレッドシートに評価が記入されれば、平均点なども簡単に算出できる。
今回私が評価した結果のスプレッドシートはこちら↓
https://docs.google.com/spreadsheets/d/1hdVvlDNS9lDF7XBlJStb_IDss9iPSzgDP4QmNKNq37Q/edit?usp=sharing
ちなみに無料のCommand R+のAPIには1分間に20回までしか呼べないレートリミットがある。まあそこはリトライ処理を入れてるので大丈夫だが、それ以外に1日に1000回くらいまでしか呼べない制限もあるようだ。 [追記24/04/18] 1日に1000回までというのは勘違いで、実際には1ヶ月に1000回まででした。(https://docs.cohere.com/docs/going-live)
それ以降は永遠にレートリミットエラーが出続ける。ElyzaTasks100は100問あるので1日に10回くらいしか評価できない。
おわり
てなわけで、今回はChatVectorを使った遊びが流行ってる件についての解説と、自分でもChatVectorで遊んでJapanese-WizardLM2-ChatV-7Bってモデルを作って公開してみた話について書いた。
結果的に作ったモデルが大した性能では無かったのは残念だが、とは言えCommand R+にElyzaTasks100の回答を評価させる事で、タダでLLMをいい感じに評価できる方法が確立できたのは意義がある。これからは何か新しいLLMがリリースされるたびにこの方法でシュッと評価すればそのモデルの性能がどんなもんか定量的にシュッと把握できるわけだ。
ただし、ElyzaTasks100はかなり優秀なベンチマークとは言え、これだといい子ちゃんとしての性能しか測れない。私の欲してるユースケースに完全に一致してるわけではない。いくら頭がいいLLMでもエロ知識が皆無とか、エッチな話題は全拒否してくるモデルだとつまらない。
だから例えばElyzaTasks100と同じフォーマットで自分なりのベンチマークを自ら開発するような必要もあるんじゃないのか。それで評価してこそ本当に自分が求めてる性能のLLMが見つけられるというものである。
また、ベンチマークの多様性で言えばjmt-benchなんかも同様に自動評価できるようになればいいと思う。
LLM創造性ベンチマークというものもあるらしい。こういうのも自動評価させられないだろうか?
Command R+のAPIが現在無料で使えるのは嬉しいが、有料化したらどうすんねん?という話がある。ちょっと考えてるのが、ローカルでCommand R+を動かして評価すればいいかもしれない。まあ私のPCではCommand R+は推論がメチャクチャ遅くなる。だが、例えば1文字だけ出力するだけでいいならいくら推論が遅くてもすぐ終わるわけだ。ElyzaTasks100は講評の文章をすっ飛ばして得点だけ出力させれば1文字で済む。また、ローカルでLlama.cppを動かす場合、APIと違って指定したフォーマットでの出力をグラマーで強制できるというメリットもある。
まあめげずにこれからもChatVectorで遊んで色んなモデルを作ってみるのもいいと思う。AratakoさんはOptunaをつかって自動で最適化を行っていたとの事だが、その辺についても勉強してみるべきかもしれない。
まったく人力の作業無しでモデルのマージ、ベンチマーク、評価、最適化までを完全自動化できるようになればまあ理想だろう。