Text Generation
Chinese
English
roleplay
角色扮演
chinese
sft
conversational
creative-writing
digital-human
Givenn commited on
Commit
573cc43
·
verified ·
1 Parent(s): ad40d50

Add roleplay SFT training script

Browse files
Files changed (1) hide show
  1. train_roleplay.py +229 -0
train_roleplay.py ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Fine-tune Qwen3-4B for immersive Chinese roleplay (角色扮演).
4
+ Combines shibing624 roleplay-zh + ChatHaruhi-54K datasets.
5
+ Requirements: fast plot progression, strong immersion.
6
+
7
+ Usage:
8
+ pip install transformers trl torch datasets trackio accelerate peft
9
+ python train_roleplay.py
10
+
11
+ Hardware: a10g-largex2 (2x24GB GPU) recommended
12
+ Estimated time: ~4 hours for 3 epochs
13
+ """
14
+
15
+ import os
16
+ import random
17
+
18
+ # Trackio monitoring setup
19
+ os.environ["TRACKIO_PROJECT"] = "qwen3-4b-roleplay"
20
+
21
+ from datasets import load_dataset, concatenate_datasets, Dataset
22
+ from trl import SFTTrainer, SFTConfig
23
+
24
+ # ============================================================
25
+ # Configuration
26
+ # ============================================================
27
+ MODEL_ID = "Qwen/Qwen3-4B"
28
+ OUTPUT_MODEL_ID = "Givenn/Qwen3-4B-Roleplay-Chinese"
29
+ MAX_SEQ_LENGTH = 4096
30
+ NUM_TRAIN_EPOCHS = 3
31
+ LEARNING_RATE = 2e-5
32
+ PER_DEVICE_BATCH_SIZE = 2
33
+ GRADIENT_ACCUMULATION_STEPS = 8 # effective batch = 16
34
+
35
+ # ============================================================
36
+ # 1. Load and prepare datasets
37
+ # ============================================================
38
+ print("=" * 60)
39
+ print("Loading datasets...")
40
+ print("=" * 60)
41
+
42
+ # --- Dataset 1: shibing624 roleplay-zh (ShareGPT format) ---
43
+ configs = [
44
+ "sharegpt_formatted_data-evol-gpt4",
45
+ "sharegpt_formatted_data-evol-gpt35",
46
+ "sharegpt_formatted_data-evol-male-gpt35",
47
+ "sharegpt_formatted_data-roleplay-chat-1k",
48
+ ]
49
+
50
+ shibing_datasets = []
51
+ for cfg in configs:
52
+ ds = load_dataset(
53
+ "shibing624/roleplay-zh-sharegpt-gpt4-data",
54
+ name=cfg,
55
+ split="train",
56
+ )
57
+ shibing_datasets.append(ds)
58
+ print(f" Loaded shibing624/{cfg}: {len(ds)} samples")
59
+
60
+ # Convert shibing624 ShareGPT -> messages format
61
+ def convert_shibing_to_messages(example):
62
+ messages = []
63
+ if example.get("system_prompt") and example["system_prompt"].strip():
64
+ messages.append({
65
+ "role": "system",
66
+ "content": example["system_prompt"].strip()
67
+ })
68
+ for turn in example["conversations"]:
69
+ role = "user" if turn["from"] == "human" else "assistant"
70
+ messages.append({
71
+ "role": role,
72
+ "content": turn["value"]
73
+ })
74
+ return {"messages": messages}
75
+
76
+ converted_shibing = []
77
+ for ds in shibing_datasets:
78
+ converted = ds.map(
79
+ convert_shibing_to_messages,
80
+ remove_columns=ds.column_names,
81
+ num_proc=4,
82
+ )
83
+ converted_shibing.append(converted)
84
+
85
+ shibing_combined = concatenate_datasets(converted_shibing)
86
+ print(f"\nTotal shibing624 samples: {len(shibing_combined)}")
87
+
88
+ # --- Dataset 2: ChatHaruhi-54K (Chinese novel characters) ---
89
+ print("\nLoading ChatHaruhi-54K...")
90
+ haruhi_ds = load_dataset(
91
+ "silk-road/ChatHaruhi-54K-Role-Playing-Dialogue",
92
+ split="train",
93
+ )
94
+ print(f" Loaded ChatHaruhi-54K: {len(haruhi_ds)} samples")
95
+
96
+ def convert_haruhi_to_messages(example):
97
+ messages = []
98
+ agent_role = example.get("agent_role", "")
99
+ system_content = f'你现在扮演"{agent_role}"。请完全沉浸在角色中,用角色的语气、性格和说话方式来回应。保持角色一致性,推动剧情发展,营造沉浸感。'
100
+ messages.append({"role": "system", "content": system_content})
101
+
102
+ more_dialogues = example.get("more_dialogues", [])
103
+ if more_dialogues and len(more_dialogues) > 0:
104
+ for dialogue in more_dialogues:
105
+ if isinstance(dialogue, str) and ":" in dialogue:
106
+ parts = dialogue.split(":", 1)
107
+ if len(parts) == 2:
108
+ speaker = parts[0].strip()
109
+ content = parts[1].strip()
110
+ if speaker == agent_role:
111
+ messages.append({"role": "assistant", "content": content})
112
+ else:
113
+ messages.append({"role": "user", "content": f"({speaker}){content}"})
114
+
115
+ user_role = example.get("user_role", "")
116
+ user_question = example.get("user_question", "")
117
+ if user_role:
118
+ messages.append({"role": "user", "content": f"({user_role}){user_question}"})
119
+ else:
120
+ messages.append({"role": "user", "content": user_question})
121
+
122
+ agent_response = example.get("agent_response", "")
123
+ messages.append({"role": "assistant", "content": agent_response})
124
+
125
+ return {"messages": messages}
126
+
127
+ haruhi_converted = haruhi_ds.map(
128
+ convert_haruhi_to_messages,
129
+ remove_columns=haruhi_ds.column_names,
130
+ num_proc=4,
131
+ )
132
+ print(f" Converted ChatHaruhi: {len(haruhi_converted)} samples")
133
+
134
+ # ============================================================
135
+ # 2. Combine all datasets
136
+ # ============================================================
137
+ combined_dataset = concatenate_datasets([shibing_combined, haruhi_converted])
138
+ combined_dataset = combined_dataset.shuffle(seed=42)
139
+ print(f"\n{'=' * 60}")
140
+ print(f"Total combined dataset: {len(combined_dataset)} samples")
141
+ print(f"{'=' * 60}")
142
+
143
+ # Preview a sample
144
+ print("\n--- Sample data ---")
145
+ sample = combined_dataset[0]
146
+ for msg in sample["messages"][:3]:
147
+ print(f"[{msg['role']}]: {msg['content'][:100]}...")
148
+ print("---")
149
+
150
+ # ============================================================
151
+ # 3. Setup training with SFTTrainer
152
+ # ============================================================
153
+ print("\nInitializing SFTConfig...")
154
+
155
+ training_args = SFTConfig(
156
+ output_dir="./qwen3-4b-roleplay",
157
+
158
+ # Training hyperparameters
159
+ num_train_epochs=NUM_TRAIN_EPOCHS,
160
+ per_device_train_batch_size=PER_DEVICE_BATCH_SIZE,
161
+ gradient_accumulation_steps=GRADIENT_ACCUMULATION_STEPS,
162
+ learning_rate=LEARNING_RATE,
163
+ lr_scheduler_type="cosine",
164
+ warmup_steps=100,
165
+ weight_decay=0.01,
166
+ optim="adamw_torch_fused",
167
+
168
+ # Precision & memory
169
+ bf16=True,
170
+ gradient_checkpointing=True,
171
+ max_length=MAX_SEQ_LENGTH,
172
+
173
+ # Only train on assistant responses (loss masking)
174
+ completion_only_loss=True,
175
+
176
+ # Logging
177
+ logging_steps=5,
178
+ logging_first_step=True,
179
+ disable_tqdm=True,
180
+ report_to="trackio",
181
+ run_name="qwen3-4b-roleplay-zh",
182
+
183
+ # Saving & Hub
184
+ save_strategy="steps",
185
+ save_steps=500,
186
+ save_total_limit=3,
187
+ push_to_hub=True,
188
+ hub_model_id=OUTPUT_MODEL_ID,
189
+ hub_strategy="every_save",
190
+
191
+ # Dataset processing
192
+ dataset_num_proc=4,
193
+
194
+ # Seed
195
+ seed=42,
196
+ data_seed=42,
197
+ )
198
+
199
+ print("Initializing SFTTrainer...")
200
+ trainer = SFTTrainer(
201
+ model=MODEL_ID,
202
+ args=training_args,
203
+ train_dataset=combined_dataset,
204
+ )
205
+
206
+ # ============================================================
207
+ # 4. Train
208
+ # ============================================================
209
+ print(f"\n{'=' * 60}")
210
+ print("Starting training...")
211
+ print(f" Model: {MODEL_ID}")
212
+ print(f" Dataset size: {len(combined_dataset)}")
213
+ print(f" Epochs: {NUM_TRAIN_EPOCHS}")
214
+ print(f" Effective batch size: {PER_DEVICE_BATCH_SIZE * GRADIENT_ACCUMULATION_STEPS}")
215
+ print(f" Learning rate: {LEARNING_RATE}")
216
+ print(f" Max sequence length: {MAX_SEQ_LENGTH}")
217
+ print(f" Output: {OUTPUT_MODEL_ID}")
218
+ print(f"{'=' * 60}\n")
219
+
220
+ trainer.train()
221
+
222
+ # ============================================================
223
+ # 5. Save & push
224
+ # ============================================================
225
+ print("\nSaving final model...")
226
+ trainer.save_model()
227
+ trainer.push_to_hub()
228
+ print(f"\nModel pushed to: https://huggingface.co/{OUTPUT_MODEL_ID}")
229
+ print("Training complete!")