<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>强化学习 on Kalend's Blog</title><link>https://blog.kalend.top/tags/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0/</link><description>Recent content in 强化学习 on Kalend's Blog</description><generator>Hugo -- gohugo.io</generator><language>zh</language><lastBuildDate>Thu, 17 Sep 2026 08:00:00 +0800</lastBuildDate><atom:link href="https://blog.kalend.top/tags/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0/index.xml" rel="self" type="application/rss+xml"/><item><title>4B小模型RL优化SQL查询比Postgres快81%：一个$1200的实验</title><link>https://blog.kalend.top/2026/09/17/qorl-4b-rl-sql-optimization.html/</link><pubDate>Thu, 17 Sep 2026 08:00:00 +0800</pubDate><guid>https://blog.kalend.top/2026/09/17/qorl-4b-rl-sql-optimization.html/</guid><description>
 &lt;blockquote&gt;
 &lt;p&gt;一个40亿参数的小模型，用强化学习调教后，生成的SQL执行计划比Postgres自己选的快81%。这不是论文空想，是真跑出来的数据。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="先说结论"&gt;先说结论
&lt;/h2&gt;&lt;p&gt;开发者Rohan Bansal用&lt;strong&gt;Qwen 3.8 4B蒸馏版&lt;/strong&gt;，通过两阶段训练——SFT（监督微调）+ Agentic RL（强化学习），让一个小模型学会了给Postgres生成更好的SQL查询计划。&lt;/p&gt;
&lt;p&gt;在113条复杂JOIN查询上，最终达成&lt;strong&gt;1.81倍几何平均加速&lt;/strong&gt;，总延迟下降44.7%。多次采样取最优时，零回退。&lt;/p&gt;
&lt;p&gt;总训练成本：&lt;strong&gt;$1,200&lt;/strong&gt;。全部代码开源。&lt;/p&gt;
&lt;h2 id="postgres的查询优化器有多难"&gt;Postgres的查询优化器有多难
&lt;/h2&gt;&lt;p&gt;你以为数据库最了解自己的数据？错了。Postgres做查询优化的核心难题是&lt;strong&gt;JOIN顺序选择&lt;/strong&gt;——这是个NP-hard问题。&lt;/p&gt;
&lt;p&gt;举个例子：3张表JOIN，光考虑连接顺序和扫描方式，就有&lt;strong&gt;4,608种&lt;/strong&gt;可能的执行计划。5张表？&lt;strong&gt;3,300万种&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Postgres用统计信息估算每个计划的代价，选最低的那个。但估算经常翻车——假设数据均匀分布，实际可能严重偏斜。一个早期JOIN的估算错误，会像多米诺骨牌一样污染整棵计划树。&lt;/p&gt;
&lt;h2 id="核心思路用pg_hint_plan遥控postgres"&gt;核心思路：用pg_hint_plan&amp;quot;遥控&amp;quot;Postgres
&lt;/h2&gt;&lt;p&gt;Postgres不允许你直接改它的代价模型。但有个巧妙的旁路：&lt;strong&gt;pg_hint_plan&lt;/strong&gt;扩展。&lt;/p&gt;
&lt;p&gt;只需在SQL语句上方加一行特殊注释，就能强制Postgres用指定的JOIN方式和扫描方式：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;/*+ HashJoin(a b) SeqScan(a) */&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;于是问题变成了：&lt;strong&gt;语言模型能不能学会生成比Postgres默认更好的hint？&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="两阶段训练方案"&gt;两阶段训练方案
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;阶段一：SFT蒸馏（教模型&amp;quot;说话&amp;quot;）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;模型一开始连工具都不会用。作者先用GPT-6 Astra跑了约500条查询的完整轨迹，然后把这些轨迹蒸馏进4B模型。&lt;/p&gt;
&lt;p&gt;只用了&lt;strong&gt;100条轨迹&lt;/strong&gt;（展开后382条训练样本），一个epoch就让模型学会了工具调用格式。两个epoch后开始产出有效计划。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;阶段二：Agentic RL（教模型&amp;quot;思考&amp;quot;）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;SFT只是照猫画虎，RL才让模型真正理解什么是&amp;quot;好计划&amp;quot;。&lt;/p&gt;
&lt;p&gt;训练流程：模型对每条查询生成候选hint → 发送到真实Postgres执行 → 测量执行时间 → 用自定义GRPO算法更新权重。&lt;/p&gt;
&lt;p&gt;作者还精心设计了奖励函数：用&lt;strong&gt;中位数比值&lt;/strong&gt;而非单次测量来减少噪声，并设置了5%的平局区间。&lt;/p&gt;
&lt;h2 id="训练结果从废铁到神器"&gt;训练结果：从废铁到神器
&lt;/h2&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;检查点&lt;/th&gt;
					&lt;th&gt;有效计划&lt;/th&gt;
					&lt;th&gt;几何加速&lt;/th&gt;
					&lt;th&gt;回退数&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;原始4B模型&lt;/td&gt;
					&lt;td&gt;14/113&lt;/td&gt;
					&lt;td&gt;0.85x&lt;/td&gt;
					&lt;td&gt;1&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;SFT完成&lt;/td&gt;
					&lt;td&gt;71/113&lt;/td&gt;
					&lt;td&gt;1.16x&lt;/td&gt;
					&lt;td&gt;5&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;RL 600步&lt;/td&gt;
					&lt;td&gt;99/113&lt;/td&gt;
					&lt;td&gt;1.35x&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;RL 1200步&lt;/td&gt;
					&lt;td&gt;101/113&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;1.41x&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;2&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;3次采样取最优&lt;/td&gt;
					&lt;td&gt;113/113&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;1.81x&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;从完全不会用工具，到比Postgres原生快81%。&lt;strong&gt;零回退&lt;/strong&gt;意味着没有一条查询因为用了AI的计划而变慢。&lt;/p&gt;
