Repository navigation
feat(sd3): support LoRAs - #9824
Pfannkuchensack wants to merge 18 commits into
Conversation
Identify diffusers, PEFT, SimpleTuner LyCORIS and kohya SD 3/3.5 LoRAs (and DreamBooth folders) with their Medium/Large size instead of misfiling them as Qwen-Image or SDXL; refuse layouts that cannot be applied. Add sd3_lora_loader/collection_loader; the denoise refuses LoRAs of another size before loading, patches transformer and CLIP-L/G (T5 skipped), and reserves sidecar patches under FP8 Storage. Record the SD 3 size on mains and LoRAs, filter the webv2 concept picker by it, and document LoRA support.
# Conflicts: # docs/src/content/docs/Users Guide/03.Models/03.model-format-support.mdx # docs/src/content/docs/Users Guide/04.Image Generation/generate-panel.mdx # invokeai/backend/model_manager/configs/lora.py # invokeai/frontend/api/schema.ts # tests/app/invocations/test_sd3_text_encoder.py
# Conflicts: # invokeai/frontend/api/schema.ts
lstein
left a comment
There was a problem hiding this comment.
Reviewed at a3dcf5e43c. I found no wrong images, leaks or conversion errors, but the items under "Blocker" and "Should fix" need attention before merge.
Blocker
Merge conflicts with main in configs/lora.py, 03.model-format-support.mdx and schema.ts (after #10094). schema.ts needs regenerating. In a scratch merge, keeping both sides of lora.py passed the config, SD 3 and FLUX.3 tests, and the probes from #10061 and #10094 did not overlap with SD 3 on the keys tried.
Should fix
-
A mismatched LoKR crashes instead of being refused (
sd3_denoise.py:427). The shape check in_raise_unless_loras_fitonly coversLoRALayer.- Trigger: an SD 3 main installed before this PR has no recorded size, so a Large LoKR passes the check. An SD 3 Medium LoKR that trains
norm1.linearon SD 3.5 Medium (9216×1536 vs 13824×1536) passes too. - Result:
RuntimeError: shape '[1536, 1536]' is invalid for input of size 5914624, reproduced at the first forward through the autocast wrappers. Weights are restored, butsd-3.mdx:101promises a refusal when generation starts. - Fix: compare each layer's delta shape, not just low-rank layers'.
- Trigger: an SD 3 main installed before this PR has no recorded size, so a Large LoKR passes the check. An SD 3 Medium LoKR that trains
-
Some SD 3 files still install as Qwen-Image (
lora.py:930). The Qwen probe only rejects whatis_sd3_lorarecognises. All of these identify asLoRA_LyCORIS_QwenImage_Configat width 1536:transformer.transformer_blocks.0.ff.net.0.proj.lora_A/B.weighttransformer.transformer_blocks.0.norm1.linear.lora_A/B.weight- a lone
transformer.transformer_blocks.0.attn.to_q.lora_A.weight, which also skips the promised refusal of incomplete layers lora_unet_transformer_blocks_0_attn_to_q.lora_down/up.weight, although the comment above_FLAT_TEMPLATE_REsays kohya-style trainers that keep diffusers names are covered
From reading the code, these layouts already went to Qwen before this PR. I did not trace what generation does with such a record. Qwen-Image never names
ff.netornorm1.linear, so the probe could reject them by name and also check the width of any 2-D layer. -
Re-identify can't fix a misfiled LoRA that the new probe refuses (
model_manager_default.py:88). Whenresult.configisNone, it raises "Unable to identify model format", which drops the refusal reason ininvalid_matches, and the old Qwen record stays in place.sd-3.mdxtells users Re-identify fixes misfiled LoRAs. This is from reading the code, not from a run. Please surface the reason, and have the docs say that a refused file stays as it was. -
a3dcf5e43cweakens a test (test_sd3_lora_probe_independence.py:160). Exact accepted-config lists became "these 3 configs are absent", though the old expectation still holds at this commit:[Anima]for diffusers and[]for kohya. As written, a new config claiming these files would pass. Please restore the exact lists. This also shows that a diffusers-layout CogView4 LoRA identifies as Anima, even though CogView4 has no LoRA support.
Minor
- Untested paths (
settings.test.ts:688): the code handlesvariant: nullcorrectly, but the test only usesundefined, and the API sendsnull. Changing the check to=== undefinedpasses the generation, models/core and image-actions tests, and so does hiding Medium LoRAs from Large mains. - Misleading message (
sd3_denoise.py:432): a low-rank SD 3 Medium LoRA on SD 3.5 Medium is told "another SD 3 size", but both are Medium. - Loose install checks: mixed 1536/2432 widths install as Medium because only the first attention layer is read, and
transformer_blocks.99.attn.to_qinstalls too. The denoise check catches low-rank layers at generation. - 1-D LoKR factors (
sd3_lora_constants.py:163): with otherwise valid Qwen layers, a file with 1-Dlokr_w1/lokr_w2becomesUnknown_Config._factor_shapeassumes 2-D tensors, which is the likely cause. I know of no trainer that writes this. - Refusal timing:
sd-3.mdx:101says "before anything is applied", but CLIP patching and encoding can run first. The PR text says "before it loads the transformer", but it is already loaded into RAM (sd3_denoise.py:218); only the move to VRAM is avoided. - Sidecar reservation:
calc_size()plus a flat 1 GiB undercounts F16 LoRAs cast to the inference dtype, though the margin should absorb that.mainnow haslora_sidecar_bytesfor this, so it is worth checking whether it fits after merging. - webv1:
ModelVariantSelecthas nosd3_*options, so it should render blank for sized SD 3 records (code read only).
What held up
- Conversion: the kohya path matched
convert_sd3_transformer_checkpoint_to_diffuserson 51 layers with no differences, including the qkv split, the adaLN swap and alpha scaling. Forward passes matched weights plus delta for direct, sidecar and FP8 hook application, and weights were restored exactly. - Runtime: the refusal path, patch restore, iterate reuse and the conditioning cache key showed no defects, and CFG does not double-patch.
- Records: rows without a variant load fine, and the variant survives a JSON round trip.
- Frontend and API: canvas graphs have no dangling or doubled edges, and a schema regenerated from this commit's Python equals the committed
openapi.json.
Not run: real SD 3 checkpoints, GPU or ROCm, or browser tests. The scratch merge got backend tests only, with no contract regeneration. Alpha handling for kohya split_qkv merged files is unverified.
# Conflicts: # docs/src/content/docs/Users Guide/03.Models/03.model-format-support.mdx # invokeai/backend/model_manager/configs/lora.py # invokeai/frontend/api/schema.ts
The fit check compared only low-rank layers, so a mismatched LoKR crashed at the first forward. The message no longer blames the size when both are Medium.
… 3 install checks Qwen-Image refuses SD 3 module names, mismatched attention widths and half-layers; SD 3 reads kohya names that keep diffusers' modules. Mixed widths, blocks beyond the depth and 1-D LoKR factors are handled or refused with a reason.
…-less SD 3 files The fit check no longer builds deltas on the CPU. Files that name only SD 3 modules without a complete attention layer are refused with a reason instead of becoming unknown records.
…size MLP-only files by width Tucker and kernel-dimension factors of the patch embedding are no longer refused. MLP-input and modulation layers state the model width like attention does.
|
Thanks, all addressed (merge 10ec733, then 37ea8d3 … dde1ad0): Blocker: Should fix
Minor
|
# Conflicts: # invokeai/frontend/api/schema.ts
Summary
SD 3 / 3.5 LoRAs could not be used, and most installed as another model:
After this PR, SD 3 LoRAs are identified with their size and applied:
Formats:
transformer.,base_model.model.), includingattn2, the convpos_embed.projand top-level modules.lycoris_with flattened names, LoKR).checkpoint-*copies.Recognition: SD 3 is identified by its widths (1536 Medium, 2432 Large), because Qwen-Image uses the same attention names. Attention, MLP-input and modulation layers all give the width. Kohya-style files that keep diffusers' names (
lora_unet_transformer_blocks_0_attn_to_q…) are read too. The Qwen-Image probe now rejects SD 3 widths, module names Qwen-Image never has (ff.net,norm1.linear), incomplete attention layers, and CogView4'sattn1. The generic SD/SDXL probe rejects kohyajoint_blocks.Size: new optional
variant(sd3_medium/sd3_large) on SD 3 Diffusers mains and SD 3 LoRAs, set at install. SD 3 Medium and 3.5 Medium sharesd3_medium. The webv2 concept picker hides LoRAs of the other size when both sides record one.Nodes: new
sd3_lora_loaderandsd3_lora_collection_loaderwith transformer, CLIP-L and CLIP-G fields.sd3_text_encoderpatches CLIP-L and CLIP-G with separate prefixes.sd3_denoise(1.2.0 → 1.3.0) refuses, before the transformer moves to the GPU (it is already in RAM, and text encoding can have run):It patches in place, or through a sidecar with FP8 Storage, and then reserves the patches.
webv2 and docs: SD 3 joins
LORA_SUPPORTED_BASES, the graph routes transformer and CLIP-L/G through the collection loader, and the size has labels and options in the model manager. The docs add a LoRA section to the SD 3 page and update the format matrix and the Generate panel page.Related Issues / Discussions
Closes #9741
QA Instructions
Automated:
CUDA_VISIBLE_DEVICES="" uv run --no-sync pytest -n 4 tests/backend/model_manager tests/backend/patches tests/backend/architectures tests/model_identification tests/app: 10,903 passed. The only failures were the bitsandbytes nf4/int8 tests, which need CUDA; with the GPU visible,tests/backend/model_manager/load/model_cache/torch_module_autocastpasses. Each new test file also passes on its own.convert_sd3_transformer_checkpoint_to_diffuserson random MMDiT weights, for Medium withattn2and for Large.Clybius/mspaint-style-sd3.5mediumand a DreamBooth folder.invoke()hands CLIP-L and CLIP-G their own layers.pnpm -C invokeai/frontend/api lintandtest,pnpm -C invokeai/frontend/webv2 lint, and the webv2 generation/model tests (incl. settings/taxonomy and the concepts browser test): passed.HF_ENDPOINTpoints at a mirror that lacks the Xet endpoints; they pass without it.Manual E2E on an RTX 4090 with SD 3.5 Medium (Diffusers), same seed with and without LoRA:
LoRAs:
AdamLucek/sd3.5M-jareklfoto-LoRA-DreamBooth(diffusers)bghira/sd35m-sfwbooru-lycoris(SimpleTuner LoKR)jieliu/SD3.5M-FlowGRPO-PickScore(PEFT)Clybius/mspaint-style-sd3.5medium(kohya, withattn2and the swapped adaLN)linoyts/Yarn_art_SD3_LoRA(diffusers with CLIP-L/G)tensorart/stable-diffusion-3.5-medium-turbo(4 steps)Each applies visibly. Yarn with the text-encoder part differs from yarn with the transformer part only. The turbo LoRA gives a clean image at 4 steps where the base model does not.
FP8 Storage: mspaint and yarn with CLIP.
Logs: the server log contains no
Failed to find module for LoRA layer keyorSkipping LoRA layer.Browser (webv2): for SD 3.5 Medium, the concepts slot shows only Medium LoRAs, and a LoRA marked
sd3_largeis hidden. Sending a Large LoRA through a hand-built graph fails before loading with "LoRA '…' was trained for SD 3.5 Large and does not fit this SD 3 / 3.5 Medium model."Review
The review findings are resolved:
lora_transformer_prefix is gone. No SD 3 file uses it, and it tied with FLUX OneTrainer files.ModelManagerService.reidentify_model).override_fieldsis no longer mutated.Remaining limitations:
lora_transformer_and also train both text encoders are still taken as SDXL, as before this PR.calc_size()plus 1 GiB:main'slora_sidecar_bytesreadsin_features/out_features, which the Conv2dpos_embed.projlacks.pos_embed.projhas.Compatibility / Rollout
variantis optional and defaults toNone, so stored SD 3 records stay valid and discriminator tags do not change. New installs record it. Existing mains and LoRAs get it, and misfiled LoRAs get the right base, through Re-identify in the Model Manager. A file the probes now refuse keeps its record, and Re-identify reports why. Re-identify also resets a model's default settings, which the docs note. The picker and the denoise accept records without a size, and the denoise then checks the shapes.SD3VariantTypejoins the variant unions; the OpenAPI schema and types are regenerated.generateGraphNodeTypes.jsonregenerated.Checklist
What's Newcopy (if doing a release after this PR)Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.