Nodes/Model Utility Toolkit/DoRA Learned Extract (Fixed Rank) (WIP)
ComfyUI Node

DoRA Learned Extract (Fixed Rank) (WIP)

DoRA Learned Extract (Fixed Rank) — SVD extraction refined by gradient descent

By silveroxides·Created 2 years ago·Updated 3 days ago· 17
DoRA Learned Extract (Fixed Rank) (WIP)
  • layer_parameters
  • output_path
◄model_a▾►
◄model_b▾►
◄linear_dim64►
◄conv_dim32►
◄optimize_iters500►
◄learning_rate1.000►
◄optimizerprodigy►
◄lr_scheduleplateau►
◄lr_patience2►
◄lr_factor0.90►
◄lr_cooldown2►
◄early_stop_loss0►
◄early_stop_stall2000►
◄early_stop_lr0►
◄svd_niter2►
◄lazy_loadtrue►
◄force_clear_cachefalse►
◄chunk_large_layersfalse►
◄clamp_quantile0.99►
◄min_diff0.000►
◄mismatch_modeskip►
◄output_filenameextracted_lora►
◄save_dtypefp16►
◄devicecuda►
◄skip_patterns►
◄glob_skip_patternsfalse►
◄include_modefalse►

This one's labeled "(WIP)" right in its own display name, and that's not marketing modesty - take it at face value. It's the first of five "Learned Extract" nodes in this pack, and it's the most straightforward: instead of just running an SVD on the weight diff between two checkpoints and calling it done, this node uses that SVD as a starting point and then runs actual gradient descent on top of it, directly minimizing the error between the extracted low-rank approximation and the real weight delta.

Here's why that's worth doing at all. Plain SVD gives you the mathematically optimal low-rank approximation of a matrix - optimal in the Frobenius-norm sense, per layer, in isolation. That's a real guarantee, but it's a narrow one: it doesn't account for how DoRA's magnitude-plus-direction decomposition actually behaves once loaded, and it treats every layer independently rather than jointly. Refining the SVD-initialized adapter with an optimizer that directly minimizes reconstruction loss can, in principle, do better against the thing you actually care about than the closed-form SVD answer alone. Whether it reliably does in practice is exactly what "(WIP)" is telling you nobody's fully verified yet.

Why "Fixed" specifically. Of the five Learned nodes in this pack, this is the one where you pick the rank yourself up front rather than letting the extraction target a percentage or an automatically-detected knee. That's the right choice when you already know what rank you want - matching an existing LoRA's size, hitting a specific file-size budget, or just having a strong prior from experience with this particular model pair.

Inputs that matter. model_a (finetune) and model_b (base) - same A-minus-B convention as every extraction node in this pack. linear_dim (default 64) and conv_dim (default 32) are the fixed ranks for linear/attention layers and convolutional layers respectively - every layer gets exactly this rank, no per-layer variation. svd_niter (default 2) controls how many SVD iterations run to initialize the low-rank factors before gradient descent takes over - this is unique to the Fixed variant among the Learned nodes.

The optimization itself has real depth: optimize_iters (default 500, 0 skips optimization and falls back to plain SVD) sets how many gradient steps run. optimizer (default "prodigy") picks the algorithm - Prodigy auto-tunes its own learning rate and is the community's default adaptive choice for this kind of problem, which is presumably why it's the default here too. learning_rate (default 1) matters a lot depending on which optimizer you pick - the tooltip is explicit that AdamW wants something closer to 0.01 while Prodigy wants closer to 1.0, so don't reuse one value across both. lr_schedule (default "plateau") with lr_patience (default 2), lr_factor (default 0.9), and lr_cooldown (default 2) control how the learning rate decays when progress stalls. Three early-stopping conditions - early_stop_loss (default 1e-6), early_stop_stall (default 2000 iterations with no improvement), and early_stop_lr (default 9.01e-9) - keep it from grinding through the full 500 iterations once it's clearly converged or clearly stuck.

