SD Slicer — Layer Merge
Merge two checkpoints block by block without leaving your workflow
- model_1
- model_2
- model
If you've spent any time on CivitAI you've already met merges - a huge share of the checkpoints there are just weighted averages of two parents. The catch is you get whatever blend the uploader decided. SD Slicer - Layer Merge moves that decision into your workflow: instead of downloading someone else's merge, you load two (or three, or seven) checkpoints and blend them yourself, block by block, at generation time.
This is the main node of the ComfyUI-SD-Slicer pack, and the honest pitch is that it makes "tweak the merge" a per-image operation. Take the structure from one model and the style from another, dial the blend to 30% instead of 50%, decide the whole thing on the spot. No training, no extra files, no GPU grind - the mechanism is just arithmetic on weights.
How it works
Every model is a pile of named tensors, and SD-Slicer slices them into blocks by a simple rule: a block id is the key prefix up to and including the first numeric component. So input_blocks.1.0.in_layers.0.weight becomes input_blocks.1, and joint_blocks.5.x_block.attn.qkv.weight (SD3 MMDiT) becomes joint_blocks.5. Same rule for SD1.5, SDXL, SD3, Flux, even video models - one node slices any architecture the same way.
Each block gets its own formula, defaulting to a weighted sum:
W_out = k · ( w₁·W₁ + w₂·W₂ + … + w_N·W_N )
The clever bit is the fast path. With an empty formula (plain weighted sum) the merge stays lazy - it's expressed as ComfyUI ModelPatcher.add_patches, exactly like the built-in ModelMergeSimple. Inputs are never mutated, weights never materialize, and the output is a real MODEL you can feed straight into a sampler. Add a custom formula and it takes the other path: weights are computed in float32, applied as replacement patches on a clone, and peak memory goes up. Fine on most GPUs for SD1.5; worth a thought on 8 GB VRAM with SDXL/SD3.
The inputs and the UI
model_1,model_2, … - dynamic MODEL inputs. Connect a model to the last free port and the next one appears. Weights are bound to the port number, so unplugging a middle input doesn't shift the rest.config- the hidden JSON widget the block editor writes to; you usually don't touch it by hand.- Output:
model- a proper MODEL for downstream sampling.
The workflow for actually using it: wire up your loaders, run the workflow once (the node is an OUTPUT_NODE, so loaders + this node alone is enough), then hit 🔄 Refresh block list. The node reads the model keys and shows every block with its sN weight fields and a k multiplier. The classic move - take structure from A, style/detail from B: set input_blocks.* to s1=1, s2=0 and output_blocks.* to s1=0, s2=1. There's a filter box, and per-block values fall back to a default row when you leave them alone.
There are also formula presets (interpolate, weighted subtraction, add-difference) plus a custom formula field. Formulas use positional variables - a, b, c or m1, m2… for sources, s1, s2… for the same per-block weights, k and n, and functions like lerp, clamp, abs, min, max, sign, sqrt. Worth knowing: it's evaluated by a whitelisted AST parser, not Python's eval, so a formula riding inside a shared workflow can't run arbitrary code. That's a nice safety touch in an ecosystem with a real malware history.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/kuschanow/ComfyUI-SD-Slicer
# restart ComfyUI
Or use ComfyUI Manager and search "SD Slicer". No extra Python dependencies - the pack runs on plain torch plus ComfyUI's own model-writing code, and there are no model files to download.
Where people get burned
The one real trap: merging only works between models of the same architecture. SD-Slicer checks key sets and tensor shapes before it blends and prints a clear error - missing keys, extra keys, or an example shape mismatch - but it can't fix an SD1.5 UNet meeting an SDXL one. Different depth, different tensor shapes, no dice. So: same base, blend freely; SD1.5 into SDXL, don't bother. And the block list won't exist until the node has run once - the Refresh button needs a queue to have happened.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| model_1 | MODEL | — | |
| model_2 | MODEL | — | |
| config | STRING | {} | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| model | MODEL | — |