⭐ Star LoRA Extractor
Pull the LoRA back out of a merged checkpoint
- info
What it's actually for
You know the situation. Someone uploaded a merge - a base model with three LoRAs baked in - and it does exactly the thing you wanted. But it's 6 GB, it's got someone else's aesthetic welded on, and you can't turn the effect down or point it at a different base. What you want is the difference: a small file that says "whatever was added here," loadable at 0.6 onto a model of your choosing.
That's what ⭐ Star LoRA Extractor does. It's the same technique kohya's extract_lora_from_models.py uses, wrapped as a node that runs inside your graph instead of a separate Python environment. Extraction only ever recovers an approximation, and it lives or dies on picking the correct base model - get that wrong and you're diffing two unrelated things.
How it works
For every weight both files have in common:
diff = merged_weight − base_weight
diff ≈ lora_up @ lora_down (per-matrix SVD, truncated to your chosen rank)
Diff in fp32, one matrix at a time, SVD, truncate to dim (Linear layers) or conv_dim (Conv2d), store the pair plus an alpha equal to the rank. Keys get rewritten into ComfyUI's LoRA naming (lora_unet_…, lora_te1_…) and the whole thing saves as one .safetensors into your top-level models/loras.
The memory story is why this runs on a normal machine: both files are opened as memory-mapped safetensors and read key by key, so neither 6 GB model is ever resident in full. The catch is printed at runtime - .ckpt/.pt files have no lazy path and load whole, which puts you right back over the RAM limit. Convert to safetensors first.
Quantized files work too: it dequantizes int8 / int8-convrot (Hadamard un-rotation included) and fp8_e4m3/e5m2 with scalar or per-row scales before diffing. Packed 4- and 6-bit formats (nvfp4, convrot_w4a4, asym_w4a8_int8, w6a8_int8) can't be diffed at all; the node raises instead of writing garbage.
The inputs that matter
- base_model and merge_model - dropdowns filled from
models/diffusion_models. Same architecture required (SD 1.5 with SD 1.5, Flux with Flux); mismatched shapes are skipped rather than crashing. Picking the same file twice is a hard error. - lora_name - the output filename in
models/loras..safetensorsis appended for you, and subfolders work:style/my_extractgives youmodels/loras/style/my_extract.safetensors. Refuses to overwrite unless you set overwrite to true. - dim / conv_dim - rank. Defaults of 16 and 8 are the pack's "balanced" pair, about 60–150 MB on SDXL. Go
8/4for something small, or32–64 / 16–32when the merge bundled many strong LoRAs, since those diffs don't compress as cleanly. - precision (
fp16/bf16/fp32) and device (cpu/cuda).cpuis the default and the safe one;cudais much faster if the whole thing fits in VRAM. - Optional: extract_convs (leave true), include_text_encoder (leave true unless the merge only touched the UNet), overwrite.
Output is a single info string with the save path, file size, and a layer breakdown - extracted vs. unchanged vs. skipped. Read it. If almost everything comes back "unchanged," your two files are basically the same model and the LoRA is empty.
Install
Manager → search Starnodes → Install → restart, or:
cd ComfyUI/custom_nodes
git clone https://github.com/Starnodes2024/ComfyUI_StarNodes
cd ComfyUI_StarNodes
pip install -r requirements.txt
Ignore the heavy entries in that requirements file - psd-tools, opencv-python, imageio-ffmpeg, ollama and friends belong to other nodes in the pack. This one is pure torch + safetensors, and the pack is built so a missing package knocks out only its own node, not the whole install.
You'll find it by double-clicking the canvas and searching star.
Where people get burned
The dropdown is empty. base_model and merge_model list models/diffusion_models, not models/checkpoints. A merged full checkpoint sitting in checkpoints/ simply won't show up. Move or symlink the file first.
"No differences found." Either the files are genuinely near-identical (diffs under 1e-7 are skipped as unchanged) or you're diffing against the wrong base. If you don't know which base the merger used, that's the whole problem: find the closest match by similarity and accept the noise.
It's slow, and it looks frozen. This runs an SVD per matrix across thousands of matrices. The progress bar crawls, especially on cpu. device: cuda on an SD 1.5/SDXL-sized model is a big difference; on a 20B model it may not fit and you're back to cpu. Let it run.
Quality is a dial, not a switch. Two independently quantized models carry quantization noise in the diff, and SVD truncation removes most of it - but if the merge was re-quantized after baking, that smoothing is in your file now. Diffing a bf16/fp16 pair gives the cleanest result.
VAEs are always skipped, on purpose - LoRAs don't target the VAE. If the merge's look came largely from a VAE swap, none of that is in your file, and it'll read as a bad extraction when really it was never there.
Last thing: it's a terminal node, so it only runs when you queue a prompt with it in the graph. Refresh the node list if the new LoRA doesn't show up in Load LoRA's dropdown straight away.
Inputs (10)
| Name | Type | Default | Description |
|---|---|---|---|
| base_model | COMBO | The clean base diffusion model (from models/diffusion_models). | |
| merge_model | COMBO | The diffusion model with LoRAs already baked in (from models/diffusion_models). | |
| lora_name | STRING | star_extracted_lora | File name for the extracted LoRA (saved into models/loras). May contain subfolders. |
| dim | INT | 161–1024 | Rank/size of the LoRA for Linear layers. Higher = more detail, bigger file. |
| conv_dim | INT | 81–512 | Rank for Conv2d layers (UNet resnet blocks). Lower than dim is usually fine. |
| precision | COMBO | fp16 | Save precision/quality of the extracted LoRA weights. |
| device | COMBO | cpu | Where the SVD math runs. cpu = lowest RAM/VRAM usage, cuda = much faster if it fits. |
| extract_convsopt | BOOLEAN | true | Also extract Conv2d layer differences (recommended for full fidelity). |
| include_text_encoderopt | BOOLEAN | true | Also extract text encoder (CLIP) differences. |
| overwriteopt | BOOLEAN | false | Allow overwriting an existing LoRA file with the same name. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| info | STRING | — |