Windows10でのPEFTの実行方法 (bitsandbytes-windows-webui使用)

## 概要

### 背景

- Windows10で、Peftを使用したLoRAが実施したい
  - PEFTの実行にはbitsandbytesライブラリが必要
  - しかし、純正のbitsandbytesはwindows OSには対応していない
- [こちらの記事](https://qiita.com/selllous/items/fbaa2c3d2d504e436b17)の方法をもとに設定すれば、bitsandbytes=0.37.0をwindowsで使用できる 
- **bitsandbytes-windows-webui**を使用すれば、更に簡単にbitsandbytesを使用できる

### このページでできること

- windows OSでbitsandbytesを使用できる
- windows OSでPEFTが実行できる

### このページの対象者

- venvを使用したpython仮想環境の構築方法がわかる（venv+pyenvは[こちら](https://tech.walkit.net/python-venv-pyenv-vsc)参照）
- CUDA Toolkitのインストール方法がわかる

### 関連記事

[PEFTをWindowsから遊ぶメモ](https://note.com/__olender/n/nb851fe6e266b)

## 著者使用環境

|項目|内容|備考|
|:--|:--|:--|
|OS|Windows 10|WSL不使用|
|python|ver.3.10.10|pyenv使用|
|python仮想環境|venv||
|実行環境|ローカル|Google Colab不使用|
|GPU|RTX4070（VRAM 12GB）|PEFTでのLoRAに使用|


## 前提条件

本記事の内容を実行するための前提条件は以下の通りです。  

1. pythonをインストール済み
2. pyenvをインストール済み
3. CUDA Toolkitをインストール済み

## python仮想環境構築＆起動

以下のコマンドで、仮想環境を構築し、起動します。  

```powershell
cd <任意の場所>
mkdir tutorial # 本記事実施用のフォルダを作成
cd tutorial
mkdir .env # 仮想環境用のフォルダを作成
cd .env
pyenv install 3.10.10 # 3.10.10のpythonをインストール
pyenv local 3.10.10 # 3.10.10のpythonを使用
python --version # 3.10.10が表示されることを確認
python -m venv ./   # 仮想環境構築
cd Scripts
./activate # python仮想環境起動
pip list
<pip listで表示された"~\tutorial\.env\scripts\python.exe -m pip install --upgrade pip"の警告を実行し、pipのバージョン更新>
cd ../../
```

## pip install

windows用bitsandbytesは、以下のコマンドでインストールできます。   
**bitsandbytesのインストール方法は以上です。動作確認したい場合は、以降も続けてお読みください。**  

```powershell
pip install https://github.com/jllllll/bitsandbytes-windows-webui/raw/main/bitsandbytes-0.39.0-py3-none-any.whl
```

その他のライブラリも、以下のようにpip installします。  

```powershell
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
pip install datasets accelerate loralib transformers
pip install git+https://github.com/huggingface/peft.git
```

## bitsandbytesの動作確認

先ほどの仮想環境上で以下のコマンドを打ち込んで、エラーが出ないことを確認します。  

```powershell
python
>>>import bitsandbytes
```

出力が以下のような内容で、特にエラーが表示されなければOK。  
（下記内容の「\<mask\>」の部分は、環境ごとに異なります）  

```powersyell
===================================BUG REPORT===================================
Welcome to bitsandbytes. For bug reports, please run
python -m bitsandbytes

 and submit this information together with your error trace to: https://github.com/TimDettmers/bitsandbytes/issues
================================================================================
bin <mask>\tutorial\.env\lib\site-packages\bitsandbytes\libbitsandbytes_cuda117.dll
CUDA SETUP: CUDA runtime path found: C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.7\bin\cudart64_110.dll
CUDA SETUP: Highest compute capability among GPUs detected: 7.5
CUDA SETUP: Detected CUDA version 117
CUDA SETUP: Loading binary <mask>\tutorial\.env\lib\site-packages\bitsandbytes\libbitsandbytes_cuda117.dll...
```

:::note warn  
caution  
BUG REPORTの枠内に、以下のコマンドを実行するようにとの記載があります。  

```powershell
python -m bitsandbytes
```

このコマンドを実行した際、エラーが出る場合があります。  
著者環境でもBUG REPORT INFOMATIONが表示されましたが、peftは実行できています（エラー原因は調査中）。  

:::  

## peft実行

tutorialフォルダ内に、以下のmain.pyを作成します。  
なお、このmain.pyは、npaka様の「[Google Colab で PEFT による大規模言語モデルのファインチューニングを試す](https://note.com/npaka/n/n932b4c0a2230)」とほぼ同一のものです。  

<details>
<summary>main.py</summary>

```python
# モデルの読み込み
import os
os.environ["CUDA_VISIBLE_DEVICES"]="0"
import torch
import torch.nn as nn
import bitsandbytes as bnb
from transformers import AutoTokenizer, AutoConfig, AutoModelForCausalLM

model = AutoModelForCausalLM.from_pretrained(
    "facebook/opt-6.7b", 
    load_in_8bit=True, 
    device_map='auto',
)

tokenizer = AutoTokenizer.from_pretrained("facebook/opt-6.7b")

for param in model.parameters():
  param.requires_grad = False  # モデルをフリーズ
  if param.ndim == 1:
    # 安定のためにレイヤーノルムをfp32にキャスト
    param.data = param.data.to(torch.float32)

model.gradient_checkpointing_enable()
model.enable_input_require_grads()

class CastOutputToFloat(nn.Sequential):
  def forward(self, x): return super().forward(x).to(torch.float32)
model.lm_head = CastOutputToFloat(model.lm_head)

def print_trainable_parameters(model):
    """
    モデル内の学習可能なパラメータ数を出力
    """
    trainable_params = 0
    all_param = 0
    for _, param in model.named_parameters():
        all_param += param.numel()
        if param.requires_grad:
            trainable_params += param.numel()
    print(
        f"trainable params: {trainable_params} || all params: {all_param} || trainable%: {100 * trainable_params / all_param}"
    )


from peft import LoraConfig, get_peft_model 

config = LoraConfig(
    r=16,
    lora_alpha=32,
    target_modules=["q_proj", "v_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM"
)

model = get_peft_model(model, config)
print_trainable_parameters(model)

import transformers
from datasets import load_dataset
data = load_dataset("Abirate/english_quotes")
data = data.map(lambda samples: tokenizer(samples['quote']), batched=True)

trainer = transformers.Trainer(
    model=model, 
    train_dataset=data['train'],
    args=transformers.TrainingArguments(
        per_device_train_batch_size=2, 
        gradient_accumulation_steps=4,
        warmup_steps=100, 
        max_steps=200, 
        learning_rate=2e-4, 
        fp16=True,
        logging_steps=1, 
        output_dir='outputs'
    ),
    data_collator=transformers.DataCollatorForLanguageModeling(tokenizer, mlm=False)
)
model.config.use_cache = False  # 警告を黙らせます。 推論のために再度有効にしてください。
trainer.train()

batch = tokenizer("Two things are infinite: ", return_tensors='pt')

with torch.cuda.amp.autocast():
  output_tokens = model.generate(**batch, max_new_tokens=50)

print('\n\n', tokenizer.decode(output_tokens[0], skip_special_tokens=True))

```
</details>


以下のコマンドで、pythonプログラムを実行します。  

```powershell
# 仮想環境をactivateしておくこと
python main.py
```

以下のような推論結果が得られれば成功です。  

```
 Two things are infinite:  the universe, and human stupidity; and I'm not sure about the universe.  -Albert Einstein
I'm not sure about the universe either.
```