> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Train

> Model training and fine-tuning

The `train` command group covers LLM fine-tuning and iterative agent training.

<Note>
  **Previously invisible commands.** On PraisonAI releases before [PR #4367](https://github.com/MervinPraison/PraisonAI/pull/4367), `praisonai train` exposed only 6 of the 15 subcommands, and the entire `remote` group was unreachable — you had to install `praisonai-train` on its own to use them. The integrated CLI imported only the `train` command module, so the siblings that register as an import side effect never loaded. From #4367 onwards, every subcommand documented on this page (and the new `remote` group in [Remote Training](/docs/features/praisonai-train-remote)) is reachable through the integrated CLI.
</Note>

## Usage

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai train {generate|validate|benchmark|dedup|from-trials|llm|models|agents|export|serve|list|show|apply|checkpoints|infer|remote}
```

<Note>
  Every `praisonai train <sub>` also runs as `praisonai-train <sub>` when `praisonai-train` is installed on its own.
</Note>

## Requirements

Pick the install that matches what you want to train.

| Command                              | Gets you                                                                                                      | When to use                                                     |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| `pip install praisonai-train`        | Agent training via `agents`/`list`/`show`/`apply`, no CUDA/Unsloth; litellm included for LLM-as-Judge grading | Fastest way to try LLM-as-Judge / human-feedback agent training |
| `pip install "praisonai-train[llm]"` | Above + Unsloth/torch stack for `llm`, and the `praisonai` wrapper (pulled in transitively)                   | Fine-tuning a base model                                        |
| `pip install praisonai`              | Full stack: `praisonai train ...` routes through the code tier                                                | Standard PraisonAI install                                      |
| `pip install "praisonai[train]"`     | Now equivalent to `praisonai` + `praisonai-train[llm]`                                                        | Full stack **plus** fine-tuning deps                            |

<Note>
  `pip install "praisonai[train]"` previously installed nothing (empty extra). It now pulls `praisonai-train[llm]`, so it installs the Unsloth stack.
</Note>

<Note>
  As of [PR #4367](https://github.com/MervinPraison/PraisonAI/pull/4367), the `[llm]` extra pulls in the `praisonai` wrapper transitively — the `llm` subcommand imports it during `parse_args`. You no longer need to `pip install praisonai` separately for `praisonai-train[llm]` to work; before this, `pip install "praisonai-train[llm]"` produced an `llm` command that died at `parse_args`.
</Note>

<Note>
  The `[llm]` extra now needs the modern base stack: `torch>=2.6.0`, `unsloth>=2025.9.1`, `trl>=0.18.2`, `transformers>=4.51.3`. Pinning `torch<2.6` is why install can fail — upgrade torch first. Tested on Python 3.11 with pytorch-cuda 12.4.
</Note>

***

## `praisonai train generate`

Synthesize an instruction dataset from a teacher LLM.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai train generate [OPTIONS]
```

### Options

| Option             | Short | Description                                                  | Default |
| ------------------ | ----- | ------------------------------------------------------------ | ------- |
| `--config`         | `-c`  | YAML config file (all keys below can live here)              | -       |
| `--output`         | `-o`  | Output JSONL file (**required**)                             | -       |
| `--recipe`         | `-r`  | Recipe name (e.g. `tamil`) or an inline dict                 | `tamil` |
| `--deployment`     | `-d`  | Teacher model / Azure deployment name (**required**)         | -       |
| `--num`            | `-n`  | Number of examples to generate (**required**)                | -       |
| `--concurrency`    | -     | Parallel teacher requests                                    | `32`    |
| `--start-offset`   | -     | Prompt-index offset for disjoint parallel workers            | `0`     |
| `--snapshot-every` | -     | Copy the output to `snapshots/{stem}_{n}.jsonl` every N rows | -       |

### YAML-only keys

These have no CLI flag — set them in `--config`.

| Key                     | Type   | Default                                         | Description                                         |
| ----------------------- | ------ | ----------------------------------------------- | --------------------------------------------------- |
| `endpoint`              | `str`  | env `AZURE_OPENAI_ENDPOINT` / `OPENAI_BASE_URL` | Chat completions endpoint                           |
| `api_key`               | `str`  | env `AZURE_OPENAI_KEY` / `OPENAI_API_KEY`       | Bearer / Azure api-key                              |
| `azure`                 | `bool` | auto                                            | Force Azure vs OpenAI routing                       |
| `api_version`           | `str`  | `2024-10-21`                                    | Azure only                                          |
| `max_completion_tokens` | `int`  | `2048`                                          | Teacher `max_completion_tokens`                     |
| `request_timeout`       | `int`  | `120`                                           | Per-request HTTP timeout (seconds)                  |
| `dedup_from`            | `list` | `[]`                                            | JSONL paths whose `instruction` values are excluded |
| `stop_file`             | `path` | `~/.praisonai_train_stop`                       | Touch this file to halt immediately                 |
| `snapshot_dir`          | `path` | `snapshots`                                     | Directory for `--snapshot-every` snapshots          |

### Examples

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# One-command dataset
praisonai train generate -r tamil -d gpt-4o -n 1000 -o data/tamil.jsonl

# Drive everything from YAML
praisonai train generate --config generate.yaml

# Disjoint parallel slice (offset past the first worker)
praisonai train generate -r tamil -d gpt-4o -n 5000 --start-offset 5000 -o data/b.jsonl
```

<Note>
  `output` and `num_examples` are validated up-front. `generate` writes to a sibling temp file and `os.replace`s the destination only when **at least one row is produced** — a run that fails on credentials or every teacher request leaves any existing file untouched (the swap also preserves the destination's mode and follows a symlink target). Zero unique rows exits `1`. Fixed in [PraisonAI PR #4239](https://github.com/MervinPraison/PraisonAI/pull/4239).
</Note>

See [Dataset Tooling](/docs/features/praisonai-train-dataset-tooling) for recipes, dedup, snapshots, and the `generate_dataset()` Python API.

***

## `praisonai train validate`

Quality-check and filter an instruction dataset (dedup, boilerplate/refusal, script purity, diversity).

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai train validate [OPTIONS] DATASET
```

### Options

| Option                 | Short | Description                                         | Default |
| ---------------------- | ----- | --------------------------------------------------- | ------- |
| `dataset` (positional) | -     | JSONL dataset to validate (or `input:` in config)   | -       |
| `--config`             | `-c`  | YAML config for thresholds                          | -       |
| `--out`                | `-o`  | Write filtered (kept) rows to this JSONL            | -       |
| `--no-near-dup`        | -     | Skip the O(n²) near-dup pass on very large datasets | `false` |

### YAML-only keys

| Key                | Type         | Default                      | Description                                            |
| ------------------ | ------------ | ---------------------------- | ------------------------------------------------------ |
| `near_dup`         | `bool`       | `true`                       | Enable near-duplicate detection                        |
| `near_dup_jaccard` | `float`      | `0.7`                        | 4-gram Jaccard threshold (≈ Self-Instruct ROUGE-L 0.7) |
| `min_output_chars` | `int`        | `20`                         | Below this, output is dropped as `too_short`           |
| `script_range`     | `[int, int]` | `[2944, 3071]` (Tamil block) | Unicode range that defines the target script           |
| `script_drop`      | `float`      | `0.5`                        | Drop rows below this script purity                     |
| `script_flag`      | `float`      | `0.7`                        | Flag (but keep) rows between `script_drop` and this    |

### Examples

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Validate and write the clean rows
praisonai train validate data/tamil.jsonl --out data/clean.jsonl

# Drive thresholds from YAML
praisonai train validate --config validate.yaml

# Skip the O(n^2) near-dup pass on a large dataset
praisonai train validate data/big.jsonl --no-near-dup --out data/clean.jsonl
```

The command prints an `in / kept / drops / flags / metrics` report to stdout. Malformed JSONL lines are skipped with a warning — the run still succeeds on the remaining rows.

<Note>
  `validate` writes `--out` atomically (temp file + `os.replace`), preserves the destination's file mode when overwriting, and follows symlink targets — mirroring `generate`/`dedup`. **Zero-kept exits `1`** with `error: no rows kept — all rows were dropped by QC ({drops})`; the destination is never truncated, so an `--out` that aliases the input keeps its existing bytes. Fixed in [PraisonAI PR #4316](https://github.com/MervinPraison/PraisonAI/pull/4316).
</Note>

See [Dataset Tooling](/docs/features/praisonai-train-dataset-tooling) for the full check list and the `score()` / `filter_rows()` Python API.

<Card title="Export trials with QC (from-trials --qc)" icon="filter" href="/docs/features/praisonai-train-dataset-tooling#export-trials-with-qc-from-trials-qc">
  `from-trials --qc` reuses this QC filter but auto-disables the Tamil script check for English agent trajectories — pass `qc_cfg={"script_range": [...]}` to opt back in for another language.
</Card>

***

## `praisonai train benchmark`

Measure and rank generation speed across LLM deployments.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai train benchmark [OPTIONS]
```

### Options

| Option                           | Short | Description                                                                   | Default        |
| -------------------------------- | ----- | ----------------------------------------------------------------------------- | -------------- |
| `--config`                       | `-c`  | YAML config file                                                              | -              |
| `--deployment`                   | `-d`  | Deployment/model to benchmark (repeatable; **required** via flag or config)   | -              |
| `--n`                            | `-n`  | Requests per deployment                                                       | `24`           |
| `--concurrency`                  | -     | In-flight requests per deployment                                             | `8`            |
| `--api-version`                  | -     | Azure OpenAI api-version                                                      | `"2024-10-21"` |
| `--max-tokens`                   | -     | `max_completion_tokens` per request                                           | `2048`         |
| `--recipe`                       | `-r`  | Recipe supplying the default prompt                                           | `"tamil"`      |
| `--json-mode` / `--no-json-mode` | -     | Send the `response_format` JSON hint; disable for endpoints without JSON mode | `True`         |
| `--output`                       | `-o`  | Write ranked results as JSON                                                  | -              |

### YAML-only keys

| Key               | Type   | Description                                                                    |
| ----------------- | ------ | ------------------------------------------------------------------------------ |
| `endpoint`        | `str`  | Shared endpoint for every target (falls back to `AZURE_OPENAI_*` / `OPENAI_*`) |
| `api_key`         | `str`  | Shared API key                                                                 |
| `azure`           | `bool` | `true` for Azure OpenAI, `false` for OpenAI-compatible                         |
| `prompt`          | `dict` | Explicit `{system, user}` prompt sent on every request                         |
| `request_timeout` | `int`  | Per-request timeout in seconds (default `180`)                                 |

### Examples

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Compare two deployments from the environment credentials
praisonai train benchmark -d gpt-4o -d gpt-4o-mini --n 24 --concurrency 8 -o bench.json

# Drive everything from YAML
praisonai train benchmark --config benchmark.yaml

# OpenAI-compatible endpoint without JSON mode
praisonai train benchmark -d llama-3.1-70b --no-json-mode
```

The command prints each target as it finishes, then a table ranked by `rows/min`. `n < 1` or `concurrency < 1` raises `ValueError`; if every request fails the CLI exits `1`.

See [Speed Benchmark](/docs/features/praisonai-train-benchmark) for the `benchmark_deployments()` Python API and cross-endpoint benchmarking.

***

## `praisonai train dedup`

Merge many JSONL files into one deduped file with a single shared exact + MinHash/LSH index — catching the cross-file near-duplicates that per-file `validate` always misses.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Cross-file dedup"
        A[📁 batch_*.jsonl] --> B[🔐 sha1 exact]
        B --> C[🧬 MinHash + LSH]
        C --> D[💾 merged.jsonl]
    end

    classDef input fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef out fill:#10B981,stroke:#7C90A0,color:#fff

    class A input
    class B,C process
    class D out
```

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai train dedup FILES... --out MERGED.jsonl \
    [--method minhash|sliding] \
    [--threshold 0.7] \
    [--exact-only]
```

### Options

| Option                  | Type         | Default      | Description                                                                                                                                               |
| ----------------------- | ------------ | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `FILES...` (positional) | `list[Path]` | —            | One or more JSONL files to merge and dedup across. Glob expansion happens in the shell (`batch_*.jsonl`).                                                 |
| `--out`                 | `Path`       | **required** | Path to write the merged, deduplicated JSONL.                                                                                                             |
| `--method`              | `str`        | `minhash`    | Near-dup engine — `minhash` (scalable MinHash + LSH) or `sliding` (bounded look-back window).                                                             |
| `--threshold`           | `float`      | `0.7`        | Jaccard threshold above which two rows are treated as near-duplicates.                                                                                    |
| `--exact-only`          | flag         | `false`      | Skip near-dup detection — only sha1 exact matches are removed. Fastest mode.                                                                              |
| `--config`              | `Path`       | —            | YAML supplying the same keys (`near_dup_method`, `near_dup_window`, `minhash_perm`, `ngram_n`, `minhash_seed`, `near_dup_jaccard`) for reproducible runs. |

### Examples

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Merge parallel-generation batches, remove exact + near-duplicates
praisonai train dedup batch_*.jsonl --out data/merged.jsonl

# Exact-only, fastest path (no near-dup pass)
praisonai train dedup a.jsonl b.jsonl c.jsonl --out merged.jsonl --exact-only
```

Drive it from YAML for a reproducible config:

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# dedup.yaml
inputs: [batch_1.jsonl, batch_2.jsonl, batch_3.jsonl]
out: data/merged.jsonl
near_dup_method: minhash
near_dup_jaccard: 0.75
minhash_perm: 128
ngram_n: 4
minhash_seed: 1
```

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai train dedup --config dedup.yaml
```

The command prints a `in / kept / removed` stats block on completion and exits non-zero only on IO errors. The write is atomic — an `--out` that aliases an input is never truncated before its rows are read.

<Card title="Cross-file dedup (Python API)" icon="database" href="/docs/features/praisonai-train-dataset-tooling#cross-file-dedup">
  The `global_dedup()`, `near_dedup()`, and `MinHashLSH` Python API behind this command.
</Card>

***

## `praisonai train data from-trials`

Export the passing attempts from a scored trials report into a trainer-ready SFT dataset (plus a provenance sidecar) — the "keep what verified, then fine-tune on it" step of the trials loop.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai train data from-trials REPORT [OPTIONS]
```

### Options

| Flag                  | Short | Type                   | Default               | Description                                                                                 |
| --------------------- | ----- | ---------------------- | --------------------- | ------------------------------------------------------------------------------------------- |
| `REPORT` (positional) | —     | path                   | **required**          | Serialised trials-report JSON                                                               |
| `--out`               | `-o`  | path                   | `{report_stem}.jsonl` | Output JSONL                                                                                |
| `--all`               | —     | flag                   | `false`               | Include failed / unscored attempts (`only_passed=False`)                                    |
| `--include-saturated` | —     | flag                   | `false`               | Include all-pass / zero-pass cases (`frontier_only=False`)                                  |
| `--format`            | —     | `messages` \| `alpaca` | `messages`            | `messages` = ShareGPT `{"conversations": [...]}`; `alpaca` = `{instruction, input, output}` |
| `--qc`                | —     | flag                   | `false`               | Run emitted rows through the same QC filter as `validate`                                   |

### Examples

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Basic: keep passing, tool-free, frontier attempts as ShareGPT rows
praisonai-train data from-trials trials.json -o data/train.jsonl

# Small report: include failed/unscored attempts and saturated cases
praisonai-train data from-trials trials.json --all --include-saturated -o data/train.jsonl

# Alpaca instead of ShareGPT
praisonai-train data from-trials trials.json --format alpaca -o data/train.jsonl

# Run the exported rows through the same QC filter as validate
praisonai-train data from-trials trials.json --qc -o data/train.jsonl
```

Every run also writes `{out}.jsonl.meta.json` — a provenance sidecar mapping each emitted row to its source case, attempt, and score.

<Note>
  **Exit code `1` when zero rows are exported.** The stderr message hints toward `--all` / `--include-saturated`, or to check that the report has passing tool-free attempts. Every skip is counted, never silent — the summary block names `skipped_failed`, `skipped_tool_runs`, `skipped_unscored`, `skipped_saturated`, and `skipped_no_text`.
</Note>

<Card title="from-trials (feature guide + Python API)" icon="recycle" href="/docs/features/praisonai-train-dataset-tooling#from-trials">
  The selection discipline, `export_trials()` Python API, provenance sidecar, and the rejection-sampling caveat.
</Card>

***

## `praisonai train llm`

Fine-tune an LLM using Unsloth.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai train llm [OPTIONS] [DATASET]
```

Pass a dataset path, or `--config` a file that names one — one of the two is required.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Config resolution"
        Defaults[⚙️ Defaults] --> File[📄 config.yaml]
        File --> Flags[🚩 CLI flags]
        Flags --> Preview[🔍 --dry-run<br/>preview]
        Flags --> Train[🚀 train]
    end

    classDef base fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef file fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef flag fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef out fill:#10B981,stroke:#7C90A0,color:#fff

    class Defaults base
    class File file
    class Flags flag
    class Preview,Train out
```

### Options

| Option             | Short | Description                                                                                                                                                                                         | Default              |
| ------------------ | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
| `--model`          | `-m`  | Base model to fine-tune — Llama, Gemma, Qwen, Mistral, and Phi bases are validated                                                                                                                  | -                    |
| `--config`         | `-c`  | Training config YAML. The flags below override it. See [Config keys for LLM fine-tuning](#config-keys-for-llm-fine-tuning) for every key a config file may set.                                     | -                    |
| `--dry-run`        | -     | Print the resolved config as YAML (including the settled `remote:` block, with credentials redacted) and exit, without training. Bad remote settings are caught here too. Does not load the runner. | `false`              |
| `--method`         | -     | Training method — `sft \| cpt \| dpo \| orpo \| kto \| grpo` (`method`)                                                                                                                             | -                    |
| `--max-seq-length` | -     | Sequence length (`max_seq_length`)                                                                                                                                                                  | -                    |
| `--epochs`         | -     | Epochs (`num_train_epochs`). Ignored if `--max-steps` is set.                                                                                                                                       | -                    |
| `--max-steps`      | -     | Stop after this many steps (`max_steps`)                                                                                                                                                            | -                    |
| `--learning-rate`  | -     | Learning rate (`learning_rate`)                                                                                                                                                                     | -                    |
| `--batch-size`     | -     | Per-device batch size (`per_device_train_batch_size`)                                                                                                                                               | -                    |
| `--grad-accum`     | -     | Gradient accumulation steps (`gradient_accumulation_steps`)                                                                                                                                         | -                    |
| `--lora-r`         | -     | LoRA rank (`lora_r`)                                                                                                                                                                                | -                    |
| `--lora-alpha`     | -     | LoRA alpha (`lora_alpha`)                                                                                                                                                                           | -                    |
| `--output-dir`     | -     | Where checkpoints go (`output_dir`)                                                                                                                                                                 | -                    |
| `--chat-template`  | -     | Chat template name (`chat_template`)                                                                                                                                                                | -                    |
| `--remote-host`    | -     | SSH alias to train on, as in `~/.ssh/config` (`remote.host`). Omit to train locally.                                                                                                                | -                    |
| `--remote-python`  | -     | Interpreter on the remote host (`remote.python`)                                                                                                                                                    | `python3`            |
| `--remote-workdir` | -     | Directory on the remote host to work in (`remote.workdir`)                                                                                                                                          | `~/.praisonai-train` |
| `--remote-gpus`    | -     | How many GPUs the remote run expects to find (`remote.gpus`)                                                                                                                                        | `1`                  |
| `--verbose`        | `-v`  | Verbose output                                                                                                                                                                                      | `false`              |

<Note>
  The four `--remote-*` flags send this one run to a remote GPU box instead of training locally — the same as a `remote:` block in `config.yaml` or the desktop "Run on" dropdown. Omit `--remote-host` (and set no `remote.host`) to train here as before. See [Remote in one step](/docs/features/praisonai-train-inline-remote).
</Note>

<Note>
  `--config` sets the baseline, the tuning flags override it. Run with `--dry-run` first to see the resolved config the trainer would use — file first, flags on top. Everything not covered by a flag still lives in the [config file](#config-keys-for-llm-fine-tuning), reachable via `--config`. Key config options include `method` (choose `sft`, `dpo`, `orpo`, or `kto`), `chat_template` (pick the model's chat-template family; omit to use the built-in), and `assistant_only_loss` (mask prompts out of the loss; auto-picks a masking route for any known template family).
</Note>

### Precedence

The resolved config is built in three layers — each layer overrides the one above.

| Order | Source                            | Sets                  |
| ----- | --------------------------------- | --------------------- |
| 1     | Bare defaults (from `TrainModel`) | Baseline values       |
| 2     | `--config config.yaml`            | Your saved config     |
| 3     | Tuning flags on the command line  | The keys you override |

An untouched config key survives when a different key is overridden by a flag.

### Preview a run with `--dry-run`

`--dry-run` prints the resolved config as YAML you can paste back into a file, then a comment listing which keys came from flags — and exits without training.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai-train llm -c config.yaml --lora-r 32 --epochs 2 --dry-run
```

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
lora_r: 32
max_seq_length: 2048
model_name: unsloth/gemma-2-2b-it-bnb-4bit
num_train_epochs: 2.0

# 2 value(s) came from flags: lora_r, num_train_epochs
```

When `--remote-host` (or `--remote-python`, `--remote-workdir`, `--remote-gpus`) is on the command line, the settled `remote:` block appears in the printed YAML with credentials redacted — the host, interpreter, workdir, and GPU count the run would actually use:

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai-train llm -c config.yaml --remote-host gpubox --dry-run
```

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
lora_r: 32
model_name: unsloth/gemma-2-2b-it-bnb-4bit
remote:
  gpus: 1
  host: gpubox
  python: python3
  workdir: ~/.praisonai-train
```

A bad `remote:` block is caught here too: a malformed block exits `1` with `Bad remote settings` at `--dry-run`, not after dispatch.

`--dry-run` does not load the training runner, so it works even without the `[llm]` extra installed.

### Supported models

The trainer uses each model's own chat template, so `chat_template` is optional — set it only to override.

| Model family | Example `--model`                             | Optional `chat_template` |
| ------------ | --------------------------------------------- | ------------------------ |
| Llama        | `unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit` | `llama-3.1` / `llama-3`  |
| Gemma        | `unsloth/gemma-2-2b-it-bnb-4bit`              | `gemma` / `gemma-2`      |
| Qwen         | `unsloth/Qwen2.5-0.5B-Instruct-bnb-4bit`      | `qwen-2.5` / `qwen-3`    |
| Mistral      | `unsloth/mistral-7b-instruct-v0.3-bnb-4bit`   | `mistral`                |
| Phi          | `unsloth/Phi-3-mini-4k-instruct-bnb-4bit`     | `phi-3`                  |

<Card title="Full chat_template reference" icon="graduation-cap" href="/docs/train#model--template-keys">
  Types, defaults, and the fail-fast `ValueError` when no template is available.
</Card>

<Note>
  The five families above are curated starting points, not the whole list. Run [`praisonai train models`](#praisonai-train-models) to see every model the trainer can load (\~246 `unsloth/…` repos), or `praisonai train models gemma` to filter by family.
</Note>

### Examples

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Fine-tune with a dataset
praisonai train llm dataset.json

# Fine-tune with a specific base model
praisonai train llm --model llama-3.1 dataset.json

# Fine-tune Gemma or Qwen with their native templates (no config needed)
praisonai train llm dataset.json --model unsloth/gemma-2-2b-it-bnb-4bit
praisonai train llm dataset.json --model unsloth/Qwen2.5-0.5B-Instruct-bnb-4bit

# Override tuning knobs straight from the CLI
praisonai-train llm data.jsonl --lora-r 32 --epochs 2

# Start from a config file, override one key
praisonai-train llm -c config.yaml --max-seq-length 4096

# Preview the resolved config without training
praisonai-train llm -c config.yaml --dry-run
```

<Warning>
  Pass a dataset path **or** a `--config` file that names one (via `dataset:`). With neither, the CLI refuses:

  ```
  error: No dataset given
  remediation: Pass a dataset path, or --config a file that names one.
  ```

  Exit `1` — "I forgot the dataset" never silently trains against the default Alpaca corpus.
</Warning>

<Note>
  `method: cpt` with `modules_to_save: [embed_tokens, lm_head]` enables continued pretraining on a raw-text corpus (a dataset with a `text` column). See [Continued Pretraining](/docs/features/praisonai-train-cpt).
</Note>

<Note>
  The `DATASET` positional and `--model` option are materialised into `./config.yaml` in the invocation directory, and that file is the **sole** input to the trainer — the CLI passes no overrides on top. What `--dry-run` previews is exactly what trains. See [Where config.yaml comes from](#where-config-yaml-comes-from). Fixed in [PR #4367](https://github.com/MervinPraison/PraisonAI/pull/4367).
</Note>

<Note>
  `praisonai-train llm <dataset>` (and the wrapper route `praisonai train llm <dataset>`) now actually launches the trainer. On PraisonAI releases before [PR #4317](https://github.com/MervinPraison/PraisonAI/pull/4317) the legacy dispatcher had no `train` case, so the command silently forwarded the literal word `"train"` to an LLM as a chat prompt, exited `0`, and never generated a `config.yaml` or launched the trainer subprocess. If a previous run appeared to succeed instantly without producing `lora_model/` or a `praisonai-train show` session, upgrade and re-run.
</Note>

<Warning>
  On PraisonAI releases between [#4239](https://github.com/MervinPraison/PraisonAI/pull/4239) and [#4367](https://github.com/MervinPraison/PraisonAI/pull/4367), passing `--dataset` and `--model` on the CLI could quietly overwrite tuning parameters (`lora_r`, `epochs`, `max_seq_length`, `method`) and flip `huggingface_save` on with the default `hf_model_name` — which is **not yours**. The forwarded flags put the legacy dispatcher on its "regenerate config.yaml from defaults" branch:

  | asked for                 | actually trained                       |
  | ------------------------- | -------------------------------------- |
  | `lora_r: 64`              | `16`                                   |
  | `epochs: 5`               | `1`                                    |
  | `max_seq_length: 8192`    | `2048`                                 |
  | `method: dpo`             | `sft`                                  |
  | `huggingface_save: false` | `true`, pushed to a third party's repo |

  If you ran `praisonai train llm <dataset> --model <base>` on that range and the run finished with tiny LoRAs, one epoch, or a Hub push you did not expect, this is why. Fixed in #4367 by no longer forwarding those flags.
</Warning>

<Note>
  **Vision configs no longer publish by default (PraisonAI [#4879](https://github.com/MervinPraison/PraisonAI/pull/4879)).** The CLI writes only the keys you supplied to `config.yaml`, so a vision run (any model whose name contains `vision`, `-vl-`, or `visionmodel`) typically omits `huggingface_save`. The vision trainer used to default that flag to the string `"true"` and push to the Hub anyway — or crash on a missing `hf_model_name` after training. From #4879 an omitted flag skips publishing, matching the LLM path. See [Vision Fine-Tuning](/docs/features/praisonai-train-vision).
</Note>

### Where config.yaml comes from

The CLI writes `./config.yaml` from your flags plus resolved defaults in the invocation directory, then hands that single file to the trainer.

<Steps>
  <Step title="Materialise the config">
    `praisonai train llm` resolves the config (file first, flags on top) and writes it to `./config.yaml` in the directory you ran from — the only path the legacy dispatcher and its trainer subprocess read.
  </Step>

  <Step title="Back up any existing config.yaml">
    If a `config.yaml` was already there **and** its contents differ from what's about to be written, the previous contents are saved to `./config.yaml.bak` and the run continues:

    ```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    NOTE: config.yaml already existed; the previous contents were saved to config.yaml.bak.
    ```
  </Step>

  <Step title="No-op writes leave no .bak">
    Re-running the same command produces no `.bak` file — an identical write is detected and skipped. A first run in an empty directory also writes no `.bak`.
  </Step>
</Steps>

<Warning>
  Running `praisonai train llm` in your project root materialises a `config.yaml` next to your other files. Pick a dedicated directory if you don't want it there.
</Warning>

### Config keys for LLM fine-tuning

Drive fine-tuning from `config.yaml` (all keys optional and backward compatible).

| Key                       | Type             | Default               | Description                                                                                                                                                                                                                                                                                   |
| ------------------------- | ---------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `method`                  | `str`            | `"sft"`               | Training method — `sft`, `dpo`, `orpo`, or `kto`. Case-insensitive. See [Preference Training](/docs/features/train-preference-tuning).                                                                                                                                                             |
| `model`                   | `str`            | —                     | Alias for `model_name` (parity with `--model`). If both are set, `model_name` wins.                                                                                                                                                                                                           |
| `method`                  | `str`            | `"sft"`               | Training method. One of `sft`, `cpt`, `dpo`, `orpo`, `kto`, `grpo`. `cpt` is continued pretraining on a raw-text corpus — see [Continued Pretraining](/docs/features/praisonai-train-cpt); `grpo` is scored by reward functions — see [Reward Functions & GRPO](/docs/features/train-reward-functions). |
| `embedding_learning_rate` | `float`          | `learning_rate / 10`  | Only used when `method: cpt`. Rate for `embed_tokens` / `lm_head` when they are in `modules_to_save`.                                                                                                                                                                                         |
| `chat_template`           | `str`            | `None` (model's own)  | Force a specific Unsloth chat template — e.g. `"llama-3.1"`, `"gemma"`, `"qwen-2.5"`. Required when the base model's tokenizer has no built-in template, else the trainer raises `ValueError`.                                                                                                |
| `assistant_only_loss`     | `"auto" \| bool` | `"auto"`              | Mask prompts out of the loss (SFT). `"auto"` masks whenever a route is available — TRL's `{% generation %}` or Unsloth's turn markers; `true` forces on (errors only when **neither** route is available); `false` forces off. `train_on_responses_only` is accepted as an alias.             |
| `beta`                    | `float`          | trainer default       | Preference-loss temperature for `dpo` / `orpo` / `kto`.                                                                                                                                                                                                                                       |
| `max_prompt_length`       | `int`            | `max_seq_length // 2` | Prompt truncation length for `dpo` / `orpo` / `kto`.                                                                                                                                                                                                                                          |
| `desirable_weight`        | `float`          | trainer default       | `kto` only — weight on thumbs-up examples.                                                                                                                                                                                                                                                    |
| `undesirable_weight`      | `float`          | trainer default       | `kto` only — weight on thumbs-down examples.                                                                                                                                                                                                                                                  |
| `dataset[].num_samples`   | `int`            | —                     | Train on the first N rows of the split. Positive integer — validated on load.                                                                                                                                                                                                                 |
| `train`                   | `bool`           | `true`                | Set `false` to skip training and publishing.                                                                                                                                                                                                                                                  |
| `huggingface_save`        | `bool`           | `false`               | Push merged LoRA to Hugging Face. Requires `hf_model_name`.                                                                                                                                                                                                                                   |
| `huggingface_save_gguf`   | `bool`           | `false`               | Push GGUF quantizations to Hugging Face. Requires `hf_model_name`.                                                                                                                                                                                                                            |
| `ollama_save`             | `bool`           | `false`               | Push to Ollama. Requires `ollama_model`.                                                                                                                                                                                                                                                      |

#### Unsloth surface keys

Model access, PEFT selectors, quant, and vLLM rollouts — all optional and additive. See [Unsloth Surface](/docs/features/praisonai-train-unsloth-surface) for the full walkthrough.

| Key                      | Type               | Default | Area         | Description                                                                             |
| ------------------------ | ------------------ | ------- | ------------ | --------------------------------------------------------------------------------------- |
| `hf_token`               | `str`              | —       | model access | HF token. Downloads gated bases and authenticates publishing. Overridden by `HF_TOKEN`. |
| `trust_remote_code`      | `bool`             | —       | model access | Required for custom-code models.                                                        |
| `revision`               | `str`              | —       | model access | Pin a HF branch, tag, or SHA for reproducibility.                                       |
| `finetune_last_n_layers` | `int`              | —       | PEFT         | Tune only the last N transformer blocks.                                                |
| `layers_to_transform`    | `int \| list[int]` | —       | PEFT         | Explicit layer indices to adapt.                                                        |
| `layers_pattern`         | `str`              | —       | PEFT         | Pattern selecting which layers to adapt.                                                |
| `target_parameters`      | `list[str]`        | —       | PEFT         | Selectors — required to LoRA MoE experts.                                               |
| `init_lora_weights`      | `str \| bool`      | —       | PEFT         | LoRA init scheme (`"gaussian"`, `"pissa"`, `"olora"`).                                  |
| `merged_save_dir`        | `str`              | —       | quant        | Merge the adapter into this local folder — no Hub needed.                               |
| `imatrix_file`           | `str`              | —       | quant        | Precomputed importance matrix for imatrix quants. Auto-fetched when unset.              |
| `fast_inference`         | `bool`             | `false` | vLLM         | Turn on vLLM rollouts for GRPO.                                                         |
| `gpu_memory_utilization` | `float`            | —       | vLLM         | vLLM GPU memory fraction. Only sent when `fast_inference` is on.                        |
| `max_lora_rank`          | `int`              | —       | vLLM         | Max LoRA rank vLLM allocates for. Only sent when `fast_inference` is on.                |

<Note>
  A `--config` file must be a YAML mapping of `key: value`. A top-level list (or scalar) is refused up front with `A training config is a YAML mapping of key: value.` — exit `1`.
</Note>

<Note>
  Invalid or unknown YAML keys are reported before training starts. Missing required keys (`model_name`, `max_seq_length`, `dataset`) raise `ValueError` with a minimal example; unknown keys are collected into one `WARNING` block with `did you mean` suggestions and training continues. See [Train → Config validation](/docs/docs/train#config-validation).
</Note>

<Note>
  A `model_name` not in the unsloth catalog now prints one `WARNING: …` line — with a "did you mean?" when there's a close match — and **training continues** (unsloth loads models it doesn't map, so a warning is safer than a refusal). Run [`praisonai train models`](#praisonai-train-models) to see the catalog, or `praisonai train models <family>` to filter it.
</Note>

See [Train → Config.yaml example](/docs/docs/train#config-yaml-example) for the full file.

### Publishing

Publishing defaults **off** — set the flag **and** its matching target to push a fine-tuned model.

| To publish to         | Set flag                      | And target      |
| --------------------- | ----------------------------- | --------------- |
| Hugging Face (merged) | `huggingface_save: true`      | `hf_model_name` |
| Hugging Face (GGUF)   | `huggingface_save_gguf: true` | `hf_model_name` |
| Ollama                | `ollama_save: true`           | `ollama_model`  |

Enabling a flag without its target fails fast on load:

```
ValueError: hf_model_name is required when huggingface_save or huggingface_save_gguf is enabled.
ValueError: ollama_model is required when ollama_save is enabled.
```

### Preflight validation

Before any model or GPU load, the trainer fails fast with a single friendly line for the five most common misconfigurations: stale/missing training deps (checked first, before any heavy import), no CUDA GPU, missing Hugging Face credentials, low disk (`< 10 GB` free), and an invalid `quantization_method`. The GPU check is skipped when `train: false`, so publish-only / export-only runs succeed on CPU.

See [Train → Preflight validation](/docs/docs/train#preflight-validation) for the full check table, fix guidance, and the 35 valid quantization values.

### Exit codes

The `llm` (and the `praisonai_train.train.llm.trainer` module) entrypoint prints a clean one-line error instead of a traceback for expected failures.

| Exit  | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `0`   | Training (or export) completed successfully.                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `1`   | Expected, actionable failure — printed as a single `ERROR: <message>` line to stderr with no Python traceback. Covers preflight failures, config errors, HF/Ollama publish errors, and `ollama create`/`push` failures. As of [PR #4317](https://github.com/MervinPraison/PraisonAI/pull/4317), a non-zero `SystemExit` from the router (e.g. missing training deps) also propagates through the `train_llm` Typer command, so `$?` sees the failure. Clean exits still return `0`. |
| `130` | Interrupted by `Ctrl-C` — printed as `Interrupted.` to stderr.                                                                                                                                                                                                                                                                                                                                                                                                                      |
| other | An unexpected exception — the full Python traceback is printed (a real bug; please file it).                                                                                                                                                                                                                                                                                                                                                                                        |

### Missing-deps behaviour

On a bare `pip install praisonai-train`, `llm` prints one of two messages: `LLM fine-tuning dependencies not installed` with `pip install "praisonai-train[llm]"` when `praisonai-code` is absent, or `Failed to load LLM fine-tuning runner: <ImportError>` when `praisonai-code` is present but a downstream import (torch/unsloth) failed. See [Train](/docs/train) for the full flow.

### Errors you may see

The most common error after `pip install --no-deps` or on a hand-built conda env is a stale training stack. The trainer version-checks six packages (`torch`, `transformers`, `unsloth`, `trl`, `peft`, `bitsandbytes`) **before** any heavy import and prints one actionable block:

```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
Training dependencies are missing or too old.
Missing: unsloth
torch 2.5.1 is older than the required 2.6.0
Install with: pip install -U "unsloth>=2025.9.1" "torch>=2.6.0"
```

Run the printed `pip install -U ...` command — it upgrades every listed package at once. A `dev` / `rc` suffix on a matching release satisfies the floor (`2.6.0rc1` counts as `2.6.0`). See [Train → Runtime version-floor enforcement](/docs/train#runtime-version-floor-enforcement).

### Multi-GPU launch

Fine-tune across every GPU on the machine with `torchrun` — `--nproc_per_node` is the number of GPUs.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
torchrun --nproc_per_node=2 -m praisonai_train.train.llm.trainer train --config config.yaml
```

The trainer detects the distributed launch, loads the full model per rank, and lets only rank 0 save and publish. See [Multi-GPU](/docs/features/praisonai-train-multigpu) for the full walkthrough.

### Remote in one step

Send this one run to a remote GPU box with `--remote-host` — the same as a `remote:` block in `config.yaml` or the desktop "Run on" dropdown. Precedence is `flag > YAML > default`; with no host settled, the command trains locally as before.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai-train llm dataset.json --model unsloth/gemma-2-2b-it-bnb-4bit --remote-host gpubox
```

The dispatcher ships the config (stripping the `remote:` block so the far side does not dispatch again), streams the log, and forwards Ctrl-C to the remote run. Credentials in the block (`password`, `token`, `identity_file`, …) are refused up front. See [Remote in one step](/docs/features/praisonai-train-inline-remote).

### Resume

Set `resume_from_checkpoint: true` in `config.yaml`, then re-run the same command — the trainer picks the latest checkpoint in `output_dir`.

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# config.yaml
save_strategy: "steps"
save_steps: 50
resume_from_checkpoint: true
```

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai-train llm config.yaml
```

See [Checkpointing](/docs/features/praisonai-train-checkpointing) for `save_total_limit`, best-checkpoint, and early-stopping keys.

### Environment variables

`torchrun` sets the distributed variables; you set only your Hugging Face token.

| Variable                | Set by     | Purpose                                                               |
| ----------------------- | ---------- | --------------------------------------------------------------------- |
| `LOCAL_RANK`            | `torchrun` | This process's GPU index on the node.                                 |
| `WORLD_SIZE`            | `torchrun` | Total number of processes / GPUs.                                     |
| `RANK`                  | `torchrun` | Global process rank (`0` = main process).                             |
| `UNSLOTH_USE_NEW_MODEL` | trainer    | Set to `1` under DDP before unsloth imports (DDP-safe checkpointing). |
| `HF_TOKEN`              | **you**    | Needed only when publishing to Hugging Face.                          |

***

## `praisonai train models`

List every model `praisonai train llm` knows how to load — the same catalog `model_name` is validated against.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai train models [SEARCH] [OPTIONS]
```

### Options

| Option                | Short | Description                                                    | Default |
| --------------------- | ----- | -------------------------------------------------------------- | ------- |
| `search` (positional) | —     | Case-insensitive substring filter, e.g. `gemma`, `qwen`, `7b`. | —       |
| `--json`              | `-j`  | Emit the (filtered) list as a JSON array.                      | `false` |
| `--limit`             | `-n`  | Max rows to show. `0` shows the full catalog.                  | `40`    |

### Examples

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Everything unsloth maps (~246 repos on a recent unsloth release)
praisonai train models

# Just the Gemma family
praisonai train models gemma

# JSON for scripts
praisonai train models --json

# The full list, un-truncated
praisonai train models -n 0
```

Each row is prefixed with `*` if it's part of the small curated starting set, or a space otherwise:

```
 * unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit
   unsloth/Meta-Llama-3.1-8B-Instruct
   ...
```

The trailing line explains the marker (`* = a good starting point.`) and, when the output was truncated, prints `{shown} of {total}; -n 0 for all.`

### How the catalog is built

The command reads `unsloth.models.mapper` at runtime — both the 4-bit repo keys and their 16-bit mirror values — rather than shipping a vendored copy that would go stale between releases.

When `unsloth` isn't installed (the CLI is importable without the training extra), the command falls back to a 7-entry curated list of one current base per family, sized for a single 24 GB GPU in 4-bit. See [Supported models](/docs/features/praisonai-train-package#supported-models).

### Exit codes

| Exit | Meaning                                                                                                                                        |
| ---- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `0`  | Rows printed, or JSON emitted.                                                                                                                 |
| `1`  | The filter matched nothing — prints `Nothing matches '<search>'` with the remediation `Try a family name (gemma, qwen, llama) or a size (7b).` |

***

## `praisonai train export`

Publish an already-trained model to Hugging Face, GGUF, or Ollama without re-running training.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai train export {ollama|gguf|hf} --model-dir DIR [--hf REPO] [--ollama NAME] [--quant Q] [--base-model NAME] [--config FILE]
```

Point it at a `lora_model/` you trained earlier — no dataset, no re-training. The `hf` target pushes a merged model to the Hub; `gguf` writes a local `.gguf` (and additionally pushes to the Hub when `--hf` is set); `ollama` creates and pushes an Ollama model.

### Options

| Option                | Short | Type                       | Default      | Description                                                                                                                                                                                                                               |
| --------------------- | ----- | -------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `target` (positional) | —     | `ollama` \| `gguf` \| `hf` | **required** | Export destination. Case-insensitive. An unknown target exits `1` with `Use one of: ollama, gguf, hf`.                                                                                                                                    |
| `--model-dir`         | `-d`  | path                       | **required** | Directory of the already-trained model (e.g. `lora_model`). Must be an existing directory, else exits `1`.                                                                                                                                |
| `--hf`                | —     | str                        | —            | Hugging Face repo id, e.g. `me/mymodel`. **Required** for `hf`; optional for `gguf` (adds a Hub push on top of the local `.gguf`).                                                                                                        |
| `--ollama`            | —     | str                        | —            | Ollama model name, e.g. `me/mymodel`. **Required** for `ollama`.                                                                                                                                                                          |
| `--quant`             | —     | str                        | —            | Quantization method for `gguf`/`ollama`. One of 35 values — 24 standard plus 11 imatrix; see [Export → Valid quant values](/docs/features/praisonai-train-export#valid-quant-values). An invalid value fails fast, listing every valid choice. |
| `--base-model`        | —     | str                        | inferred     | Base model id for chat-template selection. Auto-inferred from `<model-dir>/config.json:_name_or_path`, falling back to the directory name.                                                                                                |
| `--config`            | `-c`  | path                       | —            | Optional `config.yaml` for extra export knobs (e.g. `dtype`, `load_in_4bit`). Must parse to a YAML mapping — a top-level list/scalar exits `1` with `Config file must be a YAML mapping`.                                                 |
| `--mtp-draft`         | —     | flag                       | `false`      | Also download the stock MTP drafter (Gemma-4 only) for fast inference. A failure here only warns; the export still succeeds.                                                                                                              |

### Examples

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Push a merged model to Hugging Face
praisonai-train export hf     --model-dir lora_model --hf     me/my-model

# Write a local GGUF (and push it to HF when --hf is given)
praisonai-train export gguf   --model-dir lora_model --quant q4_k_m
praisonai-train export gguf   --model-dir lora_model --hf     me/my-model --quant q4_k_m

# Push to Ollama
praisonai-train export ollama --model-dir lora_model --ollama me/my-model --quant q4_k_m
```

<Note>
  Export skips the training-only validation — `TrainModel.for_export()` needs **no dataset** and no `dataset:` in a config. It still validates `--quant`. For `ollama`, when `--hf` is omitted the Modelfile `FROM` line points at the local model on disk, so `ollama create` works without a Hub round-trip.
</Note>

<Note>
  **Ollama export ≤ PR #4239 shipped an indented `TEMPLATE`.** The Modelfile embedded four leading spaces on every continuation line, so the served prompt format tokenised differently from training and degraded fine-tune quality. Re-export any Ollama model built before this fix — the export takes seconds and does not re-train. Fixed in [PraisonAI PR #4239](https://github.com/MervinPraison/PraisonAI/pull/4239).
</Note>

### Exit codes

| Exit | Meaning                                                                                                                                                                                                                                                                   |
| ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `0`  | Success — prints `Exported model to <target>.`                                                                                                                                                                                                                            |
| `1`  | Invalid target, missing `--model-dir`, missing destination for the chosen target, config not a YAML mapping, or any `ValueError` / `RuntimeError` / `CalledProcessError` raised during load or push. Errors print as a single clean line (`ERROR: <msg>`) — no traceback. |

<Card title="Export an already-trained model" icon="cloud" href="/docs/features/praisonai-train-package#exporting-an-already-trained-model">
  The `TrainModel.for_export()` Python API and behaviour notes behind this command.
</Card>

See [`praisonai train llm`](#praisonai-train-llm) for the full training path that produces the model you export here.

***

## `praisonai train agents`

Train agents through iterative feedback loops.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai train agents [OPTIONS] [AGENT_FILE]
```

### Options

| Option                  | Short       | Description                                                                                                                                                                                     | Default       |
| ----------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| `--iterations`          | `-n`        | Maximum training iterations. In LLM mode, stops early when a score reaches 9.5 (use `--no-early-stop` to force all).                                                                            | `3`           |
| `--no-early-stop`       | -           | Run all `--iterations` even when a score reaches 9.5.                                                                                                                                           | `false`       |
| `--human`               | `-h`        | Use human feedback instead of LLM grading                                                                                                                                                       | `false`       |
| `--scenarios`           | `-s`        | Path to scenarios JSON file                                                                                                                                                                     | -             |
| `--input`               | `-i`        | Single input text for training                                                                                                                                                                  | -             |
| `--expected`            | `-e`        | Expected output for the input                                                                                                                                                                   | -             |
| `--output`              | `-o`        | Output directory for training data                                                                                                                                                              | -             |
| `--model`               | `-m`        | LLM model for grading                                                                                                                                                                           | `gpt-4o-mini` |
| `--verbose` / `--quiet` | `-v` / `-q` | Show detailed progress. When `--verbose`, the orchestrator prints its own post-persist summary; a display-only encoding failure there is logged as a warning and does not affect the exit code. | `--verbose`   |
| `--dry-run`             | -           | Show what would happen without running                                                                                                                                                          | `false`       |
| `--storage-backend`     | -           | Storage backend: `file`, `sqlite`, or `redis://url`                                                                                                                                             | `file`        |
| `--storage-path`        | -           | Path for storage backend                                                                                                                                                                        | -             |

### Early Stop (LLM mode)

`--iterations N` is a **maximum**, not an exact count. In LLM-as-Judge mode, training stops early as soon as any iteration scores **≥ 9.5** — easy factual prompts often finish in one iteration.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    Start[praisonai-train agents<br/>--iterations N] --> Run[Run iteration]
    Run --> Grade[Grader scores output]
    Grade --> Check{score >= 9.5?}
    Check -->|No| More{iteration < N?}
    Check -->|Yes| Flag{--no-early-stop?}
    Flag -->|No| Stop[Log INFO + break loop<br/>early_stopped = True]
    Flag -->|Yes| More
    More -->|Yes| Run
    More -->|No| Done[All N iterations complete<br/>early_stopped = False]
    Stop --> Summary[Summary shows<br/>Requested / Stopped Early rows]
    Done --> Summary

    classDef input fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef decision fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef success fill:#10B981,stroke:#7C90A0,color:#fff

    class Start input
    class Run,Grade process
    class Check,Flag,More decision
    class Stop,Done,Summary success
```

When training stops early, an `INFO` line is logged **even in `--quiet` mode**:

```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
INFO ...orchestrator: Early stop after iteration 1/3 (score 10.0 >= 9.5 threshold). Use no_early_stop=True to run all iterations.
```

Reaching 9.5 on the **final** iteration counts as a full run — `Stopped Early` stays absent from the summary.

Human-in-the-loop mode (`--human`) does not use the 9.5 threshold — the user decides when to stop.

### Examples

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Simple training with single input
praisonai train agents --input "What is Python?"

# Training with expected output
praisonai train agents --input "What is 2+2?" --expected "4"

# Training with scenarios file
praisonai train agents --scenarios scenarios.json

# Human feedback mode
praisonai train agents --input "Explain AI" --human

# More iterations
praisonai train agents --input "Hello" --iterations 5

# Force all iterations even if a score hits 9.5
praisonai-train agents --input "What is Python?" --iterations 3 --no-early-stop

# See the early-stop INFO line in the console
praisonai-train agents --input "Capital of Italy?" --iterations 3 --verbose

# With agent file
praisonai train agents my_agent.yaml --scenarios scenarios.json
```

### Summary Rows

When training completes, the summary table always shows `Total Iterations`, `Average / Min / Max Score`, `Improvement`, and `Status`. Two extra rows appear only when the run was truncated:

| Row                    | When it appears                  | Value                                                    |
| ---------------------- | -------------------------------- | -------------------------------------------------------- |
| `Requested Iterations` | `requested > completed`          | The value passed via `--iterations`                      |
| `Stopped Early`        | `metadata.early_stopped == True` | `Yes (score X.X >= 9.5; use --no-early-stop to run all)` |

The session JSON persists both signals as `metadata.target_iterations` (int) and `metadata.early_stopped` (bool), so tools reading persisted sessions can distinguish "user asked for 3, got 1 due to 9.5" from "user asked for 1, got 1".

### Storage Backend Options

Store training data in different backends.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# SQLite backend (recommended for production)
praisonai train agents --input "Hello" --storage-backend sqlite --storage-path ~/.praisonai/train.db

# Redis backend (for distributed systems)
praisonai train agents --input "Hello" --storage-backend redis://localhost:6379

# File backend (default)
praisonai train agents --input "Hello" --storage-backend file --storage-path ~/.praisonai/train
```

### Exit Codes

| Exit  | Meaning                                                                                                                                   |
| ----- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `0`   | Training completed and the session was persisted. On non-UTF-8 consoles a warning may be printed if the summary display failed to encode. |
| `1`   | Training failed. This includes both runtime exceptions and encoding errors that occur **before** the report is saved.                     |
| `130` | Interrupted by `Ctrl-C`.                                                                                                                  |

<Note>
  As of PraisonAI 4.6.148+ (upstream commits [`ce17828`](https://github.com/MervinPraison/PraisonAI/commit/ce17828), [`1a6eed3`](https://github.com/MervinPraison/PraisonAI/commit/1a6eed3), fixes [PraisonAI#3040](https://github.com/MervinPraison/PraisonAI/issues/3040)) a display-only encoding error on Windows cp1252 no longer masquerades as a training failure — the session is still saved and the exit code is `0`.
</Note>

***

## `praisonai train serve`

Serve a fine-tuned GGUF over an OpenAI-compatible endpoint — auto-fetching the MTP drafter for lossless fast inference on Gemma-4.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai train serve [OPTIONS]
```

### Options

| Option                           | Short | Description                                                                       | Default       |
| -------------------------------- | ----- | --------------------------------------------------------------------------------- | ------------- |
| `--model-dir`                    | `-d`  | Directory of the trained model (target GGUF auto-detected; drafter files skipped) | -             |
| `--gguf`                         | -     | Explicit path to the target GGUF to serve                                         | -             |
| `--config`                       | `-c`  | Optional `config.yaml` for base-model / MTP knobs                                 | -             |
| `--base-model`                   | -     | Base model id used to select the MTP drafter                                      | -             |
| `--mtp-draft` / `--no-mtp-draft` | -     | Use the stock MTP drafter (auto: on when the family supports it)                  | `--mtp-draft` |
| `--spec-draft-n-max`             | -     | Max tokens the MTP drafter proposes per step                                      | `2`           |
| `--port`                         | -     | Port for `llama-server`                                                           | `8080`        |
| `--ngl`                          | -     | Number of layers to offload to GPU                                                | `99`          |
| `--benchmark`                    | -     | Run a one-shot speed benchmark instead of serving                                 | `false`       |

### How MTP is auto-detected

The base model name (from `--base-model`, `--config`, or the model directory's `config.json`) drives drafter selection. A Gemma-4 target fetches its matching stock drafter from the Hub; any other family falls back to plain serving with a clear message.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "serve"
      G[📦 target GGUF] --> C{Gemma-4?}
      C -->|yes| M[🧠 fetch drafter]
      C -->|no| P[📡 plain serve]
      M --> L[💨 llama-server<br/>+ MTP]
      P --> L2[📡 llama-server]
    end

    classDef input fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef decision fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef output fill:#10B981,stroke:#7C90A0,color:#fff

    class G input
    class M,P process
    class C decision
    class L,L2 output
```

### Examples

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Serve a single GGUF on the default OpenAI endpoint (:8080)
praisonai train serve --gguf model.gguf

# Serve from a model directory (target GGUF auto-detected)
praisonai train serve --model-dir lora_model

# One-shot benchmark: tokens/sec + MTP draft acceptance, then exit
praisonai train serve --gguf gemma4-e4b.gguf --benchmark
```

<Note>
  The server binds to `127.0.0.1` — there is no `--host` flag. Override the `llama-server` binary with the `LLAMA_CPP_BIN` env var (points at the binary or its directory). See [Serve & MTP Fast-Inference](/docs/features/praisonai-train-serve) for the concept-level walkthrough and the honest runtime caveats.
</Note>

***

## `praisonai train checkpoints`

List the checkpoints a training run saved — answers "what did it save?" without an `ls`.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai train checkpoints [OPTIONS]
```

### Options

| Option        | Short | Description                                    | Default   |
| ------------- | ----- | ---------------------------------------------- | --------- |
| `--model-dir` | `-d`  | Directory the run wrote to (its `output_dir`)  | `outputs` |
| `--json`      | `-j`  | Emit a JSON array instead of the human listing | `false`   |

### Examples

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# List saved checkpoints, newest step first
praisonai train checkpoints -d outputs

# Machine-readable output for scripts
praisonai train checkpoints -d outputs --json
```

Matches `checkpoint-<n>` exactly (a `checkpoint-final` directory is skipped), sorts numerically newest-first, and reports each checkpoint's size on disk. An empty or missing directory exits `1` with a remediation. JSON entries are `{"step": int, "path": str, "bytes": int}`.

See [List Checkpoints](/docs/features/praisonai-train-checkpoints) for the full workflow and `jq` patterns.

***

## `praisonai train infer`

Run a single generation against a model you just trained — answers "did it work?".

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai train infer -d lora_model "Summarise this release."
```

<Note>
  This subcommand is `infer`, **not** `generate` — `generate` is dataset generation.
</Note>

### Options

| Option                                 | Short | Description                                     | Default      |
| -------------------------------------- | ----- | ----------------------------------------------- | ------------ |
| `prompt` (positional)                  | -     | What to send the model                          | **required** |
| `--model-dir`                          | `-d`  | Path to a trained adapter or model (must exist) | `lora_model` |
| `--max-new-tokens`                     | -     | Maximum new tokens to generate                  | `256`        |
| `--temperature`                        | -     | Sampling temperature                            | `0.7`        |
| `--max-seq-length`                     | -     | Context length used when loading the model      | `2048`       |
| `--load-in-4bit` / `--no-load-in-4bit` | -     | Load the model in 4-bit quantisation            | `true`       |

### Examples

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Simplest one-liner — reply streams to stdout
praisonai train infer -d lora_model "Summarise this release."

# Longer, more focused output
praisonai train infer -d lora_model "Write release notes." \
    --max-new-tokens 512 --temperature 0.3
```

Output streams via `TextStreamer(skip_prompt=True)`, so the prompt is not echoed and the first token arrives early. Without the Unsloth / Transformers stack it exits `1` with `pip install "praisonai-train[llm]"`.

See [Infer (one-shot inference)](/docs/features/praisonai-train-infer) for the full walkthrough and the `infer` vs `serve` vs `benchmark` decision guide.

***

## `praisonai train list`

List all training sessions.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai train list [OPTIONS]
```

### Options

| Option              | Short | Description                                           | Default |
| ------------------- | ----- | ----------------------------------------------------- | ------- |
| `--limit`           | `-n`  | Max sessions to show                                  | `20`    |
| `--json`            | `-j`  | Output as JSON                                        | `false` |
| `--storage-backend` | -     | Storage backend: `file`, `sqlite`, or `redis://url`   | `file`  |
| `--storage-path`    | -     | Path for storage backend (file dir or sqlite db path) | -       |

### Examples

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# List recent sessions
praisonai train list

# Show more sessions as JSON
praisonai train list --limit 50 --json

# List sessions from a SQLite backend
praisonai train list --storage-backend sqlite --storage-path ~/.praisonai/train.db

# List sessions from Redis
praisonai train list --storage-backend redis://localhost:6379

# List sessions from a custom file directory
praisonai train list --storage-backend file --storage-path ~/.praisonai/train
```

<Note>
  Without `--storage-backend`, `list` scans the default `~/.praison/train` JSON directory. Sessions written by `praisonai train agents --storage-backend sqlite ...` (or Redis, or a custom file dir) are only visible when the same backend/path pair is passed to `list`.
</Note>

Each row shows the session ID, iteration count, size, and last-modified time.

***

## `praisonai train show`

Show details of a training session, including its iterations and best score.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai train show [OPTIONS] SESSION_ID
```

### Options

| Option              | Short | Description                                           | Default |
| ------------------- | ----- | ----------------------------------------------------- | ------- |
| `--iterations`      | `-i`  | Show detailed iteration info                          | `false` |
| `--json`            | `-j`  | Output as JSON                                        | `false` |
| `--storage-backend` | -     | Storage backend: `file`, `sqlite`, or `redis://url`   | `file`  |
| `--storage-path`    | -     | Path for storage backend (file dir or sqlite db path) | -       |

### Examples

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Show session summary
praisonai train show train-abc123

# Show with detailed iterations
praisonai train show train-abc123 --iterations

# Output as JSON
praisonai train show train-abc123 --json

# Session stored in a SQLite database
praisonai train show train-abc123 \
    --storage-backend sqlite --storage-path ~/.praisonai/train.db

# Session stored in Redis
praisonai train show train-abc123 --storage-backend redis://localhost:6379

# Session stored in a custom file directory
praisonai train show train-abc123 \
    --storage-backend file --storage-path ~/.praisonai/train
```

The summary highlights the best iteration (★) with its score and feedback.

***

## `praisonai train apply`

Apply learned suggestions from a session to an agent via hooks. Uses the best-scoring iteration by default.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai train apply [OPTIONS] SESSION_ID
```

### Options

| Option              | Short | Description                                           | Default    |
| ------------------- | ----- | ----------------------------------------------------- | ---------- |
| `--agent`           | `-a`  | Path to agent YAML file                               | -          |
| `--iteration`       | `-n`  | Specific iteration number                             | best score |
| `--run`             | `-r`  | Run agent with this prompt after applying             | -          |
| `--json`            | `-j`  | Output as JSON                                        | `false`    |
| `--storage-backend` | -     | Storage backend: `file`, `sqlite`, or `redis://url`   | `file`     |
| `--storage-path`    | -     | Path for storage backend (file dir or sqlite db path) | -          |

### Examples

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Apply best iteration to default agent
praisonai train apply train-abc123

# Apply a specific iteration
praisonai train apply train-abc123 --iteration 2

# Apply to an agent from a YAML file
praisonai train apply train-abc123 --agent my_agent.yaml

# Apply and run immediately
praisonai train apply train-abc123 --run "Hello, how are you?"

# Apply a session stored in SQLite
praisonai train apply train-abc123 \
    --storage-backend sqlite --storage-path ~/.praisonai/train.db

# Apply a session stored in Redis, then run
praisonai train apply train-abc123 \
    --storage-backend redis://localhost:6379 \
    --run "Hello, how are you?"

# Apply a session written to a custom file dir
praisonai train apply train-abc123 \
    --storage-backend file --storage-path ~/.praisonai/train
```

<Warning>
  The backend flags must match those used at training time. Applying with `--storage-backend file` (default) to a session written with `--storage-backend sqlite ...` will error with `Session not found: <id>`.
</Warning>

***

## Standalone script

Install `praisonai-train` on its own to get a `praisonai-train` console script that exposes the same subcommands directly.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
pip install praisonai-train

praisonai-train agents --input "What is Python?"
praisonai-train data from-trials trials.json -o data/train.jsonl
praisonai-train list
praisonai-train show train-abc123 --iterations
praisonai-train apply train-abc123 --iteration 2 --run "Hello, how are you?"
praisonai-train export hf --model-dir lora_model --hf me/my-model
```

Every `praisonai train <sub>` example above runs as-is with the `praisonai-train <sub>` prefix.

***

## See Also

* [Train](/docs/train) - Training overview and fine-tuning setup
* [Export a trained model](/docs/features/praisonai-train-export) - Publish a trained model without re-training
* [Unsloth Surface](/docs/features/praisonai-train-unsloth-surface) - Gated models, PEFT selectors, offline merge, and vLLM rollouts
* [Train → Preflight validation](/docs/docs/train#preflight-validation) - Fast, friendly config checks before any GPU load
* [Serve & MTP Fast-Inference](/docs/features/praisonai-train-serve) - Serve a GGUF over OpenAI HTTP with MTP speculative decoding
* [Dataset Tooling](/docs/features/praisonai-train-dataset-tooling) - Generate and quality-check instruction datasets
* [praisonai-train Package](/docs/features/praisonai-train-package) - Standalone package guide
* [Multi-GPU Training](/docs/features/praisonai-train-multigpu) - Fine-tune across multiple GPUs with torchrun
* [Remote Training](/docs/features/praisonai-train-remote) - Ship a run to a GPU box over SSH with the `remote` group
* [Remote in one step](/docs/features/praisonai-train-inline-remote) - Send a run to a remote GPU box with `--remote-host`, `remote:` YAML, or the desktop dropdown
* [Checkpointing](/docs/features/praisonai-train-checkpointing) - Save, resume, and keep the best checkpoint
* [Eval](/docs/cli/eval) - Evaluation and testing
* [Storage Backends](/docs/storage/backends) - Pluggable storage backends
