Table of Contents

👉 Try the demo: https://github.com/LM-Kit/lm-kit-net-samples/tree/main/console_net/model-optimization/fine-tuning/text_to_sql_fine_tuning

Text-to-SQL Fine-Tuning for C# .NET Applications


🎯 Purpose of the Demo

This demo fine-tunes a small local model on a dataset file so it writes correct SQLite for YOUR database schema, without the schema pasted into every prompt. It measures held-out quality before and after training (is the reply bare SQL, does it hit the right tables, does every identifier exist in the schema), then merges the adapter into a single deployable GGUF model and proves the artifact answers on its own.

👥 Who Should Use This Demo (Target Audience)

  • .NET developers building natural-language query features over a known schema.
  • Teams whose training data already exists as JSONL/ShareGPT/Alpaca files and who want a measurable, local training pipeline from file to artifact.

🚀 What Problem It Solves

A general model does not know your table and column names: it invents order_date, stock_quantity, customer_name. The usual workaround, pasting the schema into every prompt, costs tokens and latency on every request. Fine-tuning bakes the schema in once: prompts carry only the question, replies reference only real identifiers. Everything runs locally, so the schema never leaves the machine.

💻 Demo Application Overview

The sample loads qwen3.5:0.8b, evaluates ten held-out questions against the base model, loads a 68-conversation chat JSONL dataset with AddDatasetFile (format auto-detected), checks dataset statistics and the chat-template mask (UnmaskedSampleCount), trains with rank 16 adapters on attention and feed-forward modules under a cosine schedule with warmup and a validation split, re-evaluates, then merges the adapter into a standalone model with LoraMerger (EnableQuantization keeps the artifact at the base precision) and runs a query on the merged file.

✨ Key Features

  • File-based training data: chat JSONL in the repo, ShareGPT/Alpaca/text/ZIP load the same way.
  • A verifiable metric: schema-correct SQL on held-out questions, printed per question before and after.
  • Validation loss reported per epoch through FinetuningProgress.
  • Two artifacts from one run: a 25 MB adapter and a merged, re-quantized GGUF.

Example Output

Before fine-tuning:
  [err] SELECT * FROM VeloShop WHERE Category = 'Road Bike' AND Price < 800;
  [err] SELECT * FROM orders ORDER BY order_date DESC LIMIT 3;
BASE: schema-correct SQL 2/10

Dataset: 68 conversations, 47 to 88 tokens per sample.
Training (rank 16, 8 epochs, attention + feed-forward)...
  epoch 8/8  validation loss 0.2588

After fine-tuning:
  [ok ] SELECT * FROM products WHERE category = 'road' AND price_cents < 80000;
  [ok ] SELECT SUM(total_cents) FROM orders WHERE status = 'shipped';
TUNED: schema-correct SQL 8/10

Merged model: veloshop-sql-merged.gguf (442 MB)
Merged model answers "How many orders are pending?":
  SELECT COUNT(*) FROM orders WHERE status = 'pending';

🏗️ Architecture

data/nl2sql.jsonl  (questions -> SQL over the VeloShop schema)
        |
        v
LoraFinetuning.AddDatasetFile   -->  dataset statistics + mask check
        |
        v
TrainToAdapter  --(FinetuningProgress: loss, validation)-->  veloshop-sql.gguf
        |                                                        |
        v                                                        v
ApplyLoraAdapter -> held-out eval             LoraMerger -> veloshop-sql-merged.gguf
                                                                 |
                                                                 v
                                                    standalone model answers SQL

⚙️ Getting Started

Prerequisites

  • .NET 8.0 SDK or later.
  • First run downloads qwen3.5:0.8b (about 600 MB).
  • A CUDA GPU makes training an order of magnitude faster; CPU works.

Download

Clone the samples repository and open the demo folder console_net/model-optimization/fine-tuning/text_to_sql_fine_tuning.

Run

dotnet run -c Release

🔧 Troubleshooting

  • Out of memory during training: lower MicroBatchSize, reduce the rank, or target attention modules only.
  • Held-out score plateaus: add paraphrases per question family; raise epochs and watch the validation loss for the point of diminishing returns.
  • UnmaskedSampleCount warning: the dataset roles do not render cleanly through the model's chat template; fix the dataset before training.

🚀 Extend the Demo

  • Replace data/nl2sql.jsonl with question-to-SQL pairs over your real schema; keep the system prompt identical between training and inference.
  • Ship the 25 MB adapter instead of the merged model when one base serves several tasks (see the LoRA Adapter Hot-Swap demo).
  • Add execution-based evaluation against a real database for a stricter metric.

📚 Additional Resources

Share