Skip to content

Commit 611dfd8

Browse files
mGalarnykclaude
andcommitted
Add NLP fine-tuning tutorials
Three-part fine-tuning series under NLP/Fine_Tuning: - 01_text_to_sql: Flyte pipeline + Gradio app - 02_fomc_roberta: RoBERTa hawkish/dovish classification with LoRA/QLoRA - 03_llm_8b: 8B LLM fine-tuning notebook Also adds Cursor rules/skills, an NLP-scoped .gitignore, and root .gitignore additions for secrets, venvs, model artifacts, and Flyte local state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 268624a commit 611dfd8

44 files changed

Lines changed: 7050 additions & 1 deletion

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.cursor/rules/flyte-devbox.mdc

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
description: Flyte 2 devbox locally and on the remote GPU — UI, reports, hardware
3+
alwaysApply: true
4+
---
5+
6+
# Flyte 2: same UI locally and on the GPU
7+
8+
The backend is always a **devbox**. Same console on both hosts: Summary (Sub actions, Input, Output), Logs, Metrics, Reports, Task, Code.
9+
10+
## Hosts
11+
12+
- This laptop: 16-inch MacBook Pro, Apple **M4 Max**, **128 GB**, 16-core CPU (12P + 4E), MPS. `flyte start devbox` — **never** `--gpu` (Apple Silicon unsupported).
13+
- Remote: **NVIDIA RTX PRO 6000 Blackwell, 96 GB**. One card or **four on one node** (16-core, 256 GB host). `flyte start devbox --gpu`. Never say “Dell GPU.” Do not name a university cluster.
14+
- Do **not** start remote CUDA / QLoRA / DDP until the user grants GPU access.
15+
16+
## Run modes
17+
18+
- Browser UI: `flyte run` **without** `--local` against the devbox (`-c .flyte/devbox.yaml`). UI at `http://localhost:30080`.
19+
- `--local --tui` is the **terminal only**. Those runs never appear at `:30080`.
20+
- On the GPU host: `flyte start devbox --gpu` then `FLYTE_GPUS=1 flyte run workflow.py grid` (serial) or `--parallel jobs` (1 GPU/job on a 4-GPU node). Check `flyte get devbox` first. Do not set `FLYTE_GPUS=4` until DDP exists — that would make every `train_one` request all four cards.
21+
22+
## Task shape (so the official UI is useful)
23+
24+
- Parent tasks call children → **Sub actions**. Do not flatten the DAG into one function.
25+
- Typed parameters → **Input**. Typed return values → **Output**.
26+
- `report=True` + `import flyte.report` (required; `import flyte` does not load it) + `replace.aio` / `get_tab` / `flush.aio` → **Reports**.
27+
- Same `workflow.py` everywhere. Only the invoke path and `gpu=` change.
28+
29+
Docs: [devbox](https://www.union.ai/docs/v2/flyte/user-guide/get-started/run-modes/running-devbox/), [reports](https://www.union.ai/docs/v2/flyte/user-guide/task-programming/reports/).
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
name: tutorial-infographic
3+
description: >-
4+
Generates 16:9 talk infographics for the NLP Fine_Tuning tutorials (Flyte
5+
pipeline, hawkish–dovish labels, full vs LoRA vs QLoRA). Use when creating,
6+
regenerating, or restyling those figures, or when the user asks to save or
7+
reuse the image prompt.
8+
---
9+
10+
# Tutorial infographics
11+
12+
Read [NLP/Fine_Tuning/images/PROMPT.md](NLP/Fine_Tuning/images/PROMPT.md) and use the matching prompt verbatim (plus the shared visual rules).
13+
14+
## Rules
15+
16+
- Credit every figure: original diagram for this tutorial, plus idea credit if a paper supplied the content.
17+
- Do not name a university cluster or campus in the figure or caption.
18+
- Remote GPU is always **NVIDIA RTX PRO 6000 Blackwell (96 GB)**. One card or **four on one node** (16-core, 256 GB host). Never a vague “the GPU.”
19+
- This laptop is the **16-inch MacBook Pro, Apple M4 Max, 128 GB, MPS**.
20+
- The Flyte pipeline figure must show three cards: **MacBook Pro**, **1× RTX PRO 6000**, and **4× RTX PRO 6000**.
21+
- Copy the PNG into the tutorial `images/` folder and list it in that folder’s `SOURCE.md`.

.gitignore

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,24 @@ Thumbs.db
8787
*.ckpt
8888
*.pt
8989
*.pth
90+
*.safetensors
91+
*.onnx
92+
*.gguf
9093
.cache/
9194
huggingface/
9295
sentence-transformers/
96+
97+
# ───────────────────────────────────────────────────────────────
98+
# Fine-tuning / GPU / Flyte local state
99+
# Do not commit cluster endpoints, HF caches, or checkpoints.
100+
# Tutorial-specific extras live in NLP/.gitignore.
101+
# ───────────────────────────────────────────────────────────────
102+
.flyte/
103+
**/.flyte/
104+
.union/
105+
**/.union/
106+
wandb/
107+
**/wandb/
108+
mlruns/
109+
**/lightning_logs/
110+
**/checkpoint-*/

NLP/.gitignore

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Local secrets and Hugging Face tokens
2+
.env
3+
.env.*
4+
*.env
5+
6+
# Flyte / Union CLI config — contains YOUR cluster endpoint, project, org
7+
.flyte/
8+
**/.flyte/
9+
.union/
10+
**/.union/
11+
config.yaml
12+
13+
# Hugging Face / Transformers caches (machine-specific, often tens of GB)
14+
hf_home/
15+
huggingface/
16+
huggingface_hub/
17+
transformers_cache/
18+
offload/
19+
offload_folder/
20+
**/.cache/
21+
22+
# Training outputs and experiment trackers
23+
outputs/
24+
checkpoints/
25+
runs/
26+
wandb/
27+
mlruns/
28+
lightning_logs/
29+
checkpoint-*/
30+
31+
# Model weights and adapters (regenerate from training)
32+
*.safetensors
33+
*.onnx
34+
*.gguf
35+
*.ggml
36+
pytorch_model.bin
37+
adapter_model.bin
38+
adapter_model.safetensors
39+
adapter_config.json
40+
optimizer.pt
41+
rng_state.pth
42+
scheduler.pt
43+
trainer_state.json
44+
45+
# Downloaded datasets (FOMC xlsx is CC BY-NC; do not vendor)
46+
**/data/*.xlsx
47+
48+
# Virtualenvs created inside this folder
49+
.venv/
50+
venv/
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
# Part 1 — Text-to-SQL (full / LoRA / QLoRA)
2+
3+
Fine-tune a small causal LM on text-to-SQL with **full fine-tuning**, **LoRA**, or **QLoRA**. Adapted from Union AI's [llm-fine-tuning-lora-qlora](https://github.com/unionai/workshops/tree/main/tutorials/llm-fine-tuning-lora-qlora) workshop. Default model: [SmolLM2-135M](https://huggingface.co/HuggingFaceTB/SmolLM2-135M).
4+
5+
**Why Flyte 2:** a one-off `Trainer` script is enough for a single laptop run. This tutorial is two machines and more than one kind of work. Follow along on this **MacBook Pro** (M4 Max, 128 GB, 16-core 12P+4E, MPS) with a smoke test. Union-scale samples and QLoRA run on a remote **NVIDIA RTX PRO 6000 Blackwell (96 GB)**. Part 3 swaps in an 8B model on the same DAG. Without Flyte that is three scripts. With it, one `workflow.py`: CPU `prepare_data`, GPU `train` / `evaluate`, optional serve. `flyte run --local` here; `flyte run` on the GPU. Change `--method` or `--model_name`, not the pipeline.
6+
7+
## SDK note (already Flyte 2)
8+
9+
This workshop does **not** need a Flyte 1 → 2 migration. The upstream code already uses the Flyte 2 SDK:
10+
11+
| Flyte 1 | This tutorial (Flyte 2) |
12+
|---------|-------------------------|
13+
| `import flytekit` | `import flyte` |
14+
| `@task` / `@workflow` | `TaskEnvironment` + `@cpu_env.task` / `@gpu_env.task` |
15+
| `FlyteDirectory` | `flyte.io.Dir` |
16+
| `pyflyte run` | `flyte run` |
17+
| Decks | `report=True` |
18+
19+
`requirements.txt` pins `flyte[tui]>=2.0`. See Union's [Flyte 1 → 2 guide](https://www.union.ai/docs/v2/flyte/user-guide/migration/flyte-2/) if you later port other Flyte 1 code.
20+
21+
## What's here
22+
23+
| File | Role |
24+
|------|------|
25+
| `device_utils.py` / `run_profile.py` | CUDA vs MPS vs CPU; laptop vs GPU run sizes |
26+
| `config.py` | Flyte environments — CPU for data prep, GPU for training (`gpu=0` on a Mac so `--local` works) |
27+
| `workflow.py` | prepare data → train → evaluate base vs fine-tuned |
28+
| `report_helpers.py` | HTML/SVG reports for the Flyte UI |
29+
| `serve.py` | FastAPI serving (optional, needs a Flyte cluster) |
30+
| `app_gradio.py` | Gradio UI in front of `serve.py` (optional) |
31+
| `llm-fine-tune-tutorial.ipynb` | Notebook: pipeline diagram first, then train; placeholders, not a personal GPU endpoint |
32+
| `images/flyte_pipeline.png` | High-level DAG: two machines, CPU vs GPU tasks |
33+
34+
## Setup
35+
36+
```bash
37+
cd NLP/Fine_Tuning/01_text_to_sql
38+
39+
uv venv .venv --python 3.11
40+
source .venv/bin/activate
41+
uv pip install -r requirements.txt
42+
```
43+
44+
Optional Hugging Face token for gated models (never commit `.env`):
45+
46+
```bash
47+
cp .env.example .env
48+
# then edit .env
49+
```
50+
51+
Remote Flyte/Union config is **not** committed. If you use a cluster, generate it locally:
52+
53+
```bash
54+
flyte create config \
55+
--endpoint YOUR_CLUSTER.hosted.unionai.cloud \
56+
--project YOUR_PROJECT \
57+
--domain development \
58+
--builder remote
59+
```
60+
61+
That writes `.flyte/config.yaml`, which is gitignored. A dummy layout is in `config.yaml.example`.
62+
63+
## How LoRA and QLoRA differ from full fine-tuning
64+
65+
**Full fine-tuning** updates every weight. Effective, but expensive to train, store, and deploy.
66+
67+
**LoRA** freezes the base weights `W` and trains a low-rank update `A × B`, scaled by `alpha / r`:
68+
69+
```
70+
┌─────────────────────────┐
71+
│ Original Weight W │
72+
input ────────────→ │ (frozen) │──→ main output
73+
│ └─────────────────────────┘ │
74+
│ ┌───────────┐ ┌───────────┐ │
75+
└─────────────→ │ A (d × r) │→│ B (r × d) │→ × α/r ──→ + ──→ combined
76+
└───────────┘ └───────────┘
77+
(trainable adapters)
78+
```
79+
80+
- `r` — adapter rank (capacity vs size)
81+
- `alpha` — scale; common default is `alpha = 2 * r`. Adds no extra parameters.
82+
83+
**QLoRA** keeps those adapters in higher precision but stores the frozen base in 4-bit (NF4). That 4-bit path is [bitsandbytes](https://github.com/bitsandbytes-foundation/bitsandbytes), which **requires NVIDIA CUDA** — not Apple MPS or CPU. `requirements.txt` therefore installs `bitsandbytes` only when `sys_platform != "darwin"`. On a Mac, use `lora` or `full`; `method=qlora` raises a clear error. Run QLoRA on the remote **NVIDIA RTX PRO 6000 Blackwell (96 GB)** (same pipeline).
84+
85+
On SmolLM2-135M, QLoRA is overkill (quality often drops) and is here so the same `method` flag works when you swap in a larger model.
86+
87+
LoRA targets in `workflow.py` are LLaMA-style names (`q_proj`, `k_proj`, `v_proj`, `o_proj`, `gate_proj`, `up_proj`, `down_proj`). RoBERTa uses different names — that is handled in part 2.
88+
89+
## Run
90+
91+
Start small and **local**. The notebook (`llm-fine-tune-tutorial.ipynb`) picks MPS vs CUDA and a smoke-test size automatically.
92+
93+
```bash
94+
# Mac laptop (MPS) or any machine without NVIDIA — LoRA smoke test
95+
flyte run --local workflow.py pipeline \
96+
--method lora --epochs 1 --batch_size 2 \
97+
--max_train_samples 100 --max_eval_samples 20 --num_eval_examples 10
98+
99+
# Remote NVIDIA RTX PRO 6000 Blackwell (96 GB) — Union-scale LoRA
100+
flyte run --local workflow.py pipeline --method lora --epochs 3
101+
102+
# QLoRA — NVIDIA CUDA only (bitsandbytes 4-bit; skipped on Mac)
103+
flyte run --local workflow.py pipeline --method qlora
104+
```
105+
106+
On a Mac, `recommend_jobs()` omits QLoRA. Treat CUDA as the QLoRA path.
107+
108+
On a Flyte 2 cluster you already configured (config file stays local):
109+
110+
```bash
111+
FLYTE_GPUS=1 flyte run workflow.py pipeline --method lora --epochs 3
112+
FLYTE_GPUS=4 flyte run workflow.py pipeline --method lora --epochs 3
113+
```
114+
115+
`FLYTE_GPUS` is how many NVIDIA GPUs the train task requests (default 1). Same `workflow.py` for one GPU or four on one node. Keep `FLYTE_GPUS=1` unless a single task should own the node.
116+
117+
### Useful flags
118+
119+
| Flag | Default | Meaning |
120+
|------|---------|---------|
121+
| `--model_name` | `HuggingFaceTB/SmolLM2-135M` | Hugging Face model |
122+
| `--dataset_name` | `b-mc2/sql-create-context` | Hugging Face dataset |
123+
| `--method` | `lora` | `full`, `lora`, or `qlora` |
124+
| `--epochs` | `3` | Training epochs |
125+
| `--lr` | `2e-4` | Learning rate |
126+
| `--batch_size` | `4` | Per-device batch size |
127+
| `--max_train_samples` | `5000` | Cap on train size |
128+
| `--max_eval_samples` | `500` | Cap on eval size |
129+
| `--num_eval_examples` | `50` | Examples in the before/after report |
130+
| `--lora_r` | `16` | LoRA rank |
131+
| `--lora_alpha` | `32` | LoRA alpha |
132+
133+
## Evaluation
134+
135+
The evaluate task runs the same prompts on the base model and the fine-tuned model:
136+
137+
- Exact-match accuracy on normalized SQL
138+
- Side-by-side raw outputs (base models often ramble; fine-tuned models usually stop after the query)
139+
140+
**Results live in the [notebook](llm-fine-tune-tutorial.ipynb) (section 3).** That table is full vs LoRA vs QLoRA (QLoRA only when CUDA/bitsandbytes is available). Re-run the results cell anytime — it reads `outputs/run_<method>.json`.
141+
142+
```bash
143+
python run_grid.py # smoke: full + LoRA (QLoRA if NVIDIA)
144+
python run_grid.py --no-smoke # Union-scale sample counts (RTX PRO 6000 Blackwell)
145+
```
146+
147+
## Serve (optional)
148+
149+
Only after a successful training run, and only if you have a Flyte cluster:
150+
151+
```bash
152+
python serve.py
153+
# python serve.py --run-name YOUR_RUN_NAME
154+
```
155+
156+
Point curl at **your** deployed URL, not a sample host:
157+
158+
```bash
159+
curl -X POST https://YOUR_APP_URL/generate \
160+
-H "Content-Type: application/json" \
161+
-d '{
162+
"schema": "CREATE TABLE employees (id INT, name VARCHAR, department VARCHAR, salary INT)",
163+
"question": "What is the average salary by department?"
164+
}'
165+
```
166+
167+
Gradio UI:
168+
169+
```bash
170+
SERVER_URL=https://YOUR_APP_URL python app_gradio.py
171+
```
172+
173+
## Next
174+
175+
[Part 2 — RoBERTa on FOMC hawkish–dovish](../02_fomc_roberta/) uses the same three methods on a classification task from [Trillion Dollar Words](https://arxiv.org/abs/2305.07972).
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Adapted from Union AI's public workshop (already Flyte 2):
2+
3+
https://github.com/unionai/workshops/tree/main/tutorials/llm-fine-tuning-lora-qlora
4+
5+
Code in this folder was copied and then edited so docs and notebooks do not point at a personal GPU / Union app URL. Run locally with `flyte run --local`, or generate `.flyte/config.yaml` on your machine (gitignored).

0 commit comments

Comments
 (0)