Research / September 17, 2026
RL for text to SQL
Two small models learn to write SQL with reinforcement learning and no judge model. The reward is a program that runs the query and checks the rows. Nemotron-Nano-8B went from 26% to 35% in 44 minutes. Qwen3-4B needed a better-fed trainer, then 53% to 74%. A newer 9B base reached 82% in one round.
The short version. We wanted small models that turn a plain-English question into a SQL query for a shop's database. Instead of a judge model, the reward is a program: it runs the query and checks the rows. Nemotron-Nano-8B improved in a single 44-minute round, and the gain held up on three repeat scores. Qwen3-4B barely moved in four rounds. Feeding the trainer differently in a fifth round took it from 53% to 74%. A newer base, Qwen3.5-9B, reached 82% in one round, once we found two bugs in our own tooling that had hidden the gain. The recipe is public.
Paired differences: Nemotron +9 points (+5 to +13), proven. Qwen +3 points (-2 to +7), not proven. The interval is the range the true number very likely sits in.
| Item | Value | 95% interval |
|---|---|---|
| Nemotron-Nano-8B, base | 26% | 20% to 33% |
| Nemotron-Nano-8B, one round | 35% | 28% to 42% |
| Qwen3-4B, base | 58% | 52% to 64% |
| Qwen3-4B, best of four rounds | 61% | 56% to 67% |
The recipe in six steps
Seed. A Postgres database with data in it. Ours has eight tables and 300 orders.
Author. A teacher model writes each question with its SQL. We keep a task only if the SQL returns one to fifty rows on two runs. That gave 741 tasks. Before anything else, set aside the test set: 140 tasks picked by a hash of the id, never trained on.
Verify. The verifier runs the model's query read-only and compares the rows to the reference. A match scores 1, wrong rows 0.1, a query that does not run 0. There is no judge, so a longer answer cannot score higher.
Benchmark. Sample each test task four times. Report the first-try pass rate (pass@1) with its 95% interval, beside the rate for any of four tries (pass@4). The gap between them is what RL can work with. Then read the failures.
Train. GRPO with a LoRA adapter and eight samples per question. 600 steps took 44 minutes on one H100.
Score. Sample the test set four times and take the paired difference against the base. A gain is proven only when the interval excludes zero, and we score both sides three times.
What happened
Nemotron moved in one round. Most of the gain was queries that now run.
Three repeat scores of the gain: +9, +10 and +11 points, all clear of zero.
| Item | Value |
|---|---|
| Base | 55% |
| After one round | 35% |
Qwen3-4B started at 58% and got the meaning wrong, not the syntax. Four GRPO rounds and one SFT pass reached 61%, a gain we cannot separate from noise. In the first two rounds the training reward rose while the test score did not. Without the held-out set, that run would have shipped.
Round five: the same reward, fed differently
A larger run on another schema used the same reward, tasks and model. Three things about feeding the trainer differed.
| Rounds one to four | Round five | |
|---|---|---|
| Answers per update | 8, one question at a time | 512, 32 questions times 16 answers |
| Which questions | all 601 | the 326 the model got right some of the time (the difficulty band) |
| Replies cut off by the length budget | scored zero | ignored |
| Test set | 140 tasks, band about 6 points | 459 tasks, band about 3.5 points |
We also grew the test, because a 6-point band can never prove a 3-point gain. On the bigger test the round-four gain turned out to be real.
Paired differences against base: round four +2.6 (+0.1 to +5.0), step 50 +20.5 (+17.9 to +23.1), step 75 +21.8 (+19.2 to +24.5). Step 50 was measured twice from scratch, 0.73 both times. The base was scored through the same server as the checkpoints (+0.4 points, inside the band).
| Item | Value | 95% interval |
|---|---|---|
| Base | 53% | 49% to 56% |
| Round four | 55% | 52% to 59% |
| Round five, step 25 | 57% | 54% to 61% |
| Round five, step 50 | 73% | 69% to 77% |
| Round five, step 75 | 74% | 71% to 78% |
| Round five, step 100 | 74% | 70% to 78% |
The same numbers against training samples. For Qwen3-4B, rounds one to four are left of the vertical line and round five is right of it; the Qwen3.5-9B line runs the round-five recipe from its first step. The shaded band is the 95% uncertainty band on each point.
What the model learned is reliability, not new ability.
Fifty steps took about seven hours on one H100 and 25,600 scored answers. Rounds one to four had used 32,000 answers over 8,000 steps for three points.
| Item | Value |
|---|---|
| pass@4, right on any of four tries, base | 76% |
| pass@4, step 75 | 79% |
| pass^4, right on every try, base | 25% |
| pass^4, step 75 | 65% |
| Replies with no query, base | 13% |
| Replies with no query, step 75 | 0% |
| SQL errors, base | 12% |
| SQL errors, step 75 | 5% |
Next. The curve is flat from step 50. Re-measure the difficulty band on the round-five model, then a bigger base or harder questions.
Qwen3.5 on the same test
Does a newer base start higher? Same prompt, reply budget and 459 questions.
Both Qwen3.5 models reason in plain text before the query. Every bar is scored on the first reply. Earlier versions of this chart read the 9B checkpoints as flat at 55%: one bug scored the wrong turn, the other served the base model under the adapter's name.
| Item | Value | 95% interval |
|---|---|---|
| Qwen3-4B (the model above), base | 53% | 49% to 56% |
| Qwen3.5-9B, base | 60% | 57% to 64% |
| Qwen3.5-9B, round one, step 25 | 79% | 75% to 82% |
| Qwen3.5-9B, round one, step 50 | 81% | 77% to 84% |
| Qwen3.5-9B, round one, step 75, adapter served correctly | 82% | 79% to 85% |
| Qwen3.5-9B, round one, step 75, second scoring pass | 83% | 79% to 86% |
| Qwen3.5-9B, round two, step 25 | 83% | 80% to 86% |
| Qwen3.5-9B, round three, step 25 (questions re-picked on step 75) | 84% | 81% to 87% |
| Qwen3.5-9B, round three, step 50 (the round's end) | 84% | 81% to 87% |
| Qwen3.5-9B, all four checkpoints as first served | 62% | 58% to 65% |
| Qwen3.5-4B, base | 35% | 32% to 38% |
One round of the round-five recipe puts the 9B above every Qwen3-4B checkpoint, and a second scoring pass on the same adapter landed within a point of the first. Three quarters of the questions it now gets right all four times. Most of the gain comes early: 79% at step 25, 81% at step 50, 82% at step 75, and round two's first checkpoint, 83%, is within noise of that. Round three re-picked the training questions from the step-75 model itself, so the trainer saw questions it still gets wrong sometimes, and its first checkpoint is 84%: two points over step 75, an interval that just clears zero. Step 50, the end of the round, is 84% again, with the easy and the hard questions both up. That is where this model stops on this test: 84% on the first try, 88% in four tries, and the recipe has no lever left that the interval can see.
We nearly missed it. For two days every checkpoint of this model scored the same as its base, and we wrote the run up as flat. The training log had said so all along: the trainer's own reward on the questions it trained on was 0.95 while the served adapter scored 0.65 on them. Both bugs are fixed.
| Bug that hid the gain | The check that catches it now |
|---|---|
| The simulator wrote a second user turn where the eval asked for one, and on 15% of the 9B rows and 65% of the 4B rows the grader scored that second reply (#586) | count the turns per row |
| The trained adapter, saved by one library and loaded by another, used layer names the server did not recognise, so it loaded without a warning and changed nothing: every "checkpoint" we scored was the base model (#588) | score any served adapter on a hundred questions it trained on before believing a number |
| Fix in the recipe for the Qwen3.5 stack | Without it |
|---|---|
| Weight-sync rename between transformers 5 and vLLM 0.29 | every trained weight silently dropped |
| Optimized linear-attention kernels | one micro-batch took 138 GB |
| vLLM memory share large enough for weights plus cache | out of memory |
| Gradient checkpointing on TRL 1.13 | out of memory (on TRL 0.19 with Qwen3-4B, leave it off: garbage completions at reward zero) |
For researchers
Each held-out task gets samples at temperature 0.7 and pass. pass@ is the estimator of Chen et al. [3], pass the share passing every sample, intervals are percentile bootstraps over tasks [4], :
The difference is paired on tasks, each side:
The verifier is a program, so the reward is verifiable in the sense of [1, §7]. For a candidate query and the reference ,
Floats compare to two decimals, column order is free, row order counts
only when has ORDER BY. GRPO [2] on a LoRA adapter [5], TRL
[7], vLLM generation [6]. Advantage
over the
samples of a prompt, KL penalty against the base with coefficient
.
Thinking on, LoRA r16, TRL 0.19.1 GRPO. r3 vs base +0.029 (-0.016 to +0.073). Date and time archetype 0.43 to 0.58, +0.15 (+0.03 to +0.27). r4 vs r3 -0.007 (-0.048 to +0.034). By round four the training reward sat at 0.65 and the KL to the base at 0.03.
| Item | Value | 95% interval |
|---|---|---|
| base | 0.58 | 0.52 to 0.64 |
| r1 | 0.58 | 0.52 to 0.64 |
| r2 | 0.60 | 0.54 to 0.67 |
| sft-think | 0.60 | 0.54 to 0.67 |
| r3 | 0.61 | 0.56 to 0.67 |
| r4 | 0.61 | 0.54 to 0.67 |
| Round | From | Steps | lr | Generation | |
|---|---|---|---|---|---|
| r1 | base | GRPO 100 | 2e-5 | 0.04 | HF generate |
| r2 | r1 | GRPO 200 | 5e-5 | 0.01 | HF generate |
| sft-think | base | self-distillation on 199 verified traces, hosted SFT, 2 epochs | |||
| r3 | r2 | GRPO 1,000 | 2e-5 | 0.01 | vLLM, 8 prompts per generate |
| r4 | r3 | GRPO 1,000 | 2e-5 | 0.01 | vLLM, same settings |
| Nemotron r1 | base | GRPO 600 | 2e-5 | 0.01 | vLLM, steps per generation 8, max completion 512, 44 min on one H100 |
| Cost to r4 | fourteen hours, six checkpoints, twelve test-set scores, under $100 of GPU |
| Correction on record | before SDK 0.51, simulate(tasks=...) drafted a tool surface and sent it to the policy, so 42 of r3's 560 first-pass replies were tool calls scored as failures; the r3 row is the clean re-measure and the polluted files are under raw/with-drafted-tools/ |
| Round-five test | the 1,482 extra questions were written the same way as the first 741 |
| Method | RLHF book [1]: verifiable rewards (§7), over-optimization symptoms (§14), fixed held-out split with an interval on the paired difference (§16) |
| Code | recipes/04-train/text-to-sql |
| Data | while-ai/text-to-sql-shop, configs eval-base to eval-r4, eval-nemotron-8b-base, eval-nemotron-8b-r1; adapters while-ai/text-to-sql-shop-r1 to -r4 and -nemotron-8b-r1 |
detailed thinking off; the reasoning mode does not engage on these prompts, and the thinking-on arm also scores 0.26.
| Item | Value | 95% interval |
|---|---|---|
| base | 0.26 | 0.20 to 0.33 |
| r1 | 0.35 | 0.28 to 0.42 |
560 rows per side per run. Base scored 0.26 / 0.26 / 0.26, r1 0.35 / 0.36 / 0.37. By difficulty: medium +0.10 (+0.01 to +0.18), hard +0.12 (+0.05 to +0.20), easy flat.
| Item | Value | 95% interval |
|---|---|---|
| Run 1 | +0.087 | +0.048 to +0.130 |
| Run 2 | +0.098 | +0.055 to +0.148 |
| Run 3 | +0.111 | +0.068 to +0.155 |
References
- Lambert, N. (2025). Reinforcement Learning from Human Feedback. arXiv:2504.12501. Online at rlhfbook.com.
- Shao, Z., Wang, P., Zhu, Q., Xu, R., Song, J., Bi, X., Zhang, H., Zhang, M., Li, Y. K., Wu, Y. and Guo, D. (2024). DeepSeekMath: Pushing the limits of mathematical reasoning in open language models. arXiv:2402.03300.
- Chen, M., Tworek, J., Jun, H., Yuan, Q., Pinto, H. P. de O., Kaplan, J., et al. (2021). Evaluating large language models trained on code. arXiv:2107.03374.
- Efron, B. (1979). Bootstrap methods: Another look at the jackknife. The Annals of Statistics, 7(1), 1-26.
- Hu, E. J., Shen, Y., Wallis, P., Allen-Zhu, Z., Li, Y., Wang, S., Wang, L. and Chen, W. (2022). LoRA: Low-rank adaptation of large language models. International Conference on Learning Representations. arXiv:2106.09685.
- Kwon, W., Li, Z., Zhuang, S., Sheng, Y., Zheng, L., Yu, C. H., Gonzalez, J. E., Zhang, H. and Stoica, I. (2023). Efficient memory management for large language model serving with PagedAttention. Proceedings of the 29th ACM Symposium on Operating Systems Principles. arXiv:2309.06180.
- von Werra, L., Belkada, Y., Tunstall, L., Beeching, E., Thrush, T., Lambert, N., Huang, S., Rasul, K. and Gallouédec, Q. (2020). TRL: Transformer Reinforcement Learning [software]. github.com/huggingface/trl.
- whilehq (2026). whileai SDK [software]. Apache 2.0. github.com/whilehq/whileai-sdk.
Run it
pip install "whileai>=0.51" "psycopg[binary]" openai anthropic
git clone https://github.com/whilehq/whileai-sdk
cd whileai-sdk/recipes/04-train/text-to-sql
python rollout.py --model qwen3-4b --split holdout --k 4
python build.pyThe README has the database setup, the Modal command for training, and
delta.py for the paired score.