1. 为什么是32B:这个规模到底解决了什么问题
1.1 医疗场景的真实痛点
先聊一个反直觉的现象:很多人以为医疗模型难在"专业知识的深度",实际做下来你会发现,难在"多模态信息的对齐"和"输出形式的规范性"。
放射科医生看一张胸部CT片子,脑子里同时在做三件事:识别病灶区域、对照患者病史、按结构化报告模板组织语言。通用大模型做第一件事勉强凑合,第二件事基本靠猜,第三件事——让模型输出一份符合放射科书写规范的诊断报告——几乎必翻车。不是模型不够聪明,而是通用模型压根没见过几万份"图像-报告"配对数据。
我这次的目标很明确:做一个能接受医学影像输入、输出结构化诊断描述的多模态模型。输入是图,输出是文本,中间跨越的是视觉特征和医学语义之间的鸿沟。这个鸿沟,7B模型填不平,70B模型填得起但跑不动,32B是一个经过成本、效果、工程复杂度三方权衡后的选择。
1.2 7B、32B、70B的取舍逻辑
这个选择不是拍脑袋定的。我列一个实际训练中会遇到的资源账单,你就明白为什么32B是甜点区:
- 7B模型(约140亿参数):bf16权重占14GB,LoRA训练单卡A100 40G就能跑,甚至消费级显卡勉强能玩。但实际测试下来,7B在多模态医疗场景的短板很明显——复杂推理能力弱,面对"多个病灶并存""影像征象与病史关联"这类需要多步推理的问题,经常给出答非所问的结论。
- 32B模型(约640亿参数):bf16权重占64GB,LoRA训练需要4卡A100 80G起步,全参微调需要8卡甚至更多。这个规模跑到什么水平?恰好是"能进行多步推理、能理解复杂指令、能记住大量医学知识"的门槛。最关键的,32B开源社区生态成熟,基座模型选择多。
- 70B模型:效果当然更好,但显存需求直接翻倍,训练和推理成本指数上升。对大多数团队来说,不是技术不行,是账单不行。
我最后选定了Qwen2.5-32B-Instruct作为语言基座。原因有三:其一,它的中文能力和指令跟随能力在开源模型里属于第一梯队,医疗场景对中文术语的理解要求极高;其二,32B这个尺寸的上下文窗口和推理能力足够支撑"图文交错输入"这种复杂任务;其三,社区生态活跃,踩坑有人陪,出了问题能找到讨论。
1.3 视觉编码器与连接器的选型
多模态模型的结构,说白了就是三部分:视觉编码器负责"看图",语言模型负责"说话",连接器负责"翻译"。前两者是成熟组件,真正决定上限的往往是连接器。
视觉编码器我用了SigLIP-SO400M,不是CLIP。原因很简单:SigLIP在图文对齐上用了更稳定的损失函数,视觉特征的判别力更强。医学影像有个特点——病灶区域往往只占整张图像的极小比例,编码器必须能捕捉细粒度特征。SigLIP的patch-level特征保留做得比CLIP好,这对后续的细粒度识别至关重要。
连接器走的是**两层MLP(多层感知机)**路线,不是Q-Former也不是Resampler。MLP结构简单、参数少、训练稳定,对医疗这种数据量不算海量的领域更友好。Q-Former那套交叉注意力机制理论上能压缩更多视觉token,但在医学影像上容易丢失高分辨率细节,我实测下来性价比不高。
连接器输出维度是4096,和Qwen2.5的hidden size对齐,这样喂进去的语言模型无需改动内部结构,只用标准的attention机制处理视觉token和文本token的拼接序列。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据工程:一块显卡换不回一条坏数据
2.1 医疗数据的来源与合规审查
这是整个项目里最耗时、也最不能省的一环。模型训练领域有句老话叫"垃圾进,垃圾出",在医疗场景要再加一句:"违规进,凉凉出"。数据合规不是法务部门的事,是训练工程师的第一道生死线。
我这次的数据来源主要有三类:
- 公开学术数据集:如PubMed上的图文配对数据、公开的胸部X光数据集、皮肤镜图像数据集等,这些数据集的授权协议要逐条核对,有些只允许科研用途,商用需要单独申请。
- 医院合作脱敏数据:通过正规合作渠道获取的脱敏影像与报告,所有患者身份信息(姓名、病历号、人脸区域)必须经过不可逆脱敏处理,且无法反推。
- 合成数据与大模型生成数据:用已有报告模板和影像描述,结合GPT-4等通用大模型生成多样化的报告文本,再与脱敏影像配对。这类数据占比控制在20%以内,防止模型学到"AI味"。
合规审查的流程是这样的:所有原始数据先经过脱敏工具扫描,再人工抽检10%确认无隐私泄露风险,最后登记数据来源、用途、有效期,建立完整的溯源台账。
2.2 从原始图文到干净样本的清洗流程
数据清洗听起来土,实际上是决定模型上限的隐形王牌。我写了一个多级过滤pipeline,每一级都在砍数据,砍完你才知道原来原始数据里有一半是没法用的。
第一步是图像质量过滤:用OpenCV检查图像的清晰度(拉普拉斯方差)、亮度分布、是否存在黑边或水印。医学影像对清晰度的要求比自然图像高一个量级,模糊的X光片连医生都看不清,模型更不行。这一步大概过滤掉15%的图像。
第二步是图文匹配度过滤:用CLIP计算图像和文本的相似度分数,低于阈值的样本直接淘汰。你可能会问,CLIP本身在医学图像上就不准,用它过滤会不会误杀?会。但我的策略是保留阈值两端的样本:相似度极高的肯定没问题,相似度极低的肯定不匹配,中间灰色地带全部保留,再靠后面的规则进一步判断。
第三步是文本质量过滤:清洗报告中的乱码、特殊符号、过长或过短文本,去除重复样本。用MinHash做去重,相似度超过0.85的样本只保留一条——重复数据会让模型在训练时对这部分特征过拟合,等于变相压缩了其他样本的学习机会。
第四步是医学语义校验:用规则检查报告中是否包含关键解剖部位术语,比如胸部X光报告必须包含"肺""纵隔""心影"等至少一个核心关键词。这一步能有效过滤掉"图文配对但完全不相关"的噪声样本。
清洗后的数据量,大约是原始数据量的40%。看着心疼,但值得。多模态模型的对齐能力全靠数据质量撑着,宁缺毋滥。
2.3 数据合成:补足长尾场景与指令多样性
医疗数据有个天然的"二八定律"——常见病种的样本占了80%,但真正考验模型能力的,恰恰是那20%的罕见病和复杂场景。公开数据集的罕见病样本少得可怜,怎么办?合成。
我用的是"模板加改写"两层策略:
第一层:结构化模板生成。 基于放射科报告的书写规范,设计了一套JSON模板,包含"检查部位、影像表现、诊断意见、建议"四个字段。每个字段填充可替换的医疗实体,比如"影像表现"里可以替换为"双肺纹理增粗""右上肺可见斑片状高密度影"等不同描述。这种模板生成的数据有两大优势:结构规范、覆盖均匀,不会出现某种表述方式过度集中。
第二层:大模型改写强化。 把模板生成的报告丢给GPT-4o等通用大模型做同义改写,输出更加口语化、多样化的表述。比如"右上肺可见斑片状高密度影,边界模糊"可以改写成"右上肺有片状模糊影,看不太清边界"。这一层的作用是让模型学会"一个意思,多种说法",避免模型在推理时只会输出模板语言。
训练数据的指令也做了多样性扩充。原始报告是"描述式"的,但真实医生使用场景是"提问式"的。我把描述式文本转成了三类指令:诊断式("请根据图像给出诊断建议")、问答式("患者右肺上叶的病灶大小约多少")、报告式("请生成一份完整的胸部X光检查报告")。
2.4 质量分级与采样策略
数据清洗完后不是直接喂给模型,还要做质量分级。我把数据分成了三个等级:
| 等级 | 标准 | 训练阶段 | 采样权重 |
|---|---|---|---|
| A级 | 专家标注、图文强相关、结构完整 | 全阶段 | 1.0 |
| B级 | 图文相关但表述不完美 | 预训练、SFT后段 | 0.6 |
| C级 | 弱相关或合成数据 | 仅预训练 | 0.3 |
采样权重的意思是,在训练时每个batch里A级样本被选中的概率是C级的3倍多。这样既保证了高质量数据的主导地位,又让C级数据发挥"语料扩充、防止过拟合"的作用。
做这一步的初衷是:在一次试验中我发现模型对某类罕见病描述出现了严重的"鹦鹉学舌"现象,输出格式完美但内容完全是背下来的。后来定位到是合成数据的比例太高、多样性不足。加了质量分级和权重控制后,这个问题明显缓解。
3. 训练策略拆解:为什么不是一步直接SFT
3.1 三阶段设计的总体思路
很多人在做垂直领域大模型时有个误区:拿基座模型直接SFT(监督微调)领域数据,完事。效果呢?往往惨不忍睹。直接SFT的问题是"没学会走路就想跑步"——模型还没理解视觉特征和医学语义的基本对应关系,就要求它生成复杂报告,它只能硬编,学不到泛化能力。
我这次采用的是三阶段渐进式训练策略:
- 特征对齐阶段:让视觉编码器的输出和语言模型的输入空间对齐。这个阶段只训练连接器(MLP),冻结所有其他参数。目标是让模型"看懂"医学图像,即视觉特征能被语言模型有效理解。
- 领域继续预训练阶段:用大量医疗图文数据和通用文本数据混合,低学习率训练整个模型(或大部分参数)。目标是让模型在保持通用能力的同时,吸收医学知识。
- 指令微调阶段:用高质量的指令-响应对训练模型学会"遵循医疗指令、输出结构化报告"。这个阶段会加入多轮对话数据和人机对齐策略。
三个阶段的比例是15% / 55% / 30%,前两个阶段花的时间长是因为数据量大,第三个阶段数据量少但迭代频繁。
3.2 特征对齐:为什么不直接全参训练
第一个阶段我冻结了语言模型和视觉编码器的所有参数,只训练连接器MLP。很多人会问:这样是不是太保守了?为什么不直接全参训练,让所有参数一起适应?
答案在于训练稳定性。全参训练时,视觉特征和文本语义的分布差异很大,初始梯度方向可能互相干扰,导致loss震荡甚至发散。冻结大部分参数、只训练连接器,相当于先修一条"翻译通道",让两个异构空间建立起初步映射。这条通道稳定后,再放开其他参数做细调,训练过程就平稳得多。
这个阶段的训练数据以图像-描述对为主,每张图像配一段简短描述,不需要完整报告。数据量在50万对左右,训练3个epoch,学习率设在1e-4,batch size 32。训练完成后我做了个快速验证:拿一张测试图像让模型"描述看到了什么",如果输出的是合理的病灶描述(哪怕语法不完整),说明特征对齐基本完成。
3.3 领域继续预训练:防止灾难性遗忘
第二阶段是领域继续预训练。这一步的要点是:不是只喂医学数据,要和通用数据混合。
具体比例是医疗图文数据40%、通用文本数据30%、通用图文数据20%、代码数据10%。为什么保留这么多通用数据?因为模型一旦只学医学数据,很快就会"忘记"通用能力——指令跟随变差、推理能力退化、甚至中文表达能力下降。这个现象叫灾难性遗忘,在垂直领域微调中几乎必然出现,混合通用数据是成本最低的缓解手段。
学习率需要降得很低。32B模型在继续预训练阶段,我用的是峰值学习率1e-5,warmup占比3%,采用余弦退火。低学习率的意义在于:模型已经学会了通用知识,这个阶段只是"微调"它的知识分布,而不是"重写"它的参数。学习率稍大一点,loss就会出现明显的震荡,训练不稳定。
训练轮数控制在1个epoch左右。因为医疗数据的规模毕竟有限,多轮重复学习反而会让模型对训练集中某些特定的表述方式过度依赖,损害泛化能力。
3.4 指令微调:决定模型"好用不好用"的关键一步
前两个阶段解决"懂不懂"的问题,第三阶段解决"听不听话"的问题。指令微调的数据量不需要太大——我这边SFT数据大概3万条,比预训练数据少了两个数量级,但每条数据的质量要求极高。
SFT数据包含几类:
- 单轮诊断指令:"请分析这张X光片并给出诊断结论",期望输出为完整的结构化报告
- 多轮追问指令:第一轮问"图中病灶的位置在哪",第二轮问"这个病灶的大小和形态如何",第三轮问"请给出建议"。多轮数据让模型学会对话式追问,这是急诊场景的高频需求
- 纠错指令:给出一个包含错误的报告,指令是"请修正这份报告中的错误"。这类数据能显著提升模型对细节的敏感度
- 安全拒绝指令:当输入质量过低或问题超出范围时,输出"图像质量不足以诊断,请重新摄片"或"该问题超出我的诊断范围,请咨询临床医生"。
SFT阶段的学习率设为5e-6,训练2-3个epoch。这个阶段需要密切关注过拟合迹象——如果训练集loss持续下降但验证集loss回升,说明模型开始背训练集了,应该立即停止。
3.5 LoRA与全参微调的最终选择
32B模型全参微调的显存开销很大(后面细算),很多团队因此选择LoRA。我也做了对比实验,结论是:LoRA可用,但性能上限不如全参微调。
LoRA的本质是在原有权重旁边并联一个低秩矩阵,训练时只更新这个矩阵。在指令微调阶段,LoRA的效果其实相当不错——毕竟SFT数据量小,不需要大规模更新参数。但在领域预训练阶段,数据量大、知识注入需求高,LoRA的参数量上限(通常只占总参数0.1%-1%)就会暴露不足,模型难以吸收足够多的医学知识。
我的最终方案是:
- 特征对齐阶段:只训练MLP,显存占用极低
- 领域预训练阶段:全参微调(冻结视觉编码器的部分层)
- 指令微调阶段:LoRA + 全参微调对比实验,最终选LoRA作为主方案
原因在于,指令微调的批次数据规模小,全参微调虽然效果好,但每次实验的成本高、迭代慢。LoRA可以用更低的显存跑更大batch,而且可以通过调整秩(rank)控制参数更新量,灵活度高。
4. 真正要命的工程细节:显存规划与loss异常
4.1 32B模型训练的资源账单
说句掏心窝子的话:训练32B模型,一半的时间在调参,另一半的时间在算显存账。先把这笔账算清楚。
以bf16精度为例,一个32B模型:
- 模型权重:32B × 2字节 = 64GB
- 梯度:32B × 2字节 = 64GB
- AdamW优化器状态:每个参数需要2份动量 + 2份方差 = 32B × 8字节 = 256GB
- 合计:384GB
这是什么概念?8张A100 80G是640GB显存——如果不开任何优化策略,只能勉强放下。等batch size一上去,直接爆显存。所以必须上优化手段:
| 优化手段 | 节省内容 | 显存占用降低 |
|---|---|---|
| AdamW bitsandbytes 8bit | 优化器状态 | 约减少128GB |
| ZeRO Stage 2 | 梯度按卡分片 | 64GB均分到8卡,每卡省8GB |
| 梯度检查点 | 激活值 | 显存降约60%-70% |
用上这些手段后,8卡A100 80G可以跑batch size 6、序列长度4096的训练任务,GPU利用率大约78%。这个数字是实测值,如果你的数据序列更长或batch更大,需要重新算账。
4.2 并行策略选型与实测对比
并行策略的组合方式很多,我逐个试过之后总结的经验是:能用ZeRO就不用张量并行,能张量并行就不用流水线并行。
- ZeRO Stage 2:把梯度和优化器状态分片到各卡,通信量适中,适合8卡以内的集群。
- ZeRO Stage 3:把模型参数也分片,适合单卡放不下完整模型的场景。但通信开销显著增大,实测训练速度比Stage 2慢约30%。
- 张量并行(TP=2):把模型按层内维度切分,减少显存压力。缺点是通信频繁,且对注意力机制的实现有侵入性。
- 流水线并行(PP):把模型按层切分到不同卡,但存在"气泡"问题,GPU利用率偏低。
我最终用的是ZeRO Stage 2 + 梯度检查点组合。模型34GB的权重被ZeRO分片到8卡,每卡约占4GB;优化器状态用8bit后大幅压缩。这样每卡还剩约60GB可用空间,足够跑batch size 6-8的训练任务。
如果你的训练数据序列特别长(比如长报告),可以考虑在注意力层加序列并行(Sequence Parallelism)。我在长序列场景下试过,显存确实省了,但实现复杂度高,收益与成本不一定匹配。常规场景用不上就不用给自己加戏。
4.3 loss不降、NaN与震荡:踩过最深的三个坑
第一个坑:loss不降或极慢。表现是训练前500步loss基本保持不变,像焊死了一样。排查链路是:先检查数据加载是否正确,确认图像和文本是配对进入模型的(这个问题最常见——dataloader拼接错误导致"图不对文");然后检查学习率,32B模型用1e-4学习率做全参微调,loss确实可能不降,物理原因在于参数空间太大、梯度方向被稀释。我建议领域预训练阶段把峰值学习率压到1e-5,这是我在对比实验后固定下来的值。
第二个坑:loss突然变成NaN。这个现象通常出现在训练中途某个step,之前一切正常,下一秒loss直接变成NaN。我的排查经验是:
- 检查学习率是否过大——如果训练初期的loss曲线有小幅上升趋势然后突变成NaN,大概率是这个原因
- 检查混合精度设置——bf16在数值范围上比fp16宽很多,但在极小概率下仍可能出现溢出。可以尝试关闭AMP跑100步,如果loss恢复正常,说明是精度问题;如果还是NaN,再看下一步
- 检查数据中是否有异常值——极端案例(比如像素值全是0的图像)会导致梯度爆炸。我用了一个简单的方法:在数据加载后加一步数值范围检查,把非法样本自动过滤掉
第三个坑:训练集loss下降但评估集指标反而变差。这个不是bug,是过拟合的警告信号。我遇到的实际案例是:训练集loss从1.8降到1.2,但验证集上生成的报告质量反而下滑,开始出现"背模板"的现象。解决办法是缩小训练轮数、提高dropout率、增加数据多样性。医疗模型对过拟合的容忍度比其他领域更低——患者不会按照你训练集里的模板生病。
4.4 数据加载与序列打包的优化
医疗数据以长文本为主,一份完整的CT报告可能长达500-1000个token。直接把长文本截断会丢失关键信息,不截断又会导致batch中序列长度差异巨大、显存浪费严重。我的方案是序列打包(sequence packing):把多份短文本拼接成固定长度(比如4096)的样本,用特殊的separator token分隔,并在attention mask中标记出各段文本的边界,确保不同段落的token不会互相attend。
序列长度我在训练中设过多个档位对比:2048、4096、8192。2048对长报告来说会截断关键信息,8192虽然能塞下完整报告,但训练速度下降超过40%。最终选择了4096作为默认值,这样既覆盖了绝大多数报告的长度要求,又保持了可接受的训练效率。
数据加载还有一个容易忽略的细节:使用WebDataset或类似格式。30万张图片如果用小文件方式读取,IO会成为训练瓶颈,GPU利用率掉到50%以下。我的做法是:把图片打包成tar包(每个tar包约1GB),用流式方式读取,配合多进程预取,IO基本不再是瓶颈。
5. 评测不是最后一步:医疗场景的验证方法
5.1 为什么通用指标在医疗场景不够用
训练结束后,马上会遇到一个问题:怎么判断模型好不好?常识是看BLEU、ROUGE这些生成指标,但我必须说,这些指标在医疗场景有严重的失真。
BLEU衡量的是n-gram重叠率,但医疗报告的专业性和结构性决定了:用户不关心你的措辞是否和参考答案一模一样,只关心医学术语是否准确、诊断结论是否正确。我见过一个案例,模型生成的报告BLUE分数很高,但它把"右上肺"写成了"左上肺",一个字的偏差在医学上意味着完全不同的诊断。这种错误在做BLEU评估时可能不会触发惩罚,但在临床上是致命的。
同理,困惑度(PPL)也不能作为判断标准——PPL反映的是模型对数据分布的拟合程度,不反映医学知识掌握程度。一个对医学文本过拟合的模型,PPL可能很好看,但实际问答能力一塌糊涂。
5.2 我搭的三层评测体系
经过几轮试错,我确定了一套三层评估体系:
第一层:通用能力回归。 用CMMLU、MMLU、GSM8K等通用基准测试模型在SFT后是否出现能力退化。医疗模型不能因为垂直而变笨,如果通用分数下滑超过3%,说明灾难性遗忘控制失败,需要调回通用数据比例。
第二层:医疗领域基准。 我采集了公开的医学问答集、影像报告集,构建了一个约2000条的评测集,覆盖6大部位(胸、腹、骨、脑、乳腺、皮肤),要求模型输出结构化报告。自动评判采用"关键实体准确率"——从参考报告中抽取病灶位置、大小、形态、诊断结论等关键实体,与模型输出逐一比对。这个指标比BLEU更能反映医疗语义的正确性。
第三层:人工评测。 我请了3位具有主治医师及以上资质的医生对模型输出打分,维度包括:
- 医学准确性(是否有事实性错误)
- 描述完整性(是否覆盖了图像中的所有关键结构)
- 报告规范性(是否符合书写规范、逻辑清晰)
- 安全边界(是否在输入质量差时给出合理拒绝)
人工评测每次花费的时间成本很高,但对于医疗模型是必须的,没有任何自动指标可以完全替代临床视角的判断。
5.3 从评测结果反推训练策略
评测不只是给模型打分,更是为了找bug、修bug。我在第一轮人工评测中发现一个高频问题:模型经常忽略图像中的次要病灶,只描述最明显的病变。比如胸片里主病灶是右上肺的肿瘤,但左下肺还有一个小的结节,模型往往"视而不见"。
这个问题的根源在于训练数据的分布——大部分报告描述的确实是"最显著病灶",次要病灶被提及的频率太低。我把训练数据中带有"多病灶描述"的样本占比从15%提升到40%,并且降低了只描述单一病灶的样本的采样权重。第二轮评测中,次要病灶的召回率从58%提升到79%,说明数据分布调整起了作用。
另一个发现是:模型对低质量输入图像的拒绝能力不足。有些图像严重过曝或分辨率极低,人类医生一眼就能判断"无法诊断",但模型还在硬着头皮输出报告——这是非常危险的行为模式。我在SFT数据里专门增加了一批"低质量图像+安全拒绝"配对样本,让模型学会"没有把握时不要乱说"。
5.4 训练过程中的评估频率与快速验证
除最终评测外,训练过程中的快速验证同样重要。我的做法是:每训练500步,用50条评测集做一次快速验证,生成样本人工扫一眼。这个方法帮我发现了两次问题——一次是数据加载bug导致的图像位置错位,另一次是学习率过高导致的报告开始胡言乱语。这两次问题如果不在训练中及早发现,等到训练完再看,几千张GPU卡时直接打水漂。
快速验证的数据量不用太大,50条足够看出趋势。这些评测数据要固定不变,才能保证不同checkpoint之间的对比有意义。
从立项到训练跑通,这个问题我反复琢磨过很多轮:选型、数据、训练策略、工程优化、评测,每个环节单拎出来都有大坑,组合在一起更像是"在泥地里拖一台重卡"。最深的体会是:多模态医疗模型的训练没有什么玄学,每一个效果提升的背后,都是数据分布、训练参数、评测反馈之间反复较量的结果。32B这个规模,恰好让你感受到"大模型训练"的真实成本,又不至于像70B那样一步踏空就倾家荡产。
这一篇把训练启动前的选型逻辑和训练全流程的决策点都交代了。真正把模型推向生产环境,还有推理优化和部署评估这座大山在后面等着——那部分是下半场的主题,等我跑完再写。如果你正在做类似的事情,有不同选型或训练策略的经验,欢迎多交流。
