「比 Postgres 快 81% 的查询计划」是标题,「延迟降低 44.7%」「1.81x 加速」是正文。三个数字不是互相打架,但也不是同一个数字:1.81x 与 44.7% 是同一个加速比的两种基数写法,81% 只能反推为同一个 1.8054× 的「倍率减一」写法,见下——不是耗时下降。

我按仓库里的逐查询原始结果(results-per-query.csv,113 行)复算了全部头条数字,逐格吻合,也对上了仓库文档里作者本人的一句更正。

口径核账

数字量的是什么(基线都是 PostgreSQL 默认计划)我的复算级别
1.81x池化几何平均加速比1.8054×,68 wins / 0 regressionsA
44.7%1 − Σ选中中位数 / Σ默认中位数1 − 31.756 s / 57.398 s = 44.67%(省 25.642 s)A
81%只出现在标题、文末引用块与 HN 标题;正文全文零次出现、未定义唯一自洽读法是「倍率 − 1」:1.8054 − 1 = 80.54%,即吞吐写法B
1.40x / 1.44x模型自己的选择 / 轨迹内规则1.4048×(119/7)、1.4355×(129/1)A

换算自洽性:1 − 1/1.8054 = 44.61%,与逐查询求和的 44.67% 同源;反过来,把 81% 按延迟口径读,需要 5.26× 加速(1/(1−0.81)),而发布数据里任何口径都到不了——最高是事后视角上限 1.8089×。仓库文档里作者已经写明(experiments/033-…/results.md,我打开核对过原文):

The 1.805× ratio is not an 80.5% latency reduction. The measured summed-latency reduction is 44.7%, and both measures exclude search cost.

所以这不是虚标。真正的口径问题是基数:读者会把「快 81%」读成「耗时降 81%」,两者差 1.8 倍。至于 81% 这个写法从哪来,我没找到任何推导文本,1.8054 − 1 是我按数值反推的唯一自洽来源(D 级推断,只作解释)。

这个口径差在讨论现场没人提:HN 讨论帖(item 49731285,提交于 2026-09-17 02:50 CST)我通读了一遍,「81%」只出现在标题、顶楼引用和一条「81% 不算什么」的评论里,没有人把它和正文的 44.7% 放在一起问过;作者本人在帖内只回了一条评论,也不谈口径。

