当前位置: 首页 > news >正文

基于C-MTEB/CMedQAv2-rerankingv的Qwen3-1.7b模型微调-demo

「model_fine-tuning.zip」

「model_fine-tuning.zip」 提取码: suk3

源码,之后整理到github

基于C-MTEB/CMedQAv2-rerankingv的Qwen3-1.7b模型微调demo
整体的流程,也是基于peft

C-MTEB/CMedQAv2-rerankingv 数据集│▼
调用 GPT-4 API 生成多轮对话│▼
整理生成的对话为统一 JSONL 格式│▼数据质量校验与过滤│▼使用 JSONL 数据进行 LoRA 微调│▼训练模型│▼评估微调效果┌───┴────┐│        │▼        ▼效果满意    效果不佳│           │▼           ▼
部署模型/推理   调整训练参数或数据,返回微调
model_fine-tuning/
└── data/                               # 数据集
├── qwen3_1_7b                          # 模型
├── generate_from_cmedqa.py             # 生成微调数据的脚本
├── huggingface_datasets_to_jsonl.py    # .arrow格式数据转为jsonl的脚本
├── inference.py                        # 推理测试脚本
├── merge_lora_to_base.py               # 合并模型脚本
├── sft_cmedqa_multiround.jsonl         # 请求完成微调数据
└── requirements.txt                    # 依赖

添加依赖

pip install -r requirements.txt -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

下载模型到本地

modelscope download --model Qwen/Qwen3-1.7B --local_dir ./qwen3_1_7b

微调代码

from transformers import (AutoModelForCausalLM,AutoTokenizer,Trainer,TrainingArguments,DataCollatorForLanguageModeling,
)
from datasets import load_dataset
from peft import get_peft_model, LoraConfig, TaskType
import os
import torch
import argparse
import torch._dynamo
torch._dynamo.disable()# ==== 配置 ====
MODEL_NAME = "./qwen3_1_7b"  # 本地模型路径
DATA_PATH = "sft_cmedqa_multiround.jsonl"
OUTPUT_DIR = "qwen3_1_7b_lora_output"parser = argparse.ArgumentParser()
parser.add_argument("--resume_from_checkpoint", type=str, default=None)
parser.add_argument("--fp16", action="store_true")
parser.add_argument("--bf16", action="store_true")
parser.add_argument("--epochs", type=int, default=3)
parser.add_argument("--batch_size", type=int, default=2)
args = parser.parse_args()tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"torch_dtype = torch.float32
if args.fp16:torch_dtype = torch.float16
elif args.bf16:torch_dtype = torch.bfloat16model = AutoModelForCausalLM.from_pretrained(MODEL_NAME,trust_remote_code=True,torch_dtype=torch_dtype,device_map="auto",
)# 关闭 use_cache,启用梯度检查点
model.config.use_cache = False
model.gradient_checkpointing_enable()# LoRA 配置
lora_config = LoraConfig(r=8,lora_alpha=32,lora_dropout=0.1,bias="none",task_type=TaskType.CAUSAL_LM,
)
model = get_peft_model(model, lora_config)# 确认至少有参数需要梯度
trainable_params = 0
total_params = 0
for n, p in model.named_parameters():total_params += p.numel()if p.requires_grad:trainable_params += p.numel()
print(f"Trainable params: {trainable_params} / {total_params}")
assert trainable_params > 0, "没有可训练参数!"dataset = load_dataset("json", data_files=DATA_PATH, split="train")def format_conversation(example):conv = example["conversations"]text = ""for msg in conv:if msg["role"] == "user":text += f"<|user|>\n{msg['content']}\n"elif msg["role"] == "assistant":text += f"<|assistant|>\n{msg['content']}\n"encodings = tokenizer(text, truncation=True, max_length=2048, padding="max_length")encodings["labels"] = encodings["input_ids"].copy()  # 关键return encodingstokenized_dataset = dataset.map(format_conversation,remove_columns=["conversations"],desc="Tokenizing dataset",load_from_cache_file=False,
)training_args = TrainingArguments(output_dir=OUTPUT_DIR,per_device_train_batch_size=args.batch_size,gradient_accumulation_steps=4,num_train_epochs=args.epochs,learning_rate=2e-5,logging_steps=10,save_strategy="epoch",save_total_limit=2,fp16=args.fp16,bf16=args.bf16,report_to="none",ddp_find_unused_parameters=False if torch.cuda.device_count() > 1 else None,gradient_checkpointing=True,
)trainer = Trainer(model=model,args=training_args,train_dataset=tokenized_dataset,data_collator=DataCollatorForLanguageModeling(tokenizer, mlm=False),
)if __name__ == "__main__":print(f"Starting training... GPUs: {torch.cuda.device_count()} | Resume: {args.resume_from_checkpoint}")trainer.train(resume_from_checkpoint=args.resume_from_checkpoint)model.save_pretrained(os.path.join(OUTPUT_DIR, "adapter"))tokenizer.save_pretrained(os.path.join(OUTPUT_DIR, "adapter"))print("训练完成,LoRA adapter 已保存。")

