N番煎じでELYZA社のELYZA-japanese-Llama-2-7bをDatabricksで動かす

N番煎じシリーズです。

ELYZA社が商用利用可能な日本語LLMを公開されました。

https://note.com/elyza/n/na405acaca130

既に以下の御大方が試されていますが、気にせず私も試してみます。

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

https://note.com/shi3zblog/n/na52fe80cd7cf

いつものようにDatabricks上で確認しています。DBRは13.3LTS MLです。

# モデルの取得と保管

以前の記事と同様に、モデルのダウンロード & Unity Catalogのボリュームに保管します。
https://qiita.com/isanakamishiro2/items/159ae600bb999ba6d1c0

```python
# 念のため、transformersの最新化
%pip install -U -qq transformers accelerate

dbutils.library.restartPython()
```

```python
import os
from huggingface_hub import snapshot_download

UC_VOLUME = "/Volume/モデルを保管するUnity Catalogのボリュームパス"

model = "elyza/ELYZA-japanese-Llama-2-7b-instruct"
local_dir = "/tmp/elyza/ELYZA-japanese-Llama-2-7b-instruct"
uc_dir = "/models--elyza--ELYZA-japanese-Llama-2-7b-instruct"

snapshot_location = snapshot_download(
    repo_id=model,
    local_dir=local_dir,
    local_dir_use_symlinks=False,
)

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

```

なお、ELIZA-japanese-Llama-2-7bはfastモデル(```elyza/ELYZA-japanese-Llama-2-7b-fast-instruct```)が存在します。こちらはトークナイザを改良し、推論速度1.82倍と大幅な効率化に成功しているようです。
こちらのモデルも合わせてダウンロードしておきます。（コードは省略）

# transformersで推論

