跳至主要内容

微调 Qwen3:分步指南

在一小时内基于医学推理数据集微调 Qwen3-32B 模型的分步指南。
· 8分钟 阅读

使用 AI 探索

在 QwQ 和 Qwen2.5 成功的基础上,Qwen3 在推理、创造力和对话能力方面实现了重大飞跃。它同时开放了稠密模型和 Mixture-of-Experts(MoE)模型的访问权限,参数规模涵盖 0.6B 到 235B-A22B,旨在在广泛任务上表现出色。

在本教程中,我们将基于医学推理数据集微调 Qwen3-32B 模型。目标是优化模型对患者问题的推理与准确应答能力,确保其在医学问答中采取精准且高效的方法。 

我们通过免费周五通讯 The Median 为读者带来 AI 前沿动态,精炼解读一周要闻。订阅即可每周用几分钟保持敏锐:

如需进一步了解 Qwen 3 及其对比表现,请查看我们的入门指南:Qwen 3:功能特性、与 DeepSeek-R1 的对比、获取方式等。 

微调 Qwen3 配图

图片由作者提供

1. 环境配置

前往您的 RunPod 控制台,确保账户余额至少 $5。接着选择 A100 SXM 机型,并选择最新版本的 PyTorch。最后点击“Deploy On-Demand”以启动您的 Pod。

在 RunPod 上配置 pod

来源: My Pods

修改 Pod 设置,将容器磁盘大小调整为 100 GB。同时,将您的 Hugging Face 访问令牌添加为环境变量。

更改存储并设置环境变量

来源: My Pods

应用设置后,点击“Connect”以启动 Jupyter Lab。 

Runpod,Pod 正在运行

来源: My Pods

Jupyter Lab 已预装必要的包与扩展,您无需额外配置即可开始训练。

Runpod,Jupyter Notebook

来源: My Pods

继续之前,请确保所有所需的 Python 包均已安装且为最新版本。 

在 Jupyter Notebook 单元格中运行以下命令:

%%capture
%pip install -U accelerate 
%pip install -U peft 
%pip install -U trl 
%pip install -U bitsandbytes
%pip install -U transformers

使用先前保存的 Hugging Face 访问令牌登录 Hugging Face Hub。 

from huggingface_hub import login
import os

hf_token = os.environ.get("HF_TOKEN")
login(hf_token)

2. 加载模型与分词器

尽管我们有 80GB 的显存,仍将通过 4 位量化加载 Qwen3 模型。这样可以轻松将模型装入 GPU,并顺利进行微调。 

从 Hugging Face Hub 下载并加载 Qwen3-32B 模型与分词器。

from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch

bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_use_double_quant=False,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
)

model_dir = "Qwen/Qwen3-32B"
tokenizer = AutoTokenizer.from_pretrained(model_dir, use_fast=True)
model = AutoModelForCausalLM.from_pretrained(
    model_dir,
    quantization_config=bnb_config,   
    device_map="auto",  
    torch_dtype=torch.bfloat16,
    trust_remote_code=True             
)

model.config.use_cache = False
model.config.pretraining_tp = 1

下载模型和分词器3. 加载与处理数据集

本节我们将设计一种提示词风格,促使模型进行批判性思考。我们将创建包含系统提示、问题占位符、思维链以及最终回答的提示结构。 

train_prompt_style = """Below is an instruction that describes a task, paired with an input that provides further context. 
Write a response that appropriately completes the request. 
Before answering, think carefully about the question and create a step-by-step chain of thoughts to ensure a logical and accurate response.

### Instruction:
You are a medical expert with advanced knowledge in clinical reasoning, diagnostics, and treatment planning. 
Please answer the following medical question. 

### Question:
{}

### Response:
<think>
{}
</think>
{}"""

接下来,我们将创建 Python 函数,使用上述训练提示风格并填充相应值,以生成 “text” 列。

EOS_TOKEN = tokenizer.eos_token  # Must add EOS_TOKEN

def formatting_prompts_func(examples):
    inputs = examples["Question"]
    complex_cots = examples["Complex_CoT"]
    outputs = examples["Response"]
    texts = []
    for question, cot, response in zip(inputs, complex_cots, outputs):
        # Append the EOS token to the response if it's not already there
        if not response.endswith(tokenizer.eos_token):
            response += tokenizer.eos_token
        text = train_prompt_style.format(question, cot, response)
        texts.append(text)
    return {"text": texts}

