lora_train.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. from transformers import (AutoTokenizer,
  2. AutoModelForCausalLM, #因果模型(自回归模型)
  3. DataCollatorForLanguageModeling,#数据整理器, 用于将分词后的输入数据整理成模型训练所需要的格式
  4. Trainer,#训练器
  5. TrainingArguments
  6. )
  7. from datasets import load_dataset
  8. #设置预训练模型的路径
  9. model_name="/root/autodl-tmp/code/model"
  10. #设置微调后模型保存的路径
  11. finetuned_model_path="/root/autodl-tmp/code/fined"
  12. #加载模型
  13. # ---------- 第 1 步:配置 4bit 量化 ----------
  14. model = AutoModelForCausalLM.from_pretrained(
  15. model_name,
  16. local_files_only = True, #仅从本地文件加载模型
  17. )
  18. #加载预训练模型的tokenizer
  19. tokenizer = AutoTokenizer.from_pretrained(model_name)
  20. def preprocess_function(examples):
  21. """
  22. 标准的 SFT 数据处理流程
  23. - 只训练 assistant 的回答部分
  24. - 正确处理截断和 EOS token
  25. - 保持 chat template 格式
  26. """
  27. max_length = 1024
  28. input_ids_list = []
  29. labels_list = []
  30. for question, cot, response in zip(
  31. examples["Question"],
  32. examples["Complex_CoT"],
  33. examples["Response"]
  34. ):
  35. # 构建对话消息
  36. messages = [
  37. {
  38. "role": "system",
  39. "content": "您是一位在临床推理、诊断和治疗计划方面具有高级知识的医学专家。请回答以下医学问题。"
  40. },
  41. {
  42. "role": "user",
  43. "content": question
  44. },
  45. {
  46. "role": "assistant",
  47. "content": f"<think>\n{cot}\n</think>\n{response}"
  48. }
  49. ]
  50. # ============ Step 1: 生成完整对话 ============
  51. full_text = tokenizer.apply_chat_template(
  52. messages,
  53. tokenize=False,
  54. add_generation_prompt=False
  55. )
  56. # ============ Step 2: 生成 prompt 部分(不含 assistant 回复)============
  57. prompt_messages = messages[:-1] # 去掉最后的 assistant
  58. prompt_text = tokenizer.apply_chat_template(
  59. prompt_messages,
  60. tokenize=False,
  61. add_generation_prompt=True # 添加 assistant 开始标记
  62. )
  63. # ============ Step 3: Tokenize ============
  64. full_ids = tokenizer.encode(full_text, add_special_tokens=False,
  65. max_length=max_length,padding="max_length", truncation=True)
  66. prompt_ids = tokenizer.encode(prompt_text, add_special_tokens=False,
  67. max_length=max_length,padding="max_length", truncation=True)
  68. # ============ Step 4: 处理截断(保留 EOS)============
  69. # apply_chat_template 已经自动添加了 EOS,所以只需要处理截断
  70. if len(full_ids) > max_length:
  71. # 截断到 max_length-1,然后手动添加 EOS
  72. full_ids = full_ids[:max_length - 1]
  73. full_ids.append(tokenizer.eos_token_id)
  74. # 如果没有超长,full_ids 已经包含 EOS,不需要额外处理
  75. # ============ Step 5: 构建 labels(关键!)============
  76. # prompt 部分设为 -100(不计算 loss)
  77. # assistant 部分保留原值(计算 loss)
  78. prompt_len = len(prompt_ids)
  79. labels = (
  80. [-100] * prompt_len + # prompt 部分:不训练
  81. full_ids[prompt_len:] # assistant 部分:训练(包含 EOS)
  82. )
  83. # 确保长度一致
  84. labels = labels[:len(full_ids)]
  85. input_ids_list.append(full_ids)
  86. labels_list.append(labels)
  87. return {
  88. "input_ids": input_ids_list,
  89. "attention_mask": [[1] * len(ids) for ids in input_ids_list],
  90. "labels": labels_list
  91. }
  92. dataset = load_dataset(
  93. "json",
  94. data_files={
  95. "train": "/root/autodl-tmp/code/data/train.json",
  96. "valid": "/root/autodl-tmp/code/data/validation.json"
  97. }
  98. )
  99. dataset = dataset.map(preprocess_function, batched = True)
  100. #设置微调模型参数
  101. from peft import LoraConfig,get_peft_model,TaskType
  102. #Lora配置
  103. lora_config=LoraConfig(
  104. r=8,#低秩矩阵的秩,根据情况调整
  105. lora_alpha=16,#用于缩放Lora权重更新的超参数
  106. target_modules=['self_attn.q_proj','self_attn.k_proj','self_attn.v_proj','self_attn.o_proj'],
  107. lora_dropout=0.05,#dropout
  108. bias='none',#微调偏置
  109. task_type=TaskType.CAUSAL_LM
  110. )
  111. #获取应用了Lora后的微调模型
  112. model = get_peft_model(model,lora_config)
  113. model.config.use_cache = False
  114. #实际上直接计算所有可训练参数就行。
  115. trainable_params = 0
  116. all_params = 0
  117. for _, param in model.named_parameters():
  118. num_params = param.numel()
  119. all_params += num_params
  120. if param.requires_grad:
  121. trainable_params += num_params
  122. print(f"可训练参数量: {trainable_params}")
  123. print(f"总参数量: {all_params}")
  124. print(f"可训练参数占比: {100 * trainable_params / all_params:.2f}%")
  125. model.enable_input_require_grads() # 开启梯度检查点时,要执行该方法
  126. #初始化可视化功能
  127. #训练参数配置
  128. training_args = TrainingArguments(
  129. output_dir=finetuned_model_path,#输出目录,用于保存模型和日志
  130. label_names=["labels"], # 显式指定标签名称,确保 Trainer 正确识别 labels
  131. overwrite_output_dir=True,#覆盖输出目录的现有内容
  132. num_train_epochs=20,#训练的总轮次
  133. per_device_train_batch_size=6,#每个训练设备上的批次大小
  134. per_device_eval_batch_size=6,#每个评估设备上的批次大小
  135. save_steps=1,#每隔多少步保存一次模型
  136. save_total_limit=5,#保存的模型文件数量限制
  137. prediction_loss_only=True,#在评估期间只返回损失
  138. report_to="tensorboard", # 关键:启用 tensorboard 上报
  139. logging_dir="./logs",
  140. save_strategy="epoch",
  141. #梯度累积步数
  142. #通过将多个小批量的梯度累积起来,然后一次性更新模型参数,从而在有限的内存资源下实现相当于更大批量的训练效果。
  143. #当累积的步数达到预设的 gradient_accumulation_steps 时,使用累积的梯度更新模型参数,并清零累积的梯度。
  144. #节省内存
  145. #模拟大批量训练
  146. #提高训练稳定性
  147. gradient_accumulation_steps=16, #梯度累积步数
  148. #梯度检查点,用于减少深度学习模型训练过程中内存占用
  149. #反向传播需要存储前向传播中每一层的中间激活值(activations),以便计算梯度。
  150. # 对于非常深的模型或大批量数据,这些激活值会占用大量内存
  151. #Gradient Checkpointing 的核心思想是只存储部分激活值,并在反向传播时重新计算其他激活值,从而减少内存占用。
  152. gradient_checkpointing=True,#启用梯度检查点
  153. )
  154. #创建模型数据整理器
  155. data_collator = DataCollatorForLanguageModeling(
  156. tokenizer=tokenizer,
  157. mlm=False #准备数据以进行标准语言模型任务
  158. )
  159. #创建训练器
  160. trainer = Trainer(
  161. model = model,#要训练的模型
  162. args = training_args,#训练的参数
  163. data_collator=data_collator, # 用于准备训练数据
  164. train_dataset=dataset["train"],#训练的数据集
  165. eval_dataset=dataset["valid"] #评估的数据集
  166. )
  167. #开始训练模型
  168. trainer.train()