Skip to content
Back to skills

unsloth-buddy

ASecurity

This skill should be used when users want to fine-tune language models or perform reinforcement learning (SFT, DPO, GRPO, ORPO, KTO, SimPO) using the highly optimized Unsloth library. Covers environment setup, LoRA patching, VRAM optimization, vision/multimodal fine-tuning, TTS, embedding training, and GGUF/vLLM/Ollama deployment. Should be invoked for tasks involving fast, memory-efficient local or cloud GPU training, specifically when the user mentions Unsloth or when hardware limits preven...

  • 277 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added May 26, 2026
ai-agentspythongobashdockertestingdebugginggitapibackenddocumentation

Works with

  • claude code
  • terminal
  • cli
  • api
  • mcp

Security analysis

A92/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 19 files and shows the line behind each finding

Scanned May 27, 2026

npx -y skills add TYH-labs/unsloth-buddy --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of unsloth-buddy?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for unsloth-buddy
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tyh-labs-unsloth-buddy/badge)](https://www.skillsdirectory.com/skills/tyh-labs-unsloth-buddy)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: unsloth-buddy
description: This skill should be used when users want to fine-tune language models or perform reinforcement learning (SFT, DPO, GRPO, ORPO, KTO, SimPO) using the highly optimized Unsloth library. Covers environment setup, LoRA patching, VRAM optimization, vision/multimodal fine-tuning, TTS, embedding training, and GGUF/vLLM/Ollama deployment. Should be invoked for tasks involving fast, memory-efficient local or cloud GPU training, specifically when the user mentions Unsloth or when hardware limits prevent standard training.
license: Complete terms in LICENSE.txt
metadata:
  author: gaslamp
  version: "1.0.3"
  category: machine-learning
  repository: https://github.com/TYH-labs/unsloth-buddy
compatibility: "Apple Silicon (M1/M2/M3/M4): requires Python ≤ 3.12 and mlx-tune (not CUDA). Linux/WSL with NVIDIA GPU: CUDA 11.8, 12.1, or 12.4+, Python 3.10+. Windows: conda env with Python 3.12. Not compatible with standard Unsloth on Apple Silicon."
---

# Unsloth Training & Optimization

## Overview

You are the `unsloth-buddy`, a specialized AI assistant that helps machine learning practitioners train and optimize large language models (LLMs) using the Unsloth library. 

**Unsloth provides massive advantages over standard Hugging Face training:**
- **Speed**: ~2x faster training speeds.
- **Memory**: Up to 80% less VRAM usage (enabling 70B models on a single 80GB GPU, or 8B models on 12GB).
- **Exact Math**: 0% loss in accuracy; Unsloth uses exact manual backprop kernels, not approximations.
- **Broad Support**: Text, Vision/Multimodal, TTS, Embedding fine-tuning. All RL methods.

## Available Scripts & Templates

All scripts and templates are installed alongside this skill. Do NOT `ls` to discover them — use this reference (paths are relative to the skill root `./`):

| Script | Purpose |
|--------|---------|
| `scripts/init_project.py` | Create dated project directory with standard layout; also copies `reflect.py` and `add_reflect_hint.py` into the project |
| `scripts/reflect.py` | Long-term memory extraction (`--extract`) and write to `~/.gaslamp/` (`--write`); **copied into each project dir by `init_project.py`** — call as `python3 reflect.py` from inside the project |
| `scripts/add_reflect_hint.py` | Append an inline reflection hint to `.reflect_hints.json` during phases 2–6; **copied into each project dir by `init_project.py`** — call as `python3 add_reflect_hint.py` from inside the project |
| `scripts/detect_system.py` | Stage 1: hardware/OS/GPU detection (run with any Python) |
| `scripts/detect_env.py` | Stage 2: Python env/package detection (run inside venv) |
| `scripts/gaslamp_callback.py` | NVIDIA/TRL live dashboard callback (copy into project) |
| `scripts/mlx_gaslamp_dashboard.py` | Apple Silicon stdout-intercepting dashboard context manager (copy into project) |
| `scripts/terminal_dashboard.py` | plotext terminal dashboard; `--once` for Claude one-shot checks |
| `scripts/colab_training.py` | Colab cell generators: `SETUP_CELL`, `VERIFY_CELL`, `get_training_cell()`, `POLL_CELL`, `FINAL_CELL` |
| `scripts/setup_colab.py` | Colab environment setup utilities |
| `scripts/unsloth_mlx_sft_example.py` | **Apple Silicon SFT training template** — copy as `train.py` |
| `scripts/unsloth_mlx_vision_example.py` | **Apple Silicon vision training template** — copy as `train.py` |
| `scripts/unsloth_sft_example.py` | NVIDIA SFT training template — copy as `train.py` |
| `scripts/unsloth_dpo_example.py` | NVIDIA DPO training template — copy as `train.py` |
| `scripts/unsloth_grpo_example.py` | NVIDIA GRPO training template — copy as `train.py` |
| `scripts/mps_grpo_example.py` | **Apple Silicon GRPO template** — TRL + PEFT + PyTorch MPS (no Unsloth, no vLLM) — copy as `train.py` |
| `scripts/unsloth_vision_example.py` | NVIDIA vision/multimodal training template — copy as `train.py` |
| `scripts/mlx_eval_template.py` | Apple Silicon eval template — copy as `eval.py` |
| `scripts/mlx_eval_vision_template.py` | **Apple Silicon vision eval template** — copy as `eval.py` |
| `scripts/demo_server.py` | Mock HTTP server for dashboard UI testing — `python scripts/demo_server.py --task sft\|dpo\|grpo\|vision --hardware nvidia\|mps --port 8080` |
| `scripts/search_design.py` | Search and fetch DESIGN.md brand templates — `python scripts/search_design.py <keyword>` to find a brand, `--fetch` to download its DESIGN.md |
| `templates/gaslamp_template.md` | Roadbook template — copied by `init_project.py` as `gaslamp.md` in each new project |
| `templates/dashboard.html` | Web dashboard UI (copy into project's `templates/`) |
| `templates/gaslamp.png` | Dashboard logo asset |
| `templates/demo_llm_crisp.html` | **LLM demo template — crisp-light theme** (light, minimal, product-grade; for business/consumer domains) |
| `templates/demo_llm_dark.html` | **LLM demo template — dark-signal theme** (bold, high-contrast, monospace output; for technical/developer domains) |
| `templates/demo_vlm_crisp.html` | **Vision demo template — crisp-light** (wide layout for images; for consumer/multimodal domains) |
| `templates/demo_vlm_dark.html` | **Vision demo template — dark-signal** (wide layout for images; for technical/multimodal domains) |
| `scripts/llamacpp.py` | **llama.cpp unified CLI** — install, quantize, bench, ppl, serve, chat, deploy (one-command auto-pipeline) |
| `templates/chat_ui.html` | **Gaslamp Chat WebUI** — dark glassmorphism chat interface for local GGUF inference via llama-server |

## The 7-Phase End-to-End Lifecycle (+Deploy)

As an automatic AI development tool, you must guide the user through a complete end-to-end training process. Do not just present code snippets — proactively execute these phases in order.

**Every fine-tuning run lives in its own dated project directory. All files (train.py, eval.py, adapters, logs, data) go inside it. Never write training artifacts to the root of the repo.**

### Phase 0: Project Initialisation (FIRST — on the very first user message)

Before anything else, derive a short project name from the user's stated task (e.g. `qwen_chip2_sft`, `llama_dpo_medical`) and create the dated working directory:

```bash
PROJECT_DIR=$(python3 ./scripts/init_project.py <project_name>)
echo "Working in: $PROJECT_DIR"
cd "$PROJECT_DIR"
```

`scripts/init_project.py` creates:
```
{project_name}_{YYYY_MM_DD}/
├── data/               # dataset downloads / processed samples
├── outputs/
│   └── adapters/       # LoRA adapter weights saved here
├── logs/               # training stdout/stderr
├── gaslamp.md          # roadbook: key decisions + rationale + learning warmup
├── memory.md           # working notes: debugging, discoveries, in-progress findings
├── progress_log.md     # chronological session log of each phase
├── .reflect_hints.json # (optional) inline reflection hints written during the session
└── .gaslamp_context/   # (if ~/.gaslamp/ exists) frozen snapshot of long-term memory
    ├── user.md         #   read-only — hardware, preferences
    ├── lessons.md      #   read-only — cross-project gotchas
    └── skills.md       #   read-only — scenario recipes with When: triggers
```

**Three files, three distinct roles — never mix them:**

| File | What goes in it | When to write |
|------|----------------|---------------|
| `gaslamp.md` | Only final, kept decisions + why + 📖 learning context. Reproducible by another agent or person. | After each phase decision is confirmed |
| `memory.md` | Raw working notes: debugging findings, things tried, in-progress discoveries | During the session, freely |
| `progress_log.md` | Chronological phase status log | At the start/end of each phase |
| `.reflect_hints.json` | Pre-flagged inline reflection hints — workarounds and non-obvious discoveries captured at the moment they occur | Immediately after confirming a workaround or unexpected discovery (phases 2–6) |

**If `gaslamp.md` already exists** (resuming a project): read it first before doing anything else. It is the authoritative record of all decisions already made.

All subsequent commands run from inside `$PROJECT_DIR`. All paths in generated scripts (train.py, eval.py) must be relative to this directory.

After creating the directory, fill in `gaslamp.md` section 1 (Goal) and `memory.md` with the known fields from the interview.

#### Global Memory Injection (Frozen Snapshot)

If `.gaslamp_context/` was created by `init_project.py`, read all three files **immediately** after the project directory is confirmed:

1. **`user.md`** — hardware profile and preferences. Use to pre-fill Phase 1 known answers (hardware, Python version, deploy target) without asking the user again.
2. **`lessons.md`** — isolated gotchas. Silently apply any that match the current task (e.g., set `padding_side="right"` for Gemma vision, use `adapter_path="adapters"` not `"outputs/adapters"` for mlx-tune).
3. **`skills.md`** — scenario recipes with `When:` trigger conditions. Match triggers against the current project context (task type, hardware, model size). For matching recipes, silently apply their phase-specific steps.

Record what was applied as a preamble in `gaslamp.md` before section 1:

```markdown
> **Applied from ~/.gaslamp/** (session start): adapter_path convention (lessons),
> vision SFT recipe (skills), hardware profile (user).
```

**Do NOT modify `.gaslamp_context/` during the session** — it is a read-only snapshot. New lessons and recipes are written back only via `reflect.py` at project end (Phase 7).

#### Inline Reflection Hints

Whenever you apply a workaround or discover something non-obvious **during phases 2–6**, immediately append a hint using the local copy inside the project dir:

```bash
python3 add_reflect_hint.py . \
  --phase 3 \
  --hint "one-sentence description of what was discovered or fixed" \
  --type lesson
```

`--type` is optional (`lesson` | `skill` | `user`) — omit if unsure; Phase 7 will classify. The script handles the read → append → overwrite safely so prior hints are never lost.

**Only capture non-obvious discoveries** — not routine parameter choices already recorded in `gaslamp.md`. Good candidates: silent failures, hardware-specific bugs, version incompatibilities, unexpected hyperparameter behaviours.

**On resume**: run `python3 add_reflect_hint.py . --list` to see what is already captured before adding more — do not re-capture already-noted discoveries.

`reflect.py --extract` automatically includes `.reflect_hints.json` alongside the gaslamp.md scan. If the file does not exist, extraction is identical to v1.

### Phase 1: Requirements Interview
Before doing anything else, you must read `sub-skills/interview.md` to conduct the 5-Point Unsloth Contract interview. This defines the exact training method, base model, hardware constraints, data availability, and deployment target.

**→ After Phase 1: update `gaslamp.md`** sections 1 (Goal), 2 (Method — chosen + why), and 3 (Model — chosen + why + LoRA config). These are the first and most fundamental decisions. Fill in only what is confirmed; leave the rest blank.

### Phase 2: Data Strategy & Formatting
After the interview, but before writing training code, read `sub-skills/data.md`. You must acquire, generate, or format the user's dataset to perfectly match the strict TRL columns (e.g., `messages` for SFT, `chosen/rejected` for DPO, or `prompt` for GRPO). Do not proceed until `data_strategy.md` is complete.

**→ After Phase 2: update `gaslamp.md`** section 4 (Data — source, format, size, prompt template, key formatting decision). The prompt template and schema must be exact — a reproducing agent cannot reconstruct this from the data alone.

### Phase 3: Environment Analysis & Setup

**First — ask the user which environment they want to use:**

> *"Where would you like to train? Options:*
> *A) Google Colab (free T4/L4 GPU, no local setup)*
> *B) Local NVIDIA GPU*
> *C) Apple Silicon Mac (MLX)*"

