← All work
TransformersUnslothQwen3LLMFine-tuneSQL

Text To Sql

Can I fine-tune the Qwen3 0.6B model so that it can generate SQL that meets the requirements of the given SQL schema?

In this example, I fine-tuned the Qwen3 1.7B model on instructions and sql outputs on gretelai/synthetic_text_to_sql dataset. Given a database schema and a natural-language question, the model is trained to return JSON with sql and explanation fields. Outputs are not guaranteed correct or safe to execute.

The question

Can I fine-tune the Qwen3 0.6B model so that it can generate SQL that meets the requirements of the given SQL schema?

Approach

  • 105,851 training examples but used 27,500 because of poor GPU
  • 2,500 test and 25,000 train set
  • Qwen3 0.6B as base model
  • unsloth for gpu efficiency and speed up the train

Result

I fine-tuned the Qwen3 model on 25,000 examples. However, what I overlooked here was the quality of the dataset. I trained the model using a fixed JSON schema, and while the model learned the schema perfectly, the SQL code it generated didn’t work correctly. After doing some research, I discovered that the dataset was a synthetic one created in 2024, and it was clear that some examples didn’t produce SQL output that worked correctly.

Since I put a lot of effort into training this model and benchmarking it - and since it was the first LLM I ever trained—I’m proud to showcase this failed model in my portfolio.

text-to-sql-performance

text-to-sql-inference-speed

These benchmark charts provide us with a great deal of information about the models. We can see that the training was definitely not in vain; the AI generates SQL faster and has clearly learned the JSON schema. However, it is also clear that the incorrect SQL statements caused the base model to forget the SQL statements it had learned, leading it to deviate from our original objective.

What I Learned

  • I understood the need to ensure the accuracy and quality of the dataset before training the model
  • I understand why benchmarks are done; before running the benchmarks, I thought I had trained a model that actually worked well - at least better than the base model.
  • I learned how to provide data to a chat model

Downloads

NOTEBOOK

Notebook

Download .ipynb ↓

Code and saved outputs, as recorded in the notebook.

In [ ]
!pip install -q unsloth trl datasets
In [ ]
from datasets import load_dataset

raw_datasets = load_dataset("gretelai/synthetic_text_to_sql")
In [ ]
raw_datasets
In [ ]
raw_train_dataset = raw_datasets["train"]
raw_train_dataset[0]
In [ ]
raw_train_dataset.features
In [ ]
# Veri setini rastgele karıştırıp 25.000 satıra indiriyoruz
shortened_train = raw_datasets["train"].shuffle(seed=42).select(range(25000))
shortened_test = raw_datasets["test"].shuffle(seed=42).select(range(min(2500, len(raw_datasets["test"]))))

from datasets import DatasetDict
shortened_datasets = DatasetDict({
    "train": shortened_train,
    "test": shortened_test
})

display(shortened_datasets)
In [ ]
from unsloth import FastLanguageModel
import torch

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="unsloth/Qwen3-0.6B",
    max_seq_length=1024,
    dtype=torch.float16,
    load_in_4bit=False,
)

model = FastLanguageModel.get_peft_model(
    model,
    r=16,
    lora_alpha=16,
    target_modules=[
        "q_proj", "k_proj", "v_proj", "o_proj",
        "gate_proj", "up_proj", "down_proj"
    ],
    lora_dropout=0,
    bias="none",
    use_gradient_checkpointing="unsloth",
)
In [ ]
print(type(model).__name__)
print(model.config.architectures)
In [ ]
inputs = tokenizer("This is the first sentence.", "This is the second one.", "This is the third bear.")
inputs
In [ ]
tokenizer.convert_ids_to_tokens(inputs["input_ids"])
In [ ]
import json

def tokenize_fn(example):
    input_text = json.dumps({
        "context": example["sql_context"],
        "prompt": example["sql_prompt"]
    }, ensure_ascii=False)

    output_text = json.dumps({
        "sql": example["sql"],
        "explanation": example["sql_explanation"]
    }, ensure_ascii=False)

    prompt_messages = [
        {"role": "user", "content": input_text}
    ]

    prompt = tokenizer.apply_chat_template(
        prompt_messages,
        tokenize=False,
        add_generation_prompt=True
    )

    # Hedef yanıtın sonunu EOS tokenıyla işaretliyoruz.
    completion = output_text + tokenizer.eos_token

    prompt_ids = tokenizer(
        prompt, add_special_tokens=False, padding=True, truncate=True
    )["input_ids"]

    completion_ids = tokenizer(
        completion, add_special_tokens=False, padding=True, truncate=True
    )["input_ids"]

    input_ids = prompt_ids + completion_ids

    labels = [-100] * len(prompt_ids) + completion_ids

    return {
        "input_ids": input_ids,
        "attention_mask": [1] * len(input_ids),
        "labels": labels
    }

def tokenize_batch(batch):
    results = {
        "input_ids": [],
        "attention_mask": [],
        "labels": []
    }

    for i in range(len(batch["sql_prompt"])):
        example = {
            "sql_context": batch["sql_context"][i],
            "sql_prompt": batch["sql_prompt"][i],
            "sql": batch["sql"][i],
            "sql_explanation": batch["sql_explanation"][i]
        }

        tokens = tokenize_fn(example)

        for key in results:
            results[key].append(tokens[key])

    return results
In [ ]
tokenized_datasets = shortened_train.map(tokenize_batch, batched=True)
tokenized_test_datasets = shortened_test.map(tokenize_batch, batched=True)

tokenized_datasets
tokenized_test_datasets
In [ ]
from transformers import DataCollatorForSeq2Seq

data_collator = DataCollatorForSeq2Seq(
    tokenizer=tokenizer,
    model=model,
    padding=True,
    label_pad_token_id=-100,
    return_tensors="pt"
)
In [ ]
samples = tokenized_datasets[:8]
samples = {k: v for k, v in samples.items() if k in ["input_ids", "attention_mask", "labels"]}
[len(x) for x in samples["input_ids"]]
In [ ]
list_samples = [
    {key: samples[key][i] for key in samples}
    for i in range(len(samples["input_ids"]))
]

batch = data_collator(list_samples)
{k: v.shape for k, v in batch.items()}
In [ ]
from transformers import TrainingArguments

training_args = TrainingArguments(
    output_dir="qwen3-sql-unsloth",
    per_device_train_batch_size=5,
    per_device_eval_batch_size=3,
    gradient_accumulation_steps=2,
    num_train_epochs=10,
    learning_rate=1e-4,
    fp16=True,
    bf16=False,
    optim="adamw_8bit",
    logging_steps=20,
    eval_strategy="epoch",
    save_strategy="epoch",
    save_total_limit=2,
    load_best_model_at_end=True,
    metric_for_best_model="eval_loss",
    greater_is_better=False,
    report_to="none",
)
In [ ]
from transformers import Trainer

trainer = Trainer(
    model,
    training_args,
    train_dataset=tokenized_datasets,
    eval_dataset=tokenized_test_datasets,
    data_collator=data_collator,
    processing_class=tokenizer,
)
In [ ]
import os

os.environ["PYTORCH_ALLOC_CONF"] = "expandable_segments:True"
In [ ]
trainer.train()