【QLoRA編】日本語LLMのファインチューニング & 低スペックのローカル環境のアプリで動かす

【QLoRA編】日本語LLMのファインチューニング & 低スペックのローカル環境のアプリで動かす

 

Contents - 目次(もくじ)

【動画で解説】QLoRA編:日本語LLMのファインチューニング & ローカル環境のアプリで実行
– Japanese LLM Fine Tuning Tutorial

 

 

【現代の魔法】QLoRA編
:日本語LLMのファインチューニング & ローカル環境アプリで動かす方法


視聴時間:53分16秒

文字情報だけではわかりにくい場合に、QLoRA編の日本語大規模言語モデルのファインチューニングの解説動画をご活用いただけますと幸いです。

 




 

【動画の内容:QLoRA編 日本語LLMのファインチューニング & ローカル環境アプリで使う方法】

0:00 はじめに
1:15 Google Colaboratoryの使い方
2:01 【ステップ1】ファインチューニング前のLLMで推論 編
9:32 【ステップ2】LLMのQLoRAファインチューニング 編
21:02 ファインチューニングのパラメータの設定
23:59 ファインチューニング後のLLMで推論
27:17 LLMを4-bit量子化&GGUF化(Llama.cppでLLMをGGUF形式のファイルに変換)
35:00 【ステップ3】ローカル環境でファインチューニングしたLLMを実行 編
41:18 ChatGPT代替アプリJanのパラメータ解説
47:08 ELYZAのLLMを使う方法
51:29 おわりに

 

 

unsloth編:LLMのファインチューニング

 

 

2023年の末からChatGPTをはじめとした大規模言語モデル(LLM:Large Language Model)を触り始めました。
その後、2024年1月には、日本語LLMのファインチューニング(再学習・微調整)について学んだ成果を以下のチュートリアル記事

日本語LLMのファインチューニング入門 – 自作・Hugging Face公開データセット対応 

にまとめさせていただきました。
LLMについては、右も左も分からない状況で既存の情報を調査し、手探りの状態のため、チュートリアルコードを作成した後に

ファインチューニングしたLLMをローカル環境で気軽に使うにはどうすればいいのだろうか?

という疑問が湧いてきました。

今回のチュートリアル作成時の目標としては

 

①自作のカスタムデータセットでオープンソースの日本語LLMをファインチューニングする

②ファインチューニングした日本語LLMをローカル環境でChatGPTのようなユーザーインターフェイス(UI)のアプリで動かす

③高スペックのGPU搭載パソコンの方はもちろんのこと、CPUだけの低スペックのパソコンでも楽しめる

 

ことです。
チュートリアル作成にあたっては

情報を調べては・・・・
コードを実行し・・・・
エラーの原因を探り・・・
時に諦め・・・
また、情報を調べ・・・

を繰り返し、今回の目標の達成に向けて日々調査・検証を繰り返していました。

 

最終的に分かったこととしては、現状(調査・検証を開始した2024年2月時点)で目標を達成するための方法/要件の一例としては

 

・方法/要件①
:LLMのファインチューニングはGoogle Colaboratoryで行う

・方法/要件②
:ファインチューニング後のLLMを「4-bit量子化」する

(CPUのみの低スッペックパソコンの場合)

・方法/要件③
:LLMを「GGUF形式」にする

* GGUF(GPT-Generated Unified Format) – Apple SiliconなどのCPUでもLLMを使えるようにした単一ファイルの形式。

・方法/要件④
:ローカル環境で「GGUF形式」のLLMを使えるアプリ(ソフトウェア)を使う

 

といったことのようでした。

 

上記の方法/要件を満たすために使えるものとしては

 

【方法/要件①②③を満たすLLMのファインチューニングのフレームワーク】

unslothai/unsloth | GitHub
*「QLoRA」(Quantized Low-Rank Adaptation) のファインチューニングができるフレームワーク
*QLoRA:LoRAに4-bitのNormalFloat(NF4)などの量子化を組み合わせた場合には「QLoRA」と呼ぶ

【方法/要件④を満たすローカル環境で使える対話型生成AIアプリ】
Jan:Open-source ChatGPT alternative(AGPLv3 License.)| Jan AI
*Windows・Mac対応

 

があることが分かりましたので、今回のチュートリアルでは

 

・「unsloth」のフレームワークを使いGoogle Colaboratoryで日本語LLMのファインチューニングを行う

・「Jan」というアプリを活用してQLoRAでファインチューニングしたLLMを動かす

 

方法を採用しています。
チュートリアルコード作成にあたっては、自作のカスタムデータセットをCSV形式で読み込む際に、どのライブラリを使えばいいのか?に関して難渋しました…
尚、チュートリアル作成時点で試せた範囲の情報では、Google Colaboratoryの無料プランで使える「T4 GPU」を活用して

・7B(70億パラメータ)

のLLMを「QLoRA」でファインチューニングできることを確認しています。

 

ローカル環境でLLMを実行したい方のニーズとしては

・ゆくゆくは、自分の所属する業界で、ビジネス上で活用したい!
・自分の好きなようにLLMを楽しみたい!

などと、ChatGPTをはじめとした

・AIサービス提供者から切り離された状態で、対話型生成AIを使いたい

といったことではないかと思います。
一連の情報が、LLMを自分好みにカスタマイズして活用したいと考えている方が、既存のLLMのカスタマイズし、今後の実践利用に向けて検証するために試行錯誤を始めるきっかけになることがありましたら幸いです。

 

 

チュートリアルコードリンク・プログラムのライセンス

 

 

Google Colaboratoryのチュートリアルコード:
QLoRA-LLM-FineTuning-for-Japanese-AI-Beginners.ipynb(Apache-2.0 license)| Google Colaboratory

 

チュートリアルコード「QLoRA-LLM-FineTuning-for-Japanese-AI-Beginners.ipynb」のライセンス:

Apache-2.0 license

©︎ 2024 child programmer

 

 

チュートリアルで使う自作用カスタムデータセットのテンプレートをダウンロード

 

 

オリジナルデータセット対応版のチュートリアルコードを使う方用です。
データセットのテンプレートをエクセル(Windowsの方)やNumbers(Macの方)で編集後に、

・dataset
(dataset.csv)

という名前のCSV形式ファイルで書き出してください。


*ダウンロードされる圧縮ファイルを解凍するとCSV形式ファイルが編集できるようになります。

 

チュートリアルで利用しているサンプルデータセットも公開しています。
ご自身のオリジナルデータセットで学習前に、必要に応じて練習用としてご活用いただけますと幸いです。
ダウンロード時点のファイル名は

・sample_dataset
(sample_dataset.csv)

になっていますが、利用する際には

・dataset
(dataset.csv)

という名前に変更してご利用ください。


*ダウンロードされる圧縮ファイルを解凍するとCSV形式ファイルが編集できるようになります。

 

 

【unsloth編】
QLoRA:日本語LLMのファインチューニング – 最終更新:2024年4月5日

 

 

ローカル環境で使うために、日本語LLMのファインチューニングし「GGUF形式」で保存してみましょう

 

 

【ステップ1】
ファインチューニング前のLLMで推論 編

 

 

後で回答を変化を確認しやすいように、ファインチューニング前のLLMで推論してみましょう。
チュートリアルでは

・「指示チューニング」
(LLMの基盤モデルに質問と回答を学習させた事前学習済みモデル)

・「人間のフィードバックからの強化学習」
(RLHF:Reinforcement Learning from Human Feedback)

されている

tokyotech-llm/Swallow-7b-instruct-hf | Hugging Face
*Llama2の日本語能力強化版のSwallowの7B(70億パラメータ)版の指示チューニングLLM
*「東京工業大学情報理工学院の岡崎研究室/横田研究室」「国立研究開発法人産業技術総合研究所」の研究チームにより開発されたLLM
注:使用するLLMによって、実行コードを微調整する必要があります。
コードの書き方は、Hugging Face内のそれぞれのLLMのページでご確認ください。

を活用して、ファインチューニング前後の変化を比較していきます。

 

 

【事前準備①】unslothをクローン&依存関係のインストール

 

 

実行コード

# %%capture
import torch
major_version, minor_version = torch.cuda.get_device_capability()
# Must install separately since Colab has torch 2.2.1, which breaks packages
!pip install "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git"
if major_version >= 8:
    # Ampere・Hopperなどの新型GPU (RTX 30xx, RTX 40xx, A100, H100, L40)の場合に以下を実行
    !pip install --no-deps packaging ninja einops flash-attn xformers trl peft accelerate bitsandbytes
else:
    # 旧型のGPU (V100, Tesla T4, RTX 20xx)の場合に以下を実行
    !pip install --no-deps xformers trl peft accelerate bitsandbytes
pass

 

出力例

Collecting unsloth[colab-new]@ git〜

〜