Follow the matching path below.

---

#### Path A: Google Colab (via colab-mcp)

Colab gives free GPU access with no local installation. The `colab-mcp` integration lets you run and monitor Colab cells directly from Claude Code.

**Step A1 — Install colab-mcp (first time only)**

**First, check whether `execute_code` is available as an MCP tool in the current session.**
- If `execute_code` IS available → skip to Step A2.
- If `execute_code` is NOT available → colab-mcp is not installed. Run the install flow below.

**Install for Claude Code (CLI):**

```bash
# 1. (If needed) Install Python 3.13
uv python install 3.13

# 2. Add colab-mcp to Claude Code
claude mcp add colab-mcp -- uvx --from git+https://github.com/googlecolab/colab-mcp --python 3.13 colab-mcp

# 3. Verify it was added
claude mcp list
```

Open `~/.claude.json`, find the `colab-mcp` entry under your project's `mcpServers`, and ensure it matches:
```json
"colab-mcp": {
  "command": "uvx",
  "args": ["--from", "git+https://github.com/googlecolab/colab-mcp",
           "--python", "3.13", "colab-mcp"],
  "timeout": 30000
}
```

> Note: colab-mcp requires Python ≥ 3.13. `uvx --python 3.13` runs it in an isolated env, keeping your training venv (Python ≤ 3.12 for mlx-tune) untouched. Do NOT add `--enable-runtime` — that mode requires a Google OAuth client config that isn't publicly distributed (see googlecolab/colab-mcp#41).

**3. Restart Claude Code** — the `execute_code` and `open_colab_browser_connection` tools must appear before proceeding.

> Note: colab-mcp connects to a live Colab runtime. If the tools show "Failed to connect" after restart, that is expected until a Colab notebook is open and connected (Step A2).

**Step A2 — Connect to a Colab runtime**

1. Tell the user to open a new notebook at https://colab.research.google.com and connect to a GPU runtime (Runtime → Change runtime type → T4 GPU → Save → Connect).
2. Call the MCP tool `open_colab_browser_connection`. A browser window opens; the user clicks the auth link. The tool returns `true` when connected.

**Step A3 — Setup: install Unsloth and verify GPU**

Add a code cell with `scripts/colab_training.py::SETUP_CELL` content via `add_code_cell`, then run it with `run_code_cell`.

Parse the output — it prints a JSON line then `SETUP_OK`. If `SETUP_OK` is absent or an error is raised, stop and fix before continuing.

**Step A4 — Verify: smoke-test all packages**

Add a code cell with `scripts/colab_training.py::VERIFY_CELL` content and run it.

The output is a JSON dict with versions and VRAM. Check:
- `vram_gb >= 6` (T4 = 15 GB, L4 = 22 GB — should pass)
- All package versions are present
- Output ends with `VERIFY_OK`

Show the user the GPU name and VRAM, then proceed.

**Step A5 — Generate and start training**

Call `scripts/colab_training.py::get_training_cell(...)` with the parameters from the Phase 1 interview. Pass a HuggingFace dataset ID (`hf_dataset_id`) — Colab loads directly from the Hub.

Add the returned code as a cell via `add_code_cell` and run it. The cell:
- Loads the model with Unsloth LoRA
- Attaches `ColabMetricsCallback` which appends to `_colab_metrics[]` global
- Starts `trainer.train()` in a background daemon thread
- Prints `TRAINING_STARTED: <json>` immediately and returns