The rest is shared plumbing: chunk_large_layers, clamp_quantile (default 0.99), min_diff (default 0), mismatch_mode (default "skip"), skip_patterns/glob_skip_patterns, output_filename (default "extracted_lora"), save_dtype (default fp16), device (default cuda), lazy_load (on by default), and force_clear_cache (on by default).

Output. output_path (STRING). Output node - runs as a terminal step regardless of downstream wiring.

Installing it. ComfyUI Manager, search "Model Utility Toolkit," or:

cd ComfyUI/custom_nodes
git clone https://github.com/silveroxides/ComfyUI-ModelUtils

then restart.

Troubleshooting. Given the WIP label, the most useful advice is to actually verify the output rather than trust it - load the extracted adapter and compare against the finetune directly before you rely on it for anything. If optimize_iters runs the full 500 without hitting an early-stop condition, that's a sign the learning rate or optimizer choice isn't suited to your model pair; try dropping learning_rate if you switched to AdamW and left it at Prodigy's default of 1, since that's the single most common mismatch this schema invites. And remember optimize_iters: 0 is a legitimate, faster fallback to plain SVD if the gradient-descent refinement isn't paying off for your case - that's effectively what DoRAExtractKnee and its siblings already do without the extra compute cost.

CategoryModelUtils/DoRA

Inputs (28)

NameTypeDefaultDescription
model_aCOMBOFinetuned model (A - B = LoRA)
model_bCOMBOBase model (A - B = LoRA)
linear_dimINT641–16384Rank for linear/attention layers
conv_dimINT321–16384Rank for conv layers
optimize_itersINT5000–10000Number of gradient descent iterations (0 = skip optimization)
learning_rateFLOAT1.0000.0001–100Base learning rate. AdamW prefers ~0.01, Prodigy prefers ~1.0
optimizerCOMBOprodigyOptimization algorithm
lr_scheduleCOMBOplateauLearning rate scheduling strategy
lr_patienceINT20–1000Steps to wait before decaying LR (Plateau)
lr_factorFLOAT0.900.01–1Factor to decay LR by (Plateau)
lr_cooldownINT20–1000Steps to wait after decay before checking again (Plateau)
early_stop_lossFLOAT00–1Stop early if MSE loss drops below this value
early_stop_stallINT20000–10000Stop early if loss doesn't improve for this many iterations
early_stop_lrFLOAT00–1Stop early if learning rate drops below this value
svd_niterINT20–10SVD iterations for initialization
lazy_loadBOOLEANtrueLow memory mode: load tensors from disk on demand
force_clear_cacheBOOLEANfalseClear CUDA cache after each layer; slower but useful under severe VRAM pressure.
chunk_large_layersBOOLEANfalseSplit large fused layers (QKV, MLP) into chunks
clamp_quantileFLOAT0.990.5–1Clamp outlier singular values
min_diffFLOAT0.0000–1Skip layers with max difference below this
mismatch_modeCOMBOskipHandle missing or incompatible model tensors by skipping them, substituting zeros where supported, or aborting.
output_filenameSTRINGextracted_loraOutput filename without extension, written under ComfyUI's LoRA directory.
save_dtypeCOMBOfp16Data type used to save the extracted DoRA tensors.
deviceCOMBOcudaDevice used for extraction arithmetic; CUDA out-of-memory processing falls back per affected layer where supported.
skip_patternsSTRINGPatterns for layers to skip (regex or glob depending on glob_skip_patterns)
glob_skip_patternsBOOLEANfalseWhen True, skip_patterns use glob syntax (* = any sequence, ? = any char, dots are literal). When False (default), patterns are Python regex matched as substrings.
include_modeBOOLEANfalseUse Skip Patterns as a whitelist instead. Only matching layers are extracted; an empty whitelist extracts nothing.
layer_parametersoptMODELUTILS_LAYER_PARAMETERSOptional Layer Parameter Configuration. a=linear_dim; b=conv_dim; c=clamp_quantile; d=min_diff Full names are also accepted. Unassigned values use this node's settings; existing filters still apply.

Outputs (1)

NameTypeDescription
output_path*—