瞿才应
← 回到主页的项目列表

个人项目 · 执行反馈

SQL 数据分析 Agent 后训练

SQL 能跑,答案却可能不对。我给 Qwen3-4B 做多轮数据分析 Agent 的后训练,让模型自己查表、试跑查询,再提交 SQL。在 BIRD 这个公开的问句转 SQL 基准上,用执行结果筛训练轨迹、筛老师、筛训练题,每轮先写好比较规则再上 H20 跑。

  • Qwen3-4B
  • LoRA
  • 拒绝采样微调
  • DPO
  • RLOO
  • FSDP2
  • vLLM
resultsBIRD · Qwen3-4B
官方执行准确率 EX
57.9%
原版 49.5%,同轮单次作答
整条流水线对原版
+8.4点
官方 EX,95% 区间 [+6.2, +10.7]
边界题 RL 对离线起点
+2.6点
项目判分,95% 区间 [+0.7, +4.7]
RL 对起点的 pass@64
−6.2点
采 64 次至少对一次的概率;采一次对起点反而高 3.7 点
01

问题是执行结果能教会模型什么

查出了一个看着合理的数,不代表答对了。漏一个过滤条件、多交一列、数错统计对象,SQL 都可能正常执行。模型看到的查询结果,也不会主动告诉它这些口径错在哪里。

对错可以自动核对

把模型 SQL 和标准 SQL 分别执行,比较两边结果,就能给训练轨迹判对错。不用另训奖励模型,也不用逐题人工给奖励。

对错信号不解释原因

奖励只说最后有没有做对,没告诉模型错的是过滤、关联还是分母。我想看它能把现有能力做稳多少,又能不能学会原来做不对的题。

这里的 Agent 是能自己调用工具的模型,后训练是在已有指令模型上继续训练。我没有把任务改成直接生成一条 SQL,而是保留查表、看结果、改错、再交答案的过程。

02

怎么做,先固定考场和比较规则

模型拿到问题、外部知识提示 evidence 和建表语句。想看实际取值,就自己查。每步输出一个 JSON 动作,最多 4 步,提交也算一步;评测时全程看不到标准 SQL 和标准结果。

schema重新看表结构
query试跑只读查询,拿到结果或报错
finish提交最终 SQL;还有步数时可按执行报错修改

SQL 在独立进程的只读 SQLite 沙箱里执行,只允许单条查询,函数按白名单放行。超时、结果超限、步数用完没提交都算错,不拿截断的结果判对。模型不用每个动作都走一遍,也可以多次查询。

数据库整库切开

训练和选型

训练题池 6682 题、49 个库。开发集 dev 是 615 题、6 个库,用来选训练家族、检查老师门槛。

验证和最终测试

validation 是 1612 题、14 个库。BIRD 官方 dev 留作本项目的 test,预检后是 1501 题、11 个库。同一个库只在一个集合里。

官方 dev 原有 1534 题,参考 SQL 有 33 题在沙箱里跑不出完整结果,统一在评测前排除。19 题结果超过一万行,12 题用了被禁止的取当前时间函数,2 题超时;这不是说标准答案写错了。把排除题全部记错的保守结果也同时报告。

训练留下哪几步

先学做对的整条轨迹

轨迹就是一次从读题到提交的完整对话。拒绝采样微调让原版自己做题,只留下执行验证正确的 3435 条,含 568 条真实报错后修好的轨迹。微调时学有效助手动作,报错的动作和后来改掉的错误提交不学。

让正确轨迹更容易被选中

偏好优化 DPO 把同一题的一条正确轨迹和一条错误轨迹配对,训练模型更偏向正确的那条,共 547 对。监督微调(SFT)只照正确示范学,DPO 会同时用上正确和错误轨迹。

让老师补学生不会的题

蒸馏是让学生学大模型做对的过程。235B 老师先在 dev 上比原版高至少 5 个点、区间下界也大于零,才生成数据。它补出 832 道学生原采样全错的新题,再接 DPO,得到离线起点 PTR。

再用当前模型自己采样