然后,我们将从 FreedomIntelligence/medical-o1-reasoning-SFT 数据集中加载前 2000 个样本,并应用 formatting_prompts_func 函数以创建 “text” 列。

from datasets import load_dataset

dataset = load_dataset(
    "FreedomIntelligence/medical-o1-reasoning-SFT",
    "en",
    split="train[0:2000]",
    trust_remote_code=True,
)
dataset = dataset.map(
    formatting_prompts_func,
    batched=True,
)
dataset["text"][10]

如我们所见,text 列包含系统提示、说明、问题、思维链以及问题的答案。

数据集第 11 个样本

新的 STF 训练器不接收分词器,因此我们将通过 transformers 的简单函数将分词器转换为数据整理器(data collator)。

from transformers import DataCollatorForLanguageModeling

data_collator = DataCollatorForLanguageModeling(
    tokenizer=tokenizer,
    mlm=False
)

4. 微调前的模型推理

推理时的提示风格与训练提示不同。它保留了训练提示中的所有内容,但不包含思维链与最终回答。

inference_prompt_style = """Below is an instruction that describes a task, paired with an input that provides further context. 
Write a response that appropriately completes the request. 
Before answering, think carefully about the question and create a step-by-step chain of thoughts to ensure a logical and accurate response.

### Instruction:
You are a medical expert with advanced knowledge in clinical reasoning, diagnostics, and treatment planning. 
Please answer the following medical question. 

### Question:
{}

### Response:
<think>

"""

为测试模型在微调前的表现,我们取数据集中的第 11 个样本,格式化后转换为 tokens 并输入模型。

question = dataset[10]['Question']
inputs = tokenizer(
    [inference_prompt_style.format(question) + tokenizer.eos_token],
    return_tensors="pt"
).to("cuda")

outputs = model.generate(
    input_ids=inputs.input_ids,
    attention_mask=inputs.attention_mask,
    max_new_tokens=1200,
    eos_token_id=tokenizer.eos_token_id,
    use_cache=True,
)
response = tokenizer.batch_decode(outputs, skip_special_tokens=True)
print(response[0].split("### Response:")[1])

结果显示,我们获得了很长的思考部分,但即便生成了 1200 个新 token 也没有回答。这与数据集短小精炼的风格有明显差异。

微调前模型推理

5. 模型设置

现在我们将实现 LoRA(低秩适配)。LoRA 的原理是冻结模型的大部分参数,仅引入一小部分可训练参数,并以低秩分解的形式加入。这样无需更新或存储完整权重,即可让模型适配新任务。

这种方法在保证与全量微调相当精度的同时,更省内存、更快且更具成本效益。

from peft import LoraConfig, get_peft_model

# LoRA config
peft_config = LoraConfig(
    lora_alpha=16,                           # Scaling factor for LoRA
    lora_dropout=0.05,                       # Add slight dropout for regularization
    r=64,                                    # Rank of the LoRA update matrices
    bias="none",                             # No bias reparameterization
    task_type="CAUSAL_LM",                   # Task type: Causal Language Modeling
    target_modules=[
        "q_proj",
        "k_proj",
        "v_proj",
        "o_proj",
        "gate_proj",
        "up_proj",
        "down_proj",
    ],  # Target modules for LoRA
)

model = get_peft_model(model, peft_config)

接下来我们将配置并初始化 SFTTrainer(监督式微调训练器),这是 Hugging Face 的 transformers 与 trl 库提供的高层封装。SFTTrainer 将数据集、模型、数据整理器、训练参数与 LoRA 配置等关键组件整合为一个精简的工作流,极大简化微调过程。 

from trl import SFTTrainer
from transformers import TrainingArguments


# Training Arguments
training_arguments = TrainingArguments(
    output_dir="output",
    per_device_train_batch_size=1,
    per_device_eval_batch_size=1,
    gradient_accumulation_steps=2,
    optim="paged_adamw_32bit",
    num_train_epochs=1,
    logging_steps=0.2,
    warmup_steps=10,
    logging_strategy="steps",
    learning_rate=2e-4,
    fp16=False,
    bf16=False,
    group_by_length=True,
    report_to="none"
)

# Initialize the Trainer
trainer = SFTTrainer(
    model=model,
    args=training_arguments,
    train_dataset=dataset,
    peft_config=peft_config,
    data_collator=data_collator,
)

