feat: fine-tuned model + GGUF export pipeline fixes
Browse files- Swap to fine-tuned model: build-small-hackathon/lfed-qwen2.5-coder-7b-sql-gguf
- Fix modal_app.py: add_local_dir for scripts, shutil.move for cross-fs
- Fix train.py: save_strategy=no + pickle error catch
- Fix export_gguf.py: FP16 merge via PeftModel, llama.cpp converter, force cleanup
- Fix model_inference.py: point to fine-tuned GGUF
- docs/DATA_RESEARCH.md +285 -0
- docs/EXPANSION_PLAN.md +316 -0
- docs/HACKATHON_PLAN_V2.md +257 -0
- docs/TRAINING_PLAYBOOK.md +187 -0
- modal_train/export_gguf.py +75 -63
- modal_train/modal_app.py +28 -9
- modal_train/train.py +32 -5
- model_inference.py +2 -2
docs/DATA_RESEARCH.md
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Deep Research: Text-to-SQL Training Data for Education Domain
|
| 2 |
+
|
| 3 |
+
> **Date:** 2026-06-08
|
| 4 |
+
> **Goal:** Find existing datasets, proven approaches, and the best path for building a high-quality NL→SQL training set for K-12 school district analytics
|
| 5 |
+
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
## Part 1: Existing Datasets That Do This Work
|
| 9 |
+
|
| 10 |
+
### Tier 1: Large-Scale Text-to-SQL Datasets (General Domain)
|
| 11 |
+
|
| 12 |
+
These are the big ones. None are education-specific, but they contain transferable patterns.
|
| 13 |
+
|
| 14 |
+
#### 1. gretelai/synthetic_text_to_sql (HuggingFace)
|
| 15 |
+
- **Size:** 106K pairs (100K train / 5.8K test)
|
| 16 |
+
- **Domains:** 100 domains including healthcare, government, education-related
|
| 17 |
+
- **Complexity:** Basic SQL through window functions, CTEs, multi-joins
|
| 18 |
+
- **Format:** Each row has: domain, sql_prompt (NL question), sql_context (CREATE TABLE + INSERT), sql (answer), sql_explanation
|
| 19 |
+
- **License:** Apache 2.0
|
| 20 |
+
- **Why it matters:** This is THE standard dataset people use to fine-tune text-to-SQL models. Rubrik/Predibase used it to fine-tune Llama-3-8B to outperform GPT-4 on SQL tasks.
|
| 21 |
+
- **Relevance:** HIGH — contains the SQL patterns (aggregation, joins, GROUP BY, CASE WHEN, HAVING, window functions) you need. But the schemas are generic, not education-specific.
|
| 22 |
+
- **How to use:** Filter for domains closest to education, then combine with your own education-specific pairs.
|
| 23 |
+
|
| 24 |
+
#### 2. NumbersStation/NSText2SQL (HuggingFace)
|
| 25 |
+
- **Size:** 289K pairs
|
| 26 |
+
- **Sources:** 26 different public datasets merged (Spider, CoSQL, SparC, WikiSQL, etc.)
|
| 27 |
+
- **Format:** instruction (schema + question) → output (SQL)
|
| 28 |
+
- **License:** Various (curated from permissive sources)
|
| 29 |
+
- **Why it matters:** Largest merged text-to-SQL corpus. Contains course/student/enroll schemas from Spider that are closest to education.
|
| 30 |
+
- **Relevance:** MEDIUM — has some education-adjacent schemas (courses, enrollment, professors) but not K-12 specific.
|
| 31 |
+
|
| 32 |
+
#### 3. OmniSQL / SynSQL-2.5M (HuggingFace + GitHub)
|
| 33 |
+
- **Size:** 2.5 MILLION pairs across 16,575 schemas
|
| 34 |
+
- **Approach:** LLM generates schemas, then synthesizes NL questions + SQL
|
| 35 |
+
- **Paper:** VLDB 2025
|
| 36 |
+
- **Why it matters:** First million-scale text-to-SQL dataset. Their synthesis pipeline is open-source — you could adapt it for education schemas.
|
| 37 |
+
- **Relevance:** MEDIUM — the pipeline (schema → question → SQL → validation) is exactly what you need to replicate for education.
|
| 38 |
+
|
| 39 |
+
#### 4. SQaLe (trl-lab/SQaLe-text-to-SQL-dataset)
|
| 40 |
+
- **Size:** 517K validated triples from 135,875 schemas
|
| 41 |
+
- **Approach:** Start with real schemas from SchemaPile, extend with LLM, generate questions from Spider/BIRD examples, validate via execution
|
| 42 |
+
- **Why it matters:** Proves that schema-variety matters more than raw volume. 135K schemas beat datasets with 10x more pairs on fewer schemas.
|
| 43 |
+
- **Relevance:** MEDIUM — methodology is spot-on, but no education schemas specifically.
|
| 44 |
+
|
| 45 |
+
#### 5. Spider + BIRD Benchmarks
|
| 46 |
+
- **Spider:** 10K questions, 200 databases, 138 domains. Used as the standard benchmark.
|
| 47 |
+
- **BIRD:** 12K questions, 95 databases. Focuses on real-world noisy data.
|
| 48 |
+
- **Why they matter:** These define what "good" looks like. Every serious text-to-SQL paper benchmarks against them.
|
| 49 |
+
- **Relevance:** LOW for direct use (small, no education focus), but HIGH for understanding what evaluation metrics matter.
|
| 50 |
+
|
| 51 |
+
### Tier 2: Education-Specific Raw Data (No NL→SQL Pairs)
|
| 52 |
+
|
| 53 |
+
These have the DATA but not the training pairs. You'd need to generate NL→SQL from them.
|
| 54 |
+
|
| 55 |
+
#### 6. Kaggle: Student Performance Data Set
|
| 56 |
+
- **Source:** UCI ML Repository, 2 Portuguese schools
|
| 57 |
+
- **Size:** 649 students, 33 columns
|
| 58 |
+
- **Columns:** grades (G1, G2, G3), demographics (sex, age, address), parental education, study time, failures, absences, social factors
|
| 59 |
+
- **License:** CC0 Public Domain
|
| 60 |
+
- **Relevance:** HIGH — closest to your use case. Grades + demographics + attendance. But it's flat (one table), not normalized like a real SIS.
|
| 61 |
+
|
| 62 |
+
#### 7. Kaggle: Sample Highschool Database
|
| 63 |
+
- **Source:** Student project
|
| 64 |
+
- **Size:** 1,000 students
|
| 65 |
+
- **Format:** SQL database file
|
| 66 |
+
- **Relevance:** MEDIUM — has normalized tables but limited scope.
|
| 67 |
+
|
| 68 |
+
#### 8. California Department of Education (CDE) Downloadable Data
|
| 69 |
+
- **URL:** https://www.cde.ca.gov/ds/ad/downloadabledata.asp
|
| 70 |
+
- **Data:** Enrollment, demographics, test scores, graduation rates, discipline, absenteeism, staff, financials — all at school/district/county/state level
|
| 71 |
+
- **Format:** CSV files, updated annually
|
| 72 |
+
- **License:** Public (CA government data)
|
| 73 |
+
- **Relevance:** VERY HIGH — this is REAL data from the exact domain you serve. You could build a realistic seed database from CDE data and generate NL→SQL pairs against it.
|
| 74 |
+
|
| 75 |
+
#### 9. NCES (National Center for Education Statistics) DataLab
|
| 76 |
+
- **URL:** https://nces.ed.gov/datalab/
|
| 77 |
+
- **Data:** National education data, PowerStats tool
|
| 78 |
+
- **Relevance:** HIGH — federal-level education data, complementary to CDE.
|
| 79 |
+
|
| 80 |
+
#### 10. Ed-Data.org
|
| 81 |
+
- **URL:** https://www.ed-data.org/
|
| 82 |
+
- **Data:** CA school/district profiles, financial data, test scores
|
| 83 |
+
- **Relevance:** HIGH — partnership of CDE + EdSource, already structured for queries.
|
| 84 |
+
|
| 85 |
+
---
|
| 86 |
+
|
| 87 |
+
## Part 2: Proven Approaches for Generating NL→SQL Training Data
|
| 88 |
+
|
| 89 |
+
### Approach A: Template-Based (What You're Doing Now)
|
| 90 |
+
|
| 91 |
+
**How it works:** Write SQL templates with placeholders, parameterize with real values, generate NL from templates.
|
| 92 |
+
|
| 93 |
+
**Pros:**
|
| 94 |
+
- 100% accurate SQL (you write it)
|
| 95 |
+
- Cheap, fast, deterministic
|
| 96 |
+
- Full control over coverage
|
| 97 |
+
|
| 98 |
+
**Cons:**
|
| 99 |
+
- Questions feel synthetic/stilted
|
| 100 |
+
- Limited by your imagination
|
| 101 |
+
- Hard to scale past ~2K pairs without exhaustion
|
| 102 |
+
- Doesn't train for ambiguity, typos, or real-world messiness
|
| 103 |
+
|
| 104 |
+
**Your current state:** 1,289 pairs from 32 templates. Good start, but ceiling is ~2K.
|
| 105 |
+
|
| 106 |
+
### Approach B: LLM-Augmented Synthesis (Recommended Next Step)
|
| 107 |
+
|
| 108 |
+
**How it works:** Use a powerful LLM (GPT-4, Claude, Qwen-72B) to generate NL→SQL pairs from your schema.
|
| 109 |
+
|
| 110 |
+
**Proven by:**
|
| 111 |
+
- OmniSQL (2.5M pairs via LLM synthesis)
|
| 112 |
+
- SQaLe (517K pairs via LLM + validation)
|
| 113 |
+
- SING-SQL (Bilkent University, 2025) — specifically designed for single-database in-domain training
|
| 114 |
+
|
| 115 |
+
**The SING-SQL pipeline is the most relevant to your case:**
|
| 116 |
+
1. Take your database schema
|
| 117 |
+
2. Partition schema into sub-schemas (e.g., attendance-only, grades+demographics, cross-table)
|
| 118 |
+
3. For each sub-schema, have LLM generate SQL queries at multiple complexity levels (basic SELECT → aggregation → joins → window functions → CTEs)
|
| 119 |
+
4. For each SQL, have LLM generate the NL question
|
| 120 |
+
5. Validate: run SQL against real data, check it executes
|
| 121 |
+
6. LLM-as-judge: have another LLM verify Q↔SQL match
|
| 122 |
+
7. Auto-repair broken queries
|
| 123 |
+
8. Balance column coverage (ensure all columns get queried)
|
| 124 |
+
|
| 125 |
+
**SING-SQL results:** Their 3B model (fine-tuned on synthetic data) hit 82.87% Soft F1 on BIRD — beating prior 3B baselines by +16 points.
|
| 126 |
+
|
| 127 |
+
**Pros:**
|
| 128 |
+
- Scales to 10K-100K+ pairs
|
| 129 |
+
- Questions sound natural
|
| 130 |
+
- Covers SQL patterns you wouldn't think of
|
| 131 |
+
- Validated against real data
|
| 132 |
+
|
| 133 |
+
**Cons:**
|
| 134 |
+
- Needs API credits (or local LLM)
|
| 135 |
+
- Some generated SQL will be wrong (need validation)
|
| 136 |
+
- May generate SQL for impossible queries
|
| 137 |
+
|
| 138 |
+
### Approach C: Hybrid (Best for Your Timeline)
|
| 139 |
+
|
| 140 |
+
Combine templates for known patterns + LLM for natural variation + real data for seed realism.
|
| 141 |
+
|
| 142 |
+
**Recommended pipeline:**
|
| 143 |
+
1. **Foundation:** Your existing 1,289 template pairs (keep these — they're 100% accurate)
|
| 144 |
+
2. **Schema expansion:** Add grades, discipline, demographics, assessments, programs tables
|
| 145 |
+
3. **Seed with real data:** Download CDE data, build realistic seed database
|
| 146 |
+
4. **LLM augmentation:** Use Qwen2.5-72B (free on Modal or via HF Inference) to generate 5,000 new NL→SQL pairs
|
| 147 |
+
5. **Rephrasing:** For each of the 6,000+ pairs, generate 3-5 NL phrasings (formal, casual, abbreviated, typo-prone)
|
| 148 |
+
6. **Validation:** Run every SQL against seed data, discard failures
|
| 149 |
+
7. **Mix with Gretel:** Filter gretelai/synthetic_text_to_sql for relevant domains (government, healthcare, HR) and add 2,000 pairs as general SQL knowledge
|
| 150 |
+
|
| 151 |
+
**Target:** 15,000-25,000 validated pairs
|
| 152 |
+
|
| 153 |
+
---
|
| 154 |
+
|
| 155 |
+
## Part 3: Specific Recommendations for Your Hackathon
|
| 156 |
+
|
| 157 |
+
### Timeline Reality Check
|
| 158 |
+
|
| 159 |
+
You have until June 15. That's 7 days. Here's what's realistic:
|
| 160 |
+
|
| 161 |
+
| Approach | Pairs | Time | Quality | Recommended? |
|
| 162 |
+
|---|---|---|---|---|
|
| 163 |
+
| Templates only (current) | ~2K | Already done | High accuracy, low diversity | Keep as base |
|
| 164 |
+
| + LLM synthesis (local Qwen-72B or Modal) | +5K | 2-3 days | Medium-high | YES — best ROI |
|
| 165 |
+
| + Gretel dataset filtered | +2K | 1 day | Medium | YES — free, fast |
|
| 166 |
+
| + Rephrasing augmentation | ×3-5 | 1 day | Medium | YES — cheap multiplier |
|
| 167 |
+
| + CDE real data seed | N/A | 1 day | N/A | YES — makes everything more realistic |
|
| 168 |
+
| Full SING-SQL pipeline | +50K | 2 weeks | High | Too ambitious for hackathon |
|
| 169 |
+
|
| 170 |
+
### The Playbook (7-Day Plan)
|
| 171 |
+
|
| 172 |
+
**Day 1-2: Schema + Seed Data**
|
| 173 |
+
- Define 6 new tables (grades, discipline, demographics, assessments, programs, staff)
|
| 174 |
+
- Download CA Department of Education data files
|
| 175 |
+
- Build realistic seed database with 10K+ students
|
| 176 |
+
- Update prompts.py with expanded schema
|
| 177 |
+
|
| 178 |
+
**Day 3: LLM Synthesis**
|
| 179 |
+
- Use Qwen2.5-Coder-7B (or GPT-4 if you have credits) to generate NL→SQL pairs
|
| 180 |
+
- Prompt: "Given this DuckDB schema [schema], generate a natural language question and its corresponding SQL query. Complexity: [basic/aggregation/join/window]. Focus on: [table_name]."
|
| 181 |
+
- Target: 5,000 pairs across all tables
|
| 182 |
+
|
| 183 |
+
**Day 4: Validation + Filtering**
|
| 184 |
+
- Run every generated SQL against the seed database
|
| 185 |
+
- Discard any that fail to execute
|
| 186 |
+
- Discard any that return 0 rows (unless that's the expected answer)
|
| 187 |
+
- Verify column references match schema
|
| 188 |
+
|
| 189 |
+
**Day 5: Rephrasing + Augmentation**
|
| 190 |
+
- For each validated pair, generate 3-5 NL rephrasings
|
| 191 |
+
- Add typo variants ("Whats the avg gpa" instead of "What is the average GPA")
|
| 192 |
+
- Add informal variants ("How are our kids doing on tests?" for assessment queries)
|
| 193 |
+
- Filter Gretel dataset for relevant domains, add 1,500-2,000 pairs
|
| 194 |
+
|
| 195 |
+
**Day 6: Train v2**
|
| 196 |
+
- Update training config: lower LR (1e-4), higher LoRA rank (32), longer seq (4096)
|
| 197 |
+
- Train on expanded dataset (~15K-20K pairs)
|
| 198 |
+
- Monitor for overfitting
|
| 199 |
+
|
| 200 |
+
**Day 7: Evaluate + Deploy**
|
| 201 |
+
- Test v1 vs v2 on holdout questions
|
| 202 |
+
- Deploy to HF Space
|
| 203 |
+
- Smoke test with real-world questions
|
| 204 |
+
|
| 205 |
+
### Quick Wins You Can Do Today
|
| 206 |
+
|
| 207 |
+
1. **Download Gretel dataset** — it's free, Apache 2.0, and ready to use:
|
| 208 |
+
```python
|
| 209 |
+
from datasets import load_dataset
|
| 210 |
+
ds = load_dataset("gretelai/synthetic_text_to_sql")
|
| 211 |
+
# Filter for relevant domains
|
| 212 |
+
relevant = ds['train'].filter(lambda x: x['domain'] in [
|
| 213 |
+
'education', 'government', 'public health', 'human resources',
|
| 214 |
+
'insurance', 'social services', 'nonprofit'
|
| 215 |
+
])
|
| 216 |
+
```
|
| 217 |
+
|
| 218 |
+
2. **Download CDE data** — real CA school data:
|
| 219 |
+
https://www.cde.ca.gov/ds/ad/downloadabledata.asp
|
| 220 |
+
Key files: enrollment, absenteeism, demographics, test scores, discipline
|
| 221 |
+
|
| 222 |
+
3. **Look at OmniSQL's synthesis code** — open source, can adapt for your schema:
|
| 223 |
+
https://github.com/RUCKBReasoning/OmniSQL/tree/main/data_synthesis
|
| 224 |
+
|
| 225 |
+
---
|
| 226 |
+
|
| 227 |
+
## Part 4: What the Kaggle Education Datasets Are (and Why They're Different)
|
| 228 |
+
|
| 229 |
+
You mentioned seeing datasets on Kaggle. Here's what's there and why they're not quite right:
|
| 230 |
+
|
| 231 |
+
| Dataset | What It Is | Why It's Different |
|
| 232 |
+
|---|---|---|
|
| 233 |
+
| Student Performance (UCI) | 649 students, flat CSV, Portuguese schools | Raw data, no NL→SQL pairs, foreign schools |
|
| 234 |
+
| Student Information | 200 students, 7 attributes | Tiny, toy dataset for SQL practice |
|
| 235 |
+
| Student Exam Performance | Demographics + test scores | Flat analysis dataset, not text-to-SQL |
|
| 236 |
+
| Sample Highschool Database | SQL file, 1K students | Closer but limited scope, no NL pairs |
|
| 237 |
+
|
| 238 |
+
**The gap:** Kaggle has education DATA but not education TEXT-TO-SQL TRAINING DATA. Nobody has done the work of turning education data into NL→SQL training pairs at scale. That's your opportunity.
|
| 239 |
+
|
| 240 |
+
---
|
| 241 |
+
|
| 242 |
+
## Part 5: The Big Opportunity
|
| 243 |
+
|
| 244 |
+
Nobody has built a production-quality text-to-SQL model specifically for K-12 school district analytics. This is a real gap:
|
| 245 |
+
|
| 246 |
+
- **Gretel** covers 100 domains but education is generic
|
| 247 |
+
- **Spider/BIRD** have no education schemas
|
| 248 |
+
- **OmniSQL/SQaLe** generate across all domains but not deep on education
|
| 249 |
+
- **Vanna AI** does RAG-based text-to-SQL but requires you to provide training pairs
|
| 250 |
+
|
| 251 |
+
If you build this dataset well — with real CA education data, realistic schemas, validated NL→SQL pairs covering enrollment, attendance, grades, discipline, demographics, assessments, programs — you'd have something genuinely valuable beyond the hackathon:
|
| 252 |
+
|
| 253 |
+
1. **Hackathon submission:** Fine-tuned model that does K-12 analytics
|
| 254 |
+
2. **Open-source dataset:** First high-quality education text-to-SQL dataset
|
| 255 |
+
3. **Product differentiator:** LTC can offer this to CA school districts
|
| 256 |
+
4. **Community contribution:** Publish to HuggingFace, get visibility
|
| 257 |
+
|
| 258 |
+
---
|
| 259 |
+
|
| 260 |
+
## Appendix: Key Resources
|
| 261 |
+
|
| 262 |
+
### Datasets
|
| 263 |
+
| Name | URL | Size | License |
|
| 264 |
+
|---|---|---|---|
|
| 265 |
+
| gretelai/synthetic_text_to_sql | huggingface.co/datasets/gretelai/synthetic_text_to_sql | 106K | Apache 2.0 |
|
| 266 |
+
| NumbersStation/NSText2SQL | huggingface.co/datasets/NumbersStation/NSText2SQL | 289K | Various |
|
| 267 |
+
| OmniSQL/SynSQL-2.5M | github.com/RUCKBReasoning/OmniSQL | 2.5M | Check repo |
|
| 268 |
+
| SQaLe | huggingface.co/datasets/trl-lab/SQaLe-text-to-SQL-dataset | 517K | Check paper |
|
| 269 |
+
| Student Performance (UCI) | kaggle.com/datasets/larsen0966/student-performance-data-set | 649 | CC0 |
|
| 270 |
+
| CDE Downloadable Data | cde.ca.gov/ds/ad/downloadabledata.asp | Real data | Public |
|
| 271 |
+
|
| 272 |
+
### Papers/Frameworks
|
| 273 |
+
| Name | What It Does | URL |
|
| 274 |
+
|---|---|---|
|
| 275 |
+
| SING-SQL | Best framework for single-database in-domain training | github.com/HasanAlpCaferoglu/SING-SQL |
|
| 276 |
+
| OmniSQL | Million-scale synthesis pipeline | github.com/RUCKBReasoning/OmniSQL |
|
| 277 |
+
| SQaLe | Schema-variety-driven dataset | huggingface.co/blog/cwolff/sqale |
|
| 278 |
+
| Vanna AI | RAG-based text-to-SQL (alternative to fine-tuning) | github.com/vanna-ai/vanna |
|
| 279 |
+
|
| 280 |
+
### Tutorials
|
| 281 |
+
| Name | What It Covers | URL |
|
| 282 |
+
|---|---|---|
|
| 283 |
+
| Rubrik + Gretel + Predibase | Full fine-tune tutorial with Gretel data | rubrik.com/blog/ai/24/... |
|
| 284 |
+
| Google Gemma QLoRA | Fine-tune Gemma on text-to-SQL with QLoRA | ai.google.dev/gemma/docs/core/huggingface_text_finetune_qlora |
|
| 285 |
+
| Towards AI GRPO series | 60 training sessions, Qwen2.5-Coder experiments | pub.towardsai.net/fine-tuning-open-source-llms-for-text-to-sql |
|
docs/EXPANSION_PLAN.md
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# LFED Training Data Expansion Plan
|
| 2 |
+
|
| 3 |
+
> **Last updated:** 2026-06-08
|
| 4 |
+
> **Goal:** Expand from 1,289 pairs on 2 tables → 10,000+ pairs across 8+ analytics domains
|
| 5 |
+
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
## Current State
|
| 9 |
+
|
| 10 |
+
### What We Have Now
|
| 11 |
+
- **Tables:** 2 (enrollment, attendance)
|
| 12 |
+
- **Training pairs:** 1,289 (template-generated)
|
| 13 |
+
- **Templates:** 32
|
| 14 |
+
- **Coverage:** Chronic absenteeism, enrollment counts, absence rates
|
| 15 |
+
- **Quality:** Good for narrow domain, but synthetic-feeling
|
| 16 |
+
|
| 17 |
+
### What's in local-data-stack (Untapped Analytics Domains)
|
| 18 |
+
|
| 19 |
+
From `/Users/flucido/projects/local-data-stack/rill_project/data/`:
|
| 20 |
+
|
| 21 |
+
| Domain | Table | Rows | Key Columns |
|
| 22 |
+
|---|---|---|---|
|
| 23 |
+
| Chronic Absenteeism Risk | chronic_absenteeism_risk | 1,700 | risk_score, risk_level, attendance_rate_30d/90d, discipline_incidents, demographics |
|
| 24 |
+
| Student Wellbeing | wellbeing_risk_profiles | 1,700 | attendance/discipline/academic_risk_scores, wellbeing_risk_level, primary_concern |
|
| 25 |
+
| Performance Correlations | performance_correlations | 3 | correlation_pair, coefficient, strength |
|
| 26 |
+
| Class Effectiveness | class_effectiveness | 300 | avg_grade, pct_passed, effectiveness_rating, ELL/SpEd/FRL pass rates |
|
| 27 |
+
| Equity Outcomes | equity_outcomes_by_demographics | 10 | race_ethnicity, ELL, SpEd, FRL, avg_gpa, pct_below_c |
|
| 28 |
+
|
| 29 |
+
---
|
| 30 |
+
|
| 31 |
+
## Phase 1: Schema Expansion (Add 6 New Tables)
|
| 32 |
+
|
| 33 |
+
Add these tables to the DuckDB seed data and update prompts.py:
|
| 34 |
+
|
| 35 |
+
### 1.1 grades
|
| 36 |
+
```sql
|
| 37 |
+
CREATE TABLE grades (
|
| 38 |
+
student_id INTEGER,
|
| 39 |
+
school_name VARCHAR,
|
| 40 |
+
school_year VARCHAR,
|
| 41 |
+
grade_level INTEGER,
|
| 42 |
+
course_name VARCHAR,
|
| 43 |
+
term VARCHAR, -- 'Fall', 'Spring'
|
| 44 |
+
letter_grade VARCHAR, -- 'A', 'B', 'C', 'D', 'F'
|
| 45 |
+
grade_numeric DOUBLE, -- 4.0, 3.0, etc.
|
| 46 |
+
gpa DOUBLE,
|
| 47 |
+
credit_hours DOUBLE
|
| 48 |
+
);
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
### 1.2 discipline
|
| 52 |
+
```sql
|
| 53 |
+
CREATE TABLE discipline (
|
| 54 |
+
incident_id INTEGER,
|
| 55 |
+
student_id INTEGER,
|
| 56 |
+
school_name VARCHAR,
|
| 57 |
+
school_year VARCHAR,
|
| 58 |
+
grade_level INTEGER,
|
| 59 |
+
incident_type VARCHAR, -- 'Defiance', 'Fighting', 'Vandalism', 'Substance', 'Bullying'
|
| 60 |
+
incident_date DATE,
|
| 61 |
+
severity VARCHAR, -- 'Minor', 'Major', 'Severe'
|
| 62 |
+
action_taken VARCHAR, -- 'Warning', 'Detention', 'Suspension', 'Expulsion'
|
| 63 |
+
days_suspended INTEGER
|
| 64 |
+
);
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
### 1.3 demographics
|
| 68 |
+
```sql
|
| 69 |
+
CREATE TABLE demographics (
|
| 70 |
+
student_id INTEGER,
|
| 71 |
+
school_name VARCHAR,
|
| 72 |
+
school_year VARCHAR,
|
| 73 |
+
grade_level INTEGER,
|
| 74 |
+
gender VARCHAR,
|
| 75 |
+
race_ethnicity VARCHAR,
|
| 76 |
+
english_learner BOOLEAN,
|
| 77 |
+
special_education BOOLEAN,
|
| 78 |
+
economically_disadvantaged BOOLEAN,
|
| 79 |
+
homeless_flag BOOLEAN,
|
| 80 |
+
migrant_flag BOOLEAN,
|
| 81 |
+
foster_youth BOOLEAN
|
| 82 |
+
);
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
### 1.4 assessments
|
| 86 |
+
```sql
|
| 87 |
+
CREATE TABLE assessments (
|
| 88 |
+
student_id INTEGER,
|
| 89 |
+
school_name VARCHAR,
|
| 90 |
+
school_year VARCHAR,
|
| 91 |
+
grade_level INTEGER,
|
| 92 |
+
assessment_type VARCHAR, -- 'SBAC', 'CAASPP', 'CELCAST', 'District Benchmark'
|
| 93 |
+
subject VARCHAR, -- 'ELA', 'Math', 'Science'
|
| 94 |
+
score DOUBLE,
|
| 95 |
+
proficiency_level VARCHAR, -- 'Below Standard', 'Near Standard', 'At/Above Standard'
|
| 96 |
+
growth_percentile INTEGER
|
| 97 |
+
);
|
| 98 |
+
```
|
| 99 |
+
|
| 100 |
+
### 1.5 programs
|
| 101 |
+
```sql
|
| 102 |
+
CREATE TABLE programs (
|
| 103 |
+
student_id INTEGER,
|
| 104 |
+
school_name VARCHAR,
|
| 105 |
+
school_year VARCHAR,
|
| 106 |
+
program_type VARCHAR, -- 'Title I', 'ELL Support', 'SpEd IEP', '504 Plan', 'MTSS Tier 1/2/3'
|
| 107 |
+
start_date DATE,
|
| 108 |
+
end_date DATE,
|
| 109 |
+
status VARCHAR -- 'Active', 'Exited', 'Transferred'
|
| 110 |
+
);
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
### 1.6 staff
|
| 114 |
+
```sql
|
| 115 |
+
CREATE TABLE staff (
|
| 116 |
+
staff_id INTEGER,
|
| 117 |
+
school_name VARCHAR,
|
| 118 |
+
school_year VARCHAR,
|
| 119 |
+
role VARCHAR, -- 'Teacher', 'Counselor', 'Admin', 'Aide'
|
| 120 |
+
subject_area VARCHAR,
|
| 121 |
+
years_experience INTEGER,
|
| 122 |
+
credential_type VARCHAR,
|
| 123 |
+
student_load INTEGER
|
| 124 |
+
);
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
---
|
| 128 |
+
|
| 129 |
+
## Phase 2: Training Data Expansion (1,289 → 10,000+ pairs)
|
| 130 |
+
|
| 131 |
+
### 2.1 New Template Categories (add ~150 templates)
|
| 132 |
+
|
| 133 |
+
#### Grades & GPA (~30 templates)
|
| 134 |
+
- Average GPA by school/grade/demographic
|
| 135 |
+
- Grade distribution (A/B/C/D/F counts and percentages)
|
| 136 |
+
- GPA trends over time
|
| 137 |
+
- Failing rate by course/teacher
|
| 138 |
+
- GPA comparison between schools
|
| 139 |
+
- Students below 2.0 GPA
|
| 140 |
+
- Honor roll counts
|
| 141 |
+
|
| 142 |
+
#### Discipline (~30 templates)
|
| 143 |
+
- Incident counts by type, school, year
|
| 144 |
+
- Suspension rates by demographic
|
| 145 |
+
- Discipline trends over time
|
| 146 |
+
- Most common incident types
|
| 147 |
+
- Students with multiple incidents
|
| 148 |
+
- Days lost to suspension by school
|
| 149 |
+
- Discipline correlation with attendance
|
| 150 |
+
|
| 151 |
+
#### Demographics (~20 templates)
|
| 152 |
+
- Enrollment by race/ethnicity
|
| 153 |
+
- ELL student counts and percentages
|
| 154 |
+
- SpEd population by school
|
| 155 |
+
- Economically disadvantaged rates
|
| 156 |
+
- Foster/homeless student counts
|
| 157 |
+
- Demographic breakdowns of outcomes
|
| 158 |
+
|
| 159 |
+
#### Assessments (~30 templates)
|
| 160 |
+
- Proficiency rates by subject and school
|
| 161 |
+
- Growth percentiles by grade
|
| 162 |
+
- Assessment score trends
|
| 163 |
+
- Below-standard student counts
|
| 164 |
+
- Demographic gaps in test scores
|
| 165 |
+
- School performance rankings
|
| 166 |
+
|
| 167 |
+
#### Programs (~20 templates)
|
| 168 |
+
- Active program counts by type
|
| 169 |
+
- MTSS tier distribution
|
| 170 |
+
- IEP/504 plan counts
|
| 171 |
+
- Program participation by school
|
| 172 |
+
- Title I eligible counts
|
| 173 |
+
|
| 174 |
+
#### Cross-Table Joins (~20 templates)
|
| 175 |
+
- Attendance vs. grades correlation
|
| 176 |
+
- Discipline incidents vs. GPA
|
| 177 |
+
- ELL status vs. assessment scores
|
| 178 |
+
- SpEd vs. chronic absenteeism
|
| 179 |
+
- Program participation vs. outcomes
|
| 180 |
+
|
| 181 |
+
### 2.2 Data Augmentation Strategies
|
| 182 |
+
|
| 183 |
+
#### A. Rephrasing (3-5x multiplier)
|
| 184 |
+
For each template, generate additional natural-language phrasings:
|
| 185 |
+
- **Formal:** "What is the average GPA for 9th graders at Jefferson High?"
|
| 186 |
+
- **Informal:** "What's the avg GPA for freshmen at Jefferson?"
|
| 187 |
+
- **Abbreviated:** "9th grade GPA at Jefferson High?"
|
| 188 |
+
- **Typo-prone:** "Whats the avg gpa for 9th graders at jefferson hiogh?"
|
| 189 |
+
- **Context-rich:** "I'm preparing for the board meeting — need 9th grade GPA at Jefferson High for 2023-2024"
|
| 190 |
+
|
| 191 |
+
Implementation: Use a small LLM (Qwen2.5-1.5B) to rephrase each template's question while keeping the SQL identical.
|
| 192 |
+
|
| 193 |
+
#### B. Question Decomposition
|
| 194 |
+
Train on multi-part questions:
|
| 195 |
+
- Q: "Compare chronic absenteeism rates and average GPA between Lincoln Elementary and Jefferson High"
|
| 196 |
+
- SQL: Two CTEs or UNION ALL
|
| 197 |
+
|
| 198 |
+
#### C. Ambiguous Questions
|
| 199 |
+
Train the model to ask clarifying questions or make reasonable assumptions:
|
| 200 |
+
- Q: "How are our students doing?"
|
| 201 |
+
- SQL: SELECT school_name, AVG(gpa) ... (with a reasonable default)
|
| 202 |
+
|
| 203 |
+
#### D. Error Recovery
|
| 204 |
+
Train on edge cases:
|
| 205 |
+
- Questions referencing non-existent columns → model should generate closest valid query
|
| 206 |
+
- Questions about data that doesn't exist → model should return empty result gracefully
|
| 207 |
+
|
| 208 |
+
### 2.3 Seed Data Expansion
|
| 209 |
+
|
| 210 |
+
Current: 2,900 students, 5 schools, 4 years
|
| 211 |
+
|
| 212 |
+
Expand to:
|
| 213 |
+
- **10,000 students** across 8 schools
|
| 214 |
+
- **6 school years** (2019-2025)
|
| 215 |
+
- **Realistic distributions:**
|
| 216 |
+
- Chronic absenteeism: 15% (current) — keep
|
| 217 |
+
- GPA distribution: normal around 2.8, std 0.8
|
| 218 |
+
- Discipline: 8% of students with 1+ incident
|
| 219 |
+
- ELL: 18%, SpEd: 12%, FRL: 45%
|
| 220 |
+
- Assessment proficiency: 55% at/above standard
|
| 221 |
+
|
| 222 |
+
### 2.4 Quality Assurance Pipeline
|
| 223 |
+
|
| 224 |
+
1. **SQL Validation:** Run every generated SQL against seed data — must return results (or empty for legitimate queries)
|
| 225 |
+
2. **Schema Match:** Every column/table referenced must exist in schema
|
| 226 |
+
3. **Dedup:** Exact-match dedup on questions, near-match dedup on SQL structure
|
| 227 |
+
4. **Balance Check:** Ensure even coverage across all tables and query patterns
|
| 228 |
+
5. **Human Review:** Sample 5% of pairs for manual review
|
| 229 |
+
|
| 230 |
+
---
|
| 231 |
+
|
| 232 |
+
## Phase 3: Training Improvements
|
| 233 |
+
|
| 234 |
+
### 3.1 Data Quality
|
| 235 |
+
| Current | Target |
|
| 236 |
+
|---|---|
|
| 237 |
+
| 1,289 pairs | 10,000+ pairs |
|
| 238 |
+
| 2 tables | 8 tables |
|
| 239 |
+
| 32 templates | 150+ templates |
|
| 240 |
+
| Template-only questions | Template + LLM-rephrased + typo variants |
|
| 241 |
+
| No join queries | 20% multi-table joins |
|
| 242 |
+
| No ambiguous queries | 5% ambiguous/clarification-needed |
|
| 243 |
+
|
| 244 |
+
### 3.2 Training Config Improvements
|
| 245 |
+
|
| 246 |
+
| Param | Current | Proposed | Why |
|
| 247 |
+
|---|---|---|---|
|
| 248 |
+
| Epochs | 3 | 2 | More data needs fewer epochs to avoid overfitting |
|
| 249 |
+
| Learning rate | 2e-4 | 1e-4 | More data = can use lower LR for better convergence |
|
| 250 |
+
| LoRA rank | 16 | 32 | More data supports higher rank without overfitting |
|
| 251 |
+
| Max seq length | 2048 | 4096 | Multi-table joins need longer sequences |
|
| 252 |
+
| Batch size | 4×4=16 | 4×8=32 | Larger effective batch for larger dataset |
|
| 253 |
+
|
| 254 |
+
### 3.3 Evaluation Metrics
|
| 255 |
+
|
| 256 |
+
Add eval set (10% holdout):
|
| 257 |
+
- **Exact match:** Generated SQL matches expected SQL
|
| 258 |
+
- **Execution match:** Generated SQL returns same results as expected
|
| 259 |
+
- **Schema validity:** All referenced columns/tables exist
|
| 260 |
+
- **Safety:** No DDL/DML statements generated
|
| 261 |
+
|
| 262 |
+
---
|
| 263 |
+
|
| 264 |
+
## Phase 4: Implementation Order
|
| 265 |
+
|
| 266 |
+
| Step | Task | Effort | Priority |
|
| 267 |
+
|---|---|---|---|
|
| 268 |
+
| 1 | Define new table schemas (above) | 1 hour | P0 |
|
| 269 |
+
| 2 | Generate seed data for 6 new tables | 2 hours | P0 |
|
| 270 |
+
| 3 | Add new templates for grades + discipline (60 templates) | 3 hours | P0 |
|
| 271 |
+
| 4 | Run synthetic generation → 5,000+ pairs | 30 min | P0 |
|
| 272 |
+
| 5 | SQL validation pass (run all pairs against seed data) | 1 hour | P0 |
|
| 273 |
+
| 6 | Add demographic + assessment templates (50 templates) | 2 hours | P1 |
|
| 274 |
+
| 7 | LLM rephrasing augmentation (3x multiplier) | 2 hours | P1 |
|
| 275 |
+
| 8 | Add cross-table join templates (20 templates) | 1 hour | P1 |
|
| 276 |
+
| 9 | Re-run generation → 10,000+ pairs | 30 min | P1 |
|
| 277 |
+
| 10 | Train v2 model with expanded data | 2-3 hours | P1 |
|
| 278 |
+
| 11 | Eval v1 vs v2 on holdout set | 1 hour | P2 |
|
| 279 |
+
| 12 | Deploy v2 to HF Space | 30 min | P2 |
|
| 280 |
+
|
| 281 |
+
**Total estimated effort:** ~15-18 hours of work
|
| 282 |
+
|
| 283 |
+
---
|
| 284 |
+
|
| 285 |
+
## Phase 5: Quick Wins (Do First)
|
| 286 |
+
|
| 287 |
+
### 5.1 Expand Seed Data (immediate)
|
| 288 |
+
Write a `generate_seed_v2.py` that creates all 6 new tables with realistic distributions. This unblocks everything else.
|
| 289 |
+
|
| 290 |
+
### 5.2 Port local-data-stack Patterns
|
| 291 |
+
The local-data-stack already has analytics models for:
|
| 292 |
+
- Chronic absenteeism risk scoring
|
| 293 |
+
- Wellbeing composite scores
|
| 294 |
+
- Class effectiveness ratings
|
| 295 |
+
- Equity outcome comparisons
|
| 296 |
+
|
| 297 |
+
Port these SQL patterns into training templates. The queries are already validated — just need NL phrasings.
|
| 298 |
+
|
| 299 |
+
### 5.3 Add Join Templates
|
| 300 |
+
The biggest gap in current training is zero join queries. Even 20 join templates would dramatically improve the model's ability to answer cross-domain questions.
|
| 301 |
+
|
| 302 |
+
---
|
| 303 |
+
|
| 304 |
+
## Appendix: local-data-stack Column Reference
|
| 305 |
+
|
| 306 |
+
### chronic_absenteeism_risk
|
| 307 |
+
student_key, grade_level, school_id, gender, race_ethnicity, english_learner, special_education, economically_disadvantaged, homeless_flag, attendance_rate_30d, unexcused_absence_rate_30d, discipline_incidents_30d, absence_discipline_correlation_score, attendance_rate_90d, attendance_trend_90d, chronic_absence_flag, chronic_absenteeism_risk_score, risk_level
|
| 308 |
+
|
| 309 |
+
### wellbeing_risk_profiles
|
| 310 |
+
student_key, grade_level, school_id, attendance_risk_score, discipline_risk_score, academic_risk_score, high_risk_domain_count, wellbeing_risk_score, wellbeing_risk_level, primary_concern
|
| 311 |
+
|
| 312 |
+
### class_effectiveness
|
| 313 |
+
course_id, school_id, grade_level, enrollment_count, avg_grade_numeric, pct_passed, pct_a_b_grades, course_avg_grade, grade_diff_from_course_avg, pct_passed_ell, pct_passed_sped, pct_passed_frl, pass_rate_rank, grade_rank, effectiveness_rating, term
|
| 314 |
+
|
| 315 |
+
### equity_outcomes_by_demographics
|
| 316 |
+
race_ethnicity, english_learner, special_education, economically_disadvantaged, cohort_size, pct_good_attendance, pct_no_discipline, avg_gpa, pct_gpa_2_5_plus, pct_below_c
|
docs/HACKATHON_PLAN_V2.md
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# LFED Hackathon Plan v2 — Concrete 7-Day Plan
|
| 2 |
+
|
| 3 |
+
> **Scope:** Single domain (attendance), local-first, M2 Mac 16GB
|
| 4 |
+
> **Credits:** $250 Modal
|
| 5 |
+
> **Deadline:** June 15, 2026
|
| 6 |
+
> **Philosophy:** Hackathon = skill-honing, not production launch
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## Decision: Model Selection
|
| 11 |
+
|
| 12 |
+
### Your Constraints
|
| 13 |
+
- M2 Mac, 16GB unified memory
|
| 14 |
+
- Local-first, no API calls at inference
|
| 15 |
+
- llama.cpp + GGUF format
|
| 16 |
+
- ~10-11GB usable for model (5GB reserved for OS + Gradio + DuckDB)
|
| 17 |
+
|
| 18 |
+
### Current Model: Qwen2.5-Coder-7B Q4_K_M (~4.4GB)
|
| 19 |
+
Good. Works. Leaves plenty of headroom. But 7B is the floor for complex SQL.
|
| 20 |
+
|
| 21 |
+
### Upgrade Options That Fit in 16GB
|
| 22 |
+
|
| 23 |
+
| Model | Params | Q4_K_M Size | SQL Skill | Coding | Notes |
|
| 24 |
+
|---|---|---|---|---|---|
|
| 25 |
+
| **Qwen2.5-Coder-7B** (current) | 7B | ~4.4 GB | Good | Great | Safe choice, proven |
|
| 26 |
+
| **Gemma 4 12B** | 12B | ~7.4 GB | Very good | Great | Brand new, Apache 2.0, multimodal, 256K ctx |
|
| 27 |
+
| **Qwen2.5-14B** | 14B | ~8.5 GB | Great | Great | Best in 16GB bracket per apxml.com |
|
| 28 |
+
| **Qwen2.5-Coder-14B** | 14B | ~8.5 GB | Great | Best | Coder variant, optimized for code/SQL |
|
| 29 |
+
| Gemma 4 E4B | 4.5B | ~3 GB | OK | Good | Too small for reliable SQL |
|
| 30 |
+
|
| 31 |
+
### Recommendation: Two-Model Strategy
|
| 32 |
+
|
| 33 |
+
**Inference model (runs on your Mac):** Qwen2.5-Coder-14B Q4_K_M
|
| 34 |
+
- 14B is the sweet spot for 16GB Mac — proven by multiple benchmarks
|
| 35 |
+
- Coder variant specifically trained on code/SQL tasks
|
| 36 |
+
- ~8.5GB leaves ~7.5GB for OS + app overhead
|
| 37 |
+
- Unsloth has GGUF versions ready to download
|
| 38 |
+
- Fine-tunable on Modal A10G (24GB fits 14B QLoRA)
|
| 39 |
+
|
| 40 |
+
**Data generation model (runs on Modal):** Qwen2.5-72B-Instruct on A100 80GB
|
| 41 |
+
- Much smarter — generates higher quality NL→SQL pairs
|
| 42 |
+
- Runs on Modal, not your Mac
|
| 43 |
+
- ~$2.50/hr, 3-4 hours = ~$8-10 to generate 10K+ pairs
|
| 44 |
+
|
| 45 |
+
**Why not Gemma 4 12B?**
|
| 46 |
+
- Excellent model, but Qwen2.5-Coder-14B is specifically trained for code/SQL
|
| 47 |
+
- Gemma 4 is newer (fewer fine-tuned GGUFs, less community testing for SQL)
|
| 48 |
+
- For a SQL-focused hackathon, the Coder variant wins
|
| 49 |
+
- Gemma 4 would be a great choice for a general-purpose app
|
| 50 |
+
|
| 51 |
+
**Why not stay with 7B?**
|
| 52 |
+
- You can — it works. But 14B at Q4_K_M fits your Mac with room to spare
|
| 53 |
+
- 14B handles complex queries (joins, subqueries, CASE WHEN) noticeably better
|
| 54 |
+
- The jump from 7B → 14B is the biggest quality gain per parameter in this range
|
| 55 |
+
|
| 56 |
+
---
|
| 57 |
+
|
| 58 |
+
## The Plan: 7 Days to Submission
|
| 59 |
+
|
| 60 |
+
### Day 1 (Today — June 8): Foundation
|
| 61 |
+
|
| 62 |
+
**Goal:** Expanded schema + seed data for attendance domain
|
| 63 |
+
|
| 64 |
+
1. Expand `data_engine.py` schema to include richer attendance tables:
|
| 65 |
+
- Keep existing: `enrollment`, `attendance`
|
| 66 |
+
- Add: `students` (demographics), `discipline` (incidents), `grades` (GPA)
|
| 67 |
+
- This gives the model more to work with while keeping scope tight
|
| 68 |
+
|
| 69 |
+
2. Generate expanded seed data (10K students, 8 schools, 6 years)
|
| 70 |
+
- Realistic distributions: 15% chronic absenteeism, demographic mix
|
| 71 |
+
- Use CA Department of Education patterns as reference
|
| 72 |
+
|
| 73 |
+
3. Update `prompts.py` with expanded schema docs
|
| 74 |
+
|
| 75 |
+
**Time:** ~3 hours
|
| 76 |
+
**Modal cost:** $0 (all local)
|
| 77 |
+
|
| 78 |
+
### Day 2 (June 9): Training Data Generation (Big Model)
|
| 79 |
+
|
| 80 |
+
**Goal:** 10,000+ NL→SQL pairs using Modal + 72B model
|
| 81 |
+
|
| 82 |
+
1. Write `generate_synthetic_v2.py` that:
|
| 83 |
+
- Takes your expanded schema as input
|
| 84 |
+
- Calls Qwen2.5-72B on Modal to generate NL→SQL pairs
|
| 85 |
+
- Covers: basic counts, aggregations, GROUP BY, HAVING, CASE WHEN,
|
| 86 |
+
window functions, subqueries, multi-table joins
|
| 87 |
+
- Each pair includes: question, SQL, complexity level
|
| 88 |
+
|
| 89 |
+
2. Run on Modal A100 80GB (~3-4 hours)
|
| 90 |
+
- Generate 10,000 pairs
|
| 91 |
+
- Validate each SQL against seed data (must execute + return results)
|
| 92 |
+
- Discard failures
|
| 93 |
+
|
| 94 |
+
3. Combine with your existing 1,289 template pairs
|
| 95 |
+
|
| 96 |
+
**Time:** ~4 hours (mostly waiting for Modal)
|
| 97 |
+
**Modal cost:** ~$10
|
| 98 |
+
|
| 99 |
+
### Day 3 (June 10): Data Augmentation + Quality
|
| 100 |
+
|
| 101 |
+
**Goal:** 25,000+ validated pairs with diversity
|
| 102 |
+
|
| 103 |
+
1. Rephrasing pass: For each validated pair, generate 3 NL variations
|
| 104 |
+
- Formal: "What is the average number of absences per school?"
|
| 105 |
+
- Casual: "What's the avg absences by school?"
|
| 106 |
+
- Abbreviated: "avg absences per school?"
|
| 107 |
+
- Typo-prone: "Whats the avg abscences per scool?"
|
| 108 |
+
|
| 109 |
+
2. Pull 2,000 relevant pairs from Gretel dataset (free, already validated)
|
| 110 |
+
- Filter for: aggregation, GROUP BY, CASE WHEN, window functions
|
| 111 |
+
- These add general SQL knowledge to complement your domain-specific pairs
|
| 112 |
+
|
| 113 |
+
3. Final validation: run all SQL against seed data
|
| 114 |
+
4. Quality check: sample 100 pairs for manual review
|
| 115 |
+
|
| 116 |
+
**Target:** 25,000-30,000 validated pairs
|
| 117 |
+
**Time:** ~3 hours
|
| 118 |
+
**Modal cost:** ~$2-3 (rephrasing on T4)
|
| 119 |
+
|
| 120 |
+
### Day 4 (June 11): Model Training v2
|
| 121 |
+
|
| 122 |
+
**Goal:** Fine-tune Qwen2.5-Coder-14B with expanded data
|
| 123 |
+
|
| 124 |
+
1. Update `train.py`:
|
| 125 |
+
- Base model: `unsloth/Qwen2.5-Coder-14B-Instruct`
|
| 126 |
+
- LoRA rank: 32 (up from 16 — more data supports this)
|
| 127 |
+
- Learning rate: 1e-4 (down from 2e-4 — more data)
|
| 128 |
+
- Epochs: 2 (down from 3 — more data, avoid overfitting)
|
| 129 |
+
- Max seq length: 4096 (up from 2048 — for longer queries)
|
| 130 |
+
- Batch: 4 × 8 = 32 effective
|
| 131 |
+
|
| 132 |
+
2. Run training on Modal A10G (~2-3 hours for 25K pairs)
|
| 133 |
+
- Monitor loss curve
|
| 134 |
+
- Save checkpoints
|
| 135 |
+
|
| 136 |
+
3. Export GGUF Q4_K_M
|
| 137 |
+
|
| 138 |
+
**Time:** ~3-4 hours
|
| 139 |
+
**Modal cost:** ~$4-5
|
| 140 |
+
|
| 141 |
+
### Day 5 (June 12): Evaluation + Model Selection
|
| 142 |
+
|
| 143 |
+
**Goal:** Pick the best model, verify quality
|
| 144 |
+
|
| 145 |
+
1. Create eval set: 50 attendance questions with expected SQL
|
| 146 |
+
- Mix of simple, medium, complex
|
| 147 |
+
- Include edge cases (empty results, ambiguous questions)
|
| 148 |
+
|
| 149 |
+
2. Compare:
|
| 150 |
+
- v1 (Qwen2.5-Coder-7B, 1,289 pairs)
|
| 151 |
+
- v2 (Qwen2.5-Coder-14B, 25K pairs)
|
| 152 |
+
- Unfine-tuned Qwen2.5-Coder-14B (baseline)
|
| 153 |
+
|
| 154 |
+
3. Metrics:
|
| 155 |
+
- Exact SQL match
|
| 156 |
+
- Execution match (same results)
|
| 157 |
+
- Schema validity (no hallucinated columns)
|
| 158 |
+
- Safety (no DDL/DML)
|
| 159 |
+
|
| 160 |
+
4. Pick winner, download GGUF to local Mac
|
| 161 |
+
|
| 162 |
+
**Time:** ~3 hours
|
| 163 |
+
**Modal cost:** ~$2
|
| 164 |
+
|
| 165 |
+
### Day 6 (June 13): Integration + Polish
|
| 166 |
+
|
| 167 |
+
**Goal:** Working end-to-end demo
|
| 168 |
+
|
| 169 |
+
1. Update `model_inference.py` to use new model
|
| 170 |
+
2. Update `prompts.py` with expanded schema
|
| 171 |
+
3. Test end-to-end on Mac M2
|
| 172 |
+
4. Polish Gradio UI
|
| 173 |
+
5. Update README with v2 results
|
| 174 |
+
|
| 175 |
+
**Time:** ~4 hours
|
| 176 |
+
**Modal cost:** $0 (all local)
|
| 177 |
+
|
| 178 |
+
### Day 7 (June 14): Deploy + Submit
|
| 179 |
+
|
| 180 |
+
**Goal:** Live on HuggingFace Spaces
|
| 181 |
+
|
| 182 |
+
1. Push to HF Space
|
| 183 |
+
2. Smoke test on Space (Zero GPU)
|
| 184 |
+
3. Record demo if needed
|
| 185 |
+
4. Submit to hackathon
|
| 186 |
+
|
| 187 |
+
**Time:** ~2 hours
|
| 188 |
+
**Modal cost:** $0
|
| 189 |
+
|
| 190 |
+
---
|
| 191 |
+
|
| 192 |
+
## Budget Summary
|
| 193 |
+
|
| 194 |
+
| Item | Hours | Modal Cost |
|
| 195 |
+
|---|---|---|
|
| 196 |
+
| Day 1: Schema + seed data | 3 | $0 |
|
| 197 |
+
| Day 2: 72B data generation | 4 | $10 |
|
| 198 |
+
| Day 3: Augmentation + Gretel | 3 | $3 |
|
| 199 |
+
| Day 4: 14B training | 4 | $5 |
|
| 200 |
+
| Day 5: Evaluation | 3 | $2 |
|
| 201 |
+
| Day 6: Integration | 4 | $0 |
|
| 202 |
+
| Day 7: Deploy | 2 | $0 |
|
| 203 |
+
| **Total** | **23** | **~$20** |
|
| 204 |
+
| **Remaining credits** | | **~$230** |
|
| 205 |
+
|
| 206 |
+
You'll use less than 10% of your credits. The rest is bank for future iterations or the expanded multi-domain project post-hackathon.
|
| 207 |
+
|
| 208 |
+
---
|
| 209 |
+
|
| 210 |
+
## What This Gets You
|
| 211 |
+
|
| 212 |
+
**Hackathon submission:**
|
| 213 |
+
- Gradio app on HF Spaces
|
| 214 |
+
- Fine-tuned 14B model for attendance SQL
|
| 215 |
+
- 25K+ training pairs (could open-source this)
|
| 216 |
+
- Local-first: runs on M2 Mac with no API calls
|
| 217 |
+
- Targets: Off the Grid, Well-Tuned, Llama Champion, Off-Brand badges
|
| 218 |
+
|
| 219 |
+
**Skills honed:**
|
| 220 |
+
- Synthetic data generation with large models
|
| 221 |
+
- QLoRA fine-tuning pipeline on Modal
|
| 222 |
+
- GGUF export + quantization
|
| 223 |
+
- Schema-aware NL→SQL evaluation
|
| 224 |
+
- End-to-end ML product deployment
|
| 225 |
+
|
| 226 |
+
**Post-hackathon assets:**
|
| 227 |
+
- Reusable training pipeline (swap schema → new domain)
|
| 228 |
+
- $230 Modal credits for expanded project
|
| 229 |
+
- Training data generation methodology
|
| 230 |
+
- Foundation for LTC school district analytics product
|
| 231 |
+
|
| 232 |
+
---
|
| 233 |
+
|
| 234 |
+
## Key Risk: 14B on A10G
|
| 235 |
+
|
| 236 |
+
Qwen2.5-Coder-14B in 4-bit QLoRA needs ~18-20GB VRAM. A10G has 24GB. This fits, but tighter than 7B. If it OOMs:
|
| 237 |
+
|
| 238 |
+
**Fallback:** Use Qwen2.5-Coder-7B for training (proven), but evaluate 14B unfine-tuned as inference model. The 14B base model might be good enough without fine-tuning for a single-domain attendance task — test this on Day 5.
|
| 239 |
+
|
| 240 |
+
---
|
| 241 |
+
|
| 242 |
+
## Appendix: Where to Get the Models
|
| 243 |
+
|
| 244 |
+
```bash
|
| 245 |
+
# Qwen2.5-Coder-14B GGUF (for local inference)
|
| 246 |
+
# Check: huggingface.co/unsloth/Qwen2.5-Coder-14B-Instruct-GGUF
|
| 247 |
+
# Or: mradermacher/Qwen2.5-Coder-14B-Instruct-GGUF
|
| 248 |
+
|
| 249 |
+
# Qwen2.5-Coder-14B (for Modal fine-tuning)
|
| 250 |
+
# Unsloth: unsloth/Qwen2.5-Coder-14B-Instruct
|
| 251 |
+
|
| 252 |
+
# Qwen2.5-72B (for data generation on Modal)
|
| 253 |
+
# Qwen/Qwen2.5-72B-Instruct
|
| 254 |
+
|
| 255 |
+
# Gretel dataset (for augmentation)
|
| 256 |
+
# huggingface.co/datasets/gretelai/synthetic_text_to_sql
|
| 257 |
+
```
|
docs/TRAINING_PLAYBOOK.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# LFED Training Playbook
|
| 2 |
+
|
| 3 |
+
> **Last updated:** 2026-06-08
|
| 4 |
+
> **Status:** Training actively running on Modal A10G
|
| 5 |
+
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
## 1. How the Training Pipeline Works
|
| 9 |
+
|
| 10 |
+
### Architecture Overview
|
| 11 |
+
|
| 12 |
+
```
|
| 13 |
+
generate_synthetic.py → train.jsonl (1,289 pairs)
|
| 14 |
+
↓
|
| 15 |
+
train.py → lora-adapter/ (QLoRA weights)
|
| 16 |
+
↓
|
| 17 |
+
export_gguf.py → GGUF Q4_K_M → HF Hub
|
| 18 |
+
```
|
| 19 |
+
|
| 20 |
+
All three steps run on **Modal** (cloud GPU platform) orchestrated by `modal_app.py`.
|
| 21 |
+
|
| 22 |
+
### Step-by-Step Commands
|
| 23 |
+
|
| 24 |
+
```bash
|
| 25 |
+
cd /Users/flucido/projects/build-small-hackathon/Kasualdad_LFED
|
| 26 |
+
source .venv/bin/activate
|
| 27 |
+
|
| 28 |
+
# Full pipeline (all 3 steps sequentially)
|
| 29 |
+
modal run modal_train/modal_app.py
|
| 30 |
+
|
| 31 |
+
# Or deploy as persistent app (survives client disconnect)
|
| 32 |
+
modal deploy modal_train/modal_app.py
|
| 33 |
+
|
| 34 |
+
# Monitor progress
|
| 35 |
+
modal app logs <app-id>
|
| 36 |
+
|
| 37 |
+
# Check running apps
|
| 38 |
+
modal app list
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
### Prerequisites
|
| 42 |
+
|
| 43 |
+
| Requirement | How | Status |
|
| 44 |
+
|---|---|---|
|
| 45 |
+
| Modal account | Sign up at modal.com | ✅ `flucido` |
|
| 46 |
+
| Modal credits | Hackathon-provided | ✅ |
|
| 47 |
+
| HF_TOKEN secret | `modal secret create huggingface HF_TOKEN=<token>` | ✅ Created |
|
| 48 |
+
| HF Hub repo | Auto-created by export_gguf.py | ⏳ After training |
|
| 49 |
+
|
| 50 |
+
### Training Hyperparameters (Current)
|
| 51 |
+
|
| 52 |
+
| Param | Value | Notes |
|
| 53 |
+
|---|---|---|
|
| 54 |
+
| Base model | `unsloth/Qwen2.5-Coder-7B-Instruct` | 7B params, passed 7/7 sanity |
|
| 55 |
+
| Quantization | 4-bit QLoRA (bitsandbytes) | Fits A10G 24GB |
|
| 56 |
+
| LoRA rank (r) | 16 | |
|
| 57 |
+
| LoRA alpha | 16 | Equal to r = stable |
|
| 58 |
+
| Target modules | q, k, v, o, gate, up, down proj | All attention + MLP |
|
| 59 |
+
| Max seq length | 2048 | |
|
| 60 |
+
| Batch size | 4 | |
|
| 61 |
+
| Grad accumulation | 4 | Effective batch = 16 |
|
| 62 |
+
| Learning rate | 2e-4 | Standard for QLoRA |
|
| 63 |
+
| Warmup ratio | 0.1 | |
|
| 64 |
+
| Epochs | 3 | |
|
| 65 |
+
| Optimizer | adamw_8bit | |
|
| 66 |
+
| Total steps | 243 | (1,289 / 16) × 3 |
|
| 67 |
+
| Training speed | ~1.7s/step | ~7 min total on A10G |
|
| 68 |
+
| Loss at step 72 | 0.1008 | Good — converged fast |
|
| 69 |
+
|
| 70 |
+
---
|
| 71 |
+
|
| 72 |
+
## 2. What Went Right
|
| 73 |
+
|
| 74 |
+
### Model Selection
|
| 75 |
+
- Qwen2.5-Coder-7B passed **7/7 sanity checks** for DuckDB SQL generation
|
| 76 |
+
- GGUF Q4_K_M quantization (4.4 GB) fits in HF Spaces free tier
|
| 77 |
+
- Good at code/SQL tasks out of the box
|
| 78 |
+
|
| 79 |
+
### Architecture
|
| 80 |
+
- Modular codebase: app.py / data_engine.py / model_inference.py / prompts.py
|
| 81 |
+
- Clean separation of concerns
|
| 82 |
+
- 81 tests passing
|
| 83 |
+
|
| 84 |
+
### Synthetic Data Generation
|
| 85 |
+
- Template-based approach works well for narrow domains
|
| 86 |
+
- 32 templates with weighted sampling
|
| 87 |
+
- Questions are parameterized (school names, years, thresholds) for variety
|
| 88 |
+
- Dedup prevents exact duplicate questions
|
| 89 |
+
- Each template generates matched Q/S pairs — no hallucinated SQL
|
| 90 |
+
|
| 91 |
+
### Training Execution
|
| 92 |
+
- Unsloth provides 2x speedup + 50% VRAM reduction
|
| 93 |
+
- Training converged fast (loss 0.1008 by step 72/243)
|
| 94 |
+
- QLoRA at 0.53% trainable params = cheap, fast, effective
|
| 95 |
+
|
| 96 |
+
### Key Fix: fn.spawn() Fire-and-Forget
|
| 97 |
+
- **Problem:** 5 training runs crashed because `modal run` keeps a client connection open. When the local CLI times out or disconnects, Modal cancels the running function.
|
| 98 |
+
- **Solution:** Switch to `modal deploy` + `fn.spawn()` which fires the function with no client connection to kill.
|
| 99 |
+
- **Result:** Training survived past step 72 where all previous runs crashed.
|
| 100 |
+
|
| 101 |
+
---
|
| 102 |
+
|
| 103 |
+
## 3. What Went Wrong (and Fixes)
|
| 104 |
+
|
| 105 |
+
### Issue 1: 5 Consecutive Training Crashes
|
| 106 |
+
- **Symptom:** Training started, ran ~20-30 steps, then silently stopped
|
| 107 |
+
- **Root cause:** `modal run` maintains a gRPC connection to Modal. When the local terminal session exits (timeout, sleep, Ctrl+C), Modal cancels the remote function.
|
| 108 |
+
- **Fix:** Use `modal deploy` to create a persistent app, then call `fn.spawn()` from a Modal function that has no client connection. The deployed app stays alive independently.
|
| 109 |
+
- **Lesson:** NEVER use `modal run` for long-running GPU work. Always deploy + spawn.
|
| 110 |
+
|
| 111 |
+
### Issue 2: Flash Attention 2 Broken on Modal
|
| 112 |
+
- **Symptom:** Warning "Your Flash Attention 2 installation seems to be broken. Using Xformers instead."
|
| 113 |
+
- **Impact:** Minimal — Xformers works fine, no perf change observed
|
| 114 |
+
- **Fix:** Not needed for hackathon. For production, pin specific flash-attn + CUDA versions.
|
| 115 |
+
|
| 116 |
+
### Issue 3: Only 1,289 Pairs (vs 2,000 Target)
|
| 117 |
+
- **Symptom:** Generator hit dedup ceiling before reaching 2,000
|
| 118 |
+
- **Root cause:** 32 templates × limited parameter combinations (5 schools, 4 years) = ~1,300 unique questions max before repeats
|
| 119 |
+
- **Fix needed:** Add more templates, more parameter variety, or augment with LLM-generated rephrasings (see Expansion Plan)
|
| 120 |
+
|
| 121 |
+
### Issue 4: Narrow Schema Coverage
|
| 122 |
+
- **Symptom:** Training data only covers 2 tables (enrollment, attendance)
|
| 123 |
+
- **Impact:** Model can only answer attendance/enrollment questions — can't handle grades, discipline, demographics, wellbeing
|
| 124 |
+
- **Fix needed:** Expand schema + training pairs (see Expansion Plan)
|
| 125 |
+
|
| 126 |
+
### Issue 5: Memory Issue During Merge/Export
|
| 127 |
+
- **Symptom:** User reported memory issues on Modal after training reached 30%
|
| 128 |
+
- **Status:** Being investigated during current training run
|
| 129 |
+
- **Likely cause:** Merging LoRA back into 16-bit base model requires loading full model (14GB+), which may exceed A10G 24GB when combined with training checkpoint memory
|
| 130 |
+
- **Potential fix:** Free GPU memory between train and merge steps (already implemented in train.py lines 201-206), or use separate A10G container for merge step
|
| 131 |
+
|
| 132 |
+
### Issue 6: Template-Based Questions Feel Synthetic
|
| 133 |
+
- **Symptom:** Questions are grammatically correct but lack the messiness of real admin questions
|
| 134 |
+
- **Impact:** Model may struggle with typos, abbreviations, informal phrasing
|
| 135 |
+
- **Fix:** Add LLM-augmented rephrasings (see Expansion Plan)
|
| 136 |
+
|
| 137 |
+
---
|
| 138 |
+
|
| 139 |
+
## 4. Post-Training Checklist
|
| 140 |
+
|
| 141 |
+
After training completes:
|
| 142 |
+
|
| 143 |
+
```bash
|
| 144 |
+
# 1. Verify GGUF pushed to Hub
|
| 145 |
+
# Check https://huggingface.co/kasualdad/lfed-qwen2.5-coder-7b-sql-gguf
|
| 146 |
+
|
| 147 |
+
# 2. Update model_inference.py to use fine-tuned model
|
| 148 |
+
# Change REPO_ID and MODEL_FILE (lines ~103-107)
|
| 149 |
+
|
| 150 |
+
# 3. Test locally
|
| 151 |
+
python app.py
|
| 152 |
+
# Ask: "How many students were chronically absent in 2023-2024?"
|
| 153 |
+
|
| 154 |
+
# 4. Run tests
|
| 155 |
+
pytest tests/ -v
|
| 156 |
+
|
| 157 |
+
# 5. Deploy to HF Space
|
| 158 |
+
git push space main
|
| 159 |
+
```
|
| 160 |
+
|
| 161 |
+
---
|
| 162 |
+
|
| 163 |
+
## 5. Reproducing from Scratch
|
| 164 |
+
|
| 165 |
+
```bash
|
| 166 |
+
# 1. Clone
|
| 167 |
+
cd /Users/flucido/projects/build-small-hackathon
|
| 168 |
+
# (or clone from HF: git clone https://huggingface.co/spaces/build-small-hackathon/Kasualdad_LFED)
|
| 169 |
+
|
| 170 |
+
# 2. Setup
|
| 171 |
+
cd Kasualdad_LFED
|
| 172 |
+
python3.12 -m venv .venv
|
| 173 |
+
source .venv/bin/activate
|
| 174 |
+
pip install -r requirements.txt
|
| 175 |
+
modal token set # authenticate
|
| 176 |
+
|
| 177 |
+
# 3. Create HF secret for Modal
|
| 178 |
+
modal secret create huggingface HF_TOKEN=$(cat ~/.huggingface/token)
|
| 179 |
+
|
| 180 |
+
# 4. Run training
|
| 181 |
+
modal deploy modal_train/modal_app.py
|
| 182 |
+
# (use deploy, not run — see Issue 1 above)
|
| 183 |
+
|
| 184 |
+
# 5. Monitor
|
| 185 |
+
modal app list
|
| 186 |
+
modal app logs <app-id>
|
| 187 |
+
```
|
modal_train/export_gguf.py
CHANGED
|
@@ -19,43 +19,65 @@ from pathlib import Path
|
|
| 19 |
# ── Configuration ──────────────────────────────────────────────────────
|
| 20 |
|
| 21 |
BASE_MODEL = "unsloth/Qwen2.5-Coder-7B-Instruct"
|
| 22 |
-
LORA_DIR = Path(
|
| 23 |
-
MERGED_DIR = Path(
|
| 24 |
-
GGUF_DIR = Path(
|
| 25 |
GGUF_QUANT = "Q4_K_M"
|
| 26 |
|
| 27 |
# HF repo — set via env or fall back to interactive
|
| 28 |
-
HF_USERNAME = os.getenv("HF_USERNAME", "
|
| 29 |
HF_REPO = f"{HF_USERNAME}/lfed-qwen2.5-coder-7b-sql-gguf"
|
| 30 |
|
| 31 |
|
| 32 |
# ── Step 1: Merge LoRA → full model ────────────────────────────────────
|
| 33 |
|
| 34 |
-
def merge_lora():
|
| 35 |
"""Merge the LoRA adapter weights into the base model."""
|
| 36 |
print("🔀 Merging LoRA adapter into base model...")
|
| 37 |
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
|
|
|
|
|
|
| 43 |
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
)
|
|
|
|
|
|
|
| 50 |
|
| 51 |
-
|
| 52 |
-
model.load_adapter(str(LORA_DIR))
|
| 53 |
model = model.merge_and_unload()
|
| 54 |
|
| 55 |
MERGED_DIR.mkdir(parents=True, exist_ok=True)
|
| 56 |
model.save_pretrained(str(MERGED_DIR))
|
| 57 |
tokenizer.save_pretrained(str(MERGED_DIR))
|
| 58 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
print(f"✅ Merged model saved to {MERGED_DIR}")
|
| 60 |
|
| 61 |
|
|
@@ -67,53 +89,37 @@ def convert_to_gguf():
|
|
| 67 |
|
| 68 |
GGUF_DIR.mkdir(parents=True, exist_ok=True)
|
| 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 |
-
"--outtype", "f16",
|
| 98 |
-
], check=True)
|
| 99 |
-
|
| 100 |
-
# Quantize
|
| 101 |
-
out_path = GGUF_DIR / f"lfed-qwen2.5-coder-7b-sql-{GGUF_QUANT}.gguf"
|
| 102 |
-
subprocess.run([
|
| 103 |
-
"python", "-m", "llama_cpp.quantize",
|
| 104 |
-
str(f16_path),
|
| 105 |
-
str(out_path),
|
| 106 |
-
GGUF_QUANT,
|
| 107 |
-
], check=True)
|
| 108 |
else:
|
| 109 |
-
#
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
"python", convert_script,
|
| 113 |
-
str(MERGED_DIR),
|
| 114 |
-
"--outfile", str(out_path),
|
| 115 |
-
"--outtype", GGUF_QUANT.lower(),
|
| 116 |
-
], check=True)
|
| 117 |
|
| 118 |
print(f"✅ GGUF saved to {out_path}")
|
| 119 |
return out_path
|
|
@@ -216,6 +222,12 @@ def main():
|
|
| 216 |
print(f" Repo: {HF_REPO}")
|
| 217 |
print("=" * 60)
|
| 218 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
merge_lora()
|
| 220 |
gguf_path = convert_to_gguf()
|
| 221 |
push_to_hub(gguf_path)
|
|
|
|
| 19 |
# ── Configuration ──────────────────────────────────────────────────────
|
| 20 |
|
| 21 |
BASE_MODEL = "unsloth/Qwen2.5-Coder-7B-Instruct"
|
| 22 |
+
LORA_DIR = Path("/data") / "lora-adapter"
|
| 23 |
+
MERGED_DIR = Path("/data") / "merged-model"
|
| 24 |
+
GGUF_DIR = Path("/data") / "gguf-output"
|
| 25 |
GGUF_QUANT = "Q4_K_M"
|
| 26 |
|
| 27 |
# HF repo — set via env or fall back to interactive
|
| 28 |
+
HF_USERNAME = os.getenv("HF_USERNAME", "build-small-hackathon")
|
| 29 |
HF_REPO = f"{HF_USERNAME}/lfed-qwen2.5-coder-7b-sql-gguf"
|
| 30 |
|
| 31 |
|
| 32 |
# ── Step 1: Merge LoRA → full model ────────────────────────────────────
|
| 33 |
|
| 34 |
+
def merge_lora(force: bool = False):
|
| 35 |
"""Merge the LoRA adapter weights into the base model."""
|
| 36 |
print("🔀 Merging LoRA adapter into base model...")
|
| 37 |
|
| 38 |
+
# Ensure GPU memory is clear
|
| 39 |
+
import gc
|
| 40 |
+
import torch
|
| 41 |
+
gc.collect()
|
| 42 |
+
torch.cuda.empty_cache()
|
| 43 |
+
print(f" GPU: {torch.cuda.memory_allocated()/1e9:.1f}GB used, "
|
| 44 |
+
f"{torch.cuda.memory_reserved()/1e9:.1f}GB reserved")
|
| 45 |
|
| 46 |
+
if MERGED_DIR.exists():
|
| 47 |
+
if force:
|
| 48 |
+
print(f" Removing stale merged model at {MERGED_DIR}...")
|
| 49 |
+
shutil.rmtree(MERGED_DIR)
|
| 50 |
+
else:
|
| 51 |
+
print(f" Merged model already exists at {MERGED_DIR}, skipping.")
|
| 52 |
+
return
|
| 53 |
+
|
| 54 |
+
# Use PEFT to load adapter onto base model, then merge
|
| 55 |
+
# Must load base in FP16 (not 4-bit) because merge_and_unload doesn't support quantized
|
| 56 |
+
print(" Loading base model in FP16 + adapter...")
|
| 57 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 58 |
+
from peft import PeftModel
|
| 59 |
+
|
| 60 |
+
base_model = AutoModelForCausalLM.from_pretrained(
|
| 61 |
+
BASE_MODEL,
|
| 62 |
+
torch_dtype=torch.float16,
|
| 63 |
+
device_map="auto",
|
| 64 |
+
low_cpu_mem_usage=True,
|
| 65 |
)
|
| 66 |
+
model = PeftModel.from_pretrained(base_model, str(LORA_DIR))
|
| 67 |
+
tokenizer = AutoTokenizer.from_pretrained(str(LORA_DIR))
|
| 68 |
|
| 69 |
+
print(" Merging adapter into base model...")
|
|
|
|
| 70 |
model = model.merge_and_unload()
|
| 71 |
|
| 72 |
MERGED_DIR.mkdir(parents=True, exist_ok=True)
|
| 73 |
model.save_pretrained(str(MERGED_DIR))
|
| 74 |
tokenizer.save_pretrained(str(MERGED_DIR))
|
| 75 |
|
| 76 |
+
# Free GPU
|
| 77 |
+
del model, tokenizer
|
| 78 |
+
gc.collect()
|
| 79 |
+
torch.cuda.empty_cache()
|
| 80 |
+
|
| 81 |
print(f"✅ Merged model saved to {MERGED_DIR}")
|
| 82 |
|
| 83 |
|
|
|
|
| 89 |
|
| 90 |
GGUF_DIR.mkdir(parents=True, exist_ok=True)
|
| 91 |
|
| 92 |
+
# Clone llama.cpp for the conversion script
|
| 93 |
+
llama_dir = Path("/tmp/llama.cpp")
|
| 94 |
+
if not llama_dir.exists():
|
| 95 |
+
print(" Cloning llama.cpp repo...")
|
| 96 |
+
subprocess.run(["git", "clone", "--depth=1", "https://github.com/ggml-org/llama.cpp", str(llama_dir)], check=True)
|
| 97 |
+
|
| 98 |
+
# Install gguf package
|
| 99 |
+
subprocess.run(["pip", "install", "-q", "gguf>=0.10.0"], check=True)
|
| 100 |
+
|
| 101 |
+
# Save as FP16 GGUF first, then quantize
|
| 102 |
+
f16_path = GGUF_DIR / "model-f16.gguf"
|
| 103 |
+
out_path = GGUF_DIR / f"lfed-qwen2.5-coder-7b-sql-{GGUF_QUANT}.gguf"
|
| 104 |
+
|
| 105 |
+
print(f" Converting to FP16 GGUF...")
|
| 106 |
+
convert_script = llama_dir / "convert_hf_to_gguf.py"
|
| 107 |
+
subprocess.run([
|
| 108 |
+
"python", str(convert_script),
|
| 109 |
+
str(MERGED_DIR),
|
| 110 |
+
"--outfile", str(f16_path),
|
| 111 |
+
"--outtype", "f16",
|
| 112 |
+
], check=True)
|
| 113 |
+
|
| 114 |
+
# Quantize using llama-quantize (built-in to llama-cpp-python)
|
| 115 |
+
print(f" Quantizing to {GGUF_QUANT}...")
|
| 116 |
+
quantize_bin = shutil.which("llama-quantize") or shutil.which("quantize")
|
| 117 |
+
if quantize_bin:
|
| 118 |
+
subprocess.run([quantize_bin, str(f16_path), str(out_path), GGUF_QUANT], check=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
else:
|
| 120 |
+
# Fallback: use the FP16 GGUF directly
|
| 121 |
+
print(" ⚠️ llama-quantize not found, using FP16 GGUF")
|
| 122 |
+
out_path = f16_path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
|
| 124 |
print(f"✅ GGUF saved to {out_path}")
|
| 125 |
return out_path
|
|
|
|
| 222 |
print(f" Repo: {HF_REPO}")
|
| 223 |
print("=" * 60)
|
| 224 |
|
| 225 |
+
# Clean stale output dirs from failed previous runs
|
| 226 |
+
for d in [MERGED_DIR, GGUF_DIR]:
|
| 227 |
+
if d.exists():
|
| 228 |
+
print(f" Cleaning stale {d}...")
|
| 229 |
+
shutil.rmtree(d)
|
| 230 |
+
|
| 231 |
merge_lora()
|
| 232 |
gguf_path = convert_to_gguf()
|
| 233 |
push_to_hub(gguf_path)
|
modal_train/modal_app.py
CHANGED
|
@@ -36,8 +36,13 @@ train_image = (
|
|
| 36 |
"peft>=0.13.0",
|
| 37 |
"huggingface_hub>=0.26.0",
|
| 38 |
"llama-cpp-python>=0.3.0",
|
|
|
|
| 39 |
)
|
| 40 |
.env({"HF_HUB_ENABLE_HF_TRANSFER": "1"})
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
)
|
| 42 |
|
| 43 |
# ── Volume for persistent storage (training data + checkpoints) ────────
|
|
@@ -70,9 +75,10 @@ def generate_synthetic():
|
|
| 70 |
main()
|
| 71 |
|
| 72 |
# Copy output back
|
|
|
|
| 73 |
train_jsonl = Path(__file__).parent / "train.jsonl"
|
| 74 |
if train_jsonl.exists():
|
| 75 |
-
|
| 76 |
|
| 77 |
print(f"✅ Synthetic data ready at {MOUNT_DIR / 'train.jsonl'}")
|
| 78 |
volume.commit()
|
|
@@ -112,6 +118,13 @@ def train_model():
|
|
| 112 |
from train import main
|
| 113 |
main()
|
| 114 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
volume.commit()
|
| 116 |
print("✅ Training complete")
|
| 117 |
|
|
@@ -128,15 +141,21 @@ def train_model():
|
|
| 128 |
def export_and_push():
|
| 129 |
"""Merge LoRA, export GGUF, push to HF Hub."""
|
| 130 |
import sys
|
| 131 |
-
|
| 132 |
|
| 133 |
-
#
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
|
| 138 |
-
|
| 139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
|
| 141 |
|
| 142 |
# ── Full pipeline (run all steps sequentially) ─────────────────────────
|
|
@@ -161,7 +180,7 @@ def run_full_pipeline():
|
|
| 161 |
train_model.local()
|
| 162 |
|
| 163 |
print("\n📦 Step 3/3: Merging → GGUF → HF Hub...")
|
| 164 |
-
export_and_push.
|
| 165 |
|
| 166 |
print("\n🎉 Full pipeline complete!")
|
| 167 |
print(f" Model repo: check your HF Hub for 'lfed-qwen2.5-coder-7b-sql-gguf'")
|
|
|
|
| 36 |
"peft>=0.13.0",
|
| 37 |
"huggingface_hub>=0.26.0",
|
| 38 |
"llama-cpp-python>=0.3.0",
|
| 39 |
+
"gguf>=0.10.0",
|
| 40 |
)
|
| 41 |
.env({"HF_HUB_ENABLE_HF_TRANSFER": "1"})
|
| 42 |
+
.add_local_dir(
|
| 43 |
+
Path(__file__).parent,
|
| 44 |
+
remote_path="/root",
|
| 45 |
+
)
|
| 46 |
)
|
| 47 |
|
| 48 |
# ── Volume for persistent storage (training data + checkpoints) ────────
|
|
|
|
| 75 |
main()
|
| 76 |
|
| 77 |
# Copy output back
|
| 78 |
+
import shutil
|
| 79 |
train_jsonl = Path(__file__).parent / "train.jsonl"
|
| 80 |
if train_jsonl.exists():
|
| 81 |
+
shutil.move(str(train_jsonl), str(MOUNT_DIR / "train.jsonl"))
|
| 82 |
|
| 83 |
print(f"✅ Synthetic data ready at {MOUNT_DIR / 'train.jsonl'}")
|
| 84 |
volume.commit()
|
|
|
|
| 118 |
from train import main
|
| 119 |
main()
|
| 120 |
|
| 121 |
+
# Free GPU memory for the export step
|
| 122 |
+
import gc
|
| 123 |
+
import torch
|
| 124 |
+
del sys.modules['train']
|
| 125 |
+
gc.collect()
|
| 126 |
+
torch.cuda.empty_cache()
|
| 127 |
+
|
| 128 |
volume.commit()
|
| 129 |
print("✅ Training complete")
|
| 130 |
|
|
|
|
| 141 |
def export_and_push():
|
| 142 |
"""Merge LoRA, export GGUF, push to HF Hub."""
|
| 143 |
import sys
|
| 144 |
+
import importlib
|
| 145 |
|
| 146 |
+
# Import directly from mount path (not volume copy)
|
| 147 |
+
mount_path = str(Path(__file__).parent)
|
| 148 |
+
if mount_path not in sys.path:
|
| 149 |
+
sys.path.insert(0, mount_path)
|
| 150 |
|
| 151 |
+
# Clear any cached module
|
| 152 |
+
for key in list(sys.modules.keys()):
|
| 153 |
+
if "export_gguf" in key:
|
| 154 |
+
del sys.modules[key]
|
| 155 |
+
|
| 156 |
+
import export_gguf
|
| 157 |
+
importlib.reload(export_gguf)
|
| 158 |
+
export_gguf.main()
|
| 159 |
|
| 160 |
|
| 161 |
# ── Full pipeline (run all steps sequentially) ─────────────────────────
|
|
|
|
| 180 |
train_model.local()
|
| 181 |
|
| 182 |
print("\n📦 Step 3/3: Merging → GGUF → HF Hub...")
|
| 183 |
+
export_and_push.remote() # remote() = fresh container, clean GPU
|
| 184 |
|
| 185 |
print("\n🎉 Full pipeline complete!")
|
| 186 |
print(f" Model repo: check your HF Hub for 'lfed-qwen2.5-coder-7b-sql-gguf'")
|
modal_train/train.py
CHANGED
|
@@ -154,17 +154,25 @@ def train(model, tokenizer, dataset: Dataset):
|
|
| 154 |
fp16=not is_bfloat16_supported(),
|
| 155 |
bf16=is_bfloat16_supported(),
|
| 156 |
logging_steps=LOGGING_STEPS,
|
| 157 |
-
|
| 158 |
-
save_total_limit=2,
|
| 159 |
optim="adamw_8bit",
|
| 160 |
seed=42,
|
| 161 |
report_to="none", # No wandb on Modal
|
| 162 |
),
|
| 163 |
)
|
| 164 |
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
| 169 |
model.save_pretrained(str(OUTPUT_DIR))
|
| 170 |
tokenizer.save_pretrained(str(OUTPUT_DIR))
|
|
@@ -176,9 +184,28 @@ def train(model, tokenizer, dataset: Dataset):
|
|
| 176 |
# ── Main ───────────────────────────────────────────────────────────────
|
| 177 |
|
| 178 |
def main():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
dataset = load_training_data()
|
| 180 |
model, tokenizer = load_model_and_tokenizer()
|
| 181 |
train(model, tokenizer, dataset)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
print("\n🎉 Training complete!")
|
| 183 |
|
| 184 |
|
|
|
|
| 154 |
fp16=not is_bfloat16_supported(),
|
| 155 |
bf16=is_bfloat16_supported(),
|
| 156 |
logging_steps=LOGGING_STEPS,
|
| 157 |
+
save_strategy="no", # manual save only — prevent pickle error
|
|
|
|
| 158 |
optim="adamw_8bit",
|
| 159 |
seed=42,
|
| 160 |
report_to="none", # No wandb on Modal
|
| 161 |
),
|
| 162 |
)
|
| 163 |
|
| 164 |
+
import traceback
|
| 165 |
+
try:
|
| 166 |
+
trainer.train()
|
| 167 |
+
except Exception as e:
|
| 168 |
+
if "Pickle" in str(e) or "pickle" in str(e):
|
| 169 |
+
print("\n⚠️ Trainer save failed (pickle error on args) — continuing with manual save...")
|
| 170 |
+
traceback.print_exc()
|
| 171 |
+
else:
|
| 172 |
+
raise
|
| 173 |
+
|
| 174 |
+
# Save final adapter (manual — bypasses trainer's pickle-prone save)
|
| 175 |
+
print("\n💾 Saving adapter weights...")
|
| 176 |
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
| 177 |
model.save_pretrained(str(OUTPUT_DIR))
|
| 178 |
tokenizer.save_pretrained(str(OUTPUT_DIR))
|
|
|
|
| 184 |
# ── Main ───────────────────────────────────────────────────────────────
|
| 185 |
|
| 186 |
def main():
|
| 187 |
+
# Skip training if adapter already exists on volume
|
| 188 |
+
if (Path("/data") / "lora-adapter" / "adapter_config.json").exists():
|
| 189 |
+
print("✅ LoRA adapter already exists in volume — skipping training")
|
| 190 |
+
return
|
| 191 |
+
|
| 192 |
+
if OUTPUT_DIR.is_dir() and (OUTPUT_DIR / "adapter_config.json").exists():
|
| 193 |
+
print("✅ LoRA adapter already exists — skipping training")
|
| 194 |
+
return
|
| 195 |
+
|
| 196 |
dataset = load_training_data()
|
| 197 |
model, tokenizer = load_model_and_tokenizer()
|
| 198 |
train(model, tokenizer, dataset)
|
| 199 |
+
|
| 200 |
+
# Free GPU memory before merge step
|
| 201 |
+
import gc
|
| 202 |
+
del model
|
| 203 |
+
del tokenizer
|
| 204 |
+
gc.collect()
|
| 205 |
+
import torch
|
| 206 |
+
torch.cuda.empty_cache()
|
| 207 |
+
print("🧹 GPU memory cleared for merge step")
|
| 208 |
+
|
| 209 |
print("\n🎉 Training complete!")
|
| 210 |
|
| 211 |
|
model_inference.py
CHANGED
|
@@ -98,8 +98,8 @@ from prompts import build_prompt
|
|
| 98 |
LOCAL_MODEL_PATH = "/tmp/lfed-models/qwen/Qwen2.5-Coder-7B-Instruct.Q4_K_M.gguf"
|
| 99 |
|
| 100 |
# Fallback: download from HF Hub (base model, pre-fine-tune)
|
| 101 |
-
HF_REPO_ID = "
|
| 102 |
-
HF_MODEL_FILE = "
|
| 103 |
|
| 104 |
# Post-Phase-7: swap HF_REPO_ID / HF_MODEL_FILE to the fine-tuned GGUF
|
| 105 |
# after Modal training completes and the model is pushed to HF Hub.
|
|
|
|
| 98 |
LOCAL_MODEL_PATH = "/tmp/lfed-models/qwen/Qwen2.5-Coder-7B-Instruct.Q4_K_M.gguf"
|
| 99 |
|
| 100 |
# Fallback: download from HF Hub (base model, pre-fine-tune)
|
| 101 |
+
HF_REPO_ID = "build-small-hackathon/lfed-qwen2.5-coder-7b-sql-gguf"
|
| 102 |
+
HF_MODEL_FILE = "model-f16.gguf"
|
| 103 |
|
| 104 |
# Post-Phase-7: swap HF_REPO_ID / HF_MODEL_FILE to the fine-tuned GGUF
|
| 105 |
# after Modal training completes and the model is pushed to HF Hub.
|