PraisonAI Train
Fine-tune a base model with Unsloth or improve an agent iteratively with LLM/human feedback — one command each
Fine-tune a base model with Unsloth or improve an agent iteratively with LLM/human feedback — one command each.
The PraisonAI Desktop app wraps praisonai-train llm in a Train tab — live loss chart, log, and reconnect-safe progress.
Standalone Install
Training now ships as its own package (praisonai-train, import praisonai_train). Install just what you need:
pip install praisonai-train # agent training (LLM-as-Judge + human feedback), no CUDA/Unsloth
pip install "praisonai-train[llm]" # + Unsloth / torch fine-tuning stackThe standalone CLI exposes the training commands directly:
praisonai-train agents --input "What is Python?" --iterations 3
praisonai-train llm dataset.json --model llama-3.1
praisonai-train list
praisonai-train show <session>
praisonai-train apply <session>Quick Start
- LLM Fine-tuning
Fine-tune a base model on your dataset with Unsloth. Pick the tab for your model family — the trainer uses each model’s own chat template, so
chat_templateis optional.bash pip install "praisonai-train[llm]"Llama
bash praisonai-train llm dataset.json \ --model unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bitOptional YAML override:
chat_template: "llama-3.1".Gemma
bash praisonai-train llm dataset.json \ --model unsloth/gemma-2-2b-it-bnb-4bitOptional YAML override:
chat_template: "gemma"(or"gemma-2").Qwen
bash praisonai-train llm dataset.json \ --model unsloth/Qwen2.5-0.5B-Instruct-bnb-4bitOptional YAML override:
chat_template: "qwen-2.5". - Agent Training
Improve an agent iteratively with LLM-as-Judge or human feedback.
python from praisonaiagents import Agent agent = Agent(instructions="You are a helpful assistant.")bash pip install praisonai-train praisonai-train agents --input "What is Python?"One
pip install praisonai-traingives you the agent, the trainer, and the grader —litellmships as a core dependency, so live LLM-as-Judge grading works with no extra install.Runs cleanly on Windows and other non-UTF-8 consoles — the summary automatically falls back to ASCII (
PASSED/NEEDS WORK) with no configuration needed. Exit codes:0= training persisted,1= training failed,130= interrupted. See Train CLI → Exit Codes.
Beginner-safe defaults
A minimal config trains locally and never pushes to a hub unless you ask it to.
A config with just model_name, max_seq_length, and dataset: trains locally and saves the LoRA to lora_model/. Publishing to Hugging Face or Ollama is opt-in — set the flag and the target name. Response-only masking auto-probes the tokenizer, so assistant_only_loss: auto is safe on any template.
Three safety guarantees:
- Minimal config trains locally — no push, no missing-repo crash.
assistant_only_loss: autonever crashes on a stock Gemma / Qwen / Llama template — it falls back to full-sequence SFT.- Unknown / misnamed keys warn instead of crashing — typos are collected into one block with
did you meansuggestions and training continues.
Three runtime NOTEs surface behaviour that used to be silent — read them if the console shows something unexpected:
num_samplesslices the head, before shuffle. Settingdataset[].num_samples: Nwithoutshuffle: truetrains on the first N rows — often sorted by topic and unrepresentative. The trainer printsNOTE: num_samples takes the FIRST N rows before shuffle; add shuffle: true to sample randomly.Addshuffle: trueto the dataset entry to sample uniformly.split_type: validation/test/evalis added to training, not held out. These splits are concatenated into the training set. The trainer warns:WARNING: dataset split_type 'validation' is ADDED to the TRAINING data (not held out). Use val_split_ratio for a held-out eval set.Useval_split_ratio: 0.05for a real held-out set.max_steps: 2800fires when neither length is set. With neithernum_train_epochsnormax_steps, the trainer defaults tomax_steps: 2800and printsNOTE: neither num_train_epochs nor max_steps set; defaulting to max_steps: 2800. Set num_train_epochs: 1-3 (or max_steps) to control training length.
Preflight validation
Before any model download or GPU load, the trainer fails fast with one friendly line for the five most common non-developer misconfigurations.
The version-floor check runs first — it lives inside the lazy import helper, before the trainer reaches the CUDA probe.
| Preflight check | Message when it fails | Fix |
|---|---|---|
| Training dependency versions | Training dependencies are missing or too old. + Missing: <names> / <name> <ver> is older than the required <floor> + Install with: pip install -U "<name>>=<floor>" ... | Run the printed pip install -U ... command — it upgrades all six at once. |
CUDA GPU (skipped when train: false) | No CUDA GPU detected. Fine-tuning needs a GPU. On a rented box check the driver; on CPU it cannot run. | Use a GPU box; on a rented VM re-install CUDA drivers. Set train: false to publish/export on CPU without training. |
Hugging Face credentials (only when huggingface_save / huggingface_save_gguf / push_to_hub is on) | Publishing to Hugging Face is enabled but no credentials were found. Run 'huggingface-cli login' or 'export HF_TOKEN=hf_...'. To train without publishing, set huggingface_save: false. | Run huggingface-cli login (cached tokens count) or export HF_TOKEN=hf_.... |
Low disk (< 10 GB free on output_dir or .) | Low disk: only 3.2 GB free on the training disk (need ~10 GB minimum). Free up space or set output_dir to a larger disk. | Free space, or set output_dir in config.yaml to a larger volume. |
Quantization method (only when huggingface_save_gguf or ollama_save is on) | quantization_method 'q4km' is not valid. Choose one of: ... | Use one of the 35 values below. |
The 35 valid quantization_method values — 24 standard plus 11 imatrix — are listed in full under Quantization method. The imatrix (IQ) family is what lets a large model fit on a laptop.
How It Works
Point the trainer at a base model and dataset to fine-tune, or at a scenario to iteratively improve an agent.
Real interaction — agent-training path: an agent gives so-so answers → run praisonai-train agents --input "Explain AI" --human → review N iterations → praisonai-train apply {session_id} --run "Explain AI" bakes the improvements into the agent via hooks → the same agent answers better.
CLI Reference
The praisonai-train CLI exposes fourteen subcommands, grouped into data (generate, validate, benchmark, dedup, from-trials), training (llm, agents), serving / export / inference (serve, export, checkpoints, infer), and utilities (list, show, apply).
| Subcommand | What it does | Deep link |
|---|---|---|
generate | Produce synthetic instruction/response rows from a recipe | Dataset Tooling |
validate | Score / QC an existing JSONL against the recipe’s checks | Dataset Tooling → validate |
benchmark | Compare deployments by throughput, latency, and success rate | Speed Benchmark |
dedup | Exact + MinHash/LSH near-dedup across many JSONL files | Cross-file dedup |
data from-trials | Export passing agent trials to a trainer-ready SFT dataset (+ provenance) | Dataset Tooling → from-trials |
llm | Fine-tune a base LLM on a generated dataset | Train CLI → llm |
agents | Distil an agent workflow into a dataset for training | Train CLI → agents |
serve | Serve a fine-tuned GGUF over an OpenAI-compatible endpoint (auto-fetches the MTP drafter on Gemma-4) | Serve & MTP Fast-Inference |
export | Publish an already-trained model (hf / gguf / ollama) without re-running training | Train CLI → export |
checkpoints | List what a run saved — after training, before you export or resume | List Checkpoints |
infer | One-shot prompt against a model you just trained — a “did it work?” check | Infer |
list | List locally available recipes / adapters | Train CLI → list |
show | Show a recipe’s schema and defaults | Train CLI → show |
apply | Apply a trained adapter / merge with the base weights | Train CLI → apply |
See Train CLI for full flags.
Serve a trained model
Run a trained GGUF on an OpenAI-compatible endpoint — MTP fast inference auto-enables for Gemma-4.
# Serve a trained Gemma-4 model on http://localhost:8080/v1 (MTP auto-enabled)
praisonai-train serve --gguf model.gguf
# Or point at a trained model directory (GGUF auto-detected)
praisonai-train serve -d lora_model
# One-shot speed test with drafter acceptance rate
praisonai-train serve --gguf model.gguf --benchmark
# Custom port, no MTP
praisonai-train serve --gguf model.gguf --no-mtp-draft --port 9000Every flag, with its default:
| Flag | Type | Default | Description |
|---|---|---|---|
--gguf | str | unset | Path to the target GGUF to serve. |
--model-dir / -d | str | unset | Directory of the trained model (GGUF auto-detected, drafter files skipped). |
--config / -c | str | unset | Optional config.yaml for base-model / MTP knobs. |
--base-model | str | unset | Base model id used to select the MTP drafter. |
--mtp-draft / --no-mtp-draft | bool | True | Use the stock MTP drafter — on automatically when the family supports it. |
--spec-draft-n-max | int | 2 | Max tokens the MTP drafter proposes per step. |
--port | int | 8080 | Port for llama-server. |
--ngl | int | 99 | Number of layers to offload to the GPU. |
--benchmark | bool | False | Run a one-shot speed benchmark instead of serving. |
A non Gemma-4 model serves without MTP and says so — no configuration needed. The base model name (which drives drafter selection) is recovered in this order: --base-model > --config model_name > the model dir’s config.json _name_or_path.
Export an already-trained model
Publish or convert a locally-trained model without re-running training.
# Publish a locally-trained model to Ollama with a q4_k_m quantization
praisonai-train export ollama --model-dir lora_model --ollama yourname/mymodel --quant q4_k_m
# Publish merged weights to Hugging Face
praisonai-train export hf --model-dir lora_model --hf yourname/mymodel-hf
# Produce a LOCAL .gguf (ready to serve) and also push it to the Hub
praisonai-train export gguf --model-dir lora_model --quant q4_k_m --hf yourname/mymodel-ggufThe first argument is the target — ollama, gguf, or hf. Every flag:
| Flag | Type | Default | Description |
|---|---|---|---|
--model-dir / -d | str | required | Directory of the already-trained model. |
--config / -c | str | unset | Optional config.yaml for extra export knobs. |
--ollama | str | unset | Ollama model name, e.g. yourname/mymodel. Required for the ollama target. |
--hf | str | unset | Hugging Face repo id, e.g. yourname/mymodel. Required for the hf target. |
--quant | str | unset | Quantization method for gguf / ollama, e.g. q4_k_m. |
--base-model | str | unset | Base model id (for chat-template selection). |
--mtp-draft / --no-mtp-draft | bool | False | Also download the stock MTP drafter (Gemma-4 only) for fast inference. |
The gguf target always writes a local .gguf (so you can serve it) and only additionally pushes to the Hub when --hf is set. The model name (which drives chat-template selection) falls back to the trained model’s config.json _name_or_path, then the directory name, when --base-model / model_name aren’t given.
Clean errors & preflight
Fine-tuning validates the environment up front and fails with a single actionable line — no traceback — so a non-developer knows exactly what to fix.
What to expect:
- Training deps missing or too old → a single
Training dependencies are missing or too old.block naming each package plus onepip install -U ...command, raised before any heavy import. - No CUDA GPU / missing HF credentials / low disk (
< 10 GB) / badquantization_method→ a singleERROR: {reason}line and exit1, checked before any download starts. Ctrl-C→ printsInterrupted.and exits130.- Unknown config keys → one grouped
WARNING:block with adid you mean '{key}'?suggestion for each; training continues.
GPU out of memory
If the GPU runs out of memory during training, the run stops and prints an ordered fix.
The CLI attaches the fix to the ERROR: line on stderr:
The GPU ran out of memory. In order of what usually helps most:
1. Lower max_seq_length (try 2048, or 4096 if you were higher)
2. Set use_gradient_checkpointing: unsloth (if it is off)
3. Lower per_device_train_batch_size, raising gradient_accumulation_steps
by the same factor to keep the effective batch size
4. Use a smaller model, or a 4-bit one (load_in_4bit: true)Apply the steps to config.yaml, cheapest first — re-run after each and stop once the run fits.
- Lower max_seq_length
Sequence length is the biggest single lever. Drop it to
2048(try4096only if you were already higher).yaml max_seq_length: 2048 - Enable gradient checkpointing
Trades a little speed for a large memory saving.
yaml use_gradient_checkpointing: unsloth - Halve the batch, double accumulation
Keep the effective batch size while halving memory — halve
per_device_train_batch_sizeand doublegradient_accumulation_steps.yaml per_device_train_batch_size: 1 gradient_accumulation_steps: 8 - Switch to a 4-bit base model
Last resort — pick a 4-bit base or load the current one in 4-bit.
yaml model_name: unsloth/llama-3.1-8b-bnb-4bit load_in_4bit: true
Fine-tuning Setup
Push a fine-tuned model to Hugging Face and Ollama.
Hugging Face token
A Hugging Face token does two jobs: downloading a gated base (Llama, Gemma) and publishing a fine-tuned model. Set it once via the env var or config hf_token.
export HF_TOKEN="${HF_TOKEN:?Set HF_TOKEN in your shell}"Train a gated Llama base — pass the token by either channel:
# config.yaml
model_name: "meta-llama/Meta-Llama-3.1-8B-Instruct"
hf_token: "hf_xxx" # or export HF_TOKEN — same token, either channelPrecedence: the HF_TOKEN env var wins, then a cached huggingface-cli login, then config hf_token. See Model access & reproducibility and Hub Privacy.
Model access & reproducibility
Reach gated and custom-code models, and pin a revision so a multi-day run is reproducible.
| Key | Type | Default | Description |
|---|---|---|---|
hf_token | str | — | Config-level HF token. Downloads gated bases and authenticates publishing. Overridden by HF_TOKEN. |
trust_remote_code | bool | — | Required for custom-code models. Only sent when set. |
revision | str | — | Pin a HF branch, tag, or commit SHA. Only sent when set. |
# config.yaml
model_name: "meta-llama/Meta-Llama-3.1-8B-Instruct"
revision: "main" # reproducible across days
trust_remote_code: falseSee Unsloth Surface → Model access.
Save merged locally
Merge the adapter into a folder on this disk — no Hub account, no token, no network.
# config.yaml
merged_save_dir: "./merged-model"
save_method: "merged_16bit" # default; also "merged_4bit_forced", "lora"The merge step runs on every run and is a no-op unless merged_save_dir is set. See Unsloth Surface → Offline merged export.
Fast rollouts for GRPO
grpo previously always ran the slow HF-generate path for its rollouts. Flip fast_inference: true to swap it for vLLM.
# config.yaml
method: grpo
fast_inference: true
gpu_memory_utilization: 0.6 # only forwarded when fast_inference is on
max_lora_rank: 32 # only forwarded when fast_inference is onSee Unsloth Surface → vLLM fast rollouts and Reward Functions & GRPO.
Conda setup: what the script installs on your machine
setup_conda_env.sh probes the host for a GPU before creating the env, so a non-Nvidia box gets a truthful build instead of a working-looking CUDA one.
The probe order is nvidia-smi → /proc/driver/nvidia/gpus → rocminfo / amd-smi → CPU. The pytorch-cuda=12.4 pin now lives in exactly one place, chosen by the detected accelerator, so the two conda create branches cannot disagree.
An AMD host installs the CPU build and prints — the installer does not build a ROCm stack for you, so follow the official PyTorch instructions then re-run:
WARNING: an AMD GPU was detected. This installer only knows the CUDA
and CPU builds, so it will install the CPU one. Follow
https://pytorch.org/get-started/locally/ for a ROCm build,
then re-run this script.A CPU-only Linux host installs the CPU build and prints — the env is usable for dataset work and exports, but not fine-tuning (see GPU check):
WARNING: no GPU detected. Fine-tuning needs one -- praisonai-train
llm refuses to run on CPU. The environment will still be
usable for dataset work and exports.macOS still gets a CPU build with no warning (unchanged).
Chat Template
Leave chat_template unset for tokenizers that ship their own — the trainer uses the model’s native template and only overrides it when you set the key.
If a model has no built-in template and you leave chat_template unset, the trainer fails fast with a clear error you can grep for:
Tokenizer for model '<name>' has no chat template and none was provided via config 'chat_template'. Set 'chat_template' (e.g. 'gemma', 'qwen-2.5', 'llama-3.1') so conversations format correctly.Training method (method:)
method: picks the trainer — sft (default) for instruction tuning, cpt for continued pretraining on a raw-text corpus, or a preference method.
method: | Trainer used | Required dataset columns | Notes |
|---|---|---|---|
sft (default) | SFTTrainer | Chat (conversations or messages) or Alpaca (formatted to text) | Standard supervised fine-tuning. messages is the shape praisonaiagents.eval.export_sft writes. |
cpt | UnslothTrainer | text | Continued pretraining — raw-text corpus. See Continued Pretraining. |
dpo | DPOTrainer | prompt, chosen, rejected | Preference. |
orpo | ORPOTrainer | prompt, chosen, rejected | Preference. |
kto | KTOTrainer | prompt, completion, label | Preference. |
grpo | GRPOTrainer | prompt | Scored by reward functions; supports fast vLLM rollouts. See Reward Functions & GRPO. |
Multi-Model Fine-tuning
Gemma and Qwen now fine-tune out of the box 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-4bitBoth commands are the two models the SDK actually tests (2× RTX A6000). Forcing llama-3.1 on every model was the old default and silently corrupted Gemma/Qwen training.
Assistant-only Loss
Response-only loss trains on assistant replies only — prompts are masked out of the loss. It’s on by default via assistant_only_loss: auto.
assistant_only_loss: auto # default — mask whenever any route is availableThe trainer masks through two routes, chosen automatically:
assistant_only_loss(TRL’s built-in) — used when the chat template ships{% generation %}markers.train_on_responses_only(Unsloth’s turn-marker masking) — the fallback that masks by locating literal turn-marker strings. It works on Llama, Qwen, Gemma, Mistral, and Phi templates — none of which ship{% generation %}today.
auto resolves to mask whenever any route is available — the template supports {% generation %}, or turn markers are known for it.
| Value | Behaviour | When to use |
|---|---|---|
auto | Mask whenever any route is available, else full-sequence SFT | Recommended default — masks correctly on every listed template |
true | Force masking; fails fast at model-prep time if no route is available | You want masking guaranteed |
false | Force off — train on the full sequence | You want full-sequence loss regardless |
train_on_responses_only is accepted as an alias for assistant_only_loss if you’re migrating from an older config. Both real YAML booleans (assistant_only_loss: true) and the string forms ("true" / "false" / "yes" / "no" / "on" / "off") work.
The run-summary Loss mask: line names which route actually ran:
| Route | Line printed |
|---|---|
| off | Loss mask: full sequence (training on prompts too) |
assistant_only_loss | Loss mask: assistant replies only (TRL assistant_only_loss) |
train_on_responses_only | Loss mask: assistant replies only (unsloth turn markers) |
Each Unsloth chat template maps to a marker family used by the turn-marker route:
| Chat template | Family | Instruction marker | Response marker | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
llama-3, llama3, llama-3.1, llama-31, llama | llama-3 | `<\ | start_header_id\ | >user<\ | end_header_id\ | >\n\n` | `<\ | start_header_id\ | >assistant<\ | end_header_id\ | >\n\n` |
chatml, qwen-2.5, qwen3, phi-4, gpt-oss, yi-chat, unsloth, gemma_chatml, … | chatml | `<\ | im_start\ | >user\n` | `<\ | im_start\ | >assistant\n` | ||||
gemma, gemma2, gemma-3, gemma3, gemma-3n, gemma-4 | gemma | <start_of_turn>user\n | <start_of_turn>model\n | ||||||||
mistral | mistral | [INST] | [/INST] | ||||||||
phi-3, phi-35, phi-3.5 | phi | `<\ | user\ | >\n` | `<\ | assistant\ | >\n` | ||||
zephyr | zephyr | `<\ | user\ | >\n` | `<\ | assistant\ | >\n` |
Marker resolution matches longest-first (so llama-3.1 beats llama), is case-insensitive, and normalises hyphens/dots (gemma-2 == gemma2). When chat_template is unset the resolver falls back to the model’s repo name only (not the unsloth/ org prefix). An unknown model resolves to no markers — the trainer refuses to guess rather than mask on the wrong boundary and train on nothing.
Initilise praisonai train
praisonai train initRequirements
Install training dependencies:
pip install "praisonai-train[llm]" # LLM fine-tuning (Unsloth / torch / CUDA)
pip install praisonai-train # agent training (LLM-as-Judge + human feedback) — no CUDA/Unsloth
pip install "praisonai[train]" # via the wrapper — pulls praisonai-train[llm]Pick the flavour that matches your flow: agent training (praisonai-train) needs no CUDA/Unsloth — its base install already pulls litellm for live LLM-as-Judge grading — while LLM fine-tuning (praisonai-train[llm]) pulls the full torch/Unsloth stack.
pip install "praisonai-train[llm]" installs the whole ML stack for you — you only run the base install:
| Package | Minimum floor |
|---|---|
transformers | >= 4.51.3 |
unsloth | >= 2025.9.1 |
trl | >= 0.18.2 |
peft | >= 0.13.0 |
accelerate | >= 0.34.0 |
bitsandbytes | >= 0.45.0 |
torch | >= 2.6.0 |
The CUDA-12.4 pin is imposed by the conda installer, not required by torch — a torch 2.6 wheel built for another CUDA (or ROCm) also satisfies the runtime check. Verified end-to-end on unsloth 2026.7.4 / trl 0.24.0 / torch 2.10 / peft 0.19.1, across Qwen2.5-0.5B and Gemma-2-2B.
Runtime version-floor enforcement
The six floors are checked at runtime, not just by pip — because pip is routinely bypassed.
pip install --no-deps, a hand-built conda env, and older setup_conda_env.sh torch pins all install a stack pip never validated. The trainer re-checks the six floors itself, before importing torch/transformers/unsloth/etc.
| Package | Floor |
|---|---|
torch | 2.6.0 |
transformers | 4.51.3 |
unsloth | 2025.9.1 |
trl | 0.18.2 |
peft | 0.13.0 |
bitsandbytes | 0.45.0 |
A matching release with a dev or rc suffix satisfies the floor — 2.6.0rc1 counts as 2.6.0, so testing an upstream prerelease is not blocked.
A missing or stale stack prints one block naming exactly what to fix:
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 pip install -U "<name>>=<floor>" ... command the trainer printed — it upgrades every listed package in one go.
Required for training:
- Huggingface token
- Base model to train on (e.g. unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit)
- Dataset to train on (e.g. yahma/alpaca-cleaned)
- Huggingface model name to upload to (e.g. mervinpraison/llama3.1-instruct) (Optional)
- Ollama model name to upload to (e.g. mervinpraison/llama3.1-instruct) (Optional)
If training dependencies are missing when you run praisonai-train llm, you’ll see one of two messages depending on what’s installed.
When praisonai-code is not installed (bare pip install praisonai-train):
LLM fine-tuning dependencies not installed
Install with: pip install "praisonai-train[llm]"When praisonai-code is installed but a downstream import failed (e.g. torch/unsloth missing):
Failed to load LLM fine-tuning runner: <underlying ImportError>The wrapper’s older pip install "praisonai[train]" hint is no longer printed from the praisonai-train llm entrypoint (PraisonAI PR #3053).
sequenceDiagram
participant User
participant CLI as praisonai-train llm
participant Bridge as _code_bridge
User->>CLI: praisonai-train llm dataset.json
CLI->>Bridge: import praisonai_code.cli.main
alt praisonai_code absent
Bridge-->>CLI: ImportError + code_available()=False
CLI-->>User: "LLM fine-tuning dependencies not installed"<br/>Install with: pip install "praisonai-train[llm]"
else praisonai_code present but downstream import fails
Bridge-->>CLI: ImportError + code_available()=True
CLI-->>User: "Failed to load LLM fine-tuning runner: <real error>"
else all deps present
Bridge-->>CLI: PraisonAI runner
CLI-->>User: Fine-tune runs
endSupported model families
The trainer uses each base model’s own chat template by default — no more force-applied Llama template — so Gemma, Qwen, and Llama all format correctly out of the box.
| Model family | Example base model | chat_template value |
|---|---|---|
| Llama 3.x | unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit | "llama-3.1" (or leave unset — model has its own) |
| Gemma 2 / Gemma 3 | unsloth/gemma-2-2b-it-bnb-4bit | "gemma" (or leave unset) |
| Qwen 2.5 / Qwen 3 | unsloth/Qwen2.5-0.5B-Instruct-bnb-4bit | "qwen-2.5" (or leave unset) |
| Mistral / Phi | via native template | leave unset |
To upload to ollama.com (Linux)
sudo cat /usr/share/ollama/.ollama/id_ed25519.pubSave the output to ollama.com → Ollama keys.
Ollama template selection
The trainer picks an Ollama TEMPLATE and stop-token block per model family, matched case-insensitively against model_name.
| Family keyword | Turn markers / templating | Stop tokens | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
llama | `< | start_header_id | > / < | eot_id | >` header blocks | `< | start_header_id | >, < | end_header_id | >, < | eot_id | >` |
qwen | `< | im_start | > / < | im_end | > (adds num_ctx 32768` + Qwen system prompt) | `< | endoftext | >` | ||||
mistral | [INST] ... [/INST] | [INST], [/INST] | ||||||||||
phi | `< | im_start | >…< | im_sep | >` | `< | im_start | >, < | im_end | >, < | im_sep | >` |
deepseek | bare role content | — | ||||||||||
llava | `< | im_start | > / < | im_end | >` | </s>, USER:, ASSSISTANT: | ||||||
gemma | {start_of_turn} / {end_of_turn}, system folded into first user turn | {end_of_turn}, {start_of_turn} | ||||||||||
| anything else | falls back to a Llama-style header template | `< | start_header_id | >, < | end_header_id | >, < | eot_id | >` |
See the full templates in the trainer source (prepare_modelfile_content).
Ollama environment variables
Point the trainer at a configured or remote daemon and a bigger models disk.
| Variable | Purpose | Default | When to set |
|---|---|---|---|
OLLAMA_HOST | Address the trainer probes and where ollama create/push publish to. Accepts host, host:port, or http://host:port (IPv6 literal [::1]:11434 supported). | 127.0.0.1:11434 | A configured or remote Ollama daemon on a non-default host — stops the trainer ignoring a healthy remote daemon. |
OLLAMA_MODELS | Directory Ollama stores models in. The trainer pre-checks free space on this volume before ollama create. | ~/.ollama/models | The default volume is small — point at a large disk with export OLLAMA_MODELS=/big/disk/ollama-models. |
Disk pre-check
Before ollama create runs, the trainer estimates the space it needs at ~1.5× the source model size and refuses to start if the OLLAMA_MODELS volume has less free:
Not enough disk for the Ollama model: 4.2 GB free on '/home/me/.ollama/models', need ~15.0 GB. Point Ollama at a bigger disk, e.g. `export OLLAMA_MODELS=/big/disk/ollama-models`.Quantization passthrough
quantization_method in the config flows through to ollama create --quantize {q}, so models aren’t stored as huge f16. It’s the same key GGUF export uses, and it’s validated against the 35 valid values up front.
Daemon startup + surfaced errors
If the trainer starts ollama serve and the HTTP API on /api/version doesn’t respond within 15 seconds, the raised RuntimeError includes the ollama serve stderr — so port in use or a bad OLLAMA_HOST shows up in the error instead of vanishing.
Push failures & key registration
A push whose stderr matches unauthorized / 401 / 403 / forbidden / invalid key translates to a key-registration hint, with your public key inlined when it’s readable so you can paste it straight into ollama.com:
Ollama rejected the push (unauthorized). Register your public key at https://ollama.com/settings/keys and make sure the model is namespaced as `<username>/<name>`.
Your public key (~/.ollama/id_ed25519.pub):
ssh-ed25519 AAAAC3Nz...A non-zero exit from ollama create raises a RuntimeError with the captured stderr (e.g. ` ollama create me/model:latest failed: Error: invalid Modelfile ) instead of a raw CalledProcessError` traceback.
Config.yaml example
Drive an LLM fine-tuning run from a config file instead of flags.
Minimal — train locally
No publish flags — trains and saves the LoRA to lora_model/, pushing nowhere.
method: sft # sft (default) | dpo | orpo | kto — see Preference Training
model_name: "unsloth/gemma-2-2b-it-bnb-4bit"
max_seq_length: 2048
dataset:
- name: "yahma/alpaca-cleaned"
num_samples: 20 # subset for a fast smoke test (honored as of #3279)Publish to HF + Ollama
Publishing defaults off — set the flag and the matching target name. Real YAML booleans (not "true" strings).
model_name: "unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit"
max_seq_length: 2048
dataset:
- name: "yahma/alpaca-cleaned"
huggingface_save: true # real YAML bool; "true" string also works
hf_model_name: "yourname/llama-3.1-instruct-lora"
ollama_save: true
ollama_model: "yourname/llama3.1-instruct"
assistant_only_loss: auto # tri-state — auto probes the template (default)Multi-GPU + checkpoints + eval
A realistic combined run: two GPUs, checkpoint every 50 steps, keep the 3 best, hold out 5% for eval, and stop early if it plateaus. See Newer keys (from PraisonAI #3283) for every key below.
model_name: "unsloth/gemma-2-2b-it-bnb-4bit"
max_seq_length: 2048
dataset:
- name: "yahma/alpaca-cleaned"
# Checkpoint every 50 steps, keep only the 3 most recent
save_strategy: "steps"
save_steps: 50
save_total_limit: 3
final_model_dir: "lora_model"
resume_from_checkpoint: true # picks up the latest if a checkpoint exists
# Hold out 5% for eval; keep the best checkpoint; stop if flat for 3 evals
val_split_ratio: 0.05
load_best_model_at_end: true
metric_for_best_model: "eval_loss"
early_stopping_patience: 3# 2 GPUs on one machine
torchrun --nproc_per_node=2 -m praisonai_train.train.llm.trainer train --config config.yamlBoth real YAML booleans (train: true) and the string forms (train: "true") work — pick whichever reads better. This holds for vision configs too, as of PraisonAI #4879; before then a real YAML boolean crashed the vision path on .lower().
The full-config reference below shows every optional key with its default.
# Training method — sft (default) | dpo | orpo | kto. Case-insensitive.
method: sft
model_name: "unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit"
model_parameters: "8b"
# Preference-tuning knobs (dpo / orpo / kto only) — commented out for SFT.
# beta: 0.1 # preference-loss temperature (dpo/orpo/kto)
# max_prompt_length: 1024 # prompt truncation (default: max_seq_length // 2)
# desirable_weight: 1.0 # kto only — weight on thumbs-up examples
# undesirable_weight: 1.0 # kto only — weight on thumbs-down examples
# Publishing — all default OFF. Set the flag AND its target to push.
huggingface_save: true
hf_model_name: "yourname/llama-3.1-instruct"
ollama_save: true
ollama_model: "yourname/llama3.1-instruct"
# Optional: force a specific Unsloth chat template (default: model's own).
# Required when the base model's tokenizer has no built-in chat template.
chat_template: "llama-3.1" # or "gemma", "qwen-2.5"
# Response-only loss — tri-state, default "auto" (probe the template).
assistant_only_loss: auto
dataset:
- name: "yahma/alpaca-cleaned"
split_type: "train"
processing_func: "format_prompts"
rename:
input: "input"
output: "output"
instruction: "instruction"
filter_data: false
filter_column_value: "id"
filter_value: "alpaca"
num_samples: 20000 # Now honored — trains on the first 20000 rows of the split (PraisonAI #3279).
dataset_text_field: "text"
dataset_num_proc: 1 # optional — default is unset (Unsloth sizes workers from memory)
packing: false
max_seq_length: 2048
load_in_4bit: true
lora_r: 16
lora_target_modules:
- "q_proj"
- "k_proj"
- "v_proj"
- "o_proj"
- "gate_proj"
- "up_proj"
- "down_proj"
lora_alpha: 16
lora_dropout: 0
lora_bias: "none"
use_gradient_checkpointing: "unsloth"
random_state: 3407
use_rslora: false
loftq_config: null
# Optional: pick the right chat template for the model family.
# Omit to use the model's own built-in template.
chat_template: "llama-3.1" # or "gemma", "gemma-2", "qwen-2.5", "mistral", "phi-3", ...
# Optional: mask prompts out of the loss (auto picks a masking route; see Assistant-only Loss).
assistant_only_loss: auto
per_device_train_batch_size: 2
gradient_accumulation_steps: 2
warmup_steps: 5
num_train_epochs: 1
max_steps: 10
learning_rate: 2.0e-4
logging_steps: 1
optim: "adamw_8bit"
weight_decay: 0.01
lr_scheduler_type: "linear"
seed: 3407
output_dir: "outputs"
quantization_method:
- "q4_k_m"praisonai-train llm dataset.jsonSilent-behaviour notes
The trainer prints these at runtime — they change results without erroring, so know them up front.
New / newly-honored fine-tuning config keys
These keys map straight to the trainer in praisonai_train/train/llm/trainer.py — all optional, all backward-compatible.
| Key | Type | Default | Description | |
|---|---|---|---|---|
method | str | "sft" | Training method — sft, dpo, orpo, or kto. Case-insensitive. An unknown value raises ValueError listing every supported method. See Preference Training. | |
model | str | — | Alias for model_name. Accepted for parity with the --model CLI flag; if both are set, model_name wins. | |
chat_template | `str \ | null` | null (use model’s own) | Override the tokenizer’s built-in chat template. Common values: "gemma", "qwen-2.5", "llama-3.1". Trainer raises ValueError if the model has no template and this is unset. |
assistant_only_loss | `”auto” \ | bool` | "auto" | Mask prompts out of the loss. "auto" masks whenever a route is available (TRL {% generation %} or Unsloth turn markers); true forces on (errors only when neither route is available); false forces off. train_on_responses_only is accepted as an alias. SFT only — ignored for preference methods. |
beta | float | trainer default | dpo / orpo / kto. Preference-loss temperature, passed straight to the trainer’s beta. | |
max_prompt_length | int | max_seq_length // 2 | dpo / orpo / kto. Prompt truncation length for preference methods. | |
desirable_weight | float | trainer default | kto only. Weight on thumbs-up (label: true) examples. | |
undesirable_weight | float | trainer default | kto only. Weight on thumbs-down (label: false) examples. | |
train | bool | true | Set false to skip training and publishing (validation still runs). | |
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. | |
ollama_push | bool | true | Push to ollama.com after ollama create. Set false to keep the model local only. | |
quantization_method | `str \ | list[str]` | — | GGUF/Ollama quantization. A single-element list (e.g. ["q4_k_m"], as shipped by older templates) is unwrapped to the scalar automatically. |
dataset[].num_samples | int | — | Train on the first N rows of the split. Must be a positive integer — validated on load. Now honored as of #3279 (was silently ignored before). | |
lora_r | int | 16 | LoRA rank. Now honored from config (was hardcoded pre-#3274). | |
lora_alpha | int | 16 | LoRA alpha. Now honored from config. | |
lora_target_modules | list[str] | ["q_proj","k_proj","v_proj","o_proj","gate_proj","up_proj","down_proj"] | LoRA target modules. Now honored from config. | |
lora_dropout | float | 0 | LoRA dropout. Now honored from config. | |
lora_bias | str | "none" | LoRA bias mode. Now honored from config. | |
use_gradient_checkpointing | `str \ | bool` | "unsloth" | Gradient checkpointing mode. Now honored from config. |
random_state | int | 3407 | PEFT seed. Now honored from config. | |
use_rslora | bool | false | Enable RS-LoRA. Now honored from config. | |
loftq_config | `dict \ | null` | null | LoftQ config. Now honored from config. |
dataset_num_proc | `int \ | null` | null | Dataset tokenization workers (goes on SFTConfig). When unset, Unsloth sizes workers from available memory. Set to 1 for deterministic, single-process tokenization; set a larger integer to pin an explicit count. |
packing | bool | false | Enable sequence packing (goes on SFTConfig). | |
num_train_epochs | `int \ | null` | null | If set and max_steps unset, train by epochs instead of steps. |
Hub privacy & preference-tuning keys (from PraisonAI #4357)
PraisonAI #4357 made every Hub push private by default and added preference tuning (dpo / orpo / kto). All keys are optional.
| Key | Type | Default | Description |
|---|---|---|---|
hf_private | bool | true | Push as a private repo. Set false to publish publicly. Accepts "false" / "0" / "no". See Hub Privacy. |
save_method | str | "merged_16bit" | Passed to Unsloth’s push_to_hub_merged. Was hardcoded before #4357. |
commit_message | str | — | Optional Hub commit message. Only sent when set. |
tags | list[str] | — | Optional Hub repo tags. Only sent when set. |
method | str | "sft" | Training method: sft, cpt, dpo, orpo, kto, grpo (case-insensitive). See Preference Tuning and Reward Functions & GRPO. |
beta | float | TRL default | Preference strength (β). dpo / orpo / kto only. |
max_prompt_length | int | max_seq_length // 2 | Max prompt tokens for preference methods. |
desirable_weight | float | 1.0 | KTO weight for thumbs-up examples. |
undesirable_weight | float | 1.0 | KTO weight for thumbs-down examples. |
Newer keys (from PraisonAI #3283)
PraisonAI #3283 added multi-GPU, checkpointing, eval, early stopping, and a raw-SFTConfig escape hatch — all optional, all backward-compatible.
Checkpointing & resume — see Checkpointing:
| Key | Type | Default | Description | ||
|---|---|---|---|---|---|
save_strategy | `”no” \ | “steps” \ | “epoch”` | "steps" if save_steps set, else "no" | When to write checkpoints. |
save_steps | int | 100 | Step interval when save_strategy: steps. | ||
save_total_limit | int | unset | Keep only the N most recent checkpoints. | ||
save_safetensors | bool | true | Serialise in safetensors format (dropped with a warning if the installed TRL doesn’t accept it). | ||
resume_from_checkpoint | `bool \ | str` | false | true = latest in output_dir; a path = that checkpoint. | |
final_model_dir | str | "lora_model" | Where the final adapter is saved (and reloaded for inference). |
Evaluation & best-checkpoint:
| Key | Type | Default | Description | ||
|---|---|---|---|---|---|
val_split_ratio | float | unset | Fraction held out as eval (e.g. 0.05). Required for load_best_model_at_end / early_stopping_patience. | ||
eval_strategy | `”no” \ | “steps” \ | “epoch”` | "steps" if eval_steps set, else "epoch" | When to evaluate. |
eval_steps | int | 100 | Step interval when eval_strategy: steps. | ||
per_device_eval_batch_size | int | inherits per_device_train_batch_size (or 2) | Batch size for eval. | ||
load_best_model_at_end | bool | false | Reload the best checkpoint at end. Auto-aligns save/eval strategies. | ||
metric_for_best_model | str | "eval_loss" | Metric to rank checkpoints on. | ||
greater_is_better | bool | false | Direction of the ranking metric. |
Early stopping:
| Key | Type | Default | Description |
|---|---|---|---|
early_stopping_patience | int | unset | Stop after N evals with no improvement. Requires val_split_ratio. Auto-enables load_best_model_at_end. |
early_stopping_threshold | float | 0.0 | Minimum change to count as improvement. |
Multi-GPU (DDP) — see Multi-GPU:
| Key | Type | Default | Description |
|---|---|---|---|
ddp_find_unused_parameters | bool | true (under DDP) | Keep true for multimodal / MoE; set false for a small speedup on dense-text models. |
Quantization / precision:
| Key | Type | Default | Description | ||
|---|---|---|---|---|---|
dtype | `”float16″ \ | “bfloat16” \ | null` | null | Torch dtype for the loaded model (Unsloth decides if unset). |
load_in_8bit | bool | unset | 8-bit quantisation. Mutually exclusive with load_in_4bit. | ||
full_finetuning | bool | false | Train the base weights directly, skip LoRA entirely. |
Advanced LoRA:
| Key | Type | Default | Description | |
|---|---|---|---|---|
modules_to_save | list[str] | unset | Extra modules to fully train (e.g. ["embed_tokens", "lm_head"]). Required for method: cpt to actually adapt the vocabulary. Without it, CPT trains no embeddings and prints a NOTE. | |
embedding_learning_rate | float | learning_rate / 10 | Only used when method: cpt. Rate for embed_tokens / lm_head; the default is the recipe UnslothTrainer was written to enforce. See Continued Pretraining. | |
rank_pattern | dict | unset | Per-module LoRA ranks (PEFT). | |
alpha_pattern | dict | unset | Per-module LoRA alphas (PEFT). | |
use_dora | bool | false | DoRA — Weight-Decomposed Low-Rank Adaptation. | |
finetune_last_n_layers | int | unset | Tune only the last N transformer blocks — the cheap-run recipe. | |
layers_to_transform | `int \ | list[int]` | unset | Explicit layer indices to adapt. |
layers_pattern | str | unset | Regex / pattern selecting which layers to adapt. | |
target_parameters | list[str] | unset | Parameter selectors — required to LoRA MoE experts. | |
init_lora_weights | `str \ | bool` | unset | LoRA init scheme, e.g. "gaussian", "pissa", "olora". |
See Unsloth Surface → PEFT selectors for the “which recipe?” decision guide and MoE examples.
Extra optimization / logging / hub knobs:
| Key | Type | Default | Description |
|---|---|---|---|
max_grad_norm | float | unset | Gradient clipping norm. |
warmup_ratio | float | unset | Alternative to warmup_steps (setting it pops warmup_steps). |
neftune_noise_alpha | float | unset | NEFTune noise scale. |
dataloader_num_workers | int | unset | Torch DataLoader workers. |
data_seed | int | unset | Deterministic dataset shuffle. |
adam_beta1 / adam_beta2 / adam_epsilon | float | unset | AdamW hyperparameters. |
group_by_length | bool | unset | Length-aware batching. |
lr_scheduler_kwargs | dict | unset | Passthrough to the LR scheduler. |
logging_first_step | bool | true | Log at step 0. |
report_to | str | "wandb" if PRAISON_WANDB else "none" | none / tensorboard / wandb. |
run_name | str | inherits PRAISON_WANDB_RUN_NAME or "praisonai-train" | Wandb run name. |
push_to_hub | bool | false | Push checkpoints to the Hub during training. |
hub_model_id | str | inherits hf_model_name | Repo id for push_to_hub. |
hub_strategy | str | "every_save" | Hub push cadence. |
Generic training_arguments: passthrough — set any raw SFTConfig field. The trainer merges it into the config it hands TRL, then drops any field the installed TRL’s SFTConfig doesn’t accept with a WARNING (so version drift warns instead of crashing).
training_arguments:
torch_compile: true
gradient_checkpointing_kwargs:
use_reentrant: falseMulti-GPU
Launch across every GPU with one torchrun command — the trainer detects the distributed launch and handles DDP.
torchrun --nproc_per_node=2 -m praisonai_train.train.llm.trainer train --config config.yamlEach rank loads the full model on its own GPU (device_map = {"": local_rank}), Unsloth runs through its DDP-safe checkpointing path, and only rank 0 writes the final adapter and publishes. Set ddp_find_unused_parameters: false for a small speedup on dense-text models (leave the default true for multimodal / MoE). See Multi-GPU for the full walkthrough.
Checkpointing & Resume
Save mid-run so an interruption never costs the whole training — then resume from the latest.
| Key | Type | Default | Description | ||
|---|---|---|---|---|---|
save_strategy | `”no” \ | “steps” \ | “epoch”` | "steps" if save_steps set, else "no" | When to write checkpoints. |
save_steps | int | 100 | Step interval when save_strategy: steps. | ||
save_total_limit | int | unset | Keep only the N most recent checkpoints. | ||
resume_from_checkpoint | `bool \ | str` | false | true = latest in output_dir; a path = that checkpoint. | |
final_model_dir | str | "lora_model" | Where the final adapter is saved. |
Resume after an interruption — re-launch the same command with:
resume_from_checkpoint: true # picks the latest checkpoint in output_dirSee Checkpointing for the full reference.
Eval + Early Stopping
Hold out an eval set to monitor overfitting, keep the best checkpoint, and stop when it plateaus.
val_split_ratio carves the eval set; load_best_model_at_end reloads the best checkpoint; early_stopping_patience stops after N flat evals (and requires val_split_ratio). See Checkpointing → Evaluation & Best-Checkpoint.
Config validation
The trainer runs a preflight pass on load — before any model, dataset, GPU, or download is touched — so a non-developer gets a clean, actionable error in seconds instead of a traceback after minutes.
Required keys
Preflight checks
| Check | Trigger | Failure message (paraphrased) |
|---|---|---|
| Training dependency versions | praisonai-train llm invoked with a missing/stale torch / transformers / unsloth / trl / peft / bitsandbytes | Training dependencies are missing or too old. + Missing: <names> / <name> <ver> is older than the required <floor> + Install with: pip install -U "<name>>=<floor>" ... |
| Required keys | model_name, max_seq_length, or dataset missing | Config is missing required keys: [...]. Minimal example: ... |
| Publish target set | huggingface_save / huggingface_save_gguf true but hf_model_name missing | hf_model_name is required when huggingface_save or huggingface_save_gguf is enabled. |
| Publish target set | ollama_save: true but ollama_model missing | ollama_model is required when ollama_save is enabled. |
CUDA available (only when train: true, default) | No CUDA GPU | No CUDA GPU detected. Fine-tuning needs a GPU. On a rented box check the driver; on CPU it cannot run. |
| HF credentials | Any publish flag set but neither HF_TOKEN nor a cached huggingface-cli login | Publishing to Hugging Face is enabled but no credentials were found. Run \huggingface-cli login\ or \export HF_TOKEN=hf_…\. To train without publishing, set huggingface_save: false. |
| Free disk | output_dir (or .) has < 10 GB free | Low disk: only X.X GB free on the training disk (need ~10 GB minimum). Free up space or set output_dir to a larger disk. |
| Quantization method | huggingface_save_gguf or ollama_save true and quantization_method outside the allowed set | quantization_method '{val}' is not valid. Choose one of: ... — the full 35-value list is under Quantization method. |
The required-keys message shows a copy-paste minimal config:
ValueError: Config is missing required keys: ['model_name', 'max_seq_length', 'dataset']. Minimal example:
model_name: unsloth/gemma-2-2b-it-bnb-4bit
max_seq_length: 2048
dataset:
- name: yahma/alpaca-cleanedGPU check
Enforced only when train: true (the default). A publish-only or export-only run (train: false) is valid on CPU — cross-link Publishing.
No CUDA GPU detected. Fine-tuning needs a GPU. On a rented box check the driver; on CPU it cannot run.Version floors
Six packages are version-checked at runtime, before any heavy import — pip install --no-deps and hand-built conda envs routinely bypass pip’s metadata.
| Package | Floor |
|---|---|
torch | 2.6.0 |
transformers | 4.51.3 |
unsloth | 2025.9.1 |
trl | 0.18.2 |
peft | 0.13.0 |
bitsandbytes | 0.45.0 |
Run the pip install -U "<name>>=<floor>" ... line the trainer prints — it upgrades every stale package at once. See Runtime version-floor enforcement for the full behaviour.
Hugging Face credentials
When publishing is enabled, either an HF_TOKEN / HUGGING_FACE_HUB_TOKEN env var or a cached huggingface-cli login (read via huggingface_hub.get_token()) is accepted — you don’t need both.
Publishing to Hugging Face is enabled but no credentials were found. Run `huggingface-cli login` or `export HF_TOKEN=hf_...`. To train without publishing, set huggingface_save: false.Disk headroom
The trainer needs at least 10 GB free on the output_dir volume (or . when output_dir is unset).
Low disk: only 3.2 GB free on the training disk (need ~10 GB minimum). Free up space or set output_dir to a larger disk.Quantization method
Enforced only when a GGUF or Ollama push is enabled. 35 values are valid — 24 standard plus 11 importance-matrix (IQ) quants. An unknown value lists all valid choices. The IQ family is what lets a large model fit on a laptop.
Standard (24):
bf16 f16 f32 fast_quantized not_quantized quantized
q2_k q2_k_l q3_k_l q3_k_m q3_k_s q3_k_xs
q4_0 q4_1 q4_k q4_k_m q4_k_s
q5_0 q5_1 q5_k q5_k_m q5_k_s
q6_k q8_0IMatrix — large-model / laptop-fit (11):
iq1_m iq1_s iq2_m iq2_s iq2_xs iq2_xxs
iq3_m iq3_s iq3_xxs iq4_nl iq4_xsSupply your own importance matrix with imatrix_file, or leave it unset and Unsloth auto-fetches one from unsloth/<base>-GGUF. See Unsloth Surface.
Unknown keys
Unrecognized keys don’t crash — the trainer collects all of them and prints one block with the closest known key suggested via difflib:
WARNING: unrecognized config key(s) — these will be ignored:
- num_sample (did you mean 'num_samples'?)
- max_step (did you mean 'max_steps'?)Training continues after the warning.
Unknown model name
A model_name outside the unsloth catalog also warns rather than refuses — unsloth’s loader falls through to a generic path for models it doesn’t map, so a typo shouldn’t block a working config. On a close match:
WARNING: 'unsloth/Meta-Llama-3.1-8B-Instrct-bnb-4bit' is not in unsloth's model list, so it may not load. Did you mean: unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit? Run `praisonai-train models` to see the full list.With no close match, it lists three known-good starting points instead. Suggestions match the repo name (not the full id), so a right model under the wrong org surfaces the unsloth/… mirror. Run praisonai train models to see the whole list. Training continues after the warning.
See Export a trained model to publish an already-trained model without re-running training.
Publishing
Publishing to Hugging Face or Ollama is opt-in — set the flag and the matching target.
train: false is a valid config — the trainer skips training and publishing (previously it crashed with an AttributeError on a None model).
Publish without re-training
Already trained a model? Publish it with praisonai train export — no dataset, no re-training.
praisonai-train export hf --model-dir lora_model --hf me/my-model
praisonai-train export gguf --model-dir lora_model --hf me/my-model --quant q4_k_m
praisonai-train export ollama --model-dir lora_model --ollama me/my-model --quant q4_k_mfor_export() skips the training-only validation — an export-only run needs no dataset: (or a config file at all). See Export an already-trained model.
Publishing failure modes
Actionable messages replace raw HTTP tracebacks.
Ollama publish knobs
Ollama exports quantize on create and can be redirected to a bigger disk.
--quantize—export ollama --quant q4_k_mpasses--quantizetoollama createso the model isn’t stored as a hugef16.OLLAMA_MODELS— set this env var to point Ollama at a larger disk when short on space. The trainer disk-pre-checks it beforeollama createand fails withexport OLLAMA_MODELS=/big/disk/ollama-modelsguidance instead of a cryptic “no space left on device”.- Push
401/403→ an actionable message: register your public key (~/.ollama/id_ed25519.pub) at ollama.com/settings/keys and namespace the model as{username}/{name}. The key value is printed for you to copy.
Hugging Face push errors
A rejected Hub push turns into a one-line fix, not a stack trace.
| Situation | Message you’ll see | Fix |
|---|---|---|
| HF token invalid / expired | Hugging Face rejected the token (401). It is invalid or expired — run \huggingface-cli login\ or \export HF_TOKEN=hf_…\ with a valid write token. | New write-scoped token |
| HF wrong repo owner or read-only token | No write access to '{repo}' (403). The repo must be under your own username and the token must have write scope. Set hf_model_name to '{your-username}/{name}'. | Namespace to your user; write token |
| Ollama key not registered | Ollama rejected the push (unauthorized). Register your public key at https://ollama.com/settings/keys and make sure the model is namespaced as '{username}/{name}'. plus your key printed inline from ~/.ollama/id_ed25519.pub | Paste the printed key at the URL |
| Ollama disk full | Not enough disk for the Ollama model: X GB free on '{path}', need ~Y GB. Point Ollama at a bigger disk, e.g. \export OLLAMA_MODELS=/big/disk/ollama-models\. | Move $OLLAMA_MODELS |
| Ollama daemon didn’t start | ollama serve did not become ready in 15 seconds. ollama serve output: {stderr} | Fix the underlying stderr (port busy, bad OLLAMA_HOST) |
ollama create failed | Raised as a RuntimeError with the create-command stderr, not a raw CalledProcessError | Follow the printed guidance |
Run summary
Just before training starts, the trainer prints a one-block summary so you can confirm the config resolved as intended.
──────────── PraisonAI Train ────────────
Model: unsloth/gemma-2-2b-it-bnb-4bit
Examples: 950 (+50 eval)
Loss mask: assistant replies only
Steps: 200 · batch 2 × accum 2 × 2 GPUs
Checkpoints: steps @ 50 steps · Output: lora_model/
─────────────────────────────────────────The Loss mask: line is the resolved auto / true / false decision — assistant replies only or full sequence. The block now also reports the GPU count (× N GPUs under torchrun), the held-out eval count (+N eval), the checkpoint strategy, and the final final_model_dir — so you can verify the whole run resolved as intended without reading the DEBUG noise.
Non-developer errors
The trainer translates the failures hit in practice into a single clean ERROR: line — never a traceback for these.
| What went wrong | Error you see |
|---|---|
| No GPU on a training run | ERROR: No CUDA GPU detected. Fine-tuning needs a GPU. On a rented box check the driver; on CPU it cannot run. |
| Publishing enabled, no HF login | ERROR: Publishing to Hugging Face is enabled but no credentials were found. Run huggingface-cli login or export HF_TOKEN=hf_... |
| < 10 GB free disk | ERROR: Low disk: only X.X GB free on the training disk (need ~10 GB minimum). Free up space or set output_dir to a larger disk. |
Wrong --quant value | ERROR: quantization_method 'q4km' is not valid. Choose one of: ... — the full 35-value list is under Quantization method. |
| HF token rejected | ERROR: Hugging Face rejected the token (401). It is invalid or expired — run huggingface-cli login or export HF_TOKEN=hf_... with a valid write token. |
| No write access to HF repo | ERROR: No write access to '{repo}' (403). The repo must be under your own username and the token must have write scope. Set hf_model_name to '{your-username}/{name}'. |
| Ollama daemon didn’t come up | ERROR: ollama serve did not become ready in 15.0 seconds. + the captured ollama serve stderr |
| Ollama key not registered | ERROR: Ollama rejected the push (unauthorized). Register your public key at https://ollama.com/settings/keys ... + your local ~/.ollama/id_ed25519.pub |
| Not enough disk on the Ollama volume | ERROR: Not enough disk for the Ollama model: X.X GB free on '{dir}', need ~Y GB. Point Ollama at a bigger disk, e.g. export OLLAMA_MODELS=/big/disk/ollama-models |
Everything else — a genuine bug — still shows its full traceback so it’s not silently swallowed. Ctrl-C exits 130.
Silent-behaviour NOTES
Three behaviours that used to surprise users now print an explicit NOTE: / WARNING: line.
num_samplestakes the first N rows before shuffle — repeated runs with the same seed use the same deterministic subset.split_typeofvalidation/valid/test/evalis ADDED to the training set — no validation set is held out. Warned viaWARNING: dataset split_type '{x}' is ADDED to the training set.- Default
max_steps: 2800fires when neithernum_train_epochsnormax_stepsis set:NOTE: neither num_train_epochs nor max_steps set; defaulting to max_steps: 2800. Set num_train_epochs: 1-3 (or max_steps) to be explicit.When both are set,max_stepssilently wins (logged at DEBUG). dataset.processing_funcis ignored — advertised in old templates but never read. Warned viaWARNING: dataset.processing_func (...) is not supported and will be ignored; formatting is chosen automatically from the dataset's columns.quantization_method: ["q4_k_m"](single-element list) is unwrapped to the scalar — older templates shipped it as a one-element list; both forms now work.
Advanced
Fine-tune loss to assistant turns only, in a model-agnostic way.
Model & template keys
Two optional keys control how conversations are formatted and which tokens count toward the loss.
| Option | Type | Default | Description | |
|---|---|---|---|---|
chat_template | str | None | Unsloth chat-template name (e.g. "llama-3.1", "gemma", "gemma-2", "qwen-2.5", "qwen-3", "mistral", "phi-3", "chatml"). Omit to keep the model’s own built-in template. See Unsloth’s get_chat_template for the full list. | |
assistant_only_loss | `”auto” \ | bool` | "auto" | Mask prompts out of the loss. Masks via TRL’s {% generation %} route when the template has it, else Unsloth’s turn-marker route for any known template family (Llama / Qwen / Gemma / Mistral / Phi). See Assistant-only Loss. |
Model-agnostic response-only training
Leave assistant_only_loss: auto to let the trainer mask the loss to assistant turns whenever any route is available. Because none of Unsloth’s chat templates ship {% generation %} today, the marker-based route (train_on_responses_only) is what actually masks on Gemma / Qwen / Llama / Mistral / Phi — no template change needed.
model_name: "unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit"
chat_template: "llama-3.1"
assistant_only_loss: true # force on — errors only if NO route is availableThe trainer picks the route automatically: TRL’s assistant_only_loss when the template exposes {% generation %} blocks, otherwise Unsloth’s train_on_responses_only turn markers for any known template family. You only hit an error when neither route exists — an unknown template with no known markers.
Which chat_template should I use?
Under the hood — modern TRL SFT surface
The trainer targets the current TRL API. Anyone writing custom training code should note it now uses SFTConfig in place of TrainingArguments, passes processing_class= instead of the removed tokenizer= kwarg, moves dataset_text_field, max_length (TRL renamed max_seq_length → max_length), packing, and dataset_num_proc onto SFTConfig, and lets modern TRL tokenize the text column internally instead of pre-tokenizing with a dummy field.
Weights & Biases
Track loss curves and checkpoints for each run.
wandb loginexport PRAISON_WANDB=True
export WANDB_LOG_MODEL=checkpoint
export WANDB_PROJECT=praisonai-test
export PRAISON_WANDB_RUN_NAME=praisonai-trainBest Practices
Start with praisonai-train alone if you only want agent training
pip install praisonai-train pulls praisonaiagents plus litellm (for LLM-as-Judge grading) — enough for agents, list, show, and apply. Add [llm] only when you need Unsloth fine-tuning.
Set HF_TOKEN in the environment
Export HF_TOKEN in your shell before fine-tuning so the trainer can push to Hugging Face. Never commit the raw token.
Start small, then scale
Use a low max_steps and num_samples: 20 for a first run — a full smoke test finishes in under a minute. Ramp up once the pipeline is green.
Leave assistant_only_loss: auto
The trainer enables masking whenever the tokenizer supports it and falls back cleanly otherwise. Only set true / false if you need to force a specific behaviour.
Publish flags are opt-in
For local experiments, delete the huggingface_save / ollama_save keys entirely — no target, no push, no crash. Set the flag and its target (hf_model_name / ollama_model) only when you want to publish.
Publish flags are opt-in for vision runs too (PraisonAI #4879)
Before #4879 the vision trainer defaulted every publish flag to the string "true", so a plain vision config that never mentioned huggingface_save pushed the fine-tuned model to the Hub — or crashed with KeyError: 'hf_model_name' after training had already completed. It now behaves like the LLM path: publishing runs only when the flag and its target (hf_model_name for huggingface_save / huggingface_save_gguf, ollama_model for ollama_save) are both set. YAML booleans (train: true, huggingface_save: true) work in vision configs too — the old code crashed calling .lower() on a real bool.
Publishing needs credentials + write access — check them once
Run huggingface-cli login (or export HF_TOKEN=hf_...) and namespace the repo under your own username. Preflight catches missing credentials before the run; a rejected push still translates 401/403 into an actionable fix. For Ollama, register your signing key at ollama.com — a rejected push prints your public key to copy.
Track runs with Weights & Biases
Set PRAISON_WANDB=True and the WANDB_* variables to log loss curves and checkpoints for each run.
Let the tokenizer's native template win
Only set chat_template for models without one, or to force a specific one. Forcing llama-3.1 on Gemma/Qwen was the old default and silently corrupted training — now the trainer uses each model’s own template by default.
Match chat_template to your model family
Omit chat_template to use the model’s built-in template — that’s correct for most Instruct models. Set it explicitly (gemma, qwen-2.5, llama-3.1, …) only when overriding or when the tokenizer ships no template, otherwise the trainer raises ValueError at model prep.
Enable assistant_only_loss for instruction datasets
Turn on assistant_only_loss: true so loss is computed only on assistant turns — but confirm your chat template exposes {% generation %} blocks (e.g. Unsloth’s llama-3.1, gemma-2, qwen-2.5). Templates without them cannot produce the mask and TRL will error.
Related
Serve a GGUF on an OpenAI endpoint with MTP fast inference.
Train CLIFull flag reference for all eleven subcommands.
Serve & MTP Fast-InferenceServe a GGUF over OpenAI HTTP with lossless MTP speculative decoding.
Multi-GPU TrainingFine-tune across multiple GPUs with torchrun.
Remote GPU TrainingTrain on another box over SSH — runs survive your laptop closing.
Remote in One StepSend a run to a remote GPU box with --remote-host, remote: YAML, or the desktop dropdown.
Save, resume, and keep the best checkpoint.
praisonai-train PackageInstall and use training without the full wrapper.
Desktop → TrainingPrefer a UI? Run this fine-tune from the Desktop app’s Train tab.
Hub Privacy & Upload OptionsHub pushes are private by default — opt in to publish, tune save method, commit, and tags.
Preference TuningFine-tune with DPO, ORPO, or KTO preference pairs.
Installation ExtrasOptional dependency groups and the train install matrix.
ModelsUse your fine-tuned model with an Agent.