ArtificialGuyBR

Home / Blog / Training LoRAs

Textual Inversion Training Guide 2025: Embeddings vs LoRAs

When to use Textual Inversion over LoRA, Kohya_ss and A1111 settings, and the learning-rate gap explained.

10 sources cited Training LoRAs

Textual Inversion Training Guide 2025: Embeddings vs LoRAs

Textual Inversion (TI) teaches Stable Diffusion new concepts by learning only token embeddings inside a frozen text encoder. Unlike LoRA or full fine-tuning, TI never touches the model weights — it simply adds a new "word" to the tokenizer and optimizes its vector representation. The result is a tiny .safetensors file (typically 10–50 KB) that you drop into your embeddings folder and invoke by filename in prompts.

Textual inversion new token → existing weights Learns a vector in token space. Model weights untouched. Kilobytes. Portable inside one base. LoRA low-rank delta → the weights Learns a change to the weights. Alters how the model computes. Megabytes. Tied to its base. Both steer output, at different layers of the stack. Rule of thumb: an embedding can name a thing the model already knows; a LoRA can teach it something new.
Embedding vs LoRA: what each one actually changes

How LoRA training differs from TI — the two methods complement each other for different use cases.

TL;DR

When an Embedding Still Beats a LoRA

ScenarioWhy TI winsSource
Cross-model portabilityTI trained on vanilla base model works on most finetunesA1111 wiki
VRAM limitedOnly token embeddings update; U-Net and VAE stay frozenKohya_ss docs
Quick iteration1,000–2,000 steps typical for TIKohya_ss docs
Style transfer--use_style_template isolates aesthetic without subject biasKohya_ss docs

Embeddings shine when you need a portable concept token that works across models derived from the same base. LoRAs still win for multi-subject scenes and precise pose control.

How Textual Inversion Works

TI adds a special placeholder token to the tokenizer (e.g., <mychar>), expands the embedding matrix by one row, then freezes the entire model — U-Net, VAE, and text encoder — while optimizing only that new row. During training, each image is paired with a prompt template containing the placeholder. The gradient flows through the frozen text encoder into the new embedding vector, nudging it until the model reconstructs the training images when prompted.

The mechanism is model-specific by design. The embedding dimension, tokenizer vocabulary, and text encoder architecture are fixed per model family. SD 1.5 uses a 768-dim CLIP-L encoder; SDXL uses dual CLIP-L/CLIP-G encoders; FLUX and SD3 use entirely different transformer architectures. This is why a TI embedding trained on SD 1.5 cannot load on SDXL or FLUX — the token ID space and embedding dimensions do not match.

Training in Kohya_ss (SD 1.5 / SDXL)

Prerequisites

Core Parameters

ParameterRecommended ValueNotes
--pretrained_model_name_or_pathpath to v1-5-pruned-emaonly.safetensors or SDXL baseMust be vanilla base model
--token_stringunique string like mychar123Must not exist in tokenizer
--init_wordconcept word like woman or catMulti-word allowed (e.g., anime girl)
--num_vectors_per_token1 (default) or 4–5 for complex subjectsMore vectors = more prompt tokens consumed
--learning_rate1e-6 (Kohya docs) or 2e-6 (code default) or 0.005 (paper)See learning rate note
--lr_schedulerconstant
--optimizer_typeAdamW8bit (preferred) or AdamWAdamW8bit + bf16 may error on Linux
--mixed_precisionbf16 (RTX 30/40) or fp16
--max_train_steps1000–2000Save checkpoints every 200 steps
--cache_latentsenabledReduces VRAM significantly
--use_object_templateenabled (for subjects)Ignores caption files
--use_style_templateenabled (for styles)Ignores caption files

Learning Rate Discrepancy

Three values circulate in the community:

All produce working embeddings. The 0.005 value reproduces the paper's setup; 1e-6/2e-6 account for Kohya_ss's different optimizer, precision, and typical dataset sizes. Start at 1e-6, increase only if convergence stalls.

Example Command (SD 1.5)

accelerate launch --num_cpu_threads_per_process 2 train_textual_inversion.py \
  --pretrained_model_name_or_path="models/Stable-diffusion/v1-5-pruned-emaonly.safetensors" \
  --train_data_dir="data/mychar" \
  --output_dir="output/mychar" \
  --output_name="mychar-ti" \
  --save_model_as=safetensors \
  --token_string="mychar123" \
  --init_word="woman" \
  --num_vectors_per_token=5 \
  --learning_rate=1e-6 \
  --lr_scheduler="constant" \
  --optimizer_type="AdamW8bit" \
  --mixed_precision="bf16" \
  --cache_latents \
  --use_object_template \
  --vae="models/VAE/vae-ft-mse-840000-ema-pruned.safetensors" \
  --max_train_steps=1000 \
  --save_every_n_steps=200

For SDXL, use sdxl_train_textual_inversion.py with --resolution=1024,1024 and the SDXL base checkpoint.

Step-by-step LoRA training tutorial — if you decide LoRA fits your use case better.

