
训练一个 4B 模型,产出比 Postgres 快 81% 的查询计划
作者:Rohan Bansal(Crosby 1 号工程师;本文是他在业余时间做的实验)
2015 年,Leis 等人问过一个问题:查询优化器到底有多好?十年后他们又问了一次,结论没变——还是差得远。
这件事让我意外。Postgres 明明掌握着表里数据的一切信息,选执行计划能有多难?答案是:极其难。优化器要做的一件核心工作——join 顺序——早被证明是 NP-hard。
但真正让这个实验成立的是另一个观察:**验证一个计划好不好,比选出一个好计划容易得多**。好优化器产出跑得快的计划,差优化器产出慢的。而语言模型恰好擅长「输出容易验证」的任务。既然这里只有一个优化轴——查询的执行时间——问题就干净地化简成:强化那些能让模型产出更快计划的动作。
于是有了这个实验:**能不能通过监督微调(SFT)加 agentic 强化学习(RL),把一个小的开源权重模型训成能产出比 Postgres 默认更好的查询计划?**
答案是可以。几个亮点:
在 113 条 join 密集的查询上,**总延迟降低 44.7%**,几何平均加速 1.81 倍。注意起点:这个 4B 模型最初连 113 条里 99 条的计划都产不出来。
搭了一套 Postgres 测量装置,把并发容器之间 Linux page cache 争用的噪声压到最低。
为「本质上带噪声的环境」设计了一个自定义的 GRPO 变体来给 rollout 打分。
把 RL 拆到两台机器上:vLLM 与训练器跑在租来的 2×H100 节点,四个 Postgres 容器跑在他自己的桌子上。
用大约五百条 GPT-6 Astra 的 agent 轨迹做 off-policy 蒸馏。全部花费 1200 美元。
模型与 harness
他选了家里那台双 3090(起名 FLOPper)能舒服训推的 4B 模型。当时 Qwen 3.8 系列发布,但没有 4B 版本,于是他用了德国小团队 Empero 的蒸馏版:用 Qwen 3.8 的 2.4T 模型当老师,把能力蒸进 Qwen 3.5 4B。这个蒸馏版并不是全面更好,它在 MMLU 这类通用知识上更好、在 GSM8K 这类多步数学上略差。
模型定了之后,他写了一个轻量 agent harness `qo-agent` 来组织「提示的生成」。它拿到六个工具:列出关系定义与索引(inspect_relation)、取列的规划器统计(get_column_stats)、看默认计划的估算或候选计划的存储计划(get_plan)、校验并执行一个候选计划以拿到计时(evaluate_candidate)、直接把默认计划当候选并结束搜索(keep_default)、提交某个候选并结束(finish)。
为了用上结构化输出,agent 被要求产出 `PlanAction` 这种 JSON 对象;`evaluate_candidate` 会把它们编译成 Postgres hint,前置到原查询上再执行。
先验证这个问题能不能被前沿模型解
跑未训练的 4B 之前,他先用 10 条 JOB 查询验证了这件事本身可解:把 Astra 和 Qwen 3.8 2.4T 也放进同一个 harness。Astra 给 5 个候选时,几何平均加速 2.54 倍、总负载加速 2.12 倍,任务覆盖 10/10,没有回归。如果前沿模型都改不动 Postgres 的默认计划,那就没理由指望 4B 能。
噪声:先给 Postgres 消音
整篇论文的论点、以及训练过程本身,都依赖「一种跑法是否比默认更快」的测量。而同一个查询在 Postgres 上连跑 20 次,耗时不会一样——数据页会被 Postgres 的 buffer 和操作系统的文件缓存两级缓存吃掉。
所以他先量化噪声:用固定 CPU 核数与内存切片的 Docker 容器跑 Postgres,先设 4 个容器(更少会让后续训练慢到不可行),测出页缓存争用对计时的影响,再据此设计测量与训练协议。
奖励设计:以及它怎么把模型教怂了
最初的奖励很直白:加速比 = 三次默认计划耗时的中位数 ÷ 三次候选计划耗时的中位数。无效候选扣 0.1(自然对数尺度);候选的计划指纹与默认完全相同时再扣 0.05;如果整条轨迹一个有效候选都没有,直接扣 3 分。
跑了几轮之后,模型被那个 −3 吓住了:它变得极度厌恶产出无效计划,于是**一遍又一遍地交回 Postgres 的默认计划**,宁可吃那 0.05 的小罚。GRPO 还放大了这个问题——标准 GRPO 把同一 query 的多个 rollout 奖励转成相对优势(减去组均值、再除以组标准差),于是「大家都交默认计划」这种塌缩行为被进一步强化。
必须重新设计 per-rollout 奖励与相对优势,才能让模型愿意冒险去找更快的计划。
指标口径
几何平均加速:每条查询等权,衡量的是「模型是否真的学到东西」——大于 1 倍意味着平均每条查询都更快了。
总负载加速:把整个查询集当成一个批次,总默认耗时 ÷ 总候选耗时,衡量实用性。但它可能被某一条撞大运的计划主导,其余全是退化的——所以两个指标要一起看。
模型最后学到了什么
337 次搜索里有 295 次,它先去看了关系定义、列统计或默认计划,再提交候选。339 次里有 235 次用满了五次候选机会。
在 `job-01d` 这条拿到 90 倍加速的查询上,它的推理是:「mi-index 的顺序扫描正在 575k 上做一个有损过滤,耗时 11ms。看起来用 bitmap 会快得多。」然后它真的强制了 bitmap 扫描。
偏好也很集中:1,347 个动作里,扫描类 hint 出现 1,141 次、`Leading` 树 917 次、`Parallel` 572 次,而 `Rows` 修正只用了 146 次。它经常强制 nested loop 而不是 hash join,偏好 index scan 而不是 bitmap 或顺序扫描,常开 `enable_sort=off` 和 `random_page_cost=1.1`。
真正带来收益的是三种模式:用 `Leading` 重写 join 顺序;不改 join 顺序、只修一次扫描;以及用 `Parallel`。
成本
除了家里那张卡满负荷时的电费(约 9 美元一天),这个项目本来可以是免费的。他不耐烦等结果,租了 Lambda 的 2×H100 SXM 节点跑约 95 小时约 800 美元,另外约 400 美元 OpenAI API 费用用来生成 Astra 的轨迹示范。
总计 1200 美元。
结论
通过 off-policy 蒸馏加强化学习,一个 4B 小模型从「看不懂自己被包在什么 harness 里」,做到了 join 密集查询负载上 1.81 倍的几何平均加速、总延迟下降 44.7%。
他说:在 5T 参数的巨兽面前,人们很容易把模型看小,但不该这样。前沿智能极其强大——他从 Astra 轨迹里蒸馏这件事本身就是证据,大模型不会消失。但他相信这个实验证明了一个很多公司正在意识到的假设:**数据是自己的,搭一个 RL 环境、花小钱在细分领域训练和推理开源模型,并没有那么遥远**。小模型会越来越多地被用在这里,因为训练和推理都更快更便宜。
他自己搭基础设施与训练栈是为了学习,但现成的方案正在快速变多。他想试的下一步包括:结构化 hint 能走多远、以及在这个环境里换更小的模型会怎样。
原文:https://rohanbansal.com/qorl
#查询优化 #强化学习 #小模型