minimind/train_qwen_lora.py

163 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import os
import json
import torch
from torch.utils.data import Dataset, DataLoader
from transformers import AutoTokenizer, AutoModelForCausalLM
from peft import LoraConfig, get_peft_model, TaskType
from modelscope import snapshot_download
# ==================== 1. 动态填充的高效通信与专利数据集 ====================
class TelecomPatentDataset(Dataset):
def __init__(self, jsonl_path, tokenizer, max_length=384):
self.data = []
self.tokenizer = tokenizer
self.max_length = max_length
with open(jsonl_path, 'r', encoding='utf-8') as f:
for line in f:
if line.strip():
self.data.append(json.loads(line.strip()))
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
messages = self.data[idx]["messages"]
prompt = self.tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=False
)
# 不使用全局静态 padding,仅在此处截断,由 collate_fn 做动态批次 padding
encoded = self.tokenizer(
prompt,
max_length=self.max_length,
truncation=True,
return_tensors=None
)
return {
"input_ids": encoded["input_ids"],
"attention_mask": encoded["attention_mask"]
}
def collate_fn(batch, tokenizer):
# 动态批次填充:仅填充至当前 batch 的最大实际长度,节省 70% 显存与计算量
max_len = max(len(x["input_ids"]) for x in batch)
# 向上对齐到 8 的倍数,加速 Tensor Core 计算
max_len = (max_len + 7) // 8 * 8
pad_id = tokenizer.pad_token_id or 0
input_ids_batch = []
attention_mask_batch = []
labels_batch = []
for x in batch:
cur_len = len(x["input_ids"])
pad_len = max_len - cur_len
input_ids = x["input_ids"] + [pad_id] * pad_len
attention_mask = x["attention_mask"] + [0] * pad_len
labels = x["input_ids"] + [-100] * pad_len
input_ids_batch.append(input_ids)
attention_mask_batch.append(attention_mask)
labels_batch.append(labels)
return (
torch.tensor(input_ids_batch, dtype=torch.long),
torch.tensor(attention_mask_batch, dtype=torch.long),
torch.tensor(labels_batch, dtype=torch.long)
)
def main():
device = "cuda:0" if torch.cuda.is_available() else "cpu"
print(f"[Device] 当前训练设备: {device} ({torch.cuda.get_device_name(0) if 'cuda' in device else 'CPU'})", flush=True)
# ==================== 2. 加载 Qwen2.5 底座模型 ====================
model_id = "Qwen/Qwen2.5-0.5B-Instruct"
print(f"[ModelScope] 正在加载模型 {model_id} ...", flush=True)
model_dir = snapshot_download(model_id)
tokenizer = AutoTokenizer.from_pretrained(model_dir, trust_remote_code=True)
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained(
model_dir,
torch_dtype=torch.bfloat16,
device_map=device,
trust_remote_code=True
)
# ==================== 3. 配置 LoRA 适配层 (PEFT) ====================
peft_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=8,
lora_alpha=16,
lora_dropout=0.05,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]
)
model = get_peft_model(model, peft_config)
trainable_params, all_params = model.get_nb_trainable_parameters()
print(f"[Params] 总参数: {all_params / 1e6:.2f} M | LoRA 可训参数: {trainable_params / 1e6:.2f} M | 占比: {100 * trainable_params / all_params:.2f}%", flush=True)
# ==================== 4. 数据加载与优化器 ====================
dataset_path = "dataset/telecom_patent_sft.jsonl"
if not os.path.exists(dataset_path):
raise FileNotFoundError(f"未找到数据集 {dataset_path},请先运行 python prepare_data.py")
dataset = TelecomPatentDataset(dataset_path, tokenizer, max_length=384)
batch_size = 4
accumulation_steps = 2
dataloader = DataLoader(
dataset,
batch_size=batch_size,
shuffle=True,
collate_fn=lambda b: collate_fn(b, tokenizer)
)
epochs = 1 # 2600条高质量数据跑 1 轮(650个批次)即已充分收敛
total_steps = len(dataloader) // accumulation_steps
optimizer = torch.optim.AdamW(model.parameters(), lr=2e-4)
model.train()
print(f"[Training] 开始动态轻量微调 2600 条数据 (优化步数: {total_steps} 步) ...", flush=True)
step = 0
opt_step = 0
total_loss = 0.0
optimizer.zero_grad()
for input_ids, attention_mask, labels in dataloader:
step += 1
input_ids = input_ids.to(device)
attention_mask = attention_mask.to(device)
labels = labels.to(device)
outputs = model(input_ids=input_ids, attention_mask=attention_mask, labels=labels)
loss = outputs.loss / accumulation_steps
loss.backward()
total_loss += outputs.loss.item()
if step % accumulation_steps == 0:
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
optimizer.step()
optimizer.zero_grad()
opt_step += 1
if opt_step % 25 == 0 or opt_step == total_steps:
print(f"Step [{opt_step}/{total_steps}] | Loss: {outputs.loss.item():.4f}", flush=True)
avg_loss = total_loss / len(dataloader)
print(f"[Done] 训练完成!平均 Loss: {avg_loss:.4f}", flush=True)
# ==================== 5. 保存 LoRA 权重 ====================
save_dir = "output/qwen_telecom_lora"
os.makedirs(save_dir, exist_ok=True)
model.save_pretrained(save_dir)
tokenizer.save_pretrained(save_dir)
print(f"\n[Saved] 2600条数据训练成功!LoRA 权重已保存至: {save_dir}", flush=True)
if __name__ == "__main__":
main()