☰
用Qwen3.5-0.8B模型,4GB显存就能当“懂行”医疗助手!超详细微调脚本
2026/10/14 2:04:47 网站建设 项目流程

本文提供了一份完整的Qwen3.5-0.8B中文医疗模型微调脚本,通过Unsloth加速加载、QLoRA(4bit)压缩显存占用至0.5GB、SFT医疗问答训练,以及多科室数据统一转Alpaca指令格式,实现高效微调。训练采用LoRA低秩适配,仅训练少量参数,配合梯度累积与8bit优化器,100步即可完成验证。脚本包含完整逻辑链路、代码实现及推理测试,让你直观看到模型从通用到专业的转变过程。


把开源大模型变成“懂行”的医疗助手,需要几步?这份 Qwen3.5-0.8B 中文医疗模型微调脚本,给出了完整答案。它把 Unsloth、QLoRA(4bit)、SFT 串成一条流水线:Unsloth 负责加速加载,4bit 量化把 0.8B 模型的显存占用压到约 0.5GB,4GB 显存就能跑;SFT 则用“问题-答案”对让模型学会医疗问答。

数据侧同样讲究:内科、外科、儿科、肿瘤科、妇产科、男科六类科室数据,统一转成 Alpaca 指令格式,套用固定提示模板,保证训练与推理口径一致。训练走 LoRA 低秩适配,只训练少量参数,配合梯度累积与 8bit 优化器,100 步即可完成验证;保存的只是轻量适配器权重,随时加载推理。微调完成后,脚本还会用“头晕怎么办”“高血压注意什么”等提问现场测试,让你直观看到模型变“专业”的过程。

完整的逻辑链路:

整个脚本的知识树:

完整的代码如下:

