跳转至

电商客服

记录该项目的全过程和实验数据。

流水线统计

步骤 数量 说明
原始数据行数 212,807 chat.txt 原始行
过滤空内容/? 符号 1,531 行 无意义行
原始会话数 10,945 按 session_id 聚合
原始级去重 1,138 行跳过 同 session 同角色连续相同→跳过
消息级去重 4,088 条合并 连���同内容消息合并
变量替换后 10,941 个会话 [姓名x] [金额x] 随机替换

质量过滤

项目 数量
删除低质量 asst 回复 2,951 条(空回复/纯数字/单字敷衍)
因质量不合格丢弃 session 16 个
质量过滤后保留 10,928 个会话

低质量清洗

规则 数量
非客服对话 527 个
过短(<55 tokens) 453 个
user 含系统订单摘要 1,459 个
乱码严重 302 个
asst 平均短回复 16 个
低质量合计剔除 2,757 个会话
清洗后保留 8,168 个会话

场景分布

场景 数量 占比
退换货/售后 2,617 24.0%
物流查询 3,435 31.4%
商品咨询 1,206 11.0%
投诉/抱怨 21 0.2%
其他 889 8.1%

模板套话过滤

项目 数量
删除纯模板 asst 回复 2,015 条
受影响的 session 数 1,998

最终

项目 数量
跨会话去重 0 个移除
总过滤率 25.4%

输出文件: - jddc_trl_sft_rag.jsonl(含模板完整版):8,168 条 - jddc_trl_sft_rag_no_template.jsonl(去模板纯净版):8,168 条


模型打分统计

指标 数量 占比
总数据 8,168 条 100%
解析失败(score=-1) 202 条 2.5%
超范围 1 条 0.0%
高质量(≥6 分) 7,298 条 89.3%
低质量(<6 分) 667 条 8.2%
  • 有效评分均分:7.3,最高 9,最低 2

去 RAG 噪声 + 分层切分

剔除含 RAG 注入 session:SFT 918 条 + DPO 150 条 = 1,068 条
SFT 6,381 条 → 按场景分层 80/10/10 切分

文件 条数 用途
jddc_sft_train.jsonl 5,103 SFT 训练
jddc_sft_eval.jsonl 636 验证(防过拟合)
jddc_sft_test.jsonl 642 测试(最终评测)
jddc_dpo_pool.jsonl 517 DPO 候选
jddc_parse_fail.jsonl 202 解析失败

场景分布(5,103/636/642)

场景 训练 验证 测试
物流查询 2,092 261 263
退换货/售后 1,655 206 208
商品咨询 752 94 94
其他 592 74 74
投诉/抱怨 12 1 3

SFT 训练结果

消融实验:LoRA target_modules 对比

配置 方法 rank target_modules 可训参数 占比
V1 QLoRA 16 q_proj, k_proj, v_proj, o_proj 11.8M 0.29%
V2 QLoRA 16 上述 + gate_proj, up_proj, down_proj 33.0M 0.81%
V3 QLoRA 8 q_proj, k_proj, v_proj, o_proj(同 V1) 5.9M 0.15%
V4 LoRA(全精度) 16 q_proj, k_proj, v_proj, o_proj(同 V1) 11.8M 0.29%

V1 训练分析(rank=16,仅 Attention)

指标 起始 最终 评估
Training Loss 1.76 1.21 持续下降,正常拟合
Validation Loss 1.64 1.24 同步下降,无过拟合 ✓
Token Accuracy 65% 73% +8pp
Avg Loss - 1.58 全程均值

V2 训练分析(rank=16,Attention + MLP)

指标 Step 100 Step 200 Step 300 最终 评估
Training Loss 1.39 1.11 1.05 1.06 收敛更快
Validation Loss 1.34 1.17 1.13 1.13 更低 ✓
Token Accuracy 71% 74% 75% 75% 更高 ✓
Avg Loss - - - 1.49 最优

V3 训练分析(rank=8,仅 Attention)

指标 Step 100 Step 200 Step 300 Step 400 Step 500 Step 600 最终
Training Loss 1.86 1.53 1.44 1.34 1.34 1.30 1.31
Validation Loss 1.72 1.50 1.41 1.35 1.33 1.32 1.32
Token Accuracy 64% 68% 70% 71% 72% 72% 72%

V4 训练分析(LoRA 全精度,rank=16,仅 Attention)

指标 Step 100 Step 200 Step 300 最终
Training Loss 1.61 1.36 1.29 1.31
Validation Loss 1.56 1.38 1.33 1.33
Token Accuracy 67% 70% 71% 71%

