Sign In

Master Anima | Simple tutorial for creating an Anima checkpoint merge!

Download

1 variant available

bf16 SafeTensor

BF16, good balance • 3.9 GB

Verified:

Type

Checkpoint Merge

Stats

332

Reviews

Published

Apr 16, 2026

Base Model

Anima

Hash

AutoV2
7C94A6A538

License:

Anima

Hi everyone,

Merging checkpoints is pretty straightforward, but Anima takes it to another level. It only took me 3 minutes on this in Colab (relying just on RAM).

By the way, official Anima uses 'net', so if your models don't have those names in their keys, you'll have to change them (the second script does this for you). Actually, I've been working on another script to extract info from checkpoints, but these might be useful to you guys.

Of course, there are more custom merges or specialized finetunes out there that add characters or concepts, but that's the fun part of merging.

The only downside to Anima is that it's modular, and I prefer a single file. Even though combining them is possible, the best thing to do is figure out how to make the most out of them or learn from the process.


Checkpoint Analyzer:

# @title CHECKPOINT ANALYZER 
!pip install safetensors torch --quiet

import torch
from safetensors import safe_open
import os
from pathlib import Path

# === CONFIGURATION ===
folder = "/content/civitai_downloads"
BASE_NAME = "MASTER_ANIMA_V2_1.safetensors"
TOP_K = 5  # How many layers to show in the mutation ranking

files = [str(p) for p in Path(folder).glob("*.safetensors")]
files = [p for p in files if os.path.getsize(p) > 100_000_000]
files.sort()

# Find the base model
try:
    base_path = next(v for v in files if BASE_NAME in v)
    print(f"➤ Analyzing differences against BASE: {BASE_NAME}\n" + "="*60)
except StopIteration:
    raise ValueError(f"X Error! '{BASE_NAME}' not found.")

other_models = [p for p in files if p != base_path]

# === ADVANCED ANALYSIS ENGINE ===
for target_path in other_models:
    target_name = os.path.basename(target_path)
    print(f"\n➤ Analyzing: {target_name} ...")

    sum_similarity = 0.0
    sum_absolute_difference = 0.0
    common_layers = 0

    # List to save the layer mutation log
    differences_by_layer = []

    with safe_open(base_path, framework="pt", device="cpu") as f_base, \
         safe_open(target_path, framework="pt", device="cpu") as f_target:

        keys_base = set(f_base.keys())
        keys_target = set(f_target.keys())
        common_keys = keys_base.intersection(keys_target)
        missing_layers = len(keys_base) - len(common_keys)

        # DNA Detector (To hunt impostors)
        if len(common_keys) == 0:
            print("   X Incompatible architectures. No common layers.")
            base_example = list(keys_base)[:1][0] if keys_base else "None"
            target_example = list(keys_target)[:1][0] if keys_target else "None"
            print(f"      ➤ Base DNA: Starts with '{base_example.split('.')[0]}...'")
            print(f"      ➤ {target_name} DNA: Starts with '{target_example.split('.')[0]}...'")
            continue

        for key in common_keys:
            # Lazy extraction
            t_base = f_base.get_tensor(key).to(torch.float32).flatten()
            t_target = f_target.get_tensor(key).to(torch.float32).flatten()

            if t_base.shape[0] > 10:
                similarity = torch.nn.functional.cosine_similarity(t_base, t_target, dim=0).item()
                difference = torch.abs(t_base - t_target).mean().item()

                sum_similarity += similarity
                sum_absolute_difference += difference
                common_layers += 1

                # Save the layer in the log
                differences_by_layer.append((key, difference, similarity))

            del t_base, t_target

    # --- PRINT RESULTS ---
    if common_layers > 0:
        average_similarity = (sum_similarity / common_layers) * 100
        average_difference = sum_absolute_difference / common_layers

        print(f"   ➤ Global Structural Similarity: {average_similarity:.2f}%")
        print(f"   ➤ Mean Absolute Error (MAE): {average_difference:.6f}")

        if missing_layers > 0:
            print(f"   ➤ Warning: The model is missing {missing_layers} tensors compared to the base.")

        # Sort the list by difference (descending)
        differences_by_layer.sort(key=lambda x: x[1], reverse=True)

        print(f"\n   ➤ TOP {TOP_K} MOST MUTATED LAYERS (Where the training was concentrated):")
        for i in range(min(TOP_K, len(differences_by_layer))):
            key, diff, sim = differences_by_layer[i]
            # Shorten the name if it is ridiculously long
            short_key = key if len(key) < 55 else "..."+key[-52:]
            print(f"      {i+1}. {short_key}")
            print(f"         ↳ Deviation: {diff:.5f} | Similarity: {sim*100:.2f}%")

        # Verdict
        print("\n   ➤ VERDICT:")
        if average_similarity > 99.5:
            print("      Practically a CLONE. Minimal statistical changes.")
        elif average_similarity > 95.0:
            print("      Light Finetune. Modifies visual style while preserving base logic.")
        else:
            print("      Heavy Finetune. Intensive training that deeply alters the model.")

Low RAM Optimized Merge Script (Free Colab):

# @title LOW RAM OPTIMIZED MERGE SCRIPT (Free Colab)
!pip install safetensors torch --quiet

import torch
from safetensors import safe_open
from safetensors.torch import save_file
import os
from pathlib import Path
import gc

# === YOUR FOLDER & OUTPUT ===
folder = "/content/civitai_downloads"  # ← Change if needed
final_output = "/content/MASTER_ANIMA_V2.safetensors"

