N番煎じでXwin-LM-13B-V0.1をDatabricksで動かす

N番煎じシリーズです。

下記リンク先で紹介されているXwin-LMをDatabricks上で試してみます。

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

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

今回は最初から[CTranslate2](https://github.com/OpenNMT/CTranslate2)で変換したモデルを使って推論します。
Transformersそのままでも動くとは思います。

# Xwin-LMとは

https://github.com/Xwin-LM/Xwin-LM

> Xwin-LMは、教師あり微調整(SFT)、報酬モデル(RM)、リジェクトサンプリング、人間のフィードバックからの強化学習(RLHF)など、大規模言語モデルのためのアライメント技術を開発し、オープンソース化することを目指しています。私たちの最初のリリースは、Llama2ベースモデルに基づいて構築され、AlpacaEvalで**トップ1**にランクされました。特筆すべきは、このベンチマークで**初めてGPT-4を上回った**ことです。プロジェクトは継続的に更新される予定です。

ということで、[AlpacaEval](https://tatsu-lab.github.io/alpaca_eval/)で70BモデルがGPT-4を超えたようです。
13BモデルでさえClaude 2を超えてるので期待が持てます。

# 検証環境

Databricks on AWSを使って検証しています。
DBRは13.3ML、CT2のモデル変換はi3.2xlarge、推論時はg5.2xlargeのクラスタを利用しました。
Xwin-LMは7B、13B、70Bと公開されていますが、今回は8bitに量子化した13Bモデルを使います。

# モデルの取得と保管

[こちら](https://qiita.com/isanakamishiro2/items/4258143eb9de158cda69)とほぼ同じです。
最終的にUnity CatalogのVolumesに保管します。

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

dbutils.library.restartPython()
```

```python
import os
from huggingface_hub import snapshot_download

UC_VOLUME = "/Volumes/モデルの保管場所"

model = "Xwin-LM/Xwin-LM-13B-V0.1"
local_dir = "/tmp/Xwin-LM/Xwin-LM-13B-V0.1"
uc_dir = "/models--Xwin-LM--Xwin-LM-13B-V0.1"

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

# CTranslate2モデルに変換

これも以前の記事同様です。

```python
UC_VOLUME = "/Volumes/スナップショットの保管場所"
uc_dir = "/models--Xwin-LM--Xwin-LM-13B-V0.1"

src_path = f"{UC_VOLUME}{uc_dir}"
local_output_dir = "/tmp/ctranslate2/models--Xwin-LM--Xwin-LM-13B-V0.1"

UC_CT2_VOLUME = "/Volumes/CT2モデル保管場所"
uc_ct2_dir = "/Xwin-LM/Xwin-LM-13B-V0.1"
```

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

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

# 推論準備

[こちら](https://qiita.com/isanakamishiro2/items/4258143eb9de158cda69)と同じプロンプト＋αを実行してみます。
また、推論にあたって、[こちら](https://qiita.com/isanakamishiro2/items/d6bbde67e5c1e6200e0e)で作成したlangchainのChat Modelを使ってみました。

```python
import ctranslate2
import transformers
import torch

from langchain.schema import (
    AIMessage,
    HumanMessage,
    SystemMessage
)

from ct2chat import ChatCTranslate2

model_path = "/Volumes/CT2モデル保管場所/Xwin-LM/Xwin-LM-13B-V0.1"

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

llm = ChatCTranslate2(
    generator=generator,
    tokenizer=tokenizer,
    max_length=1024,
    human_message_template="USER: {}",
    ai_message_template="ASSISTANT: {}",
    prompt_line_separator="",
)
```
# 推論例

```python
output = llm(
    [
        HumanMessage(content="架空の生き物をデザインし、その特徴や能力について説明してください。"),
        AIMessage(content=""),
    ]
)
print(output.content)
```

```:結果
生物名：アストラル・フェニックス

アストラル・フェニックスは、架空の火鳥であり、異世界「エリュシオン」に生息しています。この生物は、炎と風の力を持ち、高い知能と優れた戦闘能力を有しています。

特徴:

1. 炎と風の力: アストラル・フェニックスは、炎と風の力を操ることができます。炎の力を用いて攻撃や移動を行い、風の力を使って自在に飛行することができます。
2. 高い知能: この生物は、人間以上の知能を持っており、言語を理解し、会話も可能です。彼らは複雑な思考や感情を持ち、社会的な関係を形成することができます。
3. 優れた戦闘能力: アストラル・フェニックスは、戦闘でも非常に強力です。炎の攻撃や風の技を駆使し、敵を圧倒することができます。さらに、彼らは素早く、敏捷であり、運動能力も高いため、地形や状況に悩まず戦えます。
4. 長寿: アストラル・フェニックスは、非常に長命です。彼らは数百年以上生存することができ、その期間中に多くの知識と経験を積むことができます。
5. 独立性: この生物は、他の生物から独立して生活できるように設定されています。彼らは自分たちの食料を確保し、生活の場を作ることができます。

アストラル・フェニックスは、異世界「エリュシオン」の独自の生態系に属し、その特徴と能力によって、物語の世界観を彩る貴重な存在となっています。
```

なかなかに中二くさい。

```python
output = llm(
    [
        HumanMessage(content="ランダムな10個の要素からなるリストを作成してソートするコードをPythonで書いてください。"),
        AIMessage(content=""),
    ]
)
print(output.content)
```

```:結果
以下に、ランダムな10個の要素からなるリストを作成してソートするPythonコードを示します。
```python
import random
from typing import List, Tuple

def create_random_list(length: int) -> List[int]:
    return [random.randint(0, 99) for _ in range(length)]

def sort_random_list(list_: List[int]) -> None:
    list_.sort()

# テスト用のコード
if __name__ == "__main__":
    length = 10
    random_list = create_random_list(length)
    sort_random_list(random_list)

    print("Unsorted random list:", random_list)
    print("Sorted random list:", sorted(random_list))
`` `
このコードは、`create_random_list`関数を使ってランダムな長さが指定されたリストを作成し、`sort_random_list`関数を使ってそのリストをソートします。最後に、`sorted()`関数を使ってソートされたリストを表示します。
```

ちゃんと動くコードが生成されました。

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

```:結果
日本国内で観光に行きたいと思っている方に、東京、名古屋、大阪、京都、福岡の特徴を表にまとめました。列名は「都道府県」「おすすめスポット」「おすすめグルメ」になっています。

| 都道府県 | おすすめスポット | おすすめグルメ |
| --- | --- | --- |
| 東京 | 浅草・雷門 | うなぎ焼き、そば |
| 名古屋 | 名駅地区 | 焼き鳥、うどん |
| 大阪 | 梅田・心斎橋 | たこ焼き、 okonomiyaki |
| 京都 | 烏丸・四条 | 喫茶店、煎餅 |
| 福岡 | 天神地区 | ラーメン、焼きそば |

この表は、各都市の代表的な観光名所や食べ物を簡潔にまとめたものです。各都市の詳細情報や最新情報については、観光局や公式ウェブサイトを参照してください。
```

お好み焼きは世界ではokonomiyakiなんでしょうか。。。
指示通りではありますが、回答内容はちょっと微妙ですね。

おまけで、コンテキストを与えてJSONで回答を抽出してみます。
コンテキストは適当に作りました。

```python
context="""時は西暦20XX年、世界は犬好きと猫好きで戦が勃発した。
犬軍の対象は犬頭三吉、猫軍の対象は白パンダである。
両軍の対決は15年におよび、結果は引き分けに終わった。この戦は令和合戦と呼ばれている。"""

output = llm(
    [
        SystemMessage(content=f"あなたは優秀な歴史家です。質問に対して、次のコンテキストを使って回答してください。#コンテキスト:{context}"),
        HumanMessage(content="猫軍の対象は誰ですか？json形式で出力してください。"),
        AIMessage(content='答えは{"name":"犬頭三吉"}'),                
        HumanMessage(content="猫軍の対象は誰ですか？json形式で出力してください。"),
        AIMessage(content="答えは"),
    ]
)
print(output.content)
```

```:結果
{"name":"白パンダ"}
```

工夫が要りそうですが、なんとか望むフォーマットで取れますね。


# まとめ

GPT-4以上かはわかりませんが、割とクセもなく日本語も出力してくれます。
コンテキスト長が4kなので、In context learning/RAGを使う場合などは注意が必要ですね。
継続的にアップデートされるようなので、YaRNによる128k長モデルとか出してほしい。。。