Views
No views yet
1# python 3.10.12
2!pip install -U pip
3!pip install -U transformers
4!pip install -U bitsandbytes
5!pip install -U accelerate
6!pip install -U datasets
7!pip install -U peft
8!pip install -U trl
9!pip install -U wandb
10!pip install ipywidgets --upgrade
11
12
13from transformers import (
14 AutoModelForCausalLM,
15 AutoTokenizer,
16 BitsAndBytesConfig,
17 TrainingArguments,
18 logging,
19)
20from peft import (
21 LoraConfig,
22 PeftModel,
23 get_peft_model,
24)
25import os, torch, gc
26from datasets import load_dataset
27import bitsandbytes as bnb
28from trl import SFTTrainer
29
30
31# Hugging Face Token
32HF_TOKEN = "your-token"
33
34
35# モデルを読み込み。
36# llm-jp-3 1.8B, 3.7B, 13Bのsnapshotをダウンロード済みでmodelsディレクトリに格納してあります。
37# base_model_idの値はomnicampusの環境におけるモデルのパスを表しており、それ以外の環境で実行する場合は変更の必要があります。
38# その他のモデルは取得に承諾が必要なため、各自でダウンロードお願いします。
39base_model_id = "llm-jp/llm-jp-3-13b" #Fine-Tuningするベースモデル
40# omnicampus以外の環境をご利用の方は以下をご利用ください。
41# base_model_id = "llm-jp/llm-jp-3-13b"
42new_model_id = "llm-jp-3-13b-finetune" #Fine-Tuningしたモデルにつけたい名前
43
44
45"""
46bnb_config: 量子化の設定
47
48 - load_in_4bit:
49 - 4bit量子化形式でモデルをロード
50
51 - bnb_4bit_quant_type:
52 - 量子化の形式を指定
53
54 - bnb_4bit_compute_dtype:
55 - 量子化された重みを用いて計算する際のデータ型
56
57"""
58
59bnb_config = BitsAndBytesConfig(
60 load_in_4bit=True,
61 bnb_4bit_quant_type="nf4", # nf4は通常のINT4より精度が高く、ニューラルネットワークの分布に最適です
62 bnb_4bit_compute_dtype=torch.bfloat16,
63)
64
65
66"""
67model: モデル
68
69 - base_model:
70 - 読み込むベースモデル (事前に定義したもの)
71
72 - quantization_config:
73 - bnb_configで設定した量子化設定
74
75 - device_map:
76 - モデルを割り当てるデバイス (CPU/GPU) "auto"で自動に割り当てられます。
77
78tokenizer: トークナイザー
79
80 - base_model:
81 - 読み込むベースモデル (事前に定義したもの)
82
83 - trust_remote_code:
84 - リモートコードの実行を許可 (カスタムモデルなど)
85"""
86model = AutoModelForCausalLM.from_pretrained(
87 base_model_id,
88 quantization_config=bnb_config,
89 device_map="auto"
90)
91
92tokenizer = AutoTokenizer.from_pretrained(base_model_id, trust_remote_code=True)
93
94
95"""
96find_all_linear_names: モデル内の4bit量子化線形層を探します。
97"""
98
99def find_all_linear_names(model):
100 cls = bnb.nn.Linear4bit # 4bit量子化線形層クラスを指定
101 lora_module_names = set() # ここに取得した線形層を保持します。
102
103 # モデル内の全てのモジュールを探索します
104 for name, module in model.named_modules():
105 if isinstance(module, cls): # モジュールが4bit量子化線形層の場合
106 names = name.split('.') # モジュールの名前を分割 (ネストされてる際などに対処)
107 lora_module_names.add(names[0] if len(names) == 1 else names[-1]) # 最下層の名前をlora_module_namesに追加
108
109 # 'lm_head' は16ビット演算の際に除外する必要があるため、lora_module_namesから削除
110 if 'lm_head' in lora_module_names:
111 lora_module_names.remove('lm_head')
112
113 return list(lora_module_names) # lora_module_namesをリストに変換して返します。
114
115modules = find_all_linear_names(model)
116
117
118"""
119peft_config: PEFTの構成設定
120
121 - r
122 - LoRA のランク (4, 8, 16 ,32...)
123 - 増やすほど学習が捗るが, 過学習のリスクも高まるので注意
124
125 - lora_alpha
126 - LoRAのスケーリング係数
127
128 - lora_dropout
129 - ドロップアウト率(過学習を防ぐための割合)
130
131 - bias
132 - バイアス項の扱い ("none"の場合、LoRAはバイアスを学習しない)
133
134 - task_type
135 - タスクタイプ
136
137 - target_modules
138 - LoRAを適用するターゲットモジュール (前のコードで特定した層)
139"""
140
141peft_config = LoraConfig(
142 r=16,
143 lora_alpha=32,
144 lora_dropout=0.05,
145 bias="none",
146 task_type="CAUSAL_LM",
147 target_modules=modules,
148)
149
150model = get_peft_model(model, peft_config)
151
152
153"""
154学習に用いるデータセットの指定
155今回はLLM-jp の公開している Ichikara Instruction を使います。データにアクセスするためには申請が必要ですので、使いたい方のみ申請をしてください。
156Ichikara Instruciton を Hugging Face Hub にて公開することはお控えください。
157また、CC-BY-NC-SAですのでモデルはライセンスを継承する前提でお使いください。
158
159下記のリンクから申請を終えた先に Google Drive があり、Distribution20241221_all というフォルダごとダウンロードしてください。
160今回は「ichikara-instruction-003-001-1.json」を使います。必要であれば展開(!unzip など)し、データセットのパスを適切に指定してください。
161omnicampusの開発環境では取得したデータを左側にドラッグアンドドロップしてお使いください。
162
163https://liat-aip.sakura.ne.jp/wp/llmのための日本語インストラクションデータ作成/llmのための日本語インストラクションデータ-公開/
164関根聡, 安藤まや, 後藤美知子, 鈴木久美, 河原大輔, 井之上直也, 乾健太郎. ichikara-instruction: LLMのための日本語インストラクションデータの構築. 言語処理学会第30回年次大会(2024)
165
166"""
167
168dataset = load_dataset("json", data_files="/content/ichikara-instruction-003-001-1.json")
169dataset
170
171
172# 学習時のプロンプトフォーマットの定義
173prompt = """### 指示
174{}
175### 回答
176{}"""
177
178
179"""
180formatting_prompts_func: 各データをプロンプトに合わせた形式に合わせる
181"""
182EOS_TOKEN = tokenizer.eos_token # トークナイザーのEOSトークン(文末トークン)
183def formatting_prompts_func(examples):
184 input = examples["text"] # 入力データ
185 output = examples["output"] # 出力データ
186 text = prompt.format(input, output) + EOS_TOKEN # プロンプトの作成
187 return { "formatted_text" : text, } # 新しいフィールド "formatted_text" を返す
188pass
189
190# # 各データにフォーマットを適用
191dataset = dataset.map(
192 formatting_prompts_func,
193 num_proc= 4, # 並列処理数を指定
194)
195
196dataset
197
198
199# データを確認
200print(dataset["train"]["formatted_text"][3])
201
202
203# データをtrainデータとtestデータに分割 (test_sizeの比率に)
204# dataset = dataset["train"].train_test_split(test_size=0.1)
205# dataset
206
207
208"""
209training_arguments: 学習の設定
210
211 - output_dir:
212 -トレーニング後のモデルを保存するディレクトリ
213
214 - per_device_train_batch_size:
215 - デバイスごとのトレーニングバッチサイズ
216
217 - per_device_
218 _batch_size:
219 - デバイスごとの評価バッチサイズ
220
221 - gradient_accumulation_steps:
222 - 勾配を更新する前にステップを積み重ねる回数
223
224 - optim:
225 - オプティマイザの設定
226
227 - num_train_epochs:
228 - エポック数
229
230 - eval_strategy:
231 - 評価の戦略 ("no"/"steps"/"epoch")
232
233 - eval_steps:
234 - eval_strategyが"steps"のとき、評価を行うstep間隔
235
236 - logging_strategy:
237 - ログ記録の戦略
238
239 - logging_steps:
240 - ログを出力するステップ間隔
241
242 - warmup_steps:
243 - 学習率のウォームアップステップ数
244
245 - save_steps:
246 - モデルを保存するステップ間隔
247
248 - save_total_limit:
249 - 保存しておくcheckpointの数
250
251 - max_steps:
252 - トレーニングの最大ステップ数
253
254 - learning_rate:
255 - 学習率
256
257 - fp16:
258 - 16bit浮動小数点の使用設定(第8回演習を参考にすると良いです)
259
260 - bf16:
261 - BFloat16の使用設定
262
263 - group_by_length:
264 - 入力シーケンスの長さによりバッチをグループ化 (トレーニングの効率化)
265
266 - report_to:
267 - ログの送信先 ("wandb"/"tensorboard"など)
268"""
269
270training_arguments = TrainingArguments(
271 output_dir=new_model_id,
272 per_device_train_batch_size=1,
273 gradient_accumulation_steps=2,
274 optim="paged_adamw_32bit",
275 num_train_epochs=1,
276 logging_strategy="steps",
277 logging_steps=10,
278 warmup_steps=10,
279 save_steps=100,
280 save_total_limit = 2,
281 max_steps = -1,
282 learning_rate=5e-5,
283 fp16=False,
284 bf16=False,
285 seed = 3407,
286 group_by_length=True,
287 report_to="none"
288)
289
290
291"""
292SFTTrainer: Supervised Fine-Tuningに関する設定
293
294 - model:
295 - 読み込んだベースのモデル
296
297 - train_dataset:
298 - トレーニングに使用するデータセット
299
300 - eval_dataset:
301 - 評価に使用するデータセット
302
303 - peft_config:
304 - PEFT(Parameter-Efficient Fine-Tuning)の設定(LoRAを利用する場合に指定)
305
306 - max_seq_length:
307 - モデルに入力されるシーケンスの最大トークン長
308
309 - dataset_text_field:
310 - データセット内の学習に使うテキストを含むフィールド名
311
312 - tokenizer:
313 - モデルに対応するトークナイザー
314
315 - args:
316 - トレーニングに使用するハイパーパラメータ(TrainingArgumentsの設定を指定)
317
318 - packing:
319 - 入力シーケンスのパッキングを行うかどうかの設定 (False に設定することで、各入力を独立して扱う)
320"""
321trainer = SFTTrainer(
322 model=model,
323 train_dataset=dataset["train"],
324 peft_config=peft_config,
325 max_seq_length= 512,
326 dataset_text_field="formatted_text",
327 tokenizer=tokenizer,
328 args=training_arguments,
329 packing= False,
330)
331
332model.config.use_cache = False # キャッシュ機能を無効化
333trainer.train() # トレーニングを実行
334
335
336# タスクとなるデータの読み込み。
337# omnicampusの開発環境では、左にタスクのjsonlをドラッグアンドドロップしてから実行。
338import json
339datasets = []
340with open("./elyza-tasks-100-TV_0.jsonl", "r") as f:
341 item = ""
342 for line in f:
343 line = line.strip()
344 item += line
345 if item.endswith("}"):
346 datasets.append(json.loads(item))
347 item = ""
348
349
350# モデルによるタスクの推論。
351from tqdm import tqdm
352
353results = []
354for data in tqdm(datasets):
355
356 input = data["input"]
357
358 prompt = f"""### 指示
359 {input}
360 ### 回答
361 """
362
363 tokenized_input = tokenizer.encode(prompt, add_special_tokens=False, return_tensors="pt").to(model.device)
364 attention_mask = torch.ones_like(tokenized_input)
365
366 with torch.no_grad():
367 outputs = model.generate(
368 tokenized_input,
369 attention_mask=attention_mask,
370 max_new_tokens=100,
371 do_sample=False,
372 repetition_penalty=1.2,
373 pad_token_id=tokenizer.eos_token_id
374 )[0]
375 output = tokenizer.decode(outputs[tokenized_input.size(1):], skip_special_tokens=True)
376
377 results.append({"task_id": data["task_id"], "input": input, "output": output})
378
379
380# こちらで生成されたjsolを提出してください。
381# 本コードではinputとeval_aspectも含んでいますが、なくても問題ありません。
382# 必須なのはtask_idとoutputとなります。
383import re
384jsonl_id = re.sub(".*/", "", new_model_id)
385with open(f"./{jsonl_id}-outputs.jsonl", 'w', encoding='utf-8') as f:
386 for result in results:
387 json.dump(result, f, ensure_ascii=False) # ensure_ascii=False for handling non-ASCII characters
388 f.write('\n')
389
390
391# モデルとトークナイザーをHugging Faceにアップロード
392model.push_to_hub(new_model_id, token=HF_TOKEN, private=True) # Online saving
393tokenizer.push_to_hub(new_model_id, token=HF_TOKEN, private=True) # Online saving