在线强化学习 RL 按执行结果奖惩当前模型的轨迹,用同题其他轨迹的平均奖励作基线,这个算法叫 RLOO。LoRA 只更新少量附加参数,每步把新参数交给采样引擎 vLLM;也做了 FSDP2 多卡分片的全参数训练和在线蒸馏对照。

主比较用比官方执行准确率 EX 更严的项目判分。EX 比结果集合,忽略重复行和顺序;项目判分要求行数、列数、重复行都对上,标准 SQL 要求排序时顺序也要对。两种分数分别报告,不能混加。

主要训练条件各跑三个随机种子,改变初始化和数据顺序。区间按数据库整库重抽,种子也配对重抽,这叫聚类 bootstrap。同库的题不是独立样本,不能把它们拆散后算出过窄的区间。

03

讲解片

explainer网页原片 · 10:32

在浏览器里按原分辨率播放,带字幕。也可以在 B 站看。

04

结果,成立和没成立的都留下

每轮先写定要比什么,不看完结果再换指标或挑种子。下面和主页用同一组主比较,pass@k 是训练后的独立采样研究,不算一个新训练版本。

preregistered主比较与判定
各轮预先写定的主比较与判定
版本主比较判定
v0.3修复式 SFT 对比普通 SFT反向
v0.5拒绝采样微调对比原始模型成立
v0.732B 老师轨迹对比自采不成立
v0.8混入 235B 老师轨迹对比自采成立
v0.9在线 RL 对比起点未过线
v0.10全参数 RL 对比 LoRA RL反向
v0.11边界题 RL 对比起点成立
v0.12在线蒸馏对比离线蒸馏不成立
pass@k边界题 RL 对比起点,每题采 64 次收窄了能力边界

v0.3、v0.5 先写定了比较,还没有统一的判定线,表中按区间概括;v0.5 的主比较在完整 validation 的 1612 题上。从 v0.7 起,成立要求 test 差值均值和 95% 下界都大于零,validation 均值也大于零,主比较用项目判分。

官方 EX 从 49.5% 到 57.9%,大头来自离线训练

v0.11 同一轮、同一套协议复评,1501 题。贪心解码每次选概率最高的下一个文本片段,单次作答,不从多次里挑。训练模型取三个种子的均值;最终 RLb 用事先登记的第 200 步。

原版 B离线起点 PTR边界题 RLb
看数据表
阶段官方 EX项目判分EX 答对题数
原版 B49.5%45.3%743
拒绝采样、老师补题、DPO 后的 PTR55.2%51.9%845 / 819 / 822
PTR 再做边界题 RL,RLb@20057.9%54.5%873 / 857 / 878

逐种子题数按 42、43、44 排列。EX 和项目判分各按自己的规则算,不是同一个指标。

按项目判分拆开,离线起点比原版高约 6.6 个点,边界题 RL 在它上面再加 2.6 个点,最后一步过了预注册检验。按全部 1534 题、排除题一律算错,官方 EX 是 48.4% 到 56.7%。

全参数分布式 RL 反而更差

FSDP2 全参数版对 LoRA 版低 4.2 个点,95% 区间 [−7.1, −1.2]。全参数只试了一个学习率,不能把负结果全归到全参数更新本身。

在线蒸馏没证明更好

学生自己采样,再让老师逐 token(文本片段)打分的在线蒸馏,比离线补题版 TR 低 0.8 个点,95% 区间 [−3.9, +2.6]。TR 是接 DPO 之前的版本,这个区间跨零,既不能说更好,也不能说等效。

05

为什么要筛轨迹、筛老师、筛训练题

先看模型还查不查,再看分数

只监督最后提交,先查数据的习惯消失了

每行是用过 query 的题占比。早期两种 SFT 列种子 42;各阶段的题集和范围在表中分开列,不把它当同一批题的消融。

原版、早期 SFT多轮拒绝采样微调(离线一支)边界题 RLb
看数据表
模型评测范围用过 query 的题占比
原版 B早期验证子集256 / 256100%
普通 SFT,C 种子 42同一验证子集0 / 2560%
修复式 SFT,D 种子 42同一验证子集0 / 2560%
多轮拒绝采样微调,v0.5完整 validation1612 / 1612100%
最终 RLb,种子 42 / 43 / 44完整 test,各种子分别算均为 1501 / 1501均为 100%