执行微调

CUDA_VISIBLE_DEVICES=1 python train.py --batch_size 2 --fp16

过程日志:

((llm_env) ) root@plains-tires-held-g4v-yuanmomo-master-0:/home/demo/model-distillation# CUDA_VISIBLE_DEVICES=1 python train.py --batch_size 2 --fp16
Loading checkpoint shards: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 2/2 [00:00<00:00,  3.09it/s]
Trainable params: 1605632 / 1722180608
Tokenizing dataset: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 999/999 [00:03<00:00, 282.01 examples/s]
Detected kernel version 5.4.0, which is below the recommended minimum of 5.5.0; this can cause the process to hang. It is recommended to upgrade the kernel to the minimum version or higher.
No label_names provided for model class `PeftModelForCausalLM`. Since `PeftModel` hides base models input arguments, if label_names is not given, label_names can't be set automatically within `Trainer`. Note that empty label_names list will be used instead.
Starting training... GPUs: 1 | Resume: None
{'loss': 2.0273, 'grad_norm': 1.5650986433029175, 'learning_rate': 1.9520000000000003e-05, 'epoch': 0.08}                                                                                                        
{'loss': 1.9873, 'grad_norm': 1.3627800941467285, 'learning_rate': 1.898666666666667e-05, 'epoch': 0.16}                                                                                                         
{'loss': 1.9509, 'grad_norm': 1.0356942415237427, 'learning_rate': 1.8453333333333335e-05, 'epoch': 0.24}                                                                                                        
{'loss': 1.862, 'grad_norm': 0.833026111125946, 'learning_rate': 1.792e-05, 'epoch': 0.32}                                                                                                                       
{'loss': 1.8477, 'grad_norm': 0.6192384362220764, 'learning_rate': 1.7386666666666667e-05, 'epoch': 0.4}                                                                                                         
{'loss': 1.835, 'grad_norm': 0.5466067790985107, 'learning_rate': 1.6853333333333333e-05, 'epoch': 0.48}                                                                                                         
{'loss': 1.8568, 'grad_norm': 0.3964546322822571, 'learning_rate': 1.632e-05, 'epoch': 0.56}                                                                                                                     
{'loss': 1.7916, 'grad_norm': 0.33305487036705017, 'learning_rate': 1.578666666666667e-05, 'epoch': 0.64}                                                                                                        
{'loss': 1.7686, 'grad_norm': 0.3385171592235565, 'learning_rate': 1.5253333333333335e-05, 'epoch': 0.72}                                                                                                        
{'loss': 1.8246, 'grad_norm': 0.2938034236431122, 'learning_rate': 1.4720000000000001e-05, 'epoch': 0.8}                                                                                                         
{'loss': 1.805, 'grad_norm': 0.32724013924598694, 'learning_rate': 1.418666666666667e-05, 'epoch': 0.88}                                                                                                         
{'loss': 1.791, 'grad_norm': 0.27404868602752686, 'learning_rate': 1.3653333333333334e-05, 'epoch': 0.96}                                                                                                        
{'loss': 1.7492, 'grad_norm': 0.2603466510772705, 'learning_rate': 1.3120000000000001e-05, 'epoch': 1.04}                                                                                                        
{'loss': 1.7613, 'grad_norm': 0.27473559975624084, 'learning_rate': 1.2586666666666668e-05, 'epoch': 1.12}                                                                                                       
{'loss': 1.788, 'grad_norm': 0.3106350004673004, 'learning_rate': 1.2053333333333335e-05, 'epoch': 1.2}                                                                                                          
{'loss': 1.7613, 'grad_norm': 0.29145100712776184, 'learning_rate': 1.152e-05, 'epoch': 1.28}                                                                                                                    
{'loss': 1.7717, 'grad_norm': 0.37819281220436096, 'learning_rate': 1.0986666666666668e-05, 'epoch': 1.36}                                                                                                       
{'loss': 1.7711, 'grad_norm': 0.32270991802215576, 'learning_rate': 1.0453333333333334e-05, 'epoch': 1.44}                                                                                                       
{'loss': 1.7217, 'grad_norm': 0.29068511724472046, 'learning_rate': 9.920000000000002e-06, 'epoch': 1.52}                                                                                                        
{'loss': 1.7852, 'grad_norm': 0.288211852312088, 'learning_rate': 9.386666666666668e-06, 'epoch': 1.6}                                                                                                           
{'loss': 1.7763, 'grad_norm': 0.36920636892318726, 'learning_rate': 8.853333333333334e-06, 'epoch': 1.68}                                                                                                        
{'loss': 1.7377, 'grad_norm': 0.2973214387893677, 'learning_rate': 8.32e-06, 'epoch': 1.76}                                                                                                                      
{'loss': 1.7797, 'grad_norm': 0.36736875772476196, 'learning_rate': 7.786666666666666e-06, 'epoch': 1.84}                                                                                                        
{'loss': 1.7523, 'grad_norm': 0.38346627354621887, 'learning_rate': 7.253333333333335e-06, 'epoch': 1.92}                                                                                                        
{'loss': 1.7311, 'grad_norm': 0.3454875349998474, 'learning_rate': 6.720000000000001e-06, 'epoch': 2.0}                                                                                                          
{'loss': 1.7222, 'grad_norm': 0.3131895065307617, 'learning_rate': 6.186666666666668e-06, 'epoch': 2.08}                                                                                                         
{'loss': 1.7503, 'grad_norm': 0.277790904045105, 'learning_rate': 5.653333333333334e-06, 'epoch': 2.16}                                                                                                          
{'loss': 1.7709, 'grad_norm': 0.33882805705070496, 'learning_rate': 5.12e-06, 'epoch': 2.24}                                                                                                                     
{'loss': 1.7486, 'grad_norm': 0.31532227993011475, 'learning_rate': 4.586666666666667e-06, 'epoch': 2.32}                                                                                                        
{'loss': 1.7431, 'grad_norm': 0.3307243287563324, 'learning_rate': 4.053333333333333e-06, 'epoch': 2.4}                                                                                                          
{'loss': 1.7335, 'grad_norm': 0.3195337653160095, 'learning_rate': 3.52e-06, 'epoch': 2.48}                                                                                                                      
{'loss': 1.7236, 'grad_norm': 0.29411816596984863, 'learning_rate': 2.986666666666667e-06, 'epoch': 2.56}                                                                                                        
{'loss': 1.7224, 'grad_norm': 0.3093280792236328, 'learning_rate': 2.4533333333333333e-06, 'epoch': 2.64}                                                                                                        
{'loss': 1.7166, 'grad_norm': 0.331863671541214, 'learning_rate': 1.9200000000000003e-06, 'epoch': 2.72}                                                                                                         
{'loss': 1.7529, 'grad_norm': 0.27812764048576355, 'learning_rate': 1.3866666666666668e-06, 'epoch': 2.8}                                                                                                        
{'loss': 1.7508, 'grad_norm': 0.34852325916290283, 'learning_rate': 8.533333333333334e-07, 'epoch': 2.88}                                                                                                        
{'loss': 1.7318, 'grad_norm': 0.31317082047462463, 'learning_rate': 3.2e-07, 'epoch': 2.96}                                                                                                                      
{'train_runtime': 823.7569, 'train_samples_per_second': 3.638, 'train_steps_per_second': 0.455, 'train_loss': 1.786485984802246, 'epoch': 3.0}                                                                   
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 375/375 [13:43<00:00,  2.20s/it]
训练完成,LoRA adapter 已保存。

