File size: 4,768 Bytes
f022d95
 
1ecd6e2
 
f022d95
1ecd6e2
 
f022d95
1ecd6e2
 
 
 
 
 
 
f022d95
 
1ecd6e2
f022d95
1ecd6e2
 
 
f022d95
1ecd6e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f022d95
 
1ecd6e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f022d95
1ecd6e2
 
 
 
 
 
 
 
 
f022d95
 
1ecd6e2
 
 
 
 
 
 
 
f022d95
1ecd6e2
 
f022d95
1ecd6e2
f022d95
1ecd6e2
 
f022d95
1ecd6e2
 
 
 
 
 
 
 
 
f022d95
1ecd6e2
 
f022d95
1ecd6e2
f022d95
1ecd6e2
 
 
 
f022d95
1ecd6e2
f022d95
1ecd6e2
 
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
---
base_model: google/gemma-3-270m-it
datasets:
  - chabab/text2sql-oracle-postgres
library_name: transformers
license: apache-2.0
pipeline_tag: text-generation
tags:
  - text-to-sql
  - sql
  - oracle
  - postgresql
  - gemma3
  - trl
  - sft
---

# gemma-3-270m-text2sql-oracle-postgres

[`google/gemma-3-270m-it`](https://huggingface.co/google/gemma-3-270m-it) fine-tuned to turn a
schema + a natural-language question into **one** dialect-correct SQL statement — Oracle or
PostgreSQL — with no markdown fences and no commentary.

At 270M parameters it runs on CPU and quantizes to ~290 MB.

## Results

Held-out test split (60 examples), greedy decoding, normalized exact match against gold SQL:

| Slice | Exact match |
|---|---|
| **Overall** | **78.3%** (47/60) |
| Oracle | 86.7% (39/45) |
| PostgreSQL | 53.3% (8/15) |
| easy | 93.3% (14/15) |
| medium | 69.2% (27/39) |
| hard | 100% (6/6) |

Per-example predictions are in [`eval_results.json`](./eval_results.json).

Caveats worth knowing before you rely on these numbers:

- The test split is skewed 45 Oracle / 15 PostgreSQL, so the PostgreSQL figure rests on 15
  examples and has a wide error bar.
- Exact match is strict. Several "failures" are valid SQL that differs from gold — an extra
  `LIMIT`, a different but equivalent predicate. Real semantic accuracy is higher than 78.3%.
- The most common genuine error is dialect leakage: emitting `LIKE` where PostgreSQL gold uses
  `ILIKE`. If case-insensitive matching matters to you, check that specific pattern.
- Only the 7 schemas in the training set (hr, sales, banking, inventory, tickets, university,
  logistics) are represented. Generalization to unseen schemas is untested.

## Usage

The model expects the system prompt naming the dialect, then a `Schema:` block and a `Question:`
block — the same shape as the training data.

```python
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

model_id = "chabab/gemma-3-270m-text2sql-oracle-postgres"
tok = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(
    model_id,
    dtype=torch.bfloat16,
    attn_implementation="eager",  # Gemma-3 needs eager attention for correct generation
    device_map="auto",
)

messages = [
    {"role": "system", "content": "You convert natural language into PostgreSQL SQL. Use only tables and columns from the provided schema. Reply with one SQL statement and nothing else. No markdown fences. No commentary."},
    {"role": "user", "content": """Schema:
employees(
  employee_id INTEGER PK,
  first_name VARCHAR(50),
  last_name VARCHAR(50),
  hire_date DATE,
  salary NUMERIC(12,2),
  department_id INTEGER FK->departments.department_id
)

Question:
Show the five employees with the largest salary. Return only the SQL."""},
]

ids = tok.apply_chat_template(messages, add_generation_prompt=True,
                              return_tensors="pt", return_dict=True).to(model.device)
out = model.generate(**ids, max_new_tokens=256, do_sample=False)
print(tok.decode(out[0][ids["input_ids"].shape[-1]:], skip_special_tokens=True).strip())
# SELECT first_name, last_name, salary FROM employees ORDER BY salary DESC LIMIT 5;
```

Two things matter for output quality:

- **Use greedy decoding** (`do_sample=False`). The task has one right answer; sampling only adds
  drift.
- **Use `attn_implementation="eager"`.** Gemma-3 generates degenerate repeated tokens under the
  default SDPA path in some configurations.

## GGUF / local inference

Quantized builds for Ollama, LM Studio, and llama.cpp:
[`chabab/gemma-3-270m-text2sql-oracle-postgres-GGUF`](https://huggingface.co/chabab/gemma-3-270m-text2sql-oracle-postgres-GGUF)

## Training

Full-parameter SFT (no LoRA — the model is small enough to tune end to end) with TRL `SFTTrainer`
on one L4 GPU, about 10 minutes.

| | |
|---|---|
| Base | `google/gemma-3-270m-it` |
| Data | `chabab/text2sql-oracle-postgres` — 684 train / 60 validation / 60 test |
| Epochs | 5 |
| Effective batch size | 16 (4 × 4 grad accum) |
| Learning rate | 5e-5, cosine, 10 warmup steps |
| Max sequence length | 1024 |
| Precision | bf16 |

Final metrics: train loss **0.0256**, eval loss **0.0683**, eval token accuracy **98.5%**.
Eval loss fell monotonically through training with no divergence.

## Limitations

Generated SQL is not validated against a live database. The model can produce syntactically valid
statements that reference the wrong table or misread the intent — two of the observed test
failures do exactly that. Review output before executing it, and never run generated SQL against
production with write permissions.

## License

Apache 2.0, inheriting the [Gemma terms of use](https://ai.google.dev/gemma/terms) from the base
model.