Skip to content

HomeGuides › How I train character LoRAs for Krea 2

How I train character LoRAs for Krea 2

Every setting, every measurement, and the reasoning behind both. This is the exact pipeline behind the characters in the shop — no settings withheld.

Most LoRA guides give you a config and a shrug. This one gives you the config and the alternative it was measured against, because almost every number below started as a different number that changed after timing it or looking at the output side by side.

Every figure here is measured on one machine. Training speeds come out of a real train.log. Image generation times come from the file timestamps of actual batches. Nothing is a manufacturer claim.

The hardware

GPU        NVIDIA GeForce RTX 5090 - 32,607 MiB VRAM, driver 591.86
CPU        Intel Core i9-14900KF
OS         Windows 11, training inside WSL2 (Ubuntu 24.04.4 LTS)
RAM        WSL capped at 24 GB in .wslconfig
Stack      PyTorch 2.11.0+cu128, CUDA 12.8
Trainer    ai-toolkit
Previews   ComfyUI, on the Windows side

Everything runs on one card. There is no second GPU to offload previews onto. Training, captioning and image generation all queue for the same 32 GB, and that constraint shaped more decisions than any quality consideration did.

Train on Raw, generate on Turbo

Krea 2 ships in more than one variant, and picking the wrong one costs you either quality or the ability to train at all. Krea 2 Raw (26 GB) is the variant intended for fine-tuning, so that is what trains. krea2_turbo_mxfp8 renders the previews at roughly half the VRAM of bf16.

LoRAs trained on Raw transfer to Turbo. That is the whole trick, and it is what allows training against the full model while rendering previews at 8 steps.

The dataset

Across every character dataset on disk: median 48 images, mean 49, range 8–101.

40–80 images is the working range. Below forty, the model starts binding incidental details to the character — a background, a jacket, a particular crop. Past a hundred, the extra images mostly repeat what the earlier ones already said.

Around 200 candidates are generated per character, and the dataset is selected from them — for variety of pose, distance, angle, expression, lighting and setting, not for which images look best.

A dataset that is uniformly beautiful in one lighting setup teaches the model that this person arrives with that lighting.

That generalises: anything constant across the dataset and absent from the captions is what the trigger word ends up meaning. If every image is lit the same way and no caption mentions lighting, the trigger learns the lighting.

Captions

Captioning runs on Qwen3-VL-8B-Instruct locally, in batches of 4, falling back to one-at-a-time if a batch fails. Three rules shape every caption.

Trigger first, always

Captions are written trigger, description. Writing description, trigger does not fire the LoRA reliably. Position genuinely matters.

The traits phrase goes in every caption, in full

Every caption for a character carries the same anatomical description. That is what holds the physique together — reference conditioning is heavily face-biased in every model tested here, so the face transfers on its own while the body drifts back to the base model's prior unless the text insists otherwise.

No magnitude words

"Extremely muscular", "very large", and category nouns produced a physique noticeably bigger than the reference. Describe anatomy precisely instead: well-developed deltoids works, huge shoulders overshoots.

Describe what should vary; stay silent about what should be learned.

For a character LoRA, describe the person precisely and let the environment vary. For a style LoRA it is the exact inverse: describe the content thoroughly and say nothing about the look, because the look is what is being trained.

The ai-toolkit config, in full

A real generated config, copied verbatim:

job: extension
config:
  name: <character>
  process:
  - type: sd_trainer
    device: cuda:0
    trigger_word: <character>
    network:
      type: lora
      linear: 32
      linear_alpha: 32
    save:
      dtype: float16
      save_every: 250
      max_step_saves_to_keep: 6
    datasets:
    - folder_path: <dataset>
      caption_ext: txt
      caption_dropout_rate: 0.05
      shuffle_tokens: false
      cache_latents_to_disk: true
      resolution:
      - 512
      - 1024
    train:
      batch_size: 1
      steps: 2000
      gradient_accumulation_steps: 1
      train_unet: true
      train_text_encoder: false
      content_or_style: content
      gradient_checkpointing: true
      noise_scheduler: flowmatch
      optimizer: adamw8bit
      lr: 0.0002
      dtype: bf16
      ema_config:
        use_ema: true
        ema_decay: 0.99
    model:
      name_or_path: <krea2-raw>
      arch: krea2
      quantize: true
      qtype: qfloat8
    sample:
      sampler: flowmatch
      sample_every: 2001
      width: 1024
      height: 1024
      prompts: []
      guidance_scale: 3.5
      sample_steps: 30

Why each setting is what it is

resolution: [512, 1024] — and specifically not 768

Krea 2 was pretrained at 256, 512 and 1024. Never at 768.

The [512, 768, 1024] in ai-toolkit's example configs is inherited from Flex configurations. It is copied boilerplate, not a Krea 2 recommendation. Training at a resolution the base model never saw spends your step budget on a bucket it has no prior for.

512 px 1.17 s 48% of steps 1024 px 3.66 s 52% 0 s 1.5 s 3.0 s Blended average across the run: 2.45 s/step
Cost per training step by resolution bucket, measured on the RTX 5090. The two buckets are close to an even split of steps, which is what puts the blended rate near 2.45 s.

linear: 32, linear_alpha: 32