早期普通 SFT 和修复式 SFT 都只教最后怎么提交。修复版的 512 对训练数据里,真实修复只有 17 对,约 97% 是按规则造错。种子 42 在 256 道题里有 254 道原样重复提交同一条 SQL;修复版比普通版低 17 到 20 个点。

我的理解是,只教最终提交、又大量用规则造错,让模型学成了照抄 SQL,没有学到先查再交的过程。这两点各自的作用没单独检验。后续改为学真实、做对的多轮轨迹,查询恢复了,只算间接验证。

最终模型每题都查过数据,不代表每题都成功交卷,仍有没提交的题。这个习惯原版就有,不能说是训练从零教会的。

老师的参数多,不保证适合这个考场

32B 老师在同一套协议下没有表现出优势,等量数据蒸馏的主比较没成立。所以换 235B 时,我先要求老师在 dev 上比原版强,过门槛才补学生做不对的题。最后选哪个离线家族也只看 dev,不拿 test 选。

在线奖励要有对有错,才有训练信号

RLOO 每步取 16 题,每题采 8 条完整对话。最终结果对上记 1,否则记 0;每条奖励减去同组另外 7 条的平均,全对或全错时优势都是零。边界题是起点采 8 次、做对 1 到 7 次的训练题,只筛训练题池,不看评测题。

同样前 200 步,边界题让训练信号更密

有信号的组是同题轨迹里有对有错的组,标注单位为 %。两种训练只换了题池,其他设置保持一致。

普通在线 RL,v0.9边界题 RLb,v0.11
看数据表
训练方式种子 42种子 43种子 44统计窗口
普通在线 RL15.6%16.2%19.2%前 200 步
边界题 RLb48.2%45.3%49.0%前 200 步

信号从约 16% 到 19% 提到 45% 到 49%,但没有证明边界题比普通 RL 的结果更好。两者只差 0.9 个点,95% 区间 [−1.3, +2.7]。普通 RL 在原本那轮检验中没过线,后续同一批模型重评过线也只算探索,不改判。

剩下的大头还是结果值不对

最终系统还错在哪

v0.11 同轮 test,按项目判分自动分类。最终系统三个种子的题数均值取整;表里保留原版和离线起点作对照。

看数据表
错因原版 B离线 PTR最终 RLb
行列数都对,值不对309320312
行数不对166165172
返回空结果8810298
列数不对1659883
最终 SQL 执行失败27511
没有提交64326
超出判分上限201

从原版 B 到最终 RLb,列数不对、没提交和执行失败的净计数都少了,值不对的总数没少。这是各类错题的净计数,不是说原来的语义错题一道都没修好;错因分析是结果出来后补算的描述,不参与主比较判定。

一道仍然错的真实题

题目问“有三键的分子有多少个”。模型用 COUNT(*) 数了符合条件的键,得到 6;标准 SQL 按 molecule_id 去重,答案是 4。有的分子不止一个三键,查询能跑、数也像真的,但统计对象错了。离线起点和最终模型的三个种子都没修掉这道题。

06

一次更准,不代表能做对的范围更大

训练结束后,我另写了 pass@k 研究的协议,只采样,不再训练。pass@k 是采 k 次至少做对一次的概率;它需要知道标准答案才能算,不是部署时可以直接拿到的准确率。

采样次数变多,离线起点反超在线 RL

同一套温度 1、不截断采样,每题 64 段完整对话,沿用项目判分。原版只有一个模型,PTR 和 RLb 各取三个种子的均值;横轴每翻倍一档。

原版 B离线起点 PTR边界题 RLb
看数据表
pass@kk=1k=2k=4k=8k=16k=32k=64
原版 B44.1%48.5%51.6%54.1%56.4%58.7%61.0%
离线起点 PTR50.8%57.3%62.5%66.7%70.1%73.0%75.4%
边界题 RLb54.5%58.0%60.8%63.2%65.4%67.4%69.3%
RLb − PTR,点+3.7+0.8−1.6−3.4−4.7−5.6−6.2