[公式](https://huggingface.co/elyza/ELYZA-japanese-Llama-2-7b-instruct)のコードに則ります。
推論用のノートブックを作って、以下のコードを実行。

```python
%pip install -U -qq transformers accelerate
dbutils.library.restartPython()
```

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

UC_VOLUME = "/Volumes/モデルを保管するUnity Catalogのボリュームパス"

tokenizer_path = f"{UC_VOLUME}/models--elyza--ELYZA-japanese-Llama-2-7b-instruct"
model_path = f"{UC_VOLUME}/models--elyza--ELYZA-japanese-Llama-2-7b-instruct"
# fastモデルを使う場合は、以下のコメントを外す
# tokenizer_path = f"{UC_VOLUME}/models--elyza--ELYZA-japanese-Llama-2-7b-fast-instruct"
# model_path = f"{UC_VOLUME}/models--elyza--ELYZA-japanese-Llama-2-7b-fast-instruct"

B_INST, E_INST = "
", "
"
B_SYS, E_SYS = "
\n", "\n
\n\n"
DEFAULT_SYSTEM_PROMPT = "あなたは誠実で優秀な日本人のアシスタントです。"
text = "クマが海辺に行ってアザラシと友達になり、最終的には家に帰るというプロットの短編小説を書いてください。"

tokenizer = AutoTokenizer.from_pretrained(tokenizer_path)
model = AutoModelForCausalLM.from_pretrained(model_path, torch_dtype="auto")

if torch.cuda.is_available():
    model = model.to("cuda")

prompt = "{bos_token}{b_inst} {system}{prompt} {e_inst} ".format(
    bos_token=tokenizer.bos_token,
    b_inst=B_INST,
    system=f"{B_SYS}{DEFAULT_SYSTEM_PROMPT}{E_SYS}",
    prompt=text,
    e_inst=E_INST,
)

with torch.no_grad():
    token_ids = tokenizer.encode(prompt, add_special_tokens=False, return_tensors="pt")

    output_ids = model.generate(
        token_ids.to(model.device),
        max_new_tokens=256,
        pad_token_id=tokenizer.pad_token_id,
        eos_token_id=tokenizer.eos_token_id,
    )
output = tokenizer.decode(output_ids.tolist()[0][token_ids.size(1) :], skip_special_tokens=True)
print(output)
```

```:結果
承知しました。以下にクマが海辺に行ってアザラシと友達になり、最終的には家に帰るというプロットの短編小説を記述します。

クマは山の中でゆっくりと眠っていた。
その眠りに落ちたクマは、夢の中で海辺を歩いていた。
そこにはアザラシがいた。
クマはアザラシに話しかける。

「おはよう」とクマが言うと、アザラシは驚いたように顔を上げた。
「あ、おはよう」アザラシは少し緊張した様子だった。
クマはアザラシと友達になりたいと思う。

「私はクマと��
```

関数にラップして、X上で紹介されているプロンプトも実行してみます。

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

    prompt = "{bos_token}{b_inst} {system}{prompt} {e_inst} ".format(
        bos_token=tokenizer.bos_token,
        b_inst=B_INST,
        system=f"{B_SYS}{DEFAULT_SYSTEM_PROMPT}{E_SYS}",
        prompt=instruction,
        e_inst=E_INST,
    )

    with torch.no_grad():
        token_ids = tokenizer.encode(prompt, add_special_tokens=False, return_tensors="pt")

        output_ids = model.generate(
            token_ids.to(model.device),
            max_new_tokens=256,
            pad_token_id=tokenizer.pad_token_id,
            eos_token_id=tokenizer.eos_token_id,
        )
    output = tokenizer.decode(output_ids.tolist()[0][token_ids.size(1) :], skip_special_tokens=True)

    return output
```

```python
generate_batch("架空の生き物をデザインし、その特徴や能力について説明してください。")
```
```:結果
承知しました。架空の生き物として、「ファントム」をデザインし、その特徴や能力について説明します。

ファントムは、体の大きさがその倍率に比例して大きくなるという特徴を持っています。例えば、ファントムが体長10メートルの個体であれば、その体長が10倍になると、体長は100メートルになります。この特性を利用して、ファントムは遠くの敵を攻撃することができます。

また、ファントムは自分の体を透明にすることができます。この能力を利用して、ファントムは敵の動向
```

```python
generate_batch("ランダムな10個の要素からなるリストを作成してソートするコードをPythonで書いてください。")
```
```:結果
承知しました。ランダムな10個の要素からなるリストを作成してソートするPythonのコードは以下の通りです: 

```python
import random

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

# ソートする\nelements.sort()

# ソート後のリストをprintする
print(elements)
`` `

上記のコードでは、`random.randint(1, 10)`を10回繰り返して、ランダムな整数を10個作成します。その後、リストとして保管します。次に、`elements.sort()`でソートします。最後に、`print(elements)`でソート後のリストを
```

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

| 都道府県 | おすすめスポット | おすすめグルメ |
|:----:|:------:|:------:|
|東京 | 東京タワー、スカイツリー、浅草 | ラーメン、たこ焼き |
|名古屋 | 名古屋城、熱田神宮、金の卵 | カレー、つくね |
|大阪 | USJ、通天閣、グリコの看板 | たこ焼き、お好み焼き |
|京都 | 金閣寺、清水寺、祇園 | 祇園祭、京都大学 |
|福岡 | 天神、博多駅、ハウステンボス | 皿うどん、もつ鍋 |
```

これは・・・しゅごい・・・。7bでここまで日本語扱えるのしゅごい・・・。

ちなみに、GPUクラスタ(g5.2xlarge)で実行していますが、
上のプロンプトを実行すると、通常のモデルで9秒前後、fastモデルで6～8秒前後の推論時間でした。
出力内容が違うので、厳密な比較になっていませんが、fastモデルしゅごい。

# いつものやつ

というわけで、CTranslate2を使って8bit量子化＆高速化します。fastモデルのみ今回はやってみます

ノートブックを新たに作って、以下のコードで変換。
```python
%pip install -U -qq ctranslate2 transformers accelerate
dbutils.library.restartPython()
```

```python
UC_VOLUME = "/Volumes/モデルを保管しているるUnity Catalogのボリュームパス"
uc_dir = "/models--elyza--ELYZA-japanese-Llama-2-7b-fast-instruct"

src_path = f"{UC_VOLUME}{uc_dir}"
local_output_dir = "/tmp/ctranslate2/models--elyza--ELYZA-japanese-Llama-2-7b-fast-instruct"

UC_CT2_VOLUME = "/Volumes/CT2で変換したモデルを保管するボリューム"
uc_ct2_dir = "/elyza/ELYZA-japanese-Llama-2-7b-fast-instruct"
```

```python
!ct2-transformers-converter --model {src_path} --copy_files tokenizer_config.json special_tokens_map.json tokenizer.json  --output_dir {local_output_dir} --quantization int8_bfloat16 --low_cpu_mem_usage
```

```python
dbutils.fs.cp("file:"+local_output_dir, f"{UC_CT2_VOLUME}{uc_ct2_dir}", recurse=True)
```

※ fastモデルではない通常モデルの場合、--copy_files引数にはtokenizer.modelの指定が必要です。

推論実行。

```python
import ctranslate2
import transformers
import torch

model_path = "/Volumes/CT2を保管しているボリュームパス/elyza/ELYZA-japanese-Llama-2-7b-fast-instruct"

# ジェネレーターとトークナイザーの準備
device = "cuda" if torch.cuda.is_available() else "cpu"
generator = ctranslate2.Generator(model_path, device=device)
tokenizer = transformers.AutoTokenizer.from_pretrained(model_path)

B_INST, E_INST = "
", "
"
B_SYS, E_SYS = "
\n", "\n
\n\n"
DEFAULT_SYSTEM_PROMPT = "あなたは誠実で優秀な日本人のアシスタントです。"

# 推論用関数
def generate_batch(instruction:str) -> str:

    prompt = "{bos_token}{b_inst} {system}{prompt} {e_inst} ".format(
        bos_token=tokenizer.bos_token,
        b_inst=B_INST,
        system=f"{B_SYS}{DEFAULT_SYSTEM_PROMPT}{E_SYS}",
        prompt=instruction,
        e_inst=E_INST,
    )

    # 推論の実行
    tokens = tokenizer.convert_ids_to_tokens(
        tokenizer.encode(prompt, add_special_tokens=False)
    )
    results = generator.generate_batch(
        [tokens],
        max_length=256,
        sampling_topk=1,
        sampling_temperature=0.1,
        repetition_penalty=1.1,
        include_prompt_in_result=False,
        return_scores=True,
    )

    text = tokenizer.decode(results[0].sequences_ids[0])
    return text
```


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

デザイン: 

身体の大部分は水分で構成されており、その中には様々なミネラルが溶けています。この生き物の名前は「ミネラル」です。
形状は魚のようにも鳥のようにも見えます。

特徴: 

ミネラルの口から出る水は、ミネラルが溶けているため、様々な栄養素を豊富に含んでいます。そのため、この生き物を漁るときは網ではなく、すくっている水を飲むことで、栄養を摂取することができます。
また、ミネラルの皮膚からは、様々なミネラルが蒸発していまいます。これを拾うことで、更なる栄養補給ができます。

能力: 

ミネラルは自身の体液を武器として使用することができます。その体液は非常に強酸性であり、敵対する生命体を瞬時に死に至らしめるほどです。
しかし、同時に自分自身も致命傷を与えてしまう可能性があるため、使用は極めて慎重に行わなければなりません。
```

推論時間は3～6秒、VRAM使用量は7.26GBでした。
高速化されていますが、もともとのモデルが早いのでtransformersそのままでも実利用に問題なさそうです。

# まとめ

しゅごい。日本語LLMの選択肢がどんどん増えていって嬉しい限りです。

# おまけ

```python
generate_batch("呪術廻戦の今後の展開を教えてください。")
```

```:結果
「呪術廻戦」の今後の展開について予測を行います。

1. 主人公が現状を打破する
    - 手裏剣を習い、強くなっていく
    - キイハーデスの正体と目的を知る
2. ライバルとの対決
    - マジシャンと死闘を繰り広げる
3. 敵の本拠地への進撃
    - 七つの大罪に挑む
4. 最終決戦
    - キイハーデスと一騎打ちになる
5. 物語は完結し、新たな世界へと歩み始める
```

誰やねん。