四维对比

指标 V1(QLoRA) V2(+MLP) V3(r8) V4(LoRA) 结论
量化 4-bit 4-bit 4-bit 全精度 -
最终 Train Loss 1.21 1.06 1.31 1.31 V2 最优
最终 Eval Loss 1.24 1.13 1.32 1.33 V2 最优
Token Accuracy 73% 75% 72% 71% V2>V1>V4≈V3
Avg Loss 1.58 1.49 ~1.50 ~1.39 V2 最优

关键发现

  • QLoRA > 全精度 LoRA:V1 和 V4 仅量化方式不同,其余完全一致。V1(4-bit)eval loss=1.24,V4(全精度)=1.33。量化噪声在小数据集上起到正则化作用,反而优于全精度
  • rank=16→8 代价明显:V3 eval loss 从 1.24 退至 1.32,收敛慢约 300 步——rank 本身构成容量瓶颈
  • V2 整体最优:MLP 层 + QLoRA + rank=16
  • 消融结论:QLoRA 对本任务既省显存又提效果,rank=16 是容量下限

Pair-wise 盲测结果

V1 全量评测(仅 Attention,1,231 条对比)

指标 Base SFT
胜率 25.3% (311) 68.4% (842)
平局 - 78
平均回复长度 198.9 字 30.4 字
长度缩减 - 84.7%

V2 全量评测(Attention + MLP,1,256 条对比)

指标 Base SFT
胜率 25.6% (322) 67.0% (841)
平局 - 93
平均回复长度 180.2 字 25.8 字
长度缩减 - 85.7%

V3 全量评测(rank=8,仅 Attention,1,231 条对比)

指标 Base SFT
胜率 25.9% (319) 67.0% (825)
平局 - 87
平均回复长度 198.9 字 29.7 字
长度缩减 - 85.1%

V4 全量评测(LoRA 全精度,仅 Attention,1,256 条对比)

指标 Base SFT
胜率 27.5% (346) 64.8% (814)
平局 - 96
平均回复长度 180.9 字 24.8 字
长度缩减 - 86.3%

四维对比分析

指标 V1(r16 QLoRA) V2(+MLP) V3(r8) V4(LoRA)
可训参数 11.8M 33.0M 5.9M 11.8M
SFT 胜率 68.4% 67.0% 67.0% 64.8%
Base 胜率 25.3% 25.6% 25.9% 27.5%
SFT 长度 30.4 字 25.8 字 29.7 字 24.8 字
训练数据中位 19 字 19 字 19 字 19 字

发现

  • V3 rank=8 胜率 67.0%,仅比 V1 rank=16 低 1.4pp——参数减半但效果接近,rank 边际收益递减
  • V3 与 V2(+MLP, 33M 参数)胜率平齐(67.0%)——rank 不是 bottleneck 时,Attention 层已足够
  • V4(全精度)胜率垫底(64.8%),证实 QLoRA 正则化在本任务的优势
  • 训练指标排序 V2>V1>V3>V4 与盲测胜率不完全对齐:V3 训练 loss 最差但胜率与 V2 持平,说明 eval loss 不完全代表用户感知质量
  • V1/V3(transformers)产出 1231 对;V2/V4(vLLM)产出 1256 对。差 25 对来自:260 条对话中有 25 条消息数为奇数,transformers 用 max_t = len(msgs)//2 截断了尾轮(用户发送后无 assistant 回复),vLLM 未做截断而多生成了这 25 轮。胜率按比例计算,不影响横向对比

  • V1(QLoRA)胜率最高(68.4%),V4(全精度 LoRA)最低(64.8%),差了 3.6pp——与 training loss 一致,QLoRA 在小数据上的正则化效应在胜率中得到验证

  • V2(+MLP)与 V1 持平,MLP 贡献边际
  • 结论:QLoRA 是本任务最佳选择——既省显存又提效果,全精度反而退步

DPO 偏好数据构造

总览

文件 条数 占比 来源 方法
一层 jddc_dpo_pairwise.jsonl 311 20% 测试集 0-259 SFT vs Base 中 SFT 输的 + DS V4 改写 chosen
二层 jddc_dpo_layer2_clean.jsonl 150 10% 合成 DS V4 按失败模式 few-shot 生成
三层 jddc_dpo_layer3.jsonl 127 8% 测试集 260-359 SFT 自对弈(修复 AB 映射 + 去重后重跑)
四层 jddc_dpo_layer4.jsonl 354 23% eval 集 0-299 SFT 自对弈(修复 AB 映射 + 去重)
五层 jddc_dpo_layer5.jsonl 579 38% eval 集 300-635 SFT vs Base 中 SFT 输的 + DS V4 改写 chosen
合计 - 1,521 100% - -

