🔬 Easy LoRA Extractor
Have the Before and After? Get the LoRA Out of It
- model
- clip
- model
- clip
- forensics
- lora_path
You know the situation: someone trained a checkpoint on top of a base you have, and you want the difference - the character, the style, the concept - as a reusable LoRA. Or you trained one yourself and want to hand out a 100 MB adapter instead of a 7 GB file. That's exactly what the Easy LoRA Extractor does: it takes a base (before) and a fine-tuned (after) checkpoint and extracts the delta as a LoRA.
This is the same trick the "LoRA extraction" scripts and web UIs do, but wrapped as a node with the full Easy LoRAMerger treatment - SVD rank selection, noise-aware thresholding, and a forensic report at the end.
How it works
The pipeline is straightforward under the hood:
- Both checkpoints get normalized into a shared key space.
- Keys present in both with compatible shapes get matched.
- The delta is computed:
Δ = tuned − base. - Each delta matrix is decomposed with SVD into the low-rank
lora_A/lora_Bpair that is a LoRA.
The clever parts are the knobs around step 4. svd_mode defaults to auto_energy, which keeps the singular values that retain your chosen energy_threshold (default 0.95 - higher rank, more fidelity; lower, more compression). noise_thresholding is on by default and uses Marchenko-Pastur theory to tell real signal from noise - but the tooltip has the gotcha: turn it off for cross-model deltas, like a Turbo checkpoint vs its Base, where the "noise model" can wrongly reject genuine signal.
It also handles the bane of extraction: models that were trained with a baked-in LoRA attenuation. strength_multiplier set to auto detects that attenuation (via detection_mode - auto_fast samples 30 layers, auto_precise checks all of them) and compensates before the SVD runs.
Inputs worth setting
checkpoint_base- the unedited "before" state.checkpoint_tuned- the "after" state whose concept you're extracting.rank/rank_mode- leaverank_modeonautoand let the spectrum decide; it's the upper bound when auto.lora_format-native(CivitAI-compatiblediffusion_model.*keys, merged QKV) ordiffusers(HuggingFace-styletransformer.*keys, split QKV). Native unless you specifically need the Diffusers one.save_trigger- off by default, which previews the extraction applied to an optional connectedmodel/clipinstead of writing a file.
Outputs: model and clip (the extraction applied at strength_model / strength_clip, so you can preview before saving), forensics (the extraction report), and lora_path (where the .safetensors landed once save_trigger is on).
Installing it
Same as the rest of the pack - Manager (search "Easy LoRA Merger") or:
cd ComfyUI/custom_nodes
git clone https://github.com/Terpentinas/EasyLoRAMerger
Restart and you're done. No extra model files.
Gotchas
Cross-architecture extraction (Flux base + SDXL tuned) is a non-starter - the normalizer can't reconcile the key spaces, and the report will tell you so. And the #1 "why is my extracted LoRA weak" fix is letting strength_multiplier run in auto; the #2 fix is remembering that if your tuned checkpoint barely differs from base, there's not much signal to extract. Preview with save_trigger off before you commit anything to disk.
Inputs (20)
| Name | Type | Default | Description |
|---|---|---|---|
| checkpoint_base | COMBO | Reference (unedited) checkpoint — the 'before' state. | |
| checkpoint_tuned | COMBO | Fine-tuned checkpoint — the 'after' state whose concept to extract. | |
| rank_mode | COMBO | auto | auto: automatically determine optimal rank from SVD spectrum analysis. manual: use the specified rank value below. |
| rank | INT | 641–320 | Target rank (when rank_mode='manual') or upper bound (when rank_mode='auto'). |
| alpha_mode | COMBO | auto | auto: alpha = effective_rank × component_scale (e.g., TE=0.5×). manual: use the specified alpha value below. |
| alpha | FLOAT | 641–512 | Alpha value (only used when alpha_mode='manual'). |
| svd_mode | COMBO | auto_energy | auto_energy: automatic rank selection via energy threshold. manual: use specified rank. full: keep all singular values (no compression). |
| energy_threshold | FLOAT | 0.950.5–1 | Only used in 'auto_energy' mode. Energy retention threshold (0.50–1.0). Higher = more precision (higher effective rank). Lower = more compression. |
| noise_thresholding | BOOLEAN | true | Enable Marchenko-Pastur noise-aware SVD thresholding. Disable for cross-model deltas (e.g., Turbo vs. Base) where the noise model may incorrectly reject signal. |
| strength_multiplier | COMBO | auto | auto: automatically detect baking attenuation and compensate. 1.5/2.0/3.0: manual multiplier applied before SVD. |
| detection_mode | COMBO | auto_fast | auto_fast: sample 30 layers for quick attenuation estimate (default). auto_precise: analyze ALL layers for exact estimate. |
| save_trigger | BOOLEAN | false | — |
| filename | STRING | extracted_lora | Filename for the output LoRA (.safetensors added automatically). |
| lora_format | COMBO | native | native: standard CivitAI-compatible format (diffusion_model.* keys, merged QKV). diffusers: HuggingFace Diffusers format (transformer.* keys, QKV split). |
| device | COMBO | auto | auto: pick best available (CUDA if enough VRAM). cuda: force GPU. cpu: force CPU. |
| modelopt | MODEL | Optional: connect a model to preview the extracted LoRA applied to it. | |
| clipopt | CLIP | Optional: connect a CLIP to preview the extracted LoRA applied to it. | |
| strength_modelopt | FLOAT | 1.000–10 | Strength of the extracted LoRA when applied to the model (0.0–10.0). Ignored if model/clip not connected. |
| strength_clipopt | FLOAT | 1.000–10 | Strength of the extracted LoRA when applied to the CLIP (0.0–10.0). Ignored if model/clip not connected. |
| save_folderopt | STRING | /tmp/ComfyUI/models/checkpoints | Output folder. Leave blank to use ComfyUI's default loras folder. |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| model | MODEL | — |
| clip | CLIP | — |
| forensics | STRING | — |
| lora_path | STRING | — |