Skip to main content
Fine-tune a model in the Desktop app, then point your agent at the checkpoint it produced.
The app runs one training job at a time — two fine-tunes on one GPU do not co-exist — and the job survives closing the window, so you can reconnect and keep watching.
On Windows this required PraisonAI #4515; earlier releases killed the trainer on every relaunch.
Persistent run state (a run reappears in history after any engine restart) landed in PraisonAI #4510; earlier releases dropped the live run on restart and would start a second trainer beside it.
Quit-safety on macOS and Linux (the trainer dies with the engine on Quit, not just on Stop) landed in PraisonAI #4508; earlier releases orphaned the trainer to init and held the GPU until reboot.

Quick Start

1

Open the Fine-Tuning tab

Pick a method, a base model, and a dataset. The form collects everything the engine needs to start a run.
2

Start the run

The app posts your config to /train/start. Invalid configs are rejected immediately, before any model download begins.
3

Watch progress and stop when needed

Loss, step, and log lines stream live. Stop ends the run and releases the GPU. The checkpoint stays on disk under the run’s directory.
4

Load the checkpoint

When the run reports done, point an agent’s llm= at the checkpoint directory to chat with your fine-tuned model.

Supported Methods

The form supports these training methods. GRPO is validated but cannot be launched from the UI form — it needs reward_funcs, which the form does not collect, so run it from the command line.
Column requirements come from the dataset’s contents, so the app names them in the per-method hint rather than checking them up front. The trainer still enforces them.

How It Works

The engine writes your config, spawns a training subprocess in its own process group, and follows its output.

Run IDs

Each run becomes a directory, so run ids are unique case-insensitivelyrun-x and RUN-X are the same run because macOS and Windows filesystems fold case. Auto-generated ids append a numeric suffix, so two starts inside the same second no longer collide. Starting a run with an id that already exists returns 400 Bad Request.
Reserved Windows device names (CON, PRN, COM1…) and trailing dots are refused.

Stop Semantics

The Stop button posts to /train/stop/{run_id}, naming the run to cancel — so a stale tab cannot cancel a newer run. The GPU is released in every case. A run this engine did not spawn is only adopted when the recorded start-time fingerprint still matches the live pid. If the pid has been recycled to an unrelated process, the run is not adopted — it is already surfaced as terminal, so Stop has nothing to signal and no unrelated process group is ever SIGTERM’d from the trainer.

History & Retention

Finished runs stay in memory so the history pane can show them, within bounded caps. The full log file stays on disk.
train.log in each run directory is the complete record. The in-memory metric series is a tail for the chart, not an archive.

Preflight Validation

Invalid configs are rejected before a single byte is downloaded, so you learn about a mistake in seconds rather than after a multi-gigabyte model load.

Best Practices

An explicit run id makes the checkpoint directory predictable. Remember ids are case-insensitive — Exp-1 and exp-1 are the same run.
GRPO needs reward_funcs, which the form cannot collect. Launch it with praisonai train instead and watch it from the same engine.
praisonai-train pulls torch and unsloth, which you may keep in a separate CUDA-matched venv. Set PRAISONAI_TRAIN_CMD to choose the interpreter — --config <path> is always appended, so the override picks the interpreter, not the contract.
Progress is a ring buffer replayed from a cursor and the run’s state is written to runs/<run-id>/run.json at every transition. Close the window and reopening the run replays what you missed. A run whose child process is still alive after an engine restart reappears as running and Stop still reaches it — provided the child’s start-time fingerprint still matches the pid we recorded. A recycled pid is refused, and the run appears as interrupted instead. State files written before this fingerprint was added (pre-upgrade) are also refused; at most one run shows as interrupted across a single upgrade restart.
Adoption after a restart requires both the recorded pid and its start-time fingerprint to match the live process — a pid alone is not enough because the OS recycles pids. If it did not check, a recycled pid could be adopted as running (wedging Start behind a phantom run) and Stop could SIGTERM whatever unrelated program now holds that pid. The Rust shell guards its own engine the same way — see src-tauri/src/adopt.rs and its PidReused rejection.
The chart shows only recent metrics. For the complete series, read train.log in the run directory.
Stop names the run it cancels, so a stale tab is refused. Trigger Stop from the tab showing the run you actually want to end.

Engine API

The /train/* routes and their status codes

Settings Reference

Model, sampling, and safety fields

Chat & Streaming

Talk to your fine-tuned model

Models & API Keys

Point the app at a local checkpoint