五层详情

属性 一层 二层 三层 四层 五层
数据来源 SFT vs Base 负样本 LLM 合成 SFT 自对弈 SFT 自对弈 SFT vs Base 负样本
占用数据 测试集 0-259 测试集 260-359 eval 0-299 eval 300-635
chosen 来源 DS V4 改写 DS V4 生成 SFT 生成(DS 挑优) SFT 生成(DS 挑优) DS V4 改写
rejected 来源 SFT 生成 DS V4 生成 SFT 生成(DS 挑劣) SFT 生成(DS 挑劣) SFT 生成

⚠ 四层修复了 AB 映射 bug(gen1/gen2 未真正随机化),从 718 降至 354;五层已按修复后的流程生成 ⚠ eval 集(636 条)在 SFT 阶段仅做 validation 监控,未参与梯度更新,用作 DPO 数据无 SFT 泄露风险

  • 最终 DPO 评测使用 360-641(282 条干净数据),所有 DPO 训练数据均不与其重叠

DPO 训练结果

基于五层全量 1,521 对数据。

三组实验配置

参数 实验一 实验二 实验三
基座模型 SFT checkpoint SFT on chosen 数据 SFT on chosen 数据
chosen 数据源 - 层1+2+5(DS 生成,944 对) 同实验二
lr 1e-5 1e-5 1.5e-5
beta 0.05 0.05 0.1
Epochs 2 2 2
Steps 342 342 342

三组结果对比

指标 实验一(纯 DPO) 实验二(SFT→DPO) 实验三(SFT→DPO+) 结论
Final Eval Loss 0.509 0.572 0.424 实验三最优
Reward Margin 0.486 0.301 1.032 实验三 2.1x
Reward Accuracy 76.3% 82.1% 82.1% 实验二/三平齐
Entropy 1.77 1.28 1.17 实验三最"自信"
Logps/chosen -40.75 -33.99 -35.99 实验二最短
Logps/rejected -30.50 -34.41 -40.70 实验三推最远
偏好方向 ❌ 偏好 rejected ✅ 偏好 chosen ✅✅ 强偏好 chosen

关键发现

  • 实验一仍是方向性错误:Logps/rejected(-30.50)> Logps/chosen(-40.75),模型觉得 rejected 回复更"顺口"。DPO 拉出了 0.486 margin,但推错了方向——是在错误的起点上硬拉
  • 实验二"先 SFT 后 DPO"修复了方向:SFT on chosen 数据让模型学到"什么是好回复",DPO 时 chosen 天然 logp 更高。rejected 被打到 -34.41(down 4 个点),chosen 稳在 -33.99
  • 实验三 lr↑ + beta↑ 释放了 DPO 的潜力:beta 从 0.05→0.1 告诉模型"更重视偏好",lr 从 1e-5→1.5e-5 加速收敛。rejected 被推到 -40.70(down 6 个点),chosen 仅从 -33.99 降到 -35.99,margin 从 0.30 跳到 1.03
  • 三组实验构成完整的消融链:纯 DPO 不行 → 两阶段范式修复方向 → 调参放大效果。reward margin 从 0.49→0.30→1.03 的走势说明:范式比调参重要,但正确范式 + 合理调参 = 效果最大化

DPO 最终盲测

使用干净测试集 360-641(282 条,从未被任何 DPO 层使用)。

指标 纯 SFT(V1) 二轮 SFT(DS chosen) DPO 最终 变化
SFT/DPO 胜率 64.0% 77.9% 88.3% +24.3pp
Base 胜率 32.9% 19.1% 10.7% -22.2pp
平局 41 39 14 更果断
SFT/DPO 长度 25.0 字 23.0 字 38.8 字 +55%
Base 长度 175.5 字 175.5 字 175.5 字 不变
长度缩减 85.7% 86.9% 77.9% -