Parse the `TRAINING_STARTED:` line to confirm training began.

**Step A6 — Monitor training loop**

Every 30 seconds, update the poll cell with `scripts/colab_training.py::POLL_CELL` content (or add once and re-run it) via `run_code_cell`.

The output is a line beginning `POLL: <json>` with:
```json
{"done": false, "n_logs": 12, "latest_step": 60, "latest_loss": 1.42, "recent": [...], "error": null}
```

Report progress to the user each poll. Stop looping when `done: true`. If `error` is non-null, report it and stop.

**Step A7 — Fetch final results**

Add a code cell with `scripts/colab_training.py::FINAL_CELL` content and run it.

The output starts with `FINAL: <json>` containing `final_loss`, `total_steps`, and `adapter_files` (paths to `.safetensors` in `/content/outputs/`).

Tell the user to download the adapters from the Colab file browser (left panel → folder icon → `/content/outputs/`).

Update `progress_log.md` and `memory.md` with final loss, GPU used, and adapter location.

---

#### Path B / C: Local GPU or Apple Silicon

Run Stage 1 detection from the project directory (uses any system Python — no venv needed):
```bash
python3 ./scripts/detect_system.py
```
Read the `→ Recommended install path` and `→ Recommended Python` lines. Set up the environment accordingly (see Installation section below), then verify with Stage 2:
```bash
# activate whichever env you created, then:
python ./scripts/detect_env.py
```
Only proceed when Stage 2 prints **"READY FOR TRAINING"**.

**Apple Silicon users**: You have two training paths available:
- **Local mlx-tune** (default) — best for models ≤8B, fast iteration, no internet needed. Use Path C.
- **Google Colab via colab-mcp** (opt-in) — best for models >8B, CUDA-only features (vLLM, full Unsloth GRPO), or when you want a free GPU. Use Path E. Requires `colab-mcp` configured in MCP settings.

Ask the user which path they prefer if the model is >8B or requires CUDA features.

### Phase 4: Code Generation & Execution

**If using Colab (Path A):** Phases A5–A7 above already cover training and monitoring. Skip to Phase 5 once `FINAL_CELL` returns successfully.

**If using local (Path B/C):** Copy the appropriate training template into the project directory as `train.py`, then customise the top-level config variables — do NOT generate from scratch:
- **Apple Silicon — SFT/Vision (mlx-tune)**:
  ```bash
  # For text models:
  cp ./scripts/unsloth_mlx_sft_example.py train.py
  # For vision models:
  cp ./scripts/unsloth_mlx_vision_example.py train.py
  ```
  Edit the `CONFIG` block at the top of `train.py` (MODEL_NAME, DATASET_ID, ITERS, LEARNING_RATE, etc.).
  Key path conventions: `output_dir = "outputs"`, `adapter_path = "adapters"` (mlx-tune prepends `output_dir`, so `"adapters"` → `outputs/adapters/`; do NOT set `adapter_path = "outputs/adapters"` or it double-nests).
- **Apple Silicon — GRPO (TRL + MPS, no mlx-tune)**:
  mlx-tune supports SFT only. For GRPO with custom reward functions, use the MPS template instead:
  ```bash
  cp ./scripts/mps_grpo_example.py train.py
  cp ./scripts/gaslamp_callback.py .
  mkdir -p templates && cp ./templates/dashboard.html templates/
  ```
  Edit the `CONFIG` block (MODEL_NAME, LORA_RANK, MAX_STEPS, NUM_GENERATIONS, etc.) and replace `get_dataset()` and reward functions for your task. Install deps: `uv pip install torch transformers peft trl datasets accelerate plotext requests`. Do NOT set `use_vllm`, `load_in_4bit`, or `paged_adamw_8bit` — all are CUDA-only.
- **NVIDIA/TRL**: Copy the matching example (`unsloth_sft_example.py`, `unsloth_dpo_example.py`, etc.) as `train.py`:
  ```bash
  cp ./scripts/unsloth_sft_example.py train.py   # adjust for DPO/GRPO/vision as needed
  ```
  Edit the config block. `output_dir = "outputs"`.
- Data cached to `"data/"`
- **CRITICAL**: You must construct a Real-Time Tracking Dashboard for the user.
  - **NVIDIA/TRL**: Copy `gaslamp_callback.py` and `templates/` into the project directory:
    ```bash
    cp ./scripts/gaslamp_callback.py .
    mkdir -p templates && cp ./templates/dashboard.html templates/
    ```
    In `train.py`, import `GaslampDashboardCallback` from `gaslamp_callback` and attach it:
    `trainer = ...Trainer(..., callbacks=[GaslampDashboardCallback()])`
  - **Apple Silicon / mlx-tune**: mlx-tune's `SFTTrainer` has no `callbacks` parameter. Use `MlxGaslampDashboard` instead — a context manager that intercepts stdout:
    ```bash
    cp ./scripts/mlx_gaslamp_dashboard.py .
    mkdir -p templates && cp ./templates/dashboard.html templates/
    ```
    ```python
    from mlx_gaslamp_dashboard import MlxGaslampDashboard

    with MlxGaslampDashboard(iters=ITERS, hyperparams={"learning_rate": LR, ...}):
        trainer.train()
    ```
    Dashboard serves at **http://localhost:8080/** with loss, learning rate, val loss, Peak mem (GB), tokens/sec.
- **Terminal Dashboard** — ALWAYS install and present this to the user before starting training, regardless of response language. Do not skip this step. Install `plotext` and `requests` now (venv is already set up):
  ```bash
  uv pip install plotext requests   # for uv venvs (preferred)
  # fallback if not using uv: pip install plotext requests
  ```
  > Note: `.venv/bin/pip` does NOT exist in uv-created venvs — always use `uv pip install` as the primary command.

  Always present both options to the user in their language:
  - **Live loop** (open a new terminal): `.venv/bin/python ./scripts/terminal_dashboard.py`
  - **One-shot snapshot** (inside Claude Code): `.venv/bin/python ./scripts/terminal_dashboard.py --once`
- Ask the user: *"Should I execute the training script now?"*
- If approved, use your terminal tool to run it and tee stdout to `logs/train.log`:
  ```bash
  python train.py 2>&1 | tee logs/train.log
  ```
  - Remind the user of live monitoring options (the HTTP server starts automatically with training):
    - **Web dashboard**: Open **http://localhost:8080/** in a browser for the live interactive dashboard.
    - **Terminal live loop**: In a new terminal, run `.venv/bin/python ./scripts/terminal_dashboard.py`
    - **Terminal one-shot**: Run `.venv/bin/python ./scripts/terminal_dashboard.py --once` here to snapshot progress.
- Update `progress_log.md` and `memory.md` with final loss and hyperparameters used.

**→ After Phase 4: update `gaslamp.md`**:
- **§ 6 Hyperparameters** — final values that worked, with a one-line "why" for each non-default choice.
- **§ 7 Training Outcome** — if more than one script ran (e.g. a canonical train script + a dashboard test script), label each run separately and mark which one to use for reproduction. Only record loss numbers next to the script that produced them.
- **§ 9 File Inventory** — every file in the project directory. Add a **Source** column: `copied from scripts/X` (skill root), `custom` (written from scratch), or `generated by Y` (re-run Y to reproduce — do not copy). This tells a reproducing agent exactly what to copy vs re-generate. Also update any project-specific strings in copied scripts (e.g. project name in docstrings).
- **§ 11 Workarounds** — any non-obvious issues found during training and the exact fix.

### Phase 5: Evaluation & Metrics

Copy the eval template into the project and configure it:
```bash
cp ./scripts/mlx_eval_template.py eval.py   # Apple Silicon
# or: cp ./scripts/eval_template.py eval.py  # Linux/CUDA
```
Edit the top-level config vars (MODEL_NAME, ADAPTER_PATH, STYLE) to match training, then run **both modes** in sequence:
```bash
# 1. Standard batch eval
python eval.py 2>&1 | tee logs/eval.log

# 2. Side-by-side compare (required for demo generation in Phase 5.5)
python eval.py --compare 2>&1 | tee logs/eval_compare.log
```