6. 模型训练

开始训练前,我们将清理缓存并释放部分内存与 GPU 显存,以避免训练过程中出现 OOM(内存溢出)问题。方法如下:

import gc, torch
gc.collect()
torch.cuda.empty_cache()
model.config.use_cache = False
trainer.train()

训练开始后,您会注意到 Pod 的 GPU 利用率为 100%,GPU 显存使用约为 85%。这表明我们已达到理想负载。

GPU 显存与算力利用率

本次训练共耗时 42 分钟,可以看到训练损失逐步下降。

训练损失

7. 微调后的模型推理

我们将在与之前相同的样本上测试微调后的模型,并对结果进行对比。

question = dataset[10]['Question']
inputs = tokenizer(
    [inference_prompt_style.format(question) + tokenizer.eos_token],
    return_tensors="pt"
).to("cuda")

outputs = model.generate(
    input_ids=inputs.input_ids,
    attention_mask=inputs.attention_mask,
    max_new_tokens=1200,
    eos_token_id=tokenizer.eos_token_id,
    use_cache=True,
)
response = tokenizer.batch_decode(outputs, skip_special_tokens=True)
print(response[0].split("### Response:")[1])

思考部分被压缩且更为精准,回答也更为准确,符合数据集风格。这是非常出色的结果。

微调后模型推理

我们再用数据集中的另一个样本来测试微调后 Qwen3 的表现。

question = dataset[100]['Question']
inputs = tokenizer(
    [inference_prompt_style.format(question) + tokenizer.eos_token],
    return_tensors="pt"
).to("cuda")

outputs = model.generate(
    input_ids=inputs.input_ids,
    attention_mask=inputs.attention_mask,
    max_new_tokens=1200,
    eos_token_id=tokenizer.eos_token_id,
    use_cache=True,
)
response = tokenizer.batch_decode(outputs, skip_special_tokens=True)
print(response[0].split("### Response:")[1])

同样,思考过程简明、准确,回答也与数据集一致且可靠。

微调后模型推理

如果您在微调 Qwen3 模型时遇到问题,请参考配套笔记本: fine-tuning-qwen-3。

8. 保存模型 

最后一步是保存模型与分词器,并将其上传到 Hugging Face Hub,以便我们构建应用或与开源社区共享模型。 

new_model_name = "Qwen-3-32B-Medical-Reasoning"
model.push_to_hub(new_model_name)
tokenizer.push_to_hub(new_model_name)

模型与分词器上传后会生成仓库链接。例如: kingabzpro/Qwen-3-32B-Medical-Reasoning · Hugging Face

Hugging Face 上的 kingabzpro/Qwen-3-32B-Medical-Reasoning 模型库

来源: kingabzpro/Qwen-3-32B-Medical-Reasoning

该模型仓库包含合适的标签、说明、配套笔记本以及所有必要文件,便于您自行加载与测试。

结语

Qwen-3 再次推动了 AI 的普惠化进程。您可以轻松下载 Qwen-3,在本地电脑(无网络)运行、进行微调,甚至在本地服务器上部署。Qwen-3 的确践行了开放 AI 的理念。

本教程演示了如何在 Runpod 平台上,基于医学推理数据集微调 Qwen-3 模型。值得一提的是,整个过程成本不到 $3。若要进一步提升微调效果,建议在完整数据集上训练至少 3 个 epoch。

您可以通过我们的指南进一步了解 Qwen 3 模型:Qwen 3:功能特性、与 DeepSeek-R1 的对比、获取方式等。您还可以查看以下指南:如何在本地使用 Qwen 2.4-VL 与 微调 DeepSeek R1(推理模型)。

Abid Ali Awan

作者

Abid Ali Awan

作为一名持证的数据科学家,我热衷于利用前沿技术打造创新的机器学习应用。凭借在语音识别、数据分析与报告、MLOps、对话式人工智能以及自然语言处理方面的扎实背景,我不断打磨构建智能系统的能力,力求带来切实影响。除技术专长外,我也擅长沟通,能够将复杂概念提炼为清晰、简明的表述。因此,我成为数据科学领域备受关注的博主,与不断壮大的数据专业人士社区分享洞见与实践经验。目前,我专注于内容创作与编辑,借助大语言模型打造有力且吸引人的内容,帮助企业与个人更好地发挥数据价值。

主题