三阶段增量分析

  • SFT → 二轮 SFT:+13.9pp(64.0%→77.9%)。仅用 DS 生成的 944 条 chosen 回复(层1+2+5)做第二轮 SFT,模型学到了"准确完整地回答"而非"极度简短",长度不变但质量显著提升
  • 二轮 SFT → DPO:+10.4pp(77.9%→88.3%)。DPO 在校准偏好,长度从 23→39 字是因为模型学会了 DS chosen 的"简洁但不敷衍"风格,同时 rejected 被打压
  • 三阶段消融验证了"先 SFT 后 DPO"范式:SFT 决定回答的内容质量(+13.9pp),DPO 决定偏好方向(+10.4pp),两者互补而非替代
  • 回复长度从 25.0→38.8 字是有意为之:DS 生成的 chosen 回复(约 30-40 字)比纯 SFT(19 字中位)更完整,DPO 学到的是"简洁但不敷衍"。相比 Base 的 175 字,77.9% 的长度缩减仍保留了显著的简洁性
  • 平局从 41→14:模型偏好处判更清晰,回答质量更稳定

SFT vs DPO 内部对决

将二轮 SFT 模型(77.9% vs Base)与最终 DPO 模型(88.3% vs Base)直接对比,看 DPO 相较 SFT 的净增量。

指标 二轮 SFT DPO 最终
胜率 39.9% (524) 56.0% (735)
平局 - 54
平均长度 22.5 字 39.8 字

DPO 赢在哪里

场景 SFT 表现 DPO 表现
用户问题模糊 "建议申请售后"(17 字,直接跳结论) "请提供具体问题,我帮您核实"(29 字,先了解再处理)
问题未解决时结束 "不客气哈 有需要随时找我"(过于随意) "我会继续跟进,有结果第一时间告知"(给出承诺和预期)
退款到哪 绕弯核查询流程(77 字,没回答问题) "退到原支付账户"(37 字,直接给答案)

SFT 赢在哪里

场景 SFT 表现 DPO 表现
简单确认 "亲,可以退货哦"(准确直接) 加了无关的"无法使用"前提,显得机械
确认用户意图 "退2个,对吗?"(先确认再执行) "是可以的,只退两个"(跳过确认,风险更高)
简单操作 直接记录订单号和上门信息 额外加了收费说明,多余信息稀释核心操作

总结

  • DPO 净 +16.1pp,优势在需要主动性、核实、跟进的中等复杂度场景
  • SFT 在简单确认场景仍有优势——极简回复比 DPO 的模板化表达更自然
  • DPO 回复更长(39.8 vs 22.5),但长度增加来自信息增量而非废话:核实用户问题、给出具体承诺、提供准确信息
  • 这说明 DPO 不是简单偏长回复,而是在"短但敷衍"和"长且完整"之间做了有信息增量的选择——SFT 的长度是"刚好够"(22.5 字),DPO 的长度是"刚好对"(39.8 字)

踩坑记录

坑 1:LoRA 权重原地覆盖 → Base/SFT 实际是同一模型

PeftModel.from_pretrained(base_model, checkpoint) 会原地修改 base_model。评测时以为加载了两个模型,实则两者都带 LoRA。

  • 现象:Base 胜率异常高达 69%
  • 修复:通过 model.disable_adapter_layers() / enable_adapter_layers() 动态开关 LoRA
  • 修复后:SFT 胜率 68.4%,回复长度从 188 字降至 30 字

坑 2:LLM-as-Judge 全量评分无效

初始评测用逐维度打分模式,让 judge 同时看到参考回复和两个模型的输出。结果 SFT 在各维度均略低于 Base——但实际仍是同一模型的输出在比。 改为 pair-wise 盲测 + 明确「简洁直接」标准后结果回归合理,也证明了绝对评分不如相对对比可靠。

坑 3:RAG 知识注入 → 去留两难

注入的假知识与对话无关(68% 为无用物流站点),但一键删除会连带削掉退货场景(因 [金额x] 变量集中在该场景)。最终保留 RAG 数据,在 SFT 中接受 13.3% 的轻度噪声。

坑 4:随机变量替换 → 数据分布偏移

[姓名x] [金额x] [地址x] 在不同运行中产生不同随机值。验证发现 8168 vs 8158 的 10 条差异来自去 RAG 后 token 变短被过滤,需精确对齐后按行匹配置信分数。同一次 pipeline 产出的数据天然行对齐,不同运行则无法保证。

坑 5:同一模型自比 → 暴露 judge 的位置偏差

两个回复完全相同(都是同一 SFT 模型输出),但 judge 打出 69%:31%。理论上 50:50,实际严重偏向前一个。LLM-as-judge 的 A/B 对比存在位置偏见——模型倾向选靠前的回答。 评测时必须随机打乱 AB 顺序,且不能仅凭胜率绝对值做结论。

坑 6:评测结果反过来验证了之前的所有结论