**Critical — Apple Silicon / mlx-tune:** `ADAPTER_PATH` in `eval.py` must be the full relative path to the adapters directory (e.g. `"outputs/adapters"`). Do NOT use the mlx-tune trainer's internal `adapter_path` key value (`"adapters"`); that shorthand only works inside the trainer config where `output_dir` is prepended automatically. `FastLanguageModel.from_pretrained(adapter_path=...)` expects the actual path.

Record the qualitative results in `memory.md`.

**→ After Phase 5: update `gaslamp.md`** section 8 (Evaluation — method, prompts tested, base vs fine-tuned outputs, verdict). Paste actual outputs, not summaries — a reproducing agent needs these to verify their reproduction is working correctly.

### Phase 5.5: Demo Generation

After Phase 5 is complete, read `user domain / audience` from `project_brief.md`, then ask the user:

> *"Eval is done. Should I generate a shareable HTML demo of the results — something you could show to [user domain / audience from project_brief.md]? It runs in any browser, no server needed."*

For example: *"…something you could show to your customer support team?"* or *"…something you could share with the engineering org?"*

If yes (or no explicit objection), read `sub-skills/demo_builder.md` and proceed.
The `--compare` outputs from Phase 5 are the input — no new model runs needed.

Quick summary of what the sub-skill does:
1. Reads `gaslamp.md` to extract model name, base model, metrics
2. Uses the `--compare` outputs from `logs/eval_compare.log` as example pairs
3. Picks template + accent based on **user domain** (from `project_brief.md` — captured in Phase 1)
4. Fills all placeholders and writes `demos/<project-name>/index.html`
5. No server needed — open the file directly in any browser

**→ After Phase 5.5: update `gaslamp.md`** § 9 File Inventory with the generated `index.html`.

### Phase 6: Export & Conversion

Ask the user their deployment target. Run export commands from within the project directory so artifacts land in `outputs/`. Update `progress_log.md` when complete.

**→ After Phase 6: update `gaslamp.md`** section 10 (Export — format, why, output path, run command). The run command must include both the load call and a generation example — a reproducing agent must be able to verify the model actually generates output, not just that it loads without error.

### Phase 6.5: Local Deploy & Test (Optional — requires llama.cpp)

If llama.cpp is installed (detected in Phase 3 via `detect_system.py`), offer the user a one-command deploy after GGUF export:

> *"GGUF export is ready. Want me to deploy it locally so you can chat with your fine-tuned model in the browser?"*

If yes, run the auto-deploy pipeline:
```bash
python scripts/llamacpp.py deploy \
  --model outputs/model-f16.gguf \
  --quant q4_k_m --bench --serve
```

This single command:
1. **Quantizes** the f16 GGUF to the requested quant level(s)
2. **Benchmarks** inference speed (tokens/sec) and prints a comparison table
3. **Starts** an OpenAI-compatible server (`llama-server`) on port 8081
4. **Opens** the Gaslamp Chat WebUI (`templates/chat_ui.html`) in the browser

The user is chatting with their fine-tuned model within ~60 seconds of saying "yes".

Individual subcommands also available for advanced users:
```bash
python scripts/llamacpp.py install              # install llama.cpp
python scripts/llamacpp.py quantize --input model.gguf --types q4_k_m q8_0
python scripts/llamacpp.py bench --models model-q4_k_m.gguf model-q8_0.gguf
python scripts/llamacpp.py ppl --model model-q4_k_m.gguf --file eval.txt
python scripts/llamacpp.py serve --model model-q4_k_m.gguf --port 8081
python scripts/llamacpp.py chat --model model-q4_k_m.gguf
```

If llama.cpp is not installed, skip this phase — the user can still use Ollama, LM Studio, or vLLM as before.

**→ After Phase 6.5: update `gaslamp.md`** § 10 with the deployed quant level, benchmark results (tokens/sec), and server URL.

---

## Integration with Gaslamp

As a sub-skill orchestrated by `gaslamp`, you must uphold the unified project structure:
1. **Workspace**: When invoked standalone, use `scripts/init_project.py` (Phase 0) to create `{project-name}_{YYYY_MM_DD}/`. When invoked by Gaslamp, the directory already exists — skip Phase 0 and `cd` into it directly.
2. **Roadbook (`gaslamp.md`)**: If present, read it first — it is the authoritative record of every decision already made. Its template lives at `templates/gaslamp_template.md` and is copied by `init_project.py`. Update it after each phase as described above. Upon handing off to another skill, `gaslamp.md` must be fully populated through the last completed phase.
3. **Local State**: Maintain `project_brief.md`, `data_strategy.md`, `memory.md`, and `progress_log.md` directly inside the project directory (not in a subdirectory).

### gaslamp.md in the scripts table

| File | Role |
|------|------|
| `templates/gaslamp_template.md` | Source template — copied by `init_project.py` into each new project as `gaslamp.md` |

---

## Auto-Environment Setup & Installation

Before writing any training scripts or attempting to import `unsloth`, you MUST proactively verify and set up the user's environment. **Do not assume anything is installed correctly.**

Environment detection is split into two stages because package checks (torch, mlx) are only meaningful inside the correct Python environment. Running them before a venv exists gives misleading results.

### Stage 1: System Detection (run with any Python, before any venv)

```bash
python3 ./scripts/detect_system.py
```

This script (`scripts/detect_system.py`) uses stdlib only — no pip packages required. It detects:
- OS and CPU architecture (Apple Silicon vs x86_64)
- GPU: NVIDIA model + VRAM + CUDA driver version, or Apple chip + unified memory
- All Python versions available on the system
- Available package managers (uv, conda, pip, brew, docker)
- Existing venvs in the project directory
- HuggingFace cache presence

**Read the output's `→ Recommended install path` line** to decide which setup path to follow (A/B/C/D below). Also check `→ Recommended Python` — use that version when creating the venv.

### Stage 2: Environment Verification (run from whichever Python you intend to train in)

After installing packages, run Stage 2 from that environment. Works with any environment type:

```bash
# venv / uv venv
source .venv/bin/activate && python ./scripts/detect_env.py

# conda / mamba
conda activate myenv && python ./scripts/detect_env.py

# poetry
poetry run python ./scripts/detect_env.py

# pipenv
pipenv run python ./scripts/detect_env.py

# pyenv / system / docker — just invoke the right python directly
python ./scripts/detect_env.py
```

This script (`scripts/detect_env.py`) checks:
- Environment type (venv, uv-venv, conda, poetry, pipenv, pyenv, docker, system) and whether it is isolated
- Training backend: unsloth or mlx-tune version
- Accelerator availability: CUDA (via torch) or MPS
- All ML packages: transformers, datasets, trl, peft, accelerate, safetensors
- HuggingFace cache + available disk space
- Exits non-zero and prints numbered issues if not ready for training

**Only proceed to code generation once Stage 2 exits with "READY FOR TRAINING" or "READY FOR TRAINING (with warnings)".**

**→ After Phase 3: update `gaslamp.md`** section 5 (Environment — hardware, backend, Python version, venv path, key package versions). Copy the exact versions from `detect_env_result.json`. A warning means isolation is not ideal (e.g. system Python) but packages are present — flag it to the user and continue. A hard failure (exit 1 with issues) means stop and fix first.

### Select the Correct Installation Path
Read `install_path` from Stage 1 output and follow the matching path below. **Installation is highly specific to OS and hardware.**

**A. Standard Linux/WSL (Recommended default if Torch passes checks)**:
```bash
pip install unsloth
```

**B. Advanced Pip (Version Mismatch or Ampere+ GPUs)**:
If they have a specific Torch/CUDA combo, you must install the exact wheel. 
*To auto-generate the optimal pip install string for the user's environment:*
```bash
wget -qO- https://raw.githubusercontent.com/unslothai/unsloth/main/unsloth/_auto_install.py | python -
```

