Llama-3のファインチューニング(QLoRA)を試す

1年くらい前と比べるとローカルで動かせるような小さいモデルの性能も上がってきたような気がします。
少し前にMetaからLLama-3がリリースされたので、何番煎じかわかりませんがQLoRAによるファインチューニングを試してみました。
※ 機械学習やAIに関する数学的な理論などについてはズブの素人です。



Llama-3

今回使用するモデルはLlama-3-8B-Instructです。

Llama-3は「日本語で回答してください」等の指示をすれば、それなりに違
和感のない日本語で回答してくれます。
試しに、過去の記事の一部を与えて要約させてみます。

# システムプロンプト
system = "この記事を要約してください。"


prompt = """```
ChatVRMのバックエンドとしてLangChain(とFastAPI)を使う

目次
ChatVRM
やりたいこと
API(LangChain×FastAPI)の実装
フロント(ChatVRM)側の実装
ChatVRM
PixivさんがChatVRMという素晴らしいアプリケーションを公開してくれました。ブラウザ上でVRMモデルを表示し、Koeiro APIによる文章読み上げや、感情表現をしてくれます。MITライセンスなので、改変などもしやすそうです。

画像
VRMはサンプルモデル
pixiv/ChatVRM
Contribute to pixiv/ChatVRM development by creating an account on GitHub.

GitHub
ChatVRMでは、以下のようにOpenAIのChat Completion APIを直接叩いてキャラクターの返答を生成しています。stream: trueとすると、SSE (Server-Sent Events)というものを使って順次生成された情報を返してくれます。ChatGPTで文章が少しずつ表示されていくアレです。
```
"""

chat = [
    { "role": "system", "content": system },
    { "role": "user", "content": prompt.strip() },
]
prompt = tokenizer.apply_chat_template(chat, tokenize=False, add_generation_prompt=True)

token_ids = tokenizer.encode(prompt, add_special_tokens=False, return_tensors="pt")
with torch.no_grad():
    output_ids = model.generate(
        token_ids.to(model.device),
        do_sample=True,
        temperature=0.5,
        top_p=0.9,
        max_new_tokens=1024,
        eos_token_id=[
            tokenizer.eos_token_id,
            tokenizer.convert_tokens_to_ids("<|eot_id|>")
        ],
    )
output = tokenizer.decode(output_ids.tolist()[0][token_ids.size(1) :], skip_special_tokens=True)
print(output)

システムプロンプトを「この記事を要約してください。」とした場合、英語で回答が生成されています。

Here is a summary of the article:

The article discusses using LangChain and FastAPI as the backend for ChatVRM, an application that displays VRM models in a browser and uses Koeiro API to read out articles and express emotions.

The article mentions that ChatVRM is an open-source application under the MIT license, making it easy to modify. It also explains that ChatVRM uses OpenAI's Chat Completion API to generate character responses, and that setting `stream: true` allows for Server-Sent Events (SSE) to return generated information in real-time, similar to how ChatGPT displays text gradually.

「この記事を要約してください。必ず日本語で回答してください。」のように、日本語での回答を指示すれば、日本語で回答が生成されます。

この記事は、ChatVRMというアプリケーションを使用して、VRMモデルをブラウザ上で表示し、文章読み上げや感情表現を行うことを目指す。ChatVRMは、OpenAIのChat Completion APIを使用してキャラクターの返答を生成し、SSE (Server-Sent Events)を使用して順次生成された情報を返す。記事では、LangChainとFastAPIを使用してChatVRMのバックエンドを実装し、フロントサイドの実装についても触れています。

ただ、無視されることもありますし、必ず言語指定の指示が必要なのも微妙です(小さいモデルに対して指示を増やしたくない)。


目標

日本語の質問は日本語で回答するというスタイルを学習してもらうことを目指します。一方で、英語の質問に対しては英語での回答を維持したいです。

大量のデータで追加事前学習を行い日本語を流暢に出来たりしたら良いですが、個人のリソースでは不可能に近いため、ごく小規模のデータで試します。
データは、過去にLlama-2に口調を覚えさせるために作っていた1000件ほどのQAペアを手直して用意しました(口調を普通にする, 回答の文を少し長めにする など)。また、英訳したデータを10%ほど混ぜました。

学習はUnslothというライブラリを使って実施しました。Unslothを使うとVRAMの消費が大幅に減ります。
LLama-3 8Bのサンプルもあり、簡単に実行できます。

※ Unsloth未使用だとOOMで動かせなかったため、未使用時との比較はできていません。

