DeepSeek + HuatuoGPT SFT 调试全记录
这篇记录给 DeepSeek-Coder-7B-base-v1.5 做医疗 SFT 的全过程——在已有的 LoRA checkpoint(checkpoint-2000)基础上,用 FreedomIntelligence/HuatuoGPT-sft-data-v1(22.6 万条医疗对话)继续微调。难点不在训练本身,而在一路把各种中断(显存、端口、网络、数据格式)都堵掉,让它能在后台稳定跑十几个小时。
起步的两个坑
基于 supervised_finetuning.py 写了个 run_sft_deepseek_huatuo.sh,第一次跑就撞上两件事:
- 路径写错:脚本里调用
MedicalGPT/supervised_finetuning.py,但已经在MedicalGPT目录下运行,被拼成了MedicalGPT/MedicalGPT/...,找不到文件。改成supervised_finetuning.py即可。 - batch 太大:
per_device_train_batch_size一开始设了 12,对 7B 模型直接 OOM。降到 2、梯度累积保持 4(有效 batch 约 16)。
让它能长期后台跑
训练要跑十几个小时,前台一断 SSH 就没了,所以改成 nohup 后台运行,日志写到带时间戳的 logs/sft_training_YYYYMMDD_HHMMSS.log,同时把进程 PID 存下来方便随时 kill,启动时打印一遍配置。之后随时 tail -f 看进度。
卡在数据集下载
后台跑起来后日志停在 tokenizer 加载完那一行,两分钟没动静,GPU 只占了 779MB——其实是卡在数据集下载。提前手动把数据集下好、切到镜像源,并确认 HF_DATASETS_CACHE 和脚本一致:
export HF_ENDPOINT=https://hf-mirror.com
端口冲突
再跑报 EADDRINUSE: address already in use,是上一次中断的 torchrun 还占着通信端口。清掉残留进程就好:
pkill -f supervised_finetuning.py
数据字段对不上
进入训练又报 KeyError: 'conversations'。脚本默认数据里有 conversations 字段,而 HuatuoGPT 实际是 {"data": ["问:...", "答:..."]}。改 preprocess_function 同时兼容两种格式,并把 问:/答: 前缀去掉、映射成标准对话:
if "data" in cols and "conversations" not in cols:
def convert_huatuo_to_conversations(examples):
out = []
for item in examples['data']:
if isinstance(item, list) and len(item) >= 2:
out.append([
{"from": "human", "value": item[0].replace('问:', '').strip()},
{"from": "gpt", "value": item[1].replace('答:', '').strip()},
])
return {"conversations": out}
顺带修了个量化加载分支里的 UnboundLocalError——把 load_in_8bit / load_in_4bit 的判断补全,保证各种配置下 model 都会被正确初始化。另外加了个 check_and_optimize_memory(),模型加载后打印每块卡的显存并启用 Flash / Memory-Efficient SDP。
全量训练结果
坑都堵完后跑全量:batch 4、梯度累积 4、2 epochs、学习率 1e-5,用 tee 同时输出到终端和日志。
2025-08-10 21:54 启动,2025-08-11 19:27 完成,历时 21 小时 29 分。总步数 27,974,训练样本 223,781、验证 2,261;训练 loss 从 ~1.82 降到 1.5231,验证 loss 1.5198(和训练 loss 基本贴合,没过拟合),困惑度 4.5712,速度 5.784 samples/sec,单卡最大显存约 20GB,梯度范数一直稳在 0.6–0.8。产物在 outputs-sft-deepseek-huatuo-full/,每 1000 步存一次、保留最近 5 个 checkpoint。
需要部署时把 LoRA 合并回基模型即可:
from peft import PeftModel
base = AutoModelForCausalLM.from_pretrained("deepseek-ai/deepseek-coder-7b-base-v1.5")
model = PeftModel.from_pretrained(base, "outputs-sft-deepseek-huatuo-full")
merged = model.merge_and_unload()
回头看,这次真正花时间的全是工程问题:数据格式适配、分布式端口清理、后台加日志、提前下数据、显存优化——模型本身反倒是最省心的部分。