差值在取整前计算,不能直接拿表中已经保留一位小数的百分数相减。温度 1 的 pass@1 和前面的单次贪心分数也不能混用。

只看 RL 这一步,采一次比 PTR 高 3.7 个点,95% 区间 [+1.8, +5.5];采 64 次反而低 6.2 个点,区间 [−7.7, −4.5]。两个区间都不跨零,三个种子都在采 2 到 4 次之间被起点反超,按事先写定的规则判为“这个预算下收窄了能力边界”。

概率集中到了少数写法

每题 64 条里不同最终 SQL 的平均条数,PTR 是 20.4,RLb 是 9.9。这里只统一了空白、大小写等后按字符串去重,不是语义多样性,也不是判定依据。

离线阶段把范围扩出来了

采 64 次,PTR 比原版高 14.5 个点。加上在线 RL 后,整条流水线仍比原版高 8.3 个点。收窄说的是 RL 对它的离线起点,不是整条流水线对原版。

也不是没学会任何新题。把三个种子的对话合起来,只有 RLb 至少做对过一次的有 25 题,只有 PTR 做对过的有 110 题。事后盲标抽查发现两边都有碰巧算对的 SQL,标注也有分歧,不把这些题数直接当语义新能力,不用抽查改正式判定。

“收窄”只限温度 1、每题 64 次的预算。没采到正确 SQL,不代表它彻底忘了这道题;更大预算下会怎样,这个实验没有回答。

07

能说什么,不能说什么

能说

  • 在同一套模型、输入和推理预算下,执行验证的离线训练有稳定收益,最终系统官方 EX 高 8.4 个点。
  • 边界题 RL 比离线起点更容易一次答对,按项目判分的主比较成立,但没有证明比普通 RL 更好。
  • 在这套采样设置和预算下,RL 的一次答对率更高,至少答对一次的题范围却比离线起点小。

不能说

  • 不能拿绝对分数比排行榜。这里不给列描述和代表值,只有固定步数和单次贪心,不是同一套输入与预算。
  • 按数据库隔离只防我这次后训练泄漏,不能保证基座预训练没见过 BIRD。
  • test 被多轮重复使用,已经不是一次性的留出集。跨版本没做多重比较校正,结果也不推广到别的基座和数据集。
  • 行为恢复、错因净变化和 SQL 字符串集中都是描述,不能各自当成机制的因果证明。
  • 没证明更好不等于等效,也不能把实验准确率当线上业务效果。真实业务还要补口径文档、权限、方言和人工确认的评测集。

继续做,我会先改这些

  • 刷新边界题。训练中重新采样,换掉已经稳定会做的题,比较时把预采样耗时一起算进去。
  • 按错因补口径数据。让模型提交前核对统计对象、分母、过滤条件,而不是继续只加在线 RL 步数。
  • 先把业务验收补齐。接真实分析任务前,用人工确认过的结果建评测集,再按固定规则比较。
08

术语速查

轨迹
一次从读题到提交的完整多轮对话,包含模型动作、工具结果和报错。
项目判分
比官方 EX 更严格的结果比较。重复行和行列数要对上,标准 SQL 要求排序时顺序也要一致。
拒绝采样微调
让模型多次做题,用执行结果筛掉错的,拿做对的有效动作继续训练。
DPO
同题正确和错误轨迹配成偏好对,让模型相对更偏向正确的那条。
LoRA
冻结原模型,训练少量附加参数。这里用于离线微调和在线 RL,更新后采样端直接加载。
FSDP2
把参数、梯度和优化器状态分到多张卡,训练时再按需拼起来,用来做全参数分布式对照。
RLOO
用同题其他轨迹的平均奖励作基线,奖励高于基线的加强,低于基线的压低,不另训价值网络。
聚类 bootstrap
按数据库整库有放回地重抽,配对种子也重抽,用差值分布估区间,保留同库题之间的相关性。
pass@k
采 k 次至少做对一次的概率。这里由每题 64 条对话估计,需要标准答案判对错,部署时不知道该挑哪一条。
→

其他项目