Nodes/comfyui-timesaver/TS LoRA Unmerged
ComfyUI Node

TS LoRA Unmerged

Your turbo LoRA is getting rounded off before it ever runs

By AlexYez·Created 2 years ago·Updated about 23 hours ago· 17
TS LoRA Unmerged
  • model
  • model
◄lora_name▾►
◄strength1.00►

The problem nobody notices until they compare

Every LoRA loader in ComfyUI merges. W + strength · B·A is computed once and written into the weight matrix. That's the right call in float32 and completely free at run time - no extra math per step, no extra VRAM. The trouble is that your model isn't in float32. On bf16 weights, round-to-nearest throws away a large part of a small update. On int8 weights, requantizing after the merge adds noise several times the size of the update you were trying to apply.

For a style LoRA at 0.7 you'd never notice. For a six-step turbo LoRA it's the whole ballgame, because each of those six steps has to land. Viggle's own LPIPS numbers against the diffusers reference put it bluntly: 0.093 merged vs 0.052 unmerged on bf16, 0.086 vs 0.038 on int8. You didn't misconfigure anything - the merge ate it.

TS LoRA Unmerged applies the adapter as a side branch instead: y = W·x + B·A·x, added to the output of every targeted layer on the fly, exactly what diffusers/PEFT do before anyone calls fuse_lora().

How it actually works

The model you feed in is not touched. The node clones the patcher and hangs the LoRA on the clone, then wraps the diffusion model and the outer sampling call so the branch runs inside the layer's own compute dtype - on an fp8 model that means bf16, not fp8, which would zero out a third of a small LoRA.

Mapping LoRA keys onto the model's layers is done through ComfyUI's own comfy.lora key map rather than a table someone wrote down, which matters for Qwen Image 2.1: it fuses its MLP, so gate_layer and proj are one gate_up layer in ComfyUI while the LoRA addresses the two halves separately. Under int8 the down projection also runs in a fused kernel that skips its module entirely, so that branch gets added one level up, at the output of the whole MLP, from the same numbers.

The LoRA's tensors sit in VRAM only while sampling runs - about 0.7 GB for the r128 Qwen turbo LoRA - and are dropped afterwards. They're placed before the model loads, after asking ComfyUI to free that much, so its memory manager plans around them instead of discovering them the hard way.

The cost is real and the author quotes it: roughly 10–25% per step. Measured on Qwen Image 2.1 int8 with the r128 turbo LoRA at 1024×1024 on an RTX 3080 Ti Laptop: 2.8 s per step without any LoRA, 3.45 s with the branch. The payoff is that the result is bitwise identical to Viggle's own node, where a stock merge of the same LoRA lands about half the LoRA's effect away from it.

The inputs that matter

  • model - the diffusion MODEL. Not modified; a clone gets the LoRA.
  • lora_name - a COMBO over models/loras. Plain linear LoRAs with diffusers/PEFT naming (lora_A/lora_B, alpha from the file's metadata) or kohya naming (lora_down/lora_up plus alpha).
  • strength - FLOAT, default 1.0, range −100 to 100, step 0.01. The tooltip is the useful part: it multiplies on top of the file's own alpha/rank, and turbo LoRAs want 1.0. Exactly 0 returns the clone untouched, so muting the branch costs nothing.

One output: model, the same model with the LoRA added at run time. Wire it where a stock LoRA loader's output would go - into your sampler or guider chain.

When to reach for it, and when not to

Small, few-step, load-bearing LoRAs on bf16 or int8 weights. Viggle's turbo LoRA for Qwen Image 2.1 is the case it was written for. For an ordinary style LoRA, the stock merge is free and good enough - don't pay a fifth of your step time to fix rounding you can't see.

Worth knowing: the pack's own TS LoRA Loader has a per-row merge / branch switch that uses this same code path, so if you stack several LoRAs you can chain them and pick per row rather than choosing all-or-nothing.

A few things are refused on purpose, and the log tells you which:

  • DoRA is refused with a message - a side branch can't renormalise it.
  • LoKr, LoHa and conv LoRAs are not applied.
  • Full-weight .diff / .diff_b entries can't be expressed as B·A, so they're skipped, and the log names how many and where the first one is. A stock loader still applies those.

Installing it

It's part of the Timesaver pack (82 nodes, TS prefix, ComfyUI's V3 node API on a pinned comfy_api.v0_0_2 namespace). No models to download and nothing extra to install for this node.

ComfyUI Manager → Custom Nodes Manager → search Timesaver → install → restart. Or by hand:

cd ComfyUI/custom_nodes
git clone https://github.com/AlexYez/comfyui-timesaver
cd comfyui-timesaver
python -m pip install -r requirements.txt

On Windows portable, run pip from the bundled interpreter (python_embeded\python.exe -m pip install -r requirements.txt) or the dependencies land in the wrong Python.

Where it goes wrong

"None of the N tensors in '…' match a linear layer of this model." Either the LoRA is for a different architecture, or it isn't a plain LoRA (LoKr/LoHa/conv flavours won't match). Nothing to debug - it's the wrong file.

A warning that the LoRA was NOT applied. If a model never calls the diffusion-model wrapper, the branch has nowhere to hook in. The node says so and tells you to use a stock loader rather than handing you an unchanged model silently.

OOM during sampling, not at load. The LoRA's weights are extra VRAM. If you were already against the ceiling, this is what tips you over - try the same graph at a smaller resolution before blaming the node.

It's slower. That's the deal. Run it once with a stock loader and once with the branch at the same seed; if your step count is high enough that you can't see the difference, take the free path.

CategoryTS/Utils

Inputs (3)

NameTypeDefaultDescription
modelMODELThe diffusion model. It is not modified; a clone gets the LoRA.
lora_nameCOMBOA LoRA of linear layers (diffusers/PEFT or kohya naming).
strengthFLOAT1.00-100–100Multiplier on top of the file's own alpha / rank. Turbo LoRAs want 1.0.

Outputs (1)

NameTypeDescription
modelMODELThe same model with the LoRA added at run time.