メインコンテンツへスキップ
見出し画像

Llamma-65bなどの大規模言語モデルをQLoRAでファインチューニング

    概要

    先日、llmtuneというライブラリを試しましたが、本記事のライブラリが色々な意味で、上位互換な印象です。

    インストール

    miniconda環境で構築しました。

    conda create -n gua
    conda activate gua
    conda install pip


    レポジトリのクローン

    git clone https://github.com/artidoro/qlora

    pip

    cd qlora
    pip install -U -r requirements.txt

    サンプルコードの実行

    たくさんのサンプルコードがありました。

    Jupyter-notebookでの 7bの実行
    examples/guanaco_7B_demo_colab.ipynb を実行すればOKです。 (colabでも動きそうですね)

    実行すると、web uiでchatができました。日本であまり話題にならないだけあって、日本語力の低さが際立ちます。

    画像
    日本語は壊滅的でした


    画像
    英語は喋れます

    各種モデルのトレーニング

    scriptsフォルダに色々とサンプルコードが入っていました。
    oasst1というデータセットを学習するようです(約85k行)。

    そのまま実行しても、errorが出ました。
    AttributeError: /home/user/miniconda3/envs/gua/lib/python3.11/site-packages/bitsandbytes/libbitsandbytes_cpu.so: undefined symbol: cquantize_blockwise_fp16_nf4

    GPUではなくcpuで動かそうとしていることが原因のようです。cuda toolkitをinstallすると解決できました。

    conda install -c nvidia cuda-toolkit

    例えば、7bをfine tuningする際は以下のコマンドになります

    sh scripts/finetune_guanaco_7b.sh 

    RTX-3090でのgpu使用率は以下の感じです。メモリは余裕で余っています。
    演算自体は、どちらか1枚のgpuで行っている印象です。

    画像

    140W制限※で、学習に27時間ほどかかるようでした。
    (※手持ちのマシンをフル出力で動かすと、放熱が不十分でプロセスがzombie化します)

    65bのファインチューニング

    実行コード

    sh scripts/finetune_guanaco_65b.sh 

    学習開始まで、3分ほどかかった気がします。
    RTX-3090 x2で動きました。
    Falcontuneとかはmulti gpu非対応なので、ありがたい仕様です。

    画像

    学習完了には230時間ほど、必要なようです。流石の65bです。
    H100の購入を検討したくなる計算量です。

    画像

    オリジナルデータのqlora

    自作のdatasetで試してみます。
    まずは、適当にデータセットを作ります。
    何件かのデータを、databricks-dolly-15k-jaからデータを引っ張ってきました

    test.json

    [
        {
            "input": "ヴァージン・オーストラリア航空はいつから運航を開始したのですか?ヴァージン・オーストラリア航空(Virgin Australia Airlines Pty Ltd)はオーストラリアを拠点とするヴァージン・ブランドを冠する最大の船団規模を持つ航空会社です。2000年8月31日に、ヴァージン・ブルー空港として、2機の航空機、1つの空路を運行してサービスを開始しました。2001年9月のアンセット・オーストラリア空港の崩壊後、オーストラリアの国内市場で急速に地位を確立しました。その後はブリスベン、メルボルン、シドニーをハブとして、オーストラリア国内の32都市に直接乗り入れるまでに成長しました。",
            "output": "ヴァージン・オーストラリア航空は、2000年8月31日にヴァージン・ブルー航空として、2機の航空機で単一路線の運航を開始しました。"
        },
        {
            "input": "魚の種類はどっち?イコクエイラクブカとロープ",
            "output": "イコクエイラクブカ"
        },
        {
            "input": "ラクダはなぜ水なしで長く生きられるのか?",
            "output": "ラクダは、長時間にわたってエネルギーと水分で満たされた状態を保つために、腰の脂肪を利用しています。"
        },
        {
            "input": "アリスの両親には3人の娘がいる:エイミー、ジェシー、そして三女の名前は?",
            "output": "三女の名前はアリス"
        },
        {
            "input": "小森田友明はいつ生まれたの?小森田は1981年7月10日、熊本県に生まれる。高校卒業後、2000年にJ1リーグのアビスパ福岡に入団。2001年にMFとしてデビューするも、出番は少なく、2001年シーズン終了後にJ2リーグに降格する。2002年、J2の大分トリニータに移籍。守備的MFとしてレギュラーに定着し、2002年に優勝、2003年に昇格を果たす。2005年まで多くの試合に出場した。2005年9月、J2のモンテディオ山形に移籍。2006年、J2のヴィッセル神戸へ移籍。守備的ミッドフィルダーとしてレギュラーになったものの、夏には徐々に出番が少なくなっていった。2007年、地元に本拠地を置く日本フットボールリーグのロッソ熊本(後のロアッソ熊本)へ移籍。レギュラーとして活躍し、2008年にはクラブはJ2に昇格した。その後は出場機会は限られたものの、多くの試合に出場した。2010年、インドネシアに渡り、ペルセラ・ラモンガンに移籍。2010年7月、日本に戻り、J2のギラヴァンツ北九州に入団。2012年に引退するまで、守備的ミッドフィルダーやセンターバックとして多くの試合に出場した。",
            "output": "小森田友明は1981年7月10日に生まれました。"
        }
    ]


    evalの設定にちょっと躓いたので、evalなしで実行します。
    (eval関係のcommandが残ってます)

    dataset_path=dataset/test.json
    eval_dataset_size=1
    python qlora.py \
        --model_name_or_path huggyllama/llama-7b \
        --output_dir ./output/test \
        --logging_steps 10 \
        --save_strategy steps \
        --data_seed 42 \
        --save_steps 10 \
        --save_total_limit 40 \
        --evaluation_strategy steps \
        --eval_dataset_size $eval_dataset_size\
        --max_eval_samples 1000 \
        --per_device_eval_batch_size 1 \
        --max_new_tokens 32 \
        --dataloader_num_workers 3 \
        --group_by_length \
        --logging_strategy steps \
        --remove_unused_columns False \
        --do_train \
        --lora_r 64 \
        --lora_alpha 16 \
        --lora_modules all \
        --double_quant \
        --quant_type nf4 \
        --bf16 \
        --bits 4 \
        --warmup_ratio 0.03 \
        --lr_scheduler_type constant \
        --gradient_checkpointing \
        --dataset $dataset_path \
        --dataset_format input-output \
        --source_max_len 16 \
        --target_max_len 512 \
        --per_device_train_batch_size 1 \
        --gradient_accumulation_steps 16 \
        --max_steps 1875 \
        --eval_steps 187 \
        --learning_rate 0.0002 \
        --adam_beta2 0.999 \
        --max_grad_norm 0.3 \
        --lora_dropout 0.1 \
        --weight_decay 0.0 \
        --seed 0
    

    max_steps=1875のままで動かしてみます。
    今回はデータ数が少ないこともあり、すぐにlossがゼロになったので、途中で止めました。

    画像

    Loraモデルの動作確認

    jupyterで動かします。
    まずは、LoRA前の応答を見てみます。

    import torch
    from peft import PeftModel
    from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
    
    #load base model
    
    model_id = "huggyllama/llama-7b"
    bnb_config = BitsAndBytesConfig(
        load_in_4bit=True,
        bnb_4bit_use_double_quant=True,
        bnb_4bit_quant_type="nf4",
        bnb_4bit_compute_dtype=torch.bfloat16
    )
    
    tokenizer = AutoTokenizer.from_pretrained(model_id)
    model = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=bnb_config, device_map={"":0})
    
    model.eval()
    
    text = "#Q: ラクダはなぜ水なしで長く生きられるのか? #A"
    device = "cuda:0"
    inputs = tokenizer(text, return_tensors="pt").to(device)
    
    with torch.no_grad():
      outputs = model.generate(**inputs, max_new_tokens=256)
    print(tokenizer.decode(outputs[0], skip_special_tokens=True))

    回答はこちら

    #Q: ラクダはなぜ水なしで長く生きられるのか?
    #A: ラクダは、水を含む物質を含むことができる。 そのため、ラクダは、水を含む物質を含むことができる。 そのため、ラクダは、水を含む物質を含むことができる。 そのため、ラクダは、水を含む物質を含むことができる。 そのため、ラクダは、水を含む物質を含むことができる。 そのため、ラクダは、水を含む物質を含むことができる。 そのため、ラクダは、水を含む物質を含むことができる。 そのため、ラクダは、水を含む物質を含むことが

    ※ベースがllammaで、instructionの学習をしていないこと、さらに日本語をちゃんと勉強していないようなので、おかしな回答になっています

    QLoRAの推論

    
    # apply peft model
    peft_name = "guanaco/qlora/output/test/checkpoint-50"
    model = PeftModel.from_pretrained(
        model, 
        peft_name, 
        device_map={"":0}
    )
    model.eval()
    
    text = "#Q: ラクダはなぜ水なしで長く生きられるのか? #A"
    device = "cuda:0"
    inputs = tokenizer(text, return_tensors="pt").to(device)
    
    with torch.no_grad():
      outputs = model.generate(**inputs, max_new_tokens=256)
    print(tokenizer.decode(outputs[0], skip_special_tokens=True))

    回答はこちら
    #Q: ラクダはなぜ水なしで長く生きられるのか?
    #A: ラクダは、2016年7月31日に生まれました。

    意味不明ですが、学習データセットの「 "output": "小森田友明は1981年7月10日に生まれました。"」に引っ張られたのだと思います。
    学習自体は成功しているものと思われます。

    まとめ

    • QLoRAで65bレベルのLLMを、24GB x2 のVRAMなどで学習可能でした

    • 一般家庭用のコンピュータで65bレベルをチューニング・推論できるのは素晴らしいことだと思います

    • llamaはあまりinstructionされていなかったり、日本語が微妙なので、fine tuningしたいデータの量、質によっては、予めinstruciton済みのモデルをfine tuningした方が良いかもしれません


     
     
     
    化学・大規模言語モデル・ロボットなど 記事はメモ書きですのでご了承ください

    あなたへのおすすめ