DoRA Extract (Ratio)
DoRA Extract (Ratio) — extract a DoRA by relative singular-value magnitude
- output_path
Three ways to pick a rank when extracting a DoRA from two checkpoints, and this pack ships all three as separate nodes: knee detection finds the natural elbow in each layer's singular-value curve automatically, quantile keeps enough singular values to hit a target cumulative percentage, and this one - ratio - takes the simplest approach of the three. It keeps any singular value that's still at least a certain fraction of the largest one in that layer, and throws away anything smaller than the threshold. No accumulation, no curve-shape analysis - just a straight relative-magnitude cutoff, layer by layer.
The distinction from quantile is worth being precise about, because they sound similar and aren't quite the same thing. Quantile asks "how much of the total energy have I captured so far" - a global, cumulative question. Ratio asks "is this singular value still big enough to matter compared to the biggest one in this layer" - a local, per-value question. In practice the two often land on similar ranks for a given layer, but not identically: a layer with one dominant singular value and a long, gently-sloping tail of smaller ones can hit a high quantile target quickly (most of the energy is in that one value) while still failing a strict ratio threshold on many of the tail values, or the reverse, depending on how the two thresholds are set relative to each other.
How it works. model_a minus model_b gives the per-layer weight delta; SVD decomposes it into singular values; the node keeps every singular value greater than max(S) / ratio, where S is that layer's full set of singular values. A ratio of 2 keeps anything at least half the size of the layer's largest singular value - a fairly tight cut. Push ratio up and you're dividing by a bigger number, which lowers the bar and keeps more (weaker) singular values, meaning higher rank and a more faithful but larger extraction.
Inputs that matter. model_a (finetune) and model_b (base) - same convention as the pack's other extraction nodes. linear_ratio and conv_ratio (both default 2, range 1–100) are the actual dial, split between linear/attention layers and convolutional ones. linear_max_rank and conv_max_rank (both default 128) are hard ceilings regardless of what the ratio threshold would otherwise allow through.
Shared plumbing with the rest of this node family: chunk_large_layers (off by default) for splitting fused QKV/MLP blocks before extraction, clamp_quantile (default 0.99) to bound outlier singular values, min_diff (default 0) to skip barely-changed layers, mismatch_mode (default "skip") for layers that don't align between the two checkpoints, skip_patterns (regex, or glob with glob_skip_patterns on) to manually exclude layers, save_dtype (default fp16), device (default cuda), output_filename (default "extracted_lora"), lazy_load (on by default), and force_clear_cache (on by default).
Output. output_path (STRING), and this is an output node - it runs as a terminal step in the graph whether or not anything downstream reads that path.
Installing it. ComfyUI Manager, search "Model Utility Toolkit," or:
cd ComfyUI/custom_nodes
git clone https://github.com/silveroxides/ComfyUI-ModelUtils
then restart.
Troubleshooting. If you're getting a much bigger or smaller extraction than expected, check ratio before anything else - because it divides rather than accumulates, small changes near the default of 2 can shift a layer's kept rank more sharply than the equivalent nudge would on the quantile node. If a layer's singular values are all fairly close in magnitude (no single dominant one), ratio-based selection tends to keep either almost all of them or almost none, depending on which side of the threshold that flat spread happens to fall - quantile handles that shape more gracefully, so if a specific layer's output looks off, that's worth trying as an alternative rather than just cranking the ratio further. As always, two full checkpoints in memory at once is the real resource cost here; leave lazy_load and force_clear_cache on unless your hardware can comfortably hold both.
Inputs (17)
| Name | Type | Default | Description |
|---|---|---|---|
| model_a | COMBO | Finetuned model (A - B = LoRA) | |
| model_b | COMBO | Base model (A - B = LoRA) | |
| linear_ratio | FLOAT | 2.01–100 | Ratio threshold for linear layers (higher = more SVs kept) |
| conv_ratio | FLOAT | 2.01–100 | Ratio threshold for conv layers (higher = more SVs kept) |
| linear_max_rank | INT | 1281–16384 | Maximum rank for linear layers |
| conv_max_rank | INT | 1281–16384 | Maximum rank for conv layers |
| lazy_load | BOOLEAN | true | Low memory mode: load tensors from disk on demand |
| force_clear_cache | BOOLEAN | true | Clear CUDA cache after each layer |
| chunk_large_layers | BOOLEAN | false | Split large fused layers (QKV, MLP) into chunks |
| clamp_quantile | FLOAT | 0.990.5–1 | Clamp outlier singular values |
| min_diff | FLOAT | 0.0000–1 | Skip layers with max difference below this |
| mismatch_mode | COMBO | skip | 3 options: skip, zeros, error |
| output_filename | STRING | extracted_lora | — |
| save_dtype | COMBO | fp16 | 3 options: fp16, bf16, fp32 |
| device | COMBO | cuda | 2 options: cuda, cpu |
| skip_patterns | STRING | Patterns for layers to skip (regex or glob depending on glob_skip_patterns) | |
| glob_skip_patterns | BOOLEAN | false | When True, skip_patterns use glob syntax (* = any sequence, ? = any char, dots are literal). When False (default), patterns are Python regex matched as substrings. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| output_path | STRING | — |