学習後は、LoRAアダプターの保存やベースモデルとのマージも簡単にできます。

# マージして保存
model.save_pretrained_merged("model", tokenizer, save_method = "merged_16bit",)

# Loraのみ保存
model.save_pretrained_merged("model", tokenizer, save_method = "lora",)

学習後の推論

「日本語の質問は日本語で回答するというスタイルを学習してもらう」という目標が達成できたか確認します。

① システムプロンプトを日本語で「この記事を要約してください。」とした場合

system = "この記事を要約してください。"

prompt = """```
ChatVRMのバックエンドとしてLangChain(とFastAPI)を使う

目次
ChatVRM
やりたいこと
API(LangChain×FastAPI)の実装
フロント(ChatVRM)側の実装
ChatVRM
PixivさんがChatVRMという素晴らしいアプリケーションを公開してくれました。ブラウザ上でVRMモデルを表示し、Koeiro APIによる文章読み上げや、感情表現をしてくれます。MITライセンスなので、改変などもしやすそうです。

画像
VRMはサンプルモデル
pixiv/ChatVRM
Contribute to pixiv/ChatVRM development by creating an account on GitHub.

GitHub
ChatVRMでは、以下のようにOpenAIのChat Completion APIを直接叩いてキャラクターの返答を生成しています。stream: trueとすると、SSE (Server-Sent Events)というものを使って順次生成された情報を返してくれます。ChatGPTで文章が少しずつ表示されていくアレです。
```
"""

日本語で回答が生成されました。


ChatVRMというアプリケーションを使用して、VRMモデルをブラウザ上で表示し、Koeiro APIによる文章読み上げや、感情表現をしてくれる。ChatVRMでは、OpenAIのChat Completion APIを直接叩いてキャラクターの返答を生成している。stream: trueを指定することで、SSE (Server-Sent Events)を使って順次生成された情報を返すことができる。


② システムプロンプトを英語で「Summarize this article.」とした場合

system = "Summarize this article."

prompt = """```
ChatVRMのバックエンドとしてLangChain(とFastAPI)を使う

目次
ChatVRM
やりたいこと
API(LangChain×FastAPI)の実装
フロント(ChatVRM)側の実装
ChatVRM
PixivさんがChatVRMという素晴らしいアプリケーションを公開してくれました。ブラウザ上でVRMモデルを表示し、Koeiro APIによる文章読み上げや、感情表現をしてくれます。MITライセンスなので、改変などもしやすそうです。

画像
VRMはサンプルモデル
pixiv/ChatVRM
Contribute to pixiv/ChatVRM development by creating an account on GitHub.

GitHub
ChatVRMでは、以下のようにOpenAIのChat Completion APIを直接叩いてキャラクターの返答を生成しています。stream: trueとすると、SSE (Server-Sent Events)というものを使って順次生成された情報を返してくれます。ChatGPTで文章が少しずつ表示されていくアレです。
```
"""

英語で回答が生成されました

The article discusses the use of LangChain and FastAPI as the backend for ChatVRM, an application that displays VRM models in a browser and uses the Koeiro API to read out text and express emotions. The author highlights the flexibility of ChatVRM, which is licensed under the MIT license and can be easily modified. The article also explains how ChatVRM uses OpenAI's Chat Completion API to generate character responses, and how it uses SSE (Server-Sent Events) to display the responses in a streaming manner.

たまに日本語で指示しても英語で生成されてしまうことがありますが、概ね問題なさそうです。

ここからはllama.cppで4bit量子化(Q4_K_M)したモデルで実験してみます。
LLama-3は8Bでもそれなりに賢いので、いろいろな指示に従ってくれます。
※ ファインチューニングは関係なく、素のLlama-3-8B-Instructで対応可能


① 語尾(キャラクター)の指定

system = "語尾を「にゃ」にして猫キャラで喋ってください。"

text = "適当に今日の日記を書いてください。"

messages = [
    { "role": "system", "content": system },
    { "role": "user", "content": text.strip() },
]

res = llm.create_chat_completion(
    messages=messages,
    temperature=0.6,
    max_tokens=2048,
    top_p=0.9
)

print(res["choices"][0]["message"]["content"])

口調の例なども記載していないごく短い指示ですが、ちゃんと従ってくれていそうです。

にゃにゃ、今日はとても楽しかったにゃ!朝から友達と公園で遊びましたにゃ。スイカを食べたり、玉ねぎを投げ合ったりしたにゃ。夕方には、家の前で花火大会を見たにゃ。光る玉が空に向かって飛んでいく姿はすごく美しかったにゃ。にゃにゃ、本当に最高に楽しい一日だったにゃ!