LoRA 微调过程 的训练指标解释:


1. loss

  • 含义:训练损失(Cross-Entropy Loss),衡量模型预测和真实标签之间的差距。

  • 变化趋势

    • 2.02731.7166,整体下降,说明模型逐渐学习到了任务特征。
    • 中间有小幅波动(如 1.85681.79161.8246),这是正常的,因为梯度更新和学习率调度会带来局部震荡。

2. grad_norm

  • 含义:梯度范数,表示当前训练步的梯度大小。

  • 观察

    • 训练初期 grad_norm 较大(1.56),随着训练进行逐步下降到 0.2~0.3 左右,说明模型参数趋于稳定,训练更加平稳。
    • 如果grad_norm过大可能导致梯度爆炸,过小可能训练停滞。这里数值合理。

3. learning_rate

  • 含义:当前步的学习率,训练中采用了 学习率调度(lr scheduler)

  • 观察

    • 初始学习率大约 2e-5,逐渐下降到接近 3e-7
    • 这种 warmup + cosine/linear decay 调度有助于先快速学习,再精细收敛。

4. epoch

  • 含义:当前训练轮数。

  • 观察

    • 训练总共运行了 3.0 epoch,日志每隔一定 step 输出一次指标。
    • 逐轮 loss 下降,模型性能提升。

