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()

回头看,这次真正花时间的全是工程问题:数据格式适配、分布式端口清理、后台加日志、提前下数据、显存优化——模型本身反倒是最省心的部分。