Nodes/was-node-suite-comfyui/Power LoRA Merger
ComfyUI Node Runs on cloud

Power LoRA Merger

Bake your LoRA stack into one file

By WASasquatch·Created 4 years ago·Updated a day ago· 1,864
Power LoRA Merger
  • model
  • clip
  • options
  • model
  • clip
  • lora_path
◄output_filenamemerged_lora.safetensors►
◄output_model_strength1.00►
◄output_clip_strength1.00►
◄modesvd►
◄block_mix_recipeconcept_a_style_b►
◄lora_1_enabledtrue►
◄lora_1None►
◄lora_1_weight1.00►
◄lora_2_enabledtrue►
◄lora_2None►
◄lora_2_weight1.00►
◄lora_3_enabledtrue►
◄lora_3None►
◄lora_3_weight1.00►
◄lora_4_enabledtrue►
◄lora_4None►
◄lora_4_weight1.00►
◄lora_5_enabledtrue►
◄lora_5None►
◄lora_5_weight1.00►
◄lora_6_enabledtrue►
◄lora_6None►
◄lora_6_weight1.00►
◄lora_7_enabledtrue►
◄lora_7None►
◄lora_7_weight1.00►
◄lora_8_enabledtrue►
◄lora_8None►
◄lora_8_weight1.00►
◄lora_9_enabledtrue►
◄lora_9None►
◄lora_9_weight1.00►
◄lora_10_enabledtrue►
◄lora_10None►
◄lora_10_weight1.00►
◄lora_11_enabledtrue►
◄lora_11None►
◄lora_11_weight1.00►
◄lora_12_enabledtrue►
◄lora_12None►
◄lora_12_weight1.00►
◄lora_13_enabledtrue►
◄lora_13None►
◄lora_13_weight1.00►
◄lora_14_enabledtrue►
◄lora_14None►
◄lora_14_weight1.00►
◄lora_15_enabledtrue►
◄lora_15None►
◄lora_15_weight1.00►
◄lora_16_enabledtrue►
◄lora_16None►
◄lora_16_weight1.00►
◄lora_17_enabledtrue►
◄lora_17None►
◄lora_17_weight1.00►
◄lora_18_enabledtrue►
◄lora_18None►
◄lora_18_weight1.00►
◄lora_19_enabledtrue►
◄lora_19None►
◄lora_19_weight1.00►
◄lora_20_enabledtrue►
◄lora_20None►
◄lora_20_weight1.00►
◄lora_21_enabledtrue►
◄lora_21None►
◄lora_21_weight1.00►
◄lora_22_enabledtrue►
◄lora_22None►
◄lora_22_weight1.00►
◄lora_23_enabledtrue►
◄lora_23None►
◄lora_23_weight1.00►
◄lora_24_enabledtrue►
◄lora_24None►
◄lora_24_weight1.00►
◄lora_25_enabledtrue►
◄lora_25None►
◄lora_25_weight1.00►
◄lora_26_enabledtrue►
◄lora_26None►
◄lora_26_weight1.00►

You've got five LoRAs in a chain and you load them every single run at the same weights. Or you want to hand someone your look as a single file. Or two LoRAs fight each other and you want the fight settled once, not every generation. That's what this node is: it takes a stack of LoRAs, does the math, and writes one .safetensors into your LoRA folder that loads like any other.

Two things are worth knowing up front. It writes a file, so it isn't free - every run of the graph rewrites it. And merging is compression: what comes out is a good approximation of the stack, not a bit-exact one. For a style blend you'll never notice. For two LoRAs that each needed every rank they have, you will.

The rows and the mode

Rows are added with the buttons on the node - ➕ Add LoRA, ➖ Remove LoRA, ❌ Clear LoRAs, and ♻️ Refresh LoRA List, which is the one you press after downloading something new, because the dropdowns are built when ComfyUI builds node definitions. Each row has lora_N_enabled, lora_N and lora_N_weight, and the weight range is deliberately wider than normal: −10 to +10, because a negative weight applies the LoRA in reverse, which is a genuinely useful trick for subtracting a concept you don't want.

mode is where it gets interesting. svd is the default and the one you'll use 90% of the time - it recompresses the combined stack back down to one rank, so the file stays small. Everything else exists for a specific job:

  • add stacks them exactly. Bigger file, closest match to loading them one after another.
  • rebase recompresses a single LoRA - a repack, not a merge.
  • add-diff / add-orth start from the first LoRA and add only what the others differ by; the orthogonalised version interferes less. This is the family to reach for when one LoRA's character is being coloured by another.
  • diff-export saves just the difference between the first two.
  • moe picks (or blends) the strongest source per module - mixture-of-experts flavour routing, tuned by moe_temperature over in the options node.
  • obfuscate rewrites a stack's factors without changing what it does. Legitimate uses exist; so does what you're thinking.
  • block-mix routes each part of the model to LoRA A or B by block_mix_recipe - concept_a_style_b and friends. Concept from one, style from the other, without either smearing into the other's territory.

The node counts your rows and complains if the mode doesn't match: rebase takes exactly one, block-mix exactly two, and the diff modes need at least two.

Outputs

