Skip to content

Commit 0c4c420

Browse files
committed
docs: design spec for fine-tuning student explainability epic
8-issue epic covering SHAP narrator, summarizer, and explainer fine-tuning with Qwen 3.5 (4B + 9B evaluation). Includes Colab notebook design (single Run All, Unsloth + LoRA on A100), SHAP narrator task type with grounding metrics, and dashboard integration via model-client.ts Ollama adapter.
1 parent b9cbe63 commit 0c4c420

1 file changed

Lines changed: 305 additions & 0 deletions

File tree

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
# Design Spec: Fine-Tuning for Student Explainability
2+
3+
**Date:** 2026-04-02
4+
**Epic label:** `fine-tuning: student-explainability`
5+
**Epic branch:** `fine-tuning/student-explainability`
6+
**Status:** Draft
7+
8+
---
9+
10+
## 1. Goal
11+
12+
Fine-tune a small language model (Qwen 3.5) on Bishop State domain data to replace GPT-4o-mini for three inference tasks in the dashboard. The primary value is improved explainability: advisors get SHAP-grounded, institution-aware narratives instead of templated rule-engine output. Secondary benefits include FERPA compliance (all inference on-premises), offline deployment, and institutional scalability.
13+
14+
### Tasks to Fine-Tune
15+
16+
| Task | Input | Output | Priority |
17+
|------|-------|--------|----------|
18+
| **SHAP Narrator** | SHAP values + student profile + risk factors | Grounded advisor narrative + interventions | Highest (new) |
19+
| **Summarizer** | Query results + original question | Plain-English summary for advisors | Medium (exists) |
20+
| **Explainer** | Course pairing stats (DFWI, delivery, instructor) | Data-driven analysis + recommendation | Medium (exists) |
21+
22+
### Out of Scope
23+
24+
- Query Analyzer (NL → SQL) — high risk, deferred to future epic
25+
- Model serving infrastructure (RunPod, dedicated GPU hosting) — use local Ollama for now
26+
27+
## 2. Prerequisites
28+
29+
Before the epic branch is created:
30+
31+
1. **Merge `feature/distillation-pipeline``main`** — brings in `training/` pipeline modules, `schools/bishop-state/config.yaml`, seed queries, `model-client.ts`
32+
2. **Merge `feature/shap-explainability``main`** — brings in per-student SHAP computation (Step 10b), SHAP-aware `enrich_with_llm()`, student API SHAP exposure, feasibility report
33+
34+
## 3. Epic Structure
35+
36+
### Branching
37+
38+
- **Epic branch:** `fine-tuning/student-explainability` (from `main` after prereq merges)
39+
- **Feature branches:** `fine-tuning/issue-N-description` → PR into epic branch
40+
- **Final PR:** epic branch → `main`
41+
42+
### Issue Breakdown
43+
44+
```
45+
+---------------+
46+
| #1 Prereq: |
47+
| Merge both |
48+
| branches |
49+
+-------+-------+
50+
|
51+
+------------+------------+
52+
v v v
53+
+----------+ +----------+ +----------+
54+
| #2 SHAP | | #3 Colab | | #4 Distill|
55+
| narrator | | notebook | | summarizer|
56+
| task type| | (Unsloth)| | + explain |
57+
+----+-----+ +----+-----+ +----+-----+
58+
| | |
59+
v | |
60+
+----------+ | |
61+
| #5 Distill| | |
62+
| SHAP | | |
63+
| narrator | | |
64+
+----+-----+ | |
65+
| | |
66+
+-------------+------------+
67+
v
68+
+----------+
69+
| #6 Train |
70+
| 4B + 9B |
71+
| evaluate |
72+
+----+-----+
73+
|
74+
+----+-----+
75+
v v
76+
+----------+ +----------+
77+
| #7 Export | | #8 Update|
78+
| + wire | | docs & |
79+
| dashboard| | report |
80+
+----------+ +----------+
81+
```
82+
83+
| # | Title | Description | Depends | Labels |
84+
|---|-------|------------|---------|--------|
85+
| 1 | Merge distillation-pipeline and shap-explainability to main | Merge both feature branches, resolve conflicts, verify CI || `type:chore` |
86+
| 2 | Add SHAP narrator task type to training pipeline | New prompt template, output schema, seed data generator, eval metrics | #1 | `type:feature`, `area:ai` |
87+
| 3 | Build Colab training notebook (Unsloth + LoRA) | Single "Run All" notebook, parameterized config, 3-phase training, GGUF export. Replace `training/finetune.py` (MLX) with Unsloth wrapper. | #1 | `type:feature`, `area:ai` |
88+
| 4 | Distill training pairs for summarizer and explainer | Run distillation for both existing tasks (~1,500 pairs each via Claude API). Prepare datasets. | #1 | `type:feature`, `area:ai` |
89+
| 5 | Distill training pairs for SHAP narrator | Generate ~1,500 SHAP narrator pairs from student data + SHAP values. Requires SHAP data in DB. | #2 | `type:feature`, `area:ai` |
90+
| 6 | Train and evaluate 4B + 9B models | Run Colab notebook for both model sizes. Evaluate via ship criteria. Compare metrics, pick winner. | #3, #4, #5 | `type:spike`, `area:ai` |
91+
| 7 | Export models and wire into dashboard | GGUF export, Ollama registration, wire `model-client.ts` into consumer routes, update `enrich_with_llm` model string. | #6 | `type:feature`, `area:ai`, `area:frontend` |
92+
| 8 | Update documentation and feasibility report | Update feasibility report with actual results, update README and CLAUDE.md. | #6 | `type:documentation` |
93+
94+
### Parallelism
95+
96+
Issues #2, #3, and #4 can proceed concurrently after #1. Issue #5 waits only on #2. Issue #6 is the convergence point. Issues #7 and #8 are parallel after #6.
97+
98+
## 4. Colab Notebook Design
99+
100+
### Principles
101+
102+
- **Single "Run All" execution.** No babysitting. No manual cell-by-cell.
103+
- **Parameterized at the top.** One config cell is the only thing the user edits.
104+
- **Checkpoint and resume.** If Colab disconnects, set `SKIP_DOMAIN_ADAPTATION=True` to resume from Phase 2.
105+
- **Chat template alignment.** Uses `tokenizer.apply_chat_template()` throughout — never manual ChatML tokenization (D4BL's critical lesson).
106+
107+
### Notebook Structure
108+
109+
```
110+
Cell 1: Configuration (ONLY cell the user edits)
111+
-------------------------------------------------
112+
SCHOOL = "bishop-state"
113+
MODEL_SIZES = ["4b", "9b"]
114+
REPO_URL = "https://github.com/codebenders/datathon.git"
115+
REPO_BRANCH = "fine-tuning/student-explainability"
116+
HF_TOKEN = "" # or userdata.get('HF_TOKEN')
117+
PHASE_1_EPOCHS = 1
118+
PHASE_2_EPOCHS = 7
119+
SKIP_DOMAIN_ADAPTATION = False # True to reuse cached Phase 1
120+
121+
Cell 2+: Fully autonomous
122+
-------------------------------------------------
123+
- GPU detection + validation (assert A100/T4/L4)
124+
- pip install unsloth, trl, peft
125+
- Clone repo, load schools/{SCHOOL}/config.yaml
126+
- For each model size:
127+
- Phase 1: Domain adaptation
128+
- Load base Qwen model via Unsloth (4-bit NF4)
129+
- Train on training_data/{school}/domain.jsonl
130+
- LoRA rank 16, all modules, 1 epoch, lr 2e-4, effective batch 32
131+
- Save merged checkpoint
132+
- Phase 2: Task adapters (narrator, summarizer, explainer)
133+
- Load Phase 1 checkpoint
134+
- Train LoRA adapter per task
135+
- Eval after each task, print ship-criteria table
136+
- Narrator: LoRA r=16, attention+FFN, 7 epochs, lr 1e-4
137+
- Summarizer: LoRA r=8, attention only, 7 epochs, lr 1e-4
138+
- Explainer: LoRA r=16, attention+FFN, 4 epochs, lr 1e-4
139+
- Phase 3: GGUF export
140+
- Quantize each task adapter to q4_k_m
141+
- Upload to Google Drive (or HF Hub if HF_TOKEN provided)
142+
- Print comparison table: 4B vs 9B metrics across all tasks
143+
- Recommend winner based on ship criteria
144+
```
145+
146+
### Training Hyperparameters
147+
148+
Based on D4BL's proven configurations:
149+
150+
| Parameter | Phase 1 (Domain) | Phase 2 (Tasks) |
151+
|-----------|------------------|-----------------|
152+
| LoRA rank | 16 | 8-16 (task-dependent) |
153+
| LoRA alpha | 32 | 16-32 |
154+
| Learning rate | 2e-4 | 1e-4 |
155+
| Batch size (per device) | 8 | 4-8 |
156+
| Gradient accumulation | 4 | 2-4 |
157+
| Epochs | 1 | 4-7 |
158+
| Max sequence length | 4096 | 4096-8192 |
159+
| Optimizer | AdamW 8-bit | AdamW 8-bit |
160+
| Precision | bf16 (A100) | bf16 (A100) |
161+
162+
### What the Notebook Does NOT Do
163+
164+
- Does not run distillation (that's local via `python -m training.distill`)
165+
- Does not register Ollama models (local after downloading GGUFs)
166+
- Does not modify the repo (read-only clone for config + training data)
167+
168+
## 5. SHAP Narrator Task Design
169+
170+
### New Task Type: `narrator`
171+
172+
This is the highest-value task — it transforms per-student SHAP attribution data into advisor-facing narratives that explain *why* a student is at risk and *what specifically to do about it*.
173+
174+
### Input Format (at inference)
175+
176+
```json
177+
{
178+
"student_profile": {
179+
"enrollment_intensity": "Part-Time",
180+
"gpa_year1": 1.4,
181+
"math_placement": "R",
182+
"course_completion_rate": 0.55,
183+
"gateway_math_completed": false,
184+
"at_risk_alert": "HIGH",
185+
"retention_probability": 0.28
186+
},
187+
"readiness_score": 0.38,
188+
"readiness_level": "low",
189+
"risk_factors": [
190+
"Low first-year GPA (1.4 / 4.0)",
191+
"Gateway math not completed in Year 1"
192+
],
193+
"shap": {
194+
"retention": {
195+
"base_value": 0.52,
196+
"top_positive": [
197+
{"feature": "total_credits_attempted", "shap_value": 0.05, "value": 12.0}
198+
],
199+
"top_negative": [
200+
{"feature": "CompletedGatewayMathYear1", "shap_value": -0.18, "value": 0.0},
201+
{"feature": "Enrollment_Intensity_First_Term", "shap_value": -0.12, "value": 1.0}
202+
]
203+
},
204+
"gateway_math": { ... },
205+
"low_gpa": { ... }
206+
}
207+
}
208+
```
209+
210+
### Output Schema
211+
212+
```json
213+
{
214+
"narrative": "2-3 sentence explanation grounded in SHAP attribution",
215+
"key_drivers": [
216+
"Gateway math not completed (-0.18 on retention)",
217+
"Part-time enrollment (-0.12 on retention)"
218+
],
219+
"recommended_actions": [
220+
"Priority enrollment in MAT 100 next term",
221+
"Explore full-time enrollment options and financial aid",
222+
"Connect with Math Bootcamp (2x pass rate for participants)"
223+
],
224+
"data_limitations": [
225+
"Retention model trained on 2019-2023 cohorts; 2024+ patterns may differ"
226+
]
227+
}
228+
```
229+
230+
### Distillation Strategy
231+
232+
1. Pull ~4K students from `student_level_with_predictions` joined with `llm_recommendations`
233+
2. For each medium/low readiness student (~2K): build input from `shap_explanations` + `input_features` columns
234+
3. Send to Claude (teacher model) with system prompt grounded in Bishop State context from `config.yaml`
235+
4. Validate output JSON schema, deduplicate (Jaccard 1.0), split 80/10/10
236+
5. Target: ~1,500 validated training pairs
237+
238+
### Eval Metrics (Ship Criteria)
239+
240+
| Metric | Threshold | Blocking? |
241+
|--------|-----------|-----------|
242+
| `json_valid_rate` | >= 95% | Yes |
243+
| `schema_valid_rate` | >= 90% | Yes |
244+
| `shap_grounding_rate` | >= 80% (narrative mentions >= 2 of top-3 SHAP features) | Yes |
245+
| `action_specificity` | LLM-judged: are actions Bishop State-specific? | No |
246+
247+
## 6. Dashboard Integration
248+
249+
### Model Client as Single Adapter
250+
251+
`model-client.ts` becomes the sole inference routing layer. Existing routes (`explain-pairing/route.ts`, `query-summary/route.ts`) that currently instantiate their own OpenAI clients will be refactored to call `generateExplanation()` and `generateSummary()` from `model-client.ts`.
252+
253+
### Ollama Model Naming
254+
255+
```
256+
bishop-state-narrator:{size} # SHAP narrator
257+
bishop-state-summarizer:{size} # Query summary
258+
bishop-state-explainer:{size} # Course pairing
259+
```
260+
261+
Where `{size}` is `4b` or `9b` based on evaluation results.
262+
263+
### SHAP Narrator Integration Point
264+
265+
`generate_readiness_scores.py` already has `--enrich-with-llm` with the SHAP-aware prompt. The only change is the model string:
266+
267+
```bash
268+
# Before (OpenAI)
269+
python ai_model/generate_readiness_scores.py --enrich-with-llm --llm-model gpt-4o-mini
270+
271+
# After (fine-tuned)
272+
python ai_model/generate_readiness_scores.py --enrich-with-llm --llm-model ollama/bishop-state-narrator:4b
273+
```
274+
275+
### Environment Variables
276+
277+
```env
278+
MODEL_BACKEND=ollama # or "openai" (fallback)
279+
OLLAMA_BASE_URL=http://localhost:11434
280+
MODEL_SIZE=4b # set after evaluation picks winner
281+
SCHOOL_CODE=bishop-state
282+
```
283+
284+
### Fallback Behavior
285+
286+
The operator sets `MODEL_BACKEND` to either `ollama` or `openai`. There is no automatic failover — if Ollama is down and `MODEL_BACKEND=ollama`, the route returns an error. This is intentional: silent fallback to OpenAI would send student data to an external service without the operator's knowledge, violating the FERPA benefit.
287+
288+
## 7. Cost Estimate
289+
290+
| Item | Cost |
291+
|------|------|
292+
| Claude API distillation (~4,500 pairs across 3 tasks) | $5-10 |
293+
| Colab A100 compute (~4 hours for 2 model sizes) | $8-16 |
294+
| **Total per training run** | **$13-26** |
295+
| Iteration runs (subsequent) | $8-16 each |
296+
297+
## 8. Success Criteria
298+
299+
The epic is complete when:
300+
301+
1. All three tasks pass ship criteria on the winning model size
302+
2. `MODEL_BACKEND=ollama` serves all three tasks in the dashboard without OpenAI
303+
3. SHAP narrator produces grounded narratives that cite specific feature attributions
304+
4. Feasibility report is updated with actual metrics and model selection rationale
305+
5. Colab notebook is documented and reproducible (clone + Run All)

0 commit comments

Comments
 (0)