修完 LoRA 覆盖 bug 后重新跑测,SFT 68.4% vs Base 31.6%。之前基于 bug 数据得出的所有结论("基座太强""SFT 无意义""数据拖后腿")全部作废。教训:评测框架本身的正确性必须先验证,否则后续分析全是在给 bug 编理由。


vLLM 推理加速对比

方案 s/it 260 轮预估 技术差异
逐条 model.generate() 60.02s 4h15min Transformers 原生,无 KV cache 复用
vLLM 单条推理 12.30s 52min PagedAttention + KV cache 优化
  • 加速比:4.9x,评测时间从 4 小时压缩到 1 小时以内
  • 加速来自 vLLM 的 PagedAttention 显存管理和跨轮 KV cache 预取(enable_prefix_caching=True),而非连续批处理
  • 对话评测天然串行——为模拟真实部署(模型自己的回复成为下一轮上下文),无法用测试集固定上下文并行推理。这是真实性 vs 速度的权衡,vLLM 将此权衡下串行的代价降到 4.9x

材料汇总

简历项目介绍

电商客服多轮对话模型微调与偏好对齐 针对京东客服退换货、物流查询等高频场景,基于 Qwen3-4B-Instruct 完成 SFT+DPO 全流程。清洗并构建 5,103 条多轮客服指令数据,使用 QLoRA(rank=16)完成 SFT,对 rank、target_modules、量化方案展开四组消融实验。创新性地构建五层、1,521 组多维度偏好数据(真实负样本 + LLM 合成 + 自对弈),经两阶段"先 SFT 后 DPO"范式训练。最终在 pair-wise 盲测中 DPO 模型以 88.3% 胜率显著优于基座(barely 10.7%),较大幅提升 SFT baseline(64.0%)。沉淀 LoRA 权重覆盖、LLM-as-Judge 位置偏见、自对弈信号强度等多项工程踩坑经验。

面试叙事要点

环节 讲什么 关键数字
开头 京东客服场景,退换货/物流/商品咨询 5,103 训练 / 636 验证 / 642 测试
SFT QLoRA rank=16,四组消融 QLoRA > 全精度 LoRA,rank=16 是容量底线
DPO 数据 五层 1,521 对,三层互补 真实负样本 54% + 合成 10% + 自对弈 36%
DPO 训练 三组消融:纯 DPO → SFT+DPO → 调参 reward margin 0.49→0.30→1.03
最终结果 pair-wise 盲测 DPO 88.3% vs Base SFT 64.0% → DPO 88.3%(+24.3pp)
收尾 踩坑经验 LoRA 覆盖、AB 映射、judge 位置偏见

面试追问预答

Q: LoRA rank 怎么选的? 跑过四组消融:V3 rank=8 eval loss 1.32 vs V1 rank=16 eval loss 1.24,rank 本身构成容量瓶颈。rank=16 在此任务上是最优的性价比平衡点。

Q: QLoRA 为什么比全精度 LoRA 效果好? 四组实验中 V1(QLoRA 4-bit)胜率 68.4%,V4(全精度 LoRA)仅 64.8%。4-bit 量化噪声在小规模客服数据上起到隐性正则化,省 60% 显存还提 3.6pp 效果。

Q: DPO 数据怎么构建的? 1,521 对来自五个独立层:一层/五层利用 SFT vs Base 评测的负样本(DS 改写 chosen),二层用 DS V4 按失败模式 few-shot 合成,三/四层利用 SFT 自对弈(DS 思考模式盲测评优)。三层自对弈发现 55% 的初始偏好来自语气词差异,引入"实质性差异"标准后重新筛选,只用质量对。

Q: 为什么选 DPO 不选 PPO? 资源角度:DPO 不需要额外构建 reward model,1,521 对偏好数据在 QLoRA 上直接收敛。PPO 需要在线采样 + 训练价值函数,4B 模型单卡部署成本过高。

Q: beta 参数如何调? 三组 DPO 消融实验:SFT→DPO(beta=0.05)reward margin 0.30;beta 提高到 0.1 + lr 提高到 1.5e-5 后 reward margin 跳至 1.03。chosen/rejected 区分度增强明显,最终盲测选择 beta=0.1 + lr=1.5e-5 为最佳。

Q: 项目中最大的坑? 评测 LoRA 覆盖 bug——PeftModel.from_pretrained 原地修改 base_model,导致 Base 和 SFT 实际是同一模型。排查后通过 disable/enable_adapter_layers 修复。教训:评测框架的正确性须先自行验证,否则后续分析全在给 bug 编理由。