顺带纠两个常见误读。一,1.81x 不是模型自己选的,模型自选是 1.40x;1.81x 是一条固定规则在每个查询的 15 个候选里挑出来的。二,这两个头条数字都不含搜索成本:033 评测跑了 1h 43m 51s,是三倍搜索预算的离线重放(作者原话 “it is still an offline replay”)。口径再钉一层:它们都是逐查询执行中位数之和,不含模型推理、目录检查与候选搜索,既不是端到端工作负载基准,也不是把整批查询优化完所需的墙钟时间(仓库原话:“These are sums of individual query medians, excluding model inference, inspection and candidate search. They are not an end-to-end workload benchmark or the wall time needed to optimize the workload.")。

测量装置与适用边界

装置是 4 容器 × 8 GiB × 4 物理核的安静单机,PostgreSQL 18.6 + pg_hint_plan 1.8.1(来自仓库 scripts/imdb/manifest.json;文章通篇没给版本号),最终配置 shared_buffers = 2GB、work_mem = 4MB。

为什么非要把 shared_buffers 从 128 MB 调到 2 GB?这不是调优,是把噪声压下去。文章给了校准表(级别 A:文章一手,我打开原文逐格核对),两次独立校准:

shared_buffers / work_mem平均 no-op 误判率p90 查询
128 MB / 4 MB5.0% / 5.4%13% / 20%
2 GB / 4 MB1.8% / 1.2%1.3% / 0%
128 MB / 32 MB7.0% / 6.6%20% / 23%
2 GB / 32 MB1.7% / 1.3%0% / 0%

「no-op 误判率」指的是:拿一个与默认计划完全相同的候选去测,有百分之几的概率会被 5% 死区判成加速。128 MB 下,查询 job-13b 的 20 次测量分成两簇(14 次 186–204 ms、6 次 227–253 ms),全量枚举下这个 no-op 候选约 40% 的概率被愚弄;换成 2 GB 后它的 CV 从 10.3% 降到 0.9%。work_mem 对噪声没有任何影响,权重全在 shared_buffers 上。

三条硬边界:

  1. 只对反复执行的同一批查询有效(摊销型)。作者自述其目标不是在一次性的查询上、于「时间/效率帕累托前沿」赢过 Postgres,而是「在反复执行的查询上我们也许能赢」。
  2. 只在安静单机 + 8.5 GB 数据集全内存 + 全部只读 SELECT 的条件下测过(HN 顶楼 refibrillator 的原话是「8 GB 数据集、shared_buffers 却只给其中一小部分、测量前预热过」;那是调参前的 128 MB 默认值,最终配置为 2 GB)。作者把 Postgres 从 Lambda 挪回家里,因为租的机器噪声失控。冷缓存、超内存、并发写、异分布负载,原文未覆盖。
  3. RL 相对 SFT 的净贡献本轮没有被隔离。 作者原话:需要一次 matched three-search 的 SFT 评测,"It was not part of 033."

奖励设计的最小反例

初始奖励把「无效候选 −0.1、与默认同指纹 −0.05、整条无有效候选 −3」直接交给普通 GRPO(优势 = 奖励 − 组均值)。下面这组四条 rollout(文章原表,级别 A:我逐格核对)暴露了问题:

hint发生了什么rewardadvantage
Leading((t cn) mc)树被拒,t 与 cn 从不直接连接−3.00−1.43
MergeJoin(t cn)该查询从不连接这两个关系−3.00−1.43
NestLoop(t mc)148 ms vs 118 ms = 0.80×,比默认慢−0.23+1.34
HashJoin(mc cn)118 ms = 1.00×,与默认同指纹−0.05+1.52(强化最多)

组均值 = (−3.00−3.00−0.23−0.05)/4 = −1.57。四条计划没有一条优于默认,reward 全为负,却有一条拿到全组最高的正优势——因为 GRPO 只做组内相对化,不问组内最优是否值得一提。

锚定版把基线换成兄弟均值 b_i = max(0, mean(同组其余有质量分的 rollout 的 q_j)),同一组四条优势变成 −0.10 / −0.10 / −0.18 / −0.02,全部转负。质量函数是 q = soft_0.05(ln(clip(s, 0.1, 10))),其中 soft 是加法式收缩:0.80× → ln0.8 + 0.05 = −0.1731 ≈ −0.18,与原文表格吻合(公式源码在 vendored prime-rl fork 里,我没有抓到,这一步是由原文表格反推,D 级;但反推结果逐格吻合)。与默认同指纹再扣 0.02。

三个容易混淆的点:

  • 那个「5%」是对数带 abs(log(speedup)) ≤ 0.05,即 0.9512×–1.0513×(我验 exp(±0.05))。仓库原话:这是 “a descriptive neutral band, not a per-query significance test”;选择器用的 1.05× 预筛阈值是另一条规则。
  • 锚不是「锚定默认计划」,而是锚兄弟均值,且下限为 0。真正能拿正优势的条件是 q_i > b_i ≥ 0,也就是必须实打实快过死区。
  • 代价:032 那轮 600 updates 里,11,589 条 episode 有 10,936 条拿到非丢弃的锚定信用,其中零优势 2,395 条(21.9%);另有 60 条抽样查询一条 episode 都没送进训练器。

复现成本阶梯

级与做法需要什么卡点
① 用发布权重直接跑评测单张 GPU 跑 vLLM(config.toml:serving_gpu_ids=[0]、max_num_seqs=4、bf16)+ PG 18.6 镜像 + IMDb 夹具 + 113 条 JOB底座路径写死为 /home/rohan/models/qwen-4b-rl-031-step-600;我没在公开树里找到 033 用的 adapter 文件 → 这一级可能直接断
② 用教师轨迹做 off-policy 蒸馏已公开的教师推理轨迹Astra 是真实模型(OpenAI 有官方发布页),但权重与轨迹均未公开;而且 API 只返回推理摘要(作者自述 “reasoning tokens aren’t supplied”),要靠 trace inversion 补救
③ 自采轨迹 / 端到端 RL2×H100 + 家用 4 个 PG 容器经 Tailscale 回连;600 updates ≈ 21 小时测得到才训得动:先把 no-op 误判率压到 1%–2%,否则学的是噪声
④ 验证 44.7% 本身三倍搜索预算(代码未训练,作者说 “There was no additional training.")1h 43m 51s、2543 次模型请求、4,183 次 SQL 执行——两个头条数字都不含这些成本

$1,200 要放回正确的格子:$800 是为「更快拿到训练结果」租的 2×H100 SXM ≈ 95 小时,$400 是生成 Astra 轨迹的 OpenAI API 费。它不含那台家用机 FLOPper 的购置成本:vLLM 推理与测量装置共用它(2×RTX 3090 / 16 物理核 / 64 GB / 2 TB NVMe,4 个 Postgres worker 也跑在上面;作者脚注只提电费 ~$9/day),也不含本人时间(作者是在 Recurse Center 休假期间做的)。所以「$1,200 很便宜」的真实前提是:你已经有一台安静的 16 物理核家用机,额外花 $1,200 只是买速度。

与 Leis 2025 的对照

Leis 等人在 2025 年的 PVLDB 回顾(18(12): 5531–5536)里重申:基数估计误差普遍存在、往往是劣质计划的主因,成本模型与枚举策略相对次要。他们还有一句与本文装置直接咬合——misestimation 造成的性能退化在索引更多时更明显。

这篇文章没有修这个主因,而是绕开它。作者明确放弃做估计器:「这等于检验我们能否造一个更好的基数估计器……我们要跟几十年的基数估计研究硬碰;而且光是推理延迟就远超收益。」模型的动作分布也印证这点:最终评测 1,347 个动作里 scan hint 1,141 次、Leading 树 917 次、Parallel 572 次,而基数修正 Rows 只有 146 次。它学的是「在既有的估计误差之下换一个物理计划」,Leis 文中引的「约 10% 的 JOB 查询因估计误差跑不完」(PostgreSQL 9.4 时代)这类问题被绕过,而不是消除。

这里可以顺手反驳一条热门批评。HN 上有人断言「除主键外没有任何索引、没有额外统计」,这与夹具直接冲突:scripts/imdb/manifest.json 写明 21 张表 / 21 主键 / 23 二级索引 / 44 个索引,scripts/imdb/README.md 说明 load.sql 原样 vendor 了 JOB 的 fkindexes.sql(我打开核对)。那条批评建立在文章里节选的表定义上,不在实际装置上。

泛化边界(均为仓库一手数字):同一权重、同一设置,三次搜索的模型几何平均在 1.3272×–1.4651× 之间浮动,所以单次搜索不足以比较相邻 checkpoint;job-31a 三次搜索全部保留默认,而 032 的单次搜索曾找到 7.70×;去掉 job-01d 后池化从 1.8054× 掉到 1.7434×;三次搜索互相共享,作者写明「不是独立确认运行」;JOB 本身也是被反复使用的 held-out 集,不是全新盲测。

该不该放进你的查询路径

  • 负载是「同一批重分析查询被反复执行、库不换、延迟比吞吐重要」——值得花一周搭测量装置,先量你库里的 no-op 误判率。压不到 1%–2%,后面所有 RL 都在学噪声。
  • 一次性即席查询、或强并发写入的生产库,第二条边界就挡死了:这套结果没有任何证据支持。
  • 想做类似项目,最该抄的不是模型,是那套测量装置与奖励设计。RL 相对 SFT 的净贡献本轮未隔离,别当成「RL 让 4B 学会了优化器」的证据。

证据分级:A = 官方一手且我独立复算/抓取成功(口径表全部复算值、manifest 索引数、四条 rollout 奖励表、校准表、Leis 摘要原句);B = 官方一手未复现(81% 的标题写法、Astra 轨迹数为 440 条而非「半千条」);C = 单源自述($1,200 构成、8.5 GB、4.66B 可训参数);D = 推断(81% = 1.8054 − 1 的反推、soft 为加法式收缩、min_peers=2 的实现语义)。D 级只作解释,不作事实。

参考链接(均为我实测可打开的页面):原文|代码仓库|033 评测报告(含「1.805× 不是 80.5% 延迟下降」那句)|Leis 2025|HN 讨论帖