I audited 92 of my own LoRAs. Six things were wrong. None of them were the LoRAs.

I have been training SDXL LoRAs for about eleven months — places, creatures, interiors, faces, heraldry, all of it visual research for a novel I'm writing. Ninety-two models published here. Somewhere north of two thousand downloads.
And for most of that time I had a nagging feeling that the renders never quite looked like the training data. Close. Recognizable. But washed out, somehow. Like the model knew the thing and was only willing to tell me about half of it.
Two nights ago I finally sat down and audited the whole library instead of guessing. Thirteen controlled renders, one changed variable at a time. Here is everything I found, including the two things I got confidently wrong in the middle.
Short version: six separate problems, and not one of them required retraining anything.
Step 0 — read your own metadata. It costs zero GPU time.
Before rendering a single test image, read the ss_* metadata block out of your safetensors files. You do not need to load the tensors. The header is JSON: first 8 bytes are a little-endian length, then that many bytes of JSON.
For each file you get base model, training resolution, network rank and alpha, dataset image count, epochs, and — the important one — ss_tag_frequency, the actual caption vocabulary the model was trained on.
This took about a minute for 170 files and answered more questions than the next six hours of rendering did.
Do this first. It is free and it is the highest-value step in the whole process.
Finding 1 — every one of my LoRAs had a trigger word. I was using none of them.
Twenty of the twenty-seven models I actually use in production carry a clean activation token appearing on 85–100% of their training images. jsolarum_details. jstewards_living_stone. jisla_buena_life. All of them.
Not one of my prompts contained a single trigger. A year of renders, fired with the <lora:> tag alone.
I assumed this would be the whole story. It wasn't — see the mistakes section — but it is real and it is free to fix.
⚠ And check the trigger against the filename, because they lie. Four of mine did not match:
A model named
symbols_and_glyphstriggers onjsolarum_glyphA model named
velvet_room_v2triggers onjsolarum_cabaret— 314 of 314 images. It is not a v2 of anything. It is a different dataset wearing the wrong name.A "characters" model is 112/136 tagged as a place set
A "recipe vault" model is half geometry
The trigger token is the truth. The filename is a label somebody typed while tired.
⛔ And one model named canines has a cat as its top tag — 94 of 158 images. Sixty percent of my dog LoRA is cats. It has been in production for months. I would never have found that without reading the metadata.
Finding 2 — caption quality sorts your library into three tiers
Reading ss_tag_frequency across the library, three distinct regimes fell out:
Tier A — flat captions. One trigger plus 6–7 identical phrases on every single image. The concept never attaches to the trigger; it smears evenly across all seven tokens. Prompting with the trigger alone gets you roughly a seventh of what the model knows.
➜ Prompt these with the whole caption block, not just the trigger.
Tier B — properly captioned. Trigger on 100% of images, plus varied per-image descriptions. These are your good models. They respond to the trigger plus a phrase near their training vocabulary.
Tier C — auto-caption noise. Clean trigger, but the descriptions are BLIP output — "a blue motorcycle parked on a beach". The vocabulary is meaningless.
➜ I tested one of these against a no-LoRA control. It changed the image and added nothing of its subject. Tier C is effectively dead weight until recaptioned.
You can sort your entire library into these three buckets from the metadata alone, before rendering anything.
Finding 3 — the checkpoint was hiding the style half of every model
This is the one that cost me a year.
I train on Juggernaut-XL v9 and render on it. One of my flat-caption sets has painterly biopunk in all 215 of its captions, and painterly output never, ever appeared.
I assumed the model hadn't learned it. So I ran a 2×2 — same seed, same prompt, same everything, only the checkpoint and the LoRA changing:
Juggernaut-XL v9, LoRA on — photoreal, carved detail
SDXL base 1.0, LoRA on — ★ painterly, carved detail
Juggernaut-XL v9, LoRA off — photoreal, generic
SDXL base 1.0, LoRA off — photoreal, generic
The LoRA had learned it perfectly well. Juggernaut was suppressing it at inference.
Juggernaut is a photorealism merge with a very strong prior, and that prior was quietly overruling the style half of everything I trained on it. Same models, different checkpoint, and the register I had been chasing for months simply appeared.
⚠ Two honest caveats. A fourth render — the same prompt with the words painterly biopunk deleted — came back photoreal. So the LoRA carries the craft (carving, age, hand-worked surfaces) but not the style on its own. You need the checkpoint and the words. And base SDXL is weaker than a good merge in other ways, so this is a trade, not a free win. I checked faces specifically, since that was the thing most likely to break the plan, and they came out fine.
If your LoRAs feel muted, render one test on a different checkpoint before you retrain anything.
The rules, compactly
Thirteen renders, one variable at a time. In rough order of how much they cost me:
Read your metadata first. Free, and it answers most of it.
Use your trigger words. Check them against the filename; the filename lies.
Test on a second checkpoint before concluding a LoRA didn't learn something.
Style words are required, not garnish. The checkpoint permits the style; the word asks for it; the LoRA helps the request survive a long prompt. You need all three. ⚠ See the update at the end — this turned out to be a symptom of how I captioned, not a property of LoRAs.
★ Cast an anchor. Every prompt needs at least one LoRA that carries the world or subject. Two modifier LoRAs — texture plus expression, say — is a null stack, and the base model will happily fill the gap with its own defaults. This was my single most repeated failure: three separate images, one cause.
Subject first. Put the scene before the style block. SDXL weights earlier tokens more heavily, and I watched a seven-phrase style block outvote "a great hall" twice.
★ Name the inhabitation. A LoRA gives you the shape of a room. It will not give you the mug, the cable run, the pinned-up roster. Ask for those by name or you get an architectural render with nobody in it.
⚠ Use a material word once. I put "copper" in a prompt three times — the graft, the pipes, the room — and the person came out made of copper. One material, on one object.
⚠ Occupation nouns are anchors to the real world. "Dock laborer" dragged a complete twentieth-century package — uniform, hard hat, nationality, age — into three separate images. Describe the body and what it's doing, not the job.
Put national insignia in your negative. One render produced a flag patch on a sleeve, in a setting that has no such nation. "Branded insignia" did not catch it. Add
flag, national insignia, military patch, hi-visexplicitly.Weight discipline. 0.45–0.55 for a two-LoRA stack held up across everything I tested. Cranking weight to force a diluted signal burns before it helps.
What I got wrong, in order
I want this part in here because I read a lot of confident technique posts and very few that show the wrong turns.
I called the trigger-word finding "the biggest fix of the year" before testing it. Then I tested it. The trigger changes the image substantially — but it did not produce the register I was looking for. Real finding, wrong size, and I had already written it down as a conclusion.
I then said the LoRA "learned the painterly style fine" and Juggernaut was hiding it. Half right. Removing the style words from the prompt gave a photograph. The LoRA does not carry the style unaided. I had to correct that claim too.
And the single most useful thing in the whole audit was a control I didn't ask for. I was running some of this through a second assistant in a terminal. It rendered the test I specified and then added a no-LoRA control on its own initiative — same prompt, same seed, model removed. Without it, my test proved nothing: the caption block literally asks for willow and moss, so the base model would have drawn them regardless. That control is the only reason the result means anything.
If you take one methodological thing from this: render the no-LoRA version. Every time. It is one image and it is the difference between a finding and a vibe.
Update, one day after publishing: a reader corrected this article, and the correction is better than what it replaces. DetroitWobbly pointed out that the style never attached to my trigger for a simple reason — I told 215 captions the thing was called "painterly biopunk." Those words own the concept, so the trigger never had to learn it. "You need the checkpoint and the words" isn't a property of LoRAs; it's a symptom of my captioning, and it's fixable by retraining. Their rule, which they call A0: don't caption what you want the trigger to remember; caption what you want the prompt to remain able to change. That also folds my Tier A finding into the same cause. I'm re-running the 215-image set both ways to find out.
Running this on your own library
Read the safetensors headers. Base model, resolution, rank/alpha, image count, epochs,
ss_tag_frequency.Sort into the three caption tiers.
Flag anything trained on fewer than ~10 images for many epochs — I found three at 3 images / 200 epochs, badly overfit, one of them still in production.
Flag any trigger that doesn't match its filename.
Then render: strength sweep, a neutral-prompt contamination test, and a no-LoRA control. Same seed throughout.
Steps 1–4 cost nothing but a minute. I wish I had done them in month one.
The script I used to read the headers is attached to this article if you want to run the same pass.
All ninety-two models are free here and will stay that way. They're visual research for Solarum, a biopunk novel I'm writing — two hundred years after a collapse, what's left of humanity living around Saturn, and an authority offering to let everyone sleep through the hard part.
Chapters and world notes, as they get written: signalofsolarum.substack.com
Happy to answer questions in the comments. If you run the metadata pass on your own library and find something odd, I'd genuinely like to hear it — I suspect the mismatched-trigger problem is much more common than anyone realizes.