5. train_runtime / train_samples_per_second / train_steps_per_second

  • train_runtime:总训练时长 823s(约 13.7 分钟)。
  • train_samples_per_second:处理速率 3.64 样本/s。
  • train_steps_per_second:每秒训练 0.455 个 step。
  • train_loss:最终平均训练损失 1.786,和最后几步的 loss (1.73~1.75) 一致,说明训练稳定收敛。

6. 总结训练过程

  • loss 持续下降,证明模型学习有效。
  • grad_norm 稳定且逐渐减小,训练过程健康。
  • 学习率 从高到低衰减,符合 fine-tuning 策略。
  • 最终模型:LoRA adapter 已保存,可直接用于推理或进一步验证。

微调完成后,会在 qwen3_1_7b_lora_output 下生成三个目录:

目录说明
adapter/最终的 LoRA 微调结果,你部署 / 推理 时用的就是它
checkpoint-250/训练中间保存的模型 checkpoint(比如训练到第 250 步)
checkpoint-375/训练最后一步保存的模型(假设总步数是 375)

合并模型

python merge_lora_to_base.py

from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel
import torch
import os# ==== 配置部分 ====
BASE_MODEL = "./qwen3_1_7b"  # 改为你训练时的本地模型路径
LORA_MODEL_PATH = "./qwen3_1_7b_lora_output/adapter"  # 改为你保存 adapter 的目录
MERGED_OUTPUT_PATH = "./qwen3_1_7b_merged"# ==== Step 1: 加载基础模型和 LoRA adapter ====
print("加载基础模型和 LoRA adapter...")
base_model = AutoModelForCausalLM.from_pretrained(BASE_MODEL,trust_remote_code=True,device_map="auto",  # 更通用(也支持多卡)torch_dtype=torch.float16
)model = PeftModel.from_pretrained(base_model, LORA_MODEL_PATH)
model = model.merge_and_unload()  # 合并 LoRA 权重# ==== Step 2: 保存合并后的模型 ====
print(f"保存合并模型到:{MERGED_OUTPUT_PATH}")
os.makedirs(MERGED_OUTPUT_PATH, exist_ok=True)
model.save_pretrained(MERGED_OUTPUT_PATH)
tokenizer = AutoTokenizer.from_pretrained(LORA_MODEL_PATH, trust_remote_code=True)  #  保证一致
tokenizer.save_pretrained(MERGED_OUTPUT_PATH)print("LoRA 合并完成!模型可直接推理部署。")
python merge_lora_to_base.py((llm_env) ) root@plains-tires-held-g4v-yuanmomo-master-0:/home/demo/model-distillation# python3 merge_lora_to_base.py 
加载基础模型和 LoRA adapter...
Loading checkpoint shards: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 2/2 [00:05<00:00,  2.89s/it]
保存合并模型到:./qwen3_1_7b_merged
LoRA 合并完成!模型可直接推理部署。

执行推理测试

inference.py

