File size: 9,174 Bytes
48ffb67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
654a17a
 
 
 
48ffb67
 
654a17a
 
 
 
 
 
 
48ffb67
654a17a
 
 
 
 
 
 
 
 
48ffb67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
---
license: apache-2.0
base_model: Qwen/Qwen3.5-9B
language:
  - en
pipeline_tag: text-generation
tags:
  - text-to-sql
  - sql
  - bird
  - spider
  - agentic
  - qwen3.5
library_name: transformers
---

# sqrl-9b

**sqrl-9b** is a 9B-parameter agentic text-to-SQL model built on
[Qwen/Qwen3.5-9B](https://huggingface.co/Qwen/Qwen3.5-9B). It answers natural-language
questions over SQLite databases by optionally *probing the database first* — running
read-only exploration queries to check value formats, join paths, and filters — before
committing to a final SQL answer. It is the larger sibling of
[feyninc/sqrl-4b](https://huggingface.co/feyninc/sqrl-4b), trained with the same recipe.

Training stages:

1. **SFT distillation** — fine-tuned on ~10.2k execution-verified agentic trajectories
   sampled from a much stronger RL-trained 35B teacher (Qwen3.6-35B-A3B, 70.3% BIRD-dev EX)
   on cleaned BIRD-train + Spider-train questions. Only trajectories whose final SQL
   execution-matched the gold answer were kept.
2. **CISPO RL** — online reinforcement learning (group-relative advantage, group size 8)
   with execution-match reward on the same cleaned BIRD+Spider question pool, with
   async dynamic sampling. Best checkpoint at step 60.

## Results

All BIRD-dev numbers measured on the full 1534-question set at temperature 0.7 with the
agentic harness (execution-clustered majority vote = sample 8 candidates, group them by
identical execution result set, return the largest cluster's query).

| Benchmark | Metric | Score |
|---|---|---|
| BIRD-dev | pass@1 (avg over 8 samples) | **66.6%** EX |
| BIRD-dev | vote-of-8 (execution-clustered) | **69.8%** EX |

Family comparison on identical seeds (pass@1 / vote@8):
[sqrl-4b](https://huggingface.co/feyninc/sqrl-4b) 64.6 / 68.8 ·
[sqrl-9b](https://huggingface.co/feyninc/sqrl-9b) 66.6 / 69.8 ·
[sqrl-35b-a3b](https://huggingface.co/feyninc/sqrl-35b-a3b) 68.7 / 70.6.

Notes from the measurement campaign:
- **sqrl-9b vote-of-8 (69.8%) is the family's cost/performance sweet spot** — within
  0.8 pts of anything the whole family achieves under any sampling/voting scheme,
  including the 35B. vote-of-4 captures ~85% of the voting gain at half the cost.
- Vote unanimity is a strong free confidence signal: when all 8 samples agree (~60% of
  questions) the answer is right ~87% of the time; narrow/tied votes are right ~19-43% —
  useful for routing or escalation.
- The pre-RL SFT checkpoint scores 64.0% pass@1 on the same harness; base Qwen3.5-9B
  scores ~56%.

## How it works — the agentic protocol

The model emits **exactly one action block per turn**, after a brief reasoning summary:

- `<sql> SELECT ... </sql>` — a read-only *exploration* query. Execute it against the
  database and feed the result back as a user turn wrapped in `<observation>` tags.
  The model continues (up to a step budget).
- `<answer> SELECT ... </answer>` — the final SQL. Execute and return.

Many questions are answered directly (zero exploration turns); the model learned to
explore only when seeing real data would change its answer (value-format checks,
ambiguous joins).

## Usage

### Serve with vLLM

```bash
vllm serve feyninc/sqrl-9b --served-model-name sqrl-9b \
  --data-parallel-size 4 --gpu-memory-utilization 0.90 --max-model-len 32768
```

> **Important:** do **not** enable a reasoning parser (e.g. `--reasoning-parser qwen3`).
> The model's post-`</think>` content carries the action protocol; stripping/rerouting
> the think block breaks it. Parse `message.content` — if it contains a `</think>`,
> take everything after it.

Recommended sampling: `temperature 0.7, top_p 0.95` (matches training); greedy also works.

### Prompt format

System prompt (fill `{schema}`, `{evidence}`, `{max_steps}`):

````text
<role>
You are an expert data analyst, fluent in SQL, with a meticulous eye for
matching a question's intent to the exact tables, columns, and stored value formats of
a database.
</role>

<task>
Translate the user's natural-language question into a SQL query that answers
it, using the database schema in <schema> and any domain hints in <evidence>. You can
run read-only queries against the database to inspect it before giving your final
answer.
</task>

<database_engine>
SQLite
</database_engine>

<schema>
{schema}
</schema>

<evidence>
{evidence}
</evidence>

<protocol>
Think through the problem internally first. Then, in your response, write a
BRIEF summary of your reasoning — 2-4 sentences stating which tables and columns are
relevant, the joins and filters, and any exact value-format detail. Be decisive: state
the plan once, do not second-guess or restate. End your message with EXACTLY ONE action
block (and nothing after it):

  <sql> a read-only query to inspect the database </sql>
     Use this when uncertain and you want to see real data before answering — e.g.
     confirm a value's exact stored format, check a filter actually matches rows,
     sanity-check an intermediate result, or verify a join. You will see the result
     rows and then continue.

  <answer> your final SQL query </answer>
     Use this once you are confident. It is executed and scored, and the task ends.

Examples:
  I'll check the exact county value before filtering, since the format may vary.
  <sql> SELECT DISTINCT `County Name` FROM frpm LIMIT 10 </sql>

  The schools are in the frpm table; I'll select `School Name` and filter `County Name`
  to 'Alameda'. The schema is clear, so I can answer directly.
  <answer> SELECT `School Name` FROM frpm WHERE `County Name` = 'Alameda' </answer>
</protocol>

<rules>
- Use only the tables and columns defined in <schema>.
- Quote identifiers containing spaces or special characters with backticks.
- Return exactly the columns the question asks for — no more, no fewer.
- Use the hints in <evidence> to resolve ambiguous terms and value encodings.
- If you already know the correct query, go straight to <answer> — investigating is
  optional; only run <sql> when seeing the data would actually change your answer.
- The action block must be the LAST thing in your message; do not discuss the tags
  themselves in your reasoning.
- You have at most {max_steps} <sql> steps; after that you must give <answer>.
</rules>
````

First user turn:

```text
Question: {question}

Reason about it, then give <sql> to investigate or <answer> to finish.
```

After executing an exploration `<sql>`, feed the result back as a **user** message:

```text
<observation>
{tab-separated result rows, or the error message}
</observation>
Continue with <sql> or <answer>.
```

`{schema}` is a readable dump of the SQLite schema (CREATE-TABLE-like listing of
tables/columns/types). `{evidence}` is the BIRD external-knowledge hint, or
`(none provided)`. `max_steps=5` was used in training. When the step budget is
exhausted, send `You must finish now. Give your <answer>.` as a user turn.

### Minimal driver loop

```python
import re
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY")

messages = [{"role": "system", "content": system_prompt},
            {"role": "user", "content": user0}]
for step in range(6):
    r = client.chat.completions.create(model="sqrl-9b", messages=messages,
                                       temperature=0.7, top_p=0.95, max_tokens=8192)
    content = r.choices[0].message.content
    if "</think>" in content:                       # strip inline think scratchpad
        content = content.split("</think>")[-1].strip()
    messages.append({"role": "assistant", "content": content})
    ans = re.findall(r"<answer>(.*?)</answer>", content, re.S)
    if ans:
        final_sql = ans[-1].strip(); break
    sql = re.findall(r"<sql>(.*?)</sql>", content, re.S)
    if not sql:
        break
    obs = run_readonly(sql[-1].strip())             # your sqlite executor
    messages.append({"role": "user",
                     "content": f"<observation>\n{obs}\n</observation>\n"
                                "Continue with <sql> or <answer>."})
```

## Checkpoint notes

- This repo contains the **full merged HF checkpoint** (LoRA rank 32 merged into the
  base). Qwen3.5-9B has untied embeddings, so the merge is direct: the trained head
  lands in `lm_head`, `embed_tokens` is untouched. Loads with standard `transformers` /
  vLLM — no custom code beyond `trust_remote_code` if your transformers version
  requires it for Qwen3.5.
- The vision tower is inherited from the base model unchanged. The model is usable as a
  standard VL checkpoint, but SQL training was text-only.
- Text-to-SQL training data: BIRD-train and Spider-train, execution-filtered and
  semantically cleaned (3-model LLM-judge panel). BIRD-dev and Spider-test were never
  trained on.

## Intended use & limitations

Built for SQLite text-to-SQL with schema + optional evidence in context. Works best
with the exact prompt protocol above (it was both SFT'd and RL'd under it). Not tuned
for other SQL dialects; identifier quoting follows SQLite backtick conventions. As with
all text-to-SQL models, execute generated SQL read-only and validate before acting on
results.