Model Diff to LoRA
Bake your whole LoRA stack into one file
- model_before
- model_after
- lora_path
- info
You've finally found the look - style at 0.7, details at 0.5, identity at 1.0 - and now you're stuck babysitting three LoRA loaders and three strength sliders on every generation. Model Diff to LoRA fixes that the permanent way: it bakes your entire stack into a single .safetensors LoRA you can ship, share, or swap onto a different base. No retraining, no merge scripts, no checkpoint-sized files. Just one node that freezes the recipe.
What it's for
You hand it two MODEL objects:
model_before- your untouched base modelmodel_after- the same base after whatever you stacked on it (a LoRA chain, block-level scaling, an IP-Adapter patch, an external merge delta, anything ComfyUI applies as a model patch)
The node computes the difference and factors it into a low-rank LoRA that, applied at strength 1.0 to the base, reproduces the "after" behaviour. That's the classic LoRA-extraction trick from the checkpoint world - capture the delta between a base and a fine-tune as a tiny LoRA instead of shipping a whole model - but done live in ComfyUI. The payoff over a checkpoint-diff tool: it works on in-memory MODEL objects, so it can freeze an exact combination of LoRAs at the exact strengths you settled on. Runtime stackers like rgthree's Power Lora Loader stay flexible; this is the snapshot.
How it works
ComfyUI doesn't bake LoRA weights into the model - it stores them as patches on model_patcher.patches. So the node first applies those patches via comfy.lora.calculate_weight() on both inputs to get their effective weights. Then it walks every trainable weight (attention and MLP layers; norms, biases, and embeddings are skipped), subtracts, and throws out any layer whose diff norm is below a hardcoded threshold. Noise doesn't earn a place in your file.
Every surviving layer gets an SVD. Singular values come back in descending order, so a rank-r LoRA is just a truncation of the same decomposition - which is why ticking several ranks costs almost nothing: one SVD per layer, sliced. The output is standard kohya-style LoRA key naming (lora_unet_...) with ss_network_dim/ss_network_alpha metadata and an auto-detected architecture (Flux incl. Klein 4B/9B, Krea 2, Qwen-Image, Z-Image, WAN, SDXL, SD 1.5), so ComfyUI's own LoRA loader reads it back with zero conversion.
SVD runs on CUDA when available, CPU otherwise. On big Flux/SDXL models that means seconds to tens of seconds on a GPU, minutes on CPU - it's not hung, it's decomposing.
The inputs that matter
Only a few you'll actually touch:
enabled- on by default; flips itself off after a successful save so it won't silently re-extract on every queue.model_before/model_after- the twoMODELobjects; the whole point of the node.- The rank tick-boxes (
rank_8throughrank_256) -32and64are on by default. Tick a ladder like 16/32/64/128 and you get one file per rank, all from a single SVD pass, so you can A/B them and keep the smallest that still holds up. For most edits rank 16–32 captures the signal; higher ranks just cost file size. output_path/output_name- leave the path empty forComfyUI/output/extracted_loras; both remember what you last typed. A timestamp and rank get appended to the filename.
Two outputs: lora_path (the saved file's absolute path - conveniently the largest rank, so you can wire it straight into a LoRA loader to test what you just baked) and info (a multi-line summary of layers extracted, skipped, compression ratio, and SVD device).
Installing it
Via ComfyUI Manager, search "Model Diff to LoRA". Or the manual route:
cd ComfyUI/custom_nodes
git clone https://github.com/shootthesound/comfyui-model-diff-to-lora
Restart ComfyUI, then find Model Diff to LoRA under loaders/lora. No extra pip installs and no model downloads - it only uses torch, safetensors, and comfy.lora, all of which ComfyUI already ships.
Common issues
- "Extraction disabled" - normal.
enabledauto-disables after a save; flip it back on when you want to run again. - "No layers extracted - models may be identical" - you wired both inputs from the same point in the graph, or the edit is below the 0.001 noise threshold.
- "No ranks selected" - untick all six boxes and it politely refuses to guess.
- The file isn't in your LoRA dropdown - default output is
ComfyUI/output/extracted_loras, notmodels/loras. Setoutput_pathto your loras folder, or drag the file over. - Older guides (and older READMEs) mention a single
output_rankslider - current versions (1.2.0) use the rank tick-boxes. If you're staring at a slider, you're on an old build; update.
It's a small, quiet utility with a single job - but if you've ever shipped "use these three LoRAs at these strengths" as instructions to someone, you'll feel the relief the moment you hand them one file instead.
Inputs (11)
| Name | Type | Default | Description |
|---|---|---|---|
| enabled | BOOLEAN | true | Enable extraction. Auto-disables after successful save. |
| model_before | MODEL | Original base model (before LoRA chain) | |
| model_after | MODEL | Modified model (after LoRA chain) | |
| rank_8 | BOOLEAN | false | Save a rank-8 LoRA. |
| rank_16 | BOOLEAN | false | Save a rank-16 LoRA. |
| rank_32 | BOOLEAN | true | Save a rank-32 LoRA. |
| rank_64 | BOOLEAN | true | Save a rank-64 LoRA. |
| rank_128 | BOOLEAN | false | Save a rank-128 LoRA. |
| rank_256 | BOOLEAN | false | Save a rank-256 LoRA. |
| output_path | STRING | Save directory. Leave empty for ComfyUI/output/extracted_loras. Remembers last used path. | |
| output_name | STRING | extracted_lora | Filename prefix (timestamp will be appended). Remembers last used name. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| lora_path | STRING | Full path to the saved LoRA file (for chaining to loaders) |
| info | STRING | Extraction summary with layer count, compression ratio, etc. |