Rank 16 at 1000 steps gave a likeness worth calling "a good 60%". Rank 32 at 1500 steps was noticeably better on the same dataset and seed. For a photographic character, 32 is where it stops improving enough to justify the cost. Alpha equals rank; no scaling games.

steps: 2000

The same two characters were trained at 2000 and at 2500 steps and compared across five framings with identical seeds. The identity was already resolved at 2000. The extra 500 steps bought nothing, and cost about 24 minutes each.

quantize: true, qtype: qfloat8

This is what makes Krea 2 fit. Blackwell has native FP8 tensor cores, so quantising to qfloat8 costs very little and brings a 26 GB model comfortably inside 32 GB with room for the optimizer, the gradients and the cached latents.

train_text_encoder: false

UNet only. Training the text encoder on a 40–80 image dataset overfits the trigger word to the dataset's phrasing rather than to the person.

shuffle_tokens: false

Deliberate. The captions are ordered — trigger first, then the traits phrase, then the variable content. Shuffling tokens would destroy the position rule above.

sample_every: 2001 — previews disabled during training

The step count is 2000, so an interval of 2001 means the sampler never fires, and the prompt list is empty too. This is worth 30–40 minutes on every run: four preview images at sample_steps: 30 cost about five minutes on a 5090, and sampling at every checkpoint means paying that eight times. Previews are rendered afterwards in ComfyUI at 8 steps instead.

Training times, measured

A real run, caught in progress:

<character>: 97%|#########7| 1948/2000 [1:24:17<01:33, 1.80s/it, lr: 2.0e-04 loss: 4.288e-02]

1,948 steps in 1 hour 24 minutes. From the checkpoint timestamps of that same run, 250 steps apart:

StepWritten atElapsed
50007:05
75007:1712 min
100007:2710 min
125007:369 min
150007:4711 min
175007:5912 min

1,250 steps in 54 minutes, or 2.59 s/step. The long-run average across many characters is 2.45 s/step, about 82 minutes for a full 2000-step character LoRA. The run above is slightly slower because the machine was in use.

Measured and rejected

This is the section worth reading twice.

512 + 1024 - 2000 steps CHOSEN 82 min 512 only - 0.25 MP REJECTED 43 min 0 35 min 70 min Half the time, for a product whose whole value is the likeness.
Minutes per character LoRA. The 0.25 MP run was measured over 1,970 steps and compared on the same character, same dataset, same seed.

Training at 0.25 MP

43 minutes instead of 82 — almost exactly half. Rejected: the likeness degrades visibly. Half the time for a worse product is not a trade worth taking when the product is the likeness.

Fizgig, a Krea 2 specific trainer

A trainer built specifically for Krea 2, advertising dramatically faster training. It would not start under WSL2 at all — CUDA driver error: unknown error down four different install routes, with CUDA working fine in isolated tests. On native Windows it does run, and gives 2.97 s/step at 1024, which is slower than ai-toolkit's 2.45. Its advertised "20 minutes" came from training at 0.25 MP, and its standard Krea 2 preset is rank 32/32 — the same as here.

The lesson generalises to every speed claim you will ever read: check what resolution the number was measured at. A trainer that is "3x faster" at 0.25 MP against your 1 MP is not faster. It is smaller.

Generating images in ComfyUI

Model      krea2_turbo_mxfp8.safetensors
Sampler    euler
Scheduler  simple
Steps      8
CFG        1.0
Size       2 MP (9:16 for characters)
LoRA       character LoRA at weight 1.0

Measured throughput, from the file timestamps of two real batches: 11.7 s median over 120 images, and 13.2 s over 70. That is end-to-end per image, including saving; the sampling itself is about 8 seconds.

Startup costs 21 seconds to launch plus 8 to load the models, which is why ComfyUI stays open during a batch. The flip side: closing it frees 18 GB of VRAM — measured, from 11.2 GB free to 29.5. On a single-GPU machine that is the difference between being able to start a training run and not.

Things that cost me time

A LoRA that needs weight 1.5 to apply is a symptom, not a setting. Something upstream is wrong — usually the trigger word, the caption position, or an undertrained rank.

Copy-of-a-copy degrades. Bootstrap a character from a small LoRA, generate a synthetic dataset from it, and the bootstrap's likeness becomes the synthetic set's likeness, which becomes V1's. Make the bootstrap better than it needs to be.

Training on a model's own output cannot expand its priors. Synthetic generation works for characters, because the bootstrap supplies an identity the base model does not have. It does not work for shifting a distribution.

Style LoRAs are limited by subject variety, not image count. A style trained on 49 frames that were all night-time urban scenes failed on cars, animals, landscapes and daytime exteriors. Rank 32 and 86 frames changed nothing.

Checking a filename is not checking identity. A copy step here once compared file size to decide whether to reinstall a LoRA, so a retrained LoRA at the same rank was never copied and every test ran against the old weights. Compare hashes.

Where these characters live

This is the pipeline behind the original synthetic character LoRAs for Krea 2 — each one a specific person: the same face, the same build, the same presence, in any scene you can describe.

Also here: the AI reels bundle, 900+ vertical clips with no voiceover and no watermarks. And custom LoRA commissions — you bring the source material and pick the base model: Krea 2, Flux, SDXL, Illustrious, Pony, Qwen-Image or WAN.

Browse the characters

← All guides