File size: 5,926 Bytes
03171a5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from unsloth import FastLanguageModel
import torch
from datasets import load_dataset, concatenate_datasets

from trl import SFTTrainer
from transformers import TrainingArguments
from unsloth import is_bfloat16_supported

max_seq_length = 512 # unslothではRoPEをサポートしているのでコンテキスト長は自由に設定可能
dtype = None # Noneにしておけば自動で設定
load_in_4bit = True # 今回は8Bクラスのモデルを扱うためTrue

model_id = "llm-jp/llm-jp-3-13b"
new_model_id = "llm-jp-3-13b-it" #Fine-Tuningしたモデルにつけたい名前、it: Instruction Tuning
# FastLanguageModel インスタンスを作成
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name=model_id,
    dtype=dtype,
    load_in_4bit=load_in_4bit,
    trust_remote_code=True,
)

# SFT用のモデルを用意
model = FastLanguageModel.get_peft_model(
    model,
    r = 32,
    target_modules = ["q_proj", "k_proj", "v_proj", "o_proj",
                      "gate_proj", "up_proj", "down_proj",],
    lora_alpha = 32,
    lora_dropout = 0.05,
    bias = "none",
    use_gradient_checkpointing = "unsloth",
    random_state = 3407,
    use_rslora = False,
    loftq_config = None,
    max_seq_length = max_seq_length,
)


datasets_list = [
    "/home/knishizawa/Matsuo_AI/LLM_Course2024/Distribution20241221_all/ichikara-instruction-003-001-1.json",
    "/home/knishizawa/Matsuo_AI/LLM_Course2024/Distribution20241221_all/ichikara-instruction-003-001-2.1.json",
    "/home/knishizawa/Matsuo_AI/LLM_Course2024/Distribution20241221_all/ichikara-instruction-003-001-2.2.json",
    "/home/knishizawa/Matsuo_AI/LLM_Course2024/Distribution20241221_all/ichikara-instruction-003-001-5.1.json",
    "/home/knishizawa/Matsuo_AI/LLM_Course2024/Distribution20241221_all/ichikara-instruction-003-001-5.2.json",
    "/home/knishizawa/Matsuo_AI/LLM_Course2024/Distribution20241221_all/ichikara-instruction-003-002-1.json",
    "/home/knishizawa/Matsuo_AI/LLM_Course2024/Distribution20241221_all/ichikara-instruction-003-003-1.json"
]

valid_datasets = []

# 学習時のプロンプトフォーマットの定義
prompt = """### 指示
{}
### 回答
{}"""
EOS_TOKEN = tokenizer.eos_token  # トークナイザーのEOSトークン

# フォーマット関数
def formatting_prompts_func(examples):
    input_text = examples["text"]
    output_text = examples["output"]
    text = prompt.format(input_text, output_text) + EOS_TOKEN
    return { "formatted_text": text }

# データセットのロードとフォーマット
for file in datasets_list:
    try:
        dataset = load_dataset("json", data_files=file, split="train")
        dataset = dataset.map(formatting_prompts_func, num_proc=4)
        valid_datasets.append(dataset)
        print(f"成功: {file} - {len(dataset)} 件ロード")
        # データ確認
        print(dataset[3]["formatted_text"])
    except Exception as e:
        print(f"エラー: {file} - {e}")

# マージと保存
if valid_datasets:
    merged_dataset = concatenate_datasets(valid_datasets)
    if len(merged_dataset) > 0:
        save_dir = "/home/knishizawa/Matsuo_AI/LLM_Course2024/merged_dataset"
        merged_dataset.save_to_disk(save_dir)
        print(f"マージされたデータセットが {save_dir} に保存されました。")
    else:
        print("マージされたデータセットが空です。")
else:
    print("有効なデータセットが見つかりませんでした。")


trainer = SFTTrainer(
    model = model,
    tokenizer = tokenizer,
    train_dataset=merged_dataset,
    max_seq_length = max_seq_length,
    dataset_text_field="formatted_text",
    packing = False,
    args = TrainingArguments(
        per_device_train_batch_size = 2,
        gradient_accumulation_steps = 4,
        num_train_epochs = 1,
        logging_steps = 10,
        warmup_steps = 10,
        save_steps=100,
        save_total_limit=2,
        max_steps=-1,
        learning_rate = 2e-4,
        fp16 = not is_bfloat16_supported(),
        bf16 = is_bfloat16_supported(),
        group_by_length=True,
        seed = 3407,
        output_dir = "outputs",
        report_to = "none",
    ),
)

# 学習実行
trainer_stats = trainer.train()
# モデルの保存ディレクトリ
save_dir = "./saved_model"
# モデルの保存
model.save_pretrained(save_dir)
# トークナイザの保存 (必要に応じて)
tokenizer.save_pretrained(save_dir)
print(f"モデルが {save_dir} に保存されました。")



# ELYZA-tasks-100-TVの読み込み。事前にファイルをアップロードしてください
# データセットの読み込み。
# omnicampusの開発環境では、左にタスクのjsonlをドラッグアンドドロップしてから実行。
import json
datasets = []
with open("./elyza-tasks-100-TV_0.jsonl", "r") as f:
    item = ""
    for line in f:
      line = line.strip()
      item += line
      if item.endswith("}"):
        datasets.append(json.loads(item))
        item = ""

# 学習したモデルを用いてタスクを実行
from tqdm import tqdm

# 推論するためにモデルのモードを変更
FastLanguageModel.for_inference(model)

results = []
for dt in tqdm(datasets):
  input = dt["input"]

  prompt = f"""### 指示\n{input}\n### 回答\n"""

  inputs = tokenizer([prompt], return_tensors = "pt").to(model.device)

  outputs = model.generate(**inputs, max_new_tokens = 512, use_cache = True, do_sample=False, repetition_penalty=1.2)
  prediction = tokenizer.decode(outputs[0], skip_special_tokens=True).split('\n### 回答')[-1]

  results.append({"task_id": dt["task_id"], "input": input, "output": prediction})

# jsonlで保存
with open(f"{new_model_id}_output.jsonl", 'w', encoding='utf-8') as f:
    for result in results:
        json.dump(result, f, ensure_ascii=False)
        f.write('\n')