Training in A1111 WebUI

  1. Open the Textual Inversion tab
  2. Create embedding: name it, set initialization text (e.g., woman), choose vectors per token (1–16)
  3. Preprocess: point to source images, enable "Create flipped copies" for data augmentation
  4. Train: select embedding, set learning rate (default 0.005), max steps (1,000–5,000), log directory
  5. Template: use subject.txt for objects/people, style.txt for aesthetics
  6. Start training — monitor loss and preview images

A1111 supports learning rate scheduling via prompt syntax: 0.005:100, 1e-3:1000, 1e-5 drops LR at specified steps.

Using the Trained Embedding

  1. Copy the .safetensors file to stable-diffusion-webui/embeddings/
  2. No restart needed — the embedding loads immediately
  3. In prompts, use the filename (without extension) as the token: photo of mychar-ti
  4. Weight it like any token: (mychar-ti:1.2) increases influence

Negative embeddings and token weighting guide — pair your trained embedding with proper weighting for best results.

Common Errors and Fixes

SymptomCauseFix
Loss: nanLearning rate too highDrop to 1e-6 or lower; use constant scheduler
Token string already exists in tokenizerPlaceholder collides with vocabularyAdd numbers/special chars: mychar123, <mychar>
CUDA setup error with AdamW8bit + bf16Linux platform incompatibilitySwitch to AdamW + bf16 or AdamW8bit + fp16
Embedding works on training model onlyTI is model-specific by designTrain on vanilla base model for best portability
No improvement after 500+ stepsToken string missing from captions/templatesEnsure --use_object_template enabled or token appears in captions
Overtraining artifacts at 1,300+ stepsToo many steps for dataset sizeStop at 800–1,000 steps; use earlier checkpoint

FAQ

When should I use an embedding instead of a LoRA? Use embeddings when file size matters (sharing, mobile), you need cross-model portability, or you are training a single subject/style with limited VRAM. LoRAs handle multi-concept scenes and higher fidelity better.

What learning rate is best for textual inversion? Start at 1e-6 with Kohya_ss (AdamW8bit, fp16/bf16). Use 0.005 only if reproducing the original paper setup (vanilla Adam, fp32, 3–5 images). Both converge; 1e-6 is safer for larger datasets.

Can I use embeddings across different Stable Diffusion models? Only within the same architecture family. An SD 1.5 embedding works on most SD 1.5 finetunes but not on SDXL, SD3, or FLUX. Each model family needs its own TI training.

How many training images do I need for textual inversion? The original paper showed 3–5 images suffice. Community practice uses 15–25 for robustness. More images require proportionally more steps.

Does textual inversion work with SDXL or FLUX? SDXL: yes, via sdxl_train_textual_inversion.py. FLUX and SD3: no — their transformer architectures lack TI implementations in Kohya_ss or diffusers.

Why is my embedding not working on other models? TI binds to a specific model's tokenizer and text encoder. Finetunes that modify the text encoder (e.g., CLIP skip, merged models) break compatibility. Train on the vanilla base for maximum portability.

What does --num_vectors_per_token do? Each vector consumes one token from the 77-token prompt limit. Default is 1. Values 4–5 capture complex subjects better but reduce prompt space. The paper found a single vector often suffices.

Model Support Summary

| Model Family | TI Support | Script |

| SD 1.x / 2.x | Yes | train_textual_inversion.py |

| SDXL | Yes | sdxl_train_textual_inversion.py |

| SD 3 / 3.5 | No | LoRA only |

| FLUX.1 | No | LoRA only |

| HunyuanImage | No | LoRA only |

| Anima | No | LoRA only |

For SD3, FLUX, and newer architectures, LoRA remains the only personalization option in Kohya_ss.


Sources

json
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {"@type": "Question", "name": "When should I use an embedding instead of a LoRA?", "acceptedAnswer": {"@type": "Answer", "text": "Use embeddings when file size matters, you need cross-model portability, or you are training a single subject/style with limited VRAM."}},
    {"@type": "Question", "name": "What learning rate is best for textual inversion?", "acceptedAnswer": {"@type": "Answer", "text": "Start at 1e-6 with Kohya_ss or 0.005 to reproduce the original paper setup."}},
    {"@type": "Question", "name": "Can I use embeddings across different Stable Diffusion models?", "acceptedAnswer": {"@type": "Answer", "text": "Only within the same architecture family. SD 1.5 embeddings do not work on SDXL, SD3, or FLUX."}},
    {"@type": "Question", "name": "How many training images do I need?", "acceptedAnswer": {"@type": "Answer", "text": "The original paper showed 3–5 images suffice; community practice uses 15–25 for robustness."}},
    {"@type": "Question", "name": "Does textual inversion work with SDXL or FLUX?", "acceptedAnswer": {"@type": "Answer", "text": "SDXL: yes via sdxl_train_textual_inversion.py. FLUX and SD3: no."}},
    {"@type": "Question", "name": "Why is my embedding not working on other models?", "acceptedAnswer": {"@type": "Answer", "text": "TI binds to a specific model tokenizer and text encoder. Finetunes that modify the text encoder break compatibility."}},
    {"@type": "Question", "name": "What does --num_vectors_per_token do?", "acceptedAnswer": {"@type": "Answer", "text": "Each vector consumes one token from the 77-token prompt limit. Default is 1."}}
  ]
}