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