# -*- coding: utf-8 -*- """ Qwen3.5-0.8B 中文医疗模型SFT微调 (GPU版) 课程: 高质量微调数据工程与评估 环境: 需要GPU,建议4GB以上显存 依赖: pip install unsloth transformers trl datasets pandas """ import os # 设置 HuggingFace 镜像站点,加速国内模型下载 os.environ["HF_ENDPOINT"] = "https://hf-mirror.com" # 在 huggingface_hub 被加载前,直接修改它的常量,确保镜像生效 import huggingface_hub.constants as _c _c.DEFAULT_ENDPOINT = "https://hf-mirror.com" _c.HF_ENDPOINT = "https://hf-mirror.com" _c.ENDPOINT = "https://hf-mirror.com" # 修复 AutoDL 等环境下 OMP_NUM_THREADS 未正确设置的问题 # OpenMP 线程数默认可能过大,导致多进程数据加载时 CPU 资源竞争 if not os.environ.get("OMP_NUM_THREADS"): os.environ["OMP_NUM_THREADS"] = "4" import json import glob import torch import pandas as pd from datasets import Dataset # ======================================== # 配置 # ======================================== def resolve_model_name(): """优先使用本机/AutoDL已经下载好的模型目录,找不到时再使用 Hugging Face 仓库名。 查找优先级: 1. AutoDL 平台常见缓存目录(含 snapshots 快照目录) 2. 兜底使用 HuggingFace 仓库名,由 transformers 自动下载 """ # script_dir = os.path.dirname(os.path.abspath(__file__)) candidates = [ # AutoDL 常见 modelscope/huggingface 缓存目录,snapshots 目录是模型的实际存储位置 "/root/autodl-tmp/models/Qwen--Qwen3.5-0.8B/snapshots/master", "/root/autodl-tmp/models/models/Qwen3.5-0.8B/snapshots/master", "/root/autodl-tmp/models/models/Qwen--Qwen3.5-0.8B/snapshots/master", "/root/autodl-tmp/models/models/Qwen3.5-0.8B", "/root/autodl-tmp/models/models/Qwen--Qwen3.5-0.8B", # 课程目录内的本地模型目录(可选,取消注释即可启用) # os.path.join(script_dir, "models", "Qwen", "Qwen3___5-0___8B"), ] for path in candidates: if os.path.exists(path): return path # 线上未找到本地模型,返回 HuggingFace 仓库名进行在线加载 return "Qwen/Qwen3.5-0.8B" MODEL_NAME = resolve_model_name() # 数据路径:原始CSV数据集 & 预处理后的JSONL文件 DATA_DIR = "【数据集】中文医疗数据" PROCESSED_DATA_DIR = "processed_data" # 输出路径:训练日志 & 保存的LoRA权重 OUTPUT_DIR = "outputs_medical" LORA_SAVE_DIR = "lora_model_medical" # ===== 训练超参数 ===== MAX_SEQ_LENGTH = 2048 # 最大序列长度,超过会被截断 MAX_STEPS = 100 # 总训练步数(小数据集快速验证用) BATCH_SIZE = 2 # 每GPU的batch size GRAD_ACCUM = 4 # 梯度累积步数,等效batch = 2 * 4 = 8 LORA_R = 16 # LoRA秩,越大表达能力越强但参数量增加 LR = 2e-4 # 学习率,LoRA微调建议1e-4~5e-4 # 医疗对话提示模板,使用中文问答格式,训练和推理共用 MEDICAL_PROMPT = """你是一个专业的医疗助手。请根据患者的问题提供专业、准确的回答。 ### 问题: {} ### 回答: {}""" # ======================================== # 数据准备 # ======================================== def read_csv_with_encoding(file_path): """尝试使用不同编码读取CSV,gb18030是GBK超集优先尝试 中文医疗数据集可能使用多种编码,需要逐个尝试 """ import io for encoding in ['gb18030', 'utf-8', 'gbk', 'gb2312']: try: return pd.read_csv(file_path, encoding=encoding) except (UnicodeDecodeError, UnicodeError): continue # 所有编码都失败时,用 errors='replace' 强制读取(替换乱码字符) with open(file_path, 'r', encoding='gb18030', errors='replace') as f: return pd.read_csv(io.StringIO(f.read())) def load_from_jsonl(jsonl_path): """从JSONL文件加载数据""" data = [] with open(jsonl_path, 'r', encoding='utf-8') as f: for line in f: line = line.strip() if line: data.append(json.loads(line)) print(f" 从JSONL加载 {len(data)} 条数据: {jsonl_path}") return data def load_from_csv(data_dir, max_samples=2000): """从原始CSV文件加载数据,按科室分类组织 数据目录结构: data_dir/ ├── IM_内科/*.csv ├── Surgical_外科/*.csv ├── Pediatric_儿科/*.csv ├── Oncology_肿瘤科/*.csv ├── OAGD_妇产科/*.csv └── Andriatria_男科/*.csv """ data = [] # 英文目录名 -> 中文科室名的映射 departments = { 'IM_内科': '内科', 'Surgical_外科': '外科', 'Pediatric_儿科': '儿科', 'Oncology_肿瘤科': '肿瘤科', 'OAGD_妇产科': '妇产科', 'Andriatria_男科': '男科' } for dept_dir, dept_name in departments.items(): dept_path = os.path.join(data_dir, dept_dir) if not os.path.exists(dept_path): continue print(f" 处理{dept_name}数据...") csv_files = [f for f in os.listdir(dept_path) if f.endswith('.csv')] for csv_file in csv_files: file_path = os.path.join(dept_path, csv_file) try: df = read_csv_with_encoding(file_path) for _, row in df.iterrows(): # 兼容不同列名:question/ask 和 answer/response question = str(row.get('question', row.get('ask', ''))).strip() answer = str(row.get('answer', row.get('response', ''))).strip() # 过滤无效数据:空值、NaN、过短的问题/回答 if not question or not answer or question == 'nan' or answer == 'nan': continue if len(question) < 5 or len(answer) < 10: continue # 统一格式化为Alpaca指令格式 data.append({ "instruction": "请回答以下医疗相关问题", "input": question, "output": answer, }) if len(data) >= max_samples: break except Exception as e: print(f" 处理文件出错: {e}") continue if len(data) >= max_samples: break if len(data) >= max_samples: break print(f" 从CSV加载 {len(data)} 条数据") return data def prepare_training_data(): """准备训练数据,优先使用processed_data下的JSONL,否则回退到CSV 加载优先级: 1. processed_data/medical_alpaca_train.jsonl (训练集) 2. processed_data/medical_alpaca_sampled.jsonl (采样集) 3. processed_data/medical_alpaca_full.jsonl (全量集) 4. 上述都不存在时,从原始CSV目录加载(最多2000条) """ jsonl_pattern = os.path.join(PROCESSED_DATA_DIR, "*.jsonl") candidates = [ os.path.join(PROCESSED_DATA_DIR, "medical_alpaca_train.jsonl"), os.path.join(PROCESSED_DATA_DIR, "medical_alpaca_sampled.jsonl"), os.path.join(PROCESSED_DATA_DIR, "medical_alpaca_full.jsonl"), ] for path in candidates: if os.path.exists(path): return load_from_jsonl(path) for path in sorted(glob.glob(jsonl_pattern)): return load_from_jsonl(path) print(" 未找到processed_data下的JSONL,将从原始CSV加载") return load_from_csv(DATA_DIR, max_samples=2000) def make_dataset(data_list, tokenizer): """将数据列表转换为HuggingFace Dataset,每条末尾添加EOS token EOS token 告诉模型何时停止生成,是训练稳定性关键 """ eos_token = tokenizer.eos_token or "" def formatting_func(examples): texts = [] for inp, out in zip(examples["input"], examples["output"]): # 用MEDICAL_PROMPT模板拼接问题和回答,末尾追加EOS text = MEDICAL_PROMPT.format(inp, out) + eos_token texts.append(text) return {"text": texts} # 从列表创建Dataset,再批量格式化文本 dataset = Dataset.from_list(data_list) dataset = dataset.map(formatting_func, batched=True) return dataset # ======================================== # 模型加载与LoRA # ======================================== def load_model(): """使用Unsloth加载模型,4bit量化以节省显存 Unsloth 是优化的模型加载库,比原生 transformers 快2-5倍 4bit量化(NF4)将模型权重从16bit压缩到4bit,大幅降低显存占用 0.8B 模型量化后约占用 0.5GB 显存 """ from unsloth import FastLanguageModel model, tokenizer = FastLanguageModel.from_pretrained( model_name=MODEL_NAME, max_seq_length=MAX_SEQ_LENGTH, dtype=None, # 自动选择最佳数据类型 load_in_4bit=True, # 启用4bit量化 ) return model, tokenizer def setup_lora(model): """配置LoRA适配器,使用unsloth梯度检查点 LoRA(Low-Rank Adaptation)只训练少量低秩矩阵,不修改原模型权重 目标模块说明: - q_proj, k_proj, v_proj, o_proj: 注意力层的QKV和输出投影 - gate_proj, up_proj, down_proj: FFN层的门控和上下投影 unsloth梯度检查点:比标准checkpoint更节省显存 """ from unsloth import FastLanguageModel model = FastLanguageModel.get_peft_model( model, r=LORA_R, # LoRA秩 target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"], # 对7个线性层加LoRA lora_alpha=16, # 缩放因子,通常设为r的2倍 lora_dropout=0, # SFT微调通常不需要dropout bias="none", # 不训练bias use_gradient_checkpointing="unsloth", # unsloth优化的梯度检查点 random_state=3407, # 固定随机种子,保证可复现 use_rslora=False, # 不使用RS-LoRA loftq_config=None, # 不使用LoftQ初始化 ) return model # ======================================== # 训练 # ======================================== def train_sft(model, tokenizer, dataset): """使用SFTTrainer训练,优先使用新版SFTConfig,回退到TrainingArguments SFTTrainer 是 TRL 库提供的监督微调训练器,包装了 transformers Trainer 自动处理:数据整理、损失计算、梯度累积、混合精度等 """ from unsloth import is_bfloat16_supported bf16_val = is_bfloat16_supported() # 检查GPU是否支持bfloat16 fp16_val = not bf16_val # 不支持bf16则用fp16 train_kwargs = dict( per_device_train_batch_size=BATCH_SIZE, gradient_accumulation_steps=GRAD_ACCUM, warmup_steps=5, # 学习率预热步数,避免训练初期不稳定 max_steps=MAX_STEPS, learning_rate=LR, fp16=fp16_val, bf16=bf16_val, logging_steps=1, # 每步记录日志,方便监控 optim="adamw_8bit", # 8bit AdamW优化器,节省显存 weight_decay=0.01, # 权重衰减,防止过拟合 lr_scheduler_type="linear", # 线性衰减学习率 seed=3407, output_dir=OUTPUT_DIR, report_to="none", # 不上报wandb等外部平台 ) from trl import SFTTrainer try: # 新版 TRL 使用 SFTConfig(推荐) from trl import SFTConfig sft_config = SFTConfig( **train_kwargs, max_seq_length=MAX_SEQ_LENGTH, dataset_text_field="text", # 数据集中的文本字段名 dataset_num_proc=2, # 数据预处理并行进程数 packing=False, # 不打包多条样本,避免跨样本注意力 ) trainer = SFTTrainer( model=model, processing_class=tokenizer, # 新版API使用 processing_class train_dataset=dataset, args=sft_config, ) except ImportError: # 旧版 TRL 回退到 TrainingArguments from transformers import TrainingArguments training_args = TrainingArguments(**train_kwargs) trainer = SFTTrainer( model=model, tokenizer=tokenizer, train_dataset=dataset, dataset_text_field="text", max_seq_length=MAX_SEQ_LENGTH, dataset_num_proc=2, packing=False, args=training_args, ) # 打印GPU信息,便于监控显存使用 gpu_stats = torch.cuda.get_device_properties(0) start_mem = round(torch.cuda.max_memory_reserved() / 1024 / 1024 / 1024, 3) print(f" GPU: {gpu_stats.name}, 总显存: {round(gpu_stats.total_memory / 1024 / 1024 / 1024, 1)} GB") print(f" 训练前已占用显存: {start_mem} GB") trainer.train() # 训练后打印峰值显存 peak_mem = round(torch.cuda.max_memory_reserved() / 1024 / 1024 / 1024, 3) print(f" 训练后峰值显存: {peak_mem} GB") # ======================================== # 推理 # ======================================== def generate_response(model, tokenizer, question, max_new_tokens=256): """推理前调用for_inference,张量置于cuda 推理流程: 1. for_inference() 将模型切换到推理模式,禁用dropout等训练特性 2. 用MEDICAL_PROMPT构造输入,回答部分留空 3. tokenizer() 将文本转为token ids,移到GPU 4. model.generate() 自回归生成回答 5. 解析输出,提取"### 回答:"后的内容 """ from unsloth import FastLanguageModel FastLanguageModel.for_inference(model) # 切换到推理模式 # 构造prompt,回答部分留空让模型生成 prompt = MEDICAL_PROMPT.format(question, "") # 使用 tokenizer.tokenizer 获取底层tokenizer(兼容unsloth包装) inputs = tokenizer.tokenizer(prompt, return_tensors="pt").to("cuda") with torch.no_grad(): # 推理不需要梯度,节省显存 outputs = model.generate( **inputs, max_new_tokens=max_new_tokens, temperature=0.7, # 温度控制随机性,0.7适中 top_p=0.9, # nucleus采样,只考虑累积概率前90%的token use_cache=True, # 使用KV缓存加速生成 do_sample=True, # 采样模式(非贪婪解码) ) # 解码生成的token序列 response = tokenizer.decode(outputs[0], skip_special_tokens=True) # 提取"### 回答:"后的内容,兼容中英文冒号 if "### 回答:" in response: answer = response.split("### 回答:")[-1].strip() elif "### 回答:" in response: answer = response.split("### 回答:")[-1].strip() else: answer = response return answer # ======================================== # 主入口 # ======================================== if __name__ == "__main__": # 检查GPU可用性,本脚本仅支持GPU运行 if not torch.cuda.is_available(): print("错误: 未检测到GPU,本脚本仅支持GPU运行") exit(1) print(f"\n{'='*60}") print(" Qwen3.5-0.8B 中文医疗模型SFT微调 (GPU版)") print(f" 模型: {MODEL_NAME}") print(f" 训练步数: {MAX_STEPS}, batch={BATCH_SIZE}, grad_accum={GRAD_ACCUM}") print(f"{'='*60}\n") # 1. 加载模型(4bit量化 + Unsloth加速) print("--- 1/7 加载模型 ---") model, tokenizer = load_model() # 2. 配置LoRA(低秩适配器,只训练少量参数) print("\n--- 2/7 配置LoRA ---") model = setup_lora(model) # 3. 准备数据并格式化为带EOS的文本 print("\n--- 3/7 准备训练数据 ---") data_list = prepare_training_data() dataset = make_dataset(data_list, tokenizer) print(f" 训练样本数: {len(dataset)}") # 4. SFT训练(监督微调) print("\n--- 4/7 开始SFT训练 ---") train_sft(model, tokenizer, dataset) # 5. 打印GPU显存统计 print("\n--- 5/7 GPU显存统计 ---") if torch.cuda.is_available(): allocated = round(torch.cuda.memory_allocated() / 1024 / 1024 / 1024, 3) reserved = round(torch.cuda.max_memory_reserved() / 1024 / 1024 / 1024, 3) print(f" 当前分配: {allocated} GB, 峰值保留: {reserved} GB") # 6. 测试推理(验证微调效果) print("\n--- 6/7 测试推理 ---") test_questions = [ "我最近总是感觉头晕,应该怎么办?", "感冒发烧应该吃什么药?", "高血压患者需要注意什么?", ] for q in test_questions: ans = generate_response(model, tokenizer, q) print(f" Q: {q}") print(f" A: {ans[:300]}...") # 截断显示前300字符 print() # 7. 保存LoRA模型(仅保存适配器权重,不保存完整模型) print("--- 7/7 保存LoRA模型 ---") os.makedirs(LORA_SAVE_DIR, exist_ok=True) model.save_pretrained(LORA_SAVE_DIR) tokenizer.save_pretrained(LORA_SAVE_DIR) print(f" 已保存至: {LORA_SAVE_DIR}") print("\n微调完成")

