N番煎じでCyberAgent社のCALM2をDatabricksで動かす

TheBloke兄貴のAWQ量子化を待っていたのですが、キャンセルされたっぽいですね。復活期待。

# 導入

CyberAgent社が日本語LLMであるCALM2を発表しました。
特徴は32Kトークンのコンテキスト対応で、約50,000文字の日本語入力が可能なようです。
SNS上でもかなり評判がよかったので試してみます。

https://www.cyberagent.co.jp/news/detail/id=29479

既に偉大な以下のような先達方が試されているのですが（お世話になっております）、N番煎じシリーズなので気にせずにやります。あと、少し変わったやり方？で推論します。

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

https://qiita.com/taka_yayoi/items/e0f5392c79638bab6b29

# Step1. ダウンロード

いつものように、モデルをダウンロードして保管します。
今回はChatモデルを利用します。

https://huggingface.co/cyberagent/calm2-7b-chat

今回もモデルのスナップショットをUnity Catalogのボリュームに保管します。パスは```/Volumes/training/llm/model_snapshots```です。環境に合わせて変更してください。

```python
%pip install -U -qq transformers accelerate

dbutils.library.restartPython()
```

```python
import os
from huggingface_hub import snapshot_download

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

model = "cyberagent/calm2-7b-chat"
local_dir = f"/tmp/{model}"
uc_dir = "/models--cyberagent--calm2-7b-chat"

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)
```

# Step2. 推論準備

CALM2はアーキテクチャがLlamaです。（ライセンス体系がApache 2.0なので、Llama2などのFine-tuningモデルではなく自前で学習された互換モデルだと思われます）
つまり、CTranslate2などの推論高速化ライブラリでも利用しやすい構造になっています。

通常のTransformersで推論するのは先達の記事を見ていただくとして、今回はCTranslate2で8bit量子化したモデルで推論します。

というわけで、まずは変換処理。必要なモジュールをインストール後、```ct2-transformers-converter```を呼び出して変換、UnityCatalogのボリュームに変換済みモデルを保管します。

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

```python
UC_VOLUME = "/Volumes/training/llm/model_snapshots"
uc_dir = "/models--cyberagent--calm2-7b-chat"

src_path = f"{UC_VOLUME}{uc_dir}"
local_output_dir = f"/tmp/ctranslate2/{uc_dir}"

UC_CT2_VOLUME = "/Volumes/training/llm/ctranslate2_models"
uc_ct2_dir = "/cyberagent/calm2-7b-chat"
```

```python
!ct2-transformers-converter --model {src_path} --copy_files tokenizer.json tokenizer_config.json special_tokens_map.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)
```

# Step3. CT2モデルで推論

モデルをロードして、推論。
推論用の関数を```generate_batch```にラップ。

```python
import ctranslate2
import transformers
import torch

model_path = "/Volumes/training/llm/ctranslate2_models/cyberagent/calm2-7b-chat"

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

def generate_batch(instruction:str) -> str:

    prompt = f"USER: {instruction}\nASSISTANT: "

    # 推論の実行
    tokens = tokenizer.convert_ids_to_tokens(
        tokenizer.encode(prompt, add_special_tokens=False)
    )
    results = generator.generate_batch(
        [tokens],
        max_length=512,
        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
```

では、いくつかやってみましょう。
Elyza社のモデルを試したときに行ったプロンプトで聞いてみます。

## 推論その1

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