Successfully built unsloth
Installing collected packages: xxhash, unsloth, shtab, docstring-parser, dill, multiprocess, tyro, datasets
Successfully installed datasets-2.18.0 dill-0.3.8 docstring-parser-0.16 multiprocess-0.70.16 shtab-1.7.1 tyro-0.7.3 unsloth-2024.3 xxhash-3.4.1

〜

Installing collected packages: bitsandbytes, xformers, trl, peft, accelerate
Successfully installed accelerate-0.28.0 bitsandbytes-0.43.0 peft-0.9.0 trl-0.7.11 xformers-0.0.25

 

 

【事前準備②】LLMをダウンロード

 

 

LLMの一例

# 日本語継続事前学習済みモデル – 基盤モデル(2023年12月公開)
tokyotech-llm/Swallow-7b-hf

#「Swallow-7b-hf」に比べて、より多くの日本語トークンで学習された
# 日本語継続事前学習済みモデル – 基盤モデル(2024年3月公開)
tokyotech-llm/Swallow-7b-plus-hf

#「Mistral-7B-v0.1」で学習された
# 日本語継続事前学習済みモデル – 基盤モデル(2024年3月公開)
# 7Bモデルだが、Swallow 13Bに迫る日本語性能とのこと
tokyotech-llm/Swallow-MS-7b-v0.1

#「Swallow-7b-hf」の指示チューニングモデル(2023年12月公開)
tokyotech-llm/Swallow-7b-instruct-hf

今回は、ローカル環境でファインチューニング後のLLMを使うために「instruction」「input」「output」からなるAlpaca形式で学習された指示チューニングされた「Swallow-7b-instruct-hf」のLLMを利用します。

【Hugging Faceで現在公開されている日本語LLMをチェック】

キーワード「japanese」(トレンド順)- Models | Hugging Face

【補足説明】

モデル名で散見する「b」というのはモデルのパラメータ(重み)が「billion:10億」という意味です。
*1.4B:14億パラメータ
*1.7B:17億パラメータ
*3.6B:36億パラメータ
*7B:70億パラメータ
*13B:130億パラメータ
*70B:700億パラメータ

試した範囲では、東京大学・松尾研究室発のAIカンパニーからリリースされていることで有名なLlama形式で学習されているELYZA(イライザ)の

elyza/ELYZA-japanese-Llama-2-7b-fast-instruct | Hugging Face

のLLMの場合には、このノートブックでもファインチューニングして推論までできましたが、ローカル環境のアプリで動かそうとするとエラー(アプリがクラッシュ)となるようでした。

(LLMのダウンロードに3〜16分ほどかかります)

実行コード

# LLMダウンロード
from unsloth import FastLanguageModel
import torch

# 最大シーケンス長の指定
# 内部でRoPEスケーリングを自動サポート
max_seq_length = 2048
# データ型dtypeの指定。「None」で自動検出
dtype = None
# 4-bit量子化。メモリ使用量を減らすために4-bit量子化を使用する場合には「True」。「False」でもよい。
load_in_4bit = True

# 英語版ですが以下の4-bit量子化モデルも指定できるようです
# その他の4-bit量子化のLLMは https://huggingface.co/unsloth へ
fourbit_models = [
    "unsloth/mistral-7b-bnb-4bit",
    "unsloth/mistral-7b-instruct-v0.2-bnb-4bit",
    "unsloth/llama-2-7b-bnb-4bit",
    "unsloth/llama-2-13b-bnb-4bit",
    "unsloth/codellama-34b-bnb-4bit",
    "unsloth/tinyllama-bnb-4bit",
]

# LLMの指定など
model, tokenizer = FastLanguageModel.from_pretrained(
    # @markdown  ファインチューニングを実行するLLMを指定します。
    model_name = "tokyotech-llm/Swallow-7b-instruct-hf" # @param {type:"string"}
    ,
    max_seq_length = max_seq_length,
    dtype = dtype,
    load_in_4bit = load_in_4bit,
    #「hf」のモデルで利用。その他の場合にはコメントアウト「#」などで無効化
    #「hf:RLHF - Reinforcement Learning from Human Feedback
    # 人間のフィードバックからの強化学習
    token = "hf_...",
)

 

出力例

config.json: 100%
 721/721 [00:00 00:00, 51.5kB/s]

==((====))==  Unsloth: Fast Llama patching release 2024.3
   \\   /|    GPU: Tesla T4. Max memory: 14.748 GB. Platform = Linux.
O^O/ \_/ \    Pytorch: 2.2.1+cu121. CUDA = 7.5. CUDA Toolkit = 12.1.
\        /    Bfloat16 = FALSE. Xformers = 0.0.25. FA = False.
 "-____-"     Free Apache license:〜

model.safetensors.index.json: 100%
 23.9k/23.9k [1.57MB/s]
Downloading shards: 100% 3/3 [01:32<00:00, 30.52s/it]
model-00001-of-00003.safetensors: 100% 4.94G/4.94G [00:26 00:00, 234MB/s]
model-00002-of-00003.safetensors: 100% 4.95G/4.95G [223MB/s]
model-00003-of-00003.safetensors: 100% 3.77G/3.77G [160MB/s]
Loading checkpoint shards: 100% 3/3 [20.84s/it]
generation_config.json: 100% 203/203 [14.3kB/s]
tokenizer_config.json: 100% 772/772 [43.5kB/s]
tokenizer.model: 100% 914k/914k [35.8MB/s]
special_tokens_map.json: 100% 411/411 [26.2kB/s]

よく見ると、ナマケモノ(sloth)も出力されていますね!

 

 

推論①:指示形式

 

 

実行コード

alpaca_prompt = """以下に、あるタスクを説明する指示があり、それに付随する入力が更なる文脈を提供しています。リクエストを適切に完了するための回答を記述してください。\n\n

### Instruction:
{}

### Input:
{}

### Response:
{}"""

# 通常の2倍のスピードで推論を実行
FastLanguageModel.for_inference(model)
# @markdown 推論をするには、以下のプロンプトを入力後にコードを実行します。
input_prompt = "こんにちは。" # @param {type:"string"}
inputs = tokenizer(
[
    alpaca_prompt.format(
        # instruction:プロンプトとして活用しています。
         input_prompt,
        # input:必要に応じて入力します。
        "",
        # output:文章を生成するにはここを空欄にします。
        "",
    )
], return_tensors = "pt").to("cuda")

from transformers import TextStreamer
text_streamer = TextStreamer(tokenizer)
_ = model.generate(**inputs, streamer = text_streamer, max_new_tokens = 64) #「max_new_tokens = 」の値で出力のトークン数を指定できます

 

出力例①(「こんにちは。」と入力した例)

以下に、あるタスクを説明する指示があり、それに付随する入力が更なる文脈を提供しています。リクエストを適切に完了するための回答を記述してください。



### Instruction:
こんにちは。

### Input:


### Response:
こんにちは、お客様。

ちょっと変な気もしますが、ファインチューニング前でもまずまずかと思います。
1つの知識だけを学ばすと、挨拶の時ですら学習した知識が出てしまう事象も経験しましたので、今回のファインチューニングでは、あえて挨拶も学習させます。

 

出力例①(「YouTubeのRehabC – デジタルで、遊ぶ。チャンネルについて教えてください。」と入力した例)

以下に、あるタスクを説明する指示があり、それに付随する入力が更なる文脈を提供しています。リクエストを適切に完了するための回答を記述してください。



### Instruction:
YouTubeのRehabC – デジタルで、遊ぶ。チャンネルについて教えてください。

### Input:


### Response:
RehabCは、2016年に開設されたYouTubeチャンネルです。このチャンネルは、ビデオゲーム、特にPCゲームを中心に、幅広いゲームに関するコンテンツを提供しています。RehabCのチャンネルには、ゲームプレイ、チュートリアル、ゲームの紹介、

間違いですね。正確には2014年に開設されたYouTubeチャンネルです。
また、コンテンツはゲーム系になっていますので、これも現時点(2024年3月時点)では間違いですね。

 

 

推論②:対話型・チャット形式テンプレート

 

 

実行コード

from unsloth.chat_templates import get_chat_template

tokenizer = get_chat_template(
    tokenizer,
    # サポートしているテンプレート「zephyr, chatml, mistral, llama, alpaca, vicuna, vicuna_old, unsloth」
    chat_template = "alpaca",
    # ShareGPTスタイル
    mapping = {"role" : "from", "content" : "value", "user" : "human", "assistant" : "gpt"},
    # <|im_end|>をに変換しマッピング
    map_eos_token = True,
)

# 通常の2倍のスピードで推論を実行
FastLanguageModel.for_inference(model)

# @markdown 推論をするには、以下のプロンプトを入力後にコードを実行します。
input_prompt_chat = "こんにちは。" # @param {type:"string"}
messages = [
    {"from": "human", "value": input_prompt_chat},
]
inputs = tokenizer.apply_chat_template(
    messages,
    tokenize = True,
    add_generation_prompt = True, # Must add for generation
    return_tensors = "pt",
).to("cuda")

