实习期间我独立负责的一个内容合规质检模型:输入文案 + 平台规则,输出结构化 JSON——是否违规、违规原文、并精确溯源到具体哪一条规则,覆盖小红书/抖音/视频号/公众号四个平台。基座 Qwen2.5-14B-Instruct,项目的硬指标是溯源率 90%。从数据、训练、评估到部署全程我自己做。数据集分工:公众号在同事版本上改造、视频号原本是同事的但质量不行我推倒重做、小红书和抖音完全自己从头做。
这是我实习里投入最大的一个项目,跨了近两个月。完整复盘我拆成一组深度文章,本页是把它们串起来、再补上过程细节的总览:
- 从零搭数据集——最源头:爬规则 / 清洗 / 分类体系 / 造样本 / exp_001-003 第一次微调
- 接手后重做数据集——视频号推倒重做、那些编不出来的违规样本
- exp_005 到 exp_009 六轮微调试错全记录——每轮配置/loss/怎么崩的
- 8 卡跑 7B 分布式微调——多卡不是线性提速、GC 省显存不省速度
- 评估口径之战——同一个模型从 29% 到 96%
- 踩坑与决策全集——现象→根因→解法,自用速查
模型要解决的真问题
输出结构化 JSON:{is_compliant, violations:[{original_text(≥10字精确子串), title, content, suggestion}]}。推理是三阶段——全量扫描候选 → 逐条复核 → 按引用去重。四平台规则结构差异极大,直接决定方案:小红书 49 条/14 类、抖音 107 条/21 类、视频号 135 条/11 类、公众号 53 条全挤在 1 个类别。后面会看到,这个结构差异比模型能力更决定效果。
方案演进:六轮试错
不是一上来就有答案,是一版版逼出来的(前面 exp_001-003 在从零搭数据集里,exp_003 规则归类已到 97%):
- exp_005(LoRA r64/alpha128,6849 条,3 epoch,1287 步,22 分钟):cutoff=1024 太短导致长样本截断,loss 假性收敛到 0.0137,溯源率只有 35-52%。
- exp_006(cutoff 提到 4096):解决截断,loss 回到真实的 0.245,但吞吐慢约 7 倍(15 → 2 samples/s),一轮训练近 3 小时。
- 认知转折:发现”溯源差”很大程度是评估格式错了——评估一次塞全量规则,而训练每条只见 1-3 条规则,分布完全对不上。改成按类别逐组推理再合并(per-category),不用重训。
- exp_007(单规则二分类 + RAG,16056 条):失败。RAG 召回只有 5-35%,正确规则没进模型视野,学的东西用不上。
- 三阶段推理(scan→verify→dedup,base 模型):去重让精确率明显回升(抖音两阶段→三阶段 20.6%→38.5%)。核心洞见在这——规则的分类结构比模型能力更决定效果:同一方案小红书 59% vs 公众号 11%,差 5 倍,就因为公众号 53 条规则全挤在一个类别里。
- exp_008(最终部署版,batch_format 对齐三阶段推理,QLoRA 4-bit,6889 条,430 步):merge 后用 AWQ INT4 把 28GB 压到约 10GB,跑在 24G 卡上,vLLM + FastAPI 部署。
- exp_009(Hard Negative 冲 90%):灾难性失败,平均 Recall 从 79.6% 暴跌到 46%。5 分钟脚本回滚到 exp_008。
- exp_010(修了 HardNeg bug 再训):mini-eval 60.7% 低于回滚门槛,自动回滚——但意外发现门槛本身是用更松的口径定的,这又绕回了”口径”问题。
最终生产部署的是 exp_008-AWQ。每次重训前都得先算成本(4096 cutoff 慢 7 倍、一轮近 3 小时),所以养成了”冲新高前先钉死可回退基线”的习惯。
几个真因被挖出来的 bug
- 视频号 0% 召回:模型其实认对了违规类型,但条目号是从训练记忆里掏的——同一个”赌博”违规在训练数据里被打了 4 个不同文档的冲突标题(20 次”第七条”/20 次”第五条”/13 次”第八条”/10 次”第二条”),模型学成了”赌博→随机吐一个”,而不是”从 prompt 复制”。一个数据标注问题,伪装成了模型召回问题。 小红书一对一加前缀就好,视频号一对多加前缀没用。
- 规则归类全 0%:评估脚本用
zip按位置配对 pred/exp 违规,顺序不同就全判错(之前单条测 89.7% 是因为只有 1 条违规刚好对上)。改集合匹配修复——修完才暴露真问题:精确率只有 5-8%,模型严重过度预测(per-category 推理时不相关类别也乱报,某轮抖音 TP=9/FP=166)。 - AI 类别疯狂误报:训练数据污染——某 AI 类别 566 条样本里 23.9% 的文案跟 AI 无关却塞进了 AI 的 prompt,违规标题甚至标成了别的平台的规则。
- 过度预测才是病根:抽 28 条让独立子 agent 当人审,结论是 GT 全合理、模型主标签命中率高,但副标签常乱挂(“手都冻红了”标成”过度博取关注”、虚假宣传乱挂”AI 托管”)。模型不是漏,是多报——这正解释了为什么 Hard Negative(让模型更挑剔)方向没错,但数据有毒反而搞砸。
环境与运维:一半时间在这上面
这个项目很大一部分精力耗在工具链和部署上,记几个真实的:
- torch 被装坏:上一次会话装 llm-compressor 把 torch 2.6.0 的文件混进了 2.10.x 的,连 transformers 都导不进来。修复经历了 transformers 降级 → 从 wheel 全量提取 torch 文件 → 手工把被改名的
functorch子目录(~ompile/~inops)改回 → 删掉 70 个多余的新版文件。 - vLLM segfault:torch.compile / dynamo 阶段崩,用
--enforce-eager禁掉 compile 绕过。 - AWQ 量化:autoawq 与 transformers 版本不兼容,import 直接段错误,改用 llm-compressor;量化必须
device_map='cpu'(14B BF16=28GB > 24GB 显存)。 - 最隐蔽的全 0% bug:vLLM 用
--served-model-name exp_008-AWQ注册,但 eval 脚本 MODEL 写的是完整路径 → 404 → 请求静默失败返回空 → 所有指标全 0%。 - 跨机器部署:模型几次在不同 GPU 机器间迁移(48G/24G/8 卡共享机),最后部署在一台只读的共享服务器上,通过本机的反向 SSH 隧道才连得进去;隧道还反复软死(TCP 通但 HTTP 000),换了一串端口重建。
- 上下文压缩的状态倒退:长对话被压缩后,恢复的摘要有时是几天前的旧任务,导致重跑了已经被超越的工作——靠人工把话题拉回来才纠偏。
- 还有一堆小坑:nohup 输出要
-u否则看不到进度、端口被占pkill -9不释放得用fuser -k、中文路径在 SSH heredoc 报语法错得本地写好再 scp。
评估:1653 条的子 agent 审核 + 跨厂商对照
测试集本身也不可信(GT 由大模型生成)。我用 6 个子 agent 并行把一份 1653 条的测试集逐条审了一遍,发现 GT 硬错率约 25%(平台标错、规则名是编的、什么都往”过度营销”筐里塞)。关键洞见:用一份错的 GT 测出来的低分,衡量的是”被测模型和生成 GT 的那个 LLM 像不像”,不是真实能力——模型更精准,反而和糙 GT 的一致性更低、分数更难看。
溯源率的口径之战
这是整个项目最值钱的认知:同一个 exp_008,溯源率从 28.87%(最严:独立子 agent 看全部 344 条规则当标准答案、按违规条数穷举)到 96%(self-check:拿模型自己的预测当标准答案)——差的全是口径。其中两个尤其要说清:
- 94% 的”自洽率”是盲点:它衡量两次推理一不一致,temperature=0 本来就稳,连没微调的基座也能 90%+,证明不了微调起作用。我把这点如实写进报告,没拿它充数。
- 28.87% 不是模型差:我拉了当前最强的 DeepSeek-v4-pro,用完全相同的口径跑,它也只有 31.22%、我的模型 32.92%,几乎打平——证明这是 RAG 召回的天花板(子 agent 看全集 344 条,模型只检索 top-K),给业界最强模型看全集也就这水平。
冲指标的压力下我一度想选最松的口径凑 90%,但那是自欺。最后评估报告写的是”24G 硬件 + 现有数据,工程天花板约 75-78%,到不了 90%“,对外该报的是 88.5%(明显违规子集)/ 单平台最高 92.1% / 严格口径 70-78%,每个数字都标明口径。完整推演见 评估口径之战。
留下的判断习惯
异常好的 loss 先查截断和 mask;高准确率先核对评估格式和模型真实输入;“0% 召回”先看是不是训练标签冲突;线上差指标好先比训练分布和推理分布;冲新高前先钉死可回退基线;一个数字低,先用同口径拉个更强的模型一起跑、分清是”模型不行”还是”口径对谁都这么低”;最该警惕的不是低指标,是那个为了好看而悄悄选松的口径——把口径写清楚,比把数字做高更重要。