代码输出如下:

2026年AI行业最大的机会,毫无疑问就在应用层!

字节跳动已有7个团队全速布局Agent

大模型岗位暴增69%,年薪破百万!

腾讯、京东、百度开放招聘技术岗,80%与AI相关……

如今,超过60%的企业都在推进AI产品落地,而真正能交付项目的大模型应用开发工程师**,**却极度稀缺!

落地AI应用绝对不是写几个prompt,调几个API就能搞定的,企业真正需要的,是能搞定这三项核心能力的人:

✅RAG:融入外部信息,修正模型输出,给模型装靠谱大脑

✅Agent智能体:让AI自主干活,通过工具调用(Tools)环境交互,多步推理完成复杂任务。比如做智能客服等等……

✅微调:针对特定任务优化,让模型适配业务

目前,脉脉上有超过1000家企业发布大模型相关岗位,人工智能岗平均月薪7.8w!实习生日薪高达4000!远超其他行业收入水平!

技术的稀缺性,才是你「值钱」的关键!

具备AI能力的程序员,比传统开发高出不止一截!有的人早就转行AI方向,拿到百万年薪!👇🏻👇🏻

AI浪潮,正在重构程序员的核心竞争力!现在入场,仍是最佳时机!

我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】

⭐️从大模型微调到AI Agent智能体搭建

