Kasualdad commited on
Commit
31c493f
·
1 Parent(s): 8cbb8cf

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 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(__file__).parent / "lora-adapter"
23
- MERGED_DIR = Path(__file__).parent / "merged-model"
24
- GGUF_DIR = Path(__file__).parent / "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", "kasualdad")
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
- if MERGED_DIR.exists():
39
- print(f" Merged model already exists at {MERGED_DIR}, skipping.")
40
- return
41
-
42
- from unsloth import FastLanguageModel
 
 
43
 
44
- model, tokenizer = FastLanguageModel.from_pretrained(
45
- model_name=BASE_MODEL,
46
- max_seq_length=2048,
47
- dtype=None,
48
- load_in_4bit=False, # Load in 16-bit for merge
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  )
 
 
50
 
51
- # Load and merge adapter
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
- # Check if llama.cpp conversion script is available (installed in Modal image)
71
- # The script is typically at: llama.cpp/convert_hf_to_gguf.py
72
- convert_script = shutil.which("convert_hf_to_gguf.py")
73
- if not convert_script:
74
- # Try common locations
75
- candidates = [
76
- "/app/llama.cpp/convert_hf_to_gguf.py",
77
- "/llama.cpp/convert_hf_to_gguf.py",
78
- "convert_hf_to_gguf.py",
79
- ]
80
- for c in candidates:
81
- if Path(c).exists() or shutil.which(c):
82
- convert_script = c
83
- break
84
-
85
- if not convert_script:
86
- # Fallback: use llama-cpp-python's built-in converter
87
- print(" Using llama-cpp-python converter...")
88
- from llama_cpp import Llama
89
- # llama-cpp-python >= 0.2.0 can convert directly
90
- # We'll save as FP16 GGUF first, then quantize
91
- f16_path = GGUF_DIR / "model-f16.gguf"
92
-
93
- subprocess.run([
94
- "python", "-m", "llama_cpp.convert",
95
- str(MERGED_DIR),
96
- "--outfile", str(f16_path),
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
- # Use llama.cpp's built-in converter with --outtype for direct quant
110
- out_path = GGUF_DIR / f"lfed-qwen2.5-coder-7b-sql-{GGUF_QUANT}.gguf"
111
- subprocess.run([
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
- train_jsonl.rename(MOUNT_DIR / "train.jsonl")
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
- sys.path.insert(0, str(MOUNT_DIR))
132
 
133
- # Copy export script
134
- export_script = Path(__file__).parent / "export_gguf.py"
135
- dest = MOUNT_DIR / "export_gguf.py"
136
- dest.write_text(export_script.read_text())
137
 
138
- from export_gguf import main
139
- main()
 
 
 
 
 
 
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.local()
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
- save_steps=SAVE_STEPS,
158
- save_total_limit=2,
159
  optim="adamw_8bit",
160
  seed=42,
161
  report_to="none", # No wandb on Modal
162
  ),
163
  )
164
 
165
- trainer.train()
166
-
167
- # Save final adapter
 
 
 
 
 
 
 
 
 
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 = "mradermacher/Qwen2.5-Coder-7B-Instruct-GGUF"
102
- HF_MODEL_FILE = "Qwen2.5-Coder-7B-Instruct.Q4_K_M.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.
 
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.