lora_path is the one to pay attention to - the file's name relative to your LoRA folder, e.g. styles/mixed.safetensors (output_filename allows subfolders). Feed it to a loader or a text node to log what a run produced.

model and clip are the same model and clip you wired in, with the freshly merged LoRA already applied - so you can generate and judge the merge in the same queue instead of saving, finding it, and re-running. Both are optional. output_model_strength and output_clip_strength change only that live application; the saved file is untouched by them. If you don't connect a model, the model output is nothing - don't wire it downstream and then wonder.

options takes a Power LoRA Merger Options node for rank, dtype, module filters and the per-mode controls. Unconnected, you merge at rank 32 in bf16, which is a sane default.

Install

ComfyUI Manager → WAS Node Suite v3 → install → restart. Or:

cd ComfyUI/custom_nodes
git clone https://github.com/WASasquatch/was-node-suite-comfyui.git

Nothing is pip-installed and nothing is downloaded. ComfyUI 0.14.0+, Python 3.10+. The LoRA nodes live in the extras feature group, on by default.

Where people get burned

The merge runs out of memory. Several LoRAs at fp32, plus the SVD, is a real load. cpu in the options node does the whole thing on the processor at no VRAM cost, just slowly.

The merged LoRA comes out overcooked. Merging adds, so a stack of strong LoRAs at weight 1 each is stronger than the stack was when loaded separately. preserve_norm in the options node rescales each part back to the average source strength - that's the fix, and it's off by default.

Artefacts the sources don't have. Force compute_dtype to fp32 in the options node. bf16 is fine for storage; it's the arithmetic over hundreds of modules where small errors compound.

A LoRA's trigger words dominate everything. That's the text-encoder half of a merged file. Put lora_te in exclude_patterns on the options node and merge only the image side.

Fine detail goes missing. Your rank is too low for the material. 64–128 for a complicated merge, or set rank to 0 and let each module pick a rank from auto_rank_threshold.

CategoryWAS Suite/LoRA

Inputs (86)

NameTypeDefaultDescription
output_filenameSTRINGmerged_lora.safetensorsName to save the merged LoRA under, inside your LoRA folder. Sub-folders are allowed, for example 'styles/mixed.safetensors'. '.safetensors' is added when it is missing, and the name cannot step outside the LoRA folder.
output_model_strengthFLOAT1.000–100How strongly the merged LoRA is applied to the model output, if a model is connected. 1.0 is full strength, 0.5 is half. This does not change the saved file, only what comes out of the model socket.
output_clip_strengthFLOAT1.000–100The same for the clip output: how strongly the merged LoRA is applied to the connected clip. Lower it when a LoRA's trigger words are overwhelming the rest of the prompt. Ignored where no clip is connected, as with a diffusion transformer.
modeCOMBOsvdHow the LoRAs are combined. `svd` recompresses the combined result back to one rank, which keeps the file small and is the usual choice.
block_mix_recipeCOMBOconcept_a_style_bOnly read in `block-mix` mode: which of the two LoRAs each part of the model comes from. `all_a` and `all_b` route everything one way.
modeloptMODELOptional. A model to apply the merged LoRA to once it is saved, so the merge can be tested in the same run. Leave it unconnected to only write the file.
clipoptCLIPOptional, and only used when a model is connected too: the clip the merged LoRA's text-encoder half is applied to.
optionsoptWAS_LORA_MERGE_OPTIONSOptional settings from a Power LoRA Merger Options node: rank, precision, module filters and the per-mode controls. Leave it unconnected to merge at rank 32 in bf16.
lora_1_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_1optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_1_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_2_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_2optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_2_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_3_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_3optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_3_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_4_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_4optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_4_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_5_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_5optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_5_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_6_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_6optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_6_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_7_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_7optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_7_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_8_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_8optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_8_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_9_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_9optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_9_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_10_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_10optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_10_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_11_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_11optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_11_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_12_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_12optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_12_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_13_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_13optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_13_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_14_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_14optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_14_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_15_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_15optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_15_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_16_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_16optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_16_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_17_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_17optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_17_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_18_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_18optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_18_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_19_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_19optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_19_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_20_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_20optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_20_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_21_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_21optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_21_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_22_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_22optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_22_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_23_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_23optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_23_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_24_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_24optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_24_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_25_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_25optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_25_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.
lora_26_enabledoptBOOLEANtrueWhether this row takes part. `true` applies it; `false` mutes it and keeps the file and the strength it was set to, so neither is typed again to bring it back.
lora_26optCOMBONoneLoRA file this row uses. `None` leaves the row empty, which counts the same as switching it off.
lora_26_weightoptFLOAT1.00-10–10How strongly this row counts. 1.0 is the LoRA as it was trained, below 1 weakens it, above 1 pushes it past what it was trained for, and a negative value applies it in reverse. 0 leaves the row out.

Outputs (3)

NameTypeDescription
modelMODELThe connected model with the merged LoRA applied, or nothing when no model was connected.
clipCLIPThe connected clip with the merged LoRA applied, or nothing when no clip was connected. A model on its own is still patched.
lora_pathSTRINGThe saved file's name relative to your LoRA folder, such as 'styles/mixed.safetensors'. Feed it to a loader, or to a text node to record what a run produced.