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