# === PARAMETERS ===
USE_ADD_DIFFERENCE = True
SCALE_ALPHA = 0.35  # A slightly lower alpha (0.3 or 0.35) is better when mixing so many models
BASE_NAME = "landuoAnima_v011.safetensors" # ← Put the exact name here

files = [str(p) for p in Path(folder).glob("*.safetensors")]
files = [p for p in files if os.path.getsize(p) > 100_000_000]
files.sort()

# Automatically find the index of your official V3
try:
    BASE_INDEX = next(i for i, v in enumerate(files) if BASE_NAME in v)
    print(f"➤ Base model '{BASE_NAME}' found at index {BASE_INDEX}")
except StopIteration:
    raise ValueError(f"➤ Error! '{BASE_NAME}' not found in the folder.")

if len(files) < 2:
    raise ValueError("You need at least 2 models.")

# 1. Load ONLY the final recipe into RAM (~6GB instead of 18GB)
print(f"Loading base recipe into RAM: {os.path.basename(files[BASE_INDEX])}")
recipe_tensors = {}
# safe_open only extracts what we ask for, does not load everything at once
with safe_open(files[BASE_INDEX], framework="pt", device="cpu") as f_base:
    for key in f_base.keys():
        recipe_tensors[key] = f_base.get_tensor(key)

others = [p for i, p in enumerate(files) if i != BASE_INDEX]

# 2. Process the other models 'reading from disk' tensor by tensor
for p in others:
    print(f"  + Merging layer by layer: {os.path.basename(p)}...")

    # Open files in read mode without uploading them entirely into RAM
    with safe_open(p, framework="pt", device="cpu") as f_m, \
         safe_open(files[BASE_INDEX], framework="pt", device="cpu") as f_base_disk:

        for key in f_m.keys():
            # Only process if the layer exists in all models
            if key in recipe_tensors and key in f_base_disk.keys():

                # Extract ONLY the current layer into RAM (weighs very little)
                t_m = f_m.get_tensor(key).to(torch.float32)
                t_base = f_base_disk.get_tensor(key).to(torch.float32)
                t_recipe = recipe_tensors[key].to(torch.float32)

                if USE_ADD_DIFFERENCE:
                    t_recipe = t_recipe + SCALE_ALPHA * (t_m - t_base)
                else:
                    t_recipe = t_recipe * (1.0 - SCALE_ALPHA) + t_m * SCALE_ALPHA

                # Overwrite in the recipe with the original format and free temporary memory
                recipe_tensors[key] = t_recipe.to(recipe_tensors[key].dtype)
                del t_m, t_base, t_recipe

    # Force RAM garbage collection just in case
    gc.collect()

print(f"\nSaving to: {os.path.basename(final_output)}")
save_file(recipe_tensors, final_output)
print("Merge successful! We survived the free Colab RAM.")

Final Translation Script (Removes net): V2 (Model) + V2_1 (Net) | HYBRID:

# @title TRANSLATOR SCRIPT (MERGE)
!pip install safetensors torch --quiet

import torch
from safetensors import safe_open
from safetensors.torch import save_file
import os

# === PATH CONFIGURATION ===
base_model_names = "/content/civitai_downloads/MASTER_ANIMA_V2.safetensors"
net_content_model = "/content/civitai_downloads/MASTER_ANIMA_V2_1.safetensors"

hybrid_output = "/content/ANIMA_ULTIMATE_HYBRID.safetensors"
ALPHA = 0.5

def clean_name(key):
    # Step 1: Remove known network prefixes
    for prefix in ["model.", "net.", "network_aloha.", "base_model."]:
        if key.startswith(prefix):
            key = key[len(prefix):]

    # Step 2: Remove the clutter of 'diffusion_model.' if it exists
    if key.startswith("diffusion_model."):
        key = key[len("diffusion_model."):]

    return key

print("➤ Starting Hybrid Merge with Deep Cleaning...")

# 1. Map the content of the Official (V2_1)
content_map = {}
with safe_open(net_content_model, framework="pt", device="cpu") as f_src:
    for k in f_src.keys():
        content_map[clean_name(k)] = k

# 2. Merge using the names of the Rebel (V2) as a mold
hybrid_recipe = {}
with safe_open(base_model_names, framework="pt", device="cpu") as f_base, \
     safe_open(net_content_model, framework="pt", device="cpu") as f_src_disk:

    name_keys = list(f_base.keys())
    print(f"➤ Base layers detected: {len(name_keys)}")

    merged_count = 0

    for k_base in name_keys:
        layer_dna = clean_name(k_base)

        # Now it should find the common DNA
        if layer_dna in content_map:
            k_src = content_map[layer_dna]

            t_base = f_base.get_tensor(k_base).to(torch.float32)
            t_src = f_src_disk.get_tensor(k_src).to(torch.float32)

            if t_base.shape == t_src.shape:
                t_final = (t_base * (1 - ALPHA)) + (t_src * ALPHA)
                hybrid_recipe[k_base] = t_final.to(torch.bfloat16)
                merged_count += 1
            else:
                hybrid_recipe[k_base] = t_base.to(torch.bfloat16)

            del t_base, t_src
        else:
            hybrid_recipe[k_base] = f_base.get_tensor(k_base).to(torch.bfloat16)

    print(f"➤ Hybrid Finished!")
    print(f"   ➤ Layers translated and merged successfully: {merged_count}")

# 3. Save
print(f"➤ Saving to: {hybrid_output}...")
save_file(hybrid_recipe, hybrid_output)
print("➤ THE ULTIMATE HYBRID IS BORN.")