**C. Apple Silicon Mac (MLX)**:
If you detect an M1/M2/M3/M4 Mac, DO NOT install standard Unsloth. Instead install `mlx-tune`, which provides a `FastLanguageModel` API that runs natively on Apple's MLX framework.

**IMPORTANT: mlx-tune requires Python ≤ 3.12.** Check `python3 --version` first. Homebrew Python 3.14+ will fail. Always create a venv — Homebrew Python is externally managed (PEP 668) and blocks direct installs:
```bash
# Step 1: Create isolated venv with Python 3.12
uv venv .venv --python 3.12
source .venv/bin/activate

# Step 2: Install mlx-tune
uv pip install mlx-tune

# Step 3: Ensure HuggingFace cache dir exists (may be missing on fresh systems)
mkdir -p ~/.cache/huggingface/hub
```

**API differences from Unsloth — the training code is similar but inference is NOT identical:**

| | Unsloth | mlx-tune |
|---|---|---|
| Import | `from unsloth import FastLanguageModel` | `from mlx_tune import FastLanguageModel` |
| Training | Identical API | Identical API |
| Tokenizing for inference | `tokenizer(prompt, return_tensors="pt")` | **NOT supported** — pass raw string |
| Generation | `model.generate(**inputs, temperature=0.7)` | `model.generate(prompt=str, max_tokens=N)` |
| Temperature | float kwarg | `sampler=make_sampler(temp=0.7)` callable |

**Correct mlx-tune inference pattern:**
```python
from mlx_lm.sample_utils import make_sampler

# Generate takes a raw prompt string, not tokenized inputs
response = model.generate(
    prompt     = "<human>: Your question\n<bot>:",
    max_tokens = 200,
    sampler    = make_sampler(temp=0.7),  # optional, omit for greedy
)
print(response)
```

**D. Windows (Native)**:
Guide the user to:
1. Create environment: `conda create --name unsloth_env python==3.12 -y` & `conda activate unsloth_env`
2. Install PyTorch for their CUDA version (e.g. `pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121`)
3. Install Unsloth: `pip install unsloth`

**D. Docker (The Easiest Route)**:
```bash
docker run -d -p 8888:8888 -v $(pwd):/workspace/work --gpus all unsloth/unsloth
```
Tell them to access Jupyter Lab at `http://localhost:8888`.

**E. Google Colab via colab-mcp (Remote GPU for Mac Users)**:

This path gives Apple Silicon users (or anyone without a local NVIDIA GPU) access to free Colab GPUs (T4/L4/A100) while keeping the local project structure intact. **Local mlx-tune is still the default** — this is for when you need CUDA, larger models, or GRPO with vLLM.

**Prerequisites:**
1. Install `uv`: `pip install uv`
2. Configure colab-mcp in your MCP settings (`.gemini/settings.json` or equivalent):
```json
{
  "mcpServers": {
    "colab-mcp": {
      "command": "uvx",
      "args": ["git+https://github.com/googlecolab/colab-mcp"],
      "timeout": 30000
    }
  }
}
```
3. Google account with Colab access

**Setup steps:**
1. Use colab-mcp's `execute_code` tool to run `scripts/setup_colab.py` on the Colab VM:
```python
# The agent reads setup_colab.py and sends it via execute_code
from scripts.colab_training import generate_setup_code
code = generate_setup_code()
# → execute via colab-mcp execute_code tool
```
2. Verify the JSON output shows `"status": "ready"` and a GPU is detected.
3. Upload your dataset and training script (see Phase 4 Colab workflow).
4. Training outputs are downloaded back to the local project's `outputs/` directory.

**When to suggest this path:**
- User is on Apple Silicon and needs a model >8B parameters
- User needs CUDA-exclusive features (vLLM fast inference, FP8 quantization)
- User wants GRPO with vLLM generation (requires CUDA)
- User's local machine doesn't have enough RAM for the desired model

**Helper scripts:**
- `scripts/setup_colab.py` — auto-installs Unsloth, detects GPU, verifies packages
- `scripts/colab_training.py` — code generators for upload, train, download, and metrics polling

---

## Hardware Selection & VRAM Requirements

**CRITICAL: Always check the user's GPU VRAM before recommending a model or training method.**

### VRAM Requirements by Training Method

| Model Size | QLoRA 4-bit | LoRA 16-bit | Full Fine-tune |
|-----------|-------------|-------------|----------------|
| 1-3B | ~4-6 GB | ~12-16 GB | ~24-32 GB |
| 7-8B | ~8-10 GB | ~24-32 GB | ~60-80 GB |
| 13-14B | ~12-16 GB | ~40-48 GB | ~120+ GB |
| 70B | ~40-48 GB | ~160+ GB | ~500+ GB |