```:出力
私がデザインした架空の生き物は「トリックスター」です。

トリックスターは、いたずら好きで好奇心旺盛な生き物です。体長は約10cmで、鮮やかな色彩とユニークな形状をしています。彼らの主な特徴の一つは、その独特な動きです。彼らは常に忙しく動き回り、時には滑稽なダンスを踊ることもあります。また、彼らは非常に器用であり、さまざまな物を手でつかむことができます。

トリックスターの能力は多岐にわたります。彼らは優れた情報収集能力を持っており、隠された情報を見つけるのが得意です。また、彼らは優れたコミュニケーション能力を有しており、他者と協力して問題を解決することができます。さらに、彼らは創造的な思考力を持ち、新しいアイデアを生み出すことができます。

トリックスターは、人々を楽しませることが大好きです。彼らはしばしば、いたずらや冗談を使って人々を楽しませます。また、彼らは芸術や文化にも深い関心を持っています。そのため、彼らはしばしば絵画や彫刻などの作品を創作します。

以上が、トリックスターの特徴や能力についての簡単な説明です。
```

## 推論その2

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

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

# ランダムに要素を生成してリストに追加する関数
def generate_random_list(size):
    return [random.choice(range(len(list))) for _ in range(size)]

# リストをソートする関数
def sort_list(lst):
    # リストを逆順に並べ替える関数
    def reverse(x, y):
        return x[-1] - y[-1]

    # リストを降順で並べ替える関数
    def sort_down(lst):
        return sorted(lst, key=reverse)

    # ランダムに生成したリストをソートする関数
    lst = generate_random_list(10)
    return sort_down(lst)

# テスト用のコード
print(sort_list([2, 3, 4, 5, 6, 7, 8, 9, 10, 1]))
print(sort_list([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))
`` `
このコードは、`generate_random_list()`という関数を定義しています。この関数は、ランダムな整数を生成してリストに追加します。次に、`sort_list()`という関数を定義しています。この関数は、リストを逆順に並べ替え、それを降順で並べ替えたリストを返します。最後に、生成されたリストを`sort_down()`関数を使ってソートします。
```

ちなみにこのコードはそのままだと動きませんでした。


## 推論その3

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

```:出力
表: 日本国内で観光したい地域とその特徴

| 都道府県 | おすすめスポット | おすすめグルメ |
| --- | --- | --- |
| 東京 | 浅草寺、東京タワー、明治神宮、お台場 | お寿司、もんじゃ焼き、ラーメン |
| 名古屋 | 名古屋城、熱田神宮、東山動植物園、ひつまぶし | 味噌カツ、手羽先、きしめん |
| 大阪 | 通天閣、ユニバーサルスタジオジャパン、道頓堀、たこ焼き | 串カツ、お好み焼き、たこ焼き |
| 京都 | 清水寺、金閣寺、嵐山、抹茶スイーツ | 京料理、湯豆腐、にしんそば |
| 福岡 | 大宰府天満宮、博多ラーメン、もつ鍋、明太子 | 屋台、水炊き、辛子明太子 |
```

お勧めスポットに食べ物が入ってくるのは、食いしん坊だからでしょうか。。。

## 推論その4

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

```:出力
呪術廻戦の今後の展開については、まだ具体的な内容は発表されていません。しかし、作者の芥見下々先生は「次号から新章突入」と明言していますので、新たな展開が待っていることは間違いなさそうです。
また、芥見下々先生のインタビューによると、「五条悟の過去編」「乙平の過去編」「夏油傑の過去編」など、これまで描かれてこなかったキャラクターたちの過去や秘密が明らかになる可能性があるようです。
さらに、2023年にはアニメ第2期の放送が決定しており、原作とは違った視点で描かれるオリジナルストーリーも期待されます。
以上のように、今後も目が離せない展開が続くことが予想される呪術廻戦。今後の動向に注目しましょう！
```

# まとめ

7Bという少ないパラメータで、破綻なく日本語文章を作れているのが凄いですね。
簡単に試しただけですが、日本語文章の生成についてはある程度これで十分な気がします。
RAGに利用する際の性能であったりとか、ユースケースに応じた性能がどうなのかは気になりますが、日本語中心のユースケースにおいては第一候補にあがるのではないでしょうか。


しかし、今週はいろんなニュース盛沢山で、LLM関連はやりたいこと・書きたいことが本当に尽きなくて楽しい。自分の時間が無限になればいいのに。。。