ComfyUI Node

Model Diff to LoRA

Bake your whole LoRA stack into one file

By shootthesound·Created 4 months ago·Updated about a month ago· 6
Model Diff to LoRA
  • model_before
  • model_after
  • lora_path
  • info
enabledtrue
rank_8false
rank_16false
rank_32true
rank_64true
rank_128false
rank_256false
output_path
output_nameextracted_lora

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 model
  • model_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 two MODEL objects; the whole point of the node.
  • The rank tick-boxes (rank_8 through rank_256) - 32 and 64 are 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 for ComfyUI/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. enabled auto-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, not models/loras. Set output_path to your loras folder, or drag the file over.
  • Older guides (and older READMEs) mention a single output_rank slider - 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.

Categoryloaders/lora

Inputs (11)

NameTypeDefaultDescription
enabledBOOLEANtrueEnable extraction. Auto-disables after successful save.
model_beforeMODELOriginal base model (before LoRA chain)
model_afterMODELModified model (after LoRA chain)
rank_8BOOLEANfalseSave a rank-8 LoRA.
rank_16BOOLEANfalseSave a rank-16 LoRA.
rank_32BOOLEANtrueSave a rank-32 LoRA.
rank_64BOOLEANtrueSave a rank-64 LoRA.
rank_128BOOLEANfalseSave a rank-128 LoRA.
rank_256BOOLEANfalseSave a rank-256 LoRA.
output_pathSTRINGSave directory. Leave empty for ComfyUI/output/extracted_loras. Remembers last used path.
output_nameSTRINGextracted_loraFilename prefix (timestamp will be appended). Remembers last used name.

Outputs (2)

NameTypeDescription
lora_pathSTRINGFull path to the saved LoRA file (for chaining to loaders)
infoSTRINGExtraction summary with layer count, compression ratio, etc.