CivArchive
    ← All articles
    Published October 12, 2026by mad_macs

    You can't merge two checkpoints and get the best of both — 12 measurements

    73 views0 reactions0 comments on CivitAI0 collected
    krea 2techniquecomfyuilorablock mergemodel mergetutorialguidecheckpointmerging

    How a three-way merge is actually built — same prompt, same seed, same recipe


    The idea is good. The arithmetic isn't.

    The pitch for merging is irresistible: you like the faces on one checkpoint, the skin on a second, the way a third holds a body type — so blend them, take the best of each, and land somewhere better than all three.

    I believed that for long enough to build a production base out of it, retrain a roster of character LoRAs on the result, and render the whole roster through it. This is what the measurements actually say. Some of it is good news. A lot of it is the opposite of the intuition, and the parts that bite are invisible in a merge tool's UI.

    Model names are generic on purpose. Below, A is the anchor (the largest weight share), B is the mid-weight leg, and C is the smallest leg. Which checkpoints they actually were doesn't matter to any of the findings — the numbers are reproducible on any three fine-tunes in the same family, which is rather the point.

    Scope note: everything below was measured on Krea 2 — a 28-block MMDiT, 430 tensors. Where a finding is architectural I say so. Don't assume the block numbers transfer to another family; do assume the classes of failure do, because they're about weight math and licensing, not about one model.


    1. A merge is not an interpolation of its parents

    This is the one that changes how you work.

    I blended two fine-tunes, 60/40, and measured the output's hair-chroma tone proxy:

    • Model A alone — 0.154
    • Model C alone — 0.166
    • A + C at 60/40 — 0.122

    The merge came out worse than both parents. Not the average of them — worse than either.

    If you take one thing from this article, take this: you cannot predict a merged checkpoint from its parents' measurements. Whatever property you care about has to be measured on the merged artifact itself, every time, before you commit it to anything.

    2. "Variance compression" was never the explanation

    The obvious rescue theory is that averaging two correlated fine-tunes flattens their variance, and that flatness is the tone loss. It sounds right. The maths doesn't support it: measured across all 430 tensors, std_parent / std_merged came out at mean 1.0019 — essentially 1.0.

    A weighted average of correlated fine-tunes preserves variance. So if your merge looks dead, it isn't because you averaged the weights. It's because you averaged the wrong bands, which is the next problem.

    3. Identity lives in blocks 7–13 — and block 9 alone carries more face than any other block

    Swap one block group at a time between two checkpoints, both directions, fixed seed, and ask which blocks move who the person is:

    • Blocks 7–13 — face shape, eyes, lips. This is the identity region.
    • Block 9 — on its own, moves the face more than any other single block does.
    • Blocks 4–13 — a slightly wider band that scored best on a 448-image test.

    Put your identity swaps in 4–13 and nowhere else. Everything else is a different decision.

    4. Blocks 21–27 are tone, hair colour and grade — they barely touch the face

    Good news: the "look" half of your merge is separable from the "person" half. Skin tone, hair colour and overall colour grade live in 21–27, and moving them leaves face shape essentially untouched. So "I want that checkpoint's grade but I'm keeping my face" is a genuinely addressable request — via 21–27, not by blending the whole file.

    5. txtfusion controls composition, not the face — and almost nobody pins it

    Krea 2 has a txtfusion group (49 tensors) that decides how the prompt is read and how the shot is framed. It is not identity.

    This is the silent one. If your band spec only covers the block ranges and lets txtfusion fall through to the global default, you are averaging your framing. First attempt in the research I'm drawing on swapped identity into the right blocks plus txtfusion, and every render collapsed to stock-photo framing — wider crop, phone in frame, the candid look gone. The faces were fine. The composition was a blend of two different photographers.

    Pin txtfusion to whichever parent has the composition you want, as a separate decision from the face decision.

    6. The standard band split cuts the identity region in half

    The spec I started with — and the one most block-merge tools hand you as a default — is three bands: 0-9 / 10-20 / 21-27.

    That boundary at 10 lands inside 4–13. So your "identity" band is mixing identity with texture, and any weight you assign to a third model in that band only reaches half the face region.

    If you're using 0-9/10-20/21-27, try 0-3 / 4-13 / 14-20 / 21-27 instead.

    7. Never merge quantized weights

    FP8 and INT8 merges produce silent garbage. Not an error — a file that loads, renders, and is quietly wrong. Every input must be true BF16 before you start.

    Common trap: a "scaled FP8" checkpoint is not just an FP8 tensor. It's float8_e4m3fn weight + a weight_scale scalar + a comfy_quant U8 JSON blob. Dequant is:

    bf16_weight = weight.float() * weight_scale

    then drop the weight_scale and comfy_quant keys entirely. If you merge the raw FP8 tensors, you get the silent-garbage case.

    8. Normalize your key prefixes, or you'll abort a mergeable trio

    Some checkpoints ship model.diffusion_model.blocks.N..., others ship bare blocks.N....

    A naive key-set comparison between the two reports 0 common keys and refuses a merge that is perfectly legal. Worse, a naive merge across the two would produce nonsense. Detect and strip the prefix per input, then verify with a canonical-key count before writing anything — on Krea 2 that's 430.

    9. Screen the merged artifact, never the parents (see #1)

    Once more, because it's the cheapest mistake to make: parent stats tell you nothing about the child. Render the merge, measure the merge.

    10. Screen on your recipe — a checkpoint's own favourite sampler is not transferable

    Sampler choice is base-specific, and this blindsides people who test a candidate on the settings it was tuned for.

    Same base, two samplers:

    • base: Base 1 · its own best sampler: dpmpp_2m_sde / simple — reference · the other sampler: euler / beta — −21.5% tone
    • base: Base 2 · its own best sampler: euler / beta — its best result · the other sampler: dpmpp_2m_sde — −7.9%, i.e. behind

    Base 2 wins its own recipe by a wide margin and loses on Base 1's recipe. A merged checkpoint renders on one recipe, and there is no recipe on which both parents are at their best — so a candidate that only wins under settings your pipeline doesn't use has not been shown to be an improvement.

    Screen candidates on the production recipe you actually render with. Sweep levers only for a candidate that already looks close.

    11. Screen the no-LoRA control too

    A character face LoRA adds 9–14% hair chroma on every base it's applied to — including bases it was never trained on (measured +12.7%, +9.1%, +14.2% on three different bases).

    So a screen run only with the LoRA makes a candidate look better than it is. It also conflates two different defects: a natively flat base, and a base that's merely incompatible with your LoRAs. Those need opposite fixes — reject versus retrain — so render both conditions and report two verdicts:

    • Base-only (no LoRA on either side) → is the candidate natively flat?
    • Production (LoRA both sides) → does it work in the real render path?

    12. Baking a LoRA into a base is not a merge — and it imports the LoRA's scene prior

    Baking looks like the clean solution: you want a LoRA's look without shipping the LoRA, so you fold it into the weights:

    out = base + strength * (up @ down) * (alpha / rank)

    The math is fine. The assumption is not. Whatever the source LoRA conditions on comes along — not just its look.

    Measured, and this is the arm that settles it: I baked a character LoRA that had been trained on a clothed-lifestyle dataset (athleisure, bedroom interiors) into a permissive base. Then I rendered an explicitly nude prompt:

    • arm: 1 · base: original base · LoRAs: face + body · result: nude ✅
    • arm: 2 · base: original base · LoRAs: none · result: nude ✅
    • arm: 3 · base: baked base · LoRAs: face + body · result: clothed ❌
    • arm: 4 · base: baked base · LoRAs: none · result: clothed ❌

    Arm 4 is the decisive one: the baked base renders clothed with no LoRAs loaded at all. The clothing prior entered through the bake. Dropping the bake strength to 1.0 / 0.7 / 0.5 didn't rescue it, and pushing the LoRA strengths up didn't either.

    Rules: a bake is only safe for a pure style/look LoRA. Baking anything that carries a scene, clothing or composition prior imports that prior into the base and can break content compliance on every render. And A/B a baked base for content compliance, not just for tone — the original check on this bake measured hair hue and called it a win (24.1° → 27.1°, against a 28.3° target) while the base had quietly stopped producing nudes.


    The gate that isn't about math: licensing decides, not quality

    You can do all of the above perfectly and still not be allowed to publish the result.

    On Civitai, allowDerivatives and SellMerge are the uploader's grants, and they gate your inputs — not your output. The strictest input wins. One input with allowDerivatives: False kills redistribution of the whole blend, at any setting on the others.

    Two things that catch people:

    • A populated allowCommercialUse list is not "commercial OK." Read the individual entries.
    • A mostly-permissive blend is still unpublishable. My production base was ~70% permissive

    A model can permit Image (sell your renders) while omitting Sell/SellMerge (sell the checkpoint). Those are different permissions and only one of them is about your merge. by weight, and it is permanently internal-only, because the remaining 30% (two inputs) doesn't grant derivatives. Percentage of weight is irrelevant to the licence.

    And the upstream licence binds you separately from the uploader's flags. If you distribute a merged derivative under the Krea 2 Community License, you have to: name the derivative starting with "Krea"; ship a copy of the agreement to recipients; include the attribution notice; and state that you modified the Krea model. Civitai's own compliance is relaxed — most of the top Krea 2 checkpoints don't do the naming clause — but the licence text is unambiguous, so don't take the crowd's behaviour as permission.


    Bonus: make your merge self-describing without re-encoding 25 GB

    A merged checkpoint has no idea it's a merge. Six months later you'll be guessing which recipe produced it — I recovered mine by least-squares fitting the parents against the output over a few hundred million elements per block (per-block, not per-band: it found the real band edges, which is how I discovered the file matched a different spec than the one I'd assumed).

    You can skip that entirely. Safetensors is [8-byte LE header length][JSON header][tensor data], and each tensor's data_offsets are relative to the start of the data section — so they don't depend on the header's size. Growing the header shifts the data but leaves every offset valid. The tensor bytes are copied verbatim, never parsed or re-encoded.

    So write your merge recipe into __metadata__ in ~30 seconds per 25 GB file:

    1. Reserve header space, stream the tensor data across, then seek back and backfill. 2. os.replace() — never write in place. ComfyUI mmaps these files, and an in-place write corrupts a live mapping. 3. Verify key set, offsets and data size before swapping.

    Do that and probe_metadata.py answers for your bases the way it already does for your LoRAs.


    The checklist

    • All inputs true BF16 (dequant scaled-FP8 first; drop weight_scale + comfy_quant)
    • Key prefixes normalized per input; canonical key count verified (430 on Krea 2)
    • Bands 0-3 / 4-13 / 14-20 / 21-27 — not 0-9/10-20/21-27
    • Identity weights only in 4–13
    • txtfusion pinned explicitly to the parent whose composition you want
    • Every input's allowDerivatives / SellMerge read individually
    • Merge streamed one tensor at a time (two BF16 unets + output do not fit in 32 GB VRAM)
    • Merged artifact screened on your production recipe
    • Base-only AND with-LoRA arms both rendered
    • Content-compliance arm included if you baked anything
    • __metadata__ written with the recipe

    The open question

    The single finding I'd most like someone to contradict is #1. Everything I've measured says a merge lands off the line between its parents rather than on it — sometimes usefully, never predictably.

    If you have a merge that measurably beat both parents on your own metric, I want the recipe. Especially the band spec and how you pinned txtfusion. Post it below and I'll reproduce it on Krea 2 and report the numbers either way.

    Archived from CivitAI · Updated October 12, 2026View source