LLMのTanuki-8Bを使ってみた

# 概要
ローカル環境でも動作するTanuki-8Bモデルを利用していろいろ試したみたいと思います．初回は，とりあえず動作させることと，日本語の質問応答を実装してみました．会話履歴を考慮したチャットボットについては[次回](https://qiita.com/AzukiImo/items/5b10356c7bda8e24cbdd)です．




東大松尾・岩澤研究室LLM開発プロジェクトの大規模言語モデルTanuki-8B・Tanuki-8x8Bが Hugging Faceにて公開されています．GPT3.5前後のスコア値をもつ日本語で高い能力のLLMの実現を目指したモデルで，8Bパラメータの**Tanuki-8B-dpo-v1.0** と更に大規模な 8x8Bパラメータの **Tanuki-8x8B-dpo-v1.0** の2種類があるようです．それぞれ4bit，8bit量子化モデルも用意されており，Tanuki-8Bはローカル環境やGoogle Coaboの無料枠でも十分高速に動作しそうなモデルです．RTX3060(12GB)でも高速に動作しました．


### 利用モデル

https://huggingface.co/weblab-GENIAC/Tanuki-8B-dpo-v1.0


### 利用モデルの公式アナウンス

:::note
公式より **weblab-GENIAC/Tanuki-8B-dpo-v1.0** について

Tanuki-8Bは、フルスクラッチで約1.3Tトークン事前学習を行った約8Bパラメータの大規模言語モデルです。Tanuki-8x8B-dpo-v1.0は、SFTおよびDPOにより対話用に調整されています。

より詳細な情報については[ブログ記事](https://zenn.dev/matsuolab/articles/377f7ae8b1169e)を参照してください。
:::

# 1. Tanuki-8B

Tanuki-8Bは，フルスクラッチで約1.3Tトークン事前学習を行った約8Bパラメータの大規模言語モデルです．SFT (Supervised Fine-Tuning) によって，望ましい出力を生成するよう学習させたあと，DPO (Direct Preference Optimization) によって，モデルは人間の価値観や選好により近い応答を生成できるように調整したモデルとなっています．

日本語での応答能力・作文性能を評価する代表的なベンチマーク「Japanese MT-Bench」では，
* Tanuki-8x8B: 8.0点
* Tanuki-8B: 7.3点
* GPT-3.5:　8.0点？

のようなスコアを獲得しています．今回は，8x8B版ではなくて，パラメータ数が少ない8B版を利用して対話のコードを実装してみたいと思います．モデルの詳細については，[大規模言語モデルTanuki-8B，8x8Bの位置づけや開発指針など](https://zenn.dev/matsuolab/articles/377f7ae8b1169e)を参考にしてください．


# 2. 基本的な実装

## 通常のモデル
文字を少しずつ表記してもらったほうが，返答感があるのでTextStreamerを使い文字を返答させてみます．最初に通常のモデルでコードを書いてみました．基本的に[Tanuki-8B-dpoの使い方](https://huggingface.co/weblab-GENIAC/Tanuki-8B-dpo-v1.0)に記載されているコードそのままです．

~~~python: モデルの読み込み
from transformers import AutoModelForCausalLM, AutoTokenizer, TextStreamer

base_model = "weblab-GENIAC/Tanuki-8B-dpo-v1.0"

tokenizer = AutoTokenizer.from_pretrained(base_model)
model = AutoModelForCausalLM.from_pretrained(base_model, device_map="auto", torch_dtype="auto")
streamer = TextStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
~~~

この段階でトークナイザーとモデルが読み込まれています．最後にメッセージの入力と返答に関する部分となります．プロンプトの形式ですが，日本語版Aplacaプロンプト形式を利用しているという指示に従い，次のような形に変更して，質問文を入力します．

:::note
**プロンプトの基本形**
prompt = [
　　{"role": "system", "content": "以下は、タスクを説明する指示です。要求を適切に満たす応答を書きなさい。"},
　　{"role": "user", "content": ここに質問文を記入する}
]
:::

```user_message```に質問文を入力します．promptの基本形に変更後，```apply_chat_template()```を利用してトークナイズ＆ID変換します．```model.eval()```以降は通常の文章生成と同一です．```generate()``` 関数に好みのオプションを指定して文章を生成させます．

apply_chat_templateの詳しい使い方については，[公式ドキュメント](https://huggingface.co/docs/transformers/main/en/main_classes/processors#transformers.ProcessorMixin.apply_chat_template)が一番正確です．うまく使えば会話履歴をコントロールできそうです．これは次回の予定です:sweat_smile:




~~~python: 対話
user_message = "紅茶の美味しい入れ方について教えて？"
prompt = [
    {"role": "system", "content": "以下は、タスクを説明する指示です。要求を適切に満たす応答を書きなさい。"},
    {"role": "user", "content": user_message}
]

input_ids = tokenizer.apply_chat_template(prompt, add_generation_prompt=True, return_tensors="pt").to(model.device)

model.eval()
with torch.no_grad():
    result = model.generate(
              input_ids,
              max_new_tokens=500,  # とりあえず500トークンあれば十分か？
              pad_token_id=tokenizer.pad_token_id,
              eos_token_id=tokenizer.eos_token_id,
              do_sample=False,
              streamer=streamer
          )
    del input_ids
    torch.cuda.empty_cache()
~~~

ラフな入力「```紅茶の美味しい入れ方について教えて？```」に対しての返答は？


:::note
**出力結果**
紅茶の美味しい入れ方について、いくつかのポイントを押さえて説明しますね。まず、紅茶を淹れる前に必要な道具と材料を準備しましょう。必要なものには、ティーポット、ティーカップ、ティーバッグまたはリーフティー、お湯、そして温度計があると便利です。

最初に、ティーポットに適量のお湯を入れます。一般的には、1リットルの水に対してティースプーン1杯のティーバッグやリーフティーが目安ですが、好みに応じて調整してください。お湯の温度は90度から96度が理想的です。この温度帯は紅茶の風味を最大限に引き出すために重要です。

次に、ティーバッグやリーフティーをティーポットに入れ、蓋をしっかりと閉めます。このとき、ティーバッグが完全に浸かるようにしますが、あまり長時間浸けすぎると苦味が出ることがありますので注意が必要です。

お湯を注ぎ始める前に、ティーポットを少し冷ますと良いでしょう。これにより、お湯が均等に広がり、茶葉が均一に抽出されます。お湯を注ぐ際には、一度に大量のお湯を入れるのではなく、少量ずつ注ぎながら調整します。これにより、茶葉が過度に蒸らされず、バランスの取れた味わいになります。

抽出時間はティーバッグやリーフティーによって異なりますが、一般的には3分から5分程度が目安です。ただし、紅茶の種類や好みによって調整してください。例えば、ダージリンやアッサムのような濃い紅茶は長めに抽出すると良いでしょう。

最後に、ティーカップに紅茶を注ぎます。このとき、ティースプーン1杯程度の茶葉をカップに入れ、お湯を注いで軽く混ぜます。これにより、茶葉が均等に広がり、風味が均一に広がります。

最後に、カップに注いだ紅茶を少し冷ましてから飲むと、より美味しく感じられます。また、ミルクや砂糖を加える場合は、紅茶が完全に冷めてから加えると風味が引き立ちます。

これらのポイントを押さえることで、美味しい紅茶を楽しむことができるでしょう。ぜひ試してみてくださいね。
:::

**最後に，最後に** と2回繰り返し使用されているのが若干目に付きますが，しっかりと返答できているようです．

モデルをそのまま読み込むと，GPUメモリーが12GBくらい必要になりました．

## 量子化モデル
量子化といっても，難しい手順はなく，bitsandbytsライブラリーを追加して，設定する行をコードに追加する程度です．

:::note
準備
pip install bitsandbytes
:::

量子化モデルを扱うには，基本的に次の1行を加えることになります．

:::note
* 8bit量子化：
　　　　config = BitsAndBytesConfig(load_in_8bit=True)
* 4bit量子化：
　　　　config = BitsAndBytesConfig(load_in_4bit=True)
:::

あとは，モデル読み込み時，```model = AutoModelForCausalLM.from_pretrained()```
に，量子化モデルを使うというオプションを追加すればOKとなります．



~~~python: 8bit量子化
from transformers import AutoModelForCausalLM, AutoTokenizer, TextStreamer, BitsAndBytesConfig
import torch

base_model = "weblab-GENIAC/Tanuki-8B-dpo-v1.0"

tokenizer = AutoTokenizer.from_pretrained(base_model)

# 8bit量子化の設定
config = BitsAndBytesConfig(load_in_8bit=True)
model = AutoModelForCausalLM.from_pretrained(
            base_model,
            quantization_config=config,
            device_map="auto",
            torch_dtype="auto"
        )
streamer = TextStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
~~~
この段階でトークナイザーとモデルが読み込まれています．あとは，先程と同様に表示する部分を記述すればOKです．

紅茶の入れ方については，全く同一の返答だったので，他の質問をしてみました．


~~~Python: 8bit量子化
user_message = "$\\int x^2 dx$の計算をしてください．"
prompt = [
    {"role": "system", "content": "以下は、タスクを説明する指示です。要求を適切に満たす応答を書きなさい。"},
    {"role": "user", "content": user_message}
]

input_ids = tokenizer.apply_chat_template(prompt, add_generation_prompt=True, return_tensors="pt").to(model.device)
#print(input_ids)

model.eval()
with torch.no_grad():
    result = model.generate(
              input_ids,
              max_new_tokens=500,
              pad_token_id=tokenizer.pad_token_id,
              eos_token_id=tokenizer.eos_token_id,
              do_sample=False,
              streamer=streamer
          )
    del input_ids
    torch.cuda.empty_cache()
~~~

高校生に戻って，不定積分の計算を頼んでみました．```$\\int x^2 dx$の計算をしてください．```という質問です．TeXの記法が学習されている可能性が高いです．ちなみに，```\int```という表記をすると```<>:4: SyntaxWarning: invalid escape sequence '\i'```が表示されます．返答は，全く同一なので，影響は軽微なのかもしれません．返答速度は量子化なしの状態よりも高速になっています．

:::note
**8bitモデルの返答**
もちろんです。積分 $\int x^2 dx$ を計算する手順を順を追って説明します。

まず、積分 $\int x^2 dx$ は基本的な不定積分の一つです。この積分を計算するために、一般的な方法である「べき関数の積分」を使います。具体的には、$x^n$ の不定積分は $\frac{x^{n+1}}{n+1} + C$ という公式を利用します。ここで $n$ は定数です。

今回の場合、$n = 2$ ですので、公式に当てはめると次のようになります（Cは積分定数））：

$\int x^2 dx = \frac{x^{2+1}}{2+1} + C = \frac{x^3}{3} + C$

したがって、$\int x^2 dx$ の計算結果は $\frac{x^3}{3} + C$ となります。ここで $C$ は任意の定数です。

まとめると、$\int x^2 dx = \frac{x^3}{3} + C$ です。これが求める積分の結果です。
:::
結果は，あっているっぽいぞ！:grinning::grinning::grinning:

残念ながら，$\int \log x dx$ の計算は間違えていました．利用しているGPUメモリは8GBくらいでした．


# 3. 4bit， 8bit， 量子化なし
GPUメモリの利用量の大まかな値です．あくまでも目安だと思ってください．

|モデル|GPUメモリ|
|-----|--------:|
|量子化なし| 12GB |
|8bitモデル | 9GB |
|4bitモデル | 6GB |

紅茶の入れ方に対する返答は量子化なし，8bitとは異なり，次のような簡易なものになりました．ただ，これだけ表示されれば返答の良し悪しはわかりにくいと思われます．

:::note
**4bitモデルの返答**
紅茶の美味しい入れ方にはいくつかのポイントがあります。まず、新鮮な茶葉を使用することが大切です。茶葉はできるだけオーガニックなものを選び、可能であれば地元の農園から購入すると良いでしょう。これにより、風味が一層豊かになります。

次に、適切な温度の水を使うことが重要です。一般的には90度前後が適温とされています。熱すぎると茶葉が苦くなり、冷たすぎると茶葉が十分に抽出されません。湯温を確認するために、沸騰したお湯を少し冷ますと良いでしょう。

ティーポットやカップも重要な要素です。ガラス製のティーポットは透明で茶葉の色や抽出過程を楽しむことができ、また保温性も高いのでおすすめです。カップも保温性のあるものを選ぶと、最後まで温かいお茶を楽しむことができます。

お湯を注ぐ際には、まず少量のお湯で茶葉を蒸らします。これを「ファーストフラッシュ」と呼び、約30秒から1分程度が目安です。その後、少しずつお湯を追加して抽出します。一度に大量のお湯を注ぐと茶葉が過抽出され、苦味が出ることがありますので注意が必要です。

抽出時間も重要で、一般的には2〜3分が適切ですが、茶葉の種類や好みによって調整してください。最後に、茶葉をカップに注ぎ、軽くかき混ぜて均一な濃さになるようにします。このとき、茶葉が沈殿している場合は、軽く振って混ぜると良いでしょう。

最後に、ミルクや砂糖を加える場合は、紅茶の抽出が終わった後に加えると、風味が引き立ちます。例えば、ミルクティーにする場合は、温めた牛乳を少しずつ加えて好みの濃さに調整します。砂糖を加える場合も同様に、少量から始めて味を見ながら調整すると良いでしょう。

これらのポイントを押さえることで、紅茶の美味しさを最大限に引き出すことができます。ぜひ試してみてください。
:::

積分の計算については，完全に異なる返答で，計算結果も途中で終わり，最終的に000が連続して出力されるという結果になりました．

:::note
**4bitモデルの返答**

もちろんです。積分 $\int x^2 dx$ を計算する方法を説明します。この積分は基本的な不定積分の一つであり、比較的簡単に解くことができます。

まず、関数 $f(x) = x^2$ の不定積分を求めるために、原始関数（アンチディファレンシャル（逆微分）を見つけます。具体的には、$f(x)$ の不定積分を $F(x)$ とすると、$F'(x) = f(x)$ となるような関数 $F(x)$ を探します。

関数 $f(x) = x^2$ の場合、その導関数は $f'(x) = 2x$ です。したがって、原始関数 $F(x)$ は次のように求められます（100000000000
:::

GPUメモリが潤沢にある環境であれば，そもそもTanuki-8Bではなく，Tanuki-8x8BやLlama3が動作できる．GPUメモリが少ない環境でもそこそこの日本語性能ということを考えると8bit量子化モデルのTanuki-8Bはおすすめできるモデルではないかと思います．4bitモデルだとちょっと返答に不安が残りました．

Tanuki-8Bで指示チューニングあたりも試みたいと思います．


# 参考記事

* MacBookで動作させたという記事

https://qiita.com/nabata/items/aaacf28cb098e14e1ab4

* RAGを実装したという記事

https://zenn.dev/mkj/articles/93dbd6c9d94c58

* Tanuki-8x8B （ColabのA100使用）

https://note.com/npaka/n/ndbbd243f8073

* tanuki instructを試すという記事（tanuki-8b-instructモデルはもう公開されていないかも）

https://qiita.com/isanakamishiro2/items/5838f6ad7a214ae0c2c3


* 会話履歴を考慮したモデル

https://qiita.com/AzukiImo/items/5b10356c7bda8e24cbdd