N番煎じでMicrosoftのPhi-2をDatabricksで動かす

mixtralはAutoAWQかExLlamaV2が対応されたら試そうと思います。

# 導入

MicrosoftからPhi-2というLLMが公開されました。ライセンスはMicrosoftリサーチライセンスです。

https://huggingface.co/microsoft/phi-2

[Microsoft Ignite 2023](https://news.microsoft.com/ignite-2023/)でも発表されていたのですが、2.7Bという小さいパラメータ数にも関わらず、Llama 13B以上の性能を発揮し、特にMathやコード生成の分野で優秀な能力を持っています。

以下のように、既にGigazineさんで取り上げられたり、大先生も試されていますが、気にせず試してみようと思います。
それがN番煎じシリーズ。

https://gigazine.net/news/20231213-microsoft-phi-2-small-language-models/

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


今回は小パラメータのモデルということもあり、CPUでの速度を見たかったので、CPUクラスタで動かします。

# Step1. 準備

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

```python
%pip install -U -qq transformers sentencepiece accelerate bitsandbytes einops

dbutils.library.restartPython()
```

それからモデルをダウンロード。UnityCatalogのボリュームに保管します。

```python
def download_model(model_id:str):
    import os
    from huggingface_hub import snapshot_download

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

    local_dir = f"/tmp/{model_id}"
    uc_dir = f"/models--{model_id.replace('/', '--')}"

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

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

model_id = "microsoft/phi-2"
download_model(model_id)
```

# Step2. モデルのロード

transformersを使ってモデルを読み込みます。
CPUで利用するため、dtypeはtorch.float32を指定しています。

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

model_path = "/Volumes/training/llm/model_snapshots/models--microsoft--phi-2"

# CPUでの推論用
model = AutoModelForCausalLM.from_pretrained(model_path, torch_dtype=torch.float32, device_map="cpu", trust_remote_code=True)
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
```

# Step3. 推論その1

まずは公式サンプルを実行してみます。

```python
inputs = tokenizer('''def print_prime(n):
   """
   Print all primes between 1 and n
   """''', return_tensors="pt", return_attention_mask=False)

outputs = model.generate(**inputs, max_length=200)
text = tokenizer.batch_decode(outputs)[0]
print(text)
```

~~~:出力
def print_prime(n):
   """
   Print all primes between 1 and n
   """
   for i in range(2, n+1):
       for j in range(2, i):
           if i % j == 0:
               break
       else:
           print(i)

print_prime(20)
```

## Exercises

1. Write a Python function that takes a list of numbers and returns the sum of all even numbers in the list.

```python
def sum_even(numbers):
    """
    Returns the sum of all even numbers in the list
    """
    return sum(filter(lambda x: x % 2 == 0, numbers))

print(sum_even([1, 2, 3, 4, 5, 6])) # Output: 12
```

2. Write a Python function that takes
~~~

ちなみに、生成したコード(print_prime)はちゃんと動きました。

# Step4. 推論その2

日本語指定でコード生成できるかやってみます。

```python
inputs = tokenizer('''def print_fibonacci(n):
   """
   フィボナッチ数列をnまで表示する
   """''', return_tensors="pt", return_attention_mask=False)

outputs = model.generate(**inputs, max_length=200)
text = tokenizer.batch_decode(outputs)[0]
print(text)
```

~~~:出力
def print_fibonacci(n):
   """
   フィボナッチ数列をnまで表示する
   """
   a, b = 0, 1
   for i in range(n):
      print(a, end=' ')
      a, b = b, a+b
   print()

def print_fibonacci_recursive(n):
   """
   フィボナッチ数列をnまで表示する
   """
   if n <= 1:
      print(n, end=' ')
   else:
      print_fibonacci_recursive(n-1)
      print_fibonacci_recursive(n-2)
      print(n, end=' ')

def print_fibonacci
~~~

題材がよくあるもののためか、関数名の時点で明確なのかはわからないのですが、ちゃんと関数が生成できています。
ただ、print_fibonacci_recursiveは挙動がおかしいかな。

もう少し複雑な関数を作らせてみましょう。

# Step5. 推論その3

LightGBMのモデルを学習させる関数を作成させてみます。

```python
inputs = tokenizer('''def train_lightgbm_model(df):
   """
   Pandas Dataframeのデータを使ってlightgbmのモデルを構築する
   """''', return_tensors="pt", return_attention_mask=False)

outputs = model.generate(**inputs, max_length=512)
text = tokenizer.batch_decode(outputs)[0]
print(text)
```

```:出力
def train_lightgbm_model(df):
   """
   Pandas Dataframeのデータを使ってlightgbmのモデルを構築する
   """
   # データを学習データとテストデータに分割
   train_df, test_df = train_test_split(df, test_size=0.2)

   # データを学習データとテストデータに分割
   train_X = train_df.drop(['target'], axis=1)
   train_y = train_df['target']
   test_X = test_df.drop(['target'], axis=1)
   test_y = test_df['target']

   # モデルを作成
   model = lgb.LGBMClassifier()

   # モデルの構築
   model.fit(train_X, train_y)

   # モデルの推論
   predictions = model.predict(test_X)

   # モデルの損失を計算
   loss = model.score(test_X, test_y)

   return loss, predictions

# データの読み込み
df = pd.read_csv('data.csv')

# データの読み込み
df = pd.read_csv('data.csv')

# データの読み込み
df = pd.read_csv('data.csv')

# データの読み込み
df = pd.read_csv('data.csv')

# データの読み込み
df = pd.read_csv('data.csv')

# データの読み込み
df = pd.read_csv('data.csv')

# データの読み込�
```

train_lightgbm_model関数は動きそうなコードではありますが、sklernのモジュールをインポートしたりする必要がありそうです。あと、トレーニングしたモデルを返して欲しい（このあたりはきちんとdocstring内に記載すれば大丈夫だと思いますが）

# まとめ

軽くですが、Phi-2でコード生成を試してみました。
あまり得意ではないと思いますが、日本語指定でも動くコード生成は多少できそうです。

また、今回はCPUクラスタで試しましたが、実用的な速度を出す場合はGPUが必要かと思います。

しかしローカルLLMは小パラメータで優秀なモデルが次々と出てきたり、MixtralのようなMoEのLLMがオープンソースで出てきたりと、発展が目覚ましいですね。
記事にはしていませんが、プロプライエタリのLLMも発展がすさまじいので、競い合ってどちらも成長して行って欲しい。

2023年もそろそろ終わりですが、来年も面白い年になりそうです。
※　とか言ってたら、2023年中にまだまだ凄いニュースが出たりするんだろうなあ。