Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
50c4bf6
feat: Phase 10 post-training completion (v0.4.0)
cemililik Apr 25, 2026
b1c04b1
fix: resolve ruff lint errors in Phase 10 modules and tests
cemililik Apr 25, 2026
93522c1
fix: correct test mock signatures for OOM recovery and safety pipeline
cemililik Apr 25, 2026
3d33c29
docs: align roadmap index with phase files after Phase 10 completion
cemililik Apr 25, 2026
7113205
fix: address Phase 10 code review findings
cemililik Apr 25, 2026
29ec185
refactor: apply round-2 review feedback for Phase 10 modules
cemililik Apr 25, 2026
ce54287
fix: address round-3 review (manifest integrity, deadlock, injection)
cemililik Apr 25, 2026
3b1efa2
chore: address SonarCloud findings (Wave 1 quick wins + Wave 2 float ==)
cemililik Apr 25, 2026
a1ce398
refactor: reduce cognitive complexity across 18 functions (Wave 4)
cemililik Apr 25, 2026
3f98af4
chore: address round-5 review feedback
cemililik Apr 25, 2026
3d330b7
chore: address Codacy/Bandit findings
cemililik Apr 25, 2026
5763087
fix: update test mocks for refactored private signatures
cemililik Apr 25, 2026
d2e0380
fix: address round-6 review (judge sentinel, safety/webhook redaction…
cemililik Apr 25, 2026
5aa69d0
refactor: address SonarCloud round-7 (workflow perms, deploy/export/w…
cemililik Apr 26, 2026
94b564b
fix: address round-8 review (GaLore/LoRA accounting, quant detection,…
cemililik Apr 26, 2026
7772727
chore: suppress Codacy/Bandit subprocess notices in export.py
cemililik Apr 26, 2026
c00f0e4
docs: post-v0.4.0 housekeeping — Phase 10 notebook + roadmap close-out
cemililik Apr 26, 2026
36d4f3d
feat: Phase 10.5 Quickstart Layer (v0.4.5)
cemililik Apr 26, 2026
8977a00
Merge remote-tracking branch 'origin/main' into development
cemililik Apr 26, 2026
9f7561c
fix: address PR #9 review findings (medium-severity hardening)
cemililik Apr 26, 2026
993d840
test: skip GRPO reward tests when trl.GRPOTrainer not importable
cemililik Apr 26, 2026
e47cec6
fix: address PR #9 round-2 review (dates, BYOD UX, math correctness, …
cemililik Apr 26, 2026
90e0c5f
feat(grpo): additive default reward — combined format/length + option…
cemililik Apr 26, 2026
c469450
fix(quickstart): PR review hardening — per-run dirs, null-safe YAML, …
cemililik Apr 26, 2026
efd88eb
fix(docs): correct generate-data phase reference in domain-expert README
cemililik Apr 26, 2026
e8fa195
chore(templates): replace approximate counts with exact values in LIC…
cemililik Apr 26, 2026
02554cf
fix(cli,wizard,ci): flag propagation, BYOD path validation, wheel-ins…
cemililik Apr 26, 2026
b3e39f0
fix(grpo): rename to max_completion_length + decimal-preserving rewar…
cemililik Apr 26, 2026
7343f57
fix(quickstart): actionable FileExistsError + absolute subprocess paths
cemililik Apr 26, 2026
b3f78d4
chore(ci,docs): wire wheel-install-smoke into notify-failure + reward…
cemililik Apr 26, 2026
94700b2
docs(roadmap): sync EN/TR mirrors + correct PyPI status + remove TR n…
cemililik Apr 26, 2026
1ef46de
fix(quickstart): ASCII glyphs + multi-GPU VRAM probe + model-selectio…
cemililik Apr 26, 2026
c3e2caa
refactor(cli,trainer,test,ci): inherited-flags helper + regex hoist +…
cemililik Apr 26, 2026
56351b9
refactor(wizard): extract BYOD dataset resolution into a helper
cemililik Apr 26, 2026
2b0df78
fix: round-7 PR review — JSON error envelopes, posix-test, reward str…
cemililik Apr 26, 2026
1c83bea
refactor: SonarCloud round — duplicated literals, cog-complexity, reg…
cemililik Apr 26, 2026
dfeb6e5
fix: SonarCloud round 2 — exit-code clamp, gold-answer presence, abs-…
cemililik Apr 26, 2026
b88225d
fix: SonarCloud round 3 — FileExistsError catch, non-string gold, rel…
cemililik Apr 26, 2026
a933021
refactor: SonarCloud round 4 — cog complexity, torch.quantile dim, ex…
cemililik Apr 26, 2026
dff8027
fix(grpo,tests): defuse ReDoS in answer regex + drop hardcoded /tmp p…
cemililik Apr 26, 2026
1319d57
chore: suppress Codacy/Bandit subprocess + AI-tooling notices with ex…
cemililik Apr 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 100 additions & 1 deletion .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,105 @@ jobs:
forgelm --config config_template.yaml --dry-run
forgelm --config config_template.yaml --dry-run --output-format json

- name: Quickstart templates smoke test
run: |
# Every bundled template must render to a valid YAML that
# passes pydantic validation. Catches template drift the
# moment a config schema changes.
#
# Note: smoke-tests 4 of 5 templates. `domain-expert` is BYOD
# (no bundled dataset), so `quickstart --dry-run` rejects it
# without `--dataset PATH`. It's covered separately by the
# pytest unit `test_domain_expert_intentionally_has_no_bundled_data`.
forgelm quickstart --list
for tpl in customer-support code-assistant medical-qa-tr grpo-math; do
echo "=== Quickstart dry-run: $tpl ==="
forgelm quickstart "$tpl" --dry-run --output "/tmp/qs-$tpl.yaml"
forgelm --config "/tmp/qs-$tpl.yaml" --dry-run
done
Comment thread
coderabbitai[bot] marked this conversation as resolved.

wheel-install-smoke:
# ------------------------------------------------------------------
# This is the only test that catches package_data globs being broken
# — editable installs always hide this. `pip install -e .` resolves
# forgelm/templates/* via Path(__file__).parent regardless of what
# setuptools would actually copy into the wheel, so a broken
# [tool.setuptools.package-data] entry is invisible until a real user
# runs `pip install forgelm` from PyPI and gets a missing-asset
# FileNotFoundError on `forgelm quickstart`.
#
# We split this into a dedicated job (rather than appending to
# test-latest-deps) because:
# 1. Building a wheel + spawning a fresh venv is unrelated to the
# "latest deps still resolve" axis and adds ~1 minute the matrix
# doesn't need to pay twice.
# 2. A failure here points unambiguously at packaging, not at a
# transitive dep bump that broke training.
# 3. We deliberately install from the wheel WITHOUT --no-deps so
# the install path matches what end-users hit.
# ------------------------------------------------------------------
name: Wheel install smoke test
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v5

- uses: actions/setup-python@v6
with:
python-version: "3.11"

- name: Build wheel
run: |
set -euo pipefail
pip install --upgrade pip build
python -m build --wheel

- name: Install wheel into a fresh venv and run quickstart from /tmp
run: |
set -euo pipefail
# Build a clean venv so nothing from the checkout's cwd leaks in.
python -m venv /tmp/wheel-test
/tmp/wheel-test/bin/pip install --upgrade pip

# Resolve the wheel glob explicitly: there must be exactly one
# forgelm-*.whl, otherwise the test environment is ambiguous.
shopt -s nullglob
wheels=(dist/forgelm-*.whl)
if [ "${#wheels[@]}" -ne 1 ]; then
echo "Expected exactly one forgelm wheel in dist/; found ${#wheels[@]}: ${wheels[*]:-(none)}" >&2
exit 1
fi
wheel="${wheels[0]}"
echo "Installing: $wheel"
/tmp/wheel-test/bin/pip install "$wheel"

# Run from /tmp specifically so the source tree is NOT on
# sys.path — any template asset must come from the wheel's
# site-packages copy, not from the checkout.
cd /tmp

echo "=== forgelm quickstart --list ==="
/tmp/wheel-test/bin/forgelm quickstart --list | tee /tmp/qs-list.txt

# Every registered template must appear in the listing.
for tpl in customer-support code-assistant domain-expert medical-qa-tr grpo-math; do
if ! grep -q "$tpl" /tmp/qs-list.txt; then
echo "MISSING template '$tpl' from quickstart --list output" >&2
exit 1
fi
done

echo "=== forgelm quickstart customer-support --dry-run ==="
/tmp/wheel-test/bin/forgelm quickstart customer-support \
--dry-run --output /tmp/wheel-qs.yaml

if [ ! -f /tmp/wheel-qs.yaml ]; then
echo "Quickstart did not materialize /tmp/wheel-qs.yaml" >&2
exit 1
fi
echo "Wheel-install smoke test passed."

test-min-deps:
name: Test with minimum supported versions
runs-on: ubuntu-latest
Expand Down Expand Up @@ -87,7 +186,7 @@ jobs:

notify-failure:
name: Notify on failure
needs: [test-latest-deps, test-min-deps]
needs: [test-latest-deps, test-min-deps, wheel-install-smoke]
if: failure()
runs-on: ubuntu-latest
permissions:
Expand Down
62 changes: 62 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,68 @@ All notable changes to ForgeLM are documented here.

## [Unreleased]

## [0.4.5] — 2026-04-26

### Added

**Quickstart Layer (Phase 10.5)** — One-command bundled templates with opinionated defaults. Primary community-growth driver: closes the gap between "I just installed ForgeLM" and "I have a fine-tuned model running locally."

- **`forgelm/quickstart.py`** — Template registry + orchestrator:
- `Template` (frozen dataclass) — `name`, `title`, `description`, `primary_model`, `fallback_model`, `trainer_type`, `estimated_minutes`, `min_vram_for_primary_gb`, `bundled_dataset`, `license_note`.
- `TEMPLATES: Dict[str, Template]` — 5 entries: `customer-support`, `code-assistant`, `domain-expert`, `medical-qa-tr`, `grpo-math`.
- `auto_select_model(template, available_vram_gb)` — picks primary model when VRAM ≥ threshold (10–12 GB), fallback otherwise; explicit `no-gpu-detected` reason when CUDA is absent.
- `_detect_available_vram_gb()` — wraps `torch.cuda.mem_get_info()`; returns `None` when no GPU (test mock point).
- `run_quickstart(template_name, *, model_override, dataset_override, output_path, dry_run, available_vram_gb)` → `QuickstartResult` — copies seed dataset, substitutes `model.name_or_path` and `data.dataset_name_or_path`, writes `configs/<template>-YYYYMMDDHHMMSS.yaml`. Generated YAML is identical in shape to a hand-written one — same trainer, same schema.
- `format_template_list()`, `summarize_result(result)` — text/JSON renderers for CLI use.

- **`forgelm quickstart <template>` CLI subcommand** (in `forgelm/cli.py`):
- `--list` — prints the registry; honors top-level `--output-format json` for CI.
- `--model <id>` — override auto-selected model.
- `--dataset <path>` — override the bundled seed dataset (required for `domain-expert`).
- `--output <path>` — custom YAML output path (default: `./configs/<template>-<timestamp>.yaml`).
- `--dry-run` — generate config only; skip training and chat.
- `--no-chat` — train but skip the post-training chat REPL.
- On a successful run, subprocess-invokes `forgelm --config <out>` and then `forgelm chat <output_dir>` (unless `--no-chat`).

- **Wizard integration** — `forgelm --wizard` now opens with "Start from a template?":
- Yes → routes to the quickstart selector; the wizard becomes a thin shell over `run_quickstart()`.
- No → falls through to the existing 8-step interactive flow.
- No bifurcation: identical code paths and YAML schema downstream.

- **5 bundled templates** under `forgelm/templates/`:
- `customer-support/` — Qwen2.5-7B-Instruct primary, SmolLM2-1.7B-Instruct fallback. SFT trainer. 60-example seed JSONL in `{"messages": [...]}` format.
- `code-assistant/` — Qwen2.5-Coder-7B-Instruct primary. SFT. 60-example Python/programming Q&A.
- `domain-expert/` — BYOD; empty data with a README explaining how to pair with `forgelm ingest` (Phase 11) or a custom JSONL.
- `medical-qa-tr/` — Turkish medical Q&A SFT. 40+ examples; every answer ends with "Tıbbi acil durumlarda 112'yi arayın..." (medical-disclaimer guardrail).
- `grpo-math/` — Qwen2.5-Math-7B-Instruct primary, Qwen2.5-Math-1.5B-Instruct fallback. GRPO trainer (`grpo_num_generations: 4`). 40 grade-school math word problems in prompt-only format.

- **Conservative defaults** in every template config:
- QLoRA 4-bit NF4, LoRA rank=8, `per_device_train_batch_size=1`, gradient checkpointing on, safety eval / compliance artifacts opt-in only.
- Designed so the smallest fallback model + the bundled seed dataset run end-to-end on a 12 GB consumer GPU.

- **`forgelm/templates/LICENSES.md`** — Full attribution for bundled seed datasets (CC-BY-SA 4.0, author-original); contributing guide for new templates; medical-disclaimer note for `medical-qa-tr`.

- **`pyproject.toml` `[tool.setuptools.package-data]`** — bundles `*.yaml`, `*.jsonl`, `*.md` under `forgelm.templates` into the wheel so `pip install forgelm` users get the templates without a source checkout.

- **Tests** — `tests/test_quickstart.py` (40 tests, GPU-independent via `_detect_available_vram_gb` mock):
- Registry consistency, get-template lookups, list ordering.
- Every template's YAML config is parseable, has `model`/`training`/`data`/`lora` sections, and uses the placeholder dataset string the quickstart layer overwrites.
- Every bundled JSONL parses as JSON line by line.
- `auto_select_model` primary/fallback/no-gpu paths.
- End-to-end `run_quickstart` flow: substitution, overrides win over auto-select, dry-run hint in summary, `domain-expert` rejects without `--dataset`.
- CLI dispatch (`--list` text + JSON, `--dry-run` writes YAML and exits 0, missing template → CONFIG_ERROR).
- **Regression**: every generated YAML round-trips through `load_config()` — the strongest guard against template drift the moment a config schema changes.

- **CI** — `.github/workflows/nightly.yml` adds a per-template smoke test that runs `forgelm quickstart <name> --dry-run` followed by `forgelm --config <out> --dry-run` for every bundled template.

### Documentation

- New "Option 0: One-Command Quickstart Template" section at the top of `docs/guides/quickstart.md`.
- `docs/roadmap.md`, `docs/roadmap-tr.md`, `docs/roadmap/phase-12-quickstart.md`, `docs/roadmap/releases.md` updated to mark Phase 10.5 as Done.
- `README.md` quickstart section updated to lead with `forgelm quickstart`.

---

## [0.4.0] — 2026-04-25

### Added
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ ForgeLM/
│ ├── safety.py # Llama Guard + harm categories + auto-revert
│ ├── compliance.py # EU AI Act Articles 9-17 + Annex IV
│ ├── webhook.py # Slack/Teams notifications
│ ├── grpo_rewards.py # Built-in GRPO format/length shaping reward fallback
│ └── ... # benchmark, judge, merging, synthetic, wizard, ...
├── tests/ # pytest, 26 test modules
├── docs/
Expand Down
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
- **LLM-as-Judge**: API-based (OpenAI) or local model scoring for quality assessment
- **Auto-Revert**: Automatically discard models that fail loss, benchmark, or safety thresholds

### Quickstart Layer (v0.4.5)
- **One-Command Templates**: `forgelm quickstart customer-support` — bundled templates for SFT, code, BYOD domain expert, Turkish medical Q&A, and GRPO math reasoning. Auto-downsizes models on small GPUs.
- **Conservative Defaults**: Every template ships QLoRA 4-bit, rank=8, batch=1, gradient checkpointing on — designed to run on a single 12 GB GPU.
- **Wizard Integration**: `forgelm --wizard` opens with "Start from a template?" — same code paths, same YAML schema as a hand-written config.

### Post-Training (v0.4.0)
- **Interactive Chat**: `forgelm chat ./model` — streaming REPL with `/reset`, `/save`, `/temperature`, `/system` commands; optional Llama Guard safety routing
- **GGUF Export**: `forgelm export ./model --quant q4_k_m` — wraps `llama-cpp-python` converter; 6 quant levels; SHA-256 appended to integrity manifest
Expand All @@ -52,7 +57,13 @@
pip install -e .
pip install -e ".[export]" # GGUF export (optional, non-Windows)

# Generate config interactively
# Fastest path: pick a bundled template (v0.4.5+)
forgelm quickstart --list
forgelm quickstart customer-support # render config + train + chat
forgelm quickstart code-assistant --dry-run # render config only
forgelm quickstart medical-qa-tr --model your-org/your-model # override

# Or generate config interactively
forgelm --wizard

# Validate without training
Expand Down
2 changes: 1 addition & 1 deletion config_template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ training:
# kto_beta: 0.1 # KTO loss parameter
# orpo_beta: 0.1 # ORPO odds ratio weight
# grpo_num_generations: 4 # GRPO: responses per prompt
# grpo_max_new_tokens: 512
# grpo_max_completion_length: 512 # legacy alias `grpo_max_new_tokens` still accepted
# grpo_reward_model: "org/reward-model" # GRPO: reward model for scoring
# report_to: "tensorboard" # "tensorboard", "wandb", "mlflow", or "none"
# run_name: "my_experiment" # W&B/MLflow run name; auto-generated if omitted
Expand Down
22 changes: 17 additions & 5 deletions docs/guides/alignment.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,23 +156,35 @@ training:
GRPO generates multiple responses per prompt, scores them, and reinforces better ones — no human preference data needed.

```json
{"prompt": "Solve: What is 15% of 240?"}
{"prompt": "Solve: What is 15% of 240?", "gold_answer": "36"}
```

```yaml
training:
trainer_type: "grpo"
grpo_num_generations: 4 # Generate 4 responses per prompt
grpo_max_new_tokens: 512 # Max response length
grpo_reward_model: null # HF path to a sequence classification model used as reward.
# ForgeLM wraps it as a callable automatically. null = TRL defaults.
grpo_num_generations: 4 # Generate 4 responses per prompt
grpo_max_completion_length: 512 # Max tokens per completion (legacy alias `grpo_max_new_tokens` still accepted)
grpo_reward_model: null # See "Reward selection" below.
learning_rate: 1.0e-6 # Very low LR for RL stability
num_train_epochs: 1

data:
dataset_name_or_path: "./data/math_prompts.jsonl"
```

### Reward selection

GRPO needs a reward signal. ForgeLM wires reward callables additively (TRL sums multiple reward funcs into a single scalar):

1. **`grpo_reward_model` set** — Loads the HF sequence-classification model at that path and uses its scalar output as the only reward signal. The built-in rewards below are bypassed; the operator opted into a learned reward.
2. **No `grpo_reward_model`** — A baseline reward is always wired:
- **`combined_format_length_reward`** (`forgelm/grpo_rewards.py`) — `0.8 × format_match + 0.2 × length_shaping`. The format component returns 1.0 when the generation ends with `Answer: <value>` (case-insensitive, units allowed); the length component returns `min(len(completion) / 200, 1.0)` so early training has a non-flat gradient even before format compliance kicks in.
- **`_math_reward_fn`** (`forgelm/trainer.py`) — appended only when the dataset has a `gold_answer` field. Captures the value after `Answer:`, strips common units (`$`, `%`, `km/h`, `m²`, `liters`, …), and compares to `gold_answer` with exact-string match first, then numeric tolerance (1e-6). Returns `1.0` for a correct answer, `0.0` otherwise.

The bundled `forgelm quickstart grpo-math` template ships with `gold_answer` populated, so the model gets both format teaching AND correctness teaching out of the box. To use a real reward model on top of grpo-math, set `grpo_reward_model` and the built-in rewards are bypassed.

For your own dataset: the format+length baseline applies regardless. Add a `gold_answer` field per row to also get the correctness signal — the prompt's expected output format is `Answer: <value>` (with optional units that get stripped).

> **Note:** GRPO requires a reward function or verifiable reward. For math, correctness of the answer is the reward. For general text, you may need a reward model.

---
Expand Down
29 changes: 29 additions & 0 deletions docs/guides/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,35 @@ pip install -e ".[qlora]"

## 2. Generate Config

### Option 0: One-Command Quickstart Template (v0.4.5+)

The fastest path: pick a bundled template and let ForgeLM pick the model, dataset, and conservative defaults for you.

```bash
# List the bundled templates
forgelm quickstart --list

# Generate a config (and a small bundled seed dataset) for a customer-support assistant
forgelm quickstart customer-support --dry-run

# Run end-to-end: render config, train, then drop into chat with the result
forgelm quickstart customer-support
```

Bundled templates (all use QLoRA 4-bit, rank-8, batch=1 by default — safe to run on a single 12 GB GPU):

| Template | Trainer | What you get |
|---|---|---|
| `customer-support` | SFT | Polite, brand-safe support replies |
| `code-assistant` | SFT | Short Python/programming Q&A |
| `domain-expert` | SFT | Empty (BYOD — pair with your JSONL) |
| `medical-qa-tr` | SFT | Turkish medical Q&A with safety disclaimers |
| `grpo-math` | GRPO | Grade-school math reasoning |

ForgeLM auto-downsizes the model on small GPUs (e.g. `Qwen2.5-7B-Instruct` → `SmolLM2-1.7B-Instruct` when < 10 GB VRAM is available). Override with `--model your-org/your-model` or `--dataset path/to/your.jsonl`.

See [LICENSES.md](https://github.com/cemililik/ForgeLM/blob/main/forgelm/templates/LICENSES.md) for the licenses of bundled seed datasets (CC-BY-SA 4.0, author-original).

### Option A: Interactive Wizard

```bash
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ across retries. Each retry attempt is logged to the audit trail.
| `kto_beta` | float | `0.1` | KTO loss parameter |
| `orpo_beta` | float | `0.1` | ORPO odds ratio weight |
| `grpo_num_generations` | int | `4` | GRPO: responses per prompt |
| `grpo_max_new_tokens` | int | `512` | GRPO: max response length |
| `grpo_max_completion_length` | int | `512` | GRPO: max tokens per completion (legacy alias `grpo_max_new_tokens` accepted) |
| `grpo_reward_model` | string | `null` | GRPO: reward model path (HF or local) |

---
Expand Down
Loading
Loading