import torch
from transformers import AutoTokenizer, AutoModelForCausalLM# ==== 已合并模型路径 ====
#MERGED_MODEL_PATH = "qwen3_1_7b_merged"
MERGED_MODEL_PATH = "qwen3_1_7b"
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"# ==== 加载 tokenizer 和模型 ====
tokenizer = AutoTokenizer.from_pretrained(MERGED_MODEL_PATH, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(MERGED_MODEL_PATH,torch_dtype=torch.float16,device_map="auto",  # 自动放入GPUtrust_remote_code=True
)
model.eval()# ==== 推理函数 ====
def chat(prompt: str, max_new_tokens=128):inputs = tokenizer(prompt, return_tensors="pt").to(DEVICE)with torch.no_grad():outputs = model.generate(**inputs,max_new_tokens=max_new_tokens,do_sample=True,top_p=0.95,temperature=0.7,repetition_penalty=1.05,eos_token_id=tokenizer.eos_token_id)return tokenizer.decode(outputs[0], skip_special_tokens=True)# ==== 测试 ====
if __name__ == "__main__":query = "全部症状:手指关节不小心韧带扭伤现在关节肿大痛发病时间及原因:治疗情况:关节骨头没问题。看能用什么有效的外敷药治疗。 —— 你怎么看?"result = chat(query)print("用户输入:", query)print("模型回复:", result)
python inference.py((llm_env) ) root@plains-tires-held-g4v-yuanmomo-master-0:/home/demo/model-distillation# python3 inference.py 
用户输入: 解释一下什么是胃溃疡?
模型回复: 解释一下什么是胃溃疡? 胃溃疡是指胃壁上发生的一种溃疡病,一般发生在胃的黏膜层和肌层之间。胃溃疡的常见症状包括腹痛、食欲减退、消化不良等。
胃溃疡是消化性溃疡的一种,主要由胃酸和胃蛋白酶的作用引起。这些消化液在胃内分解食物,帮助消化。但当胃内环境不稳定时,胃酸和胃蛋白酶会损伤胃黏膜,导致溃疡的发生。胃溃疡的成因包括:
1. 长期使用某些药物,如非甾体抗炎药(NSAIDs)等,
((llm_env) ) root@plains-tires-held-g4v-yuanmomo-master-0:/home/demo/model-distillation# python3 inference.py 
用户输入: 全部症状:手指关节不小心韧带扭伤现在关节肿大痛发病时间及原因:治疗情况:关节骨头没问题。看能用什么有效的外敷药治疗。 —— 你怎么看?
模型回复: 全部症状:手指关节不小心韧带扭伤现在关节肿大痛发病时间及原因:治疗情况:关节骨头没问题。看能用什么有效的外敷药治疗。 —— 你怎么看? 需要怎样的帮助?根据你的描述,手指关节因韧带扭伤导致肿胀、疼痛,并且已经出现关节骨突出的情况,但没有骨折或明显骨骼损伤。你可能需要以下几个方面的处理:1. **冰敷**:在受伤后24-48小时内,可以使用冰袋或冰敷贴对肿胀的部位进行冷敷,每次15-20分钟,每天3-4次,以减少炎症和肿胀。
2. **加压包扎**:使用弹性绷带对受伤部位进行适当加压包扎,有助于减轻
http://www.lryc.cn/news/605005.html

相关文章:

  • Android基础(二)了解Android项目
  • 端侧大模型迎来“轻”革命!移远通信 × RWKV 打造“轻量AI大脑”
  • 单片机电路基础
  • 【NCS随笔】如何在hello_world添加蓝牙功能(一)
  • sqli-labs:Less-7关卡详细解析
  • 国内数据集成厂商有哪些?如何选择最适合的数据集成平台?
  • Qt 与物联网(IoT)开发
  • 【Linux】重生之从零开始学习运维之备份恢复
  • String模拟实现的补充说明
  • 第1课:向量与矩阵运算
  • QT中QTableView+Model+Delegate实现一个demo
  • 【ESP32设备通信】-LAN8720与ESP32集成
  • 如何设计一个站内消息系统:架构设计合集(八)
  • 订单识别技术原理及场景应用
  • 【音视频】WebRTC 开发环境搭建-Web端
  • MYSQL:视图
  • Qt 下载说明
  • uniApp实战六:Echart图表集成
  • 实现implements InitializingBean, DisposableBean 有什么用
  • 【MATLAB/Simulink】查看MATLAB以往版本的帮助文档
  • 牛顿-拉夫森法求解非线性方程组
  • 无人机惯性导航模块运行与技术难点!
  • 25年新算法!基于猛禽的优化算法(BPBO):一种元启发式优化算法,附完整免费MATLAB代码
  • 《数学模型》——最大流与最小费用流问题
  • Mediapipe 的某些模型,网络下载不来可以去gitee找找看
  • 双塔模型 + 自监督学习:解决长尾物品表征难题
  • Helm在Kubernetes中的应用部署指南与案例解析
  • FragmentManager 返回栈与 Activity 运行栈的关系(当按下Back键时屏幕会如何变化?)
  • 基于SpringBoot+MyBatis+MySQL+VUE实现的便利店信息管理系统(附源码+数据库+毕业论文+远程部署)
  • 如何不让android studio自动换行