Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Train a character/identity LoRA locally on FLUX.1-dev via the comfyui-mcp train_* tools (GPU Docker + ostris ai-toolkit). Use when the user wants to train a LoRA of a person/character from their photos on the local GPU — covers dataset prep, launch, monitoring, and using the result in ComfyUI. For WAN/Z-Image training via the ai-toolkit UI see ai-toolkit-trainer.
.claude/skills/artokun-train-character-lora/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-10 | ✗→✓ | ▲ Improved | 57% | 0% |
| case-06 | ✗→✓ | ▲ Improved | -32% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 16% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 52% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 31% | 0% |
The trainer runs ostris ai-toolkit's run.py inside a headless GPU Docker container, driven through the three train_* MCP tools. You (the LLM) are the UI. Each takes an action: train_prepare_dataset owns the datasets, train_start owns the jobs, and train_doctor owns the trainer itself. You generate the dataset, launch the job, watch progress, and the finished LoRA lands in ComfyUI models/loras/ and the LoRA catalog without further steps.
quantization, RTX 4090 class).
train_doctor {action:"doctor"}. Preflight once per session. Checks docker daemon,--gpus all GPU passthrough, trainer image, HF_TOKEN. If image:false, run train_doctor {action:"build_image"} (one-time, several minutes, since it builds CUDA plus torch plus ai-toolkit). If hfTokenSet:false, warn the user: the first run downloads FLUX.1-dev (gated HF repo) and needs HF_TOKEN in the MCP server env.
train_prepare_dataset {action:"prepare"}. Stage the images. See "Dataset" below.train_start {action:"start"}. Launch. Returns a job id at once; training runsdetached.
train_start {action:"status", id}. Poll progress (progress.step/totalSteps/loss,recent samples, log tail). Poll on a slow cadence (every few minutes). A 2000-step run is roughly an hour on a 4090. Don't block on it.
status:"completed" means the .safetensors was copied tomodels/loras/<name>.safetensors and upserted into the LoRA catalog (result has the paths and catalog id). Verify by loading it in a Flux workflow (LoraLoaderModelOnly, strength 1.0) with the trigger word in the prompt.
Call train_prepare_dataset {action:"prepare"} with name, items: [{path, caption?}, ...] and a defaultCaption.
backgrounds, distances (close-up, half-body, full-body). Variety beats count.
ohwx, zxc_person), NOT areal word. Use it as defaultCaption and pass it as trigger to train_start.
expression); the model learns the constant identity from the images themselves. Start each caption with the trigger word, e.g. ohwx person sitting in a cafe, laughing, natural light. Keep them short and factual. When in doubt, the trigger word alone (defaultCaption) is a workable baseline.
img_00001.<ext> etc. Source files are never modified.| Param | Default | When to change | |-------|---------|----------------| | steps | 2000 | 200 for a smoke test; 1500–3000 real runs. More ≠ better (overbake = plasticky). | | lr | 1e-4 | 5e-5 for a tighter/subtler identity. | | rank | 16 | 32 for very detailed characters. | | resolution | 512,768,1024] | 512] if VRAM-constrained. | | quantize | true | Keep true on 24GB. | | saveEvery / sampleEvery | 250 | Lower (100) to watch early progress. |
train_start {action:"status"}'s progress.samples are host paths. Look at them.(ai-toolkit prints no saved-sample lines, so they populate at finalize from the output dir; mid-run you can look directly in the job's output/<name>/samples/ folder.) Identity should be recognizable by ~1/3 of the run; if samples stay generic past halfway, the run will likely underfit. Cancel (train_start {action:"cancel", id}) and check captions and trigger.
saveEvery steps under the job's output/ dir, so a cancelledrun isn't a total loss.
no_docker / no_image from train_start {action:"start"}: runtrain_doctor {action:"doctor"}, follow its hints.
resolution to [512], keep quantize:true,batch stays 1.
handoff failed in job error: training itself finished; the LoRA is still under thejob's output/<name>/ dir. Copy it into models/loras/ manually and upsert the catalog.
long as the log tail moves, it's fine. The HF cache persists across runs.
packs/ and observed renders; not a vendor prompting guide.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-10 | fail→pass | 17,385 | 20,087 | +16% | 1 | 1 | 0% | 2,152 | 3,383 | +57% | 0 | 0 | — |
case-01 | fail→fail | 30,258 | 9,454 | -69% | 1 | 1 | 0% | 5,135 | 2,015 | -61% | 0 | 0 | — |
case-02 | fail→fail | 16,443 | 16,733 | +2% | 1 | 1 | 0% | 331 | 1,752 | +429% | 0 | 0 | — |
case-03 | fail→fail | 28,011 | 21,052 | -25% | 1 | 1 | 0% | 4,517 | 1,824 | -60% | 0 | 0 | — |
case-04 | fail→fail | 27,182 | 17,871 | -34% | 1 | 1 | 0% | 3,271 | 1,950 | -40% | 0 | 0 | — |
case-20 | pass→pass | 19,046 | 5,463 | -71% | 1 | 1 | 0% | 2,307 | 2,476 | +7% | 0 | 0 | — |
case-05 | fail→fail | 22,591 | 18,728 | -17% | 1 | 1 | 0% | 2,535 | 1,866 | -26% | 0 | 0 | — |
case-06 | fail→pass | 22,740 | 9,772 | -57% | 1 | 1 | 0% | 3,243 | 2,192 | -32% | 0 | 0 | — |
case-07 | fail→pass | 27,013 | 7,302 | -73% | 1 | 1 | 0% | 1,598 | 1,851 | +16% | 0 | 0 | — |
case-08 | fail→pass | 14,555 | 12,546 | -14% | 1 | 1 | 0% | 1,624 | 2,469 | +52% | 0 | 0 | — |
case-09 | pass→pass | 17,387 | 11,143 | -36% | 1 | 1 | 0% | 1,939 | 2,365 | +22% | 0 | 0 | — |
case-11 | fail→pass | 17,437 | 11,228 | -36% | 1 | 1 | 0% | 1,896 | 2,491 | +31% | 0 | 0 | — |
case-12 | pass→pass | 20,558 | 12,535 | -39% | 1 | 1 | 0% | 2,303 | 2,693 | +17% | 0 | 0 | — |
case-13 | fail→pass | 22,020 | 12,350 | -44% | 1 | 1 | 0% | 2,696 | 2,671 | -1% | 0 | 0 | — |
case-14 | fail→pass | 16,690 | 19,483 | +17% | 1 | 1 | 0% | 2,430 | 3,385 | +39% | 0 | 0 | — |
case-15 | pass→pass | 19,676 | 9,804 | -50% | 1 | 1 | 0% | 2,399 | 2,157 | -10% | 0 | 0 | — |
case-16 | fail→pass | 17,777 | 31,913 | +80% | 1 | 1 | 0% | 1,695 | 2,044 | +21% | 0 | 0 | — |
case-17 | fail→pass | 17,202 | 12,138 | -29% | 1 | 1 | 0% | 1,936 | 2,421 | +25% | 0 | 0 | — |
case-18 | pass→pass | 20,182 | 12,287 | -39% | 1 | 1 | 0% | 2,440 | 2,618 | +7% | 0 | 0 | — |
case-19 | fail→pass | 14,978 | 22,576 | +51% | 1 | 1 | 0% | 2,465 | 2,418 | -2% | 0 | 0 | — |
case-21 | fail→pass | 16,890 | 14,037 | -17% | 1 | 1 | 0% | 2,017 | 2,998 | +49% | 0 | 0 | — |
case-22 | fail→pass | 13,925 | 2,212 | -84% | 1 | 1 | 0% | 1,389 | 1,757 | +26% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 18 counted toward the lift figure. The other 4 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +55 percentage points is the difference between those two pass rates over the 18 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.6-flash | verified | 8/27/2026 | +36% |
| gemini-3.6-flash | verified | 8/23/2026 | +27% |
Other measured skills in the registry, with their headline benchmark lift.