| |
| """ |
| Fine-tune Qwen3-4B for immersive Chinese roleplay (角色扮演). |
| Combines shibing624 roleplay-zh + ChatHaruhi-54K datasets. |
| Requirements: fast plot progression, strong immersion. |
| |
| Usage: |
| pip install transformers trl torch datasets trackio accelerate peft |
| python train_roleplay.py |
| |
| Hardware: a10g-largex2 (2x24GB GPU) recommended |
| Estimated time: ~4 hours for 3 epochs |
| """ |
|
|
| import os |
| import random |
|
|
| |
| os.environ["TRACKIO_PROJECT"] = "qwen3-4b-roleplay" |
|
|
| from datasets import load_dataset, concatenate_datasets, Dataset |
| from trl import SFTTrainer, SFTConfig |
|
|
| |
| |
| |
| MODEL_ID = "Qwen/Qwen3-4B" |
| OUTPUT_MODEL_ID = "Givenn/Qwen3-4B-Roleplay-Chinese" |
| MAX_SEQ_LENGTH = 4096 |
| NUM_TRAIN_EPOCHS = 3 |
| LEARNING_RATE = 2e-5 |
| PER_DEVICE_BATCH_SIZE = 2 |
| GRADIENT_ACCUMULATION_STEPS = 8 |
|
|
| |
| |
| |
| print("=" * 60) |
| print("Loading datasets...") |
| print("=" * 60) |
|
|
| |
| configs = [ |
| "sharegpt_formatted_data-evol-gpt4", |
| "sharegpt_formatted_data-evol-gpt35", |
| "sharegpt_formatted_data-evol-male-gpt35", |
| "sharegpt_formatted_data-roleplay-chat-1k", |
| ] |
|
|
| shibing_datasets = [] |
| for cfg in configs: |
| ds = load_dataset( |
| "shibing624/roleplay-zh-sharegpt-gpt4-data", |
| name=cfg, |
| split="train", |
| ) |
| shibing_datasets.append(ds) |
| print(f" Loaded shibing624/{cfg}: {len(ds)} samples") |
|
|
| |
| def convert_shibing_to_messages(example): |
| messages = [] |
| if example.get("system_prompt") and example["system_prompt"].strip(): |
| messages.append({ |
| "role": "system", |
| "content": example["system_prompt"].strip() |
| }) |
| for turn in example["conversations"]: |
| role = "user" if turn["from"] == "human" else "assistant" |
| messages.append({ |
| "role": role, |
| "content": turn["value"] |
| }) |
| return {"messages": messages} |
|
|
| converted_shibing = [] |
| for ds in shibing_datasets: |
| converted = ds.map( |
| convert_shibing_to_messages, |
| remove_columns=ds.column_names, |
| num_proc=4, |
| ) |
| converted_shibing.append(converted) |
|
|
| shibing_combined = concatenate_datasets(converted_shibing) |
| print(f"\nTotal shibing624 samples: {len(shibing_combined)}") |
|
|
| |
| print("\nLoading ChatHaruhi-54K...") |
| haruhi_ds = load_dataset( |
| "silk-road/ChatHaruhi-54K-Role-Playing-Dialogue", |
| split="train", |
| ) |
| print(f" Loaded ChatHaruhi-54K: {len(haruhi_ds)} samples") |
|
|
| def convert_haruhi_to_messages(example): |
| messages = [] |
| agent_role = example.get("agent_role", "") |
| system_content = f'你现在扮演"{agent_role}"。请完全沉浸在角色中,用角色的语气、性格和说话方式来回应。保持角色一致性,推动剧情发展,营造沉浸感。' |
| messages.append({"role": "system", "content": system_content}) |
| |
| more_dialogues = example.get("more_dialogues", []) |
| if more_dialogues and len(more_dialogues) > 0: |
| for dialogue in more_dialogues: |
| if isinstance(dialogue, str) and ":" in dialogue: |
| parts = dialogue.split(":", 1) |
| if len(parts) == 2: |
| speaker = parts[0].strip() |
| content = parts[1].strip() |
| if speaker == agent_role: |
| messages.append({"role": "assistant", "content": content}) |
| else: |
| messages.append({"role": "user", "content": f"({speaker}){content}"}) |
| |
| user_role = example.get("user_role", "") |
| user_question = example.get("user_question", "") |
| if user_role: |
| messages.append({"role": "user", "content": f"({user_role}){user_question}"}) |
| else: |
| messages.append({"role": "user", "content": user_question}) |
| |
| agent_response = example.get("agent_response", "") |
| messages.append({"role": "assistant", "content": agent_response}) |
| |
| return {"messages": messages} |
|
|
| haruhi_converted = haruhi_ds.map( |
| convert_haruhi_to_messages, |
| remove_columns=haruhi_ds.column_names, |
| num_proc=4, |
| ) |
| print(f" Converted ChatHaruhi: {len(haruhi_converted)} samples") |
|
|
| |
| |
| |
| combined_dataset = concatenate_datasets([shibing_combined, haruhi_converted]) |
| combined_dataset = combined_dataset.shuffle(seed=42) |
| print(f"\n{'=' * 60}") |
| print(f"Total combined dataset: {len(combined_dataset)} samples") |
| print(f"{'=' * 60}") |
|
|
| |
| print("\n--- Sample data ---") |
| sample = combined_dataset[0] |
| for msg in sample["messages"][:3]: |
| print(f"[{msg['role']}]: {msg['content'][:100]}...") |
| print("---") |
|
|
| |
| |
| |
| print("\nInitializing SFTConfig...") |
|
|
| training_args = SFTConfig( |
| output_dir="./qwen3-4b-roleplay", |
| |
| |
| num_train_epochs=NUM_TRAIN_EPOCHS, |
| per_device_train_batch_size=PER_DEVICE_BATCH_SIZE, |
| gradient_accumulation_steps=GRADIENT_ACCUMULATION_STEPS, |
| learning_rate=LEARNING_RATE, |
| lr_scheduler_type="cosine", |
| warmup_steps=100, |
| weight_decay=0.01, |
| optim="adamw_torch_fused", |
| |
| |
| bf16=True, |
| gradient_checkpointing=True, |
| max_length=MAX_SEQ_LENGTH, |
| |
| |
| completion_only_loss=True, |
| |
| |
| logging_steps=5, |
| logging_first_step=True, |
| disable_tqdm=True, |
| report_to="trackio", |
| run_name="qwen3-4b-roleplay-zh", |
| |
| |
| save_strategy="steps", |
| save_steps=500, |
| save_total_limit=3, |
| push_to_hub=True, |
| hub_model_id=OUTPUT_MODEL_ID, |
| hub_strategy="every_save", |
| |
| |
| dataset_num_proc=4, |
| |
| |
| seed=42, |
| data_seed=42, |
| ) |
|
|
| print("Initializing SFTTrainer...") |
| trainer = SFTTrainer( |
| model=MODEL_ID, |
| args=training_args, |
| train_dataset=combined_dataset, |
| ) |
|
|
| |
| |
| |
| print(f"\n{'=' * 60}") |
| print("Starting training...") |
| print(f" Model: {MODEL_ID}") |
| print(f" Dataset size: {len(combined_dataset)}") |
| print(f" Epochs: {NUM_TRAIN_EPOCHS}") |
| print(f" Effective batch size: {PER_DEVICE_BATCH_SIZE * GRADIENT_ACCUMULATION_STEPS}") |
| print(f" Learning rate: {LEARNING_RATE}") |
| print(f" Max sequence length: {MAX_SEQ_LENGTH}") |
| print(f" Output: {OUTPUT_MODEL_ID}") |
| print(f"{'=' * 60}\n") |
|
|
| trainer.train() |
|
|
| |
| |
| |
| print("\nSaving final model...") |
| trainer.save_model() |
| trainer.push_to_hub() |
| print(f"\nModel pushed to: https://huggingface.co/{OUTPUT_MODEL_ID}") |
| print("Training complete!") |
|
|