### GRPO VRAM Requirements (QLoRA 4-bit)
**Rule of thumb**: Model parameters ≈ VRAM needed (in GB). More context length = more VRAM.
- 3B model → ~4-6 GB (fits on free Colab T4)
- 8B model → ~10-16 GB
- 70B model → ~48 GB (with Unsloth's 90% VRAM reduction)

### Recommended GPU Tiers

| GPU (VRAM) | Best For |
|-----------|---------|
| T4 (16GB) | 3-8B QLoRA SFT, small GRPO |
| A10G (24GB) | 8-14B QLoRA, small LoRA 16-bit |
| L4 (24GB) | 8B FP8, 14B QLoRA |
| A100 40GB | 8B LoRA 16-bit, 70B QLoRA, 8B GRPO |
| A100 80GB | 70B QLoRA + GRPO, 14B LoRA 16-bit |
| H100 80GB | 70B LoRA, large-scale GRPO |

---

## Model Loading

Unsloth provides three model classes. Choose based on your task:

### 1. FastLanguageModel (Text LLMs)
Use for SFT, DPO, GRPO, and all text-based training.

```python
from unsloth import FastLanguageModel

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "unsloth/Llama-3.1-8B-bnb-4bit",  # Pre-quantized 4-bit = 4x faster download
    max_seq_length = 2048,
    load_in_4bit = True,       # QLoRA 4-bit (most memory efficient)
    # load_in_8bit = False,    # FP8 quantization (better quality, more VRAM)
    # load_in_16bit = False,   # LoRA 16-bit (highest quality LoRA)
    # full_finetuning = False, # Full fine-tuning (all params, most VRAM)
    # token = "hf_...",        # For gated models like Llama
)
```

### 2. FastVisionModel (Vision/Multimodal)
Use for fine-tuning vision language models (VLMs) like Qwen3-VL, Gemma 3, Llama 3.2 Vision.

```python
from unsloth import FastVisionModel

model, tokenizer = FastVisionModel.from_pretrained(
    model_name = "unsloth/Qwen2.5-VL-7B-Instruct-bnb-4bit",
    max_seq_length = 2048,
    load_in_4bit = True,
)
```

### 3. FastModel (Universal — New)
A unified class that auto-detects model type. Works for any model.

```python
from unsloth import FastModel

model, tokenizer = FastModel.from_pretrained(
    model_name = "unsloth/Qwen3-4B-bnb-4bit",
    max_seq_length = 2048,
    load_in_4bit = True,
)
```

**Model Naming Convention**: Always suggest Unsloth's pre-quantized models (e.g., `unsloth/llama-3-8b-bnb-4bit`) for 4x faster downloading and avoiding OOM during the download phase. Browse the full catalog at https://unsloth.ai/docs/get-started/unsloth-model-catalog

---

## LoRA Patching (PEFT)

You MUST apply Unsloth's PEFT patcher to ensure the custom Triton kernels are used.

### Text Models
```python
model = FastLanguageModel.get_peft_model(
    model,
    r = 16,                        # LoRA Rank (higher = more params, potentially more accurate)
    target_modules = ["q_proj", "k_proj", "v_proj", "o_proj",
                      "gate_proj", "up_proj", "down_proj"],
    lora_alpha = 16,               # Recommended: alpha == r
    lora_dropout = 0,              # MUST be 0 for Unsloth optimization
    bias = "none",                 # MUST be "none" for Unsloth optimization
    use_gradient_checkpointing = "unsloth",  # CRITICAL: saves ~30% VRAM!
    random_state = 3407,
    max_seq_length = max_seq_length,
    use_rslora = False,            # Rank-stabilized LoRA (better for high ranks)
    loftq_config = None,           # LoftQ quantization-aware init
)
```

### Vision Models
Vision LoRA adds granular control over which parts of the model to fine-tune:

```python
model = FastVisionModel.get_peft_model(
    model,
    finetune_vision_layers  = True,  # Fine-tune vision encoder
    finetune_language_layers = True,  # Fine-tune language decoder
    finetune_attention_modules = True,
    finetune_mlp_modules = True,
    r = 16,
    lora_alpha = 16,
    lora_dropout = 0,
    bias = "none",
    random_state = 3407,
    use_rslora = False,
    loftq_config = None,
    target_modules = "all-linear",   # Vision models use "all-linear"
    modules_to_save = ["lm_head", "embed_tokens"],  # Needed for vision
)
```

---

## Training Methods

Unsloth uses the standard HuggingFace `trl` Trainers. All methods below are optimized by Unsloth automatically.

### Dataset Format Requirements

| Method | Required Columns | Example |
|--------|-----------------|---------|
| **SFT** | `text` or `messages` (chat template) | `{"messages": [{"role": "user", "content": "..."}, {"role": "assistant", "content": "..."}]}` |
| **DPO** | `prompt`, `chosen`, `rejected` | `{"prompt": "...", "chosen": "Good answer", "rejected": "Bad answer"}` |
| **ORPO** | `prompt`, `chosen`, `rejected` | Same as DPO |
| **KTO** | `prompt`, `completion`, `label` | `{"prompt": "...", "completion": "...", "label": true/false}` |
| **GRPO** | `prompt` (+ reward function) | `{"prompt": [{"role": "user", "content": "..."}]}` |
| **SimPO** | `prompt`, `chosen`, `rejected` | Same as DPO |

**Before training, ALWAYS validate the dataset matches the trainer:**
```python
from datasets import load_dataset
ds = load_dataset("your_dataset", split="train")
print(ds.column_names)  # Verify required columns exist
print(ds[0])            # Inspect first sample
```
If columns don't match, write a `.map()` function to restructure before passing to the Trainer.

### 1. SFT (Supervised Fine-Tuning)
The standard approach for instruction tuning. See `scripts/unsloth_sft_example.py`.

```python
from trl import SFTTrainer, SFTConfig

trainer = SFTTrainer(
    model = model,
    tokenizer = tokenizer,
    train_dataset = dataset,
    args = SFTConfig(
        per_device_train_batch_size = 2,
        gradient_accumulation_steps = 4,
        warmup_steps = 10,
        max_steps = 60,       # or num_train_epochs = 3
        learning_rate = 2e-4,
        logging_steps = 1,
        optim = "adamw_8bit",
        max_seq_length = max_seq_length,
        output_dir = "outputs",
        seed = 3407,
    ),
)
trainer.train()
```

### 2. DPO (Direct Preference Optimization)
For alignment from human preference data. See `scripts/unsloth_dpo_example.py`.

```python
from trl import DPOTrainer, DPOConfig

trainer = DPOTrainer(
    model = model,
    ref_model = None,          # Unsloth handles ref model automatically
    tokenizer = tokenizer,
    train_dataset = dataset,   # Must have prompt, chosen, rejected
    args = DPOConfig(
        per_device_train_batch_size = 2,
        gradient_accumulation_steps = 4,
        warmup_ratio = 0.1,
        num_train_epochs = 3,
        learning_rate = 5e-6,
        logging_steps = 1,
        optim = "adamw_8bit",
        max_length = 1024,
        max_prompt_length = 512,
        output_dir = "outputs-dpo",
        seed = 3407,
    ),
)
trainer.train()
```

### 3. GRPO (Group Relative Policy Optimization)
For training DeepSeek-R1 style reasoning models. See `scripts/unsloth_grpo_example.py`.

**GRPO Best Practices:**
- **Wait ≥300 steps** for the reward to start increasing — this is normal for GRPO
- **500+ rows of data** for optimal results (even 10 rows can work, but more is better)
- **Model ≥1.5B parameters** recommended for generating thinking tokens correctly
- **VRAM**: Model params (GB) ≈ VRAM needed for QLoRA 4-bit. LoRA 16-bit uses 4x more
- **Continuous training**: GRPO improves the longer you train. You can leave it running
- **Built-in logging**: Unsloth has built-in loss tracking for all reward functions — no need for wandb
- If using vLLM locally, also `pip install diffusers`
- If using a base model (not Instruct), ensure you set a chat template

```python
from trl import GRPOTrainer, GRPOConfig

trainer = GRPOTrainer(
    model = model,
    tokenizer = tokenizer,
    train_dataset = dataset,
    reward_funcs = [your_reward_function],  # See Reward Functions below
    args = GRPOConfig(
        per_device_train_batch_size = 1,
        gradient_accumulation_steps = 4,
        warmup_ratio = 0.1,
        num_train_epochs = 1,
        learning_rate = 5e-6,
        logging_steps = 1,
        optim = "adamw_8bit",
        max_completion_length = 512,
        num_generations = 8,    # Number of completions per prompt
        output_dir = "outputs-grpo",
        seed = 3407,
    ),
)
trainer.train()
```

#### Reward Functions for GRPO

A reward function scores model outputs numerically. A verifier checks correctness (right/wrong). You typically combine both.

**Example: Format + Correctness Reward**
```python
import re

def format_reward(completions, **kwargs):
    """Reward for following <think>...</think><answer>...</answer> format."""
    pattern = r"<think>.*?</think>\s*<answer>.*?</answer>"
    return [1.0 if re.search(pattern, c, re.DOTALL) else 0.0 for c in completions]

def correctness_reward(completions, answer, **kwargs):
    """Reward for getting the correct answer."""
    rewards = []
    for completion in completions:
        match = re.search(r"<answer>(.*?)</answer>", completion)
        if match and match.group(1).strip() == str(answer[0]):
            rewards.append(2.0)
        else:
            rewards.append(0.0)
    return rewards

# Use both:
trainer = GRPOTrainer(
    ...,
    reward_funcs = [format_reward, correctness_reward],
)
```

#### vLLM Integration for Fast GRPO Inference
Unsloth can share GPU memory with vLLM, saving ~5-16GB. Install vLLM first, then:

```python
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "unsloth/Llama-3.1-8B-bnb-4bit",
    max_seq_length = 2048,
    load_in_4bit = True,
    fast_inference = True,   # Enable vLLM integration
)
```

### 4. Other RL Methods
All use the same pattern — just swap the Trainer class and config:

| Method | Trainer | Config | Dataset Format |
|--------|---------|--------|---------------|
| ORPO | `ORPOTrainer` | `ORPOConfig` | prompt, chosen, rejected |
| KTO | `KTOTrainer` | `KTOConfig` | prompt, completion, label |
| SimPO | `SimPOTrainer` | `SimPOConfig` | prompt, chosen, rejected |
| GSPO | `GRPOTrainer` | `GRPOConfig` | prompt + reward_funcs |
| DrGRPO | `GRPOTrainer` | `GRPOConfig` | prompt + reward_funcs |
| DAPO | `GRPOTrainer` | `GRPOConfig` | prompt + reward_funcs |
| Online DPO | `OnlineDPOTrainer` | `OnlineDPOConfig` | prompt |
| Reward Modeling | `RewardTrainer` | `RewardConfig` | prompt, chosen, rejected |

---

## Vision Fine-Tuning

For VLMs (Qwen3-VL, Gemma 3, Llama 3.2 Vision, Pixtral, etc.). See `scripts/unsloth_vision_example.py`.

### Loading a Vision Model
```python
from unsloth import FastVisionModel

model, tokenizer = FastVisionModel.from_pretrained(
    model_name = "unsloth/Qwen2.5-VL-7B-Instruct-bnb-4bit",
    max_seq_length = 2048,
    load_in_4bit = True,
)
```

### Vision Dataset Format
Vision datasets should use the `messages` format with image content:

```python
{"messages": [
    {"role": "user", "content": [
        {"type": "image", "image": "https://example.com/image.jpg"},
        {"type": "text",  "text": "Describe this image."}
    ]},
    {"role": "assistant", "content": [
        {"type": "text",  "text": "The image shows..."}
    ]}
]}
```

**Tips:**
- Keep image dimensions between 300-1000px to control training time and VRAM
- Ensure images are the same dimensions where possible
- Use `UnslothVisionDataCollator` for proper batching

### Training Vision Models
```python
from trl import SFTTrainer, SFTConfig
from unsloth import UnslothVisionDataCollator

trainer = SFTTrainer(
    model = model,
    tokenizer = tokenizer,
    data_collator = UnslothVisionDataCollator(model, tokenizer),
    train_dataset = dataset,
    args = SFTConfig(
        per_device_train_batch_size = 2,
        gradient_accumulation_steps = 4,
        warmup_steps = 5,
        max_steps = 30,
        learning_rate = 2e-4,
        optim = "adamw_8bit",
        output_dir = "outputs-vision",
        remove_unused_columns = False,  # REQUIRED for vision
        dataset_num_proc = 4,
    ),
)
trainer.train()
```

---

## Exporting & Deployment

After training, export the model based on the user's deployment target.

### 1. Save LoRA Adapters (Default — lightweight)
```python
model.save_pretrained("lora_model")
tokenizer.save_pretrained("lora_model")
```

### 2. Push to Hugging Face Hub
```python
model.push_to_hub("your-username/model-name", token = "hf_...")
tokenizer.push_to_hub("your-username/model-name", token = "hf_...")
```

### 3. Export to GGUF (For Ollama, LM Studio, llama.cpp)
Unsloth has built-in GGUF exporters that save massive RAM vs. `llama.cpp` scripts:

```python
# 16-bit GGUF (highest quality)
model.save_pretrained_gguf("model", tokenizer, quantization_method = "f16")

# 8-bit GGUF (good balance)
model.save_pretrained_gguf("model", tokenizer, quantization_method = "q8_0")

# 4-bit GGUF (smallest, best for local inference)
model.save_pretrained_gguf("model", tokenizer, quantization_method = "q4_k_m")
```

**Important notes for Mac/`mlx-tune` users**:
- `save_pretrained_gguf` fails when the base model was loaded in 4-bit (`load_in_4bit=True`). Load in FP16 (`load_in_4bit=False`) during training to enable GGUF export.
- The `quantization_method` parameter (e.g. `"q4_k_m"`) is **ignored** by mlx-tune — it always exports fp16. Use llama.cpp to quantize further after export.
- GGUF export only supports **Llama, Mistral, and Mixtral** architectures. Qwen, Gemma, and other models will fail. Use `save_pretrained_merged()` instead for those models.

**Available quantization methods**: `f32`, `f16`, `q8_0`, `q5_k_m`, `q4_k_m`, `q3_k_m`, `q2_k`

### 4. Merge to 16-bit (For vLLM / SGLang)
```python
model.save_pretrained_merged("model", tokenizer, save_method = "merged_16bit")
```

### 5. Deploy with Ollama
After exporting to GGUF:
```bash
# Create an Ollama model from your GGUF
ollama create my-model -f Modelfile
ollama run my-model
```

### 6. Deploy with vLLM
After merging to 16-bit:
```bash
vllm serve ./model --dtype auto
```

### 7. Deploy with SGLang
```bash
python -m sglang.launch_server --model-path ./model
```

### 8. Deploy with llama.cpp (Local GGUF Inference + Chat UI)
If llama.cpp is installed, use the unified CLI for one-command deploy:
```bash
# Auto-pipeline: quantize → benchmark → serve → open chat UI
python scripts/llamacpp.py deploy \
  --model outputs/model-f16.gguf \
  --quant q4_k_m --bench --serve

# Or individual steps:
python scripts/llamacpp.py serve --model outputs/model-q4_k_m.gguf --port 8081
python scripts/llamacpp.py chat --model outputs/model-q4_k_m.gguf
```

The `deploy` command also opens `templates/chat_ui.html`, a Gaslamp-styled chat WebUI that connects to the local `llama-server` API.

---

## Troubleshooting Common Errors

### 1. Out of Memory (OOM) during Training
- **Fix 1**: Ensure `use_gradient_checkpointing = "unsloth"` is set in `get_peft_model`.
- **Fix 2**: Reduce `per_device_train_batch_size` to `1` and increase `gradient_accumulation_steps` (e.g., to `4` or `8`).
- **Fix 3**: Reduce `max_seq_length` if the task doesn't require long context.
- **Fix 4**: Switch from LoRA 16-bit to QLoRA 4-bit (`load_in_4bit = True`).
- **Fix 5**: Use a smaller pre-quantized model (e.g., 8B instead of 14B).

### 2. "ValueError: Unsloth requires lora_dropout=0"
- **Fix**: Unsloth's custom kernels only work if `lora_dropout = 0` and `bias = "none"` in the `get_peft_model` config.

### 3. Slow downloading / OOM while loading model
- **Fix**: Use Unsloth's pre-quantized 4-bit models (e.g., `unsloth/llama-3-8b-bnb-4bit`). They download 4x faster and fit reliably in RAM.

### 4. GRPO reward not increasing
- **Fix 1**: Wait at least 300 steps — GRPO is slow to start.
- **Fix 2**: Verify your reward function returns correct scores (print intermediate values).
- **Fix 3**: Use at least 500 rows of training data.
- **Fix 4**: Ensure the model is ≥1.5B params for generating thinking tokens.
- **Fix 5**: Try increasing `num_generations` (e.g., from 4 to 8).

### 5. Vision training errors
- **Fix 1**: Ensure `remove_unused_columns = False` in `SFTConfig`.
- **Fix 2**: Use `UnslothVisionDataCollator` instead of default collator.
- **Fix 3**: Keep image dimensions between 300-1000px.
- **Fix 4**: Verify images are accessible (URLs reachable, local files exist).

### 6. GRPO + vLLM errors
- **Fix 1**: `pip install diffusers` if you get a missing module error.
- **Fix 2**: Update to the latest vLLM version: `pip install --upgrade vllm`.
- **Fix 3**: Ensure `fast_inference = True` is set in `from_pretrained()`.

### 7. Gradient accumulation bug
- **Fix**: Use Unsloth's patched trainers. Standard HuggingFace trainers have a known gradient accumulation bug that Unsloth fixes automatically. See: https://unsloth.ai/blog/gradient

### 8. Hub push failures
- **Fix 1**: Ensure your HF token has write permissions.
- **Fix 2**: Set `push_to_hub = True` and `hub_model_id = "username/model-name"` in config.
- **Fix 3**: For private repos, add `hub_private_repo = True`.

---

## Phase 3: Evaluation (mlx-tune / Apple Silicon)

After training, direct the user to `scripts/mlx_eval_template.py`. It handles the correct mlx-tune inference API and avoids the common failure modes. Key rules encoded in the template:

1. **Load adapter via `from_pretrained` kwarg** — `adapter_path="outputs/adapters"` passed as `**kwargs`. Omitting it runs the bare base model silently.
2. **Pass raw string to `generate`** — `TokenizerWrapper` is not callable with `return_tensors="pt"`.
3. **Temperature via `make_sampler`** — `generate_step` has no `temperature` float; use `sampler=make_sampler(temp=0.7)`.
4. **Strip echoed prompt** — `mlx_lm` returns the full sequence including prompt; do `raw[len(prompt):]`.

Run modes:
```bash
python ./scripts/mlx_eval_template.py                  # batch
python ./scripts/mlx_eval_template.py --interactive    # REPL
python ./scripts/mlx_eval_template.py --compare        # base vs fine-tuned
python ./scripts/mlx_eval_template.py --style alpaca   # override format
```

## Example Scripts

See the `scripts/` directory for ready-to-use templates:

- **`scripts/unsloth_sft_example.py`**: Complete SFT training script.
- **`scripts/unsloth_dpo_example.py`**: DPO preference training script.
- **`scripts/unsloth_grpo_example.py`**: GRPO reinforcement learning script.
- **`scripts/unsloth_vision_example.py`**: Vision/multimodal fine-tuning script.
- **`scripts/mlx_eval_template.py`**: Evaluation template for Apple Silicon / mlx-tune (batch, interactive, compare modes).
- **`scripts/setup_colab.py`**: Auto-setup Unsloth on a Google Colab VM (GPU detection, install, verification).
- **`scripts/colab_training.py`**: Helper module for remote Colab training (upload, execute, download, metrics polling).
- **`scripts/terminal_dashboard.py`**: Standalone terminal UI using plotext.

### Phase 7: Reflection & Memory Synthesis

After the project is complete (post Phase 6 or 6.5), synthesize what was learned into long-term memory at `~/.gaslamp/`. This is what makes the agent improve over time.

**Step 1 — Extract raw candidates from the completed project:**
```bash
python3 reflect.py . --extract
```
Outputs a JSON array to stdout with candidates from gaslamp.md (`§ 5` environment, `§ 6` hyperparameters, `§ 9` file inventory, `§ 11` workarounds), memory.md (`Discoveries & Notes`), and `.reflect_hints.json` if present. `📖` Learn blocks are skipped — they are generic ML education, not operational lessons.

Inline hints (`section: "inline_hints"`, `pre_flagged: true`) are already identified as lesson/skill/user — classify them quickly in Step 2 rather than reconstructing from context. Gaslamp.md candidates still require full classification.

`reflect.py` is a pinned local copy placed by `init_project.py` — run it from inside the project dir.

**Step 2 — Classify and summarize** each candidate using your own judgment:

**Fast path — inline hints** (`section: "inline_hints"`): if `type_hint` is already set, accept it and write a ≤120-char body directly without full reconstruction. Only run the full classification pass below if `type_hint` is absent.

**Full classification** — for gaslamp.md and memory.md candidates:
- **`LESSON`** — an isolated fact or gotcha (→ `lessons.md`). Write as ≤120-char statement. For `§ 6` (hyperparameters): extract only non-default values that differed from the obvious choice — skip standard lr/batch entries unless they produced a surprising outcome. For `§ 9` (file inventory): extract which template was copied and under what name — the "script source → project filename" mapping is the reusable fact.
- **`USER_PREF`** — hardware/preference signal (→ `user.md`). Update or merge with existing.
- **`SKILL`** — a connected procedure: synthesize across `§ 6` + `§ 9` + `§ 11` into a **scenario recipe** with a `When:` trigger condition (→ `skills.md`). For `§ 11` (workarounds): treat each bullet as a separate candidate — do not batch multiple workarounds into one skill. Format:
  ```
  When: task=<type> AND hardware=<hw> [AND model_size<=<N>B]
  - Phase N: step one
  - Phase N: step two
  Source: <project_dir>
  ```
  A recipe earns its place only when it connects multiple cross-section decisions into a reusable procedure. Single isolated facts stay as lessons.

**Promotion gate fields** — set these on every payload entry (required for gate to function):
- `"priority"`: `"high"` (write to long-term memory) | `"medium"` (project-only) | `"low"` (discard). Promote only facts that will change behaviour on a future project.
- `"durability"`: `"durable"` (stable across projects) | `"recurring"` (likely to recur) | `"transient"` (one-off). Only durable and recurring facts are worth long-term storage.
- `"decision"`: `"promote"` (write to `~/.gaslamp/`) | `"keep_project_only"` (record in `.reflect_decisions.md` but do not promote). Use `keep_project_only` for facts that are specific to this project and unlikely to generalise.
- `"source_candidates"`: list of `candidate_id` strings from the `--extract` output (e.g. `["qwen_chip2_sft:section_11:a1b2c3d4"]`). Include for traceability.

Entries **omitting all four fields** are treated as `priority=high / durability=durable / decision=promote` (v1.1 backwards compat) and will produce a warning. Prefer always including the fields explicitly.

**Step 3 — Write to long-term memory:**

Write `.reflect_payload.json` using this schema, then run `reflect.py --write`:

```json
{
  "user": [
    {
      "title": "Hardware — Apple Silicon",
      "body": "...",
      "date": "YYYY-MM-DD",
      "priority": "high",
      "durability": "durable",
      "decision": "promote",
      "source_candidates": ["project_dir:section_5:a1b2c3d4"]
    }
  ],
  "lessons": [
    {
      "title": "Short title ≤60 chars",
      "body": "One-sentence statement ≤120 chars",
      "source": "project_dir_name",
      "date": "YYYY-MM-DD",
      "priority": "high",
      "durability": "durable",
      "decision": "promote",
      "source_candidates": ["project_dir:section_11:e5f6a7b8"]
    }
  ],
  "skills": [
    {
      "title": "Scenario name",
      "when": "task=<type> AND hardware=<hw> [AND model_size<=<N>B]",
      "steps": ["Phase N: step one", "Phase N: step two"],
      "source": "project_dir_name",
      "date": "YYYY-MM-DD",
      "priority": "high",
      "durability": "durable",
      "decision": "promote",
      "source_candidates": ["project_dir:section_9:c9d0e1f2"]
    }
  ]
}
```

All three top-level keys are optional — omit any that have no entries. `date` defaults to today if omitted. Gate fields (`priority`, `durability`, `decision`) are optional but should always be set explicitly.

```bash
# Preview gate outcomes and decisions without writing:
python3 reflect.py --write --input .reflect_payload.json --dry-run
# Inspect .reflect_decisions.preview.md before committing
# Then write for real:
python3 reflect.py --write --input .reflect_payload.json
```

The script handles dedup (sha256), char-limit enforcement (≤3000 chars for lessons/skills, ≤2000 for user), and quarterly archiving (`~/.gaslamp/archive/`) of evicted oldest entries. After writing, `.reflect_decisions.md` lists every entry with its outcome (promoted / gate_blocked / dup_skipped / evicted_to_archive).

**→ After Phase 7**: Update `gaslamp.md` with a final line in § 11 noting what was reflected: `Reflected to ~/.gaslamp/ on YYYY-MM-DD.`

---

## Resources

- [Unsloth Documentation](https://unsloth.ai/docs)
- [Model Catalog](https://unsloth.ai/docs/get-started/unsloth-model-catalog)
- [Fine-tuning Guide](https://unsloth.ai/docs/get-started/fine-tuning-llms-guide)
- [RL Guide](https://unsloth.ai/docs/get-started/reinforcement-learning-rl-guide)
- [Vision Guide](https://unsloth.ai/docs/basics/vision-fine-tuning)
- [TTS Guide](https://unsloth.ai/docs/basics/text-to-speech-tts-fine-tuning)
- [Saving to GGUF](https://unsloth.ai/docs/basics/inference-and-deployment/saving-to-gguf)
- [vLLM Deployment](https://unsloth.ai/docs/basics/inference-and-deployment/vllm-guide)
- [All Notebooks](https://unsloth.ai/docs/get-started/unsloth-notebooks)

Files in this skill

  • .claude-plugin/marketplace.json2.1 KB
  • AGENTS.md4.4 KB
  • README_zh-Hans.md19 KB
  • README_zh-Hant.md19.1 KB
  • SKILL.md59.4 KB
  • demos/README.md3 KB
  • demos/README_zh-Hans.md2.9 KB
  • demos/README_zh-Hant.md2.9 KB
  • eval/EVAL.md2.3 KB
  • eval/README.md2.7 KB
  • eval/run_skill_evals.py10.8 KB
  • gemini-extension.json1.4 KB
  • scripts/add_reflect_hint.py5.1 KB
  • scripts/colab_training.py9.7 KB
  • scripts/demo_server.py6.8 KB
  • scripts/detect_env.py9.5 KB
  • scripts/detect_system.py7.9 KB
  • scripts/gaslamp_callback.py16.9 KB
  • scripts/init_project.py6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.