&lt;h2 id="几个值得注意的技术细节"&gt;几个值得注意的技术细节
&lt;/h2&gt;&lt;p&gt;◆ &lt;strong&gt;噪声控制是关键&lt;/strong&gt;：作者花了大量精力去抖——4个Postgres容器隔离运行、2GB shared_buffers确保缓存驻留、warmup机制、中位数配对比较。没有这些，RL的奖励信号就是垃圾。&lt;/p&gt;
&lt;p&gt;◆ &lt;strong&gt;奖励函数迭代了两版&lt;/strong&gt;：第一版太严厉（-3惩罚），模型吓得只敢返回默认计划。改成锚定GRPO后才真正学会探索。&lt;/p&gt;
&lt;p&gt;◆ &lt;strong&gt;SFT的val loss没动≠没在学&lt;/strong&gt;：第二个epoch验证损失没降，但实际性能提升了。这说明loss不是万能指标。&lt;/p&gt;
&lt;p&gt;◆ &lt;strong&gt;适用场景明确&lt;/strong&gt;：不是对所有SQL都有用。适合&lt;strong&gt;重复执行的分析型查询&lt;/strong&gt;——训练成本摊到成千上万次执行后，每次查询都更快。&lt;/p&gt;
&lt;h2 id="给开发者的启示"&gt;给开发者的启示
&lt;/h2&gt;&lt;p&gt;这个实验证明了一件事：&lt;strong&gt;小模型+RL在垂直领域可以打败大模型和传统系统&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;关键不是模型有多大，而是你能不能定义清楚&amp;quot;什么是好的&amp;quot;——在这个场景里，执行时间就是唯一的裁判。一旦有了可验证的奖励信号，4B参数就够了。&lt;/p&gt;
&lt;p&gt;作者原话：&lt;em&gt;&amp;ldquo;容易忽视小模型的存在，毕竟5万亿参数的庞然大物让人目眩。但我们不应该。&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;原文：&lt;a class="link" href="https://rohanbansal.com/qorl" target="_blank" rel="noopener"
 &gt;Training a 4B model to produce 81% faster query plans than Postgres&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;讨论：&lt;a class="link" href="https://news.ycombinator.com/item?id=49731285" target="_blank" rel="noopener"
 &gt;Hacker News&lt;/a&gt;（381分/80评论）&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>