Download
1 variant available
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.")