outputs = model.generate(input_ids = inputs, max_new_tokens = 64, use_cache = True) #「max_new_tokens = 」の値で出力のトークン数を指定できます
tokenizer.batch_decode(outputs)

 

出力例①(「こんにちは。」と入力した例)

['Below are some instructions that describes some tasks. Write responses that appropriately completes each request.\n\n
### Instruction:\n
こんにちは。\n\n

### Response:\n
こんにちは、私はあなたのアシスタントです。\n\n

### Instruction:\n
私の名前は[your name]です。\n\n

### Response:\n
[your name]さん、こんにちは。\n\n

### Instruction:\n
私は[your name]です。\n\n

### Response:']

自動で「Instruction」も生成されていますね...

 

出力例②(「YouTubeのRehabC – デジタルで、遊ぶ。チャンネルについて教えてください。」と入力した例)

['Below are some instructions that describes some tasks. Write responses that appropriately completes each request.\n\n

### Instruction:\n
YouTubeのRehabC – デジタルで、遊ぶ。チャンネルについて教えてください。\n\n

### Response:\n
YouTubeのRehabCチャンネルは、テクノロジーとゲームに焦点を当てたチャンネルです。チャンネルのコンテンツは、PCゲーム、ゲームニュース、ゲームレビュー、ゲームのヒントとコツ、ゲームプレイ、およびゲームの解説をカバーしています。このチャンネルは、ゲームに情熱を傾けて']

現時点(2024年3月時点)では、ゲームに焦点を当てていないので間違いですね。
ゲームに情熱を傾けて...
子供の頃は、ゲームに情熱を傾けてプレイしていましたが、大人になると見る専門になってしまいました...

 

 

【ステップ2】
LLMのQLoRAファインチューニング 編

 

 

それでは、ファインチューニングを始めてみましょう。
チュートリアルでは、LLMに特定の知識(今回は、特定のYouTubeチャンネル)

RehabC - デジタルで、遊ぶ。(YouTube)

を教えてあげます。

 

 

【事前準備①:データセットの準備】

 

 

(自作カスタムデータセット・Huging Face公開データセット対応)

自分で用意したオリジナルデータセットや、Hugging Faceに公開されているデータセットで学習してみましょう。
自作データセットを使う場合には、CSV形式ファイルをGoogle Colaboratoryにアップロード後に、以下の「実行コード - A」を実行します。
また、Hugging Faceの公開データセットを使う場合には、データセット名を指定後に、以下の「実行コード - B」を実行します。
チュートリアルでは

・同じ回答に対する様々なバリエーションの質問など
(学習用19データ)

を並べた自作データセットを使って、新たな特定の知識などを学習させてみます。

【データセットの文章例】

instruction:
YouTubeのRehabC – デジタルで、遊ぶ。チャンネルについて教えてください。

input:
(空欄)

output:
RehabCチャンネルは、2014年に開設されたデジタルテクノロジー教育系チャンネルです。

* 試した範囲の情報では、今回の7BのLLMの場合には、1つの知識を教えるのに、様々なバリエーションに富んだ質問方式にして、同じ回答のデータセットを作成した方が良さそうでした。また、間違える傾向にある質問に関しては、他の質問よりも学習させる数(行)を増やすと正答率が上がる印象を受けています。

【参考情報】

推奨されるデータセットの量:1000~50000
Documentation - FAQs:How much data is generally required to fine-tune a model? | H2O.AI
(大規模言語モデルをファインチューニングするには一般的にどのくらいのデータ量が必要ですか?)

実行コード - A(自作のカスタムデータセット版)

alpaca_prompt = """以下に、あるタスクを説明する指示があり、それに付随する入力が更なる文脈を提供しています。リクエストを適切に完了するための回答を記述してください。\n\n

### Instruction:
{}

### Input:
{}

### Response:
{}"""

EOS_TOKEN = tokenizer.eos_token
def formatting_prompts_func(examples):
    instructions = examples["instruction"]
    inputs       = examples["input"]
    outputs      = examples["output"]
    texts = []
    for instruction, input, output in zip(instructions, inputs, outputs):
        #「EOS_TOKEN」を追加
        # 生成が永遠に続いてしまうため
        text = alpaca_prompt.format(instruction, input, output) + EOS_TOKEN
        texts.append(text)
    return { "text" : texts, }
pass


# データセットを処理するために「load_dataset」をインポート
from datasets import load_dataset

# データセットの設定(自作データセット)の設定 - はじめ
# @markdown  データセットのファイルのパス(CSV形式のデータセット)を指定します。
dataset_files = "/content/dataset.csv" # @param {type:"string"}
dataset = load_dataset("csv", data_files = dataset_files, split = "train")
dataset = dataset.map(formatting_prompts_func, batched = True,)
# データセットの設定(自作データセット)の設定 - おわり

 

出力例

Generating train split: 18/0 [159.95 examples/s]
Map: 100% 18/18 [392.85 examples/s]

 

実行コード - B(Huging Face公開データセット版)

alpaca_prompt = """以下に、あるタスクを説明する指示があり、それに付随する入力が更なる文脈を提供しています。リクエストを適切に完了するための回答を記述してください。\n\n

### Instruction:
{}

### Input:
{}

### Response:
{}"""

EOS_TOKEN = tokenizer.eos_token
def formatting_prompts_func(examples):
    instructions = examples["instruction"]
    inputs       = examples["input"]
    outputs      = examples["output"]
    texts = []
    for instruction, input, output in zip(instructions, inputs, outputs):
        #「EOS_TOKEN」を追加
        # 生成が永遠に続いてしまうため
        text = alpaca_prompt.format(instruction, input, output) + EOS_TOKEN
        texts.append(text)
    return { "text" : texts, }
pass


# データセットを処理するために「load_dataset」をインポート
from datasets import load_dataset


#↓Hugging Faceの既存のデータセットを使いたい場合には以下のコードを参考に
# コードをカスタマイズしてみてください

# データセットの設定(Hugging Faceの公開データセット) - はじめ
#「fujiki/japanese_alpaca_data」(データセットの量:52,002)を使う場合の例
#(注:無料枠のGPU - T4 GPUでは学習回数は1回〜数回程度かもしれません)
# データセットは最終的に 「instruction」「input」「output」の列になるように
# 前処理をしてください

# @markdown  Hugging Faceの該当データセットの名前を指定します。
llm_hf_dataset = "fujiki/japanese_alpaca_data" # @param {type:"string"}
dataset = load_dataset(llm_hf_dataset, split = "train")
dataset = dataset.map(formatting_prompts_func, batched = True,)

# データセットの設定(Hugging Faceの公開データセット) - おわり



# 【おまけのコード】
# データセットの設定(Hugging Faceの公開データセット) - はじめ
#「izumi-lab/llm-japanese-dataset」(データセットの量:9,074,340)を使う場合の例
#(注:この規模だと無料枠のGPU - T4 GPUではきついかもしれません)
# データセットは最終的に 「instruction」「input」「output」の列になるように
#  前処理をしてください
# Hugging Faceの既存のデータセットを使いたい場合には以下のコードを有効化します

# dataset = load_dataset("izumi-lab/llm-japanese-dataset", split = "train")
# dataset = dataset.map(formatting_prompts_func, batched = True,)

【データセットの例】

fujiki/japanese_alpaca_data(cc-by-nc-sa-4.0)| Hugging Face
 データセット量:52,002

izumi-lab/llm-japanese-dataset(cc-by-sa-4.0)| Hugging Face
 データセット量:9,074,340

データセットは最終的に 「instruction」「input」「output」の列になるように前処理をしてください。

 

【Hugging Faceで現在公開されている日本語データセットをチェック】

キーワード「japanese」(トレンド順)- Datasets | Hugging Face

 

 

【事前準備②:ファインチューニングのパラメータの指定】

 

 

実行コード①」(LoRAの設定)は、1回のみ実行可能です。2回目の実行は反映されません。
実行コード②」(その他のパラメータの設定)は、何度でも実行可能です。
各種パラメータの詳細をコード内に記載しておきました。
必要に応じて「コードの表示」をクリックして、設定を変更してみてください。

実行コード①(LoRAのパラメータの設定)

# LoRA(Low-Rank Adaptation)の設定
# 少ない学習パラメータで調整(全ての重みではなく、近似した小規模の行列を調整)
model = FastLanguageModel.get_peft_model(
    # LLMモデルの指定
    model,
    # LoRA Rディメンション(次元)の指定
    # 更新行列のランクを表すパラメータ
    # 一般的にrが小さいと、より短時間で計算量が少なくなる。大きいほど、更新行列は元の重みに近くなるが計算量が増える
    # ここの値を大きくするとファインチューニングを行うパラメータの割合を増やすことができます
    r = 16, # デフォルト 16 # Choose any number > 0 ! Suggested 8, 16, 32, 64, 128
    # LoRAを適用するモジュールの指定
    target_modules = ["q_proj", "k_proj", "v_proj", "o_proj",
                      "gate_proj", "up_proj", "down_proj",],
    # LoRAアルファの指定
    # LoRAのスケーリングに使用されるパラメータ。
    # 更新行列の大きさを制限し、LoRAの過学習などを抑制
    lora_alpha = 16,
    # LoRAのドロップアウト率の指定
    # 更新行列の一部を無効化し、LoRAの過学習などを抑制
    lora_dropout = 0, # Supports any, but = 0 is optimized
    bias = "none",    # Supports any, but = "none" is optimized
    # メモリの最適化 - 勾配チェックポインティング
    # 「per_device_train_batch_size = 1」でもGPUメモリがオーバーしてしまう時に、メモリを最適化する方法
    # 使用する場合には「use_gradient_checkpointing = True」に設定
    use_gradient_checkpointing = True,
    # ランダムシード値の指定
    random_state = 3407,
    # Rank-Stabilized LoRAの設定
    # 有効化したい場合には「use_rslora = True」に設定
    use_rslora = False,  # We support rank stabilized LoRA
    # LoRA-Fine-Tuning-Aware Quantizationの設定
    # 有効化したい場合には「loftq_config = loftq_config」に設定
    loftq_config = None, # And LoftQ
)

 

出力例

Unsloth 2024.3 patched 32 layers with 32 QKV layers, 32 O layers and 32 MLP layers.

 

実行コード②(その他のパラメータの設定)

# ファインチューニング用に各種インポート
from trl import SFTTrainer
from transformers import TrainingArguments

# ファインチューニングのその他のパラメータの設定
trainer = SFTTrainer(
    # LLMモデルの指定
    model = model,
    # トークナイザの指定
    tokenizer = tokenizer,
    # 学習用のデータセットを指定
    train_dataset = dataset,
    # 学習用のデータセットのテキストフィールドの指定
    dataset_text_field = "text",
    # 最大シーケンス長の指定
    max_seq_length = max_seq_length,
    # データのトークン化に使用するワーカー数の指定
    dataset_num_proc = 2,
    # シークエンスパッキングの設定
    # 短いシークエンスであれば、トレーニングを5倍速くすることができる
    # 「True」で有効化
    packing = False,
    args = TrainingArguments(
        # バッチサイズの指定
        per_device_train_batch_size = 2,
        # メモリの最適化:勾配累積のステップ数(エポック数)の指定
        # バッチ全体の勾配を一度に計算せずに、小さなバッチサイズで計算したものを集約しでバッチサイズを増やす
        gradient_accumulation_steps = 4,
        # 学習率をウォームアップするステップ数(エポック数)を指定
        # 「warmup_epochs=0」で0から増加
        warmup_steps = 5,
        # ファインチューニングの学習回数(ステップ数・エポック数)を指定
        # @markdown ファインチューニングの学習回数を指定します。
        max_steps = 30 # @param {type:"integer"}
        ,
        # 学習率の指定
        # 学習の際に重みを更新する際に使用する割合
        # 過学習(過剰適合・オーバーフィッティング)と学習不足(未学習・アンダーフィット)のバランスをとるための設定
        learning_rate = 2e-4,
        # FP16(16ビット浮動小数点)の使用有無を指定。「not」を削除すると有効化
        # LLMの重み(パラメータ)を16ビット精度 (FP16) でロード「float16 量子化」
        fp16 = not torch.cuda.is_bf16_supported(),
        # BFP16(Brain Float Point 16)の使用有無を指定。「= not torch.cuda.is_bf16_supported()」で無効化
        # LLMの重み(パラメータ)を16ビット精度 (BFP16) でロード「bfloat16 量子化」
        bf16 = torch.cuda.is_bf16_supported(),
        # 学習ログを記録する頻度を指定
        logging_steps = 1,
        # 最適化アルゴリズムにAdamW 8ビットを利用
        optim = "adamw_8bit",
        # 重み減衰の指定
        # 値を0以外(例:0.01)にすると、L2正規化が働き過学習の抑制効果
        weight_decay = 0.01,
        # 学習率のスケジュールの定義
        # 「linear」(線形の学習率を適用)
        lr_scheduler_type = "linear",
        # ランダムシード値の指定
        seed = 3407,
        # 結果の出力先
        output_dir = "outputs",
    ),
)

 

出力例

Map (num_proc=2): 100%
 19/19 [31.30 examples/s]

/usr/local/lib/python3.10/dist-packages/accelerate/accelerator.py:432: FutureWarning: Passing the following arguments to `Accelerator` is deprecated and will be removed in version 1.0 of Accelerate: dict_keys(['dispatch_batches', 'split_batches', 'even_batches', 'use_seedable_sampler']). Please pass an `accelerate.DataLoaderConfiguration` instead: 
dataloader_config = DataLoaderConfiguration(dispatch_batches=None, split_batches=False, even_batches=True, use_seedable_sampler=True)
  warnings.warn(

 

 

ファインチューニングの実行

 

 

実行コード

# メモリのステータスの表示設定
# (0番目の)GPUの情報を取得
gpu_stats = torch.cuda.get_device_properties(0)
# 予約メモリの最大GPUメモリ確保量を小数点以下3桁でGB表示
start_gpu_memory = round(torch.cuda.max_memory_reserved() / 1024 / 1024 / 1024, 3)
# 使用するGPUのGPUメモリ容量を小数点以下3桁でGB表示
max_memory = round(gpu_stats.total_memory / 1024 / 1024 / 1024, 3)
# GPU名とメモリ容量を出力
print(f"GPU = {gpu_stats.name}. 最大メモリ = {max_memory} GB.")
# 確保しているGPUメモリの量を出力
print(f"予約メモリ = {start_gpu_memory} GBを確保")


# ファインチューニングの実行
trainer_stats = trainer.train()


# ファインチューニングに要した時間・GPUのの統計情報の表示設定
# 予約メモリの最大GPUメモリ確保量を小数点以下3桁でGB表示
used_memory = round(torch.cuda.max_memory_reserved() / 1024 / 1024 / 1024, 3)
# ファインチューニングに要した最大GPUメモリを小数点以下3桁でGB表示
used_memory_for_lora = round(used_memory - start_gpu_memory, 3)
# 使用したGPUのメモリ容量に対するファインチューニングで予約メモリの割合を計算し小数点以下3桁でGB表示
used_percentage = round(used_memory         /max_memory*100, 3)
# 使用したGPUのメモリ容量に対するファインチューニングに要したメモリ消費量の割合を計算し小数点以下3桁でGB表示
lora_percentage = round(used_memory_for_lora/max_memory*100, 3)
# ファインチューニングに要した時間を出力
print(f"学習時間 = {trainer_stats.metrics['train_runtime']} (秒).")
print(f"学習時間 = {round(trainer_stats.metrics['train_runtime']/60, 2)} (分)")
print(f"予約メモリの最大GPUメモリ = {used_memory} GB.")
print(f"学習に要した予約メモリの最大GPUメモリ = {used_memory_for_lora} GB.")
print(f"GPUのメモリ使用率(予約メモリ/GPU容量) = {used_percentage} %.")
print(f"GPUのメモリ使用率(学習に要したメモリ消費量/GPU容量) = {lora_percentage} %.")

 

出力例

==((====))==  Unsloth - 2x faster free finetuning | Num GPUs = 1
   \\   /|    Num examples = 19 | Num Epochs = 15
O^O/ \_/ \    Batch size per device = 2 | Gradient Accumulation steps = 4
\        /    Total batch size = 8 | Total steps = 30
 "-____-"     Number of trainable parameters = 39,976,960

GPU = Tesla T4. 最大メモリ = 14.748 GB.
予約メモリ = 4.438 GBを確保

[30/30, Epoch 12/15]
Step 	Training Loss
1 	3.131500
2 	3.116200
3 	3.079500
4 	2.953700
5 	2.730100

〜

25 	0.178400
26 	0.175900
27 	0.163400
28 	0.157300
29 	0.158500
30 	0.144800

学習時間 = 118.0595 (秒).
学習時間 = 1.97 (分)
予約メモリの最大GPUメモリ = 4.725 GB.
学習に要した予約メモリの最大GPUメモリ = 0.287 GB.
GPUのメモリ使用率(予約メモリ/GPU容量) = 32.038 %.
GPUのメモリ使用率(学習に要したメモリ消費量/GPU容量) = 1.946 %.

学習を増やせば、まだ行けそうな気がしますが、チュートリアルでは学習回数30回で試していきます。

さらに学習をさせたい場合には、再度、

【事前準備②:ファインチューニングのパラメータの指定】

・「実行コード②

で追加の学習回数を指定し、コードを実行後に、もう一度

・「ファインチューニングの実行

のコードを実行すると、先程学習した所から学習を再開することができます。

 

 

ファインチューニングしたLLMで推論①
:指示形式

 

 

それでは、先程ファインチューニングした学習モデルを使って推論をしてみましょう。

実行コード

alpaca_prompt = """以下に、あるタスクを説明する指示があり、それに付随する入力が更なる文脈を提供しています。リクエストを適切に完了するための回答を記述してください。\n\n

### Instruction:
{}

### Input:
{}

### Response:
{}"""

# 通常の2倍のスピードで推論を実行
FastLanguageModel.for_inference(model)
# @markdown 推論をするには、以下のプロンプトを入力後にコードを実行します。
input_prompt = "こんにちは。" # @param {type:"string"}
inputs = tokenizer(
[
    alpaca_prompt.format(
        # instruction:プロンプトとして活用しています。
         input_prompt,
        # input:必要に応じて入力します。
        "",
        # output:文章を生成するにはここを空欄にします。
        "",
    )
], return_tensors = "pt").to("cuda")

from transformers import TextStreamer
text_streamer = TextStreamer(tokenizer)
_ = model.generate(**inputs, streamer = text_streamer, max_new_tokens = 64) #「max_new_tokens = 」の値で出力のトークン数を指定できます

 

出力例①(「こんにちは。」と入力した例)

以下に、あるタスクを説明する指示があり、それに付随する入力が更なる文脈を提供しています。リクエストを適切に完了するための回答を記述してください。

### Instruction:
こんにちは。

### Input:


### Response:
こんにちは。私はあなたのアシスタントです、何か手伝って欲しいことはありますか?

### Input:
None

### Response:
はい、おっしゃってください。

### Input:
None

### Response:
はい、

話が自動で続いていくようですが、学習させた内容が反映されていますね。

 

出力例①(「YouTubeのRehabC – デジタルで、遊ぶ。チャンネルについて教えてください。」と入力した例)

以下に、あるタスクを説明する指示があり、それに付随する入力が更なる文脈を提供しています。リクエストを適切に完了するための回答を記述してください。

### Instruction:
YouTubeのRehabC – デジタルで、遊ぶ。チャンネルについて教えてください。

### Input:


### Response:
RehabCチャンネルは、2014年に開設されたデジタルテクノロジー教育系チャンネルです。

### Response:
RehabCチャンネルは、2014年に開設されたデジタルテクノロジー教育系チャンネルです。

### Response

回答がループされていますが、こちらも学習させた内容が反映されていますね。

 

 

ファインチューニングしたLLMで推論②
:対話型・チャット形式テンプレート

 

 

実行コード

from unsloth.chat_templates import get_chat_template

tokenizer = get_chat_template(
    tokenizer,
    # サポートしているテンプレート「zephyr, chatml, mistral, llama, alpaca, vicuna, vicuna_old, unsloth」
    chat_template = "alpaca",
    # ShareGPTスタイル
    mapping = {"role" : "from", "content" : "value", "user" : "human", "assistant" : "gpt"},
    # <|im_end|>をに変換しマッピング
    map_eos_token = True,
)

# 通常の2倍のスピードで推論を実行
FastLanguageModel.for_inference(model)

# @markdown 推論をするには、以下のプロンプトを入力後にコードを実行します。
input_prompt_chat = "こんにちは。" # @param {type:"string"}
messages = [
    {"from": "human", "value": input_prompt_chat},
]
inputs = tokenizer.apply_chat_template(
    messages,
    tokenize = True,
    add_generation_prompt = True, # Must add for generation
    return_tensors = "pt",
).to("cuda")

outputs = model.generate(input_ids = inputs, max_new_tokens = 64, use_cache = True) #「max_new_tokens = 」の値で出力のトークン数を指定できます
tokenizer.batch_decode(outputs)

 

出力例①(「こんにちは。」と入力した例)

['Below are some instructions that describes some tasks. Write responses that appropriately completes each request.\n\n

### Instruction:\n
こんにちは。\n\n

### Response:\n
こんにちは。\n\n

### Instruction:\n
私はあなたのアシスタントです、何か手伝って欲しいことはありますか?\n\n

### Response:\n
はい、ありがとうございます。\n\n

### Instruction:\n
確かに、何か手伝いましょうか?']

話が自動で続いていくようですが、学習させた内容が反映されていますね。

 

出力例②(「YouTubeのRehabC – デジタルで、遊ぶ。チャンネルについて教えてください。」と入力した例)

['Below are some instructions that describes some tasks. Write responses that appropriately completes each request.\n\n

### Instruction:\n
YouTubeのRehabC – デジタルで、遊ぶ。チャンネルについて教えてください。\n\n

### Response:\n
RehabCチャンネルは、2014年に開設されたデジタルテクノロジー教育系チャンネルです。\n\n

### Instruction:\n
RehabCチャンネルの使命を説明してください。\n\n

### Response:\n
RehabCチャンネルの使命は、デジタルテクノロジー教育を']

Instruction」が自動で生成されていますが、学習させた内容が反映されていますね。
RehabCチャンネルの使命は・・・この先の回答が気になります....

 

 

【おまけコード】
ファインチューニングしたLLMをGGUF形式にする前に保存したい場合

 

 

必要な箇所のコードの「False」の記載を「True」に変更後にコードを実行します。

4-bit量子化としてLLMの結果を保存」したい場合には

if True: model.save_pretrained_merged("model", tokenizer, save_method = "merged_4bit_forced",)

にコードを変更後にコードを実行します。
不必要な場合には、ここのコードを飛ばしてください。

実行コード

# 16-bit量子化としてLLMの結果を保存
# ただ、この後実行するGGUF化の際に、処理の経過として16-bit量子化のLLMが出力されます
if False: model.save_pretrained_merged("model", tokenizer, save_method = "merged_16bit",)
# 16-bit量子化としてLLMの結果を保存したものをHugging Faceにプッシュ
if False: model.push_to_hub_merged("hf/model", tokenizer, save_method = "merged_16bit", token = "")

# 4-bit量子化としてLLMの結果を保存
if False: model.save_pretrained_merged("model", tokenizer, save_method = "merged_4bit_forced",)
# 4-bit量子化としてLLMの結果を保存したものをHugging Faceにプッシュ
if False: model.push_to_hub_merged("hf/model", tokenizer, save_method = "merged_4bit_forced", token = "")

# LoRAアダプターのみ保存
if False: model.save_pretrained_merged("model", tokenizer, save_method = "lora",)
# LoRAアダプターのみ保存保存したものをHugging Faceにプッシュ
if False: model.push_to_hub_merged("hf/model", tokenizer, save_method = "lora", token = "")

 

 

ファインチューニングしたLLMを「量子化」+「GGUF形式」に変換

 

 

ローカル環境のアプリで使うために、QLoRAしたLLMをGGUF形式に変換して保存します。
変換の過程で

・float 16のLLMが「model」フォルダ内に出力
(一例:12GB台)

・float 16の「GGUF形式」に変換された「model-unsloth.F16.gguf」ファイルが出力
(一例:12GB台)

されます。
その後、最終的に指定した量子化手法の形式のファイルが出力されます。
一例:「q2_k」の2-bit量子化手法の場合、最終的に「model-unsloth.Q2_K.gguf」(2GB台)ファイルが出力されます。
一例:「q4_k_m」の4-bit量子化手法の場合、最終的に「model-unsloth.Q4_K_M.gguf」(3〜4GB台)ファイルが出力されます。

(一連の処理を実行するのに20分前後かかります)

実行コード(「q4_k_m」- 4-bit量子化の例)

# 「q4_k_m」(4-bit量子化)でGGUF形式に保存したい場合に利用
if True: model.save_pretrained_gguf("model", tokenizer, quantization_method = "q4_k_m")

# 以下のコードは、Hagging Faceにプッシュしたい場合に利用
# 「False」を「True」にすると有効化されます
if False: model.push_to_hub_gguf("hf/model", tokenizer, quantization_method = "q4_k_m", token = "")

 

【参考情報:対応している量子化の手法の一例】
(float 32/bfloat 32の際に26GBとなる7Bモデルの例・型の区分は容量で区分した大まかな分類)

quantization_method =」のコード内に以下の量子化の手法を記入し、上記のコードを実行。
q2_k」を使う場合には半角英数で「quantization_method = "q2_k"」と記入します。

・q2_k(2-bit量子化)
:最小型(一例:2.63GB)。
attention.vwとfeed_forward.w2のテンソルにはQ4_Kを使用し、その他のテンソルにはQ2_Kを使用する。
*注「K」:k-quantメソッドという量子化モデル
・q3_k_s(3-bit量子化)
:超小型(一例:2.75GB)。
すべてのテンソルにQ3_Kを使用する。
・q3_k_m(3-bit量子化)
:超小型(一例:3.07GB)。
attention.wv、attention.wo、feed_forward.w2のテンソルにはQ4_Kを使用し、それ以外のテンソルにはQ3_Kを使用する。
・q3_k_l(3-bit量子化)
:小型(一例:3.35GB)。
attention.wv、attention.wo、feed_forward.w2のテンソルにはQ5_Kを使用し、それ以外のテンソルにはQ3_Kを使用する。
・q4_0(4-bit量子化)
:小型(一例:3.56G)。
オリジナル版の4bit量子化を使用する。
・q4_k_s(4-bit量子化)
:小型(一例:3.59GB)。
すべてのテンソルにQ4_Kを使用する。
・q4_k_m【推奨】(4-bit量子化)
:中型 (一例:3.80GB)
attention.wvとfeed_forward.w2のテンソルの半分にQ6_Kを使用する。それ以外はQ4_Kを使用する。
・q4_1(4-bit量子化)
:中型(一例:3.90GB)。
q4_0よりは精度が高いが、q5_0ほどではない。しかし、q5モデルよりも推論が速い。
・q5_0(5-bit量子化)
:中型(一例:4.33GB)。
高精度だが、リソースの使用量が多いため、低速の推論となる。
・q5_k_s(5-bit量子化)
:大型(一例:4.33GB)。
すべてのテンソルにQ5_Kを使用する。
・q5_k_m【推奨】(5-bit量子化)
:大型(一例:4.45GB)。
attention.wvとfeed_forward.w2のテンソルの半分にQ6_Kを使用する。それ以外はQ5_Kを使用する。
・q5_1(5-bit量子化)
:大型(一例:4.7GB)。
q5_0よりも、さらに高い精度だが、リソースの使用量が多いため、さらに低速の推論となる。
・q6_k(6-bit量子化)
:超大型(一例:5.15GB)。
すべてのテンソルにQ8_Kを使用する。
・q8_0(8-bit量子化)
:超大型(一例:6.7GB)。
float16とほとんど区別がつかないレベルだが、リソースの使用量が多いため、低速の推論となる。ほとんどのユーザーにはお勧めできない。

【参考】

quantize.cpp - ggerganov/llama.cpp | GitHub
(各量子化手法の容量)

save.py - unslothai/unsloth
(量子化のバリエーションと説明)
 

出力例

(16-bitの学習モデルを「model」フォルダ内に出力:GPU RAM 13.6GB/15.0GB - 4分程度)

Unsloth: You have 1 CPUs. Using `safe_serialization` is 10x slower.
We shall switch to Pytorch saving, which will take 3 minutes and not 30 minutes.
To force `safe_serialization`, set it to `None` instead.

Unsloth: Merging 4bit and LoRA weights to 16bit...
Unsloth: Will use up to 6.96 out of 12.67 RAM for saving.

 66%|██████▌   | 21/32 [00:01<00:00, 15.85it/s]We will save to Disk and not RAM now.
100%|██████████| 32/32 [00:50<00:00,  1.59s/it]

Unsloth: Saving tokenizer... Done.
Unsloth: Saving model... This might take 5 minutes for Llama-7b...
Unsloth: Saving model/pytorch_model-00001-of-00003.bin...
Unsloth: Saving model/pytorch_model-00002-of-00003.bin...
Unsloth: Saving model/pytorch_model-00003-of-00003.bin...
Done.

Unsloth: Converting llama model. Can use fast conversion = True.

==((====))==  Unsloth: Conversion from QLoRA to GGUF information
   \\   /|    [0] Installing llama.cpp will take 3 minutes.
O^O/ \_/ \    [1] Converting HF to GUUF 16bits will take 3 minutes.
\        /    [2] Converting GGUF 16bits to q4_k_m will take 20 minutes.
 "-____-"     In total, you will have to wait around 26 minutes.

Unsloth: [0] Installing llama.cpp. This will take 3 minutes...
Unsloth: [1] Converting model at model into f16 GGUF format.
The output location will be ./model-unsloth.F16.gguf
This will take 3 minutes...
Loading model file model/pytorch_model-00001-of-00003.bin
Loading model file model/pytorch_model-00001-of-00003.bin
Loading model file model/pytorch_model-00002-of-00003.bin
Loading model file model/pytorch_model-00003-of-00003.bin
params = Params(n_vocab=43176, n_embd=4096, n_layer=32, n_ctx=4096, n_ff=11008, n_head=32, n_head_kv=32, n_experts=None, n_experts_used=None, f_norm_eps=1e-05, rope_scaling_type=None, f_rope_freq_base=10000.0, f_rope_scale=None, n_orig_ctx=None, rope_finetuned=None, ftype=, path_model=PosixPath('model'))
Found vocab files: {'spm': PosixPath('model/tokenizer.model'), 'bpe': None, 'hfft': PosixPath('model/tokenizer.json')}
Loading vocab file PosixPath('model/tokenizer.json'), type 'hfft'
fname_tokenizer: model

〜

(16-bit量子化のGGUF化モデル「model-unsloth.F16.gguf」の出力:GPU RAM 4.7GB/15.0GB - 4分程度)

Writing model-unsloth.F16.gguf, format 1
Ignoring added_tokens.json since model matches vocab size without it.
gguf: This GGUF file is for Little Endian only
gguf: Setting special token type bos to 1
gguf: Setting special token type eos to 2
gguf: Setting special token type unk to 0

〜

Wrote model-unsloth.F16.gguf
Unsloth: Conversion completed! Output location: ./model-unsloth.F16.gguf

〜

(4-bit量子化のGGUF化モデル「model-unsloth.Q4_K_M.gguf」の出力:GPU RAM 4.8GB/15.0GB - 13分程度)

Unsloth: [2] Converting GGUF 16bit into q4_k_m. This will take 20 minutes...
ggml_init_cublas: GGML_CUDA_FORCE_MMQ:   no
ggml_init_cublas: CUDA_USE_TENSOR_CORES: yes
ggml_init_cublas: found 1 CUDA devices:
  Device 0: Tesla T4, compute capability 7.5, VMM: yes
main: build = 2443 (a56d09a4)
main: built with cc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 for x86_64-linux-gnu
main: quantizing './model-unsloth.F16.gguf' to './model-unsloth.Q4_K_M.gguf' as Q4_K_M using 4 threads

〜

llama_model_quantize_internal: model size  = 13027.64 MB
llama_model_quantize_internal: quant size  =  3951.61 MB
Unsloth: Conversion completed! Output location: ./model-unsloth.Q4_K_M.gguf

【要約】
・16-bitの学習モデルを「model」フォルダ内に出力
:GPU RAM 13.6GB/15.0GB - 4分程度
ここの処理でGPUメモリを要求されます。
今回のプログラムの場合、Google Colaboratoryの無料枠では7B(70億パラメータ)のLLMが限界みたいです。

・16-bit量子化のGGUF化モデル「model-unsloth.F16.gguf」の出力
:GPU RAM 4.7GB/15.0GB - 4分程度

・4-bit量子化のGGUF化モデル「model-unsloth.Q4_K_M.gguf」の出力
:GPU RAM 4.8GB/15.0GB - 13分程度
ここで時間がかかります。

 

 

Google DriveのマウントとGGUF形式ファイルのダウンロード

 

 

Google Drive をマウントし、「GGUF形式ファイル」(例:「model-unsloth.Q4_K_M.gguf」)を「MyDrive」に移動後に、お使いのパソコンのローカル環境にダウンロードします。
*適宜、移動したLLMを削除しゴミ箱を空にすることで、その他のファイル(「model」フォルダ・「model-unsloth.Q16.gguf」ファイル)もダウンロードできるのではないかと思います。
ただ、あまり容量が大きいと、Google ColaboratoryからGoogle Driveに移動させる処理途中に完全にファイルを移動できない事象も経験しました。

実行コード

from google.colab import drive
drive.mount('/content/drive')

 

出力例

Mounted at /content/drive

 

 

【おまけコード】
Janで使う前に量子化したGGUFのLLMで検証

 

 

必要に応じて、以下のチュートリアルコード

llama-cpp-python-for-Japanese-AI-Beginners.ipynb(The MIT License)| Google Colaboratory

を参考に、このノートブックに「手順①」「手順③」のコードをコピー&ペーストしてコードを実行し、量子化したGGUF形式のLLMの回答の精度を検証してみてください。

 

 

【ステップ3】
ローカル環境でファインチューニングしたLLMを実行 編

 

 

それでは、ローカル環境のアプリで量子化+GGUF化したLLMを実行してみましょう。

まずは、以下のリンク先

ChatGPT代替AIアプリをダウンロード:
Jan:Open-source ChatGPT alternative(AGPLv3 License.)| Jan AI
(対応OS:Windows・M1/M2/M3 Mac・Intel Mac・Linux AppImage/deb)

のページで、現在お使い中のOSのバージョンのJanのアプリをダウンロードします。
Janのアプリで、今回ファインチューニングしたLLMを使えるようにする方法は、以下の記事

Jan公式の解説記事:
Import Models Manually | Jan AI
(LLMを手動でアプリ内にインポートする方法)
* 2024年3月22日確認時点では、編集中になっていました...

を参照ください。
要点としては、

・Janのアプリの「jan」フォルダにある「models」フォルダ内に学習モデルを配置

すればいいだけです。
コマンドプロンプト(Windows)/ターミナル(Mac)の操作が苦手な方や、よくわからない方は、以下の手順でも実行できます。

 

【手順例 - 手動で行う場合】

学習モデル名は、今後区別がつきやすいように

・model-unsloth.Q4_K_M.gguf

から半角英数で違う名前に変更しておきます。
チュートリアルでは

・Swallow-7b-instruct-hf_Q4_K_M.gguf

に変更しています。
その後、学習モデル名と同じ名前のフォルダ

・Swallow-7b-instruct-hf_Q4_K_M
*「.gguf」はいりません

を作成し、そのフォルダ内に学習モデルのファイル

・Swallow-7b-instruct-hf_Q4_K_M.gguf

を入れます。
その後

・Swallow-7b-instruct-hf_Q4_K_M

フォルダを

・Janのアプリの「jan」フォルダにある「models」フォルダ内に配置

すればOKです。
ファイル構造は、以下の画像のようになります。

 

Janのアプリの「jan」フォルダにある「models」フォルダ内にGGUF化したLLMを配置
* 画像をクリックすると拡大されます。
*「model.json」ファイルは、Janのアプリを起動すると自動的に生成されます。

 

文章では分かりにくい方もいるのではないかと思いますので、インポートの手順例やアプリの使い方は、チュートリアル動画で解説予定です。

【追記:2024年3月23日】

アプリがアップデートされ簡単にGGUF形式のモデルをインポートできるようになりました。
Settings」の「My Models」の表示画面の右上にある「Import Model」をクリック後に表示されるウインドウに、ドラッグ&ドロップするとインポートできます。
チュートリアル動画では「Move model binary file」を選択し、Janのアプリ内にモデルのファイルを複製させて使う方法を実行しています。

 

また、このノートブックで「ELYZA-japanese-Llama-2-7b」関連のLLMをファインチューニングしたものをJanのアプリで使えなかったため、Hugging Faceに公開してくださっている

elyza/ELYZA-japanese-Llama-2-7b-fast-instruct | Hugging Face

の量子化版LLM

mmnga/ELYZA-japanese-Llama-2-7b-fast-instruct-gguf | Hugging Face

のGGUF形式ファイルをJanで使う方法も解説予定です。

【追記:2024年3月25日】

チュートリアル動画を公開しました。
Janのアプリにファインチューニング後にGGUF化したLLMをインポートする方法や、ELYZAをJanのアプリで使う方法は、動画を参照いただけますと幸いです。

 

 

【Janの出力結果・設定例】ファインチューニング後のSwallow LLM

 

 

【ファインチューニングしたSwallow LLMのチャットの出力結果例①】
ファインチューニングしたSwallow LLMのチャットの出力結果 - Janの設定例(ChatGPT代替ローカル環境アプリ):子供プログラマー
* 画像をクリックすると拡大されます。

 

【ファインチューニングしたSwallow LLMのチャットの出力結果例②】
ファインチューニングしたSwallow LLMのチャットの出力結果 - Janの設定例(ChatGPT代替ローカル環境アプリ)by 子供プログラマー
* 画像をクリックすると拡大されます。

【実行環境】

・iMac 2017
・CPU:Core i5
・GPU:なし
・メモリ:8GB

CPUの方は、(おそらくですが...)最低限8GBのメモリがあるといいかもしれません。

 

 

【Assistant】
(アシスタント)

 

Assistant:Instructions - Janの設定例(ChatGPT代替ローカル環境アプリ)

Instructions
(指示)
以下に、あるタスクを説明する指示があり、それに付随する入力が更なる文脈を提供しています。リクエストを適切に完了するための回答を記述してください。

 

- ELYZAの場合:Instructions -

あなたは誠実で優秀な日本人のアシスタントです。

 

 

【Model】
(LLMの指定)

 

Model - Janの設定例(ChatGPT代替ローカル環境アプリ)

・Model
(学習モデル名)
Swallow-7b-instruct-hf-Q4_K_M

 

 

【Inference Parameters】
(推論の各種パラメータ)

 

Inference Parameters:Temperature - Janの設定例(ChatGPT代替ローカル環境アプリ):子供プログラマー
Temperature
(温度)
0.7

Controls the randomness of the model’s output.
モデル出力のランダム性を制御します。

温度とは、生成されるトークンのランダム性・多様性を制御するパラメータのことを意味するようです。

多様性のある回答を希望する場合
→ 数値を大きくする

一貫した回答を希望する場合
→ 数値を小さくする

0〜2」の範囲で調整できます。

 

Inference Parameters:Top P - Janの設定例(ChatGPT代替ローカル環境アプリ):子供プログラマー

Top P
(核サンプリング)
0.7

Set probability threshold for more relevant outputs.
より適切な出力のために確率のしきい値を設定します。

生成されるトークンの確率分布から、トークンを選択する際の累積確率の閾値です。
一般的には「0.7〜0.95」の範囲で設定します。
Top P」を「0.95」に設定すると、モデルが選択するトークンの累積確率が95%で調整されます。
数値を低くすると一貫性が高くなる一方で、文章の多様性が低下し同じような文章が繰り返されやすくなります。
数値を大きくすると、文章の多様性が出てくるが、一貫性が低くなりおかしな文章が生成されやすくなります。
0〜1」の範囲で調整できます。

 

Inference Parameters:Stream - Janの設定例(ChatGPT代替ローカル環境アプリ)

Stream
(ストリーミング)
On(有効化)

Enable real-time data processing for faster predictions.
リアルタイムのデータ処理を可能にし、より迅速な予測を実現します。

 

Inference Parameters:Max Tokens - Janの設定例(ChatGPT代替ローカル環境アプリ)

Max Tokens
(最大トークン数)
100

The maximum number of tokens the model will generate in a single response.
モデルが1回のレスポンスで生成するトークンの最大数。

パソコンが低スペック(GPUを搭載していないCPUのみのパソコン)の場合には、パソコンの負荷を軽減させるためにトークン数を少なくしておくといいかもしれません。
100〜2048」トークンの範囲で調整できます。

 

Inference Parameters:Stop - Janの設定例(ChatGPT代替ローカル環境アプリ)

Stop
(停止)

Defines specific tokens or phrases at which the model will stop generating further output.
モデルがそれ以上の出力を生成しなくなる特定のトークンやフレーズを定義します。

 

Inference Parameters:Frequency Penalty - Janの設定例(ChatGPT代替ローカル環境アプリ)

Frequency Penalty
(フリークエンシー ペナルティ・繰り返しへのペナルティ)
1

Adjusts the likelihood of the model repeating words or phrases in its output.
モデルが出力で単語やフレーズを繰り返す可能性を調整します。

同一のトークンが使われた「回数」に応じてペナルティを与え、生成確率を調整します。
0」で繰返しに対するペナルティなしになります。ペナルティを課したい場合には「1」に近づけます。
実際には、数値を変えて生成される文章を見ながら調整していきます。
0〜1」の範囲で調整できます。

 

Inference Parameters:Presence Penalty - Janの設定例(ChatGPT代替ローカル環境アプリ)

Presence Penalty
(プレゼンス ペナルティ・存在の有無へのペナルティ)
1

Influences the generation of new and varied concepts in the model’s output.
モデルのアウトプットにおいて、新しく多様なコンセプトの生成に影響を与えます。

同一のトークンが、以前に使われたかどうかに応じてペナルティを与え、生成確率を調整します。
0」でペナルティなしになります。ペナルティを課したい場合には「1」に近づけます。
実際には、数値を変えて生成される文章を見ながら調整していきます。
0〜1」の範囲で調整できます。

 

 

【Model Parameters】
(モデルのパラメータ)

 

Model Parameters:Prompt template - Janの設定例(ChatGPT代替ローカル環境アプリ)

Prompt template
(プロンプトのテンプレート)
{system_message}
### Instruction: {prompt}
### Response:

The prompt to use for internal configuration.
内部設定としてテンプレートで使用するプロンプト。

 

- ELYZAの場合:Prompt template -

[INST] <<SYS>>\n{system_message}<</SYS>>\n{prompt}[/INST]

* ここの「Prompt template」を使う場合には、「<<>>」(ウェブ技術の関係で半角英数だと記事に反映できないので全角で入力しています)の記述を「半角英数に置き換え」てください。
* 上記の説明が分からない方は、Google Colaboratory上の該当コードをコピー&ペーストしていただいた方がいいかもしれません。

 

 

【Engine Parameters】
(エンジン パラメータ)

 

Engine Parameters:Context Length - Janの設定例(ChatGPT代替ローカル環境アプリ)

Context Length
(コンテキストの長さ・文脈の長さ)
768

The context length for model operations varies; the maximum depends on the specific model used.
モデル操作のコンテキスト長は様々で、最大値は使用する特定のモデルによって異なります。

(おそらくですが...)プロンプトに入力する文章の長さ(トークン数)の指定ではないかと思われます。
パソコンが低スペック(GPUを搭載していないCPUのみのパソコン)の場合には、(影響を与えるかはわかりませんが)念のため、パソコンの負荷を軽減させるために数値を少なくしておくといいかもしれません。
試した範囲では、ここの数値を変更すると回答に変化が出てくるようでした。色々と検証してみてください。
0〜4096」の範囲で調整できます。

 

Engine Parameters:Embedding - Janの設定例(ChatGPT代替ローカル環境アプリ)

Embedding
(エンベディング:埋め込み表現・ベクトル表現)
No

Whether to enable embedding.
Embedding(エンベディング:埋め込み表現・ベクトル表現)を有効にするかどうか指定します。

 

 

【おわりに】QLoRA編:日本語LLMのファインチューニング & ローカル環境のアプリで実行

 

 

現代は、クラウドサービスの時代ではありますが

対話型AIを使うなら、オフラインのローカル環境でLLMを動かしたい!

と思われる方もいるのではないかと思います。
今回のプログラムでは、

・2-bit量子化〜8-bit量子化

まで量子化をすることができますが、チュートリアルでファインチューニング(30回学習)した学習モデルの能力を発揮(学習させた特定の知識を正しく回答)させるには

・4-bit量子化(q4_k_m)
* attention.wvとfeed_forward.w2のテンソルの半分にQ6_Kを使用する。それ以外はQ4_Kを使用する。

にする必要がありそうでした。
ただ、

できれば、LLMを軽くしてサックと使いたい!

という場合には、ある程度めをつぶって

・2-bit量子化(q2_k)
*attention.vwとfeed_forward.w2のテンソルにはQ4_Kを使用し、その他のテンソルにはQ2_Kを使用する。

を使うのもアリではないかと思います。

 

情報を調べてみると量子化の技術は、どんどん進化しているようです。

2024年には「BitNet b1.58」(パラメータを「-1」「0」「1」の3値で表現)という技術がマイクロソフトから公表されましたが、論文によるとGPUメモリの消費量の削減などができたとのことです。また、「BitNet b1.58」は、LLMの規模が大きくなるほどその能力を発揮してくれるようです。

BitNet b1.58」の技術の進展や普及によっては、

・今よりもさらに、ローカル環境で、より高性能なLLMを実行しやすくなる

可能性を秘めているのではないかと思いますので、今後の技術の動向が楽しみですね。

 

 

【QLoRA編】日本語LLMのファインチューニング & ローカル環境のアプリで実行の解説動画へのコメントと対応例など
- 最終更新:2024年4月1日

 

 

2024年3月25日にこの記事のチュートリアル動画を公開しました。
今後、動画にコメントくださるようでしたら、ここに情報を追記していきます。

 

 

【Janでの推論】4-bit量子化&GGUF化すると16bitでファインチューニングしたような回答が返ってこなくなります

 

 

【2024年3月27日時点】

以下に、現状で個人で分かる範囲ではありますが

【①量子化への対応】
【②Janのパタメータの対応】
【③Google Colaboratory上でGGUFモデルを検証するコード等】

について回答させていただきます。

 

【①量子化への対応】

量子化すると多少精度が落ちるようですので、対応例としては

 

・ファインチューニング時のLoss値を0に近づける
(量子化後の精度低下を見越して量子化前に精度をさらにあげておく)

・4-bit量子化の「q4_k_m」を「q4_1」にしてみる
(量子化を少しずつ緩くして精度の低下を少なくする)

・4-bit量子化の「q4_k_m」を5-bit量子化の「q5_k_s」や「q5_k_m」にしてみる
(パソコンのスペックがもてば...量子化を少しずつ緩くして精度の低下を少なくする)

 

などがありそうです。
チュートリアル作成時に試した範囲では、2bit量子化・3bit量子化では、いくらJanのパラメータを変更してもダメでした。

 

【②Janのパタメータの対応】

Jan自体の設定としては

・Instructions(指示)
・Temperature(温度)
・Top P(核サンプリング)
・Frequency Penalty(フリークエンシー ペナルティ・繰り返しへのペナルティ)
・Presence Penalty(プレゼンス ペナルティ・存在の有無へのペナルティ)
・Context Length(コンテキストの長さ・文脈の長さ)

あたりが、回答精度に影響を与える印象を持っています。
また、以下のパラメータ

・Instructions(指示)
・Temperature(温度)
・Top P(核サンプリング)
・Frequency Penalty(フリークエンシー ペナルティ・繰り返しへのペナルティ)
・Presence Penalty(コンテキストの長さ・文脈の長さ)

を固定した状態で「Context Length」の数値を変更して、変化を見るといいかもしれません。

補足情報として

Embedding(エンベディング:埋め込み表現・ベクトル表現)

を有効化すると回答精度が下がる印象を受けていますので、無効化(初期設定は無効化)しておくのが無難かもしれません。

 

【③Google Colaboratory上でGGUFモデルを検証するコード等】

unslothの公式のチュートリアルコードで

・GGUF化後の検証コード

がなかったので、今回のチュートリアルコードでも検証コードを作成していませんでした...
もしご興味があれば、

・llama.cpp

を調べて、Google Colaboratoryやローカル環境で検証いただくといいかもしれません。
今後、時間が取れる時に、Google Colabolatory上で何かしらの形でllama.cppなどを使って推論できないか?検証してみますね。

【追記:2024年3月31日】

Google Colabolatory上でllama.cppで作成したGGUF形式のファイルで推論をするためのチュートリアルコードを公開しました。

【LLM入門】llama-cpp-pythonの使い方 - GGUF形式ファイルで推論編

 

 

【Janでの推論】色々試してみたが量子化すると16bitでファインチューニングのようにはいかない

 

 

【2024年4月1日時点】

以下は上述の「【Janでの推論】4-bit量子化&GGUF化すると16bitでファインチューニングしたような回答が返ってこなくなります」というコメントをくださった方の続編のコメントへの回答です。
コメントでは、回答を書いた上で質問をすると正答が得られることもあったようでしたが、それでファインチューニングの成果が出たと言えるのか?などとお悩みのようでした。
YouTubeの仕様かは分かりませんが、コメントを閲覧するにはコメントの表示順を「新しい順」にすると表示されるようでした。

 

【回答】

現状(2024年4月1日時点)の技術で、量子化前のLLMをローカル環境で気軽に動かすには

・パソコンのスペック(GPUメモリ)

が要求されますので、悩ましいところですね....
ローカル環境派の1人としての一抹の希望としては、2024年2月にマイクロソフトリサーチから公表された1-bit LLMのBitNet b1.58の技術の進展と普及に期待したいです。
(いつになるかは分かりませんが...)

ただ、これまでのLLMのファインチューニングのチュートリアルを作成するにあたって、LLMのファインチューニングについて調査していく中で実は・・・

・(RoLAまたはQLoRAの)ファインチューニングで、このチュートリアル動画のように「特定の知識を学習」させることは向いていない

ということを知っていましたが、実際にやってみるとできてしまったので、まだまだわからないことが多い領域なのではないかと思われます。
また、事前情報では

・「RLHF」(Reinforcement Learning from Human Feedback)
*人間による評価を利用した強化学習

も実施していかないと思うような回答が得られにくいという認識でいました。
そのため、RoLAまたはQLoRAのファインチューニングを実施するだけでも、正答に近い回答が得られることにも驚いています。

数年前までは、研究機関や資金力のある大企業でないとLLMを扱うことは難しい時代でしたが、現在は、自分のような低スペックのパソコンしか持っていない庶民が、LLMを

・(将来...)「実用的」に使うために色々なことを検証できる

ので、面白い時代になってきているのではないかと思います。
そして、これまでに機械学習の発展に関わってくださった偉大な先人の方々や、クラウド上でGPUを無料(または有料)で使わせていただけるサービス提供者の方々にも感謝したいです。

 

 

by 子供プログラマー

 

 

BitNet b1.58を学習中
【学習中...】1ビットLLMに挑戦してみたい:-1・0・1の3値でパラメータを表現 

気軽にチャットAIが始められるおすすめの拡張機能です。会員登録やログイン不要で使えるチャットAIもあります。
【使い方】ChatHub入門 – チャットAIをはじめよう