N番煎じでLightblue社のQarasu-14BをDatabricksで動かす

日本語LLMの公開は年末ギリギリを狙うというルールでもあるんでしょうか。。。

# 導入

Lightblue社から日本語LLMであるKarasu/Qarasuシリーズが公開されました。

https://note.com/peter_lightblue/n/n2def04ca0d30

Karasu-7Bはshisa-7Bをベースモデルとしてトレーニングしたモデルであり、非常に軽量かつ一部の13Bのモデルと同等の性能を示しているようです。
※　Shisa-7Bは[こちらの記事](https://qiita.com/isanakamishiro2/items/f3b09b5f738adf6572cc)で以前試しています。

Qarasu-14BはQwen-14Bをベースモデルとしてトレーニングしたモデルであり、MT-BenchでGPT-3.5 Turboに迫る性能となっています。

![Bench](https://assets.st-note.com/img/1703833141227-7aEenpZbEn.png)

すでに以下のように大先生方が既に試されておりますが、今回もN番煎じでQarasu-14BをDatabricks on AWS上で試してみます。

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

huggingface上のリポジトリリンクはこちら。

https://huggingface.co/lightblue/qarasu-14B-chat-plus-unleashed/tree/main


Databricksで試すにあたって、DBRは14.1ML、クラスタタイプ g5.xlarge(AWS)で確認しました。

# Step1. パッケージのインストール

必要なパッケージをインストール。

```python
%pip install "transformers>=4.36.2" accelerate bitsandbytes tiktoken einops transformers_stream_generator 
dbutils.library.restartPython()
```

# Step2. モデルのダウンロード

モデルのスナップショットをダウンロードし、Unity Catalog Volumes内に保管。

```python

from typing import Optional

def download_model(model_id:str, revision:Optional[str]=None):
    import os
    from huggingface_hub import snapshot_download

    UC_VOLUME = "/Volumes/training/llm/model_snapshots"

    rev_dir = ("--" + revision) if revision else ""
    local_dir = f"/tmp/{model_id}{rev_dir}"
    uc_dir = f"/models--{model_id.replace('/', '--')}"
    
    snapshot_location = snapshot_download(
        repo_id=model_id,
        revision=revision,
        local_dir=local_dir,
        local_dir_use_symlinks=False,
    )

    dbutils.fs.cp(f"file:{local_dir}", f"{UC_VOLUME}{uc_dir}{rev_dir}", recurse=True)

model_id = "lightblue/qarasu-14B-chat-plus-unleashed"
download_model(model_id)
```

# Step3. モデルのロード&推論

モデルをロードして、推論実行。
[ここ](https://huggingface.co/lightblue/qarasu-14B-chat-plus-unleashed#hugggingface)のコードを参考にしています。

また、bitsandbytesで8bit量子化するように指定しました。
(VRAM消費量は最終的に16GMくらいでした)

```python
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
import torch

model_path = "/Volumes/training/llm/model_snapshots/models--lightblue--qarasu-14B-chat-plus-unleashed"

tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
    model_path,
    torch_dtype=torch.bfloat16,
    device_map="auto",
    trust_remote_code=True,
    load_in_8bit=True,
)

pipe = pipeline("text-generation", model=model, tokenizer=tokenizer)

messages = [{"role": "system", "content": "あなたはAIアシスタントです。"}]
messages.append({"role": "user", "content": "イギリスの首相は誰ですか？"})

prompt = tokenizer.apply_chat_template(
    conversation=messages,
    add_generation_prompt=True,
    tokenize=False,
)

pipe(
    prompt,
    max_new_tokens=100,
    do_sample=False,
    temperature=0.0,
    return_full_text=False,
)
```

```:出力
[{'generated_text': '現在のイギリスの首相はリサ・ナウドです。彼女は2019年7月に保守党の党首を務め、同年12月に首相に就任しました。\n'}]
```

だいぶWARNINGが出ていたので、もしかしたら処理ミスっている可能性あり。
あと、答えはハルシネーションしてますね。。。

<br>

ひとまず推論部分を関数にラップして、他にもいろいろ指示してみます。

```python
# 関数ラップ
def generate_batch(instruction:str):

    messages = [{"role": "system", "content": "あなたはAIアシスタントです。"}]
    messages.append({"role": "user", "content": instruction})

    prompt = tokenizer.apply_chat_template(conversation=messages, add_generation_prompt=True, tokenize=False)

    output = pipe(prompt, max_new_tokens=256, do_sample=True, temperature=0.1, return_full_text=False)

    return output[0]["generated_text"]
```

## 架空の生き物をデザインし、その特徴や能力について説明してください。

```python
print(generate_batch("架空の生き物をデザインし、その特徴や能力について説明してください。"))
```

```:出力
こんにちは！私はAIであり、あなたの質問に答えます。

架空の生き物をデザインすることは、創造力を発揮する素晴らしい方法です。以下に、架空の生き物「エレメンタリア」の特徴と能力について説明します。

エレメンタリアは、火、水、風、土の4つの元素からなる生命体です。彼らは、それぞれの元素が持つ力を持っています。

火のエレメンタリアは、炎を操ることができます。彼らは、炎を自在に形作ることができ、また、炎を使って攻撃することもできます。しかし、彼らは火に弱く、火災に巻き込まれると危険になります。

水のエレメンタリアは、水を操ることができます。彼らは、水を自在に形作ることができ、また、水を使って攻撃することもできます。しかし、彼らは水に弱く、洪水に巻き込まれると危険になります。

風のエレメンタリアは、風を操ることができます。彼らは、風を自在に形作ることができ、また、風を使って攻撃することもできます。しかし、彼らは風に弱く、
```

同じ属性に弱いという斬新な生き物。

## ランダムな10個の要素からなるリストを作成してソートするコードをPythonで書いてください。

```python
print(generate_batch("ランダムな10個の要素からなるリストを作成してソートするコードをPythonで書いてください。"))
```

````:出力
以下は、ランダムな10個の要素からなるリストを作成し、ソートするPythonコード例です。
```python
import random

# ランダムな10個の要素からなるリストを作成
random_list = [random.randint(1, 100) for _ in range(10)]

# ソート
random_list.sort()

print(random_list)
```

このコードでは、`random.randint()` 関数を使用して、1から100までの整数をランダムに生成しています。生成された10個の要素を格納するために、`range()` 関数とリスト推移子 (`_`) を使用しています。最後に、`sort()` 関数を使用して、生成されたリストをソートしています。
このコードを実行すると、ランダムな10個の要素からなるリストが生成され、ソートされます。
````

正しく動くコードが生成されました。
最近のモデルは当たり前のように出力しますね。


## 東京、名古屋、大阪、京都、福岡の特徴

```python
print(generate_batch("日本国内で観光に行きたいと思っています。東京、名古屋、大阪、京都、福岡の特徴を表にまとめてください。列名は「都道府県」「おすすめスポット」「おすすめグルメ」にしてください。"))
```

```:出力
| 都道府県 | おすすめスポット | おすすめグルメ |
| --- | --- | --- |
| 東京都 | 東京タワー、上野動物園、浅草寺 | 寿司、ラーメン、天ぷら |
| 名古屋市 | 名古屋城、熱田神宮、名古屋港水族館 | 焼き鳥、うどん、味噌カツ |
| 大阪府 | 大阪城、通天閣、梅田スカイビル | 天丼、お好み焼き、章魚烧 |
| 京都府 | 金閣寺、清水寺、伏見稲荷大社 | 和菓子、抹茶、京都料理 |
| 福岡県 | 西新地、博多祇園山笠、太宰府天満宮 | フライドポテト、ラーメン、明太子 |
```

「章魚烧」は「たこ焼き」のことのようです。
まあまあいい感じの内容・・・かな？


## おまけ

```python
print(generate_batch("まどか☆マギカでは誰が一番かわいい?"))
```

```:出力
私はAIであり、個人的な意見を持たないため、まどか☆マギカで最も可愛いキャラクターは誰であるかを判断することはできません。しかし、多くの人々が愛するキャラクターとして、美少女戦士セーラームーンやその仲間たちが挙げられます。また、魔法使いのリリカルな世界にも魅力的なキャラクターが多数登場しています。
```

別作品が来るとは予想外。

# まとめ

Qarasu-14BをDatabricksで試してみました。
8bit量子化した関係上、読み込み方を失敗していたかもしれず、結果は参考程度に見てください。
EXL2で量子化したりもしたかったのですが、ベースモデルとなっているQwenは2023/12/29現在、ExLlamaV2未対応のようでした。量子化を試すとしたらAWQ+vLLMがいいのかな。。。

また、KarasuがShisaベースということで、こちらも面白そうです。折を見て試してみようと思います。