② レスポンスフォーマット指定

system = "必ず有効なJSON形式のみで返答してください。"

text = """
固有名詞を抜き出してください。キーはnameにしてください。

```
ChatVRMのバックエンドとしてLangChain(とFastAPI)を使う

目次
ChatVRM
やりたいこと
API(LangChain×FastAPI)の実装
フロント(ChatVRM)側の実装
ChatVRM
PixivさんがChatVRMという素晴らしいアプリケーションを公開してくれました。ブラウザ上でVRMモデルを表示し、Koeiro APIによる文章読み上げや、感情表現をしてくれます。MITライセンスなので、改変などもしやすそうです。

画像
VRMはサンプルモデル
pixiv/ChatVRM
Contribute to pixiv/ChatVRM development by creating an account on GitHub.

GitHub
ChatVRMでは、以下のようにOpenAIのChat Completion APIを直接叩いてキャラクターの返答を生成しています。stream: trueとすると、SSE (Server-Sent Events)というものを使って順次生成された情報を返してくれます。ChatGPTで文章が少しずつ表示されていくアレです。
```
"""

messages = [
    { "role": "system", "content": system },
    { "role": "user", "content": text.strip() },
]

res = llm.create_chat_completion(
    messages=messages,
    temperature=0.6,
    max_tokens=2048,
    top_p=0.9
)

print(res["choices"][0]["message"]["content"])

内容が正しいかはともかく、それらしい形式で返ってきます。

{
    "name": [
        "ChatVRM",
        "LangChain",
        "FastAPI",
        "Pixiv",
        "Koeiro API",
        "OpenAI",
        "Chat Completion API"
    ]
}


③ 関数呼び出しのようなこと

system = """
あなたは誠実で優秀なアシスタントです。

<tools></tools>のXMLタグ内に利用可能なツールが提供されています。ユーザーのクエリを支援するために、1つまたは複数の関数を呼び出すことができます。

<tools>
{
  "type": "function",
  "function": {
    "name": "web_search",
    "description": "web_search(query: str) -> dict - 指定されたクエリでウェブ検索を実行し、関連する情報を取得します。",
    "parameters": {
      "type": "object",
      "properties": {
        "query": {
          "type": "string"
          "description": "検索クエリ"
        }
      },
      "required": ["query"]
    }
  }
}
</tools>

各ツール呼び出しには、以下のpydanticモデルのJSONスキーマを使用してください。
{
  "properties": {
    "arguments": {
      "title": "Arguments",
      "type": "object" 
    },
    "name": {
      "title": "Name",
      "type": "string"
    }
  }
}

各ツール呼び出しに対して、関数名と引数を含むJSONオブジェクトを<tool_call></tool_call>のXMLタグ内で以下のように返してください。
<tool_call>
{
  "arguments": <args-dict>,
  "name": <function-name>
}
</tool_call>
"""

text = """2024年現在の米国の大統領は?
"""


messages = [
    { "role": "system", "content": system },
    { "role": "user", "content": text.strip() },
]

res = llm.create_chat_completion(
    messages=messages,
    temperature=0.6,
    max_tokens=2048,
    top_p=0.9
)

print(res["choices"][0]["message"]["content"])

いいですね。

<tool_call>
{
    "arguments": {"query": "2024年現在の米国の大統領"},
    "name": "web_search"
}
</tool_call>


ベンチマーク

npakaさんの記事を参考に、日本語 MT Bench で評価を行いました。
ただし、評価に使用するモデルはGPT-4からGPT-4-Turboに変更しています。

全体スコア

########## First turn ##########
                                        score
model                            turn        
Llama-3-8B-Instruct-jp-nk2t-v0.2 1     6.6125

########## Second turn ##########
                                       score
model                            turn       
Llama-3-8B-Instruct-jp-nk2t-v0.2 2     6.325

########## Average ##########
                                    score
model                                    
Llama-3-8B-Instruct-jp-nk2t-v0.2  6.46875


カテゴリごとのスコア

Coding 5.30
Extraction 8.95
Humanities 7.05
Math 4.25
Reasoning 5.05
Roleplay 6.50
STEM 6.90
Writing 7.75

※ 80問中4問ほど英語で回答しているものがあったため、それらのスコアは手動で半分にしました。



マージ後のモデルは一応huggingfaceにアップロードしてあります。https://huggingface.co/nk2t/Llama-3-8B-Instruct-jp-nk2t-v0.2


この記事が気に入ったらサポートをしてみませんか?