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

「整合包model_fine-tuning.zip」

pan.quark.cn/s/3642f6cfc...

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

bash 复制代码
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                    # 依赖

添加依赖

perl 复制代码
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

下载模型到本地

css 复制代码
modelscope download --model Qwen/Qwen3-1.7B --local_dir ./qwen3_1_7b

微调代码

python 复制代码
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.bfloat16

model = 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 encodings

tokenized_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 已保存。")

执行微调

css 复制代码
CUDA_VISIBLE_DEVICES=1 python train.py --batch_size 2 --fp16

过程日志:

ruby 复制代码
((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

python 复制代码
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 合并完成!模型可直接推理部署。")
ruby 复制代码
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

python 复制代码
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",  # 自动放入GPU
    trust_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)
ruby 复制代码
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. **加压包扎**:使用弹性绷带对受伤部位进行适当加压包扎,有助于减轻
相关推荐
飞哥数智坊3 小时前
AI 编程一年多,我终于明白:比技巧更重要的,是熟练度
人工智能·ai编程
新智元3 小时前
收手吧 GPT-5-Codex,外面全是 AI 编程智能体!
人工智能·openai
IT_陈寒3 小时前
Java 性能优化:5个被低估的JVM参数让你的应用吞吐量提升50%
前端·人工智能·后端
阿里云云原生4 小时前
阿里云基础设施 AI Tech Day AI 原生,智构未来——AI 原生架构与企业实践专场
人工智能
Memene摸鱼日报5 小时前
「Memene 摸鱼日报 2025.9.16」OpenAI 推出 GPT-5-Codex 编程模型,xAI 发布 Grok 4 Fast
人工智能·aigc
xiaohouzi1122335 小时前
OpenCV的cv2.VideoCapture如何加GStreamer后端
人工智能·opencv·计算机视觉
用户125205597085 小时前
解决Stable Diffusion WebUI训练嵌入式模型报错问题
人工智能
Juchecar5 小时前
一文讲清 nn.LayerNorm 层归一化
人工智能
martinzh5 小时前
RAG系统大脑调教指南:模型选择、提示设计与质量控保一本通
人工智能