剖析AI技术的应用场景,用实战经验落地AI技术。从GPT到最火的开源模型,让你从容面对AI技术革新!

大模型微调

  • 掌握主流大模型(如DeepSeek、Qwen等)的微调技术,针对特定场景优化模型性能。

  • 学习如何利用领域数据(如制造、医药、金融等)进行模型定制,提升任务准确性和效率。

RAG应用开发

  • 深入理解检索增强生成(Retrieval-Augmented Generation, RAG)技术,构建高效的知识检索与生成系统。
  • 应用于垂类场景(如法律文档分析、医疗诊断辅助、金融报告生成等),实现精准信息提取与内容生成。

AI Agent智能体搭建

  • 学习如何设计和开发AI Agent,实现多任务协同、自主决策和复杂问题解决。
  • 构建垂类场景下的智能助手(如制造业中的设备故障诊断Agent、金融领域的投资分析Agent等)。

如果你也有以下诉求:

快速链接产品/业务团队,参与前沿项目

构建技术壁垒,从竞争者中脱颖而出

避开35岁裁员危险期,顺利拿下高薪岗

迭代技术水平,延长未来20年的新职业发展!

……

那这节课你一定要来听!

因为,留给普通程序员的时间真的不多了!

立即扫码,即可免费预约

「AI技术原理 + 实战应用 + 职业发展」

「大模型应用开发实战公开课」

👇👇

👍🏻还有靠谱的内推机会+直聘权益!!

完课后赠送:大模型应用案例集、AI商业落地白皮书

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询